发帖
3 0 0

用esp8266维修改造家用天然气监测报警器,并增加小程序查看天然气浓度

zzbinfo
金牌会员

7

主题

16

回帖

2766

积分

金牌会员

积分
2766
电子DIY 38 3 9 小时前
[i=s] 本帖最后由 zzbinfo 于 2025-5-12 10:47 编辑 [/i]

在家里天然气开通之前就购买了天然气报警器,等天然气安装开通后,装上报警器发现,报警器是个坏的,最近抽了点时间,拆开维修改造一下,过程记录一下,给有需要的朋友参考。

IMG_20250510_094527.jpg

IMG_20250510_094552.jpg

买的是这种比较便宜的基础型号,拆开后,发现甲烷传感器已经瘪掉了,测量输出脚,持续输出高电平,分析电路后推断,传感器应该就是类似于常用的MQ-4型,引脚和电平也兼容,购买传感器换上后测试,工作正常,用气体打火机检测,也能正常报警,这里注意,就是刚刚买的传感器,开机时,有个预热过程,正常预热后,电平保持在0.1--1.8v之间。

IMG_20250510_100136.jpg

这是更换后两个传感器的对比图。

至此,报警器就维修好了,为了学习一点物联网知识,我们来把基础的报警器改造一下,改造成能联网,并用手机实时检测厨房甲烷浓度。

我用的esp8266-01,这个模块便宜,好用。但是没有引出ADC,所以就需要我们另外用个单片机采集一下传感器的输入端。我采用的是STC15W408AS,主要是便宜。参考的电路图如下,这个图也不是专门为这次改造设计的,只是正好满足拿来用。

QQ图片20250512100042.png

IMG_20250510_101127.jpg

这个是装配好的样子,原来的外壳里面正好有位置来固定和安装新增的电路板。用热熔胶固定。

IMG_20250510_102526.jpg

按照安装规范,甲烷报警器需要装在离厨房天花板30cm左右,不能离灶太近,容易被油烟等影响使用。我是正好粘在旁边橱柜顶上部分。

IMG_20250510_104751.jpg

下面贴一下代码,esp8266部分代码:

/*

  • 2025-4-22日 stc408as获取温度和湿度等信息通过串口传给8266,上传到云,通过小程序查看和控制
  • 官网https://bemfa.com
    */
    #include <ESP8266WiFi.h>
    #include <ESP8266httpUpdate.h>
    #include <WiFiManager.h>
    #include "PubSubClient.h"//默认,加载MQTT库文件 旧版本的库会经常断线,更新后解决。 以前代码中定时判断部分有问题,需要更新。

#define BYTE unsigned char
#define Ctrl1 0 //led
const char *Ver = "1.0";

const char *mqtt_server = "bemfa.com"; //默认,MQTT服务器地址
const int mqtt_server_port = 9501; //默认,MQTT服务器端口

String upUrl = "http://bin.bemfa.com/b/*******.bin";

//*需要修改的部分//

#define ID_MQTT "" //用户私钥,可在控制台获取,修改为自己的UID
const char *dhttopic = "
"; //主题名字,可在控制台新建
WiFiManager wm;
//**************************************************//
//最大字节数
#define MAX_PACKETSIZE 512
//设置心跳值60s
#define KEEPALIVEATIME 601000
//设置上传速率2s(1s<=upDataTime<=60s)
//下面的3代表上传间隔是30秒
#define upDataTime 30
1000

bool startSTAFlag = false;

//tcp客户端相关初始化,默认即可
WiFiClient TCPclient;
PubSubClient client(TCPclient);//mqtt初始化

bool ResetWiFi = false;

//相关函数初始化
//连接WIFI
void doWiFiTick();
void ReadCom();
bool AutoConfig();
void updateBin();

void callback(char* topic, byte* payload, unsigned int length) {
String Mqtt_Buff = "+";
for (int i = 0; i < length; i++) {
Mqtt_Buff += (char)payload[i];
}
Serial.print(Mqtt_Buff);
Serial.println();
//开关的控制指令和固件升级指令
// Switch on the LED if an 1 was received as first character
if (Mqtt_Buff == "on") {//如果接收字符on,亮灯
//turnOnLed();//开灯函数

} else if (Mqtt_Buff == "off") {//如果接收字符off,亮灯
//turnOffLed();//关灯函数
}else if(Mqtt_Buff == "+update"){
//如果收到指令update
updateBin();//执行升级
}
Mqtt_Buff = "";
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect(ID_MQTT)) {//连接mqtt
Serial.println("connected");
client.subscribe(dhttopic);//修改,修改为你的主题 // QoS 1
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
startSTAFlag = false;//重新连接WiFi
// Wait 5 seconds before retrying
delay(5000);
}
}
}

bool AutoConfig()//断电重连
{
WiFi.begin();
//WiFi.begin("Honor V9","1234567890");
//如果觉得时间太长可改
if(!ResetWiFi)
{
for (int i = 0; i < 50; i++)
{
ESP.wdtFeed();
int wstatus = WiFi.status();
ESP.wdtFeed();
if (wstatus == WL_CONNECTED)
{
Serial.println("WIFI SmartConfig Success");
Serial.printf("SSID:%s", WiFi.SSID().c_str());
Serial.printf(", PSW:%s\r\n", WiFi.psk().c_str());
Serial.print("LocalIP:");
Serial.print(WiFi.localIP());
Serial.print(" ,GateIP:");
Serial.println(WiFi.gatewayIP());
return true;
}
else
{
Serial.print("WIFI AutoConfig Waiting......");
Serial.println(wstatus);//返回值一般为6说明未连接

      delay(200);
      ESP.wdtFeed();
      delay(200);
      ESP.wdtFeed();
      delay(200);
      ESP.wdtFeed();
      delay(200);   
      ESP.wdtFeed();
      delay(200);  

    }
}
Serial.println("WIFI AutoConfig Faild!" );
 bool res;
// res = wm.autoConnect(); // auto generated AP name from chipid
// res = wm.autoConnect("AutoConnectAP"); // anonymous ap
res = wm.autoConnect("AutoConnectAP","1234567890"); // password protected ap

if(!res) {
    Serial.println("Failed to connect");
    // ESP.restart();
  return false;  
} 
else {
    //if you get here you have connected to the WiFi  
    Serial.println("connected...yeey :)");
     return true;
}

} else {
ResetWiFi = false;
bool res;
// res = wm.autoConnect(); // auto generated AP name from chipid
// res = wm.autoConnect("AutoConnectAP"); // anonymous ap
res = wm.autoConnect("AutoConnectAP","1234567890"); // password protected ap

if(!res) {
    Serial.println("Failed to connect");
    // ESP.restart();
  return false;  
} 
else {
    //if you get here you have connected to the WiFi  
    Serial.println("connected...yeey :)");
     return true;
}

}
return false;
}

/**************************************************************************
WIFI
**************************************************************************/
/

WiFiTick
检查是否需要初始化WiFi
检查WiFi是否连接上,若连接成功启动TCP Client
控制指示灯
*/
void doWiFiTick(){
//static bool startSTAFlag = false;
static bool taskStarted = false;
static uint32_t lastWiFiCheckTick = 0;

if (!startSTAFlag) {
startSTAFlag = AutoConfig();

}
//连接成功建立
else {
if (taskStarted == false) {
taskStarted = true;
Serial.print("\r\nGet IP Address: ");
Serial.println(WiFi.localIP());
}
}
}
//

void ReadCom(){

String inString="";

while(Serial.available()>0){
inString += char(Serial.read());
delay(10); // 延时函数用于等待字符完全进入缓冲区,可以尝试没有延时,输出结果会是什么
}
// 检查是否接收到数据,如果接收到数据,则输出该数据

if(inString.length() > 4){
      //此时会收到串口的指令
  client.publish(dhttopic, inString.c_str());//数据上传    指定所需的 QoS 1 级别。

}

}

