2025-09-26 6:20 PM
Hello,
I am learning bare metal programming for STM32F446RE and am trying to write a simple transmit driver for USART2. I managed to do it but my only problem is when I use HSI 16Mhz clock speed (or anything lower) I get a weird first character. When I use a higher clock speed it works fine (using another clock source).
Does anyone know the reason for that?
Thank you
void USART_init(){
RCC->APB1ENR|=(1<<17);
USART2->CR1&=~(1<<12);
USART2->CR1&=~(1<<10);
USART2->CR1&=~(1<<15);
USART2->CR1|=(1<<13);
// Baud Rate of 115200 and Clock of 16Mhz
USART2->BRR=(8<<4 | 11);
USART2->CR1|=(1<<3);
USART2->CR1|=(1<<2);
char g[15];
sprintf(g,"Hello\r\n");
for(int i=0;i<strlen(g);i++){
while(!(USART2->SR & (1<<7)));
USART2->DR=g[i];
}
while(!(USART2->SR & (1<<6)));
2025-09-26 9:54 PM
One possible explanation is that the state of the TXD pin is wrong during your code that sets up the UART and directs output to TXD. You only notice this when running at 16 MHz or slower because then the processor takes sufficient time for this wrong state to be received as a weird character.
A way to avoid this is to initially have the TXD pin as GPIO output in the UART line-idle state, then set up the UART, and only then tell the pin to use the alternate function of UART.
As a point of coding style, I imagine you #include the ST header for your processor to get things like RCC->APB1ENR defined. That also has all the bits defined; your code would be more readable and less error-prone if you were to use the bit definitions. For example the line that enables the clock would become:
RCC->APB1ENR|=RCC_APB1ENR_USART2;Similarly for the USART e.g. USART_CR1_TXE
2025-09-26 10:36 PM - edited 2025-09-26 10:39 PM
I suspect a little problem with the setup code. Setup the UART first, then set the GPIO->AFR and GPIO->MODER registers for UART pins. For UART, write the BRR first, then set CR1 with a single asignment. Your BRR setting is funny and not exactly correct. It should simply be:
USART2->BRR = (UART_CLK_FREQ + BAUD_RATE / 2) / BAUD_RATE;
USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
2025-10-02 8:36 PM - edited 2025-10-02 8:38 PM
Thank you for your answers.
Sorry for the late reply.
2025-10-30 5:08 PM
I ended up finding what the problem was. In my GPIO_init() function, I was clearing the OTYPER and PUPDR registers for both PA2 and PA3. What I had in mind was simply specifying push-pull and no pull-up, pull-down.
I guess for some reason that was triggering a start condition/pulling the line low before sending anything, hence the garbage first byte of data. To solve it I removed OTYPER and PUPDR and now simply set the mode and alternate functionality.
// removed these two lines...
GPIOx->OTYPER&=~(3<<pinNum*2);
GPIOx->PUPDR&=~(3<<pinNum*2);Could someone confirm if this makes sense and possibly explain further please?
Thank you
2025-10-30 9:05 PM
You want the pin to have a defined state at startup to avoid an invalid character being sent. What would be best is to have an external pullup resistor to keep the pin idle high, and only initialize the pin after the UART is controlling it.
If you don't have an external pullup, still initialize it as AF after the peripheral is started.
The pin starts up without the internal pullup/pulldown enabled so messing with those isn't going to help anything. If it's going to float low, it has already done so.
2025-10-30 11:48 PM
Actually, it's quite a good idea to set the pull-up on RXD. This way you avoid false reception when the other party is disconnected or not yet initialized.