TeensyTimerTool

I'm curious. I've been trying to encapsulate some timers in a class, and I can't seem to get the tick timers to automatically generate from the next available channel. In other words, every instance of my class overwrites the first TCK timer channel.

I'm wondering if this is a) my mistake, and there is a simple way around it, or b) a limitation of the TeensyTimerTool library?

For the moment I'm instantiating 8 timers (or however many I might need) before I create any objects, and then pass the appropriate OneShotTimer to the class instances by reference.

Wondering if one of you pros could give me a little guidance with this one. Just getting started, but I'll post the code:

Code:
#pragma once

#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

class motor
{
    public:
      int8_t step_pin;
      int8_t dir_pin;
      int32_t position_steps;
  
      double delay_usec;
      double pulsewidth_usec;

      bool stopped;

      TeensyTimerTool::OneShotTimer tmr;   

     motor(int8_t _step_pin, int8_t _dir_pin, TeensyTimerTool::OneShotTimer &_tmrx)
      : tmr(_tmrx)
     {
         this->step_pin = _step_pin;
         this->dir_pin = _dir_pin;
         pulsewidth_usec = 2;    //Teensystep default = 5us
         delay_usec = 1; 
         stopped = true;    
          
         pinMode(step_pin, OUTPUT); digitalWrite(step_pin, HIGH);
         pinMode(dir_pin, OUTPUT); digitalWrite(dir_pin, HIGH); 

         setSpeed(10);

         tmr.begin([this] { this->step_callback();} ); 
         tmr.trigger( 10000 ); 
  
     }

      void start() {stopped = false; setSpeed(1);}
      void setSpeed(double stps_per_sec) { delay_usec = (1000000 / stps_per_sec) - pulsewidth_usec; }
      void step_callback() 
      {
          if (!stopped)
          {
              if (!digitalRead(step_pin)) {
                position_steps++; 
                  tmr.trigger(pulsewidth_usec);
              }
              else {
                  tmr.trigger(delay_usec);
              }
              digitalWrite(step_pin, !digitalRead(step_pin));
          }
      }
      double steps_per_sec () { return 1000000 / (pulsewidth_usec + delay_usec); }
            
};


Before someone recommends the TeensyStep library (which is fantastic), I should say that I am trying to control 8 motors simultaneously by joint angle, updating position on the fly and RotateControl with some sort of PID loop seems to be the only way to accomplish this task, but you cannot use overrideSpeed for individual motors in a controller, and you can only have 4 RotateControllers at a time due to the number of timers on the Teensy 3.6. For this reason, I'm trying to hack something together with TCK timers on a Teensy 4.1.
 
Last edited:
I'm curious. I've been trying to encapsulate some timers in a class, and I can't seem to get the tick timers to automatically generate from the next available channel. In other words, every instance of my class overwrites the first TCK timer channel.

I can have a look but I need your calling code and some information how to identify your issue.

Here already a few remarks to your code:

  • Any reason why you define your timers in the sketch and then pass them to your motor objects? Might be cleaner to directly add them as member variables in your class.
  • You do a lot of hardware related stuff in the constructor. If you have your motors as global objects (which is pretty common in the Arduino world) the constructors may be called early in the startup process. It can be that Teensyduino didn't initialize everything yet when the constructor is called. If you need to talk to hardware It is always better to introduce a begin() function which you'd call from setup() or any other convenient place.
 
  • Any reason why you define your timers in the sketch and then pass them to your motor objects? Might be cleaner to directly add them as member variables in your class.
  • You do a lot of hardware related stuff in the constructor. If you have your motors as global objects (which is pretty common in the Arduino world) the constructors may be called early in the startup process. It can be that Teensyduino didn't initialize everything yet when the constructor is called. If you need to talk to hardware It is always better to introduce a begin() function which you'd call from setup() or any other convenient place.

  • What I intended to say is that I couldn't get the timers to initialize on different channels if I added them as member variables. Each instance of the class would just use the first channel of the TCK timer, whereas if I define all the timers in the main INO file, the TeensyTimerTool library seems to keep track of available channels. Perhaps I'm missing something.
  • Agreed. I'll clean this up. A begin() function is a great idea and I'll give that a whirl, but I'm still not sure how I can get around defining my timers in the main sketch.
Hope that makes sense.

