8.1實驗內容
通過本實驗主要學習以下內容:
8.1.1ADC原理
我們知道,自然界中有非常多的模擬信號,比如光照強度,還有其他的例如溫度、聲音等等,那么人們是怎么來衡量一個模擬信號的呢?
我們通常會說今天光照度達到了3萬Lux(照度單位),現在測量到的體溫是36.5℃,我們所處的環境是40分貝,沒錯,人們就是通過將這些模擬信號數字化,從而達到衡量這些模擬信號的目的。那對于MCU來說,如果要測量一個模擬量,可以通過自帶的ADC(Analog-to-Digital converters)模塊,即模-數轉換器將模擬量轉化為可以被MCU讀取到的數字量。
8.1.2GD32H757 ADC工作原理
GD32H757有3個逐次逼近型ADC(SAR ADC),其中ADC1最大有效位為14bit,有20個外部通道,一個內部通道(DAC_OUT0通道);ADC1最大有效位為14bit,有18個外部通道,3個內部通道(電池電壓(VBAT)通道、 參考電壓輸入通道(VREFINT) 和DAC_OUT1通道);ADC2最大有效位為12bit, 有17個外部通道,4個內部通道(電池電壓(VBAT)通道、 參考電壓輸入通道(VREFINT)、內部溫度傳感通道(VSENSE)和高精度溫度傳感器通道(VSENSE2))。
這三個ADC可以獨立工作,也可以讓ADC0和ADC1工作在同步模式下。有最多42個外部ADC引腳可用于將連接到這些引腳的電壓值轉換為數字量,這些引腳號可以通過Datasheet獲得。
表中ADC012_INx的意思是:該IO口可以作為通道x用于ADC0、ADC1和ADC2。如ADC012_IN10,表示PC0可以用于ADC0的通道10使用,也可以作為ADC1和ADC2的通道10使用。但要注意:不能在同一個時刻讓不同的ADC去轉換同一個通道,否則會有無法預料的結果 |
以下總結了GD32H757 ADC的特性:
- 高性能:
– ADC采樣分辨率:ADC0/1可配置14位、12位、10位或者8位分辨率,ADC2可配置12位、10位、8位或者6位分辨率;
– ADC0/1采樣率:14位分辨率為4 MSPs,12位分辨率為4.5 MSPs,10位分辨率為5.14 MSPs,8位分辨率為6 MSPs。分辨率越低,轉換越快;
– ADC2采樣率:12位分辨率為5.3 MSPs,10位分辨率為6.15 MSPs,8位分辨率為7.27 MSPs,6位分辨率為8.89 MSPs。分辨率越低,轉換越快;
–前置校準時間:131個ADC時鐘周期;
–可編程采樣時間;
–數據存儲模式:最高有效位對齊和最低有效位對齊;
– DMA請求。
- 模擬輸入通道:
– ADC0有20個外部模擬輸入通道,ADC1有18個外部模擬輸入通道,ADC2有17個外部模擬輸入通道;
– 1個內部溫度傳感通道(VSENSE);
– 1個內部參考電壓輸入通道(VREFINT);
– 1個外部監測電池VBAT供電引腳輸入通道;
– 1個內部高精度溫度傳感器通道(VSENSE2);
–與DAC內部通道連接。
- 轉換開始的發起:
–軟件;
– TRIGSEL觸發。
- 運行模式:
–轉換單個通道,或者掃描一序列的通道;
–單次運行模式,每次觸發轉換一次選擇的輸入通道;
–連續運行模式,連續轉換所選擇的輸入通道;
–間斷運行模式;
–同步模式(適用于具有兩個或多個ADC的設備)。
- 轉換結果閾值監測器功能: 模擬看門狗。
- 常規序列轉換結束、模擬看門狗事件和溢出事件都可以產生中斷。
- 過采樣:
– ADC0/1為32位的數據寄存器,ADC2為16位數據寄存器;
– ADC0/1可調整的過采樣率,從2x到1024x,ADC2可調整的過采樣率,從2x到256x;
– ADC0/1高達11位的可編程數據移位,ADC2為8位的可編程數據移位,。
- ADC0/1供電要求:1.8V到3.6V,一般電源電壓為3.3V,ADC2供電要求:1.71V到3.6V,一般電源電壓為3.3V;
- 通道輸入范圍: VREF- ≤VIN ≤VREF+;
- 數據可以路由到HPDF進行后期處理。
下面介紹下GD32H757的ADC框圖:
標注1:輸入電壓和參考電壓
輸入電壓引腳定義如下表:
GD32H757的ADC是14/12bit有效位的,14bit滿量程對應的轉換值為16383,12bit為4095,以14bit為例,當采樣引腳上的電壓等于ADC參考電壓時,得到的轉換值即為16383。故理論采樣是指可通過以下公式得到:
采樣數值=實際電壓/參考電壓*16383
標注2:輸入通道
前面提到,ADC0有20個外部模擬輸入通道,ADC1有18個外部模擬輸入通道,ADC2有17個外部模擬輸入通道。其中外部通道可以通過Datasheet進行查詢。
標注3:規則組
GD32H757的ADC轉換組稱為規則組,也叫常規序列。
規則組有三個重要的參數,其一為轉換的個數,其二為轉換的序列,其三為轉換周期,規定好這三個參數后,一旦開始規則組的轉換,則ADC就按照轉換序列一個一個的進行模-數轉換,直到達到要求的轉換個數。
規則組的轉換個數由ADC_RSQ0寄存器的RL[3:0]位規定,轉換的總數目為RL[3:0]+1,轉換總數目最大為16個;轉換序列和轉換周期由ADC_RSQ0~ADC_RSQ8共同決定,我們以RSQ8寄存器為例來看下:
ADC_RSQ0寄存器:
舉個例子,現需要按照CH3->CH2->CH1的順序進行規則組轉換,則設定RL[3:0] = 2,然后設定RSQ0為2(CH3),RSQ1為1(CH2),RSQ2為0(CH1),則當
開始規則組轉換時,ADC首先進行RSQ0規定的通道即CH3的轉換,再進行RSQ1規定的通道即CH2的轉換,最后進行RSQ2規定的通道即CH1轉換,當這三個通道轉換完后,規則組轉換結束。
需要注意的是,每轉換一個規則組通道,轉換結果都會放在寄存器ADC_RDATA中,所以CPU一定要在下一個通道轉換完成前將上一個通道轉換結果讀走,否則會導致上一個通道數據被新的數據覆蓋。所以在多通道規則組轉換時,為了保證能讀到所有通道的數據,一定要使用DMA(直接存儲器訪問控制器),每個通道轉換結束后,都會給DMA發送請求,DMA就會將最新的ADC_RDATA中的數據搬走。關于ADC配合DMA的使用,后面會詳細介紹。
標注4:觸發源
ADC的規則組需要選特定的觸發源用于觸發ADC轉換,注意,ADC的Enable(即ADC_CTL1寄存器的ADC_ON位置“1”)不會觸發ADC轉換,而是當選定的觸發源來臨后ADC才開始轉換。
觸發源分為內部觸發和外部觸發,內部觸發是指軟件觸發;外部觸發源是除了內部觸發源以外的觸發源,外部觸發源可以通過TRISEL選擇。TRIGSEL觸發相關內容,請讀者參考GD32H7系列用戶手冊TRIGSEL章節。
標注5:規則組數據寄存器
如標注3規則組的表述,每個ADC的規則組只有一個數據寄存器ADC_RDATA,每轉換一個通道,轉換結果放在這個寄存器中,在下一通道轉換結束前必須要將上一個通道的轉換結果取走。
標注6:ADC中斷及標志位
ADC的中斷總共有兩種:規則組轉換結束中斷以及模擬看門狗,可以通過將ADC_CTL0中的EOCIE、WDE0IE、WDE2IE、WDE2IE置“1”來開啟相應中斷。
ADC_STAT寄存器中的EOC和WDE0,1,2表示相應事件發生,EOC置“1”表示規則組的轉換已經結束。
8.1.3DMA原理
本實驗中ADC通道有兩個,分別為PC2_C和PC3_C,所以我們用規則組多通道采樣實現雙電壓讀取,從上一節內容中可以知道,ADC規則組實現多通道轉換時,必須要用到DMA。下面我們介紹下DMA原理。
DMA(直接存儲器訪問控制器)是一個非常好用的外設,它提供了一種硬件的方式在外設和存儲器之間或者存儲器和存儲器之間傳輸數據,而無需CPU的介入,從而使CPU可以專注在處理其他系統功能上。GD32H757有兩個DMA,其中DMA0有8個通道,DMA1也有8個通道。
GD32H757支持DMA的單一傳輸和多拍傳輸模式。這里只講解單一傳輸。DMA實現很簡單,只要配置好以下幾要素即可。
- 源地址和目標地址:DMA進行數據搬運過程為從源地址讀取到數據,再搬運到目標地址。本實驗中,需要把ADC轉換結果搬運到自定義的buffer中,所以源地址就要設置為ADCx_RDATA寄存器地址,目標地址為buffer地址。
- 源和目標的地址增量方式:地址增量方式有固定模式和增量模式兩種,固定模式是指進行一次DMA搬運后,下次搬運的源地址或目標地址保持不變;增量模式指進行一次DMA搬運后,下次搬運的源地址或目標地址會加1。本實驗中,源地址始終都應該為ADCx_RDATA地址,所以源地址增量方式需要設置為固定模式,而目標地址為自定義buffer,我們需要用buffer[0]存儲x軸數據,buffer[1]存儲y軸數據,所以目標地址增量方式需要設置為增量模式。
- DMA傳輸方向:DMA傳輸方向有三種,分別為外設地址->存儲器地址、存儲器地址->外設地址以及存儲器->存儲器。本實驗中源地址是外設地址,目標地址為自定義buffer地址即存儲器地址,故傳輸方向需設置為外設地址->存儲器地址。
- 源和目標數據位寬:源和目標數據位寬表示每次搬運的數據長度,可以設置為8bit、16bit和32bit。本實驗中ADC的數據只占用ADCx_RDATA寄存器的低半字即16bit,所以源和目標位寬選擇16bit即可。
- DMA傳輸個數和循環模式:傳輸個數表示一輪DMA傳輸可以搬運的次數。循環模式表示當一輪DMA傳輸結束后,是否直接進行下一輪搬運,當開啟循環模式后,當上一輪DMA傳輸結束后,源地址和目標地址會恢復到最開始的狀態。本實驗中,需要轉換2個通道ADC,故DMA傳輸個數設置為2,循環模式開啟。
- DMA通道優先級:DMA的每個通道都有一個軟件優先級,當DMA控制器在同一時間接收到多個外設請求時,仲裁器將根據外設請求的優先級來決定響應哪一個外設請求。優先級包括軟件優先級和硬件優先級,優先級規則如下:
軟件優先級:分為4級,低,中,高和極高??梢酝ㄟ^寄存器DMA_CHxCTL的PRIO位域來配置。
硬件優先級:當通道具有相同的軟件優先級時,編號低的通道優先級高。例:通道0和通道2配置為相同的軟件優先級時,通道0的優先級高于通道2。
上面描述了DMA配置的一些要素,那么DMA是如何被觸發的呢,這里就要講到另外一個外設:DMAMUX。
8.1.4DMAMUX原理
我們來看下DMA系統架構:
我們可以看到,DMA0/1的每個通道都有一個輸入源Peri_reqx,而這些輸入源則是由DMAMUX輸出。下圖為DMAMUX的框圖:
將以上兩個圖放在一起看就更好理解:
可以看到DMAMUX的請求路由器總共有16個通道,其中通道0~7用于DMA0的通道0~7;通道8~15用于DMA1的通道0~7。
那么請求路由器的觸發源是什么呢?這個可以通過GD32H7用戶手冊DMAMUX章節中查出。比如本實驗中使用PC2_C和PC3_C進行ADC轉換,從datasheet中查出這兩個引腳分別對應為ADC2_IN0和ADC2_IN1:
所以DMAMUX的請求路由器觸發源選擇ADC2即可,下圖為DMAMUX請求輸入源輸入信號表部分截圖:
講到這里可能有的讀者,特別是用過GD前期產品(如GD32F3/F4系列)的讀者就會有疑問了,如果DMA用于ADC2,我需要選擇哪個DMA通道呢?答案就是:任意一個沒有被使用的通道即可!因為GD32H7產品的DMA通道沒有綁定到特定外設上,所以只需要選擇任意一個沒有被使用的通道就可以了,這種設計方式,讓DMA的使用更加的靈活方便。
8.2硬件設計
本實驗是使用PC2_C和PC3_C來進行電壓采集,讀者可以采用飛線方式外接電壓到這兩個引腳進行測試。
8.3代碼解析
本實驗用到兩個ADC2通道,使用ADC2規則組搭配DMA1通道0進行數據轉換和搬運,ADC2規則組和DMA1通道0都開啟循環模式,一旦開始ADC2規則組轉換,會持續PC2_C和PC3_C上的電壓進行轉換和數據搬運。
8.3.1DMA和ADC初始化
在driver_adc.c中定義driver_adc_regular_ch_dma_config函數,該函數實現DMA和ADC的初始化。
C void driver_adc_regular_ch_dma_config(typdef_adc_ch_general *ADC, typdef_adc_ch_parameter *ADC_CH,void *buffer) { dma_single_data_parameter_struct dma_single_data_parameter; rcu_periph_clock_enable(ADC->dma_parameter.rcu_dma); /*DMA時鐘開啟*/ rcu_periph_clock_enable(RCU_DMAMUX); dma_deinit(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel); /*DMA通道參數復位*/ /*DMA源地址、目標地址、增量方式、傳輸位寬、傳輸方向、傳輸個數、優先級設置*/ dma_single_data_parameter.request = ADC->dma_parameter.request; dma_single_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(ADC->adc_port)); dma_single_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_single_data_parameter.memory0_addr = (uint32_t)(buffer); dma_single_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE; if(ADC->adc_mode == ADC_DAUL_REGULAL_PARALLEL) { dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_32BIT; } else { dma_single_data_parameter.periph_memory_width = DMA_PERIPH_WIDTH_16BIT; } dma_single_data_parameter.direction = DMA_PERIPH_TO_MEMORY; dma_single_data_parameter.number = ADC->dma_parameter.dma_number; dma_single_data_parameter.priority = ADC->dma_parameter.dma_priority; dma_single_data_mode_init(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel, &dma_single_data_parameter); /*DMA循環模式設置*/ if(ADC->dma_parameter.dma_circulation_mode == ENABLE) { dma_circulation_enable(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel); } else { dma_circulation_disable(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel); } dma_channel_enable(ADC->dma_parameter.dma_periph, ADC->dma_parameter.dma_channel); /* 使能DMA */ driver_adc_config(ADC,ADC_CH); /* ADC初始化 */ } |
ADC初始化函數定義如下:
C++ void driver_adc_config(typdef_adc_ch_general *ADC,typdef_adc_ch_parameter *ADC_CH) { uint8_t i; adc_idx_enum idx_adc; adc_deinit(ADC->adc_port); /* ADC clock config */ if(ADC->adc_port==ADC0){ idx_adc=IDX_ADC0; }else if(ADC->adc_port==ADC1){ idx_adc=IDX_ADC1; }else{ idx_adc=IDX_ADC2; } rcu_adc_clock_config(idx_adc, RCU_ADCSRC_PER); adc_clock_config(ADC->adc_port, ADC->adc_psc); /*配置ADC時鐘頻率*/ rcu_periph_clock_enable(ADC->rcu_adc); /*使能ADC時鐘*/ /*配置ADC相關IO口,先配置時鐘,再將IO口設置為模擬輸入*/ for(i=0 ;ich_count; i++) { if(ADC_CH[i].adc_channel < ADC_CHANNEL_17) { rcu_periph_clock_enable(ADC_CH[i].rcu_port); gpio_mode_set(ADC_CH[i].port, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, ADC_CH[i].pin); } else { if(ADC->adc_port==ADC2) { if(ADC_CH[i].adc_channel == ADC_CHANNEL_17) { adc_internal_channel_config(ADC_CHANNEL_INTERNAL_VBAT, ENABLE); } else if(ADC_CH[i].adc_channel == ADC_CHANNEL_18) { adc_internal_channel_config(ADC_CHANNEL_INTERNAL_TEMPSENSOR, ENABLE); } else if(ADC_CH[i].adc_channel == ADC_CHANNEL_19) { adc_internal_channel_config(ADC_CHANNEL_INTERNAL_VREFINT, ENABLE); } else if(ADC_CH[i].adc_channel == ADC_CHANNEL_20) { adc_internal_channel_config(ADC_CHANNEL_INTERNAL_HP_TEMPSENSOR, ENABLE); } } else { rcu_periph_clock_enable(ADC_CH[i].rcu_port); gpio_mode_set(ADC_CH[i].port, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, ADC_CH[i].pin); } } } adc_sync_mode_config(ADC->adc_mode); /*配置ADC工作模式,如獨立模式,規則并行模式等*/ adc_special_function_config(ADC->adc_port, ADC_SCAN_MODE, ADC->adc_scan_function); /*配置規則組的掃描模式和連續轉換模式*/ if(ADC->adc_channel_group == ADC_REGULAR_CHANNEL) { adc_special_function_config(ADC->adc_port, ADC_CONTINUOUS_MODE, ADC->adc_continuous_function); } adc_data_alignment_config(ADC->adc_port, ADC_DATAALIGN_RIGHT); /*選擇數據右對齊*/ adc_channel_length_config(ADC->adc_port, ADC->adc_channel_group, ADC->ch_count); /*配置轉換通道數*/ if(ADC->adc_channel_group == ADC_REGULAR_CHANNEL) /*配置轉換順序*/ { for(i = 0;i< ADC->ch_count;i++) { adc_regular_channel_config(ADC->adc_port, i, ADC_CH[i].adc_channel,ADC_CH[i].sample_time); } } else if(ADC->adc_channel_group == ADC_INSERTED_CHANNEL) { for(i = 0;i< ADC->ch_count;i++) { adc_inserted_channel_config(ADC->adc_port, i, ADC_CH[i].adc_channel,ADC_CH[i].sample_time); } } /*選擇觸發源及使能外部觸發模式*/ adc_external_trigger_config(ADC->adc_port, ADC->adc_channel_group, ADC->adc_external_trigger_mode); /*選擇是否需要使用DMA*/ if(ADC->DMA_mode == ENABLE) { adc_dma_request_after_last_enable(ADC->adc_port); adc_dma_mode_enable(ADC->adc_port); } /*ADC的使能和自校準,ADC使能后需要經過一定的ADC_CLK后才能校準,本示例中直接使用1ms延時*/ adc_enable(ADC->adc_port); delay_ms(1); /* ADC calibration mode config */ adc_calibration_mode_config(ADC->adc_port, ADC_CALIBRATION_OFFSET_MISMATCH); /* ADC calibration number config */ adc_calibration_number(ADC->adc_port, ADC_CALIBRATION_NUM32); adc_calibration_enable(ADC->adc_port); } |
在driver_adc.h中聲明了ADC DMA的結構體:
C typedef struct __typdef_adc_dma_parameter { rcu_periph_enum rcu_dma; //DMA時鐘 uint32_t dma_periph; //DMA號 dma_channel_enum dma_channel;//DMA通道號 uint32_t request;//DMA請求 uint32_t dma_number; //DMA傳輸個數 uint32_t dma_priority; //DMA通道優先級 EventStatus dma_circulation_mode;//循環模式 }typdef_adc_dma_parameter; |
這段代碼比較簡單,請讀者按照前面介紹的DMA原理進行解析。
8.3.2BSP_ADC設置所需要的參數及IO口結構體定義
在bsp_adc.c中,對BSP_ADC設置所需要的參數及IO擴結構體數組進行了定義:
C typdef_adc_ch_general BSP_ADC= { .rcu_adc = RCU_ADC2, /* ADC2的時鐘 */ .adc_psc = ADC_CLK_SYNC_HCLK_DIV6, /* ADC2設置為HCLK 6分頻 */ .adc_port = ADC2, /* ADC口為ADC2 */ .adc_mode = ADC_SYNC_MODE_INDEPENDENT, /* ADC模式為獨立模式 */ .adc_channel_group = ADC_REGULAR_CHANNEL, /* 使用規則組 */ .adc_scan_function = ENABLE, /* 開啟掃描模式 */ .adc_continuous_function = ENABLE, /* 開啟循環模式 */ .ch_count = 2, /* 轉換長度為2 */ .adc_external_trigger_mode = EXTERNAL_TRIGGER_DISABLE, .dma_parameter = { .rcu_dma = RCU_DMA1, /* DMA1的時鐘 */ .dma_periph = DMA1, /* 使用DMA1 */ .dma_channel = DMA_CH0, /* 使用通道4 */ .dma_number = 2, /* DMA傳輸長度為2 */ .request = DMA_REQUEST_ADC2, .dma_priority = DMA_PRIORITY_HIGH, /* DMA通道優先級 */ .dma_circulation_mode = ENABLE /* DMA循環模式打開 */ }, .DMA_mode = ENABLE /* 使用DMA */ }; typdef_adc_ch_parameter BSP_ADC_ch[2] = { { .rcu_port = RCU_GPIOC, /* GPIOC時鐘 */ .port = GPIOC, /* GPIO port */ .pin = GPIO_PIN_2, /* PC2 */ .gpio_speed = GPIO_OSPEED_12MHZ, /* PC2速度設置為12MHz */ .adc_channel = ADC_CHANNEL_0, /* PC2是ADC2的通道0 */ .sample_time = 240 /* 設置采樣周期為240 */ } , { .rcu_port = RCU_GPIOC, /* GPIOC時鐘 */ .port = GPIOC, /* GPIO port */ .pin = GPIO_PIN_3, /* PC3 */ .gpio_speed = GPIO_OSPEED_12MHZ, /* PC3速度設置為12MHz */ .adc_channel = ADC_CHANNEL_1, /* PC3是ADC2的通道1 */ .sample_time = 240 /* 設置采樣周期為55.5 */ } };/* ADC通道參數配置,包括IO口,和對應通道以及采樣周期 */ |
8.3.3BSP_ ADC初始化和觸發ADC轉換的具體實現函數
在bsp_adc.c中定義了DMA和ADC初始化和觸發ADC轉換的函數:
C uint16_t BSP_ADC_data[2] ; void bsp_ADC_config() { driver_adc_regular_ch_dma_config(&BSP_ADC,BSP_ADC_ch,(uint16_t*)BSP_ADC_data); driver_adc_software_trigger_enable(&BSP_ADC); } |
8.3.4main函數實現
C int main(void) { driver_init();//延時函數初始化 bsp_uart_init(&BOARD_UART);//BOARD_UART串口初始化 bsp_ADC_config();//bsp ADC配置 while (1) { delay_ms(100);//延時100ms printf_log(" the BSP_ADC data is %d,%d \r\n", BSP_ADC_data[0],BSP_ADC_data[1]);//打印ADC數據 } } |
本例程main函數首先進行了延時函數初始化,為了演示實驗結果,這里初始化了BOARD_UART串口,關于串口的使用,請讀者參考串口章節,然后是BSP_ADC配置。在主循環中,每100ms打印一次PC2_C和PC3_C的ADC轉換數據。
8.4實驗結果
如上main函數實現說明。
-
adc
+關注
關注
95文章
5663瀏覽量
539800 -
GD32
+關注
關注
7文章
347瀏覽量
23771 -
GD32開發板
+關注
關注
0文章
3瀏覽量
1065
發布評論請先 登錄
相關推薦
評論