news 2026/10/3 7:55:54

ESP32接入4G模块:PPP拨号实现蜂窝网络联网教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32接入4G模块:PPP拨号实现蜂窝网络联网教程

这阵子折腾了好几个晚上,终于把一块吃灰很久的4G模块和ESP32凑到了一块儿,实现了真正的蜂窝网络上网。整个过程并不复杂,但中间踩的坑确实不少,尤其是PPP拨号这一块的细节,网上资料很零散。这篇学习笔记就把我自己的操作过程、关键原理和踩坑记录完整地整理出来,给同样想玩“ESP32 + 4G模块”的朋友一个参考。

先说清楚这玩意儿能干什么:ESP32本身只带Wi-Fi和蓝牙,离开路由器就没法上网。接上4G模块之后,ESP32就能像手机一样直接通过SIM卡连接蜂窝网络,实现真正的“无网线、无路由器”的联网能力。特别适合野外数据采集、移动车载设备、无人机地面站、临时布点的IoT传感器这些场景。

如果你是刚接触ESP32的开发者,想给设备加一个“永远在线”的联网通道;或者你手头正好有一块闲置的4G模组,想让它跑起来——这篇笔记都适合你。我会从方案选型讲到硬件接线,再到ESP-IDF里的代码配置和实际拨号过程,最后把最容易出问题的几个坑单独拿出来分析。整个过程中涉及到的工具、命令和代码片段,都是我自己实际跑通过的,可以直接参考。

1. 这个项目解决什么问题:为什么ESP32非要接4G模块

1.1 场景拆解:哪些地方Wi-Fi真的不可靠

先说实际需求。我最初想做的是一个户外环境监测节点,设备丢在农田里,周围几百米都没有路由器,也没人愿意给它专门拉一条宽带。如果只用ESP32自带的Wi-Fi,这个设备就是一个废铁。接上4G模块后,只要SIM卡有信号,设备就能把温湿度、土壤数据传到云平台。

还有一个非常典型的场景是车载设备。车在高速上跑,Wi-Fi热点切换非常痛苦,有时候压根没有信号覆盖。用4G模块拨号上网,网络跟着SIM卡走,信号跟着基站走,完全不受周围Wi-Fi环境影响。

还有一类场景是临时展会、户外直播、应急通信这类需要快速布网的场合。你可能今天在A现场,明天在B现场,拉网线、调试路由器费时费力。一块4G模块+一张流量卡,通电就能联网,运维成本低很多。

1.2 方案对比:AT指令TCP/IP、串口透传、PPP拨号怎么选

刚接触这个项目的时候,我最先想到的是用AT指令让模块直接建TCP连接。也就是通过串口向4G模块发送类似AT+QIOPEN这样的命令,让模块自己去连接远程服务器,ESP32只负责收发数据。这种方式实现起来最简单,很多同学的第一版代码都是这么写的。

但仔细一想,问题就来了:如果云平台用的是MQTT协议,AT指令模式还能对付;可一旦涉及HTTP/HTTPS、WebSocket,或者需要跑自定义TCP协议,AT指令会让你在“模块协议栈”和“MCU协议栈”之间来回切,代码越写越丑。而且很多模块的AT指令缓冲区和并发连接能力有限,不适合做复杂通信。

另一种方案是串口透传模式,模块固件把串口数据直接转换成网络数据。这个方案在部分4G模块上支持,但它把网络栈完全封装在模块内部,ESP32拿不到完整的网络状态和IP层信息,没法做精细控制。对于想学习网络协议栈的同学来说,这种方案也不太友好。

最后我选了PPP拨号方案。PPP全称Point-to-Point Protocol,是一种数据链路层协议,专门用来在两个网络节点之间建立点对点连接。拨号时,ESP32把4G模块当作一只“外置调制解调器”(和早年电脑通过电话线上网是同一个思路),通过AT指令发起拨号,然后整个串口通道切换为PPP数据通道,ESP32的lwIP协议栈直接跑在这个PPP链路上。这样ESP32拿到的就是一个正常的IP网络接口,TCP/UDP/HTTP/MQTT全部照常使用。虽然前期配置比AT指令麻烦一点,但一旦跑通,后续开发体验会顺很多。

