基于STM32与DDS技术的信号发生器设计与实现详解

发布时间:2026/7/30 1:33:39
基于STM32与DDS技术的信号发生器设计与实现详解 1. 项目缘起与核心目标又到了期末课程设计的时候这次我接到的题目是“信号发生器的设计”。说实话这个题目在电子、通信、自动化这些专业里算是个“经典保留曲目”了。它不像一些纯理论的课题这个设计非常“接地气”从需求分析、方案选型、硬件焊接、软件编程到最后的调试测试几乎涵盖了电子系统开发的完整流程。对于咱们学生来说能把一个信号发生器从无到有做出来并且能稳定输出几种基本波形那成就感绝对是满满的。那么这个信号发生器到底要做什么呢简单来说它就是一个能产生特定电信号的设备。这些信号的波形、频率和幅度都是可以调节的。最常见的波形就是正弦波、方波和三角波。在实验室里我们用的那些安捷伦、泰克的高端信号发生器功能强大精度也高但内部结构复杂价格昂贵。我们的课程设计目标不是复刻一台商用仪器而是理解其核心原理并用我们手头能接触到的、成本可控的元器件比如单片机去实现一个简化版的、功能明确的核心系统。通过这个项目我们不仅要学会看芯片手册、画原理图、写驱动代码更重要的是建立起一个完整的“系统思维”。你会遇到各种问题为什么我的数码管显示乱跳为什么按下按键没反应为什么输出的波形失真严重解决这些问题的过程才是课程设计最宝贵的收获。接下来我就把我这次设计的过程、踩过的坑以及一些心得详细地分享出来。2. 核心方案选型为什么是DDS与单片机拿到题目第一个要决定的就是技术路线。信号发生器的核心是“信号合成”主流方法有模拟电路如运放搭建的文氏桥振荡器和数字合成两种。模拟方案电路复杂频率和幅度的数字化控制困难显然不适合我们这种需要灵活调节的课程设计。数字方案里直接数字频率合成DDS技术脱颖而出。DDS的原理其实很直观它把一个周期的波形比如正弦波的幅度值预先计算好并存入一个ROM表中这个表就是“波形查找表”。然后通过一个相位累加器以固定的步长频率控制字累加累加器的输出作为地址去查找表里取出对应的幅度值再经过数模转换器DAC变成模拟电压输出。改变频率控制字就改变了相位累加的步长从而改变了输出波形的频率。这种方法频率分辨率极高切换速度也快。确定了DDS作为核心算法接下来就是载体。FPGA和单片机是两大候选。FPGA并行处理能力强可以轻松实现高速、高精度的DDS内核是高性能信号发生器的首选像网络热词里提到的“基于FPGA的信号发生器”就是走这个路线。但它开发门槛相对较高需要掌握硬件描述语言如Verilog对于期末设计来说周期可能比较紧张。而单片机特别是像STC89C52这类经典的51单片机或者更强大的STM32凭借其易用性、丰富的片上资源和成熟的生态成为了绝大多数课程设计的首选。单片机通过软件编程来实现DDS的相位累加和查表输出虽然速度比不上FPGA但对于输出音频段几Hz到几十kHz的波形完全够用并且极大地简化了系统复杂度。考虑到我们设计的功能几种波形、频率可调、数码管显示我最终选择了以STM32F103C8T6也就是常说的“蓝桥杯单片机”主控作为核心控制器。它性能比51强有DMA和DAC能更好地实现高质量的波形输出也方便后续功能扩展。注意如果你手头只有51单片机开发板也完全可以做。51单片机没有硬件DAC可以用PWM加低通滤波来模拟DAC或者使用外接DAC芯片如PCF8591。STM32方案在波形质量上会更有优势。3. 系统硬件设计详解硬件是整个系统的骨架。我的设计主要包含以下几个模块单片机最小系统、波形生成与输出模块、人机交互模块按键与数码管和电源模块。3.1 单片机最小系统与核心外设连接STM32F103C8T6的最小系统包括电源、复位电路、晶振和启动模式选择电路。这部分可以参考任何一款标准开发板的原理图。这里我重点讲与信号生成相关的引脚连接。波形输出的核心是数模转换。STM32F103C8T6内部集成了一个12位的DAC我将其通道1PA4作为模拟输出引脚。为了驱动能力更强、波形更干净我在PA4后面接了一个电压跟随器用一片运放如LM358构成然后再输出到示波器探头。对于没有硬件DAC的单片机如51系列则需要外接DAC芯片。一种经济实惠的方案是使用AD9850这类集成的DDS芯片单片机只需要通过SPI或并行接口给它发送频率控制字它就能直接输出高质量的正弦波方波通过内部比较器也能产生。这在热词“ad9850信号发生器stm32”中也有体现。另一种方案是用单片机的PWM输出经过一个二阶有源低通滤波器滤除高频开关噪声得到平滑的模拟波形。这时你需要精心设计滤波器的截止频率并确保PWM的频率远高于你希望输出的信号频率。3.2 人机交互模块设计人机交互决定了用户使用的便利性。我使用了4个独立按键和4位共阴数码管。按键电路四个按键一端接地另一端分别连接到STM32的四个GPIO口如PA0-PA3并在GPIO口上接上拉电阻到VCC。这样按键未按下时单片机读到的是高电平按下时读到低电平。这里的关键是按键消抖。机械按键在闭合和断开的瞬间会产生一段时间的抖动单片机如果直接检测会误判为多次按下。我采用软件消抖当检测到按键按下低电平后延时10-20ms再次检测如果仍然是低电平则确认为有效按键。更高级的做法是使用按键状态机或者利用定时器中断进行扫描这样可以实现非阻塞的、可靠的按键检测甚至实现长按、连按等功能。数码管显示电路我使用了4位一体的共阴数码管。为了节省IO口采用动态扫描方式驱动。段选a-g, dp接单片机的8个IO口如PB0-PB7位选控制哪一位亮接另外4个IO口如PA8-PA11并通过三极管如8550 PNP型来放大电流驱动位选。动态扫描的原理是利用人眼的视觉暂留快速轮流点亮每一位数码管。例如先让位选1有效送出第1位要显示的数字段码保持几毫秒然后关闭位选1打开位选2送出第2位的段码……如此循环。扫描频率要足够快通常50Hz否则会看到闪烁。3.3 电源与信号调理系统采用USB供电5V通过AMS1117-3.3V稳压芯片为STM32单片机提供3.3V核心电压。运放电路如果需要可以使用±5V或±12V电源这取决于你选用的运放型号和期望的输出电压摆幅。在DAC输出端除了电压跟随器有时还需要加入一些简单的滤波电路以滤除DAC输出中的高频毛刺或量化噪声尤其是在输出正弦波时。一个一阶或二阶的RC低通滤波器就能起到不错的效果。如果你的设计包含幅度调节功能可以在电压跟随器后级再加一个由数字电位器或模拟乘法器构成的增益可控放大电路。4. 软件设计与DDS算法实现软件是系统的大脑。我的程序基于STM32的HAL库开发整体结构包括初始化、DDS波形表生成、定时器中断驱动输出、按键扫描与处理、数码管动态显示等模块。4.1 波形查找表的生成与存储这是DDS的核心。首先我们要在程序里定义一个数组作为波形查找表。对于正弦波一个周期采样多少个点表的大小决定了波形的精度和最高输出频率。采样点越多波形越光滑但计算量和存储空间也越大。我选择了一个包含256个点的正弦表sin_table[256]。如何生成这个表可以在电脑上用Python或MATLAB计算好然后以数组形式复制到代码中。也可以在单片机初始化时实时计算。为了节省运行时间我采用预计算的方式。计算时要注意幅度值要映射到DAC的输出范围内。对于STM32的12位DAC输出范围是0-3.3V对应数值0-4095。我们希望波形以中点1.65V 数值2048为基准上下对称。因此正弦波每个点的值计算公式为DAC_Value 2048 2047 * sin(2 * π * i / 256)。这里2047是幅度确保输出在0-4095之内。计算出的浮点数需要转换为整数存入表中。对于方波和三角波的表就更简单了。方波表前128个点设为4095高电平后128个点设为0低电平。三角波表值从0线性增加到4095再线性减少到0。4.2 定时器中断与DDS输出引擎波形输出需要精确的定时。我使用STM32的一个基本定时器如TIM6来产生固定频率的中断。在中断服务函数中实现DDS的相位累加和查表输出。定义变量定义一个32位的相位累加器phase_accumulator和一个32位的频率控制字Freq_Word。中断服务程序流程每次进入定时器中断将phase_accumulator累加上Freq_Word。取phase_accumulator的高8位因为我们有256个点的表作为查找表的索引index。根据当前选择的波形类型全局变量wave_type从对应的波形表中取出table[index]。将这个值写入DAC的数据寄存器HAL_DAC_SetValue。可选如果需要幅度调节可以将查表值乘以一个幅度系数0.0-1.0后再写入DAC。输出信号的频率由定时器中断频率和频率控制字共同决定。公式为输出频率 (Freq_Word * 定时器中断频率) / 2^N其中N是相位累加器的位数我用了32位定时器中断频率就是定时器溢出的频率。通过按键改变Freq_Word就能实现频率的连续调节。提示使用DMA配合定时器触发DAC转换是更高级、更高效的方法。定时器以目标波形频率的256倍假设表长256触发DMADMA自动将整个波形表的数据依次搬运到DAC完全不需要CPU干预。这在输出高频信号时尤其有效热词中“stm32 hal dac dma 三种波形”指的就是这种方案。我在后续优化中采用了这个方法。4.3 按键与显示逻辑在主循环中我不断扫描按键并更新显示。按键处理采用状态机模型。每个按键有“释放”、“消抖”、“按下”、“保持”等状态。在“按下”状态确认时根据按键功能修改变量。例如按键A切换波形类型正弦-方波-三角波-正弦...同时更新wave_type。按键B频率增加。增大Freq_Word。按键C频率减少。减小Freq_Word。按键D幅度调节模式切换。数码管显示我将当前波形类型如“Sin”、“Squr”、“Tri”和频率值单位Hz分解成数字存入显示缓冲区。在定时器中断或另一个低优先级定时器中执行动态扫描函数刷新数码管显示。频率值较大时需要做单位换算如显示10.25 kHz。5. 调试过程与典型问题排查调试是项目中最耗时也最能学到东西的环节。下面是我遇到的一些典型问题及解决思路。5.1 数码管显示乱码或闪烁现象该亮的段不亮不该亮的段微亮或者显示内容闪烁不稳定。排查检查电路首先用万用表检查数码管段选、位选线与单片机IO口的连接是否正确、有无虚焊。共阴/共阳是否接错限流电阻是否合适检查代码确认段码表是否正确。共阴和共阳的段码是相反的。动态扫描的延时时间很关键。如果位选切换太快延时太短LED亮度会不足如果太慢延时太长就会看到明显的闪烁。我调试发现每位点亮1-2ms整体扫描周期在5-10ms以内即频率100-200Hz效果比较好。驱动能力单片机IO口的拉电流能力有限。如果直接驱动多位数码管的所有段同时亮比如显示数字‘8’电流可能不够导致亮度暗淡或单片机IO口损坏。这就是为什么位选要加三极管驱动的原因。确保三极管型号PNP/NPN和接线正确。5.2 按键失灵或连击现象按下按键没反应或者按一次程序识别为多次。排查硬件检查确认上拉电阻已接好按键引脚在未按下时是否为高电平按下时是否为稳定的低电平。消抖算法这是最常见的原因。我最初用的延时消抖但在主循环中如果其他地方有长延时就会导致按键响应迟钝甚至丢失。后来改成了状态机消抖将按键扫描放在一个1ms的定时器中断里状态判断和动作执行分离彻底解决了问题。这也是“按键状态机”的优势所在。软件逻辑确认按键处理函数中没有阻塞性操作。按键检测和动作执行应快速完成。5.3 输出波形失真或频率不准现象正弦波有台阶感量化失真、毛刺多方波边沿不陡峭、有振铃实际输出频率与设定值偏差大。排查波形表精度对于正弦波256点的表在低频时足够光滑但在接近奈奎斯特频率采样频率的一半时台阶感会很明显。可以尝试增加表长到512或1024点但这会占用更多内存。对于方波理论上是完美的0和1跳变但实际DAC输出有上升/下降时间。滤波电路DAC输出的信号含有高频谐波。如果没有后级滤波在示波器上就会看到很多毛刺。检查电压跟随器和RC低通滤波器是否工作正常运放供电是否充足。方波本身包含丰富的高频成分不适合过度滤波否则会变成“圆角”的梯形波。频率精度输出频率不准根源在于定时器中断的频率和频率控制字的计算。首先校准你的系统时钟HSE或HSI确保定时器预分频和重装载值计算正确。频率控制字Freq_Word的计算公式要使用浮点数或高精度整数运算避免舍入误差累积。可以用示波器测量实际频率反向调整计算参数。DAC参考电压DAC的输出电压精度依赖于参考电压的稳定性。如果使用VDDA作为参考要确保电源干净。如果需要更高精度可以外接一个精准的基准电压源芯片如REF3030。5.4 无PWM波形输出现象在尝试用PWM滤波方案时示波器上看不到任何波形。排查引脚复用STM32的引脚有复用功能。必须通过GPIO初始化函数HAL_GPIO_Init将对应引脚配置为复用推挽输出模式并且映射到正确的定时器通道上。例如PA8做PWM输出它对应定时器1的通道1。你需要先开启TIM1和GPIOA的时钟配置PA8为AF_PP再初始化TIM1的PWM模式。定时器配置确认定时器的预分频PSC和自动重装载值ARR已设置这决定了PWM的频率。然后设置捕获/比较寄存器CCR的值这决定了占空比。最后一定要启动定时器HAL_TIM_PWM_Start和对应的通道HAL_TIM_PWM_Start_IT如果需要中断。示波器设置检查示波器探头是否接好触发模式是否正确通常用边沿触发时间档位和电压档位是否合适。有时候不是没有信号而是示波器没捕捉到。6. 功能扩展与优化思路完成基本功能后可以尝试一些扩展让项目更有深度。幅度可调在DAC输出后级增加一个由数字电位器如MCP41xxx系列或模拟乘法器如AD633构成的程控放大电路。单片机通过SPI控制数字电位器的阻值或者改变乘法器的输入电压来实现输出幅度的数字化调节。波形存储与调用利用STM32的内部Flash或外接EEPROM存储多组用户自定义的波形参数频率、幅度、波形类型实现快速调用。频率扫描与调制功能让输出频率在一定范围内自动线性或对数变化扫频或者用另一个低频信号调制波来控制输出波形的频率FM或幅度AM。这需要更复杂的软件算法。上位机控制通过串口UART或USB与电脑通信开发一个简单的上位机软件可以用LabVIEW、Python的PyQt或Tkinter编写实现从电脑端设置波形参数、控制启停甚至上传自定义波形数据。这涉及到通信协议的设计。提高性能如前所述使用DMA定时器触发DAC的方式来输出波形可以极大地减轻CPU负担提高输出频率上限和波形纯度。这也是从“能工作”到“工作得好”的关键一步。7. 项目总结与心得反思回顾整个课程设计从最初的方案论证、元器件选型、电路焊接到后来的代码调试、问题排查最后看到示波器上稳定显示出自定义的波形时那种感觉是无法替代的。这个过程让我对DDS原理、单片机系统开发、模拟数字电路接口有了非常直观和深刻的理解。几个关键的体会第一理论计算必须扎实。频率控制字怎么算、定时器参数怎么配、滤波器截止频率怎么定这些都需要提前算清楚不能光靠试。第二调试需要耐心和方法。遇到问题要像侦探一样从电源、时钟、信号路径、软件逻辑一步步缩小范围。示波器和逻辑分析仪是最好的老师。第三文档和注释很重要。无论是电路图上的标注还是代码里的注释都能在后期调试和回顾时节省大量时间。第四不要惧怕复杂方案。比如DMA一开始觉得很难但真正用起来后发现它能让系统变得更简洁高效。这个基础的信号发生器平台其实有无限的扩展可能。它不仅仅是一个课程设计作业更是一个很好的学习和实验工具。你可以用它来测试放大器的频率响应可以作为锁相环的参考源甚至可以尝试输出一些简单的音频信号。希望我的这些经验和踩过的坑能给正在做类似项目的你一些实实在在的帮助。