☰
嵌入式Linux CAN总线调试:libsocketcan与canutils编译安装实战
2026/10/1 9:15:20 网站建设 项目流程

1. 为什么调试CAN总线要先搞定这两套工具

做嵌入式Linux开发绕不开CAN总线,尤其是汽车电子、工业控制、机器人这几个方向。车机网关、BMS电池管理、电机控制器、传感器采集,全靠CAN把各个节点串起来。我在RK3568、i.MX6ULL这些板子上调过不少CAN驱动,最常见的尴尬局面是:内核配置打开了,ip link也能看到can0了,结果想发一帧报文出去,却发现手头连个顺手的工具都没有。

这时候libsocketcan和canutils就是必需品。libsocketcan是一个用户态库,把SocketCAN的内核接口封装成一套干净的API,你写应用程序的时候不用直接跟socket结构体死磕;canutils则是一组命令行工具集,包含cansend、candump、cansequence、canutils这些命令,专门用来发报文、抓报文、做链路测试,调试驱动和验证通信链路非常方便。

这篇文章面向的是刚接触Linux CAN开发、或者已经在用SocketCAN但一直没把工具链理顺的工程师。我会把libsocketcan和canutils从下载、编译、安装到实际使用整个流程捋一遍,重点讲那些文档里不会写的坑。两块网卡互发、使用candump抓取日志、用cansend手动注入报文来模拟故障,这些场景我都实战验证过,踩过的坑也会一并整理成速查表,方便你直接对照排查。

2. 动手之前,先搞明白SocketCAN和这两套工具的定位

2.1 内核里已经有的东西,为什么还要装额外的库

很多人第一次查资料时容易被绕晕:明明Linux内核自带了can子系统,ip命令也能配置CAN接口,为什么还要专门装libsocketcan和canutils?

SocketCAN是内核提供的CAN协议实现,它在网络协议栈的基础上进行了CAN总线适配,让CAN设备可以被当作网络接口来管理。这就是为什么你能用ip link set can0 up type can bitrate 500000来配置波特率、启动接口。但这只是内核提供的底层能力,用户态想操作它,还是得通过socket接口去编程,过程比较繁琐:要socket()创建套接字、bind()绑定接口、构造struct can_frame填充报文。这些操作写起来很啰嗦,而且容易出错。

libsocketcan的作用就是把这一层封装起来。它提供了can_do_start()、can_do_stop()、can_set_bitrate()这样的接口函数,你只需要几行代码就能完成对CAN接口的操作,不需要关心底层的setsockopt参数怎么填、结构体怎么初始化。对于做应用层开发的工程师来说,能省掉大量不必要的细节。

canutils则完全是另一条路线,它不提供库,而是提供可执行命令。它基于libsocketcan之上,让你在shell里面就能直接操作CAN总线。比如调试驱动时,我想快速确认can0能不能正常把报文发出去,一条命令就搞定了,不用写一个测试程序然后交叉编译再拷到板子上。

所以这两个工具是互补的:canutils偏运维和调试,libsocketcan偏程序开发。如果你是做应用开发的,两个都值得装;如果只是调驱动、查链路,只装canutils就够了。

2.2 交叉编译还是本机编译,看你的目标平台

安装方式取决于你的目标平台。如果你在x86的Ubuntu机器上直接做CAN开发调试(通过USB-CAN适配器连接外部设备),那直接本机编译安装就行,很省事。如果你的目标平台是ARM开发板,比如RK3568、IMX6ULL,那就要用交叉编译工具链,把编译出来的二进制和库文件拷到板子上运行。

我强烈建议你本机也装一份、板子也装一份。本机那份用来做语法验证和快速原型,板子那份才是真正跑业务用的。交叉编译的时候要注意工具链的版本和内核头文件版本,否则可能出现结构体大小不一致的问题,后面我会详细讲。

3. libsocketcan安装实战:源码编译与两个必踩的坑

3.1 下载与编译,一条龙走通

libsocketcan的源码托管在GitHub上,是开源的,没有复杂的依赖,编译流程非常标准。

# 克隆源码 git clone https://github.com/linux-can/libsocketcan.git cd libsocketcan # 生成configure脚本 ./autogen.sh # 配置编译选项 ./configure --host=arm-linux-gnueabihf --prefix=/usr/local/arm/libsocketcan

这里说明一下:如果是在x86本机编译,--host参数可以不加,默认就是本机编译。如果是在给ARM板子交叉编译,--host要指定成你的交叉工具链前缀,比如我的工具链是arm-linux-gnueabihf-,那--host=arm-linux-gnueabihf。--prefix指定安装路径,建议单独设置一个输出目录,避免污染系统路径,后面打包拷贝也方便。