As a side note, and this is probably off-topic here, I've been trying to get similar timing using this tool as you've achieved with the TeensyStep library. Even with all but one "stepper" timer commented out, and all extraneous code/delays etc kept to a minimum, I can see just the slightest bit of jitter on my oscilloscope whereas your TeensyStep library produces absolutely still and perfectly timed steps. I've tried this with all the different varieties of timers available-- I've tried it on the Teensy 3.6, 4.0 and 4.1-- and I've spent countless idle hours looking at your library and trying to piece together this black magic, but your programming is way too sophisticated for me to make heads or tails. For example, you declare an object:

Code:
namespace TeensyStep
{
    template <typename Accelerator, typename TimerField>
    class RotateControlBase : public TeensyStep::MotorControlBase<TimerField>
        void doRotate(int N, float speedFactor = 1.0);
        void accTimerISR();

        Accelerator accelerator;
And I cannot for the life of me figure out where or how this is defined as a class and how that loops back in with the LinRotAccelerator and LinStepAccelerators. :confused:

To be clear, this is a failing of mine, not yours. Both libraries accomplish their intended tasks extremely well, though I am curious to here your response to my initial comment.

People like myself rely on combing the internet for crumbs left out by coders like you and Paul Stoffregen.
 
What I intended to say is that I couldn't get the timers to initialize on different channels if I added them as member variables. Each instance of the class would just use the first channel of the TCK timer, whereas if I define all the timers in the main INO file, the TeensyTimerTool library seems to keep track of available channels. Perhaps I'm missing something.

I'd love to help you but, as I wrote in my last post, I really need to see your sketch. Ideally some minimal version which I can compile and which shows the behaviour you describe. Without seeing your sketch this is all just guess work. Together with the sketch please also describe how you determine that only one TCK channel is used.
 
Hello all,
Firstly I am still fairly green to C++ (and the Teensy family) but I will try and explain a problem I have encountered with the TeensyTimerTool. I have version 0.3.2 currently installed (I recently did an update of a number of libraries).

Earlier this year I created a project called "The G.P.S.CLOCK" which I posted about in the blog project submission using an older version of this fab tool. The program behind this complied without error, however since updating the TimerTool revision the program no longer compiles with the following error.:

---------------------

PROJECT: In function 'void filterfield()':

PROJECT:263: error: reference to 'hours' is ambiguous

hours = (((raw_nmea[counter+1] - '0') * 10) + (raw_nmea[counter+2] - '0')); // Extract the Hours

^

D:\MY DOCUMENTS\ARDUINO\PROJECT\PROJECT.ino:70:15: note: candidates are: signed char hours

signed char hours = 0; // HOUR STORAGE

^

In file included from D:\MY DOCUMENTS\Arduino\libraries\TeensyTimerTool\src/frequency.h:27:0,

from D:\MY DOCUMENTS\Arduino\libraries\TeensyTimerTool\src/baseTimer.h:9,

from D:\MY DOCUMENTS\Arduino\libraries\TeensyTimerTool\src/timer.h:4,

from D:\MY DOCUMENTS\Arduino\libraries\TeensyTimerTool\src/TeensyTimerTool.h:4,

from D:\MY DOCUMENTS\ARDUINO\PROJECT\PROJECT.ino:14:

d:\program files\arduino\hardware\tools\arm\arm-none-eabi\include\c++\5.4.1\chrono:542:45: note: typedef struct std::chrono::duration<long long int, std::ratio<3600ll> > std::chrono::hours

typedef duration<int64_t, ratio<3600>> hours;

^

---------------
I use the following global variables:

signed char hours = 0; // HOUR STORAGE
unsigned char minutes = 0; // MINUTES STORAGE
unsigned char days = 0; // Day of Week storage
unsigned char seconds = 0; // Seconds of the Minute


All of which are now listing similar errors to the one listed about. Also I noticed that the words "hours", "minutes", "days" and "seconds" have also changed to an orange colour within the IDE.

Any help or advise as to why/what has changed would be most welcome - This is the time line of my code. "Production" release was complied successfully on the 12th Aug 2020. The update happened on the 19th Nov and now things no longer compile

Kindest regards
Mat
 
Hello all,
Firstly I am still fairly green to C++ (and the Teensy family) but I will try and explain a problem I have encountered with the TeensyTimerTool. I have version 0.3.2 currently installed (I recently did an update of a number of libraries).

Earlier this year I created a project called "The G.P.S.CLOCK" which I posted about in the blog project submission using an older version of this fab tool. The program behind this complied without error, however since updating the TimerTool revision the program no longer compiles with the following error.:

...

Just a quess because of a post that passed by ...

One update to the library involved allowing times specified in words ... including hours

Seems that is indicated with this line :: typedef duration<int64_t, ratio<3600>> hours;

Change those variable names causing errors to be unique in some way : myHours or hoursGPS ...
 
Hi, Thanks for the fast reply! Okay thanks for the advise. It was one of the approaches I was looking at but before I spend time doing this I wanted to check it was not a error with the Teensy tool as I said - my code used to work, now it doesn't

Kindest and stay safe!
Mat
 
Actually 'hours' is one of the functions from the new (since c++11) std::chrono namespace which deals with handling time. Since v3.0 the TeensyTimerTool supports this namespace by default. If you don't like it you can of course opt out in the config file (see #223 of this thread). Looks like I forgot to add the opt out information to the WIKI.

However, since you are doing clocks you might actually find std::chrono quite useful. You can use it to do things like e.g.
Code:
t1.begin(callback, 250ms);
t1.begin(callback, 4us + 2ms);
t1.begin(callback, 1h);
t1.begin(callback, 60min);


In case you are interested, here some general infos about how to use the new types and how to define std::chrono clocks. https://github.com/TeensyUser/doc/wiki/durations-timepoints-and-clocks
 
I have renamed my variables throughout my code. Now when I complete I get this new error. Sorry to ask for advise, as I said I am quite new to C++ and the Teensy.
Thanks in advance for any advise.

Mat


New error:
---------
Arduino: 1.8.13 (Windows 10), TD: 1.53, Board: "Teensy LC, Serial, 48 MHz, Smallest Code, US English"


In file included from D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\config.cpp:80:0:

D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\Teensy/TCK/TCK.h: In instantiation of 'static TeensyTimerTool::ITimerChannel* TeensyTimerTool::TCK_t::getTimer() [with counterType = long unsigned int]':

D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\config.cpp:84:44: required from here

D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\Teensy/TCK/TCK.h:51:32: error: invalid new-expression of abstract class type 'TeensyTimerTool::TckChannel<long unsigned int>'

channels[chNr] = new TckChannel<counterType>();

^

In file included from D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\Teensy/TCK/TCK.h:4:0,

from D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\config.cpp:80:

D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\Teensy/TCK/TckChannel.h:245:11: note: because the following virtual functions are pure within 'TeensyTimerTool::TckChannel<long unsigned int>':

class TckChannel : public TckChannelBase

^

In file included from D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\Teensy/TCK/TckChannelBase.h:3:0,

from D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\Teensy/TCK/TckChannel.h:5,

from D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\Teensy/TCK/TCK.h:4,

from D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\TeensyTimerTool\src\config.cpp:80:

d:\program files\arduino\hardware\teensy\avr\libraries\teensytimertool\src\itimerchannel.h:22:23: note: virtual float TeensyTimerTool::ITimerChannel::getMaxPeriod() const

virtual float getMaxPeriod() const = 0;

^
----------------
 
That looks like a bug. Sorry, I don't use the LC much. Probably something LC specific.
I'll have a look later today
 
Thanks Luni!
The LC was just spot on for the G.P.S.CLOCK but i would like to stick with that for now and support the code if needed. Going forward the next project I am starting to work on/look at is on the 3.5!
hopefully if it is a bug its a simple one to fix.
Also thanks for the information on:

t1.begin(callback, 250ms);
t1.begin(callback, 4us + 2ms);
t1.begin(callback, 1h);
t1.begin(callback, 60min);

and the link! I will go into learning mode :)

