Arduino Due: TWI/I2C communication using TC - timer counter

Hello!

I'm trying to create a TWI/I2C communication between two Arduino Due Cards, the master uses the Atmel library and the slave uses the Arduino IDE library. I'm using ports SDA20 and SCL21 on both cards and they share the same ground.

I can read and write when using a project that has not included the ”TC - timer counter”, but it doesn't work if I do include it. And if try to remove the "TC-timer counter" it will still not work. The master should send one byte and receive 10 from the slave. The problem is that it stays in this loop in twi.c and in the function twi_master_write and nothing happens:

while (1)
{
status = p_twi->TWI_SR;
if (status & TWI_SR_NACK)
{
return TWI_RECEIVE_NACK;
}

if (status & TWI_SR_TXRDY)
{
break;
}
}

I have attached the master(without the ASF it was too large) and the slave.

Can you see what the problem is, please?

Thanks in advance!

TWI_Slave.zip (1.53 KB)

TWI_Master.zip (16.5 KB)

Here's the TWI-code.

twi.c (18.3 KB)

twi.h (11.1 KB)

I don't know why it stays in that while-loop and I don't the Atmel library. But I can tell how to make the Slave code better.

Use the "volatile" keyword for variables that are used both in the interrupts and in the loop(). For example 'state' and 'automatic' and maybe more.

Don't use Serial functions in the interrupt routines receiveHandler() and requestHandler(). The Serial functions use interrupts themself. That means it might work until something changes and then it suddenly goes wrong.

Don't use this in the receiveHandler:

while(!Wire.available()) {
  }

Test in the receiveHandler if the number of received bytes is correct. Some users on this forum tell that the arduino Due sometimes receives a false interrupt with zero bytes.

void receiveHandler(int mBytes) {
  if( mBytes == 1) {
    byte newMessage = Wire.read();
    if (automatic) {
      ...

I don't know if the outputBuffer is handled correctly.

I'm using ports SDA20 and SCL21 on both cards This can't work since SDA/SCL have pull-ups (on both cards), you want to connect SDA/SCL to SDA1/SCL1(SDA1/SCL1 don't have pull-ups).

ard_newbie:
I'm using ports SDA20 and SCL21 on both cards This can't work since SDA/SCL have pull-ups (on both cards), you want to connect SDA/SCL to SDA1/SCL1(SDA1/SCL1 don't have pull-ups).

Ok thanks, SDA20 and SCL21 on the slave? Then SDA1 and SCL1 on the master?
What do I have to change in the code?

The Due has pullup resistors of 1k5 from SDA to 3.3V and from SCL tot 3.3V. The value of 1k5 is a low value, they should have chosen 4k7 or 10k.

When you connect both Arduino boards, it is 1k5 // 1k5 = 750 Ω combined.
The pulldown current is 3.3 V / 750 Ω = 4.4 mA.
The maximum pulldown current for I2C is specified as 3 mA, that is for normal speed (100 kHz ... 400 kHz). I don't know if the Sercom hardware limits the current to 3 mA, probably not.

The second I2C bus is "Wire1".
There were some issues in the past with "Wire1", I hope they are solved.

You could create a define:

// Select the I2C bus
// #define WIRE Wire
#define WIRE Wire1

and then:
WIRE.begin()
WIRE.write()
WIRE.available()

Are you going to fix everything that I mentioned in my previous post ?

SDA20 and SCL21 on the slave? Then SDA1 and SCL1 on the master?

It's up to you, providing you add #define Wire Wire1 at the beginning of your sketch, just after the #include of libraries, as stated above. You could also use SDA1/SCL1 on both cards, and add pull ups of 2K2. It seems that this resistor value is OK.

You can test your code for I2C in Master and I2C in Slave modes with a DUE on a single DUE board, here is an example sketch using the builtin temperature sensor (hook a jumper between SDA/SDA1 and another one between SCL/SCL1):

/******************************************************************************/
/************    Master TWI0, Slave TWI1, temperature reading     *************/
/******************************************************************************/
#define read  1
#define write 0
#define TemperatureAddress      (0b1001000)

/*********************     Init TWIn      ************************/

void I2c_Init(Twi* pTWI, boolean Master) {
  if (pTWI == TWI0) {
    PMC->PMC_PCER0 |= PMC_PCER0_PID22;      // TWI0 power ON
    PIOA->PIO_PDR |= PIO_PDR_P17            // Enable peripheral control
                     | PIO_PDR_P18;
    PIOA->PIO_ABSR &= ~(PIO_PA17A_TWD0      // TWD0 & TWCK0 Peripherals type A
                        | PIO_PA18A_TWCK0);
  }
  else {
    PMC->PMC_PCER0 |= PMC_PCER0_PID23;      // TWI1 power ON
    PIOB->PIO_PDR |= PIO_PDR_P13            // Enable peripheral control
                     | PIO_PDR_P12;
    PIOB->PIO_ABSR &= ~(PIO_PB12A_TWD1      // TWD1 & TWCK1 Peripherals type A
                        | PIO_PB13A_TWCK1);
  }
  // I2C lines are Open drain by hardware, no need to program PIO_MDER

  pTWI->TWI_CR = TWI_CR_SWRST;  // TWIn software reset
  pTWI->TWI_RHR;                // Flush reception buffer

  pTWI->TWI_CR = TWI_CR_SVDIS | TWI_CR_MSDIS; // Disable Master and Slave modes

  //Enable master mode
  if (Master == true) {
    //enter slave address
    pTWI->TWI_MMR |= TWI_MMR_DADR(TemperatureAddress);

    pTWI->TWI_CR = TWI_CR_MSEN;  // Master mode enable
    //clockwave from 100khz to 400khz
    SetClock(pTWI, 400000); // from 100000 to 400000
  }
  else { // Enable Slave mode
    pTWI->TWI_SMR = TWI_SMR_SADR(TemperatureAddress);
    pTWI->TWI_CR = TWI_CR_SVEN;     // Slave mode enable
  }
}

/****************************     Start    ****************************/

void I2c_Start(Twi* pTWI, uint8_t slave_address, uint8_t mread) { //read=1, write=0
  //set slave address
  pTWI->TWI_MMR = (pTWI->TWI_MMR & ~TWI_MMR_DADR_Msk)
                  | TWI_MMR_DADR(slave_address);
  //set read/write direction
  if (mread == write) { //write
    pTWI->TWI_MMR &= ~TWI_MMR_MREAD;
  }
  else if (mread == read) { //read
    pTWI->TWI_MMR |= TWI_MMR_MREAD;
  }
  //send start
  pTWI->TWI_CR |= TWI_CR_START;

  //wait for ack
  while (!(pTWI->TWI_SR & TWI_SR_TXRDY));
}

/***************************   Stop    ****************************/

void I2c_Stop(Twi* pTWI) {
  pTWI->TWI_CR |= TWI_CR_STOP;
}

/**********************     Read 1 byte   **************************/

uint8_t I2c_ReadByte(Twi* pTWI) {
  uint8_t receivedByte;
  //If the stop bit in the control register is not set,
  //Sam3x will automatically ACK after reading TWIn_RHR register
  //RXRDY will be set when data arrives in TWIn_RHR register

  while (!(pTWI->TWI_SR & TWI_SR_RXRDY));
  //reading data will clear RXRDY bit in the status register
  receivedByte = pTWI->TWI_RHR;
  return receivedByte;
}

/*****************     Read the last byte   ***********************/

uint8_t I2c_ReadLastByte(Twi* pTWI) {
  uint8_t receivedByte;
  //Sam3x requires stop bit to be set before data is set on the TWIn_RHR
  //when stop bit is set, Sam3x will send a NACK instead of an ACK automatically
  I2c_Stop(pTWI);
  //When data arrives in the TWIn_RHR register RXRDY is set in the Status Register
  while (!(pTWI->TWI_SR & TWI_SR_RXRDY));
  //reading data will clear RXRDY bit in the status register
  receivedByte = pTWI->TWI_RHR;
  while (!(pTWI->TWI_SR & TWI_SR_TXCOMP));
  return receivedByte;
}

/************************     Write 1 byte     *******************/

void I2c_WriteByte(Twi* pTWI, uint8_t data) { 
  pTWI->TWI_THR |= data;
  //wait for ack
  while (!(pTWI->TWI_SR & TWI_SR_TXRDY));
}

/***********************    Write last byte    *******************/

void I2c_WriteLastByte(Twi* pTWI, uint8_t data) { 
  pTWI->TWI_THR |= data;
  I2c_Stop(pTWI);
  //wait for ack
  while (!(pTWI->TWI_SR & TWI_SR_TXRDY));
  while (!(pTWI->TWI_SR & TWI_SR_TXCOMP));
}

/*****************   Set Interrupt Configuration   *********************/

void I2c_Interrupt(Twi* pTWI, uint32_t InterrConfig) {
  pTWI->TWI_IER = InterrConfig;
  if (pTWI == TWI0) {
    NVIC_EnableIRQ(TWI0_IRQn);
  }
  else {
    NVIC_EnableIRQ(TWI1_IRQn);
  }
}

/*******************        TWI1 Handler    ***********************/

void TWI1_Handler(void) {
  uint32_t status = TWI1->TWI_SR;
  if (status & TWI_SR_SVREAD) {
    TWI1->TWI_RHR;
    TWI1->TWI_THR = temperature();
  }
}

/**********************    Configure clock    *********************/

void SetClock(Twi* pTWI , uint32_t frequency) {
  uint32_t CLDIV = 0;
  uint32_t CKDIV = 0;
  uint8_t readyByte = 0;

  while (!readyByte) {
    CLDIV = ((VARIANT_MCK / (2 * frequency)) - 4) / (1 << CKDIV) ;
    if ( CLDIV <= 255 ) {
      readyByte = 1 ;
    }
    else {
      CKDIV++ ;
    }
  }
  pTWI->TWI_CWGR = (CKDIV << 16) | (CLDIV << 8) | CLDIV;
}

void setup() {

  // Power OFF all peripherals
  PMC->PMC_PCDR0 = 0xFFFFFFFF;
  PMC->PMC_PCDR1 = 0xFFFFFFFF;
  Serial.begin(250000);
  PMC->PMC_PCER1 |= PMC_PCER1_PID37;  // ADC power ON
  ADC->ADC_ACR |= ADC_ACR_TSON;       // Temperature sensor ON

  I2c_Init(TWI0, true);   // TWI0 Master
  I2c_Init(TWI1, false);  // TWI1 Slave
  uint32_t InterruptConfig = TWI_IER_SVACC;  // | TWI_IER_SVREAD
  I2c_Interrupt(TWI1, InterruptConfig);

}

void loop() {

  uint8_t ReceivedByte;
  static uint32_t Oldmillis;

  if ((millis() - Oldmillis) > 1000) {
    Oldmillis = millis();
    I2c_Start(TWI0, TemperatureAddress, 1);
    ReceivedByte = I2c_ReadLastByte(TWI0);
    printf("%d\xB0" "C\n", ReceivedByte );
  }
}

/*****************   Temperature function    *******************/

uint8_t temperature() {
  float trans = 3.3 / 4096;
  float offset = 0.76;
  float factor = 0.00265;
  float fixtemp = 15;
  uint32_t ulValue;
  float treal;
  uint8_t __treal;

  ADC->ADC_CHER |= 1 << ADC_TEMPERATURE_SENSOR;
  ADC->ADC_CR = ADC_CR_START;
  while (!(ADC->ADC_ISR & ADC_ISR_DRDY));
  ulValue = ADC->ADC_LCDR;

  ADC->ADC_CHDR |= 1 << ADC_TEMPERATURE_SENSOR;
  treal = fixtemp + (( trans * ulValue ) - offset ) / factor;
  __treal = (uint8_t) treal;
  return __treal;
}

Thanks for the answers!

It works now. The solution was to initialize the TWI-port again. Because it seems like after adding the "TC - Timer Counter", the previous initialization was overridden. So, we had to set up the TWI-ports again in the conf_board.h.

We added this in the ASF/config/conf_board.h and now it works:

#define CONF_BOARD_TWI1 TWI1