KL26Z PIT, ADC, Two Channel DMA Ping-Pong Buffer

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

KL26Z PIT, ADC, Two Channel DMA Ping-Pong Buffer

Jump to solution
1,516 Views
g_lowes2
Contributor II

Hi,

I am using the FRDM-KL26Z development board with MCUXpresso IDE to sample a signal using the ADC triggered via PIT. I plan to use two DMA buffers in a ping-pong arrangement to collect ADC samples. The idea is to process data in Buffer A whilst Buffer B is being filled in a continuous ping-pong fashion. The problem I am having is linking the two buffers to automatically start and stop transferring data from the ADC. The only way I have achieved this is to use the DMA Callback function to manually Stop and Start transfer which is not ideal because I think this should happen in hardware using the DMA_SetChannelLinkConfig function.

Whilst trying the DMA_SetChannelLinkConfig function to establish a link, Buffer A fills as normal being the first buffer initiated and a link is indeed created to Buffer B but only the last sample taken from Buffer A is placed into the array and then nothing more. I believe the problem is down to the Buffer B not having ERQ bit set in the DMA_DCR register but the only other way I can see of setting this is manually via the DMA Callback function. I am aiming for a full hardware routine with minimal interrupt intervention to maintain a low power operation.

I am fairly new to this platform so I hope this makes sense and I appreciate any help.

Gavin

1 Solution
1,235 Views
Hui_Ma
NXP TechSupport
NXP TechSupport

Hi,

For the ADC as the only hardware trigger source for both DMA channels, each ADC conversion done will trigger different DMA channel to transfer result to different buffer. That was my understanding of "ping-pong".

While, from your code, you are using DMA channel link and will link to another DMA channel after 10 times ADC conversion result transfer to the same buffer. I don't think you are using "ping-pong" mode.

Buffer 0  <-------1----------ADC result -----------1----------> Buffer 1                          ping-pong mode

Buffer 0 <--------10--------ADC result ------------10--------> Buffer 1                          not ping-pong mode

I don't know if you understand my above description.            

I had a discussion with Kinetis L family product engineer, there doesn't need to use two DMA channels for this application.

Customer just need to use one DMA channel, after each DMA transfer, re-config the DMA channel destination address to another buffer at DMA callback function.

Or, customer can use two DMA channels, one channel was triggered by ADC module, the other channel be software trigger and link to previous channel to transfer buffer address to previous DMA channel destination address register.

Wish it helps.


Have a great day,
Mike

-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

View solution in original post

0 Kudos
3 Replies
1,235 Views
Hui_Ma
NXP TechSupport
NXP TechSupport

Hi,

There is an application note AN4590 for ADC scan mode at Kinteis K family product.

Customer also could refer to apply at KL26 product.

Please check here to download AN4590 application note.

Wish it helps.


Have a great day,
Mike

-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

1,235 Views
g_lowes2
Contributor II

Hi Mike,

Thanks for your fast reply. Unfortunately the application notes you suggested are for the K series which have enhanced eDMA functionality which I believe my L series does not have, only basic DMA. The only option I have in terms of channel link are:

00 No channel-to-channel linking
01 Perform a link to channel LCH1 after each cycle-steal transfer followed by a link to LCH2 after the BCR decrements to 0.
10 Perform a link to channel LCH1 after each cycle-steal transfer
11 Perform a link to channel LCH1 after the BCR decrements to 0.

At present I have selected the final option which is fine in terms of linking to Buffer B (LCH1 in this case) after Buffer A has exhausted, the only problem being that Buffer B is not active (ERQ bit set to 0).

I have attached my code which works as a ping-pong buffer but i believe that i should not need to intervene in the DMA Callback functions to start and stop buffers, this should be carried out in hardware via the channel link?

Gavin

/*CODE NOTES*******************************************************************
*
* Ping-Pong buffer to sample ADC0 pin 8 using PIT set by user. The size of @
* Buffer 0 and 1 are set manually within code. When red light on board is
* active Buffer 0 is active, when green light is active Buffer 1 is active.
*
******************************************************************************/

