|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- /* Teensy 3.x, LC, 4.0 ADC library
- https://github.com/pedvide/ADC
- Copyright (c) 2020 Pedro Villanueva
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be
- included in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
- */
-
- #ifdef ADC_USE_DMA
-
- #include "AnalogBufferDMA.h"
-
- //#define DEBUG_DUMP_DATA
- // Global objects
- AnalogBufferDMA *AnalogBufferDMA::_activeObjectPerADC[2] = {nullptr, nullptr};
-
- #if defined(__IMXRT1062__) // Teensy 4.0
- #define SOURCE_ADC_0 ADC1_R0
- #define DMAMUX_ADC_0 DMAMUX_SOURCE_ADC1
- #define SOURCE_ADC_1 ADC2_R0
- #define DMAMUX_ADC_1 DMAMUX_SOURCE_ADC2
- #elif defined(KINETISK)
- #define SOURCE_ADC_0 ADC0_RA
- #define DMAMUX_ADC_0 DMAMUX_SOURCE_ADC0
- #ifdef ADC_DUAL_ADCS
- #define SOURCE_ADC_1 ADC1_RA
- #define DMAMUX_ADC_1 DMAMUX_SOURCE_ADC1
- #endif
- #elif defined(KINETISL)
- #define SOURCE_ADC_0 ADC0_RA
- #define DMAMUX_ADC_0 DMAMUX_SOURCE_ADC0
- #endif
-
- //=============================================================================
- // Debug support
- //=============================================================================
-
- #ifdef DEBUG_DUMP_DATA
- static void dumpDMA_TCD(DMABaseClass *dmabc)
- {
- #ifndef KINETISL
- Serial.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->TCD);
-
- Serial.printf("SA:%x SO:%d AT:%x NB:%x SL:%d DA:%x DO: %d CI:%x DL:%x CS:%x BI:%x\n", (uint32_t)dmabc->TCD->SADDR,
- dmabc->TCD->SOFF, dmabc->TCD->ATTR, dmabc->TCD->NBYTES, dmabc->TCD->SLAST, (uint32_t)dmabc->TCD->DADDR,
- dmabc->TCD->DOFF, dmabc->TCD->CITER, dmabc->TCD->DLASTSGA, dmabc->TCD->CSR, dmabc->TCD->BITER);
- #else
- Serial.printf("%x %x:", (uint32_t)dmabc, (uint32_t)dmabc->CFG);
-
- Serial.printf("SAR:%x DAR:%x DSR_BCR:%x DCR:%x\n", (uint32_t)dmabc->CFG->SAR, dmabc->CFG->DAR, dmabc->CFG->DSR_BCR,
- dmabc->CFG->DCR);
- #endif
- }
- #endif
-
- //=============================================================================
- // Init - Initialize the object including setup DMA structures
- //=============================================================================
- void AnalogBufferDMA::init(ADC *adc, int8_t adc_num)
- {
- // enable DMA and interrupts
- #ifdef DEBUG_DUMP_DATA
- Serial.println("AnalogBufferDMA::init");
- Serial.flush();
- #endif
-
- #ifndef KINETISL
- // setup a DMA Channel.
- // Now lets see the different things that RingbufferDMA setup for us before
- // See if we were created with one or two buffers. If one assume we stop on completion, else assume continuous.
- if (_buffer2 && _buffer2_count)
- {
- #ifdef ADC_DUAL_ADCS
- _dmasettings_adc[0].source((volatile uint16_t &)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0));
- #else
- _dmasettings_adc[0].source((volatile uint16_t &)(SOURCE_ADC_0));
- #endif
- _dmasettings_adc[0].destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason
- _dmasettings_adc[0].replaceSettingsOnCompletion(_dmasettings_adc[1]); // go off and use second one...
- _dmasettings_adc[0].interruptAtCompletion(); //interruptAtHalf or interruptAtCompletion
- #ifdef ADC_DUAL_ADCS
- _dmasettings_adc[1].source((volatile uint16_t &)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0));
- _dmasettings_adc[1].destinationBuffer((uint16_t *)_buffer2, _buffer2_count * 2); // 2*b_size is necessary for some reason
- _dmasettings_adc[1].replaceSettingsOnCompletion(_dmasettings_adc[0]); // Cycle back to the first one
- _dmasettings_adc[1].interruptAtCompletion(); //interruptAtHalf or interruptAtCompletion
- #endif
-
- _dmachannel_adc = _dmasettings_adc[0];
-
- _stop_on_completion = false;
- }
- else
- {
- // Only one buffer so lets just setup the dmachannel ...
- // Serial.printf("AnalogBufferDMA::init Single buffer %d\n", adc_num);
- #ifdef ADC_DUAL_ADCS
- _dmachannel_adc.source((volatile uint16_t &)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0));
- #else
- _dmachannel_adc.source((volatile uint16_t &)(SOURCE_ADC_0));
- #endif
- _dmachannel_adc.destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason
- _dmachannel_adc.interruptAtCompletion(); //interruptAtHalf or interruptAtCompletion
- _dmachannel_adc.disableOnCompletion(); // we will disable on completion.
- _stop_on_completion = true;
- }
-
- if (adc_num == 1)
- {
- #ifdef ADC_DUAL_ADCS
- _activeObjectPerADC[1] = this;
- _dmachannel_adc.attachInterrupt(&adc_1_dmaISR);
- _dmachannel_adc.triggerAtHardwareEvent(DMAMUX_ADC_1); // start DMA channel when ADC finishes a conversion
- #endif
- }
- else
- {
- _activeObjectPerADC[0] = this;
- _dmachannel_adc.attachInterrupt(&adc_0_dmaISR);
- _dmachannel_adc.triggerAtHardwareEvent(DMAMUX_ADC_0); // start DMA channel when ADC finishes a conversion
- }
- //arm_dcache_flush((void*)dmaChannel, sizeof(dmaChannel));
- _dmachannel_adc.enable();
-
- adc->adc[adc_num]->continuousMode();
- adc->adc[adc_num]->enableDMA();
-
- #ifdef DEBUG_DUMP_DATA
- dumpDMA_TCD(&_dmachannel_adc);
- dumpDMA_TCD(&_dmasettings_adc[0]);
- dumpDMA_TCD(&_dmasettings_adc[1]);
- #if defined(__IMXRT1062__) // Teensy 4.0
-
- if (adc_num == 1)
- {
- Serial.printf("ADC2: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", ADC2_HC0, ADC2_HS, ADC2_CFG, ADC2_GC, ADC2_GS);
- }
- else
- {
- Serial.printf("ADC1: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", ADC1_HC0, ADC1_HS, ADC1_CFG, ADC1_GC, ADC1_GS);
- }
- #endif
- #endif
- #else
- // Kinetisl (TLC)
- // setup a DMA Channel.
- // Now lets see the different things that RingbufferDMA setup for us before
- _dmachannel_adc.source((volatile uint16_t &)(SOURCE_ADC_0));
- ;
- _dmachannel_adc.destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason
- _dmachannel_adc.disableOnCompletion(); // ISR will hae to restart with other buffer
- _dmachannel_adc.interruptAtCompletion(); //interruptAtHalf or interruptAtCompletion
- _activeObjectPerADC[0] = this;
- _dmachannel_adc.attachInterrupt(&adc_0_dmaISR);
- _dmachannel_adc.triggerAtHardwareEvent(DMAMUX_ADC_0); // start DMA channel when ADC finishes a conversion
- _dmachannel_adc.enable();
-
- adc->startContinuous(adc_num);
- adc->enableDMA(adc_num);
- #ifdef DEBUG_DUMP_DATA
- dumpDMA_TCD(&_dmachannel_adc);
- #endif
-
- #endif
-
- _last_isr_time = millis();
- }
-
- //=============================================================================
- // stopOnCompletion: allows you to turn on or off stopping when a DMA buffer
- // has completed filling. Default is on when only one buffer passed in to the
- // constructor and off if two buffers passed in.
- //=============================================================================
- void AnalogBufferDMA::stopOnCompletion(bool stop_on_complete)
- {
- #ifndef KINETISL
- if (stop_on_complete)
- _dmachannel_adc.TCD->CSR |= DMA_TCD_CSR_DREQ;
- else
- _dmachannel_adc.TCD->CSR &= ~DMA_TCD_CSR_DREQ;
- #else
- if (stop_on_complete)
- _dmachannel_adc.CFG->DCR |= DMA_DCR_D_REQ;
- else
- _dmachannel_adc.CFG->DCR &= ~DMA_DCR_D_REQ;
- #endif
- _stop_on_completion = stop_on_complete;
- }
-
- //=============================================================================
- // ClearCompletion: if we have stop on completion, then clear the completion state
- // i.e. reenable the dma operation. Note only valid if we are
- // in the stopOnCompletion state.
- //=============================================================================
- bool AnalogBufferDMA::clearCompletion()
- {
- if (!_stop_on_completion)
- return false;
- // should probably check to see if we are dsiable or not...
- _dmachannel_adc.enable();
- return true;
- }
-
- //=============================================================================
- // processADC_DMAISR: Process the DMA completion ISR
- // common for both ISRs on those processors who have more than one ADC
- //=============================================================================
- void AnalogBufferDMA::processADC_DMAISR()
- {
- //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
- uint32_t cur_time = millis();
-
- _interrupt_count++;
- _interrupt_delta_time = cur_time - _last_isr_time;
- _last_isr_time = cur_time;
- // update the internal buffer positions
- _dmachannel_adc.clearInterrupt();
- #ifdef KINETISL
- // Lets try to clear the previous interrupt, change buffers
- // and restart
- if (_buffer2 && (_interrupt_count & 1))
- {
- _dmachannel_adc.destinationBuffer((uint16_t *)_buffer2, _buffer2_count * 2); // 2*b_size is necessary for some reason
- }
- else
- {
- _dmachannel_adc.destinationBuffer((uint16_t *)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason
- }
-
- // If we are not stopping on completion, then reenable...
- if (!_stop_on_completion)
- _dmachannel_adc.enable();
-
- #endif
- }
-
- //=============================================================================
- // adc_0_dmaISR: called for first ADC when DMA has completed filling a buffer.
- //=============================================================================
- void AnalogBufferDMA::adc_0_dmaISR()
- {
- if (_activeObjectPerADC[0])
- {
- _activeObjectPerADC[0]->processADC_DMAISR();
- }
- #if defined(__IMXRT1062__) // Teensy 4.0
- asm("DSB");
- #endif
- }
-
- //=============================================================================
- // adc_1_dmaISR - Used for processors that have a second ADC object
- //=============================================================================
- void AnalogBufferDMA::adc_1_dmaISR()
- {
- if (_activeObjectPerADC[1])
- {
- _activeObjectPerADC[1]->processADC_DMAISR();
- }
- #if defined(__IMXRT1062__) // Teensy 4.0
- asm("DSB");
- #endif
- }
-
- #endif // ADC_USE_DMA
|