1
\$\begingroup\$

I am working with a very old problem which is to convert ADC and send to UART via DMA. I am stuck at making the correct sampling time. What I had done so far is to configure TIMER2 and ADC. I Used ADC2 and TIMER2. Timer2 TRGO Update event is used to Trigger the ADC. Everything seems fine. ADC is converting the values and sending to USART is also working properly but the I set the Timer 2 interrupt to happen at every second. I also blinked the LED in interrupt Service Routine. The LED blinks at 2hz rate. But the ADC sampling seems faster then the LED Blinking rate. I also tried to change the Timer 2 interrupt rate by increasing and decreasing the counter values but it doesn't effect the ADC sampling rate. I think I am doing something wrong.

Here is the Timer configuration which I am doing for STM32F407 board at 168Mhz Clock

void TIM2_Configuration(void){
    // enable TIM2 clock (bit0)
    RCC->APB1ENR |= (1 << 0);
    //TIM2->PSC = 1749; //FOR DISCOVERY
        //TIM2->PSC = 1874; //FOR NECLUEO

        TIM2->PSC = 8399;   //For 84Mhz
    TIM2->ARR = 10000; //it will get one second delay
    
     /* Reset the MMS Bits */
  TIM2->CR2 &= (uint16_t)~TIM_CR2_MMS;
  /* Select the TRGO source */
  TIM2->CR2 |=  TIM_CR2_MMS_1; //UPDATE EVENT
    
    

    // Update Interrupt Enable
    TIM2->DIER |= (1 << 0);

    // enable TIM2 IRQ from NVIC
    NVIC_EnableIRQ(TIM2_IRQn);

    // Enable Timer 2 module (CEN, bit0)
    TIM2->CR1 |= (1 << 0);
    
    
}

and this is how I am configuring my ADC


void init_ADC(void)
{
    RCC->AHB1ENR|=RCC_AHB1ENR_GPIOCEN;  //GPIOC clock enable
    GPIOC->MODER|=(3u<<(2*0));  //ADC input pin is analogue mode    
    RCC->APB2ENR|=RCC_APB2ENR_ADC1EN;       //ADC clock enable
    ADC1->SQR1&=~ADC_SQR1_L;                        //set number of conversions per sequence to 1
    ADC1->SQR3&=~ADC_SQR3_SQ1;                  //clear channel select bits
    ADC1->SQR3|=10;                     //set channel
    ADC1->CR1 &= ~ADC_CR1_SCAN; //SCAN mode disabled    
    ADC1->CR2 &= ~ADC_CR2_CONT; //Disable continuous conversion
    
    ADC1->CR2 &=  ~(1<<29);
    ADC1->CR2 |= (1<<28);   //Trigger on Rising edge
    
    //External Event TIM2 TRGO
    ADC1->CR2 &= ~(1<<3);   
    ADC1->CR2 |= (1<<2);
    ADC1->CR2 |= (1<<1);
    ADC1->CR2 &= ~(1<<0);
    
    
    ADC1->SMPR1 |= ADC_SMPR1_SMP10;
    
    
    
    ADC1->CR2|=ADC_CR2_ADON;                        //enable ADC    
    ADC1->CR2|=ADC_CR2_SWSTART;     
}

This is how I am doing in IRQ for TIMER 2 which is also working fine

void TIM2_IRQHandler(void)
{
   

    // clear interrupt status
    if (TIM2->DIER & 0x01) {
        if (TIM2->SR & 0x01) {
            TIM2->SR &= ~(1U << 0);
        }
    }

    GPIOD->ODR ^= (1 << 13);

    
}

and Here in the main while(True) loop I read and send data to usart like this

  while(1)
    {
            
            while((ADC1->SR&ADC_SR_EOC)==0){__NOP();}       
                    
                unsigned int volts = ADC1->DR;  
     
            uart_tx(((volts/10000)%10)+48);
            uart_tx(((volts/1000)%10)+48);
            uart_tx(((volts/100)%10)+48);
            uart_tx(((volts/10)%10)+48);
            uart_tx(((volts)%10)+48);
            uart_tx('\r');
            uart_tx('\n');
             // slow down
            //
             delay(300);
        
    }

I am not sure why it is sampling so fast instead of the same rate which happens for the LED at GPIOD-13.

Also I am totally blank about the DMA. I had gone trough the HAL libraries but I am not sure how to test and proceed toward DMA direction from this point. Any guidance or helping material is highly appreciated. I also didn't find any source of help other than the reference manual and the Datasheet. Which although is pretty explainetry but didn't seems friendly to me.

Is there any tutorial series over internet which deals with CMSIS at this advance level (like handling DMAs and multiple interrupts)? I had seen few tutorials but they only deal with little peripherals. Not joining two or more peripherals together like I am doing by using TIMER ADC , DMA and USART together?

\$\endgroup\$
2
  • 2
    \$\begingroup\$ CMSIS only helps with core peripherals not with peripherals from the chip vendor, so there is no coverage of DMA, ADC or interrupts (other then the NVIC). \$\endgroup\$
    – Arsenal
    Commented Aug 12, 2020 at 15:09
  • \$\begingroup\$ Running just the timer numbers: you get a 500 ms update event with 168 MHz. It's a bit hard to diagnose what's wrong if you say "it seems faster", can you be more precise? Like take an actual time measurement between UART outputs for each sample. I've done something similar (trigger DAC with timer (which gets data from DMA) which triggers ADC (which transfers data with DMA)) and it worked like expected. \$\endgroup\$
    – Arsenal
    Commented Aug 12, 2020 at 15:17

1 Answer 1

1
\$\begingroup\$

You're likely configuring your external trigger in the ADC wrong:

//External Event TIM2 TRGO
ADC1->CR2 &= ~(1<<3);   
ADC1->CR2 |= (1<<2);
ADC1->CR2 |= (1<<1);
ADC1->CR2 &= ~(1<<0);

The bit positions are 24-27. I'd like to recommend using the processor defines to prevent such mistakes:

//External Event TIM2 TRGO
const uint32_t TIM2_TRGO = ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1; // 0110
ADC1->CR2 &= ~(ADC_CR2_EXTSEL);   // remove all selections
ADC1->CR2 |= TIM2_TRGO;

I'm not very familiar with online courses, the reference manual did a good job providing all the info I needed to get DMA working as well.

There is often a section about DMA for each peripheral and then there is the chapter about the whole DMA controller.

It's easiest if you work with same width transfers at first, so if you need bytes in the peripheral use a byte array in the memory.

Often it goes something like this:

  • Configure the channel to the correct peripheral source (different DMA channels or streams can only be connected to certain peripherals).

  • Set the correct peripheral address (data register), peripheral size, memory address and memory size. Set the correct direction (transfer from or to the peripheral)

  • Set if the memory or peripheral address shall be incremented with each transfer (usually memory yes and peripheral no for an array in memory)

  • Set the number of transfers

  • enable the DMA channel

  • enable the DMA request generation in the peripheral

If you get that working (like have 10 samples of the ADC in an 16 bit array) you can try out different things like a circular buffer with the DMA or different sizes, get the data from ADC directly in 32 bit wide arrays. Stuff like that.

\$\endgroup\$
1
  • \$\begingroup\$ Wow.. Thanks! That works perfectly as expected. Yes, I just miss understood the bit 3:0 number this way. And Now I checked again, The way I was doing actually makes continuous conversions that's why I was seeing the conversions that fast. Now I will try the DMA, Thanks Again. \$\endgroup\$ Commented Aug 12, 2020 at 15:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.