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

Linux下SPI设备驱动开发

一.SPI协议介绍

1.硬件连接介绍

        引脚含义:

        DO(MOSI):Master Output, Slave Input,SPI主控用来发出数据,SPI从设备用来接收数据。

        DI(MISO):Master Input, Slave Output,SPI主控用来发出数据,SPI从设备用来接收数据。

        SCK:Serial Clock,时钟。

        CS:Chip Select,芯片选择引脚。

2.SPI协议

a.传输示例

        设现在主控芯片要传输一个0x56数据给SPI FLASH,时序如下:

        首先我们要先拉低 CS0 选中 SPI Flash,0x56的二进制为 0b0101 0110,因此我们在每个 SCK 时钟周期,DO 输出对应的电平。SPI Flash 会在每个时钟周期的上升沿读取 DO 上的电平。 

b.SPI 模式

        在 SPI 协议中,有两个值来确定 SPI 的模式。CPOL:表示 SPICLK 的初始电平,0 为低电平,1 为高电平。CPHA:表示相位,即第一个还是第二个时钟沿采样数据,0 为第一个时钟沿,1 为第二个时钟沿。

        模式0:CPOL=0,CPHA=0,SPICLK初始电平为低电平,在第一个时钟沿采样数据。

        模式1:CPOL=0,CPHA=1,SPICLK初始电平为低电平,在第二个时钟沿采样数据

        模式2:CPOL=1,CPHA=0,SPICLK初始电平为高电平,在第一个时钟沿采样数据

        模式3:CPOL=1,CPHA=1,SPICLK初始电平为低电平,在第一个时钟沿采样数据。

        常用的是模式0和模式3,因为它们都是在上升沿采集数据,不用在乎时钟电平的初始电平是什么,只要在上升沿采集数据就行。 

3.特点

a.采用主-从模式(Master-Slave) 的控制方式

        SPI 规定了两个 SPI 设备之间通信必须由主设备 (Master) 来控制次设备 (Slave). 一个 Master 设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI 协议还规定 Slave 设备的 Clock 由 Master 设备通过 SCK 管脚提供给 Slave 设备, Slave 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave 设备不能正常工作。

c.采用同步方式(Synchronous)传输数据

        Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的。

d.数据交换(Data Exchanges)

        SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 “发送者(Transmitter)” 或者 “接收者(Receiver)”. 在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了。

​         一个 Slave 设备要想能够接收到 Master 发过来的控制信号, 必须在此之前能够被 Master 设备进行访问 (Access)。所以, Master 设备必须首先通过 SS/CS pin 对 Slave 设备进行片选, 把想要访问的 Slave 设备选上。

        ​ 在数据传输的过程中, 每次接收到的数据必须在下一次数据传输之前被采样.。如果之前接收到的数据没有被读取, 那么这些已经接收完成的数据将有可能会被丢弃, 导致 SPI 物理模块最终失效。因此, 在程序中一般都会在 SPI 传输完数据后, 去读取 SPI 设备里的数据, 即使这些数据(Dummy Data)在我们的程序里是无用的。

二.SPI总线设备驱动模型

1.SPI主机驱动

        SPI 主机驱动就是 SOC 的 SPI 控制器驱动,类似 I2C 驱动里面的适配器驱动。 Linux 内核使用 spi_master 表示 SPI 主机驱动, spi_master 是个结构体,定义在 include/linux/spi/spi.h 文件
中,部分代码如下:

struct spi_master {struct device dev;struct list_head list;
.....int (*transfer)(struct spi_device *spi,struct spi_message *mesg);
.....int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
}

        上述代码中,transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去。

        SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱
动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一
样。和 I2C 适配器驱动一样,SPI 主机驱动一般都是 SOC 厂商去编写的。

        SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册
spi_master。

        spi_master的申请与释放:

        spi_alloc_master函数用于申请spi_master,函数定义如下所示:

struct spi_master *spi_alloc_master(struct device *dev,unsigned size)

        dev:设备,一般是 platform_device 中的 dev 成员变量。

        size:私有数据大小,可以通过 spi_master_get_devdata 函数获取到这些私有数据。

         spi_master 的释放通过 spi_master_put 函数来完成,当我们删除一个 SPI 主机驱动的时候就需要释放掉前面申请的 spi_master,spi_master_put 函数原型如下:

void spi_master_put(struct spi_master *master)

        master:要释放的 spi_master。

        spi_master的注册与注销:

        当 spi_master 初始化完成以后就需要将其注册到 Linux 内核,spi_master 注册函数为
spi_register_master,函数原型如下:

