news 2026/11/4 6:02:25

STM32+NRF24L01+DHT11无线温湿度采集系统实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32+NRF24L01+DHT11无线温湿度采集系统实战解析

做过几年嵌入式开发的人,大概率都绕不开两块东西:一块是 STM32,另一块就是 2.4G 无线模块。我之前用 STM32F103VE 配合 NRF24L01 和 DHT11 做过一套无线温湿度采集系统,说实话,这套组合在工业和 HVAC 场景里非常典型,比如机房温湿度监控、实验室环境记录、冷冻仓库货架温度巡检,甚至一些老式空调项目的改造预留,都能看到类似方案。整套系统看着简单——DHT11 采温湿度,STM32 通过 SPI 把数据喂给 NRF24L01,接收端再把数据还原出来——但真跑起来,丢包、误码、湿度偶尔读出来是 0%、从机莫名复位,问题一个接一个。这篇想把完整的工程经验做一次整理,从器件选型、电路连接、驱动编写、无线链路调试,到最后的常见问题排查,尽量写得实在一些。如果你正打算用 STM32F103VE 做低成本无线温湿度采集,或者纯粹想入门 2.4GHz 无线通信的实战项目,这套方案值得参考。

1. 方案解析:STM32F103VE、NRF24L01、DHT11 为什么能搭在一起

1.1 三个核心器件的分工与选型理由

STM32F103VE 属于增强型系列,Cortex-M3 内核,512KB Flash、64KB SRAM,资源对这个项目来说其实很富余。你可能会问,既然资源这么足,为什么还要选它?我的看法是,这套方案的核心价值不在“算”,而在“管”——管 SPI 时序、管单总线时序、管无线重发逻辑。F103VE 的 SPI 主模式最高能跑到 18Mbps,GPIO 翻转速度也够快,用来模拟 DHT11 的单总线读取完全没有压力。更重要的是它 3.3V 供电,和 NRF24L01、DHT11 在同一条电源轨上,不用做电平转换,简化了电路设计。

NRF24L01 是 Nordic 的 2.4GHz 收发芯片,市面上最常见的模块是带 PA+LNA 的版本和裸片版。这个项目里用裸片版就够了,它的特点是:支持 250Kbps、1Mbps、2Mbps 三档速率,6 路数据管道,内置自动应答和自动重发,通信距离在空旷环境下 30 米左右没问题。工业 HVAC 场景中,只要不是穿过好几层钢筋混凝土,这个距离基本够用。DHT11 则是典型的单总线数字温湿度传感器,精度±2°C、±5%RH,放在对精度要求不高的监控场合很合适,而且价格低、引脚少,焊起来也省事。

有人可能会问,为什么不用 DHT22 或者 SHT30?如果预算允许,SHT30 当然更好,I2C 接口、精度高、抗干扰强,但它的单价是 DHT11 的好几倍。DHT11 的定位就是“够用且便宜”,在很多工业现场改造项目里,老板要的是能看出趋势、能报警,不是做计量级测量。所以选型这个东西,本质上是个 trade-off,别盲目追高。

器件接口精度供电典型成本适合场景
DHT11单总线±2°C / ±5%RH3.3V~5.5V很低环境趋势监测、低成本采集
DHT22单总线±0.5°C / ±2%RH3.3V~5.5V中等对湿度精度有一定要求
SHT30I2C±0.3°C / ±2%RH3.3V~5.5V较高计量级、长期稳定性要求高

1.2 系统拓扑与数据流设计

整套系统的数据流并不复杂:发送端 STM32F103VE 定时读取 DHT11,得到 40 位温湿度数据,经过简单校验后封装成协议帧,通过 SPI 写入 NRF24L01 的 TX FIFO,然后拉高 CE 引脚触发发送。NRF24L01 发送后等待接收端自动应答,如果没收到 ACK,模块内部按预设的重发次数和间隔自动重传。接收端 NRF24L01 工作在 RX 模式,收到数据后通过 SPI 把 Payload 读出来,再经串口打印或者驱动 LCD 显示。

