OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2654|回复: 0

《STM32F407 探索者开发指南》第三十一章 ADC实验(下)

[复制链接]

1140

主题

1152

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4895
金钱
4895
注册时间
2019-5-8
在线时间
1248 小时
发表于 2023-8-7 14:11:41 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 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

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

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规则序列寄存器1ADC_SQR1
ADC规则序列寄存器1描述如图31.4.1.1所示:                                 
image001.png
图31.4.1.1 ADC_SQR1寄存器

首先确认是使用六个通道,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 ADCHAL库驱动
本实验用到的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,它们的时钟开启方法如下:
  1. __HAL_RCC_ADC1_CLK_ENABLE ();           /* 使能ADC1时钟 */
  2. __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 程序流程图
QQ截图20230803155512.png
图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读取初始化函数。
  1. /**
  2. *@brief       ADC N通道(6通道) DMA读取 初始化函数
  3. *  @note      本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置
  4. *              另外,由于本函数用到了6个通道,宏定义会比较多内容,因此,本函数就不采用宏定义的
  5. *               方式来修改通道了,直接在本函数里面修改,这里我们默认使用PA0~PA5这6个通道.
  6. *
  7. *             注意: 本函数还是使用 ADC_ADCX(默认=ADC1)和ADC_ADCX_DMASx(DMA2_Stream4)
  8. *              及其相关定义
  9. *             不要乱修改adc.h里面的这两部分内容, 必须在理解原理的基础上进行修改, 否则可能导
  10. *             致无法正常使用.
  11. *
  12. *@param       mar         : 存储器地址
  13. *@retval      无
  14. */
  15. voidadc_nch_dma_init(uint32_t mar)
  16. {
  17.    ADC_ADCX_CHY_CLK_ENABLE();    /* 使能ADCx时钟 */
  18.    
  19.     if ((uint32_t)ADC_ADCX_DMASx > (uint32_t)DMA2)
  20.                                           /* 大于DMA1_Stream7,则为DMA2 */
  21.     {
  22.        __HAL_RCC_DMA2_CLK_ENABLE();    /* DMA2时钟使能 */
  23.     }
  24.     else
  25.     {
  26.        __HAL_RCC_DMA1_CLK_ENABLE();    /* DMA1时钟使能 */
  27.     }
  28.     /* DMA配置 */
  29.    g_dma_nch_adc_handle.Instance = ADC_ADCX_DMASx;              /* 设置DMA数据流*/
  30.    g_dma_nch_adc_handle.Init.Channel = DMA_CHANNEL_0;          /* 设置DMA通道 */
  31.     /* DIR = 1 , 外设到存储器模式 */
  32.    g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;
  33.    g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;    /* 外设非增量模式 */
  34.    g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE;         /* 存储器增量模式 */
  35.     /* 外设数据长度:16位 */
  36.    g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  37.     /* 存储器数据长度:16位 */
  38.    g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
  39.    g_dma_nch_adc_handle.Init.Mode = DMA_NORMAL                 /* 外设流控模式 */
  40.    g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;  /* 中等优先级 */
  41.    HAL_DMA_Init(&g_dma_nch_adc_handle);                          /* 初始化DMA */
  42.     /* 配置DMA传输参数 */
  43.    HAL_DMA_Start(&g_dma_nch_adc_handle, (uint32_t)&ADC_ADCX->DR, mar, 0);
  44.     /* 设置ADC对应的DMA */
  45.    g_adc_nch_dma_handle.DMA_Handle = &g_dma_nch_adc_handle;
  46.    g_adc_nch_dma_handle.Instance = ADC_ADCX;
  47.     /* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */
  48.    g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  49.     g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B;      /* 12位模式 */
  50.    g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;      /* 右对齐 */
  51.    g_adc_nch_dma_handle.Init.ScanConvMode = ENABLE;                  /* 扫描模式 */
  52.     /* 连续转换模式,转换完成之后接着继续转换 */
  53.    g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE;
  54.     /* 禁止不连续采样模式 */
  55.    g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE;
  56.     /* 使用转换通道数,需根据实际转换通道去设置 */
  57.    g_adc_nch_dma_handle.Init.NbrOfConversion = ADC_CH_NUM;
  58.     /* 不连续采样通道数为0 */
  59.    g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0;
  60.     /* 软件触发 */
  61.    g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  62.    g_adc_nch_dma_handle.Init.ExternalTrigConvEdge =
  63.                              ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发, 此位忽略 */
  64.     /* 开启DMA连续转换 */
  65.    g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE;
  66.    HAL_ADC_Init(&g_adc_nch_dma_handle);                   /* 初始化ADC */
  67.    adc_nch_dma_gpio_init();    /* GPIO 初始化 */
  68.     /* 设置采样规则序列1~6 */
  69.    adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH0, 1,
  70.                                    ADC_SAMPLETIME_480CYCLES);
  71.    adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH1, 2,
  72.                                    ADC_SAMPLETIME_480CYCLES);
  73.    adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH2, 3,
  74.                                    ADC_SAMPLETIME_480CYCLES);
  75.    adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH3, 4,
  76.                                   ADC_SAMPLETIME_480CYCLES);
  77.    adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH4, 5,
  78.                                    ADC_SAMPLETIME_480CYCLES);
  79.    adc_channel_set(&g_adc_nch_dma_handle, ADC_ADCX_CH5, 6,
  80.                                    ADC_SAMPLETIME_480CYCLES);
  81.    HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3);
  82.     /* 设置DMA中断优先级为3,子优先级为3 */
  83.    HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn);           /* 使能DMA中断 */
  84.     /* 开始DMA数据传输 */
  85.    HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar, sizeof(uint16_t));
  86.     /* TCIE=1, 使能传输完成中断 */
  87.    __HAL_DMA_ENABLE_IT(&g_dma_nch_adc_handle, DMA_IT_TC);
  88. }
