BÀI 7. LẬP TRÌNH STM32  – Giao tiếp I2C với SHT20

Giao thức I2C (Inter-Integrated Circuit) là một chuẩn phổ biến được sử dụng để truyền dữ liệu đồng bộ theo kiểu nối tiếp giữa các thành phần khác nhau trong một thiết bị điện tử. Nó được phát triển bởi công ty Philips Semiconductor (nay là NXP Semiconductors) vào năm 1982 và đã được ứng dụng rộng rãi trong nhiều lĩnh vực điện tử nhờ vào sự đơn giản và linh hoạt của nó.

Nguyên lý hoạt động của I2C

Hoạt động của I2C dựa trên sơ đồ 2 dây, bao gồm:

  • SDA (Serial Data Line) – đường dữ liệu, nơi truyền nhận dữ liệu giữa các thiết bị
  • SCL (Serial Clock Line) – đường xung clock, dùng để đồng bộ hóa việc truyền nhận dữ liệu

Sơ đồ kết nối các thiết bị qua chuẩn giao tiếp I2C như hình vẽ.

Trong sơ đồ này, chỉ có một thiết bị Master (thiết bị điều khiển), và tất cả các thiết bị còn lại là Slave (thiết bị bị điều khiển). Master khởi xướng việc truyền tải dữ liệu, tạo ra các xung nhịp clock và tín hiệu điều khiển, trong khi các thiết bị Slave sẽ phản hồi các yêu cầu từ thiết bị Master.

Trong giao tiếp I2C, các đường SDA và SCL được nối với nguồn thông qua các điện trở kéo lên, thường đặt ở thiết bị Master (ví dụ như vi điều khiển). Các điện trở này đảm bảo rằng các đường dữ liệu và xung clock sẽ duy trì trạng thái cao khi không có tín hiệu hoạt động từ thiết bị Master hoặc Slave.

Các điện trở kéo lên cũng giúp giảm thiểu và lọc nhiễu, cũng như hạn chế các biến dạng tín hiệu trên các đường truyền dữ liệu, điều này rất quan trọng để đảm bảo việc truyền nhận dữ liệu ổn định trong môi trường điện từ nhiễu hoặc khi có kết nối dài.

Giá trị điện trở thường là 4,7 hoặc 10kOm.

Quy trình triển khai giao tiếp I2C gồm các bước cơ bản sau:

  1. Điều kiện bắt đầu (Start condition): Thiết bị master tạo ra tín hiệu start để thông báo cho tất cả các thiết bị về việc bắt đầu truyền dữ liệu. Tín hiệu bắt đầu bằng việc chuyển tín hiệu trên SDA từ trạng thái cao sang thấp. Sau một khoảng thời gian (tùy thuộc vào tần số), SCL sẽ chuyển từ trạng thái cao xuống thấp.
  2. Địa chỉ (ADDR): Thiết bị master gửi 7 bit đầu tiên là địa chỉ của thiết bị slave mà nó muốn giao tiếp. Bit thứ 8 sẽ chỉ định hướng truyền dữ liệu: đọc – 1 hoặc ghi – 0.
  3. Bit xác nhận (ACK): Thiết bị slave gửi một bit xác nhận, gọi là bit ACK (Acknowledgement). Giá trị 1 cho biết slave đã nhận được dữ liệu.
  4. Dữ liệu (DATA): Dữ liệu được truyền giữa master và slave dưới dạng các byte liên tiếp. Cụ thể master gửi 8 bit dữ liệu, slave trả lời bằng 1 bit xác nhận.
  5. Stop condition: Thiết bị master tạo ra tín hiệu dừng để kết thúc quá trình truyền nhận dữ liệu. Khi kết thúc, SDA sẽ chuyển từ trạng thái thấp lên cao. Sau khoảng thời gian (tùy thuộc vào tần số), SCL cũn sẽ thực hiên chuyển động tương tự như SDA.

Dưới đây là một ví dụ write data tới Slave

Địa chỉ trong I2C gồm 7 bit (vì vậy tối đa có thể kết nối đến 127 thiết bị trên cùng một bus), còn bit thứ 8 xác định xem Slave sẽ nhận hay gửi dữ liệu trong byte tiếp theo. Sau đó là bit thứ 9 – bit xác nhận ACK.

Nếu Slave nhận đúng địa chỉ của mình và đọc đủ toàn bộ byte, thì vào chu kỳ clock thứ 9, nó sẽ kéo đường SDA xuống mức thấp (0), tạo ra tín hiệu ACK – tức là kiểu như “Hiểu rồi!”. Master nhìn thấy điều đó và hiểu rằng mọi thứ đang diễn ra đúng kế hoạch, có thể tiếp tục truyền dữ liệu.

Nhưng nếu Slave không tồn tại, không nhận ra địa chỉ, nhận sai byte, bị lỗi, cháy nổ hay chuyện gì khác xảy ra, thì vào chu kỳ thứ 9, sẽ không có thiết bị nào kéo SDA xuống 0 – tức là không có ACK, mà là NACK. Lúc đó, Master đành “buồn bã” và tạm dừng mọi nỗ lực truyền thông cho đến khi tình hình khá hơn

Qúa trình đọc dữ liệu từ Slave như sau:

Việc đọc dữ liệu trong I2C thực tế gần như tương tự, nhưng có một điểm cần lưu ý mà tôi đã từng mất rất nhiều thời gian để nhận ra. Khi nhận byte cuối cùng, bạn phải cho Slave biết rằng không còn cần dữ liệu từ nó nữa và gửi NACK (Not Acknowledged) cho byte cuối cùng. Nếu bạn gửi ACK thì sau tín hiệu dừng (Stop condition), Master sẽ không giải phóng đường SDA — đó là cách hoạt động của máy trạng thái (finite state machine).

Vậy nên, việc nhận hai byte sẽ như sau (với R=1, tức là chế độ đọc):

  1. Master gửi địa chỉ Slave với bit đọc (R=1).
  2. Slave nhận địa chỉ và bắt đầu gửi dữ liệu.
  3. Master tiếp nhận byte dữ liệu đầu tiên và gửi ACK (Slave tiếp tục gửi byte tiếp theo).
  4. Master tiếp nhận byte dữ liệu cuối cùng và gửi NACK, báo hiệu rằng không cần thêm dữ liệu nữa.
  5. Sau đó, Master tạo điều kiện dừng (Stop condition), kết thúc quá trình truyền nhận dữ liệu.

Điều này giúp tránh việc Master tiếp tục giữ đường SDA kéo xuống sau khi quá trình truyền kết thúc

Sau phần tìm hiểu về giao tiếp I2C, bây giờ là lúc chúng ta sẽ bắt tay vào thực nghiệm để hiểu cách làm việc của giao thức này.

Đầu tiên chúng ta cấu hình I2C trong CubeMX

Chân PB7 và PB6 tương ứng là SDA, SCL của giao tiếp I2C. Như trong cấu hình, các bạn có thể thấy STM32F103C8T6 được trang bị 2 I2C, chúng ta sẽ sử dụng I2C1 để thực hiện thí nghiệm này.

Rất nhiều loại cảm biến, module, màn hình LCD có giao thức là chuẩn I2C. Để thực hành, trong bài này tôi sẽ sử dụng cảm biến nhiệt độ độ ẩm SHT20, lý do là vì: thứ nhất nó nằm trong bộ sưu tập cảm biến của tôi; thứ 2 cách đọc cảm biến này khá đơn giản nên tôi muốn bắt đầu từ SHT20.

Datasheet https://sensirion.com/media/documents/CCDE1377/635000A2/Sensirion_Datasheet_Humidity_Sensor_SHT20.pdf

Đây là cảm biến có dải đo độ ẩm từ 0 – 100%, nhiệt độ từ -40 đến 125 độ C.

Sơ đồ kết nối cảm biến tới vi điều khiển theo chuẩn I2C như sau:

Trên module cảm biến đã có hàn trở và tụ nên nhiệm vụ của chúng ta chỉ cần nối dây SDA, SCL tới SDA, SCL của vi điều khiển STM32F103C8T6 tương ứng với chân PB7 và PB6. Chân GND nối vào GND của mạch vi điều khiển, còn chân VDD tới nguồn 3.3V.

Để đọc các giá trị cần thiết của cảm biến SHT20, chúng ta cần gửi tới Slave (SHT20) command Trigger T measurement và Trigger RH measurement, tương ứng giá trị là 0xE3 và 0xE5.

Cách đọc giá trị được trình bày theo sơ đồ sau:

Đầu tiên cần phải khai báo địa chỉ của cảm biến + 1 bit = 0 (Write) để thông báo rằng Master (STM32F103C8T6) muốn ghi dữ liệu command vào Slave, Byte tiếp theo là Byte command. Ví dụ để đọc giá trị nhiệt độ cần phải ghi vào Slave command 0xE3, để đọc độ ẩm command 0xE5. Sử dụng hàm  HAL_I2C_Master_Transmit() để ghi dữ liệu vào Slave. Sau đó đọc giá trị trả về từ Slave, cũng là địa chỉ của Slave + 1 bit = 1 (Read) để thông báo là nhận giá trị trả về, tiếp theo là 3 byte nhận được (bao gồm 2 byte giá trị nhiệt độ và 1 byte checksum). Hàm HAL_I2C_Master_Receive() cho phép chúng ta nhận dữ liệu trả về từ Slave.

Địa chỉ của cảm biến SHT20 mặc định là 0x40. Bắt buộc phải dịch sang trái 1 bit để bit thứ 8 chúng ta có thể cài đặt bit Write hay Read theo như cấu trúc bản tin của giao thức I2C.

Tiếp theo là các mảng, các biến cần thiết để lưu trữ dữ liệu và tính toán ra giá trị nhiệt độ, độ ẩm từ cảm biến.

Trong vòng lặp while chúng ta tiến hành đọc dữ liệu từ cảm biến.

3 dòng code này thực hiện quá trình nhận dữ liệu nhiệt độ từ cảm biến theo chuẩn giao tiếp I2C đã đề cập ở trên. Trong hai hàm Transmit và Receive có các tham số giống nhau: đầu tiên là con trỏ đến địa chỉ của i2c1, tiếp theo là địa chỉ của Slave đã được dịch sang trái 1 bit, sau đó là giá trị command đối với Transmit và biến lưu dữ liệu đối với Receive, tiếp đến là số lượng byte tương ứng và cuối cùng là timeout.

Slave trả về 3 byte, 2 byte đầu tiên chứa giá trị nhiệt độ và byte cuối cùng là byte checksum (chúng ta sẽ đề cập checksum ở một bài viết khác).

Giá trị nhiệt độ trả về 2 byte đầu tiên cần phải chuyển đổi theo các hàm sau:

Do sử dụng công thức toán học nên bắt buộc các bạn phải khai báo thư viện

#include “math.h”

Cách đọc giá trị độ ẩm cũng tương tự như cách đọc nhiệt độ.

Các bạn buil chương trình và chạy ở chế độ debug để theo dõi kết quả nhiệt độ, độ ẩm thu được.

Giá trị nhiệt đọ hiển thị là 26.2, độ ẩm là 64.2. Các bạn thử tăng nhiệt độ bằng cách chạm vào cảm biến hoặc thổi vào cảm biến để xem giá trị độ ẩm thay đổi.

Như vậy, trong bài hôm nay chúng ta đã tìm hiểu giao thức I2C và thực hành đọc dữ liệu từ cảm biến SHT20 qua chuẩn giao tiếp phổ biến này, phương pháp sử dụng là Polling. Tương tự như chuẩn giao tiếp UART, cách đọc Polling này thường gây tốn tài nguyên cho CPU nếu như hệ thống phức tạp và tồn tại nhiều tác vụ phải giải quyết song song. Bài tiếp theo chúng ta sẽ sử dụng các hàm Interrupt I2C của thư viện HAL để giải quyết bài toán.

Xin chào tạm biệt và hẹn gặp lại!


Subscribe to my newsletter

4 bình luận cho “BÀI 7. LẬP TRÌNH STM32  – Giao tiếp I2C với SHT20”

  1. Ảnh đại diện Nga Nguyễn Thị Thúy
    Nga Nguyễn Thị Thúy

    Bài viết quá hay ạ, tiếc là không có cảm biến sht20 để thử. Dù sao cũng cảm ơn ad rất nhiều.

    Thích

  2. Ảnh đại diện colorfulsuperblyef5490c90a
    colorfulsuperblyef5490c90a

    Thank you

    Thích

  3. Ảnh đại diện colorfulsuperblyef5490c90a
    colorfulsuperblyef5490c90a

    Hôm nào làm về SPI đi ad

    Đã thích bởi 1 người

    1. SPI sẽ tiếp theo I2C nhé bạn

      Thích

Gửi phản hồi cho Nga Nguyễn Thị Thúy Hủy trả lời