diff --git a/Config.h b/Config.h index 917d891..6001570 100644 --- a/Config.h +++ b/Config.h @@ -33,6 +33,8 @@ #define ARDUINO_UNO_NANO #elif defined(ARDUINO_AVR_MEGA2560) #define ARDUINO_MEGA +#elif defined(NANO_EVERY_PINOUT) + #define ARDUINO_NANO_EVERY #else #error "Platform not recognised" #endif @@ -50,6 +52,7 @@ // Works on Arduino Uno (Timer1/pin D8) // Arduino Nano (Timer1/pin D8) // Arduino Mega (Timer4/pin D49) +// Arduino Nano Every (Timer B2/any pin) // If we don't use this, then the selected input pin must support change interrupt // (defaults to pin D2 on Uno, Nano and Mega, GPIO2 on ESP8266 and GPIO5 on ESP32. #define USETIMER @@ -63,7 +66,7 @@ // ESP8266: 2 // Other: 2 #if defined(USETIMER) - #if defined(ARDUINO_UNO_NANO) + #if defined(ARDUINO_UNO_NANO) || defined(ARDUINO_NANO_EVERY) #define INPUTPIN 8 #elif defined(ARDUINO_MEGA) #define INPUTPIN 49 @@ -131,15 +134,15 @@ // through this name with a ".local" suffix (e.g. http://DccInspector.local/). #define DNSNAME "DccInspector" -// OLED isn't supported on Uno or Nano -#if defined(ARDUINO_UNO_NANO) +// OLED isn't supported on Uno or Nano or Nano Every +#if defined(ARDUINO_UNO_NANO) || defined(ARDUINO_NANO_EVERY) #if defined(USE_OLED) #undef USE_OLED #endif #endif -// HTTP Server isn't supported on Uno or Nano or Mega -#if defined(ARDUINO_UNO_NANO) | defined(ARDUINO_MEGA) +// HTTP Server isn't supported on Uno or Nano or Mega or Nano Every +#if defined(ARDUINO_UNO_NANO) || defined(ARDUINO_MEGA) || defined(ARDUINO_NANO_EVERY) #if defined(USE_HTTPSERVER) #undef USE_HTTPSERVER #endif diff --git a/DCCInspector-EX.ino b/DCCInspector-EX.ino index b7fc8fd..8de0cba 100644 --- a/DCCInspector-EX.ino +++ b/DCCInspector-EX.ino @@ -18,6 +18,7 @@ /////////////////////////////////////////////////////// // +// Add support for Arduino Nano Every with MegaCoreX: newHeiko Apr 2024 // Add buttons to web page for modifying options: NMcK Apr 2021 // Regroup functions into separate classes: NMcK Jan 2021 // Move configuration items to Config.h: NMcK Jan 2021 @@ -34,9 +35,9 @@ // Removed use of AVR timer interrupts: NMcK June 2020 // DCC packet analyze: Ruud Boer, October 2015 // -// The DCC signal is detected on Arduino digital pin 8 (ICP1 on Uno/Nano), -// or pin 49 (ICP4 on Mega), or pin GPIO2 on ESP8266/ESP32. This causes an interrupt, -// and in the interrupt response code the time between interrupts is measured. +// The DCC signal is detected on Arduino digital pin 8 (ICP1 on Uno/Nano), or pin 49 +// (ICP4 on Mega), or pin GPIO2 on ESP8266/ESP32, or any pin on Nano Every. This causes an +// interrupt, and in the interrupt response code the time between interrupts is measured. // // Use an opto-isolator between the track signal (~30V p-p) and the // digital input (0 to 5V or 0 to 3.3V) to prevent burn-out. @@ -124,6 +125,10 @@ #include #define digitalWrite(pin, state) digitalWrite2(pin, state) #define digitalRead(pin) digitalRead2(pin) +#elif defined(USE_DIO2) && defined(ARDUINO_NANO_EVERY) +#define digitalWrite(pin, state) digitalWriteFast(pin, state) +#define digitalRead(pin) digitalReadFast(pin) +#define GPIO_PREFER_SPEED #endif #ifdef USETIMER diff --git a/EventTimer.h b/EventTimer.h index 4d77930..7463b71 100644 --- a/EventTimer.h +++ b/EventTimer.h @@ -25,6 +25,8 @@ #if defined(ARDUINO_UNO_NANO) || defined(ARDUINO_AVR_MEGA2560) #include "EventTimer_AtMega.h" +#elif defined(ARDUINO_NANO_EVERY) + #include "EventTimer_AtMega0series.h" #elif defined(ESP32) #include "EventTimer_ESP32.h" #else diff --git a/EventTimer_AtMega0series.h b/EventTimer_AtMega0series.h new file mode 100644 index 0000000..93e9f4d --- /dev/null +++ b/EventTimer_AtMega0series.h @@ -0,0 +1,174 @@ +/* Copyright (c) 2024 Heiko Rosemann + * Based on EventTimer_AtMega.h (c) 2021 Neil McKechnie + * + * This Library is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software. If not, see + * . + */ + +/* + * EventTimer_AtMega0series.h + * + * Timer functions for measuring elapsed time between events. On the mega0-series, + * the functions use the Input Capture capability of the microcontroller to capture the + * timer value very precisely using hardware. + * + * The pin used as input pin can be configured in Config.h + * + * Since the Timer counter is limited to 16 bits, it will overflow if the interval exceeds + * about 32milliseconds. To avoid this, we check micros() to determine if pulse + * length is too long and, in that case, calculate the ticks from micros(). + * + */ + +#ifndef eventtimer_default_h +#define eventtimer_default_h + +#include +#include "Config.h" +#include "Event.h" + +#define TICKSPERMICROSEC 8 // 8 (125ns) or 16 (62.5ns) + +#if not defined(ARDUINO_NANO_EVERY) + #error "Architecture not supported by EventTimer library." +#endif + +/* + * User event handler is sent the time since the last valid event. + * If the user event handler decides that this event isn't valid + * (e.g. time since last one is too short) then it should return + * false to reject the event. If the event is valid, then it + * should return true to accept it. + */ +typedef bool EventHandler(unsigned long eventInterval); + +class EventTimerClass { +public: + + // Initialise the object instance, validating that the input pin is + // correct and noting the reference to the user handler for future use. + bool begin(int pin, EventHandler userHandler) { +#if defined(GPIO_PREFER_SPEED) + if (pin != INPUTPIN) { + Serial.println(F("ERROR: Cannot use fast GPIO with pin != INPUTPIN")); + Serial.print(F("pin=")); + Serial.println(pin); + Serial.print(F("INPUTPIN=")); + Serial.println(INPUTPIN); + return false; + } +#endif + Event & myEvent = Event::assign_generator_pin(pin); + if (myEvent.get_channel_number() == 255) { + Serial.print(F("ERROR: pin=")); + Serial.print(pin); + return false; + } + + myEvent.set_user(event::user::tcb2_capt); + + this->pin = pin; + this->callUserHandler = userHandler; + + // Set up input capture. + // Configure Timer B2 to increment CNT on an 8MHz clock, + // (every 125ns), or 16MHz (every 62.5ns), no interrupt. + // Use Input Capture Pin to capture time of input change + // and interrupt the CPU. + #if TICKSPERMICROSEC==8 + TCB2.CTRLA = TCB_CLKSEL_CLKDIV2_gc | TCB_ENABLE_bm; // Prescaler CLK/2 + #elif TICKSPERMICROSEC==16 + TCB2.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; // Prescaler CLK/1 + #else + #error "TICKSPERMICROSEC" not 8 or 16. + #endif + TCB2.CTRLB = TCB_CNTMODE_CAPT_gc; // Setup Timer B2 for capture mode + TCB2.EVCTRL = TCB_FILTER_bm | TCB_CAPTEI_bm; // Enable input capture filter and event + TCB2.INTCTRL = TCB_CAPT_bm; // Enable input capture interrupt + TCB2.INTFLAGS = TCB_CAPT_bm; // Clear interrupt flag + + myEvent.start(); + + return true; + }; + + // Utility function to give number of ticks since the last event. Useful + // for determining how much time has elapsed within the interrupt handler since + // the interrupt was triggered. + inline unsigned long elapsedTicksSinceLastEvent() { + return (unsigned int)(TCB2.CNT - thisEventTicks); + }; + + // Function called from the interrupt handler to calculate the gap between interrupts, + // and to invoke the user program's handler. The user's handler is passed the + // number of ticks elapsed since the last valid interrupt. It returns true/false to + // indicate if this interrupt is deemed to be 'valid' or not. + void processInterrupt() { + // Time-critical bits. + unsigned long thisEventMicros = micros(); + thisEventTicks = TCB2.CCMP; +#if defined(GPIO_PREFER_SPEED) + byte diginState = digitalRead(INPUTPIN); +#else + byte diginState = digitalRead(pin); +#endif + + // Set up input capture for next edge. + if (diginState) + TCB2.EVCTRL |= TCB_EDGE_bm; // Capture next falling edge on input + else + TCB2.EVCTRL &= ~TCB_EDGE_bm; // Capture next rising edge on input + + // Initially estimate time ticks using micros() values. + unsigned long eventSpacing = (thisEventMicros - lastValidEventMicros) * TICKSPERMICROSEC; + if (eventSpacing < 60000L) { + // Estimated time is well within range of timer count, so calculate ticks from ICRn + eventSpacing = thisEventTicks - lastValidEventTicks; + } + // Call user handler. + bool accepted = callUserHandler(eventSpacing); + if (accepted) { + lastValidEventTicks = thisEventTicks; + lastValidEventMicros = thisEventMicros; + } + }; + + // Utility function to return the number of timer ticks per microsecond. + // On the AtMega 0 series, this is 8 or 16, depending on the timer pre-scaler setting. + inline unsigned int ticksPerMicrosec() { + return TICKSPERMICROSEC; + }; + + // Utility function to inform whether input capture is in use or not. For this + // version, it always returns true. + inline bool inputCaptureMode() { return true; }; + +private: + EventHandler *callUserHandler = 0; + unsigned int lastValidEventTicks = 0; + unsigned long lastValidEventMicros = 0; + unsigned int thisEventTicks = 0; + int pin = -1; + +} /* class EventTimerClass */; + +// Declare singleton class instance. +EventTimerClass EventTimer; + +// Interrupt handler for input capture event +ISR(TCB2_INT_vect) { + EventTimer.processInterrupt(); +} + +#endif