CAN 通信与摄像头采集
在嵌入式 Linux 项目中,CAN 通信 和摄像头采集是两个非常典型的外设场景。CAN 常用于工业控制、车载通信和多节点数据上报;摄像头则常用于图像采集、视觉识别和人机交互。本文以 i.MX6ULL 为例,整理一套从硬件、设备树、驱动到应用测试的实战流程。
一、系统架构
整体系统可以简单理解为:
- 摄像头模块:负责采集图像数据,通常通过 I2C 配置寄存器,通过 CSI/MIPI 接口输出图像。
- i.MX6ULL 主控:运行嵌入式 Linux,负责摄像头驱动、V4L2 图像采集和 CAN 通信。
- CAN 总线:用于与其他 MCU 节点通信,例如 STM32、电机控制板、传感器采集板等。
- 应用层:通过 V4L2 获取图像,通过 SocketCAN 收发 CAN 报文。
Linux 内核中,这两类外设分别对应不同子系统:
| 外设 | 内核框架 | 用户空间接口 |
|---|---|---|
| CAN | SocketCAN | can0、candump、cansend |
| 摄像头 | V4L2 / Media | /dev/videoX、v4l2-ctl |
二、CAN 通信部分
2.1 CAN 总线简介
CAN 是一种差分串行总线,具有较好的抗干扰能力和多节点通信能力。常见特点包括:
- 支持多主通信;
- 具备错误检测与重传机制;
- 适合工业现场、车载、机器人等场景;
- Linux 下通过 SocketCAN 将 CAN 设备抽象为网络接口。
硬件上,CAN 控制器通常集成在 SoC 内部,外部通过 CAN 收发器连接到 CANH、CANL。总线两端一般需要接入 120Ω 终端电阻,否则容易出现通信不稳定。
2.2 设备树配置
以 i.MX6ULL 为例,CAN 节点通常需要在设备树中使能,并配置引脚和时钟。示例如下:
&can1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_can1>;
xceiver-supply = <®_can1_3v3>;
status = "okay";
};
引脚配置示例:
pinctrl_can1: can1grp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO08__FLEXCAN1_TX 0x1b0b0
MX6UL_PAD_GPIO1_IO09__FLEXCAN1_RX 0x1b0b0
>;
};
具体引脚需要根据原理图确认,不能直接照搬。
2.3 Linux 端 CAN 调试
启动系统后,可以先查看 CAN 接口:
ip link show can0
配置波特率并启动 CAN 接口:
sudo ip link set can0 type can bitrate 500000
sudo ip link set can0 up
监听 CAN 报文:
candump can0
发送 CAN 报文:
cansend can0 123#1122334455667788
其中:
123是 CAN ID;1122334455667788是 8 字节数据。
如果 candump 没有输出,可以依次检查:
- CAN 收发器供电是否正常;
- CANH、CANL 是否接反;
- 总线两端是否有 120Ω 终端电阻;
- 波特率是否与对端一致;
- 设备树中 CAN 节点是否真正使能;
dmesg | grep -i can是否有报错。
三、摄像头部分
3.1 摄像头硬件连接
以 OV5640 为例,摄像头通常通过以下接口连接到 i.MX6ULL:
- I2C 接口:用于配置摄像头寄存器;
- CSI/MIPI 接口:用于输出图像数据;
- PWDN/RESET:用于电源控制和复位;
- MCLK:摄像头主时钟。
调试前建议先确认:
- I2C 地址是否正确,例如 OV5640 常见地址为
0x3c; - 供电电压是否稳定;
- CSI 数据线和时钟线连接是否正确;
- PWDN、RESET 引脚极性是否符合模组要求。
可以使用 i2cdetect 初步确认摄像头是否出现在总线上:
i2cdetect -y 1
3.2 设备树配置
OV5640 的设备树节点一般放在对应的 I2C 控制器下,例如 &i2c1。示例如下:
&i2c1 {
clock-frequency = <100000>;
status = "okay";
ov5640: camera@3c {
compatible = "ovti,ov5640";
reg = <0x3c>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_csi1 &pinctrl_csi_pwn_rst>;
clocks = <&clks IMX6UL_CLK_CSI>;
clock-names = "csi_mclk";
pwn-gpios = <&gpio1 4 GPIO_ACTIVE_LOW>;
rst-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
csi_id = <0>;
mclk = <24000000>;
mclk_source = <0>;
status = "okay";
port {
ov5640_ep: endpoint {
remote-endpoint = <&csi1_ep>;
};
};
};
};
CSI 节点也需要使能:
&csi {
status = "okay";
port {
csi1_ep: endpoint {
remote-endpoint = <&ov5640_ep>;
};
};
};
注意:引脚、GPIO 编号、I2C 总线号必须和实际硬件一致。不同开发板的 OV5640 引脚可能完全不同,不能盲目复制。
3.3 内核配置
摄像头驱动通常依赖 V4L2 和 CSI 采集接口。内核中需要确认以下配置已开启:
Device Drivers --->
Multimedia support --->
[*] V4L platform devices
[*] MXC Video For Linux Video Capture
[*] OV5640 camera support
如果使用正点原子等第三方 BSP,建议优先参考其提供的 OV5640 驱动和设备树配置,因为原厂内核驱动可能缺少部分板级适配。
3.4 摄像头调试
系统启动后,可以先查看视频设备:
ls /dev/video*
查看摄像头支持格式:
v4l2-ctl -d /dev/video0 --list-formats-ext
查看完整信息:
v4l2-ctl -d /dev/video0 --all
简单采集测试:
v4l2-ctl -d /dev/video0 --stream-mmap=4 --stream-count=120 --stream-to=test.yuv
如果采集失败,可以查看内核日志:
dmesg | grep -i ov5640
dmesg | grep -i csi
常见问题包括:
- I2C 通信失败,检查地址和上拉电阻;
- 无图像,检查 MCLK、PWDN、RESET;
- 图像花屏,检查 CSI 数据线和时钟;
- 节点未出现,检查设备树和内核驱动是否匹配。
四、CAN 与摄像头联动示例
一个比较典型的联动场景是:
- 摄像头采集图像;
- 应用层对图像做简单处理或特征提取;
- 通过 CAN 总线将结果发送给 MCU;
- MCU 根据结果控制电机、指示灯或其他执行机构。
例如,Linux 端可以通过 SocketCAN 发送一帧检测结果:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int main(void)
{
int s;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (s < 0) {
perror("socket");
return -1;
}
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(s);
return -1;
}
frame.can_id = 0x200;
frame.can_dlc = 8;
memset(frame.data, 0, sizeof(frame.data));
frame.data[0] = 0x01; // 示例:检测结果
frame.data[1] = 0x02; // 示例:目标数量
if (write(s, &frame, sizeof(frame)) != sizeof(frame)) {
perror("write");
close(s);
return -1;
}
printf("CAN frame sent, id=0x%03X\n", frame.can_id);
close(s);
return 0;
}
交叉编译后放到开发板运行:
arm-linux-gnueabihf-gcc can_send.c -o can_send
运行前确保 CAN 接口已经启动:
sudo ip link set can0 type can bitrate 500000
sudo ip link set can0 up
./can_send
对端 MCU 如果正常接收,就能看到对应 CAN 报文。
五、调试顺序建议
实际开发中,建议按下面顺序推进:
-
先调通 CAN 物理层
用示波器或逻辑分析仪确认 CANH、CANL 波形,确认终端电阻和波特率。
-
再调通 Linux CAN 接口
使用
candump、cansend验证收发是否正常。 -
再调通摄像头 I2C
使用
i2cdetect确认摄像头地址。 -
再调通摄像头驱动
确认
/dev/videoX出现,并能采集图像。 -
最后做联动应用
把图像结果通过 CAN 发送出去,或者根据 CAN 指令控制摄像头工作模式。
六、踩坑总结
- CAN 不通:优先检查 120Ω 终端电阻、波特率、CANH/CANL 是否接反。
- 摄像头 I2C 扫描不到:检查供电、上拉电阻、I2C 地址和引脚复用。
- 设备树节点不生效:确认修改的 dts 是否真正被编译,U-Boot 加载的 dtb 是否与编译结果一致。
- 摄像头有节点但无图像:检查 MCLK、PWDN、RESET、CSI 数据线和驱动匹配。
- CAN 能发不能收:检查对端是否也在发送,ID 过滤是否开启,总线是否只接了一个节点。
七、小结
CAN 通信和摄像头采集看似属于两个方向,但在嵌入式项目中经常需要协同工作。CAN 负责可靠、实时的控制与数据交互,摄像头负责环境感知和图像处理。把两者结合起来,可以做出智能小车、工业视觉终端、机器人感知节点等完整项目。
后续可以继续扩展到 Qt 界面显示、OpenCV 图像处理、ROS2 通信,或者将摄像头结果通过 CAN 发送给 STM32 执行运动控制。