写点什么

RT-Thread 记录(十五、I/O 设备模型之 SPI 设备)

作者:矜辰所致
  • 2022 年 8 月 25 日
    江苏
  • 本文字数:10704 字

    阅读完需:约 35 分钟

RT-Thread记录(十五、I/O 设备模型之SPI设备)
本文学习一下I/O 设备模型之SPI设备使用,I/O 设备模型篇的最后一篇文章。
复制代码


前言

本文应该是 RT-Thread I/O 设备模型最后一篇,SPI 设备的学习测试。


我以前就说过,我的记录是以应用为目的,实际上我们在使用 RT-Thread 的时候,有很多常用的设备,官方或者很多开发者都已经给我们写好了驱动和软件包,我们并不需要自己重新写一篇,很多时候直接导入软件包,直接调用现成的 API 函数就可以。


RT-Thread 文章接下来的系列,应该会更新几篇 软件包和组件的使用,本文把 SPI 设备做一个学习测试。


本 RT-Thread 专栏记录的开发环境:

RT-Thread 记录(一、RT-Thread 版本、RT-Thread Studio 开发环境 及 配合 CubeMX 开发快速上手)

https://xie.infoq.cn/article/44be1057caace7a6a2c4c4b59

RT-Thread 记录(二、RT-Thread 内核启动流程 — 启动文件和源码分析)

https://xie.infoq.cn/article/44be1057caace7a6a2c4c4b59

RT-Thread 内核篇系列博文链接:

RT-Thread 记录(三、RT-Thread 线程操作函数及线程管理与 FreeRTOS 的比较)

https://xie.infoq.cn/article/1d2e8e030ae689d6b8ee44b05

RT-Thread 记录(四、RT-Thread 时钟节拍和软件定时器)

https://xie.infoq.cn/article/3198c9b741782036bfd6e54e9

RT-Thread 记录(五、RT-Thread 临界区保护

https://xie.infoq.cn/article/7a41020e03184950664df7391

RT-Thread 记录(六、IPC 机制之信号量、互斥量和事件集)

https://xie.infoq.cn/article/1f49bfd6c69377deb9eee838f

RT-Thread 记录(七、IPC 机制之邮箱、消息队列)

https://xie.infoq.cn/article/360b04e7bc6024917afecef1d

RT-Thread 记录(八、理解 RT-Thread 内存管理)

https://xie.infoq.cn/article/bd5d8f8a19fa11ea9feacf34d

RT-Thread 记录(九、RT-Thread 中断处理与阶段小结)

https://xie.infoq.cn/article/3da9530cfac06feece3523a0c

RT-Thread 设备篇系列博文链接:

RT-Thread 记录(十、全面认识 RT-Thread I/O 设备模型)

https://xie.infoq.cn/article/40536d29988d683c78b4ba5ff

RT-Thread 记录(十一、I/O 设备模型之 UART 设备 — 源码解析)

https://xie.infoq.cn/article/38bb4bf15cb81f1fdb060b29e

RT-Thread 记录(十二、I/O 设备模型之 UART 设备 — 使用测试)

https://xie.infoq.cn/article/1df47f3ae5174dfcb418b07b6

RT-Thread 记录(十三、I/O 设备模型之 PIN 设备)

https://xie.infoq.cn/article/af3ff1066f61cddeb134c3c88

RT-Thread 记录(十四、I/O 设备模型之 ADC 设备)

https://xie.infoq.cn/article/2eda5e23a77db3a60d2595d97

一、SPI 通讯基础

SPI 通讯基本知识不过多介绍,原理与基础可自行网上查询,本文这里只做应用所需的简单概述:


SPI 是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,SPI 的通讯速度可以达到几十 M,并且在芯片的管脚上只占用四根线:


(1)MISO– Master Input Slave Output,主设备数据输入,从设备数据输出;(2)MOSI– Master Output Slave Input,主设备数据输出,从设备数据输入;(3)SCLK – Serial Clock,时钟信号,由主设备产生;(4)CS – Chip Select,从设备使能信号,由主设备控制。



SPI 以主从方式工作,通常有一个主设备和一个或多个从设备。


SPI 通讯有 4 中模式,由 CPOL (时钟的极性)和 CPHA (时钟的相位)决定:


CPOL=0,表示当 SCLK=0 时处于空闲态,空闲低电平,所以有效状态就是 SCLK 处于高电平时 CPOL=1,表示当 SCLK=1 时处于空闲态,空闲高电平,所以有效状态就是 SCLK 处于低电平时 CPHA=0,表示数据采样是在第 1 个边沿 CPHA=1,表示数据采样是在第 2 个边沿


如下表格:



对于我们的从机设备,比如传感器,支持的模式会使用手册中说明:比如我们今天要测试的 SPI Flash:


二、SPI 设备操作函数

来了解一下 RT-Thread 提供的 SPI 设备操作函数:



与前面的设备不同的地方在于,SPI 因为可以一主多从,所以 SPI 设备多了一个挂载操作,就是 RT-Thread 系统驱动会注册好 SPI 总线,然后我们需要把自己所用的 SPI 设备挂载到总线上,使得可以对该设备进行操作 。


☆ 自定义传输数据函数 rt_spi_transfer_message 为核心,其实在其之后的那些都可以使用这个函数来表达,这个下文会说明。☆


.

2.1 挂载 SPI 设备

SPI 驱动注册完 SPI 总线,需要用 SPI 挂载函数将要使用的 SPI 设备需要挂载到已经注册好的 SPI 总线上:


/*参数   描述device     SPI 设备句柄name     SPI 设备名称bus_name   SPI 总线名称user_data   用户数据指针返回   ——RT_EOK     成功其他错误码   失败*/rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device,                                  const char           *name,                                  const char           *bus_name,                                  void                 *user_data)
复制代码


此函数用于挂载一个 SPI 设备到指定的 SPI 总线,并向内核注册 SPI 设备,并将 user_data 保存到 SPI 设备的控制块里。


一般 SPI 总线命名原则为 spix, SPI 设备命名原则为 spixy ,如 spi10 表示挂载在 spi1 总线上的 0 号设备。user_data 一般为 SPI 设备的 CS 引脚指针,进行数据传输时 SPI 控制器会操作此引脚进行片选。


对于我们测试使用的 STM32 而言,有专门的挂载函数 rt_hw_spi_device_attach


/*参数   描述bus_name       SPI 总线名称device_name     SPI 设备名称
后面2个参数是设置片选引脚:
cs_gpiox GPIOA、GPIOB之类...cs_gpio_pin GPIO口名称返回 ——RT_EOK 成功其他错误码 失败*/rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, GPIO_TypeDef *cs_gpiox, uint16_t cs_gpio_pin)
复制代码

2.2 配置 SPI 设备

上面介绍 SPI 通讯基础的时候讲到过 SPI 的工作模式等细节,RT-Thread 里使用 SPI 配置函数进行配置:


/*参数     描述device   SPI 设备句柄cfg   SPI 配置参数指针返回   ——RT_EOK   成功*/rt_err_t rt_spi_configure(struct rt_spi_device        *device,                          struct rt_spi_configuration *cfg)
...
/** * SPI configuration structure */struct rt_spi_configuration{ rt_uint8_t mode; /* 模式 */ rt_uint8_t data_width; /* 数据宽度,可取8位、16位、32位 */ rt_uint16_t reserved; /* 保留 */ rt_uint32_t max_hz; /* 最大频率 */};

