本帖最后由 正点原子运营 于 2024-4-26 17:08 编辑
1)实验平台:正点原子 M144Z-M3 STM32F103最小系统板
2) 章节摘自【正点原子】M144Z-M3最小系统板使用指南——STM32F103版
6)正点原子STM32技术交流QQ群:725095144
本章介绍使用STM32F103的DMA进行单通道的ADC采集。通过本章的学习,读者将学习到DMA、ADC的使用。 本章分为如下几个小节: 34.1 硬件设计 34.2 程序设计 34.3 下载验证
34.1 硬件设计 34.1.1 例程功能 1. LCD上不断刷新显示PA1引脚输入电压采样的数字量和模拟量。 2. LED0闪烁,提示程序正在运行
34.1.2 硬件资源 1. LED LED0 - PB5 2. 正点原子2.8/3.5/4.3/7/10寸TFTLCD模块 3. ADC3 Channel1 - PA1 4. DMA2 Channel5
34.1.3 原理图 本章实验使用的ADC3为STM32F103的片上资源,因此没有对应的连接原理图。
34.2 程序设计 34.2.1 HAL库的ADC驱动 本章实验中使用DAM2 Channel5的外设到存储器模式,将ADC3的转换结果保存到指定的存储器中,其具体的步骤如下: ①:初始化ADC ②:配置ADC通道 ③:开始ADC DMA传输 在HAL库中对应的驱动函数如下: ①:初始化ADC 请见第34.2.1小节中初始化ADC的相关内容。 ②:配置ADC通道 请见第34.2.1小节中配置ADC通道的相关内容。 ③:开始ADC DMA传输 该函数用于开始ADC DMA传输,其函数原型如下所示: - HAL_StatusTypeDef HAL_ADC_Start_DMA( ADC_HandleTypeDef* hadc,
- uint32_t* pData,
- uint32_t Length);
复制代码该函数的形参描述,如下表所示: 表34.2.1.1 函数HAL_ADC_Start_DMA()形参描述 该函数的返回值描述,如下表所示: 表34.2.1.2 函数HAL_ADC_Start_DMA()形参描述 该函数的使用示例,如下所示: - #include"stm32f1xx_hal.h"
- void example_fun(void)
- {
- uint16_t buf;
-
- /* 开始ADC DMA传输 */
- HAL_ADC_Start_DMA(&adc_handle, &buf, sizeof(buf) / sizeof(uint16_t));
- }
复制代码
34.2.2 ADC驱动 本章实验的ADC驱动主要负责向应用层提供ADC初始化和启动ADC的DMA采集的函数,同时实现DMA的中断回调函数。本章实验中,ADC的驱动代码包括adc.c和adc.h两个文件。 ADC驱动中,对DMA、GPIO、ADC的相关宏定义,如下所示: - #define ADC_DMA_ADCX ADC3
- #define ADC_DMA_ADCX_CLK_ENABLE() \
- do { \
- __HAL_RCC_ADC3_CLK_ENABLE(); \
- }while (0)
- #define ADC_DMA_ADCX_CHY ADC_CHANNEL_1
- #define ADC_DMA_ADCX_CHY_GPIO_PORT GPIOA
- #define ADC_DMA_ADCX_CHY_GPIO_PIN GPIO_PIN_1
- #define ADC_DMA_ADCX_CHY_GPIO_CLK_ENABLE() \
- do { \
- __HAL_RCC_GPIOA_CLK_ENABLE(); \
- }while (0)
- #define ADC_DMA_ADCX_DMACX DMA2_Channel5
- #define ADC_DMA_ADCX_DMA_CLK_ENABLE() \
- do { \
- __HAL_RCC_DMA2_CLK_ENABLE(); \
- }while (0)
- #define ADC_DMA_ADCX_DMACX_IRQn DMA2_Channel4_5_IRQn
- #define ADC_DMA_ADCX_DMACX_IRQHandler DMA2_Channel4_5_IRQHandler
- ADC驱动中,ADC的初始化函数,如下所示:
- /**
- *@brief 初始化ADC DMA读取
- *@param memory_base: 读取目标内存基地址
- *@retval 无
- */
- void adc_dma_init(uint32_t memory_base)
- {
- /* 配置ADC */
- g_adc_dma_handle.Instance = ADC_DMA_ADCX;
- g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
- g_adc_dma_handle.Init.ScanConvMode = 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 = 1;
- g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
- HAL_ADC_Init(&g_adc_dma_handle);
-
- g_adc_dma_memory_base = memory_base;
- }
- /**
- *@brief HAL库ADC初始化MSP函数
- *@param hadc: ADC句柄
- *@retval 无
- */
- voidHAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
- {
- GPIO_InitTypeDef gpio_init_struct = {0};
-
- if (hadc->Instance == ADC_DMA_ADCX)
- {
- ADC_DMA_ADCX_CLK_ENABLE();
- ADC_DMA_ADCX_CHY_GPIO_CLK_ENABLE();
- ADC_DMA_ADCX_DMA_CLK_ENABLE();
-
- /* 初始化ADC采样引脚 */
- gpio_init_struct.Pin = ADC_DMA_ADCX_CHY_GPIO_PIN;
- gpio_init_struct.Mode = GPIO_MODE_ANALOG;
- gpio_init_struct.Pull = GPIO_NOPULL;
- gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
- HAL_GPIO_Init(ADC_DMA_ADCX_CHY_GPIO_PORT, &gpio_init_struct);
-
- /* 配置DMA */
- g_adc_dma_dma_handle.Instance = ADC_DMA_ADCX_DMACX;
- g_adc_dma_dma_handle.Init.Direction =DMA_PERIPH_TO_MEMORY;
- g_adc_dma_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;
- g_adc_dma_dma_handle.Init.MemInc = DMA_MINC_ENABLE;
- g_adc_dma_dma_handle.Init.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD;
- g_adc_dma_dma_handle.Init.MemDataAlignment =DMA_MDATAALIGN_HALFWORD;
- g_adc_dma_dma_handle.Init.Mode = DMA_NORMAL;
- g_adc_dma_dma_handle.Init.Priority =DMA_PRIORITY_VERY_HIGH;
- HAL_DMA_Init(&g_adc_dma_dma_handle);
-
- __HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_adc_dma_dma_handle);
-
- HAL_NVIC_SetPriority(ADC_DMA_ADCX_DMACX_IRQn, 0, 0);
- HAL_NVIC_EnableIRQ(ADC_DMA_ADCX_DMACX_IRQn);
- }
- }
复制代码从上面的代码中可以看出,该ADC初始化函数初始化了ADC,同时也使能了ADC的DMA请求,并且在HAL库ADC初始化MSP函数中配置了DMA。 ADC驱动中,启动ADC的DMA采集的函数,如下所示: - /**
- *@brief 开启ADC DMA读取
- *@param length: DMA读取次数
- *@retval 无
- */
- voidadc_dma_enable(uint32_t length)
- {
- HAL_ADC_Stop_DMA(&g_adc_dma_handle);
-
- adc_channel_set( &g_adc_dma_handle,
- ADC_DMA_ADCX_CHY,
- ADC_REGULAR_RANK_1,
- ADC_SAMPLETIME_239CYCLES);
-
- HAL_ADC_Start_DMA( &g_adc_dma_handle,
- (uint32_t *)g_adc_dma_memory_base,
- length);
- }
复制代码从上面的代码中可以看到,开启ADC的DMA采集就是先配置ADC通道,然后调用函数HAL_ADC_Start_DMA()开始ADC DMA传输。 DAC驱动代码中,ADC转换完成回调函数,如下所示: - /**
- *@brief HAL库ADC转换完成回调函数
- *@param hadc: ADC句柄
- *@retval 无
- */
- voidHAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
- {
- if (hadc->Instance == ADC_DMA_ADCX)
- {
- g_adc_dma_sta = 1;
- }
- }
复制代码ADC转换完成回调函数中可以看到,当ADC转换完成后,便将相应的标志位置1,在应用程序中便可以此来判断ADC的采集是否完成。
34.2.3 实验应用代码 本章实验的应用代码,如下所示: - #define ADC_DMA_BUF_SIZE 50
- uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];
- extern uint8_t g_adc_dma_sta;
- int main(void)
- {
- uint16_t adc_result;
- uint16_t voltage;
- uint16_t index;
- uint32_t result_sum;
-
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(RCC_PLL_MUL9); /* 配置时钟,72MHz */
- delay_init(72); /* 初始化延时 */
- usart_init(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 DMATEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
-
- lcd_show_string(30, 110, 200, 16, 16, "ADC3_CH1_VAL:0", BLUE);
- lcd_show_string(30, 130, 200, 16, 16, "ADC3_CH1_VOL:0.000V", BLUE);
-
- /* 开启ADC DMA读取 */
- adc_dma_enable(ADC_DMA_BUF_SIZE);
-
- while (1)
- {
- if (g_adc_dma_sta == 1)
- {
- g_adc_dma_sta = 0;
-
- /* 对DMA读取的多个ADC数据进行均值滤波 */
- for (result_sum = 0, index=0; index<ADC_DMA_BUF_SIZE; index++)
- {
- result_sum += g_adc_dma_buf[index];
- }
- adc_result = result_sum / ADC_DMA_BUF_SIZE;
- lcd_show_xnum(134, 110, adc_result, 5, 16, 0, BLUE);
-
- /* 计算实际电压值(扩大1000倍) */
- voltage = (adc_result * 3300) / 4095;
- lcd_show_xnum(134, 130, voltage / 1000, 1, 16, 0, BLUE);
- lcd_show_xnum(150, 130, voltage % 1000, 3, 16, 0x80, BLUE);
-
- adc_dma_enable(ADC_DMA_BUF_SIZE);
- }
-
- LED0_TOGGLE();
-
- delay_ms(100);
- }
- }
复制代码从上面的代码中可以看出,应用代码中定义了一个数组作为DMA传输的目的存储器,随后在完成相关初始化后,就开启ADC的DMA传输,随后便等待DMA传输完成,DMA传输完成后对所有ADC转换的结构进行均值滤波后在LCD上进行显示,同时显示转换的电压模拟量。
34.3 下载验证 在完成编译和烧录操作后,可以看到LCD上不断地刷新显示ADC3通道1(PA1引脚)采集到电压的数字量和模拟量,此时可以通过杜邦线给PA1引脚接入不同的电压值(注意共地,且输入电压不能超过3.3V,否则可能损坏开发板),可以看到LCD上显示的电压数字量和模拟量也随之改变。 |