Skip to main content
Associate II
March 4, 2026
Question

UART TX inconsistent/delayed timing NUCLEO-F413ZH

  • March 4, 2026
  • 10 replies
  • 1434 views

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!
 

10 replies

Associate II
March 4, 2026

The screenshots of the logic analyzer show the capture of UART TX on top and UART RX, which is the 1-wire interface, on the bottom. In my code, I don't use any RX API calls since my application does not care about receiving.

TDK
Super User
March 4, 2026

What is inconsistent about the timing? What are you expecting to see?

Ahh, you want the character to start immediately, and a 0-1 bit delay is not acceptable. I don't think the UART hardware peripheral is set up to do that, unfortunately. 1-bit delays are generally not a big deal in UART communication.

"If you feel a post has answered your question, please click ""Accept as Solution""."
Associate II
March 4, 2026

Hi TDK,

Thanks for your swift reply.
As you can see, in the captures, one time the delta time is 15 us and in the other one is 97 us. The first is acceptable for my application since it's a fraction of the bit time (baud rate 9600) and as you can see, the logic analyzer reads the same value that it was sent (0xE7). In the other capture since it takes almost a bit time, the frames are not aligned and the value of the byte sent in the 1-wire interface is shifted 1 bit. My goal is to get that byte out consistently with very little delay, like the example with 15 us. 
So, according to what you are saying, I won't be able to do that? Is that true across all the STMicro MCUs?

TDK
Super User
March 4, 2026

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""."
Pavel A.
Super User
March 4, 2026

So transmit the byte right in the user button interrupt handler. To just send one byte you don't need the HAL ...IT overhead, it can be cut off, saving more time.

 

 

Associate II
March 4, 2026

I have also tried sending the byte in the button ISR and the result is the same. Sometimes I'd get an acceptable ~10 us delay and other times I'd get 100+ us. Granted that this was using HAL_UART_Transmit_IT().

The other thing you are proposing is to not use HAL_UART_Transmit_IT(). Can you please tell me what do you recommend using/doing?

Pavel A.
Super User
March 4, 2026

and other times I'd get 100+ us

This is very strange. Is there some other interrupt with priority same or higher than the EXTI pin interrupt?

The other thing you are proposing is to not use HAL_UART_Transmit_IT(). Can you please tell me what do you recommend using/doing?

Sending one byte takes just one write to the U(S)ART data register. Look at the "LL" library (stm32xxx_ll_usart.h, LL_USART_TransmitData8).

Waiting for the completion may be not necessary if the time between the GPIO pulses is long enough.

 

TDK
Super User
March 5, 2026

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""."
Associate II
March 5, 2026

@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
March 5, 2026

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

Associate II
March 5, 2026

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
March 5, 2026

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""."
Associate II
March 5, 2026

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.

TDK
Super User
March 5, 2026

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""."
Michal Dudka
Lead
March 6, 2026

I took an STM32F042K (Nucleo), clocked it at 48MHz, ran a simple program that waits for a rising edge on PB3, and as soon as it arrives, it sends 1 byte via UART (baud rate 9600). The delay of 2-3us corresponds to what would be expected with this method.

I assume that the core of your problem is HAL_UART_Transmit_IT. Try sending using the HAL_UART_Transmit() function or, in general, simply without HAL.


 while (1){
 	while(!LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_3)){}
 	while(!LL_USART_IsActiveFlag_TXE(USART2)){}
 	LL_USART_TransmitData8(USART2, 0b01010101);
 	while(LL_GPIO_IsInputPinSet(GPIOB, LL_GPIO_PIN_3)){}
 }

G01.PNGG02.PNG

waclawek.jan
Super User
March 6, 2026

@Michal Dudka ,

the UART in 'F0 is different from that in 'F4. I believe it may behave differently in this detail.

Again, this is ST unwilling to properly version the IP modules in the individual STM32 families...

This, btw., may be solution to @alsierraromero 's problem, too - using a newer STM32, including and beyond 'F0/'F3.

JW

Pavel A.
Super User
March 6, 2026

The interrupt entry itself can cause varying delay (as Jan W. explained here).

For better synchronization, maybe reconnect the trigger signal from the GPIO to the hardware flow control for the UART, with preloaded TX byte in the DR....) so that the CPU won't be in the loop at all.

 

Michal Dudka
Lead
March 6, 2026

What he describes is not software jitter. It is a feature of the UART on the F4. I decided to test it myself, and it is true—there is a 1-bit jitter (so it depends on the baud rate). The datasheet does not discuss this in detail.

Associate II
March 6, 2026

Thank you @TDK @Pavel A. @Michal Dudka and @waclawek.jan for all your help. Since it's clear now that the issue is related to the F4, I'll try a newer STM32 like @waclawek.jan suggested.

Cheers!

Associate II
March 6, 2026

Picked a NUCLEO-H533RE, used the user button callback to send a single byte via UART4 and it's working just fine. The byte is being sent consistently 4.4us after the GPIO edge.