1.3 为什么“随身WiFi”本质上也在走同一条路

可能有人会好奇,市面上那些插SIM卡的随身WiFi是怎么实现的?说实话,很多随身WiFi棒子内部的结构就是一颗通信芯片加一个路由芯片,SIM卡的数据连接最终通过PPP或者类似的NDIS方式走网络协议栈,再通过Wi-Fi分享出去。也就是说,我这次做的“ESP32 + 4G模块”本质上就是精简版的随身WiFi——只不过我不需要分享Wi-Fi,直接让芯片自己用网络。

理解了这一层,再去看PPP拨号就不会觉得它陌生了。它真的不是古董技术,今天的蜂窝网络数据通道依然在大量使用类似机制。只是模组厂商把很多步骤封装在了内部,开发者只看到“模块自己联网了”,反而忽略了底层的拨号本质。顺着这个思路,我决定不依赖模块的AT指令建连能力,自己上手走一遍PPP,把这层窗户纸捅破。

2. 硬件准备与连线:模块选型、引脚分配、供电细节

2.1 4G模块选型与SIM卡准备

市面上常见的4G模块品牌有移远、广和通、中兴微等,我手头这块是移远的EC200U系列,支持LTE Cat 1,速率不算高,但胜在功耗低、价格便宜,非常适合物联网场景。你如果用的是SIM7600、SIM800等模块,只要支持PPP拨号,思路完全一样。

选模块时有几个点要注意:

  • 确认模块支持串口AT指令和PPP协议。绝大部分4G模块都支持,但个别只支持USB的模组需要额外处理,建议先看数据手册。
  • 尽量选择有完整开源资料和示例代码的型号,调试时会省很多力气。
  • 模块的供电要求通常比较苛刻,EC200U在发射瞬间电流可能到2A,普通稳压芯片容易直接拉垮。这一点我下面专门讲。

SIM卡方面,我用的是一张物联网卡,注意确认卡的APN(接入点名称),一般物联网卡都有专门的APN,比如天翼物联的ctnb、移动的cmiot等。如果APN填错,PPP可以拨通但拿不到正确的IP,无法上网。这个细节很容易被忽略。

2.2 ESP32与模块的硬件连接:我用的是UART方案

PPP over Serial的意思就是通过串口跑PPP协议,所以把4G模块的串口和ESP32的串口连起来就行。我选了经典款ESP32 DevKit,使用UART2作为数据通道,引脚对应关系如下:

ESP32引脚4G模块引脚说明
GPIO17TXD(模块侧)ESP32发送,接模块接收
GPIO16RXD(模块侧)ESP32接收,接模块发送
GNDGND必须共地
GPIO4PWRKEY可选,用于开机脉冲控制

需要注意串口是交叉连接的,ESP32的TXD接模块的RXD,ESP32的RXD接模块的TXD。很多新手第一次就栽在这里,TX接TX,浪费了半天。

模块的PWRKEY引脚是一个开机电平触发脚,EC200U需要在PWRKEY上拉一个几百毫秒的低电平脉冲才能开机。如果用PC调试,可以直接用USB转串口工具配合串口助手发AT指令验证。但在ESP32项目里,我更建议直接用ESP32的GPIO控制PWRKEY,这样每次开机都能自动完成模块上电复位流程。

2.3 供电与电平匹配:最容易翻车的环节

4G模块是“电流大户”,这一点真的不要低估。EC200U在LTE状态下平均电流300mA左右,峰值可以到2A。如果你想用ESP32开发板自带的3.3V稳压输出给模块供电,几乎必挂——压降一出来,模块要么反复重启,要么拨号中途电流不够直接掉线。

我踩坑之后换成了单独的DC-DC降压板,输入5V(也可以用锂电池或12V电源),输出4V给4G模块供电,实测稳定很多。模块数据手册的供电范围一般是3.4V到4.3V,最佳工作电压在4V附近。供电电路上还建议并一个大容值的电解电容,比如470uF或1000uF,以及一个100nF高频去耦电容,用来扛住瞬时大电流。

