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

2023-04-11  

开始


申请开发板的时候我的开发目标是基于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

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。