Periodic task for triggering ADC conversions with stm32 HAL. How to use DMA?

Consider the following task used to periodically sampling a sensor:

void Sensor1_Task(void *pvParameters) {
    TickType_t lastWakeTime = xTaskGetTickCount();
    const TickType_t samplingPeriod = pdMS_TO_TICKS(20);

    while (1) {
        // Start ADC conversion for sensor 1
        HAL_ADC_Start(&hadc1);
        // Wait for ADC conversion to complete
        HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
        // Read ADC value for sensor 1
        sensor1_value = HAL_ADC_GetValue(&hadc1);
        // Stop ADC conversion for sensor 1
        HAL_ADC_Stop(&hadc1);

        // Delay until next sampling time
        vTaskDelayUntil(&lastWakeTime, samplingPeriod);
    }
}

This work very well, but I wish to replace HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);´ with DMA functions, i.e. HAL_ADC_Start_DMA` as

// some sort of global var
uint8_t convCompleted = 0;

// The following part replace HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY); in the previous code snipper
// ...
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)rawValues, 1);
while(!convCompleted);
HAL_ADC_Stop_DMA(&hadc1);
// ....

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*hadc) {
  // Shall I defer the interrupt to a task to set convCompleted = 1?
  convCompleted = 1;
 }

but here interrupts come into play and I know that it is generally a good idea to defer them to some task by releasing semaphores. However, for the above use-case I a not sure how to proceed.

One way is by using a timer and putting the dma in double buffer mode. Have the DMA run continuously in circular mode. Each time the timer triggers, you can swap buffer locations and read the previous buffer while the new one is written to. You can increment a task notification each transfer complete interrupt for debugging if you want to see how fast the buffer is filling, and adjust ADC resolution/sampling time until the buffer is guaranteed to fill at least once per timer period.

1 Like

Read this page - This page describes the xSemaphoreGiveFromISR() FreeRTOS API function which is part of the RTOS interrupt safe semaphore API source code function set. FreeRTOS is a professional grade open source RTOS for microcontrollers..

The code should look something like the following:

SemaphoreHandle_t xSemaphore = NULL;

void Sensor1_Task( void *pvParameters )
{
    xSemaphore = xSemaphoreCreateBinary();

    while( 1 )
    {
        HAL_ADC_Start_DMA(&hadc1, (uint32_t*)rawValues, 1);
        xSemaphoreTake( xSemaphore, portMAX_DELAY );
        HAL_ADC_Stop_DMA(&hadc1);

        // Delay until next sampling time
        vTaskDelayUntil(&lastWakeTime, samplingPeriod);
    }
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef*hadc)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    xSemaphoreGiveFromISR( xSemaphore, &( xHigherPriorityTaskWoken ) );

    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
1 Like

Generally, if I do a transfer via DMA, I end up disabling the interrupts from the device, and take get the completion from the DMA channel. That is the whole purpose of DMA, that the program doesn’t need to deal with the individual transmissions. If you are only going to get 1 reading, don’t use DMA. If you are getting a number of them, you generally don’t want to do anything until they all are in (or at least the “first buffer” of them if doing a long running collection).

1 Like

Thanks for the insight. You mean if I have something like the following:

const uint16_t NR_SAMPLES = 10000;
uint32_t rawValues[NR_SAMPLES]
// ...
        // every e.g. 100 ms calls the following
        HAL_ADC_Start_DMA(&hadc1, (uint32_t*)rawValues, NR_SAMPLES);
        xSemaphoreTake( xSemaphore, portMAX_DELAY );
        HAL_ADC_Stop_DMA(&hadc1);

i.e. in case I want to acquire a large information source every 100ms (e.g. a picture every 100 ms) then it makes sense to use DMA? Otherwise if the information source is small (e.g. one uint32_t size. like a temperature sensor reading) I better of using polling?
If so, is there any rule of thumbs on how large the source of information should be to move from polling to DMA?

A crude guideline is if you need to use more work to setup the DMA then to read the data itself, then it doesn’t make sense to use DMA. If it is at all close, you likely don’t want to do it. DMA is for when there is too much work to read for the processor to comfortably keep up ,

That is one problem of just using libraries, as you lose you sense of how much work is being done.

1 Like

By re-reading, I got another question: if you disable the interrupts, how do you know when the DMA has finished reading?
Do you run something like the following?

while(some_register != some_value){};

The DMA Channel has a transfer complete interrupt.