接着执行编译和安装:

make -j4 make install

交叉编译时,如果遇到报错cannot find -l socketcan,别慌,这说明你本机的库路径里没有libsocketcan的库。解决办法是在config时指定LDFLAGS和CFLAGS:

./configure --host=arm-linux-gnueabihf --prefix=/usr/local/arm/libsocketcan \ LDFLAGS="-L/usr/local/arm/libsocketcan/lib" \ CFLAGS="-I/usr/local/arm/libsocketcan/include"

这一步做完,理论上就能跑通。编译产物里会有libsocketcan.so和头文件libsocketcan.h,它们在--prefix指定的目录里。

3.2 坑一:交叉编译工具链前缀写错,浪费半小时

我第一次交叉编译libsocketcan的时候,--host写了arm-linux-gnueabi,但我的工具链实际前缀是arm-linux-gnueabihf。configure阶段没报错,到链接阶段就提示找不到编译器,排查半天才发现是前缀写错了。

所以交叉编译前,建议先确认工具链的前缀:

ls /usr/bin/arm-*

看到什么前缀就用什么前缀。如果工具链放在自定义目录,记得先把工具链的bin目录加入PATH:

export PATH=$PATH:/opt/arm-gcc/bin

3.3 坑二:依赖关系导致编译出来的库在板子上跑不起来

如果你是用交叉编译工具链编的库,拷到板子上运行时接到error while loading shared libraries: libsocketcan.so.2: cannot open shared object file,说明系统的动态链接器找不到这个库。两种解决办法:

第一种,把库文件放到板子的/usr/lib或者/lib,然后执行ldconfig刷新缓存:

cp libsocketcan.so.2 /usr/lib/ ldconfig

第二种,把库放在自定义目录,然后设置环境变量:

export LD_LIBRARY_PATH=/opt/libsocketcan/lib:$LD_LIBRARY_PATH

我在实际生产环境中更推荐第一种,因为ldconfig之后所有程序都能自动找到,不用每个shell都设置环境变量。这个坑也很常见,很多新手编译完了没拷库,就直接拿着编译产物去板子上跑,结果就是各种cannot open shared object file。

4. canutils安装实战:版本选择与内核依赖

4.1 下载canutils源码并交叉编译

canutils的仓库也在GitHub上,老版本叫canutils,新版本已经拆分成can-utils(中间多了个横杠)并增加了更多工具。不过目前嵌入式行业里大家习惯还是叫canutils,我用的是linux-can仓库下的can-utils,功能更全,包含cansend、candump、cangen、cansequence、canfdtest等。

git clone https://github.com/linux-can/can-utils.git cd can-utils # 生成configure脚本 ./autogen.sh # 交叉编译配置,prefix单独指定输出目录 ./configure --host=arm-linux-gnueabihf --prefix=/usr/local/arm/canutils make -j4 make install

canutils对libsocketcan是有依赖的,它在底层也要调用libsocketcan的部分接口,所以编译canutils之前,最好把libsocketcan先编出来并安装到指定目录,然后配置canutils的编译环境时把libsocketcan的库路径加进来:

./configure --host=arm-linux-gnueabihf --prefix=/usr/local/arm/canutils \ LDFLAGS="-L/usr/local/arm/libsocketcan/lib" \ CFLAGS="-I/usr/local/arm/libsocketcan/include"

如果不加这步,configure可能会报libsocketcan.h not found之类的错误。如果你用的是较新的can-utils版本,它们对libsocketcan的依赖可能不是强制的,但加上总没错,功能完整性有保障。

4.2 内核版本和头文件版本要对应,不然编译都过不去

canutils里有些工具会直接使用内核头文件定义的struct can_frame、struct canfd_frame这些结构体。编译时如果工具链自带的内核头文件版本太老,会导致编译报错,比如找不到CANFD_MTU这种宏定义。

解决办法是把目标板子对应内核源码里的include/uapi/linux/can.h拷贝到交叉编译工具链的头文件目录里,覆盖掉旧的。我在编译支持CAN FD的工具时遇到过这个问题,把内核头文件替换成目标板对应版本之后就正常了。

如果你的内核开启了CAN FD支持,一定要确保canutils也支持CAN FD。老版本的canutils不支持CAN FD,发送FD帧会直接报错。最新的can-utils已经支持,建议直接用最新的。

4.3 验证安装:在板子上跑一下 cansend --help

编译完拷到板子上之后,先跑一下cansend --help或者candump --help,看能不能正常输出帮助信息。如果提示not found,可能是二进制文件权限问题,chmod +x一下就好。如果提示No such file or directory,大概率是动态库没找到,对照上面3.2节的方法处理。

5. 核心命令实战:从配置网卡到收发报文

