1. 程式人生 > 其它 >【STM32H7教程】第94章 STM32H7的SPI匯流排應用之雙機通訊(DMA方式)

【STM32H7教程】第94章 STM32H7的SPI匯流排應用之雙機通訊(DMA方式)

完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第94章       STM32H7的SPI匯流排應用之雙機通訊(DMA方式)

本章節為大家講解SPI DMA方式雙機通訊。

94.1 初學者重要提示

94.2 SPI DMA主從機硬體接線

94.3 SPI DMA主機程式設計

94.4 SPI DMA從機程式設計

94.5 SPI DMA主從機使用注意事項

94.6 SPI DMA主從機驅動移植和使用

94.7 實驗例程設計框架

94.8 實驗例程說明(MDK)

94.9 實驗例程說明(IAR)

94.10 總結

 

 

94.1 初學者重要提示

  1.   學習本章節前,務必優先學習第72章SPI基礎和第73章SPI Flash的DMA玩法方式。本章實現的SPI DMA通訊方式的主機和從機,跟SPI DMA方式驅動SPI Flash是類似的。
  2.   本章是採用的SPI DMA全雙工通訊方式。
  3.   大家根據自己接線的穩定性,可以適當調節SPI主機和從機的時鐘速度,其中從機的時鐘速度是可以高於主機速度的,這樣通訊的容錯性更好些。

94.2 SPI DMA主從機硬體接線

接線方式如下,使用的兩塊V7板子,一塊板子做主機,一塊板子做從機。

 

對應的引腳資訊如下:

實際專案中使用,推薦大家務必比將硬體片選引腳NSS接上,實現全程硬體控制收發。如果大家不使用硬體片選,而使用下面的方式:

這種方式有個比較明顯的缺點,主從機上電次序不同,很容易造成從機CLK識別錯誤,即高低電平變化導致資料傳輸錯位。改成加入硬體SPI片選NSS引腳後,完美解決了這個問題

94.3 SPI DMA主機程式設計

SPI DMA主機程式實現和本教程72的SPI DMA配置是一樣的,只是多了SPI硬體片選引腳NSS配置。

94.3.1 第1步:SPI匯流排配置

SPI匯流排配置通過如下兩個函式實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_InitSPIBus
*    功能說明: 配置SPI匯流排。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/ void bsp_InitSPIBus(void) { g_spi_busy = 0; bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_16, SPI_PHASE_1EDGE, SPI_POLARITY_LOW); } /* ********************************************************************************************************* * 函 數 名: bsp_InitSPIParam * 功能說明: 配置SPI匯流排引數,時鐘分頻,時鐘相位和時鐘極性。 * 形 參: _BaudRatePrescaler SPI匯流排時鐘分頻設定,支援的引數如下: * SPI_BAUDRATEPRESCALER_2 2分頻 * SPI_BAUDRATEPRESCALER_4 4分頻 * SPI_BAUDRATEPRESCALER_8 8分頻 * SPI_BAUDRATEPRESCALER_16 16分頻 * SPI_BAUDRATEPRESCALER_32 32分頻 * SPI_BAUDRATEPRESCALER_64 64分頻 * SPI_BAUDRATEPRESCALER_128 128分頻 * SPI_BAUDRATEPRESCALER_256 256分頻 * * _CLKPhase 時鐘相位,支援的引數如下: * SPI_PHASE_1EDGE SCK引腳的第1個邊沿捕獲傳輸的第1個數據 * SPI_PHASE_2EDGE SCK引腳的第2個邊沿捕獲傳輸的第1個數據 * * _CLKPolarity 時鐘極性,支援的引數如下: * SPI_POLARITY_LOW SCK引腳在空閒狀態處於低電平 * SPI_POLARITY_HIGH SCK引腳在空閒狀態處於高電平 * * 返 回 值: 無 ********************************************************************************************************* */ void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity) { /* 提高執行效率,只有在SPI硬體引數發生變化時,才執行HAL_Init */ if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity) { return; } s_BaudRatePrescaler = _BaudRatePrescaler; s_CLKPhase = _CLKPhase; s_CLKPolarity = _CLKPolarity; /* 設定SPI引數 */ hspi.Instance = SPIx; /* 例化SPI */ hspi.Init.BaudRatePrescaler = _BaudRatePrescaler; /* 設定波特率 */ hspi.Init.Direction = SPI_DIRECTION_2LINES; /* 全雙工 */ hspi.Init.CLKPhase = _CLKPhase; /* 配置時鐘相位 */ hspi.Init.CLKPolarity = _CLKPolarity; /* 配置時鐘極性 */ hspi.Init.DataSize = SPI_DATASIZE_8BIT; /* 設定資料寬度 */ hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 資料傳輸先傳高位 */ hspi.Init.TIMode = SPI_TIMODE_DISABLE; /* 禁止TI模式 */ hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */ hspi.Init.CRCPolynomial = 7; /* 禁止CRC後,此位無效 */ hspi.Init.CRCLength = SPI_CRC_LENGTH_8BIT; /* 禁止CRC後,此位無效 */ hspi.Init.NSS = SPI_NSS_SOFT; /* 使用軟體方式管理片選引腳 */ hspi.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA; /* 設定FIFO大小是一個數據項 */ hspi.Init.NSSPMode = SPI_NSS_PULSE_DISABLE; /* 禁止脈衝輸出 */ hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI後,SPI相關引腳保持當前狀態 */ hspi.Init.Mode = SPI_MODE_MASTER; /* SPI工作在主控模式 */ /* 設定SPI引數 */ hspi.Instance = SPIx; /* 例化SPI */ hspi.Init.BaudRatePrescaler = _BaudRatePrescaler; /* 設定波特率 */ hspi.Init.Direction = SPI_DIRECTION_2LINES; /* 全雙工 */ hspi.Init.CLKPhase = _CLKPhase; /* 配置時鐘相位 */ hspi.Init.CLKPolarity = _CLKPolarity; /* 配置時鐘極性 */ hspi.Init.DataSize = SPI_DATASIZE_8BIT; /* 設定資料寬度 */ hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 資料傳輸先傳高位 */ hspi.Init.TIMode = SPI_TIMODE_DISABLE; /* 禁止TI模式 */ hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */ hspi.Init.CRCPolynomial = 7; /* 禁止CRC後,此位無效 */ hspi.Init.CRCLength = SPI_CRC_LENGTH_8BIT; /* 禁止CRC後,此位無效 */ hspi.Init.FifoThreshold = SPI_FIFO_THRESHOLD_16DATA; /* 設定FIFO大小是一個數據項 */ hspi.Init.NSS = SPI_NSS_HARD_OUTPUT; /* 片選引腳 */ hspi.Init.NSSPMode = SPI_NSS_PULSE_DISABLE; /* 禁止脈衝輸出 */ hspi.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; /* 低電平有效 */ hspi.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE; /* MSS, 插入到NSS有效邊沿和第一個 資料開始之間的額外延遲,單位SPI時鐘週期個數 */ hspi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_10CYCLE; /* MIDI, 兩個連續資料幀之間 插入的最小時間延遲,單位SPI時鐘週期個數 */ hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI後,SPI相關引腳保持當前狀態 */ hspi.Init.Mode = SPI_MODE_MASTER; /* 復位配置 */ if (HAL_SPI_DeInit(&hspi) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } /* 初始化配置 */ if (HAL_SPI_Init(&hspi) != HAL_OK) { Error_Handler(__FILE__, __LINE__); } }

