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

Linux: SPI应用编程

目录

  • 一、基础知识
    • SPI 设备文件:
    • SPI 控制结构:
    • SPI 系统调用:
    • SPI 参数配置:
      • 1. 设置 SPI 模式
      • 2. 设置 SPI 位宽
      • 3. 设置 SPI 速度
      • 4. 进行 SPI 数据传输
      • 5. ioctl支持的方法汇总
  • 二、示例程序
    • 示例1:同时读写
    • 示例2:先写后读
    • 示例3:先读后写
    • 示例4:内核源码提供的demo
  • 三、知识补充
    • “双线”、“四线”和“三线”
    • 8位、16位、32位
    • 8位、16位等位数与硬件的关系

  在 Linux 中,SPI(Serial Peripheral Interface)是一种串行通信协议,用于在主设备(如 CPU)和一个或多个从设备(如传感器、存储器等)之间传输数据。SPI 通信通常通过四个基本信号线进行:时钟(SCLK)、主输出从输入(MOSI)、主输入从输出(MISO)和片选(CS)。

  • SCLK(Serial Clock):由主设备生成的时钟信号。
  • MOSI(Master Out Slave In):主设备发送数据到从设备。
  • MISO(Master In Slave Out):从设备发送数据到主设备。
  • CS(Chip Select):选择具体的从设备。主设备通过拉低某个从设备的 CS 线来激活对应的从设备。

一、基础知识

SPI 设备文件:

  • 在 Linux 中,SPI 设备通常表现为 /dev/spidevX.Y 形式的设备文件。
  • X 表示 SPI 主机控制器的编号。
  • Y 表示SPI 总线上设备的编号。
  • 可以通过以下命令检查 /dev/spidev 设备:
ls /dev/spidev*

SPI 控制结构:

  • 使用 struct spi_ioc_transfer 结构体来描述一次 SPI 传输操作。
  • 这个结构体定义了 SPI传输的数据方向、长度和速度等参数。
