10+ Mbit USART signal on STM32F429

Previously I wrote about the need for high-speed UART signal to feed into RS-485 line. Today I decided to try getting the required baud rate out of STM32F429-Discovery board.


The main issue is with clock configuration. To get maximum speed of 11.25 Mbit, we need 90MHz APB2 clock. The nightmare of configuring clock sources on STM parts is notorious. It is very easy to get lost in all the clock sources and prescalers. Luckily, nowadays STM32CubeMX simplifies it a great deal, by providing both GUI and auto-configuration options, depending on desired output.

To get 90MHz APB2 clock, we have to boost our core clock to 180MHz (since minimum prescaler there is 2) instead of default 72MHz. I could not figure out correct settings for obtaining 180MHz from 8MHz external clock source HSE, so I decided to settle with 10.5MBit readily obtainable from 168MHz core.
All we have to do, is change PWR_REGULATOR_VOLTAGE_SCALE to 1, which enables up to168MHz. In comment near this definition, there's a comment, that to enable 180MHz, we need to "activate over-drive mode". Will look into that later.
Other interesting change to note, is the need to change FLASH_LATENCY to 5, which deals with number of clock cycles to skip when accessing flash, which is with a somewhat limited speed. Otherwise we just have to change PLLM, PLLN, PLLP, PLLQ values to fit our bill and then feed PLL clock with appropriate dividers into APB1/APB2 configuration. All of it is done in SystemClock_Config() function in main.c.
  /* Configure the main internal regulator output voltage */
  __HAL_RCC_PWR_CLK_ENABLE();

  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /* Initializes the CPU, AHB and APB busses clocks */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSICalibrationValue = 16;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
    _Error_Handler(__FILE__, __LINE__);
  }

  /* Initializes the CPU, AHB and APB busses clocks */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                              | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) {
    _Error_Handler(__FILE__, __LINE__);
  }

I also decided to use USART6 (on PC6/7) for this experiment, so have to add extra configs to usart.c as well:
void MX_USART6_UART_Init(void) {

  huart6.Instance = USART6;
  huart6.Init.BaudRate = 10500000;
  huart6.Init.WordLength = UART_WORDLENGTH_8B;
  huart6.Init.StopBits = UART_STOPBITS_1;
  huart6.Init.Parity = UART_PARITY_NONE;
  huart6.Init.Mode = UART_MODE_TX_RX;
  huart6.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart6.Init.OverSampling = UART_OVERSAMPLING_8;
  if (HAL_UART_Init(&huart6) != HAL_OK) {
    _Error_Handler(__FILE__, __LINE__);
  }
}
We also have to add UART_HandletypeDef huart6 to usart.c and as an extern to usart.h, as well as init function forward declaration.

For this experiment we don't care about deinitialization code, that's pretty straight-forward - disable clock and release GPIOs. I have done it in code, but won't describe here.

In main() function in main.c I just try to send a couple of bytes, which I'll try to capture and verify with an oscilloscope and a FT232 (although that supports up to 3Mbit only, we'll see what happens):
  MX_USART6_UART_Init();

  uint8_t data[] = {0x33, 0x55, 0x77};
  while(1) {
   HAL_UART_Transmit(&huart6, data, 3, 100);
   HAL_Delay(5000);
  }

Ah, yes, printf_retarget.c gets changed to use USART6 instead of USART1. And that should be it. Now flash and see:

We get the same data decoded (0x33, 0x55, 0x77) with baudrate of 10 500 000, roughly 5 bits fit in 0.5us period, which gives us about 0.1us per bit or 10Mbit. I could try to measure it more accurately, but that's good enough.

I pushed this to a separate branch, to avoid having problems with a specific hardware requirements and allow mainline to be usable without extra HW.

I tried receiving the data with Putty (HTerm does not support custom speeds :/), and, as expected, it outputs garbage, even when no transmission is happening. If somebody would like to argue, that data sent (0x33, 0x55, 0x77) is garbage anyway, then I have to note, that I changed the output to ASCII string "FAAAAAST!" instead, to work around  this issue.

I did some experiments with setting 11.25 Mbit rate. Turns out, you might need some weird value crystal, to run at these speeds with USB connectivity enabled. USB requires 48 +-0.12MHz clock fed to it, which is not obtainable from 8MHz external crystal. If you don't need USB, then overdrive can be enabled, by adding

 /** Activate the Over-Drive mode */
 if (HAL_PWREx_EnableOverDrive() != HAL_OK) {
  _Error_Handler(__FILE__, __LINE__);
 }
after HAL_RCC_OscConfig() call in SystemClock_Config() function. Then settings PLLM = 8, PLLN = 360, PLLP = RCC_PLLP_DIV2 can be used to get 90MHz on APB2 bus and it works like a charm.

No comments:

Post a Comment