本网页已闲置超过3分钟,按键盘任意键或点击空白处,即可回到网页

含GPS的环境监测器
发布时间:2021-05-30
分享到:
含GPS的环境监测器
发布时间:2021-05-30
分享到:

该环境监测器由太阳能Wio终端、多个传感器以及一个全球定位系统组成,Wio终端上的液晶显示屏不仅可以显示结果,还可以用来在地图上显示所测量的不同位置。该监测器由太阳能电池板和一个可充电的锂离子电池供电,它们可以与Wio终端连接。

硬件组件:

设备设计:

为了完全确定环境参数,该监测器包括4个不同的传感器;SCD30、模拟紫外线传感器、电容式湿度传感器和温度传感器。

SCD30传感器可用于同时测定空气湿度、温度和CO2浓度;紫外线传感器,用于确定土壤温度和湿度的设备。

数据显示在Wio终端屏幕上,并在位置或环境传感器的情况下自动更新。在土壤测量的情况下,一旦按下按钮,测量就会更新,以避免进行与土壤状态无关的测量。使用Wio终端屏幕的传感器界面如下所示。

运行该项目所需的电力由太阳能电池板提供。该太阳能电池板可以与电池结合,以便连续测量不同的参数。因此,它可以用于现场应用,以确定不同的环境参数并记录它们。

(设备:包括传感器、电池、太阳能充电器)

无线终端接口:

Wio终端设备上的三个按钮,每个按钮上显示不同的屏幕。

当按下第一个按钮上时,设备显示由不同传感器和全球定位系统测量的当前数据。按下设备上的蓝色按钮后,传感器会记录当前数据,包括用户的位置,稍后可以在地图中观察到这些数据。地图上的这个位置可以向用户指示测量的位置。

当按下第二个按钮时,用户可以看到记录的数据,并进行土壤测量。用户最多可以进行10次不同的测量并记录下来,每次测量的次数如下所示显示在屏幕上。当用户切换到带有地图显示的第三个菜单时,还会显示每个样品测量的位置。

最后,在第三个菜单上,可以看到测量区域的地图。在测量前,用户必须下载该地图,并在以下时间上传至Wio终端。图像可以从不同的来源获取,如谷歌地图,经度和纬度的限制必须记录并添加到程序中,以便正确定位彩色圆圈。用户的当前位置由红色圆圈指示,而第二个菜单中指示的过去位置可以用蓝色看到。

(本项目中可用于测量环境 参数的三种不同显示器的图片)

代码:

#include <Seeed_FS.h>
#include "TFT_eSPI.h"
#include <SPI.h>
#include <SD/Seeed_SD.h>
#include <Wire.h>
#include "SparkFun_SCD30_Arduino_Library.h"
#include <RawImage.h>
#include <SoftwareSerial.h>
#include <TinyGPS++.h>
    
//我们定义类的类型对于我们将在整个项目中使用不同的传感器(gps、光和气体传感器)
SCD30 airSensor;
TinyGPSPlus gps;

//还定义了类的类型对于屏幕中对象的显示
TFT_eSPI tft;

//指出了引脚对于全球定位系统设备
SoftwareSerial mySerial(2, 3);

//定义代码中使用的变量
double dist_LAT = 34.9722899, dist_LONG = 138.3868869;
String p_hour, p_lat, p_lng, p_alt, p_sat, p_date;
String p_my_speed, p_my_course, p_dist_dTo, p_dist_cTo;
String pdist_LAT, pdist_LONG;
String sdist_LAT = String(dist_LAT);
String sdist_LONG = String(dist_LONG);
String Date[10], Dat;
int menu = 1;
byte RX1;
uint8_t Cut = 78;
char buffer[64], Data [64], Message;   // 通过串行端口接收数据的缓冲阵列
int count=0;                           //缓冲区阵列计数器
double Val1, Val2;
int count3 = 0;
int y, Latc, Lonc, i, j, Lat[10], val1, val2, val3, s, p, z, tUV, tlux;
float tTemp, tHum, tCO2, Buff;
uint8_t val[2];
double LatLim1, LatLim2, LonLim1, LonLim2;
String N_Stemp, p_Temp, p_Stemp, N_mois, N_EC, N_CO2, N_Hum, N_Temp, p_STemp, p_mois, p_EC, p_CO2, p_Hum;
int Log_f = 0;
String N_date, hour0;
unsigned long p_time;
int N_y, N_m, N_d;
String hr1, min1, sec1;
String F_name, N_Mois, p_Mois, nhour0[5];
bool sd;
float tEC, tMois, tStemp;
int l, Lng[10];
String N_lux, N_UV, p_lux, p_UV;
int Num = 0;
int k = 1;
float lux[10], Temp[10], Hum[10], CO2[10], Stemp[10], EC[10], Mois[10],  UV[10];
String Longitude[10], Latitude[10], Alt[10], Sat[10];
static const int MAX_SATELLITES = 40;
float uUV, ulux, uCO2, uSTemp, uHum, uTemp, uEC, uMois;

