Giao thức I2C và giao tiếp với cảm biến nhiệt hồng ngoại MLX90614

Giao thức I2C được sử dụng trong rất nhiều ứng dụng quen thuộc như giao tiếp với module thời gian thực DS1307, DS3231, module I2C-LCD 16×2, IC EEPROM… Vậy giao thức I2C được định nghĩa như thế nào, nó hoạt động ra sao? Bài viết sau đây sẽ giúp các bạn hiểu rõ về giao thức I2C và thực hiện một ví dụ đó là sử dụng vi điều khiển STM32 để đọc nhiệt độ từ cảm biến hồng ngoại MLX90614. 

I. ĐỊNH NGHĨA GIAO THỨC I2C (Inter-Integrated Circuit)
I2C là một giao thức giao tiếp được phát triển bởi Philips Semiconductors để truyền, nhận dữ liệu giữa một hoặc có thể nhiều Master – được xem như là các thiết bị điều khiển trung tâm với một hoặc nhiều Slave – được xem như là các ngoại vi trên cùng một hệ thống thông qua hai đường truyền tín hiệu.

Hình 1: Minh hoạ kết nối sử dụng giao tiếp I2C

[HỌC ONLINE TẠI TAPIT: LẬP TRÌNH VI ĐIỀU KHIỂN STM32, VI XỬ LÝ ARM CORTEX – M]

Các thiết bị kết nối với bus I2C được phân thành hai loại: master và slave. Trong đó, master sở hữu quyền kiểm soát để thực hiện đưa ra yêu cầu đến các slave, còn slave là một thiết bị đáp ứng các yêu cầu từ master. Như hình minh họa ở trên, master thông thường là các vi điều khiển, slave sẽ là các ngoại vi như cảm biến nhiệt độ, LCD driver, EEPROM,…

Tại một thời điểm chỉ có duy nhất một thiết bị master ở trạng thái hoạt động trên bus I2C. master điều khiển bus clock SCL và quyết định hoạt động nào sẽ được thực hiện trên bus dữ liệu SDA. Tất cả các thiết bị đáp ứng các yêu cầu từ thiết bị master này đều là slave. Để phân biệt giữa nhiều thiết bị slave khi được kết nối với cùng một hệ thống bus I2C thì mỗi thiết bị slave sẽ có một địa chỉ vật lý 7-bit cố định.

Khi một thiết bị master muốn truyền hoặc nhận dữ liệu từ một thiết bị slave, master sẽ xác định địa chỉ thiết bị slave cụ thể trên đường SDA và sau đó tiến hành truyền dữ liệu. Tất cả các thiết bị slave khác không gửi tín hiệu phản hồi về, trừ khi địa chỉ của chúng được chỉ định bởi thiết bị master trên đường SDA.

II.PHƯƠNG THỨC HOẠT ĐỘNG – TRƯỜNG HỢP SỬ DỤNG
Trường hợp 1: Thiết bị master muốn gửi dữ liệu cho một thiết bị slave

  • Master thực hiện một điều kiện bắt đầu (START)
  • Master gửi địa chỉ của slave (Device Address) cần nhận dữ liệu và Bit cấu hình đọc ghi dữ liệu (R/W) được gửi kèm có giá trị bằng 0 thể hiện hoạt động gửi dữ liệu. 
  • Slave phản hồi bằng bit xác nhận (ACK), xác nhận có slave hoạt động trên hệ thống bus
  • Master gửi địa chỉ thanh ghi của slave – địa chỉ mà master muốn ghi/bắt đầu ghi dữ liệu.
  • Slave phản hồi bằng bit xác nhận (ACK), xác nhận có địa chỉ thanh thi, sẵn sàng nhận dữ liệu
  • Master gửi các dữ liệu (Data) cần ghi vào thanh ghi cho slave, có thể một hoặc nhiều byte.
  • Master thực hiện kết thúc việc truyền dữ liệu bằng một điều kiện kết thúc (STOP).


Hình 2: Khung truyền dữ liệu khi thiết bị master muốn gửi dữ liệu cho thiết bị slave

Trường hợp 2: Thiết bị master muốn đọc dữ liệu từ một thiết bị slave

  • Master thực hiện một điều kiện bắt đầu (START)
  • Master gửi địa chỉ của slave (Device Address) cần nhận dữ liệu, theo kèm là bit cấu hình đọc ghi dữ liệu (R/W) có giá trị bằng 0 thể hiện hoạt động gửi dữ liệu (bằng 0 để gửi tiếp địa chỉ thanh ghi)
  • Slave phản hồi bằng bit xác nhận (ACK), xác nhận có slave hoạt động trên hệ thống bus
  • Master gửi địa chỉ thanh ghi của Slave – địa chỉ mà master muốn ghi /bắt đầu ghi dữ liệu.
  • Slave phản hồi bằng bit xác nhận (ACK), xác nhận có địa chỉ thanh ghi trên thiết bị slave. 
  • Master gửi lại điều kiện bắt đầu cùng với địa chỉ của thiết bị slave, theo sau đó là giá trị 1 của bit R/W thể hiện hoạt động đọc dữ liệu.
  • Slave phản hồi bằng bit xác nhận (ACK) 
  • Master nhận dữ liệu từ slave, có thể một hoặc nhiều byte.
  • Master kết thúc việc nhận dữ liệu bằng cách thực hiện bit xác nhận (NACK) và theo sau đó là một điều kiện kết thúc (STOP).


Hình 3: Khung truyền dữ liệu khi thiết bị master muốn đọc dữ liệu từ thiết bị slave

Phân tích các thành phần trong khung truyền: 
1. Điều kiện bắt đầu và điều kiện kết thúc (STAT, STOP)

Hình 4: Tín hiệu của điều kiện bắt đầu và điều kiện kết thúc

  • Giao tiếp I2C được khởi tạo bằng cách master thực hiện điều kiện bắt đầu và kết thúc bằng cách master thực hiện điều kiện kết thúc. Một sự chuyển đổi logic từ mức HIGH sang mức LOW trên đường truyền SDA trong khi đường truyền SCL ở mức HIGH được định nghĩa là một điều kiện bắt đầu. Một sự chuyển đổi mức logic từ mức LOW sang mức HIGH trên đường truyền SDA trong khi đường SCL ở mức HIGH được định nghĩa là điều kiện kết thúc. 

2. Các bit địa chỉ 

  • Các bit địa chỉ giúp xác định, phân biệt các slave khác nhau trên hệ thống bus I2C, các master phải có/ được cài đặt các địa chỉ khác nhau. Thông thường có 7 bit địa chỉ.
  • Bit địa chỉ được gửi kèm với bit cầu hình đọc/ghi dữ liệu.

3. Bit cấu hình đọc/ghi dữ liệu (R/W)

  • Bit này xác định hướng truyền dữ liệu hay có thể hiểu là thiết bị nào sẽ điều khiển đường SDA: Nếu Master cần truyền dữ liệu đến Slave, bit Read / Write được thiết lập mức logic LOW. Ngược lại, nếu Master cần nhận dữ liệu từ Slave, bit này được thiết lập mức logic HIGH.

4. Bit xác nhận (ACK/NACK)

  • Mỗi byte dữ liệu đều được xác nhận bởi một bit ACK từ phía nhận dữ liệu gửi cho phía gửi dữ liệu để báo rằng byte đã được nhận thành công và có thể tiếp tục gửi byte dữ liệu tiếp theo. Bit ACK có giá trị LOW. Khi nó có giá trị HIGH thì được gọi là bit NACK, bit NACK được gửi đi trong một số trường hợp như: 
    • Phía nhận đang bận và không thể nhận hay truyền dữ liệu vì đang thực hiện một tính năng khác. 
    • Trong quá trình truyền nhận, dữ liệu/địa chỉ không hợp lệ, không tồn tại. 
    • Trong quá trình truyền, phía nhận không thể nhận thêm các byte dữ liệu nữa. 
    • Trong trường hợp master yêu cầu dữ liệu từ slave, master đã nhận đủ và không nhận thêm dữ liệu. 

5. Các bit dữ liệu (Data)

  • Dữ liệu được truyền tới các thiết bị slave hoặc được đọc từ các thiết bị slave, bản chất của việc này chính là thực hiện đọc/ghi các thanh ghi trong thiết bị slave. Các thanh ghi này nằm trong bộ nhớ của slave và được xác định, phân biệt bởi các địa chỉ, các thanh ghi sẽ chứa thông tin, có thể là thông tin cấu hình hoạt động của thiết bị slave, có thể thông tin dữ liệu mà slave có được trong quá trình hoạt động như dữ liệu lấy mẫu từ cảm biến. Để điều khiển thiết bị slave thực hiện một nhiệm vụ chức nào đó, master cũng sẽ thực hiện việc ghi vào thanh ghi của thiết bị slave. 

6. Lặp lại điều kiện bắt đầu (Repeated Start)

  • Việc thiết bị master thực hiện lặp lại điều kiện bắt đầu cũng tương tự như việc master thực hiện điều kiện bắt đầu (START), lặp lại điều kiện bắt đầu được sử dụng để thay thế cho việc thực hiện điều kiện STOP rồi thực hiện điều kiện START. 
  • Lặp lại điều kiện bắt đầu được sử dụng trong trường hợp thiết bị master muốn đọc dữ liệu từ thiết bị slave sau khi đã nhận các phản hồi về địa chỉ thiết bị slave và địa chỉ thanh ghi muốn đọc dữ liệu. 

III. MỘT SỐ HÀM HỖ TRỢ LÀM VIỆC VỚI I2C TRONG THƯ VIỆN HAL – STM32

1. HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress,uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)

-> Dùng để đọc một lượng dữ liệu ở chế độ blocking (hàm sẽ không return và chờ cho đến khi có 1 sự kiện hoặc khi có dữ liệu đến) từ bộ nhớ có địa chỉ cụ thể.

– Chi tiết các tham số hàm này như sau:

  • hi2c : Là con trỏ đến structure I2C_HandleTypeDef có chứa thông tin cấu hình cho bộ i2c được chỉ định
  • DevAddress : Địa chỉ của Slave
  • MemAddress : Địa chỉ bộ nhớ của Slave
  • MemAddSize : Kích thước của địa chỉ bộ nhớ
  • pData : Con trỏ trỏ đến Data Buffer
  • Size : Lượng Data được gửi
  • Timeout : Thời gian chờ

– Giá trị trả về: Status

2. HAL_I2C_Mem_Write(I2C_HandleTypeDef * hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t * pData, uint16_t Size, uint32_t Timeout)

-> Dùng để ghi một lượng dữ liệu ở chế độ blocking từ bộ nhớ có địa chỉ cụ thể

– Tham số và giá trị trả về giống HAL_I2C_Mem_Read

3. HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size,uint32_t Timeout): Dùng để nhận một lượng data ở mode Master

– Giá trị trả về : Status

– Lưu ý: Vì hàm này dùng để nhận data ở mode Master nên sẽ không có tham số MemAddress và MemAddSize

4. HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size,uint32_t Timeout): Dùng để truyền một lượng data ở mode Master

– Giá trị trả về : Status

IV. THỰC HÀNH GIAO TIẾP I2C, VI ĐIỀU KHIỂN STM32 VÀ MODULE MLX90614

a. Thông số kỹ thuật

Điện áp hoạt động: 2,6 ~ 3,6V

Dải đo: Nhiệt độ môi trường xung quanh -40°C đến +125°C, nhiệt độ vật thể -70°C đến +380° có thể đo được

Sai số: Chênh lệch 0,5°C ở nhiệt độ phòng, 0.2°C đối với nhiệt độ đối tượng

Độ phân giải đo: 0,02°C

b. Cách đặt vị trí, khoảng cách của vật cần đo đến cảm biến

Vị trí: Đặt vật cần đo tại điểm Point heat source, tại điểm này cảm biến có độ nhạy cao nhất (100%) thì nhiệt độ của đối tượng cần đo sẽ chính xác nhất. Khoảng cách để đo giá trị chính xác nhất: Point heat source = 7 đến 10 cm.

c. Các đọc giá trị nhiệt độ từ cảm biến MLX90614
Các bạn cần đọc tài liệu datasheet của cảm biến này.
MLX có 2 bộ nhớ quan trọng: EEPROM và RAM.
Địa chỉ truy cập RAM:         000x xxxx*
Địa chỉ truy cập EEPROM: 001x xxxx*

Nhiệt độ của đối tượng (temperature of object) được lưu trong bộ nhớ RAM, giá trị trả về sẽ được đưa vào 2 thanh ghi TOBJ1 hoặc TOBJ2. Địa chỉ: TOBJ1: 0x07, TOBJ2: 0x08

Sau khi đọc giá trị nhiệt độ của đối tượng ở TOBJ1 hoặc TOBJ2, ta thực hiện chuyển nhiệt độ đổi qua độ K qua công thức:

Với To: nhiệt độ của đối tượng (K).

Toreg: địa chỉ thanh ghi chứa nhiệt độ chuyển sang hệ thập phân.

d. Thực hành sử dụng vi điều khiển STM32 giao tiếp với cảm biến MLX90614 

Bước 1: Mở phần mềm STM32CubeIDE, chọn vi điều khiển. Ở đây mình sẽ thực hành trên dòng STM32F303. Các bạn vẫn có thể sử dụng KeilC để thực hành và sử dụng vi điều khiển STM32 khác.

Bước 2: Cấu hình Serial Wires

Bước 3: Cấu hình ngoại vi I2C. Ở đây mình sẽ chọn bộ I2C1 để thực hành.

Bước 4: Sinh code và chuyển qua giao diện C/C++ (main.c)

Bước 5: Ở vòng lặp while(1) chúng ta thực hiện đọc giá trị nhiệt độ từ cảm biến và quy đổi về độ C

Bước 6: Thực hiện biên dịch và chuyển qua giao diện Debug để kiểm tra các giá trị

Hình ảnh thực hành của nhóm:

Hi vọng với bài viết này các bạn có thể hiểu rõ hơn về giao thức I2C cũng như sử dụng các API có liên quan trong thư viện HAL. Các bạn có thể thực hành với một số ngoại vi I2C khác như LCD-I2C, EEPROM hoặc giao tiếp giữa 2 vi điều khiển với nhau để hiểu hơn về giao thức này

Chúc các bạn thành công!

Nhóm thực tập:
Phương Duy, Thảo Ly, Thị Nghị, Đức Hưng

Fanpage Cộng đồng Kỹ thuật TAPIT: TAPIT – Learning, Research and Sharing Community

Tài liệu tham khảo:
– Understanding the I 2C Bus – Texas Instruments – Jonathan Valdez, Jared Becker
– MLX90614 datasheet.