ดิว.นินจา

ดิว.นินจา

Friday, December 21, 2018

สร้างอุปกรณ์ไอโอทีกำเนิดสัญญาณบน ESP32 และ NETPIE

อุปกรณ์ไอโอทีกำเนิดสัญญาณ หรือที่ผู้เขียนเรียกย่อว่า SGIoT (Signal Generator Internet of Thing) อาจจะเป็นสิ่งประดิษฐ์ใหม่ที่ยังไม่ค่อยมีใครพูดถึงนัก เนื่องจากมิใช่เป็นอุปกรณ์ที่ใช้งานทั่วไปในชีวิตประจำวัน แต่หากท่านศึกษาด้านวิศวกรรมศาสตร์หรือวิทยาศาสตร์ โดยเฉพาะสาขาที่เกี่ยวข้องกับไฟฟ้า ระบบควบคุม ฟิสิกส์ คงจะเคยได้ใช้งานเครื่องกำเนิดสัญญาณในห้องปฏิบัติการเพื่อวิเคราะห์วงจร ศึกษาผลตอบสนองความถี่ หรือกระตุ้นพลวัตของระบบ เครื่องมือที่ใช้ส่วนใหญ่มีคุณสมบัติหลักคือความเที่ยงตรงและความเชื่อถือได้สูง มีฟังก์ชันช่วยให้ใช้งานง่าย กำเนิดสัญญาณได้หลายชนิดและได้ถึงความถี่สูงมากๆ แต่อาจไม่มีความจำเป็นต้องเชื่อมต่ออินเทอร์เน็ต ที่สำคัญคือราคาจะสูงตามคุณภาพ การที่จะลงทุนซื้อมาสักเครื่องใช้ทดสอบต้นแบบฮาร์ดแวร์ที่สร้างขึ้นเป็นงานอดิเรกอาจจะทำให้ถูกค้อนจาก CEO ที่บ้านได้ ดังนั้นในบทความนี้จะนำเสนอการสร้างอุปกรณ์กำเนิดสัญญาณบน ESP32 ที่สั่งงานผ่านอินเทอร์เน็ตได้โดย NETPIE จุดเด่นของโครงการนี้คือมีต้นทุนต่ำ พกพาสะดวก ใช้งานได้ดีในย่านความถี่ต่ำถึงประมาณ 1 KHz และที่สำคัญเป็นการเพิ่มทักษะการเขียนโปรแกรม ESP32+การพัฒนา NETPIE Freeboard SGIoT เป็นอุปกรณ์ที่เหมาะสมสำหรับประกอบการสอนเชิงปฏิบัติการในชั้นเรียนเกี่ยวกับการพัฒนา IoT ด้านวิศวกรรมไฟฟ้า เครื่องกล ระบบควบคุม โดยเฉพาะเมื่อเนื้อหาเกี่ยวข้องกับการประมวลผลสัญญาณดิจิทัล การแปลง Z อัลกอริทึม FFT การวิเคราะห์วงจรไฟฟ้า ผลตอบสนองความถี่ของตัวควบคุมป้อนกลับ

ฮาร์ดแวร์ต้นแบบ

อุปกรณ์ฮาร์ดแวร์ที่ใช้สำหรับสร้าง SGIoT มีเพียงบอร์ด ESP32 ที่ท่านมีอยู่ ในตัวอย่างนี้ผู้เขียนใช้ผลิตภัณฑ์ ESP32 Dev Kit V1 จาก www.doit.am ที่หาได้ในลิ้นชัก และก็ออปแอมป์ MCP604 จากบริษัท Microchip ที่สามารถใช้ไฟเลี้ยง 3.3 โวลต์ ต่อเป็นวงจรตามแรงดัน (voltage follower) สำหรับบัฟเฟอร์ป้องกัน ESP32 เสียหายหากเกิดลัดวงจร (ซึ่งอาจจะไม่จำเป็นถ้าท่านใช้งานอย่างระวัง หรืออาจจะแค่ต่อตัวต้นทานสัก 100 โอห์มไว้จำกัดกระแส เอาต์พุตของ ESP32 เองก็มีการป้องกันภายในระดับหนึ่ง) วงจรออปแอมป์ใช้เพียงภาคเดียว ดังนั้นจะใช้ MCP601 ก็เพียงพอ แต่พอดีมี MC604 เหลืออยู่เลยนำมาใช้ รูปที่ 1 แสดงต้นแบบที่ประกอบขึ้นบนบอร์ดอเนกประสงค์

รูปที่ 1 ต้นแบบ SGIoT สร้างจากบอร์ด ESP32 Dev Kit V1 และออปแอมป์ MCP604

หมายเหตุ : เการใช้ออปแอมป์ทำให้ฃ่วงแรงดันของเอาต์พุตที่เป็นเชิงเส้นน้อยกว่าช่วงของไฟเลี้ยง 0 - 3.3 โวลต์

เหตุผลสำคัญที่ต้องใช้ ESP32 แทน ESP8266 ที่ราคาต่ำกว่าคือ ESP8266 มีเอาต์พุตที่เป็นสัญญาณแอนะล็อกแท้ (DAC) ให้ใช้งาน หากใช้ ESP8266 จะมีเอาต์พุตเป็นแบบ PWM ซึ่งจะต้องผ่านวงจร LPF จึงจะได้รูปคลื่นสัญญาณต่อเนื่อง

คุณสมบัติของ SGIoT ที่ต้องการ

  • สามารถเลือกประเภทสัญญาณพื้นฐาน 5 ชนิด คือ sine, square, triangle, sawtooth, random
  • ปรับความถี่ได้ตั้งแต่ 0.001 – 1000 Hz
  • ปรับขนาดสัญญาณได้ โดยค่ายอดสูงสุดจำกัดไว้ไม่เกิน 1 โวลต์ และให้ค่าเฉลี่ยของสัญญาณอยู่ที่ 1.65 โวลต์ ดังนั้นเอาต์พุตจะถูกจำกัดอยู่ในช่วง 0.65 - 2.65 โวลต์
  • สามารถส่งคำสั่งผ่านพอร์ตอนุกรมหรือจาก NETPIE Freeboard โดยมีการอัพเดตค่าปัจจุบันไปยัง widget ควบคุม
  • เพื่อให้ได้ความถี่สูงสุดเท่าที่ซอฟต์แวร์จะทำได้ ในการใช้งานปกติจะไม่มีการส่งค่าใดๆ เพื่อแสดงผลเป็นรายคาบ (นอกจากว่าผู้ใช้งานสั่งให้ส่งค่าออกพอร์ตอนุกรมเพื่อตรวจสอบการทำงาน)

หมายเหตุ : ความถี่สูงสุดถูกจำกัดโดยคาบเวลาการทำงานของฟังก์ชัน loop() หากต้องการทราบความถี่สูงสุดที่กำเนิดได้ ทำได้โดยวัดค่าคาบเวลาของคำสั่งทั้งหมดใน loop() โดยฟังก์ชัน micros() และคูณด้วยจำนวนจุดข้อมูล ตัวอย่างเช่นวัดคาบเวลา loop() ได้ 10 ไมโครวินาที ความละเอียดข้อมูล 36 จุดต่อไซเคิล จะได้คาบเวลาน้อยสุดของไซเคิล 360 ไมโครวินาที หรือแปลงเป็นความถี่สูงสุดเท่ากับ 2.7 KHz ข้อจำกัดนี้อ้างอิงกับรูปคลื่นไซน์ที่มีการแปรค่าอย่างต่อเนื่องในหนึ่งไซเคิล หากต้องการเพิ่มความถี่สูงสุดสามารถทำได้โดยลดจำนวนการสุ่มในหนึ่งไซเคิลลง ซึ่งมีผลทำให้สัญญาณมีลักษณะเป็นขั้นมากขึ้น

งานหลักของการพัฒนา SGIoT คือส่วนของโปรแกรม ซึ่งจะเลือกอธิบายเฉพาะส่วนสำคัญ ในเบื้องต้นจะเขียนส่วนที่อิมพลิเมนต์ฟังก์ชันสำหรับกำเนิดสัญญาณโดยสั่งงานผ่านพอร์ตอนุกรม เมื่อทดสอบว่าทำงานถูกต้องแล้วจึงเพิ่มส่วนการสื่อสารกับ NETPIE ภายหลัง

เวกเตอร์เก็บข้อมูลรูปคลื่นสัญญาณ

สมมุติว่าเราต้องการเขียนฟังก์ชันเพื่อกำเนิดสัญญาณรูปคลื่นไซน์ แนวคิดเบื้องต้นคือใชฟังก์ชัน sin() โดยตรงและใส่ค่ามุมที่ต้องการ วิธีการดังกล่าวมีข้อเสียคือ ฟังก์ชันคำนวณค่า sin() จะใช้เวลาการประมวลผลนาน ทำให้เป็นข้อจำกัดหากต้องการสร้างสัญญาณที่มีความถี่สูง (คาบเวลาแปรผกผันกับความถี่) วิธีการที่เหมาะสมกว่าคือคำนวณค่า sin() ที่มีความละเอียดตามต้องการและเก็บข้อมูลไว้ในเวกเตอร์ที่สามารถเรียกใช้ได้ทันที เพราะข้อมูลนี้เป็นค่าคงที่ไม่ขึ้นกับความถี่และสามารถปรับมาตราส่วนตามขนาดค่ายอดที่ผู้ใช้กำหนด ในตัวอย่างนี้จะใช้ความละเอียด 36 จุดต่อหนึ่งไซเคิลของรูปคลื่น หากต้องการความละเอียดมากกว่านี้ก็สามารถทำได้แต่จะทำให้ความถี่สูงสุดที่กำเนิดได้ลดลง

ในช่วงต้นของโปรแกรม นิยามจำนวนจุดและเวกเตอร์สำหรับเก็บข้อมูลสัญญาณประเภทต่างๆ


