Linux内核编程(十九)SPI子系统一驱动MCP2515(SPI转CAN模块)

本文目录

对于SPI基础知识这里不做过多讲解,详情查看:SPI基础知识实践讲解-STM32版。

一、 SPI驱动框架图

本框图中spi核心层和spi适配器驱动层不需要我们去关心,如果未来要去原厂工作的话,可以深入了解其工作原理和内容,这里我们不做过多介绍。

二、编写SPI驱动device框架

  1. 查看开发板中可用的SPI引脚。
  2. 修改设备树文件。
    修改完成设备树后重新编译设备树源码生成出来的boot.img烧录至开发板。
    (1)修改spi控制器源码设备树文件。
       将控制器的pinctrl-0复用引脚的spi0m0修改为我们所使用的spi0m1。由于原厂工程师已经写完spi控制器的引脚复用功能,所以我们只需要修改即可。
c 复制代码
spi0: spi@fe610000 {
    compatible = "rockchip,rk3066-spi";
    reg = <0x0 0xfe610000 0x0 0x1000>;  // SPI 控制器寄存器基地址
    interrupts = <GIC SPI 103 IRQ TYPE LEVEL HIGH>;  // 中断配置
    #address-cells = <1>;  // 地址单元数量
    #size-cells = <0>;  // 大小单元数量
    clocks = <&cru CLK_SPI>, <&cru PCLK_SPIO>;  // SPI 和 APB 时钟
    clock-names = "spiclk", "apb pclk";  // 时钟名称
    dmas = <&dmac0 20>, <&dmac0 21>;  // DMA 通道配置(TX, RX)
    dma-names = "tx", "rx";  // DMA 通道名称
    pinctrl-names = "default", "high-speed";  // 引脚控制模式
   // pinctrl-0 = <&spi0m0_cs0 &spi0m0_csl &spi0m0_pins>;  // 默认引脚控制配置
    pinctrl-0 = <&spi0m1_cs0  &spi0m1_pins>;  // 默认引脚控制配置
  //  pinctrl-1 = <&spi0m0_cs0 &spi0m0_csl &spi0m0_pins_high_speed>;  // 高速模式引脚配置
    pinctrl-1 = <&spi0m1_cs0  &spi0m1_pins_high_speed>;  // 高速模式引脚配置
    status = "disabled";  // 当前 SPI 控制器状态,禁用状态
};

(2)添加引用spi控制器

在开发板设备树根节点下添加以下节点。如果设备树中没有reg 和 spi-max-freguengy这俩个属性就会返回错误。返回错误设备注册不成功。从而就不会和驱动匹配上(具体分析spi控制器的注册流程)。以下的SPI工作模式以及传输模式等都需要根据具体的SPI外设来设置!!

c 复制代码
&spi0 {
	status ="okay";
	mcp2515:mcp2515@0{
		status ="okay";
		compatible="my-mcp2515";
		reg = <0>; /* 1. 选择片选0 */
		spi-max-frequency = <24000000>; /* 2. 设置SPI时钟最大频率为24MHz,不要超过50M */
		
		//3. 设置工作模式,如果不写,则默认为0,0
		spi-cpha = <1>;  // 设置时钟相位
		spi-cpol = <0>;  // 设置时钟极性 
		
		//4. 设置传输模式,高位先传还是低位先传。默认为高位先传。
		//spi-lsb-first = <1>;  // 设置为 LSB 优先
		
		//5. 选择片选信号为高电平/低电平选中。默认为低电平选中。
		// spi-cs-high = <1>;      // 设置片选信号为高电平有效
	}
}

reg 属性用于设置 SPI 设备的片选引脚,而 spi-max-frequency 属性则用于设置 SPI 总线的最大时钟频率。通常,SPI 设备的片选和时钟频率都需要在设备树中进行配置,以便正确地初始化 SPI 设备。

三、编写SPI驱动driver框架

c 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/kernel.h>


static int mcp2515_probe(struct spi_device *spi)
{
    return 0;
}

static int mcp2515_remove(struct spi_device *spi)
{
    return 0;
}

