1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html##
第三十五章 单通道ADC采集实验
本章介绍使用APM32F407模数转换器(ADC)进行带通道的电压采集。通过本章的学习,读者将学习到单通道ADC的使用。
本章分为如下几个小节:
35.1 硬件设计
35.2 程序设计
35.3 下载验证
35.1 硬件设计
35.1.1 例程功能
- ADC1采集通道1(PA1)上的电压,并在LCD上显示ADC转换后电压的数字量和换算后的模拟量
- LED0闪烁,指示程序正在运行
35.1.2 硬件资源 - LED
LED0 - PF9 - 正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
- ADC1
通道1 - PA1
35.1.3 原理图
本章实验使用的ADC1为APM32F407的片上资源,因此没有对应的连接原理图。
35.2 程序设计
35.2.1 Geehy标准库的ADC驱动
本章实验将使用ADC1的通道1(PA1引脚)采集外部输入电压的模拟量,并将其转换为数字量,其具体的步骤如下:
①:配置ADC通用控制寄存器
②:配置ADC
③:使能ADC
④:配置ADC规则通道
⑤:启动转换规则通道
⑥:等待规则通道转换结束
⑦:读取规则通道的转换结果
在Geehy标准库中对应的驱动函数如下:
①:配置ADC通用控制寄存器
该函数用于配置ADC通用控制寄存器,其函数原型如下所示:
void ADC_CommonConfig(ADC_CommonConfig_T* adcCommonConfig);
该函数的形参描述,如下表所示:
形参 描述
adcCommonConfig 指向ADC通用控制寄存器配置结构体的指针
需自行定义,并根据ADC通用控制寄存器的配置参数填充结构体中的成员变量
表35.2.1.1 函数ADC_CommonConfig()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
无 无
表35.2.1.2 函数ADC_CommonConfig()返回值描述
该函数使用了ADC_CommonConfig_T类型的结构体变量传入ADC通用控制寄存器的配置参数,该结构体的定义如下所示:
typedef enum
{
/* 所有ADC都独立 */
ADC_MODE_INDEPENDENT = (uint8_t)0x00, /* 独立模式 */
/* ADC1和ADC2一起工作,ADC3独立 */
/* 规则同时+注入同时组合模式 */
ADC_MODE_DUAL_REGSIMULT_INJECSIMULT = (uint8_t)0x01,
/* 规则同时+交替触发组合模式 */
ADC_MODE_DUAL_REGSIMULT_ALTERTRIG = (uint8_t)0x02,
ADC_MODE_DUAL_INJECSIMULT = (uint8_t)0x05, /* 仅注入同时模式 */
ADC_MODE_DUAL_REGSIMULT = (uint8_t)0x06, /* 仅规则同时模式 */
ADC_MODE_DUAL_INTERL = (uint8_t)0x07, /* 仅交替模式 */
ADC_MODE_DUAL_ALTERTRIG = (uint8_t)0x09, /* 仅交替触发模式 */
/* ADC1、ADC2和ADC3一起工作 */
/* 规则同时+注入同时组合模式 */
ADC_MODE_TRIPLE_REGSIMULT_INJECSIMULT = (uint8_t)0x11,
/* 规则同时+交替触发组合模式 */
ADC_MODE_TRIPLE_REGSIMULT_ALTERTRIG = (uint8_t)0x12,
ADC_MODE_TRIPLE_INJECSIMULT = (uint8_t)0x15, /* 仅注入同时模式 */
ADC_MODE_TRIPLE_REGSIMULT = (uint8_t)0x16, /* 仅规则同时模式 */
ADC_MODE_TRIPLE_INTERL = (uint8_t)0x17, /* 仅交替模式 */
ADC_MODE_TRIPLE_ALTERTRIG = (uint8_t)0x19 /* 仅交替触发模式 */
} ADC_MODE_T;
typedef enum
{
ADC_PRESCALER_DIV2, /*PCLK2 2分频 */
ADC_PRESCALER_DIV4, /*PCLK2 4分频 */
ADC_PRESCALER_DIV6, /*PCLK2 6分频 */
ADC_PRESCALER_DIV8 /*PCLK2 8分频 */
} ADC_PRESCALER_T;
typedef enum
{
ADC_ACCESS_MODE_DISABLED, /* 禁止DMA模式 */
ADC_ACCESS_MODE_1, /* 使能DMA模式1 */
ADC_ACCESS_MODE_2, /* 使能DMA模式2 */
ADC_ACCESS_MODE_3 /* 使能DMA模式3 */
} ADC_ACCESS_MODE_T;
typedef enum
{
ADC_TWO_SAMPLING_5CYCLES, /* 5个Tadcclk */
ADC_TWO_SAMPLING_6CYCLES, /* 6个Tadcclk */
ADC_TWO_SAMPLING_7CYCLES, /* 7个Tadcclk */
ADC_TWO_SAMPLING_8CYCLES, /* 8个Tadcclk */
ADC_TWO_SAMPLING_9CYCLES, /* 9个Tadcclk */
ADC_TWO_SAMPLING_10CYCLES, /* 10个Tadcclk */
ADC_TWO_SAMPLING_11CYCLES, /* 11个Tadcclk */
ADC_TWO_SAMPLING_12CYCLES, /* 12个Tadcclk */
ADC_TWO_SAMPLING_13CYCLES, /* 13个Tadcclk */
ADC_TWO_SAMPLING_14CYCLES, /* 14个Tadcclk */
ADC_TWO_SAMPLING_15CYCLES, /* 15个Tadcclk */
ADC_TWO_SAMPLING_16CYCLES, /* 16个Tadcclk */
ADC_TWO_SAMPLING_17CYCLES, /* 17个Tadcclk */
ADC_TWO_SAMPLING_18CYCLES, /* 18个Tadcclk */
ADC_TWO_SAMPLING_19CYCLES, /* 19个Tadcclk */
ADC_TWO_SAMPLING_20CYCLES /* 20个Tadcclk */
} ADC_TWO_SAMPLING_T;
typedef struct
{
ADC_MODE_T mode; /* ADC模式 */
ADC_PRESCALER_T prescaler; /* ADC预分频器 */
ADC_ACCESS_MODE_T accessMode; /* DMA模式 */
ADC_TWO_SAMPLING_T twoSampling; /* 2个采样阶段之间的延迟 */
} ADC_CommonConfig_T;
该函数的使用示例,如下所示:
#include "apm32f4xx.h"
#include "apm32f4xx_adc.h"
void example_fun(void)
{
ADC_CommonConfig_T adc_comm_init_struct;
/* 配置ADC通用控制寄存器 */
adc_comm_init_struct.mode = ADC_MODE_INDEPENDENT;
adc_comm_init_struct.prescaler = ADC_PRESCALER_DIV4;
adc_comm_init_struct.accessMode = ADC_ACCESS_MODE_DISABLED;
adc_comm_init_struct.twoSampling = ADC_TWO_SAMPLING_5CYCLES;
ADC_CommonConfig(&adc_comm_init_struct);
}
②:配置ADC
该函数用于配置ADC的各项参数,其函数原型如下所示:
void ADC_Config(ADC_T* adc, ADC_Config_T* adcConfig);
该函数的形参描述,如下表所示:
形参 描述
adc 指向ADC外设结构体的指针
例如:ADC1、ADC2等(在apm32f4xx.h文件中有定义)
adcConfig 指向ADC配置结构体的指针
需自行定义,并根据ADC的配置参数填充结构体中的成员变量
表35.2.1.3 函数ADC_Config()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
无 无
表35.2.1.4 函数ADC_Config()返回值描述
该函数使用ADC_Config_T类型的结构体变量传入ADC外设的配置参数,该结构体的定义如下所示:
typedef enum
{
ADC_RESOLUTION_12BIT, /* 12位 */
ADC_RESOLUTION_10BIT, /* 10位 */
ADC_RESOLUTION_8BIT, /* 8位 */
ADC_RESOLUTION_6BIT /* 6位 */
} ADC_RESOLUTION_T;
typedef enum
{
ADC_EXT_TRIG_EDGE_NONE, /* 禁止触发检测 */
ADC_EXT_TRIG_EDGE_RISING, /* 上升沿上的触发检测 */
ADC_EXT_TRIG_EDGE_FALLING, /* 下降沿上的触发检测 */
ADC_EXT_TRIG_EDGE_RISING_FALLING, /* 上升沿和下降沿上的触发检测 */
} ADC_EXT_TRIG_EDGE_T;
typedef enum
{
ADC_EXT_TRIG_CONV_TMR1_CC1, /* 定时器1的CC1事件 */
ADC_EXT_TRIG_CONV_TMR1_CC2, /* 定时器1的CC2事件 */
ADC_EXT_TRIG_CONV_TMR1_CC3, /* 定时器1的CC3事件 */
ADC_EXT_TRIG_CONV_TMR2_CC2, /* 定时器2的CC2事件 */
ADC_EXT_TRIG_CONV_TMR2_CC3, /* 定时器2的CC3事件 */
ADC_EXT_TRIG_CONV_TMR2_CC4, /* 定时器2的CC4事件 */
ADC_EXT_TRIG_CONV_TMR2_TRGO, /* 定时器2的TRGO事件 */
ADC_EXT_TRIG_CONV_TMR3_CC1, /* 定时器3的CC1事件 */
ADC_EXT_TRIG_CONV_TMR3_TRGO, /* 定时器3的TRGO事件 */
ADC_EXT_TRIG_CONV_TMR4_CC4, /* 定时器4的CC4事件 */
ADC_EXT_TRIG_CONV_TMR5_CC1, /* 定时器5的CC1事件 */
ADC_EXT_TRIG_CONV_TMR5_CC2, /* 定时器5的CC2事件 */
ADC_EXT_TRIG_CONV_TMR5_CC3, /* 定时器5的CC3事件 */
ADC_EXT_TRIG_CONV_TMR8_CC1, /* 定时器8的CC1事件 */
ADC_EXT_TRIG_CONV_TMR8_TRGO, /* 定时器8的TRGO事件 */
ADC_EXT_TRIG_CONV_EINT_11 /* EINT线11 */
} ADC_EXT_TRIG_CONV_T;
typedef enum
{
ADC_DATA_ALIGN_RIGHT, /* 右对齐 */
ADC_DATA_ALIGN_LEFT /* 左对齐 */
} ADC_DATA_ALIGN_T;
typedef struct
{
ADC_RESOLUTION_T resolution; /* 分辨率 */
uint8_t scanConvMode; /* 扫描模式 */
uint8_t continuousConvMode; /* 连续转换模式 */
ADC_EXT_TRIG_EDGE_T extTrigEdge; /* 规则通道的外部触发 */
ADC_EXT_TRIG_CONV_T extTrigConv; /* 启动规则组转换的外部事件 */
ADC_DATA_ALIGN_T dataAlign; /* 数据对齐方式 */
uint8_t nbrOfChannel; /* 规则通道序列长度 */
} ADC_Config_T;
该函数的使用示例,如下所示:
#include "apm32f4xx.h"
#include "apm32f4xx_adc.h"
void example_fun(void)
{
ADC_Config_T adc_init_struct;
/* 配置ADC1 */
adc_init_struct.resolution = ADC_RESOLUTION_12BIT;
adc_init_struct.scanConvMode = DISABLE;
adc_init_struct.continuousConvMode = DISABLE;
adc_init_struct.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE;
adc_init_struct.dataAlign = ADC_DATA_ALIGN_RIGHT;
adc_init_struct.nbrOfChannel = 1;
ADC_Config(ADC1, &adc_init_struct);
}
③:使能ADC
该函数用于使能ADC,其函数原型如下所示:
void ADC_Enable(ADC_T* adc);
该函数的形参描述,如下表所示:
形参 描述
adc 指向ADC外设结构体的指针
例如:ADC1、ADC2等(在apm32f4xx.h文件中有定义)
表35.2.1.5 函数ADC_Enable()形参描述
该函数的返回值描述如下表所示
返回值 描述
无 无
表35.2.1.6 函数ADC_Enable()返回值描述
该函数的使用示例,如下所示:
#include "apm32f4xx.h"
#include "apm32f4xx_adc.h"
void example_fun(void)
{
/* 使能ADC1 */
ADC_Enable(ADC1);
}
④:配置ADC规则通道
该函数用于配置ADC规则通道,其函数原型如下所示:
void ADC_ConfigRegularChannel( ADC_T* adc,
uint8_t channel,
uint8_t rank,
uint8_t sampleTime);
该函数的形参描述,如下表所示:
形参 描述
adc 指向ADC外设结构体的指针
例如:ADC1、ADC2等(在apm32f4xx.h文件中有定义)
channel ADC通道
例如:ADC_CHANNEL_0、ADC_CHANNEL_1等(在apm32f4xx_adc.h文件中有定义)
rank 转换顺序
sampleTime ADC通道的采样周期
例如:ADC_SAMPLETIME_3CYCLES
ADC_SAMPLETIME_15CYCLES等(在apm32f4xx_adc.h文件中有定义)
表35.2.1.7 函数ADC_ConfigRegularChannel()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
无 无
表35.2.1.8 函数ADC_ConfigRegularChannel()返回值描述
该函数的使用示例,如下所示:
#include "apm32f4xx.h"
#include "apm32f4xx_adc.h"
void example_fun(void)
{
/* 使能ADC1通道1的规则通道 */
ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_1, 1, ADC_SAMPLETIME_480CYCLES);
}
⑤:启动转换规则通道
该函数用于启动转换规则通道,其函数原型如下所示:
void ADC_SoftwareStartConv(ADC_T* adc);
该函数的形参描述,如下表所示:
形参 描述
adc 指向ADC外设结构体的指针
例如:ADC1、ADC2等(在apm32f4xx.h文件中有定义)
表35.2.1.9 函数ADC_SoftwareStartConv()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
无 无
表35.2.1.10 函数ADC_SoftwareStartConv()返回值描述
该函数的使用示例,如下所示:
#include "apm32f4xx.h"
#include "apm32f4xx_adc.h"
void example_fun(void)
{
/* 使能ADC1转换规则通道 */
ADC_SoftwareStartConv(ADC1);
}
⑥:读取ADC状态标志
该函数用于读取ADC的状态标志,其函数原型如下所示:
uint8_t ADC_ReadStatusFlag(ADC_T* adc, ADC_FLAG_T flag);
该函数的形参描述,如下表所示:
形参 描述
adc 指向ADC外设结构体的指针
例如:ADC1、ADC2等(在apm32f4xx.h文件中有定义)
flag 指定的ADC状态标志
例如:ADC_FLAG_AWD、ADC_FLAG_EOC等(在apm32f4xx_adc.h文件中有定义)
表35.2.1.11 函数ADC_ReadStatusFlag()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
SET 事件标志发生
RESET 事件标志为发生
表35.2.1.12 函数ADC_ReadStatusFlag()返回值描述
该函数的使用示例,如下所示:
#include "apm32f4xx.h"
#include "apm32f4xx_adc.h"
void example_fun(void)
{
uint8_t flag;
/* 读取ADC1的转换结束标志 */
flag = ADC_ReadStatusFlag(ADC1, ADC_FLAG_EOC)
if (flag == SET)
{
/* Do something. */
}
else
{
/* Do something. */
}
}
⑦:读取规则通道转换结果
该函数用于读取规则通道的转换结果,其函数原型如下所示:
uint16_t ADC_ReadConversionValue(ADC_T* adc);
该函数的形参描述,如下表所示:
形参 描述
adc 指向ADC外设结构体的指针
表35.2.1.13 函数ADC_ReadConversionValue()形参描述
该函数的返回值描述,如下表所示:
返回值 描述
uint16_t类型数据 转换结果
表35.2.1.14 函数ADC_ReadConversionValue()返回值描述
该函数的使用示例,如下所示:
#include "apm32f4xx.h"
#include "apm32f4xx_adc.h"
void example_fun(void)
{
uint16_t data;
/* 读取规则通道转换结果 */
data = ADC_ReadConversionValue(ADC1);
/* Do something. */
}
35.2.2 ADC驱动
本章实验的ADC驱动主要负责向应用层提供ADC的初始化和获取ADC转换结果的函数。本章实验中,ADC的驱动代码包括adc.c和adc.h两个文件。
ADC驱动中,对ADC、GPIO的相关宏定义,如下所示:
#define ADC_ADCX ADC1
#define ADC_ADCX_CHY ADC_CHANNEL_1
#define ADC_ADCX_CHY_CLK_ENABLE() \
do { \
RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1); \
} while (0)
#define ADC_ADCX_CHY_GPIO_PORT GPIOA
#define ADC_ADCX_CHY_GPIO_PIN GPIO_PIN_1
#define ADC_ADCX_CHY_GPIO_CLK_ENABLE() \
do { \
RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOA); \
} while (0)
ADC驱动中,ADC的初始化函数,如下所示:
/**
* @brief 初始化ADC
* @note 本函数配置ADC为12位分辨率,时钟频率ADCCLK为21MHz,
* ADC转换时间TCONV=采样周期+12个周期
* 例如使用最大采样周期480个周期,则ADC的转换时间为492个ADC时钟周期,
* 即约为23.43微秒
* @param 无
* @retval 无
*/
void adc_init(void)
{
GPIO_Config_T gpio_init_struct;
ADC_Config_T adc_init_struct;
ADC_CommonConfig_T adc_comm_init_struct;
/* 使能时钟 */
ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADC时钟 */
ADC_ADCX_CHY_GPIO_CLK_ENABLE(); /* 使能ADC输入引脚端口时钟 */
/* 配置ADC输入引脚 */
gpio_init_struct.pin = ADC_ADCX_CHY_GPIO_PIN; /* ADC输入引脚 */
gpio_init_struct.mode = GPIO_MODE_AN; /* 模拟模式 */
gpio_init_struct.pupd = GPIO_PUPD_NOPULL; /* 禁止上拉/下拉 */
GPIO_Config(ADC_ADCX_CHY_GPIO_PORT, &gpio_init_struct);/* 配置ADC输入引脚 */
/* 配置ADC通用控制寄存器 */
adc_comm_init_struct.mode = ADC_MODE_INDEPENDENT;
adc_comm_init_struct.prescaler = ADC_PRESCALER_DIV4;
adc_comm_init_struct.accessMode = ADC_ACCESS_MODE_DISABLED;
adc_comm_init_struct.twoSampling = ADC_TWO_SAMPLING_5CYCLES;
ADC_CommonConfig(&adc_comm_init_struct);
/* 配置ADC */
adc_init_struct.resolution = ADC_RESOLUTION_12BIT;
adc_init_struct.scanConvMode = DISABLE;
adc_init_struct.continuousConvMode = DISABLE;
adc_init_struct.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE;
adc_init_struct.dataAlign = ADC_DATA_ALIGN_RIGHT;
adc_init_struct.nbrOfChannel = 1;
ADC_Config(ADC_ADCX, &adc_init_struct);
ADC_Enable(ADC_ADCX); /* 使能ADC */
}
从上面的代码中可以看出,ADC的初始化函数中,不仅配置了ADC通用控制寄存器和ADC,该配置了ADC1通道1对应的GPIO引脚,同时也配置了该引脚为模拟模式。
ADC驱动中,获取ADC转换结果的函数,如下所示:
/**
* @brief 获取ADC转换后的结果
* @param ch: ADC通道,范围:ADC_CHANNEL_0~ADC_CHANNEL_18
* @retval ADC转换后的结果
*/
uint16_t adc_get_result(uint8_t ch)
{
/* 配置指定ADC规则通道 */
ADC_ConfigRegularChannel(ADC_ADCX, ch, 1, ADC_SAMPLETIME_480CYCLES);
/* 开始转换规则通道 */
ADC_SoftwareStartConv(ADC_ADCX);
/* 等待规则通道转换结束 */
while (ADC_ReadStatusFlag(ADC_ADCX, ADC_FLAG_EOC) == RESET);
/* 返回规则通道的转换结果 */
return ADC_ReadConversionValue(ADC_ADCX);
}
/**
* @brief 获取ADC转换且进行均值滤波后的结果
* @param ch : ADC通道,范围:ADC_CHANNEL_0~ADC_CHANNEL_18
* @param times: 参与均值滤波的原始数据的个数
* @retval ADC转换且进行均值滤波后的结果
*/
uint16_t adc_get_result_average(uint8_t ch, uint8_t times)
{
uint32_t temp_val = 0;
uint8_t t;
for (t=0; t<times; t++)
{
temp_val += adc_get_result(ch);
delay_ms(5);
}
return temp_val / times;
}
以上两个函数都是用于获取ADC转换结果的函数,其中函数adc_get_result()会配置并开启ADC指定通道的规则通道转换,并等待其转换结束后,读取其转换的1次结果;而函数adc_get_result_averagr()则是多次调用啊含糊adc_get_result()获取多次ADC的转换结果,然后进行均值滤波。
35.2.3 实验应用代码
本章实验的应用代码,如下所示:
int main(void)
{
uint16_t adcdata;
uint16_t voltage;
NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_3); /* 设置中断优先级分组为组3 */
sys_apm32_clock_init(336, 8, 2, 7); /* 配置系统时钟 */
delay_init(168); /* 初始化延时功能 */
usart_init(115200); /* 初始化串口 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
adc_init(); /* 初始化ADC */
lcd_show_string(30, 50, 200, 16, 16, "APM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "ADC 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);
while (1)
{
/* 获取ADC通道5转换且进行均值滤波后的结果 */
adcdata = adc_get_result_average(ADC_ADCX_CHY, 10);
/* 显示原始值 */
lcd_show_xnum(134, 110, adcdata, 5, 16, 0, BLUE);
/* 计算实际电压值(扩大1000倍) */
voltage = (adcdata * 3300) / 4095;
/* 显示电压值的整数部分 */
lcd_show_xnum(134, 130, voltage / 1000, 1, 16, 0, BLUE);
/* 显示电压值的小数部分(保留三位小数) */
lcd_show_xnum(150, 130, voltage % 1000, 3, 16, 0x80, BLUE);
LED0_TOGGLE();
delay_ms(100);
}
}
从上面的代码中可以看出,在进行完包括ADC的所有初始化工作后,便不断地获取ADC1通道1进行5次转换后经过均值滤波后的结果,并将该原始值显示在LCD上,同时还通过该电压的原始值计算出了电压的模拟量,并在LCD上进行显示。
35.3 下载验证
在完成编译和烧录操作后,可以看到LCD上实时刷新显示着ADC1通道1(PA1引脚)采集到电压的数字量和模拟量,此时可以通过杜邦线给PA1引脚接入不同的电压值(注意共地,且输入电压不能超过3.3V,否则可能损坏开发板),可以看到LCD上显示的电压数字量和模拟量也随之改变。