Why are all irq disabled for retarget write on STM32? - c

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?

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

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:
Copy bytes to the ring queue.
Copy as many bytes from the queue to the UART as space in the UART Tx FIFO allows.
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

uart receive interrupt waits for enter then continues after enter

I am trying to program a kind of cli with uart receive interrupts for an embedded system. I want my code to stay in uart function till i press the enter as '\r' then go on till it gets to the next uart function and wait for enter again. like some kind of scanf.
int main(void)
{
while (1)
{
uart_interrupt_receive();
//something else
uart_interrupt_receive();
//something else
}
I dont want to use scanf or getchar if possible. I got here so far. I cant decide which flags to use to make it work the way i want or how else i can change it?
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)(){
if (USART2->ISR & USART_ISR_RXNE) //is rx flag active
{
char rx = (char)(USART2->RDR & 0xFF); //received char rx
if ((rx == '\r') || (rx == '\n')) //if rx enter
{
//go on
}//if
else
{
//wait
}//else
}//if
}//uart_rxcplt_callback
Stalling the whole MCU inside an interrupt is not a great idea. Generally speaking, you should keep data reception and higher layer application logic separated if possible. There's two ways to deal with UART rx:
"Old school" way of interrupts where the hardware doesn't have much in the way of rx hardware buffers. In that case you would let the UART rx interrupt store the incoming character in a software ring buffer. This ring buffer needs protection against race conditions.
If DMA is supported then you can skip interrupts and DMA everything into a DMA buffer. Race condition considerations apply here too.
In the end your main application sits with a buffer of received data of some kind. It call poll that buffer until empty and do whatever the program should be doing based on data received.

Weirdness using the raspberry pi pico uart with the c sdk

I am extremely disconcerted.
I'm making a remote controlled machine using a pi pico to drive the motors and read some sensors, and a raspberry pi 4 to send commands to the pi pico via serial and host the web interface.
The following code seems to work... but... If I remove the if with uart_is_writable coding and its content it doesn't work. Does anyone have any idea why?
#include <stdlib.h>
#include <string.h>
#include "pico/stdlib.h"
#include "hardware/uart.h"
#include "hardware/irq.h"
//DEFINES
#define UART_ID uart0
#define BAUD_RATE 19200
#define DATA_BITS 8
#define STOP_BITS 1
#define PARITY UART_PARITY_NONE
#define UART_TX_PIN 0
#define UART_RX_PIN 1
#define LED_PIN PICO_DEFAULT_LED_PIN
static int chars_rxed = 0;
volatile char uCommand[32] = {0, 0};
void on_uart_rx(void) {
char tmp_string[] = {0, 0};
new_command = true;
while (uart_is_readable(UART_ID)) {
uint8_t ch = uart_getc(UART_ID);
tmp_string[0] = ch;
strcat(uCommand, tmp_string);
if(uart_is_writable(UART_ID)){
uart_putc(UART_ID, '-');
uart_puts(UART_ID, uCommand);
uart_putc(UART_ID, '-');
}
chars_rxed++;
}
}
int main(){
uart_init(UART_ID, BAUD_RATE);
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
uart_set_hw_flow(UART_ID, false, false);
uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);
uart_set_fifo_enabled(UART_ID, false);
int UART_IRQ = UART_ID == uart0 ? UART0_IRQ : UART1_IRQ;
irq_set_exclusive_handler(UART_IRQ, on_uart_rx);
irq_set_enabled(UART_IRQ, true);
uart_set_irq_enables(UART_ID, true, false);
uart_puts(UART_ID, "\nOK\n");
while (1){
tight_loop_contents();
if(uCommand[0] != 0){
uart_putc(UART_ID, '/');
uart_puts(UART_ID, uCommand);
memset(uCommand, 0, sizeof(uCommand));
}
}
}
The example code you linked to is for a simple tty/echo implementation.
You'll need to tweak it for your use case.
Because Tx interrupts are disabled, all output to the transmitter has to be polled I/O. Also, the FIFOs in the uart are disabled, so only single char I/O is used.
Hence, the uart_is_writable to check whether a char can be transmitted.
The linked code ...
echos back the received char in the Rx ISR. So, it needs to call the above function. Note that if Tx is not ready (i.e. full), the char is not echoed and is dropped.
I don't know whether uart_putc and uart_puts check for ready-to-transmit in this manner.
So, I'll assume that they do not.
This means that if you call uart_putc/uart_puts and the Tx is full, the current/pending char in the uart may get trashed/corrupted.
So, uart_is_writable should be called for any/each char to be sent.
Or ... uart_putc/uart_puts do check and will block until space is available in the uart Tx fifo. For you use case, such blocking is not desirable.
What you want ...
Side note: I have done similar [product/commercial grade] programming on an RPi for motor control via a uart, so some of this is from my own experience.
For your use case, you do not want to echo the received char. You want to append it to a receive buffer.
To implement this, you probably want to use ring queues: one for received chars and one for chars to be transmitted.
I assume you have [or will have] devised some sort of simple packet protocol to send/receive commands. The Rpi sends commands that are (e.g.):
Set motor speed
Get current sensor data
The other side should respond to these commands and execute the desired action or return the desired data.
Both processors probably need to have similar service loops and ISRs.
The Rx ISR just checks for space available in the Rx ring queue. If space is available, it gets a char from the uart and enqueues it. If no Rx char is available in the uart, the ISR may exit.
The base level code service loop should:
Check if the uart Tx can accept another character (via uart_is_writable) and, if so, it can dequeue a char from the Tx ring queue [if available] and send it (via uart_putc). It can loop on this to keep the uart transmitter busy.
Check to see if enough chars are received to form a packet/message from the other side. If there is such a packet, it can service the "request" contained in it [dequeueing the chars to make more space in the Rx ring queue].
If the base level needs to send a message, it should enqueue it to the Tx ring queue. It will be sent [eventually] in the prior step.
Some additional thoughts ...
The linked code only enables Rx interrupts and runs the Tx in polled mode. This may be enough. But, for maximum throughput and lowest latency, you may want to make the Tx interrupt driven as well.
You may also wish the enable the FIFOs in the uart so you can queue up multiple characters. This can reduce the number of calls to the ISR and provide better throughput/latency because the service loop doesn't have to poll so often.

