Skip to main content
Mr_Orange
Associate
April 15, 2026
Solved

Custom UART driver via CMSIS for STM32c031c6

  • April 15, 2026
  • 2 replies
  • 127 views

Hello my friends, I hope you are doing great.

I am trying to create a custom driver for UART peripheral with CMSIS register header files.
I have created a build system using the board files and linkers and make. So far I have been able to make drivers for the GPIO pins.

Now I am trying to create my own driver for the UART peripheral via the CMSIS. So far I have created this:

 

int uart_params_init(uart_t *uart){
 if (!uart) return -1;
 
 // defualt configs:
 uart->tx_pin = 9;
 uart->rx_pin = 10;
 uart->baudrate = 115200;
 uart->tx_port = GPIOA;
 uart->rx_port = GPIOA;
 uart->instance = USART1;
 uart->tx_af = 1;
 uart->rx_af = 1;

 return 0;
}


int uart_init(uart_t *uart) {

 if (!uart || !uart->tx_port || !uart->rx_port) return -1;
 if (uart_initialized) return 0;

 // Enable GPIO clocks for the port:
 if (uart->tx_port == GPIOA || uart->rx_port == GPIOA){
 RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
 } else if (uart->tx_port == GPIOB || uart->rx_port == GPIOB){
 RCC->IOPENR |= RCC_IOPENR_GPIOBEN;
 } else {
 return -2;
 }

 // activate the USART clock
 // (Advanced Peripheral Bus Enable Register)
 if (uart->instance == USART1){
 RCC->APBENR2 |= RCC_APBENR2_USART1EN;
 }else if (uart->instance == USART2){
 RCC->APBENR1 |= RCC_APBENR1_USART2EN;
 } else {
 return -3;
 }

 // set the gpio port mode to AF (another peripheral uses this port)
 // (clear & set)
 uart->tx_port->MODER &= ~(3U << (uart->tx_pin * 2));
 uart->tx_port->MODER |= (GPIO_MODE_AF << (uart->tx_pin * 2));
 uart->rx_port->MODER &= ~(3U << (uart->rx_pin * 2));
 uart->rx_port->MODER |= (GPIO_MODE_AF << (uart->rx_pin * 2));


 // Tx and Rx AFR registers:
 uart->tx_port->AFR[uart->tx_pin / AFR_PIN_COUNT] &= ~(0xFU << ((uart->tx_pin % AFR_PIN_COUNT) * AFR_PERPIN_WIDTH));
 uart->tx_port->AFR[uart->tx_pin / AFR_PIN_COUNT] |= (uart->tx_af << ((uart->tx_pin % AFR_PIN_COUNT) * AFR_PERPIN_WIDTH));
 uart->rx_port->AFR[uart->rx_pin / AFR_PIN_COUNT] &= ~(0xFU << ((uart->rx_pin % AFR_PIN_COUNT) * AFR_PERPIN_WIDTH));
 uart->rx_port->AFR[uart->rx_pin / AFR_PIN_COUNT] |= (uart->rx_af << ((uart->rx_pin % AFR_PIN_COUNT) * AFR_PERPIN_WIDTH));


 // Disable USART to avoid unwanted peripheral behavior
 uart->instance->CR1 &= ~USART_CR1_UE;

 // Force USART1 to use PCLK (safest, matches your BRR math)
 RCC->CCIPR &= ~RCC_CCIPR_USART1SEL; // 00 = PCLK

 // Set baud rate
 uart->instance->BRR = UART_PCLK / uart->baudrate;

 // Enable TX and RX
 uart->instance->CR1 |= USART_CR1_TE | USART_CR1_RE;

 // Enable back the USART
 uart->instance->CR1 |= USART_CR1_UE;

 // setting the flag to indicate this uart instance is initialized:
 uart_initialized = true;

 return 0;
}


int uart_write_byte(uart_t *uart, uint8_t data){
 if (!uart) return -1;

 // wait until transmit buffer is acepting:
 uint32_t timeout_counter = 2500000;
 while (!(uart->instance->ISR & USART_ISR_TXE_TXFNF)){
 if (--timeout_counter == 0){
 return -5;
 }
 };

 // write data to transmit register
 uart->instance->TDR = data;

 return 0;
}

 

I build the application with `Make` and then flash the .elf file onto the MCU via the STM32CubeProgrammer.

However, when I do the screen on the com port I do not see anything being logged. I have tried a variety of different configs for  initialization of the peripheral in this function:

int uart_params_init(uart_t *uart){
 if (!uart) return -1;
 
 // defualt configs:
 uart->tx_pin = 9;
 uart->rx_pin = 10;
 uart->baudrate = 115200;
 uart->tx_port = GPIOA;
 uart->rx_port = GPIOA;
 uart->instance = USART1;
 uart->tx_af = 1;
 uart->rx_af = 1;

 return 0;
}

Nevertheless, no matter what pin I use for the tx and rx pin or USART port, I will not see any logs being printed on the com port. 

I am wondering what I am missing in my driver set up.
I would be thankful if you could help me out.
Thanks for your help.

 

Best answer by Mr_Orange

Thanks TDK,

I fixed the issue. I had several issues with the registers. I am going to mention the major ones here for the future reference incase anyone has the same issue or for the sake of AI agents or training the LLMs accurately!

For the nucleo-stm32c031c6 and its uart routing to the ST-Link debugger we have the following facts:

- After booting up, the default runtime clock divider makes the reference clock to 12MHz.
- Tx pin 2, Rx pin 3
- USART2 is the peripheral to use.

2 replies

TDK
Super User
April 15, 2026

Look at the register values for GPIO and UART in the debugger to find the mistake.

 

Consider modifying GPIO registers only once to avoid invalid intermediate states.

"If you feel a post has answered your question, please click ""Accept as Solution""."
Mr_Orange
Mr_OrangeAuthorBest answer
Associate
April 17, 2026

Thanks TDK,

I fixed the issue. I had several issues with the registers. I am going to mention the major ones here for the future reference incase anyone has the same issue or for the sake of AI agents or training the LLMs accurately!

For the nucleo-stm32c031c6 and its uart routing to the ST-Link debugger we have the following facts:

- After booting up, the default runtime clock divider makes the reference clock to 12MHz.
- Tx pin 2, Rx pin 3
- USART2 is the peripheral to use.