news 2026/10/16 1:06:07

DS18B20单总线测温与数码管动态显示:单片机实战全解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DS18B20单总线测温与数码管动态显示:单片机实战全解

搞单片机开发的人,十有八九会碰这个组合:DS18B20温度传感器,把温度读出来,再用数码管显示。看起来特别基础,但我在实际带项目调试时发现,越基础的项目越容易翻车——时序差几个微秒,就读不到存在脉冲;数码管扫描频率不够,拍出来的照片全是数值重影;更别提负数温度的补码换算,很多人第一次做随便赋值,最后显示一个刺眼的负号或者乱码。

这个项目的本质是“单总线通信 + 时序控制 + 动态显示”三件事的集合。适合两类人来参考:一是刚学完C语言、想做一个完整单片机项目的入门开发者,把外设驱动、显示扫描、主循环调度一次打通;二是做产品预研的工程师,用这个方案快速验证温度采集和显示逻辑,后面扩展成多路采集、继电器联动都不难。

我下面按“方案设计 → 硬件电路 → 代码实现 → 调试排查”的顺序,把我在实际调试中踩过的坑、验证过的参数都整理出来,尽量让读者照着做就能跑通。

1. DS18B20项目设计思路:先想清楚这几件事

1.1 需求拆解:核心指标其实是两个

这个需求看起来只有一句话,其实隐含了两个核心指标。

第一个是测温范围与精度。DS18B20能在-55℃到+125℃内工作,在-10℃到+85℃之间的典型精度是±0.5℃。对大多数环境监测、设备热保护场景来说,这个精度完全够用。默认12位分辨率下,温度台阶是0.0625℃,读数本身足够细腻,真正要留意的是你准备用几位小数去显示。

第二个是显示实时性。温度变化是缓慢信号,不需要毫秒级刷新,但数码管必须用不低于50Hz的频率刷新,否则人眼会看到闪烁。这两个时间尺度天然矛盾——传感器转换一次温度要750ms,而数码管每位点亮只能维持几毫秒。处理不好,就会出现屏幕闪得厉害、温度却半天不变的效果。

所以这个项目的工程量不在“读温度”这个动作本身,而在把单总线协议吃透,以及把数码管刷得既稳定又均匀。

1.2 为什么选DS18B20而不是别的传感器

现在测温方案很多,热电偶、热敏电阻、I2C接口的数字传感器一大把。DS18B20能成为入门项目的“标准答案”,核心有三点。

其一,单总线通信,最少只占一个IO口。对比I2C需要两根线、SPI需要四根线,单总线把时钟和数据合成一根线,对引脚紧张的51单片机非常友好,省下来的IO可以做按键、控制继电器。

其二,数字输出,没有模拟信号处理的麻烦。热敏电阻要配分压电路、运放和ADC,还得做非线性校准;DS18B20直接把数字结果吐出来,换算公式也很简单,不依赖基准电压稳定性。

其三,单总线协议本身就值得学。你在项目里学会的高低电平时序、复位应答、读写时序,后面做单总线协议的其他器件都能复用。这也是我坚持建议不要用现成模块封装板、而是自己搭一次电路的原因——网上很多DS18B20一体模块内置了上拉电阻和滤波电容,插上就能用,方便归方便,但把底层时序藏起来之后,出了问题很难排查。

1.3 整体架构:数据是怎么流动的

整个系统的数据流向非常清晰:

DS18B20 → 单总线读取温度寄存器 → 换算成十进制温度值 → 拆分成十位/个位/小数位数字 → 查找段码表 → 动态扫描刷新数码管

主控我用51系列芯片来做示例,晶振常见11.0592MHz或12MHz,两者都可以,但延时函数必须按实际晶振重新核算。如果只是照搬网上的代码,时序差一点,温度数据就可能随机跳变。

显示方案有两种选择:一是用带驱动芯片的数码管模块,内部集成扫描逻辑,代码写起来很轻松,但学不到底层;二是用普通共阴/共阳数码管,IO直接驱动,自己写动态扫描。我强烈建议用第二种,因为动态扫描是单片机开发绕不开的核心技能,后面做点阵屏、LED灯带控制都能用上。

