Skip to content
13 changes: 8 additions & 5 deletions Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions DCCInspector-EX.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -124,6 +125,10 @@
#include <DIO2.h>
#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
Expand Down
2 changes: 2 additions & 0 deletions EventTimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
174 changes: 174 additions & 0 deletions EventTimer_AtMega0series.h
Original file line number Diff line number Diff line change
@@ -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
* <http://www.gnu.org/licenses/>.
*/

/*
* 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 <Arduino.h>
#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