/*** struct spi_ioc_transfer - 描述单个 SPI 传输* @tx_buf: 持有指向用户空间缓冲区的指针,用于传输数据,或者为 null。*          如果没有提供数据,则发送零值。* @rx_buf: 持有指向用户空间缓冲区的指针,用于接收数据,或者为 null。* @len: 发送和接收缓冲区的长度,以字节为单位。* @speed_hz: 临时覆盖设备的比特率。* @bits_per_word: 临时覆盖设备的字长。* @delay_usecs: 如果非零,则在最后一位传输后等待的时间(微秒),然后可选择在下一次传输前取消选* 择设备。* @cs_change: 如果为真,则在开始下一次传输之前取消选择设备。* @word_delay_usecs: 如果非零,单次传输中每个字之间的等待时间(微秒)。此属性需要 SPI 控制器* 显式支持,否则将被静默忽略。** 这个结构体直接映射到内核的 spi_transfer 结构体;* 字段具有相同的含义,只不过指针位于不同的地址空间(在某些情况下,例如 32 位 i386 用户空间与 * 64 位 x86_64 内核,可能会有不同的大小)。* 零初始化结构体,包括当前未使用的字段,以适应潜在的未来更新。** SPI_IOC_MESSAGE 使用户空间能够执行类似于内核 spi_sync() 的操作。* 传递一个相关传输的数组,它们将一起执行。* 每个传输可以是半双工(单向)或全双工(双向)。** 例如:*	struct spi_ioc_transfer mesg[4];*	...*	status = ioctl(fd, SPI_IOC_MESSAGE(4), mesg);** 举例来说,一个传输可以发送一个九位命令(在 16 位字中右对齐),下一个传输可以读取一个 8 位数据块,* 然后通过临时取消选择芯片来结束该命令;下一个传输可以发送另一个九位命令(重新选择芯片),最后一个* 传输可能会写入一些寄存器值。*/
struct spi_ioc_transfer {// 用户空间发送缓冲区指针,用于存储发送数据,若为null,则发送0__u64		tx_buf;// 用户空间接收缓冲区指针,用于存储接收数据,若为null,则不接收数据__u64		rx_buf;// 发送和接收缓冲区的长度(以字节为单位)__u32		len;// 此次传输临时覆盖设备的比特率__u32		speed_hz;// 最后一位传输后延迟的时间(微秒),在下一个传输前可选地取消选择设备__u16		delay_usecs;// 此次传输临时覆盖设备的字长__u8		bits_per_word;// 在开始下一个传输前取消选择设备__u8		cs_change;// 发送数据时每个字的位数__u8		tx_nbits;// 接收数据时每个字的位数__u8		rx_nbits;// 在同一个传输中各字之间延迟的时间(微秒),需要SPI控制器显式支持,否则会被忽略__u8		word_delay_usecs;// 填充字段,保持结构体对齐__u8		pad;/** 如果结构体spi_ioc_transfer的内容发生不兼容的更改,* 则ioctl编号(当前为0)必须更改;* 具有固定大小字段的ioctl会进行更多的错误检查,* 而像这样字段大小可变的ioctl则不会。** 注意:该结构体在64位和32位用户空间中的布局相同。*/
};

  “临时覆盖设备”指的是在单次 SPI 传输过程中,暂时性地改变 SPI 设备的一些配置参数,而不是永久性地修改设备的基本设置。具体来说:

  • speed_hz:临时覆盖设备的比特率(频率)。这意味着在本次传输过程中,SPI 通信将使用指定的频率,而不是设备默认或之前设置的频率。
  • bits_per_word:临时覆盖设备的字长。这意味着在本次传输过程中,SPI 通信将使用指定的位宽,而不是设备默认或之前设置的位宽。

  这些参数允许在不同的 SPI 传输中灵活调整通信参数,而不需要频繁地修改设备的基本配置。这样做可以提高效率,并且更好地适应不同类型的 SPI 传输需求。

  delay_usecs 字段表示在最后一次位传输之后的延迟时间(以微秒为单位)。这个延迟时间是在下一个传输开始之前的一个可选等待时间。具体来说:

  1. 延迟时间

    • delay_usecs 指定了在最后一个位传输完成后,SPI 控制器需要等待的时间(以微秒为单位)。
    • 如果 delay_usecs 为零,则没有额外的等待时间。
    • 如果 delay_usecs 不为零,则 SPI 控制器会在最后一个位传输完成后等待指定的时间。
  2. 取消选择设备

    • cs_change 字段用于控制是否在下一个传输开始前取消选择设备(即释放片选信号)。
    • 如果 cs_change 为真(1),则在延迟时间结束后,SPI 控制器会取消选择设备。
    • 如果 cs_change 为假(0),则不会取消选择设备。

示例场景:

假设我们有以下传输序列:

  1. 第一次传输:

    • 发送命令字。
    • 接收响应字。
    • 设置 delay_usecs 为 100 微秒。
    • 设置 cs_change 为 1。
  2. 第二次传输:

    • 发送新的命令字。
    • 接收新的响应字。
    • 设置 delay_usecs 为 0 微秒。
    • 设置 cs_change 为 0。
第一次传输过程:1. 发送命令字。
2. 接收响应字。
3. 等待 100 微秒。
4. 取消选择设备(释放片选信号)。第二次传输过程:1. 重新选择设备(激活片选信号)。
2. 发送新的命令字。
3. 接收新的响应字。
4. 不取消选择设备(保持片选信号激活状态)。

  通过这种方式,可以在不同的传输之间引入必要的延迟,并根据需要选择或取消选择设备,从而实现更复杂的 SPI 通信逻辑。

SPI 系统调用:

  使用标准的文件 I/O 操作(如 open(), read(), write()ioctl())来控制 SPI 设备。
特别地,ioctl() 常用于设置 SPI 参数和发起数据传输。

SPI 参数配置:

  可以通过 ioctl() 调用来设置 SPI 速度、模式等参数。部分示例如下:

1. 设置 SPI 模式

SPI 模式(Mode)定义了 SPI 设备如何同步数据传输。共有四种模式:

  • Mode 0: CPOL=0, CPHA=0
  • Mode 1: CPOL=0, CPHA=1
  • Mode 2: CPOL=1, CPHA=0
  • Mode 3: CPOL=1, CPHA=1

使用 SPI_IOC_WR_MODE 来设置 SPI 模式,使用 SPI_IOC_RD_MODE 来读取当前模式。

#include <linux/spi/spidev.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>int fd = open("/dev/spidev0.0", O_RDWR);
if (fd < 0) {perror("Failed to open SPI device");return -1;
}// 设置 SPI 模式
uint8_t mode = SPI_MODE_0;
if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0) {perror("Failed to set SPI mode");return -1;
}// 读取 SPI 模式
if (ioctl(fd, SPI_IOC_RD_MODE, &mode) < 0) {perror("Failed to get SPI mode");return -1;
}

2. 设置 SPI 位宽

  SPI 位宽(Bits per Word)定义了每次传输的数据位数。使用 SPI_IOC_WR_BITS_PER_WORD 来设置位宽,使用 SPI_IOC_RD_BITS_PER_WORD 来读取当前位宽。

// 设置 SPI 位宽
uint8_t bits = 8;
if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {perror("Failed to set bits per word");return -1;
}// 读取 SPI 位宽
if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0) {perror("Failed to get bits per word");return -1;
}

3. 设置 SPI 速度

   SPI 速度(Speed)定义了数据传输的速率。使用 SPI_IOC_WR_MAX_SPEED_HZ 来设置速度,使用 SPI_IOC_RD_MAX_SPEED_HZ 来读取当前速度。

// 设置 SPI 速度
uint32_t speed = 500000;
if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {perror("Failed to set max speed Hz");return -1;
}// 读取 SPI 速度
if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) {perror("Failed to get max speed Hz");return -1;
}

4. 进行 SPI 数据传输

  数据传输是通过 spi_ioc_transfer 结构体完成的。你可以使用 SPI_IOC_MESSAGE(n) 来发送和接收数据,其中 n 是传输的消息数量。

#include <linux/spi/spidev.h>struct spi_ioc_transfer transfer = {.tx_buf = (uintptr_t)tx_buf,.rx_buf = (uintptr_t)rx_buf,.len = sizeof(tx_buf),.speed_hz = speed,.bits_per_word = bits,.delay_usecs = 0,
};if (ioctl(fd, SPI_IOC_MESSAGE(1), &transfer) < 0) {perror("Failed to transfer SPI message");return -1;
}

  当你使用 SPI_IOC_MESSAGE(1) 时,表示你将执行一个 SPI 消息传输,而 SPI_IOC_MESSAGE(2) 则表示你将连续执行两个 SPI 消息传输。

SPI_IOC_MESSAGE(1):

  • 功能:执行一个 SPI 消息传输。
  • 使用场景:当你只需要进行一次读写操作时,可以使用这个命令。
  • 示例:你已经有了一个定义好的 spi_ioc_transfer 结构体,其中包含了发送和接收缓冲区的地址、传输长度、传输速度等参数。通过 SPI_IOC_MESSAGE(1),你可以将这个结构体传递给 SPI 驱动,从而完成一次 SPI 传输。

SPI_IOC_MESSAGE(2):

  • 功能:连续执行两个 SPI 消息传输。
  • 使用场景:当你需要连续进行两次读写操作时,可以使用这个命令。这可以用于一些特殊的 SPI 通信协议,或者在需要发送多个命令/数据对时提高效率。
  • 示例:你有两个 spi_ioc_transfer 结构体,每个都定义了一个 SPI 传输。通过 SPI_IOC_MESSAGE(2),你可以将这两个结构体作为一个数组传递给 SPI 驱动,从而连续完成两次 SPI 传输。