static const struct of_device_id mcp2515_of_match[] = {
    { .compatible = "my-mcp2515", },
    {  }
};

static struct spi_driver mcp2515_driver = {
    .driver = {
        .name = "mcp2515",
        .owner = THIS_MODULE,
        .of_match_table = mcp2515_of_match,
    },
    .probe = mcp2515_probe,
    .remove = mcp2515_remove,
};

static int __init mcp2515_init(void)
{
    return spi_register_driver(&mcp2515_driver);
}

static void __exit mcp2515_exit(void)
{
    spi_unregister_driver(&mcp2515_driver);
}

module_init(mcp2515_init);
module_exit(mcp2515_exit);
MODULE_LICENSE("GPL");

四、实验一编写mcp2515驱动

MCP2515 是由 Microchip 提供的一款独立的 CAN (Controller Area Network) 控制器,它通过 SPI 接口与主机通信。即SPI转CAN的一个模块。

1. 注册字符设备或杂项设备框架

c 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

static ssize_t myread(struct file *fp, char __user *ubuf, size_t size, loff_t *loft)
{
	  return 0;
}

static int my_open(struct inode *node, struct file *fp)
{
       return 0;
}

ssize_t my_write(struct file *fp, const char __user *ubuf, size_t size, loff_t *loft)
{
    // 若不需要实际的写操作,可以直接返回0或-EINVAL
    return 0;
}

static const struct file_operations myfops = {
    .read = myread,
    .open = my_open,
    .write = my_write,
};

static struct miscdevice mcp2515_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "mcp2515",
    .fops = &myfops,
};

static int mcp2515_probe(struct spi_device *spi)
{
    int ret;
    ret = misc_register(&mcp2515_misc);
    if (ret) {
        pr_err("Failed to register misc device\n");
        return ret;
    }

    return 0;
}

static int mcp2515_remove(struct spi_device *spi)
{
    pr_info("MCP2515 SPI device removed\n");
    misc_deregister(&mcp2515_misc);
    return 0;
}

static const struct of_device_id mcp2515_of_match[] = {
    { .compatible = "my-mcp2515", },
    { }
};

static struct spi_driver mcp2515_driver = {
    .driver = {
        .name = "mcp2515",  //不会与之匹配
        .owner = THIS_MODULE,
        .of_match_table = mcp2515_of_match,
    },
    .probe = mcp2515_probe,
    .remove = mcp2515_remove,
};

static int __init mcp2515_init(void)
{
    pr_info("MCP2515 driver loading\n");
    return spi_register_driver(&mcp2515_driver);
}

static void __exit mcp2515_exit(void)
{
    pr_info("MCP2515 driver unloading\n");
    spi_unregister_driver(&mcp2515_driver);
}

module_init(mcp2515_init);
module_exit(mcp2515_exit);

MODULE_LICENSE("GPL");

2. MCP2515相关配置

MCP2515提供了一系列的SPI指令,通过向MCP2515发送SPI指令就可以完成复位、读、写等操作,具体的SPI指令如下所示:

(1)SPI向寄存器写数据

成功时:返回发送的字节数,通常是 len。

失败时:返回负的错误代码,通常为 -EINVAL(无效参数)或 -EIO(输入/输出错误)等。
注意:我们要将写入的数据一次性的按照写入的先后顺序存放在一个数组中。

c 复制代码
int spi_write(struct spi_device *spi, const void *buf, unsigned len);
/*
	spi:指向目标 SPI 设备的指针。这是你要向其发送数据的 SPI 设备。
	buf:指向包含要发送的数据的缓冲区的指针。这些数据将被通过 SPI 总线发送。
	len:要发送的数据的字节数。表示从 buf 中发送的字节数。
*/

●示例: 我们通过mcp2515手册可知,对其进行写操作需要写入0x02。使用spi_write来进行写操作。

c 复制代码
#include <linux/spi/spi.h>

