你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32H7的ADC应用之定时器触发配合DMA双缓冲

[复制链接]
STMCU小助手 发布时间:2021-10-30 23:35
45.1 初学者重要提示
  学习本章节前,务必优先学习第44章,需要对ADC的基础知识和HAL库的几个常用API有个认识。
  开发板右上角有个跳线帽,可以让ADC的稳压基准接3.3V或者2.5V,本章例子是接到3.3V。
  STM32H7的ADC支持偏移校准和线性度校准。

45.2 ADC稳压基准硬件设计
注:学习前务必优先看第14章的2.1小节,对电源供电框架有个了解。

ADC要采集的准确,就需要有一个稳定的稳压基准源,V7开发板使用的LM285D-2.5,即2.5V的基准源。硬件设计如下:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


关于这个原理图要注意以下问题:

LM285D-2.5输出的是2.5V的稳压基准,原理图这里做了一个特别的处理,同时接了一个上拉电阻到VDDA(3.3V),然后用户可以使用开发板右上角的跳线帽设置Vref选择3.3V稳压还是2.5V稳压。

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


下面再来了解下LM285的电气特性:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


通过这个表,我们要了解以下几点知识:

  LM285的典型值是2.5V,支持的最小值2.462V,最大值2.538V。工作电流是20uA到20mA,温飘是±20ppm/℃。
  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数据传输的实现思路框图如下:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


下面将程序设计中的相关问题逐一为大家做个说明。

45.3.1 触发ADC的定时器选择和配置
ADC转换既可以选择外部触发也可以选择软件触发。定时器属于外部触发方式,使用定时器触发的好处是可以设置任何ADC能够支持的转换频率。

对于ADC1,ADC2,ADC3来说,规则通道支持的外部触发源如下:

  1. #define ADC_EXTERNALTRIG_T1_CC1           ((uint32_t)0x00000000)
  2. #define ADC_EXTERNALTRIG_T1_CC2           ((uint32_t)ADC_CFGR_EXTSEL_0)
  3. #define ADC_EXTERNALTRIG_T1_CC3           ((uint32_t)ADC_CFGR_EXTSEL_1)
  4. #define ADC_EXTERNALTRIG_T2_CC2           ((uint32_t)(ADC_CFGR_EXTSEL_1 | ADC_CFGR_EXTSEL_0))
  5. #define ADC_EXTERNALTRIG_T3_TRGO          ((uint32_t)ADC_CFGR_EXTSEL_2)
  6. #define ADC_EXTERNALTRIG_T4_CC4           ((uint32_t)(ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_0))
  7. #define ADC_EXTERNALTRIG_EXT_IT11         ((uint32_t)(ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_1))
  8. #define ADC_EXTERNALTRIG_T8_TRGO          ((uint32_t)(ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_1 |
  9. ADC_CFGR_EXTSEL_0))
  10. #define ADC_EXTERNALTRIG_T8_TRGO2         ((uint32_t) ADC_CFGR_EXTSEL_3)
  11. #define ADC_EXTERNALTRIG_T1_TRGO          ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_0))
  12. #define ADC_EXTERNALTRIG_T1_TRGO2         ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_1))
  13. #define ADC_EXTERNALTRIG_T2_TRGO          ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_1 | ADC_CFGR_EXTSEL_0))
  14. #define ADC_EXTERNALTRIG_T4_TRGO          ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2))
  15. #define ADC_EXTERNALTRIG_T6_TRGO          ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_0))
  16. #define ADC_EXTERNALTRIG_T15_TRGO         ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_1))
  17. #define ADC_EXTERNALTRIG_T3_CC4           ((uint32_t)(ADC_CFGR_EXTSEL_3 | ADC_CFGR_EXTSEL_2 | ADC_CFGR_EXTSEL_1 | ADC_CFGR_EXTSEL_0))
  18. #define ADC_EXTERNALTRIG_HR1_ADCTRG1      ((uint32_t) ADC_CFGR_EXTSEL_4)
  19. #define ADC_EXTERNALTRIG_HR1_ADCTRG3      ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_0))
  20. #define ADC_EXTERNALTRIG_LPTIM1_OUT       ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_1))
  21. #define ADC_EXTERNALTRIG_LPTIM2_OUT       ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_1| ADC_CFGR_EXTSEL_0))
  22. #define ADC_EXTERNALTRIG_LPTIM3_OUT       ((uint32_t) (ADC_CFGR_EXTSEL_4 | ADC_CFGR_EXTSEL_2))
复制代码


我们这里使用的是TIM1_CC1。

接下来就是TIM1的时钟配置问题,代码如下:

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

  通过宏定义设置选择的时钟源
使用那个时钟源,将另一个注释掉即可:

  1. /* 选择ADC的时钟源 */
  2. #define ADC_CLOCK_SOURCE_AHB     /* 选择AHB时钟源 */
  3. //#define ADC_CLOCK_SOURCE_PLL   /* 选择PLL时钟源 */
复制代码


  PLL2或者AHB时钟源配置
  1. #if defined (ADC_CLOCK_SOURCE_PLL)
  2.     /* 配置PLL2时钟为的72MHz,方便分频产生ADC最高时钟36MHz */
  3.     RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
  4.     PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
  5.     PeriphClkInitStruct.PLL2.PLL2M = 25;
  6.     PeriphClkInitStruct.PLL2.PLL2N = 504;
  7.     PeriphClkInitStruct.PLL2.PLL2P = 7;
  8.     PeriphClkInitStruct.PLL2.PLL2Q = 7;
  9.     PeriphClkInitStruct.PLL2.PLL2R = 7;
  10.     PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0;
  11.     PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
  12.     PeriphClkInitStruct.PLL2.PLL2FRACN = 0;
  13.     PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;
  14.     if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  15.     {
  16.         Error_Handler(__FILE__, __LINE__);  
  17.     }
  18. #elif defined (ADC_CLOCK_SOURCE_AHB)

  19.   /* 使用AHB时钟的话,无需配置,默认选择*/

  20. #endif
