注釋:這是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; }
下面提出我的疑問:
網友提示:
ENV打開了 spix, 就會加載對應的drv 里面的注冊函數。
我的理解:圖YY

繼續找,猜想注冊spi2硬件的時候 肯定 會用到這個包含“spi2”字符串的SPI2_BUS_CONFIG信息。 《==== 接下來的目標轉換為找到這個在哪注冊的。
實測,“spi2”這個字符串一定要和硬件所使用的對應(其他文件內我還會配置硬件SPI2對應的IO口),為什么?
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,再繼續完善本文。