news 2026/9/20 14:45:36

STM32+ESP8266+OneNet智能家居实战:MQTT协议从硬件到云端全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32+ESP8266+OneNet智能家居实战:MQTT协议从硬件到云端全解析

1. 项目缘起与整体设计思路

1.1 为什么选择 STM32 + OneNet 这套组合

这个项目是我 2022 年做的一个智能家居控制系统,核心架构就是 STM32 做主控,ESP8266 负责联网,数据上云走 OneNet 平台,通信协议用 MQTT。当时市面上做智能家居的方案很多,有直接用 ESP32 单芯片搞定的,也有用树莓派跑 HomeAssistant 的,但我最后还是选了 STM32 + ESP8266 这个组合,原因有几个。

第一是成本可控。STM32F103C8T6 这颗芯片当时散买也就十块钱出头,ESP8266-01S 模块五六块钱,加上一些继电器、传感器,整个硬件成本能压在五十块以内。对于学生做毕设或者个人练手来说,这个成本非常友好。第二是分工清晰,STM32 负责实时性要求高的本地控制逻辑,比如按键响应、传感器轮询、继电器状态管理,ESP8266 只负责透传数据到云端,各司其职,调试的时候也容易定位问题出在哪一环。第三是学习价值高,你能同时接触到裸机开发、串口通信、AT 指令、MQTT 协议、云平台接入这一整条链路,比直接用一个封装好的模组学到的东西多得多。

OneNet 这个平台当时选它,主要是因为它对个人开发者免费,MQTT 接入文档写得还算清楚,而且支持物模型和多协议接入。虽然后来 OneNet 的版本迭代比较多,但 2022 年那会儿的旧版 MQTT 接入方式用起来还是挺顺手的。

1.2 系统整体架构拆解

整个系统的数据流是这样的:STM32 通过 GPIO 读取温湿度传感器(我用的是 DHT11)的数据,同时管理继电器模块控制家电开关。STM32 和 ESP8266 之间通过 UART 串口通信,STM32 发 AT 指令给 ESP8266,ESP8266 负责建立 TCP 连接、完成 MQTT 握手、发布和订阅消息。云端 OneNet 收到数据后可以在网页端或者手机 App 上展示,同时也能下发控制指令回来。

这里有个关键设计决策:MQTT 的报文拼装到底放在 STM32 端还是 ESP8266 端。我选择的是在 STM32 端拼装 MQTT 报文,ESP8266 只做透传。为什么这么选?因为 ESP8266 如果刷了支持 MQTT 的固件,虽然能省掉 STM32 拼报文的工作,但固件版本五花八门,AT 指令集不统一,调试起来反而更麻烦。让 ESP8266 老老实实做透传,STM32 端自己控制 MQTT 报文的每一个字节,虽然代码量大一些,但可控性最强,出了问题也容易抓包分析。

提示:如果你用的是 ESP8266 的 AT 固件,务必确认固件版本支持AT+CIPSTART建立 TCP 连接和AT+CIPSEND发送数据,这两个指令是整个透传方案的基础。

1.3 功能边界与适用人群

这个系统实现的功能包括:温湿度数据实时上报、继电器控制家电开关、云端数据展示、远程控制指令下发。没有做视频监控、语音控制这些复杂功能,因为那些需要更强的算力和带宽,超出了 STM32F103 的能力范围。

适合谁来参考?如果你正在做基于 STM32 的毕业设计,或者想入门物联网开发但不知道从哪下手,这个项目是一个很好的练手选择。它涉及的知识点覆盖面广,但每个环节的难度都不算太高,只要你有基本的 C 语言基础和单片机开发经验就能跟上。如果你是完全零基础的小白,建议先补一下 STM32 的 GPIO 操作和串口通信,再来看这个项目会轻松很多。

2. 硬件选型与核心细节解析

2.1 主控芯片与外围模块的搭配逻辑

主控我用的 STM32F103C8T6,也就是常说的“蓝板”或“最小系统板”。这颗芯片是 Cortex-M3 内核,72MHz 主频,64KB Flash,20KB RAM,对于这个项目来说绰绰有余。选它还有一个很实际的原因:资料多。江科大、正点原子这些教程铺天盖地,遇到问题搜一下基本都能找到答案。

