Why HAL slow down the UART transmission.?

167 views Asked by At

I am using the Nucleo board STML073Rz to sample a sine wave of frequency 1khz. My STMCubeIDE configuration is given below.

System Clock : HSE 8Mhz

Tiimer2

prescalar : 8-1, ARR : 100-1, Timer Over flow frequency : 10000Hz, Trigger Event Selection: Update Event

ADC

Prescalar: Synchronous clock mode devided by 2, ADC resolution : 8bit resolution, Data alignment : right, External trigger conversion source : Timer2 trigger out event

UART2

clock : sysclock 8Mhz, baud rate : 115200, word length : 8bit, overrun : disabled

My requirement is sample the sine wave which is given into the ADC6 channel at a sampling frequency of 10000Hz. I have used the following logic to send the samples to gateway.

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    endOfConversion = 1;
    HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
}

HAL_TIM_Base_Start(&htim2);
HAL_ADC_Start_IT(&hadc);

numsamples=4500;
txcount = 0;
endOfConversion=0;
do
{
    if(endOfConversion==1)
    {
        adc_value = HAL_ADC_GetValue(&hadc);
        HAL_UART_Transmit(&huart1, &adc_value, 1, 10);
        endOfConversion=0;
        txcount+=1;
    }
}while(txcount<=numsamples);
HAL_ADC_Stop_IT(&hadc);

Here only the logic is displayed not the whole code. At the gateway I plot the fft from the samples and it was showing peak at 2000Hz instead of 1000Hz.

After some debug I came to know that the HAL delay the transmission of 1byte and hence only alteranate samples were sent to gateway.

Why the HAL UART delay the transmission?

1

There are 1 answers

4
GandhiGandhi On

Lets say this is the timeline of events with | marking off 10000Hz (100us) intervals.

The way Timer2 and the ADC are configured, there'll be a TIM2 and an ADC trigger on every conversion. From the datasheet, ADC conversions are so fast(0.4uz) relative to 100us intervals that we can pretend the ADC conversion callback happens at the same time as the trigger in the diagram.

|------|------|-----|-----|------|
^Tim2 IRQ / ADC trigger / HAL_ADC_ConvCpltCallback

Like @the-busybee's comment mentioned, UART speed is relevant. The UART is configured in poll mode, so that's going to be relevant too. HAL_UART_Transmit(&huart1, &adc_value, 1, 10); sends 10 bits (not ~bytes~, thanks @pmacfarlane) over the 115200Hz baud rate line, which will take about ~100us (this is a rough magnitude guess coming from 10/115200).

The previous timeline with timer and ADC events happen in hardware in the background. Meanwhile, the code runnning in the main do {} while loop is happening in software in the foreground at the same time. If the UART transmit time takes over 100us, then here's a likely timeline of what's happening:

|------|------|-----|-----|------|
^Tim2 IRQ / ADC trigger / HAL_ADC_ConvCpltCallback

|......Auuuuuuur....Auuuuuur......A
  |    | |     ^ The main loop resets the conversion flag with `endOfConversion=0`
  |    | ^ Time spent transmitting the read value over the UART
  |    ^ main loop reads ADC value HAL_ADC_GetValue(&hadc);
  ^ The main while loop is waiting for `if(endOfConversion==1)` to be true

The UART transmission is still ongoing when the next irq happens, but because endOfConversion gets reset after the UART finishes, then the loop has to wait for another TIM2 IRQ to read and transmit another sample.

I think you can fix this by resetting endOfConversion before the UART transmit to take out all of those "." states in the software timeline above like this:

    if(endOfConversion==1)
    {
        adc_value = HAL_ADC_GetValue(&hadc);
        endOfConversion=0;       
        HAL_UART_Transmit(&huart1, &adc_value, 1, 10);
        txcount+=1;
    }

If this solution causes funny sample rate issues, then you'll need to do something fancier like cache the data to transmit later, or use a higher baud rate UART (if possible).


Here's what I mean by caching the data to transmit later if you have enough memory.

numsamples=4500;
txcount = 0;
endOfConversion=0;
uint16_t samples[4500];
// Measure all of the samples
do
{
    if(endOfConversion==1)
    {
        adc_value = HAL_ADC_GetValue(&hadc);
        samples[txcount] = adc_value;
        endOfConversion=0;
        txcount+=1;
    }
}while(txcount<=numsamples);
HAL_ADC_Stop_IT(&hadc);

// Transmit the samples
for(int i = 0; i < numsamples; i++){
    adc_value = samples[i]
    HAL_UART_Transmit(&huart1, &adc_value, 1, 10);
}