电平匹配方面,EC200U的逻辑电平是1.8V,而ESP32的UART电平是3.3V。直接连上去会存在电平不匹配的问题。稳妥做法是用两路电平转换电路,或者在模块数据手册确认是否可以直接兼容3.3V输入。部分模块的引脚标称可以容忍3.3V,但为了保险起见,最好不要直接接,加一个TXB0104或者简单的分压电路更放心。我这一版直接在模块的TX脚串联了一个1K电阻到ESP32的RX脚,实测也能工作,但如果你手头有理电芯片,还是按标准电路来做。

2.4 别忘了串口电平选择

如果你用的是模块的USB接口而不是串口,那事情会更麻烦一点。ESP32经典款不带USB Host功能,跑不了USB协议栈,所以学习阶段强烈建议走UART通道。选择模块上有标注“UART”或者“MAIN UART”的引脚,不要接在debug串口上,调试口的AT指令功能有限。

3. 软件侧准备:ESP-IDF的PPP支持怎么打开

3.1 为什么选择ESP-IDF而不是Arduino

网上很多ESP32的教程都是基于Arduino框架的,配置简单,适合快速上手。但PPP拨号这种东西,Arduino的支持并不完善——最关键的是它默认网络协议栈不一定会把PPP接口暴露出来,很多情况下你需要自己折腾很底层的参数,费力不讨好。

所以我这次乖乖用了乐鑫官方的ESP-IDF开发框架,版本是v5.x。ESP-IDF内置了完整的esp_netif网络接口抽象层,PPP Support是官方直接提供的功能。你只需要在menuconfig里打开开关,然后在代码里创建一个PPP网络接口,整个连接逻辑就清晰了。

3.2 menuconfig关键配置

在项目目录下执行idf.py menuconfig,需要检查以下几个配置项:

  • Component config → ESP-NETIF Adapter → Support PPPoS (Point to Point Protocol over Serial)。这个是总开关,必须打开。
  • Component config → LWIP → Enable IPV6。如果你的场景不需要IPv6,可以关掉,省一点内存。
  • Component config → ESP-NETIF Adapter → Enable validation callback for PPP。建议打开,它会在PPP状态变化时给你回调通知,方便调试。
  • 串口相关的驱动配置默认就行,波特率我设的是115200,4G模块和ESP32之间的UART波特率不要拉到太高,否则在长线干扰下容易误码。

3.3 核心代码结构:PPP网络接口初始化

配置好之后,代码逻辑其实非常清晰。主要步骤是:创建一个esp_netif对象绑定PPP协议,配置UART参数,然后启动一个后台任务持续处理串口数据。我简化一下核心逻辑:

#include "esp_netif.h" #include "esp_netif_ppp.h" static esp_netif_t *ppp_netif = NULL; static void ppp_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == IP_EVENT && event_id == IP_EVENT_PPP_GOT_IP) { esp_netif_dns_info_t dns; ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip)); esp_netif_get_dns_info(ppp_netif, ESP_NETIF_DNS_MAIN, &dns); ESP_LOGI(TAG, "DNS: " IPSTR, IP2STR(&dns.ip)); xSemaphoreGive(net_connected_sem); } else if (event_base == IP_EVENT && event_id == IP_EVENT_PPP_LOST_IP) { ESP_LOGW(TAG, "PPP disconnected"); } } void ppp_init(void) { esp_netif_config_t ppp_netif_config = ESP_NETIF_DEFAULT_PPP(); ppp_netif = esp_netif_new(&ppp_netif_config); assert(ppp_netif); esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &ppp_event_handler, NULL); }

实际的UART收发逻辑我放在另一个任务里,用的是ESP-IDF的UART驱动。串口收到的所有数据直接uart_read_bytes读出来,然后交给esp_netif_receive送给PPP协议栈;反过来,协议栈要发数据的时候,通过注册的回调ppp_output交给UART驱动发送。这一层数据通路打通了,剩下的就交给lwIP了。

4. 实操过程:从串口AT调试到顺利拨号联网