ESP8266 模块我用的是 ESP-01S,体积小、价格便宜,但有个坑要注意:它的供电必须是 3.3V,而且峰值电流能到 300mA 左右,如果你直接用 STM32 板子上的 3.3V 输出给它供电,很可能会因为电流不够导致模块反复重启。我的做法是单独用一个 AMS1117-3.3 稳压芯片给它供电,输入接 5V,输出 3.3V,这样稳定性好很多。

继电器模块用的是常见的 4 路 5V 继电器,光耦隔离的那种。这里有个细节:STM32 的 GPIO 输出是 3.3V,而继电器模块的输入信号如果是 5V 电平触发,直接接上去可能驱动不了。我选的这款继电器模块支持 3.3V 触发,所以可以直接连。如果你手头的继电器模块只支持 5V 触发,中间需要加一个电平转换电路,或者用三极管做驱动。

DHT11 温湿度传感器是单总线协议,接一个 GPIO 就行。它的精度一般,温度 ±2℃,湿度 ±5%,但对于智能家居这种场景够用了。如果你对精度要求高,可以换成 SHT30 或者 AHT20,走 I2C 接口,代码稍微改一下就行。

2.2 STM32 端 MQTT 报文拼装的实现要点

MQTT 协议的核心报文结构分为固定头、可变头和有效载荷三部分。固定头第一个字节是报文类型和标志位,第二个字节开始是剩余长度。这里有个容易踩坑的地方:剩余长度的编码方式。如果剩余长度小于 128 字节,用一个字节表示就行;如果大于等于 128 字节,需要用多个字节,每个字节的低 7 位有效,最高位表示是否还有后续字节。

我在 STM32 端实现的时候,写了一个MQTT_EncodeRemainingLength函数专门处理这个编码。举个例子,如果剩余长度是 200,二进制是 11001000,那么第一个字节存低 7 位即 1001000,最高位置 1 表示还有后续,得到 0xC8;第二个字节存剩下的 1,得到 0x01。最终编码结果是0xC8 0x01

CONNECT 报文的可变头里需要填协议名、协议级别、连接标志、保持连接时间。协议名是 “MQTT”,协议级别用 0x04 表示 MQTT 3.1.1。连接标志位里要设置用户名密码标志、清理会话标志等。有效载荷里依次填客户端 ID、用户名、密码。OneNet 旧版 MQTT 接入时,客户端 ID 用设备 ID,用户名用产品 ID,密码用鉴权信息,这些在 OneNet 控制台创建设备后都能拿到。

PUBLISH 报文用来上报数据,主题格式一般是$dp或者自定义的 topic。OneNet 的物模型接入方式下,数据需要按照它规定的 JSON 格式上传。比如上传温度和湿度,payload 大概是{"temp":25,"humi":60}这样的结构。SUBSCRIBE 报文用来订阅控制指令的下发主题,订阅成功后云端下发的消息会通过 ESP8266 透传给 STM32,STM32 解析后执行相应动作。

2.3 ESP8266 的 AT 指令配置流程

ESP8266 上电后需要依次发送一系列 AT 指令完成初始化。我整理了一个标准的配置流程:

AT+RST // 复位模块 AT+CWMODE=1 // 设置为 Station 模式 AT+CWJAP="WiFi名称","WiFi密码" // 连接路由器 AT+CIPMUX=0 // 设置单连接模式 AT+CIPSTART="TCP","183.230.40.39",6002 // 连接 OneNet MQTT 服务器 AT+CIPMODE=1 // 开启透传模式 AT+CIPSEND // 进入透传发送

这里有几个关键点。AT+CIPSTART里的 IP 和端口是 OneNet 旧版 MQTT 服务器的地址,如果你用的是新版 OneNet 或者别的平台,这个地址需要相应修改。AT+CIPMODE=1开启透传模式后,之后发送的所有数据都会直接通过 TCP 发出去,不再需要每次发AT+CIPSEND。但要注意,透传模式下退出需要用+++,而且前后要留至少 1 秒的静默时间,否则+++会被当成普通数据发出去。

注意:ESP8266 的 AT 指令响应时间不确定,尤其是AT+CWJAP连接路由器可能需要几秒钟。STM32 端在发送指令后需要等待模块返回 “OK” 或 “>” 等预期响应,不能发完就立刻发下一条。我一般用超时重试机制,每条指令最多等 5 秒,超时就重发,重试 3 次还不行就复位模块重新来。

