串口的核心配置步骤:
- 使能 GPIO 和串口时钟。
- 配置 TX/RX 引脚为复用功能。
- 配置串口波特率、数据位等核心参数。
- 使能串口外设。
- (可选)配置中断和 NVIC,实现异步收发。
- 实现基本的收发函数(或使用中断处理)。
以下是详细的配置要点:
1. 使能外设时钟
串口通信依赖于GPIO 时钟和串口外设时钟,需先在RCC
(复位和时钟控制)中使能:
- GPIO 时钟:根据串口使用的引脚所在端口(如 PA、PB 等)使能,例如
-
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)。
- 串口时钟:根据串口编号(如 USART1、USART2 等)使能,其中:
//USART1 挂载在APB2总线(高速),需用
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE)。
//USART2/3、UART4/5 挂载在APB1总线(低速),需用
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE)。
2. 配置 GPIO 引脚
串口通信需要至少两根引脚:发送端(TX) 和接收端(RX),需将其配置为复用推挽输出(TX)和浮空输入 / 上拉输入(RX):
- TX 引脚:复用功能推挽输出(保证能稳定输出高 / 低电平)。
- RX 引脚:浮空输入(适用于外部有上拉电阻的场景)或上拉输入(避免无信号时电平不稳定)。
GPIO_InitTypeDef GPIO_InitStructure;
// 配置TX引脚(PA9)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置RX引脚(PA10)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
// 或 GPIO_Mode_IPU; // 上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
3. 配置串口核心参数(USART_InitTypeDef)
通过USART_InitTypeDef
结构体配置串口的核心通信参数,包括:
- 波特率:如 9600、115200 等(需与通信设备一致)。
- 数据位:通常为 8 位(
USART_WordLength_8b
)。 - 停止位:通常为 1 位(
USART_StopBits_1
)。 - 校验位:无校验(
USART_Parity_No
)、奇校验或偶校验(要求数据帧(包括数据位)中 “1” 的总数为偶/奇数)。 - 硬件流控:通常关闭(
USART_HardwareFlowControl_None
),如需使用 RTS/CTS 则需额外配置引脚。 - 模式:使能发送(
USART_Mode_Tx
)和 / 或接收(USART_Mode_Rx
)。
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200; // 波特率115200
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8位数据
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1位停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 同时使能发送和接收
USART_Init(USART1, &USART_InitStructure); // 初始化USART1
4. 使能串口
配置完成后,需通过USART_Cmd
函数使能串口外设:
USART_Cmd(USART1, ENABLE); // 使能USART1
5. 中断配置(可选,用于异步收发)
若需要通过中断处理接收 / 发送(而非轮询),需额外配置:
- 使能串口中断:通过
USART_ITConfig
使能特定中断(如接收完成中断USART_IT_RXNE
)。 - 配置 NVIC:设置中断优先级,确保中断能被 CPU 响应。
NVIC_InitTypeDef NVIC_InitStructure;
// 使能USART1接收中断(RXNE:接收缓冲区非空)
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // USART1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
NVIC_Init(&NVIC_InitStructure);
之后需实现中断服务函数(如USART1_IRQHandler
),在中断中处理接收 / 发送逻辑。
6. 收发数据函数实现
- 发送数据:通过
USART_SendData
函数发送单个字节,需先判断发送缓冲区是否为空(USART_FLAG_TXE
)。 - 接收数据:通过
USART_ReceiveData
函数读取接收缓冲区数据,需先判断接收缓冲区是否非空(USART_FLAG_RXNE
)。
// 发送一个字节
void USART_SendByte(USART_TypeDef* USARTx, uint8_t data) {
while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET); // 等待发送缓冲区为空
USART_SendData(USARTx, data);
}
// 接收一个字节
uint8_t USART_ReceiveByte(USART_TypeDef* USARTx) {
while (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET); // 等待接收完成
return (uint8_t)USART_ReceiveData(USARTx);
}
关于通过串口打印
方法一:printf重定向【嵌入式学习笔记】---- printf重定向_printf 重定向 fputc putchar-CSDN博客
方法二:重新一个函数,结合va_list和vsprintf(关于printf,sprintf和vsprintf的区别见收藏)
对于传参中参数类型和个数不确定的格式转换,请使用 vsprintf。
1. printf是直接输出
2. sprintf是将发送格式化输出到指定数组。格式化用法与 printf一致,相当于把 printf输出的内容放入指定数组中。sprintf需要指定明确的可变参数的类型和个数(%d,%s...)
3. vsprintf算是对printf的封装,通过va_list 可以不用固定设置传入的可变参数,更为方便。
// 定义发送缓冲区,大小根据实际需求设置(例如200字节)
u8 USART1_TX_BUF[200]; // u8 通常是 unsigned char 的别名
void u1_printf(char *format,...)
/*
format:格式化字符串,例如 "Hello, %s! Age: %d\r\n"。
...:可变参数列表,对应 format 中的占位符(如 %s, %d)。
例如:调用 u1_printf("Hello, %s! Age: %d\r\n", "Alice", 25); 时:
格式化字符串:vsprintf 生成 "Hello, Alice! Age: 25\r\n" 并存入 USART1_TX_BUF。
*/
{
u16 i;
va_list list_data; //用于存储 可变参数的类型。需包含"stdarg.h"头文件
va_start(list_data, format); //将format后的 可变参数(如 %s, %d)存入list_data
vsprintf((char *)USART1_TX_BUF, format, list_data);//将根据format格式化的字符串保存到字符数组
va_end(list_data); //与va_start成对出现
for(i = 0; i < strlen((const char *)USART1_TX_BUF); i++) //将字符串按字节发送
{
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) != 1);
USART_SendData(USART1, USART1_TX_BUF[i]);
}
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != 1); //等发送完成
}