cancel
Showing results for 
Search instead for 
Did you mean: 

ADC implementation with DMA in STM32G431 using LL_DMA API functions and Zephyr framework

AScen.1
Associate II

Dear All

I am working on STM32G431 trying to implement ADC using DMA in Zephyr framework.

Below is my code,

#include <zephyr.h>
 
#include <sys/printk.h>
 
#include <drivers/adc.h>
 
 
#include <stm32g4xx_ll_system.h>
 
#include <stm32g4xx_ll_dma.h>
 
#include <stm32g4xx_ll_adc.h>
 
 
// Rank and sequence definitions for STM32G4 from adc_stm32.c Zephyr driver
 
#define RANK(n)     LL_ADC_REG_RANK_##n
 
static const u32_t table_rank[] = {
 
    RANK(1),
 
    RANK(2),
 
    RANK(3),
 
    RANK(4),
 
    RANK(5),
 
    RANK(6),
 
    RANK(7),
 
    RANK(8),
 
    RANK(9),
 
    RANK(10),
 
    RANK(11),
 
    RANK(12),
 
    RANK(13),
 
    RANK(14),
 
    RANK(15),
 
    RANK(16),
 
};
 
 
#define SEQ_LEN(n)  LL_ADC_REG_SEQ_SCAN_ENABLE_##n##RANKS
 
static const u32_t table_seq_len[] = {
 
    LL_ADC_REG_SEQ_SCAN_DISABLE,
 
    SEQ_LEN(2),
 
    SEQ_LEN(3),
 
    SEQ_LEN(4),
 
    SEQ_LEN(5),
 
    SEQ_LEN(6),
 
    SEQ_LEN(7),
 
    SEQ_LEN(8),
 
    SEQ_LEN(9),
 
    SEQ_LEN(10),
 
    SEQ_LEN(11),
 
    SEQ_LEN(12),
 
    SEQ_LEN(13),
 
    SEQ_LEN(14),
 
    SEQ_LEN(15),
 
    SEQ_LEN(16),
 
};
 
 
#define NUM_ADC_1_CH    5
 
#define BUFFER_SIZE  2
 
static s16_t m_sample_buffer[BUFFER_SIZE];
 
 
#define ADC_FILTER_CONST 5          // filter multiplier = 1/(2^ADC_FILTER_CONST)
 
 
volatile uint16_t adc_readings[NUM_ADC_1_CH] = {};
 
static volatile uint32_t adc_filtered[NUM_ADC_1_CH] = {};
 
const uint32_t adc_1_sequence[] = {
 
    LL_ADC_CHANNEL_1,      
 
    LL_ADC_CHANNEL_2,      
 
    LL_ADC_CHANNEL_3,     
 
    LL_ADC_CHANNEL_4,      
 
    LL_ADC_CHANNEL_5,      
 
    LL_ADC_CHANNEL_6, 
 
    LL_ADC_CHANNEL_7, 
 
};
 
static void adc_setup()
{
    struct device *dev_adc = device_get_binding(DT_ADC_1_NAME);
 
    if (dev_adc == 0) {
        printk("ADC device not found\n");
        return;
    }
 
    struct adc_channel_cfg channel_cfg = {
 
        .gain = ADC_GAIN_1,
 
        .reference = ADC_REF_INTERNAL,
 
        .acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_TICKS, 248),
 
        .channel_id = LL_ADC_CHANNEL_0,
 
        .differential = 0
 
    };
 
    int ret = adc_channel_setup(dev_adc, &channel_cfg);
 
    if(ret){
 
        printk("channel setup error.. \n");
 
    }
 
 
    // enable internal reference voltage and temperature
 
    LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1),
 
        LL_ADC_PATH_INTERNAL_VREFINT | LL_ADC_PATH_INTERNAL_TEMPSENSOR);
 
    for (int i = 0; i < NUM_ADC_1_CH; i++) {
 
        LL_ADC_REG_SetSequencerRanks(ADC1, table_rank[i], adc_1_sequence[i]);
    }
 
    LL_ADC_REG_SetSequencerLength(ADC1, table_seq_len[NUM_ADC_1_CH]);
 
    LL_ADC_SetDataAlignment(ADC1, LL_ADC_DATA_ALIGN_LEFT);
 
    LL_ADC_SetResolution(ADC1, LL_ADC_RESOLUTION_12B);
 
    LL_ADC_REG_SetOverrun(ADC1, LL_ADC_REG_OVR_DATA_OVERWRITTEN);
 
    // Enable DMA transfer on ADC and circular mode
 
    LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
}
 
