模块 1 实践项目

练习 1:虚拟 CAN 环境搭建

目标:独立创建和验证 vcan 设备。

难度:入门

步骤:

  1. 加载 vcan 内核模块
  2. 创建 vcan0 和 vcan1 两个虚拟总线
  3. 验证两个总线互相独立(向 vcan1 发送,vcan0 不应收到)
  4. 编写清理脚本 vcan-cleanup.sh,一键删除所有 vcan 设备

验证标准:

# 应该能看到两个独立的 vcan 设备
ip link show | grep vcan

# vcan0 和 vcan1 的帧互不干扰
candump vcan0 &        # 后台运行,只监听 vcan0
cansend vcan1 123#FF   # 发往 vcan1
# candump vcan0 不应收到此帧

提示:

  • 使用 ip link del <name> 删除 vcan 设备
  • 使用 ls /sys/class/net/ | grep vcan 列出所有 vcan 设备

练习 2:CAN 帧收发工具

目标:编写一个交互式的 CAN 收发工具。

难度:进阶

要求:

  1. 命令行参数指定 CAN 设备(如 ./can_tool vcan0)
  2. 提供交互模式:输入 ID:data1 data2 ... 即可发送
  3. 同时接收并打印所有帧
  4. 支持按 CAN ID 过滤(命令行参数 --filter 0x123)

示例用法:

./can_tool vcan0
> 0x123:11 22 33 44      # 发送帧
> 0x000:01 05            # 发送 NMT 启动命令
> quit                   # 退出

提示:

  • 使用 select() 或 poll() 同时监听 stdin 和 CAN socket
  • 或使用两个线程:一个发送,一个接收
  • 解析用户输入时注意十六进制转换

验证标准:

# 终端 1
./can_tool vcan0

# 终端 2
cansend vcan0 123#112233
cansend vcan0 456#AABBCC

# 终端 1 应显示接收到的两帧

练习 3:NMT 命令工具

目标:编写一个完整的 NMT 命令行工具,支持所有 NMT 命令。

难度:进阶

要求:

  1. 支持子命令:start / stop / preop / reset-node / reset-comm
  2. 支持单播(指定节点 ID)和广播(节点 ID = 0)
  3. 打印友好的提示信息

示例用法:

./nmt_tool vcan0 start 1       # 启动节点 1
./nmt_tool vcan0 stop 0        # 广播停止所有节点
./nmt_tool vcan0 preop 5       # 节点 5 进入 Pre-Operational
./nmt_tool vcan0 reset-comm 1  # 重置节点 1 的通信

NMT 命令码参考:

命令命令码说明
start0x01启动节点(进入 Operational)
stop0x02停止节点(进入 Stopped)
preop0x80进入 Pre-Operational
reset-node0x81重置节点
reset-comm0x82重置通信

提示:

  • 参考 examples/03-nmt-command/ 中的 nmt_start_node.c
  • 将发送逻辑封装为函数,通过参数传递命令码和节点 ID

练习 4:CANOpen COB-ID 分析器

目标:编写一个 CAN 帧解析工具,自动识别 CANOpen COB-ID 类型。

难度:高级

要求:

  1. 接收 CAN 帧并解析字段
  2. 区分标准帧和扩展帧
  3. 识别常见的 CANOpen COB-ID(NMT、SDO、PDO、Heartbeat、EMCY)
  4. 显示人类可读的描述信息

示例输出:

[0x000] NMT Command: Start -> Node 1
[0x701] Heartbeat from Node 1 (State: Operational)
[0x601] SDO Request to Node 1
[0x581] SDO Response from Node 1
[0x181] PDO1 TX from Node 1
[0x081] EMCY from Node 1
[0x123] Unknown COB-ID (可能是自定义)

COB-ID 识别参考:

COB-ID 范围类型节点 ID 提取
0x000NMT数据 Byte 1
0x080 + NEMCYCOB-ID - 0x080
0x180 + NPDO1 TXCOB-ID - 0x180
0x200 + NPDO1 RXCOB-ID - 0x200
0x580 + NSDO TXCOB-ID - 0x580
0x600 + NSDO RXCOB-ID - 0x600
0x700 + NHeartbeatCOB-ID - 0x700

提示:

  • 使用 switch 语句或查表法识别 COB-ID 范围
  • 心跳状态解析:0x00 = Boot-up, 0x04 = Stopped, 0x05 = Operational, 0x7F = Pre-Operational

思考题

  1. 物理层:为什么 CAN 使用差分信号而不是单端信号?在工业环境(电机、变频器附近)中的优势是什么?

  2. 仲裁机制:如果两个节点同时发送,ID 分别为 0x100 和 0x200,哪个会赢得仲裁?如果 ID 相同会怎样?

  3. 帧格式:CAN 数据帧最多只能携带 8 字节数据。如果需要通过 SDO 传输 100 字节的对象字典条目,该怎么处理?

  4. COB-ID:为什么 NMT 命令的 COB-ID 固定为 0x000?这与 CAN 的仲裁机制有什么关系?

  5. SocketCAN:struct can_frame 中的 can_id 字段如何区分标准帧和扩展帧?(提示:查看 CAN_EFF_FLAG 的定义)

  6. 错误处理:如果 candump 没有收到 cansend 发送的帧,可能的原因有哪些?(至少列出 3 种)


练习提交与验证

每个练习完成后,确保:

  • 代码可以编译通过(cmake + make 无错误无警告)
  • 可以在 vcan 上正常运行
  • 有基本的错误处理(参数校验、socket 错误检查)
  • 代码注释清晰,变量命名合理
如果这篇文章对你有帮助,欢迎请我喝杯咖啡 ☕
Cygnus Yang 支付宝支付宝
Cygnus Yang 微信微信