04-CAN驱动开发.md 30 KB


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帧)

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过滤器)

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配置

/* 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)

flexcan1: can@02090000 {
    compatible = "fsl,imx6ul-flexcan", "fsl,imx6q-flexcan";
    reg = <0x02090000 0x4000>;
    interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>;
    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)

&flexcan1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_flexcan1>;
    xceiver-supply = <&reg_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创建与绑定

#include <linux/can.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

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帧

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帧

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过滤器设置

/* 接收特定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接口

# 设置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,驱动入口注册网络设备:

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. 设置中断:注册接收和错误中断处理函数

    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 回调实现:

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接收通过中断方式实现,接收中断处理函数从邮箱读取数据:

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(&regs->iflag1);

    /* 2. 处理接收中断 */
    if (iflag1 & FLEXCAN_TX_MB) {
        /* 发送完成中断 */
        writel(FLEXCAN_TX_MB, &regs->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, &regs->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 = &regs->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 版本):

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

cd lib/modules/4.1.15/
cp ip /sbin/ip -f      # 建议先备份根文件系统
rm ip
reboot
ip -V                  # 重启后确认版本号

can-utils 移植(原书使用 2020.02.04 版本):

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设备

# 查看所有网络接口,确认can0是否存在
ifconfig -a

# 查看CAN设备详细信息
ip -details link show can0

6.3 基本收发测试

步骤1:设置CAN接口并打开

# 设置波特率
ip link set can0 type can bitrate 500000

# 打开接口
ifconfig can0 up

步骤2:接收端监听

candump can0

步骤3:发送端发送数据

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设备:

# 关闭接口(如果已打开)
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接口

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