去年碰到一个测甲醛的需求,正好对开发板也比较感兴趣,偶然了解了arduino以后,兴趣就从原来的树莓派转移到了开发板,于是就用arduino写了个检测套件做为入门。

因为需要联网上传数据,最终用arduino + esp8266 + 甲醛传感器把项目做了出来。但随着对esp8266的了解,发现完全可以拿掉arduino,直接用esp8266作为MCU进行数据处理和传输,而不是仅仅作为一个网络透传工具来使用,所以趁着前两天有点时间,把这个项目用esp8266重新做了一遍。

两个版本的结构对比:

arduino主控版本,通过5V usb供电,arduino作为核心,从传感器接收数据(被动模式,只需要接收 ,所以只需要传感器tx连接arduino的rx,另一条线不需要),向esp8266发送数据(只需要发送无需接收,所以esp8266的rx连接arduino的tx,另一条线不需要连接),同时显示实时数据到液晶屏上。这种连接方式可以将开发板的串口同时连接传感器和wifi模块,节省宝贵的串口资源(软串口可以随意定义,但是同时只有一对能正常监听)。

esp8266主控版本的结构更为简单,通过5V usb取电,由5v转固定3.3v版本的ams1117提供3.3v供电(esp8266),传感器采用主动模式,由esp8266主动询问并获取数据,由wifi连接送到服务器,而由于esp8266的IO接口限制(从性价比考虑,12F能以低廉的价格提供较多的IO,而相对地,如果IO数量要求不高,尤其是用于产品量产的情况下,可以采用01S型号,如果接口、Flash要求较高,可以不采用esp8266,而是考虑更高规格的esp32),LCD1602通过I2C转接板,和esp8266通信可以只占用两个接口。

ESP8266系列模组技术规格

安信可wifi模组选型表:
http://wiki.ai-thinker.com/_media/esp8266/esp8266_module_list.png

(其实01S和12F其实也只是5块钱和8块钱的区别)

项目用到的组件:

Arduino/ESP8266 QuickStart

开发环境:

  • esp8266和arduino都可以使用C/C++风格的Arduino IDE来进行开发,不同的是前者需要额外引入相关的开发板定义并下载类库。
  • 资源:
    • arduino中文社区 https://www.arduino.cn/
    • wiki https://wiki.arduino.cn/
    • ide下载 https://www.arduino.cc/en/Main/Software
  • arduino开发板的开发比较简单,打开IDE直接就可以垒代码。
  • esp8266开发需要:
    • 添加开发板定义,文件 -> 首选项 -> 附加开发板管理器网址中填入 http://arduino.esp8266.com/stable/package_esp8266com_index.json ;
    • 工具 -> 开发板 -> 开发板管理器 -> 找到esp8266并下载安装(因为需要从github下载,所以速度会极慢,耐心等待,或者自行从网上搜索离线安装的方法吧);
    • 待下载完成后,即可在 工具 -> 开发板 中找到新安装的esp8266。
  • esp8266的工作模式:
    • 烧录模式:需要GPIO 0接地;
    • 工作模式:需要GPIO 0与GND断开;
    • 可以在GPIO 0与GND中间加一个拨片开关,方便快速切换;
    • RESET引脚接地建议使用触点开关,因为我手头没有,所以才用拨片开关。
  • LCD1602直连,需要用到LiquidCrystal库,较低版本的ide中默认不带,需要自行下载,操作方法是由工具菜单中的管理库菜单项打开库管理器,搜索LiquidCrystal后,下载安装,并在头部将库引入#include <LiquidCrystal.h>。
  • LCD1602的I2C连接,需要用到LiquidCrystal_I2C库,使用方法同上,唯一不同的是库名为LiquidCrystal_I2C。
  • 其它外设、功能所依赖的库的流程和上面一样,ide如果安装过,直接引入使用,如果没安装过,在库管理器中下载再引入使用。
void setup() {
  //开机运行的代码,只会运行一次
}

void loop() {
  //开机代码执行后,循环运行loop中的代码
}

串口

  • 串口分为硬串口和软串口;
  • 软串口可以由任意IO接口定义而来,但是没有缓存,同时只能监听一个;
  • 连线方式:开发板的TX/RX接外设的RX/TX,收发对调;
  • 没有库依赖;
Serial.begin(9600);//定义串口波特率,默认格式为8N1
Serial.print("A");//打印字符
Serial.write(65);//打印ASCII码对应的字符
while (Serial.available()) {
    int val = Serial.read();
    //……
}

WIFI

  • 依赖ESP8266HTTPClient库;
#include <ESP8266WiFi.h>
WiFi.begin("ssid", "112233445566");
//通过指定的wifi名和密码连接wifi
while (WiFi.status() != WL_CONNECTED)
{
    //检测到wifi连接未建立
}
String ip = WiFi.localIP();//获取wifi的IP

LCD1602和LCD1602_I2C

  • 前者依赖LiquidCrystal库,后者依赖LiquidCrystal_I2C库;
  • 连线方式:直连LCD1602模块采用4线或8线连接模式,I2C采用SCL/SDA两根线连接,前者适合IO宽裕的情况,需要占用6个引脚,后者只需要占用两个引脚。
  • 只支持英文字库,也可以自定义点阵,但是1602每个字符只有5*8像素,显示复杂内容会很难分辨,因此有更复杂需求时应该选择更好的屏幕,用合适的设备做合适的事情。
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,16,2);
//I2C地址0x27,16行,2列
//模块地址是固定的,具体数值请参考说明书或厂商相关文档。
 
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
//通过6个IO接口驱动屏幕
//代表rs, en, d4, d5, d6, d7引脚