STM32f091rc UART Receive function returning only the last byte of the packet instead of the full data packet

I have been working on STM32f091rc board, trying to get UART1 and UART2 work. I tried sending 8 bytes of packet from a controller to the STM board. Due to some reasons, my function is just displaying the last byte of the packet. My Receiving function is given below:-
uint8_t rxd[10];
void getChar (void) {
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) { // Check RXNE to
//see if there is data
for(j=0; j<8; j++) {
rxd[i] = (0xFF & (USART1->RDR));
}
What am i doing wrong? Can anyone please point me in the right direction? Thanks for your time.
The UART->RDR register has no buffers, it holds only the last fully received byte. If another byte is received, it will be overwritten.
You should ensure that the value in RDR is read out every time after a byte arrives, and before the next one is received. There are 3 basic ways to do it.
Polling
Check the RXNE flag regularly, and read RDR exactly once when it's set. Repeat until you have the whole data packet. Reading a byte from RDR clears the RXNE flag, wait until it's set again before you read the next byte.
Interrupt
Set the RXNEIE bit in CR1 and enable the interrupt corresponding to the UART in NVIC. The interrupt handler will be called every time a byte is received. The handler can be very simple at first, just reading RDR and storing it in a buffer. Later you can add error checking and recovery. Don't forget to declare every variable the interrupt handler touches as volatile.
DMA
Set up the DMA channel first (USART1 is mapped to DMA1_Channel3 by default, can be remapped, check the reference manual for others):
DMA1_Channel3->CPAR = (uint32_t)&USART1->RDR;
DMA1_Channel3->CMAR = (uint32_t)rxd; // start of receive array
DMA1_Channel3->CNDTR = 8; // 8 bytes to receive
DMA1_Channel3->CCR = DMA_CCR_MINC | DMA_CCR_EN; // Memory increment, enable
then set up the serial port, and enable DMA receive in USART1->CR3. The end of transfer is signaled in the DMA1->ISR register, you can check it regularly in the main program, or enable an interrupt in DMA1_Channel3->CCR (and in NVIC). You should clear the interrupt flag through DMA1->IFCR, otherwise you'll get endless interrupts when enabled. To start another transfer, set the CNDTR register again. Declare all variables touched by DMA or the interrupt handler as volatile.
Here:
for(j=0; j<8; j++) {
rxd[i] = (0xFF & (USART1->RDR));
}
you use j as a loop counter but you write to rxd under index i, not j. You overwrite the same byte 8 times.
Another thing is that you wait for the USART_FLAG_RXNE flag to be set, then you read from the RDR register 8 times. This flag is set when the first byte is received, then you read it 8 times - you very likely read much faster than the speed at which you send the data. If you want to do it by polling - which seems to be your intention - you should wait for the USART_FLAG_RXNE flag being set after reading each byte separately, as USART peripheral in this MCU has no FIFO and can only hold a single received byte for you to read (mentioned RDR register).
Your logic is completely wrong.
for(j=0; j<8; j++)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != SET); // wait for the data
rxd[i] = (0xFF & (USART1->RDR));
}

