2025-11-26 11:17 AM - last edited on 2025-11-27 1:19 AM by Andrew Neil
I'm writing a UART driver for NUCLEO-F103RB and I followed all steps in the book "Bare-metal embedded C programming" and advices from Gemini but still can't see the receiving output from my PC.
How can I fix this?
Other parts than UART work without problem in this setting.
main.c
#include <stdint.h>
#include <stdio.h>
#include "gpio.h"
#include "uart.h"
#if !defined(__SOFT_FP__) && defined(__ARM_FP)
#warning "FPU is not initialized, but the project is compiling for an FPU. Please initialize the FPU before use."
#endif
unsigned readBit(unsigned line, unsigned position){
unsigned mask = 1U<<position;
return ((mask & line) >> position);
}
int main(void)
{
RCC_APB2EN_R |= 1U<<2;
RCC_APB2EN_R |= 1U<<4;
//led init
GPIOA_MODE_R &= ~(1U<<20);
GPIOA_MODE_R |= 1U<<21;
GPIOA_MODE_R &= ~(1U<<22);
GPIOA_MODE_R &= ~(1U<<23);
//button init
GPIOC_MODE_R &= ~(1U<<20);
GPIOC_MODE_R &= ~(1U<<21);
GPIOC_MODE_R |= 1U<<22;
GPIOC_MODE_R &= ~(1U<23);
uart_init();
unsigned short button_pressed = 0;
unsigned short led_on = 0;
/* Loop forever */
for(;;){
for(int i = 0; i < 500000; i++);
printf("Hello from STM32...\r\n");
}
}
gpio.h
#define PERIPH_BASE 0x40000000UL
#define GPIOA_OFFSET 0x00010800UL
#define GPIOC_OFFSET 0x00011000UL
#define GPIOA_BASE PERIPH_BASE + GPIOA_OFFSET
#define GPIOC_BASE PERIPH_BASE + GPIOC_OFFSET
#define RCC_OFFSET 0x00021000UL
#define RCC_BASE PERIPH_BASE + RCC_OFFSET
#define APB2EN_R_OFFSET 0x18UL
#define RCC_APB2EN_R *(volatile unsigned int *)(RCC_BASE + APB2EN_R_OFFSET)
#define A_MODE_R_OFFSET 0x00UL
#define C_MODE_R_OFFSET 0x04UL
#define GPIOA_MODE_R *(volatile unsigned int *)(GPIOA_BASE + A_MODE_R_OFFSET)
#define GPIOC_MODE_R *(volatile unsigned int *)(GPIOC_BASE + C_MODE_R_OFFSET)
#define OD_R_OFFSET 0x0CUL
#define GPIOA_OD_R *(volatile unsigned int *)(GPIOA_BASE + OD_R_OFFSET)
#define ID_R_OFFSET 0x08UL
#define GPIOC_ID_R *(volatile unsigned int *)(GPIOC_BASE + ID_R_OFFSET)
#define BSRR_OFFSET 0x10UL
#define GPIOA_BSRR *(volatile unsigned int*)(GPIOA_BASE + BSRR_OFFSET)
uart.h
#define PERIPH_BASE 0x40000000UL
#define GPIOA_OFFSET 0x00010800UL
#define GPIOC_OFFSET 0x00011000UL
#define GPIOA_BASE PERIPH_BASE + GPIOA_OFFSET
#define GPIOC_BASE PERIPH_BASE + GPIOC_OFFSET
#define USART2_BASE PERIPH_BASE + 0x00013800UL
#define RCC_OFFSET 0x00021000UL
#define RCC_BASE PERIPH_BASE + RCC_OFFSET
#define RCC_APB1EN_R *(volatile unsigned int *)(RCC_BASE + 0x1CUL)
#define RCC_APB2EN_R *(volatile unsigned int *)(RCC_BASE + 0x18UL)
#define C_MODE_R_OFFSET 0x04UL
#define GPIOA_MODEL_R *(volatile unsigned int *)(GPIOA_BASE)
#define GPIOC_MODE_R *(volatile unsigned int *)(GPIOC_BASE + C_MODE_R_OFFSET)
#define USART2_BRR_R *(volatile unsigned int *)(USART2_BASE + 0x08UL)
#define USART2_CR1_R *(volatile unsigned int *)(USART2_BASE + 0x0CUL)
#define USART2_SR_R *(volatile unsigned int *)(USART2_BASE + 0x00UL)
#define USART2_DR_R *(volatile unsigned int *)(USART2_BASE + 0x04UL)
#define APB1_CLK 36000000
#define USART_BAUDRATE 115200
void uart_init(void);
uart.c
#include <stdint.h>
#include "uart.h"
void uart_init(void){
RCC_APB2EN_R |= 1U<<2;
RCC_APB1EN_R |= 1U<<17;//UART2 enable
/*GPIOA_MODEH_R &= ~(1U<<4);
GPIOA_MODEH_R |= (1U<<5);
GPIOA_MODEH_R &= ~(1U<<6);
GPIOA_MODEH_R |= (1U<<7);
*/
GPIOA_MODEL_R &= ~(0xFUL << 8);
// Set MODE9[1:0] = 10 (Output mode, max speed 2 MHz)
GPIOA_MODEL_R |= (1U << 9);
// Set CNF9[1:0] = 10 (Alternate function output Push-pull)
GPIOA_MODEL_R |= (1U << 11);
//int uartdiv = APB2_CLK/USART_BAUDRATE;
unsigned uartdiv = APB1_CLK/USART_BAUDRATE;
unsigned mantissa = uartdiv / 16;
unsigned fraction = uartdiv % 16;
USART2_BRR_R = (mantissa << 4) | fraction;
//USART2_BRR_R = (APB1_CLK + (USART_BAUDRATE/2U))/(16U * USART_BAUDRATE);
USART2_CR1_R |= 1U<<3;
USART2_CR1_R |= 1U<<13;
}
int __io_putchar(int ch)
{
while(!(USART2_SR_R & (1U<<7))){}
USART2_DR_R = (ch & 0xFF);
return ch;
}
2025-11-26 11:44 AM
Welcome to the forum.
Please see How to write your question to maximize your chances to find a solution for best results
@Jajang01 wrote:NUCLEO-F103RB
So, presumably, you are using the ST-Link's VCP (Virtual COM Port) to connect to the PC ?
Are you sure that you are using the correct UART pins for that ?
Have you tried one of the ST examples for reference?
2025-11-26 1:09 PM
For things to work, you need to get everything correct. Nobody is going to want to sift through 100 lines of manually typed values for the needle in a haystack.
Use the CMSIS header files, they are correct and make everything more readable.
You can also compare register values after using your code and using the working HAL examples to see what is different.
2025-11-26 1:13 PM
Yes, it shows up in the device manager and the user manual says the USART2 communication between the target STM32 and ST-LINK MCU is enabled, to support virtual COM port by default
2025-11-27 12:01 AM
I changed the code using the baud rate calculation from HAL but it transmits weird things
#include <stdint.h>
#include "uart.h"
#define USART_DIV(_PCLK_, _BAUD_) (((_PCLK_)*25U)/(4U*(_BAUD_)))
#define USART_DIVMANT(_PCLK_, _BAUD_) (USART_DIV((_PCLK_), (_BAUD_))/100U)
#define USART_DIVFRAQ(_PCLK_, _BAUD_) (((USART_DIV((_PCLK_), (_BAUD_)) - (USART_DIVMANT((_PCLK_), (_BAUD_)) * 100U)) * 16U + 50U) / 100U)
#define USART_BRR(_PCLK_, _BAUD_) (((USART_DIVMANT((_PCLK_), (_BAUD_)) << 4U) + \
((USART_DIVFRAQ((_PCLK_), (_BAUD_)) & 0xF0U) << 1U)) + \
(USART_DIVFRAQ((_PCLK_), (_BAUD_)) & 0x0FU))
void uart_init(void){
RCC->APB2ENR |= 1U<<2;
RCC->APB1ENR |= 1U<<17;//UART2 enable
GPIOA->CRL &= ~(0xFUL << 8);
GPIOA->CRL |= (1U << 9);
GPIOA->CRL |= (1U << 11);
/*unsigned uartdiv = APB1_CLK/USART_BAUDRATE;
unsigned mantissa = uartdiv / 16;
unsigned fraction = uartdiv % 16;
USART2_BRR_R = (mantissa << 4) | fraction;*/
/*uint32_t divier = (APB1_CLK + (USART_BAUDRATE/2U))/USART_BAUDRATE;
USART2->BRR = divier;*/
USART2->BRR = USART_BRR(36000000, 115200);
USART2->CR1 |= 1U<<3;
USART2->CR1 |= 1U<<13;
}
int __io_putchar(int ch)
{
while(!(USART2->SR & (1U<<7))){}
USART2->DR = (ch & 0xFF);
return ch;
}2025-11-27 12:53 AM
UART baud rates are standardized.
Take a scope (or logic analyser), and measure the actual bit length.
The best way to do this is to repeatedly transmit a 0x55 or 0xAA character.
And check the waveform of single character transmissions.
I would do that at 9600bps instead of 115200, but this is personal preference.
2025-11-27 12:53 AM - edited 2025-11-27 12:56 AM
@Jajang01 wrote:the user manual says the USART2 communication between the target STM32 and ST-LINK
It's more important that you use the correct pins ...
@Jajang01 wrote:I changed the code using the baud rate calculation from HAL but it transmits weird things
At least that shows that you are using the correct pins!
Garbage characters shows that your baud rate is wrong.
Again (as @TDK also said), use the supplied ST examples for reference...
2025-11-27 1:01 AM
At least you get something.
Probably baud rate still not correct.
What TDK meant:
USART2_CR1_R |= 1U<<3;
This "1U<<3" is unreadable for anybody but you, and in half a year it will probably be unreadable for you, too.
So please use the bit definitions from ST which you can usually find in ..\Drivers\CMSIS\Device\ST\STM32xyz\Include\stm32xyz.h
Bare metal (I approve!) doesn't mean to make things more complicated than necessary.
2025-11-27 1:56 AM
> This "1U<<3" is unreadable for anybody but you, and in half a year it will probably be unreadable for you, too.
> So please use the bit definitions from ST which you can usually find in ..\Drivers\CMSIS\Device\ST\STM32xyz\Include\stm32xyz.h
Most of us learned this the hard way, when it takes hours to understand our own code written just a few weeks ago.