复制代码
该函数比较长,很多配置和单通道ADC采集(DMA读取)实验是一样的,就不全部讲解,下面对不同点说明一下。

在ADC的初始化中,直接把6个通道对应的GPIO口初始化放在函数内部实现。ADC_HandleTypeDef结构体的配置与单通道ADC采集(DMA读取)实验主要不同的是我们使用了6个转换通道,然后需要对每个通道ADC_ChannelConfTypeDef结构体进行配置,用HAL_ADC_ConfigChannel函数来初始化。

DMA初始化过程是一样的。下面介绍多通道ADC的gpio初始化函数,其定义如下:
  1. /**
  2. *@brief       多通道ADC的gpio初始化函数
  3. *@param       无
  4. *@note        此函数会被adc_nch_dma_init()调用
  5. *@note        PA0-ADC_CHANNEL_0、PA1-ADC_CHANNEL_1、PA2-ADC_CHANNEL_2
  6. *                PA3-ADC_CHANNEL_3、PA4-ADC_CHANNEL_4、PA5-ADC_CHANNEL_5
  7. * @retval      无
  8. */
  9. voidadc_nch_dma_gpio_init(void)
  10. {
  11.    GPIO_InitTypeDef gpio_init_struct;
  12.    __HAL_RCC_GPIOA_CLK_ENABLE();                   /* 开启GPIOA引脚时钟 */
  13. gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3
  14. | GPIO_PIN_4 | GPIO_PIN_5;   /* GPIOA0~5 */
  15.    gpio_init_struct.Mode = GPIO_MODE_ANALOG;            /* 模拟 */
  16.    gpio_init_struct.Pull = GPIO_NOPULL;                  /* 不带上下拉 */
  17.    HAL_GPIO_Init(GPIOA, &gpio_init_struct);
  18. }
复制代码
该函数初始化GPIOA0~5,都是不带上下拉的模拟模式。此外本实验还要用到上个实验的adc_dma_enable函数和ADC_ADCX_DMASx_IRQHandler中断服务函数,所以这两个函数放在ADC实验公共部分程序里面,分别大家使用和移植,这里就不列出来,请回顾上个实验的介绍。