/** * 上面结构体第一个参数: mode * SPI configuration structure * 其中与 SPI mode 相关的宏定义有 */#define RT_SPI_CPHA (1<<0) /* bit[0]:CPHA, clock phase */#define RT_SPI_CPOL (1<<1) /* bit[1]:CPOL, clock polarity *//* 设置数据传输顺序是MSB位在前还是LSB位在前 */#define RT_SPI_LSB (0<<2) /* bit[2]: 0-LSB */#define RT_SPI_MSB (1<<2) /* bit[2]: 1-MSB *//* 设置SPI的主从模式 */#define RT_SPI_MASTER (0<<3) /* SPI master device */#define RT_SPI_SLAVE (1<<3) /* SPI slave device */
#define RT_SPI_CS_HIGH (1<<4) /* Chipselect active high */#define RT_SPI_NO_CS (1<<5) /* No chipselect */#define RT_SPI_3WIRE (1<<6) /* SI/SO pin shared */#define RT_SPI_READY (1<<7) /* Slave pulls low to pause */
#define RT_SPI_MODE_MASK (RT_SPI_CPHA | RT_SPI_CPOL | RT_SPI_MSB | RT_SPI_SLAVE | RT_SPI_CS_HIGH | RT_SPI_NO_CS | RT_SPI_3WIRE | RT_SPI_READY)/* 设置时钟极性和时钟相位 */#define RT_SPI_MODE_0 (0 | 0) /* CPOL = 0, CPHA = 0 */#define RT_SPI_MODE_1 (0 | RT_SPI_CPHA) /* CPOL = 0, CPHA = 1 */#define RT_SPI_MODE_2 (RT_SPI_CPOL | 0) /* CPOL = 1, CPHA = 0 */#define RT_SPI_MODE_3 (RT_SPI_CPOL | RT_SPI_CPHA) /* CPOL = 1, CPHA = 1 */
#define RT_SPI_BUS_MODE_SPI (1<<0)#define RT_SPI_BUS_MODE_QSPI (1<<1)
/** * 上面结构体第二个和第四个参数: data_width 和 max_hz *///根据 SPI 主设备及 SPI 从设备可发送及接收的数据宽度格式 和频率 设置。

/* * 示例程序 */ struct rt_spi_configuration cfg; cfg.data_width = 8; cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; cfg.max_hz = 20 * 1000 *1000; /* 20M */
rt_spi_configure(spi_dev, &cfg);
复制代码

2.3 访问 SPI 设备

前面的两个函数类似于 SPI 的初始化工作,接下来就是我们熟悉的设备操作函数:

2.3.1 查找 SPI 设备

I/O 设备模型通用的查找函数:


/*参数   描述name   SPI 设备名称返回   ——设备句柄   查找到对应设备将返回相应的设备句柄RT_NULL   没有找到设备*/rt_device_t rt_device_find(const char* name);
复制代码


注意事项和 ADC 设备一样,用来接收的设备句柄不是使用rt_device_t ,但是与 ADC 也有不一样的地方,具体如下图:



因为 SPI 设备的接口体并没有 typedef 重定义,所以使用起来还得直接使用结构体指针表示。

2.3.2 自定义数据传输

自定义传输函数rt_spi_transfer_message,是访问 SPI 设备的关键函数!


获取到 SPI 设备句柄就可以使用 SPI 设备管理接口访问 SPI 设备器件,进行数据收发:


/*参数   描述device     SPI 设备句柄message   消息指针返回   ——RT_NULL   成功发送非空指针   发送失败,返回指向剩余未发送的 message 的指针*/struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device  *device,                                               struct rt_spi_message *message)
复制代码


其中第二个参数,消息的结构体,这也是发送消息的关键:


/** * SPI message structure */struct rt_spi_message{    const void *send_buf;           /* 发送缓冲区指针,其值为 RT_NULL 时,                    表示本次传输为只接收状态,不需要发送数据。*/    void *recv_buf;                 /* 接收缓冲区指针,其值为 RT_NULL 时,                    表示本次传输为只发送状态,不需要保存接收到的数据 */    rt_size_t length;               /* 发送 / 接收 数据字节数,单位为 word ,                    长度为 8 位时,每个 length 占用 1 个字节;                    当数据长度为 16 位时,每个 length 占用 2 个字节*/    struct rt_spi_message *next;    /* 指向继续发送的下一条消息的指针 ,                    若只发送一条消息,则此指针值为 RT_NULL。                    多个待传输的消息通过 next 指针以单向链表的形式连接在一起。*/        unsigned cs_take    : 1;        /* 片选选中                     cs_take 值为 1 时,表示在传输数据前,设置对应的 CS 为有效状态。*/    unsigned cs_release : 1;        /* 释放片选                       cs_release 值为 1 时,表示在数据传输结束后,释放对应的 CS。*/};
复制代码


关于最后两个参数:


传输的第一条消息 cs_take 需置为 1,设置片选为有效,


传输的最后一条消息的 cs_release 需置 1,释放片选。


示例 1 ,只发一条(主要关注最后两个参数的设置):


struct rt_spi_message msg1;

msg1.send_buf = send_buf;msg1.recv_buf = receive_buf;msg1.length = send_length;msg1.cs_take = 1; // 传输之前要先把总线拉低msg1.cs_release = 1; // 传输之后要把总线释放msg1.next = RT_NULL;

rt_spi_transfer_message(struct rt_spi_device *device, &msg1);
复制代码


示例 2 ,先发后收(主要关注最后两个参数的设置):


struct rt_spi_message msg1,msg2;
uint8 id[5] = {0};
msg1.send_buf = send_buf;msg1.recv_buf = RT_NULL;msg1.length = send_length;msg1.cs_take = 1; // 传输之前要先把总线拉低msg1.cs_release = 0; // 本次结束之后并不释放总线,因为还要发送,所以为0msg1.next = &msg2;
msg2.send_buf = RT_NULL;msg2.recv_buf = id;msg2.length = 5; //接收5个字节msg2.cs_take = 0; //前面已经拉低了,没有释放,所以这里是不需要拉低的msg2.cs_release = 1; //但是这个完成以后,需要释放总线,这是结尾msg2.next = RT_NULL;

rt_spi_transfer_message(struct rt_spi_device *device, &msg1);
复制代码


示例 3 ,假如有 3 个 message:


struct rt_spi_message msg1,msg2,msg3;

msg1.send_buf = send_buf;msg1.recv_buf = RT_NULL;msg1.length = length1;msg1.cs_take = 1; // 传输之前要先把总线拉低msg1.cs_release = 0; // 本次结束之后并不释放总线,因为还要发送,所以为0msg1.next = &msg2;
msg2.send_buf = RT_NULL;msg2.recv_buf = receive_buff;msg2.length = length2; msg2.cs_take = 0; //前面已经拉低了,没有释放,所以这里是不需要拉低的msg2.cs_release = 0; //这里也不需要释放,前面会拉,后面会放msg2.next = &msg3;

msg3.send_buf = RT_NULL;msg3.recv_buf = receive_buff;msg3.length = len3; //msg3.cs_take = 0; //前面已经拉低了,没有释放,所以这里是不需要拉低的msg3.cs_release = 1; //但是这个完成以后,需要释放总线,这是结尾msg3.next = RT_NULL;

rt_spi_transfer_message(struct rt_spi_device *device, &msg1);
复制代码

2.3.3 数据收发函数

除了上面通用的自定义数据传输函数, RT-Thread 还提供了一系列简单的数据收发函数,其实都是通过上面的函数演变而来,我们也简单的过一遍:


传输一次数据:


/*参数   描述device     SPI 设备句柄send_buf   发送数据缓冲区指针recv_buf   接收数据缓冲区指针length     发送/接收 数据字节数返回   ——0   传输失败非 0 值   成功传输的字节数*/rt_size_t rt_spi_transfer(struct rt_spi_device *device,                          const void           *send_buf,                          void                 *recv_buf,                          rt_size_t             length)
复制代码


使用此函数等同于:


struct rt_spi_message msg;
msg.send_buf = send_buf;msg.recv_buf = recv_buf;msg.length = length;msg.cs_take = 1;msg.cs_release = 1;msg.next = RT_NULL;

rt_spi_transfer_message(struct rt_spi_device *device, &msg);
复制代码


发送一次数据:


/*参数   描述device     SPI 设备句柄send_buf   发送数据缓冲区指针length     发送数据字节数返回   ——0   发送失败非 0 值   成功发送的字节数*/rt_inline rt_size_t rt_spi_send(struct rt_spi_device *device,                                const void           *send_buf,                                rt_size_t             length){    return rt_spi_transfer(device, send_buf, RT_NULL, length);}
复制代码


