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.

F28027 Simple slave I2C transmit

Other Parts Discussed in Thread: MSP430F5438, MSP430F5438A

Hi Folks,

I am seeing a problem with the F28027 LaunchPad I2C That makes me think I am misunderstanding how the I2C should work when transmitting in slave-transmitter mode.

My configuration for the I2C is as follows:

		/* Setup I/O lines. */
		GPIO_setDirection(gpio, (GPIO_Number_e)GPIO_SCL_LINE_NUMBER, GPIO_Direction_Input);  /* GPIO 33 for clock. */
		GPIO_setPullUp   (gpio, (GPIO_Number_e)GPIO_SCL_LINE_NUMBER, GPIO_PullUp_Enable);
		GPIO_setMode     (gpio, (GPIO_Number_e)GPIO_SCL_LINE_NUMBER, GPIO_33_Mode_SCLA);
		GPIO_setDirection(gpio, (GPIO_Number_e)GPIO_SDA_LINE_NUMBER, GPIO_Direction_Input);  /* GPIO 28 for data. */
		GPIO_setPullUp   (gpio, (GPIO_Number_e)GPIO_SDA_LINE_NUMBER, GPIO_PullUp_Enable);
		GPIO_setMode     (gpio, (GPIO_Number_e)GPIO_SDA_LINE_NUMBER, GPIO_28_Mode_SDDA);

		/* Initialise I2C bus peripheral. */
		i2c->I2CSAR   = 0U;			/* Clear slave address. */
		i2c->I2CMDR  |= I2C_I2CMDR_FREE_BIT; 	/* Stop free run on breakpoint for debug breakpoints. */
		i2c->I2CMDR  &= ~I2C_I2CMDR_BC_BITS;	/* Set bit count to 8 bits per byte. */
		i2c->I2CMDR  &= ~I2C_I2CMDR_MST_BIT;	/* Select slave mode. */
		i2c->I2COAR   = 0x50;

		i2c->I2CPSC   = 0x04U & I2C_I2CPSC_IPSC_BITS;	/* Prescalar = 4, so module clock = 12Mhz. */
		i2c->I2CCLKL  = 0x0AU;				/* T_low = T_mod * (ICCL + d) = 1.25 us. NB: Must be non-zero. */
		i2c->I2CCLKH  = 0x0AU;				/* T_high = T_mod * (ICCH + d) = 1.25 us. NB: Must be non-zero. */

		i2c->I2CIER  |= (uint16_t)I2C_IntEn_NACK;	/* Enable AAS, P, NACK and transmit and receive register ready interrupts. */
		i2c->I2CIER  |= (uint16_t)I2C_IntEn_Rx_Rdy;
		i2c->I2CIER  |= (uint16_t)I2C_IntEn_Tx_Rdy;
		i2c->I2CIER  |= (uint16_t)I2C_IntEn_Stop;
		i2c->I2CIER  |= (uint16_t)I2C_IntEn_Slave_Addr;

		i2c->I2CMDR  |= I2C_I2CMDR_IRS_BIT;	/* Enable I2C. */
		i2c->I2CSTR   = 0xFFFFU;			/* Clear the I2C status register. */

		/* Configure I2C interrupt. */
		PIE_registerPieIntHandler(pie, PIE_GroupNumber_8, PIE_SubGroupNumber_1, (intVec_t)&i2c_isr);
		PIE_enableInt(pie, PIE_GroupNumber_8, PIE_InterruptSource_I2CA1);
		CPU_enableInt(cpu, CPU_IntNumber_8);

The ISR for the I2C is currently very simple. It does nothing with received data except read it out of the receive register and when transmitting it should place some dummy data (0x55h) in the transmit register when a repeated start uses a read bit  and every time the register becomes empty after that, until the master sends a stop.

