AVR单片机TCB定时器:输入捕获、单次触发与PWM模式实战详解

发布时间:2026/7/1 11:39:18
AVR单片机TCB定时器:输入捕获、单次触发与PWM模式实战详解 1. 从“定时”到“捕获”TCB定时器的角色演变在嵌入式开发尤其是AVR单片机项目中定时器是驱动一切周期性任务、测量时间间隔、生成精确波形的核心引擎。我们最熟悉的往往是TC0、TC1这类通用定时器用于延时、PWM输出等常规操作。然而当项目需求变得复杂比如需要精确测量一个外部脉冲的宽度、在特定延迟后执行一次高精度动作或者需要更灵活的PWM生成时通用定时器有时会显得捉襟见肘。这时AVR DA/DB/DD等新系列单片机中引入的TCBTimer/Counter Type B定时器就成为了解决问题的利器。TCB定时器被设计为一种“增强型”定时器它剥离了TC0/TC1上一些复杂的多模式功能专注于三件事高精度的输入捕获、可靠的单次触发One-Shot以及简洁的PWM生成。它的设计哲学是“专精而非全能”这使得它在特定场景下比通用定时器更高效、更易用。很多开发者初次接触TCB时可能会被其相对简单的寄存器配置所迷惑以为它功能有限。但恰恰是这种简洁性让它避免了模式冲突和配置陷阱在输入捕获和单次定时任务中表现出了极高的稳定性和精度。我个人在多个电机测速、超声波测距和精密定时开关项目中都深度依赖TCB。例如在测量直流电机编码器脉冲频率时TCB的输入捕获模式配合事件系统可以几乎无开销地记录下脉冲边沿的精确时刻避免了在中断中处理大量代码带来的时间抖动。这种“把专业的事交给专业的硬件”的思路正是TCB的价值所在。接下来我将抛开数据手册的平铺直叙结合真实踩坑和实战经验带你深入TCB的这三个核心模式看看它们如何解决实际工程问题。2. 核心模式一输入捕获模式——捕捉瞬间的艺术输入捕获功能是TCB的看家本领其核心目的是精确记录某个外部事件通常是引脚上的边沿信号发生时定时器计数器的瞬时值。这个看似简单的操作却是测量脉冲宽度、频率、占空比乃至实现数字解调的基础。2.1 工作原理与时钟源选择精度从何而来TCB本质上是一个16位的向上计数器TCNT。在输入捕获模式下它持续在后台运行计数。当配置的捕获引脚通常是芯片复用引脚如TCB0_WO对应的PA2上发生指定的电平时钟上升沿、下降沿或任意边沿时硬件会自动将当前TCNT的值锁存到捕获寄存器CCMP中并可以产生一个中断标志。这里第一个关键选择是时钟源CLKSEL。TCB的时钟可以来自系统时钟CLK_PER这是最常用的选择计时精度最高。如果你的系统时钟是20MHz那么每个计数周期就是50ns。这是高精度测量的基础。预分频后的系统时钟当需要捕获较长的时间间隔时可以对系统时钟进行2、4、8...等分频以扩展计数范围避免计数器过快溢出。事件系统Event System这是AVR单片机的一大特色。TCB可以直接由另一个外设如另一个定时器的溢出、ADC转换完成触发的事件来驱动计数。这可以实现完全由硬件联动的精密时序控制无需CPU干预。例如可以用TC0的溢出事件来驱动TCB计数从而实现两个定时器的级联获得远超16位的计数范围。注意数据手册中可能还会提到TCA作为时钟源。在AVR DA/DB系列中这通常是通过事件系统间接实现的而非直接连接。配置时务必查阅当前芯片的“复用信号MUX”表格确认正确的信号路径。选择时钟源的核心原则是在保证不溢出的前提下尽可能使用更高的计时分辨率。例如要测量一个预计最大10ms的脉冲系统时钟20MHz。如果直接使用系统时钟计数器在10ms内会计数200,000次远小于6553516位溢出值此时应直接使用系统时钟以获得50ns的分辨率。如果脉冲可能长达1秒则必须使用分频或事件系统级联来扩展量程。2.2 实战配置从寄存器到代码我们以AVR128DA48为例配置TCB0在PA2引脚上捕获上升沿使用系统时钟并使能中断。第一步引脚配置PA2引脚需要配置为输入并启用上拉电阻如果需要以消除抖动。更重要的是要通过PORTMUX.TCBROUTEA寄存器将TCB0的输出路由到正确的端口。对于DA系列TCB0默认路由到PA2但显式设置是个好习惯。// 设置PA2为输入启用上拉根据外部电路决定是否需要 PORTA.DIRCLR PIN2_bm; PORTA.PIN2CTRL PORT_PULLUPEN_bm; // 启用上拉 // 确认TCB0路由到PORTA (对于DA系列这常常是默认值但显式设置更安全) PORTMUX.TCBROUTEA PORTMUX_TCB0_PORTA_gc;第二步TCB模式与时钟配置这是核心步骤。我们需要配置TCB0.CTRLA控制A、TCB0.CTRLB控制B和TCB0.EVCTRL事件控制寄存器。// TCB0.CTRLA - 控制A寄存器 // 选择时钟源系统时钟不分频 (CLK_PER) // 同时启用定时器ENABLE位 TCB0.CTRLA TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; // TCB0.CTRLB - 控制B寄存器 // 设置模式为输入捕获CNTMODE1 // 设置捕获边沿为上升沿CCMPINIT0, CCMPEN1? 注意不同系列位域名称可能不同 // 对于DA系列更常见的配置是 TCB0.CTRLB TCB_CNTMODE_INT_gc; // 输入捕获模式使用中断 // 边沿选择通常在EVCTRL或INTCTRL寄存器需要仔细查手册 // TCB0.EVCTRL - 事件控制寄存器 // 使能输入捕获事件并选择边沿 TCB0.EVCTRL TCB_CAPTEI_bm | TCB_EDGE_bm; // 使能捕获事件上升沿触发第三步中断配置使能捕获中断并设置中断控制器。// TCB0.INTCTRL - 中断控制寄存器 TCB0.INTCTRL TCB_CAPT_bm; // 使能捕获中断 // 在合适的地方如main函数初始化部分使能全局中断 sei();第四步中断服务程序ISR当中断发生时读取CCMP寄存器值并清除中断标志。ISR(TCB0_INT_vect) { // 读取捕获值 uint16_t capture_value TCB0.CCMP; // 处理捕获值例如计算与上一次捕获的时间差 static uint16_t last_capture 0; uint16_t period capture_value - last_capture; // 注意处理计数器溢出 last_capture capture_value; // 根据时钟频率将period转换为时间微秒、纳秒 // uint32_t time_us (period * 1000000UL) / F_CPU; // 必须手动清除中断标志位 TCB0.INTFLAGS TCB_CAPT_bm; }2.3 避坑指南溢出处理与噪声滤波坑1计数器溢出16位计数器最大计数值为65535。如果两次捕获事件间隔过长计数器可能已经溢出一次或多次。简单的current - last计算会得到错误结果。正确的处理方法是使用溢出中断或利用捕获值本身的特性进行扩展。一种稳健的方法是启用定时器溢出中断如果支持或者在捕获中断中检查TCNT是否接近65535并做预判。更通用的方法是使用32位或64位的软件扩展计数器。在每次捕获中断中不仅读取CCMP还读取一个软件维护的、记录溢出次数的overflow_count。volatile uint32_t overflow_count 0; volatile uint32_t last_capture_extended 0; ISR(TCB0_INT_vect) { uint16_t capture_hardware TCB0.CCMP; uint32_t capture_extended (overflow_count 16) | capture_hardware; uint32_t period_extended capture_extended - last_capture_extended; last_capture_extended capture_extended; // 使用扩展后的值进行时间计算 TCB0.INTFLAGS TCB_CAPT_bm; } // 假设有溢出中断如果TCB支持或使用另一个定时器监控 ISR(OTHER_OVF_VECTOR) { overflow_count; }坑2输入噪声与抖动机械开关或长导线可能引入毛刺导致误触发。TCB本身硬件滤波能力有限。解决方案有软件去抖在中断中不立即处理而是启动一个短延时如几毫秒后再读取引脚状态确认。但这会损失实时性。硬件滤波在输入引脚加RC低通滤波电路。这是最有效的方法。利用事件系统可以先通过一个具有数字滤波功能的外设如外部中断INT某些型号支持滤波对信号进行整形再通过事件系统触发TCB捕获。这实现了硬件级的可靠触发。3. 核心模式二单次触发模式——精准的“一次性”定时单次触发模式是TCB另一个极具价值的模式。顾名思义它让定时器在启动后计数到一个预定值CCMP寄存器设定然后自动停止并产生中断。这完美解决了“在精确延时后执行一个动作”的需求并且在计时期间完全不需要CPU参与节省了软件定时器轮询的开销。3.1 为何需要单次触发对比软件延时假设你需要让一个LED在系统启动5秒后点亮。新手可能会写一个_delay_ms(5000)。这个函数是阻塞的CPU在这5秒内什么也做不了。使用通用定时器溢出中断你需要配置比较匹配值并不断重置计数器逻辑稍显复杂。TCB的单次触发模式则非常优雅将CCMP设置为5秒对应的计数值例如5s * 20MHz 100,000,000显然超出16位因此必须使用分频或级联。将模式设置为单次触发CNTMODE2。启动定时器写CTRLA或触发事件。CPU可以立即去处理其他任务。5秒后TCB计数到CCMP值自动停止并触发中断。你在中断里点亮LED即可。3.2 配置详解与级联应用配置单次触发模式的关键寄存器是TCBn.CTRLB和TCBn.CCMP。// 假设使用系统时钟128分频则每个计数周期为 6.4us (20MHz / 128) // 要定时5ms: 5ms / 6.4us ≈ 781.25取整为781 uint16_t target_count 781; // 1. 先写CCMP目标值 TCB0.CCMP target_count; // 2. 配置模式为单次触发并选择时钟 // CLKSEL选择CLK_PER/128模式为单次 TCB0.CTRLA TCB_CLKSEL_CLKDIV128_gc; // 先不启用ENABLE TCB0.CTRLB TCB_CNTMODE_SINGLE_gc; // 单次触发模式 // 3. 通过软件或事件启动定时器 // 方法A: 软件启动 TCB0.CTRLA | TCB_ENABLE_bm; // 写入ENABLE位计数器开始从0计数 // 方法B: 通过事件系统启动更灵活可由其他外设触发 // 需要配置EVCTRL寄存器并配置事件生成器。对于更长的定时超过16位计数器最大范围就需要级联Cascading。最常用的方法是利用事件系统让另一个定时器如TCA的溢出作为TCB的计数时钟CLKSEL选择事件或者用TCA的溢出事件来“踢”一下TCB的计数使能。这样一个32位甚至更长的“虚拟定时器”就由硬件构成了。3.3 实战案例硬件看门狗与精确时序链单次触发模式的一个高级应用是构建一个硬件看门狗。你可以配置一个TCB为单次模式定时比如100ms。在主循环的关键位置你通过写TCBn.CNT 0来重置计数器在单次模式下写入CNT寄存器可以重置计数器并重新开始。如果程序跑飞无法及时“喂狗”TCB就会超时触发中断在中断里执行系统复位。另一个案例是构建无需CPU干预的精确时序链。例如控制一个雷达模块先发一个10us的触发脉冲等待100us后打开ADC采样窗口采样持续50us后关闭。这可以用三个TCB或一个TCA加两个TCB配合事件系统完成TCB0单次模式10us后触发中断在中断里拉低触发引脚并通过事件系统启动TCB1。TCB1单次模式100us后触发中断在中断里启动ADC并通过事件系统启动TCB2。TCB2单次模式50us后触发中断在中断里停止ADC。 整个流程由硬件自动执行时序精度可达时钟周期级别CPU只需初始化之后可休眠以省电。4. 核心模式三PWM输出模式——简洁的波形生成器虽然TCA是更强大的PWM发生器但TCB也提供了基本的8位PWM输出功能。它的PWM模式相对简单配置快捷适用于只需要一路简单PWM的场合比如控制LED亮度、驱动蜂鸣器或生成一个基础舵机信号。4.1 TCB PWM的工作原理与局限性TCB在PWM模式下计数器工作在8位模式计数范围0-255。它比较TCNT和CCMP寄存器此时CCMP的低8位CCMPL作为比较值根据比较结果驱动输出引脚电平。当TCNT CCMPL时输出高电平或低电平取决于极性配置。当TCNT CCMPL时输出反转。因此CCMPL的值直接决定了占空比。PWM频率由时钟源和计数周期决定Fpwm Fclk / (256 * Prescaler)。TCB PWM的主要局限性在于分辨率固定为8位对于需要高精度调光或电机控制的应用可能不够。通常只有一路输出每个TCB只能控制一个引脚。灵活性较低缺少TCA那样的波形缓冲、双斜率模式等高级功能。所以TCB的PWM模式定位是补充和简化。当你只需要一路简单的PWM且不想配置复杂的TCA多通道时TCB是绝佳选择。4.2 配置步骤与占空比计算配置TCB0在PA2引脚输出PWM频率约为1kHz系统时钟20MHz。计算参数目标频率Fpwm 1kHz 1000 Hz。Fpwm Fclk / (256 * Prescaler)Prescaler Fclk / (256 * Fpwm) 20,000,000 / (256 * 1000) ≈ 78.125预分频系数必须取2的幂次。最接近的是64得到~1220Hz或128得到~610Hz。我们选择128分频得到约610Hz。配置代码// 1. 引脚配置将PA2设置为输出因为要输出PWM波形 PORTA.DIRSET PIN2_bm; // 路由TCB0输出到PA2 (对于DA系列通常默认即是) PORTMUX.TCBROUTEA PORTMUX_TCB0_PORTA_gc; // 2. 配置TCB为PWM模式 // 先停止定时器 TCB0.CTRLA 0; // 设置PWM模式 (CNTMODE3)并可能设置输出极性取决于CTRLB具体位 // 对于DA系列PWM模式通常通过CTRLB.CNTMODE选择且可能自动使能输出 TCB0.CTRLB TCB_CNTMODE_PWM8_gc; // 8位PWM模式 // 如果需要反相输出可以设置相应的位例如TCB0.CTRLB | TCB_CCMPINIT_bm; (请查手册确认) // 3. 设置比较值决定占空比 (0-255) // 例如设置50%占空比255 * 0.5 127.5取整128 TCB0.CCMP 128; // CCMPL被用作比较值 // 4. 配置时钟并启动定时器 // 使用CLK_PER/128 作为时钟源 TCB0.CTRLA TCB_CLKSEL_CLKDIV128_gc | TCB_ENABLE_bm;运行以上代码后你应该能在PA2引脚上用示波器测量到频率约610Hz占空比约50%的PWM方波。4.3 动态调整占空比与注意事项在程序运行中动态改变PWM占空比非常简单只需更新CCMP寄存器即可。硬件会在下一个PWM周期自动采用新值。// 将亮度从50%渐变到100% for (uint8_t duty 128; duty 255; duty) { TCB0.CCMP duty; _delay_ms(10); // 简单的延时实现渐变效果 }重要提示在PWM模式下CCMP寄存器是双缓冲的。这意味着你写入的新值不会立即生效而是要等到当前PWM周期结束计数器归零时才会加载。这避免了在周期中间改变占空比可能产生的毛刺脉冲。但这也意味着如果你需要非常同步地更新多路PWM例如TCB和TCA需要考虑它们周期是否对齐。另一个常见问题是输出引脚无信号。请按以下顺序排查引脚方向是否已设置为输出DIRSET引脚复用PORTMUX.TCBROUTEA是否已正确配置不同型号芯片的TCB输出可能路由到不同引脚必须查表确认。模式是否正确CTRLB.CNTMODE是否确为PWM模式定时器使能CTRLA.ENABLE位是否置1CCMP值CCMP是否被设置为一个介于0和255之间的值如果为0则输出恒低如果为255则输出恒高或反之取决于极性。5. 模式融合与高级应用超越数据手册的用法掌握了三种基本模式后我们可以将它们组合或者利用TCB的其他特性如事件系统接口、异步计数来实现更巧妙的系统设计。5.1 输入捕获与单次触发的组合测量长脉冲假设要测量一个可能长达数秒的高电平脉冲宽度直接输入捕获会因计数器频繁溢出而难以处理。我们可以结合两种模式将TCB配置为单次触发模式CCMP设置为一个较长的定时如1秒使用低频时钟源。将脉冲信号同时连接到TCB的捕获引脚和外部中断引脚。上升沿触发外部中断在中断中启动TCB单次定时器。下降沿触发TCB的输入捕获通过事件系统或引脚变化。在TCB的输入捕获中断中读取CCMP值这是下降沿时刻的计数值同时TCB单次定时器仍在后台计数。如果脉冲在单次定时器超时1秒前结束我们就能通过捕获值精确计算脉宽精度为单次定时器的时钟分辨率。如果脉冲超过1秒单次定时器会先产生溢出中断我们在中断里记录“已过了1秒”然后重置并重启单次定时器继续测量。最终脉宽 溢出次数 * 1秒 最后一次捕获值对应的时间。这种方法用硬件完成了大部分计时工作软件只需处理溢出次数大大减轻了CPU负担和中断延迟误差。5.2 利用事件系统构建外设联动事件系统是AVR新系列单片机的“神经系统”TCB是其重要参与者。它可以作为事件的使用者Consumer也可以作为事件的产生者Generator。作为使用者TCB可以被其他外设的事件触发。例如由ADC转换完成触发单次定时ADC转换完成后产生事件触发TCB开始一个单次定时用于实现精确的采样保持时间。由TCA溢出触发计数实现硬件级联扩展定时范围。作为产生者TCB可以产生事件去驱动其他外设。例如TCB输入捕获触发DMA当捕获发生时产生事件触发DMA将捕获值CCMP直接搬运到内存数组实现零CPU开销的数据记录。TCB单次定时结束触发DAC输出用于生成精确的时序波形。配置事件系统的核心是EVSYS.CHANNELn和EVSYS.USERTCBn寄存器。你需要指定事件生成器如ADC0_RESRDY连接到某个事件通道再将这个通道分配给TCB作为异步事件输入。// 示例配置ADC0转换完成事件通过通道0触发TCB0计数 // 1. 选择事件生成器 (ADC0转换完成) 连接到通道0 EVSYS.CHANNEL0 EVSYS_GENERATOR_ADC0_RESRDY_gc; // 2. 将通道0的事件用户设置为TCB0的事件输入 EVSYS.USERTCB0 EVSYS_USER_CHANNEL0_gc; // 3. 配置TCB0使用异步事件作为计数使能或时钟具体取决于CTRLA.CLKSEL选择 TCB0.CTRLA TCB_CLKSEL_EVENT_gc; // 使用事件作为时钟源 // 或者在单次模式下用事件来启动计数 // TCB0.EVCTRL TCB_STARTEI_bm; // 使能事件启动5.3 低功耗设计中的TCB在电池供电设备中TCB可以成为低功耗的关键。由于其可以配置为在异步模式下使用外部32.768kHz晶体或内部超低功耗振荡器运行并且能够通过事件系统与其他外设联动因此可以在CPU深度睡眠SLEEP_MODE_STANDBY时仍然工作。一个典型场景是周期性唤醒配置TCB为单次触发模式时钟源选择外部32.768kHz时钟CLK_PER外部。设置CCMP为需要的睡眠时间对应的计数值。使能TCB中断。启动TCB然后让CPU进入STANDBY睡眠模式。TCB在后台独立计数超时后产生中断该中断可以将CPU唤醒。由于使用的是32kHz时钟功耗极低定时却非常精准。这种方案比使用看门狗定时器WDT唤醒精度更高比使用RTC定时器更灵活因为TCB可以有多路。在需要长时间、多组复杂定时的低功耗数据记录器中这种设计非常有效。