How to change the sampling of internal ADC

cancel
Showing results for 
Search instead for 
Did you mean: 

How to change the sampling of internal ADC

217 Views
Contributor I

Hi everyone, 

I'm a little new to this processor so bare with me. I have used the MCUXpresso to generate an ADC example with interrupt for the LPCXpresso546/540xx, from there i added a PWM @500Hz and 50% duty cycle. I then save the ADC readings in an array and stop once the array is full. The ADC sampling rate however is too slow running at around 100 sps, from which i have verified by toggling an output at the interrupt service routine. I cant seem to figure out how and where to change the sampling rate of the ADC in this example. Its just a training exercise to get more familiar, but any help would be greatly appreciated. Code is below:

/*
* Copyright (c) 2016, Freescale Semiconductor, Inc.
* Copyright 2016-2019 NXP
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "board.h"
#include "fsl_adc.h"
#include "fsl_clock.h"
#include "fsl_power.h"
#include "fsl_ctimer.h"

#include "pin_mux.h"
#include <stdbool.h>
/*******************************************************************************
* Definitions
******************************************************************************/
#define DEMO_ADC_BASE ADC0
#define DEMO_ADC_SAMPLE_CHANNEL_NUMBER 2U
#define DEMO_ADC_IRQ_ID ADC0_SEQA_IRQn
#define DEMO_ADC_IRQ_HANDLER_FUNC ADC0_SEQA_IRQHandler
#define DEMO_ADC_CLOCK_DIVIDER 4U
static adc_result_info_t gAdcResultInfoStruct;
adc_result_info_t *volatile gAdcResultInfoPtr = &gAdcResultInfoStruct;
volatile bool gAdcConvSeqAIntFlag;
const uint32_t g_Adc_12bitFullRange = 4096U;

#define CTIMER CTIMER1 /* Timer 1 */
#define CTIMER_MAT_OUT kCTIMER_Match_1 /* Match output 1 */
#define CTIMER_CLK_FREQ CLOCK_GetFreq(kCLOCK_BusClk)

#define toggle_PORT 4U
#define toggle_PIN 4U

/*******************************************************************************
* Prototypes
******************************************************************************/
static void ADC_Configuration(void);

/*******************************************************************************
* Variables
******************************************************************************/
volatile uint32_t g_pwmPeriod = 0U;
volatile uint32_t g_pulsePeriod = 0U;
uint16_t array_Test[1500] = {0};
uint16_t array_Pointer = 0;

/*******************************************************************************
* Code
******************************************************************************/

status_t CTIMER_GetPwmPeriodValue(uint32_t pwmFreqHz, uint8_t dutyCyclePercent, uint32_t timerClock_Hz)
{
/* Calculate PWM period match value */
g_pwmPeriod = (timerClock_Hz / pwmFreqHz) - 1;

/* Calculate pulse width match value */
if (dutyCyclePercent == 0)
{
g_pulsePeriod = g_pwmPeriod + 1;
}
else
{
g_pulsePeriod = (g_pwmPeriod * (100 - dutyCyclePercent)) / 100;
}
return kStatus_Success;
}

static void ADC_ClockPower_Configuration(void)
{
/* SYSCON power. */
POWER_DisablePD(kPDRUNCFG_PD_VDDA); /* Power on VDDA. */
POWER_DisablePD(kPDRUNCFG_PD_ADC0); /* Power on the ADC converter. */
POWER_DisablePD(kPDRUNCFG_PD_VD2_ANA); /* Power on the analog power supply. */
POWER_DisablePD(kPDRUNCFG_PD_VREFP); /* Power on the reference voltage source. */
POWER_DisablePD(kPDRUNCFG_PD_TS); /* Power on the temperature sensor. */

CLOCK_EnableClock(kCLOCK_Adc0); /* SYSCON->AHBCLKCTRL[0] |= SYSCON_AHBCLKCTRL_ADC0_MASK; */
}

/*!
* @brief Main function
*/
int main(void)
{

ctimer_config_t config;
uint32_t srcClock_Hz;
uint32_t timerClock;

/* Initialize board hardware. */
/* attach 12 MHz clock to FLEXCOMM0 (debug console) */
CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);

/* Use 12 MHz clock for some of the Ctimers */
CLOCK_AttachClk(kFRO12M_to_ASYNC_APB);

BOARD_InitPins();
BOARD_BootClockPLL220M();
BOARD_InitDebugConsole();

/* Enable the power and clock for ADC. */
ADC_ClockPower_Configuration();
PRINTF("ADC interrupt example.\r\n");

/* Toggle pin intially set high. */
GPIO_PinWrite(GPIO, toggle_PORT, toggle_PIN, 1);

#if !(defined(FSL_FEATURE_ADC_HAS_NO_CALIB_FUNC) && FSL_FEATURE_ADC_HAS_NO_CALIB_FUNC)
uint32_t frequency = 500U;
/* Calibration after power up. */
#if defined(FSL_FEATURE_ADC_HAS_CALIB_REG) && FSL_FEATURE_ADC_HAS_CALIB_REG
DEMO_ADC_BASE->CTRL |= ADC_CTRL_BYPASSCAL_MASK;
frequency = CLOCK_GetFreq(kCLOCK_BusClk);
if (true == ADC_DoOffsetCalibration(DEMO_ADC_BASE, frequency))
#else
#if defined(SYSCON_ADCCLKDIV_DIV_MASK)
frequency = CLOCK_GetFreq(DEMO_ADC_CLOCK_SOURCE) / CLOCK_GetClkDivider(kCLOCK_DivAdcClk);
#else
frequency = CLOCK_GetFreq(DEMO_ADC_CLOCK_SOURCE);
#endif /* SYSCON_ADCCLKDIV_DIV_MASK */
if (true == ADC_DoSelfCalibration(DEMO_ADC_BASE, frequency))
#endif /* FSL_FEATURE_ADC_HAS_CALIB_REG */
{
PRINTF("ADC Calibration Done.\r\n");
PRINTF("Frequency = %d\r\n", frequency);
}
else
{
PRINTF("ADC Calibration Failed.\r\n");
}
#endif /* FSL_FEATURE_ADC_HAS_NO_CALIB_FUNC */

/* Configure the ADC as basic polling mode. */
ADC_Configuration();

/* Enable the interrupt. */
/* Enable the interrupt the for sequence A done. */
ADC_EnableInterrupts(DEMO_ADC_BASE, kADC_ConvSeqAInterruptEnable);
NVIC_EnableIRQ(DEMO_ADC_IRQ_ID);

PRINTF("Configuration Done.\r\n");

#if defined(FSL_FEATURE_ADC_HAS_CTRL_RESOL) & FSL_FEATURE_ADC_HAS_CTRL_RESOL
PRINTF("ADC Full Range: %d\r\n", g_Adc_12bitFullRange);
#endif /* FSL_FEATURE_ADC_HAS_CTRL_RESOL */

/* CTimer0 counter uses the AHB clock, some CTimer1 modules use the Aysnc clock */
srcClock_Hz = CTIMER_CLK_FREQ;

PRINTF("CTimer example to generate a PWM signal\r\n");

CTIMER_GetDefaultConfig(&config);
timerClock = srcClock_Hz / (config.prescale + 1);

CTIMER_Init(CTIMER, &config);

/* Get the PWM period match value and pulse width match value of 500Hz PWM signal with 50% dutycycle */
CTIMER_GetPwmPeriodValue(500, 50, timerClock);
CTIMER_SetupPwmPeriod(CTIMER, CTIMER_MAT_OUT, g_pwmPeriod, g_pulsePeriod, false);
CTIMER_StartTimer(CTIMER);