复制代码

对于PLL2的时钟输出,直接使用STM32CubeMX里面的时钟树配置即可,效果如下:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


选择PLL2P输出作为ADC时钟源:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


  ADC分频设置
无论是使用AHB时钟还是PLL2时钟都支持分频设置:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


AHB支持下面三种分频设置:

  1. #define ADC_CLOCK_SYNC_PCLK_DIV1   ((uint32_t)ADC_CCR_CKMODE_0)  
  2. #define ADC_CLOCK_SYNC_PCLK_DIV2   ((uint32_t)ADC_CCR_CKMODE_1)
  3. #define ADC_CLOCK_SYNC_PCLK_DIV4   ((uint32_t)ADC_CCR_CKMODE)   

  4. #define ADC_CLOCKPRESCALER_PCLK_DIV1   ADC_CLOCK_SYNC_PCLK_DIV1   /* 这三个仅仅是为了兼容,已经不推荐使用 */
  5. #define ADC_CLOCKPRESCALER_PCLK_DIV2   ADC_CLOCK_SYNC_PCLK_DIV2   
  6. #define ADC_CLOCKPRESCALER_PCLK_DIV4   ADC_CLOCK_SYNC_PCLK_DIV4   
复制代码


PLL2支持下面几种分频设置:

  1. #define ADC_CLOCK_ASYNC_DIV1       ((uint32_t)0x00000000)                                       
  2. #define ADC_CLOCK_ASYNC_DIV2       ((uint32_t)ADC_CCR_PRESC_0)                                 
  3. #define ADC_CLOCK_ASYNC_DIV4       ((uint32_t)ADC_CCR_PRESC_1)                                   
  4. #define ADC_CLOCK_ASYNC_DIV6       ((uint32_t)(ADC_CCR_PRESC_1|ADC_CCR_PRESC_0))                 
  5. #define ADC_CLOCK_ASYNC_DIV8       ((uint32_t)(ADC_CCR_PRESC_2))                                
  6. #define ADC_CLOCK_ASYNC_DIV10      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_0))                 
  7. #define ADC_CLOCK_ASYNC_DIV12      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_1))                 
  8. #define ADC_CLOCK_ASYNC_DIV16      ((uint32_t)(ADC_CCR_PRESC_2|ADC_CCR_PRESC_1|ADC_CCR_PRESC_0))
  9. #define ADC_CLOCK_ASYNC_DIV32      ((uint32_t)(ADC_CCR_PRESC_3))                                
  10. #define ADC_CLOCK_ASYNC_DIV64      ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_0))                 
  11. #define ADC_CLOCK_ASYNC_DIV128     ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_1))               
  12. #define ADC_CLOCK_ASYNC_DIV256     ((uint32_t)(ADC_CCR_PRESC_3|ADC_CCR_PRESC_1|ADC_CCR_PRESC_0))
复制代码


有了这些认识后再看实际的分频配置就好理解了:

  1. #if defined (ADC_CLOCK_SOURCE_PLL)
  2. /* 采用PLL异步时钟,2分频,即72MHz/2 = 36MHz */
  3.     AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_ASYNC_DIV2;     
  4. /* 采用AHB同步时钟,4分频,即200MHz/4 = 50MHz */     
  5. #elif defined (ADC_CLOCK_SOURCE_AHB)
  6.     AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_SYNC_PCLK_DIV4;      
  7. #endif
复制代码