此函数直接是上面函数忽略接收数据的效果,可以直接看上面的函数内容。


接收一次数据:


/*参数   描述device     SPI 设备句柄recv_buf   接收数据缓冲区指针length     接收数据字节数返回   ——0     接收失败非 0 值   成功接收的字节数*/rt_inline rt_size_t rt_spi_recv(struct rt_spi_device *device,                                void                 *recv_buf,                                rt_size_t             length){    return rt_spi_transfer(device, RT_NULL, recv_buf, length);}
复制代码


与上面发送一次数据相反,传输一次数据函数忽略接收的数据。


连续两次发送数据:


/*参数   描述device   SPI 设备句柄send_buf1   发送数据缓冲区 1 指针send_length1   发送数据缓冲区 1 数据字节数send_buf2   发送数据缓冲区 2 指针send_length2   发送数据缓冲区 2 数据字节数返回   ——RT_EOK   发送成功-RT_EIO   发送失败*/rt_err_t rt_spi_send_then_send(struct rt_spi_device *device,                               const void           *send_buf1,                               rt_size_t             send_length1,                               const void           *send_buf2,                               rt_size_t             send_length2)
复制代码


本函数适合向 SPI 设备中写入一块数据,第一次先发送命令和地址等数据,第二次再发送指定长度的数据。


之所以分两次发送而不是合并成一个数据块发送,或调用两次 rt_spi_send(),是因为在大部分的数据写操作中,都需要先发命令和地址,长度一般只有几个字节。如果与后面的数据合并在一起发送,将需要进行内存空间申请和大量的数据搬运。


而如果调用两次 rt_spi_send(),那么在发送完命令和地址后,片选会被释放,大部分 SPI 设备都依靠设置片选一次有效为命令的起始,所以片选在发送完命令或地址数据后被释放,则此次操作被丢弃。


使用此函数等同于:


struct rt_spi_message msg1,msg2;
msg1.send_buf = send_buf1;msg1.recv_buf = RT_NULL;msg1.length = send_length1;msg1.cs_take = 1;msg1.cs_release = 0;msg1.next = &msg2;
msg2.send_buf = send_buf2;msg2.recv_buf = RT_NULL;msg2.length = send_length2;msg2.cs_take = 0;msg2.cs_release = 1;msg2.next = RT_NULL;

rt_spi_transfer_message(struct rt_spi_device *device, &msg1);
复制代码


先发送后接收数据:


/*参数   描述device       SPI 从设备句柄send_buf     发送数据缓冲区指针send_length   发送数据缓冲区数据字节数recv_buf     接收数据缓冲区指针recv_length   接收数据字节数返回   ——RT_EOK     成功-RT_EIO   失败*/rt_err_t rt_spi_send_then_recv(struct rt_spi_device *device,                               const void           *send_buf,                               rt_size_t             send_length,                               void                 *recv_buf,                               rt_size_t             recv_length)
复制代码


本函数适合从 SPI 从设备中读取一块数据,第一次会先发送一些命令和地址数据,然后再接收指定长度的数据。


使用此函数等同于:


struct rt_spi_message msg1,msg2;
msg1.send_buf = send_buf;msg1.recv_buf = RT_NULL;msg1.length = send_length;msg1.cs_take = 1;msg1.cs_release = 0;msg1.next = &msg2;
msg2.send_buf = RT_NULL;msg2.recv_buf = recv_buf;msg2.length = recv_length;msg2.cs_take = 0;msg2.cs_release = 1;msg2.next = RT_NULL;

rt_spi_transfer_message(struct rt_spi_device *device, &msg1);
复制代码

2.3.4 特殊场景

特殊场景部分暂时并不能体会其中的意义,所以这里直接套用官方的说明,等以后再使用过程中如果确实遇到问题,再来更新自己的心得体会。