while (1)
{
//GETCHAR();
gAdcConvSeqAIntFlag = false;
ADC_DoSoftwareTriggerConvSeqA(DEMO_ADC_BASE);

while (!gAdcConvSeqAIntFlag)
{
}
PRINTF("gAdcResultInfoStruct.result = %d\r\n", gAdcResultInfoStruct.result);
PRINTF("gAdcResultInfoStruct.channelNumber = %d\r\n", gAdcResultInfoStruct.channelNumber);
PRINTF("gAdcResultInfoStruct.overrunFlag = %d\r\n", gAdcResultInfoStruct.overrunFlag ? 1U : 0U);
PRINTF("\r\n");

if(array_Pointer < 1500)
{
array_Test[array_Pointer] = gAdcResultInfoStruct.result;
array_Pointer++;
}
else
{
while(1);
}
}
}

/*
* ISR for ADC conversion sequence A done.
*/
void DEMO_ADC_IRQ_HANDLER_FUNC(void)
{
if (kADC_ConvSeqAInterruptFlag == (kADC_ConvSeqAInterruptFlag & ADC_GetStatusFlags(DEMO_ADC_BASE)))
{
ADC_GetChannelConversionResult(DEMO_ADC_BASE, DEMO_ADC_SAMPLE_CHANNEL_NUMBER, gAdcResultInfoPtr);
ADC_ClearStatusFlags(DEMO_ADC_BASE, kADC_ConvSeqAInterruptFlag);
gAdcConvSeqAIntFlag = true;
GPIO_PortToggle(GPIO, toggle_PORT, 1u << toggle_PIN);
}
/* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F Store immediate overlapping
exception return operation might vector to incorrect interrupt */
#if defined __CORTEX_M && (__CORTEX_M == 4U)
__DSB();
#endif
}

/*
* Configure the ADC as normal converter in polling mode.
*/
void ADC_Configuration(void)
{
adc_config_t adcConfigStruct;
adc_conv_seq_config_t adcConvSeqConfigStruct;

/* Configure the converter. */
#if defined(FSL_FEATURE_ADC_HAS_CTRL_ASYNMODE) & FSL_FEATURE_ADC_HAS_CTRL_ASYNMODE
adcConfigStruct.clockMode = kADC_ClockSynchronousMode; /* Using sync clock source. */
#endif /* FSL_FEATURE_ADC_HAS_CTRL_ASYNMODE */
adcConfigStruct.clockDividerNumber = DEMO_ADC_CLOCK_DIVIDER;
#if defined(FSL_FEATURE_ADC_HAS_CTRL_RESOL) & FSL_FEATURE_ADC_HAS_CTRL_RESOL
adcConfigStruct.resolution = kADC_Resolution12bit;
#endif /* FSL_FEATURE_ADC_HAS_CTRL_RESOL */
#if defined(FSL_FEATURE_ADC_HAS_CTRL_BYPASSCAL) & FSL_FEATURE_ADC_HAS_CTRL_BYPASSCAL
adcConfigStruct.enableBypassCalibration = false;
#endif /* FSL_FEATURE_ADC_HAS_CTRL_BYPASSCAL */
#if defined(FSL_FEATURE_ADC_HAS_CTRL_TSAMP) & FSL_FEATURE_ADC_HAS_CTRL_TSAMP
adcConfigStruct.sampleTimeNumber = 0U;
#endif /* FSL_FEATURE_ADC_HAS_CTRL_TSAMP */
#if defined(FSL_FEATURE_ADC_HAS_CTRL_LPWRMODE) & FSL_FEATURE_ADC_HAS_CTRL_LPWRMODE
adcConfigStruct.enableLowPowerMode = false;
#endif /* FSL_FEATURE_ADC_HAS_CTRL_LPWRMODE */
#if defined(FSL_FEATURE_ADC_HAS_TRIM_REG) & FSL_FEATURE_ADC_HAS_TRIM_REG
adcConfigStruct.voltageRange = kADC_HighVoltageRange;
#endif /* FSL_FEATURE_ADC_HAS_TRIM_REG */
ADC_Init(DEMO_ADC_BASE, &adcConfigStruct);

#if !(defined(FSL_FEATURE_ADC_HAS_NO_INSEL) && FSL_FEATURE_ADC_HAS_NO_INSEL)
/* Use the temperature sensor input to channel 0. */
ADC_EnableTemperatureSensor(DEMO_ADC_BASE, true);
#endif /* FSL_FEATURE_ADC_HAS_NO_INSEL. */

/* Enable channel DEMO_ADC_SAMPLE_CHANNEL_NUMBER's conversion in Sequence A. */
adcConvSeqConfigStruct.channelMask =
(1U << DEMO_ADC_SAMPLE_CHANNEL_NUMBER); /* Includes channel DEMO_ADC_SAMPLE_CHANNEL_NUMBER. */
adcConvSeqConfigStruct.triggerMask = 0U;
adcConvSeqConfigStruct.triggerPolarity = kADC_TriggerPolarityPositiveEdge;
adcConvSeqConfigStruct.enableSingleStep = false;
adcConvSeqConfigStruct.enableSyncBypass = false;
adcConvSeqConfigStruct.interruptMode = kADC_InterruptForEachSequence;
ADC_SetConvSeqAConfig(DEMO_ADC_BASE, &adcConvSeqConfigStruct);
ADC_EnableConvSeqA(DEMO_ADC_BASE, true); /* Enable the conversion sequence A. */
/* Clear the result register. */
ADC_DoSoftwareTriggerConvSeqA(DEMO_ADC_BASE);
while (!ADC_GetChannelConversionResult(DEMO_ADC_BASE, DEMO_ADC_SAMPLE_CHANNEL_NUMBER, &gAdcResultInfoStruct))
{
}
ADC_GetConvSeqAGlobalConversionResult(DEMO_ADC_BASE, &gAdcResultInfoStruct);
}

