news 2026/9/9 7:25:28

STM32输入捕获从原理到实战:测频率、占空比与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32输入捕获从原理到实战:测频率、占空比与避坑指南

1. 从按键到脉冲:为什么我们需要输入捕获?

如果你玩过正点原子的STM32开发板,大概率是从点亮一个LED或者读取一个按键状态开始的。这些操作直观、简单,能快速建立成就感。但当你开始接触电机控制、编码器测速、红外遥控解码,甚至是测量一个简单方波的频率和占空比时,你会发现,GPIO的简单“读高低电平”变得力不从心。这时候,STM32内部一个强大的外设——通用定时器(General-purpose timer)的输入捕获(Input Capture)功能,就该登场了。

简单来说,输入捕获就是让定时器这个“高精度秒表”去帮你“抓拍”某个引脚上电平跳变发生的精确时刻。比如,一个脉冲的上升沿来了,定时器立刻把当前计数值“咔嚓”一下存到专门的寄存器里,这个值就代表了从定时器启动到上升沿发生所经过的时间。如果再捕获一次下降沿,两个时间一减,脉冲的高电平宽度(占空比)就出来了;连续捕获两个上升沿,脉冲的周期(频率)也就知道了。

我最初学这个功能时,总觉得它比PWM输出要抽象。PWM是我命令定时器输出波形,一切尽在掌握;而输入捕获是定时器被动响应外部世界的“突发事件”,需要理解中断、影子寄存器、滤波等概念。但正是这种“被动响应”的能力,让STM32能从简单的控制器,升级为能与复杂外部信号“对话”的交互核心。正点原子的Mini板,以其简洁的外设和丰富的例程,是啃下这块硬骨头的绝佳平台。接下来,我就以Mini板常用的TIM2定时器为例,带你从原理到代码,彻底搞懂输入捕获,并分享几个调试中容易踩的坑。

2. TIM2输入捕获的硬件原理与核心寄存器拆解

正点原子STM32 Mini板通常基于STM32F103系列,其TIM2是一个高级控制定时器,功能全面。理解输入捕获,不能只停留在库函数调用,必须扒开寄存器看看它到底是怎么工作的。这能让你在调试异常时,心里有底。

2.1 信号通路:从引脚到捕获寄存器

想象一下信号进入STM32的旅程:

  1. 引脚:首先,你需要将一个GPIO引脚(例如PA0)配置为复用功能,并将其映射到TIM2的通道1(TIM2_CH1)。
  2. 边沿检测器:信号进入定时器后,首先经过边沿检测器。你可以通过寄存器配置它是检测上升沿、下降沿,还是双边沿。这是捕获的“触发器”。
  3. 输入滤波器:这是一个非常实用但常被忽略的环节。如果输入信号有毛刺,比如机械按键抖动,滤波器可以设置一个采样频率和数字滤波深度,只有当连续N个采样点都是有效电平时,才认为边沿有效。这对于抗干扰至关重要。
  4. 预分频器:捕获到的边沿信号,可以经过一个预分频器。比如设置为“每2个事件触发一次捕获”,这可以用来测量频率较低的信号,减少中断开销。
  5. 捕获单元:这是核心。当有效的边沿事件最终到来时,硬件会立即把当前定时器计数器(CNT)的值,复制到对应的捕获/比较寄存器(CCR1)中。注意,这里有一个“影子寄存器”的概念:你程序里读到的CCR1寄存器,可能并不是直接抓拍的那个值,而是它的影子寄存器。这种设计保证了在捕获瞬间,即使程序正在读取CCR1,也不会读到正在被硬件更新的、不完整的值,确保了数据的原子性。
  6. 中断/DMA:在值被捕获到CCR1的同时,如果使能了捕获中断,就会产生一个中断。你可以在中断服务函数里,读取CCR1的值,进行后续计算。对于高频信号,还可以使用DMA将CCR值直接搬运到内存数组,极大减轻CPU负担。

2.2 关键寄存器精讲

