Home > Software design >  Why are all irq disabled for retarget write on STM32?
Why are all irq disabled for retarget write on STM32?

Time:05-11

I am looking at the following function which retargets stdout to UART in the STM32 Std peripheral library.

int _write(int fd, char *ptr, int len) {
  uint32_t primask = __get_PRIMASK();
  __disable_irq();

  for (int i = 0; i < len; i  ) {
    while (USART_GetFlagStatus(RETARGET_CFG_UART, USART_FLAG_TXE) == RESET) {
    }
    USART_SendData(RETARGET_CFG_UART, (uint8_t) * (ptr   i));
  }

  if (!primask) {
    __enable_irq();
  }

  return len;
}

Before transmitting over UART it masks exceptions which can have a priority set via __disable_irq() (which I understand includes all peripheral and GPIO interrupts).

My question is, why is this UART tx implemented this way? Can the disable/enable irq calls be removed so that other functionality in the program is not delayed due to potentially lengthy data transactions?

CodePudding user response:

I suspect the author of that code is disabling all interrupts just because there might be some interrupts that write bytes to the UART, and the author wants to ensure that all the bytes sent to the _write function get written consecutively, instead of having other, unrelated bytes in the middle.

I'm assuming all the ISRs defined in your system are relatively quick compared to the serial transmission, and the receiver doesn't care if there are some small delays in the transmission. So it should be OK to leave any interrupts enabled if those interrupts are not related to the UART.

You should get a firm grasp of all the ISRs in your system and what they do, and then you should use that to decide which specific IRQs should be disabled during this operation (if any).

CodePudding user response:

It's a quasi-bad design.

For one thing, always calling __disable_irq but not __enable_irq is suspicious.

It prevents nested calls where the caller is also doing __disable_irq/__enable_irq.

It assumes that a Tx ISR is not involved. That is, the UART is running in polled mode. But, if that were true, why did the code disable/enable interrupts?

If there is more data to send to the UART Tx than can be put into the Tx FIFO, the code [as you've seen] will block.

This means that the base/task level can't do other things.


When I do such UART code, I use a ring queue. I define one that is large enough to accomodate any bursts (e.g. queue is 10,000).

The design assumes that if the _write is called and the ring queue becomes full before all data is added, the ring queue size should be increased (i.e. ring queue full is a [fatal] design error).

Otherwise, the base/task level is trying to send too much data. That is, it's generating more data than can be sent at the Tx baud rate.

The _write process:

  1. Copy bytes to the ring queue.
  2. Copy as many bytes from the queue to the UART as space in the UART Tx FIFO allows.
  3. Tx ISR will be called if space becomes available. It repeats step (2)

With this, _write will not block if the UART Tx FIFO becomes full.

The Tx ISR will pick up the slack. That is, when there is more space available in the FIFO, and the Tx is "ready", the Tx ISR will be called.


Here is some pseudo code to illustrate what I mean:

// push_tx_data -- copy data from Tx ring queue into UART Tx FIFO
int
push_tx_data(void)
{

    // fill Tx FIFO with as much data as it can hold
    while (1) {
        // no free space in Tx FIFO
        if (USART_GetFlagStatus(RETARGET_CFG_UART, USART_FLAG_TXE) != RESET)
            break;

        // dequeue byte to transmit (stop if queue empty)
        int i = tx_queue_dequeue();
        if (i < 0)
            break;

        // put this data byte into the UART Tx FIFO
        uint8_t buf = i;
        USART_SendData(RETARGET_CFG_UART, buf);
    }
}

// tx_ISR -- handle interrupt from UART Tx
void
tx_ISR(void)
{

    // presumeably interrupts are already disabled in the ISR ...

    // copy all the data we can
    push_tx_data();

    // clear the interrupt pending flag in the interrupt controller or ISR if
    // necessary (i.e.) USART_SendData didn't clear it when the FIFO was full
}

// _write -- send data
int
_write(int fd, char *ptr, int len)
{
    uint32_t primask = __get_PRIMASK();

    // running from task level so disable interrupts
    if (! primask)
        __disable_irq();

    int sent = 0;

    // add to [S/W] tx ring queue
    for (int i = 0; i < len; i  ) {
        if (! tx_queue_enqueue(ptr[i]))
            break;
          sent;
    }

    // send data from queue into FIFO
    push_tx_data();

    // reenable interrupts (task level)
    if (! primask)
        __enable_irq();

    return sent;
}
  • Related