關於這兩個函式有以下四點要做個說明:

  •   函式bsp_InitSPIBus裡面的配置是個初始設定。實際驅動晶片時,會通過函式bsp_InitSPIParam做再配置。
  •   函式bsp_InitSPIParam提供了時鐘分頻,時鐘相位和時鐘極性配置。一般主機和從機此處設定為一樣即可,但推薦
  •   SPI硬體片選NSS設定為SPI_NSS_HARD_OUTPUT。
  •   這裡特別注意主機是hspi.Init.Mode = SPI_MODE_MASTER。

94.3.2 第2步:SPI DMA配置

配置程式碼實現如下,註釋比較詳細:

/*
*********************************************************************************************************
*    函 數 名: bsp_InitSPIParam
*    功能說明: 配置SPI匯流排時鐘,GPIO,中斷,DMA等
*    形    參: SPI_HandleTypeDef 型別指標變數
*    返 回 值: 無
*********************************************************************************************************
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *_hspi)
{
    /* 配置 SPI匯流排GPIO : SCK MOSI MISO */
    {
        GPIO_InitTypeDef  GPIO_InitStruct;
            
        /* SPI和GPIP時鐘 */
        SPIx_SCK_CLK_ENABLE();
        SPIx_MISO_CLK_ENABLE();
        SPIx_MOSI_CLK_ENABLE();
        SPIx_CLK_ENABLE();

        /* SPI SCK */
        GPIO_InitStruct.Pin       = SPIx_SCK_PIN;
        GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull      = GPIO_PULLUP;
        GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
        GPIO_InitStruct.Alternate = SPIx_SCK_AF;
        HAL_GPIO_Init(SPIx_SCK_GPIO, &GPIO_InitStruct);

        /* SPI MISO */
        GPIO_InitStruct.Pin = SPIx_MISO_PIN;
        GPIO_InitStruct.Alternate = SPIx_MISO_AF;
        HAL_GPIO_Init(SPIx_MISO_GPIO, &GPIO_InitStruct);

        /* SPI MOSI */
        GPIO_InitStruct.Pin = SPIx_MOSI_PIN;
        GPIO_InitStruct.Alternate = SPIx_MOSI_AF;
        HAL_GPIO_Init(SPIx_MOSI_GPIO, &GPIO_InitStruct);
        
        /* SPI NSS */
        GPIO_InitStruct.Pin = SPIx_NSS_PIN;
        GPIO_InitStruct.Alternate = SPIx_NSS_AF;
        HAL_GPIO_Init(SPIx_NSS_GPIO, &GPIO_InitStruct);
    }

    /* 配置DMA和NVIC */
    #ifdef USE_SPI_DMA
    {
        /* 使能DMA時鐘 */
        DMAx_CLK_ENABLE();      

        /* SPI DMA傳送配置 */        
        hdma_tx.Instance                = SPIx_TX_DMA_STREAM;    /* 例化使用的DMA資料流 */
        hdma_tx.Init.FIFOMode           = DMA_FIFOMODE_ENABLE;   /* FIFO*/
        hdma_tx.Init.FIFOThreshold      = DMA_FIFO_THRESHOLD_FULL;/* 禁止FIFO此位不起作用,用於設定閥值 */
        hdma_tx.Init.MemBurst            = DMA_MBURST_SINGLE;    /* 禁止FIFO此位不起作用,用於儲存器突發 */
        hdma_tx.Init.PeriphBurst         = DMA_PBURST_SINGLE;     /* 禁止FIFO此位不起作用,用於外設突發 */
        hdma_tx.Init.Request             = SPIx_TX_DMA_REQUEST;     /* 請求型別 */  
        hdma_tx.Init.Direction           = DMA_MEMORY_TO_PERIPH;    /* 傳輸方向是從儲存器到外設 */  
        hdma_tx.Init.PeriphInc           = DMA_PINC_DISABLE;        /* 外設地址自增禁止 */ 
        hdma_tx.Init.MemInc              = DMA_MINC_ENABLE;         /* 儲存器地址自增使能 */  
        hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;     /* 外設資料傳輸位寬選擇位元組,即8bit */ 
        hdma_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;     /* 儲存器資料傳輸位寬選擇位元組,即8bit */    
        hdma_tx.Init.Mode                = DMA_NORMAL;              /* 正常模式 */
        hdma_tx.Init.Priority            = DMA_PRIORITY_LOW;        /* 優先順序低 */

         /* 復位DMA */
        if(HAL_DMA_DeInit(&hdma_tx) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);     
        }
        
         /* 初始化DMA */
        if(HAL_DMA_Init(&hdma_tx) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);     
        }

        /* 關聯DMA控制代碼到SPI */
        __HAL_LINKDMA(_hspi, hdmatx, hdma_tx);    

        /* SPI DMA接收配置 */    
        hdma_rx.Instance                = SPIx_RX_DMA_STREAM;     /* 例化使用的DMA資料流 */
        hdma_rx.Init.FIFOMode           = DMA_FIFOMODE_ENABLE;    /* FIFO*/
        hdma_rx.Init.FIFOThreshold      = DMA_FIFO_THRESHOLD_FULL;/* 禁止FIFO此位不起作用,用於設定閥值 */
        hdma_rx.Init.MemBurst            = DMA_MBURST_SINGLE;    /* 禁止FIFO此位不起作用,用於儲存器突發 */
        hdma_rx.Init.PeriphBurst         = DMA_PBURST_SINGLE;    /* 禁止FIFO此位不起作用,用於外設突發 */
        hdma_rx.Init.Request             = SPIx_RX_DMA_REQUEST;    /* 請求型別 */  
        hdma_rx.Init.Direction           = DMA_PERIPH_TO_MEMORY;   /* 傳輸方向從外設到儲存器 */  
        hdma_rx.Init.PeriphInc           = DMA_PINC_DISABLE;       /* 外設地址自增禁止 */   
        hdma_rx.Init.MemInc              = DMA_MINC_ENABLE;        /* 儲存器地址自增使能 */   
        hdma_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;    /* 外設資料傳輸位寬選擇位元組,即8bit */ 
        hdma_rx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;    /* 儲存器資料傳輸位寬選擇位元組,即8bit */   
        hdma_rx.Init.Mode                = DMA_NORMAL;             /* 正常模式 */
        hdma_rx.Init.Priority            = DMA_PRIORITY_HIGH;      /* 優先順序高 */

         /* 復位DMA */
        if(HAL_DMA_DeInit(&hdma_rx) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);     
        }
        
         /* 初始化DMA */
        if(HAL_DMA_Init(&hdma_rx) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);     
        }

        /* 關聯DMA控制代碼到SPI */
       __HAL_LINKDMA(_hspi, hdmarx, hdma_rx);    

        /* 配置DMA傳送中斷 */
        HAL_NVIC_SetPriority(SPIx_DMA_TX_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(SPIx_DMA_TX_IRQn);
        
        /* 配置DMA接收中斷 */
        HAL_NVIC_SetPriority(SPIx_DMA_RX_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(SPIx_DMA_RX_IRQn);
        
        /* 配置SPI中斷 */
        HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(SPIx_IRQn);
    }
    #endif
    
    #ifdef USE_SPI_INT
        /* 配置SPI中斷優先順序並使能中斷 */
        HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(SPIx_IRQn);
    #endif
}

