当前位置: 首页 > news >正文

Linux CAN 驱动浅析

前言

  1. 开组会,感觉全部门的人都会网络,看以太网和 WIFI 体系又过于庞大,看的头昏脑涨。
  2. 既然那些东西太难了,那就曲线救国,看 CAN 驱动。在 Linux 中 CAN 也是属于网络驱动一部分。
  3. 但是 CAN 的硬件设计非常的优秀,直接把硬件层和数据链路层的活都搞好了,而应用层又不是驱动工程师该做的事情,因此个人感觉 CAN 驱动还是相对简单的。(叠甲:刚接触,小白的莫名其妙自信)
  4. 对于 I2C、SPI 这类驱动程序,是被分为了适配器驱动层和设备驱动层,因为这两种协议通常用于主从模式的板级通信,针对具体设备(如传感器、存储器、转换器等)的通信。而每个设备可能有不同的初始化、读写和中断处理需求。因此,设计有设备驱动层,用于处理与每个设备特有的通信协议。
  5. 而对于 CAN 这类设备,是不会区分主从设备的(可以软件实现,例如 CANopen),他更多的是倾向于传输原始的数据流,因此不进行设备驱动层设计。
  6. 这个时候有人又要说了,UART 不也是倾向于传输原始数据吗?为什么 UART 适配器驱动层上面还有一个 TTY 和字符设备层呢?这是因为,UART 是传输原始数据没错,但是 UART 多用于控制台。而控制台可以是串口,也可以是 LCD 或键盘等多种形式,为了统一标准输入和标准输出,弄了一个 TTY抽象层。
  7. 综上所述,CAN 的设计是相对简单的,基本是原厂适配好 CAN 控制器,那么就可以直接移交给应用层进行数据交互了。
  8. 个人邮箱:zhangyixu02@gmail.com
  9. 微信公众号:风正豪

在这里插入图片描述

正文

Linux 网络通讯设计

  1. 在 Linux 中,所有的网络通讯采用统一的接口,对于底层实现,数据包发送都是调用的 net_device_ops.ndo_start_xmit 函数。
  2. 在网络通讯中,所有的数据接收都是采用中断实现(后面又一个 NAPI 机制,即中断 + 轮询,但 CAN 似乎没有采用该机制)。

