Giao tiếp I2C với nhiều module

I. I2C LÀ GÌ?

Tất nhiên là đã có bài viết giới thiệu về I2C trên Arduino.vn Tuy nhiên bài viết này mình xin nói về 1 hướng khác. Các bạn quan tâm có thể xem lại những bài viết về I2C của các tác giả khác nhé!

Đầu năm 1980 Phillips đã phát triển một chuẩn giao tiếp nối tiếp 2 dây được gọi là I2C. I2C là tên viết tắt của cụm từ Inter-Intergrated Circuit. Đây là đường Bus giao tiếp giữa các IC với nhau. I2C mặc dù được phát triển bới Philips, nhưng nó đã được rất nhiều nhà sản xuất IC trên thế giới sử dụng. I2C trở thành một chuẩn công nghiệp cho các giao tiếp điều khiển, có thể kể ra đây một vài tên tuổi ngoài Philips như: Texas Intrument(TI), MaximDallas, analog Device, National Semiconductor ... Bus I2C được sử dụng làm bus giao tiếp ngoại vi cho rất nhiều loại IC khác nhau như các loại Vi điều khiển 8051, PIC, AVR, ARM... chip nhớ như: RAM tĩnh (Static Ram), EEPROM, bộ chuyển đổi tương tự số (ADC), số tương tự(DAC), IC điểu khiển LCD, LED...

Cấu tạo và nguyên lý hoạt động

I2C sử dụng hai đường truyền tín hiệu:

  • Một đường xung nhịp đồng hồ(SCL) chỉ do Master phát đi ( thông thường ở 100kHz và 400kHz. Mức cao nhất là 1Mhz và 3.4MHz).
  • Một đường dữ liệu(SDA) theo 2 hướng.

Có rất nhiều thiết bị có thể cùng được kết nối vào một bus I2C, tuy nhiên sẽ không xảy ra chuyện nhầm lẫn giữa các thiết bị, bởi mỗi thiết bị sẽ được nhận ra bởỉ một địa chỉ duy nhất với một quan hệ chủ/tớ tồn tại trong suốt thời gian kết nối. Mỗi thiết bị có thể hoạt động như là thiết bị nhận hoặc truyền dữ liệu hay có thể vừa truyền vừa nhận. Hoạt động truyền hay nhận còn tùy thuộc vào việc thiết bị đó là chủ (master) hãy tớ (slave).

Một thiết bị hay một IC khi kết nối với bus I2C, ngoài một địa chỉ (duy nhất) để phân biệt, nó còn được cấu hình là thiết bị chủ hay tớ.Tại sao lại có sự phân biệt này ? Đó là vì trên một bus I2C thì quyền điều khiển thuộc về thiết bị chủ. Thiết bị chủ nắm vai trò tạo xung đồng hồ cho toàn hệ thống, khi giữa hai thiết bị chủ-tớ giao tiếp thì thiết bị chủ có nhiệm vụ tạo xung đồng hồ và quản lý địa chỉ của thiết bị tớ trong suốt quá trình giao tiếp. Thiết bị chủ giữ vai trò chủ động, còn thiết bị tớ giữ vai trò bị động trong việc giao tiếp.

Về lý thuyết lẫn thực tế I²C sử dụng 7 bit để định địa chỉ, do đó trên một bus có thể có tới 2^7 địa chỉ tương ứng với 128 thiết bị có thể kết nối, nhưng chỉ có 112 , 16 địa chỉ còn lại được sử dụng vào mục đích riêng. Bit còn lại quy định việc đọc hay ghi dữ liệu (1 là write, 0 là read)

Điểm mạnh của I²C chính là hiệu suất và sự đơn giản của nó: một khối điều khiển trung tâm có thể điều khiển cả một mạng thiết bị mà chỉ cần hai lối ra điều khiển.

Ngoài ra I2C còn có chế độ 10bit địa chỉ tương đương với 1024 địa chỉ, tương tự như 7 bit, chỉ có 1008 thiết bị có thể kết nối, còn lại 16 địa chỉ sẽ dùng để sử dụng mục đích riêng (Mình chưa rõ lắm)

Vậy, làm thế nào để nó có thể giao tiếp với nhiều thiết bị?

II. LÀM THẾ NÀO?

Vâng, như mình đã nói ở trên, Mỗi thiết bị có 1 địa chỉ được cài sẵn hoặc 1 địa chỉ thiết bị duy nhất để thiết bị chủ (Master) có thể giao tiếp. 2 chân SDA VÀ SCL là 2 chân của giao tiếp I2C, trong đó chân SCL là chân Clock, có tác dụng đồng bộ hóa việc truyền dữ liệu giữa các thiết bị, và việc tạo ra xung clock đó là do thiết bị chủ (Master). Chân còn lại là chân SDA là chân truyền dữ liệu (DATA). 2 chân này luôn hoạt động ở chế độ mở, vì vậy để sử dụng được cần phải có trở kéo. tức là nối +5v => trở => I2C bởi các thiết bị trên bus i2c hoạt động ở mức thấp. Giá trị thường được sử dụng cho các điện trở là từ 2K cho tốc độ vào khoảng 400 kbps, và 10K cho tốc độ thấp hơn khoảng 100 kbps.

Hãy tưởng tượng bạn là nhân viên phát bưu phẩm, đến 1 khu phố trên tay có bưu phẩm cần chuyển phát. Tính năng của bạn có thể phát và nhận bưu phẩm để chuyển đi cho cả khu phố, tuy nhiên, yếu tố quan trọng là bạn cần phải có địa chỉ. mỗi 1 thiết bị sẽ có 1 địa chỉ riêng để thiết bị chủ có thể truy cập để lấy dữ liệu 

Hải Đăng PPK

Ví dụ cảm biến gia tốc ADXL345

Có 1 địa chỉ duy nhất cho riêng module và đồng thời bổ xung bên trong có 3 địa chỉ riêng biệt cho các trục X,Y,Z. Nếu chúng ta cần đọc các dữ liệu từ trục X, trước hết chúng ta cần đến địa chỉ chính của Module, sau đó mới đến địa chỉ của trục X. Việc tìm tên các địa chỉ này các bạn cần tìm trong datasheet của linh kiện. Đây là datasheet của cảm biến gia tốc ADXL345 Mở ra đọc tại mục Register map các bạn có thể thấy địa chỉ của trục X là 0x32 và 0x33

0x32 DATAX0  X-Axis Data 0

0x33  DATAX1  X-Axis Data 1

Trong ví dụ dưới đây mình sẽ đưa ra 1 trường hợp về việc kết nối arduino với 2 cảm biến (Cảm biến gia tốc 10 bậc tự do GY - 80 và Cảm biến gia tốc 6 bậc tự do GY521), cả 2 cảm biến đều sử dụng giao tiếp I2C

Cảm biến gia tốc GY-521 

Cảm biến gia tốc GY-80

Dưới đây là cách kết nối 2 module với arduino, các bạn có thể thấy hơi mâu thuẫn so với ban đầu mình nói, tại sao không có điện trở nào kéo ở đây? Thực tế thì bên trong module đã có điện trở kéo, vì thế chúng ta không cần kéo gì nữa cả laugh

Bây giờ để giao tiếp với chúng, ta cần phải biết được địa chỉ của nó là gì, đối với mỗi 1 cảm biến trên 1 module kia sẽ có 1 địa chỉ riêng. Các bạn cần phải tìm chúng trong datasheet của mỗi loại. Ở đây mình đưa ra các địa chỉ của các cảm biến để các bạn tiện theo dõi

Đối với GY-80, có 4 địa chỉ: 0x53  cho cảm biến gia tốc,  0x69 cho 3 trục con quay,  0x1E  3 trục Từ trường và 0x77 cho cảm biến áp lực và nhiệt kế  

Đối với GY-521, chỉ có một địa chỉ và đó là  0x68. Các bạn cũng có thể kiểm tra và xác định được địa chỉ của chúng bằng cách sử dụng bản sketch mẫu I2C Scanner . Nạp code đó vào và nó sẽ tự cho các bạn biết các địa chỉ có trên thiết bị. 

Sau khi đã tìm thấy địa chỉ của các thiết bị, chúng ta cũng cần phải tìm ra địa chỉ của các thanh ghi bên trong của chúng để đọc dữ liệu. Ví dụ, nếu chúng ta muốn đọc dữ liệu cho trục X từ các cảm biến 3 trục gia tốc của GY-80, chúng ta cần phải tìm địa chỉ nơi dữ liệu của trục X được lưu trữ. Bằng cách đọc datasheet của các cảm biến của GY-80 và ở đây là datasheet của cảm biến 3 trục gia tốc ADXL345 mà mình đã nêu ở phần đầu bài viết, chúng ta có thể thấy rằng dữ liệu cho trục X được lưu trữ trong hai thanh ghi, DATAX0 với một địa chỉ 0x32  và DATAX1 với một địa chỉ 0x33.

Bây giờ chúng ta chỉ việc code và hãy nghiên cứu xem code đã làm những gì?

#include <Wire.h>
int ADXLAddress = 0x53; // Địa chỉ của cảm biến gia tốc trong module GY80
#define X_Axis_Register_DATAX0 0x32 // địa chỉ của data0 trục X trong cảm biến gia tốc ADXL345 trong module GY-80
#define X_Axis_Register_DATAX1 0x33 //địa chỉ của data1 trục X trong cảm biến gia tốc ADXL345 trong module GY-80
#define Power_Register 0x2D // thanh ghi điều khiển năng lượng cung cấp
int X0,X1,X_out;
void setup() {
  Wire.begin(); // Khởi tạo thư viện WIRE
  Serial.begin(9600);
  delay(100);
  // kích hoạt tính năng đo lường
  Wire.beginTransmission(ADXLAddress);//bắt đầu việc truyền tải yêu cầu tới các cảm biến
  Wire.write(Power_Register);
  // Cho phép đo
  Wire.write(8);  
  Wire.endTransmission();
}
void loop() {
  Wire.beginTransmission(ADXLAddress); // Bắt đầu truyền đến cảm biếnr 
  //Thu thập dữ liệu từ các thanh ghi
  Wire.write(X_Axis_Register_DATAX0);
  Wire.write(X_Axis_Register_DATAX1);
  
  Wire.endTransmission(); // Kết thúc việc truyền dữ liệu từ 2 thanh ghi
  Wire.requestFrom(ADXLAddress,2); // Yêu cầu truyền 2 byte từ 2 thanh ghi
  
  if(Wire.available()<=2) {  // 
    X0 = Wire.read(); // Đọc dữ liệu từ thanh ghi
    X1 = Wire.read();   
  }
  
  Serial.print("X0= ");
  Serial.print(X0);
  Serial.print("   X1= ");
  Serial.println(X1);
}

 

Việc lấy dữ liệu từ các cảm biến trên module khác cũng tương tự. Như vậy các bạn đã biết cách sử dụng giao tiếp I2C. Và rõ ràng 2 module này có vẻ không được nhiều bạn quan tâm sử dụng lắm bởi giá thành cũng như là về vấn đề các bạn cần ứng dụng đến không nhiều. Vì thế mình xin tiếp tục đưa ra ví dụ thứ 2 mà khá nhiều bạn còn băn khoăn. Đó là việc sử dụng LCD 1602 module i2c và module thời gian thực DS1307 với arduino uno.

 

Trước khi đọc tiếp phần này, mình lại 1 lần nữa mong muốn các bạn đọc datasheet của linh kiện.

Ở đây, đối với LCD1602 dùng module I2C. địa chỉ tùy thuộc vào jump các bạn cắm trên module i2c. Các bạn có thể đọc datasheet của LCD này tại đây  Mình lấy ví dụ địa chỉ OPEN là 0x27 Hoặc nó có thể thay đổi từ 0X20 - 0X27

Với module thời gian thực DS1307 các bạn có thể đọc datasheet tại đây. Địa chỉ của module DS1307 là 0x68. Làm thế nào để biết nó là 0X68 thì các bạn hãy đọc lại phần đầu bài viết, phần I2C SCANNER.. Bên trong DS1307 sẽ có các địa chỉ để đọc các giá trị giờ, phút, giây...

