0
点赞
收藏
分享

微信扫一扫

uniapp 小程序 周选择器

若如初梘 2024-11-06 阅读 28

本文目录


  

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

一、 SPI驱动框架图

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

二、编写SPI驱动device框架

  1. 查看开发板中可用的SPI引脚。
    在这里插入图片描述
  2. 修改设备树文件。
    (1)修改spi控制器源码设备树文件。
       将控制器的pinctrl-0复用引脚的spi0m0修改为我们所使用的spi0m1。由于原厂工程师已经写完spi控制器的引脚复用功能,所以我们只需要修改即可。
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外设来设置!!

&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框架

#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", },
    {  }
};
MODULE_DEVICE_TABLE(of, mcp2515_of_match);


MODULE_DEVICE_TABLE(spi, mcp2515_id_table);

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. 注册字符设备或杂项设备框架

#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. SPI写数据

成功时:返回发送的字节数,通常是 len。
失败时:返回负的错误代码,通常为 -EINVAL(无效参数)或 -EIO(输入/输出错误)等。

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

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

#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);
    }
}


3. SPI读寄存器数据

这里有以下两个API函数。
(1)spi_read(直接读)
成功时:返回接收的字节数(通常是 len)。失败时:返回负的错误代码。

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 等)。

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命令,然后发送要读取寄存器的地址,再进行读取值。所以我们需要先写后读。

#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;  // 返回读取到的寄存器值
}

4. MCP2515相关配置

(1)复位操作(进入配置模式):通过查看器件手册来获取复位命令,再SPI来对其进行写入。

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;
}
举报

相关推荐

0 条评论