Mat
 
Hi Luni,
Just as a bit of a FYI.. My code compiles perfectly on the 3.5 but errors on the LC. I hope this supports the bug theory.
 
@MatA: Can you give the current version on GitHub a try (need to download from the master branch, there is no release yet)

Tested on a LC with this code, which compiles and blinks now:
Code:
PeriodicTimer t1(TCK);

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  t1.begin([] { digitalToggleFast(LED_BUILTIN); }, 250ms);
}

void loop(){
}
 
@Luni: Downloaded the Master branch version and I am happy to report that this release has fixed the issue I reported 11:48 on the 22nd Nov.
In conjunction with the rename of my variables I can fully compile the GPS clock code again. Thanks for fixing this so quick.

Kindest and stay safe

Mat
 
Can someone please confirm that if using this library, then the "using namespace" is required?

Code:
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

What is the impact of missing out the "using namespace".

Some time ago I did read somewhere in this thread, but the penny hasn't dropped :-(

Also, when I was helping a friend use this library to implement our own joystick switch debounce (on a Teensy LC), we had problems with switches not being detected. The code had "tight" while loops. On a hunch we added "delay(1)" in the while loops and the code now worked. I sort of recall reading that TeensyTimerTool uses yield? To be honest I was surprised that it was necessary. I know that the Teensy LC is less well endowed with timers, as compared to the 3 series. Do the 3 series still need to yield?

