Can Kinetis CPU sample 30 A/D channels via HW trigger and HW DMA with no software involvement?

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

Can Kinetis CPU sample 30 A/D channels via HW trigger and HW DMA with no software involvement?

1,323 Views
mscovel
Contributor III

I am a little embarrassed to ask this because I am certain it is in the reference manual for our CPU (K65) but I wonder if anyone in the community has done this.

Is it possible to:

1. Software configure the A/D peripheral (16 bit samples, averaging, sample time, etc)

2. Software configure the DMA peripheral (memory buffers, 30 A/D channel numbers, etc)

3. Software configure a timer (PIT, PDB or other) to trigger A/D conversions at a predetermined rate.

And then let these hardware peripherals perform the A/D trigger, read, store, mux cycle without software involvement?

Anyone have any experience at this?

0 Kudos
4 Replies

1,108 Views
dereksnell
NXP Employee
NXP Employee

1,108 Views
mscovel
Contributor III

Thanks Derek!

0 Kudos

1,108 Views
mjbcswitzerland
Specialist V

Hi Mark

Here is one method (from the uTasker project).

#define ADC_CHANNELS     4

static unsigned long ulCalibrate = ADC_CALIBRATE;

static unsigned short usADCbuffer = {0};               // output buffer for ADC samples
static const unsigned long ADC_Mux_values[ADC_CHANNELS] = {(ADC_CFG2_MUXSEL_B | ADC_SAMPLE_LONG_PLUS_12 | ADC_SAMPLE_AVERAGING_32), 
                                                             (ADC_CFG2_MUXSEL_A | ADC_SAMPLE_LONG_PLUS_12 | ADC_SAMPLE_AVERAGING_32),
                                                             (ADC_CFG2_MUXSEL_A | ADC_SAMPLE_LONG_PLUS_12 | ADC_SAMPLE_AVERAGING_32),
                                                             (ADC_CFG2_MUXSEL_B | ADC_SAMPLE_LONG_PLUS_12 | ADC_SAMPLE_AVERAGING_32)
};
static const unsigned long ADC_inputs[ADC_CHANNELS] = {(ADC_D1_DIFF | ADC_SC1A_DIFF), 
                                                       (ADC_SE9_SINGLE),
                                                       (ADC_SE14_SINGLE),
                                                       (ADC_DP3_SINGLE)
};

void fnStartADC(void)
{
    ADC_SETUP adc_setup;                                                 // interrupt configuration parameters
    PWM_INTERRUPT_SETUP pwm_setup;
    adc_setup.int_type = ADC_INTERRUPT;                                  // identifier when configuring
    adc_setup.ucDmaChannel = 0;
    adc_setup.dma_int_priority = 3;                                      // priority of DMA interrupt the user wants to set
    adc_setup.usDmaTriggerSource = DMAMUX0_CHCFG_SOURCE_ADC0;            // trigger DMA on ADC conversion ready
    adc_setup.pga_gain = PGA_GAIN_OFF;                                   // PGA gain can be specified for certain inputs
    adc_setup.int_priority = PRIORITY_ADC;                               // ADC interrupt priority
    adc_setup.int_adc_controller = 0;                                    // ADC controller 0
    adc_setup.int_handler = 0;
    adc_setup.int_adc_int_type = (ADC_DISABLE_INTS);
    adc_setup.int_adc_offset = 0;                                        // no offset
    adc_setup.int_adc_bit = ADC_D1_DIFF;                                 // ADC DM3 single-ended
    adc_setup.dma_int_handler = _int_scan_ready;                         // no user DMA interrupt call-back
    adc_setup.ptrADC_Buffer = usADCbuffer;                               // ADC sample buffer to be used
    adc_setup.ulADC_buffer_length = sizeof(usADCbuffer);                 // physical length of the buffer
    adc_setup.int_adc_mode = (ulCalibrate | ADC_FULL_BUFFER_DMA | ADC_SELECT_INPUTS_A | ADC_CLOCK_BUS_DIV_2 | ADC_CLOCK_DIVIDE_8 | ADC_SAMPLE_ACTIVATE_LONG | ADC_CONFIGURE_ADC | ADC_REFERENCE_VREF | ADC_CONFIGURE_CHANNEL | ADC_SINGLE_ENDED_INPUT | ADC_SINGLE_SHOT_MODE | ADC_12_BIT_MODE | ADC_ALTERNATIVE_HW_TRIGGER); // hardware triggering (DMA to buffer with interrupt on buffer completion)
    adc_setup.int_adc_sample = (ADC_SAMPLE_LONG_PLUS_12 | ADC_SAMPLE_AVERAGING_8); // additional sampling clocks and hardware averaging
    adc_setup.hw_trigger = ADC_HW_TRIGGER_TPM1;                          // trigger on TPM overflow
    adc_setup.int_adc_result = 0;                                        // no result is requested
    adc_setup.int_adc_speed = (unsigned char)(ADC_SAMPLING_SPEED(5000000)); // 5MHz sampling (must be between 100kHz and 5MHz)
    fnConfigureInterrupt((void *)&adc_setup);                            // configure ADC
    ulCalibrate = 0;                                            

    pwm_setup.int_type = PWM_INTERRUPT;
    pwm_setup.pwm_mode = (PWM_SYS_CLK | PWM_PRESCALER_16 | PWM_NO_OUTPUT | PWM_DMA_CHANNEL_ENABLE | PWM_FULL_BUFFER_DMA_AUTO_REPEAT); // clock PWM timer from the system clock with /16 pre-scaler
    pwm_setup.ucDmaChannel = 1;
    pwm_setup.usDmaDestination = ADD_ADC0_CFG2;
    pwm_setup.usDmaTriggerSource = DMAMUX0_CHCFG_SOURCE_FTM1_C0;         // when TPM1 channel 0 fires it triggers the transfer of the next ADC CFG2 setting
    pwm_setup.dma_int_handler = 0;
    pwm_setup.ptrPWM_Buffer = ADC_Mux_values;
    pwm_setup.ulPWM_buffer_length = sizeof(ADC_Mux_values);
    pwm_setup.pwm_frequency = PWM_FREQUENCY(1000, 16);                   // generate 1000Hz time base
    pwm_setup.int_handler = 0;                                           // no user interrupt call-back on PWM cycle
    pwm_setup.pwm_reference = (_TPM_TIMER_1 | 0);                        // timer module 1, channel 0
    pwm_setup.pwm_value   = _PWM_PERCENT(80, pwm_setup.pwm_frequency);   // 80% PWM (high/low)
    fnConfigureInterrupt((void *)&pwm_setup);                            // enter configuration
    pwm_setup.ucDmaChannel = 2;
    pwm_setup.usDmaDestination = ADD_ADC0_SC1A;
    pwm_setup.usDmaTriggerSource = DMAMUX0_CHCFG_SOURCE_FTM1_C1;         // when TPM1 channel 1 fires it triggers the transfer of the next ADC SC1A setting
    pwm_setup.dma_int_handler = 0;
    pwm_setup.ptrPWM_Buffer = ADC_inputs;
    pwm_setup.ulPWM_buffer_length = sizeof(ADC_inputs);
    pwm_setup.pwm_reference = (_TPM_TIMER_1 | 1);                        // timer module 1, channel 1
    pwm_setup.pwm_value   = _PWM_PERCENT(90, pwm_setup.pwm_frequency);   // 90% PWM (high/low)
    fnConfigureInterrupt((void *)&pwm_setup);                            // enter configuration
}

