Неправильная реальная скорость передачи для связи UART на микроконтроллере stm32f4 при программировании его на голом железе

Я пытаюсь передать массив символов, используя usart2, напрямую настраивая связанные регистры: RCC, GPIO, USART2, и я получаю плохую скорость передачи при измерении с помощью осциллографа (около 8 Кбод при ожидании 9600.)

Я кодирую его, используя atollic True Studio 9.0.1 и проект «нового встроенного C», выбирая правильный MCU, stm32F401RE, и оставляю по умолчанию все, кроме отладочного зонда.

У меня только один include: #include "stm32f4xx.h"

Мое удивление заключается в том, что когда я создаю проект, используя stm32CubeMX и генерируя минимальный код, а затем заменяю содержимое main.c своим "голым" кодом проекта, временная развертка для uart выглядит почти идеально на осциллографе (9571 бод).

Разве это не интересно? Что может происходить?

Это мой код:

Включить и основной цикл:

#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++;
  }
}

Простая тестовая функция, которая все время отправляет «U»:

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];
    }

}

Функция инициализации:

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


}

person Jubeor    schedule 25.09.2018    source источник
comment
Как настроены ваши часы? Похоже, что в одном случае MCU может работать на частоте 60 МГц, в другом - 72 МГц.   -  person domen    schedule 25.09.2018
comment
Почему в комментариях к коду указано 115200 бод, а в вопросе - 9600 бод?   -  person cooperised    schedule 25.09.2018
comment
Ваш проект CubeMX вызывает функцию с именем SystemClock_Config()? Покажите эту функцию и эквивалентную функцию в своем проекте True Studio.   -  person kkrambo    schedule 25.09.2018
comment
математика хороша для тактовой частоты 16 МГц 9600 бод. Но на что на самом деле установлены ваши часы? Как вы это запускаете, есть ли загрузчик или какой-то другой код, который запускался между сбросом и этим кодом?   -  person old_timer    schedule 26.09.2018
comment
отправьте 0x55s как можно быстрее, 8N1 сделает прямоугольную волну на прицеле.   -  person old_timer    schedule 26.09.2018
comment
Это неуместно, но почему вы объявляете и используете pRCC и так далее? вы полностью должны иметь возможность использовать RCC->APB1ENR вместо pRCC->APB1ENR.   -  person J Faucher    schedule 03.11.2018


Ответы (1)


Что ж, после проверки моего кода и двойной проверки справочного руководства для моего микроконтроллера (RM0368 - справочное руководство для микроконтроллеров stm32f401xB / C / D / E) я понял и решил проблему. Я уточню.

Прежде всего я не выбрал источник системных часов и предположил, что это были HSI (высокоскоростные внутренние часы), и по какой-то причине это было не так, это был HSE, то есть внешний высокоскоростной осциллятор. Поэтому я решил, что мне нужна функция инициализации часов, чтобы выбрать правильный источник синхронизации.

Во-вторых, я полностью забыл о прескалере в APB1 (Advanced Peripheral Bus 1). Частота делится на 4. Таким образом, в функции инициализации часов я также настраиваю предварительный делитель APB1 на известное значение, в данном примере деленное на единицу.

/* 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++;
  }
}

Функция инициализации часов устанавливает основные часы и часы APB1 на известные значения. Таким образом, конфигурация позже согласована:

/* 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;

}

Инициализация USART2 настраивает его как UART со скоростью передачи 9600 бод, когда тактовая частота источника - HSI (16 МГц), а предварительный делитель для APB1 установлен на 1. Для получения дополнительной информации о значениях, пожалуйста, обратитесь к справочному руководству:

/*
 * 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.

}

Тестовая функция такая же, но я использую old_timer предложение для генерации прямоугольной последовательности импульсов, составляющей половину частоты последовательной передачи:

/* 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];
    }

}

Надеюсь, это закрывает вопрос. Если кому-то нужны дополнительные разъяснения, спрашивайте.

Я не анализировал происхождение конфигурации часов. Я предполагаю, что это связано с файлом инициализации startup_stm32f40xx.s.

person Jubeor    schedule 28.09.2018