深入解析MCU定时器与PWM:从原理到实战,掌握MC68HC08AB16A TIMB模块
1. 项目概述:为什么需要深入理解MCU的定时器与PWM?
在嵌入式开发领域,尤其是涉及电机驱动、LED调光、开关电源或蜂鸣器发声等场景时,脉宽调制(PWM)和定时器是工程师手中最基础也最强大的工具。它们就像是系统的心跳和节拍器,负责生成精确的时间基准和可调占空比的波形。很多新手在接触这些概念时,往往只停留在调用库函数或复制示例代码的层面,一旦遇到波形异常、占空比不准或者系统进入低功耗后定时器“罢工”等问题,就会束手无策。
究其根本,是因为没有吃透硬件模块的工作原理和寄存器配置的底层逻辑。今天,我们就以一款经典的8位微控制器——MC68HC08AB16A的TIMB(定时器B)模块为例,进行一次彻底的“庖丁解牛”。我将结合自己十多年在工控和消费电子领域的踩坑经验,不仅带你走通标准配置流程,更会深入解析每个寄存器位背后的设计意图,以及那些数据手册上不会明说,但实际调试中至关重要的“潜规则”。无论你是正在学习这款老牌MCU的学生,还是在维护或升级旧有产品的工程师,相信这篇近万字的详解都能让你对定时器和PWM有一个脱胎换骨的理解。
2. TIMB模块架构与核心设计思想拆解
在动手写代码之前,我们必须先建立对TIMB模块的宏观认知。把它想象成一个多功能数字时钟/信号发生器综合体。
2.1 核心组件与工作流程
TIMB的核心是一个16位向上计数器(TBCNTH:TBCNTL)。这个计数器就像一块永不停止的电子秒表,其“滴答”声的来源可以是内部总线时钟经过分频后的信号,也可以是来自外部引脚PTD4/TBCLK的时钟。计数器的计数上限由一个叫做模数寄存器(TBMODH:TBMODL)的值决定。当计数器从0开始累加,一直数到这个模数值时,就会产生一次“溢出”(Overflow),计数器清零并重新开始,同时置位溢出标志位(TOF)。这个“从0到模数”再回到0的循环,就定义了一个PWM信号的周期。
那么占空比如何控制呢?这就要靠四个通道寄存器(TBCHxH:TBCHxL)了。每个通道都配备了一组这样的寄存器,用于存放一个比较值。在计数器累加的过程中,硬件会不断地将当前计数值与通道寄存器里的值进行比较。当两者相等时,就触发一次“比较匹配”事件。我们可以通过配置,让这个匹配事件去控制对应I/O引脚(PTF4/TBCH0等)的输出电平:是拉高、拉低还是翻转。PWM信号的高电平宽度(即脉冲宽度),就是由这个比较值决定的。
举个例子:假设总线时钟是8MHz,我们通过预分频器选择除以8,那么计数器的时钟就是1MHz(周期1微秒)。如果我们将模数寄存器设置为1000,那么PWM的周期就是1000 * 1us = 1ms(频率1kHz)。接着,我们将通道0的比较寄存器设置为300。那么在每个1ms的周期内,当计数器从0数到300时,引脚输出发生一次动作(比如由低变高);当计数器数到1000溢出时,引脚再发生一次动作(比如由高变低)。这样,我们就得到了一个高电平时间为300us,低电平时间为700us,占空比为30%的PWM波。
2.2 关键模式解析:无缓冲PWM vs. 缓冲PWM
这是TIMB模块的一个高级特性,也是容易混淆的点。数据手册里提到了“Unbuffered”和“Buffered”两种PWM/输出比较模式。
无缓冲模式(Unbuffered):这是最直接的模式。你写入通道寄存器(TBCHxH:TBCHxL)的值会立刻生效,用于下一次及之后的比较。如果你在PWM波形输出过程中修改了这个值,新的脉宽可能会在当前周期就立即生效,这可能导致一个“毛刺”或宽度异常的脉冲,破坏波形的连续性。这种模式适用于对实时性要求极高,且不介意单个周期波形可能畸变的场景。
缓冲模式(Buffered):这是更安全、更常用的PWM模式。在此模式下,TIMB为通道寄存器准备了一个“影子寄存器”(Buffer)。你写入TBCHxH:TBCHxL的值并不会立即参与比较,而是先暂存在这个影子寄存器里。只有当当前PWM周期结束(即计数器溢出)时,影子寄存器中的值才会被自动加载到真正的比较寄存器中,在下一个周期生效。这就保证了你在任意时刻修改脉宽,都不会打断当前正在输出的PWM周期,从而获得平滑、无毛刺的波形变换。这对于电机调速、灯光渐变等应用至关重要。
在MC68HC08AB16A中,通道0和1可以配对(通过设置MS0B),通道2和3可以配对(通过设置MS2B)来实现缓冲PWM。在配对模式下,通常由通道0或通道2的寄存器作为主控制寄存器,其影子寄存器机制确保了输出的稳定性。
2.3 中断与低功耗协同设计
TIMB模块提供了丰富的中断源:计数器溢出(TOF)和每个通道的比较/捕获事件(CHxF)。合理利用中断可以解放CPU,让它在等待定时事件时去处理其他任务。例如,你可以使能溢出中断,在每个PWM周期结束时进行一些状态检查或计算下一个周期的参数。
更巧妙的是它与MCU低功耗模式的互动。在WAIT模式下,CPU休眠,但TIMB可以继续运行。这意味着你可以用TIMB生成一个周期性的中断来唤醒CPU,实现极低功耗的间歇性工作。这里有一个非常重要的坑:如果你计划用TIMB中断从WAIT模式唤醒MCU,那么在进入WAIT模式前,绝对不能设置TSTOP位来停止定时器。一旦停了,中断就无法产生,MCU就可能“睡死”过去。在STOP模式下,整个TIMB模块都会停止以节省功耗,直到外部中断将其唤醒。
3. 寄存器配置详解与实操要点
理解了架构,我们进入实战环节。配置TIMB生成PWM,本质上就是操作一系列寄存器。下面我不仅列出步骤,更会解释每一步“为什么”要这么做。
3.1 核心寄存器地图速览
首先,我们得知道要去哪里“拨动开关”。TIMB的主要寄存器在内存映射中的地址如下:
| 寄存器名称 | 地址 (高字节) | 地址 (低字节) | 主要功能 |
|---|---|---|---|
| TBSC(状态控制) | $0040 | - | 控制定时器启停、复位、选择时钟源、管理溢出中断 |
| TBCNT(计数器) | $0041(TBCNTH) | $0042(TBCNTL) | 只读,反映16位计数器的当前值 |
| TBMOD(模数) | $0043(TBMODH) | $0044(TBMODL) | 读写,设定计数器的上限值,决定PWM周期 |
| TBSC0/1/2/3(通道控制) | $0045,$0048,$0032,$0035 | - | 配置各通道模式、中断、输出行为 |
| TBCH0/1/2/3(通道值) | $0046,$0049,$0033,$0036 | $0047,$004A,$0034,$0037 | 存放各通道的比较值或捕获值,决定PWM脉宽 |
3.2 分步配置流程与原理剖析
根据数据手册的初始化流程,结合最佳实践,我总结出以下配置步骤。请务必按顺序操作,否则可能导致不可预期的输出。
第一步:停止并复位定时器(操作TBSC寄存器)在修改任何定时器参数(尤其是周期和脉宽)之前,必须先让定时器停下来,并把它归零。这就像调整一个正在运转的机械钟表,你得先按住指针。
// 假设我们使用C语言和特定的编译器头文件 TBSC = 0x60; // 二进制 0110 0000这行代码同时设置了TSTOP=1(停止计数)和TRST=1(复位计数器)。TRST是只写位,读出来永远是0,所以这个值主要是为了设置TSTOP。为什么先停止再复位?因为如果计数器还在跑,你复位它之后它可能立刻又开始从0计数,导致你的后续配置还没完成,第一个PWM周期就已经开始了,产生混乱的波形。
第二步:设置PWM周期(写入TBMODH:TBMODL)周期 = (模数值 + 1) * 计数器时钟周期。假设我们需要1kHz的PWM(周期1ms),计数器时钟为1MHz(1us),那么模数值 = (1ms / 1us) - 1 = 999。
TBMODH = 0x03; // 999 = 0x03E7,高字节为0x03 TBMODL = 0xE7; // 低字节为0xE7重要提示:写入模数寄存器时,必须先写高字节(TBMODH),再写低字节(TBMODL)。硬件设计如此:写高字节会暂时“锁住”溢出标志和中断,直到低字节被写入后才更新内部寄存器并解除锁定。这防止了在写入过程中发生不完整的模数更新,导致产生一个极短或极长的错误周期。
第三步:设置PWM脉冲宽度(写入TBCHxH:TBCHxL)脉宽 = 比较值 * 计数器时钟周期。假设我们需要30%占空比,那么比较值 = 999 * 30% ≈ 300 (0x012C)。
TBCH0H = 0x01; // 通道0,高字节 TBCH0L = 0x2C; // 低字节对于缓冲PWM模式,此时写入的值是存入影子寄存器,将在下一个周期生效。对于无缓冲模式,则需谨慎,因为可能立即生效。
第四步:配置通道工作模式(操作TBSCx寄存器)这是最复杂也最关键的一步,需要配置多个控制位。我们以配置通道0为“缓冲PWM,比较匹配时输出低电平”为例:
// 目标:MS0B:MS0A = 1:0 (缓冲PWM), ELS0B:ELS0A = 1:0 (比较匹配时清零输出),TOV0=1 (溢出时翻转) // TBSC0寄存器位:[CH0F][CH0IE][MS0B][MS0A][ELS0B][ELS0A][TOV0][CH0MAX] // 我们需要: MS0B=1, MS0A=0, ELS0B=1, ELS0A=0, TOV0=1 // 即二进制 0011 0100 = 0x34 TBSC0 = 0x34;- MS0B:MS0A=1:0:这选择了“缓冲输出比较/PWM”模式。注意,当MS0B=1时,它直接链接了通道0和1,通道1的控制寄存器TBSC1将被禁用,其引脚PTF5/TBCH1恢复为通用I/O。这是硬件联动的,务必知晓。
- ELS0B:ELS0A=1:0:这表示“在比较匹配时清零输出”(Clear Output on Compare)。这意味着当计数器值等于TBCH0中的比较值时,对应引脚输出低电平。
- TOV0=1:这是PWM生成的灵魂所在!
TOVx位控制“溢出时翻转”(Toggle on Overflow)。当计数器溢出(从模数值回到0)时,引脚输出电平会发生翻转。组合起来就是PWM的工作原理:假设初始输出为高。当比较匹配时(ELS设置的动作),输出被强制为低;当计数器溢出时(TOV动作),输出翻转,从低变回高。如此循环,就产生了从高电平开始,在比较点变低,在周期结束变高的标准PWM波。如果你想得到从低电平开始的PWM,只需将ELS配置为“比较匹配时置位输出”(1:1)即可。
第五步:启动定时器(再次操作TBSC寄存器)所有配置完成后,释放定时器,让它开始奔跑。
TBSC = 0x00; // 清除TSTOP位,启动定时器。TRST位是只写的,无需关心。此时,你就能在PTF4/TBCH0引脚上测量到频率1kHz、占空比30%的PWM波形了。
3.3 关键位域深度解析与避坑指南
关于TOVx的致命禁忌:数据手册用了一个NOTE强烈警告:在PWM信号生成中,绝对不要将通道编程为“在输出比较时翻转”(即ELSxB:ELSxA = 0:1)。为什么?这破坏了PWM的生成机制。如果比较时是翻转,溢出时也是翻转,那么输出就是一个50%占空比的方波,你无法通过改变比较值来调节脉宽。更严重的是,这会妨碍可靠地生成0%占空比(常低)和100%占空比(常高),并且在软件出错或噪声干扰时,通道无法自我校正。这是一个硬性规定,务必遵守。
生成0%和100%占空比:这是常见的需求,比如用PWM控制电机使能。TIMB提供了优雅的硬件支持:
- 0%占空比(常低):清除
TOVx位(设为0)。这样,溢出时引脚不会翻转。同时,将ELSx配置为“比较匹配时清零输出”(1:0)。由于比较值可以设置为大于模数值(这样比较匹配永远不会发生),或者简单地让比较匹配的动作去清零一个本来就是低电平的输出,结果就是输出永远保持低电平。 - 100%占空比(常高):除了清除
TOVx位,还需要设置CHxMAX位。设置CHxMAX会强制PWM输出保持在100%占空比的电平(即高电平,如果ELS配置为比较时清零的话,这个“100%电平”就是比较动作的反状态)。CHxMAX位在设置后的下一个周期生效,提供了确定性的行为。
- 0%占空比(常低):清除
读取计数器的“锁存”机制:
TBCNTH和TBCNTL是16位只读计数器。但硬件设计了一个细节:当你读取高字节TBCNTH时,当前低字节TBCNTL的值会被锁存到一个缓冲器中。随后你再读取TBCNTH,得到的还是之前锁存的高字节,只有当你读取了TBCNTL之后,锁存才会释放,下一次读TBCNTH才会获取新的值。这个机制是为了让你能原子性地读取完整的16位计数器值,防止在读取高低字节的间隙,计数器变化导致数据错位。因此,正确的读取顺序必须是:先读TBCNTH,再读TBCNTL。在中断服务程序中尤其要注意这一点。
4. 实战应用:配置双通道缓冲PWM驱动直流电机
理论说得再多,不如来一个实际案例。假设我们要用MC68HC08AB16A驱动一个直流电机,需要两路互补的PWM(例如用于H桥控制),并且要求能平滑调整速度(改变占空比)。
4.1 硬件连接与需求分析
- 引脚分配:使用通道0(PTF4/TBCH0)和通道1(PTF5/TBCH1)生成一对PWM。我们将它们配置为缓冲PWM模式,这样改变速度时波形不会抖动。
- 目标参数:PWM频率定为20kHz(周期50us),这个频率高于人耳听觉范围,可以避免电机啸叫。计数器时钟选择内部总线时钟8MHz 8分频,即1MHz。
- 设计思路:利用通道0和1的链接功能(MS0B=1)。此时,通道0为主通道,其寄存器
TBCH0H:TBCH0L控制脉宽,TBSC0控制输出行为。通道1的寄存器被禁用,但其引脚PTF5/TBCH1会输出与通道0互补的PWM波形(这是缓冲PWM链接模式的特性之一,非常适合H桥驱动)。
4.2 代码实现与注释
/* MC68HC08AB16A 双通道缓冲PWM初始化代码 */ /* 目标:20kHz PWM,初始占空比50%,使用通道0和1链接模式 */ #define BUS_CLK_MHZ 8 // 假设总线频率8MHz void TIMB_PWM_Init(void) { // Step 1: 停止并复位TIMB计数器 // TSTOP=1, TRST=1, 其他位清零(禁用溢出中断,预分频选择/1,但马上会改) TBSC = 0x60; // Step 2: 配置时钟预分频。目标计数器时钟=1MHz,所以8MHz/8=1MHz => PS[2:0]=011 // TBSC寄存器: [TOF][TOIE][TSTOP][-][-][PS2][PS1][PS0] // 我们保持TSTOP=1, TRST会在写入时自动清零,设置PS=011。 // 注意:先停止定时器再改时钟源是好习惯。 TBSC = 0x63; // 0110 0011 // Step 3: 设置PWM周期。周期 = (MOD + 1) / 计数器时钟频率 // 计数器时钟频率 = 1MHz, 周期 = 50us = 0.00005s // MOD = (周期 * 计数器时钟频率) - 1 = (50e-6 * 1e6) - 1 = 50 - 1 = 49 TBMODH = 0x00; // 49 = 0x0031 TBMODL = 0x31; // Step 4: 设置初始脉冲宽度(占空比50%)。比较值 = MOD * 占空比 = 49 * 0.5 ≈ 25 (0x0019) TBCH0H = 0x00; TBCH0L = 0x19; // 通道0比较值,也即初始脉宽 // Step 5: 配置通道0为缓冲PWM模式,并定义输出行为。 // 我们需要:MS0B=1 (缓冲模式), MS0A=0, ELS0B:ELS0A=1:0 (比较匹配时清零输出), TOV0=1 (溢出时翻转) // 这将产生一个初始为高,比较点变低,溢出点变高的PWM。 // 在链接模式下,通道1会自动输出互补信号。 // TBSC0 = 0x34; // 0011 0100 // 但我们可能希望使能通道0的比较中断,以便在每次脉宽结束时做某些处理(可选) // CH0IE=1 使能中断。则值为 0111 0100 = 0x74 TBSC0 = 0x74; // Step 6: 启动TIMB计数器 // 只需清除TSTOP位。保持预分频设置(PS=011)和溢出中断禁用(TOIE=0)。 TBSC = 0x03; // 0000 0011 } /* 用于动态调整PWM占空比的函数 */ void Set_Motor_Speed(unsigned char duty_percent) { unsigned int compare_value; // 计算新的比较值,确保不超过模数值 // MOD = 49, duty_percent范围0-100 if(duty_percent > 100) duty_percent = 100; compare_value = (49 * duty_percent) / 100; // 写入通道0的比较寄存器。由于是缓冲模式,新值将在下一个PWM周期生效。 // 必须先写高字节,再写低字节。 TBCH0H = (unsigned char)(compare_value >> 8); TBCH0L = (unsigned char)(compare_value & 0x00FF); // 注意:在极端情况下,如果duty_percent为100,compare_value=49。 // 此时PWM输出为常高吗?不是的。因为我们的配置是“比较匹配清零,溢出翻转”。 // 当比较值等于模数值时,比较事件和溢出事件几乎同时发生(先后顺序有硬件优先级)。 // 为了获得真正的100%占空比,更好的方法是使用CH0MAX位。 }4.3 进阶:实现0%-100%全范围平滑控制
如上代码注释所述,当占空比需要达到100%时,仅仅将比较值设为模数值是不够可靠的,因为比较事件和溢出事件的竞争可能导致最后一个脉冲极窄。正确的做法是使用CHxMAX位。
void Set_PWM_Duty_Cycle(unsigned char duty_percent) { if(duty_percent == 0) { // 0% 占空比:清除TOV0,输出将保持为ELS动作后的状态(低电平) // 首先,停止定时器更新以避免毛刺(可选但推荐) TBSC |= 0x20; // 设置TSTOP,停止计数 TBSC0 &= ~0x02; // 清除TOV0位 (TOV0是bit1) // 确保ELS是1:0 (比较时清零),这样输出就是低 TBSC0 = (TBSC0 & 0xCF) | 0x20; // 保持其他位,设置ELS0B:ELS0A=1:0 (bits 5:4) TBSC &= ~0x20; // 清除TSTOP,启动计数 } else if(duty_percent == 100) { // 100% 占空比:清除TOV0,并设置CH0MAX TBSC |= 0x20; // 停止计数 TBSC0 &= ~0x02; // 清除TOV0 TBSC0 |= 0x01; // 设置CH0MAX (bit0) // 同样确保ELS是1:0,这样“100%电平”就是高(因为比较清低,但CH0MAX强制高) TBSC0 = (TBSC0 & 0xCF) | 0x20; TBSC &= ~0x20; // 启动计数 } else { // 正常占空比:确保CH0MAX被清除,TOV0被设置 TBSC |= 0x20; // 停止计数 TBSC0 &= ~0x01; // 清除CH0MAX TBSC0 |= 0x02; // 设置TOV0 // 计算并写入比较值 unsigned int cmp = (49 * duty_percent) / 100; TBCH0H = (cmp >> 8); TBCH0L = (cmp & 0xFF); TBSC &= ~0x20; // 启动计数 } }关键提示:在修改
TOVx或CHxMAX位,或者改变ELS模式时,强烈建议先停止定时器(TSTOP=1)。因为这些控制位的改变可能会立即影响输出逻辑,如果此时计数器正在运行,可能导致引脚输出一个瞬间的毛刺。对于电机驱动等应用,这种毛刺可能是致命的。
5. 常见问题排查与调试心得实录
即使按照手册一步步配置,在实际硬件调试中依然会遇到各种问题。下面是我在多年项目中总结的关于TIMB模块的“避坑指南”。
5.1 问题一:完全没有PWM波形输出
- 检查顺序:
- 引脚功能复用:首先确认你的PWM输出引脚(如PTF4)是否被正确配置为特殊功能输出,而非通用I/O。对于MC68HC08,通常当外设模块启用时,相应引脚会自动切换功能。但最好检查一下数据方向寄存器(DDRF)和端口控制寄存器,确保没有将其强制设置为输入或通用输出锁定了错误电平。
- 定时器是否真的启动了?:单步调试,检查
TBSC寄存器的TSTOP位。确保在初始化最后阶段该位被清0。一个常见的疏忽是:在初始化序列中设置了TSTOP,但后续操作(如修改预分频)时又意外地写入了包含TSTOP=1的值。 - 时钟源有没有?:检查
TBSC中的PS[2:0]位。如果你选择了内部总线时钟分频,请确认MCU的时钟系统(CGM)已正确初始化,总线时钟确实存在。如果你选择了外部时钟TBCLK,请用示波器测量PTD4引脚是否有时钟信号输入,并注意其频率不能超过总线频率/2。 - 模数寄存器是否为0?:如果
TBMODH:TBMODL被意外设置为0,那么计数器从0增加到0就会立即溢出,PWM周期极短(可能只有两个时钟周期),在示波器上可能看起来像一条直流电平线或高频噪声。务必确认写入的模数值符合预期。
5.2 问题二:PWM频率或占空比不准
- 计算错误:这是最常见的原因。反复核对你的计算公式:
PWM周期 = (TBMOD值 + 1) / (总线时钟频率 / 预分频系数)占空比 = (TBCHx值) / (TBMOD值 + 1)注意,TBMOD和TBCHx都是16位无符号整数。确保计算过程中没有溢出,特别是当使用32位中间变量时。 - 寄存器写入顺序:对于
TBMOD和TBCHx这种16位寄存器,必须先写高字节,后写低字节。如果顺序反了,在写入的瞬间,硬件会看到一个错误的16位数,可能导致产生一个极其怪异的脉冲。 - 同步问题(缓冲模式):在缓冲PWM模式下,你写入
TBCHxH:L的新脉宽值,要到当前周期结束(计数器溢出)后才会生效。如果你在写入后立即测量,看到的还是上一个周期的占空比。这不是错误,而是特性。如果你的应用要求严格同步,可以在写入新值后,等待溢出标志(TOF)置位,再进行下一步操作。
5.3 问题三:试图用中断处理,但中断不触发或只触发一次
- 中断使能位:你配置了
CHxIE或TOIE吗?在TBSCx寄存器中使能了通道中断(CHxIE=1),或在TBSC中使能了溢出中断(TOIE=1)吗? - 全局中断开关:MCU的全局中断是否打开?通常需要一条
CLI()或asm(“cli”)指令。 - 中断标志清除方式:这是最容易出错的地方!TIMB的中断标志(
CHxF和TOF)采用“读取状态寄存器后写入0”的方式来清除。顺序绝对不能错!
如果先写后读,或者读了之后没有写,标志位可能无法清除,导致中断只发生一次后就卡死。#pragma interrupt_handler TIMB_OVF_ISR void TIMB_OVF_ISR(void) { unsigned char dummy; dummy = TBSC; // 1. 读取TBSC寄存器(此时TOF=1) TBSC = 0x00; // 2. 向TOF位写0。注意:这里写0是清除TOF,但不能影响其他位(如TSTOP)。 // 更安全的做法是:TBSC &= ~0x80; // 只清除TOF位(bit7) // ... 你的中断处理代码 ... } - 中断向量表:确保你的链接器脚本和启动代码正确地将
TIMB溢出中断或TIMB通道中断的向量指向了你编写的ISR函数地址。
5.4 问题四:进入低功耗模式后,定时器相关功能异常
- WAIT模式下的定时器:如前所述,如果你希望用TIMB中断唤醒WAIT模式,则进入WAIT前不能设置
TSTOP。同时,确保相应的中断(CHxIE或TOIE)已使能。 - STOP模式后的恢复:从STOP模式唤醒后,TIMB计数器会从停止时的值继续计数。这可能导致唤醒后第一个PWM周期的长度不正常。如果应用对此敏感,可以在退出STOP模式后,执行一次定时器复位(
TRST=1)并重新配置PWM参数。 - 寄存器保护:在Break中断调试时,注意
SBFCR寄存器中的BCFE位。如果BCFE=0(默认),在Break状态下对状态位的写操作会被忽略。这意味着你无法在调试器中单步执行中断标志清除序列。如果需要调试,可以暂时设置BCFE=1,但要注意这可能会影响实时性。
6. 结合PIT模块实现复杂定时调度
MC68HC08AB16A除了TIMB,还有一个更简单的可编程中断定时器(PIT)。它没有输出比较功能,只能产生周期性中断。但正因其简单,它非常适合作为系统的“心跳”或任务调度器。
6.1 PIT与TIMB的分工建议
- TIMB:专注于硬件波形生成。所有需要精确波形输出(PWM、方波、脉冲序列)的任务都交给它。利用其硬件自动比较和翻转的特性,不占用CPU即可产生稳定信号。
- PIT:专注于软件任务调度。配置一个固定的周期(如1ms),在其溢出中断中更新软件计时器、扫描按键、刷新显示等。这样,你的主程序可以设计成基于时间片的协作式调度,代码结构更清晰。
6.2 PIT配置示例
配置PIT产生1ms中断(假设总线时钟8MHz):
void PIT_Init(void) { // 1. 停止并复位PIT PSC = 0x60; // PSTOP=1, PRST=1 // 2. 设置预分频和模数,产生1ms中断 // 选择预分频:总线时钟/64 => 8MHz/64 = 125kHz (周期8us) // 所需周期1ms = 1000us, 则计数值 = 1000us / 8us = 125 // 模数值 = 125 - 1 = 124 (0x7C) PMODH = 0x00; PMODL = 0x7C; // 3. 配置控制寄存器:使能溢出中断,启动计数器 // PSC: [POF][POIE][PSTOP][-][-][PPS2][PPS1][PPS0] // 我们需要: POIE=1, PSTOP=0, PPS=110 (/64) => 0100 0110 = 0x46 PSC = 0x46; } // PIT溢出中断服务例程 #pragma interrupt_handler PIT_ISR void PIT_ISR(void) { unsigned char dummy; dummy = PSC; // 读PSC清除POF标志 PSC &= ~0x80; // 向POF位写0(安全写法) // 在此处处理1ms定时任务,例如: // g_msTicks++; // if(g_msTicks % 10 == 0) { /* 10ms任务 */ } }通过将TIMB用于硬件实时控制,PIT用于软件任务调度,你可以构建一个稳定可靠的嵌入式系统基础框架。这种思路在资源有限的8位MCU开发中非常有效,能最大限度地发挥硬件效能,减轻CPU负担。