2

What I want to accomplish

So I want to accomplish the following:

I have 3 FreeRTOS-Threads which all shall read one of 3 (5) channels of my ADC. I want to poll the ADC. The Threads then enter the read value into a FreeRTOS-queue.

My code so far

I have the following functions:

ADC initialisation

void MX_ADC_Init(void)
{
    hadc.Instance = ADC;
    hadc.Init.Resolution = ADC_RESOLUTION_12B;
    hadc.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV4;
    hadc.Init.ScanConvMode = DISABLE;
    hadc.Init.ContinuousConvMode = DISABLE;
    hadc.Init.DiscontinuousConvMode = DISABLE;
    hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc.Init.NbrOfConversion = 1;
    hadc.Init.DMAContinuousRequests = DISABLE;
    hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
    hadc.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    hadc.Init.LowPowerAutoPowerOff = DISABLE;
    hadc.Init.LowPowerAutoWait = DISABLE;
    if (HAL_ADC_Init(&hadc) != HAL_OK)
    {
        Error_Handler();
    }

    for(int ch = 0; ch < GPIO_AI_COUNT; ch++)
    {
        ADC_Select_Ch(ch);
    }
}

GPIO initialisation

GPIO_InitTypeDef GpioInitStruct = {0};
GpioInitStruct.Pin = GPIO_AI1_PIN | GPIO_AI2_PIN | GPIO_AI3_PIN | GPIO_AI4_PIN | GPIO_AI5_PIN;
GpioInitStruct.Pull = GPIO_NOPULL;
GpioInitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GpioInitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOB, &GpioInitStruct);

Where the GPIO_AI2_PIN definition is defined as:

/* Analog Inputs ----------------------------------------------------------- */
#define GPIO_AI_COUNT 5

#define GPIO_AI1_PIN GPIO_PIN_3
#define GPIO_AI1_PORT GPIOB
#define GPIO_AI1_CH ADC_CHANNEL_2 /* ADC_IN2, Datasheet P. 51 */

#define GPIO_AI2_PIN GPIO_PIN_4
#define GPIO_AI2_PORT GPIOB
#define GPIO_AI2_CH ADC_CHANNEL_3 /* ADC_IN3, Datasheet P. 51 */

#define GPIO_AI3_PIN GPIO_PIN_14
#define GPIO_AI3_PORT GPIOB
#define GPIO_AI3_CH ADC_CHANNEL_1 /* ADC_IN1, Datasheet P. 55 */

#define GPIO_AI4_PIN GPIO_PIN_13
#define GPIO_AI4_PORT GPIOB
#define GPIO_AI4_CH ADC_CHANNEL_0 /* ADC_IN0, Datasheet P. 55 */

#define GPIO_AI5_PIN GPIO_PIN_2
#define GPIO_AI5_PORT GPIOB
#define GPIO_AI5_CH ADC_CHANNEL_4 /* ADC_IN4, Datasheet P. 54 */

Changing channel

void ADC_Select_Ch(uint8_t channelNb)
{
    adcConf.Rank = ADC_RANKS[channelNb];
    adcConf.Channel = GPIO_AI_CH[channelNb];
    adcConf.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
    if (HAL_ADC_ConfigChannel(&hadc, &adcConf) != HAL_OK)
    {
        Error_Handler();
    }
}

Where ADC_RANKS and GPIO_AI_CH are static arrays of the channels and ranks I want to use. The ranks increase with every channel.

Reading a channel

uint32_t ADC_Read_Ch(uint8_t channelNb)
{
    uint32_t adc_value = 0;

    ADC_Select_Ch(channelNb);
    HAL_ADC_Start(&hadc);
    if(HAL_OK == HAL_ADC_PollForConversion(&hadc, ADC_CONVERSION_TIMEOUT))
    {
        adc_value = HAL_ADC_GetValue(&hadc);
    }
    HAL_ADC_Stop(&hadc);

    printf("Ch%d / %x) %d\r\n", channelNb, adcConf.Channel, adc_value);

    return adc_value;
}

The problem

No matter what I try, the ADC only ever reads in the channel before the last channel I defined. Every time a conversion happens, the method HAL_ADC_GetValue(...) returns only the value of one channel, one, which I haven't even "selected" with my method.

What I've tried so far

I tried several different things:

  • Change NumberOfConversions
  • Change ScanMode, ContinuousConvMode, Overrun, EOCSelection, etc.
  • Use only Rank "1" when choosing a channel
  • Not use HAL_ADC_Stop(...), that however resulted in a failure (error handler was called)
  • Using the read functions etc. in the main(), not in a FreeRTOS thread - this also resulted in only one channel being read.
  • Change GPIO setup
  • Make the adcConfig global and public, so that maybe the config is shared among the channel selections.
  • Different clock settings
  • "Disabling" all other channels but the one I want to use (*)
  • Several other things which I've already forgotten

There seems to be one big thing I completely miss. Most of the examples are with one of the STM32Fxx microcontrollers, so maybe the ADC hardware is not the same and I can't do it this way. However, since I am using HAL, I should be able to do it this way. It would be weird, if it wouldn't be somehow the same across different uC families.

I really want to use polling, and ask one channel of the ADC by using some kind of channel selection, so that I can read them in different FreeRTOS tasks.

Disabling channels

I tried "disabling" channels but the one I've used with this function:

void ADC_Select_Ch(uint8_t channelNb)
{
    for(int ch = 0; ch < GPIO_AI_COUNT; ch++)
    {
        adcConf.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
        adcConf.Channel = GPIO_AI_CH[ch];
        adcConf.Rank = ADC_RANK_NONE;

        if (HAL_ADC_ConfigChannel(&hadc, &adcConf) != HAL_OK)
        {
            Error_Handler();
        }
    }

    adcConf.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
    adcConf.Channel = GPIO_AI_CH[channelNb];
    if (HAL_ADC_ConfigChannel(&hadc, &adcConf) != HAL_OK)
    {
        Error_Handler();
    }
}

Can anyone help me? I'm really stuck, and the Reference Manual does not provide a good "guide" on how to use it. Only technical information, lol.

Thank you!

Vandrey
  • 531
  • 1
  • 8
  • 23
  • Are you using some kind of mutex to make sure your three tasks are not all configuring / reading the ADC at the same time? What if you just run one of your three tasks - does that work? – pmacfarlane Jan 22 '23 at 18:10
  • I do use a mutex within the tasks (CMSIS FreeRTOS, "lock", cant show code right now). But that is a good question that I will follow up the next time I work on it! Thank you! – Vandrey Jan 23 '23 at 09:53

2 Answers2

1

I think your general approach seems reasonable. I've done something similar on a project (for an STM32F0), where I had to switch the ADC between two channels. I think you do need to disable the unused channels. Here is some code verbatim from my project:

static void configure_channel_as( uint32_t channel, uint32_t rank )
{
    ADC_ChannelConfTypeDef sConfig = { 0 };
    
    sConfig.Channel      = channel;
    sConfig.Rank         = rank;
    sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
    if ( HAL_ADC_ConfigChannel ( &hadc, &sConfig ) != HAL_OK )
    {
        dprintf ( "Failed to configure channel\r\n" );
    }
}
    
void adc_configure_for_head( void )
{
    configure_channel_as ( ADC_CHANNEL_0, ADC_RANK_CHANNEL_NUMBER );
    configure_channel_as ( ADC_CHANNEL_6, ADC_RANK_NONE );
}
    
void adc_configure_for_voltage( void )
{
    configure_channel_as ( ADC_CHANNEL_6, ADC_RANK_CHANNEL_NUMBER );
    configure_channel_as ( ADC_CHANNEL_0, ADC_RANK_NONE );
}
    
uint16_t adc_read_single_sample( void )
{
    uint16_t result;
    
    if ( HAL_ADC_Start ( &hadc ) != HAL_OK )
        dprintf ( "Failed to start ADC for single sample\r\n" );
    
    if ( HAL_ADC_PollForConversion ( &hadc, 100u ) != HAL_OK )
        dprintf ( "ADC conversion didn't complete\r\n" );
    
    result = HAL_ADC_GetValue ( &hadc );
    
    if ( HAL_ADC_Stop ( &hadc ) != HAL_OK )
        dprintf ( "Failed to stop DMA\r\n" );
    
    return result;
}

My project was bare-metal (no-OS) and had a single thread. I don't know enough about how your tasks are scheduled, but I'd be concerned that they might be "fighting over" the ADC if there is a chance they could be run concurrently (or pre-empt each other). Make sure the entire configure / read sequence is protected by some kind of mutex or semaphore.

EDIT: I notice a bug in your "Disabling channels" code, where you don't seem to set the rank of your enabled channel. I don't know if that is a transcription error, or an actual bug.

pmacfarlane
  • 3,057
  • 1
  • 7
  • 24
  • Indeed that is a bug! Thank you for highlighting it, I will try it with the fixed version the next time I'm working on that project. Your concern about threads fighting over resources is valid, that may happen. However, as far as I am aware, I already use locks/mutexes to hinder such behaviour. I will check everything again once I can work on the project again. For now, thank you for your input!!! – Vandrey Jan 23 '23 at 09:57
  • Hey, today I was again working on the project. I was able to make it work within 5 minutes because you highlighted the bug!!! Thank you so much!!! – Vandrey Feb 12 '23 at 13:16
1

Ranks are used to sort the ADC channels for cases of continuous measurrements or channel scans. HAL_ADC_PollForConversion only works on a single channel and somehow needs to now which channel to pick, therefore it will use the one with the lowest rank. To configure a specific channel to be measured once, set its rank to ADC_REGULAR_RANK_1. No need to disable any other channels, but remember to properly configure the ranks of all channels if you want to switch to channel scanning or continuous measurements.

HAL_ADC_ConfigChannel(&hadc1, &channel_config) only updates the configuration of the channel itself but does not update the configuration of the ADC peripheral itself. So it has to be understood as "configure this channel" and not as "configure ADC to use this channel"

Sagre
  • 462
  • 3
  • 12