简体   繁体   中英

Unable to read multiple channels from single ADC with DMA [bare metal]

I'm trying to read two ADC channels sequentially from my STM32F407ZGT6 using DMA. I'm just trying to get the values from two potentiometers independently on each channel. Although the program doesn't crash, I does not update my variable's value ( sensor_val ).

I'm using DMA2_Stream0 Channel 0, since I'm using ADC1. For my ADC1, I'm using PB1 (channel 9) and PA1 (channel 1). I tried to follow this tutorial , except that I do not want to trigger my ADC from a timmer just yet, and I've been also checking the example on this question . My ADC callback also never gets called. As far as my understanding goes, the sequence of calls should be:

ADC1->SR EOC --> ADC->CR1 EOCIE --> DMA2_Stream0_IRQHandler() --> dma_ADC_callback()

⮤─────────────────────────────────────────────────────────┘

Maybe I do need to include a periodic call to read the ADC?

All the ADC/DMA functions are on their separate.c/.h files. I've already tried to declare sensor_val as a global variable or as an extern variable from the adc.c file, and both give the same result. Here is an approximate mwe of my code:

#include <stdint.h> //uint32_t
#include <stdio.h>  //printf
#include "stm32f407xx.h"

#define set(val, pos)       ((val) << (pos))
#define msk(size, pos)      (((1UL << (size)) - 1UL) << (pos))

static uint32_t sensor_val[2];
static void dma_ADC_callback(void);

void gpio_init(void)
{
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;    //Port A
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;    //Port B
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;    //Port C
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;    //Port D
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN;    //Port F
    RCC->AHB1ENR;
    
    //PORT B
    GPIOA->MODER |= set(m,(2));     //ADC || PA1 || ADC123_IN1
    GPIOB->MODER |= set(m,(2));     //ADC || PB1 || ADC12_IN9
}

void adc_init(void)
{
    /*Enable clock access to ADC*/
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
//  RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
//  RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;

    /* Config ADC parameters*/
    /* Regular Sequence Register 3
     * Since the sequence starts from
     * the back, we need to set channels
     * from SQ3[0] to SQ1[19]
     */
    ADC1->SQR3 |= set(0b1001,0); //sets channel PB1 (ADC12_IN9) as 1st conversion
    ADC1->SQR3 |= set(0b0001,5); //sets channel PA1 (ADC123_IN1) as 2nd conversion
    ADC1->SQR1 |= set(0b0001,ADC_SQR1_L_Pos); //tells the channel sequence lenght = 2

    /*If using more than one channel
     * SCAN is required
     */
    ADC1->CR1 |= set(1,ADC_CR1_SCAN_Pos);

    /*Adjust ADC sample time
     * The resulting frequency is
     * APB2/#cycles:
     * 000: 3 cycles
     * 001: 15 cycles
     * 010: 28 cycles
     * 011: 56 cycles
     * 100: 84 cycles
     * 101: 112 cycles
     * 110: 144 cycles
     * 111: 480 cycles = 42MHz/480 = 87.5kHz
     * */
    ADC1->SMPR2 |= set(0b111,ADC_SMPR2_SMP0_Pos);   //channel 0
    ADC1->SMPR2 |= set(0b111,ADC_SMPR2_SMP9_Pos);   //channel 9

    /*Turn Interruption On*/
    ADC1->CR1 |= set(1,ADC_CR1_EOCIE_Pos);

    /*Enable ADC*/
    ADC1->CR2  |= set(1,ADC_CR2_ADON_Pos);
}

void adc_start_conversion(void)
{
    ADC1->CR2 |= set(1,ADC_CR2_EOCS_Pos);       //enables multi-channel conversion
    ADC1->CR2 |= set(1,ADC_CR2_CONT_Pos);       //enables continuous conversion
    ADC1->CR2 |= set(1,ADC_CR2_SWSTART_Pos);    //starts conversion
}

/*ADC1 DMA2 => DMA2_Ch0_Stream0 and 4*/
/*ADC2 DMA2 => DMA2_Ch1_Stream2 and 3*/
/*ADC3 DMA2 => DMA2_Ch2_Stream0 and 1*/
void dma2_stream0_init(uint32_t memo, uint32_t periph, uint32_t len)
{
    /*Enable clock acces to DMA*/
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;

    /*Diable DMA2 Stream 0*/
    DMA2_Stream0->CR &= ~DMA_SxCR_EN;

    /*Clear all interrupt flags of Stream 0*/
    DMA1->LIFCR |= DMA_LIFCR_CFEIF0;
    DMA1->LIFCR |= DMA_LIFCR_CDMEIF0;
    DMA1->LIFCR |= DMA_LIFCR_CTEIF0;
    DMA1->LIFCR |= DMA_LIFCR_CHTIF0;

    /*Set the source buffer*/ //Memory Address
    DMA2_Stream0->M0AR = memo;

    /*Set destination buffer*/ //Peripherial Address
    DMA2_Stream0->PAR = periph;

    /*Set the length*/
    DMA2_Stream0->NDTR = len;

    /*Set Control options
     * Select Stream0_CH0       |
     * Prioritu Lvl = High      |
     * Memory Increment On      |
     * Circular mode on         |
     * Direction Per->Mem (0b00)|
     * Enable Transfer Complete interrupt
     */
    DMA2_Stream0->CR &= ~(DMA_SxCR_CHSEL |
                          DMA_SxCR_PL |
                          DMA_SxCR_MSIZE |
                          DMA_SxCR_PSIZE |
                          DMA_SxCR_PINC);
    DMA2_Stream0->CR |= (set(0,DMA_SxCR_CHSEL_Pos) |
                         set(2,DMA_SxCR_PL_Pos) |
                         set(1,DMA_SxCR_MINC_Pos) |
                         set(1,DMA_SxCR_CIRC_Pos) |
                         set(0,DMA_SxCR_DIR_Pos) |
                         set(1,DMA_SxCR_TCIE_Pos)
                         );

    /*Enable direct mode and disable FIFO*/
    DMA2_Stream0->FCR = 0;//set(0,DMA_SxFCR_FEIE_Pos);

    /*!! Enable DMA1 Stream 6*/
    DMA2_Stream0->CR |= set(1,DMA_SxCR_EN_Pos);

    /*Enable ADC transmitter DMA*/
    ADC1->CR2 |= set(1,ADC_CR2_DMA_Pos);

    /*DMA Interrupt enable in NVIC*/
    NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}


void DMA2_Stream0_IRQHandler(void)
{
    /*Check for transfer complete interrupt*/
    if(DMA2->LISR & msk(1,DMA_LISR_TCIF0_Pos))
    {
        //Clear flag
        DMA2->LIFCR |= msk(1,DMA_LIFCR_CTCIF0_Pos);

        //Callback
        dma_ADC_callback();
    }
}

static void dma_ADC_callback(void)
{
    printf("[ Readings Pots.: | %li | %li ]\r\n", sensor_val[0], sensor_val[1]);
}

int main(void)
{
    /*Setup*/
    //clock_init_168(); //SYSCLK = 168MHz, AHB = 84MHz, APB1 = 42MHz, APB2 = 84MHz
    //init_systick_MS(SYSTICK_LOAD_VAL_MS);
    gpio_init();

    dma2_stream0_init((uint32_t)&sensor_val, (uint32_t)&ADC1->DR, 2);
    adc_init();
    adc_start_conversion(); //continuous conversion

    char count = 0;

    for(;;)
    {
        debug_msg("count : %d", __PRETTY_FUNCTION__, count);
        delayMS(500);
        count++;
    }
}

I finally found a way to fix it!

First of all, the ADC has to be enabled before the DMA . With the code above, ADC1->CR2 |= set(1,ADC_CR2_DMA_Pos) wouldn't get set because I was trying to set the DMA before the ADC. Hence, my interruption never got called. The correct order of function calls is:

adc_init();
dma2_stream0_init((uint32_t)&sensor_val, (uint32_t)&ADC1->DR, 2);
adc_start_conversion();

However, after ADC_CR2_DMA was getting set, my callback got called, but only once . So I had to disable DMA selection with ACD1->CR2 = ADC_CR2_DDS , so DMA would issue conversion requests recurrently:

/*Enable ADC transmitter DMA*/
ADC1->CR2 |= (set(1,ADC_CR2_DMA_Pos) |  //<<make sure ADC is already enabled
              set(1,ADC_CR2_DDS_Pos));  //<<without DDS, the DMA does a single conversion
              

Once I did that, my callback got called and I was reading data, but only sensor_val[0] was being updated. That was happening because I had PSIZE == 00 , which is the value used to set MSIZE in direct mode. Since I'm using DMA for ADC (maximum 12-bit resolution), I changed sensor_val to uint16_t and PSIZE = 0b01 :

DMA2_Stream0->CR |= (set(0,DMA_SxCR_CHSEL_Pos)  |
                     set(2,DMA_SxCR_PL_Pos)     |
                     set(1,DMA_SxCR_PSIZE_Pos)  | //<<sets MSIZE=PSIZE in direct mode
                     set(1,DMA_SxCR_MINC_Pos)   |
                     set(1,DMA_SxCR_CIRC_Pos)   |
                     set(0,DMA_SxCR_DIR_Pos)    |
                     set(1,DMA_SxCR_TCIE_Pos));

And voilá!


NOTES

  • PINC should be kept at reset value for this configuration, otherwise ADC will expect data with more than 16-bits.
  • If you need slower rates of acquisition, instead of setting DDS , a DMA request function can be created where the DMA bit is reset and set again:
void adc_new_dma_conversion(void)
{
    /*DMA has to be reset first
     * then re-enabled to generate
     * a new DMA request
     */
    ADC1->CR2 &= ~set(1,ADC_CR2_DMA_Pos);
    ADC1->CR2 |= set(1,ADC_CR2_DMA_Pos);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM