diff --git a/.gitignore b/.gitignore index b9c4714..5a919ac 100644 --- a/.gitignore +++ b/.gitignore @@ -236,3 +236,4 @@ pip-log.txt *.cbp *.layout *.save-failed +*.cmd diff --git a/ADC.cpp b/ADC.cpp index 1fbd72d..8fbc43a 100644 --- a/ADC.cpp +++ b/ADC.cpp @@ -187,6 +187,12 @@ const uint8_t ADC::sc1a2channelADC0[]= { // new version, gives directly the pin 0, 66, 0, 0, 70, 0, 0, 0, // 22-29 0 // 31 means disabled, but just in case }; +#elif defined(ADC_TEENSY_4) +const uint8_t ADC::sc1a2channelADC0[]= { // new version, gives directly the pin number + 21, 24, 25, 0, 0, 19, 18, 14, 15, 0, 0, 17, 16, 22, + 23, 20, 0, 0, 0, 0, 0, 0, //14-21 + 0, 0, 0, 0, 0, 0 //22-27 +}; #endif // defined ///////// ADC1 @@ -204,6 +210,12 @@ const uint8_t ADC::sc1a2channelADC1[]= { // new version, gives directly the pin 0, 67, 0, 0, 0, 0, 0, 0, // 22-29. 0 }; +#elif defined(ADC_TEENSY_4) +const uint8_t ADC::sc1a2channelADC1[]= { // new version, gives directly the pin number + 21, 0, 0, 26, 27, 19, 18, 14, 15, 0, 0, 17, 16, 22, // 0-13 + 23, 20, 0, 0, 0, 0, 0, 0, //14-21 + 0, 0, 0, 0, 0, 0 //22-27 +}; #endif diff --git a/ADC_Module.cpp b/ADC_Module.cpp index bf10119..a14ed4d 100644 --- a/ADC_Module.cpp +++ b/ADC_Module.cpp @@ -58,7 +58,11 @@ ADC_Module::ADC_Module(uint8_t ADC_number, , PDB0_CHnC1(ADC_num? PDB0_CH1C1 : PDB0_CH0C1) #endif #if defined(ADC_TEENSY_4) - , IRQ_ADC(ADC_num? IRQ_NUMBER_t::IRQ_ADC2 : IRQ_NUMBER_t::IRQ_ADC1) + , XBAR_IN(ADC_num? XBARA1_IN_QTIMER4_TIMER3 : XBARA1_IN_QTIMER4_TIMER0) + , XBAR_OUT(ADC_num? XBARA1_OUT_ADC_ETC_TRIG10 : XBARA1_OUT_ADC_ETC_TRIG00) + , QTIMER4_INDEX(ADC_num? 3 : 0) + , ADC_ETC_TRIGGER_INDEX(ADC_num? 4 : 0) + , IRQ_ADC(ADC_num? IRQ_NUMBER_t::IRQ_ADC2 : IRQ_NUMBER_t::IRQ_ADC1) #elif ADC_NUM_ADCS==2 // IRQ_ADC0 and IRQ_ADC1 aren't consecutive in Teensy 3.6 // fix by SB, https://github.com/pedvide/ADC/issues/19 @@ -1473,3 +1477,104 @@ uint32_t ADC_Module::getPDBFrequency() { } #endif + +#if ADC_USE_TIMER && !ADC_USE_PDB +#if defined(ADC_TEENSY_4) // only supported by Teensy 4... +// try to use some teensy core functions... +// mainly out of pwm.c +extern "C" { + extern void xbar_connect(unsigned int input, unsigned int output); + extern void quadtimer_init(IMXRT_TMR_t *p); + extern void quadtimerWrite(IMXRT_TMR_t *p, unsigned int submodule, uint16_t val); + extern void quadtimerFrequency(IMXRT_TMR_t *p, unsigned int submodule, float frequency); +} + +void ADC_Module::startTimer(uint32_t freq) { + // First lets setup the XBAR + CCM_CCGR2 |= CCM_CCGR2_XBAR1(CCM_CCGR_ON); //turn clock on for xbara1 + xbar_connect(XBAR_IN, XBAR_OUT); + + // Update the ADC + uint8_t adc_pin_channel = adc_regs.HC0 & 0x1f; // remember the trigger that was set + setHardwareTrigger(); // set the hardware trigger + adc_regs.HC0 = (adc_regs.HC0 & ~0x1f) | 16; // ADC_ETC channel remember other states... + singleMode(); // make sure continuous is turned off as you want the trigger to di it. + + // setup adc_etc - BUGBUG have not used the preset values yet. + if ( IMXRT_ADC_ETC.CTRL & ADC_ETC_CTRL_SOFTRST) {// SOFTRST + // Soft reset + atomic::clearBitFlag(IMXRT_ADC_ETC.CTRL, ADC_ETC_CTRL_SOFTRST); + delay(5); // give some time to be sure it is init + } + if (ADC_num == 0) { // BUGBUG - in real code, should probably know we init ADC or not.. + IMXRT_ADC_ETC.CTRL |= + (ADC_ETC_CTRL_TSC_BYPASS | ADC_ETC_CTRL_DMA_MODE_SEL | ADC_ETC_CTRL_TRIG_ENABLE(1 << ADC_ETC_TRIGGER_INDEX)); // 0x40000001; // start with trigger 0 + IMXRT_ADC_ETC.TRIG[ADC_ETC_TRIGGER_INDEX].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(0); // chainlength -1 only us + IMXRT_ADC_ETC.TRIG[ADC_ETC_TRIGGER_INDEX].CHAIN_1_0 = + ADC_ETC_TRIG_CHAIN_IE0(1) /*| ADC_ETC_TRIG_CHAIN_B2B0 */ + | ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel) ; + + if (interrupts_enabled) { + // Not sure yet? + } + if (adc_regs.GC && ADC_GC_DMAEN) { + IMXRT_ADC_ETC.DMA_CTRL |= ADC_ETC_DMA_CTRL_TRIQ_ENABLE(ADC_ETC_TRIGGER_INDEX); + } + } else { + // This is our second one... Try second trigger? + // Remove the BYPASS? + IMXRT_ADC_ETC.CTRL &= ~(ADC_ETC_CTRL_TSC_BYPASS); // 0x40000001; // start with trigger 0 + IMXRT_ADC_ETC.CTRL |= ADC_ETC_CTRL_DMA_MODE_SEL | ADC_ETC_CTRL_TRIG_ENABLE(1 << ADC_ETC_TRIGGER_INDEX); // Add trigger + IMXRT_ADC_ETC.TRIG[ADC_ETC_TRIGGER_INDEX].CTRL = ADC_ETC_TRIG_CTRL_TRIG_CHAIN(0); // chainlength -1 only us + IMXRT_ADC_ETC.TRIG[ADC_ETC_TRIGGER_INDEX].CHAIN_1_0 = + ADC_ETC_TRIG_CHAIN_IE0(1) /*| ADC_ETC_TRIG_CHAIN_B2B0 */ + | ADC_ETC_TRIG_CHAIN_HWTS0(1) | ADC_ETC_TRIG_CHAIN_CSEL0(adc_pin_channel) ; + + if (adc_regs.GC && ADC_GC_DMAEN) { + IMXRT_ADC_ETC.DMA_CTRL |= ADC_ETC_DMA_CTRL_TRIQ_ENABLE(ADC_ETC_TRIGGER_INDEX); + } + } + + // Now init the QTimer. + // Extracted from quadtimer_init in pwm.c but only the one channel... + // Maybe see if we have to do this every time we call this. But how often is that? + IMXRT_TMR4.CH[QTIMER4_INDEX].CTRL = 0; // stop timer + IMXRT_TMR4.CH[QTIMER4_INDEX].CNTR = 0; + IMXRT_TMR4.CH[QTIMER4_INDEX].SCTRL = TMR_SCTRL_OEN | TMR_SCTRL_OPS | TMR_SCTRL_VAL | TMR_SCTRL_FORCE; + IMXRT_TMR4.CH[QTIMER4_INDEX].CSCTRL = TMR_CSCTRL_CL1(1) | TMR_CSCTRL_ALT_LOAD; + // COMP must be less than LOAD - otherwise output is always low + IMXRT_TMR4.CH[QTIMER4_INDEX].LOAD = 24000; // low time (65537 - x) - + IMXRT_TMR4.CH[QTIMER4_INDEX].COMP1 = 0; // high time (0 = always low, max = LOAD-1) + IMXRT_TMR4.CH[QTIMER4_INDEX].CMPLD1 = 0; + IMXRT_TMR4.CH[QTIMER4_INDEX].CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(8) | + TMR_CTRL_LENGTH | TMR_CTRL_OUTMODE(6); + + quadtimerFrequency(&IMXRT_TMR4, QTIMER4_INDEX, freq); + quadtimerWrite(&IMXRT_TMR4, QTIMER4_INDEX, 5); + +} + +//! Stop the PDB +void ADC_Module::stopTimer() { + quadtimerWrite(&IMXRT_TMR4, QTIMER4_INDEX, 0); + setSoftwareTrigger(); + + +} + +//! Return the PDB's frequency +uint32_t ADC_Module::getTimerFrequency() { + // Can I reverse the calculations of quad timer set frequency? + uint32_t high = IMXRT_TMR4.CH[QTIMER4_INDEX].CMPLD1; + uint32_t low = 65537 - IMXRT_TMR4.CH[QTIMER4_INDEX].LOAD; + uint32_t highPlusLow = high + low; // + if (highPlusLow == 0) return 0; // + + uint8_t pcs = (IMXRT_TMR4.CH[QTIMER4_INDEX].CTRL >> 9) & 0x7; + uint32_t freq = (F_BUS_ACTUAL >> pcs)/highPlusLow; + //Serial.printf("ADC_Module::getTimerFrequency H:%u L:%u H+L=%u pcs:%u freq:%u\n", high, low, highPlusLow, pcs, freq); + return freq; +} + +#endif // Teensy 4 +#endif // ADC_USE_TIMER \ No newline at end of file diff --git a/ADC_Module.h b/ADC_Module.h index 75432cd..d69561e 100644 --- a/ADC_Module.h +++ b/ADC_Module.h @@ -501,7 +501,7 @@ class ADC_Module { //////////// PDB //////////////// - //// Only works for Teensy 3.0 and 3.1, not LC (it doesn't have PDB) + //// Only works for Teensy 3.x not LC nor tensy 4.0 (they don't have PDB) #if ADC_USE_PDB //! Start PDB triggering the ADC at the frequency @@ -518,6 +518,37 @@ class ADC_Module { uint32_t getPDBFrequency(); #endif + //////////// TIMER //////////////// + //// Only works for Teensy T3.x and T4 (not LC) on T3.x (If USE_PDB is defined just calls back to PDB) + #if ADC_USE_TIMER + #if ADC_USE_PDB + void startTimer(uint32_t freq) __attribute__((always_inline)) { startPDB(freq); } + + //! Stop the PDB + void stopTimer() __attribute__((always_inline)) { stopPDB(); } + + //! Return the PDB's frequency + uint32_t getTimerFrequency() __attribute__((always_inline)) { return getPDBFrequency(); } + + + #else + //! Start a timer to trigger the ADC at the frequency + /** Call startSingleRead or startSingleDifferential on the pin that you want to measure before calling this function. + * See the example adc_pdb.ino. + * \param freq is the frequency of the ADC conversion, it can't be lower that 1 Hz + */ + void startTimer(uint32_t freq); + + //! Stop the timer + void stopTimer(); + + //! Return the timer's frequency + uint32_t getTimerFrequency(); + #endif + + #endif + + //////// OTHER STUFF /////////// @@ -670,7 +701,12 @@ class ADC_Module { #if ADC_USE_PDB reg PDB0_CHnC1; // PDB channel 0 or 1 #endif - + #ifdef ADC_TEENSY_4 + uint8_t XBAR_IN; + uint8_t XBAR_OUT; + uint8_t QTIMER4_INDEX; + uint8_t ADC_ETC_TRIGGER_INDEX; + #endif const IRQ_NUMBER_t IRQ_ADC; // IRQ number protected: diff --git a/AnalogBufferDMA.cpp b/AnalogBufferDMA.cpp new file mode 100644 index 0000000..f23d227 --- /dev/null +++ b/AnalogBufferDMA.cpp @@ -0,0 +1,240 @@ +/* Teensy 3.x, LC, 4.0 ADC library + https://github.com/pedvide/ADC + Copyright (c) 2019 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. +*/ +#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 +#define SOURCE_ADC_1 ADC1_RA +#define DMAMUX_ADC_1 DMAMUX_SOURCE_ADC1 +#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) { + _dmasettings_adc[0].source((volatile uint16_t&)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0)); + _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 + + _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 + + _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); + _dmachannel_adc.source((volatile uint16_t&)((adc_num == 1) ? SOURCE_ADC_1 : SOURCE_ADC_0)); + _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) { + _activeObjectPerADC[1] = this; + _dmachannel_adc.attachInterrupt(&adc_1_dmaISR); + _dmachannel_adc.triggerAtHardwareEvent(DMAMUX_ADC_1); // start DMA channel when ADC finishes a conversion + } 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->startContinuous(adc_num); + adc->enableDMA(adc_num); + +#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 +} diff --git a/AnalogBufferDMA.h b/AnalogBufferDMA.h new file mode 100644 index 0000000..eaa29c3 --- /dev/null +++ b/AnalogBufferDMA.h @@ -0,0 +1,78 @@ +/* Teensy 3.x, LC, 4.0 ADC library + * https://github.com/pedvide/ADC + * Copyright (c) 2019 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. + */ + +#ifndef ANALOGBUFFERDMA_H +#define ANALOGBUFFERDMA_H + +#include // for digitalWrite +#include "DMAChannel.h" +#include "ADC.h" +// lets wrap some of our Dmasettings stuff into helper class +class AnalogBufferDMA { + // keep our settings and the like: +public: // At least temporary to play with dma settings. +#ifndef KINETISL + DMASetting _dmasettings_adc[2]; +#endif + DMAChannel _dmachannel_adc; + + static AnalogBufferDMA *_activeObjectPerADC[2]; + static void adc_0_dmaISR(); + static void adc_1_dmaISR(); + void processADC_DMAISR(); +public: + + AnalogBufferDMA(volatile uint16_t *buffer1, uint16_t buffer1_count, + volatile uint16_t *buffer2 = nullptr, uint16_t buffer2_count = 0) : + _buffer1(buffer1), _buffer1_count(buffer1_count), _buffer2(buffer2), _buffer2_count(buffer2_count) {}; + + void init(ADC *adc, int8_t adc_num = -1); + + void stopOnCompletion(bool stop_on_complete); + inline bool stopOnCompletion(void) {return _stop_on_completion;} + bool clearCompletion(); + inline volatile uint16_t *bufferLastISRFilled() {return (!_buffer2 || (_interrupt_count & 1))? _buffer1 : _buffer2;} + inline uint16_t bufferCountLastISRFilled() {return (!_buffer2 || (_interrupt_count & 1))? _buffer1_count : _buffer2_count;} + inline uint32_t interruptCount() {return _interrupt_count;} + inline uint32_t interruptDeltaTime() {return _interrupt_delta_time;} + inline bool interrupted() {return _interrupt_delta_time != 0;} + inline void clearInterrupt() {_interrupt_delta_time = 0;} + inline void userData(uint32_t new_data) {_user_data = new_data;} + inline uint32_t userData(void) {return _user_data;} +protected: + + volatile uint32_t _interrupt_count = 0; + volatile uint32_t _interrupt_delta_time; + volatile uint32_t _last_isr_time; + + volatile uint16_t *_buffer1; + uint16_t _buffer1_count; + volatile uint16_t *_buffer2; + uint16_t _buffer2_count; + uint32_t _user_data = 0; + bool _stop_on_completion = false; +}; + +#endif diff --git a/examples/adc_dma/adc_dma.ino b/examples/adc_dma/adc_dma.ino new file mode 100644 index 0000000..97169f4 --- /dev/null +++ b/examples/adc_dma/adc_dma.ino @@ -0,0 +1,144 @@ +/* Example for using DMA with ADC + This example uses DMA object to do the sampling. It does not use a timer so it runs + at whatever speed the ADC will run at with current settings. + + It should work for Teensy LC, 3.x and T4 + + DMA: using AnalogBufferDMA with two buffers, this runs in continuous mode and when one buffer fills + an interrupt is signaled, which sets flag saying it has data, which this test application + scans the data, and computes things like a minimum, maximum, average values and an RMS value. + For the RMS it keeps the average from the previous set of data. +*/ + + +#include +#include + + +// This version uses both ADC1 and ADC2 +#if defined(KINETISL) +const int readPin_adc_0 = A0; +#elif defined(KINETISK) +const int readPin_adc_0 = A0; +const int readPin_adc_1 = A2; +#else +const int readPin_adc_0 = A0; +const int readPin_adc_1 = 26; +#endif + +ADC *adc = new ADC(); // adc object +const uint32_t initial_average_value = 2048; + +// Going to try two buffers here using 2 dmaSettings and a DMAChannel +#ifdef KINETISL +const uint32_t buffer_size = 500; +#else +const uint32_t buffer_size = 1600; +#endif + +DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size]; +DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[buffer_size]; +AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size, dma_adc_buff2, buffer_size); + +#if ADC_NUM_ADCS>1 +DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff1[buffer_size]; +DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc2_buff2[buffer_size]; +AnalogBufferDMA abdma2(dma_adc2_buff1, buffer_size, dma_adc2_buff2, buffer_size); +#endif + +void setup() { + while (!Serial && millis() < 5000) ; + + pinMode(LED_BUILTIN, OUTPUT); + pinMode(readPin_adc_0, INPUT); //pin 23 single ended +#if ADC_NUM_ADCS>1 + pinMode(readPin_adc_1, INPUT); +#endif + + Serial.begin(9600); + Serial.println("Setup ADC_0"); + // reference can be ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_1V2 (not for Teensy LC) or ADC_REF_EXT. + //adc->setReference(ADC_REFERENCE::REF_1V2, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2 + + adc->setAveraging(8); // set number of averages + adc->setResolution(12); // set bits of resolution + + + // always call the compare functions after changing the resolution! + //adc->enableCompare(1.0/3.3*adc->getMaxValue(ADC_0), 0, ADC_0); // measurement will be ready if value < 1.0V + //adc->enableCompareRange(1.0*adc->getMaxValue(ADC_1)/3.3, 2.0*adc->getMaxValue(ADC_1)/3.3, 0, 1, ADC_1); // ready if value lies out of [1.0,2.0] V + + // enable DMA and interrupts + Serial.println("before enableDMA"); Serial.flush(); + + + // setup a DMA Channel. + // Now lets see the different things that RingbufferDMA setup for us before + abdma1.init(adc, ADC_0); + abdma1.userData(initial_average_value); // save away initial starting average +#if ADC_NUM_ADCS>1 + Serial.println("Setup ADC_1"); + adc->setAveraging(8, ADC_1); // set number of averages + adc->setResolution(12, ADC_1); // set bits of resolution + abdma2.init(adc, ADC_1); + abdma2.userData(initial_average_value); // save away initial starting average + adc->adc1->startContinuous(readPin_adc_1); +#endif + + // Start the dma operation.. + adc->adc0->startContinuous(readPin_adc_0); + + Serial.println("End Setup"); +} + +char c = 0; + + +void loop() { + + // Maybe only when both have triggered? +#if ADC_NUM_ADCS>1 + if ( abdma1.interrupted() && abdma2.interrupted()) { + if (abdma1.interrupted()) ProcessAnalogData(&abdma1, 0); + if (abdma2.interrupted()) ProcessAnalogData(&abdma2, 1); + Serial.println(); + } +#else + if ( abdma1.interrupted()) { + ProcessAnalogData(&abdma1, 0); + Serial.println(); + } +#endif + +} + +void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) { + uint32_t sum_values = 0; + uint16_t min_val = 0xffff; + uint16_t max_val = 0; + + uint32_t average_value = pabdma->userData(); + + volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled(); + volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled(); + + float sum_delta_sq = 0.0; + if ((uint32_t)pbuffer >= 0x20200000u) arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1)); + while (pbuffer < end_pbuffer) { + if (*pbuffer < min_val) min_val = *pbuffer; + if (*pbuffer > max_val) max_val = *pbuffer; + sum_values += *pbuffer; + int delta_from_center = (int) * pbuffer - average_value; + sum_delta_sq += delta_from_center * delta_from_center; + + pbuffer++; + } + + int rms = sqrt(sum_delta_sq / buffer_size); + average_value = sum_values / buffer_size; + Serial.printf(" %d - %u(%u): %u <= %u <= %u %d ", adc_num, pabdma->interruptCount(), pabdma->interruptDeltaTime(), min_val, + average_value, max_val, rms); + pabdma->clearInterrupt(); + + pabdma->userData(average_value); +} diff --git a/examples/adc_timer/adc_timer.ino b/examples/adc_timer/adc_timer.ino new file mode 100644 index 0000000..f6b7ba0 --- /dev/null +++ b/examples/adc_timer/adc_timer.ino @@ -0,0 +1,201 @@ +/* Example for triggering the ADC with Timer + Valid for the current Teensy 3.x and 4.0. + + On Teensy 3.x this uses the PDB timer, and should work the same as the example adc_pdb.ino, but + has been extended some to add additional command to read in a whole buffer of readings, using the + timer. + + On Teensy 4, this uses one or two of the unused QTimers. + + Setting it up: The variables readPin must be defined for a pin that is valid for the first (or only) + ADC. If the processor has a second ADC and is enabled, than readPin2 must be configured to be a pin + that is valid on the second ADC. + + Example usage: + Start the timers at some frequency: s 3000 + get a single read from the ADC(s): v + print out the actual frequency: p + Read in a whole buffer and print out data: t + Stop the timers: s +*/ + + +#include +#include + +const int readPin = A0; // ADC0 +#define USE_ADC_0 +#define USE_ADC_1 + +#if ADC_NUM_ADCS>1 +const int readPin2 = A2; // ADC1 +#endif + +ADC *adc = new ADC(); // adc object; + +#define BUFFER_SIZE 500 + +uint16_t buffer_ADC_0[BUFFER_SIZE]; +uint16_t buffer_adc_0_count = 0xffff; +uint32_t delta_time_adc_0 = 0; +uint16_t buffer_ADC_1[BUFFER_SIZE]; +uint16_t buffer_adc_1_count = 0xffff; +uint32_t delta_time_adc_1 = 0; + +elapsedMillis timed_read_elapsed; + +void setup() { + + pinMode(LED_BUILTIN, OUTPUT); + pinMode(readPin, INPUT); + + Serial.begin(9600); + while (!Serial && millis() < 5000) ; // wait up to 5 seconds for serial monitor. + + Serial.println("Begin setup"); + + ///// ADC0 //// + adc->setAveraging(1); // set number of averages + adc->setResolution(8); // set bits of resolution + adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED); // change the conversion speed + adc->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED); // change the sampling speed + + ////// ADC1 ///// +#if ADC_NUM_ADCS>1 + pinMode(readPin2, INPUT); + adc->setAveraging(1, ADC_1); // set number of averages + adc->setResolution(8, ADC_1); // set bits of resolution + adc->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_HIGH_SPEED, ADC_1); // change the conversion speed + adc->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_HIGH_SPEED, ADC_1); // change the sampling speed +#endif + + Serial.println("End setup"); + + Serial.println("Enter a command such as: s 3000 to start doing something"); + +} + +char c = 0; +int value; +int value2; + +void loop() { + + if (Serial.available()) { + c = Serial.read(); + if (c == 'v') { // value + Serial.print("Value ADC0: "); + value = (uint16_t)adc->readSingle(ADC_0); // the unsigned is necessary for 16 bits, otherwise values larger than 3.3/2 V are negative! + Serial.printf("%d = ", value); + Serial.println(value * 3.3 / adc->getMaxValue(ADC_0), DEC); +#if ADC_NUM_ADCS>1 && defined(USE_ADC_1) + Serial.print("Value ADC1: "); + value2 = (uint16_t)adc->readSingle(ADC_1); // the unsigned is necessary for 16 bits, otherwise values larger than 3.3/2 V are negative! + Serial.printf("%d = ", value2); + Serial.println(value2 * 3.3 / adc->getMaxValue(ADC_1), DEC); +#endif + } else if (c == 's') { // start Timer, before pressing enter write the frequency in Hz + uint32_t freq = Serial.parseInt(); + if (freq == 0) { + Serial.println("Stop Timer."); + adc->adc0->stopTimer(); + adc->adc1->stopTimer(); + } + else { + Serial.print("Start Timer with frequency "); + Serial.print(freq); + Serial.println(" Hz."); + adc->adc0->stopTimer(); + adc->adc0->startSingleRead(readPin); // call this to setup everything before the Timer starts, differential is also possible + adc->adc0->enableInterrupts(adc0_isr); + adc->adc0->startTimer(freq); //frequency in Hz +#if ADC_NUM_ADCS>1 && defined(USE_ADC_1) + adc->adc1->stopTimer(); + adc->adc1->startSingleRead(readPin2); // call this to setup everything before the Timer starts + adc->adc1->enableInterrupts(adc1_isr); + adc->adc1->startTimer(freq); //frequency in Hz +#endif + } + } else if (c == 'p') { // print Timer stats + Serial.print("Frequency: "); + Serial.println(adc->adc0->getTimerFrequency()); + } else if (c == 't') { // Lets try a timed read + timed_read_elapsed = 0; + buffer_adc_0_count = 0; + buffer_adc_1_count = 0; + Serial.println("Starting Timed read"); + } + } + + + // Print errors, if any. + if (adc->adc0->fail_flag != ADC_ERROR::CLEAR) { + Serial.print("ADC0: "); Serial.println(getStringADCError(adc->adc0->fail_flag)); + } +#if ADC_NUM_ADCS>1 && defined(USE_ADC_1) + if (adc->adc1->fail_flag != ADC_ERROR::CLEAR) { + Serial.print("ADC1: "); Serial.println(getStringADCError(adc->adc1->fail_flag)); + } +#endif + adc->resetError(); + + // See if we have a timed read test that finished. + if (delta_time_adc_0)printTimedADCInfo(ADC_0, buffer_ADC_0, delta_time_adc_0); + if (delta_time_adc_1)printTimedADCInfo(ADC_1, buffer_ADC_1, delta_time_adc_1); + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN)); + + delay(10); +} + +void printTimedADCInfo(uint8_t adc_num, uint16_t *buffer, uint32_t &delta_time) { + uint32_t min_value = 0xffff; + uint32_t max_value = 0; + uint32_t sum = 0; + for (int i = 0; i < BUFFER_SIZE; i++) { + if (buffer[i] < min_value) min_value = buffer[i]; + if (buffer[i] > max_value) max_value = buffer[i]; + sum += buffer[i]; + } + float average_value = (float)sum / BUFFER_SIZE; // get an average... + float sum_delta_sq = 0; + for (int i = 0; i < BUFFER_SIZE; i++) { + int delta_from_center = (int)buffer[i] - average_value; + sum_delta_sq += delta_from_center * delta_from_center; + } + int rms = sqrt(sum_delta_sq / BUFFER_SIZE); + Serial.printf("ADC:%d delta time:%d freq:%d - min:%d max:%d avg:%d rms:%d\n", adc_num, + delta_time, (1000 * BUFFER_SIZE) / delta_time, + min_value, max_value, (int)average_value, rms); + + delta_time = 0; + +} + + +// Make sure to call readSingle() to clear the interrupt. +void adc0_isr() { + uint16_t adc_val = adc->adc0->readSingle(); + if (buffer_adc_0_count < BUFFER_SIZE) { + buffer_ADC_0[buffer_adc_0_count++] = adc_val; + if (buffer_adc_0_count == BUFFER_SIZE) delta_time_adc_0 = timed_read_elapsed; + } + digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); +#if defined(__IMXRT1062__) // Teensy 4.0 + asm("DSB"); +#endif +} + +#if ADC_NUM_ADCS>1 +void adc1_isr() { + uint16_t adc_val = adc->adc1->readSingle(); + if (buffer_adc_1_count < BUFFER_SIZE) { + buffer_ADC_1[buffer_adc_1_count++] = adc_val; + if (buffer_adc_1_count == BUFFER_SIZE) delta_time_adc_1 = timed_read_elapsed; + } +#if defined(__IMXRT1062__) // Teensy 4.0 + asm("DSB"); +#endif + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); +} +#endif diff --git a/examples/adc_timer_dma/adc_timer_dma.ino b/examples/adc_timer_dma/adc_timer_dma.ino new file mode 100644 index 0000000..60fb538 --- /dev/null +++ b/examples/adc_timer_dma/adc_timer_dma.ino @@ -0,0 +1,216 @@ +/* Example for triggering the ADC with Timer using DMA instead of interrupts + Valid for the current Teensy 3.x and 4.0. + + + Timers: + On Teensy 3.x this uses the PDB timer. + + On Teensy 4, this uses one or two of the unused QTimers. + + Setting it up: The variables readPin must be defined for a pin that is valid for the first (or only) + ADC. If the processor has a second ADC and is enabled, than readPin2 must be configured to be a pin + that is valid on the second ADC. + + DMA: using AnalogBufferDMA with two buffers, this runs in continuous mode and when one buffer fills + an interrupt is signaled, which sets flag saying it has data, which this test application + scans the data, and computes things like a minimum, maximum, average values and an RMS value. + For the RMS it keeps the average from the previous set of data. +*/ + + +#include +#include +#include + +//#define PRINT_DEBUG_INFO + +#ifdef KINETISL +#error "Sorry: This example will not run on Teensy LC" +#endif + +// This version uses both ADC1 and ADC2 +#ifdef KINETISK +const int readPin_adc_0 = A0; +const int readPin_adc_1 = A2; +#else +const int readPin_adc_0 = A0; +const int readPin_adc_1 = 26; +#endif + +ADC *adc = new ADC(); // adc object +const uint32_t initial_average_value = 2048; + +extern void dumpDMA_structures(DMABaseClass *dmabc); +elapsedMillis elapsed_sinc_last_display; + +// Going to try two buffers here using 2 dmaSettings and a DMAChannel + +const uint32_t buffer_size = 1600; +DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size]; +DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2[buffer_size]; +AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size, dma_adc_buff2, buffer_size); + +#if ADC_NUM_ADCS>1 +DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2_1[buffer_size]; +DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2_2[buffer_size]; +AnalogBufferDMA abdma2(dma_adc_buff2_1, buffer_size, dma_adc_buff2_2, buffer_size); +#endif + +void setup() { + while (!Serial && millis() < 5000) ; + + pinMode(LED_BUILTIN, OUTPUT); + pinMode(readPin_adc_0, INPUT); // Not sure this does anything for us +#if ADC_NUM_ADCS>1 + pinMode(readPin_adc_1, INPUT); +#endif + Serial.begin(9600); + Serial.println("Setup both ADCs"); + // reference can be ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_1V2 (not for Teensy LC) or ADC_REF_EXT. + //adc->setReference(ADC_REFERENCE::REF_1V2, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2 + + // Setup both ADCs + adc->setAveraging(8); // set number of averages + adc->setResolution(12); // set bits of resolution +#if ADC_NUM_ADCS>1 + adc->setAveraging(8, ADC_1); // set number of averages + adc->setResolution(12, ADC_1); // set bits of resolution +#endif + + // always call the compare functions after changing the resolution! + //adc->enableCompare(1.0/3.3*adc->getMaxValue(ADC_0), 0, ADC_0); // measurement will be ready if value < 1.0V + //adc->enableCompareRange(1.0*adc->getMaxValue(ADC_1)/3.3, 2.0*adc->getMaxValue(ADC_1)/3.3, 0, 1, ADC_1); // ready if value lies out of [1.0,2.0] V + + // enable DMA and interrupts + //Serial.println("before enableDMA"); Serial.flush(); + + + // setup a DMA Channel. + // Now lets see the different things that RingbufferDMA setup for us before + abdma1.init(adc, ADC_0/*, DMAMUX_SOURCE_ADC_ETC*/); + abdma2.userData(initial_average_value); // save away initial starting average +#if ADC_NUM_ADCS>1 + abdma2.init(adc, ADC_1/*, DMAMUX_SOURCE_ADC_ETC*/); + abdma2.userData(initial_average_value); // save away initial starting average +#endif + //Serial.println("After enableDMA"); Serial.flush(); + + // Start the dma operation.. + adc->adc0->startSingleRead(readPin_adc_0); // call this to setup everything before the Timer starts, differential is also possible + adc->adc0->startTimer(3000); //frequency in Hz + + // Start the dma operation.. +#if ADC_NUM_ADCS>1 + adc->adc1->startSingleRead(readPin_adc_1); // call this to setup everything before the Timer starts, differential is also possible + adc->adc1->startTimer(3000); //frequency in Hz +#endif + + print_debug_information(); + + Serial.println("End Setup"); + elapsed_sinc_last_display = 0; +} + +void loop() { + + // Maybe only when both have triggered? +#if ADC_NUM_ADCS>1 + if ( abdma1.interrupted() && (abdma2.interrupted())) { + if ( abdma1.interrupted()) { + ProcessAnalogData(&abdma1, 0); + } + if ( abdma2.interrupted()) { + ProcessAnalogData(&abdma2, 1); + } + Serial.println(); + elapsed_sinc_last_display = 0; + } +#else + if ( abdma1.interrupted()) { + ProcessAnalogData(&abdma1, 0); + Serial.println(); + elapsed_sinc_last_display = 0; + } +#endif + if (elapsed_sinc_last_display > 5000) { + // Nothing in 5 seconds, show a heart beat. + digitalWriteFast(13, HIGH); + delay(250); + digitalWriteFast(13, LOW); + delay(250); + digitalWriteFast(13, HIGH); + delay(250); + digitalWriteFast(13, LOW); + elapsed_sinc_last_display = 0; + } +} + +void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) { + uint32_t sum_values = 0; + uint16_t min_val = 0xffff; + uint16_t max_val = 0; + + uint32_t average_value = pabdma->userData(); + + volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled(); + volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled(); + + float sum_delta_sq = 0.0; + if ((uint32_t)pbuffer >= 0x20200000u) arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1)); + while (pbuffer < end_pbuffer) { + if (*pbuffer < min_val) min_val = *pbuffer; + if (*pbuffer > max_val) max_val = *pbuffer; + sum_values += *pbuffer; + int delta_from_center = (int) * pbuffer - average_value; + sum_delta_sq += delta_from_center * delta_from_center; + + pbuffer++; + } + + int rms = sqrt(sum_delta_sq / buffer_size); + average_value = sum_values / buffer_size; + Serial.printf(" %d - %u(%u): %u <= %u <= %u %d ", adc_num, pabdma->interruptCount(), pabdma->interruptDeltaTime(), min_val, + average_value, max_val, rms); + pabdma->clearInterrupt(); + + pabdma->userData(average_value); +} + +void print_debug_information() +{ +#ifdef PRINT_DEBUG_INFO + // Lets again try dumping lots of data. + Serial.println("\n*** DMA structures for ADC_0 ***"); + dumpDMA_structures(&(abdma1._dmachannel_adc)); + dumpDMA_structures(&(abdma1._dmasettings_adc[0])); + dumpDMA_structures(&(abdma1._dmasettings_adc[1])); + Serial.println("\n*** DMA structures for ADC_1 ***"); + dumpDMA_structures(&(abdma2._dmachannel_adc)); + dumpDMA_structures(&(abdma2._dmasettings_adc[0])); + dumpDMA_structures(&(abdma2._dmasettings_adc[1])); + +#if defined(__IMXRT1062__) + + Serial.println("\n*** ADC and ADC_ETC ***"); + Serial.printf("ADC1: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", IMXRT_ADC1.HC0, IMXRT_ADC1.HS, IMXRT_ADC1.CFG, IMXRT_ADC1.GC, IMXRT_ADC1.GS); + Serial.printf("ADC2: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", IMXRT_ADC2.HC0, IMXRT_ADC2.HS, IMXRT_ADC2.CFG, IMXRT_ADC2.GC, IMXRT_ADC2.GS); + Serial.printf("ADC_ETC: CTRL:%x DONE0_1:%x DONE2_ERR:%x DMA: %x\n", IMXRT_ADC_ETC.CTRL, + IMXRT_ADC_ETC.DONE0_1_IRQ, IMXRT_ADC_ETC.DONE2_ERR_IRQ, IMXRT_ADC_ETC.DMA_CTRL); + for (uint8_t trig = 0; trig < 8; trig++) { + Serial.printf(" TRIG[%d] CTRL: %x CHAIN_1_0:%x\n", + trig, IMXRT_ADC_ETC.TRIG[trig].CTRL, IMXRT_ADC_ETC.TRIG[trig].CHAIN_1_0); + } +#endif +#endif +} + +#ifdef PRINT_DEBUG_INFO +void dumpDMA_structures(DMABaseClass *dmabc) +{ + 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); +} +#endif \ No newline at end of file diff --git a/examples/adc_timer_dma_oneshot/adc_timer_dma_oneshot.ino b/examples/adc_timer_dma_oneshot/adc_timer_dma_oneshot.ino new file mode 100644 index 0000000..3c61ba7 --- /dev/null +++ b/examples/adc_timer_dma_oneshot/adc_timer_dma_oneshot.ino @@ -0,0 +1,226 @@ +/* Example for triggering the ADC with Timer using DMA instead of interrupts + Valid for the current Teensy 3.x and 4.0. + + This example is pretty much the same as adc_timer_dma.ino except only one buffer is added to + each of our DMA buffer objects, so instead of running continuously in round robin format it + stops after each buffer is filled and waits for us to signal to run again. We do that by + entering something at the keyboard. + + Timers: + On Teensy 3.x this uses the PDB timer. + + On Teensy 4, this uses one or two of the unused QTimers. + + Setting it up: The variables readPin must be defined for a pin that is valid for the first (or only) + ADC. If the processor has a second ADC and is enabled, than readPin2 must be configured to be a pin + that is valid on the second ADC. + + DMA: using AnalogBufferDMA with two buffers, this runs in continuous mode and when one buffer fills + an interrupt is signaled, which sets flag saying it has data, which this test application + scans the data, and computes things like a minimum, maximum, average values and an RMS value. + For the RMS it keeps the average from the previous set of data. +*/ + + +#include +#include +#include + +//#define PRINT_DEBUG_INFO + +#ifdef KINETISL +#error "Sorry: This example will not run on Teensy LC" +#endif + +// This version uses both ADC1 and ADC2 +#ifdef KINETISK +const int readPin_adc_0 = A0; +const int readPin_adc_1 = A2; +#else +const int readPin_adc_0 = A0; +const int readPin_adc_1 = 26; +#endif + +ADC *adc = new ADC(); // adc object +const uint32_t initial_average_value = 2048; + +extern void dumpDMA_structures(DMABaseClass *dmabc); +elapsedMillis elapsed_sinc_last_display; + +// Only providing one buffer so for each one so should stop after it finishing +const uint32_t buffer_size = 1600; +DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff1[buffer_size]; +AnalogBufferDMA abdma1(dma_adc_buff1, buffer_size); + +#if ADC_NUM_ADCS>1 +DMAMEM static volatile uint16_t __attribute__((aligned(32))) dma_adc_buff2_1[buffer_size]; +AnalogBufferDMA abdma2(dma_adc_buff2_1, buffer_size); +#endif + +void setup() { + while (!Serial && millis() < 5000) ; + + pinMode(LED_BUILTIN, OUTPUT); + pinMode(readPin_adc_0, INPUT); // Not sure this does anything for us +#if ADC_NUM_ADCS>1 + pinMode(readPin_adc_1, INPUT); +#endif + Serial.begin(9600); + Serial.println("Setup both ADCs"); + // reference can be ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_1V2 (not for Teensy LC) or ADC_REF_EXT. + //adc->setReference(ADC_REFERENCE::REF_1V2, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2 + + // Setup both ADCs + adc->setAveraging(8); // set number of averages + adc->setResolution(12); // set bits of resolution +#if ADC_NUM_ADCS>1 + adc->setAveraging(8, ADC_1); // set number of averages + adc->setResolution(12, ADC_1); // set bits of resolution +#endif + + // always call the compare functions after changing the resolution! + //adc->enableCompare(1.0/3.3*adc->getMaxValue(ADC_0), 0, ADC_0); // measurement will be ready if value < 1.0V + //adc->enableCompareRange(1.0*adc->getMaxValue(ADC_1)/3.3, 2.0*adc->getMaxValue(ADC_1)/3.3, 0, 1, ADC_1); // ready if value lies out of [1.0,2.0] V + + // enable DMA and interrupts + Serial.println("before enableDMA"); Serial.flush(); + + + // setup a DMA Channel. + // Now lets see the different things that RingbufferDMA setup for us before + abdma1.init(adc, ADC_0/*, DMAMUX_SOURCE_ADC_ETC*/); + abdma1.userData(initial_average_value); // save away initial starting average +#if ADC_NUM_ADCS>1 + abdma2.init(adc, ADC_1/*, DMAMUX_SOURCE_ADC_ETC*/); + abdma2.userData(initial_average_value); // save away initial starting average +#endif + Serial.println("After enableDMA"); Serial.flush(); + + // Start the dma operation.. + adc->adc0->startSingleRead(readPin_adc_0); // call this to setup everything before the Timer starts, differential is also possible + adc->adc0->startTimer(3000); //frequency in Hz + + // Start the dma operation.. +#if ADC_NUM_ADCS>1 + adc->adc1->startSingleRead(readPin_adc_1); // call this to setup everything before the Timer starts, differential is also possible + adc->adc1->startTimer(3000); //frequency in Hz +#endif + + print_debug_information(); + + + Serial.println("End Setup"); + elapsed_sinc_last_display = 0; +} + +void loop() { + + // Maybe only when both have triggered? +#if ADC_NUM_ADCS>1 + if ( abdma1.interrupted() && (abdma2.interrupted())) { + if ( abdma1.interrupted()) { + ProcessAnalogData(&abdma1, 0); + } + if ( abdma2.interrupted()) { + ProcessAnalogData(&abdma2, 1); + } + Serial.println(); + elapsed_sinc_last_display = 0; + } +#else + if ( abdma1.interrupted()) { + ProcessAnalogData(&abdma1, 0); + Serial.println(); + elapsed_sinc_last_display = 0; + } +#endif + + if (Serial.available()) { + while (Serial.read() != -1) ; // get rid of everything... + abdma1.clearCompletion(); // run it again +#if ADC_NUM_ADCS>1 + abdma2.clearCompletion(); // run it again... +#endif + } + if (elapsed_sinc_last_display > 5000) { + // Nothing in 5 seconds, show a heart beat. + digitalWriteFast(13, HIGH); + delay(250); + digitalWriteFast(13, LOW); + delay(250); + digitalWriteFast(13, HIGH); + delay(250); + digitalWriteFast(13, LOW); + elapsed_sinc_last_display = 0; + } +} + +void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) { + uint32_t sum_values = 0; + uint16_t min_val = 0xffff; + uint16_t max_val = 0; + + uint32_t average_value = pabdma->userData(); + + volatile uint16_t *pbuffer = pabdma->bufferLastISRFilled(); + volatile uint16_t *end_pbuffer = pbuffer + pabdma->bufferCountLastISRFilled(); + + float sum_delta_sq = 0.0; + if ((uint32_t)pbuffer >= 0x20200000u) arm_dcache_delete((void*)pbuffer, sizeof(dma_adc_buff1)); + while (pbuffer < end_pbuffer) { + if (*pbuffer < min_val) min_val = *pbuffer; + if (*pbuffer > max_val) max_val = *pbuffer; + sum_values += *pbuffer; + int delta_from_center = (int) * pbuffer - average_value; + sum_delta_sq += delta_from_center * delta_from_center; + + pbuffer++; + } + + int rms = sqrt(sum_delta_sq / buffer_size); + average_value = sum_values / buffer_size; + Serial.printf(" %d - %u(%u): %u <= %u <= %u %d ", adc_num, pabdma->interruptCount(), pabdma->interruptDeltaTime(), min_val, + average_value, max_val, rms); + pabdma->clearInterrupt(); + + pabdma->userData(average_value); +} + +void print_debug_information() +{ +#ifdef PRINT_DEBUG_INFO + // Lets again try dumping lots of data. + Serial.println("\n*** DMA structures for ADC_0 ***"); + dumpDMA_structures(&(abdma1._dmachannel_adc)); + dumpDMA_structures(&(abdma1._dmasettings_adc[0])); + dumpDMA_structures(&(abdma1._dmasettings_adc[1])); + Serial.println("\n*** DMA structures for ADC_1 ***"); + dumpDMA_structures(&(abdma2._dmachannel_adc)); + dumpDMA_structures(&(abdma2._dmasettings_adc[0])); + dumpDMA_structures(&(abdma2._dmasettings_adc[1])); + +#if defined(__IMXRT1062__) + + Serial.println("\n*** ADC and ADC_ETC ***"); + Serial.printf("ADC1: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", IMXRT_ADC1.HC0, IMXRT_ADC1.HS, IMXRT_ADC1.CFG, IMXRT_ADC1.GC, IMXRT_ADC1.GS); + Serial.printf("ADC2: HC0:%x HS:%x CFG:%x GC:%x GS:%x\n", IMXRT_ADC2.HC0, IMXRT_ADC2.HS, IMXRT_ADC2.CFG, IMXRT_ADC2.GC, IMXRT_ADC2.GS); + Serial.printf("ADC_ETC: CTRL:%x DONE0_1:%x DONE2_ERR:%x DMA: %x\n", IMXRT_ADC_ETC.CTRL, + IMXRT_ADC_ETC.DONE0_1_IRQ, IMXRT_ADC_ETC.DONE2_ERR_IRQ, IMXRT_ADC_ETC.DMA_CTRL); + for (uint8_t trig = 0; trig < 8; trig++) { + Serial.printf(" TRIG[%d] CTRL: %x CHAIN_1_0:%x\n", + trig, IMXRT_ADC_ETC.TRIG[trig].CTRL, IMXRT_ADC_ETC.TRIG[trig].CHAIN_1_0); + } +#endif +#endif +} + +#ifdef PRINT_DEBUG_INFO +void dumpDMA_structures(DMABaseClass *dmabc) +{ + 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); +} +#endif \ No newline at end of file diff --git a/examples/analogReadIntervalTimer/analogReadIntervalTimer.ino b/examples/analogReadIntervalTimer/analogReadIntervalTimer.ino index 11d14b7..d99c10b 100644 --- a/examples/analogReadIntervalTimer/analogReadIntervalTimer.ino +++ b/examples/analogReadIntervalTimer/analogReadIntervalTimer.ino @@ -185,8 +185,12 @@ void timer1_callback(void) { // first: see which pin finished and then save the measurement into the correct buffer void adc0_isr() { - uint8_t pin = ADC::sc1a2channelADC0[ADC0_SC1A&ADC_SC1A_CHANNELS]; // the bits 0-4 of ADC0_SC1A have the channel +#if defined(__IMXRT1062__) // Teensy 4.0 + uint8_t pin = ADC::sc1a2channelADC0[ADC1_HC0&0x1f]; // the bits 0-4 of ADC0_SC1A have the channel +#else + uint8_t pin = ADC::sc1a2channelADC0[ADC0_SC1A&ADC_SC1A_CHANNELS]; // the bits 0-4 of ADC0_SC1A have the channel +#endif // add value to correct buffer if(pin==readPin0) { digitalWriteFast(ledPin+3, HIGH); @@ -214,4 +218,4 @@ void adc0_isr() { //digitalWriteFast(ledPin+2, !digitalReadFast(ledPin+2)); -} +} \ No newline at end of file diff --git a/settings_defines.h b/settings_defines.h index 8e69847..ab281af 100644 --- a/settings_defines.h +++ b/settings_defines.h @@ -107,6 +107,21 @@ #define ADC_USE_PDB (0) #endif +// Use TIMER - Either PDB above or other...? +#if defined(ADC_TEENSY_3_1) // Teensy 3.1 + #define ADC_USE_TIMER (1) +#elif defined(ADC_TEENSY_3_0) // Teensy 3.0 + #define ADC_USE_TIMER (1) +#elif defined(ADC_TEENSY_LC) // Teensy LC + #define ADC_USE_TIMER (0) +#elif defined(ADC_TEENSY_3_5) // Teensy 3.5 + #define ADC_USE_TIMER (1) +#elif defined(ADC_TEENSY_3_6) // Teensy 3.6 + #define ADC_USE_TIMER (1) +#elif defined(ADC_TEENSY_4) // Teensy 4 + #define ADC_USE_TIMER (1) +#endif + // Has internal reference? #if defined(ADC_TEENSY_3_1) // Teensy 3.1 #define ADC_USE_INTERNAL_VREF (1)