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.

MSP432P401R: eUSCI interrupt servicing suboptimal/broken

Part Number: MSP432P401R

On various eUSCI parts an IV (interrupt vector) is defined that gives a numerical code that denotes which of the various interrupt sources for the actual vector has asserted, is enabled, and is the highest priority.  This is good.

However, reading this register has the side-effect of clearing the IFG associated with said IV.   This is bad.  Let me show why...

For example, let's talk about the transmission side of an eUSCI UART.  By definition, TXIFG being a 1 (asserted) indicates that the TXBUF is empty and another byte can be written to the TXBUF for transmission.  This is pretty fundamental.  If TXIFG is 0 one shouldn't write to TXBUF.

But now consider the following interrupt driven transmit code for an eUSCI UART.

First the sender:

error_t gps_send_block(uint8_t *ptr, uint16_t len) {
  if (!len || !ptr)
    return FAIL;

  if (m_tx_buf)
    return EBUSY;

  m_tx_len = len;
  m_tx_idx = 0;
  m_tx_buf = ptr;
  /*
   * On start up UCTXIFG should be asserted, so enabling the TX interrupt
   * should cause the ISR to get invoked.
   */
  //call Usci.enableTxIntr();
  BITBAND_PERI(EUSCI_A2->IE, EUSCI_A_IE_TXIE_OFS) = 1
  return SUCCESS;
}

m_tx_buf is used to hold the pointer to the buffer we want to transmit, m_tx_idx is where we are in that buffer (next byte to transmit), and m_tx_len is how many bytes we want to transmit.

We use the fact that TXIFG should be pending with an empty TXBUF to start us off as soon as we turn on the interrupt.

And here is the interrupt service routine.   Only the TX portion is shown.

void EUSCIA2_Handler() @C() @spontaneous() __attribute__((interrupt)) {
  uint8_t data;
  uint8_t *buf;

  switch(EUSCI_A2->IV) {
    case MSP432U_IV_TXIFG:
      if (m_tx_idx >= m_tx_len) {
        buf = m_tx_buf;
        // Usci.disableTxIntr();
        BITBAND_PERI(EUSCI_A2->IE, EUSCI_A_IE_TXIE_OFS) = 0;
        m_tx_buf = NULL;
        gps_send_block_done(buf, m_tx_len, SUCCESS);
        return;
      }
      data = m_tx_buf[m_tx_idx++];
      // Usci.setTxbuf(data);
      EUSCI_A2->TXBUF = data;
      return;
  }
}

Now on the last byte, we will interrupt, m_tx_idx will be m_tx_len - 1, we will stuff the last byte into the TXBUF and return from the interrupt.  When the last byte has been transfered from TXBUF into the shift register for transmission, TXIFG will come up again and we will interrupt.

By reading the IV register in the switch statement, TXIFG will be cleared.  We will see that m_tx_idx is >= m_tx_len and enter the termination clause.  We will disable the TxInterrupt and do the call out indicating that the transmission has completed.

But note that TXBUF is empty and TXIFG is clear saying TXBUF is full.  That is wrong.  This a direct result of the side effect of clearing TXIFG whne reading EUSCI_A2->IV and the TXIFG interrupt is the highest priority.  If we try to fire up another send_block it will fail because when we turn on the interrupt the TXIFG is down and we won't enter the interrupt routine to start the new send up.  TXIFG will never come up again because there is nothing that would kick it.  We could write to TXBUF and that would eventually get things working again, but that technically is a no-no because one should write to TXBUF unless TXIFG being 1 says that TXBUF is empty.

Work arounds:

1) reorder the interrupt routine to be something like:

void EUSCIA2_Handler() @C() @spontaneous() __attribute__((interrupt)) {
  uint8_t data;
  uint8_t *buf;

  switch(EUSCI_A2->IV) {
    case MSP432U_IV_TXIFG:
      data = m_tx_buf[m_tx_idx++];
      // Usci.setTxbuf(data);
      EUSCI_A2->TXBUF = data;
      if (m_tx_idx >= m_tx_len) {
        buf = m_tx_buf;
        // Usci.disableTxIntr();
        BITBAND_PERI(EUSCI_A2->IE, EUSCI_A_IE_TXIE_OFS) = 0;
        m_tx_buf = NULL;
        gps_send_block_done(buf, m_tx_len, SUCCESS);
      }
      return;
  }
}

So when the last byte is written, we detect this and disable interrupts.   Because interrupts are disabled for TXIFG, when the hardware finishes sending the byte in the shift register and copies TXBUF down to the shift register, TXBUF will go empty, TXIFG will go to a 1, and no interrupt will happen.  Because no interrupt occurs, EUSCI_A2->IV isn't read and the problem doesn't occur.  There is a valid state, meaning TXBUF is empty and TXIFG is asserted indicating that this is the case.

So what is the problem with this work around.  When we signal completion, the hw state is TXBUF is full and TXIFG is deasserted.  Signalling completion indicates that the current send is done and you can fire up another one if you want.  If another send is immediately fired up, it will have to wait for the still in progress prior send to complete.  This will be one byte time which at slow baud rates can be significant.

But it can work and is what I am currently doing on my MSP432 based designs.

2) Replace the IFG that the IV read killed.

In the first example interrupt handler, place BITBAND_PERI(EUSCI_A2->IFG,EUSCI_A_IFG_TXIFG_OFS) = 1.  This will reassert the TXIFG.

However, it is unclear how this interacts with the rest of the hardware.  The BITBAND operation will instruct the memory system to do an atomic access to the peripheral register holding the TXIFG flag.  But what isn't clear is how the rest of the eUSCI h/w interacts with this register.  For example, if an RXIFG needs to be set (or STTIFG, start interrupt flag), is it possible to lose this flag because of interactions with the memory system setting the IFG register on behalf of the TXIFG write?

In other words, ensuring there isn't a h/w race condition is problematic.

In conclusion, it is problematic/sub-optimal having the h/w interrupt system automatically clear the TXIFG flag.  Doing so creates an invalid state for the h/w in question causing problems.

It would be best in the future to not do that kind of thing.

  • Hello,

    In your application, is it possible to simply check the TXIFG without reading the UCAxIV?
    If you are servicing a number of flags in the USCI handler it may not be efficient, but if you are only using TX & RX it could be considered?

    -Priya
  • Priya Thanigai said:
    Hello,

    In your application, is it possible to simply check the TXIFG without reading the UCAxIV?
    If you are servicing a number of flags in the USCI handler it may not be efficient, but if you are only using TX & RX it could be considered?

    -Priya

    that will work.  i should have thought of that.  getting old  (sigh).

    thanks

    also please pass my comments on to your system architects.  its why i posted the full write up.  (my background is computer architecture and system design)

  • Thanks for getting back to us! This feedback is very useful & I will pass it to our internal teams.
    I believe we are already doing it differently in future generation devices.
  • Priya Thanigai said:
    Thanks for getting back to us! This feedback is very useful & I will pass it to our internal teams.
    I believe we are already doing it differently in future generation devices.

    if they would like an external review, I'd be open to spending time looking at it.

    just a thought.

  • Another suggestion, which may work better. Setting and clearing the UCSWRST bit will help recover that last (lost) TXIFG.
    If you can set the UCSWRST when you disable TXIE and clear it before you re-enable, that first TXIFG should be asserted again.
    From the TRM, section 22.3.1,

    The eUSCI_A is reset by a Hard Reset or by setting the UCSWRST bit. After a Hard Reset, the UCSWRST bit is automatically set, keeping the eUSCI_A in a reset condition. When set, the UCSWRST bit sets the UCTXIFG bit and resets the UCRXIE, UCTXIE, UCRXIFG, UCRXERR, UCBRK, UCPE, UCOE, UCFE, UCSTOE, and UCBTOE bits. Clearing UCSWRST releases the eUSCI_A for operation.
  • Priya Thanigai said:
    Another suggestion, which may work better. Setting and clearing the UCSWRST bit will help recover that last (lost) TXIFG.
    If you can set the UCSWRST when you disable TXIE and clear it before you re-enable, that first TXIFG should be asserted again.
    From the TRM, section 22.3.1,

    The eUSCI_A is reset by a Hard Reset or by setting the UCSWRST bit. After a Hard Reset, the UCSWRST bit is automatically set, keeping the eUSCI_A in a reset condition. When set, the UCSWRST bit sets the UCTXIFG bit and resets the UCRXIE, UCTXIE, UCRXIFG, UCRXERR, UCBRK, UCPE, UCOE, UCFE, UCSTOE, and UCBTOE bits. Clearing UCSWRST releases the eUSCI_A for operation.

    thanks for trying to think of solutions.

    Hitting SWRST is a rather big hammer.  The problem with UCSWRST is it kills everything including any potential receive that is in progress.

    Your first suggestion is the way to go.

**Attention** This is a public forum