2016年7月30日 星期六

[EPS8266] ESP 12E 傳送PH以及溫度資料到ThingSpeak


簡介


ESP 12E 算是EPS 8266 系統列很牛的晶片,也提供比較多的腳位,

找把它拿來替代Arduino取得魚缸中的資料,顯示在LCD上,並傳到ThingSpeak。



PH 2的原因是因為他爬升到穩定需要一點時間,但我20秒才讓他取一次值,

他需要取6次,去頭去尾取中間的平均值,它的取值時間不建議小於800ms,

一般來說校正後放入水中要達到穩要幾分鐘,例如海水缸,它會從0慢慢的爬到

8.x,達到穩定後,水中的PH值改變時,它就能夠快速反應。(當然你如果從8變到10,

還是要爬很久,但如果只是0.X的變化是很快的)

材料

ESP 12E

PH meter

DS18b20 (防水版本)

1602 I2C LCD

杜邦線

麵包板(小)

麵包板電源模組

DC 9V電源供應器

USB to TTL (燒錄程式碼用)

Wi-Fi  AP (讓ESP 12 可以傳資料到ThingSpeak)

ThingSpeak Account

接線


ESP 12E    麵包板      PH meter   DS18b20     LCD

VCC          3.3V                                                  

GND         GND         GND           GND            GND    USB to TTL

CH_PID   3.3V

GPIO 14                                         Data

GPIO 4                                                                  SDA

GPIO 5                                                                  SCL

ADC (A0)                  Data                            

                     5V           VCC          VCC             VCC

------------以下是燒錄時才要加的------
RX                                                                                       TX                                                                                                                
TX                                                                                       RX

GND      GND

GPIO0   GND

這邊有2個重點,

(1)DS18b20需要搭配1個4.7K電阻,詳情請參考[Arduino]DS18B20 防水溫度計
                         
(2)ESP 12 E 的ADC,支援的類比為10bit,但電壓範為是0~1V,這點

比較特別,一開始輸入2V怎麼都輸出1024,查了很多文才發現這個重點。

When your potentiometer is near 0V it prints 0 and when it reaches 1V it should print 1024.

料來源:

http://randomnerdtutorials.com/esp8266-adc-reading-analog-values-with-nodemcu/

但PH meter output 為0~5V analog data, 所以我只好拿起5顆10K電阻做分壓,

因為我用的不是精密電阻,10顆幾塊錢那種,所以我必須再重新校正一次PH meter,

因為電阻會有誤差,然候這樣子會導致你拿到的可能不是1/5的電壓值,所以重新依據

目前的狀況校正。(手邊沒有其他東西可用時才用這招,不是個好方法)


ThingSpeak


建立一個CHANNEL,使用2個欄位如下所示,並且把API KEY貼到程式碼中。





程式碼



#include <LiquidCrystal_I2C.h>
#include <Wire.h>  
#include <ESP8266WiFi.h>
#include <OneWire.h>
#define SensorPin A0           //pH meter Analog output to Arduino Analog Input 0
#define Offset 0.42           //deviation compensate
#define samplingInterval 20
#define printInterval 800
#define ArrayLenth  40    //times of collection
int pHArray[ArrayLenth];   //Store the average value of the sensor feedback
int pHArrayIndex=0;   
OneWire  ds(14);  // on pin 14 (a 4.7K resistor is necessary)
// replace with your channels thingspeak API key,
String apiKey = "your ThingSpeak API Key";
const char* ssid = "your ssid";
const char* password = "your password";

const char* server = "api.thingspeak.com";
LiquidCrystal_I2C lcd(0x27,16,2); // Check I2C address of LCD, normally 0x27 or 0x3F
static const byte degrees_glyph[] = { 0x00, 0x07, 0x05, 0x07, 0x00 };
WiFiClient client;


void setup()  {
lcd.begin(4,5);      // In ESP8266-01, SDA=0, SCL=2               
lcd.backlight();
// Register the custom symbol...
Serial.begin(115200);
delay(10);
Serial.println();
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
}

