1. STM32F103C8T6多串口通信基础认知STM32F103C8T6作为经典的Cortex-M3内核微控制器其内置的三个独立USART模块让多设备通信变得简单。实际项目中我经常需要同时连接GPS模块、蓝牙设备和调试终端这时候多串口配置就成了基本功。与常见的51单片机不同STM32的串口功能更强大支持DMA传输、硬件流控制和灵活的中断配置。这块蓝色小开发板上的三个串口引脚分布很有规律USART1PA9(TX)、PA10(RX) - 唯一挂载在APB2高速总线上的串口USART2PA2(TX)、PA3(RX) - 注意它属于APB1低速总线USART3PB10(TX)、PB11(RX) - 同样在APB1总线第一次使用时容易忽略时钟使能的差异USART1需要开启RCC_APB2PeriphClockCmd而USART2/3则使用RCC_APB1PeriphClockCmd。我就曾因为忘记开启GPIOB时钟导致USART3死活不工作调试了半天才发现问题。2. 三串口完整配置实战2.1 硬件初始化关键步骤配置串口就像组装乐高积木必须按步骤进行。以USART1为例完整的初始化应该包含// 时钟使能最容易遗漏的步骤 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // GPIO配置 GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; // TX引脚 GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; // RX引脚 GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; // 浮空输入 GPIO_Init(GPIOA, GPIO_InitStructure); // 中断优先级设置NVIC配置 NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_Init(NVIC_InitStructure); // 串口参数配置 USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_Init(USART1, USART_InitStructure); // 使能中断和串口 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_Cmd(USART1, ENABLE);实测中发现波特率误差超过3%就会导致通信失败。建议使用示波器测量实际波特率特别是当使用非标准晶振时。我曾经用8MHz晶振配置115200波特率结果因为分频系数舍入误差导致通信异常。2.2 中断服务函数优化技巧原始代码中的中断处理有个潜在风险没有考虑缓冲区溢出保护。改进后的版本应该这样写void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE)){ uint8_t ch USART_ReceiveData(USART1); // 环形缓冲区处理 uint16_t tmp (USART1_RX_STA 1) % USART1_REC_LEN; if(tmp ! USART1_RX_INDEX){ // 防止溢出 USART1_RX_BUF[USART1_RX_STA] ch; USART1_RX_STA tmp; } // 自定义协议处理 if(ch 0x0A last_char 0x0D){ process_complete_packet(); } last_char ch; } }对于多串口系统中断优先级设置很关键。建议将最频繁通信的串口设为最高优先级比如USART1PreemptionPriority0最高USART2PreemptionPriority1USART3PreemptionPriority23. 多串口数据收发实战3.1 轮询与中断模式对比在main函数中我更喜欢用状态机的方式处理数据接收while(1){ // 非阻塞式处理三个串口 handle_uart1(); handle_uart2(); handle_uart3(); // 系统心跳等其它任务 system_tick(); } void handle_uart1(void) { if(USART1_RX_STA 0x8000){ printf(UART1 Received: %.*s\r\n, USART1_RX_STA 0x3FFF, USART1_RX_BUF); // 清空缓冲区 USART1_RX_STA 0; } }实测发现当三个串口同时高速通信时比如都设成115200波特率中断方式会占用大量CPU资源。这时候可以考虑降低不关键串口的波特率使用DMA传输特别适合固定长度数据采用硬件流控制RTS/CTS3.2 调试技巧与常见问题用逻辑分析仪抓取的串口波形显示当系统负载较重时可能会出现这样的问题字节间隔时间不稳定偶发性丢帧校验错误增加解决方法包括在中断服务函数中最先读取DR寄存器适当提升中断优先级增加硬件滤波电容针对电磁干扰有个坑我踩过多次USART2和USART3的时钟源是APB1最大36MHz而USART1挂载在APB272MHz。这意味着当使用高波特率时USART2/3需要更精确的时钟配置。4. 高级应用与性能优化4.1 DMA与串口配合实战对于需要高速传输的场景DMA是必备利器。配置步骤比普通中断复杂但能大幅降低CPU负载// DMA初始化 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)USART1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)uart1_rx_buffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize BUF_SIZE; DMA_Init(DMA1_Channel5, DMA_InitStructure); // 串口DMA使能 USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);注意DMA通道与串口的对应关系USART1_RX → DMA1_Channel5USART1_TX → DMA1_Channel4USART3_RX → DMA1_Channel34.2 低功耗设计技巧在电池供电项目中我这样优化串口功耗不用的串口完全关闭时钟接收时启用中断唤醒动态调整波特率低速模式时降低速率// 进入低功耗前配置 USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后恢复 SystemInit(); // 重新初始化时钟 USART_Cmd(USART1, ENABLE);实测电流可以从mA级降到μA级。特别注意唤醒后需要重新初始化时钟系统因为STOP模式会关闭高速时钟。