/*******************************************************************************
* ADC_DMA_PIT
*******************************************************************************/
// Standard
#include <stdio.h>
#include <stdlib.h>
// Board
#include "board.h"
#include "clock_config.h"
#include "peripherals.h"
#include "pin_mux.h"
// CMSIS
#include "MKL26Z4.h"
// Utilities
#include "fsl_debug_console.h"
// Drivers
#include "fsl_adc16.h"
#include "fsl_dmamux.h"
#include "fsl_dma.h"
#include "fsl_pit.h"
#include "fsl_clock.h"
#include "fsl_gpio.h"
#include "fsl_port.h"

/*******************************************************************************
* Definitions
*******************************************************************************/

#define ADC16_SAMPLE_COUNT 10U // Buffer size
#define PIT_TIMER_IN_MICROSECONDS 500000U // Sample time

#define ADC_BASE_ADDRESS ADC0
#define ADC_CH_GROUP 0U
#define ADC_CH_NUM 8U
#define ADC_RESULTS_REGISTER (uint32_t)(&ADC0->R[0])

#define PIT_SOURCE_CLOCK CLOCK_GetFreq(kCLOCK_BusClk)

#define DMAMUX_BASE_ADDRESS DMAMUX0
#define DMA_CHANNEL_0 0U
#define DMA_ADC_SOURCE kDmaRequestMux0ADC0
#define DMA_BASE_ADDRESS DMA0

#define ADC_CH_GROUP_2 1U

#define DMA_CHANNEL_1 1U

#define BOARD_REDLED_GPIO BOARD_LED_RED_GPIO
#define BOARD_REDLED_GPIO_PIN BOARD_LED_RED_GPIO_PIN

#define BOARD_GREENLED_GPIO BOARD_LED_GREEN_GPIO
#define BOARD_GREENLED_GPIO_PIN BOARD_LED_GREEN_GPIO_PIN

/*******************************************************************************
* Prototypes
*******************************************************************************/

void BOARDS_ConfigTriggerSource(void);
static void ADC_Configuration(void);
static void periodicIntTimer(void);
static void DMA_Configuration(void);

/*******************************************************************************
* Variables
*******************************************************************************/

adc16_config_t adcUserConfig;
adc16_channel_config_t adcChnConfig;

pit_config_t pitConfig;

dma_handle_t g_DMA_handle_0;
dma_transfer_config_t g_transferConfig_0;

dma_handle_t g_DMA_handle_1;
dma_transfer_config_t g_transferConfig_1;

dma_channel_link_config_t g_dmaChLink_0;
dma_channel_link_config_t g_dmaChLink_1;

static uint32_t g_adc16SampleDataArray_0[ADC16_SAMPLE_COUNT];
static uint32_t g_adc16SampleDataArray_1[ADC16_SAMPLE_COUNT];

volatile bool g_Transfer_Done_0 = false;
volatile bool g_Transfer_Done_1 = false;

gpio_pin_config_t red_led_config;
gpio_pin_config_t green_led_config;

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

void DMA_Call_0(dma_handle_t *handle, void *param)
{
g_Transfer_Done_1 = false;
g_Transfer_Done_0 = true;
DMA_StopTransfer(&g_DMA_handle_0);
DMA_SubmitTransfer(&g_DMA_handle_1, &g_transferConfig_1, kDMA_EnableInterrupt);
DMA_StartTransfer(&g_DMA_handle_1);
}

void DMA_Call_1(dma_handle_t *handle, void *param)
{
g_Transfer_Done_0 = false;
g_Transfer_Done_1 = true;
DMA_StopTransfer(&g_DMA_handle_1);
DMA_SubmitTransfer(&g_DMA_handle_0, &g_transferConfig_0, kDMA_EnableInterrupt);
DMA_StartTransfer(&g_DMA_handle_0);
}

void BOARDS_ConfigTriggerSource(void)
{
/* ADC hardware trigger configured to use PIT trigger 0*/
SIM->SOPT7 |= SIM_SOPT7_ADC0TRGSEL(4) | SIM_SOPT7_ADC0ALTTRGEN(1);
}

static void ADC_Configuration(void)
{
ADC16_GetDefaultConfig(&adcUserConfig);
// adcUserConfig.referenceVoltageSource = kADC16_ReferenceVoltageSourceVref;
// adcUserConfig.clockSource = kADC16_ClockSourceAsynchronousClock;
// adcUserConfig.enableAsynchronousClock = true;
// adcUserConfig.clockDivider = kADC16_ClockDivider8;
// adcUserConfig.resolution = kADC16_ResolutionSE12Bit;
// adcUserConfig.longSampleMode = kADC16_LongSampleDisabled;
// adcUserConfig.enableHighSpeed = false;
// adcUserConfig.enableLowPower = false;
// adcUserConfig.enableContinuousConversion = false;

ADC16_Init(ADC_BASE_ADDRESS, &adcUserConfig);

ADC16_DoAutoCalibration(ADC_BASE_ADDRESS);
if (kStatus_Success == ADC16_DoAutoCalibration(ADC_BASE_ADDRESS))
{
PRINTF("ADC CALIBRATION COMPLETE\n");
}else
{
PRINTF("ADC CALIBRATION FAILED\n");
}

adcChnConfig.channelNumber = ADC_CH_NUM;
adcChnConfig.enableDifferentialConversion = false;
adcChnConfig.enableInterruptOnConversionCompleted = false;
ADC16_SetChannelConfig(ADC_BASE_ADDRESS, ADC_CH_GROUP, &adcChnConfig);

ADC16_EnableHardwareTrigger(ADC_BASE_ADDRESS, true);

ADC16_EnableDMA(ADC_BASE_ADDRESS, true);
}

static void periodicIntTimer(void)
{
PIT_GetDefaultConfig(&pitConfig);
PIT_Init(PIT, &pitConfig);
PIT_SetTimerPeriod(PIT, kPIT_Chnl_0, USEC_TO_COUNT(PIT_TIMER_IN_MICROSECONDS, PIT_SOURCE_CLOCK));
PIT_DisableInterrupts(PIT, kPIT_Chnl_0, kPIT_TimerInterruptEnable);
PIT_StartTimer(PIT, kPIT_Chnl_0);
}

static void DMA_Configuration(void)
{
DMAMUX_Init(DMAMUX_BASE_ADDRESS);

/* DMA MUX config for channel 0 */
DMAMUX_SetSource(DMAMUX_BASE_ADDRESS, DMA_CHANNEL_0, DMA_ADC_SOURCE);
DMAMUX_EnableChannel(DMAMUX_BASE_ADDRESS, DMA_CHANNEL_0);

/* DMA MUX config for channel 1 */
DMAMUX_SetSource(DMAMUX_BASE_ADDRESS, DMA_CHANNEL_1, DMA_ADC_SOURCE);
DMAMUX_EnableChannel(DMAMUX_BASE_ADDRESS, DMA_CHANNEL_1);

DMA_Init(DMA_BASE_ADDRESS);

/* DMA Channel 0 Config */
DMA_CreateHandle(&g_DMA_handle_0, DMA_BASE_ADDRESS, DMA_CHANNEL_0);
DMA_SetCallback(&g_DMA_handle_0, DMA_Call_0, NULL);
DMA_PrepareTransfer(&g_transferConfig_0, (void *)ADC_RESULTS_REGISTER,
sizeof(uint32_t), (void *)g_adc16SampleDataArray_0, sizeof(uint32_t),
sizeof(g_adc16SampleDataArray_0), kDMA_PeripheralToMemory);
DMA_SubmitTransfer(&g_DMA_handle_0, &g_transferConfig_0, kDMA_EnableInterrupt);

/* DMA Channel 1 Config */
DMA_CreateHandle(&g_DMA_handle_1, DMA_BASE_ADDRESS, DMA_CHANNEL_1);
DMA_SetCallback(&g_DMA_handle_1, DMA_Call_1, NULL);
DMA_PrepareTransfer(&g_transferConfig_1, (void *)ADC_RESULTS_REGISTER,
sizeof(uint32_t), (void *)g_adc16SampleDataArray_1, sizeof(uint32_t),
sizeof(g_adc16SampleDataArray_1), kDMA_PeripheralToMemory);
DMA_SubmitTransfer(&g_DMA_handle_1, &g_transferConfig_1, kDMA_EnableInterrupt);

/* DMA LINK Config */
g_dmaChLink_0.linkType = kDMA_ChannelLinkChannel1AfterBCR0;
g_dmaChLink_0.channel1 = DMA_CHANNEL_1;
DMA_SetChannelLinkConfig(DMA_BASE_ADDRESS, DMA_CHANNEL_0, &g_dmaChLink_0);
/* DMA LINK Config */
g_dmaChLink_1.linkType = kDMA_ChannelLinkChannel1AfterBCR0;
g_dmaChLink_1.channel1 = DMA_CHANNEL_0;
DMA_SetChannelLinkConfig(DMA_BASE_ADDRESS, DMA_CHANNEL_1, &g_dmaChLink_1);

DMA_SetTransferConfig(DMA_BASE_ADDRESS, DMA_CHANNEL_0, &g_transferConfig_0);
DMA_SetTransferConfig(DMA_BASE_ADDRESS, DMA_CHANNEL_1, &g_transferConfig_1);

DMA_EnableCycleSteal(DMA_BASE_ADDRESS, DMA_CHANNEL_0, true);
DMA_EnableCycleSteal(DMA_BASE_ADDRESS, DMA_CHANNEL_1, true);
/* Inital Start Transfer on Buffer 0 */
DMA_StartTransfer(&g_DMA_handle_0);
}

