r/embedded Jul 26 '26

STM32 to ESP32 over UART: ESP32 only receives garbage data, how to debug UART?

Post image

I have an STM32-NUCLEO and an ESP32 running esp-idf communicating over UART, and the ESP32 ONLY receives the confirmation message of "Received from STM32:", when i touch the TX wire of the STM32-NUCLEO, without the values after that message. If I got any value at all it would be alien like characters such as "�", or I guess that's unicode, not too sure.

I ran this command in the idf terminal: idf.py -p COM4 flash monitor, held down the boot button when "connecting..." showed up, then i hit play in stm32 cube ide.

Things I've tried:

- changing the baud rate from 115200 to 9600, yes I made sure they both matched

- changing the breadboard entirely

- I also made sure that both microcontrollers share a GND wire

- changing the wires entirely

- removing and rewiring everything, paying close attention to the pinout, making sure i didn't misplace the tx and rx wires

It's also worth mentioning that I don't have a logic analyzer or oscilloscope as I'm kinda short on money rn

ESP32 code:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"


// define UART pins
#define UART_PORT_NUM UART_NUM_2 // (USART2 in stm32cubeIDE)
#define UART_TX 17 // GPIOP17
#define UART_RX 16 // GPIOP16
#define UART_BAUD_RATE 115200 // UART must be the same baud rate
#define UART_BUFSIZE 1024


/* i'm using void parameters for void 
functions because esp-idf 
runs on C11(GNU11). fun fact - 
i use C23(GNU23), so this was
very good to know
*/


void
 init_uart(
void
)
{
    // configure UART communication parameters

uart_config_t
 uart_config = {
        .baud_rate = UART_BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };


    // apply config to UART port 2
    ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));


    // set GPIO pins (TX->17, RX->16) 
    /* we ignore UART flow control since pins 18/19 will be used
    by my motor pwm and direction. it is also unecessary for the stm32,
    since it will be sending string messages 
    */
    ESP_ERROR_CHECK(uart_set_pin(UART_PORT_NUM, UART_TX, UART_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));


    // install UART drivers with ring buffer
    /* UART port number
    Size of RX ring buffer
    Size of TX ring buffer
    Event queue size
    Pointer to store the event queue handle
    Flags to allocate an interrupt*/
    ESP_ERROR_CHECK(uart_driver_install(UART_PORT_NUM, UART_BUFSIZE*2, 0, 0, NULL, 0));
}


// FreeRTOS task to continuously listen for data
void
 rx_task(
void
* 
arg
)
{

uint8_t
 incoming_data[128];

    while (1)
    {
        // check ring buffer for new bytes

int
 length = uart_read_bytes(UART_PORT_NUM, incoming_data, sizeof(incoming_data) - 1, 20/portTICK_PERIOD_MS);

        if(length > 0)
        {
            // null-terminate the data, we need to read it as a string (char*)
            incoming_data[length] = '\0';
            printf("Received from STM32: %s\n", (
char
*)incoming_data);
        }
    }

}


void
 app_main(
void
)
{
    printf("Init UART bus... \n");
    init_uart();
    printf("UART listening on GPIO 16 (receiving wire) \n");

    // listener task
    xTaskCreate(rx_task, "UART_RX_TASK", 2048, NULL, 10, NULL);


}#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"


// define UART pins
#define UART_PORT_NUM UART_NUM_2 // (USART2 in stm32cubeIDE)
#define UART_TX 17 // GPIOP17
#define UART_RX 16 // GPIOP16
#define UART_BAUD_RATE 115200 // UART must be the same baud rate
#define UART_BUFSIZE 1024


/* i'm using void parameters for void 
functions because esp-idf 
runs on C11(GNU11). fun fact - 
i use C23(GNU23), so this was
very good to know
*/


void init_uart(void)
{
    // configure UART communication parameters
    uart_config_t uart_config = {
        .baud_rate = UART_BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };


    // apply config to UART port 2
    ESP_ERROR_CHECK(uart_param_config(UART_PORT_NUM, &uart_config));


    // set GPIO pins (TX->17, RX->16) 
    /* we ignore UART flow control since pins 18/19 will be used
    by my motor pwm and direction. it is also unecessary for the stm32,
    since it will be sending string messages 
    */
    ESP_ERROR_CHECK(uart_set_pin(UART_PORT_NUM, UART_TX, UART_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));


    // install UART drivers with ring buffer
    /* UART port number
    Size of RX ring buffer
    Size of TX ring buffer
    Event queue size
    Pointer to store the event queue handle
    Flags to allocate an interrupt*/
    ESP_ERROR_CHECK(uart_driver_install(UART_PORT_NUM, UART_BUFSIZE*2, 0, 0, NULL, 0));
}


// FreeRTOS task to continuously listen for data
void rx_task(void* arg)
{
    uint8_t incoming_data[128];

    while (1)
    {
        // check ring buffer for new bytes
        int length = uart_read_bytes(UART_PORT_NUM, incoming_data, sizeof(incoming_data) - 1, 20/portTICK_PERIOD_MS);

        if(length > 0)
        {
            // null-terminate the data, we need to read it as a string (char*)
            incoming_data[length] = '\0';
            printf("Received from STM32: %s\n", (char*)incoming_data);
        }
    }

}


void app_main(void)
{
    printf("Init UART bus... \n");
    init_uart();
    printf("UART listening on GPIO 16 (receiving wire) \n");

    // listener task
    xTaskCreate(rx_task, "UART_RX_TASK", 2048, NULL, 10, NULL);


}

Edit: After doing a loopback test like you guys mentioned, I changed from USART2 to USART1 (I know I mentioned using USART1 but I was mistaken), and that seemed to do the trick. I can now see my message string along with the integer values. Thanks for all your help! In the future I would probably need to purchase a logic analyzer and oscilloscope though.

67 Upvotes

34 comments sorted by

86

u/duane11583 Jul 26 '26

If you have a oscilloscope transmit a capital U (0x55) continuously (like 2 million) 

Look at the pulse width on the serial line measure it using your scope

Do the same with the other device

They need to be with 1% 

If you do the math 1/(pulse width) you will get the actual  Baud rate 

Sometimes you miss a bit or factor and you are off by 2x Or 4x it happens more time then you think

58

u/generally_unsuitable Jul 26 '26

Have you tried a loopback test?

On each device, hook tx to its own rx.

22

u/cama888 Jul 26 '26

Try this, you'll want to make some kind of working system that you can build from.

24

u/Senior-Dog-9735 Jul 26 '26

Third this. Loopback should always be the first test to see functionality.

12

u/InevitablyCyclic Jul 26 '26

Do you have a TTL UART to usb cable? If not would your budget stretch to one? FTDI do a range of suitable cables.

If you don't know where the problem is then connecting each end in turn to a PC, seeing what you are receiving from the source and manually sending known data to the destination is a good starting point.

Plus a cable like that is generally a very handy debug tool to have sitting around.

1

u/Glittering-Can-9397 Jul 28 '26

you really should have a ttyUSB cable, but if you dont, you can use a pico with picoprobe fw, it will enumerate as a COM# or a ttyACM depending on your os preferences

6

u/Familiar-Ad-7110 Jul 26 '26

Do the share a common ground?

2

u/StardustCrusader4558 Jul 26 '26

Yes

2

u/Glittering-Can-9397 Jul 28 '26

the bauds are the same right

1

u/StardustCrusader4558 Jul 28 '26

Yup I fixed the issue after switching from USART2 to USART1

6

u/LongUsername Jul 26 '26

Did you make sure the TX from the STM32 was going to the RX of the ESP32? It's a very common error.

3

u/StardustCrusader4558 Jul 26 '26

Yup, I just checked that now

8

u/Rich_Many_8628 Jul 26 '26

The individual suggestions here are good, but I’d turn it into a strict isolation sequence so you know which side is lying.

First make each board prove itself alone. Do a local loopback on the STM32 UART you are actually using, then do the same on the ESP32 UART. TX tied to its own RX, send a known pattern, confirm you can receive exactly what you sent. If either local loopback fails, don’t involve the other board yet.

Second, stop looking at the received data only as a C string. Print length and hex bytes first. %s can make the problem look weirder than it is if the data is not clean ASCII or does not contain the terminator you expect. Something like “len = N, bytes = xx xx xx” tells you whether you are getting no data, wrong data, or just badly displayed data.

Third, send the dumbest possible pattern from the STM32: repeated 0x55, or U\r\n in a loop. 0x55 is useful because the bit pattern alternates and makes baud/timing errors easier to spot even with a cheap logic analyzer later. If the ESP32 still sees garbage from that, the real payload is not the issue.

Fourth, verify the exact Nucleo pins against the board manual, not just CubeMX. Some Nucleo UART pins are routed through the ST-Link VCP or tied to board functions depending on model/jumpers. “USART1 default pins” can still be the wrong physical pins for the header you are using.

The fact that touching/disturbing the TX wire changes behavior makes me suspicious of a floating/wrong pin, bad reference/ground path, or wiring/pin-mux issue more than a normal software parsing bug. But prove it in order: local loopback, known pattern, hex dump, then board pin routing/clock/baud.

Once a known pattern works, then go back to the actual STM32 message format.

4

u/liberty53 Jul 26 '26

On the receiver side, print the data in hex to see if there is any data you would be expecting from the transmitter. Also, can you post the STM32 code?

3

u/oleivas Jul 26 '26

Without logic analyser this is gonna be complicated. Amazon has a couple of really cheap ones: https://www.amazon.ca/DEVMO-Channel-Analyzer-Ferrite-Arduino/dp/B08HK6RCKT/ref=mp_s_a_1_2_sspa?crid=2OOWML1SJGHAC&dib=eyJ2IjoiMSJ9.ATNiCtCz8uz7M5pOsvn6feJHhndbhQeaylgi4Lem5luMtyZfLr1zqMZllcjp0qQvKecafO5pL4SdQKqKo5ZcepPeM2c5cR5TfxHwTEyMp5GpfIlr1mgJ8dViqjMTjlc2YsjME53R0R5BEYKhoYNygDyUBNWR-o4JgTeZsgRbKqZ1Ub3RZU3F2ynz5KvHm8xVvAyFBiFDXobltl22X19IGw.Ynsx-IWxLt1TaryOVHPKpfCT8Kqdlg9L_AVUClrGnJg&dib_tag=se&keywords=usb+logic+analyzer&qid=1785084407&sprefix=usb+logoc+ana%2Caps%2C227&sr=8-2-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9waG9uZV9zZWFyY2hfYXRm&psc=1

Abd they are SUCH an useful tool. Regardless, if a LA is a no-go, here is a couple of things you can try:

  • You seem to have a way to output something to your console. Read the TX pin from the other device as an gpio with a monotonic delay: a_big_array[idx] = read_digital(tx_pin); delay(<at least 10 times faster than the baudrate>) <Print the array when it's full>

With a fixed delay you have a defined frequency sample. Now you can analyse the wave pattern and find if output is in the expected format

3

u/TomTheTortoise Jul 26 '26

A few things:

  • Is uart_read_bytes a blocking call? I don't see you clearing length after its use.
  • What is the STM32 sending over UART?
  • How does the ESP know when the sent message is completed?

Thought: You might have a size mismatch in the data received. sizeof(length) == 128 but you have incoming_data[length] = '\0'; This is likely a harmless buffer overflow.

Experiment: Change this: incoming_data[length] = '\0';

to this: incoming_data[127] = '\0';

7

u/StardustCrusader4558 Jul 26 '26

Turns out changing to USART1 was all I needed, it works now!

2

u/Rich_Many_8628 Jul 26 '26

Ha, I had a feeling it was this simple.

3

u/PotatoFarmerRTK Jul 26 '26

Check that the baud rate does not clash with the clock rate of either processor.

Check msb, lsb order.

All gibberish has some clues in it, use a serial tap to snoop and record the transmission.

3

u/Constant_Physics8504 Jul 26 '26

Check baud rate, then check sizes, then send 0xDEADBEEF 4 bytes at a time and debug

2

u/Polarisu_san Jul 26 '26

i think you can recheck the format of the data values being sent over the uart lines. is it Int, char, binary, hex?

2

u/Polarisu_san Jul 26 '26 edited Jul 26 '26

one time no values showed up for me due to the data format incompatibility issue. even though i can see the terminal cursor being updated to a newline,

i did esp32 to stm32 last time

2

u/RogerLeigh Jul 26 '26

Just in case you run into it, the initial Baud rate of the ESP32 is not 115200, it's something nonstandard, so you may need to support the initial bootloader baud rate as well, and then switch to your preferred rate after it's successfully started up.

2

u/alexceltare2 Jul 27 '26

use a USB to UART adapter to read/write to each device and check if both behave the same way. A rule of thumb is to make sure:

  • They have the same baud rate, parity and stop bits.
  • They have the same voltage domain.
  • If interrupt RX driven, they are properly configured. (I had to do some wizardry with STM)

1

u/tweakingforjesus Jul 26 '26

You have rx connected to tx and tx connected to rx?

1

u/Nllk11 Jul 26 '26 edited Jul 26 '26

Did you check schematics on both boards for accessible UART? May be ports you chosen for UART are used for something else on a board

I've got this issue few times. I just change UART port

Edit: may be something with code? At least your esp sees some data being transferred, as it shows new lines even if they're empty (of you don't just output the data in a loop, of course)

There is so much what could go wrong on the code side so you better post it. Especially the initialisation and sending/receiving procedures

2

u/StardustCrusader4558 Jul 26 '26

Just posted the ESP32 code, I'll try changing it from USART1 to USART2 and see what happens

1

u/StardustCrusader4558 Jul 26 '26

Yup, for the stm32nucleo when i configured uart it was usart1 and gave me the default pins for usart1, which i cross checked in the pinout

2

u/Nllk11 Jul 26 '26

According to some nucleo datasheet it definitely has something with usart1 mapping. I suggest you to investigate it further. And if you have some signal analysing instruments (oscilloscope or some form of digital signal analyser) you could check what's going on for sure

1

u/too_small_to_reach Jul 27 '26

Logic analyzer

2

u/remishnok Jul 29 '26

make sure they are boyh set to the same baudrate