Giter VIP home page Giter VIP logo

teensyhelpers's Introduction

This repository contains some helper functions and classes for the PJRC Teensy boards:

Content

  • IntervalTimerEx
    Subclasses the standard IntervalTimer to allow passing state to callbacks. You can choose between attaching std::function<void()> callbacks or the traditional void pointer pattern.

  • attachInterruptEx
    Overloads the attachInterrupt function to allow attaching std::function<void()> callbacks.

  • pinModeEx
    Overloaded version of the pinMode function which can set the pin mode for a arbitrary long list of pins.

  • attachYieldFunc
    Add your own function to the yield call stack

  • teensy_clock
    Use the new c++11 std::chrono time system to implement a std::chrono compliant clock which uses the cycle counter as time base. It counts time in 1.667ns steps (1/F_CPU) since 0:00h 1970-01-01.

  • instanceList
    Helper to automatically maintain a list of all active objects of a class and call member functions on all of these objects. Helpful for example if you need to periodically tick all existing objects of a class.

All functions and classes use the underlying Teensyduino mechanisms and bookkeeping. They can mixed with the standard ones.

Installation:

I didn't bother to make this a library. To use it just copy the *.h and *.cpp files from the corresponding subfolders of /src into your sketch to use them.


IntervalTimerEx

IntervalTimerEx subclasses the stock IntervalTimer so that you can pass state to the timer callbacks. IntervalTimerEx provides either a traditional void(*)(void*) pattern or, more modern std::function<> typed callbacks. You can choose which to use with a preprocessor switch in IntervalTimer.h

  • std::function approach:
    Usage is exactly the same as with the normal IntervalTimer. However, it accepts more or less anything witch can be called (functions, member functions, lambdas, functors) as callbacks.

  • Traditional void pointer approach:
    Might be more familiar to some users. Uses less resources, doesn't use dynamic memory. You define the state which is passed to the callback function in IntervalTimer.begin().

Examples:

std::function approach

#include "IntervalTimerEx.h"

// plain vanilla void(*)() callback
void myCallback_0(){
    Serial.print("myCallback_0 ");
    Serial.println(millis());
}

// need to manipulate the calling timer in the callback?
void myCallback_1(IntervalTimer* caller){
    Serial.print("--> myCallback_1 ");
    Serial.println(millis());

    caller->end(); // e.g. do a one shot timer
}

IntervalTimerEx t1, t2;

