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

【SPI】【一】SPI驱动入门

一、SPI基础

📢SPI是一种常见的设备通用通信协议。它有一个独特优势就是可以无中断传输数据,可以连续地发送或接收任意数量的位。而在I2C和UART中,数据以数据包的形式发送,有着限定位数。

1 SPI优缺点

  • 优点

    SPI 通讯无起始位和停止位,因此数据可以连续流传输而不会中断;没有像 I2C 这样的复杂的从站寻址系统,数据传输速率比 I2C 更高(几乎快两倍)。独立的 MIS0 和 MOSI 线路,可以同时发送和接收数据。

  • 缺点

    SPI 使用四根线(I2C 和 UART 使用两根线),没有信号接收成功的确认(I2C 拥有此功能),没有任何形式的错误检查(如 UART 中的奇偶校验位等)。

2 SPI通信原理

在 SPI 设备中,设备分为主机与从机系统。

  • 主机是控制设备(通常是控制器)
  • 从机(通常是传感器或存储芯片)从主机那获取指令。

一套SPI通讯共包含四种信号线:

  • MOSI(Master 0utput/slave Input) - 信号线,主机输出,从机输入。
  • MISo(Master Input/slave output) - 信号线,主机输入,从机输出。
  • SCLK(Clock) - 时钟信号:
  • Ss/cs(5lave Select/chip select) - 片选信号。
2.1 时钟信号

每个时钟周期传输一位数据,因此数据传输的速度取决于时钟信号的频率。 时钟信号由于是主机配置生成的,因此SPI 通信始终由主机启动。

设备共享时钟信号的任何通信协议都称为同步。 SPI 是一种同步通信协议,还有一些异步通信不使用时钟信号。

例如在 UART 通信中,双方都设置为预先配置的波特率,该波特率决定了数据传输的速度和时序。

2.2 片选信号

主机通过拉低从机的 CS/SS 来使能通信。 在空闲/非传输状态下,片选线保持高电平。在主机上可以存在多个CS/SS 引脚,允许主机与多个不同的从机进行通讯。

2.3 MOSI和MISO

主机通过 MOSI 以串行方式将数据发送给从机,从机也可以通过 MISO 将数据发送给主机,两者可以同时进行。所以理论上,SPI 是一种全双工的通讯协议。

传输步骤:

以传输模式:CPOL=0、CPHA =0为例:

  1. 主设备配置SPI参数
    1. 设置时钟极性 (<font style="background-color:rgb(242,243,245);">CPOL</font>): 定义 SCK 线在空闲状态(无数据传输时)的电平(高电平 <font style="background-color:rgb(242,243,245);">1</font> 或低电平 <font style="background-color:rgb(242,243,245);">0</font>)。
    2. 设置时钟相位 (<font style="background-color:rgb(242,243,245);">CPHA</font>): 定义数据在 SCK 时钟的哪个边沿(第一个边沿或第二个边沿)进行采样(锁存)。
  2. 主机输出时钟信号

  1. 主机拉低从机SS/CS 引脚,激活从机

  1. 主机通过MOSI将数据发送给从机

  1. 如果需要响应,则从机通过MISO将数据返回给主机

  1. 主设备取消选中从设备 (结束通信):
    1. 最后一个数据位传输完成后,主设备将目标从设备的片选 (<font style="background-color:rgb(242,243,245);">CS</font> / <font style="background-color:rgb(242,243,245);">SS</font>) 引脚拉高(无效)。
    2. 这个上升沿通知从设备通信结束。从设备停止驱动 MISO 线(变为高阻态)。
    3. SCK 线恢复到 <font style="background-color:rgb(242,243,245);">CPOL</font> 定义的空闲电平。
2.4 数据传输

为了开始通信,总线上的主设备需要使用从设备支持的频率来配置时钟,这个频率最高为几兆赫兹左右。然后主设备将某个从设备的SS线置为低电平,来选中这个从设备。如果等待时间是必要的话(例如进行模数转换),主设备必须在这段时间结束后,才可以发出时钟周期信号。