5.1 配置CAN接口:ip命令一条龙

SocketCAN把CAN接口当成网络接口来管理,所以配置接口用的是ip命令。这一步核心步骤我列一下:

# 设置波特率并启动can0 sudo ip link set can0 down sudo ip link set can0 type can bitrate 500000 sudo ip link set can0 up

第一行先down掉,是为了确保配置时接口处于初始状态,不然设置波特率可能报RTNETLINK answers: Device or resource busy。第三行up之后,用ip link show can0确认状态,看到NOARP标志和UP状态就说明接口正常起来了。

如果要配置CAN FD模式,命令稍微变一下:

sudo ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on

这里bitrate是仲裁段波特率,dbitrate是数据段波特率,FD模式下通常数据段比仲裁段快很多,比如仲裁段500k、数据段2M。具体数值取决于总线上所有节点的协商结果,不是随便设的。

5.2 发送报文:cansend的几种玩法

cansend是最常用的发送命令,基本格式是:

cansend <接口名> <帧ID>#<数据>

比如往can0上发一个ID=123、数据为DE AD BE EF 01 02 03 04 的标准帧:

cansend can0 123#DEADBEEF01020304

如果是扩展帧(29位ID),在ID后面加一个大写R表示远程帧,或者直接写标准ID也可以:

cansend can0 1F334455#1122334455667788

注意,#后面的数据必须成对出现,不能出现单个十六进制字符,不然会报错。如果不想手动构造数据,可以用cangen随机生成报文来做压力测试:

cangen can0 -v

cangen默认会不断地生成随机ID、随机长度、随机数据的CAN帧,非常适合做总线负载测试。加-v参数会把每一帧的内容打印出来,跟candump配合使用可以实时观察。

5.3 接收报文:candump抓包与实时监控

candump是抓CAN报文的神器,它会把总线上所有的帧都打印出来:

candump can0

默认输出格式长这样:

can0 123 [8] DE AD BE EF 01 02 03 04 can0 456 [4] 11 22 33 44

第一列是接口名,第二列是帧ID,方括号里是数据长度,后面是实际数据。这个输出格式看着简单,但在分析问题的时候信息量很大。我调试时经常需要把抓到的报文保存下来做离线分析,加-L参数就能存成日志文件:

candump -L can0 > can_log.txt

还有一个比较隐藏的用法,-n参数指定抓几帧后自动退出:

candump -n 10 can0

这在自动化测试脚本里非常有用。比如脚本里想验证“发出去5帧,总线上能抓到5帧”,用-n配合统计逻辑,整个验证流程就很干净。

candump还支持过滤,只抓特定ID的帧。方式是在接口名后加,然后写过滤条件。比如只抓ID=123的帧:

candump can0,123:7FF

这里的123:7FF是ID和掩码,7FF表示只匹配低11位(标准帧),这个语法跟tcpdump的过滤规则思路类似,上手很快。你还可以加-t d让时间戳以带小数点的秒数显示,方便分析报文间隔。

5.4 双接口互发:验证链路是否通的终极方法

拿到一块板子,上面有can0和can1两个CAN控制器,想验证这两路之间能不能通信,最快的办法就是把can0和can1用杜邦线物理相连(CAN_H接CAN_H、CAN_L接CAN_L),然后一端发、一端抓:

# 终端1:启动can1并抓包 sudo ip link set can1 up type can bitrate 500000 candump can1 # 终端2:配置can0并发送 sudo ip link set can0 up type can bitrate 500000 cansend can0 123#DEADBEEF

如果can1端能抓到can1 123 [4] DE AD BE EF,说明两路CAN收发器、控制器、内核驱动、用户态工具全链路正常。这个方法是我每次拿到新板子必做的第一项测试,成本低、见效快。

如果两端都连好了却抓不到帧,常见的可能原因有几类:波特率没有对齐、双绞线接线接反(CAN_H和CAN_L调换)、没有共地、终端电阻没有接。其中终端电阻是最容易被忽视的,高速CAN总线要求在总线的两端各接一个120欧姆终端电阻,如果链路短、速率低也许能勉强工作,但速率一高就容易出错。

5.5 cansequence:自动检测丢帧和错序

cansequence这个命令很多人不太熟悉,但它是我调试CAN通信质量时最依赖的工具,经常用来自测虚拟CAN接口。它专门用来测试链路的丢帧率和时序正确性。用法是两端配合,一端作为发送端:

cansequence can0

另一端作为接收端:

cansequence can1

当一端发、一端收时,接收端会自动统计收到的帧序号是否连续,如果不连续就说明有帧丢失,并按时间累计丢帧率。这个工具做长时间稳定性测试很好用,跑个几小时看最终的漏帧统计,基本可以判断链路质量。注意,这个工具会占满总线带宽,测试期间不要在上面跑其他CAN业务。

