Skip to main content
Graduate II
August 4, 2025
Solved

UART driver issue NUCLEO F411RE

  • August 4, 2025
  • 4 replies
  • 961 views

I'm trying to set up a simple UART debug just for transmission from the the NUCLEO board.

uart.c

#include "uart.h"

#define GPIOAEN			(1U<<0)
#define UART2EN			(1U<<17)

#define CR1_TE			(1U<<3)
#define CR1_UE			(1U<<13)
#define SR_TXE			(1U<<7)

#define SYS_FREQ		16000000
#define APB1_CLK		SYS_FREQ

#define UART_BAUDRATE	115200

static void uart2_set_baudrate(uint32_t periph_clk, uint32_t baudrate);
static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate);
static void uart2_write(int ch);


int __io_putchar(int ch)
{
	uart2_write(ch);
	return ch;
}

void uart2_tx_init(void)
{
	/*** Configure UART GPIO pin ***/
	/* Enable clock access to GPIOA */
	RCC->AHB1ENR |= GPIOAEN;

	/* Set PA2 mode to alternate function mode */
	GPIOA->MODER &=~ (1U<<4);
	GPIOA->MODER |= (1U<<5);

	/* Set PA2 alt function to UART tx (AF07) */
	GPIOA->AFR[0] |= (1U<<8);
	GPIOA->AFR[0] |= (1U<<9);
	GPIOA->AFR[0] |= (1U<<10);
	GPIOA->AFR[0] &=~ (1U<<11);

	/*** Configure the UART ***/
	/* Enable clock access to UART2 */
	RCC->AHB1ENR |= UART2EN;

	/* Configure baudrate */
	uart2_set_baudrate(APB1_CLK, UART_BAUDRATE);

	/* Configure transfer direction */
 USART2->CR1 = CR1_TE;

	/* Enable UART module */
 USART2->CR1 |= CR1_UE;
}

static void uart2_write(int ch)
{
	/* Ensure transmit data register empty */
	while(!(USART2->SR & SR_TXE)){}

	/* Write to the transmit data register */
	USART2->DR = (ch & 0xFF);
}


static void uart2_set_baudrate(uint32_t periph_clk, uint32_t baudrate)
{
	USART2->BRR = compute_uart_bd(periph_clk, baudrate);
}

static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate)
{
	return ((periph_clk + (baudrate / 2U)) / baudrate);
}

 

 uart.h

#ifndef __UART_H__
#define __UART_H__

#include "stm32f4xx.h"
#include <stdint.h>

void uart2_tx_init(void);

#endif

 

main.c

#include "stm32f4xx.h"
#include <stdio.h>
#include "uart.h"

static void psuedo_delay(int dly);

int main(void)
{
	/* Initialize UART2 */
	uart2_tx_init();

 while(1)
 {
 	printf("hi there!\n\r");
 	psuedo_delay(9000);
 }
}

static void psuedo_delay(int dly)
{
	for(int i = 0; i < dly; i++)
	{
 // DO NOTHING
	}
}

 

If I'm right then PA2 has been set up as a UART TX with a Baud rate of 115200 & this is connected to the USB on the board.

 