区别与注意事项:

  • 效率:使用 SPI_IOC_MESSAGE(2) 进行连续两次传输可能比分别使用两次 SPI_IOC_MESSAGE(1) 更高效,因为它减少了与内核空间的交互次数。
  • 复杂性:使用 SPI_IOC_MESSAGE(2) 可能会稍微增加代码的复杂性,因为你需要管理两个 spi_ioc_transfer 结构体而不是一个。
  • 灵活性:虽然 SPI_IOC_MESSAGE(2) 提供了连续传输的便利,但如果你需要在两次传输之间执行其他操作(如检查状态、处理数据等),则可能需要使用两次
    SPI_IOC_MESSAGE(1)
    硬件支持:并非所有的 SPI 硬件都支持连续的多消息传输。在使用 SPI_IOC_MESSAGE(2) 之前,你需要确认你的硬件和驱动支持这种操作模式。

5. ioctl支持的方法汇总

// 定义SPI_IOC_MESSAGE宏,用于SPI设备的I/O控制操作,以发送和接收SPI协议数据
#define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)])/* 以下定义用于读取和设置SPI工作模式(SPI_MODE_0到SPI_MODE_3,共8位) */
#define SPI_IOC_RD_MODE			_IOR(SPI_IOC_MAGIC, 1, __u8) // 读取SPI工作模式
#define SPI_IOC_WR_MODE			_IOW(SPI_IOC_MAGIC, 1, __u8) // 设置SPI工作模式/* 以下定义用于读取和设置SPI的位顺序(最小有效位或最大有效位先传输) */
#define SPI_IOC_RD_LSB_FIRST		_IOR(SPI_IOC_MAGIC, 2, __u8) // 读取SPI位顺序
#define SPI_IOC_WR_LSB_FIRST		_IOW(SPI_IOC_MAGIC, 2, __u8) // 设置SPI位顺序/* 以下定义用于读取和设置SPI设备的字长(每个字的数据位数,范围1到N) */
#define SPI_IOC_RD_BITS_PER_WORD	_IOR(SPI_IOC_MAGIC, 3, __u8) // 读取SPI设备字长
#define SPI_IOC_WR_BITS_PER_WORD	_IOW(SPI_IOC_MAGIC, 3, __u8) // 设置SPI设备字长/* 以下定义用于读取和设置SPI设备的默认最大传输速率(以Hz为单位) */
#define SPI_IOC_RD_MAX_SPEED_HZ		_IOR(SPI_IOC_MAGIC, 4, __u32) // 读取SPI设备最大传输速率
#define SPI_IOC_WR_MAX_SPEED_HZ		_IOW(SPI_IOC_MAGIC, 4, __u32) // 设置SPI设备最大传输速率/* 以下定义用于读取和设置SPI模式字段的32位值,用于更复杂的配置需求 */
#define SPI_IOC_RD_MODE32		_IOR(SPI_IOC_MAGIC, 5, __u32) // 读取SPI模式字段(32位)
#define SPI_IOC_WR_MODE32		_IOW(SPI_IOC_MAGIC, 5, __u32) // 设置SPI模式字段(32位)

二、示例程序

示例1:同时读写

  使用 C 语言编写用户空间程序来与 SPI 设备通信。以下是一个简单的 SPI 通信示例程序:

#include <linux/spi/spidev.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>int main() {int fd = open("/dev/spidev0.0", O_RDWR);if (fd < 0) {perror("Failed to open SPI device");return -1;}// 设置 SPI 模式uint8_t mode = SPI_MODE_0;if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0) {perror("Failed to set SPI mode");return -1;}// 设置 SPI 位宽uint8_t bits = 8;if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {perror("Failed to set bits per word");return -1;}// 设置 SPI 速度uint32_t speed = 500000;if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {perror("Failed to set max speed Hz");return -1;}// 定义要发送和接收的数据uint8_t tx_buf[] = {0xAA, 0xBB, 0xCC}; // 要发送的数据uint8_t rx_buf[sizeof(tx_buf)] = {0};  // 接收的数据缓存// 设置 SPI 传输参数struct spi_ioc_transfer transfer = {.tx_buf = (uintptr_t)tx_buf,  // 发送缓冲区.rx_buf = (uintptr_t)rx_buf,  // 接收缓冲区.len = sizeof(tx_buf),        // 数据长度.speed_hz = speed,            // SPI 速度.bits_per_word = bits,        // 每字节位数.delay_usecs = 0,             // 延迟(微秒)};// 执行 SPI 传输if (ioctl(fd, SPI_IOC_MESSAGE(1), &transfer) < 0) {perror("Failed to transfer SPI messages");return -1;}// 输出接收到的数据printf("Received data:");for (size_t i = 0; i < sizeof(rx_buf); i++) {printf(" 0x%02X", rx_buf[i]);}printf("\n");close(fd);return 0;
}

  保存代码为 spi_example.c,然后使用以下命令编译:

gcc spi_example.c -o spi_example

  然后运行编译后的程序:

./spi_example

示例2:先写后读

#include <linux/spi/spidev.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>int main() {int fd = open("/dev/spidev0.0", O_RDWR);if (fd < 0) {perror("Failed to open SPI device");return -1;}uint8_t mode = SPI_MODE_0;if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0) {perror("Failed to set SPI mode");return -1;}uint8_t bits = 8;if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {perror("Failed to set bits per word");return -1;}uint32_t speed = 500000;if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {perror("Failed to set max speed Hz");return -1;}// 1. 写入数据uint8_t tx_buf[] = {0xAA, 0xBB, 0xCC}; // 要发送的数据struct spi_ioc_transfer transfer_write = {.tx_buf = (uintptr_t)tx_buf,.rx_buf = 0, // 不接收数据.len = sizeof(tx_buf),.speed_hz = speed,.bits_per_word = bits,.delay_usecs = 0,};if (ioctl(fd, SPI_IOC_MESSAGE(1), &transfer_write) < 0) {perror("Failed to write SPI data");return -1;}// 2. 读取数据uint8_t rx_buf[3] = {0}; // 接收缓冲区struct spi_ioc_transfer transfer_read = {.tx_buf = 0, // 不发送数据.rx_buf = (uintptr_t)rx_buf,.len = sizeof(rx_buf),.speed_hz = speed,.bits_per_word = bits,.delay_usecs = 0,};if (ioctl(fd, SPI_IOC_MESSAGE(1), &transfer_read) < 0) {perror("Failed to read SPI data");return -1;}printf("Received data:");for (size_t i = 0; i < sizeof(rx_buf); i++) {printf(" 0x%02X", rx_buf[i]);}printf("\n");close(fd);return 0;
}

示例3:先读后写

#include <linux/spi/spidev.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>int main() {int fd = open("/dev/spidev0.0", O_RDWR);if (fd < 0) {perror("Failed to open SPI device");return -1;}uint8_t mode = SPI_MODE_0;if (ioctl(fd, SPI_IOC_WR_MODE, &mode) < 0) {perror("Failed to set SPI mode");return -1;}uint8_t bits = 8;if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits) < 0) {perror("Failed to set bits per word");return -1;}uint32_t speed = 500000;if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {perror("Failed to set max speed Hz");return -1;}// 1. 读取数据uint8_t rx_buf[3] = {0}; // 接收缓冲区struct spi_ioc_transfer transfer_read = {.tx_buf = 0, // 不发送数据.rx_buf = (uintptr_t)rx_buf,.len = sizeof(rx_buf),.speed_hz = speed,.bits_per_word = bits,.delay_usecs = 0,};if (ioctl(fd, SPI_IOC_MESSAGE(1), &transfer_read) < 0) {perror("Failed to read SPI data");return -1;}printf("Received data:");for (size_t i = 0; i < sizeof(rx_buf); i++) {printf(" 0x%02X", rx_buf[i]);}printf("\n");// 2. 写入数据uint8_t tx_buf[] = {0xAA, 0xBB, 0xCC}; // 要发送的数据struct spi_ioc_transfer transfer_write = {.tx_buf = (uintptr_t)tx_buf,.rx_buf = 0, // 不接收数据.len = sizeof(tx_buf),.speed_hz = speed,.bits_per_word = bits,.delay_usecs = 0,};if (ioctl(fd, SPI_IOC_MESSAGE(1), &transfer_write) < 0) {perror("Failed to write SPI data");return -1;}close(fd);return 0;
}

示例4:内核源码提供的demo

// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>#include <linux/types.h>
#include <linux/spi/spidev.h>static int verbose;/*** 从指定文件描述符中读取数据* * 此函数的目的是读取指定数量的数据,但不超过缓冲区的大小* 它至少会尝试读取2个字节,以确保有最小量的数据用于后续处理* * @param fd 要读取的文件描述符* @param len 请求读取的字节数*/
static void do_read(int fd, int len)
{unsigned char buf[32]; // 定义一个字节缓冲区,最大存储32个字节unsigned char *bp; // 指向缓冲区的指针,用于遍历读取的数据int status; // 存储read函数的返回状态// 确保读取的字节数不超过缓冲区大小,且至少为2字节if (len < 2)len = 2;else if (len > sizeof(buf))len = sizeof(buf);// 清空缓冲区,为读取新数据做准备memset(buf, 0, sizeof buf);// 尝试从文件描述符fd中读取数据status = read(fd, buf, len);if (status < 0) {// 如果读取失败,输出错误信息并返回perror("read");return;}if (status != len) {// 如果实际读取的字节数少于预期,输出提示信息并返回fprintf(stderr, "short read\n");return;}// 打印读取到的数据,前两个字节单独打印printf("read(%2d, %2d): %02x %02x,", len, status, buf[0], buf[1]);status -= 2;bp = buf + 2;// 遍历并打印剩余的字节数据while (status-- > 0)printf(" %02x", *bp++);printf("\n");
}/*** 执行消息传输* 本函数通过 ioctl 接口实现 SPI (Serial Peripheral Interface) 设备的数据收发* * @param fd 文件描述符,标识 SPI 设备* @param len 指定本次传输的数据长度*/
static void do_msg(int fd, int len)
{// 定义结构体数组,用于描述 SPI 数据传输过程中的发送和接收操作struct spi_ioc_transfer xfer[2];// 定义缓冲区和缓冲区指针,用于数据存储和操作unsigned char buf[32], *bp;// 定义变量,用于存储 ioctl 操作的状态int status;// 初始化 xfer 和 buf,确保内存清零,避免垃圾数据影响memset(xfer, 0, sizeof xfer);memset(buf, 0, sizeof buf);// 确保传输长度不超过缓冲区大小,防止溢出if (len > sizeof buf)len = sizeof buf;//下面是一次配置两个传输的一种示例操作// 设置发送数据的起始字节,这是一个常见的 SPI 通信握手字节buf[0] = 0xaa;// 配置第一个 xfer 元素为发送操作,指定发送缓冲区和长度,没有配置rxbuf//表示只进行发送xfer[0].tx_buf = (unsigned long)buf;xfer[0].len = 1;// 配置第二个 xfer 元素为接收操作,指定接收缓冲区和长度//没有配置txbuf,表示只进行接受xfer[1].rx_buf = (unsigned long)buf;xfer[1].len = len;// 调用 ioctl 函数,执行 SPI 数据传输status = ioctl(fd, SPI_IOC_MESSAGE(2), xfer);// 检查 ioctl 操作状态,如果出错则打印错误信息并返回if (status < 0) {perror("SPI_IOC_MESSAGE");return;}// 打印接收的数据信息,包括长度和数据内容printf("response(%2d, %2d): ", len, status);for (bp = buf; len; len--)printf(" %02x", *bp++);printf("\n");
}/*** 打印SPI设备的状态信息* * @param name SPI设备的名称,用于打印输出时标识设备* @param fd SPI设备的文件描述符,用于执行ioctl操作获取设备状态* * 本函数通过文件描述符fd使用ioctl系统调用,获取并打印SPI设备的当前配置和状态信息* 包括SPI模式、每个字的数据位数、是否先发送LSB(低位在前)以及最大传输速率*/
static void dumpstat(const char *name, int fd)
{// 定义变量以存储SPI设备的状态信息__u8 lsb, bits;__u32 mode, speed;// 读取并打印SPI设备的模式if (ioctl(fd, SPI_IOC_RD_MODE32, &mode) < 0) {perror("SPI rd_mode");return;}// 读取并打印SPI设备是否配置为低位在先(LSB first)if (ioctl(fd, SPI_IOC_RD_LSB_FIRST, &lsb) < 0) {perror("SPI rd_lsb_fist");return;}// 读取并打印SPI设备每个字的数据位数if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits) < 0) {perror("SPI bits_per_word");return;}// 读取并打印SPI设备的最大传输速率if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed) < 0) {perror("SPI max_speed_hz");return;}// 打印SPI设备的状态信息printf("%s: spi mode 0x%x, %d bits %sper word, %d Hz max\n", name, mode,bits, lsb ? "(lsb first) " : "", speed);
}// 主函数,处理命令行参数并执行相应操作
int main(int argc, char **argv)
{int c;int readcount = 0;int msglen = 0;int fd;const char *name;// 处理命令行选项while ((c = getopt(argc, argv, "hm:r:v")) != EOF) {switch (c) {case 'm':msglen = atoi(optarg);if (msglen < 0)goto usage;continue;case 'r':readcount = atoi(optarg);if (readcount < 0)goto usage;continue;case 'v':verbose++;continue;case 'h':case '?':usage:fprintf(stderr,"usage: %s [-h] [-m N] [-r N] /dev/spidevB.D\n",argv[0]);return 1;}}// 检查剩余的参数if ((optind + 1) != argc)goto usage;name = argv[optind];// 打开设备文件fd = open(name, O_RDWR);if (fd < 0) {perror("open");return 1;}// 打印设备文件的状态dumpstat(name, fd);// 根据指定的消息长度执行操作if (msglen)do_msg(fd, msglen);// 根据指定的读取次数执行读取操作if (readcount)do_read(fd, readcount);// 关闭设备文件close(fd);return 0;
}

