嵌入式开发笔记——CPLD与MCU模拟SPI通信
- 作者:zzssdd2
- E-mail:zzssdd2@foxmail.com
一、需求描述
- MCU需要接收來自CPLD的升級固件數據
- CPLD對MCU只進行發送數據,不接收MCU的數據
- CPLD無法告知數據傳輸的開始和結束,需要MCU自行判斷(CPLD只是數據透傳,不做數據判斷)
- 數據通信速率至少是UART通信的115200波特率
- PCB上MCU與CPLD之間通過3個普通IO引腳連接
二、功能分析
-
MCU與CPLD之間有3根線,那么可以選擇UART通信或者SPI通信方式。
-
由于CPLD無法通知MCU數據傳輸的開始與結束,MCU需要自行判別,那么MCU可以通過中斷方式來檢測數據傳輸的開始,通過超時來檢測數據傳輸的結束。
-
UART與SPI的區別在于前者是異步通信后者是同步通信方式,不論是SPI還是UART方式都需要MCU通過IO模擬方式軟件實現。使用UART傳輸如果收發雙方產生的波特率存在偏差則會導致數據傳輸出錯,而同步傳輸方式有時鐘信號的約束,相比異步傳輸方式數據準確率會更高。如果使用軟件模擬UART,需要使用定時器作為波特率發生器。如果波特率比較高,那么定時器中斷頻率就需要更高,這樣會影響整個MCU系統的實時性。綜合考慮后選擇SPI方式。
-
CPLD對MCU只發送數據,那么MCU只需要作為SPI的從機即可,三個IO分配為SPI的CS、CLK、DAT引腳。
-
由于CS是低電平有效,那么將CS引腳配置為中斷輸入方式,當CS中斷觸發后開始數據接收處理。因為CPLD也不知道數據傳輸什么時候結束,所以無法通過將CS置高電平來告訴數據傳輸的結束,那么CS置高電平只能表明一個字節傳輸結束。MCU可以通過超時方式來判斷一包數據的結束,類似于串口的空閑中斷方式。
-
SPI數據接收在外部中斷中操作。將CLK引腳配置為外部中斷的上升沿觸發,CS有效的情況下CLK中斷觸發后進行數據接收。
-
SPI空閑中斷采用100us周期定時器判斷。為了MCU系統的實時性,只有CS中斷觸發后才會開啟定時器,超時判斷完成后關閉定時器。
-
CPLD向MCU發送一字節的時序圖如下(速率:200KBit/s):
三、軟件實現
GPIO的配置:無數據CLK為低電平,CS低有效。CS上升沿、下降沿都會觸發中斷,判斷1字節傳輸的起始與結束;CLK上升沿觸發中斷,數據在CLK上升沿采樣
/* *********************************************************************************************** * 函 數: BSP_CPLD_GPIO_Init * 描 述: 配置CPLD的SPI通信引腳 * 輸 入: 無 * 輸 出: 無 *********************************************************************************************** */ void BSP_CPLD_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStruct = {0};/* GPIO Ports Clock Enable */CPLD_PIN_CLK_ENABLE();/*Configure GPIO pin : PtPin */GPIO_InitStruct.Pin = CPLD_SPI_CSN_PIN;GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;GPIO_InitStruct.Pull = GPIO_PULLUP;HAL_GPIO_Init(CPLD_SPI_CSN_PORT, &GPIO_InitStruct);GPIO_InitStruct.Pin = CPLD_SPI_SCK_PIN;GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;GPIO_InitStruct.Pull = GPIO_PULLDOWN;HAL_GPIO_Init(CPLD_SPI_SCK_PORT, &GPIO_InitStruct);GPIO_InitStruct.Pin = CPLD_SPI_DAT_PIN;GPIO_InitStruct.Pull = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(CPLD_SPI_DAT_PORT, &GPIO_InitStruct); }CPLD_SPI_CS外部中斷函數:用于使能數據接收、空閑檢測
/* *********************************************************************************************** * 函 數: CPLD_CS_EXTI_IRQHandler * 描 述: CPLD_SPI_CS中斷函數 * 輸 入: 無 * 輸 出: 無 *********************************************************************************************** */ void CPLD_CS_EXTI_IRQHandler(void) {if (__HAL_GPIO_EXTI_GET_IT(CPLD_SPI_CSN_PIN)){if (READ_BIT(CPLD_SPI_CSN_PORT->IDR, CPLD_SPI_CSN_PIN)){/* CS高失能SPI數據接收 */CLEAR_BIT(g_tCpldSpi.ucState, CPLD_FLAG_CSN);}else{/* CS低使能SPI數據接收 */s_tCpldSpi.ucByte = 0;s_tCpldSpi.ucBitCount = 0;SET_BIT(g_tCpldSpi.ucState, CPLD_FLAG_CSN);/* 開啟空閑檢測 */if (0 == s_tCpldSpi.ucIdleCheck){s_tCpldSpi.ucIdleCheck = 1;HAL_TIM_Base_Start_IT(&Tim7Handle);}}__HAL_GPIO_EXTI_CLEAR_IT(CPLD_SPI_CSN_PIN);} }CPLD_SPI_SCK外部中斷函數:用于SPI數據的接收
/* *********************************************************************************************** * 函 數: CPLD_SCK_EXTI_IRQHandler * 描 述: CPLD_SPI_SCK中斷函數 * 輸 入: 無 * 輸 出: 無 *********************************************************************************************** */ void CPLD_SCK_EXTI_IRQHandler(void) {if (__HAL_GPIO_EXTI_GET_IT(CPLD_SPI_SCK_PIN)){/* CSN有效則進行數據接收 */if (READ_BIT(g_tCpldSpi.ucState, CPLD_FLAG_CSN)){if (READ_BIT(CPLD_SPI_DAT_PORT->IDR, CPLD_SPI_DAT_PIN)){s_tCpldSpi.ucByte |= (0x80 >> s_tCpldSpi.ucBitCount);}else{s_tCpldSpi.ucByte &= ~(0x80 >> s_tCpldSpi.ucBitCount);}/* 收滿一字節后存向接收FIFO */ if (++s_tCpldSpi.ucBitCount > 7){g_tCpldSpi.ucaRxBuf[g_tCpldSpi.usRxWrite] = s_tCpldSpi.ucByte;if (++g_tCpldSpi.usRxWrite >= 1024){g_tCpldSpi.usRxWrite = 0;}if (g_tCpldSpi.usRxCount < CPLD_SPI_RX_BUF_LEN){g_tCpldSpi.usRxCount++;}/* SPI收到新數據,設置一個標記,供應用程序查詢 */SET_BIT(g_tCpldSpi.ucState, CPLD_FLAG_RXNE);}}__HAL_GPIO_EXTI_CLEAR_IT(CPLD_SPI_SCK_PIN);} }定時器中斷函數:判斷CPLD_SPI空閑中斷的發生
/* *********************************************************************************************** * 函 數: TIM7_IRQHandler * 描 述: 定時器7中斷函數,100us中斷周期 * 輸 入: 無 * 輸 出: 無 *********************************************************************************************** */ void TIM7_IRQHandler(void) { static uint16_t t100us_cnt = 0;if (__HAL_TIM_GET_FLAG(&Tim7Handle, TIM_FLAG_UPDATE) &&__HAL_TIM_GET_IT_SOURCE(&Tim7Handle, TIM_IT_UPDATE)){/* CPLD-SPI空閑檢測,1ms */if (READ_BIT(g_tCpldSpi.ucState, CPLD_FLAG_CSN)){t100us_cnt = 0;}else{t100us_cnt++;}if (t100us_cnt > 10){/* SPI收到一幀數據,設置一個標記,供應用程序查詢 */SET_BIT(g_tCpldSpi.ucState, CPLD_FLAG_IDLE); #if ENABLE_RTOStx_event_flags_set(&tx_event_flags, TX_EVENT_CPLD_SPI_IDLE, TX_OR); #endifs_tCpldSpi.ucIdleCheck = 0;HAL_TIM_Base_Stop_IT(&Tim7Handle);}__HAL_TIM_CLEAR_IT(&Tim7Handle, TIM_IT_UPDATE);} }四、功能驗證
經多次發送固件數據驗證,MCU均能正常接收數據,并且沒有出現數據錯誤的情況,可用于該項目。
五、拓展
該方法也可以用于實現模擬UART功能,僅提供思路,未經過驗證(以115200,8-N-1為例)。
- 將UART的接收引腳配置為上拉模式、下降沿觸發中斷(判斷起始位)。
- 中斷第一次觸發后表明收到起始位,收到起始位后打開定時器中斷(如果波特率為115200,那么中斷周期應小于8.6us,如果每Bit數據需要多次采樣,則需要更短的中斷周期)。
- 每中斷一次判斷一次數據引腳,數據收滿10Bit后判斷是否為停止位,若數據接收正確則存入接收FIFO。
- 在收到起始位后開始計時,1ms內沒有再次收到起始位則認為收到一幀數據,產生軟空閑中斷,然后關閉定時器。
為上拉模式、下降沿觸發中斷(判斷起始位)。
- 中斷第一次觸發后表明收到起始位,收到起始位后打開定時器中斷(如果波特率為115200,那么中斷周期應小于8.6us,如果每Bit數據需要多次采樣,則需要更短的中斷周期)。
- 每中斷一次判斷一次數據引腳,數據收滿10Bit后判斷是否為停止位,若數據接收正確則存入接收FIFO。
- 在收到起始位后開始計時,1ms內沒有再次收到起始位則認為收到一幀數據,產生軟空閑中斷,然后關閉定時器。
使用該UART方式優勢在于比SPI方式使用更少的引腳,只需要1個IO即可完成通信。缺點在于如果要求通信速率高或需要多次采樣,那么產生波特率的定時器中斷頻率高,如果被其他更高優先級中斷打斷可能造成波特率不準,數據錯誤。還有就是UART方式在數據通信速率上沒有SPI有優勢。不到萬不得已不建議使用軟件UART方式。
總結
以上是生活随笔為你收集整理的嵌入式开发笔记——CPLD与MCU模拟SPI通信的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 标准清洗槽中的质量参数的监控方法
- 下一篇: 51单片机初学1-51单片机介绍