news 2026/10/9 1:08:01

零基础用ESP32驱动WS2812B灯带:心跳呼吸灯实战教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
零基础用ESP32驱动WS2812B灯带:心跳呼吸灯实战教程

零基础学ESP32:WS2812B灯带——让灯光随心跳动!

说起玩ESP32,我接触过的第一个让人眼前一亮的项目,就是驱动WS2812B灯带。这种灯带最大的魅力在于:一根数据线就能控制几十甚至上百个灯珠,每个灯珠还能独立发出1670万种颜色,根本不需要像传统RGB灯带那样一根线控制一种颜色。更妙的是,配合ESP32强大的处理能力,咱们完全可以让灯带的亮度跟随心跳节奏起伏,做一个专属的“心跳呼吸灯”。

这篇文章就带你从零开始,亲手搭一个心跳灯项目。你不需要有电路基础,也不需要懂什么高深的编程知识,只要跟着步骤走,用一块开发板、一段灯带、几根杜邦线,就能让灯光真正“活”起来——像心跳一样,一下一下地搏动。项目本身不到两小时就能完成,但过程中涉及的环境搭建、接线、写逻辑、调参数,全是ESP32开发的童子功,做完这个,你后面想做智能灯、音乐律动灯、环境氛围灯,都是顺手的事。

1. 项目整体拆解与硬件选型

1.1 核心需求解析

先说清楚这个项目到底要做什么。我们最终要实现的效果是:灯带上的灯珠以“心跳”的节奏明暗变化——突然亮起、逐渐熄灭,再突然亮起、再逐渐熄灭。这个节奏模拟的是心脏搏动时的压力波形,不是均匀的呼吸感,而是那种“砰、砰”的冲击感。

要实现这个效果,拆开来就三件事:

  • 用ESP32产生一个模拟心跳的亮度曲线(也就是一组随时间变化的数据)。
  • 把这个曲线转化成WS2812B灯带能识别的控制信号。
  • 让整条灯带或者某几颗灯珠,按曲线同步变亮变暗。

听起来好像不难,但这个项目真正的学习价值在于:它逼着你理解“时序控制”和“数据协议”这两个单片机开发的核心概念。WS2812B的通信协议非常讲究时序,哪怕差个几百纳秒,灯珠就可能显示乱码。而ESP32作为主控,要做的就是精准地把颜色数据“喂”给灯带,这中间涉及到的GPIO操作、库的选择、数据的组织方式,全是后面做任何灯效项目的基础。

1.2 硬件清单与选型说明

先说开发板。ESP32家族型号很多,对新手来说,我推荐选择ESP32 DevKitC或者NodeMCU-32S这类经典款,它们都使用ESP32-WROOM-32模组,引脚兼容性好、资料多、踩坑少。如果你手头的是ESP32-S3或者ESP32-C3,其实也能做,代码几乎一样,只是引脚编号有所不同。

选板子的核心参考点是:只要能跑Arduino框架的ESP32开发板都行,不必追求最新最贵。ESP32-S3虽然性能更强、有AI加速,但对这个项目来说纯属“高射炮打蚊子”,经典款完全够用。

灯带方面,选WS2812B-5050规格就好,就是最常见的那种,30灯/米或60灯/米都可以。我建议新手先用30灯/米的,间距大、散热好、调试起来方便观察。数量上买1米就够,也就是30颗灯珠,足够玩出各种花样了。

电源这块特别提醒一句:WS2812B灯带对电源要求比较高,全亮白色时,一颗灯珠的电流差不多要60mA。如果接60颗灯珠全白,瞬间电流能到3.6A,这时候如果还用USB线供电,大概率会压降严重,导致灯带闪烁甚至ESP32重启。所以我的建议是:

  • 测试阶段用USB供电、点亮少量灯珠完全没问题。
  • 如果要跑长灯带,准备一个5V 2A以上的电源适配器,灯带电源线和ESP32的VIN分开接,共地就行。