45.3.3 ADC的DMA双缓冲配置
由于函数HAL_ADC_Start_DMA封装的DMA传输函数是HAL_DMA_Start_IT,而不是专门的DMA双缓冲函数HAL_DMAEx_MultiBuferStart_IT。所以要实现双缓冲效果的话,可以使用半传输完成中断和传输完成中断配合实现双缓冲效果。这里使用的是DMA1_Stream1:

  1. 1.    /*
  2. 2.    ******************************************************************************************************
  3. 3.    *    函 数 名: bsp_InitADC
  4. 4.    *    功能说明: 初始化ADC
  5. 5.    *    形    参: 无
  6. 6.    *    返 回 值: 无
  7. 7.    ******************************************************************************************************
  8. 8.    */
  9. 9.    void bsp_InitADC(void)
  10. 10.    {
  11. 11.        ADC_HandleTypeDef   AdcHandle = {0};
  12. 12.        DMA_HandleTypeDef   DmaHandle = {0};   
  13. 13.        ADC_ChannelConfTypeDef   sConfig = {0};
  14. 14.        GPIO_InitTypeDef         GPIO_InitStruct;
  15. 15.   
  16. 16.        
  17. 17.      /* ## - 1 - 配置ADC采样的时钟 ####################################### */
  18. 18.    #if defined (ADC_CLOCK_SOURCE_PLL)
  19. 19.        /* 配置PLL2时钟为的72MHz,方便分频产生ADC最高时钟36MHz */
  20. 20.         RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
  21. 21.        PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
  22. 22.        PeriphClkInitStruct.PLL2.PLL2M = 25;
  23. 23.        PeriphClkInitStruct.PLL2.PLL2N = 504;
  24. 24.        PeriphClkInitStruct.PLL2.PLL2P = 7;
  25. 25.        PeriphClkInitStruct.PLL2.PLL2Q = 7;
  26. 26.        PeriphClkInitStruct.PLL2.PLL2R = 7;
  27. 27.        PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0;
  28. 28.        PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
  29. 29.        PeriphClkInitStruct.PLL2.PLL2FRACN = 0;
  30. 30.        PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;
  31. 31.        if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  32. 32.        {
  33. 33.            Error_Handler(__FILE__, __LINE__);  
  34. 34.        }
  35. 35.    #elif defined (ADC_CLOCK_SOURCE_AHB)
  36. 36.      
  37. 37.      /* 使用AHB时钟的话,无需配置,默认选择*/
  38. 38.      
  39. 39.    #endif
  40. 40.   
  41. 41.        /* ## - 2 - 配置ADC采样使用的采集引脚 ####################################### */
  42. 42.        __HAL_RCC_GPIOC_CLK_ENABLE();
  43. 43.   
  44. 44.        GPIO_InitStruct.Pin = GPIO_PIN_0;
  45. 45.        GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
  46. 46.        GPIO_InitStruct.Pull = GPIO_NOPULL;
  47. 47.        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
  48. 48.   
  49. 49.        /* ## - 3 - 配置ADC采样使用的时钟 ####################################### */
  50. 50.        __HAL_RCC_DMA1_CLK_ENABLE();
  51. 51.        DmaHandle.Instance                 = DMA1_Stream1;            /* 使用的DMA1 Stream1 */
  52. 52.        DmaHandle.Init.Request             = DMA_REQUEST_ADC1;        /* 请求类型采用DMA_REQUEST_ADC1 */  
  53. 53.        DmaHandle.Init.Direction           = DMA_PERIPH_TO_MEMORY;    /* 传输方向是从外设到存储器 */  
  54. 54.        DmaHandle.Init.PeriphInc           = DMA_PINC_DISABLE;        /* 外设地址自增禁止 */
  55. 55.        DmaHandle.Init.MemInc              = DMA_MINC_ENABLE;         /* 存储器地址自增使能 */  
  56. 56.        DmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;/* 外设数据传输位宽选择半字,16bit */     
  57. 57.         DmaHandle.Init.MemDataAlignment    = DMA_MDATAALIGN_HALFWORD;/* 存储器数据传输位宽选半字,16bit */   
  58. 58.        DmaHandle.Init.Mode                = DMA_CIRCULAR;            /* 循环模式 */   
  59. 59.        DmaHandle.Init.Priority            = DMA_PRIORITY_LOW;        /* 优先级低 */  
  60. 60.        DmaHandle.Init.FIFOMode            = DMA_FIFOMODE_DISABLE;    /* 禁止FIFO*/
  61. 61.        DmaHandle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* 禁止FIFO此位不起作用,用于设置阀值 */
  62. 62.        DmaHandle.Init.MemBurst      = DMA_MBURST_SINGLE;       /* 禁止FIFO此位不起作用,用于存储器突发 */
  63. 63.        DmaHandle.Init.PeriphBurst   = DMA_PBURST_SINGLE;      /* 禁止FIFO此位不起作用,用于外设突发 */
  64. 64.     
  65. 65.        /* 初始化DMA */
  66. 66.        if(HAL_DMA_Init(&DmaHandle) != HAL_OK)
  67. 67.        {
  68. 68.            Error_Handler(__FILE__, __LINE__);     
  69. 69.        }
  70. 70.        
  71. 71.        /* 开启DMA1 Stream1的中断 */
  72. 72.        HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 2, 0);
  73. 73.        HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
  74. 74.        
  75. 75.        /* 关联ADC句柄和DMA句柄 */
  76. 76.        __HAL_LINKDMA(&AdcHandle, DMA_Handle, DmaHandle);
  77. 77.   
  78. 78.        /* ## - 4 - 配置ADC ########################################################### */
  79. 79.        __HAL_RCC_ADC12_CLK_ENABLE();
  80. 80.        AdcHandle.Instance = ADC1;
  81. 81.        
  82. 82.    #if defined (ADC_CLOCK_SOURCE_PLL)
  83. 83.        AdcHandle.Init.ClockPrescaler  = ADC_CLOCK_ASYNC_DIV2;     /* 采用PLL异步时钟,2分频,即72MHz/2
  84. 84.                                                                      = 36MHz */
  85. 85.    #elif defined (ADC_CLOCK_SOURCE_AHB)
  86. 86.        AdcHandle.Init.ClockPrescaler  = ADC_CLOCK_SYNC_PCLK_DIV4;  /* 采用AHB同步时钟,4分频,即200MHz/4
  87. 87.                                                                        = 50MHz */
  88. 88.    #endif
  89. 89.        AdcHandle.Init.Resolution            = ADC_RESOLUTION_16B;   /* 16位分辨率 */
  90. 90.        AdcHandle.Init.ScanConvMode          = ADC_SCAN_DISABLE;     /* 禁止扫描,因为仅开了一个通道 */
  91. 91.        AdcHandle.Init.EOCSelection          = ADC_EOC_SINGLE_CONV;  /* EOC转换结束标志 */
  92. 92.        AdcHandle.Init.LowPowerAutoWait      = DISABLE;              /* 禁止低功耗自动延迟特性 */
  93. 93.        AdcHandle.Init.ContinuousConvMode    = DISABLE;            /* 禁止自动转换,采用的定时器触发转换 */
  94. 94.        AdcHandle.Init.NbrOfConversion       = 1;         /* 使用了1个转换通道 */
  95. 95.        AdcHandle.Init.DiscontinuousConvMode = DISABLE;   /* 禁止不连续模式 */
  96. 96.        AdcHandle.Init.NbrOfDiscConversion   = 1;         /* 禁止不连续模式后,此参数忽略,此位是用来配置
  97. 97.                                                             不连续子组中通道数 */
  98. 98.        AdcHandle.Init.ExternalTrigConv      = ADC_EXTERNALTRIG_T1_CC1;            /* 定时器1的CC1触发 */
  99. 99.        AdcHandle.Init.ExternalTrigConvEdge  = ADC_EXTERNALTRIGCONVEDGE_RISING;    /* 上升沿触发 */
  100. 100.        AdcHandle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR; /* DMA循环模式接收ADC
  101. 101.                                                                                       转换的数据 */
  102. 102.        AdcHandle.Init.BoostMode    = ENABLE;                 /* ADC时钟超过20MHz的话,使能boost */
  103. 103.        AdcHandle.Init.Overrun    = ADC_OVR_DATA_OVERWRITTEN; /* ADC转换溢出的话,覆盖ADC的数据寄存器 */
  104. 104.        AdcHandle.Init.OversamplingMode         = DISABLE;    /* 禁止过采样 */
  105. 105.   
  106. 106.        /* 初始化ADC */
  107. 107.        if (HAL_ADC_Init(&AdcHandle) != HAL_OK)
  108. 108.        {
  109. 109.            Error_Handler(__FILE__, __LINE__);
  110. 110.        }
  111. 111.      
  112. 112.        /* 校准ADC,采用偏移校准 */
  113. 113.        if (HAL_ADCEx_Calibration_Start(&AdcHandle, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
  114. 114.        {
  115. 115.            Error_Handler(__FILE__, __LINE__);
  116. 116.        }
  117. 117.      
  118. 118.        /* 配置ADC通道  */
  119. 119.        sConfig.Channel      = ADC_CHANNEL_10;              /* 配置使用的ADC通道 */
  120. 120.        sConfig.Rank         = ADC_REGULAR_RANK_1;          /* 采样序列里的第1个 */
  121. 121.        sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;     /* 采样周期 */
  122. 122.        sConfig.SingleDiff   = ADC_SINGLE_ENDED;            /* 单端输入 */
  123. 123.        sConfig.OffsetNumber = ADC_OFFSET_NONE;             /* 无偏移 */
  124. 124.        sConfig.Offset = 0;                                 /* 无偏移的情况下,此参数忽略 */
  125. 125.   
  126. 126.        if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK)
  127. 127.        {
  128. 128.            Error_Handler(__FILE__, __LINE__);
  129. 129.        }
  130. 130.      
  131. 131.        /* ## - 5 - 配置ADC的定时器触发 ####################################### */
  132. 132.        TIM1_Config();
  133. 133.      
  134. 134.        /* ## - 6 - 启动ADC的DMA方式传输 ####################################### */
  135. 135.        if (HAL_ADC_Start_DMA(&AdcHandle, (uint32_t *)ADCxValues, 128) != HAL_OK)
  136. 136.        {
  137. 137.            Error_Handler(__FILE__, __LINE__);
  138. 138.        }
  139. 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偏移校准
  第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存储区。

  1. /* 配置SRAM的MPU属性为Device或者Strongly Ordered,即关闭Cache */
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  3. MPU_InitStruct.BaseAddress      = 0x60000000;
  4. MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  11. MPU_InitStruct.SubRegionDisable = 0x00;
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
复制代码

  方法二:
设置SRAM的缓冲区做32字节对齐,大小最好也是32字节整数倍,然后调用函数SCB_InvalidateDCache_by_Addr做无效化操作即可,保证CPU读取到的数据是刚更新好的。
本章节配套例子是直接使用的方法二。例子中变量的定义方式如下:

  1. /* 方便Cache类的API操作,做32字节对齐 */
  2. #if defined ( __ICCARM__ )
  3. #pragma location = 0x38000000
  4. uint16_t ADCxValues[128];
  5. #elif defined ( __CC_ARM )
  6. ALIGN_32BYTES(__attribute__((section (".RAM_D3"))) uint16_t ADCxValues[128]);
  7. #endif
复制代码

对于IAR需要#pragma location指定位置,而MDK通过分散加载即可实现,详情看前面第26章,有详细讲解。

45.3.5 DMA中断处理
调用函数HAL_ADC_Start_DMA开启了DMA的传输完成中断,半传输完成中断,传输错误中断和直接模式错误中断。通过传输完成中断和半传输完整中断可以实现双缓冲的处理:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: DMA1_Stream1_IRQHandler
  4. *    功能说明: DMA1 Stream1中断服务程序
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void DMA1_Stream1_IRQHandler(void)
  10. {
  11.     /* 传输完成中断 */
  12.     if((DMA1->LISR & DMA_FLAG_TCIF1_5) != RESET)
  13.     {
  14.         /* 清除标志 */
  15.         DMA1->LIFCR = DMA_FLAG_TCIF1_5;

  16.         /* 当前使用的缓冲0 */
  17.         if((DMA1_Stream1->CR & DMA_SxCR_CT) == RESET)
  18.         {
  19.             /*
  20.                 1、当前正在使用缓冲0,此时可以动态修改缓冲1的数据。
  21.                    比如缓冲区0是IO_Toggle,缓冲区1是IO_Toggle1,那么此时就可以修改IO_Toggle1。
  22.                 2、变量所在的SRAM区已经通过MPU配置为WT模式,更新变量IO_Toggle会立即写入。
  23.                 3、不配置MPU的话,也可以通过Cahce的函数SCB_CleanDCache_by_Addr做Clean操作。
  24.             */

  25.         }
  26.         /* 当前使用的缓冲1 */
  27.         else
  28.         {
  29.              /*
  30.                1、当前正在使用缓冲1,此时可以动态修改缓冲0的数据。
  31.                   比如缓冲区0是IO_Toggle,缓冲区1是IO_Toggle1,那么此时就可以修改IO_Toggle。
  32.                2、变量所在的SRAM区已经通过MPU配置为WT模式,更新变量IO_Toggle会立即写入。
  33.                3、不配置MPU的话,也可以通过Cahce的函数SCB_CleanDCache_by_Addr做Clean操作。
  34.             */

  35.         }
  36.     }

  37.     /* 半传输完成中断 */   
  38.     if((DMA1->LISR & DMA_FLAG_HTIF1_5) != RESET)
  39.     {
  40.         /* 清除标志 */
  41.         DMA1->LISR = DMA_FLAG_HTIF1_5;
  42.     }

  43.     /* 传输错误中断 */
  44.     if((DMA1->LISR & DMA_FLAG_TEIF1_5) != RESET)
  45.     {
  46.         /* 清除标志 */
  47.         DMA1->LISR = DMA_FLAG_TEIF1_5;
  48.     }

  49.     /* 直接模式错误中断 */
  50.     if((DMA1->LISR & DMA_FLAG_DMEIF1_5) != RESET)
  51.     {
  52.         /* 清除标志 */
  53.         DMA1->LISR = DMA_FLAG_DMEIF1_5;
  54.     }
  55. }

  56. /*
  57. *********************************************************************************************************
  58. *    函 数 名: DMA1_Stream1_IRQHandler
  59. *    功能说明: DMA1 Stream1中断服务程序
  60. *    形    参: 无
  61. *    返 回 值: 无
  62. *********************************************************************************************************
  63. */
  64. void DMA1_Stream1_IRQHandler(void)
  65. {
  66.     /* 传输完成中断 */
  67.     if((DMA1->LISR & DMA_FLAG_TCIF1_5) != RESET)
  68.     {

  69.         HC574_TogglePin(GPIO_PIN_23);

  70.         /*
  71.            1、使用此函数要特别注意,第1个参数地址要32字节对齐,第2个参数要是32字节的整数倍。
  72.            2、进入传输完成中断,当前DMA正在使用缓冲区的前半部分,用户可以操作后半部分。
  73.         */
  74.         SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[64]), 128);

  75.         s_DmaFlag = 2;

  76.         /* 清除标志 */
  77.         DMA1->LIFCR = DMA_FLAG_TCIF1_5;
  78.     }

  79.     /* 半传输完成中断 */   
  80.     if((DMA1->LISR & DMA_FLAG_HTIF1_5) != RESET)
  81.     {
  82.         /*
  83.            1、使用此函数要特别注意,第1个参数地址要32字节对齐,第2个参数要是32字节的整数倍。
  84.            2、进入半传输完成中断,当前DMA正在使用缓冲区的后半部分,用户可以操作前半部分。
  85.         */
  86.         SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[0]), 128);

  87.         s_DmaFlag = 1;

  88.         /* 清除标志 */
  89.         DMA1->LIFCR = DMA_FLAG_HTIF1_5;
  90.     }

  91.     /* 传输错误中断 */
  92.     if((DMA1->LISR & DMA_FLAG_TEIF1_5) != RESET)
  93.     {
  94.         /* 清除标志 */
  95.         DMA1->LIFCR = DMA_FLAG_TEIF1_5;
  96.     }

  97.     /* 直接模式错误中断 */
  98.     if((DMA1->LISR & DMA_FLAG_DMEIF1_5) != RESET)
  99.     {
  100.         /* 清除标志 */
  101.         DMA1->LIFCR = DMA_FLAG_DMEIF1_5;
  102.     }
  103. }