最后在main.c里面编写如下代码:
  1. #define ADC_DMA_BUF_SIZE 50 * 6 /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍 */
  2. uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];    /* ADC DMA BUF */
  3. extern uint8_tg_adc_dma_sta;    /* DMA传输状态标志, 0, 未完成; 1, 已完成 */
  4. int main(void)
  5. {
  6.     uint16_t i, j;
  7.     uint16_t adcx;
  8.     uint32_t sum;
  9.     float temp;
  10.    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
  11.     delay_init(168);                      /* 延时初始化 */
  12.     usart_init(115200);                  /* 串口初始化为115200 */
  13.     led_init();                            /* 初始化LED */
  14.     lcd_init();                            /* 初始化LCD */
  15.    adc_nch_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
  16.     lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
  17.     lcd_show_string(30,  70, 200, 16, 16, "ADC 6CH DMATEST", RED);
  18.     lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  19. lcd_show_string(30, 110, 200, 12, 12, "ADC1_CH0_VAL:", BLUE);
  20. /* 先在固定位置显示小数点 */
  21.     lcd_show_string(30, 122, 200, 12, 12, "ADC1_CH0_VOL:0.000V", BLUE);
  22.    
  23. lcd_show_string(30, 140, 200, 12, 12, "ADC1_CH1_VAL:", BLUE);
  24. /* 先在固定位置显示小数点 */
  25.     lcd_show_string(30, 152, 200, 12, 12, "ADC1_CH1_VOL:0.000V", BLUE);
  26. lcd_show_string(30, 170, 200, 12, 12, "ADC1_CH2_VAL:", BLUE);
  27. /* 先在固定位置显示小数点 */
  28.     lcd_show_string(30, 182, 200, 12, 12, "ADC1_CH2_VOL:0.000V", BLUE);
  29. lcd_show_string(30, 200, 200, 12, 12, "ADC1_CH3_VAL:", BLUE);
  30. /* 先在固定位置显示小数点 */
  31.     lcd_show_string(30, 212, 200, 12, 12, "ADC1_CH3_VOL:0.000V", BLUE);
  32. lcd_show_string(30, 230, 200, 12, 12, "ADC1_CH4_VAL:", BLUE);
  33. /* 先在固定位置显示小数点 */
  34.     lcd_show_string(30, 242, 200, 12, 12, "ADC1_CH4_VOL:0.000V", BLUE);
  35. lcd_show_string(30, 260, 200, 12, 12, "ADC1_CH5_VAL:", BLUE);
  36. /* 先在固定位置显示小数点 */
  37.     lcd_show_string(30, 272, 200, 12, 12, "ADC1_CH5_VOL:0.000V", BLUE);
  38.     adc_dma_enable(ADC_DMA_BUF_SIZE);   /* 启动ADC DMA采集 */
  39.     while (1)
  40.     {
  41.         if (g_adc_dma_sta == 1)
  42.         {
  43.             /* 循环显示通道0~通道5的结果 */
  44.             for(j = 0; j < 6; j++)  /* 遍历6个通道 */
  45.             {
  46.                 sum= 0; /* 清零 */
  47.                 for (i = 0; i < ADC_DMA_BUF_SIZE / 6; i++)  
  48.                 {/* 每个通道采集了10次数据,进行10次累加 */
  49.                    sum +=g_adc_dma_buf[(6 * i) + j];  /* 相同通道的转换数据累加 */
  50.                 }
  51.                adcx = sum / (ADC_DMA_BUF_SIZE / 6);    /* 取平均值 */
  52.                
  53.                  /* 显示ADCC采样后的原始值 *
  54.                lcd_show_xnum(108, 110 + (j * 30), adcx, 4, 12, 0, BLUE);
  55. /* 获取计算后的带小数的实际电压值,比如3.1111 */
  56.                temp = (float)adcx * (3.3 / 4096);
  57.                adcx = temp;    /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
  58. /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
  59.                lcd_show_xnum(108, 122 + (j * 30), adcx, 1, 12, 0, BLUE);  
  60. /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
  61.                temp -= adcx;
  62. /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
  63.                temp *= 1000;   
  64. /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
  65.                lcd_show_xnum(120, 122 + (j * 30), temp, 3, 12, 0X80, BLUE);
  66.             }
  67.            g_adc_dma_sta = 0;                   /* 清除DMA采集完成状态标志 */
  68.            adc_dma_enable(ADC_DMA_BUF_SIZE);    /* 启动下一次ADC DMA采集 */
  69.         }
  70.         LED0_TOGGLE();
  71.         delay_ms(100);
  72.     }
  73. }
复制代码
这里使用了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所示:     
image005.png
图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在开发板上的位置,如下图所示:     
image007.png
图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 ADCHAL库驱动
本实验用到的ADC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。下面介绍单通道ADC过采样(16位分辨率)配置步骤。