#define DATAPOINTS 36
float sinvec[DATAPOINTS],sqvec[DATAPOINTS],trivec[DATAPOINTS],
 stvec[DATAPOINTS],randvec[DATAPOINTS];

สำหรับรูปคลื่นไซน์ ค่ามุมที่เปลี่ยนในแต่ละจุดข้อมูลคำนวณได้จาก


const float twopi = 44/7;
float astep = twopi/DATAPOINTS;

ฟังก์ชันกำเนิดข้อมูลสำหรับรูปคลื่นไซน์เขียนได้เป็นดังนี้


void gensinvec(void)   // generate sin-wave data
{
  radangle = 0;
  int i = 0;
  int middlepts = DATAPOINTS/2;
  for (i=0;i

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


void gensqvec(void)   // generate square-wave data
{
  int i=0;
  int middlepts = DATAPOINTS/2;
  for (i = 0; i

อธิบายโดยย่อคือรูปคลื่นสี่เหลี่ยมคือการสลับค่าระหว่างค่า 1 กับ -1 ทุกครึ่งไซเคิล ส่วนรูปคลื่นสามเหลี่ยมและฟันเลื่อยสร้างได้จากสมการเส้นตรงที่มีจุดตัดแกน y และความชันสอดคล้องกับแต่ละส่วนของรูปคลื่น ฟังก์ชันเหล่านี้จะถูกเรียกใน setup() และเก็บข้อมูลรูปคลื่นที่กำเนิดไว้ในตัวแปร sqvec, trivec, stvec ส่วนการสร้างสัญญาณ random ไม่ต้องใช้ตัวแปรเวกเตอร์เก็บข้อมูล แต่จะกำเนิดค่าใน loop() และส่งเอาต์พุตออกในทันที

นิยามตัวแปร sigselect สำหรับเลือกประเภทสัญญาณ


int sigselect = 0;  // signal select 0/1/2/3/4 = sin/square/triangle/sawtooth/random

และตัวแปร sigfreq, sigperiod, stepperiod, stepperiod_us เป็นแบบ float สำหรับคำนวณความถี่และคาบเวลาสัญญาณ ความถี่ที่ผู้ใช้เลือกจะเก็บในตัวแปร sigfreq คาบเวลาของสัญญาณและคาบเวลาของแต่ละจุดข้อมูลคำนวณได้จาก


sigperiod = 1/sigfreq;      // signal period in second
stepperiod = sigperiod/DATAPOINTS; // period between data points (seconds)
stepperiod_us = 1000000*stepperiod; // period between data points (microsecs)

โดย stepperiod_us เป็นคาบเวลาของจุดข้อมูลที่มีหน่วยเป็นไมโครวินาที ซึ่งจะใช้ฟังก์ชัน micros() เพื่อตรวจสอบว่าถึงเวลาที่จะส่งจุดข้อมูลใหม่ออกขา DAC เมื่อไร

ตัวอย่างเช่นต้องการกำเนิดสัญญาณรูปคลื่นไซน์ความถี่ 1 KHz คาบเวลาของ 1 ไซเคิลคือ 1000 ไมโครวินาที เมื่อใช้ความละเอียด 36 จุดต่อไซเคิล คาบเวลาในการส่งแต่ละจุดข้อมูลจะประมาณ 28 ไมโครวินาที (ซึ่งจัดว่าสั้นมาก เป็นอีกเหตุผลหนึ่งที่เลือกใช้ตัวประมวลผลสมรรถนะสูงเช่น ESP32)

ดังนั้นส่วนกำเนิดสัญญาณในฟังก์ชัน loop() เขียนได้ดังนี้


  currenttime_us = micros();
  if (currenttime_us stepperiod_us)
  {
    previoustime_us = currenttime_us;
        if (sigselect==0) sigout = (int)(sigmag*sinvec[idx])+127;
        else if (sigselect==1) sigout = (int)(sigmag*sqvec[idx])+127;
        else if (sigselect==2) sigout = (int)(sigmag*trivec[idx])+127;  
        else if (sigselect==3) sigout = (int)(sigmag*stvec[idx])+127;  
        else if (sigselect==4) sigout = random(-sigmag, sigmag)+127;  
    if (serialout) Serial.println(sigout);  // can be observed by serial plotter
    dacWrite(DAC1, sigout);

ข้อมูลสัญญาณที่กำเนิดไว้โดย setup() ถูกแปลงเป็นค่าที่เหมาะสมสำหรับ DAC 8 บิต โดยตัวแปร sigselect ทำหน้าที่เลือกประเภทของสัญญาณที่ส่งออก หากผู้ใช้ตั้งค่า serialout=1 จะส่งข้อมูลออกพอร์ตอนุกรมด้วยเพื่อการตรวจสอบโดยไม่ต้องใช้สโคป

ส่วนรับคำสั่งจากผู้ใช้

ส่วนรับและแปลคำสั่งจากผู้ใช้จะใช้ฟังก์ชัน cmdInt() เหมือนกับที่ใช้ในตัวอย่างโปรแกรมในบทความอื่นก่อนหน้านี้ คำสั่งที่สร้างไว้มีทั้งหมดดังนี้

  • sigfreq = x : ตั้งค่าความถี่ของสัญญาณเท่ากับ x Hz
  • sigmag=x : ตั้งค่าขนาดสูงสุดของสัญญาณ จำกัดที่ 1 โวลต์ (ยอดถึงยอด)
  • sigselect=i : เลือกประเภทสัญญาณ I = 0/1/2/3/4 แทนสัญญาณดังนี้ 0=sin, 1=square, 2=triangle, 3=sawtooth, 4=random
  • serialout=0/1 : ปิด/เปิดการส่งข้อมูลออกพอร์ตอนุกรม (แนะนำให้ปิดการใช้งานกรณีกำเนิดสัญญาณความถี่สูง เพราะคำสั่ง Serial.println() ทำให้เวลาการประมวลผลในแต่ละจุดข้อมูลเพิ่มขึ้น)

ทดสอบการทำงาน

โปรแกรมการใช้งานพื้นฐาน (โดยยังไม่เชื่อมต่อินเทอร์เน็ต) คือ signalgen.ino คอมไพล์และโหลดโปรแกรมลงบนต้นแบบในรูปที่ 1 เราสามารถตรวจสอบการทำงานของโปรแกรมว่าถูกต้องหรือไม่โดยใช้คำสั่ง


serialout=1

เพื่อส่งข้อมูลรูปคลื่นจากเครื่องกำเนิดสัญญาณออกทางพอร์ตอนุกรม ปรับค่าความถี่ไม่สูงมาก เช่น 10 Hz แล้วใช้ Serial Plotter แสดงผล รูปที่ 2 แสดงกราฟของสัญญาณทั้ง 5 ประเภทจากหน้าต่าง Serial Plotter พบว่ามีลักษณะรูปคลื่นถูกต้อง แต่ไม่สามารถตรวจสอบความถี่ได้เนื่องจากแกน x ของกราฟไม่ได้แสดงข้อมูลของเวลา

รูปที่ 2 ตรวจสอบรูปคลื่นสัญญาณจาก Serial Plotter

การตรวจสอบความถี่ต้องอาศัยออสซิลโลสโคป โดยใช้สายวัดจับที่เอาต์พุตของออปแอมป์ จากการวัดค่าคาบเวลาของรูปคลื่นไซน์ที่ความถี่ 1 KHz ได้ประมาณ 1 มิลลิวินาที ดังนั้นสรุปได้ว่าอุปกรณ์ทำงานได้ดีตามเป้าหมาย อย่างไรก็ตามเมื่อเพิ่มความถี่สูงขึ้นจะพบว่าค่าคลาดเคลื่อนของคาบเวลาจะเพิ่มมากขึ้นตาม ความถี่สูงสุดที่อุปกรณ์กำเนิดได้คือประมาณ 2.5 KHz แต่เพื่อความเที่ยงตรงของคาบเวลาย่านความถี่ที่ใช้งานไม่ควรเกิน 1 KHz รูปที่ 3 แสดงการวัดสัญญาณทั้ง 5 ประเภทโดยออสซิลโลสโคป

รูปที่ 3 วัดเอาต์พุตของอุปกรณ์กำเนิดสัญญาณโดยออสซิลโลสโคป

เพิ่ม OLED แสดงผล

จุดมุ่งหมายในการพัฒนา SGIoT คือสามารถพกพาไปใช้นอกสถานที่ได้โดยไม่ต้องมีคอมพิวเตอร์ติดไปด้วย ขอเพียงมีสัญญาณ WiFi เพื่อสามารถสั่งงานผ่าน NETPIE App ในโทรศัพท์ได้ แต่เพื่อความสะดวกสำหรับผู้ใช้จะเพิ่ม OLED ขนาดเล็กเพื่อแสดงข้อมูลเท่าที่จำเป็น คือชนิดของสัญญาณ ขนาด และความถี่ โดยจอภาพจะอัพเดทเฉพาะเวลามีการเปลี่ยนแปลงโดยผู้ใช้เท่านั้น รูปที่ 4 แสดงการเชื่อมต่อ OLED SSD1306 เข้ากับ i2c ของ ESP32 (SCL=22, SDA=21)

รูปที่ 4 เพิ่มจอ OLED สำหรับแสดงสถานะของตัวกำเนิดสัญญาณ

การใช้งานร่วมกับ NETPIE

หลังจากที่ได้ต้นแบบอุปกรณ์กำเนิดสัญญาณพื้นฐานที่ทำงานได้ถูกต้องแล้ว ขั้นตอนการพัฒนาต่อคือเพิ่มฟังก์ชันให้เป็นอุปกรณ์ไอโอทีที่สามารถส่งคำสั่งจาก NETPIE Freeboard โดยการทำงานของอุปกรณ์นี้จะไม่เหมาะสมและไม่จำเป็นที่จะส่งข้อมูลใดๆ ผ่านเครือข่ายเพื่อแสดงผล เหตุผลสำคัญคือคำสั่ง microgear ที่ใช้ในการ chat หรือ publish จะใช้เวลามากและมีผลต่อการกำเนิดสัญญาณที่ต้องการให้ลูปทำงานเร็วที่สุด ดังนั้นฟังก์ชันสำหรับอุปกรณ์ไอโอทีนี้คือการสั่งงานผ่านอินเทอร์เน็ตเท่านั้น อย่างไรก็ตาม จะต้องมีการอัพเดท control widgets ให้มีค่าและสถานะถูกต้องทุกครั้งที่มีการสั่งงานผ่าน Freeboard หรือพอร์ตอนุกรม

เพื่อเอื้ออำนวยต่อการสร้าง widget ควบคุมบน NETPIE ที่จะได้กล่าวถึงต่อไป จะเพิ่มคำสั่งสำหรับหยุดและเริ่มการกำเนิดสัญญาณ

  • sgactive=0/1 : 0 = หยุด 1 = เริ่มกำเนิดสัญญาณ

ในฟังก์ชัน cmdInt() เพิ่มคำสั่ง sgactive เพื่อให้ผู้ใช้สามารถสั่งหยุดหรือเริ่มกำเนิดสัญญาณได้ บริเวณส่วนบน สร้างตัวแปร SGactive เป็นชนิด bool


bool SGactive = 1;  // flag to stop/start signal generator

และเพิ่มเงื่อนไขไปในโปรแกรมดังนี้


if (SGactive)  {
	// คำสั่งกำเนิดสัญญาณเดิม
}
else   {
 	dacWrite(DAC1, DACinactvalue);
}

กล่าวคือ เมื่อหยุดการทำงาน เอาต์พุตจากขา DAC1 จะเป็นค่าคงที่ที่กำหนดโดยตัวแปร DACinactvalue ในตัวอย่างโปรแกรมตั้งไว้ที่ค่ากลางคือ 127 (1.65 โวลต์)

หมายเหตุ : ตัวแปร SGactive จะถูกตั้งค่าเป็น 1 อัตโนมัติ เมื่อมีการเลือกประเภทของสัญญาณโดยคำสั่ง sigselect

ณ เวลาที่เขียนบทความนี้ NETPIE ยังไม่มีไลบรารี microgear รองรับ ESP32 เป็นทางการ แต่สามารถใช้ ESP32 microgear ที่พัฒนาโดย บ.แล่มเลย ซึ่งใช้งานได้ดีกับ ESP32 Arduino Core เวอร์ชันเก่าก่อนที่จะมีการติดตั้งโดย Board Manager จากการทดสอบของผู้เขียนเองพบว่าไลบรารีของแล่มเลย มีปัญหากับ ESP32 Arduino Core version 1.0.0-rc2 ที่ติดตั้งโดย Board Manager โดยมักจะหลุดหรือค้างบ่อย

ในบทความนี้จะไม่ลงรายละเอียดการเขียนโปรแกรมเพื่อให้ ESP32 เชื่อมต่อกับ NETPIE เพราะได้กล่าวอธิบายในบทความอื่นและในหนังสือแล้ว ขั้นตอนหลักคือสร้าง APPID, key, secret และนำข้อมูลมาใส่ในส่วนต้นของโปรแกรม รวมถึงตั้งชื่อ ALIAS ตัวอย่างเช่น


#define APPID   "SGIoT" // เปลี่ยนเป็นข้อมูลของท่าน
#define KEY     "bGrLbn1JHT0jLN" // เปลี่ยนเป็นข้อมูลของท่าน
#define SECRET  "wn78K4BdPkrakVlXl1hZG5a" // เปลี่ยนเป็นข้อมูลของท่าน
#define ALIAS   "SGIOT" // เปลี่ยนเป็นข้อมูลของท่าน 

ข้อมูลที่จะส่งให้ NETPIE มีเพียงการอัพเดทสถานะของสไลเดอร์และสวิตช์ควบคุม โดยจะส่งเฉพาะเมื่อผู้ใช้เปลี่ยนแปลงค่าเท่านั้น นิยามชื่อ topic


#define SGIOTDATATOPIC "/sgiotcdata/" ALIAS  // SGOIT control data topic

และเขียนคำสั่งการ publish ค่าไว้ในฟังก์ชัน update_freeboard() ที่จะถูกเรียกทุกครั้งที่มีการเปลี่ยนแปลงสถานะหรือค่าตัวแปรที่เกี่ยวข้องกับการกำเนิดสัญญาณ


void update_freeboard(void)
{
      String cstates = (String)sinstate+","
      +(String)sqstate+"," +(String)tristate+"," +(String)ststate+"," 
      +(String)randstate+"," +(String)sigmagv+","+(String)sigfreq; 
      microgear.publish(SGIOTDATATOPIC, cstates);
}

ค่าที่ต้องการอัพเดทคือสถานะของ widgets บน Freeboard ประกอบด้วยสวิตช์ที่เป็นตัวเลือกประเภทของสัญญาณ 5 ชนิด สถานะถูกกำหนดโดยตัวแปร sinstate, sqstate, tristate, ststate, randstae และสไลเดอร์ปรับค่าขนาดและความถี่ของสัญญาณที่ค่าตำแหน่งสไลเดอร์ถูกกำหนดโดย sinmagv และ sinfreq

สร้าง Freeboard ชื่อ SGIoTFB และ datasource ชนิด Microgear ชื่อ SGIoTds และ widgets ดังแสดงในรูปที่ 5

รูปที่ 5 หน้า Freeboard สำหรับควบคุมอุปกรณ์กำเนิดสัญญาณ

สำหรับการสร้างและตั้งค่า slider widgets และ HTML widgets ไม่มีอะไรแตกต่างจากที่เคยอธิบายในบทความอื่นบนเว็บนี้ ตัวอย่างเช่นการตั้งค่า slider widget สำหรับปรับความถี่จะเป็นดังรูปที่ 6

รูปที่ 6 การตั้งค่า slider widget สำหรับปรับความถี่

ส่วนที่ต้องใส่คำสั่งมี 2 ช่อง คือช่อง ONSTOP ACTION ที่จะส่งข้อมูลให้กับ ESP32 เมื่อ slider ถูกปรับไปหยุดที่ตำแหน่งใหม่ ใช้วิธี microgear.chat() มีรูปแบบดังนี้


microgear["SGIoTds"].chat("SGIOT","sigfreq="+value)

SGIoTds คือชื่อของ datasource ที่ตั้งไว้ก่อนหน้า ดังนั้นสตริงที่ส่งให้ ESP32 ที่ตั้งชื่อ ALIAS=SGIOT คือ sigfreq=x โดย x คือค่าตำแหน่งใหม่ของสไลเดอร์

ส่วนในช่อง AUTO UPDATED VALUE สำหรับรับค่าข้อมูลที่พับลิชมาจากด้าน ESP32 เพื่อปรับค่าตำแหน่งของสไลเดอร์ให้ตรงกับค่าตัวแปรในโปรแกรม ใส่คำสั่งดังนี้


datasources["SGIoTds"]["/SGIoT/sgiotcdata/SGIOT"].split(",")[6]

โดยใช้ split() เลือกข้อมูลตำแหน่งที่เป็นค่าความถี่จากสตริงรวมที่พับลิชมา

สำหรับปุ่มกด Update ใช้สำหรับปรับค่าของ widgets ทั้งหมดให้ตรงกับค่าปัจจุบันกรณีที่ปิดเปิดหน้าต่างใหม่หรือรีเฟรช โดยจะเพิ่มคำสั่ง updatefb เพื่อเรียกฟังก์ชัน update_freeboard() โดยตรง

widgets ส่วนที่น่าสนใจในการพัฒนาอุปกรณ์นี้คือสวิตช์ toggle 5 ตัวที่ใช้ในการเลือกประเภทสัญญาณ โดยการควบคุมชุดนี้จะต้องมีลักษณะการทำงานแบบ mutually exclusive ผู้ที่คุ้นเคยกับการสร้างส่วนติดต่อผู้ใช้จะรู้จักในชื่อ radio button คือจะมีสวิตช์เพียงตัวเดียวเท่านั้นที่สามารถอยู่ในสถานะ ON สิ่งที่แตกต่างจาก radio button คือเราสามารถสั่งให้สวิตช์ทั้งหมดอยู่ในสถานะ OFF ได้ ทำให้อุปกรณ์กำเนิดสัญญาณให้ค่าคงที่ที่นิยามเป็นระดับศูนย์ (ผู้ใช้กำหนดไว้ในโปรแกรม)ทางเอาต์พุต NETPIE Freeboard ไม่มี widget เฉพาะสำหรับ radio button แต่เราสามารถสร้างเองได้โดยใช้ toggle widgets ทำงานร่วมกัน

ตัวอย่างเช่นในรูปที่ 5 สัญญาณ sine ถูกเลือกอยู่ หากผู้ใช้กดเลือกปุ่ม square คำสั่งเลือกสัญญาณ sigselect=1 (square wave) จะถูกส่งไปยัง ESP32 เพื่อเปลี่ยนรูปแบบการกำเนิดสัญญาณเป็นรูปคลื่นสีเหลี่ยม และ ESP32 ก็จะเปลี่ยนสถานะของตัวแปรสำหรับสวิตช์ทั้งหมด และพับลิชค่ามาเพื่ออัพเดท toggle widgets ทั้ง 5 ตัว รูปที่ 7 แสดงตัวอย่างการตั้งค่า toggle widget สำหรับเลือกรูปคลื่น sine

รูปที่ 7 การตั้งค่า toggle widget สำหรับรูปคลื่น sine

คำสั่งที่ใส่ในช่องอินพุตสำหรับตั้งค่าเป็นดังนี้

  • TOGGLE STATE: datasources["SGIoTds"]["/SGIoT/sgiotcdata/SGIOT"].split(",")[0]==1
  • ONTOGGLEON ACTION: microgear["SGIoTds"].chat("SGIOT","sigselect=0")
  • ONTOGGLEOFF ACTION: microgear["SGIoTds"].chat("SGIOT","sgactive=0")

เมื่อสั่ง ON คำสั่ง sigselect=0 คือเลือกกำเนิดสัญญาณ sine ส่วนเมื่อสั่ง OFF จะส่งคำสั่ง sgactive=0 เพื่อสั่งให้อุปกรณ์หยุดกำเนิดสัญญาณ ส่วนทางด้านการอัพเดตสถานะจะกำหนดโดย TOGGLE STATE โดยเลือกข้อมูลตำแหน่งแรกจากสตริงที่พับลิชมา

การปรับปรุงให้อุปกรณ์ทำงานได้ดีขึ้น

ในการพัฒนาขั้นต้น โปรแกรม ESP32 ตั้งชื่อว่า SGIoT1t_NETPIE.ino โดยเขียนในรูปแบบปกติคือทุกสิ่งที่ทำงานอย่างต่อเนื่องจะรวมอยู่ใน loop() การสื่อสารกับ NETPIE ทำได้โดยเพิ่มโค้ดเข้าไปที่ส่วนท้ายของ loop() ดังนี้


  if (netpie)   {  // can turn netpie off by command
    newmgloop = millis();
    if ((newmgloop - lastmgloop)>mginterval)   {
      lastmgloop = newmgloop;
      if (microgear.connected()) microgear.loop();
      else   {
          Serial.println("connection lost, reconnect...");
          microgear.connect(APPID);       
      }
    }
  }

โดยเพิ่มตัวแปร bool ชื่อ netpie เพื่อให้ผู้ใช้สามารถเลือกไม่ใช้งานได้กรณีไม่มีสัญญาณ WiFi หลักการทำงานเหมือนกับการใช้งานทั่วไป คือตรวจสอบว่ายังเชื่อมต่อกับ NETPIE อยู่หรือไม่ ถ้ายังต่ออยู่ต้องเรียก microgear.loop() เป็นระยะเพื่อรักษาการเชื่อมต่อไว้ ในที่นี้ตั้งคาบเวลาไว้ประมาณ 100 มิลลิวินาที แต่หากการเชื่อมต่อหลุดไป ต้องเชื่อมต่อใหม่โดยคำสั่ง microgear.connect(APPID)

สังเกตว่าการเขียนโปรแกรมลักษณะนี้ ส่วนที่เพิ่มเข้าไปจะมีผลกับคาบเวลาการกำเนิดสัญญาณเพราะจะทำให้คาบเวลาของ loop() นานขึ้น แต่จะไม่กระทบมากนักหากยังเชื่อมต่ออยู่ เพราะจับเวลาที่เพิ่มขึ้นประมาณ 1 ไมโครวินาทีเท่านั้นซึ่งน้อยมากจนไม่มีผล ปัญหาจะเกิดขึ้นหากการเชื่อมต่อหลุด เช่นเน็ตไม่เสถียร การเชื่อมต่อใหม่โดยฟังก์ชัน microgear.connect() จะใช้เวลามากจนถึงขั้นอุปกรณ์หยุดกำเนิดสัญญาณเมื่อเชื่อมต่อไม่สำเร็จ (จะสังเกตได้จาก LED บนบอร์ดค้าง)

การแก้ปัญหานี้ทำได้โดยอาศัยข้อได้เปรียบที่สำคัญอีกประการของ ESP32 คือมีคอร์ประมวลผล 2 ชุด หากสามารถแยกโปรแกรมส่วนกำเนิดสัญญาณและส่วนเชื่อมต่อ NETPIE เป็น 2 เทรดที่อิสระต่อกัน เมื่อมีปัญหาการเชื่อมต่อ NETPIE จะไม่กระทบการกำเนิดสัญญาณ หลักการแยกคอร์ประมวลผลบน ESP32 นี้ได้อธิบายและสาธิตในหนังสือ "ระบบควบคุมและอินเทอร์เน็ตเชื่อมต่อสรรพสิ่ง" โดยจะต้องอาศัยฟังก์ชันของ FreeRTOS ที่เป็นไลบรารีบน ESP32 Arduino core อยู่แล้วไม่ต้องโหลดเพิ่มเติม

เนื้อหาของการกำหนดคอร์ประมวลผลให้กับทาสก์นี้มีรายละเอียดค่อนข้างมากไม่สามารถใส่ในบทความนี้จึงขออ้างอิงหนังสือและดูโปรแกรม sgIoT2t_NETPIE.ino ประกอบ กล่าวโดยสรุปคือเราทราบว่าฟังก์ชัน loop() จะทำงานบนคอร์หมายเลข 1 ดังนั้นจังสร้างทาสก์สำหรับ NETPIE และกำหนดให้ทำงานบนคอร์หมายเลข 0 โดยฟังก์ชันที่เพิ่มเติมดังนี้


void freertos_init(void) {
   T1_ms = 1000;   // ms
   T1ticks = pdMS_TO_TICKS(T1_ms);  
   xTaskCreatePinnedToCore(Task1, "NETPIETask", 10000, NULL, 3, &xTask1h, 0);   
   if (!netpie) vTaskSuspend(xTask1h);
}
// task for NETPIE microgear
void Task1( void * parameter)   {
  if (netpie&(!netpieinit))   {
      microgear.init(KEY,SECRET,ALIAS);   // กำหนดค่าตันแปรเริ่มต้นให้กับ microgear
      microgear.connect(APPID);           // ฟังก์ชั่นสำหรับเชื่อมต่อ NETPIE
      update_freeboard();
      netpieinit=1;
  }
  for(;;)   {
    if (netpie)   {  // can turn netpie off by command
        if (microgear.connected()) microgear.loop();
        else   {
            Serial.println("connection lost, reconnect...");
            microgear.connect(APPID);       
        }
    }  // if (netpie)
    vTaskDelay(T1ticks);
  }  // for(;;)
  vTaskDelete(NULL);
}

หมายเหตุ : หากเราอยากทราบว่าส่วนของโค้ดหรือฟังก์ชันหนึ่งถูกประมวลผลบนคอร์ใหน สามารถใช้คำสั่ง


Serial.println("This part of code run on core"+(String)xPortGetCoreID());

แทรกเข้าไปในส่วนนั้น จะแสดงข้อความระบุหมายเลขคอร์ออกทาง Serial Monitor

การทดสอบ SGIoT

เพื่อแสดงให้เห็นประโยชน์ในการพกพา จะทดสอบใช้การควบคุมผ่าน NETPIE Mobile App ผลจากการทดสอบบางส่วนแสดงในรูปที่ 8 – 12 โดยทุก widgets สามารถทำงานได้ตามวัตถุประสงค์ ทดลองปิด WiFi เพื่อให้การเชื่อมต่อ NETPIE ไม่สำเร็จ หากใช้โปรแกรม SGIoT1t_NETPIE.ino การกำเนิดสัญญาณจะถูกกระทบอย่างมากจนไม่สามารถทำงานได้ แต่สำหรับโปรแกรม SGIoT2t_NETPIE.ino อุปกรณ์จะยังกำเนิดสัญญาณได้ เพียงแต่ไม่สามารถควบคุมผ่าน NETPIE Freeboard ต้องใช้คำสั่งผ่านพอร์ตอนุกรมเท่านั้น

รูปที่ 8 ทดสอบการกำเนิดรูปคลื่นซายน์
รูปที่ 9 ทดสอบการกำเนิดรูปคลื่นสี่เหลี่ยม
รูปที่ 10 ทดสอบการกำเนิดรูปคลื่นสามเหลี่ยม
รูปที่ 11 ทดสอบการกำเนิดรูปคลื่นฟันเลื่อย
รูปที่ 12 ทดสอบการกำเนิดรูปคลื่น random

สรุป

บทความนี้นำเสนอการสร้างอุปกรณ์ SGIoT สำหรับกำเนิดสัญญาณโดยสามารถควบคุมและแสดงสถานะผ่าน NETPIE Freeboard โดยใช้เพียงบอร์ด ESP32 และออปแอมป์ ประโยชน์หลักคือใช้ประกอบการสอนในวิชาด้านวิศวกรรมศาสตร์ หรือศึกษาด้านการพัฒนา IoT หรือใช้ทดสอบวงจรและระบบขั้นพื้นฐานได้ แม้ว่าการประยุกต์ใช้งานอุปกรณ์นี้อาจไม่แพร่หลายเท่าอุปกรณ์ IoT ประเภทอื่นแต่ก็อาจเป็นประโยชน์ในงานเฉพาะด้านวิศวกรรม/อุตสาหกรรมที่เกี่ยวข้องกับสัญญาณและระบบ

เมื่อเข้าใจหลักการแล้วผู้อ่านสามารถเพิ่มชนิดของสัญญาณที่ต้องการ หรือปรับแต่งเพื่อเพิ่มความถี่สูงสุด (โดยลดความละเอียดของรูปคลื่น) ในกรณีใช้ในบริเวณที่ไม่มีสัญญาณ WiFi อาจปรับปรุงอุปกรณ์โดยเพิ่มสวิตช์กดให้บายพาส หรือให้รอรับคำสั่งบายพาสการเชื่อมต่อ มิฉะนั้นโปรแกรมจะค้างอยู่ตรงจุดเชื่อมต่อเครือข่าย

วีดีโอแสดงการทำงาน

รวมโปรแกรมทั้งหมด SGIoT.zip

No comments:

Post a Comment

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

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