94.3.3 第3步:SPI DMA傳輸設定和MPU配置

SPI DMA方式主要通過函式bsp_spiTransfer實現資料傳輸(程式碼裡面的查詢和中斷方式請忽略):

/*
*********************************************************************************************************
*                                 選擇DMA,中斷或者查詢方式
*********************************************************************************************************
*/
#define USE_SPI_DMA    /* DMA方式  */
//#define USE_SPI_INT    /* 中斷方式 */
//#define USE_SPI_POLL   /* 查詢方式 */

/* 查詢模式 */
#if defined (USE_SPI_POLL)

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/* 中斷模式 */
#elif defined (USE_SPI_INT)

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/* DMA模式使用的SRAM4 */
#elif defined (USE_SPI_DMA)
    #if defined ( __CC_ARM )    /* IAR *******/
        __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
        __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    #elif defined (__ICCARM__)   /* MDK ********/
        #pragma location = ".RAM_D3"
        uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
        #pragma location = ".RAM_D3"
        uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    #endif
#endif

/*
*********************************************************************************************************
*    函 數 名: bsp_spiTransfer
*    功能說明: 啟動資料傳輸
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_spiTransfer(void)
{
    if (g_spiLen > SPI_BUFFER_SIZE)
    {
        return;
    }
    
    /* DMA方式傳輸 */
#ifdef USE_SPI_DMA
    wTransferState = TRANSFER_WAIT;
    
    if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    while (wTransferState == TRANSFER_WAIT)
    {
        ;
    }
#endif

    /* 中斷方式傳輸 */    
#ifdef USE_SPI_INT
    wTransferState = TRANSFER_WAIT;

    if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    while (wTransferState == TRANSFER_WAIT)
    {
        ;
    }
#endif

    /* 查詢方式傳輸 */    
#ifdef USE_SPI_POLL
    if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)    
    {
        Error_Handler(__FILE__, __LINE__);
    }    
#endif
}

DMA方式要特別注意幾點:

  •   通過本手冊第26章的記憶體塊超方便使用方式,將DMA緩衝定義到SRAM4上。因為本工程是用的DTCM做的主RAM空間,這個空間無法使用通用DMA1和DMA2。
  •   程式這裡SPI DMA方式主控用的是等待傳輸完成,大家根據自己實際應用可以做修改,詳情大家可以看此貼作為拓展:【深入探討】DMA到底能不能起到加速程式執行的作用,DMA死等操作是否合理,多個DMA資料流同時刷是否處理過來https://www.armbbs.cn/forum.php?mod=viewthread&tid=109765
  •   由於程式裡面開啟了資料Cache,會造成DMA和CPU訪問SRAM4資料不一致的問題,特此將SRAM4空間關閉Cache。
    /* 配置SRAM4的MPU屬性為Non-cacheable */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

分散載入設定:

94.3.4 第4步:應用程式碼設計

應用部分的程式碼設計如下:

/*
*********************************************************************************************************
*    函 數 名: DemoSpiMaster
*    功能說明: SPI 主機通訊
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiMaster(void)
{
    uint8_t count = 0;
    uint8_t ucKeyCode;        /* 按鍵程式碼 */
    
    /***************設定SPI Flash片選上拉,防止影響 ***************/
    {
        GPIO_InitTypeDef gpio_init;

        /* 開啟GPIO時鐘 */
        __HAL_RCC_GPIOD_CLK_ENABLE();
        
        gpio_init.Mode = GPIO_MODE_OUTPUT_PP;    
        gpio_init.Pull = GPIO_NOPULL;        
        gpio_init.Speed = GPIO_SPEED_HIGH;      
        gpio_init.Pin = GPIO_PIN_13;    
        HAL_GPIO_Init(GPIOD, &gpio_init);

        GPIOD->BSRR = GPIO_PIN_13;
    }
    
    sfDispMenu();        /* 列印命令提示 */
    
    bsp_StartAutoTimer(0, 100);    /* 啟動1個100ms的自動重灌的定時器 */
    
    while(1)
    {
        bsp_Idle();        /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */
        
        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔100ms 進來一次 */  
            bsp_LedToggle(2);
        }
        
        /* 按鍵濾波和檢測由後臺systick中斷服務程式實現,我們只需要呼叫bsp_GetKey讀取鍵值即可。 */
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下,傳送資料給從機*/
                    g_spiTxBuf[0] = count++;
                    g_spiTxBuf[1] = count++;
                    g_spiTxBuf[2] = count++;
                    g_spiTxBuf[3] = count++;
                    g_spiLen = 4;
                    printf("SPI主機發送資料:%d,%d,%d,%d\r\n",
 g_spiTxBuf[0],g_spiTxBuf[1],g_spiTxBuf[2],g_spiTxBuf[3]);

                    bsp_spiTransfer();
                    printf("SPI主機接收資料:%d,%d,%d,%d\r\n", 
g_spiRxBuf[0],g_spiRxBuf[1],g_spiRxBuf[2],g_spiRxBuf[3]);
                    break;
                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        
        }
    }
}

這部分程式碼比較好理解,大家按下K1按鍵後,會打印發送的資料並列印SPI從機裝置返回的資料。

 

94.4 SPI DMA從機程式設計

SPI DMA從機設計程式如下,與主機不同的是部分配置選項要設定為從機方式。

94.4.1 第1步:SPI匯流排配置

SPI匯流排配置通過如

