Forum Rule: Always post complete source code & details to reproduce any issue!
Page 11 of 11 FirstFirst ... 9 10 11
Results 251 to 261 of 261

Thread: TeensyTimerTool

  1. #251
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    6,837
    @luni
    I did a kludge to DCCWaveform.cpp to use the IntervalTImer for T3.x and GPT for T4.1. I just did a test with the arduino motor shield with a T3.5 and hook up a LogicAnalzer to pin2 power and pin 12 (signal) and got the following waveform:
    Click image for larger version. 

Name:	Capture.jpg 
Views:	26 
Size:	31.2 KB 
ID:	23453

    The short pulses are 58.1us and the long about 116us - think within specs. 58 = digit 1 and 116 = digit 0. And Power pin goes high when I turn on track power. So think the kludge worked. Tomorrow may hook up a test track and see what happens. Here is the kludge (looking forward to see what you come up with):

    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;
    
    #if !defined(TEENSYDUINO)
    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")); 
    }
    
    #else
    
    void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
      mainTrack.motorDriver=mainDriver;
      progTrack.motorDriver=progDriver;
    
      mainTrack.setPowerMode(POWERMODE::OFF);      
      progTrack.setPowerMode(POWERMODE::OFF);
    
      // Initialise timer1 to trigger every 58us (NORMAL_SIGNAL_TIME)
      TimerA.beginPeriodic(interruptHandler, NORMAL_SIGNAL_TIME);
    
    
    
    }
    
    void DCCWaveform::setDiagnosticSlowWave(bool slow) {
      TimerA.beginPeriodic(interruptHandler, slow? SLOW_SIGNAL_TIME : NORMAL_SIGNAL_TIME);
      DIAG(F("\nDCC SLOW WAVE %S\n"),slow?F("SET. DO NOT ADD LOCOS TO TRACK"):F("RESET")); 
    }
    #endif
    
    
    
    
    
    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 
    }

  2. #252
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,435
    Great, looks like it works! Let the trains departure :-)

    Just a comment: If this is only for you, your method of hacking the teensy timers into the code is certainly fine. If you want to have it integrated to the library it might be better to just use the interface they provide for extension to other timers. It is easy to use: you only need to add a teensy folder containing a Timer.h file where you derive a Timer class from the abstract VirtualTimer class and fill in the 4 or 5 functions (initialize, setPeriod etc) they need. Since IntervalTimer and the timers from the TimerTool provide all this functionality you just need to relay their calls to the corresponding Teensy Timer calls. The rest of the library can stay more or less untouched (at least that seems to be the idea).
    The pull request I sent you yesterday shows how to do this. If you want to have different timers for different purposes (like you did), you can do that as well, just distinguish which one you create by the number they pass in the Timer constructor.

  3. #253
    Senior Member+ mjs513's Avatar
    Join Date
    Jul 2014
    Location
    New York
    Posts
    6,837
    Quote Originally Posted by luni View Post
    Great, looks like it works! Let the trains departure :-)

    Just a comment: If this is only for you, your method of hacking the teensy timers into the code is certainly fine. If you want to have it integrated to the library it might be better to just use the interface they provide for extension to other timers. It is easy to use: you only need to add a teensy folder containing a Timer.h file where you derive a Timer class from the abstract VirtualTimer class and fill in the 4 or 5 functions (initialize, setPeriod etc) they need. Since IntervalTimer and the timers from the TimerTool provide all this functionality you just need to relay their calls to the corresponding Teensy Timer calls. The rest of the library can stay more or less untouched (at least that seems to be the idea).
    The pull request I sent you yesterday shows how to do this. If you want to have different timers for different purposes (like you did), you can do that as well, just distinguish which one you create by the number they pass in the Timer constructor.
    Well ran a test this morning and the train has departed: https://forum.pjrc.com/threads/65956...and-Teensy-4-x !

    Am planning to redo what I did to use a Teensy specific Time.h file to conform to their format but just wanted to test to see if it would work. So guess next up is to work on your merge and see what I can do. Really appreciate the help. Also glad with the EEPROM lib updated as well. So guess there was added value in me trying to port over the lib

  4. #254
    Junior Member
    Join Date
    Mar 2020
    Location
    Bristrol - UK
    Posts
    8
    Hi Luni,
    Sorry to bother you again with what could be a Mat 101 issue. I have been experimenting more with timing routines and the Timer Tool, moving from the LC to a Teensy 3.5. with an Audio board.

    The project is Radio based. I have a user programmable time delay (from 5 mins to about 15-20 minutes). When this timer expires it transmits some Morse code then starts a new count down.
    There is another counter also running at a rate of 10ms.

    My code uses a periodic timer set to 10ms from this I generate my 1s, 20ms and 10ms timers - my original code used the Periodic t1 (TCK); This works just fine and my code is doing as expected, when the countdown expires Morse is generated.

    As my "project" (in a lose terms) grows I need to move away from the software interrupts. I started using the FTMx interrupt source. However it seams the Audio (I used the Audio design tool) breaks when I select FTMx. With a scope I see the data IN line (pin 22 A8PWM) becomes stuck until I change the code back to TCK. Is there any way around this FTMx resource issue?

    As per my last post this is a steep learning curve for me so let me thank you in advance for any advise/guidance you can give.

    Regards.
    Mat.

  5. #255
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,435
    AFAIK the audio library uses some FTM timers. Should be documented somewhere. Did you try to use another FTM module?

  6. #256
    Member Van's Avatar
    Join Date
    Mar 2018
    Location
    Dresden, Germany
    Posts
    54
    Hi there!
    How can I change frequency without having to restart it?
    i'm trying to use it as a BPM Clock.
    Cheers!

  7. #257
    @luni
    I was using 0.1.10 Release till date and today I updated to 0.3.2 release.
    I see that my code is not working anymore. Here is the example that I was using earlier.
    I have this code working with correct timing on 0.1.10 Release + 600/528/150 Mhz

    Instead of 10mSec I am gettting this function called at every 1.5uSec Approx.
    Click image for larger version. 

