
简介基于STM32与ESP8266的智能家居系统完整工程包面向物联网开发者、嵌入式学习者以及智能家居方案设计人员解决家庭环境实时监控与远程控制需求。STM32作为主控负责温湿度、光照、烟雾等传感器的数据采集与处理ESP8266通过Wi-Fi将数据上传至云端或App端并支持微信小程序查看室内状态、控制智能灯泡与插座。压缩包共127个文件以C/H源码为主涵盖STM32外设驱动、MQTT通信、ESP8266固件与传感器驱动同时附带了微信小程序前端JS/WXML/WXSS、JSON配置、Keil工程文件、PNG原理图及Markdown说明文档整体仅441KB目录划分清晰便于二次开发与移植。已有244人学习下载适合想从零搭建或改造智能家居原型、需要参考前后端完整联调思路的开发者也可作为基于STM32Wi-Fi模块的课程设计或毕设项目参考。1. 一套不用云平台也能跑的智能家居控制链路把智能插座从米家App里拆出来连到自家局域网里用——这件事在绝大多数Wi-Fi模组方案里做不到因为云端固件和平台绑定死了。但如果主控是STM32F103C8T6Wi-Fi选ESP8266-01S整个控制闭环可以完全绕开第三方云平台传感器数据经STM32的ADC和GPIO采集后通过USART1送给ESP8266ESP8266以MQTT协议发布到自建Broker或局域网服务器微信小程序用WebSocket订阅同一主题就能拿到数据反向控制则走订阅/发布对称路径。这个组合适合做毕设、样板间或小批量智能家居设备的工程师成本在二十元上下每个环节都能断点调试不用抓瞎。下面从硬件分工开始讲再落到标准库代码、AT指令序列和小程序端最后补几个资源包里没写清楚的坑。2. STM32F103与ESP8266-01S的串口分工与传感器挂载2.1 为什么选F103C8T6而不是F407或直接上NodeMCU不少初学者会问ESP8266本身也是单片机能读传感器能联网为什么非要挂一颗STM32原因在于实时性和外设资源。ESP8266虽然便宜但GPIO模拟时序受Wi-Fi协议栈和FreeRTOS调度影响很大DHT22这种单总线传感器在Wi-Fi发包高峰期经常读到数据位翻转温湿度变成乱码。STM32F103C8T6的Cortex-M3内核主频72MHz定时器、ADC、USART都走硬件外设采样时序稳定。另一层原因是标准库封装完整ADC多通道扫描、DMA传输、中断优先级这些在F1上闭眼就能配不需要像F407那样考虑DMA2的流映射冲突——对于智能家居这种低速数据量F1算力已经过剩。在Keil 5里新建标准库工程时记得用Keil5安装STM32芯片包后选择STM32F103C8设备宏定义处写STM32F10X_MD, USE_STDPERIPH_DRIVER。如果手头是C6T6芯片包版本选Low-density但多数智能家居项目直接用MD就够C8T6的64KB Flash能装下MqttKit、传感器驱动和RTOS三件套。2.2 外设资源分配与ESP8266连接原理图这个项目的核心理念是“STM32做采集与决策ESP8266只做网络管道”。为此串口资源分配如下外设功能引脚波特率USART1PA9/PA10与ESP8266通信PA9-TX / PA10-RX115200USART2PA2/PA3调试日志输出PA2-TX / PA3-RX460800ADC1PA0烟雾传感器MQ-2PA012bitADC1PA1光照传感器光敏电阻PA112bitPB12智能继电器控制灯具推挽输出GPIOPB13蜂鸣器报警推挽输出GPIOESP8266与STM32的连接原理图相对简单ESP8266的RX接PA9TX接PA10供电必须单独接3.3V且保证峰值电流500mA以上。很多教程把ESP8266直接接在STM32开发板的3.3V引脚上结果Wi-Fi发射时电压跌落导致复位——这是硬件上最常见的坑后面专门讲。串口1的初始化片段USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate 115200; 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_ITConfig(USART1, USART_IT_RXNE, ENABLE);这段配置里需要注意GPIO_Mode_AF_PP和GPIO_Mode_IN_FLOATING配对。TX引脚必须配置为复用推挽RX引脚保持浮空输入否则电平匹配不对会在ESP8266侧出现乱码。波特率115200对ESP8266的AT固件是默认值但如果你用自编译固件两边的波特率必须一致这里没有协商机制。2.3 串口帧结构设计设备端与Wi-Fi模块的对话协议STM32和ESP8266之间不能直接裸传传感器数值因为网络侧可能需要携带设备ID、数据类型、时间戳控制侧还需要区分“查询”“设置”“恢复出厂”等命令。工程上我一般设计一个带CRC校验的简版帧字节偏移内容说明00xAA帧头10x55帧头校验2命令字0x01查询 / 0x02上报 / 0x10控制3数据长度负载字节数4..N负载JSON字符串或二进制数据N1CRC8从帧头累加到数据区末尾STM32侧收到的每个控制帧都先查CRC再执行CRC不合直接丢弃。这一步能过滤掉绝大部分因串口噪声产生的伪指令——尤其当排线较长ESP8266的TX信号耦合到STM32的RX线上时没有校验的裸协议会随机开灯。3. STM32标准库工程里的数据采集与串口协议解析3.1 TIM2毫秒时基与GPIO模拟时序DHT22温湿度传感器用的是单总线协议虽然片内没有标准的硬件单总线控制器但用TImer做时基 输入捕获中断完全能解析。先配置TIM2为1ms滴答给上层提供delay_us基础函数。void TIM2_Init(void) { TIM_TimeBaseInitTypeDef TIM_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_InitStructure.TIM_Period 72 - 1; // 1MHz计数1ms中断 TIM_InitStructure.TIM_Prescaler 1000 - 1; TIM_InitStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_InitStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_InitStructure); TIM_Cmd(TIM2, ENABLE); }这里TIM_Period71、TIM_Prescaler999的实际效果是72MHz / (72 × 1000) 1kHz即1ms中断一次。如果把Prescaler改成71、Period改成7199就是50Hz的PWM输出时基能直接驱动舵机或LED呼吸灯——同一颗TIM2可以做不同用途但中断回调里要区分TIM_IT_Update事件。DHT22读取的关键在起始信号的时序主机拉低18ms唤醒然后拉高20~40us等待从机响应。之后STM32用轮询GPIO的方式采集电平宽度uint8_t DHT22_ReadByte(void) { uint8_t data 0; for (int i 0; i 8; i) { while (GPIO_ReadInputDataBit(DHT22_GPIO_PORT, DHT22_GPIO_PIN) RESET); delay_us(40); if (GPIO_ReadInputDataBit(DHT22_GPIO_PORT, DHT22_GPIO_PIN) SET) { data | (0x80 i); } while (GPIO_ReadInputDataBit(DHT22_GPIO_PORT, DHT22_GPIO_PIN) SET); } return data; }逻辑是当引脚拉低后开始等待上升沿上升沿到来后延时40us再采样。DHT22数据位的高电平宽度恰好是26us0或70us1延时40us采样落在区分区间内能稳定分辨两种电平。若读到全是0xFF优先检查GPIO是否配置成开漏并外接4.7k上拉电阻而不是怀疑传感器坏了。3.2 ADC多通道规则组连续扫描烟雾传感器MQ-2输出的是模拟电压接在PA0上光敏电阻的分压接PA1。这里用ADC1规则组连续扫描模式这样可以在一次DMA搬运里拿到两个通道的数据。void ADC_Scan_Init(void) { ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_values; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 2; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_Init(DMA1_Channel1, DMA_InitStructure); ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; ADC_InitStructure.ADC_ContinuousConvMode ENABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 2; ADC_Init(ADC1, ADC_InitStructure); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_SoftwareStartConvCmd(ADC1, ENABLE); }DMA配置里DMA_PeripheralDataSize_HalfWord和DMA_MemoryDataSize_HalfWord必须成对出现因为ADC采样结果是12bit用Byte宽度的DMA搬运会截断高四位。adc_values声明为uint16_t[2]索引0对应通道0烟雾索引1对应通道1光照。 转换完成后烟雾浓度阈值判断建议用滑动平均而不是单次采样我一般取最近10次的平均值再与阈值比较防止MQ-2预热期瞬时波动触发误报。3.3 串口接收状态机与CRC校验ESP8266和STM32之间是双向通信接收端必须做状态机。F103的串口1中断里每次只收一个字节把数据喂给状态机而不是等一个完整包再一次性处理——不然在BSS段里开大缓冲容易爆SRAM。typedef enum { FRAME_STATE_HEAD1, FRAME_STATE_HEAD2, FRAME_STATE_CMD, FRAME_STATE_LEN, FRAME_STATE_DATA, FRAME_STATE_CRC } frame_state_t; void USART1_IRQHandler(void) { uint8_t byte USART_ReceiveData(USART1); static frame_state_t state FRAME_STATE_HEAD1; static uint8_t frame[64]; static uint8_t index 0; switch (state) { case FRAME_STATE_HEAD1: if (byte 0xAA) state FRAME_STATE_HEAD2; break; case FRAME_STATE_HEAD2: if (byte 0x55) { state FRAME_STATE_CMD; index 0; } else state FRAME_STATE_HEAD1; break; case FRAME_STATE_CMD: frame[index] byte; state FRAME_STATE_LEN; break; case FRAME_STATE_LEN: frame[index] byte; frame_len byte; state FRAME_STATE_DATA; break; case FRAME_STATE_DATA: frame[index] byte; if (index frame_len 2) state FRAME_STATE_CRC; break; case FRAME_STATE_CRC: if (CRC8_Check(frame, index) byte 0x00) Process_Command(frame, frame_len); state FRAME_STATE_HEAD1; break; default: state FRAME_STATE_HEAD1; break; } }CRC8校验我采用多项式0x31X8 X5 X4 1查表法CPU占用极低在72MHz主频下单片处理一个64字节的帧只需要几十微秒对中断处理时间完全无压力。注意帧长度字段存放在frame[1]位置而数据区从frame[2]开始很多移植代码栽在这数据字节数算错以后CRC永远过不了表现就是STM32偶尔能收到消息但从不执行控制指令。4. ESP8266的MQTT固件改造与微信小程序上报链路4.1 方案AESP8266刷AT固件STM32内嵌MqttKit资源包里出现MqttKit.c并不是偶然它意味着这套系统把MQTT协议栈放在了STM32侧ESP8266只承担透明的TCP透传工作。这种方案的好处是Wi-Fi模块可以被替换成任何串口网卡不绑定乐鑫生态缺点是STM32的Flash要多占约12KBF103C8T6在剩余空间上仍够用。关键配置是ESP8266进入透传模式ATRST ATCWMODE1 ATCWJAPMySmartHome,password123 ATCIPSTARTTCP,192.168.1.100,1883 ATCIPMODE1 ATCIPSENDATCIPMODE1开启透传后STM32发往串口的所有字节都会原封不动地从TCP连接发出去反过来TCP收到的数据直接出现在串口RX上。这时MqttKit只需要维护CONNECT、PUBLISH、SUBSCRIBE三个报文结构不需要关心Wi-Fi链路细节。透传模式下重启ESP8266会丢连接所以STM32要在启动时等待ESP8266返回WIFI GOT IP和CONNECT OK超时5秒后自动重连这是资源包里没有明说的时序依赖。4.2 方案BESP8266跑AT-MQTT固件直接响应主题指令如果你不想让STM32的代码量膨胀可以把ESP8266换成乐鑫官方AT固件v2.2.0以上版本它原生提供ATMQTTUSERCFG和ATMQTTCONN指令。STM32侧只需要解析固件返回的MQTTSUB事件即可代码量能砍掉一半左右。ATMQTTUSERCFG0,1,smart_home_1,user,secret,0,0, ATMQTTCONN0,192.168.1.100,1883,1 ATMQTTSUB0,device/1/cmd,1第一个参数0代表链路ID固定填0即可第二个参数1表示使用SSL实际上这里填的是(0关闭/1开启)——但一般家庭局域网不用SSL填0可以减少握手开销。使用ESP8266入门教程里最常见的问题是ATMQTTUSERCFG中的client_id不要重名否则Broker会把旧连接踢掉表现为ESP8266反复掉线重连日志里出现连接被拒绝。4.3 微信小程序端的数据订阅与指令下发小程序侧不能直连原生TCP要么通过自建网关做HTTP转发要么用WebSocket桥接MQTT Broker。轻量做法是在现有Broker旁边加一个websocket-mqtt代理小程序里用wx.connectSocket连接后按MQTT协议发送PUBLISH。这里给出一个简化版的小程序控制代码const mqtt require(../../utils/mqtt.min.js); const client mqtt.connect(ws://192.168.1.100:8083/mqtt, { clientId: wechat_ Date.now(), username: user, password: secret }); client.on(connect, function () { client.subscribe(device/1/status, { qos: 1 }); }); client.on(message, function (topic, message) { const data JSON.parse(message.toString()); if (topic device/1/status) { this.setData({ temperature: data.temp, humidity: data.hum, smoke: data.smoke }); } }); function toggleRelay() { client.publish(device/1/cmd, JSON.stringify({ relay: 1 }), { qos: 1 }); }需要说明的是mqtt.min.js是小程序常用的MQTT客户端封装底层基于WebSocket与原生TCP的MQTT在报文格式上完全一致。{ qos: 1 }表示至少送达一次如果网络抖动导致重复消息STM32侧要根据命令序号去重——我一般在控制帧里加一个递增的sequence字段STM32只执行比上次序号大的指令。这样即使小程序重发继电器也不会来回抖。4.4 Broker主题设计主题QoS方向负载示例device/1/status1设备→APP{temp:26.5,hum:48,smoke:0}device/1/cmd1APP→设备{seq:12,relay:1}device/1/ack1设备→APP{seq:12,result:ok}gateway/1/online0设备→APP{ip:192.168.1.106}主题层级用设备ID区分多设备适合扩展到套房、办公室场景。QoS0的心跳主题即使丢失也无所谓但指令和状态反馈必须用QoS1否则偶尔丢一条控制指令用户体验是灾难性的。5. 供电复位、串口回环与OTA遗留的三个必查项第一ESP8266的3.3V供电不能和STM32共用LDO。ESP8266发射瞬间会拉出300mA以上的电流而很多F103核心板的AMS1117-3.3输出能力在800mA左右看起来够用但DC-DC的带宽不足导致纹波瞬间抬高到200mV以上STM32复位阈值刚好卡在边缘。最稳的做法是给ESP8266单独的ME6211稳压芯片供电并在VCC和GND之间放470uF电解电容和100nF陶瓷电容并联。我之前调试时遇到“ESP8266单独工作正常、一接STM32就随机重启”查到最后就是共地之后LDO自激。第二串口回环是一个隐蔽问题。如果ESP8266的TX和RX信号线在PCB上并行走线超过5cmWi-Fi射频会通过串口线耦合进RX数据线空闲状态下STM32的串口中断会不断收到0xFF。解决方式有两个一是把USART1波特率降到57600缩短数据线上的翻转沿时间二是在代码里打开串口接收超时机制超过200ms没收到一个帧就清空FIFO并复位状态机。相比于加磁珠这两个软件措施更直接。第三OTA升级区域要提前规划。F103C8T6的64KB Flash被拆成两个16KB区用于Bootloader和App区如果出厂时不规划分区后面做在线升级只能把整个Flash擦掉重烧要把STM32的Boot0跳到高电平后才能进入串口下载。资源包里如果带了stm32f10x_flash.c说明固件代码里已经预留了Flash写接口可以在App区直接接收分块镜像分块写入时每块512字节写完一页读取校验一次失败就回滚到上一页。最后一个建议把这套系统往企业应用推进时把“家庭”这个主题设计成可配置参数。酒店和办公室场景里每个房间本质上就是一台独立的STM32网关它们共享同一套MQTT Broker——你只需要让管理端根据device/前缀分发权限就能在小程序里同时管理几十个房间的灯光和空调。如果要加人工智能层面的自动调节最可靠的做法是在STM32上放一个简单的线性回归模型输入是过去24小时的温湿度序列输出是空调目标温度训练在云端做推理放MCU端这样断网时设备也能自我决策。这类系统做出来的价值不在于功能多炫而在于每个环节都能复现、能调试、能维护。本文还有配套的精品资源点击获取