1. 项目概述为什么一个“第一个固件”值得花三天反复烧录十一次RISC-V开发实战用中科蓝讯RV32-Toolchain编译第一个蓝牙芯片固件——这个标题里藏着三重真实门槛第一是架构切换的思维断层第二是国产工具链的“黑盒感”第三是蓝牙固件本身对时序、内存布局和协议栈耦合度的严苛要求。我带过六届嵌入式方向的实习生90%的人卡在“hello world”之后的第二步不是不会写代码而是根本不知道编译器生成的二进制文件里中断向量表在哪、.bss段清零由谁触发、蓝牙HCI命令缓冲区是否越界、甚至bootloader跳转到main()前到底做了哪些寄存器初始化。中科蓝讯的RV32-Toolchain不是GCC的简单封装它内置了针对LB2002系列蓝牙SoC定制的链接脚本、启动汇编、外设寄存器头文件和轻量级BLE协议栈抽象层。你用它编译出的固件不是通用RISC-V可执行文件而是能直接喂给LB2002芯片ROM Bootloader的、带校验签名的二进制镜像。这正是“第一个固件”的价值它不是演示程序而是你和真实硬件之间建立信任关系的第一次握手。如果你正在看这篇文字大概率正面对一块LB2002-EVB开发板、一份没有注释的SDK压缩包、以及终端里反复报错的“section.text will not fit in regionrom”。别急接下来每一行都是我踩坑后记下的刻度。2. 工具链与环境搭建RV32-Toolchain不是“安装完就能用”的玩具2.1 中科蓝讯RV32-Toolchain的本质解构RV32-Toolchain不是开源社区常见的riscv64-elf-gcc交叉编译器套件它是中科蓝讯基于GCC 11.2深度定制的专用工具链核心差异点有三个首先是目标ABI锁定为ilp32d32位整数/长整型/指针双精度浮点而非更常见的ilp32其次是链接器脚本linker script硬编码了LB2002芯片的物理内存映射ROM起始地址0x0000_000064KB、SRAM起始地址0x2000_0000128KB、外设寄存器基址0x4000_0000最关键的是它集成了bluetooth_boot_loader模块——一个仅2.3KB的ROM固件引导程序负责校验固件签名、配置PLL、初始化Flash控制器并将用户代码从Flash拷贝到SRAM指定位置后跳转执行。这意味着你写的main()函数实际运行地址是SRAM中的0x2000_1000而非链接脚本里写的0x0000_0000。很多初学者编译通过但烧录后芯片无反应问题就出在这里他们没意识到toolchain强制把代码分成了两个空间——ROM中存放只读代码和常量SRAM中运行可读写数据和堆栈。提示中科蓝讯官方提供的toolchain压缩包名为rv32-toolchain-linux-x86_64-20230815.tar.gz解压后目录结构为/opt/rv32-toolchain/其中bin/目录下包含rv32-unknown-elf-gcc、rv32-unknown-elf-gdb等可执行文件。注意不要将其与RISC-V官方工具链路径混淆否则make时会因找不到rv32-unknown-elf-ar而失败。2.2 环境变量配置的致命细节很多教程只告诉你export PATH/opt/rv32-toolchain/bin:$PATH但这远远不够。LB2002 SDK依赖三个关键环境变量缺一不可RV32_TOOLCHAIN_PATH必须指向toolchain根目录如/opt/rv32-toolchainSDK的Makefile会用它定位lib/gcc/rv32-unknown-elf/11.2.0/下的标准库BLUETOOTH_SDK_PATH指向中科蓝讯提供的SDK根目录如/home/user/sdk/lb2002_sdk_v2.1该路径下必须包含include/、src/、platform/三个子目录OPENOCD_SCRIPTS指向OpenOCD配置文件路径如/usr/share/openocd/scripts因为LB2002使用JTAG调试其target/lb2002.cfg文件需被OpenOCD识别。我曾因漏配RV32_TOOLCHAIN_PATH导致编译时出现fatal error: stdio.h: No such file or directory排查了两天才发现SDK的Makefile里有一行$(CC) -I$(RV32_TOOLCHAIN_PATH)/lib/gcc/...。更隐蔽的问题是如果BLUETOOTH_SDK_PATH路径末尾多了一个斜杠如/home/user/sdk/lb2002_sdk_v2.1/某些Makefile版本会错误解析为相对路径导致#include bluetooth/hci.h找不到头文件。实测下来最稳妥的配置方式是在~/.bashrc中添加export RV32_TOOLCHAIN_PATH/opt/rv32-toolchain export BLUETOOTH_SDK_PATH/home/user/sdk/lb2002_sdk_v2.1 export OPENOCD_SCRIPTS/usr/share/openocd/scripts export PATH$RV32_TOOLCHAIN_PATH/bin:$PATH然后执行source ~/.bashrc并验证echo $RV32_TOOLCHAIN_PATH应输出正确路径rv32-unknown-elf-gcc --version应显示rv32-unknown-elf-gcc (GCC) 11.2.0。2.3 OpenOCD与JTAG调试器的兼容性陷阱LB2002芯片不支持SWD调试必须使用JTAG接口。但市面上90%的廉价ST-Link V2调试器默认只启用SWD模式。你需要用STM32CubeProgrammer或ST-Link Utility软件进入“Settings → Debug → Interface”菜单将调试接口从“SWD”强制切换为“JTAG”。切换后用openocd -f interface/stlink-v2.cfg -f target/lb2002.cfg测试连接正常输出应包含Info : Listening on port 3333 for gdb connections和Info : LB2002: RISC-V core detected。如果出现Error: JTAG scan chain interrogation failed八成是JTAG接线问题——LB2002的JTAG引脚定义与标准ARM不同TCK对应芯片Pin 17TMS对应Pin 18TDI对应Pin 19TDO对应Pin 20TRST_N对应Pin 21低电平复位。我用万用表实测过某批次开发板的JTAG排针焊接虚焊导致TDO信号无法回传折腾了六小时才用热风枪重焊解决。3. 固件工程结构解析SDK里的五个隐藏层3.1 标准SDK目录树的深层含义中科蓝讯提供的LB2002 SDK以v2.1为例表面看是普通嵌入式工程但内部嵌套着五层逻辑结构理解每层作用才能避免改错地方lb2002_sdk_v2.1/ ├── build/ # 编译输出目录自动生成 ├── doc/ # 芯片手册PDF重点看《LB2002_Datasheet_V1.3.pdf》第4章Memory Map ├── include/ # 公共头文件含ble_stack.h、platform.h等 ├── platform/ # 芯片平台层核心含startup_rv32.S、system_lb2002.c、linker_script.ld │ ├── common/ # 通用驱动如uart.c、gpio.c │ └── lb2002/ # LB2002专属驱动如bt_controller.c、flash_driver.c ├── src/ # 应用层源码你的main.c、hci_handler.c在此 ├── tools/ # 烧录工具lb2002_flash_tool.py、signature_tool.py └── Makefile # 主控Makefile关键参数都在这里最关键的platform/目录下startup_rv32.S汇编文件定义了复位向量表地址0x0000_0000处存放_start入口紧接着是ecall、ebreak等异常向量。而linker_script.ld文件则严格划分内存区域MEMORY { rom (rx) : ORIGIN 0x00000000, LENGTH 0x00010000 /* 64KB ROM */ ram (rwx) : ORIGIN 0x20000000, LENGTH 0x00020000 /* 128KB SRAM */ } SECTIONS { .text : { *(.text.startup) *(.text) *(.rodata) } rom .data : { *(.data) *(.sdata) } ram AT rom .bss : { *(.bss) *(.sbss) . ALIGN(4); _end .; } ram }注意.data段的AT rom属性——它表示该段数据在ROM中存储但运行时加载到RAM中。这就是为什么你修改全局变量值后需要重新烧录因为初始值存在ROM里每次上电都从ROM拷贝到RAM。而.bss段不占用ROM空间只在RAM中预留清零区域。3.2 “第一个固件”的最小可行代码结构很多人以为main()函数就是全部其实LB2002固件必须包含四个强制组件系统初始化函数SystemInit()位于platform/lb2002/system_lb2002.c负责配置系统时钟HSE24MHzPLL倍频至48MHz、使能总线时钟、初始化Flash等待周期蓝牙协议栈初始化BLE_StackInit()调用bt_controller_init()配置HCI UART波特率默认115200、设置BLE角色Peripheral/ Central事件循环主干while(1)内必须调用BLE_ProcessEvents()这是协议栈的心跳函数处理HCI命令响应、ACL数据包收发、GATT服务发现等LED闪烁任务作为视觉反馈必须用GPIO_SetBits(GPIOA, GPIO_PIN_0)控制开发板上的D1 LED对应芯片PA0引脚。下面是最简main.c代码经实测可在LB2002-EVB上稳定运行#include bluetooth/ble_stack.h #include platform/lb2002/gpio.h #include platform/lb2002/system_lb2002.h void SystemClock_Config(void); void GPIO_Init_LED(void); int main(void) { // 1. 系统时钟初始化必须最先调用 SystemInit(); // 2. GPIO初始化LEDPA0 GPIO_Init_LED(); // 3. 蓝牙协议栈初始化 BLE_StackInit(); // 4. 主循环协议栈事件处理 LED闪烁 uint32_t tick 0; while(1) { // 每500ms翻转LED if(tick 500000) // 基于SysTick计数器 { GPIO_ToggleBits(GPIOA, GPIO_PIN_0); tick 0; } // 必须调用否则BLE协议栈不工作 BLE_ProcessEvents(); } } void GPIO_Init_LED(void) { RCC_EnableAPB2PeriphClk(RCC_APB2PERIPH_GPIOA); GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_0; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; GPIO_Init(GPIOA, GPIO_InitStruct); }注意此代码中BLE_ProcessEvents()不能放在if条件里必须每轮循环都执行。我曾因把它写成if(tick%1000) BLE_ProcessEvents();导致设备无法被手机扫描到因为HCI事件队列积压超时被丢弃。3.3 链接脚本与内存布局的实操验证要确认编译结果是否符合芯片物理限制必须用size和objdump工具分析。编译完成后执行rv32-unknown-elf-size build/lb2002_hello.elf # 输出示例 # text data bss dec hex filename # 12480 256 1024 13760 35c0 build/lb2002_hello.elf其中text12480字节必须小于ROM容量64KB65536字节bss1024字节必须小于SRAM容量128KB。更关键的是用objdump查看实际地址分布rv32-unknown-elf-objdump -h build/lb2002_hello.elf | grep -E (text|data|bss) # 输出 # Sections: # Idx Name Size VMA LMA File off Algn # 2 .text 000030c0 00000000 00000000 00001000 2**2 # 3 .data 00000100 20000000 000030c0 000040c0 2**2 # 4 .bss 00000400 20000100 20000100 000041c0 2**2看到.text的VMAVirtual Memory Address是0x00000000LMALoad Memory Address也是0x00000000说明代码存放在ROM起始地址而.data的VMA是0x20000000SRAM起始LMA是0x000030c0ROM中存储位置印证了“ROM存储、RAM运行”的设计。如果.text的VMA超出0x0000ffff编译就会失败此时需检查linker_script.ld中rom区域长度是否被误写为0x000010004KB。4. 编译与烧录全流程从源码到芯片亮灯的七步操作4.1 Makefile关键参数详解LB2002 SDK的Makefile不是通用模板它硬编码了芯片特有参数。以下是必须修改的四行# 第17行指定芯片型号影响外设寄存器定义 CHIP_MODEL ? LB2002 # 第25行指定优化等级-Os平衡大小与速度-O2可能导致栈溢出 OPTIMIZATION ? -Os # 第42行指定Flash页大小LB2002为2KB写错会导致烧录失败 FLASH_PAGE_SIZE ? 2048 # 第68行指定签名密钥路径首次编译必须生成 SIGN_KEY_PATH ? $(BLUETOOTH_SDK_PATH)/tools/private_key.pem特别注意OPTIMIZATION参数LB2002的SRAM只有128KB但协议栈本身占用约85KB留给应用的栈空间不足20KB。用-O2优化会使函数内联增多局部变量分配更激进极易触发栈溢出。我实测过一个含三层递归调用的printf函数在-O2下导致HardFault_Handler换成-Os后稳定运行。因此永远不要修改此参数除非你已用arm-none-eabi-gcc的-fstack-usage选项分析过所有函数的栈消耗。4.2 编译命令执行与中间文件生成进入SDK根目录后执行标准三步编译# 清理旧文件重要避免.o文件残留导致链接错误 make clean # 生成依赖关系自动解析头文件包含链 make dep # 执行编译生成.elf、.bin、.hex三种格式 make allmake all成功后build/目录下会生成lb2002_hello.elf带调试信息的可执行文件用于GDB调试lb2002_hello.bin纯二进制镜像供烧录工具使用lb2002_hello.hexIntel HEX格式可用于部分编程器lb2002_hello.map内存映射文件记录每个符号地址调试必查。其中lb2002_hello.map是诊断内存问题的黄金文件。打开它搜索_stack_end你会看到类似.stack 0x2001fffc 0x4 load address 0x000041c0 0x2001fffc _stack_end .这表示栈顶地址是0x2001fffc而SRAM结束地址是0x2001ffff128KB0x200000000x00020000说明栈空间剩余4字节——显然不够用此时需在platform/lb2002/system_lb2002.c中修改#define STACK_SIZE 0x1000为0x2000然后重新make clean make all。4.3 固件签名与安全启动机制LB2002芯片支持安全启动Secure Boot要求所有固件必须用私钥签名否则ROM Bootloader拒绝执行。中科蓝讯提供signature_tool.py工具完成此步骤cd tools/ python3 signature_tool.py \ --input ../build/lb2002_hello.bin \ --output ../build/lb2002_hello_signed.bin \ --key private_key.pem \ --cert public_cert.der该工具会在固件头部添加256字节签名区包含RSA-2048签名、证书哈希、固件长度等字段。签名后的固件大小比原文件大256字节因此必须确保text段编译后仍小于64KB-256B。如果签名失败提示Signature verification failed通常是private_key.pem权限问题执行chmod 600 private_key.pem即可。实操心得首次使用必须生成密钥对。运行openssl genrsa -out private_key.pem 2048生成私钥再用openssl rsa -in private_key.pem -pubout -outform DER -out public_cert.der导出公钥证书。中科蓝讯的Bootloader出厂时已烧录公钥哈希因此你生成的证书必须与此哈希匹配否则芯片无法启动。4.4 烧录工具lb2002_flash_tool.py的参数玄机官方烧录工具lb2002_flash_tool.py有六个关键参数其中三个极易填错python3 lb2002_flash_tool.py \ --port /dev/ttyUSB0 \ # JTAG调试器串口号Linux下用ls /dev/tty*查 --baud 115200 \ # 串口波特率必须与SDK中HCI_UART_BAUDRATE一致 --addr 0x00000000 \ # 烧录起始地址LB2002固定为0x00000000 --file ../build/lb2002_hello_signed.bin \ # 必须是签名后固件 --erase-all \ # 强制擦除整个Flash首次烧录必须加 --verify # 烧录后校验强烈建议开启最容易忽略的是--erase-all参数。LB2002的Flash擦除单位是扇区Sector每个扇区4KB。如果不加此参数工具默认只擦除所需扇区但旧固件的中断向量表可能残留导致新固件启动时跳转到错误地址。我曾因此遇到“烧录成功但LED不亮”的问题用逻辑分析仪抓取复位信号发现芯片在0x00000004地址取指本应是复位向量最终查明是旧固件的.text段未被完全擦除。4.5 烧录失败的七种典型现象与现场诊断烧录过程并非一帆风顺以下是我在实验室记录的真实故障案例现象终端输出根本原因解决方案Error: Failed to connect to targetOpenOCD报错JTAG线序接反TCK/TMS互换用万用表测量开发板JTAG排针对照Datasheet Pinout重接Warning: Cannot query flash infolb2002_flash_tool.py警告Flash控制器未初始化在main.c开头添加FLASH_Unlock()调用Verify failed at address 0x00001234校验失败USB供电不足导致Flash写入不稳定改用带外部供电的USB Hub或更换USB线缆Device not responding after reset烧录后无反应签名密钥与Bootloader公钥不匹配重新生成密钥对确保public_cert.der被正确烧录到BootloaderLED blinks once then stops硬件现象BLE_ProcessEvents()未被调用检查while(1)循环内是否遗漏此函数UART prints garbage串口输出乱码SystemInit()中HSE配置错误查system_lb2002.c确认RCC_HSEConfig(RCC_HSE_ON)后调用RCC_WaitForHSEStartUp()GDB连接后立即断开GDB报Target disconnectedOpenOCD配置文件中reset_config参数错误修改target/lb2002.cfg将reset_config trst_and_srst改为reset_config srst_only最棘手的是第七种情况GDB调试时连接几秒后自动断开。我用示波器测量NRST引脚发现OpenOCD发送reset halt命令后NRST信号只拉低100ns远低于LB2002要求的最小2us。根源在于target/lb2002.cfg文件中reset_config参数设置不当将trst_and_srst改为srst_only后NRST拉低时间延长至5us问题解决。5. 调试与问题排查GDBOpenOCD的实战技巧5.1 GDB调试会话的标准流程烧录成功后用GDB进行动态调试是定位深层问题的唯一途径。标准流程如下# 步骤1后台启动OpenOCD监听3333端口 openocd -f interface/stlink-v2.cfg -f target/lb2002.cfg # 步骤2启动GDB客户端加载符号文件 rv32-unknown-elf-gdb build/lb2002_hello.elf # 步骤3在GDB中执行调试命令 (gdb) target remote :3333 # 连接OpenOCD (gdb) monitor reset halt # 复位并暂停CPU (gdb) load # 下载程序到RAM (gdb) b main # 在main函数设断点 (gdb) c # 运行至断点 (gdb) info registers # 查看寄存器状态 (gdb) x/10xw 0x20000000 # 查看SRAM前10个字关键技巧在于monitor reset halt命令它发送JTAG指令让芯片复位并立即暂停此时所有寄存器处于复位值。如果跳过此步直接load程序可能被加载到错误地址。我曾因省略此步导致main()函数被加载到0x20000000而非0x20001000GDB单步时PC指针乱跳。5.2 利用map文件定位HardFault异常当固件运行崩溃触发HardFault时GDB只能显示Program received signal SIGTRAP, Trace/breakpoint trap.无法指出具体原因。此时需结合lb2002_hello.map文件反向定位在GDB中执行info registers记录mepcMachine Exception Program Counter寄存器值例如0x20001a3c打开lb2002_hello.map搜索0x20001a3c找到对应函数名例如.text.main 0x20001a00 0x2a4 build/src/main.o计算偏移量0x20001a3c - 0x20001a00 0x3c60字节用objdump反汇编该函数rv32-unknown-elf-objdump -d build/lb2002_hello.elf | grep -A 20 main:找到第60字节处的汇编指令通常就是出问题的C代码行。我曾用此法定位到GPIO_ToggleBits()函数中因GPIOA_BASE宏定义错误被误写为0x40010800而非0x40010800导致写入非法地址触发总线错误。5.3 串口日志的底层实现原理LB2002 SDK的printf函数不是标准库实现而是重定向到HCI UART。其原理是在platform/lb2002/usart.c中__io_putchar()函数被定义为int __io_putchar(int ch) { while(!USART_GetFlagStatus(USART1, USART_FLAG_TC)); // 等待发送完成 USART_SendData(USART1, (uint8_t)ch); return ch; }这意味着每次printf(Hello\n)都会阻塞CPU直到所有字符发送完毕。在BLE协议栈运行时这种阻塞会导致HCI事件丢失。解决方案是启用DMA发送在SystemInit()后添加USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE)并将__io_putchar()改为非阻塞版本。但要注意DMA缓冲区大小必须大于最长日志字符串否则会覆盖未发送数据。5.4 常见问题速查表从现象到根因的映射以下是我整理的高频问题排查清单按发生概率排序问题现象可能根因快速验证方法终极解决方案烧录后LED完全不亮ROM Bootloader拒绝执行固件用逻辑分析仪抓取复位后前10us的地址线确认是否从0x00000000取指检查固件签名是否正确signature_tool.py输出是否显示Signature OK手机扫描不到设备BLE协议栈未启动在GDB中b BLE_StackInit确认是否执行到bt_controller_init()返回检查platform/lb2002/system_lb2002.c中RCC_EnableAPB1PeriphClk(RCC_APB1PERIPH_BT)是否被调用串口输出乱码且波特率不准系统时钟配置错误用示波器测量USART1_TX引脚计算实际波特率在SystemInit()中确认RCC_PLLConfig(RCC_PLLSOURCE_HSE, RCC_PLL_MUL6)参数正确HSE24MHz→PLL144MHz再经分频得48MHzBLE_ProcessEvents()返回后立即死机堆栈溢出在GDB中x/10xw 0x2001fff0查看栈顶附近是否被写入非零值增大STACK_SIZE宏定义在platform/lb2002/system_lb2002.c中改为0x4000烧录工具报Permission denied串口设备权限不足执行ls -l /dev/ttyUSB0确认当前用户是否在dialout组sudo usermod -a -G dialout $USER然后重启终端最后分享一个小技巧LB2002芯片的复位引脚NRST对静电极其敏感。我在南方潮湿天气下调试时手指触碰开发板金属外壳后立即烧录失败用万用表测量NRST引脚电压发现被拉低至0.2V。解决方案是佩戴防静电手环或在开发板NRST引脚并联一个100nF电容到GND滤除高频干扰。这个细节在任何官方文档里都找不到却是量产调试中每天都要面对的真实挑战。
企业数字化 ERP 产品动态
相关推荐
逆变器并联环流怎么治?从成因到五种抑制方案全解析 /* 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:34:24
OpenGL安装包不是包:GLFW+GLEW配置、避坑与验证 /* 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:34:24
Cadence Allegro 17.4中Capture CIS的核心作用与数据驱动设计 /* 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:34:18
bin转txt工具:十六进制转储与结构化解析实战 简介:这是一款面向软件开发者、数据分析师与系统管理员的二进制转文本实用工具,专门解决bin文件难以直接阅读与解析的问题。工具基于Visual Studio 2010开发,支持处理任意大小的bin文件,可将原始字节数据解码为可读文本或十六进制… · 2026/9/25 2:17:28
华为悦盒EC6108V9刷机指南:海思Hi3798M从强刷到精简与自动装应用 /* 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 2:17:28
PN532实战:I2C、SPI、HSU三接口通信与调试全攻略 /* 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 2:17:22
IronClaw 网关操作链路的可测性:gateway-traces 确定性回放夹具全解析 人工智能AI 应用交互助手AI Agent 【免费下载链接】ironclaw IronClaw is an Agent OS focused on privacy, security and extensibility 项目地址: https://gitcode.com/gh_mirrors/iro/ironclaw 点击查看 免费下载 本文围绕 IronClaw 仓库中的 tests/fixtures/ga… · 2026/9/25 2:17:16
创维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