(วิธีการทำให้ “ESP8266” อ่านและเขียนไปยัง “Arduino UNO Maker” ผ่านบัส I2C.)
ESP8266 เป็นอุปกรณ์ที่รองรับการทำงานแบบ IoT ได้เป็นอย่างดี แต่ข้อเสียประการหนึ่งของ ESP8266 คือ จำนวน GPIO หรือขาสำหรับนำเข้าสัญญาณและนำออกสัญญาณที่สามารถใช้งานได้โดยไม่เป็นปัญหากับการทำงานของบอร์ดมีไม่มากนัก ดังนั้น การขยายพอร์ตให้กับ ESP8266 จึงเป็นสิ่งที่นักพัฒนาต้องประสบพบเจอ ซึ่งสามารถเลือกดำเนินการได้หลายวิธี เช่น ใช้ PCF8574 เป็นพอร์ตขยายผ่านทางบัส I2C หรือเชื่อมต่อกับ Arduino ผ่านทางพอร์ตสื่อสารอนุกรม (Serial Port) เพื่อให้ Arduino เป็นผู้ทำงานและส่งผลลัพธ์กลับมาทางพอร์ตสื่อสารอนุกรม เป็นต้น โดยในบทความนี้เลือกการใช้ Arduino Uno เป็นบอร์ดสำหรับเป็น I/O ให้กับ ESP8266 โดยอาศัยการสั่งงานผ่านทางบัส I2C
โดยตัวอย่างในการทดลองครั้งนี้ใช้ภาษา C/C++ สำหรับบอร์ด Arduino Uno และภาษาไพธอนของ MicroPython สำหรับบอร์ด ESP8266
อุปกรณ์ทดลอง
- บอร์ด ESP8266 (ในบทความนี้ใช้ WeMos D1 mini)
- บอร์ด Arduino Uno (ในบทความนี้ใช้บอร์ด Arduino Uno Maker)
- LED x 8 (บนบอร์ด Arduino Uno Maker มีมาให้แล้ว)
- SW x 8 (เลือกใช้ ET-TEST 10P/INP)
การเชื่อมต่อ
- การเชื่อมต่อระหว่าง ESP8266 กับ Arduino Uno Maker
D1 (GPIO5) –> [SDA] –> A4
D2 (GPIO4) –> [SCL] –> A5
- การเชื่อมต่อระหว่าง SWx8 กับ Arduino Uno Maker
D2 –> SW1
D3 –> SW2
D4 –> SW3
D5 –> SW4
D6 –> SW5
D7 –> SW6
D8 –> SW7
D9 –> SW8
5V –> VCC
GND –> GND
การทำงานของET-TEST 10P/INP มีการใช้ R-Pull up ทำให้สถานะของขานำเข้าสัญญาณเมื่อไม่มีการกดสวิตช์มีค่าเป็น 1 และเมื่อกดสวิตช์ทำให้มีค่าเป็น 0
- วงจร LED ของบอร์ด Arduino Uno Maker เป็นดังภาพที่ 6 ซึ่งหลอดติดเมื่อส่งค่า 1 ไปที่ขา D2 ถึง D13 และหลอดดับเมื่อส่งค่า 0 ไปยังขาดังกล่าว
โปรแกรมสำหรับบอร์ด Arduino Uno
การทำงานของโปรแกรมฝั่ง Arduino Uno มี 2 ส่วน คือ ส่วนของการรับค่าจากบัส I2C และส่วนของการนำค่าส่งเข้าไปยังบัส I2C โดย กำหนดค่าตำแหน่งของบอร์ด Arduino Uno ที่ใช้ในการระบุอุปกรณ์ที่เชื่อมต่อกับบัส I2C เป็น 7
กำหนดให้มีรูปแบบของการรับค่าจากบัสเป็นข้อมูลขนาด 2 ไบต์ โดยไบต์ 0 เป็นตัวกำหนดคำสั่ง ซึ่งในที่นี้กำหนดไว้ 3 รูปแบบ และไบต์ 1 ใช้สำหรับเป็นข้อมูลประกอบคำสั่ง โดยคำสั่งทั้ง 3 ได้แก่
0 สำหรับสั่งให้ LED ที่เชื่อมต่อกับขา D2 ถึง D9 ดับ และไม่ได้นำค่าไบต์ 1 มาใช้งาน
1 สำหรับสั่งให้ LED ที่เชื่อมต่อกับขา D2 ถึง D9 ติด และไม่ได้นำค่าไบต์ 1 มาใช้งาน
2 สำหรับสั่งให้ LED ที่เชื่อมต่อกับขา D2 ถึง D9 ติดตามค่ารูปแบบที่อ่านได้จากไบต์ 1
ซึ่งการกำหนดฟังก์ชันที่ตอบสนองกับการรับคำสั่งจากบัส I2C คือฟังก์ชัน i2cReceive() ที่ต้องระบุเอาไว้ในคำสั่ง Wire.onReceive()
การอ่านข้อมูลจากบอร์ด Arduino Uno ผ่านทางบัส I2C ซึ่งในตัวอย่างการทดลองนี้เป็นการอ่านสถานะสวิตช์ที่เชื่อมต่อกับพอร์ต D2 ถึง D9 ดังภาพที่ 6 โดยฟังก์ชันที่ตอบสนองเมื่อเกิดการร้องขอข้อมูลจากบัสคือ i2cRequest() และคำสั่งสำหรับระบุการตอบสนองเมื่อมีการร้องขอข้อมูลคือคำสั่ง Wire.onRequest()
โค้ดโปรแกรมของฝั่งบอร์ด Arduino Uno เป็นดัง code-01
// code-01 โค้ดของ Arduino Uno
#include <Wire.h>
int unoAddr = 7;
void i2cReceive( int bytes ) {
uint8_t dInput = Wire.read();
uint8_t pattern = Wire.read();
Serial.println(dInput);
if (dInput == 0) {
for (int ledPin = 2; ledPin <= 9; ledPin++) {
pinMode(ledPin, OUTPUT);
}
for (int ledPin = 2; ledPin <= 9; ledPin++) {
digitalWrite(ledPin, 0);
}
}
else if (dInput == 1) {
for (int ledPin = 2; ledPin <= 9; ledPin++) {
pinMode(ledPin, OUTPUT);
}
for (int ledPin = 2; ledPin <= 9; ledPin++) {
digitalWrite(ledPin, 1);
}
}
else if (dInput == 2) {
for (int ledPin = 2; ledPin <= 9; ledPin++) {
pinMode(ledPin, OUTPUT);
}
uint8_t mask = 0x01;
for (int ledPin = 2; ledPin <= 9; ledPin++) {
digitalWrite(ledPin, pattern & mask);
mask <<= 1;
}
} else {
return;
}
}
void i2cRequest() {
for (int ledPin = 2; ledPin <= 9; ledPin++) {
pinMode(ledPin, INPUT_PULLUP);
}
uint8_t bitNo = 0;
uint8_t unoBuffer = 0;
for (int swPin = 2; swPin <= 9; swPin++) {
unoBuffer |= digitalRead( swPin ) << bitNo;
bitNo++;
}
Serial.println(unoBuffer, HEX);
Wire.write(unoBuffer);
}
void setup() {
Serial.begin(115200);
Serial.println("UNO Started");
Wire.begin(unoAddr);
Wire.onReceive(i2cReceive);
Wire.onRequest(i2cRequest);
}
void loop() {
}
โปรแกรมของ ESP8266
โค้ดของฝั่ง ESP8266 มีการเชื่อมต่อกับบัส I2C ผ่านทางขา SDA และ SCL โดยกำหนดความถี่ของสัญญาณนาฬิกาในการสื่อสารเป็น 10000 และมีการสร้างตัวแปร unoBuffer เป็นแถวลำดับขนาด 2 ไบต์ เพื่อใช้สำหรับการสั่งคำสั่งและข้อมูลให้กับบอร์ด Arduino Uno ซึ่งโปรแกรมตัวอย่างตาม code-02 ทำการสั่งให้หลอด LED ของ Arduino Uno ดับเป็นเวลา 1 วินาที หลังจากนั้นสั่งให้ทั้ง 8 หลอดติด เป็นเวลา 1 วินาที แล้วสั่งให้หลอดติดดับคั่นกันไปเป็นเวลา 1 วินาที สุดท้ายทำการอ่านข้อมูลจากบอร์ด Arduino Uno และแสดงผลบน terminal ของโปรแกรม
คำสั่งสำหรับสร้างตัวแปรI2C
ตัวแปรI2C = machine.I2C(sda=ขาSDA, scl=ขาSCL, freq=ความถี่ในการสื่อสาร )
คำสั่งสำหรับสร้างตัวแปรสำหรับอ้างอิงขาหรือพอร์ตของ ESP8266
ตัวแปรขา = machine.Pin(หมายเลขขา)
คำสั่งสำหรับส่งข้อมูลไปยังบัส I2C มีรูปแบบการใช้งานดังนี้
ตัวแปรI2C.writeto( ตำแหน่งของอุปกรณ์, ข้อมูลที่ส่งเข้าไปในบัส )
คำสั่งสำหรับอ่านข้อมูลจากบัส I2C จำนวน n ไบต์มีรูปแบบการใช้งานดังนี้
ตัวแปรเก็บข้อมูล = ตัวแปรI2C.readfrom( ตำแหน่งของอุปกรณ์, จำนวนไบต์ที่อ่าน )
#code-02 โค้ดของ ESP8266
from machine import Pin, I2C
import time
sclPin = Pin(5)
sdaPin = Pin(4)
unoAddr = 7
unoBuffer = bytearray(2)
i2c = I2C(sda = sdaPin, scl = sclPin, freq=100000)
unoBuffer[0] = 0
unoBuffer[1] = 0
i2c.writeto(unoAddr, unoBuffer)
time.sleep_ms(1000)
unoBuffer[0] = 1
unoBuffer[1] = 0
i2c.writeto(unoAddr, unoBuffer)
time.sleep_ms(1000)
unoBuffer[0] = 2
unoBuffer[1] = 0xAA;
i2c.writeto(unoAddr, unoBuffer)
time.sleep_ms(1000)
dInput = i2c.readfrom(unoAddr,1)
print(dInput)
สรุป
จากบทความนี้จะพบว่า การสื่อสารระหว่างอุปกรณ์ผ่านบัส I2C นั้นไม่ได้สลับซับซ้อน โดยหลักการของฝั่งอุปกรณ์ คือ กำหนดหมายเลขอุปกรณ์ สร้างฟังก์ชันตอบสนองการถูกร้องขอข้อมูล และสร้างฟังก์ชันการตอบสนองเมื่อรับคำสั่ง/ข้อมูล ส่วนฝั่งตัวควบคุมมีหน้าที่เขียนและอ่านข้อมูลโดยระบุตำแหน่งของอุปกรณ์พร้อมกับคำสั่ง หรือตัวแปรนำเข้าข้อมูลจากบัส I2C ซึ่งทางทีม JarutEx คาดหวังว่า เมื่อผู้อ่านนำไปทดลองตามบทความนี้จะสร้างทักษะและความเข้าใจในการเขียนโปรแกรมเพื่อสื่อสารระหว่างอุปกรณ์ผ่านทางบัส I2C และสุดท้ายขอให้มีความสุขและสนุกกับการเขียนโปรแกรมและสร้างสิ่งใหม่ ๆ ครับ
(C) 2020, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2020/09/26