12.2 C
London
Thứ Năm, Tháng 10 9, 2025

Giao thức I2C – Ví dụ với STM32


📌 Giới thiệu

I2C (Inter-Integrated Circuit) là một giao thức truyền thông nối tiếp, cho phép nhiều thiết bị ngoại vi (slave) giao tiếp với một hoặc nhiều thiết bị điều khiển (master) chỉ thông qua hai dây tín hiệu:

  • SDA (Serial Data Line): đường dữ liệu.
  • SCL (Serial Clock Line): đường xung nhịp.

I2C thường được sử dụng trong các hệ thống nhúng, cảm biến, IC giao tiếp nội bộ bo mạch.

Hình minh họa I2C bus

⚖️ So sánh với UART và SPI

Tiêu chíUARTSPII2C
Số dây2 (TX, RX)4 + mỗi thiết bị thêm 1 CS2 (SDA, SCL)
Thiết bị2 (point-to-point)Nhiều (nhưng cần nhiều CS)Nhiều (qua địa chỉ)
Đồng bộKhông
Tốc độTrung bình (115200bps)Cao (MHz)100kHz – 400kHz (tới vài MHz)
Đa masterKhôngKhông (thường chỉ 1)Có (hỗ trợ arbitration)

👉 I2C nằm giữa UART và SPI: ít dây hơn SPI, hỗ trợ nhiều thiết bị hơn UART.


⏳ Lịch sử ngắn gọn

  • 1982: Philips giới thiệu chuẩn I2C.
  • 1992: Bổ sung chế độ nhanh (400kHz) và địa chỉ 10 bit.
  • Sau đó: mở rộng fast-mode plus (1MHz), high-speed (3,4MHz), ultra-fast (5MHz).
  • 1995: Intel đưa ra SMBus (biến thể của I2C) dùng cho quản lý hệ thống.

🔌 Hoạt động phần cứng

Đặc điểm:

  • Open-drain / Open-collector: Thiết bị chỉ có thể kéo xuống mức thấp, mức cao được giữ nhờ điện trở kéo lên (pull-up resistor).
  • Pull-up resistor: Thường dùng 4.7kΩ (có thể điều chỉnh tùy bus dài/ngắn).
  • Tương thích điện áp: Dễ nối các thiết bị 3.3V và 5V, nhưng đôi khi cần mạch dịch mức.
Hình minh họa pull up resistor trên 2 dây I2C

📡 Nguyên tắc giao tiếp I2C

1. Start & Stop Condition

  • Start: SDA đi từ cao → thấp khi SCL đang cao.
  • Stop: SDA đi từ thấp → cao khi SCL đang cao.

2. Địa chỉ và ACK/NACK

  • Mỗi thiết bị slave có một địa chỉ (address). Trong chế độ phổ biến, dùng 7 bit làm địa chỉ.
  • Khi master gửi địa chỉ + bit đọc/ghi, slave tương ứng nếu nhận diện địa chỉ sẽ gửi ACK (acknowledge) bằng cách kéo SDA thấp tại chu kỳ ACK. Nếu không có ACK, tức là không có thiết bị phản hồi.
  • Sau mỗi byte dữ liệu, cũng có bit ACK/NACK để xác nhận dữ liệu đã nhận đúng hay chưa.

3. Clock Stretching

  • Trong quá trình truyền, nếu slave chưa sẵn sàng (ví dụ cần thời gian xử lý hoặc chuẩn bị dữ liệu), nó có thể giữ SCL ở mức thấp, làm master đợi trước khi tiếp tục gửi xung clock mới.
  • Nhờ đó, slave có thể “nói” rằng nó cần thời gian — master phải tuân thủ và không gửi tiếp nếu SCL vẫn bị kéo thấp

4. Arbitration (đa master)

  • Nếu có nhiều master bắt đầu truyền cùng lúc, sẽ có cơ chế tranh chấp: mỗi master khi gửi bit “1” (tức không kéo thấp) sẽ kiểm tra xem SDA thực tế có bằng 1 không. Nếu nó khác (tức có thiết bị khác kéo thấp), master đó biết đã thua arbitration và dừng truyền tiếp.
  • Cơ chế này đảm bảo rằng dòng SDA phản ánh “logic thấp” nếu bất kỳ master nào kéo thấp — giúp giải quyết tình huống nhiều master bước vào bus cùng lúc.

