2022-01-15 05:49 PM
I am running a scan mode of AD9 and AD10. I use a timer to generate an interrupt every 15ms....Inside this IRQ I start an ADC conversion scan.....After each conversion I store the value into an array. I have a potentiometer hooked up to adcValue[0] ...This is very stable...I have a battery (1.6V ) connected to adcValue[1] and it is bouncing all over the place. I do NOT want to use DMA. Can someone please look at my code and tell me what I am missing? The ADClock is 64MHz and the Timer is running at 32.768kHz.
Here is my code setting up the ADC, TIMER and IRQs:
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 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.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 2;
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_OVERWRITTEN;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_9;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_10;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
/**
* @brief LPTIM1 Initialization Function
* @param None
* @retval None
*/
static void MX_LPTIM1_Init(void)
{
/* USER CODE BEGIN LPTIM1_Init 0 */
/* USER CODE END LPTIM1_Init 0 */
/* USER CODE BEGIN LPTIM1_Init 1 */
/* USER CODE END LPTIM1_Init 1 */
hlptim1.Instance = LPTIM1;
hlptim1.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC;
hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV32;
hlptim1.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
hlptim1.Init.OutputPolarity = LPTIM_OUTPUTPOLARITY_HIGH;
hlptim1.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE;
hlptim1.Init.CounterSource = LPTIM_COUNTERSOURCE_INTERNAL;
hlptim1.Init.Input1Source = LPTIM_INPUT1SOURCE_GPIO;
hlptim1.Init.Input2Source = LPTIM_INPUT2SOURCE_GPIO;
if (HAL_LPTIM_Init(&hlptim1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN LPTIM1_Init 2 */
/* USER CODE END LPTIM1_Init 2 */
}
/**
* @brief LPTIM2 Initialization Function
* @param None
* @retval None
*/
static void MX_LPTIM2_Init(void)
{
/* USER CODE BEGIN LPTIM2_Init 0 */
/* USER CODE END LPTIM2_Init 0 */
/* USER CODE BEGIN LPTIM2_Init 1 */
/* USER CODE END LPTIM2_Init 1 */
hlptim2.Instance = LPTIM2;
hlptim2.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC;
hlptim2.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV32;
hlptim2.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
hlptim2.Init.OutputPolarity = LPTIM_OUTPUTPOLARITY_HIGH;
hlptim2.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE;
hlptim2.Init.CounterSource = LPTIM_COUNTERSOURCE_INTERNAL;
hlptim2.Init.Input1Source = LPTIM_INPUT1SOURCE_GPIO;
hlptim2.Init.Input2Source = LPTIM_INPUT2SOURCE_GPIO;
if (HAL_LPTIM_Init(&hlptim2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN LPTIM2_Init 2 */
/* USER CODE END LPTIM2_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, GPIO_PIN_SET);
/*Configure GPIO pin : PA2 */
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF0_LSCO;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PA8 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF0_MCO;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pin : PB1 */
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/*Configure GPIO pin : PE4 */
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI1_IRQn);
}
/* USER CODE BEGIN 4 */
void SystemClock_Decrease(void)
{
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
/* Select MSI as system clock source */
/* Note: Keep AHB and APB prescaler settings from previous structure initialization */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
/* Initialization Error */
Error_Handler();
}
/* Disable PLL to reduce power consumption since MSI is used from that point */
/* Change MSI frequency */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.MSIState = RCC_MSI_ON;
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_5;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_OFF;
if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
/* Initialization Error */
Error_Handler();
}
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
HAL_LPTIM_OnePulse_Start_IT(&hlptim1, 40, 0);
if (GPIO_Pin == GPIO_PIN_1)
EXTI->IMR1 &= ~EXTI_IMR1_IM1;
}
void HAL_LPTIM_AutoReloadMatchCallback(LPTIM_HandleTypeDef *hlptim)
{
if (hlptim == &hlptim1) //debounce timer
{
HAL_LPTIM_OnePulse_Stop_IT(&hlptim1);
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
{
EXTI->PR1 |= EXTI_PR1_PIF1; //clear any that came AFTER debounce
EXTI->IMR1 |= EXTI_IMR1_IM1; //enable / unmask
ISR.B_LockOut = T;
}
}
else
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, 0); //LED on
HAL_ADC_Start_IT(&hadc1);
hadc1.Instance->IER |= ADC_EOC_SINGLE_CONV;
}
}
/*
* A/D conversion complete callback
*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc == &hadc1)
{
adcValue[cnt] = HAL_ADC_GetValue(&hadc1);
cnt++;
if ((hadc1.Instance->ISR) & 8UL)
{
HAL_ADC_Stop_IT(&hadc1);
cnt = 0;
ISR.A_Conversion = T;
}
}
}
2022-01-16 10:03 AM
The conversion time is 260 clock cycles per channel, but HAL drivers are a broken bloatware and can easily waste such amounts of time. It's probably possible with sane code, but that would still be pretty fragile. When using ADC scanning on more than a single channel, DMA is almost compulsory.
2022-01-16 10:11 AM
Hello....
Thank you for your response....I just posted another question in the forum regarding using DMA as it seems that is the only way to go here....but I am having issues....Not sure how to link it to this post....
Here is the url....Could you please comment on this?
2022-01-21 05:07 AM
Hi @SWenn.1 ,
There is solution for your need of sequence conversion without using DMA.
The STM32WB55 embeds an ADC featuring groups regular and injected (all STM32 series have ADC group regular, but not all have group injected).
ADC group injected can have a sequence up to 4 ranks with each its own data register (therefore, 4 data registers).
For your application: you can convert the sequence of 2 channels, then read conversion data without timing constraint (data registers not overwritten at each conversion).
There is an example in STM32WB FW package:
Using LL driver:
...\Firmware\Projects\P-NUCLEO-WB55.Nucleo\Examples_LL\ADC\ADC_GroupsRegularInjected_Init
with customization:
replace ADC_INJ_InitStruct.SequencerLength = LL_ADC_INJ_SEQ_SCAN_DISABLE;
by ADC_INJ_InitStruct.SequencerLength = LL_ADC_INJ_SEQ_SCAN_ENABLE_2RANKS;
and add LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, LL_ADC_CHANNEL_<...>);
and use "LL_ADC_EnableIT_JEOS(ADC1)" to an interruption once the 2 channels are converted.
You can do the equivalent with HAL, main code lines:
ADC_InjectionConfTypeDef sConfigInjected = {0};
sConfigInjected.InjectedChannel = ADC_CHANNEL_<...>;
sConfigInjected.InjectedRank = ADC_INJECTED_RANK_1;
...
HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected)
ADC_InjectionConfTypeDef sConfigInjected = {0};
sConfigInjected.InjectedChannel = ADC_CHANNEL_<...>;
sConfigInjected.InjectedRank = ADC_INJECTED_RANK_2;
...
HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected)
HAL_ADCEx_InjectedStart_IT(...);
By the way, the most common way to perform sequence conversion is to use ADC group regular + DMA transfer.
Best regards
Philippe
2022-01-21 07:29 AM
Hi Phillipe....
Thank you for the response.....Since this post I am attempting dma and regular channel as you suggest, but I am not having any luck. I am using LPTimer2 as my 15ms timer (I don't have access to other timers as they are being used). Can you explain to me why even though I am getting into the ISRs I never see my array values change from 0?
Here is my LPTimer2 callback, which I am able to set a break point and see it is getting called:
else
{
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_4, 0); //LED on
hadc1.Instance->IER |= ADC_EOC_SEQ_CONV;
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcValue, AD_NUMBERSAMPLES);
}
Here is A/D callback which I also get into:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc == &hadc1)
{
HAL_ADC_Stop_DMA(&hadc1);
hadc1.Instance->IER &= ~ADC_EOC_SEQ_CONV;
hadc1.Instance->ISR |= ADC_EOC_SEQ_CONV;
ISR.A_Conversion = T;
}
}
A/D setup done by CubeMX looks like this:
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.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 3;
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_OVERWRITTEN;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_9;
sConfig.Rank = ADC_REGULAR_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();
}
/** Configure Regular Channel
*/
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_10;
sConfig.Rank = ADC_REGULAR_RANK_3;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
2022-01-21 07:50 AM
Hello,
If you go into DMA IRQ handler with data not transfered as expected, it may be due an issue in DMA configuration.
You can refer to this example in STM32WB FW package:
...\Firmware\Projects\P-NUCLEO-WB55.Nucleo\Examples\ADC\ADC_SingleConversion_TriggerTimer_DMA
with DMA configuration done in stm32wbxx_hal_msp.c.
ADC conversion trigger is TIM2, you can easily customize it to use LPTIM2.
In your code, if you are doing circular conversion, you should set:
hadc1.Init.DMAContinuousRequests = ENABLE;
(but this is not your issue since you do not see at least one value in your destination buffer).
Best regards
Philippe