搜索
bottom↓
回复: 0

《ATK-DFPGL22G之FPGA开发指南_V1.0》第三十三章 单通道ADC采集实验

[复制链接]

出0入234汤圆

发表于 2023-7-19 17:52:58 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2023-7-19 17:52 编辑

1)实验平台:正点原子 DFZU2EG_4EV MPSoC开发板
2)购买链接:https://item.taobao.com/item.htm?&id=692368045899
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-340252-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子FPGA交流群:994244016 lQLPJxaFi2zaB4UWWrDAMgIsFEW2pwLb3abnwDMA_90_22.png
lQDPJxaFi2nfFizMjM0CbLCPlxn_FVheIQLb3aGrwFQA_620_140.jpg

lQLPJxaFi2nfFhLMkM0BXrDNvOUyeU_FPgLb3aGvQNIA_350_144.png

第三十三章 单通道ADC采集实验


本章介绍使用APM32E103模数转换器(ADC)进行带通道的电压采集。通过本章的学习,读者将学习到单通道ADC的使用。
本章分为如下几个小节:
33.1 硬件设计
33.2 程序设计
33.3 下载验证


33.1 硬件设计
33.1.1 例程功能

1. ADC1采集通道1(PA1)上的电压,并在LCD上显示ADC转换后电压的数字量和换算后的模拟量
2. LED0闪烁,指示程序正在运行
33.1.2 硬件资源
1. LED
        LED0 - PB5
2. 正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
3. ADC1
        通道1 - PA1
33.1.3 原理图
本章实验使用的ADC1为APM32E103的片上资源,因此没有对应的连接原理图。
33.2 程序设计
33.2.1 Geehy标准库的ADC驱动

本章实验将使用ADC1的通道1(PA1引脚)采集外部输入电压的模拟量,并将其转换为数字量,其具体的步骤如下:
①:配置ADC
②:使能ADC
③:配置ADC规则通道
④:启动转换规则通道
⑤:等待规则通道转换结束
⑥:读取规则通道的转换结果
在Geehy标准库中对应的驱动函数如下:
①:配置ADC
该函数用于配置ADC的各项参数,其函数原型如下所示:
void ADC_Config(ADC_T* adc, ADC_Config_T* adcConfig);
该函数的形参描述,如下表所示:
lQLPJwaD3JSP7uLMm80C47BleuosnrroBgStACQ9wOkB_739_155.png
表33.2.1.1 函数ADC_Config()形参描述

该函数的返回值描述,如下表所示:
lQLPJv-NQIh_emI2zQLnsBWU4UI5577YBK0AwRcAEgA_743_54.png
表33.2.1.2函数ADC_Config()返回值描述

该函数使用ADC_Config_T类型的结构体变量传入ADC外设的配置参数,该结构体的定义如下所示:
typedef enum
{
/* 独立模式 */
ADC_MODE_INDEPENDENT              = ((uint32_t)0x00000000),
/* 组合的常规同时和注入同时模式 */
          ADC_MODE_REG_INJEC_SIMULT                   = ((uint32_t)0x00010000),
/* 组合的常规同时和交替触发模式 */
ADC_MODE_REG_SIMULT_ALTER_TRIG    = ((uint32_t)0x00020000),
/* 组合注入同时和快速交织模式 */
ADC_MODE_INJEC_SIMULT_FAST_TNTERL = ((uint32_t)0x00030000),
/* 组合注入同步和慢速交织模式 */
ADC_MODE_INJEC_SIMULT_SLOW_INTERL = ((uint32_t)0x00040000),
/* 注入同时模式 */
ADC_MODE_INJEC_SIMULT             = ((uint32_t)0x00050000),
/* 常规同时模式 */
ADC_MODE_REG_SIMULT               = ((uint32_t)0x00060000),
/* 快速交错模式 */
ADC_MODE_FAST_INTERL              = ((uint32_t)0x00070000),
/* 慢速交错模式 */
ADC_MODE_SLOW_INTERL              = ((uint32_t)0x00080000),
/* 备用触发模式 */
    ADC_MODE_ALTER_TRIG               = ((uint32_t)0x00090000)
} ADC_MODE_T;