2. 硬件电路设计与接线:新手最爱在这里翻车

2.1 器件清单与选型要点

先把器件列出来,这个清单基本就是标准答案。

器件型号/规格数量作用
温度传感器DS18B20,TO-92封装1采集温度,输出数字量
主控芯片STC89C52或AT89C511运行单总线协议、驱动显示
数码管4位共阴,0.56英寸或0.5英寸1显示温度值
上拉电阻4.7kΩ1单总线空闲时拉高电平
限流电阻220Ω或330Ω8数码管段电流限制
滤波电容104,即0.1μF2DS18B20电源与主控电源滤波
其他面包板/洞洞板、杜邦线若干搭建电路

选型时最需要提醒的是:数码管共阴还是共阳,直接影响段码表。共阴的公共端接GND,要给某段高电平才会亮;共阳正好反过来,公共端接VCC,要给低电平才会亮。新手最常犯的错就是硬件选了共阳,代码却用共阴的段码表,结果数字显示得乱七八糟。

DS18B20有三种常见封装:TO-92、SOIC-8和MSOP-8,用得最多的是TO-92,外形和三极管一模一样。引脚从左到右分别是GND、DQ、VDD,注意不是两端的GND和VDD,DQ在中间。我见过有人把DQ和VDD接反,折腾一晚上都读不出来,最后发现是引脚顺序搞错。

2.2 上拉电阻取值:为什么一定是4.7kΩ

单总线协议要求总线空闲时必须保持高电平,但DS18B20的输出级是开漏结构,它自己只能把总线拉低,不能主动输出高电平。所以必须在DQ线上接一个上拉电阻到VCC,否则总线浮空,读出来的数据大概率是随机值。

阻值选取要平衡两个因素。

阻值太小,总线被拉低时电流偏大。按5V供电、4.7kΩ上拉计算,拉低时电流大约是1mA,功耗很低;如果换1kΩ,电流到5mA,虽然一时半会儿不会烧,但会给开漏输出级增加负担,时序长线线上压降也可能偏离预期。

阻值太大,上升沿变慢。单总线读时序要求主机释放总线后在15微秒内完成采样,如果上拉电阻超过10kΩ,再加上导线分布电容和芯片输入电容,上升沿就会变缓,采样时可能还没来得及升到高电平,就被误判成0。

工程上4.7kΩ是黄金值,3.3V和5V系统都能直接配。如果DQ线需要拉得很长,比如超过1米,可以考虑降到2.7kΩ,但单总线本来就不适合长距离传输,真要做长线采集,应该走RS485之类的差分方案,而不应该硬扛单总线。

2.3 数码管驱动电路设计

四位数码管有静态驱动和动态扫描两种接法。

静态驱动是每个数码管独立占一组IO,显示稳定、代码简单,但4位数码管要占32个IO,对51单片机来说太奢侈,通常要加译码器或移位寄存器才能玩。

动态扫描把所有数码管的段选线并联在一起,只把位选线分开,一次只点亮一位,靠快速轮换在视觉上形成“同时亮”的效果。IO占用只有8根段选线加4根位选线,一共12个IO,这也是我这次采用的方式。

限流电阻的放置位置有个细节值得强调:放在段选线上,而不是位选线上。因为动态扫描时任何时候只有一个位被点亮,同一时刻流过某一段的电流就是单个LED的电流;如果把限流电阻放在位选线上,该位同时点亮多段时,总电流就是多个LED电流之和,对单片机IO口压力很大。

限流电阻的取值按这个公式估算:

R = (VCC - LED压降) / LED工作电流

LED压降一般按1.8V到2.0V算,5V供电下想让工作电流在8mA左右,电阻取330Ω或390Ω都很常见。如果发现亮度不够,优先考虑提高每位点亮时长或者换高亮数码管,不要盲目减小限流电阻,否则LED老化快、功耗也难看。

