cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L4 ADC + DMA Multiple Channels (> 6 Channels) not working

Hailing SKY
Associate II

Dear All,

I am using the ADC + DMA function in STM32L476 to record data from multiple sensing inputs (12 channels in total). I have succeeded to record 4 channels using ADC1_IN1, IN6, IN7 and IN8, but when I increased the channel number to 6, the results are wrong.

The CH1 data were overwritten by the data from CH6, and there was nothing for the location where data from CH6 should be.

Here are my codes (partial). Please let me know your ideas.

The attached code is for NUCLEO - L476RG board.

#define BUF_SIZE (4000) // data length for each channel
#define CHANNEL_COUNT (4) // change to 6 in the 6-channel case
 
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
 
/* Private variables ---------------------------------------------------------*/
uint8_t datastore1[BUF_SIZE]={0};
uint8_t datastore2[BUF_SIZE]={0};
uint8_t datastore3[BUF_SIZE]={0};
uint8_t datastore4[BUF_SIZE]={0};
// uint8_t datastore5[BUF_SIZE]={0};
// uint8_t datastore6[BUF_SIZE]={0};
uint8_t adcValue[CHANNEL_COUNT*BUF_SIZE]={0};
uint8_t indi_led = 0;
uint8_t indi_int = 0;
 
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
 
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_ADC1_Init(void);
 
int main(void)
{
 
 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
 HAL_Init();
 SystemClock_Config();
 
 /* Initialize all configured peripherals */
 MX_GPIO_Init();
 MX_DMA_Init();
 // MX_USART1_UART_Init();
 MX_ADC1_Init();
 
 /* Infinite loop */
 /* USER CODE BEGIN WHILE */
 while (1)
 { // if interruptted, indi_int = 1
 if (indi_int == 1)
 {
 // HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); // PE7 interrupt
 // HAL_Delay(3000);
 indi_int = 0;
 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET);
 HAL_ADC_Start_DMA(&hadc1,(uint32_t*)adcValue,BUF_SIZE*CHANNEL_COUNT); // start conversion
 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_SET);
 }
 
 if (indi_led == 1)
 {
 HAL_UART_Transmit(&huart1,(uint8_t*)datastore1,BUF_SIZE,3000);
 HAL_UART_Transmit(&huart1,(uint8_t*)datastore2,BUF_SIZE,3000);
  HAL_UART_Transmit(&huart1,(uint8_t*)datastore3,BUF_SIZE,3000);
  indi_led = 0;
  HAL_UART_Transmit(&huart1,(uint8_t*)datastore4,BUF_SIZE,3000);
  // HAL_UART_Transmit(&huart1,(uint8_t*)datastore5,BUF_SIZE,3000);
  // HAL_UART_Transmit(&huart1,(uint8_t*)datastore6,BUF_SIZE,3000);
 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_9, GPIO_PIN_RESET);	
 HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_RESET);
 indi_int = 0;
 HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
 }
 }
}
 
// transfer data from adcValue to the varibles for each channel
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
	uint16_t tem_index=0;
	uint16_t data_num=0;
	for(tem_index=0;tem_index<BUF_SIZE/2;tem_index++)
	{
 //HAL_GPIO_WritePin(GPIOE, GPIO_PIN_8, GPIO_PIN_SET);
 data_num=tem_index*4; // 6 for 6-channel
 datastore1[tem_index]=adcValue[data_num];
 datastore2[tem_index]=adcValue[data_num+1];
 datastore3[tem_index]=adcValue[data_num+2];
 datastore4[tem_index]=adcValue[data_num+3];
 //datastore5[tem_index]=adcValue[data_num+4];
 //datastore6[tem_index]=adcValue[data_num+5];
	}
}
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	uint16_t tem_index=BUF_SIZE/2;
	uint16_t data_num=0;
	for(tem_index=BUF_SIZE/2;tem_index<BUF_SIZE;tem_index++)
	{
 data_num=tem_index*4; // 6 for 6-channel
 datastore1[tem_index]=adcValue[data_num];
 datastore2[tem_index]=adcValue[data_num+1];
 datastore3[tem_index]=adcValue[data_num+2];
 datastore4[tem_index]=adcValue[data_num+3];
 //datastore5[tem_index]=adcValue[data_num+4];
 //datastore6[tem_index]=adcValue[data_num+5];
 //HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); 
 HAL_ADC_Stop_DMA(&hadc1);
 indi_led = 1;
	}
}
 
/* ADC1 init function */
static void MX_ADC1_Init(void)
{
 ADC_MultiModeTypeDef multimode;
 ADC_ChannelConfTypeDef sConfig;
 
  /**Common config 
  */
 hadc1.Instance = ADC1;
 hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
 hadc1.Init.Resolution = ADC_RESOLUTION_8B;
 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
 hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
 hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
 hadc1.Init.LowPowerAutoWait = DISABLE;
 hadc1.Init.ContinuousConvMode = ENABLE;
 hadc1.Init.NbrOfConversion = 4; // change to 6 for the 6-channel case
 hadc1.Init.DiscontinuousConvMode = DISABLE;
 hadc1.Init.NbrOfDiscConversion = 1;
 hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
 hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
 hadc1.Init.DMAContinuousRequests = ENABLE;
 hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
 hadc1.Init.OversamplingMode = DISABLE;
 
 if (HAL_ADC_Init(&hadc1) != HAL_OK)
 {
  _Error_Handler(__FILE__, __LINE__);
 }
 
  /**Configure the ADC multi-mode 
  */
 multimode.Mode = ADC_MODE_INDEPENDENT;
 if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
 {
  _Error_Handler(__FILE__, __LINE__);
 }
 
  /**Configure Regular Channel 
  */
 sConfig.Channel = ADC_CHANNEL_1;
 sConfig.Rank = 1;
 sConfig.SamplingTime = ADC_SAMPLETIME_24CYCLES_5;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
  _Error_Handler(__FILE__, __LINE__);
 }
 
  /**Configure Regular Channel 
  */
 sConfig.Channel = ADC_CHANNEL_6;
 sConfig.Rank = 2;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
  _Error_Handler(__FILE__, __LINE__);
 }
 
  /**Configure Regular Channel 
  */
 sConfig.Channel = ADC_CHANNEL_7;
 sConfig.Rank = 3;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
  _Error_Handler(__FILE__, __LINE__);
 }
 
   /**Configure Regular Channel 
  */
 sConfig.Channel = ADC_CHANNEL_8;
 sConfig.Rank = 4;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
  _Error_Handler(__FILE__, __LINE__);
 }
 
  /**Configure Regular Channel 
  */
 //sConfig.Channel = ADC_CHANNEL_9;
 //sConfig.Rank = 5;
// if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 //{
 // _Error_Handler(__FILE__, __LINE__);
 //}
 
  /**Configure Regular Channel 
  */
// sConfig.Channel = ADC_CHANNEL_10;
// sConfig.Rank = 6;
// if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
// {
 // _Error_Handler(__FILE__, __LINE__);
 //}
}

32 REPLIES 32
T J
Lead

After the Cube has run initialisation;

I use this to initialise the ADC once...

    ADC_RowCounter = 0;
    Ave_ADC_ChannelCounter = 0;
    ADC_ChannelsCounter = ADC_ChannelCount;  		// 5  ADC channels
    HAL_ADC_Start_DMA(&hadc, (uint32_t *)ADC_DMABuffer, ADC_ChannelCount); 
    

T J
Lead

and this is the interrupt routine, only this one, no HALF callback ??

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {

   ADC_ChannelsCounter = ADC_ChannelCount;

   HAL_ADC_CompleteCycle = true;        // signal foreground to process

   // copy 15 results away now

   if(ADC_RowCounter >= ADC_RowCount) ADC_RowCounter = 0;      

   for (int i = 0; i < ADC_ChannelCount; i++) {

       //here add 2nd dimension to the array

       HAL_ADC_Channel_Buffer[ADC_RowCounter][i] = ADC_DMABuffer[i];   // AVEColumnSum += HAL_ADC_Channel_Buffer [Ave_ADC_RowCounter][Ave_ADC_ChannelCounter];

   }

   ADC_RowCounter++;                       // for next time through the loop

   if(ADC_RowCounter >= ADC_RowCount) ADC_RowCounter = 0;      

}

T J
Lead