其他配件:杜邦线若干(母对母)、面包板一块、330Ω电阻一个、1000μF电解电容一个(可选但强烈建议)。

硬件推荐型号说明
主控ESP32 DevKitC / NodeMCU-32S经典款,资料多,扩展性强
灯带WS2812B 30灯/米(1米)单总线控制,逐灯独立变色
电源5V 2A适配器测试时USB供电即可,长灯带务必外接电源
辅助面包板+杜邦线+330Ω电阻电阻串在数据线上作保护

2. 开发环境搭建与核心驱动原理

2.1 Arduino IDE环境配置(含离线包方案)

环境这块我默认大家用的是Arduino IDE 2.x版本,这是目前最主流的玩法。安装完IDE之后,要在首选项里添加ESP32开发板管理器地址:

https://espressif.github.io/arduino-esp32/package_esp32_index.json

然后到“开发板管理器”里搜esp32,安装esp32 by Espressif Systems。这里有个常见坑:国内网络下载这个包经常失败,尤其是首次安装要拉几百MB的文件。解决办法是用离线安装包,网上搜“arduino esp32离线包 2.0.x”就能找到打包好的压缩包,解压后放到Arduino的硬件目录(文档/Arduino/hardware/espressif)下,重启IDE就能识别。

安装完之后,在“开发板”菜单里选择你的板型。经典款一般是选**“ESP32 Dev Module”**,不需要管Flash大小之类的细节,默认就行。如果你用的是ESP32-S3,就需要选“ESP32S3 Dev Module”,像ESP32-S3核心板板载1-N16R8这种,在PlatformIO里要选esp32-s3-devkitc-1,但在Arduino IDE里选ESP32S3 Dev Module就够。插上开发板之后,如果设备管理器能认到串口,说明驱动已经装好;如果认不到,多半是CP210x或者CH340驱动的问题,后面排查章节会详细说。

2.2 WS2812B单总线协议原理

在动手写代码之前,必须先搞清楚WS2812B是怎么被控制的。它只有一根数据线(Din),所有灯珠串联在这根线上,每个灯珠内置芯片,从数据线上“提取”属于自己的那24位数据(分别代表G、R、B各8位),然后把多余的数据继续传给下一颗灯珠。

关键在于,这24位数据的传输靠的是不同脉宽的方波:

  • 0码:高电平约0.35μs,低电平约0.8μs。
  • 1码:高电平约0.8μs,低电平约0.45μs。
  • 复位信号:低电平保持至少80μs,代表一帧数据结束。

也就是说,每一颗灯珠的颜色,本质上是24个“宽窄不同的方波”串起来。ESP32要做的事情,就是在极短的时间里,不断翻转GPIO引脚的电平,把这些方波“画”出来。这个过程的精度要求达到微秒级甚至亚微秒级,如果用digitalWrite()这种高层API去操作,速度完全跟不上。

正常开发中,我们不会手动去翻转引脚,而是用现成的库。最常见的是Adafruit_NeoPixel库和FastLED库。两个库都能正确驱动WS2812B,但FastLED在性能和功能上更胜一筹,尤其适合做动画效果;Adafruit_NeoPixel则更容易上手。

为了照顾零基础的朋友,我下面主推Adafruit_NeoPixel,代码好理解、API直观,装库直接在库管理器里搜“NeoPixel”安装就行。如果你后面要追求更复杂的灯效,再换FastLED不迟。

2.3 PlatformIO进阶方案

如果你对Arduino IDE的卡顿和代码管理不满意,完全可以试试VSCode + PlatformIO这个组合。PlatformIO本质上是一个跨平台嵌入式开发环境,支持Arduino框架,但对项目的管理更规范,编译速度在Windows上其实比Arduino IDE还快一些。

在PlatformIO里新建ESP32项目时,选择开发板型号直接在platformio.ini里声明:

[env:esp32dev] platform = espressif32 board = esp32dev framework = arduino

如果你用的是ESP32-S3 1-N16R8,需要改成:

