【STM32H7教程】第45章 STM32H7的ADC應用之定時器觸發配合DMA雙緩沖


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

第45章       STM32H7的ADC應用之定時器觸發配合DMA雙緩沖

本章教程為大家講解定時器觸發配合DMA雙緩沖做ADC數據采集,實際項目中有一定的使用價值,一個緩沖接收數據的時候,另一個緩沖可以做數據處理。

45.1 初學者重要提示

45.2 ADC穩壓基准硬件設計

45.3 ADC驅動設計

45.4 ADC板級支持包(bsp_adc.c)

45.5 ADC驅動移植和使用

45.6 實驗例程設計框架

45.7 實驗例程說明(MDK)

45.8 實驗例程說明(IAR)

45.9 總結

 

 

45.1 初學者重要提示

  1.   學習本章節前,務必優先學習第44章,需要對ADC的基礎知識和HAL庫的幾個常用API有個認識。
  2.   開發板右上角有個跳線帽,可以讓ADC的穩壓基准接3.3V或者2.5V,本章例子是接到3.3V。
  3.   STM32H7的ADC支持偏移校准和線性度校准。如果使用線性度校准的話,特別要注意此貼的問題:http://www.armbbs.cn/forum.php?mod=viewthread&tid=91436
  4.   ADC的專業術語詮釋文檔,推薦大家看看:http://www.armbbs.cn/forum.php?mod=viewthread&tid=89414

45.2 ADC穩壓基准硬件設計

注:學習前務必優先看第14章的2.1小節,對電源供電框架有個了解。

ADC要采集的准確,就需要有一個穩定的穩壓基准源,V7開發板使用的LM285D-2.5,即2.5V的基准源。硬件設計如下:

 

關於這個原理圖要注意以下問題:

LM285D-2.5輸出的是2.5V的穩壓基准,原理圖這里做了一個特別的處理,同時接了一個上拉電阻到VDDA(3.3V),然后用戶可以使用開發板右上角的跳線帽設置Vref選擇3.3V穩壓還是2.5V穩壓。

 

下面再來了解下LM285的電氣特性:

 

通過這個表,我們要了解以下幾點知識:

  1.   LM285的典型值是2.5V,支持的最小值2.462V,最大值2.538V。工作電流是20uA到20mA,溫飄是±20ppm/℃。
  2.   Iz是Reference current參考電流的意思:
  •  參考電流是20uA到1mA,溫度25℃,參考電壓最大變化1mV。
  •  參考電流是20uA到1mA,全范圍溫度(−40°C to 85°C),參考電壓最大變化1.5mV。
  •  參考電流是1mA到20mA,溫度25℃,參考電壓最大變化10mV。
  •  參考電流是1mA到20mA,全范圍溫度(−40°C to 85°C),參考電壓最大變化30mV。

 

那么問題來了,V7開發板上LM285的參考電流是多少? 簡單計算就是:

(VDDA – 2.5V) /  1K  =(3.3 – 2.5V) / 1K = 0.8mA。

45.3 ADC驅動設計

定時器觸發ADC做DMA數據傳輸的實現思路框圖如下:

 

下面將程序設計中的相關問題逐一為大家做個說明。

45.3.1 觸發ADC的定時器選擇和配置

ADC轉換既可以選擇外部觸發也可以選擇軟件觸發。定時器屬於外部觸發方式,使用定時器觸發的好處是可以設置任何ADC能夠支持的轉換頻率。

對於ADC1,ADC2,ADC3來說,規則通道支持的外部觸發源如下:

#define ADC_EXTERNALTRIG_T1_CC1           ((uint32_t)0x00000000)
#define ADC_EXTERNALTRIG_T1_CC2           ((uint32_t)ADC_CFGR_EXTSEL_0)
#define ADC_EXTERNALTRIG_T1_CC3           ((uint32_t)ADC_CFGR_EXTSEL_1)
#define ADC_EXTERNALTRIG_T2_CC2           ((uint32_t)(ADC_CFGR_EXTSEL_1 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_T3_TRGO          ((uint32_t)ADC_CFGR_EXTSEL_2)
#define ADC_EXTERNALTRIG_T4_CC4           ((uint32_t)(ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_EXT_IT11         ((uint32_t)(ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_1))
#define ADC_EXTERNALTRIG_T8_TRGO          ((uint32_t)(ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_1 |
 ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_T8_TRGO2         ((uint32_t) ADC_CFGR_EXTSEL_3)
#define ADC_EXTERNALTRIG_T1_TRGO          ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_T1_TRGO2         ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_1))
#define ADC_EXTERNALTRIG_T2_TRGO          ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_1 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_T4_TRGO          ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2))
#define ADC_EXTERNALTRIG_T6_TRGO          ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_T15_TRGO         ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_1))
#define ADC_EXTERNALTRIG_T3_CC4           ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_1 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_HR1_ADCTRG1      ((uint32_t) ADC_CFGR_EXTSEL_4)
#define ADC_EXTERNALTRIG_HR1_ADCTRG3      ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_LPTIM1_OUT       ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_1))
#define ADC_EXTERNALTRIG_LPTIM2_OUT       ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_1| ADC_CFGR_EXTSEL_0))
#define ADC_EXTERNALTRIG_LPTIM3_OUT       ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_2))

 

我們這里使用的是TIM1_CC1。

接下來就是TIM1的時鍾配置問題,代碼如下:

1.    /*
2.    ******************************************************************************************************
3.    *    函 數 名: TIM1_Config
4.    *    功能說明: 配置TIM1,用於觸發ADC,當前配置的100KHz觸發頻率
5.    *    形    參: 無                                      
6.    *    返 回 值: 無
7.    ******************************************************************************************************
8.    */
9.    static void TIM1_Config(void)
10.    {
11.        TIM_HandleTypeDef  htim ={0};
12.        TIM_OC_InitTypeDef sConfig = {0};
13.    
14.    
15.        /* 使能時鍾 */  
16.        __HAL_RCC_TIM1_CLK_ENABLE();
17.          
18.        /*-----------------------------------------------------------------------
19.            bsp.c 文件中 void SystemClock_Config(void) 函數對時鍾的配置如下: 
20.    
21.            System Clock source       = PLL (HSE)
22.            SYSCLK(Hz)                = 400000000 (CPU Clock)
23.            HCLK(Hz)                  = 200000000 (AXI and AHBs Clock)
24.            AHB Prescaler             = 2
25.            D1 APB3 Prescaler         = 2 (APB3 Clock  100MHz)
26.            D2 APB1 Prescaler         = 2 (APB1 Clock  100MHz)
27.            D2 APB2 Prescaler         = 2 (APB2 Clock  100MHz)
28.            D3 APB4 Prescaler         = 2 (APB4 Clock  100MHz)
29.    
30.            因為APB1 prescaler != 1, 所以 APB1上的TIMxCLK = APB1 x 2 = 200MHz;
31.            因為APB2 prescaler != 1, 所以 APB2上的TIMxCLK = APB2 x 2 = 200MHz;
32.            APB4上面的TIMxCLK沒有分頻,所以就是100MHz;
33.    
34.            APB1 定時器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14,LPTIM1
35.            APB2 定時器有 TIM1, TIM8 , TIM15, TIM16,TIM17
36.    
37.            APB4 定時器有 LPTIM2,LPTIM3,LPTIM4,LPTIM5
38.    
39.        TIM12CLK = 200MHz/(Period + 1) / (Prescaler + 1) = 200MHz / 2000 / 1 = 100KHz
40.        ----------------------------------------------------------------------- */  
41.         HAL_TIM_Base_DeInit(&htim);
42.        
43.         htim.Instance = TIM1;
44.        htim.Init.Period            = 1999;
45.        htim.Init.Prescaler         = 0;
46.        htim.Init.ClockDivision     = 0;
47.        htim.Init.CounterMode       = TIM_COUNTERMODE_UP;
48.        htim.Init.RepetitionCounter = 0;
49.        HAL_TIM_Base_Init(&htim);
50.        
51.        sConfig.OCMode     = TIM_OCMODE_PWM1;
52.        sConfig.OCPolarity = TIM_OCPOLARITY_LOW;
53.    
54.        /* 占空比50% */
55.        sConfig.Pulse = 1000;  
56.        if(HAL_TIM_OC_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1) != HAL_OK)
57.        {
58.            Error_Handler(__FILE__, __LINE__);
59.        }
60.    
61.        /* 啟動OC1 */
62.        if(HAL_TIM_OC_Start(&htim, TIM_CHANNEL_1) != HAL_OK)
63.        {
64.            Error_Handler(__FILE__, __LINE__);
65.        }
66.    }

 

這里把幾個關鍵的地方闡釋下:

  •   第11 – 12行,對作為局部變量的HAL庫結構體做初始化,防止不確定值配置時出問題。
  •   第18 – 65行,注釋已經比較詳細,配置TIM1的頻率是100KHz,這個速度就是ADC的觸發頻率。

TIM1CLK = 200MHz / (Period + 1) / (Prescaler + 1) = 200MHz/(1999+1)/(0+1) = 100KHz

占空比 = Pulse / (Period + 1) = 1000 / (1999+1)= 50%

 

這些知識點在前面的定時器章節有更詳細的說明。

45.3.2 ADC時鍾源選擇

根據第44章2.2小節的講解,我們知道ADC有兩種時鍾源可供選擇,可以使用來自AHB總線的系統時鍾,也可以使用PLL2,PLL3,HSE,HSI或者CSI時鍾。

如果采用AHB時鍾,不需要做專門的配置,而采用PLL2,PLL3時鍾需要特別的配置,下面是使用AHB或者PLL2時鍾的配置。

  •   通過宏定義設置選擇的時鍾源

使用那個時鍾源,將另一個注釋掉即可:

/* 選擇ADC的時鍾源 */
#define ADC_CLOCK_SOURCE_AHB     /* 選擇AHB時鍾源 */
//#define ADC_CLOCK_SOURCE_PLL   /* 選擇PLL時鍾源 */

 

  •   PLL2或者AHB時鍾源配置
#if defined (ADC_CLOCK_SOURCE_PLL)
    /* 配置PLL2時鍾為的72MHz,方便分頻產生ADC最高時鍾36MHz */
    RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    PeriphClkInitStruct.PLL2.PLL2M = 25;
    PeriphClkInitStruct.PLL2.PLL2N = 504;
    PeriphClkInitStruct.PLL2.PLL2P = 7;
    PeriphClkInitStruct.PLL2.PLL2Q = 7;
    PeriphClkInitStruct.PLL2.PLL2R = 7;
    PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0;
    PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
    PeriphClkInitStruct.PLL2.PLL2FRACN = 0;
    PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
    {
        Error_Handler(__FILE__, __LINE__);  
    }
#elif defined (ADC_CLOCK_SOURCE_AHB)
  
  /* 使用AHB時鍾的話,無需配置,默認選擇*/
  
#endif

 

對於PLL2的時鍾輸出,直接使用STM32CubeMX里面的時鍾樹配置即可,效果如下:

 

選擇PLL2P輸出作為ADC時鍾源:

 

  •   ADC分頻設置

