Arduino – Giao tiếp I2C

Arduino – Giao tiếp I2C

Ngoài chuẩn truyền thông thông nối tiếp Serial, chúng ta còn có một chuẩn giao tiếp khác giữa các vi điều khiển, IC đó là chuẩn I2C.

Ở chương này, chúng ta sẽ tìm hiểu các nội dung sau:

  • Các khái niệm liên quan tới giao tiếp I2C như: mô hình master/slave, hoạt động cơ bản – truyền, nhận bit của giao thức I2C.

  • Cách xác định địa chỉ của thiết bị trong giao tiếp I2C bằng chương trình, xác định địa chỉ với nhiều thiết bị khác nhau, những vấn đề xảy ra với nhiều thiết bị giống nhau trong truyền, nhận dữ liệu I2C.

  • Sử dụng giao tiếp I2C đơn giản với LCD và OLED.

  • Giao tiếp giữa 2 board Arduino Uno với nhau thông qua chuẩn I2C.

Mô hình Master/slave

Master/slave là một mô hình giao tiếp mà một thiết bị có thể điều khiển ít nhất một thiết bị khác. Mô hình master/slave được dùng để chỉ các mô hình giao tiếp giữa các thiết bị điện tử, cơ khí và máy tính.

Trong mô hình master/slave, một thiết bị được chọn làm master (thiết bị chủ điều khiển các thiết bị khác), các thiết bị còn lại làm vai trò slave (là các thiết bị chịu sự điều khiển của master).

Ví dụ, trong một mô hình ta có một vi điều khiển muốn điều khiển, hoặc đọc dữ liệu từ các cảm biến, relay,…​ thì vi điều khiển đóng vai trò là một master và các cảm biến, relay sẽ đóng vai trò là slave.

master slave
Hình 1. Sơ đồ khối cơ bản của mô hình master/slave

Giao tiếp I2C

Giới thiệu

I2C là một chuẩn giao tiếp theo mô hình master/slave được phát triển vào năm 1982 bởi hãng Philips cho việc giao tiếp ngoại vi giữa các vi điều khiển, IC trong khoảng cách ngắn. Chuẩn giao tiếp I2C được sử dụng rất phổ biến trong các thiết bị điện tử bởi đặc tính dễ thực hiện với giao tiếp giữa một hoặc nhiều thiết bị làm master với một hoặc hoặc nhiều thiết bị làm slave. Các hãng sản xuất IC ngày nay đều hỗ trợ giao tiếp I2C trên các IC có ứng dụng cần giao tiếp với các IC hay các vi điều khiển khác.

Giao tiếp I2C chỉ sử dụng 2 dây cho việc giao tiếp giữa 128 thiết bị với việc định địa chỉ 7 bit và 1024 thiết bị với việc định địa chỉ 10 bit. Khi đó, mỗi thiết bị trong mô hình master/slave sẽ có một địa chỉ cố định và duy nhất và master sẽ lựa chọn thiết bị nào cần giao tiếp.

Hoạt động

I2C sử dụng 2 dây giao tiếp là SCL và SDA. Dây SCL (viết tắt của Serial Clock Data) là dây truyền xung clock phát từ thiết bị master đồng bộ với việc truyền dữ liệu. Dây SDA (Serial Data) là dây truyền dữ liệu.

Mạch vật lý I2C là mạch cực thu hở, do đó để mạng I2C có thể hoạt động được, cần tối thiểu 2 cặp điện trở pull-up như trên hình, thông thường các giá trị điện trở 4k7 hoặc 1k2 được sử dụng, tùy thuộc vào tốc độ truyền và khoảng cách truyền.

i2c network
Hình 2. Mô hình mạng I2C

Truyền nhận bit trong I2C

Các dữ liệu được truyền trong I2C dạng các chuỗi 8 bit. Sau khi bit Start đầu tiên được truyền, 8 bit tiếp theo chứa thông tin về địa chỉ của thiết bị sẽ được truyền tiếp. Sau đó, một bit ACK sẽ được truyền để xác nhận việc truyền bit thành công. Sau khi truyền bit ACK, 8 bit tiếp theo chứa thông tin địa chỉ thanh ghi nội của thiết bị slave sẽ được truyền. Sau khi hoàn tất việc định địa chỉ với 8 bit này, một bit ACK nữa sẽ được truyền để xác nhận. Sau đó, 8 bit Data sẽ được truyền tiếp theo. Cuối cùng, quá trình truyền kết thúc với 1 bit ACK và 1 bit Stop xác nhận việc truyền dữ liệu kết thúc.

