Giao tiếp I2C trên STM32F103 với module RTC DS3231

I2C – Inter- Intergrated Circuit là chuẩn truyền thông nối tiếp gồm 2 dây Clock và  Data được phát minh bởi hãng Philips. Chuẩn I2C trở nên thông dụng với nhiều module và IC sử dụng rộng rãi như: IC nhớ (24Cxxx), cảm biến góc nghiêng(MPU6050), module giao tiếp LCD (dùng IC PCF8574), IC thời gian thực (DS1307, DS3231, BQ32000), IC chuyển đổi tín hiệu số, tương tự… Chuẩn giao tiếp này hỗ trợ giao tiếp ở tốc độ 100Khz hoặc 400Khz. 

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

STM32F103C8T6 có 2 bộ chuyển đổi I2C với các tính năng được tóm tắt như sau:

  • Có thể lập trình là Master hay Slave.
  • Đối với Master: Tạo ra xung clock và tạo ra tín hiệu start, stop.
  • Đối với Slave : Lập trình được địa chỉ của thiết bị I2C, chế độ kiểm tra bit stop.
  • Số địa chỉ được sinh ra cũng như được kiểm tra là 7 bit hoặc 10 bit.
  • Hỗ trợ 2 chuẩn tốc độ là 100khz và 400 Khz.
  • Có bộ lọc nhiễu Analog.
  • Tích hợp mode DMA.
  • Có các cờ báo trạng thái : Nhận, truyền, kết thúc chuyển đổi, báo lỗi…
  • Có các ngắt như: Ngắt buffer truyền, nhận; ngắt sự kiện, ngắt báo lỗi.

Quá trình truyền data tùy thuộc vào modde cấu hình của I2C là master hoặc slave, ở chế độ 10 bit địa chỉ hay 7 bit địa chỉ (phổ biến hơn). Dưới đây là ví dụ về cấu hình I2C là master:

Tóm tắt quá trình truyền như sau:

  • Đầu tiên bộ I2C sẽ tạo ra tín hiệu start đến EV5: SB = 1 (tạo ra điều kiện bắt đầu trên thanh ghi SR1)
  • Sau đó gửi địa chỉ và hướng truyền
  • Đợi bit ACK từ Slave truyền cho Master 
  • Tiếp tục kiểm tra EV6: ADDR = 1(Master sẽ gởi địa chỉ của Slave và Slave nhận đúng chính xác địa chỉ của mình do Master gởi) – EV8.1: TxE=1 (hiện tại thì thanh ghi dịch và thanh ghi dữ liệu hiện đang trống) 
  • Kiểm tra EV8 (lúc này dữ liệu thanh ghi dịch đã có và bắt đầu truyền đến Slave)
  • Gửi Data đến thiết bị slave với địa chỉ truyền ở trên
  • Nếu Slave nhận được dữ liệu sẽ trả về bit ACK cho Master để xác nhận đã dữ liệu
  • Kiểm tra EV8_2 – tương ứng code là kiểm tra toàn bộ Data đã truyền xong chưa (TXE, BTF)
  • Đợi truyền xong và bộ I2C sẽ tạo ra tín hiệu kết thúc.

Trên đây là tóm lược về quá trình truyền, nhận. Các bạn có thể tham khảo trong Reference Manual, mục I2C – Function description.

* Một vài thanh ghi quan trọng.

        1.   I2C_CR1 – I2C control register 1.

  • ACK : Cho phép nhận hoặc không nhận tín hiệu ACK return từ thiết bị được truyền.
  • STOP : Sinh ra tín hiệu stop kết thúc quá trình giao tiếp.
  • START : Sinh ra tín hiệu start để bắt đầu quá trình giao tiếp.
  • PE: Peripheral enable – bit này được bật lên bằng 1 khi quá trình giao tiếp I2C đang được thực hiện và kết thúc khi có tín hiện End communication.

2. I2C_CR1 – I2C control register 1.

  • ITBUFEN : Cho phép hoặc không cho phép xảy ra ngắt khi có Data trong truyền/nhận.
  • ITEVTEN : Cho phép ngắt khi xảy ra các sự kiện sau trên các bit sau: SB, ADDR, ADD10, STOPF, BTF , TxE, RxE.
  • ITERREN : Cho phép hoặc không cho phép xảy ra ngắt khi có lỗi xảy ra.
  • FREQ[5:0] : Bộ chia clock tần số được tính từ nhánh clock hệ thống chia cho bộ I2C.

      3. I2C_OAR1 – I2C own address register 1.

  • ADDMODE : Cài đặt số bit địa chỉ của slave là 7 hay 10 bit.
  • ADD[9:8] : Chỉ sử dụng khi mode địa chỉ là 10 bit.
  • ADD[7:1] : Bit địa chỉ của thiết bị slave.
  • ADD0 : Chỉ sử dụng khi mode địa chỉ là 10 bit.

     4. I2C_DR – I2C data register.