無論是使用AHB時鍾還是PLL2時鍾都支持分頻設置:

 

AHB支持下面三種分頻設置:

#define ADC_CLOCK_SYNC_PCLK_DIV1   ((uint32_t)ADC_CCR_CKMODE_0)  
#define ADC_CLOCK_SYNC_PCLK_DIV2   ((uint32_t)ADC_CCR_CKMODE_1) 
#define ADC_CLOCK_SYNC_PCLK_DIV4   ((uint32_t)ADC_CCR_CKMODE)   

#define ADC_CLOCKPRESCALER_PCLK_DIV1   ADC_CLOCK_SYNC_PCLK_DIV1   /* 這三個僅僅是為了兼容,已經不推薦使用 */
#define ADC_CLOCKPRESCALER_PCLK_DIV2   ADC_CLOCK_SYNC_PCLK_DIV2   
#define ADC_CLOCKPRESCALER_PCLK_DIV4   ADC_CLOCK_SYNC_PCLK_DIV4    

 

PLL2支持下面幾種分頻設置:

#define ADC_CLOCK_ASYNC_DIV1       ((uint32_t)0x00000000)                                       
#define ADC_CLOCK_ASYNC_DIV2       ((uint32_t)ADC_CCR_PRESC_0)                                  
#define ADC_CLOCK_ASYNC_DIV4       ((uint32_t)ADC_CCR_PRESC_1)                                   
#define ADC_CLOCK_ASYNC_DIV6       ((uint32_t)(ADC_CCR_PRESC_1|ADC_CCR_PRESC_0))                 
#define ADC_CLOCK_ASYNC_DIV8       ((uint32_t)(ADC_CCR_PRESC_2))                                
#define ADC_CLOCK_ASYNC_DIV10      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_0))                 
#define ADC_CLOCK_ASYNC_DIV12      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_1))                 
#define ADC_CLOCK_ASYNC_DIV16      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_1|ADC_CCR_PRESC_0)) 
#define ADC_CLOCK_ASYNC_DIV32      ((uint32_t)(ADC_CCR_PRESC_3))                                
#define ADC_CLOCK_ASYNC_DIV64      ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_0))                 
#define ADC_CLOCK_ASYNC_DIV128     ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_1))                
#define ADC_CLOCK_ASYNC_DIV256     ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_1|ADC_CCR_PRESC_0)) 

 

有了這些認識后再看實際的分頻配置就好理解了:

#if defined (ADC_CLOCK_SOURCE_PLL)
/* 采用PLL異步時鍾,2分頻,即72MHz/2 = 36MHz */
    AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_ASYNC_DIV2;     
/* 采用AHB同步時鍾,4分頻,即200MHz/4 = 50MHz */     
#elif defined (ADC_CLOCK_SOURCE_AHB)
    AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_SYNC_PCLK_DIV4;      
#endif

 

45.3.3 ADC的DMA雙緩沖配置

由於函數HAL_ADC_Start_DMA封裝的DMA傳輸函數是HAL_DMA_Start_IT,而不是專門的DMA雙緩沖函數HAL_DMAEx_MultiBuferStart_IT。所以要實現雙緩沖效果的話,可以使用半傳輸完成中斷和傳輸完成中斷配合實現雙緩沖效果。這里使用的是DMA1_Stream1:

1.    /*
2.    ******************************************************************************************************
3.    *    函 數 名: bsp_InitADC
4.    *    功能說明: 初始化ADC
5.    *    形    參: 無
6.    *    返 回 值: 無
7.    ******************************************************************************************************
8.    */
9.    void bsp_InitADC(void)
10.    {
11.        ADC_HandleTypeDef   AdcHandle = {0};
12.        DMA_HandleTypeDef   DmaHandle = {0};    
13.        ADC_ChannelConfTypeDef   sConfig = {0};
14.        GPIO_InitTypeDef         GPIO_InitStruct;
15.    
16.        
17.      /* ## - 1 - 配置ADC采樣的時鍾 ####################################### */
18.    #if defined (ADC_CLOCK_SOURCE_PLL)
19.        /* 配置PLL2時鍾為的72MHz,方便分頻產生ADC最高時鍾36MHz */
20.         RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
21.        PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
22.        PeriphClkInitStruct.PLL2.PLL2M = 25;
23.        PeriphClkInitStruct.PLL2.PLL2N = 504;
24.        PeriphClkInitStruct.PLL2.PLL2P = 7;
25.        PeriphClkInitStruct.PLL2.PLL2Q = 7;
26.        PeriphClkInitStruct.PLL2.PLL2R = 7;
27.        PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0;
28.        PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
29.        PeriphClkInitStruct.PLL2.PLL2FRACN = 0;
30.        PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;
31.        if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
32.        {
33.            Error_Handler(__FILE__, __LINE__);  
34.        }
35.    #elif defined (ADC_CLOCK_SOURCE_AHB)
36.      
37.      /* 使用AHB時鍾的話,無需配置,默認選擇*/
38.      
39.    #endif
40.    
41.        /* ## - 2 - 配置ADC采樣使用的采集引腳 ####################################### */
42.        __HAL_RCC_GPIOC_CLK_ENABLE();
43.    
44.        GPIO_InitStruct.Pin = GPIO_PIN_0;
45.        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
46.        GPIO_InitStruct.Pull = GPIO_NOPULL;
47.        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
48.    
49.        /* ## - 3 - 配置ADC采樣使用的時鍾 ####################################### */
50.        __HAL_RCC_DMA1_CLK_ENABLE();
51.        DmaHandle.Instance                 = DMA1_Stream1;            /* 使用的DMA1 Stream1 */
52.        DmaHandle.Init.Request             = DMA_REQUEST_ADC1;        /* 請求類型采用DMA_REQUEST_ADC1 */  
53.        DmaHandle.Init.Direction           = DMA_PERIPH_TO_MEMORY;    /* 傳輸方向是從外設到存儲器 */  
54.        DmaHandle.Init.PeriphInc           = DMA_PINC_DISABLE;        /* 外設地址自增禁止 */ 
55.        DmaHandle.Init.MemInc              = DMA_MINC_ENABLE;         /* 存儲器地址自增使能 */  
56.        DmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;/* 外設數據傳輸位寬選擇半字,16bit */     
57.         DmaHandle.Init.MemDataAlignment    = DMA_MDATAALIGN_HALFWORD;/* 存儲器數據傳輸位寬選半字,16bit */    
58.        DmaHandle.Init.Mode                = DMA_CIRCULAR;            /* 循環模式 */   
59.        DmaHandle.Init.Priority            = DMA_PRIORITY_LOW;        /* 優先級低 */  
60.        DmaHandle.Init.FIFOMode            = DMA_FIFOMODE_DISABLE;    /* 禁止FIFO*/
61.        DmaHandle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* 禁止FIFO此位不起作用,用於設置閥值 */
62.        DmaHandle.Init.MemBurst      = DMA_MBURST_SINGLE;       /* 禁止FIFO此位不起作用,用於存儲器突發 */
63.        DmaHandle.Init.PeriphBurst   = DMA_PBURST_SINGLE;      /* 禁止FIFO此位不起作用,用於外設突發 */
64.     
65.        /* 初始化DMA */
66.        if(HAL_DMA_Init(&DmaHandle) != HAL_OK)
67.        {
68.            Error_Handler(__FILE__, __LINE__);     
69.        }
70.        
71.        /* 開啟DMA1 Stream1的中斷 */
72.        HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 2, 0);
73.        HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
74.        
75.        /* 關聯ADC句柄和DMA句柄 */
76.        __HAL_LINKDMA(&AdcHandle, DMA_Handle, DmaHandle);
77.    
78.        /* ## - 4 - 配置ADC ########################################################### */
79.        __HAL_RCC_ADC12_CLK_ENABLE();
80.        AdcHandle.Instance = ADC1;
81.        
82.    #if defined (ADC_CLOCK_SOURCE_PLL)
83.        AdcHandle.Init.ClockPrescaler  = ADC_CLOCK_ASYNC_DIV2;     /* 采用PLL異步時鍾,2分頻,即72MHz/2 
84.                                                                      = 36MHz */
85.    #elif defined (ADC_CLOCK_SOURCE_AHB)
86.        AdcHandle.Init.ClockPrescaler  = ADC_CLOCK_SYNC_PCLK_DIV4;  /* 采用AHB同步時鍾,4分頻,即200MHz/4
87.                                                                        = 50MHz */
88.    #endif
89.        AdcHandle.Init.Resolution            = ADC_RESOLUTION_16B;   /* 16位分辨率 */
90.        AdcHandle.Init.ScanConvMode          = ADC_SCAN_DISABLE;     /* 禁止掃描,因為僅開了一個通道 */
91.        AdcHandle.Init.EOCSelection          = ADC_EOC_SINGLE_CONV;  /* EOC轉換結束標志 */
92.        AdcHandle.Init.LowPowerAutoWait      = DISABLE;              /* 禁止低功耗自動延遲特性 */
93.        AdcHandle.Init.ContinuousConvMode    = DISABLE;            /* 禁止自動轉換,采用的定時器觸發轉換 */
94.        AdcHandle.Init.NbrOfConversion       = 1;         /* 使用了1個轉換通道 */
95.        AdcHandle.Init.DiscontinuousConvMode = DISABLE;   /* 禁止不連續模式 */
96.        AdcHandle.Init.NbrOfDiscConversion   = 1;         /* 禁止不連續模式后,此參數忽略,此位是用來配置
97.                                                             不連續子組中通道數 */
98.        AdcHandle.Init.ExternalTrigConv      = ADC_EXTERNALTRIG_T1_CC1;            /* 定時器1的CC1觸發 */
99.        AdcHandle.Init.ExternalTrigConvEdge  = ADC_EXTERNALTRIGCONVEDGE_RISING;    /* 上升沿觸發 */
100.        AdcHandle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR; /* DMA循環模式接收ADC
101.                                                                                       轉換的數據 */
102.        AdcHandle.Init.BoostMode    = ENABLE;                 /* ADC時鍾超過20MHz的話,使能boost */
103.        AdcHandle.Init.Overrun    = ADC_OVR_DATA_OVERWRITTEN; /* ADC轉換溢出的話,覆蓋ADC的數據寄存器 */
104.        AdcHandle.Init.OversamplingMode         = DISABLE;    /* 禁止過采樣 */
105.    
106.        /* 初始化ADC */
107.        if (HAL_ADC_Init(&AdcHandle) != HAL_OK)
108.        {
109.            Error_Handler(__FILE__, __LINE__);
110.        }
111.      
112.        /* 校准ADC,采用偏移校准 */
113.        if (HAL_ADCEx_Calibration_Start(&AdcHandle, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
114.        {
115.            Error_Handler(__FILE__, __LINE__);
116.        }
117.      
118.        /* 配置ADC通道  */
119.        sConfig.Channel      = ADC_CHANNEL_10;              /* 配置使用的ADC通道 */
120.        sConfig.Rank         = ADC_REGULAR_RANK_1;          /* 采樣序列里的第1個 */
121.        sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;     /* 采樣周期 */
122.        sConfig.SingleDiff   = ADC_SINGLE_ENDED;            /* 單端輸入 */
123.        sConfig.OffsetNumber = ADC_OFFSET_NONE;             /* 無偏移 */ 
124.        sConfig.Offset = 0;                                 /* 無偏移的情況下,此參數忽略 */
125.    
126.        if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK)
127.        {
128.            Error_Handler(__FILE__, __LINE__);
129.        }
130.      
131.        /* ## - 5 - 配置ADC的定時器觸發 ####################################### */
132.        TIM1_Config();
133.      
134.        /* ## - 6 - 啟動ADC的DMA方式傳輸 ####################################### */
135.        if (HAL_ADC_Start_DMA(&AdcHandle, (uint32_t *)ADCxValues, 128) != HAL_OK)
136.        {
137.            Error_Handler(__FILE__, __LINE__);
138.        }
139.    }

 

