To whoever may be struggling with eUSCI I2C implementation using interrupts....
I have spent countless hours fruitlessly combing through the net looking for working examples, or at least pointers to how to implement I2C communication using eUSCI module.
There are already plenty of comments on TI's toy examples, so I do not need to say anything more about it. Documentation contains almost everything but it is so dense, it seems
even punctuation marks matter. I still wasn't able to find very fine details about how to implement "repeated start" with auto-stop bit (not sure if it is even possible). For example,
in order to modify UCASTPx (from sending 1 byte reg address to reading desired nr of bytes), I have to reset the module, UCSWRST = 1, but doing so will mess up (yet) unsent STOP bit.
I could not get the "repeated start" to work even with manually sending stop bits. The next best thing seems sending start bit immediately after the stop bit and this code does it.
Not sure if this will be of any use to anyone, but this code is working for me on MSP430FR2433 (clock running at 8 MHz and I2C at 100kHz), running as a master, in single
master mode.
Any comments on how to improve it, made it more robust or efficient are welcome (no claims to perfection, just working).
Dispatch routine, used for both reading and writing to a slave.
I2c_Fault_t i2c_dispatch( pi2c_Stream_t pI2c, // struct containing I2C base address, rx/tx buffer and some auxiliary data
uint8_t slave_address, // I2C slave address
uint8_t reg_address, // slave reg address
uint8_t * pdata, // pointer to data to be sent (rx data to be written to)
uint8_t tx_size, // tx_size if nr of data bytes, excluding slave reg adddress
uint8_t rx_size) // nr of rx bytes if reading
{
// check if module is not busy
I2c_Fault_t ret_value = i2c_is_ready( pI2c );
if ( ret_value )
{
return ret_value;
}
pI2c->rx_size = rx_size;
pI2c->tx_size = tx_size + 1; // one extra byte for reg address
pI2c->buff[0] = reg_address; // put reg address as the first byte of the buffer to be sent out
uint8_t i = 1;
while ( tx_size > 0 ) // write other data (no data in case of read)
{
pI2c->buff[i] = *pdata;
pdata ++;
tx_size --;
}
pI2c->ptr = 0; // initialize pointer of the buffer
pI2c->busy = 1; // set I2C structure to busy
i2c_disable_module( pI2c ); // put in reset condition while changing parameters
i2c_set_slave_address( pI2c, slave_address );
i2c_set_mode( pI2c, UCTR ); // both RD and WR start with WR
// setup auto-stop bit if transmit only (will not be followed by repeated start)
HWREG16( pI2c->base_address + OFS_UCBxTBCNT ) = pI2c->tx_size;
HWREG16( pI2c->base_address + OFS_UCBxCTLW1 ) &= ~( UCASTP3_L ); // clear before setting bits
HWREG16( pI2c->base_address + OFS_UCBxCTLW1 ) |= UCASTP1_L; // automatic stop bit generation after all bytes are sent
i2c_send_start( pI2c );
i2c_enable_module( pI2c );
i2c_clear_interrupt( pI2c, UCTXIFG | UCNACKIFG | UCCLTOIFG | UCALIFG | UCSTPIFG);
i2c_enable_interrupt( pI2c, UCTXIE0 | UCNACKIE | UCCLTOIE | UCALIE | UCSTPIE );
return I2C_FAULT_NONE;
}
Most of the actual work is done in the ISR (i2c_list[0] is just a pointer to the structure that was initialized in the dispatch routine (relevant parts of the
structure are only ptr and buff (ptr points to the current data to be send or received in buff array).
#pragma vector=USCI_B0_VECTOR
__interrupt void USCI_B0_ISR( void )
{
switch( __even_in_range( UCB0IV, 0x1e ) )
{
case USCI_I2C_UCALIFG:
// set AL fault
i2c_list[0].fault = I2C_FAULT_AL;
break;
case USCI_I2C_UCNACKIFG:
// set NACK fault and send STOP bit
i2c_list[0].fault = I2C_FAULT_NACK;
break;
case USCI_I2C_UCSTTIFG:
case USCI_I2C_UCRXIFG3:
case USCI_I2C_UCTXIFG3:
case USCI_I2C_UCRXIFG2:
case USCI_I2C_UCTXIFG2:
case USCI_I2C_UCRXIFG1:
case USCI_I2C_UCTXIFG1:
i2c_list[0].fault = I2C_FAULT_UNDEF;
break;
case USCI_I2C_UCRXIFG0:
// Read data from the RXBUF
i2c_list[0].buff[i2c_list[0].ptr++] = HWREG16( EUSCI_B0_BASE + OFS_UCBxRXBUF );
// if the last byte received, disable interrupt RX interrupt and enable STOP bit interrupt
if( i2c_list[0].ptr == i2c_list[0].rx_size )
{
i2c_list[0].rx_size = 0; // to make sure USCI_I2C_UCSTPIFG goes to exit path, another flag can be used for just this purpose
i2c_disable_interrupt( &i2c_list[0], UCRXIE0 | UCBCNTIE );
HWREG16( EUSCI_B0_BASE + OFS_UCBxIE ) |= ( UCSTPIE );
}
break;
case USCI_I2C_UCTXIFG0:
// write bytes to TXBUF
HWREG16( EUSCI_B0_BASE + OFS_UCBxTXBUF ) = i2c_list[0].buff[i2c_list[0].ptr];
i2c_list[0].ptr++;
// if the last byte transmitted, disable TX interrupt,
if( i2c_list[0].ptr == i2c_list[0].tx_size )
{
HWREG16( EUSCI_B0_BASE + OFS_UCBxIE ) &= ~( UCTXIE0 );
}
break;
case USCI_I2C_UCSTPIFG:
// if reading slave, sent stop and then start bits
if( i2c_list[0].rx_size )
{
// prepare for repeat reception
i2c_list[0].ptr = 0;
HWREG16(EUSCI_B0_BASE + OFS_UCBxCTLW0) |= UCSWRST;
HWREG16( EUSCI_B0_BASE + OFS_UCBxCTLW0 ) &= ~EUSCI_B_I2C_TRANSMIT_MODE;
HWREG16( EUSCI_B0_BASE + OFS_UCBxTBCNT ) = i2c_list[0].rx_size ;
HWREG16( EUSCI_B0_BASE + OFS_UCBxCTLW1 ) &= ~UCASTP_3;
HWREG16( EUSCI_B0_BASE + OFS_UCBxCTLW1 ) |= UCASTP1_L; // automatic stop bit generation after all bytes are received
HWREG16( EUSCI_B0_BASE + OFS_UCBxCTLW0 ) |= UCTXSTT;
HWREG16(EUSCI_B0_BASE + OFS_UCBxCTLW0) &= ~UCSWRST;
HWREG16( EUSCI_B0_BASE + OFS_UCBxIFG ) &= ~( UCRXIFG0 | UCNACKIFG | UCCLTOIFG | UCBCNTIFG );
HWREG16( EUSCI_B0_BASE + OFS_UCBxIE ) |= ( UCRXIE0 | UCNACKIE | UCCLTOIE | UCBCNTIE );
} else {
// write mode - after all the data bytes are sent out, disable interrupts and the eUSCI module
i2c_list[0].busy = 0;
HWREG16( EUSCI_B0_BASE + OFS_UCBxIE ) &= ~( UCSTPIE | UCNACKIE | UCCLTOIE );
HWREG16(EUSCI_B0_BASE + OFS_UCBxCTLW0) |= UCSWRST;
__low_power_mode_off_on_exit( ); // end of writing data
}
break;
case USCI_I2C_UCBCNTIFG:
i2c_list[0].cnt_flag = 1;
break;
case USCI_I2C_UCCLTOIFG:
i2c_list[0].fault = I2C_FAULT_CLTO;
break;
case USCI_I2C_UCBIT9IFG:
break;
}
// handle I2C faults
if( i2c_list[0].fault )
{
HWREG16( EUSCI_B0_BASE + OFS_UCBxCTLW0 ) |= UCTXSTP;
HWREG16( EUSCI_B0_BASE + OFS_UCBxIE ) = 0x0;
__low_power_mode_off_on_exit( ); // exit with a fault
}
}
Cheers