0 Kudos
3 Replies

69 Views
NXP Employee
NXP Employee

Hello Blair,

The sample rate will be determined by how and when you are doing a conversion in additon to the frequency that the ADC module is running at and the sampling time. In your case the sampling time is at its shortest which is good and I can help you double check the ADC frequency if you can provide the MCU that you are using in the development board and if you are basing your initial code on an example so that I can run it from my end.

In addition there are some common practices that can help you improve the conversion rate. 

1. If you are inclined in using the interruption, try to keep the interrupt handler as short as possible. Generally flags that enable or disable other functions.

2. PRINTFs will cause delays in execution as well as they are meant for debugging purposes. So if possible I recommend to keep all the information within one printf.

Another recommendation would be to consider restructuring when the conversion is made. For example if you do the conversion within the while loop of your main and with the conversion completion flag  you can enable the counter in an external function, you will have many more samples ready than how it is being done at the moment.

Please let me know if this helps, or if you have further questions.

Best Regards,

Sabina

0 Kudos

69 Views
Contributor I

Hi Sabrina, Thanks for your response! The MCU I am using is the LPC54628J512. 

The code is a meddly of the ADC interrupt example and PWM example from the MCUXpresso Config Tools application. This was very helpful, and I was able to control the conversion rate in the end by using a timer callback to trigger every time I wanted to sample the PWM signal. However I still could use some more information on how to manually set the sample rate of the internal ADC outside of using timers like I have done now, is there any way to do this? Ideally i would like to sample the PWM signal at 5000 sps, 10 times that of the 500Hz PWM signal at 50% duty cycle.

Kind regards,

Blair 

0 Kudos

69 Views
NXP Employee
NXP Employee

Hi, 

I think you need to use the hardware triggering from a timer. There is possible to do some adjustment of  sampling using clock and other settings, but this would be probably too limited and hard to tune.

Do you need the sampling to be exactly synchronized with the generated PWM or is it enough to achieve the sample rate of multiply of the PWM freq as you mentioned ? 

Are you using the config tools ? With using the MCUXPresso Config tools, it's to possible configure the ADC initialization and triggering  in a GUI. 

pastedImage_1.png

best regards

Petr Hradsky

0 Kudos