Như vậy để kết nối 2 module cùng dùng i2c các bạn chỉ cần gọi địa chỉ của LCD là 0x27, địa chỉ của DS1307 là 0X68 là có thể sử dụng đồng thời 2 module trên cùng 2 chân SDA, SCL của Arduino

#include <Wire.h>
#include "RTClib.h"
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x20,16,2); // 0x27 là địa chỉ của lcd 16x2
RTC_DS1307 RTC;

void setup () {
   lcd.init(); 
  lcd.backlight(); //đèn nền bật
  // cài đặt số cột và số dòng 
  lcd.begin(16, 2);
  // in logo lên màn hình
  lcd.print("www.arduino.vn");  
  lcd.setCursor(0, 1);
  lcd.print("haidangppk");
  delay (2500);
  lcd.clear();
    
   // Serial.begin(9600);
    Wire.begin();
Wire.beginTransmission(0x68);// địa chỉ của ds1307
Wire.write(0x07); // 
Wire.write(0x10); // 
Wire.endTransmission();

    RTC.begin();
  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
}

void loop () {
   DateTime now = RTC.now();
   lcd.setCursor(6, 0);
   lcd.print(now.hour(), DEC);
   lcd.print(":");
   lcd.print(now.minute(), DEC);
   lcd.print(":");
   lcd.print(now.second(), DEC);
   lcd.print(" "); 
    
   lcd.setCursor(5, 1);
   lcd.print(now.day(), DEC);
   lcd.print("/");
   lcd.print(now.month(), DEC);
   lcd.print("/");
   lcd.print(now.year(), DEC);
   lcd.print(""); 
  
   delay(1000);
}

Bài viết của mình có thể có sai sót, mong các bạn góp ý. Chúc các bạn thành công!

Trần Hải Đăng

lên
34 thành viên đã đánh giá bài viết này hữu ích.
Các dự án được truyền cảm hứng

Select any filter and click on Apply to see results

Các bài viết cùng tác giả

Nghiên cứu về tín hiệu hồng ngoại của Remote điều hòa, ứng dụng trong các dự án nhà thông minh <Phần 2>

Ở bài viết trước mình đã hướng dẫn các bạn cách cấu hình lại thư viện để nhận được tín hiệu điều khiển hồng ngoại có độ dài lớn hơn 100 rawbuff. Tuy nhiên thực tế các biến trong thư viện đang sử dụng dạng 8bit cho rawbuff, điều đó đồng nghĩa với giá trị rawbuff chúng ta có thể tăng tối đa là 255. Mình đã nghiên cứu rất nhiều các bài viết, các câu trả lời trên các diễn đàn quốc tế chuyên về arduino tuy nhiên chưa tìm được câu trả lời thỏa đáng cho các vấn đề mình gặp phải. Việc giải mã tín hiệu là vô cùng khó khăn và phức tạp. Thậm chí mình đã phải bỏ ra 10$ để thanh toán cho 1 phần mềm để phân tích, giải mã tín hiệu hồng ngoại.Đây chính là rào cản lớn đối với các bạn muốn nghiên cứu về tín hiệu hồng ngoại của điều hòa. Bài viết này mình sẽ hướng dẫn các bạn cách nhận giá trị điều khiển hồng ngoại có kích thước tín hiệu lớn. Và đưa ra các vấn đề mà các bạn thường mắc phải khi nghiên cứu tín hiệu hồng ngoại.

lên
22 thành viên đã đánh giá bài viết này hữu ích.

Nghiên cứu về tín hiệu hồng ngoại của Remote điều hòa, ứng dụng trong các dự án nhà thông minh

Tín hiệu hồng ngoại của điều khiển điều hòa là 1 dạng tín hiệu đặc biệt, nó hoàn toàn khác so với tín hiệu hồng ngoại của tivi hay các thiết bị điện gia dụng. Bài viết này mình sẽ giúp các bạn tìm hiểu về tín hiệu hồng ngoại của điều khiển điều hòa, trên cơ sở đó xây dựng dự án Smart Home cho chính mình.

Ví dụ như trước khi về nhà mình sẽ điều khiển điều hòa bật trước với nhiệt độ tùy chọn chẳng hạn.

lên
29 thành viên đã đánh giá bài viết này hữu ích.