i2c trans recei
Hình 3. Các bit truyền, nhận trong giao tiếp I2C.

Để hiểu rõ hơn về quá trình truyền nhận dữ liệu và bit trong I2C, tham khảo: Understanding the I2C Bus, How I2C Communication Works and How To Use It with Arduino.

Sử dụng giao thức I2C

Với các vi điều khiển, IC, cảm biến,…​ có hỗ trợ chuẩn giao tiếp I2C thì sẽ có 2 chân có tên SCL và SDA, tương ứng với 2 dây SCL, SDA cho giao tiếp I2C. Với board Arduino Uno, chân A4 là chân SDA và chân A5 là chân SCL, 2 chân A4, A5 này cũng được nối tới 2 chân SDA, SCL tương ứng ở header sử dụng với OLED của board Arduino Uno. Với các IC và vi điều khiển được sản xuất ngày nay đều có hỗ trợ điện trở nội kéo lên do đó trong việc kết nối dây không cần thêm điện trở nội kéo lên. Với các module hỗ trợ I2C chỉ có 1 tính năng như OLED SSD1306, LCD 1602 để hiển thị, Cảm biến AM2315 để đọc nhiệt độ, độ ẩm của môi trường,…​ thường hoạt động ở chế độ slave và có 4 chân: SDA, SCL, GND và VCC.

oled ssd1306
Hình 4. Hình ảnh module sử dụng giao tiếp I2C (OLED-SSD1306)

Viết chương trình cho I2C

Chương trình cho giao tiếp I2C giữa cảm biến và vi điều khiển thường cần thư viện thích hợp cho từng loại vi điều khiển. Các cảm biến, thiết bị,…​ sẽ đọc dữ liệu từ môi trường và gởi về cho vi điều khiển cũng như vi điều khiển sẽ điều khiển các thiết bị thông qua giao tiếp I2C. Nhiều loại cảm biến, module cần dùng thêm thư viện riêng cho việc đọc, hiển thị dữ liệu,…​

Một số chương trình có yêu cầu phải khai báo địa chỉ của thiết bị slave trong giao tiếp I2C thì việc đọc, hiển thị dữ liệu mới thực hiện được. Khi đó, nảy sinh vấn đề phải xác định được địa chỉ của thiết bị trong giao tiếp I2C. Địa chỉ này thường được cung cấp trong datasheet của thiết bị, hoặc có thể xác định thông qua các chương trình đọc địa chỉ.

Để sử dụng thư viện I2C trong Arduino, người dùng cần gọi thư viện <Wire.h> tích hợp sẵn trên trình biên dịch Arduino IDE.

Xác định địa chỉ của thiết bị trong giao tiếp I2C

Như đã trình bày ở phần giới thiệu, mỗi thiết bị trong giao tiếp I2C sẽ có một địa chỉ riêng. Các địa chỉ này sẽ được nhà sản xuất cung cấp trong datasheet của thiết bị. Ở phần này, chúng ta sẽ tìm hiểu về chương trình xác định địa chỉ của một thiết bị trong giao tiếp I2C. Một số linh kiện có hỗ trợ I2C nhưng người dùng khó tìm được datasheet chính thức từ nhà sản xuất thì việc dùng một chương trình ngắn để định địa chỉ I2C là một việc làm đơn giản và cần thiết.

Yêu cầu

Xác định địa chỉ các thiết bị trong giao tiếp I2C và phát hiện số thiết bị có giao tiếp I2C trong kết nối hiện tại.

Linh kiện cần dùng

  • Board Arduino Uno

  • Module LCD 20×4

Source code

#include <Wire.h>

void setup()
{
  Wire.begin();         // Khởi tạo thư viện Wire
  Serial.begin(9600);
  while (!Serial);      // Đợi Serial Monitor hiển thị
  Serial.println("I2C Scanner");
}

void loop()
{
  byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  //  Định địa chỉ I2C có 7 bit ứng với 128 thiết bị, việc quét sẽ tiến hành
  //  từ 1 tới 127 (1 thiết bị làm master nên chỉ còn 127)
  for (address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");
      nDevices++;
      Serial.println("Device no. " + String(nDevices));
    } else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }

  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("number of devices " + String(nDevices));

  delay(5000);  // Delay 5s cho quá trình scan tiếp theo
}

Với chương trình này, đầu tiên ta kiểm tra khi chỉ có 1 master – 1 slave với master là board Arduino Uno và slave là Module LCD 20×4 (các kiến thức cơ bản về hoạt động của LCD bạn đọc tham khảo ở nội dung phần tiếp theo).

Bảng 1. Bảng kết nối board Arduino Uno với Module LCD 20×4
Module LCD20x4 Arduino Uno

VCC

5V

GND

GND

SDA

A4

SCL

A5

modulelcd2004
Hình 5. Hình ảnh kết nối LCD 20X04 với board Arduino Uno

Sau khi kết nối thành công, ta nạp chương trình trên vào board Arduino Uno và kiểm tra kết quả.

I2C Scan
Hình 6. Kết quả hiển thị địa chỉ I2C của LCD 20X04

Kết quả:

  • Số thiết bị kết nối vào là 1 thiết bị.

  • Slave có địa chỉ I2C là 0x3F, ta sẽ dùng địa chỉ này để khai báo trong những chương trình giao tiếp I2C với module này.

Tiếp theo, ta xét việc định địa chỉ với chương trình trên khi trong giao tiếp I2C có ít nhất 2 thiết bị làm slave. Kết nối board Arduino Uno với LCD 20×04 và OLED SSD1306.

Bảng 2. Bảng kết nối board Arduino Uno với OLED
OLED SSD1306 Arduino Uno

VCC

5V

GND

GND

SDA

A4

SCL

A5

oled
Hình 7. Hình ảnh kết nối OLED với board Arduino Uno

Kết nối song song các chân SCL, SDA, VCC, GND của các thiết bị slave và thiết bị master với nhau. Sau khi kết nối thành công, nạp chương trình trên vào cho board Arduino Uno.

Kết quả

i2c scan 2dev
Hình 8. Kết quả hiển thị địa chỉ I2C của LCD 20X04 và OLED SSD1306

Bạn đọc có thể sẽ đặt câu hỏi rằng: “Vậy việc định địa chỉ sẽ như thế nào khi trong giao tiếp I2C có 2 thiết bị giống nhau?”

Để kiểm chứng việc này, ta sẽ kết nối 2 thiết bị giống hệt nhau trong giao tiếp I2C và nạp chương trình trên. Kết quả nhận được sẽ là chương trình chỉ phát hiện được có 1 thiết bị kết nối vào, và trả về địa chỉ I2C duy nhất của thiết bị đó.

Một số module có các option để lựa chọn địa chỉ I2C thông qua việc setup phần cứng, một số module cho phép người dùng có thể định lại địa chỉ I2C bằng phần mềm, nếu không chúng ta có thể sử dụng các mạch I2C Multiplexer khi muốn kết nối nhiều module có cùng địa chỉ I2C.

Giới thiệu về LCD và OLED

Giới thiệu về LCD

LCD là chữ viết tăt của Liquid Crystal Display, tiếng Việt có nghĩa là màn hình tinh thể lỏng, đây là loại thiết bị để hiển thị nội dung, cấu tạo bởi các tế bào (cũng là các điểm ảnh) chứa các tinh thể lỏng (liquid crystal) có khả năng thay đổi tính phân cực của ánh sáng và do đó thay đổi cường độ ánh sáng truyền qua khi kết hợp với các kính lọc phân cực. LCD có ưu điểm là phẳng, cho hình ảnh sáng, chân thật và tiết kiệm năng lượng.

lcd 2004
Hình 9. Màn hình LCD 20×04

Các hãng sản xuất linh kiện ngày nay sản xuất nhiều module LCD hỗ trợ cho việc tương tác với các vi điều khiển mà phổ biến nhất là 2 module LCD text 16×02 và LCD text 20×04. Các thông số 16×02 và 20×04 là số hàng và số cột tương ứng của các module, ví dụ với 16×02 cho biết module có 16 hàng và 2 cột, 20×04 là 20 hàng và 4 cột.

Như ở hình vẽ, các module có 16 chân để kết nối với chức năng mỗi chân:

  • VSS: Tương đương với GND – cực âm.

  • VDD: Tương đương với VCC – cực dương (5V).

  • Constrast Voltage (Vo): Điều khiển độ sáng màn hình.

  • Register Select (RS): Lựa chọn thanh ghi (RS=0 chọn thanh ghi lệnh, RS=1 chọn thanh ghi dữ liệu).

  • Read/Write (R/W): R/W=0 ghi dữ liệu , R/W=1 đọc dữ liệu.

  • Enable pin: Cho phép ghi vào LCD.

  • D0 - D7: 8 chân nhận dữ liệu.

  • Backlight (Backlight Anode (+) và Backlight Cathode (-)): Tắt bật đèn màn hình LCD.

Để khắc phục nhược điểm đấu nối nhiều dây với module LCD thông thường, các nhà sản xuất đã tích hợp thêm IC LCM1602 hỗ trợ giao tiếp I2C vào module LCD, việc đấu nối cũng như nạp chương trình từ đây sẽ trở nên đơn giản hơn.

