Bài 10: Giao thức I2C, lập trình STM32 với module RTC DS3231

giao thức i2c

Giao thức I2C là một chuẩn truyền thông nối tiếp đồng bộ trong vi xử lý. Giao thức này được sử dụng rất nhiều trong thiết kế vì tính đơn giản và dễ làm việc. Tuy nhiên bù lại tốc độ thường không cao và chỉ sử dụng trong mạch (on board).

Bài 10 trong serie Học lập trình STM32 từ A tới Z

Giao thức I2C là gì

I2C viết tắt của Inter-Integrated Circuit, là một phương thức giao tiếp được phát triển bởi hãng Philips Semiconductors, dùng để truyền tín hiệu giữa vi xử lý và các IC trên bus nối tiếp.

Các bạn cứ hình dung I2C giống một lớp học: thầy giáo (Master) đứng trên bục gọi tên một bạn học sinh (địa chỉ Slave), cả lớp cùng nghe nhưng chỉ bạn được gọi tên mới đứng lên trả lời.

Đặc điểm:

  • Tốc độ không cao
  • Thường sử dụng onboard với đường truyền ngắn
  • Nối được nhiều thiết bị trên cùng một bus
  • Giao tiếp đồng bộ, sử dụng Clock từ Master
  • Sử dụng 7 bit hoặc 10 bit địa chỉ
  • Chỉ sử dụng 2 chân tín hiệu SDA, SCL
  • Các tốc độ thường gặp: Standard mode (100 kbit/s) và Fast mode (400 kbit/s), ngoài ra còn Low-speed mode (10 kbit/s)

Kết nối vật lý của giao thức I2C

Bus I2C sử dụng 2 dây tín hiệu là SDA (Serial Data Line) và SCL (Serial Clock Line). Dữ liệu truyền trên SDA được đồng bộ với mỗi xung SCL. Đường SCL chỉ Master mới có quyền điều khiển.

Tất cả các thiết bị đều dùng chung 2 đường tín hiệu này.

H1 2

Hai đường bus SDA và SCL hoạt động ở chế độ Open Drain hay cực máng hở. Nghĩa là tất cả các thiết bị trong mạng đều chỉ có thể kéo 2 đường này về 0 chứ không thể kéo lên 1. Nhờ vậy sẽ tránh được việc ngắn mạch khi thiết bị này kéo lên cao, thiết bị kia kéo xuống thấp.

Để giữ mức logic 1 ở trạng thái mặc định, phải mắc thêm 2 điện trở kéo lên Vcc (thường từ 1k – 4k7).

Mỗi bus I2C sẽ có 3 chế độ chính:

  • Một Master, nhiều Slave
  • Nhiều Master, nhiều Slave
  • Một Master, một Slave
H2
Một Master nhiều Slave
H3 1
Nhiều Master, nhiều Slave

Tại một thời điểm truyền nhận dữ liệu chỉ có một Master được hoạt động, điều khiển dây SCL và phát tín hiệu bắt đầu tới các Slave.

Tất cả các thiết bị đáp ứng sự điều khiển của Master gọi là Slave. Các Slave được phân biệt với nhau bằng 7 bit địa chỉ.

Cách truyền dữ liệu của giao thức I2C

Giao thức (phương thức giao tiếp) là cách các thiết bị đã thống nhất với nhau khi sử dụng một chuẩn nào đó để truyền và nhận tín hiệu.

Dữ liệu được truyền đi trên dây SDA được thực hiện như sau:

  1. Master thực hiện điều kiện bắt đầu I2C (Start Condition)
  2. Gửi địa chỉ 7 bit + 1 bit Đọc/Ghi (R/W) để báo muốn đọc hoặc ghi dữ liệu tại Slave có địa chỉ trên
  3. Nhận phản hồi từ bus, nếu có một bit ACK (Slave kéo SDA xuống thấp) thì Master sẽ tiếp tục truyền
  4. Nếu là đọc dữ liệu (R/W = 1), chân SDA của Master sẽ là input, đọc dữ liệu từ Slave gửi về. Nếu là ghi dữ liệu (R/W = 0), chân SDA sẽ là output, ghi dữ liệu vào Slave
  5. Truyền điều kiện kết thúc (Stop Condition)

Mỗi lần giao tiếp có cấu trúc như sau:

H4 2

Start condition (Điều kiện bắt đầu)

Bất cứ khi nào thiết bị Master quyết định bắt đầu một giao dịch, nó sẽ kéo SDA từ mức cao xuống mức thấp trong khi SCL vẫn đang ở mức cao, sau đó mới kéo SCL xuống.

