KL27Z DMA from ADC problems

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

KL27Z DMA from ADC problems

Jump to solution
1,785 Views
aihans
Contributor II

Hi,

I'm working on a small project with the FRMD-KL27Z (MKL27Z644) board and now I'm stuck.

The final goal is to read out the ADC and send the data to a PC through USB. The ADC to USB connection will be established with DMA.

The ADC is already working as desired and the next step would be to use DMA to transfer the ADC data.

At the moment the data should be written to a single register, this register is read, the data analysed and the value visualised through different LED colors.

This works only if I set DMA_DCR0 START in the main loop.

But I'd like to do this without using the CPU too much, with the direct DMA procedure as mentioned in the reference manual.

Is there any error I'm making, but I can't find?

My registers are set as followed:

SIM_SCGC6_ADC0 = 1

SIM_SCGC6_DMAMUX = 1

SIM_SCGC7_DMA = 1

ADC is working with

ADC0_SC2_DMAEN = 1

ADC0_SC3_ADCO = 1

The DMA only starts on Software command in the main loop.

DMA_DCR0_SAR = ADC0_RA

DMA_DCR0_DAR = Register in RAM

DMA_DCR0_EINT = 1

DMA_DCR0_ERG = 1

DMA_DCR0_CS = 1 (also tried 0)

DMA_DCR0_EADREQ = 1 (also tried 0)

DMA_DCR0_DREQ = 1

DMA_DCR0_SSIZE = 10 (16 bit)

DMA_DCR0_DSIZE = 10 (16 bit)

DMAMUX0_CHCFG0_SOURCE = ADC0

DMAMUX0_CHCFG0_TRIG = 0 (also tried 1)

The initialization works:

- Enable clocks

- Disable ADC, DMA, DMAMUX

- Initialize all Registers

- Enable ADC, DMA, DMAMUX

- set DMA_DSR_BCR0_DONE = 1

- set DMA_DSR_BCR0_BCR = 2 (since 2 Bytes will be transmitted, adc runs in 16 bit signle ended mode)

- ADC calibration

- NVIC Interrupt enable (DMA0)

- main loop starts.

The DMA Interrupt handler does this:

DMA_DSR_BCR0_DONE = 1

DMA_DSR_BCR0_BCR = 2

DMA_DCR0_ERQ = 1

Is there any point I'm missing?

The error seems to be, that the DMA transfer isn't started by the ADC. No data is transfered to the Destination register, and the  DMA Interrupt handler is never called.

Thanks,

Hans

Tags (4)
0 Kudos
1 Solution
1,338 Views
aihans
Contributor II

I found the problem and the ADC DMA transfer is now functionable.

There was an issue with the Kvasir library (www.kvasir.io) I'm using.

This library tries to set as many bits in a bit field as possible in a single apply, to be more efficient.

Therefore 

DMA_DSR_BCR0_DONE = 1

and

DMA_DSR_BCR0_BCR = 2

were set at the same time, resulting in DMA_DSR_BCR0_DONE clearing DMA_DSR_BCR0_BCR.

So the DMA Interrupt always registered the DMA transfer as completed and never exited the Interrupt handler due to a lack of Bytes to be transferred during the next activation through the ADC. (ADC0_SC2_DMAEN = 1).

I then inserted sequencepoints to the apply and it worked.

Thanks for the help, it got me some issues to think about.

View solution in original post

0 Kudos
10 Replies
1,339 Views
aihans
Contributor II

I found the problem and the ADC DMA transfer is now functionable.

There was an issue with the Kvasir library (www.kvasir.io) I'm using.

This library tries to set as many bits in a bit field as possible in a single apply, to be more efficient.

Therefore 

DMA_DSR_BCR0_DONE = 1

and

DMA_DSR_BCR0_BCR = 2

were set at the same time, resulting in DMA_DSR_BCR0_DONE clearing DMA_DSR_BCR0_BCR.

So the DMA Interrupt always registered the DMA transfer as completed and never exited the Interrupt handler due to a lack of Bytes to be transferred during the next activation through the ADC. (ADC0_SC2_DMAEN = 1).

