ubuntu CAN 驱动开发流程

在 Ubuntu 系统中,驱动开发本质上是 Linux 内核驱动开发(Ubuntu 基于 Linux 内核)。CAN 驱动作为一种特殊的硬件驱动,其开发流程遵循 Linux 内核驱动的通用规范,同时需适配 CAN 总线特性(如 SocketCAN 子系统)。以下是详细的开发流程:

一、通用 Linux 驱动开发流程(Ubuntu 环境)

Ubuntu 驱动开发的核心是为 Linux 内核编写驱动模块(.ko 文件),流程如下:

1. 开发环境搭建
  • 安装内核源码:驱动需与内核版本匹配,需安装目标内核的源码和开发包:
    # 查看当前内核版本
    uname -r
    # 安装对应版本的内核源码和开发包
    sudo apt-get install linux-source-$(uname -r) linux-headers-$(uname -r)
    
  • 安装编译工具
    sudo apt-get install build-essential gcc make libssl-dev bc flex bison
    
  • 配置开发目录:将内核源码解压到工作目录(如 /usr/src/linux-source-xxx),并生成配置文件:
    cd /usr/src/linux-source-xxx
    sudo cp /boot/config-$(uname -r) .config  # 复用当前内核配置
    sudo make oldconfig  # 更新配置(默认即可)
    
2. 需求分析与硬件调研
  • 明确硬件特性:确定 CAN 硬件类型(如集成在 SoC 的 CAN 控制器、USB-to-CAN 适配器、PCIe CAN 卡等),获取硬件手册(datasheet),了解:
    • 寄存器布局(初始化、发送、接收、中断等寄存器);
    • 通信时序(波特率生成逻辑、时钟源);
    • 中断/DMA 机制(数据收发的触发方式);
    • 支持的 CAN 协议(CAN 2.0A/B、CAN FD 等)。
  • 确定驱动类型:CAN 驱动在 Linux 中通常作为 网络设备驱动(因 SocketCAN 将 CAN 设备抽象为网络接口),需遵循 Linux 网络设备模型(struct net_device)。
3. 驱动框架设计

基于 Linux 内核的网络设备驱动框架和 SocketCAN 子系统,设计驱动核心模块:

  • 模块入口/出口module_init() 注册驱动,module_exit() 注销驱动。
  • 设备匹配:通过 platform_driverusb_driver 等框架(根据硬件总线类型)与硬件匹配(如设备树节点、USB VID/PID)。
  • 核心功能模块
    • 设备初始化(open()):配置 CAN 控制器时钟、波特率、工作模式;
    • 数据发送(hard_start_xmit()):将上层数据(SocketCAN 传递的 CAN 帧)写入硬件发送缓冲区;
    • 数据接收(中断/DMA 处理):硬件接收数据后触发中断,驱动读取数据并提交给 SocketCAN;
    • 中断处理(irq_handler_t):处理发送完成、接收完成、错误等中断;
    • 设备关闭(stop()):停止 CAN 控制器,释放资源。
4. 编码实现

遵循 Linux 内核编码规范(如缩进、命名、注释),核心代码结构示例:

#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/can/dev.h>  // SocketCAN 设备接口
#include <linux/platform_device.h>

// CAN 控制器私有数据结构
struct my_can_priv {
    struct net_device *dev;  // 网络设备对象
    void __iomem *regs;      // 寄存器映射地址
    int irq;                 // 中断号
    // ... 其他硬件相关状态(发送缓冲区、接收缓冲区等)
};

// 发送函数:将 CAN 帧写入硬件
static netdev_tx_t my_can_xmit(struct sk_buff *skb, struct net_device *dev) {
    struct my_can_priv *priv = netdev_priv(dev);
    struct can_frame *cf = (struct can_frame *)skb->data;
    
    // 1. 检查硬件发送缓冲区是否空闲
    // 2. 将 cf 中的 ID、数据等写入硬件发送寄存器
    // 3. 启动发送
    // ...
    
    netdev_kfree_skb(skb);  // 释放 skb
    return NETDEV_TX_OK;
}

// 中断处理函数
static irqreturn_t my_can_irq(int irq, void *dev_id) {
    struct net_device *dev = dev_id;
    struct my_can_priv *priv = netdev_priv(dev);
    u32 status = readl(priv->regs + STATUS_REG);  // 读取状态寄存器
    
    if (status & RX_INT) {  // 接收中断
        // 1. 从硬件接收寄存器读取 CAN 帧
        // 2. 封装为 skb 并提交给 SocketCAN(can_rx(dev, skb))
    } else if (status & TX_INT) {  // 发送完成中断
        // 清理发送状态,唤醒等待队列等
    }
    // ... 处理错误中断等
    return IRQ_HANDLED;
}

// 设备打开/初始化函数
static int my_can_open(struct net_device *dev) {
    struct my_can_priv *priv = netdev_priv(dev);
    // 1. 使能 CAN 控制器时钟
    // 2. 配置波特率(根据 dev->can.ctrlmode 中的参数)
    // 3. 使能接收/发送中断
    // ...
    netif_start_queue(dev);  // 启动发送队列
    return 0;
}

// 网络设备操作集
static const struct net_device_ops my_can_netdev_ops = {
    .ndo_open = my_can_open,
    .ndo_stop = my_can_stop,  // 需实现关闭逻辑
    .ndo_start_xmit = my_can_xmit,
    // ... 其他操作(如设置波特率、获取统计信息等)
};