Question is, what do I need to replace "delay(1)" with to allow TeensyTimerTool to work on the Teensy LC? In this case the added 1ms isn't too critical, but I suspect that there is a correct library call to do the same thing.
 
Can someone please confirm that if using this library, then the "using namespace" is required?

Code:
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

What is the impact of missing out the "using namespace".


Namespaces are a c++ concept to avoid clashes of symbol names (variables, functions etc) between libraries and user code. E.g. the teensy timer tool uses a class called "Timer". It is very likely that other libraries or the user code also use some "Timer". If all of those libraries and/or the user code use the standard namespace, the linker has no chance to find out which one you want to use. One can easily prevent this, so called namespace pollution, by declaring all symbols within a dedicated namespace. E.g., the TeensyTimerTool declares all symbols in the namespace TeensyTimerTool. -> The full name of the TeensyTimerTool Timer class is:

Code:
#include "TeensyTimerTool.h"

TeensyTimerTool::Timer t1;

If you now have another library also declaring a Timer class but in the namespace "SomeOtherNamespace" you can distinguish them by

Code:
#include "TeensyTimerTool.h"
#include "someOtherLib"

TeensyTimerTool::Timer t1;
SomeOtherNamespace:: Timer t2;

Instead of always writing the fully qualified names you can tell the compiler to import all symbols from a namespace by:
Code:
#include "TeensyTimerTool.h"

using namespace TeensyTimerTool; // import all symbols from the TeensyTimerTool namespace

Timer t1;   // this is now a shorthand for TeeensyTimerTool::Timer t1;

So, you don't need the "using namespace TeensyTimerTool" at all if you instead fully qualify all names (which, however, quickly gets tedious).


Also, when I was helping a friend use this library to implement our own joystick switch debounce (on a Teensy LC), we had problems with switches not being detected. The code had "tight" while loops. On a hunch we added "delay(1)" in the while loops and the code now worked. I sort of recall reading that TeensyTimerTool uses yield? To be honest I was surprised that it was necessary. I know that the Teensy LC is less well endowed with timers, as compared to the 3 series. Do the 3 series still need to yield?

The TTT uses hardware based timers (GPT, PIT, FTM, TMR...) and/or purely software based timers (TCK). Actually, I didn't bother to implement a hardware timer for the LC. So, for the LC only the TCK timers work (you can always use IntervalTimer if you need a hardware timer for the LC). The T3.x have 4 IntervalTimers and the TTT provides access to their hardware FTM timers. (Here the corresponding chapter of the documentation: https://github.com/luni64/TeensyTimerTool/wiki/Supported-Timers)

The software timers can only work if their tick() function is called as often as possible. To make this easier for the user the TTT injects some code into the yield call stack which automatically calls the tick() function from yield() in the background. (You can change that behaviour in the config file). If you need to have some tight loop without calling yield(), or delay() the TCK timers can't work.

Question is, what do I need to replace "delay(1)" with to allow TeensyTimerTool to work on the Teensy LC? In this case the added 1ms isn't too critical, but I suspect that there is a correct library call to do the same thing.
Replacing it by "yield()" should work and doesn't eat up as much processing time as delay(1);

Hope that helps
 
Last edited:
Hi luni, much appreciate you answering my questions.

I am a C++ noob, so I appreciate your explanation of "namespace". I had no idea that you could use multiple libraries whose name(?) is the same. Apologies I am not 100% on the correct C++ terminology.
 