[env:esp32s3dev] platform = espressif32 board = esp32-s3-devkitc-1 framework = arduino board_build.flash_size = 16MB board_build.partitions = huge_app.csv

注意:ESP32-S3 N16R8表示16MB Flash、8MB PSRAM。PlatformIO默认分区表比较小,如果之后要跑摄像头或者大量固件,需要自定义分区表,新手可以先不纠结,跑这个灯带项目用默认配置就行。

串口监视器在PlatformIO里是通过Serial Monitor插件打开的,Serial.print()的用法和Arduino IDE完全一样,这点对迁移非常友好。

3. 心跳灯核心逻辑与完整代码实现

3.1 心跳波形怎么来的

接下来就是整个项目的灵魂部分:怎么生成一个“心跳感”的亮度曲线。

很多人一开始会想着用sin函数做个呼吸灯效果,但心跳和呼吸是两回事。呼吸是平滑的正弦波,亮起来暗下去都很均匀;心跳则是“快速收缩、慢速舒张”,灯应该快速亮起(模拟心室收缩),然后缓慢熄灭(模拟舒张期)。

医学上,心跳波形大致长这样:上升沿很陡,下降沿比较平缓,中间还有一个小的峰。我们不需要做得那么精确,但至少要保留“快亮慢灭”这个特征。

我用的方案是:分段贝塞尔曲线。把一次心跳分成四段,每一段用不同的数学表达式去映射亮度:

  • 第一段(0%~30%时间):从20%亮度线性升到100%亮度,制造一个陡峭的上升沿。
  • 第二段(30%~45%时间):从100%亮度掉到80%亮度,模拟那个“重搏波切迹”。
  • 第三段(45%~80%时间):从80%亮度缓慢降到15%亮度,这是舒张期。
  • 第四段(80%~100%时间):从15%亮度平滑降到0%,彻底熄灭等待下一次心跳。

每一段的斜率都不一样,合在一起就有那种“鼓点一样”的搏动感。

这里有个细节,就是用map()的方式把百分比映射成0~255的亮度值。比如第一段到100%亮度时,灯珠的RGB值就是(255,255,255),但如果直接用白色,你会觉得刺眼,而且耗电很高。我实际调试下来,把最大亮度限制在180左右效果最好,既有心跳冲击感,又不会过度曝光,夜晚看起来非常柔和。

3.2 完整代码逐段解析

下面是我测试过、可以直接抄的代码。注释写得很细,新手照着一步步看就能懂。

#include <Adafruit_NeoPixel.h> #define LED_PIN 13 // 数据引脚接到GPIO13 #define LED_COUNT 30 // 灯珠数量(30灯/米灯带) #define BRIGHTNESS 180 // 最大亮度上限,防止刺眼和过流 Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); const int heartBeatInterval = 900; // 一次心跳总时长(毫秒),大概67次/分钟 void setup() { strip.begin(); strip.setBrightness(BRIGHTNESS); strip.clear(); strip.show(); } void loop() { heartbeatAnimation(); } // 一次完整的“心跳”动画 void heartbeatAnimation() { unsigned long startTime = millis(); float brightness = 0; while (millis() - startTime < heartBeatInterval) { float progress = (float)(millis() - startTime) / heartBeatInterval; if (progress < 0.3) { // 第一段:快速上升(0%-30%时间段) // 用二次曲线让上升稍微带一点加速感,更贴近真实搏动 float t = progress / 0.3; brightness = 255 * (t * t); } else if (progress < 0.45) { // 第二段:快速回落(30%-45%时间段) // 模拟重搏波切迹,亮度从峰值掉到80% float t = (progress - 0.3) / 0.15; brightness = 255 * (1.0 - 0.2 * t); } else if (progress < 0.8) { // 第三段:缓慢衰减(45%-80%时间段) // 平滑过渡到低亮度,模拟舒张期 float t = (progress - 0.45) / 0.35; brightness = 255 * (0.8 - 0.6 * t); } else { // 第四段:收尾熄灭(80%-100%) // 彻底归零,等待下一拍 float t = (progress - 0.8) / 0.2; brightness = 255 * (0.2 * (1.0 - t)); } // 统一做一次亮度钳位 if (brightness > 255) brightness = 255; if (brightness < 0) brightness = 0; setAllLeds((uint8_t)brightness); delay(10); // 控制单次循环节奏,让波形更平滑 } } // 让所有灯珠统一变亮成同一个颜色 void setAllLeds(uint8_t brightness) { for (int i = 0; i < LED_COUNT; i++) { // 用白色光,R/G/B都设为同一亮度 strip.setPixelColor(i, strip.Color(brightness, brightness, brightness)); } strip.show(); }

这段代码里的关键是millis()函数的时间管理。我没有用delay()去整体卡住程序,而是用while(millis() - startTime < heartBeatInterval)这种非阻塞式的写法,让整个动画函数能精确地控制一次心跳的总时长。delay(10)放在循环里只是为了控制刷新率,避免一秒钟刷几千次浪费CPU。

提示:这里面最需要注意的是时序。WS2812B的数据速率是800kHz,也就是每秒钟传800k个比特位。Adafruit_NeoPixel库会在底层自动处理好这些时序,你不需要手动去折腾delayMicroseconds(),但也不能在strip.show()之后立刻做大量耗时操作,否则可能影响下一帧的传输稳定性。

3.3 关键参数的计算与调优

再来聊聊参数为什么这么定,以及怎么改出一版适合你自己的。

首先是heartBeatInterval = 900。正常成年人的静息心率是每分钟60~100次,换算成周期就是600ms~1000ms。900ms对应大约67次/分钟,节奏偏慢,放在桌面上看会显得很沉稳。如果你想要更急促的“运动感”,改成600就能模拟快节奏心跳;想要更慵懒的夜灯氛围,可以拉到1200。

亮度曲线的分段比例同样可以调整。我测试下来,0.3/0.15/0.35/0.2这个比例的组合最能还原真实心跳波形。如果把第一段拉长,会变得像“叹气”而非“心跳”;如果第二段去掉,就是普通呼吸灯了。

还有一个我踩过坑的参数:灯珠数量。如果LED_COUNT和实际灯珠数量不一致,多出来的部分会乱闪或者整条不亮。买的是1米30灯,就写30;贪便宜买了“1米60灯”的,就要写60,这个必须确认清楚。

3.4 用FastLED实现同样的效果

如果你更想用FastLED,核心逻辑完全一样,只是接口换了一下。把Adafruit_NeoPixel的初始化代码替换成:

#include <FastLED.h> #define LED_PIN 13 #define NUM_LEDS 30 #define BRIGHTNESS 180 CRGB leds[NUM_LEDS]; void setup() { FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS); FastLED.setBrightness(BRIGHTNESS); } void allLeds(uint8_t brightness) { fill_solid(leds, NUM_LEDS, CRGB(brightness, brightness, brightness)); FastLED.show(); }

FastLED在颜色转换和动画引擎上更强大,数组操作也直观得多。但一段完整的loop()里面,同样的波形计算逻辑照搬就行,只是把setPixelColor换成leds[i]赋值。GitHub上还有专门的FastLED心跳折线动画库,有兴趣可以研究一下。

4. 实操接线与烧录调试流程

4.1 接线图与注意事项

接线是整个项目里最容易出问题的一环。WS2812B灯带一般有三根线:红色(5V)、白色/黑色(GND)、绿色或白色(Din,数据线)。

灯带一端通常会预先焊好三根线,如果没有,需要自己焊——注意先剥线,别把相邻的铜芯焊在一起。

接线步骤:

  1. 灯带的GND接ESP32的GND。
  2. 灯带的Din接ESP32的GPIO13(或者其他任意数字引脚,只要代码里对应改)。
  3. 灯带的5V接ESP32的VIN(外部供电时)。