Bảng 3. Bảng kết nối Arduino Uno với IC LCM1602
LCM1602 Arduino Uno

VCC

5V

GND

GND

SDA

A4

SCL

A5

modulelcd2004
Hình 10. Hình ảnh kết nối module LCD2004 với board Arduino Uno

Chúng ta sẽ viết chương trình hiển thị ký tự lên màn hình LCD. Với module LCD có module I2C kèm theo, chúng ta cần thêm thư viện LiquidCrystal_I2CWire.h.

Link download thư viện : Liquid Crystal I2C. Add thư viện sau khi download vào chương trình.

Yêu cầu

Chương trình hiển thị dòng chữ “UniCloud Vietnam” và “Hello World” lên LCD trên 2 hàng

Source code

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x3F, 20, 4); //  Khởi tạo lcd 20x04 với 0x3F là địa chỉ của LCD

void setup()
{
  lcd.begin();                      // Khởi tạo LCD
  lcd.backlight();                  // Mở đèn nền của LCD
  lcd.setCursor(1,0);               // Lệnh setCursor() tương tự như với thư viện LiquidCrystal
  lcd.print("UniCloud Vietnam");
  lcd.setCursor(3,1);
  lcd.print("Hello, world!");
}

void loop()
{
  //  Không làm gì cả
}
Giới thiệu về OLED

OLED là chữ viết tắt của Organic Light Emitting Diode), là loại màn hình hiển thị bao gồm một lớp vật liệu hữu cơ với thành phần chính là carbon nằm giữa hai điện cực anode và cathode, nó sẽ tự động phát sáng mỗi khi có dòng điện chạy qua. OLED sử dụng diode phát quang hữu cơ, chính vì thế OLED không cần tới đèn nền chiếu sáng nên có kích thước nhỏ gọn cũng như tiết kiệm điện hơn so với các loại LCD, độ sáng của OLED cũng tương đối tốt ở môi trường sáng tự nhiên.

OLED SSD1306
oled ssd1306
Hình 11. Hình ảnh màn hình OLED SSD1306

OLED SSD1306 là loại OLED có màn hình loại nhỏ, kích thước tầm 0.96 inch cho tới 1.25 inch. OLED SSD1306 hỗ trợ chuẩn giao tiếp I2C được sử dụng khá rộng rãi trong các sản phẩm điện tử. Tấm nền của OLED được điều khiển bằng chip driver SSD1306. Về cơ bản, để OLED có thể hiển thị được các thông tin mong muốn thì cần có thư viện hỗ trợ, cũng giống như khi làm việc với LCD. Tùy vào mỗi loại vi điều khiển với kiến trúc phần cứng khác nhau mà sẽ có những thư viện OLED SSD1306 khác nhau hỗ trợ, ví dụ như với các board dùng chip ESP8266 có thể sử dụng thư viện: ESP8266 OLED SSD1306, với board Arduino Uno (dùng chip ATmega328), thư viện OLED hỗ trợ được nhiều người sử dụng là Adafruit SSD1306.

Để sử dụng được hoàn chỉnh tất cả các tính năng thư viện này bao gồm cả những tính năng đồ họa, người dùng cần cài đặt thêm thư viện: Adafruit GFX Library (thư viện hỗ trợ thêm các tính năng đồ họa).

Viết chương trình hiển thị thời gian lên màn hình OLED

Yêu cầu

Chương trình hiển thị thời gian từ lúc nạp chương trình vào board Arduino Uno.

Đấu nối

Bảng 4. Kết nối Arduino Uno với OLED
OLED Arduino Uno

VCC

5V

GND

GND

SDA

A4

SCL

A5

oled
Hình 12. Hình ảnh kết nối OLED SSD1306 với board Arduino Uno

Source code

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4
#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH  16

#if (SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

Adafruit_SSD1306 display(OLED_RESET);
int time_run = 0;

void setup()
{
  Serial.begin(9600);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.display();
  delay(2000);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("Hello, world!");
  display.display();
  delay(2000);
  display.clearDisplay();
}
void loop()
{
  int hour_run, min_run, sec_run;
  delay(1000);
  time_run ++;
  hour_run = time_run / 3600;
  min_run = (time_run % 3600) / 60;
  sec_run = time_run % 60;
  display.clearDisplay();
  display.setCursor(0, 0);
  display.println(String(hour_run) + ":" + String(min_run) + ":" + String(sec_run));
  display.display();
}

Ngoài ra, chúng ta cũng có thể tự tìm hiểu thêm các tính năng đồ họa của OLED với thư viện Adafruit SSD1306 với các chương trình có sẵn trong phần Example của thư viện.

Giao tiếp giữa 2 board Arduino Uno

Có nhiều cách để giao tiếp giữa các vi điều khiển với nhau như truyển nhận qua Serial như đã đề cập ở bài hướng dẫn trước. Phần này, chúng ta sẽ tìm hiểu cách giao tiếp giữa 2 board Arduino Uno thông qua chuẩn giao tiếp I2C.

Yêu cầu*

Board master điều khiển bật tắt LED trên thiết bị slave.

Kết nối

Đầu tiên ta thực hiện việc kết nối giữa 2 board Arduino Uno.

Bảng 5. Kết nối giữa Arduino
Master Slave

5V

5V

GND

GND

A4

A4

A5

A5

duo arduino i2c
Hình 13. Hình ảnh kết nối gĩưa 2 board Arduino Uno

Ở đây, để thuận tiện ta có thể lấy ngõ ra 5V trên master để cấp nguồn cho slave, người dùng cũng có thể cấp nguồn riêng cho thiết bị slave trong các ứng dụng thực tế tùy theo yêu cầu sử dụng.

Nạp các chương trình cho master và slave.

Source code cho master

//  Master
#include <Wire.h>
void setup()
{
  Serial.begin(9600); //  Khởi tạo giao tiếp I2C
  Wire.begin();       //  Khởi tạo truyền nhận dữ liệu I2C
}

void loop()
{
  // Bắt đầu quá trình truyền dữ liệu trên Serial Monitor
  while (Serial.available()) {
    char c = Serial.read();       // Đọc dữ liệu từ serial nếu có và lưu vào biến c
    if (c == 'H') {
      Wire.beginTransmission(3);  //  Bắt đầu truyền đến slave với address la 3
      Wire.write('H');            //  Truyền chữ H đến slave
      Wire.endTransmission();     //  Kết thúc quá trình truyền
    } else if (c == 'L') {
      Wire.beginTransmission(3);
      Wire.write('L');
      Wire.endTransmission();
    }
  }
}

Source code cho slave

//  Slave
#include <Wire.h>
#define pinLed  13               //  Định nghĩa chân LED
void setup()
{
  Wire.begin(3);                //  Khởi tạo giao tiếp I2C với địa chỉ của slave là 3
  Wire.onReceive(receiveEvent); //  Đăng kí hàm receiveEvent sẽ được gọi khi nhận được dữ liệu
  pinMode(pinLed,OUTPUT);
  digitalWrite(pinLed,LOW);
}

void loop()
{
  //  Không làm gì cả
}
void receiveEvent()
{
  while(Wire.available()) {
    char c = Wire.read();       //  Lưu dữ liệu nhận được từ master vào biến c nếu có
    if(c == 'H')                //  So sánh dữ liệu nhận được và điều khiển LED
      digitalWrite(pinLed,HIGH);
    else if(c == 'L')
      digitalWrite(pinLed,LOW);
  }
}

Giải thích source code

I2C sử dụng việc định địa chỉ 7 bit tương ứng với 128 thiết bị kết nối. Trong Arduino, ta có thể định địa chỉ cho slave bởi hàm Wire.begin(địa chỉ slave), khi đó, slave được khởi tạo một địa chỉ với địa chỉ nằm trong khoảng từ 1 tới 127.
Master sẽ truyền dữ liệu tới slave qua câu lệnh Wire.beginTransmission(địa chỉ slave). Master thì không cần truyền địa chỉ.

Kết quả

Sau khi nạp chương trình cho master và slave thành công, ta mở cửa số Serial Monitor ở board Arduino Uno master lên và gõ vào dòng Send kí tự H hoặc L tương ứng với các lệnh để bật và tắt LED trên board Arduino Uno slave.

send data h
Hình 14. Hình ảnh gởi dữ liệu trên Serial Monitor

Tổng kết

Qua phần này, chúng ta đã tìm hiểu được những khái niệm cơ bản về giao tiếp I2C, các kết nối giữa vi điều và các IC sử dụng giao thức này, hiểu được cách xác định địa chỉ và tìm hiểu một số chương trình cơ bản dùng giao thức này.
Bạn đọc có thể tham khảo các ứng dụng mở rộng của giao thức này với các module cảm biến gia tốc, thời gian thực, các module phối hợp chuẩn I2C và SPI,…​ Tùy vào những ứng dụng cụ thể mà người dùng sẽ phải lựa chọn những IC thích hợp dùng giao tiếp I2C.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *