AVR

ATtiny – Using the USI module as a I2C Master

Xin chào,

ATtiny là dòng MCU siêu nhỏ và rẻ, nó thường được sử dụng trong các dự án đơn giản và không cần hiệu năng cao để tiết kiệm chi phí. Ví dụ nếu chúng ta muốn truyền dữ liệu từ cảm biến nhiệt độ về trung tâm điều khiển, một MCU ATtiny85 và một module nRF24L01+ là quá đủ dùng và tiết kiệm so với việc dùng cả board Arduino UNO (Nano, Micro,…).

Tuy nhiên, khi tham khảo Datasheet, ta thấy rằng các MCU dòng ATtiny không hỗ trợ module SPI và module TWI như trên dòng ATmega, thay vào đó là module USI có thể được cấu hình để thực hiện giao tiếp SPI và I2C.

Trong giới hạn bài viết này mình sẽ giới thiệu cách sử dụng module USI ở chế độ I2C Master, chế độ I2C Slave sẽ được giới thiệu ở bài tiếp theo.


INTRODUCTION

Universal Serial Interface (USI) module trên các MCU như ATtiny85, ATtiny2313 có thể hoạt động ở chế độ Two-wire (I2C). USI cung cấp các hỗ trợ về phần cứng cơ bản cần cho giao tiếp đồng bộ nối tiếp (Synchronous Serial Communication). Kết hợp với control software, USI cho phép giao tiếp ở cả Standard Mode (<100kbps) và Fast Mode (<400kbps).

Trong bài viết này, chúng ta sẽ thực hiện giao tiếp I2C (Master modes) ở Standard Mode và không sử dụng Interrupt và Timer.


UNIVERSAL SERIAL INTERFACE – USI

Trước tiên, chúng ta cần hiểu rõ về giao tiếp I2C, các bạn tham khảo bài viết về TWI/ I2C của mình nhé.

Trong giao tiếp I2C, Thiết bị Master luôn luôn điều khiển xung clock. Với module USI, xung clock được tạo bởi software bằng cách điều khiển USCK pin, còn việc truyền nhận thì được thực hiện bởi hardware. Chú ý rằng việc truyền nhận chỉ được thực hiện ở negative edges của xung clock.

Vì clock làm tăng giá trị đếm của 4-bit Counter, counter-overflow có thể được sử dụng để chỉ ra rằng quá trình truyền nhận đã hoàn thành.

Chúng ta sẽ nói về các bước của giao tiếp I2C một lần nữa:

I2Cdiagram

Figure 1 – Two-wire Mode, Typical Timing Diagram

  1. START condition được tạo bởi Master bằng cách kéo SDA line xuống mức LOW trong khi giữ SCL line ở mức HIGH (A).
  2. Start detector của Slave sẽ giữ SCL line ở mức LOW sau khi Master đã kéo nó xuống LOW (B). Điều này cho phép Slave “wake-up from sleep” hoặc hoàn thành các tác vụ khác trước khi setting up USI Data Register để nhận địa chỉ sẽ được gửi từ Master.
  3. Master chuẩn bị dữ liệu để gửi và release SCL line về mức HIGH (C). Slave nhận dữ liệu và shift vào USIDR tại positive edge của SCL clock.
  4. Sau khi 8 bits SLA+R/W được gửi đi, Slave counter overflows và SCL line bị kéo xuống mức LOW (D). Nếu Slave không phải là thiết bị mà Master yêu cầu, nó sẽ đưa SCL về mức HIGH và đợi một START condition mới.
  5. Khi Slave nhận ra nó là thiết bị mà Master yêu cầu, nó sẽ giữ SDA line ở mức LOW để gửi ACK, Slave có thể giữ SCL line ở mức LOW sau khi đã gửi ACK (E).
  6. Các byte dữ liệu có thể được truyền nhận (Read hoặc Write) cho đến khi STOP condition được gửi bởi Master (F).

REGISTER DESCRIPTION

Module USI có các registers sau:

1 – USI Control Register (USICR) – dùng để Enable Interrupt, chọn Wire Mode, chọn Clock Source và Toggle Clock Port Pin.

USICR

Figure 2 – USI Control Register

  • Bit 7 – USISIE: Start Condition Interrupt Enable – Được sử dụng ở Slave Mode.
  • Bit 6 – USIOIE: Counter Overflow Interrupt Enable – Được sử dụng ở Slave Mode.
  • Bit 5:4 – USIWM1:0: Wire Mode – Chúng ta sẽ chọn mode USIWM1:USIWM0 = 10.
  • Bit 3:2 – USICS1:0: Clock Source Select – Chọn Clock Source cho USI Data Register và 4-bit Counter. Chúng ta sẽ chọn External, positive edge và Software clock strobe (USITC). Ở mode này, clock sẽ được tạo từ SCL pin bằng cách ghi ‘1’ vào bit USITC. The output is changed at the opposite edge of the sampling of the data input.
  • Bit 1 – USICLK: Clock Strobe –  Sử dụng cùng với các bit 3:2 để chọn Source Clock.
  • Bit 0 – USITC: Toggle Clock Port Pin – ghi ‘1’ vào bit này sẽ toggle SCL pin và tăng giá trị đếm của 4-bit Counter lên 1 đơn vị khi sử dụng External Source Clock và Software clock strobe. Điều này cho phép sớm phát hiện khi nào transfer hoàn thành ở Master Mode.

2 – USI Status Register (USISR) – chứa 4 Flags và 4-bit counter.

  • Bit 7:4 – Do ở Master Mode không sử dụng các Flags này nên chúng ta sẽ tìm hiểu nó ở bài tiếp theo khi làm việc ở Slave Mode.
  • Bit 3:0 – USICNT3:0: Counter Value – giá trị của Counter được tăng lên bởi External Clock Edge, hoặc Timer/Counter0 Compare Match hoặc bởi software sử dụng USICLK/USITC strobe bits.

3 – USI Data Register (USIDR) chứa incoming và outgoing data.


IMPLEMENTATION

Để thực hiện giao tiếp I2C ở Master Mode, chúng ta sẽ viết các functions sau:

  • Khởi tạo USI module thành I2C Master: void USI_I2C_master_init().
  • Bắt đầu giao tiếp: uint8_t USI_I2C_master_start(uint8_t address, uint8_t mode).
  • Gửi dữ liệu đến Slave: uint8_t USI_I2C_master_transmit(uint8_t data).
  • Nhận dữ liệu từ Slave: uint8_t USI_I2C_master_receive(uint8_t mode).
  • Thực hiện shift dữ liệu ở USIDR: uint8_t USI_I2C_master_transfer(uint8_t mode).
  • Kết thúc transmission: void USI_I2C_master_stop().

Các function này đều được thực hiện theo các Flowcharts sau:

diagramTransfer

Figure 3 – Transfer Flowchart

diagramTransceiver

Figure 4 – Transmit/Receive Flowchart

Khởi tạo USI module thành I2C Master

Trong phần khởi tạo USI module as I2C Master, chúng ta sẽ cấu hình 2 chân SDA và SCL là OUTPUT ở mức HIGH. Sau đó là cấu hình USICR thành External, positive edge + software clock strobe (USITC) và xóa các Flags trong USISR.

void USI_I2C_master_init() {
    USI_DDR |= (1 << USI_SCL) | (1 << USI_SDA);
    USI_PORT |= (1 << USI_SCL) | (1 << USI_SDA);
    // External, positive edge; software clock strobe (USITC)
    USICR = (1 << USIWM1) | (1 << USICS1) | (1 << USICLK);
    // clear Interrupt Flags and Counter.
    USISR = (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) | (1 << USIDC);
    USIDR = 0xFF;
}

Transfer dữ liệu

Đây là phần quan trọng nhất trong giao tiếp I2C, giúp trao đổi dữ liệu giữa Master và Slave. Function này có 1 parameter: mode – USI_ACK và USI_DATA.

Tham khảo Figure 3.

uint8_t USI_I2C_master_transfer(uint8_t mode) {
    // Respond ACK, NACK
    if (mode == USI_ACK)
        // 1 bit = 2 edges = 2 counts
        USISR = 0xFE;
    // transmit Data
    else if (mode == USI_DATA)
        // 4 bit = 16 edges = 16 counts
        USISR = 0xF0;

    // Data transmission
    do {
        // Positive SCL edge
        USICR |= (1 << USITC);
        // wait for SCL go HIGH
        while (!(USI_PORT & (1 << USI_SCL)));
        // wait a TWI HIGH PERIOD
        _delay_us(HIGH_PERIOD);

        // Negative SCL edge
        USICR |= (1 << USITC);
        // wait a TWI LOW PERIOD
        _delay_us(LOW_PERIOD);
    } while (!(USISR & (1 << USIOIF)));
    // store data
    uint8_t data = USIDR;		

    // Pre-load data register with "released level" data.
    USIDR = 0xFF;
    // Release SDA and set as OUTPUT
    releaseSDA();
    USI_DDR |= (1 << USI_SDA);	

    return data;
}

Bắt đầu giao tiếp

Để bắt đầu giao tiếp, chúng ta sẽ gửi START condition, tiếp đến là SLA+R/W và đợi ACK từ Slave. Nếu ACK = 0 thì Slave đã sẵn sàng. Function này có 2 parameters: address – địa chỉ Slave; mode – USI_READ và USI_WRITE.

Tham khảo Figure 1 và 4.

uint8_t USI_I2C_master_start(uint8_t address, uint8_t mode) {
    // release and wait for SCL, SDA to HIGH
    releaseSCL();
    while (!(USI_PORT & (1 << USI_SCL)));
    releaseSDA();
    while (!(USI_PORT & (1 << USI_SDA)));

    // generate START
    pullSDA();
    _delay_us(LOW_PERIOD);
    pullSCL();
    _delay_us(HIGH_PERIOD);
    releaseSDA();

    // transmit ADDRESS
    pullSCL();	// pull SCL to LOW
    // mode = 1 - READ, 0 - WRITE, load SLA+W to USIDR
    USIDR = (address << 1) + mode;
    // transfer SLA+W
    USI_I2C_master_transfer(USI_DATA);
    // set SDA as input to read ACK
    USI_DDR &= ~(1 << USI_SDA);
    // read ACK from SLAVE
    if (USI_I2C_master_transfer(USI_ACK) & 0x01)
        return 1;
    return 0;
}

Gửi một byte dữ liệu đến Slave

Để gửi dữ liệu đến Slave, chúng ta cần copy dữ liệu vào USIDR, shift dữ liệu với function USI_I2C_master_transfer(), sau đó đọc ACK từ Slave.

Tham khảo Figure 4.

uint8_t USI_I2C_master_transmit(uint8_t data) {
    // pull SCL to LOW
    pullSCL();
    // load SLA+W to USIDR
    USIDR = data;
    // transfer data
    USI_I2C_master_transfer(USI_DATA);
    // set SDA as input
    USI_DDR &= ~(1 << USI_SDA);
    // read ACK from SLAVE
    if (USI_I2C_master_transfer(USI_ACK) & 0x01)
        return 1;
    return 0;
}

Nhận một byte dữ liệu từ Slave

Để nhận dữ liệu từ Slave, đầu tiên chúng ta cần cấu hình SDA là INPUT, sau đó shift dữ liệu với function USI_I2C_master_transfer() và gửi (N)ACK từ đến Slave. Function này có một parameter: mode – NOTLAST và LAST.

Tham khảo Figure 4.

uint8_t USI_I2C_master_receive(uint8_t mode) {
    // set SDA as INPUT
    USI_DDR &= ~(1 << USI_SDA);
    // read a byte
    uint8_t data = USI_I2C_master_transfer(USI_DATA);
    // Prepare a ACK
    if (mode == NOTLAST)
        USIDR = 0x7F;
    else
        USIDR = 0xFF;
    // Transmit (N)ACK to Slave
    USI_I2C_master_transfer(USI_ACK);
    return data;
}

Kết thúc transmission

Để kết thúc một transmission chúng ta chỉ cần gửi STOP condition đến Slave.
Tham khảo Figure 1

void USI_I2C_master_stop() {
    // pull SDA,release and wait for SCL to HIGH
    pullSDA();
    releaseSCL();
    while (!(USI_PORT & (1 << USI_SCL)));

    // generate STOP
    _delay_us(LOW_PERIOD);
    releaseSDA();
    _delay_us(HIGH_PERIOD);
}

CONCLUSION

Như vậy chúng ta đã tìm hiểu về cách sử dụng USI module ở Master Mode. Toàn bộ Source code các bạn có thể Clone từ Github của mình.

Cảm ơn các bạn đã theo dõi bài viết. Thân ái và quyết thắng.

Reference:
[1] USITWIX project.
[2] Atmel AVR310: Using the USI Module as a I2C Master.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s