From 336d0d0e83a759b899fda19398f1c3ea2b3ae759 Mon Sep 17 00:00:00 2001 From: Kurt Eckhardt Date: Mon, 30 Dec 2019 09:41:47 -0800 Subject: [PATCH 1/5] WIP - Add T4 Timer --- .gitignore | 1 + ADC_Module.cpp | 100 ++++++++++++++++++++++++++++++++- ADC_Module.h | 40 +++++++++++++- examples/adc_timer.ino | 123 +++++++++++++++++++++++++++++++++++++++++ settings_defines.h | 15 +++++ 5 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 examples/adc_timer.ino 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_Module.cpp b/ADC_Module.cpp index bf10119..ca055e5 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,97 @@ 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 = 16; // ADC_ETC channel + 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) ; + + 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) ; + + 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/examples/adc_timer.ino b/examples/adc_timer.ino new file mode 100644 index 0000000..d0349c6 --- /dev/null +++ b/examples/adc_timer.ino @@ -0,0 +1,123 @@ +/* Example for triggering the ADC with Timer +* Valid for Teensy 3.0 and 3.1 +*/ + + +#include +#include + +const int readPin = A9; // ADC0 +const int readPin2 = A2; // ADC1 + +ADC *adc = new ADC(); // adc object; + +void setup() { + + pinMode(LED_BUILTIN, OUTPUT); + pinMode(readPin, INPUT); + pinMode(readPin2, INPUT); + + Serial.begin(9600); + + 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 + 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"); + +} + +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.println(value*3.3/adc->getMaxValue(ADC_0), DEC); + #if ADC_NUM_ADCS>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.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->enableInterrupts(adc0_isr, ADC_0); + adc->adc0->startTimer(freq); //frequency in Hz + #if ADC_NUM_ADCS>1 + adc->adc1->stopTimer(); + adc->adc1->startSingleRead(readPin2); // call this to setup everything before the Timer starts + adc->enableInterrupts(adc1_isr, ADC_1); + adc->adc1->startTimer(freq); //frequency in Hz + #endif + } + } else if(c=='p') { // pbd stats + Serial.print("Frequency: "); + Serial.println(adc->adc0->getTimerFrequency()); + } + + } + + + // 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 + if(adc->adc1->fail_flag != ADC_ERROR::CLEAR) { + Serial.print("ADC1: "); Serial.println(getStringADCError(adc->adc1->fail_flag)); + } + #endif + adc->resetError(); + + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN)); + + delay(10); +} + + +// Make sure to call readSingle() to clear the interrupt. +void adc0_isr() { + adc->adc0->readSingle(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); +} + +#if ADC_NUM_ADCS>1 +void adc1_isr() { + adc->adc1->readSingle(); + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); +} +#endif + +// Timer interrupt is enabled in case you need it. +void Timer_isr(void) { + //PDB0_SC &=~PDB_SC_PDBIF; // clear interrupt + //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); +} 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) From 7e253a25a277f0445cc65dff6529c54ae0839aa5 Mon Sep 17 00:00:00 2001 From: Kurt Eckhardt Date: Tue, 31 Dec 2019 08:26:14 -0800 Subject: [PATCH 2/5] T4 - Timer startng to work with ISR Have an example which is almost a direct copy of the adc_pdb app, except change PDB to Timer... But I also extended the test app, to do do a timed read into buffers. Currently defaulting to 500 count. When the count is reached, the code will print out things like delta time, computed frquency (currently using millis, micros would be closer), plus min, max, avg, and then a sort of RMS... Next up debug DMA --- ADC_Module.cpp | 15 ++- AnalogBufferDMA.cpp | 179 ++++++++++++++++++++++++++++ AnalogBufferDMA.h | 72 ++++++++++++ examples/adc_timer.ino | 123 -------------------- examples/adc_timer/adc_timer.ino | 194 +++++++++++++++++++++++++++++++ 5 files changed, 456 insertions(+), 127 deletions(-) create mode 100644 AnalogBufferDMA.cpp create mode 100644 AnalogBufferDMA.h delete mode 100644 examples/adc_timer.ino create mode 100644 examples/adc_timer/adc_timer.ino diff --git a/ADC_Module.cpp b/ADC_Module.cpp index ca055e5..a14ed4d 100644 --- a/ADC_Module.cpp +++ b/ADC_Module.cpp @@ -1497,7 +1497,7 @@ void ADC_Module::startTimer(uint32_t freq) { // 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 = 16; // ADC_ETC channel + 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. @@ -1514,7 +1514,12 @@ void ADC_Module::startTimer(uint32_t freq) { 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) ; - IMXRT_ADC_ETC.DMA_CTRL |= ADC_ETC_DMA_CTRL_TRIQ_ENABLE(ADC_ETC_TRIGGER_INDEX); + 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? @@ -1525,7 +1530,9 @@ void ADC_Module::startTimer(uint32_t freq) { 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) ; - IMXRT_ADC_ETC.DMA_CTRL = ADC_ETC_DMA_CTRL_TRIQ_ENABLE(ADC_ETC_TRIGGER_INDEX); + 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. @@ -1565,7 +1572,7 @@ uint32_t ADC_Module::getTimerFrequency() { 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); + //Serial.printf("ADC_Module::getTimerFrequency H:%u L:%u H+L=%u pcs:%u freq:%u\n", high, low, highPlusLow, pcs, freq); return freq; } diff --git a/AnalogBufferDMA.cpp b/AnalogBufferDMA.cpp new file mode 100644 index 0000000..8998c5a --- /dev/null +++ b/AnalogBufferDMA.cpp @@ -0,0 +1,179 @@ +/* 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 + +#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 + + + +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 + + _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]; + + 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(); +} + +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 (_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 + } + _dmachannel_adc.enable(); + +#endif +} + +void AnalogBufferDMA::adc_0_dmaISR() { + if (_activeObjectPerADC[0]) { + _activeObjectPerADC[0]->processADC_DMAISR(); + } +#if defined(__IMXRT1062__) // Teensy 4.0 + asm("DSB"); +#endif +} + +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..e049041 --- /dev/null +++ b/AnalogBufferDMA.h @@ -0,0 +1,72 @@ +/* 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, uint16_t buffer2_count) : + _buffer1(buffer1), _buffer1_count(buffer1_count), _buffer2(buffer2), _buffer2_count(buffer2_count) {}; + + void init(ADC *adc, int8_t adc_num = -1); + inline volatile uint16_t *bufferLastISRFilled() {return (_interrupt_count & 1)? _buffer1 : _buffer2;} + inline uint16_t bufferCountLastISRFilled() {return (_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; +}; + +#endif diff --git a/examples/adc_timer.ino b/examples/adc_timer.ino deleted file mode 100644 index d0349c6..0000000 --- a/examples/adc_timer.ino +++ /dev/null @@ -1,123 +0,0 @@ -/* Example for triggering the ADC with Timer -* Valid for Teensy 3.0 and 3.1 -*/ - - -#include -#include - -const int readPin = A9; // ADC0 -const int readPin2 = A2; // ADC1 - -ADC *adc = new ADC(); // adc object; - -void setup() { - - pinMode(LED_BUILTIN, OUTPUT); - pinMode(readPin, INPUT); - pinMode(readPin2, INPUT); - - Serial.begin(9600); - - 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 - 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"); - -} - -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.println(value*3.3/adc->getMaxValue(ADC_0), DEC); - #if ADC_NUM_ADCS>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.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->enableInterrupts(adc0_isr, ADC_0); - adc->adc0->startTimer(freq); //frequency in Hz - #if ADC_NUM_ADCS>1 - adc->adc1->stopTimer(); - adc->adc1->startSingleRead(readPin2); // call this to setup everything before the Timer starts - adc->enableInterrupts(adc1_isr, ADC_1); - adc->adc1->startTimer(freq); //frequency in Hz - #endif - } - } else if(c=='p') { // pbd stats - Serial.print("Frequency: "); - Serial.println(adc->adc0->getTimerFrequency()); - } - - } - - - // 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 - if(adc->adc1->fail_flag != ADC_ERROR::CLEAR) { - Serial.print("ADC1: "); Serial.println(getStringADCError(adc->adc1->fail_flag)); - } - #endif - adc->resetError(); - - //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN)); - - delay(10); -} - - -// Make sure to call readSingle() to clear the interrupt. -void adc0_isr() { - adc->adc0->readSingle(); - //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); -} - -#if ADC_NUM_ADCS>1 -void adc1_isr() { - adc->adc1->readSingle(); - //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); -} -#endif - -// Timer interrupt is enabled in case you need it. -void Timer_isr(void) { - //PDB0_SC &=~PDB_SC_PDBIF; // clear interrupt - //digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN) ); -} diff --git a/examples/adc_timer/adc_timer.ino b/examples/adc_timer/adc_timer.ino new file mode 100644 index 0000000..3be333f --- /dev/null +++ b/examples/adc_timer/adc_timer.ino @@ -0,0 +1,194 @@ +/* Example for triggering the ADC with Timer + Valid for Teensy 3.0 and 3.1 +*/ + + +#include +#include + +const int readPin = A0; // ADC0 +#define USE_ADC_0 +#define USE_ADC_1 + +#if ADC_NUM_ADCS>1 +const int readPin2 = A1; // 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 + 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); + } + + } + } 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 From 1bce35f56228c1863994af0b231dfa9b6b8f3288 Mon Sep 17 00:00:00 2001 From: Kurt Eckhardt Date: Sat, 4 Jan 2020 08:41:33 -0800 Subject: [PATCH 3/5] DMA operation With Timer This adds support for Timers as well as allows the DMA buffer object to run with just one buffer. In this mode, the dma buffer will code will stop the dma operation when the buffer is filled. It then allows you to call a method to get another buffer full of analog samples. Added examples that use the timer object. On Teensy 3.x boards this will use the PDB code. On Teensy 4, this will use unused Quad timers. There are three examples. The first simply uses the timers and interrupts on each analog conversion which happens at the frequency specified. A second one use the DMA buffer object with two buffers, and runs continuous analog samples round robins between them and prints out info, like min, max, average. A third one is like the second one, except you only pass one buffer to the dma buffer object, so it halts doing conversions when the buffer is filled and then restarts it, when you call a method on the dma buffer object. All three of these should compile on Teensy 3.x and 4 boards. I tested a few of them on T3.6 and the second one on T3.2 as well as T4. --- AnalogBufferDMA.cpp | 87 +++++-- AnalogBufferDMA.h | 12 +- examples/adc_timer/adc_timer.ino | 31 ++- examples/adc_timer_dma/adc_timer_dma.ino | 212 +++++++++++++++++ .../adc_timer_dma_oneshot.ino | 220 ++++++++++++++++++ 5 files changed, 534 insertions(+), 28 deletions(-) create mode 100644 examples/adc_timer_dma/adc_timer_dma.ino create mode 100644 examples/adc_timer_dma_oneshot/adc_timer_dma_oneshot.ino diff --git a/AnalogBufferDMA.cpp b/AnalogBufferDMA.cpp index 8998c5a..7fcfd41 100644 --- a/AnalogBufferDMA.cpp +++ b/AnalogBufferDMA.cpp @@ -43,6 +43,10 @@ AnalogBufferDMA *AnalogBufferDMA::_activeObjectPerADC[2] = {nullptr, nullptr}; #define DMAMUX_ADC_0 DMAMUX_SOURCE_ADC0 #endif +//============================================================================= +// Debug support +//============================================================================= + #ifdef DEBUG_DUMP_DATA static void dumpDMA_TCD(DMABaseClass *dmabc) { @@ -63,6 +67,9 @@ static void dumpDMA_TCD(DMABaseClass *dmabc) +//============================================================================= +// Init - Initialize the object including setup DMA structures +//============================================================================= void AnalogBufferDMA::init(ADC *adc, int8_t adc_num) { // enable DMA and interrupts @@ -73,18 +80,30 @@ void AnalogBufferDMA::init(ADC *adc, int8_t adc_num) #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[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 - _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]; - _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; @@ -138,6 +157,40 @@ void AnalogBufferDMA::init(ADC *adc, int8_t adc_num) _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(); @@ -150,16 +203,21 @@ void AnalogBufferDMA::processADC_DMAISR() { #ifdef KINETISL // Lets try to clear the previous interrupt, change buffers // and restart - if (_interrupt_count & 1) { - _dmachannel_adc.destinationBuffer((uint16_t*)_buffer2, _buffer2_count * 2); // 2*b_size is necessary for some reason + 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 + _dmachannel_adc.destinationBuffer((uint16_t*)_buffer1, _buffer1_count * 2); // 2*b_size is necessary for some reason } - _dmachannel_adc.enable(); + + // 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(); @@ -169,6 +227,9 @@ void AnalogBufferDMA::adc_0_dmaISR() { #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(); diff --git a/AnalogBufferDMA.h b/AnalogBufferDMA.h index e049041..eaa29c3 100644 --- a/AnalogBufferDMA.h +++ b/AnalogBufferDMA.h @@ -44,12 +44,17 @@ class AnalogBufferDMA { void processADC_DMAISR(); public: - AnalogBufferDMA(volatile uint16_t *buffer1, uint16_t buffer1_count, volatile uint16_t *buffer2, uint16_t buffer2_count) : + 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); - inline volatile uint16_t *bufferLastISRFilled() {return (_interrupt_count & 1)? _buffer1 : _buffer2;} - inline uint16_t bufferCountLastISRFilled() {return (_interrupt_count & 1)? _buffer1_count : _buffer2_count;} + + 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;} @@ -67,6 +72,7 @@ class AnalogBufferDMA { volatile uint16_t *_buffer2; uint16_t _buffer2_count; uint32_t _user_data = 0; + bool _stop_on_completion = false; }; #endif diff --git a/examples/adc_timer/adc_timer.ino b/examples/adc_timer/adc_timer.ino index 3be333f..f6b7ba0 100644 --- a/examples/adc_timer/adc_timer.ino +++ b/examples/adc_timer/adc_timer.ino @@ -1,5 +1,22 @@ /* Example for triggering the ADC with Timer - Valid for Teensy 3.0 and 3.1 + 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 */ @@ -11,7 +28,7 @@ const int readPin = A0; // ADC0 #define USE_ADC_1 #if ADC_NUM_ADCS>1 -const int readPin2 = A1; // ADC1 +const int readPin2 = A2; // ADC1 #endif ADC *adc = new ADC(); // adc object; @@ -98,16 +115,6 @@ void loop() { adc->adc1->enableInterrupts(adc1_isr); adc->adc1->startTimer(freq); //frequency in Hz #endif - 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); - } - } } else if (c == 'p') { // print Timer stats Serial.print("Frequency: "); 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..4f69fbe --- /dev/null +++ b/examples/adc_timer_dma/adc_timer_dma.ino @@ -0,0 +1,212 @@ +/* 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 + +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*/); +#if ADC_NUM_ADCS>1 + abdma2.init(adc, ADC_1/*, DMAMUX_SOURCE_ADC_ETC*/); +#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; +} + +char c = 0; + +int average_value = 2048; + +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; + + 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); + Serial.printf(" %d - %u(%u): %u <= %u <= %u %d ", adc_num, pabdma->interruptCount(), pabdma->interruptDeltaTime(), min_val, + sum_values / buffer_size, max_val, rms); + pabdma->clearInterrupt(); +} + +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..623c8d2 --- /dev/null +++ b/examples/adc_timer_dma_oneshot/adc_timer_dma_oneshot.ino @@ -0,0 +1,220 @@ +/* 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 + +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*/); + abdma2.init(adc, ADC_1/*, DMAMUX_SOURCE_ADC_ETC*/); + 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; +} + +char c = 0; + +int average_value = 2048; + +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; + + 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); + Serial.printf(" %d - %u(%u): %u <= %u <= %u %d ", adc_num, pabdma->interruptCount(), pabdma->interruptDeltaTime(), min_val, + sum_values / buffer_size, max_val, rms); + pabdma->clearInterrupt(); +} + +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 From dc7d617b29ca90624240aa3464d4695066c00f5f Mon Sep 17 00:00:00 2001 From: Kurt Eckhardt Date: Sun, 5 Jan 2020 09:35:52 -0800 Subject: [PATCH 4/5] Add DMA example without timer Added a DMA buffer example that did not use a timer. Runs on TLC, 3.x and 4 Turned off debug printing in DMA object Updated examples to save last average value as user value in dma object --- AnalogBufferDMA.cpp | 2 +- examples/adc_dma/adc_dma.ino | 144 ++++++++++++++++++ examples/adc_timer_dma/adc_timer_dma.ino | 14 +- .../adc_timer_dma_oneshot.ino | 16 +- 4 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 examples/adc_dma/adc_dma.ino diff --git a/AnalogBufferDMA.cpp b/AnalogBufferDMA.cpp index 7fcfd41..f23d227 100644 --- a/AnalogBufferDMA.cpp +++ b/AnalogBufferDMA.cpp @@ -24,7 +24,7 @@ */ #include "AnalogBufferDMA.h" -#define DEBUG_DUMP_DATA +//#define DEBUG_DUMP_DATA // Global objects AnalogBufferDMA *AnalogBufferDMA::_activeObjectPerADC[2] = {nullptr, nullptr}; 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_dma/adc_timer_dma.ino b/examples/adc_timer_dma/adc_timer_dma.ino index 4f69fbe..60fb538 100644 --- a/examples/adc_timer_dma/adc_timer_dma.ino +++ b/examples/adc_timer_dma/adc_timer_dma.ino @@ -38,6 +38,7 @@ 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; @@ -87,8 +88,10 @@ void setup() { // 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(); @@ -108,10 +111,6 @@ void setup() { elapsed_sinc_last_display = 0; } -char c = 0; - -int average_value = 2048; - void loop() { // Maybe only when both have triggered? @@ -151,6 +150,8 @@ void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) { 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(); @@ -167,9 +168,12 @@ void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) { } 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, - sum_values / buffer_size, max_val, rms); + average_value, max_val, rms); pabdma->clearInterrupt(); + + pabdma->userData(average_value); } void print_debug_information() diff --git a/examples/adc_timer_dma_oneshot/adc_timer_dma_oneshot.ino b/examples/adc_timer_dma_oneshot/adc_timer_dma_oneshot.ino index 623c8d2..3c61ba7 100644 --- a/examples/adc_timer_dma_oneshot/adc_timer_dma_oneshot.ino +++ b/examples/adc_timer_dma_oneshot/adc_timer_dma_oneshot.ino @@ -42,6 +42,7 @@ 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; @@ -88,7 +89,11 @@ void setup() { // 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.. @@ -108,10 +113,6 @@ void setup() { elapsed_sinc_last_display = 0; } -char c = 0; - -int average_value = 2048; - void loop() { // Maybe only when both have triggered? @@ -159,6 +160,8 @@ void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) { 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(); @@ -175,9 +178,12 @@ void ProcessAnalogData(AnalogBufferDMA *pabdma, int8_t adc_num) { } 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, - sum_values / buffer_size, max_val, rms); + average_value, max_val, rms); pabdma->clearInterrupt(); + + pabdma->userData(average_value); } void print_debug_information() From 2ba671c77ddb039f56c9fabdb92d38a8a5c771f9 Mon Sep 17 00:00:00 2001 From: Kurt Eckhardt Date: Mon, 6 Jan 2020 15:28:59 -0800 Subject: [PATCH 5/5] T4 - IntervalTImer example compile The Interval Timer example did not compile on a T4. Example was using T3.x register name, plus the reverse table from channel to pin was not defined for T4 --- ADC.cpp | 12 ++++++++++++ .../analogReadIntervalTimer.ino | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) 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/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