วันพฤหัสบดีที่ 30 พฤศจิกายน พ.ศ. 2560

การเขียนโปรแกรมเบื้องต้นกับ Arduino (การส่งข้อมูลแบบ I2C)

ไม่มีความคิดเห็น

การสื่อสารข้อมูลระหว่างบอร์ด Arduino ด้วยบัส I2C

บทความนี้กล่าวถึง การทดลองใช้บอร์ด Arduino (ATmega328P, 5V/16MHz) จำนวน 2 ชุด เพื่อสื่อสารข้อมูลกัน ด้วยระบบบัส I2C โดยโปรแกรมให้ Arduino ทำหน้าที่เป็นอุปกรณ์ที่เรียกว่า I2C Master และ I2C Slave อย่างละชุด และในการเขียนโปรแกรม ได้ใช้คำสั่งจากไลบรารี่ที่ชื่อว่า Wire ของ Arduino

การสื่อสารข้อมูลด้วยระบบบัส I2C

โดยทั่วไป มักพบเห็นได้ว่า มีการนำบอร์ดไมโครคอนโทรลเลอร์ อย่างเช่น Arduino มาเชื่อมต่อกับอุปกรณ์อื่น เช่น โมดูลเซนเซอร์หลากหลายประเภท โดยใช้วิธีการสื่อสารข้อมูลตามรูปแบบของบัส I2C โดยฝ่ายหนึ่งจะทำหน้าที่ตามบทบาทที่เรียกว่า I2C Master ซึ่งโดยส่วนใหญ่ ก็จะเป็นไมโครคอนโทรลเลอร์ และอีกฝ่ายหนึ่งจะเป็น I2C Slave ซึ่งอาจมีได้หลายชุด
การสื่อสารผ่านบัส I2C เป็นการสื่อสารแบบ Synchronous & Serial (หมายถึง การส่งข้อมูลทีละบิต และใช้สัญญาณ Clock ในการกำหนดจังหวะการส่งข้อมูล) ข้อดีของการสื่อสารข้อมูลแบบบัส I2C คือ ใช้สายสัญญาณเพียง 2 เส้น คือ SCL (สายสัญญาณ Serial Clock) และ SDA (สายสัญญาณข้อมูล Serial Data) และเป็นสัญญาณแบบ 2 ทิศทาง (Bidirectional) มีวงจรภายในสำหรับ I/O แบบ Open-Drain/Open-Collector (เวลาใช้งานต้องมีตัวต้านทานแบบ Pull-up Resistors ต่ออยู่ด้วย)
บัส I2C สามารถพ่วงอุปกรณ์ได้หลายอุปกรณ์ แต่ละอุปกรณ์จะมีหมายเลขที่อยู่ (Device Address) ที่ต้องไม่ซ้ำกัน โดยทั่วไปจะใช้หมายเลขที่อยู่ขนาด 7 บิต (7-bit Device Address) ซึ่งระบุได้ถึง 128 อุปกรณ์ หรือถ้ามีมากกว่านั้น จะเป็น 10 บิต (10-bit Device Address)
อุปกรณ์ที่ทำหน้าที่เป็น I2C Master จะเป็นฝ่ายเริ่มการสื่อสารข้อมูล และสร้างสัญญาณ SCL มาควบคุมจังหวะ มีอัตราการส่งข้อมูลอยู่ที่ 100kHz และ 400kHz (บางกรณี ได้สูงกว่า 1MHz) เมื่อไม่มีการสื่อสารใดๆ สถานะลอจิกของ SCL และ SDA จะเป็น 1 หรือ HIGH เมื่อบัส I2C เริ่มต้นสื่อสาร อุปกรณ์ I2C Master จะส่งบิต Start (หรือเรียกว่า Start Condition) ตามด้วยการส่งไบต์ควบคุม (Control Byte) ออกไปก่อน ซึ่งจะเป็นการระบุหมายเลขของอุปกรณ์ Slave ที่อุปกรณ์ Master ต้องการจะสื่อสารด้วย และในไบต์ดังกล่าวจะมีบิตที่เรียกว่า Read/Write (R/W) Bit สำหรับระบุว่า จะเป็นการเขียนหรืออ่านข้อมูลต่อจากนั้น ถ้าเป็นบิตเขียน (R/W Bit = 0) อุปกรณ์ Master จะส่งข้อมูลไบต์ไปยังอุปกรณ์ Slave เท่านั้น แต่ถ้าเป็นบิตอ่าน (R/W Bit = 1) ต่อไปจะเป็นการรับข้อมูลไบต์จากอุปกรณ์ Slave เพียงอย่างเดียว   นอกจากนั้นในการรับส่งข้อมูลแต่ละไบต์ ฝ่ายรับจะต้องทำการส่งบิตที่เรียกว่า ACK (Acknowledge) Bit ซึ่งจะต้องเป็นลอจิก 0 (ดึงสัญญาณ SDA ลง GND) เมื่อ SCL เป็น 1 เพื่อแจ้งให้ฝ่ายส่งทราบว่า ได้รับข้อมูลไบต์แล้วและพร้อมจะทำงานต่อไป ถ้าจบการสื่อสาร ก็จะต้องส่งบิต Stop (หรือเรียกว่า Stop Condition)
การเขียนโปรแกรมสำหรับ Arduino เพื่อใช้สื่อสารข้อมูลผ่านบัส I2C ก็ทำได้ไม่ยาก เพราะสามารถเรียกใช้คำสั่งจากไลบรารี่ของ Arduino (ใช้ซอฟต์แวร์ตามเวอร์ชัน 1.0.x) ที่ชื่อว่า Wire และสามารถใช้งานได้ทั้งกรณี I2C Master หรือ I2C Slave ความเร็วในการรับส่งข้อมูลจะอยู่ที่ 100kHz (default) และมีการใช้งานตัวต้านทาน pull-up ที่อยู่ภายในชิปไมโครคอนโทรลเลอร์ของ Arduino อีกด้วย

Arduino Sketch

โค้ดตัวอย่างต่อไปนี้ ใช้สำหรับสาธิตการทำงานของบอร์ด Arduino (ใช้ชิป ATmega328P, 5V/16MHz) ซึ่งแบ่งเป็นสองกรณีคือ ทำงานแบบ I2C Master และ I2C Slave โดยต้องกำหนด #define MASTER หรือ #define SLAVE ในโค้ดดังกล่าวก่อนทำขั้นตอน Build   ในตัวอย่างนี้ ได้ใช้ความเร็วที่ 400kHz แทนที่จะเป็น 100kHz นอกจากนั้น ยังปิดการใช้งานตัวต้านทานภายในแบบ Pull-up (Interal pull-up resistors) ของชิป และจะต้องต่อตัวต้านทานเพิ่ม เช่น เลือกใช้ค่าที่ 2.2kΩ
โดยสรุป พฤติกรรมการทำงานของ I2C Master และ I2C Slave มีดังนี้ I2C Master ส่งข้อมูลหนึ่งไบต์ ไปยัง I2C Slave เพื่อเก็บไว้ภายในหน่วยความจำ (ตัวแปร) จากนั้น I2C Master ก็จะทำการร้องขอและอ่านข้อมูลหนึ่งไบต์จาก I2C Slave ซึ่งจะเป็นข้อมูลไบต์ที่ได้รับล่าสุด แล้วเว้นระยะเวลาประมาณ 500 มิลลิวินาที ก่อนทำขั้นตอนซ้ำ โดยเปลี่ยนค่าของข้อมูลไบต์ (เพิ่มค่าทีละหนึ่ง) และในกรณีของ I2C Master จะมีการแสดงข้อความทาง Serial ด้วย
//////////////////////////////////////////////////////////////////////////
// Author: RSP @ ESL (Embedded System Lab), KMUTNB
// Date: 2014-APR-10
// Target Board: Arduino (ATmega328P, 5V, 16MHz)
// Arduino IDE: version 1.0.5
// Description:  
//   This Arduino sketch demonstates how to implement either
//   a master or a slave device for the I2C bus using Arduino boards.
//   Two Arduino boards are required, one for the master and
//   one for the slave.
//   To build the Arduino sketch for the I2C master, MASTER must be 
//   defined in the code. For the I2C slave, SLAVE must be defined.
//   This example uses 0x74 as the I2C slave address. 
//   The speed rate is at 400kHz. Internal pull-up resistors are
//   disabled. External pull-up registers (4.7k or 2.2k) are required.
//
//////////////////////////////////////////////////////////////////////////

#include <Wire.h> // use the Wire library

//-----------------------------------------------------------------

//#define SLAVE

#define I2C_ADDR  (0x74) /* 7-bit I2C address of the slave device */
#define LED_PIN   (13)   // LED pin

#if !defined(SLAVE) && !defined(MASTER)
 #define MASTER
#endif

#if defined(SLAVE) && defined(MASTER)
 #error "Please do not define both MASTER and SLAVE."
#endif

//-----------------------------------------------------------------
#ifdef SLAVE

byte data = 0;

// This function will be called when the slave device receives
// a transmission from a master.
void i2c_recv_event( int numBytes ) {
  // expect only one byte to be read
  data = Wire.read(); // read only byte
}

// This function will be called when a master requests data from this slave device.
void i2c_send_event( ) {
  digitalWrite( LED_PIN, HIGH );
  Wire.write( data ); // send only one byte
  digitalWrite( LED_PIN, LOW );
}

// Uno or 328P:  A4 for SDA and A5 for the SCL line of the I2C bus.
void setup() {
  pinMode( LED_PIN, OUTPUT );
  Serial.begin( 115200 ); // set baudrate
  Wire.begin( I2C_ADDR );  
  TWBR = 12; // use 400kHz instead of 100kHz
  
  // Note: The default Wire library enables the internal pullup resistors. 
  // Disable internal pull-up resistors on SDA and SCL.
  //pinMode( SDA, INPUT );
  //pinMode( SCL, INPUT );
  digitalWrite(SDA, 0);
  digitalWrite(SCL, 0);
  
  // register the handlers for I2C events
  Wire.onReceive( i2c_recv_event );
  Wire.onRequest( i2c_send_event ); 
}

void loop() {
  // empty
}

#endif // SLAVE

//----------------------------------------------------------------
#ifdef MASTER

byte data = 0; 
char sbuf[32];

void i2c_scan() {
  int count = 0;
  Serial.println( "Scanning I2C slave devices..." );
  for( byte addr=0x01; addr <= 0x7f; addr++ ) {
     Wire.beginTransmission( addr ); 
     if ( Wire.endTransmission() == 0 ) {
       sprintf( sbuf, "I2C device found at 0x%02X.", addr );
       Serial.println( sbuf );
       count++;
    }
  }
  if (count > 0 ) {
    sprintf( sbuf, "Found %d I2C devices.", count );
  } else {
    sprintf( sbuf, "No I2C device found." );
  }
  Serial.println( sbuf );
}

void test_write_read( byte value ) {
  sprintf( sbuf, "Master: 0x%02X sent.", data );
  Serial.println( sbuf );

  Wire.beginTransmission( I2C_ADDR );
  Wire.write( value ); // write one byte
  Wire.endTransmission();
  
  delayMicroseconds(10);

  digitalWrite( LED_PIN, HIGH );
  Wire.beginTransmission( I2C_ADDR );
  Wire.requestFrom( (uint16_t)I2C_ADDR, 1 ); // read one byte
  digitalWrite( LED_PIN, LOW );

  if ( Wire.available() == 1 ) {
    byte data = Wire.read();
    sprintf( sbuf, "Master: 0x%02X received.", data );
    Serial.println( sbuf );
  } else {
    Serial.println( "Master: error!" );
  }
  Wire.endTransmission();
}

// Uno or 328P:  A4 for SDA and A5 for the SCL line of the I2C bus.
void setup() {
  Serial.begin( 115200 );
  Wire.begin();  
  TWBR = 12; // use 400kHz instead of 100kHz
  pinMode( LED_PIN, OUTPUT );
  
  // Note: The default Wire library enables the internal pullup resistors.
  // Disable internal pull-up resistors on SDA and SCL
  //pinMode( SDA, INPUT );
  //pinMode( SCL, INPUT );
  digitalWrite(SDA, 0);
  digitalWrite(SCL, 0);
  delay(100);
  //i2c_scan(); // perform I2C device scanning
  delay(2000);
}

void loop() {
  test_write_read( data );
  data++;
  delay(500);
}

#endif // MASTER

//////////////////////////////////////////////////////////////////////////

Wiring Diagram
   Master        Slave  
   A4 (SDA) ---  A4 (SDA), 2.2k or 4.7k pull-up to VCC
   A5 (SCL) ---  A5 (SCL), 2.2k or 4.7k pull-up to VCC
   GND      ---  GND 



รูปแสดงการต่อวงจรโดยใช้บอร์ด Arduino จำนวน 2 ชุด เชื่อมต่อกับแบบบัส I2C
มีการต่อตัวต้านทาน Pull-up ที่สัญญาณ SCL และ SDA ไปยัง VCC


รูปแสดงข้อความที่ปรากฏใน Serial Monitor ที่ได้รับจากบอร์ด Arduino ที่เป็น I2C Master


รูปแสดงคลื่นสัญญาณ SCL (1) และ SDA (2) เมื่อวัดด้วยออสซิลโลสโคป
(400kHz, pull-up 4.7kΩ to 5V)


รูปแสดงคลื่นสัญญาณ SCL (1) และ SDA (2) เมื่อวัดด้วยออสซิลโลสโคป
(400kHz, pull-up 2.2kΩ to 3.3V)


รูปแสดงคลื่นสัญญาณ SCL (1) และ LED (2) เมื่อวัดด้วยออสซิลโลสโคป
ช่วง HIGH ของ LED (Master) หมายถึง ช่วงที่ I2C Master ร้องขอและอ่านข้อมูลหนึ่งไบต์จาก I2C Slave


รูปแสดงคลื่นสัญญาณ SCL (1) และ LED (2) เมื่อวัดด้วยออสซิลโลสโคป
ช่วง HIGH ของ LED (Slave) หมายถึง ช่วงที่ I2C Slave ส่งข้อมูลไบต์ไปยัง I2C Master
ซึ่งตรงกับช่วงเวลาการทำงานของฟังก์ชัน i2c_send_event( )


แหล่งข้อมูลอ้างอิงและศึกษาเพิ่มเติม (Reference)

ไม่มีความคิดเห็น :

แสดงความคิดเห็น