4.1 第一步:先用AT指令确认模块活着

硬件连好之后,我先把ESP32程序里UART初始化的部分单独跑起来,写成一段只负责回显AT指令的调试代码。用电脑的串口助手直接观察模块是否正常启动,避免一上来就跑复杂逻辑,出问题都不知道是哪一环。

典型的AT检查流程:

AT # 模块基本响应,正常返回OK ATE0 # 关闭回显,避免后续PPP数据受到干扰 AT+CPIN? # 检查SIM卡是否识别,返回READY表示正常 AT+CSQ # 查询信号强度,返回数字越大越好,比如+CSQ: 18 AT+CREG? # 查询网络注册状态,返回0,1或0,5表示已注册

我第一次测试的时候在AT+CPIN?这一步卡了很久,模块一直返回+CME ERROR: SIM not inserted。检查之后发现是SIM卡槽接触不良,重新插拔一下才好。如果插卡正确但依然报错,可以检查一下SIM卡座的焊点和卡的方向,有些卡槽需要把卡往里推到位才能锁住。

4.2 第二步:设置APN和拨号前的最后准备

确认模块已经注册上网络之后,接下来要在AT指令模式下把APN设置好。APN就是网关接入点,每个运营商都不一样,具体可以问SIM卡供应商。我这张卡用的APN是ctnb,对应的AT指令是:

AT+CGDCONT=1,"IP","ctnb"

设置成功之后可以再用AT+CGDCONT?查询一遍,确认参数已经生效。

这里有一个很容易忽略的点:如果在AT模式下提前开启了额外的数据连接,或者模块内缓存了旧的拨号参数,PPP拨号可能会异常。我的习惯是在拨号前执行一遍AT+CFUN=1把射频模块完整打开,然后再设置APN。

下一步准备进入拨号流程。注意,一旦执行拨号指令,串口就不能再收发AT指令了,串口完全被PPP协议占用。如果拨号失败想退回AT模式,通常需要拉低复位引脚重新上电,或者发送“+++”字符序列切换回命令模式。

4.3 第三步:编译烧录,日志里看到Got IP

代码写好后执行:

idf.py set-target esp32 idf.py build idf.py -p /dev/ttyUSB0 flash monitor

这里要提醒一下,开发板的USB转串口芯片在Linux下一般显示为/dev/ttyUSB0或/dev/ttyACM0。如果用Windows,注意在设备管理器里确认端口号。

在ESP32启动后,我的程序流程是这样的:

  1. 先把模块通过GPIO拉PWRKEY开机。
  2. 延时等待模块启动完成(这里至少等3秒,模块初始化需要时间)。
  3. 发送ATE0、AT+CGDCONT=1,"IP","ctnb"配置。
  4. 发送ATD*99***1#发起PPP拨号。
  5. 串口模式切换到PPP数据模式,开始等待PPP协议协商。
  6. 看到IP_EVENT_PPP_GOT_IP事件,说明拿到了IP地址,网络正式通了。

第一次成功的时候,日志里出现:

I (12450) ppp: Got IP: 10.xxx.xxx.xxx I (12452) ppp: DNS: 120.xxx.xxx.xxx

那一刻还是很有成就感的。

4.4 第四步:验证网络连通性

拿到IP不等于可以上网,还要验证路由和DNS。我直接写了两个小测试,一个用来连TCP服务器:

int sock = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_port = htons(8888); inet_pton(AF_INET, "47.xxx.xxx.xxx", &addr.sin_addr); connect(sock, (struct sockaddr *)&addr, sizeof(addr));

另一个是用lwIP自带的DNS解析,测试域名解析是否正常:

struct hostent *he = gethostbyname("www.baidu.com"); if (he) { ESP_LOGI(TAG, "DNS resolve OK: %s", inet_ntoa(*(struct in_addr *)he->h_addr)); }

实测下来,TCP连接和DNS解析都正常。如果发现DNS不行,十有八九是运营商或模块没有把DNS服务器地址下发给PPP链路。解决办法有两个:一是手动配置esp_netif_set_dns_info,填入运营商的公共DNS,比如114.114.114.114或者223.5.5.5;二是检查拨号前是否漏了AT+CGDCONT的参数配置,因为有些运营商会通过这个PDP上下文下发DNS地址。

