Part Number:MSP430FR5969
Hi,
When's the correct time to set a repeated start command for the USCIB0 in I2C mode? When I set a start in 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. I found that if I add a delay in the ISR, the data is in fact transmitted and then the start is issued.
Code on github: github.com/.../cribbage_LED
Here's the capture without a delay (not working):
Here's a capture with delay (working)
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
}