Morning all
Decided to start playing around with porting over the CommandStation-EX. CommandStation-EX (https://github.com/DCC-EX/CommandStation-EX) is a library that currently only supports Arduino Mega and Arduino Uno boards and is used for controlling locomotives that use dcc/dcc++:
What is DCC? DCC stands for Digital Command Control. It is a system where digital commands are sent to the locomotives through the rails. DCC is allows independent control of multiple locomotives without complicated wiring, toggle switches or power packs
Guess you know where this is going - I want to port it over to use T3.x and T4.x instead of the Mega/Uno and replace all the timer stuff with TeensyTool.

NOTE: If you think this should be a new thread let me know.

The basic structure of the library is shown below:
DCC_EX.PNG Looking through the code the primary area that I have to change are the DCCwaveform.h/.cpp files. There are other associated files but those are ancillary and think I can handle them.

Anyway DCCwaform.cpp shows setting up the timers and the associated callbacks for the timers:
Code:
/*
 *  © 2020, Chris Harlow. All rights reserved.
 *  © 2020, Harald Barth.
 *  
 *  This file is part of Asbelos DCC API
 *
 *  This 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.
 *
 *  It 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 CommandStation.  If not, see <https://www.gnu.org/licenses/>.
 */
#include <Arduino.h>

#include "DCCWaveform.h"
#include "DIAG.h"
 
const int NORMAL_SIGNAL_TIME=58;  // this is the 58uS DCC 1-bit waveform half-cycle 
const int SLOW_SIGNAL_TIME=NORMAL_SIGNAL_TIME*512;

DCCWaveform  DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
DCCWaveform  DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);


bool DCCWaveform::progTrackSyncMain=false; 
bool DCCWaveform::progTrackBoosted=false; 
VirtualTimer * DCCWaveform::interruptTimer=NULL;      
  
[COLOR="#FF0000"]void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver, byte timerNumber) {
  mainTrack.motorDriver=mainDriver;
  progTrack.motorDriver=progDriver;

  mainTrack.setPowerMode(POWERMODE::OFF);      
  progTrack.setPowerMode(POWERMODE::OFF);
  switch (timerNumber) {
    case 1: interruptTimer= &TimerA; break;
    case 2: interruptTimer= &TimerB; break;
#ifndef ARDUINO_AVR_UNO  
    case 3: interruptTimer= &TimerC; break;
#endif    
    default:
      DIAG(F("\n\n *** Invalid Timer number %d requested. Only 1..3 valid.  DCC will not work.*** \n\n"), timerNumber);
      return;
  }
  interruptTimer->initialize();
  interruptTimer->setPeriod(NORMAL_SIGNAL_TIME); // this is the 58uS DCC 1-bit waveform half-cycle
  interruptTimer->attachInterrupt(interruptHandler);
  interruptTimer->start();
}
[/COLOR]
void DCCWaveform::setDiagnosticSlowWave(bool slow) {
  interruptTimer->setPeriod(slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME);
  interruptTimer->start(); 
  DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET")); 
}

void DCCWaveform::loop() {
  mainTrack.checkPowerOverload();
  progTrack.checkPowerOverload();
}


[COLOR="#FF0000"]// static //
void DCCWaveform::interruptHandler() {
  // call the timer edge sensitive actions for progtrack and maintrack
  bool mainCall2 = mainTrack.interrupt1();
  bool progCall2 = progTrack.interrupt1();

  // call (if necessary) the procs to get the current bits
  // these must complete within 50microsecs of the interrupt
  // but they are only called ONCE PER BIT TRANSMITTED
  // after the rising edge of the signal
  if (mainCall2) mainTrack.interrupt2();
  if (progCall2) progTrack.interrupt2();
}[/COLOR]


// An instance of this class handles the DCC transmissions for one track. (main or prog)
// Interrupts are marshalled via the statics.
// A track has a current transmit buffer, and a pending buffer.
// When the current buffer is exhausted, either the pending buffer (if there is one waiting) or an idle buffer.


// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};


DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
  // establish appropriate pins
  isMainTrack = isMain;
  packetPending = false;
  memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
  state = 0;
  // The +1 below is to allow the preamble generator to create the stop bit
  // fpr the previous packet. 
  requiredPreambles = preambleBits+1;  
  bytes_sent = 0;
  bits_sent = 0;
  sampleDelay = 0;
  lastSampleTaken = millis();
  ackPending=false;
}

POWERMODE DCCWaveform::getPowerMode() {
  return powerMode;
}

void DCCWaveform::setPowerMode(POWERMODE mode) {

  // Prevent power switch on with no timer... Otheruise track will get full power DC and locos will run away.  
  if (!interruptTimer) return; 
  
  powerMode = mode;
  bool ison = (mode == POWERMODE::ON);
  motorDriver->setPower( ison);
}


