简介:面向MSP430F5529单片机课程学习与嵌入式入门实验的文档,聚焦从通用I/O配合定时器产生25Hz方波,并用片内ADC测量峰值幅度、在OLED上显示结果的完整实验过程。内容围绕Timer_A的停止、增计数、连续计数和增/减计数模式,捕获/比较模块、8种输出模式及ADC采样中断展开,同时说明按键切换50Hz、75Hz、100Hz方波并同步测量频率显示的实现思路,可用于课程实验、课程设计或自学时对照理解。资源包共1个doc文件,大小16.11MB,主体为实验原理与模块配置说明,便于按定时器、ADC和显示部分查阅;已有1697人学习,可作为梳理方波生成、峰值测量、变频与OLED联调流程的参考。
1. 从一块「波形好好的、读数却不动」的板子说起:25Hz 方波发生与峰值测量
不少人第一次在 MSP430F5529 上做这道方波实验,都会撞上同一个场面:示波器上 25Hz 方波跳得规规矩矩,OLED 上的峰值读数却纹丝不动,像根本没接信号。问题往往不出在 ADC,而出在采样策略——单点采样只抓到某个随机相位上的电压,运气好是满量程,运气差就是 0。这道题表面上看只是「定时器出波形、ADC 读电压、OLED 显示」三段拼接,真要做得站得住脚,得回答三个问题:Timer_A 怎么把 32768Hz 切成精确的半周期、ADC 怎么在一个电周期内覆盖到峰值、频率怎么在 1 秒门控里数得准。它适合正在写单片机课程设计、需要交一份完整实验报告的人,也适合想把 Timer_A 和 ADC12_A 这两个外设真正吃透、而不是只会抄例程的开发者。
2. 需求拆解与时钟规划:四个指标分别压在哪几个外设上
2.1 把实验要求翻译成可验收的四个硬指标
实验指导书上的四句话,落到验收环节其实是四个可以量化的指标。第一,从 IO 口输出 25Hz 方波,判定标准不是「看起来在跳」,而是 1 秒内数到 25 个上升沿、周期误差小于 1%。第二,用内部 AD 测出峰值幅度并显示,判定标准是读数和万用表实测的 VOH 差值不超过 2 个 LSB。第三,按键往复切换 25/50/75/100Hz 四档,判定标准是每次按下只跳一档、连按十次不错乱。第四,测量并显示频率变化,判定标准和第一条共用一套门控。
把这四条摊开,会发现它们对时钟的要求是冲突的:方波发生要一个稳而慢的时基,ADC 采样要一个快的转换时钟,OLED 刷屏要 CPU 有余力。所以方案的第一步不是写代码,而是先把时钟树定下来,让慢时基和快时钟各归各位。我一般会在纸上先画三个圈:ACLK 管定时和门控,MCLK 管运算和刷屏,SMCLK 或 ADC12CLK 管采样,圈画清楚了,后面寄存器基本不会写错。
另一个容易被跳过的是测量口径。峰值幅度到底指的是什么?IO 推挽输出的高电平约等于 VCC,如果 ADC 的参考电压也是 AVCC,那么理论峰值就是满量程,测出来恒为 4090 附近并不奇怪,这不是代码错了,是拓扑决定的。这一点在报告里必须交代清楚,否则答辩时被问一句「为什么峰值不随频率变」就会卡住。
2.2 时钟树:ACLK 用 32768Hz,MCLK 拉到 10MHz
这套方案里最省事的一个决定是 ACLK 直接取 REFOCLK,也就是片内 32768Hz 参考时钟。理由很实在:不用焊晶振,而 32768 是 2 的 15 次方,对它分频做 1 秒门控、做半周期计数都是整数关系,算起来干净。REFOCLK 的精度依赖出厂校准,常温下大致在百分之一量级,对这道题的 ±1% 判定线够用。
MCLK 则必须拉高。主频太低的时候,ADC 连续转换加中断会把 CPU 时间吃得七七八八,主循环几乎推不动,OLED 半天不刷新一次,这就是后面避坑章节要展开的场景。配置代码大致是这样:
WDTCTL = WDTPW + WDTHOLD; // 关看门狗,不关它 32768 个周期后系统就复位 UCSCTL3 |= SELREF_2; // FLL 的参考源选 REFOCLK UCSCTL0_H = 0x08; // 解锁 UCS 控制寄存器 UCSCTL1 |= DCORSEL_4; // DCO 频率档位,配合下面的倍频系数落到 10MHz UCSCTL2 |= 304; // FLL 倍频系数 N UCSCTL2 |= FLLD_0; // DCO 输出不分频 UCSCTL4 |= SELA_2; // ACLK 直接取 REFOCLK,32768Hz这里有个参数值得单独说:UCSCTL2 |= 304里的 304 是倍频系数 N,FLL 实际输出的关系是「参考频率 × (N+1)」,也就是 32768 × 305 ≈ 9.99MHz,所以注释写 10MHz 是合理的近似。如果改成 244,输出就掉到 8MHz 附近,采样窗口和延时函数的时间全部要重算,__delay_cycles的消抖时长也会跟着漂。我见过有人只改了 FLL 系数没改延时参数,按键消抖从 20ms 变成 25ms 还觉得是按键坏了,其实是自己给自己挖坑。
UCSCTL3 |= SELREF_2这一句也别漏。默认参考源不是 REFOCLK 的时候,ACLK 的来源和 FLL 的参考会一起跑偏,表现是定时器周期整体偏移,示波器一看频率差了百分之几,很难往时钟配置上想。
2.3 外设分工与引脚分配
把外设和引脚先钉死,后面调试才能对得上号。这套实验的资源占用其实很轻,一张表就能列清楚:
| 资源 | 承担的功能 | 关键配置 |
|---|---|---|
| Timer_A0 | 产生 25/50/75/100Hz 半周期中断 | TASSEL_1 + MC_1,ACLK 计数 |
| P2.0 | 方波输出口 | P2DIR 置位,普通 IO 输出 |
| Timer_A1 | 1 秒测频门控与 ADC 采样窗口计时 | TASSEL_1 + MC_1 |
| ADC12_A 通道 A1 | 采样方波电压 | P6SEL 置位,单通道重复转换 |
| P6.1 | 模拟输入脚 | 关闭内部上下拉,切到外设功能 |
| P2.1 | 频率切换按键 | 下降沿中断 + 内部上拉 |
| I2C 接口 | OLED 显示 | 按开发板丝印定义,走软件或硬件 I2C |
引脚配置里最容易漏的是 P6.1 的方向位。模拟输入脚必须设成输入,而且P6SEL要置位把引脚从数字 IO 切到 ADC 通道,只设方向不设 SEL,读回来永远是 0。同理,P2.0 作为普通 IO 输出时不需要动P2SEL,一旦误置位,引脚会切到定时器比较输出通道,中断翻转就失效了,波形反而由硬件输出单元产生,频率对不对全看 OUTMOD 配得巧不巧。
3. 方波发生链路:Timer_A 增计数加半周期中断翻转
3.1 TA0CCR0 的取值推导与四档频率的量化误差
方波的本质是电平每半个周期翻转一次,所以定时器只需要按半周期中断即可。Timer_A 工作在增计数模式时,计数器从 0 数到 CCR0 然后归零,一个完整周期包含 CCR0+1 个时钟源周期。这个「+1」就是第一个容易翻车的地方:很多人直接写32768/(2*f)就完事,忽略了计数器要数 CCR0+1 次才溢出。
按 ACLK = 32768Hz 计算四档的半周期计数值,取整方式统一为「先四舍五入再减一」:
| 目标频率 | 理论半周期计数 32768/(2f) | 取整后 CCR0 | 实际半周期 | 实际频率 | 相对误差 |
|---|---|---|---|---|---|
| 25Hz | 655.36 | 654 | 19.989ms | 25.014Hz | +0.06% |
| 50Hz | 327.68 | 327 | 10.010ms | 49.951Hz | -0.10% |
| 75Hz | 218.45 | 217 | 6.653ms | 75.152Hz | +0.20% |
| 100Hz | 163.84 | 163 | 5.005ms | 99.902Hz | -0.10% |
这套取值下四档误差都在 ±0.2% 以内,1 秒门控数上升沿分别得到 25、49、75、99 或 100,和理论值一致。要注意 100Hz 那一档,如果取 164 而不是 163,实际频率会掉到 99.4Hz,门控有可能数到 99,验收时被判不合格,边界档位多核实一遍值不值。
误差来源要说清楚,答辩时这是加分项。一是计时量化误差,也就是上面这个取整;二是 REFOCLK 本身的频偏,片内校准一般在几个百分点以内,实际测出来可能有千分之几的偏移;三是中断响应延迟,CPU 从中断向量跳到翻转语句要几十个时钟周期,25Hz 到 100Hz 的量级下这点抖动在 μs 级,可以忽略,但如果有人想做到 kHz 以上,就得换成硬件输出模式。
3.2 切频时先停表再写 CCR0 的顺序问题
按键切频最容易踩的坑是直接改TA0CCR0。计数器正在往上跑的时候你把比较值改小,它会一直数到 0xFFFF 溢出才回来,中间会冒出一个长达几十毫秒的畸形周期;改大则可能错过一次比较,波形抖一下。示波器上看得清清楚楚,OLED 上的频率读数也会偶尔跳成 0。
稳妥的写法是停表、清零、改值、重启,四步一个都不能少:
void Set_Freq(unsigned char idx) { // 四档对应半周期计数值,已按 CCR0 = round(32768/(2f)) - 1 计算 static const unsigned int ccr_tab[4] = {654, 327, 217, 163}; TA0CTL &= ~MC_1; // 先停计数,避免修改 CCR0 时产生畸形周期 TA0CTL |= TACLR; // 清空 TAR 和计数方向 TA0CCR0 = ccr_tab[idx]; // 写入新的半周期值 TA0CTL |= MC_1; // 重新以增计数模式启动 }TA0CTL &= ~MC_1只清模式位,时钟源选择和分频设置保留着,不用重配。TACLR是自动复位的,写 1 之后硬件自己清零,不需要手动写回 0。这个函数还承担了一个隐性职责:它必须足够短,因为调用它的地方通常是按键中断服务程序,中断里停留太久会影响主循环的门控计时。
档位索引的维护也要想清楚。用index = (index + 1) & 0x03比写index++; if(index==4) index=0;少一个分支,而且天然不会越界。如果数组用unsigned int HZ[4]又忘了边界判断,第五次按键就会读到数组外面的内存,现象是频率突然变成某个莫名其妙的数值,这种玄学问题查起来能耗掉一晚上。
3.3 输出级配置与波形核对的三个动作
P2.0 的配置只有两行,但顺序有讲究:先设方向为输出,再决定初值,最后才开中断。如果先开中断再设方向,中断可能在配置中途就来了,翻转一个还没配好的引脚,第一个周期就是错的。
P2DIR |= BIT0; // P2.0 设为输出 P2OUT &= ~BIT0; // 初始输出低电平,保证第一个半周期从低开始 TA0CCTL0 = CCIE; // 使能 TA0 比较中断,中断里翻转 P2.0中断服务程序里就一句P2OUT ^= BIT0;,不需要判断方向,也不需要清标志——比较中断的 CCIFG 在进入 ISR 时由硬件自动清除。这一点和端口中断不同,端口中断必须手动P2IFG &= ~BIT1,忘了清就一直重入,现象是系统像卡死一样。
波形核对我一般走三步。第一步,示波器接 P2.0 和地,看周期是不是 40ms,占空比是不是接近 50%。第二步,把时基拉开到 1ms/div,看上升沿有没有抖动,抖动量应该在 μs 级。第三步,把频率切到 100Hz,再看一次,如果高频档的占空比明显偏离 50%,多半是中断里干了别的事,比如顺手刷了一次 OLED。
还有一点要提醒:如果板子上 P2.0 同时接了 LED 或者别的外设,输出波形会被负载拉出畸变,上升沿变缓,ADC 采到的峰值也会偏低。这种情况要么断开负载,要么换一个空闲 IO 做输出。
4. 测量与显示链路:ADC12 峰值窗口、1 秒门控测频与 OLED 刷新
4.1 单点采样测不到峰值:200ms 窗口内取最大值
前面说过,单点采样的结果完全取决于采样瞬间信号处在什么相位。25Hz 方波的周期是 40ms,高电平持续 20ms,如果你恰好在低电平区间采,读数就是 0;如果运气好落在高电平中间,读数接近满量程。更麻烦的是这个结果看起来「有变化」,让人误以为是噪声,实际是采样相位在漂。
正确做法是开一个足够长的观察窗口,在窗口内反复采样并取最大值。窗口长度要覆盖整数个信号周期,25Hz 下 200ms 正好是 5 个周期,最坏情况也一定能碰到高电平区间。实现上让 ADC 工作在重复单通道模式,主循环只管读结果寄存器比大小:
#define PEAK_WINDOW 6553 // 32768Hz 下约 200ms,覆盖 25Hz 信号的 5 个周期 unsigned int Measure_Peak(void) { unsigned int peak = 0, v; ADC12CTL0 |= ADC12ENC | ADC12SC; // 启动重复单通道转换 TA1CTL = TASSEL_1 + MC_1 + TACLR; // 用 ACLK 做窗口计时 TA1CCR0 = PEAK_WINDOW; while (TA1R < PEAK_WINDOW) { // 窗口内不断取最大值 if (ADC12IFG & ADC12IFG1) { ADC12IFG &= ~ADC12IFG1; // 手动清标志 v = ADC12MEM1; if (v > peak) peak = v; } } TA1CTL = MC_0; // 关窗口计时 ADC12CTL0 &= ~ADC12ENC; // 停止转换,避免影响后续配置 return peak; }窗口长度的取值有个权衡:太短覆盖不到高电平,太长则刷新变慢。25Hz 是四档里最低的频率,按它来定窗口最保险;如果只在 100Hz 以上工作,50ms 就够。TA1CCR0设成 6553 时实际窗口是 6554/32768 ≈ 200.0ms,误差可以忽略。
这段代码里ADC12IFG & ADC12IFG1的写法要留意。头文件里ADC12IFG1是位掩码,直接写if (ADC12IFG1)判断的是常量,永远为真,循环会疯狂空转,把 CPU 占满。这是个典型翻车点,代码能跑,但主程序慢得离谱。
4.2 ADC12 的通道、参考源与采样保持周期怎么配
ADC12_A 的配置分三块:转换模式、通道选择、采样时序。转换模式用ADC12CONSEQ_2就是重复单通道,一次触发之后自动一轮接一轮转,直到你清 ENC。通道选择要写进ADC12MCTLx,这里有个硬规矩:MCTL 的序号必须和结果存储寄存器 MEM 的序号一致。你把通道 A1 写进ADC12MCTL2,但ADC12CTL1里CSTARTADD选的是 MEM1,那 MEM1 对应的 MCTL1 根本没配通道,采样结果自然不对。
P6SEL |= BIT1; // P6.1 切到模拟功能,对应 ADC 通道 A1 P6DIR &= ~BIT1; // 模拟输入必须是输入方向 ADC12CTL0 = ADC12SHT0_2 + ADC12ON + ADC12MSC; ADC12CTL1 = ADC12SHP + ADC12CSTARTADD_1 + ADC12CONSEQ_2; ADC12MCTL1 = ADC12INCH_1; // 通道 A1 写进 MCTL1,与 MEM1 对应ADC12SHT0_2把采样保持时间设成 16 个 ADC12CLK,对方波这种源阻抗不高的信号足够,太小会导致采样不充分读数偏低。ADC12SHP置位表示用采样定时器产生 SAMPCON,采样周期等于 4 个 ADC12CLK 乘上 SHTx 设定值;这一位置 0 的话采样周期由外部 SHI 信号的高电平宽度决定,外部信号一短就采不准,属于典型的「配置看起来没错但结果就是偏」的场景。
参考电压这一块要明确交代。代码里没有设ADC12REFON和ADC12REF2_5V,说明 VR+ 默认接的是 AVCC,也就是 3.3V,满量程 4096 码对应 3.3V。换算关系是V = code * 3.3 / 4096。如果换成内部 2.5V 参考,而输入信号峰值是 3.3V,读数会直接饱和到 4095,看起来像「怎么测都是满的」,实际是量程选错了。
4.3 1 秒门控计数和捕获法测周期,两条路线的取舍
测频有两条常见路线,选哪条取决于你手上有多少定时器资源。
路线一是 1 秒门控计数,也就是在原方案里用的办法:开一个 1 秒的定时窗口,在窗口里轮询 P2.0 的电平,检测到从 0 到 1 就加一。实现简单,不需要额外的引脚连接,缺点是分辨率被门控时间锁死,1 秒只能分辨 1Hz,测 25Hz 时量化误差就是 4%。对这道题够用,因为四档频率间隔很大。
unsigned int Measure_Freq_1s(void) { unsigned int cnt = 0; unsigned char last = (P2IN & BIT0) ? 1 : 0; TA1CTL = TASSEL_1 + MC_1 + TACLR; TA1CCR0 = 32767; // 32768 个 ACLK 周期 = 1.000s while (TA1R < 32767) { unsigned char now = (P2IN & BIT0) ? 1 : 0; if (last == 0 && now == 1) cnt++; // 只数上升沿,避免高低电平各数一次 last = now; } TA1CTL = MC_0; return cnt; }这里读的是P2IN而不是P2OUT。读输出寄存器也能工作,但读输入寄存器反映的是引脚真实电平,万一引脚被外部拉低,读输入能立刻发现问题,读输出则会骗自己。
路线二是捕获法测周期:把方波信号从 P2.0 引到一个带捕获功能的输入脚,配成上升沿捕获,记录两次捕获之间 ACLK 走过的计数差,周期倒数就是频率。精度能到单个时钟周期,25Hz 下相对误差万分之几,比门控法高两个数量级。代价是要多接一根线,而且捕获中断的频率和信号频率一致,100Hz 下每秒 100 次中断,对 CPU 有压力。
两条路线的对比如下:
| 维度 | 1 秒门控计数 | 捕获法测周期 |
|---|---|---|
| 额外接线 | 不需要 | 需要把输出引到捕获输入脚 |
| 分辨率 | 1Hz(1s 门控) | 约 30μs(单周期) |
| 低频表现 | 越好,计数值大 | 越差,单周期计数小 |
| 中断负担 | 无 | 每个信号周期一次 |
| 适用场景 | 25~100Hz 四档切换 | 宽频段、需要高精度 |
我一般先用门控法把功能跑通,验收时如果被问到精度,再补一句捕获法的思路和寄存器配法,这样既保证了进度,也留了发挥空间。
4.4 OLED 三行布局、刷新节流与按键消抖
OLED 的信息布局按三行排最省事:第一行显示当前频率和单位,第二行显示峰值码值或换算后的电压,第三行显示当前档位序号。第一行和第二行都是测量结果,第三行是状态,看的人一眼就能分清哪两个数在变。
刷新节奏得控制。OLED 走 I2C,刷一屏的耗时在毫秒量级,如果每轮主循环都刷,测量窗口会被拖长,频率读数就开始飘。合理做法是测量完一轮刷一次,也就是 200ms 到 1 秒一次,人眼完全够看,CPU 也不至于被显示占满。想再稳一点,可以只在数值变化时才刷。
按键消抖的坑留到下一章细说,这里先给一个不依赖 Timer_A1 的写法:
#pragma vector = PORT2_VECTOR __interrupt void Port2_ISR(void) { if (P2IFG & BIT1) { __delay_cycles(200000); // 10MHz 下约 20ms,避开抖动区 if ((P2IN & BIT1) == 0) { // 再确认一次电平仍然为低 index = (index + 1) & 0x03; Set_Freq(index); } P2IFG &= ~BIT1; // 端口中断标志必须手动清 } }__delay_cycles的实参依赖 MCLK,10MHz 下 200000 个周期约 20ms,MCLK 一改这个数就得跟着改,这是显式写死延时的代价。用定时器做消抖更稳,但会占用资源,对这道题来说没必要。
5. 避坑与排查:五个把实验卡住的故障点
5.1 中断里默认关总中断,主循环像死了一样
现象:进了一次按键中断之后,OLED 再也不刷新,方波还在输出,但频率再也切不动,按复位才恢复。
原因:MSP430 在响应中断时会自动把 GIE 清零,中断服务程序执行期间不再响应任何其他中断,无论优先级高低。如果 ISR 里有耗时操作,比如 20ms 延时消抖,这段时间内定时器中断、ADC 中断全部被挡住,主循环也停在那里等 ISR 返回。
解决:ISR 尽量短,把耗时逻辑挪到主循环用标志位处理。确实需要嵌套的话,在 ISR 开头手动__enable_interrupt(),但要注意被嵌套打断的共享变量必须加volatile,否则编译器优化会把变量缓存在寄存器里,主循环读到的永远是旧值。这道题里,消抖延时放在 ISR 里是可以接受的,因为 20ms 内错过几次定时器中断对方波影响很小,但如果同时要做门控计时,就必须把消抖挪出去。
5.2 主时钟太低,ADC 把 CPU 挤得没时间跑主程序
现象:ADC 一开,OLED 刷新变得极慢,偶尔直接卡住几秒;把 ADC 关掉立刻恢复正常。
原因:ADC 工作在连续转换模式时,转换完成标志置位非常频繁,如果主循环里用轮询方式处理,几乎全部时间都耗在判断标志上。MCLK 越低,单位时间内能执行的指令越少,这个比例越夸张。原实验心得里提到的「主时钟频率要高于 ADC 采样频率,否则主程序难以前进」说的就是这件事。
解决:三条路可以选。一是把 MCLK 拉到 8MHz 以上,给主循环留出余量;二是别用自由连续转换,改成定时器触发,采样率可控;三是把窗口取最大值那段独立成一个函数,窗口结束后立即关 ENC,不让 ADC 一直空转。我一般三条一起上,成本最低。
5.3 按键中断抢用 Timer_A1,门控计时被打断
现象:频率读数偶尔变成 0,或者突然翻倍,按键按得越频繁出现概率越高。
原因:主循环用 Timer_A1 做 1 秒门控,按键中断里为了消抖又把TA1CCR0和TA1CTL重写了一遍。门控计时被中途截断,计数值自然不对,而且这种错误是概率性的,很难稳定复现。
解决:一个定时器只服务一个用途。消抖要么用__delay_cycles硬延时,要么单独开 Timer_A2,绝不能和门控计时共用。如果定时器实在不够,就把消抖改成主循环里的状态机:中断里只置一个「按键已按下」的标志,主循环每 10ms 查一次电平,连续三次为低才认定有效。
5.4 MCTL 索引和 CSTARTADD 对不上,读回来永远是 0
现象:ADC 配置全都写了,ADC12MEM1读出来恒为 0,换成ADC12MEM0偶尔有值但明显不对。
原因:ADC12CTL1里CSTARTADD_1指定结果存进 MEM1,但通道配置写的是ADC12MCTL2。MEM1 对应的 MCTL1 没有配置通道,转换的其实是默认通道,结果自然不会进 MEM1。
解决:MCTL 序号、MEM 序号、CSTARTADD 三者必须一一对应。改通道的时候三个地方一起改,改完用单步调试看一眼ADC12MEMx是不是随输入电压变化,确认了再往下走。
5.5 峰值读数恒为满量程,其实不是 bug
现象:不管频率切到哪一档,峰值读数始终在 4090 附近,几乎不动,看起来像 ADC 坏了。
原因:P2.0 推挽输出的高电平约等于 VCC,ADC 的参考电压也是 AVCC,输入信号的理论峰值就是满量程。读数恒定为满量程是拓扑决定的正确结果,不是代码问题。
解决:先想清楚测量对象是什么。如果要测的是 IO 的高电平,那就在报告里写明「实测 VOH」,并给出码值与电压的换算。如果确实想看到峰值随频率或幅度变化,就得在信号链路上做文章:加分压电阻把幅度压到参考电压一半左右,或者改用内部 2.5V 参考并确保输入不超过 2.5V,或者干脆换一个外部信号源接到 ADC 输入脚。
6. 验收与答辩:三步自检表加一套误差归因的说法
验收前我会走三步自检,每一步都能独立定位问题,不会出现「全都对不上但不知道从哪查」的局面。
| 步骤 | 动作 | 合格判据 | 不通过时先查什么 |
|---|---|---|---|
| 静态检查 | 示波器测 P2.0,四档逐一切换 | 周期 40/20/13.3/10ms,占空比 50%±5% | TA0CCR0 取值、TACLR 有没有执行 |
| 动态检查 | 1 秒门控计数 + OLED 读数比对 | 四档读数误差 ≤1%,且连续 5 次稳定 | 门控定时器是否被其他中断打断 |
| 边界检查 | 连续按键 20 次、断电重启 3 次 | 档位循环不错乱,重启后默认 25Hz | 索引边界判断、初始化顺序 |
答辩时最容易被追问的是误差从哪来,这里有一套现成的说法,按贡献从大到小排:计时量化误差来自 CCR0 取整,25Hz 档约 0.06%,100Hz 档约 0.1%;时钟源误差来自 REFOCLK 的片内校准偏差,量级在千分之几;测量量化误差来自 1 秒门控只能分辨 1Hz,25Hz 下约 4%,这是整套方案里最大的一项。把这三项摆出来,再说明如果换捕获法可以把第三项压到万分之几,评委基本不会再追。
被问到「为什么用中断翻转 IO 而不用 Timer_A 的 PWM 输出模式」,回答要点是:中断翻转的抖动在 μs 级,25~100Hz 完全够用,而且中断里可以顺手做别的事;硬件输出模式确实更稳,但输出单元只有固定几种 OUTMOD,频率分辨率反而受 OUTMOD 约束,这道题不需要那个精度,用中断更直观。
我还有一条血泪经验:每次做定时器加 ADC 联调的实验,都强制先单独把波形跑通、用示波器量一遍频率,再打开 ADC,最后才接 OLED。三段分开验证,出问题时能立刻锁定在哪一段,比一上来全打开然后对着一个不动的数字猜要快得多。这套顺序后来成了我做所有嵌入式实验的固定动作,也希望能帮到你。
本文还有配套的精品资源,点击获取