2.4 数据上报频率与心跳机制的设计

数据上报频率我设的是 5 秒一次。为什么选 5 秒?因为 DHT11 本身采样周期就不宜太快, datasheet 建议间隔不低于 1 秒,而 MQTT 的 PUBLISH 报文如果发得太频繁,ESP8266 的发送缓冲区和网络带宽都会有压力。5 秒是一个比较平衡的值,既能保证数据的实时性,又不会给系统造成太大负担。

MQTT 的 Keep Alive 我设的是 60 秒。这个值决定了客户端在没有发送任何报文的情况下,最多多久必须发一次 PINGREQ 心跳包。如果设得太短,会增加不必要的网络流量;设得太长,服务器可能认为客户端已经掉线。60 秒是 MQTT 协议推荐的常见值,配合 5 秒一次的数据上报,实际上心跳包很少需要单独发,因为数据上报本身就起到了保活的作用。

但这里有个隐患:如果网络断开或者 OneNet 服务器主动断开连接,STM32 端需要能检测到并重新连接。我的做法是在每次发送 PUBLISH 报文后检查 ESP8266 的返回,如果连续几次发送失败,就触发重连流程:先发+++退出透传,再重新走一遍AT+CIPSTART和 MQTT CONNECT 流程。

3. 实操过程与核心环节实现

3.1 开发环境搭建与工程配置

开发环境我用的是 Keil MDK5,配合 STM32F1 的芯片包。安装 Keil5 的时候有个坑:如果你电脑上同时装了 Keil C51,两者可能会冲突。我的建议是分开装在不同目录,或者干脆用 STM32CubeIDE,免费而且没有版权问题。

新建工程后,需要把 STM32F10x 标准外设库或者 HAL 库添加到工程里。我用的标准库,因为代码量小、执行效率高,而且网上例程多。需要添加的文件包括启动文件startup_stm32f10x_md.s、系统初始化文件system_stm32f10x.c、以及 GPIO、USART、RCC 这些外设的驱动文件。

时钟配置很关键。STM32F103 外部晶振是 8MHz,经过 PLL 倍频到 72MHz 作为系统时钟。APB1 总线时钟是 36MHz,APB2 是 72MHz。USART1 挂在 APB2 上,所以波特率计算时用的是 72MHz。我用的波特率是 115200,这个速率和 ESP8266 通信足够快,而且误差小。

串口初始化代码大概长这样:

void USART1_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // PA9 TX GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); // PA10 RX GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); USART_Cmd(USART1, ENABLE); }

3.2 DHT11 驱动与数据采集

DHT11 是单总线器件,时序要求比较严格。它的通信过程是:主机拉低总线至少 18ms 作为起始信号,然后释放总线,DHT11 响应后会拉低总线 80us 再拉高 80us,之后开始传输 40 位数据,每位数据以 50us 低电平开始,高电平的持续时间决定数据是 0 还是 1——26-28us 表示 0,70us 表示 1。

在 STM32 上实现的时候,需要用微秒级延时。我直接用 SysTick 定时器做延时基准,delay_us函数用循环计数实现。读数据的时候要关中断,因为中断会打断时序导致读取失败。实测下来,如果不关中断,读取成功率大概只有六七成,关了中断之后基本百分之百成功。

