Electrical – STM32F103 ADC via DMA – sequence scan to RAM can’t make it work as expected

adcdmastm32

I'm using an STM32F103C8T6 BluePill board and CubeMX generated init code (Low-level libs) to setup the ADC + DMA to sequentially scan though 8 channels and write to an array in RAM. However, it either doesn't work at all, or (when configured as in the code below) seems to just convert/capture channel 1 (PA0) 8 times.

I've trawled the data-sheet, appnotes, CubeMX example code, numerous examples online and for the life of me I can't see what's wrong in my setup – so I'm hoping someone here might spot it!

All the other stuff (clocks, GPIO pins etc.) is the standard boilerplate LL init code generated by CubeMX so hopefully no surprises.

Code calls MX_ADC1_Init() and then ADC_DMA_Start() to kick things off, but the contents of RawVals is always 8 of the same reading plus or minus a bit or two of noise (but the values all follow the voltage on PA0).

#define NUM_ADCS    8
uint16_t RawVals[NUM_ADCS];


/* ADC1 init function */
void MX_ADC1_Init(void)
{
    LL_ADC_InitTypeDef ADC_InitStruct = {0};
    LL_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0};
    LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};

    LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
    /* Peripheral clock enable */
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC1);

    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);
    /**ADC1 GPIO Configuration  
     *  PA0-WKUP   ------> ADC1_IN0
     *  PA1   ------> ADC1_IN1
     *  PA4   ------> ADC1_IN4
     *  PA5   ------> ADC1_IN5
     *  PA6   ------> ADC1_IN6
     *  PA7   ------> ADC1_IN7 
     */
    GPIO_InitStruct.Pin = LL_GPIO_PIN_0|LL_GPIO_PIN_1|LL_GPIO_PIN_4|LL_GPIO_PIN_5|LL_GPIO_PIN_6|LL_GPIO_PIN_7;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* ADC1 DMA Init */

    /* ADC1 Init */
    LL_DMA_SetDataTransferDirection(ADC_DMA, ADC_DMA_CH, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

    //LL_DMA_SetChannelPriorityLevel(ADC_DMA, ADC_DMA_CH, LL_DMA_PRIORITY_LOW);
    LL_DMA_SetChannelPriorityLevel(ADC_DMA, ADC_DMA_CH, LL_DMA_PRIORITY_MEDIUM);

    LL_DMA_SetMode(ADC_DMA, ADC_DMA_CH, LL_DMA_MODE_CIRCULAR);

    LL_DMA_SetPeriphIncMode(ADC_DMA, ADC_DMA_CH, LL_DMA_PERIPH_NOINCREMENT);

    LL_DMA_SetMemoryIncMode(ADC_DMA, ADC_DMA_CH, LL_DMA_MEMORY_INCREMENT);

    LL_DMA_SetPeriphSize(ADC_DMA, ADC_DMA_CH, LL_DMA_PDATAALIGN_HALFWORD);

    LL_DMA_SetMemorySize(ADC_DMA, ADC_DMA_CH, LL_DMA_MDATAALIGN_HALFWORD);

    // LL_DMA_ConfigAddresses(DMA_TypeDef *DMAx, uint32_t Channel, uint32_t SrcAddress, uint32_t DstAddress, uint32_t Direction)
    LL_DMA_ConfigAddresses(ADC_DMA, ADC_DMA_CH, LL_ADC_DMA_GetRegAddr(ADC1, LL_ADC_DMA_REG_REGULAR_DATA), (uint32_t)RawVals, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

    LL_DMA_SetDataLength(ADC_DMA, ADC_DMA_CH, DMA_DATA_SIZE*2); // Size in bytes

    /**
     * Common config
     */
    ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT; // 12-bits right-aligned
    ADC_InitStruct.SequencersScanMode = LL_ADC_SEQ_SCAN_ENABLE; // Sequentially scan through channels
    LL_ADC_Init(ADC1, &ADC_InitStruct);
    ADC_CommonInitStruct.Multimode = LL_ADC_MULTI_INDEPENDENT; // ADC1 operating alone
    LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(ADC1), &ADC_CommonInitStruct);
    ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
    ADC_REG_InitStruct.SequencerLength = 8;
    ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
    //ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; // Run through conversions once & then stop
    ADC_REG_InitStruct.ContinuousMode =  LL_ADC_REG_CONV_CONTINUOUS;
    ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
    LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);

    /**
     * Configure Regular Channel
     */
    LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, LL_ADC_CHANNEL_0);
    LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_0, REGULAR_CHANNEL_SAMPLE_TIME);

    /**
     * Configure Regular Channel
     */
    LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, LL_ADC_CHANNEL_1);
    LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_1, REGULAR_CHANNEL_SAMPLE_TIME);

    /**
     * Configure Regular Channel
     */
    LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_3, LL_ADC_CHANNEL_4);
    LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_4, REGULAR_CHANNEL_SAMPLE_TIME);

    /**
     * Configure Regular Channel
     */
    LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_4, LL_ADC_CHANNEL_5);
    LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_5, REGULAR_CHANNEL_SAMPLE_TIME);

    /**
     * Configure Regular Channel
     */
    LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_5, LL_ADC_CHANNEL_6);
    LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_6, REGULAR_CHANNEL_SAMPLE_TIME);

    /**
     * Configure Regular Channel
     */
    LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_6, LL_ADC_CHANNEL_7);
    LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_7, REGULAR_CHANNEL_SAMPLE_TIME);

    /**
     * Configure Regular Channel
     */
    LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_7, LL_ADC_CHANNEL_TEMPSENSOR);
    LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_TEMPSENSOR, LL_ADC_SAMPLINGTIME_239CYCLES_5);
    LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_TEMPSENSOR);

    /**
     * Configure Regular Channel
     */
    LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_8, LL_ADC_CHANNEL_VREFINT);
    LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_239CYCLES_5);
    LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_VREFINT);

}


void ADC_DMA_Start(void)
{
    LL_ADC_Enable(ADC1);
    LL_DMA_EnableChannel(ADC_DMA, ADC_DMA_CH);
    LL_ADC_REG_StartConversionSWStart(ADC1);
}

Best Answer

Well I found the problem and it's in the initialisation code generated by STM32CubeMX itself!

This line in MX_ADC_Init():

ADC_REG_InitStruct.SequencerLength = 8;

should be:

ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_8RANKS;

which expands to:

#define LL_ADC_REG_SEQ_SCAN_ENABLE_8RANKS  (ADC_SQR1_L_2 | ADC_SQR1_L_1 | ADC_SQR1_L_0)

in the headers, which in raw terms is:

((0x4U << (20U)) | (0x2U << (20U)) | (0x1U << (20U)))

So basically CubeMX was missing out the bit-shift or is expecting LL_ADC_REG_Init to insert it.