news 2026/9/30 1:24:12

嵌入式Debug四层排查法:从硬件到应用的高效定位指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式Debug四层排查法:从硬件到应用的高效定位指南

1. 嵌入式Debug的底层逻辑:为什么你总在瞎猜

干嵌入式这行十来年,我最怕听到的一句话就是“我加个打印看看”。不是说打印不能用,而是很多人把打印当成了唯一的排查手段,出了问题就到处塞printf,串口刷得飞起,最后把自己都看晕了。更离谱的是,有些问题加了打印就消失,去掉打印又出现,典型的时序敏感型bug,这时候你盯着那堆日志能看出花来才怪。

嵌入式Debug和纯软件开发有个本质区别:你面对的是一个物理世界和数字世界的交界体。PC上写代码,内存是内存,CPU是CPU,操作系统帮你兜底。嵌入式呢?寄存器配置错一位,时钟树就歪了;中断优先级设反了,系统就卡死;栈空间少给了256字节,跑着跑着就进HardFault。这些问题在纯软里根本不存在,但在嵌入式里是家常便饭。

所以排查思路必须分层。我总结下来,嵌入式的问题基本逃不出四类:硬件层、驱动层、系统层、应用层。这四层像一栋楼,地基是硬件,一楼是驱动,二楼是系统,三楼是应用。你三楼漏水,不一定是一楼水管破了,也可能是地基沉降导致墙体开裂。很多兄弟一上来就查应用代码,查了半天发现是电源纹波太大导致MCU复位,这就是典型的“在三楼找地基的问题”。

这套四类排查法的核心逻辑是:从现象反推层级,从层级锁定根因,从根因验证假设。不是让你按顺序一层层查,而是根据现象特征快速定位到最可能的层级,然后用该层级的专用工具和方法深挖。比如系统随机重启,你优先查硬件层和系统层;某个外设数据不对,优先查驱动层;功能逻辑异常,优先查应用层。方向对了,效率能差出十倍。

注意:不要迷信“经验直觉”。我见过太多老手凭感觉换芯片、换电容,最后发现是个宏定义写错了。直觉可以用来缩小范围,但验证必须靠数据。

2. 四类排查法总览:从现象到根因的映射关系

2.1 四类问题的典型现象对照表

先把这个表刻在脑子里,遇到问题先对号入座。这张表是我这些年踩坑踩出来的,不敢说百分百准,但能帮你省掉至少一半的无效排查时间。

问题层级典型现象优先排查方向常用工具
硬件层随机重启、死机、发热、通信误码、ADC跳变电源、时钟、复位、信号完整性示波器、万用表、逻辑分析仪
驱动层外设不工作、数据错位、中断不触发、DMA卡死寄存器配置、时序匹配、中断优先级寄存器查看器、逻辑分析仪、示波器
系统层任务调度异常、内存泄漏、栈溢出、死锁RTOS配置、堆栈分配、中断嵌套系统View、内存分析工具、调试器
应用层逻辑错误、状态机跑飞、协议解析异常代码逻辑、边界条件、并发竞争调试器、日志、单元测试

这张表怎么用?举个例子:你的设备每隔几小时重启一次,没有规律。先看现象——随机重启,大概率硬件层或系统层。硬件层查什么?电源纹波、复位引脚、看门狗。系统层查什么?栈溢出、内存耗尽、任务饿死。两边同时下手,用示波器抓电源和复位引脚,用调试器看栈使用率,基本半小时内能锁定方向。

2.2 为什么是这四类而不是别的分法

有人问,为什么不按“软件/硬件”分?太粗了。驱动层的问题,你说是软件还是硬件?寄存器配置是软件,但时序匹配是硬件特性。按四层分,每层都有明确的边界和工具集,排查时不会串味。

也有人问,为什么不按“功能模块”分?比如通信问题、显示问题、存储问题。这是按症状分,不是按根因分。通信问题可能是硬件层(信号完整性)、驱动层(波特率配置)、系统层(中断延迟)、应用层(协议解析)。按功能分你会到处乱撞,按层级分你才能逐层收敛。