单通道ADC过采样(16位分辨率)配置步骤
1)开启ADCx和通道输出的GPIO时钟,配置该IO口的复用功能输出
首先开启ADCx的时钟,然后配置GPIO为复用功能输出。本实验我们默认用到ADC1通道5,对应IO是PA5,它们的时钟开启方法如下:
  1. __HAL_RCC_ADC1_CLK_ENABLE ();           /* 使能ADC1时钟 */
  2. __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 程序流程图
QQ截图20230803155622.png
图31.5.3.2.1 单通道ADC过采样(16位分辨率)实验程序流程图

31.5.3.3 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ADC驱动源码包括两个文件:adc.c和adc.h。本实验沿用前面实验中的函数,并没有改动。

现在看以下main.c如下代码:
  1. #define ADC_OVERSAMPLE_TIMES    256 /*过采样次数, 提高4bit分辨率, 需要256倍采样*/
  2. /* ADC DMA采集 BUF大小, 应等于过采样次数的整数倍 */
  3. #define ADC_DMA_BUF_SIZE        ADC_OVERSAMPLE_TIMES * 10  
  4. uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMABUF */
  5. extern uint8_tg_adc_dma_sta;               /* DMA传输状态标志, 0,未完成; 1, 已完成 */
  6. extern ADC_HandleTypeDefg_adc_handle;    /* ADC句柄 */
  7. int main(void)
  8. {
  9.     uint16_t i;
  10.     uint32_t adcx;
  11.     uint32_t sum;
  12.     float temp;
  13.     HAL_Init();                                      /* 初始化HAL库 */
  14.    sys_stm32_clock_init(336, 8, 2, 7);         /* 设置时钟,168Mhz */
  15.     delay_init(168);                                /* 延时初始化 */
  16.     usart_init(115200);                            /* 串口初始化为115200 */
  17.     led_init();                                      /* 初始化LED */
  18.     lcd_init();                                      /* 初始化LCD */
  19. adc_dma_init((uint32_t)&g_adc_dma_buf);     /* 初始化ADC DMA采集 */
  20.     lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
  21.     lcd_show_string(30,  70, 200, 16, 16, "ADCOverSample TEST", RED);
  22.     lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  23. lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH5_VAL:", BLUE);.
  24. /* 先在固定位置显示小数点 */
  25.     lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH5_VOL:0.000V", BLUE);
  26.     adc_dma_enable(ADC_DMA_BUF_SIZE);    /* 启动ADC DMA采集 */
  27.     while (1)
  28.     {
  29.         if (g_adc_dma_sta == 1)
  30.         {
  31.             /* 计算DMA 采集到的ADC数据的平均值 */
  32.            sum = 0;
  33.            for (i = 0; i < ADC_DMA_BUF_SIZE; i++)    /* 累加 */
  34.            {
  35.                 sum+=g_adc_dma_buf;
  36.             }
  37.             adcx = sum / (ADC_DMA_BUF_SIZE /ADC_OVERSAMPLE_TIMES); /* 取平均值 */
  38.             /* 除以2^4倍, 得到12+4位 ADC精度值, 注意: 提高 N bit精度, 需要 >> N */
  39.             adcx >>= 4;
  40.             /* 显示ADCC采样后的原始值 */
  41.            lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE);
  42.             /* 获取计算后的带小数的实际电压值,比如3.1111 */
  43.             temp = (float)adcx * (3.3 / 65536);  
  44.             adcx = temp;           /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
  45.             /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
  46.            lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE);  
  47.             temp -= adcx;/* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111*/
  48.             temp *= 1000;
  49.             /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数 */
  50.             /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
  51.            lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE);
  52.            g_adc_dma_sta = 0;                      /* 清除DMA采集完成状态标志 */
  53.            adc_dma_enable(ADC_DMA_BUF_SIZE);    /* 启动下一次ADC DMA采集 */
  54.         }
  55.         LED0_TOGGLE();
  56.         delay_ms(100);
  57.    }
  58. }
复制代码
此部分代码,我们在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所示:     
image011.png
图31.5.4.1 单通道ADC过采样(16位分辨率)实验测试图

上图中,我们使用杜邦线将P11的ADC和RV1连接,使得PA5连接到电位器上,测试的是电位器的电压,并可以通过螺丝刀调节电位器改变电压值,范围:0~3.3V。

LED0闪烁,提示程序运行。大家可以试试把杜邦线接到其他地方,看看电压值是否准确?注意:一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-24 08:35

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表