news 2026/7/30 15:00:25

STM32 HAL库TIM定时器实战:从PWM到编码器,避坑指南与高级应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32 HAL库TIM定时器实战:从PWM到编码器,避坑指南与高级应用

1. 项目概述:为什么是HAL库的TIM定时器?

如果你正在用STM32做项目,尤其是基于CubeMX和HAL库,那么TIM定时器绝对是你绕不开的核心外设。它远不止是“定时”那么简单,更像是一个隐藏在芯片内部的“瑞士军刀”。从精确的延时、PWM波生成,到电机控制、编码器读数,再到高级的定时器级联和DMA传输,TIM的身影无处不在。

我刚开始接触HAL库时,面对HAL_TIM_Base_InitHAL_TIM_PWM_Start这一系列API,以及CubeMX里TIM那一大堆眼花缭乱的参数配置,确实有点懵。官方手册讲得很全,但不够“接地气”,很多实际项目中才会遇到的细节和坑,得自己踩过才知道。这篇笔记,就是把我这几年在多个STM32项目(从简单的LED呼吸灯到复杂的无刷电机FOC控制)中,使用HAL库操作TIM定时器积累的经验、踩过的坑和总结的技巧,系统地梳理出来。

它不是对官方手册的简单翻译,而是一个一线工程师的实战笔记。我会重点讲清楚“为什么要这么配置”,而不仅仅是“怎么配置”。无论你是刚入门的新手,还是想深化理解的熟手,希望这篇超过5000字的深度解析,能帮你把TIM定时器这把“瑞士军刀”用得更加得心应手。

2. TIM定时器的核心架构与工作模式解析

STM32的定时器种类繁多,从基本定时器(TIM6, TIM7)、通用定时器(TIM2-TIM5, TIM9-TIM14)到高级定时器(TIM1, TIM8),功能逐步增强。但无论哪种,其核心架构和工作原理是相通的。理解这个核心,是灵活运用的前提。

2.1 核心计数单元:从0到ARR的旅程

所有TIM定时器的核心都是一个16位或32位的计数器。你可以把它想象成一个水桶,水龙头(时钟源)以固定的速度向里滴水(计数递增或递减)。

  • 时钟源 (CK_PSC):这是水龙头的流速。它通常来源于系统主时钟(如72MHz),但会经过一个“分频器”进行减速。
  • 预分频器 (PSC):这个分频器就是水龙头上的阀门。你可以设置PSC寄存器的值(0-65535)。实际驱动计数器的时钟频率CK_CNT = CK_PSC / (PSC + 1)。例如,系统时钟72MHz,PSC设为71,则CK_CNT = 72MHz / (71+1) = 1MHz,即计数器每1微秒计数一次。这里第一个坑:PSC设置的是分频系数,实际分频值是PSC+1。
  • 计数器 (CNT):这就是水桶当前的水位。它根据计数方向(向上、向下、中央对齐)不断变化。
  • 自动重装载寄存器 (ARR):这是水桶的“满水位”刻度。当计数器达到这个值(向上计数)或减少到0(向下计数)时,就会发生“溢出”事件,计数器复位,并可以产生更新中断(Update Interrupt)或DMA请求。

定时时间的计算:这是最基础的公式。定时周期T = (ARR + 1) * (PSC + 1) / CK_PSC。假设我们要实现一个1ms的定时中断:CK_PSC=72MHz, 设PSC=71,则CK_CNT=1MHz,计数周期为1us。要得到1ms,需要计数1000次,所以ARR = 1000 - 1 = 999这里第二个坑:ARR设置的是重装载值,计数器从0计数到ARR,总共是ARR+1个时钟周期。

2.2 四大工作模式与HAL库对应API