Khi điều kiện bắt đầu được gửi bởi Master, tất cả các thiết bị Slave đều hoạt động, kể cả khi đang ở chế độ ngủ (sleep mode), và đợi bit địa chỉ.

H5 1

Bit Read/Write

Bit này xác định hướng truyền dữ liệu. Nếu Master cần gửi dữ liệu đến Slave, bit này được thiết lập là ‘0’. Nếu Master cần nhận dữ liệu từ Slave, bit này được thiết lập là ‘1’.

Bit ACK / NACK

ACK / NACK là viết tắt của Acknowledged / Not-Acknowledged. Nếu địa chỉ của một Slave nào đó trùng với địa chỉ Master phát ra, Slave đó sẽ kéo bit này về ‘0’. Ngược lại, nó vẫn ở mức logic ‘1’ (mặc định).

Khối dữ liệu

Khối dữ liệu gồm 8 bit, được bên gửi thiết lập với các bit dữ liệu cần truyền tới bên nhận. Theo sau khối này là một bit ACK / NACK, được bên nhận kéo về ‘0’ nếu nhận thành công dữ liệu. Ngược lại, nó vẫn ở mức logic ‘1’.

Cặp khối dữ liệu + bit ACK / NACK được lặp lại cho đến khi quá trình truyền dữ liệu hoàn tất.

Điều kiện kết thúc (Stop condition)

Sau khi các khung dữ liệu cần thiết được truyền qua đường SDA, Master thả SCL lên mức cao trước, sau đó kéo SDA từ mức thấp lên mức cao trong khi SCL đang ở mức cao.

H6 1

Giới thiệu chip thời gian thực DS3231

DS3231 là chip thời gian thực (RTC), giao tiếp thông qua giao thức I2C. Chip làm việc ở dải điện áp từ 2.3 đến 5.5V, tích hợp sẵn thạch anh bù nhiệt bên trong nên rất nhỏ gọn và chạy chính xác.

Có 2 bộ hẹn giờ (Alarm) có thể cấu hình từ giây tới ngày trong tháng.

Datasheet các bạn tải tại đây: https://datasheets.maximintegrated.com/en/ds/DS3231.pdf

DS3231 có địa chỉ 7 bit là 0x68. Cách đọc và ghi được mô tả như trong hình.

H7 8

Bảng sau mô tả địa chỉ lưu các giá trị thời gian, ngày tháng năm, đó là từ 0x00 tới 0x06.

Các byte từ 0x07 tới 0x0D lưu giá trị hẹn giờ Alarm 1 và Alarm 2.

Các byte từ 0x0E tới 0x12 là các thanh ghi điều khiển và trạng thái của DS3231.

H8 6

Cấu hình giao thức I2C trên STM32 CubeMX

Mở phần mềm, chọn chip STM32F103C8 nhấn Start Project.

Trong SYS chọn Debug: Serial Wire. Chi tiết trong Bài 3.

Trong tab Connectivity: chọn I2C1

Mode: I2C

Parameter Settings: để mặc định với Speed Mode là Standard, Clock Speed 100kHz.

Trong NVIC tick chọn bật ngắt cho I2C1 event interrupt.

H9 8

Cấu hình thêm PC13 là Led để kiểm tra trạng thái I2C.

H10 6

Đặt tên project rồi Gen code.

H11 6

Lập trình giao thức I2C

Trong Keil C chúng ta khai báo như sau.

Khai báo địa chỉ của DS3231 là 0x68<<1. Các bạn phải dịch trái 1 bit vì các hàm I2C của thư viện HAL yêu cầu vậy, bit thấp nhất là bit R/W sẽ được HAL tự thêm vào sau địa chỉ đó.

Lưu ý: Đây là lỗi rất hay gặp khi mới dùng I2C với HAL. Nếu truyền thẳng 0x68 không dịch bit thì HAL_I2C_IsDeviceReady() sẽ luôn trả về lỗi, các bạn sẽ tưởng là đấu dây sai hoặc module hỏng.

Khai báo 2 nguyên mẫu hàm là BCD2DEC và DEC2BCD. Hai hàm này có tác dụng chuyển đổi dữ liệu kiểu BCD thành số thập phân và ngược lại, vì dữ liệu đọc ghi vào DS3231 là kiểu BCD.

Hai mảng lưu dữ liệu truyền và nhận dạng BCD gồm 7 byte.

Một biến Status để đọc phản hồi của giao thức I2C.

Định nghĩa kiểu dữ liệu struct để lưu các giá trị thời gian, sau đó khởi tạo 2 biến TimeNow để đọc giá trị thời gian và TimeSet để cài đặt thời gian cho DS3231.

H12 5

Sau đó viết hàm thực thi chuyển đổi BCD2DEC và ngược lại.

