气象信息采集装置

由于单位地处偏远,没有可用的准确的天气预报,今年冬天雾霾又实在很重,于是就想自己做个硬件实时采集气象数据发到服务器,供微信订阅号查询。

首先想到的是基于arduino平台,搭建各种传感器的数据采集端,通过联网模块发送至服务器(参见:生活小助手订阅号python后台),然后服务器对数据进行记录和格式处理,当打开微信订阅号发送查询请求时,服务器再将结果返回。

硬件模块:

温湿度模块,由于冬天室外可达零下十几度,为了保证测量范围和精度,放弃常用的DHT11选择DHT22,5V/1.5mA,仅占用一个数字IO口。

RTC模块,根据时间信息进行程序控制。比如晚上降低采集频率节省电能。选择DS1302,1~2mA,占用两个数字IO口,另有一个片选口。

Wifi模块ESP8266,开始设想的是用GSM模块发送,但是功耗较大不容易控制,用wifi模块的弊端是采集器周围必须有wifi覆盖,限制了采集器的放置。5V,66mA,工作电流大,但是不必常开,休眠电流小,uA级可忽略,使用uart总线。

降雨检测模块(定性)。5V/5mA,占用一个模拟输入口。

气压检测模块BMP180,3.3V/1~2mA,使用SPI总线。

PM2.5模块,选择的是某宝国产的模块,5V/150mA,空载48mA。由于PM2.5耗电巨大,故增加继电器模块当不需要PM2.5工作的时候使得PM2.5断电,选择常开继电器,工作时通电闭合电路使得PM2.5模块工作。串联合适的电阻,使得继电器工作电流控制在约5mA(2~4mA触发),uart总线。

主控选择简单易用的Arduino平台,选择mini为了降低模块整体大小能放到气象箱内。3.3V/20mA,休眠电流微安级可忽略。

太阳能电池板:峰值可达12V/3W,给3.7V2600mAh的充满电只需4个小时。

3.3V降压稳压模块,因为很多模块是3.3V工作的,所以需要降压,用的是AMS1117-3.3V。

5V升压稳压模块,主要是将单节锂电池的电压升至5V以支持部分模块工作,某宝买的,没有型号。

全模块待机电流约15mA,满负荷工作时约380mA,每次采集全部信息需要1分钟,按照每小时采集一次,全天需要的电能约5V/512mAh,3.7V2600mAh锂电池按照90%转换效率,可以支持4.5天的工作时长,而太阳能电池板在即使阴天也能提供一定的输出,因而,理论上该装置是可以持续工作的。

【连线】:

电路图就不画了(我自己是手画了个图就照着焊了=_=!),合理分配引脚,没什么多余的外围电路元件,都是引用的模块。。。

【代码】:

这里引用了三个库,DS1302、BMP180、DHT,主要是因为这三个涉及数据封装解析,其他的雨滴模块直接读取,PM2.5和ESP8266都是直接读写串口。

#define RELAY_PIN 10
先注意这句,是继电器控制引脚,为了节省电能设置的常开(电路断)继电器,耗电大的几个模块如ESP8266、PM2.5都通过继电器模块控制,工作前需要闭合继电器,工作结束需要断开。继电器控制很简单,数字IO口基本操作。
pinMode(RELAY_PIN,OUTPUT);
digitalWrite(RELAY_PIN,HIGH);
先说简单的,【雨滴模块】:

引脚定义

//RAIN DROP
#define RAIN_A_PIN 0
#define RAIN_D_PIN 3
定义了两个引脚,一个模拟IO一个数字IO,因为是定性检测,所以设置好阈值后一般判断数字IO即可。

初始化:

pinMode(RAIN_D_PIN,INPUT);
检测:
if_rain = digitalRead(RAIN_D_PIN)==LOW?true:false;
【PM2.5模块】:
//PM2.5
#define SOFTSER_RX 12
#define SOFTSER_TX 11
const byte StartUpCmd[9]={0xAA,0x01,0x00,0x00,0x00,0x00,0x01,0x66,0xBB};
const byte ReadCmd[9]={0xAA,0x02,0x00,0x00,0x00,0x00,0x01,0x67,0xBB};
const byte Read325Cmd[9]={0xAA,0x52,0x00,0x00,0x00,0x00,0x01,0x67,0xBB};
const byte Read2510Cmd[9]={0xAA,0x53,0x00,0x00,0x00,0x00,0x01,0x67,0xBB};
const byte ShutDownCmd[9]={0xAA,0x03,0x00,0x00,0x00,0x00,0x01,0x68,0xBB};
SoftwareSerial PMSerial(SOFTSER_RX, SOFTSER_TX);
#define PM_SAMP_MAX 20
由于ESP8266需要串口,为保证ESP8266工作正常,将通信速率低的PM2.5模块(9600bps)设置为软件串口,保证通信速率高的ESP8266(115200bps)正常工作减小芯片运算压力,上述代码定义了软件模拟串口的收发引脚以及PM2.5的相关命令。PM_SAMP_MAX宏定义PM2.5采样次数,PM2.5模块的说明书里有介绍,为保证精度最好在模块开机并运行稳定后做多次采样。这里定义的软件模拟串口PMSerial也同于调试模式下的调试信息输出。

初始化:

PMSerial.begin(9600);
应用的时候直接和普通串口一样PMSerial.print()、PMSeiral.read(),具体的使用方法,如模块开机、信息采集、信息解析需要参考对应的模块文档。不过好在市面上多数模块使用方法几乎相同,所以并不是大问题,可以参考网上其他教程或资料。无非是指定格式的字节数组收发和处理,模块交互设计比较友好(发一个命令,返回一个结果),不是大问题,这里不重点描述。
PMSerial.write(StartUpCmd,9);
delay(2000);
uint16_t pm25[PM_SAMP_MAX];
uint16_t pm10[PM_SAMP_MAX];
uint32_t pm25sum = 0;
uint32_t pm10sum = 0;
memset(pm25,0,sizeof(pm25));
memset(pm10,0,sizeof(pm10));
int k = 0;
for(k=0;k<PM_SAMP_MAX;k++){
	bool getData = false;
	int timeoutCtn = 0;
	while(timeoutCtn<500 && !getData){
		delay(10);
		while (PMSerial.available())  //串口获取到数据开始解析
		{
			byte c = PMSerial.read(); //读取一个字节获取的数据
			if(c==0xAA){
				PMSerial.readBytes(readbuf,8);
				if(readbuf[0]==0x01){
					PMSerial.write(ReadCmd,9);
				}else if(readbuf[0]==0x02){
					pm25[k] = readbuf[3]*256+readbuf[4];
					pm10[k] = readbuf[1]*256+readbuf[2];
				#ifdef __DEBUG
					PMSerial.print("PM25 ");
					PMSerial.print(pm25[k]);
					PMSerial.print("PM10  ");
					PMSerial.println(pm10[k]);
				#endif
					getData = true;
					break;
				}
			}
		}
		timeoutCtn++;
	}
	pm25sum += pm25[k];
	pm10sum += pm10[k];
	delay(1000);
	PMSerial.write(ReadCmd,9);
}
PMSerial.write(ShutDownCmd,9);
【ESP8266】:

虽然ESP8266只涉及串口通信,但是可能因为模块设计或通信速率较高(115200bps)的原因,因为容易出错,首先是电压需要控制在3.3V,我试过当整个模块用usb接口供电,模块就不能正常工作,没有深入排查,暂时不能明确是电压不足还是电源波纹等因素干扰。

先定义wifi连接命令宏以及连接远程服务器命令宏:

#define AT_WIFI_CONN "AT+CWJAP=\"wifiSSID\",\"password\""
#define AT_TCP_CONN  "AT+CIPSTART=\"TCP\",\"123.123.123.123\",9999"
由于存在发送失败的可能,因而定义最大重发次数:
#define RESEND_MAX 3
封装发送AT指令函数:
bool ESPAT_SEND(String at,char match){
    bool getReturn = false;
    char ctmp ;
    int ctn = 0;
    Serial.println(at);
    while(ctn<3000 && !getReturn){
        ctn++;
        if(Serial.available()){
            ctmp = Serial.read();
            if(ctmp == match){
                while(Serial.available()){
                    ctmp = Serial.read();  
                }
                return true;            
            }
        }  
        delay(4);
    }
    return false;
}
定义了两个ESAPI_SEND,只用该函数做示例,ESP8266模块的AT指令通信模式下,每发送一个AT指令,模块都将有一个应答。该函数的第一个参数为AT指令,第二个参数为应答中的匹配关键字段,若应答字符串中匹配到了该关键字,则返回应答成功。