u8 DHT11_Read_Byte(void) { u8 i, dat = 0; for(i = 0; i < 8; i++) { while(DHT11_DATA_IN() == 0); // 等待50us低电平结束 delay_us(40); dat <<= 1; if(DHT11_DATA_IN() == 1) dat |= 1; while(DHT11_DATA_IN() == 1); // 等待剩余高电平结束 } return dat; }

读出来的数据是 5 个字节:湿度整数、湿度小数、温度整数、温度小数、校验和。校验和等于前四个字节相加的低 8 位。如果校验不通过,直接丢弃这次数据重新读。

3.3 MQTT 连接 OneNet 的完整流程

连接 OneNet 的 MQTT 服务器,第一步是建立 TCP 连接。ESP8266 发送AT+CIPSTART="TCP","183.230.40.39",6002,如果返回 “CONNECT” 和 “OK” 就说明 TCP 通了。然后发送 MQTT CONNECT 报文。

CONNECT 报文的拼装我写了一个函数,把各个字段按顺序填入缓冲区。客户端 ID 用 OneNet 设备详情里的设备 ID,用户名用产品 ID,密码用鉴权信息。这些字符串需要先算长度,然后按 MQTT 的字符串编码格式(2 字节长度 + 内容)填入。

发送 CONNECT 报文后,OneNet 会返回 CONNACK 报文,固定头是0x20 0x02,可变头第一个字节是连接确认标志,0x00 表示连接成功,非零表示失败。我在这里加了一个判断,如果 CONNACK 返回失败,就打印错误码方便排查。

连接成功后,需要发送 SUBSCRIBE 报文订阅控制指令主题。OneNet 旧版 MQTT 的下发主题一般是$creq/开头的,具体格式在设备详情页能看到。订阅成功后,云端下发的控制指令会通过 ESP8266 透传到 STM32 的串口接收缓冲区,STM32 解析 JSON 后执行相应操作。

数据上报用 PUBLISH 报文,QoS 等级我用的 0,也就是最多发一次,不保证到达。为什么不用 QoS 1?因为 QoS 1 需要处理 PUBACK 确认报文,代码复杂度增加不少,而且对于温湿度这种周期性上报的数据,丢一两个包影响不大,下一个周期就补上了。但如果是控制指令的下发,建议用 QoS 1,确保指令不丢。

3.4 云端数据展示与远程控制联调

OneNet 控制台里可以创建数据流模板,把温度、湿度、继电器状态这些数据流定义好。设备上线后,数据流会自动出现在设备详情页,可以看实时数据曲线。也可以创建应用,用拖拽的方式做一个简单的数据展示界面,支持手机端查看。

远程控制的联调过程是这样的:在 OneNet 控制台或者手机 App 上点击控制按钮,云端会向设备订阅的主题下发一条 JSON 指令,比如{"relay1":1}表示打开继电器 1。STM32 收到后解析这个 JSON,把对应的 GPIO 拉高,继电器吸合,家电通电。同时 STM32 会把这个状态变化作为一条数据上报回去,云端界面上的状态图标就会更新。

联调的时候我遇到过一个坑:OneNet 下发的 JSON 指令里,字段名和我在 STM32 端解析用的字符串不一致,导致解析失败。后来我在 OneNet 的物模型定义里把字段名固定下来,STM32 端也按同样的字段名解析,问题就解决了。所以建议在项目开始之前,先把云端和设备的字段名约定好,避免后期来回改。

提示:调试 MQTT 通信的时候,可以用 MQTTX 这个客户端工具模拟设备连接 OneNet,先验证云端配置是否正确,再调 STM32 端的代码。这样能把问题范围缩小,不用每次都烧录 STM32 来测试。

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

4.1 ESP8266 连接失败与超时问题

ESP8266 连接 OneNet 失败是最常见的问题,表现是AT+CIPSTART返回 “ERROR” 或者 “CLOSED”。排查思路按顺序来:先确认 WiFi 是否连接成功,发AT+CWJAP?查询当前连接状态;如果 WiFi 没问题,检查 OneNet 的 IP 和端口是否写对,旧版 MQTT 服务器地址是183.230.40.39:6002,新版可能不一样;如果地址也对,那可能是模块固件版本太老,不支持某些 AT 指令,需要重新刷固件。

刷 ESP8266 固件的时候,接线要注意:GPIO0 拉低进入下载模式,GPIO2 拉高,CH_PD 拉高。刷写工具用官方的 Flash Download Tool,选对固件文件和地址。刷完之后 GPIO0 要拉高才能正常运行。我遇到过刷完固件后模块没反应的情况,后来发现是 GPIO0 忘了拉高,模块一直停在下载模式。

还有一个坑是a fatal esptool.py error occurred: failed to connect to esp8266: timed out,这个错误一般是串口被占用或者接线不对。检查一下串口线是不是只接了 TX 和 RX,没有接地线;或者串口被其他软件占用了,关掉其他串口工具再试。

4.2 MQTT 连接被服务器拒绝的排查

CONNACK 返回非零错误码,说明 MQTT 连接被 OneNet 拒绝了。常见的错误码和原因:

错误码含义排查方向
0x01协议版本不支持检查协议级别是否填的 0x04
0x02客户端 ID 无效检查设备 ID 是否和 OneNet 上一致
0x04用户名或密码错误检查产品 ID 和鉴权信息
0x05未授权检查设备是否已在 OneNet 上激活

我遇到最多的是 0x04,原因是鉴权信息填错了。OneNet 的鉴权信息是一串比较长的字符串,复制的时候容易多复制空格或者少复制字符。建议直接从控制台复制,粘贴到代码里之后再用串口打印出来核对一遍。

还有一个隐蔽的问题:MQTT 报文的剩余长度编码错误。如果剩余长度算错了,服务器解析报文时会出错,可能直接断开连接。我写了一个校验函数,在发送前把拼好的报文打印出来,人工核对一下固定头和剩余长度是否正确。

4.3 数据上报成功但云端不显示

有时候 STM32 这边显示 PUBLISH 报文发送成功了,但 OneNet 控制台上看不到数据。这种情况一般是数据格式不对。OneNet 的物模型接入要求数据按照特定的 JSON 格式上传,比如{"temp":25},如果字段名和物模型里定义的不一致,云端会丢弃这条数据。

排查方法:在 OneNet 控制台的设备日志里看有没有收到数据。如果日志里有数据但解析失败,说明格式有问题;如果日志里根本没有数据,说明 PUBLISH 报文没有真正到达服务器,可能是主题写错了或者 QoS 等级不匹配。

还有一种可能是数据流没有创建。OneNet 旧版需要先在数据流模板里定义好数据流名称,设备上报的数据才能被正确解析和展示。如果数据流名称对不上,数据会被丢弃。

4.4 系统长时间运行后死机或重启

这个问题我调试了很久才找到原因。系统跑几个小时之后,STM32 会莫名其妙死机或者重启。排查下来发现是串口接收缓冲区溢出导致的。ESP8266 透传模式下,云端下发的数据会随时通过串口发过来,如果 STM32 的串口接收中断处理不及时,数据就会丢失,严重时会导致缓冲区指针越界,引发 HardFault。

解决办法是加大串口接收缓冲区,并且在中断里只做数据搬运,不做解析。解析工作放到主循环里做,这样中断处理时间短,不容易丢数据。另外,在解析 JSON 的时候加了长度校验,防止越界访问。

还有一个原因是电源问题。ESP8266 在发送数据时电流会突然增大,如果供电不足会导致电压跌落,STM32 可能因此复位。我在 ESP8266 的电源引脚旁边并了一个 100uF 的电解电容和一个 0.1uF 的陶瓷电容,电压跌落问题明显改善。

4.5 常见问题速查表

现象可能原因解决方法
ESP8266 无响应供电不足或接线错误单独供电,检查 TX/RX 交叉连接
AT+CWJAP 返回 ERRORWiFi 密码错误或信号弱核对密码,靠近路由器测试
TCP 连接失败IP 端口错误或模块固件问题核对 OneNet 地址,刷新固件
CONNACK 返回 0x04鉴权信息错误重新复制鉴权信息,核对无空格
数据上报后云端无显示JSON 格式或字段名不匹配核对物模型定义,查看设备日志
运行一段时间后死机串口缓冲区溢出或电源不稳加大缓冲区,增加滤波电容
继电器不动作GPIO 电平不匹配或驱动能力不足检查继电器触发电压,加驱动电路

提示:调试的时候建议在 STM32 端加一个 LED 指示灯,用不同的闪烁频率表示不同的状态,比如慢闪表示正在连接 WiFi,快闪表示 MQTT 已连接,常亮表示数据上报正常。这样不用接串口线就能大致判断系统运行状态,非常实用。

5. 项目扩展与个人实操体会

5.1 可以继续往上加的功能

这个基础版本跑通之后,可以扩展的方向挺多的。比如加一个 OLED 屏幕,本地显示温湿度和继电器状态,这样即使网络断了也能看到当前数据。OLED 用 I2C 接口,STM32 的 I2C 外设直接驱动,代码量不大。

还可以加红外接收头,学习家里的空调遥控器编码,实现红外控制空调。或者加一个蜂鸣器,温度超过阈值的时候本地报警。这些外设的驱动都不复杂,关键是 GPIO 的分配要提前规划好,别到时候引脚不够用。

如果想让控制更灵活,可以把继电器换成可控硅或者固态继电器,支持调光或者调速。但这就涉及到交流电了,安全风险比较高,不建议没有电工基础的人尝试。

5.2 我在这个项目里踩过的坑

最大的坑是 ESP8266 的固件版本问题。我一开始用的模块是卖家刷好的 AT 固件,版本比较老,不支持AT+CIPMODE=1透传模式。后来重新刷了安信可的最新 AT 固件才搞定。所以建议拿到模块后先发AT+GMR查一下固件版本,确认支持你需要的指令。

第二个坑是 MQTT 报文的剩余长度编码。我一开始只处理了小于 128 字节的情况,结果上报数据稍微长一点就出问题。后来补上了多字节编码的逻辑才稳定。这个细节在 MQTT 协议文档里有详细说明,但很容易被忽略。

第三个坑是 DHT11 的时序。我一开始没关中断,读取成功率很低。后来在读取函数前后加了__disable_irq()__enable_irq(),问题就解决了。但要注意关中断的时间不能太长,否则会影响串口接收。DHT11 一次读取大概需要 4ms 左右,这个时间关中断是可以接受的。

5.3 给后来者的几点建议

如果你打算做类似的项目,我的建议是先把 ESP8266 单独调通,用串口助手手动发 AT 指令,确认能连接 WiFi、能建立 TCP、能发 MQTT 报文。这一步通了之后再接 STM32,用代码自动发指令。这样能把问题分离开,不会一上来就面对一堆不确定因素。

另外,OneNet 的文档虽然不算特别详细,但设备日志功能很有用。调试的时候多看看设备日志,能看到云端到底收到了什么、解析结果是什么,比盲目猜要高效得多。

最后,代码里一定要加足够的串口打印信息。STM32 的串口资源很宝贵,但调试阶段可以牺牲一个串口专门用来打印日志。等系统稳定了再把日志精简掉。我习惯用printf重定向到串口,配合#define DEBUG宏来控制日志开关,发布的时候关掉宏就行。

这个项目我从硬件搭建到云端联调,前后花了大概两周的业余时间。中间断断续续踩了不少坑,但每解决一个问题,对整个链路的理解就深一层。现在回头看,STM32 + ESP8266 + OneNet 这套方案虽然不算最新,但作为学习物联网全栈开发的入门项目,性价比还是很高的。

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

微信占满C盘?三步迁移文件+清理缓存,彻底释放空间

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

作者头像 李华
网站建设 2026/9/20 14:44:04

GetQzonehistory:扫码一次,把QQ空间历史说说全部导出成Excel

GetQzonehistory&#xff1a;扫码一次&#xff0c;把QQ空间历史说说全部导出成Excel 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 想把QQ空间的历史说说导出来&#xff1f;网页版翻到…

作者头像 李华
网站建设 2026/9/20 14:43:02

VMware与Hyper-V冲突全解析:从报错原理到关闭VBS及兼容模式配置

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

作者头像 李华
网站建设 2026/9/20 14:42:04

STM32H7双核FreeRTOS实时调度优化实践

简介&#xff1a;STM32H7双核架构下的FreeRTOS实时调度算法优化实践&#xff0c;是一份面向嵌入式开发者的技术文档&#xff0c;核心聚焦双核环境下的实时任务调度优化。文档从STM32H7的Cortex-M7/M4双核结构、共享资源与通信机制切入&#xff0c;系统讲解FreeRTOS任务调度原理…

作者头像 李华
网站建设 2026/9/20 14:37:51

轻量推理引擎colibri:蜂鸟式端侧部署与调优实战

如果你去搜索 colibri&#xff0c;会发现它并不是某一个公司独占的项目名&#xff0c;嵌入式模块、AI 工具链、甚至鸟类识别 App 都可能用它。但把它放到边缘计算和端侧智能这个语境里&#xff0c;colibri 几乎已经成了一个形容词&#xff1a;小、快、省。这个词在很多语言里都…

作者头像 李华
网站建设 2026/9/20 14:33:22

LangChain前端架构与开发实践全解析

1. LangChain前端技术全景解析作为一位长期从事AI应用开发的工程师&#xff0c;我见证了LangChain如何从最初的后端框架逐步扩展到完整的前后端开发生态。今天我们就来深入剖析LangChain前端技术的核心架构与应用实践。2. LangChain前端核心架构2.1 组件化设计理念LangChain前端…

作者头像 李华