// 初始化,相当于main 函数
void setup() {

Serial.begin(115200);
client.setServer(mqtt_server, mqtt_server_port);
client.setCallback(callback);
Serial.println("Beginning...");
Serial.println(Ver);
}

//循环
void loop() {
doWiFiTick();
if (!client.connected()) {//判断mqtt是否连接
reconnect();
}
client.loop();//mqtt客户端
ReadCom();
}

//当升级开始时,打印日志
void update_started() {
Serial.println("CALLBACK: HTTP update process started");
}

//当升级结束时,打印日志
void update_finished() {
Serial.println("CALLBACK: HTTP update process finished");
}

//当升级中,打印日志
void update_progress(int cur, int total) {
Serial.printf("CALLBACK: HTTP update process at %d of %d bytes...\n", cur, total);
}

//当升级失败时,打印日志
void update_error(int err) {
Serial.printf("CALLBACK: HTTP update fatal error code %d\n", err);
}

/**

  • 固件升级函数
  • 在需要升级的地方,加上这个函数即可,例如setup中加的updateBin();
  • 原理:通过http请求获取远程固件,实现升级
    */
    void updateBin(){
    Serial.println("start update");
    WiFiClient UpdateClient;

ESPhttpUpdate.onStart(update_started);//当升级开始时
ESPhttpUpdate.onEnd(update_finished); //当升级结束时
ESPhttpUpdate.onProgress(update_progress); //当升级中
ESPhttpUpdate.onError(update_error); //当升级失败时

HTTPClient http; //以把httpclient拿出来单独处理,成功后再开始升级,成功率就是100%了
int retry=0;
while(!http.begin(UpdateClient, upUrl))
{
delay(200);
if(retry++>10)
{
Serial1.println("connect failed");
return;
}
}
Serial1.println("start update");
String strCurVersion = ESP.getSdkVersion();

t_httpUpdate_return ret = ESPhttpUpdate.update(http, strCurVersion);
switch(ret) {
case HTTP_UPDATE_FAILED: //当升级失败
Serial.println("[update] Update failed.");
break;
case HTTP_UPDATE_NO_UPDATES: //当无升级
Serial.println("[update] Update no Update.");
break;
case HTTP_UPDATE_OK: //当升级成功
Serial.println("[update] Update ok.");
break;
}
}
/*
//TODO - in v3.0.0 make this an enum
constexpr int HTTP_UE_TOO_LESS_SPACE = (-100);
constexpr int HTTP_UE_SERVER_NOT_REPORT_SIZE = (-101);
constexpr int HTTP_UE_SERVER_FILE_NOT_FOUND = (-102);
constexpr int HTTP_UE_SERVER_FORBIDDEN = (-103);
constexpr int HTTP_UE_SERVER_WRONG_HTTP_CODE = (-104);
constexpr int HTTP_UE_SERVER_FAULTY_MD5 = (-105);
constexpr int HTTP_UE_BIN_VERIFY_HEADER_FAILED = (-106);
constexpr int HTTP_UE_BIN_FOR_WRONG_FLASH = (-107);
constexpr int HTTP_UE_SERVER_UNAUTHORIZED = (-108);
*/
代码主要还是参考巴法云官方示例修改,这里主要注意升级一下官方自带的PubSubClient库,旧版本的会频繁断联。

408as部分的代码也比较简单,就是基础的ADC采集,也没有做滤波,有需要滤波的可以参考其他代码进行修改。代码如下:

// stc15w408as 115200

#include "STC15Fxxxx.H"
#include "intrins.h"
#include <stdio.h>
#include <string.h>
#include <math.h>

#define Ver "1.0" //定义固件版本信息
#define uchar unsigned char
#define uint unsigned int
typedef unsigned char BYTE;
typedef unsigned int WORD;
#define FOSC 11059200L //System frequency
#define BAUD 115200 // //UART baudrate
#define T50Hz (FOSC / 12 / 2 / 50)
#define Timer2_Reload (65536UL -(FOSC / 4 / BAUD)) //Timer 2 重装值,

