Debugging issues with Serial communication on STM32 blue pill

Hi,

I am encountering some problems with the serial communication in debug mode.

The board is a BluePill STM32F103 with Arduino Framework.

When the programm is running normally, I send 8 bytes into the serial buffer and get from Serial.available() the return value 8.

But when I am using debugging and move from one line to another, I only get the return value of 1.

Can anybody tell me why the return value is incorrect in debug mode?

#include <Arduino.h>
int bytesinBuffer = 0;

void setup() {
Serial.begin(9600);
}

void loop() {
if(Serial.available() > 0)
{
  bytesinBuffer = Serial.available();
}
}

The Serial receive logic works this way:

  1. The USART peripehral is set up and a receive callback function is given that will be executed when one character has been received
  1. This function will then set the internal callback and start the interrupt-driven UART receive routine (the HAL_UART_Receive_IT STM32 HAL function)
  1. At some point when a UART character is physically sent, the CPU internally flags that the UART interrupt routine should be executed, and it will do so after the current instruction has been executed. Code execution will then go into this function.

The HAL_UART_IRQHandler() is the STM32 HAL’s internal IRQ handler function and will at some point call into the receive callback that it was given in the first place, to signal that now the character can be read and further processed. The call of the callback happens in

  1. The receive callback is then executed. It will get the received character (uart_getc()) and write it into the receive buffer, which is a circular (ring) buffer (with a head and a tail).
  1. Finally, after the internal ring buffer has been updated, the interrupt execution stops.

  2. You can see that the available() function simply interrogates the ring buffer regarding the head and tail position, giving you the length of the currently stored data.

So, for every received character, the interrupt service routine (ISR) has to be executed which takes the data from the USART peripheral and pushes it into the Serial’s receive buffer.

Also note that the USART has no internal buffer (or FIFO) (see also here. If another character is received before the previous one is read out, an overrun condition occurs. The chip will then behave as specified in its reference manual

So, the ISR has to be quick enough to pull out the data and save it elsewhere, otherwise it only sees the first received character and an overrun condition (ORE = overrun error). The overrun itself will cause again an interrupt to be queued, which will eventuall land in this function which clears the error bit again.

So if we hit line 2067 in the above code, we know an overrun has occurred.

So, back to your problem.

Let’s say you place a breakpoint at the if(Serial.available() > 0) line. The chip halts then at this point. Then you send the data via serial, and then go to the next line.

By having halted the CPU at the point of the UART transmission, you don’t give it the chance to execute the ISR routines. The USART peripherael will just receive the byte internally (write it into the USART’s data register) and signal that a RX interrupt. However, since you’ve halted the chip, the does not get executed until you advance to the next line. Then, the next byte will come via serial, and it overwrites the previous character, while still signaling the USART interrupt has occurred. In short: You don’t give the chip a chance to execute the ISR by halting it at the wrong place, thus causing an internal overrun of the UART data.

You can also see that this internal overrun has occurred by printing out the byte that is has received and setting a global variable in the code that resets the ORE flag (to know it happened).

    /* UART Over-Run interrupt occurred --------------------------------------*/
    if (((isrflags & USART_SR_ORE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      extern int num_overruns_occurred;
      huart->ErrorCode |= HAL_UART_ERROR_ORE;
      num_overruns_occurred++;
    }

and test code.

#include <Arduino.h>
int bytesinBuffer = 0;
int num_overruns_occurred = 0; /* global variable modified by UART code if ORE occurrs */

void setup()
{
    Serial.begin(9600);
}

void loop()
{
    if (Serial.available() > 0)
    {
        bytesinBuffer = Serial.available();
        char c = (char) Serial.read();
        Serial.println("Num available: " + String(bytesinBuffer));
        Serial.println("First received character:" + String(c));
        Serial.println("Num Overruns: " + String(num_overruns_occurred));
    }   
    if(millis() % 1000 == 0) {
        Serial.println("Bytes in buffer: " + String(bytesinBuffer));
        Serial.flush();
    }
}

A breakpoint is place in the if (Serial.available() > 0) line, the data “12345678” is sent, and then the code is stepped over.

And what do we see?

That the num_overruns_occurred has been incremented to one, and that the readout character is '1'. So, excactly as the reference manual said. Without doubt, the overrun has occurred, causing this problem of yours.

So how can it be avoided? Simple. The chip must not be halted while you’re sending the data. You only want to halt the chip after the reception has happened and all the ISRs were executed that copied the data into the internal buffer.

So what we could do is place the breakpoint after the read() function. However, then there may still be a small change for a race condition if the code checks the data at the exact time that really only 1 character has been received.

Against that, we can either place a small delay() before the read or wait until 8 bytes have been received. An 8-byte transmission at 9600 baud and 8N1 config will last ~8.33 milliseconds, so 9 ms will be safe.

So I use the code

#include <Arduino.h>
int bytesinBuffer = 0;
int num_overruns_occurred = 0; /* global variable modified by UART code if ORE occurrs */

void setup()
{
    Serial.begin(9600);
}

void loop()
{
    if (Serial.available() > 0)
    {
        delay(9);
        bytesinBuffer = Serial.available();
        char c = (char) Serial.read();
        Serial.println("Num available: " + String(bytesinBuffer));
        Serial.println("First received character:" + String(c));
        Serial.println("Num Overruns: " + String(num_overruns_occurred));
    }   
}

and place the breakpoint at the char c line, let debugging start and send the data I get

8 bytes successfully received.

The other way would be to simply wait until Serial.available() == 8.

If you send it arbitrary length strings, you can also read a timed-read function. E.g., doing

#include <Arduino.h>

void setup()
{
    Serial.begin(9600);
    Serial.setTimeout(1000);
}

void loop()
{
    if (Serial.available() > 0)
    {
        //at least one character has been received.
        //read the whole string.
        String s = Serial.readStringUntil('\n');
        Serial.println("Received: " + s);
    }   
}

Will set the read timeout to 1000ms, check if at least one char was received, and then read the string until the newline character or abort if the timeout was reached. One can also read Serial.readString() to read everything until just a timeout occurs.

If I place the breakpoint at te

    Serial.println("Received: " + s);

and send 12345678 with a newline ending, I always get the right string.

1 Like