void DCCWaveform::checkPowerOverload() {
  
  static int progTripValue = motorDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once, hence static

  if (millis() - lastSampleTaken  < sampleDelay) return;
  lastSampleTaken = millis();
  int tripValue= motorDriver->getRawCurrentTripValue();
  if (!isMainTrack && !ackPending && !progTrackSyncMain && !progTrackBoosted)
    tripValue=progTripValue;
  
  switch (powerMode) {
    case POWERMODE::OFF:
      sampleDelay = POWER_SAMPLE_OFF_WAIT;
      break;
    case POWERMODE::ON:
      // Check current
      lastCurrent = motorDriver->getCurrentRaw();
      if (lastCurrent <= tripValue) {
        sampleDelay = POWER_SAMPLE_ON_WAIT;
	if(power_good_counter<100)
	  power_good_counter++;
	else
	  if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
      } else {
        setPowerMode(POWERMODE::OVERLOAD);
        unsigned int mA=motorDriver->raw2mA(lastCurrent);
        unsigned int maxmA=motorDriver->raw2mA(tripValue);
        DIAG(F("\n*** %S TRACK POWER OVERLOAD current=%d max=%d  offtime=%l ***\n"), isMainTrack ? F("MAIN") : F("PROG"), mA, maxmA, power_sample_overload_wait);
	power_good_counter=0;
        sampleDelay = power_sample_overload_wait;
	if (power_sample_overload_wait >= 10000)
	    power_sample_overload_wait = 10000;
	else
	    power_sample_overload_wait *= 2;
      }
      break;
    case POWERMODE::OVERLOAD:
      // Try setting it back on after the OVERLOAD_WAIT
      setPowerMode(POWERMODE::ON);
      sampleDelay = POWER_SAMPLE_ON_WAIT;
      break;
    default:
      sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
  }
}





// process time-edge sensitive part of interrupt
// return true if second level required
bool DCCWaveform::interrupt1() {
  // NOTE: this must consume transmission buffers even if the power is off
  // otherwise can cause hangs in main loop waiting for the pendingBuffer.
  switch (state) {
    case 0:  // start of bit transmission
      setSignal(HIGH);
      state = 1;
      return true; // must call interrupt2 to set currentBit

    case 1:  // 58us after case 0
      if (currentBit) {
        setSignal(LOW);
        state = 0;
      }
      else  {
        setSignal(HIGH);  // jitter prevention
        state = 2;
      }
      break;
    case 2:  // 116us after case 0
      setSignal(LOW);
      state = 3;
      break;
    case 3:  // finished sending zero bit
      setSignal(LOW);  // jitter prevention
      state = 0;
      break;
  }

  // ACK check is prog track only and will only be checked if 
  // this is not case(0) which needs  relatively expensive packet change code to be called.
  if (ackPending) checkAck();

  return false;

}[COLOR="#FF0000"][/COLOR]

void DCCWaveform::setSignal(bool high) {
  if (progTrackSyncMain) {
    if (!isMainTrack) return; // ignore PROG track waveform while in sync
    // set both tracks to same signal
    motorDriver->setSignal(high);
    progTrack.motorDriver->setSignal(high);
    return;     
  }
  motorDriver->setSignal(high);
}
 [COLOR="#B22222"]     
void DCCWaveform::interrupt2() {
  // set currentBit to be the next bit to be sent.

  if (remainingPreambles > 0 ) {
    currentBit = true;
    remainingPreambles--;
    return;
  }

  // beware OF 9-BIT MASK  generating a zero to start each byte
  currentBit = transmitPacket[bytes_sent] & bitMask[bits_sent];
  bits_sent++;

  // If this is the last bit of a byte, prepare for the next byte

  if (bits_sent == 9) { // zero followed by 8 bits of a byte
    //end of Byte
    bits_sent = 0;
    bytes_sent++;
    // if this is the last byte, prepere for next packet
    if (bytes_sent >= transmitLength) {
      // end of transmission buffer... repeat or switch to next message
      bytes_sent = 0;
      remainingPreambles = requiredPreambles;

      if (transmitRepeats > 0) {
        transmitRepeats--;
      }
      else if (packetPending) {
        // Copy pending packet to transmit packet
        for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
        transmitLength = pendingLength;
        transmitRepeats = pendingRepeats;
        packetPending = false;
        sentResetsSincePacket=0;
      }
      else {
        // Fortunately reset and idle packets are the same length
        memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
        transmitLength = sizeof(idlePacket);
        transmitRepeats = 0;
        if (sentResetsSincePacket<250) sentResetsSincePacket++;
      }
    }
  }  
}
[/COLOR]