Thanh ghi này gồm 8 bit, chứa data của quá trình truyền hoặc nhận.

       5. I2C_SR1 – I2C status register 1:

Thanh ghi này chứa các cờ báo trạng thái của quá trình giao tiếp I2C:
  • TIME OUT: Báo hết thời gian đợi quá trình truyền nhận data.
  • PECERR: Chấp nhận lỗi PCE và phản hồi lại quá trình tín hiệu lỗi.
  • OVR : Báo quá trình overrun/ underrun.
  • AF: Báo tín hiệu ACK bị lỗi hay không.
  • TxE : Báo buffer truyền có trống hay không.
  • RxE : Báo buffer nhận có trống hay không.
  • STOPF : Cờ báo có kiểm tra quá trình stop hay không.
  • BTF: Báo truyền byte dữ liệu đã xong hay chưa.
  • ADDR: Báo địa chỉ đã được gửi(mode master) hay không tương thích (mode slave).
  • SB: Có sử dụng bit start hay không.

       6.  I2C_CCR – I2C clock control register:

  • F/S : Sử dụng mode Fast(400khz) hay Standard(100Khz).
  • DUTY: Chỉ sử dụng cho Fast mode với 2 chế độ .
  • CCR[11:0] : Thanh ghi chứa Clock control cho bộ , công thức chia tham khảo thêm trong reference manual.

Tiếp theo bài viết sẽ hướng dẫn ví dụ thực hành sử dụng board STM32F103C8T6 để giao tiếp với module DS3231 qua giao thức I2C.

Ở bài này mình sẽ sẽ dùng 2 ngoại vi:

                + I2C: Giao tiếp với module DS3231

                + UART: Sử dụng để retarget/redirect hàm printf để in ra màn hình.

 Các bạn nào chưa hiểu về UART hay retarget/redirect có thể đọc lại các bài viết sau:

Đầu tiên các bạn mở CubeMX và thực hiện các bước cấu hình như sau để sinh code.

Bước 1:

Đầu tiên, chúng ta sẽ cấu hình chân nạp dữ liệu SWD.

Bước 2:

Sử dụng UART1 với mục đích in dữ liệu thời gian thực lên phần mềm Hercules. Chỉnh sửa lại tốc độ baud là 9600 Bits/s

Bước 3:

Tiếp chúng ta bật chức năng I2C

Bước 4:

Ở phần cấu hình I2C, chúng ta giữ mặc định:

  • I2C Speed Mode: Standard mode
  • I2C Clock Speed: 100000Hz

Bước 5:

Tiếp theo, Click vào tab NVIC Settings để bật ngắt I2C khi có sự kiện xảy ra hoặc có lỗi đường truyền xảy ra.

Bước 6:

Chuyển sang tab NVIC Settings để bật ngắt UART

Bước 7:

Đến đây, các bạn Setting project của mình sau đó sinh code từ CubeMX

Bước 8:

 Copy đoạn code retarget/redirect hàm printf để sử dụng cho UART. 

Bước 9:

  • Ở hình dưới là các thanh ghi bên trong con chip DS3231, ở trong bài viết này các bạn chỉ quan tâm đến địa chỉ thanh ghi từ 0x00->0x06
  • Ở địa chỉ thanh ghi:

         – 0x00: Chứa dữ liệu về giây. Thanh ghi ứng với địa chỉ 0x00 này sử dụng 8 bit chính, từ 0-3 cho hàng đơn vị và từ 4-6 cho hàng chục, giá trị của giây đếm từ 0-59, sau khi đếm đến 59 sẽ bị reset về 0.

         – 0x01: Chứa dữ liệu về phút. Thanh ghi ứng với địa chỉ 0x01 này sử dụng 4 bit chính từ 0-3 cho hàng đơn vị và 4-6 cho hàng chục, giá trị của phút từ 0-59, sau khi đếm đến 59 sẽ bị reset về 0 

         – 0x02: Chứa dữ liệu về giờ. Thanh ghi ứng với địa chỉ 0x02 sử dụng 8 bit chính, từ 0-3 cho hàng đơn vị và từ 4-5 cho hàng chục, giá trị của giờ từ 0-12 tùy thuộc vào việc set bit 6 trong thanh ghi, thông thường mình sẽ để nguyên thanh ghi chỉ đọc giá trị giờ ra thì giá trị của giờ sẽ từ 0-23, sẽ bị reset về 0 nếu qua 23h

         – 0x03: Chứa dữ liệu về thứ trong tuần (daysofweek). Thanh ghi ứng với địa chỉ 0x03 sử dụng 3 bit chính, từ 0-3 cho hàng đơn vị, giá trị thứ trong tuần từ 0-7 tương ứng 0 là thứ 2, 1 là thứ 3…

         – 0x04: Chứa dữ liệu về ngày. Thanh ghi ứng với địa chỉ 0x04 sẽ sử dụng 8 bit chính, từ 0-3 cho hàng đơn vị của ngày và bit 4-5 cho hàng chục, giá trị ngày từ 0-31.

         – 0x05: Chứa dữ liệu về tháng. Thanh ghi ứng với địa chỉ 0x05 sẽ sử dụng 8bit chính, từ 0-3 cho hàng đon vị và bit 4 cho hàng chục, giá trị của tháng là 0-12.

         – 0x06: Chứa dữ liệu về giờ. Thanh ghi ứng với địa chỉ 0x06 sử dụng 8bit chính, từ 0-3 cho hàng đơn vị và 4-7 cho hàng chục, giá trị của năm là 0-99.

 

Bước 10:

  • Sau khi đã biết được ý nghĩa của từng thanh ghi ứng với địa chỉ của nó. Chúng ta tiếp tục qua project KeilC để định nghĩa một số thanh ghi địa chỉ mà chúng ta sử dụng.
  • Ở bài viết này bạn chỉ cần 2 define:

     #define DS3231_ADRESS (0x68<<1)

     #define DS3231_REG_TIME 0x00

Sau đó bạn hãy khai báo 1 struct để phục vụ cho việc đọc ghi dữ liệu thời gian thực. Sau khi tạo xong struct thì chúng ta sẽ khai báo luôn.

DS3231_t DS3231;

Bước 11:

Vì dữ liệu ở trong thanh ghi của DS3231 là mã BCD (Binary code decimal) vì vậy chúng ta phải chuyển sang mã DEC (Decimal). Tiếp theo chúng ta sẽ viết 2 hàm, hàm thứ nhất là chuyển đổi mã BCD sang mã DEC mục đích để khi đọc dữ liệu từ DS3231 chúng ta sẽ chuyển sang hệ 10, hàm thứ 2 là chuyển mã DEC sang mã BCD mục đích để khi cấu hình thời gian ban đầu chúng ta sẽ chuyển đổi thời gian từ mã sang mã nhị phân để thực hiện động tác ghi vào thanh ghi.

Bước 12:

  • Để thực hiện việc đọc và ghi dữ liệu vào thanh ghi của DS3231

            + void I2C_WriteBuffer(I2C_HandleTypeDef hi, uint8_t DEV_ADDR, uint8_t sizebuf)

            + void I2C_ReadBuffer(I2C_HandleTypeDef hi, uint8_t DEV_ADDR, uint8_t sizebuf)

  • Thông số truyền vào:

            + I2C_HandleTypeDef hi: I2C hiện tại bạn đang sử dụng

            + DEV_ADDR: Địa chỉ thanh ghi bắt đầu ghi dữ liệu

             + Sizebuf: Size dữ liệu mà bạn ghi vào bộ nhớ

Hàm truyền I2C đến địa chỉ DS323, dữ liệu truyền đi nằm trong bộ đệm DS3231.I2C_Buffer

HAL_I2C_Master_Transmit(&hi, (uint16_t) DEV_ADDR, (uint8_t*) &DS3231.I2C_Buffer, (uint16_t) sizebuf, (uint32_t)1000);

Hàm đọc I2C từ địa chỉ DS3231, dữ liệu đọc được nằm trong bộ đệm DS3231.I2C_Buffer

HAL_I2C_Master_Receive(&hi, (uint16_t) DEV_ADDR, (uint8_t*) &DS3231.I2C_Buffer, (uint16_t) sizebuf, (uint32_t)1000)

Bước 13:

Tiếp theo chúng ta sẽ thực hiện hàm đọc dữ liệu, để bắt đầu đọc dữ liệu thì đầu tiên chúng ta sẽ ghi vào phần tử đầu tiên bộ đệm I2C_Buffer giá trị 0x00 sau đó truyền đến địa chỉ DS3231 để bắt đầu thực hiện quá trình  đọc/ghi dữ liệu. Đợi cho trạng thái của I2C sẵn sàng rồi mới đọc dữ liệu. Vì dữ liệu đọc được ở mã BCD nên chúng ta sẽ chuyển sáng mã DEC bằng hàm RTC_BCD2DEC();

Bước 14:

Tiếp theo là hàm cài đặt dữ liệu thời gian thực, để ghi vào bộ nhớ DS3231 đầu tiên các bạn hãy ghi 0x00 vào phần tử đầu tiên của bộ đệm để bắt đầu quá trình ghi, sau đó ghi dữ liệu cần cấu hình.

Bước 15:

Tiếp theo chúng ta sẽ cấu hình giờ ban đầu cho DS3231 và ở trong vòng while(1) của hàm main mình đã đã dữ liệu thời gian thực 1s 1 lần và sử dụng hàm printf để in lên trên màn hình Hercules.

Bước 16:

Mở phần mềm Hercules lên và quan sát dữ liệu.

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

Xem thêm: Tổng hợp các bài hướng dẫn Lập trình vi điều khiển STM32 tại đây.

Nhóm TAPIT ARM R&D