A Better USI I2C Library For The MSP430

USI

TI’s MSP430 chips are rather interesting – they’re low power, very capable, and available for under a dollar in most cases. Some of these chips, though, don’t have native SPI or I2C interfaces; instead, everything is done through a USI, or Universal Serial Interface module. [Jan] found the stock I2C USI module was a little rough around the edges, so he created his own.

[Jan] found the TI example code for using the USI as an I2C device overly complicated and something that an intern whipped up in a week and was never touched again. In response to this, he created a much, much simpler USI/I2C module that’s actually readable. It’s available over on the GitHub if you want to grab it for yourself.

Compared to the TI code, [Jan]’s library is dead simple. There are only two functions, one for initialization, and another for sending and receiving. Easy, small, and it works. Can’t do much better than that.

7 thoughts on “A Better USI I2C Library For The MSP430

  1. I’ve tried to do this, as well as use ti’s lib. The frustrating thing is that each slave device implements i2c slightly differently requiring digging through the library to add in the right options. 7 or 8 bit addressing? repeated starts? little or big endian? What if you have to use USCI1 instead of USCI0? Not to mention all he other non-standard but close i2c like protocols. After a while you just end up re-implementing. The best ways to make i2c easier are:
    1) knowing the USCI and USART modules well
    2) be good at writing state machines
    3) use your logic analyzer liberally
    I could be wrong, but I think that i2c headaches are just the price you pay for using less pins. Although it does get easier/faster after the first few times.

    1. Author here. Please note that this micro-library is strictly for USI, not USCI. I use it on the tiny MSP430G2412 chips. It does support repeated start, but not 10-bit addressing. I used it with microchip I/O expanders, Freescale MMA845x accelerometers, SFH7773 light/proximity sensors.

      In general, I agree that I2C is more complex than one might think.

      As to MSP430 and libraries, I agree with other commenters that the state of the libraries is poor. But I would not consider arduino as a “clean” ideal. It is very difficult to strike a good balance between code size, ease of use, and universal applicability. My code definitely favors small size and easy to use, but is not universal at all. TI has huge frameworks for the larger chips, those are big, difficult to use and dominate your code base, but are very flexible.

  2. I just wish someone would come up with a better all around Lib for the MSP430 and the watch. TI’s nightmare is a mess and the open source copy is as bad. Honestly Why cant the MSP430 have good CLEAN libraries like the arduino? Instead we get convoluted messes that are full of “trendyness” in programming styles that are useless to the world.

  3. A better USCI library. After searching for an MSP430G2553 library I found nothing and built this by trial and error. ENJOY!

    /*
    * USCI_I2C_Master.c
    *
    * Created on: Oct 25, 2016
    * Author: JLoy
    */

    #include

    unsigned char* gpTXPos = 0;
    unsigned char* gpTXEnd = 0;
    unsigned char* gpRXPos = 0;
    unsigned char* gpRXEnd = 0;

    const unsigned char gbWait = 1;

    unsigned char USCI_I2C_NotReady()
    {
    return (UCB0STAT & UCBBUSY);
    }

    void USCI_I2C_Init(unsigned char pinSel, unsigned char clkDiv)
    {
    P1SEL |= pinSel; // Assign I2C pins to USCI_B0
    P1SEL2|= pinSel;

    UCB0CTL1 |= UCSWRST; // Enable SW reset
    UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC; // I2C Master, synchronous mode
    UCB0CTL1 = UCSSEL_2 + UCSWRST; // Use SMCLK, keep SW reset
    UCB0BR0 = clkDiv; // fSCL = SMCLK/12 = ~100kHz
    UCB0BR1 = 0;
    }

    void USCI_I2C_TxRx(const unsigned char slaveAddress, const unsigned char sendCount, const unsigned char* pSendData, unsigned char recvCount, unsigned char* pRecvData)
    {
    gpTXPos = pSendData;
    gpTXEnd = pSendData + sendCount;
    gpRXPos = pRecvData;
    gpRXEnd = pRecvData + recvCount;

    UCB0CTL1 |= UCSWRST; // Enable SW reset
    UCB0TXBUF = 0; // Clear TX buffer (Not necessary?)
    UCB0I2CSA = slaveAddress; // Slave Address is 069h
    UCB0CTL1 &= ~UCSWRST; // Clear SW reset, resume operation

    UCB0I2CIE = UCNACKIE; // Enable NACK interrupt

    // TRANSMIT DATA
    UCB0CTL1 |= UCTR + UCTXSTT; // I2C TX, start condition
    IE2 |= UCB0TXIE; // Enable TX interrupt

    if (gbWait)
    __bis_SR_register(CPUOFF + GIE); // Enter LPM0 w/ interrupts for TX
    }

    //——————————————————————————-
    // This interrupt will both SEND and RECEIVE
    //——————————————————————————-
    #pragma vector = USCIAB0TX_VECTOR
    __interrupt void USCIAB0TX_ISR(void)
    {
    if(UCB0CTL1 & UCTR && IFG2 & UCB0TXIFG) { // TX mode (UCTR == 1)
    if (gpTXPos < gpTXEnd) // TRUE if more bytes remain
    UCB0TXBUF = *gpTXPos++; // Load TX buffer
    else { // no more bytes to send
    IE2 &= ~UCB0TXIE; // Disable TX interrupt
    IFG2 &= ~UCB0TXIFG; // Clear USCI_B0 TX int flag
    if (gpRXPos < gpRXEnd) { // RX setup
    UCB0CTL1 &= ~UCTR; // Set Receive Clear UCTR
    UCB0CTL1 |= UCTXSTT; // I2C restart condition
    IE2 |= UCB0RXIE; // Enable RX interrupt
    if (gpRXPos + 1 == gpRXEnd) { // 1 RX byte
    while (UCB0CTL1 & UCTXSTT); // wait for START to send STOP (this is dumb!)
    UCB0CTL1 |= UCTXSTP; // send STOP
    }
    }
    else { // TX only
    UCB0CTL1 |= UCTXSTP; // send STOP
    LPM0_EXIT; // Exit LPM0
    }
    }
    }
    else if (IFG2 & UCB0RXIFG) { // (UCTR == 0) // RX mode
    *gpRXPos++ = UCB0RXBUF; // Read RX buffer
    if (gpRXPos + 1 == gpRXEnd) // If NEXT to LAST send STOP
    UCB0CTL1 |= UCTXSTP; // send STOP
    if (gpRXPos == gpRXEnd) { // Last read SHUTDOWN
    IE2 &= ~UCB0RXIE; // Disable RX and TX interrupt
    IFG2 &= ~UCB0RXIFG; // Clear USCI_B0 TX int flag
    LPM0_EXIT; // Exit LPM0
    }
    }
    }

    #pragma vector = USCIAB0RX_VECTOR
    __interrupt void USCIAB0RX_ISR(void)
    {
    if (UCB0STAT & UCNACKIFG) {
    UCB0CTL1 |= UCTXSTP; // Generate I2C stop condition
    UCB0STAT &= ~UCNACKIFG; // Clear IFG
    LPM0_EXIT; // Exit LPM0
    }
    }

  4. I’m trying to implement a protocol “parser i2c command” state machine, above USI i2c from a MSP430G2332 (as i2c slave), to communicate with another microcontroller (as i2c master), but I’ve some problems. The state machine needded by usi is unflexible to implement this parser struct, it just process data from interrupt, and just send one byte per time. Is there another way to implement this USI state machine, to permit parser command “in parallelel” with parser commands state machine?

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.