用STM32F103的DAC做个简易信号发生器:从配置到波形输出(标准库版)
2026/6/4 4:49:55 网站建设 项目流程

用STM32F103的DAC打造多功能信号发生器:从基础配置到高级波形合成

在嵌入式开发领域,信号发生器是一个既经典又实用的项目。对于已经掌握STM32基础知识的开发者来说,利用STM32F103内置的DAC(数字模拟转换器)构建一个简易信号发生器,不仅能巩固DAC和ADC的使用技巧,还能深入理解波形生成的数学原理和实现方法。本文将带你从零开始,使用标准库函数开发一个功能完整的信号发生器,支持正弦波、方波、三角波等常见波形输出,并通过ADC回采验证波形质量。

1. 硬件基础与项目规划

STM32F103ZET6微控制器内置两个12位精度的DAC模块,每个DAC有一个独立的输出通道:

  • DAC通道1对应PA4引脚
  • DAC通道2对应PA5引脚

关键硬件参数

  • 参考电压Vref+通常接3.3V
  • 12位分辨率对应4096个量化等级
  • 输出电压范围:0V~Vref+
  • 输出电压计算公式:Vout = Vref × (DORx / 4095)

在规划信号发生器项目时,我们需要考虑以下几个核心功能模块:

  1. 波形生成模块:负责产生不同波形的数字样本
  2. DAC输出模块:将数字样本转换为模拟信号
  3. 用户交互模块:通过按键或串口切换波形类型和调整参数
  4. 反馈验证模块:通过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 正弦波生成

正弦波是最基本的连续波形,可以通过查表法或实时计算法生成。

查表法实现步骤

  1. 预先计算一个周期正弦波的采样值
  2. 将采样值存储在数组中
  3. 定时从数组中取出值送入DAC
#define SINE_TABLE_SIZE 256 uint16_t sineTable[SINE_TABLE_SIZE]; void GenerateSineTable(void) { for(int i=0; i<SINE_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 性能优化技巧

  1. 使用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);
  2. 定时器触发实现精确时序

    // 配置定时器触发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);
  3. 输出缓冲与滤波

    • 在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占用率。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询