5. Chế độ nâng cao – hỗ trợ 10-bit địa chỉ

  • Ngoài chế độ 7-bit, I2C cũng định nghĩa chế độ 10-bit địa chỉ, nơi địa chỉ rộng hơn để hỗ trợ nhiều thiết bị hơn.
  • Trong trường hợp 10-bit, địa chỉ được gửi qua hai khung dữ liệu (frames) — khung đầu có mã tiền tố “11110” + bit MSB của địa chỉ + RW, khung sau chứa 8 bit địa chỉ thấp.
10 bit mode I2C

⚙️ Ví dụ với STM32 (HAL Library)

Với STM32, bạn có thể dùng CubeMX để cấu hình I2C dễ dàng:

  1. Chọn Peripheral I2C trong CubeMX, ví dụ I2C1.
  2. Chọn chân SDA, SCL tương ứng (thường hiển thị màu xanh khi enable).
  3. Cấu hình tốc độ (Standard Mode 100kHz hoặc Fast Mode 400kHz).
  4. Generate code và mở trong STM32CubeIDE.

Code ví dụ: STM32 Master đọc 1 byte từ Slave

#include "main.h"
#include "i2c.h"
#include "usart.h"

uint8_t data;

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_USART2_UART_Init();

  while (1)
  {
    // Gửi địa chỉ thanh ghi cần đọc
    uint8_t reg = 0x75; 
    HAL_I2C_Master_Transmit(&hi2c1, 0x68 << 1, &reg, 1, HAL_MAX_DELAY);

    // Đọc dữ liệu từ slave
    HAL_I2C_Master_Receive(&hi2c1, 0x68 << 1, &data, 1, HAL_MAX_DELAY);

    // In ra UART
    char msg[30];
    sprintf(msg, "Gia tri: 0x%02X\r\n", data);
    HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);

    HAL_Delay(1000);
  }
}

👉 Ở ví dụ này, STM32 cấu hình làm master, đọc thanh ghi WHO_AM_I từ MPU6050 qua I2C.


⚠️ Lưu ý khi sử dụng I2C

  • Địa chỉ trùng lặp: Một số IC có địa chỉ cố định → khó dùng nhiều IC cùng loại.
  • Chiều dài bus hạn chế: Bus dài hoặc nhiều thiết bị gây nhiễu, cần tính toán lại pull-up resistor hoặc dùng bus extender.
  • Tốc độ: Không phù hợp cho truyền dữ liệu tốc độ cao hoặc khoảng cách xa.

✅ Kết luận

I2C là một trong những giao thức truyền thông quan trọng nhất trong thế giới nhúng. Với chỉ hai dây, nó cho phép kết nối nhiều cảm biến, IC, module ngoại vi trong một hệ thống.

Trên thực tế, bạn có thể dễ dàng triển khai I2C trên Arduino, Raspberry Pi, hoặc STM32. Mỗi nền tảng đều có thư viện hỗ trợ, giúp việc lập trình nhanh chóng và hiệu quả.

Đánh giá bài viết!
Admin Peter
Admin Peterhttps://capitlab.com/
Coder & Hardware Designer $ Embedded Linux

Nội dung hấp dẫn

Modbus RTU STM32 (Phần 3)

Ở phần 1 và phần 2 mình đã giới...

Modbus RTU STM32 (Phần 2)

Giao thức Modbus trên STM32

Modbus RTU STM32 (Phần 1)

Trong các hệ thống điều khiển công nghiệp, Modbus...

STM32 HAL driver với cảm biến nhịp tim MAX30100 sử dụng STM32CUBE IDE

Cấu tạo và cách giao tiếp với cảm biến...

Thiết kế mạch cầu H tích hợp PID

Bài viết này mình sẽ chia sẻ cho...

Đặt mạch Trung Quốc, mua linh kiện hiếm

Bài viết nói về đặt mạch Trung Quốc và...

Nội dung liên quan

Modbus RTU STM32 (Phần 2)

Giao thức Modbus trên STM32

Modbus RTU STM32 (Phần 1)

Trong các hệ thống điều khiển công nghiệp, Modbus RTU là một trong những giao thức truyền thông phổ biến và dễ triển khai...

STM32 HAL driver với cảm biến nhịp tim MAX30100 sử dụng STM32CUBE IDE

Cấu tạo và cách giao tiếp với cảm biến nhịp tim MAX30100 Cảm biến MAX30100 là một module đo nhịp tim và nồng độ oxy...