ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MSP430F5529定时器与ADC实现25Hz方波生成及测量

MSP430F5529定时器与ADC实现25Hz方波生成及测量

简介:面向MSP430F5529单片机学习者的一份实验文档,主题是利用定时器与通用输入输出口产生25Hz方波,再借助内部模数转换器测量方波峰值幅度,并显示在OLED屏上;同时通过按键可将方波频率切换为50Hz、75Hz和100Hz,由模数转换器配合测量并显示变化后的频率。压缩包内包含一个doc格式文档,大小约16.11MB,正文从实验原理讲起,系统介绍定时器Timer_A的工作模式、捕获比较模块与八种输出方式,以及模数转换的采样量化、中断机制等关键知识点,适合单片机课程实验、毕业设计或入门自学。目前已有1696人学习浏览。文档中还给出了定时器配置流程、ADC测量思路和频率切换实现要点,能够帮助读者掌握定时器动态调频、模拟量采集与OLED显示的完整开发路径,对深入理解MSP430F5529内部结构及提升嵌入式编程能力很有价值。

1. 25Hz 方波实验报告的真正价值:一套能跑的定时器+ADC 模板

25Hz 方波不算高难度信号,但要让 MSP430F5529 自己产生、自己测量、再把结果显示出来,中间横着定时器、中断、ADC 三个坎。我拆的这份实验报告,把整条链路写全了:Timer_A 产生 25Hz 方波,ADC12 采集峰值,上升沿计数测频率,OLED 显示,最后还能用按键切到 50/75/100Hz。它不是原理罗列,是带注释、能直接编译下载跑通的完整工程,适合正在啃 F5529 定时器与 ADC 的同学,也适合要做低频信号发生器加幅度测量的开发者。下面按实际调试顺序,把原理、参数和踩坑逐段拆开。

2. 定时器选型:为什么是 Timer_A 而不是 Timer_B/D

MSP430F5529 的定时器资源比想象中丰富,报告开篇花了大篇幅讲四类定时器,一开始看觉得啰嗦,实际拆代码时才明白,选型不清楚,后面 CCR0、TA0CTL 这些寄存器根本对不上号。

2.1 定时器四兄弟:WDT、Timer_A、Timer_B、Timer_D

报告里把 F5529 的定时器归纳成四类,各自定位完全不同。这里用一张表把关键特性拉齐,后面选型就靠它。

定时器计数长度核心特性本实验用在哪
WDT软件可选 8 种定时时间看门狗/定时器双模式,带密码保护,可停止程序开头直接关掉,防止溢出复位
Timer_A固定 16 位4 种计数模式,最多 7 个捕获/比较通道,8 种输出模式,CCR0 独立中断向量TA0 产生方波,TA1 做 1 秒频率计数
Timer_B8/10/12/16 位可编程增加比较锁存器,多组锁存可同步更新,所有输出支持高阻本实验未使用
Timer_D固定 16 位高分辨率模式,双捕获模式,外部故障输入,可与第二定时器同步本实验未使用

选型逻辑很直白:这个实验只需要在半周期精确翻转一个 IO 引脚,Timer_A 一个 CCR0 通道加一路中断就够,Timer_B 的计数长度可配和比较锁存器对低频方波没有增益,Timer_D 的高分辨率与故障输入是电机控制和电源拓扑场景用的。Timer_A 的资料最多、中断向量结构最简单,作为入门模板最合适。

还要注意看门狗。实验代码第一行就是WDTCTL = WDTPW + WDTHOLD;,意思是喂狗周期内程序没跑完也不会被强行复位。很多人刚接触 F5529 时会忘记关 WDT,结果程序运行到一半突然复位,现象极其像「代码写错了」。这份实验报告把这一步放在 main 开头,是标准的工程习惯。

2.2 计数模式与捕获/比较:方波只是「定时溢出 + 引脚翻转」

Timer_A 有四种工作模式,报告里表 2-2 列得很清楚:停止模式、增计数模式、连续计数模式、增/减计数模式。本实验用的是增计数模式,即 TA0R 从 0 往上数,数到 TA0CCR0 时触发事件,然后归零重新开始。

为什么 25Hz 方波要按「半周期」算?因为一次计数溢出只翻转一次引脚,翻转两次才是一个完整周期。25Hz 的周期是 40ms,半周期是 20ms。ACLK 选 32768Hz 时,20ms 对应的计数值是 32768 × 0.02 = 655.36,这就是代码里TA0CCR0 = (int)(32768 / (2 * HZ[index]))的来历。如果把 CCR0 直接设为 1310 就错了,那得到的是 125Hz 的高电平宽度,而不是 25Hz 方波。