#define STC_Y1 97560U // 89C/LEXX、90C/LEXX
#define STC_Y3 14050U // 10F/Lxx、11F/Lxx 、12C/LExx、15F104E/L104E(A版)、15F204E/L204EA(A版)
#define STC_Y5 13043U // 15F/L/Wxx(Y3内核个别型号除外)
#define CNT_PCA_CNT 6000
/Define UART parity mode/
#define NONE_PARITY 0 //None parity
#define ODD_PARITY 1 //Odd parity
#define EVEN_PARITY 2 //Even parity
#define MARK_PARITY 3 //Mark parity
#define SPACE_PARITY 4 //Space parity

#define PARITYBIT NONE_PARITY //Testing even parity

#define S1_S0 0x40 //P_SW1.6
#define S1_S1 0x80 //P_SW1.7

#define CTRL_NUM 2 //控制端口数量
#define MAX_TIME 255 //限制最大定时时长

typedef bit BOOL;

bit ser_flag=0;

uchar idata Receive_Buffer[32]; //接收缓冲

uchar Buf_Index=0; //缓冲空间索引
uchar T_Count=0; //计时计数 产生秒计时
uchar S_Count=0; //计时计数 显示模式切换 默认显示10秒

BYTE code df_Table[]={ 0,1,1,2,3,3,4,4,5,6,6,7,8,8,9,9 }; //温度小数位对照表

sbit DHT11 = P3^7; // P3^7 //DHT11模块数据引脚
sbit Rain_pin = P3^6; //下雨传感器引脚
bit Temp_flag = 0; //温度转换标志
sbit DQ = P1^5; //温度传送数据IO口
sbit Ctrl1 = P1^3;
sbit Ctrl2 = P1^4;//两个控制端口
sbit CTRL = P1^1; //8266控制端口

/*******************************************/
#define ADC_POWER 0x80 //ADC电源控制位
#define ADC_FLAG 0x10 //ADC完成标志
#define ADC_START 0x08 //ADC起始控制位
#define ADC_SPEEDLL 0x00 //540个时钟
#define ADC_SPEEDL 0x20 //360个时钟
#define ADC_SPEEDH 0x40 //180个时钟
#define ADC_SPEEDHH 0x60 //90个时钟

sfr ADC_LOW2 = 0xBE; //ADC低2位结果

void InitADC(); //初始化ADC
void ShowResult(BYTE ch);
BYTE GetADCResult(BYTE ch);

void Clear_Data (void);
void Delay1ms(unsigned char c);

void Temp_change() ;
void Get_temp();

//void Delay20us();

BYTE cnt=0; //定时器0中断次数
WORD value;
WORD cnt_pca;

//BYTE UART0_Rx_Count;
BYTE idata Temp_Buf[5];
BYTE idata Temp_Value[2];
BYTE FLAG,temp;

bit busy;
bit isTimer;

void SendData(BYTE dat);
void SendString(char *s);

void UartInit(void) //115200bps@11.0592MHz
{

S1_8bit();				//8位数据
S1_USE_P30P31();		//UART1 使用P30 P31口	默认
AUXR &= ~(1<<4);	//Timer stop		波特率使用Timer2产生
AUXR |= 0x01;		//S1 BRT Use Timer2;
AUXR &= ~(1<<3);	//Timer2 set As Timer //
AUXR |=  (1<<2);	//Timer2 set as 1T mode
TH2 = (BYTE)(Timer2_Reload >> 8);
TL2 = (BYTE)Timer2_Reload;
IE2  &= ~(1<<2);	//禁止中断	//
AUXR &= ~(1<<3);	//定时		  //
AUXR |=  (1<<4);	//Timer run enable

REN = 1;	//允许接收
ES  = 1;	//允许中断

}

