Forum Rule: Always post complete source code & details to reproduce any issue!
Page 10 of 11 FirstFirst ... 8 9 10 11 LastLast
Results 226 to 250 of 262

Thread: TeensyTimerTool

  1. #226
    Senior Member
    Join Date
    Jan 2015
    Location
    France
    Posts
    145
    Thank you !

  2. #227
    Banned
    Join Date
    Oct 2020
    Posts
    6
    Great Tool, Luni!

  3. #228
    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 by drewhamiltonasdf; 11-14-2020 at 09:56 PM.

  4. #229
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,424
    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.

  5. #230
    Quote Originally Posted by luni View Post
    • 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.

    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.

  6. #231
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,424
    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.

  7. #232
    Junior Member
    Join Date
    Mar 2020
    Location
    Bristrol - UK
    Posts
    8
    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

  8. #233
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    13,729
    Quote Originally Posted by MatA View Post
    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 ...

  9. #234
    Junior Member
    Join Date
    Mar 2020
    Location
    Bristrol - UK
    Posts
    8
    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

  10. #235
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,424
    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/wi...nts-and-clocks

  11. #236
    Junior Member
    Join Date
    Mar 2020
    Location
    Bristrol - UK
    Posts
    8
    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\Teensy TimerTool\src\config.cpp:80:0:

    D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\Teensy TimerTool\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\Teensy TimerTool\src\config.cpp:84:44: required from here

    D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\Teensy TimerTool\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\Teensy TimerTool\src\Teensy/TCK/TCK.h:4:0,

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

    D:\Program Files\ARDUINO\hardware\teensy\avr\libraries\Teensy TimerTool\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\Teensy TimerTool\src\Teensy/TCK/TckChannelBase.h:3:0,

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

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

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

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

    virtual float getMaxPeriod() const = 0;

    ^
    ----------------

  12. #237
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,424
    That looks like a bug. Sorry, I don't use the LC much. Probably something LC specific.
    I'll have a look later today

  13. #238
    Junior Member
    Join Date
    Mar 2020
    Location
    Bristrol - UK
    Posts
    8
    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

  14. #239
    Junior Member
    Join Date
    Mar 2020
    Location
    Bristrol - UK
    Posts
    8
    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.

  15. #240
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,424
    @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(){
    }

  16. #241
    Junior Member
    Join Date
    Mar 2020
    Location
    Bristrol - UK
    Posts
    8
    @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

  17. #242
    Member
    Join Date
    Jun 2015
    Location
    Cambridge, UK
    Posts
    28
    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.

  18. #243
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,424
    Quote Originally Posted by badsector View Post
    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/TeensyTime...pported-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 by luni; 12-10-2020 at 05:53 PM.

  19. #244
    Member
    Join Date
    Jun 2015
    Location
    Cambridge, UK
    Posts
    28
    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.

  20. #245
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    6,768
    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:
    Click image for larger version. 

Name:	DCC_EX.PNG 
Views:	16 
Size:	75.8 KB 
ID:	23436 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;      
      
    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();
    }
    
    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();
    }
    
    
    // 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();
    }
    
    
    // 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;
    
    }
    
    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);
    }
          
    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++;
          }
        }
      }  
    }
    
    
    
    // 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

  21. #246
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,424
    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.

  22. #247
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    6,768
    Quote Originally Posted by luni View Post
    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

  23. #248
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,424
    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...

  24. #249
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    6,768
    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/f...dards_2006.pdf.
    According the https://www.nmra.org/sites/default/f...dards_2006.pdf
    the lowest frequency of the DCC is 1000/(61us+64us)=8kHZ with max 9.3kHz.

  25. #250
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    6,768
    @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/CommandSta...e/TeensyBranch

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •