--- title: "Linux CAN驱动开发" tags: [CAN, SocketCAN, 嵌入式Linux, 网络驱动, FlexCAN] created: 2026-09-17 updated: 2026-09-17 pdf_ref: "【正点原子】I.MX6U嵌入式Linux驱动开发指南V2.0.1/72_第六十六章 Linux CAN驱动实验.pdf" --- # Linux CAN驱动开发 ## 1. CAN协议概述 ### 1.1 什么是CAN CAN(Controller Area Network,控制局域网络)由德国BOSCH(博世)公司开发,现已成为国际标准(ISO 11898),是当前应用最广泛的现场总线之一。CAN最初为汽车电子设计,现已广泛应用于工业自动化、医疗、船舶和工业设备等领域。 ### 1.2 CAN总线拓扑 CAN总线使用两根差分线(CAN_H 和 CAN_L)连接所有节点,形成多主总线。每个节点都是独立的CAN控制器,同一CAN网络中所有节点的通信速度必须一致。总线两端各接一个120Ω终端电阻,用于匹配阻抗、吸收信号反射。 ### 1.3 CAN核心特点 | 特性 | 说明 | | ---------- | ------------------------------------------------ | | 多主控制 | 总线空闲时所有单元均可发送,通过ID仲裁决定优先级 | | 系统柔软性 | 节点无地址概念,增减节点不影响其他单元软硬件 | | 通信速度快 | 最高1Mbps(距离<40m),最远10km(速率<5Kbps) | | 错误检测 | 所有单元均可检测错误,检测后立即通知全网 | | 错误恢复 | 发送单元检测到错误后自动重发,直到成功 | | 故障封闭 | 可区分暂时性错误和持续性错误,隔离故障节点 | | 连接节点多 | 理论无限制,实际受总线延迟和电气负载约束 | ### 1.4 CAN电气属性 CAN总线采用差分信号传输: - **显性电平(逻辑0)**:CAN_H = 3.5V,CAN_L = 1.5V,电位差2V - **隐性电平(逻辑1)**:CAN_H ≈ CAN_L ≈ 2.5V,电位差0V - 总线空闲时始终处于隐性状态 - 最新CAN-FD最高速度可达5Mbps ### 1.5 CAN帧格式 CAN协议定义了5种帧类型: | 帧类型 | 用途 | | ------ | ---------------------------- | | 数据帧 | CAN节点之间传输数据 | | 遥控帧 | 接收单元向发送单元请求数据 | | 错误帧 | 检测到错误时通知其他单元 | | 过载帧 | 接收单元通知尚未做好接收准备 | | 帧间隔 | 分隔数据帧/遥控帧与前面的帧 | #### 数据帧结构(7段) 数据帧是CAN通信中最常用的帧,由以下7段组成: 1. **帧起始(SOF)**:1位显性电平(0),标志帧开始 2. **仲裁段**:表示帧优先级 - 标准帧:11位ID(ID10~ID0),最高7位不能全为1 - 扩展帧:29位ID(基本ID 11位 + 扩展ID 18位) 3. **控制段**:6位,包含保留位(r1、r0)和DLC(数据长度,0~8字节) 4. **数据段**:0~8字节有效数据,从MSB开始发送 5. **CRC段**:15位CRC值 + 1位CRC界定符,校验范围覆盖SOF、仲裁段、控制段、数据段 6. **ACK段**:ACK槽 + ACK界定符,接收单元在ACK槽发送显性位确认接收 7. **帧结束(EOF)**:7位隐性位 #### 遥控帧 遥控帧与数据帧结构基本相同,主要区别: - 遥控帧**没有数据段** - 遥控帧的RTR位为隐性(1),数据帧的RTR位为显性(0) - DLC表示所请求的数据帧数据长度 ### 1.6 CAN位时序与波特率 CAN总线一个位分为4个段: | 段名 | 说明 | Tq范围 | | ------------------- | ---------------- | ------ | | SS(同步段) | 固定1个Tq | 1 Tq | | PTS(传播时间段) | 补偿总线传播延迟 | 1~8 Tq | | PBS1(相位缓冲段1) | 补偿相位误差 | 1~8 Tq | | PBS2(相位缓冲段2) | 补偿相位误差 | 2~8 Tq | **波特率计算公式**: ``` 波特率 = f_CANCLK / (总Tq数) 总Tq = SS + PTS + PBS1 + PBS2 = 1 + (PROPSEG+1) + (PSEG1+1) + (PSEG2+1) ``` ### 1.7 CAN仲裁机制 在总线空闲时,最先开始发送的单元获得发送权。多个单元同时发送时,从仲裁段第一位开始逐位比较:发送显性电平(0)的单元在仲裁中优先。连续输出显性电平最多的单元赢得仲裁,失利的单元立刻停止发送转为接收。 ### 1.8 FlexCAN 控制器(原书 66.1.5 节) I.MX6ULL 的 CAN 控制器称为 **FlexCAN**,符合 CAN2.0B 协议,支持标准/扩展格式,**支持 64 个消息缓冲(邮箱)**,最高 1Mbit/S,每个邮箱可独立配置为接收或发送并带独立接收掩码。驱动文件位于 `drivers/net/can/flexcan.c`。 **FlexCAN 支持的 4 种工作模式**: | 模式 | 使能方式 | 说明 | | ----------------------- | ------------ | ------------------------------------------------------------------ | | 正常模式(Normal) | 默认 | 正常收发,所有 CAN 协议功能使能 | | 冻结模式(Freeze) | MCR.FRZ = 1 | 无法收发,CAN 总线同步丢失 | | 仅监听模式(Listen-Only) | CTRL.LOM = 1 | 禁止发送,错误计数器冻结,只接收其他单元的 ACK,工作于被动错误模式 | | 回环模式(Loop-Back) | CTRL.LPB = 1 | 内部回环,用于自测,发送数据流直接反馈给内部接收单元 | 另有 2 种低功耗模式:**禁止模式(Disable)** 和 **停止模式(Stop)**。 **位时序寄存器位域**(CTRL 寄存器): | 位域 | 作用 | 取值范围 | 对应段 | | ------- | ------------------ | ------------ | ------ | | PRESDIV | 分频值,决定 Tq | — | — | | PROPSEG | 传播时间段 PTS | 0~7 → 1~8 Tq | PTS | | PSEG1 | 相位缓冲段1 PBS1 | 0~7 → 1~8 Tq | PBS1 | | PSEG2 | 相位缓冲段2 PBS2 | 1~7 → 2~8 Tq | PBS2 | | RJW | 再同步补偿宽度 SJW | 0~3 → 1~4 Tq | SJW | 其中 `f_Tq = f_CANCLK / (PRESDIV + 1)`,SYNC_SEG 固定为 1 个 Tq。总 Tq 与波特率关系见 1.6 节。 --- ## 2. Linux CAN架构 > ⚠️ **来源说明**:原书第六十六章只讲解 FlexCAN 的使能与测试,并明确说明“本教程不讲解应用编程”;本章 SocketCAN 数据结构、API 及 flexcan.c 源码分析等均不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。 ### 2.1 SocketCAN架构 Linux通过SocketCAN框架将CAN总线抽象为网络设备,用户空间通过标准socket接口访问CAN: ``` ┌─────────────────────────────────────────────┐ │ 用户空间应用 │ │ (cansend / candump / 自定义CAN程序) │ ├─────────────────────────────────────────────┤ │ SocketCAN接口层 │ │ socket() / bind() / send() / recv() │ ├─────────────────────────────────────────────┤ │ CAN协议栈 (can_dev.c) │ │ can_frame / can_filter / can_dlc │ ├─────────────────────────────────────────────┤ │ 网络设备接口层 (net_device) │ ├─────────────────────────────────────────────┤ │ CAN控制器驱动 │ │ (flexcan.c / mcp251x.c / sja1000.c 等) │ ├─────────────────────────────────────────────┤ │ CAN控制器硬件 │ │ FlexCAN / MCP2515 / SJA1000 等 │ └─────────────────────────────────────────────┘ ``` ### 2.2 SocketCAN核心特点 - CAN接口被抽象为网络设备(如 can0、can1) - 使用标准socket API:`socket(PF_CAN, SOCK_RAW, CAN_RAW)` - 支持can-utils工具集:cansend、candump、canconfig等 - 支持CAN过滤器,可按ID过滤接收特定帧 - 支持回环测试模式 ### 2.3 核心数据结构 #### can_frame(CAN帧) ```c struct can_frame { canid_t can_id; /* 32位CAN ID + 帧标志位 */ __u8 can_dlc; /* 数据长度 (0~8) */ __u8 __pad; /* 填充 */ __u8 __res0; /* 保留 */ __u8 __res1; /* 保留 */ __u8 data[CAN_MAX_DLEN]; /* CAN帧数据 (最大8字节) */ }; ``` `can_id` 字段的位定义: - bit 0~28:CAN ID(标准帧用 bit0~10,扩展帧用 bit0~28) - bit 29:`CAN_ERR_FLAG`(0x20000000)— 错误帧标志 - bit 30:`CAN_RTR_FLAG`(0x40000000)— 远程帧标志 - bit 31:`CAN_EFF_FLAG`(0x80000000)— 扩展帧标志 #### can_filter(CAN过滤器) ```c struct can_filter { canid_t can_id; /* CAN ID */ canid_t can_mask; /* 掩码,与can_id进行按位与 */ }; ``` ### 2.4 Linux CAN驱动框架 ``` +------------------+ | SocketCAN API | +--------+---------+ | +--------+---------+ | can_dev.c | CAN核心层 +--------+---------+ | +--------+---------+ | net_device | 网络设备接口 +--------+---------+ | +---------------+---------------+ | | | +-------+------+ +-----+-------+ +-----+-------+ | flexcan.c | | mcp251x.c | | sja1000.c | | FlexCAN驱动 | | MCP251x驱动 | | SJA1000驱动 | +-------+------+ +-----+-------+ +-----+-------+ | | | +-------+------+ +-----+-------+ +-----+-------+ | FlexCAN | | MCP2515 | | SJA1000 | | (i.MX6ULL) | | (SPI CAN) | | (PCI CAN) | +---------------+ +-------------+ +-------------+ ``` --- ## 3. CAN设备树配置 ### 3.1 设备树节点格式 CAN设备在设备树中通常表示为网络设备节点,包含以下关键属性: - `compatible`:匹配驱动程序 - `reg`:寄存器基地址和大小 - `interrupts`:中断号和类型 - `clocks`:时钟源 - `bitrate`:CAN总线波特率 ### 3.2 I.MX6ULL FlexCAN设备树示例 #### Pinctrl配置 ```dts /* CAN1引脚复用 - UART3_CTS/RTS 复用为 FlexCAN1 */ pinctrl_flexcan1: flexcan1grp { fsl,pins = < MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020 MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020 >; }; ``` #### FlexCAN1控制器节点(imx6ull.dtsi) ```dts flexcan1: can@02090000 { compatible = "fsl,imx6ul-flexcan", "fsl,imx6q-flexcan"; reg = <0x02090000 0x4000>; interrupts = ; clocks = <&clks IMX6UL_CLK_CAN1_IPG>, <&clks IMX6UL_CLK_CAN1_SERIAL>; clock-names = "ipg", "per"; stop-mode = <&gpr 0x10 1 0x10 17>; status = "disabled"; }; ``` #### 使能FlexCAN1(imx6ull-alientek-emmc.dts) ```dts &flexcan1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_flexcan1>; xceiver-supply = <®_can_3v3>; status = "okay"; }; ``` **关闭 FlexCAN2**:I.MX6ULL 有两个 CAN 控制器,NXP 官方 EVK 两个都使能了;但正点原子 I.MX6U-ALPHA 开发板把 FlexCAN2 的 IO 分配给了 **ECSPI3**,因此必须在 `imx6ull-alientek-emmc.dts` 中把名为 `flexcan2` 的节点**屏蔽/删除**,否则 ECSPI3 外设无法使用(原书 66.3.1 节)。 ### 3.3 关键属性说明 | 属性 | 说明 | | ----------------- | --------------------------------------- | | `compatible` | 匹配FlexCAN驱动:`"fsl,imx6ul-flexcan"` | | `reg` | 寄存器基地址0x02090000,映射大小0x4000 | | `interrupts` | GIC中断号110,高电平触发 | | `clocks` | 两个时钟:IPG总线时钟和Serial时钟 | | `xceiver-supply` | CAN收发器供电电压(3.3V) | | `status = "okay"` | 使能该CAN控制器 | --- ## 4. SocketCAN API > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识(原书不讲解 CAN 应用编程,但测试中用到了 `ip` 与 can-utils 命令)。 ### 4.1 Socket创建与绑定 ```c #include #include #include #include #include int s; struct sockaddr_can addr; struct ifreq ifr; /* 1. 创建CAN原始套接字 */ s = socket(PF_CAN, SOCK_RAW, CAN_RAW); /* 2. 指定CAN网络接口 */ strcpy(ifr.ifr_name, "can0"); ioctl(s, SIOCGIFINDEX, &ifr); /* 3. 绑定到CAN接口 */ addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; bind(s, (struct sockaddr *)&addr, sizeof(addr)); ``` ### 4.2 发送CAN帧 ```c struct can_frame frame; /* 设置帧ID(标准帧) */ frame.can_id = 0x5A1; /* 设置数据长度 */ frame.can_dlc = 8; /* 填充8字节数据 */ frame.data[0] = 0x11; frame.data[1] = 0x22; frame.data[2] = 0x33; frame.data[3] = 0x44; frame.data[4] = 0x55; frame.data[5] = 0x66; frame.data[6] = 0x77; frame.data[7] = 0x88; /* 发送帧 */ write(s, &frame, sizeof(struct can_frame)); ``` ### 4.3 接收CAN帧 ```c struct can_frame rx_frame; int nbytes; /* 接收CAN帧 */ nbytes = read(s, &rx_frame, sizeof(struct can_frame)); if (nbytes > 0) { printf("ID: 0x%03X, DLC: %d, Data: ", rx_frame.can_id, rx_frame.can_dlc); for (int i = 0; i < rx_frame.can_dlc; i++) { printf("%02X ", rx_frame.data[i]); } printf("\n"); } ``` ### 4.4 CAN过滤器设置 ```c /* 接收特定ID的帧 */ struct can_filter filters[2]; /* 只接收ID=0x123和0x456的帧 */ filters[0].can_id = 0x123; filters[0].can_mask = CAN_SFF_MASK; /* 0x7FF */ filters[1].can_id = 0x456; filters[1].can_mask = CAN_SFF_MASK; setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, filters, sizeof(filters)); ``` ### 4.5 ip命令配置CAN接口 ```bash # 设置CAN波特率为500Kbps ip link set can0 type can bitrate 500000 # 打开CAN接口 ip link set can0 up # 关闭CAN接口 ip link set can0 down # 开启回环测试模式 ip link set can0 type can bitrate 500000 loopback on # 查看CAN接口信息 ip -details link show can0 # 设置CANFD波特率(CAN-FD) ip link set can0 type canfd bitrate 1000000 dbitrate 2000000 fd on ``` ### 4.6 can-utils工具集 | 工具 | 用途 | 示例 | | ----------- | -------------- | ------------------------------------------ | | `cansend` | 发送CAN帧 | `cansend can0 5A1#11.22.33.44.55.66.77.88` | | `candump` | 接收/显示CAN帧 | `candump can0` | | `canconfig` | 配置CAN参数 | `canconfig can0 bitrate 500000` | | `cangen` | 生成随机CAN帧 | `cangen can0 -g 100 -I 42A -L 8 -D i` | | `canplayer` | 重放CAN日志 | `canplayer -I candump.log` | | `canecho` | 回显CAN帧 | `canecho can0` | | `canstat` | 显示CAN统计 | `canstat can0` | **cansend帧格式**:`cansend <接口> <帧ID>#<数据>` - 帧ID为十六进制,不含0x前缀 - 数据字节之间用点号分隔 - 标准帧ID范围:0x000~0x7FF - 扩展帧ID范围:0x00000000~0x1FFFFFFF **candump输出格式**:`<接口> <帧ID> [数据]` ``` can0 5A1 [8] 11 22 33 44 55 66 77 88 ``` --- ## 5. 完整源码分析 > ⚠️ **来源说明**:本节 flexcan.c 源码分析不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识(原书仅给出驱动文件路径 `drivers/net/can/flexcan.c`)。 ### 5.1 FlexCAN驱动框架(drivers/net/can/flexcan.c) Linux内核中的FlexCAN驱动位于 `drivers/net/can/flexcan.c`,驱动入口注册网络设备: ```c static const struct of_device_id flexcan_of_match[] = { { .compatible = "fsl,imx6q-flexcan", }, { .compatible = "fsl,imx6ul-flexcan", }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, flexcan_of_match); static struct platform_driver flexcan_driver = { .driver = { .name = "flexcan", .of_match_table = flexcan_of_match, }, .probe = flexcan_probe, .remove = flexcan_remove, }; module_platform_driver(flexcan_driver); ``` ### 5.2 FlexCAN初始化流程 FlexCAN驱动的初始化主要包括以下步骤: 1. **获取设备资源**:从设备树获取寄存器基地址、中断号、时钟 2. **映射寄存器**:通过 `devm_ioremap_resource()` 映射CAN控制器寄存器 3. **初始化硬件**:配置FlexCAN控制器的工作模式、波特率等 4. **注册网络设备**:调用 `register_netdev()` 注册CAN网络设备 5. **设置中断**:注册接收和错误中断处理函数 ```c static int flexcan_probe(struct platform_device *pdev) { struct net_device *dev; struct flexcan_priv *priv; struct flexcan_regs __iomem *regs; struct resource *res; void __iomem *base; u32 clock_freq = 24000000; /* 24MHz */ /* 1. 获取并映射寄存器 */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res); /* 2. 分配并初始化net_device */ dev = alloc_netdev(sizeof(*priv), "can%d", NET_NAME_UNKNOWN, flexcan_setup, priv); priv = netdev_priv(dev); priv->dev = dev; priv->regs = base; priv->clock_freq = clock_freq; /* 3. 设置网络设备操作集 */ dev->netdev_ops = &flexcan_netdev_ops; /* 4. 注册网络设备 */ register_netdev(dev); return 0; } ``` ### 5.3 FlexCAN发送流程 CAN发送通过网络设备的 `ndo_start_xmit` 回调实现: ```c static netdev_tx_t flexcan_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct flexcan_priv *priv = netdev_priv(dev); struct can_frame *cf = (struct can_frame *)skb->data; struct flexcan_mb __iomem *mb; u32 can_id; /* 1. 获取空闲发送邮箱 */ mb = flexcan_get_tx_mb(priv); /* 2. 设置CAN ID */ can_id = cf->can_id; if (cf->can_id & CAN_EFF_FLAG) can_id |= FlexCAN_ESF_WR; /* 扩展帧标志 */ writel(can_id, &mb->can_id); /* 3. 写入数据到邮箱 */ writel(cf->data[0] | (cf->data[1] << 8) | (cf->data[2] << 16) | (cf->data[3] << 24), &mb->data[0]); writel(cf->data[4] | (cf->data[5] << 8) | (cf->data[6] << 16) | (cf->data[7] << 24), &mb->data[1]); /* 4. 设置数据长度并触发发送 */ writel(FlexCAN_MB_CS_TX | FlexCAN_MB_CS_LENGTH(cf->can_dlc), &mb->cs); /* 5. 释放skb */ can_free_echo_skb(dev, 0); dev->trans_start = jiffies; return NETDEV_TX_OK; } ``` ### 5.4 FlexCAN接收中断处理 CAN接收通过中断方式实现,接收中断处理函数从邮箱读取数据: ```c static irqreturn_t flexcan_irq(int irq, void *dev_id) { struct net_device *dev = dev_id; struct flexcan_priv *priv = netdev_priv(dev); struct flexcan_regs __iomem *regs = priv->regs; u32 iflag1; /* 1. 读取中断标志 */ iflag1 = readl(®s->iflag1); /* 2. 处理接收中断 */ if (iflag1 & FLEXCAN_TX_MB) { /* 发送完成中断 */ writel(FLEXCAN_TX_MB, ®s->iflag1); can_free_echo_skb(dev, 0); dev->stats.tx_packets++; } /* 3. 处理接收FIFO中断 */ if (iflag1 & FLEXCAN_RX_MB) { flexcan_receive(dev); writel(FLEXCAN_RX_MB, ®s->iflag1); } return IRQ_HANDLED; } static int flexcan_receive(struct net_device *dev) { struct flexcan_priv *priv = netdev_priv(dev); struct flexcan_regs __iomem *regs = priv->regs; struct can_frame *cf; struct sk_buff *skb; struct flexcan_mb __iomem *mb; u32 can_id, can_dlc; /* 1. 分配skb */ skb = netdev_alloc_skb(dev, sizeof(*cf)); cf = (struct can_frame *)skb_put(skb, sizeof(*cf)); /* 2. 从接收邮箱读取数据 */ mb = ®s->mb[0]; can_id = readl(&mb->can_id); can_dlc = readl(&mb->cs) & FlexCAN_MB_CS_LENGTH_MASK; /* 3. 填充can_frame */ cf->can_id = can_id & CAN_SFF_MASK; cf->can_dlc = can_dlc; cf->data[0] = readl(&mb->data[0]) & 0xFF; cf->data[1] = (readl(&mb->data[0]) >> 8) & 0xFF; cf->data[2] = (readl(&mb->data[0]) >> 16) & 0xFF; cf->data[3] = (readl(&mb->data[0]) >> 24) & 0xFF; cf->data[4] = readl(&mb->data[1]) & 0xFF; cf->data[5] = (readl(&mb->data[1]) >> 8) & 0xFF; cf->data[6] = (readl(&mb->data[1]) >> 16) & 0xFF; cf->data[7] = (readl(&mb->data[1]) >> 24) & 0xFF; /* 4. 递交协议栈 */ skb->protocol = htons(ETH_P_CAN); skb->ip_summed = CHECKSUM_UNNECESSARY; netif_receive_skb(skb); dev->stats.rx_packets++; dev->stats.rx_bytes += cf->can_dlc; return 0; } ``` ### 5.5 FlexCAN波特率配置 FlexCAN的波特率通过CTRL寄存器配置,涉及5个关键参数: | 参数 | 寄存器位域 | Tq范围 | 说明 | | ------- | ------------ | ------ | ---------------------------------- | | PRESDIV | CTRL.PRESDIV | - | 分频值,Tq = CAN_CLK / (PRESDIV+1) | | PROPSEG | CTRL.PROPSEG | 1~8 Tq | 传播时间段 | | PSEG1 | CTRL.PSEG1 | 1~8 Tq | 相位缓冲段1 | | PSEG2 | CTRL.PSEG2 | 2~8 Tq | 相位缓冲段2 | | RJW | CTRL.RJW | 1~4 Tq | 再同步补偿宽度 | **波特率计算**: ``` Tq = CAN_CLK / (PRESDIV + 1) 总Tq = 1 + (PROPSEG + 1) + (PSEG1 + 1) + (PSEG2 + 1) 波特率 = CAN_CLK / ((PRESDIV + 1) × 总Tq) ``` 例如:CAN_CLK = 24MHz,PRESDIV = 5,PROPSEG = 6,PSEG1 = 7,PSEG2 = 2 ``` Tq = 24MHz / (5+1) = 4MHz (250ns) 总Tq = 1 + 7 + 8 + 3 = 19 波特率 = 4MHz / 19 ≈ 210.5Kbps ``` --- ## 6. 实验验证 ### 6.0 移植 iproute2 与 can-utils(原书 66.4.2/66.4.3 节) busybox 自带的 `ip` 命令不支持 CAN 操作,需要移植 iproute2(原书使用 4.4.0 版本): ```bash tar -vxzf iproute2-4.4.0.tar.gz cd iproute2-4.4.0 # 修改 Makefile:CC := gcc 改为 CC:=arm-linux-gnueabihf-gcc make # 得到的 ip 命令先拷贝到临时目录验证(切勿直接覆盖!) sudo cp ip /home/zuozhongkai/linux/nfs/test_rootfs/lib/modules/4.1.15/ -f ``` 在开发板上验证 `./ip -V` 打印版本号正常后,再替换 busybox 的 `/sbin/ip`: ```bash cd lib/modules/4.1.15/ cp ip /sbin/ip -f # 建议先备份根文件系统 rm ip reboot ip -V # 重启后确认版本号 ``` can-utils 移植(原书使用 2020.02.04 版本): ```bash tar -vxzf can-utils-2020.02.04.tar.gz cd can-utils-2020.02.04 ./autogen.sh ./configure --target=arm-linux-gnueabihf --host=arm-linux-gnueabihf \ --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/can-utils --disable-static --enable-shared make make install sudo cp bin/* /home/zuozhongkai/linux/nfs/rootfs/usr/bin/ -f # 拷贝到 /usr/bin ``` ### 6.1 内核配置 使能Linux内核的FlexCAN驱动: ``` -> Networking support -> CAN bus subsystem support # 打开CAN总线子系统 -> CAN Device Drivers -> Platform CAN drivers with Netlink support -> Support for Freescale FLEXCAN based chips # 选中 ``` ### 6.2 检查CAN设备 ```bash # 查看所有网络接口,确认can0是否存在 ifconfig -a # 查看CAN设备详细信息 ip -details link show can0 ``` ### 6.3 基本收发测试 **步骤1:设置CAN接口并打开** ```bash # 设置波特率 ip link set can0 type can bitrate 500000 # 打开接口 ifconfig can0 up ``` **步骤2:接收端监听** ```bash candump can0 ``` **步骤3:发送端发送数据** ```bash cansend can0 5A1#11.22.33.44.55.66.77.88 ``` **预期输出(接收端)**: ``` can0 5A1 [8] 11 22 33 44 55 66 77 88 ``` ### 6.4 回环测试 在单板上进行CAN回环测试,无需外部CAN设备: ```bash # 关闭接口(如果已打开) ifconfig can0 down # 开启回环模式 ip link set can0 type can bitrate 500000 loopback on # 重新打开接口 ifconfig can0 up # 后台接收数据 candump can0 & # 发送数据 cansend can0 5A1#11.22.33.44.55.66.77.88 ``` **预期输出**:开发板收到自己发送的数据。 ### 6.5 关闭CAN接口 ```bash ifconfig can0 down ``` --- ## 7. 跨平台对比 > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。 ### 7.1 IMX6ULL vs STM32 vs RK3568 CAN差异 | 特性 | I.MX6ULL | STM32F4/F7 | RK3568 | | ---------- | ---------------------- | ----------------------------------- | ----------------------- | | CAN控制器 | FlexCAN | bxCAN/FDCAN | CAN 2.0 | | CAN版本 | CAN2.0B | CAN2.0B + CAN-FD | CAN2.0B | | 最大速率 | 1Mbps | 1Mbps (CAN-FD 8Mbps) | 1Mbps | | 消息邮箱 | 64个MB | bxCAN 3个TX + 3个RX | 多通道 | | 接收FIFO | 支持(含ID过滤) | 支持(2级FIFO) | 支持 | | 设备树兼容 | `"fsl,imx6ul-flexcan"` | `"st,stm32-can"` | `"rockchip,rk3568-can"` | | 驱动文件 | `flexcan.c` | `kvaser_pci.c` / `c_can_platform.c` | `rockchip_can.c` | | 回环模式 | 支持(CTRL.LPB) | 支持(BTR.LBKM) | 支持 | | 低功耗模式 | 禁止/停止模式 | Sleep/Stop模式 | 支持 | ### 7.2 驱动移植要点 - **I.MX6ULL**:FlexCAN控制器由NXP提供内核原生支持,驱动位于 `drivers/net/can/flexcan.c` - **STM32**:bxCAN使用通用CAN驱动(c_can_platform.c),FDCAN需专用驱动 - **RK3568**:Rockchip平台CAN驱动位于 `drivers/net/can/rockchip/rockchip_can.c` --- ## 8. 面试精选 > ⚠️ **来源说明**:本节不属于《I.MX6U嵌入式Linux驱动开发指南》内容,为扩展知识。 ### Q1: SocketCAN相比传统字符设备CAN驱动有什么优势? **答**:SocketCAN是Linux下CAN总线的标准访问方式,相比传统字符设备驱动有以下优势: 1. **统一接口**:使用标准socket API,与网络编程模型一致,学习成本低 2. **协议栈支持**:内核提供完整的CAN协议栈,支持过滤器、错误处理、统计信息 3. **多进程共享**:多个进程可以同时打开同一个CAN设备,无需额外的设备管理 4. **工具生态**:can-utils等标准工具直接可用,方便调试 5. **可扩展性**:支持CAN FD、CAN XL等新标准,无需修改应用层接口 ### Q2: CAN总线的仲裁机制是如何工作的?与I2C的仲裁有何区别? **答**:CAN采用**逐位仲裁**机制:多个节点同时发送时,从仲裁段第一位开始逐位比较。发送显性电平(0)的节点在仲裁中优先,因为CAN总线上显性电平会覆盖隐性电平(线与机制)。连续输出显性电平最多的节点赢得仲裁,失利节点立即停止发送转为接收。 与I2C仲裁的区别: - CAN:基于ID优先级,ID越小优先级越高(显性电平多) - I2C:基于地址唯一性,通过SDA线与实现仲裁,仲裁失败节点检测到不一致后停止 - CAN仲裁期间可以继续发送数据帧,I2C仲裁仅在地址阶段 ### Q3: FlexCAN驱动中的消息邮箱(Mailbox)机制是什么? **答**:FlexCAN使用消息邮箱(Mailbox)作为数据收发的缓冲区。每个邮箱可以独立配置为发送或接收模式,包含: - **ID寄存器**:存储CAN ID - **CS寄存器**:控制/状态寄存器,包含帧格式、DLC、发送/接收状态 - **DATA寄存器**:存储0~8字节数据 发送流程:CPU将数据写入空闲的发送邮箱,设置CS寄存器触发发送。FlexCAN自动完成仲裁、CRC计算和发送。 接收流程:FlexCAN将接收到的帧与邮箱ID比较,匹配后存入对应邮箱,触发接收中断。支持接收掩码寄存器,实现灵活的ID过滤。 ### Q4: CAN波特率计算中,位时序的各段有什么作用?采样点如何设置? **答**: - **SS(同步段)**:固定1个Tq,用于同步各节点时钟 - **PTS(传播时间段)**:补偿信号在总线上的物理传播延迟 - **PBS1(相位缓冲段1)**:补偿相位误差(振荡器漂移等) - **PBS2(相位缓冲段2)**:补偿相位误差 - **SJW(再同步跳转宽度)**:再同步时PBS1和PBS2的调整范围 采样点位于PBS1结束处,用于读取总线电平。采样点位置 = (1 + PTS + PBS1) / 总Tq × 100%。通常设置采样点在75%~80%处,以获得最佳抗干扰能力。采样点太靠前容易采到不稳定信号,太靠后容易受位填充影响。 ### Q5: 在嵌入式Linux中如何调试CAN通信故障? **答**:分层排查: 1. **硬件层**:检查CAN_H/CAN_L接线、终端电阻(120Ω)、收发器供电 2. **物理层**:使用示波器查看CAN_H和CAN_L的差分波形,确认电平正常 3. **驱动层**:`dmesg | grep can` 查看驱动加载日志;检查设备树配置是否正确 4. **协议层**:使用candump监听帧数据,检查帧ID、DLC、数据是否正确 5. **工具层**:确认波特率一致、过滤器配置正确、can-utils版本兼容 6. **环回测试**:`ip link set can0 type can loopback on` 排除外部设备干扰 7. **统计信息**:`ip -s link show can0` 查看错误计数、丢帧统计 8. **逻辑分析仪**:捕获CAN总线原始信号,分析时序和错误帧 --- **内容来源**: 《I.MX6U嵌入式Linux驱动开发指南》第六十六章 Linux CAN驱动实验 **最后更新**: 2026-09-17