TIM定时器主要有四种基础工作模式,HAL库为每种模式都提供了清晰的API接口。

  1. 定时器基础模式:最简单的定时功能,用于产生周期性的中断或事件。

    • HAL操作流程HAL_TIM_Base_Init()初始化 ->HAL_TIM_Base_Start_IT()启动并开启中断 -> 在HAL_TIM_PeriodElapsedCallback()回调函数中编写中断服务逻辑。
    • 注意HAL_TIM_Base_Start()HAL_TIM_Base_Start_IT()的区别在于是否使能更新中断。如果你只需要内部计时,不需要中断,用前者。
  2. 输出比较模式:这是生成PWM、驱动舵机、产生特定频率方波的核心。

    • 核心原理:定时器计数时,会不断与捕获/比较寄存器(CCR)的值进行比较。当两者匹配时,根据配置翻转输出引脚的电平。
    • PWM生成:在向上计数模式下,CNT<CCR时输出有效电平(高或低),CNT>CCR时输出相反电平。通过调节CCR的值,就能改变一个周期内有效电平的占比,即占空比。占空比 = CCR / (ARR + 1)。
    • HAL操作流程HAL_TIM_PWM_Init()->HAL_TIM_PWM_ConfigChannel()配置通道和极性 ->HAL_TIM_PWM_Start()启动输出。动态修改占空比使用__HAL_TIM_SET_COMPARE(&htimx, TIM_CHANNEL_x, new_ccr_value)
  3. 输入捕获模式:用于测量外部信号的脉宽或频率,比如测量超声波模块的回响时间、读取红外遥控的编码。

    • 核心原理:当输入引脚发生跳变(上升沿/下降沿)时,硬件会自动将当前CNT的值锁存到CCR寄存器中,并可以产生中断。通过记录两次跳变时的CCR值,就能计算出时间间隔。
    • HAL操作流程HAL_TIM_IC_Init()->HAL_TIM_IC_ConfigChannel()配置边沿和滤波 ->HAL_TIM_IC_Start_IT()启动。在HAL_TIM_IC_CaptureCallback()回调函数中,通过HAL_TIM_ReadCapturedValue()读取捕获值并进行计算。
    • 重要技巧:测量高电平脉宽时,通常先捕获上升沿,在中断中切换为捕获下降沿,再捕获一次,两次值之差即为脉宽。需注意计数器溢出的处理。
  4. 编码器模式:专门用于读取正交编码器信号,是电机位置、速度反馈的关键。

    • 核心原理:将TIM的两个输入通道配置为编码器接口模式,硬件会自动根据A、B两相的边沿关系,控制CNT向上或向下计数,直接反映位置信息。
    • HAL操作流程:在CubeMX中直接选择“Encoder Mode”,配置编码器类型和极性。代码中只需HAL_TIM_Encoder_Start()。通过__HAL_TIM_GET_COUNTER()读取位置,通过定时读取位置差计算速度。

3. CubeMX图形化配置的深层逻辑与避坑指南

CubeMX极大地简化了配置,但“傻瓜式”操作背后,理解每个选项的含义至关重要,否则极易配置出错。

3.1 时钟源与内部时钟选择

在CubeMX的TIM配置页面,“Clock Source”通常默认为“Internal Clock”。这意味着定时器使用APB总线时钟。这里有一个关键点:当APB预分频器不为1时,定时器时钟会倍频

例如,在STM32F1系列中,如果AHB时钟为72MHz,APB1预分频器设为2(APB1时钟为36MHz),那么挂载在APB1上的定时器(如TIM2-TIM4)的时钟会自动倍频到72MHz。CubeMX会帮你计算好并显示最终的“Timer Frequency”。你必须以这个显示的频率作为你计算的CK_PSC,而不是想当然的APB总线时钟。

3.2 参数配置页:PSC、ARR与计数模式

  • Prescaler (PSC):如前所述,输入分频系数。
  • Counter Period (ARR):自动重装载值。
  • Counter Mode
    • Up:向上计数,最常用。从0到ARR,溢出后归零。
    • Down:向下计数。从ARR到0,溢出后重载为ARR。
    • Center Aligned:中央对齐模式。先向上计数到ARR,再向下计数到0。此模式下,PWM频率会是向上/向下模式的一半,但对称性更好,常用于电机驱动,可以减少谐波。
  • auto-reload preload:这个选项非常重要!它控制ARR寄存器是否具有缓冲(影子寄存器)。
    • Disable:当你修改ARR值时,新值立即生效。如果修改时CNT值大于新ARR,会立即产生更新事件,可能导致PWM波形出现毛刺。
    • Enable:修改的ARR值会先写入预装载寄存器,等到下次更新事件(计数器溢出)时才真正生效到影子寄存器。这是保证波形稳定、无毛刺的关键,在需要动态调整频率(ARR)或占空比(CCR)时,务必使能。

3.3 输出比较通道配置:PWM生成细节