struct spi_device *spi;
void mcp2515_write_reg(char reg, char value)
{
    int ret;
    char write_buf[] = {0x02, reg, value};  // MCP2515 写寄存器的指令

    // 向 MCP2515 发送数据
    ret = spi_write(spi, write_buf, sizeof(write_buf));
    if (ret < 0) {
        printk(KERN_ERR "MCP2515 write reg failed: %d\n", ret);
    }
}

(2)SPI读寄存器数据

这里有以下两个API函数。

(1)spi_read(直接读)

成功时:返回接收的字节数(通常是 len)。失败时:返回负的错误代码。

c 复制代码
int spi_read(struct spi_device *spi, void *buf, unsigned len);
/*
	spi:指向 SPI 设备的指针。
	buf:指向存放接收数据的缓冲区的指针。接收到的数据将存放在这个缓冲区中。
	len:要接收的数据的字节数。
*/

(2)spi_write_then_read(先写后读)

这个函数用于 SPI 设备的 "write then read" 操作,即先发送数据,然后读取数据。成功时:返回传输的字节数,通常等于 n_tx 或 n_rx,这取决于设备的特性。失败时:返回负的错误代码(如 -EINVAL 或 -EIO 等)。

c 复制代码
int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx);
/*
	spi:指向 SPI 设备的指针,表示我们将要操作的 SPI 设备。
	txbuf:指向要发送的缓冲区的指针。它包含我们要发送的数据。
	n_tx:要发送的字节数。
	rxbuf:指向接收数据的缓冲区的指针。读取到的数据会存放在这个缓冲区中。
	n_rx:要接收的字节数。
*/

●示例: mcp2515读寄存器的值,我们查看其手册发现,要想读寄存器的值需要先写入0x03命令,然后发送要读取寄存器的地址,再进行读取值。所以我们需要先写后读。

c 复制代码
#include <linux/spi/spi.h>
struct spi_device *spi;

char mcp2515_read_reg(char reg)
{
    int ret;
    u8 write_buf[] = { 0x03, reg };  // 0x03 是读取寄存器命令,具体命令值请参考 MCP2515 数据手册
    u8 read_buf;  // 用于存储读取到的数据

    // SPI write then read 操作
    ret = spi_write_then_read(spi, write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf));
    if (ret < 0) {
        printk(KERN_ERR "SPI write then read error: %d\n", ret);
        return ret;  // 返回错误代码
    }

    printk(KERN_INFO "Read value from MCP2515 register 0x%02X: 0x%02X\n", reg, read_buf);
    return read_buf;  // 返回读取到的寄存器值
}

(3)复位操作(进入配置模式)

通过查看器件手册来获取复位命令,当复位器件后,器件会进入配置模式(具体看器件手册,每个器件不一样,这里只针对MCP2515)。

c 复制代码
struct spi_device *spi;
static int mcp2515_reset()
{
    int ret;
    u8 write_buf[] = { 0xc0 };  // 假设 0xc0 是复位命令,具体命令根据 datasheet 确定
    // 向 MCP2515 设备写入复位命令
    ret = spi_write(spi, write_buf, sizeof(write_buf));
    if (ret < 0) {
        printk(KERN_ERR "SPI write error: %d\n", ret);
        return ret;
    }
    printk(KERN_INFO "MCP2515 reset command sent successfully.\n");
    return 0;
}

如何来查看我们设置的复位模式是否成功呢?由于器件当复位后,器件会进入配置模式。可查看器件的状态寄存器(只读,地址0x0e),使用上面的读命令来获取该寄存器的值,从而来判断是否配置成功。

(4)配置CAN波特率

MCP2515发送SPI复位指令之后就会进入配置模式下,只有在配置模式才可以修改其他寄存器来配置功能。

CAN总线上的所有器件都必须使用相同的波特率,而CNF1、CNF2和CNF3三个寄存器就是用来配置CAN总线波特率的,更具体的说明为CNF1、CNF2和CNF3三个寄存器通过对位时间各个段的配置,进而设置CAN总线的波特率。对于位时间的具体讲解,请查看CAN总线驱动这篇文章。