// Wait until there is no packet pending, then make this pending
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
  if (byteCount >= MAX_PACKET_SIZE) return; // allow for chksum
  while (packetPending);

  byte checksum = 0;
  for (int b = 0; b < byteCount; b++) {
    checksum ^= buffer[b];
    pendingPacket[b] = buffer[b];
  }
  pendingPacket[byteCount] = checksum;
  pendingLength = byteCount + 1;
  pendingRepeats = repeats;
  packetPending = true;
  sentResetsSincePacket=0;
}

int DCCWaveform::getLastCurrent() {
   return lastCurrent;
}

// Operations applicable to PROG track ONLY.
// (yes I know I could have subclassed the main track but...) 

void DCCWaveform::setAckBaseline() {
      if (isMainTrack) return;
      int baseline = motorDriver->getCurrentRaw();
      ackThreshold= baseline + motorDriver->mA2raw(ackLimitmA);
      if (Diag::ACK) DIAG(F("\nACK baseline=%d/%dmA Threshold=%d/%dmA Duration: %dus <= pulse <= %dus"),
			  baseline,motorDriver->raw2mA(baseline),
			  ackThreshold,motorDriver->raw2mA(ackThreshold),
                          minAckPulseDuration, maxAckPulseDuration);
}

void DCCWaveform::setAckPending() {
      if (isMainTrack) return; 
      ackMaxCurrent=0;
      ackPulseStart=0;
      ackPulseDuration=0;
      ackDetected=false;
      ackCheckStart=millis();
      ackPending=true;  // interrupt routines will now take note
}

byte DCCWaveform::getAck() {
      if (ackPending) return (2);  // still waiting
      if (Diag::ACK) DIAG(F("\n%S after %dmS max=%d/%dmA pulse=%duS"),ackDetected?F("ACK"):F("NO-ACK"), ackCheckDuration, 
           ackMaxCurrent,motorDriver->raw2mA(ackMaxCurrent), ackPulseDuration);
      if (ackDetected) return (1); // Yes we had an ack
      return(0);  // pending set off but not detected means no ACK.   
}

void DCCWaveform::checkAck() {
    // This function operates in interrupt() time so must be fast and can't DIAG 
    
    if (sentResetsSincePacket > 6) {  //ACK timeout
        ackCheckDuration=millis()-ackCheckStart;
        ackPending = false;
        return; 
    }
      
    lastCurrent=motorDriver->getCurrentRaw();
    if (lastCurrent > ackMaxCurrent) ackMaxCurrent=lastCurrent;
    // An ACK is a pulse lasting between minAckPulseDuration and maxAckPulseDuration uSecs (refer @haba)
        
    if (lastCurrent>ackThreshold) {
       if (ackPulseStart==0) ackPulseStart=micros();    // leading edge of pulse detected
       return;
    }
    
    // not in pulse
    if (ackPulseStart==0) return; // keep waiting for leading edge 
    
    // detected trailing edge of pulse
    ackPulseDuration=micros()-ackPulseStart;
               
    if (ackPulseDuration>=minAckPulseDuration && ackPulseDuration<=maxAckPulseDuration) {
        ackCheckDuration=millis()-ackCheckStart;
        ackDetected=true;
        ackPending=false;
        transmitRepeats=0;  // shortcut remaining repeat packets 
        return;  // we have a genuine ACK result
    }      
    ackPulseStart=0;  // We have detected a too-short or too-long pulse so ignore and wait for next leading edge 
}
I highlighted timer stuff in red just for easier review.

Guess the questions I have is which timer is the best to use and will constructs like:
Code:
    case 1: interruptTimer= &TimerA; break;
    case 2: interruptTimer= &TimerB; break;
work?

Just for full code listing here is Timer.cpp (looks familar):
Code:
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)

#include "ATMEGA2560/Timer.h"

Timer TimerA(1);
Timer TimerB(3);
Timer TimerC(4);
Timer TimerD(5);

ISR(TIMER1_OVF_vect)
{
    TimerA.isrCallback();
}

ISR(TIMER3_OVF_vect)
{
    TimerB.isrCallback();
}

ISR(TIMER4_OVF_vect)
{
    TimerC.isrCallback();
}

ISR(TIMER5_OVF_vect)
{
    TimerD.isrCallback();
}

#elif defined(ARDUINO_AVR_UNO)      // Todo: add other 328 boards for compatibility

