Jetson Nano与STM32串口通信保姆级教程:从Python脚本到HAL库配置(含完整代码)
Jetson Nano与STM32串口通信实战指南:从环境搭建到双向数据流
1. 边缘计算与嵌入式通信的黄金组合
在智能硬件开发领域,Jetson Nano作为边缘计算节点的代表,与STM32这类经典MCU的协同工作已经成为物联网项目的标准配置。这种组合充分利用了Nano的AI处理能力和STM32的实时控制特性,而串口通信则是两者之间最直接、可靠的桥梁。
我最近在一个智能农业监控项目中采用了这套方案,Nano负责处理摄像头采集的图像数据并运行目标检测模型,STM32则控制灌溉系统和传感器阵列。两者通过串口交换指令和状态信息,整个系统响应延迟控制在毫秒级。这种架构既避免了将所有计算集中在单一处理器上,又保证了关键控制的实时性。
2. Jetson Nano端环境配置
2.1 硬件连接与端口确认
Jetson Nano提供了多个串口选项,最常用的是/dev/ttyTHS1(40针GPIO接头上的第8和第10引脚)。连接前需要确认:
- 引脚对应关系:
Jetson Nano引脚 功能 STM32连接点 8 (TX) 发送 UART2_RX 10 (RX) 接收 UART2_TX 6 (GND) 地线 GND
注意:务必先断开电源再进行硬件连接,错误的接线可能损坏设备
2.2 软件环境准备
在Jetson Nano上执行以下命令安装必要组件:
sudo apt update sudo apt install python3-pip pip3 install pyserial验证串口设备权限:
ls -l /dev/ttyTHS1如果显示crw-rw----,需要将当前用户加入dialout组:
sudo usermod -a -G dialout $USER sudo reboot3. STM32端通信框架搭建
3.1 CubeMX基础配置
在CubeMX中启用USART2:
- 模式:Asynchronous
- 波特率:115200
- 字长:8 Bits
- 停止位:1
- 校验位:None
启用DMA控制器:
- 添加USART2_RX的DMA流
- 模式:Circular
- 优先级:Medium
生成代码时勾选"Generate peripheral initialization as a pair of .c/.h files"
3.2 中断驱动接收实现
修改stm32f4xx_it.c添加接收中断处理:
void USART2_IRQHandler(void) { if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE)) { uint8_t ch = (uint8_t)(huart2.Instance->DR & 0xFF); // 自定义缓冲区处理逻辑 custom_rx_buffer[rx_index++] = ch; if(ch == '\r' || rx_index >= BUF_SIZE-1) { rx_ready = 1; rx_index = 0; } } HAL_UART_IRQHandler(&huart2); }4. 双向通信协议设计
4.1 数据帧结构优化
建议采用以下帧格式提升通信可靠性:
[起始符][长度][数据][校验][结束符] 0xAA 1字节 N字节 1字节 0x55Python端封帧函数示例:
def build_frame(data): length = len(data) checksum = sum(data) & 0xFF return bytes([0xAA, length]) + data + bytes([checksum, 0x55])4.2 流控制与错误处理
在STM32端实现自动重传机制:
#define MAX_RETRY 3 void send_with_retry(UART_HandleTypeDef *huart, uint8_t *data, uint16_t size) { uint8_t retry = 0; HAL_StatusTypeDef status; do { status = HAL_UART_Transmit(huart, data, size, 1000); if(status != HAL_OK) { retry++; HAL_Delay(10); } } while(status != HAL_OK && retry < MAX_RETRY); }5. 高级调试技巧
5.1 逻辑分析仪抓包分析
当通信异常时,使用Saleae逻辑分析仪可以准确捕获物理层信号。重点关注:
- 波特率实际值误差(应<3%)
- 起始位/停止位电平
- 字节间隔时间
5.2 Python端调试工具
推荐使用serial.tools.miniterm进行手动测试:
python3 -m serial.tools.miniterm /dev/ttyTHS1 115200在交互界面中可以:
- 直接发送十六进制数据(Ctrl+T Ctrl+X进入hex模式)
- 显示接收数据的ASCII和hex双格式
- 记录通信日志到文件
6. 性能优化实战
6.1 DMA双缓冲技术
在CubeMX中配置USART2_RX使用双缓冲DMA:
// 在main.c中添加 uint8_t dma_buffer1[64]; uint8_t dma_buffer2[64]; HAL_UARTEx_ReceiveToIdle_DMA(&huart2, dma_buffer1, sizeof(dma_buffer1)); __HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT);处理接收完成回调:
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if(huart == &huart2) { // 处理dma_buffer1数据 // 重新启动接收 HAL_UARTEx_ReceiveToIdle_DMA(&huart2, dma_buffer2, sizeof(dma_buffer2)); } }6.2 Python端多线程处理
创建独立的接收线程避免阻塞主程序:
import threading class SerialManager: def __init__(self, port): self.ser = serial.Serial(port, 115200) self.rx_queue = queue.Queue() self.running = True self.thread = threading.Thread(target=self._rx_thread) self.thread.start() def _rx_thread(self): while self.running: if self.ser.in_waiting: data = self.ser.read(self.ser.in_waiting) self.rx_queue.put(data) def get_data(self): return self.rx_queue.get_nowait() if not self.rx_queue.empty() else None7. 常见问题解决方案
7.1 数据丢失问题排查流程
- 检查硬件连接
- 确认TX-RX交叉连接
- 测量GND之间阻抗应<1Ω
- 验证波特率一致性
- 双方配置完全相同的波特率
- 使用示波器测量实际波特率
- 测试最小系统
- 简化收发程序到最基本功能
- 逐步添加复杂功能
7.2 编码问题处理
当传输非ASCII字符时,建议统一使用UTF-8编码:
Python端:
data = "中文测试".encode('utf-8') ser.write(data)STM32端:
// 使用支持UTF-8的库如u8g2进行解码 u8g2_DrawUTF8(&u8g2, x, y, (char*)rx_buffer);8. 项目实战:环境监测系统
8.1 系统架构设计
Jetson Nano (边缘计算节点) │ ├─ 运行YOLOv5目标检测 ├─ 处理摄像头数据 └─ 通过串口发送控制指令 │ ▼ STM32F4 (下位机控制器) ├─ 接收并解析指令 ├─ 采集温湿度传感器数据 └─ 控制继电器和执行机构8.2 关键代码片段
Nano端传感器数据处理:
def process_sensor_data(raw): # 数据包示例: TEMP:25.6,HUM:60.2 try: parts = raw.decode('utf-8').split(',') temp = float(parts[0].split(':')[1]) hum = float(parts[1].split(':')[1]) return {'temperature': temp, 'humidity': hum} except: return NoneSTM32端指令解析:
typedef enum { CMD_SET_RELAY = 0x10, CMD_GET_SENSOR = 0x20, CMD_SET_PWM = 0x30 } CommandType; void parse_command(uint8_t *data) { CommandType cmd = (CommandType)data[0]; switch(cmd) { case CMD_SET_RELAY: HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, data[1] ? GPIO_PIN_SET : GPIO_PIN_RESET); break; // 其他命令处理... } }