这里面有个设计点容易忽略:发送端和接收端的角色不是固定死的。NRF24L01 支持收发模式切换,所以这套系统完全可以让一块板子既做采集又做中继,或者主机轮询多个从机。工业 HVAC 场景里,我见过不少设计是把接收端做成网关,转发到 RS485 总线或者以太网,但底层无线采集节点还是这套方案。所以我建议一开始写驱动时就别把收发逻辑焊死,留出模式切换的接口,后面扩展会省很多事。

2. 硬件搭接细节:电源、SPI、单总线,每一根线都有讲究

2.1 引脚分配与连接关系

STM32F103VE 的 SPI1 可以挂在 PA5(SCK)、PA6(MISO)、PA7(MOSI),片选用 PA4,CE 我习惯用 PA3。DHT11 的数据线放在 PB0,串口调试用 USART1。这样分配的思路是:SPI1 和 DHT11 数据线分属不同端口,改线时互不干扰,调试时可以单独测 SPI 回环,也可以单独测 DHT11 波形,问题定位快。

信号STM32F103VE 引脚外设引脚
SPI1_SCKPA5NRF24L01 SCK
SPI1_MISOPA6NRF24L01 MISO
SPI1_MOSIPA7NRF24L01 MOSI
SPI1_CSNPA4NRF24L01 CSN
CEPA3NRF24L01 CE
DHT11_DATAPB0DHT11 DATA
USART1_TX/RXPA9/PA10调试串口

NRF24L01 的 IRQ 引脚我建议接一个空闲 GPIO,不要悬空。虽然可以用查询方式读状态寄存器来判断发送是否完成,但遇到自动重发正在频繁进行时,查询会占用大量 CPU 时间,用外部中断或者轮询 IRQ 的方式更高效。IRQ 可以放到 PB1,配置成下降沿触发。这里的下降沿是 NRF24L01 在事件发生时主动拉低的,和很多模块的高电平触发不一样,我第一次用的时候反了,结果中断风暴直接把系统卡死,后面会细说。

2.2 电源完整性是无线模块的命门

NRF24L01 的电源质量直接影响发射功率和接收灵敏度,这一点很多人栽过跟头。模块正常工作电流在发射模式下可能到 13mA 左右,但内部 PA 启动瞬间会有比较陡的电流变化。如果直接用 STM32 板载的 3.3V LDO 供电,且 LDO 输入输出压差不够或者滤波电容不足,很容易出现发射瞬间电压跌落,导致无线模块复位或者数据错乱。

我的做法是在 NRF24L01 的 VCC 引脚附近就近放一个 10uF 的钽电容和一个 0.1uF 的陶瓷电容,两者并联。钽电容负责瞬态电流,陶瓷电容负责高频去耦。如果你用的是带 PA+ LNA 的 NRF24L01 模块,电流需求会更大,建议单独用一颗 AMS1117-3.3 从 5V 取电,不要和 MCU 共用 LDO。这个细节直接影响通信稳定性,尤其当传输距离拉远后,发射功率增大,掉压问题会成倍放大。

DHT11 的供电是 3.3V 到 5.5V 都能工作,但注意,DHT11 的数据线输出高电平是跟着 VCC 走的,也就是说如果你用 5V 给 DHT11 供电,那它数据线的高电平就是 5V,STM32F103VE 的 GPIO 是 5V 容忍,但 NRF24L01 不是。为了避免电平打架,我建议整套系统统一用 3.3V 供电,DHT11 也接 3.3V,省去电平转换的麻烦。

2.3 DHT11 上拉电阻与布线注意事项

DHT11 是单总线协议,数据线在空闲时被上拉到高电平。STM32 的 GPIO 内部有上拉,但内部上拉太弱,加上导线本身的寄生电容,会导致上升沿变缓,严重时主机读到的“1”电平不够高,出现误码。我实测下来,DHT11 数据线上最好外接一个 4.7kΩ 或者 10kΩ 的上拉电阻到 3.3V。如果你的 DHT11 模块已经带了上拉电阻,那就不用再加,但要确认一下阻值,有些模块用的是 1kΩ,也问题不大,只是稍微费一点电。

布线方面,DHT11 的线越短越好。我之前做过一次实验,把 DHT11 用 30 厘米的杜邦线接到板子上,读取成功率急剧下降,波形上能看到上冲和下冲。原因很简单:DHT11 的时序是微秒级的,线太长加上分布电容和电感,会直接把边沿搞坏。工业现场如果传感器必须拉远,我建议用带屏蔽的双绞线,屏蔽层单端接地,或者干脆改用 I2C 接口的 SHT30,抗干扰能力完全不是一个级别。

3. 软件驱动:SPI 配置、单总线时序、无线重发,一个都不能少

3.1 基于标准库的 SPI 初始化和 NRF24L01 寄存器配置

STM32F103VE 的 SPI1 初始化,我用的是标准外设库,时钟源选 PCLK2 的二分频,配置成主模式、8 位数据、CPOL=0、CPHA=0。NRF24L01 对 SPI 时序要求不挑剔,4MHz 以内的 SCK 都能稳定跑,我习惯把 SPI 分频配成 4 分频,也就是 9MHz,留一点裕量。要注意的一点是,NRF24L01 的 CSN 片选信号在每次 SPI 事务之前必须拉低,事务结束后拉高,且 CSN 低电平期间 SCK 的跳变不能有毛刺,否则很容易把模块的状态机搞乱。

初始化 NRF24L01 的寄存器顺序有讲究。我的经验是先关 CE,让模块进入待机模式,然后按顺序设置 EN_AA、EN_RXADDR、SETUP_AW、SETUP_RETR、RF_CH、RF_SETUP,接着写发送地址和接收地址,再写 RX_PW_P0 设置接收载荷宽度,最后清空 FIFO 并重新拉高 CE。这里有个容易踩的坑:EN_AA 和 EN_RXADDR 如果不匹配,自动应答功能会失效。比如你只使能了数据管道 0,但 EN_AA 的对应位写错了,模块收发数据时不会产生 ACK,发送端会一直重发直到超时。

void NRF24L01_Init(void) { uint8_t buf[5]; NRF24L01_CE_LOW(); // 写配置寄存器,屏蔽中断,开启 CRC,使能收发器 NRF24L01_WriteReg(REG_CONFIG, 0x0E); // 使能自动应答,只针对数据管道0 NRF24L01_WriteReg(REG_EN_AA, 0x01); // 使能接收地址,只使能数据管道0 NRF24L01_WriteReg(REG_EN_RXADDR, 0x01); // 设置地址宽度为5字节 NRF24L01_WriteReg(REG_SETUP_AW, 0x03); // 自动重发:延时1500us,重发10次 NRF24L01_WriteReg(REG_SETUP_RETR, 0x1A); // 选择射频通道 40(2400+40 = 2440MHz) NRF24L01_WriteReg(REG_RF_CH, 40); // 发射速率2Mbps,发射功率0dBm NRF24L01_WriteReg(REG_RF_SETUP, 0x0E); // 发送地址 buf[0] = 0xE7; buf[1] = 0xE7; buf[2] = 0xE7; buf[3] = 0xE7; buf[4] = 0xE7; NRF24L01_WriteReg(REG_TX_ADDR, buf, 5); // 接收地址与发送地址一致 NRF24L01_WriteReg(REG_RX_ADDR_P0, buf, 5); // 数据管道0有效数据宽度为8字节(温湿度帧) NRF24L01_WriteReg(REG_RX_PW_P0, 8); // 清空发送和接收FIFO NRF24L01_FlushTx(); NRF24L01_FlushRx(); NRF24L01_CE_HIGH(); }

上面这几个参数不是随便写的。RF_CH 用了 40,对应 2440MHz,这个频点在实际测试中干扰相对较少。如果你所在的现场有其他 2.4G 设备,比如 Wi-Fi 或者无线键鼠,最好换个信道。RF_SETUP 里的 2Mbps 速率是我有意选择的,速率越高,单位比特在空中停留时间越短,抗干扰能力反而越好,代价是接收灵敏度会低 2-3dBm。室内场景优先选 2Mbps,室外空旷场景可以降成 1Mbps 换更远距离。

3.2 DHT11 单总线读取时序的实现要点

DHT11 的通信协议是单总线,主机先拉低数据线至少 18ms,然后释放并延时等待传感器响应。传感器响应后拉低 80us 再拉高 80us,随后输出 40 位数据,每位数据以 50us 低电平开始,之后的高电平持续 26-28us 表示“0”,持续 70us 左右表示“1”。读取的关键在时延精度,STM32F103VE 主频 72MHz,使用循环延时或者 SysTick 延时都可以,但注意别用带操作系统的延时函数,DHT11 的时序容差只允许几十微秒的抖动。

我写 DHT11 驱动时,更倾向于用 GPIO 读取和简单的循环延时,而不是依赖定时器捕获。原因在于单总线时序中高低电平的翻转很快,定时器捕获虽然准确,但代码复杂,中断频繁,反而可能干扰其他逻辑。简单的方式是:先用一个函数读出当前电平,然后短暂延时再读下一个状态。实测下来,只要延时函数本身校准过,并且没有跑操作系统,读出 40 位的成功率可以做到 95% 以上。

uint8_t DHT11_ReadByte(void) { uint8_t i, data = 0; for (i = 0; i < 8; i++) { while (DHT11_DATA_IN() == 0); // 等待50us低电平结束 DelayUs(30); if (DHT11_DATA_IN() == 1) { data |= (1 << (7 - i)); while (DHT11_DATA_IN() == 1); // 等待高电平结束 } } return data; }

这段代码里有几个细节值得说。第一个细节是读取函数里的 while 等待,虽然能保证时序对齐,但如果传感器故障或者掉线,while 循环会死等,程序直接卡死。所以我在实际项目里加了超时判断,用 SysTick 计数,超过 100us 就退出并返回错误标志。第二个细节是每个字节先读高位,DHT11 输出的数据格式是湿度整数、湿度小数、温度整数、温度小数、校验和,其中小数部分在 DHT11 上固定为 0,但你还是得按协议读完,因为校验和是包含这 4 个字节的。第三个细节是读取完成后要检查校验和,即前四个字节相加的低 8 位是否等于第五个字节,不相等就丢弃本次数据,留到下一轮再采。

uint8_t DHT11_ReadData(int16_t *temp, uint8_t *humidity) { uint8_t buf[5]; uint8_t i; uint16_t sum; DHT11_DATA_OUT(); DHT11_DATA_LOW(); DelayMs(20); // 起始信号,大于18ms DHT11_DATA_IN(); DelayUs(40); // 释放总线后等待响应 if (DHT11_DATA_IN() == 0) { while (DHT11_DATA_IN() == 0); // 等待80us低电平结束 while (DHT11_DATA_IN() == 1); // 等待80us高电平结束 for (i = 0; i < 5; i++) { buf[i] = DHT11_ReadByte(); } sum = buf[0] + buf[1] + buf[2] + buf[3]; if ((sum & 0xFF) != buf[4]) { return 0; // 校验失败 } *humidity = buf[0]; *temp = buf[2]; return 1; } return 0; }

这里还有个容易被忽略的点:DHT11 的读数不是持续更新的。手册上写的典型采样周期是 1-2 秒,如果你采集间隔小于 1 秒,读出来的数据大概率是上一次缓存的值。所以我会在主循环里用 2 秒的软件定时器去触发读取,既省电又保证数据有效性。

3.3 无线数据包的封装与自动重发机制

数据在空中不是裸传的,我定义了一个 8 字节的协议帧,结构是:帧头 0xAA、节点 ID、温度高字节、温度低字节、湿度字节、状态字节、校验和、帧尾 0x55。温度用 int16 表示,带符号位,这样负温度也能表达。状态字节记录发送端自检状态,比如 DHT11 是否读失败、电池电压是否低。

发送端在每次发送前,先清空 RX FIFO,再写 TX FIFO,拉高 CE 至少 10us 触发发送。发送完成后读取 STATUS 寄存器,如果 TX_DS 位置位,说明收到 ACK,发送成功;如果 MAX_RT 位置位,说明自动重发超过了设定次数,需要做异常处理。这里我强烈建议每次发送后都手动清一下 STATUS 寄存器,否则中断标志会一直挂在那里,干扰下一次发送。

uint8_t NRF24L01_SendPacket(uint8_t *data, uint8_t len) { uint8_t status; NRF24L01_CE_LOW(); NRF24L01_FlushTx(); NRF24L01_WritePayload(data, len); NRF24L01_CE_HIGH(); DelayUs(15); NRF24L01_CE_LOW(); while (NRF24L01_IRQ_PIN() != 0); // 等待IRQ拉低 status = NRF24L01_ReadReg(REG_STATUS); NRF24L01_WriteReg(REG_STATUS, status); // 清状态标志 if (status & (1 << 4)) // MAX_RT { NRF24L01_FlushTx(); return 2; // 重发超时 } if (status & (1 << 5)) // TX_DS { return 1; // 发送成功 } return 0; }

这个函数里用了一个 while 等 IRQ 引脚拉低,同样有死等风险。实际使用中我给这个循环加了一个超时计数,超过 50ms 就退出并返回超时,避免无线模块异常时把系统挂死。重发次数和重发延时这两个参数也值得讲一下:SETUP_RETR 寄存器低 4 位是重发次数,高 4 位是重发延时。延时设得越长,总耗时越高,但两个模块之间冲突的概率也越小。我的配置是 1500us 延时、重发 10 次,在大多数室内环境下足够可靠。

接收端的处理相对简单,NRF24L01 工作在 RX 模式,CE 拉高后持续监听。收到数据后 RX_DR 中断标志置位,读取 RX FIFO 即可。如果是多节点轮询,接收端可以通过数据帧里的节点 ID 字段区分数据来源,把不同节点的温湿度分别缓存。

4. 联调实录:丢包、乱码、DHT11 读不到,我是怎么一步步排查的

4.1 无线丢包与 ACK 超时的处理

第一次联调时,我遇到的现象是发送端每隔几秒报一次 MAX_RT 超时,接收端收到的数据有明显缺口。排查过程花了整整一个下午,最后发现原因有两个。第一个原因是发送地址和接收地址不匹配。NRF24L01 的地址是 5 字节,我收发两端写地址时,一个高位在前、一个低位在前,虽然看起来写的内容一样,但实际在空中接收时地址完全不同。这个问题用逻辑分析仪看 SPI 波形才能发现,普通串口打印根本看不出来。

第二个原因是接收端的 Rx 模式还没真正建立,发送端就开始发数据了。NRF24L01 从 TX 模式切到 RX 模式需要一点时间,如果切换太频繁,模块的状态机可能来不及稳定。解决方法是把模式切换函数做得干净利落,先 CE 拉低,等待 SPI 写完成,再写 CONFIG 寄存器切换 PWR_UP 和 PRIM_RX 位,最后 CE 拉高,整个过程不要插入其他 SPI 操作。

4.2 DHT11 读取失败:起始信号、上拉电阻、干扰源

DHT11 读取失败的问题更普遍。我遇到过三种典型情况:第一种是起始信号时间不够,我最初用 DelayMs(18),但因为代码里其他中断干扰,实际延时可能被压缩到 10ms 左右,传感器不响应。后来我把起始信号延长到 25ms,同时关掉了不必要的定时器中断,读取成功率立刻上来了。第二种是数据线上的上拉电阻太小,模块上没有配电阻,我用 1kΩ 上拉后,波形上的高电平被拉得不够高,STM32 读到错误电平。换回 4.7kΩ 就好了。第三种是来自 2.4G 模块的干扰,NRF24L01 的天线离 DHT11 太近,发射瞬间的高频能量耦合到数据线上,导致 DHT11 读取失败。解决办法是把天线朝向另一侧,或者在 DHT11 数据线上串联一个几百欧的电阻来抑制干扰。

4.3 通信距离不足与天线布局的实测

工业现场对无线距离的要求往往比实验室苛刻。我实测过裸片 NRF24L01 在 2Mbps 速率下,室内穿一堵墙的距离大概是 10-15 米,室外空旷能到 30 米左右。如果你需要更远距离,可以考虑带 PA 的模块,或者降低速率到 250Kbps。但要注意,带 PA 的模块对电源要求更高,而且辐射功率大,在某些应用场景里可能会有合规问题,选型时得想清楚。天线布局上,我建议 NRF24L01 的天线区域(一般是 PCB 板边那个蛇形走线区域)不能有覆铜,也不能被塑料外壳完全包死。有些外壳开模避开了天线位置,但把天线贴在金属板上,通信距离直接减半,这种坑最好在设计阶段就规避。

5. 常见问题速查与几个值得长期保留的优化习惯

5.1 问题排查速查表

现象可能原因排查思路与解决办法
发送端一直 MAX_RT收发地址不匹配、信道不一致、接收端未进入 RX检查地址字节序、RF_CH 是否一致,用示波器看 CE 和 IRQ 波形
接收端收不到数据但有 ACK接收地址错误或 RX_PW_P0 设置不对核对 RX_ADDR_P0 与发送端 TX_ADDR,确认载荷宽度
DHT11 读取超时起始信号太短、上拉电阻不对、传感器损坏延长起始信号到 25ms,检查上拉阻值,最直接的办法是换一颗传感器
湿度读到 0% 或温度乱跳校验和不对、线太长、干扰大加校验失败重读机制,缩短单总线线长,尝试串电阻
通信距离明显变短天线被遮挡、电源电压跌落、速率太高检查天线区域,用独立 LDO 供电,降低速率至 1Mbps 或 250Kbps
模块发热且不休眠CE 引脚悬空或电平不对确保 CE 有确定的 GPIO 控制,待机时 CE 拉低

5.2 我个人的三条经验

这套系统跑通之后,我留了几个习惯,虽然不起眼,但相当管用。第一个习惯是在每一包数据里都带一个自增序列号。接收端拿到序列号后就能计算丢包率,这个指标在判断无线链路健康度时比单纯的 RSSI 更直观。第二个习惯是定时重启 NRF24L01,因为模块长时间运行后偶尔会进入无法收发数据的状态,简单粗暴地在定时器里检测通信失败次数,超过阈值就重新初始化模块,代价是几毫秒的复位时间,换来的是系统长期稳定。第三个习惯是接收端把数据同时打到串口和本地 Flash,这样即使上位机断开,数据也不会丢。

最后再分享一个小技巧:联调时先不要同时上 DHT11 和 NRF24L01,先把 STM32 的 SPI 用回环测试跑通——MISO 和 MOSI 短接,发一个字节看能不能收到。SPI 没问题之后,再接 NRF24L01,这时如果有问题,问题一定出在无线配置,而不是 SPI 时序。同样的逻辑,DHT11 可以先单独读,把数据打印到串口,确认读数稳定后再把两个模块整合到一起。这样做的好处是,每次只引入一个新变量,遇到问题五分钟就能定位。这套项目如果只是搭个 Demo,可能一天就能跑通,但要做到长期稳定运行,建议你还是按这个思路把每个环节都验证一遍。

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

嵌入式内存管理:栈溢出、内存泄露与堆碎片实战解析

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

作者头像 李华
网站建设 2026/11/4 6:02:00

工业操作系统进阶逻辑:从实验室预研到装备现场稳定落地

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

作者头像 李华
网站建设 2026/11/4 6:01:56

算力机房传感器供电链路实战:压降、PoE与DC-DC方案

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

作者头像 李华
网站建设 2026/11/4 6:01:41

基于ADS122U04与PIC32的高精度工业温度采集系统设计

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

作者头像 李华
网站建设 2026/11/4 6:00:40

工业级温度测量前端设计:ADS122U04+TM4C123闭环信号链实战

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

作者头像 李华
网站建设 2026/11/4 6:00:38

MIPI与SerDes协同原理:从协议栈到物理层的高速链路设计

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

作者头像 李华