基于硬件 SPI 的数据抽象实例(附代码)
扫描二维码
随时随地手机看文章
作者 | Acuity
1.写在前面
spi(Serial Peripheral Interface)即串行外设接口。与i2c一样,spi也常用外设设备通信的总线,从事嵌入式开发必不可少的掌握。根据本人以往的经历,对spi进行总结(主要是MCU范畴,Linux已有成熟的驱动设备),主要目的及实现:1)spi总线与spi设备分离;2)快速使用新的硬件spi或者模拟spi;3)方便移植spi总线设备及spi外设程序到不同mcu平台。2.spi总线抽象
此部分实现源码为:spi_core.c spi_core.h2.1 spi总线模型对外接口(API)
/*extern function*/
extern int spi_send_then_recv(struct spi_dev_device *spi_dev,const void *send_buff,
unsigned short send_size,void *recv_buff,unsigned short recv_size);
extern int spi_send_then_send(struct spi_dev_device *spi_dev,const void *send_buff1,
unsigned short send_size1,const void *send_buff2,unsigned short send_size2);
extern int spi_send_recv(struct spi_dev_device *spi_dev,const void *send_buff,void *recv_buff,unsigned short data_size);
extern int spi_send(struct spi_dev_device *spi_dev,const void *send_buff,unsigned short send_size);
1)spi_send_then_recv,标准spi,常规操作,发送完一帧再接收,如读取某芯片寄存器的值;2)spi_send_then_send,标准spi,常规操作,发送完一帧再发送,如向某芯片寄存器(地址)写入数据;3)spi_send_recv,非标spi,具体看芯片时序图,产生时钟信号,发送完成的同时,也接收完成;第二种情况是,只接收,发送动作只是用来产生时钟信号,如一些AD芯片;4)spi_send,标准或非标spi都使用,只发送无返回值或者无须理会返回值,如spi LCD屏。2.2 spi总线抽象API实现
以“spi_send_then_recv”函数为例:1)spi_dev:spi设备指针,类型为“struct spi_dev_device”,驱动一个spi外设时,首先需要对此指针进行初始化;2)send_buff:待发送数据(缓存);3)send_size:发送数据量大小(单位字节);4):recv_buff:存放返回值数据缓存(地址);5):recv_size:返回数据量大小。另外3个函数,第一个参数都为spi设备指针,其他参数为发送/接收缓冲区,收发数据量等,通过变量名即可看出。2.3 struct spi_de_device
该结构体为关键,调用API驱动一个外设时,需要先初始化(类似Linux的注册设备驱动)。一个完整的spi外设,包括片选和总线量部分,一个总线可和多个片选组成,驱动多个外设,因此struct spi_dev_device设计原型为:struct spi_dev_device
{
void (*spi_cs)(unsigned char state);
struct spi_bus_device *spi_bus;
};
1)第一个参数为函数指针,主要功能的实现spi外设片选的选择(拉低/拉高)功能;2)第二个参数为spi总线相关的结构体指针,主要是底层相关收据收发的的功能,具体继续往下看改结构体。2.4 struct spi_bus_device *spi_bus
该结构体为底层硬件相关的spi总线实现,具体由实际需求实现,如用硬件spi还是用模拟spi。struct spi_bus_device*spi_bus原型为:struct spi_bus_device
{
int (*spi_bus_xfer)(struct spi_dev_device *spi_bus,struct spi_dev_message *msg);
void *spi_phy;
unsigned char data_width;
};
1)第一个参数是函数指针,为spi总线收发函数,这部分就是我们平常写裸机代码时候写到的,只是这里把它放在一个结构体里面,以函数指针的方式实现;这样的好处是,上层接口不变,更好其他MCU或者使用模拟spi时,只需修改此部分的函数实体,上层代码不需变动。2)第二个参数,一个指针,表示具体物理spi,如stm32的SPI1、SPI2,或者模拟spi;3)第三参数,数据宽度,一般是8bit或者16bit。其他参数,如数据速率、spi模式等,其实也可以放在此处,只是个人觉得此类参数不常变动,为了节约内存,故不加入此结构体配置中。下面中断分析函数指针“int (*spi_bus_xfer) (struct spi_dev_device *spi_bus,struct spi_dev_message *msg)”的实现。
2.5 spi_bus_xfer
该函数指针入口参数为spi设备指针(struct spi_dev_device )、spi设备信息帧指针(struct spi_dev_message)。struct spi_dev_device与前面提及的为同一类参数,struct spi_dev_message为收发数据信息帧,其原型如下:struct spi_dev_message
{
const void *send_buf;
void *recv_buf;
int length;
unsigned char cs_take : 1;
unsigned char cs_release : 1;
};
1)send_buf:待发送数据(缓存);2)recv_buf:存放返回值数据缓存(地址);3)length:发送/接收数据长度;4)cs_take:使能片选;5)cs_release:释放片选。3. spi总线抽象实现
此部分实现源码为:spi_hw.c spi_hw.h3.1 spi总线抽象API实现
第一步:“spi_send_then_recv”,实现代码如下:int spi_send_then_recv(struct spi_dev_device *spi_dev,const void *send_buff,unsigned short send_size,void *recv_buff,unsigned short recv_size)
{
struct spi_dev_message message;
message.length = send_size;
message.send_buf = send_buff;
message.recv_buf = 0;
message.cs_take = 1;
message.cs_release = 0;
spi_dev->spi_bus->spi_bus_xfer(spi_dev,