I2C in eCos on AT91SAM7 platforms

Published:

Using eCos on AT91SAM7 family processors has generally been a positive experience, however, the absence of some important functionalities can be somewhat of a problem. One of the most striking deficiencies of eCos is the lack of an interrupt-based driver for I2C, a popular bus used especially with various sensor chips. As we had to devise such a driver in one of our projects, we publish it below. The code can be used for the eCos userspace application on both SAM7X and SAM7S and, after some modification, can successfully be used seperately from the eCos platform.
The code is released on the BSD license. We appreciate any remarks!

First we have to declare a few variables – mutex and flag handles, and some buffer structures:

// rx_tx_struct 
typedef struct {
    cyg_int16 len;
    cyg_uint16 index;
     char *buffer;
} I2C_RX_TX_STRUCT;
I2C_RX_TX_STRUCT rx_data_struct, tx_data_struct;
// handles, flags, mutexes
cyg_flag_t rxrdy_flag;
cyg_flag_t txrdy_flag;
cyg_flag_t txcomp_flag;
cyg_flag_t done_flag;
cyg_handle_t  hIntr;
cyg_interrupt intr;
cyg_mutex_t twi_mutex;

Initialization of the I2C is quite tricky – there are some bugs in AT91SAM7 silicon (mentioned in the Atmel’s errata, with no proposed solution) – that is the reason for our “dirty” init code.

void i2c_initialize() {
        // initialize flags and mutex
        cyg_flag_init(&rxrdy_flag);
        cyg_flag_init(&txrdy_flag);
        cyg_flag_init(&txcomp_flag);
        cyg_flag_init(&done_flag);
        cyg_mutex_init(&twi_mutex);
        // there are bugs in the at91 i2c, trying to avoid it...
        HAL_ARM_AT91_GPIO_CFG_DIRECTION (AT91_TWI_TWD, AT91_PIN_IN);
        HAL_ARM_AT91_GPIO_CFG_DIRECTION (AT91_TWI_TWCK, AT91_PIN_IN);
        cyg_thread_delay(10);
        HAL_ARM_AT91_GPIO_CFG_DIRECTION (AT91_TWI_TWD, AT91_PIN_OUT);
        HAL_ARM_AT91_GPIO_CFG_DIRECTION (AT91_TWI_TWCK, AT91_PIN_OUT);
        HAL_ARM_AT91_GPIO_PUT(AT91_TWI_TWD,1);
        HAL_ARM_AT91_GPIO_PUT(AT91_TWI_TWCK,1);
        cyg_thread_delay(10);
        // finally configure the TWI
        HAL_ARM_AT91_PIO_CFG(AT91_TWI_TWD);
        HAL_ARM_AT91_PIO_CFG(AT91_TWI_TWCK);
        HAL_WRITE_UINT32(AT91_PMC + AT91_PMC_PCER, AT91_PMC_PCER_TWI);
        // disable interrupts
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IDR, 0xffffffff);
        // reset i2c
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_CR,AT91_TWI_CR_SWRST);
        // set mode to master
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_CR,AT91_TWI_CR_MSEN);
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_CWGR, (7<<16)|(255<<8)|(255));
        // prepare structs
        rx_data_struct.len = -1;
        tx_data_struct.len = -1;
        // create and attach interrupts
        cyg_interrupt_create(CYGNUM_HAL_INTERRUPT_TWI, 1, 
               (CYG_ADDRWORD)0, &i2cISR, &i2cDSR, &hIntr, &intr);
        cyg_interrupt_attach(hIntr);
        // configure interrupt; false, false = edge triggered, falling edge
        cyg_interrupt_configure(CYGNUM_HAL_INTERRUPT_TWI, false, false); 
        cyg_interrupt_unmask(CYGNUM_HAL_INTERRUPT_TWI);
}

ISR code (interrupt service routine) – as we can see it only tells the OS to launch DSR:

cyg_uint32 i2cISR(cyg_uint32 vector, CYG_ADDRWORD data) {
        cyg_interrupt_mask(vector);
        cyg_interrupt_acknowledge(vector);
        return (CYG_ISR_HANDLED | CYG_ISR_CALL_DSR);
}

DSR – Delayed service routine – handles the interrupt, fills in the buffers, etc.

void i2cDSR(cyg_uint32 vector, cyg_ucount32 count, CYG_ADDRWORD data) {
  cyg_uint32 sr, rx_data;
  HAL_READ_UINT32(AT91_TWI + AT91_TWI_SR, sr);
  if (rx_data_struct.len != -1) {
    if ((sr & AT91_TWI_RXRDY) == AT91_TWI_RXRDY) {
      HAL_READ_UINT32(AT91_TWI + AT91_TWI_RHR, rx_data);
      if (rx_data_struct.index < rx_data_struct.len) {
              rx_data_struct.buffer[rx_data_struct.index] = rx_data;
              rx_data_struct.index++;
      }
      if ((rx_data_struct.len > 1) && (rx_data_struct.index == rx_data_struct.len-1)) {
              HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_CR, AT91_TWI_CR_STOP);
      } else if (rx_data_struct.index == rx_data_struct.len) {
              cyg_flag_setbits(&(done_flag),1);
      }
    }
  } else if (tx_data_struct.len != -1) {
    if ((sr & AT91_TWI_TXRDY) == AT91_TWI_TXRDY) {
        if (tx_data_struct.index < tx_data_struct.len) {
                HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_THR, tx_data_struct.buffer[tx_data_struct.index]);
                tx_data_struct.index++;
        } else if (tx_data_struct.index == tx_data_struct.len) {
                cyg_flag_setbits(&(done_flag),1);
        }
    }
  }

  if ((sr & AT91_TWI_TXCOMP) == AT91_TWI_TXCOMP) {
          if ((tx_data_struct.len != -1) && (tx_data_struct.index == tx_data_struct.len)) {
                  cyg_flag_setbits(&(txcomp_flag), 1);
          }
          if ((rx_data_struct.len != -1) && (rx_data_struct.index == rx_data_struct.len)) {
                  cyg_flag_setbits(&(txcomp_flag), 1);
          }
  }

  cyg_interrupt_unmask(vector);
}

Function for reading registers:

cyg_uint32 i2c_getValue(cyg_uint32 device_addr, cyg_uint32 reg_addr, cyg_uint8 IADRSZ, char* buffer, int nbytes) {
  cyg_mutex_lock(&twi_mutex);

  HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_CR, AT91_TWI_CR_MSEN);
  HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_MMR, (device_addr << 16) | (0x1000) | (IADRSZ << 8));
  HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IADR,reg_addr);

  if (nbytes == 1)
          HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_CR, AT91_TWI_CR_START | AT91_TWI_CR_STOP);
  else
          HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_CR, AT91_TWI_CR_START);

  rx_data_struct.len = nbytes;
  rx_data_struct.buffer = buffer;
  rx_data_struct.index = 0;
  tx_data_struct.len = -1;

  cyg_flag_maskbits(&rxrdy_flag,0);
  cyg_flag_maskbits(&txcomp_flag,0);
  cyg_flag_maskbits(&done_flag,0);

  HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IER, AT91_TWI_RXRDY);
  cyg_flag_wait(&done_flag, 1, CYG_FLAG_WAITMODE_AND | CYG_FLAG_WAITMODE_CLR);
  HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IDR, AT91_TWI_RXRDY);
  HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IER, AT91_TWI_TXCOMP);
  cyg_flag_wait(&txcomp_flag, 1, CYG_FLAG_WAITMODE_AND | CYG_FLAG_WAITMODE_CLR);
  HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IDR, AT91_TWI_TXCOMP);

  cyg_mutex_unlock(&twi_mutex);
  return nbytes;
}

Function for setting registers:

void i2c_setValue(cyg_uint32 device_addr, cyg_uint32 reg_addr, cyg_uint8 IADRSZ, char *data, int nbytes) {
        if (nbytes == 0) return;

        cyg_mutex_lock(&twi_mutex);

        tx_data_struct.len = nbytes;
        tx_data_struct.buffer = data;
        tx_data_struct.index = 1;
        rx_data_struct.len = -1;
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_CR, AT91_TWI_CR_MSEN);
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_MMR, ((device_addr << 16) | (IADRSZ << 8)));
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IADR, reg_addr);

        cyg_flag_maskbits(&txrdy_flag,0);
        cyg_flag_maskbits(&done_flag,0);
        cyg_flag_maskbits(&txcomp_flag,0);

        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_THR, data[0]);
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IER, AT91_TWI_TXRDY);

        cyg_flag_wait(&done_flag, 1, CYG_FLAG_WAITMODE_AND | CYG_FLAG_WAITMODE_CLR);
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IDR, AT91_TWI_TXRDY);
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IER, AT91_TWI_TXCOMP);
        cyg_flag_wait(&txcomp_flag, 1,CYG_FLAG_WAITMODE_AND | CYG_FLAG_WAITMODE_CLR);
        HAL_WRITE_UINT32(AT91_TWI + AT91_TWI_IDR, AT91_TWI_TXCOMP);
        cyg_mutex_unlock(&twi_mutex);
}

And that’s about it. It may not look like a lot of code, but required some hours step-by-step comparisons of the code with the datasheets and further debugging to get it working properly. To our knowledge there should be no problems with the driver (we’ve tested it extensively and successfully used it in several solutions), but should you experience any abnormal behaviour, please comment so that perhaps we can remedy the problem.