嵌入式WiFi智能插座编程:实现远程开关、电量统计与定时控制功能
智能插座不是“ESP接继电器+网页点一下”就完事。量产固件要把三件事拆开:远程指令只改状态、计量芯片独立采数、定时任务非阻塞跑。主循环里一堆delay、继电器直接跟WiFi回调耦合,基本就是掉线卡死、定时漂移、电量跳变的起点。
一、系统分层
典型架构:
云端/MQTT ──> WiFi模块(ESP8266/ESP32)
│ UART/AT或直跑固件
▼
主控逻辑(状态机)
┌────────┴────────┬──────────┐
继电器驱动 计量芯片 按键/LED
(GPIO) (HLW8032/CSE) (本地覆盖)
WiFi侧只负责收指令、回状态;继电器归本地状态机管;计量芯片走独立串口或脉冲采集;定时任务挂在系统节拍上。
二、远程开关:指令改状态,别直接动继电器
MQTT下发{"switch":1},固件别在回调里直接set_gpio,先更状态再统一执行:
typedef struct {
uint8_t relay_req; // 请求状态
uint8_t relay_act; // 实际状态
uint8_t lock; // 童锁
} socket_ctl_t;
void mqtt_on_msg(const char*json){
if(json_get_bool(json,"switch"))
ctl.relay_req = 1;
else
ctl.relay_req = 0;
// 不在这里操作GPIO
}
void relay_task_10ms(void){
if(ctl.lock) return;
if(ctl.relay_req != ctl.relay_act){
gpio_write(RELAY_PIN, ctl.relay_req);
ctl.relay_act = ctl.relay_req;
mqtt_publish_state(ctl.relay_act); // 状态回传
nv_save_relay(ctl.relay_act); // 断电记忆
}
}
好处:本地按键、APP、定时任务都走relay_req,不会互相打架;状态变化才上报,不刷屏。
按键本地覆盖同理:
if(key_edge()){
ctl.relay_req = !ctl.relay_act;
}
三、电量统计:走计量芯片,不靠ADC瞎算
电阻采样+MCU ADC算功率误差大、校准烦。正经用HLW8032/CSE7759B,UART直接读电压电流有功功率;HLW8012类则是测CF脉冲周期。
HLW8032串口4800bps,帧格式固定,解析核心:
uint8_t hlw_rx[24];
void meter_parse(void){
if(uart_recv(hlw_rx,24)!=24) return;
if(hlw_rx[0]!=0x55) return; // 帧头
uint16_t v = (hlw_rx[2]<<8)|hlw_rx[3];
uint16_t i = (hlw_rx[4]<<8)|hlw_rx[5];
uint16_t p = (hlw_rx[8]<<8)|hlw_rx[9];
volt = v * V_COEF;
curr = i * I_COEF;
power = p * P_COEF; // 系数产测标定
}
电能累计用功率对时间积分,别每帧清零:
static float energy_kwh;
void energy_accum(uint32_t ms){
static uint32_t last;
uint32_t dt = ms-last; last=ms;
energy_kwh += power * (dt/1000.0f) / 3600.0f / 1000.0f;
}
要点:
系数放NV,出厂标定点存进去
功率<5W当待机,可不上报
电流过流/功率过载→强制断继电器
串口电平转换:计量芯片5V、WiFi模块3.3V要分压
四、定时控制:非阻塞状态机
别写delay(3600000)等定时到——WiFi回调、MQTT心跳全卡死。用系统节拍比较:
typedef struct {
uint32_t on_sec;
uint32_t off_sec;
uint8_t enable;
uint8_t repeat;
} timer_t;
timer_t timers[4];
void timer_task(uint32_t now_sec){
for(int k=0;k<4;k++){
if(!timers[k].enable) continue;
if(timers[k].on_sec && now_sec>=timers[k].on_sec){
ctl.relay_req = 1;
timers[k].on_sec = timers[k].repeat? timers[k].on_sec+86400:0;
}
if(timers[k].off_sec && now_sec>=timers[k].off_sec){
ctl.relay_req = 0;
timers[k].off_sec = timers[k].repeat? timers[k].off_sec+86400:0;
}
}
}
时间源用SNTP对时后的RTC秒;没联网先用MCU节拍近似,联网后校正。
倒计时的写法:
if(timer.countdown){
if(now_sec >= timer.off_at)
ctl.relay_req = 0;
}
时间比较一律用now-last>=interval,别用now>target,49天回卷会炸。
五、掉电记忆与过载保护
断电再来电,继电器状态从NV读,别默认常开:
ctl.relay_act = nv_load_relay(); // 上电关闭/恢复上次
gpio_write(RELAY_PIN, ctl.relay_act);
过载保护独立判断,不等APP:
if(power > MAX_POWER_W || curr > MAX_I_A){
ctl.relay_req = 0;
mqtt_publish_fault(OVERLOAD);
}
六、落坑清单
MQTT回调里直接写继电器→本地按键冲突
主循环delay等定时→WiFi断线不重连
ADC电阻分压算功率→误差20%
HLW串口波特率错→全是0x00
5V计量芯片直连3.3V RX→烧IO
断电不存继电器状态→来电自启危险负载
定时用系统秒但不对时→凌晨跳变
功率每帧上报→云端刷屏限流
过载只上报不断继电器→跳闸/起火
按键和APP同时操作无仲裁→状态撕裂
七、落地顺序
继电器单独状态机,relay_req/relay_act分离
MQTT/按键/定时都只改relay_req
串口接HLW8032,标定电压电流功率系数
功率积分算电能,存NV
定时任务挂RTC秒,非阻塞比较
SNTP对时校正
断电记忆+过载强制断
状态变化上报,功率按阈值上报
智能插座固件稳不稳,不在能连WiFi,在执行出口唯一、采样走专用计量芯片、定时不阻塞主循环。远程开关只改状态,电量交给HLW/CSE,定时挂在节拍上——三者解耦后,APP、按键、定时、过载互不踩踏。剩下的都是细节:串口电平、系数标定、断电记忆、上报节流,哪条漏了现场就出怪问题。





