AVR

ATtiny – Using the USI module as a I2C Slave

Xin chào, ở bài viết trước mình đã giới thiệu về Master Mode, bây giờ chúng ta sẽ tìm hiểu về cách sử dụng USI module ở Slave Mode.

Phần nguyên lý hoạt động của I2C mình đã giới thiệu ở bài viết này, các bạn có thể tham khảo lại nếu cần.


IMPLEMENTATION

Trong giao tiếp I2C, Master là thiết bị chủ động khởi tạo giao tiếp, còn Slave thì bị động nhận yêu cầu từ Master, do đó để kịp thời đáp ứng lại yêu cầu từ Master, chúng ta sẽ sử dụng Interrupt ở Slave Mode.

flowchart

Figure 1 – USI TWI Slave mode overview flowchart.

Khi START condition được phát hiện, USI Start Condition Interrupt được thực thi, tại đây chúng ta sẽ xóa Flags, enable USI Overflow Interrupt và cài đặt 4-bit Counter để bắt đầu nhận byte dữ liệu đầu tiên. Byte dữ liệu này chứa 7 bit địa chỉ của Slave và 1 bit Read/Write. Khi Slave nhận thấy địa chỉ trong gói dữ liệu trùng khớp với địa chỉ của mình, nó sẽ cài đặt lại 4-bit Counter để tiếp tục truyền/nhận dữ liệu. Quá trình này lặp lại cho đến khi STOP condition được gửi đến Slave.

Có 3 trạng thái trong giao tiếp gồm “Address Mode“, “Master Read Data Mode“, “Master Write Data Mode“.

ADDRESS MODE

Chế độ này chỉ xảy ra ở lần đầu tiên USI Overflow Interrupt được thực thi sau khi START detection được phát hiện. Ở chế độ này Slave sẽ nhận được 8 bit SLA+R/W từ Master, tùy theo địa chỉ Slave và Read/Write bit, giao tiếp sẽ được tiếp tục theo cách tương ứng.

MASTER READ MODE

Ở chế độ này Slave sẽ gửi dữ liệu đến Master. Khi Master đã nhận được gói dữ liệu, nó sẽ phản hồi (N)ACK.

MASTER WRITE MODE

Ở chế độ này Slave sẽ nhận dữ liệu từ Master. Khi dữ liệu được nhận thành công, Slave sẽ phản hồi (N)ACK đến Master. Sau đó Slave sẽ đợi xem có STOP condition được gửi đến hay không. Quá trình này lặp lại cho đến khi Slave nhận được STOP condition.

flowchartUSIslave

Figure 2 – Flowchart of the processes in the USI Overflow Interrupt.

Ở Slave Mode, các tiến trình xử lý đều được thực hiện bởi Interrupt, chúng ta chỉ sử dụng 1 function để khởi tạo và cài đặt USI module thành I2C Slave:

void USI_I2C_slave_init(uint8_t address).

Khởi tạo USI module/I2C Slave

Trong phần này chúng ta sẽ config cho chân SDA thành INPUT, SCL thành OUTPUT. START condition Interrupt Enable; USIWM1:0 = 11; Shift Register Clock Source: External, positive edge; 4-bit Counter Clock Source: External, Both Edges.

void USI_I2C_slave_init(uint8_t address) {
  slaveAddress = address;
  // set SDA as INPUT and SCL as OUTPUT
  USI_DDR &= ~(1 << USI_SDA);
  USI_DDR |= (1 << USI_SCL);
  USI_PORT |= (1 << USI_SCL) | (1 << USI_SDA);

  // START condition Interrupt Enable; USIWM1:0 = 11;
  // Shift Register Clock Source: External, positive edge;
  // 4-bit Counter Clock Source: External, Both Edges.
  USICR = (1 << USISIE) | (1 << USIWM1) | (1 << USIWM0) | (1 << USICS1);

  // clear Interrupt Flags and Counter.
  USISR = (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) | (1 <<< USIDC);

  sei();

  comMode = NONE;
}

Interrupt Service Routines (ISR)

Thuật toán trong các ISR này đều được mô tả ở Figure 2.

  • USI_START_vect: tại phần này chúng ta sẽ config SDA thành INPUT, mode=ADDRESS_MODE và đợi START condition để enable USI Overflow Interrupt.
ISR(USI_START_vect) {

  // set SDA as INPUT
  USI_DDR &= ~(1 << USI_SDA);

  comMode = ADDRESS_MODE;
  recCount = 0;
  tranCount = 0;
  flushRecBuffer();

  while ((USI_PIN & (1 << USI_SCL)) && !(USI_PIN & (1 << USI_SDA)));

  // if STOP condition occurred
  if (USI_PIN & (1 << USI_SDA)) {
    //USART_print("STOP detect.");
    USICR = (1 << USISIE) | (1 << USIWM1) | (1 << USICS1);
    comState = STOP;
  }
  else {
    USART_print("START detect.");
    comState = START;
    USICR = (1 << USISIE) | (1 << USIOIE) | (1 << USIWM1) | (1 << USIWM0) | (1 << USICS1);
  }
  // clear Flags and set 4-bit Counter for 8-bit SLA+R/W
  USISR = (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) | (1 << USIDC);
  USIDR = 0x00;
}
  • USI_OVERFLOW_vect: tại phần này, dựa vào các chế độ hoạt động chúng ta sẽ cài đặt 4-bit Counter và các registers cho phù hợp.
ISR(USI_OVERFLOW_vect) {
  switch (comMode) {
    case ADDRESS_MODE:
                    if ((USIDR >> 1) == slaveAddress) {
                      if (USIDR & 0x01)
                        comMode = MASTER_READ_DATA;
                      else
                        comMode = MASTER_WRITE_DATA;

                      // send ACK to Master
                      SEND_ACK()
                    }
                    // if Address not matched, initialize START condition mode
                    else
                      INIT_START_CONDITION_MODE();

                    USIDR = 0x00;
                    break;

    case MASTER_WRITE_DATA:
                    comMode = SLAVE_RECEIVE_DATA;
                    INIT_DATA_RECEPTION();
                    // STOP condition detector
                    // wait for SDA HIGH
                    while (!(USI_PIN & (1 << USI_SDA)));
                    // if STOP condition
                    if (USI_PIN & (1 << USI_SCL)) {
                      INIT_START_CONDITION_MODE();
                      comState = STOP;
                    }
                    break;

    case SLAVE_RECEIVE_DATA:
                    comMode = MASTER_WRITE_DATA;
                    if (recCount < BUFFERSIZE) {
                      recBuffer[recCount++] = USIDR;
                      // send ACK to Master
                      SEND_ACK();
                    }
                    else {
                      SEND_NACK();
                    }
                    break;

    case MASTER_READ_DATA:
                    // if NACK
                    if (USIDR) {
                      INIT_START_CONDITION_MODE();
                    }
                    else {
                      comMode = SLAVE_TRANSMIT_DATA;
                      if (tranCount < BUFFERSIZE) {
                        USIDR = tranBuffer[tranCount];
                        INIT_DATA_TRANSMITTION();
                      }
                    }
                    break;

    case SLAVE_TRANSMIT_DATA:
                    comMode = MASTER_READ_DATA;
                    RECEIVE_ACK();
                    break;
  }
}

Trên đây chỉ là một phần sourcecode mình dùng để mô tả cách hoạt động của USI module ở I2C Slave Mode. Các bạn có thể clone sourcecode 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.

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