cancel
Showing results for 
Search instead for 
Did you mean: 

UART TX inconsistent/delayed timing NUCLEO-F413ZH

alsierraromero
Associate III

Hi,

I'm working on a piece of code using UART, where I need to send a byte of information right after the negative edge of that line is detected. Basically, my dev kit is connected to a 1-wire device through a simple 2 N-FET transistor for the TX side and directly to RX. Additionally, I have another GPIO connected to the 1-wire interface to detect, via interrupt, when the 1-wire device pulls the line down (start bit). The 1-wire device releases the bus right after the start bit.
The problem that I'm facing is that the timing at which the byte is sent by the MCU is very inconsistent, with a delay that is not tolerable by the application. I've tried several things with no improvements:

        - Send UART command using non-blocking mechanisms (interrupt and DMA)
        - Making the GPIO interrupt the one with the highest priority
        - Disable interrupts before sending UART command and enabling afterwards i.e.: __disable_irq() and              __enable_irq() 
         - Send commands in the GPIO edge callback
         - Send commands in main after checking a flag set in the edge callback
 
For the sake of simplicity, I tried all the above with a different setup. Instead of using a UART to 1-wire interface, I made a completely separate project, with the dev kit by itself, not connected to anything, where I'd send the UART command after detecting the edge of the user button in the dev kit. The results were the same. 
Just for reference, find below the simple piece of code of this last scenario I mentioned:
 
while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
      if(ready)
      {
          ready = 0;
          __disable_irq();
          HAL_GPIO_TogglePin(GPIOB, LD1_Pin);
          HAL_UART_Transmit_IT(&huart3, smsg, sizeof(smsg));
          __enable_irq();
      }

  }
  /* USER CODE END 3 */
}
 
where ready is set in the user button GPIO callback.
The piece of code above uses UART3, but the result is the same using UART5 for example.
Find attached the clock setup and logic analyzer captures that show the inconsistency of the timing.
I'd appreciate your help on this matter. Thanks!
 
26 REPLIES 26
 

Ah, so the issue is not what I thought.

The issue is that the first bit is ill-formed with the duration being 1-2 bits. Correct?

What you're doing should be working. I'm not immediately sure why it's not. I suspect if I create a new project and try to duplicate the results, I won't be able to. But I may try later.

If you feel a post has answered your question, please click "Accept as Solution".
TDK
Super User

As expected, I cannot duplicate this on an STM32F4 board. I suspect something is fighting for driving that line. An analog capture might help, or a complete project.

The delay between the edge and the first bit is as expected (0-1 bits of jitter) but the first bit is well defined.

Or I've mistaken what pin is connected to what in your signal plots. It's not very clear.

If you feel a post has answered your question, please click "Accept as Solution".

@TDK @Pavel A. Thanks for your tips and trying to replicate the issue.

In the 1-wire interface, the first bit is the start bit and the one that forms the frame (the next 8 bits). If it takes the MCU more than half of the bit time to start transmitting then the whole frame shifts and, when the UART RX on the other side samples at the middle of the bit, it will be corrupted and therefore the wrong data will be received. I've tried everything possible, even creating a blank project (several times) only using the bare minimum IOs, writing directly in the data register in the gpio edge callback, reducing the priority of all interrupts except the EXTI corresponding to the GPIO and still the jitter varies wildly (10 - 100+ us). This is really a head scratcher for me since I've done this with a  MCU from a different manufacturer and it works fine. 

waclawek.jan
Super User

This is the right time to post a minimal schematics (best based on some "known good" board such as Nucleo) and minimal but complete compilable code, so that we can reproduce the problem.

If you don't have such, it's time to prepare it, as a proof of concept.

JW

As I explained at my initial post, the origin and main reason why I'm facing this issues is about sending data through a 1-wire interface with minimum latency. In order to remove the 1-wire interface stuff out of the equation, I also made a small proof of concept using the NUCLEO-F413ZH, where I send a byte via UART3 (PD8) right after the user button (PC13) has been pressed. Capturing the difference in time between the positive edge of the button pulse and when the byte starts, shows the issue I'm facing. For reference check the attached screenshots. Code below: 

 

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2026 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart3;

/* USER CODE BEGIN PV */
static uint8_t ready = 0;
uint8_t smsg[] = {0xAA};
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART3_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 96;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 2;
  RCC_OscInitStruct.PLL.PLLR = 2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses 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_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART3 Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_USART3_UART_Init(void)
{

  /* USER CODE BEGIN USART3_Init 0 */

  /* USER CODE END USART3_Init 0 */

  /* USER CODE BEGIN USART3_Init 1 */

  /* USER CODE END USART3_Init 1 */
  huart3.Instance = USART3;
  huart3.Init.BaudRate = 9600;
  huart3.Init.WordLength = UART_WORDLENGTH_8B;
  huart3.Init.StopBits = UART_STOPBITS_1;
  huart3.Init.Parity = UART_PARITY_NONE;
  huart3.Init.Mode = UART_MODE_TX_RX;
  huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart3.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart3) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART3_Init 2 */

  /* USER CODE END USART3_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  /* USER CODE BEGIN MX_GPIO_Init_1 */

  /* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

  /*Configure GPIO pin : usr_btn_Pin */
  GPIO_InitStruct.Pin = usr_btn_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(usr_btn_GPIO_Port, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

  /* USER CODE BEGIN MX_GPIO_Init_2 */

  /* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if(GPIO_Pin == usr_btn_Pin) // Check if the interrupt came from PC13
  {
	  USART3->DR = 0xAA;
  }
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @PAram  file: pointer to the source file name
  * @PAram  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

 

 

TDK
Super User

Well if that's the problem, then there's no way to reduce jitter below 1 bit. You could do this using DMA or a timer channel or SPI or something else, but the UART peripheral won't help you here.

If you feel a post has answered your question, please click "Accept as Solution".

I've tried DMA and the issue shows as well.

I'm just trying to understand why if the MCU is running with a HCLK=96MHz (~10ns), it takes roughly 10000 cycles (100 us) to send the byte given the the aforementioned setup and code. This is happening even writing directly to the UART data register in the GPIO callback.

Because that's how the UART peripheral was designed. It sends out a bitstream. Nothing to send? Send IDLE for another bit.

UART + DMA won't show any better results than just UART. The UART peripheral won't help you here.

If you feel a post has answered your question, please click "Accept as Solution".

Sorry, maybe I wasn't clear enough. I'm not talking about the time it takes to send the byte. I'm talking about the difference in time between the event that it's the trigger (push of a button) and when the data starts to show in the UART TX line (the edge of the start bit). Check the screenshots sent before