//我们将初始化TinyGPS++库中的自定义类型,以确定卫星的位置、高度和数量
TinyGPSCustom ExtLat(gps, "GPGGA", 3); 
TinyGPSCustom ExtLng(gps, "GPGGA", 5); 
TinyGPSCustom totalGPGSVMessages(gps, "GPGSV", 1); 
TinyGPSCustom messageNumber(gps, "GPGSV", 2);     
TinyGPSCustom satsInView(gps, "GPGSV", 3);         
TinyGPSCustom satNumber[4]; // 待稍后初始化
TinyGPSCustom elevation[4];
TinyGPSCustom azimuth[4];
TinyGPSCustom snr[4];

//定义用于测定温度、电导率和湿度的土壤探针装置的地址
const byte Addr[] = {0x01, 0x04, 0x00, 0x00, 0x00, 0x03, 0xB0, 0x0B};

struct
{
  bool active;
  int elevation;
  int azimuth;
  int snr;
  int dsp;
} sats[MAX_SATELLITES];

    volatile uint32_t newlines = 0UL;
    
    static void handleRxChar( uint8_t c )
    {
      if (c == '\n')
        newlines++;
    }

void setup() {

  mySerial.begin(9600);
  tft.begin();
  tft.setRotation(3);

//此命令初始化Wio终端中包含的SD设备
        if (!SD.begin(SDCARD_SS_PIN, SDCARD_SPI)) {
   sd = false;
   }
       else {
          SerialUSB.println("initialization done.");
//首先,我们需要初始化将在这个项目中使用的设备,以及串行通信
  Wire.begin();
  airSensor.begin();
  Serial.begin(9600);
        sd = true;
    }
        for (int i=0; i<4; ++i)
    {
      satNumber[i].begin(gps, "GPGSV", 4 + 4 * i); // 偏移量4, 8, 12, 16
      elevation[i].begin(gps, "GPGSV", 5 + 4 * i); // 偏移量 5, 9, 13, 17
      azimuth[i].begin(gps, "GPGSV", 6 + 4 * i); // 偏移量 6, 10, 14, 18
      snr[i].begin(gps, "GPGSV", 7 + 4 * i); // 偏移量7, 11, 15, 19
    }

//为整个循环中使用的一些变量设置初始值
p = 0;
j = 0;
p = 0;
s = 1;
Num = 0;
count = 0;

//这些变量表示所显示地图上纬度和经度的最小值和最大值
LatLim1 = 50;
LatLim2 = 55;
LonLim1 = 50;
LonLim2 = 60;

//这些命令将程序上使用的pin设置为输入引脚,用于测量UV光强度
pinMode (A7, INPUT);
pinMode (A4, INPUT);
pinMode (A6, INPUT);

//这些命令初始化Wio终端中的按钮
pinMode(WIO_5S_PRESS, INPUT_PULLUP);
pinMode(WIO_KEY_A, INPUT_PULLUP);
pinMode(WIO_KEY_B, INPUT_PULLUP);
pinMode(WIO_KEY_C, INPUT_PULLUP);
pinMode(WIO_5S_LEFT, INPUT_PULLUP);
pinMode(WIO_5S_RIGHT, INPUT_PULLUP);
}

void loop() {

//这些命令从SCD30传感器获取测量值
if (airSensor.dataAvailable())
{
tTemp = airSensor.getTemperature();
tHum = airSensor.getHumidity();
tCO2 = airSensor.getCO2(); 
}

//由于紫外线传感器有一个模拟输出,从它连接的引脚读取信号
for (i=0; i<10; i++){
tUV = tUV + analogRead (A7);
}

tUV = tUV/10;

//使用以下命令将环境测量值转换为字符串变量
//这些变量将显示在屏幕上
N_UV = String(tUV);
N_Temp = String(tTemp);
N_Hum = String(tHum);
N_CO2 = String(tCO2);
  
long N_mjd;

//命令将传感器和GPS的数据保存到SD卡。此功能每6秒执行一次
while (mySerial.available() > 0) {
char c = mySerial.read();
gps.encode(c);
}

//计算纬度和经度值,并将其转换为用于显示的字符串
    double lat0 = gps.location.lat();
    double lat1 = (lat0 -int(lat0))*60;
    double lat2 = (lat1 - int(lat1))*60;
    String lat3 = String(int(lat0))+':' + String(int(lat1))+':'+String(lat2) + ' ' + String(ExtLat.value());
    double lng0 = gps.location.lng();
    double lng1 = (lng0 -int(lng0))*60;
    double lng2 = (lng1 - int(lng1))*60;
    String lng3 = String(int(lng0))+':' + String(int(lng1))+':'+String(lng2) + ' ' + String(ExtLng.value());

//计算时间并将其转换为字符串以供显示
    int hr = gps.time.hour()+9;
    if (hr>24) hr -= 24;
    String hr0 = '0'+String(hr);
     hr1 = hr0.substring(hr0.length()-2); 
    String min0 = '0'+String(gps.time.minute());
     min1 = min0.substring(min0.length()-2); 
    String sec0 = '0'+String(gps.time.second());
     sec1 = sec0.substring(sec0.length()-2);
    hour0 =  hr1+':'+min1+':'+sec1;

//计算日期并将其转换为字符串以供显示
    if (gps.date.isUpdated()) {
        int y = gps.date.year();
        int m = gps.date.month();
        int d = gps.date.day();
        if(m<3){y--; m+=12;}
        long mjd = int(361.7015*y)+int(y/400)-int(y/100)+int(30.59*(m-2))+d-678912;
        if(gps.time.hour()>14) 
            N_mjd = 58580+(mjd-51412);
        else
            N_mjd = 58579+(mjd-51412);
        long n = N_mjd+678881;
        long a = 4*n+3+3*(4*(n+1)/146097+1);
        long b = 5*((a % 1461)/4)+2;
        N_y = int(a/1461);
        N_m = int(b/153+3);
        N_d = int((b % 153)/5+1);
        if (N_m>12){N_y++; N_m-=12;}
        N_date = String(N_y)+'/'+('0'+String(N_m)).substring(('0'+String(N_m)).length()-2)+'/'+('0'+String(N_d)).substring(('0'+String(N_d)).length()-2);
    }

//当用户按下Wio终端上的按钮时,将触发土壤测量的RS485通信
//测量位置也固定并显示在Wio终端屏幕上
if (digitalRead(WIO_5S_PRESS) == LOW) {

//只有Wio终端处于数据显示模式时,才会进行土壤测量
if (menu ==1){  

//此代码保存初始化测量时的当前位置,以便在lcd屏幕上显示
lng0 = lng0*100;
Lng [p] = map(lng0, LonLim1, LonLim2, 0, 240);
Lat [p] = map (lat0, LatLim1, LatLim2, 0, 320);
lng0 = lng0/100;

//进行土壤温度和湿度测量
for (i=0;i<10;i++){
tStemp = tStemp + analogRead (A4);
tMois = tMois + analogRead (A6);
}
tStemp = tStemp/10;
tStemp = tStemp*(-0.0826) + 108.65;
tMois = tMois/10;
tMois = tMois*5/1024;

N_Stemp = String (tStemp);
N_Mois = String (tMois);

Date[p] =  N_date;
nhour0[p] = hour0;
Alt[p] = String(gps.altitude.meters());
Sat[p] = String(gps.satellites.value());
Temp[p] = tTemp;
Hum[p] = tHum;
CO2[p] = tCO2;
UV[p] = tUV;
Stemp[p] = tStemp;
Mois[p] = tMois;
Latitude[p] = lat3;
Longitude[p] = lng3;

//这些线确定将在地图屏幕上显示的点数
p++;
count++;
if (p>10){
  p = 0;
  count = 10;
}

//s的值决定测量的标题第一次显示在Wio终端屏幕上
s = 1;  

  }
}

//此条件将显示测量值
if (menu == 1){
  
//第一次显示菜单时,我们需要初始化屏幕颜色和测量标题
    if (s==1){
    tft.fillScreen (TFT_BLACK);
    tft.setTextSize(3);
    tft.drawString("GPS",50,3);
    tft.drawString("SCD30",30,150);
    tft.drawString("Soil",200,3);
    tft.drawString("Light",200,150);
    tft.setTextSize(1);
    tft.drawString("Date",20,33);
    tft.drawString("Time",20,53);
    tft.drawString("LAT",20,70);
    tft.drawString("LONG",20,87);
    tft.drawString("ALT (m)",20,107);
    tft.drawString("Satellites",20,127);
    tft.drawString("Temp (oC)",20,180);
    tft.drawString("Hum (%)",20,200);
    tft.drawString("CO2 (ppm)",20,220);
    tft.drawString("Temp (oC)",180,33);
    tft.drawString("Moisture (V)",180,53);
    tft.drawString("UV ",180,180);
    }

    if (N_date != p_date || s == 1) {    //显示日期
      tft.fillRect(50,33,70,20,TFT_BLACK);
      tft.drawString(N_date,50,33);
      p_date = N_date;
    } 
    
    if (hour0 != p_hour || s == 1) {    //显示时间
      tft.fillRect(50,53,100,16,TFT_BLACK);
      tft.drawString(hour0,50,53);
      p_hour = hour0;
    }
     
    if (lat3 != p_lat || s == 1) {      //显示纬度
      tft.fillRect(50,70,110,10,TFT_BLACK);
      tft.drawString(lat3,50,70);
      p_lat = lat3;
    }
    
    if (lng3 != p_lng || s == 1) {      //显示经度
      tft.fillRect(50,90,87,10,TFT_BLACK);
      tft.drawString(lng3,50,90);
      p_lng = lng3;
    }

    if (String(gps.altitude.meters()) != p_alt || s == 1) { //显示高度
      tft.fillRect(70,107,107,10,TFT_BLACK);
      tft.drawString(String(gps.altitude.meters()),70,107);
      p_alt = String(gps.altitude.meters());
    }
    
    if (String(gps.satellites.value()) != p_sat || s == 1) {  //显示卫星数
      tft.fillRect(90,127,110,10, TFT_BLACK);
      tft.drawString(String(gps.satellites.value()),90,127);
      p_sat = String(gps.satellites.value());
    }
    
    tft.fillRect(0,140,320,2,TFT_YELLOW);
    tft.fillRect(160,0,2,240,TFT_YELLOW);


if (N_Temp != p_Temp || s == 1){ //显示环境温度
    tft.fillRect(80,180,32,16,TFT_BLACK);
    tft.drawString(N_Temp, 80, 180);
    p_Temp = N_Temp;
}

if (N_Hum != p_Hum || s == 1){ //显示空气湿度
    tft.fillRect(90,200,32,16,TFT_BLACK);
    tft.drawString(N_Hum, 80, 200);
    p_Hum = N_Hum;
}

if (N_CO2 != p_CO2 || s == 1){ //显示CO2浓度
    tft.fillRect(80,220,32,16,TFT_BLACK);
    tft.drawString(N_CO2, 80, 220); 
    p_CO2 = N_CO2;
}

if (N_Stemp != p_Stemp || s == 1){ //显示土壤中的温度
    tft.fillRect(240,33,32,16,TFT_BLACK);
    tft.drawString(N_Stemp, 240, 33);
    p_Stemp = N_Stemp;
}

if (N_Mois != p_Mois || s == 1){ //显示土壤体积含水量
    tft.fillRect(270,53,32,16,TFT_BLACK);
    tft.drawString(N_Mois, 260, 53); 
    p_Mois = N_Mois;
}

if (N_UV != p_UV || s == 1){ //显示来自UV传感器的强度
    tft.fillRect(200,180,80,16,TFT_BLACK);
    tft.drawString(N_UV, 200, 180); 
    p_UV = N_UV;
}

s = 0;
}

if (menu == 2){
  
   if (digitalRead(WIO_5S_RIGHT) == LOW) {
      Num = Num + 1;
      l = 1;
   }
   if (digitalRead(WIO_5S_LEFT) == LOW) {
      Num = Num - 1;
      l = 1;
   }

   if (Num<0){
   Num = count; 
   }

   if (Num>count-1){
   Num = 0; 
   }

  Dat = String(Num);
  
if (l == 1){

    tft.fillScreen (TFT_BLACK);
    tft.setTextSize(3);
    tft.drawString("GPS",50,3);
    tft.drawString("SCD30",30,150);
    tft.drawString("Soil",170,3);
    tft.drawString("Light",200,150);
    tft.setTextColor(TFT_YELLOW);
    tft.drawString("Sample",170,210);
    tft.drawString(Dat,285,210);
    tft.setTextColor(TFT_WHITE);
    tft.setTextSize(1);
    tft.drawString("Date",20,33);
    tft.drawString("Time",20,53);
    tft.drawString("LAT",20,70);
    tft.drawString("LONG",20,87);
    tft.drawString("ALT (m)",20,107);
    tft.drawString("Satellites",20,127);
    tft.drawString("Temp (oC)",20,180);
    tft.drawString("Hum (%)",20,200);
    tft.drawString("CO2 (ppm)",20,220);
    tft.drawString("Temp (oC)",180,33);
    tft.drawString("Moisture (%)",180,53);
    tft.drawString("UV: ",180,180);

    tft.fillRect(0,140,320,2,TFT_YELLOW);
    tft.fillRect(160,0,2,240,TFT_YELLOW);

    Dat = String (Date[Num]);
    //Date
    tft.drawString(Dat,50,33);

    Dat = String (nhour0[Num]);
    tft.drawString(Dat,50,53);
      p_hour = hour0;

    Dat = String (Latitude[Num]);
      tft.drawString(Dat,50,70);

    Dat = String (Longitude[Num]);
      tft.drawString(Dat,50,90);

    Dat = Alt[Num];
      tft.drawString(Dat,70,107);

    Dat = Sat[Num];
      tft.drawString(Dat,90,127);

    Dat = String(Temp[Num]);
    tft.drawString(Dat, 80, 180);

    Dat = String(Hum[Num]);
    tft.drawString(Dat, 50, 200);

    Dat = String(CO2[Num]);
    tft.drawString(Dat, 80, 220); 

    Dat = String(Stemp[Num]);
    tft.drawString(Dat, 240, 33);

    Dat = String(Mois[Num]);
    tft.drawString(Dat, 260, 53);
   
    Dat = String(UV [Num]);
    tft.drawString(Dat, 240, 180); 
}
  l = 0;
  
}

//初始化地图显示,以查看当前的位置和测量的地点
if (menu == 3){
  delay (100);
if (k==1){

//命令显示地图,存储在SD卡内存中
drawImage<uint16_t>("map.bmp", 0, 0);

//此函数获取坐标,以及用户指示的最大坐标,以将其映射并在屏幕上显示
lng0 = lng0*100;
Latc = map (lat0, LatLim1, LatLim2, 0, 320);
Lonc = map (lng0, LonLim1, LonLim2, 0, 240);
lng0 = lng0/100;

if (l==1){
//在当前位置画一个绿色圆圈
tft.fillCircle(Latc, Lonc, 4, TFT_RED);
}

if (l==0){
//在过去的位置画一个绿色圆圈(仅当在菜单2上时)
tft.fillCircle(Lat[Num], Lng[Num], 4, TFT_BLUE);  
}

delay (100);
}
k = 0;
}


//此代码用于检测按钮的输入和菜单之间的变化
   if (digitalRead(WIO_KEY_C) == LOW) {
    menu = 1;
    l = 1;
    s = 1;
    k = 1;
   }
   else if (digitalRead(WIO_KEY_B) == LOW) {
    menu = 2;
    s = 1;
    l = 1;
    k = 1;
   }
   else if (digitalRead(WIO_KEY_A) == LOW) {
   menu = 3;
   s = 1;
   k = 1;
   }
   
  delay (100);
}

加入微信技术交流群

技术交流,职业进阶

关注与非网服务号

获取电子工程师福利

加入电路城 QQ 交流群

与技术大牛交朋友

讨论