I'm trying to transmit a char array using the usart2 configuring the associated registers directly: RCC, GPIO, USART2 and I obtain a bad baud rate when measuring with the oscilloscope (about 8k baud when expecting 9600.)
I'm coding it using atollic True Studio 9.0.1 and a "new embedded C" project selecting the correct MCU, stm32F401RE, and leaving everything but the debug probe as default.
I only have one include: #include "stm32f4xx.h"
My surprise is that when I create the project using stm32CubeMX and generating the minimum code and then substitute the main.c content with my "bare-metal" project code the time base for the uart looks almost perfect on the oscilloscope (9571 bauds).
Isn't it interesting? What may be going on?
This is my code:
Include and main loop:
#include "stm32f4xx.h"
void UART2_Init(void);
void UART2_Test_TX(void);
int main(void)
{
int i = 0;
UART2_Init();
UART2_Test_TX();
while (1)
{
i++;
}
}
A simple test function that sends "U" all the time:
void UART2_Test_TX(void)
{
USART_TypeDef * pUSART2;
pUSART2 = USART2;
char data[] = "U";
while(1)
{
while(!(pUSART2->SR && (1<<7)))// TXE transmit data register empty
{
}
pUSART2->DR = (uint16_t)data[0];
}
}
The initialization function:
void UART2_Init(void)
{
RCC_TypeDef * pRCC;
pRCC = RCC;
GPIO_TypeDef * pGPIOA;
pGPIOA = GPIOA;
USART_TypeDef * pUSART2;
pUSART2 = USART2;
//1. Enable the peripheral clock
/*
* The USART2 is connected to the APB1 bus so we have to check here
* the Reset and Clock Configuration Enable register for APB1APB1_ENR
*
* */
pRCC->APB1ENR |= (1 << 17); // Set the USART2EN bit to enable the clock (RCC_APB1ENR_USART2EN)
//2. Configure the GPIO PINS related to UART TX and RX
/*
*
* To do this we need to find the alternate function of the pins in a reference table. That is located in the Section4, table 8 of the Data sheet:
* USART2_RX *PA3, PD6
* USART2_TX *PA2, PD5
* Also in the user manual of the board (UM1724) the RX and TX pins accessible from the PC are located in port A. We have a winner.
*
* That's good but not enough. PINs MAY HAVE UP TO 16 DIFFERENT FUNCTIONALITIES so we need another table and register to select it (in datasheet table 9)
*
* 2.1 So, enable the RCC clock for GPIOA AHB1
* 2.2 Configure the PINs as alternate function
* 2.3 Configure or not Internal Pull-up resistor
* 2.4 select the alternate function Table 9 of datasheet + GPIOA_AFRL, the low pins registers, from 0 to 7
*
* */
pRCC->AHB1ENR |= 1<<0;//RCC_AHB1ENR_GPIOAEN; // 2.1 Enable the source clock for GPIOA
// configuring pin 2 TX
pGPIOA->MODER &= ~(0b11<<4); // 2.2 Clear previous configuration in PIN2
pGPIOA->MODER |= (0b10<<4); // 2.2 Configure PIN2 as alternate function GPIO_Mode_AF
pGPIOA->AFR[0] &= ~(0b1111<<8); // 2.4 clear the bits in the register ;
pGPIOA->AFR[0] |= (0b0111 <<8); // 2.4 AF7 for TX pin
// FOR SPI, I2C, UART the lines must be held high. So we need pull_up resistors
pGPIOA->PUPDR &= ~(0b11<<4);
pGPIOA->PUPDR |= (0b01 <<4);
// configuring pin 3 RX
pGPIOA->MODER &= ~(0b11<<6); // 2.2 Clear previous configuration in PIN3
pGPIOA->MODER |= (0b10<<6); // 2.2 Configure PIN3 as alternate function GPIO_Mode_AF
pGPIOA->AFR[0] &= ~(0b1111<<12); // 2.4 clear the bits in the register;
pGPIOA->AFR[0] |= (0b0111 <<12); // 2.4 AF7 for RX pin
// FOR SPI, I2C, UART the lines must be held high. So we need pull_up resistors
// pGPIOA->PUPDR &= ~(0b11<<6);
// pGPIOA->PUPDR |= (0b01 <<6);
// Note: I would be more efficient to configure all the pins at the same time but we did this way for clarity
//3. Configure the UART parameters: baudrate, data width, parity, number of stop bits etc
/*
* OVERSAMPLE 16
* Baudrate 115200
*
* OVER8 sampling divider
* 19.3.4 BaudRate = Fck/(16 * USARTDIV) .
* USARTDIV = DIV_Mantisa +(DIV_Fraction/ 8 x (2- OVER8))
* Fck = 16Mhz (default HSI)
*
* data width 8
* parity None
* stopbits 1
* */
// Configuring baudrate: 115200, real baudrate 115107.913669065. Error 0.08%
pUSART2->CR1 &= ~(1<<15); // O: Oversample 16 OK1
pUSART2->BRR &= ~(0xFFFF); // Clear the mantisa and fraction
pUSART2->BRR |= (104<<4); // Mantisa
pUSART2->BRR |= (3<<0); // Fraction
uint32_t cBRR = pUSART2->BRR;
//pUSART2->BRR |= (0x9B); // Mantisa and Fraction as Hex OK1
/*
pUSART2->CR1 &= ~(1<<12); // 8 bits OK1
pUSART2->CR1 &= ~(1<<10); // Parity control disable OK1
pUSART2->CR2 &= ~(0b11<<12); // 1 stop bits OK1
*/
//4. Enable the TX engine of UART2 (do we need RX or we can save power?)
/*
* UE bit USART enable
* TE bit Transmit enable
* TDR Register to output the data
* */
pUSART2->CR1 |= (1<<3); // O: Transmit enable
//5. ENABLE THE USART peripheral Always at the end
/*
* Section 19.3.2
* USART_CR1.UE enable the usart
* USART_CR1.M number of bits 8,9
* USART_CR2 number of stops
* DMA enable...
*
* */
pUSART2->CR1 |= (1<<13); // O: USART enable
// Here is ready
}
Well, After checking my code and double checking the reference manual for my microcontroller (RM0368 is the reference manual for stm32f401xB/C/D/E MCUs) I understand and solve the problem. I will elaborate.
First of all I had not selected the system clock source and assumed that it was the HSI (High Speed Internal clock) and for some reason that wasn't the case it was the HSE, that is, external high speed oscillator. So I decided that I need a clock init function to select the correct clock source.
Second I completely forgot about the prescaler in the APB1 (Advanced Peripheral Bus 1). It was dividing the frequency by 4. So in the clock init function I also configure the APB1 prescaler for a known value, in the example, divided by one.
The initialization function for the clocks set the main and the APB1 clocks to a known values. So the configuration later is coherent:
The USART2 initialization configures it as a UART with a baudrate of 9600 bauds when the source clock is the HSI (16MHz) and the prescaler for the APB1 is set to 1. For more information about the values please refer to the reference manual:
The test function is the same but I incorporate old_timer suggestion to generate a square pulse train of half the frequency of the serial transmission:
I hope this close the question. If someone need further clarification please ask.
I have not analyze the origin of the clock configuration. I guess that has to do with the startup_stm32f40xx.s the initialization file.