捕获/比较是另一组概念。TAxCCTLn里的 CAP 位决定模块工作在捕获还是比较模式:

  • 捕获模式(CAP=1):记录外部事件发生的时间,比如测量脉宽、测量频率,可以选择上升沿捕获、下降沿捕获或双沿捕获。
  • 比较模式(CAP=0):当 TAxR 计数到 TAxCCRn 时,CCIFG 置位,EQUx 信号生效,输出单元按 OUTMODx 产生指定电平变化。

本实验用的是比较模式,且走的是「中断 + 手动翻转」路线,而不是直接配置输出单元。初始化代码里关键三行是这样的:

TA0CCR0 = 655; // 半周期约 20ms TA0CTL = TASSEL_1 + MC_1 + TACLR; // ACLK 时钟源,增计数模式,清零启动 TA0CCTL0 = CCIE; // 使能 CCR0 比较中断

TASSEL_1表示定时器时钟选 ACLK,也就是 32.768kHz;MC_1是增计数模式;TACLR把计数器清零后再启动。CCIE 打开后,每次 CCR0 比较相等都会触发中断,中断服务函数里P2OUT ^= 0x01完成翻转。这套逻辑只看三个寄存器就够,是理解 Timer_A 最精简的入口。

2.3 CCR0 独立中断向量与手动翻转的取舍

F5529 的 Timer_A 中断分成两路:CCR0 单独占用一个中断向量TIMER0_A0_VECTOR,CCR1 到 CCRx 共用另一个中断向量TIMER0_A1_VECTOR。报告代码里用的就是 CCR0 独立向量,中断响应快,也不用在中断里查 TA0IV 判断是谁触发的。

为什么不用输出单元直接生成方波?因为 P2.0 这一脚在默认路径里就是普通 IO,并没有把 TA0.0 的 PWM 输出引出来。想用输出单元得先去查数据手册,把对应的 PxSEL 复用功能打开,然后配OUTMOD_4(翻转模式),才能做到不进中断自动翻转。实验选中断手动翻转,一是代码直观,二是顺便演示了 CCR0 中断的标准写法。

这里有一个非常典型的翻车点:F5529 的引脚功能由 P2SEL0 和 P2SEL1 两个寄存器决定,很多人习惯性写一句P2SEL |= BIT0,自以为是初始化引脚,结果把 P2.0 切去了复用功能,P2OUT 再也管不住这个脚,方波「莫名其妙消失」。实验代码里从头到尾没动 P2SEL,P2.0 保持通用 IO 功能,信号就能正常输出。

3. 生成 25Hz 方波:CCR0 计算、时钟配置与中断反转

原理讲完,这一章直接落到代码上。25Hz 方波的核心就是两件事:把 CCR0 算准,把时钟配对。CCR0 算错最多是频率偏一点,时钟配错则是整个系统跑不起来。

3.1 频率公式与 CCR0 取值:为什么是 32768/(2×25)

代码里的查找表定义如下:

unsigned int HZ[4] = {25, 50, 75, 100};

主循环每次切换频率时,重新计算TA0CCR0。计算公式已经说了,现在把四个频率的实际取值和误差拉一张表,方便你照着验算。

目标频率CCR0 理论值代码取整实际频率误差
25Hz655.3665525.014Hz0.06%
50Hz327.6832750.076Hz0.15%
75Hz218.4521875.156Hz0.21%
100Hz163.84163100.515Hz0.51%

误差来源是取整。用 32768Hz 做定时器时钟时,CCR0 只能是整数,频率越高取整误差占比越大。100Hz 时 0.51% 的误差对 OLED 显示和按键切频实验完全够用,但如果要做精密信号源,就得把定时器时钟换成 SMCLK 的 10MHz,同样的取整误差会缩小到 0.02% 以内。代价是中断频率变高,中断服务函数里要做的事越少越好。

3.2 时钟初始化代码:ACLK 选 REFOCLK,MCLK 拉到 10MHz

报告实验七的代码段里,时钟初始化占了很大篇幅,而且注释写得很实在。拆出来看是这样:

