用STM32F103的DAC做个简易信号发生器:从配置到波形输出(标准库版)

用STM32F103的DAC做个简易信号发生器:从配置到波形输出(标准库版) 用STM32F103的DAC打造多功能信号发生器从基础配置到高级波形合成在嵌入式开发领域信号发生器是一个既经典又实用的项目。对于已经掌握STM32基础知识的开发者来说利用STM32F103内置的DAC数字模拟转换器构建一个简易信号发生器不仅能巩固DAC和ADC的使用技巧还能深入理解波形生成的数学原理和实现方法。本文将带你从零开始使用标准库函数开发一个功能完整的信号发生器支持正弦波、方波、三角波等常见波形输出并通过ADC回采验证波形质量。1. 硬件基础与项目规划STM32F103ZET6微控制器内置两个12位精度的DAC模块每个DAC有一个独立的输出通道DAC通道1对应PA4引脚DAC通道2对应PA5引脚关键硬件参数参考电压Vref通常接3.3V12位分辨率对应4096个量化等级输出电压范围0V~Vref输出电压计算公式Vout Vref × (DORx / 4095)在规划信号发生器项目时我们需要考虑以下几个核心功能模块波形生成模块负责产生不同波形的数字样本DAC输出模块将数字样本转换为模拟信号用户交互模块通过按键或串口切换波形类型和调整参数反馈验证模块通过ADC回采输出信号验证波形质量2. DAC基础配置与电压输出首先我们需要完成DAC的基础配置这是信号发生器项目的核心。STM32标准库提供了简洁的API来配置DAC模块。2.1 DAC初始化代码实现void DAC_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; DAC_InitTypeDef DAC_InitStructure; // 使能GPIOA和DAC时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); // 配置PA4为模拟输入(DAC_OUT1) GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); // DAC通道1配置 DAC_InitStructure.DAC_Trigger DAC_Trigger_None; // 不使用硬件触发 DAC_InitStructure.DAC_WaveGeneration DAC_WaveGeneration_None; // 禁用内置波形生成 DAC_InitStructure.DAC_OutputBuffer DAC_OutputBuffer_Disable; // 禁用输出缓冲 DAC_Init(DAC_Channel_1, DAC_InitStructure); // 使能DAC通道1 DAC_Cmd(DAC_Channel_1, ENABLE); }2.2 电压输出函数封装为了方便控制输出电压我们可以封装一个设置电压的函数void DAC_SetVoltage(uint16_t millivolts) { // 确保电压值在有效范围内(0-3300mV) millivolts (millivolts 3300) ? 3300 : millivolts; // 计算DAC值DAC_Value (millivolts / 3300) * 4095 uint16_t dacValue (uint32_t)millivolts * 4095 / 3300; // 设置DAC输出 DAC_SetChannel1Data(DAC_Align_12b_R, dacValue); }注意DAC输出电压精度受参考电压稳定性和PCB布线影响在高精度应用中建议使用外部精密参考电压源。3. 波形生成原理与实现信号发生器的核心是波形生成算法。不同的波形有不同的数学表达和实现方法。3.1 正弦波生成正弦波是最基本的连续波形可以通过查表法或实时计算法生成。查表法实现步骤预先计算一个周期正弦波的采样值将采样值存储在数组中定时从数组中取出值送入DAC#define SINE_TABLE_SIZE 256 uint16_t sineTable[SINE_TABLE_SIZE]; void GenerateSineTable(void) { for(int i0; iSINE_TABLE_SIZE; i) { // 生成0-3.3V范围的正弦波 float value 1.65f 1.65f * sin(2 * PI * i / SINE_TABLE_SIZE); sineTable[i] (uint16_t)(value * 4095 / 3.3f); } } void OutputSineWave(uint32_t frequency) { static uint32_t phase 0; uint32_t step (SINE_TABLE_SIZE * frequency) / 1000; // 假设定时器频率为1kHz DAC_SetChannel1Data(DAC_Align_12b_R, sineTable[phase % SINE_TABLE_SIZE]); phase step; }3.2 方波与三角波生成方波和三角波的生成相对简单可以直接通过算法实现。方波生成函数void OutputSquareWave(uint32_t frequency, uint16_t amplitude_mV) { static uint32_t counter 0; uint32_t halfPeriod 1000 / (2 * frequency); // 毫秒转换为计数器值 if(counter halfPeriod) { DAC_SetVoltage(amplitude_mV); // 高电平 } else { DAC_SetVoltage(0); // 低电平 } counter (counter 1) % (2 * halfPeriod); }三角波生成函数void OutputTriangleWave(uint32_t frequency, uint16_t amplitude_mV) { static uint32_t counter 0; static int16_t direction 1; static uint16_t currentVoltage 0; uint32_t step (amplitude_mV * 2 * frequency) / 1000; currentVoltage direction * step; if(currentVoltage amplitude_mV) { currentVoltage amplitude_mV; direction -1; } else if(currentVoltage 0) { currentVoltage 0; direction 1; } DAC_SetVoltage(currentVoltage); }4. 系统集成与性能优化完整的信号发生器需要整合波形生成、用户交互和反馈验证功能。4.1 主程序框架设计typedef enum { WAVE_SINE, WAVE_SQUARE, WAVE_TRIANGLE, WAVE_DC } WaveType; WaveType currentWave WAVE_SINE; uint32_t frequency 100; // 默认100Hz uint16_t amplitude 2500; // 默认2.5V int main(void) { // 硬件初始化 SystemInit(); DAC_Config(); GenerateSineTable(); Timer_Config(); // 配置定时器用于波形更新 UART_Config(115200); // 配置串口 Button_Config(); // 配置按键 while(1) { // 处理用户输入 HandleUserInput(); // 根据当前波形类型输出 switch(currentWave) { case WAVE_SINE: OutputSineWave(frequency); break; case WAVE_SQUARE: OutputSquareWave(frequency, amplitude); break; case WAVE_TRIANGLE: OutputTriangleWave(frequency, amplitude); break; case WAVE_DC: DAC_SetVoltage(amplitude); break; } // 定期通过ADC检测输出 if(timerFlag) { timerFlag 0; uint16_t adcValue ReadADC(); float measuredVoltage adcValue * 3.3f / 4095; UART_SendVoltage(measuredVoltage); } } }4.2 性能优化技巧使用DMA减轻CPU负担// 配置DMA将波形数据自动传输到DAC DAC_DMACmd(DAC_Channel_1, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr DAC_DHR12R1_Address; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)sineTable; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize SINE_TABLE_SIZE; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_Init(DMA1_Channel3, DMA_InitStructure); DMA_Cmd(DMA1_Channel3, ENABLE);定时器触发实现精确时序// 配置定时器触发DAC更新 DAC_InitStructure.DAC_Trigger DAC_Trigger_T2_TRGO; DAC_Init(DAC_Channel_1, DAC_InitStructure); // 配置TIM2产生所需频率的触发信号 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period SystemCoreClock / (SINE_TABLE_SIZE * frequency) - 1; TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update); TIM_Cmd(TIM2, ENABLE);输出缓冲与滤波在DAC输出端添加运算放大器缓冲电路根据输出频率范围设计合适的低通滤波器使用屏蔽线减少高频噪声干扰5. 扩展功能与进阶应用基础波形生成实现后可以考虑添加更多实用功能提升项目的实用价值。5.1 频率与幅度调节通过旋转编码器或电位器实现频率和幅度的连续调节void HandleEncoderInput(void) { static int16_t lastCount 0; int16_t currentCount ReadEncoder(); if(currentCount ! lastCount) { int16_t delta currentCount - lastCount; if(adjustingFrequency) { frequency delta * 10; // 每步10Hz frequency CLAMP(frequency, 1, 10000); // 限制1Hz-10kHz } else { amplitude delta * 100; // 每步100mV amplitude CLAMP(amplitude, 0, 3300); // 限制0-3.3V } lastCount currentCount; UpdateDisplay(); } }5.2 波形叠加与调制实现波形叠加和简单的调制功能可以大大扩展信号发生器的应用场景// AM调制实现 void OutputAMWave(uint32_t carrierFreq, uint32_t modFreq, float modDepth) { static uint32_t carrierPhase 0; static uint32_t modPhase 0; // 载波 float carrier sin(2 * PI * carrierPhase / SINE_TABLE_SIZE); // 调制信号 float modulation 1.0f - modDepth * (1.0f sin(2 * PI * modPhase / SINE_TABLE_SIZE)) / 2.0f; // 调制后的输出 float output 1.65f 1.65f * carrier * modulation; uint16_t dacValue (uint16_t)(output * 4095 / 3.3f); DAC_SetChannel1Data(DAC_Align_12b_R, dacValue); // 更新相位 carrierPhase (SINE_TABLE_SIZE * carrierFreq) / 1000; modPhase (SINE_TABLE_SIZE * modFreq) / 1000; }5.3 任意波形生成通过串口或SD卡导入自定义波形数据实现任意波形生成#define ARB_TABLE_SIZE 1024 uint16_t arbTable[ARB_TABLE_SIZE]; void LoadWaveformFromSDCard(void) { FIL file; UINT bytesRead; if(f_open(file, waveform.bin, FA_READ) FR_OK) { f_read(file, arbTable, sizeof(arbTable), bytesRead); f_close(file); } } void OutputArbitraryWave(uint32_t frequency) { static uint32_t phase 0; uint32_t step (ARB_TABLE_SIZE * frequency) / 1000; DAC_SetChannel1Data(DAC_Align_12b_R, arbTable[phase % ARB_TABLE_SIZE]); phase step; }在实际项目中我发现使用DMA配合定时器触发可以显著提高波形输出的稳定性和精确度特别是在生成高频信号时。通过合理设置DMA缓冲区和定时器频率可以实现高达100kHz的信号输出同时保持极低的CPU占用率。