3. 代码实现:DS18B20读温度、数码管显示全流程

3.1 单总线时序基础:复位、写、读

DS18B20所有操作都靠一根IO上的时序组合完成,基础操作只有四个:复位、写0、写1、读位。

先看复位。主机把总线拉低480到960微秒,然后释放。DS18B20检测到这个下降沿后,会在15到60微秒内把总线拉低60到240微秒,作为存在脉冲。主机释放总线后等大约60到70微秒再去检测电平,读到低电平说明传感器在线。这个过程也叫复位应答,相当于总线上的“握手”。

写时序是整个协议里最容易理解错的地方。想写0,就把总线拉低并保持整个时隙,一般60微秒,期间不释放;想写1,就把总线拉低1到15微秒后立刻释放,让上拉电阻把总线带回高电平。关键区别不是拉低时间长短,而是“拉低之后是否释放总线”,很多人把这两个动作搞反,写出来的数据就反了。

读时序对延时精度最敏感。主机先拉低总线1到15微秒,然后释放,在释放后的15微秒窗口内采样总线电平。传感器想传0,就会在这段时间内继续拉低总线;想传1,总线被上拉电阻保持高电平。这个15微秒的采样窗口非常窄,延时稍差就会读到错值。

3.2 DS18B20驱动代码:四个基础函数

我用STC89C52的C51环境做示例,DQ接P3.7。先定义一个IO口:

sbit DQ = P3^7; // DS18B20数据线

然后是基础延时函数。这里必须注意:不同的晶振、不同的编译优化等级,一个空循环的实际耗时差别很大,最好用示波器实测校准。下面这个版本面向12MHz晶振写:

void DelayUs(unsigned int us) { while (us--) { _nop_(); _nop_(); _nop_(); _nop_(); } }

复位与存在检测:

bit DS18B20_Reset(void) { bit presence; DQ = 0; DelayUs(480); // 拉低至少480us DQ = 1; // 释放总线,等待存在脉冲 DelayUs(70); // 等传感器应答 presence = DQ; // 读到0表示存在 DelayUs(250); // 把剩余时序走完 return presence; }

写字节:

void DS18B20_WriteByte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { DQ = 0; // 每个时隙先拉低 if (dat & 0x01) { DQ = 1; // 写1:短暂拉低后释放 DelayUs(60); } else { DelayUs(60); // 写0:全程保持低 } DQ = 1; DelayUs(1); // 时隙间隔 dat >>= 1; } }

读字节:

unsigned char DS18B20_ReadByte(void) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { DQ = 0; _nop_(); // 拉低约1us DQ = 1; // 释放总线 _nop_(); // 约1us后采样 dat >>= 1; if (DQ) { dat |= 0x80; } DelayUs(60); // 把这个时隙走完 } return dat; }

这四段代码是整套系统的地基。写完先别急着接数码管,直接用一个调试串口或者LED验证读取到的原始字节,确认能跑通再往下走。

3.3 温度采集与数值换算

DS18B20的读温流程分两步:先启动温度转换,再读取暂存器。

启动转换的命令序列:

DS18B20_Reset(); DS18B20_WriteByte(0xCC); // 跳过ROM,单设备时用 DS18B20_WriteByte(0x44); // 启动温度转换

0xCC是跳过ROM指令,意思是“不比对64位序列号,直接对总线上所有传感器操作”。只有单设备时可以这样偷懒,如果总线上挂了多个DS18B20,必须用0x55匹配ROM指令,先读出每个传感器的64位ROM序列号,再逐个点名。这个扩展后面单独说。

转换完成后,读取暂存器:

unsigned char low, high; int raw; float temp; DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读暂存器(9个字节) low = DS18B20_ReadByte(); // 温度低字节 high = DS18B20_ReadByte(); // 温度高字节

温度换算的核心逻辑在这里:

raw = (high << 8) | low; // 12位分辨率下,低4位是小数位 // 判断负数:高字节的高5位全是1 if (raw & 0x8000) { raw = ~raw + 1; // 取补码的绝对值 temp = raw / 16.0; return -temp; // 返回负温度 } return raw / 16.0;

默认12位分辨率时,原始值除以16就是摄氏温度。比如读取到0x0191,也就是十进制401,除以16等于25.0625℃,和理论一致。负温度时情况特殊,比如-25℃对应的原始值是0xFFE7,如果不管符号位直接除以16,会得到一千多度,这就是负数补码没有处理的典型错误。

3.4 温度值拆成数码管的显示数字

假设我们显示到小数点后一位,格式是“XX.X”,4位数码管刚好够用。遇到负温度,我习惯把最高位用来显示负号,剩下三位显示整数和小数。比如-12.3℃,数码管显示“-12.3”。

拆数字的方法很多,我推荐用放大十倍的技巧,避开浮点数误差:

unsigned char c, d0, d1, d2, d3; int v; d0 = ' '; // 最高位可能是符号位 if (temp < 0) { v = (int)(-temp * 10); d0 = 0x40; // 段码表中负号对应的段 } else { v = (int)(temp * 10); } d1 = v / 100; // 十位 d2 = v % 100 / 10; // 个位 d3 = v % 10; // 小数位 // 存入显示缓冲区,后续由动态扫描读取 displayBuf[0] = d0; displayBuf[1] = d1; displayBuf[2] = d2; displayBuf[3] = d3;

这里displayBuf是全局数组,动态扫描函数会不断访问它。用整数放大计算能避免浮点数四舍五入的不一致问题,实测更稳定。

共阴数码管的段码表,0到9和一个负号,放在Flash里:

unsigned char code segTable[] = { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F, // 9 0x40 // 负号 - };

如果是共阳数码管,需要把每个段码按位取反,比如0显示0xC0而不是0x3F。这一点在代码里写死之前,一定要先确认硬件接的是共阴还是共阳,反了就是满屏乱码。

3.5 动态扫描:消隐和刷新率是关键

我用P0做段选线、P2低4位做位选线来示例。假设位选是低电平有效,也就是选中某位时对应IO输出0:

void DisplayScan(void) { unsigned char pos; for (pos = 0; pos < 4; pos++) { P0 = 0x00; // 第一件事:消隐,清除上一段的段码 P2 = ~(0x01 << pos); // 第二件事:切换位选,选中当前位 P0 = segTable[displayBuf[pos]]; if (pos == 3) { P0 |= 0x80; // 如果第3位是小数字,点亮小数点 } DelayMs(2); // 每位点亮约2ms } P0 = 0x00; // 扫描结束前再消隐一次,防止最后一位有拖影 }

消隐的顺序非常关键。必须先关段选,再切位选。如果先切位选、后改段选,前一位会在最后几个毫秒里显示出下一位的段码,视觉上就是一层明显的重影。

刷新率方面,4位数码管整体刷新周期最好控制在20ms以内,即刷新率大于50Hz。每位点亮时间等于总周期除以位数,按2ms算,总周期8ms,刷新率125Hz,亮度和稳定性都不错。如果每位点亮时间太长,比如超过5ms,人眼就能看出闪烁;太短,LED平均电流不够,亮度明显下降。这是一个需要在实际硬件上微调平衡的参数。

3.6 主循环整合:先解决转换时间和显示刷新冲突

温度转换要750ms,动态扫描每位只能亮2ms,这两个时间尺度天然冲突。如果简单地在主循环里先Delay 750ms再读温度然后扫描显示,显示会一卡一卡,观感很差。

正确做法是“先启动转换,过段时间再来读”。把“启动转换”和“读取结果”拆成两步,期间持续刷新数码管。伪代码如下:

void main(void) { unsigned char tCnt = 0; float currentTemp = 0; bit converting = 0; while (1) { if (!converting) { DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0x44); converting = 1; } if (converting && tCnt >= 20) { DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); low = DS18B20_ReadByte(); high = DS18B20_ReadByte(); currentTemp = CalcTemp(low, high); UpdateDisplayBuf(currentTemp); converting = 0; tCnt = 0; } DisplayScan(); // 核心:显示扫描不能停 tCnt++; DelayMs(10); // 粗略定时 } }

注意一点:在转换期间,绝对不要在DQ线上做任何写操作,否则会中断转换过程。最好的状态是,主循环里唯一干预DQ的地方就是固定的启动转换和读取数据两个时间点。

tCnt的定时只是个粗糙方案,更规范的做法是开一个定时器中断做时间节拍,主循环只做状态切换和显示扫描。但只要把握住“转换期间不打扰、显示不能停”这两个原则,这个基础轮询版本就足够稳定了。

4. 调试实战:我踩过的坑和排查技巧

4.1 高频问题速查表

把这些年做这个项目见过的问题整理成一张表,照着排查快得多。

现象可能原因排查方法
数码管一个数字都不亮公共端接错、共阴共阳选反万用表二极管档测公共端和段位之间的压降
数字乱码、算子不像段码表方向错、位选极性反单独测试一位数码管,逐段点亮验证
稳定显示85.0℃只读了上电默认值,没跑转换检查是否执行0x44命令并等待750ms
温度值乱跳读时序采样点偏晚,或上拉电阻缺失用示波器看DQ波形,确认采样窗口
显示-0.6℃这种怪值负数补码没处理检查MSB高5位符号判断逻辑
数码管闪烁明显刷新率低于50Hz缩短每位的延时,或增加总扫描频率
只有首个温度对,后面一直不变0xCC后没重新复位,或状态机卡死每次读写前后都执行一次复位

4.2 避坑一:上电默认值85℃

第一次接上DS18B20,数码管显示85.0℃,这是所有人都会遇到的现象。别慌,不是传感器坏了,而是DS18B20上电时暂存器里存放的就是85℃这个默认值。只有正确执行了0x44转换命令并等待转换结束后再读,才会拿到真实温度。

很多人栽在“上电后只读一次”的写法上。只要程序里没有启动转换的步骤,或者启动转换后马上读取,没有等完750ms,读到的就还是旧值。解决办法就是严格按照“复位→0xCC→0x44→等待→复位→0xCC→0xBE→读取”这个顺序来,一个都不能少。

4.3 避坑二:负数温度的补码处理

如果只是做室内温度计,可能永远接触不到负温度。但消费电子、冷链设备、北方户外设备,冬天零下是常态。负数不处理,显示效果比乱码还离谱。

一个常见错误是只判断最高位,然后用raw/16,得到一个大正数。正确的补码处理是:先判断符号位,负数时按位取反再加1,得到绝对值,除以16,最后在显示端加负号。我在代码里给的是完整的负数分支,建议保留下来,别在调试时为了省事删掉。

4.4 避坑三:上拉电阻缺失和长线问题

面包板自己搭电路时,最容易漏的就是DQ线上的4.7kΩ上拉电阻。开发板通常在MCU侧已经集成好上拉,但面包板没有,漏接后总线浮空,读出来的数据要么全1要么全0,完全没有规律。这个问题接上电阻就能解决,不必改代码。

如果线太长,即使接了上拉,也可能因为分布电容导致上升沿过慢。这时有两种处理思路:一是把采样点提前到释放总线后5到8微秒,避开边缘;二是把上拉降到3.3kΩ,加快上升沿。但根本上,单总线不适合超过1米的布线,需要远距离采集时果断换方案。

4.5 时序调试心得:没有示波器怎么调

时序问题最难排查,因为肉眼看不到。我建议有条件尽量用逻辑分析仪看DQ引脚波形,但如果没有,也有土办法。

在DS18B20_Reset函数里加一个标志位,每次成功检测到存在脉冲就翻转一个LED。手握住传感器让温度升高,观察LED是否保持固定频率闪烁。如果程序卡死在某个读周期,LED的闪烁节奏会突然停止或不规律,这时就把串口打印打开,逐字节输出读到的暂存器内容,定位是哪个环节出错。

在读时序的采样点选择上,我的经验是采样时间越早越好。我实测过把采样点延后到20微秒以上,10次里有3次读到错误数据,温度跳变毫无规律。后来把采样点控制在释放后的8微秒左右,连续读100次基本零错误。这个窗口虽然手册给到15微秒,但为了余量,越靠近起始端越安全。

4.6 分辨率调整与EEPROM的一个隐藏坑

DS18B20默认12位精度,转换时间最长750ms。如果觉得这个时间太长,可以改成9位分辨率,转换时间缩短到93.75ms,但是分辨率降到0.5℃。写配置寄存器的命令序列是:复位→0xCC→0x4E→写TH→写TL→写配置字。

配置字的Bit6和Bit5控制分辨率:00对应9位,01对应10位,10对应11位,11对应12位。改完后可以执行0x48命令把配置复制到EEPROM,这样断电后也会保留。

这里有个隐藏坑:如果改分辨率时没写EEPROM,系统一旦断电,芯片上电后又恢复12位默认配置,你可能会疑惑“明明改了怎么又回去了”。所以改完配置后,建议重新掉电再上电验证一次,确认真正生效。

4.7 多点测温的扩展思路

这个项目做完后,最顺理成章的扩展就是总线上挂多个DS18B20。单总线上每个DS18B20都有唯一的64位ROM序列号,包含家族码、48位序列号和8位CRC校验。多设备时不能用0xCC跳过ROM,必须先执行0x33读取每个传感器的序列号,再通过0x55匹配ROM来点名访问。

48位序列号决定了一条总线上理论可以挂多个设备,但实际受总线电容和时序影响,挂太多会让读时序边缘变得很脏。我建议一条总线上稳定挂8个以内,再多就分多条总线。上位机可以通过CRC校验来确认读到的序列号是否正确,这是很有价值的一个工程细节。

4.8 显示部分的小技巧

最后分享一个显示小技巧。如果只需要显示“XX.X”格式,比如25.5℃,第三位的小数点需要常亮。方法是把段码按位或上0x80(共阴数码管小数点段对应的位),不用单独写额外的IO控制函数。这样在动态扫描函数里加一行判断就行,代码结构不会被破坏。

亮度不够时,优先增加每位点亮时间,但总周期要控制在20ms以内,也就是每位最多5ms。把点亮时间从1ms调到2ms到3ms,亮度改善非常明显,再往上就需要考虑用三极管或者专门的数码管驱动芯片来增强驱动能力了。

这个项目做下来,我最深的体会是“引脚定义和极性搞对,代码就赢了一半”。DS18B20和数码管,原理都不难,但在实际调试中,恰恰是那些看似简单的电平方向、时序宽度、共阴共阳选择,决定了整个项目能不能顺利跑起来。如果现在你调试时发现温度值完全不合理,先别急着改代码,拿万用表从头到尾量一遍接线,再对着时序图一级一级核DS18B20的读写延时,大概率比闷头改代码更快见效。后面如果要做温控产品,可以在现有基础上加一个阈值比较函数,设置上限下限去控制继电器或风扇,这个项目就是一个非常扎实的起点。

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

Spring Boot线程池实战:从@Async踩坑到可监控ThreadPoolExecutor

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

作者头像 李华
网站建设 2026/10/16 1:05:19

HttpContext.Current.Server.MapPath 报空引用异常?原理与替代方案全解析

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

作者头像 李华
网站建设 2026/10/16 1:04:49

Vue过滤器从入门到实践:核心用法、链式调用与避坑指南

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

作者头像 李华
网站建设 2026/10/16 1:04:47

MCU的职责边界:STM32+ESP8266+云平台应用范围划分实践

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

作者头像 李华
网站建设 2026/10/16 1:03:56

用IDL脚本实现ENVI GLT重投影:原理、步骤与避坑指南

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

作者头像 李华
网站建设 2026/10/16 1:03:54

双电芯主动均衡方案:MP2642GR-Z与PIC18F4685工程实践

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

作者头像 李华