每个 CAN 报文的二进制数据都通过这些位时间段进行传输。位时间的配置对于 CAN 总线的正确通信至关重要,它影响总线的传输速率、同步和误差处理能力,TQ是每个段持续时间的最小单位。对于mcp2515手册上给出了TQ的计算公式,如下:

其中FOSC为MCP2515外设晶振频率8MHZ(每秒钟有 8,000,000 次周期),BRP为CNF1寄存器设置的预分频系数,可以任意设置,确保最后的TQ为整数即可,这里设置为0x01,然后对TQ进行计算(2×2/8000000)得到TQ=500ns,这时候已经可以确定最小单位TQ的值为500ns。

根据下面的公式,假设需要设置CAN的波特率为125k,则位时间为:1/125000=8000ns。则总的位时间所占8000/500=16个TQ。所以我们需要配置CNF1、CNF2和CNF3三个寄存器来设置相应位时间段的TQ数量,从而来配置为指定的CAN波特率值。

而在MCP2515的数据手册中要求时间段的设定必须满足以下要求:

(1)传播段 + 相位缓冲段 PS1 >= 相位缓冲段 PS2

(2)传播段 + 相位缓冲段 PS1 >= TDELAY (TDELAY 典型值为 1-2 TQ)

(3)相位缓冲段 PS2 > 同步跳转宽度 SJW(SJW 最大值为 4 TQ,一般取1 TQ即可满足要求)

最终确定各个段的值位:同步段1个TQ,传播段2个TQ,相位缓冲段PS1 7个TQ,相位缓冲段PS2 6个TQ。

位段 TQ数量
同步段 1
传播段 2
相位缓冲段1 7
相位缓冲段2 6

然后来根据各个段的值确定CNF1、CNF2、CNF3的取值。

寄存器 寄存器地址 设置值
CNF1 0x2a 0x01
CNF2 0x29 0xb1
CNF3 0x28 0x05

我们可以根据上表来编写代码来配置,如下所示:

c 复制代码
#define CNF1 0x2a                    // 寄存器定义
#define CNF2 0x29
#define CNF3 0x28

 
mcp2515_write_reg(CNF1, 0x01);  // 写入寄存器配置值
mcp2515_write_reg(CNF2, 0xb1);
mcp2515_write_reg(CNF3, 0x05);

(5)配置接收缓冲器0

只需要配置bit6-5为11,其余位全为0,即0x60。

c 复制代码
#define RXB0CTRL 0x60

mcp2515_write_reg(CNF3, 0x60);

(6)配置中断使能寄存器

因为上一步配置的是缓冲0寄存器,所以我们打开缓冲器0的中断,如下所示:

c 复制代码
#define CANINTE 0x2b

mcp2515_write_reg(CANINTE, 0x05);

(7)拓展:掩码-位修改指令

通过 SPI 总线改变 MCP2515 控制器的寄存器位。这种操作通常用于设置、清除或切换某些寄存器的特定位。mask为1的位可以被修改,为0的位不可以修改。

c 复制代码
#include <linux/spi/spi.h>
#include <linux/kernel.h>
struct spi_device *spi_dev;

// mcp2515_change_regbit 用于改变 MCP2515 寄存器的某些位
void mcp2515_change_regbit(char reg, char mask, char value) {
    int ret;
    char write_buf[] = {0x05, reg, mask, value};  // 写入指令:0x05 是指令,后面是寄存器地址、掩码和新值

    // 通过 SPI 写入指令来修改寄存器位
    ret = spi_write(spi_dev, write_buf, sizeof(write_buf));
    if (ret < 0) {
        printk(KERN_ERR "mcp2515_change_regbit failed: SPI write error\n");
    }
}

举例: 设置地址为0x2e的寄存器的第6-7bit为10。使用代码:mcp2515_change_regbit(0x2e, 0xc0, 0x80);

(8)修改控制寄存器

为了方便演示,这里我们设置为环回模式。其他时刻可以任意选择相应的模式即可。

c 复制代码
#define CANCTRL 0xf

mcp2515_change_regbit(CANCTRL, 0xe0, 0x40);  

3. 完善驱动write函数

MCP2515有三个发送缓冲器,每个发送缓冲器的控制由TXBnCTRL(n范围0~2)寄存器管理,该寄存器决定了何时发送报文以及发送时的报文状态。我们使用发送缓冲器0来做实验,对应的寄存器地址为0x30。

(1)首先使用发送缓冲器0的控制寄存器来设置优先级--设置为最高优先级。

c 复制代码
#define TXB0CTRL 0x30   //发送缓冲器控制寄存器地址    
      
mcp2515_change_regbit(TXB0CTRL, 0x03, 0x03);   //只对该寄存器低两位进行修改,修改值为0x03

(2)每个发送缓冲区的控制寄存器 TXBnCTRL 的配置是发送给缓冲器的第一个字节。接下来的 5 个字节用于装载 CAN 帧的标准和扩展标识符,以及其他报文的仲裁信息(分别对应寄存器3-3~3-7)。最后的 8 个字节用于存储待发送的 8 个数据字节(对应寄存器3-8)。我们使用发送缓冲器0,所以这些寄存器对应的地址为0x31~0x3d,对于每个发送缓冲器相关寄存器的地址都为连续地址。

c 复制代码
#define  SEND_REG0  0x31

ssize_t my_write(struct file *fp, const char __user *ubuf, size_t size, loff_t *loft)
{
	char w_kbuf[13] = {0};  //设置13个字节存储数组
	int ret;	          
	// 从用户空间复制数据到内核缓冲区
	ret = copy_from_user(w_kbuf, buf, size);
	if (ret) {
	    printk("copy_from_user w_kbuf is error\n");
	    return -1;
	}
	// 将数据写入MCP2515寄存器
	for (i = 0; i < sizeof(w_kbuf); i++) {
	    mcp2515_write_reg(SEND_REG0 + i, w_kbuf[i]);
	}   
	 return 0;
}

数据设置完成之后,需要将TXBnCTRL控制寄存器的bit3设置为1,从而启动相应缓冲器的报文发送,TXBnCTRL寄存器就是上面修改发送缓冲器优先级的寄存器,具体设置代码如下所示:

c 复制代码
#define TXB0CTRL 0x30   //发送缓冲器控制寄存器地址
mcp2515_change_regbit(TXB0CTRL, 0x08, 0x08);   //只对该寄存器bit3进行修改,将bit3设置为1

(3)在报文发送成功后,CANINTF.TXnIF(n范围0~2)寄存器将会被置1,该寄存器内容如下所示。可以通过该寄存器来判断报文是否发送成功,判断完成之后,需要对该寄存器的相应标志位进行手动清零。由于使用的是缓冲器为0,所以这里要判断的为位bit2,

c 复制代码
#define CANINTF 0x2c 
 
 // 等待发送完成
while (!(mcp2515_read_reg(CANINTF) & (1 << 2)));
 
// 清除发送完成标志
mcp2515_change_regbit(CANINTF, 0x04, 0x00);

●完整write代码

c 复制代码
#include <linux/spi/spi.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>  // 用于 copy_from_user 等操作

#define TXB0CTRL 0x30   //发送缓冲器控制寄存器地址
#define  SEND_REG0  0x31  //发送缓冲器0地址
#define CANINTF 0x2c //发送完成中断标志寄存器
#define CANCTRL 0xf


ssize_t my_write(struct file *fp, const char __user *ubuf, size_t size, loff_t *loft)
{
	char w_kbuf[13] = {0};  //设置13个字节存储数组
	int ret;	          
	// 从用户空间复制数据到内核缓冲区
	ret = copy_from_user(w_kbuf, buf, size);
	if (ret) {
	    printk("copy_from_user w_kbuf is error\n");
	    return -1;
	}
	// 将数据写入MCP2515寄存器
	for (i = 0; i < sizeof(w_kbuf); i++) {
	    mcp2515_write_reg(SEND_REG0 + i, w_kbuf[i]);
	}   
	mcp2515_change_regbit(TXB0CTRL, 0x08, 0x08);   //开始发送
		
	while (!(mcp2515_read_reg(CANINTF) & (1 << 2)));  // 等待发送完成
	mcp2515_change_regbit(CANINTF, 0x04, 0x00); // 清除发送完成标志
    return size;
}