typedef enum
{
    ADC_EXT_TRIG_CONV_TMR1_CC1  = ((uint32_t)0x00000000), /* 定时器1的CC1事件 */
    ADC_EXT_TRIG_CONV_TMR1_CC2  = ((uint32_t)0x00020000), /* 定时器1的CC2事件 */
ADC_EXT_TRIG_CONV_TMR2_CC2  = ((uint32_t)0x00060000), /* 定时器2的CC2事件 */
/* 定时器3的TRGO事件 */
    ADC_EXT_TRIG_CONV_TMR3_TRGO = ((uint32_t)0x00080000),
ADC_EXT_TRIG_CONV_TMR4_CC4  = ((uint32_t)0x000A0000), /* 定时器4的CC4事件 */
/* EINT线11的TRGO事件 */
    ADC_EXT_TRIG_CONV_EINT11_T8_TRGO  = ((uint32_t)0x000C0000),
    ADC_EXT_TRIG_CONV_TMR1_CC3  = ((uint32_t)0x00040000), /* 定时器1的CC3事件 */
    ADC_EXT_TRIG_CONV_None      = ((uint32_t)0x000E0000), /* 无事件 */

    ADC_EXT_TRIG_CONV_TMR3_CC1  = ((uint32_t)0x00000000), /* 定时器3的CC1事件 */
ADC_EXT_TRIG_CONV_TMR2_CC3  = ((uint32_t)0x00020000), /* 定时器2的CC3事件 */
ADC_EXT_TRIG_CONV_TMR8_CC1  = ((uint32_t)0x00060000), /* 定时器8的CC1事件 */
/* 定时器8的TRGO事件 */
    ADC_EXT_TRIG_CONV_TMR8_TRGO = ((uint32_t)0x00080000),
    ADC_EXT_TRIG_CONV_TMR5_CC1  = ((uint32_t)0x000A0000), /* 定时器5的CC1事件 */
    ADC_EXT_TRIG_CONV_TMR5_CC3  = ((uint32_t)0x000C0000)  /* 定时器5的CC3事件 */
} ADC_EXT_TRIG_CONV_T;

typedef enum
{
    ADC_DATA_ALIGN_RIGHT = 0x00000000,                    /* 右对齐 */
    ADC_DATA_ALIGN_LEFT  = 0x00000800                     /* 左对齐 */
} ADC_DATA_ALIGN_T;


typedef struct
{
    ADC_MODE_T          mode;
    uint8_t             scanConvMode;                /* 扫描模式 */
    uint8_t             continuosConvMode;        /* 连续转换模式 */
    ADC_EXT_TRIG_CONV_T externalTrigConv;        /* 启动规则组转换的外部事件 */
    ADC_DATA_ALIGN_T    dataAlign;                        /* 数据对齐方式 */
    uint8_t             nbrOfChannel;                /* 规则通道序列长度 */
} ADC_Config_T;
该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_adc.h"

void example_fun(void)
{
    ADC_Config_T adc_init_struct;
   
    /* 配置ADC1 */
    adc_init_struct.mode = ADC_MODE_INDEPENDENT;
    adc_init_struct.scanConvMode = DISABLE;
    adc_init_struct.continuosConvMode = DISABLE;
    adc_init_struct.externalTrigConv = ADC_EXT_TRIG_CONV_None;
    adc_init_struct.dataAlign = ADC_DATA_ALIGN_RIGHT;
    adc_init_struct.nbrOfChannel = 1;
    ADC_Config(ADC_ADCX, &adc_init_struct);
}
②:使能ADC
该函数用于使能ADC,其函数原型如下所示:
void ADC_Enable(ADC_T* adc);
该函数的形参描述,如下表所示:
lQLPJwo2pUYLfaJPzQLmsFq1VAOCK638BK0AaPbA6QA_742_79.png
表33.2.1.3 函数ADC_Enable()形参描述

该函数的返回值描述如下表所示
lQLPJwm2aDHNAGI1zQLmsC7a8DTv0mSMBK0AVjYA6QA_742_53.png
表33.2.1.4 函数ADC_Enable()返回值描述

该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_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);
该函数的形参描述,如下表所示:
lQLPJx2_9QNp0iLNARzNAuew-iJKqBDRgz0ErQCGh8ASAA_743_284.png
表33.2.1.5 函数ADC_ConfigRegularChannel()形参描述

