Jetson Nano与STM32串口通信实战指南:从硬件对接到Python代码优化
在嵌入式开发领域,Jetson Nano作为一款强大的边缘计算设备,经常需要与各种微控制器进行数据交互。而串口通信作为最基础、最可靠的通信方式之一,依然是大多数开发者的首选。本文将带您从零开始,一步步实现Jetson Nano与STM32之间的稳定串口通信,避开那些新手常踩的"坑"。
1. 硬件准备与连接
1.1 所需材料清单
在开始之前,请确保您已准备好以下硬件:
- Jetson Nano开发板(任何版本均可)
- STM32开发板(本文以STM32F4为例,但原理适用于其他系列)
- USB转TTL模块(用于调试,如CH340、CP2102等)
- 杜邦线若干(建议使用不同颜色区分功能)
- 万用表(非必须,但强烈推荐用于排查连接问题)
1.2 引脚连接详解
Jetson Nano的40针GPIO接口中包含多个串口,其中最常用的是/dev/ttyTHS1,对应引脚如下:
| Jetson Nano引脚 | STM32引脚 | 信号类型 | 颜色建议 |
|---|---|---|---|
| 6 (GND) | GND | 地线 | 黑色 |
| 8 (UART1_TX) | USART2_RX | 发送 | 绿色 |
| 10 (UART1_RX) | USART2_TX | 接收 | 蓝色 |
注意:连接时务必确保TX接RX,RX接TX,这是串口通信中最常见的错误之一。
1.3 电源考虑
虽然Jetson Nano和STM32可以共用电源,但建议:
- 对于高功率外设(如摄像头、电机等),使用独立电源供电
- 确保共地连接(GND相连),这是信号稳定的基础
- 使用万用表检查各连接点电压,避免短路风险
2. Jetson Nano环境配置
2.1 系统设置与权限
Jetson Nano默认的串口权限可能限制普通用户访问,我们需要进行以下配置:
# 将用户加入dialout组 sudo usermod -a -G dialout $USER # 修改串口设备权限(临时方案) sudo chmod 666 /dev/ttyTHS1 # 永久解决方案:创建udev规则 echo 'KERNEL=="ttyTHS1", MODE="0666"' | sudo tee /etc/udev/rules.d/99-ttyTHS1.rules sudo udevadm control --reload-rules2.2 Python环境搭建
推荐使用Python 3.6+环境,并安装必要的库:
# 安装pip(如果尚未安装) sudo apt-get install python3-pip # 安装pyserial库 pip3 install pyserial # 可选:安装交互式调试工具 pip3 install ipython2.3 基础通信测试
创建一个简单的测试脚本serial_test.py:
import serial import time def main(): try: ser = serial.Serial( port='/dev/ttyTHS1', baudrate=115200, timeout=1 ) print(f"Serial port {ser.name} opened successfully!") while True: ser.write(b'Hello STM32\n') time.sleep(1) if ser.in_waiting > 0: data = ser.readline().decode('utf-8').strip() print(f"Received: {data}") except Exception as e: print(f"Error: {str(e)}") finally: if 'ser' in locals() and ser.is_open: ser.close() if __name__ == "__main__": main()运行此脚本前,请确保STM32端已正确配置并运行。如果一切正常,您应该能看到周期性的发送和接收信息。
3. STM32固件开发
3.1 CubeMX基础配置
使用STM32CubeMX进行初始化配置:
- 选择正确的STM32型号
- 启用USART2:
- 模式:Asynchronous
- 波特率:115200
- 字长:8 Bits
- 停止位:1
- 校验:None
- 启用USART1(用于调试输出)
- 生成代码时启用DMA(可选,提升性能)
3.2 中断接收实现
修改生成的代码,添加中断接收逻辑:
/* Private variables ---------------------------------------------------------*/ uint8_t rxBuffer[256]; uint8_t rxIndex = 0; volatile uint8_t dataReady = 0; /* USER CODE BEGIN 0 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART2) { if(rxBuffer[rxIndex] != '\n') { // 以换行符作为消息结束标志 rxIndex++; } else { dataReady = 1; rxIndex = 0; } HAL_UART_Receive_IT(&huart2, &rxBuffer[rxIndex], 1); } } /* USER CODE END 0 */3.3 主循环处理
在main函数中添加数据处理逻辑:
/* Infinite loop */ while (1) { if(dataReady) { // 处理接收到的数据 processReceivedData(rxBuffer, rxIndex); // 通过USART1发送回显(调试用) HAL_UART_Transmit(&huart1, rxBuffer, rxIndex, HAL_MAX_DELAY); // 重置标志位 dataReady = 0; rxIndex = 0; } /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ }4. 高级应用与性能优化
4.1 数据协议设计
简单的文本协议虽然易于调试,但在实际项目中,我们通常需要更高效的二进制协议。以下是一个示例帧结构:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 帧头 | 2 | 固定为0xAA55 |
| 数据长度 | 1 | 有效数据长度(最大255) |
| 数据 | N | 有效载荷 |
| CRC校验 | 1 | 从帧头到数据的异或校验 |
Python端解析代码示例:
def parse_frame(data): if len(data) < 4: return None if data[0] != 0xAA or data[1] != 0x55: return None length = data[2] if len(data) < 4 + length: return None payload = data[3:3+length] crc = data[3+length] # 计算校验 calc_crc = 0 for b in data[:3+length]: calc_crc ^= b if calc_crc != crc: return None return payload4.2 性能优化技巧
Jetson Nano端优化:
- 使用单独的线程处理串口接收
- 实现环形缓冲区减少数据丢失风险
- 考虑使用
select模块监控串口活动
STM32端优化:
- 启用DMA传输减少CPU负载
- 使用双缓冲技术
- 合理设置中断优先级
4.3 常见问题排查
当通信出现问题时,可以按照以下步骤排查:
基础检查
- 确认连线正确(TX-RX交叉)
- 检查共地连接
- 确认波特率等参数一致
信号质量检查
- 使用示波器或逻辑分析仪观察信号波形
- 检查电压电平是否匹配(3.3V对3.3V)
软件调试
- 在STM32端添加LED指示灯辅助调试
- 使用
minicom或screen直接测试串口 - 检查Python脚本的编码设置(特别是中文处理)
# 调试用串口监控命令 # 在终端中直接查看串口输出 screen /dev/ttyTHS1 1152004.4 实际项目经验分享
在工业环境中使用时,有几个关键点需要注意:
- 电磁干扰:长距离传输时考虑使用RS485代替TTL
- 错误恢复:实现自动重连机制
- 日志记录:保存通信日志便于后期分析
- 超时处理:设置合理的超时时间,避免线程阻塞
一个健壮的工业级实现可能会包含以下特性:
class RobustSerial: def __init__(self, port, baudrate): self.port = port self.baudrate = baudrate self.connection = None self.reconnect_attempts = 0 self.max_reconnect = 5 def connect(self): try: self.connection = serial.Serial( port=self.port, baudrate=self.baudrate, timeout=1, write_timeout=1 ) self.reconnect_attempts = 0 return True except Exception as e: print(f"Connection failed: {str(e)}") self.reconnect_attempts += 1 if self.reconnect_attempts < self.max_reconnect: time.sleep(1) return self.connect() return False def send_command(self, cmd, retries=3): for attempt in range(retries): try: if not self.connection or not self.connection.is_open: if not self.connect(): continue self.connection.write(cmd) return True except Exception as e: print(f"Send failed (attempt {attempt+1}): {str(e)}") self.connection = None return False