调试阶段如果只点亮几颗灯珠,可以直接让灯带5V接USB的5V和GND,此时ESP32和灯带共用一个电源。但跑全亮动画时,务必用独立电源。

还有一个非常重要的细节:在数据线和ESP32之间串联一颗330Ω的电阻。这颗电阻能有效抑制信号线上的反射,特别是在线比较长的时候,没有它容易出现数据错乱、尾部灯珠闪烁的毛病。别小看这一颗电阻,它能帮你省下大量排查时间。

注意:灯带的Din输入脚需要接在靠近灯带“IN”端的那一侧,有些灯带标注了箭头方向,信号传输方向是单向的,接反了整条灯带都不会亮。接反不会烧东西,但会让人白忙一场。

4.2 编译烧录全流程

把代码写好后,在Arduino IDE里点击验证按钮(对勾图标),第一次编译会稍微慢一些,因为要编译ESP32整个框架,大概30秒到2分钟。如果编译通过,点击上传按钮,会立刻开始编译然后烧录。

这里有个常用技巧:先按住开发板上的BOOT键再插USB,松开BOOT键,可以强制进入下载模式。虽然大部分ESP32开发板支持自动下载电路(一键下载),但偶尔会失败,手动进下载模式基本百分百能成功。

烧录完成后,串口监视器会显示代码里的Serial.print()输出。如果你在代码里加了调试信息,比如打印当前亮度值,就能实时观察运行状态。

4.3 串口监视器与实时调试技巧

调试心跳灯效果的绝招是:把亮度曲线的数值用串口打印出来,配合串口绘图器观察波形。Arduino IDE 2.x自带Serial Plotter绘图器,打开后能把串口输出的数字画成实时曲线。

具体的做法是在heartbeatAnimation()里加一行调试代码:

Serial.println(brightness);

打开串口绘图器,调整串口波特率为115200(setup里要加Serial.begin(115200)),就能看到一条类似心电图的波形在屏幕上滚动。如果波形陡升缓降、带一个小回踩,说明你的心跳曲线做得非常到位;如果看起来像三角波,说明上升沿的二次曲线没生效,检查一下那个t * t有没有写对。这个方法比我说的任何理论都直观。

5. 常见问题与排查技巧实录

5.1 灯带不亮怎么排查

这个项目新手成功率其实挺高的,但一旦出问题,大多数都出在以下几类。我按出现概率排个序:

灯带完全不亮。先用万用表量灯带电源两端,确认有没有5V。没电压就检查电源接线;有电压就检查Din线有没有接对、代码里LED_PIN写的什么数字。我犯过的低级错误是板子型号选错,导致GPIO编号实际对应不上管脚,程序烧进去根本控制不了那个引脚。

前几颗灯亮,后面的不亮。这是典型的信号传输受干扰问题,或者数据线过长。WS2812B的协议是串行传输的,如果脉冲变形,第一颗之后的灯就收不到正确的数据。解决办法就是串330Ω电阻,并且把数据线缩短,尽量别用那种1米长的杜邦线。

灯乱闪,颜色完全不对。大概率是供电不足。一条60灯的灯带在跑白色动画时,瞬时电流能到3.5A左右,USB口一般只能提供500mA,结果就是电压被拉低、灯珠IC工作异常,表现出来就是疯狂乱闪。换个2A以上的电源适配器,问题立刻消失。

颜色偏黄或者偏蓝。这是GRB和RGB顺序选错了。Adafruit_NeoPixel库初始化时的NEO_GRB就是默认的G-R-B顺序,有些灯带用的是NEO_RGB,如果顺序不对就把这个枚举值改掉。FastLED里面就是WS2812B, LED_PIN, GRB这一处,反了就偏色。

5.2 开发板与串口驱动问题速查

设备管理器不显示COM口:先换一根数据线,很多Type-C线只有充电功能、没有数据信号线。然后检查CP210x或CH340驱动,Windows下装了驱动就能识别。ESP32-S3部分开发板用CH343或CP2102N,驱动稍有不同,但思路一样。