/*
*********************************************************************************************************
*    函 數 名: bsp_InitSPIBus
*    功能說明: 配置SPI匯流排。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitSPIBus(void)
{    
    g_spi_busy = 0;
    
    bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_16, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
}

/*
*********************************************************************************************************
*    函 數 名: bsp_InitSPIParam
*    功能說明: 配置SPI匯流排引數,時鐘分頻,時鐘相位和時鐘極性。
*    形    參: _BaudRatePrescaler  SPI匯流排時鐘分頻設定,支援的引數如下:
*                                 SPI_BAUDRATEPRESCALER_2    2分頻
*                                 SPI_BAUDRATEPRESCALER_4    4分頻
*                                 SPI_BAUDRATEPRESCALER_8    8分頻
*                                 SPI_BAUDRATEPRESCALER_16   16分頻
*                                 SPI_BAUDRATEPRESCALER_32   32分頻
*                                 SPI_BAUDRATEPRESCALER_64   64分頻
*                                 SPI_BAUDRATEPRESCALER_128  128分頻
*                                 SPI_BAUDRATEPRESCALER_256  256分頻
*                                                        
*             _CLKPhase           時鐘相位,支援的引數如下:
*                                 SPI_PHASE_1EDGE     SCK引腳的第1個邊沿捕獲傳輸的第1個數據
*                                 SPI_PHASE_2EDGE     SCK引腳的第2個邊沿捕獲傳輸的第1個數據
*                                 
*             _CLKPolarity        時鐘極性,支援的引數如下:
*                                 SPI_POLARITY_LOW    SCK引腳在空閒狀態處於低電平
*                                 SPI_POLARITY_HIGH   SCK引腳在空閒狀態處於高電平
*
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
{
    /* 提高執行效率,只有在SPI硬體引數發生變化時,才執行HAL_Init */
    if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
    {        
        return;
    }

    s_BaudRatePrescaler = _BaudRatePrescaler;    
    s_CLKPhase = _CLKPhase;
    s_CLKPolarity = _CLKPolarity;
    
    
    /* 設定SPI引數 */
    hspi.Instance               = SPIx;                   /* 例化SPI */
    hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 設定波特率 */
    hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全雙工 */
    hspi.Init.CLKPhase          = _CLKPhase;              /* 配置時鐘相位 */
    hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置時鐘極性 */
    hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 設定資料寬度 */
    hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 資料傳輸先傳高位 */
    hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
    hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
    hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC後,此位無效 */
    hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC後,此位無效 */
    hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用軟體方式管理片選引腳 */
    hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 設定FIFO大小是一個數據項 */
    hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脈衝輸出 */
    hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI後,SPI相關引腳保持當前狀態 */  
    hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */

    /* 設定SPI引數 */
    hspi.Instance               = SPIx;                           /* 例化SPI */
    hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;             /* 設定波特率 */
    hspi.Init.Direction         = SPI_DIRECTION_2LINES;         /* 全雙工 */
    hspi.Init.CLKPhase          = _CLKPhase;                     /* 配置時鐘相位 */
    hspi.Init.CLKPolarity       = _CLKPolarity;                   /* 配置時鐘極性 */
    hspi.Init.DataSize          = SPI_DATASIZE_8BIT;               /* 設定資料寬度 */
    hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;             /* 資料傳輸先傳高位 */
    hspi.Init.TIMode            = SPI_TIMODE_DISABLE;             /* 禁止TI模式  */
    hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE;     /* 禁止CRC */
    hspi.Init.CRCPolynomial     = 7;                               /* 禁止CRC後,此位無效 */
    hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;             /* 禁止CRC後,此位無效 */
    hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_16DATA;    /* 設定FIFO大小是一個數據項 */
    
    hspi.Init.NSS         = SPI_NSS_HARD_INPUT;                 /* 片選引腳 */
    hspi.Init.NSSPMode    = SPI_NSS_PULSE_DISABLE;                /* 禁止脈衝輸出 */
    hspi.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;               /* 低電平有效 */
    hspi.Init.MasterSSIdleness        = SPI_MASTER_SS_IDLENESS_00CYCLE; /* MSS, 插入到NSS有效邊沿和第一個
資料開始之間的額外延遲,單位SPI時鐘週期個數 */

    hspi.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_10CYCLE; /* MIDI, 兩個連續資料幀之間
插入的最小時間延遲,單位SPI時鐘週期個數 */
    
    hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI後,SPI相關引腳保持當前狀態 */  
    hspi.Init.Mode                  = SPI_MODE_SLAVE;                

    /* 復位配置 */
    if (HAL_SPI_DeInit(&hspi) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }    

    /* 初始化配置 */
    if (HAL_SPI_Init(&hspi) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);
    }    
}

關於這兩個函式有以下三點要做個說明:

  •   函式bsp_InitSPIBus裡面的配置是個初始設定。實際驅動晶片時,會通過函式bsp_InitSPIParam做再配置。
  •   函式bsp_InitSPIParam提供了時鐘分頻,時鐘相位和時鐘極性配置。一般主機和從機此處設定為一樣即可。
  •   SPI硬體片選NSS設定為SPI_NSS_HARD_INPUT。
  •   這裡特別注意主機是hspi.Init.Mode = SPI_MODE_SLAVE。

94.4.2 第2步:SPI DMA配置

配置程式碼實現如下,註釋比較詳細:

/*
*********************************************************************************************************
*    函 數 名: bsp_InitSPIParam
*    功能說明: 配置SPI匯流排時鐘,GPIO,中斷,DMA等
*    形    參: SPI_HandleTypeDef 型別指標變數
*    返 回 值: 無
*********************************************************************************************************
*/
void HAL_SPI_MspInit(SPI_HandleTypeDef *_hspi)
{
    /* 配置 SPI匯流排GPIO : SCK MOSI MISO */
    {
        GPIO_InitTypeDef  GPIO_InitStruct;
            
        /* SPI和GPIP時鐘 */
        SPIx_SCK_CLK_ENABLE();
        SPIx_MISO_CLK_ENABLE();
        SPIx_MOSI_CLK_ENABLE();
        SPIx_CLK_ENABLE();

        /* SPI SCK */
        GPIO_InitStruct.Pin       = SPIx_SCK_PIN;
        GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull      = GPIO_PULLUP;
        GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_HIGH;
        GPIO_InitStruct.Alternate = SPIx_SCK_AF;
        HAL_GPIO_Init(SPIx_SCK_GPIO, &GPIO_InitStruct);

        /* SPI MISO */
        GPIO_InitStruct.Pin = SPIx_MISO_PIN;
        GPIO_InitStruct.Alternate = SPIx_MISO_AF;
        HAL_GPIO_Init(SPIx_MISO_GPIO, &GPIO_InitStruct);

        /* SPI MOSI */
        GPIO_InitStruct.Pin = SPIx_MOSI_PIN;
        GPIO_InitStruct.Alternate = SPIx_MOSI_AF;
        HAL_GPIO_Init(SPIx_MOSI_GPIO, &GPIO_InitStruct);
        
        /* SPI NSS */
        GPIO_InitStruct.Pin = SPIx_NSS_PIN;
        GPIO_InitStruct.Alternate = SPIx_NSS_AF;
        HAL_GPIO_Init(SPIx_NSS_GPIO, &GPIO_InitStruct);
    }

    /* 配置DMA和NVIC */
    #ifdef USE_SPI_DMA
    {
        /* 使能DMA時鐘 */
        DMAx_CLK_ENABLE();      

        /* SPI DMA傳送配置 */        
        hdma_tx.Instance                = SPIx_TX_DMA_STREAM;    /* 例化使用的DMA資料流 */
        hdma_tx.Init.FIFOMode           = DMA_FIFOMODE_ENABLE;   /* FIFO*/
        hdma_tx.Init.FIFOThreshold      = DMA_FIFO_THRESHOLD_FULL;/* 禁止FIFO此位不起作用,用於設定閥值 */
        hdma_tx.Init.MemBurst            = DMA_MBURST_SINGLE;    /* 禁止FIFO此位不起作用,用於儲存器突發 */
        hdma_tx.Init.PeriphBurst         = DMA_PBURST_SINGLE;     /* 禁止FIFO此位不起作用,用於外設突發 */
        hdma_tx.Init.Request             = SPIx_TX_DMA_REQUEST;     /* 請求型別 */  
        hdma_tx.Init.Direction           = DMA_MEMORY_TO_PERIPH;    /* 傳輸方向是從儲存器到外設 */  
        hdma_tx.Init.PeriphInc           = DMA_PINC_DISABLE;        /* 外設地址自增禁止 */ 
        hdma_tx.Init.MemInc              = DMA_MINC_ENABLE;         /* 儲存器地址自增使能 */  
        hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;     /* 外設資料傳輸位寬選擇位元組,即8bit */ 
        hdma_tx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;     /* 儲存器資料傳輸位寬選擇位元組,即8bit */    
        hdma_tx.Init.Mode                = DMA_NORMAL;              /* 正常模式 */
        hdma_tx.Init.Priority            = DMA_PRIORITY_LOW;        /* 優先順序低 */

         /* 復位DMA */
        if(HAL_DMA_DeInit(&hdma_tx) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);     
        }
        
         /* 初始化DMA */
        if(HAL_DMA_Init(&hdma_tx) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);     
        }

        /* 關聯DMA控制代碼到SPI */
        __HAL_LINKDMA(_hspi, hdmatx, hdma_tx);    

        /* SPI DMA接收配置 */    
        hdma_rx.Instance                = SPIx_RX_DMA_STREAM;     /* 例化使用的DMA資料流 */
        hdma_rx.Init.FIFOMode           = DMA_FIFOMODE_ENABLE;    /* FIFO*/
        hdma_rx.Init.FIFOThreshold      = DMA_FIFO_THRESHOLD_FULL;/* 禁止FIFO此位不起作用,用於設定閥值 */
        hdma_rx.Init.MemBurst            = DMA_MBURST_SINGLE;    /* 禁止FIFO此位不起作用,用於儲存器突發 */
        hdma_rx.Init.PeriphBurst         = DMA_PBURST_SINGLE;    /* 禁止FIFO此位不起作用,用於外設突發 */
        hdma_rx.Init.Request             = SPIx_RX_DMA_REQUEST;    /* 請求型別 */  
        hdma_rx.Init.Direction           = DMA_PERIPH_TO_MEMORY;   /* 傳輸方向從外設到儲存器 */  
        hdma_rx.Init.PeriphInc           = DMA_PINC_DISABLE;       /* 外設地址自增禁止 */   
        hdma_rx.Init.MemInc              = DMA_MINC_ENABLE;        /* 儲存器地址自增使能 */   
        hdma_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;    /* 外設資料傳輸位寬選擇位元組,即8bit */ 
        hdma_rx.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;    /* 儲存器資料傳輸位寬選擇位元組,即8bit */   
        hdma_rx.Init.Mode                = DMA_NORMAL;             /* 正常模式 */
        hdma_rx.Init.Priority            = DMA_PRIORITY_HIGH;      /* 優先順序高 */

         /* 復位DMA */
        if(HAL_DMA_DeInit(&hdma_rx) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);     
        }
        
         /* 初始化DMA */
        if(HAL_DMA_Init(&hdma_rx) != HAL_OK)
        {
            Error_Handler(__FILE__, __LINE__);     
        }

        /* 關聯DMA控制代碼到SPI */
       __HAL_LINKDMA(_hspi, hdmarx, hdma_rx);    

        /* 配置DMA傳送中斷 */
        HAL_NVIC_SetPriority(SPIx_DMA_TX_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(SPIx_DMA_TX_IRQn);
        
        /* 配置DMA接收中斷 */
        HAL_NVIC_SetPriority(SPIx_DMA_RX_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(SPIx_DMA_RX_IRQn);
        
        /* 配置SPI中斷 */
        HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(SPIx_IRQn);
    }
    #endif
    
    #ifdef USE_SPI_INT
        /* 配置SPI中斷優先順序並使能中斷 */
        HAL_NVIC_SetPriority(SPIx_IRQn, 1, 0);
        HAL_NVIC_EnableIRQ(SPIx_IRQn);
    #endif
}

94.4.3 第3步:SPI DMA傳輸設定和MPU配置

SPI DMA方式主要通過函式bsp_spiTransfer實現資料傳輸(程式碼裡面的查詢和中斷方式請忽略):

/*
*********************************************************************************************************
*                                 選擇DMA,中斷或者查詢方式
*********************************************************************************************************
*/
#define USE_SPI_DMA    /* DMA方式  */
//#define USE_SPI_INT    /* 中斷方式 */
//#define USE_SPI_POLL   /* 查詢方式 */

/* 查詢模式 */
#if defined (USE_SPI_POLL)

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/* 中斷模式 */
#elif defined (USE_SPI_INT)

uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];

/* DMA模式使用的SRAM4 */
#elif defined (USE_SPI_DMA)
    #if defined ( __CC_ARM )    /* IAR *******/
        __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
        __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    #elif defined (__ICCARM__)   /* MDK ********/
        #pragma location = ".RAM_D3"
        uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
        #pragma location = ".RAM_D3"
        uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    #endif
#endif

/*
*********************************************************************************************************
*    函 數 名: bsp_spiTransfer
*    功能說明: 啟動資料傳輸
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_spiTransfer(void)
{
    if (g_spiLen > SPI_BUFFER_SIZE)
    {
        return;
    }
    
    /* DMA方式傳輸 */
#ifdef USE_SPI_DMA
    wTransferState = TRANSFER_WAIT;
    
    if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    //while (wTransferState == TRANSFER_WAIT)
    //{
        ;
    //}
#endif

    /* 中斷方式傳輸 */    
#ifdef USE_SPI_INT
    wTransferState = TRANSFER_WAIT;

    if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    
    {
        Error_Handler(__FILE__, __LINE__);
    }
    
    //while (wTransferState == TRANSFER_WAIT)
    //{
        ;
    //}
#endif

    /* 查詢方式傳輸 */    
#ifdef USE_SPI_POLL
    if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)    
    {
        Error_Handler(__FILE__, __LINE__);
    }    
#endif
}

DMA方式要特別注意兩點:

  • 通過本手冊第26章的記憶體塊超方便使用方式,將DMA緩衝定義到SRAM4上。因為本工程是用的DTCM做的主RAM空間,這個空間無法使用通用DMA1和DMA2。
  • 由於程式裡面開啟了資料Cache,會造成DMA和CPU訪問SRAM4資料不一致的問題,特此將SRAM4空間關閉Cache。
    /* 配置SRAM4的MPU屬性為Non-cacheable */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

分散載入設定:

94.4.4 第4步:應用程式碼設計

應用部分的程式碼設計如下:

/*
*********************************************************************************************************
*    函 數 名: DemoSpiSlave
*    功能說明: SPI 從機通訊
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiSlave(void)
{
    uint8_t count = 0;
    
    /***************設定SPI Flash片選上拉,防止影響 ***************/
    {
        GPIO_InitTypeDef gpio_init;

        /* 開啟GPIO時鐘 */
        __HAL_RCC_GPIOD_CLK_ENABLE();
        
        gpio_init.Mode = GPIO_MODE_OUTPUT_PP;    /* 設定推輓輸出 */
        gpio_init.Pull = GPIO_NOPULL;            /* 上下拉電阻不使能 */
        gpio_init.Speed = GPIO_SPEED_HIGH;      /* GPIO速度等級 */    
        gpio_init.Pin = GPIO_PIN_13;    
        HAL_GPIO_Init(GPIOD, &gpio_init);

        GPIOD->BSRR = GPIO_PIN_13;
    }
    
    sfDispMenu();        /* 列印命令提示 */
    
    bsp_StartAutoTimer(0, 100);    /* 啟動1個100ms的自動重灌的定時器 */
    
    /* 上電後,準備接收主機命令 */
    g_spiTxBuf[0] = count++;
    g_spiTxBuf[1] = count++;
    g_spiTxBuf[2] = count++;
    g_spiTxBuf[3] = count++;
    g_spiLen = 4;
    bsp_spiTransfer();
    
    while(1)
    {
        bsp_Idle();        /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */
        
        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔100ms 進來一次 */  
            bsp_LedToggle(2);
        }

        if (wTransferState != TRANSFER_WAIT)
        {
            printf("SPI從機發送資料 = %d,%d,%d,%d\r\n", g_spiTxBuf[0], g_spiTxBuf[1], g_spiTxBuf[2],
 g_spiTxBuf[3]);
            printf("SPI從機接收資料 = %d,%d,%d,%d\r\n", g_spiRxBuf[0], g_spiRxBuf[1], g_spiRxBuf[2], 
g_spiRxBuf[3]);
            g_spiTxBuf[0] = count++;
            g_spiTxBuf[1] = count++;
            g_spiTxBuf[2] = count++;
            g_spiTxBuf[3] = count++;
            g_spiLen = 4;
            bsp_spiTransfer();
        }
    }
}

從機設計這裡主要注意兩點:

  • 上電後優先準備一次從機資料接收。
  • 從機接收到主機發送的資料後,將接收到的資料打印出來並打印發送的資料。

 

94.5 SPI DMA主從機使用注意事項

大家根據自己接線的穩定性,可以適當調節SPI主機和從機的時鐘速度,其中從機的時鐘速度是可以高於主機速度的,這樣通訊的容錯性更好些。

94.6 SPI DMA主從機驅動移植和使用

移植步驟如下:

  • 第1步:複製bsp_spi_bus.c,bsp_spi_bus.h到自己的工程目錄,並新增到工程裡面。
  • 第2步:根據使用的第幾個SPI,SPI時鐘,SPI引腳和DMA通道等,修改bsp_spi_bus.c檔案開頭的巨集定義
/*
*********************************************************************************************************
*                                時鐘,引腳,DMA,中斷等巨集定義
*********************************************************************************************************
*/
#define SPIx                            SPI1
#define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
#define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()

#define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
#define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()

#define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_SCK_GPIO                    GPIOB
#define SPIx_SCK_PIN                    GPIO_PIN_3
#define SPIx_SCK_AF                        GPIO_AF5_SPI1

#define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_MISO_GPIO                    GPIOB
#define SPIx_MISO_PIN                     GPIO_PIN_4
#define SPIx_MISO_AF                    GPIO_AF5_SPI1

#define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPIx_MOSI_GPIO                    GPIOB
#define SPIx_MOSI_PIN                     GPIO_PIN_5
#define SPIx_MOSI_AF                    GPIO_AF5_SPI1

#define SPIx_TX_DMA_STREAM               DMA2_Stream3
#define SPIx_RX_DMA_STREAM               DMA2_Stream2

#define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX
#define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX

#define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn
#define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn

#define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
#define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler

#define SPIx_IRQn                        SPI1_IRQn
#define SPIx_IRQHandler                  SPI1_IRQHandler
  • 第3步:如果使用DMA方式的話,請不要使用TCM RAM,因為通用DMA1和DMA2不支援。併為了防止DMA和CPU同時訪問DMA緩衝造成的資料一致性問題,將這塊空間關閉Cache處理,比如使用的SRAM4:
/* 配置SRAM4的MPU屬性為Non-cacheable */
MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress      = 0x38000000;
MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

HAL_MPU_ConfigRegion(&MPU_InitStruct);
  • 第4步:初始化SPI。
/* 針對不同的應用程式,新增需要的底層驅動模組初始化函式 */
bsp_InitSPIBus();    /* 配置SPI匯流排 */    
  • 第5步:SPI Flash驅動主要用到HAL庫的SPI驅動檔案,簡單省事些可以新增所有HAL庫C原始檔進來。
  • 第6步:應用方法看本章節配套例子即可。特別注意分散載入設定:

94.7 實驗例程設計框架

通過程式設計框架,讓大家先對配套例程有一個全面的認識,然後再理解細節,本次實驗例程的設計框架如下:

 

第1階段,上電啟動階段:

  • 這部分在第14章進行了詳細說明。

第2階段,進入main函式:

  • 第1部分,硬體初始化,主要是MPU,Cache,HAL庫,系統時鐘,滴答定時器和LED。
  • 第2部分,應用程式設計部分,實現SPI雙擊通訊。

94.8 實驗例程說明(MDK)

配套例子:

V7-070_SPI DMA雙機通訊(主機)

V7-071_SPI DMA雙機通訊(從機)

實驗目的:

  1. 學習SPI Flash主從機通訊實現。

實驗操作:

  1. K1按鍵按下,主機列印。
  2. SPI從機等待主機訊息。

上電後串列埠列印的資訊:

波特率 115200,資料位 8,奇偶校驗位無,停止位 1。

主機:

 

從機:

 

程式設計:

系統棧大小分配:

RAM空間用的DTCM:

硬體外設初始化