/*****************************************************/
void tm0_init() //50ms 定时
{
TR0 = 0; //停止计数
ET0 = 1; //允许中断
TMOD = (TMOD & ~0x03) | 0; //工作模式,0: 16位自动重装, 1: 16位定时/计数, 2: 8位自动重装, 3: 16位自动重装, 不可屏蔽中断
AUXR &= ~0x80; //12T
TMOD &= ~0x04; //定时
TH0 = 0x00;
TL0 = 0x4C;
TR0 = 1; //开始运行
}

//T0 中断函数
void T0_INT() interrupt 1
{
uint i;
BYTE temp[5];
cnt++;
if(cnt==20) //50ms *100
{

 if(Temp_flag == 1)
  { 
   Get_temp();
   i = GetADCResult(0);
   i= (i<<2)+ADC_LOW2;
   SendString("#");
   sprintf(temp,"%d",i);
   SendString(temp);
   Temp_flag = 0;
  } else {   
   
   Temp_change();
   Temp_flag = 1;
  }
  
cnt=0; 

}

}

void main()
{

tm0_init();
InitADC();
UartInit();
EA = 1;
isTimer = 0;
Temp_flag = 0;
//SendString("ces\r\n"); 
while (1)
{                                   //user's function

  
}

}

//
void Delay30us() //@11.0592MHz
{
unsigned char i;

_nop_();
_nop_();
i = 80;
while (--i);

}

//
void Delay40us() //@11.0592MHz
{
unsigned char i;

i = 108;
while (--i);

}

//========================================================================
// 函数: void delay_ms(unsigned char ms)
// 描述: 延时函数。
// 参数: ms,要延时的ms数, 这里只支持1~255ms. 自动适应主时钟.
// 返回: none.
// 版本: VER1.0
// 日期: 2013-4-1
// 备注:
//========================================================================
void Delay1ms(unsigned char c)
{

 unsigned int i;
 do{
      i = FOSC / 13000;
	  while(--i)	;   //14T per loop
 }while(--c);

}
/ds18b20****/
/dsb8b20 1ms延时函数/
void Delay600us() //@11.0592MHz
{
unsigned char i, j;

i = 7;
j = 113;
do
{
	while (--j);
} while (--i);

}
void Delay60us() //@11.0592MHz
{
unsigned char i, j;

i = 1;
j = 162;
do
{
	while (--j);
} while (--i);

}

void Delay36us() //@11.0592MHz
{
unsigned char i;

i = 97;
while (--i);

}

/ds18b20初始化函数************/

void Init_DS18B20(void)
{
uint i=2200;
DQ=1;
Delay60us(); // 12*6
DQ=0;
Delay600us();//最小480 39,最大960 80 12*50
DQ=1;
Delay60us();//15-60us 12*5
//while(DQ!=0);
while(DQ&&(i>0)) i--;
//TESTLED = 1;
DQ=1;//让传感器释放总线,避免影响下一步
}
/***********ds18b20读一个字节**************/
unsigned char ReadOneChar(void)
{
uchar i=0;
uchar dat = 0;

for (i=8;i>0;i--)
{
	DQ=0;// 给脉冲信号
	dat>>=1;
	DQ=1; // 给脉冲信号
	if(DQ)
	dat|=0x80;
	Delay36us();//15-60us采样  12*3
}
return(dat);  

}

/ds18b20写一个字节***/

void WriteOneChar(uchar dat)
{
unsigned char i=0;
for (i=8; i>0; i--)
{
DQ = 0;
_nop_();
DQ = dat&0x01; //取最低位
Delay36us(); //15-60us采样 12*3
DQ = 1; //上升沿将数据送入
_nop_();
_nop_();
dat>>=1;
}

}