// 平台驱动 probe 函数(硬件匹配时调用)
static int my_can_probe(struct platform_device *pdev) {
    struct net_device *dev;
    struct my_can_priv *priv;
    int ret;
    
    // 1. 分配网络设备
    dev = alloc_candev(sizeof(struct my_can_priv), 0);  // SocketCAN 专用分配函数
    if (!dev) return -ENOMEM;
    
    // 2. 映射寄存器地址(从设备树获取物理地址)
    priv = netdev_priv(dev);
    priv->regs = devm_ioremap_resource(&pdev->dev, &res);  // res 从设备树解析
    if (IS_ERR(priv->regs)) return PTR_ERR(priv->regs);
    
    // 3. 获取中断号(从设备树)
    priv->irq = platform_get_irq(pdev, 0);
    ret = devm_request_irq(&pdev->dev, priv->irq, my_can_irq, 0, "my_can", dev);
    if (ret) return ret;
    
    // 4. 初始化网络设备操作集
    dev->netdev_ops = &my_can_netdev_ops;
    // ... 初始化其他 CAN 相关参数(如支持的模式、最大数据长度等)
    
    // 5. 注册网络设备
    ret = register_netdev(dev);
    if (ret) {
        free_candev(dev);
        return ret;
    }
    
    platform_set_drvdata(pdev, dev);
    return 0;
}

// 平台驱动结构体
static const struct platform_driver my_can_driver = {
    .probe = my_can_probe,
    .remove = my_can_remove,  // 需实现注销逻辑
    .driver = {
        .name = "my-can-controller",  // 与设备树中 compatible 匹配
        .of_match_table = my_can_of_match,  // 设备树匹配表
    },
};
module_platform_driver(my_can_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
5. 编写编译脚本(Makefile)

驱动需与内核源码一起编译,Makefile 示例:

obj-m += my_can_driver.o  # 驱动模块名

KERNELDIR ?= /lib/modules/$(shell uname -r)/build  # 内核源码路径
PWD := $(shell pwd)

default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules  # 编译模块

clean:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) clean  # 清理编译产物
6. 编译与加载测试
  • 编译驱动
    make  # 生成 my_can_driver.ko
    
  • 加载驱动模块
    sudo insmod my_can_driver.ko  # 加载模块
    lsmod | grep my_can_driver    # 确认模块已加载
    dmesg | tail                  # 查看内核日志(检查初始化是否成功)
    
  • 验证设备:若驱动正常,会生成 CAN 网络接口(如 can0):
    ip link show  # 查看是否存在 can0 接口
    
7. 功能测试

使用 SocketCAN 工具测试驱动功能:

  • 安装工具
    sudo apt-get install can-utils
    
  • 配置 CAN 接口
    sudo ip link set can0 type can bitrate 500000  # 设置波特率
    sudo ip link set can0 up                      # 启动接口
    
  • 发送/接收测试
    # 终端1:接收 CAN 帧
    candump can0
    
    # 终端2:发送 CAN 帧(ID=0x123,数据=0x11 0x22)
    cansend can0 123#1122
    
    若终端1能收到终端2发送的帧,说明驱动基本功能正常。
8. 调试与优化
  • 内核日志调试:通过 printk() 在关键流程打印日志(注意级别,如 KERN_INFOKERN_ERR),用 dmesg 查看。
  • 工具调试
    • ftrace:跟踪函数调用流程,分析性能瓶颈;
    • devmem:直接读写硬件寄存器,验证配置是否正确;
    • netstat -i:查看 CAN 接口的统计信息(发送/接收帧数量、错误数)。
  • 问题修复:针对测试中发现的问题(如丢帧、波特率错误、中断异常),优化驱动逻辑(如增加缓冲区、调整中断优先级、修复时序配置)。
9. 驱动部署
  • 若需长期使用,可将驱动模块复制到 /lib/modules/$(uname -r)/kernel/drivers/net/can/ 目录,并通过 depmod -a 更新依赖。
  • 若需开机自动加载,可在 /etc/modules 中添加模块名。

二、CAN 驱动开发的特殊注意事项

  1. SocketCAN 子系统集成:CAN 驱动需使用 can_dev.h 提供的接口(如 alloc_candev()can_rx()),而非通用网络设备接口,确保与 SocketCAN 协议栈兼容。
  2. 波特率配置:需根据硬件时钟源和 CAN 协议要求,实现波特率计算逻辑(如通过 ndo_set_config() 回调处理用户设置的波特率)。
  3. 错误处理:CAN 总线错误(如仲裁丢失、位错误)需通过 can_error_report() 上报给 SocketCAN,供上层诊断。
  4. 硬件差异适配
    • 对于 USB-to-CAN 设备,需基于 usb_driver 框架开发,通过 USB 端点收发数据;
    • 对于 PCIe CAN 卡,需基于 pci_driver 框架,通过 PCIe 配置空间获取资源。

三、参考资源

  • Linux 内核文档:Documentation/networking/can.rst(SocketCAN 子系统说明);
  • 内核源码中的 CAN 驱动示例:drivers/net/can/(如虚拟 CAN 驱动 vcan.c、常用控制器驱动 slcan.c);
  • CAN 工具手册:man candumpman cansend(测试工具用法)。

通过以上流程,可在 Ubuntu 环境中完成 CAN 驱动的开发、测试与部署,核心是遵循 Linux 内核驱动规范并适配 SocketCAN 子系统的要求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值