这四类的划分依据是抽象层级和可控性。硬件层你改的是电路和器件,驱动层你改的是寄存器和时序,系统层你改的是资源和调度,应用层你改的是逻辑和状态。每一层的修改成本和风险完全不同,硬件改一版要重新打板,应用层改一行代码重新编译就行。所以排查顺序应该是:先应用层后系统层,先驱动层后硬件层,从低成本到高成本。

实操心得:我习惯在项目初期就建一个“问题层级记录表”,每解决一个bug就记录它属于哪一层、根因是什么、用了什么工具。三个月后回看,你会发现80%的问题集中在某一两层,后续排查直接重点盯防。

3. 硬件层排查:电源、时钟、复位的三板斧

3.1 电源问题:嵌入式系统的万恶之源

电源不稳,一切免谈。我遇到过太多“软件问题”最后查出来是电源纹波超标。嵌入式系统对电源的要求比你想的苛刻得多,尤其是MCU内核电压、PLL供电、ADC参考电压这些敏感节点。

排查电源问题,第一步不是拿示波器乱戳,而是先看原理图和PCB布局。几个关键点:去耦电容是否靠近引脚、电源走线是否够粗、地平面是否完整、模拟电源和数字电源是否隔离。这些在原理图阶段就能看出问题,不用等板子回来。

板子回来了,上电前先做静态检查:用万用表测各路电源对地阻抗,确认没有短路。上电后用示波器看纹波,注意要用AC耦合、带宽限制到20MHz、探头地线尽量短。纹波超过芯片手册要求的50%就要警惕了。

我实测过一个案例:某STM32板子ADC采样值跳变严重,软件滤波怎么调都不行。后来用示波器看VDDA引脚,发现纹波高达80mV,而手册要求小于10mV。换了一颗低ESR的钽电容,问题直接消失。这种问题你在代码里查一辈子也查不出来。

注意:示波器探头的地线夹子会引入环路电感,测高频纹波时尽量用弹簧地针,不要用鳄鱼夹。这个细节很多人忽略,导致测出来的纹波比实际大好几倍。

3.2 时钟问题:PLL失锁与晶振不起振

时钟是嵌入式系统的心脏。时钟不对,串口波特率就偏,通信就误码;时钟不稳,定时器就飘,控制周期就乱。时钟问题分两类:晶振不起振和PLL失锁。

晶振不起振,先查负载电容匹配。很多新手直接抄参考设计的电容值,但不同晶振的负载电容要求不一样。计算公式是:CL = (C1 * C2) / (C1 + C2) + Cstray,其中Cstray是PCB寄生电容,一般2-5pF。你要根据晶振手册的CL值反推C1和C2。如果晶振手册写CL=12pF,Cstray取3pF,那C1=C2≈18pF。差几pF可能就不起振,尤其是32.768kHz的低频晶振。

PLL失锁,先查参考时钟频率是否正确、PLL配置参数是否匹配、VCO供电是否干净。用示波器测PLL输出引脚,如果频率不对或者抖动大,基本就是配置问题。有些MCU的PLL对电源纹波极其敏感,VDDA上有一点噪声就失锁,这时候要在PLL供电引脚单独加LC滤波。

3.3 复位问题:看门狗与电源监控的博弈

复位问题最让人头疼,因为它往往表现为“随机重启”,你盯着代码看一天也看不出所以然。复位源有很多:上电复位、掉电复位、看门狗复位、软件复位、外部复位引脚被拉低。

排查复位问题,第一步是读复位状态寄存器。几乎所有MCU都有RCC_CSR之类的寄存器,记录上次复位的原因。先把这个读出来,能直接排除掉一大半可能性。如果是看门狗复位,查喂狗逻辑;如果是掉电复位,查电源;如果是软件复位,查代码里的NVIC_SystemReset调用。

我遇到过一个经典案例:设备在电机启动瞬间复位。读复位寄存器发现是掉电复位,但电源电压用万用表测一直是3.3V。后来用示波器抓,发现电机启动时电源瞬间跌到2.7V,持续时间只有几十微秒,万用表根本抓不到。解决方案是在电源入口加一个大容量电解电容和TVS管,问题解决。

实操心得:调试复位问题时,把复位引脚引出来接逻辑分析仪,同时抓电源电压。两个信号对比看,能快速判断是复位引脚被干扰还是电源跌落。这个操作花不了十分钟,但能省你两天时间。

4. 驱动层排查:寄存器、时序、中断的三角关系

4.1 寄存器配置:从手册到代码的最后一公里

驱动层的问题,十有八九是寄存器配置不对。芯片手册几百页,寄存器几十个,每个位都有讲究。我见过太多人直接抄网上的例程,结果芯片型号差一个字母,寄存器定义就变了。

排查寄存器问题,我的习惯是先读后写。配置之前先把相关寄存器的当前值读出来,和手册的复位默认值对比。如果默认值就不对,说明芯片可能没正常工作或者时钟没使能。配置之后再读一遍,确认写入生效。有些寄存器是只读的,有些是写1清零的,有些需要解锁序列,这些手册里都有,但很容易看漏。

举个例子:STM32的GPIO配置,很多人忘了使能GPIO时钟就直接写寄存器,结果怎么都不工作。还有AFR寄存器,一个引脚可能对应多个复用功能,选错了就是没输出。这种问题用调试器的寄存器查看窗口一看便知,比打印快得多。

4.2 时序匹配:逻辑分析仪是你的最佳搭档

驱动层的时序问题,示波器和逻辑分析仪是神器。I2C的ACK位、SPI的CPOL/CPHA、UART的起始位采样、CAN的位定时,这些用逻辑分析仪抓一下,一目了然。

我调I2C的时候有个习惯:先用逻辑分析仪抓波形,确认起始条件、地址、ACK、数据、停止条件都符合预期。如果从机不ACK,先查地址对不对(7位还是8位)、上拉电阻够不够、从机供电是否正常。I2C的上拉电阻很讲究,4.7k是常见值,但高速模式下可能要2.2k甚至1k。上拉不够,上升沿太缓,从机就认不出高电平。

SPI的坑更多。CPOL和CPHA四个组合,选错了数据就错位。还有片选信号的建立时间和保持时间,有些从机要求片选拉低后延迟一段时间才能发时钟,你发太快它就丢数据。这些时序参数在从机手册里都有,但很多人不看,直接抄例程,结果换个从机就翻车。

4.3 中断优先级:嵌套与抢占的微妙平衡

中断优先级配错,系统行为会变得极其诡异。高优先级中断频繁触发,低优先级中断永远得不到执行,这叫中断饿死。两个中断互相等待对方释放资源,这叫中断死锁。中断里调用了不可重入函数,数据被踩得乱七八糟。

Cortex-M的中断优先级分抢占优先级和子优先级。抢占优先级高的可以打断抢占优先级低的,子优先级只在同时挂起时决定谁先执行。很多人把这两个搞混,配出来的优先级和预期完全不一样。我的建议是:能用抢占优先级解决的就不要用子优先级,子优先级只在极少数场景下有用。

还有中断嵌套的深度。嵌套太深,栈会爆。Cortex-M的栈是向下生长的,每个中断嵌套都要压栈。如果中断服务函数里局部变量多,嵌套几层栈就没了。我一般会在调试阶段把栈填充成特定模式(比如0xDEADBEEF),跑一段时间后看栈使用峰值,留出至少30%的余量。

注意:中断服务函数里不要调用printf。printf不可重入,而且耗时极长,会严重干扰系统实时性。要打印就用环形缓冲区加DMA,或者干脆用调试器的实时变量查看功能。

5. 系统层排查:RTOS、内存、栈的暗礁

5.1 RTOS任务调度:优先级反转与死锁

用了RTOS,问题就从“代码逻辑”升级到了“系统行为”。任务优先级反转是经典问题:低优先级任务持有互斥锁,高优先级任务等锁,中优先级任务疯狂运行,导致高优先级任务被无限期阻塞。解决方案是优先级继承,大多数RTOS的互斥锁都支持这个特性,但你要记得开启。