__interrupt static void i2c_isr (void) {
	I2C_Obj * i2c = (I2C_Obj *)i2cHandle;
	I2C_IntSource_e source = (I2C_IntSource_e)i2c->I2CISRC;
	I2C_Status_e status = (I2C_Status_e)i2c->I2CSTR;
	volatile uint_least8_t d = 0U;

	if (source == I2C_IntSrc_Slave_Addr) {
		flags.direction = ((status & I2C_Status_Slave_Dir) > 0) ? I2C_Direction_Read : I2C_Direction_Write;	/* Record the R/W# bit. */
		if (!flags.start) {			/* Check if this is the first START or if it is a REPEATED START. */
			flags.start = true;		/* Flag start of communication. */
			if (flags.direction == I2C_Direction_Read) {
				asm("    ESTOP0");	/* Halt if START has READ bit. */
			}
		} else if (flags.direction == I2C_Direction_Read) {
			i2c->I2CDXR = 0x0055U;	/* Transmit first byte after REPEATED START. */
		} else {
			asm("    ESTOP0");		/* Halt if REPEATED START has WRITE bit. */
		}
	} else if (source == I2C_IntSrc_Rx_Rdy) {
		d = i2c->I2CDRR;						/* Clear the receive register. */
	} else if (source == I2C_IntSrc_Tx_Rdy) {
		i2c->I2CDXR == 0x0055U;					/* Transmit some dummy data. */
	} else if (source == I2C_IntSrc_NACK) {
		asm("    ESTOP0");						/* Halt if NACK received. */
	} else if (source == I2C_IntSrc_Stop) {
		flags.start = false;						/* End of the communication, reset flags. */
		flags.direction = I2C_Direction_Write;
	} else {
		asm("    ESTOP0");		/* Incorrect interrupt source. */
	}
	PIE_clearInt(pie, PIE_GroupNumber_8);	/* Set the PIEACK bit for the PIE group. */
	return;
}

However, what actually happens, is the start and write portions of a communication works OK, as does the following repeated start, the first 0x55h is transmitted, but then the CLK line is held low indefinitely. The expected interrupt  for Tx_Rdy after the first 0x55h is transmitted never seems to occur! So the Tx_Rdy, NACK, Stop and else branches of the ISR's if are never taken...

Have I misunderstood how the I2C works in slave transmitter mode? Maybe it is down to the BCM setting?

Thanks for any help!

  • I had not enabled the TXRDY interrupt, so I have added that into the code shown above. Now the interrupts occur in the following order:

    AAS, RXRDY, TXRDY, AAS, TXRDY

    The first TXRDY actually seems to occur *before* the restart byte with the read bit. Also there is still no STOP or NACK.

    There has been no change in the output, it still hangs after reading the first byte from the slave :(

  • Hi Toby,

    Maybe im not seeing it clearly in your code, do you set the I2CCNT before loading DXR? or are you running in repeat mode? I went back to the TRM and found this paragraph:

    "If a device pulls down the clock line for a longer time, the result is that all clock generators must enter the
    wait state. In this way, a slave slows down a fast master and the slow device creates enough time to store
    a received byte or to prepare a byte to be transmitted."

     

    Looks like that is what the slave is doing, it might be pulling SCL low waiting to transfer another byte, which never gets loaded. Yoyu can check the XSMT == 0 to see if the slave transmitter is expecting more bytes to be loaded

     

    "As a slave-transmitter, the
    I2C module then shifts the serial data out on SDA with the clock pulses that are generated by
    the master. While a slave, the I2C module does not generate the clock signal, but it can hold
    SCL low while the intervention of the device is required (XSMT = 0 in I2CSTR) after a byte has
    been transmitted"

  • Hi Vishal,

    I had not set I2CCNT. I have now fixed it to 1, with little change. Below is my current code to which I have added a trace array to track what branches are or aren't taken in the ISR.

    It seems now that again, the WRITE to the slave works fine, but the repeated start AAS interrupt appears to have lower priority than the TX Ready interrupt. OK, so I can work around that by flagging the first TX per communication and ignoring the trailing AAS. However I still see an incorrect number of TX_Rdy interrupts which cause the output from the slave to hang the line while it waits for a byte to be loaded when no byte is available.

    E.g. If the slave is due to transmit a one byte response to the first communication after a device reset. Take the SMBus Read Byte protocol.

    The master writes the address witha  WRITE bit, then a data byte (the SMBus command code), then the master sends a repeat start and addresses again, this time with a READ bit. Then the device should respond with a single byte which the master receives then NACKS and STOPs.

    What I see happening for that last part however is that two TX ready interrupts occur, if I load data on the first and ignore the second, the first data byte is never sent, the slave just holds the clock as if it is still waiting on data. If I try ignoring the first and attempt to load on the second, the second never occurs so the byte never loads.... I guess because the device is still waiting on data for the first one.

    So the main issue is that even after I load the first byte, it does not appear to be sent out until I load a second byte.

    So why dont I just load two bytes? Well because then the second "dummy" byte is sent out at the start of the next communication as it's already sitting there in the register. Grrr

    I must not be understanding how the peripheral is intended to operate in slave-transmitter mode but I fail to see how else it can be achieved. Please advise!

    My configuration of the I2C:

    /* Setup I/O lines. */
    GPIO_setDirection(gpio, (GPIO_Number_e)GPIO_SCL_LINE_NUMBER, GPIO_Direction_Input);  /* GPIO 33 for clock. */
    GPIO_setPullUp   (gpio, (GPIO_Number_e)GPIO_SCL_LINE_NUMBER, GPIO_PullUp_Enable);
    GPIO_setMode     (gpio, (GPIO_Number_e)GPIO_SCL_LINE_NUMBER, GPIO_33_Mode_SCLA);
    GPIO_setDirection(gpio, (GPIO_Number_e)GPIO_SDA_LINE_NUMBER, GPIO_Direction_Input);  /* GPIO 28 for data. */
    GPIO_setPullUp   (gpio, (GPIO_Number_e)GPIO_SDA_LINE_NUMBER, GPIO_PullUp_Enable);
    GPIO_setMode     (gpio, (GPIO_Number_e)GPIO_SDA_LINE_NUMBER, GPIO_28_Mode_SDDA);
    
     
    /* Initialise I2C bus peripheral. */
    i2c->I2CSAR   = 0U;          /* Clear slave address. */
    i2c->I2CMDR  |= I2C_I2CMDR_FREE_BIT;     /* Stop free run on breakpoint for debug breakpoints. */
    i2c->I2CMDR  &= ~I2C_I2CMDR_BC_BITS; /* Set bit count to 8 bits per byte. */
    i2c->I2CMDR  &= ~I2C_I2CMDR_MST_BIT; /* Select slave mode. */
    i2c->I2COAR   = 0x50;
    
     
    i2c->I2CPSC   = 0x04U & I2C_I2CPSC_IPSC_BITS;    /* Prescalar = 4, so module clock = 12Mhz. */
    i2c->I2CCLKL  = 0x0AU;               /* T_low = T_mod * (ICCL + d) = 1.25 us. NB: Must be non-zero. */
    i2c->I2CCLKH  = 0x0AU;               /* T_high = T_mod * (ICCH + d) = 1.25 us. NB: Must be non-zero. */
    
     
    i2c->I2CIER  |= (uint16_t)I2C_IntEn_NACK;    /* Enable AAS, P, NACK and transmit and receive register ready interrupts. */
    i2c->I2CIER  |= (uint16_t)I2C_IntEn_Rx_Rdy;
    i2c->I2CIER  |= (uint16_t)I2C_IntEn_Tx_Rdy;
    i2c->I2CIER  |= (uint16_t)I2C_IntEn_Stop;
    i2c->I2CIER  |= (uint16_t)I2C_IntEn_Slave_Addr;
    
     
    i2c->I2CMDR  |= I2C_I2CMDR_IRS_BIT;  /* Enable I2C. */
    i2c->I2CSTR   = 0xFFFFU;         /* Clear the I2C status register. */
    
     
    /* Configure I2C interrupt. */
    PIE_registerPieIntHandler(pie, PIE_GroupNumber_8, PIE_SubGroupNumber_1, (intVec_t)&i2c_isr);
    PIE_enableInt(pie, PIE_GroupNumber_8, PIE_InterruptSource_I2CA1);
    CPU_enableInt(cpu, CPU_IntNumber_8);

    My I2C ISR:

    typedef enum {
        ISR_START = 0,
        AAS_int,
        Start_with_read,
        Start_with_write,
        RS_with_read,
        RS_with_write,
        Rx_int,
        Tx_int,
        Txing,
        NACK_int,
        Stop_int,
    } trace_t;
    
    trace_t trace[100] = {0};
    int16_t trace_index = 0;
    
    int16_t dummy_data[] = {0xAA, 0x11, 0x44};
    int16_t dummy_index = 0;
    
    __interrupt static void i2c_isr(void) {
    	I2C_IntSource_e source = (I2C_IntSource_e)i2c->I2CISRC;
    	I2C_Status_e status = (I2C_Status_e)i2c->I2CSTR;
    	uint_least8_t d = 0U;
    
    //	i2c->I2CSTR = 0x602FU;		/* Clear the I2C status register?. */
    
    	if (trace_index >= 80) trace_index = 0;
    	trace[trace_index++] = ISR_START;
    	trace[trace_index++] = (trace_t)status;
    
    	if (source == I2C_IntSrc_Slave_Addr) { 		/* Adressed as slave interrupt. */
    		trace[trace_index++] = AAS_int;
    		flags.direction = ((status & I2C_Status_Slave_Dir) > 0) ? CMD_Direction_Read : CMD_Direction_Write;	/* Record the R/W# bit. */
    		if (!flags.start) {
    			trace[trace_index++] = (flags.direction == CMD_Direction_Read) ? Start_with_read : Start_with_write;
    			flags.start = true;	/* Flag this START as the start of a new message. */
    		} else if (flags.direction == CMD_Direction_Read) {
    			trace[trace_index++] = RS_with_read;
    		} else {
    			trace[trace_index++] = RS_with_write;
    		}
    	} else if (source == I2C_IntSrc_Rx_Rdy) {	/* Received byte ready interrupt. */
    		trace[trace_index++] = Rx_int;
    		d = (uint_least8_t)i2c->I2CDRR;    /* Remove received data and discard it. */
    	} else if (source == I2C_IntSrc_Tx_Rdy) {
    		trace[trace_index++] = Tx_int;
    		if (!(status & I2C_Status_Slave_Dir)) {    /* ONLY LOAD TX DATA ON FIRST TX_RDY INTERRUPT */
    			trace[trace_index++] = Txing;
    			i2c->I2CDXR = dummy_data[dummy_index++];	/* Load a byte of the dummy data. */
    			if (dummy_index >= 3) dummy_index = 0;
    		}
    	} else if (source == I2C_IntSrc_NACK) {
    		trace[trace_index++] = NACK_int;
    	} else if (source == I2C_IntSrc_Stop) {
    		trace[trace_index++] = Stop_int;
    		i2c->I2CMDR &= ~I2C_I2CMDR_NAKMOD_BIT;		/* Clear any NACK. */
    		FLAGS_resetFlags((FLAGS_Handle)&flags); 	/* Clear flags. */
    	}
    
    	CPU_enableGlobalInts(cpu);		/* DINT (INTM = 1). */
    	PIE_clearInt(pie, PIE_GroupNumber_8);	/* Set the PIEACK bit for the PIE group. */
    	return;
    }

    If you add 'trace' to the expressions window and keep an eye on the I2CDXR register, you will see the behaviour I have described

  • I've changed the title of the thread as I dont seem to be able to reject Vishal's answer, that, although it provides helpful information, does not answer the issue.
    T

  • Thanks for alerting Toby. I've gone ahead and rejected the answer on your behalf. Normally when you click on it will immediately update.

  • Hi Blake,
    When I tried, it does update in the browser view asynchronously, but upon refresh it seems like the change was not saved as it constantly reverts to accepted.
    Thanks for rejecting it for me as that seems to have done the trick!
    T

    PS I've reverted the post title now the answer is rejected.

  • OK, so I've found that the I2CEMDR.BCM default value after a rest is 1!!!!

    I noticed this when scrolling through the user guide (SPURFZ9) and saw that the backwards compatibility mode timing diagram (Fig 17) looks very like the issue I am seeing.

    Clearing the BCM bit after a reset seems to have solved my issue (with my basic test code ISR at least).

    Why a backwards compatibility mode should be enabled by default is beyond me... But it may be worth adding some more prominent information about this fact to the next revision of the user guide
    T

  • Toby it is so great to see you've found a solution and as well documented here. Thank you so much for doing that! Feedback is noted as well.

  • No problem Blake.
    Another item of feedback in relation to the documentation is that of the I2C interrupt priorities.
    The fact that the I2C interrupts (AAS, NACK, etc) have priority is alluded to twice in the documentation, but as far as I can tell, there is nowhere it actually specifies what the priority order is. Might be good to add that information as well :)
    T

  • "Why a backwards compatibility mode should be enabled by default is beyond me"

    I'm not roaming this forum normally, but now that I stumbled across this post...

    Backwards compatibility should be _always_ enabled by default. The reason is clear: code that requires backwards compatibility usually is not forward-compatible. It doesn't know that backwards compatibility has to be enabled. OTOH, newer code that knows of the incompatible change can well know that it has to disable backwards compatibility first to profit form the changes.

    On the MSP430F5438A, a new reference module was introduced, which was overriding the reference voltage controls of the ADC. Here too, the new module could be switched off into backwards compatibility mode and return control to the ADC. If this had been the case by default, old code, compiled for the MSP430F5438 (non-A), would have worked 1:1 on the 'replacement' CPU. But since backwards compatibility was off by default, the code had to be updated and recompiled, making the backwards compatibility completely useless.
  • You make a good argument for it there Jens! :D