该函数的返回值描述,如下表所示:
lQLPJxi1YluhMCI0zQLnsInLXp8OixCVBK0Am5eABgA_743_52.png
表33.2.1.6 函数ADC_ConfigRegularChannel()返回值描述

该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_adc.h"

void example_fun(void)
{
    /* 使能ADC1通道1的规则通道 */
    ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_1, 1, ADC_SAMPLETIME_480CYCLES);
}
④:启动转换规则通道
该函数用于启动转换规则通道,其函数原型如下所示:
void ADC_EnableSoftwareStartConv(ADC_T* adc);
该函数的形参描述,如下表所示:
lQLPJwJcqWgi6qJNzQLlsJ76S3tULgTwBK0AsDDA6QA_741_77.png
表33.2.1.7 函数ADC_EnableSoftwareStartConv ()形参描述

该函数的返回值描述,如下表所示:
lQLPJwtYxi8J96I1zQLlsJo7WcoV_DCrBK0AeQ6A6QE_741_53.png
表33.2.1.8 函数ADC_EnableSoftwareStartConv ()返回值描述

该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_adc.h"

void example_fun(void)
{
    ADC_EnableSoftwareStartConv(ADC1); /* 使能ADC1转换规则通道 */
}
⑤:读取ADC状态标志
该函数用于读取ADC的状态标志,其函数原型如下所示:
uint8_t ADC_ReadStatusFlag(ADC_T* adc, ADC_FLAG_T flag);
该函数的形参描述,如下表所示:
lQLPJwrsi2Gx9WLMms0C5rCbX4rfpVSwSQStANNXQAYA_742_154.png
表33.2.1.9 函数ADC_ReadStatusFlag()形参描述

该函数的返回值描述,如下表所示:
lQLPJwZOM4DCIGJTzQLmsGANX02A3FdOBK0A5LyA6QA_742_83.png
表33.2.1.10 函数ADC_ReadStatusFlag()返回值描述

该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_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);
该函数的形参描述,如下表所示:
lQLPJx_88E6zg-I0zQLmsHtx49obnXIQBK0BGDyAEgA_742_52.png
表33.2.1.11 函数ADC_ReadConversionValue()形参描述

该函数的返回值描述,如下表所示:
lQLPJw7L5LVu8xI1zQLlsIf62wnYvM-KBK0BKFeABgA_741_53.png
表33.2.1.12 函数ADC_ReadConversionValue()返回值描述

该函数的使用示例,如下所示:
#include "apm32e10x.h"
#include "apm32e10x_adc.h"

void example_fun(void)
{
    uint16_t data;
   
    /* 读取规则通道转换结果 */
    data = ADC_ReadConversionValue(ADC1);
   
    /* Do something. */
}
33.2.2 ADC驱动
本章实验的ADC驱动主要负责向应用层提供ADC的初始化和获取ADC转换结果的函数。本章实验中,ADC的驱动代码包括adc.c和adc.h两个文件。
ADC驱动中,对ADC、GPIO的相关宏定义,如下所示:
/* 单通道ADC采集定义 */
#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_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA); }while(0)