4.5 拨号后的链路保活与自动重连

PPP链路建立起来之后并不是完全放心的。我在实测中发现,如果网络空闲时间比较长,运营商侧的PDP上下文可能会被回收,表现为ESP32这边链路还挂着,但流量已经发不出去了。这个问题需要主动检查。

最简单的保活方案是定期发送一个ICMP Ping包到公网地址,比如每隔30秒ping一次223.5.5.5。如果连续几次没有回应,就主动断开PPP链路并重新拨号。在ESP-IDF里可以用esp_ping组件实现,配置一个定时器即可。

还有一点是SIM卡的流量耗尽或者余额不足时,模块可能不会立刻断链,而是表现为所有包都发到黑洞里。这种时候不要过度依赖自动检测,最好在应用层上报心跳,云端发现设备失联后再远程触发重启。

5. 常见问题排查:拨号启动失败、断线重连、功耗偏高

5.1 拨号一直超时,连IP都拿不到

这个坑我踩了不下五次,最典型的症状是发完ATD*99***1#之后,日志里面一直在重复PPP LCP配置请求,但模块就是没有响应。

排查路径我整理成了速查表:

现象可能原因解决办法
发完AT指令无任何返回接线错误、模块没开机用USB转串口直接连接模块PC测试,确认模块能独立工作
返回ERROR但SIM卡已识别APN没设置或错误确认APN后执行AT+CGDCONT=1,"IP","正确的APN"
LCP一直发送但无应答串口回显没关,或波特率不稳定确认执行过ATE0,试试降低波特率到9600或115200
模块返回NO CARRIER信号差、SIM卡欠费用AT+CSQ看信号值,低于10基本不可用
LCP协商成功但拿不到IPPDP上下文配置问题重新设置AT+CGDCONT,或换一张SIM卡交叉验证

5.2 网络通了但DNS解析失败

我实际遇到过一次这种问题:IP层通了,TCP连接也没问题,但gethostbyname永远超时。后来发现PPP协商成功的事件里,DNS地址字段是空的。这很可能是模块没有从网络侧获取到DNS信息。

解决方式比较粗暴但有效:直接用esp_netif_set_dns_info手动配置DNS地址。我在代码里写了一个逻辑,如果IP_EVENT_PPP_GOT_IP回调中DNS是0.0.0.0,就默认设置为223.5.5.5和114.114.114.114,问题立刻解决。

5.3 模块复位后ESP32侧ppp会话挂了

如果你在运行中手动按下模块复位键,或者模块因为供电波动自动重启,ESP32侧的PPP接口不会自动恢复。这是很常见的一个问题。我在实践中采用了一个看门狗方案:

  1. 建立一个“PPP链路状态”标志位。
  2. 每10秒检测一次这个标志位和最后一次收到TCP ACK的时间。
  3. 如果超过30秒没有任何数据活动,并且状态位显示PPP连接中断,就调用esp_netif_action_disconnect和esp_netif_action_start重新初始化PPP接口。

需要注意的是,重新初始化PPP接口之前,务必先确认4G模块已经完成了重启流程并且串口重新处于AT命令模式。否则你发出的ATD指令会被当成PPP数据,导致协商永远失败。

5.4 功耗偏高,电池供电怎么优化

这个项目的最终形态大概率是电池供电的,所以功耗是一个躲不开的话题。指路给我实测数据:静态待机时纯ESP32跑PPP协议栈大约几十毫安,但4G模块只要注册上网,电流就会稳定在几十毫安到几百毫安之间。如果跑业务数据和频繁心跳,平均功耗会明显抬升。

