本帖最后由 正点原子运营 于 2023-1-11 18:08 编辑
第三十一章 ADC实验
1)实验平台:正点原子MiniPro STM32H750开发板
2) 章节摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
3)购买链接:https://detail.tmall.com/item.htm?id=677017430560
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h750_minipro.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)MiniPro STM32H750技术交流QQ群:170313895
31.4 多通道ADC采集(DMA读取)实验本实验我们来学习多通道ADC采集(DMA读取)实验。本实验使用常规转换组多通道的连续转换模式,并且通过软件触发,即通过对ADCx_CR寄存器的ADSTART位写1启动转换。由于使用连续转换模式,所以使用DMA读取转换结果的方式。下面先带大家来了解本实验要配置的寄存器。
31.4.1 ADC寄存器 本实验我们很多的设置和单通道ADC采集(DMA读取)实验是一样的,所以下面介绍寄存器的时候我们不会继续全部都介绍,而是针对性选择与单通道ADC采集(DMA读取)实验不同设置的ADCx_SQR寄存器进行介绍,其他的配置基本一样的。另外我们用到DMA读取数据,配置上和单通道ADC采集(DMA读取)实验是一样的。
ADCx常规序列寄存器有四个(ADCx_SQR1~ ADCx_SQR4),具体怎么配置,需要看我们用多少个通道,比如本实验我们使用6个通道同时采集ADC数据,具体配置如下: l ADCx常规序列寄存器1(ADCx_SQR1) ADCx常规序列寄存器1描述如图31.4.1.1所示: 图31.4.1.1 ADCx_SQR1寄存器
L[3:0]位用于设置常规序列的长度,取值范围:0~15,表示常规序列长度为1~16。本实验使用到6个通道,所以设置这几个位的值为5即可。
SQ1[4:0]~SQ4[4:0]位设置常规组序列的第1~4个转换编号,第5~16个转换编号的设置请查看ADCx_SQR2和ADCx_SQR4寄存器。设置过程非常简单,忘记了请参考前面给大家整理出来的常规序列寄存器控制关系汇总表。
下面我们来看看本实验是怎么设置的:SQ1[4:0]位赋值为14、SQ2[4:0]位赋值为15、SQ3[4:0]位赋值为16、SQ4[4:0]位赋值为17、SQ5[4:0]位赋值为18、SQ6[4:0]位赋值为19,即常规序列1到6分别对应的通道是14到19。其中SQ5[4:0]位和SQ6[4:0]位是在ADCx_SQR2寄存器中配置。
31.4.2 硬件设计
1. 例程功能使用ADC1采集(DMA读取)通道14\15\16\17\18\19的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA0\PA1\PA2\PA3\PA4\PA5到你想测量的电压源(0~3.3V),然后通过TFTLCD显示的电压值。LED0闪烁,提示程序运行。
2. 硬件资源1)RGB灯 RED : LED0 - PB4 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)ADC1 :通道14 - PA2、通道15 - PA3、通道16 - PA0、 通道17 - PA1、通道18 - PA4、通道19 - PA5 5)DMA(DMA1 数据流7 DMA请求源9)
3. 原理图ADC和DMA属于STM32H750内部资源,实际上我们只需要软件设置就可以正常工作,另外还需要将待测量的电压源连接到ADC通道上,以便ADC测量。本实验,我们通过ADC1的通道14\15\16\17\18\19来采集外部电压值,并通过DMA来读取。
31.4.3 程序设计
31.4.3.1 ADC的HAL库驱动本实验用到的ADC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。下面介绍多通道ADC采集(DMA读取)配置步骤。
多通道ADC采集(DMA读取)配置步骤1)开启ADCx和ADC通道对应的IO时钟,并配置该IO为模拟功能 首先开启ADCx的时钟,然后配置GPIO为模拟模式。本实验我们默认用到ADC1通道14、15、16、17、18、19,对应IO是PA2、PA3、PA0、PA1、PA4和PA5,它们的时钟开启方法如下: - __HAL_RCC_ADC12_CLK_ENABLE(); /* 使能ADC1 &ADC2 时钟 */
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */
复制代码2)初始化ADCx, 配置其工作参数 通过HAL_ADC_Init函数来设置ADCx时钟分频系数、分辨率、模式、扫描方式、对齐方式等信息。 注意:该函数会调用:HAL_ADC_MspInit回调函数来存放ADC及GPIO时钟使能、GPIO初始化等代码。我们也可以不存放在这个函数里,本实验就没用到这个MSP回调函数。
3)配置ADC通道 在HAL库中,通过HAL_ADC_ConfigChannel函数来设置配置ADC的通道,根据需求设置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。这里配置多通道输出,需要多次调用该函数。
4)初始化DMA 通过HAL_DMA_Init函数初始化DMA,包括配置通道,外设地址,存储器地址,传输数据量等。 HAL库为了处理各类外设的DMA请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接DMA和外设句柄。这个宏定义为__HAL_LINKDMA。
5)使能DMA对应数据流中断,配置DMA中断优先级并开启中断,启动ADC和DMA 通过HAL_ADC_Start_DMA函数开启ADC转换,通过DMA传输结果。 通过HAL_DMA_Start_IT函数启动DMA读取,使能DMA中断。 通过HAL_NVIC_EnableIRQ函数使能DMA数据流中断。 通过HAL_NVIC_SetPriority函数设置中断优先级。
6)编写中断服务函数 DMA中断对于每个数据流都有一个中断服务函数,比如DMA1_Stream7的中断服务函数为DMA1_Stream7_IRQHandler。简单的做法就是在,对应的中断服务函数里面,通过判断相关的中断标志位的方式,完成中断逻辑代码,最后清楚该中断标志位,本实验的做法就是如此。 还可以通过调用HAL库提供的DMA中断公用处理函数HAL_DMA_IRQHandler,然后定重新义相关的中断回调处理函数。
31.4.3.2 程序流程图
图31.4.3.2.1多通道ADC采集(DMA读取)实验程序流程图
31.4.3.3 程序解析在本实验中adc.h头文件只是添加了一些函数声明,下面开始介绍adc.c的函数,本实验只增加了一个函数,ADC的N通道(6通道)DMA读取初始化函数,其定义如下: - /**
- *@brief ADC N通道(6通道) DMA读取 初始化函数
- * @note 另外,由于本函数用到了6个通道, 宏定义会比较多内容, 因此,本函数就不采用宏定义
- 的方式来修改通道了,直接在本函数里面修改, 这里我们默认使用PA0~PA5这6个通道.
- * 注意: 本函数还是使用 ADC_ADCX(默认=ADC1) 和ADC_ADCX_DMASx
- (默认=DMA1_Stream7) 及其相关定义不要乱修改adc.h里面的这两部分内容, 必须在
- 理解原理的基础上进行修改, 否则可能导致无法正常使用.
- * @param par : 外设地址
- *@param mar : 存储器地址
- *@retval 无
- */
- voidadc_nch_dma_init(uint32_t par, uint32_t mar)
- {
- GPIO_InitTypeDef gpio_init_struct;
- ADC_ChannelConfTypeDef adc_ch_conf = {0};
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA引脚时钟 */
- ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADC1/2时钟 */
- /* 得到当前stream是属于DMA2还是DMA1 */
- if ((uint32_t)ADC_ADCX_DMASx > (uint32_t)DMA2)
- {
- __HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */
- }
- else
- {
- __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */
- }
- __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_CLKP); /* ADC外设时钟选择 */
- /* 初始化ADC多通道对应的GPIO
- *PA0-ADC_CHANNEL_16、PA1-ADC_CHANNEL_17、PA2-ADC_CHANNEL_14
- *PA3-ADC_CHANNEL_15、PA4-ADC_CHANNEL_18、PA5-ADC_CHANNEL_19
- */
- 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; /* 模拟 */
- HAL_GPIO_Init(GPIOA, &gpio_init_struct);
- /* 初始化DMA */
- g_dma_nch_adc_handle.Instance = ADC_ADCX_DMASx; /* 使用DMA1 Stream7 */
- /* 请求选择DMA_REQUEST_ADC1 */
- g_dma_nch_adc_handle.Init.Request = ADC_ADCX_DMASx_REQ;
- /* 传外设到存储器模式 */
- 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; /* 中等优先级 */
- g_dma_nch_adc_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* 禁止FIFO*/
- HAL_DMA_Init(&g_dma_nch_adc_handle); /* 初始化DMA */
- /* 将DMA与adc联系起来 */
- __HAL_LINKDMA(&g_adc_nch_dma_handle, DMA_Handle, g_dma_nch_adc_handle);
- /* 初始化ADC */
- g_adc_nch_dma_handle.Instance = ADC_ADCX; /* 选择哪个ADC */
- /* 输入时钟2分频,即adc_ker_ck=per_ck/2=32Mhz*/
- g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;
- g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_16B; /* 16位模式 */
- g_adc_nch_dma_handle.Init.ScanConvMode = ADC_SCAN_ENABLE; /* 扫描模式 */
- g_adc_nch_dma_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;/*关闭EOC中断 */
- g_adc_nch_dma_handle.Init.LowPowerAutoWait = DISABLE; /* 自动低功耗关闭 */
- g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE; /* 使能连续转换模式 */
- /* 赋值范围是1~16,本实验用到6个通道 */
- g_adc_nch_dma_handle.Init.NbrOfConversion = 6;
- /* 禁止常规转换组不连续采样模式 */
- g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE;
- /* 配置不连续采样模式的通道数,禁止常规转换组不连续采样模式后,此参数忽略 */
- 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; /* 采用软件触发的话,此位忽略 */
- g_adc_nch_dma_handle.Init.ConversionDataManagement =
- ADC_CONVERSIONDATA_DMA_ONESHOT; /* DMA单次传输ADC数据 */
- /* 有新的数据后直接覆盖掉旧数据 */
- g_adc_nch_dma_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
- /* 设置ADC转换结果的左移位数 */
- g_adc_nch_dma_handle.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
- g_adc_nch_dma_handle.Init.OversamplingMode = DISABLE; /* 过采样关闭 */
- HAL_ADC_Init(&g_adc_nch_dma_handle); /* 初始化 */
- HAL_ADCEx_Calibration_Start(&g_adc_nch_dma_handle, ADC_CALIB_OFFSET,
- ADC_SINGLE_ENDED); /* ADC校准 */
-
- /* 配置ADC通道 */
- adc_ch_conf.Channel =ADC_CHANNEL_14; /* 配置使用的ADC通道 */
- adc_ch_conf.Rank =ADC_REGULAR_RANK_1; /* 采样序列里的第1个 */
- /* 采样周期为810.5个时钟周期 */
- adc_ch_conf.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;
- adc_ch_conf.SingleDiff = ADC_SINGLE_ENDED ; /* 单端输入 */
- adc_ch_conf.OffsetNumber = ADC_OFFSET_NONE; /* 无偏移 */
- adc_ch_conf.Offset = 0; /* 无偏移的情况下,此参数忽略 */
- adc_ch_conf.OffsetRightShift = DISABLE; /* 禁止右移 */
- adc_ch_conf.OffsetSignedSaturation = DISABLE; /* 禁止有符号饱和 */
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); /* 配置ADC通道 */
- adc_ch_conf.Channel =ADC_CHANNEL_15; /* 配置使用的ADC通道 */
- adc_ch_conf.Rank =ADC_REGULAR_RANK_2; /* 采样序列里的第2个 */
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); /* 配置ADC通道 */
- adc_ch_conf.Channel =ADC_CHANNEL_16; /* 配置使用的ADC通道 */
- adc_ch_conf.Rank =ADC_REGULAR_RANK_3; /* 采样序列里的第3个 */
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); /* 配置ADC通道 */
- adc_ch_conf.Channel = ADC_CHANNEL_17; /* 配置使用的ADC通道 */
- adc_ch_conf.Rank =ADC_REGULAR_RANK_4; /* 采样序列里的第4个 */
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); /* 配置ADC通道 */
- adc_ch_conf.Channel =ADC_CHANNEL_18; /* 配置使用的ADC通道 */
- adc_ch_conf.Rank =ADC_REGULAR_RANK_5; /* 采样序列里的第5个 */
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); /* 配置ADC通道 */
- adc_ch_conf.Channel =ADC_CHANNEL_19; /* 配置使用的ADC通道 */
- adc_ch_conf.Rank =ADC_REGULAR_RANK_6; /* 采样序列里的第6个 */
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf); /* 配置ADC通道 */
- /* 配置DMA数据流请求中断优先级 */
- HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3);
- HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn);
- HAL_DMA_Start_IT(&g_dma_nch_adc_handle, par, mar, 0); /* 启动DMA,并开启中断 */
- /* 开启ADC,通过DMA传输结果 */
- HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar, 0);
- }
复制代码adc_nch_dma_init函数包含了输出通道对应IO的初始代码、NVIC、使能时钟、ADC时钟预分频系数、ADC工作参数和ADC通道配置等代码。大部分代码和单通道ADC采集(DMA读取)实验一样,下面来看看该函数的代码内容。
第一部分使能ADC、DMA和GPIO的时钟。
第二部分选择ADC的时钟源为per_ck,per_ck默认选择hsi_ker_ck作为时钟源,即ADC的时钟源为64MHZ的hsi_ker_ck。
第三部分是设置ADC采集通道对应IO引脚工作模式,这里用到6个通道。
第四部分初始化DMA,并通过__HAL_LINKDMA宏定义将DMA相关的配置关联到ADC的句柄中。
第五部分是初始化ADC,并校准ADC。
第六部分是配置ADC通道,这里有6个通道需要配置。
第七部分是配置DMA数据流请求中断优先级,并使能该中断。
第八部分是启动DMA并开启DMA中断,以及启动ADC并通过DMA传输转换结果。
为了方便代码的管理和移植性等,这里就没有使用HAL_ADC_MspInit这个函数来存放使能时钟、GPIO、NVIC相关的代码,而是全部存放在adc_nch_dma_init函数中。
最后在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_t g_adc_dma_sta; /* DMA传输状态标志, 0,未完成; 1, 已完成 */
- int main(void)
- {
- uint16_t i,j;
- uint16_t adcx;
- uint32_t sum;
- float temp;
- sys_cache_enable(); /* 打开L1-Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
- delay_init(480); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- mpu_memory_protection(); /* 保护相关存储区域 */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- /* 初始化ADC DMA采集 */
- adc_nch_dma_init((uint32_t)&ADC1->DR, (uint32_t)&g_adc_dma_buf);
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "ADC DMATEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 130, 200, 12, 12, "ADC1_CH14_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 142, 200, 12, 12, "ADC1_CH14_VOL:0.000V", BLUE);
-
- lcd_show_string(30, 160, 200, 12, 12, "ADC1_CH15_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 172, 200, 12, 12, "ADC1_CH15_VOL:0.000V", BLUE);
- lcd_show_string(30, 190, 200, 12, 12, "ADC1_CH16_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 202, 200, 12, 12, "ADC1_CH16_VOL:0.000V", BLUE);
- lcd_show_string(30, 220, 200, 12, 12, "ADC1_CH17_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 232, 200, 12, 12, "ADC1_CH17_VOL:0.000V", BLUE);
- lcd_show_string(30, 250, 200, 12, 12, "ADC1_CH18_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 262, 200, 12, 12, "ADC1_CH18_VOL:0.000V", BLUE);
- lcd_show_string(30, 280, 200, 12, 12, "ADC1_CH19_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 292, 200, 12, 12, "ADC1_CH19_VOL:0.000V", BLUE);
- adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */
- while (1)
- {
- if (g_adc_dma_sta == 1)
- {
- /* 清除D Cache数据 */
- SCB_InvalidateDCache();
- /* 循环显示通道14~通道19的结果 */
- for(j = 0; j < 6; j++) /* 遍历6个通道 */
- {
- sum = 0; /* 清零 */
- /* 每个通道采集了50次数据,进行50次累加 */
- for (i = 0; i < ADC_DMA_BUF_SIZE / 6; i++)
- {
- sum += g_adc_dma_buf[(6 * i) + j]; /* 相同通道的转换数据累加 */
- }
- adcx = sum / (ADC_DMA_BUF_SIZE / 6); /* 取平均值 */
-
- /* 显示结果 */
- /* 显示ADCC采样后的原始值 */
- lcd_show_xnum(114, 130 + (j * 30), adcx, 5, 12, 0, BLUE);
- /* 获取计算后的带小数的实际电压值,比如3.1111 */
- temp = (float)adcx * (3.3 / 65536);
- adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
- /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
- lcd_show_xnum(114, 142 + (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(126, 142 + (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。本实验用到6个通道,每个通道使用50个uint16_t大小的空间存放ADC的结果。
输入通道14的转换数据存放在g_adc_dma_buf[0]到g_adc_dma_buf[49],输入通道15的转换数据存放在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读取)通道14\15\16\17\18\19的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA0\PA1\PA2\PA3\PA4\PA5到你想测量的电压源(0~3.3V)。
这6个通道对应引出来的引脚PA0\PA1\PA2\PA3\PA4\PA5在开发板上的位置,如下图所示: 图31.4.4.2 ADC1的通道14\15\16\17\18\19引脚在开发板位置示意图
这六个通道可以同时测量不同测试点的电压,只需要用杜邦线分别接到不同的电压测试点即可。注意:一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。
31.5 单通道ADC过采样(26位分辨率)实验本实验我们来学习单通道ADC过采样(26位分辨率)实验。本实验使用常规转换组单通道的单次转换模式,并且通过软件触发,即通过对ADCx_CR寄存器的ADSTART位写1启动转换。下面先带大家来了解本实验要配置的寄存器。
31.5.1 ADC寄存器 本实验很多配置和单通道ADC采集实验是一样的,下面只介绍ADCx_CFGR2寄存器。 l ADCx配置寄存器2(ADCx_CFGR2) ADCx配置寄存器2描述如图31.5.1.1所示: 图31.5.1.1 ADCx_CFGR2寄存器
OSR[9:0]位用于设置ADC的过采样率。OSR[9:0]=0~1023,表示1x~1024x过采样。本实验使用过采样,并且是26位分辨率,所以设置OSR[9:0] = 1023。 ROVSM位用于设置常规过采样模式,默认为0即可,即连续模式。 TROVS位用于设置已触发常规过采样,默认为0即可,即会在触发后连续完成某一通道的所有过采样转换。 OVSS[3:0]位用于设置过采样结果右移位数。 ROVSE位是常规过采样使能位,置1使能常规过采样。
31.5.2 硬件设计
1. 例程功能ADC1过采样(26位分辨率)采集通道19(PA5)上面的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA5到你想测量的电压源(0~3.3V),然后通过TFTLCD显示的电压值。LED0闪烁,提示程序运行。
2. 硬件资源1)RGB灯 RED : LED0 - PB4 2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面) 3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动) 4)ADC1 :通道19 - PA5
3. 原理图ADC属于STM32H750内部资源,实际上我们只需要软件设置就可以正常工作,另外还需要将待测量的电压源连接到ADC通道上,以便ADC测量。本实验,我们通过ADC1过采样(26位分辨率)通道19(PA5)来采集外部电压值,开发板有一个电位器,可调节的电压范围是:0~3.3V,可以通过断路帽将PA5与电位器连接,从而测量电位器的电压。
31.5.3 程序设计
31.5.3.1 ADC的HAL库驱动本实验用到的ADC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。
单通道ADC过采样(26位分辨率)配置步骤1)开启ADCx和ADC通道对应的IO时钟,并配置该IO为模拟功能 首先开启ADCx的时钟,然后配置GPIO为模拟模式。本实验我们默认用到ADC1通道19,对应IO是PA5,它们的时钟开启方法如下: - __HAL_RCC_ADC12_CLK_ENABLE (); /* 使能ADC1 &ADC2 时钟 */
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */
复制代码2)初始化ADCx,配置其工作参数 通过HAL_ADC_Init函数来设置ADCx时钟分频系数、分辨率、模式、扫描方式、对齐方式、开启过采样等信息。 注意:该函数会调用:HAL_ADC_MspInit回调函数来存放ADC及GPIO时钟使能、GPIO初始化等代码。我们也可以不存放在这个函数里,本实验就没用到这个MSP回调函数。
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过采样(26位分辨率)实验程序流程图
31.5.3.3 程序解析adc.h文件只是添加了函数声明,下面来介绍adc.c文件的adc_oversample_init函数。 - /**
- *@brief ADC 过采样 初始化函数
- * @note 本函数可以控制ADC过采样范围从1x ~ 1024x, 得到最高26位分辨率的AD转换结果
- *@param osr : 过采样倍率, 0 ~ 1023, 表示:1x ~ 1024x过采样倍率
- *@param ovss: 过采样右移位数, 0~11, 表示右移0位~11位.
- * @note 过采样后, ADC的转换时间相应的会慢 osr倍.
- *@retval 无
- */
- voidadc_oversample_init(uint32_t osr, uint32_t ovss)
- {
- GPIO_InitTypeDef gpio_init_struct;
- ADC_ADCX_CHY_GPIO_CLK_ENABLE(); /* 开启ADC通道IO引脚时钟 */
- ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADC1/2时钟 */
- __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_CLKP); /* ADC外设时钟选择 */
- gpio_init_struct.Pin = ADC_ADCX_CHY_GPIO_PIN; /* ADC通道IO引脚 */
- gpio_init_struct.Mode = GPIO_MODE_ANALOG; /* 模拟 */
- HAL_GPIO_Init(ADC_ADCX_CHY_GPIO_PORT, &gpio_init_struct);
- g_adc_handle.Instance = ADC_ADCX; /* 选择哪个ADC */
- /* 输入时钟2分频,即adc_ker_ck=per_ck/2=32Mhz*/
- g_adc_handle.Init.ClockPrescaler=ADC_CLOCK_ASYNC_DIV2;
- g_adc_handle.Init.Resolution = ADC_RESOLUTION_16B; /* 16位模式 */
- g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 非扫描模式 */
- g_adc_handle.Init.EOCSelection =ADC_EOC_SINGLE_CONV;/* 关闭EOC中断 */
- g_adc_handle.Init.LowPowerAutoWait= DISABLE; /* 自动低功耗关闭 */
- g_adc_handle.Init.ContinuousConvMode= DISABLE; /* 关闭连续转换 */
- g_adc_handle.Init.NbrOfConversion= 1; /* 赋值范围是1~16,本实验用到1个通道 */
- /* 禁止常规转换组不连续采样模式 */
- g_adc_handle.Init.DiscontinuousConvMode= DISABLE;
- /* 配置不连续采样模式的通道数,禁止常规转换组不连续采样模式后,此参数忽略 */
- g_adc_handle.Init.NbrOfDiscConversion= 0;
- g_adc_handle.Init.ExternalTrigConv=ADC_SOFTWARE_START; /* 软件触发 */
- /* 采用软件触发的话,此位忽略 */
- g_adc_handle.Init.ExternalTrigConvEdge=ADC_EXTERNALTRIGCONVEDGE_NONE;
- /* 常规通道的数据仅仅保存在DR寄存器里面 */
- g_adc_handle.Init.ConversionDataManagement=ADC_CONVERSIONDATA_DR;
- /* 有新的数据后直接覆盖掉旧数据 */
- g_adc_handle.Init.Overrun =ADC_OVR_DATA_OVERWRITTEN;
- /* 设置ADC转换结果的左移位数 */
- g_adc_handle.Init.LeftBitShift =ADC_LEFTBITSHIFT_NONE;
- g_adc_handle.Init.OversamplingMode= ENABLE; /* 开启过采样 */
- g_adc_handle.Init.Oversampling.Ratio = osr; /* 设置osr+1倍过采样 */
- g_adc_handle.Init.Oversampling.RightBitShift = ovss; /* 数据右移ovss bit */
- g_adc_handle.Init.Oversampling.TriggeredMode =
- ADC_TRIGGEREDMODE_SINGLE_TRIGGER; /* 会在触发后连续完成通道的所有过采样转换 */
- g_adc_handle.Init.Oversampling.OversamplingStopReset=
- ADC_REGOVERSAMPLING_CONTINUED_MODE;/* ROVSE=1, 使能常规过采样 */
- HAL_ADC_Init(&g_adc_handle); /* 初始化 */
- HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_CALIB_OFFSET,
- ADC_SINGLE_ENDED); /* ADC校准 */
- }
复制代码adc_oversample_init函数大概可以分为下面几个部分的功能: 第一部分使能ADC和GPIO的时钟。 第二部分选择ADC的时钟源为per_ck,per_ck默认选择hsi_ker_ck作为时钟源,即ADC的时钟源为64MHZ的hsi_ker_ck。 第三部分是设置ADC采集通道对应IO引脚工作模式。 第四部分初始化ADC,并校准ADC。在这里,开启过采样、还配置过采样率等。
最后在main.c里面编写如下代码: - int main(void)
- {
- uint32_t adcx;
- float temp;
- sys_cache_enable(); /* 打开L1-Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
- delay_init(480); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- mpu_memory_protection(); /* 保护相关存储区域 */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- /* 初始化ADC, 1024x过采样, 不移位
- *26位ADC分辨率最大值为:67108864, 实际上由于分辨率太高 ,低位值已经不准确
- *一般我们可以设置 ovss=4, 缩小16倍, 即22位分辨率, 低位值会相对稳定一些.
- *这里我们为了演示26位过采样ADC转换效果, 把分辨率调到最大, 26位,并且不移位.
- */
- adc_oversample_init(1024 - 1, ADC_RIGHTBITSHIFT_NONE);
- 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_CH19_VAL:", BLUE);
- /* 先在固定位置显示小数点 */
- lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH19_VOL:0.000V", BLUE);
- while (1)
- {
- /* 获取通道5的转换值,10次取平均 */
- adcx =adc_get_result_average(ADC_ADCX_CHY, 10);
- lcd_show_xnum(142, 110, adcx, 8, 16, 0, BLUE); /* 显示ADCC采样后的原始值 */
- /* 获取计算后的带小数的实际电压值,比如3.1111 */
- temp = (float)adcx * (3.3 / 67108864);
- adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为整形 */
- /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
- lcd_show_xnum(142, 130, adcx, 1, 16, 0, BLUE);
- temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
- temp *= 1000; /*小数部分乘以1000,如:0.1111就转换为111.1,相当于保留三位小数*/
- /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
- lcd_show_xnum(158, 130, temp, 3, 16, 0X80, BLUE);
- LED0_TOGGLE();
- delay_ms(100);
- }
- }
复制代码此部分代码,我们在TFTLCD模块上显示一些提示信息后,将每隔100ms读取一次ADC通道5的值,并显示读到的ADC值(数字量),以及其转换成模拟量后的电压值。同时控制LED0闪烁,以提示程序正在运行。
31.5.4 下载验证下载代码后,LED0闪烁,提示程序运行。可以看到LCD显示如图31.5.4.1所示: 图31.5.4.1 单通道ADC过采样(26位分辨率)实验测试图
上图中,我们使用短路帽将P3的ADC和RV1连接,使得PA5连接到电位器上,测试的是电位器的电压,并可以通过螺丝刀调节电位器改变电压值,范围:0~3.3V。LED0闪烁,提示程序运行。
大家也可以用杜邦线将ADC排针接到其它待测量的电压点,看看测量到的电压值是否准确?但是要注意:一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。 |