SPI 子系统

Last edited
Last updated July 17, 2023
Pages
Tags

1. SPI 简介

SPI 是串行外设接口(serial peripheral interface)的缩写,是 Motoral 公司推出的一种同步串行接口技术,是一种高速的,全双工,同步的通信总线。
SPI 优点:
  • 支出全双工通信
  • 通信简单
  • 数据传输速率块
1):高速、同步、全双工、非差分、总线式
2):主从机通信模式
缺点:
  • 没有指定的流控制,没有应答机制确认是否接收到数据
  • 所以跟 IIC 总线协议比较在数据的可靠性上有一定缺陷

2. SPI 时序解析

可以一主机多从机,具体和哪个从机通信通过 cs 片选决定
  • MISO:主机输入,从机输出
  • MOSI:主机输出,从机输入
  • CLK:时钟线(只能主机控制)
  • CS:片选线(根据是否低电平来确定是否选用这个)
数据传输的四种方式:
CPOL(时钟极性):
  • 0:时钟其实拉低电平
  • 1:时钟起始为高电平
CPHA(时钟相位):
  • 0:第一个时钟周期采样
  • 1:第二个时钟周期采样
notion image
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由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。

3. SPI 的驱动框架

3.1 SPI 层次划分

 

3.2 配置核心层和总线驱动层源码参与内核编译

  1. 进入内核顶层目录make menuconfig
  1. Device Drivers > [*]SPI support >
    1. <> STMicroelectronics STM32 SPI controller (M->Y)
  1. make uImage LOADADDR=0XC2000000
  1. cp arch/arm/boot/uImage ~/tftpboot
  1. 重启开发板

4. SPI 设备驱动

1. 分配设备驱动对象
struct spi_driver { const struct spi_device_id *id_table; int (*probe)(struct spi_device *spi); int (*remove)(struct spi_device *spi); struct device_driver driver; };
2. 注册对象
spi_register_driver(struct spi_driver *driver)
3. 注销
void spi_unregister_driver(struct spi_driver *driver)
4. 一键注册宏
#define module_spi_driver(__spi_driver) \ module_driver(__spi_driver, spi_register_driver, \ spi_unregister_driver) #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);

4.2 实例

5. SPI 设备树

5.1 数码管的硬件连接图

notion image

5.2 spi4 的设备树节点

文件 stm32mp151.dtsi
spi4: spi@44005000 { #address-cells = <1>; #size-cells = <0>; compatible = "st,stm32h7-spi"; reg = <0x44005000 0x400>; interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>; clocks = <&rcc SPI4_K>; resets = <&rcc SPI4_R>; dmas = <&dmamux1 83 0x400 0x01>, <&dmamux1 84 0x400 0x01>; dma-names = "rx", "tx"; power-domains = <&pd_core>; status = "disabled"; };

5.3 补充 spi4 设备树节点以及添加 m74hc595 子节点

查看帮助文档 Documentation/devicetree/bindings/spi
"#address-cells": enum: [0, 1] "#size-cells": const: 0 cs-gpios: description: | GPIOs used as chip selects. If that property is used, the number of chip selects will be increased automatically with max(cs-gpios, hardware chip selects). So if, for example, the controller has 4 CS lines, and the cs-gpios looks like this cs-gpios = <&gpio1 0 0>, <0>, <&gpio1 1 0>, <&gpio1 2 0>;
编辑 stm32mp157a-fsmp1a.dts 添加:
&spi4{ pinctrl-names = "default", "sleep"; pinctrl-0 = <&spi4_pins_b>; pinctrl-1 = <&spi4_sleep_pins_b>; cs-gpios=<&gpioe 11 0>; status = "okay"; m74hc595@0 { compatible = "hqyj,m74hc595"; spi-max-frequency = <10000000>; reg = <0>; }; };

6. SPI 数据读写相关 API

6.1 struct spi_device

只要设备和驱动匹配成功,在内核中就会存在一个 spi_device 结构体存放匹配成功的设备信息
struct spi_device { struct device dev;//设备信息存放的位置 struct spi_controller *controller;//我们使用的控制器的上一级节点 struct spi_controller *master; //指向我们使用的控制器 u32 max_speed_hz;//SPI最大传输速率 u8 chip_select;//当前从机使用的片选引脚编号索引 u8 bits_per_word;//传输的每一个字需要的比特数 bool rt;//读写位 u32 mode;//工作模式,包括时钟格式、片选信号等 void *controller_state;//控制器状态 char modalias[SPI_NAME_SIZE];//存放设备的名字 };

6.2 SPI 数据收发 API

int spi_read(struct spi_device *spi, void *buf, size_t len) 功能:从指定的从机读取 参数: spi:当前从机对应的设备结构指针 buf:存放数据的buf len:期望读取的数据长度 返回值:成功返回0,失败返回错误码 int spi_write(struct spi_device *spi, const void *buf, size_t len) 功能:向指定的从机写入 参数: spi:当前从机对应的设备结构指针 buf:存放数据的buf len:期望写入的数据长度 返回值:成功返回0,失败返回错误码 int spi_write_then_read(struct spi_device *spi, const void *txbuf, unsigned n_tx, void *rxbuf, unsigned n_rx);

7. 数码管显示实例

关于如何显示多个数字?
修改 buf 的第一个参数为 2 就能点亮第二个显像管,如果用线程去写,直接在一个线程里开循环就行(需要交给中断底半部);如果用异步的方法去写,指示当前位置的数字会被抢占,目前还没有修复。