ดิว.นินจา

ดิว.นินจา

Thursday, August 30, 2018

การส่งข้อมูลจาก ESP32 ขึ้น NETPIE ผ่าน Arduino UNO + NB-IoT Shield

ปัจจุบัน NB-IoT เริ่มได้รับความนิยมมากขึ้นเมื่อมีเครือข่ายรองรับทั่วประเทศ โดยมีข้อดีด้านการใช้พลังงานน้อยและส่งได้ระยะทางไกล ทั้ง AIS และ True มีจำหน่ายผลิตภัณฑ์ NB-IoT shields โดยสามารถใช้งานร่วมกับบอร์ด Arduino ในตระกูล UNO/Mega และเมื่อไม่นานทีมพัฒนา NETPIE เริ่มเปิดตัวไลบรารี microgear สำหรับใช้งานร่วมกับ NB-IoT พร้อมบทความการใช้งานเบื้องต้น จากการทดสอบพบว่าใช้งานได้ดี ถึงแม้ว่าฟังก์ชันจะยังไม่ครบสมบูรณ์เท่ากับ microgear สำหรับฮาร์ดแวร์อื่นเช่น ESP8266 ก็ตาม

ข้อจำกัดที่สำคัญของชิลด์ที่มีจำหน่ายอยู่เวลานี้คือต้องใช้ร่วมกับบอร์ด Arduino UNO หรือ Mega ที่มีทรัพยากรจำกัดและสมรรถนะด้อยกว่าตัวประมวลผลในตระกูล ESP ตัวประมวลผล avr บน UNO มีขนาด 8 บิต เท่านั้น ในบทที่ 7 ของหนังสือ "ระบบควบคุมและอินเทอร์เน็ตเชื่อมต่อสรรพสิ่ง" ได้เปรียบเทียบเวลาในการคำนวณอัลกอริทึม PID ได้ผลคือ UNO ใช้เวลา 172 ไมโครวินาที ขณะที่ ESP8266 และ ESP32 ใช้เวลา 45 และ 14 ไมโครวินาที ตามลำดับ

หน่วยความจำที่น้อย (32KB Flash, 2 KB SRAM) เป็นปัญหาสำคัญอีกประการหนึ่งของบอร์ด Arduino UNO ผู้เขียนเคยใช้บอร์ด UNO ในการอิมพลิเมนต์ตัวควบคุมในหนังสือ Feedback Control with Scilab and Arduino โดยเพิ่มฟังก์ชันในโปรแกรมตามเนื้อหาแต่ละบท เมื่อเขียนจนจบเล่มพบว่าหน่วยความจำของ UNO เต็มไม่สามารถโหลดโปรแกรมทั้งหมดลงไปได้ จนจำเป็นต้องตัดบางส่วนทิ้งไป นอกจากนั้นการนำโปรแกรมที่เขียนสำหรับ ESP ไปประยุกต์ใช้บน Arduino ต้องมีการแก้ไขอยู่หลายส่วนเช่น ขาอุปกรณ์ ไลบรารี สำหรับผู้อ่านที่พัฒนาบนฮาร์ดแวร์อื่น เช่น Raspberry PI หากต้องการแปลงมารันบน Arduino คงแทบเรียกได้ว่าต้องเขียนโปรแกรมกันใหม่เลยทีเดียว

วิธีการที่เหมาะสมกว่าคือ คงระบบที่เราพัฒนาและทดสอบไว้เป็นอย่างดีแล้วบนฮาร์ดแวร์เดิม และเพิ่มการติดต่อสื่อสารระหว่างระบบเดิมกับ UNO+NB-IoT การเชื่อมต่อที่สะดวกคือพอร์ตอนุกรม แต่ด้วยข้อจำกัดของ UNO ที่ไม่มีฮาร์ดแวร์พอร์ตอนุกรมสำรองให้ใช้งาน บทความนี้จึงนำเสนอการสื่อสารข้อมูลโดยใช้บัส i2c ที่ใช้งานได้ดีเมื่อระยะห่างระหว่างบอร์ดไม่มาก ตัวอย่างในบทความจะใช้ผลิตภัณฑ์บอร์ด node32pico จากบริษัท Ayarafun/LamLoei ที่มีเซนเซอร์ HTS221 สำหรับวัดอุณหภูมิและความชื้นบนบอร์ด ทำหน้าที่เป็น master ส่งข้อมูลอุณหภูมิและความชื้นให้กับ UNO slave ที่จะรับข้อมูลและส่งผ่าน NB-IoT ขึ้น NETPIE เนื้อหาที่จะกล่าวต่อไปจะเน้นอธิบายตรงส่วนการสื่อสารผ่าน i2c เนื่องจากตรงส่วนการส่งข้อมูลไปยัง NETPIE จะไม่แตกต่างจากคำแนะนำในบทความ "การใช้งาน NETPIE และ True NB-IoT" หมายเหตุ : ในตัวอย่างนี้ใช้บอร์ด NB-IoT ของ AIS ซึ่งในส่วนของโค้ดจะเหมือนกันทุกประการ

