天天看點

《嵌入式 – GD32開發實戰指南》第13章 DAC

開發環境:

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+ 以獲得更精确的轉換結果。

《嵌入式 – GD32開發實戰指南》第13章 DAC

13.1.2 DAC主要特征

● 2個DAC轉換器:每個轉換器對應1個輸出通道

● 8位或者12位單調輸出

● 12位模式下資料左對齊或者右對齊

● 同步更新功能

● 噪聲波形生成或三角波形生成

● 雙DAC通道同時或者分别轉換

● 每個通道都有DMA功能

● 外部觸發轉換

● 輸入參考電壓VREF+

《嵌入式 – GD32開發實戰指南》第13章 DAC

【注意】一旦使能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寄存器。

《嵌入式 – GD32開發實戰指南》第13章 DAC
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 位來進行選擇。

《嵌入式 – GD32開發實戰指南》第13章 DAC

TIMERx_TRGO 信号是由定時器生成的,而軟體觸發是通過設定 DAC_SWT 寄存器的 SWTRx位生成的。

13.2 DAC寄存器描述

我們介紹一下要實作 DAC 輸出,需要用到的一些寄存器。首先是 DAC控制寄存器DAC_CTL,該寄存器的各位描述如下圖所示。

《嵌入式 – GD32開發實戰指南》第13章 DAC

DAC_CTL的低 16 位用于控制通道0,而高 16 位用于控制通道 1,我們這裡僅列出比較重要的最低 8 位的較長的描述。

《嵌入式 – GD32開發實戰指南》第13章 DAC

首先,我們來看 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,該寄存器各位描述如下圖所示。

《嵌入式 – GD32開發實戰指南》第13章 DAC

該寄存器用來設定 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的電壓步進。

《嵌入式 – GD32開發實戰指南》第13章 DAC

當然啦,還需要萬用表測量引腳電壓即可。你可以使用一個固定值,或者延時更長這樣便于測量。為了更好的測量,筆者将轉換電壓設定為固定值,是以在循環體的前面加了一句話。

da = 2048;

接下來看看實驗結果:

《嵌入式 – GD32開發實戰指南》第13章 DAC

當然也可以使用萬用表測量實際電壓。

13.4.2 DAC正弦波輸出

将程式編譯好後下載下傳到闆子中,通過示波器可看到波形輸出。

《嵌入式 – GD32開發實戰指南》第13章 DAC

這裡測量出的正弦波的頻率是187.42kHz,和計算結果相符。

歡迎通路我的網站

BruceOu的哔哩哔哩

BruceOu的首頁

BruceOu的部落格

BruceOu的簡書