简介:这份文档围绕MSP430F5529单片机的25Hz方波发生与峰值测量实验展开,面向正在学习嵌入式基础、单片机课程实验及课程设计的学生。内容从通用I/O与定时器入手,梳理Timer_A的四种计数模式、捕获/比较通道和8种输出模式,说明如何配置定时周期输出25Hz方波;并结合内部ADC采样、中断处理与OLED显示,给出峰值幅度测量、按键切换50Hz、75Hz、100Hz以及同步测量频率变化的实验思路,对理解定时器与ADC协同工作较有帮助。压缩包仅含1个doc文档,约16.11MB,原理讲解、实验步骤与结果展示集中在一份材料中,便于按实验环节查阅。已有1697人学习下载,可作为实验预习、报告整理和参数配置排错的参考。
1. 25Hz 方波看起来简单:真正要交的是「发生—测量—显示」闭环
25Hz 不是随手挑的数。周期 40ms、半周期 20ms,MSP430F5529 的 ACLK 取 32768Hz,除下来半周期装值 655 还有富余;ADC12 单次 12 位转换不到 5μs;OLED 刷一屏字符几十毫秒也不会把测量窗口撕开——三个模块刚好卡在一个谁都不憋屈的节奏上。很多人第一次接这个题,本能反应是把 DCO 拉到最高、SMCLK 一路分频下去,频率是准了,可采样相位和显示刷新全乱,最后示波器上看着像方波,OLED 上的峰值却在 0 和满量程之间跳。这道题真正的难点从来不是「产生一个 25Hz」,而是让方波发生、峰值测量、频率计数、按键换挡这四件事在同一条时间轴上互不干扰。下面这套方案按课设/毕设答辩的标准走:先立总体分工,再逐个模块落到寄存器和代码,最后给一张能拿去对拍的误差表。适合要交单片机课程设计、实验报告,或者需要一份能上台讲清楚的完整方案的人。
2. 总体方案设计:三条链路、五个外设与一张资源分配表
先把「一个单片机同时干四件事」拆成三条互不阻塞的链路,后面的所有设计都从这张图上长出来。
第一条是发生链路:Timer_A0 用 ACLK 做时钟源,增计数模式,每计到 TA0CCR0 触发一次比较中断,中断里把 P2.0 取反。半周期 20ms 翻转一次,输出就是 40ms 周期的 25Hz 方波。这条链路是纯硬件的,CPU 只在中断里执行一条异或指令,占用可以忽略。
第二条是测量链路:P6.1 复用为模拟输入 A1,ADC12 单通道单次转换,软件触发。关键不在转换本身,而在于「什么时候采」——单点采样撞在方波低电平上就得到 0,撞在高电平上才是峰值。所以真正的设计点是采样窗口怎么覆盖一个完整周期。
第三条是计数与显示链路:Timer_A1 用同一路 ACLK 做 1 秒时间门,门内轮询 P2.0 数上升沿得到频率;OLED 通过 IIC 挂在两个 GPIO 上,只做低速字符刷新。
2.1 三条链路的时间预算怎么分配
把三个模块的时间尺度摆在一起看,取舍就清楚了。方波半周期 20ms,是三者里最慢的节拍;ADC 一次「采样保持 + 转换」在 SMCLK 约 10MHz 下总计约 5μs;OLED 刷一个新数字,IIC 在 100kHz 速率下送 4 个字符大约 2~3ms。
这意味着:一次 1 秒的计数窗口里,方波要翻转 50 次,ADC 理论上能采 20 万次,OLED 能刷 300 多屏。资源是绝对富余的,问题只出在主循环被阻塞上。原始代码把 1 秒计数写成while(TA1R < 32768)的忙等,这 1 秒里 CPU 什么都干不了,峰值采样只能挤在窗口外面做。我的做法是把「秒门」和「峰值扫描」放在同一个 1 秒周期里的不同相位:前 40ms 做一轮峰值扫描(200 次采样覆盖一个 25Hz 周期),剩下时间做边沿计数。这样两件事互不重叠,也不会互相拖慢。
提示:如果答辩时被问到「为什么不用 ADC 中断」,答案就在这里——峰值扫描需要的是「一轮里取最大」,中断方式每次都要进出栈,反而比轮询慢,而且 MEM1 只有一个结果寄存器,中断频繁触发容易覆盖。
2.2 外设与引脚资源分配表
资源分配写在纸上再去写代码,能省掉一半的调试时间。这张表就是我们这套方案的实际占用:
| 资源 | 用途 | 配置要点 |
|---|---|---|
| P2.0 | 方波输出 | P2DIR 置位,关闭复用,普通推挽输出 |
| P6.1 / A1 | ADC 模拟输入 | P6SEL 置位,P6DIR 清零,数字输入关断 |
| P2.1 | 换挡按键 | 内部上拉,下降沿触发端口中断 |
| TA0 | 方波定时 | ACLK=32768Hz,增计数,CCR0 比较中断 |
| TA1 | 1 秒时间门 | ACLK 增计数,CCR0=0xFFFF,靠 TA1R 判读 |
| ADC12 | 峰值采样 | 单通道单次,SHP=1,结果存 MEM1 |
| OLED | 显示 | IIC 两线,字符模式,两行数据 |
表里最容易出问题的是 P6.1 那一行。MSP430F5529 的端口是复用的,P6SEL 不置位,引脚就是普通数字 IO,ADC 采到的永远是轨到轨的方波数字量——看起来「有数」,但完全没有意义。这个坑我在第 5 章还会展开。
2.3 时钟选型:为什么方波走 ACLK 而不是 SMCLK
这是整套方案里唯一一个值得争论的决策。用 SMCLK(DCO 出来的约 10MHz)做方波定时器,分辨率高得多,25Hz 装值 200000,误差能压到 0.001% 以下。但代价是:DCO 受温度和供电波动影响,FLL 没锁稳的时候频率会飘,实测能差出千分之几;而且 16 位定时器装不下 200000,必须用增/减计数或者软件分频,代码复杂度立刻上一个台阶。
ACLK 走 REFOCLK 的 32768Hz,精度由内部参考决定,温漂小,分频链简单,装值 655 在 16 位范围内绰绰有余。25Hz 这个量级根本不需要高分辨率时钟,用 ACLK 是典型的「用对的工具」。最终误差在 0.1% 量级,示波器上完全看不出来,答辩时也解释得通。
时钟初始化这一段,四行寄存器决定了后面所有模块的行为:
// 系统时钟:REFOCLK 32768Hz → ACLK,DCO+FLL ≈ 9.96MHz → MCLK/SMCLK UCSCTL3 |= SELREF_2; // FLL 参考源选 REFOCLK UCSCTL0_H = 0x08; // DCO 调制位,压住低频段的抖动 UCSCTL1 |= DCORSEL_4; // DCO 频段 4,覆盖 10MHz 档 UCSCTL2 |= 304; // FLL 倍频 N=304 → 32768×304 ≈ 9.96MHz UCSCTL2 |= FLLD_0; // 倍频后不分频 UCSCTL4 |= SELA_2; // ACLK 源选 REFOCLK,不随 DCO 变化DCORSEL_4是把 DCO 粗调到能覆盖 10MHz 的频段,UCSCTL2里的 304 是 FLL 的倍频系数,32768 乘 304 得到约 9.96MHz,这个值不需要精确到小数点,MCLK 只要够快、够稳就行。SELA_2那一行是关键:ACLK 一旦绑定到 REFOCLK,它就与 DCO 彻底解耦,后面方波频率的稳定性完全由这一行保证。
注意:这几行执行完之后,UCS 需要一段稳定时间。原始代码没有等 UCSCTL7 的故障标志清零就直接配定时器,在部分开发板上会出现上电后第一秒频率明显偏快。规范做法是写完时钟后插入
while(UCSCTL7 & (XT2OFFG + XT1LFOFFG + DCOFFG));再清一次UCSCTL7。
3. 方波发生模块:Timer_A 增计数与半周期装值的完整实现
方波的本质是「定时器每隔固定时间让 IO 翻转一次」,所以整章的落点只有一个:装值怎么算,什么时候安全地改写装值。
3.1 半周期装值与频率精度换算
Timer_A 工作在增计数模式时,计数器 TA0R 从 0 加到 TA0CCR0,然后归零,这是一个完整的计数周期,长度是 TA0CCR0 + 1 个时钟。中断在 TA0R 等于 TA0CCR0 时触发,所以中断间隔就是 (TA0CCR0 + 1) / f_ACLK,而方波周期是中断间隔的两倍。
严格公式是:
TA0CCR0 = round( f_ACLK / (2 × f_out) ) - 1原始代码写的是(int)(32768/(2*HZ[index])),没有减 1,直接把商截断取整。这个差别看起来只有 1 个计数,但对 25Hz 来说,1 个计数就是 0.15% 的误差。把两种取整方式算出来对比:
| 目标频率 | 理论装值 | 直接截断 | 实际输出 | 减一取整 | 实际输出 |
|---|---|---|---|---|---|
| 25 Hz | 655.36 | 655 | 24.976 Hz | 654 | 25.014 Hz |
| 50 Hz | 327.68 | 327 | 49.951 Hz | 327 | 49.951 Hz |
| 75 Hz | 218.45 | 218 | 74.813 Hz | 217 | 75.156 Hz |
| 100 Hz | 163.84 | 163 | 99.902 Hz | 163 | 99.902 Hz |
四个频点里,减一之后 25Hz 和 75Hz 的误差反而变小,50Hz 和 100Hz 因为理论值恰好落在 .68 和 .84,两种取整结果一样。所以工程上更稳的写法是「四舍五入后再减一」,能把误差压到 0.1% 以内,示波器的频率计基本读不出偏差。
这个细节在答辩里非常吃香:老师问「你的 25Hz 准不准」,你能当场把公式和误差表写出来,比说「挺准的」强太多。
3.2 定时器配置与中断服务代码
初始化只做四件事:把 P2.0 配成输出、算装值、开比较中断、把定时器跑起来。
#define ACLK_HZ 32768u const unsigned int HZ[4] = {25, 50, 75, 100}; volatile unsigned char index = 0; // 当前频点下标 void wave_init(void) { P2DIR |= BIT0; // P2.0 设为输出 P2SEL &= ~BIT0; // 关掉复用功能,走普通 IO // 半周期装值:四舍五入后减一,误差最小 TA0CCR0 = (ACLK_HZ + HZ[index]) / (2 * HZ[index]) - 1; TA0CCTL0 = CCIE; // 允许 CCR0 比较中断 TA0CTL = TASSEL_1 + MC_1 + TACLR; // ACLK + 增计数 + 先清计数器 } #pragma vector = TIMER0_A0_VECTOR __interrupt void TA0_ISR(void) { P2OUT ^= BIT0; // 每半个周期翻转一次 }TASSEL_1选 ACLK,MC_1是增计数到 CCR0 归零,TACLR清计数器、清分频器、清计数方向,这三个位合起来写进 TA0CTL,定时器的行为就定死了。中断服务里只有一条异或,执行时间几十个时钟周期,相对 20ms 的中断间隔可以忽略。
装值算式的写法值得单独说一句:(ACLK_HZ + f) / (2f) - 1这个形式,等于先做ACLK_HZ/(2f) + 0.5再取整,用整数运算实现了四舍五入,比(int)(...)截断干净。
3.3 按键换挡与改频的安全时序
换挡这件事,表面上是「按一下换一个频率」,实际上有两个真实风险:按键抖动导致一次按下跳好几档,以及改写 TA0CCR0 的时机不对导致输出卡死或多出一个周期。
先说抖动。机械按键的抖动窗口通常在 5~20ms,原始代码用 Timer_A1 做了一个 655 个 ACLK 周期的延时,655/32768 ≈ 20ms,刚好覆盖最坏情况。while(TA1R <= 655)这个忙等虽然土,但在中断里足够短,不影响其他逻辑。
再说改频时机,这是真正容易翻车的地方。增计数模式下,如果新写入的 TA0CCR0 小于当前 TA0R,计数器会一路加到 0xFFFF 才归零,那个周期会变成一个约 2 秒的超长周期。稳妥的做法是先把定时器停住、清计数器、写装值、再启动:
// 安全改频:停 → 清 → 写 → 启,四步不能省 void wave_set_freq(unsigned char idx) { TA0CTL &= ~MC_1; // MC 清零,定时器暂停 TA0CTL |= TACLR; // 清 TA0R 与分频器 TA0CCR0 = (ACLK_HZ + HZ[idx]) / (2 * HZ[idx]) - 1; TA0CTL |= MC_1; // 恢复增计数 index = idx; } #pragma vector = PORT2_VECTOR __interrupt void P2_ISR(void) { if (P2IFG & BIT1) { P2IFG &= ~BIT1; // 先清标志,防重入 TA1CTL = TASSEL_1 + MC_1 + TACLR; TA1CCR0 = 655; // 约 20ms 消抖窗口 while (TA1R < 655) ; // 粗延时 if ((P2IN & BIT1) == 0) // 二次确认仍为低电平 wave_set_freq((index + 1) & 0x03); // 0→1→2→3→0 } }(index + 1) & 0x03用位与代替了if (index == 4) index = 0,一行完成环形递增,四个频点刚好是 2 的幂,这种写法在小数组上很顺手。判断条件用(P2IN & BIT1) == 0直接读输入寄存器,上拉状态下松开是高电平,按下是低电平,逻辑清晰。
停表再写装值的动作只持续几个时钟周期,期间 P2.0 保持上一个电平不变,输出波形上看不出任何缺口。这一手是我在实测里被一个「换挡后频率卡在 2Hz 两秒钟」的现象逼出来的,后面第 5 章会完整复盘。
4. 峰值测量与显示模块:ADC12 采样链路和 OLED 刷新节奏
方波产生出来之后,接下来要回答两个问题:这个方波的峰值到底是多少伏,以及怎么让 OLED 上的数字不跳。
4.1 ADC12 单通道单次转换的寄存器组合
ADC12 的寄存器看着多,实际上一共就四组:控制寄存器 0 管采样时间和开关,控制寄存器 1 管触发源和结果存放位置,MCTLx 管通道选择,MEMx 存结果。四者必须一一对应,错一个就是黑匣子。
void adc_init(void) { P6SEL |= BIT1; // P6.1 切到模拟功能 A1 P6DIR &= ~BIT1; // 方向设为输入 ADC12CTL0 = ADC12SHT0_2 + ADC12ON; // 采样保持 8 个 ADC12CLK,开 ADC ADC12CTL1 = ADC12SHP + ADC12CSTARTADD_1 // 采样定时器模式,结果存 MEM1 + ADC12CONSEQ_0; // 单通道单次转换 ADC12MCTL1 = ADC12INCH_1; // MEM1 对应 A1,与 CSTARTADD_1 对齐 }ADC12SHT0_2表示采样保持时间取 8 个 ADC12CLK,配合ADC12SHP=1时,实际采样周期是 4 × 8 = 32 个 ADC12CLK,在 10MHz 下约 3.2μs。这个时间足够让采样电容把 3.3V 的电平吃稳,又不会慢到影响扫描速度。
ADC12CSTARTADD_1和ADC12MCTL1这一对是全篇最容易错的地方:前者是「转换结果放进哪个 MEM 寄存器」,后者是「哪个 MCTL 寄存器负责配置通道」,两者必须编号一致。原始代码写的是CSTARTADD_1配ADC12MCTL2,结果就是读 MEM1 拿到的是 A0 通道的默认配置,测出来的数跟 P6.1 上的信号毫无关系。这个 bug 极其隐蔽,因为程序不报错,OLED 也照样显示数字。
4.2 峰值捕捉:单点采样为什么必错
只用一次转换去测方波峰值,成功率大概只有一半——采样时刻落在高电平上就是对的,落在低电平上就是 0。更麻烦的是这个错误不会稳定复现,示波器上波形很漂亮,OLED 上的数字却时不时跳一下。
正确的思路是在一个完整周期内密集采样,取最大值。25Hz 的周期是 40ms,如果每 200μs 采一次,40ms 能采 200 个点,无论相位怎么漂,必然有大量采样点落在高电平区。
unsigned int adc_peak(void) { unsigned int peak = 0, i; ADC12CTL0 |= ADC12ENC; // 使能转换 for (i = 0; i < 200; i++) { // 200 × 200μs ≈ 40ms,覆盖一个周期 ADC12CTL0 |= ADC12SC; // 软件触发一次转换 while (!(ADC12IFG & BIT1)) ; // 等 MEM1 结果就绪 if (ADC12MEM1 > peak) // 只保留最大值 peak = ADC12MEM1; ADC12IFG &= ~BIT1; // 手动清标志 __delay_cycles(2000); // 约 200μs @10MHz,拉开采样相位 } ADC12CTL0 &= ~ADC12ENC; // 关转换,降低功耗 return peak; }__delay_cycles(2000)在主频 10MHz 下是 200μs,200 次循环合计 40ms,正好等于 25Hz 的一个完整周期。切到 50/75/100Hz 时周期更短,40ms 覆盖了 2~4 个周期,取最大值依然成立,不需要改参数——这就是把扫描窗口按最低频点设计的理由。
这段代码的另一个好处是:它不依赖方波的相位,也不依赖中断时序。哪怕主循环被其他任务推迟了几毫秒,只要 200 次采样覆盖的时间超过一个周期,峰值就是对的。
4.3 码值到毫伏的整数换算与显示布局
ADC12 是 12 位,参考电压默认是 AVCC(3.3V),所以满量程 4095 对应 3300mV。原始代码里用(result*3.3)/4096算出浮点再用sprintf格式化,这段被注释掉了——注释掉是对的,MSP430F5529 没有硬件浮点单元,把sprintf拉进来会多吃几 KB 的 Flash,而且格式化一次要几毫秒。
改成整数运算,一行搞定:
// 码值 → 毫伏,先乘后除,避免整数除法丢精度 unsigned int mv = (unsigned long)peak * 3300UL / 4096UL; LCD_ShowNum(10, 1, test_hz, 4, 16); // 第一行:实测频率 LCD_ShowChar(50, 1, 'H', 16); LCD_ShowChar(58, 1, 'Z', 16); LCD_ShowNum(10, 3, peak, 4, 16); // 第三行左:原始码值 LCD_ShowNum(70, 3, mv, 4, 16); // 第三行右:换算后的毫伏值(unsigned long)peak * 3300先转成 32 位再乘,峰值最大 4095,乘完 13 万多,用 16 位会溢出。这一步转型不能省,否则 4095 × 3300 = 13513500 被截成 16 位,结果完全错乱。
显示布局上,第一行放频率,第三行放峰值和电压,中间留一行空白。这个空白不是浪费——OLED 在刷新时如果上下行同时变化,人眼会感觉整屏在闪,隔一行刷新视觉上稳定得多。刷新节奏也要控制:频率值每 1 秒更新一次,峰值每 1 秒更新一次,两者错开 500ms 写入,避免同一时刻连续几十个 IIC 字节把主循环拖住。
提示:如果想让读数更稳,在
adc_peak()外面再套一层「连续三轮取中位数」,能压掉偶发的电源毛刺。代价是测量窗口从 40ms 拉长到 120ms,对 1 秒的刷新周期来说完全吃得下。
5. 避坑与排查:五个在实验台上真实翻车的点
这一章全是踩坑记录。每一条都能对应到一个具体的、可复现的现象,按「现象 → 原因 → 解决」的顺序写,方便对着排查。
5.1 现象:OLED 上的峰值恒定不动,换什么信号都一样
原因:ADC12CTL1里写的是ADC12CSTARTADD_1,结果存 MEM1,但通道配置写在了ADC12MCTL2。MEM1 对应的通道寄存器是 MCTL1,MCTL2 管的是 MEM2 的通道选择,所以 MEM1 实际用的还是复位默认值 A0,采的是 P6.0 上悬空的电平。程序不报错,因为 MEM1 确实有转换结果,只是它跟 P6.1 没关系。
解决:把通道配置改到ADC12MCTL1 = ADC12INCH_1,并且记住一条规则——CSTARTADD_n和MCTLx的编号必须相同,序列转换时还要保证MCTL的 EOS 位标在最后一个通道上。排查手段很简单:把 P6.1 接地,如果读数不归零,通道八成配错了。
5.2 现象:按键换挡后,方波停住约 2 秒才恢复
原因:在增计数模式下直接改写 TA0CCR0,如果新装值小于当前 TA0R,计数器会一直加到 0xFFFF 才归零,这个周期长度是 65535 个 ACLK 周期,正好 2 秒。从 100Hz 切回 25Hz 时最容易触发,因为装值从 163 跳到 654,而写入瞬间 TA0R 很可能已经超过 163。
解决:改频必须走「停表 → 清计数器 → 写装值 → 启表」四步,也就是 3.3 节里wave_set_freq()的写法。另一种等价做法是等TA0R == 0的瞬间再写,但软件判零有竞争风险,停表法更干净。
5.3 现象:中断嵌套怎么都进不去,按键经常没反应
原因:MSP430 进入任何中断服务程序时会自动清除 SR 寄存器里的 GIE 位,效果等同于全局关中断。原始代码在主程序里用__enable_interrupt()开了总中断,但一旦进了 TIMER0_A0 中断,其他中断全部被挡住,直到这个中断返回。
解决:确实需要嵌套时,在中断服务程序开头显式再开一次__enable_interrupt(),但要清楚代价——嵌套层数一多,栈会迅速吃掉 RAM,而且多个中断同时改同一个全局变量时会出现竞态。我的建议是能不嵌套就不嵌套:这套方案里方波中断只做一次异或,执行几十个周期,按键中断晚响应几微秒毫无影响,根本不需要嵌套。原文提到的「迷惑行为」,多半就是这个自动关中断加上忙等延时叠加出来的。
5.4 现象:1 秒计数窗口里数到的边沿数比实际少 1~2 个
原因:软件轮询法的采样率不够,或者轮询循环里塞了耗时操作。100Hz 方波每 10ms 一个周期,上升沿间隔 10ms,理论上轮询频率只要有 1kHz 就不会漏。但如果while(TA1R < 32768)的循环体里插入了 OLED 刷新或者__delay_cycles,单次轮询周期被拉长到毫秒级,边沿就会被跳过。
解决:计数窗口内保持循环体极简,只做「读 P2OUT → 比较上次状态 → 计数」三件事。更稳妥的方案是用捕获模式:把 TA0CCR1 配成上升沿捕获,每次捕获中断里cnt++,硬件替你抓边沿,CPU 忙不忙都不影响。代价是多占一个捕获比较通道,而这套方案里 TA0 的通道 1 正好空着。
5.5 现象:所有功能都对,就是频率随上电次数飘,有时快千分之几
原因:UCS 配置完成后没有等待时钟稳定。DCO 的 FLL 环路需要一段时间锁定,UCSCTL2写完立刻配定时器,前几十毫秒里 ACLK 和 SMCLK 都还没进入稳态,方波频率自然不准。另一种可能是UCSCTL7里的振荡器故障标志没有清除,FLL 一直在异常状态下工作。
解决:时钟初始化之后加一个等待循环,把XT2OFFG、XT1LFOFFG、DCOFFG三个标志清干净再往下走,然后插一段几十毫秒的延时让 FLL 锁定。这一步在实验台上很难复现,在批量板子上却经常出现,属于典型的玄学问题,但只要把等待加上就彻底消失。
6. 验收与答辩:用一张误差表证明这 25Hz 是真的
验收这件事,最忌讳的是「我觉得对」。把示波器接上 P2.0,把万用表接在方波和地之间,再用秒表对着 OLED 数一分钟,三样东西一摆,方案的可信度立刻不一样。
第一步对频率。示波器测周期,25Hz 应该是 40.0ms,50Hz 是 20.0ms,以此类推;同时看占空比,中断翻转法天然是 50%,只要负载不重、IO 驱动能力够,偏差在 2% 以内都算正常。第二步对峰值,示波器读 Vpp 再除以 2,和 OLED 上的毫伏值比对,差值控制在 3% 以内(约 100mV)就算合格。第三步对计数,让 OLED 显示的频率值和示波器读数同屏出现,两者一致说明边沿计数逻辑没问题。
答辩现场能拿出来的东西,最好是一张这样的表:
| 验收项 | 判据 | 实测手段 | 常见偏差来源 |
|---|---|---|---|
| 方波频率 | 25/50/75/100Hz,误差 < 1% | 示波器测周期 | 装值取整方式 |
| 占空比 | 50% ± 2% | 示波器占空比读数 | IO 驱动能力、示波器探头电容 |
| 峰值幅度 | 与示波器 Vpp/2 差 < 3% | 双通道对比 | ADC 参考电压未校准 |
| 频率显示 | 与示波器读数一致 | 同屏拍照 | 轮询漏边沿 |
| 按键换挡 | 连按 20 次,每次都只跳一档 | 手动计数 | 消抖窗口过短 |
表格填满之后,还有一个必答题:「你怎么保证这不是巧合?」我的答法是现场演示换挡——从 25Hz 一路按到 100Hz 再按回来,示波器上的周期从 40ms 变到 10ms 再变回来,OLED 上的频率和峰值同步变化,整个过程不断波、不卡死、不跳档。能稳定走完这一圈,方案就是成立的。
有个血泪教训我一直记着:第一次交这个实验的时候,我的演示在最后一步翻车了——从 100Hz 切回 25Hz,方波停了整整两秒,老师盯着屏幕没说话,那两秒比两分钟还长。后来才知道是TA0CCR0的改写时机问题。从那以后我每次做定时器相关的项目,改分频、改装值之前都强制走一遍「停表、清零、写值、启表」,一次都没再出过事。希望这套流程也能帮到你。
这份方案里的寄存器配置、峰值扫描逻辑、安全改频时序和那张误差表,都整理在随附的实验文档里,可以直接对着改引脚和频点,搬到你自己的板子上跑通。
本文还有配套的精品资源,点击获取