我们挑几个最核心的寄存器,用“人话”解释:

  • TIMx_CCMR1(捕获/比较模式寄存器1):这个寄存器负责通道1和2的模式设置。

    • CC1S[1:0]:这两位决定通道1是输入还是输出。对于输入捕获,必须设置为01(输入,映射到TI1)或10(输入,映射到TI2)。简单应用通常用01,即信号直接来自本通道的引脚。
    • IC1F[3:0]:输入捕获滤波器。设置越大,滤波效果越强,但延迟也略微增加。对于无抖动的方波可以设为0,对于有抖动的信号(如红外接收头输出)可以设为较高的值,如0xF(8个时钟滤波)。
    • IC1PSC[1:0]:输入捕获预分频器。00表示每个边沿都捕获,01表示每2个边沿捕获一次,以此类推。
  • TIMx_CCER(捕获/比较使能寄存器)

    • CC1E:通道1捕获使能位,必须置1。
    • CC1P:捕获极性选择。0表示捕获上升沿,1表示捕获下降沿。这里有个大坑:很多教程只讲到这里。实际上,在STM32F1中,还有一个CC1NP位(用于互补通道,通常为0)。但更关键的是,CC1P和CC1NP共同决定了边沿检测类型。标准库函数TIM_ICInit()内部会帮你正确设置这对组合。如果你直接操作寄存器,只设CC1P=1,可能无法捕获下降沿,必须同时设置CC1NP=0。这是新手极易出错的地方。
  • TIMx_DIER(DMA/中断使能寄存器)

    • CC1IE:通道1捕获中断使能。置1后,捕获发生时会产生中断。
  • TIMx_SR(状态寄存器)

    • CC1IF:通道1捕获中断标志。硬件在捕获发生时置1,软件读CCR1寄存器或向该位写0可以清除它。调试时一定要看这个标志位有没有被置起来,这是判断硬件是否正常触发的第一线索。
  • TIMx_CCR1(捕获/比较寄存器1):这就是最终存放“抓拍”时间戳的地方。在输入捕获模式下,它是只读的。

理解了这些,再看库函数配置,就不会觉得是一堆“黑魔法”了。库函数只是用更友好的方式,帮你把这些寄存器位组合起来。

3. 基于标准库的输入捕获测频实战代码逐行解析

理论说再多,不如一行代码。我们以测量PA0引脚(TIM2_CH1)上方波频率为例,使用STM32标准外设库。正点原子的例程通常采用此库,结构清晰。

3.1 初始化步骤与“为什么”

// 1. 使能时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // TIM2挂在APB1总线 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOA挂在APB2总线 // 2. 初始化GPIO GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入,防止悬空 // GPIO_Mode_IN_FLOATING; // 浮空输入,如果外部信号有确定驱动能力可用 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输入模式下此参数影响不大 GPIO_Init(GPIOA, &GPIO_InitStructure); // 注意:这里还没有配置复用功能!对于标准库的输入捕获,GPIO模式设置为输入即可, // 复用功能的重映射(如果有)需要通过GPIO_PinRemapConfig配置,但TIM2_CH1默认在PA0,通常无需重映射。 // 3. 初始化定时器基本时基 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // 自动重装载值ARR,16位定时器最大值 TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 预分频器PSC,72分频 // 为什么是72-1?因为系统时钟72MHz,72分频后得到1MHz的计数时钟,即每个计数1微秒。 // 这样,ARR=65535,最大可测量65.535ms的周期(对应约15Hz频率)。对于更高频率,需减小PSC。 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频,与输入滤波无关,通常DIV1 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 4. 初始化输入捕获参数 TIM_ICInitTypeDef TIM_ICInitStructure; TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; // 通道1 TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 捕获上升沿 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 直接映射到TI1输入 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 每个边沿都捕获 TIM_ICInitStructure.TIM_ICFilter = 0x0; // 不滤波,适用于干净信号 TIM_ICInit(TIM2, &TIM_ICInitStructure); // 这个函数内部会智能配置CCMR1、CCER等寄存器,包括正确设置边沿极性组合。 // 5. 使能中断 TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE); // 使能通道1捕获中断 // 配置NVIC(嵌套向量中断控制器) NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 6. 启动定时器 TIM_Cmd(TIM2, ENABLE);

3.2 中断服务函数与频率计算逻辑

中断函数是处理捕获数据的核心。我们的目标是测频率,思路是:捕获连续两个上升沿的时间戳,其差值就是一个周期的计数值。

