1. 项目概述
最近在做一个需要树莓派和STM32协同工作的项目,两者之间的通信方案选择了最经典的串口通信。虽然网上有不少相关资料,但在实际调试过程中还是踩了不少坑。今天就把整个实现过程整理成笔记,重点分享那些官方文档里不会告诉你的实战经验。
串口通信作为嵌入式开发中最基础的通信方式之一,看似简单,但要实现稳定可靠的双向通信,需要注意的细节还真不少。特别是当树莓派和STM32这两个不同架构的设备需要通信时,从硬件连接到软件配置,每一步都可能成为调试路上的绊脚石。
2. 硬件准备与连接
2.1 硬件选型建议
树莓派端:我使用的是树莓派4B,其实3B+及以上型号都可以。树莓派5B的串口配置稍有不同,需要注意GPIO引脚定义的变化。
STM32端:选用的是STM32F103RCT6,也就是常说的"蓝莓派"开发板。这个型号性价比高,资源丰富,特别适合学习使用。如果你用的是其他型号,比如STM32F407,原理也是一样的,只是外设名称可能不同(如USART改为UART)。
连线材料:
- 杜邦线:建议使用优质镀金杜邦线,劣质线材可能导致通信不稳定
- USB-TTL模块:推荐CH340G或CP2102芯片的,用于调试STM32串口
2.2 硬件连接详解
正确的硬件连接是通信成功的第一步,这里有几个关键点需要注意:
-
交叉连接:树莓派的TX引脚要接STM32的RX引脚,树莓派的RX接STM32的TX。新手最容易犯的错误就是TX-TX、RX-RX直连,这样肯定无法通信。
-
共地连接:两边的GND引脚必须连接在一起,这是很多通信问题的根源。没有共地参考,信号电平就无法正确识别。
-
引脚对应关系:
- 树莓派4B的默认串口引脚:
- TXD (GPIO14) - 第8引脚
- RXD (GPIO15) - 第10引脚
- GND - 第6/9/14/20/25等任意接地引脚
- STM32F103的USART1引脚:
- PA9 (USART1_TX)
- PA10 (USART1_RX)
- 树莓派4B的默认串口引脚:
-
电平匹配:树莓派的GPIO是3.3V电平,STM32F103也是3.3V,所以可以直接连接。如果使用5V电平的MCU,需要加电平转换电路。
注意:有些树莓派教程会提到需要禁用蓝牙才能使用默认串口,但在我们的实现中使用了额外的串口,所以不需要修改系统默认配置。
3. 软件环境配置
3.1 树莓派端配置
树莓派端的核心是Python的pyserial库,但在这之前需要做一些基础配置:
bash复制# 1. 更新系统
sudo apt update
sudo apt upgrade -y
# 2. 安装Python环境(Raspberry Pi OS默认已安装Python3)
sudo apt install python3-pip
# 3. 安装串口库
pip install pyserial
# 4. 添加用户到dialout组(获取串口访问权限)
sudo usermod -a -G dialout pi
# 5. 查看可用串口
ls /dev/ttyAMA*
在我的配置中,使用的是树莓派4B的额外串口(ttyAMA2),所以不需要禁用蓝牙。如果你使用默认串口(ttyAMA0),可能需要修改/boot/config.txt文件。
3.2 STM32开发环境
STM32端的开发主要依赖以下工具:
-
STM32CubeMX:用于图形化配置外设和生成初始化代码
- 版本:建议使用6.x及以上版本
- 安装时记得勾选对应系列的HAL库
-
开发IDE:
- Keil MDK-ARM:需要注册,有32K代码限制
- STM32CubeIDE:免费,基于Eclipse,适合初学者
- PlatformIO:跨平台,适合VSCode用户
-
驱动支持:
- ST-Link驱动:用于程序下载和调试
- USB转串口驱动:如CH340、CP2102等
4. STM32CubeMX详细配置
4.1 工程创建与时钟配置
- 新建工程,选择正确的芯片型号(STM32F103RCT6)
- 进入Clock Configuration界面:
- HSE选择Crystal/Ceramic Resonator
- PLL Source选择HSE
- PLL Mulitplier设为x9(72MHz系统时钟)
- 系统时钟源选择PLLCLK
- APB1 Prescaler设为/2(36MHz)
- APB2 Prescaler设为/1(72MHz)
经验分享:时钟配置不正确会导致串口波特率计算错误,表现为接收乱码。如果遇到乱码问题,首先检查时钟树配置是否正确。
4.2 USART1参数配置
-
在Connectivity下选择USART1
-
模式选择Asynchronous(异步模式)
-
参数设置:
- Baud Rate: 115200
- Word Length: 8 Bits
- Parity: None
- Stop Bits: 1
- Over Sampling: 16 Samples
-
NVIC Settings中使能USART1全局中断
- Preemption Priority: 0
- Sub Priority: 0
4.3 生成代码设置
-
Project Manager标签页:
- Toolchain/IDE选择你使用的开发环境
- 勾选"Generate peripheral initialization as a pair of '.c/.h' files"
-
点击Generate Code生成工程
5. 核心代码实现
5.1 STM32中断接收实现
STM32端的核心在于中断接收的实现,这里有几个关键点需要注意:
-
中断接收初始化:
在MX_USART1_UART_Init()函数后,需要手动启动中断接收:c复制HAL_UART_Receive_IT(&huart1, &rx_data, 1); -
中断回调函数:
c复制void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 处理接收到的数据 if (rx_data == '\n' || rx_data == '\r') { if (rx_index > 0) { rx_buffer[rx_index] = '\0'; command_received = 1; } rx_index = 0; } else if (rx_index < RX_BUFFER_SIZE - 1) { rx_buffer[rx_index++] = rx_data; } // 必须重新启动中断接收 HAL_UART_Receive_IT(&huart1, &rx_data, 1); } } -
指令处理函数:
c复制void process_command(void) { if (command_received) { char response[128]; snprintf(response, sizeof(response), "Received: %s\r\n", rx_buffer); HAL_UART_Transmit(&huart1, (uint8_t*)response, strlen(response), 100); command_received = 0; memset(rx_buffer, 0, sizeof(rx_buffer)); } }
5.2 树莓派Python脚本优化
原始的Python脚本可以进一步优化,增加以下功能:
-
自动检测可用串口:
python复制def find_serial_port(): ports = serial.tools.list_ports.comports() for port in ports: if "ttyAMA" in port.device: return port.device return None -
带超时的读取函数:
python复制def read_with_timeout(ser, timeout=1.0): start_time = time.time() data = b'' while time.time() - start_time < timeout: if ser.in_waiting > 0: data += ser.read(ser.in_waiting) if b'\n' in data: # 检测到结束符 break time.sleep(0.01) return data.decode('utf-8', errors='ignore') -
指令队列处理:
python复制class CommandProcessor: def __init__(self, ser): self.ser = ser self.command_queue = [] def send_command(self, cmd, expect_response=True, timeout=1.0): self.ser.write(cmd.encode('utf-8') + b'\n') if expect_response: return read_with_timeout(self.ser, timeout) return None
6. 调试技巧与问题排查
6.1 常见问题解决方案
-
乱码问题:
- 检查两端波特率是否一致
- 确认STM32时钟配置正确(72MHz)
- 确保硬件连接正确,特别是共地
-
只能接收单字节:
- 检查是否在回调函数中重新启动了中断接收
- 确认NVIC中断优先级设置正确
-
通信不稳定:
- 尝试降低波特率(如改为9600测试)
- 检查杜邦线连接是否牢固
- 在TX/RX线上加10K上拉电阻
6.2 调试工具推荐
- 逻辑分析仪:如Saleae,可以抓取串口信号波形
- 串口调试助手:
- Windows: SecureCRT, Putty
- Linux: minicom, screen
- 跨平台: CoolTerm
- STM32 ST-Link Utility:可以查看STM32寄存器和内存状态
6.3 性能优化建议
-
缓冲区管理:
- 使用环形缓冲区替代线性缓冲区
- 实现双缓冲机制减少数据拷贝
-
协议设计:
- 添加帧头帧尾(如0xAA,0x55)
- 包含校验和(如CRC8)
- 实现简单的协议版本控制
-
错误处理:
- 添加超时重传机制
- 实现心跳包检测连接状态
- 记录错误日志便于分析
7. 项目扩展思路
基础通信实现后,可以考虑以下扩展方向:
-
协议封装:
- 实现Modbus RTU协议
- 设计自定义二进制协议提高效率
-
多设备通信:
- 使用RS485总线连接多个STM32
- 实现设备地址识别和广播功能
-
数据可视化:
- 在树莓派上运行Web服务展示数据
- 使用Matplotlib实时绘制传感器数据
-
远程监控:
- 通过MQTT将数据上传到云平台
- 实现手机APP远程监控和控制
在实际项目中,我使用这种通信方式成功实现了树莓派作为主控制器,STM32作为下位机的分布式控制系统。树莓派负责数据处理和网络通信,STM32负责实时控制和数据采集,两者各司其职,发挥各自优势。