void Init_Board_Led(void)
{
CLOCK_EnableClock(kCLOCK_PortE);
/* Red LED */
PORT_SetPinMux(PORTE, 29U, kPORT_MuxAsGpio);
red_led_config.pinDirection = kGPIO_DigitalOutput;
red_led_config.outputLogic = 1U;
GPIO_PinInit(BOARD_REDLED_GPIO, BOARD_REDLED_GPIO_PIN, &red_led_config);
/* Green LED */
PORT_SetPinMux(PORTE, 31U, kPORT_MuxAsGpio);
green_led_config.pinDirection = kGPIO_DigitalOutput;
green_led_config.outputLogic = 1U;
GPIO_PinInit(BOARD_GREENLED_GPIO, BOARD_GREENLED_GPIO_PIN, &green_led_config);
}


/*******************************************************************************
* Main
*******************************************************************************/

int main(void) {

BOARD_InitBootPins();
BOARD_InitBootClocks();
BOARD_InitBootPeripherals();
BOARD_InitDebugConsole();
Init_Board_Led();

periodicIntTimer();
ADC_Configuration();
DMA_Configuration();
BOARDS_ConfigTriggerSource();

while(1)
{
/* Red LED ON = Buffer 0 active */
GPIO_TogglePinsOutput(BOARD_REDLED_GPIO, 1u << BOARD_REDLED_GPIO_PIN);
while (g_Transfer_Done_0 != true)
{
}
GPIO_TogglePinsOutput(BOARD_REDLED_GPIO, 1u << BOARD_REDLED_GPIO_PIN);

/* Green LED ON = Buffer 1 active */
GPIO_TogglePinsOutput(BOARD_GREENLED_GPIO, 1u << BOARD_GREENLED_GPIO_PIN);
while (g_Transfer_Done_1 != true)
{
}
GPIO_TogglePinsOutput(BOARD_GREENLED_GPIO, 1u << BOARD_GREENLED_GPIO_PIN);
}

}

0 Kudos
1,236 Views
Hui_Ma
NXP TechSupport
NXP TechSupport

Hi,

For the ADC as the only hardware trigger source for both DMA channels, each ADC conversion done will trigger different DMA channel to transfer result to different buffer. That was my understanding of "ping-pong".

While, from your code, you are using DMA channel link and will link to another DMA channel after 10 times ADC conversion result transfer to the same buffer. I don't think you are using "ping-pong" mode.

Buffer 0  <-------1----------ADC result -----------1----------> Buffer 1                          ping-pong mode

Buffer 0 <--------10--------ADC result ------------10--------> Buffer 1                          not ping-pong mode

I don't know if you understand my above description.            

I had a discussion with Kinetis L family product engineer, there doesn't need to use two DMA channels for this application.

Customer just need to use one DMA channel, after each DMA transfer, re-config the DMA channel destination address to another buffer at DMA callback function.

Or, customer can use two DMA channels, one channel was triggered by ADC module, the other channel be software trigger and link to previous channel to transfer buffer address to previous DMA channel destination address register.

Wish it helps.


Have a great day,
Mike

-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------

0 Kudos