6. 常见问题与排查技巧实录

6.1 问题速查表

我把实际工作中遇到的高频问题整理成了一张表,按症状、可能原因、解决办法三个维度分类,用起来非常方便:

现象可能原因解决办法
ip link set can0 up报No such device内核没配置CAN驱动或设备树没使能检查内核CONFIG_CAN、CONFIG_CAN_RAW等配置项,检查设备树中CAN节点状态
RTNETLINK answers: Device or resource busy接口没有先down掉就设置参数先执行ip link set can0 down再配置波特率
cansend报write: No buffer space available发送缓冲区满,总线可能被阻塞检查对端是否在线、波特率是否匹配,降低发送频率
candump抓不到任何报文接口没起来或滤波器配置错误用ip link show can0确认状态为UP,检查candump过滤规则
发送时报Operation not supported内核SocketCAN版本不支持某些特性确认内核版本,检查CONFIG_CAN_CALC_BITTIMING等选项
交叉编译时报cannot find -lsocketcanlibsocketcan的库路径没配置configure时加LDFLAGS="-L/你的路径/lib"
板子上运行时报No such file or directory动态库缺失或架构不匹配拷贝对应架构的libsocketcan.so到板子并执行ldconfig

6.2 “can’t locate document: /notsupported.asp”这类报错别搞混

有些搜索热词里会出现can't locate document: /notsupported.asp这种报错,这是纯网页服务器相关的报错,跟CAN总线没有任何关系。我之前也遇到过,看到“can't”就以为是CAN工具包的问题,结果排查半天在浏览器里打开设备管理页面时才反应过来。如果你在配置工业网关或车机设备时看到这个报错,去检查web服务配置或设备固件就行,不用动CAN工具链。这个报错信息价值不大,果断跳过就好。

6.3 排查CAN通信问题的心法:先分层后逐段验证

CAN通信链路可以分成四层:物理层(线缆、接头、终端电阻)、数据链路层(控制器、收发器)、内核驱动层(SocketCAN驱动、设备树)、应用层(canutils或自研程序)。排查问题时我一般从物理层往上逐层确认:

第一步确认物理连接是否正确,用万用表量一下CAN_H和CAN_L之间的电阻,正常情况下应该能看到60欧姆左右(两个120欧姆终端电阻并联)。如果量出来是120欧姆,说明只接了一个终端电阻;如果量出来接近0,说明有短路。

第二步确认波特率是否一致,总线上所有节点的波特率必须严格一致,差一点都会导致通信异常。可以用示波器量CAN_H对地的信号,数一下一帧的时间长度,反推实际波特率。

第三步确认内核驱动是否正常,dmesg | grep can看看有没有报错。如果设备树里CAN节点没有使能,或者GPIO的复用功能配错了,内核对这个设备可能完全无感。

第四步确认用户态工具是否正常,按照第5节的双接口互发测试来验证。如果发送不报错但接收端抓不到,大概率是物理层或波特率的问题。如果发送直接报错,内核驱动或设备树的概率比较大。

7. 实测经验:一套完整的验收流程可以这样跑

写到最后,我分享一个实际项目中常用的快速验收流程。拿到一块新的CAN板子,我通常会在半小时内按下面这套顺序跑完,确认工具链和硬件都正常:

首先配置接口并启动,用can0和can1做回环测试验证基本功能。然后跑一次candump can1 &放后台,再通过cansend can0 123#DEADBEEF发一帧,确认能收到。然后测试扩展帧和错误帧:cansend can0 00000123#01020304,并在对端抓包确认ID能区分。接着挂载cangen can0做随机压力测试,同时用candump全量抓包,确认长时间运行无丢帧、无bus-off。最后用cansequence can0 can1做一次完整的链路质量测试,确认丢帧率在可接受范围内。这一套跑下来,硬件的稳定性基本就有底了。

如果你需要给程序提供可调用的接口,libsocketcan的API也很直观,基本流程是:can_do_start()启动接口、can_do_stop()停止接口、can_set_bitrate()设置波特率。我自己写CAN通信服务时,把libsocketcan封装了一层,用起来比直接操作socket清爽很多。

根据我个人在实际调试中的体会,libsocketcan和canutils这两套工具最难得的一点是:它们把内核SocketCAN的底层细节封装得足够简单,让工程师可以把更多注意力放在业务逻辑上。前期花十几分钟把环境搭好、把工具链理顺,后面无论是调驱动、查故障还是做自动化测试,效率都能提升一大截。别嫌装工具麻烦,这一步做扎实了,后面少走很多弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询