ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

小型线控底盘CAN总线控制与双模式切换实战指南

小型线控底盘CAN总线控制与双模式切换实战指南 近两年做小车底盘和无人车项目的人越来越多尤其是果园巡检、小型厂区转运这类场景既不需要重卡级线控底盘又不能再用“遥控车改一改”的粗放方案。很多人卡在同一个地方底盘的转向、驱动、刹车怎么统一控制遥控和自主导航之间怎么平滑切换CAN总线报文的ID和字节定义怎么定才合理之前我在做一款小型无人驾驶线控底盘时也在这些问题上反复折腾过。网上资料虽然多但大多只讲某一个点要么是CAN总线协议的理论要么是AGV的A*算法要么是STM32的循迹小车。真正把“线控底盘 CAN总线 双模式切换”串成一条完整技术链路的文章并不多。这篇文章就把我实际梳理过的方案整理出来内容包括线控底盘的核心结构和工作原理CAN总线在底盘通信中的作用遥控模式与自主导航模式如何设计与切换一套完整的CAN报文协议设计思路基于STM32的底盘控制示例代码AGV导航中A*算法的应用与改进思路常见故障排查与工程化建议。不管你是准备入门无人驾驶底盘还是已经在做AGV小车、机器人底盘都可以按这篇文章的思路搭一套自己的控制方案。1. 小型线控底盘到底是什么1.1 线控底盘的概念线控底盘简单理解就是“用电子信号代替机械连接来控制车辆的行驶状态”。传统车辆转向靠方向盘带动转向柱刹车靠脚踩踏板推动液压机构而线控底盘把这些操作全部转换成电信号通过控制器和电机执行器来完成。小型无人驾驶线控底盘通常包含以下几个子系统子系统作用常用执行器驱动系统提供前进和后退的动力直流无刷电机、伺服电机转向系统控制车轮偏转方向转向舵机、转向电机制动系统减速或停车电磁制动器、电子刹车控制系统接收指令并控制执行器MCU、运动控制板通信系统连接遥控器、导航模块、上位机CAN总线、串口、以太网之所以强调“小型”是因为这类底盘通常面向园区、果园、室内仓储等场景载重一般在几十公斤到几百公斤之间车身紧凑改装空间有限因此对控制器的体积、功耗、接口数量都有要求。1.2 为什么叫“线控”而不是“电控”“线控”这个词来自汽车行业的Drive-by-Wire也就是用线缆传输控制信号。电控的范围更广任何用电来控制设备的系统都可以叫电控。线控强调的是传统机械连接被电子信号取代之后控制链路变成了“指令输入 - 控制器 - 执行器 - 车辆运动”这条数字化通路。这个区别对开发者来说很重要如果是“电控”你可以只把车灯、喇叭换成电动的如果是“线控”你必须把转向、驱动、制动都做成可编程、可被外部指令控制的接口。对于做无人驾驶或AGV的人来说底盘必须是线控的因为自动导航算法产生的转向信号和速度信号最终都要落到底盘的执行器上。1.3 典型应用场景小型线控底盘最常见的几个落地场景果园巡检在果园里沿着固定路线巡检采集温湿度、病虫害图像底盘需要具备一定的越障能力和精确的路径跟踪能力。厂区转运在工厂车间、仓库里运送物料路线相对固定但需要频繁启停、避障、对接工位。园区安防搭载摄像头和传感器在园区内巡逻对底盘的定位精度和自主导航能力要求较高。科研教学高校实验室用它做算法验证底盘的开放性和二次开发能力很关键。在这些场景里底盘既不能完全依赖人工遥控又不能一步到位做到全无人所以“遥控 自主导航”的双模式设计就成了最实用的方案。2. 环境准备与开发工具2.1 硬件清单本文涉及的实操内容基于以下硬件环境你可以根据自己的底盘型号和主控芯片做调整硬件说明线控底盘本体支持CAN总线的转向、驱动、制动接口主控板STM32F103系列或更高性能的F407系列CAN收发器TJA1050或SN65HVD230遥控器航模遥控器输出PWM或SBUS信号定位模块RTK-GPS或激光雷达用于自主导航惯性测量单元IMUMPU6050等用于姿态感知电源系统24V直流电源为电机和控制器供电2.2 软件工具工具用途STM32CubeMX生成初始化代码配置CAN外设Keil MDK 或 STM32CubeIDE编写和编译固件CAN分析仪抓取和发送CAN报文调试协议串口调试助手查看底盘控制器的调试日志版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。2.3 建议的项目结构chassis_controller/ ├── Core/ │ ├── Inc/ │ │ ├── can.h │ │ ├── motor.h │ │ └── chassis.h │ └── Src/ │ ├── main.c │ ├── can.c │ ├── motor.c │ └── chassis.c ├── Drivers/ │ └── STM32F1xx_HAL_Driver/ ├── Middlewares/ │ └── control/ │ ├── mode_switch.c │ └── pid_controller.c └── README.md这个结构把硬件驱动、控制逻辑、模式管理分开后续加导航算法或者换遥控器协议时不需要大规模改动底层代码。3. CAN总线核心知识点3.1 CAN总线是什么CAN总线Controller Area Network控制器局域网是BOSCH公司开发的一种串行通信协议最初用于汽车内部电子设备之间的通信现在已经成为工业控制、机器人、AGV底盘等领域的标准总线之一。它解决的核心问题是多个控制器之间如何可靠地共享数据。在底盘系统中主控板、电机驱动器、IMU、遥控接收机可能是不同的设备它们需要频繁交换速度、转角、状态等信息。如果每个设备都用独立线束连接线缆数量会非常多而且容易出故障。CAN总线只需要两条线CAN_H和CAN_L就能把所有节点挂上去极大简化了布线。除此之外CAN总线还有几个特点非常适合底盘多主机通信任意节点都可以主动发送数据不分主从。报文优先级仲裁ID越小的报文优先级越高保证关键控制指令优先传输。错误检测与自动重发节点在发送时会校验数据一旦发现错误会自动重发保证通信可靠。差分信号传输抗干扰能力强适合电机频繁启停的电磁环境。3.2 CAN总线的物理层CAN总线物理层使用的是差分电压传输。两条线分别叫CAN_H和CAN_L正常情况下显性电平逻辑0CAN_H电压高于CAN_L电压隐性电平逻辑1CAN_H和CAN_L电压接近差值很小。这种差分方式的好处显而易见外部电磁干扰通常同时作用于两条线产生的共模干扰不会破坏差分信号所以CAN总线在电机、逆变器这些强干扰源附近依然能稳定工作。在实际布线时还有几个关键点需要注意项目要求终端电阻总线两端各接一个120欧姆电阻线束类型使用双绞线降低电磁干扰布线长度高速模式下建议控制在40米以内共模干扰处理外壳接地必要时在CAN接口处加共模电感或电容滤波3.3 CAN总线的数据帧格式CAN总线有四种帧类型数据帧、远程帧、错误帧、过载帧。底盘控制中最常用的是数据帧。标准数据帧由以下几部分组成字段说明ID11位标识符决定报文优先级DLC数据长度代码表示数据场的字节数(0-8)Data数据场最多8个字节CRC校验字段用于错误检测使用8个字节的数据场时我们通常会把数据拆分成不同的位段。例如Byte0控制模式Byte1目标速度Byte2目标转向角度Byte3~Byte7预留或状态反馈这样一帧CAN报文就能完成一条完整控制指令的传输非常高效。3.4 为什么底盘控制系统选CAN而不是串口或以太网很多新手会问用串口UART也能发数据为什么一定要CAN主要有三点原因实时性和确定性UART是点对点通信主控需要逐个轮询设备CAN是事件驱动设备有数据随时发送优先级高的报文不会被阻塞。可靠性CAN的差分信号和错误处理机制比UART的单端信号抗干扰能力更强在电机频繁启停的底盘上不容易丢包。扩展性底盘的传感器和执行器会不断增加CAN总线支持多个节点直接挂载后续加设备不需要重新设计通信架构。当然CAN也不是万能的。如果底盘对通信速率要求极高比如实时传输图像数据CAN的带宽就捉襟见肘了这时需要配合以太网来分工。4. 小型线控底盘系统架构设计4.1 底盘控制系统的层级划分一个完整的线控底盘控制系统可以分成三层导航决策层上位机 / 工控机 / 树莓派 / Jetson ↓ 目标速度、目标转角 底盘控制层STM32 / 运动控制板 ↓ CAN报文 执行层电机驱动器、转向舵机、制动器上位机负责导航定位、路径规划、避障算法底盘控制层负责把上位机发来的目标速度和目标转角转换成电机驱动器和转向执行器的具体控制信号执行层负责实际运动输出。这种分层架构让算法工程师和嵌入式工程师的工作解耦做导航的人不需要关心底层电机怎么转做底盘的人也不需要了解A*路径规划的细节。4.2 核心模块设计一个典型的小型线控底盘包含以下核心模块驱动模块驱动模块包含驱动电机和驱动器。小型底盘通常使用直流无刷电机BLDC或轮毂电机驱动器接收PWM或CAN指令控制电机转速。转向模块转向模块的常见方案有两种舵机转向适合小型底盘转向角度范围有限控制简单电机转向阿克曼转向接近真实汽车转向结构适合高速和中大型底盘。制动模块小型底盘一般使用电子刹车或电磁制动器。紧急制动时底盘控制层需要在最短时间内切断电机输出并锁止车轮。感知模块感知模块包括IMU惯性测量单元、编码器、激光雷达、视觉传感器等。编码器用于获取电机转速反馈IMU用于姿态估计激光雷达用于定位和避障。4.3 遥控 自主导航双模式设计小型无人驾驶线控底盘最实用的功能就是在遥控模式和自主导航模式之间自由切换。这样在调试阶段可以人工遥控测试底盘机械性能在运行阶段切换到自动模式执行巡检或转运任务。两种模式的控制逻辑差异如下项目遥控模式自主导航模式指令来源遥控器(2.4G PWM/SBUS)上位机(串口/CAN/以太网)速度来源遥控器油门通道路径规划算法转角来源遥控器转向通道路径跟踪算法安全机制遥控器急停开关上位机急停指令 传感器避障4.4 模式切换策略模式切换是整个底盘控制中最容易出问题的地方。如果不加处理直接从自主导航切回遥控可能会出现“底盘还在按旧速度跑遥控器指令插不进来”的情况。我的做法是用一个专门的模式管理状态机REMOTE_MODE遥控模式底盘只响应遥控器指令AUTO_MODE自主导航模式底盘响应上位机指令START底盘上电初始状态E_STOP急停状态任何模式下均可进入。模式切换时最关键的一步是清空之前的控制目标把目标速度置零等确认底盘速度降到安全范围后再切换执行器控制权。这样做可以避免切换瞬间出现底盘突然加速或转向的情况。5. CAN协议设计从零定义一套底盘通信协议5.1 报文ID规划CAN报文ID的作用不只是标识发送者它的另一个重要功能是决定发送优先级。ID越小优先级越高。底盘控制中安全关键指令需要高优先级状态反馈可以稍微低一点。下面是一个常见的ID规划方案报文方向报文ID含义优先级底盘控制层 - 执行层0x01电机速度指令极高底盘控制层 - 执行层0x02转向角度指令高底盘控制层 - 执行层0x03制动指令极高底盘控制层 - 导航层0x11底盘速度反馈中底盘控制层 - 导航层0x12底盘转角反馈中底盘控制层 - 导航层0x13底盘故障状态较高这样规划的原因是制动和电机速度指令直接关系到行驶安全必须优先发送不能让它们被低优先级的反馈报文阻塞。5.2 CAN数据帧格式定义以一条驱动控制指令为例数据场可以这样定义Byte内容说明Byte0控制标志位bit0: 使能, bit1: 正反转, bit2: 制动Byte1速度高字节目标速度的高8位Byte2速度低字节目标速度的低8位Byte3转向高字节目标转向角的高8位Byte4转向低字节目标转向角的低8位Byte5校验和Byte0~Byte4的累加和Byte6~Byte7预留扩展使用5.3 CanOpen和自定义协议的选择如果底盘采购的是工业级驱动器通常会支持CANopen协议这是CAN上层应用协议的标准化版本。CANopen定义了对象字典、PDO过程数据对象、SDO服务数据对象等机制可以直接使用标准的控制字和目标速度对象来控制驱动器。如果是自己设计的底盘执行器是舵机和电机驱动器可以选用更精简的自定义协议。自定义协议的好处是协议栈更简单MCU资源占用少报文长度固定解析方便数据结构可控便于调试。缺点是需要自己考虑错误的检查和通信鲁棒性比如超时、丢帧、数据异常的处理。5.4 协议设计中的常见坑数据边界错位速度值可能是负值倒车如果用无符号数传输负数会被解析成很大的正数。建议所有数值统一用有符号数或者用额外的标志位表示方向。字节序混乱多个设备对接时如果一方按大端模式解析另一方按小端模式发送数据就会错乱。建议在协议文档里明确写明字节序并统一使用小端模式或大端模式。分帧粘帧CAN报文单帧最多传8字节如果协议数据超过8字节就要分多帧发送接收方需要拼接。拼接时注意帧顺序和时间戳避免不同帧被错误组合。6. 实战基于STM32的底盘控制示例6.1 初始化CAN外设用STM32CubeMX配置CAN非常简单。以STM32F103为例选中CAN1外设配置波特率为500kbps使能中断。// 文件路径Core/Src/can.c void MX_CAN1_Init(void) { CAN_FilterTypeDef can_filter_config {0}; hcan1.Instance CAN1; hcan1.Init.Prescaler 9; // 72MHz / (9 * (1 7 6)) 500kbps hcan1.Init.Mode CAN_MODE_NORMAL; hcan1.Init.SyncJumpWidth CAN_SJW_1TQ; hcan1.Init.TimeSeg1 CAN_BS1_7TQ; hcan1.Init.TimeSeg2 CAN_BS2_6TQ; hcan1.Init.TimeTriggeredMode DISABLE; hcan1.Init.AutoBusOff ENABLE; hcan1.Init.AutoWakeUp DISABLE; hcan1.Init.AutoRetransmission ENABLE; hcan1.Init.ReceiveFifoLocked DISABLE; hcan1.Init.TransmitFifoPriority DISABLE; if (HAL_CAN_Init(hcan1) ! HAL_OK) { Error_Handler(); } can_filter_config.FilterIdHigh 0x0000; can_filter_config.FilterIdLow 0x0000; can_filter_config.FilterMaskIdHigh 0x0000; can_filter_config.FilterMaskIdLow 0x0000; can_filter_config.FilterFIFOAssignment CAN_RX_FIFO0; can_filter_config.FilterBank 0; can_filter_config.FilterMode CAN_FILTERMODE_IDMASK; can_filter_config.FilterScale CAN_FILTERSCALE_32BIT; can_filter_config.FilterActivation ENABLE; if (HAL_CAN_ConfigFilter(hcan1, can_filter_config) ! HAL_OK) { Error_Handler(); } if (HAL_CAN_Start(hcan1) ! HAL_OK) { Error_Handler(); } if (HAL_CAN_ActivateNotification(hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) ! HAL_OK) { Error_Handler(); } }这里波特率计算需要解释一下STM32F103的CAN外设时钟来自APB1通常是36MHz有时候我配置时会忘记把时钟树调对导致波特率差一倍。如果CAN分析仪上看到乱码或者没有数据先检查时钟配置。6.2 发送控制报文底盘控制层向上位机反馈状态或向下执行器发送指令都通过CAN发送函数完成。// 文件路径Core/Src/main.c #include can.h CAN_TxHeaderTypeDef tx_header; uint8_t can_tx_data[8]; uint32_t tx_mailbox; void Chassis_SendSpeed(int16_t speed, int16_t steer) { tx_header.IDE CAN_ID_STD; // 标准帧 tx_header.RTR CAN_RTR_DATA; // 数据帧 tx_header.DLC 8; // 8字节 tx_header.StdId 0x01; // 电机速度指令ID can_tx_data[0] 0x01; // 使能 can_tx_data[1] (uint8_t)(speed 8); can_tx_data[2] (uint8_t)(speed 0xFF); can_tx_data[3] (uint8_t)(steer 8); can_tx_data[4] (uint8_t)(steer 0xFF); can_tx_data[5] can_tx_data[0] can_tx_data[1] can_tx_data[2] can_tx_data[3] can_tx_data[4]; // 累加校验 can_tx_data[6] 0x00; can_tx_data[7] 0x00; HAL_CAN_AddTxMessage(hcan1, tx_header, can_tx_data, tx_mailbox); }6.3 接收报文并切换模式在CAN接收中断中根据收到的报文ID判断报文类型调用对应的处理函数。// 文件路径Core/Src/stm32f1xx_it.c #include can.h CAN_RxHeaderTypeDef rx_header; uint8_t can_rx_data[8]; void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { if (hcan-Instance CAN1) { HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, rx_header, can_rx_data); switch (rx_header.StdId) { case 0x11: // 遥控器数据 RemoteControl_Process(can_rx_data); break; case 0x12: // 上位机路径规划数据 AutoMode_Process(can_rx_data); break; case 0x13: // 急停指令 Chassis_EStop(); break; default: break; } } }6.4 速度闭环控制底盘的速度控制需要闭环否则电机的实际转速会因负载变化而波动。常用的算法是增量式PID。// 文件路径Middlewares/control/pid_controller.c typedef struct { float kp; float ki; float kd; float target; float current; float last_error; float integral; float output; } PID_TypeDef; float PID_Update(PID_TypeDef *pid, float target, float current) { float error target - current; float delta_error error - pid-last_error; pid-integral error; if (pid-integral 1000.0f) pid-integral 1000.0f; if (pid-integral -1000.0f) pid-integral -1000.0f; pid-output pid-kp * error pid-ki * pid-integral pid-kd * delta_error; pid-last_error error; return pid-output; }这里做了一次积分限幅防止长时间误差累加导致输出过大底盘突然加速。6.5 遥控器解析遥控器接收机通常输出PWM信号或SBUS信号。PWM方式每个通道占一个定时器输入捕获通道SBUS则是一根信号线串行传输16个通道数据。下面以PWM方式为例// 文件路径Core/Src/main.c #include tim.h uint16_t channel_pwm[4]; void RemoteControl_Read() { channel_pwm[0] __HAL_TIM_GET_COUNTER(htim1); // 转向通道 channel_pwm[1] __HAL_TIM_GET_COUNTER(htim2); // 油门通道 channel_pwm[2] __HAL_TIM_GET_COUNTER(htim3); // 模式开关 channel_pwm[3] __HAL_TIM_GET_COUNTER(htim4); // 急停开关 } int16_t RemoteControl_GetSpeed() { // 假设油门通道PWM范围1000~2000中位1500 int16_t speed (channel_pwm[1] - 1500) * 0.1f; if (speed 100) speed 100; if (speed -100) speed -100; return speed; }6.6 主循环中的模式管理主循环需要周期性检查当前模式并根据模式调用不同的控制函数。// 文件路径Core/Src/main.c typedef enum { MODE_START 0, MODE_REMOTE, MODE_AUTO, MODE_E_STOP } ChassisMode; ChassisMode current_mode MODE_START; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_CAN1_Init(); MX_TIM1_Init(); current_mode MODE_REMOTE; while (1) { switch (current_mode) { case MODE_REMOTE: RemoteControl_Read(); int16_t speed RemoteControl_GetSpeed(); Chassis_SendSpeed(speed, 0); break; case MODE_AUTO: // 等待上位机通过CAN发来的目标速度和目标转角 // 数据已经在接收中断中处理完毕 break; case MODE_E_STOP: Chassis_SendSpeed(0, 0); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); // 刹车 break; default: break; } HAL_Delay(20); // 50Hz控制周期 } }7. AGV自主导航中的A*算法7.1 A*算法在AGV中的作用当底盘切换到自主导航模式时上位机需要完成三件事知道自己在哪里定位知道目标在哪里规划一条从当前位置到目标的路径。第三步就是路径规划算法的工作A*算法是其中最经典、最常用的一种。它能在有障碍物的栅格地图上找到一条从起点到终点的最短路径同时兼顾搜索效率。7.2 A*算法的基本原理A*算法维护两个集合Open List待考察的节点集合Close List已经考察过的节点集合。每个节点的评估函数为F(n) G(n) H(n)其中G(n)从起点到当前节点n的实际代价H(n)从当前节点n到终点的估计代价启发函数F(n)经过当前节点n的总估计代价。算法每次从Open List中选取F值最小的节点进行扩展直到找到终点或Open List为空。说明一下这是路径规划中H2的常用公式表达属于算法核心我这里保留公式是为了让有算法基础的同学直接对上。7.3 A*算法在AGV底盘上的简化实现下面给出一个简化的A*算法实现框架适合放到上位机中做路径规划。# 文件路径lib/astar.py import heapq class Node: def __init__(self, x, y): self.x x self.y y self.g 0 self.h 0 self.parent None def f(self): return self.g self.h def __lt__(self, other): return self.f() other.f() def heuristic(a, b): # 曼哈顿距离 return abs(a.x - b.x) abs(a.y - b.y) def astar(grid, start, end): open_list [] close_set set() start_node Node(start[0], start[1]) end_node Node(end[0], end[1]) heapq.heappush(open_list, start_node) while open_list: current heapq.heappop(open_list) if (current.x, current.y) (end_node.x, end_node.y): path [] while current: path.append((current.x, current.y)) current current.parent return path[::-1] close_set.add((current.x, current.y)) for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]: nx, ny current.x dx, current.y dy if nx 0 or ny 0 or nx len(grid) or ny len(grid[0]): continue if grid[nx][ny] 1: # 1表示障碍物 continue if (nx, ny) in close_set: continue neighbor Node(nx, ny) neighbor.g current.g 1 neighbor.h heuristic(neighbor, end_node) neighbor.parent current # 简化处理不做Open List去重实际应用时需优化 heapq.heappush(open_list, neighbor) return None # 没找到路径 if __name__ __main__: # 0可通行1障碍物 grid [ [0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 0, 0, 1, 0], [0, 1, 0, 0, 0], [0, 0, 0, 0, 0], ] path astar(grid, (0, 0), (4, 4)) print(path)7.4 A*算法用于底盘时的注意事项栅格粒度栅格地图的分辨率直接决定路径平滑度和计算量。过小的栅格会导致路径很绕过大的栅格会让底盘偏离目标。建议栅格大小根据底盘宽度设置比如底盘宽50cm栅格就设成50cm或稍大一点。路径平滑A*规划出来的路径是折线路径如果直接让底盘走折线转向会很生硬。实际应用中需要对路径做平滑处理比如使用贝塞尔曲线插值或者轨迹优化算法。动态避障A*是全局路径规划算法适合静态地图。如果环境中出现临时障碍物需要配合局部避障算法比如DWA动态窗口法实现“全局规划 局部避障”的组合方案。定时重规划底盘移动过程中如果地图信息发生变化比如障碍物移走了需要周期性重新执行A*算法。这个周期可以设成1秒或2秒具体频率取决于计算资源和环境变化速度。8. 常见问题与排查思路在实际调试线控底盘时最容易遇到下面几类问题。8.1 CAN通信不通问题现象常见原因解决思路CAN分析仪收不到报文波特率不匹配用CAN分析仪自动识别波特率或核对STM32的时钟树和分频配置没有报文但总线电压异常缺少终端电阻在总线两端各接120欧姆电阻数据偶尔丢帧线束过长或干扰过大使用双绞线缩短线束长度必要时加共模电感节点一直报错没有公共地确认所有CAN节点的GND连接到一起8.2 底盘上电后不动作问题现象常见原因解决思路电机不转控制指令未发到驱动器查看CAN分析仪确认报文ID和数据是否正确电机振动但不转使能信号未置位检查控制字的使能bit是否置1舵机不动转向角度值超出范围检查PWM或CAN报文的数值范围8.3 遥控切自主时底盘突然抖动这个问题的根本原因通常是模式切换瞬间底盘控制目标突然跳变。比如遥控模式下速度是50切换到自主模式的瞬间上位机给的目标速度是80底盘的PID会立刻加大输出导致抖动甚至失控。解决办法是在模式切换时增加一个平滑过渡逻辑让目标速度从一个值逐渐过渡到另一个值而不是瞬时切换。8.4 CAN总线共模干扰在电机频繁启停的底盘上CAN通信偶尔出错是非常常见的问题。排查思路如下用示波器观察CAN_H和CAN_L信号看差分信号是否正常确认底盘电源和CAN收发器电源是否隔离电机驱动线是否需要屏蔽处理外壳地是否可靠连接。小底盘空间有限经常出现电机线和CAN线走同一个线槽的情况这时候CAN线一定要用双绞线并远离电机动力线。9. 工程化建议与最佳实践9.1 协议设计文档必须先行底盘控制涉及多个模块主控板、驱动器、遥控器、上位机、导航算法。在动手写代码之前先把CAN报文协议确定下来并输出一份协议文档里面说明每个报文的ID、周期、数据格式、校验方式、字节序。哪怕只是自己一个人开发这个文档也能帮你避免“改了一处忘记另一处”的尴尬情况。9.2 安全功能要独立实现底盘的安全不能只依赖上位机的急停指令。建议在底盘控制层单独实现以下安全机制CAN通信超时检测如果一定时间内没有收到上位机或遥控器的有效指令底盘自动进入安全停车状态速度软限位限制底盘最大前进、后退速度防止算法异常导致底盘飞车转角软限位限制最大转向角度防止转向机构撞到机械限位急停开关手动急停开关必须直接硬接到底盘控制器的急停输入引脚不经过上层算法。9.3 报文校验不能省CAN硬件本身带有CRC校验能检测出传输错误。但在应用层我们仍然建议在数据场中加上累加校验或CRC校验。原因是CAN硬件校验只能保证“这帧报文传输过程中没有损坏”不能保证“这帧报文的内容是合理的”。应用层校验可以防止数据解析出错导致的异常控制。9.4 日志和调试手段底盘调试时经常遇到“偶发问题”没有日志很难定位。建议在底盘控制层预留串口日志接口输出以下信息当前工作模式目标速度/实际速度目标转角/实际转角CAN收发错误计数最近一次急停触发原因。有了这些日志配合CAN分析仪大部分问题都能快速定位。9.5 底盘标定不能偷懒很多人以为底盘买回来直接就能跑。实际上转向角度、速度反馈都需要标定。比如转向舵机PWM值1500对应车轮正前方但这个数值可能因为安装误差出现偏移。标定的操作很简单发送一组已知指令观察底盘实际动作记录偏差然后在控制代码里做修正。9.6 从小场景开始测试我做底盘测试时习惯遵循下面的流程加垫块把底盘架空先测试电机正转、反转、刹车是否正常让底盘在地上缓慢移动测试遥控模式基本功能测试急停开关和通信超时保护使用CAN分析仪录制底盘控制报文确认协议正确在小范围场地测试自主导航模式并逐步扩大测试区域。这条流程下来大部分问题都能在安全范围内暴露出来避免直接上路测试时出现意外。10. 下一步可以怎么学小型线控底盘涉及的技术点非常多这篇文章覆盖了CAN总线通信、底盘控制架构、遥控和自主导航双模式切换以及A*路径规划的基础应用。如果这些内容你已经掌握了下一步可以往这几个方向深入加入里程计标定利用编码器数据计算底盘位移做更精确的定位。引入IMU融合定位把IMU数据和轮式里程计做卡尔曼滤波融合提高姿态和位置估计精度。局部避障算法在A*全局路径规划的基础上加入DWA或TEB算法实现动态避障。更高级的CANopen协议如果你的驱动器支持CANopen可以学习PDO、SDO、对象字典的配置方法。底盘动力学建模对底盘的加速、制动、转向响应特性建模为上层算法提供更真实的前馈控制。我自己做底盘调试时最大的感受是不要急着把导航算法跑起来先把底层通信和运动控制做稳。CAN一通、遥控能跑、急停可靠这三个点拿下了后面加导航算法只是时间问题。如果这篇文章对你有帮助可以收藏备用。如果你正在做类似的小型无人驾驶底盘欢迎在交流中分享你的CAN协议和底盘控制方案一起把这类底盘的工程化经验沉淀下来。
返回列表