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

摘要:
注:这是一篇19世纪初的博客,内容很笼统,没有完全理解。19年底,当我有空的时候,我再次查看了RTThreadSPI和GPIO。这一次,我有了深刻的理解。有时间组织并上传-------------------------------------------------------------------------------------------------------------------------

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

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

使用SPI设备驱动框架操作max32865读取PT100的例子程序:

#include "board.h" 
#include "drv_spi.h"
#include "max31865.h"

#define TempModule_SPI_BUS_NAME "spi2" // 对应硬件
#define TempModule_DEVICE_NAME "spi20" // 这个名字无所谓 不需要对应硬件

#define CS_PIN 28

static struct stm32_hw_spi_cs spi_cs;

static struct TempModule_device_ TempModule_device;


int rt_hw_TempModule_Config(void)
{
rt_err_t res;

struct rt_spi_device * rt_spi_device;

rt_pin_mode(CS_PIN, PIN_MODE_OUTPUT);

spi_cs.GPIOx = GPIOB;
spi_cs.GPIO_Pin = GPIO_PIN_12;

// 这个要根据SPI设备名字 来 查找 设备 功能1: 把spi20挂到spi2上
res = rt_hw_spi_device_attach(TempModule_SPI_BUS_NAME, TempModule_DEVICE_NAME, spi_cs.GPIOx, spi_cs.GPIO_Pin);
if( res == RT_EOK )
{
rt_kprintf("
 rt_hw_spi_device_attach(), OK! 
");
}

rt_spi_device = (struct rt_spi_device *)rt_device_find(TempModule_DEVICE_NAME);

TempModule_device.Handle_TempModule_spibus = rt_spi_device;

if( rt_spi_device == RT_EOK )
{
rt_kprintf("
 rt_device_find OK! 
");
}

/* config spi */
{
struct rt_spi_configuration cfg;
cfg.data_width = 8;
cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_1 | RT_SPI_MSB;
cfg.max_hz = 1 * 30 *1000; /* SPI max 42MHz,ssd1351 4-wire spi */

res = rt_spi_configure(rt_spi_device, &cfg);

if( res == RT_EOK )
{
rt_kprintf("
 rt_spi_configure(), OK! 
");
}
}

return RT_EOK;
}


static int rt_hw_TempModule_init(void)
{
rt_hw_TempModule_Config();

// IO 方向
// rt_pin_mode(TempModuleNCS_PIN, PIN_MODE_OUTPUT); 

// 中断

// IO初值设置
TempModule_NCS_H();
rt_kprintf("rt_hw_TempModule_init 
");    
return 0;
}
INIT_PREV_EXPORT(rt_hw_TempModule_init);    /* 组件自动初始化 */

 

u8 TempModuleByte( u8 Sdata)
{
u8 Rdata = 0;
rt_enter_critical();
//    Sdata = 0x55;
// rt_spi_send_then_recv第一个形参:struct rt_spi_device *device;
// rt_spi_send_then_recv( TempModule_device.Handle_TempModule_spibus, &Sdata, (rt_size_t)1, &Rdata, (rt_size_t)1);
//Rdata = rt_spi_send(TempModule_device.Handle_TempModule_spibus, &Sdata, 1);

rt_spi_transfer(TempModule_device.Handle_TempModule_spibus, &Sdata, &Rdata, 1);

rt_exit_critical();
return Rdata;
}


void TempModuleWrite(u16 WrPara)
{
u8 tmp[2] = {0};

tmp[0]= WrPara>>8;
tmp[1]= WrPara&0xFF;

//    TempModuleByte(WrPara>>8); 
//    TempModuleByte(WrPara&0xFF);

rt_spi_send(TempModule_device.Handle_TempModule_spibus, tmp, 2);
}


u8 TempModuleRead(u8 adr)
{
u8 tmp;

// TempModuleByte(adr); 
// tmp = TempModuleByte(0xFF);

tmp = rt_spi_sendrecv8(TempModule_device.Handle_TempModule_spibus, adr); 

return tmp;
}


float Get_tempture(void)
{
float temps;
uint16_t dtemp[2];
uint16_t data_temp;
dtemp[0]=TempModuleRead(0x1); 
//    rt_kprintf("dtemp[0] = %d 
",dtemp[0]);    

dtemp[1]=TempModuleRead(0x2); 
//    rt_kprintf("dtemp[1] = %d 
",dtemp[1]);    

data_temp=(dtemp[0]<<7)+(dtemp[1]>>1);//Get 15Bit DATA;
temps=data_temp;
temps=(temps*402)/32768;//Here is the rtd R value;
temps=(temps-100)/0.385055;//A gruad
return temps;
}

下面提出我的疑问:

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

 描述如下: SPI总线  "spiX"(X是数字1或2这样)  这个必须对应实际的硬件 。 请解释下为什么  。想看看,对于硬件的spi2是不是在软件里就是注册了 “spi2”这个字符串作为其名字。
 我故意把这个字符串取名为“spi1”  板子硬件是spi2,实验结果不可行。 我的意思是RTT在内部关联了,我想找出关联的地方 。
 

 网友提示:

 ENV打开了 spix, 就会加载对应的drv 里面的注册函数。

 我的理解:图YY

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

 找到了
 
 体会和感悟: 通过定义宏条件预编译,来确实编译某段代码。这段代码,可以是某硬件的初始化代码。

 继续找,猜想注册spi2硬件的时候 肯定 会用到这个包含“spi2”字符串的SPI2_BUS_CONFIG信息。  《====    接下来的目标转换为找到这个在哪注册的。

实测,“spi2”这个字符串一定要和硬件所使用的对应(其他文件内我还会配置硬件SPI2对应的IO口),为什么?

 明白了 。
注册的时候通过find函数(内含strcmp比对函数),通过我们的函数名去找RTT内部配置好的信息(RTT内部的信息已经把“spi1”与hspi1挂钩了)。
{
这里有两种玩法:一是我以为用户给出“spi2”字符串,然后RTT内部去解析,然后再去配置单片机的SPI2而不是SPI1.
                          二是RTT内部的配置各种硬件的代码早已经写好,只能用户打开一个宏定义开关而已,我们给出“spi2”,RTT自己也有一套包含“spi1”“spi2”、“spi3”这些字符串信息的配置信息,RTT只要判断用户想要的是哪个就可以了。
} 《==这里的截图可以看出,RTT显然是第二种思路。

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

spi_config[]配置信息容器:

这个是个核心,如果一个SPIx的宏都没打开,那么这个数组的长度就是0. 待初始化的SPI总线的个数就是0. 

可以通过数组的长度来计算需要初始化的SPI总线(spi1、spi2)的个数。

上述过程是由RTT的组件自动初始化技术:INIT_BOARD_EXPORT(负责硬件初始化的函数名); 完成。

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

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

 

下图是 -- 图X:

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

针对具体硬件SPI总线的抽象类也含有一份配置信息,可以用来存储用户的配置信息。

 

到这里,已经基本解决了上述的一个疑惑:针对硬件的spi2,是不是在软件里就是使用了 “spi2”这个字符串作为其名字? 答案是:是的。

到了这里,也就慢慢拓展开了SPI设备驱动框架的玩法。

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

使用组件自动初始化技术调用 rt_hw_spi_bus_init()函数。

// 关于组件自动初始化技术,参考另外的博文。

https://mp.weixin.qq.com/s?__biz=MzAwMDUwNDgxOA==&mid=2652663356&idx=1&sn=779762953029c0e0946c22ef2bb0b754&chksm=810f28a1b678a1b747520ba3ee47c9ed2e8ccb89ac27075e2d069237c13974aa43537bff4fba&mpshare=1&scene=1&srcid=0111Ys4k5rkBto22dLokVT5A&pass_ticket=bGNWMdGEbb0307Tm%2Ba%2FzAKZjWKsImCYqUlDUYPZYkLgU061qPsHFESXlJj%2Fyx3VM#rd

知道了组件自动初始化,我们来看一下这个spi总线初始化的函数干了啥?

static int rt_hw_spi_bus_init(void)
{
rt_err_t result;
for (int i = 0; i < sizeof(spi_config) / sizeof(spi_config[0]); i++)
{
spi_bus_obj[i].config = &spi_config[i];                    //  保存用户的配置信息,到单片机外设层面的硬件抽象层。用户在rt_config.h用使用宏开关打开配置信息。
spi_bus_obj[i].spi_bus.parent.user_data = &spi_config[i];
spi_bus_obj[i].handle.Instance = spi_config[i].Instance;

if (spi_bus_obj[i].spi_dma_flag & SPI_USING_RX_DMA_FLAG)
{
/* Configure the DMA handler for Transmission process */
spi_bus_obj[i].dma.handle_rx.Instance = spi_config[i].dma_rx->Instance;
#if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32G0)
spi_bus_obj[i].dma.handle_rx.Init.Request = spi_config[i].dma_rx->request;
#endif
spi_bus_obj[i].dma.handle_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
spi_bus_obj[i].dma.handle_rx.Init.PeriphInc = DMA_PINC_DISABLE;
spi_bus_obj[i].dma.handle_rx.Init.MemInc = DMA_MINC_ENABLE;
。。。
}。。。

}result = rt_spi_bus_register(&spi_bus_obj[i].spi_bus, spi_config[i].bus_name, &stm_spi_ops);
}return result;
}

 对于rt_hw_spi_bus_init()函数,上文我们分析了其内部的一个重要部分,如图X所示。

我们再来看其内部的另一个重要部分,rt_spi_bus_register这个注册函数。

对于rt_spi_bus_register有两条分析路线,   

分支一: 最后一个形参,stm_spi_ops ,关注的是这是啥东西,能干嘛。我们要查找stm_spi_ops的定义。          

分支二:rt_spi_bus_register函数本身,关注的是他执行了哪些动作。

RT Thread的SPI设备驱动框架的使用以及内部机制分析第8张   STM32层面的SPI总线的硬件抽象的类

下面,进行分支一的讨论:

static const struct rt_spi_ops  stm_spi_ops =
{
.configure = spi_configure,
.xfer = spixfer,
};

static rt_err_t  spi_configure (struct rt_spi_device *device, struct rt_spi_configuration *configuration)  //  这里的形参们,用品红色来表示
{...

struct stm32_spi *spi_drv = rt_container_of(device->bus, struct stm32_spi, spi_bus);  // 找到针对具体硬件SPI总线的抽象出来的类的对象
spi_drv->cfg = configuration;

return stm32_spi_init(spi_drv, configuration); // 将配置信息填入对应该SPI总线的硬件抽象层的类对象
}

static rt_err_t  stm32_spi_init(struct stm32_spi *spi_drv, struct rt_spi_configuration *cfg)
{
RT_ASSERT(spi_drv != RT_NULL);
RT_ASSERT(cfg != RT_NULL);

SPI_HandleTypeDef *spi_handle = &spi_drv->handle; // 获取该硬件抽象层的类对象的详细信息

if (cfg->mode & RT_SPI_SLAVE)
{spi_handle->Init.Mode = SPI_MODE_SLAVE;}
else{spi_handle->Init.Mode = SPI_MODE_MASTER;}

if (cfg->mode & RT_SPI_3WIRE)

{spi_handle->Init.Direction = SPI_DIRECTION_1LINE;}
else{spi_handle->Init.Direction = SPI_DIRECTION_2LINES;}

... SPI_DATASIZE_8BIT;

... SPI_DATASIZE_16BIT;

... SPI_PHASE_2EDGE;

... SPI_POLARITY_LOW;

... SPI_NSS_SOFT;

... 省...略...不一一列举...

if (HAL_SPI_Init(spi_handle) != HAL_OK) 

// 这里采用其他方法(stm32的HAL库函数),完成了一系列动作:对stm32单片机的底层寄存器的操作配置。达成了最终的目的。
{return RT_EIO;}

if (spi_drv->spi_dma_flag & SPI_USING_TX_DMA_FLAG)
{
HAL_DMA_Init(&spi_drv->dma.handle_tx);

__HAL_LINKDMA(&spi_drv->handle, hdmatx, spi_drv->dma.handle_tx);

/* NVIC configuration for DMA transfer complete interrupt */
HAL_NVIC_SetPriority(spi_drv->config->dma_tx->dma_irq, 0, 1);
HAL_NVIC_EnableIRQ(spi_drv->config->dma_tx->dma_irq);
}

__HAL_SPI_ENABLE(spi_handle);  // 使能相应的SPI

//#define __HAL_SPI_ENABLE(__HANDLE__)  SET_BIT((__HANDLE__)->Instance->CR1, SPI_CR1_SPE)  这是在操作底层硬件:单片机的底层寄存器

return RT_EOK;
}

通过分支一的讨论,我们知道了,stm_spi_ops具备配置单片机SPIx底层寄存器的全部能力,但是需要我们在外部给入参数,看上文的 品红色 示意处。

下面,进行分支二 rt_spi_bus_register 的讨论:

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

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

该设备注册函数:

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

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

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

《==== 具体设备对应的object->name,是在哪被赋值的  ______?????_________

需要仿真跟一下,仿真也是有一定技巧和难度的,需要根据设备对象容器内的链表节点查找其他的对象。》

 RT Thread的SPI设备驱动框架的使用以及内部机制分析第14张 如果找不到,就新建一个设备挂上去。
 

未完待续。。。

这里面涉及的知识很美,很诱人。有思想,有抽象。

 硬件抽象真美 

 

时间限制,有机会以后再接触RTT,再继续完善本文。

 

免责声明:文章转载自《RT Thread的SPI设备驱动框架的使用以及内部机制分析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇云原生时代消息中间件的演进路线wcf通道Channel下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Oracle Data Guard

DG是 Oracle Data Guard的简称。也就是Oracle11g的 数据卫士。 由于在工作中 Oracle和 SQL SERVER2008 同时都需要维护管理。给我的感觉这里的 DG 其实和 Sql Server 2008的镜像实现的功能是一样的(当然更强大一些)。DG 中的物理备用库 尤其和 Sql Server 2008 的镜像实现的功能是一...

CentOS7 初始化配置

一、在安装的时候配置网卡名称的参数 1. 选择“Install Centos 7” 2. 按Tab,打开kernel启动选项后,增加 net.ifnames=0 biosdevname=0 二、最小化安装完成之后必备安装软件 # 添加epel源,安装基础软件,设置主机名rpm -ivh http://mirrors.aliyun.com/epel/epe...

Debug与Release版本的区别

  Debug 和 Release 并没有本质的区别,他们只是VC预定义提供的两组编译选项的集合,编译器只是按照预定的选项行动。如果我们愿意,我们完全可以把Debug和Release的行为完全颠倒过来。当然也可以提供其他的模式,例如自己定义一组编译选项,然后命名为MY_ABC等。习惯上,我们仍然更愿意使用VC已经定义好的名称。    Debug版本包括调试...

Socket编程 (一)

<<Linux 网络编程>>摘要           注意: 其中的大部分成员是网络字节序(大端字节序); 编程: 服务器先要做的事情: socket() 初始化Socket bind() 绑定本地端口 listen() 监听端口 accept() 响应客户端请求 一个例子   客户端(Windows 系统, Java...

Android开发 -- Bootloader

本文转载自:http://blog.csdn.net/jmq_0000/article/details/7378348 LK是什么            LK 是 Little Kernel 它是 appsbl (Applications ARM Boot Loader)流程代码  ,little kernel 是小内核小操作系统。           ...

STM32 ADC基础与多通道采样

12位ADC是一种逐次逼近型模拟数字数字转换器。它有多达18个通道,可测量16个外部和2个内部信号源。ADC的输入时钟不得超过14MHZ,它是由PCLK2经分频产生。如果被ADC转换的模拟电压低于低阀值或高于高阀值,AWD模拟看门狗状态位被设置。 ADC通常要与DMA一起使用 这里只是简单的用库配置ADC 不断扫描来实现ADC的应用。 配置DMA: voi...