// 定义全局变量 volatile uint32_t g_capture_count = 0; // 捕获值 volatile uint32_t g_last_capture = 0; // 上一次捕获值 volatile uint32_t g_period_ticks = 0; // 周期计数值 volatile uint8_t g_capture_flag = 0; // 捕获完成标志 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) // 检查是否是通道1中断 { // 读取本次捕获值 g_capture_count = TIM_GetCapture1(TIM2); // 计算周期(注意处理计数器溢出) if (g_last_capture != 0) { // 正常情况下,本次值应大于上次值(向上计数) if (g_capture_count > g_last_capture) { g_period_ticks = g_capture_count - g_last_capture; } else { // 如果本次值更小,说明定时器在两次捕获间发生了溢出(ARR从65535翻到0) // 周期 = (ARR - 上次值) + 本次值 + 1。因为ARR=0xFFFF,所以简化如下: g_period_ticks = (0xFFFF - g_last_capture) + g_capture_count + 1; } g_capture_flag = 1; // 设置标志,通知主循环周期已更新 } // 更新上一次捕获值,为下次计算做准备 g_last_capture = g_capture_count; // 清除中断标志位(非常重要!) TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); } }

在主循环中,你可以检查g_capture_flag,然后计算频率:

if (g_capture_flag) { g_capture_flag = 0; // 已知计数时钟为1MHz (1 tick = 1us),周期单位是微秒 float period_us = (float)g_period_ticks * 1.0f; // 1 tick/μs float frequency_hz = 1000000.0f / period_us; // 频率 = 1 / 周期 printf("Period: %.2f us, Frequency: %.2f Hz\r\n", period_us, frequency_hz); }

这里有几个关键点:

  1. 溢出处理:代码中else分支处理了计数器溢出。这是测量长周期信号时必须考虑的。如果定时器在两次捕获之间溢出了多次,上述简单处理就不够了,需要引入一个溢出计数变量。
  2. 标志位通信:中断函数应尽可能短平快,只做最必要的读写和计算,通过设置标志位让主循环处理显示、传输等耗时操作。
  3. 清除中断标志TIM_ClearITPendingBit必须调用,否则中断会连续触发,导致程序卡死。

4. 进阶应用:双边沿捕获与高精度占空比测量

只测频率还不够,很多场景(如PWM调速反馈、舵机信号解析)需要测量占空比。这就需要捕获一个周期内的上升沿和下降沿。

4.1 配置与思路

一种方法是使用同一个通道,在中断中动态切换捕获极性。但更高效、更可靠的方法是使用定时器的PWM输入模式,这其实是输入捕获的一个特殊应用。不过,我们先用标准双通道法来理解原理。

我们可以配置通道1捕获上升沿,通道2捕获下降沿,并且将两个通道都映射到同一个输入引脚TI1上。这样,硬件会自动将同一个信号路由给两个捕获单元。

// 初始化部分,在基本时基配置后 TIM_ICInitTypeDef TIM_ICInitStructure; // 通道1配置为上升沿捕获,直接映射到TI1 TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 直接TI1 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x0; TIM_ICInit(TIM2, &TIM_ICInitStructure); // 通道2配置为下降沿捕获,间接映射到TI1 TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI; // 间接TI1 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x0; TIM_ICInit(TIM2, &TIM_ICInitStructure); // 使能两个通道的中断 TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);

4.2 中断处理与计算

在中断函数中,我们需要区分是哪个通道触发的中断。

volatile uint32_t g_rise_capture = 0, g_fall_capture = 0; volatile uint32_t g_high_ticks = 0, g_period_ticks = 0; volatile uint8_t g_duty_flag = 0; void TIM2_IRQHandler(void) { // 处理通道1(上升沿)中断 if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) { g_rise_capture = TIM_GetCapture1(TIM2); // 如果已经有一个下降沿捕获值,则可以计算高电平时间 if (g_fall_capture != 0) { // 注意时序:一个完整的周期可能是 上升沿 -> 下降沿 -> 上升沿 // 这里计算的是上一个下降沿到本次上升沿的时间?不对。 // 更稳健的做法是记录连续两个上升沿的时间差作为周期, // 记录上升沿和紧随其后的下降沿时间差作为高电平时间。 // 这需要更复杂的状态机来管理,是常见的难点。 // 简化处理:假设信号稳定,我们总是用最新的上升沿和下降沿计算。 // 但这样需要确保下降沿发生在上升沿之后。 if (g_fall_capture > g_rise_capture) { g_high_ticks = g_fall_capture - g_rise_capture; } else { // 处理计数器溢出 g_high_ticks = (0xFFFF - g_rise_capture) + g_fall_capture + 1; } } TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); } // 处理通道2(下降沿)中断 if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET) { g_fall_capture = TIM_GetCapture2(TIM2); // 如果已经有一个上升沿捕获值,则可以计算高电平时间(同上) if (g_rise_capture != 0) { if (g_fall_capture > g_rise_capture) { g_high_ticks = g_fall_capture - g_rise_capture; } else { g_high_ticks = (0xFFFF - g_rise_capture) + g_fall_capture + 1; } g_duty_flag = 1; // 标志高电平时间已更新 } TIM_ClearITPendingBit(TIM2, TIM_IT_CC2); } // 周期计算仍需依赖两次上升沿,可以复用之前的逻辑,单独维护一个周期计算状态。 }

这里暴露了一个关键问题:简单的双通道捕获,在计算占空比和周期时,需要精心设计状态机来配对正确的上升沿和下降沿,否则在信号占空比变化或中断响应延迟时容易出错。这也是为什么对于精确的PWM测量,推荐使用下面要讲的“PWM输入模式”,它硬件自动配对,更为可靠。

5. 调试避坑指南:从现象到根源的完整排查链路

输入捕获功能不出数据,或者数据跳变严重,是新手常遇到的问题。别慌,按照以下链路排查,能解决90%的问题。

5.1 现象:完全捕获不到任何中断

  • 第一步:检查硬件连接

    • 用万用表或示波器确认信号是否真的到达了MCU引脚。信号电压是否在GPIO可识别的范围(对于3.3V系统,高电平>2.0V,低电平<0.8V)?
    • 检查GPIO配置模式。输入捕获必须配置为输入模式GPIO_Mode_IN_FLOATINGGPIO_Mode_IPU/IPD),而不是复用推挽输出。这是我见过最多的配置错误。
  • 第二步:检查定时器时钟与使能

    • 确认RCC_APB1PeriphClockCmd(TIM2, ENABLE)已调用。用调试器查看RCC->APB1ENR寄存器对应位是否为1。
    • 确认TIM_Cmd(TIM2, ENABLE)已调用。查看TIM2->CR1寄存器的CEN位是否为1。
  • 第三步:检查中断配置

    • 确认TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE)已调用。
    • 确认NVIC配置正确,且全局中断已开启(在启动文件startup_stm32f10x_xx.s中默认开启)。
    • 在中断服务函数入口设置断点。如果断点永远打不上,说明中断没触发。进入中断函数后,首先检查TIM_GetITStatus读取的状态标志是否真的为1。有时是清除标志位的代码位置不对,导致中断持续触发,程序跑飞。
  • 第四步:检查捕获通道配置

    • 确认TIM_ICInit函数被正确调用,参数无误。特别是TIM_ChannelTIM_ICSelection
    • 使用寄存器视图直接观察。在调试模式下,查看TIM2->CCER寄存器,CC1E位应为1(捕获使能),CC1P/CC1NP位应正确设置。查看TIM2->CCMR1CC1S位应为01或10(输入模式)。