void loop()  {
byte i;
byte present = 0;
byte type_s;
byte data[12];
byte addr[8];
float celsius, fahrenheit;
int h=20;

if ( !ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }
  
  Serial.print("ROM =");
  for( i = 0; i < 8; i++) {
    Serial.write(' ');
    Serial.print(addr[i], HEX);
  }

  if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.println("CRC is not valid!");
      return;
  }
  Serial.println();
 
  // the first ROM byte indicates which chip
  switch (addr[0]) {
    case 0x10:
      Serial.println("  Chip = DS18S20");  // or old DS1820
      type_s = 1;
      break;
    case 0x28:
      Serial.println("  Chip = DS18B20");
      type_s = 0;
      break;
    case 0x22:
      Serial.println("  Chip = DS1822");
      type_s = 0;
      break;
    default:
      Serial.println("Device is not a DS18x20 family device.");
      return;
  } 

  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);        // start conversion, with parasite power on at the end
  
  delay(1000);     // maybe 750ms is enough, maybe not
  // we might do a ds.depower() here, but the reset will take care of it.
  
  present = ds.reset();
  ds.select(addr);    
  ds.write(0xBE);         // Read Scratchpad

  Serial.print("  Data = ");
  Serial.print(present, HEX);
  Serial.print(" ");
  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
    Serial.print(data[i], HEX);
    Serial.print(" ");
  }
  Serial.print(" CRC=");
  Serial.print(OneWire::crc8(data, 8), HEX);
  Serial.println();

  // Convert the data to actual temperature
  // because the result is a 16 bit signed integer, it should
  // be stored to an "int16_t" type, which is always 16 bits
  // even when compiled on a 32 bit processor.
  int16_t raw = (data[1] << 8) | data[0];
  if (type_s) {
    raw = raw << 3; // 9 bit resolution default
    if (data[7] == 0x10) {
      // "count remain" gives full 12 bit resolution
      raw = (raw & 0xFFF0) + 12 - data[6];
    }
  } else {
    byte cfg = (data[4] & 0x60);
    // at lower res, the low bits are undefined, so let's zero them
    if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
    //// default is 12 bit resolution, 750 ms conversion time
  }
  celsius = (float)raw / 16.0;
  fahrenheit = celsius * 1.8 + 32.0;
  Serial.print("  Temperature = ");
  Serial.print(celsius);
  Serial.print(" Celsius, ");
  Serial.print(fahrenheit);
  Serial.println(" Fahrenheit");
  static unsigned long samplingTime = millis();
  static unsigned long printTime = millis();
  static float pHValue,voltage;
  if(millis()-samplingTime > samplingInterval)
  {
      pHArray[pHArrayIndex++]=analogRead(SensorPin);
      if(pHArrayIndex==ArrayLenth)pHArrayIndex=0;
      voltage = avergearray(pHArray, ArrayLenth)*5.0/1024;
      pHValue = 3.5*voltage+Offset;
      samplingTime=millis();
  }
  if(millis() - printTime > printInterval)   //Every 800 milliseconds, print a numerical, convert the state of the LED indicator
  {
    Serial.print("Voltage:");
    Serial.print(voltage,2);
    Serial.print("    pH value: ");
    Serial.println(pHValue,2);
    printTime=millis();
  }


if (client.connect(server,80)) { // "184.106.153.149" or api.thingspeak.com
String postStr = apiKey;
postStr +="&field1=";
postStr += String(celsius);
postStr +="&field2=";
postStr += String(pHValue);
postStr += "\r\n\r\n";

client.print("POST /update HTTP/1.1\n");
client.print("Host: api.thingspeak.com\n");
client.print("Connection: close\n");
client.print("X-THINGSPEAKAPIKEY: "+apiKey+"\n");
client.print("Content-Type: application/x-www-form-urlencoded\n");
client.print("Content-Length: ");
client.print(postStr.length());
client.print("\n\n");
client.print(postStr);
lcd.setCursor(0, 0);
lcd.print("Temperature:");
lcd.print(celsius,1);
lcd.setCursor(0, 1);
lcd.print("PH Value:");
lcd.print(pHValue,1);
Serial.print("Temperature: ");
Serial.print(celsius);
Serial.print("PH: ");
Serial.print(pHValue);
Serial.println("send to Thingspeak");
}
client.stop();

Serial.println("Waiting…");
// thingspeak needs minimum 15 sec delay between updates
delay(20000);
}
double avergearray(int* arr, int number){
  int i;
  int max,min;
  double avg;
  long amount=0;
  if(number<=0){
    Serial.println("Error number for the array to avraging!/n");
    return 0;
  }
  if(number<5){   //less than 5, calculated directly statistics
    for(i=0;i<number;i++){
      amount+=arr[i];
    }
    avg = amount/number;
    return avg;
  }else{
    if(arr[0]<arr[1]){
      min = arr[0];max=arr[1];
    }
    else{
      min=arr[1];max=arr[0];
    }
    for(i=2;i<number;i++){
      if(arr[i]<min){
        amount+=min;        //arr<min
        min=arr[i];
      }else {
        if(arr[i]>max){
          amount+=max;    //arr>max
          max=arr[i];
        }else{
          amount+=arr[i]; //min<=arr<=max
        }
      }//if
    }//for
    avg = (double)amount/(number-2);
  }//if
  return avg;
}

執行畫面


























PH 尚在未穩定狀態,所以2.9 並不正確,到達穩定要一點時間 ,目前程式每次執行時間

間隔20秒,對PH取值而言太久,需要再做調整,PH到達穩定約10~20分鐘。

下圖是ThingSpeak上的資料(隔了一天)。

沒有留言:

張貼留言