lcd.init();//初始化
lcd.clear();//清除屏幕
lcd.print("ABCD123456");//输出内容
lcd.backlight();//背光开启
lcd.noBacklight();//背光关闭
lcd.setCursor(x,y);//移动光标到x列y行

HTTPClient

  • 依赖ESP8266HTTPClient库;
#include <ESP8266HTTPClient.h>
const char *headerKeys[] = {"tomorrow", "timestr", "light"};
//一般API在返回复杂内容时,会用Json序列化结果,
//用ResponseHeader的好处是无需让开发板省去
//反序列化的过程。
void weather(){
  HTTPClient http;
  String req = "/Api/Weather";
  http.begin("http://***.com", 83, req);
  http.collectHeaders(headerKeys, 3);
  //保存headerKeys数组中ResponseHeader  
  http.addHeader("User-Agent", "ESP8266-12F", true);
  //设置httpRequestHeader  
  http.GET();
  String result=http.getString();
  //获取http的结果  
  String disp=http.header("timestr");
  lcd.print(disp);
  lcd.setCursor(0,1);
  lcd.print(result);
  String light=http.header("light");
  if(light=="on"){
    lcd.backlight();
  }else{
    lcd.noBacklight();
  }
  delay(3000);//延迟3秒
  
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("TOMORROW: ");
  lcd.setCursor(0,1);
  disp=http.header("tomorrow");
  lcd.print(disp);
}

甲醛传感器模块:

  • 项目所用的是型号为wz-s的电化学气体传感器;
  • 需要保存在无干扰环境里,否则会需要较长的时间进行校零(自动进行);
  • 测试会被其它易挥发成分干扰;
  • 传感器具有使用有效期;
  • 不同传感器灵敏度不一样,电化学传感器灵敏度更高,而且很多传感器是污染物检测传感器冒充的。
  • 参数:
输入电压:3.3v ~ 5v
分辨率:0.001ppm
串口:9600, 8N1

设置主动模式:ff 01 78 40 00 00 00 00 47
设置问答模式:ff 01 78 41 00 00 00 00 46
请求数据命令:ff 01 86 00 00 00 00 00 79
返回数据示例:ff 86 00 2A 00 00 00 20 30
返回数据中包含两种单位的,按需选择。
甲醛浓度换算关系:1ppm=1.228 mg/m3
国家标准:室内甲醛浓度小于80μg/m3为合格
012345678
起始命令高位低位保留保留高位低位校验
0xFF0x86μg/m3μg/m3ppbppb
#include <ESP8266WiFi.h>
#include <LiquidCrystal_I2C.h>
#include <ESP8266HTTPClient.h>
LiquidCrystal_I2C lcd(0x27,16,2);
int flag=0;
int arr[9];
int idx;
bool light=false;
const String host = "http://server.server.server";
const char *headerKeys[] = {"tomorrow", "timestr", "light"};
const int cmd0[]={0xff,0x01,0x78,0x41,0x00,0x00,0x00,0x00,0x46};
const int cmd1[]={0xff,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
void setup() {
  Serial.begin(9600);  
  lcd.init();
  for (int i = 0; i < 9; i++) {
    arr[i] = -1;
    Serial.write(cmd0[i]);
  }

  flag=false;
  WiFi.begin("ssid", "12341234");
  while (WiFi.status() != WL_CONNECTED)
  {
    lcd.clear();
    lcd.print("WIFI Connecting.");
    if(flag){
      lcd.backlight();
    }else{
      lcd.noBacklight();
    }
    flag=(flag+1) % 3;
    delay(500);
  }
  lcd.backlight();
  delay(3000);
}

void loop() {
  lcd.clear();
  lcd.setCursor(0,0);
  flag = (flag + 1) % 3;
  switch(flag){
    case 0:
      lcd.print("IP Address:");
      lcd.setCursor(0,1);
      lcd.print(WiFi.localIP());
      break;
    case 1:
      weather();
      break;
    case 2:
      for(int i=0;i<9;i++){
        Serial.write(cmd1[i]);
      }
      while (Serial.available()) {
        int val = Serial.read();
        idx = idx + 1;
        arr[idx] = val;        
      }
      HTTPClient http;
      idx = -1;
      String reqx = "/Api/Ch2o?value=";
      String para2 = String(arr[2] * 256 + arr[3]);          
      String req = "/Api/Log?id=";
      //Log接口只用于服务器控制台打印
      for (int i = 0; i < 9; i++) {
        req += String(arr[i]);
        req += ",";
        arr[i] = -1;
      }
      req += para2;
      reqx += para2;

      lcd.print("HCHO(ug/m3)");
      lcd.setCursor(0,1);
      lcd.print(para2);
      lcd.print(" (VT<80)");
      http.begin(host, 83, req);
      http.addHeader("User-Agent", "ESP8266", true);
      http.GET();
      http.begin(host, 83, reqx);
      http.addHeader("User-Agent", "ESP8266", true);
      http.GET();
      break;
  }
  delay(2000);
}
void weather(){
  HTTPClient http;
  String req = "/Api/Weather";
  //常用天气预报接口均为中文,为了免去esp8266进行解析,
  //在服务器上请求天气并返回格式化的结果
  http.begin(host, 83, req);
  http.collectHeaders(headerKeys, 3);
  http.addHeader("User-Agent", "ESP8266-12F", true);
  http.GET();
  String result=http.getString();
  String disp=http.header("timestr");//天气接口调用时间
  lcd.print(disp);
  lcd.setCursor(0,1);
  lcd.print(result);
  String light=http.header("light");//是否开灯
  if(light=="on"){
    lcd.backlight();
  }else{
    lcd.noBacklight();
  }
  delay(3000);
  
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("TOMORROW: ");
  lcd.setCursor(0,1);
  disp=http.header("tomorrow");//明日天气
  lcd.print(disp);
}
分类: articles