5.2 现象:捕获值不变或变化无规律

  • 第一步:检查信号质量

    • 用示波器看波形。是否有严重毛刺?边沿是否陡峭?如果边沿缓慢(如RC充放电),可能无法触发边沿检测。考虑使用施密特触发器整形或调整输入滤波器(IC1F)。
  • 第二步:检查计数器是否在跑

    • 在调试模式下,实时观察TIM2->CNT寄存器的值。它应该随着时间递增。如果不增,检查定时器预分频器PSC是否设置过大(比如65535),导致计数极慢;或者ARR是否设为0。
  • 第三步:检查中断标志清除

    • 确保在中断函数末尾清除了对应的中断标志(TIM_ClearITPendingBit)。如果不清除,中断会连续发生,你可能来不及处理第一次捕获的数据,就被第二次中断覆盖,看起来像是数据没变。
  • 第四步:处理计数器溢出

    • 如果你的信号周期较长,超过(ARR+1) * 计数周期,那么计数器会在一个信号周期内多次溢出。你需要一个溢出计数变量(TIM_Update中断),在计算周期时将其考虑进去。例如:总周期 = 溢出次数 * (ARR+1) + 本次捕获值 - 上次捕获值

5.3 现象:测量值精度差、跳动大

  • 根源分析:量化误差与中断延迟

    • 量化误差:这是由定时器分辨率决定的。如果你的计数时钟是1MHz(1us),那么测量的最小时间单位就是1us。对于10kHz的信号(周期100us),理论上有±1us的误差,即±1%的误差。提高基准时钟频率(减小PSC)可以降低量化误差,但会缩短最大可测周期。
    • 中断延迟:从捕获事件发生到CPU进入中断服务函数读取CCR值,存在延迟。虽然这个延迟是微秒级的,且对于周期测量(两次捕获在中断中相减)可以抵消大部分,但在高频率或需要绝对时间戳的场合仍有影响。对于超高精度要求,需使用DMA方式。
    • 信号抖动:信号源本身就不稳定。使用示波器的统计功能测量频率,看其标准差,与MCU测量结果对比。
  • 软件滤波:对于跳动的数值,可以在主循环中进行软件滤波,如滑动平均滤波、中值滤波等。

    #define FILTER_LEN 10 uint32_t freq_buffer[FILTER_LEN] = {0}; uint8_t buf_index = 0; uint32_t filtered_freq = 0; // 在计算得到frequency_hz后 freq_buffer[buf_index] = (uint32_t)frequency_hz; buf_index = (buf_index + 1) % FILTER_LEN; // 计算平均值 uint32_t sum = 0; for(int i=0; i<FILTER_LEN; i++) sum += freq_buffer[i]; filtered_freq = sum / FILTER_LEN;