我的优化思路有这几个方向:

  • 在不需要数据传输时,用AT+CFUN=0把模块切到飞行模式,电流可以降到很低。需要传输数据时再切回AT+CFUN=1重新注册网络。
  • 对于低频上报场景,可以考虑“拨号-传数-断线”的循环,每次上报事件发生时才发起PPP拨号,上报完成后主动挂断。这样4G模块大部分时间处于休眠状态。
  • 在模块侧选择支持PSM(Power Saving Mode)或者eDRX的低功耗模式,部分新版模块固件支持这些省电特性。这方面不同运营商支持程度不一样,需要实测。
  • ESP32本身在深度睡眠下可以做到微安级待机,搭配一个RTC定时器唤醒操作流程,整体待机时间能拉长很多。

5.5 我最后的调试经验

如果说这个项目最值得记住的一句话,那就是:先保证AT指令能稳定工作,再想PPP拨号的事。ES32和4G模块之间的串口链路是最容易出错的一环,但它也是最容易被忽略的一环。很多人(包括我)一上来就想直接跑PPP,结果AT指令还回显乱七八糟,拨号自然是水中花。

我在调试过程中养成了检查日志的习惯。ESP-IDF的日志系统很强大,把ESP_LOG_LEVEL调到ESP_LOG_VERBOSE之后,可以看到lwIP和UART驱动的详细行为。很多问题看着像是“玄学”,打开详细日志之后,原因就一目了然了。比如UART丢字节、PPP帧校验失败、LCP协商超时,这些都会在日志里有明确提示。

另外,养成“按模块节奏走”的习惯也很重要。4G模块开机之后至少要等2到3秒才能接受AT指令,某些模块甚至需要5秒以上。如果ESP32启动速度比较快,一上电就发AT,模块还在初始化,只会收到一串乱码。这个等待时间不能省。

6. 后续还能怎么玩:从拨号到物联网平台

PPP拨号打通之后,后面的路就宽了。ESP32已经是一个有独立公网IP能力的节点,你完全可以在这个基础上继续接入云平台。我自己下一步计划是把MQTT协议跑在这个PPP链路上,对接阿里云的物联网套件。AT指令模式下模块自带的TCP/IP协议栈不太容易和MQTT库配合,但PPP模式下,ESP32的lwIP已经是一个完整的协议栈,移植标准MQTT客户端就是很常规的操作了。

另外还有一个值得尝试的方向是把4G模块的USB接口用起来。ESP32-S3是支持USB Host的,可以直连4G模块的USB口跑RNDIS或ECM协议,数据传输带宽比串口PPP高不少。不过USB Host在资源占用和协议实现上要复杂一些,串口PPP方案稳定后,我准备把那部分也研究一遍。

回到开头说的随身WiFi话题——有些便携WiFi盒子其实就是一颗4G通信芯片加一颗带Wi-Fi的SoC,数据通道同样是走PPP或者NDIS,只是外面加了一层壳子和电源管理。理解了ESP32+4G模块的PPP拨号机制,你其实就已经理解了一大半随身WiFi的工作逻辑。差别只在于,你的ESP32不需要把网络热点分享出去,自己直接消费流量就行。

最后分享一个小细节:如果你用的是物联网卡而不是普通手机SIM卡,请注意大多数物联网卡是默认关闭“语音服务”的,只开放数据业务。这不影响PPP拨号,但如果一开始用ATD*99***1#拨不通,可以先换个普通手机卡试试,排除掉SIM卡本身的限制。这个排查方式简单但有效,我自己就是靠“换卡交叉验证”才确认模块没有问题,最终定位到APN配置上。

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

物联网技术架构详解:从感知层到应用层的完整链路与实战解析

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

作者头像 李华
网站建设 2026/10/3 7:54:46

TC4x看门狗WTU配置实战:窗口计算、功能安全联动与调试踩坑

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

作者头像 李华
网站建设 2026/10/3 7:54:40

UML活动图在PPT流程图中的实战建模与动态交付

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

作者头像 李华
网站建设 2026/10/3 7:54:30

CosyVoice本地部署指南:零基础Windows两小时搞定高质量中文TTS

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

作者头像 李华
网站建设 2026/10/3 7:54:17

Orin NX完整系统迁移空板实战:从dd克隆到引导适配全记录

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

作者头像 李华
网站建设 2026/10/3 7:54:05

DRV8818+STM32F031步进电机工业控制方案

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

作者头像 李华