简介
上一篇讲解了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);
}
}
- 初始化ADC
因为ADC的相关讲解和例程已经在前面的文章中包含,所以这里不再赘述,跳转栏目目录可以找到。这里的ADC配置为通道1,即PA1管脚。
- 初始化定时器
这个定时器用来触发DAC输出,同样前面的文章中讲过定时器相关的内容,我这里配置1ms触发一次,使用更新事件的外部触发。
- 初始化DAC
前面我使用的是定时器5,所以先使能DAC的外部触发,触发源为定时器5;使能DAC内部的三角波噪声生成器;增益选择2047,即三角波峰值;设置DAC数据寄存器值为2032,所以三角波最终的输出范围就是2032-4079;输出到通道0,对应PA4管脚。
- 主循环
主循环每隔10ms使能ADC采集一次数据,并通过串口发送到上位机。