I then inserted sequencepoints to the apply and it worked.

Thanks for the help, it got me some issues to think about.

0 Kudos
1,338 Views
mjbcswitzerland
Specialist V

Hello Hans

Yes, writing the clearing and length is not possible in a single instruction - has the library that you are using been tested before in such an application?

I would be interested to hear the ADC sampling rate and USB throughput that you achieve when you are finished, plus the development time required based on the library that you use.

Configuring for and testing on the FRDM-KL27Z for bulk USB took about 20 minutes in my environment but I didn't see what maximum rate was achievable (by optimising RAM buffer sizes to trade off RAM use and interrupt rates), nor did I test isochronous mode with ADC/USB synchronisation.

Regards

Mark

0 Kudos
1,338 Views
Alice_Yang
NXP TechSupport
NXP TechSupport

Hello Hans,

Please place the Enable peripheral request (DMA_DCR0_ERQ = 1)

  into the initialize DMA to have a try .

And check whether the COCO flag have been set .

Hope it helps

Alice

0 Kudos
1,338 Views
aihans
Contributor II

Hello Alice,

thanks for the reply.

DMA_DCR0_ERQ is set to 1 in the initialization.

This was a transfer error of myself. I wrote  DMA_DCR0_ERG instead of DMA_DCR0_ERQ.

The COCO flag is set too.

I'm still stuck in the DMA0_IRQHandler.

Thanks,

Hans

0 Kudos
1,338 Views
Alice_Yang
NXP TechSupport
NXP TechSupport

Hello Hans,

"I'm still stuck in the DMA0_IRQHandler." , do you mean the ADC can trigger the DMA now ?

0 Kudos
1,338 Views
aihans
Contributor II

Hi Alice,

sorry for the late answer, I wasn't available during the holidays.

Yes the ADC is now able to trigger the DMA.

The problem was a detail in the library I'm using, that I wasn't aware of.

The ADC now triggers the DMA, and the DMA interrupt handler is called, but never ends.

At the moment I assume, that after setting

DMA_DCR0_ERQ = 1

in the Interrupt handler, the ADC directly triggers the next transfer and so on.

I will try to work on this issue the next days.

Thanks,

Hans

0 Kudos
1,338 Views
mjbcswitzerland
Specialist V

Hans

I see that you have configured DMA to be triggered by the ADC conversion, but I don't see where you are controling the ADC conversion from.

Often it is done by using a PIT which is configured to run at a certain sampling frequency and triggers the ADC according to the setting in SIM_SOPT7.

You can get some extra details in http://www.utasker.com/docs/uTasker/uTaskerADC.pdf

and some background of using the techniques in USB Audio device operation in http://www.utasker.com/docs/uTasker/uTaskerUSB_Audio.pdf

As reference I have attached a FRDM-KL27Z binary that you can load to your board. It samples the input ADC0_SE8 (J4-2 connector) at 8kHz (controlled by PIT0 triggering each conversion).

The ADC0 conversion complete is used to trigger DMA channel 1 to save the ADC values to a buffer.

At the same time the ADC0 conversion complete also triggers DMA channel 0 (with higher priority) to copy the buffer value to a PWM mark-space-ratio output value. This results in a simple ADC/DAC (with the PWM output performing a DAC function) and a delay line of the buffer sample period.

The PWM is 2kHz with the ADC 12 bit range approximately controlling the PWM output 0..100% range. The PWM output is connected to the RED LED on the board so the analogue signal on J4-2 controls the red intensity after the delay-line reaction delay.

The ADC buffer can also be monitored by using the memory debugger in the FRDM-KL27Z's virtual COM UART at 115'200 Baud.

Hit enter to see the command line menu and then move to the I/O menu.

Then enter

"md 20000c00 w 128" [memory display at the address 0x20000c00 with word dispay of 128 entries].

This is the location of the buffer in memory and it is being constantly updated by DMA.

Eg.