三、知识补充

“双线”、“四线”和“三线”

  在SPI (Serial Peripheral Interface) 通信中,“双线”、“四线”和“三线”指的是数据传输方式的不同变体:

  • 双线 (Dual SPI):
    • 在双线模式下,SPI 设备可以同时通过两条数据线进行数据传输。
    • 通常情况下,一条数据线用于发送数据 (MOSI), 另一条用于接收数据 (MISO)。
    • 在双线模式中,除了标准的 MOSI 和 MISO 外,还会额外使用一条数据线来增加数据吞吐量。
    • 例如,在读操作中,一条数据线用于发送地址或命令,另一条数据线用于接收数据;而在写操作中,两条数据线都用于发送数据。
  • 四线 (Quad SPI):
    • 四线模式进一步扩展了数据传输能力,使用四条数据线进行数据传输。
    • 这种模式可以在单次时钟周期内传输更多的数据,从而显著提高数据传输速率。
    • 例如,在读操作中,三条数据线用于发送地址或命令,第四条数据线用于接收数据;而在写操作中,四条数据线都用于发送数据。
  • 三线 (Three-Wire SPI):
    • 三线SPI是一种特殊的SPI模式,它减少了所需的信号线数量,仅使用三条信号线:SCK (串行时钟)、MOSI (主出从入) 和 CS (片选)。
    • 在这种模式下,MISO (主入从出) 信号线被省略了。
    • 通常用于不需要双向数据流的应用场景,比如只读或只写的存储器芯片。

  这些不同的数据传输方式提供了不同的性能和成本权衡,可以根据具体的应用需求选择最合适的传输模式。

8位、16位、32位

  当提到 SPI 的 8 位、16 位或 32 位时,这指的是 SPI 数据帧的大小,即每次传输的数据量。具体来说,这些术语指的是 SPI 通信中数据字的长度。例如,8 位 SPI 表示每次传输 8 位数据;16 位 SPI 表示每次传输 16 位数据;而 32 位 SPI 则表示每次传输 32 位数据。

SPI 数据帧大小的意义:

  1. 数据宽度:决定了每次 SPI 事务中传输的数据量。
  2. 兼容性:不同的设备可能支持不同的数据宽度,因此选择正确的数据宽度对于确保设备间的正确通信至关重要。
  3. 性能:更宽的数据帧可以提高数据传输速率,但这也取决于设备的能力和 SPI 总线的最大频率。

  位数与接线的关系:SPI的接线并不直接与数据帧的大小有关。

  SPI 通信通常使用以下四条线:SCK (Serial Clock)MOSI (Master Out Slave In)MISO (Master In Slave Out)SS (Slave Select)

  这些线路不论是在 8 位、16 位还是 32 位 SPI 中都是相同的。数据帧的大小通过软件配置来确定,而不是通过硬件接线。这些设置不会影响 SPI 的物理接线。

8位、16位等位数与硬件的关系

  1. 硬件支持:

    • 不同的 SPI 控制器硬件可能支持不同的数据位数。例如,一些控制器可能仅支持 8 位数据帧,而其他高级控制器则可能支持 8 位、16 位甚至 32 位数据帧。
  2. 寄存器配置:

    • 在硬件层面上,SPI 控制器通常会有一些寄存器用来配置数据帧的位数。例如,在 STM32 微控制器中,SPI 控制寄存器 SPI_CR1 中的 DFF 位(Data Frame Format)可以用来选择数据帧是 8 位还是 16 位。
  3. 时钟管理:

    • 数据帧的位数会影响 SPI 时钟的管理。例如,如果选择了 16 位数据帧,那么 SPI 时钟将在 16 个时钟周期内完成一次数据传输。
示例:STM32 的 SPI 控制器在 STM32 微控制器中,SPI 控制器支持 8 位或 16 位的数据帧,并且可以通过 SPI 
控制寄存器.SPI_CR1 中的 DFF 位进行配置:如果 DFF 位被清零(0),则数据帧长度为 8 位。如果 DFF 位被置位(1),则数据帧长度为 16 位。

  个人水平有限,欢迎大家在评论区进行指导和交流!!!😁😁😁

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • OpenCV 100道面试题及参考答案(7万字长文)
  • 错误: 找不到或无法加载主类 App.class,Java文件是怎么编译的
  • Android12 添加设置控制导航栏显示和状态栏下拉
  • MyBatis中使用的设计模式详细解析
  • 基于python的web框架 Flask 入门基础知识【1】
  • pnpm 查看库的所有版本
  • Centos设置IP地址方法
  • flutter执行Asset中的可执行文件
  • 嵌入式Linux应用程序开发-2 Linux基础命令
  • 国外服务器独立ip的好处
  • 动态规划的正确打开
  • Linux学习-上传本地镜像到指定镜像仓库
  • Spring Boot DevTools:简化开发,实现热部署
  • 【自由能系列(初级)】大脑功能与贝叶斯计算——深层生成模型的自由能原理
  • (每日一问)计算机网络:浏览器输入一个地址到跳出网页这个过程中发生了哪些事情?(废话少说版)
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  • “大数据应用场景”之隔壁老王(连载四)
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • 【mysql】环境安装、服务启动、密码设置
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • Java 23种设计模式 之单例模式 7种实现方式
  • java8-模拟hadoop
  • JavaScript创建对象的四种方式
  • JavaScript类型识别
  • laravel5.5 视图共享数据
  • Python语法速览与机器学习开发环境搭建
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • win10下安装mysql5.7
  • 分类模型——Logistics Regression
  • 规范化安全开发 KOA 手脚架
  • 利用jquery编写加法运算验证码
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 实战|智能家居行业移动应用性能分析
  • 使用 Docker 部署 Spring Boot项目
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 栈实现走出迷宫(C++)
  • Prometheus VS InfluxDB
  • Unity3D - 异步加载游戏场景与异步加载游戏资源进度条 ...
  • # Redis 入门到精通(七)-- redis 删除策略
  • #13 yum、编译安装与sed命令的使用
  • #if 1...#endif
  • ${ }的特别功能
  • (2024.6.23)最新版MAVEN的安装和配置教程(超详细)
  • (c语言)strcpy函数用法
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • (转)Mysql的优化设置
  • .naturalWidth 和naturalHeight属性,
  • .NET Core Web APi类库如何内嵌运行?
  • .NET Framework 4.6.2改进了WPF和安全性