Linux: SPI应用编程

2024-08-30 06:28
文章标签 linux 应用 编程 spi

本文主要是介绍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 位。

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

这篇关于Linux: SPI应用编程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1120044

相关文章

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

将Python应用部署到生产环境的小技巧分享

《将Python应用部署到生产环境的小技巧分享》文章主要讲述了在将Python应用程序部署到生产环境之前,需要进行的准备工作和最佳实践,包括心态调整、代码审查、测试覆盖率提升、配置文件优化、日志记录完... 目录部署前夜:从开发到生产的心理准备与检查清单环境搭建:打造稳固的应用运行平台自动化流水线:让部署像

Linux Mint Xia 22.1重磅发布: 重要更新一览

《LinuxMintXia22.1重磅发布:重要更新一览》Beta版LinuxMint“Xia”22.1发布,新版本基于Ubuntu24.04,内核版本为Linux6.8,这... linux Mint 22.1「Xia」正式发布啦!这次更新带来了诸多优化和改进,进一步巩固了 Mint 在 Linux 桌面

LinuxMint怎么安装? Linux Mint22下载安装图文教程

《LinuxMint怎么安装?LinuxMint22下载安装图文教程》LinuxMint22发布以后,有很多新功能,很多朋友想要下载并安装,该怎么操作呢?下面我们就来看看详细安装指南... linux Mint 是一款基于 Ubuntu 的流行发行版,凭借其现代、精致、易于使用的特性,深受小伙伴们所喜爱。对

什么是 Linux Mint? 适合初学者体验的桌面操作系统

《什么是LinuxMint?适合初学者体验的桌面操作系统》今天带你全面了解LinuxMint,包括它的历史、功能、版本以及独特亮点,话不多说,马上开始吧... linux Mint 是一款基于 Ubuntu 和 Debian 的知名发行版,它的用户体验非常友好,深受广大 Linux 爱好者和日常用户的青睐,

Linux(Centos7)安装Mysql/Redis/MinIO方式

《Linux(Centos7)安装Mysql/Redis/MinIO方式》文章总结:介绍了如何安装MySQL和Redis,以及如何配置它们为开机自启,还详细讲解了如何安装MinIO,包括配置Syste... 目录安装mysql安装Redis安装MinIO总结安装Mysql安装Redis搜索Red

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或

在Ubuntu上部署SpringBoot应用的操作步骤

《在Ubuntu上部署SpringBoot应用的操作步骤》随着云计算和容器化技术的普及,Linux服务器已成为部署Web应用程序的主流平台之一,Java作为一种跨平台的编程语言,具有广泛的应用场景,本... 目录一、部署准备二、安装 Java 环境1. 安装 JDK2. 验证 Java 安装三、安装 mys