開(kāi)始
申請(qǐng)開(kāi)發(fā)板的時(shí)候我的開(kāi)發(fā)目標(biāo)是基于GD32F310設(shè)計(jì)一個(gè)全雙工串口轉(zhuǎn)單線半雙工串口的串行舵機(jī)控制器,但是這個(gè)項(xiàng)目和我本職工作的一個(gè)項(xiàng)目比較類似,不方便開(kāi)源通信部分的代碼,所以臨時(shí)改變文章的主題為測(cè)試ADC的精度,項(xiàng)目的所有代碼已在github開(kāi)源,希望文章的內(nèi)容對(duì)朋友們的工作和學(xué)習(xí)有所幫助;
移植固件庫(kù)
到GD32的官網(wǎng)下載文檔三份:GD32F310數(shù)據(jù)手冊(cè)/GD32F3x0用戶手冊(cè)/GD32F3x0固件庫(kù)使用手冊(cè),最新版本固件庫(kù)壓縮包一份;固件庫(kù)經(jīng)過(guò)我的整理,提取了項(xiàng)目開(kāi)發(fā)的基礎(chǔ)文件并歸類到三個(gè)文件夾中,作為基礎(chǔ)空白的工程項(xiàng)目:
bsp:板級(jí)支持相關(guān)的代碼文件,包含了各個(gè)外設(shè)模塊的初始化函數(shù)/基本的驅(qū)動(dòng)函數(shù),需要自己實(shí)現(xiàn);
user:實(shí)現(xiàn)用戶的業(yè)務(wù)邏輯代碼,同時(shí)也作為系統(tǒng)內(nèi)核/固件庫(kù)和用戶代碼的接口,基礎(chǔ)的接口模板由固件庫(kù)壓縮包提供,刪減后可以在其基礎(chǔ)上進(jìn)行開(kāi)發(fā),main函數(shù)就在該文件夾的文件中;
device:和芯片內(nèi)核/外設(shè)相關(guān)的文件,由固件庫(kù)壓縮包提供,內(nèi)核相關(guān)的文件需要?jiǎng)h減,僅保留適合本項(xiàng)目開(kāi)發(fā)環(huán)境的文件;
實(shí)現(xiàn)系統(tǒng)串口
系統(tǒng)串口使用的是USART1在PA2/PA3,由于GD32F310G-START并未提供串口轉(zhuǎn)USB電路,所以需要使用杜邦線外接一個(gè)串口轉(zhuǎn)USB的模塊與電腦串口軟件進(jìn)行通信;
進(jìn)入bsp文件夾,新建文件bsp_uart.c/.h,代碼內(nèi)容如下:
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" //系統(tǒng)串口打印初始化 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); //初始化串口外設(shè) 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); }
實(shí)現(xiàn) ADC
ADC的模擬輸入端口需要注意,PA0作為UserKey已經(jīng)通過(guò)10k電阻下拉到地,PA2/PA3已作為串口TX/RX使用,它們都不太適合作為本應(yīng)浮空的ADC通道,故選擇PA1作為ADC的輸入通道;在bsp文件夾內(nèi)新建文件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測(cè)試輸出原始結(jié)果(平均值) float adc_test_voltage = 0.0 ; //adc測(cè)試輸出電壓值(平均值) void Test_Adc_Init(void) { //設(shè)置模擬輸入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); //設(shè)置測(cè)試通道的GPIO為模擬模式 //設(shè)置ADC外設(shè) rcu_periph_clock_enable(TEST_ADC_PERCLK); rcu_adc_clock_config(RCU_ADCCK_APB2_DIV6); //ADC時(shí)鐘源設(shè)置 adc_data_alignment_config(ADC_DATAALIGN_RIGHT); //數(shù)據(jù)對(duì)齊模式:右對(duì)齊 adc_channel_length_config(ADC_REGULAR_CHANNEL, 1U); //規(guī)則轉(zhuǎn)換通道長(zhǎng)度:1 adc_external_trigger_source_config(ADC_REGULAR_CHANNEL, ADC_EXTTRIG_REGULAR_NONE); //觸發(fā)源設(shè)置:軟件觸發(fā) adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE); //觸發(fā)設(shè)置:開(kāi)啟規(guī)則轉(zhuǎn)換觸發(fā) adc_enable(); //ADC啟動(dòng) rt_thread_mdelay(1); //延時(shí)穩(wěn)定 adc_calibration_enable(); //ADC使用內(nèi)部校準(zhǔn) } INIT_BOARD_EXPORT(Test_Adc_Init); //開(kāi)始一次AD轉(zhuǎn)換 uint16_t Test_Adc_Sample(void) { adc_regular_channel_config(0U, TEST_ADC_CHANNEL, ADC_SAMPLETIME_239POINT5); //設(shè)置測(cè)試通道至規(guī)則轉(zhuǎn)換隊(duì)列頭,設(shè)置采樣時(shí)間 adc_software_trigger_enable(ADC_REGULAR_CHANNEL); //軟件觸發(fā)使能,ADC開(kāi)始轉(zhuǎn)換 while(!adc_flag_get(ADC_FLAG_EOC)); //等待轉(zhuǎn)換結(jié)束 adc_flag_clear(ADC_FLAG_EOC); return (adc_regular_data_read()); //返回轉(zhuǎn)換結(jié)果 } //獲取原始結(jié)果 uint16_t Test_Adc_Get_Raw(void) { return adc_test_raw_data ; } //獲取轉(zhuǎn)換電壓值 float Test_Adc_Get_Voltage(void) { return adc_test_voltage ; }
GD32F310只有8k的RAM個(gè)人認(rèn)為是不適合移植操作系統(tǒng)的,內(nèi)存比較小,沒(méi)辦法寫很復(fù)雜的線程代碼,其實(shí)這個(gè)簡(jiǎn)單的測(cè)試項(xiàng)目也用不上多線程調(diào)度,我就是純屬吃飽了撐著了,把F303移植好的RT-Thread直接拖過(guò)來(lái)用,關(guān)于RT-Thread移植的教程在網(wǎng)絡(luò)上有非常多,所以我就寫一些大致流程細(xì)節(jié)我就不方便展開(kāi)講了;RT-Thread是一款非常優(yōu)秀好用的國(guó)產(chǎn)RTOS,國(guó)產(chǎn)硬件配國(guó)產(chǎn)軟件實(shí)在般配;
新建rtos文件夾,整理rt-thread nano源碼包提供的文件,復(fù)制到rtos文件夾中;
main.h內(nèi)添加 #include "rtthread.h"
找到gd32f3x0_it.c,注釋掉以下幾個(gè)函數(shù),使其失效
// 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管理相關(guān)的宏,并添加如下代碼
#include "main.h" //使得RT-Thread能夠找到其他被項(xiàng)目include的文件 #include "finsh_config.h" //使用控制臺(tái)msh功能需要引用此文件 #define RT_USING_FINSH //使用控制臺(tái) #define RT_USING_HEAP //取消這個(gè)宏的注釋使其有效
找到finsh_port.c,修改和添加我們的串口接口代碼,供控制臺(tái)使用
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)); } }
如果我沒(méi)有遺漏什么細(xì)節(jié)的話,此時(shí)編譯代碼并下載運(yùn)行程序,能夠在串口軟件里收到RT-Thread的系統(tǒng)信息打印的內(nèi)容:
\ | / - RT - Thread Operating System / | \ 3.1.5 build Apr 10 2022 2006 - 2020 Copyright by rt-thread team msh >
擁有了操作系統(tǒng),我們就可以利用RT-Thread的自動(dòng)初始化功能,運(yùn)行我們的串口/ADC外設(shè)初始化代碼:
INIT_BOARD_EXPORT(Test_Adc_Init); //ADC初始化函數(shù)加入RTT板級(jí)自動(dòng)初始化隊(duì)列 INIT_BOARD_EXPORT(System_Uart_Init); //系統(tǒng)串口初始化函數(shù)加入RTT板級(jí)自動(dòng)初始化隊(duì)列
添加ADC測(cè)試代碼
在bsp_adc.c文件中,實(shí)現(xiàn)一個(gè)RTOS線程代碼,其功能是循環(huán)采集ADC的電壓數(shù)據(jù)并且保存到一個(gè)變量中;
//ADC自動(dòng)轉(zhuǎn)換線程入口 void Test_Adc_Value_Update_Thread(void) { //轉(zhuǎn)換次數(shù)記錄,轉(zhuǎn)換結(jié)果累加 uint32_t count = 0, data_count = 0; while (1) { if(count < TEST_ADC_SAMPLES_REPEATED_NUMBER)//轉(zhuǎn)換次數(shù)未滿 { data_count += Test_Adc_Sample();//進(jìn)行一次轉(zhuǎn)換并累加結(jié)果原始數(shù)據(jù) count ++;//轉(zhuǎn)換次數(shù) +1 } else//轉(zhuǎn)換次數(shù)已滿 { adc_test_raw_data = data_count/TEST_ADC_SAMPLES_REPEATED_NUMBER ;//累加原始數(shù)據(jù)求平均 adc_test_voltage = 3.3 / 4096 * adc_test_raw_data ;//平均的累加數(shù)據(jù)轉(zhuǎn)換為電壓值 data_count = 0 ;//重新開(kāi)始下一輪轉(zhuǎn)換 count = 0 ; rt_thread_mdelay(100);//釋放線程 } } } //上電后開(kāi)啟ADC自動(dòng)轉(zhuǎn)換線程 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); } //開(kāi)機(jī)后自動(dòng)生成并啟動(dòng)ADC轉(zhuǎn)換線程 INIT_APP_EXPORT(Test_Adc_Value_Update_Thread_Init);
運(yùn)行后,adc_test_raw_data/adc_test_voltage這兩個(gè)變量每隔100ms更新一次測(cè)量的ADC數(shù)值;
到main.c添加如下代碼,使系統(tǒng)控制臺(tái)每2s打印輸出一次ADC的測(cè)量結(jié)果:
#include//打印ADC數(shù)據(jù)線程 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); }
如圖所示,燒錄運(yùn)行后,可以從串口控制臺(tái)得到打印的數(shù)據(jù):
測(cè)量電壓
如圖所示,將可調(diào)穩(wěn)壓電源的輸出線與開(kāi)發(fā)板的GND/PA1相連,并且把萬(wàn)用表的表筆一同并聯(lián)到線路上,以萬(wàn)用表的讀數(shù)為基準(zhǔn),測(cè)量GD32F310的ADC轉(zhuǎn)換精度:
我這里使用ADC對(duì)電源的輸出電壓進(jìn)行100次采樣后求取平局值,得到的結(jié)果如下表所示:
從表中可以看出,延長(zhǎng)ADC采樣時(shí)間對(duì)ADC精度是有一定幫助的,在低電壓時(shí),ADC的讀數(shù)似乎非常差,原始數(shù)據(jù)會(huì)在0-6之間跳動(dòng),幾乎無(wú)法準(zhǔn)確的讀取穩(wěn)定的數(shù)值,直到把輸入電壓提升到50mV才有所穩(wěn)定,個(gè)人認(rèn)為這個(gè)情況是電源端的問(wèn)題,可能可調(diào)電源在低壓輸出時(shí)不夠穩(wěn)定,手里暫時(shí)沒(méi)有找到其他可以構(gòu)成分壓的電路降低電壓去測(cè)試,所以0.05V以下的電壓測(cè)量結(jié)果沒(méi)有太大的參考價(jià)值,電壓上升到1V以上后ADC的測(cè)量就比較穩(wěn)定了,雖然進(jìn)行100次累加求平均后讀數(shù)依然有跳動(dòng)的情況,但精度已經(jīng)基本滿足大部分工程的要求
-
萬(wàn)用表
+關(guān)注
關(guān)注
88文章
2126瀏覽量
131069 -
adc
+關(guān)注
關(guān)注
99文章
6698瀏覽量
549171
發(fā)布評(píng)論請(qǐng)先 登錄
如何選擇數(shù)字萬(wàn)用表?
數(shù)字萬(wàn)用表使用教程下載

普通萬(wàn)用表與數(shù)字萬(wàn)用表的優(yōu)缺點(diǎn)
萬(wàn)用表,萬(wàn)用表是什么意思
數(shù)字萬(wàn)用表測(cè)電流原理_數(shù)字萬(wàn)用表怎么測(cè)電流_數(shù)字萬(wàn)用表測(cè)電流圖解
萬(wàn)用表測(cè)量電壓原理_萬(wàn)用表怎么測(cè)電壓_萬(wàn)用表測(cè)量電壓的方法

如何使用萬(wàn)用表測(cè)試電壓詳細(xì)方法和原理說(shuō)明

評(píng)論