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 reset 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 reset 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.