1
\$\begingroup\$

I am trying to scan multiple ADC channels on my STM32H753ZI Nucleo board. I have a TIM2 running at a super slow frequency (2Hz) which triggers the ADC conversions. I have it very slow so that I can see the data printing in real time. I also have a few interrupts running that check for errors in the DMA transmission and an overrun in the ADC conversion. I typically get a "Transfer Error" flag when I first run it. None after that. I also would usually get ADC overrun errors until I set the AUTDLY bit in the CFGR register.

At the moment, I just have two potentiometers connected to ADC3_CH2,CH3 (PF9, PF7). When I print the data being sent over DMA, it is all zeroes. I have looked at this post and took a lot of info from it. Unfortunately, it still does not work. One question among many is why you have to enable the SRAM clock?

My code is posted below. Any help is greatly appreciated.

--EDIT--

The above issue was resolved by moving the location that the value is stored to the SRAM.

However, the data that I am receiving is not totally accurate. Only one of the channels is correct, while the other 2 give junk data. Below is the edited code to reflect changes I have made since the original post.

I think it might be some sort of timing issue or something with the clocks, but I really don't know.

main.c

#include "stm32h7xx.h"
#include "printUSART.h"
#include "timer.h"
#include "adc.h"
#include "dma.h"

int tim_flag = 0;
int dma_flag = 0;
int adc_flag = 0;

int main(void){

    USART3_Init();
    ADC3_CH32_Init();
    ADC3_CH32_Scan();
    DMA_Init();
    TIM2_CH1_Init();
    TIM2_CH1_Freq_DC(2, 0.5);

    printf("Start of Conversions:\n");

    while(1){
        if(tim_flag == 1){
            printf("\nData: %f, %f, %f\n", (val[0] / 65535.0 * 3.3), (val[1] / 65535.0 * 3.3), (val[2] / 65535.0 * 3.3));       // print value in data register
            tim_flag = 0;
        }

        if(dma_flag == 1){
            printf("Transfer Error\n");
            dma_flag = 0;
        }
        else if(dma_flag == 2){
//          printf("Transfer Complete\n");
            dma_flag = 0;
        }

        if(adc_flag == 1){
            printf("Overrun\n");
            adc_flag = 0;
        }
    }
}

void TIM2_IRQHandler(void){
    if(TIM2->SR & TIM_SR_UIF){
        TIM2->SR &= ~TIM_SR_UIF;
        tim_flag = 1;
    }
}

void ADC3_IRQHandler(void){
    if(ADC3->ISR & ADC_ISR_OVR){
        ADC3->ISR |= ADC_ISR_OVR;
        adc_flag = 1;
    }
}

void DMA1_Stream0_IRQHandler(void){
    if(DMA1->LISR & DMA_LISR_TEIF0){
        DMA1->LIFCR |= DMA_LIFCR_CTEIF0;
        dma_flag = 1;
    }

    if(DMA1->LISR & DMA_LISR_TCIF0){
        DMA1->LIFCR |= DMA_LIFCR_CTCIF0;
        dma_flag = 2;
    }
}

adc.c

/*
 * adc.c
 *
 *  Created on: Jul 1, 2021
 *      Author: jeremywolfe
 *
 *  ADC3_INP3 on PF7
 *  ADC3_INP2 on PF9
 *  ADC3_INP4 on PF5
 */
#include "adc.h"

void ADC3_CH32_Init(void){
    ADC3->CR &= ~ADC_CR_ADEN;                           // ensure that the ADC is off
    RCC->AHB4ENR |= RCC_AHB4ENR_GPIOFEN;                // enable clock for port F
    RCC->AHB4ENR |= RCC_AHB4ENR_ADC3EN;                 // enable clock for ADC3
    ADC3_COMMON->CCR |= ADC_CCR_CKMODE_0 | ADC_CCR_CKMODE_1;    // (25.4.3) choosing the clock source for ADC

    GPIOF->MODER = GPIO_MODER_MODE9_0 | GPIO_MODER_MODE9_1; // setting PF5, 7, 9 as alternate function
    GPIOF->MODER = GPIO_MODER_MODE7_0 | GPIO_MODER_MODE7_1;
    GPIOF->MODER = GPIO_MODER_MODE5_0 | GPIO_MODER_MODE5_1;

    ADC3->CR &= ~ADC_CR_DEEPPWD;                        // (25.4.6) takes ADC out of deep power down mode

    ADC3->CR |= ADC_CR_ADVREGEN;                        // (25.4.6) enable the voltage regulator
    while(!(ADC3->ISR & ADC_ISR_LDORDY)){}              // (25.4.6) waits for reg to startup

    /*  Turning on the ADC (25.4.9) */
    ADC3->ISR |= ADC_ISR_ADRDY;                         // clear the ADRDY bit
    ADC3->CR |= ADC_CR_ADEN;                            // enable the ADC
    while(!(ADC3->ISR & ADC_ISR_ADRDY)){}               // wait for ADC to be ready
    ADC3->ISR |= ADC_ISR_ADRDY;

    ADC3->CR &= ~ADC_CR_ADSTART;

}