死锁更隐蔽:任务A等信号量1,任务B等信号量2,任务A持有信号量2,任务B持有信号量1,互相等,永远等不到。排查死锁,用RTOS自带的系统View或者Trace工具,看每个任务的状态和持有的资源。FreeRTOS有vTaskList和vTaskGetRunTimeStats,能打印任务状态和CPU占用率,非常实用。

任务栈溢出是另一个高频问题。每个任务创建时都要指定栈大小,给少了就溢出,给多了浪费RAM。我一般先用一个估算值,跑起来后用uxTaskGetStackHighWaterMark查历史最小剩余栈,再调整。这个函数返回的是栈使用峰值到栈底的剩余字数,如果小于10就要警惕了。

5.2 内存管理:堆碎片与泄漏的慢性毒药

嵌入式系统的内存管理比PC上脆弱得多。没有MMU,没有虚拟内存,堆碎片积累到一定程度,malloc就返回NULL。很多兄弟不检查malloc返回值,直接往NULL指针写数据,不死机才怪。

排查内存问题,第一步是统计堆的使用情况。大多数RTOS提供xPortGetFreeHeapSize和xPortGetMinimumEverFreeHeapSize,能看当前剩余堆和历史最小剩余堆。如果历史最小剩余堆接近0,说明堆快耗尽了,要么加大堆,要么查泄漏。

内存泄漏的排查比较麻烦,没有PC上Valgrind那么方便的工具。我的做法是:在malloc和free里加计数,定期打印当前分配块数和总字节数。如果只增不减,就是泄漏。然后二分法定位:注释掉一半代码,看泄漏是否消失,逐步缩小范围。

实操心得:我习惯在项目初期就实现一个简单的内存跟踪模块,记录每次malloc的文件名、行号、大小。出问题时直接打印未释放的块,一目了然。这个模块不到100行代码,但能省你无数个通宵。

5.3 栈溢出:最危险的沉默杀手

栈溢出是嵌入式系统最危险的问题之一,因为它往往不立即报错,而是悄悄踩坏相邻内存,导致各种诡异现象。栈溢出分两种:任务栈溢出和主栈溢出。任务栈溢出影响单个任务,主栈溢出影响中断和启动代码。

检测栈溢出,硬件上可以用MPU(内存保护单元)设置栈边界,越界就触发异常。软件上可以填充魔数,定期检查栈底魔数是否被改写。Cortex-M的调试组件里有个栈指针限制寄存器,可以设置栈的下界,越界触发HardFault,这个功能在调试阶段非常有用。

栈大小的估算,我的经验值是:简单任务512字节起步,复杂任务2KB起步,中断嵌套深的4KB起步。当然这要看具体编译器优化和局部变量大小。最靠谱的方法还是跑起来看高水位线,用数据说话。

6. 应用层排查:逻辑、状态机、并发的三重门

6.1 逻辑错误:边界条件与防御性编程

应用层的逻辑错误,大多数是边界条件没处理好。数组越界、整数溢出、除零、空指针,这些在PC上可能崩溃报错,在嵌入式上可能静默地踩坏内存,过一会儿才发作。

防御性编程是基本功。每个函数入口检查参数有效性,每个数组访问检查索引范围,每个除法检查除数非零。这些检查在PC上可能觉得冗余,在嵌入式上却是保命的。我见过一个项目,因为没检查串口接收长度,上位机发了一个超长包,直接踩爆了接收缓冲区,设备死机。加一行长度检查就能避免的事,硬是查了两天。

还有整数溢出。嵌入式里经常用uint8_t、uint16_t省内存,但运算时忘了提升类型,结果溢出。比如uint8_t a=200, b=100,a+b在8位里是44,不是300。这种问题编译器不一定警告,要靠代码审查和静态分析工具。

6.2 状态机跑飞:非法状态与超时保护

状态机是嵌入式应用的核心模式,但状态机跑飞也是高频问题。常见原因:非法状态转移、事件丢失、超时未处理。