โครงสร้างการเชื่อมต่อ

เพื่อความกระชับของเนื้อหา เราตั้งสมมุติฐานว่าท่านพอมีความรู้พื้นฐานเกี่ยวกับ i2c มาบ้างแล้ว อธิบายโดยสังเขป i2c คือการสื่อสารโดยใช้สัญญาณเพียง 2 เส้น คือ SCL (คล็อก), SDA (ข้อมูล) อุปกรณ์หลายตัวสามารถต่อเข้ากับบัส i2c เดียวกัน โดย master จะเป็นตัวเลือกว่าจะสื่อสารกับอุปกรณ์ตัวใด และเป็นตัวส่งคล็อก ส่วนตัวอื่นก็จะต้องมีสถานะเป็น high impedance เพื่อมิให้เกิดการโหลดหรือชนกันของสัญญาณ

ในการใช้งาน ทั้ง SCL, SDA จะต้องมีตัวต้านทานพูลอัพต่อกับไฟเลี้ยงเสมอ ในไลบรารี Wire ที่ใช้บน Arduino จะตั้งให้ใช้งานตัวต้านทานพูลอัพภายในตัวประมวลผล มีค่าประมาณ 10 K Ohms สิ่งที่ต้องพิจารณาในกรณีที่ต่อระบบที่ใช้ไฟเลี้ยงต่างกัน อย่างกรณีเชื่อมต่อ ESP32 (3.3 โวลต์) เข้ากับ UNO (5 โวลต์) หากกำหนดให้ตัวต้านทานภายในของ UNO ดึงขึ้น 5 โวลต์จะทำให้แรงดันที่บัสสูงเกินกว่าไฟเลี้ยงของ ESP32 และอาจเกิดความเสียหายได้ ดังนั้นในการต่อระบบเข้าด้วยกันที่เหมาะสมจะเป็นดังรูปที่ 1 โดยต่อตัวต้านทานพูลอัพภายนอกค่าประมาณ 4.7 K Ohms กับไฟเลี้ยง 3.3 โวลต์ และเข้าไปแก้ไลบรารี Wire ให้ไม่ใช้งานตัวต้านทานพูลอัพภายใน วิธีการนี้จะทำให้รับประกันได้ว่าจะไม่เกิดความเสียหายกับขา SCL, SDA ของ ESP32

รูปที่ 1 การเชื่อมต่อ i2c ระหว่างระบบที่ใช้ไฟเลี้ยง 3.3 V และ 5 V

การแก้ไขไลบรารีทำได้ไม่ยาก โดยหาว่า Arduino ที่ใช้งานอยู่ติดตั้งไว้ที่ไดเรคทอรีใด แล้วเข้าไปในไดเรคทอรีย่อย /hardware/arduino/avr/libraries/Wire/src/utility จะเห็นไฟล์ชื่อ twi.c เปิดไฟล์โดย Editor ที่ท่านใช้ เลื่อนลงมาประมาณบรรทัดที่ 75 จะเห็นคำสั่งดังนี้


// activate internal pullups for twi.
  digitalWrite(SDA, 1);
  digitalWrite(SCL, 1);

ให้แก้ไขเป็น


// deactivate internal pullups for twi.
  digitalWrite(SDA, 0);
  digitalWrite(SCL, 0);

เซฟไฟล์ หลังจากนี้เมื่อคอมไพล์โปรแกรม ตัวต้านทานพูลอัพภายในจะไม่ถูกใช้งาน หากในอนาคตต้องการใช้งานก็แก้กลับเป็นเหมือนเดิม