复制代码


注释的比较清楚。如果输出的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是最方便的,可以在中断服务程序里面发消息给任务,让数据可以得到及时处理。而裸机方式的话,需要用户实时查询变量,检测到有数据了再进行处理。具体实现代码如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_GetAdcValues
  4. *    功能说明: 获取ADC的数据并打印
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_GetAdcValues(void)
  10. {
  11.     uint32_t values;
  12.     float  temp;

  13.     /* 当前DMA操作是后半个缓冲,读取前半个缓冲的前4个数值求平均 */
  14.     if(s_DmaFlag == 1)
  15.     {
  16.         DISABLE_INT();
  17.         s_DmaFlag = 0;
  18.         values = (ADCxValues[0] + ADCxValues[1] + ADCxValues[2] + ADCxValues[3])/4;

  19.         ENABLE_INT();
  20.     }
  21.     /* 当前DMA操作是后前个缓冲,读取后半个缓冲的前4个数值求平均 */
  22.     else if(s_DmaFlag == 2)
  23.     {
  24.         DISABLE_INT();
  25.         s_DmaFlag = 0;
  26.         values = (ADCxValues[64] + ADCxValues[65] + ADCxValues[66] + ADCxValues[67])/4;
  27.         ENABLE_INT();
  28.     }

  29.     /* 打印读出的串口值 */
  30.     temp = values *3.3 / 65536;

  31.     printf("ADCxValues = %d, %5.3f\r\n", values, temp);
  32. }
复制代码


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的触发源。

注意事项:

关于此函数的讲解在本章的2.1小节。
函数前面的static表示限制在bsp_adc.c文件里面调用。
45.4.2 函数bsp_InitADC
函数原型:

void bsp_InitADC(void)

函数描述:

此函数用于配置定时器触发ADC做DMA传输。

注意事项:

关于此函数的讲解在本章2.2小节。
使用举例:

作为初始化函数,直接在bsp.c文件的bsp_Init函数里面调用即可。

45.4.3 函数bsp_GetAdcValues
函数原型:

void bsp_GetAdcValues(void)

函数描述:

此函数用于获取ADC的转换数据。

注意事项:

关于此函数的讲解在本章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 实验例程设计框架
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


  第1阶段,上电启动阶段:

这部分在第4章进行了详细说明。
  第2阶段,进入main函数:

  第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED,串口和ADC。
  第2步,应用程序设计部分,周期性的打印数据,方便查看。
  第3步,DMA中断,以双缓冲方式存储ADC数据。
45.7 实验例程说明(MDK)
配套例子:

V7-019_ADC定时器触发+DMA双缓冲实现

实验目的:

学习ADC定时器触发 + DMA双缓冲的实现。
实验内容:

例子默认用的AHB时钟供ADC使用,大家可以通过bsp_adc.c文件开头宏定义切换到PLL2专用时钟。
使用的TIM1的OC1作为ADC的外部触发源,触发速度是100KHz,即ADC的采样率也是100KHz。
使用DMA的半传输完成中断和传输完成中断实现数据的双缓冲更新。
采集引脚使用的PC0,另外特别注意开发板上的Vref稳压基准跳线帽短接的3.3V。
每隔500ms,串口会打印一次。
板子正常运行时LED2闪烁。
PC0引脚位置(稳压基准要短接3.3V):

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


程序设计:

  系统栈大小分配:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


  RAM空间用的DTCM:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.     MPU_Config();

  13.     /* 使能L1 Cache */
  14.     CPU_CACHE_Enable();

  15.     /*
  16.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  17.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  18.        - 设置NVIV优先级分组为4。
  19.      */
  20.     HAL_Init();

  21.     /*
  22.        配置系统时钟到400MHz
  23.        - 切换使用HSE。
  24.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  25.     */
  26.     SystemClock_Config();

  27.     /*
  28.        Event Recorder:
  29.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  30.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  31.     */   
  32. #if Enable_EventRecorder == 1  
  33.     /* 初始化EventRecorder并开启 */
  34.     EventRecorderInitialize(EventRecordAll, 1U);
  35.     EventRecorderStart();
  36. #endif

  37.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  38.     bsp_InitTimer();      /* 初始化滴答定时器 */
  39.     bsp_InitUart();    /* 初始化串口 */
  40.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
  41.     bsp_InitLed();        /* 初始化LED */   
  42. bsp_InitADC();    /* 初始化ADC */
  43. }

复制代码

  MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和D3域的SRAM4。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: MPU_Config
  4. *    功能说明: 配置MPU
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.     MPU_Region_InitTypeDef MPU_InitStruct;

  12.     /* 禁止 MPU */
  13.     HAL_MPU_Disable();

  14.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  15.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  16.     MPU_InitStruct.BaseAddress      = 0x24000000;
  17.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  18.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  19.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  20.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  21.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  22.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  23.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  24.     MPU_InitStruct.SubRegionDisable = 0x00;
  25.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  26.     HAL_MPU_ConfigRegion(&MPU_InitStruct);


  27.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  28.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  29.     MPU_InitStruct.BaseAddress      = 0x60000000;
  30.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  31.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  32.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  33.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
  34.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  35.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  36.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  37.     MPU_InitStruct.SubRegionDisable = 0x00;
  38.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  39.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  40. /* 配置SRAM4的属性为Write through, read allocate,no write allocate */
  41.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  42.     MPU_InitStruct.BaseAddress      = 0x38000000;
  43.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  44.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  45.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
  46.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  47.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  48.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
  49.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  50.     MPU_InitStruct.SubRegionDisable = 0x00;
  51.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  52.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  53.     /*使能 MPU */
  54.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  55. }

  56. /*
  57. *********************************************************************************************************
  58. *    函 数 名: CPU_CACHE_Enable
  59. *    功能说明: 使能L1 Cache
  60. *    形    参: 无
  61. *    返 回 值: 无
  62. *********************************************************************************************************
  63. */
  64. static void CPU_CACHE_Enable(void)
  65. {
  66.     /* 使能 I-Cache */
  67.     SCB_EnableICache();

  68.     /* 使能 D-Cache */
  69.     SCB_EnableDCache();
  70. }
复制代码


  主功能:

主程序实现如下操作:

  每隔500ms打印一次PC0引脚的采集值。
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: main
  4. *    功能说明: c程序入口
  5. *    形    参: 无
  6. *    返 回 值: 错误代码(无需处理)
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.     uint8_t ucKeyCode;     /* 按键代码 */   

  12. #if defined ( __CC_ARM )   
  13.     TempValues1 = 0; /* 避免MDK警告 */  
  14.     TempValues2 = 0;   
  15. #endif

  16.     bsp_Init();          /* 硬件初始化 */

  17.     PrintfLogo();      /* 打印例程名称和版本等信息 */
  18.     PrintfHelp();      /* 打印操作提示 */

  19.     bsp_StartAutoTimer(0, 500);    /* 启动1个500ms的自动重装的定时器 */

  20.     /* 进入主程序循环体 */
  21.     while (1)
  22.     {
  23.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */

  24.         /* 判断定时器超时时间 */
  25.         if (bsp_CheckTimer(0))   
  26.         {
  27.             /* 每隔500ms 进来一次 */  
  28.             bsp_LedToggle(2);

  29.             /*
  30.                这里仅仅是为了展示方便,500ms更新一次,如果是实际工程里面应用
  31.                裸机工程: 要实时调用下面的函数读取双缓冲里面的数据。
  32.                RTOS工程:要在DMA的中断服务程序里面给ADC任务发消息,让ADC任务可以及时读取数据。
  33.             */
  34.             bsp_GetAdcValues();
  35.         }

  36.         /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
  37.         ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
  38.         if (ucKeyCode != KEY_NONE)
  39.         {
  40.             switch (ucKeyCode)
  41.             {
  42.                 case KEY_DOWN_K1:            /* K1键按下 */
  43.                     printf(" K1键按下 \r\n");
  44.                     break;

  45.                 default:
  46.                     /* 其它的键值不处理 */
  47.                     break;
  48.             }
  49.         }
  50.     }
  51. }
