第1阶段,上电启动阶段:
这部分在第14章进行了详细说明。
第2阶段,进入main函数:
第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED,串口和ADC。
第2步,周期性的打印ADC采集的多通道数据。
46.7 实验例程说明(MDK)
配套例子:
V7-024-ADC+DMA的多通道采集
实验目的:
学习ADC + DMA的多通道采集实现。
实验内容:
例子默认用的PLL时钟供ADC使用,大家可以通过bsp_adc.c文件开头宏定义切换到AHB时钟。
采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度。
每隔500ms,串口会打印一次。
板子正常运行时LED2闪烁。
PC0引脚位置(稳压基准要短接3.3V):
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
程序设计:
系统栈大小分配:
RAM空间用的DTCM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
46.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 。
STM32H7的ADC多通道并不是同步采样的,本质上是通过内部的多路选择器不断切换实现的,一个采集完毕了才会采集另一个。
46.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。
46.3 ADC驱动设计
ADC做DMA数据传输的实现思路框图如下:
下面将程序设计中的相关问题逐一为大家做个说明。
46.3.1 ADC软件触发
ADC转换既可以选择外部触发也可以选择软件触发。我们这里选择的是软件触发方式的多通道转换,即连续转换序列,软件触发。对应的时序如下(在第44章的2.7小节有详细讲解软件触发和硬件触发的时序):。
ADSTART表示软件启动转换。
ADSTP表示停止转换。
EOC表示一个通道转换结束。
EOS表示所有通道转换结束。
关于这个时序图的解读:
配置为连续转换的话,软件启动ADSTART会开启所有通道转换,全部转换完毕后,继续进行下一轮转换。调用了停止转换ADSTP后,会停止转换。
每个通过转换完毕有个EOC标志,所有通道转换完毕有个EOS标志。
46.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
46.3.3 ADC的DMA配置
由于函数HAL_ADC_Start_DMA封装的DMA传输函数是HAL_DMA_Start_IT。而我们这里仅需要用到DMA传输,而用不到中断,所以不开启对应的NVIC即可,这里使用的是DMA1_Stream1,测量了PC0,Vbat/4,VrefInt和温度四个通道。
1. /*
2. ******************************************************************************************************
3. * 函 数 名: bsp_InitADC
4. * 功能说明: 初始化ADC,采用DMA方式进行多通道采样,采集了PC0, Vbat/4, VrefInt和温度
5. * 形 参: 无
6. * 返 回 值: 无
7. ******************************************************************************************************
8. */
9. void bsp_InitADC(void)
10. {
11. ADC_HandleTypeDef AdcHandle = {0};
12. DMA_HandleTypeDef DMA_Handle = {0};
13. ADC_ChannelConfTypeDef sConfig = {0};
14. GPIO_InitTypeDef GPIO_InitStruct;
15.
16. /* ## - 1 - 配置ADC采样的时钟 ####################################### */
17. #if defined (ADC_CLOCK_SOURCE_PLL)
18. /* 配置PLL2时钟为的72MHz,方便分频产生ADC最高时钟36MHz */
19. RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
20. PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
21. PeriphClkInitStruct.PLL2.PLL2M = 25;
22. PeriphClkInitStruct.PLL2.PLL2N = 504;
23. PeriphClkInitStruct.PLL2.PLL2P = 7;
24. PeriphClkInitStruct.PLL2.PLL2Q = 7;
25. PeriphClkInitStruct.PLL2.PLL2R = 7;
26. PeriphClkInitStruct.PLL2.PLL2RGE = RCC_PLL2VCIRANGE_0;
27. PeriphClkInitStruct.PLL2.PLL2VCOSEL = RCC_PLL2VCOWIDE;
28. PeriphClkInitStruct.PLL2.PLL2FRACN = 0;
29. PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2;
30. if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
31. {
32. Error_Handler(__FILE__, __LINE__);
33. }
34. #elif defined (ADC_CLOCK_SOURCE_AHB)
35.
36. /* 使用AHB时钟的话,无需配置,默认选择*/
37.
38. #endif
39.
40. /* ## - 2 - 配置ADC采样使用的时钟 ####################################### */
41. __HAL_RCC_GPIOC_CLK_ENABLE();
42.
43. GPIO_InitStruct.Pin = GPIO_PIN_0;
44. GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
45. GPIO_InitStruct.Pull = GPIO_NOPULL;
46. HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
47.
48. /* ## - 3 - 配置ADC采样使用的时钟 ####################################### */
49. __HAL_RCC_DMA1_CLK_ENABLE();
50. DMA_Handle.Instance = DMA1_Stream1; /* 使用的DMA1 Stream1 */
51. DMA_Handle.Init.Request = DMA_REQUEST_ADC3; /* 请求类型采用DMA_REQUEST_ADC3 */
52. DMA_Handle.Init.Direction = DMA_PERIPH_TO_MEMORY; /* 传输方向是从外设到存储器*/
53. DMA_Handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址自增禁止 */