// Interrupt call back when all samples are ready
//
static void _int_scan_ready(void)
{
}

It used TPM1 to trigger ADC conversions (HW triggers) and sets up two DMA triggers on the TPM0's channels 0 and 1 which 'fire' at 80% and 90%  of the sampling cycle.

There are 3 DMA channels involved (the DMA setup is integrated in the ADC and PWM APIs which support DMA register to circular buffer transfers and buffer to register transfers respectively).

The first DMA channel copies the ADC0RA value to the buffer at each ADC conversion completion (after a scan set it will reset to the start of the buffer and the interrupt _int_scan_ready is called so that the collected scan samples can be processed).

The second DMA channel (called after the 80% of the sample cycle (assumed after ADC has completed) copies the input channel (A or B) setting for the next input to be sampled (if all are on inputs A or B this is not needed). [ADC0_CFG2 needs changing if the input changes between A and B - See appendix D of http://www.utasker.com/docs/uTasker/uTaskerADC.pdf for a description of the HW input muxing in the ADC.]

The third DMA channel (called after 90% of the sample cycle) copies the input (the ADC input itself) setting for the next input to be sampled. [ADC0_SC1A is the ADC channel setting].

The ref code uses just 4 inputs but it can be extended to a larger number. The same can be performed for ADC1 in parallel with a different set (ADC1 would need to use TPM2 for trigger). The scan output buffer can be larger (eg. collects 10 sets of scans before completing, and a half-scan interrupt can be used (half-DMA ready) so that a ping-pong processing can be performed (further samples collected whilst first half are processed).

This is just one possible configuration since there are various trigger and DMA possibilities to do about the same thing.

Since sometimes two ADC registers need to be set up it would further be possible to chain DMA channel to do it. The two TPM channel triggers is however simpler to configure. It is possible to trigger the two for a single DMA trigger but it is not advisable since, according to the manuals, this could lead to undefined behavior - although practical tests seems to be fine.

Regards

Mark

P.S. Don't forget that the ADCs need to be calibrated once on first use after reset.

P.P.S. The sample buffer has interleaved samples.

uTasker developer and supporter (+5'000 hours experience on +60 Kinetis derivatives in +80 product developments)
Kinetis: http://www.utasker.com/kinetis.html

0 Kudos

1,108 Views
mscovel
Contributor III

Thanks Mark

0 Kudos