0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

GD32开发实战指南(基础篇) 第13章 DAC

嵌入式大杂烩 来源:嵌入式大杂烩 作者:嵌入式大杂烩 2023-05-17 08:57 次阅读

开发环境:

MDK:Keil 5.30

开发板:GD32F207I-EVAL

MCU:GD32F207IK

1 DAC工作原理

1.1 DAC介绍

数字/模拟转换模块(DAC)是12位数字输入,电压输出的数字/模拟转换器。DAC可以配置为8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位模式时,数据可以设置成左对齐或右对齐。DAC模块有2个输出通道,每个通道都有单独的转换器。在双DAC模式下,2个通道可以独立地进行转换,也可以同时进行转换并同步地更新2个通道的输出。DAC可以通过引脚输入参考电压VREF+ 以获得更精确的转换结果。

1684244752692x28i72f2rj

1.2 DAC主要特征

● 2个DAC转换器:每个转换器对应1个输出通道

● 8位或者12位单调输出

● 12位模式下数据左对齐或者右对齐

● 同步更新功能

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

● 双DAC通道同时或者分别转换

● 每个通道都有DMA功能

● 外部触发转换

● 输入参考电压VREF+

1684244753144zltvtaa191

【注意】一旦使能DACx通道,相应的GPIO引脚(PA4或者PA5)就会自动与DAC的模拟输出相连(DAC_OUTx)。为了避免寄生的干扰和额外的功耗,引脚PA4或者PA5在之前应当设置成模拟输入(AIN)。

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寄存器。

16842447535406xg4vy5rcl

  • 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 位来进行选择。

1684244753868wt857usmh9

TIMERx_TRGO 信号是由定时器生成的,而软件触发是通过设置 DAC_SWT 寄存器的 SWTRx位生成的。

2 DAC寄存器描述

我们介绍一下要实现 DAC 输出,需要用到的一些寄存器。首先是 DAC控制寄存器DAC_CTL,该寄存器的各位描述如下图所示。

1684244754259px134xmere

DAC_CTL的低 16 位用于控制通道0,而高 16 位用于控制通道 1,我们这里仅列出比较重要的最低 8 位的详细描述。

16842447546331jhmc2yugx

首先,我们来看 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,该寄存器各位描述如下图所示。

16842447551314p74t5int6

该寄存器用来设置 DAC 输出,通过写入 12 位数据到该寄存器,就可以在 DAC 输出通道0得到我们所要的结果。

3 DAC应用代码实现

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);

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资源,建议使用笔者给出的方式。

4 实验现象

4.1 DAC普通方式输出

将程序编译好后下载到板子中,通过串口助手可以看到在接收区有电压值输出。这个和ADC输入不同,我们使用DAC的目的是通过板子得到相应的模拟电压值,看到串口的输出值只是我们的调试手段,要想确认实验是否成功,是需要通过电压表测量PA4的电压值是否串口的输出一致。我们设置的步进是400,因此电压值也是在以400*3.3/4096的电压步进。

1684244755534dnbw6gad0l

当然啦,还需要万用表测量引脚电压即可。你可以使用一个固定值,或者延时更长这样便于测量。为了更好的测量,笔者将转换电压设置为固定值,因此在循环体的前面加了一句话。

da = 2048;

接下来看看实验结果:

1684244755998peppujpozi

当然也可以使用万用表测量实际电压。

4.2 DAC正弦波输出

将程序编译好后下载到板子中,通过示波器可看到波形输出。

C:\\Users\\BruceOu\\Desktop\\scope_9.png

这里测量出的正弦波的频率是187.42kHz,和计算结果相符。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • mcu
    mcu
    +关注

    关注

    146

    文章

    16022

    浏览量

    343694
  • dac
    dac
    +关注

    关注

    43

    文章

    1973

    浏览量

    189547
  • 开发板
    +关注

    关注

    25

    文章

    4437

    浏览量

    94084
  • 模拟转换器
    +关注

    关注

    0

    文章

    38

    浏览量

    12642
  • GD32
    +关注

    关注

    7

    文章

    333

    浏览量

    23748
收藏 人收藏

    评论

    相关推荐

    GD32开发实战指南(基础篇) 第1章 开发环境搭建

    开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 GD32F207I-EVAL
    的头像 发表于 05-07 23:35 9035次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础篇) 第1章 <b class='flag-5'>开发</b>环境搭建

    GD32开发实战指南(基础篇) 第8章 定时器

    开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 PWM输出的工作原理 脉冲宽度调制(PWM) ,是英文“Pulse Wi
    的头像 发表于 05-12 22:14 6358次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础篇) 第8章 定时器

    GD32开发实战指南(基础篇) 第14章 内部温度传感器

    GD32 有一个内部的温度传感器,可以用来测量 CPU 及周围的温度(TA)。该温度传感器在内部和 ADCx_IN16 输入通道相连接,此通道把传感器输出的电压转换成数字值。温度传感器模拟输入
    的头像 发表于 05-17 08:58 4041次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础篇) 第14章 内部温度传感器

    GD32开发实战指南(基础篇) 第16章 RTC

    开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 RTC工作原理 1.1 RTC简介
    的头像 发表于 05-18 22:14 5757次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础篇) 第16章 RTC

    【图书分享】《STM32库开发实战指南

    ,可作为高校电子信息、通信工程、信息工程等相关专业的教材,也适合作为从事嵌入式领域科技工作者的参考书。目录前言第一部分 库开发初级 1 为什么学习STM32 
    发表于 03-13 17:01

    Arduino开发实战指南 AVR

    第一基础1初识Arduino2编写Arduino程序
    发表于 08-03 16:14

    Arduino开发实战指南 AVR

    Arduino开发实战指南AVR
    发表于 04-04 12:20

    GD32和STM32有哪些不同的地方

    一、前言GD32是国内开发的一款单片机,据说开发的人员是来自ST公司的,GD32也是以STM32作为模板做出来的。所以GD32和STM32有
    发表于 08-09 07:03

    什么是GD32

    一、前言什么GD32GD32是国内开发的一款单片机,据说开发的人员是来自ST公司的,GD32也是以STM32作为模板做出来的。所以
    发表于 08-12 07:46

    GD32 MCU学习这本书的理由

    首先,学习GD32 MCU原理固件库开发指南可以让我了解这个芯片的内部结构和工作原理,从而深入理解如何在嵌入式系统中使用它。其次,固件库是嵌入式系统中必不可少的软件组件之一,它提供了基本的底层
    发表于 03-10 20:06

    GD32 MCU原理及固件库开发指南》 + 初读感悟

    GD32 MCU原理固件库开发指南这本书内容丰富,囊括了GD32中的所有外设,书中首先介绍了如何使用MDK或IAR软件搭建GD32工程环境,让初学者能快速基于工程上手编程。书中主要对
    发表于 03-31 22:11

    GD32 MCU原理及固件库开发指南》+读后感

    2介绍GD32 MCU快速入门与开发平台搭建的方法,包括对软硬件开发平台、调试工具、GD32
    发表于 06-06 21:52

    GD32和STM32的区别

    一、前言GD32是国内开发的一款单片机,据说开发的人员是来自ST公司的,GD32也是以STM32作为模板做出来的。所以GD32和STM32有
    发表于 11-18 20:51 46次下载
    <b class='flag-5'>GD32</b>和STM32的区别

    GD32移植到STM32开发平台

    GD32移植到STM32开发平台
    发表于 12-02 14:51 27次下载
    <b class='flag-5'>GD32</b>移植到STM32<b class='flag-5'>开发</b>平台

    GD32开发实战指南(基础篇) 第19章 程序加密

    GD32通过读取芯片唯一ID号来实现程序的保护,防止被抄袭。96位的产品唯一身份标识所提供的参考号码对任意一个GD32微控制器
    的头像 发表于 05-20 09:10 3396次阅读
    <b class='flag-5'>GD32</b><b class='flag-5'>开发</b><b class='flag-5'>实战</b><b class='flag-5'>指南</b>(基础篇) 第19章 程序加密