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.

MSP430FR5969: USCIB0 I2C write command sequence for repeated start

Part Number: MSP430FR5969


Hi,

When's the correct time to set a repeated start command for the USCIB0 in I2C mode? When I set UCTXSTT=1 during the TX ISR, the system issues a repeated start before transmitting the data, even though the state diagram shows it should issue the start afterwards. See diagram.

What I'm trying to do in the TX ISR is:

1. put data into UCB0TXBUF

2. change slave address

3. set UCTR = 1 or UCTR = 0, depending upon my needs for the next command

4. set UCTXSTT=1

But this just produces a repeated start before sending the data and also skips about half of the data (see IMAGE 1 below).

I'm attempting to send this message:

    		0x0040>>1 	| IO::USCI_I2C::ADDR,	// set address
    		0x0003 		| IO::USCI_I2C::WRITE,	// set ptr
    		0x00FF 		| IO::USCI_I2C::WRITE,	// write to register
		0x0040>>1	| IO::USCI_I2C::ADDR,	// set address
    		0x0001 		| IO::USCI_I2C::WRITE,	// set ptr
    		0x00AA 		| IO::USCI_I2C::WRITE,	// write to register

    		0x0046>>1 	| IO::USCI_I2C::ADDR,	// set address
    		0x0003 		| IO::USCI_I2C::WRITE,	// set ptr
    		0x00FF 		| IO::USCI_I2C::WRITE,	// write to register
		0x0046>>1	| IO::USCI_I2C::ADDR,	// set address
    		0x0001 		| IO::USCI_I2C::WRITE,	// set ptr
    		0x00AA 		| IO::USCI_I2C::WRITE	// write to register

IMAGE 1: Here's the capture without a delay (incorrect):

I found that if I add a delay in the ISR, the data is in fact transmitted and then the start is issued. 

	// 3. RESTART w/ new address
	else if(isAddr(nextCmd))
	{
		UCB0I2CSA = (uint8_t)nextCmd;
		/* "Setting UCTXSTT generates a repeated START condition. In this case,
		 UCTR may be set or cleared to configure transmitter or receiver,
		 and a different slave address may be written into UCBxI2CSA, if
		 desired." */
		// to accomplish this, send a start write or start read command
		// this will set the UCTR flag appropriately and set the start bit

		// increment counter past address cmd to get to next rd/wr command
		// (we need to peek to see if it's a read or a write).
		_delay_cycles(2000); // with this, the code will execute as expected, issuing a start AFTER the data is transmitted
		nextCmd = seq[++seqCtr];
		if(isWrite(nextCmd)) startWr();
		else if(isRead(nextCmd)) startRd();
	}

Code on github:  github.com/.../cribbage_LED

Here's a capture with delay (correct)

Here's my ISR an accompanying code.

/*	USCII2C.h
 *  Created on: Dec 26, 2016
 *      Author: benny		 */
#ifndef USCII2C_H_
#define USCII2C_H_

// include to use standard types
#include <stdint.h>

namespace IO
{
	class USCI_I2C
	{
	public:
		// used as bitmasks to check addresses
		enum TRANSACTION_TYPE {
			ADDR = 1<<8,
			READ = 1<<9,
			WRITE = 1<<10
		};
		// maybe try this later to replace TRANSACTION_TYPE
//		union I2C_TRANSACTION
//		{
//			uint16_t packet;
//			uint8_t data;
//			uint8_t isAddr:	1;
//			uint8_t isRd: 	1;
//			uint8_t isWr: 	1;
//		};
		USCI_I2C();
		// initialize the I2C hardware and member variables
		// currently just use MCLK as the clock source
		// busy counts will prevent an infinite loop if the bus becomes permanently busy
		// set to -1 to disable.
		void init(double F_MCLK, double F_I2C, uint8_t defaultAddress,
				unsigned busyCnts = 0, volatile unsigned char *SEL_PORT = 0,
				uint8_t PINS = 0);
		//
		void transaction(uint16_t *seq, uint16_t seqLen,
				uint8_t *recvData, uint16_t wakeupSRBits);
		// Use this to check whether a previously scheduled I2C sequence has been
		// fully processed.
		inline bool done() { return(state == IDLE); };
		// returns true if an acknowledge was received for the given address
		// ported from TI_USCI_I2C_slave_present
		// 	- made to work for eUSCI
		//	- replaced master-slave terminology w/ coordinator-client
		bool checkAddr(uint8_t addr);
		// used by ISR
		inline void handleTxRxInt(bool isWrInt);
		inline void startSeq();
	private:
		// check the flag set in the data packet
		inline bool isAddr(uint16_t seq)	{ return seq & ADDR; };
		inline bool isWrite(uint16_t seq) 	{ return seq & WRITE; };
		inline bool isRead(uint16_t seq) 	{ return seq & READ; };
		// start a write command (Coordinator-sender)
		inline void startWr();
		// start a read command (Coordinator-receiver)
		inline void startRd();
		inline void waitForBusFree();