H13 3

Các bạn tìm hàm Callback của giao thức I2C bằng cách đi từ stm32f1xx_it.c như các bài trước, hoặc sử dụng danh sách Functions như sau.

Mở tab Functions ở phía Project workspace, mở file I2C và tìm tới hàm:

HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c)

H14 3

Copy hàm đó và dán vào phía trên main(). Sau đó thêm các bước sau:

Kiểm tra bằng lệnh if(hi2c->Instance == I2C1) xem có phải ngắt của I2C1 không.

Lưu các giá trị thời gian vào biến DS3231_TimeNow, với byte đầu tiên là giây, sau đó là phút, giờ… Sử dụng hàm chuyển đổi BCD2DEC.

H15 1

Trong main(), đầu tiên chúng ta quét thử xem có thiết bị I2C nào kết nối không bằng cách:

Tắt Led PC13.

Cho hàm HAL_I2C_IsDeviceReady vào vòng lặp for, khi có kết nối sẽ lưu địa chỉ của Slave đó vào Status và bật Led PC13.

H16 1

Trước while(1) chúng ta sẽ ghi giá trị thời gian cho DS3231, hiện tại là 14h42 ngày 17 tháng 7 năm 2020. Ta sẽ ghi như sau, sau đó ghi vào I2C bằng hàm HAL_I2C_Mem_Write_IT(&hi2c1, DS3231_ADDRESS, 0x00, I2C_MEMADD_SIZE_8BIT, u8_tranBuffer, 7);

Với 0x00 là địa chỉ thanh ghi bắt đầu,

I2C_MEMADD_SIZE_8BIT: địa chỉ thanh ghi dài 8 bit

Gửi 7 byte: từ 0x00 –> 0x06

H17 1

Trong while(1) ta sẽ đọc dữ liệu từ DS3231 mỗi 5s một lần, ta làm như sau:

H18

Nhấn F7 để build và nạp chương trình. Kết nối module DS3231 với Blue Pill theo sơ đồ sau: SDA với PB7, SCL với PB6, VCC với 5V, GND với GND.

Sơ đồ đấu nối STM32 I2C với module RTC DS3231: PB6 nối SCL, PB7 nối SDA, 5V nối VCC, GND nối GND trên Blue Pill

Module ZS-042 đã có sẵn điện trở kéo lên cho SDA, SCL nên không cần mắc thêm. PB6, PB7 của STM32F103 là chân chịu được 5V (5V tolerant) nên cấp 5V cho module vẫn an toàn, cấp 3.3V cũng chạy được.

Sau đó nhấn vào Debug. Trong Debug, add 2 biến Time_now và Status vào Watch 1.

H19

Ta thấy Status hiển thị 0xD1, nghĩa là 11010001, chính là địa chỉ 0x68<<1 cộng với bit R/W = 1 (đọc dữ liệu).

Thời gian sẽ hiển thị đúng với giá trị ta nạp vào và thay đổi mỗi 5s một lần.

H20

Kết

Giao thức I2C rất dễ sử dụng, vì Slave luôn phản hồi lại Master bằng các bit ACK/NACK, vậy nên việc truyền sai địa chỉ, truyền sai dữ liệu rất dễ phát hiện.

Hi vọng bạn đã nắm rõ cách thức hoạt động của giao thức I2C, hãy chuyển tới bài tiếp theo để tiếp tục học nhé!

5/5 - (3 bình chọn)

8 những suy nghĩ trên “Bài 10: Giao thức I2C, lập trình STM32 với module RTC DS3231”

  1. Đào Duy Ngữ nói:

    Cho mình hỏi ở cái hàm HAl_I2C_MEM_READ_IT cái đoạn gửi địa chỉ 0x00 mà trong khi đó là dữ liệu bộ nhớ từ 0x00-0x06 thì chỉ cần đưa địa chỉ đầu tiên là 0x00 thôi à.

      • Thông nói:

        Hình như đoạn đó anh phải đọc dữ liệu từ slave ra vi xử lí, nên anh phải lưu vào buffer là hàm u8_revBuffer , vì hàm callback của anh , anh dùng u8_revBuffer ạ

    • Hoang nói:

      mình cũng có suy nghĩ giống bạn, u8_tranBuffer là mảng chứa data thời gian thiết lập, u8_revBuffer mới là mảng chứa thời gian trả về của IC, nên mình nghĩ là cần đọc data từ u8_revBuffer. có thể ad bị nhầm lẫn.

  2. Hoang nói:

    mình làm theo nhưng không chạy được, debug không thấy sự thay đổi của biến timenow. À mà mình thắc mắc tại sao biến timeset không có chức năng gì trong code ta?

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *