一开始计划打算直接使用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
sudo modprobe mttcan
  • 配置CAN波特率
sudo ip link set can0 type can bitrate 1000000
  • 开启CAN
sudo ip link set can0 up
  • 直接使用终端显示接收到的CAN消息帧
sudo candump can0

异常处理

有时候会因为CAN的不正常关闭,导致CAN会一直显示被占用:

RTNETLINK answers: Device or resource busy

这时候首先需要检查CAN的状态:

ifconfig can0
# 运行结果
can0: flags=129<UP,NOARP>  mtu 16
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 10  (UNSPEC)
        RX packets 3  bytes 24 (24.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 200 

从运行结果中可以看到,这时候CAN仍处于UP的状态,就需要手动对CAN进行关闭:

sudo ip link set can0 down

这时候再次查询CAN的状态就会显示无占用了:

can0: flags=128<NOARP>  mtu 16
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 10  (UNSPEC)
        RX packets 3  bytes 24 (24.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 1 overruns 0  carrier 1  collisions 0
        device interrupt 200  

电机控制命令发送及接收

最后

最后通过查阅类似开发经验得知,有不少开发者也同样遇到接收不稳定的问题,有博主通过更换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_t send_jetson_buf[UARTS_TX_BUF_SZ] = {0};

关于更多的介绍可以查看Cortex-M7的手册或者是《安富莱_STM32-V7开发板_用户手册》

在串口中断回调函数中增加数据解包以及状态发送的代码:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if (huart->Instance == USART10) 
    {
        static uint8_t last_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;
        }
        else if (jetson_rx_index >= 2)
        {
            jetson_rx_buffer[jetson_rx_index++] = jetson_rx_byte;

            if (jetson_rx_index >= RX_FRAME_LEN)
            {
                uint8_t checksum = 0;
                for (int i = 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函数定义:

static void send_state_check(void)
{
    int p = 0;

    send_jetson_buf[p++] = UARTS_SYNC0;
    send_jetson_buf[p++] = UARTS_SYNC1;
    send_jetson_buf[p++] = UARTS_MSG_STATE;

    int len_pos = p; p += 2;

    uint32_t t_ms = HAL_GetTick(); 
    memcpy(&send_jetson_buf[p], &t_ms, 4); p += 4;

    send_jetson_buf[p++] = g_motor_count;
    for(uint8_t i=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_t len = (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_t checksum = 0;
    for(int i=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端发送命令并进行数据接收:

def synchronous_exchange(self, left_torque, right_torque, terrain_mode='LG10'):
    """
    Perform synchronous exchange: send torque command then read sensor data
    """
    if not self.connected:
        return None

    # Step 1: Send torque command
    tx_success = self.send_torque_command(left_torque, right_torque, terrain_mode)

    # Step 2: Small delay to allow hardware processing
    time.sleep(0.0001)  # 0.1ms delay

    # Step 3: Read sensor response
    sensor_data = self.read_sensor_data()

    return sensor_data
def send_torque_command(self, left_torque, right_torque, terrain_mode='LG10'):
    """
    Synchronous send of torque command
    """
    if not self.connected:
        return False

    try:
        # Update state tracking
        self.last_left_torque = left_torque
        self.last_right_torque = right_torque
        self.last_terrain_mode = terrain_mode

        # Convert terrain mode to code
        terrain_code = self.TERRAIN_CODES.get(terrain_mode, 3)

        # Frame header and tail
        frame_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 frame
        frame = frame_head + data + bytes([checksum]) + frame_tail

        # Send frame
        self.ser.write(frame)
        self.ser.flush()  # Ensure data is sent immediately
        self.tx_count += 1
        return True

    except Exception:
        return False
def read_sensor_data(self):
    """
    Synchronous read of sensor data
    Returns parsed frame or None if no complete frame available
    """
    if not self.connected:
        return None

    try:
        # Read available data
        data = self.ser.read(self.ser.in_waiting or 1)
        for b in data:
            b = b if isinstance(b, int) else ord(b)

            if self.rx_state == 0:
                if b == SYNC0:
                    self.rx_state = 1
                    self.rx_frame = bytearray([b])
            elif self.rx_state == 1:
                if b == SYNC1:
                    self.rx_state = 2
                    self.rx_frame.append(b)
                else:
                    self.rx_state = 0
            elif self.rx_state >= 2:
                self.rx_frame.append(b)
                if len(self.rx_frame) >= 5:
                    # Read length field early
                    length = struct.unpack('<H', self.rx_frame[3:5])[0]
                    expected_len = 7 + length  # SYNC0+SYNC1+MSG+LEN(2)+payload+CHECK+END
                    if len(self.rx_frame) == expected_len:
                        result = self.parse_rx_frame(self.rx_frame)
                        self.rx_state = 0
                        return result
    except Exception:
        pass
    return None

最终能够实现200Hz较为稳定的通讯。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