		// used by the state machine
		// set even values to each state to allow quick processing
		// in ISR using the __even_in_range intrinsic
		// TODO: do I need this, or was this just for USI?
		enum STATE {
			IDLE = 0,
			START = 2,
			PREPARE_ACKNACK = 4,
			HANDLE_RXTX = 6,
			RECEIVED_DATA = 8,
			PREPARE_STOP = 10,
			STOP = 12
		} state;
		uint8_t defAddr;	// default I2C address
		uint16_t *seq;
		uint16_t seqLen, seqCtr;
		uint8_t *recvData;
		uint16_t wakeupSRBits;
		unsigned busyCnts;
	};
	// externally defined object required for use in the interrupt
	extern USCI_I2C i2c;
} /* namespace IO */

#endif /* USCII2C_H_ */

/* 	USCII2C.cpp
 *  Created on: Dec 26, 2016
 *      Author: benny
 */
// include msp430 header to get access to USCI registers
#include "msp430.h"
#include "USCII2C.h"
#include <cassert>
#include <stdio.h>

IO::USCI_I2C::USCI_I2C()
{
	this->state = IDLE;
}
void IO::USCI_I2C::init(double F_MCLK, double F_I2C, uint8_t defaultAddress,
		unsigned busyCnts, volatile unsigned char *SEL_PORT, uint8_t PINS)
{
	this->busyCnts = busyCnts;
	if(this->busyCnts == 0) this->busyCnts = 1;
	this->defAddr = defaultAddress;
	// configure I2C pins, e.g. P1SEL1 |= (BIT6 | BIT7)
	*SEL_PORT |= PINS;
	// put eUSCI_B in reset state while we config it
	UCB0CTLW0 = UCSWRST;
	// use SMCLK as clock source, I2C Mode, send I2C stop
	UCB0CTLW0 |= UCMODE_3 | UCMST | UCSSEL__SMCLK;
	assert(F_MCLK/F_I2C > 1);
	UCB0BRW = F_MCLK/F_I2C;	// set I2C frequency
	UCB0I2CSA = defaultAddress; 	// client address
	UCB0CTLW0 &= ~UCSWRST; 	// put eUSCI_B in operational state
	// enable TX interrupt and NACK interrupt
	UCB0IE |= UCTXIE0 | UCNACKIE;
}

void IO::USCI_I2C::waitForBusFree() {
	unsigned cnt = 0;
	while (UCB0STAT & UCBBUSY)
	{
		// increment counter
		busyCnts++;
		// if counter hits threshold, alert user
		if(cnt == busyCnts)
		{
			printf("oops! I2C bus frozen\n");
			assert(0);
		}
	}
}