硬體外設的初始化是在 bsp.c 檔案實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘:
       - 呼叫函式HAL_InitTick,初始化滴答時鐘中斷1ms。
       - 設定NVIV優先順序分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鐘到400MHz
       - 切換使用HSE。
       - 此函式會更新全域性變數SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。
       - 預設不開啟,如果要使能此選項,務必看V7開發板使用者手冊第xx章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
bsp_InitDWT();      /* 初始化DWT時鐘週期計數器 */       
    bsp_InitKey();         /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();       /* 初始化滴答定時器 */
    bsp_InitLPUart();     /* 初始化串列埠 */
    bsp_InitExtIO();     /* 初始化FMC匯流排74HC574擴充套件IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();         /* 初始化LED */    
bsp_InitExtSDRAM(); /* 初始化SDRAM */

    /* 針對不同的應用程式,新增需要的底層驅動模組初始化函式 */
    bsp_InitSPIBus();    /* 配置SPI匯流排 */        

}

MPU配置和Cache配置:

資料Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM)和FMC的擴充套件IO區以及SRAM4

/*
*********************************************************************************************************
*    函 數 名: MPU_Config
*    功能說明: 配置MPU
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

    /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
    
    /* 配置FMC擴充套件IO的MPU屬性為Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /* 配置SRAM4的MPU屬性為Non-cacheable */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 數 名: CPU_CACHE_Enable
*    功能說明: 使能L1 Cache
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}

每10ms呼叫一次按鍵處理:

按鍵處理是在滴答定時器中斷裡面實現,每10ms執行一次檢測。

/*
*********************************************************************************************************
*    函 數 名: bsp_RunPer10ms
*    功能說明: 該函式每隔10ms被Systick中斷呼叫1次。詳見 bsp_timer.c的定時中斷服務程式。一些處理時間要求
*              不嚴格的任務可以放在此函式。比如:按鍵掃描、蜂鳴器鳴叫控制等。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
    bsp_KeyScan10ms();
}

主功能:

主機程式實現如下操作:

/*
*********************************************************************************************************
*    函 數 名: DemoSpiMaster
*    功能說明: SPI 主機通訊
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiMaster(void)
{
    uint8_t count = 0;
    uint8_t ucKeyCode;        /* 按鍵程式碼 */
    
    /***************設定SPI Flash片選上拉,防止影響 ***************/
    {
        GPIO_InitTypeDef gpio_init;

        /* 開啟GPIO時鐘 */
        __HAL_RCC_GPIOD_CLK_ENABLE();
        
        gpio_init.Mode = GPIO_MODE_OUTPUT_PP;    
        gpio_init.Pull = GPIO_NOPULL;        
        gpio_init.Speed = GPIO_SPEED_HIGH;      
        gpio_init.Pin = GPIO_PIN_13;    
        HAL_GPIO_Init(GPIOD, &gpio_init);

        GPIOD->BSRR = GPIO_PIN_13;
    }
    
    sfDispMenu();        /* 列印命令提示 */
    
    bsp_StartAutoTimer(0, 100);    /* 啟動1個100ms的自動重灌的定時器 */
    
    while(1)
    {
        bsp_Idle();        /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */
        
        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔100ms 進來一次 */  
            bsp_LedToggle(2);
        }
        
        /* 按鍵濾波和檢測由後臺systick中斷服務程式實現,我們只需要呼叫bsp_GetKey讀取鍵值即可。 */
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下,傳送資料給從機*/
                    g_spiTxBuf[0] = count++;
                    g_spiTxBuf[1] = count++;
                    g_spiTxBuf[2] = count++;
                    g_spiTxBuf[3] = count++;
                    g_spiLen = 4;
                    printf("SPI主機發送資料:%d,%d,%d,%d\r\n",
 g_spiTxBuf[0],g_spiTxBuf[1],g_spiTxBuf[2],g_spiTxBuf[3]);

                    bsp_spiTransfer();
                    printf("SPI主機接收資料:%d,%d,%d,%d\r\n", 
g_spiRxBuf[0],g_spiRxBuf[1],g_spiRxBuf[2],g_spiRxBuf[3]);
                    break;
                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        
        }
    }
}

從機實現程式如下:

/*
*********************************************************************************************************
*    函 數 名: DemoSpiSlave
*    功能說明: SPI 從機通訊
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiSlave(void)
{
    uint8_t count = 0;
    
    /***************設定SPI Flash片選上拉,防止影響 ***************/
    {
        GPIO_InitTypeDef gpio_init;

        /* 開啟GPIO時鐘 */
        __HAL_RCC_GPIOD_CLK_ENABLE();
        
        gpio_init.Mode = GPIO_MODE_OUTPUT_PP;    /* 設定推輓輸出 */
        gpio_init.Pull = GPIO_NOPULL;            /* 上下拉電阻不使能 */
        gpio_init.Speed = GPIO_SPEED_HIGH;      /* GPIO速度等級 */    
        gpio_init.Pin = GPIO_PIN_13;    
        HAL_GPIO_Init(GPIOD, &gpio_init);

        GPIOD->BSRR = GPIO_PIN_13;
    }
    
    sfDispMenu();        /* 列印命令提示 */
    
    bsp_StartAutoTimer(0, 100);    /* 啟動1個100ms的自動重灌的定時器 */
    
    /* 上電後,準備接收主機命令 */
    g_spiTxBuf[0] = count++;
    g_spiTxBuf[1] = count++;
    g_spiTxBuf[2] = count++;
    g_spiTxBuf[3] = count++;
    g_spiLen = 4;
    bsp_spiTransfer();
    
    while(1)
    {
        bsp_Idle();        /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */
        
        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔100ms 進來一次 */  
            bsp_LedToggle(2);
        }

        if (wTransferState != TRANSFER_WAIT)
        {
            printf("SPI從機發送資料 = %d,%d,%d,%d\r\n", g_spiTxBuf[0], g_spiTxBuf[1], g_spiTxBuf[2],
 g_spiTxBuf[3]);
            printf("SPI從機接收資料 = %d,%d,%d,%d\r\n", g_spiRxBuf[0], g_spiRxBuf[1], g_spiRxBuf[2], 
g_spiRxBuf[3]);
            g_spiTxBuf[0] = count++;
            g_spiTxBuf[1] = count++;
            g_spiTxBuf[2] = count++;
            g_spiTxBuf[3] = count++;
            g_spiLen = 4;
            bsp_spiTransfer();
        }
    }
}

94.9 實驗例程說明(IAR)

配套例子:

V7-070_SPI DMA雙機通訊(主機)

V7-071_SPI DMA雙機通訊(從機)

實驗目的:

  1. 學習SPI Flash主從機通訊實現。

實驗操作:

  1. K1按鍵按下,主機列印。
  2. SPI從機等待主機訊息。

上電後串列埠列印的資訊:

波特率 115200,資料位 8,奇偶校驗位無,停止位 1。

主機:

從機:

程式設計:

系統棧大小分配:

RAM空間用的DTCM:

硬體外設初始化