這里把幾個關鍵的地方闡釋下:

  •   第11 - 13行,對作為局部變量的HAL庫結構體做初始化,防止不確定值配置時出問題。
  •   第18 - 39行,前面2.2小節已經講解,ADC時鍾源選擇AHB時鍾還是PLL時鍾。
  •   第42 – 47行,選擇PC0作為數據采集引腳。
  •   第50- 69行,配置DMA的基本參數,注釋較詳細。這里是采用的ADC外設到內部SRAM的傳輸方向,數據帶寬設置16bit,循環傳輸模式。
  •   第72 – 73行,配置DMA的中斷優先級,並使能。
  •   第76行,這行代碼比較重要,應用中容易被遺忘,用於關聯ADC句柄和DMA句柄。在用戶調用ADC的DMA傳輸方式函數HAL_ADC_Start_DMA時,此函數內部調用的HAL_DMA_Start_IT會用到DMA句柄。
  •   第79 - 110行,主要是ADC的配置,注釋較詳細,配置ADC1為16bit模式,采用定時器1的CC1作為外部觸發。
  •   第113 – 116行,這里的是采用的ADC偏移校准,如果要采用線性度校准,務必要注意此貼的問題:http://www.armbbs.cn/forum.php?mod=viewthread&tid=91436
  •   第119 -129行,配置ADC通道參數。這里使用的通道10是PC0引腳的復用功能,不是隨意設置的。另外注意這里的采用周期,取的最小值,方便實現ADC外部觸發的最快速度。
  •   第132行,配置ADC的定時器觸發,在本章2.2小節有講解。
  •   第135 – 138行,啟動ADC的DMA方式數據傳輸。

45.3.4 DMA存儲器選擇注意事項

由於STM32H7 Cache的存在,凡是CPU和DMA都會操作到的存儲器,我們都要注意數據一致性問題。對於本章節要實現的功能,要注意讀Cache問題,防止DMA已經更新了緩沖區的數據,而我們讀取的卻是Cache里面緩存的。這里提供兩種解決辦法:

  •   方法一:

關閉DMA所使用SRAM存儲區。

/* 配置SRAM的MPU屬性為Device或者Strongly Ordered,即關閉Cache */
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;

 

  •   方法二:

設置SRAM的緩沖區做32字節對齊,大小最好也是32字節整數倍,然后調用函數SCB_InvalidateDCache_by_Addr做無效化操作即可,保證CPU讀取到的數據是剛更新好的。

 

本章節配套例子是直接使用的方法二。例子中變量的定義方式如下:

/* 方便Cache類的API操作,做32字節對齊 */
#if defined ( __ICCARM__ )
#pragma location = 0x38000000
uint16_t ADCxValues[128];
#elif defined ( __CC_ARM )
ALIGN_32BYTES(__attribute__((section (".RAM_D3"))) uint16_t ADCxValues[128]);
#endif

 

對於IAR需要#pragma location指定位置,而MDK通過分散加載即可實現,詳情看前面第26章,有詳細講解。

45.3.5 DMA中斷處理

調用函數HAL_ADC_Start_DMA開啟了DMA的傳輸完成中斷,半傳輸完成中斷,傳輸錯誤中斷和直接模式錯誤中斷。通過傳輸完成中斷和半傳輸完整中斷可以實現雙緩沖的處理:

/*
*********************************************************************************************************
*    函 數 名: DMA1_Stream1_IRQHandler
*    功能說明: DMA1 Stream1中斷服務程序
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DMA1_Stream1_IRQHandler(void)
{
    /* 傳輸完成中斷 */
    if((DMA1->LISR & DMA_FLAG_TCIF1_5) != RESET)
    {
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_TCIF1_5;

        /* 當前使用的緩沖0 */
        if((DMA1_Stream1->CR & DMA_SxCR_CT) == RESET)
        {
            /*
                1、當前正在使用緩沖0,此時可以動態修改緩沖1的數據。
                   比如緩沖區0是IO_Toggle,緩沖區1是IO_Toggle1,那么此時就可以修改IO_Toggle1。
                2、變量所在的SRAM區已經通過MPU配置為WT模式,更新變量IO_Toggle會立即寫入。
                3、不配置MPU的話,也可以通過Cahce的函數SCB_CleanDCache_by_Addr做Clean操作。
            */
            
        }
        /* 當前使用的緩沖1 */
        else
        {
             /*
               1、當前正在使用緩沖1,此時可以動態修改緩沖0的數據。
                  比如緩沖區0是IO_Toggle,緩沖區1是IO_Toggle1,那么此時就可以修改IO_Toggle。
               2、變量所在的SRAM區已經通過MPU配置為WT模式,更新變量IO_Toggle會立即寫入。
               3、不配置MPU的話,也可以通過Cahce的函數SCB_CleanDCache_by_Addr做Clean操作。
            */

        }
    }

    /* 半傳輸完成中斷 */    
    if((DMA1->LISR & DMA_FLAG_HTIF1_5) != RESET)
    {
        /* 清除標志 */
        DMA1->LISR = DMA_FLAG_HTIF1_5;
    }

    /* 傳輸錯誤中斷 */
    if((DMA1->LISR & DMA_FLAG_TEIF1_5) != RESET)
    {
        /* 清除標志 */
        DMA1->LISR = DMA_FLAG_TEIF1_5;
    }

    /* 直接模式錯誤中斷 */
    if((DMA1->LISR & DMA_FLAG_DMEIF1_5) != RESET)
    {
        /* 清除標志 */
        DMA1->LISR = DMA_FLAG_DMEIF1_5;
    }
}

