OS版本:RT-Thread 4.0.0

测试BSP:STM32F407

SPI简介

SPI总线框架其实和I2C差不多,可以说都是总线设备+从设备,但SPI设备的通信时序配置并不固定,也就是说控制特定设备的总线需要单独配置;

SPI的特性是工作方式众多,有标准SPI和QSPI

QSPI: QSPI 是 Queued SPI 的简写,是 Motorola 公司推出的 SPI 接口的扩展,比 SPI 应用更加广泛。在 SPI 协议的基础上,Motorola 公司对其功能进行了增强,增加了队列传输机制,推出了队列串行外围接口协议(即 QSPI 协议)。使用该接口,用户可以一次性传输包含多达 16 个 8 位或 16 位数据的传输队列。一旦传输启动,直到传输结束,都不需要 CPU 干预,极大的提高了传输效率。与 SPI 相比,QSPI 的最大结构特点是以 80 字节的 RAM 代替了 SPI 的发送和接收数据寄存器。

Dual SPI Flash: 对于 SPI Flash 而言全双工并不常用,可以发送一个命令字节进入 Dual 模式,让它工作在半双工模式,用以加倍数据传输。这样 MOSI 变成 SIO0(serial io 0),MISO 变成 SIO1(serial io 1),这样一个时钟周期内就能传输 2 个 bit 数据,加倍了数据传输。

Quad SPI Flash: 与 Dual SPI 类似,Quad SPI Flash增加了两根 I/O 线(SIO2,SIO3),目的是一个时钟内传输 4 个 bit 数据。

所以对于 SPI Flash,有标准 SPI Flash,Dual SPI Flash, Quad SPI Flash 三种类型。在相同时钟下,线数越多传输速率越高。

SPI驱动分析

RT-Thread将驱动层抽象成设备,应用只需熟悉设备接口即可,驱动的分析我们从其 设备类的实现来剖析;

SPI的驱动里面主要包含两种设备 rt_spi_device(挂载SPI总线并配置了使能引脚和通信时序之后的设备) 和 rt_spi_bus(SPI总线、类似Linux的SPI适配器);

rt_spi_bus 即 SPI 总线,rt_spi_device 是绑定 rt_spi_configuration 之后的设备

struct rt_spi_device
{
struct rt_device parent;
struct rt_spi_bus *bus; struct rt_spi_configuration config;
void *user_data;
}; struct rt_spi_bus
{
struct rt_device parent;
rt_uint8_t mode;
const struct rt_spi_ops *ops; struct rt_mutex lock;
struct rt_spi_device *owner;
};

在使用 SPI 操作具体设备之前,需要 rt_hw_spi_device_attach 对对应设备的SPI时序配置进行绑定,官方的说法是将设备挂载到SPI总线;

下面我们一步步来看 SPI 设备时怎么样初始化和注册设备的;

其中 SPI 总线bus 在drv_spi.c 中的 rt_hw_spi_init(), 系统启动时进行了自动初始化

int rt_hw_spi_init(void)
{
stm32_get_dma_info();
return rt_hw_spi_bus_init(); //SPI-bus注册
}
INIT_BOARD_EXPORT(rt_hw_spi_init);

而设备的挂载需要在用户程序实现,可以使用前挂载,也可以使用自动初始化实现

// 自动初始化实现SPI设备挂载
int w25q_spi_device_init()
{
__HAL_RCC_GPIOB_CLK_ENABLE();
return rt_hw_spi_device_attach("spi1", "spi10", GPIOB, GPIO_PIN_14);  //设备挂载到SPI总线,抽象为 spi10 设备,同时使用时还需进行 rt_spi_configure
} 
INIT_DEVICE_EXPORT(w25q_spi_device_init);

注意设备驱动在使用之前,需要对挂载的设备进行 rt_spi_configure,当然也可以在自动初始化中就配置

    spi_dev_w25q = (struct rt_spi_device *)rt_device_find(name);
if (!spi_dev_w25q)
{
rt_kprintf("spi sample run failed! can't find %s device!\n", name);
}
else
{
/* config spi */
{
struct rt_spi_configuration cfg;
cfg.data_width = ;
cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB; /* SPI Compatible: Mode 0 and Mode 3 */
cfg.max_hz = * * ; /* 50M */
rt_spi_configure(spi_dev_w25q, &cfg);
}

以上是设备句柄的实现流程

SPI设备驱动

在 spi_dev.c 中可以看出,SPI设备的主要操作没有主要使用 I/O 设备模型来操作;

其 spi_device_ops 没有实现 contorl ,其读写则通过 rt_spi_transfer 实现;

但是官方给出的SPI驱动主要接口为 下面两个,

rt_spi_configure

rt_spi_transfer_message

主要是 rt_spi_transfer_message 可以更加灵活的适应各种SPI设备的通信协议

当然还有其他数据传输接口,但都可以用 自定义传输 rt_spi_transfer_message 来实现,使用方式如下

        struct rt_spi_message msg1, msg2;

        msg1.send_buf   = &w25x_read_id;
msg1.recv_buf = RT_NULL;
msg1.length = ;
msg1.cs_take = ;
msg1.cs_release = ;
msg1.next = &msg2; msg2.send_buf = RT_NULL;
msg2.recv_buf = id;
msg2.length = ;
msg2.cs_take = ;
msg2.cs_release = ;
msg2.next = RT_NULL; rt_spi_transfer_message(spi_dev_w25q, &msg1);
rt_kprintf("use rt_spi_transfer_message() read w25q ID is:%x%x\n", id[], id[]);     // 其等同于下面的操作
     rt_spi_send_then_recv(spi_dev_w25q, &w25x_read_id, , id, );
rt_kprintf("use rt_spi_send_then_recv() read w25q ID is:%x%x\n", id[], id[]);

spi传输的核心实现在 drv_spi.c 中的 spixfer() 函数,实现spi数据的收发

先分析 spi 传输的消息体

struct rt_spi_message
{
const void *send_buf; /** 发送缓冲区指针 */
void *recv_buf; /** 接收缓冲区指针 */
rt_size_t length; /** 发送 / 接收 数据字节数 */
struct rt_spi_message *next; /** 指向继续发送的下一条消息的指针 */ unsigned cs_take : ; /** 片选选中 */
unsigned cs_release : ; /** 片选释放 */
};

这样第一包数据

msg1.cs_take = 1;
msg1.cs_release = 0;

中间包数据

msgx.cs_take = 0;
msgx.cs_release = 0;

最后一包的数据使用

msgn.cs_take = 0;
msgn.cs_release = 1;

同时应该指导 SPI 总线的工作原理,其在发送数据的同时也在接收数据,即发送数据时忽略了接收缓存,而接收数据也必须要发送数据来接收;

spixfer 则调用Hal 库的 传输函数实现数据传输

HAL_SPI_TransmitReceive_DMA  /  HAL_SPI_TransmitReceive

HAL_SPI_Transmit_DMA  /  HAL_SPI_Transmit

HAL_SPI_Receive_DMA  /  HAL_SPI_Receive

这里我们注意 Hal 库的SPI传输支持 轮询、中断即DMA 三种方式,其中轮询支持超时检错,即数据传输完成、传输异常等可以较好发现,而DMA方式则需另外判断标志位处理,当然有出错回调处理;

SPI驱动的具体使用

修改 board 文件夹下的板级 Kconfig 文件,增加对 SPI 的支持

    menuconfig BSP_USING_SPI
bool "Enable SPI BUS"
default n
select RT_USING_SPI
if BSP_USING_SPI
config BSP_USING_SPI1
bool "Enable SPI1 BUS"
default n config BSP_SPI1_TX_USING_DMA
bool "Enable SPI1 TX DMA"
depends on BSP_USING_SPI1
default n config BSP_SPI1_RX_USING_DMA
bool "Enable SPI1 RX DMA"
depends on BSP_USING_SPI1
select BSP_SPI1_TX_USING_DMA
default n config BSP_USING_SPI2
bool "Enable SPI2 BUS"
default n config BSP_SPI2_TX_USING_DMA
bool "Enable SPI2 TX DMA"
depends on BSP_USING_SPI2
default n config BSP_SPI2_RX_USING_DMA
bool "Enable SPI2 RX DMA"
depends on BSP_USING_SPI2
select BSP_SPI2_TX_USING_DMA
default n

进入env 使能 SPI, 另外 CubeMX 使能相应SPI外设 ,具体操作可参考上节

接下来即可使用SPI设备驱动了,当然对应的拓展有 SPI-flash  及其引申出的 块设备文件系统,下次在单独描述。

#if 1
/*
* 程序清单:这是一个 SPI 设备使用例程
* 例程导出了 spi_w25q_sample 命令到控制终端
* 命令调用格式:spi_w25q_sample spi10
* 命令解释:命令第二个参数是要使用的SPI设备名称,为空则使用默认的SPI设备
* 程序功能:通过SPI设备读取 w25q 的 ID 数据
*/
#include "drv_spi.h" int w25q_spi_device_init()
{
__HAL_RCC_GPIOB_CLK_ENABLE();
return rt_hw_spi_device_attach("spi1", "spi10", GPIOB, GPIO_PIN_14);
}
INIT_DEVICE_EXPORT(w25q_spi_device_init); #define W25Q_SPI_DEVICE_NAME "spi10" static void spi_w25q_sample(int argc, char *argv[])
{
struct rt_spi_device *spi_dev_w25q;
char name[RT_NAME_MAX];
rt_uint8_t w25x_read_id = 0x90;
rt_uint8_t id[] = {}; if (argc == )
{
rt_strncpy(name, argv[], RT_NAME_MAX);
}
else
{
rt_strncpy(name, W25Q_SPI_DEVICE_NAME, RT_NAME_MAX);
} // rt_hw_spi_device_attach("spi1", "spi10", GPIOB, GPIO_PIN_14); /* 查找 spi 设备获取设备句柄 */
spi_dev_w25q = (struct rt_spi_device *)rt_device_find(name);
if (!spi_dev_w25q)
{
rt_kprintf("spi sample run failed! can't find %s device!\n", name);
}
else
{
/* config spi */
{
struct rt_spi_configuration cfg;
cfg.data_width = ;
cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB; /* SPI Compatible: Mode 0 and Mode 3 */
cfg.max_hz = * * ; /* 50M */
rt_spi_configure(spi_dev_w25q, &cfg);
} /* 方式1:使用 rt_spi_send_then_recv()发送命令读取ID */
rt_spi_send_then_recv(spi_dev_w25q, &w25x_read_id, , id, );
rt_kprintf("use rt_spi_send_then_recv() read w25q ID is:%x%x\n", id[], id[]); /* 方式2:使用 rt_spi_transfer_message()发送命令读取ID */
struct rt_spi_message msg1, msg2; msg1.send_buf = &w25x_read_id;
msg1.recv_buf = RT_NULL;
msg1.length = ;
msg1.cs_take = ;
msg1.cs_release = ;
msg1.next = &msg2; msg2.send_buf = RT_NULL;
msg2.recv_buf = id;
msg2.length = ;
msg2.cs_take = ;
msg2.cs_release = ;
msg2.next = RT_NULL; rt_spi_transfer_message(spi_dev_w25q, &msg1);
rt_kprintf("use rt_spi_transfer_message() read w25q ID is:%x%x\n", id[], id[]); }
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(spi_w25q_sample, spi en25q sample);
#endif

RT-Thread 设备驱动SPI浅析及使用的更多相关文章

  1. RT thread 设备驱动组件之USART设备

    本文以stm32f4xx平台介绍串口驱动,主要目的是:1.RTT中如何编写中断处理程序:2.如何编写RTT设备驱动接口代码:3.了解串行设备的常见处理机制.所涉及的主要源码文件有:驱动框架文件(usa ...

  2. linux设备驱动模型-浅析-转

    1.  typeof typeof并非ISO C的关键字,而是gcc对C的一个扩展.typeof是一个关键字(类似sizeof),用于获取一个表达式的类型. 举个简单的例子: char tt; typ ...

  3. RT-Thread 设备驱动I2C浅析及使用

    由于 I2C 可以控制多从机的属性,设备驱动模型分为  I2C总线设备(类似与Linux里面的I2C适配器) + I2C从设备: 系统I2C设备驱动主要实现 I2C 总线设备驱动,而具体的I2C 从设 ...

  4. RT-Thread 设备驱动UART浅析

    OS版本:RT-Thread 4.0.0 芯片:STM32F407 RT-Thread的串口驱动框架与Linux相识,分成 I/O设备框架 + 设备底层驱动: 1. serial设备初始化及使用 将配 ...

  5. RT-Thread 设备驱动ADC浅析与改进

    OS版本:RT-Thread 4.0.0 芯片:STM32F407 下面时官方ADC提供的参考访问接口 访问 ADC 设备 应用程序通过 RT-Thread 提供的 ADC 设备管理接口来访问 ADC ...

  6. RT Thread的SPI设备驱动框架的使用以及内部机制分析

    注释:这是19年初的博客,写得很一般,理解不到位也不全面.19年末得空时又重新看了RTThread的SPI和GPIO,这次理解得比较深刻.有时间时再整理上传. -------------------- ...

  7. RT Thread SPI设备 使用

    后记: 之前,我把SPI的片选在Cubemx中配置成了SPI_NSS.现在我给它改为了GPIO_OUTPUT.  同时参考了别人的类似的一个操作无线模块(采用SPI设备驱动)的例子程序(清楚了RTT的 ...

  8. Ardupilot设备驱动 IIC、SPI、USART

    设备代码层次结构 ​ Ardupilot设备驱动代码的层次结构采用 前端实现 和 后端实现 分割,前端库主要供机器代码层调用,后端库主要供前端调用.这里前端可以理解为应用层,后端理解为驱动层,前端调用 ...

  9. Linux设备驱动剖析之SPI(三)

    572至574行,分配内存,注意对象的类型是struct spidev_data,看下它在drivers/spi/spidev.c中的定义: struct spidev_data { dev_t de ...

随机推荐

  1. Hadoop2.6.5高可用集群搭建

    软件环境: linux系统: CentOS6.7 Hadoop版本: 2.6.5 zookeeper版本: 3.4.8 主机配置: 一共m1, m2, m3, m4, m5这五部机, 每部主机的用户名 ...

  2. 解决 C# webbrowser 弹出json下载问题

    把以下内容保存为 .reg ,然后导入注册表,即可解决C# webbrowser 弹出json下载问题,也可通过程序修改. Windows Registry Editor Version 5.00 [ ...

  3. 【转】linux命令

    shell实例手册 0 说明{ 手册制作: 雪松    更新日期: 2015-11-02 欢迎系统运维加入Q群: 198173206  # 加群请回答问题    欢迎运维开发加入Q群: 3655344 ...

  4. CXF-JAX-WS开发(一)入门案例

    一.Web Service 1.定义 W3C定义,Web服务(Web service)应当是一个软件系统,用以支持网络间不同机器的互动操作. 2.作用 多系统间数据通信 二.CXF是什么? CXF是目 ...

  5. AI.框架理论.语义网.语言间距.孤单

    刷个博客,转载自于科学网:AI.框架理论.语义网.语言间距.孤单 一:引言: AI几乎是计算机科学家的梦想,自动化比计算机发展的要早的多.早期的自动化节省了大量人力,激发了人类懒惰的滋长和对自身进化缓 ...

  6. jquery的attr和prop

    注意不同版本的attr和prop,attr适用于自定义dom值,prop适用于带有固有属性

  7. 【sqli-labs】 less38 GET -Stacked Query Injection -String based (GET型堆叠查询字符型注入)

    这个直接用union select就可以 http://192.168.136.128/sqli-labs-master/Less-38/?id=0' union select 1,2,3%23 看一 ...

  8. C# 根据空格数截取

    #region --根据空格数截取 ; ; //循环截取 , }; while (!sr.EndOfStream) { ; i < strTest.Length - ; i++) { ).Tri ...

  9. CorelDRAW关于使用鼠标的5个技巧分享

    CorelDRAW重度依赖者对快捷键和技巧性的操作爱不释手.本文我们介绍使用CorelDRAW关于鼠标操作的5个技巧,这是五种超快,非常有效,特别实用的技巧,如果每天的工作结合这些技巧的使用,效率会大 ...

  10. 【udacity】机器学习-神经网络

    Evernote Export 1.神经网络 神经元 细胞的主体称为细胞体,然后有轴突.突触 他们构建的方式是可以调整的 我们会有一些输入的放电信号视为放电频率或输入的强度 X1​w1​X2​w2​X ...