Reputation: 61
I have an I2C slave running on an Arduino Due. The slave is working fine for all WRITE operations from the master, and also works correctly with many READ operations. However I am observing that when transmitting a byte value of less than 0x80 the SDA line is being held low by the SAM3x at the end of the byte transmission:
As an I2C slave the SAM3x should be releasing the SDA line right after it has transmitted the final bit of the byte - i.e. when the SCL line goes low after the eighth SCL high during this byte transmission. This then frees the line for the master to pull it low to signal its "ACK" of the data. This is also vital for the subsequent STOP condition, during which the SDA line should transition high after SCL line has transitioned high. Since the master is the one that implements this STOP condition no slave should be holding SDA low except for the eight clock cycles where it is transmitting the READ byte value, and those ACK clock cycles following slave address and WRITE bytes.
For comparison, below is the waveform I'm seeing on our picoscope when the byte value being transmitted is > 0x80 (in this case the byte value is 0x88):
I can see that in the case of the 0x88 transmission the SDA line is released immediately following the ACK clock cycle, thus neither master nor slave is holding it low. The master is then able to bring it low again such that it can implement the STOP condition. Releasing SDA to go high at this point is trivial, not required by the standard AFAIK. However it provides us the benefit that we can see that the slave must be holding the line low after - or maybe even during - the ACK bit clock cycle.
Note: I have verified that the SAM3x is the one holding the clock line low simply by resetting its I2C controller. As soon as we do that the SDA line is immediately released.
I have scoured the web looking for examples of I2C slave implementations on a SAM3x / Arduino Due and have found little that could aid in explaining what is happening here. Also the SAM3x datasheet (section 33.10) does not mention anything about this behaviour, as far as I could tell. In fact it rather states:
Until a STOP or REPEATED START condition is detected, TWI continues sending data loaded in the TWI_THR register
This indicates that it will release the SDA line after each complete byte (the full content of the TWI_THR register) has been shifted out - even if there is another byte in THR waiting to be sent. We know that the full byte has been shifted out, since we can see from the scope the exact same pattern for the lower nibble of the 0x08 byte as for the "working" byte value 0x88.
As we can see, the picoscope's I2C decoding is able to interpret both byte values, and presents both operations as being similarly successful. However, with the SDA line being held low, subsequent I2C operations then fail. Thus we need to find a solution to this - other than "only transmit bytes greater than 0x80"(!)
For completion, the I2C hardware initialisation code is below:
void Configure12cAsSlave(void) {
PMC->PMC_PCER1 |= PMC_PCER1_PID37;
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_ODER
TWI1->TWI_CR = TWI_CR_SWRST; // TWIn software reset
TWI1->TWI_RHR; // Flush reception buffer
TWI1->TWI_CR = TWI_CR_SVDIS | TWI_CR_MSDIS; // Disable Master and Slave modes
// Enable slave mode
TWI1->TWI_SMR = TWI_SMR_SADR(I2C_SLAVE_ADDRESS);
TWI1->TWI_CR = TWI_CR_SVEN; // Slave mode enable
// Interrupt on Receive Ready (WRITE ops) and Transmit Ready (READ ops)
TWI1->TWI_IER = TWI_IER_RXRDY | TWI_IER_TXRDY;
NVIC_EnableIRQ(TWI1_IRQn);
NVIC_SetPriority(TWI1_IRQn, IRQ_PRIORITY__I2C);
}
The interrupts that trigger the ISR are TWI_IER_RXRDY | TWI_IER_TXRDY. When TWI_IER_TXRDY (Transmit Hold Register ready) fires, the code simply writes a byte into TWI_THR as follows:
TWI1->TWI_THR = (uint32_t) iByteSent;
This byte has been previously loaded into TWI_THR during an earlier call to the ISR, following the end of a preceding write operation in which a register address has been provided to the slave. This means that THR is being written to twice during the same "SVACC" (Slave access) and could be interpreted then as having two bytes to transmit, rather than one. However this is necessary because on initialisation of the I2C controller as slave the TXRDY event fires continuously despite there being no actual I2C READ operation at the time, waiting for a byte to be written into the THR register. This blocks the Arduino from continuing until I write some dummy byte value into the THR register. However, that dummy byte then gets sent as the data byte value in the next (first) READ operation, and the byte value I provide in the TXRDY event handler then gets sent in the subsequent READ operation, and so on for subsequent READ operations. Thus every byte is delayed by one operation. In order to ensure the correct data value gets sent in all READ operations, I write to the THR register just as soon as I know what internal register address is being referred to - even before I know that a READ operation is about to happen (WRITE operations use the same format - except that the internal register address bytes are followed by another WRITE byte, thus my write to THR in those cases was redundant). This may seem convoluted but seemed necessary in getting the SAM3x working as an I2C slave. Given that 50% of byte values are being transmitted correctly in READ operations I trust that this "preloading" of the THR register is not the cause of the issues with transmitting bytes < 0x80 that I'm seeing.
On the Arduino Due I am using TWI1, via the two D20 and D21 pins that appear next to D19, as opposed to the two pins of the same numbers that appear above AREF, at the other end (top) of the headers along the right hand side. Note that although the code refers to this as "TWI1" (as apposed to TWI0), on the Arduino Due schematic its lines are referred to as SCL0-3 and SDA0-3 (as opposed to SCL1 and SDA1, which relate to TWI0). Thus the pins I am using for I2C include 1.5k pullups on the Arduino board itself. There are no other pullups on these lines. The I2C master is a third party system (an embedded custom FPGA), a black-box from my perspective.
Regarding the SAM3x holding TXRDY high right after initialisation of the I2C controller as slave: I wondered if this might be somehow related to other startup activities, thus moved the I2C slave initialisation into the main loop, to occur 1 second after the rest of the initialisation. This had no effect - TXRDY is still high immediately after I2C slave mode initialisation.
Upvotes: 1
Views: 48