md 20000c00 w 128
Memory Display
0x20000c00     0fff 0fff 0fff 0fff 0fff 0fff 0fff 0fff  ................
0x20000c10     0fa6 0fa6 0ac9 03ab 004b 0009 0001 0001  .........K......
0x20000c20     0001 0001 0001 0001 0001 0001 0001 0001  ................
0x20000c30     0001 0001 0001 0001 0001 0001 0001 0001  ................
0x20000c40     0001 0001 0001 0001 0001 0001 0001 0001  ................
0x20000c50     0001 0001 0001 0001 003f 01d6 065b 09a7  .........?...[..
0x20000c60     0cb5 0fa6 0fff 0fff 0fff 0fff 0fff 0fff  ................
0x20000c70     0fff 0fff 0fff 0fff 0fff 0fff 0fff 0fff  ................
0x20000c80     0fff 0fff 0fff 0fff 0fff 0fff 0fff 0fff  ................
0x20000c90     0fff 0fff 0fff 0fff 0fff 0fff 0fff 0fff  ................
0x20000ca0     0fff 0fff 0fff 0fff 0fff 0fff 0fa6 0aff  ................
0x20000cb0     007e 0001 0001 0001 0001 0001 0001 0001  ................
0x20000cc0     0001 0001 0001 0001 0001 0001 0001 0001  ................
0x20000cd0     0001 0001 0001 0001 0001 0001 0001 0001  ................
0x20000ce0     0001 0001 0001 0001 0001 0001 0001 0001  ................
0x20000cf0     0001 0001 0001 0001 0001 00c4 0411 09a7  ................

I could set up such a configuration in a few minutes using the uTasker project (as detailed in the referenced documents) and also simulate the complete ADC/DMA/timer operations to check that all was OK, before generating the binary that runs on the HW.

Regards

Mark

0 Kudos
1,337 Views
aihans
Contributor II

Hi Mark,

thanks for the binary.

The ADC conversion is not controlled.

The ADC runs in continuous conversion mode.

Goal is to take as many ADC conversions as possible and send them to a PC through USB.

Therefore controlling the ADC with a PIT or another interrupt handler would just slow the system down.

Regards,

Hans

0 Kudos
1,337 Views
mjbcswitzerland
Specialist V

Hans

I think that you may have some conceptional issues.

1. Triggering the ADC with a PIT doesn't require interrupts - it is simply accurately defining the sampling rate, which can be set up as high as you like.

2. Using free-run mode at the ADC means that you control the conversion rate by the ADC's inherent conversion time, depending on the way that it is configured, which will also work equivalently but with less control of the rate.

3. If you intend to transfer via USB you can either use bulk mode or isochronous mode.

- in bulk mode you can transfer about 1MBytes/s, which limits to 500kHz ADC sampling rate (

- in isochronous mode you need to accurately control the host clock and local sampling clock timing otherwise there will be data loss due to synchronisation issues

This means that in both cases you need to use a well defined sampling rate and simply using the ADC's inherent and fastest possible rate will likely lead to more difficulties at the data interface.

As reference I have attached a free-running version (ADC in continuous conversion mode triggering DMA to memory and also memory to PWM output). The ADC is set up with HW averaging and good resolution, so the sampling rate is not particularly high (controlled by the conversion time itself). There are NO interrupts used in the process since the memory is using a modulo 2 buffer and so the DMA performs wrap-arounds itself.

Regards

Mark

0 Kudos
1,337 Views
mjbcswitzerland
Specialist V

Hans

Assuming that you would finally like to transfer using USB bulk (no clock synchronisation requirements) I have attached a binary for the FRDM-KL27 which allows the ADC to free-run, stores the data to a RAM buffer using DMA and then sends the RAM buffer to USB (when connecting with a terminal emulator to the virtual COM port the raw data is seen).

To do this I however configured the DMA to emulate a half-buffer interrupt operation (as the Kinetis K parts do inherently) so that the application can send half of the buffer to USB while the other half is still being filled (ping-pong operation).

The ADC is sampling at about 100ksamples/second and so the bulk data rate is about 200kBytes/s (due to 12 bit content).

USB drivers for the CDC can be downloaded from http://www.utasker.com/kinetis/FRDM-K64F.html (search for "USB-CDC with 5 UARTs" - although the page is for the FRDM-K64F, the same driver is valid).

This is the code that I used to configure this and send to USB (the PWM output is still being updated by DMA but this is not shown) is the following:

#define AD_DA_BUFFER_LENGTH        (256)                                 // buffer for 1s at 256 bytes/s (512 bytes in total)
static signed short *ptrADC_buffer = 0;                                  // pointer to aligned buffer - 12 bit samples

static void fnConfigureADC(void)
{
    ADC_SETUP adc_setup;                                                 // interrupt configuration parameters
    adc_setup.int_type = ADC_INTERRUPT;                                  // identifier when configuring
    adc_setup.dma_int_priority = 3;                                      // priority of DMA interrupt the user wants to set
    adc_setup.pga_gain = PGA_GAIN_OFF;                                   // PGA gain can be specified for certain inputs
    adc_setup.int_adc_controller = 0;                                    // ADC controller 0
    adc_setup.int_adc_int_type = (ADC_SINGLE_SHOT_TRIGGER_INT);          // interrupt type
    adc_setup.int_adc_bit = ADC_SE8_SINGLE;                              // A0 [J4-2]
    ptrADC_buffer = uMalloc((AD_DA_BUFFER_LENGTH * sizeof(unsigned short))); // create buffer on heap
    adc_setup.ptrADC_Buffer = ptrADC_buffer;
    adc_setup.ulADC_buffer_length = (AD_DA_BUFFER_LENGTH * sizeof(unsigned short)); // physical length of the buffer
    adc_setup.ucDmaChannel = 1;                                          // DMA channel 1 used
    adc_setup.int_adc_mode = (ADC_CALIBRATE| ADC_LOOP_MODE | ADC_FULL_BUFFER_DMA_AUTO_REPEAT | ADC_FULL_BUFFER_DMA |
                              ADC_HALF_BUFFER_DMA | ADC_SELECT_INPUTS_A | ADC_CLOCK_BUS_DIV_2 | ADC_CLOCK_DIVIDE_4 |
                              ADC_SAMPLE_ACTIVATE_LONG | ADC_CONFIGURE_ADC | ADC_REFERENCE_VREF | ADC_CONFIGURE_CHANNEL |
                              ADC_SINGLE_ENDED | ADC_SINGLE_SHOT_MODE | ADC_12_BIT_MODE); // continuous conversion (DMA to buffer)
    adc_setup.dma_int_handler = fnUSBtransfer;                           // half-buffer call-back to transfer to USB
    adc_setup.int_adc_sample = (ADC_SAMPLE_LONG_PLUS_12 | ADC_SAMPLE_AVERAGING_OFF); // additional sampling clocks without hardware averaging

    fnConfigureInterrupt((void *)&adc_setup);                            // configure ADC
}

The half-buffer DMA interrupt is handled using:

static void fnUSBtransfer(void)
{
    static unsigned char ucPingPong = 0;
    fnInterruptMessage(ADC_TASK, (unsigned char)(ADC_TRIGGER_1 + ucPingPong));
    ucPingPong ^= 1;
}

which sends an event to a task that then transfers the latest half buffer to USB.

In the task the event handler does:

if ((ADC_TRIGGER_1 == ucInputMessage[MSG_INTERRUPT_EVENT])) {
    fnWrite(USBPortID_comms, ptrADC_buffer, AD_DA_BUFFER_LENGTH)); // send first half buffer content to USB-CDC interface
}
else if ((ADC_TRIGGER_2 == ucInputMessage[MSG_INTERRUPT_EVENT])) {
    fnWrite(USBPortID_comms, (ptrADC_buffer + (AD_DA_BUFFER_LENGTH/2)), AD_DA_BUFFER_LENGTH)); // send second half buffer content to USB-CDC interface
}

USB-CDC is of course opened by other code (creating the USBPortID_comms) but that is the complete application code needed to do the bulk-transfer type and so to complete such a project.

Isochronous can be more efficient (eg. audio class) since it allows the ADC DMA to store directly to the USB output buffer but would require accurate timing synchronisation as noted earlier and so free-running ADC would probably not be possible.

Regards

Mark

0 Kudos