ARTICLE DETAIL

资讯详情

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

Jetson Orin NX CAN总线调试全流程:从MCP2515硬件焊接到SocketCAN自启动

Jetson Orin NX CAN总线调试全流程:从MCP2515硬件焊接到SocketCAN自启动 做Jetson Orin NX的CAN总线调试核心难点从来不是某个单独环节而是整个链条太长从元器件选型、引脚焊接、设备树适配到驱动加载、SocketCAN配置、开机自启动每一步都可能让你卡上一整天。最近我在Orin NX上对接机器人底盘和传感器把这块完整跑了一遍踩了不少坑也找到了比较稳的路径。这篇文章打算按真实操作顺序把“从硬件焊接到开机自启动”全流程拆开讲清楚文中涉及MCP2515模块、TJA1050收发器、Jetson 40pin接口、Linux内核驱动、SocketCAN工具链、systemd服务以及我在调试过程里遇到的典型问题和解决办法适合正在做Jetson相关机器人、车载设备或工控项目的朋友参考。1. 项目背景与方案选型为什么选择SPI转CAN而不是USB转CAN1.1 Jetson Orin NX的CAN总线需求从哪里来Jetson Orin NX作为一块算力不错的嵌入式平台经常被用做机器人主控、自动驾驶小车、工业控制器这类角色。这些场景里CAN总线几乎是绕不开的通信接口底盘电机驱动、IMU、激光雷达、车载ECU很多设备都是CAN协议输出。Orin NX本身算力强但接口资源并不原生覆盖CAN模块上没有集成CAN控制器这点和树莓派类似需要靠外部扩展。扩展CAN的方式无非三种SPI转CAN、USB转CAN、UART转CAN。从稳定性和实时性来说SPI转CAN是嵌入式的首选它不占用USB口不依赖USB控制器调度通信延迟也更可控。USB转CAN虽然即插即用很方便但在机器人这种多设备环境下USB口本来就很紧张摄像头、WiFi模块、调试线一插留给CAN适配器的位置就很尴尬。而且USB适配器在系统负载高的时候偶尔会出现丢帧的情况这个在实时控制场景里不太能接受。1.2 方案选型的对比和取舍我把几种方案放在一起做了个对比方便你判断自己的场景适合哪种方案成本接入方式实时性稳定性适合场景SPI转CANMCP2515低40pin GPIOSPI总线高高嵌入式量产、机器人主控USB转CANgs_usb中USB口中中快速原型验证、PC上位机UART转CAN中UART低中低速通信、简单点对点原生CAN控制器高板载最高最高对实时性要求极端的工业场景我这次用的方案是MCP2515芯片加TJA1050收发器这也是市面上最常见的SPI转CAN模块组合。MCP2515负责CAN协议处理TJA1050负责物理层差分信号转换两者配合很成熟Linux内核自带驱动Jetson上不需要自己写内核模块省了很多麻烦。选型时还有一个点容易忽略模块的逻辑电平。Jetson的40pin GPIO都是3.3V逻辑而很多CAN模块为了兼容传统5V系统会把MCP2515也供电到5V。这时候模块的SPI引脚高电平接近5V直接接Jetson的3.3V引脚很危险。我建议你优先选择明确标注“3.3V逻辑兼容”的模块或者用MCP2515供电3.3V、收发器采用TJA1051/3这类支持3.3V的型号。如果手里只有5V模块一定要加电平转换芯片或者确认模块板上已经做了电平转换。2. 硬件准备与焊接实操别小看这几根线问题多半出在这里2.1 元器件清单和接线规划硬件准备不复杂但每一项都要确认到位。我这边用的清单是这样Jetson Orin NX开发套件核心板加载板MCP2515 CAN模块板载TJA1050或TJA1051收发器120Ω终端电阻两个DB9公母头如果你的设备端是DB9接口杜邦线若干或者直接焊排针万用表、电烙铁、焊锡丝、热缩管接线是整个硬件部分最关键的一环。Jetson Orin NX载板的40pin接口定义与树莓派不大一样接线前一定要打开官方的引脚图确认别凭记忆接。以我这次默认采用的SPI接口为例参考接线如下MCP2515模块引脚Jetson 40pin引脚说明VCCPin13.3V或Pin25V必须先确认模块逻辑电平GNDPin6必须共地SCKPin23SPI0_SCKSPI时钟MOSIPin19SPI0_MOSISPI主机输出MISOPin21SPI0_MISOSPI主机输入CSPin24SPI0_CS0SPI片选INTPin18GPIO支持中断MCP2515中断输出必须接这里要特别说下INT引脚。MCP2515在收到数据或者发送完成时会拉低INT引脚处理器通过中断或轮询来处理。如果你不接INT虽然可以通过SPI轮询方式读到消息但实时性差很多而且驱动往往依赖中断来主动上报数据。很多自制板卡第一次调试起不来查来查去发现是INT引脚没接或者接错了复用功能的引脚。另外CAN_H和CAN_L是收发器输出的一对差分信号模块上通常有CANH、CANL两个接线端子直接连到总线上。如果你的设备端是DB9接头标准定义是Pin2为CAN_L、Pin7为CAN_H但也有非标接法接线前拿万用表测量确认一下最稳妥。2.2 焊接顺序和工艺细节硬件焊接上我踩过最大的坑是虚焊和相邻引脚短路。MCP2515模块引脚间距不算密但如果你用的是排针焊接时稍不留神焊锡就会连到隔壁引脚。这里分享几个实操经验第一先焊排针后焊DB9或者线缆。排针建议先把模块插在面包板上固定这样排针位置正、不会歪。烙铁温度控制在350℃左右每个引脚焊接时间不要超过3秒防止焊盘反复加热后脱落。第二焊完用万用表蜂鸣档逐个检查相邻引脚是否短路同时确认每一根信号线都通。这一步只要两分钟但能帮你省出后面两小时的排查时间。我见过不少人烧了板子才发现是GND和VCC之间短路这种低级事故完全可以通过上电前的电阻测量来避免。第三如果你不是用成品模块而是自己搭MCP2515加收发器电路务必要在VCC和GND之间加104去耦电容MCP2515的INT引脚还要接一个10kΩ上拉电阻到VCC。收发器侧如果使用的是TJA1050它的电源VCC一般是5V但信号引脚TXD和RXD与MCP2515互连时要注意电平匹配。如果用TJA1051/3这类宽电压收发器会省事很多。2.3 终端电阻、共地和走线注意事项CAN总线是个差分总线物理上要求在总线两端各接一个120Ω终端电阻。这个电阻的作用是匹配阻抗、消除信号反射。如果你的总线上只有两个节点那就必须在两头都接如果节点很多只在最远两端接中间节点不要接。我遇到过很典型的情况单独测试一个CAN模块的时候发送数据总是报错candump一开全是错误帧最后发现是因为总线上只有一个节点没有任何设备去ACK它。CAN协议里发送节点要等到总线上的ACK slot被其他节点拉低才认为发送成功。如果你只是单节点测试帧发出去没人回应就会反复重发并计入错误计数。这种情况下最简单的验证方法是在CAN_H和CAN_L之间接一个120Ω电阻模拟总线终端条件避免反射加剧错误。还有一个经常被忽略的点是共地。CAN是差分信号理论上可以不共地但收发器的共模输入范围有限如果两个节点之间地电位差太大轻则误码重则烧掉收发器。所以我的习惯是所有CAN节点之间至少保证一条GND连通。走线方面CAN_H和CAN_L尽量不要用飞线跨很长的距离两根线最好缠绕在一起或者用双绞线减少共模干扰。Jetson板子内部临时调试可以用杜邦线但一旦要跑起来跑长期尽量换成正规的双绞线或者屏蔽双绞线。3. 内核驱动与SocketCAN配置让系统认识你的CAN设备3.1 上电后先确认SPI设备有没有被识别硬件接好后第一步不是急着配CAN而是先确认系统有没有看到SPI设备。Jetson上电进入Ubuntu后输入以下命令ls /dev/spidev* ls /sys/bus/spi/devices/正常情况你会看到类似spi0.0这样的设备其中0.0表示SPI总线0、片选0。如果这里什么都没有说明SPI控制器没起来或者引脚复用不对物理层的问题还没解决不要往下走。接着查看内核日志确认MCP2515有没有被识别dmesg | grep -i spi dmesg | grep -i mcp如果你的设备树里已经注册了MCP2515节点打开系统日志就能看到类似mcp2515 spi0.0: MCP2515 successfully initialized这样的信息。如果没看到那大概率是设备树的问题需要给内核补充硬件信息。3.2 设备树Overlay把MCP2515挂到SPI总线上Jetson的设备树和树莓派的overlay机制并不完全一样但思路类似你得告诉内核在哪个SPI总线上、哪个片选位置、挂了一个什么设备。这里以常见的L4TLinux for Tegra环境为例说明一下通用做法。第一步写一个设备树片段内容类似下面这样/dts-v1/; /plugin/; / { compatible nvidia,p3737-0000p3509-0000; fragment0 { target-path /; __overlay__ { mcp2515_osc: mcp2515_osc { compatible fixed-clock; clock-frequency 8000000; #clock-cells 0; }; mcp2515: mcp25150 { compatible microchip,mcp2515; reg 0; clocks mcp2515_osc; spi-max-frequency 5000000; interrupt-parent gpio; interrupts 某GPIO号 IRQ_TYPE_LEVEL_LOW; status okay; }; }; }; };这里有一个反直觉的地方MCP2515本身是芯片不是晶振为什么设备树里要写时钟因为MCP2515需要外接晶振常见的模块板载的是8MHz晶振所以驱动需要知道晶振频率。如果你的模块是16MHz晶振时钟频率要改成16000000。如果不配置时钟驱动可能会因为无法确定晶振频率而初始化失败。第二步将dts编译成dtbodtc - -I dts -O dtb -o mcp2515-overlay.dtbo mcp2515.dts第三步把编译好的dtbo放到引导加载目录并且修改extlinux.conf。不同JetPack版本、不同载板的做法会有差异但思路一样找到当前内核实际加载的dtb路径替换成包含overlay的新dtb或者通过bootloader的overlay机制在启动时加载。操作前一定先备份原始dtb文件万一变砖了还能用恢复模式刷回来。如果你对设备树不熟这里有另一个偷懒但有效的办法直接用USB转CAN适配器搭起整条通信链路验证业务逻辑没问题之后再回来折腾SPI转CAN的设备树。很多人在项目初期把时间浪费在设备树上其实先用USB适配器跑通协议再回头处理硬件接入整个项目排期会健康很多。3.3 加载SocketCAN驱动并启动can0设备树配置好并重新启动后内核会自动加载mcp251x驱动模块。如果驱动还没加载可以手动执行sudo modprobe mcp251x然后查看网络接口列表ifconfig -a如果一切正常你会看到can0接口。第一次配置波特率并启动我通常用以下命令sudo ip link set can0 type can bitrate 500000 sudo ip link set can0 up这里500000就是500kbps波特率具体数值要和你连接的CAN总线设备保持一致。CAN总线上所有节点的波特率必须相同否则通信直接失败。设置完成后用下面的命令确认接口状态ip -details link show can0输出里应该包含state UP和bitrate 500000看到这些信息就说明CAN接口已经激活。如果你用的是MCP2518FD这类支持CAN FD的芯片并且dts里注册的驱动是mcp251xfd还能开启CAN FD模式sudo ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on sudo ip link set can0 upCAN FD和经典CAN不兼容总线上所有节点都必须支持CAN FD才能用这个模式。我建议在没有明确需求的情况下先跑经典CAN稳定之后再考虑能不能上FD。3.4 SocketCAN调试工具链can-utils的基本用法SocketCAN是Linux内核原生的CAN协议栈接口被抽象成网络设备所以像ip、ifconfig这类工具也能看到can0。但真要收发CAN数据得靠can-utils这个工具包sudo apt update sudo apt install can-utils安装完之后最常用的四个命令是cansend发送CAN帧。比如cansend can0 123#DEADBEEF表示往can0发送一个ID为0x123、数据为DE AD BE EF的帧。candump监听CAN帧。candump can0会把总线上所有数据打印出来加-e参数可以同时显示错误帧。cansniffer类似candump但按ID分类显示适合观察重复出现的帧。canfdtest专门测试CAN/CAN FD链路的工具。以我实际调试时的习惯我会先开一个终端跑candumpcandump -e can0然后在另一个终端跑cansendcansend can0 123#DEADBEEF如果链路正常candump那边应该立刻打印出同样的帧。如果只发送不接收先别急着查软件回头看看终端电阻、波特率、共地这三个物理层要素。4. 数据收发调试与总线诊断从跑通到跑稳4.1 单节点自测怎么判断电路到底通不通很多人的第一反应是手头只有一个CAN节点怎么自测这里有两个层面可以测。第一层用SocketCAN的loopback模式。把can0配置成回环模式数据从控制器发出后不经总线直接回到接收端sudo ip link set can0 down sudo ip link set can0 type can bitrate 500000 loopback on sudo ip link set can0 up cansend can0 123#DEADBEEF candump can0如果loopback模式下能收到自己发的帧至少说明MCP2515和Jetson之间的SPI通信、驱动加载、设备树配置都是正常的。但要注意loopback模式绕过物理层收发器不能验证TJA1050那边是否正常也不能验证你接的CAN_H/CAN_L线路是否正确。第二层用逻辑分析仪或者示波器看物理波形。把探头夹在CAN_H和GND之间再用cansend发数据正常情况下隐性电平约2.5V显性时CAN_H会跳到3.5V左右。如果波形完全不动说明收发器或者线路有问题如果波形畸变严重多半是终端电阻没接或者走线太长。4.2 双节点通信从“能发”到“能收”loopback测过只是第一步真正要跑业务必须有双节点。最简单的办法是找另一块带CAN的设备或者再弄一个USB-CAN适配器接电脑。假设你手头有USB-CAN适配器插到电脑上用PC端的CAN调试助手或者Linux下can-utils去收发。两个节点的接线如下设备A的CAN_H接设备B的CAN_H设备A的CAN_L接设备B的CAN_L设备A的GND接设备B的GND总线两端各接120Ω终端电阻波特率设置一致后在Jetson上发在电脑端收确认数据一致。然后反过来电脑发Jetson上candump收。这里我遇到过非常隐蔽的问题Jetson能收到电脑发的所有帧但电脑收不到Jetson发的。排查半天发现Jetson的can0没有设波特率就up了默认波特率是空值导致控制器根本没按正确时序发数据。用ip -details link show can0检查bitrate是否为预期值这种低级错误其实很常见。还有一个高概率问题ID过滤。candump如果不带过滤条件会打印所有ID但如果你在电脑调试助手里设置了ID过滤只接收某个范围的IDJetson发的ID不在范围内自然就收不到。这不是硬件问题是软件配置问题。4.3 错误帧、bus-off和负载率总线健康怎么看CAN总线的优势之一是会自我诊断但坏消息是错误帧的出现频率直接反映总线健康状况。用candump或candump -e观察如果总线上不断出现错误帧要立刻停下来排查而不是继续业务调试。CAN错误大概分几类位错误、填充错误、CRC错误、格式错误、ACK错误。对调试者来说最需要关注的三个原因缺终端电阻波形反射导致位错误表现为随机错误帧。波特率不一致所有节点都可能报错而且报错节奏非常规律。单节点发送无人ACK发送节点会持续报ACK错误错误计数不断上升直到进入bus-off状态。bus-off是CAN控制器保护机制之一当发送错误计数超过256控制器会主动离线不再参与总线通信。遇到bus-off通常要重启接口才能恢复sudo ip link set can0 down sudo ip link set can0 up查看接口的错误统计信息可以用ip -details -statistics link show can0输出里能看到rx、tx以及错误计数这是判断总线状态的重要依据。我在现场排查故障时第一件事就是看这个统计比抓一整天错误帧效率高得多。负载率也是总线健康度的指标但很多人算不明白。这里给个简化版公式经典CAN标准帧8字节数据算上帧头帧尾和填充位约100~130个位。每一秒发送1000帧波特率500kbps负载率大约是(110 × 1000) / 500000 22%。也就是说负载率其实就是“每秒总线上的总位时间”占“波特率”的比例。总线负载率超过70%后实时性会明显下降高优先级帧也可能被低优先级帧堵住。如果要提升吞吐量优先考虑提高波特率或者改CAN FD。5. 开机自启动的完整配置让CAN接口每次上电自动就绪5.1 使用systemd服务实现开机自启动Jetson虽然运行的是Ubuntu但它是嵌入式设备不可能每次都手动敲命令启动CAN接口。项目里最稳妥的方式是用systemd服务来管理。我的做法是写一个脚本专门做CAN接口的初始化等幂操作#!/bin/bash # /usr/local/bin/setup_can0.sh # 等待内核完成驱动probe for i in $(seq 1 10); do if [ -e /sys/class/net/can0 ]; then break fi sleep 1 done # 配置波特率并启动 ip link set can0 type can bitrate 500000 ip link set can0 up # 如果支持CAN FD可以增加下面两行 # ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on # ip link set can0 up exit 0脚本里等我加入了一个循环因为Jetson开机时USB、SPI等外设的初始化时序不能完全预知保险起见等待can0网络接口出现最多等10秒。如果不用这个等待逻辑systemd服务可能在驱动还没probe完时就执行然后报“No such device”后面即使can0出现了也不会自动up。然后创建systemd服务文件[Unit] DescriptionSetup CAN0 interface Afternetwork.target Beforenetwork-pre.target [Service] Typeoneshot RemainAfterExityes ExecStart/usr/local/bin/setup_can0.sh [Install] WantedBymulti-user.target最后启用服务sudo chmod x /usr/local/bin/setup_can0.sh sudo systemctl daemon-reload sudo systemctl enable setup-can0.service sudo systemctl start setup-can0.service启动后用systemctl status setup-can0.service查看状态用journalctl -u setup-can0.service查看日志。如果服务显示active并且can0已经up开机自启动就算搞定了。5.2 启动时序问题为什么有时候服务明明执行了can0还是没起来systemd服务排坑里最典型的一个场景是重启Jetson后服务显示active但can0就是不出现。这时候先别急着改服务配置先执行一次ip link show can0如果输出提示“No such device”基本可以断定是服务执行时驱动还没完成初始化。我推荐的等待循环并不是唯一的解法你还可以把服务依赖写成Aftersys-subsystem-net-devices-can0.device但这要求系统已经注册了can0的device unit如果驱动probe本身失败了写这个依赖反而会让服务一直等待。实际项目中我用等待循环加日志的方式最简单也最容易定位问题。还有一个更隐蔽的问题Jetson的SPI控制器可能会在待机或休眠后重启导致can0接口消失。如果你发现设备跑了一段时间后can0不见了多半是电源管理把SPI控制器挂起了。这个可以在设备树里给SPI节点加一条status okay并禁用runtime suspend但具体配置取决于内核版本。我的经验是在无人值守的机器人场景下没必要为了省那一点点功耗去开复杂的电源管理策略稳定性优先。5.3 多路CAN接口的名称固定问题如果你接了不止一路CAN比如一路连底盘、一路连传感器系统启动时注册顺序可能导致接口名不稳定变成can0、can1互换。解决思路是用udev规则根据设备路径或者序列号固定接口名。对SPI转CAN这种固定设备来说更简单的方式是在内核层面通过设备树节点顺序确定接口名can0、can1的分配顺序一般就是设备树里的注册顺序。只要你在设备树里保持两个MCP2515节点的固定顺序can0和can1的对应关系就基本稳定。如果你一定要用udev做保险可以这样写规则KERNELcan*, SUBSYSTEMnet, ACTIONadd, DRIVERSmcp251x, ATTR{device/../of_node/reg}0, NAMEcan0但说实话这种规则写在Jetson上还需要额外调试应对临时项目有点杀鸡用牛刀。我的建议是先把单路调稳再考虑多路命名。6. 常见问题速查与避坑笔记6.1 高频故障排查速查表故障现象可能原因排查和解决办法ip link show看不到can0驱动未加载、设备树未生效、SPI接线错误dmesg查mcp251x日志检查SPI设备列表核对接线can0 up时提示No such device网络接口未注册驱动probe失败确认INT引脚配置确认晶振频率确认DTS是否编译加载成功cansend后candump看不到数据波特率不一致、ID过滤、物理层不通检查双方波特率去掉ID过滤检查CAN_H/CAN_L链路全是错误帧或者发送即报错缺终端电阻、总线电压异常、波特率不对在总线两端接120Ω电阻万用表测CAN_H/CAN_L静态电压单节点发送总是报ACK错误总线上没有其他节点应答加第二个节点或者暂时用loopback模式测试开机后can0没有自动upsystemd服务执行太早或服务未启用给脚本加等待循环systemctl enable确认开机启动跑一段时间后can0消失SPI控制器被电源管理挂起检查内核日志禁用相关设备的runtime suspend能收不能发且无报错可能被另一个节点总线仲裁压制或发送缓冲区满用candump先观察总线流量再用cansend发送测试帧6.2 我亲身踩过的三个坑第一个坑一开始我用的CAN模块是5V逻辑版本直接接Jetson的3.3V SPI引脚结果系统日志里SPI通信时好时坏偶尔can0能起来重启后又消失。排查了很久才发现是电平不匹配导致芯片间歇性锁死。换了一个3.3V逻辑兼容的模块后问题彻底消失。硬件电平匹配这个事真的一定要一开始就确认清楚。第二个坑调试时我发现candump能看到自己的错误帧但没意识到这是ACK错误。我以为模块坏了换了好几个模块都一样后来才反应过来那是因为总线上只有一个节点发送的帧根本没人应答。CAN协议规定发送节点没有收到ACK就重发重发次数多了就进bus-off。这个问题不算复杂但不亲身踩一遍真的很难想到。第三个坑systemd服务配置好之后我手动执行脚本一切正常但一重启就失效。查下来发现服务启动时间在内核加载驱动之前脚本里的ip link set命令提前执行报错了而systemd日志又没细看。后来加上等待循环并在服务里设置Afternetwork.target重启验证才通过。从此我习惯在任何开机自启动脚本里都主动加设备等待逻辑不依赖系统默认的启动顺序。6.3 几条实用经验总结如果你现在正准备在Jetson上做CAN项目下面几条是我最想提前告诉你的一先用USB-CAN适配器把协议层跑通再上SPI转CAN。USB适配器免设备树、免焊接能让你快速验证通信逻辑是否正确。等项目整体通了再花时间调设备树和驱动避免两件事交叉在一起出问题都不知道从哪查。二设备树改动一定要备份原文件。Jetson启动依赖dtb改错了很可能开不了机。大部分Jetson载板有恢复模式能救回来但会折腾你一晚上。改之前cp一份备份成本很低。三转速表一样对待错误帧。错误帧是总线给你的诊断信息不是噪声。只要candump看到错误帧说明物理层或配置有硬伤先解决再往下走。四can接口的自启动配置永远要考虑“设备还没准备好”这个竞态条件。不管是systemd服务还是rc.local脚本加上等待和重试逻辑比什么启动顺序都可靠。五调试CAN不要只依赖一种工具。电脑端调试助手适合看协议数据逻辑分析仪适合看波形时序万用表适合量电平。多管齐下问题定位速度能快好几倍。最后分享一个小技巧candump支持时间戳输出加-t d可以显示相对时间加-t a可以显示绝对时间。在分析两个节点之间通信延迟时这个时间戳非常有用。你甚至可以把candump输出重定向到文件再导入Python做离线分析尤其是排查偶发性丢帧问题时比盯着终端看有效得多。
返回列表