2020-02-06 03:18 AM
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?
2021-05-28 02:29 AM
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.
2022-12-20 07:10 AM
Thank you @gjanet this is working. But how to attach Sampling time to ADC using Timers??