配置一个PWM输出通道时,除了选择通道和引脚,还有几个关键选项:

  • Mode:选择“PWM Generation CHx”。
  • Pulse:初始的CCR值,决定了初始占空比。
  • Fast Mode:使能后,当CNT与CCR匹配时,输出会立即被强制为有效电平(而非等到周期结束),可以产生更窄的脉冲,但一般不常用。
  • CH Polarity:极性选择。High表示默认(CNT<CCR时)输出高电平;Low则相反。这个设置需要和你驱动的外设(如LED低电平点亮)匹配。
  • CH Idle State:定时器停止(或未启动)时,输出引脚的状态。

避坑提示:如果你发现PWM输出波形反了(该高的时候低),首先检查的不是代码,而是CubeMX中这个“CH Polarity”配置。

3.4 输入捕获通道配置:滤波与分频

测量有噪声的信号时(如遥控器、机械开关),输入捕获的配置尤为关键。

  • IC Filter:输入滤波器。可以设置需要连续多少个相同采样点才认为电平有效,用于滤除毛刺。值越大,抗干扰能力越强,但会损失边沿分辨率。对于机械按键消抖,设置4-8个时钟滤波通常很有效。
  • IC Prescaler:输入分频器。可以设置为每N个边沿捕获一次。例如设为3,则每4个上升沿(从0开始计数)才触发一次捕获。用于测量频率远低于定时器计数频率的信号,可以降低CPU中断负荷。

4. HAL库API的实战应用与高级技巧

理解了配置,下一步就是在代码中灵活调用HAL库API。HAL库采用了“初始化-启动-回调”的层次化设计。

4.1 基础定时器中断:精准的软件延时与任务调度

除了简单的延时,基础定时器中断更重要的用途是提供系统“心跳”,实现简单的多任务调度(时间片轮转)。

// 在main.c中声明定时器句柄 TIM_HandleTypeDef htim2; // 在stm32f1xx_it.c中找到定时器中断服务函数 void TIM2_IRQHandler(void) { HAL_TIM_IRQHandler(&htim2); // HAL库中断分发器 } // 在main.c或单独的模块中重写回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { static uint32_t tick = 0; tick++; // 每10ms执行一次的任务 if (tick % 10 == 0) { Task_10ms(); } // 每100ms执行一次的任务 if (tick % 100 == 0) { Task_100ms(); } // 每1000ms执行一次的任务 if (tick % 1000 == 0) { Task_1000ms(); tick = 0; // 防止溢出 } } }

技巧:使用一个静态变量tick作为软时钟,通过取模运算来调度不同周期的任务,这是一种轻量级的实时性要求不高的调度方法。

4.2 PWM动态调光与呼吸灯实现

动态调整PWM占空比是常见需求,如LED呼吸灯、电机调速。

// 启动PWM HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // 在主循环或中断中动态修改占空比 void Update_PWM_DutyCycle(uint16_t duty) { // duty范围 0-ARR // 方法1:使用HAL宏(推荐,效率高) __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty); // 方法2:直接操作寄存器(效率最高) // TIM3->CCR1 = duty; // 方法3:使用HAL函数(会检查锁,稍慢但安全) // __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, duty); 内部也是调用这个 // TIM_CCxChannelCmd(htim3.Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE); } // 实现呼吸灯效果 void Breath_LED_Effect(void) { static uint16_t pwmVal = 0; static int8_t dir = 1; // 1递增,-1递减 pwmVal += dir * 5; // 步进值,控制呼吸速度 if(pwmVal > __HAL_TIM_GET_AUTORELOAD(&htim3)) { pwmVal = __HAL_TIM_GET_AUTORELOAD(&htim3); dir = -1; } else if(pwmVal == 0) { dir = 1; } __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pwmVal); HAL_Delay(10); // 用延时或定时器控制变化间隔 }

重要心得:频繁修改CCR时,如果开启了ARR预装载(推荐),那么CCR通常也有预装载功能(在CubeMX的通道配置中叫“Fast Mode”下方的选项,或代码中配置TIM_CCPreloadControl)。为了波形稳定,也建议使能CCR预装载,并在修改后调用__HAL_TIM_PRELOAD_CONFIG(&htimx, TIM_PRELOAD_ENABLE)或等待更新事件生效。

4.3 输入捕获测量脉宽:处理溢出是关键

测量一个未知的高电平脉冲宽度,是输入捕获的经典应用。难点在于如何处理计数器溢出。

// 全局变量,用于在中断和主程序间传递数据 volatile uint32_t capture_count = 0; volatile uint8_t capture_flag = 0; volatile uint32_t overflow_count = 0; // 定时器更新中断回调(溢出中断) void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIMx) { // 你的定时器实例 overflow_count++; // 溢出次数加1 } } // 输入捕获中断回调 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIMx) { static uint32_t first_value = 0; if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { // 通道1触发 if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET) { __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC1); uint32_t current_capture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); if(capture_flag == 0) { // 第一次捕获,上升沿 first_value = current_capture; // 切换为下降沿捕获 __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING); capture_flag = 1; overflow_count = 0; // 清空溢出计数 } else { // 第二次捕获,下降沿 uint32_t total_ticks; // 计算总的计数值 = 溢出周期数 * (ARR+1) + (当前值 - 第一次值) // 注意处理当前值小于第一次值的情况(说明发生了溢出) if(current_capture >= first_value) { total_ticks = overflow_count * (__HAL_TIM_GET_AUTORELOAD(htim) + 1) + (current_capture - first_value); } else { total_ticks = (overflow_count - 1) * (__HAL_TIM_GET_AUTORELOAD(htim) + 1) + (current_capture + (__HAL_TIM_GET_AUTORELOAD(htim)+1) - first_value); } // 转换为时间(微秒),假设CK_CNT = 1MHz capture_count = total_ticks; // 1 tick = 1us // 恢复为上升沿捕获,准备下一次测量 __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); capture_flag = 0; } } } } }

这段代码的逻辑是:首次上升沿捕获,记录CNT值并切换为下降沿捕获;下降沿到来时,再次记录CNT值,并结合期间发生的溢出次数,计算出总的脉冲宽度。这是输入捕获测量长脉宽的标准方法。

4.4 编码器模式与速度计算

编码器模式配置简单,但如何从位置信息中稳定、准确地计算速度,是工程上的重点。

// 启动编码器接口 HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL); // 定时(如10ms)读取和计算速度 int32_t get_encoder_speed(void) { static int32_t last_count = 0; int32_t current_count = (int32_t)__HAL_TIM_GET_COUNTER(&htim4); int32_t diff = current_count - last_count; // 处理计数器溢出/下溢(假设是32位计数器或处理了16位翻转) // 对于16位计数器,如果diff > 32767,则 diff -= 65536;如果 diff < -32768,则 diff += 65536。 last_count = current_count; // 速度 = 脉冲差 / 时间间隔 // 假设每转产生N个脉冲,时间间隔T=0.01s // 转速 RPM = (diff / N) / T * 60 return diff; // 返回脉冲差,在主函数中换算 }

速度计算滤波:直接计算瞬时速度(脉冲差)噪声会很大。常用的方法是M法测速(固定时间测脉冲数)或T法测速(固定脉冲数测时间)。在单片机中,M法更易实现。为了平滑,可以对连续多次的速度值进行滑动平均滤波。

#define SPEED_BUFFER_SIZE 5 int32_t speed_buffer[SPEED_BUFFER_SIZE] = {0}; uint8_t speed_index = 0; int32_t get_filtered_speed(void) { int32_t raw_speed = get_encoder_speed(); speed_buffer[speed_index] = raw_speed; speed_index = (speed_index + 1) % SPEED_BUFFER_SIZE; int32_t sum = 0; for(int i=0; i<SPEED_BUFFER_SIZE; i++) { sum += speed_buffer[i]; } return sum / SPEED_BUFFER_SIZE; }

5. 高级应用与性能优化实战

掌握了基础功能后,可以探索一些高级用法,这些用法能极大提升系统性能和灵活性。

5.1 定时器与DMA的联用:解放CPU

PWM输出数据流、ADC规则转换触发等场景,结合DMA可以完全解放CPU。