int spi_register_master(struct spi_master *master)

        master:要注册的 spi_master。

        如果要注销 spi_master 的话可以使用 spi_unregister_master 函数,此函数原型为:

void spi_unregister_master(struct spi_master *master)

        master:要注销的 spi_master。

2.SPI设备驱动

        spi 设备驱动和 i2c 设备驱动也很类似,Linux 内核使用 spi_driver 结构体来表示 spi 设备驱动 , 我 们 在 编 写 SPI 设 备 驱 动 的 时 候 需 要 实 现 spi_driver 。 spi_driver 结 构 体 定 义 在include/linux/spi/spi.h 文件中,结构体内容如下:

    struct spi_driver {  int         (*probe)(struct spi_device *spi);  int         (*remove)(struct spi_device *spi);  void            (*shutdown)(struct spi_device *spi);  int         (*suspend)(struct spi_device *spi, pm_message_t mesg);  int         (*resume)(struct spi_device *spi);  struct device_driver    driver;  }; 

        可以看出,spi_driver 和 i2c_driver、platform_driver 基本一样,当 SPI 设备和驱动匹配成功
以后 probe 函数就会执行。spi_driver 初始化完成以后需要向 Linux 内核注册,spi_driver 注册函数为spi_register_driver,函数原型如下:

int spi_register_driver(struct spi_driver *sdrv)

        sdrv:要注册的 spi_driver。

        注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_driver 函
数完成 spi_driver 的注销,函数原型如下:

void spi_unregister_driver(struct spi_driver *sdrv)

        sdrv:要注销的 spi_driver。

        driver是为device服务的,spi_driver注册时会扫描SPI bus上的设备,进行驱动和设备的绑定,probe函数用于驱动和设备匹配时被调用。SPI的通信是通过消息队列机制,而不是像I2C那样通过与从设备进行对话的方式。

        spi_device结构体的内容如下:

    struct spi_device {  struct device       dev;  struct spi_master   *master;  u32         max_speed_hz;  u8          chip_select;  u8          mode;    u8          bits_per_word;  int         irq;  void            *controller_state;  void            *controller_data;  char            modalias[32];   }; 

        spi_driver 注册示例程序如下所示:

/* probe 函数 */
static int xxx_probe(struct spi_device *spi){/* 具体函数内容 */return 0;
}
/* remove 函数 */
static int xxx_remove(struct spi_device *spi){/* 具体函数内容 */return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct spi_device_id xxx_id[] = {{"xxx", 0},{}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {{ .compatible = "xxx" },{ /* Sentinel */ }
};
/* SPI 驱动结构体 */
static struct spi_driver xxx_driver = {.probe = xxx_probe,.remove = xxx_remove,.driver = {.owner = THIS_MODULE,.name = "xxx",.of_match_table = xxx_of_match,},.id_table = xxx_id,
};
/* 驱动入口函数 */
static int __init xxx_init(void){return spi_register_driver(&xxx_driver);
}
/* 驱动出口函数 */
static void __exit xxx_exit(void){spi_unregister_driver(&xxx_driver);
}module_init(xxx_init);
module_exit(xxx_exit);

3.SPI 设备和驱动匹配过程

        SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,SPI 总线为 spi_bus_type,定义在 drivers/spi/spi.c 文件中,内容如下所示:

struct bus_type spi_bus_type = {.name = "spi",.dev_groups = spi_dev_groups,.match = spi_match_device,.uevent = spi_uevent,
};

        SPI 设备和驱动的匹配函数为 spi_match_device,其函数内容如下:

static int spi_match_device(struct device *dev, struct device_driver *drv) {const struct spi_device *spi = to_spi_device(dev);const struct spi_driver *sdrv = to_spi_driver(drv);/* 用于完成设备树设备和驱动匹配 */if (of_driver_match_device(dev, drv))return 1;/* 用于ACPI形式的匹配 */if (acpi_driver_match_device(dev, drv))return 1;/* 用于传统无设备树设备和驱动匹配 */if (sdrv->id_table)return !!spi_match_id(sdrv->id_table, spi);/* 比较modalias成员变量和name成员变量是否相等 */return strcmp(spi->modalias, drv->name) == 0;
}

4.SPI 设备数据收发处理

        当向 Linux 内核注册成功 spi_driver 后就可以使用 SPI 核心层提供的 API 函数来对设备进行读写操作了。首先是 spi_transfer 结构体,此结构体用于描述 SPI 传输信息,结构体内容如下:

struct spi_transfer {const void *tx_buf;	//保存着要发送的数据void *rx_buf;		//用于保存接收到的数据unsigned len;		//要进行传输的数据长度dma_addr_t tx_dma;	dma_addr_t rx_dma;	struct sg_table tx_sg;struct sg_table rx_sg;unsigned cs_change:1;unsigned tx_nbits:3;unsigned rx_nbits:3;#define SPI_NBITS_SINGLE 0x01 	/* 1bit transfer */#define SPI_NBITS_DUAL 0x02 	/* 2bits transfer */#define SPI_NBITS_QUAD 0x04 	/* 4bits transfer */u8 bits_per_word;u16 delay_usecs;u32 speed_hz;struct list_head transfer_list;
};

        spi_transfer 需要组织成 spi_message, spi_message 也是一个结构体,内容如下:

struct spi_message {struct list_head transfers;	struct spi_device *spi;	unsigned is_dma_mapped:1;....../* completion is reported through a callback */void (*complete)(void *context);void *context;unsigned frame_length;unsigned actual_length;int status;	struct list_head queue;void *state;
};

        在使用spi_message之前需要对其进行初始化:

void spi_message_init(struct spi_message *m)
//m:要初始化的 spi_message

        spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中:

void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
//t:要添加到队列中的 spi_transfer
//m:spi_transfer 要加入的 spi_message

        spi_message 准备好后既可以进行数据传输,数据传输分为同步传输和异步传输:

/***同步传输会阻塞的等待 SPI 数据传输完成***/
int spi_sync(struct spi_device *spi, struct spi_message *message)
//spi:要进行数据传输的 spi_device
//message:要传输的 spi_message
/***异步传输不会阻塞等待,需设置spi_message中的
complete回调函数,当异步传输完成后此函数就会被调用***/
int spi_async(struct spi_device *spi, struct spi_message *message)
//spi:要进行数据传输的 spi_device
//message:要传输的 spi_message

        SPI 数据传输示例代码如下:

/********** SPI 多字节发送 **********/
static int spi_send(struct spi_device *spi, u8 *buf, int len){int ret;struct spi_message m;struct spi_transfer t = {	  //1. 定义一个spi_transfer结构体变量,并设置成员变量.tx_buf = buf,.len = len,};spi_message_init(&m); 		  //2. 初始化 spi_messagespi_message_add_tail(t, &m);  //3. 将 spi_transfer 添加到 spi_message 队列ret = spi_sync(spi, &m); 	  //4. 同步传输return ret;
}
/********** SPI 多字节接收 **********/
static int spi_receive(struct spi_device *spi, u8 *buf, int len){int ret;struct spi_message m;struct spi_transfer t = {	  //1. 定义一个spi_transfer结构体变量,并设置成员变量.rx_buf = buf,.len = len,};spi_message_init(&m); 		  //2. 初始化 spi_messagespi_message_add_tail(t, &m);  //3. 将 spi_transfer 添加到 spi_message 队列ret = spi_sync(spi, &m);	  //4. 同步传输return ret;
}
http://www.lryc.cn/news/594975.html

相关文章:

  • WPF实现加载初始页面后跳转到主界面并销毁初始页面资源
  • docker磁盘空间不足解决办法
  • Linux驱动15 --- buildroot杂项驱动开发方法
  • windows内核研究(驱动开发-多核同步之临界区和自旋锁)
  • 【Linux内核】Linux驱动开发
  • 智慧场景:定制开发开源AI智能名片S2B2C商城小程序赋能零售新体验
  • 莘默曹工-Cd Automation半导体调功器 RS2300-
  • Mac安装Typescript报错
  • 电脑声音修复?【图文详解】电脑没有声音?声音异常
  • 如何升级到macOS Tahoe:全面指南与实用步骤
  • node.js 为什么要装 express组件
  • Node.js的Transform 流
  • 深度学习-常用环境配置
  • Spring 对数组和集合类的自动注入
  • 机器学习初学者理论初解
  • Oracle 数据库共享池与大池调优指南
  • ElasticSearch:不停机更新索引类型(未验证)
  • Django基础(五)———模板结构
  • 中小型企业如何选择合适的WMS系统?
  • 如何用DispatcherTimer提高运行总时间的精确度
  • AI探索 | 基于 Node.js 开发 MCP 客户端+服务端及优秀项目分享
  • Node.js- node管理工具nvm
  • Spring @RequestBody注解详解与实践
  • Dockerfile 完全指南:从入门到精通
  • 西门子 S7-1500 信号模块硬件配置全解析:从选型到实战
  • (10)机器学习小白入门 YOLOv:YOLOv8-cls 模型评估实操
  • 使用 Tailwind CSS 控制元素在移动端不显示
  • 【LuckiBit】macOS/Linux 常用命令大全
  • Jenkins pipeline触发下游流水线
  • 用Java 代码实现一个简单的负载均衡逻辑