0
votes

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


}
1
How is your clock configured? Looks like in one case MCU might be running at 60MHz in other 72MHz.domen
Why do the code comments mention 115200 baud, when the question mentions 9600 baud?cooperised
Does your CubeMX project call a function named SystemClock_Config()? Show that function and the equivalent function in your True Studio project.kkrambo
the math is good for a 16Mhz clock 9600 baud. But what is your clock really set to? How are you running this, is there a bootloader or some other code that ran between reset and this code?old_timer
send 0x55s as fast as you can, 8N1 it will make a square wave on the scope.old_timer

1 Answers

1
votes

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.

/* Includes */
#include "stm32f4xx.h"

/* Private macro */
/* Private variables */
/* Private function prototypes */

void CLOCK_Init(void);

void UART2_Init(void);

void UART2_Test_TX(void);

/* Private functions */

/**
**===========================================================================
**
**  Abstract: main program
**
**===========================================================================
*/
int main(void)
{
  int i = 0;

  /* Initialization */

  CLOCK_Init();

  UART2_Init();

/* Test */
  UART2_Test_TX();

  /* Infinite loop */
  while (1)
  {
    i++;
  }
}

The initialization function for the clocks set the main and the APB1 clocks to a known values. So the configuration later is coherent:

/* CLOCK_Init
 * System clock source HSI
 * APB1 prescaler 1
 *
 * Register affected: RCC_CFGR
 *
 */
void CLOCK_Init(void)
{

    RCC_TypeDef * pRCC;
    pRCC = RCC;
    RCC_ClocksTypeDef clocks;

    uint32_t cpCFGR;

    uint32_t mask;

    //Setting HSI as system clock
    cpCFGR = pRCC->CFGR;


    // clear the SW1 SW0: Clock Source HSI
    mask = ~((uint32_t)0b011);

    cpCFGR &= mask;

    pRCC->CFGR = cpCFGR;

    // wait till SWS is 00, that is the clock source is HSI
    while(1)
    {
        cpCFGR = pRCC->CFGR>>2;
        if((~(cpCFGR) & (uint32_t)0b010) == (uint32_t)0b010) break;
    }


    /* Since the UART2 is connected to APB1 lets configure
     * the prescaler in a known value, lets say 1
     * */
    //change the preescaler of APB1 from 4 to 1

    cpCFGR = pRCC->CFGR;

    mask = ~((uint32_t)0x00001C00); // RCC_CFGR_PPRE1

    cpCFGR &= mask;

    pRCC->CFGR = cpCFGR;

}

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:

/*
 * UART2_Init
 * Asumming that the source clock is HSI and the APB1 prescaler is 1
 *
 * Register modified:   RCC_APB1ENR,
 *                      GIPIOA_MODER, GPIOA_AFR, GPIOA_PUPDR,
 *                      USART2_CR1, USART2_BRR
 *
 */
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 (we are not really going to use this
    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 I did this way for clarity


    //3. Configure the UART parameters: baudrate, data width, parity, number of stop bits etc
    /*
     * OVERSAMPLE 16
     * Baudrate 9600
     *
     * OVER8 sampling divider
     * 19.3.4 BaudRate = Fck/(16 * USARTDIV) .
     *      USARTDIV = DIV_Mantisa +(DIV_Fraction/ 8 x (2- OVER8))
     *      Fpclk = 16Mhz (default HSI)
     *
     * data width 8
     * parity None
     * stopbits 1
     * */



    // Configuring baudrate: 9600

    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

/*
    // configuring the baudrate deducting the system clock from the oscilloscope measurement, 13336000Hz
    // The USARTDIV is 86.822916667: 86+13/16
    pUSART2->BRR &= ~(0xFFFF); // Clear the mantisa and fraction
    pUSART2->BRR |= (86<<4); // Mantisa
    pUSART2->BRR |= (13<<0); //  Fraction
    uint32_t cBRR = pUSART2->BRR;
*/

    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.

}

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:

/* UART2_Test_TX
 * Sends forever the character U to produce a square train of frec half baudrate
 */
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
        {
            __NOP();
        }

        // Feed the data register with data
        pUSART2->DR = (uint16_t)data[0];
    }

}

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.