6. 从标准库到HAL库与CubeMX的配置迁移

现在很多新项目使用STM32CubeMX和HAL库。虽然底层逻辑不变,但配置方式差异很大。了解如何迁移,能让你更灵活。

6.1 使用CubeMX图形化配置

  1. Pinout & Configuration:在Pinout视图,找到PA0,将其功能设置为TIM2_CH1
  2. Configuration -> TIM2
    • Clock Source:选择Internal Clock
    • Parameter Settings
      • Prescaler (PSC - 16 bits value):设为71(因为HAL库的预分频值是实际分频数-1,与标准库一致)。
      • Counter ModeUp
      • Counter Period (AutoReload Register):65535。
      • Internal Clock Division (CKD)No Division
      • auto-reload preloadDisable
    • 下方切换到“Input Capture Channel 1”
      • Polarity SelectionRising Edge
      • IC SelectionDirect
      • IC PrescalerNo Prescaler
      • IC Filter0
  3. NVIC Settings:勾选TIM2 global interrupt并设置优先级。
  4. 生成代码

6.2 HAL库代码要点

CubeMX生成的代码会初始化好外设。你需要补充中断回调逻辑。

// 在main.c的合适位置(如用户代码区2)定义变量 uint32_t last_capture = 0, period_ticks = 0; uint8_t capture_flag = 0; // 启动输入捕获 HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); // 重写输入捕获回调函数 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { uint32_t current_capture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); if (last_capture != 0) { if (current_capture > last_capture) { period_ticks = current_capture - last_capture; } else { period_ticks = (0xFFFF - last_capture) + current_capture + 1; } capture_flag = 1; } last_capture = current_capture; } } // 主循环中检查capture_flag并计算频率

HAL库与标准库的一个显著区别:HAL库在中断中自动清除了标志位,你不需要手动调用__HAL_TIM_CLEAR_IT。这使得代码更简洁,但也意味着如果你在回调函数里做了太耗时的操作,可能会影响下一次中断的响应。

7. 项目实战扩展:将输入捕获应用到真实场景

