Неправильная реальная скорость передачи данных UART на микроконтроллере stm32f4 при программировании "голого металла"
Я пытаюсь передать массив символов с помощью usart2, напрямую конфигурируя соответствующие регистры: RCC, GPIO, USART2 и получаю неверную скорость передачи данных при измерении с помощью осциллографа (около 8 кбод при ожидании 9600).
Я кодирую его, используя atollic True Studio 9.0.1 и проект "новый встроенный C", выбирая правильный MCU, stm32F401RE, и оставляя все, кроме зонда отладки, по умолчанию.
У меня есть только одно: #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
}
1 ответ
Что ж, после проверки моего кода и двойной проверки справочного руководства для моего микроконтроллера (RM0368 - справочное руководство для микроконтроллеров stm32f401xB/C/D/E) я понимаю и решаю проблему. Я уточню.
Прежде всего, я не выбрал источник системных часов и предположил, что это был HSI (High Speed Internal Clock), и по какой-то причине это был не тот случай, когда это был 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 файл инициализации.