本帖最后由 正点原子运营 于 2023-8-3 15:56 编辑
第三十一章 ADC实验
1)实验平台:正点原子探索者STM32F407开发板
2) 章节摘自【正点原子】STM32F407开发指南 V1.1
3)购买链接:https://detail.tmall.com/item.htm?id=609294673401
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32f407_explorerV3.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)STM32技术交流QQ群:151941872
31.4 多通道ADC采集(DMA读取)实验本实验我们来学习使用规则多通道的连续转换模式,并且使用DMA读取ADC的数据。
31.4.1 ADC寄存器本实验我们很多的设置和单通道ADC采集(DMA读取)实验是一样的,所以下面介绍寄存器的时候我们不会继续全部都介绍,而是针对性选择与单通道ADC采集(DMA读取)实验不同设置的ADC_SQRx寄存器进行介绍,其他的配置基本一样的。另外我们用到DMA读取数据,配置上和单通道ADC采集(DMA读取)实验是一样的。
ADC规则序列寄存器有四个(ADC_SQR1~ADC_SQR3),具体怎么配置,需要看我们用多少个通道,比如本实验我们使用6个通道同时采集ADC数据,具体配置如下:
l ADC规则序列寄存器1(ADC_SQR1) ADC规则序列寄存器1描述如图31.4.1.1所示: 首先确认是使用六个通道,L[3:0]位:用于存储规则序列的长度,取值范围:0~16,表示规则序列长度为:1~16。我们这里使用了6个通道,所以设置这几个位的值为5即可。
SQ13~SQ16表示规则序列中的第13~16个序列的转换通道(0~17),每个规则序列的转换通道,可以由SQx(x=1~16)指定,比如我们设置:SQ13[4:0]=1,就表示规则序列13的转换通道为1(ADC1/2/3_CH1)。SQ1~SQ12由寄存器ADC_SQR2~3控制。
SQ1~SQ16的用法,下面我们来看看本实验是怎么设置的:SQ1位赋值为0、SQ2位赋值为1、SQ3位赋值为1、SQ4位赋值为3、SQ5位赋值为4、SQ6位赋值为5,即规则序列1到6分别对应的通道是0到5。SQ1~SQ6位都是在ADC_SQR3寄存器中配置。
31.4.2 硬件设计1. 例程功能 使用ADC1采集(DMA读取)通道0\1\2\3\4\5的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA0\PA1\PA2\PA3\PA4\PA5到你想测量的电压源(0~3.3V),然后通过TFTLCD显示的电压值。LED0闪烁,提示程序运行。
2. 硬件资源 1)LED灯 LED0 –PF9 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子 2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)ADC1 : 通道0–PA0、通道1–PA1、通道2–PA2、 通道3–PA3、通道4–PA4、通道5–PA5 5)DMA(DMA2数据流7外设请求通道0)
3. 原理图 ADC和DMA属于STM32F407内部资源,实际上我们只需要软件设置就可以正常工作,不过我们需要在外部连接其端口到被测电压上面。本实验,我们通过ADC1的通道0\1\2\3\4\5来采集外部电压值,并通过DMA来读取。
31.4.3 程序设计31.4.3.1 ADC的HAL库驱动 本实验用到的ADC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。下面介绍多通道ADC采集(DMA读取)配置步骤。
多通道ADC采集(DMA读取)配置步骤1)开启ADCx和通道输出的GPIO时钟,配置该IO口的复用功能输出 首先开启ADCx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到ADC1通道0、1、2、3、4、5,对应IO是PA0、PA1、PA2、PA3、PA4和PA5,它们的时钟开启方法如下: - __HAL_RCC_ADC1_CLK_ENABLE (); /* 使能ADC1时钟 */
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */
复制代码IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化ADCx,配置其工作参数 通过HAL_ADC_Init函数来设置ADCx时钟分频系数、分辨率、模式、扫描方式、对齐方式等信息。 注意:该函数会调用:HAL_ADC_MspInit回调函数来完成对ADC底层以及其输入通道IO的初始化,包括:ADC及GPIO时钟使能、GPIO模式设置等。这里为了后续的方便管理和理解,我们用adc_nch_dma_init函数来完成GPIO时钟使能、GPIO模式设置等功能,所以没有重定义该回调函数。
3)配置ADC通道并启动AD转换器 在HAL库中,通过HAL_ADC_ConfigChannel函数来设置配置ADC的通道,根据需求设置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。这里配置多通道输出,需要多次调用该函数。 配置好ADC通道之后,通过HAL_ADC_Start函数启动AD转换器。
4)初始化DMA 通过HAL_DMA_Init函数初始化DMA,包括配置通道,外设地址,存储器地址,传输数据量等。 HAL库为了处理各类外设的DMA请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接DMA和外设句柄。这个宏定义为__HAL_LINKDMA。
5)使能DMA对应数据流中断,配置DMA中断优先级,使能ADC,使能并启动DMA 通过HAL_ADC_Start函数开启ADC转换。 通过HAL_DMA_Start_IT函数启动DMA读取,使能DMA中断。 通过HAL_NVIC_EnableIRQ函数使能DMA数据流中断。 通过HAL_NVIC_SetPriority函数设置中断优先级。
6)编写中断服务函数 DMA中断对于每个数据流都有一个中断服务函数,比如DMA2_Stream4的中断服务函数为DMA2_Stream4_IRQHandler。HAL库提供了通用DMA中断处理函数HAL_DMA_IRQHandler,在该函数内部,会对DMA传输状态进行分析,然后调用相应的中断处理回调函数。
31.4.3.2 程序流程图 图31.4.3.2.1 多通道ADC采集(DMA读取)实验程序流程图 31.4.3.3 程序解析 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ADC驱动源码包括两个文件:adc.c和adc.h。本实验代码在单通道ADC采集(DMA读取)实验代码上进行追加。
在本实验中adc.h文件只是添加了一些函数声明,下面直接开始介绍adc.c的程序,首先是ADC的N通道(6通道) DMA读取初始化函数。 - /**
- *@brief ADC N通道(6通道) DMA读取 初始化函数
- * @note 本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置
- * 另外,由于本函数用到了6个通道,宏定义会比较多内容,因此,本函数就不采用宏定义的
- * 方式来修改通道了,直接在本函数里面修改,这里我们默认使用PA0~PA5这6个通道.
- *
- * 注意: 本函数还是使用 ADC_ADCX(默认=ADC1)和ADC_ADCX_DMASx(DMA2_Stream4)
- * 及其相关定义
- * 不要乱修改adc.h里面的这两部分内容, 必须在理解原理的基础上进行修改, 否则可能导
- * 致无法正常使用.
- *
- *@param mar : 存储器地址
- *@retval 无
- */
- voidadc_nch_dma_init(uint32_t mar)
- {
- ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADCx时钟 */
-
- if ((uint32_t)ADC_ADCX_DMASx > (uint32_t)DMA2)
- /* 大于DMA1_Stream7,则为DMA2 */
- {
- __HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */
- }
- else
- {
- __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */
- }
- /* DMA配置 */
- g_dma_nch_adc_handle.Instance = ADC_ADCX_DMASx; /* 设置DMA数据流*/
- g_dma_nch_adc_handle.Init.Channel = DMA_CHANNEL_0; /* 设置DMA通道 */
- /* DIR = 1 , 外设到存储器模式 */
- g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
- g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */
- g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */
- /* 外设数据长度:16位 */
- g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
- /* 存储器数据长度:16位 */
- g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
- g_dma_nch_adc_handle.Init.Mode = DMA_NORMAL /* 外设流控模式 */
- g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */
- HAL_DMA_Init(&g_dma_nch_adc_handle); /* 初始化DMA */
- /* 配置DMA传输参数 */
- HAL_DMA_Start(&g_dma_nch_adc_handle, (uint32_t)&ADC_ADCX->DR, mar, 0);
- /* 设置ADC对应的DMA */
- g_adc_nch_dma_handle.DMA_Handle = &g_dma_nch_adc_handle;
- g_adc_nch_dma_handle.Instance = ADC_ADCX;
- /* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */
- g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
- g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 12位模式 */
- g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 右对齐 */
- g_adc_nch_dma_handle.Init.ScanConvMode = ENABLE; /* 扫描模式 */
- /* 连续转换模式,转换完成之后接着继续转换 */
- g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE;
- /* 禁止不连续采样模式 */
- g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE;
- /* 使用转换通道数,需根据实际转换通道去设置 */
- g_adc_nch_dma_handle.Init.NbrOfConversion = ADC_CH_NUM;
- /* 不连续采样通道数为0 */
- g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0;
- /* 软件触发 */
- g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
- g_adc_nch_dma_handle.Init.ExternalTrigConvEdge =
- ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发, 此位忽略 */
- /* 开启DMA连续转换 */
- g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE;
- HAL_ADC_Init(&g_adc_nch_dma_handle); /* 初始化ADC */
- adc_nch_dma_gpio_init(); /* GPIO 初始化 */
- /* 设置采样规则序列1~6 */
- adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH0, 1,
- ADC_SAMPLETIME_480CYCLES);
- adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH1, 2,
- ADC_SAMPLETIME_480CYCLES);
- adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH2, 3,
- ADC_SAMPLETIME_480CYCLES);
- adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH3, 4,
- ADC_SAMPLETIME_480CYCLES);
- adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH4, 5,
- ADC_SAMPLETIME_480CYCLES);
- adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH5, 6,
- ADC_SAMPLETIME_480CYCLES);
- HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3);
- /* 设置DMA中断优先级为3,子优先级为3 */
- HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn); /* 使能DMA中断 */
- /* 开始DMA数据传输 */
- HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar, sizeof(uint16_t));
- /* TCIE=1, 使能传输完成中断 */
- __HAL_DMA_ENABLE_IT(&g_dma_nch_adc_handle, DMA_IT_TC);
- }
复制代码该函数比较长,很多配置和单通道ADC采集(DMA读取)实验是一样的,就不全部讲解,下面对不同点说明一下。
在ADC的初始化中,直接把6个通道对应的GPIO口初始化放在函数内部实现。ADC_HandleTypeDef结构体的配置与单通道ADC采集(DMA读取)实验主要不同的是我们使用了6个转换通道,然后需要对每个通道ADC_ChannelConfTypeDef结构体进行配置,用HAL_ADC_ConfigChannel函数来初始化。
DMA初始化过程是一样的。下面介绍多通道ADC的gpio初始化函数,其定义如下: - /**
- *@brief 多通道ADC的gpio初始化函数
- *@param 无
- *@note 此函数会被adc_nch_dma_init()调用
- *@note PA0-ADC_CHANNEL_0、PA1-ADC_CHANNEL_1、PA2-ADC_CHANNEL_2
- * PA3-ADC_CHANNEL_3、PA4-ADC_CHANNEL_4、PA5-ADC_CHANNEL_5
- * @retval 无
- */
- voidadc_nch_dma_gpio_init(void)
- {
- GPIO_InitTypeDef gpio_init_struct;
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA引脚时钟 */
- gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3
- | GPIO_PIN_4 | GPIO_PIN_5; /* GPIOA0~5 */
- gpio_init_struct.Mode = GPIO_MODE_ANALOG; /* 模拟 */
- gpio_init_struct.Pull = GPIO_NOPULL; /* 不带上下拉 */
- HAL_GPIO_Init(GPIOA, &gpio_init_struct);
- }
复制代码该函数初始化GPIOA0~5,都是不带上下拉的模拟模式。此外本实验还要用到上个实验的adc_dma_enable函数和ADC_ADCX_DMASx_IRQHandler中断服务函数,所以这两个函数放在ADC实验公共部分程序里面,分别大家使用和移植,这里就不列出来,请回顾上个实验的介绍。
最后在main.c里面编写如下代码: - #define ADC_DMA_BUF_SIZE 50 * 6 /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍 */
- uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */
- extern uint8_tg_adc_dma_sta; /* DMA传输状态标志, 0, 未完成; 1, 已完成 */
- int main(void)
- {
- uint16_t i, j;
- uint16_t adcx;
- uint32_t sum;
- float temp;
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- adc_nch_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "ADC 6CH DMATEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 12, 12, "ADC1_CH0_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 122, 200, 12, 12, "ADC1_CH0_VOL:0.000V", BLUE);
-
- lcd_show_string(30, 140, 200, 12, 12, "ADC1_CH1_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 152, 200, 12, 12, "ADC1_CH1_VOL:0.000V", BLUE);
- lcd_show_string(30, 170, 200, 12, 12, "ADC1_CH2_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 182, 200, 12, 12, "ADC1_CH2_VOL:0.000V", BLUE);
- lcd_show_string(30, 200, 200, 12, 12, "ADC1_CH3_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 212, 200, 12, 12, "ADC1_CH3_VOL:0.000V", BLUE);
- lcd_show_string(30, 230, 200, 12, 12, "ADC1_CH4_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 242, 200, 12, 12, "ADC1_CH4_VOL:0.000V", BLUE);
- lcd_show_string(30, 260, 200, 12, 12, "ADC1_CH5_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 272, 200, 12, 12, "ADC1_CH5_VOL:0.000V", BLUE);
- adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */
- while (1)
- {
- if (g_adc_dma_sta == 1)
- {
- /* 循环显示通道0~通道5的结果 */
- for(j = 0; j < 6; j++) /* 遍历6个通道 */
- {
- sum= 0; /* 清零 */
- for (i = 0; i < ADC_DMA_BUF_SIZE / 6; i++)
- {/* 每个通道采集了10次数据,进行10次累加 */
- sum +=g_adc_dma_buf[(6 * i) + j]; /* 相同通道的转换数据累加 */
- }
- adcx = sum / (ADC_DMA_BUF_SIZE / 6); /* 取平均值 */
-
- /* 显示ADCC采样后的原始值 *
- lcd_show_xnum(108, 110 + (j * 30), adcx, 4, 12, 0, BLUE);
- /* 获取计算后的带小数的实际电压值,比如3.1111 */
- temp = (float)adcx * (3.3 / 4096);
- adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
- /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
- lcd_show_xnum(108, 122 + (j * 30), adcx, 1, 12, 0, BLUE);
- /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
- temp -= adcx;
- /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
- temp *= 1000;
- /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
- lcd_show_xnum(120, 122 + (j * 30), temp, 3, 12, 0X80, BLUE);
- }
- g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
- adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */
- }
- LED0_TOGGLE();
- delay_ms(100);
- }
- }
复制代码这里使用了DMA传输数据,DMA传输的数据存放在g_adc_dma_buf数组里,该数组的大小是50 * 6,六个通道,每个通道使用50个uint16_t大小的空间存放ADC的结果。例程中该数据存放数据的分配是ADC1_CH1的数据存放在g_adc_dma_buf[0]到g_adc_dma_buf[49],ADC1_CH15的数据存放在g_adc_dma_buf[50]到g_adc_dma_buf[99],后面的以此类推。
然后对数组的每个通道的数据取平均值,减少误差。在LCD屏显示结果的处理和单通道ADC采集实验一样。首先我们在液晶固定位置显示了小数点,然后后面计算步骤中,先计算出整数部分在小数点前面显示,然后计算出小数部分,在小数点后面显示。这样就在液晶上面显示转换结果的整数和小数部分。
31.4.4 下载验证下载代码后,LED0闪烁,提示程序运行。可以看到LCD显示如图31.4.4.1所示: 图31.4.4.1 多通道ADC采集(DMA读取)实验测试图 使用ADC1采集(DMA读取)通道0\1\2\3\4\5的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA0\PA1\PA2\PA3\PA4\PA5到你想测量的电压源(0~3.3V)。
这6个通道对应引出来的引脚PA0\PA1\PA2\PA3\PA4\PA5在开发板上的位置,如下图所示: 图31.4.4.2 ADC1的通道0\1\2\3\4\5引脚在开发板位置示意图 这六个通道可以同时测量不同测试点的电压,只需要用杜邦线分别接到不同的电压测试点即可。注意:一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。
31.5 单通道ADC过采样(16位分辨率)实验本实验我们来学习使用规则单通道的连续模式的ADC过采样(16位分辨率)。
31.5.1 ADC寄存器本实验我们很多的设置和单通道ADC采集实验是一样的,这里只是采样时间的不同,前面的实验也实现了设置采样时间的函数。所以本实验的ADC过采样是基于代码逻辑实现的,这里就不对寄存器进行介绍了。
31.5.2 硬件设计1. 例程功能 ADC1过采样(16位分辨率)采集通道5(PA5)上面的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA5到你想测量的电压源(0~3.3V),然后通过TFTLCD显示的电压值。LED0闪烁,提示程序运行。
2. 硬件资源 1)LED灯 LED0 –PF9 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子 2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)ADC1 :通道5 – PA5
3. 原理图 ADC属于STM32F407内部资源,实际上我们只需要软件设置就可以正常工作,不过我们需要在外部连接其端口到被测电压上面。本实验,我们通过ADC1过采样(16位分辨率)通道5(PA5)来采集外部电压值,开发板有一个电位器,可调节的电压范围是:0~3.3V。使用杜邦线将P11的ADC和RV1连接好后,并下载程序后,就可以用螺丝刀调节电位器变换多种电压值进行测试。
有的朋友可能还想测试其他地方的电压值,我们只需要1跟杜邦线,一端接到P11的ADC排针上,另外一端就接你要测试的电压点。一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。 31.5.3 程序设计31.5.3.1 ADC的HAL库驱动 本实验用到的ADC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。下面介绍单通道ADC过采样(16位分辨率)配置步骤。
单通道ADC过采样(16位分辨率)配置步骤1)开启ADCx和通道输出的GPIO时钟,配置该IO口的复用功能输出 首先开启ADCx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到ADC1通道5,对应IO是PA5,它们的时钟开启方法如下: - __HAL_RCC_ADC1_CLK_ENABLE (); /* 使能ADC1时钟 */
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */
复制代码IO口复用功能是通过函数HAL_GPIO_Init来配置的。
2)初始化ADCx,配置其工作参数 通过HAL_ADC_Init函数来设置ADCx时钟分频系数、分辨率、模式、扫描方式、对齐方式、开启过采样等信息。 注意:该函数会调用:HAL_ADC_MspInit回调函数来完成对ADC底层以及其输入通道IO的初始化,包括:ADC及GPIO时钟使能、GPIO模式设置等。
3)配置ADC通道并启动AD转换器 在HAL库中,通过HAL_ADC_ConfigChannel函数来设置配置ADC的通道,根据需求设置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。 配置好ADC通道之后,通过HAL_ADC_Start函数启动AD转换器。
4)读取ADC值 这里选择查询方式读取,在读取ADC值之前需要调用HAL_ADC_PollForConversion等待上一次转换结束。然后就可以通过HAL_ADC_GetValue来读取ADC值。
31.5.3.2 程序流程图 图31.5.3.2.1 单通道ADC过采样(16位分辨率)实验程序流程图 31.5.3.3 程序解析 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ADC驱动源码包括两个文件:adc.c和adc.h。本实验沿用前面实验中的函数,并没有改动。
现在看以下main.c如下代码: - #define ADC_OVERSAMPLE_TIMES 256 /*过采样次数, 提高4bit分辨率, 需要256倍采样*/
- /* ADC DMA采集 BUF大小, 应等于过采样次数的整数倍 */
- #define ADC_DMA_BUF_SIZE ADC_OVERSAMPLE_TIMES * 10
- uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMABUF */
- extern uint8_tg_adc_dma_sta; /* DMA传输状态标志, 0,未完成; 1, 已完成 */
- extern ADC_HandleTypeDefg_adc_handle; /* ADC句柄 */
- int main(void)
- {
- uint16_t i;
- uint32_t adcx;
- uint32_t sum;
- float temp;
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
- delay_init(168); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "ADCOverSample TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH5_VAL:", BLUE);.
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH5_VOL:0.000V", BLUE);
- adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */
- while (1)
- {
- if (g_adc_dma_sta == 1)
- {
- /* 计算DMA 采集到的ADC数据的平均值 */
- sum = 0;
- for (i = 0; i < ADC_DMA_BUF_SIZE; i++) /* 累加 */
- {
- sum+=g_adc_dma_buf;
- }
- adcx = sum / (ADC_DMA_BUF_SIZE /ADC_OVERSAMPLE_TIMES); /* 取平均值 */
- /* 除以2^4倍, 得到12+4位 ADC精度值, 注意: 提高 N bit精度, 需要 >> N */
- adcx >>= 4;
- /* 显示ADCC采样后的原始值 */
- lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE);
- /* 获取计算后的带小数的实际电压值,比如3.1111 */
- temp = (float)adcx * (3.3 / 65536);
- adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
- /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
- lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE);
- temp -= adcx;/* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111*/
- temp *= 1000;
- /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数 */
- /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
- lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE);
- g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
- adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */
- }
- LED0_TOGGLE();
- delay_ms(100);
- }
- }
复制代码此部分代码,我们在TFTLCD模块上显示一些提示信息后,将每隔100ms读取一次ADC通道5的值。
我们这里是采用ADC多次采集的方式,来提高ADC精度,采样速度每提高4倍,采样精度提高1bit。同时,在ADC采样速度降低4倍的情况下,还要提高4bit精度,需要256次采集才能得出1次数据,相当于ADC速度慢了256倍。这里提高精度实质就是增大采集数据量,把均值的小数部分放大,以提高精度。
在获取到ADC的均值后显示读到的ADC值(数字量),以及其转换成模拟量后的电压值。同时控制LED0闪烁,以提示程序正在运行。16位ADC分辨率最大值为:65535。
31.5.4 下载验证下载代码后,LED0闪烁,提示程序运行。可以看到LCD显示如图31.5.4.1所示: 图31.5.4.1 单通道ADC过采样(16位分辨率)实验测试图 上图中,我们使用杜邦线将P11的ADC和RV1连接,使得PA5连接到电位器上,测试的是电位器的电压,并可以通过螺丝刀调节电位器改变电压值,范围:0~3.3V。
LED0闪烁,提示程序运行。大家可以试试把杜邦线接到其他地方,看看电压值是否准确?注意:一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。 |