当前位置:首页 > 汽车电子 > 汽车电子
[导读]在车载诊断(OBD)、电池管理系统(BMS)、车身控制模块(BCM)等汽车电子场景中,CAN总线依然是数据交互的事实标准。Linux从2.6内核开始引入SocketCAN子系统,将CAN总线抽象为网络设备,开发者可以用标准socket API操作CAN报文。本文将演示如何从设备树配置、驱动移植到用户空间通信,实现一个可靠的CAN数据链路。


在车载诊断(OBD)、电池管理系统(BMS)、车身控制模块(BCM)等汽车电子场景中,CAN总线依然是数据交互的事实标准。Linux从2.6内核开始引入SocketCAN子系统,将CAN总线抽象为网络设备,开发者可以用标准socket API操作CAN报文。本文将演示如何从设备树配置、驱动移植到用户空间通信,实现一个可靠的CAN数据链路。

SocketCAN架构概览

SocketCAN在Linux网络栈中增加了CAN协议族(AF_CAN),提供RAW、BCM(广播管理器)、ISO-TP等多种套接字类型。驱动层只需要实现struct net_device_ops和struct can_priv,并将接收到的CAN帧通过netif_rx()送入协议栈。用户空间则通过socket(PF_CAN, SOCK_RAW, CAN_RAW)创建套接字。

设备树配置CAN控制器

以常见的STM32MP1或i.MX6ULL为例,其内置的FlexCAN或bxCAN控制器需要在设备树中描述:

&can1 {

   pinctrl-names = "default";

   pinctrl-0 = <&pinctrl_can1>;

   status = "okay";

};


&pinctrl {

   pinctrl_can1: can1grp {

       fsl,pins = <

           MX6UL_PAD_UART3_CTS_B__CAN1_TX   0x1b020

           MX6UL_PAD_UART3_RTS_B__CAN1_RX   0x1b020

       >;

   };

};

对于外挂的MCP2515 SPI转CAN模块,则需要添加SPI子节点:

&spi1 {

   can0: mcp2515@0 {

       compatible = "microchip,mcp2515";

       reg = <0>;

       spi-max-frequency = <10000000>;

       clocks = <&clk16m>;

       interrupt-parent = <&gpio4>;

       interrupts = <14 IRQ_TYPE_LEVEL_LOW>;

       status = "okay";

   };

};

驱动核心:发送与接收

以内核自带flexcan驱动为例,发送函数核心逻辑如下:

static netdev_tx_t flexcan_start_xmit(struct sk_buff *skb, struct net_device *ndev)

{

   struct flexcan_priv *priv = netdev_priv(ndev);

   struct can_frame *cf = (struct can_frame *)skb->data;

   struct flexcan_regs __iomem *regs = priv->regs;


   /* 将CAN帧写入硬件发送邮箱 */

   writel(flexcan_mb_code(TXMB, FLEXCAN_MB_CODE_TX_DATA), ®s->mb[TXMB].ctrl);

   writel(cf->can_id, ®s->mb[TXMB].id);

   writel(*(u32 *)cf->data, ®s->mb[TXMB].data[0]);

   writel(*(u32 *)(cf->data + 4), ®s->mb[TXMB].data[1]);


   /* 触发发送请求 */

   writel(FLEXCAN_MB_CNT_CODE(TXMB, FLEXCAN_MB_CODE_TX_DATA) | FLEXCAN_MB_CNT_TX,

          ®s->mb[TXMB].ctrl);


   return NETDEV_TX_OK;

}

接收则在中断处理函数中完成,通过can_rx_offload机制将帧送入协议栈:

static irqreturn_t flexcan_irq(int irq, void *dev_id)

{

   struct flexcan_priv *priv = dev_id;

   struct net_device *ndev = priv->dev;

   struct sk_buff *skb;

   struct can_frame *cf;


   skb = alloc_can_skb(ndev, &cf);

   if (!skb) return IRQ_HANDLED;


   cf->can_id = readl(®s->mb[RXMB].id);

   *(u32 *)cf->data = readl(®s->mb[RXMB].data[0]);

   *(u32 *)(cf->data + 4) = readl(®s->mb[RXMB].data[1]);

   cf->can_dlc = get_len(readl(®s->mb[RXMB].ctrl));


   netif_receive_skb(skb);

   return IRQ_HANDLED;

}

用户空间通信:can-utils

驱动正常工作后,使用ip命令配置CAN接口:

ip link set can0 type can bitrate 500000

ip link set can0 up

发送和接收报文用cansend和candump:

# 发送标准帧 ID=0x123 数据 0x11 0x22 0x33

cansend can0 123#112233


# 监听所有报文

candump can0


# 过滤特定ID

candump can0,0x123:0x7FF

若需在应用程序中编程,使用SocketCAN API:

int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);

struct sockaddr_can addr;

struct ifreq ifr;


strcpy(ifr.ifr_name, "can0");

ioctl(s, SIOCGIFINDEX, &ifr);

addr.can_family = AF_CAN;

addr.can_ifindex = ifr.ifr_ifindex;

bind(s, (struct sockaddr *)&addr, sizeof(addr));


struct can_frame frame;

frame.can_id = 0x123;

frame.can_dlc = 3;

frame.data[0] = 0x11;

frame.data[1] = 0x22;

frame.data[2] = 0x33;

write(s, &frame, sizeof(frame));  // 发送


read(s, &frame, sizeof(frame));   // 接收

可靠性增强措施

汽车电子对可靠性要求极高,以下几点值得注意:

错误处理:监听CAN_ERR_FLAG,当总线关闭时自动重启。驱动中可配置can_priv->state状态机。

DLC校验:严格检查数据长度,防止溢出。

电源管理:在休眠/唤醒时调用can_change_state()通知协议栈。

实时性:对于高优先级报文(如刹车指令),可使用SO_PRIORITY设置socket优先级,并配合rtprio调度。

写在最后

从设备树配置到驱动编写,再到用户空间SocketCAN编程,Linux为CAN总线提供了完整的开发栈。对于汽车电子工程师而言,掌握这套体系意味着可以快速适配不同CAN控制器(FlexCAN、MCP2515、SJA1000),并在应用层实现符合ISO 15765、J1939等高层协议的数据交互。CAN虽老,但在确定性实时通信领域,它依然是嵌入式Linux最可靠的选择之一。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