Search code examples
stm32channeladchal

STM32WL55JC1 - HAL ADC wont change channels


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!


Solution

  • 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.