开始
申请开发板的时候我的开发目标是基于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); }
如图所示,烧录运行后,可以从串口控制台得到打印的数据:
测量电压
如图所示,将可调稳压电源的输出线与开发板的GND/PA1相连,并且把万用表的表笔一同并联到线路上,以万用表的读数为基准,测量GD32F310的ADC转换精度:
我这里使用ADC对电源的输出电压进行100次采样后求取平局值,得到的结果如下表所示:
从表中可以看出,延长ADC采样时间对ADC精度是有一定帮助的,在低电压时,ADC的读数似乎非常差,原始数据会在0-6之间跳动,几乎无法准确的读取稳定的数值,直到把输入电压提升到50mV才有所稳定,个人认为这个情况是电源端的问题,可能可调电源在低压输出时不够稳定,手里暂时没有找到其他可以构成分压的电路降低电压去测试,所以0.05V以下的电压测量结果没有太大的参考价值,电压上升到1V以上后ADC的测量就比较稳定了,虽然进行100次累加求平均后读数依然有跳动的情况,但精度已经基本满足大部分工程的要求
-
万用表
+关注
关注
88文章
2074浏览量
127691 -
adc
+关注
关注
98文章
6498浏览量
544660
发布评论请先 登录
相关推荐
评论