简介:面向电子类课程设计与模拟电路实验的波形发生器设计报告,围绕方波—三角波—正弦波函数发生器的完整设计流程展开,适合电子信息、自动化等专业学生完成课程设计、撰写实验报告或准备电子竞赛时参考。压缩包内共1个doc文档,大小约304KB,正文涵盖设计要求、系统概述、单元电路设计、Multisim仿真与分析、整体电路、心得体会、附录及参考文献等模块。报告详细给出滞回比较器与RC充放电产生方波的原理、积分电路实现方波到三角波转换、差分放大器非线性特性完成三角波到正弦波变换,并包含元件参数选择计算、比较器门限宽度推导、仿真波形与电路图。目前已有707人学习下载,可帮助读者快速理解四波段频率范围、RC时间常数手控频率、峰峰值输出指标等关键要求,对照电路框图与仿真步骤完成从理论推导到Multisim验证的闭环,也可作为撰写课程设计报告的结构范式与排错参考。
1. 波形发生器设计报告里最该先算清的三组参数
很多波形发生器设计报告写到一半卡住,不是电路画不出来,而是指标没定死:想输出 1MHz 正弦,却选了 MCU 内置 DAC 加查表,表长 256 点,触发频率得上到 256MHz,芯片根本给不了这个机会。所以报告第一页不该是框图,而是三组数:频率范围与分辨率、输出幅度与偏置范围、失真度和杂散要求。这三组数一旦落定,方案基本被锁死——低频高精度走 MCU 查表,中高频走专用 DDS 芯片,要几十兆赫兹以上或任意波形,只能上 FPGA 配高速 DAC。下面按选型、实现、程控、验证四步,把波形发生器从纸面指标推到能上电出波形。
2. 波形发生器三条技术路线的选型对比与 DDS 核心公式
2.1 模拟振荡、MCU 查表、专用 DDS 芯片的适用边界
模拟路线用运放搭文氏桥或者方波-三角波发生器,优点是零量化噪声、无采样混叠、元件成本低到可以忽略。代价是频率靠电位器或者变容二极管调,程控几乎做不了,频率精度依赖 RC 元件的温漂,实测能稳在 ±3% 已经算好。要用单片机调,就得挂数字电位器或者程控电容阵列,电路复杂度立刻超过数字方案。所以模拟路线适合固定频率、单次校准的场景,比如传感器激励源。
MCU 查表路线是课程设计和中小批量产品最常走的一条。定时器按固定频率触发 DAC,DMA 把码表一个个搬进 DAC 数据寄存器,输出频率等于触发频率除以每周期点数。它的好处是波形随便改、频率分辨率高、上位机一句话就能调参,坏处是输出频率上限被 DAC 刷新率和 DMA 带宽卡死。STM32F4 系列的 DAC 在 12 位精度下,稳定跑到 1MSPS 左右比较现实,再往上建立时间就成了瓶颈。
专用 DDS 芯片是第三条路。以 AD9833 为例,25MHz 参考时钟、28 位相位累加器,输出频率分辨率约 0.1Hz,正弦和三角波能到 12.5MHz。它把相位累加、正弦查找、DAC 全部集成在片内,单片机只负责写寄存器。代价是输出为电流型,必须外接运放做 I/V 转换,正弦的谐波抑制一般,宽带杂散需要靠外部滤波器压。
| 路线 | 频率范围 | 频率分辨率 | 程控难度 | 典型成本 | 适合场景 | | 模拟振荡 | 1Hz~1MHz | 差,靠元件 | 高 | 极低 | 固定频率激励 | | MCU 查表加 DAC | 1Hz~100kHz | 优 | 低 | 低 | 课程设计、低频信号源 | | 专用 DDS 芯片 | 0.1Hz~12.5MHz | 优 | 低 | 中 | 台式函数发生器 | | FPGA 加高速 DAC | 到几百 MHz | 优 | 高 | 高 | 任意波形、射频激励 |
提示:选型时先把「最高输出频率」乘上「每周期最少点数」,得到需要的触发频率或者采样率,再回头看候选器件的刷新能力。这一步比看数据手册第一页的主频有用得多。
2.2 相位累加器与频率控制字:DDS 输出的两个公式
DDS 的核心是一个位宽为 N 的相位累加器,每个时钟周期累加一次频率控制字 FTW。累加器溢出就代表相位绕了一圈,输出频率由此决定:
f_out = f_clk × FTW / 2^N FTW = round(f_out × 2^N / f_clk)频率分辨率就是 FTW 步进 1 带来的频率变化,等于f_clk / 2^N。N 取 28、f_clk 取 25MHz 时,分辨率约 0.093Hz,足够覆盖音频到中频段的所有需求。N 取 32、f_clk 取 100MHz 时,分辨率降到 0.023Hz,代价是累加器和查找表地址位宽变大。
2.2.1 频率控制字的整数效应与误差估算
FTW 只能取整数,所以实际输出频率f_clk × FTW / 2^N和目标值总有偏差,最大偏差不超过半个频率分辨率。低频段这个误差可以忽略,高频段就不一样了:输出 10MHz、分辨率 0.093Hz 时相对误差只有十亿分之一,但输出 1Hz 时绝对误差可以忽略、相对误差也只有 0.09%。真正需要注意的是,实际输出的是「量化后的频率」而不是设定值,报告里应该写成实测值而不是设定值。
def ftw(f_out, f_clk, n_bits=28): """计算频率控制字及其对应的实际输出频率""" full = 1 << n_bits ftw_val = round(f_out * full / f_clk) # 钳位:至少为 1 保证有输出,最大不超过累加器满量程 ftw_val = max(1, min(ftw_val, full - 1)) real = f_clk * ftw_val / full return ftw_val, real if __name__ == "__main__": for f in (1, 100, 1000, 100_000, 1_000_000): w, real = ftw(f, 25e6) print(f"目标 {f:>9} Hz -> FTW={w:>9d}, 实际 {real:>14.6f} Hz, " f"偏差 {real - f:+.6f} Hz")这段脚本解决的是「设定值能不能达到」这个反复被问的问题。f_clk换成你实际用的晶振频率,n_bits换成芯片的相位累加器位宽,跑一遍就能知道每个目标频率的量化误差。返回的两个值里,ftw_val是要写进频率寄存器的数,real是要写进报告的实测频率。
2.3 每周期点数、采样率与重建滤波器截止频率怎么定
查表方案里,输出频率f_out = f_trig / P,P 是每周期码表点数,f_trig是定时器触发频率。P 越大,正弦台阶越细,量化谐波越小;但同样的 DAC 刷新能力下,P 越大输出频率越低。这是一个必须显式做的取舍,很多报告里只写了「取 256 点」却没写为什么。
| 目标最高输出频率 | 每周期点数 | 需要触发频率 | 重建滤波器截止 | | 1 kHz | 256 | 256 kSPS | 2 kHz | | 10 kHz | 128 | 1.28 MSPS | 20 kHz | | 20 kHz | 64 | 1.28 MSPS | 40 kHz | | 100 kHz | 16 | 1.6 MSPS | 200 kHz |
重建低通滤波器的截止频率通常取最高输出频率的 1.5 到 2 倍,同时必须明显低于触发频率的一半。原因在于阶梯波的镜像分量出现在f_trig ± f_out附近,如果滤波器截止接近f_trig/2,这些镜像是压不下去的,示波器 FFT 上会看到一串等间距杂散。截止取高了杂散漏出去,取低了正弦顶部被削平、幅度随频率明显下降,两边都要实测权衡。
注意:不要为了抬高输出频率把每周期点数砍到 8 点以下。8 点正弦的阶梯误差接近 10%,THD 会直接从 1% 量级掉到影响观感的水平,后面再怎么调滤波器也救不回来。
3. 用 STM32 定时器触发 DAC 加 DMA 跑通波形发生器最小系统
3.1 正弦码表生成与 12 位 DAC 的偏置处理
内置 DAC 是单极性的,输出范围 0 到参考电压,12 位对应码值 0 到 4095。要输出有正有负的正弦,必须把码表抬到中点,再用后级电路把直流偏置去掉或者做成可调偏置。码表生成时留 5% 上下余量是有必要的,DAC 输出级和后面的运放在接近电源轨时增益会下降,码值贴着 0 或者 4095 时波形顶部会塌。
#include <math.h> #include <stdint.h> #define SINE_POINTS 128 /* 每周期点数,按最高输出频率取舍 */ #define DAC_CODE_MAX 4095.0f /* 12 位满量程码值 */ #define DAC_CODE_MID 2047.5f /* 单极性输出的中点 */ static uint16_t sine_tab[SINE_POINTS]; void sine_tab_build(void) { for (int i = 0; i < SINE_POINTS; i++) { float ph = 2.0f * 3.14159265f * (float)i / (float)SINE_POINTS; /* 0.90 是使用率,上下各留 5% 余量避开输出级非线性区 */ float v = DAC_CODE_MID + DAC_CODE_MID * 0.90f * sinf(ph); sine_tab[i] = (uint16_t)(v + 0.5f); /* 加 0.5 做四舍五入 */ } }SINE_POINTS决定频率上限和波形质量,0.90f这个系数直接对应输出幅度使用率,改成 1.0 会削顶、改成 0.5 会浪费一半动态范围。sine_tab类型必须是uint16_t且按 12 位右对齐,才能配合DAC_ALIGN_12B_R直接送进数据寄存器的高 12 位。
3.2 TIM6 触发频率换算:从目标频率反推 ARR 和 PSC
定时器触发频率由预分频器 PSC 和自动重载寄存器 ARR 共同决定:f_trig = tim_clk / ((PSC+1) × (ARR+1))。在低频段固定 PSC 为 0、只调 ARR 最简单,但 ARR 只有 16 位,最低能到的触发频率就是tim_clk / 65536。
typedef struct { uint32_t psc; uint32_t arr; uint32_t real_freq; /* 实际可达的输出频率,单位 Hz */ } wave_tim_t; /* tim_clk:定时器时钟;f_out:目标输出频率;points:每周期点数 */ wave_tim_t wave_set_freq(uint32_t f_out, uint32_t tim_clk, uint32_t points) { wave_tim_t r = {0, 0xFFFF, 0}; if (f_out == 0) return r; uint64_t target = (uint64_t)tim_clk / ((uint64_t)f_out * points); if (target < 2) target = 2; /* ARR 至少为 1,触发频率有上限 */ r.arr = (uint32_t)(target - 1); if (r.arr > 0xFFFF) { /* 超出 16 位,只能用 PSC 补 */ r.psc = (uint32_t)((target + 0xFFFF) / 0x10000); r.arr = (uint32_t)(target / (r.psc + 1) - 1); } r.real_freq = (uint32_t)((uint64_t)tim_clk / ((r.psc + 1ULL) * (r.arr + 1) * points)); return r; }target是总的分频比,arr是它的整数部分减一,超过 16 位时才启用psc。返回值里的real_freq必须拿去显示或者写进报告,因为 ARR 取整后实际频率和目标值在高频段能差出几个百分点。以 84MHz 定时器时钟、128 点表为例:
| 目标输出 | 需要触发频率 | ARR | 实际输出 | | 100 Hz | 12.8 kHz | 6561 | 100.00 Hz | | 1 kHz | 128 kHz | 655 | 1000.15 Hz | | 10 kHz | 1.28 MHz | 64 | 10096 Hz | | 50 kHz | 6.4 MHz | 12 | 50538 Hz | | 100 kHz | 12.8 MHz | 5 | 109375 Hz |
高目标频率一栏的误差已经很刺眼了,根因是分频比整数化。要在这个频段继续走查表路线,唯一的办法是缩短码表长度,比如高频段切到 16 点表,让分频比回到几十的量级。
3.3 DAC 加 DMA 循环模式的初始化与启动顺序
顺序搞反是新手最常见的失败点:先开 DAC 再配 DMA,第一个完整周期会被后面的配置打断;先开 DMA 再配定时器,会出现一段无触发的静止电平。正确的顺序是时钟、GPIO、DMA、DAC、定时器,最后统一使能。
void wave_start(void) { sine_tab_build(); /* 1. DMA:内存到外设,循环模式,字长半字对应 12 位右对齐 */ hdma_dac1.Instance = DMA1_Stream5; hdma_dac1.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_dac1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_dac1.Init.MemInc = DMA_MINC_ENABLE; hdma_dac1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_dac1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_dac1.Init.Mode = DMA_CIRCULAR; /* 关键:循环重放码表 */ HAL_DMA_Init(&hdma_dac1); __HAL_LINKDMA(&hdac, DMA_Handle1, hdma_dac1); /* 2. 定时器:TRGO 选更新事件,DAC 每次更新输出一个点 */ htim6.Instance = TIM6; htim6.Init.Prescaler = 0; htim6.Init.CounterMode = TIM_COUNTERMODE_UP; htim6.Init.Period = 655; /* 对应 1kHz 输出,见表 */ HAL_TIM_Base_Init(&htim6); TIM6->CR2 = TIM_TRGO_UPDATE; /* 3. DAC:开启触发,不要软件触发 */ HAL_DAC_Init(&hdac); HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1); /* 4. 先起 DMA 搬运,再起定时器,保证第一个周期完整 */ HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)sine_tab, SINE_POINTS, DAC_ALIGN_12B_R); HAL_TIM_Base_Start(&htim6); }Mode必须是DMA_CIRCULAR,否则码表搬完一次就停,输出变成一段直流。MemDataAlignment和PeriphDataAlignment都用半字,对应 12 位数据放在 16 位寄存器低位、再靠DAC_ALIGN_12B_R对齐到高 12 位。DAC_Trigger必须配成DAC_TRIGGER_T6_TRGO,如果用DAC_TRIGGER_NONE,DAC 只会在软件写入时更新一次,波形不会周期输出。
3.4 输出频率的下限、上限与切换波形时的缓冲区安全
下限由 ARR 的 16 位和 PSC 决定,PSC 为 0、ARR 取 65535、128 点表时,最低输出约 84e6/(65536×128) ≈ 10Hz。要往下走必须提高 PSC,但 PSC 一高,同一档 ARR 下可调的分辨率就变粗,低频段微调会变得很难受。
上限受两头夹击:DAC 建立时间在 12 位精度下约 1µs 量级,DMA 每个点还要占用总线周期,实测稳定刷新在 1~2MSPS。按 128 点表算,最高正弦输出也就 8~15kHz。这个数字必须在报告里写清楚,而不是含糊地写「频率范围 1Hz 到 100kHz」——100kHz 只有在 16 点甚至 8 点表下才勉强够,波形质量已经完全不是正弦。
切换波形或频率时,先HAL_DAC_Stop_DMA停搬运,改完sine_tab指针和 ARR,再从头启动。直接在半途改指针,DMA 可能正在读旧表尾部、新表头部,输出会插进去一段跳变,后级功放或者被测电路可能被打出尖峰。
4. 波形发生器的多波形切换、幅度偏置程控与串口调参实现
4.1 一张表生成正弦、方波、三角波、锯齿波
与其准备四张表占四份 RAM,不如用一个生成函数按时序位置算出归一化值,再统一映射到 DAC 码值区间。这样码表长度、使用率、偏置点全部是同一套参数,切换波形时不用重新分配缓冲区。
typedef enum { WAVE_SINE, WAVE_SQUARE, WAVE_TRI, WAVE_SAW } wave_type_t; void wave_tab_build(wave_type_t type, uint16_t *tab, int n) { for (int i = 0; i < n; i++) { float r = (float)i / (float)n; /* 归一化相位 0 ~ 1 */ float v; switch (type) { case WAVE_SINE: v = 0.5f + 0.5f * sinf(2.0f * 3.14159265f * r); break; case WAVE_SQUARE: v = (r < 0.5f) ? 1.0f : 0.0f; /* 占空比 50% */ break; case WAVE_TRI: v = (r < 0.5f) ? (2.0f * r) : (2.0f - 2.0f * r); break; case WAVE_SAW: v = r; break; default: v = 0.5f; break; } /* 同样留 5% 余量,统一映射到 12 位码值 */ tab[i] = (uint16_t)(4095.0f * (0.05f + 0.90f * v) + 0.5f); } }方波用查表输出的边沿取决于 DAC 刷新率,1MSPS 下上升时间就是 1µs 量级,做时钟源不够用。要陡边沿,正确做法是从 DDS 或者码表信号后面接一个高速比较器,把正弦整形为方波,边沿能压到几十纳秒;或者干脆用定时器 PWM 通道直接输出。WAVE_SQUARE走 DAC 只适合做逻辑电平演示,报告中要注明。
4.2 幅度与偏置:数字域缩放还是模拟域 PGA
幅度控制的三种做法差别很大,选错了会白白丢掉 DAC 的有效位数。数字域缩放的实现最简单,改一个系数乘进码表就行,但输出幅度降到 10% 时,DAC 实际只用到 400 个码值,等效分辨率掉到 8 位多,小信号时底噪立刻显出来。数字电位器调 DAC 参考电压的方式不损失位数,但可调的参考源本身有噪声和温漂。后级 PGA 放在模拟通路里,数字域保持满幅输出,信噪比最好,代价是增加器件和调试量。
| 幅度方案 | 可调范围 | 有效位数损失 | 电路复杂度 | 建议用途 | | 数字域缩放 | 1%~100% | 明显,小幅度差 | 无 | 快速验证 | | 调 DAC 参考电压 | 10%~100% | 无 | 中 | 需要稳定幅度 | | 后级 PGA 或数字电位器 | 0~满摆幅 | 无 | 高 | 输出大摆幅信号 |
实际工程里常用组合:数字域做粗调,把幅度落在 PGA 的某个增益档附近;PGA 做细调,保证数字域始终输出接近满幅。偏置则用后级加法器实现,一路接信号、一路接可调直流,直流由第二个 DAC 通道或者 PWM 加 RC 滤波给出。
提示:偏置和幅度要分别接示波器直流耦合和交流耦合各测一次。只测交流耦合会看不到偏置漂移,只测直流耦合又不容易发现偏置引入的偶次谐波。
4.3 串口调参帧格式与解析
上位机调参用带帧头、长度和校验的定长帧最省事,丢包和粘包都容易定位。帧头用两个字节是为了避免数据区偶然出现帧头字节时误判。
typedef struct __attribute__((packed)) { uint8_t head[2]; /* 固定 0xAA 0x55 */ uint8_t cmd; /* 0x01 设频率,0x02 设幅度,0x03 设偏置,0x04 切波形 */ uint8_t len; /* 紧随其后的有效数据字节数,最大 8 */ uint8_t data[8]; uint8_t crc; /* 从 cmd 到 data 末尾的 CRC-8 */ } wave_frame_t; static uint8_t crc8(const uint8_t *p, int n) { uint8_t c = 0x00; while (n--) { c ^= *p++; for (int i = 0; i < 8; i++) c = (c & 0x80) ? (uint8_t)((c << 1) ^ 0x07) : (uint8_t)(c << 1); } return c; } /* 返回 0 表示校验通过并已执行 */ int wave_frame_handle(const wave_frame_t *f) { if (f->head[0] != 0xAA || f->head[1] != 0x55) return -1; if (f->len > sizeof(f->data)) return -2; if (crc8((const uint8_t *)&f->cmd, f->len + 2) != f->crc) return -3; switch (f->cmd) { case 0x01: { /* 小端 4 字节,单位 Hz */ uint32_t hz = (uint32_t)f->data[0] | ((uint32_t)f->data[1] << 8) | ((uint32_t)f->data[2] << 16) | ((uint32_t)f->data[3] << 24); wave_apply_freq(hz); break; } case 0x02: /* 2 字节,千分比 0~1000 */ wave_apply_amp((uint16_t)(f->data[0] | (f->data[1] << 8))); break; case 0x04: wave_apply_type((wave_type_t)f->data[0]); break; default: return -4; } return 0; }crc8的覆盖范围是cmd到data末尾,共len + 2字节,注意不要漏掉len本身。__attribute__((packed))在 Cortex-M 上让结构体不插入填充字节,直接按字节流解析不会错位。命令执行函数内部先做范围钳位,再改硬件参数,避免上位机误发一个 0 或者超大值把 DMA 配置打乱。
4.4 参数越界钳位与突变保护
频率越界要钳到[f_min, f_max]并回一条带实际值的应答帧,而不是静默丢弃。幅度变化建议加斜率限制,比如每 10ms 只允许变化 1% 满量程,直接跳变会让后级功放或者被测器件吃到一次冲击。波形切换和频率大幅变更都要走「停 DMA、改参数、重启」的完整流程,中间不要插入延时,否则输出会挂在一个固定电平上。
5. 波形发生器实测验证与杂散失真排查
5.1 用示波器 FFT 与脚本算 THD
验证波形发生器最直接的方式是示波器开 FFT,看基波和 2 到 5 次谐波的相对幅度。要注意的是示波器 FFT 的窗函数和点数会严重影响读数,用矩形窗测非整周期信号,泄漏出来的谱线会被误判成杂散。想做有说服力的数据,把波形导成 CSV,用脚本统一处理更靠谱。
import numpy as np def thd_from_csv(path, fs, f0, n_harm=5): """fs:采样率 Hz;f0:信号频率 Hz;返回 THD 比值""" x = np.loadtxt(path, delimiter=',', skiprows=1)[:, 1] x = x - x.mean() # 去直流,避免 0Hz 分量抬高底噪 X = np.abs(np.fft.rfft(x * np.hanning(len(x)))) freqs = np.fft.rfftfreq(len(x), 1.0 / fs) def peak(f): i = int(np.argmin(np.abs(freqs - f))) return X[max(0, i - 2):i + 3].max() # 取峰附近 5 点,防栅栏效应 h1 = peak(f0) hr = np.sqrt(sum(peak(k * f0) ** 2 for k in range(2, n_harm + 1))) return hr / h1 print(f"THD = {thd_from_csv('wave.csv', 1e6, 1000) * 100:.2f} %")fs要填示波器或者采集卡的采样率,不是波形发生器的触发频率,这两个值填反了整套算出来的都是错的。np.hanning加窗是为了压泄漏,代价是主瓣变宽,所以取峰值时要在中心点左右各扩两个 bin。算出来的比值乘 100 就是百分比,1kHz 正弦在 128 点表、重建滤波器截止 2kHz 的情况下,做到 1% 以内是比较正常的水平。
5.2 输出异常与杂散的定位表
| 现象 | 可能原因 | 定位动作 | | 正弦上有明显台阶 | 每周期点数太少或滤波器截止太高 | 点数翻倍再测,台阶消失即确认 | | 频率比设定值偏高 | ARR 取整截断 | 读回 ARR 反算实际频率,和设定值对比 | | 频谱上等间距杂散 | 重建滤波器缺失或截止频率过高 | 把截止降到输出频率 1.5 倍再测 | | 幅度随频率升高而下降 | 后级运放压摆率或带宽不足 | 换成高 SR 运放,或降低输出幅度复测 | | 低频段波形抖动 | 码表被改写或中断抢占 DMA | 改双缓冲,检查是否有高频中断 | | 小幅度时底噪明显 | 数字域缩放损失有效位 | 改用后级 PGA,数字域保持满幅 |
注意:排查顺序建议从时钟侧往模拟侧走,先确认触发频率和码表长度对不对,再看 DAC 输出引脚上的原始阶梯波,最后才看滤波器之后的波形。从后往前查,很容易把滤波器的问题当成码表的问题。
5.3 用 PSC 和 ARR 联合搜索逼近目标频率
高频段 ARR 整数化带来的频率误差,可以用 PSC 和 ARR 的组合来压。单独调 ARR 时可选的分频比是连续的整数,加上 PSC 之后可用分频比变成(PSC+1) × (ARR+1),组合数多了几个数量级,总能找到更接近目标的那个。
def best_div(tim_clk, f_out, points, psc_max=255): """穷举 PSC 找误差最小的 ARR 组合,返回 (psc, arr, 实际频率, 误差)""" target = tim_clk / (f_out * points) # 需要的总分频比 best = None for psc in range(psc_max): arr = round(target / (psc + 1)) - 1 if arr < 1 or arr > 65535: continue real = tim_clk / ((psc + 1) * (arr + 1) * points) err = abs(real - f_out) if best is None or err < best[3]: best = (psc, arr, real, err) return best print(best_div(84e6, 50_000, 128)) # 128 点表下逼近 50kHzpsc_max取 255 已经足够,因为 PSC 越大、分频比越粗,继续往上找只会让误差变大。返回值里第一个是写进 PSC 寄存器的值,第二个是 ARR,第三个是实际输出频率,第四个是绝对误差,直接抄进报告的实测数据表。把这段脚本和wave_set_freq一起用,先离线把每个频点的 PSC/ARR 算好存成表,上电时直接查表写寄存器,比在中断里做除法快得多,也避免了运行时反复试参数导致输出中断。
本文还有配套的精品资源,点击获取