1. 项目概述用STM32的TIMER当“外部事件计数器”在嵌入式开发里我们经常需要统计外部脉冲的数量比如旋转编码器的脉冲、光电传感器的触发次数或者简单点就是想知道一个引脚上来了多少个上升沿。很多朋友的第一反应可能是用外部中断EXTI来一个脉冲就进一次中断然后在中断服务函数里累加一个变量。这个方法对于低频信号比如每秒几个脉冲没问题但一旦频率上去比如几十KHz甚至更高频繁的中断就会成为CPU不可承受之重严重拖累系统性能甚至导致丢失脉冲。这时候STM32片内集成的通用定时器TIMER的“外部时钟模式”功能简直就是为我们量身定做的硬件外挂。它能把一个指定的GPIO引脚通常是定时器的ETR引脚直接作为定时器的计数时钟源。外部来一个有效边沿定时器的计数器就自动加1或减1完全不需要CPU干预。你可以把它想象成一个独立的、硬件实现的“电子计数器”CPU只需要在需要的时候去读取一下当前的计数值就行效率极高且绝对精准。今天要拆解的这个例程就完美演示了如何将STM32的TIM2配置成一个纯粹的外部事件计数器。它的核心思路是使用TIM2的ETR引脚PA1作为计数信号的输入口同时用另一个GPIOPC6模拟生成一个方波信号用于测试。我们将通过代码和原理把配置的每一个参数、每一步操作背后的“为什么”都讲清楚让你不仅能照搬代码更能理解其精髓举一反三应用到你的编码器测速、频率测量或者产品产量计数等实际项目中去。2. 核心思路与硬件连接解析2.1 为什么选择“外部时钟模式2”STM32的通用定时器有几种时钟源内部时钟CK_INT、外部触发输入ETR、内部触发输入ITRx用于定时器级联等。要实现外部计数我们需要让定时器的时钟来自外部引脚这就需要用到“外部时钟模式”。细看参考手册你会发现外部时钟模式又分两种外部时钟模式1External Clock Mode 1时钟信号来自定时器的某个输入通道TI1或TI2经过边沿检测器后作为触发信号TRGI驱动定时器。这种方式更常用于编码器接口或特定的触发同步。外部时钟模式2External Clock Mode 2时钟信号直接来自ETR引脚。这是最直接、最纯粹的外部计数方式。ETR引脚上的信号经过可配置的极性选择、预分频器和滤波器后直接成为定时器的计数时钟CK_PSC。本例程的目标是计数信号源明确来自一个GPIO引脚PA1因此外部时钟模式2是最自然、最合适的选择。它配置简单意图清晰ETR脚来一个脉冲上升沿或下降沿可配置计数器就动一下。2.2 引脚功能映射与硬件连接要点理解STM32的引脚复用功能是正确配置的前提。不是任何一个GPIO都能作为TIM2的ETR输入。信号输入引脚PA1对于STM32F1系列这也是本例程最可能基于的系列TIM2的ETR输入固定映射在PA1引脚上。这意味着你必须将PA1配置为复用功能输入或者浮空输入具体看外部驱动能力信号才能进入TIM2的内部电路。如果你错接到PA0或PA2代码怎么配都不会有计数。信号输出引脚PC6这里选择PC6来模拟方波是一个随意的选择因为本例程中它并不需要连接到任何定时器的特殊功能仅仅作为一个普通的GPIO输出。用GPIO_SetBits和GPIO_ResetBits配合延时函数来模拟一个频率粗略可控的方波。硬件连接测试时用一根杜邦线将PC6输出和PA1输入短接即可。这样PC6产生的方波就直接送到了TIM2的计数输入端。在实际项目中PA1连接的就是你的外部信号源如光电传感器输出、另一MCU的脉冲信号等。注意不同系列的STM32定时器引脚映射可能不同。例如在STM32F4系列上TIM2的ETR可能映射在PA0或PA5上。务必查阅你所使用芯片型号对应的《数据手册》Datasheet中的“Pinouts and pin description”章节以及《参考手册》Reference Manual中定时器章节的“TIMx alternate function mapping”部分。这是硬件工程师和嵌入式工程师最重要的习惯之一。3. 代码逐行深度解析与配置原理下面我们跳出代码注释从“为什么要这样设置”的角度把每一行关键配置嚼碎了讲。3.1 定时器时基单元初始化设定计数器的“舞台”TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 0x00; TIM_TimeBaseStructure.TIM_ClockDivision 0x0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure);这是定时器的基础框架配置即使使用外部时钟这个框架也决定了计数器如何运行。TIM_Period 0xFFFF这是自动重装载寄存器ARR的值设置为最大值65535。为什么是0xFFFF因为在这个纯计数应用中我们不希望计数器自动重载产生更新事件UIF或中断。我们只关心它从0开始能连续计多少个数。设置成最大值意味着计数器从0累加到65535后下一个脉冲会变成0溢出但如果我们在此前就读取了计数值这个溢出对我们没影响。如果你想计数超过65535就需要在计数器溢出时触发中断在中断里用一个软件变量做高位累加实现扩展计数。TIM_Prescaler 0x00预分频器设置为0即不分频。这里是个关键理解点这个预分频器是对定时器时钟源CK_PSC进行分频。现在我们的时钟源是外部ETR引脚信号。TIM_Prescaler0意味着ETR引脚每来一个有效边沿计数器CNT就加1。如果你设置TIM_Prescaler1则是每2个边沿CNT加1因为分频器是N1生效。所以在外部时钟模式下这个预分频器实际上变成了“输入脉冲分频器”。本例程不想分频所以设为0。TIM_ClockDivision 0x0时钟分频这个参数与外部时钟模式2的计数功能无关。它CDR是决定定时器内部时钟CK_INT与数字滤波器采样时钟f_DTS之间的比例主要影响的是输入捕获模块的滤波器和死区时间生成。对于纯ETR计数通常设为0即可。TIM_CounterMode TIM_CounterMode_Up计数模式为向上计数。从0开始加到ARR值0xFFFF。也可以配置为向下计数取决于你的应用逻辑。3.2 外部时钟模式2的精细配置信号如何被“识别”TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0);这行代码是激活外部时钟模式2的灵魂。我们拆开看它的四个参数TIM2指定定时器。TIM_ExtTRGPSC_OFFETR外部触发预分频器。这是独立于时基单元预分频器的、专属于ETR通路的又一个分频器。它有4种选择OFF不分频、DIV22分频、DIV44分频、DIV88分频。本例选择OFF意味着ETR引脚信号不经过这个分频器直接进入后续环节。如果需要降低计数频率可以优先考虑修改这里而不是修改时基单元的TIM_Prescaler因为逻辑上更清晰。TIM_ExtTRGPolarity_NonInvertedETR极性选择。这是关键NonInverted不反相ETR引脚上的上升沿或高电平取决于滤波器配置被识别为有效信号。这是最常用的设置。Inverted反相ETR引脚上的下降沿或低电平被识别为有效信号。你的外部信号是上升沿有效还是下降沿有效必须根据传感器或信号源的规格来正确设置这个参数否则计数值不会变化。0ETR数字滤波器参数。这个参数非常重要特别是在工业环境或有噪声的场景。它由一个4位值0-15表示用于配置一个采样频率和数字滤波窗口。0无滤波器。ETR信号直接以f_DTS频率被采样。f_DTS由前面的TIM_ClockDivision决定。当TIM_ClockDivision0时f_DTS f_CK_INT系统定时器时钟通常72MHz。这意味着在72MHz的频率下对ETR信号采样抗噪声能力最弱但响应最快。1~15启用滤波器。例如设置值为1代表f_SAMPLING f_CK_INT, N2。其含义是以f_CK_INT的频率对ETR引脚进行采样当连续采样到2次N相同的电平时才认为输入电平发生了改变。这能有效滤除毛刺脉冲。如何选择如果你的信号很干净用0。如果环境噪声大信号有毛刺就需要根据噪声脉宽和信号频率来计算并选择一个合适的滤波器值。例如你的信号是100kHz但可能有50ns的毛刺而f_CK_INT72MHz周期约13.9ns那么毛刺会持续约3-4个采样周期。设置N4或N6的滤波器就能将其滤除而真正的信号边沿变化会持续更长时间能通过滤波。3.3 启动与测试逻辑TIM_SetCounter(TIM2, 0); // 计数器清零 TIM_Cmd(TIM2, ENABLE); // 使能定时器开始计数 // 模拟产生100个方波脉冲 for(i_Loop 0; i_Loop 100; i_Loop ) { GPIO_SetBits(GPIOC, GPIO_Pin_6); Delay(10); // 假设Delay(10)大约延时10ms GPIO_ResetBits(GPIOC, GPIO_Pin_6); Delay(10); } // 读取计数结果 n_Counter TIM_GetCounter(TIM2);TIM_SetCounter(TIM2, 0)在开始前将计数器寄存器CNT清零确保我们从0开始计数。这是一个好习惯。TIM_Cmd(TIM2, ENABLE)这个函数调用后定时器才真正开始工作。对于外部时钟模式一旦使能定时器就“竖起耳朵”监听ETR引脚等待第一个有效边沿的到来。模拟方波循环这个循环通过拉高、拉低PC6并用Delay(10)间隔产生一个周期大约为20ms高10ms低10ms即频率约为50Hz的方波。循环100次共产生100个上升沿和100个下降沿。重要由于前面配置了TIM_ExtTRGPolarity_NonInverted上升沿有效因此只有上升沿会被计数。所以理论上最终n_Counter的值应该等于100。如果配置为TIM_ExtTRGPolarity_Inverted则会计数下降沿结果同样应该是100。如果信号有抖动且未启用滤波器计数值可能会大于100。TIM_GetCounter(TIM2)这是读取当前计数值的函数。它直接返回TIM2-CNT寄存器的值。读取操作本身不会影响计数器的持续运行。定时器仍在后台默默地根据ETR引脚信号进行计数。4. 进阶应用与实战经验分享掌握了基础配置我们来看看如何把这个功能用在更实际的场景以及有哪些容易踩的坑。4.1 应用场景拓展旋转编码器脉冲计数单相这是最直接的应用。将编码器的A相输出接到PA1TIM2_ETR配置为上升沿和下降沿均计数通常需要结合输入捕获模式或编码器接口模式纯ETR模式只能单边沿。本例的纯计数模式适合对方向不敏感只关心转动量的场合。产品产量计数在流水线上每个产品通过光电传感器产生一个脉冲。将这个脉冲接到ETR引脚就可以实现非接触式的高速计数CPU负载几乎为0。频率测量的基础结合定时器的内部时钟可以实现高精度频率测量。方法用另一个定时器如TIM3产生一个精确的闸门时间比如1秒在这个闸门时间内使能TIM2的外部计数。闸门时间到后关闭TIM2并读取计数值该值就是信号的频率Hz。这种方法测频范围宽精度取决于闸门时间的精度。作为其他定时器的触发源ETR信号不仅可以作为时钟还可以作为触发输入去启动、停止或同步其他定时器实现复杂的定时器联动逻辑。4.2 关键注意事项与避坑指南引脚复用功能必须使能这是最常见的问题。除了将PA1配置为输入模式GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING或GPIO_Mode_IPU/GPIO_Mode_IPD对于很多STM32系列尤其是F1还需要使能AFIO时钟并且可能需要重映射虽然TIM2_ETR通常不需要重映射。最稳妥的做法是参考官方库例程中对应定时器输入通道的GPIO配置。// 以STM32F1标准外设库为例通常需要这两步 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);信号电压与电平兼容STM32的GPIO引脚通常是3.3V电平。确保你的外部信号源也是3.3V电平。如果是5V TTL信号必须进行电平转换如使用电平转换芯片或电阻分压否则可能损坏芯片或无法正确识别。计数器溢出处理本例中ARR设置为最大值0xFFFF计满65535后会从0重新开始。如果你需要计更大的数有几种方案方案A查询溢出使能定时器更新中断UIE。在中断服务函数中对一个32位或64位的软件计数器如static volatile uint32_t s_overflow_count进行加1操作。最终计数值 s_overflow_count * 65536 TIM_GetCounter(TIM2)。方案B使用DMA将定时器的计数器寄存器CNT配置为DMA的源地址设定DMA在每次定时器更新事件溢出时将CNT的值传输到内存的一个数组中。这样可以实现无CPU干预的连续大数据块采集。滤波器的实战选择滤波器参数TIM_ETRClockMode2Config的最后一个参数不是摆设。如果你发现计数值莫名其妙多了很多大概率是信号有毛刺。你可以用示波器观察ETR引脚的实际波形。根据噪声的脉宽来估算滤波器参数。例如系统时钟f_CK_INT72MHz周期T≈13.9ns。噪声脉宽t_noise≈100ns。则噪声持续约100ns / 13.9ns ≈ 7个采样周期。为了滤除它你需要设置N大于7比如选择f_SAMPLINGf_CK_INT, N8对应参数值需要查表可能是TIM_ETRFilter_8FF之类的宏定义本例程中用的是数值需要查手册对应表。原则是在滤除噪声的前提下N值越小越好以减少对真实信号边沿的延迟。读取计数值的时机TIM_GetCounter()是瞬间读取CNT寄存器。在高速计数时如果读取瞬间刚好发生在计数器递增的过程中理论上存在读到中间状态的风险虽然概率极低。对于16位计数器一次读操作是原子的问题不大。但对于需要32位以上计数值的场景结合了溢出中断在读取软件高位计数和硬件低位计数时可能会因为中断发生而产生误差。这时需要采取临界区保护即在读取时暂时关闭定时器更新中断读完后立即恢复。4.3 调试技巧实录当你按照代码配置好却发现TIM_GetCounter读回来一直是0可以按以下步骤排查确认硬件连接万用表或示波器检查PA1引脚上是否有预期的脉冲信号电压幅度是否正确0-3.3V确认GPIO配置使用调试器在运行时查看GPIOA的配置寄存器CRL确认PA1是否被正确配置为输入模式。或者简单写段代码将PA1配置成普通输入然后用GPIO_ReadInputDataBit函数循环读取并打印其电平看是否能随外部信号变化。确认定时器是否使能查看TIM2的CR1寄存器控制寄存器1的CEN位是否为1。确认时钟模式查看TIM2的SMCR寄存器从模式控制寄存器的SMS位是否为111外部时钟模式2。同时检查ECE位外部时钟使能位是否为1。检查ETR配置查看TIM2的SMCR寄存器的ETP极性、ETPS预分频、ETF滤波器位是否与你的代码设置一致。简化测试先不用PC6模拟直接用一个导线手动触碰PA1引脚到3.3V产生上升沿和GND产生下降沿同时在调试器中观察TIM2-CNT的值是否变化。这是最直接的验证方法。利用定时器状态标志定时器的SR状态寄存器中有个TIF触发中断标志位。当ETR引脚检测到有效边沿时这个标志位会被置1即使你没开中断。在调试时可以循环读取这个标志位看它是否会随着你给PA1施加脉冲而置位这能帮你确认信号是否成功进入了定时器。通过以上层层剖析你应该不再仅仅是一个代码的搬运工而是真正理解了STM32定时器外部计数模式的机理和所有关键配置项的用途。下次当你需要统计电机转速、计量流量脉冲或者做任何事件计数时你就能自信地甩开低效的外部中断祭出这个硬件计数利器让你的系统运行得更稳健、更高效。记住嵌入式开发中能让硬件干的事绝不要麻烦CPU这是提升系统性能与可靠性的黄金法则。
STM32定时器外部时钟模式2实现高效硬件脉冲计数
发布时间:2026/6/7 14:52:08
1. 项目概述用STM32的TIMER当“外部事件计数器”在嵌入式开发里我们经常需要统计外部脉冲的数量比如旋转编码器的脉冲、光电传感器的触发次数或者简单点就是想知道一个引脚上来了多少个上升沿。很多朋友的第一反应可能是用外部中断EXTI来一个脉冲就进一次中断然后在中断服务函数里累加一个变量。这个方法对于低频信号比如每秒几个脉冲没问题但一旦频率上去比如几十KHz甚至更高频繁的中断就会成为CPU不可承受之重严重拖累系统性能甚至导致丢失脉冲。这时候STM32片内集成的通用定时器TIMER的“外部时钟模式”功能简直就是为我们量身定做的硬件外挂。它能把一个指定的GPIO引脚通常是定时器的ETR引脚直接作为定时器的计数时钟源。外部来一个有效边沿定时器的计数器就自动加1或减1完全不需要CPU干预。你可以把它想象成一个独立的、硬件实现的“电子计数器”CPU只需要在需要的时候去读取一下当前的计数值就行效率极高且绝对精准。今天要拆解的这个例程就完美演示了如何将STM32的TIM2配置成一个纯粹的外部事件计数器。它的核心思路是使用TIM2的ETR引脚PA1作为计数信号的输入口同时用另一个GPIOPC6模拟生成一个方波信号用于测试。我们将通过代码和原理把配置的每一个参数、每一步操作背后的“为什么”都讲清楚让你不仅能照搬代码更能理解其精髓举一反三应用到你的编码器测速、频率测量或者产品产量计数等实际项目中去。2. 核心思路与硬件连接解析2.1 为什么选择“外部时钟模式2”STM32的通用定时器有几种时钟源内部时钟CK_INT、外部触发输入ETR、内部触发输入ITRx用于定时器级联等。要实现外部计数我们需要让定时器的时钟来自外部引脚这就需要用到“外部时钟模式”。细看参考手册你会发现外部时钟模式又分两种外部时钟模式1External Clock Mode 1时钟信号来自定时器的某个输入通道TI1或TI2经过边沿检测器后作为触发信号TRGI驱动定时器。这种方式更常用于编码器接口或特定的触发同步。外部时钟模式2External Clock Mode 2时钟信号直接来自ETR引脚。这是最直接、最纯粹的外部计数方式。ETR引脚上的信号经过可配置的极性选择、预分频器和滤波器后直接成为定时器的计数时钟CK_PSC。本例程的目标是计数信号源明确来自一个GPIO引脚PA1因此外部时钟模式2是最自然、最合适的选择。它配置简单意图清晰ETR脚来一个脉冲上升沿或下降沿可配置计数器就动一下。2.2 引脚功能映射与硬件连接要点理解STM32的引脚复用功能是正确配置的前提。不是任何一个GPIO都能作为TIM2的ETR输入。信号输入引脚PA1对于STM32F1系列这也是本例程最可能基于的系列TIM2的ETR输入固定映射在PA1引脚上。这意味着你必须将PA1配置为复用功能输入或者浮空输入具体看外部驱动能力信号才能进入TIM2的内部电路。如果你错接到PA0或PA2代码怎么配都不会有计数。信号输出引脚PC6这里选择PC6来模拟方波是一个随意的选择因为本例程中它并不需要连接到任何定时器的特殊功能仅仅作为一个普通的GPIO输出。用GPIO_SetBits和GPIO_ResetBits配合延时函数来模拟一个频率粗略可控的方波。硬件连接测试时用一根杜邦线将PC6输出和PA1输入短接即可。这样PC6产生的方波就直接送到了TIM2的计数输入端。在实际项目中PA1连接的就是你的外部信号源如光电传感器输出、另一MCU的脉冲信号等。注意不同系列的STM32定时器引脚映射可能不同。例如在STM32F4系列上TIM2的ETR可能映射在PA0或PA5上。务必查阅你所使用芯片型号对应的《数据手册》Datasheet中的“Pinouts and pin description”章节以及《参考手册》Reference Manual中定时器章节的“TIMx alternate function mapping”部分。这是硬件工程师和嵌入式工程师最重要的习惯之一。3. 代码逐行深度解析与配置原理下面我们跳出代码注释从“为什么要这样设置”的角度把每一行关键配置嚼碎了讲。3.1 定时器时基单元初始化设定计数器的“舞台”TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 0x00; TIM_TimeBaseStructure.TIM_ClockDivision 0x0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure);这是定时器的基础框架配置即使使用外部时钟这个框架也决定了计数器如何运行。TIM_Period 0xFFFF这是自动重装载寄存器ARR的值设置为最大值65535。为什么是0xFFFF因为在这个纯计数应用中我们不希望计数器自动重载产生更新事件UIF或中断。我们只关心它从0开始能连续计多少个数。设置成最大值意味着计数器从0累加到65535后下一个脉冲会变成0溢出但如果我们在此前就读取了计数值这个溢出对我们没影响。如果你想计数超过65535就需要在计数器溢出时触发中断在中断里用一个软件变量做高位累加实现扩展计数。TIM_Prescaler 0x00预分频器设置为0即不分频。这里是个关键理解点这个预分频器是对定时器时钟源CK_PSC进行分频。现在我们的时钟源是外部ETR引脚信号。TIM_Prescaler0意味着ETR引脚每来一个有效边沿计数器CNT就加1。如果你设置TIM_Prescaler1则是每2个边沿CNT加1因为分频器是N1生效。所以在外部时钟模式下这个预分频器实际上变成了“输入脉冲分频器”。本例程不想分频所以设为0。TIM_ClockDivision 0x0时钟分频这个参数与外部时钟模式2的计数功能无关。它CDR是决定定时器内部时钟CK_INT与数字滤波器采样时钟f_DTS之间的比例主要影响的是输入捕获模块的滤波器和死区时间生成。对于纯ETR计数通常设为0即可。TIM_CounterMode TIM_CounterMode_Up计数模式为向上计数。从0开始加到ARR值0xFFFF。也可以配置为向下计数取决于你的应用逻辑。3.2 外部时钟模式2的精细配置信号如何被“识别”TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0);这行代码是激活外部时钟模式2的灵魂。我们拆开看它的四个参数TIM2指定定时器。TIM_ExtTRGPSC_OFFETR外部触发预分频器。这是独立于时基单元预分频器的、专属于ETR通路的又一个分频器。它有4种选择OFF不分频、DIV22分频、DIV44分频、DIV88分频。本例选择OFF意味着ETR引脚信号不经过这个分频器直接进入后续环节。如果需要降低计数频率可以优先考虑修改这里而不是修改时基单元的TIM_Prescaler因为逻辑上更清晰。TIM_ExtTRGPolarity_NonInvertedETR极性选择。这是关键NonInverted不反相ETR引脚上的上升沿或高电平取决于滤波器配置被识别为有效信号。这是最常用的设置。Inverted反相ETR引脚上的下降沿或低电平被识别为有效信号。你的外部信号是上升沿有效还是下降沿有效必须根据传感器或信号源的规格来正确设置这个参数否则计数值不会变化。0ETR数字滤波器参数。这个参数非常重要特别是在工业环境或有噪声的场景。它由一个4位值0-15表示用于配置一个采样频率和数字滤波窗口。0无滤波器。ETR信号直接以f_DTS频率被采样。f_DTS由前面的TIM_ClockDivision决定。当TIM_ClockDivision0时f_DTS f_CK_INT系统定时器时钟通常72MHz。这意味着在72MHz的频率下对ETR信号采样抗噪声能力最弱但响应最快。1~15启用滤波器。例如设置值为1代表f_SAMPLING f_CK_INT, N2。其含义是以f_CK_INT的频率对ETR引脚进行采样当连续采样到2次N相同的电平时才认为输入电平发生了改变。这能有效滤除毛刺脉冲。如何选择如果你的信号很干净用0。如果环境噪声大信号有毛刺就需要根据噪声脉宽和信号频率来计算并选择一个合适的滤波器值。例如你的信号是100kHz但可能有50ns的毛刺而f_CK_INT72MHz周期约13.9ns那么毛刺会持续约3-4个采样周期。设置N4或N6的滤波器就能将其滤除而真正的信号边沿变化会持续更长时间能通过滤波。3.3 启动与测试逻辑TIM_SetCounter(TIM2, 0); // 计数器清零 TIM_Cmd(TIM2, ENABLE); // 使能定时器开始计数 // 模拟产生100个方波脉冲 for(i_Loop 0; i_Loop 100; i_Loop ) { GPIO_SetBits(GPIOC, GPIO_Pin_6); Delay(10); // 假设Delay(10)大约延时10ms GPIO_ResetBits(GPIOC, GPIO_Pin_6); Delay(10); } // 读取计数结果 n_Counter TIM_GetCounter(TIM2);TIM_SetCounter(TIM2, 0)在开始前将计数器寄存器CNT清零确保我们从0开始计数。这是一个好习惯。TIM_Cmd(TIM2, ENABLE)这个函数调用后定时器才真正开始工作。对于外部时钟模式一旦使能定时器就“竖起耳朵”监听ETR引脚等待第一个有效边沿的到来。模拟方波循环这个循环通过拉高、拉低PC6并用Delay(10)间隔产生一个周期大约为20ms高10ms低10ms即频率约为50Hz的方波。循环100次共产生100个上升沿和100个下降沿。重要由于前面配置了TIM_ExtTRGPolarity_NonInverted上升沿有效因此只有上升沿会被计数。所以理论上最终n_Counter的值应该等于100。如果配置为TIM_ExtTRGPolarity_Inverted则会计数下降沿结果同样应该是100。如果信号有抖动且未启用滤波器计数值可能会大于100。TIM_GetCounter(TIM2)这是读取当前计数值的函数。它直接返回TIM2-CNT寄存器的值。读取操作本身不会影响计数器的持续运行。定时器仍在后台默默地根据ETR引脚信号进行计数。4. 进阶应用与实战经验分享掌握了基础配置我们来看看如何把这个功能用在更实际的场景以及有哪些容易踩的坑。4.1 应用场景拓展旋转编码器脉冲计数单相这是最直接的应用。将编码器的A相输出接到PA1TIM2_ETR配置为上升沿和下降沿均计数通常需要结合输入捕获模式或编码器接口模式纯ETR模式只能单边沿。本例的纯计数模式适合对方向不敏感只关心转动量的场合。产品产量计数在流水线上每个产品通过光电传感器产生一个脉冲。将这个脉冲接到ETR引脚就可以实现非接触式的高速计数CPU负载几乎为0。频率测量的基础结合定时器的内部时钟可以实现高精度频率测量。方法用另一个定时器如TIM3产生一个精确的闸门时间比如1秒在这个闸门时间内使能TIM2的外部计数。闸门时间到后关闭TIM2并读取计数值该值就是信号的频率Hz。这种方法测频范围宽精度取决于闸门时间的精度。作为其他定时器的触发源ETR信号不仅可以作为时钟还可以作为触发输入去启动、停止或同步其他定时器实现复杂的定时器联动逻辑。4.2 关键注意事项与避坑指南引脚复用功能必须使能这是最常见的问题。除了将PA1配置为输入模式GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING或GPIO_Mode_IPU/GPIO_Mode_IPD对于很多STM32系列尤其是F1还需要使能AFIO时钟并且可能需要重映射虽然TIM2_ETR通常不需要重映射。最稳妥的做法是参考官方库例程中对应定时器输入通道的GPIO配置。// 以STM32F1标准外设库为例通常需要这两步 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);信号电压与电平兼容STM32的GPIO引脚通常是3.3V电平。确保你的外部信号源也是3.3V电平。如果是5V TTL信号必须进行电平转换如使用电平转换芯片或电阻分压否则可能损坏芯片或无法正确识别。计数器溢出处理本例中ARR设置为最大值0xFFFF计满65535后会从0重新开始。如果你需要计更大的数有几种方案方案A查询溢出使能定时器更新中断UIE。在中断服务函数中对一个32位或64位的软件计数器如static volatile uint32_t s_overflow_count进行加1操作。最终计数值 s_overflow_count * 65536 TIM_GetCounter(TIM2)。方案B使用DMA将定时器的计数器寄存器CNT配置为DMA的源地址设定DMA在每次定时器更新事件溢出时将CNT的值传输到内存的一个数组中。这样可以实现无CPU干预的连续大数据块采集。滤波器的实战选择滤波器参数TIM_ETRClockMode2Config的最后一个参数不是摆设。如果你发现计数值莫名其妙多了很多大概率是信号有毛刺。你可以用示波器观察ETR引脚的实际波形。根据噪声的脉宽来估算滤波器参数。例如系统时钟f_CK_INT72MHz周期T≈13.9ns。噪声脉宽t_noise≈100ns。则噪声持续约100ns / 13.9ns ≈ 7个采样周期。为了滤除它你需要设置N大于7比如选择f_SAMPLINGf_CK_INT, N8对应参数值需要查表可能是TIM_ETRFilter_8FF之类的宏定义本例程中用的是数值需要查手册对应表。原则是在滤除噪声的前提下N值越小越好以减少对真实信号边沿的延迟。读取计数值的时机TIM_GetCounter()是瞬间读取CNT寄存器。在高速计数时如果读取瞬间刚好发生在计数器递增的过程中理论上存在读到中间状态的风险虽然概率极低。对于16位计数器一次读操作是原子的问题不大。但对于需要32位以上计数值的场景结合了溢出中断在读取软件高位计数和硬件低位计数时可能会因为中断发生而产生误差。这时需要采取临界区保护即在读取时暂时关闭定时器更新中断读完后立即恢复。4.3 调试技巧实录当你按照代码配置好却发现TIM_GetCounter读回来一直是0可以按以下步骤排查确认硬件连接万用表或示波器检查PA1引脚上是否有预期的脉冲信号电压幅度是否正确0-3.3V确认GPIO配置使用调试器在运行时查看GPIOA的配置寄存器CRL确认PA1是否被正确配置为输入模式。或者简单写段代码将PA1配置成普通输入然后用GPIO_ReadInputDataBit函数循环读取并打印其电平看是否能随外部信号变化。确认定时器是否使能查看TIM2的CR1寄存器控制寄存器1的CEN位是否为1。确认时钟模式查看TIM2的SMCR寄存器从模式控制寄存器的SMS位是否为111外部时钟模式2。同时检查ECE位外部时钟使能位是否为1。检查ETR配置查看TIM2的SMCR寄存器的ETP极性、ETPS预分频、ETF滤波器位是否与你的代码设置一致。简化测试先不用PC6模拟直接用一个导线手动触碰PA1引脚到3.3V产生上升沿和GND产生下降沿同时在调试器中观察TIM2-CNT的值是否变化。这是最直接的验证方法。利用定时器状态标志定时器的SR状态寄存器中有个TIF触发中断标志位。当ETR引脚检测到有效边沿时这个标志位会被置1即使你没开中断。在调试时可以循环读取这个标志位看它是否会随着你给PA1施加脉冲而置位这能帮你确认信号是否成功进入了定时器。通过以上层层剖析你应该不再仅仅是一个代码的搬运工而是真正理解了STM32定时器外部计数模式的机理和所有关键配置项的用途。下次当你需要统计电机转速、计量流量脉冲或者做任何事件计数时你就能自信地甩开低效的外部中断祭出这个硬件计数利器让你的系统运行得更稳健、更高效。记住嵌入式开发中能让硬件干的事绝不要麻烦CPU这是提升系统性能与可靠性的黄金法则。