在每个SPI时钟周期内,都会发生全双工数据传输。主设备在MOSI线上发送一个位,从设备读取它,同时从机在MISO线上发送一位数据,主机读取它。即使只有单向数据传输的目的,主从机之间的通信工作方式仍然是双工内。

传输通常会使用给定字长的两个移位寄存器,一个在主设备中,一个在从设备中,这两个寄存器连接成一个虚拟的环形缓冲器。数据通常先从最高位移出。在时钟信号边沿,主机和从机均移出一位,然后在传输线上输出给对方。在下一个时钟沿,每个接收器都从传输线接受对方发出的数据位,并且从移位寄存器的最低位推入。每完成这样-个移出–推入的周期后,主机和从机就交换寄存器中的一位数据。当所有数据位都经过了这样的移出–推入过程后,主机和从机就完成了寄存器上的数据交换。如果需要交换的数据比寄存器的位数还要长的话,则需要重新加载移位寄存器并重复该过程。传输可能会持续任意数量的时钟周期。完成后,主设备会停止发送时钟信号,并通常会取消选择从设备。

传输寄存器通常包含8位。但是其他字长也很常见,例如触摸屏控制器或音频编解码器通常采用 16 位字长(如德州仪器的 TSC2101 ),许多数模转换或者模数转换的设备则会采用 12 位字长。所有在总线上的没有被片选线激活的从设备必须忽略输入时钟和 MOSI 信号,并且不得从 MISO 发送数据。

2.5 模式

分别是 CPOL(Clock POlarity)和 CPHA (Clock PHAse)

  • CPOL配置SPI总线的极性
  • CPHA配置SPI总线的相位

SPI总线的极性

极性,会直接影响SPI总线空闲时的时钟信号是高电平还是低电平

  • CPOL=1:表示空闲时是高电平
  • CPOL=0:表示空闲时是低电平

SPI总线的相位

一个时钟周期会有2个跳变沿。而相位,直接决定SPI总线从那个跳变沿开始采样数据。

  • CPHA =0:表示从第一个跳变沿开始采样
  • CPHA =1:表示从第二个跳变沿开始采样

4种模式

modeCPOLCPHA
mode 000
mode 101
mode 210
mode 311

模式0(CPOL=0; CPHA=0)

特性:

CPOL = 0: 空闲时是低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA = 0: 数据在第1个跳变沿(上升沿)采样

模式1(CPOL=0; CPHA=1)

特性:

CPOL =0 : 空闲时是低电平,第1个跳变沿是上升沿,第2个跳变沿是下降沿
CPHA =1 : 数据在第2个跳变沿(下降沿)采样

模式2(CPOL=1;CPHA=0)

CPOL = 1:空闲时是高电平,第1个跳变沿是下降沿,第2个跳变沿是上升沿
CPHA = 0:数据在第1个跳变沿(下降沿)采样

模式3(CPOL=1; CPHA=1)

CPOL = 1:空闲时是高电平,第1个跳变沿是下降沿,第2个跳变沿是上升沿
CPHA = 1:数据在第2个跳变沿(上升沿)采样

2.6 DSPI 和 QSPI

SPI 协议其实是包括:Standard SPI、Dual SPI 和 Queued SPI 三种协议接口。

  • Dual SPI还是四线制,只是传输线可以变为同方向,速度是 Standard SPI 的两倍。
  • Queued SPI 是六线制,多了两根数据线,传输速度是 Standard SPI的四倍,

二、SPI硬件分析

三、SPI设备树设置

main_spi0: spi@2100000 {compatible = "ti,am654-mcspi","ti,omap4-mcspi";//与驱动对应的名字reg = <0x00 0x02100000 0x00 0x400>;interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_HIGH>;#address-cells = <1>;#size-cells = <0>;power-domains = <&k3_pds 339 TI_SCI_PD_EXCLUSIVE>;clocks = <&k3_clks 339 1>;status = "disabled";};&main_spi0{status = "okay";# assigned-clock-rates = <200000000>; //默认不用配置,SPI 设备工作时钟值,io 时钟由工作时钟分频获取pinctrl-names = "default";pinctrl-0 = <&myspi0_pins_default>;dmas = <&main_udmap 0xc600>, <&main_udmap 0x4600>;dma-names = "tx0", "rx0"; //使能DMA模式,通讯长度少于32字节不建议用,置空赋值去掉使能,如"dma-names;";# rx-sample-delay-ns = <10>; //默认不用配置,读采样延时/*default: ti,pindir-d0-in-d1-out *//*ti,pindir-d0-out-d1-in; */spidev@0{compatible = "rohm,dh2228fv";reg = <0>;//0-spidev1.0,3-spidev1.3spi-max-frequency = <24000000>;//spi clk输出的时钟频率,不超过50Mspi-cpol = <0>; //1-89501,0-89541spi-cpha = <0>; //1-89501,0-89541};
};

spiclk assigned-clock-ratesspi-max-frequency 的配置说明:

  • spi-max-frequency 是 SPI 的输出时钟,由 SPI 工作时钟 spiclk assigned-clock-rates内部分频后输出,由于内部至少 2 分频,所以关系是 spiclk assigned-clock-rates >= 2*spi-max-frequency
  • 假定需要 50MHz 的 SPI IO 速率,可以考虑配置(记住内部分频为偶数分频)spi_clk assigned-clock-rates = <100000000>spi-max-frequency =<50000000>,即工作时钟 100 MHz(PLL 分频到一个不大于 100MHz 但最接近的值),然后内部二分频最终 IO 接近 50 MHz;
  • spiclk assigned-clock-rates 不要低于 24M,否则可能有问题;
  • 如果需要配置 spi-cpha 的话, 要求 spiclk assigned-clock-rates <= 6M, 1M <=spi-max-frequency >=3M

四、spi使用方法

1 spi_test 使用

在 Linux 系统上,“spidev_test” 是一个用于测试和配置 SPI(Serial Peripheral Interface)设备的命令行工具。SPI是一种串行通信协议,通常用于连接微控制器、传感器和其他外部设备。“spidev_test” 工具通常在Linux系统上使用,用于检查SPI接口的正确性以及与SPI设备的通信是否正常工作。

  • 代码位置:kernel/tools/spi
  • 使用帮助:
./spidev_test  -h
./spidev_test: invalid option -- 'h'
Usage: ./spidev_test [-2348CDFHILMNORSZbdilopsv]
general device settings:-D --device         device to use (default /dev/spidev1.1)-s --speed          max speed (Hz)-d --delay          delay (usec)-l --loop           loopback
spi mode:-H --cpha           clock phase-O --cpol           clock polarity-F --rx-cpha-flip   flip CPHA on Rx only xfer
number of wires for transmission:-2 --dual           dual transfer-4 --quad           quad transfer-8 --octal          octal transfer-3 --3wire          SI/SO signals shared-Z --3wire-hiz      high impedance turnaround
data:-i --input          input data from a file (e.g. "test.bin")-o --output         output data to a file (e.g. "results.bin")-p                  Send data (e.g. "1234\xde\xad")-S --size           transfer size-I --iter           iterations
additional parameters:-b --bpw            bits per word-L --lsb            least significant bit first-C --cs-high        chip select active high-N --no-cs          no chip select-R --ready          slave pulls low to pause-M --mosi-idle-low  leave mosi line low when idle
misc:-v --verbose        Verbose (show tx buffer)

以下是一些常见的 “spidev_test” 命令行选项和说明:

  • -D 或 --device:用于指定SPI设备的路径。通常,SPI设备的路径类似于 “/dev/spidevX.Y”,其中X是SPI控制器的编号,Y是SPI设备的编号。
  • -s 或 --speed:用于设置SPI通信的时钟速度,以Hz为单位。例如,-s 1000000 将设置SPI时钟速度为1 MHz。
  • -b 或 --bits:用于设置每个字节的位数。SPI通信通常使用8位,但可以根据需要进行配置。
  • -v 或 --verbose:用于启用详细的输出,以便查看SPI通信的细节。
  • -w 或 --write:用于发送数据到SPI设备。可以使用此选项将数据发送到设备。
  • -r 或 --read:用于从SPI设备读取数据。可以使用此选项读取来自设备的数据。
  • -p 或 --loop:用于设置循环模式。如果启用了循环模式,“spidev_test” 将持续运行,不断发送和接收数据。
  • -h 或 --help:用于显示帮助信息,列出可用的命令行选项和其说明。
spidev_test -D /dev/spidev0.0 -s 15000000 -p "bbbbbbbbbbbbbbbb" -vspidev_test -D /dev/spidev2.0 -s 15000000 -p "bbbbbbbbbbbbbbbb" -v

2 应用编程

#include <sys/ioctl.h>int ioctl(int fd, unsigned long request, ...);

当使用ioctl函数进行SPI通信时,常用的request参数主要有以下几种:

  • SPI_IOC_RD_MODE:用于读取当前SPI通信的模式设置。这个请求参数将模式信息读取到一个整数变量中,以便检查当前SPI通信的极性和相位设置。
  • SPI_IOC_WR_MODE:用于设置SPI通信的模式。您需要提供一个整数值,该值通常由两位二进制数字组成,表示SPI通信的极性和相位。
  • SPI_IOC_RD_BITS_PER_WORD:用于读取每个数据字的位数。这个请求参数将位数信息读取到一个整数变量中。
  • SPI_IOC_WR_BITS_PER_WORD:用于设置每个数据字的位数。您需要提供一个整数值,以指定要发送和接收的每个数据字的位数。
  • SPI_IOC_RD_MAX_SPEED_HZ:用于读取SPI总线的最大速度。这个请求参数将速度信息读取到一个整数变量中,以便检查当前SPI总线的最大传输速度。
  • SPI_IOC_WR_MAX_SPEED_HZ:用于设置SPI总线的最大速度。您需要提供一个整数值,以指定要使用的最大传输速度。
  • SPI_IOC_MESSAGE(N):用于执行SPI传输的读写操作。这个请求参数需要一个指向 struct spi_ioc_transfer 数组的指针,每个元素描述了一个SPI传输操作,可以执行多个操作。
  • SPI_IOC_RD_LSB_FIRST:用于读取LSB(Least Significant Bit)优先设置。这个请求参数将LSB优先信息读取到一个整数变量中。
  • SPI_IOC_WR_LSB_FIRST:用于设置LSB优先设置。您需要提供一个整数值,以指定是否要使用LSB优先模式。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>static volatile int keep_running = 1;void int_handler(int dummy)
{keep_running = 0;
}int main(int argc, char *argv[])
{int fd;int ret;uint32_t target_speed, actual_speed;uint8_t mode = SPI_MODE_0;uint8_t bits = 8;size_t packet_len;if (argc < 4) {fprintf(stderr, "Usage: %s <device> <speed> <len>\n", argv[0]);fprintf(stderr, "Example: %s /dev/spidev0.0 15000000 256\n", argv[0]);return EXIT_FAILURE;}const char *device = argv[1];target_speed = strtoul(argv[2], NULL, 10);packet_len = strtoul(argv[3], NULL, 10);if (packet_len == 0) {fprintf(stderr, "Error: len must be > 0\n");return EXIT_FAILURE;}fd = open(device, O_RDWR);if (fd < 0) {perror("can't open device");return EXIT_FAILURE;}// 设置 SPI 模式ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);if (ret == -1) {perror("can't set spi mode");close(fd);return EXIT_FAILURE;}// 设置字长ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);if (ret == -1) {perror("can't set bits per word");close(fd);return EXIT_FAILURE;}// 申请速率ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &target_speed);if (ret == -1) {perror("can't set max speed hz");close(fd);return EXIT_FAILURE;}// 读取内核实际使用的速率ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &actual_speed);if (ret == -1) {perror("can't get max speed hz");close(fd);return EXIT_FAILURE;}printf("SPI device: %s\n", device);printf("Target speed: %u Hz (%.2f MHz)\n", target_speed, target_speed / 1e6);printf("Actual speed: %u Hz (%.2f MHz)\n", actual_speed, actual_speed / 1e6);printf("Packet length: %zu bytes\n", packet_len);// -------------------// 持续发送测试数据// -------------------uint8_t *tx_buf = malloc(packet_len);uint8_t *rx_buf = malloc(packet_len);if (!tx_buf || !rx_buf) {perror("malloc failed");close(fd);return EXIT_FAILURE;}for (size_t i = 0; i < packet_len; i++) {tx_buf[i] = (uint8_t)(i & 0xFF);rx_buf[i] = 0;}struct spi_ioc_transfer tr = {.tx_buf = (unsigned long)tx_buf,.rx_buf = (unsigned long)rx_buf,.len = packet_len,.speed_hz = actual_speed,.bits_per_word = bits,.cs_change = 0,   // 每次传输结束释放 CS};signal(SIGINT, int_handler);printf("Start continuous transfer, press Ctrl+C to stop...\n");while (keep_running) {ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);if (ret < 1) {perror("can't send spi message");break;}}printf("Transfer stopped.\n");free(tx_buf);free(rx_buf);close(fd);return EXIT_SUCCESS;
}

