0
\$\begingroup\$

I'm using STM32F4 with HAL. It is configured as a simple counter as it counts pulses via EXTI interrupt. I want to asynchronously ask for the current number of impulses via UART.

Highest frequency of EXTI pulses is 250kHz, uC clock frequency is 180MHz. I think the problem is, sometimes the frequency of pulses on EXTI input is so high that a single UART Rx callback is unable to complete before a new pulse arrives on EXTI input. EXTI has a higher priority, so it interrupts UART Rx interrupt and the UART's response is rubbish.

I'd like this setup to work in a way that EXTI indeed has a higher priority, because it cannot skip any impulse. But the UART should, on rx interrupt, grab the value of number of pulses (global variable) and send it's message 'Pulse counter = <pulseCounter>' without TX buffer being filled with rubbish values. Doesn't matter if another higher priority interrupt arrives or not.

Is there a flaw in my understanding of this problem, or is there a way to fix the code?

Here is an example of UART response: enter image description here

Here is the code initializing interrupts

HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

HAL_UART_Receive_IT(&huart1, &uartMessage, 1);
HAL_NVIC_SetPriority(USART1_IRQn, 1, 0);

Here is the code of interrupt callbacks:

EXTI:

void getCounterValue()
{
  *pGatePulseCounter = __HAL_TIM_GET_COUNTER(&htim6);
  pGatePulseCounter++;
  
  if (pGatePulseCounter == &gatePulseCounter[SAMPLE_TABLE_SIZE]) {
    pGatePulseCounter = gatePulseCounter;
    __HAL_TIM_SET_COUNTER(&htim6, 0);
  }
  
  pulseCounter++;
}

UART:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  uint8_t data[50] = {0};
  
  if (uartMessage == 'p') {
    sprintf((char*)data, "Pulse counter = <%d>\n\r", pulseCounter);
    HAL_UART_Transmit_IT(&huart1, data, sizeof(data));
    pulseCounter = 0;
  } else if (uartMessage == 'f') {
    sprintf((char*)data, "Current frequency = <%d>\n\r", pulseFrequency_Hz);
    for (int i=0; i<SAMPLE_TABLE_SIZE; i++) {
      gatePulseCounter[i] = 0;
    }
    HAL_UART_Transmit_IT(&huart1, data, sizeof(data));
  } else {
  
  }
  
  HAL_UART_Receive_IT(&huart1, &uartMessage, 1);
}

global variables:

int gatePulseCounter[SAMPLE_TABLE_SIZE];
int* pGatePulseCounter = gatePulseCounter;

int pulseFrequency_Hz;

unsigned long int pulseCounter = 0;
unsigned long int pulseCounterBuffer = 0;

uint8_t uartMessage;
\$\endgroup\$
2
  • 4
    \$\begingroup\$ Your real problem here is a mistaken design. To get this to work well, you'll need to decouple the mechanics of serial communication from the meaning. Don't try to respond to serial input in the callback, rather have ongoing serial receive into a circular buffer, then in the foreground pull things out of that (at interruptible leisure) and when you recognized a query, construct a response and transmit it. You can even do the transmission in the foreground, busy waiting and polling on the TX, since minor inter-character delay jitter does not matter with UARTs. \$\endgroup\$ Commented Aug 20, 2020 at 15:24
  • \$\begingroup\$ I reccommend to use timer for counting pulses. It could be configured to use pin as "clock" source and in TIMx_CNT register you will have how much changes was counted on pin. Also do not recommending sending UART messages by blocking approach. \$\endgroup\$
    – Misaz
    Commented Aug 26, 2020 at 8:33

1 Answer 1

0
\$\begingroup\$

Ok, I solved it.

The easiest thing to change that solves my issue is replacing:

HAL_UART_Transmit_IT(&huart1, data, sizeof(data));

with:

HAL_UART_Transmit(&huart1, data, sizeof(data), uartTimeout);

in UART RX callback.

That means I'm sending entire UART response inside UART RX callback instead of creating another interrupt request. I didn't notice any malfunctions during operation.

That being said, I still don't entirely understand why UART TX interrupt did not work correctly.

Based on the comments, I also tried it with a basic circular buffer. That means that my UART IRQ handler now looks like this:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (isFull(pQueue)) {
    return;
  } else {
    enqueue(pQueue, uartMessage);
  }
  
  HAL_UART_Receive_IT(&huart5, &uartMessage, 1);
}

and the response is generated and sent back in the while loop (a simple backgroud-foreground architecture). Note that it is now possible to properly use TX interrupt (HAL_UART_Transmit_IT()) in spite of a blocking version (HAL_UART_Transmit()):

if (isEmpty(pQueue)) {
      
    } else {
      char msg = dequeue(pQueue);
      
      uint8_t data[50] = {0};
      if (msg == 'p') {
        *pPulseCounterBuffer = *pPulseCounter;
        *pPulseCounter = 0;
        
        sprintf((char*)data, "Pulse counter = <%d>\n", *pPulseCounterBuffer); 
        
        // HAL_UART_Transmit(&huart5, data, sizeof(data), uartTimeout);
        HAL_UART_Transmit_IT(&huart5, data, sizeof(data));
      } else if (msg == 'f') {
        for (int i=0; i<SAMPLE_TABLE_SIZE; i++) {
          gatePulseCounter[i] = 0;
        }
        
        sprintf((char*)data, "Current frequency = <%d>\n", pulseFrequency_Hz);
        
        // HAL_UART_Transmit(&huart5, data, sizeof(data), uartTimeout);
        HAL_UART_Transmit_IT(&huart5, data, sizeof(data));
      } else {
        sprintf((char*)data, "Wrong command. Use <p> or <f>\n"); 
      
        // HAL_UART_Transmit(&huart5, data, sizeof(data), uartTimeout);
        HAL_UART_Transmit_IT(&huart5, data, sizeof(data));
      }
    }
\$\endgroup\$
1
  • \$\begingroup\$ HAL_UART_Transmit_IT is non-blocking and the stack local buffer gets trashed while still in use on return. The code still suffers from races and possible performance issues though. It looks like one possibility would be to remove the counter IRQ and use the hardware counter directly instead without __HAL_TIM_SET_COUNTER resets. Record the last __HAL_TIM_GET_COUNTER value during sampling and compute results relative to the prior value. Do you need to process requests inside interrupts or could you punt the work to the main loop and leave the interrupts to only manage circular buffers? \$\endgroup\$
    – doynax
    Commented Aug 20, 2020 at 12:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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