CETut – Part 9: I2C Protocol

Khi làm việc giữa các thiết bị nhúng có nhu cầu giao tiếp dữ liệu thì các giao thức tương tác là điều tối quan trọng. Ngoài các giao thức như SPI, Ethernet, ... thì giao thức I2C có vai trò đặc biệt quan trọng và được sử dụng phổ biến trong đa số thiết bị vì các đặc tính đặc biệt của I2C so với các giao thức khác. Do đó, việc tìm hiểu và thực hành sử dụng giao thức I2C là điều cần thiết để giúp mọi người nắm vững về cơ chế và cách hoạt động.
Share

Khi làm việc giữa các thiết bị nhúng có nhu cầu giao tiếp dữ liệu thì các giao thức tương tác là điều tối quan trọng. Ngoài các giao thức như SPI, Ethernet, … thì giao thức I2C có vai trò đặc biệt quan trọng và được sử dụng phổ biến trong đa số thiết bị vì các đặc tính đặc biệt của I2C so với các giao thức khác. Do đó, việc tìm hiểu và thực hành sử dụng giao thức I2C là điều cần thiết để giúp mọi người nắm vững về cơ chế và cách hoạt động.

  1. Định nghĩa I2C
  • Giao thức I2C (Inter-Integrated Circuit) là một giao thức truyền thông nối tiếp đồng bộ được phát triển bởi Philips Semiconductors, cho phép các thiết bị điện tử giao tiếp với nhau theo mối quan hệ master-slave qua hai dây tín hiệu: SDA (Serial Data) và SCL (Serial Clock) với tốc độ tùy chỉnh sao cho các bit dữ liệu sẽ được truyền từng bit một theo các khoảng thời gian đều đặn được thiết lập bởi 1 tín hiệu xung. Nó thường được sử dụng để kết nối các vi điều khiển và các thiết bị ngoại vi trong các hệ thống nhúng.
  1. Sơ đồ thiết kế hoạt động của I2C
  • Để giao thức I2C được cấu hình đúng, ta cần xác định thiết bị master và thiết bị slave của giao thức.
  • Sau đó, tùy thuộc vào 2 thiết bị master và thiết bị slave, ta cần thiết lập các tùy chỉnh cho giao thức trên 2 thiết bị.
  • Tiếp đến, lựa chọn 2 chân để nối dây tín hiệu SDA và SCL với nhau giữa hai thiết bị
  • Tùy thuộc vào quy trình truyền tải/ghi nhận dữ liệu giữa 2 thiết bị mà ta cần cài đặt để đảm bảo sau khi giao thức truyền/nhận dữ liệu thì ta có thể tiếp cận và sử dụng được dữ liệu
  • Ví dụ đơn giản về thiết lập giao thức I2C giữa STM32F103C8T6 và PCF8574
A diagram of a computer network

Description automatically generated
  1. Ưu nhược điểm của I2C
  • Ưu điểm
    • Sử dụng chỉ hai dây
    • Hỗ trợ và nhiều slave
    • Bit ACK / NACK xác nhận rằng mỗi khung hình đã được truyền thành công
    • Phần cứng không phức tạp như UART
    • Giao thức nổi tiếng và được sử dụng rộng rãi
  • Nhược điểm
    • Tốc độ truyền chậm hơn so với SPI
    • Kích thước của khung dữ liệu được giới hạn trong 8 bit
    • Thực hiện phần cứng phức tạp hơn SPI
  1. So sánh giữa UART và SPI
A diagram of a circuit diagram

Description automatically generated
  • Giải thích:
    • Full-duplex (toàn đồng bộ):
      • Trong full-duplex, cả hai thiết bị có thể truyền và nhận dữ liệu đồng thời trên cùng một đường truyền.
      • Điều này có nghĩa là có thể truyền và nhận dữ liệu mà không cần chờ đợi lượt lẻ của mỗi thiết bị.
      • Ví dụ: Khi bạn gọi điện thoại và nói chuyện với ai đó, cả hai bên đều có thể nói và nghe đồng thời.
    • Half-duplex (bán đồng bộ):
      • Trong half-duplex, mỗi thiết bị chỉ có thể truyền hoặc nhận dữ liệu tại một thời điểm nhất định.
      • Khi một thiết bị đang truyền, nó không thể nhận dữ liệu và ngược lại.
      • Ví dụ: Khi bạn sử dụng trò truyền tin bằng đối thoại (walkie-talkie), bạn phải nhấn một nút để nói và sau đó nhả nút để nghe phản hồi từ bên kia. Trong thời gian này, bạn không thể nghe được những gì đang được truyền từ bên kia.
  1. Khung truyền I2C