void IO::USCI_I2C::transaction(uint16_t *seq, uint16_t seqLen,
		uint8_t *recvData, uint16_t wakeupSRBits)
{
	// we can't start another sequence until the current one is done
	if(UCB0STAT & UCBBUSY)
		// send a stop
		UCB0CTLW0 |= UCTXSTP;
	waitForBusFree();
	// load the sequence into the library:
	assert(seq);	// ensure we have a sequence
	this->seq = seq;
	assert(seqLen);	// ensure we have a seqLength
	this->seqLen = seqLen;
	// no assert, could be a null ptr iff only writing
	this->recvData = recvData;
	// no assert, could not be waking up
	this->wakeupSRBits = wakeupSRBits;
	// update status
	seqCtr = 0;
	state = START;
	// start the sequence transmission, trigger, but don't set data yet
	startSeq();
	// exit and handle the transaction from interrupts
}
inline void IO::USCI_I2C::startWr()
{
	UCB0CTLW0 |= UCTR | UCTXSTT;
}
inline void IO::USCI_I2C::startRd()
{
	UCB0CTLW0 &= ~UCTR; UCB0CTLW0 |= UCTXSTT;
}
bool IO::USCI_I2C::checkAddr(uint8_t addr)
{
	uint8_t clientAddrBak, UCB0IEBak;
	bool present;
	UCB0IEBak = UCB0IE;                      // restore old UCB0I2CIE
	clientAddrBak = UCB0I2CSA;                   // store old slave address
	UCB0IE &= ~ UCNACKIE;                    // no NACK interrupt
	UCB0I2CSA = addr;                  // set slave address
	UCB0IE &= ~(UCTXIE0 | UCRXIE0);              // no RX or TX interrupts
	// disable interrupts so we can handle all interrupt flags here
	// and not run any of the ISR code
	__disable_interrupt();
	UCB0CTLW0 |= UCTR | UCTXSTT | UCTXSTP;       // I2C TX, start condition
	while (UCB0CTLW0 & UCTXSTP);                 // wait for STOP condition
	UCB0CTLW0 |= UCTXSTP;
	present = !(UCB0IFG & UCNACKIFG);
	UCB0IFG = 0x00;		// clear the interrupts
	__enable_interrupt();
	UCB0I2CSA = clientAddrBak;                   // restore slave address
	UCB0IE = UCB0IEBak;                   // restore interrupts
	return present;
}
inline void IO::USCI_I2C::startSeq()
{
	uint16_t curSeq = seq[seqCtr];
	// 1. check for an address byte
	if(isAddr(curSeq))
	{
		UCB0I2CSA = (uint8_t)curSeq;
		curSeq = seq[++seqCtr];	// increment and process the next sequence entry
	}
	// 2a. check for a data read byte
	if(isRead(curSeq)) startRd();
	// 2b. check for a data write byte
	else if(isWrite(curSeq)) startWr();
}
inline void IO::USCI_I2C::handleTxRxInt(bool isWrInt)
{
	// TODO: make class private variable?
	uint16_t curCmd = seq[seqCtr];
	// use this to prepare for the next command.
	// Don't set yet because we could be at the end of the sequence.
	uint16_t nextCmd;
	//////////////////////
	// process current command:
	//////////////////////
	// check for a data write byte
	if(isWrite(curCmd))
	{
		// write data from the sequence entry to the transmitter buffer
	//	UCB0TXBUF = (uint8_t)curSeq; // causes intermittent data loss :o cmds get truncated to 8 bits!
		UCB0TXBUF = curCmd;
	}
	// check for a data read byte
	else if(isRead(curCmd))
	{
		// TODO: grab data from register
		unsigned dataRead = UCB0RXBUF;
	}

	//////////////////////
	// prepare for next command
	//////////////////////
	// check for an impending stop or start
	// 1. STOP: end of sequence encountered - check for end of sequence
	if (seqCtr == seqLen)
	{
		// send a stop
		UCB0CTLW0 |= UCTXSTP;
		// set status to idle so user knows we're ready for a new sequence
		this->state = IDLE;
		return;
	}
	// no stop yet, load the next command in the sequence
	seqCtr++;
	nextCmd = seq[seqCtr];
	// 2. RESTART w/ current address:
	// 2.a. read->write
	if(isWrite(nextCmd) & !isWrInt)
	{
		startWr();
	}
	// 2.b. write->read
	else if(isRead(nextCmd) & isWrInt)
	{
		startRd();
	}
	// 3. RESTART w/ new address
	else if(isAddr(nextCmd))
	{
		UCB0I2CSA = (uint8_t)nextCmd;
		/* "Setting UCTXSTT generates a repeated START condition. In this case,
		 UCTR may be set or cleared to configure transmitter or receiver,
		 and a different slave address may be written into UCBxI2CSA, if
		 desired." */
		// to accomplish this, send a start write or start read command
		// this will set the UCTR flag appropriately and set the start bit

		// increment counter past address cmd to get to next rd/wr command
		// (we need to peek to see if it's a read or a write).
		_delay_cycles(2000); // with this, the code will execute as expected, issuing a start AFTER the data is transmitted
		nextCmd = seq[++seqCtr];
		if(isWrite(nextCmd)) startWr();
		else if(isRead(nextCmd)) startRd();
	}
}