static inline void adc_trigger_conversion(struct k_timer *timer_id)
{
    LL_ADC_REG_StartConversion(ADC1);
}
 
 
 
void adc_update_value(unsigned int pos)
{
    // low pass filter with filter constant c = 1/(2^ADC_FILTER_CONST)
 
    // adc_readings: 12-bit ADC values left-aligned in uint16_t
    adc_filtered[pos] += (uint32_t)adc_readings[pos] - (adc_filtered[pos] >> ADC_FILTER_CONST);
    printk("ADC - Read ch %d: %X\n", pos, m_sample_buffer[0]);
}
 
static void DMA1_Channel1_IRQHandler(void *args)
{
    if ((DMA1->ISR & DMA_ISR_TCIF1) != 0) // Test if transfer completed on DMA channel 1
    {
        for (unsigned int i = 0; i < NUM_ADC_1_CH; i++) {
 
            adc_update_value(i);
 
            printk("In DMA ISR \n");
        }
    }
    DMA1->IFCR |= 0x0FFFFFFF;       // clear all interrupt registers
}
 
 
static void dma_setup()
{
    LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
    //LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMAMUX1);
 
    LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1,
 
        LL_ADC_DMA_GetRegAddr(ADC1, LL_ADC_DMA_REG_REGULAR_DATA),   // source address
 
        (uint32_t)(&(adc_readings[0])),     // destination address
 
        LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
 
    // Configure the number of DMA transfers (data length in multiples of size per transfer)
 
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, NUM_ADC_1_CH);
 
    LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);
 
    LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_HALFWORD);
 
    LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_HALFWORD);
 
    LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1);     // transfer error interrupt
 
    LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);     // transfer complete interrupt
 
    LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);
 
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
 
    // Configure NVIC for DMA (priority 2: second-lowest value for STM32L0/F0)
 
    IRQ_CONNECT(DMA1_Channel1_IRQn, 2, DMA1_Channel1_IRQHandler, 0, 0);
 
    irq_enable(DMA1_Channel1_IRQn);
 
    LL_ADC_REG_StartConversion(ADC1);
}
 
void main(void)
{
    printk("Hello World! %s\n", CONFIG_BOARD);
 
    static struct k_timer adc_trigger_timer;
    adc_setup();
    dma_setup();
 
    k_timer_init(&adc_trigger_timer, adc_trigger_conversion, NULL);
    k_timer_start(&adc_trigger_timer, K_MSEC(1), K_MSEC(1));        // 1 kHz
 
    k_sleep(500);      // wait for ADC to collect some measurement values
 
}

Both device_get_binding() and adc_channel_setup() for the ADC seems working since they return succeeded.

Also the adc_trigger_conversion() enters as scheduled by the k_timer. However, it does not enter the DMA1_Channel1_IRQHandler() for some reason. 

Any clues on additional configurations that I might have missed? Could anyone please help to fix it?

2 REPLIES 2
gjanet
Associate II

Hey there,

I see this post is quite old but since I went through making ADC and DMA work on a g474 on zephyr, i might as well anwer.

I used the HAL instead of the LL driver. first step was to configure in prj.conf :

CONFIG_USE_STM32_HAL_ADC=y

CONFIG_USE_STM32_HAL_ADC_EX=y

CONFIG_USE_STM32_HAL_DMA=y

CONFIG_USE_STM32_HAL_DMA_EX=y

CONFIG_USE_STM32_HAL_GPIO=y

CONFIG_USE_STM32_HAL_CORTEX=y

Those make the HAL drivers you need available (HAL_CORTEX is for interrupt managment).

I deactivated the adc in the devicetree since we are going to activate them manually.

Activating the adc in the devicetree made it so that I couldn't manually set an interrupt using IRQ_CONNECT.

Basically, you can generate an init code using CubeMX and use it in your zephyr code.

One mistake I made that was quite basic was that I initialized the ADC before the DMA. This caused the init to set DMA registers without a DMA clock activated (which does not work).

Once I initialized the DMA before the ADC, it all worked.

#include "adc.h"
 
ADC_HandleTypeDef hadc1;
 
DMA_HandleTypeDef hdma_adc1;
 