Why does stm32f4 uart skip some characters when receiving using an interrupt

Having some problems trying to receive characters from a serial Putty Session. When I type sentences/multiple characters into Putty to send character to the stm32f4 microcontroller, it doesn't seem to receive all of the characters using an interrupt approach.
So two questions:
Not sure what is going on here. Am I missing something?
I am a little confused when an interrupt is called for receiving characters. Is the interrupt called for each character or do you process the whole string with one interrupt call?
Code:
void USART_Write(USART_TypeDef * USARTx, char * str) {
while(*str){
// First check if the Transmission Data register is empty
while ( !(USARTx->SR & USART_SR_TXE)){};
// Write Data to the register which will then get shifted out
USARTx->DR =(*str &(uint16_t) 0x01FF);
str++;
}
// Wait until transmission is complete
while ( !(USARTx->SR & USART_SR_TC)){};
}
void receive(USART_TypeDef * USARTx, volatile char *buffer,
volatile uint32_t * pCounter){
if(USARTx->SR & USART_SR_RXNE){
char c = USARTx->DR;
USART_Write(USARTx,&c);
USART_Write(USARTx,"\n\r");
}
}
void USART2_IRQHandler(void){
receive(USART2, USART2_Buffer_Rx,&Rx2_Counter);
}
Putty Session:
(I type asdfghjkl into Putty and print out the receiving characters using the USART_WRITE(...) function)
asdfghjkl
a
s
g
k
USART_Write(USARTx,&c);
...........
while(*str)...
NO! Single chars are not NUL-terminated char arrays!
You must not use a polled tx approach directly from an rx interrupt-handler. It will likely result in rx overrun as the rx buffer gets overwritten by newly received chars while the rx interrupt-handler is stuck polling the tx registers.
You need a tx interrupt and some kind of buffer, eg. a circular char queue.
Yes, I know it's a bit of a pain handling tx interrupts, circular buffers etc. If the tx interrupt finds no more chars to send it has to exit having sent none. This means that when the rx interrupt next needs to queue up char to send, it must load it into the tx register instead of the queue in order to 'prime' the tx interrupt mechanism.
Things get even more umm 'interesting' if the chars need to traverse waiting non-interrupt thread/s and/or need to be blocked up into protocol units.
Anyway, whatever, you must not use this mixed interrupt/polling. It will not work reliably and the extra delays will adversely affect other interrupts, especially lower-priority interrupts, that will remain disabled for long periods:(

C: UART, ISR, circular FIFO buffer: sometimes sends bytes in wrong order

I am pulling my hair out with an intermittent bug. I am receiving and transmitting bytes asynchronously (on a PIC16F77) and have implemented a circular software FIFO buffer for receiving and transmitting, combined with an interrupt service routine that is triggered when a byte can be sent or has been received.
The problem is that sometimes the bytes to be transmitted are done so in the wrong order.
I would hugely appreciate either:
advice on debugging it, or
assistance spotting the problem in the code
Progress so far:
It seems it only happens when there are some bytes being received - however I have been unsuccessful narrowing it down further. I am not getting any underruns or overruns, AFAIK.
It works without problems when I alter send_char() to either: 1. bypass software buffer by waiting for space in hardware buffer, and putting the byte directly into it, or 2. put the byte into the software buffer, even if there is space in the hardware buffer.
The code: (See bottom of question for description of hardware variables)
unsigned volatile char volatile rc_buff[16];
unsigned char volatile rc_begin = 0;
unsigned char volatile rc_next_free = 0;
unsigned char volatile rc_count = 0;
unsigned volatile char volatile tx_buff[16];
unsigned char volatile tx_begin = 0;
unsigned char volatile tx_next_free = 0;
unsigned char volatile tx_count = 0;
__interrupt isr(){
// If a character has arrived in the hardware buffer
if (RCIF){
// Put it in the software buffer
if (rc_count >= 16) die(ERROR_RC_OVERFLOW);
rc_buff[rc_next_free] = RCREG;
rc_next_free = (rc_next_free + 1) % 16;
rc_count++;
}
// If there is space in hardware FIFO, and interrupt
// has been enabled because stuff in software FIFO needs to be sent.
if (TXIE && TXIF){
// Put a byte from s/w fifo to h/w fifo.
// (Here, tx_count is always > 0 (in theory))
TXREG = tx_buff[tx_begin];
tx_count--;
tx_begin = (tx_begin + 1) % 16;
// If this was the last byte in the s/w FIFO,
// disable the interrupt: we don't care
// when it has finished sending.
if(tx_count==0) TXIE = 0;
}
}
void send_char(char c){
// disable interrupts to avoid bad things happening
di();
// if the hardware buffer is empty,
if (TXIF){
// put a byte directly into the hardware FIFO
TXREG = c;
} else {
// cannot send byte directly so put in the software FIFO
if (tx_count >= 16) die(ERROR_TX_OVERFLOW);
tx_buff[tx_next_free] = c;
tx_next_free = (tx_next_free + 1) % 16;
tx_count++;
// Enable TX interrupt since it now has something
// it needs to transfer from the s/w FIFO to the h/w FIFO
TXIE = 1;
}
ei();
}
char get_char(){
// wait for a byte to appear in the s/w buffer
while (!rc_count) {
// If the h/w buffer overflowed, die with error
if (OERR) die(ERROR_RC_HW_OVERFLOW)
}
// disable interrupts to avoid bad things happening
di();
unsigned char c = rc_buff[rc_begin];
rc_count--;
rc_begin = (rc_begin + 1) % 16;
ei();
return c;
}
void send_str(const unsigned char * str){
unsigned char char_idx = 0;
// until we reach the end-of-string null character,
while (str[char_idx]){
// queue a character for sending
send_char(str[char_idx++]);
}
}
Description of hardware variables:
For reference, the following are the (volatile) variables mapped to hardware registers and flags:
RCIF // Read-only receive flag: True == byte(s) are waiting in hardware receive FIFO
TXIF // Read-only transmit flag: True == there is space in the hardware transmit FIFO
RCREG // Read only: Holds the next byte from the hardware FIFO that has been received
TXREG // Write-only: Assigning a byte to this transfers the byte to the hardware transmit FIFO
TXIE // Read/Write: Enable transmit interrupt: True == trigger ISR when TX h/w FIFO has space
RCIE // Read/Write: Enable receive interrupt: True == trigger ISR when RC h/w FIFO has a byte to be read
Also, the below are special inline functions that suspend/resume interrupts to keep multiple grouped operations atomic. (The ISR cannot be interrupted by anything, including other interrupts)
di() // suspend interrupts
ei() // re-enable interrupts
Hmmm. I think there is some logic missing in your program (I will only cover the send part, since the receiver part seems to be working?):
The interrupt routine gets triggered if there is space in the send hw FIFO. You then send out one single byte from the sw buffer, adjust the index and return (note that there might still be some bytes queued within the sw buffer after that).
Whenever you send a byte, you look for space in the HW fifo and put the byte directly there, if not, you queue it in the SW buffer.
The problem seems to me that you expect the interrupt routine to drain the software buffer before returning to send_char() which isn't necessarily the case. After returning from the interrupt, the next instruction will be fully executed (there is no interrupt in the middle of an instruction). If this next instruction is the di() in send_char(), this interrupt will not happen and there will still be bytes in the sw buffer that can only be sent later (too late).
I'd either rather enqueue the bytes into the sw buffer from send_char() instead of writing directly to the fifo from send_char() or additionally check for the sw buffer to be empty before accessing the hw fifo directly.

Resources