cancel
Showing results for 
Search instead for 
Did you mean: 

NUCLEO-F103RB: Bare-metal USART driver

Jajang01
Visitor

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;
 }

 1.png

8 REPLIES 8
Andrew Neil
Super User

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?

A complex system that works is invariably found to have evolved from a simple system that worked.
A complex system designed from scratch never works and cannot be patched up to make it work.
TDK
Super User

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.

If you feel a post has answered your question, please click "Accept as Solution".

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

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;
 }

1.png

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.


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

A complex system that works is invariably found to have evolved from a simple system that worked.
A complex system designed from scratch never works and cannot be patched up to make it work.
LCE
Principal II

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.

Ozone
Principal II

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