1. 项目背景与核心需求
在工业控制和物联网应用中,设备间的可靠数据通信是系统稳定运行的关键。STM32F103C8T6作为一款性价比极高的Cortex-M3内核微控制器,搭配无线串口模块构建的双机通信系统,能够满足大多数中低速数据传输场景的需求。我曾在一个智能农业监测项目中实际使用过这套方案,当时需要将分布在农田各处的传感器数据汇总到中央控制器,实测在200米范围内通信稳定性表现优秀。
这种方案的核心优势在于:
- 硬件成本低:STM32F103C8T6开发板价格通常在20元以内,搭配的无线串口模块(如HC-12)价格约30元
- 开发门槛低:利用标准库或HAL库可以快速搭建通信框架
- 灵活性强:既支持点对点通信,也可通过修改协议实现简单组网
2. 硬件选型与连接
2.1 核心器件清单
| 器件 | 型号 | 关键参数 |
|---|---|---|
| MCU | STM32F103C8T6 | 72MHz主频,64KB Flash,20KB RAM |
| 无线模块 | HC-12 | 433MHz频段,最大传输距离1km(空旷) |
| 电平转换 | CH340G | USB转TTL,用于调试 |
2.2 硬件连接示意图
[MCU1] USART1_TX(PA9) ──> HC-12_RXD [MCU1] USART1_RXD(PA10) <── HC-12_TXD [MCU1] GND ────────────┐ ├─> HC-12_GND [MCU2] GND ────────────┘实际布线时要注意:
- 无线模块天线要尽量远离MCU的晶振和高速信号线
- 每个HC-12模块的VCC端建议并联100μF电容
- 如果传输距离超过50米,建议单独给HC-12供电(5V/500mA以上)
3. 通信协议设计
3.1 数据帧结构
经过多次实测验证,我推荐采用以下帧格式:
0xAA 0x55 [长度] [命令字] [数据] [校验和] 0x0D 0x0A- 帧头:0xAA 0x55双字节,降低误触发概率
- 长度:数据域字节数(含命令字)
- 校验和:从长度字节开始累加到数据域末尾
3.2 错误处理机制
在工业现场常见的三种异常情况处理方案:
- 数据丢失:接收方在500ms内未收到完整帧,主动发送NAK(0x15)请求重传
- 校验错误:直接丢弃当前帧,不回复任何信息
- 数据错位:通过帧头检测自动重新对齐
这里有个实际踩过的坑:早期版本没有做帧头重对齐,当发生连续误码时会导致系统死锁。后来增加了以下处理代码:
// 在中断服务函数中 if(USART_ReceiveData(USART1) == 0xAA) { if(last_byte == 0xAA && current_byte == 0x55) { // 确认找到帧头 frame_start = 1; } last_byte = current_byte; }4. 软件实现详解
4.1 初始化关键代码
void USART1_Init(u32 baudrate) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 使能时钟(注意USART1在APB2) RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置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_Init(GPIOA, &GPIO_InitStructure); // 串口参数配置 USART_InitStructure.USART_BaudRate = baudrate; 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); // 使能接收中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); NVIC_EnableIRQ(USART1_IRQn); USART_Cmd(USART1, ENABLE); }4.2 中断服务函数优化
为了避免数据竞争,我采用了双缓冲机制:
#pragma pack(1) typedef struct { u8 buffer[2][256]; u8 wr_idx; u8 rd_idx; u8 length[2]; } USART_Buffer; #pragma pack() void USART1_IRQHandler(void) { static USART_Buffer rx_buf; u8 index = rx_buf.wr_idx; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { u8 data = USART_ReceiveData(USART1); // 简单的状态机实现协议解析 switch(parse_state) { case WAIT_HEADER: if(data == 0xAA) header_cnt++; else header_cnt = 0; if(header_cnt >= 2) parse_state = GET_LENGTH; break; case GET_LENGTH: rx_buf.length[index] = data; parse_state = GET_DATA; data_cnt = 0; break; // 其他状态处理... } if(++data_cnt >= rx_buf.length[index]) { rx_buf.wr_idx ^= 0x01; // 切换缓冲区 USART_ITConfig(USART1, USART_IT_RXNE, DISABLE); // 触发主循环处理 } } }5. 系统稳定性优化
5.1 抗干扰措施
- 软件滤波:对连续接收的相同数据包进行3取2表决
- 看门狗:独立看门狗(IWDG)和窗口看门狗(WWDG)同时启用
- 电源管理:在无线模块供电线路上增加π型滤波电路
5.2 实测性能数据
在郊区环境下的测试结果:
| 距离 | 波特率 | 丢包率 | 平均延时 |
|---|---|---|---|
| 50m | 9600 | 0.02% | 12ms |
| 200m | 4800 | 0.15% | 25ms |
| 500m | 2400 | 1.2% | 50ms |
6. 常见问题排查
问题1:通信距离突然缩短
- 检查天线是否完好
- 测量供电电压是否稳定
- 确认周围是否有新的干扰源
问题2:数据出现规律性错误
- 检查波特率误差(建议控制在2%以内)
- 确认双方MCU时钟源配置一致
- 检查地线连接是否可靠
问题3:通信完全中断
- 先用示波器检查TX引脚是否有波形
- 短接本地TX-RX测试自发自收
- 更换无线模块测试
记得在项目初期,我曾遇到一个诡异的通信时断时续问题,最后发现是电源线上的100nF去耦电容虚焊。这个经历让我深刻认识到硬件可靠性的重要性,现在每次焊接完板子都会用放大镜检查关键器件的焊点。