在嵌入式开发中,串口通信就像开发者的"听诊器",既能输出调试信息观察系统状态,又能与外部设备交换数据。对于STM32开发者而言,掌握USART模块的完整配置流程是从入门到进阶的关键一步。本文将基于正点原子的usart.c实现,带你从零构建一个同时支持printf调试输出和中断接收不定长数据的通信系统。
任何通信系统的起点都是正确的硬件配置。对于STM32F103的USART1模块,我们需要关注三个核心部分:时钟使能、GPIO配置和串口参数设置。
USART1位于APB2总线上,与GPIOA的时钟需要同时使能:
c复制RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
GPIO模式配置需要特别注意:
实际项目中,如果通信距离较远,建议为RX引脚添加外部上拉电阻,提高抗干扰能力
USART_InitTypeDef结构体包含所有关键参数:
| 参数 | 典型值 | 说明 |
|---|---|---|
| BaudRate | 115200 | 需与通信方严格一致 |
| WordLength | 8b | 7b/8b/9b可选 |
| StopBits | 1 | 1/0.5/1.5/2可选 |
| Parity | No | 奇/偶/无校验 |
| HardwareFlowControl | None | 硬件流控开关 |
| Mode | Tx+Rx | 收发模式选择 |
c复制USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
在嵌入式开发中,printf的价值不言而喻。通过重定向fputc函数,我们可以让这个强大的调试工具在串口上工作。
标准库中的printf最终会调用fputc输出字符。重写该函数即可改变输出方向:
c复制int fputc(int ch, FILE *f)
{
USART_SendData(USART1, (uint8_t)ch);
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
return ch;
}
关键点:必须等待TC(发送完成)标志置位,而非TXE(发送寄存器空)标志,否则连续调用printf可能导致数据覆盖
当printf无法正常工作时,可按以下步骤检查:
查询方式接收数据会大量占用CPU资源,而中断方式才是处理串口接收的正确姿势。
正点原子的USART_RX_STA设计堪称经典:
code复制bit15 | bit14 | bit13~0
------|---------|---------
完成标志 | 0x0D标志 | 数据长度
对应的中断服务程序逻辑流程:
c复制void USART1_IRQHandler(void)
{
uint8_t res;
if(USART_GetITStatus(USART1, USART_IT_RXNE)) {
res = USART_ReceiveData(USART1);
if((USART_RX_STA&0x8000)==0) {
if(USART_RX_STA&0x4000) {
if(res==0x0A) USART_RX_STA|=0x8000;
else USART_RX_STA=0;
} else {
if(res==0x0D) USART_RX_STA|=0x4000;
else {
USART_RX_BUF[USART_RX_STA&0X3FFF]=res;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))
USART_RX_STA=0;
}
}
}
}
}
在主循环中处理接收完成的数据:
c复制if(USART_RX_STA&0x8000) {
uint16_t len = USART_RX_STA&0x3FFF;
USART_RX_BUF[len] = '\0'; // 添加字符串结束符
printf("Received: %s\r\n", USART_RX_BUF);
USART_RX_STA = 0; // 准备下一次接收
}
对于高频数据接收,建议使用环形缓冲区:
c复制#define BUF_SIZE 256
typedef struct {
uint8_t buffer[BUF_SIZE];
uint16_t head;
uint16_t tail;
} RingBuffer;
void IRQHandler() {
uint8_t data = USART_ReceiveData(USART1);
uint16_t next = (rb->head + 1) % BUF_SIZE;
if(next != rb->tail) {
rb->buffer[rb->head] = data;
rb->head = next;
}
}
在最近的一个物联网网关项目中,采用DMA+环形缓冲的方案将串口吞吐量提升了5倍,同时CPU占用率从70%降至15%。关键点在于合理利用STM32的外设资源,让硬件完成大部分工作。