/*
*********************************************************************************************************
*    函 數 名: DMA1_Stream1_IRQHandler
*    功能說明: DMA1 Stream1中斷服務程序
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DMA1_Stream1_IRQHandler(void)
{
    /* 傳輸完成中斷 */
    if((DMA1->LISR & DMA_FLAG_TCIF1_5) != RESET)
    {
        
        HC574_TogglePin(GPIO_PIN_23);
        
        /*
           1、使用此函數要特別注意,第1個參數地址要32字節對齊,第2個參數要是32字節的整數倍。
           2、進入傳輸完成中斷,當前DMA正在使用緩沖區的前半部分,用戶可以操作后半部分。
        */
        SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[64]), 128);
        
        s_DmaFlag = 2;
        
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_TCIF1_5;
    }

    /* 半傳輸完成中斷 */    
    if((DMA1->LISR & DMA_FLAG_HTIF1_5) != RESET)
    {
        /*
           1、使用此函數要特別注意,第1個參數地址要32字節對齊,第2個參數要是32字節的整數倍。
           2、進入半傳輸完成中斷,當前DMA正在使用緩沖區的后半部分,用戶可以操作前半部分。
        */
        SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[0]), 128);
        
        s_DmaFlag = 1;
        
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_HTIF1_5;
    }

    /* 傳輸錯誤中斷 */
    if((DMA1->LISR & DMA_FLAG_TEIF1_5) != RESET)
    {
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_TEIF1_5;
    }

    /* 直接模式錯誤中斷 */
    if((DMA1->LISR & DMA_FLAG_DMEIF1_5) != RESET)
    {
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_DMEIF1_5;
    }
}

 

注釋的比較清楚。如果輸出的PWM頻率較高,建議將DMA的緩沖區設置的大些,防止DMA中斷的執行頻率較高。

傳輸完成中斷里面有個擴展IO翻轉函數HC574_TogglePin(GPIO_PIN_23),大家可以通過示波器測量開發板上絲印為X23的排針,從而方便的查看DMA中斷速度。

傳輸完成中斷和半傳輸完成中斷里面還有一個變量s_DmaFlag,當s_DmaFlag= 1時表示進DMA半傳輸完成中斷,s_DmaFlag = 2表示進入DMA傳輸完成中斷。

45.3.6 讀取DMA雙緩沖數據

DMA雙緩沖方式配置一款RTOS是最方便的,可以在中斷服務程序里面發消息給任務,讓數據可以得到及時處理。而裸機方式的話,需要用戶實時查詢變量,檢測到有數據了再進行處理。具體實現代碼如下:

/*
*********************************************************************************************************
*    函 數 名: bsp_GetAdcValues
*    功能說明: 獲取ADC的數據並打印
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_GetAdcValues(void)
{
    uint32_t values;
    float  temp;
    
    /* 當前DMA操作是后半個緩沖,讀取前半個緩沖的前4個數值求平均 */
    if(s_DmaFlag == 1)
    {
        DISABLE_INT();
        s_DmaFlag = 0;
        values = (ADCxValues[0] + ADCxValues[1] + ADCxValues[2] + ADCxValues[3])/4;

        ENABLE_INT();
    }
    /* 當前DMA操作是后前個緩沖,讀取后半個緩沖的前4個數值求平均 */
    else if(s_DmaFlag == 2)
    {
        DISABLE_INT();
        s_DmaFlag = 0;
        values = (ADCxValues[64] + ADCxValues[65] + ADCxValues[66] + ADCxValues[67])/4;
        ENABLE_INT();
    }
    
    /* 打印讀出的串口值 */
    temp = values *3.3 / 65536;
    
    printf("ADCxValues = %d, %5.3f\r\n", values, temp);
}

 

45.4 ADC板級支持包(bsp_adc.c)

ADC驅動文件bsp_adc.c提供了如下三個函數:

  •   TIM1_Config
  •   bsp_InitADC
  •   bsp_GetAdcValues

45.4.1 函數TIM1_Config

函數原型:

static void TIM1_Config(void)

函數描述:

此函數用於配置TIM1工作在OC輸出比較模式,使用TIM1的CC1作為ADC的觸發源。

注意事項:

  1. 關於此函數的講解在本章的2.1小節。
  2. 函數前面的static表示限制在bsp_adc.c文件里面調用。

45.4.2 函數bsp_InitADC

函數原型:

void bsp_InitADC(void)

函數描述:

此函數用於配置定時器觸發ADC做DMA傳輸。

注意事項:

  1. 關於此函數的講解在本章2.2小節。

使用舉例:

作為初始化函數,直接在bsp.c文件的bsp_Init函數里面調用即可。

45.4.3 函數bsp_GetAdcValues

函數原型:

void bsp_GetAdcValues(void)

函數描述:

此函數用於獲取ADC的轉換數據。

注意事項:

  1. 關於此函數的講解在本章2.4,2.5和2.6小節。

使用舉例:

如果是裸機工程: 要實時調用此函數讀取雙緩沖里面的數據。

如果是RTOS工程:要在DMA的中斷服務程序里面給ADC任務發消息,讓ADC任務可以及時讀取數據。

45.5 ADC驅動移植和使用

ADC驅動的移植比較方便:

  •   第1步:復制bsp_adc.c和bsp_adc.h到自己的工程目錄,並添加到工程里面。
  •   第2步:這幾個驅動文件主要用到HAL庫的GPIO、TIM,DMA和ADC驅動文件,簡單省事些可以添加所有HAL庫.C源文件進來。
  •   第3步,應用方法看本章節配套例子即可,另外就是根據自己的需要做配置修改。

45.6 實驗例程設計框架

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

 

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

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

  第2階段,進入main函數:

  •   第1步,硬件初始化,主要是MPU,Cache,HAL庫,系統時鍾,滴答定時器,LED,串口和ADC。
  •   第2步,應用程序設計部分,周期性的打印數據,方便查看。
  •   第3步,DMA中斷,以雙緩沖方式存儲ADC數據。

45.7 實驗例程說明(MDK)

配套例子:

V7-019_ADC定時器觸發+DMA雙緩沖實現

實驗目的:

  1. 學習ADC定時器觸發 + DMA雙緩沖的實現。

實驗內容:

  1. 例子默認用的AHB時鍾供ADC使用,大家可以通過bsp_adc.c文件開頭宏定義切換到PLL2專用時鍾。
  2. 使用的TIM1的OC1作為ADC的外部觸發源,觸發速度是100KHz,即ADC的采樣率也是100KHz。
  3. 使用DMA的半傳輸完成中斷和傳輸完成中斷實現數據的雙緩沖更新。
  4. 采集引腳使用的PC0,另外特別注意開發板上的Vref穩壓基准跳線帽短接的3.3V。
  5. 每隔500ms,串口會打印一次。
  6. 板子正常運行時LED2閃爍。

PC0引腳位置(穩壓基准要短接3.3V):

 

上電后串口打印的信息:

波特率 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_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();      /* 初始化滴答定時器 */
    bsp_InitUart();    /* 初始化串口 */
    bsp_InitExtIO();    /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();        /* 初始化LED */    
bsp_InitADC();    /* 初始化ADC */
}

 

  MPU配置和Cache配置:

數據Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM),FMC的擴展IO區和D3域的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的屬性為Write through, read allocate,no write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    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();
}

 

  主功能

主程序實現如下操作:

  •   每隔500ms打印一次PC0引腳的采集值。
/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: c程序入口
*    形    參: 無
*    返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
    uint8_t ucKeyCode;     /* 按鍵代碼 */    

#if defined ( __CC_ARM )    
    TempValues1 = 0; /* 避免MDK警告 */  
    TempValues2 = 0;    
#endif
 
    bsp_Init();          /* 硬件初始化 */
    
    PrintfLogo();      /* 打印例程名稱和版本等信息 */
    PrintfHelp();      /* 打印操作提示 */

    bsp_StartAutoTimer(0, 500);    /* 啟動1個500ms的自動重裝的定時器 */
    
    /* 進入主程序循環體 */
    while (1)
    {
        bsp_Idle();        /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */

        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔500ms 進來一次 */  
            bsp_LedToggle(2);
            
            /* 
               這里僅僅是為了展示方便,500ms更新一次,如果是實際工程里面應用
               裸機工程: 要實時調用下面的函數讀取雙緩沖里面的數據。
               RTOS工程:要在DMA的中斷服務程序里面給ADC任務發消息,讓ADC任務可以及時讀取數據。
            */
            bsp_GetAdcValues();
        }

        /* 按鍵濾波和檢測由后台systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下 */
                    printf(" K1鍵按下 \r\n");
                    break;

                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        }
    }
}

 

DMA中斷處理:

/*
*********************************************************************************************************
*    函 數 名: DMA1_Stream1_IRQHandler
*    功能說明: DMA1 Stream1中斷服務程序
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DMA1_Stream1_IRQHandler(void)
{
    /* 傳輸完成中斷 */
    if((DMA1->LISR & DMA_FLAG_TCIF1_5) != RESET)
    {
        
        HC574_TogglePin(GPIO_PIN_23);
        
        /*
           1、使用此函數要特別注意,第1個參數地址要32字節對齊,第2個參數要是32字節的整數倍。
           2、進入傳輸完成中斷,當前DMA正在使用緩沖區的前半部分,用戶可以操作后半部分。
        */
        SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[64]), 128);
        
        s_DmaFlag = 2;
        
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_TCIF1_5;
    }

    /* 半傳輸完成中斷 */    
    if((DMA1->LISR & DMA_FLAG_HTIF1_5) != RESET)
    {
        /*
           1、使用此函數要特別注意,第1個參數地址要32字節對齊,第2個參數要是32字節的整數倍。
           2、進入半傳輸完成中斷,當前DMA正在使用緩沖區的后半部分,用戶可以操作前半部分。
        */
        SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[0]), 128);
        
        s_DmaFlag = 1;
        
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_HTIF1_5;
    }

    /* 傳輸錯誤中斷 */
    if((DMA1->LISR & DMA_FLAG_TEIF1_5) != RESET)
    {
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_TEIF1_5;
    }

    /* 直接模式錯誤中斷 */
    if((DMA1->LISR & DMA_FLAG_DMEIF1_5) != RESET)
    {
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_DMEIF1_5;
    }
}

 