硬體外設的初始化是在 bsp.c 檔案實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬體裝置。該函式配置CPU暫存器和外設的暫存器並初始化一些全域性變數。只需要呼叫一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鐘:
       - 呼叫函式HAL_InitTick,初始化滴答時鐘中斷1ms。
       - 設定NVIV優先順序分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鐘到400MHz
       - 切換使用HSE。
       - 此函式會更新全域性變數SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於程式碼執行時間測量,MDK5.25及其以上版本才支援,IAR不支援。
       - 預設不開啟,如果要使能此選項,務必看V7開發板使用者手冊第xx章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
bsp_InitDWT();      /* 初始化DWT時鐘週期計數器 */       
    bsp_InitKey();         /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();       /* 初始化滴答定時器 */
    bsp_InitLPUart();     /* 初始化串列埠 */
    bsp_InitExtIO();     /* 初始化FMC匯流排74HC574擴充套件IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();         /* 初始化LED */    
bsp_InitExtSDRAM(); /* 初始化SDRAM */

    /* 針對不同的應用程式,新增需要的底層驅動模組初始化函式 */
    bsp_InitSPIBus();    /* 配置SPI匯流排 */        

}

MPU配置和Cache配置:

資料Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM)和FMC的擴充套件IO區以及SRAM4

/*
*********************************************************************************************************
*    函 數 名: MPU_Config
*    功能說明: 配置MPU
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

    /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    
    
    /* 配置FMC擴充套件IO的MPU屬性為Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /* 配置SRAM4的MPU屬性為Non-cacheable */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 數 名: CPU_CACHE_Enable
*    功能說明: 使能L1 Cache
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}

每10ms呼叫一次按鍵處理:

按鍵處理是在滴答定時器中斷裡面實現,每10ms執行一次檢測。

/*
*********************************************************************************************************
*    函 數 名: bsp_RunPer10ms
*    功能說明: 該函式每隔10ms被Systick中斷呼叫1次。詳見 bsp_timer.c的定時中斷服務程式。一些處理時間要求
*              不嚴格的任務可以放在此函式。比如:按鍵掃描、蜂鳴器鳴叫控制等。
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_RunPer10ms(void)
{
    bsp_KeyScan10ms();
}

主功能:

主機程式實現如下操作:

/*
*********************************************************************************************************
*    函 數 名: DemoSpiMaster
*    功能說明: SPI 主機通訊
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiMaster(void)
{
    uint8_t count = 0;
    uint8_t ucKeyCode;        /* 按鍵程式碼 */
    
    /***************設定SPI Flash片選上拉,防止影響 ***************/
    {
        GPIO_InitTypeDef gpio_init;

        /* 開啟GPIO時鐘 */
        __HAL_RCC_GPIOD_CLK_ENABLE();
        
        gpio_init.Mode = GPIO_MODE_OUTPUT_PP;    
        gpio_init.Pull = GPIO_NOPULL;        
        gpio_init.Speed = GPIO_SPEED_HIGH;      
        gpio_init.Pin = GPIO_PIN_13;    
        HAL_GPIO_Init(GPIOD, &gpio_init);

        GPIOD->BSRR = GPIO_PIN_13;
    }
    
    sfDispMenu();        /* 列印命令提示 */
    
    bsp_StartAutoTimer(0, 100);    /* 啟動1個100ms的自動重灌的定時器 */
    
    while(1)
    {
        bsp_Idle();        /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */
        
        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔100ms 進來一次 */  
            bsp_LedToggle(2);
        }
        
        /* 按鍵濾波和檢測由後臺systick中斷服務程式實現,我們只需要呼叫bsp_GetKey讀取鍵值即可。 */
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下,傳送資料給從機*/
                    g_spiTxBuf[0] = count++;
                    g_spiTxBuf[1] = count++;
                    g_spiTxBuf[2] = count++;
                    g_spiTxBuf[3] = count++;
                    g_spiLen = 4;
                    printf("SPI主機發送資料:%d,%d,%d,%d\r\n",
 g_spiTxBuf[0],g_spiTxBuf[1],g_spiTxBuf[2],g_spiTxBuf[3]);

                    bsp_spiTransfer();
                    printf("SPI主機接收資料:%d,%d,%d,%d\r\n", 
g_spiRxBuf[0],g_spiRxBuf[1],g_spiRxBuf[2],g_spiRxBuf[3]);
                    break;
                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        
        }
    }
}

從機實現程式如下:

/*
*********************************************************************************************************
*    函 數 名: DemoSpiSlave
*    功能說明: SPI 從機通訊
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoSpiSlave(void)
{
    uint8_t count = 0;
    
    /***************設定SPI Flash片選上拉,防止影響 ***************/
    {
        GPIO_InitTypeDef gpio_init;

        /* 開啟GPIO時鐘 */
        __HAL_RCC_GPIOD_CLK_ENABLE();
        
        gpio_init.Mode = GPIO_MODE_OUTPUT_PP;    /* 設定推輓輸出 */
        gpio_init.Pull = GPIO_NOPULL;            /* 上下拉電阻不使能 */
        gpio_init.Speed = GPIO_SPEED_HIGH;      /* GPIO速度等級 */    
        gpio_init.Pin = GPIO_PIN_13;    
        HAL_GPIO_Init(GPIOD, &gpio_init);

        GPIOD->BSRR = GPIO_PIN_13;
    }
    
    sfDispMenu();        /* 列印命令提示 */
    
    bsp_StartAutoTimer(0, 100);    /* 啟動1個100ms的自動重灌的定時器 */
    
    /* 上電後,準備接收主機命令 */
    g_spiTxBuf[0] = count++;
    g_spiTxBuf[1] = count++;
    g_spiTxBuf[2] = count++;
    g_spiTxBuf[3] = count++;
    g_spiLen = 4;
    bsp_spiTransfer();
    
    while(1)
    {
        bsp_Idle();        /* 這個函式在bsp.c檔案。使用者可以修改這個函式實現CPU休眠和喂狗 */
        
        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔100ms 進來一次 */  
            bsp_LedToggle(2);
        }

        if (wTransferState != TRANSFER_WAIT)
        {
            printf("SPI從機發送資料 = %d,%d,%d,%d\r\n", g_spiTxBuf[0], g_spiTxBuf[1], g_spiTxBuf[2],
 g_spiTxBuf[3]);
            printf("SPI從機接收資料 = %d,%d,%d,%d\r\n", g_spiRxBuf[0], g_spiRxBuf[1], g_spiRxBuf[2], 
g_spiRxBuf[3]);
            g_spiTxBuf[0] = count++;
            g_spiTxBuf[1] = count++;
            g_spiTxBuf[2] = count++;
            g_spiTxBuf[3] = count++;
            g_spiLen = 4;
            bsp_spiTransfer();
        }
    }
}

94.10   總結

本章節就為大家講解這麼多,推薦大家做實際應用測試,實際專案中需要雙機通訊,可以考慮這種方式,比較實用。