封装连接服务器并向服务器发送消息函数:

bool SendToServer(String wstr){
    bool b[10];
    memset(b,0,sizeof(b));
    b[0] = ESPAT_SEND(AT_WIFI_CONN,"OK");
    b[1] = ESPAT_SEND("AT+CWJAP?","OK");
    b[2] = ESPAT_SEND(AT_TCP_CONN,'O');
    int len = wstr.length();
    b[3] = ESPAT_SEND("AT+CIPSEND="+String(len),'>');
    b[4] = ESPAT_SEND(wstr,"SEND OK");
    b[5] = ESPAT_SEND("AT+CIPCLOSE","OK");
    #ifdef __DEBUG
        int m =0;
        for(m=0;m<10;m++){
            PMSerial.print("b"+String(m)+":  ");
            PMSerial.println(b[m]?"true":"false");
        }
    #endif
    return b[1]&&b[2]&&b[3]&&b[4]&&b[5];
}
初始化并向服务器报告开机状态:
delay(3000);
bool b[10];
memset(b,0,sizeof(b));
ESPAT_SEND("ATE0","OK");
ESPAT_SEND("AT+CWMODE=1","OK");
b[0] = ESPAT_SEND("AT+CWAUTOCONN=1","OK"); //auto connect when power up
b[1] = ESPAT_SEND("AT+CIPMUX=0",'O');  //single connection mode
b[2] = ESPAT_SEND("AT+RST","OK");

int m =0;
for(m=0;m<RESEND_MAX;m++){
  if(SendToServer("Event:StationRestart")){
	  break; 
  }
  delay(1000);
}
delay(3000);
【DHT22】:

引用Arduino平台的DHT11库,DHT11和DHT22用法完全一样,只是模块量程、精度不同罢了。

初始化:

#define DHT22_PIN 8
dht DHT22;
采集:
int chk = DHT22.read22(DHT22_PIN);
wstr += String(DHT22.temperature,1)+";"+String(DHT22.humidity,1)+";";
采集的时候传入引脚,然后直接查询类成员。

【BMP180】:

走捷径可以直接google一篇博文参考,否则看看库文件以及模块文档。

初始化:

//BMP180
SFE_BMP180 BMP180;
if(BMP180.begin()){// if failure, will stuck here
 ;
}
信息采集:
bool pressGet = false;
char BMP_Status;
double BMP_T,BMP_P;
BMP_Status = BMP180.startTemperature();

if (BMP_Status != 0)
{
    delay(BMP_Status);
    BMP_Status = BMP180.getTemperature(BMP_T);
    if (BMP_Status != 0)
    {
        BMP_Status = BMP180.startPressure(3);
        if (BMP_Status != 0)
        {
            delay(BMP_Status);
            measurements.)
              
            BMP_Status = BMP180.getPressure(BMP_P,BMP_T);
            if (BMP_Status != 0)
            {
                pressGet = true;
            }   
        }
    }
}
if(pressGet){
    wstr += String(BMP_P,2)+";";  
}
else{
    wstr += "--;"; 
}
为了方便观看这里删除了注释和调试信息,原代码保留了很多注释,里面说的很详细,这里稍微介绍下,如果要用BMP180测海拔,需要提前获取基准海拔气压,比如测山上海拔,需要山脚的气压和海拔,测高差则只需基准面气压值。这个也很好理解。BMP180测气压需要先测温度,因为温度和气压是相关的。getTemperature和startPressure函数均返回需要等待的毫秒数,最后getPressure获取气压和温度值。

【DS1302】:

这个就是很常见的模块了,网上封装了很多的库,自定义了通信协议,根据文档的时序逻辑也可以自己尝试写库。当然这种轮子没必要反复造,造过几次就可以了:)。这里和时钟结合的是用了Arduino的EEPROM,用来存储同步RTC的时间信息。原本设计是从服务器获取时间(定期同步)或通过软串口获取(按键触发),该程序里偷了个懒,仅调试模式下可以同步RTC时间信息,还是直接改源码设置时间,不是很方便,同时时间也不够准。

