[TH] How to make “ESP8266” read and write to “Arduino UNO Maker” via the I2C bus?

(วิธีการทำให้ “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

ภาพที่ 1a ชุดทดลอง ESP8266+Arduino Uno Maker ของ JarutEx
ภาพที่ 1b ชุดทดลอง ESP8266+Arduino Uno Maker ของ JarutEx

อุปกรณ์ทดลอง

  1. บอร์ด ESP8266 (ในบทความนี้ใช้ WeMos D1 mini)
  2. บอร์ด Arduino Uno (ในบทความนี้ใช้บอร์ด Arduino Uno Maker)
  3. LED x 8 (บนบอร์ด Arduino Uno Maker มีมาให้แล้ว)
  4. SW x 8 (เลือกใช้ ET-TEST 10P/INP)
ภาพที่ 2 บอร์ด ESP8266 WeMos D1 mini
ภาพที่ 3 บอร์ด Arduino Uno Maker
ภาพที่ 4 บอร์ด SWx8

การเชื่อมต่อ

  1. การเชื่อมต่อระหว่าง ESP8266 กับ Arduino Uno Maker
    D1 (GPIO5) –> [SDA] –> A4
    D2 (GPIO4) –> [SCL] –> A5
  1. การเชื่อมต่อระหว่าง SWx8 กับ Arduino Uno Maker
    D2 –> SW1
    D3 –> SW2
    D4 –> SW3
    D5 –> SW4
    D6 –> SW5
    D7 –> SW6
    D8 –> SW7
    D9 –> SW8
    5V –> VCC
    GND –> GND
ภาพที่ 5 การเชื่อมต่อ SWx8 กับ Arduino Uno Maker

การทำงานของET-TEST 10P/INP มีการใช้ R-Pull up ทำให้สถานะของขานำเข้าสัญญาณเมื่อไม่มีการกดสวิตช์มีค่าเป็น 1 และเมื่อกดสวิตช์ทำให้มีค่าเป็น 0

  1. วงจร LED ของบอร์ด Arduino Uno Maker เป็นดังภาพที่ 6 ซึ่งหลอดติดเมื่อส่งค่า 1 ไปที่ขา D2 ถึง D13 และหลอดดับเมื่อส่งค่า 0 ไปยังขาดังกล่าว
ภาพที่ 6 วงจร LED บนบอร์ด Arduino Uno Maker


โปรแกรมสำหรับบอร์ด 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