Trong khi một Arduino duy nhất có thể hoàn thành nhiều tác vụ, một số dự án có thể yêu cầu sử dụng nhiều hơn một bo mạch để xử lý các chức năng khác nhau. Vì vậy, để cho phép truyền dữ liệu giữa hai microcontroller, một giao thức giao tiếp như CAN, SPI, I2C hoặc UART phải được thiết lập.
Hướng dẫn này sẽ đề cập đến những điều cơ bản về cách thức hoạt động của I2C, các kết nối phần cứng và triển khai phần mềm cần thiết để thiết lập hai bo mạch Arduino làm master và slave I2C.
I2C là gì?
Inter-Integrated Circuit (I2C) là một giao thức giao tiếp được sử dụng rộng rãi trong các hệ thống nhúng và microcontroller để cho phép truyền dữ liệu giữa những thiết bị điện tử. Không giống như SPI (Serial Peripheral Interface), I2C cho phép bạn kết nối nhiều thiết bị master với một bus có một hoặc nhiều thiết bị slave. Nó lần đầu tiên được sử dụng bởi Philips và còn được gọi là giao thức giao tiếp Two Wire Interface (TWI).
Giao tiếp I2C hoạt động như thế nào?
I2C sử dụng hai đường truyền hai chiều: Serial Data (SDA) và Serial Clock (SCL) để truyền dữ liệu và đồng bộ giao tiếp giữa các thiết bị. Mỗi thiết bị được kết nối với bus I2C có một địa chỉ duy nhất xác định nó trong quá trình giao tiếp. Giao thức I2C cho phép nhiều thiết bị chia sẻ cùng một bus và mỗi thiết bị có thể đóng vai trò là master hoặc slave.
Giao tiếp được bắt đầu bởi thiết bị master và việc định địa chỉ không chính xác của thiết bị slave có thể gây ra lỗi khi truyền. Một ưu điểm chính của giao tiếp I2C đáng chú ý là tính linh hoạt mà nó mang lại khi quản lý năng lượng. Các thiết bị hoạt động ở những mức điện áp khác nhau vẫn có thể giao tiếp hiệu quả với sự trợ giúp của bộ chuyển đổi điện áp. Điều này có nghĩa là các thiết bị hoạt động ở mức 3,3V cần có bộ chuyển đổi điện áp để kết nối với bus I2C 5V.
Thư viện Wire
Thư viện Wire là thư viện Arduino tích hợp cung cấp các chức năng để giao tiếp qua I2C. Nó sử dụng hai chân - SDA và SCL - trên bo mạch Arduino để giao tiếp I2C.
Chân I2C trên Arduino Uno:
Chân Arduino Nano I2C:
Để sử dụng thư viện, bạn phải đưa file header Wire.h vào đầu Arduino sketch của mình.
#include <Wire.h>
Thư viện Wire cung cấp các chức năng để bắt đầu giao tiếp với thiết bị I2C, gửi và nhận dữ liệu. Một số hàm quan trọng bạn nên biết bao gồm:
- Wire.begin() được sử dụng để tham gia bus I2C và bắt đầu giao tiếp.
- Wire.beginTransmission() được sử dụng để chỉ định địa chỉ slave và bắt đầu truyền.
- Wire.write() dùng để gửi dữ liệu tới thiết bị I2C.
- Wire.endTransmission() dùng để kết thúc quá trình truyền và kiểm tra lỗi.
- Wire.requestFrom() dùng để yêu cầu dữ liệu từ thiết bị I2C.
- Wire.available() được sử dụng để kiểm tra xem dữ liệu có sẵn để đọc từ thiết bị I2C hay không.
- Wire.read() dùng để đọc dữ liệu từ thiết bị I2C.
Sử dụng hàm Wire.beginTransmission() để đặt địa chỉ của cảm biến, địa chỉ này được chèn làm đối số. Ví dụ, nếu địa chỉ của cảm biến là 0x68, bạn sẽ sử dụng:
Wire.beginTransmission(0x68);
Thiết lập phần cứng Arduino I2C
Để kết nối hai bo mạch Arduino bằng I2C, bạn sẽ cần các thành phần phần cứng sau:
- Hai bảng Arduino (master và slave)
- Breadboard
- Dây cáp nối
- Hai điện trở kéo lên (pull-up resistor) 4,7kΩ
Kết nối các chân SDA và SCL của cả hai bo mạch Arduino với bảng mạch. Kết nối các điện trở kéo lên giữa những chân SDA và SCL với đường nguồn 5V trên breadboard. Cuối cùng, kết nối hai breadboard với nhau bằng dây cáp.
Mạch Arduino Uno
Mạch Arduino Nano
Thiết lập bo mạch Arduino làm thiết bị I2C Master và Slave
Sử dụng hàm Wire.requestFrom() để chỉ định địa chỉ của thiết bị slave mà chúng ta muốn giao tiếp. Sau đó sử dụng hàm Wire.read() để lấy dữ liệu từ thiết bị slave.
Mã thiết bị master:
#include <Wire.h>
void setup() {
Wire.begin(); // join i2c bus
Serial.begin(9600); // start serial for output
}
void receiveData() {
int address = 8;
int bytesToRead = 6;
Wire.requestFrom(address, bytesToRead);
while (Wire.available()) {
char data = Wire.read();
Serial.print(data);
}
delay(500);
}
void loop() {
receiveData();
}
Hàm Wire.onReceive() được sử dụng để chỉ định những việc cần làm khi thiết bị slave nhận dữ liệu từ thiết bị chính. Trong đoạn code trên, hàm Wire.available() kiểm tra xem có dữ liệu hay không và hàm Wire.read() đọc dữ liệu được gửi bởi thiết bị master.
Code thiết bị slave:
#include <Wire.h>
void setup() {
Wire.begin(8); // join the I2C bus with address 8
Wire.onReceive(receiveEvent); // call receiveEvent when data is received
}
void loop() {
delay(100);
}
void receiveEvent(int bytes) {
Wire.write("hello "); // respond with message of 6 bytes as expected by master
}
Gửi và nhận dữ liệu bằng I2C
Trong ví dụ này, hãy đọc nhiệt độ từ cảm biến nhiệt độ DHT11 giao tiếp với Arduino slave và in nó trên màn hình nối tiếp của Arduino master.
Hãy sửa đổi code đã viết trước đó để bao gồm phép đo nhiệt độ mà sau đó chúng ta sẽ gửi đến bo mạch chủ qua bus I2C. Sau đó, bo mạch chính có thể đọc giá trị đã gửi, hiển thị giá trị đó trên màn hình nối tiếp.
Code thiết bị master:
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
Serial.println("Master Initialized!");
}
void loop() {
Wire.requestFrom(8, 1); // Request temperature data from slave
if (Wire.available()) {
byte temperature = Wire.read(); // Read temperature data from slave
Serial.print("Temperature: ");
Serial.print(temperature);
Serial.println(" °C");
}
delay(2000); // Wait for 2 seconds before requesting temperature again
}
Code thiết bị slave:
#include <Wire.h>
#include <DHT.h>
#define DHTPIN 4 // Pin connected to DHT sensor
#define DHTTYPE DHT11 // DHT sensor type
DHT dht(DHTPIN, DHTTYPE);
byte temperature;
void setup() {
Wire.begin(8); // Slave address is 8
Wire.onRequest(requestEvent);
dht.begin();
}
void loop() {
delay(2000); // Wait for 2 seconds for DHT to stabilize
temperature = dht.readTemperature(); // Read temperature from DHT sensor
}
void requestEvent() {
Wire.write(temperature); // Send temperature data to master
}
Bạn có thể tùy chỉnh code này để phù hợp với bất kỳ cảm biến nào bạn có thể có trong dự án của mình hoặc thậm chí hiển thị các giá trị cảm biến trên mô-đun display để tạo nhiệt kế và máy đo độ ẩm riêng trong phòng.
Định địa chỉ slave với I2C trên Arduino
Để đọc các giá trị từ những thành phần được thêm vào bus I2C trong một dự án như vậy, điều quan trọng là bạn phải bao gồm địa chỉ slave chính xác khi viết code. May mắn thay, Arduino cung cấp một thư viện scanner giúp đơn giản hóa quá trình xác định địa chỉ slave, loại bỏ nhu cầu sàng lọc các bảng dữ liệu cảm biến dài và tài liệu trực tuyến khó hiểu.
Sử dụng đoạn code sau để xác định địa chỉ của bất kỳ thiết bị slave nào có trên bus I2C.
#include <Wire.h> // Include the Wire library for I2C communication
void setup() {
Wire.begin(); // Initialize the I2C communication
Serial.begin(9600); // Initialize the serial communication with a baud rate of 9600
while (!Serial); // Wait for the serial connection to establish
Serial.println("\nI2C Scanner"); // Print a message indicating the start of I2C scanning
}
void loop() {
byte error, address; // Declare variables to store errors and device addresses
int nDevices; // Declare a variable to store the number of devices found
Serial.println("Scanning..."); // Print a message indicating the start of I2C scanning
nDevices = 0; // Set the number of devices found to 0
for (address = 1; address < 127; address++) { // Iterate over all possible I2C addresses
Wire.beginTransmission(address); // Start a transmission to the current address
error = Wire.endTransmission(); // End the transmission and store any errors
if (error == 0) { // If no errors were found
Serial.print("I2C device found at address 0x"); // Print a message indicating a device was found
if (address < 16) Serial.print("0"); // If the address is less than 16, add a leading 0 for formatting purposes
Serial.print(address, HEX); // Print the address in hexadecimal format
Serial.println(" !"); // Print a message indicating a device was found
nDevices++; // Increment the number of devices found
}
else if (error == 4) { // If an error was found
Serial.print("Unknown error at address 0x"); // Print a message indicating an error was found
if (address < 16) Serial.print("0"); // If the address is less than 16, add a leading 0 for formatting purposes
Serial.println(address, HEX); // Print the address in hexadecimal format
}
}
if (nDevices == 0) { // If no devices were found
Serial.println("No I2C devices found\n"); // Print a message indicating no devices were found
}
else { // If devices were found
Serial.println("done\n"); // Print a message indicating the end of I2C scanning
}
delay(5000); // Delay for 5 seconds before starting the next scan
}
Giao tiếp hai bo mạch Arduino bằng giao thức giao tiếp I2C mang đến một cách linh hoạt và hiệu quả để đạt được những tác vụ phức tạp mà một bo mạch duy nhất không thể xử lý được. Với sự trợ giúp của thư viện Wire, việc giao tiếp giữa hai bo mạch sử dụng I2C trở nên dễ dàng, cho phép bạn thêm nhiều thành phần hơn vào dự án của mình.