GD32F103 DAC1实战:从零构建可调电压输出系统
在嵌入式开发中,模拟信号输出是控制外部设备的重要手段。GD32F103系列微控制器内置的12位DAC模块,为开发者提供了精准的电压输出能力。本文将带你从硬件连接到软件配置,完整实现一个基于PA5引脚的可调电压输出系统,适用于LED调光、电机控制等多种场景。
1. 硬件准备与电路设计
1.1 所需材料清单
- GD32F103开发板(如GD32F103C8T6最小系统板)
- 万用表或示波器(用于测量输出电压)
- LED及限流电阻(220Ω-1kΩ)
- 运算放大器(如LM358,可选)
- 面包板及连接线
1.2 典型连接电路
对于直接驱动LED的应用,推荐以下连接方式:
VDD 3.3V | [R] 220Ω | LED---PA5(DAC1_OUT) | GND当需要驱动更大负载时,可添加运放缓冲电路:
DAC1_OUT(PA5) ---[10kΩ]---+ | [OPAMP]---输出 | GND提示:GD32F103的DAC输出驱动能力有限,直接连接时负载阻抗应大于5kΩ
2. 开发环境搭建
2.1 工具链配置
推荐使用以下开发工具组合:
- IDE: Keil MDK或PlatformIO
- 编译工具链: ARM-GCC
- 调试器: J-Link或ST-Link(兼容模式)
2.2 基础工程创建
- 新建工程,选择GD32F103对应型号
- 添加标准外设库(GD32F10x_Firmware_Library)
- 配置系统时钟为108MHz(默认值)
关键时钟配置代码示例:
void SystemClock_Config(void) { rcu_osci_on(RCU_HXTAL); while(!rcu_osci_stab_wait(RCU_HXTAL)); rcu_pll_config(RCU_PLLSRC_HXTAL, RCU_PLL_MUL_9); rcu_osci_on(RCU_PLL_CK); while(!rcu_osci_stab_wait(RCU_PLL_CK)); rcu_ahb_clock_config(RCU_AHB_CKSYS_DIV1); rcu_apb2_clock_config(RCU_APB2_CKAHB_DIV1); rcu_apb1_clock_config(RCU_APB1_CKAHB_DIV2); rcu_system_clock_source_config(RCU_SCSS_PLL_CK); while(rcu_system_clock_source_get() != RCU_SCSS_PLL_CK); }3. DAC基础配置与电压输出
3.1 DAC模块初始化
GD32F103的DAC1对应PA5引脚,初始化步骤如下:
void DAC1_Init_Basic(void) { // 1. 使能时钟 rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_DAC); // 2. 配置PA5为模拟模式 gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_5); // 3. DAC模块配置 dac_deinit(); dac_trigger_disable(DAC1); // 禁用触发模式 dac_wave_mode_config(DAC1, DAC_WAVE_DISABLE); dac_output_buffer_enable(DAC1); // 启用输出缓冲 // 4. 使能DAC1 dac_enable(DAC1); }3.2 输出电压控制
设置输出电压的三种方法对比:
| 方法 | 精度 | 响应速度 | 适用场景 |
|---|---|---|---|
| 直接写入 | 最高 | 即时 | 静态电压设置 |
| 定时器触发 | 高 | 可调 | 周期性变化 |
| DMA传输 | 高 | 最快 | 复杂波形生成 |
基础输出电压设置函数:
void Set_DAC_Voltage(float voltage) { if(voltage > 3.3f) voltage = 3.3f; if(voltage < 0.0f) voltage = 0.0f; uint16_t dac_value = (uint16_t)(voltage * 4095 / 3.3f); dac_data_set(DAC1, DAC_ALIGN_12B_R, dac_value); }4. 进阶应用:定时器触发动态调压
4.1 定时器配置
使用TIMER1作为DAC触发源:
void TIMER1_Config(uint32_t freq_hz) { timer_parameter_struct timer_initpara; rcu_periph_clock_enable(RCU_TIMER1); // 计算ARR和PSC值 uint32_t timer_clock = 108000000; // APB2时钟 uint32_t prescaler = (timer_clock / freq_hz / 65536) + 1; uint32_t period = (timer_clock / prescaler / freq_hz) - 1; timer_initpara.prescaler = prescaler - 1; timer_initpara.period = period; timer_initpara.alignedmode = TIMER_COUNTER_EDGE; timer_initpara.clockdivision = TIMER_CKDIV_DIV1; timer_initpara.counterdirection = TIMER_COUNTER_UP; timer_init(TIMER1, &timer_initpara); timer_master_output_trigger_source_select(TIMER1, TIMER_TRI_OUT_SRC_UPDATE); timer_enable(TIMER1); }4.2 触发模式DAC配置
修改DAC初始化以支持定时器触发:
void DAC1_Init_Timer_Trigger(void) { DAC1_Init_Basic(); dac_trigger_source_config(DAC1, DAC_TRIGGER_T1_TRGO); dac_trigger_enable(DAC1); dac_output_buffer_disable(DAC1); // 触发模式下建议禁用缓冲 }4.3 动态调压实现
创建正弦波输出示例:
#define WAVE_SAMPLES 32 uint16_t sine_wave[WAVE_SAMPLES]; void Generate_Sine_Wave(void) { for(int i=0; i<WAVE_SAMPLES; i++) { float angle = 2 * 3.1415926f * i / WAVE_SAMPLES; float voltage = 1.65f + 1.65f * sinf(angle); // 0-3.3V范围 sine_wave[i] = (uint16_t)(voltage * 4095 / 3.3f); } } void DMA_Config(void) { dma_parameter_struct dma_init_struct; rcu_periph_clock_enable(RCU_DMA1); dma_deinit(DMA1, DMA_CH3); dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; dma_init_struct.memory_addr = (uint32_t)sine_wave; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT; dma_init_struct.number = WAVE_SAMPLES; dma_init_struct.periph_addr = (uint32_t)&DAC1_R12DH; dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; dma_init_struct.priority = DMA_PRIORITY_HIGH; dma_init(DMA1, DMA_CH3, &dma_init_struct); dma_circulation_enable(DMA1, DMA_CH3); dma_channel_enable(DMA1, DMA_CH3); dac_dma_enable(DAC1); }5. 实际应用案例:LED调光控制
5.1 呼吸灯实现
利用PWM原理通过DAC模拟实现:
void Breathing_LED(void) { uint16_t brightness = 0; int8_t direction = 1; while(1) { Set_DAC_Voltage(brightness * 3.3f / 4095.0f); delay_ms(10); brightness += direction * 10; if(brightness >= 4095) direction = -1; if(brightness <= 0) direction = 1; } }5.2 多级亮度控制
创建亮度预设表:
const float brightness_levels[] = { 0.0f, // 关闭 0.5f, // 25% 1.0f, // 50% 1.65f, // 75% 2.2f, // 90% 3.3f // 100% }; void Set_Brightness(uint8_t level) { if(level >= sizeof(brightness_levels)/sizeof(float)) level = sizeof(brightness_levels)/sizeof(float) - 1; Set_DAC_Voltage(brightness_levels[level]); }6. 调试技巧与性能优化
6.1 常见问题排查
- 无输出:检查PA5模式是否为GPIO_MODE_AIN
- 电压不准:测量VREF+引脚电压(应为3.3V)
- 响应延迟:降低定时器周期或使用直接写入模式
6.2 性能优化建议
- 对于静态电压输出,禁用触发和DMA以减少开销
- 需要快速更新时,使用DMA而非定时器中断
- 输出缓冲启用可改善驱动能力但增加功耗
测量代码执行时间的实用方法:
void Measure_Update_Time(void) { uint32_t start, end; start = DWT->CYCCNT; Set_DAC_Voltage(1.65f); end = DWT->CYCCNT; printf("Update time: %f us\r\n", (end-start)/108.0f); }在项目开发中,DAC模块的灵活运用可以大大简化模拟电路设计。特别是在需要精确控制电压而非PWM占空比的场景下,DAC方案能提供更线性的响应特性。