基于STM32F103内置DAC的双相函数信号发生器设计与实现

📅 发布时间:2026/9/19 22:18:18
基于STM32F103内置DAC的双相函数信号发生器设计与实现
简介基于STM32单片机的低频双相函数信号发生器设计论文面向电子工程、自动化及相关专业学生与硬件设计人员可为课程设计、毕业设计或测试仪器预研提供完整参考。压缩包内共1份doc文档约18.33MB包括摘要、绪论、方案研究、硬件电路设计、波形产生与软件设计等章节可直接阅读或按需提取设计思路。目前已有189人学习浏览内容聚焦于利用STM32片内DAC低成本实现多波形输出系统给出了两路信号频率在1000Hz2000Hz内以1Hz步进可调、准确度不低于3%的程控方法并详细阐述了正弦波相位差0°360°可设且精度不大于10°、方波占空比1%99%调节、每路输出幅度不低于2.5V且独立可调等关键参数的设计方案。文档还介绍了DDS频率合成、RTOS多任务处理、电源与时钟管理等软硬件实现细节以及总体框图、STM32F103主要配置能够帮助读者快速理解并复现该低频双相信号发生器的完整设计路径。1. 把论文里的低频双相函数信号发生器拆成可复现的方案低频双相函数信号发生器这类题目在很多课程设计里已经沦为「点个灯、出个波」的演示项目但这一份论文的价值在于把「双路独立程控」真正落到了 STM32F103 的内置双 DAC 上频率、相位差、占空比、幅度四组参数全部可调面价几乎就是一块最小系统板加一块 TFT 触摸屏。对于正在做信号源选型、或者想用低成本方案替代商用函数信号发生器的工程师这篇设计的思路可以直接抄对于学生它展示了 DDS 查表法在低频段的实用形态。下面按「方案选型 → 硬件链路 → 软件实现 → 调试验证」的顺序拆开讲重点放在 4096 点波形表、定时器触发 DAC、以及相位差和占空比的程序实现上这些恰恰是论文正文里写得最简略、但实际调板时最花时间的部分。2. 为什么选 DDS 查表法三种信号合成方案对比2.1 模拟方案、DDS 芯片方案与 MCU 内置 DAC 方案的取舍信号发生器的合成思路大体分三类模拟分立元件方案、MCU 加专用 DDS 芯片方案、MCU 内置 DAC 加查表方案。模拟方案用运放和 RC 振荡器搭频率稳定度和温漂很难做到 3% 以内更别说双路相位差精确控制专用 DDS 芯片如 AD9833、AD9959 性能好但需要额外的 SPI 配置BOM 成本和布线复杂度都上去了对学生项目或小批量测试工具来说杀鸡用牛刀。论文选择的是第三种STM32F103 内置 12 位双通道 DAC配上通用定时器 TIM2 做触发源波形数据预先算好存数组中断里查表输出。这个方案的底气来自 F103 的外设组合Cortex-M3 核跑到 72MHzDAC 可以配置为定时器触发模式双通道还能同时更新恰好满足「两路信号独立程控」的核心需求不需要外扩一片 DAPCB 面积和成本都压下来了。从设计指标看输出频率在千赫兹级12 位 DAC 在 3.3V 参考下每 LSB 约 0.8mV幅度分辨率做 100mV 绰绰有余。2.2 DDS 频率公式与本设计 4096 点查询表的关系直接数字频率合成的核心是相位累加器输出频率由频率控制字 M、系统时钟 f_clk 和相位累加器位数 N 决定f_out f_clk × M / 2^N。商用 DDS 芯片的 N 通常是 24 或 32 位所以频率分辨率能做到微赫兹级。这篇论文没有用大位宽累加器而是把波形表固定为 4096 点对应 12 位 DAC 的 4096 个电平级用一个计数器循环查表频率靠改变定时器中断频率来实现相当于把「相位累加步进」固定为 1把「系统时钟」变成可调量// 输出频率与定时器更新频率的关系 // f_out f_tim_update / N其中 N 4096 // 要输出 2000Hz需要定时器更新频率 2000 * 4096 8.192MHz #define WAVE_POINTS 4096 #define TARGET_FREQ 2000 #define TIM_CLOCK (TARGET_FREQ * WAVE_POINTS) // 8.192MHz这里的逻辑很直接每来一次定时器更新中断程序把波形表索引加 1查表得到 12 位数据写入 DAC。一个完整波形周期恰好走完 4096 个点所以定时器更新频率除以 4096 就是输出信号频率。代价是频率分辨率受限定时器时钟源 72MHz 不一定能整除目标更新频率实际输出与设定值之间存在量化误差。这一点在后面调试章节会详细算。2.3 波形表统一带来的代码结构简化四种波形里正弦波本身带相位运算论文的程序中直接写成查表内计算而不是预先生成静态数组。由于 4096 正好是 2 的 12 次方正弦函数里的角度步进2*PI/4096恰好覆盖一个完整周期配合no计数器循环查表索引天然不会越界。方波、三角波、锯齿波则不需要三角函数直接靠ha、debug、mvp三个计数器的加减和比较就能生成代码量很小适合在中断里快速执行。表驱动还有一个隐性好处如果想扩展任意波形不需要改动定时器和 DAC 驱动只需要替换波形数据生成部分。论文把这层封装处理得比较简单但读者理解了这个结构后续做任意波形发生器时能直接复用整套框架。合成方案频率稳定度双相独立控制BOM 成本适用场景模拟分立元件差随温漂难实现低但调试成本高固定频率演示MCU DDS 芯片高依赖晶振需双芯片或通道复用高高频、高精度仪表MCU 内置 DAC 查表中依赖定时器精度双通道天然独立极低低频教学、测试信号源选型结论在 1000Hz 到 2000Hz、频率准确度 3% 这个指标区间内内置 DAC 方案的综合性价比最高。DDS 芯片方案在分辨率上更强但成本和复杂度对这个设计来说是完全多余的。3. STM32F103 硬件设计双 DAC 通道与触摸屏人机链路3.1 STM32F103 内置 DAC 的通道映射与选型注意点需要先提醒一个选型坑不是所有 STM32F103 都有 DAC。论文 3.2.1 节里明确写了2 通道 12 位 D/A 转换器是 STM32F103xC、STM32F103xD、STM32F103xE 系列独有的所以拿到一个 103T8U6 之类的中容量芯片是调不出 DAC 的。正确选择是带后缀 C 或 E 的型号比如 STM32F103RCT6Flash 256KB、SRAM 48KB两个 DAC 通道完整可用。这一点对复现该设计至关重要照着论文买错芯片会让整个波形输出阶段完全黑屏。两个 DAC 输出通道的引脚固定映射DAC_OUT1 在 PA4DAC_OUT2 在 PA5。初始化时要把这两个引脚配置为模拟输入模式因为 DAC 输出驱动的是内部运放引脚不再承担数字输入输出功能。论文软件附录里的DAC_GPIO_Configuration()做的事就是这个GPIO 配置成 Analog 模式否则输出会被引脚的数字缓冲器干扰。3.2 双路输出链路与参考电压设计DAC 的满量程输出电压由参考电压决定。F103 的 VREF 引脚接模拟电源 VDDA典型值 3.3V那么 DAC 输出范围就是 0 到 3.3V。论文正文说「最大幅度不低于 2.5V」结论里写「最大不低于 3.3V」两者不矛盾正文写的是标称下限实测能做到接近 VDDA。代码里取幅度系数 A10.76配合 3.3V 参考算出满幅输出约 2.5V与正文指标严格对应。硬件链路上DAC 输出脚是高阻电压源直接驱动示波器探头没问题但带负载能力有限。常用的做法是在 PA4、PA5 之后各加一级运放跟随器用 LMV358 或 OPA340 这类轨到轨单电源运放把输出阻抗降下来。论文的框图里没有画外部运放这在实际测量时容易踩坑接上 50Ω 负载后波形幅度明显跌落不是程序的错是 DAC 输出能力不够。3.3 TFT 触摸屏接口与按键扫描的取舍人机界面用的是 2.4 寸 320×240 TFT接口是 8080 8/16 位并口触摸屏部分是电阻式四线屏通过触摸控制器读回 X、Y 坐标。论文软件里调用GPIO_Config()、x_out()、y_out()这一组函数就是典型的四线电阻屏驱动先给 X 加电压读 Y 通道的 ADC 值得到 X 坐标再交换方向读 Y 坐标。这种屏便宜、稳定、不怕油污缺点是透光率低但作为仪器面板完全够用。实际复现时要注意 F103 的 GPIO 数量消耗。8080 并口至少占 16 根数据线加若干控制线再加上 DAC 和触摸屏 ADC一个小容量封装很容易把 IO 用光。建议选 LQFP100 封装留有冗余。论文里把界面分成幅度、波形、频率、占空比、相位差、开关六个区域每个区域靠触摸坐标判断进入哪个参数设置采用的是轮询扫描而非中断方式这在 320×240 分辨率和低频操作场景下响应足够代码也简单。// DAC 与 GPIO 初始化基于标准外设库 void DAC_GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE); // PA4 和 PA5 必须配置为模拟输入不能配成推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_4 | GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); }这段代码有两个关键点一是时钟使能必须同时打开 GPIOA 和 DAC 的外设时钟漏掉 DAC 的 APB1 时钟寄存器写了白写二是引脚模式必须是GPIO_Mode_AIN如果配置成复用推挽DAC 输出会被拉到一个不确定电平。很多初学者在这两个地方反复折腾还找不到原因其实就是外设时钟和引脚模式的组合没对上。4. 波形发生软件实现定时器触发、查表输出与相位差控制4.1 TIM2 触发 DAC 的时序设计软件体系的核心是 TIM2 更新中断充当 DAC 的节拍器。DAC 支持定时器触发转换理论上可以在硬件层面做到精准输出论文附录里也出现了TIM_Configuration()和NVIC_Configuration()说明中断驱动是主路径。每次 TIM2 更新中断程序读取当前的波形类型、频率、幅度、相位差等参数调用对应波形函数得到 12 位数据写入 DAC 数据寄存器双通道可以一次写入// 双通道同时更新高 12 位为 DAC2低 12 位为 DAC1 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); DAC_SetDualChannelData( DAC_Align_12b_R, san_out(), // 通道 1 波形这里仅示意实际由 ma/mb 选择函数 san_out() // 通道 2 波形 ); } }DAC_SetDualChannelData这个函数写的是 DHR12RD 寄存器一次操作同时锁存两个通道的数据避免两路输出出现半个周期的相位错位这是双相信号发生器的关键。如果你用两个单独的写操作去更新 PA4 和 PA5即使前后间隔只有几条指令在示波器上也能看到两路正弦波在相位差为 0 时仍然有微小的错位这个细节论文里没有展开实际工程里很重要。4.2 四种波形生成函数的实现与边界处理论文附录给了四个波形函数的完整代码这里按可读性做了整理逻辑保持原样。先看正弦波生成函数u32 zheng_out(void) { u32 value; if (no 4095) { // xwc1 是相位差偏置加在角度上而不是加在输出值上 value sin((no xwc1) * 2 * PI / 4096) * 2048 2048; no; } else { no 0; value sin((no xwc1) * 2 * PI / 4096) * 2048 2048; } return value; }sin计算结果是 -1 到 1乘以 2048 再偏移 2048映射到 0 到 4096正好对上 12 位 DAC 的满量程。注意no的边界条件是no 4095当no等于 4095 时走 else 分支把no清零后重新计算一组 value。这个写法在时序上比「先 no 再判断」少一次越界风险但多了一次重复计算实际运行效果一样。xwc1是相位差偏置默认值 2048 对应 180 度因为 2048/4096 0.5 个周期。方波生成函数里变量名起得比较随意但逻辑是完整的u32 ju_out(void) { ha; if (ha zkb1) { ak47 4095; // 高电平 } if (ha zkb1) { ak47 0; // 低电平 } if (ha 4096) { ha 0; } return ak47; }zkb1是占空比阈值触摸屏上改占空比时程序把百分比换算成 4096 对应的阈值。比如 50% 占空比zkb1就是 2048。当ha从 0 走到 4095低于阈值输出高电平高于阈值输出低电平一个周期刚好完成。注意ha在等于 4096 时清零但zkb1最大可以设置为 99% 对应的 4055这样ha在 4056 到 4095 之间输出低电平占空比最小也有 1% 的低电平余量不会出现全高或全低的极端情况。三角波函数用了一个方向标志位i来控制计数器递增还是递减u32 san_out(void) { if (debug 0) { i 1; // 到达底部改为递增 } if (debug 4096) { i 0; // 到达顶部改为递减 } if (i 1) { debug debug 2; // 上升段步进 2 } if (i 0) { debug debug - 2; // 下降段步进 2 } return debug; }步进值取 2 而不是 1意味着一个三角波周期只需要 4096 个点中的 2048 个波形看起来更「尖」。这个设计里三角波和锯齿波的计数上限都用 4096与 DAC 的 12 位分辨率对齐。锯齿波最简单就是计数器一直加溢出清零u32 awm_out(void) { mvp mvp 1; if (mvp 4096) { mvp 0; } return mvp; }这里有一个值得注意的边界不统一问题ju_out用ha 4096清零awm_out用mvp 4096清零san_out用debug 4096触发方向翻转四段代码的边界条件写法不一致。实际运行中由于计数器每一步加 1和在普通情况下等价但一旦中断响应延迟导致计数器被跳过判断就会漏掉产生一个接近满幅的毛刺。稳妥写法是统一改成这是我在读这份代码时发现的一个潜在隐患。4.3 相位差与占空比的程控实现双路相位差完全靠zheng_out里的xwc1和zheng2_out里的xwc2两个变量错开实现。用户通过触摸屏点按相位差区域每点一次xwx1增加 1对应角度变化量是 360/4096 ≈ 0.088 度所以论文指标「相位差精度不大于 10 度」在这个查表结构下有 100 倍以上的余量。占空比的程控链路是触摸屏改zkx1百分比整数 1 到 99程序换算成zkb1阈值方波函数里比较判定。换算关系为zkb1 zkx1 * 4096 / 100每个百分比对应约 41 个计数点分辨率 1% 也完全够用。关键在于这套换算不能放在方波函数里每次都算否则中断里做浮点除法会拖慢时序应该在触摸事件处理的慢速上下文里算好中断只做整数比较。论文代码里zkb1是 float 类型zkx1是 u32从界面上改的是zkx1再隐式转成zkb1这里其实可以优化成纯整数计算避免浮点开销。参数变量名精度实现指标要求实际余量频率pl1/pl2定时器分频步进 1Hz准确度 3%受限分频量化相位差xwc1/xwc2查表偏移每点 0.088°精度 ≤10°约 113 倍占空比zkx1/zkb14096 点比较分辨率 ≥1%每 1% 对应 41 点幅度A1/A2输出值乘系数分辨率 100mV约 4 倍表格里频率这项的实际余量最小因为它受 72MHz 主频整除关系限制下面详细算这笔账。5. 频率准确度、相位差与占空比的调试验证技巧5.1 频率准确度 3% 的量化误差计算用示波器测量实际输出频率时你会发现设定 1500Hz输出可能是 1464.84Hz。这不是程序 bug而是 72MHz 主频除不尽的量化结果。要输出 1500Hz定时器更新频率需要是 1500×40966.144MHz72/6.14411.71875取分频系数 12实际更新频率 6MHz实际输出频率 6MHz/40961464.84Hz偏差 2.34%刚好压在 3% 指标内。这个计算解释了论文为什么敢写「频率准确度不低于 3%」同时暴露了「步进 1Hz」与「整数分频」之间的深层矛盾如果严格要求每 1Hz 都可设置且准确需要把相位累加器扩展到 24 位以上用 DDS 标准公式 f_out M×f_clk/2^24M 的步进能做到亚赫兹级。课程设计阶段用固定分频是合理取舍但工程上如果用户真的要求步进 1Hz这个方案就得改。验证技巧不要只测一个频率点。在 1000、1200、1500、1800、2000Hz 五个点分别测实际频率算相对误差。如果某个点误差超过 3%优先检查分频系数计算是向上取整还是向下取整取整方向不同误差方向不一样找一个最优分频系数可以在全频段内把误差摊薄。5.2 相位差和占空比的示波器标定相位差验证用双通道同时显示把两路正弦波调到同频用示波器的光标测量两个波形过零点的时间差 Δt相位差 Δt/T×360°。4096 点表每点对应 0.088°所以程控设置 90 度相位差时程序里对应设置约 1024 个点实际输出和理想值之间的误差来自查表索引的整数截断完全在指标范围内。一个容易忽略的问题两路 DAC 共用一个参考电压但 PCB 走线阻抗不一致会导致两路输出幅度微小不同在判断相位差时过零点会偏移。用示波器光标测量时建议把两路波形都归一化到屏幕同一格数减小幅度差异带来的过零判断误差。占空比验证比相位差更直接示波器打开脉宽测量把方波占空比依次设为 1%、50%、99%观察示波器读数。论文指标是分辨率不低于 1%4096 点下每 1% 占空比对应 41 个计数点示波器读数应该稳定跟手。如果发现占空比在低百分比时跳动明显检查zkb1是不是被转成了 float 再参与比较浮点转整数的截断误差在低占空比时会被放大。5.3 用一种更稳妥的波形参数更新策略中断里直接改zkb1、xwc1这类参数有风险如果主循环在中断触发瞬间写入阈值写入操作可能被中断打断导致这一周期用了半新半旧的数据输出波形出现一个异常宽的脉冲。更稳的做法是设置参数影子变量主循环先写影子变量设置标志位中断里只在标志位置位时整组加载参数。比如// 主循环中修改影子参数置更新标志 g_para_shadow.zkb1 new_value; g_para_update_flag 1; // 中断中检测标志位一次性更新生效参数 if (g_para_update_flag) { g_para_active g_para_shadow; g_para_update_flag 0; }这种「影子变量 标志位」模式在低频双相输出场景下不是必须的因为中断频率只有几兆赫兹参数更新很快毛刺概率低但如果后续把输出频率往上推或者把波形表改成在线计算这个策略能直接避免很多莫名其妙的问题。论文没有写这一层属于从代码到可用代码之间需要补的一课。本文还有配套的精品资源点击获取