CAN control 层

  1. 已提供核心代码,各位根据我提供的代码,与 driver/net/can 目录自行学习,不做赘述。
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/can.h>
#include <linux/can/dev.h>
#include <linux/interrupt.h> // 包含中断相关的接口#define DRV_NAME "simple_can"struct simple_can_priv {struct can_priv can; // 继承自 can_priv 用于 CAN 设备的基本设施int custom_data;     // 自定义数据,比如设备状态或配置
};static const struct can_bittiming_const simple_bittiming_const = {.name = DRV_NAME,        // 定义时序的名称,用于标识或调试.tseg1_min = 4,          // 时间段1的最小值,即TSEG1最少包含的时间量.tseg1_max = 16,         // 时间段1的最大值,即TSEG1最多包含的时间量.tseg2_min = 2,          // 时间段2的最小值,即TSEG2最少包含的时间量.tseg2_max = 8,          // 时间段2的最大值,即TSEG2最多包含的时间量.sjw_max = 4,            // 同步跳转宽度的最大值,用于相位缓冲,可提高时钟容忍度.brp_min = 1,            // 波特率预分频器(BRP)的最小值.brp_max = 256,          // 波特率预分频器(BRP)的最大值.brp_inc = 1,            // 波特率预分频器(BRP)的增量,用于调整通信速率
};// CAN 设备指针
static struct net_device *simple_can_dev;// CAN 设备打开
static int simple_can_open(struct net_device *dev) 
{struct simple_can_priv *priv = netdev_priv(dev);open_candev(dev);/* 芯片原厂工程师提供 CAN 通讯启动操作函数* 启动 CAN 时钟* 启动 CAN 控制器*/can_led_event(dev, CAN_LED_EVENT_OPEN);netif_start_queue(dev);printk(KERN_INFO "simple_can: device opened\n");return 0;
}// CAN 设备关闭
static int simple_can_close(struct net_device *dev) 
{struct simple_can_priv *priv = netdev_priv(dev);netif_stop_queue(dev);/* 芯片原厂工程师提供 CAN 通讯停止操作函数* 关闭 CAN 控制器* 关闭时钟*/close_candev(dev);can_led_event(dev, CAN_LED_EVENT_STOP);printk(KERN_INFO "simple_can: device closed\n");return 0;
}static netdev_tx_t simple_can_start_xmit(struct sk_buff *skb, struct net_device *dev) 
{struct simple_can_priv *priv = netdev_priv(dev);struct can_frame *cf = (struct can_frame *)skb->data;// 检查是否为有效数据包,如果不是有效数据包,则直接丢弃并结束函数if (can_dropped_invalid_skb(ndev, skb))return NETDEV_TX_OK;netif_stop_queue(ndev);/* 芯片原厂工程师解析上层的帧信息,将其存入指定寄存器* 根据 cf->can_id 参数获知当前的数据包是远程帧,扩展帧,还是标准帧* 根据 cf->can_dlc 参数获知当前数据报文中有效数据长度*/// 将数据包存入回环缓冲区can_put_echo_skb(skb, dev, 0);/* 芯片原厂工程师向指定寄存器写入指令,让数据包发往 CAN 总线*/printk(KERN_INFO "simple_can: start xmit\n");return NETDEV_TX_OK;
}static const struct net_device_ops simple_can_netdev_ops = {.ndo_open = simple_can_open,    // 定义设备打开操作.ndo_stop = simple_can_close,   // 定义设备关闭操作.ndo_start_xmit = simple_can_start_xmit, // 定义数据发送操作
};static irqreturn_t simple_can_interrupt(int irq, void *dev_id)
{struct net_device *dev =  (struct net_device *)dev_id;struct simple_can_priv *priv = netdev_priv(dev);struct net_device_stats *stats = &ndev->stats;struct can_frame *cf;struct sk_buff *skb;// 读取 CAN 控制器的硬件寄存器,以确定中断原因if (/* 检测对应寄存器,查看是否为接收中断 */) {// 分配skb并设置其为CAN包skb = alloc_can_skb(dev, &cf);// 将接收的数据放入CAN帧(假定从硬件寄存器读取数据)cf->can_id = /* 设置接收的CAN ID */;cf->can_dlc = get_can_dlc(/* 设置数据长度,通常从寄存器读取 */);memcpy(cf->data, /* 来源于硬件寄存器的数据 */, cf->can_dlc);stats->rx_packets++; // 记录设备接收到的数据包总数stats->rx_bytes += cf->can_dlc;  // 记录设备接收到的字节总数// 提交数据包给上层netif_rx(skb);printk(KERN_INFO "simple_can: received a packet\n");}// 处理其他中断引发的事件(如错误、状态更改等)// 函数退出时记得清除中断标志位return IRQ_HANDLED;
}static int simplecan_chip_start(struct net_device *dev)
{/* 芯片原厂工程师初始化和启动 CAN 控制器 */return 0;
}static int simplecan_set_mode(struct net_device *dev, enum can_mode mode)
{int err;switch (mode) {case CAN_MODE_START:err = simplecan_chip_start(dev);if (err)return err;netif_wake_queue(dev);break;default:return -EOPNOTSUPP;}return 0;
}static int simplecan_get_berr_counter(const struct net_device *dev,struct can_berr_counter *bec)
{bec->txerr = 0; // 模拟传输错误计数bec->rxerr = 0; // 模拟接收错误计数return 0;
}static int __init simple_can_init(void) 
{int irq;struct simple_can_priv *priv;/* 1. 获取设备树相关信息* 获取 CAN 控制器的 GIC 中断号 interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>;*/irq = platform_get_irq(pdev, 0);// 2. 分配网络设备simple_can_dev = alloc_candev(0, 0);// 3. 配置 CAN 信息,提供操作集simple_can_dev->netdev_ops = &simple_can_netdev_ops;simple_can_dev->irq = irq;simple_can_dev->flags |= IFF_ECHO;priv = netdev_priv(simple_can_dev); // 获取私有数据区指针memset(priv, 0, sizeof(struct simple_can_priv));priv->custom_data = -1;           // 设置初始的私有数据,芯片原厂工程师看情况填充priv->can.clock.freq = 8000000;   // 假设频率为8MHz,CAN 控制器的时钟频率priv->can.bittiming_const = &simple_bittiming_const;        // 定义 CAN 位时序常量priv->can.do_set_mode = simplecan_set_mode;                 // 设置 CAN 模式priv->can.do_get_berr_counter = simplecan_get_berr_counter; // 获取错误计数器// 指示 CAN 控制器支持哪些控制模式priv->can.ctrlmode_supported = CAN_CTRLMODE_BERR_REPORTING |CAN_CTRLMODE_LISTENONLY |CAN_CTRLMODE_LOOPBACK |CAN_CTRLMODE_3_SAMPLES;/* 4. 请求处理中断函数,网络驱动中,所有的接收都是通过中断实现。* 这里 flags 传入 0,表示中断触发类型采用默认设备树中的 interrupts 成员 flags*/devm_request_irq(&pdev->dev, simple_can_dev->irq, simple_can_interrupt,0, DRV_NAME, simple_can_dev);// 5. 将网络设备注册到内核register_candev(simple_can_dev);printk(KERN_INFO "simple_can: module loaded\n");return 0;
}static void __exit simple_can_exit(void) {unregister_candev(simple_can_dev);free_candev(simple_can_dev);printk(KERN_INFO "simple_can: module unloaded\n");
}module_init(simple_can_init);
module_exit(simple_can_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("https://zyxbeyourself.blog.csdn.net/");
MODULE_DESCRIPTION("Simple CAN Driver Example");

CAN 驱动测试

    1. 测试 CAN 接口是否正常
# 查询当前网络设备
ifconfig -a
# 关闭 CAN  
ip link set can0 down
# 设置仲裁段 1M 波特率,数据段3M波特率
ip link set can0 type can bitrate 1000000 dbitrate 3000000 fd on  
# 打印 can0 信息
ip -details link show can0  
# 启动CAN  
ip link set can0 up
# 发送(标准帧,数据帧,ID:123,date:DEADBEEF)
cansend can0 123##1DEADBEEF  
# 发送(扩展帧,数据帧,ID:00000123,date:DEADBEEF)
cansend can0 00000123##1DEADBEEF
# 开启打印,等待接收
candump can0  

相关文章:

  • 私有知识库 Coco AI 实战(二):摄入 MongoDB 数据
  • 【问题解决】本机navicat连接云服务器mysql
  • 工作记录9
  • 【Pandas】pandas DataFrame mod
  • 【复盘】cpu飙升引发的连锁反应
  • JDK 7 Update 0 (64位) 详细Windows 安装指南
  • 每日一题(小白)模拟娱乐篇33
  • Java 拦截器完全指南:原理、实战与最佳实践
  • Chronos - 时间序列预测语言模型
  • Redis从入门到实战先导篇
  • Jsoup、Selenium 和 Playwright 的含义、作用和区别
  • 【C/S通信仿真】
  • 17.QT-Qt窗口-工具栏|状态栏|浮动窗口|设置停靠位置|设置浮动属性|设置移动属性|拉伸系数|添加控件(C++)
  • 软件黑盒与白盒测试详解
  • 大厂Java面试:JVM调优与问题定位
  • 我的独立开发技术栈
  • Kotlin中实现静态
  • 深入解析C++ STL Queue:先进先出的数据结构
  • IMU---MPU6050
  • 数据结构-链表
  • 上海楼市明显复苏:一季度房地产开发投资增长5.1%,土地市场重燃战火
  • 解放军报社论:谱写新时代双拥工作崭新篇章
  • 央行副行长陆磊:国际化程度有效提升是上海国际金融中心建设的一个主要方向
  • 上海34年“老外贸”张斌:穿越风暴,必须靠过硬的核心竞争力
  • 外交部:制裁在涉港问题上表现恶劣的美方人士是对等反制
  • 女子遭前男友泼汽油烧伤致残案二审庭审:检方抗诉称一审量刑不当