// I2C ISR
// address I2C interrupts here, including updating the
// transmission buffer register with the next byte
// to send
#pragma vector=USCI_B0_VECTOR
__interrupt void EUSCI_B0(void)
{
	switch(__even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG))
	{
	case USCI_NONE:          break;     // Vector 0: No interrupts
	case USCI_I2C_UCALIFG:   break;     // Vector 2: ALIFG
	case USCI_I2C_UCNACKIFG:            // Vector 4: NACKIFG - client NACK'd
		UCB0CTLW0 |= UCTXSTT;           // resend start and address
		break;
	case USCI_I2C_UCSTTIFG:  break;     // Vector 6: STTIFG
	case USCI_I2C_UCSTPIFG:  break;     // Vector 8: STPIFG
	case USCI_I2C_UCRXIFG3:  break;     // Vector 10: RXIFG3
	case USCI_I2C_UCTXIFG3:  break;     // Vector 12: TXIFG3
	case USCI_I2C_UCRXIFG2:  break;     // Vector 14: RXIFG2
	case USCI_I2C_UCTXIFG2:  break;     // Vector 16: TXIFG2
	case USCI_I2C_UCRXIFG1:  break;     // Vector 18: RXIFG1
	case USCI_I2C_UCTXIFG1:  break;     // Vector 20: TXIFG1
	case USCI_I2C_UCRXIFG0: 		    // Vector 22: RXIFG0 - received data is ready
		IO::i2c.handleTxRxInt(false);
		break;
	case USCI_I2C_UCTXIFG0:             // Vector 24: TXIFG0
		// we completed a transaction, check for the next cmd
		IO::i2c.handleTxRxInt(true);
		break;
	default: break;
	}

	__bic_SR_register_on_exit(LPM0_bits); // Exit LPM0
}
#include <msp430.h> 
#include <intrinsics.h>
#include "InputHandler.h"
#include "cribbage_LED.h"
// TODO: remove, this is just for debugging I2C
#include "USCII2C.h"
// include to use standard types
#include <stdint.h>
// switch to turn off use of features on the launchpad
// E.g. LED's and buttons on the launchpad
#define LAUNCHPAD

IO::InputPin
		UP		(1, 1, 0, IO::PULLUP::UP),
		DOWN	(4, 5, 0, IO::PULLUP::UP),
		RIGHT	(1, 2, 0, IO::PULLUP::UP),
		LEFT	(1, 3, 0, IO::PULLUP::UP),
		BACK	(1, 4, 0, IO::PULLUP::UP),
		ENTER	(1, 5, 0, IO::PULLUP::UP);

// local function declarations
void setUpTimers(const double F_CLK, const double F_PIN_INTERRUPT);
void setUpPins(const double F_PIN_INTERRUPT);

int main(void)
{
	Cribbage::Controller game;
	const unsigned F_PIN_INTERRUPT = 500; // pin interrupt frequency [Hz]
	const unsigned F_ACLK = 10e3;
	const double F_MCLK = 8e6;			// desired MCLK frequency [Hz]

	WDTCTL = WDTPW | WDTHOLD;	// Stop watchdog timer

	// unlock clock system (CS)
	CSCTL0 = CSKEY;
	// set MCLK frequency to 8MHz (DCOFSEL = "110b = If DCORSEL = 0")
	CSCTL1 |= DCOFSEL2_L | DCOFSEL1_L;
	// VLO -> ACLK, DCO -> MCLK,
	CSCTL2 |= SELA__VLOCLK | SELM__DCOCLK;

    // setup timers for input debouncing
    setUpTimers(F_ACLK, F_PIN_INTERRUPT);
    // init inputs
    setUpPins(F_PIN_INTERRUPT);
	// clear lock on port settings
    PM5CTL0 &= ~LOCKLPM5;

    // setup and check HW:
    game.sysInit(F_MCLK);

    // TODO: remove, this is just for debugging I2C
    uint16_t dummyTransaction[] =
    {
    		0x0040>>1 	| IO::USCI_I2C::ADDR,	// set address
    		0x0003 		| IO::USCI_I2C::WRITE,	// set ptr
    		0x00FF 		| IO::USCI_I2C::WRITE,	// write to register
			0x0040>>1	| IO::USCI_I2C::ADDR,	// set address
    		0x0001 		| IO::USCI_I2C::WRITE,	// set ptr
    		0x00AA 		| IO::USCI_I2C::WRITE,	// write to register

    		0x0046>>1 	| IO::USCI_I2C::ADDR,	// set address
    		0x0003 		| IO::USCI_I2C::WRITE,	// set ptr
    		0x00FF 		| IO::USCI_I2C::WRITE,	// write to register
			0x0046>>1	| IO::USCI_I2C::ADDR,	// set address
    		0x0001 		| IO::USCI_I2C::WRITE,	// set ptr
    		0x00AA 		| IO::USCI_I2C::WRITE	// write to register
    };
	__enable_interrupt();
	while(1)
	{
		_delay_cycles(1000);
		if(UP.read())
		{
			P1OUT ^= BIT0;
			IO::i2c.transaction(dummyTransaction, sizeof(dummyTransaction)/sizeof(dummyTransaction[0]), 0, 0);
		}
		if(DOWN.read())
		{
			P4OUT ^= BIT6;
		}
		_BIS_SR(LPM0_bits);	// enter LPM0
	}

//	game.run();
	return 0;
}