#include "ATMEGA328/Timer.h"

Timer TimerA(1);
Timer TimerB(2);

ISR(TIMER1_OVF_vect)
{
    TimerA.isrCallback();
}

ISR(TIMER2_OVF_vect)
{
    TimerB.isrCallback();
}

#endif
and VirtualTimer.h
Code:
#ifndef VirtualTimer_h
#define VirtualTimer_h

class VirtualTimer
{
public:
    virtual void initialize() = 0;
    virtual void setPeriod(unsigned long microseconds) = 0;
    virtual void start() = 0;
	virtual void stop() = 0;

    virtual void attachInterrupt(void (*isr)()) = 0;
    virtual void detachInterrupt() = 0;
private:

};

#endif
 
Sounds like a fun project :)
I had a very quick look at the library, seems like it is already prepared for different processors. There are two folders ATMEGA328 and ATMEGA2560 in which a class Timer is derived from the abstract base class VirtualTimer. So, looks like you only need implement the Interface defined by VirtualTimer to at least fix the Timer part of the project. Might be a bit tricky since the interface requests to separate the attachInterrupt from the initialization.

Looks like the timers need to be 16bit only and you need only 4 of them? -> I'd use the FTMs for the T3.x part and one TMR(QUAD) module for the T4x part.

If you want I can give it a try.
 
Sounds like a fun project :)
I had a very quick look at the library, seems like it is already prepared for different processors. There are two folders ATMEGA328 and ATMEGA2560 in which a class Timer is derived from the abstract base class VirtualTimer. So, looks like you only need implement the Interface defined by VirtualTimer to at least fix the Timer part of the project. Might be a bit tricky since the interface requests to separate the attachInterrupt from the initialization.

Looks like the timers need to be 16bit only and you need only 4 of them? -> I'd use the FTMs for the T3.x part and one TMR(QUAD) module for the T4x part.

If you want I can give it a try.

@luni
If you want to give it a try have fun. I already have a interface made up to control the track switches using a RA8875 and a Adafruit Motor shield so any help would be appreciated.

I was actually going to scrap the virtual timer part and make it Teensy specific but your approach would allow to put it back into the library since it seems to be actively managed and improved.

Thanks again
Mike
 
They are doing some tricks with the prescalers to extend the 16bit timers to 32bit. -> Might be easier to use the Teensy 32bit timers instead.
Do you have a version already which compiles for the Teensy? Would make experimenting easier...
 
Unfortunately no - only compiles on Mega or Uno (assuming I download another lib). Was just starting to do modifications and trying to understand the timers before I started hacking. I didn't notice the pre-scalers issue.

My initial thought from looking at the flowchart for the library I was basically going to start from scratch with the timers and use timertool to get the necessary callbacks the pwm signals. Then was delete everything else (well have to play with that one). Since you dug into it a bit more (off doing other things now - I am a morning person for programming) how about I do something with using QUAD timers and go from there and see if I can get it to compile. Am I making sense?

Guess you would call the waveform frequency for pwm was 58us and go from there. See: https://www.nmra.org/sites/default/files/standards/sandrp/pdf/s-9.1_electrical_standards_2006.pdf.
According the https://www.nmra.org/sites/default/files/standards/sandrp/pdf/s-9.1_electrical_standards_2006.pdf
the lowest frequency of the DCC is 1000/(61us+64us)=8kHZ with max 9.3kHz.
 
@luni
Almost got it to compile for the T4x using TeensyTimerTool but stuck getting this error:
Code:
C:\Users\Merli\AppData\Local\Temp\arduino_build_376992\sketch\DCCWaveform.cpp: In static member function 'static void DCCWaveform::begin(MotorDriver*, MotorDriver*, byte)':
DCCWaveform.cpp:45: error: 'interruptTimer' was not declared in this scope
     case 1: interruptTimer= &TimerA; break;
because I commented out this in DCCwaveform.cpp
Code:
#if !defined(TEENSYDUINO)
VirtualTimer * DCCWaveform::interruptTimer=NULL;      
#endif
so guess the question is now how to handle VirtualTimer in conjunction with TeensyTimerTool?

I pushed the changes a new branch in my fork if you want to give it a go.
https://github.com/mjs513/CommandStation-EX/tree/TeensyBranch
 
Just a quick update. Think I got the timers sorted out with your help and using a slightly different branch that uses simplified timers. Having other issues but not with timers which going to post to a separate thread.
 
Back
Top