赞
踩
SPI, Serial Perripheral Interface, 串行外围设备接口, 是 Motorola 公司推出的一种同步串行接口技术. SPI 总线在物理上是通过接在外围设备微控制器(PICmicro) 上面的微处理控制单元 (MCU) 上叫作同步串行端口(Synchronous Serial Port) 的模块(Module)来实现的, 它允许 MCU 以全双工的同步串行方式, 与各种外围设备进行高速数据通信.
优点:支持全双工通信、通信简单、数据传输速率快
缺点:没有指定的流控制,没有应答机制确认是否接收到数据,所以跟 IIC 总线协议比较在数据可靠性上有一定的缺陷。
采用主-从模式(Master-Slave) 的控制方式
SPI 规定了两个 SPI 设备之间通信必须由主设备 (Master) 来控制次设备 (Slave). 一个 Master 设备可以通过提供 Clock 以及对 Slave 设备进行片选 (Slave Select) 来控制多个 Slave 设备, SPI 协议还规定 Slave 设备的Clock 由 Master 设备通过 SCK 管脚提供给 Slave 设备, Slave 设备本身不能产生或控制 Clock, 没有 Clock 则 Slave 设备不能正常工作.
采用同步方式(Synchronous)传输数据
Master 设备会根据将要交换的数据来产生相应的时钟脉冲(Clock Pulse), 时钟脉冲组成了时钟信号(Clock Signal) , 时钟信号通过时钟极性 (CPOL) 和 时钟相位 (CPHA) 控制着两个 SPI 设备间何时数据交换以及何时对接收到的数据进行采样, 来保证数据在两个设备之间是同步传输的.
数据交换(Data Exchanges)
SPI 设备间的数据传输之所以又被称为数据交换, 是因为 SPI 协议规定一个 SPI 设备不能在数据通信过程中仅仅只充当一个 “发送者(Transmitter)” 或者 “接收者(Receiver)”.在每个 Clock 周期内, SPI 设备都会发送并接收一个 bit 大小的数据, 相当于该设备有一个 bit 大小的数据被交换了.
(1)SPI 的通信原理很简单,它以主从方式工作,这种模式通常有一个主设备和一个或多个从设备,需要至少 4 根线,事实上 3 根也可以(单向传输时)。也是所有基于 SPI 的设备共有的,它们是 SDI(数据输入)、SDO(数据输出)、SCLK(时钟)、CS(片选)。
(2)SDO/MOSI – 主设备数据输出,从设备数据输入;
(3)SDI/MISO – 主设备数据输入,从设备数据输出;
(4)SCLK – 时钟信号,由主设备产生;
(5)CS/SS – 从设备使能信号,由主设备控制。当有多个从设备的时候,因为每个从设备上都有一个片选引脚接入到主设备机中,当我们的主设备和某个从设备通信时将需要将从设备对应的片选引脚电平拉低或者是拉高。
需要说明的是,SPI 通信有 4 种不同的模式,不同的从设备可能在出厂时就配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的主设备的 SPI 模式进行配置,通过 CPOL(时钟极性)和 CPHA(时钟相位)来控制我们主设备的通信模式,具体如下:
Mode0:CPOL=0,CPHA=0
Mode1:CPOL=0,CPHA=1
Mode2:CPOL=1,CPHA=0
Mode3:CPOL=1,CPHA=1
时钟极性 CPOL 是用来配置 SCLK 的电平出于哪种状态时是空闲态或者有效态,时钟相位 CPHA 是用来配置数据采样是在第几个边沿:
CPOL=0,表示当 SCLK=0 时处于空闲态,所以有效状态就是 SCLK 处于高电平时
CPOL=1,表示当 SCLK=1 时处于空闲态,所以有效状态就是 SCLK 处于低电平时
CPHA=0,表示数据采样是在第 1 个边沿,数据发送在第 2 个边沿
CPHA=1,表示数据采样是在第 2 个边沿,数据发送在第 1 个边沿
例如:
CPOL=0,CPHA=0:此时空闲态时,SCLK 处于低电平,数据采样是在第 1 个边沿,也就是 SCLK 由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。
CPOL=0,CPHA=1:此时空闲态时,SCLK 处于低电平,数据发送是在第 1 个边沿,也就是 SCLK 由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=0:此时空闲态时,SCLK 处于高电平,数据采集是在第 1 个边沿,也就是 SCLK 由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=1:此时空闲态时,SCLK 处于高电平,数据发送是在第 1 个边沿,也就是 SCLK 由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
需要注意的是:我们的主设备能够控制时钟,因为 SPI 通信并不像 UART 或者 IIC 通信那样有专门的通信周期,有专门的通信起始信号,有专门的通信结束信号;所以 SPI 协议能够通过控制时钟信号线,当没有数据交流的时候我们的时钟线要么是保持高电平要么是保持低电平。
1、spi_master:对 SoC 的 SPI 控制器的抽象
2、spi_bus_type:spi 的 bus_type,代表了硬件上的 SPI Bus
3、spi_device:spi 从设备
4、spi_driver:spi 具体设备的驱动
首先需要明确的一点是,SPI 主机控制器部分是整个 SPI 系统的核心存在,它并不属于 SPI 下的 bus、device、drvier 这一组结构,因为他并不是挂接到 bus 上的 device,更不是对应挂接在 bus 上 device 的 driver,而是相对独立的一个存在,所以 SPI 控制器部分,是连接到 platform 下的,并执行 platform 的 probe
对于SPI的大框架分为两层
控制器驱动层 spi_master ,主要提供transfer函数,进行spi协议的收发。spi_master 也是基于 Platform 模型的,注册 spi_master 时会扫描一个链表进行注册设备
设备驱动层,基于 spi_bus_type,在driver里则使用spi_read、spi_writer 等函数,最终也会调用到 master->transfer 函数进行发送接收。
1、spi_master
定义在 spi.h 中,下面列举几个关键成员,完整结构可查看源码
struct spi_master { struct device dev; struct list_head list;//链接到全局的 spi_master list s16 bus_num; // 总线编号,如果是负数,则是动态分配,整数指定用哪个spi控制器/驱动(从0开始) u16 num_chipselect;//片选数量,决定该控制器下面挂接多少个SPI设备,从设备的片选号不能大于这个数量 u16 mode_bits; // 控制器启动的模式位,被控制器驱动解析 u32 min_speed_hz; //最小速率 u32 max_speed_hz; //最大速率 int (*setup)(struct spi_device *spi); // 时钟和spi模式的设置函数 /*添加消息到队列的方法。这个函数不可睡眠。它的职责是安排发生的传送并且调用注册的回调函 complete()。这个不同的控制器要具体实现,传输数据最后都要调用这个函数*/ int (*transfer)(struct spi_device *spi, struct spi_message *mesg); /* 在spidev_release函数中被调用,spidev_release被登记为spi dev的release函数 */ void (*cleanup)(struct spi_device *spi); struct list_head queue; // spi_message队列头 struct spi_message *cur_msg; // 正在处理的spi_message };
2、spi_device
spi_device 用于描述一个挂接到 SPI 总线上的一个设备
struct spi_device {
struct device dev;
struct spi_master *master;//这个 spi device 挂在那个 SPI master 下
u32 max_speed_hz; //通讯时钟最大频率
u8 chip_select; //片选号,每个 master 支持多个 spi_device
u8 bits_per_word; //每个通信字的字长的比特数,默认是 8
u16 mode; //SPI device 的模式,时钟极性和时钟相位
#define SPI_CPHA 0x01 /*时钟相位*/
#define SPI_CPOL 0x02 /*时钟极性*/
#define SPI_MODE_0 (0|0)
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
char modalias[SPI_NAME_SIZE];//需要绑定的驱动的名字
};
3、spi_driver
spi_driver 代表一个驱动,他也挂接到了 spi bus 上,与对应的 spi_device 结构进行匹配后调用 probe
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
4、spi_transfer
spi_transfer 代表一个读写缓冲对,包含接收缓冲区及发送缓冲区,其实,spi_transfer的发送是通过构建spi_message实现,通过将spi_transfer中的链表transfer_list链接到spi_message中的transfers,再以spi_message形势向底层发送数据。
struct spi_transfer {
const void *tx_buf;//发送缓冲区,要写入设备的数据(必须是dma_safe),或者为NULL
void *rx_buf;//接收缓冲区,要读取的数据缓冲(必须是dma_safe),或者为NULL
unsigned len;//缓冲区长度,tx和rx的大小(字节数)
//这里不是指它们的和,而是各自的长度,它们总是相等的
dma_addr_t tx_dma;//如果spi_message.is_dma_mapped是真,这个是tx的dma地址
dma_addr_t rx_dma;//如果spi_message.is_dma_mapped是真,这个是rx的dma地址
unsigned cs_change:1;//当前spi_transfer发送完成之后重新片选。影响此次传输之后的片选。指示本次transfer结束之后是否要重新片选并调用setup改变设置。这个标志可以减少系统开销
u8 bits_per_word;//每个字长的比特数,0代表使用spi_device中的默认值 8
u16 delay_usecs;//发送完成一个spi_transfer后延时时间,此次传输结束和片选改变之间的延时,之后就会启动另一个传输或者结束整个消息
u32 speed_hz;//通信时钟。如果是0,使用默认值
u16 word_delay;
struct list_head transfer_list;//用于链接到spi_message,用来连接的双向链接节点
};
5、spi_message
spi_message 代表 spi 消息,由多个spi_transfer段组成。
spi_message用来原子的执行spi_transfer表示的一串数组传输请求。
这个传输队列是原子的,这意味着在这个消息完成之前不会有其它消息占用总线。
消息的执行总是按照FIFO的顺序。向底层提交spi_message的代码要负责管理它的内存空间。未显示初始化的内存需要使用0来初始化。
struct spi_message { struct list_head transfers;//这个 mesage 含的 transfer 链表 struct spi_device *spi;//传输的目标设备 unsigned is_dma_mapped:1;//spi_transfer 中 tx_dma 和 rx_dma 是否已经 mapped void (*complete)(void *context);//数据传输完成的回调函数 void *context;//提供给complete的可选参数 unsigned frame_length; unsigned actual_length;//spi_message已经传输了的字节数 int status;//出错与否,错误时返回 errorcode struct list_head queue; void *state; /* list of spi_res reources when the spi message is processed */ struct list_head resources; }; //spi_message: 描述一次完整的传输,即cs信号从高——>低——>高的传输 //spi_transfer: 多个 spi_transfer 构成一个 spi_message
这几个结构在应用中的关系如下:
1、SPI 驱动注册与注销
int spi_register_driver(struct spi_driver *sdrv) //注册 SPI 的驱动端
static inline void spi_unregister_driver(struct spi_driver *sdrv) //注销 SPI 驱动端
2、注册 SPI 主控制器
int spi_register_master(struct spi_master *master) //注册 SPI 控制器
void spi_unregister_master(struct spi_master *master) //注销 SPI 控制器
3、设置 SPI 驱动私有数据
static inline void spi_set_drvdata(struct spi_device *spi, void *data) //设置私有数据,将 data 指针赋值给 spi 的 dev->p->driver_data 成员
static inline void *spi_get_drvdata(struct spi_device *spi) //获取设置的私有数据
4、初始化 SPI 信息结构
static inline void spi_message_init(struct spi_message *m)
5、添加信息结构
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)//将 struct spi_message m 结构添加到 struct spi_transfer 结构的 transfer_list 成员中
6、SPI 异步传输数据
//struct spi_device *spi :交换数据的 SPI 设备
//struct spi_message *message :传输的信息结构
int spi_async(struct spi_device *spi, struct spi_message *message)
7、SPI 同步传输
int spi_sync(struct spi_device *spi, struct spi_message *message)
//该函数传输数据的过程中会进行阻塞
8、等待传输完成
void __sched wait_for_completion(struct completion *x)
//该函数在 SPI 发送的时候有调用到,等待 SPi 的数据发送完成
1、在 spi.c 中定义了总线类型,调用了总线注册函数,注册了总线
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
status = bus_register(&spi_bus_type);
2、在 spidev.c 和 dts 文件中完成了驱动和设备的注册匹配,spidev.c文件中还实现了 在/dev下创建设备文件,定义文件操作集合,read(),write(),和 ioctl()等接口,以及上文提到的常用 API 函数。
spidev.c 文件分析
在文件的开头声明了一个32位的bitmap和一个链表头:
#define SPIDEV_MAJOR 153 /* assigned */
#define N_SPI_MINORS 32 /* ... up to 256 */
static DECLARE_BITMAP(minors, N_SPI_MINORS); /* 声明了一个含32位的bitmap,用于做为spi设备的索引 */
static LIST_HEAD(device_list); /* 声明一个链表的头 */
static DEFINE_MUTEX(device_list_lock); /* 实现对链表原子操作的互斥锁 */
模块的初始化:
static int __init spidev_init(void)
{
int status;
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); /* 注册主设备号是 153 的字符设备,同时注册上对字符设备的操作的函数 */
spidev_class = class_create(THIS_MODULE, "spidev"); /* 创建一个名字是 spidev 的类,为mdev/udev在 /dev 下创建节点做准备 */
status = spi_register_driver(&spidev_spi_driver); /* 注册 驱动程序 */
return status;
}
设备匹配成功调用probe
static int __devinit spidev_probe(struct spi_device *spi) { struct spidev_data *spidev; int status; unsigned long minor; /* * spidev should never be referenced in DT without a specific * compatible string, it is a Linux implementation thing * rather than a description of the hardware. */ if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) { dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n"); WARN_ON(spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)); } spidev_probe_acpi(spi); spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); /* 申请并初始化一个 spidev_data 的区域 */ spidev->spi = spi; /* 将spidev_data结构体中的spi指针指向匹配好的spi设备 */ spin_lock_init(&spidev->spi_lock); mutex_init(&spidev->buf_lock); INIT_LIST_HEAD(&spidev->device_entry); /* If we can allocate a minor number, hook up this device. * Reusing minors is fine so long as udev or mdev is working. */ mutex_lock(&device_list_lock);/* 得到互斥锁 */ minor = find_first_zero_bit(minors, N_SPI_MINORS); /* 在0-31中找到一个没有被使用的作为次设备号 */ if (minor < N_SPI_MINORS) { struct device *dev; spidev->devt = MKDEV(SPIDEV_MAJOR, minor); /* 赋值spidev中的设备号 */ dev = device_create(spidev_class, &spi->dev, spidev->devt, /* 创建设备节点,名字是spidevA.B,A是设备结构体中的master.bus_num,B是设备中的chipselect */ spidev, "spidev%d.%d", /* A是设备结构体中的master.bus_num,B是设备中的chipselect */ spi->master->bus_num, spi->chip_select); status = IS_ERR(dev) ? PTR_ERR(dev) : 0; } else { dev_dbg(&spi->dev, "no minor number available!\n"); status = -ENODEV; } if (status == 0) { set_bit(minor, minors); /* 将使用了0-31中使用了作为次设备号的位置1 */ list_add(&spidev->device_entry, &device_list); /* 将组装好的spidev_data结构体添加到链表中 */ } mutex_unlock(&device_list_lock); /* 解锁 */ if (status == 0) spi_set_drvdata(spi, spidev); /* 将spi.dev.p指向这个组装好的spi_data结构体,方便整个文件中其他函数的使用 */ else kfree(spidev); return status; }
5、GPIO 模拟 SPI
spi bitbang是软件模拟的spi主设备驱动。当硬件上没有spi总线主控设备时,可以在软件上模拟spi主控设备,通过gpio信号线同从设备通信。spi bitbang依赖于gpio模拟spi的四根信号线,所以bitbang相关的代码依赖spi-gpio驱动
1、内核支持
Device Drivers --->
[*] SPI support --->
<*> GPIO-based bitbanging SPI Master
2、spi_device
在dtsi文件中添加设备信息
spi-gpio { compatible = "spi-gpio"; #address-cells = <0x1>; #size-cells = <0>; status = "okay"; gpio-sck = <&gpio_chip3 4 1>; gpio-miso = <&gpio_chip3 6 0>; gpio-mosi = <&gpio_chip3 5 0>; cs-gpios = <&gpio_chip3 7 1>; num-chipselects = <1>; /* clients */ spidev { compatible = "spidev"; #address-cells = <0x1>; #size-cells = <1>; reg = <0>; spi-max-frequency = <5000000>; }; };
3、/drivers/spi 下的 spi-gpio.c
static const struct of_device_id spi_gpio_dt_ids[] = {
{ .compatible = "spi-gpio" },
{}
};
static struct platform_driver spi_gpio_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = of_match_ptr(spi_gpio_dt_ids),
},
.probe = spi_gpio_probe,
.remove = spi_gpio_remove,
};
//匹配成功调用 probe 函数
static int spi_gpio_probe(struct platform_device *pdev) { int status; struct spi_master *master; struct spi_gpio *spi_gpio; struct spi_gpio_platform_data *pdata; u16 master_flags = 0; bool use_of = 0; int num_devices; status = spi_gpio_probe_dt(pdev); // 从设备树配置中获取spi相关配置信息: MOSI,MISO,SCLK等gpio信息 if (status < 0){ return status; } if (status > 0) use_of = 1; pdata = dev_get_platdata(&pdev->dev); #ifdef GENERIC_BITBANG if (!pdata || (!use_of && !pdata->num_chipselect)) return -ENODEV; #endif if (use_of && !SPI_N_CHIPSEL) num_devices = 1; else num_devices = SPI_N_CHIPSEL; // 申请MOSI,MISO,SCLK等gpio信息 status = spi_gpio_request(pdata, dev_name(&pdev->dev), &master_flags); if (status < 0) return status; // 分配spi_master结构对象 master = spi_alloc_master(&pdev->dev, sizeof(*spi_gpio) + (sizeof(unsigned long) * num_devices)); if (!master) { status = -ENOMEM; goto gpio_free; } spi_gpio = spi_master_get_devdata(master); platform_set_drvdata(pdev, spi_gpio); spi_gpio->pdev = pdev; if (pdata) spi_gpio->pdata = *pdata; master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); master->flags = master_flags; master->bus_num = pdev->id; master->num_chipselect = num_devices; // 设置setup和cleanup方法 master->setup = spi_gpio_setup; master->cleanup = spi_gpio_cleanup; #ifdef CONFIG_OF master->dev.of_node = pdev->dev.of_node; if (use_of) { int i; struct device_node *np = pdev->dev.of_node; /* * In DT environments, take the CS GPIO from the "cs-gpios" * property of the node. */ if (!SPI_N_CHIPSEL) spi_gpio->cs_gpios[0] = SPI_GPIO_NO_CHIPSELECT; else for (i = 0; i < SPI_N_CHIPSEL; i++) { status = of_get_named_gpio(np, "cs-gpios", i); if (status < 0) { dev_err(&pdev->dev, "invalid cs-gpios property\n"); goto gpio_free; } spi_gpio->cs_gpios[i] = status; } } #endif spi_gpio->bitbang.master = master; spi_gpio->bitbang.chipselect = spi_gpio_chipselect; // 初始化4种时钟模式下的传输方法,该函数接口在spi-bitbang.c中定义 if ((master_flags & (SPI_MASTER_NO_TX | SPI_MASTER_NO_RX)) == 0) { spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_txrx_word_mode0; spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_txrx_word_mode1; spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_txrx_word_mode2; spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_txrx_word_mode3; } else { spi_gpio->bitbang.txrx_word[SPI_MODE_0] = spi_gpio_spec_txrx_word_mode0; spi_gpio->bitbang.txrx_word[SPI_MODE_1] = spi_gpio_spec_txrx_word_mode1; spi_gpio->bitbang.txrx_word[SPI_MODE_2] = spi_gpio_spec_txrx_word_mode2; spi_gpio->bitbang.txrx_word[SPI_MODE_3] = spi_gpio_spec_txrx_word_mode3; } //设置bitbang的setup_transfer函数 spi_gpio->bitbang.setup_transfer = spi_bitbang_setup_transfer; spi_gpio->bitbang.flags = SPI_CS_HIGH; // 片选信号,高电平有效 status = spi_bitbang_start(&spi_gpio->bitbang); // 启动bitbang,并注册master if (status < 0) { gpio_free: if (SPI_MISO_GPIO != SPI_GPIO_NO_MISO) gpio_free(SPI_MISO_GPIO); if (SPI_MOSI_GPIO != SPI_GPIO_NO_MOSI) gpio_free(SPI_MOSI_GPIO); gpio_free(SPI_SCK_GPIO); spi_master_put(master); } return status; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。