開發環境:
MDK:Keil 5.30
開發闆:GD32F207I-EVAL
MCU:GD32F207IK
13.1 DAC工作原理
13.1.1 DAC介紹
數字/模拟轉換子產品(DAC)是12位數字輸入,電壓輸出的數字/模拟轉換器。DAC可以配置為8位或12位模式,也可以與DMA控制器配合使用。DAC工作在12位模式時,資料可以設定成左對齊或右對齊。DAC子產品有2個輸出通道,每個通道都有單獨的轉換器。在雙DAC模式下,2個通道可以獨立地進行轉換,也可以同時進行轉換并同步地更新2個通道的輸出。DAC可以通過引腳輸入參考電壓VREF+ 以獲得更精确的轉換結果。
13.1.2 DAC主要特征
● 2個DAC轉換器:每個轉換器對應1個輸出通道
● 8位或者12位單調輸出
● 12位模式下資料左對齊或者右對齊
● 同步更新功能
● 噪聲波形生成或三角波形生成
● 雙DAC通道同時或者分别轉換
● 每個通道都有DMA功能
● 外部觸發轉換
● 輸入參考電壓VREF+
【注意】一旦使能DACx通道,相應的GPIO引腳(PA4或者PA5)就會自動與DAC的模拟輸出相連(DAC_OUTx)。為了避免寄生的幹擾和額外的功耗,引腳PA4或者PA5在之前應當設定成模拟輸入(AIN)。
13.1.3 DAC功能描述
使能DAC通道
将DAC_CTL 寄存器中的 DENx 位置 ’1’ 即可打開對DAC通道x 的供電。經過一段啟動時間tWAKEUP,DAC通道x 即被使能。
注意:DENx位隻會使能DAC通道x的模拟部分,即便該位被置’0’,DAC通道x的數字部分仍然工作。
使能DAC輸出緩存
DAC內建了2個輸出緩存,可以用來減少輸出阻抗,無需外部運放即可直接驅動外部負載。每個DAC通道輸出緩存可以通過設定 DAC_CTL 寄存器的 DBOFFx 位來開啟或者關閉緩沖區。
DAC輸出電壓
數字輸入經過DAC被線性地轉換為模拟電壓輸出,其範圍為0到VREF+。任一DAC通道引腳上的輸出電壓滿足下面的關系:
DAC輸出 = VREF x (DAC_DO / 4096)
【注】官方手冊有問題的,這裡應該是4096
DAC資料格式
根據選擇的配置模式,資料按照下文所述寫入指定的寄存器:
●8位資料右對齊:使用者須将資料寫入寄存器DACx_R8DH [7:0]位
●12位資料左對齊:使用者須将資料寫入寄存器DACx_L12DH[15:4]位
●12位資料右對齊:使用者須将資料寫入寄存器DACx_R12DH[11:0]位
經過相應的移位後,寫入的資料被轉存到DACx_DH寄存器中。随後,DACx_DH寄存器的内容或被自動地傳送到DACx_DO寄存器,或通過軟體觸發或外部事件觸發被傳送到DACx_DO寄存器。
DAC轉換
如果使能了外部觸發(通過設定 DAC_CTL 寄存器的 DTENx 位),根據已經選擇的觸發事件,DAC 保持資料(DACx_DH)會被轉移到 DAC 資料輸出寄存器(DACx_DO)。否則,在外部觸發沒有使能的情況下, DAC 保持資料(DACx_DH)會被自動轉移到 DAC 資料輸出寄存器(DACx_DO)。
當 DAC 保持資料(DACx_DH)加載到 DACx_DO 寄存器時,經過 tSETTLING 時間之後,模拟輸出變得有效, tSETTLING 的值與電源電壓和模拟輸出負載有關。
選擇DAC觸發
通過設定 DAC_CTL 寄存器中 DTENx 位來使能 DAC 外部觸發。觸發源可以通過 DAC_CTL寄存器中 DTSELx 位來進行選擇。
TIMERx_TRGO 信号是由定時器生成的,而軟體觸發是通過設定 DAC_SWT 寄存器的 SWTRx位生成的。
13.2 DAC寄存器描述
我們介紹一下要實作 DAC 輸出,需要用到的一些寄存器。首先是 DAC控制寄存器DAC_CTL,該寄存器的各位描述如下圖所示。
DAC_CTL的低 16 位用于控制通道0,而高 16 位用于控制通道 1,我們這裡僅列出比較重要的最低 8 位的較長的描述。
首先,我們來看 DAC 通道0使能位(DEN0),該位用來控制 DAC 通道 0使能的,本章我們就是用的 DAC 通道 0,是以該位設定為 1。
再看關閉 DAC 通道 0輸出緩存控制位(DBOFF0),這裡 GD32 的 DAC 輸出緩存做的有些不好,如果使能的話,雖然輸出能力強一點,但是輸出沒法到 0,這是個很嚴重的問題。是以本章我們不使用輸出緩存。即設定該位為 1。DAC 通道0觸發使能位(DTEN0),該位用來控制是否使用觸發,裡我們不使用觸發,是以設定該位為 0。DAC 通道 0觸發選擇位(DTSEL0 [2:0]),這裡我們沒用到外部觸發,是以設定這幾個位為 0就行了。DAC 通道 0噪聲/三角波生成使能位(DWM0 [1:0]),這裡我們同樣沒用到波形發生器,故也設定為 0 即可。DAC 通道0噪聲波位寬(DWBW0 [3:0]),這些位僅在使用了波形發生器的時候有用,本章沒有用到波形發生器,故設定為 0 就可以了。
最後是 DAC 通道0 DMA 使能位(DDMAEN0)。
在 DAC_CTL設定好之後, DAC 就可以正常工作了, 我們僅需要再設定 DAC 的資料保持寄存器的值,就可以在 DAC 輸出通道得到你想要的電壓了(對應 IO 口設定為模拟輸入)。假設我們用的是 DAC 通道 0的 12 位右對齊資料保持寄存器:DAC0_R12DH,該寄存器各位描述如下圖所示。
該寄存器用來設定 DAC 輸出,通過寫入 12 位資料到該寄存器,就可以在 DAC 輸出通道0得到我們所要的結果。
13.3 DAC應用代碼實作
13.3.1 DAC普通方式輸出
本章我們将使用庫函數的方法來設定 DAC 子產品的通道0來輸出模拟電壓,其詳細設定步驟如下:
1)開啟 PA 口時鐘,設定 PA4為模拟輸入。
GD32F207的 DAC 通道0在 PA4上,是以,我們先要使能PA4的時鐘, 然後設定 PA4為模拟輸入。DAC 本身是輸出,但是為什麼端口要設定為模拟輸入模式呢?因為一但使能 DACx 通道後,相應的 GPIO 引腳(PA4 或者 PA5)會自動與 DAC 的模拟輸出相連,設定為輸入,是為了避免額外的幹擾。
使能 GPIOA 時鐘:
rcu_periph_clock_enable(RCU_GPIOA);
設定 PA5為模拟輸入隻需要設定初始化參數即可:
gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_5);
2)使能 DAC時鐘。
同其他外設一樣,要想使用,必須先開啟相應的時鐘。DAC 子產品時鐘是由 APB1提供的。
rcu_periph_clock_enable(RCU_DAC); //使能 DAC 通道時鐘
3)初始化 DAC,設定 DAC 的工作模式。
該部分設定全部通過 DAC_CR 設定實作,包括:DAC 通道 使能、 DAC 通道輸出緩存關閉、不使用觸發、不使用波形發生器等設定。
/* configure the DAC0 */
dac_trigger_disable(DAC0);
dac_wave_mode_config(DAC0, DAC_WAVE_DISABLE);
dac_output_buffer_enable(DAC0);
dac_trigger_disable()函數用來關閉觸發功能。
dac_wave_mode_config()設定是否使用波形發生,這裡我們前面同樣講解過不使用。是以值為 DAC_WAVE_DISABLE。
dac_output_buffer_enable用于緩存的配置,如果不使用輸出緩存,是以使用dac_output_buffer_enable()關閉緩存。
4)使能 DAC 轉換通道
初始化 DAC 之後,理所當然要使能 DAC 轉換通道,庫函數方法是:
dac_enable(DAC0); //使能 DAC0
5)設定 DAC 的輸出值。
通過前面 4 個步驟的設定, DAC 就可以開始工作了,我們使用 12 位右對齊資料格式,是以我們通過設定 DAC0_R12DH,就可以在 DAC 輸出引腳(PA4)得到不同的電壓值了。 庫函數的函數是:
dac_data_set(DAC0, DAC_ALIGN_12B_R, 0);
第二個參數設定對齊方式,可以為 12 位右對齊DAC_ALIGN_12B_R, 12 位左對齊DAC_ALIGN_12B_L 以及 8 位右對齊 DAC_ALIGN_8B_R方式。
第三個參數就是 DAC 的輸入值了,這個很好了解,初始化設定為 0。這裡,還可以讀出 DAC 的數值,函數是:
dac_output_value_get (DAC0);
是以DAC0的整體配置如下:
/*
brief Configure the DAC peripheral
param[in] none
param[out] none
retval none
*/
void dac_config(void)
{
/* DAC GPIO configuration */
dac_gpio_config();
/* enable the clock of DAC */
rcu_periph_clock_enable(RCU_DAC);
/* configure the DAC0 */
dac_trigger_disable(DAC0);
dac_wave_mode_config(DAC0, DAC_WAVE_DISABLE);
dac_output_buffer_enable(DAC0);
/* enable DAC0 and set data */
dac_enable(DAC0);
}
主函數如下:
/*
brief main function
param[in] none
param[out] none
retval none
*/
int main(void)
{
uint8_t i=0;
uint16_t da=0;
//systick init
sysTick_init();
//usart init 115200 8-N-1
com_init(COM1);
/*DAC初始化*/
dac_config();//調用DAC配置
while(1)
{
da=0;
for(i=0;i<=10;i++)
{
da=i*400;
dac_data_set(DAC0, DAC_ALIGN_12B_R, da);
printf("da=%f v\r\n",3.3*((float)da/4096));
//printf("%3.2f\r\n",3.3*((float)da/4096));
delay_ms(1000);
}
}
}
這代碼很簡單,首先是對序列槽等進行初始化,接下來就是循環設定電壓并輸出。
如果想要使用軟體觸發,則需要将DAC配置為DAC_TRIGGER_SOFTWARE。
dac_trigger_source_config(DAC0, DAC_TRIGGER_SOFTWARE);
dac_trigger_enable(DAC0);
然後在主函數中需要進行軟體觸發。
dac_software_trigger_enable(DAC0);
13.3.2 DAC正弦波輸出實作
本章我們還要通過DAC實作正弦波輸出,那麼就需要找到正弦波的曲線散點,其計算方式如下所示:
原系統時鐘周期:T_Systick=1/120M(機關:秒)
因為定時時鐘預分頻:Prescaler=0
是以定時時鐘周期:T_TIMER=T_Systick*(Prescaler+1)=1/120M(機關:秒)
因為設定的定時更新周期:Period=19
是以定時器更新周期:T_update=T_TIMER*(Period+1)=20/120M
而DAC資料更新率等于定時器更新速率:即DAC的資料更新周期為:
DAC_update=T_update=20/120M
本實驗有32個資料點,則正弦波的周期為:
T_sin=DAC_update*點數=640/120M
最後求的正弦波的頻率為:
f_sin=1/T_sin=187500Hz
是以正弦波的頻率為:
f_sin=1/T_Systick/(Prescaler+1)/(Period+1)/點數
其波形資料如下:
const uint16_t Sine12bit[32] = {
2448,2832,3186,3496,3751,3940,4057,4095,4057,3940,
3751,3496,3186,2832,2448,2048,1648,1264,910,600,345,
156,39,0,39,156,345,600,910,1264,1648,2048
};
接下來看看主函數。
/*
brief main function
param[in] none
param[out] none
retval none
*/
int main(void)
{
//systick init
sysTick_init();
//usart init 115200 8-N-1
com_init(COM1);
/*DAC初始化*/
dac_mode_init();
while(1)
{
delay_ms(1000);
}
}
主函數很簡單,那我們進入DAC_Mode_Init()看看吧。
/*
brief Configure the DAC mode
param[in] none
param[out] none
retval none
*/
void dac_mode_init(void)
{
// dac 配置
dac_config();
//DMA配置
dma_config();
// TIMER配置并啟動
timer_config();
}
dac_mode_init()函數初始化了DAC、DMA和TIMER,啟動定時器,利用定時器的觸發DAC資料更新。
完整配置代碼如下:
/*
brief Configure the GPIO peripheral
param[in] none
param[out] none
retval none
*/
static void dac_gpio_config(void)
{
/* enable the clock of GPIO */
rcu_periph_clock_enable(RCU_GPIOA);
/* config the GPIO as analog mode */
gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_4);
}
/*
brief Configure the DAC peripheral
param[in] none
param[out] none
retval none
*/
static void dac_config(void)
{
/* DAC GPIO configuration */
dac_gpio_config();
/* enable the clock of DAC */
rcu_periph_clock_enable(RCU_DAC);
dac_wave_mode_config(DAC0, DAC_WAVE_DISABLE);
dac_output_buffer_disable(DAC0);
/* configure the DAC0 */
dac_trigger_source_config(DAC0, DAC_TRIGGER_T1_TRGO);
/* DAC的DMA功能使能 */
dac_dma_enable(DAC0);
dac_trigger_enable(DAC0);
/* enable DAC0 and set data */
dac_enable(DAC0);
}
/*
brief configure the DMA peripheral
param[in] none
param[out] none
retval none
*/
static void dma_config(void)
{
dma_parameter_struct dma_init_struct;
/* enable DMA CLK */
rcu_periph_clock_enable(RCU_DMA1);
/* deinitialize DMA1 channel2 */
dma_deinit(DMA1, DMA_CH2);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;/* 存儲器到外設方向 */
dma_init_struct.memory_addr = (uint32_t)Sine12bit; /* 存儲器基位址 */
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT;
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
//dma_init_struct.periph_addr = ((uint32_t)(DAC0_R12DH_ADDRESS)); /* 外設基位址 */
dma_init_struct.periph_addr = ((uint32_t)(&DAC0_R12DH));
dma_init_struct.number = 32; /* 傳輸資料個數 */
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init(DMA1, DMA_CH2, &dma_init_struct);
/* configure DMA mode 存儲器到存儲器DMA傳輸禁能*/
dma_memory_to_memory_disable(DMA1, DMA_CH2);
//DMA循環模式開啟
dma_circulation_enable(DMA1, DMA_CH2);
dma_channel_enable(DMA1, DMA_CH2);
}
/*
brief configure the TIMER peripheral
param[in] none
param[out] none
retval none
*/
static void timer_config(void)
{
timer_parameter_struct timer_initpara;
//Enable TIMER clock
rcu_periph_clock_enable(RCU_TIMER1);
timer_deinit(TIMER1);
/* TIMER configuration */
timer_initpara.prescaler = 0;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 19;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_init(TIMER1, &timer_initpara);
//定時器主輸出觸發源選擇
timer_master_output_trigger_source_select(TIMER1,TIMER_TRI_OUT_SRC_UPDATE);
//定時器更新事件使能
timer_update_event_enable(TIMER1);
/* TIMER enable */
timer_enable(TIMER1);
}
當然也可使用TIMER中斷來更新資料,進而也可實作正弦波,但是會消耗CPU資源,建議使用筆者給出的方式。
13.4實驗現象
13.4.1 DAC普通方式輸出
将程式編譯好後下載下傳到闆子中,通過序列槽助手可以看到在接收區有電壓值輸出。這個和ADC輸入不同,我們使用DAC的目的是通過闆子得到相應的模拟電壓值,看到序列槽的輸出值隻是我們的調試手段,要想确認實驗是否成功,是需要通過電壓表測量PA4的電壓值是否序列槽的輸出一緻。我們設定的步進是400,是以電壓值也是在以400*3.3/4096的電壓步進。
當然啦,還需要萬用表測量引腳電壓即可。你可以使用一個固定值,或者延時更長這樣便于測量。為了更好的測量,筆者将轉換電壓設定為固定值,是以在循環體的前面加了一句話。
da = 2048;
接下來看看實驗結果:
當然也可以使用萬用表測量實際電壓。
13.4.2 DAC正弦波輸出
将程式編譯好後下載下傳到闆子中,通過示波器可看到波形輸出。
這裡測量出的正弦波的頻率是187.42kHz,和計算結果相符。
歡迎通路我的網站
BruceOu的哔哩哔哩
BruceOu的首頁
BruceOu的部落格
BruceOu的簡書