WDTCTL = WDTPW + WDTHOLD; // 关闭看门狗,防止程序跑飞复位 UCSCTL3 |= SELREF_2; // 参考时钟选 REFOCLK(内部 32.768kHz) UCSCTL0_H = 0x08; // DCO 频率范围设置 UCSCTL1 |= DCORSEL_4; // DCO 档位选择 4 UCSCTL2 |= 304; // FLLN=304,DCO 倍频到约 10MHz UCSCTL2 |= FLLD_0; // DCOCLKDIV 配置 UCSCTL4 |= SELA_2; // ACLK 选 REFOCLK,得到 32768Hz

这里的时钟思路是:ACLK 固定用内部 REFOCLK 的 32.768kHz,专门喂两个定时器和 ADC 的采样定时器;MCLK 用 FLL 倍频到约 10MHz,喂 CPU 和 ADC12 内核。为什么 MCLK 要拉这么高?后面 ADC 转换、OLED 刷新、按键消抖的忙等循环都要 CPU 跑,主频低了主循环会明显卡顿,报告里专门提了「主时钟频率要设置恰当,要高于 ADC 的采样频率」,这个坑在第五章还会展开。

代码里没有配任何外部晶振。如果开发板上焊了 32.768kHz 晶振且想追求更准的时基,可以把 SELA 改成选 XT1CLK,但晶振起振需要时间,失效时系统会自动切回 REFOCLK。实验报告用内部参考是更省事的做法,25Hz 方波本身也不需要晶振级精度。

3.3 中断式反转:CCIE 打开后,半周期翻转一次

方波产生的中断服务函数是整个工程里最短的函数:

#pragma vector = TIMER0_A0_VECTOR __interrupt void time_a0(void) { P2OUT ^= 0x01; // P2.0 电平反转,产生半个周期 }

逻辑就一句话:每次 CCR0 中断到达,P2.0 输出取反。初始化时TA0CCTL0 = CCIE只打开了 CCR0 中断,别忘了主函数里还有一句__enable_interrupt(),这是总中断开关。很多人写完 CCIE 忘记开 GIE,现象就是定时器在跑但引脚永远不变,查半天发现总中断没开。

P2OUT ^= 0x01用的是异或翻转,不是赋值。好处是翻转不依赖当前状态,就算某次中断里状态被外部改过,异或仍然能恢复正确电平。如果改成P2OUT &= ~BIT0或者直接赋值,方波占空比会被破坏,示波器上看就是「半个周期宽、半个周期窄」。

中断式翻转的性能边界也要清楚:25Hz 时每 20ms 才进一次中断,中断函数只有一条异或指令,CPU 占用几乎为零。但如果把频率拉到 10kHz 以上,每 50μs 一次中断,中断进出开销就会开始占 CPU。高频方波建议换成输出单元 + OUTMOD_4 翻转模式,那才是硬件生成信号的正路。

4. ADC12 采集与 OLED 显示:把波形变成 4096 级数字

方波从 P2.0 出来后,实验第二部分是把引脚信号送给 ADC 测峰值,再把结果显示在 OLED 上。这里有个容易误读的点:实验里 ADC 测的其实是信号自身幅度,12 位 ADC 的量程是 0~4095,对应 0~3.3V。

4.1 ADC12 配置:通道、采样保持时间与单次转换

报告的 ADC 初始化代码不长,但寄存器编号有一个隐蔽的坑,先按手册对齐后的标准写法给出:

P6SEL |= BIT1; // P6.1 复用为 ADC 输入通道 A1 ADC12CTL0 = ADC12SHT0_2 + ADC12ON; // 采样保持时间档位,模块上电 ADC12CTL1 = ADC12SHP + ADC12CSTARTADD_1 + ADC12CONSEQ_0; // 采样定时器触发,从 MEM1 开始,单通道单次转换 ADC12MCTL1 = ADC12INCH_1; // 选择输入通道 A1

逐项说:ADC12SHT0_2控制采样保持时间,对应公式是采样周期 = 4 × ADC12CLK × ADC12SHTx,档位 2 就是 8 个 ADC12CLK 周期。对测方波这种低阻抗信号源,这个采样窗口足够让内部电容充到稳定电平。ADC12ON是给 ADC12 模块上电,不写这个,后面配置全是空中楼阁。

