I have a STM32F446RE on a Nucleo F446RE, and I'm trying to learn how to program the UART to write over the Virtual COM Port (connected to USART2).

I'm able to download someone else's code, modify it to use USART2, and see the messages in my Linux console. This shows that the Virtual COM Port is set up properly on the device, ST-LINK, and Linux.

Yet, my code writing to it doesn't work. My code does not freeze, because the LED continues to blink. But no chars appear on the serial port, and the stats (provided by tio) say 0 bytes received.

I've tried opening it in gdb, but then it does appear to freeze.

How can I debug this? Key portions of the code are below.

nt uart_init() {
  // This Nucleo board wires USART2 to PA2 (TX) and PA3 (RX)
  RCC->AHB1ENR |= BIT(0);                  // Enable GPIOA
  RCC->APB1ENR |= BIT(17);                 // Enable USART2
  GPIOA->MODER |= 2*BIT(2) + 1;            // PA2 to AF7
  GPIOA->MODER &= ~(2*BIT(2));
  GPIOA->AFR[0] |= 0b0111 << (2*4);        // Each pin's AF is controlled by a nibble
  GPIOA->AFR[0] &= ~(0b1000UL << (2*4));
  GPIOA->MODER |= 2*BIT(3) + 1;            // PA3 to AF7
  GPIOA->MODER &= ~(2*BIT(3));
  GPIOA->AFR[0] |= 0b0111 << (3*4);
  GPIOA->AFR[0] &= ~(0b1000UL << (3*4));
  // We want 115,200 baud, and since CPU Hz is 16*10^6, and we sample each bit 16 times (default), 
  // we need to sample approximately every 8.68 cycles (16*10^6 / 115200 = 8.68...)
  // The BRR is a fixed point num, with lowest 4 bits fractional
  USART2->BRR = 0b10001011; // 0b1000 = 8, 0b1011 = 0.68
  USART2->CR1 |= 0b1100; // Enable TX and RX, leave other defaults
  USART2->CR1 |= BIT(13); // Enable the USART
  return 0;

int dbg(char* msg) {
  int cnt = 0;
  for (char* p = msg; *p; ++p) {
    while (!(USART2->SR & BIT(7))) asm(""); // Spin until the char buffer is empty
    USART2->DR= *p;
  return cnt;

int main(void) {

  for (;;) {
    GPIOA->ODR |= BIT(5);
    GPIOA->ODR &= ~BIT(5);


#define BIT(x) (1UL << (x))
  • \$\begingroup\$ Your setting of the MODER makes no sense. Just look at it. Have you debugged what those writes do to the registers and do they end up with the value you intended it to have? That's just a mess of macros and magical constants, you need the reference manual to decipher what each line does, or rather, what do the accessed bits in the accessed register do, and you should think if you want to continue coding like that if you intend to understand what you code does a week later (or when you write it). \$\endgroup\$
    – Justme
    Sep 5 at 22:10
  • \$\begingroup\$ @Justme My goal is not to produce a device for practical reasons, but to understand, without any magic, what actually goes into a MCU. So, yes, reading the datasheet is in this case good. \$\endgroup\$ Sep 6 at 0:24
  • 1
    \$\begingroup\$ If you want to work without any magic, and understand the CPU, then give the bits or bit masks some sensible names, rather than using magical numeric constants everywhere. You won't learn the MCU this way, that's just looking at the reference manual, converting what you want to do as unnamed bit numbers, and to understand the code, you need to use the reference manual again what the unnamed bit numbers mean. It may be interesting now, but that's literally unreadable write-only code, and won't help in the long run, where you need to learn the next or the 10th MCU and jump between projects. \$\endgroup\$
    – Justme
    Sep 6 at 1:08
  • \$\begingroup\$ @Justme "If you want to work without any magic, and understand the CPU, then give the bits or bit masks some sensible names" - I would like to understand this better. Could you post an example of what you'd recommend? \$\endgroup\$ Sep 6 at 1:14
  • 1
    \$\begingroup\$ There's a reason magic numbers are frowned on in most programming circles. Among other reasons, they unnecessarily obscure the intent of your code. Do not discount that one of the primary purposes of human-readable programming languages is to explain the code to other humans. Something like GPIOA->MODER |= 2*BIT(2) + 1; is absolutely horrible at expressing your intent in human-readable terms. Use the manufacturer-provided C header files which #define macros for all of these peripheral registers. \$\endgroup\$
    – brhans
    Sep 6 at 13:00

1 Answer 1


I think you are writing incorrect values to MODER registers. Why would you toggle a same bit on and then off when configuring PA2 and PA3, but then leaving an unrelated bit for PA0 set?


It is likely that you intended to OR with BIT(2×2+1) which is just BIT(5) to turn it on and you likely intened to AND with inverted mask of BIT(2×2) which is just BIT(4) to turn it off.

Those would set MODER for PA2 correct.

ORing with "2×BIT(2) + 1" will just OR with two bits, 3 and 0.

ANDing with inverted "2×BIT(2)" will just clear bit 3 again.

  • \$\begingroup\$ Where and how is the code toggling the bit on and off? My goal is to put PA2 and PA3 into Alternate Function, which according to the data sheet is "Bits 2y:2y+1 MODERy[1:0]:...10: Alternate function mode" - so, for e.g. PA2, I turn bit 5 on and bit 4 off. I don't modify PA0 at all. \$\endgroup\$ Sep 6 at 0:23
  • 1
    \$\begingroup\$ You do. You just can't see that. Take a moment to examine what do lines "GPIOA->MODER |= 2*BIT(2) + 1;" and "GPIOA->MODER &= ~(2*BIT(2));" will do. Use pen and paper, or debugger. In fact I had to guess what the macro BIT does, you don't even provide it. Anyway, if you say what you think the lines do, we can see if it matches what they really do. \$\endgroup\$
    – Justme
    Sep 6 at 0:28
  • \$\begingroup\$ Thanks, this was indeed it. Serial port works well now! \$\endgroup\$ Sep 6 at 1:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.