/**读取ds18b20当前温度/
//开始温度转换//
void Temp_change()
{
Init_DS18B20();
Delay1ms(1);
WriteOneChar(0xCC); // 跳过读序号列号的操作
WriteOneChar(0x44); // 启动温度转换
}
//获取温度数据//

void Get_temp()
{
uchar CurrentT = 0; //当前读取的温度整数部分
uchar a,b,ng;
uchar temp[5] = {0};
uchar Tdot = 0;
Init_DS18B20();
Delay1ms(1);
WriteOneChar(0xCC); //跳过读序号列号的操作
WriteOneChar(0xBE); //读取温度寄存器等(共可读9个寄存器) 前两个就是温度
Temp_Value[0]=ReadOneChar(); //读取温度值低位
Temp_Value[1]=ReadOneChar(); //读取温度值高位
a = Temp_Value[0];
b = Temp_Value[1];
if((b&0xf8)==0xf8) //高字节高5位如果全为 1,则为负数,为负数时
{ //加1,并设置负数标志为 1
b = ~b;
a = ~a+1;
if(a==0x00) //若低字节进位,则高字节加 1
(b)++;
ng = 1; //设置负数标志为 1
}else ng = 0;
Tdot = df_Table[a&0x0f]; //查表得到温度小数部分
//获取温度整数部分(低字节低4位清零,高4位右移 4 位)+(高字节高5位清零
//低三位左移4位)
CurrentT = ((a&0xf0)>>4) | ((b&0x07)<<4);
//将温度整数部分分解为3位待显示数字
SendString("#");
if(ng) SendString("-");
sprintf(temp,"%d",(uint)CurrentT);
SendString(temp);
SendString(".");
sprintf(temp,"%d",(uint)Tdot);
SendString(temp);

}
/************************************************/

/----------------------------
初始化ADC
----------------------------
/
void InitADC()
{
P1ASF = 0x05; //0x01; 1D// //设置P1.0,P1.2 口为AD口
ADC_RES = 0; //清除结果寄存器
ADC_CONTR = ADC_POWER | ADC_SPEEDLL;
Delay1ms(2); //ADC上电并延时
}

/----------------------------
读取ADC结果
----------------------------
/
BYTE GetADCResult(BYTE ch)
{
ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ch | ADC_START;
nop(); //等待4个NOP
nop();
nop();
nop();
while (!(ADC_CONTR & ADC_FLAG));//等待ADC转换完成
ADC_CONTR &= ~ADC_FLAG; //Close ADC

return ADC_RES;                 //返回ADC结果

}
/----------------------------
UART 中断服务程序
-----------------------------
/
void Uart() interrupt 4
{
uchar c;
if (RI)
{
RI = 0; //清除RI位
c = SBUF; //P0显示串口数据
if(c == '+')
{
Buf_Index = 0;
memset(Receive_Buffer,0,32);
}
Receive_Buffer[Buf_Index++]=c;
if(Buf_Index >= 32)
{
Buf_Index = 0;
memset(Receive_Buffer,0,32);
}
if(c == '\n') ser_flag = 1; //P2.2显示校验位
}
if (TI)
{
TI = 0; //清除TI位
busy = 0; //清忙标志
}
}

/----------------------------
发送串口数据
----------------------------
/
void SendData(BYTE dat)
{
SBUF=dat;
while(!TI);
TI=0;
}

/----------------------------
发送字符串
----------------------------
/
void SendString(char *s)
{
while (*s) //检测字符串结束标志
{
SendData(*s++); //发送当前字符
}
}

/*****************************************************/
BYTE ASCII2HEX(BYTE dat)
{

if(dat >= '0' && dat <= '9')	return (dat - '0');	//0~9
if(dat >= 'A' && dat <='F')     return (dat + 10 - 'A');			//A~F
return 	dat;

}
/***************************************************************/
小程序部分的界面如下:

Screenshot_20250510_104740_com.tencent.mm.jpg

小程序部分代码比较长,我把所有的代码和小程序部分的主要显示界面代码放在最后。水平有限,大家轻喷。upload 附件:代码部分.rar另外,天然气浓度这个只是参考,我只是ad转换后就直接显示了,从测量的电压看,正常情况下,输出的电压小于1.8v就是正常,高于2v就报警,对应显示的值就是小于560和大于620。

──── 0人觉得很赞 ────

使用道具 举报

挺好,可以多多分享~
完整的改造项目,很有借鉴意义
好玩
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 28700 个
  • 话题数: 40923 篇