A white rectangular object with black text

Description automatically generated
  • Trong một lần chuyển dữ liệu, master sẽ chuyển đi một khung truyền như trên.
  • Bit start : được kích hoạt bởi master.
  • Khối bit địa chỉ: Thông thường quá trình truyền nhận sẽ diễn ra với rất nhiều thiết bị, IC với nhau. Do đó để phân biệt các thiết bị này, chúng sẽ được gắn 1 địa chỉ vật lý 7 bit cố định.
  • Bit Read/Write: Bit này dùng để xác định quá trình là truyền hay nhận dữ liệu từ thiết bị Master. Nếu Master gửi dữ liệu đi thì ứng với bit này bằng ‘0’, và ngược lại, nhận dữ liệu khi bit này bằng ‘1’.
  • Bit ACK/NACK: Viết tắt của Acknowledged / Not Acknowledged. Dùng để so sánh bit địa chỉ vật lý của thiết bị so với địa chỉ được gửi tới. Nếu trùng thì Slave sẽ được đặt bằng ‘0’ và ngược lại, nếu không thì mặc định bằng ‘1’.
  • Khối bit dữ liệu: Gồm 8 bit và được thiết lập bởi thiết bị gửi truyền đến thiết bị nhân. Sau khi các bit này được gửi đi, lập tức 1 bit ACK/NACK được gửi ngay theo sau để xác nhận rằng thiết bị nhận đã nhận được dữ liệu thành công hay chưa. Nếu nhận thành công thì bit ACK/NACK được set bằng ‘0’ và ngược lại.
  • Bit stop : được kích hoạt bởi master.
  1. Quá trình truyền nhận
A screenshot of a computer

Description automatically generated
  • Bắt đầu: Thiết bị Master sẽ chuyển mạch SDA từ mức điện áp cao xuống mức điện áp thấp trong khi SCL ở mức cao ngay trước khi đường SCL chuyển từ cao xuống thấp.
  • Tiếp theo đó, Master gửi đi 7 bit địa chỉ tới Slave muốn giao tiếp cùng với bit Read/Write.
  • Slave sẽ so sánh địa chỉ vật lý với địa chỉ vừa được gửi tới. Nếu trùng khớp, Slave sẽ xác nhận bằng cách kéo đường SDA xuống 0 và set bit ACK/NACK bằng ‘0’. Nếu không trùng khớp thì SDA và bit ACK/NACK đều mặc định bằng ‘1’.
  • Thiết bị Master sẽ gửi hoặc nhận khung bit dữ liệu. Nếu Master gửi đến Slave thì bit Read/Write ở mức 0. Ngược lại nếu nhận thì bit này ở mức 1.
  • Nếu như khung dữ liệu đã được truyền đi thành công, bit ACK/NACK được set thành mức 0 để báo hiệu cho Master tiếp tục.
  • Sau khi tất cả dữ liệu đã được gửi đến Slave thành công, Master sẽ phát 1 tín hiệu Stop để báo cho các Slave biết quá trình truyền đã kết thúc bằng các chuyển lần lượt SCL, SDA từ mức 0 lên mức 1.
  1. Hàm hỗ trợ lớp trừu tượng phần cứng (HAL) cho I2C
  • Hàm truyền dữ liệu:
A black screen with white text

Description automatically generated
  • Hàm nhận dữ liệu:
A computer screen with white text

Description automatically generated
  1. Cơ chế thiết lập màn LCD với giao thức 4-bit
A black screen with white text

Description automatically generated
  • Giải thích:
    • Để khởi động màn hình, ta thực hiện chờ 15ms sau khi điện áp ổn định vượt ngưỡng 4.5V
    • Truyền lệnh 0x30 (tương ứng 00110000 hệ nhị phân) tới màn hình và đợi hơn 4.1ms
    • Truyền lệnh 0x30 tới màn hình và đợi hơn 100micro-s
    • Truyền liên tiếp theo thứ tự lệnh 0x28, 0x08, 0x01, 0x06, 0x0C. Trong đó mỗi lệnh nên cách nhau ít nhất 1ns.
    • Các giải thích ý nghĩa lệnh nằm trong bảng sau:
A black and white chart with white text

Description automatically generated
  1. Cơ chế ghi dữ liệu trong màn LCD
  • Đối với màn LCD1602, cơ chế thực hiện được mô tả ở hình dưới. Trong đó:
    • Bit RS ở mọi mức sẽ thực hiện chuyển đổi sang mức còn lại và giữ trong 1 khoảng thời gian xấp xỉ 280ns đến 300ns
    • Bit R/W ở mọi mức sẽ thực hiện chuyển đổi về mức thấp và giữ trong 1 khoảng thời gian xấp xỉ như bit RS
    • Bit E sẽ tạo ra tín hiệu chớp trong 500ns để thực hiện xác nhận bắt đầu quá trình nhập liệu dữ liệu hợp lệ vào màn hình
    • 8 bit dữ liệu sẽ được ghi nhận khi bit E ở giữa mức cao cho đến khi bit E ở giữa mức thấp. 8 bit này ở bất kỳ mức nào cũng thức hiện chuyển đổi sang mức ngược lại.
  1. Thư viện hỗ trợ lập trình với màn hình LCD
  • Để hỗ trợ thuận tiện cho giao tiếp với màn LCD , ta xây dựng một số hàm như sau:
    • Hàm lcd_send_cmd
void lcd_send_cmd (char cmd) {
char data_u, data_l;
uint8_t data_t[4];
data_u = (cmd&0xf0);
data_l = ((cmd<<4)&0xf0);
data_t[0] = data_u|0x0C;  //en=1, rs=0
data_t[1] = data_u|0x08;  //en=0, rs=0
data_t[2] = data_l|0x0C;  //en=1, rs=0
data_t[3] = data_l|0x08;  //en=0, rs=0
HAL_I2C_Master_Transmit (&hi2c1, SLAVE_ADDRESS_LCD,(uint8_t *) data_t, 4, 100);
}
  • Là hàm giúp truyền lệnh vào màn hình LCD
  • Do cơ chế giao tiếp giữa màn hình LCD1602 và mạch giao tiếp PCF8574 là 4-bit trong khi giao thức tương tác nội bộ là 8-bit nên ta thực hiện truyền 4 bit từ mức thấp tới cao
    • Nghĩa là mặc dù ta nối 16 chân của màn LCD với mạch giao tiếp PCF8574 nhưng bỏ qua các chân như RS, PWR, … thì chỉ có 4 chân dữ liệu từ P4 tới P7 là được truyền thẳng vào màn hình 
  • Hàm lcd_send_data
void lcd_send_data (char data) {
char data_u, data_l;
uint8_t data_t[4];
data_u = (data&0xf0);
data_l = ((data<<4)&0xf0);
data_t[0] = data_u|0x0D;  //en=1, rs=1
data_t[1] = data_u|0x09;  //en=0, rs=1
data_t[2] = data_l|0x0D;  //en=1, rs=1
data_t[3] = data_l|0x09;  //en=0, rs=1
HAL_I2C_Master_Transmit(&hi2c1, SLAVE_ADDRESS_LCD,(uint8_t *) data_t, 4, 100);
}
  • Dùng để truyền dữ liệu (ở đây là kí tự) vào màn hình. Các dữ liệu sẽ được thu thập thành số ASCII tương ứng với bảng thiết kế ký tự CGROM để hiển thị trên màn hình
  • Cơ chế tương tự như hàm lcd_send_cmd
  • Hàm lcd_clear
void lcd_clear (void) {
lcd_send_cmd (0x80);
for (int i=0; i<70; i++) {
lcd_send_data (‘ ‘);
}
}
  • Dùng để xóa các ký tự còn xuất hiện trên màn hình
  • Có thể dùng lệnh để xóa dựa trên bảng lệnh sẵn có của màn hình
  • Hàm lcd_put_cur
void lcd_put_cur(int row, int col) {    
switch (row)    {        
case 0:            
col |= 0x80;            
break;        
case 1:            
col |= 0xC0;            
break;    
}
     lcd_send_cmd (col);
}
  • Dùng để xác định tọa độ mà con trỏ trên màn hình sẽ di chuyển tới
  • Hàm lcd_send_string
void lcd_send_string (char *str) { while (*str) lcd_send_data (*str++);}
  • Dùng để truyền vào cả chuỗi ký tự thông qua hàm while, hỗ trợ giảm tải gọi lệnh nhiều lần
  • Hàm lcd_init
void lcd_init (void) {   
 // 4 bit initialisation
HAL_Delay(50);  // wait for >40ms
lcd_send_cmd (0x30);
HAL_Delay(5);  // wait for >4.1ms
lcd_send_cmd (0x30);
HAL_Delay(1);  // wait for >100us
lcd_send_cmd (0x30);
HAL_Delay(10);
lcd_send_cmd (0x20);  // 4bit mode
HAL_Delay(10);
 // dislay initialisation
lcd_send_cmd (0x28); 
// Function set–>DL=0 (4 bit mode),N = 1(2 line display) F = 0 (5×8 characters)
HAL_Delay(1);
lcd_send_cmd (0x08); 
//Display on/off control –> D=0,C=0, B=0  —> display off
HAL_Delay(1);
lcd_send_cmd (0x01);  // clear display
HAL_Delay(1);
HAL_Delay(1);
lcd_send_cmd (0x06); 
//Entry mode set –> I/D = 1 (increment cursor) & S = 0 (no shift)
HAL_Delay(1);
lcd_send_cmd (0x0C); 
//Display on/off control –> D = 1, C and B = 0. (Cursor and blink, last two bits)
}
  • Dùng để thiết lập màn hình dựa theo cơ chế lý thuyết
  1. Thực hành làm việc với I2C
  • Thiết bị cần chuẩn bị
    • STM32F103C8T6
A blue circuit board with yellow and white pins

Description automatically generated
  • Màn hình LCD1602 kèm mạch giao tiếp PCF8574
Màn hình LCD 1602 I2C Linh Kiện 1993
  • Nút bấm 12x12x12mm 4 chân
A close-up of several small black buttons

Description automatically generated
  • Trở 10kOhm
A close-up of a resistor

Description automatically generated
  • Dây bus
A group of colorful wires

Description automatically generated
  • Mạch nạp code cho STM32
  • Cấu hình
    • Chân PA6 sẽ được set làm chân input cho nút bấm ở chế độ pull-up
    • Chân PC13 được set làm LED (do LED có sẵn nằm trên mạch của MCU ứng với chân PC13) ở chế độ output push-pull low.
    • Trở 10kOhm sẽ được mắc chung với dây nối nút bấm và cắm vào nguồn 5V
    • Nút bấm sẽ được nối vào đất (GND) và chân PA2.
    • Chân PB7 làm chân SDA cho giao thức I2C
    • Chân PB6 làm chân SCL cho giao thức I2C
    • Chân VCC trên mạch PCF8574 nối vào chân 5V trên STM32F103C8T6
    • Chân GND trên mạch PCF8574 nối vào đất
  • Cấu hình trên STM32CubeIDE
    • Bước 1: Tạo project mới như phần trước
    • Bước 2: Vào mục Connectivity, chọn I2C1 để khởi tạo giao tiếp I2C
A screenshot of a computer

Description automatically generated
  • Bước 3: Cấu hình I2C
    • Chọn mode I2C
A screen shot of a computer

Description automatically generated
  • Giữ nguyên các thông số
A screenshot of a computer

Description automatically generated
  • Bước 4: cấu hình ngắt ngoài
    • Vào mục NVIC, enable các ngắt luồng của I2C (nếu muốn)
  • Code
A screen shot of a computer program

Description automatically generated
  • Giải thích
    • Ban đầu tắt đèn led đi và khởi tạo màn hình LCD
    • Xóa màn hình LCD.
    • Trong lặp vô hạn
      • Đọc giá trị của khi nhấn nút
      • Nếu nút nhấn đã nhấn giữ
        • Xóa màn hình
        • Đặt con trỏ về vị trí ban đầu
        • Gửi string lên màn hình báo hiệu LED bật
        • Delay 3s
      • Nếu nút nhấn được nhả ra
        • Xóa màn hình
        • Đặt con trỏ về vị trí ban đầu
        • Gửi string lên màn hình báo hiệu LED tắt
        • Delay 3s

Cùng xem video demo ví dụ trên tại link Demo ví dụ I2C với LCD

Thực hiện bài viết bởi Huỳnh Thanh Sang – Phan Ngọc Đức Thọ