掌握了基础,我们可以看看它能做什么。结合网络热词里的几个方向:

  • 编码器测速:正交编码器输出两路相位差90度的脉冲。可以使用定时器的编码器接口模式,这本质上是输入捕获的高级应用,硬件自动根据两路信号边沿计数和判断方向,直接读取CNT值就能得到位置和速度。这比软件捕获两路信号高效准确得多。
  • 红外遥控解码:NEC等红外协议用脉冲宽度表示0和1。可以用输入捕获(双边沿)测量每个高低电平的宽度,再根据协议规则解码出地址和命令。这里输入滤波器(ICFilter)非常关键,能有效抑制红外接收头的噪声。
  • PWM输入反馈:在闭环控制中,需要读取反馈的PWM占空比。使用PWM输入模式是最佳选择。该模式将一路信号同时映射到两个捕获通道(如TI1映射到IC1和IC2),并自动将其中一个通道设置为反极性。只需读取两个CCR值,一个就是周期,另一个就是高电平时间,硬件自动配对,无需复杂状态机。
  • 频率计/占空比仪:结合LCD或串口,将上述测量结果实时显示出来,就是一个简易的仪器。注意处理量程(通过调整PSC)和显示刷新率。

输入捕获是STM32定时器家族里一颗璀璨的明珠,它连接了MCU的精准计时能力和外部异步事件。从理解寄存器位开始,到写出稳健的中断服务函数,再到处理各种边界条件和噪声,这个过程是对嵌入式开发者硬件抽象能力和细致调试能力的绝佳锻炼。在Mini板上跑通它,你收获的不仅仅是一个功能,更是一套应对外部信号交互的方法论。下次当你遇到需要测量时间间隔、解析数字脉冲的任务时,你会自信地知道,TIMx的输入捕获功能正在那里待命。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 2:29:10

HDFS集群的高可用集群一遍过!!!

目录 一.HDFS的高可用集群 1.1简单介绍 1.2部署zookeeper集群 1.2.1简单介绍 1.2.2准备工作 1.2.3下载并解压软件包 1.2.4编辑配置文件 1.2.5启动zookeeper集群节点 1.3启动HDFS高可用集群 1.3.1编辑.xml文件 1.3.2初始化集群 1.3.3启动hdfs集群 1.4Yarn高可用集群…

作者头像 李华
网站建设 2026/9/9 7:24:27

智能体应急响应:数字孪生与多尺度规划,打造生产级AI运维

凌晨 2 点&#xff0c;告警群开始刷屏。支付核心链路的错误率从 0.1% 拉到 8%&#xff0c;值班工程师打开三个监控面板&#xff0c;快速判断"哪里挂了"&#xff0c;然后在几十条告警里找出真正的根因。这个过程中&#xff0c;任何一步判断失误&#xff0c;都会让故障…

作者头像 李华
网站建设 2026/8/30 11:42:20

地址驱动的日食可视化Web应用:原理、部署与测试

这次我们来看一个很小但很实用的 Web 项目&#xff1a;“Show HN: How will the Aug 12 eclipse look like from your address?”。它的目标非常明确&#xff1a;用户输入一个地址&#xff0c;页面直接算出 8 月 12 日这一天从这个地点看日食&#xff0c;到底是全食、偏食还是…

作者头像 李华
网站建设 2026/8/30 15:50:11

微分方程建模实战指南:从SIR模型到MATLAB求解,数学建模竞赛必备

1. 项目概述&#xff1a;从笔记到实战&#xff0c;微分方程建模的核心价值如果你正在准备数学建模竞赛&#xff0c;或者你的课程、科研项目里涉及到用数学模型描述现实世界的变化规律&#xff0c;那么“微分方程”这四个字绝对是你绕不开的核心。我当年第一次接触数学建模&…

作者头像 李华
网站建设 2026/8/30 18:58:42

AI资本支出与债务破纪录增长,开发者如何应对算力周期

过去一年里&#xff0c;几乎所有做 AI 相关工作的团队都遇到过同一个问题&#xff1a;算力预算怎么要都批不下来&#xff0c;但大模型 API 的价格却时不时涨一下&#xff1b;调一个 7B 模型要排队等 GPU&#xff0c;买一块卡又怕明年就被淘汰。如果你也处于这种状态&#xff0c…

作者头像 李华
网站建设 2026/8/31 4:09:20

基于YOLOv11的视频人脸检测工具:从原理到实战部署

简介&#xff1a;目标检测是计算机视觉的核心任务之一&#xff0c;旨在识别图像或视频中的特定物体并定位其位置。其原理通常基于深度学习模型&#xff0c;通过卷积神经网络提取特征&#xff0c;并预测边界框和类别。在视频分析领域&#xff0c;目标检测的技术价值尤为突出&…

作者头像 李华