在嵌入式开发中,Jetson Nano和STM32的组合堪称黄金搭档。前者作为边缘计算设备能处理复杂算法,后者则是实时控制的专家。我最近在一个智能小车项目里就用到了这对组合——Jetson Nano负责图像识别,STM32控制电机运动,两者通过串口交换数据。刚开始调试时确实踩了不少坑,后来摸索出一套稳定可靠的通信方案,今天就把完整实现过程分享给大家。
硬件清单需要准备这些材料:
接线时有个容易忽略的细节:共地连接。很多通信不稳定问题都是因为没接GND线导致的。具体连接方式如下:
实测中发现,如果传输距离超过30cm,建议加上MAX3232电平转换芯片。有次我在实验室里用长导线连接,数据误码率突然飙升,后来用示波器检查才发现是电平衰减导致的。
Jetson Nano自带Python环境,但需要安装pyserial库。这里有个坑要注意:不要用pip直接装最新版,某些版本与Jetson的ARM架构存在兼容性问题。我推荐用这个指定版本:
bash复制sudo apt-get install python3-pip
pip3 install pyserial==3.4
串口设备权限也需要特别处理。默认情况下普通用户无法访问/dev/ttyTHS1,每次都要sudo很麻烦。永久解决方案是:
bash复制sudo usermod -a -G dialout $USER
sudo chmod 666 /dev/ttyTHS1
测试通信的基础代码可以这样写:
python复制import serial
import time
def send_to_stm32(message):
try:
ser = serial.Serial(
port='/dev/ttyTHS1',
baudrate=115200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1
)
if not ser.isOpen():
ser.open()
encoded_msg = message.encode('utf-8') + b'\r\n'
ser.write(encoded_msg)
print(f"Sent: {message}")
except Exception as e:
print(f"Error: {str(e)}")
finally:
if 'ser' in locals():
ser.close()
# 示例:发送不同数据类型
send_to_stm32("Hello STM32") # 字符串
send_to_stm32("TEMP:25.6") # 传感器数据
send_to_stm32("CMD:MOVE_FORWARD") # 控制指令
实际项目中我发现,发送时加上\r\n结尾能显著提高STM32端的解析成功率。曾经因为没加结束符,导致STM32缓冲区积累数据无法及时处理。
STM32端我用的是STM32CubeIDE开发环境,基于HAL库实现。先通过CubeMX配置UART2为异步模式,波特率115200,开启全局中断。关键配置参数如下:
| 参数项 | 配置值 | 备注 |
|---|---|---|
| 波特率 | 115200 | 需与Jetson端一致 |
| 数据位 | 8 bits | 常规配置 |
| 停止位 | 1 bit | 常规配置 |
| 硬件流控 | Disable | 除非特别需要 |
| 接收中断 | Enable | 必须开启 |
接收逻辑采用中断+缓冲区的方式,这是经过多次优化后的方案。早期版本直接用HAL_UART_Receive_IT接收固定长度数据,实际使用中发现对变长指令支持不好。改进后的核心代码如下:
c复制// 在main.c中添加这些变量
#define RX_BUFFER_SIZE 128
uint8_t rxBuffer[RX_BUFFER_SIZE];
uint8_t rxIndex = 0;
volatile uint8_t cmdReady = 0;
// 中断回调函数重写
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART2) {
uint8_t received = rxBuffer[rxIndex];
if(received == '\n' || rxIndex >= RX_BUFFER_SIZE-1) {
cmdReady = 1;
} else {
rxBuffer[rxIndex++] = received;
HAL_UART_Receive_IT(&huart2, &rxBuffer[rxIndex], 1);
}
}
}
// 主循环处理
while (1) {
if(cmdReady) {
process_command(rxBuffer, rxIndex); // 自定义处理函数
rxIndex = 0;
cmdReady = 0;
HAL_UART_Receive_IT(&huart2, rxBuffer, 1);
}
// 其他任务...
}
调试时发现一个关键点:每次中断接收完成后要立即重新启用中断接收,否则会丢失后续数据。有次小车在运行中突然停止响应,排查半天才发现是这个原因。
单一方向通信往往不够用,我们需要实现双向数据交换。我在项目中设计了简单的通信协议:
数据帧格式:
code复制[消息类型][分隔符][数据][结束符]
示例:
SENSOR:TEMP=25.6;\n
CTRL:LED=ON;\n
对应的Python发送函数升级版:
python复制def send_frame(msg_type, data):
frame = f"{msg_type}:{data};\n"
send_to_stm32(frame)
# 使用示例
send_frame("CTRL", "MOTOR_SPEED=80")
send_frame("QUERY", "GET_TEMP")
STM32端的解析函数示例:
c复制void process_command(uint8_t* cmd, uint8_t length) {
char* token = strtok((char*)cmd, ":");
if(token == NULL) return;
if(strcmp(token, "CTRL") == 0) {
handle_control_command(strtok(NULL, ";"));
}
else if(strcmp(token, "QUERY") == 0) {
handle_query_command(strtok(NULL, ";"));
}
}
void handle_control_command(char* cmd) {
// 示例:处理"MOTOR_SPEED=80"
char* key = strtok(cmd, "=");
char* value = strtok(NULL, "=");
if(strcmp(key, "MOTOR_SPEED") == 0) {
uint8_t speed = atoi(value);
set_motor_speed(speed);
}
// 其他控制命令...
}
实际测试时,建议先用简单的ASCII协议,等通信稳定后再考虑二进制协议。我曾尝试直接发送二进制数据,结果因为字节对齐问题导致解析异常,后来改用JSON格式作为过渡方案。
调试工具准备:
典型问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 接收数据乱码 | 波特率不匹配 | 检查双方波特率设置 |
| 数据丢失 | 未正确处理中断 | 确保每次接收后重新启用中断 |
| 通信时好时坏 | 电源干扰或接触不良 | 检查电源稳定性,更换杜邦线 |
| 只能接收部分数据 | 缓冲区大小不足 | 增大接收缓冲区 |
| 发送后无响应 | 接线错误 | 用万用表检查TX/RX是否交叉连接 |
有个实用的调试技巧:在STM32端添加调试输出,通过另一个串口打印状态信息。例如:
c复制void debug_print(const char* message) {
HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), 100);
HAL_UART_Transmit(&huart1, (uint8_t*)"\r\n", 2, 100);
}
// 使用示例
debug_print("Received command:");
debug_print((char*)rxBuffer);
在Jetson端可以用minicom或者额外的Python脚本监控这个调试输出。我习惯用这个简单的监控脚本:
python复制import serial
def monitor_debug_port():
debug_ser = serial.Serial('/dev/ttyUSB0', 115200)
while True:
line = debug_ser.readline().decode().strip()
print(f"[DEBUG] {line}")
# 新开一个终端运行这个函数
当通信频率提高时,需要考虑更多优化措施。在我的图像传输项目中,总结出这些经验:
DMA传输:对于大数据量传输,改用DMA模式能显著降低CPU负载。在CubeMX中配置UART的DMA设置,发送和接收都建议启用。
c复制// DMA初始化代码片段
__HAL_LINKDMA(&huart2, hdmatx, hdma_usart2_tx);
__HAL_LINKDMA(&huart2, hdmarx, hdma_usart2_rx);
// 使用DMA接收
HAL_UART_Receive_DMA(&huart2, rxBuffer, BUFFER_SIZE);
流量控制:硬件流控(RTS/CTS)能有效避免缓冲区溢出。需要在CubeMX中启用硬件流控选项,并连接对应的引脚。
错误处理增强:增加超时重传机制,我在协议里添加了ACK确认和序列号:
python复制# Python端重传逻辑
def reliable_send(message, max_retry=3):
for attempt in range(max_retry):
send_to_stm32(message)
if wait_for_ack(sequence_num): # 自定义等待函数
return True
return False
对应的STM32端需要实现ACK回复:
c复制void send_ack(uint8_t seq_num) {
char ack_msg[32];
sprintf(ack_msg, "ACK:%d;\n", seq_num);
HAL_UART_Transmit(&huart2, (uint8_t*)ack_msg, strlen(ack_msg), 100);
}
在电机控制项目中,我还加入了心跳检测机制。Jetson每秒钟发送一次心跳包,如果STM32连续3次没收到就进入安全模式。这个设计在实际运行中多次避免了意外事故。