本文主要介紹RT-thread中的SPI設備驅動,涉及到的文件主要有:驅動框架文件(spi_dev.c,spi_core.c,spi.h),底層硬件驅動文件(spi_hard.c,spi_hard.h)。這里spi_hard.c和spi_hard.h是指利用MCU的硬件SPI接口,而不是通過GPIO口來模擬SPI時序。應用SPI設備驅動時,需要在rtconfig.h中宏定義#define RT_USING_SPI。
一、SPI設備驅動框架
先來看spi.h中的一些數據結構:
** * SPI message structure */ struct rt_spi_message { const void *send_buf; void *recv_buf; rt_size_t length; struct rt_spi_message *next; unsigned cs_take : 1; unsigned cs_release : 1; }; /** * SPI configuration structure */ struct rt_spi_configuration { rt_uint8_t mode; rt_uint8_t data_width; rt_uint16_t reserved; rt_uint32_t max_hz; }; struct rt_spi_ops; struct rt_spi_bus { struct rt_device parent; const struct rt_spi_ops *ops; struct rt_mutex lock; struct rt_spi_device *owner; }; /** * SPI operators */ struct rt_spi_ops { rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *configuration); rt_uint32_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message); };
/** * SPI Virtual BUS, one device must connected to a virtual BUS */ struct rt_spi_device { struct rt_device parent; struct rt_spi_bus *bus; struct rt_spi_configuration config; }; #define SPI_DEVICE(dev) ((struct rt_spi_device *)(dev))
spi_core.c,spi_dev.c這兩個文件位於RTT\components\drivers\spi目錄下,而spi.h頭文件位於RTT\\components\drivers\include\drivers目錄下。可在MKD工程的Drivers組下將上面兩個源文件加進行,並將spi.h頭文件所在目錄添加到工程的include path下。
spi_core.c文件實現了spi的抽象操作,如注冊spi總線(spi_bus),向SPI總線添加設備函數等。注: 這里將MCU的一路spi外設虛擬成spi總線,然后總線上可以掛很多spi設備(spi_device),一個spi_device有一個片選cs。spi總線和spi設備要在RTT中可以生效就必須先向RTT注冊,因此就需要使用上面的注冊SPI總線函數和向SPI總線中添加SPI設備。
spi_core.c還包含了配置SPI函數,發送和接收等通信函數,占用和釋放SPI總線函數及選擇SPI設備函數。這些函數都是抽象出來的,反映出SPI總線上的一些常規操作。真正執行這些操作的過程並不在spi_core.c源文件中,實際上,這些操作信息都是通過注冊SPI總線和向總線添加SPI設備時這些操作集就已經"注冊"下來了,真正操作時是通過注冊信息內的操作函數去實現,也可以說是一種回調操作。spi_core.c中實現的函數主要有:rt_spi_bus_register(); rt_spi_bus_attach_device(); rt_spi_configure(); rt_spi_send_then_send(); rt_spi_send_then_recv(); rt_spi_transfer(); rt_spi_transfer_message(); rt_spi_take_bus(); rt_spi_release_bus(); rt_spi_take(); rt_spi_release()。
而spi_dev.c實現了SPI設備的一些抽象操作,比如讀,寫,打開,關閉,初始化等,當然當MCU操作SPI設備的時候,是需要通過SPI總線與SPI設備進行通信的,既然通信就必然會有SPI通信協議,但是通信協議並不在這里具體,spi_dev.c這里還只是SPI設備的抽象操作而已,它只是簡單地調用spi_core.c源文件中的抽象通信而已,具體實現還是要靠上層通過SPI總線或SPI設備注冊下來的信息而實現的。spi_device.c中實現的函數主要有:_spi_bus_device_read(); _spi_bus_device_write(); _spi_bus_device_control(); rt_spi_bus_device_init();_spidev_device_read();_spidev_device_write();_spidev_device_control();rt_spidev_device_init()。
在確保了spi_core.c,spi_dev.c和spi.h這三個源文件在MDK工程內之后,接着往下走。
二、底層硬件驅動
在spi_hard.c中實現configure和xfer函數(默認沒有使用DMA):
static struct rt_spi_ops stm32_spi_ops = { configure, xfer };
然后,向RT-thread注冊spi總線:
struct stm32_spi_bus { struct rt_spi_bus parent; SPI_TypeDef * SPI; #ifdef SPI_USE_DMA DMA_Stream_TypeDef * DMA_Stream_TX; uint32_t DMA_Channel_TX; DMA_Stream_TypeDef * DMA_Stream_RX; uint32_t DMA_Channel_RX; uint32_t DMA_Channel_TX_FLAG_TC; uint32_t DMA_Channel_RX_FLAG_TC; #endif /* #ifdef SPI_USE_DMA */ }; struct stm32_spi_cs { GPIO_TypeDef * GPIOx; uint16_t GPIO_Pin; };
rt_err_t stm32_spi_register(SPI_TypeDef * SPI, struct stm32_spi_bus * stm32_spi, const char * spi_bus_name) { if(SPI == SPI1) { stm32_spi->SPI = SPI1; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);//84MHZ #ifdef SPI_USE_DMA RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); /* DMA2_Stream0 DMA_Channel_3 : SPI1_RX ; DMA2_Stream2 DMA_Channel_3 : SPI1_RX */ stm32_spi->DMA_Stream_RX = DMA2_Stream0; stm32_spi->DMA_Channel_RX = DMA_Channel_3; stm32_spi->DMA_Channel_RX_FLAG_TC = DMA_FLAG_TCIF0; /* DMA2_Stream3 DMA_Channel_3 : SPI1_TX ; DMA2_Stream5 DMA_Channel_3 : SPI1_TX */ stm32_spi->DMA_Stream_TX = DMA2_Stream3; stm32_spi->DMA_Channel_TX = DMA_Channel_3; stm32_spi->DMA_Channel_TX_FLAG_TC = DMA_FLAG_TCIF3; #endif } else if(SPI == SPI2) { stm32_spi->SPI = SPI2; RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//42MHZ #ifdef SPI_USE_DMA RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); /* DMA1_Stream3 DMA_Channel_0 : SPI2_RX */ stm32_spi->DMA_Stream_RX = DMA1_Stream3; stm32_spi->DMA_Channel_RX = DMA_Channel_0; stm32_spi->DMA_Channel_RX_FLAG_TC = DMA_FLAG_TCIF3; /* DMA1_Stream4 DMA_Channel_0 : SPI2_TX */ stm32_spi->DMA_Stream_TX = DMA1_Stream4; stm32_spi->DMA_Channel_TX = DMA_Channel_0; stm32_spi->DMA_Channel_TX_FLAG_TC = DMA_FLAG_TCIF4; #endif } else if(SPI == SPI3) { stm32_spi->SPI = SPI3; RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);//42MHZ #ifdef SPI_USE_DMA RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); /* DMA1_Stream2 DMA_Channel_0 : SPI3_RX ; DMA1_Stream0 DMA_Channel_0 : SPI3_RX */ stm32_spi->DMA_Stream_RX = DMA1_Stream2; stm32_spi->DMA_Channel_RX = DMA_Channel_0; stm32_spi->DMA_Channel_RX_FLAG_TC = DMA_FLAG_TCIF2; /* DMA1_Stream5 DMA_Channel_0 : SPI3_TX ; DMA1_Stream7 DMA_Channel_0 : SPI3_TX */ stm32_spi->DMA_Stream_TX = DMA1_Stream5; stm32_spi->DMA_Channel_TX = DMA_Channel_0; stm32_spi->DMA_Channel_TX_FLAG_TC = DMA_FLAG_TCIF5; #endif } else { return RT_ENOSYS; } return rt_spi_bus_register(&stm32_spi->parent, spi_bus_name, &stm32_spi_ops); }
最后,進行spi硬件初始化,並掛載spi設備到已注冊的spi總線。
int rt_hw_spi1_init(void) { /* register SPI bus */ static struct stm32_spi_bus stm32_spi; //it must be add static /* SPI1 configure */ { GPIO_InitTypeDef GPIO_InitStructure; /* Enable GPIO Periph clock */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA , ENABLE); GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; /* Configure SPI1 pins */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_Init(GPIOA, &GPIO_InitStructure); } /* SPI1 configuration */ /* register SPI1 to stm32_spi_bus */ stm32_spi_register(SPI1, &stm32_spi, "spi1"); /* attach spi10 */ { static struct rt_spi_device rt_spi_device_10; //it must be add static static struct stm32_spi_cs stm32_spi_cs_10; //it must be add static stm32_spi_cs_10.GPIOx = GPIOE; stm32_spi_cs_10.GPIO_Pin = GPIO_Pin_3; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_Init(GPIOE, &GPIO_InitStructure); GPIO_SetBits(GPIOE, GPIO_Pin_3); rt_spi_bus_attach_device(&rt_spi_device_10, "spi10", "spi1", (void*)&stm32_spi_cs_10);//set spi_device->bus /* config spi */ { struct rt_spi_configuration cfg; cfg.data_width = 8; cfg.mode = RT_SPI_MODE_3 | RT_SPI_MSB; /* SPI Compatible Modes 3 and SPI_FirstBit_MSB in lis302dl datasheet */ //APB2=168M/2=84M, SPI1 = 84/2,4,8,16,32 = 42M, 21M, 10.5M, 5.25M, 2.625M ... cfg.max_hz = 2625000; /* SPI_BaudRatePrescaler_16=84000000/16=5.25MHz. The max_hz of lis302dl is 10MHz in datasheet */ rt_spi_configure(&rt_spi_device_10, &cfg); } /* config spi */ } /* attach spi10 */ return 0; } INIT_BOARD_EXPORT(rt_hw_spi1_init);//rt_hw_spi1_init will be called in rt_components_board_init()
三、SPI設備初始化
這里以lis302dl三軸加速度計為例:
static rt_err_t lis302dl_init(const char * spi_device_name) { rt_uint8_t chip_id, ctrl, temp; spi_device = (struct rt_spi_device *)rt_device_find(spi_device_name); if(spi_device == RT_NULL) { rt_kprintf("\nspi_device %s for lis302dl not found!\n", spi_device_name); return -RT_ENOSYS; } // /* If not use rt_device_write or rt_device_read, then it's no necessary to rt_device_open */ // /* oflag has no meaning for spi device , so set to RT_NULL */ // if(rt_device_open(&spi_device->parent, RT_NULL) != RT_EOK) // { // rt_kprintf("\nspi_device %s for lis302dl opened failed!\n", spi_device_name); // return -RT_EEMPTY; // } LIS302DL_InitTypeDef LIS302DL_InitStruct; LIS302DL_FilterConfigTypeDef LIS302DL_FilterStruct; /* Set configuration of LIS302DL*/ LIS302DL_InitStruct.Output_DataRate = LIS302DL_DATARATE_100; LIS302DL_InitStruct.Power_Mode = LIS302DL_LOWPOWERMODE_ACTIVE; LIS302DL_InitStruct.Full_Scale = LIS302DL_FULLSCALE_2_3; LIS302DL_InitStruct.Self_Test = LIS302DL_SELFTEST_NORMAL; LIS302DL_InitStruct.Axes_Enable = LIS302DL_XYZ_ENABLE; LIS302DL_Init(&LIS302DL_InitStruct); /* MEMS High Pass Filter configuration */ LIS302DL_FilterStruct.HighPassFilter_Data_Selection = LIS302DL_FILTEREDDATASELECTION_OUTPUTREGISTER; LIS302DL_FilterStruct.HighPassFilter_Interrupt = LIS302DL_HIGHPASSFILTERINTERRUPT_1_2; LIS302DL_FilterStruct.HighPassFilter_CutOff_Frequency = LIS302DL_HIGHPASSFILTER_LEVEL_1; LIS302DL_FilterConfig(&LIS302DL_FilterStruct); /* not use internal high pass filter and INT2 */ ctrl=0x04;//enable INT1 Data ready interrupt; interrupt active high; pull-push; LIS302DL_Write(&ctrl, LIS302DL_CTRL_REG3_ADDR, 1); LIS302DL_Read(&temp, LIS302DL_CTRL_REG3_ADDR, 1); if(temp == ctrl) rt_kprintf("the LIS302DL_CTRL_REG3_ADDR(value 0x%02x) verify passed!\n", temp); else rt_kprintf("the LIS302DL_CTRL_REG3_ADDR(value 0x%02x) verify failed!\n", temp); /* Required delay for the MEMS Accelerometre: Turn-on time = 3/Output data Rate = 3/100 = 30ms in datasheet */ //rt_thread_delay(30); extern void stm32_mdelay(rt_uint32_t ms); stm32_mdelay(30); /* power_mode is active */ LIS302DL_Read(&chip_id, LIS302DL_WHO_AM_I_ADDR, 1); rt_kprintf("(chip_id of lis302dl is 0x%02x)", chip_id); return 0; } int rt_lis302dl_init(void) { rt_sem_init(&sem_lis302dl, "lis302dl", 0, RT_IPC_FLAG_FIFO); lis302dl_interrupt_int1(); lis302dl_init("spi10"); return 0; } INIT_APP_EXPORT(rt_lis302dl_init);
注意事項:
1、若需要使用rt_device_read()或rt_device_write()函數,則必須先調用rt_device_open()打開spi設備,保證該設備的ref_count大於0。硬件初始化函數中不需要調用rt_device_open()打開spi總線,因為在rt_spi_bus_attach_device()函數中沒有初始化bus->owner,從而會導致調用_spi_bus_device_read()或_spi_bus_device_write()時“RT_ASSERT(bus->owner != RT_NULL);”斷言語句進入死循環。而 _spidev_device_read()或_spidev_device_write()中斷言語句“RT_ASSERT(device->bus != RT_NULL);”正常通過。
2、在使用SPI設備驅動操作數字芯片的寄存器時,需謹慎使用rt_device_read()和rt_device_write()函數。因為根據spi讀寫時序,spi讀寫一次最少要連續操作2個字節數據(第一個為寄存器地址值,第二個為待讀取或待寫入的字節數據),並且在這2個字節數據之間CS信號不能拉高,而rt_device_read()和rt_device_write()函數僅操作一個字節后,cs信號拉高,導致字節數據不能正常讀取或寫入相應寄存器。所以,一般情況下在SPI工作在全雙工模式時,讀寫數字芯片寄存器的函數中直接使用spi_core.c中的rt_spi_transfer()、rt_spi_send_then_recv()、rt_spi_send_then_send()三個函數,如下所示:
void LIS302DL_Write(rt_uint8_t* pBuffer, rt_uint8_t WriteAddr, rt_uint16_t NumByteToWrite) { /* Configure the MS bit: - When 0, the address will remain unchanged in multiple read/write commands. - When 1, the address will be auto incremented in multiple read/write commands. */ if(NumByteToWrite > 0x01) { WriteAddr |= (rt_uint8_t)MULTIPLEBYTE_CMD; } /* the CS can't pull up between &WriteAddr and pBuffer */ //rt_device_write(&spi_device->parent, RT_NULL, &WriteAddr, 1); //rt_device_write(&spi_device->parent, RT_NULL, pBuffer, NumByteToWrite); rt_spi_send_then_send(spi_device, &WriteAddr, 1, pBuffer, NumByteToWrite);// transfer NumByteToWrite+1 bytes } void LIS302DL_Read(rt_uint8_t* pBuffer, rt_uint8_t ReadAddr, rt_uint16_t NumByteToRead) { /* Configure the MS bit: - When 0, the address will remain unchanged in multiple read/write commands. - When 1, the address will be auto incremented in multiple read/write commands. */ if(NumByteToRead > 0x01) { ReadAddr |= (rt_uint8_t)(READWRITE_CMD | MULTIPLEBYTE_CMD); } else { ReadAddr |= (rt_uint8_t)READWRITE_CMD; } /* the CS can't pull up between &WriteAddr and pBuffer */ //rt_device_write(&spi_device->parent, RT_NULL, &ReadAddr, 1); //rt_device_read(&spi_device->parent, RT_NULL, pBuffer, NumByteToRead); rt_spi_send_then_recv(spi_device, &ReadAddr, 1, pBuffer, NumByteToRead);// transfer NumByteToRead+1 bytes }
3、對於可讀取寄存器值的數字芯片,在寫入字節數據后可通過讀取相同寄存器,判斷讀出的值與寫入的值是否一致,從而判斷寄存器寫操作是否正確,如下:
void LIS302DL_Init(LIS302DL_InitTypeDef *LIS302DL_InitStruct) { rt_uint8_t ctrl = 0x00; /* Configure MEMS: data rate, power mode, full scale, self test and axes */ ctrl = (rt_uint8_t) (LIS302DL_InitStruct->Output_DataRate | LIS302DL_InitStruct->Power_Mode | \ LIS302DL_InitStruct->Full_Scale | LIS302DL_InitStruct->Self_Test | \ LIS302DL_InitStruct->Axes_Enable); /* Write value to MEMS CTRL_REG1 regsister */ LIS302DL_Write(&ctrl, LIS302DL_CTRL_REG1_ADDR, 1); rt_uint8_t temp = 0x00; LIS302DL_Read(&temp, LIS302DL_CTRL_REG1_ADDR, 1); if(temp == ctrl) rt_kprintf("\nthe LIS302DL_CTRL_REG1_ADDR(value 0x%02x) verify passed!\n", temp); else rt_kprintf("\nthe LIS302DL_CTRL_REG1_ADDR(value 0x%02x) verify failed!\n", temp); }
