0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何利用万用表测试ADC的精度

科技观察员 来源:兆易创新GD32 MCU 作者:钟梓铭 2022-04-15 15:00 次阅读

开始

申请开发板的时候我的开发目标是基于GD32F310设计一个全双工串口转单线半双工串口的串行舵机控制器,但是这个项目和我本职工作的一个项目比较类似,不方便开源通信部分的代码,所以临时改变文章的主题为测试ADC的精度,项目的所有代码已在github开源,希望文章的内容对朋友们的工作和学习有所帮助;

移植固件库

到GD32的官网下载文档三份:GD32F310数据手册/GD32F3x0用户手册/GD32F3x0固件库使用手册,最新版本固件库压缩包一份;固件库经过我的整理,提取了项目开发的基础文件并归类到三个文件夹中,作为基础空白的工程项目:

bsp:板级支持相关的代码文件,包含了各个外设模块的初始化函数/基本的驱动函数,需要自己实现;

user:实现用户的业务逻辑代码,同时也作为系统内核/固件库和用户代码的接口,基础的接口模板由固件库压缩包提供,删减后可以在其基础上进行开发,main函数就在该文件夹的文件中;

device:和芯片内核/外设相关的文件,由固件库压缩包提供,内核相关的文件需要删减,仅保留适合本项目开发环境的文件;

实现系统串口

系统串口使用的是USART1在PA2/PA3,由于GD32F310G-START并未提供串口转USB电路,所以需要使用杜邦线外接一个串口转USB的模块与电脑串口软件进行通信

进入bsp文件夹,新建文件bsp_uart.c/.h,代码内容如下:

bsp_uart.h

#ifndef _BSP_UART_H_
#define _BSP_UART_H_

#include "main.h"

#define SYSTEM_UART_PORT            USART1
#define SYSTEM_UART_PERCLK          RCU_USART1
#define SYSTEM_UART_GPIO_PORT       GPIOA
#define SYSTEM_UART_GPIO_PERCLK     RCU_GPIOA
#define SYSTEM_UART_GPIO_TX_PIN     GPIO_PIN_2
#define SYSTEM_UART_GPIO_RX_PIN     GPIO_PIN_3

void System_Uart_Init(void);

#endif

bsp_uart.c

#include "bsp_uart.h"

//系统串口打印初始化
void System_Uart_Init(void)
{
    //初始化串口IO
    rcu_periph_clock_enable(SYSTEM_UART_GPIO_PERCLK);
    gpio_af_set(SYSTEM_UART_GPIO_PORT, GPIO_AF_1, SYSTEM_UART_GPIO_TX_PIN);
    gpio_af_set(SYSTEM_UART_GPIO_PORT, GPIO_AF_1, SYSTEM_UART_GPIO_RX_PIN);
    gpio_mode_set(SYSTEM_UART_GPIO_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, SYSTEM_UART_GPIO_TX_PIN);
    gpio_output_options_set(SYSTEM_UART_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, SYSTEM_UART_GPIO_TX_PIN);
    gpio_mode_set(SYSTEM_UART_GPIO_PORT, GPIO_MODE_AF, GPIO_PUPD_PULLUP, SYSTEM_UART_GPIO_RX_PIN);
    gpio_output_options_set(SYSTEM_UART_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_10MHZ, SYSTEM_UART_GPIO_RX_PIN);
    //初始化串口外设
    rcu_periph_clock_enable(SYSTEM_UART_PERCLK);
    usart_deinit(SYSTEM_UART_PORT);
    usart_word_length_set(SYSTEM_UART_PORT, USART_WL_8BIT);
    usart_stop_bit_set(SYSTEM_UART_PORT, USART_STB_1BIT);
    usart_parity_config(SYSTEM_UART_PORT, USART_PM_NONE);
    usart_baudrate_set(SYSTEM_UART_PORT, 115200U);
    usart_receive_config(SYSTEM_UART_PORT, USART_RECEIVE_ENABLE);
    usart_transmit_config(SYSTEM_UART_PORT, USART_TRANSMIT_ENABLE);
    usart_enable(SYSTEM_UART_PORT);
}

实现 ADC

ADC的模拟输入端口需要注意,PA0作为UserKey已经通过10k电阻下拉到地,PA2/PA3已作为串口TX/RX使用,它们都不太适合作为本应浮空的ADC通道,故选择PA1作为ADC的输入通道;在bsp文件夹内新建文件bsp_adc.c/.h文件,代码如下:

bsp_adc.h

#ifndef _BSP_ADC_H_
#define _BSP_ADC_H_

#include "main.h"

#define TEST_ADC_GPIO_PERCLK                RCU_GPIOA
#define TEST_ADC_GPIO_PORT                  GPIOA
#define TEST_ADC_GPIO_PIN                   GPIO_PIN_1
#define TEST_ADC_CHANNEL                    ADC_CHANNEL_1
#define TEST_ADC_PERCLK                     RCU_ADC

#define TEST_ADC_SAMPLES_REPEATED_NUMBER    100

void Test_Adc_Init(void);
uint16_t Test_Adc_Init_Sample(void);
void Test_Adc_Value_Update_Thread(void);
void Test_Adc_Value_Update_Thread_Init(void);
uint16_t Test_Adc_Get_Raw(void);
float Test_Adc_Get_Voltage(void);

#endif

bsp_adc.c

#include "bsp_adc.h"

uint16_t adc_test_raw_data = 0 ;    //adc测试输出原始结果(平均值)
float adc_test_voltage = 0.0 ;      //adc测试输出电压值(平均值)

void Test_Adc_Init(void)
{
    //设置模拟输入IO
    rcu_periph_clock_enable(TEST_ADC_GPIO_PERCLK);
    gpio_mode_set(TEST_ADC_GPIO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, TEST_ADC_GPIO_PIN);     //设置测试通道的GPIO为模拟模式
    //设置ADC外设
    rcu_periph_clock_enable(TEST_ADC_PERCLK);
    rcu_adc_clock_config(RCU_ADCCK_APB2_DIV6);                                                  //ADC时钟源设置
    adc_data_alignment_config(ADC_DATAALIGN_RIGHT);                                             //数据对齐模式:右对齐
    adc_channel_length_config(ADC_REGULAR_CHANNEL, 1U);                                         //规则转换通道长度:1
    adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE);          //触发源设置:软件触发
    adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE);                                   //触发设置:开启规则转换触发
    adc_enable();                                                                               //ADC启动
    rt_thread_mdelay(1);                                                                        //延时稳定
    adc_calibration_enable();                                                                   //ADC使用内部校准
}
INIT_BOARD_EXPORT(Test_Adc_Init);

//开始一次AD转换
uint16_t Test_Adc_Sample(void)
{
    adc_regular_channel_config(0U, TEST_ADC_CHANNEL, ADC_SAMPLETIME_239POINT5);                 //设置测试通道至规则转换队列头,设置采样时间
    adc_software_trigger_enable(ADC_REGULAR_CHANNEL);                                           //软件触发使能,ADC开始转换
    while(!adc_flag_get(ADC_FLAG_EOC));                                                         //等待转换结束
    adc_flag_clear(ADC_FLAG_EOC);
    return (adc_regular_data_read());                                                           //返回转换结果
}

//获取原始结果
uint16_t Test_Adc_Get_Raw(void)
{
    return adc_test_raw_data ;
}

//获取转换电压值
float Test_Adc_Get_Voltage(void)
{
    return adc_test_voltage ;
}

移植操作系统

GD32F310只有8k的RAM个人认为是不适合移植操作系统的,内存比较小,没办法写很复杂的线程代码,其实这个简单的测试项目也用不上多线程调度,我就是纯属吃饱了撑着了,把F303移植好的RT-Thread直接拖过来用,关于RT-Thread移植的教程网络上有非常多,所以我就写一些大致流程细节我就不方便展开讲了;RT-Thread是一款非常优秀好用的国产RTOS,国产硬件配国产软件实在般配;

新建rtos文件夹,整理rt-thread nano源码包提供的文件,复制到rtos文件夹中;

main.h内添加 #include "rtthread.h"

找到gd32f3x0_it.c,注释掉以下几个函数,使其失效

// void HardFault_Handler(void)
// {
//     /* if Hard Fault exception occurs, go to infinite loop */
//     while(1) {
//     }
// }

// void SVC_Handler(void)
// {
// }

// void PendSV_Handler(void)
// {
// }

// void SysTick_Handler(void)
// {
//     delay_decrement();
// }

找到rtconfig.h,删掉MDK管理相关的宏,并添加如下代码

#include "main.h"            //使得RT-Thread能够找到其他被项目include的文件
#include "finsh_config.h"    //使用控制台msh功能需要引用此文件
#define RT_USING_FINSH        //使用控制台
#define RT_USING_HEAP        //取消这个宏的注释使其有效

找到finsh_port.c,修改和添加我们的串口接口代码,供控制台使用

RT_WEAK char rt_hw_console_getchar(void)
{
    /* Note: the initial value of ch must < 0 */
    int ch = -1;
    if(usart_flag_get(SYSTEM_UART_PORT, USART_FLAG_RBNE))
        ch = usart_data_receive(SYSTEM_UART_PORT);
    return ch;
}

void rt_hw_console_output(const char *str)
{
    rt_size_t i = 0, size = 0;
    char a = '\r';
    size = rt_strlen(str);
    for (i = 0; i < size; i++)
    {
        if (*(str + i) == '\n')
        {
            usart_data_transmit(SYSTEM_UART_PORT, a);
            while(RESET == usart_flag_get(SYSTEM_UART_PORT, USART_FLAG_TBE));
        }
        usart_data_transmit(SYSTEM_UART_PORT, *(str + i));
        while(RESET == usart_flag_get(SYSTEM_UART_PORT, USART_FLAG_TBE));
    }
}

如果我没有遗漏什么细节的话,此时编译代码并下载运行程序,能够在串口软件里收到RT-Thread的系统信息打印的内容:

 \ | /
- RT -     Thread Operating System
 / | \     3.1.5 build Apr 10 2022
 2006 - 2020 Copyright by rt-thread team
msh >

拥有了操作系统,我们就可以利用RT-Thread的自动初始化功能,运行我们的串口/ADC外设初始化代码:

INIT_BOARD_EXPORT(Test_Adc_Init);        //ADC初始化函数加入RTT板级自动初始化队列
INIT_BOARD_EXPORT(System_Uart_Init);    //系统串口初始化函数加入RTT板级自动初始化队列

添加ADC测试代码

在bsp_adc.c文件中,实现一个RTOS线程代码,其功能是循环采集ADC的电压数据并且保存到一个变量中;

//ADC自动转换线程入口
void Test_Adc_Value_Update_Thread(void)
{
    //转换次数记录,转换结果累加
    uint32_t count = 0, data_count = 0;
    while (1)
    {
        if(count < TEST_ADC_SAMPLES_REPEATED_NUMBER)//转换次数未满
        {
            data_count += Test_Adc_Sample();//进行一次转换并累加结果原始数据
            count ++;//转换次数 +1
        }
        else//转换次数已满
        {
            adc_test_raw_data = data_count/TEST_ADC_SAMPLES_REPEATED_NUMBER ;//累加原始数据求平均
            adc_test_voltage = 3.3 / 4096 * adc_test_raw_data ;//平均的累加数据转换为电压值
            data_count = 0 ;//重新开始下一轮转换
            count = 0 ;
            rt_thread_mdelay(100);//释放线程
        }
    }
}

//上电后开启ADC自动转换线程
void Test_Adc_Value_Update_Thread_Init(void)
{
    rt_thread_t i = rt_thread_create("ADC", Test_Adc_Value_Update_Thread, RT_NULL, 512, 4, 10);
    rt_thread_startup(i);
}
//开机后自动生成并启动ADC转换线程
INIT_APP_EXPORT(Test_Adc_Value_Update_Thread_Init);

运行后,adc_test_raw_data/adc_test_voltage这两个变量每隔100ms更新一次测量的ADC数值;

到main.c添加如下代码,使系统控制台每2s打印输出一次ADC的测量结果:

#include 

//打印ADC数据线程
void App_Print_Adc_Data_Thread(void)
{
    uint8_t str[32];
    while(1)
    {
        rt_thread_mdelay(2000);
        sprintf(str, "RAW:%d VOL:%f\r\n", Test_Adc_Get_Raw(), Test_Adc_Get_Voltage());
        rt_kprintf(str);
    }
}

int main(void)
{
    rt_thread_t i = rt_thread_create("test", App_Print_Adc_Data_Thread, RT_NULL, 1024, 4, 10);
    rt_thread_startup(i);
}

如图所示,烧录运行后,可以从串口控制台得到打印的数据:

poYBAGJZGAmAJ5fvAAXvo2C3wko080.png

测量电压

如图所示,将可调稳压电源的输出线与开发板的GND/PA1相连,并且把万用表的表笔一同并联到线路上,以万用表的读数为基准,测量GD32F310的ADC转换精度:

pYYBAGJZGAWAMND0AAhxmPKFMCA688.png

我这里使用ADC对电源的输出电压进行100次采样后求取平局值,得到的结果如下表所示:

poYBAGJZGAGAWxuGAAawqOeJ2Ko718.png

从表中可以看出,延长ADC采样时间对ADC精度是有一定帮助的,在低电压时,ADC的读数似乎非常差,原始数据会在0-6之间跳动,几乎无法准确的读取稳定的数值,直到把输入电压提升到50mV才有所稳定,个人认为这个情况是电源端的问题,可能可调电源在低压输出时不够稳定,手里暂时没有找到其他可以构成分压的电路降低电压去测试,所以0.05V以下的电压测量结果没有太大的参考价值,电压上升到1V以上后ADC的测量就比较稳定了,虽然进行100次累加求平均后读数依然有跳动的情况,但精度已经基本满足大部分工程的要求

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 万用表
    +关注

    关注

    87

    文章

    2047

    浏览量

    127370
  • adc
    adc
    +关注

    关注

    98

    文章

    6438

    浏览量

    544113
