【GD32】从零开始学GD32单片机 | DAC数模转换器 + 三角波输出例程

发布于:2024-12-22 ⋅ 阅读:(14) ⋅ 点赞:(0)

简介

上一篇讲解了ADC的使用,所以这一篇讲DAC的使用,两者其实就是互补的关系,ADC将模拟信号转为数字信号,而DAC将数字信号转为模拟信号。具体的使用上DAC就要比ADC要简单地多。

下面是DAC的结构框图。

在这里插入图片描述

输出缓冲

为了降低输出阻抗,并在没有外部运算放大器的情况下驱动外部负载,DAC内集成了一个输出缓冲区。在默认情况下,输出缓冲区是开启的,可以通过设置状态寄存器寄存器的DBOFFx来开启或者关闭缓冲区。

外部触发

和ADC一样,DAC也支持外部触发,并输出结果。用户可以选择不同的触发源,但大部分都是定时器TRGO的外部触发。

下面是DAC外部触发的对照表。

在这里插入图片描述

数据转换

如果使能了外部触发,当触发事件发生,DAC保持数据寄存器的内容才会被转移到DAC数据输出寄存器。而在外部触发没有使能的情况下,DAC保持数据寄存器内的值会被自动转移到DAC数据输出寄存器。
当DAC保持数据寄存器内的值加载到数据输出寄存器时,经过时间t之后,模拟输出变得有效,t的值与电源电压和模拟输出负载有关。

噪声波

DAC中可以将噪声波叠加到输出数据中,通过这种功能可以方便用户输出一些特殊的波形。GD32的噪声波有两种模式——LFSR噪声模式和三角噪声模式

LSFR噪声模式

在DAC控制逻辑中有一个线性反馈移位寄存器(LFSR)。在LFSR噪声模式下,LFSR 的值与 数据保持寄存器的值相加后,被写入到数据输出寄存器。
当配置的DAC噪声波位宽小于12时,LFSR的值等于LFSR寄存器最低的DWBWx位,DWBWx 位决定了不屏蔽LFSR的哪些位。

在这里插入图片描述

三角噪声模式

该模式顾名思义就是帮助用户产生三角波的。DAC会将一个三角波信号与数据保持寄存器的值相加后,写入到数据输出寄存器。三角波信号的最小值为0,最大值为(2 << DWBWx) - 1

在这里插入图片描述

例程

这个例程配置DAC输出一个三角波,将DAC的输出连接到其中一个ADC输入,读取原始值并通过串口输出到上位机中显示。
使用的软件示波器上位机下载链接:VOFA+

#include "gd32f4xx.h"
#include "systick.h"
#include "debug.h"

#define TAG "main"

int main(void)
{
    systick_config();
	debug_init();
	LOG(TAG, "DAC demo");
    
    /* 初始化ADC */
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_ADC0);
    
    gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_1);  // PA1
    
    adc_deinit();
    adc_clock_config(ADC_ADCCK_PCLK2_DIV6);  // ADC时钟,120MHz / 6 = 20MHz
    adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT);  // 独立模式
    adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);  // 数据右对齐
    adc_resolution_config(ADC0, ADC_RESOLUTION_12B);  // 12位分辨率
    adc_external_trigger_config(ADC0, ADC_ROUTINE_CHANNEL, EXTERNAL_TRIGGER_DISABLE);  // 禁用外部触发
    adc_channel_length_config(ADC0, ADC_ROUTINE_CHANNEL, 1);  // 1个规则通道
    adc_routine_channel_config(ADC0, 0, ADC_CHANNEL_1, ADC_SAMPLETIME_56);  // 转换时间 = (1 / 20MHz) * (12 + 56) = 3.4us
    adc_enable(ADC0);  // 使能ADC
    delay_1ms(1);
    adc_calibration_enable(ADC0);  // 校准ADC
    
    /* 初始化定时器 */
    rcu_periph_clock_enable(RCU_TIMER5);

    timer_parameter_struct timer_conf = {0};

    timer_deinit(TIMER5);
    timer_struct_para_init(&timer_conf);
    timer_conf.prescaler = 59;  // CK_TIMER = 60MHz / (59 + 1) = 1MHz
    timer_conf.alignedmode = TIMER_COUNTER_EDGE;
    timer_conf.counterdirection = TIMER_COUNTER_UP;  // 向上计数
    timer_conf.period = 999;  // 周期 = (1 / 1MHz) * (999 + 1) = 1ms
    timer_init(TIMER5, &timer_conf);
    timer_master_output_trigger_source_select(TIMER5, TIMER_TRI_OUT_SRC_UPDATE);  // 更新事件触发输出
    timer_enable(TIMER5);  // 使能定时器
    
    /* 初始化DAC */
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_DAC);
    
    gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_PIN_4);  // PA4
    
    dac_deinit(DAC0);  // 复位DAC
    dac_trigger_source_config(DAC0, DAC_OUT0, DAC_TRIGGER_T5_TRGO);  // 定时器外部触发
    dac_trigger_enable(DAC0, DAC_OUT0);  // 使能外部触发
    dac_wave_mode_config(DAC0, DAC_OUT0, DAC_WAVE_MODE_TRIANGLE);  // 三角噪声波模式
    dac_triangle_noise_config(DAC0, DAC_OUT0, DAC_TRIANGLE_AMPLITUDE_2047);  // 增益
    dac_enable(DAC0, DAC_OUT0);  // 使能DAC
    dac_data_set(DAC0, DAC_OUT0, DAC_ALIGN_12B_R, 0x7F0);

	while (1) {
        adc_software_trigger_enable(ADC0, ADC_ROUTINE_CHANNEL);
        while (RESET == adc_flag_get(ADC0, ADC_FLAG_EOC));
        printf("adc: %d\r\n", adc_routine_data_read(ADC0));
        delay_1ms(10);
	}
}
  1. 初始化ADC

因为ADC的相关讲解和例程已经在前面的文章中包含,所以这里不再赘述,跳转栏目目录可以找到。这里的ADC配置为通道1,即PA1管脚

  1. 初始化定时器

这个定时器用来触发DAC输出,同样前面的文章中讲过定时器相关的内容,我这里配置1ms触发一次,使用更新事件的外部触发

  1. 初始化DAC

前面我使用的是定时器5,所以先使能DAC的外部触发,触发源为定时器5;使能DAC内部的三角波噪声生成器;增益选择2047,即三角波峰值;设置DAC数据寄存器值为2032,所以三角波最终的输出范围就是2032-4079;输出到通道0,对应PA4管脚。

  1. 主循环

主循环每隔10ms使能ADC采集一次数据,并通过串口发送到上位机。

在这里插入图片描述