2
\$\begingroup\$

I am trying to implement IDLE detection described in AN3109. The idea is simple:

  • Run timer in slave reset mode with maximum possible period;
  • Configure CH2 for input capture on rising edge, however do not use it for capture. Instead mux the edge detection signal TI2FP2 as slave reset trigger. Connect this input to UART Rx wire;
  • Configure CH1 for output compare with period equal to desired IDLE time.

Without intervention timer will simply count up, rolling over infrequently. However every rising edge on UART Rx will reset count back to 0. When Rx pulses stop coming counter is allowed to grow and eventually reach CCR value, at which point IDLE interrupt is generated.

For testing purposes I configured timer for 5s period and 1s CCR pulse. The expected behavior is to see update event every 5s and output compare 1s after that. What I see, however, is both events coming simultaneously every 5s.

Here is timer initialization generated by Cube:

  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_IC_InitTypeDef sConfigIC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 5493;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 65535;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_OC_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_IC_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
  sSlaveConfig.InputTrigger = TIM_TS_TI2FP2;
  sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  sSlaveConfig.TriggerFilter = 0;
  if (HAL_TIM_SlaveConfigSynchro(&htim1, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_TIMING;
  sConfigOC.Pulse = 13107;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_OC_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 0;
  if (HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }

This is how I start timers:

HAL_TIM_OC_Start_IT(&htim1, TIM_CHANNEL_1);
HAL_TIM_Base_Start_IT(&htim1);

And here are event handlers:

void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
    printf("OC %lu\r\n", HAL_GetTick());
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    printf("UEV %lu\r\n", HAL_GetTick());
}

Any suggestions on what I am doing wrong? I suspect it could be some tiny detail in the configuration, but HAL documentation is not very helpful in this regard.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Why don't you use IDLE detection hardware? Do you want to support more then 2x baudrate period blanking time? \$\endgroup\$
    – stiebrs
    Dec 11 '19 at 13:26
  • \$\begingroup\$ @stiebrs Not all STMs have hardware support for it. Also, I have to use HAL for this project and HAL does not properly support it either. \$\endgroup\$
    – Maple
    Dec 11 '19 at 17:17
2
\$\begingroup\$

Just in case anybody tries to follow AN3109, here is what I did wrong:

  • forgot to configure TIM1 clock source as TIM_CLOCKSOURCE_INTERNAL;
  • did not enable TIM1 update interrupt in NVIC (it is not necessary for IDLE detection but required for this test);

In the end everything works as expected. Well... I'd expect HAL to support HW IDLE flag but that is probably to much to ask.

\$\endgroup\$
4
  • \$\begingroup\$ I'd expect HAL to support HW IDLE flag [...] Are you looking for __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);? \$\endgroup\$
    – Num Lock
    Oct 9 '20 at 9:25
  • \$\begingroup\$ Also there is __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE); as a check and __HAL_UART_CLEAR_IDLEFLAG(&huart1); to clear. \$\endgroup\$
    – Num Lock
    Oct 9 '20 at 9:27
  • \$\begingroup\$ @NumLock what I am looking for is something like "void HAL_UART_IdleCallback(UART_HandleTypeDef *huart);" that can be used alongside other callback functions, e.g. HAL_UART_TxCpltCallback. Without this one has to substitute default IRQ handling and basically keep copying changes every time new HAL version gets downloaded. \$\endgroup\$
    – Maple
    Oct 9 '20 at 22:31
  • \$\begingroup\$ BTW, the feature request for this sits on STM forum for more than 3 years already \$\endgroup\$
    – Maple
    Oct 10 '20 at 5:26
1
\$\begingroup\$

HAL supports UART Idle Line Detection feature, but it was a bit cumbersome to find the pieces. Important parts are as follows:

  1. Enable the idle line detection in main.c:
  /* USER CODE BEGIN USART2_Init 2 */
  __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);  // enable receive intterupts 
  __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);  // enable idle line detection 
  /* USER CODE END USART2_Init 2 */
  1. Sort out the idle line event from within USARTx_IRQHandler in stm32f4xx_it.c:
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
  if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) { 
    __HAL_UART_CLEAR_IDLEFLAG(&huart2); // taken from https://electronics.stackexchange.com/questions/471272/setting-up-stm32-timer-for-uart-idle-detection#comment1353999_480556
    uart2_idleHandler();
  } else {
    uart2_handler();
  }
  return;
  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
  /* USER CODE END USART2_IRQn 1 */
}

Testing:

  1. Create the following handlers:
void uart2_handler(void){
  char buff; 
  HAL_UART_Receive (&huart2, (uint8_t *)&buff, 1, 400);
  HAL_UART_Transmit(&huart2,(uint8_t *)&buff, 1, 0xFFFF);   // echo back 
}

void uart2_idleHandler(){
  char i[1] = {"_"}; 
  HAL_UART_Transmit(&huart2, (uint8_t *)i, 1, 0xFFFF);  // add "_" character at the end of response
}
  1. Open a serial port client in your PC, setup as 115200 baud, 8N1.
  2. Send "Hello world".
  3. You'll receive "Hello world_", which will indicate that you are properly getting idle line interrupts.

Example Project

Here is the full example that runs on STM32F407 Discovery board.

\$\endgroup\$
2
  • \$\begingroup\$ This approach is similar to what I did in the end, except I've used RTO instead of IDLE for better timing flexibility. Unfortunately that came back to bite me, due to idiotic handling of RTO in HAL, which considers RTO event an error and arbitrary terminates ongoing transfers, both Rx and Tx! The problem is that RTO flag can be set again while in user handler, then default HAL processing will detect it and terminate my transfers even though everything is going OK. Arghhh! \$\endgroup\$
    – Maple
    Jun 1 '21 at 16:51
  • 1
    \$\begingroup\$ Also to say "HAL supports it" is a wishful thinking. They merely provide bit masks to operate corresponding registers. Even though user code will survive recompilation, any changes in UART configuration will remove or add new USARTx_IRQHandler routines, so you have to go there and add your code again. Real support requires dedicated weak callback function that user can provide without tweaking framework code. \$\endgroup\$
    – Maple
    Jun 1 '21 at 16:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.