文章转载自:

http://jwGSMVJo.twdkt.cn
http://egyhSRbN.twdkt.cn
http://cLNqyITK.twdkt.cn
http://rVBuyFf2.twdkt.cn
http://VEVz5aGx.twdkt.cn
http://SGEXA0eA.twdkt.cn
http://9z83QIww.twdkt.cn
http://za6Y8AkF.twdkt.cn
http://VLsE5XC4.twdkt.cn
http://ttqybPPo.twdkt.cn
http://KiBWphmh.twdkt.cn
http://K0M3qOIn.twdkt.cn
http://L9iQNHU6.twdkt.cn
http://W4mVoDf5.twdkt.cn
http://wqwfjazC.twdkt.cn
http://HinYIJrI.twdkt.cn
http://6hpmdoy6.twdkt.cn
http://EbPD901m.twdkt.cn
http://4RjJAr1c.twdkt.cn
http://XFu3rRfF.twdkt.cn
http://3Re9ZFtX.twdkt.cn
http://xhjePx1m.twdkt.cn
http://UNoCy2ES.twdkt.cn
http://u7BFR10G.twdkt.cn
http://6a8TpQ24.twdkt.cn
http://5FMGBulc.twdkt.cn
http://Dw8hNPsd.twdkt.cn
http://NlBhkNgM.twdkt.cn
http://lkxr8HXp.twdkt.cn
http://5ItEHI70.twdkt.cn
http://www.dtcms.com/a/382621.html

相关文章:

  • C++ `std::lock_guard` 深度解析:简约而不简单的守卫者
  • JavaScript 数组过滤方法
  • JUC(3)JMM
  • 回小县城3年了
  • 连接器上的pin针和胶芯如何快速组装?
  • String、StringBuffer 和 StringBuilder 的区别
  • 测试抽奖系统,设计测试case
  • vue的响应式原理深度解读
  • Python核心技术开发指南(061)——常用魔术方法
  • 简单概述操作系统的发展
  • 从0开始:STM32F103C8T6开发环境搭建与第一个LED闪烁
  • linux C 语言开发 (九) 进程间通讯--管道
  • LinuxC++项目开发日志——高并发内存池(5-page cache框架开发)
  • MATLAB基于组合近似模型和IPSO-GA的全焊接球阀焊接工艺参数优化研究
  • SpringSecurity的应用
  • 算法题(206):方格取数(动态规划)
  • 第十六周周报
  • [硬件电路-193]:双极型晶体管BJT与场效应晶体管FET异同
  • ID3v2的header中的扩展标头(Extended Header),其Size字段如何计算整个ID3的长度?
  • 【51单片机】【protues仿真】基于51单片机的篮球计时计分器系统
  • Linux -- 权限的理解
  • Java零基础学习Day10——面向对象高级1
  • 解析通过base64 传过来的图片
  • Redis 持久化策略
  • STM32---PWR
  • 0913刷题日记
  • Java基础面试篇(7)
  • 4-机器学习与大模型开发数学教程-第0章 预备知识-0-4 复数与指数形式(欧拉公式)
  • TA-VLA——将关节力矩反馈融入VLA中:无需外部力传感器,即可完成汽车充电器插入(且可多次自主尝试)
  • 从0到1开发一个商用 Agent(智能体),Agent零基础入门到精通!_零基础开发aiagent 用dify从0到1做智能体