这个实验与单通道ADC(DMA读取)实验大致相同
1,功能描述 通过DMA读取数据
通过ADC1通道0/1/2/3/4/5(PA0/1/2/3/4/5)采集测试电压,并显示ADC转换的数字量及换算后的电压值
2,确定最小刻度
VREF+ = 3.3V 0V≤VIN≤3.3V 最小刻度 = 3.3 / 4096 F4/F7/H7系列还需要考虑ADC分辨率
3,确定转换时间
采样时间239.5个ADC时钟周期为例,可以得到转换时间为21us
4,模式组合
连续转换模式、使用扫描模式
- #include "./BSP/ADC/adc.h"
-
-
- DMA_HandleTypeDef g_dma_nch_adc_handle;
- ADC_HandleTypeDef g_adc_nch_dma_handle;
- uint8_t g_adc_dma_sta;
-
- /* ADC N通道(6通道) DMA读取 初始化函数 */
- void adc_nch_dma_init(uint32_t mar)
- {
- ADC_ChannelConfTypeDef adc_ch_conf;
-
- __HAL_RCC_DMA1_CLK_ENABLE();
-
- g_dma_nch_adc_handle.Instance = DMA1_Channel1;
- 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;
- g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
- 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);
-
- __HAL_LINKDMA(&g_adc_nch_dma_handle, DMA_Handle, g_dma_nch_adc_handle);
-
- g_adc_nch_dma_handle.Instance = ADC1;
- g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
- g_adc_nch_dma_handle.Init.ScanConvMode = ADC_SCAN_ENABLE;
- g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE;
- 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;
- HAL_ADC_Init(&g_adc_nch_dma_handle);
-
- HAL_ADCEx_Calibration_Start(&g_adc_nch_dma_handle);
-
- adc_ch_conf.Channel = ADC_CHANNEL_0;
- adc_ch_conf.Rank = ADC_REGULAR_RANK_1;
- adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
-
- adc_ch_conf.Channel = ADC_CHANNEL_1;
- adc_ch_conf.Rank = ADC_REGULAR_RANK_2;
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
-
- adc_ch_conf.Channel = ADC_CHANNEL_2;
- adc_ch_conf.Rank = ADC_REGULAR_RANK_3;
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
-
- adc_ch_conf.Channel = ADC_CHANNEL_3;
- adc_ch_conf.Rank = ADC_REGULAR_RANK_4;
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
-
- adc_ch_conf.Channel = ADC_CHANNEL_4;
- adc_ch_conf.Rank = ADC_REGULAR_RANK_5;
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
-
- adc_ch_conf.Channel = ADC_CHANNEL_5;
- adc_ch_conf.Rank = ADC_REGULAR_RANK_6;
- HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);
-
- HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);
- HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
-
- HAL_DMA_Start_IT(&g_dma_nch_adc_handle, (uint32_t)&ADC1->DR, mar, 0);
- HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar ,0);
- }
-
- /* ADC MSP初始化函数 */
- void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
- {
- if(hadc->Instance == ADC1)
- {
- GPIO_InitTypeDef gpio_init_struct;
- RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
-
- __HAL_RCC_GPIOA_CLK_ENABLE();
- __HAL_RCC_ADC1_CLK_ENABLE();
-
- gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
- gpio_init_struct.Mode = GPIO_MODE_ANALOG;
- HAL_GPIO_Init(GPIOA, &gpio_init_struct);
-
- adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
- adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
- HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
- }
- }
-
- /* 使能一次ADC DMA传输函数 */
- void adc_dma_enable(uint16_t cndtr)
- {
- ADC1->CR2 &= ~(1 << 0);
-
- DMA1_Channel1->CCR &= ~(1 << 0);
- while (DMA1_Channel1->CCR & (1 << 0));
- DMA1_Channel1->CNDTR = cndtr;
- DMA1_Channel1->CCR |= 1 << 0;
-
- ADC1->CR2 |= 1 << 0;
- ADC1->CR2 |= 1 << 22;
- }
-
- /* ADC DMA采集中断服务函数 */
- void DMA1_Channel1_IRQHandler(void)
- {
- if (DMA1->ISR & (1<<1))
- {
- g_adc_dma_sta = 1;
- DMA1->IFCR |= 1 << 1;
- }
- }
- #ifndef __ADC_H
- #define __ADC_H
-
- #include "./SYSTEM/sys/sys.h"
-
-
- void adc_nch_dma_init(uint32_t mar);
- void adc_dma_enable(uint16_t cndtr);
-
- #endif
-
-
- #include "./SYSTEM/sys/sys.h"
- #include "./SYSTEM/usart/usart.h"
- #include "./SYSTEM/delay/delay.h"
- #include "./BSP/LED/led.h"
- #include "./BSP/LCD/lcd.h"
- #include "./BSP/ADC/adc.h"
-
-
- #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;
-
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
- delay_init(72); /* 延时初始化 */
- 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 DMA TEST", 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++) /* 每个通道采集了50次数据,进行50次累加 */
- {
- sum += g_adc_dma_buf[(6 * i) + j]; /* 相同通道的转换数据累加 */
- }
- adcx = sum / (ADC_DMA_BUF_SIZE / 6); /* 取平均值 */
-
- /* 显示结果 */
- lcd_show_xnum(108, 110 + (j * 30), adcx, 4, 12, 0, BLUE); /* 显示ADC采样后的原始值 */
-
- temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */
- adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
- lcd_show_xnum(108, 122 + (j * 30), adcx, 1, 12, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
-
- temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
- temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
- lcd_show_xnum(120, 122 + (j * 30), temp, 3, 12, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
- }
-
- g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
- adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */
- }
-
- LED0_TOGGLE();
- delay_ms(100);
- }
- }
这个实验与单通道ADC(DMA读取)实验大致相同
(1)如何确定过采样率
根据要增加的分辨率位数计算过采样频率方程:
(2)如何求均值
举个例子:12位分辨率的ADC提高4位分辨率,采样频率就要提高256倍
即需要256次采集才能得到一次16位分辨率的数据
然后将这256次采集结果求和,求和的结果再右移4位,就得到提高分辨率后的结果
注意:提高N 位分辨率,需要 右移N位
1,功能描述 通过DMA读取数据
通过ADC1通道1(PA1)过采样实现16位分辨率采集电压,并显示ADC转换的数字量及换算后的电压值
2,确定最小刻度
VREF+ = 3.3V 0V≤VIN≤3.3V 最小刻度 = 3.3 / 4096 F4/F7/H7系列还需要考虑ADC分辨率
3,确定转换时间
采样时间设置为最小值1.5个ADC时钟周期,可以得到转换时间为1.17us * 256
4,模式组合
连续转换模式、不使用扫描模式
- #include "./BSP/ADC/adc.h"
-
-
- DMA_HandleTypeDef g_dma_adc_handle;
- ADC_HandleTypeDef g_adc_dma_handle;
- uint8_t g_adc_dma_sta;
-
- /* ADC DMA读取 初始化函数 */
- void adc_dma_init(uint32_t mar)
- {
- ADC_ChannelConfTypeDef adc_ch_conf;
-
- __HAL_RCC_DMA1_CLK_ENABLE();
-
- g_dma_adc_handle.Instance = DMA1_Channel1;
- g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
- g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;
- g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE;
- g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
- g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
- g_dma_adc_handle.Init.Mode = DMA_NORMAL;
- g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;
- HAL_DMA_Init(&g_dma_adc_handle);
-
- __HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);
-
- g_adc_dma_handle.Instance = ADC1;
- g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
- g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
- g_adc_dma_handle.Init.ContinuousConvMode = ENABLE;
- g_adc_dma_handle.Init.NbrOfConversion = 1;
- g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE;
- g_adc_dma_handle.Init.NbrOfDiscConversion = 0;
- g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
- HAL_ADC_Init(&g_adc_dma_handle);
-
- HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);
-
- adc_ch_conf.Channel = ADC_CHANNEL_1;
- adc_ch_conf.Rank = ADC_REGULAR_RANK_1;
- adc_ch_conf.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
- HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);
-
- HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);
- HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
-
- HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);
- HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar ,0);
- }
-
- /* ADC MSP初始化函数 */
- void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
- {
- if(hadc->Instance == ADC1)
- {
- GPIO_InitTypeDef gpio_init_struct;
- RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
-
- __HAL_RCC_GPIOA_CLK_ENABLE();
- __HAL_RCC_ADC1_CLK_ENABLE();
-
- gpio_init_struct.Pin = GPIO_PIN_1;
- gpio_init_struct.Mode = GPIO_MODE_ANALOG;
- HAL_GPIO_Init(GPIOA, &gpio_init_struct);
-
- adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;
- adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;
- HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);
- }
- }
-
- /* 使能一次ADC DMA传输函数 */
- void adc_dma_enable(uint16_t cndtr)
- {
- ADC1->CR2 &= ~(1 << 0);
-
- DMA1_Channel1->CCR &= ~(1 << 0);
- while (DMA1_Channel1->CCR & (1 << 0));
- DMA1_Channel1->CNDTR = cndtr;
- DMA1_Channel1->CCR |= 1 << 0;
-
- ADC1->CR2 |= 1 << 0;
- ADC1->CR2 |= 1 << 22;
-
- // __HAL_ADC_DISABLE(&g_adc_dma_handle);
- //
- // __HAL_DMA_DISABLE(&g_dma_adc_handle);
- // while (__HAL_DMA_GET_FLAG(&g_dma_adc_handle, __HAL_DMA_GET_TC_FLAG_INDEX(&g_dma_adc_handle)));
- // DMA1_Channel1->CNDTR = cndtr;
- // __HAL_DMA_ENABLE(&g_dma_adc_handle);
- //
- // __HAL_ADC_ENABLE(&g_adc_dma_handle);
- // HAL_ADC_Start(&g_adc_dma_handle);
- }
-
- /* ADC DMA采集中断服务函数 */
- void DMA1_Channel1_IRQHandler(void)
- {
- if (DMA1->ISR & (1<<1))
- {
- g_adc_dma_sta = 1;
- DMA1->IFCR |= 1 << 1;
- }
- }
- #ifndef __ADC_H
- #define __ADC_H
-
- #include "./SYSTEM/sys/sys.h"
-
-
- void adc_dma_init(uint32_t mar);
- void adc_dma_enable(uint16_t cndtr);
-
- #endif
-
-
-
- #include "./SYSTEM/sys/sys.h"
- #include "./SYSTEM/usart/usart.h"
- #include "./SYSTEM/delay/delay.h"
- #include "./BSP/LED/led.h"
- #include "./BSP/LCD/lcd.h"
- #include "./BSP/ADC/adc.h"
-
-
- /* ADC过采样技术, 是利用ADC多次采集的方式, 来提高ADC精度, 采样速度每提高4倍
- * 采样精度提高 1bit, 同时, ADC采样速度降低4倍, 如提高4bit精度, 需要256次采集
- * 才能得出1次数据, 相当于ADC速度慢了256倍. 理论上只要ADC足够快, 我们可以无限
- * 提高ADC精度, 但实际上ADC并不是无限快的, 而且由于ADC性能限制, 并不是位数无限
- * 提高结果就越好, 需要根据自己的实际需求和ADC的实际性能来权衡.
- */
- #define ADC_OVERSAMPLE_TIMES 256 /* ADC过采样次数, 这里提高4bit分辨率, 需要256倍采样 */
- #define ADC_DMA_BUF_SIZE ADC_OVERSAMPLE_TIMES * 10 /* ADC DMA采集 BUF大小, 应等于过采样次数的整数倍 */
-
- 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;
- uint32_t adcx;
- uint32_t sum;
- float temp;
-
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
- delay_init(72); /* 延时初始化 */
- 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, "ADC OverSample TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH1_VAL:", BLUE);
- lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH1_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[i];
- }
-
- adcx = sum / (ADC_DMA_BUF_SIZE / ADC_OVERSAMPLE_TIMES); /* 取平均值 */
- adcx >>= 4; /* 除以2^4倍, 得到12+4位 ADC精度值, 注意: 提高 N bit精度, 需要 >> N */
-
- /* 显示结果 */
- lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE); /* 显示ADC采样后的原始值 */
-
- temp = (float)adcx * (3.3 / 65536); /* 获取计算后的带小数的实际电压值,比如3.1111 */
- adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
- lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
-
- temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
- temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
- lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE); /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
-
- g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
- adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */
- }
-
- LED0_TOGGLE();
- delay_ms(100);
- }
- }