45.8 實驗例程說明(IAR)

配套例子:

V7-019_ADC定時器觸發+DMA雙緩沖實現

實驗目的:

  1. 學習ADC定時器觸發 + DMA雙緩沖的實現。

實驗內容:

  1. 例子默認用的AHB時鍾供ADC使用,大家可以通過bsp_adc.c文件開頭宏定義切換到PLL2專用時鍾。
  2. 使用的TIM1的OC1作為ADC的外部觸發源,觸發速度是100KHz,即ADC的采樣率也是100KHz。
  3. 使用DMA的半傳輸完成中斷和傳輸完成中斷實現數據的雙緩沖更新。
  4. 采集引腳使用的PC0,另外特別注意開發板上的Vref穩壓基准跳線帽短接的3.3V。
  5. 每隔500ms,串口會打印一次。
  6. 板子正常運行時LED2閃爍。

PC0引腳位置(穩壓基准要短接3.3V):

 

上電后串口打印的信息:

波特率 115200,數據位 8,奇偶校驗位無,停止位 1

 

上電后串口打印的信息:

波特率 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_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();      /* 初始化滴答定時器 */
    bsp_InitUart();    /* 初始化串口 */
    bsp_InitExtIO();    /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();        /* 初始化LED */    
bsp_InitADC();    /* 初始化ADC */
}

 

  MPU配置和Cache配置:

數據Cache和指令Cache都開啟。配置了AXI SRAM區(本例子未用到AXI SRAM),FMC的擴展IO區和D3域的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的屬性為Write through, read allocate,no write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x38000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    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();
}

 

  主功能:

主程序實現如下操作:

  •  每隔500ms打印一次PC0引腳的采集值。
/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: c程序入口
*    形    參: 無
*    返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
    uint8_t ucKeyCode;     /* 按鍵代碼 */    

#if defined ( __CC_ARM )    
    TempValues1 = 0; /* 避免MDK警告 */  
    TempValues2 = 0;    
#endif
 
    bsp_Init();          /* 硬件初始化 */
    
    PrintfLogo();      /* 打印例程名稱和版本等信息 */
    PrintfHelp();      /* 打印操作提示 */

    bsp_StartAutoTimer(0, 500);    /* 啟動1個500ms的自動重裝的定時器 */
    
    /* 進入主程序循環體 */
    while (1)
    {
        bsp_Idle();        /* 這個函數在bsp.c文件。用戶可以修改這個函數實現CPU休眠和喂狗 */

        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {
            /* 每隔500ms 進來一次 */  
            bsp_LedToggle(2);
            
            /* 
               這里僅僅是為了展示方便,500ms更新一次,如果是實際工程里面應用
               裸機工程: 要實時調用下面的函數讀取雙緩沖里面的數據。
               RTOS工程:要在DMA的中斷服務程序里面給ADC任務發消息,讓ADC任務可以及時讀取數據。
            */
            bsp_GetAdcValues();
        }

        /* 按鍵濾波和檢測由后台systick中斷服務程序實現,我們只需要調用bsp_GetKey讀取鍵值即可。 */
        ucKeyCode = bsp_GetKey();    /* 讀取鍵值, 無鍵按下時返回 KEY_NONE = 0 */
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                case KEY_DOWN_K1:            /* K1鍵按下 */
                    printf(" K1鍵按下 \r\n");
                    break;

                default:
                    /* 其它的鍵值不處理 */
                    break;
            }
        }
    }
}

 

DMA中斷處理:

/*
*********************************************************************************************************
*    函 數 名: DMA1_Stream1_IRQHandler
*    功能說明: DMA1 Stream1中斷服務程序
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DMA1_Stream1_IRQHandler(void)
{
    /* 傳輸完成中斷 */
    if((DMA1->LISR & DMA_FLAG_TCIF1_5) != RESET)
    {
        
        HC574_TogglePin(GPIO_PIN_23);
        
        /*
           1、使用此函數要特別注意,第1個參數地址要32字節對齊,第2個參數要是32字節的整數倍。
           2、進入傳輸完成中斷,當前DMA正在使用緩沖區的前半部分,用戶可以操作后半部分。
        */
        SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[64]), 128);
        
        s_DmaFlag = 2;
        
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_TCIF1_5;
    }

    /* 半傳輸完成中斷 */    
    if((DMA1->LISR & DMA_FLAG_HTIF1_5) != RESET)
    {
        /*
           1、使用此函數要特別注意,第1個參數地址要32字節對齊,第2個參數要是32字節的整數倍。
           2、進入半傳輸完成中斷,當前DMA正在使用緩沖區的后半部分,用戶可以操作前半部分。
        */
        SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[0]), 128);
        
        s_DmaFlag = 1;
        
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_HTIF1_5;
    }

    /* 傳輸錯誤中斷 */
    if((DMA1->LISR & DMA_FLAG_TEIF1_5) != RESET)
    {
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_TEIF1_5;
    }

    /* 直接模式錯誤中斷 */
    if((DMA1->LISR & DMA_FLAG_DMEIF1_5) != RESET)
    {
        /* 清除標志 */
        DMA1->LIFCR = DMA_FLAG_DMEIF1_5;
    }
}

 

45.9 總結

本章節就為大家講解這么多,DMA雙緩沖方式記錄ADC數據還是非常實用的,建議初學者熟練應用這種方案。


免責聲明!

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



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