P89LPC9321 CCU模块实战:输入捕获与PWM驱动电机控制
1. 项目概述与核心价值
在嵌入式系统开发,尤其是涉及电机控制、电源管理或精密传感器接口的项目里,定时器/计数器单元(Timer/Counter)的深度掌握是区分“能用”和“用好”的关键。很多工程师在项目初期,面对数据手册里大段的寄存器描述和时序图,常常感到无从下手,最终只能照搬例程,知其然而不知其所以然。当项目需求稍微变化,比如需要更精确的频率测量、更稳定的PWM波形或者处理带噪声的输入信号时,就会遇到瓶颈。
P89LPC9321这款经典的8位微控制器,其内置的捕获/比较单元(CCU)功能相当强大且典型。它集成了输入捕获(Input Capture)和脉宽调制(PWM)两大核心功能。输入捕获不是简单地“读引脚状态”,而是一个由硬件自动完成的、高精度的“时间戳”记录器,这对于测量未知信号的脉宽或周期至关重要。PWM也不仅仅是“输出方波”,其非对称与对称模式的选择,直接关系到电机驱动的效率、噪音以及开关电源的纹波特性。
本文将彻底拆解P89LPC9321 CCU模块的工作原理,从最底层的寄存器位操作,到中断服务程序的编写策略,并结合我在多个电机驱动和电源项目中的实际踩坑经验,为你呈现一份可以直接“抄作业”又明白“为什么这么写”的实战指南。无论你是正在评估这款芯片,还是已经用它遇到了难题,相信这篇深入解析都能帮你打通任督二脉。
2. 输入捕获机制深度解析
输入捕获功能的核心思想,是利用定时器这个不断运行的“精密时钟”,在外部引脚发生特定事件(如上升沿或下降沿)的瞬间,“咔嚓”一声拍下快照,将这个时刻的定时器计数值锁存到专门的寄存器中。这个过程完全由硬件自动完成,不受软件延迟影响,因此精度极高。
2.1 基础工作原理与寄存器配置
在P89LPC9321中,CCU模块提供了两个独立的输入捕获通道(A和B)。其使能是默认的,但要让其工作,必须完成几个关键配置。
首先,必须将对应的I/O引脚(例如P1.2对应捕获A,P1.3对应捕获B)配置为输入模式。这是很多新手容易忽略的第一步:如果引脚被配置为输出或准双向口,外部信号根本无法触发捕获逻辑。配置通常通过相应的端口模式寄存器(如P1M1, P1M2)完成。
其次,需要配置捕获边沿。这是通过CCU控制寄存器(CCCRx, x为A或B)中的ICESx位来设定的。ICESx = 0表示在下降沿触发捕获,ICESx = 1则表示在上升沿触发。例如,要测量一个正脉冲的宽度,你可以先配置为上升沿捕获,记录时间T1;在中断服务程序中改为下降沿捕获,记录时间T2;那么脉宽就是 (T2 - T1) * 定时器计数周期。这里的关键是,改变边沿选择的操作必须在捕获中断服务程序(ISR)内完成,以确保能捕捉到紧接着的相反边沿。
当捕获事件发生时,硬件会自动将16位定时器(TH2:TL2)的当前值,搬运到对应的16位输入捕获寄存器(ICRAH:ICRAL或ICRBH:ICRBL)中。同时,相应的中断标志位TICF2x(位于TIFR2寄存器中)会被置1。
注意:读取捕获寄存器的顺序有严格规定!必须先读低字节(ICRxL),再读高字节(ICRxH)。这是因为当你读取ICRxL时,高字节的内容会被自动锁存到一个影子寄存器中;随后读取ICRxH时,实际读到的是这个影子寄存器的值,而非实时的高字节。这个机制保证了在读取16位值的过程中,即使定时器仍在运行,也能获得一个完整且一致的“快照”。如果错误地先读高字节,或者读了两次低字节中间没读高字节,得到的数据将是错乱的。
2.2 高级特性:噪声滤波与事件延迟
在实际的工业环境中,输入信号常常伴有毛刺噪声。一个轻微的抖动可能会被误认为是一个有效的边沿,导致测量结果严重错误。P89LPC9321的CCU提供了一个简单但有效的数字噪声滤波器。
通过设置CCCRx寄存器中的ICNFx位为1,可以启用该滤波器。启用后,捕获逻辑需要对输入信号进行连续4次采样(采样频率为CCLK/2),且4次采样值都一致时,才认为检测到了一个有效的边沿。这相当于一个简单的数字滤波器,能有效滤除宽度小于8个CCLK周期的窄脉冲噪声。
实操心得:滤波器的代价是延迟。使能噪声滤波后,从真实边沿出现到被确认为捕获事件,会有最多4个采样周期的延迟(即8个CCLK周期)。在计算高精度时间间隔时,这个固定延迟需要被考虑进去,尤其是在高频信号测量时。如果你的信号非常干净,可以关闭滤波器以获得最快的响应。
另一个强大的功能是事件延迟计数器。它允许你在连续发生多个边沿后,才触发一次捕获。这通过CCCRx寄存器中的ICECx[2:0]三位来控制。
| ICECx2 | ICECx1 | ICECx0 | 延迟边沿数 |
|---|---|---|---|
| 0 | 0 | 0 | 0 (每个边沿都捕获) |
| 0 | 0 | 1 | 1 |
| 0 | 1 | 0 | 2 |
| 0 | 1 | 1 | 3 |
| 1 | 0 | 0 | 4 |
| 1 | 0 | 1 | 5 |
| 1 | 1 | 0 | 7 |
| 1 | 1 | 1 | 15 |
这个功能的应用场景非常巧妙。例如,在测量电机编码器的转速时,编码器每转一圈会输出N个脉冲。如果你只关心每圈的转速,而不需要每个脉冲的瞬时速度,就可以将延迟设置为N-1,这样每N个脉冲才产生一次捕获中断,大大降低了CPU的中断负载。又比如,可以对一个高频信号进行分频测量,每16个周期测量一次,既能降低软件开销,又能通过计算平均值提高测量精度和稳定性。
2.3 中断处理与软件设计要点
捕获事件可以触发中断。要使能中断,需要设置三个位:全局中断使能EA(IEN0.7)、CCU模块中断使能ECCU(IEN1.4),以及具体的捕获通道中断使能位TICIE2x(TICR2.0或TICR2.1)。
当中断发生时,程序会跳转到CCU的中断向量。中断标志TICF2x必须由软件手动清除,通常是通过向该位写0来实现(注意,很多架构是写1清标志,但P89LPC9321这里是写0清除,务必查阅数据手册确认)。
一个稳健的捕获中断服务程序(ISR)流程如下:
- 读取TISE2寄存器(中断状态编码寄存器),判断中断源(虽然对于单一捕获任务可能不需要,但在多中断源共存时很重要)。
- 立即读取捕获寄存器值(先ICRxL,后ICRxH),并将该值存入一个软件缓冲区。这是最重要的一步,防止后续操作覆盖寄存器。
- 根据测量需求,可能需要进行边沿切换(修改
ICESx位),为下一次捕获做准备。 - 清除对应的中断标志位(
TICF2x)。 - 退出中断。
踩坑记录:中断服务程序中的耗时操作。中断服务程序应该尽可能短小精悍。避免在捕获ISR中进行复杂的数学运算(如浮点数计算)或函数调用。通常的做法是:在ISR中仅完成“读取数据”和“设置标志”的动作,将耗时的计算(如计算脉宽、频率)放到主循环或低优先级任务中。否则,可能因为ISR执行时间过长,错过后续的捕获事件,导致数据丢失。
3. PWM工作原理与模式详解
脉宽调制(PWM)是一种通过调节数字脉冲信号的占空比,来模拟模拟量输出的技术。占空比(Duty Cycle)是指一个周期内高电平时间占总周期的比例。P89LPC9321的CCU提供了两种主要的PWM模式:非对称PWM和对称PWM,这两种模式在波形和适用场景上有本质区别。
3.1 非对称PWM模式
非对称PWM,也称为边沿对齐PWM。在此模式下,定时器只进行递减计数。无论TDIR2位如何设置,硬件都会强制其为递减模式。
其工作流程如下:
- 定时器从你设定的周期值(
TOR2)开始递减计数。 - 在计数过程中,硬件不断将当前计数值(TH2:TL2)与比较寄存器(
OCRxH:OCRxL)中的值进行比较。 - 当计数值与比较值匹配时,根据
OCMx[1:0]位的设置,对输出引脚执行操作。 - 当计数值递减到0时,产生下溢,输出引脚状态可能再次改变(同样取决于
OCMx设置),同时定时器自动重载TOR2的值,开始下一个周期的递减。
输出引脚行为(OCMx[1:0] = 01, 非反相PWM):
- 置位点:在比较匹配时,输出引脚被置为高电平(或有效电平)。
- 清除点:在定时器下溢(计数值回到
TOR2)时,输出引脚被清除为低电平。
这意味着,高电平的宽度由比较寄存器的值直接决定。假设TOR2=1000,OCR=300,则每个周期开始后,计数值从1000递减,减到300时输出变高,减到0时输出变低,然后开始新周期。输出波形的高电平时间是固定的(对应计数值300段时间),低电平时间是变化的。
输出引脚行为(OCMx[1:0] = 11, 反相PWM):
- 清除点:在比较匹配时,输出引脚被清除为低电平。
- 置位点:在定时器下溢时,输出引脚被置为高电平。
此时,比较值决定了低电平的宽度。
模式选择与计算:通过向
TMOD2[1:0]写入10H来选择非对称PWM模式。PWM频率的计算公式为:Fpwm = Fccu / (TOR2 + 1)。其中Fccu是CCU的时钟频率。占空比的计算公式为:占空比 = (OCR + 1) / (TOR2 + 1)(对于非反相PWM)。这里“+1”是因为计数器从TOR2递减到0,总共经历了TOR2+1个计数周期。
3.2 对称PWM模式
对称PWM,也称为中心对齐PWM。在此模式下,定时器先递增,达到TOR2后改变方向递减,减到0后再改变方向递增,如此往复。
其工作流程如下:
- 定时器从0开始递增计数。
- 在递增阶段,当计数值与比较值匹配时,根据
OCMx设置和计数方向,对输出引脚执行一次操作。 - 计数达到
TOR2后,转为递减。 - 在递减阶段,当计数值再次与比较值匹配时,会根据
OCMx设置和计数方向,对输出引脚执行另一次操作。 - 计数减回0后,再次转为递增,开始下一个周期。
输出引脚行为(OCMx[1:0] = 01, 非反相PWM):
- 清除点:在递增计数过程中,发生比较匹配时,清除输出引脚(变低)。
- 置位点:在递减计数过程中,发生比较匹配时,置位输出引脚(变高)。
输出引脚行为(OCMx[1:0] = 11, 反相PWM):
- 置位点:在递增计数过程中,发生比较匹配时,置位输出引脚(变高)。
- 清除点:在递减计数过程中,发生比较匹配时,清除输出引脚(变低)。
核心差异与优势:对称PWM的波形是关于周期中心对称的。与非对称PWM相比,其开关动作(电平跳变)在周期内分布得更均匀。这带来了一个巨大优势:在相同的PWM频率下,对称PWM产生的谐波分量更小,尤其是偶次谐波被有效抑制。在电机驱动和开关电源应用中,这意味着更小的电磁干扰(EMI)、更低的电机噪音和更平滑的转矩输出。因此,在音频应用(如Class D放大器)或对噪声敏感的高性能电机驱动中,对称PWM是首选。
模式选择与计算:通过向TMOD2[1:0]写入11H来选择对称PWM模式。PWM频率的计算公式为:Fpwm = Fccu / (2 * TOR2)。注意这里分母是2*TOR2,因为一个完整的PWM周期包含了从0到TOR2再回到0的过程。占空比的计算公式为:占空比 = OCR / TOR2(对于非反相PWM)。例如,TOR2=1000,OCR=300,则输出波形在中心点附近对称,高电平时间对应的计数值为600(300上,300下),占空比为30%。
3.3 交替输出模式与桥式驱动
这是非对称PWM模式下的一个增强功能,专为驱动H桥电路设计。通过设置TCR20寄存器中的ALTAB或ALTCD位,可以将PWM通道A/B或C/D配置成交替对。
在这种模式下,一对PWM输出(如A和B)在每个定时器周期交替有效。具体来说,在一个周期内,通道A输出有效的PWM脉冲;在下一个周期,通道B输出有效的PWM脉冲,如此循环。
这个功能对于驱动直流电机或步进电机的H桥至关重要。它可以用来实现被动续流或主动刹车。例如,在电机驱动中,我们通常需要避免H桥同侧的两个开关管(如上管和下管)同时导通,否则会导致短路。使用交替输出模式,配合合适的死区时间(通常需要外部硬件或软件插入),可以确保同一桥臂的两个信号永远不会同时有效,从硬件逻辑上防止了直通短路,大大提高了系统的可靠性。
3.4 同步更新与HALT功能
同步PWM寄存器更新:在PWM运行过程中,如果直接修改比较寄存器(OCRx)或周期寄存器(TOR2),可能会在脉冲中间产生一个“毛刺”或畸变的脉冲。为了避免这种情况,P89LPC9321采用了影子寄存器机制。
当你写入OCRxH或OCRxL时,值并不会立即生效,而是先存入一组影子寄存器。只有当软件向TCOU2位(在TCR21寄存器中)写入1时,影子寄存器中的值才会在下一个定时器溢出(或下溢)事件发生时,同步更新到真正的工作寄存器中。这个机制确保了PWM波形的连续性和平滑性,对于电机平稳调速和电源稳定输出至关重要。在更新PWM参数时,标准的操作顺序是:1) 写入新的OCRx值; 2) 置位TCOU2。
HALT功能:这是一个安全特性。当HLTEN位使能后,如果输入捕获A引脚上发生了一个使能的捕获事件,所有PWM输出引脚会立即被强制设置为FCOx位(在CCCRx寄存器中)所定义的状态(高或低),同时HLTRN位被置1,表明发生了急停。此时定时器本身继续运行,但PWM输出被锁死。这个功能常用于实现紧急刹车、过流保护或故障安全状态。要恢复PWM输出,必须由软件清除HLTRN位。软件也可以通过直接写HLTRN=1来主动触发HALT状态。
4. 时钟源与PLL:提升PWM分辨率的关键
PWM的分辨率直接决定了你对输出量控制的精细程度。分辨率通常用位数表示,例如10位分辨率意味着占空比可以有2^10=1024个不同的等级。分辨率由定时器的位数和计数范围决定。P89LPC9321的CCU定时器是16位的,理论上最大计数范围是0~65535,但这受限于时钟频率和所需的PWM频率。
基本关系:PWM频率Fpwm、时钟频率Fccu和分辨率Resolution之间存在制约关系。对于非对称PWM,近似有Fpwm ≈ Fccu / (2^Resolution)。这意味着,在固定的时钟频率下,提高PWM频率必然会降低分辨率;反之,要获得高分辨率,就必须接受较低的PWM频率。
为了突破主系统时钟(PCLK)频率的限制,P89LPC9321的CCU模块内置了一个锁相环。PLL可以将输入时钟倍频,产生一个更高的内部时钟CCUCLK供PWM定时器使用。
PLL的输入频率范围是0.5 MHz到1 MHz。它会将这个频率乘以32,产生输出。因此,要使用PLL,你需要通过TCR21寄存器中的PLLDV[3:0]位,对PCLK进行分频,以得到落在0.5-1 MHz范围内的输入频率。计算公式为:PLL_input = PCLK / (N + 1),其中N是PLLDV[3:0]的值(0-15)。然后,CCUCLK = PLL_input * 32。
例如,假设系统主频PCLK = 12 MHz。我们取N=11,则PLL_input = 12 MHz / (11+1) = 1 MHz,正好在范围内。那么CCUCLK = 1 MHz * 32 = 32 MHz。使用这个32 MHz的时钟,即使我们需要一个20 kHz的PWM频率(常用于电机驱动),也能获得很高的分辨率:TOR2_max = 32 MHz / 20 kHz = 1600。这相当于大约10.6位(log2(1600))的分辨率,比直接使用12 MHz主频(此时TOR2_max=600,分辨率仅9.2位)要精细得多。
重要警告:异步操作风险。当定时器使用PLL产生的CCUCLK运行时,它与单片机内核及其他外设(使用PCLK)是异步的。数据手册明确警告:“不鼓励在异步模式下读写定时器,结果可能不可预测”。这意味着,在PLL使能后,应尽量避免在程序运行时频繁读取TH2/TL2的值,或者将其用于与系统其他部分严格同步的计时。中断标志的置位和清除也会有若干CCLK周期的延迟。通常,PLL模式用于“设置好就放手”的纯PWM输出场景,或者配合输入捕获进行频率测量(捕获值本身是准确的)。
PLL启动序列:
- 配置PWM模块(模式、周期、占空比),但先不要启动定时器(即不要设置
TMOD2启动位)。 - 根据PCLK频率计算合适的N值,使
PCLK/(N+1)落在0.5-1 MHz之间,并将N写入PLLDV。 - 置位
PLLEN位。然后需要循环等待,直到读取PLLEN位为1,这表明PLL已经锁定并稳定。 - 最后,写入
TMOD2启动定时器。
5. CCU中断结构与高效服务程序设计
CCU模块有7个独立的中断源:1个定时器溢出中断(TOIF2),2个输入捕获中断(TICF2A, TICF2B),4个输出比较中断(TOCF2A-D)。它们共享同一个中断向量。这意味着,当进入CCU中断服务程序时,你首先需要判断是哪个事件触发了中断。
5.1 中断优先级与状态查询
硬件上固定了这7个中断源的优先级,从高到低依次为:定时器溢出 -> 输入捕获A -> 输入捕获B -> 输出比较A -> B -> C -> D。
当多个中断同时发生时,TISE2寄存器(CCU中断状态编码寄存器)的ENCINT[2:0]位会给出当前待处理的、优先级最高的中断源编码。
| ENCINT[2:0] | 中断源 | 优先级 |
|---|---|---|
| 000 | 无中断挂起 | - |
| 001 | 输出比较 D | 最低 |
| 010 | 输出比较 C | |
| 011 | 输出比较 B | |
| 100 | 输出比较 A | |
| 101 | 输入捕获 B | |
| 110 | 输入捕获 A | |
| 111 | 定时器溢出 | 最高 |
5.2 通用中断服务程序框架
一个健壮且高效的CCU中断服务程序应该遵循以下流程,以处理可能同时发生的多个中断:
// 假设使用SDCC或Keil C编译器,中断号根据具体编译器定义 void CCU_ISR(void) interrupt INTERRUPT_CCU { unsigned char int_source; do { // 1. 读取当前最高优先级的中断源 int_source = TISE2 & 0x07; // 取低3位 switch(int_source) { case 7: // 定时器溢出中断 (TOIF2) // 处理溢出,例如更新软件计数器、同步PWM参数等 TIFR2 &= ~(1 << 7); // 清除TOIF2标志 (写0清除) break; case 6: // 输入捕获A中断 (TICF2A) // 读取捕获值,注意顺序:先读ICRAL,再读ICRAH capture_value_low = ICRAL; capture_value_high = ICRAH; // 可以在这里切换捕获边沿,或设置软件标志供主循环处理 TIFR2 &= ~(1 << 0); // 清除TICF2A标志 break; case 5: // 输入捕获B中断 (TICF2B) capture_value_low = ICRBL; capture_value_high = ICRBH; TIFR2 &= ~(1 << 1); // 清除TICF2B标志 break; case 4: // 输出比较A中断 (TOCF2A) // 在PWM模式下,输出比较中断可用于在特定时刻更新其他参数 // 例如,实现复杂的PWM序列 TIFR2 &= ~(1 << 3); // 清除TOCF2A标志 break; // ... 处理其他输出比较中断 case 0: // 无中断挂起 default: // 正常情况下不应进入这里,除非读取有误 break; } // 2. 再次读取TISE2,检查是否还有其他挂起的中断 // 由于清除了当前最高优先级的中断标志,编码器会输出下一个最高优先级的中断源 int_source = TISE2 & 0x07; } while (int_source != 0); // 循环处理,直到所有挂起的中断都被处理完毕 // 3. 中断返回 }这个do...while循环结构确保了在一次中断入口中,能够处理完所有同时挂起的CCU中断,避免了因中断嵌套或丢失中断可能带来的问题。这是编写多中断源共享一个向量时的标准做法。
实操心得:中断标志的清除时机。一定要在处理完该中断对应的任务后,再清除中断标志。例如,在输入捕获中断中,必须先安全地读取
ICRxL/H寄存器,然后再清除TICF2x标志。如果先清除标志,但在读取寄存器前又发生了新的捕获事件,寄存器值会被覆盖,导致数据丢失。此外,清除标志后,该中断源才能响应新的中断请求。
6. 完整配置流程与实战代码示例
下面,我将以一个具体的实战场景为例,展示如何配置P89LPC9321的CCU模块,实现一路对称PWM输出(用于驱动电机)和一路输入捕获(用于测量编码器频率)。假设系统主频PCLK = 12 MHz,我们希望使用PLL将CCU时钟提升到32 MHz。
6.1 初始化步骤详解
第一步:系统与端口初始化
// 1. 配置端口模式 // 假设P1.2作为输入捕获A引脚,P1.6作为PWM输出B引脚(根据数据手册映射) // 将P1.2配置为输入(高阻态),P1.6配置为推挽输出 P1M1 &= ~(1 << 2); // P1.2: 清除M1 P1M2 |= (1 << 2); // P1.2: 设置M2, 配置为输入(具体模式需查表,此处为示例) P1M1 &= ~(1 << 6); // P1.6: 清除M1 P1M2 &= ~(1 << 6); // P1.6: 清除M2, 配置为准双向口,或根据手册设为推挽输出 // 2. 初始化CCU相关SFR指针(如果使用绝对地址访问) // 也可以直接通过sfr关键字定义,这里以地址操作为例 #define TCR20 (*(unsigned char volatile xdata *)0xF8) #define TCR21 (*(unsigned char volatile xdata *)0xF9) #define TOR2H (*(unsigned char volatile xdata *)0xFA) #define TOR2L (*(unsigned char volatile xdata *)0xFB) #define OCRBH (*(unsigned char volatile xdata *)0xFC) // 以OCRB为例 #define OCRBL (*(unsigned char volatile xdata *)0xFD) #define CCCRA (*(unsigned char volatile xdata *)0xFE) // 捕获控制A // ... 其他寄存器定义第二步:配置并启动PLL
// 1. 停止定时器(如果正在运行) // TMOD2[1:0] = 00 停止定时器(根据手册,可能需写其他值,此处假设00为停止) // 2. 配置PLL分频器 // PCLK=12MHz,目标PLL输入1MHz, N = (12/1) - 1 = 11 TCR21 = 0x00; // 先清零,确保BRGEN=0(虽然我们不用BRG),并设置PLLDV TCR21 |= (11 & 0x0F); // 设置PLLDV[3:0] = 1011 (11) // 3. 启动PLL并等待锁定 TCR20 |= (1 << 5); // 设置PLLEN位(假设第5位是PLLEN,需查手册确认位定义) while(!(TCR20 & (1 << 5))); // 等待PLLEN位读回为1,表明PLL已锁定 // 注意:实际位定义需严格对照数据手册表43/44第三步:配置PWM(通道B,对称模式)
// 目标:生成一个20kHz,占空比50%的对称PWM // CCUCLK = 32 MHz, Fpwm = 20 kHz // 对于对称PWM: TOR2 = Fccu / (2 * Fpwm) = 32,000,000 / (2 * 20,000) = 800 // 占空比50%,则 OCR = 占空比 * TOR2 = 0.5 * 800 = 400 // 1. 设置周期值 TOR2 = 800 TOR2H = (800 >> 8) & 0xFF; // 高字节 TOR2L = 800 & 0xFF; // 低字节 // 2. 设置比较值 OCRB = 400 OCRBH = (400 >> 8) & 0xFF; OCRBL = 400 & 0xFF; // 3. 配置比较控制寄存器CCCRB(假设地址) // 设置输出模式为非反相对称PWM (OCMB[1:0] = 01) // 同时,可以设置FCOB(HALT时的输出值),例如设为0(低电平) // 假设CCCRB寄存器位定义:bit1:0=OCMB, bit2=FCOB #define CCCRB (*(unsigned char volatile xdata *)0xFF) // 示例地址 CCCRB = 0x01; // OCMB=01, FCOB=0 // 4. 配置定时器控制寄存器TCR20 // 使能PWM输出,选择对称模式,可能还需要其他控制位 // 假设:bit1:0 = TMOD2[1:0], 11表示对称PWM; bit2 = PWM使能等 TCR20 &= ~0x03; // 清除模式位 TCR20 |= 0x03; // 设置为对称PWM模式 (11) // 可能还需要设置其他位,如定时器方向等,在对称模式下无效第四步:配置输入捕获(通道A)
// 目标:测量输入到P1.2引脚信号的频率,使用上升沿捕获,使能噪声滤波 // 1. 配置捕获控制寄存器CCCRA // ICES0=1 (上升沿), ICNF0=1 (使能噪声滤波), ICEC0[2:0]=000 (每个边沿都捕获) // 假设CCCRA寄存器位定义:bit0=ICES0, bit1=ICNF0, bit4:2=ICEC0[2:0] CCCRA = 0x03; // 二进制 0000 0011, 即ICES0=1, ICNF0=1, ICEC0=000 // 2. 使能输入捕获A中断 TICR2 |= (1 << 0); // 设置TICIE2A位(假设第0位)第五步:使能中断并启动定时器
// 1. 使能CCU模块中断 IEN1 |= (1 << 4); // 设置ECCU位 (IEN1.4) // 2. 使能全局中断 EA = 1; // 设置EA位 (IEN0.7) // 3. 最后,启动CCU定时器 // 通过设置TCR20的模式位,我们已经设置了对称模式(11),这本身可能就启动了定时器。 // 或者可能需要一个单独的启动位。根据数据手册,向TMOD2[1:0]写非00值即启动。 // 我们之前TCR20已设为0x03,假设这包含了启动命令。 // 更安全的做法是明确操作模式位寄存器(可能叫TMOD2或集成在TCR20中)。 // 假设向TCR20的bit1:0写入11即启动对称PWM定时器。 // 代码已在第三步第4点执行。6.2 输入捕获中断服务程序示例
unsigned int capture_start_time = 0; unsigned int capture_end_time = 0; unsigned char capture_state = 0; // 0:等待第一个上升沿, 1:已捕获上升沿等待下降沿 volatile unsigned long period_ticks = 0; // 测得的周期(定时器计数) volatile bit new_measurement_ready = 0; // 测量完成标志 void CCU_ISR(void) interrupt 5 { // 中断号5是CCU中断,需查手册确认 unsigned char int_src = TISE2 & 0x07; if(int_src == 6) { // 输入捕获A中断 if(capture_state == 0) { // 第一次上升沿捕获 capture_start_time_low = ICRAL; // 必须先读低字节 capture_start_time_high = ICRAH; capture_start_time = (capture_start_time_high << 8) | capture_start_time_low; // 切换为下降沿捕获,准备捕获脉冲结束 CCCRA &= ~(1 << 0); // 清除ICES0位,设置为下降沿捕获 capture_state = 1; } else if(capture_state == 1) { // 下降沿捕获,脉冲结束 capture_end_time_low = ICRAL; capture_end_time_high = ICRAH; capture_end_time = (capture_end_time_high << 8) | capture_end_time_low; // 计算脉宽(需要考虑定时器溢出!) // 简单情况,假设两次捕获间隔远小于定时器溢出周期(65536 ticks) period_ticks = capture_end_time - capture_start_time; // 切换回上升沿捕获,准备下一次测量 CCCRA |= (1 << 0); // 设置ICES0位,上升沿捕获 capture_state = 0; new_measurement_ready = 1; // 通知主循环 } TIFR2 &= ~(1 << 0); // 清除TICF2A中断标志 } // 可以添加其他中断源的处理... }关键点:处理定时器溢出。上面的示例代码假设两次捕获发生在同一个定时器计数周期内,没有发生溢出。在实际应用中,如果测量的脉宽或周期很长,定时器可能发生溢出。一个健壮的方案是:在定时器溢出中断(TOIF2)中,对一个软件扩展计数器(如
timer_overflow_count)进行加一。在计算时间间隔时,公式应为:period = (capture_end_time + overflow_count_end * 65536) - (capture_start_time + overflow_count_start * 65536)。你需要同时记录捕获发生时软件扩展计数器的值。
6.3 主循环处理与频率计算
void main(void) { float frequency_hz; unsigned long period_total_ticks; // ... 初始化代码(调用前面的初始化函数) while(1) { if(new_measurement_ready) { new_measurement_ready = 0; // 计算信号频率 // period_ticks 是定时器计数差值 // 定时器时钟频率 Fccu = 32 MHz (使用PLL后) // 定时器计数周期 T_tick = 1 / Fccu // 信号周期 T_signal = period_ticks * T_tick // 信号频率 F_signal = 1 / T_signal = Fccu / period_ticks if(period_ticks != 0) { // 避免除零错误 frequency_hz = 32000000.0 / (float)period_ticks; // Fccu=32MHz // 现在 frequency_hz 就是测量到的信号频率 // 可以用于显示、控制算法等... } // 示例:根据测量的频率调整PWM占空比(一个简单的闭环) // 假设我们想将电机转速稳定在某个频率对应值 // 这只是一个非常简化的示例,真实PID控制更复杂 // unsigned int target_ticks = ...; // 目标周期对应的计数值 // int error = target_ticks - period_ticks; // 根据error调整OCRB值... // OCRBH = (new_ocr >> 8) & 0xFF; // OCRBL = new_ocr & 0xFF; // TCOU2 = 1; // 触发影子寄存器更新 } // ... 其他主循环任务 } }通过以上完整的配置和代码框架,你应该能够基于P89LPC9321的CCU模块,构建出具备高精度输入测量和高质量PWM输出能力的嵌入式系统。记住,实际开发中务必反复核对数据手册中的寄存器地址和位定义,并根据具体的外设引脚映射进行配置。调试时,可以先用示波器观察PWM输出波形,用信号发生器产生已知频率的方波测试输入捕获功能,逐步验证每个环节的正确性。