In the foreground process ( or worker thread)

    if (HAL_ADC_CompleteCycle) {
        HAL_ADC_CompleteCycle = false;
        Ave_next_ADC_Column();
    if(ReCalADCrequested) {
            ReCalADCrequested = false;
        
            /*
            //	Prior to calibrate, we must stop the DMA conversion cycle which is endless.
            //	in "end of sequence" check flag ReCalADCrequested <- we are here now
            //	stop the ADC/DMA cycle and wait for it to stop. <- we will do this, and flag the foreground to check when the DMA stops.
            //	Now in the foreground
            //	Now Calibrate ADC as described below;
            //	Renable ADC/DMA Cycle
            */
         	
 
//            printf(string + length,"Stopping DMA%c[0K",0x1B);   	// 
 
        
            HAL_ADC_Stop_DMA(&hadc);         // stop is instant
            ADC_DMA_Wait = true;         	// waiting to recalibrate the ADC due to variant PCB temperature affecting ADC accuracy
            ADChasBeenRecalibrated = false;
    
        }			
    }

T J
Lead

Just checking if the ADC Recal is completed.

in thew worker thread(foreground process) 
 
            if(ADC_DMA_Wait) {  // ready for calibration
                ADC_DMA_Wait = false;
                process_ADCcalibration();  
            }        
            
 
 
void process_ADCcalibration(void) {
	
    //printf(string,"ADC Recal is Progressing\n\r");
// check DMA and ADC have stopped;
/*
		Calibration software procedure
		1. Ensure that ADEN=0
		2. Set ADCAL=1
		3. Wait until ADCAL=0
		4. The calibration factor can be read from bits 6:0 of ADC_DR.
				ADC Calibration code example
				// (1) Ensure that ADEN = 0 
				// (2) Clear ADEN 
				// (3) Launch the calibration by setting ADCAL
				// (4) Wait until ADCAL=0
*/
	if((ADC1->CR & ADC_CR_ADEN) != 0)				  // (1) 
    {
        ADC1->CR &= (uint32_t)(~ADC_CR_ADEN);    // (2)
    }
    ADC1->CR |= ADC_CR_ADCAL;    							// (3)
    while((ADC1->CR & ADC_CR_ADCAL) != 0) // (4)
    {
        checkBackgroundServices(); // checking DMAs housework...
        // For robust implementation, add here time-out management 
    }
    ADC_CalibrationFactor = (ADC1->DR);   	//& 0x7F);
    ADChasBeenRecalibrated = true;
    
    
//    printf(string, "ADC Recalibration Completed, Calfactor is %02X, restarting DMA ", ADC_CalibrationFactor);  	// 
    
    // restart ADC DMA
    HAL_ADC_Start_DMA(&hadc, (uint32_t *)ADC_DMABuffer, ADC_ChannelCount);     //from main, adc-start
 
//    printf(string, "restarting DMA ");
		
}    

T J
Lead

and this is the final piece, the Averager :

    uint32_t AVEColumnSum = 0;
 
// sum all channels over 16 ROWS.
    for (int Ave_ADC_RowCounter = 0; Ave_ADC_RowCounter < ADC_RowCount; Ave_ADC_RowCounter++)
        AVEColumnSum += HAL_ADC_Channel_Buffer[Ave_ADC_RowCounter][Ave_ADC_ChannelCounter];
 
 
    ADC_Channel_AVE[Ave_ADC_ChannelCounter] = AVEColumnSum;    // we 'add'  an extra bit of resolution here.....  we should divide by 16, since its a sum of 16 values
 
    Ave_ADC_ChannelCounter++;   											// for next time, its the next column
    	if(Ave_ADC_ChannelCounter >= ADC_ChannelCount) { 
        Ave_ADC_ChannelCounter = 0;    	            // for next time, its the next column
        //AllADC_AveragesUpdatedFlag = true;   		// All averages updated
    }
}

Hailing SKY
Associate II

Thanks, @Community member​, I will try your suggestions. It seems like you are using discontinuous scan mode, and using a Timer as an external trigger for the ADC+DMA conversion.

In my case, I am trying to use the continuous scan mode. I guess that is the reason the "init" is different.

By the way, can you show me your DMA settings as well if possible?

Thanks,

Hailing

T J
Lead

Sure, here is the Cube set up ...

is this what you wanted ? its from the cube...

/** 
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void) 
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();
 
  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  /* DMA1_Channel4_5_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel4_5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_5_IRQn);
 
}

Hailing SKY
Associate II

@Community member​ Thanks. I still cannot locate the issues for the 6-channel case. I will try to use timer to control the data transfer. Thanks anyway.

T J
Lead

Are you using the Cube ?

Hailing SKY
Associate II

I am quite confused why Ch 1 (first channel, ranking =1) can be overwritten by Ch 6 (last channel, ranking = 6).