别再只会点灯了!用STM32的DAC做个简易信号发生器(基于STM32F103C8T6)

发布时间:2026/6/30 14:10:55
别再只会点灯了!用STM32的DAC做个简易信号发生器(基于STM32F103C8T6) 从零打造你的迷你信号发生器STM32 DAC实战指南引言还记得第一次点亮LED时的兴奋吗对于大多数STM32初学者来说GPIO控制可能是迈入嵌入式世界的第一步。但当我们掌握了数字信号的控制后如何让微控制器与模拟世界对话就成了下一个值得探索的领域。这正是数字模拟转换器(DAC)的用武之地——它能让我们的代码产生真实的电压信号打开测量控制、音频合成等全新可能。本文将带你用STM32F103C8T6的DAC功能制作一个可调的信号发生器。不同于简单的寄存器配置教程我们会从实际应用出发通过这个项目你将学到如何让MCU输出精确的直流电压通过查表法生成正弦波等基础波形结合电位器和串口实现实时调节用万用表或简易示波器验证输出效果这个口袋信号发生器虽然简单但已经可以用于传感器校准、电路测试等实用场景。让我们暂时放下闪烁的LED一起探索STM32的模拟输出能力吧1. 硬件准备与基础配置1.1 所需材料清单在开始编码前请确保准备好以下硬件组件规格数量备注开发板STM32F103C8T61蓝色小板最常见电位器10kΩ线性1用于电压调节电阻1kΩ2限流保护电容0.1μF2滤波稳定输出杜邦线公对公若干建议使用不同颜色万用表数字式1测量输出电压示波器简易型1可选观察波形1.2 DAC引脚配置原理STM32F103C8T6只有一个DAC通道(DAC1)对应引脚PA4。配置时需特别注意模拟输入模式在使能DAC前必须将PA4设置为模拟输入(AIN)参考电压DAC输出范围取决于VREF引脚电压通常接3.3V输出缓冲建议禁用输出缓冲以获得更低的输出电压初始化代码示例void DAC_Init(void) { RCC-APB2ENR | RCC_APB2ENR_IOPAEN; // 使能GPIOA时钟 GPIOA-CRL ~GPIO_CRL_CNF4; // 清除PA4配置位 GPIOA-CRL | GPIO_CRL_CNF4_0; // 模拟输入模式(01) RCC-APB1ENR | RCC_APB1ENR_DACEN; // 使能DAC时钟 DAC-CR ~DAC_CR_BOFF1; // 禁用输出缓冲 DAC-CR | DAC_CR_EN1; // 最后使能DAC通道 }注意DAC使能后PA4将自动连接到DAC输出此时不应再将其配置为其他功能。2. 直流电压输出实现2.1 基础电压输出DAC的核心功能是将数字值转换为模拟电压。对于12位DAC输出电压计算公式为Vout Vref × (DOR / 4096)其中Vref通常为3.3VDOR是我们写入的12位数字值(0-4095)。实现固定电压输出的函数如下void DAC_SetVoltage(float voltage) { if(voltage 3.3f) voltage 3.3f; // 限制最大电压 uint16_t value (uint16_t)(voltage * 4095 / 3.3f); DAC-DHR12R1 value; // 写入12位右对齐数据保持寄存器 }2.2 电位器实时调节让电压可调能大幅提升实用性。通过ADC读取电位器位置再转换为DAC输出值形成一个闭环void ADC_Init(void) { // 初始化ADC1通道1(PA1)的代码... } void Voltage_Control_Loop(void) { ADC_Init(); DAC_Init(); while(1) { uint16_t adc_val ADC_Read(); // 假设已实现ADC读取 float voltage adc_val * 3.3f / 4095; DAC_SetVoltage(voltage); HAL_Delay(10); // 10ms更新周期 } }这种设计可以制作成一个简易的可编程电压源精度约0.8mV(3.3V/4096)。3. 波形生成技术3.1 正弦波查表法要生成周期性波形查表法是最简单高效的实现方式。首先我们需要一个正弦波样本表#define SINE_TABLE_SIZE 64 const uint16_t sine_table[SINE_TABLE_SIZE] { 2048, 2148, 2248, 2348, 2447, 2545, 2642, 2737, // ...完整表格应有64个点覆盖sin(0)到sin(2π) 2831, 2923, 3012, 3098, 3181, 3260, 3335, 3406, 3473, 3535, 3592, 3644, 3691, 3732, 3768, 3798, 3822, 3840, 3852, 3858, 3858, 3852, 3840, 3822, 3798, 3768, 3732, 3691, 3644, 3592, 3535, 3473, 3406, 3335, 3260, 3181, 3098, 3012, 2923, 2831, 2737, 2642, 2545, 2447, 2348, 2248, 2148, 2048 };波形生成函数通过定时器中断定期更新DAC值void TIM2_IRQHandler(void) { static uint8_t index 0; if(TIM2-SR TIM_SR_UIF) { TIM2-SR ~TIM_SR_UIF; // 清除中断标志 DAC-DHR12R1 sine_table[index]; index (index 1) % SINE_TABLE_SIZE; } }3.2 频率控制技巧波形频率由两个因素决定查表点数(SINE_TABLE_SIZE)定时器更新频率频率计算公式f_wave f_timer / SINE_TABLE_SIZE例如要生成1kHz正弦波使用64点表格则定时器中断频率应为64kHz。配置定时器2的代码void TIM2_Init(uint32_t frequency) { RCC-APB1ENR | RCC_APB1ENR_TIM2EN; // 使能TIM2时钟 // 假设系统时钟72MHz预分频设为0 uint32_t arr (72000000 / frequency) - 1; TIM2-ARR arr; // 自动重装载值 TIM2-PSC 0; // 预分频器 TIM2-DIER | TIM_DIER_UIE; // 使能更新中断 NVIC_EnableIRQ(TIM2_IRQn); // 使能NVIC中断 TIM2-CR1 | TIM_CR1_CEN; // 启动定时器 }4. 系统优化与扩展4.1 输出滤波设计DAC输出的阶梯状波形需要低通滤波平滑。一个简单的RC滤波器就能显著改善波形质量PA4 ---[1kΩ]------ 输出 | [0.1μF] | GND截止频率计算f_c 1 / (2πRC) ≈ 1.6kHz对于音频范围信号(20Hz-20kHz)可考虑二阶有源滤波器以获得更陡峭的滚降特性。4.2 串口控制接口通过串口接收指令可以增强信号发生器的灵活性。例如定义简单协议FREQ 1000 // 设置频率为1kHz AMP 1.5 // 设置幅度为1.5V WAVE SIN // 正弦波 WAVE SQU // 方波实现框架void USART1_IRQHandler(void) { if(USART1-SR USART_SR_RXNE) { char c USART1-DR; // 解析命令... } } void Process_Command(char* cmd) { if(strncmp(cmd, FREQ, 4) 0) { uint32_t freq atoi(cmd 5); TIM2_Init(freq * SINE_TABLE_SIZE); } // 其他命令处理... }4.3 多波形支持扩展前面的查表法我们可以支持多种波形。只需定义不同的波形表// 方波表(50%占空比) const uint16_t square_table[] { [0 ... 31] 4095, // 前32点为高电平 [32 ... 63] 0 // 后32点为低电平 }; // 三角波表 const uint16_t triangle_table[SINE_TABLE_SIZE] { // 线性递增然后递减的值... }; enum WaveType {SINE, SQUARE, TRIANGLE}; enum WaveType current_wave SINE; void TIM2_IRQHandler(void) { static uint8_t index 0; if(TIM2-SR TIM_SR_UIF) { TIM2-SR ~TIM_SR_UIF; switch(current_wave) { case SINE: DAC-DHR12R1 sine_table[index]; break; case SQUARE: DAC-DHR12R1 square_table[index]; break; // 其他波形... } index (index 1) % SINE_TABLE_SIZE; } }5. 实际应用与问题排查5.1 典型应用场景这个简易信号发生器虽然精度有限但已经可以胜任多种场景传感器校准为压力、温度传感器提供已知输入电路测试作为放大器、滤波器的输入信号源教育演示直观展示波形参数与电路响应的关系音频实验生成可听频率的正弦波测试扬声器5.2 常见问题与解决输出不稳定或噪声大检查电源滤波在VDD和GND间添加0.1μF去耦电容确保PA4配置为模拟输入且未复用其他功能尝试启用DAC输出缓冲(设置BOFFx1)波形失真严重增加查表点数(如从64点到128点)降低输出频率确保每个周期有足够采样点检查RC滤波器参数是否合适无法达到0V或满量程禁用输出缓冲(设置BOFFx0)以获得更宽输出范围检查VREF电压是否稳定确认DAC_DOR寄存器值是否正确变化定时器中断不触发确认定时器时钟已使能检查NVIC中断是否配置正确验证自动重装载值(ARR)计算是否正确6. 进阶方向当掌握了基础功能后可以考虑以下扩展DMA传输用DMA自动更新DAC值减轻CPU负担双通道输出在支持双DAC的型号上实现立体声输出任意波形通过串口或SD卡加载自定义波形数据频率扫描实现自动扫频功能用于频响分析LCD界面添加显示屏直观设置参数使用DMA的示例配置void DAC_DMA_Init(void) { // 启用DMA1时钟 RCC-AHBENR | RCC_AHBENR_DMA1EN; // 配置DMA1通道3(DAC通道1) DMA1_Channel3-CPAR (uint32_t)DAC-DHR12R1; DMA1_Channel3-CMAR (uint32_t)sine_table; DMA1_Channel3-CNDTR SINE_TABLE_SIZE; // 配置循环模式、内存递增等 DMA1_Channel3-CCR DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_EN; // 启用DAC的DMA请求 DAC-CR | DAC_CR_DMAEN1; }这个项目最让我惊喜的是仅用不到1美元的STM32F103C8T6就能实现传统上需要专用芯片的功能。在实际测试中配合适当的滤波电路输出波形质量完全可以满足一般实验需求。当第一次看到示波器上干净的正弦波时那种成就感远胜过简单的点灯实验。