void dma1_channel1_interrupt(void) { HAL_DMA_IRQHandler(&hdma_adc1); }
 
void adc12_interrupt(void) { HAL_ADC_IRQHandler(&hadc1); }
uint16_t ADC1_DMA_data[11];
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
  led_toggle(); // for testing purposes
}
void adc_init(void) {
  __HAL_RCC_DMAMUX1_CLK_ENABLE();
  __HAL_RCC_DMA1_CLK_ENABLE();
 
  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  IRQ_CONNECT(DMA1_Channel1_IRQn, 0, dma1_channel1_interrupt, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  /* DMA1_Channel2_IRQn interrupt configuration */
 
  ADC_MultiModeTypeDef multimode = {0};
  ADC_ChannelConfTypeDef sConfig = {0};
 
  /* USER CODE BEGIN ADC1_Init 1 */
 
  /* USER CODE END ADC1_Init 1 */
  /** Common config
   */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.GainCompensation = 0;
  hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.NbrOfConversion = 11;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc1.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc1) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure the ADC multi-mode
   */
  multimode.Mode = ADC_MODE_INDEPENDENT;
  if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_6;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_92CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_7;
  sConfig.Rank = ADC_REGULAR_RANK_2;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_8;
  sConfig.Rank = ADC_REGULAR_RANK_3;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_9;
  sConfig.Rank = ADC_REGULAR_RANK_4;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_15;
  sConfig.Rank = ADC_REGULAR_RANK_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_6;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_2;
  sConfig.Rank = ADC_REGULAR_RANK_7;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_3;
  sConfig.Rank = ADC_REGULAR_RANK_8;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_4;
  sConfig.Rank = ADC_REGULAR_RANK_9;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_12;
  sConfig.Rank = ADC_REGULAR_RANK_10;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  /** Configure Regular Channel
   */
  sConfig.Channel = ADC_CHANNEL_11;
  sConfig.Rank = ADC_REGULAR_RANK_11;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    printk("messed up adc1 init\n");
  }
  printk("ADC1 init done\n");
}
 
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc) {
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if (hadc->Instance == ADC1) {
    /* USER CODE BEGIN ADC1_MspInit 0 */
 
    /* USER CODE END ADC1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_ADC12_CLK_ENABLE();
 
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**ADC1 GPIO Configuration
    PC0     ------> ADC1_IN6
    PC1     ------> ADC1_IN7
    PC2     ------> ADC1_IN8
    PC3     ------> ADC1_IN9
    PA0     ------> ADC1_IN1
    PA1     ------> ADC1_IN2
    PA2     ------> ADC1_IN3
    PA3     ------> ADC1_IN4
    PB0     ------> ADC1_IN15
    PB1     ------> ADC1_IN12
    PB12     ------> ADC1_IN11
    */
    GPIO_InitStruct.Pin = MEAS_A1_Pin | MEAS_B1_Pin | MEAS_C1_Pin | MEAS_D1_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
 
    GPIO_InitStruct.Pin = MEAS_A2_Pin | MEAS_B2_Pin | MEAS_C2_Pin | MEAS_D2_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
    GPIO_InitStruct.Pin = VREF1_Pin | VREF2_Pin | TEMP_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
    /* ADC1 DMA Init */
    /* ADC1 Init */
    hdma_adc1.Instance = DMA1_Channel1;
    hdma_adc1.Init.Request = DMA_REQUEST_ADC1;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc1.Init.Mode = DMA_NORMAL;
    hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) {
      printk("messed up DMA adc1 init\n");
    }
 
    __HAL_LINKDMA(hadc, DMA_Handle, hdma_adc1);
 
    /* ADC1 interrupt Init */
    IRQ_CONNECT(ADC1_2_IRQn, 0, adc12_interrupt, 0, 0);
    HAL_NVIC_DisableIRQ(ADC1_2_IRQn);
    /* USER CODE BEGIN ADC1_MspInit 1 */
 
    /* USER CODE END ADC1_MspInit 1 */
  }
}
 
void adc_start_dma(void) {
  HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
  if (HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADC1_DMA_data, 11) != HAL_OK) {
    printk("could not start DMA\n");
  }
}

Here is some code that works with one ADC using 11 channels.

you can then just call adc_init and adc_start_dma in main.

Hope this helps, if you need more detail, don't hesitate to ask.

DMira.11
Associate II

Thank you @gjanet​ this is working. But how to attach Sampling time to ADC using Timers??