void setup(){
    t1.begin(myCallback_0, 150'000);
    t2.begin([] { myCallback_1(&t2); }, 1'000'000);  // attach callback using a lambda
}

void loop(){
}

Embedding in a user class

#include "IntervalTimerEx.h"

class DeepThought
{
 public:
    void begin()
    {
        answer = 42;
        t1.begin([this] { ISR(); }, 7.5E6);  // 7.5s
    }

 protected:
    IntervalTimerEx t1;
    int answer;

    void ISR()
    {
        Serial.printf("The answer is %d\n", answer);
    }
};

//----------------------------

DeepThought deepThought;

void setup(){
    deepThought.begin();
}

void loop(){
}

Traditional void pointer approach:

Comment out the preprocessor switch #define USE_CPP11_CALLBACKS in IntervalTimerEx.h to use this pattern.

One shot timer

#include "IntervalTimerEx.h"

// implement a one shot timer by using the timer address
// passed to the callback
void myCallback_1(void* state){
    IntervalTimer* timer = (IntervalTimer*)state;
    Serial.printf("Called @\t%d ms\n", millis());
    timer->end();
}
//------------------

IntervalTimerEx t1;

void setup(){
    while(!Serial);
    Serial.printf("Triggered @\t%d ms\n", millis());
    t1.begin(myCallback_1, &t1, 200'000); // pass the address of the timer to the callback
}

void loop(){
}

Output:

Triggered @	388 ms
Called @	588 ms

Embedding an IntervalTimer in a user class

class DeepThought
{
 public:
    void begin()
    {
        answer = 42;
        t1.begin(ISR, this, 7.5E6); // 7.5s
    }

 protected:
    IntervalTimerEx t1;
    int answer;

    static void ISR(void* obj)
    {
        DeepThought* THIS = (DeepThought*)obj;
        Serial.printf("The answer is %d\n", THIS->answer);
    }
};

DeepThougth deepThought;

void setup(){
    deepThought.begin();
}

void loop(){
}

attachInterruptEx

You can use attachInterruptEx in exactly the same as you use the standard attachInterrupt function. However, it accepts more or less anything witch can be called (functions, member functions, lambdas, functors) as callbacks.

Examples

Shows how to encapsulate and setup a pin interrupt callback function in a user class. The EdgeCounter class simply counts all edges (rising/falling) it sees on a digital pin. The pin to be monitored is settable with the begin() function.

#include "attachInterruptEx.h"

class EdgeCounter
{
 public:
    void begin(unsigned pin)
    {
        counter = 0;
        pinMode(pin, INPUT_PULLUP);
        attachInterruptEx(pin, [this] { this->ISR(); }, CHANGE);
    }

    unsigned getCounter() { return counter; }

 protected:
    void ISR(){
        counter++;
    }

    unsigned counter;
};

//-------------------------

EdgeCounter ec1, ec2;

void setup(){
    ec1.begin(0);
    ec2.begin(1);
}

void loop(){
    Serial.printf("Detected edges: Pin1: %u Pin2:%u\n", ec1.getCounter(), ec2.getCounter());
    delay(250);
}

pinModeEx

One often has to define the pin mode for a bunch of pins which can be a bit tedious. In the folder src/pinModeEx you find an overloaded version of the pinMode function which allows to set the mode for an arbitrary large list of pins.

Usage:

#include "pinModeEx.h"

const int pinA = 3, pinB = 17, switch1 = 3, switch2 = 4;

void setup(){
    pinMode({pinA, pinB, 17, LED_BUILTIN}, OUTPUT);  // set a bunch of pins to mode OUTPUT...
    pinMode({switch1, switch2}, INPUT_PULLUP);       // others to INPUT
}

attachYieldFunc()

Sometimes your have code that needs to be called by the user as often as possible. Prominent examples are AccelStepper where you are required to call run() at high speed. Using the debounce library, you need to call update(). The PID library requires a frequent call to Compute() and so on.

Usually, the user of these libraries just calls these functions in loop which works fine for simple code. As soon as there is longer running code or some delays in loop the call rate of these functions can get unacceptably low and the libraries don't work as expected.

yield()

Teensyduino provides a yield() function which is called before each call to loop(), during delay() and probably during a lot of other long running core functions while they spin.

If you want to add your own functions to be called from yield without completely overriding it, you can use attachYieldFunc(callback) which is defined in the src/attachYieldFunc folder of the repository. Here an example which will call myCallback() from yield to toggle pin 0 in the background. The delay(250) in loop does not disturb the high frequency background toggling since delay() calls yield while it spins.

#include "attachYieldFunc.h"

void myCallback(){ //will be called from yield
  digitalToggleFast(0);
}

void setup(){
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(0, OUTPUT);

  attachYieldFunc(myCallback); // attach our callback to the yield stack
}

void loop(){
  digitalToggleFast(LED_BUILTIN);
  delay(250);
}

teensy_clock

This extension implements a clock compliant to the new (>c++11) chrono::system_clock.

It uses a 64bit extension of the cycle counter where rollover is handled by the periodic timer of the SNVS_HPCR module.

The teensy_clock can be synced to the the built in RTC. It doesn't interfere with the normal use of the RTC functions and works with or without battery. Other than the usual Teensy RTC functions which are based on full seconds, the teensy_clock uses the full resolution of the cycle counter which is 1/600MHz = 1.6667 ns for a T4@ 600MHz.

A full description of the code can be found in the Teensy User WIKI: https://github.com/TeensyUser/doc/wiki/fun-with-modern-cpp#start

Example:

#include <chrono>
using namespace std::chrono;

typedef system_clock::time_point timePoint;               // save typing...

void setup(){
    while(!Serial){}
}

void loop()
{
    timePoint start = teensy_clock::now();                // generate two time points 1234ms apart
    delay(1234);
    timePoint stop = system_clock::now();

    nanoseconds  dt    = stop - start;                    // system clock has an underlying ns counter
    milliseconds dt_ms = duration_cast<milliseconds>(dt); // cast dt to milliseconds (float)

    Serial.printf("stop-start: %u ms\n", dt_ms.count());

    time_t t = system_clock::to_time_t(start);            // convert the time_point 'start' to usual time_t
    Serial.printf("Current Time: %s\n", ctime(&t));       // pretty print date/time
}

Which prints out:

stop-start: 1234 ms
Current Time: Sun Oct 18 21:01:37 2020

stop-start: 1234 ms
Current Time: Sun Oct 18 21:01:38 2020

stop-start: 1234 ms
Current Time: Sun Oct 18 21:01:39 2020

stop-start: 1234 ms
Current Time: Sun Oct 18 21:01:40 2020
...

Use the system_clock to wait...

#include <chrono>
using namespace std::chrono;

void setup(){
}

void loop()
{
    auto start = teensy_clock::now();  // current time point

    while (teensy_clock::now() < (start + 1h + 30min)) // use chrono literals
    {
        yield(); // waiting...
    }

    Serial.println("done");
}

instanceList

This helper class automatically maintains a list of all currently existing instances of a class regardless if they are constructed on the stack, the heap or in global space. The list of instances is accessible using standard c++ iterators.

Possible use cases are classes where you need to periodically call a function on all objects.

Example

Here a very simple example which demonstrates the usage of the instanceList helper class.

Let's assume we want to define a Blinker class which periodically toggles a pin. Pin number and toggling period will be set in the constructor.

To toggle the pin we implement a blink() member function. Whenever blink() is called, it simply checks an elapsedMicros variable to determine if it is time to toggle the pin. To get good performance, it is important to call blink() as often as possible.

class Blinker
{
 public:
    Blinker(int _pin, unsigned ms)
        : pin(_pin), period(ms * 1000)
    {
        pinMode(pin, OUTPUT);
    }

    void blink()
    {
        if (t > period)
        {
            digitalToggleFast(pin);
            t -= period;
        }
    }

 private:
    elapsedMicros t;
    unsigned pin, period;
};

Usage

Blinker b(0,10); // toggles pin 0 every 10ms

void setup()
{
}

void loop()
{
    b.blink();
}

So far, so good. However, if you need a lot of those Blinkers, the code can get messy quickly. Also, those Blinkers might as well be defined in a different file or in some functions and it can get difficult to keep track of all the existing Blinkers.

Blinker b1(0,10);
Blinker b2(7,100);
Blinker b3(2,24);
Blinker b4(13,500);

void setup()
{
}

void loop()
{
    b1.blink();
    b2.blink();
    b3.blink();
    b4.blink();
}

Of course, it would be a lot simpler if one could call the blink() function for all currently existing Blinker instances at once. Using the instanceList this can be done easily. All we need to do is derive the Blinker class from instanceList. To blink all instances we define a static function tick() which uses the list of instances provides and calls blink on each of them:

class Blinker : protected InstanceList<Blinker>  // <- derive from InstanceList
{
 public:
    Blinker(int _pin, unsigned ms)
        : pin(_pin), period(ms * 1000)
    {
        pinMode(pin, OUTPUT);
    }

    void blink()
    {
        if (t > period)
        {
            digitalToggleFast(pin);
            t -= period;
        }
    }

    static void tick()
    {
        for (Blinker& b : instanceList) // for each Blinker b in the instance list
        {
            b.blink();
        }
    }

 private:
    elapsedMicros t;
    unsigned pin, period;
};

Usage:

Blinker b0(0, 10);
Blinker b1(1, 60);
Blinker b2(2, 24);
Blinker b3(3, 150);
Blinker LED(13, 500);

void setup()
{
}

void loop()
{
    Blinker::tick();
}

Now, we can add and remove Blinkers without having to to update the calls to blink() accordingly.

You can use the instanceList for other things as well. Assume you want to print out information about all currently exisiting Blinker instances. Just add the following member function to the Blinker class:

static void InstanceInfo()
{
  for (Blinker& b : instanceList) // for each object in objectList
  {
     Serial.printf("pin: %2d, period (µs): %6d, address: %p\n", b.pin, b.period, &b);
  }
}

Then, you can print the information by:

Blinker b0(0, 10);
Blinker b1(1, 60);
Blinker b2(2, 24);
Blinker b3(3, 150);
Blinker LED(13, 500);

void setup()
{
    while(!Serial){}
    Blinker::InstanceInfo();
}

void loop()
{
    Blinker::tick();
}

which prints:

pin: 13, period (µs): 500000, address: 0x20001864
pin:  3, period (µs): 150000, address: 0x20001908
pin:  2, period (µs):  24000, address: 0x200018f8
pin:  1, period (µs):  60000, address: 0x200018e8
pin:  0, period (µs):  10000, address: 0x200018d8

teensyhelpers's People

Contributors

luni64 avatar lunoptics avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

teensyhelpers's Issues

Typo in attachYieldFunc.cpp

In TeensyHelpers I could not compile attachYieldFunc.cpp. The compiler said it could not find
'Eventresponder.h'. That was because the EventResponder include filename is 'EventResponder.h'.
The 'r' in the filename was in lower case instead of upper case. I fixed that and it compile ok.

Thanks for creating these great libraries. I am having a lot of fun testing them with MSC. I have made some good progress with using them.

Warren

Installation with PlatformIO

Hi,
thanks for this nice helper library! I wanted to use teensy_clock in my PlatformIO project with a Teensy 3.1. Unfortunately it was unclear how to properly incorporate the library in my source code.
What I tried so far:

  • Putting the teensy_clock folder in the lib subfolder of my project
  • Putting the teensy_clock folder in the src subfolder of my project
  • Putting the teensy_clock files directly into the src subfolder of my project
  • All of the above variants with the .cppx file renamed to cpp

I always get an error at link time:

.pio/build/teensy31/src/main.cpp.o: In function `L_214_delayMicroseconds':
main.cpp:(.text._Z20perform_needed_stepshR7StepperS0_S0_S0_+0x28): undefined reference to `cycles64::get()'
collect2: error: ld returned 1 exit status
*** [.pio/build/teensy31/firmware.elf] Error 1

(it is a different error when I rename the .cppx file)

Do you have any hints for me?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.