做嵌入式开发的朋友应该都有过这种体验想实现一个精确延时比如让LED每500ms翻转一次或者定时扫描按键、采集传感器数据第一反应就是“开个定时器中断”。早先用标准外设库的时候配置定时器可不是件省心的事寄存器一个接一个地填计算预分频还要翻手册、查公式稍不留神就把自动重载值算错一上电整个时序全乱。后来我切换到STM32CubeMX这套图形化配置工具定时器初始化这种活基本变成了“填参数、打勾、生成代码”三步操作写一个TIM1的1ms定时中断再配合一个LED闪烁Demo从新建工程到下载跑通实测下来5分钟是够用的——前提是你知道每一步为什么这么做而不是光顾着点鼠标。这篇文章我就拿一块最常见的STM32F103C8T6最小系统板来说带你把TIM1定时器1ms中断从CubeMX配置到代码实现完整过一遍。内容包括时钟树怎么设置、预分频系数和计数周期的计算逻辑、NVIC中断怎么使能、中断回调函数怎么写以及我把新手常见的问题整理成了一份排查表。不管你是刚接触STM32还是已经从标准库转HAL库这篇实战笔记都能帮你少踩几个坑。1. 项目概述与整体设计思路1.1 为什么定时器中断是嵌入式开发的基本功做单片机开发延时和定时是躲不开的需求。最简单的方案是用HAL_Delay()但这个函数是阻塞式的程序在延时期间什么都干不了一旦你的系统里同时要处理按键、显示、通信阻塞延时会让整个程序变得卡顿不堪。更麻烦的是HAL_Delay()的实现依赖SysTick中断如果系统里其他中断被长时间占用或者你在中断服务函数里调用了它整个延时可能会直接失效。定时器中断的方案则完全不同。定时器在后台独立计数计到设定值时触发中断CPU只需要在中断里处理很短的任务然后马上回到主循环继续干活。这种“时间片驱动”的思想是做更大规模嵌入式应用的基础。比如RTOS的时基、多路任务的周期调度、PWM频率的底层支撑全部依赖定时器。可以说把定时器中断玩明白后面学什么都顺。1.2 例程为什么选TIM1而不是TIM2/TIM3或SysTick很多初学者看到“TIM1”三个字就先慌了觉得高级定时器肯定特别复杂。实际恰恰相反如果只是用它的基本定时功能TIM1和普通的TIM2、TIM3几乎没有区别都是配置预分频、计数周期、使能中断这三件事。我特意选TIM1来写这个Demo就是想把“高级定时器”这层窗户纸捅破——大家嘴里的“高级”主要体现在它可以输出带死区控制的互补PWM、支持刹车输入、有重复计数寄存器这些功能在你不需要的时候完全不影响使用。从时钟树的角度看TIM1在STM32F103中挂在APB2总线上APB2的最高频率和系统主频一样是72MHz这意味着TIM1有机会获得更高的计数时钟对高频计时更加友好。相比之下TIM2、TIM3、TIM4挂在APB1上默认最高36MHz需要开启倍频器才能到72MHz。虽然对于1ms这种毫秒级中断来说两者的精度差完全可以忽略但从熟悉芯片资源的角度讲先把TIM1用明白不是坏事。至于SysTick它虽然也是一个定时器但绝大多数情况下都被HAL库拿去做HAL_GetTick()和HAL_Delay()的时基了咱们不要动它免得“拆东墙补西墙”影响整个HAL库的运行节奏。1.3 用CubeMX做这套方案的优势我自己早先用标准库写定时器初始化代码至少长这样先开RCC时钟再写时基初始化结构体设置预分频、计数模式、自动重载值然后配置NVIC优先级最后还要记得写一个中断服务函数并在里面清标志位。这套流程如果只做一次还好问题是一旦换了芯片型号、换了定时器外设这些代码从头到尾又要重新对照手册核对一遍。STM32CubeMX的核心价值是把硬件资源的管理变成了可视化操作。你会直接看到一张芯片引脚图点一下引脚就能配置功能时钟树有图形界面配置完系统时钟它会自动帮你算好PLL参数。最关键是初始化代码是自动生成的结构上非常规范哪怕你以后换用其他型号的STM32操作逻辑还是同一套。这也解释了为什么现在很多开发岗在招聘需求里直接写了“熟悉STM32CubeMX”。还有一个隐藏优势CubeMX生成的代码对HAL库版本有严格的对应关系以后升级库或者换芯片重新生成一遍工程就能适配不用在移植上花太多时间。对于做项目原型验证这种方式效率提升非常明显。2. 环境准备与工程创建细节2.1 开发工具链清单与版本选择做这个Demo需要准备四个东西STM32CubeMX图形化配置工具、STM32F1系列的器件支持包、MDK-Keil或STM32CubeIDE编译器、一个调试下载器。这里我不推荐在下载安装上浪费太多时间网上关于STM32CubeMX安装包和安装步骤的资料非常多实在卡住了就去搜一下照着操作基本都能搞定。一个比较关键的经验是CubeMX的版本尽量选择6.x以上的新版界面布局更友好生成的代码质量也更好。安装完成后第一次打开会自动提醒你安装固件支持包如果网络条件一般直接在CubeMX里进入“Help - Manage embedded software packages”找到STM32F1系列点击安装即可。版本差距带来的体验差异很大太老的版本在部分新芯片支持上会缺胳膊少腿。编译器方面如果你的电脑内存够大直接用STM32CubeIDE也可以CubeMX和IDE可以无缝联动省去来回倒工程的麻烦。如果习惯用MDK记得安装对应的芯片器件包否则打开项目后可能看不到芯片型号。调试器用ST-Link V2即可便宜稳定入门首选。2.2 新建工程的三步走打开CubeMX后选择“New Project”进入芯片选型界面。这里可以直接搜索“STM32F103C8T6”点选中后点右上角的“Start Project”。不要选成“STM32F103C8T6Rx”这类看清楚封装和Flash容量C8T6是LQFP48封装64KB Flash做这个小Demo绰绰有余。进入主界面后第一步先处理系统时钟源。在左侧的“System Core”菜单里找到RCCReset and Clock Control把“High Speed Clock (HSE)”设置为“Crystal/Ceramic Resonator”。这里设置HSE的原因是板载8MHz晶振可以作为系统时钟的源头之后通过PLL锁相环倍频到72MHz。如果你用的是某些内部RC振荡器供电的板子也可以选内部时钟但精度会略差串口通信这类对时序敏感的场景建议用外部晶振。第二步是设置调试接口。在SYS菜单里把“Debug”设置为“Serial Wire”。这一步特别重要如果Debug不选生成的代码默认不开启SWD引脚功能第一次烧录没问题第二次下载时会发现找不到芯片因为你的调试口被程序当普通GPIO用掉了。做完这一步再生成代码调试器才能一直正常工作。第三步就是配置时钟树这个放到下一节单独展开因为它和定时器频率计算直接相关有必要看得更清楚。2.3 时钟树配置与72MHz主频来源在Clock Configuration选项卡里你会看到一张从晶振到外设的时钟分配图。默认状态下HCLK可能还是8MHz这时候需要手动输入72回车之后CubeMX会自动帮你把PLL的倍频系数算好。正常情况下外部8MHz晶振经过PLL的9倍频得到72MHz系统时钟APB1总线预分频设为2得到36MHzAPB2总线预分频设为1保持72MHz。这里有一个细节必须留意定时器时钟和总线时钟的关系。在STM32F103上如果APB1预分频系数大于1那么挂载在APB1上的定时器TIM2、TIM3、TIM4等的时钟会自动变成APB1的两倍。同理APB2预分频大于1时TIM1、TIM8的时钟也会翻倍。CubeMX的时钟树图里会用一个带“x2”的字样标出定时器时钟你要学会看这张图因为它直接决定了你算出来的定时时间准不准。很多初学者在配完时钟树之后根本不看定时器那一栏写的频率是多少拿着36MHz去算72MHz的参数结果定时时间整整慢了一倍。这种问题并不是代码写错而是方案的前提就错了。所以在往下走之前建议按图确认一下TIM1的时钟输入应该是72MHz这样后面算1ms的参数才不会有偏差。3. TIM1定时器配置与计时代数计算3.1 定时器到底怎么“数数”先来看定时器工作的基本原理。定时器的核心部件是一个16位的计数器它在时钟脉冲的驱动下不断往上加从0一直数到65535数到上限时溢出归零这个过程叫“计数溢出”或者“更新事件”。如果我们希望在特定的时间点触发中断就需要在它的计数通道上加两个“关卡”一个是预分频器PSC它先把定时器时钟放慢相当于给计数器换一颗频率更低的“心跳”另一个是自动重载寄存器ARR它规定计数器数到哪个值就产生一次溢出。打个比方定时器时钟是秒针的滴答声预分频器是齿轮组负责把滴答声变成慢节奏的“秒脉冲”自动重载寄存器就像闹钟正点到了就响铃。我们配定时器中断本质上是设计这两级分频关系让“响铃”的周期正好是想要的1ms。在STM32F103中计数器、预分频器和自动重载寄存器的位宽都是16位也就是说PSC和ARR的取值范围都是0到65535。注意这里有个经典的坑寄存器里存0的时候实际分频系数是1存1的时候实际分频系数是2计算公式永远要“加1”再带进去算。3.2 预分频和计数周期的推导计算现在我们实际计算一遍1ms中断的参数。前面已经确认TIM1的输入时钟是72MHz也就是每秒钟有72,000,000个时钟脉冲。假设预分频器系数为PSC1计数器从0计数到ARR时产生更新事件那么更新事件的频率就是定时器更新频率 定时器输入时钟 / (PSC1) / (ARR1)而我们想要的更新周期是1ms对应频率1000Hz所以需要满足72,000,000 / (PSC1) / (ARR1) 1000最简单的取法是让PSC1等于72ARR1等于1000也就是PSC71、ARR999。验算一下72MHz除以72等于1MHz说明计数器每1微秒加1计数器从0数到999需要1000微秒也就是1ms触发一次更新中断。这个参数组合用得非常普遍强烈建议理解后记住。如果你需要改成其他周期方法也是完全一样的。比如要10ms中断一次可以保持PSC71不变把ARR改成9999或者保持ARR999把PSC改成719。核心就是保证两级分频的乘积等于72,000,000除以目标频率。算完之后强烈建议先心算一遍再填入CubeMX不要过度信任工具因为工具只会按你填的参数生成代码不会帮你判断参数合不合理。3.3 CubeMX中TIM1参数的填写方法回到CubeMX主界面在左侧栏“Timers”分类下找到TIM1点击引脚编号“TIM1_CH1”那个标记反而用不到——这里需要注意做定时器中断我们不需要输出比较通道只使用时基单元所以不要在“TIM1 Channel1”的下拉列表里选“PWM Generation”之类的选项保持通道为Disabled即可。然后在Configuration页面下方会多出TIM1的设置面板真正要改的是这么几个参数Prescaler预分频器填71Counter Mode计数模式选择“Up”向上计数Counter Period自动重载值填999Internal Clock Division内部时钟分频不用动保持No Divisionauto-reload preload自动重载预装载建议改为Enable。关于最后一项简单解释一下“预装载”的意义。如果设为Disable溢出后自动重载值是直接生效的如果在程序运行中随时修改ARR可能出现一个周期长一个周期短的现象。设为Enable之后遇到新的重载值不马上生效而是等当前周期结束下一次溢出时才更新这种机制能保证切换定时周期时波形是连续的。在这里虽然只跑一个固定周期看不出明显差别但养成开启预装载的习惯对你以后做PWM调速或动态频率切换会很有帮助。还有一个高级定时器特有的参数“Repetition Counter”也就是重复计数寄存器RCR。默认是0表示每次计数溢出都触发一次更新事件。如果设置成比如99那就需要计数器溢出100次才触发一次更新事件相当于又套了一层分频。这个用法在需要长定时的场合很省CPU资源但初学者先不用动它保持默认即可。3.4 开启定时器更新中断并设置NVIC参数填好之后还要把“TIM1 global interrupt”这个选项打开。在TIM1配置页面顶部有一个“NVIC Settings”选项卡点进去你会看到“TIM1 update interrupt”和“TIM1 capture compare interrupt”两个可勾选项。我们目前只使用定时器的基本时基功能所以只需要勾选“TIM1 update interrupt”。NVIC优先级可以保持默认。这里顺便说一句HAL库在HAL_Init()阶段默认使用4位抢占优先级也就是抢占优先级从0到15可选你没有特殊需求的话在CubeMX里给一个不高不低的优先级就可以了比如抢占优先级2、子优先级0。真实项目中如果同时使用串口、外部中断、DMA就要仔细规划各个中断的优先级原则是“实时性要求高的抢占等级更低数字越小优先级越高”但这个Demo不用纠结。勾选完成后先别急着生成代码。再花十秒钟检查一遍工程配置在“Project Manager”里把Toolchain选为你预期使用的IDE比如MDK-ARM V5Version选择对应版本在“Code Generator”里建议同时勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”意思是每个外设单独生成一个c和h文件。这样后续要找代码不用在单一的main.c里翻来翻去工程结构更清爽。最后点击右上角的“GENERATE CODE”等待代码生成完成再用自己的IDE打开工程。这样定时器初始化的底层代码已经全部就位接下来只需要在我们自己的代码里“加戏”了。4. LED闪烁Demo的代码实现4.1 硬件连接与引脚规划做LED闪烁最简单的方案就是使用板载LED。市面上最常见的STM32F103C8T6蓝色核心板上有一颗LED连接在PC13引脚低电平点亮。如果你用的是其他开发板查一下原理图找到LED对应的引脚即可。我这里的例程统一以PC13为例如果你用的是PA5或其他引脚后续在CubeMX里改成对应引脚就行代码逻辑完全一致。在CubeMX的芯片引脚图上用鼠标左键点击PC13引脚在弹出的菜单中选择“GPIO_Output”。设置好输出模式后在左侧GPIO配置面板里建议把Maximum output speed设为Low或者Medium就行控制一个LED完全不需要高翻转速率低速模式反而有利于减少噪声。User Label可以改成“LED”这样生成的宏名称更直观。4.2 弄懂HAL库的中断回调流程HAL库最让人摸不着头脑的地方可能是它的“回调函数”机制。定时器产生更新事件后硬件会进入中断服务函数TIM1_UP_IRQHandler这个函数在stm32f1xx_it.c中已经被CubeMX自动写好了函数内部调用了HAL_TIM_IRQHandler(htim1)。HAL库的公共中断处理函数会判断这次中断的具体来源然后调用一个弱定义的回调函数HAL_TIM_PeriodElapsedCallback。这里的关键是这个回调函数是弱定义的__weak意思是库里已经给了一个空实现你必须在自己的代码里重新实现一个同名的强函数否则中断来了什么都不会发生。很多新手把代码写在回调函数里却不生效大概率就是因为在别的文件里重复定义了函数或者函数名拼写有误。它的标准形式是带一个TIM_HandleTypeDef *htim参数的通过这个参数可以判断是哪个定时器触发了本次中断。为什么要设计成回调函数而不是直接在TIM1_UP_IRQHandler里写代码因为HAL库要把“硬件中断处理”和“用户业务逻辑”解耦。你不需要知道中断服务函数的具体细节只需要在回调里判断定时器实例就够了。这样一套代码框架在切换到TIM8或者TIM2时写法和套路是一模一样的。4.3 启动定时器中断与LED翻转逻辑打开生成好的MDK工程在main.c里的用户代码区/* USER CODE BEGIN 0 */和/* USER CODE END */之间定义一个全局变量做中断次数统计/* USER CODE BEGIN 0 */ volatile uint16_t tim1_counter 0; /* USER CODE END 0 */然后在main()函数的while (1)循环之前调用启动函数HAL_TIM_Base_Start_IT(htim1);这一步非常容易忘。CubeMX生成代码时只帮你完成定时器初始化并不会自动启动定时器中断如果你忘了调用这个函数定时器始终处于未运行状态中断一次都不会触发。HAL_TIM_Base_Start_IT的含义是“以中断模式启动定时器的时基单元”它做了两件事一是让定时器真正开始计数二是使能更新中断。接着在文件最后找一个合适的位置实现中断回调函数。如果你想保持main.c整洁也可以新建一个timer_callbacks.c文件但新手阶段放在main.c里最省事不容易出包含头文件的问题。代码非常简单void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM1) { tim1_counter; if (tim1_counter 500) { tim1_counter 0; HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } } }这个逻辑很直白每1ms进入一次回调tim1_counter加1当它累计到500时代表过去了500ms翻转一次LED的电平同时计数器清零重新开始下一轮。因为LED默认是低电平点亮初始状态为高第一次翻转后变低点亮再隔500ms翻转变高熄灭最终看到的效果就是LED以1Hz的频率闪烁也叫呼吸节奏里的标准闪烁灯非常适合用来验证定时器是否真的按照1ms在跑。4.4 主循环中可以做什么既然定时器在后台独立工作主循环自然空出来了。我把主循环里可以写的演示代码也顺手贴出来方便你理解“非阻塞”的含义while (1) { // 主循环可以运行其他任务比如按键扫描、LCD刷新等 }如果你把LED翻转写在主循环里用HAL_Delay延时那程序在延时期间会被“锁死”在那一行。但现在我们把延时任务交给了定时器主循环就能持续运转后续你想加按键检测、OLED刷新、串口数据收发都不会互相拖累。这也是这套方案对新手最有价值的地方你等于白捡了一个稳定可靠的“1秒分时器”。4.5 用调试器验证时序代码编译下载之后LED应该开始闪烁。如果一切顺利到此Demo就完成了。不过我建议你再多做一步在MDK的调试界面下打开tim1_counter这个变量的实时观察窗口全速运行后你会看到它每隔一段固定时间被清零这个跳动本身就是1ms中断在工作的直接证据。如果你手头有示波器或逻辑分析仪把探头夹在PC13引脚上能看到一个频率1Hz、占空比50%的方波。没有示波器也没关系用手机秒表对着LED数10次闪烁的耗时误差在1秒以内基本说明定时没问题。实际应用中如果要求更高的时间精度建议使用外部晶振并检查晶振负载电容但一般入门场合这个精度已经绰绰有余。5. 常见问题与排查技巧实录5.1 LED完全不动作怎么办遇到LED死活不亮先不要怀疑定时器按照从硬件到软件的思路排查。先用万用表量LED两端电压确认是不是核心板本身没供电或者LED引脚和PC13之间有跳线帽没插。确认硬件没问题后检查编译时是否真正把新代码烧录进了芯片——很多时候MDK下载报错是因为没有设置好ST-Link的下载算法。软件层面建议在main()里先单独测试一下HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin)放在主循环里能不能看到LED状态变化。如果LED一直不变大概率是GPIO配置错了检查CubeMX里PC13是不是配成了GPIO_OutputUser Label是不是“LED”生成的宏名对不对。如果LED在主循环里能翻转但定时器模式不行问题就集中在定时器配置和中断启动这两步上了。5.2 中断没有进入回调函数这是出现频率最高的问题。先检查是否调用了HAL_TIM_Base_Start_IT(htim1)这个函数必须放在死循环之前。其次检查NVIC里是否勾选了TIM1的更新中断如果只在CubeMX里改了定时器参数没勾NVIC全局中断并不会被使能。还有一个很隐蔽的坑如果你在别的文件里重新实现过HAL_TIM_PeriodElapsedCallback而编译时链接顺序把那个文件排除在外或者函数名打错一个字母那么中断来了之后虽然执行了HAL库默认的空实现但你的代码一个字节都不会跑。建议在回调函数里打一个软件断点或者用一条自己熟悉的GPIO翻转来验证别用串口打印因为串口打印本身可能还没初始化好。5.3 闪烁周期明显不对最常见的现象是LED亮1秒才闪一次甚至亮两秒才动一下。这几乎可以锁定到定时器时钟配置错了。之前提到过APB1、APB2的分频系数会影响定时器时钟如果你修改过时钟树一定要回来看TIM1的输入时钟到底是不是72MHz。在CubeMX的Clock Configuration界面里鼠标悬停在“TIM1 CLK”的标识上会显示出实际频率。如果这里显示的是36MHz说明APB2被分频了或者PLL根本没配到72MHz那么你之前按72MHz算的参数自然就慢了一倍。另一种情况是参数本身算错。比如有人把Prescaler填成了“71”之后又额外再加1其实寄存器里填71就代表分频系数是72你再在参数里额外处理一次就相当于分频了5184时间被拉得非常离谱。记住CubeMX里填的参数值就是寄存器的原始值公式里的“1”已经在计算过程中考虑进去了你只需要往参数里填数不用自己再换算。5.4 编译报错或函数未定义如果出现undefined symbol HAL_TIM_PeriodElapsedCallback之类的链接错误多半是代码没有编译进去。检查你的回调函数是否放在了不会被条件编译排除的区域内。最好不要放在stm32f1xx_it.c里重写同名函数因为CubeMX在手动修改后重新生成代码时会把用户代码区之外的内容覆盖掉你写的函数就丢了。合并多次生成代码时我的经验是能用USER CODE区域解决的就尽量写在区域内。对新手来说回调函数放在main.c末尾的USER CODE 4区域是最安全的位置CubeMX重新生成也不会删掉。5.5 中断回调里的红线与避坑虽然定时器中断是“轻量级任务”的理想场地但如果什么重活都往回调函数里塞反而会把系统拖垮。下面是几条我踩过的坑不要在回调里调用HAL_Delay()。它的时基依赖SysTick而SysTick的中断优先级如果低于当前定时器中断SysTick就无法执行HAL_Delay会卡死。不要在回调里做长耗时运算。比如浮点运算、软件解码、大数组拷贝这些操作会让中断函数占用太长时间影响其他中断的实时性。LED翻转这种GPIO操作是中断回调里比较“轻”的典型。不要在回调里直接调用printf。串口输出是阻塞式的波特率9600时一个字符就要大约1ms打印十来个字符下一次定时器中断就来了轻则时序抖动重则中断嵌套出问题。需要调试时一般置一个标志位在主循环里再处理打印。把这些红线记住了定时器中断这套机制才算真正用得明白。6. 从Demo到实际项目经验收尾这个Demo做完之后我自己再写STM32项目时很少再去翻定时器手册了因为流程已经固化成一套肌肉记忆先想清楚目标周期接着算PSC和ARR然后在CubeMX里配参数、勾中断、生成代码最后在回调函数里写业务逻辑。每次用这套流程基本都是一次通过。如果你还想把Demo往深处拓展我提供两个方向供参考。第一个方向是把单次计数改成多路任务调度——用同一个1ms中断通过多个计数器变量来产生10ms、50ms、100ms的不同任务节拍你会发现一个小小的时间片调度器就这么搭出来了按键扫描、数码管刷新、通信超时管理都可以往里塞。第二个方向是玩一下TIM1的高级特性比如把它配成PWM输出模式用同样的分频逻辑去控制电机转速或LED亮度渐变这时候你对定时器的理解就会更立体了。到最后你会发现STM32CubeMX最大的意义不在于省掉几行代码而在于它把芯片外设的配置模型统一了。从TIM1到TIM8从F1到F4系列这套配置思维是跨芯片复用的。希望这篇实战记录能帮你顺利迈过定时器中断这道门槛早日在自己的项目里把1ms“心搏”用起来。
企业数字化 ERP 产品动态
相关推荐
Allegro差分对等长检查实战:从约束设置到偏差修复全流程 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 3:53:21
Win11Debloat:16 个可回退的开关,给 Windows 11 去臃肿加隐私清理 Win11Debloat:16 个可回退的开关,给 Windows 11 去臃肿加隐私清理 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other chang… · 2026/9/25 3:53:15
深入gnhf编排器架构:状态机如何让AI代理整夜循环不丢一行代码 深入gnhf编排器架构:状态机如何让AI代理整夜循环不丢一行代码 【免费下载链接】gnhf Before I go to bed, I tell my agents: good night, have fun 项目地址: https://gitcode.com/gh_mirrors/gn/gnhf
gnhf(good night, have fun)是一… · 2026/9/25 4:25:44
VirtualBox E_FAIL (0x80004005) 报错全解析:从驱动冲突到UUID修复 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:25:44
MIPI DSI转LVDS桥接方案:LT9211与N76E003配置实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:25:44
Windows 11锁屏机制深度解析与分版本禁用方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:25:44
必应搜索出现Ref A/B/C标签?原因排查与解决指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:25:37
Cadence Sigrity TDR仿真实战:从原理到阻抗曲线分析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 4:25:37
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37