สำหรับการเชื่อมต่อสัญญาณระหว่าง 2 บอร์ด ต่อขา GPIO22 (SCL) และ GPIO21 (SDA) ของ ESP32 เข้ากับ SCL(A5), SDA(A4) ของ UNO ผู้เขียนใช้ PCB อเนกประสงค์ดังในรูปที่ 2 (ต้องตรวจสอบ PCB ที่ใช้ว่ามีลายทองแดงเชื่อมขา SDA กับขาอื่นหรือเปล่า หากมีให้ใช้คัตเตอร์กรีดออก) หากท่านใช้สายสัญญาณเชื่อมต่อ ต้องระวังมิให้สายยาวเกินไปเพราะอาจเกิดความผิดพลาดด้านเวลาในการสื่อสารได้

รูปที่ 2 PCB อเนกประสงค์สำหรับเชื่อมต่อบอร์ด

ตัวอย่างโปรแกรมจากอินเทอร์เน็ต

สำหรับการพัฒนาการรับส่งข้อมูลในบทความนี้ ผู้เขียนมิได้เริ่มเขียนเองทั้งหมด แต่แก้ไขดัดแปลงตัวอย่างจากเว็บนี้ https://medium.com/@krukmat/arduino-esp8266-through-i2c-49b78e697b7a ซึ่งเป็นโครงสร้างโปรแกรมที่ต้องการอยู่แล้ว ท่านอาจจะลองเริ่มต้นจากการันตัวอย่างดูก็ได้ แก้ไขเพียงบรรทัดเดียวคือทางด้าน ESP ใน setup() เปลี่ยน Wire.begin(0, 2) เป็น Wire.begin()

สังเกตว่าจากตัวอย่างในเว็บนี้การสื่อสารข้อมูลสามารถทำได้ทั้งสองทาง เผื่อไว้สำหรับการพัฒนาในอนาคตเมื่อ microgear สามารถส่งคำสั่งจาก NETPIE ให้กับบอร์ด แต่ ณ เวลานี้ยังไม่สามารถทำได้ ดังนั้นในบทความนี้จะพัฒนาส่วนการส่งข้อมูลขึ้นหน้า Freeboard เท่านั้น

โปรแกรมด้าน ESP32 master

หน้าที่หลักของโปรแกรมด้าน node32pico คืออ่านค่าความชื้นและอุณหภูมิจากเซนเซอร์ HTS221 ที่อยู่บนบอร์ดและส่งเป็นสตริงผ่าน i2c ไปยัง UNO slave การอ่านค่าจาก HTS221 จะใช้ไลบรารี ซึ่งต้องติดตั้งก่อนดังแสดงในรูปที่ 3 (หาไลบรารีนี้ได้โดยพิมพ์ hts221 ในช่องค้นหาของ Library Manager) คลิก Install

รูปที่ 3 การติดตั้งไลบรารี SmartEverything HTS221

โปรแกรมด้าน node32pico ทั้งหมดเขียนได้ดังนี้


// HTS221_NETPIE_NBIOT.ino
// dew.ninja  Aug 2018
// This version sends data to UNO slave
// that connects to NB-IoT shield via i2c.


#include <Wire.h>
#include <HTS221.h>

#define I2CAddressESPWifi 30
const int ONBOARD_LED = 2;  // LED for node32pico

void setup() 
{
  pinMode(ONBOARD_LED, OUTPUT);
  Wire.begin();
  smeHumidity.begin();
  Serial.begin(115200);
}

void loop()
{
   digitalWrite(ONBOARD_LED,!digitalRead(ONBOARD_LED));  //toggle LED
  double humid = smeHumidity.readHumidity();
  double tem = smeHumidity.readTemperature();
  String datastring = (String)humid+","+(String)tem;
  int datalength = datastring.length();
  char databuf[datalength+1];
  datastring.toCharArray(databuf,datalength);
  Serial.print("Sending -->");
  Serial.println(databuf);
  Wire.beginTransmission(I2CAddressESPWifi);
  Wire.write(databuf);
  Wire.endTransmission();    
  delay(1000);
}

โดยแอดเดรส i2c ของ UNO slave ตั้งไว้เท่ากับ 30 การอ่านข้อมูลจะอาศัยออปเจ็ค smeHumidity ที่สร้างโดยไลบรารี เมื่ออ่านค่าได้ก็แปลงเป็นสตริงและเป็น character array ชื่อ databuf สุดท้ายการส่งผ่าน i2c ใช้คำสั่ง


  Wire.beginTransmission(I2CAddressESPWifi);
  Wire.write(databuf);
  Wire.endTransmission();    

ซึ่งจะส่งค่าทุกวินาที การส่งค่าด้วยอัตราสูงกว่านี้ไม่มีความจำเป็นเพราะจะถูกจำกัดโดยความเร็วของอุปกรณ์ NB-IoT และ NETPIE

โปรแกรมด้าน UNO slave (ยังไม่ใช้ชิลด์ NB-IoT)

ในขั้นแรกต้องการทดสอบการสื่อสารผ่าน i2c เพียงอย่างเดียวว่าทำงานถูกต้องหรือไม่โดยยังไม่ต่อชิลด์ NB-IoT โปรแกรมทางด้าน slave ทั้งหมดเป็นดังนี้


// uno_slave.ino
// dew.ninja  August 2018
// Receive humid/temp data from  node32pico

#include <Wire.h>
#define I2CAddressESPWifi 30
#define MAXBUFFSIZE 32
char payload[MAXBUFFSIZE];
String datastring;
void setup()
{
 Serial.begin(115200);
 Wire.begin(I2CAddressESPWifi);
 Wire.onReceive(espWifiReceiveEvent);
}
void loop()
{
 delay(1);
}
void espWifiReceiveEvent(int count)
{
 datastring = "";
 Serial.print("Receiving -->");
 while (Wire.available())
 {
 char c = Wire.read();
 datastring += c;
}
 Serial.println(datastring);
}

โดยแอดเดรสที่นิยามต้องตรงกับทางด้าน master ในที่นี้ใช้ค่า 30 สังเกตว่าการรับข้อมูลจะไม่ใช้วิธีการโพลลิงใน loop() แต่จะนิยามเป็นฟังก์ชัน espWifiReceiveEvent() ที่ถูกเรียกเมื่อพบเหตุการณ์ว่าข้อมูลถูกส่งมา การอ่านโดย Wire.read() แต่ละครั้งอาจได้ข้อมูลเพียงบางส่วนของสตริงที่ส่งมา จึงต้องนำมาเรียงต่อกันในสตริง datastring จนกว่าจะได้สตริงข้อมูลที่สมบูรณ์

การทดสอบ

โหลดโปรแกรมทางด้าน master ลงใน node32pico และ slave ลงใน Arduino UNO เชื่อมต่อทั้งสองบอร์ดเข้าด้วยกันผ่าน PCB อเนกประสงค์ ควรใช้คอมพิวเตอร์ 2 เครื่องเพื่อสามารถดูผลการสื่อสารข้อมูลผ่าน Serial Monitor ของแต่ละฝั่ง รูปที่ 4 และ 5 แสดงให้เห็นว่าการสื่อสารข้อมูลผ่าน i2c ทำงานได้อย่างถูกต้อง

รูปที่ 4 ข้อมูลแสดงบน Serial Monitor ทางด้านตัวส่ง (ESP32 master)
รูปที่ 5 ข้อมูลแสดงบน Serial Monitor ทางด้านตัวรับ (UNO Slave)

เพิ่มเติมโค้ดการสื่อสารกับ NB-IoT และ NETPIE

การทดสอบในขั้นตอนที่ผ่านมาทำให้เรามั่นใจว่าสามารถส่งข้อมูลผ่าน NB-IoT shield ไปยัง NETPIE ได้อย่างแน่นอน เพราะข้อมูลได้ถูกจัดตามรูปแบบของการพับลิชไปยัง Freeboard แล้ว งานที่เหลือคือ "ยำ" โค้ดระหว่างโปรแกรม uno_slave ด้านบนกับโปรแกรมตัวอย่างจากบทความ "การใช้งาน NETPIE และ True NB-IoT" ซึ่งรายละเอียดคงไม่จำเป็นต้องกล่าวถึง โปรแกรมที่ผ่านการปรุงแต่งแล้วเป็นดังนี้


// uno_slave_nbiot.ino
// dew.ninja  August 2018
// Receive temp/humid data from 
// node32pico and send to NB-IoT shield

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)      // Arduino UNO
    #include 
    AltSoftSerial bc95serial;
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)   // Arduino MEGA2560
    #define bc95serial Serial1
#endif

#include <Arduino.h>

#include "BC95Udp.h"
#include "MicrogearNB.h"
#include <Wire.h>

#define APPID "" // ต้องแก้ไข
#define KEY "" // ต้องแก้ไข
#define SECRET "" // ต้องแก้ไข

#define I2CAddressESPWifi 30   // i2c address of this slave
String datastring;   


BC95UDP client;
Microgear mg(&client);

#define MAXBUFFSIZE 32
char payload[MAXBUFFSIZE];
long lastPublish = 0;
void setup()
{
    bc95serial.begin(9600);
    BC95.begin(bc95serial);
    BC95.reset();
   Serial.begin(115200);
   Serial.println(F("Microgear Arduino NB-IoT Start!"));
   Serial.print(F("IMEI: "));
   Serial.println(BC95.getIMEI());
   Serial.print(F("IMSI: "));
   Serial.println(BC95.getIMSI());

   Serial.print(F("Attach Network..."));
   while (!BC95.attachNetwork()) {
     Serial.print(".");
     delay(1000);
   }

   Serial.println(F("\nNB-IOT attached!"));
   Serial.print(F("RSSI: "));
   // ค่าความแรงสัญญาณ NB-IoT
   Serial.println(BC95.getSignalStrength());
   Serial.print(F("IPAddress: "));
   Serial.println(BC95.getIPAddress());

   mg.init(APPID, KEY, SECRET);
   mg.begin(5555);   
   
   Wire.begin(I2CAddressESPWifi);
   Wire.onReceive(espWifiReceiveEvent);
}
void loop()
{
 
  if(millis() - lastPublish > 1000){  // publish every second
    lastPublish = millis();
    Serial.print(F("Publishing --> "));
    Serial.println(datastring);
    datastring.toCharArray(payload, MAXBUFFSIZE-1);
    mg.publish("/nbiot/sensor", payload);  
    Serial.print(F("Sent Signal Strength: "));
    Serial.println(BC95.getSignalStrength());
    mg.publish("/nbiot/rssi", BC95.getSignalStrength()); 
  }  
  mg.loop();
   
}
void espWifiReceiveEvent(int count)
{
   datastring = "";
   Serial.print("Receiving -->");
   while (Wire.available())
   {
   char c = Wire.read();
   datastring += c;
   }
   Serial.println(datastring);
}

บัดนี้ถึงเวลาต้องใช้บอร์ด NB-IoT โดยประกบซ้อนกันได้ดังในรูปที่ 6

รูปที่ 6 การประกอบบอร์ดเข้าด้วยกัน

เมื่อเปิด Serial Monitor ของ UNO Slave จะเห็นทั้งข้อความที่รับมาจาก i2c และข้อความที่พับลิชดังในรูปที่ 7

รูปที่ 7 ข้อมูลบน Serial Monitor จากโปรแกรม uno_slave_nbiot.ino

สำหรับการสร้าง Freeboard และ datasource จะเหมือนกับที่อธิบายในบทความ "การใช้งาน NETPIE และ True NB-IoT" ทุกประการ รูปที่ 8 แสดงการแสดงผลบน Freeboard โดยใช้ gauge widgets

รูปที่ 8 การแสดงผลบน Freeboard โดยใช้ gauge widgets

สรุป

บทความนี้สำหรับผู้ที่ซื้อ NB-IoT shield มาแล้วแต่ไม่ต้องการที่จะแปลงระบบเดิมที่พัฒนาไว้มารันบนบอร์ด Arduino ที่มีสมรรถนะต่ำและทรัพยากรจำกัด การคงแอปพลิเคชันหลักไว้บน ESP8266/ESP32 และใช้การสื่อสารข้อมูลให้กับบอร์ด UNO + IoT shield มีข้อได้เปรียบหลายประการ เช่นสามารถกลับมาสื่อสารผ่าน WiFi ได้เมื่อมีสัญญาณ ผู้ที่พัฒนาระบบบน Raspberry PI สามารถนำแนวทางไปประยุกต์ใช้ได้โดยแก้ไขเพียงโปรแกรมด้าน Master

ผู้เขียนหวังว่าหลังจากอ่านแล้วจะทำให้ท่านรู้สึกว่าซื้อ NB-IoT มาแล้วคุ้มค่าดั่งห้อยพระเครื่อง ;)

No comments:

Post a Comment

แนะนำหนังสือ “ตัวควบคุมป้อนกลับบนอินเทอร์เน็ตโดย ESP8266”

ปัจจุบันเมื่อกล่าวถึงอุปกรณ์ IoT (Internet of Things) คงมีน้อยคนที่จะไม่รู้จัก ในยุคที่การเข้าถึงอินเทอร์เน็ตเป็นกิจวัตรประจำวันของมนุษย์เ...