0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

矜辰所致 来源:矜辰所致 作者:矜辰所致 2022-07-04 15:46 次阅读
本文学习一下I/O 设备模型之SPI设备使用,I/O 设备模型篇的最后一篇文章。

目录

前言
一、SPI 通讯基础
二、SPI 设备操作函数

2.1 挂载 SPI 设备
2.2 配置 SPI 设备
2.3 访问 SPI设备
2.3.1 查找 SPI 设备
2.3.2 自定义数据传输
2.3.3 数据收发函数
2.3.4 特殊场景

三、SPI 设备测试

3.1 SPI 设备使用步骤
3.2 测试

结语

前言

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

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

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

❤️
本 RT-Thread 专栏记录的开发环境:
RT-Thread记录(一、RT-Thread 版本、RT-Thread Studio开发环境 及 配合CubeMX开发快速上手)
RT-Thread记录(二、RT-Thread内核启动流程 — 启动文件和源码分析)
❤️
RT-Thread 内核篇系列博文链接:
RT-Thread记录(三、RT-Thread 线程操作函数及线程管理与FreeRTOS的比较)
RT-Thread记录(四、RT-Thread 时钟节拍和软件定时器
RT-Thread记录(五、RT-Thread 临界区保护)
RT-Thread记录(六、IPC机制之信号量、互斥量和事件集)
RT-Thread记录(七、IPC机制之邮箱、消息队列)
RT-Thread记录(八、理解 RT-Thread 内存管理)
RT-Thread记录(九、RT-Thread 中断处理与阶段小结)
❤️
STM32L051C8 上使用 RT-Thread 应用篇系列博文连接:
RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (一、无线温湿度传感器 之 新建项目)
RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (二、无线温湿度传感器 之 CubeMX配置)
RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (三、无线温湿度传感器 之 I2C通讯)
RT-Thread 应用篇 — 在STM32L051上使用 RT-Thread (四、无线温湿度传感器 之 串口通讯)
❤️
RT-Thread 设备篇系列博文链接:
RT-Thread记录(十、全面认识 RT-Thread I/O 设备模型)
RT-Thread记录(十一、I/O 设备模型之UART设备 — 源码解析)
RT-Thread记录(十二、I/O 设备模型之UART设备 — 使用测试)
RT-Thread记录(十三、I/O 设备模型之PIN设备)
RT-Thread记录(十四、I/O 设备模型之ADC设备)

一、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,从设备使能信号,由主设备控制。

pYYBAGLCmuWAfYoIAAA25IYPAK4385.png

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

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

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

如下表格:

pYYBAGLCjryAbKy8AAA8Tlo2bN8553.png

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

poYBAGLCmuWANaQPAARKR5WikzQ812.png

二、SPI 设备操作函数

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

poYBAGLCjuGAI_AjAAB4Oi-n-08144.png

与前面的设备不同的地方在于,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 也有不一样的地方,具体如下图:

pYYBAGLCmuWAHOXoAAC7LrtEE1E821.png

因为 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;				// 本次结束之后并不释放总线,因为还要发送,所以为0
msg1.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;				// 本次结束之后并不释放总线,因为还要发送,所以为0
msg1.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_take 和 cs_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的 使用步骤的注释:

poYBAGLCmuaAB_wYAACYsFC9_Bc498.png

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

pYYBAGLCmuaANmPxAABEWC_r-C4633.png

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

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

poYBAGLCmueAah4EAAB9cnNa4ws828.png

查看对应的手册资料

pYYBAGLCmueAW-CxAACHzAL-lVQ135.png

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

poYBAGLCmueAM0i7AAAiiAlf7hs304.png

3、通过STM32CubeMX 配置 SPI :

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

pYYBAGLCmueAeyVBAADYIyGYG5Y639.png

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

poYBAGLCmuiAQSnOAACatt529PI091.png

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

pYYBAGLCmuiAEDhIAACi56zeZhc351.png

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

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

poYBAGLCmuiAKyBXAABZrNA-IYk507.png

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

3.2 测试

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

第一步:检查 spi 总线

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

pYYBAGLCmuiAGbRJAABLCU3AwJI846.png

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

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

poYBAGLCmumANjmaAACe1gqastk553.png

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

pYYBAGLCmumAAGyJAABly8tj97o828.png

第三部,通讯

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

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

//读取芯片ID W25Q128的ID:0XEF17
u16 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 位),通过手册我们可以可以看到说明:

poYBAGLCmumAM6mbAAIMKDFtcWs687.png

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

pYYBAGLCmuqAblL3AACkwvivNFY297.png

测试结果:

poYBAGLCmuqADHF0AABJHUm8VLk989.png

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

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

pYYBAGLCmuqAUzdTAAA0lcJSsuM375.png

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

结语

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

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

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

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

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

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

审核编辑:符乾江

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 操作系统
    +关注

    关注

    37

    文章

    6266

    浏览量

    121842
  • RT-Thread
    +关注

    关注

    31

    文章

    1148

    浏览量

    38857
收藏 人收藏

    评论

    相关推荐

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

    学完 RT-Thread 内核,从本文开始熟悉了解 RT-Thread I/O 设备管理相关知识。
    的头像 发表于 06-30 10:38 3515次阅读
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>记录</b>(十、全面认识 I/O <b class='flag-5'>设备</b><b class='flag-5'>模型</b>)

    RT-Thread记录(十一、UART设备—源码解析)

    一文带你深入理解 RT-Thread I/O 设备模型 — UART 设备源码分析。
    的头像 发表于 07-01 11:24 5009次阅读
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>记录</b>(十一、UART<b class='flag-5'>设备</b>—源码解析)

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

    我曾经考虑过把 RT-Thread 常用的设备都写完,其实通过前面的《全面认识 RT-Thread I/O 设备模型》文章学习,以及 UAR
    的头像 发表于 07-04 12:28 3852次阅读
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>记录</b>(十四、I/O <b class='flag-5'>设备</b><b class='flag-5'>模型</b>之ADC<b class='flag-5'>设备</b>)

    RT-Thread记录(十二、UART设备—使用测试)

    从 UART 设备开始学会使用 RT-Thread I/O 设备模型
    的头像 发表于 07-02 12:42 4583次阅读
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>记录</b>(十二、UART<b class='flag-5'>设备</b>—使用测试)

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

    讲完UART设备之后,我们已经熟悉RT-Thread I/O 设备模型了,回头看看基本的 PIN 设备
    的头像 发表于 07-03 11:28 3767次阅读
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>记录</b>(十三、I/O <b class='flag-5'>设备</b><b class='flag-5'>模型</b>之PIN<b class='flag-5'>设备</b>)

    基于RT-ThreadSPI通讯

         sdk 目前还不支持 spi,没有 spi 就失去了很多乐趣,如 easyflash、spi 的屏幕,蓝讯的这次活动我接到了模拟 spi 的任务,下面介绍如何写
    的头像 发表于 08-22 09:28 1255次阅读

    RT-Thread 的 IO 设备模型框架是由哪些部分组成的呢

    RT-ThreadI/O 设备模型框架是由哪些部分组成的呢?接下来由小编给大家详细介绍一下。1、R
    发表于 03-11 18:17

    【原创精选】RT-Thread征文精选技术文章合集

    RT-Thread记录(十二、UART设备—使用测试)RT-Thread记录(十三、I/
    发表于 07-26 14:56

    如何使用RT-Thread SPI设备驱动

    RT-Thread中引入了SPI设备驱动框架。本文说明了如何使用RT-Thread SPI设备
    发表于 10-25 14:20

    RT-ThreadSPI的细节内容

    作为通信协议的两大基础,IIC和SPI两者的应用都非常广泛,上一篇文章讲过了RTT上IIC的移植与实践。 《一步到位!教你RT-Thread设备IIC驱动移植》 讲完IIC,自然少不了SPI
    的头像 发表于 09-30 15:35 3580次阅读
    <b class='flag-5'>RT-Thread</b>上<b class='flag-5'>SPI</b>的细节内容

    RT-Thread设备模型框架及创建注册设备的实现

    RT-Thread设备模型框架及创建注册设备的实现方式介绍如下:
    的头像 发表于 05-28 10:38 1802次阅读
    <b class='flag-5'>RT-Thread</b><b class='flag-5'>设备</b><b class='flag-5'>模型</b>框架及创建注册<b class='flag-5'>设备</b>的实现

    RT-Thread文档_I/O 设备模型

    RT-Thread文档_I/O 设备模型
    发表于 02-22 18:31 0次下载
    <b class='flag-5'>RT-Thread</b>文档_I/O <b class='flag-5'>设备</b><b class='flag-5'>模型</b>

    RT-Thread文档_PWM 设备

    RT-Thread文档_PWM 设备
    发表于 02-22 18:35 1次下载
    <b class='flag-5'>RT-Thread</b>文档_PWM <b class='flag-5'>设备</b>

    RT-Thread文档_SPI 设备

    RT-Thread文档_SPI 设备
    发表于 02-22 18:36 1次下载
    <b class='flag-5'>RT-Thread</b>文档_<b class='flag-5'>SPI</b> <b class='flag-5'>设备</b>

    RT-Thread文档_Pulse Encoder 设备

    RT-Thread文档_Pulse Encoder 设备
    发表于 02-22 18:39 1次下载
    <b class='flag-5'>RT-Thread</b>文档_Pulse Encoder <b class='flag-5'>设备</b>