去年碰到一个测甲醛的需求,正好对开发板也比较感兴趣,偶然了解了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为合格
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
起始 | 命令 | 高位 | 低位 | 保留 | 保留 | 高位 | 低位 | 校验 |
0xFF | 0x86 | μg/m3 | μg/m3 | ppb | ppb |
#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);
}