收藏 人收藏

    评论

    相关推荐

    如何选择数字万用表

    众所周知,万用表是电子测试领域最基本的工具,也是一种使用广泛的仪器,由于其价格低廉、操作简单、功能齐全、使用广泛等特点,专业电子工程师使用上往往忽略以下问题:1、由于万用表精度、分辨
    发表于 09-12 09:22

    数字万用表使用教程下载

    数字万用表使用教程有数字万用表的测量技巧,数字万用表综述,数字万用表的扩展应用,使用数字万用表的注意事项,
    发表于 09-06 22:09 0次下载
    数字<b class='flag-5'>万用表</b>使用教程下载

    普通万用表与数字万用表的优缺点

    普通万用表与数字万用表的优缺点 指针式与数字式万用表各有优缺点.   指针万用表是一
    发表于 11-23 11:03 4294次阅读

    万用表,万用表是什么意思

    万用表,万用表是什么意思 万用表具有用途多,量程广,使用方便等优点,是电子测量中最常用的工具。它可以用来测量电阻,交直流电压和直流电
    发表于 03-03 10:59 6972次阅读

    万用表使用技巧

    万用表使用技巧,轻松学会如何使用万用表。手把手教你使用万用表
    发表于 02-17 11:11 0次下载

    数字万用表测电流原理_数字万用表怎么测电流_数字万用表测电流图解

    使用数字万用表测电流有两种方式。第一种方法是直接使用数字式万用表进行电流测量,需要将万用表与被测电路串联,这就意味着要将电路断开,并用数字式万用表
    发表于 11-30 15:18 6.1w次阅读

    万用表测量电压原理_万用表怎么测电压_万用表测量电压的方法

    万用表是我们常用的测试工具,主要用来测试电压、电阻、电流等参数,在电子产品的测试、维修及产品制作上起到很大的作用。万用表的主要组成是由电流
    发表于 11-30 17:56 8.9w次阅读
    <b class='flag-5'>万用表</b>测量电压原理_<b class='flag-5'>万用表</b>怎么测电压_<b class='flag-5'>万用表</b>测量电压的方法

    如何使用万用表测试电压详细方法和原理说明

    万用表是我们常用的测试工具,主要用来测试电压、电阻、电流等参数,在电子产品的测试、维修及产品制作上起到很大的作用。万用表的主要组成是由电流
    发表于 02-11 08:00 19次下载
    如何使用<b class='flag-5'>万用表</b><b class='flag-5'>测试</b>电压详细方法和原理说明

    数字万用表能够取代模拟万用表

    毋庸置疑,万用表可以说是电工最常用的电子测量仪器,但是选择数字万用表还是模拟(指针式)万用表这是一个问题~有人说数字万用表已经渐渐取代模拟万用表
    发表于 08-07 17:38 6607次阅读

    模拟万用表与数字万用表有什么区别

    万用表是一种用于测量电压、电流、电阻等电气参数的电气测量仪器。万用表并非都一样,不同的万用表尺寸、精度、准确度和测量参数都各不相同。万用表
    的头像 发表于 10-15 11:00 1.4w次阅读

    万用表使用与原理 万用表ADC的原理

      数字万用表是高精度仪器。数字万用表的双积分ADC是让万用表达到高精度的关键器件。   
    发表于 01-15 15:04 1071次阅读

    万用表小电阻测试精度的差异

    万用表小电阻测试精度的差异  万用表是一种常用的电子测试仪器,它可以测量电压、电流和电阻等电学量。在电子电路设计、故障排除和科学实验等方面,
    的头像 发表于 12-11 16:44 886次阅读

    万用表的使用方法步骤 万用表的hFE功能怎么

    万用表是电子工程中常用的测量仪器,广泛应用于电路测试、电阻测量、电流测量等方面。下面将详细介绍万用表的使用方法步骤以及万用表的hFE功能使用方法。
    的头像 发表于 01-31 13:37 4216次阅读

    胜利万用表的各个型号

    胜利万用表是一款非常受欢迎的电子测量工具,广泛应用于电子、电气、通信、自动化等领域。在选择胜利万用表时,我们需要考虑多个因素,如测量范围、精度、功能、价格等。 胜利万用表的分类 胜利
    的头像 发表于 07-14 14:27 2385次阅读

    数字万用表与模拟万用表的区别

    的区别在于它们的显示方式。数字万用表使用数字显示屏来显示测量结果,而模拟万用表则使用指针和刻度盘来显示。 数字万用表: 直观性 :数字万用表的读数直观,用户可以直接从显示屏上读取精确的
    的头像 发表于 11-01 10:20 276次阅读