This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

i2c repeated start

Other Parts Discussed in Thread: MSP430G2231, MSP430G2553

hi guys,

I am trying to implement repeated start using msp430g2231 launchpad  and I am using i2c for smbus 1.1 protocols. 

I have to pass repeated start to the slave but I am not sure the right method.

I wrote,

void repeatedStart()
{
USISRL = 0xFF;
USICTL0 |= USIOE;
USICNT |= 0x01;
}
...////////////////////////////////////////////////////
in interrupt, I wrote,
case 6: 
       USICTL0 &= ~USIOE;       // SDA = input
           USICNT |= 0x01;          // Bit counter = 1, receive (N)Ack bit
              I2C_State = 8;           
break;
case 8:
 repeatedStart();
 state = 10;
        break;
case 10:
 USICTL0 |= USIOE;
USISRL = 0x2D; // to slave address + 1 for read
USICNT |= 0x08; // Bit counter = 8, RX data
is this sequence correct and my repeatedStart() follow the correct steps?
Please help me and let me know!
Thank you,
Jeonggoo Song
  • It's not a good idea to use function calls inside an ISR (it breaks compiler optimization and wastes time by unnecessarily saving and restoring registers which are already saved by the ISR)

    However, a repeated start is sut a start without previous stop. It saves a clock cyclle and in a multi-master environment ensures that no othe rmaster will take over the bus between stop and start (because there is no stop).

    On a single master environment, you may as well use stop and start. I don't know of any slave (doesn't mean there is none - there's always someone who 'bends' the standard) that makes a difference between a start and a repeated start.

  • Well a Freescale aceelerometer (MMA8451) I am working with does require a repeated start. If I send a Stop/Start it resets the register address to 0.

  • Hello Timothy,

    Does it the same for each product of freescale? I have a mpr031 and i have a similar issue. I am not sending the repeated start properly. As it could be seen in the oscilloscope, it sends and stop and then and start but (by software) i am not doing that. I checked that with a led.

    Did you solve that?

    I am using a msp430g2553. 

    This is my code.

    #include "msp430g2553.h"

    #define DEVICE_ID 0x4A //Slave Address

    void Setup_TX(void);
    void Setup_RX(void);
    void leer (char reg_addr, char bytes_rx); //Function for reading an internal register
    void escribir (char bytes_tx); //Function for writting
    void Receive(char bytes_rx);
    void Transmit(char bytes_tx);
    void llenado_tx_vector (char valor_1, char valor_2, char valor_3);
    void delay(unsigned int time);
    void uart_config(void);

    int RXByteCtr;

    unsigned char tx_vector[3]; //Transmit vector
    unsigned char rx_vector[3]; //Reception vector

    unsigned char TXByteCtr, RX = 0;

    unsigned char data_size_global;

    char flag_RPSTART; // enables repeated start
    char flag_led;


    void main(void)
    {
    WDTCTL = WDTPW + WDTHOLD; // Stop WDT
    P1DIR = 0xF7;
    P1OUT = 0x00;
    uart_config();
    P1SEL |= BIT6 + BIT7; // Assign I2C pins to USCI_B0
    P1SEL2|= BIT6 + BIT7; // Assign I2C pins to USCI_B0
    flag_RPSTART = 0;
    flag_led = 0;

    delay (65535); //Waiting for init of device

    while (!(IFG2&UCA0TXIFG)); // Debug in ACCESSPORT
    UCA0TXBUF = 0x01;


    llenado_tx_vector (0x44, 0x00, 0x00); //STOP MODE
    escribir (2);

    llenado_tx_vector (0x41, 0x12, 0x00); //SET AFE
    escribir (2);

    llenado_tx_vector (0x44, 0x01, 0x00); //RUN MODE 1
    escribir (2);

    while (!(IFG2&UCA0TXIFG)); // Debug in ACCESSPORT
    UCA0TXBUF = 0x02;

    while(1)
    {
    rx_vector[0] = 0x34; //Checking reception...

    leer (0x41, 1); //Reading AFE


    while (!(IFG2&UCA0TXIFG)); //Debug in ACCESSPORT
    UCA0TXBUF = tx_vector[0];

    while (!(IFG2&UCA0TXIFG)); //Debug in ACCESSPORT
    UCA0TXBUF = rx_vector[0]; //I see zero in Hyperterminal (AccessPort)

    delay (50000);


    rx_vector[0] = 0x34; //Checking reception...

    leer (0x43, 1); //Reading FILTER CONF REGISTER


    while (!(IFG2&UCA0TXIFG)); //Debug in ACCESSPORT
    UCA0TXBUF = tx_vector[0];

    while (!(IFG2&UCA0TXIFG)); //Debug in ACCESSPORT
    UCA0TXBUF = rx_vector[0]; //I see zero in Hyperterminal (AccessPort)

    delay (50000);

    }
    }


    //-------------------------------------------------------------------------------
    // The USCI_B0 data ISR is used to move received data from the I2C slave
    // to the MSP430 memory. It is structured such that it can be used to receive
    // any 2+ number of bytes by pre-loading RXByteCtr with the byte count.
    //-------------------------------------------------------------------------------
    #pragma vector = USCIAB0TX_VECTOR
    __interrupt void USCIAB0TX_ISR(void)
    {

    while (UCB0STAT & UCSCLLOW);

    if(RX == 1)
    { // Master Recieve?
    RXByteCtr--; // Decrement RX byte counter
    if (RXByteCtr)
    {
    rx_vector[data_size_global - RXByteCtr - 1] = UCB0RXBUF; // Move RX data to address PRxData
    }
    else
    {

    rx_vector[data_size_global - 1] = UCB0RXBUF; // Move final RX data to PRxData


    if (flag_RPSTART == 0)
    UCB0CTL1 |= UCTXSTP; // No Repeated Start: stop condition
    else
    flag_RPSTART = 0;


    __bic_SR_register_on_exit(CPUOFF + GIE); // Exit LPM0
    }
    }

    else
    { // Master Transmit

    if (TXByteCtr > 0) // Check TX byte counter
    {

    UCB0TXBUF = tx_vector[data_size_global - TXByteCtr];

    TXByteCtr--; // Decrement TX byte counter
    }
    else
    {
    if(flag_RPSTART == 1)
    {

    flag_RPSTART = 0;
    __bic_SR_register_on_exit(CPUOFF);

    }
    else
    {
    UCB0CTL1 |= UCTXSTP; // I2C stop condition


    IFG2 &= ~UCB0TXIFG; // Clear USCI_B0 TX int flag
    __bic_SR_register_on_exit(CPUOFF); // Exit LPM0
    }

    }



    //while (UCB0STAT & UCSCLLOW); //CLOCK STRETCHING
    }

    }

    #pragma vector = USCIAB0RX_VECTOR
    __interrupt void USCIAB0RX_ISR(void)
    {
    if(UCB0STAT & UCNACKIFG)
    {
    //P1OUT |= 0x01;
    if (flag_led == 0)
    {
    P1OUT |= 0x01;
    flag_led = 1;
    }
    else
    {
    P1OUT &= ~0x01;
    flag_led = 0;

    }

    UCB0STAT &= ~UCNACKIFG;
    }


    }



    void Setup_TX(void)
    {
    _DINT();
    RX = 0;
    IE2 &= ~UCB0RXIE;
    while (UCB0CTL1 & UCTXSTP); // Ensure stop condition got sent// Disable RX interrupt
    UCB0CTL1 |= UCSWRST; // Enable SW reset

    UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC; // I2C Master, synchronous mode

    UCB0CTL1 = UCSSEL_2 + UCSWRST; // Use SMCLK, keep SW reset
    UCB0BR0 = 10; // fSCL = SMCLK/10 = ~120kHz
    UCB0BR1 = 0;
    UCB0I2CSA = DEVICE_ID; //Setting slave address
    UCB0CTL1 &= ~UCSWRST; // Clear SW reset, resume operation
    IE2 |= UCB0TXIE; // Enable TX interrupt

    UCB0I2CIE |= UCNACKIE;

    }

    void Transmit(char bytes_tx)
    {
    TXByteCtr = bytes_tx; // Load TX byte counter
    data_size_global = bytes_tx;

    while (UCB0CTL1 & UCTXSTP); // Ensure stop condition got sent
    UCB0CTL1 |= UCTR + UCTXSTT; // I2C TX, start condition

    while (UCB0STAT & UCSCLLOW); //STRETCHING CLOCK

    __bis_SR_register(CPUOFF + GIE); // Enter LPM0 w/ interrupts
    }


    void Setup_RX(void)
    {
    _DINT();
    RX = 1;
    IE2 &= ~UCB0TXIE;
    UCB0CTL1 |= UCSWRST; // Enable SW reset
    UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC; // I2C Master, synchronous mode

    UCB0CTL1 = UCSSEL_2 + UCSWRST; // Use SMCLK, keep SW reset
    UCB0BR0 = 10; // fSCL = SMCLK/10 = ~120kHz
    UCB0BR1 = 0;
    UCB0I2CSA = DEVICE_ID; //Setting slave address
    UCB0CTL1 &= ~UCSWRST; // Clear SW reset, resume operation
    IE2 |= UCB0RXIE; // Enable RX interrupt

    UCB0I2CIE |= UCNACKIE;
    }

    void Receive(char bytes_rx)
    {
    RXByteCtr = bytes_rx; // Load RX byte counter
    data_size_global = bytes_rx;
    while (UCB0CTL1 & UCTXSTP); // Ensure stop condition got sent
    UCB0CTL1 |= UCTXSTT; // I2C start condition


    while (UCB0STAT & UCSCLLOW);

    __bis_SR_register(CPUOFF + GIE); // Enter LPM0 w/ interrupts
    }


    void escribir (char bytes_tx) //FUNCION QUE TRANSMITE AL SENSOR EL REGISTRO, LOS DATOS y LA CANTIDAD DE BYTES A TRANSMITIR
    {
    flag_RPSTART = 0;
    Setup_TX();
    Transmit(bytes_tx);
    while (UCB0CTL1 & UCTXSTP); // Ensure stop condition got sent
    }

    void leer (char reg_addr, char bytes_rx) //FUNCION QUE LEE EL REGISTRO ESPECIFICADO. HAY QUE INDICAR CUANTOS BYTES DEVUELVE LA CONSULTA
    {

    flag_RPSTART = 1; //Enable Repeated Start
    Setup_TX();
    tx_vector[0] = reg_addr;
    Transmit(1);
    while (UCB0CTL1 & UCTXSTP); // Ensure stop condition got sent


    Setup_RX(); //QUEDE ACA
    Receive(bytes_rx);
    while (UCB0CTL1 & UCTXSTP); // Ensure stop condition got sent

    }

    void llenado_tx_vector (char valor_1, char valor_2, char valor_3)
    {
    tx_vector[0] = valor_1;
    tx_vector[1] = valor_2;
    tx_vector[2] = valor_3;
    }

    void delay(unsigned int time)
    {
    unsigned int iter;
    iter = time;
    while(iter != 0)
    {
    iter--;
    }
    }

    void uart_config(void)
    {
    BCSCTL1 = CALBC1_1MHZ; // Set DCO
    DCOCTL = CALDCO_1MHZ;
    P1SEL = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD
    P1SEL2 = BIT1 + BIT2 ; // P1.1 = RXD, P1.2=TXD
    UCA0CTL1 |= UCSSEL_2; // SMCLK
    UCA0BR0 = 104; // 1MHz 9600
    UCA0BR1 = 0; // 1MHz 9600
    UCA0MCTL = UCBRS0; // Modulation UCBRSx = 1
    UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine**
    }
  • Hello team,

    Is there an update available for the above customer inquiry?

    Regards,

    Michael

  • I2C has a fundamental problem with its start/stop conditions.

    When the master is transmitting, the last clock cycle gets an ACK form teh slave. Which is low. Now th elast clokc puls eends. SCL is high. SDA is low. the slave must not release SDA now, as SCL is high. However, for a repeated start, both, SCL and SDA need to be high, or else the master cannot pull SDA low while SCL is high.
    The solution is, that the USCI starts sending another byte, startign with a high bit. As soon as SCL goes low again, the slave may release SDA. The master transmits a '1', and once SCL goes high again, both, SDA and SCL are high and the master can transmit a (repeated) start condition. The same applies for a stop condition: SDA needs to be low when SCL goes high, but low by the master, not by the slave, as the slave must not release SDA while SCL is high. Here again, an additional clock cycle is neede to produce the proper startign point for the following stop.

    Now if the slave releases SDA after it has sent its ACK for the previous byte, this  wold be a slave-generated stop condition and a violation of the I2C standard. And there's nothing the MSP could do against it except for forcefully holding SDA low after detecting the slave ACK (or NACK). Still a violation of the standard.

    Leonardo Mart��nez said:
    while (UCB0STAT & UCSCLLOW);

    Why that?
    Yes, checking UCSLLOW might be useful in some situations, but waiting for it to go away generally, is a waste of time t best and a lockup at worst.
    In some situations, the master will hold SCL low by itself until you wrrite to TXBUF or read RXBUF - which you'll never do then. If your ISR is for some reason not ecxecuted until the ACK bit of the start condition (in tramsit mode) or before ACKing the second incoming byte (in receive mode), this will lock the CPU forever.

    There are similar quirks on several locations in the code. You should carefully re-read the event-diagram in the users guide. It tells you exactly what will happen when.
    As long as you do not have other things to do that take time or lock interrupts, your code will probably work, but you might get into serious trouble when expanding your project.

    Leonardo Mart��nez said:
    if(flag_RPSTART == 1)
    {
    flag_RPSTART = 0;
    __bic_SR_register_on_exit(CPUOFF);
    }
    else
    {
    UCB0CTL1 |= UCTXSTP; // I2C stop condition
    IFG2 &= ~UCB0TXIFG; // Clear USCI_B0 TX int flag
    __bic_SR_register_on_exit(CPUOFF); // Exit LPM0
    }
    }

    Here is your problem. You do send a stop. :)
    In the if case, you clear RPSTART. fine. But now you leave the ISR without handling the interrupt cause. You don't write to TXBUF or set UCTXSTP or UCTXSTT. As a result, the TX interrupt is still pending and is executed immediately again. But this case, RPSTART is zero now, and you send a stop.
    You need to clear the TXIE bit, so the unhandled interrupt is ignored, or you set UCTXSTT inside the ISR, which clears the interrupt condition too. But since your ISR doesn't know how to continue...

    The best handling is to disable the TX (or RX) itnerrupts at the end of a transmission, so further interrutps are ignored. The setup function then enables the interrupt again if appropriate.

  • Hello Jens-Michael,

    Thanks! I didn't understand well the interrupt procedure. I have read a lot of times the user manual but i didn't understand how to manage the flags.

    Finally, it worked by a polling version. 

    When we need a better version i will try what you told me. That's the problem for sure. 

    Best regards!

  • Hi!

    I am in the process of communicating with the mma845q. After having many problems using USI, I decided to switch to USCI. Can you share your code showing the Repeated Start?

     

    Thank you,

    Mariano

  • Hi Mariano,

    This is part of my working code for i2c with repeated start in ISR. 

    void Setup_i2c_1(void)
    {
    _DINT();

    UCB0CTL1 |= UCSWRST; // Enable SW reset
    UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC; // I2C Master, synchronous mode
    UCB0CTL1 = UCSSEL_2 + UCSWRST; // Use SMCLK, keep SW reset
    //UCB0BR0 = 10; // fSCL = SMCLK/10 = ~120kHz
    UCB0BR0 = 4; // fSCL = SMCLK/4 = ~300kHz

    UCB0BR1 = 0;
    UCB0I2CSA = DEVICE_ID_1; //Setting slave address


    UCB0CTL1 &= ~UCSWRST; // Clear SW reset, resume operation
    IE2 |= UCB0TXIE; //INICIALIZACION
    RX = 0;

    }

    void write (char reg_address, char value) //FUNCION QUE TRANSMITE AL SENSOR EL REGISTRO, LOS DATOS y LA CANTIDAD DE BYTES A TRANSMITIR
    {
    if(auricular == 1) //AL APAGARSE DEBE VOLVER A INICIARSE CORRECTAMENTE
    Setup_i2c_1();
    else if (auricular == 2)
    Setup_i2c_2();


    RPT_Flag = 0;
    TXByteCtr = 2; // Load TX byte counter
    byte_transmit_total = TXByteCtr;

    mensaje[0] = reg_address;
    mensaje[1] = value;

    UCB0CTL1 |= UCTR + UCTXSTT; // I2C TX, start condition

    __bis_SR_register(LPM4_bits + GIE); // Enter LPM0 w/ interrupts

    while (UCB0CTL1 & UCTXSTP); // Ensure stop condition got sent
    }

    void read (char reg_addr) //FUNCION QUE LEE EL REGISTRO ESPECIFICADO. HAY QUE INDICAR CUANTOS BYTES DEVUELVE LA CONSULTA
    {

    if(auricular == 1) //AL APAGARSE DEBE VOLVER A INICIARSE CORRECTAMENTE
    Setup_i2c_1();
    else if (auricular == 2)
    Setup_i2c_2();


    RPT_Flag = 1;
    TXByteCtr = 1;
    byte_transmit_total = TXByteCtr;
    mensaje[0] = reg_addr;

    UCB0CTL1 |= UCTR + UCTXSTT; // I2C TX, start condition

    __bis_SR_register(LPM4_bits + GIE); // Enter LPM0 w/ interrupts

    while (UCB0CTL1 & UCTXSTP); // Ensure stop condition got sent

    }

    //-------------------------------------------------------------------------------
    // The USCI_B0 data ISR is used to move received data from the I2C slave
    // to the MSP430 memory. It is structured such that it can be used to receive
    // any 2+ number of bytes by pre-loading RXByteCtr with the byte count.
    //-------------------------------------------------------------------------------
    #pragma vector = USCIAB0TX_VECTOR
    __interrupt void USCIAB0TX_ISR(void)
    {
    if((IFG2&UCB0TXIFG)||(IFG2&UCB0RXIFG))
    {
    if(RX == 1)
    { // Master Recieve?

    rx_byte = UCB0RXBUF; // Move final RX data to PRxData

    while (UCB0CTL1 & UCTXSTT); //Wait until the acknowlege message has been received

    UCB0CTL1 |= UCTXSTP; // No Repeated Start: stop condition

    IE2 &= ~UCB0RXIE; //BORRA INT POR RECEPCION

    __bic_SR_register_on_exit(LPM4_bits); // Exit LPM0


    }

    else
    { // Master Transmit
    if (TXByteCtr) // Check TX byte counter
    {
    UCB0TXBUF = mensaje[byte_transmit_total - TXByteCtr]; // Load TX buffer
    TXByteCtr--; // Decrement TX byte counter
    }
    else
    {
    if(RPT_Flag == 1)
    {
    RPT_Flag = 0;

    IFG2 &= ~UCB0TXIFG; // Clear USCI_B0 TX int flag
    IE2 &= ~UCB0TXIE; //BORRA INT POR TRANSMISION

    RX = 1;
    IE2 |= UCB0RXIE;
    RXByteCtr = 1;

    UCB0CTL1 &= ~UCTR; //Sets the master as a receiver
    UCB0CTL1 |= UCTXSTT; //Start Condition sends address of slave


    }
    else
    {
    UCB0CTL1 |= UCTXSTP; // I2C stop condition
    IFG2 &= ~UCB0TXIFG; // Clear USCI_B0 TX int flag
    IE2 &= ~UCB0TXIE; //BORRA INT POR TRANSMISION

    __bic_SR_register_on_exit(LPM4_bits); // Exit LPM0
    }
    }
    }
    }

    }

  • I have the following function to do a repeated start in hardware. It has been working for me,

    /**************************************************************************************************
     * @fn          HalI2CReadRS
     *
     * @brief       Read from the I2C bus as a Master with repeated start
     *
     * input parameters
     *
     * @param       regAddress - I2C register address to read.
     * @param       pBuf - Pointer to the data buffer to put single byte read.
     *
     * output parameters
     *
     * None.
     *
     * @return      The number of bytes successfully read.
     */
    i2cLen_t IC2MHalI2CReadRS(uint8 regAddress,uint8 *pBuf)
    {
    EA = 0;
      // start a write with 1 byte of command data
      if (i2cMstStrt(0) != mstAddrAckW)
      {
        EA = 1;
        return 0;
      }

        I2C_WRITE(regAddress);

        if (I2CSTAT != mstDataAckW)
        {
          EA = 1;
        return 0;
        }
        
      I2C_STRT();

        I2C_WRITE(i2cAddr | I2C_MST_RD_BIT);
        I2C_SET_NACK();     // added for touch sensors

        I2C_READ(*pBuf++);
      I2C_STOP();
     I2C_CLR_NACK();  // added for touch sensors
     EA = 1;
     return 1;
    }

  • Hi leinho,
    Your code has worked fine for me.
    My question is why is "Setup_i2c_1()" needed every time you make a register reading?. I tried using this sentence only once at the beginning and removing it from the read function but it got lagged.
    Best regards.
  • I didn't analyze the full code, but I noticed that Setup_i2c_1() sets the slave address (which can be done at any time) and sets RX=0. The ISR sets it to 1 after sending the register address. If it isn't reset to 0, the ISR will just read and read and never write the register address again.

**Attention** This is a public forum