烧录失败、连接超时:先按BOOT键手动进入下载模式。如果还是失败,把波特率从921600降到115200,一次不成功很正常,换个USB口或者换个线,多试几次。ESP32的下载模式稳定性其实很好,反复失败大概率是线材或者供电问题。

Windows编译速度慢:这是很多人的痛点,尤其是第一次编译ESP32工程时等待时间太长。PlatformIO的解决方案是开启编译缓存和并行任务,在platformio.ini里加两句:

build_flags = -s

实际上更有效的方案是换用PlatformIO而不是Arduino IDE,它默认就开启了编译缓存,二次编译基本秒过。另外Windows Defender实时扫描会拖慢编译速度,可以把项目目录加入排除列表。

5.3 我踩过的三个比较隐蔽的坑

第一个是电源共地问题。ESP32和灯带如果用两个独立电源供电,必须把两边的GND连在一起,不然数据信号没有参考地,灯带就是乱闪。很多人忽略这个细节,怎么排查都查不出来。

第二个是board_build.partitions的坑。在PlatformIO里面如果选了ESP32-S3 16MB Flash的开发板但没设置分区表,烧录进去可能报错或者一直在重启循环,原因是固件大小超出默认分区。解决方法是加一行board_build.partitions = huge_app.csv。

第三个是中断与WS2812B的冲突。因为WS2812B的时序是由RMT外设或延时驱动的,如果在动画循环里开了高速外部中断(比如按键消抖),有可能中断打断数据传输导致帧错乱。如果你的项目后面加了按键控制,要注意按键扫描的逻辑尽量放到WS2812B的show()之后去做,或者在主循环里做非阻塞轮询。

6. 这版项目能怎么扩展

心跳灯做完只是起点。我实际操作中,经常把同一套框架往各种方向扩展,这里分享几个我觉得性价比很高的方向。

加一个按键切模式:用外部中断实现单击切换呼吸灯/心跳灯/常亮模式。ESP32的外部中断功能在这个场景非常实用,按键按下时置一个标志位,主循环检测到标志位后再切换动画函数。注意消抖,可以用软件延时或者用millis()防抖。

音乐律动灯:把麦克风模块(比如MAX9814)接到ESP32的ADC引脚,实时采集音频幅值,根据音量映射到灯带亮度。这个项目的信号链是:音频采集→ADC采样→平滑滤波→映射到灯带,每一步都有明确的回调,用我上面那套setAllLeds()的架构做起来很顺手。

加入MPU6050传感器:把心跳灯变成“体感灯”也很酷。用MPU6050检测手指按压脉搏,每次按压缩放一次心跳动画,核心逻辑和本项目的心跳波形生成部分完全一致,只需要把触发方式从定时触发改成传感器触发。

手机App控制:ESP32支持蓝牙BLE和Wi-Fi,可以做一个简单的配网小程序,用手机App调颜色、调心跳频率。这个方向适合想学前后端联调的朋友,ESP32作为下位机,处理传感器和灯带数据,手机端负责交互界面。

这些扩展方向没有一个需要重写底层代码,都是在当前这个项目的逻辑框架上增加功能模块而已。这也是我推荐新手从这种“小而完整”的项目入门的原因——它不是孤立的知识点,而是整个物联网开发链条的一环。你学会了这一套,后面不管玩什么传感器、什么执行器,思路都是相通的。

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

嵌入式工程师成长路径:从51单片机到RTOS的实战能力闭环

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

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

Java+Android学生评教系统源码实战:从JDBC到Tomcat的完整链路拆解

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

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

工控调试必备:Modbus数据模拟从零搭建与避坑全攻略

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

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

MAC协议源码调试实战:驱动/仿真识别与行为验证

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

作者头像 李华
网站建设 2026/10/9 1:06:15

不用开发板学STM32:Proteus仿真+MDK实现电子时钟

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

作者头像 李华