#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)
ADC驱动中,ADC的初始化函数,如下所示:
/**
* @brief   初始化ADC
* @note    公式:TCONV=采样时间+12.5个周期
*          采样时间由 SMPCYCCFGx[2:0]位控制,最小采样周期为1.5个,当
*          ADCCLK=14MHz,采样时间为1.5周期:TCONV=1.5l,周期+12.5,周期=14,周期=1us。
* @param   无
* @retval  无
*/
void adc_init(void)
{
    ADC_Config_T adc_init_struct;
    GPIO_Config_T gpio_init_struct;
   
    /* 使能时钟 */
ADC_ADCX_CHY_CLK_ENABLE();                          /* 使能ADC时钟 */
/* 使能ADC输入引脚端口时钟 */
ADC_ADCX_CHY_GPIO_CLK_ENABLE();                  
/* 设置ADC分频因子6,120MHz/6=20MHz,即6分频得到的ADC输入时钟频率为20MHz */
    RCM_ConfigADCCLK(RCM_PCLK2_DIV_6);                        
   
    /* 配置ADC引脚 */
    gpio_init_struct.pin = ADC_ADCX_CHY_GPIO_PIN;       /* ADC输入引脚 */
gpio_init_struct.mode = GPIO_MODE_ANALOG;           /* 模拟输入 */
/* 配置ADC输入引脚 */
    GPIO_Config(ADC_ADCX_CHY_GPIO_PORT, &gpio_init_struct);
   
    ADC_Reset(ADC_ADCX);                                /* 复位ADC1 */
   
    /* 配置ADC */
    adc_init_struct.mode = ADC_MODE_INDEPENDENT;        /* ADC工作模式:独立模式 */
    adc_init_struct.scanConvMode = DISABLE;             /* 禁止扫描模式 */
adc_init_struct.continuosConvMode = DISABLE;        /* 单次转换模式 */
/* 禁止触发检测 */
    adc_init_struct.externalTrigConv = ADC_EXT_TRIG_CONV_None;
    adc_init_struct.dataAlign = ADC_DATA_ALIGN_RIGHT;   /* 配置数据对齐方式 */
    adc_init_struct.nbrOfChannel = 1;                   /* 规则通道序列长度 */
    ADC_Config(ADC_ADCX, &adc_init_struct);             /* 配置ADC */
   
    ADC_Enable(ADC_ADCX);                               /* 使能ADC */
    ADC_ResetCalibration(ADC_ADCX);                     /* 使能复位校准 */
   
    while(ADC_ReadResetCalibrationStatus(ADC_ADCX))     /* 等待复位校准结束 */
    {
        ADC_StartCalibration(ADC_ADCX);                 /* 开启AD校准 */
    }
   
    while(ADC_ReadCalibrationStartFlag(ADC_ADCX));      /* 等待校准结束 */
}
从上面的代码中可以看出,ADC的初始化函数中,不仅配置了ADC通用控制寄存器和ADC,该配置了ADC1通道1对应的GPIO引脚,同时也配置了该引脚为模拟模式。
ADC驱动中,获取ADC转换结果的函数,如下所示:
/**
* @brief       获得ADC转换后的结果
* @param       ch: ADC通道,范围:ADC_CHANNEL_0~ADC_CHANNEL_V_REFINT
* @retval      无
*/
uint16_t adc_get_result(uint8_t ch)
{
/* 配置指定ADC规则通道 */
    ADC_ConfigRegularChannel(ADC_ADCX, ch, 1, ADC_SAMPLETIME_239CYCLES5);
    ADC_EnableSoftwareStartConv(ADC_ADCX);             /* 开始转换规则通道 */
    while(!ADC_ReadStatusFlag(ADC_ADCX, ADC_FLAG_EOC));/* 等待规则通道转换结束 */
    return ADC_ReadConversionValue(ADC_ADCX);          /* 返回规则通道的转换结果 */
}

/**
* @brief       获取通道ch的转换值,取times次,然后平均
* @param       ch   : ADC通道,范围:ADC_CHANNEL_0~ADC_CHANNEL_V_REFINT
* @param       times: 获取次数
* @retval      通道ch的times次转换结果平均值
*/
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); /* 获取times次数据 */
        delay_ms(5);
    }
   
    return temp_val / times;            /* 返回平均值 */
}
以上两个函数都是用于获取ADC转换结果的函数,其中函数adc_get_result()会配置并开启ADC指定通道的规则通道转换,并等待其转换结束后,读取其转换的1次结果;而函数adc_get_result_averagr()则是多次调用啊含糊adc_get_result()获取多次ADC的转换结果,然后进行均值滤波。
33.2.3 实验应用代码
本章实验的应用代码,如下所示:
int main(void)
{
    uint16_t adcdata;
    uint16_t voltage;
   
    NVIC_ConfigPriorityGroup(NVIC_PRIORITY_GROUP_4);  /* 设置中断优先级分组为组4 */
    sys_apm32_clock_init(15);                         /* 配置系统时钟 */
    delay_init(120);                                  /* 初始化延时功能 */
    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通道1转换且进行均值滤波后的结果 */
        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上进行显示。
33.3 下载验证
在完成编译和烧录操作后,可以看到LCD上实时刷新显示着ADC1通道1(PA1引脚)采集到电压的数字量和模拟量,此时可以通过杜邦线给PA1引脚接入不同的电压值(注意共地,且输入电压不能超过3.3V,否则可能损坏开发板),可以看到LCD上显示的电压数字量和模拟量也随之改变。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-27 18:17

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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