MC68HC908AT32 SPI与TIMA-4定时器寄存器配置与实战应用详解
1. 项目概述与核心价值
如果你正在捣鼓飞思卡尔(现恩智浦)的MC68HC908AT32这颗8位微控制器,并且项目里涉及到与外部芯片通信或者需要精准的定时、PWM输出,那么你肯定绕不开它的两个核心外设:SPI模块和TIMA-4定时器。我当年第一次用这颗芯片做一个小型电机驱动板时,就被它手册里密密麻麻的寄存器位给绕晕过。后来在几个实际项目中反复折腾,才算是把SPI的稳定通信和TIMA-4的精准PWM给玩明白了。这篇文章,我就结合自己的踩坑经验,把这两个模块的寄存器配置逻辑和应用实践掰开揉碎了讲清楚,目标是让你看完就能在自己的项目里用起来,避开那些手册里不会明说、但实际开发中一定会遇到的“坑”。
SPI,全称串行外设接口,是一种高速、全双工、同步的串行通信总线。它的价值在于极其简单的硬件需求——通常只需四根线(时钟SCK、主出从入MOSI、主入从出MISO、片选SS),就能实现微控制器与Flash、ADC、DAC、显示屏驱动器等众多外设的通信,速度远比I2C快,协议也比UART简单(没有起始位、停止位、波特率校准的烦恼)。而TIMA-4是一个4通道的16位定时器,它远不止是一个简单的计数器。通过灵活的配置,每个通道都能独立工作于输入捕获(测量外部脉冲宽度或频率)、输出比较(在精确时刻产生电平跳变)或PWM(脉宽调制)模式。这对于需要控制电机转速、LED亮度、生成特定频率信号或者捕捉传感器脉冲的应用来说,是不可或缺的。
理解这两个模块的关键,在于吃透其寄存器配置。手册上的描述往往偏重功能定义,而实际编程时,比特位的设置顺序、中断的配合、以及不同模式下的细微差别,才是成败的关键。接下来,我们就深入到寄存器层面,看看如何让它们听话地工作。
2. SPI模块:寄存器深度解析与主从配置实战
SPI模块的配置核心围绕三个寄存器:控制寄存器(SPCR)、状态与控制寄存器(SPSCR)和数据寄存器(SPDR)。手册给出了定义,但如何组合使用,里面大有学问。
2.1 核心寄存器位功能与配置逻辑
首先看SPI控制寄存器(SPCR)。这里有两个至关重要的位:
- SPE (SPI Enable):这是SPI模块的总开关。一个常见的坑是:在修改其他配置(如波特率、时钟极性)之前,一定要先清除SPE位来禁用模块,配置完成后再重新置位。因为SPI部分电路在使能时,某些配置可能被锁定或处于不稳定状态,直接修改可能导致通信异常。
- SPTIE (SPI Transmit Interrupt Enable):发送中断使能。当发送数据寄存器(SPDR)的数据被转移到移位寄存器、即可以写入下一个数据时,如果此位置1,则会触发中断。对于连续发送数据的场景,利用此中断实现“发送缓冲区空 -> 填充下一字节”的流程,是提高效率的关键,而不是傻等。
然后是SPI状态与控制寄存器(SPSCR),这个寄存器信息量巨大,包含了状态标志和部分控制位。
- SPRF (SPI Receiver Full):接收满标志。这是读取数据的关键。当SPRF=1时,表示接收数据寄存器(SPDR)里有从外设读回来的新数据。手册里强调的清除方法必须严格遵守:先读SPSCR(此时SPRF=1),再读SPDR。这个顺序不能颠倒,否则标志位可能无法正确清除。
- SPTE (SPI Transmitter Empty):发送器空标志。当SPTE=1时,表示发送数据寄存器已空,可以写入新的待发送数据。这里有个重要提示:不要在SPTE为0时向SPDR写入数据,否则会覆盖尚未发送的数据,导致通信错误。
- MODF (Mode Fault) & MODFEN (Mode Fault Enable):模式错误标志及使能。这是SPI主从模式切换的“警卫”。在主机模式下,如果MODFEN=1,则SS引脚被用于模式错误检测。若SS引脚被意外拉低(例如硬件短路),MODF标志会被置1,SPI模块会自动关闭(SPE被清零)并切换为从机模式,以防止总线冲突。在纯主机应用中,如果你确信SS引脚不会被干扰,可以设置MODFEN=0,并将SS引脚当作普通IO使用。但在多主机或热插拔风险的环境中,强烈建议启用MODFEN功能以增强鲁棒性。
- OVRF (Overflow):溢出错误标志。如果SPRF标志(表示数据已收到)还没被软件清除,下一个字节又接收完成了,OVRF就会被置1,且新数据会丢失。这通常是由于CPU处理速度跟不上SPI接收速度,或者中断服务程序执行时间过长导致的。解决方法可以是提高CPU优先级、使用DMA(如果支持)、或者优化代码确保及时读取SPDR。
- SPR1, SPR0 (SPI Baud Rate Select):波特率选择位。仅在主机模式下有效。计算公式为:
Baud Rate = CGMOUT / (2 * BD),其中BD为分频因子(2, 8, 32, 128)。这里的CGMOUT是时钟发生器模块(CGM)的输出,通常与总线时钟(Bus Clock)相关。在配置时,你需要根据外设支持的最高通信速度和系统总线时钟来反推合适的BD值。例如,总线时钟8MHz,选择BD=2,则SPI时钟为2MHz。
2.2 主从模式配置步骤与代码示例
假设我们需要将MC68HC908AT32配置为SPI主机,以2MHz时钟与一个SPI Flash芯片通信,模式为CPOL=0, CPHA=0(即模式0)。
步骤一:引脚初始化首先,需要将SPI相关的引脚(SCK, MOSI, MISO)设置为SPI功能,而非通用IO。这通常通过端口数据方向寄存器(DDR)和相关功能选择寄存器(如果存在)来配置。SS引脚如果用作普通片选,则配置为通用输出并初始化为高电平(无效)。
// 假设SCK在PTB0, MOSI在PTB1, MISO在PTB2, 用户自定义片选PTB3 DDRB = 0x0B; // PTB0, PTB1, PTB3 输出; PTB2 (MISO) 输入 PORTB |= 0x08; // 片选PTB3初始化为高电平(不选中)步骤二:SPI模块初始化遵循“先关闭,再配置,后开启”的原则。
void SPI_Master_Init(void) { // 1. 禁用SPI模块 SPCR &= ~(1<<SPE); // 2. 配置SPCR:使能SPI, 主机模式, 时钟极性CPOL=0, 时钟相位CPHA=0 // 假设SPI控制寄存器位定义如下(需根据实际头文件调整): // SPE: bit5, MSTR: bit4, CPOL: bit3, CPHA: bit2, SPR1: bit1, SPR0: bit0 SPCR = (1<<SPE) | (1<<MSTR); // 使能SPI, 设为主机, CPOL/CPHA默认为0 // 3. 配置SPSCR:设置波特率, 使能错误中断(可选) // 假设SPSCR寄存器位定义:SPR1: bit1, SPR0: bit0 (与SPCR中的可能重复,需查证!) // 注意:MC68HC908AT32的SPR1/SPR0在SPSCR中!这是易错点。 // 清除旧的波特率设置,然后设置为2分频(BD=2)。假设总线时钟8MHz,则SPI时钟=2MHz。 SPSCR &= 0xFC; // 清除SPR1, SPR0位 (假设它们在bit1, bit0) // SPSCR |= 0x00; // SPR1:SPR0 = 00, 即2分频。因为已清除,此行可省略。 // 4. (可选)使能发送中断或接收中断 // SPCR |= (1<<SPTIE); // 使能发送中断 // SPCR |= (1<<SPRIE); // 使能接收中断(需查看SPCR中是否有SPRIE位,或可能在SPSCR) // 注意:MODFEN位在SPSCR中,根据需求设置。此处我们禁用,将SS作普通IO。 // 假设MODFEN是SPSCR的bit2。 SPSCR &= ~(1<<2); // 清除MODFEN }注意:以上代码中的位定义(如
SPE,MSTR)是示例,你必须使用MC68HC908AT32官方或项目对应的头文件中的实际宏定义。最关键的一点是,务必确认SPR1/SPR0波特率选择位是在SPCR还是SPSCR中,不同型号可能不同,这是手册阅读不细最容易出错的地方。
步骤三:数据收发函数编写阻塞式(查询方式)的发送接收函数。
uint8_t SPI_TransferByte(uint8_t data) { // 等待发送缓冲区为空 while(!(SPSCR & (1<<SPTE))); // 等待SPTE标志置位 // 写入数据,启动传输 SPDR = data; // 等待接收完成 while(!(SPSCR & (1<<SPRF))); // 等待SPRF标志置位 // 清除SPRF标志(通过先读SPSCR,再读SPDR) // 注意:读SPSCR的操作通常已经在while条件中完成,但为了严格遵循手册,可以: volatile uint8_t dummy; dummy = SPSCR; // 读状态寄存器 dummy = SPDR; // 读数据寄存器,同时清除SPRF // 返回接收到的数据 return SPDR; // 或者返回dummy,但需要调整上面语句 } // 实际应用中,更常见的写法是直接返回SPDR,因为读SPDR的操作本身就清除了标志。 uint8_t SPI_TransferByte_Optimized(uint8_t data) { while(!(SPSCR & 0x20)); // 等待SPTE (假设SPTE是bit5) SPDR = data; while(!(SPSCR & 0x80)); // 等待SPRF (假设SPRF是bit7) return SPDR; // 读取数据并自动清除SPRF }从机模式的配置差异:
- SPCR配置:清除
MSTR位,设置为从机模式。 - SS引脚:必须配置为输入,并且不能禁用
MODFEN功能。从机的SS引脚由主机控制,用于选择从机。 - 时钟:
SPR1/SPR0在从机模式下无效,时钟完全由主机提供。 - 数据收发:从机无法主动发起传输。它只能在主机提供时钟且SS有效时,被动地接收数据或发送预先装入
SPDR的数据。从机的发送中断(SPTIE)用途有限,因为发送时机不由自己控制。
3. TIMA-4定时器:从输入捕获到PWM生成的完全指南
TIMA-4是一个功能强大的定时器,其核心是一个16位计数器(TACNTH:L),可以自由运行或基于模数寄存器(TAMODH:L)循环计数。四个通道(TACH0-TACH3)可独立配置。
3.1 定时器基础与通道模式解析
定时器状态与控制寄存器(TASC)是总控开关:
TOF,TOIE:定时器溢出标志与中断使能。TSTOP:停止计数器。在修改计数器模值(TAMODH:L)或通道比较值(TACHxH:L)之前,最好先停止计数器,以避免在修改过程中发生比较匹配,产生不可预期的结果。TRST:复位计数器。写1将计数器清零。这是一个“瞬间”操作位,通常硬件会在写入后自动清除它。PS2-PS0:预分频器选择。选择内部总线时钟的分频(1, 2, 4, 8, 16, 32, 64)或外部时钟(TCLK引脚)。这是决定定时器“滴答”快慢的基础。
每个通道都有一个通道状态与控制寄存器(TASCx),它决定了这个通道的行为:
MSxB, MSxA:模式选择位。这是通道的“模式开关”,决定了通道是输入捕获、输出比较还是PWM。ELSxB, ELSxA:边沿/电平选择位。在输入捕获模式下,选择在上升沿、下降沿还是双边沿捕获。在输出比较/PWM模式下,选择匹配时是置高、拉低还是翻转引脚电平。TOVx:溢出翻转位。这是实现PWM的关键!当此位置1时,每次定时器计数器溢出(从TAMOD值回到0),对应的通道引脚电平就会自动翻转一次。结合输出比较动作,就能生成PWM波。CHxF,CHxIE:通道标志位和中断使能。CHxMAX:此位通常与缓冲式PWM相关,表示该通道寄存器对是缓冲对中的主控寄存器。
3.2 输入捕获模式:精准测量脉冲宽度
输入捕获用于测量外部信号的脉冲宽度或周期。原理是:在检测到指定边沿(由ELSxB:A设定)时,将当前16位计数器TACNT的值锁存到通道寄存器TACHxH:L中。
配置步骤:
- 停止并复位定时器(
TSTOP=1, TRST=1)。 - 配置
TASC中的预分频器PS[2:0],选择合适的时间基准。例如,总线时钟8MHz,8分频,则计数器每1us加1。 - 配置目标通道的
TASCx寄存器:MSxB:A = 0:0(输入捕获模式)。ELSxB:A = 0:1(上升沿捕获)或1:0(下降沿捕获)或1:1(任意边沿捕获)。- 使能通道中断
CHxIE=1(如果需要)。
- 启动定时器(
TSTOP=0)。
测量脉冲宽度的实战代码思路:
volatile uint16_t first_edge_time = 0; volatile uint8_t capture_done = 0; volatile uint16_t pulse_width = 0; // 中断服务程序 #pragma interrupt_handler TIMA_CH0_ISR void TIMA_CH0_ISR(void) { static uint8_t edge_count = 0; uint16_t current_capture; // 读取捕获值 (先高字节,后低字节,取决于架构,可能需要原子操作) current_capture = (uint16_t)TACH0H << 8; current_capture |= TACH0L; if(edge_count == 0) { // 第一个边沿(例如上升沿) first_edge_time = current_capture; // 可以切换为捕获下降沿 TASC0 = (TASC0 & 0xCF) | (0x02 << 4); // 设置ELSxB:A为下降沿捕获(假设位4,5是ELS) edge_count = 1; } else { // 第二个边沿(下降沿) // 计算脉冲宽度,需要考虑计数器溢出 if(current_capture >= first_edge_time) { pulse_width = current_capture - first_edge_time; } else { // 发生了溢出,需要加上模值 pulse_width = current_capture + (65535 - first_edge_time); // 假设自由运行模式 } capture_done = 1; edge_count = 0; // 切换回初始边沿捕获,准备下一次测量 TASC0 = (TASC0 & 0xCF) | (0x01 << 4); // 设置回上升沿捕获 } // 清除通道中断标志 (通常通过读TASCx,然后读TACHxL或进行特定操作,具体看手册) // 假设清除标志需要写0到CH0F位 TASC0 &= ~(1<<7); // 清除CH0F标志 (假设bit7是CH0F) }关键点:输入捕获的中断标志清除方法需严格遵循手册。对于TIMA-4,通常需要先读
TASCx寄存器(此时中断标志位CHxF为1),然后再进行一次读通道数据寄存器的低字节(TACHxL)的操作。务必查阅数据手册的详细描述。
3.3 输出比较与PWM模式:生成精准时序与信号
输出比较用于在特定时刻改变引脚电平。PWM是输出比较的一种高级应用,通过周期性地改变占空比来模拟模拟信号。
3.3.1 非缓冲输出比较配置通道为输出比较模式(MSxB:A = 0:1),并设置ELSxB:A来决定匹配时引脚的动作(置1, 清0, 翻转)。你需要向TACHxH:L写入一个比较值。当TACNT计数到该值时,引脚就会根据设定动作。
难点在于动态更新比较值。如果你在计数器运行期间直接写入一个新的比较值,而这个值刚好小于当前计数器值但大于旧的比较值,那么本次比较事件就会错过。手册给出了安全的方法:
- 若要更新为一个更小的值:在输出比较中断中写入新值。因为中断发生时,旧的比较动作刚完成,计数器正在走向下一个周期,有足够时间写入更小的值。
- 若要更新为一个更大的值:在定时器溢出中断中写入新值。因为溢出意味着一个计数周期结束,在新的周期开始时写入更大的值,确保比较能发生。
3.3.2 PWM信号生成:配置与计算PWM的核心是周期和占空比。
- 周期:由定时器溢出频率决定,即由
TAMODH:L(模值)和预分频器PS[2:0]共同决定。PWM_Period = (TAMOD_Value + 1) * (Prescaler / Bus_Clock)。 - 占空比:由通道比较寄存器
TACHxH:L的值决定。在计数器从0开始向上计数到TAMOD的周期内,当TACNT等于TACHx时,引脚电平根据ELSxB:A改变(例如清零);当TACNT溢出时,TOVx位控制的“溢出翻转”功能会将引脚电平再次翻转(例如置高)。这样就形成了一个周期性的脉冲。
PWM初始化步骤(以通道0, 非缓冲模式为例):
- 停止并复位定时器:
TASC |= (1<<TSTOP) | (1<<TRST)。 - 设置PWM周期:写入
TAMODH和TAMODL。例如,需要1kHz的PWM频率,总线时钟8MHz,预分频选择8分频(则定时器时钟为1MHz)。周期 = 1/1kHz = 1000us。定时器计数次数 = 周期 / 定时器时钟周期 = 1000us / 1us = 1000。所以TAMOD值应为999(因为从0开始计数)。 - 设置PWM占空比:写入
TACH0H和TACH0L。例如,需要50%占空比,则比较值 =TAMOD_Value * 50% = 999 * 0.5 ≈ 500。 - 配置通道0控制寄存器
TASC0:MS0B:A = 0:1(输出比较模式)。TOV0 = 1(至关重要!使能溢出翻转,这是生成PWM波的关键)。ELS0B:A = 1:0(输出比较匹配时,清除引脚输出。假设我们希望PWM高电平有效,则匹配时先拉低,溢出时TOV再自动翻转为高,这样高电平时间就是TACH0到溢出的时间)。
- 启动定时器:
TASC &= ~(1<<TSTOP)。
3.3.3 缓冲式PWM/输出比较这是TIMA-4的一个高级功能,通过将两个通道(0&1, 2&3)配对,实现无毛刺的PWM占空比更新。其原理是使用两套寄存器(TACH0和TACH1)交替工作。当当前周期由TACH0控制时,软件可以安全地更新TACH1的值。在下一个定时器溢出时,硬件会自动切换到TACH1控制输出,同时TACH0变为缓冲寄存器,可以接受下一次更新。这样就避免了在单个寄存器更新时可能出现的脉冲宽度错误。
配置缓冲PWM,以通道0和1为例:
- 设置
TASC0中的MS0B=1(MS0A此时通常为0),这将链接通道0和1,并使通道0成为主控通道。 - 通道1的寄存器
TASC1在此模式下被忽略,PTE3/TACH1引脚可作为普通IO使用。 - 初始化
TACH0H:L和TACH1H:L为不同的占空比值。 - 在程序运行中,永远只向当前非活动的缓冲寄存器(通过标志位
CHxMAX判断或自己软件跟踪)写入新的占空比值。硬件会在下一个溢出边界自动切换。
4. 实战应用:SPI驱动TLC5615 DAC与TIMA-4生成PWM控制LED亮度
让我们结合一个简单案例,把SPI和TIMA-4用起来:用SPI控制一颗TLC5615数模转换器(DAC)输出一个电压,同时用TIMA-4生成一个PWM信号控制LED亮度,并且让PWM的占空比受DAC输出电压的某个设定值影响(模拟一个简单的闭环控制概念)。
系统设计:
- SPI:主机模式,用于向TLC5615发送16位数据(高4位为虚拟位,中间10位为数据,低2位为填充)。
- TIMA-4:通道0配置为PWM模式,驱动一个LED。PWM占空比由一个变量控制,该变量可被SPI接收到的命令或内部计算更新。
关键代码整合:
#include <mc68hc908at32.h> // 假设的头文件 #define DAC_CS_PORT PORTB #define DAC_CS_PIN 3 uint16_t pwm_duty = 500; // 初始占空比,对应TAMOD=999时的50% uint16_t dac_value = 512; // DAC输出值,中间值 void SPI_Init(void) { // ... 初始化代码如前文所述 ... DDRB |= (1<<DAC_CS_PIN); // DAC片选引脚为输出 DAC_CS_PORT |= (1<<DAC_CS_PIN); // 初始不选中 } void TIMA_PWM_Init(void) { // 停止并复位定时器 TASC |= (1<<TSTOP) | (1<<TRST); // 设置预分频为8分频,总线时钟8MHz -> 定时器时钟1MHz TASC = (TASC & 0xF8) | 0x03; // 假设PS[2:0]=011b为8分频 // 设置PWM周期为1ms (1kHz频率) TAMODH = (999 >> 8) & 0xFF; TAMODL = 999 & 0xFF; // 设置初始占空比 TACH0H = (pwm_duty >> 8) & 0xFF; TACH0L = pwm_duty & 0xFF; // 配置通道0为PWM模式,溢出翻转,比较匹配时清低 // MS0B:A=01 (输出比较), TOV0=1, ELS0B:A=10 (输出比较时清低) TASC0 = (1<<TOV0) | (0x01 << 4); // 假设位4,5是MS0A,MS0B?需要根据寄存器位调整。 // 更准确的设置,需要参考寄存器位定义: // TASC0 = (0 << CH0MAX) | (0 << TOV0) | (0x02 << ELS0A) | (0x01 << MS0A); // 启动定时器 TASC &= ~(1<<TSTOP); } void DAC_Write(uint16_t data) { // TLC5615需要16位数据,格式:{4'b0, 10-bit data, 2'b0} uint16_t send_data = (data & 0x03FF) << 2; // 将10位数据左移2位,低2位补0 // 高4位已经是0 DAC_CS_PORT &= ~(1<<DAC_CS_PIN); // 选中DAC // 发送高8位 SPI_TransferByte((send_data >> 8) & 0xFF); // 发送低8位 SPI_TransferByte(send_data & 0xFF); DAC_CS_PORT |= (1<<DAC_CS_PIN); // 取消选中 } void main(void) { System_Init(); // 系统初始化,时钟等 SPI_Init(); TIMA_PWM_Init(); EnableInterrupts; // 开启全局中断 DAC_Write(dac_value); // 初始DAC输出 while(1) { // 主循环,这里可以响应按键、串口命令等来更新dac_value和pwm_duty // 例如,根据某个算法更新pwm_duty // pwm_duty = some_function(dac_value, sensor_read); // 更新PWM占空比(非缓冲模式,需注意同步问题) // 简单情况下,可以在主循环更新,但为了精确,最好在定时器溢出中断中更新 // 这里演示在主循环更新(适用于变化不频繁的场景) TACH0H = (pwm_duty >> 8) & 0xFF; TACH0L = pwm_duty & 0xFF; // 可以添加延时或其他任务 Delay_ms(10); } } // 定时器溢出中断服务程序(用于安全更新PWM占空比,或处理溢出相关任务) #pragma interrupt_handler TIMA_OVF_ISR void TIMA_OVF_ISR(void) { // 如果需要非常精确地更新PWM(尤其是更新为更大的值),可以在这里写TACH0x // 清除溢出标志TOF TASC &= ~(1<<TOF); // 假设TOF是TASC的bit7 }5. 调试技巧与常见问题排查
在实际开发中,寄存器配置对了,但电路就是不工作,这种情况太常见了。以下是一些排查思路:
SPI通信失败:
- 时钟和相位(CPOL, CPHA)不匹配:这是最常见的问题。你的MCU和外设必须使用相同的模式(0,1,2,3)。务必仔细核对外设数据手册的时序图。用逻辑分析仪抓取SCK, MOSI, MISO波形是最直接的调试方法。观察时钟空闲电平(CPOL)和数据采样边沿(CPHA)。
- 片选(SS)信号问题:在主机模式下,如果你不使用MODF功能,SS引脚可能被配置为普通输出。确保在通信开始时拉低,通信结束后拉高。时序要满足外设要求。
- 波特率过高:过高的SPI时钟可能导致信号边沿不陡峭、建立保持时间不足。尤其是长距离或布线不佳时。尝试降低波特率(增大分频因子BD)。
- 中断冲突:如果使能了SPI中断,但中断服务程序(ISR)没有及时清除
SPRF或SPTE标志,会导致后续中断无法进入,通信卡死。确保ISR中严格按手册要求清除标志位。 - 数据寄存器访问冲突:避免使用“读-修改-写”指令操作
SPDR。因为读SPDR返回的是接收缓冲区,写SPDR操作的是发送缓冲区,这是两个不同的物理寄存器。
TIMA-4 PWM/输出比较无输出:
- 引脚功能未正确映射:TIMA通道的引脚(如
PTE2/TACH0)是复用的。你需要通过相关的端口控制寄存器,将引脚功能设置为定时器输出,而不是通用IO。通常有一个“外设功能使能”位或寄存器需要配置。 TOVx位未设置:在PWM模式下,必须将TOVx(溢出翻转)位置1。否则,引脚只会在比较匹配时动作一次,不会产生连续的PWM波。这是我早期最容易忘记的一步。- 计数器未运行:检查
TSTOP位是否为0。检查预分频器PS[2:0]是否配置正确。可以用调试器读取TACNTH:L的值,看它是否在递增。 - 比较值大于模值:如果
TACHx的值大于TAMOD的值,在模计数模式下,比较事件永远不会发生。确保TACHx<=TAMOD。 - 输出动作配置错误:检查
ELSxB:A位。对于常见的“高电平有效”PWM,通常配置为:比较匹配时清除输出(ELSxB:A=1:0),同时使能溢出翻转(TOVx=1)。这样,周期开始时溢出翻转置高,比较匹配时清低,产生一个正脉冲。 - 缓冲模式下的更新错误:在缓冲PWM模式下,错误地向当前激活的通道寄存器写入数据,会立即生效,破坏PWM的连续性,可能产生毛刺。必须通过
CHxMAX标志或软件状态机跟踪当前活跃的缓冲区,只更新非活跃的那个。
输入捕获值不准:
- 边沿检测抖动:如果被测信号有噪声,可能导致多次误触发。可以在硬件上增加RC滤波,或者在软件上采用“连续捕获多次取平均”或“去抖动”算法。
- 计数器溢出未处理:在测量长脉冲时,16位计数器可能溢出。你的中断服务程序必须能够处理溢出情况。通常需要定义一个32位的软件计数器,在定时器溢出中断(
TOF)中递增,在输入捕获中断中,将软件计数器的高位与捕获到的计数器低位结合起来计算时间。 - 中断响应延迟:输入捕获中断发生后,到CPU开始执行ISR、读取捕获值,存在延迟。但这个延迟是固定的,对于测量相对时间(如脉冲宽度、周期)影响不大,因为两次捕获的延迟基本相同。但对于测量绝对时间点,则需要考虑。
最后,也是最宝贵的经验:善用调试工具。如果没有逻辑分析仪,可以尝试将关键引脚(如PWM输出、捕获输入)连接到LED(加限流电阻)或通过串口打印关键寄存器值,进行最基础的调试。有条件的,一定要用逻辑分析仪查看时序,这是解决数字外设问题最直观的方式。每一次调试的过程,都是对寄存器理解加深的过程。