PWM DMA应用示例:驱动LED灯带(如WS2812)WS2812需要极高精度的时序(0码和1码的高电平时间分别为0.4us和0.8us左右)。用CPU模拟时序极其耗费资源且容易受中断干扰。用“PWM+DMA”是专业方案。

  1. 将每个LED的RGB数据(24bit)转换为对应的PWM占空比序列(0码=33%占空比,1码=66%占空比,复位码=0%占空比)。
  2. 将这个序列数组作为DMA的源数据。
  3. 配置TIM输出PWM,并使其更新事件触发DMA传输。
  4. DMA自动将数组中的数据逐个搬运到TIM的CCR寄存器,从而自动生成精确的波形序列。

ADC触发采样:配置TIM的更新事件作为ADC的外部触发源,可以以固定且精确的频率采样模拟信号,再配合DMA将采样结果搬运到内存数组,实现一个完整的、不占用CPU的数据采集链。

5.2 主从定时器级联:扩展定时范围与生成复杂波形

单个定时器的位数(16位)限制了最大定时周期。通过将一个定时器(Slave)的时钟源设置为另一个定时器(Master)的更新事件,可以实现级联,等效于一个32位或更长的定时器。

操作思路

  1. Master定时器:配置为普通定时器,产生一个较低频率的更新事件(UEV)。例如,ARR设为1000,产生1KHz的更新脉冲。
  2. Slave定时器:在CubeMX中,将其触发源(Trigger Source)选择为“ITR0”(或其他,取决于具体TIM,见参考手册“定时器内部触发连接”表格),这个ITRx就对应Master的更新事件。然后将Slave的从模式(Slave Mode)设置为“触发模式(Trigger Mode)”或“门控模式(Gated Mode)”。
  3. 效果:Slave定时器每收到一个Master的更新事件,其计数器才加1。这样,Slave的计数频率就是Master的更新频率。如果Master是1ms更新一次,Slave的ARR设为1000,那么Slave的更新周期就是1秒。这就用两个16位定时器实现了一个等效的1秒定时器。

5.3 互补输出与死区插入:电机驱动的核心

高级定时器(TIM1, TIM8)支持互补输出和死区插入,这是驱动三相全桥电路(如无刷电机、逆变器)的必备功能。

  • 互补输出:对于同一通道(如CH1),可以产生一对互补的PWM信号(CH1和CH1N),分别控制桥臂的上管和下管。它们通常是反相的,但不能同时为高,否则会短路。
  • 死区时间:为了防止上下管在开关切换的瞬间同时导通(直通),需要在互补信号之间插入一个两者都为低的短暂死区时间。高级定时器的硬件死区生成单元可以自动完成这个操作。

在CubeMX中配置互补PWM通道时,会多出“Dead Time”的配置项,单位可以是纳秒或时钟周期数,需要根据你使用的功率器件的开关特性来设置。

6. 调试技巧与常见问题排查实录

即使理解了原理,调试时还是会遇到各种问题。下面是一些常见问题的排查清单。

现象可能原因排查步骤与解决方法
定时器根本不工作,无中断无输出1. 时钟未使能。
2. 定时器未启动。
3. 中断未使能或优先级配置错误。
4. 引脚复用功能未正确映射。
1. 检查CubeMX的时钟树,确认APB总线时钟已开启且频率正确。
2. 检查代码是否调用了HAL_TIM_Base_Start_IT()或对应的启动函数。
3. 在CubeMX的NVIC设置中确认TIM中断已勾选,并检查优先级。在main函数初始化后,调用__HAL_TIM_CLEAR_FLAG(&htimx, TIM_FLAG_UPDATE)清除可能存在的悬挂中断标志。
4. 使用__HAL_AFIO_REMAP_TIMx_ENABLE()(F1系列)或检查Alternate Function是否正确。
PWM无输出或输出恒定电平1. 引脚配置错误(推挽输出?复用推挽?)。
2. PWM通道未使能。
3. CCR值设置不当(为0或大于ARR)。
4. 极性配置错误。
1. 确认GPIO模式设置为“Alternate Function Push-Pull”。
2. 确认调用了HAL_TIM_PWM_Start()
3. 检查CCR寄存器的值,用示波器或逻辑分析仪测量引脚。
4. 在CubeMX或代码中切换Polarity试试。
PWM频率或占空比不对1. PSC或ARR计算错误。
2. 时钟源频率认知错误(未考虑APB倍频)。
3. 计数器模式影响(中央对齐模式频率减半)。
1. 重新核对定时周期公式T = (ARR+1)*(PSC+1)/Fclk
2. 查看CubeMX生成的代码中htimx.Init.Prescalerhtimx.Init.Period的值,并与计算值对比。
3. 确认计数器模式,中央对齐模式频率是向上计数模式的一半。
输入捕获值不准或跳动大1. 信号有毛刺。
2. 未使用输入滤波器。
3. 中断处理时间过长,丢失边沿。
4. 未处理计数器溢出。
1. 用示波器观察输入信号质量,必要时在硬件上加RC滤波。
2. 在CubeMX中适当增加“IC Filter”值。
3. 优化中断服务函数,只做最必要的操作(如记录值、切换边沿),复杂计算放到主循环。
4. 务必使能更新中断并按照4.3节的代码处理溢出。
编码器计数方向反了或不计数1. 编码器A、B相序接反。
2. 编码器模式(TI1/TI2)选择错误。
3. 输入极性配置错误。
1. 交换编码器A、B相的接线。
2. 在CubeMX中尝试切换“Encoder Mode”为“Encoder Mode TI1”或“Encoder Mode TI2”。
3. 检查“Polarity”是否设置为“Rising Edge”(通常都是上升沿)。
动态修改ARR或CCR时波形出现毛刺未使能自动重装载预装载(ARR Preload)或比较预装载(CCR Preload)。在CubeMX中,将“auto-reload preload”和通道配置中的“Compare preload”都设为“Enable”。在代码中修改值后,新的值会在下次更新事件生效,保证波形完整。