宏定义:

//DS1302
#define CE_PIN   4
#define IO_PIN   6
#define SCLK_PIN 7
char dayofWeek[10];
DS1302 RTC(CE_PIN, IO_PIN, SCLK_PIN);
初始化设置:
RTC.write_protect(false);
RTC.halt(false);
Time t(year+2000, month, day, hour, minute, second,dayofweek);
RTC.time(t);
时间获取:
Time t = RTC.time();
if (t.hr>=4 && t.hr<22){
    wakeIntval = 221;
}
else{
    wakeIntval = 221;//892  
}
【休眠】:

这里重点提一下Arduino的休眠省电模式,因为这个在平时DIY用的不多,但是对于户外连续工作的装置还是很有用的:

【引用自网络http://www.geek-workshop.com/thread-12261-1-1.html】

#include <avr/sleep.h>
#include <avr/wdt.h>

bool WDT_RST = true;  // flag, if pic is down, reset whole system
void(* resetFunc) (void) = 0;

ISR(WDT_vect){
    wakeupCtn++;
    if(wakeupCtn%30==29){
        if(WDT_RST){
            resetFunc();
        }
        else{
            WDT_RST = true;  
        }
    }
}

void wdt_setup(int timeout_mode) {
	// ii为看门狗超时时间,支持以下数值:
	// 0=16毫秒, 1=32毫秒,2=64毫秒,3=128毫秒,4=250毫秒,5=500毫秒
	// 6=1秒 ,7=2秒, 8=4秒, 9=8秒
	byte btmp;
	if (timeout_mode > 9 ) timeout_mode = 9;
	btmp = timeout_mode & 7;
	if (timeout_mode > 7) btmp |= (1 << 5);
	btmp |= (1 << WDCE);
	//开始设置看门狗中断   
	MCUSR &= ~(1<<WDRF);  //清除复位标志
	/* In order to change WDE or the prescaler, we need to
	* set WDCE (This will allow updates for 4 clock cycles).
	*/
	WDTCSR |= (1<<WDCE) | (1<<WDE);
	//设置新的看门狗超时时间
	WDTCSR = btmp;//also  WDTCSR = 1<<WDP1 | 1<<WDP2
	//设置为定时中断而不是复位
	WDTCSR |= _BV(WDIE); 
}
在需要睡眠的地方调用EnterSleepMode();进入休眠。Arduino程序主循环中依次采集模块的输出,然后将结果汇总通过wifi模块发送到指定服务器,每个循环结束进入睡眠状态节省电能。更多休眠相关信息可以参考如下网站:

http://www.gammon.com.au/forum/?id=11497

http://swf.com.tw/?p=525

程序调试输出:

 loop
Tuesday 2017-01-24 13:40:40
delay time
temperature: 26.78 deg C, 80.20 deg F
absolute pressure: 1035.30 mb, 30.58 inHg
RainA:1023  D 0
0.0,	0.0,	1660
PM25 104 PM10  152
PM25 153 PM10  206
PM25 192 PM10  251
PM25 214 PM10  277
PM25 225 PM10  291
PM25 264 PM10  336
PM25 270 PM10  342
PM25 273 PM10  344
PM25 278 PM10  348
PM25 277 PM10  348
PM25 280 PM10  349
PM25 282 PM10  350
PM25 279 PM10  346
PM25 280 PM10  344
PM25 281 PM10  345
PM25 285 PM10  348
PM25 286 PM10  349
PM25 287 PM10  349
PM25 291 PM10  353
loop time13728
Battery:771

可以看到PM2.5的测定需要一段时间才能稳定。其中电池的测量是用模拟引脚读取,5V对应1024,因而771实际约3.76V。

 

图1 半成品电路

图2 电路完工

图3 装箱

图4 成品外观

如有疑问,可以留言!

【附录】

代码及相关资料链接:

BMP180

DHT22-AM2302

DS3231

PM2.5

ESP8266

Enerlib

资料合集

原文链接: http://www.straka.cn/blog/meteorological_info_collecter/

猜你喜欢

转载自blog.csdn.net/atp1992/article/details/79084164
今日推荐