Name:	Screenshot_16.png 
Views:	13 
Size:	10.6 KB 
ID:	23659

    Code:
    /********************************************************
     * Basic usage of the timer
     * 
     * Generates a timer from the timer pool and
     * starts it with a period of 250ms. 
     * The timer callback simply toggles the built in LED
     * 
     ********************************************************/
    
    #include "TeensyTimerTool.h"
    
    using namespace TeensyTimerTool;
    PeriodicTimer t1(PIT);
    
    void vStartTimer()
    {
      t1.begin(callback, 10'000); //10mSec
    }
    
    void vStopTimer()
    {
      t1.begin([] {}, 10'000); //10mSec  
    }
    
    void callback()
    {
        digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));    
    }
    
    void setup()
    {
        pinMode(LED_BUILTIN,OUTPUT);   
        vStartTimer();
    }
    
    void loop()
    {   
    }

  8. #258
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,435
    @luni
    I was using 0.1.10 Release till date and today I updated to 0.3.2 release.
    I see that my code is not working anymore. Here is the example that I was using earlier.
    I have this code working with correct timing on 0.1.10 Release + 600/528/150 Mhz

    Instead of 10mSec I am gettting this function called at every 1.5uSec Approx.
    Strange, this is the most simple application and should work of course. I'm currently not at home and don't have a T4 with me for testing I can have a closer look on Friday. Does it work with other timers (e.g. GPT1, TCK)?

  9. #259
    I'm on a T3.2, so I unless I've misunderstood something, I only have FTM0, FTM1 and FTM2 to use?
    (and now realizing that you probably replied to @HallMark and not me ;-) )

  10. #260
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,435
    Quote Originally Posted by jensa View Post
    @luni TeensyStep works great, but I've run into a conflict with the PWM timer (as mentioned here https://github.com/luni64/TeensyStep/issues/51). I've tried doing as you suggest in this issue by changing

    Code:
    #define USE_TIMER TIMER_DEFAULT
    to

    Code:
    #define USE_TIMER TIMER_FTM1
    I'm on a T3.2, so I have limited timers available. I think the problem is that I'm using one RotateControl and one StepControl that need to work independently?
    My project needs PWM, so I need to not use TIMER_FTM0 at all.

    So I wonder:
    - Is it so that each instance will grab a new timer?
    - Do you have a suggestion as to how to solve this?

    Thanks!
    Looks like you are in the wrong thread? This is TeensyTimerTool, not TeensyStep :-)
    Anyway, on a T3.2 FTM1 only has 2 channels. A controller needs one PIT and two FTMs so, you can only use one controller if you selected FTM1 (same with FTM2). Couldn't you switch your PWM pins so that you can use FTM0 for the Steppers and FTM1/FTM2 for PWM?

  11. #261
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,435
    Quote Originally Posted by HallMark View Post
    @luni
    I was using 0.1.10 Release till date and today I updated to 0.3.2 release.
    I see that my code is not working anymore. Here is the example that I was using earlier.
    I have this code working with correct timing on 0.1.10 Release + 600/528/150 Mhz

    Instead of 10mSec I am gettting this function called at every 1.5uSec Approx.
    Click image for larger version. 

Name:	Screenshot_16.png 
Views:	13 
Size:	10.6 KB 
ID:	23659
    Sorry for the delay, I finally found some time trying your code. I can not reproduce the problem. It generates nice 10ms calls here. Do you still have this problem?

Posting Permissions

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