调试必备工具

  1. 逻辑分析仪:几十块钱的国产货就足够。它是调试PWM、编码器、输入捕获时序的神器,可以直观看到波形、频率、占空比、相位关系,比万用表好用得多。
  2. STM32CubeMonitor:ST官方工具,可以在运行时实时读取和修改外设寄存器的值,对于调试动态配置非常有用。
  3. 串口打印:在中断回调函数中谨慎地使用printf输出关键变量(如捕获值、溢出次数),是分析逻辑错误的常用方法。注意中断中打印不宜过长,可能影响时序。

最后,再分享一个底层习惯:多查看stm32f1xx_hal_tim.c(或其他系列)源文件。HAL库的源码写得非常清晰,当你对某个API的行为有疑惑时,直接看源码是最快、最准确的理解方式。比如HAL_TIM_Base_Start_IT()里面到底做了哪些寄存器操作,一看便知。这能让你从“API调用者”逐渐变成“硬件掌控者”。

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

STM32串口中断与空闲中断高效接收不定长数据实战指南

1. 项目概述&#xff1a;从轮询到中断&#xff0c;串口数据接收的效率革命 在嵌入式开发&#xff0c;尤其是基于STM32这类MCU的项目里&#xff0c;串口通信几乎是“标配”。无论是打印调试信息、与上位机交互&#xff0c;还是连接GPS、蓝牙模块&#xff0c;串口都扮演着关键角色…

作者头像 李华
网站建设 2026/7/30 14:59:07

【EF Core】使用外部 Model

【EF Core】使用外部 Model 在实际的企业级开发中&#xff0c;我们经常会遇到需要将数据模型&#xff08;Model&#xff09;从主项目中分离出来的场景。例如&#xff0c;多个微服务共享同一个数据模型&#xff0c;或者我们希望将模型定义放在独立的类库中以便于复用和维护。本…

作者头像 李华
网站建设 2026/7/30 14:57:56

干货分享|食品企业采购环保纸包装,选厂核心参考指南

近几年绿色包装政策持续落地&#xff0c;加上消费者对食品安全、环保消费的重视程度不断提高&#xff0c;纸质食品包装已经成为烘焙、茶饮、休闲食品、礼品餐饮行业的主流选择。 不少食品行业经营者在采购时都会遇到各类难题&#xff1a;包装材质不达标存在安全隐患、供应商无法…

作者头像 李华
网站建设 2026/7/30 14:45:47

HarmonyOS掌上记账APP开发实践第94篇:如何实现删除多选框选项功能

如何删除多选框选项 场景说明 通常情况下&#xff0c;我们使用多选框都会伴随对选项的操作&#xff0c;比较常见的操作是选中后删除&#xff0c;比如删除购物车的商品、删除账单、删除图片等等。但是&#xff0c;当前HarmonyOS针对多选框组件并没有提供直接的删除其选项的方法…

作者头像 李华