第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):
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
45.1 初学者重要提示
学习本章节前,务必优先学习第44章,需要对ADC的基础知识和HAL库的几个常用API有个认识。
开发板右上角有个跳线帽,可以让ADC的稳压基准接3.3V或者2.5V,本章例子是接到3.3V。
STM32H7的ADC支持偏移校准和线性度校准。如果使用线性度校准的话,特别要注意此贴的问题:http://www.armbbs.cn/forum.php?mod=viewthread&tid=91436 。
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的电气特性:
通过这个表,我们要了解以下几点知识:
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数据传输的实现思路框图如下:
下面将程序设计中的相关问题逐一为大家做个说明。
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;