void ADC3_CH32_Scan(void){

    ADC3->IER |= ADC_IER_OVRIE;         // enable overrun interupts
    NVIC_EnableIRQ(ADC3_IRQn);          // enable on the NVIC

    ADC3->PCSEL |= ADC_PCSEL_PCSEL_2;   // preselect channel
    ADC3->PCSEL |= ADC_PCSEL_PCSEL_3;   // preselect channel
    ADC3->PCSEL |= ADC_PCSEL_PCSEL_4;   // preselect channel

    ADC3->SQR1 |= ADC_SQR1_L_1;                     // sequence of 3 channels
    ADC3->SQR1 |= ADC_SQR1_SQ1_1;                   // channel 2 first
    ADC3->SQR1 |= ADC_SQR1_SQ2_1 | ADC_SQR1_SQ1_0;  // channel 3 second
    ADC3->SQR1 |= ADC_SQR1_SQ3_2;                   // channel 4 third

    ADC3->SMPR1 |= ADC_SMPR1_SMP2_0| ADC_SMPR1_SMP2_1 | ADC_SMPR1_SMP2_2;   // max clock cycles
    ADC3->SMPR1 |= ADC_SMPR1_SMP3_0| ADC_SMPR1_SMP3_1 | ADC_SMPR1_SMP3_2;   // max clock cycles
    ADC3->SMPR1 |= ADC_SMPR1_SMP4_0| ADC_SMPR1_SMP4_1 | ADC_SMPR1_SMP4_2;   // max clock cycles

    ADC3->CFGR |= ADC_CFGR_AUTDLY;                      // wait for conversion to complete
    ADC3->CFGR &= ~ADC_CFGR_CONT;                       // disable continuous mode
    ADC3->CFGR |= ADC_CFGR_EXTEN_0;                     // trigger on rising edge
    ADC3->CFGR |= ADC_CFGR_EXTSEL_0 | ADC_CFGR_EXTSEL_1 | ADC_CFGR_EXTSEL_3;// trigger on event 9, tim1_trgo
    ADC3->CFGR |= ADC_CFGR_DMNGT_0 | ADC_CFGR_DMNGT_1;  // set DMA circular mode as data management

    ADC3->CR |= ADC_CR_ADSTART;                         // start conversions
}

int ADC3_Calibration(void){
    /*  Calibrating the ADC */
    ADC3->CR &= ~ADC_CR_ADEN;                           // ensure that the ADC is off
    ADC3->CR |= ADC_CR_ADCAL;                           // start calibration
    while(ADC3->CR & ADC_CR_ADCAL){}                    // wait until ADCAL is 0 and cal is complete

    printf("\nCalibration is complete");
    return (int)ADC3->CALFACT;
}

dma.c

/*
 * dma.c
 *
 *  Created on: Aug 30, 2021
 *      Author: jeremywolfe
 */

#include "dma.h"

uint16_t *val = (uint16_t*)0x30000000;

void DMA_Init(void){

    DMA1_Stream0->CR &= ~DMA_SxCR_EN;               // turn off DMA controller
    while(DMA1_Stream0->CR & DMA_SxCR_EN){}         // wait until off

    RCC->AHB2ENR |= RCC_AHB2ENR_D2SRAM1EN;  // SRAM clock???
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;     // DMA clock

    DMA1_Stream0->CR |= DMA_SxCR_TEIE | DMA_SxCR_TCIE;  // interrupts for transfer error and transfer complete
    NVIC_EnableIRQ(DMA1_Stream0_IRQn);                  // enable on the NVIC

    DMA1_Stream0->PAR = (uint32_t)(&(ADC3->DR));        // setting the ADC data register as the peripheral address
    DMA1_Stream0->M0AR = (uint32_t)(val);               // setting the "val" array as the memory location

    DMA1_Stream0->NDTR = 3;                             // 3 datas items

    DMAMUX1_Channel0->CCR = 115;                        // DMAMUX 115 (adc3_dma)

    DMA1_Stream0->CR |= DMA_SxCR_PL_0 | DMA_SxCR_PL_1;  // set highest priority

    // set up DMA settings
    // circular mode, peripheral, memory size = 2 bytes, memory increments by 2 bytes every time
    DMA1_Stream0->CR |= DMA_SxCR_CIRC | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC | DMA_SxCR_MSIZE_0;

    DMA1_Stream0->CR |= DMA_SxCR_EN;        // enable the DMA
}
```
\$\endgroup\$
4
  • \$\begingroup\$ 2 potential problems: #1 is that your val array is located in the DTCM memory block which only the core and MDMA modules can access, and not the 'normal' DMA - so it can't write there. #2 is that reads by the code from your val array are being cached and you're only seeing the 'stale' values from the cache. \$\endgroup\$
    – brhans
    Aug 31 at 11:53
  • \$\begingroup\$ To check if it's a cache issue, try issuing a 'DSB' instruction immediately before you printf the values. Depending on your compiler this could take one of many forms: DSB(), __DSB, asm("DSB"), __asm("DSB") or something similar to that. Note though that this is a bit of a sledgehammer approach because it invalidates the entire data cache and not just the tiny bit for val. \$\endgroup\$
    – brhans
    Aug 31 at 11:57
  • \$\begingroup\$ To check if it's a memory location issue, look at the .map file your compiler should produce after building your project. Find the entry for the val array and check if its address is in the 0x20000000-0x2001ffff DTCM range. If it is then you'll need to move it into a different memory area to be able to use the DMA to write into it. \$\endgroup\$
    – brhans
    Aug 31 at 12:01
  • \$\begingroup\$ @brhans That turned out to be the issue and DMA is now working. However, only one channel is giving correct data with the other one just sending junk. Do you see any issues with my ADC configuration that could be causing this? \$\endgroup\$ Aug 31 at 19:19
1
\$\begingroup\$

Turns out I am really dumb and forgot to put an |= when I simply had an =. This is the code in question:

GPIOF->MODER &= ~0xFFFF;
GPIOF->MODER |= GPIO_MODER_MODE9_0 | GPIO_MODER_MODE9_1;
GPIOF->MODER |= GPIO_MODER_MODE7_0 | GPIO_MODER_MODE7_1;
GPIOF->MODER |= GPIO_MODER_MODE5_0 | GPIO_MODER_MODE5_1;

Very obvious now why I was only getting data from PF5 (the last one).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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