//////////////////////////////////////////////////////////////
// main support functions and ISRs
//////////////////////////////////////////////////////////////
void setUpTimers(double F_CLK, double F_PIN_INTERRUPT)
//void setUpTimers(const double F_CLK)
{
	TA0CCR0 = F_CLK/8/F_PIN_INTERRUPT;
	TA0CCR1 = 0xFFFF;
	TA0CCR2 = 0xFFFF;
	// SMCLK, /8, count to CCR0, enable interrupts
	TA0CTL = TASSEL__ACLK | ID_3 | MC__UP | TAIE;
	// enable interrupt for TA0 CCR0
	TA0CCTL0 = CCIE;
}
void setUpPins(const double F_PIN_INTERRUPT)
//void setUpPins()
{
	double t_int_ms = 1.0 / (double)F_PIN_INTERRUPT * 1000.0;
	// link the pins defined here to cribbage library,
	// this allows the cribbage board to use the pins
	// for game control :)
	Cribbage::UP = &UP;
	Cribbage::DOWN = &DOWN;
	Cribbage::RIGHT = &RIGHT;
	Cribbage::LEFT = &LEFT;
	Cribbage::BACK = &BACK;
	Cribbage::ENTER = &ENTER;

	// initialize the input pins
	UP.init(	25.0,	3.0,	100.0,	t_int_ms);
	DOWN.init(	25.0,	3.0,	100.0,	t_int_ms);
	RIGHT.init(	25.0,	3.0,	100.0,	t_int_ms);
	LEFT.init(	25.0,	3.0,	100.0,	t_int_ms);
	BACK.init(	25.0,	3.0,	100.0,	t_int_ms);
	ENTER.init(	25.0,	3.0,	100.0,	t_int_ms);
#ifdef LAUNCHPAD
    // these pins are for debugging on the launchpad only
	P1DIR |= BIT0;
	P4DIR |= BIT6;
#endif
}

// disable WDT before any initialization takes place to prevent
// WDT reset during initialization of memory
int _system_pre_init(void)
{
  WDTCTL = WDTPW | WDTHOLD;
  return 1;
}

// ISR's
// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
#pragma vector=TIMER0_A1_VECTOR	// why do I need this???
__interrupt void Timer_A (void)
{
	// check for debounce interrupt TA0IV;
	if(TA0IV & TA0IV_TAIFG)
	{
		UP.debounce();
		DOWN.debounce();
		RIGHT.debounce();
		LEFT.debounce();
		BACK.debounce();
		ENTER.debounce();
	}
    __bic_SR_register_on_exit(LPM0_bits); // Exit LPM0
}

  • There are two transmit registers, TXBUF and the internal shift register. As shown in figure 26-12 of the User's Guide, the TXIFG interrupt happens while one byte is still being transmitted.

    The interrupt handler is responsible for doing whatever comes after that byte. It must either write the next byte, or set UCTXSTT(/UCTXSTP), but not both.

  • Hi Clemens, thanks for your advice. I got it to work by setting the start bit and then loading the next byte into the TX buffer. Previously I was loading the next byte and then setting the start bit. This was out of order.
    Regards,
    Ben

**Attention** This is a public forum