4. 完善驱动read函数

MCP2515 具有两个全接收缓冲器,当数据报文传送至某一接收缓冲器时,与该接收缓冲器对应的CANINTF.RXnIF(n范围0~1)位将置1,可以通过CANINTF.RXnIF寄存器的值来判断是否接收完成,CANINTF寄存器内容在上一节已经列出,这里不再重复。

c 复制代码
#define CANINTF 0x2c 
 
// 等待接收缓冲区满标志位被设置
while (!(mcp2515_read_reg(CANINTF) & (1 << 0)));

编写读数据相关的代码,接收缓冲器与发送寄存器相匹配,前5个字节用来装载标准和扩展标识符以及其他报文仲裁信息,最后的8个字节用于装载等待发送报文的8个可能的数据字节,且地址是连续的,间隔为一个字节。

接收缓冲器0的标准标识符高位寄存器地址为0x61,所以可以通过以下代码进行设置。当数据传送完成之后需要对CANINTF.RXnIF寄存器传输完成标志位清零,并且使用copy_to_user将读取到的数据传输到用户空间。

c 复制代码
static ssize_t myread(struct file *fp, char __user *ubuf, size_t size, loff_t *loft)
{
    char r_kbuf[13] = {0};  // 内核缓冲区,用于存储从设备读取的数据
    int i;
    int ret;
 
    // 等待接收缓冲区满标志位被设置
    while (!(mcp2515_read_reg(CANINTF) & (1 << 0)));
 
    // 从接收缓冲区读取数据到内核缓冲区
    for (i = 0; i < sizeof(r_kbuf); i++) {
        r_kbuf[i] = mcp2515_read_reg(0x61 + i);
    }
 
    // 清除接收缓冲区满标志位
    mcp2515_change_regbit(CANINTF, 0x01, 0x00);
 
    // 将内核缓冲区的数据复制到用户缓冲区
    ret = copy_to_user(buf, r_kbuf, size);
    if (ret) {
        printk("copy_to_user r_kbuf is error\n");
        return -1;  // 返回-1表示复制数据失败
    }
 
    return 0;  // 返回0表示成功读取数据
}

5. 完整驱动代码

c 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

struct spi_device *spi;  // 定义 SPI 设备

// 寄存器地址定义
#define CNF1        0x2a  // 用于控制CAN波特率
#define CNF2        0x29
#define CNF3        0x28
#define RXB0CTRL    0x60  // 接收缓冲器0控制器
#define CANINTE     0x2b  // 中断使能寄存器
#define CANCTRL     0xf   // 控制寄存器(修改工作模式)
#define TXB0CTRL    0x30  // 发送缓冲器控制寄存器地址
#define SEND_REG0   0x31  // 发送缓冲器0地址
#define CANINTF     0x2c  // 发送完成中断标志寄存器
#define STATE_REG   0x0e  // 状态寄存器(只读)

// 命令定义
#define MCP2515_WRITE_CMD  0x02  // 写指令
#define MCP2515_READ_CMD   0x03  // 读指令
#define MASK_CMD           0x05  // 掩码指令
#define REST_CMD           0xc0  // 复位指令

// SPI 写寄存器
void mcp2515_write_reg(u8 reg, u8 value)
{
    int ret;
    u8 write_buf[] = { MCP2515_WRITE_CMD , reg, value };  // MCP2515 写寄存器的指令

    // 向 MCP2515 发送数据
    ret = spi_write(spi, write_buf, sizeof(write_buf));
    if (ret < 0) {
        printk(KERN_ERR "MCP2515 write reg failed: %d\n", ret);
    }
}

// SPI 读寄存器
u8 mcp2515_read_reg(u8 reg)
{
    int ret;
    u8 write_buf[] = { MCP2515_READ_CMD, reg };  // 0x03 是读取寄存器命令
    u8 read_buf;  // 用于存储读取到的数据

    // SPI write then read 操作
    ret = spi_write_then_read(spi, write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf));
    if (ret < 0) {
        printk(KERN_ERR "SPI write then read error: %d\n", ret);
        return ret;  // 返回错误代码
    }

    printk(KERN_INFO "Read value from MCP2515 register 0x%02X: 0x%02X\n", reg, read_buf);
    return read_buf;  // 返回读取到的寄存器值
}

// 掩码设置位
void mcp2515_change_regbit(u8 reg, u8 mask, u8 value)
{
    int ret;
    u8 write_buf[] = {MASK_CMD , reg, mask, value };  // 写入指令:0x05 是指令,后面是寄存器地址、掩码和新值

    // 通过 SPI 写入指令来修改寄存器位
    ret = spi_write(spi, write_buf, sizeof(write_buf));
    if (ret < 0) {
        printk(KERN_ERR "mcp2515_change_regbit failed: SPI write error\n");
    }
}

// MCP2515 复位
static int mcp2515_reset(void)
{
    int ret;
    u8 write_buf[] = {REST_CMD};  // 假设 0xc0 是复位命令,具体命令根据 datasheet 确定
    // 向 MCP2515 设备写入复位命令
    ret = spi_write(spi, write_buf, sizeof(write_buf));
    if (ret < 0) {
        printk(KERN_ERR "SPI write error: %d\n", ret);
        return ret;
    }
    printk(KERN_INFO "MCP2515 reset command sent successfully.\n");
    return 0;
}

static int my_open(struct inode *node, struct file *fp)
{
    return 0;
}

static ssize_t myread(struct file *fp, char __user *ubuf, size_t size, loff_t *loft)
{
    u8 r_kbuf[13] = {0};  // 内核缓冲区,用于存储从设备读取的数据
    int i;
    int ret;

    // 等待接收缓冲区满标志位被设置
    while (!(mcp2515_read_reg(CANINTF) & (1 << 0)));

    // 从接收缓冲区读取数据到内核缓冲区
    for (i = 0; i < sizeof(r_kbuf); i++) {
        r_kbuf[i] = mcp2515_read_reg(0x61 + i);
    }

    // 清除接收缓冲区满标志位
    mcp2515_change_regbit(CANINTF, 0x01, 0x00);

    // 将内核缓冲区的数据复制到用户缓冲区
    ret = copy_to_user(ubuf, r_kbuf, size);
    if (ret != 0) {
        printk(KERN_ERR "Failed to copy to user\n");
        return -EFAULT;  // 返回错误代码
    }

    return 0;  // 返回0表示成功读取数据
}

ssize_t my_write(struct file *fp, const char __user *ubuf, size_t size, loff_t *loft)
{
    u8 w_kbuf[13] = {0};  // 设置13个字节存储数组
    int i;
    int ret;

    // 从用户空间复制数据到内核缓冲区
    ret = copy_from_user(w_kbuf, ubuf, size);
    if (ret != 0) {
        printk(KERN_ERR "Failed to copy from user\n");
        return -EFAULT;  // 返回错误代码
    }

    // 将数据写入 MCP2515 寄存器
    for (i = 0; i < sizeof(w_kbuf); i++) {
        mcp2515_write_reg(SEND_REG0 + i, w_kbuf[i]);
    }

    mcp2515_change_regbit(TXB0CTRL, 0x08, 0x08);  // 开始发送

    // 等待发送完成
    while (!(mcp2515_read_reg(CANINTF) & (1 << 2)));

    // 清除发送完成标志
    mcp2515_change_regbit(CANINTF, 0x04, 0x00);

    return size;
}

static const struct file_operations myfops = {
    .read = myread,
    .open = my_open,
    .write = my_write,
};

static struct miscdevice mcp2515_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "mcp2515",
    .fops = &myfops,
};

static int mcp2515_probe(struct spi_device *spi)
{
    int ret, value;

    ret = misc_register(&mcp2515_misc);
    if (ret) {
        pr_err("Failed to register misc device\n");
        return ret;
    }

    mcp2515_reset();  // 复位 MCP2515 设备
    value = mcp2515_read_reg(STATE_REG);  // 读取寄存器值
    printk(KERN_INFO "value is %x\n", value);  // 打印读取的值

    mcp2515_write_reg(CNF1, 0x01);  // 写入寄存器配置值
    mcp2515_write_reg(CNF2, 0xb1);
    mcp2515_write_reg(CNF3, 0x05);

    mcp2515_write_reg(RXB0CTRL, 0x60);
    mcp2515_write_reg(CANINTE, 0x05);
    mcp2515_change_regbit(CANCTRL, 0xe0, 0x40);

    value = mcp2515_read_reg(STATE_REG);  // 读取寄存器值
    printk(KERN_INFO "value is %x\n", value);  // 打印读取的值

    return 0;
}

static int mcp2515_remove(struct spi_device *spi)
{
    pr_info("MCP2515 SPI device removed\n");
    misc_deregister(&mcp2515_misc);
    return 0;
}

static const struct of_device_id mcp2515_of_match[] = {
    { .compatible = "my-mcp2515", },
    { }
};

static struct spi_driver mcp2515_driver = {
    .driver = {
        .name = "mcp2515",
        .owner = THIS_MODULE,
        .of_match_table = mcp2515_of_match,
    },
    .probe = mcp2515_probe,
    .remove = mcp2515_remove,
};

static int __init mcp2515_init(void)
{
    pr_info("MCP2515 driver loading\n");
    return spi_register_driver(&mcp2515_driver);
}

static void __exit mcp2515_exit(void)
{
    pr_info("MCP2515 driver unloading\n");
    spi_unregister_driver(&mcp2515_driver);
}

module_init(mcp2515_init);
module_exit(mcp2515_exit);

MODULE_LICENSE("GPL");

6. 应用程序测试代码

c 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
 
// 主函数,程序入口点
int main(int argc, char *argv[]){
    int fd;  // 文件描述符
    int i;   // 循环变量
 
    // 写缓冲区,包含13个字节的数据,将发送到MCP2515
    char w_buf[13]= {0x66,0x08,0x22,0x33,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
    // 读缓冲区,用于接收从MCP2515读取的数据
    char r_buf[13] = {0};
 
    // 打开MCP2515设备文件,获取文件描述符
    fd = open("/dev/mcp2515", O_RDWR);
    if(fd < 0){
        // 打开设备文件失败,打印错误信息并返回
        printf("open /dev/mcp2515 error \n");
        return -1;
    }
 
    // 将写缓冲区的数据写入设备
    write(fd, w_buf, sizeof(w_buf));
    // 从设备读取数据到读缓冲区
    read(fd, r_buf, sizeof(r_buf));
    
    // 打印读缓冲区的数据
    for(i = 0; i < 13; i++){
        printf("r_buf[%d] is %d\n", i, r_buf[i]);
    }
    
    // 关闭设备文件
    close(fd);
    return 0;  // 返回0表示程序正常结束
}
相关推荐
脱了格子衬衫44 分钟前
linux安装ansible
linux·运维·ansible
小丑西瓜6661 小时前
MySQL库操作
linux·服务器·数据库·mysql
小珑也要变强3 小时前
shell脚本基本概念讲解
linux·运维
爱吃喵的鲤鱼4 小时前
linux 用C语言编写自己的myshell
linux·运维·服务器·c语言·算法
矛取矛求8 小时前
Linux如何更优质调节系统性能
linux
内核程序员kevin9 小时前
在Linux环境下使用Docker打包和发布.NET程序并配合MySQL部署
linux·mysql·docker·.net
kayotin9 小时前
Wordpress博客配置2024
linux·mysql·docker
Ztiddler10 小时前
【Linux Shell命令-不定期更新】
linux·运维·服务器·ssh
小小不董10 小时前
Oracle OCP认证考试考点详解082系列16
linux·运维·服务器·数据库·oracle·dba
a1denzzz10 小时前
Linux系统的网络设置
linux·服务器·网络