在一些特殊的使用场景,某个设备希望独占总线一段时间,且期间要保持片选一直有效,期间数据传输可能是间断的,则可以按照如所示步骤使用相关接口。传输数据函数必须使用 rt_spi_transfer_message(),并且此函数每个待传输消息的片选控制域 cs_takecs_release 都要设置为 0 值,因为片选已经使用了其他接口控制,不需要在数据传输的时候控制。


获取总线:


在多线程的情况下,同一个 SPI 总线可能会在不同的线程中使用,为了防止 SPI 总线正在传输的数据丢失,从设备在开始传输数据前需要先获取 SPI 总线的使用权,获取成功才能够使用总线传输数据:


/*参数   描述device   SPI 设备句柄返回   ——RT_EOK   成功错误码   失败*/rt_err_t rt_spi_take_bus(struct rt_spi_device *device)
复制代码


选中片选:


从设备获取总线的使用权后,需要设置自己对应的片选信号为有效:


/*参数   描述device   SPI 设备句柄返回   ——0   成功错误码   失败*/rt_err_t rt_spi_take(struct rt_spi_device *device)
复制代码


增加一条消息:


使用 rt_spi_transfer_message() 传输消息时,所有待传输的消息都是以单向链表的形式连接起来的:


/*参数   描述list     待传输的消息链表节点message   新增消息指针*/rt_inline void rt_spi_message_append(struct rt_spi_message *list,                                     struct rt_spi_message *message)
复制代码


释放片选:


传输完成释放片选:


/*device   SPI 设备句柄返回   ——0     成功错误码   失败*/rt_err_t rt_spi_release(struct rt_spi_device *device)
复制代码


释放总线:


从设备不在使用 SPI 总线传输数据,必须尽快释放总线,这样其他从设备才能使用 SPI 总线传输数据:


/*参数   描述device   SPI 设备句柄返回   ——RT_EOK   成功*/rt_err_t rt_spi_release_bus(struct rt_spi_device *device);
复制代码

三、SPI 设备测试

与上一篇文章说的 ADC 设备类似,我们可以通过,但是也需要注意他的使用步骤:

3.1 SPI 设备使用步骤

board.h 文件中,我们可以查看其中关于 SPI 的 使用步骤的注释:



.


1、首先,在 RT-Thread Studio 工程中,打开 RT-Thread Settings,使能 SPI 驱动,如下图所示:


.



.


2、 宏定义 #define BSP_USING_SPI1(根据自己使用的设备硬件连接定义):.


比如我使用的开发板原理图(忽略当时的引脚标号,这里应该是 SPI1,当时写标号居然写的是 SPI2 ):



查看对应的手册资料:



所以我们需要使能的是 SPI1:



.


3、通过 STM32CubeMX 配置 SPI :.


和上一篇文章的 ADC 设备一样进行操作,如下图:



到这一步,我们已经能够找到我们需要的 HAL_SPI_MspInit 文件了,通过 spi.h 头文件找到 spi.c 文件中的这个函数:



.


4、 把HAL_SPI_MspInit 函数复制到 board.c 文件最后面,如下图:.



.


5. 查看 stm32xxxx_hal_config.h 文件 SPI 模块是否使能:.


在上一篇文章 ADC 步骤中已经讲解过,使用 STM32CubeMX 设置以后,文件会自动使能:



到这里 SPI 的配置就算全部完成了,我们可以直接在应用程序中,使用 SPI 设备操作函数实现 SPI 的读取。

3.2 测试

我们板载的是 SPI 设备是 W25Q128 ,我们测试一下 RT-Thread 的 SPI 设备模型是否能够正常通行,这里只做简单的读取 ID 的测试,官方的示例也是针对 W25Qxx 系列的,但是我还是按照自己的理解来进行。


第一步:检查 spi 总线


我们根据上面的使用步骤,配置好 SPI ,我们应用程序什么都不操作,看看初始化以后是否有 spi1 总线设备,如下图:



.


第二步:挂载 spi 设备至 spi 总线


确认了上电初始化以后 spi1 总线就已经存在,我们就可以使用 SPI 的操作函数进行,我们先把 spi 设备挂载上 spi 总线,然后进行必要的配置,操作代码如图:



到这一步,看可以看设备是否正常注册:



.


第三步,通讯


好了,接下来就可以经常正常的操作了,官方的示例是读取 W25Qxx 的 ID,至于读取 ID 操作流程,是需要查看 芯片手册的,但是我还想想到曾经在裸机使用过这个 SPI Flash ,那么我可以直接参考以前的驱动代码,这样就省去了再一次的手册查看资料 = = !


上一下裸机的有关操作代码:


//读取芯片ID W25Q128的ID:0XEF17u16 SPI_Flash_ReadID(){  u16 Temp = 0;      W25Qxx_CS_ON;              SPI1_ReadWriteByte(W25X_ManufactDeviceID);//   SPI1_ReadWriteByte(0x00);         SPI1_ReadWriteByte(0x00);         SPI1_ReadWriteByte(0x00);    //          Temp|=SPI1_ReadWriteByte(0xFF)<<8;    Temp|=SPI1_ReadWriteByte(0xFF);     W25Qxx_CS_OFF;              return Temp;  }

//指令表#define W25X_WriteEnable 0x06 #define W25X_WriteDisable 0x04 #define W25X_ReadStatusReg 0x05 #define W25X_WriteStatusReg 0x01 #define W25X_ReadData 0x03 #define W25X_FastReadData 0x0B #define W25X_FastReadDual 0x3B #define W25X_PageProgram 0x02 #define W25X_BlockErase 0xD8 #define W25X_SectorErase 0x20 #define W25X_ChipErase 0xC7 #define W25X_PowerDown 0xB9 #define W25X_ReleasePowerDown 0xAB #define W25X_DeviceID 0xAB #define W25X_ManufactDeviceID 0x90 #define W25X_JedecDeviceID 0x9F
复制代码


上电的时候读取一次设备的 ID,如果 读取的 ID 正常,说明设备正常,可以进行接下来的通讯。


通过上面的操作我们可以看到这个操作流程,先发送一个字节消息(读取指令), 然后再读取 5 个字节的消息,第 4 个字节和第 5 个字节就是 SPI Flash 的设备 ID (数据宽度 8 位),通过手册我们可以可以看到说明:



搞清楚了流程,下面的读取代码,其实和官方基本一样:



测试结果:



测试出来居然是反了,这个倒是无所谓,因为简单,反的原因这里不深究了。


当然上面是用的自定义数据传输函数rt_spi_transfer_message实现,我们也可以通过上面讲到的先发送后接收数据函数rt_spi_send_then_recv实现:



可以看到使用这种专有函数,程序会更加简单,但是我更加建议使用自定义,因为可以满足不同需求。

结语

本文我们学习了 RT-Thread 中 SPI 设备的使用方法,最终也通过简单的测试成功操作了 SPI 设备。


但是我们并没有进行正真的数据读写,在实际应用中,我们需要用到不同的 SPI 设备,就算是 SPI Flash 这一种设备,都有不同厂家不同型号的,难免有不同之处。


RT-Thread 有一个很大的特点在于他的生态比一般的 RTOS 完善,我们在实际应用中,有许许多多现成的官方或者很多开发者提供的组件或者软件包,我们可以直接导入工程进行使用。


比如就本文我们学习的 SPI 设备,我们就可以使用官方标准的组件 — SFUD 组件。


对于 RT-Thread 设备模型篇的内容,我也就更新到这篇文章,接下来就要开始学习使用 RT-Thread 的组件和软件包。


希望大家多多支持!本文就到这里,谢谢!

发布于: 刚刚阅读数: 6
用户头像

矜辰所致

关注

不浮夸,不将就,认真对待学知识的我们! 2022.08.02 加入

不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开! 为了活下去的嵌入式工程师,画画板子,敲敲代码,玩玩RTOS,搞搞Linux ...

评论

发布
暂无评论
RT-Thread记录(十五、I/O 设备模型之SPI设备)_RT-Thread_矜辰所致_InfoQ写作社区