一开始计划打算直接使用Jetson ORIN NX上的CAN实现与电机的通信,但是在调试的过程中发现ORIN上的CAN使用会存在问题。为了加速开发,后面使用了一块STM32H7的板子实现电机数据的收发,再通过串口与ORIN实现通信。
CAN通讯实现(失败)
配置ORIN的CAN并使能CAN
参考:https://gitee.com/kit-miao/orin-board/blob/master/CAN%20%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95.md
- 激活CAN
sudomodprobe mttcan- 配置CAN波特率
sudoiplinksetcan0typecan bitrate1000000- 开启CAN
sudoiplinksetcan0 up- 直接使用终端显示接收到的CAN消息帧
sudocandump can0异常处理
有时候会因为CAN的不正常关闭,导致CAN会一直显示被占用:
RTNETLINK answers: Device or resource busy这时候首先需要检查CAN的状态:
ifconfigcan0# 运行结果can0:flags=129<UP,NOARP>mtu16unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen10(UNSPEC)RX packets3bytes24(24.0B)RX errors0dropped0overruns0frame0TX packets0bytes0(0.0B)TX errors0dropped0overruns0carrier0collisions0device interrupt200从运行结果中可以看到,这时候CAN仍处于UP的状态,就需要手动对CAN进行关闭:
sudoiplinksetcan0 down这时候再次查询CAN的状态就会显示无占用了:
can0:flags=128<NOARP>mtu16unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen10(UNSPEC)RX packets3bytes24(24.0B)RX errors0dropped0overruns0frame0TX packets0bytes0(0.0B)TX errors0dropped1overruns0carrier1collisions0device interrupt200电机控制命令发送及接收
最后
最后通过查阅类似开发经验得知,有不少开发者也同样遇到接收不稳定的问题,有博主通过更换CAN芯片解决了问题,链接:
https://blog.csdn.net/qq_22146161/article/details/132193036?spm=1001.2014.3001.5506考虑到硬件开发,之后还是更换了硬件实现方案。先通过使用STM32对电机数据进行处理,再通过STM32的串口将数据发送到Jetson端。
串口通讯实现
为了不造成发送频率过高而导致的串口堵塞,我这里采取了半双工的通信方案。也就是Jetson端向STM32端发送命令,STM32端接收到命令后,对数据进行解包,然后再向Jetson端反馈当前的电机数据。
这里为了使得STM32发送数据不阻塞串口的接收中断,开启了串口的DMA。
由于STM32H7使用的是Cortex-M7内核,其包含多个存储区,包括TCM、SRAM等。为了保证DMA能够正常访问到数据,不存在Cache问题,这里将所需要发送的buffer显式地定义为dma_buffer中:
__attribute__((section(".dma_buffer")))uint8_tsend_jetson_buf[UARTS_TX_BUF_SZ]={0};关于更多的介绍可以查看Cortex-M7的手册或者是《安富莱_STM32-V7开发板_用户手册》
在串口中断回调函数中增加数据解包以及状态发送的代码:
voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart->Instance==USART10){staticuint8_tlast_byte=0;if(jetson_rx_index==0&&last_byte==0xAA&&jetson_rx_byte==0x55){jetson_rx_buffer[0]=0xAA;jetson_rx_buffer[1]=0x55;jetson_rx_index=2;}elseif(jetson_rx_index>=2){jetson_rx_buffer[jetson_rx_index++]=jetson_rx_byte;if(jetson_rx_index>=RX_FRAME_LEN){uint8_tchecksum=0;for(inti=2;i<RX_FRAME_LEN-2;i++)checksum+=jetson_rx_buffer[i];checksum&=0xFF;if(jetson_rx_buffer[RX_FRAME_LEN-2]==checksum&&jetson_rx_buffer[RX_FRAME_LEN-1]==0x0D){memcpy(&leftMotorCmdTorque,&jetson_rx_buffer[2],4);memcpy(&rightMotorCmdTorque,&jetson_rx_buffer[6],4);terrain_code=jetson_rx_buffer[10];}jetson_rx_index=0;send_state_check();}}last_byte=jetson_rx_byte;HAL_UART_Receive_IT(&huart10,&jetson_rx_byte,1);}}send_state_check函数定义:
staticvoidsend_state_check(void){intp=0;send_jetson_buf[p++]=UARTS_SYNC0;send_jetson_buf[p++]=UARTS_SYNC1;send_jetson_buf[p++]=UARTS_MSG_STATE;intlen_pos=p;p+=2;uint32_tt_ms=HAL_GetTick();memcpy(&send_jetson_buf[p],&t_ms,4);p+=4;send_jetson_buf[p++]=g_motor_count;for(uint8_ti=0;i<g_motor_count;i++){send_jetson_buf[p++]=g_motors[i].id;memcpy(&send_jetson_buf[p],&g_motors[i].pos_rad,4);p+=4;memcpy(&send_jetson_buf[p],&g_motors[i].vel_rad_s,4);p+=4;memcpy(&send_jetson_buf[p],&g_motors[i].tau_nm,4);p+=4;}send_jetson_buf[p++]=g_imu9.valid_bits;memcpy(&send_jetson_buf[p],&g_imu9.ax,4);p+=4;memcpy(&send_jetson_buf[p],&g_imu9.ay,4);p+=4;memcpy(&send_jetson_buf[p],&g_imu9.az,4);p+=4;memcpy(&send_jetson_buf[p],&g_imu9.gx,4);p+=4;memcpy(&send_jetson_buf[p],&g_imu9.gy,4);p+=4;memcpy(&send_jetson_buf[p],&g_imu9.gz,4);p+=4;memcpy(&send_jetson_buf[p],&g_imu9.rollY,4);p+=4;memcpy(&send_jetson_buf[p],&g_imu9.pitchX,4);p+=4;memcpy(&send_jetson_buf[p],&g_imu9.yawZ,4);p+=4;uint16_tlen=(uint16_t)(p-(len_pos+2));send_jetson_buf[len_pos+0]=(uint8_t)(len&0xFF);send_jetson_buf[len_pos+1]=(uint8_t)(len>>8);uint8_tchecksum=0;for(inti=2;i<p;i++)checksum+=send_jetson_buf[i];send_jetson_buf[p++]=checksum;send_jetson_buf[p++]=UARTS_END_BYTE;HAL_UART_Transmit_DMA(&UARTS_HUART,send_jetson_buf,(uint16_t)p);}Jetson端发送命令并进行数据接收:
defsynchronous_exchange(self,left_torque,right_torque,terrain_mode='LG10'):""" Perform synchronous exchange: send torque command then read sensor data """ifnotself.connected:returnNone# Step 1: Send torque commandtx_success=self.send_torque_command(left_torque,right_torque,terrain_mode)# Step 2: Small delay to allow hardware processingtime.sleep(0.0001)# 0.1ms delay# Step 3: Read sensor responsesensor_data=self.read_sensor_data()returnsensor_datadefsend_torque_command(self,left_torque,right_torque,terrain_mode='LG10'):""" Synchronous send of torque command """ifnotself.connected:returnFalsetry:# Update state trackingself.last_left_torque=left_torque self.last_right_torque=right_torque self.last_terrain_mode=terrain_mode# Convert terrain mode to codeterrain_code=self.TERRAIN_CODES.get(terrain_mode,3)# Frame header and tailframe_head=b'\xAA\x55'frame_tail=b'\x0D'# Pack data: 2 floats (left, right torque) + 1 uint8 (terrain code)data=struct.pack('<ffB',left_torque,right_torque,terrain_code)# Calculate checksum (sum of all data bytes, keep low 8 bits)checksum=sum(data)&0xFF# Construct full frameframe=frame_head+data+bytes([checksum])+frame_tail# Send frameself.ser.write(frame)self.ser.flush()# Ensure data is sent immediatelyself.tx_count+=1returnTrueexceptException:returnFalsedefread_sensor_data(self):""" Synchronous read of sensor data Returns parsed frame or None if no complete frame available """ifnotself.connected:returnNonetry:# Read available datadata=self.ser.read(self.ser.in_waitingor1)forbindata:b=bifisinstance(b,int)elseord(b)ifself.rx_state==0:ifb==SYNC0:self.rx_state=1self.rx_frame=bytearray([b])elifself.rx_state==1:ifb==SYNC1:self.rx_state=2self.rx_frame.append(b)else:self.rx_state=0elifself.rx_state>=2:self.rx_frame.append(b)iflen(self.rx_frame)>=5:# Read length field earlylength=struct.unpack('<H',self.rx_frame[3:5])[0]expected_len=7+length# SYNC0+SYNC1+MSG+LEN(2)+payload+CHECK+ENDiflen(self.rx_frame)==expected_len:result=self.parse_rx_frame(self.rx_frame)self.rx_state=0returnresultexceptException:passreturnNone最终能够实现200Hz较为稳定的通讯。