Skip to main content
Visitor II
February 6, 2020
Question

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

  • February 6, 2020
  • 2 replies
  • 2539 views

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?

    This topic has been closed for replies.

    2 replies

    Visitor II
    May 28, 2021

    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.

    Visitor II
    December 20, 2022

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