复制代码


DMA中断处理:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: DMA1_Stream1_IRQHandler
  4. *    功能说明: DMA1 Stream1中断服务程序
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void DMA1_Stream1_IRQHandler(void)
  10. {
  11.     /* 传输完成中断 */
  12.     if((DMA1->LISR & DMA_FLAG_TCIF1_5) != RESET)
  13.     {

  14.         HC574_TogglePin(GPIO_PIN_23);

  15.         /*
  16.            1、使用此函数要特别注意,第1个参数地址要32字节对齐,第2个参数要是32字节的整数倍。
  17.            2、进入传输完成中断,当前DMA正在使用缓冲区的前半部分,用户可以操作后半部分。
  18.         */
  19.         SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[64]), 128);

  20.         s_DmaFlag = 2;

  21.         /* 清除标志 */
  22.         DMA1->LIFCR = DMA_FLAG_TCIF1_5;
  23.     }

  24.     /* 半传输完成中断 */   
  25.     if((DMA1->LISR & DMA_FLAG_HTIF1_5) != RESET)
  26.     {
  27.         /*
  28.            1、使用此函数要特别注意,第1个参数地址要32字节对齐,第2个参数要是32字节的整数倍。
  29.            2、进入半传输完成中断,当前DMA正在使用缓冲区的后半部分,用户可以操作前半部分。
  30.         */
  31.         SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[0]), 128);

  32.         s_DmaFlag = 1;

  33.         /* 清除标志 */
  34.         DMA1->LIFCR = DMA_FLAG_HTIF1_5;
  35.     }

  36.     /* 传输错误中断 */
  37.     if((DMA1->LISR & DMA_FLAG_TEIF1_5) != RESET)
  38.     {
  39.         /* 清除标志 */
  40.         DMA1->LIFCR = DMA_FLAG_TEIF1_5;
  41.     }

  42.     /* 直接模式错误中断 */
  43.     if((DMA1->LISR & DMA_FLAG_DMEIF1_5) != RESET)
  44.     {
  45.         /* 清除标志 */
  46.         DMA1->LIFCR = DMA_FLAG_DMEIF1_5;
  47.     }
  48. }
复制代码


45.8 实验例程说明(IAR)
配套例子:

V7-019_ADC定时器触发+DMA双缓冲实现

实验目的:

学习ADC定时器触发 + DMA双缓冲的实现。
实验内容:

例子默认用的AHB时钟供ADC使用,大家可以通过bsp_adc.c文件开头宏定义切换到PLL2专用时钟。
使用的TIM1的OC1作为ADC的外部触发源,触发速度是100KHz,即ADC的采样率也是100KHz。
使用DMA的半传输完成中断和传输完成中断实现数据的双缓冲更新。
采集引脚使用的PC0,另外特别注意开发板上的Vref稳压基准跳线帽短接的3.3V。
每隔500ms,串口会打印一次。
板子正常运行时LED2闪烁。
PC0引脚位置(稳压基准要短接3.3V):



上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


程序设计:

  系统栈大小分配:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


  RAM空间用的DTCM:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png


  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.     MPU_Config();

  13.     /* 使能L1 Cache */
  14.     CPU_CACHE_Enable();

  15.     /*
  16.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  17.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  18.        - 设置NVIV优先级分组为4。
  19.      */
  20.     HAL_Init();

  21.     /*
  22.        配置系统时钟到400MHz
  23.        - 切换使用HSE。
  24.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  25.     */
  26.     SystemClock_Config();

  27.     /*
  28.        Event Recorder:
  29.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  30.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  31.     */   
  32. #if Enable_EventRecorder == 1  
  33.     /* 初始化EventRecorder并开启 */
  34.     EventRecorderInitialize(EventRecordAll, 1U);
  35.     EventRecorderStart();
  36. #endif

  37.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  38.     bsp_InitTimer();      /* 初始化滴答定时器 */
  39.     bsp_InitUart();    /* 初始化串口 */
  40.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
  41.     bsp_InitLed();        /* 初始化LED */   
  42. bsp_InitADC();    /* 初始化ADC */
  43. }
复制代码



  MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM),FMC的扩展IO区和D3域的SRAM4。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: MPU_Config
  4. *    功能说明: 配置MPU
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.     MPU_Region_InitTypeDef MPU_InitStruct;

  12.     /* 禁止 MPU */
  13.     HAL_MPU_Disable();

  14.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  15.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  16.     MPU_InitStruct.BaseAddress      = 0x24000000;
  17.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  18.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  19.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  20.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  21.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  22.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  23.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  24.     MPU_InitStruct.SubRegionDisable = 0x00;
  25.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  26.     HAL_MPU_ConfigRegion(&MPU_InitStruct);


  27.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  28.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  29.     MPU_InitStruct.BaseAddress      = 0x60000000;
  30.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  31.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  32.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  33.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
  34.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  35.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  36.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  37.     MPU_InitStruct.SubRegionDisable = 0x00;
  38.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  39.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  40. /* 配置SRAM4的属性为Write through, read allocate,no write allocate */
  41.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  42.     MPU_InitStruct.BaseAddress      = 0x38000000;
  43.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  44.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  45.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
  46.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  47.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  48.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
  49.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  50.     MPU_InitStruct.SubRegionDisable = 0x00;
  51.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  52.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  53.     /*使能 MPU */
  54.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  55. }

  56. /*
  57. *********************************************************************************************************
  58. *    函 数 名: CPU_CACHE_Enable
  59. *    功能说明: 使能L1 Cache
  60. *    形    参: 无
  61. *    返 回 值: 无
  62. *********************************************************************************************************
  63. */
  64. static void CPU_CACHE_Enable(void)
  65. {
  66.     /* 使能 I-Cache */
  67.     SCB_EnableICache();

  68.     /* 使能 D-Cache */
  69.     SCB_EnableDCache();
  70. }


  71.   主功能:

  72. 主程序实现如下操作:

  73. 每隔500ms打印一次PC0引脚的采集值。
  74. /*
  75. *********************************************************************************************************
  76. *    函 数 名: main
  77. *    功能说明: c程序入口
  78. *    形    参: 无
  79. *    返 回 值: 错误代码(无需处理)
  80. *********************************************************************************************************
  81. */
  82. int main(void)
  83. {
  84.     uint8_t ucKeyCode;     /* 按键代码 */   

  85. #if defined ( __CC_ARM )   
  86.     TempValues1 = 0; /* 避免MDK警告 */  
  87.     TempValues2 = 0;   
  88. #endif

  89.     bsp_Init();          /* 硬件初始化 */

  90.     PrintfLogo();      /* 打印例程名称和版本等信息 */
  91.     PrintfHelp();      /* 打印操作提示 */

  92.     bsp_StartAutoTimer(0, 500);    /* 启动1个500ms的自动重装的定时器 */

  93.     /* 进入主程序循环体 */
  94.     while (1)
  95.     {
  96.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */

  97.         /* 判断定时器超时时间 */
  98.         if (bsp_CheckTimer(0))   
  99.         {
  100.             /* 每隔500ms 进来一次 */  
  101.             bsp_LedToggle(2);

  102.             /*
  103.                这里仅仅是为了展示方便,500ms更新一次,如果是实际工程里面应用
  104.                裸机工程: 要实时调用下面的函数读取双缓冲里面的数据。
  105.                RTOS工程:要在DMA的中断服务程序里面给ADC任务发消息,让ADC任务可以及时读取数据。
  106.             */
  107.             bsp_GetAdcValues();
  108.         }

  109.         /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
  110.         ucKeyCode = bsp_GetKey();    /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
  111.         if (ucKeyCode != KEY_NONE)
  112.         {
  113.             switch (ucKeyCode)
  114.             {
  115.                 case KEY_DOWN_K1:            /* K1键按下 */
  116.                     printf(" K1键按下 \r\n");
  117.                     break;

  118.                 default:
  119.                     /* 其它的键值不处理 */
  120.                     break;
  121.             }
  122.         }
  123.     }
  124. }
复制代码


DMA中断处理:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: DMA1_Stream1_IRQHandler
  4. *    功能说明: DMA1 Stream1中断服务程序
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void DMA1_Stream1_IRQHandler(void)
  10. {
  11.     /* 传输完成中断 */
  12.     if((DMA1->LISR & DMA_FLAG_TCIF1_5) != RESET)
  13.     {

  14.         HC574_TogglePin(GPIO_PIN_23);

  15.         /*
  16.            1、使用此函数要特别注意,第1个参数地址要32字节对齐,第2个参数要是32字节的整数倍。
  17.            2、进入传输完成中断,当前DMA正在使用缓冲区的前半部分,用户可以操作后半部分。
  18.         */
  19.         SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[64]), 128);

  20.         s_DmaFlag = 2;

  21.         /* 清除标志 */
  22.         DMA1->LIFCR = DMA_FLAG_TCIF1_5;
  23.     }

  24.     /* 半传输完成中断 */   
  25.     if((DMA1->LISR & DMA_FLAG_HTIF1_5) != RESET)
  26.     {
  27.         /*
  28.            1、使用此函数要特别注意,第1个参数地址要32字节对齐,第2个参数要是32字节的整数倍。
  29.            2、进入半传输完成中断,当前DMA正在使用缓冲区的后半部分,用户可以操作前半部分。
  30.         */
  31.         SCB_InvalidateDCache_by_Addr((uint32_t *)(&ADCxValues[0]), 128);

  32.         s_DmaFlag = 1;

  33.         /* 清除标志 */
  34.         DMA1->LIFCR = DMA_FLAG_HTIF1_5;
  35.     }

  36.     /* 传输错误中断 */
  37.     if((DMA1->LISR & DMA_FLAG_TEIF1_5) != RESET)
  38.     {
  39.         /* 清除标志 */
  40.         DMA1->LIFCR = DMA_FLAG_TEIF1_5;
  41.     }

  42.     /* 直接模式错误中断 */
  43.     if((DMA1->LISR & DMA_FLAG_DMEIF1_5) != RESET)
  44.     {
  45.         /* 清除标志 */
  46.         DMA1->LIFCR = DMA_FLAG_DMEIF1_5;
  47.     }
  48. }
复制代码



45.9 总结
本章节就为大家讲解这么多,DMA双缓冲方式记录ADC数据还是非常实用的,建议初学者熟练应用这种方案。


aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2NvbW1vbi8xMzc5MTA3LzIwMjAwMS8xMzc5MTA3LTIw.png
收藏 评论0 发布时间:2021-10-30 23:35

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版