Author Topic: ADC on Kinetis - Free-running, multiple channels  (Read 4176 times)

Offline AlexS

  • Newbie
  • *
  • Posts: 46
    • View Profile
ADC on Kinetis - Free-running, multiple channels
« on: June 29, 2018, 03:29:27 PM »
Hi,

I've read all of the forum threads on how the ADC operates on the Kinetis and also spent some time trying to understand the examples from ADC_Timers.h. For all the value that uTasker provides, I think a slightly more comprehensive document covering ADC operation and individual examples on various types of setups would greatly improve the experience and alleviate the learning curve. :)

With that in mind, let me jump straight in. Our product requires several ADC channels to be running, sampling and converting at the same time, on both AD0 and AD1. I wrote a simple driver to wrap uTasker's ADC functionality with another layer to provide the same interface as the old Processor Expert drivers. I just want to make sure I got the basics right, if someone more familiar doesn't mind taking a look:

1. First of all, this is the routine for calibrating the ADC. It's only done once for both AD0 and AD1 at startup.

Code: [Select]

adc_setup.int_adc_controller = ad;
adc_setup.int_adc_mode = (ADC_CALIBRATE | 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_16_BIT_MODE | ADC_SW_TRIGGERED);
adc_setup.int_adc_sample = (ADC_SAMPLE_LONG_PLUS_12 | ADC_SAMPLE_AVERAGING_32); // additional sampling clocks
fnConfigureInterrupt((void *)&adc_setup);

As I'm setting everything up with the interrupts disabled, at this stage I should poll for the result and perform the calibration as described in the Kinetis reference manual? Is there any example on how to do this in uTasker?

2. After performing the ADC calibration, I initialize both AD modules in this manner, for continuous sampling and conversion:

Code: [Select]
adc_setup.int_adc_controller = ad;
adc_setup.int_adc_result = (ad == ADC_MODULE_0) ? (&ad0_results) : (&ad1_results);
adc_setup.int_type = ADC_INTERRUPT;                                  // identifier when configuring
adc_setup.int_adc_bit = chan_msk;
adc_setup.int_adc_mode = (ADC_CONFIGURE_ADC | ADC_CONFIGURE_CHANNEL | ADC_SEQUENTIAL_MODE | ADC_SINGLE_ENDED
| ADC_LOOP_MODE | ADC_ALL_RESULTS  | ADC_START_OPERATION); // single ended configuration in loop mode
adc_setup.int_adc_speed = (ADC_SAMPLING_SPEED(5000000));  // 5MHz sampling (must be between 100kHz and 5MHz)
adc_setup.int_adc_int_type = 0;                                      // no interrupt
fnConfigureInterrupt((void *) &adc_setup);                            // configure and start operation

Then, approximately each 2ms, I read the result values and statuses from the corresponding variables (ad0_results or ad1_results)

Code: [Select]
ADC_RESULTS res;

switch (module) {
case ADC_MODULE_0:
res.ucADC_status = ad0_results.ucADC_status[chan_idx];
res.sADC_value = ad0_results.sADC_value[chan_idx];
break;
case ADC_MODULE_1:
res.ucADC_status = ad1_results.ucADC_status[chan_idx];
res.sADC_value = ad1_results.sADC_value[chan_idx];
break;

Thanks!
Alex

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: ADC on Kinetis - Free-running, multiple channels
« Reply #1 on: June 29, 2018, 07:19:08 PM »
Hi Alex

Have you checked the ADC/DAC user's manual at http://www.utasker.com/docs/uTasker/uTaskerADC.pdf ?

Regards

Mark

Offline AlexS

  • Newbie
  • *
  • Posts: 46
    • View Profile
Re: ADC on Kinetis - Free-running, multiple channels
« Reply #2 on: June 30, 2018, 06:52:02 AM »
Hi Mark,

Indeed I have. I didn't find sections to cover the ADC calibration routine (how it's performed, not just a mention), nor details on how to run the ADC in free-running mode, without DMA.

Alex

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3234
    • View Profile
    • uTasker
Re: ADC on Kinetis - Free-running, multiple channels
« Reply #3 on: July 02, 2018, 11:16:48 AM »
Hi Alex

1. The ADC calibration needs to be performed once after each reset and for each ADC controller [if used] (before first use). The interface performs this (according to the user manual details) when configured with the flag ADC_CALIBRATE.
The references do it with
adc_setup.int_adc_mode = (ulCalibrate | 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_SW_TRIGGERED);
and then sets ulCalibration from ADC_CALIBRATE to 0 so that it only takes place when the routine is called the first time.

In your case you have called the calibration as an initial step, in which case it can be done without ADC_CONFIGURE_CHANNEL (since the calibration doesn't use a channel or need to set one up).

The routine will return once the calibration has completed - I don't actually know how long it takes and this is not referenced in the ADC data sheet. The user therefore doesn't need to check results.

2. I don't follow this code since it uses defines that are not available for the Kinetis (which version are you using?)
However I did add a polling reference to the project on 9.2.2018 when in ADC_Timers.h the define TEST_POLL_ADC can be set to get polling rather than interrupt driven operation and it may be best to follow this:
- it doesn't use ADC_LOOP_MODE (which could be used to allow the ADC to free-run) but instead sets up for a single conversion without interrupt
- it then polls the result and stops when the result is ready

        ADC_RESULTS adc_results;
        ADC_SETUP adc_setup;                                             // interrupt configuration parameters
        adc_setup.int_type = ADC_INTERRUPT;                              // identifier
        adc_setup.int_adc_mode = (ADC_READ_ONLY | ADC_CHECK_CONVERSION); // check whether the conversion has completed and don't wait for it to become ready
        adc_setup.int_adc_controller = 0;
        adc_setup.int_adc_result = &adc_results;
        fnConfigureInterrupt((void *)&adc_setup);
        if (adc_results.ucADC_status[0] == ADC_RESULT_VALID) {           // if the conversion is ready
            // result is in adc_results.sADC_value[0]
        }


By using ADC_LOOP_MODE and reading at a rate less that the conversion time will also result in a new valid sample at each read.

        ADC_RESULTS adc_results;
        ADC_SETUP adc_setup;                                             // interrupt configuration parameters
        adc_setup.int_type = ADC_INTERRUPT;                              // identifier
        adc_setup.int_adc_mode = (ADC_GET_RESULT);                       // read (wait until conversion is ready and return)
        adc_setup.int_adc_controller = 0;
        adc_setup.int_adc_result = &adc_results;
        fnConfigureInterrupt((void *)&adc_setup);                   
        // result is in adc_results.sADC_value[0]



I just updated the open source version with the same configuration (it was not included there).

I will need to review the ADC documentation again since the ADC controllers vary between processor families and more code examples in the document for each type may be needed. Generally I recommed stepping the interface code and the ADC driver code in the simulator to get a quick insight into its flag usage and capabilities. Also, the ADC interface is meant as a fairly high-level user interface which allows general operation to be achieved without knowing too many low level details. Once you know lower level details (eg. seeing what the driver is doing) it is certainly more efficient to simply read directly ADC values from their registers that pass through the interface. Therefore start with a working reference - check what it actually does and possible access directly the registers involved for final production code.

Regards

Mark