非法状态转移,比如从IDLE直接跳到RUN,跳过了INIT。这种问题要在状态机设计阶段就杜绝,每个状态只允许特定的转移,其他转移一律拒绝并记录错误。我习惯在状态机里加一个default分支,打印当前状态和收到的事件,方便排查。

事件丢失,通常是事件队列满了或者事件被覆盖。要确保事件队列有足够的深度,并且满了之后有明确的处理策略(丢弃最旧、丢弃最新、还是阻塞)。超时保护是每个状态的标配,进入一个状态就启动定时器,超时未收到预期事件就报警或复位。

6.3 并发竞争:共享资源的隐形杀手

RTOS环境下,多个任务访问共享资源,不加保护就会竞争。经典案例:任务A在读一个32位变量,任务B在写同一个变量,读出来的值高16位是新的,低16位是旧的,完全错乱。这种问题在单核上也会发生,因为任务切换可能发生在任何指令边界。

保护共享资源,最简单的是关中断,但关中断时间要尽可能短。更好的是用互斥锁或信号量,但要注意优先级继承和死锁。还有一种是原子操作,Cortex-M有LDREX/STREX指令,可以实现无锁的原子访问,但用起来比较绕。

我的原则是:能不用共享资源就不用,必须用就明确所有权。一个变量只由一个任务写,其他任务只读,读写用原子操作或临界区保护。这样责任清晰,排查也容易。

注意:volatile关键字只能防止编译器优化,不能保证原子性。很多人以为加了volatile就线程安全了,这是大错特错。volatile解决的是“编译器不知道这个变量会变”的问题,不解决“多个任务同时访问”的问题。

7. 工具链与调试手段:别只会printf

7.1 调试器:单步、断点、变量查看

调试器是嵌入式Debug的核武器,但很多人只用了它10%的功能。除了单步和断点,调试器还能做条件断点、数据断点、实时变量查看、寄存器查看、内存查看、栈回溯。

条件断点特别有用:比如一个循环跑一万次,你只想在i==5000时停下来,设个条件断点就行,不用手动跳过。数据断点更神:某个变量被意外修改,你不知道谁改的,设个写断点,一改就停,直接抓到凶手。这个功能在排查内存踩踏时简直是神器。

实时变量查看(Live Watch)可以在不暂停CPU的情况下刷新变量值,适合观察实时性要求高的系统。寄存器查看和内存查看是驱动层调试的标配,配合芯片手册,能快速定位配置问题。

7.2 日志系统:分级、缓冲、异步

printf不是不能用,是要用对。我的做法是搭一个分级日志系统:ERROR、WARN、INFO、DEBUG四级,通过宏控制输出级别。发布版本只留ERROR和WARN,调试版本全开。

日志输出要用异步方式:日志先写入环形缓冲区,再由DMA或低优先级任务输出到串口。这样不会阻塞主逻辑,也不会因为串口慢而影响实时性。环形缓冲区的大小要够,至少能存几百条日志,防止突发日志把缓冲区冲爆。

日志格式也有讲究:时间戳+级别+模块+行号+内容。时间戳用系统tick,级别用单字符(E/W/I/D),模块用缩写,行号用__LINE__。这样一条日志不超过80字符,串口输出快,看起来也清爽。

7.3 逻辑分析仪与示波器:硬件层的眼睛

逻辑分析仪抓数字信号,示波器看模拟信号,两者配合使用。逻辑分析仪适合抓SPI、I2C、UART、CAN的协议波形,能直接解码成数据,比看波形快得多。示波器适合看电源纹波、信号完整性、时钟抖动。

选逻辑分析仪,注意采样率和通道数。采样率至少是被测信号频率的5倍,抓SPI的话,如果时钟是10MHz,采样率至少要50MHz。通道数看需求,一般8通道够用,16通道更从容。示波器注意带宽和采样率,带宽至少是被测信号频率的3倍,测100MHz的信号要300MHz带宽的示波器。

实操心得:我出门调试必带三样东西:调试器、逻辑分析仪、万用表。调试器查代码,逻辑分析仪查时序,万用表查电源。这三样能覆盖90%的嵌入式问题。示波器太重,一般留在实验室用。

