STM32F103C8T6三串口实战:从LED控制到数据回传(附完整代码)
在嵌入式开发中,串口通信是最基础也最实用的功能之一。STM32F103C8T6作为一款性价比极高的Cortex-M3内核微控制器,内置了三个独立的USART模块,能够同时处理多路串口通信任务。本文将带你从硬件连接到代码实现,完整掌握如何利用这三个串口实现LED控制和数据回传功能。
1. 硬件准备与连接
1.1 所需材料清单
- STM32F103C8T6最小系统板(蓝色药丸开发板)
- USB转TTL模块(建议准备2-3个)
- LED灯及限流电阻(或直接使用开发板上的用户LED)
- 杜邦线若干
- ST-Link下载器(用于程序烧录)
1.2 引脚连接说明
STM32F103C8T6的三个串口对应引脚如下:
| 串口 | TX引脚 | RX引脚 | 复用功能重映射 |
|---|---|---|---|
| USART1 | PA9 | PA10 | 可重映射到PB6/PB7 |
| USART2 | PA2 | PA3 | 无重映射 |
| USART3 | PB10 | PB11 | 可重映射到PC10/PC11 |
实际连接时,需要注意:
- TTL模块的TX接MCU的RX,RX接MCU的TX
- 共地连接必不可少
- 若使用开发板上的LED,通常连接在PC13引脚
2. 开发环境搭建
2.1 工具链配置
推荐使用以下开发环境组合:
- Keil MDK-ARM:5.23及以上版本
- STM32CubeMX:6.0+用于初始化代码生成
- 串口调试助手:推荐使用SecureCRT或Putty
2.2 工程创建步骤
- 打开STM32CubeMX,选择STM32F103C8T6型号
- 配置时钟树,将系统时钟设置为72MHz
- 使能三个USART模块:
- USART1:异步模式,波特率9600
- USART2:异步模式,波特率9600
- USART3:异步模式,波特率9600
- 生成代码时选择MDK-ARM工具链
// 示例:CubeMX生成的USART初始化代码片段 void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 9600; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); } }3. 三串口功能实现
3.1 串口1:数据回显功能
串口1将实现最基本的回显功能,即接收到什么数据就原样返回。我们使用中断方式接收数据。
// 串口1中断回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1){ // 将接收到的字符发回 HAL_UART_Transmit(&huart1, &rx_data, 1, 100); // 重新开启接收中断 HAL_UART_Receive_IT(&huart1, &rx_data, 1); } }提示:在实际项目中,建议添加接收超时处理和缓冲区管理,避免数据丢失。
3.2 串口2:LED控制功能
串口2将接收特定指令控制LED状态。我们定义以下协议:
- 发送"ON":打开LED
- 发送"OFF":关闭LED
- 其他指令:返回"Invalid Command"
// 串口2命令处理函数 void ProcessUSART2Command(uint8_t *cmd) { if(strcmp((char*)cmd, "ON") == 0){ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); HAL_UART_Transmit(&huart2, (uint8_t*)"LED ON\r\n", 8, 100); } else if(strcmp((char*)cmd, "OFF") == 0){ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); HAL_UART_Transmit(&huart2, (uint8_t*)"LED OFF\r\n", 9, 100); } else{ HAL_UART_Transmit(&huart2, (uint8_t*)"Invalid Command\r\n", 16, 100); } }3.3 串口3:数据采集与传输
串口3将模拟一个数据采集终端,定期发送传感器数据(这里用ADC值模拟)。
// 模拟数据采集任务 void DataAcquisitionTask(void) { static uint32_t adcValue = 0; char buffer[50]; adcValue = HAL_ADC_GetValue(&hadc1); // 假设已配置ADC int len = sprintf(buffer, "ADC Value: %lu\r\n", adcValue); HAL_UART_Transmit(&huart3, (uint8_t*)buffer, len, 100); HAL_Delay(1000); // 每秒发送一次 }4. 完整代码实现与优化
4.1 工程文件结构
├── Core │ ├── Src │ │ ├── main.c │ │ ├── stm32f1xx_it.c │ │ ├── usart.c │ ├── Inc │ │ ├── main.h │ │ ├── stm32f1xx_it.h │ │ ├── usart.h ├── Drivers ├── MDK-ARM4.2 关键代码片段
主循环中整合三个串口的功能:
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_USART2_UART_Init(); MX_USART3_UART_Init(); MX_ADC1_Init(); // 启动串口接收中断 HAL_UART_Receive_IT(&huart1, &usart1_rx_data, 1); HAL_UART_Receive_IT(&huart2, &usart2_rx_data, 1); while (1) { DataAcquisitionTask(); // 串口3数据发送 ProcessSerialCommands(); // 处理串口1和2的命令 HAL_Delay(100); } }4.3 常见问题排查
无法接收数据:
- 检查TX/RX接线是否交叉连接
- 确认波特率设置一致
- 测量串口引脚是否有信号
数据乱码:
- 检查时钟配置是否正确(特别是外部晶振设置)
- 验证串口初始化参数(数据位、停止位、校验位)
中断不触发:
- 确认NVIC中断已使能
- 检查中断优先级设置
- 确保中断服务函数名称正确
5. 进阶应用场景
5.1 多协议通信方案
利用三个串口实现不同的通信协议:
- USART1:Modbus RTU协议
- USART2:自定义二进制协议
- USART3:JSON格式数据传输
5.2 数据分流处理
通过DMA实现高效数据传输,减轻CPU负担:
// DMA串口接收配置 HAL_UART_Receive_DMA(&huart1, usart1_rx_buffer, BUFFER_SIZE); HAL_UART_Receive_DMA(&huart2, usart2_rx_buffer, BUFFER_SIZE);5.3 硬件流控制
当传输速率较高(如115200以上)或距离较远时,建议启用硬件流控制:
huart1.Init.HwFlowCtl = UART_HWCONTROL_RTS_CTS;6. 性能优化技巧
环形缓冲区应用:
typedef struct { uint8_t buffer[256]; uint16_t head; uint16_t tail; } RingBuffer;中断优先级管理:
- 给关键串口分配更高优先级
- 避免在中断中进行耗时操作
低功耗优化:
- 空闲时进入STOP模式
- 通过串口唤醒MCU
在实际项目中,三串口的协同工作能力可以极大扩展STM32的应用场景。我曾在一个工业控制器项目中使用USART1连接HMI,USART2连接PLC,USART3连接无线模块,三者各司其职又相互配合,实现了稳定可靠的控制系统。