ADC12SHP把采样信号切到采样定时器控制,也就是 SAMPCON 高电平持续时长由 SHT 决定,而不是由外部 SHI 引脚电平决定。ADC12CSTARTADD_1指定转换结果从 MEM1 开始存放,ADC12CONSEQ_0是单通道单次转换模式。主循环里每次触发一次转换,读一次结果。

注意原报告代码里写的是ADC12MCTL2 |= ADC12INCH_1,但转换起始地址是 MEM1。从寄存器编号对应关系看,MEM1 应配 MCTL1,MCTL2 是给 MEM2 用的。原代码能跑是因为复位后 MCTL1 的默认通道也常是 A0,一旦要换通道就会出问题。这个细节我放在第五章避坑里细讲,这里先按正确写法来。

主循环的触发代码:

while (1) { ADC12CTL0 |= ADC12ENC; // 使能转换 ADC12CTL0 |= ADC12SC; // 软件触发一次采样转换 if (ADC12IFG1) // MEM1 转换完成标志 { result = ADC12MEM1; // 读出 12 位结果,范围 0~4095 } }

ADC12ENC是转换使能位,每次转换都要确认它是置位的,软件触发靠ADC12SC拉一个启动脉冲。转换完成后中断标志 IFG1 置位,读 MEM1 会返回 12 位数据,同时硬件自动清标志。这块逻辑简单,但要记住ADC12SC是一次性触发,下一次转换前必须重新置位,你如果在循环里只置一次,后面读到的永远是同一帧数据。

这里插一句报告没明说但重要的边界:单次转换读到的是触发时刻的瞬时电平,不是方波的峰值。方波高电平时触发读到的才是峰值,低电平时读到接近 0V。实验代码没有做多次采样取最大值,所以 OLED 上的 result 会随着触发时机在峰值和 0 之间跳变。想严谨测峰值,得改成连续采样 N 次取最大值,或者用比较器捕获峰值,这是很多人把「ADC 读一次」误当成「ADC 测峰值」的常见误区。

4.2 上升沿计数测频率:一秒钟内数几次跳变

报告里的频率测量没有用捕获模块,而是用 TA1 做 1 秒定时,主循环里轮询 P2.0 的电平跳变,数出上升沿个数。代码核心如下:

TA1CCR0 = 0xFFFF; TA1CTL = TASSEL_1 + MC_1 + TACLR; // ACLK 增计数,从 0 往上数 test_hz = 0; flag = 0; while (TA1R < 32768) // 计数到 32768,约 1 秒 { if ((P2OUT & BIT0) == 0) flag = 1; // 先记录低电平状态 if (flag == 1 && (P2OUT & BIT0)) { test_hz++; // 检测到上升沿,频率计数加一 flag = 0; } }

思路很朴素:1 秒内数到几个上升沿,频率就是几 Hz。flag的作用是防止同一个上升沿被重复计数——必须先看到低电平,再看到高电平,才算一次完整跳变。TA1 的时钟也是 ACLK 的 32768Hz,计数到 32768 正好 1 秒。

注意它读的是 P2OUT 而不是 P2IN。P2OUT 是输出锁存器的当前值,在引脚配置为输出时和引脚实际电平一致,用来数自己产生的方波没问题。如果要测外部输入的信号,建议改读 P2IN,同时确认外部信号电平满足施密特触发阈值。

轮询测频的上限也要心里有数。while 循环每次轮询间隔约几十条指令,对 100Hz 方波来说半周期 5ms,漏计数风险很小;但频率上到 1kHz 以后,半周期只有 0.5ms,轮询很可能漏掉跳变。要测高频,正确做法是改成 Timer_A 捕获模式,用 CCI 上升沿直接捕获 TAxR,两次捕获值的差就是周期,CPU 完全不用参与。

4.3 OLED 显示:原始值与电压值的两条路

显示部分调用的是实验一里封装好的 LCD 库:

LCD_ShowNum(10, 1, test_hz, 4, 16); // 第一行显示频率,4 位 LCD_ShowChar(50, 1, 'H', 16); LCD_ShowChar(58, 1, 'Z', 16); LCD_ShowNum(10, 3, result, 4, 16); // 第三行显示 ADC 原始采样值

LCD_ShowNum 的函数签名一般是(x, y, 显示的数字, 位数, 字号),这里 x=10 y=1 是第一行起始坐标,test_hz 显示 4 位,字号 16。OLED 是 I2C 接口,IIC.c 里实现了底层驱动,LCD_Init() 在 main 里先调用过。

报告里有一段被注释掉的浮点显示代码,值得单独拿出来说:

// result_s = (result * 3.3) / 4096; // 12 位满量程 4096 对应 3.3V // sprintf(buff, "%3.2f", result_s); // 格式化成两位小数 // string = &buff[0]; // LCD_ShowString(10, 3, string, 16); // 显示浮点电压

这其实是把 ADC 原始值换算成电压的正确思路。ADC12 是 12 位分辨率,满量程 4096 对应参考电压 3.3V,所以 2000 对应的电压就是 2000 × 3.3 / 4096 ≈ 1.61V。报告最终选择直接显示原始值,因为实验目的是验证 ADC 工作正常,整数显示更省事。

这里有个隐蔽问题:unsigned char buff[3]只有 3 字节,放不下%3.2f格式化出来的字符串。比如 1.61 实际是 4 个字符加结束符,需要 5 字节;如果数值到 3.30,就是 5 字符加结束符共 6 字节。buff[3] 一定越界,越界写坏相邻内存后 OLED 会显示乱码,严重时程序跑飞。这个坑也留给避坑章。

5. 避坑与常见问题排查:中断嵌套、时钟频率与寄存器错位

这份报告最值钱的部分在最后的心得体会里,作者把调试时踩的坑几乎全写出来了。我把它们按「现象 → 原因 → 解决」整理成五条,分成两组,前两条是卡死级别的,后三条是隐蔽翻车级别的。

5.1 中断与时钟:先解决两个「卡死」级问题

坑一:进入中断后默认屏蔽其他中断,按键切频失灵。

现象:按下切换频率的按键,方波频率没变化;偶尔按很多次才响应一次。

原因:MSP430 的中断机制默认不嵌套。每次进入中断服务函数,硬件自动把 GIE(总中断使能位)清零,此时无论高级还是低级中断都不响应,直到中断返回。报告原文写的是「进入中断后,默认屏蔽其他中断,无论高级或者低级」,这是 F5529 与 ARM Cortex-M 在中断行为上最大的不同,很多人从 ARM 转过来会在这里翻车。

解决:中断服务函数里只做最必要的事,比如置一个标志位就返回,主循环里再处理按键逻辑;如果确需嵌套,可以手动执行__bis_SR_register(GIE)重新打开总中断,但要评估重入风险。这个实验里按键中断和 TA0 中断频率都低,中断里干完活直接清标志是最简单的方案。

坑二:主时钟频率设置不当,主程序「难以前进」。

现象:OLED 长时间不刷新,按键反应迟钝,用仿真器暂停时发现 CPU 一直在中断里转。

原因:报告原话是「主时钟频率要设置恰当,要高于 ADC 的采样频率,否则如果连续采样的话,会一直进入中断,主程序难以前进」。本质是中断频率高于 CPU 处理能力,或者某个中断服务函数执行时间偏长,导致 while(1) 主循环被饿死。

解决:按 3.2 节配置把 MCLK 提到 10MHz;如果开了 ADC 中断,确认中断服务函数里没有耗时操作;采样触发频率不要高于实际需要。我一般会加一个计数器在主循环里自增,再用仿真器看它是否增长,来快速判断主循环是不是被饿死了。

5.2 寄存器与缓冲区:两个隐蔽翻车点,外加按键消抖

坑三:ADC12MCTL2 与 MEM1 错位,换通道后读数全乱。

现象:把采样通道从 A1 换成别的通道,ADC12MEM1 读出来的值恒为 0,或者跟另一个通道的信号混在一起。

原因:ADC12CSTARTADD_1指定转换从 MEM1 开始,单通道单次模式下实际使用的是 MEM1 对应的 MCTL1 做通道配置。原报告代码把ADC12INCH_1写在了ADC12MCTL2上,等于给 MEM2 配通道,MEM1 用的还是复位默认值。调试时如果不改通道可能侥幸能跑,一换通道就穿帮。

解决:统一成ADC12MCTL1 = ADC12INCH_1,跟 CSTARTADD_1 对齐;如果确实想用 MCTL2,就把 CSTARTADD 改成 2,让转换从 MEM2 开始。我在拆这份代码时按手册把寄存器编号对齐了,这是「看报告代码容易抄错」的典型样例。

坑四:sprintf 写浮点数,buff 数组太小导致乱码。

现象:启用被注释的浮点电压显示代码后,OLED 出现奇怪字符,甚至程序直接跑飞。

原因:unsigned char buff[3]只有 3 字节,而%3.2f格式化的字符串至少 4 个字符加结束符,写越界后覆盖了相邻变量,LCD 显示数据和中断标志全被污染。

解决:buff 定义加大到unsigned char buff[8],或者干脆不用 sprintf,直接显示原始 ADC 值。低频方波实验里显示原始值足够了,真要显示电压,整数部分和小数部分分开用 LCD_ShowNum 拼接,可以完全避开浮点格式化。

坑五:按键消抖用中断里忙等 20ms,主循环被阻塞。

现象:按键响应偏慢,切换频率瞬间 OLED 刷新出现明显停顿。

原因:按键中断服务函数里写了while (TA1R <= 655)做 20ms 忙等,这期间 GIE 是关闭的,主循环和 TA0 方波中断虽然会照常触发(TA0 中断是硬件事件,GIE 只控制响应),但主循环的 OLED 刷新被卡住。

解决:25~100Hz 方波场景下这 20ms 可以忍,不算硬伤。更稳的做法是按键中断里只置标志,主循环里检测到标志后用定时器轮询消抖,这样主循环永远不被中断长时间霸占。这属于「低频实验里能跑,但心里要清楚这笔开销」的典型例子。

6. 进阶:按键动态切频与示波器实测四步法

6.1 按键中断改 CCR0:更新时机比数值更重要

报告拓展部分要求按键循环切换 50/75/100Hz,代码用的是 P2.1 下降沿触发中断,中断里做 20ms 消抖,然后把 index 在 0~3 之间循环,最后更新 TA0CCR0:

#pragma vector = PORT2_VECTOR __interrupt void Port2_1(void) { if (P2IFG & BIT1) { // 20ms 消抖,用 TA1 数 655 个 ACLK 周期 TA1CCR0 = 0xFFFF; TA1CTL = TASSEL_1 + MC_1 + TACLR; while (TA1R <= 655); if ((P2IN & BIT1) == 0) { index++; if (index == 4) index = 0; } } TA0CCR0 = (int)(32768 / (2 * HZ[index])); // 重新计算半周期阀值 P2IFG &= ~BIT1; }

动态改 CCR0 的关键不在数值,而在更新时机。增计数模式下,如果写入的新 CCR0 比当前 TA0R 还小,计数器的行为是:先按旧值溢出归零一次,再按新 CCR0 重新计数。结果就是波形在切换瞬间会多出半个或一个异常宽度的脉冲,相位跳一下。低频方波无所谓,示波器上看就是频率变了;但如果拿这套代码去生成 PWM 驱动电机或做 D/A 波形,这个相位跳变轻则噪声重则抖动。

我一般会先读 TA0R,确认它等于 0 或小于新 CCR0 时再写寄存器。更干净的办法是只在 CCR0 中断服务函数里改,因为中断触发瞬间 TA0R 刚归零,此时写新值就是严格周期同步。

6.2 示波器实测四步法

代码能编译下载只是起点,验证波形才是收尾。我的习惯是固定四步:

第一步,示波器探头接 P2.0,看周期。25Hz 对应 40ms,50Hz 对应 20ms,75Hz 对应约 13.3ms,100Hz 对应 10ms。按键每按一次,周期必须按这个序列跳变,跳不过去就是 index 或 HZ 数组的问题。

第二步,OLED 交叉验证。频率显示值和示波器读数相差在 1Hz 以内,说明上升沿计数逻辑可靠;如果显示值稳定偏离,多半是 1 秒定时基准不对,回去查 TA1 的 TASSEL 有没有选对。

第三步,ADC 线性度验证。给 P6.1 接一个可调分压器,从 0V 调到 3.3V,看 result 是否从约 0 涨到约 4095。如果某个点跳变明显非线性,检查采样保持时间档位和信号源内阻是否匹配。

第四步,动态切换观察。按下按键瞬间盯着示波器,确认频率切换是即时的,且波形没有长时间乱态。

从那以后,我每次用定时器做信号发生,都强制走一遍「先算 CCR0 → 再看中断里更新时机 → 示波器量周期 → OLED 交叉核对」的流程。这套模板到现在还在用,低频方波、PWM 呼吸灯、超声波测距的时基都从它改出来的。报告里的完整代码带注释,适合下载后自己把 HZ 数组改一改、把显示格式换成电压值再跑一遍,能挖出不少寄存器层面的细节,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进