8. 常见问题速查与避坑指南

8.1 问题速查表

现象可能层级排查步骤工具
随机重启硬件/系统读复位寄存器→查电源→查看门狗→查栈示波器、调试器
外设不工作驱动查时钟使能→查寄存器配置→查引脚复用调试器、逻辑分析仪
通信误码硬件/驱动查波特率→查信号完整性→查上拉电阻示波器、逻辑分析仪
任务卡死系统查死锁→查优先级反转→查栈溢出系统View、调试器
数据错乱应用/系统查并发竞争→查数组越界→查指针调试器、日志
功耗异常硬件/驱动查未使用外设时钟→查引脚浮空→查电源域万用表、调试器

8.2 避坑指南:那些年我踩过的坑

坑一:迷信例程。网上的例程大多是简化版,省略了错误处理和边界检查。直接抄到项目里,跑通了还好,跑不通你都不知道从哪查。我的做法是:例程只用来理解流程,实际代码自己写,该加的检查一个不少。

坑二:忽略芯片勘误。每个芯片都有勘误表(Errata Sheet),里面记录了芯片的已知问题。有些问题会导致外设行为异常,你查手册查不出来,因为手册写的是“应该”的行为,勘误表写的是“实际”的行为。项目初期一定要把勘误表过一遍。

坑三:调试器配置错误。调试器连不上,先查时钟配置、复位引脚、调试接口是否被禁用。有些低功耗模式会关闭调试接口,导致连不上。还有SWD接口的引脚被复用成GPIO,也会导致连不上。这些在调试器的手册里都有,但很容易忽略。

坑四:栈大小拍脑袋。栈给少了溢出,给多了浪费。我的做法是:先用一个保守值,跑起来后用高水位线查实际使用量,再调整。调整后要重新跑高水位线,确认余量足够。这个流程走一遍,比拍脑袋靠谱得多。

坑五:不看反汇编。有时候C代码看起来没问题,但编译器优化后行为变了。比如volatile没加,编译器把变量缓存到寄存器里,循环里读的一直是旧值。这时候看反汇编就能发现,变量根本没从内存重新读。调试器里一般都有反汇编窗口,配合源码看,能发现很多隐藏问题。

8.3 排查心法:从现象到根因的思维框架

最后分享一个我总结的排查心法,四步走:

第一步:稳定复现。不能稳定复现的问题,先想办法复现。复现不了,一切排查都是空谈。复现的条件越简单越好,最好能写成一个测试用例。

第二步:缩小范围。用二分法、注释法、替换法,把问题范围从整个系统缩小到一个函数、一行代码。范围越小,根因越清晰。

第三步:提出假设。根据现象和范围,提出一个可验证的假设。假设要具体,比如“是电源纹波导致复位”,而不是“可能是硬件问题”。

第四步:验证假设。用工具和数据验证假设。假设成立,修复;假设不成立,回到第三步重新提假设。每次验证都要有明确的结果,不能模棱两可。

这个框架看起来简单,但能帮你避免“瞎猜”和“乱试”。嵌入式Debug最怕的就是没有章法,东一榔头西一棒子,时间花了,问题还在。有了框架,每一步都有目的,效率自然就上来了。

实操心得:我习惯在排查时开一个文档,记录每一步的操作、观察到的现象、得出的结论。排查结束后回看,能发现很多当时忽略的线索。这个习惯坚持了几年,现在遇到新问题,翻翻以前的记录,经常能找到类似的案例,直接套用解决方案。

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

FPGA图像处理入门:从像素流水线到行缓存与DDR实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:23:53

基于Vision Transformer的图像去雾算法:从源码跑通到效果调优全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:23:35

零基础网站开发入门:从环境搭建到Flask动态网站实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:23:34

小米平板1刷机救砖全攻略:从解锁TWRP到LineageOS实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:22:53

FPGA实战:CORDIC算法实现sin/cos计算与EGo1上板验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 1:22:43

广电光猫获取超级管理员密码与桥接模式设置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华