But when I open a TeraTerm window & connect to the board I get nothing just a blank terminal window. What am I missing here?

    This topic has been closed for replies.
    Best answer by NicRoberts

    I appreciate your insistence on using "official, proven and correct bit masks" but my intention here is to understand how things are working at a register level without abstraction. 

    As pointed out by @Andrew Neil some posts back now, the issue was actually the use of the drivers (yes user error as I had been blindly cut & pasting code). I was erroneously prototyping the USART configuration function in main() instead of calling it (there was a stray void in there that I was obviously blind to until this morning).

     

    The actual driver itself works as it should even with "custom constants"

    4 replies

    Super User
    August 4, 2025

    @NicRoberts wrote:

    If I'm right then PA2 has been set up as a UART TX


    You can confirm that in the board's User Manual:

    AndrewNeil_0-1754315802661.png

    AndrewNeil_1-1754315825898.png

    https://www.st.com/resource/en/user_manual/um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf

    via: https://www.st.com/en/evaluation-tools/nucleo-f411re.html#documentation

    You could also check the schematics:

    AndrewNeil_2-1754315934411.png

    via https://www.st.com/en/evaluation-tools/nucleo-f411re.html#cad-resources

     


    @NicRoberts wrote:

    with a Baud rate of 115200 


    The baud rate can be whatever you like - it just has to match what you terminal is set to.

     


    @NicRoberts wrote:

    But when I open a TeraTerm window & connect to the board I get nothing 


    Are you sure you're connecting to the correct COM port?

    Do you have an Arduino shield (or any other hardware) connected which could interfere?

    The manual describes various configuration options - has your board been modified?

    Before adding the complications of printf, get it working with just direct UART output.

    Have you tried one of the ready-made examples?

    Graduate II
    August 4, 2025

    OK so the board is set up correctly for use of USART2 over the ST-Link/USB connection, I have used this before with no issue with the board but just double checked & it all looks good.

    No other hardware connected.

    Baud rate on the board & the terminal match (115200),

    COM5: STMicroelectronics STLink Virtual COM Port selected from Tera Term pull down

     

    .....and nothing.....

    Super User
    August 4, 2025

    Flow control set to 'None' ?

     

    Have you tried one of the ready-made examples?

    Do you have a scope to see if anything is actually coming out of the TX pin?

    Graduate II
    August 12, 2025

    Are we sure APB1 is DIV2 and not DIV1 ?

    Perhaps send 'U' patterns and scope the PA2 pin to confirm activity and bit-timing

    If you're using bare-metal registers for efficiency, this is NOT efficient, and the compiler can't optimize / fold this because the registers are deemed volatile. So 4x load-store operations, when one load and one store would be the goal.

    GPIOA->AFR[0] |= (1U<<8);
    GPIOA->AFR[0] |= (1U<<9);
    GPIOA->AFR[0] |= (1U<<10);
    GPIOA->AFR[0] &=~ (1U<<11);

    Graduate II
    August 13, 2025

    Hi, 

    I'm relatively new to STM32 so I'm doing everything long hand to get a better understanding but I get the sense in what you're are saying.

    As for APB1, when I check against the auto-generated code the RCC initialiser uses DIV1. I thought I'd configured for DIV1?

    Graduate II
    October 1, 2025

    OK this is still an issue. Weirdly the code in the OP worked on a project last week.

    I've copied the exact same code into another project using the same board & it hangs waiting for the USART2 transmit data register empty flag to clear. So nothing gets sent.

    I have no idea why this would work in one project using the same hardware but not in the new project.

     

    Super User
    October 1, 2025

    @NicRoberts wrote:

    it hangs waiting for the USART2 transmit data register empty flag to clear.


    Shouldn't it be waiting for the empty flag to set ?

    You wait for the empty flag to set because that's what tells you that  the register is ready to be written-to...

     


    @NicRoberts wrote:

    I have no idea why this would work in one project using the same hardware but not in the new project.


    Maybe the problem is outside the code you copied - in other setup?

    Graduate II
    October 1, 2025


    Shouldn't it be waiting for the empty flag to set ?



    Yes, quite right, I got muddled writing the post, that what this line does right?
     
    while(!(USART2->SR & SR_TXE)){}
     
    I've  stripped everything down to just using the USART but still it just hangs waiting on the SR_TXE flag to set, which never happens. 
    Graduate
    October 1, 2025

    The problem you experience is a direct result of not using the register bit constants defined in ST-supplied headers.

    Once you replace the constants defined by you with the standard ones, you will easily notice your mistake.

    The correct way to enable USART2 is:

    RCC->APB1ENR = RCC_APB1ENR_USART2EN;

    (typo corrected in response to the post below)

    Graduate II
    October 1, 2025

    That throws an "undeclared identifier" error for RCC_APBENR1_USART2EN

    Graduate
    October 1, 2025

    You're right, there was a typo - corrected.