RT Thread的SPI設備驅動框架的使用以及內部機制分析


注釋:這是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("\n rt_hw_spi_device_attach(), OK! \r\n");
}

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("\n rt_device_find OK! \r\n");
}

/* 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("\n rt_spi_configure(), OK! \r\n");
}
}

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 \r\n");    
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 \r\n",dtemp[0]);    

dtemp[1]=TempModuleRead(0x2); 
//    rt_kprintf("dtemp[1] = %d \r\n",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;
}

 

下面提出我的疑問:

 描述如下: SPI總線  "spiX"(X是數字1或2這樣)  這個必須對應實際的硬件 。  請解釋下為什么  。想看看,對於硬件的spi2是不是在軟件里就是注冊了 “spi2”這個字符串作為其名字。
 我故意把這個字符串取名為“spi1”  板子硬件是spi2,實驗結果不可行。  我的意思是RTT在內部關聯了,我想找出關聯的地方 。
 

 網友提示:

 ENV打開了 spix, 就會加載對應的drv 里面的注冊函數。

 我的理解:圖YY

 找到了
 
 體會和感悟: 通過定義宏條件預編譯,來確實編譯某段代碼。這段代碼,可以是某硬件的初始化代碼。

 繼續找,猜想注冊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顯然是第二種思路。

 

spi_config[]配置信息容器:

這個是個核心,如果一個SPIx的宏都沒打開,那么這個數組的長度就是0. 待初始化的SPI總線的個數就是0. 

可以通過數組的長度來計算需要初始化的SPI總線(spi1、spi2)的個數。

上述過程是由RTT的組件自動初始化技術:INIT_BOARD_EXPORT(負責硬件初始化的函數名); 完成。

                   

 

下圖是 -- 圖X:

針對具體硬件SPI總線的抽象類也含有一份配置信息,可以用來存儲用戶的配置信息。

 

到這里,已經基本解決了上述的一個疑惑:針對硬件的spi2,是不是在軟件里就是使用了 “spi2”這個字符串作為其名字? 答案是:是的。

到了這里,也就慢慢拓展開了SPI設備驅動框架的玩法。

使用組件自動初始化技術調用 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函數本身,關注的是他執行了哪些動作。

   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 的討論:

該設備注冊函數:

 

 

《==== 具體設備對應的object->name,是在哪被賦值的  ______?????_________

需要仿真跟一下,仿真也是有一定技巧和難度的,需要根據設備對象容器內的鏈表節點查找其他的對象。》

   如果找不到,就新建一個設備掛上去。
 

 

未完待續。。。

這里面涉及的知識很美,很誘人。有思想,有抽象。

 硬件抽象真美 

 

時間限制,有機會以后再接觸RTT,再繼續完善本文。

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM