help with digitalWriteFast function

eyal20

New member
Hi all !

I'm building a fast controller to control 3 stepper motors using 3 high resolution step / dir drivers DRV8434 and a teensy 4.0.
Code platform is Arduino IDE.

I will not upload the entire code, just explain that it uses a main "xyz.ino" file to handle mostly the communication with the PC via a serial communication, and a class library I wrote to handle all the motor's functionalities.
// ### "xyz.ino" ###

#include "classes.h"

// all pins are const uint8_t

Motor m[3] = {
Motor("X" , &X_STEP_PIN , &X_DIR_PIN , X_SW_PIN , X_RES_0_PIN , X_RES_1_PIN ),
Motor("Y" , &Y_STEP_PIN , &Y_DIR_PIN , Y_SW_PIN , Y_RES_0_PIN , Y_RES_1_PIN ),
Motor("Z" , &Z_STEP_PIN , &Z_DIR_PIN , Z_SW_PIN , Z_RES_0_PIN , Z_RES_1_PIN )
// (Name, Step Pin , Dir Pin , Sw Pin , Resolution0 Pin , Resolution1 Pin )
};

void setup{
//...
}

// --------------------------- LOOP -------------------------
void loop(){
do_computer_command();
m[0].move_motor();
m[1].move_motor();
m[2].move_motor();
}

class header file. step and dir pins are pointers to a const uint8_t:
// ### "classes.h" ###

#include <Arduino.h>
class Motor{

private:
String name; // the name of the motor for example "X axis" / "Y"
const uint8_t *StepPin; // the pulse sending pin of the MCU to control "step" pin in driver
const uint8_t *DirPin; // the direction pin of the MCU to control "dir" pin on driver
//...

public:
//...

};

other function calls to move the motor's steps according to a step counter (not showed here), it calls the "move_step()" function at the correct timing.
// ### "classes.cpp" ###

#include "classes.h"

Motor::Motor(String n, const byte *s, const byte *d, byte sw, byte m0){ // constructor
name = n;
StepPin = s;
DirPin = d;
LimitSwPin = sw;
pinMode(*StepPin, OUTPUT);
pinMode(*DirPin, OUTPUT);
pinMode(LimitSwPin, INPUT_PULLUP);
sw_state = digitalRead(LimitSwPin);
m0Pin = m0;
}

/*
-- other functions --
*/

void Motor::move_step(){
if(do_step){
if (do_high){
digitalWriteFast(*StepPin, HIGH);
step_cyc = ARM_DWT_CYCCNT;
do_high = false;
}
else if (ARM_DWT_CYCCNT - step_cyc > 420){
digitalWriteFast(*StepPin, LOW);
do_step = false;
current_location = current_location + ((moving_dir)?1:-1);
steps_done++;
}
}
}

there are no delays and no interrupts in the entire code.
I need to reach a very high speeds so I'm using the internal cycle counter (ARM_DWT_CYCCNT) as a timing reference to call this function at the correct "timing" from a "parent" function, so according the DRV8434 datasheet HIGH must remain at least 700ns and low at least 300ns for the driver to work properly. but to activate 3 motors "in parallel" I need every MCU cycle to be used efficiently as possible, so meanwhile I'm "waiting" those 700ns the MCU needs to process the other motors commands to make the movement smooth as possible.

It is highly important at very high speeds (where the gap between steps can be short as ~2us) to be consistent with the speed and acceleration or else the stepper will loose steps.

So I know the digitalWriteFast() function must receive a constant pin number to work fast, otherwise it will be same as digitalWrite() slow. at those high speeds I can't notice any different when using the Fast write and regular write, so I think the use of pointers did not work as a solution for the constant value needed for the fast function.

Do you have any advice on how to use a class which will controls 3 motors and gets the pin number at the beginning but let the Fast digital write work ?

Thanks :)
 
Actually the digitalWriteFast code is still pretty fast when you pass in a non-constant pin...

That is: it boils down to:
Code:
static inline void digitalWriteFast(uint8_t pin, uint8_t val)
{
        if (val) {
            *portSetRegister(pin) = digitalPinToBitMask(pin);
        } else {
            *portClearRegister(pin) = digitalPinToBitMask(pin);
        }
    }
}
Where the port set and clear are macros:

Code:
#define portSetRegister(pin)     ((digital_pin_to_info_PGM[(pin)].reg + 33))
#define portClearRegister(pin)   ((digital_pin_to_info_PGM[(pin)].reg + 34))
#define digitalPinToBitMask(pin) (digital_pin_to_info_PGM[(pin)].mask)

So it simply indexes into an array of structures for each pin and grab a value for address of the register plus a bias for the
correct register...
...
If you need it faster, you could for example do the similar thing:

Setup at init time:
Code:
volatile uint32_t *pinXSetRegister = portSetRegister(pin1);
volatile uint32_t *pinXClearRegister = portClearRegister(pin1);
uint32_t pinXMask = digitalPinToBitMask(pin1);

More likely you will have the defines maybe global maybe part of class, and the initialization done at init time. But...

When you wish to set the pin: *pinXSetRegister = pinXMask;
ditto for clear.
 
Do you have any advice on how to use a class which will controls 3 motors and gets the pin number at the beginning but let the Fast digital write work ?


Here's a few things to try:

1: Move the variable from assignments inside the code to C++ initialization list syntax. For example, change this:

Code:
Motor::Motor(String n, const byte *s, const byte *d, byte sw, byte m0){        // constructor
 name = n;
 StepPin = s;
 DirPin = d;
 LimitSwPin = sw;

to this:

Code:
Motor::Motor(String n, const byte *s, const byte *d, byte sw, byte m0) : name(n), StepPin(s), DirPin(d), LimitSwPin(sw)
{        // constructor


2: Add a begin() function to initialize hardware, so your constructor initializes only the class member stuff.

Move these to begin(). Keeping this hardware init outside the constructor will help the compiler to "know" your class init.

Code:
pinMode(*StepPin, OUTPUT);
pinMode(*DirPin, OUTPUT);
pinMode(LimitSwPin, INPUT_PULLUP);
sw_state = digitalRead(LimitSwPin);


3: Inline the constructor within the class definition in your .h file. Don't put it in the .cpp file.

For example:

Code:
// ### "classes.h" ###

#include <Arduino.h>
class Motor{
  Motor(String n, const byte *s, const byte *d, byte sw, byte m0) : name(n), StepPin(s), DirPin(d), LimitSwPin(sw)
    {        // constructor


4: Use "constexpr" on the inline class constructor. Mentally prepare yourself for the code to no longer compile. If you're doing things in such a way that the compiler won't know the constant value at compile time, adding constexpr is supposed to result in compile errors. Even though it's some initial pain, you actually want this because it will help you to give the compiler whatever it needs to fully initialize the class at compile time.

Code:
// ### "classes.h" ###

#include <Arduino.h>
class Motor{
  constexpr Motor(String n, const byte *s, const byte *d, byte sw, byte m0) : name(n), StepPin(s), DirPin(d), LimitSwPin(sw)
    {        // constructor

With constexpr constructor, you may need to give any class variables not in the initialization list an assignment in the class declaration in the .h file.

Ideally your constructor can become just an empty constexpr function with everything done by the initialization list.


5: Try also using constexpr on the class variables for the pin numbers.


Hopefully some combination of these suggestions will give you the full benefit of digitalWriteFast() from within your class functions.


I will not upload the entire code, just explain that

Don't show us a huge program. But if none of these suggestions or other people's ideas work, please do consider putting the effort into composing a relatively small but complete program which just toggles some pins at high speed and measures the time for 100000 interations using the ARM cycle counter.

We're much better at answering and helping when you show a complete program which anyone can run on their Teensy and see the issue.

This is really a C++ question. The reality is many of us here (myself included) mostly think in terms of C with only simple use of classes. But there are a few really amazing C++ experts here. To get a more active conversation going, hopefully enough to catch the eye of those C++ gurus, you really need to show code that reproduces the issue.
 
Last edited:
One more suggestion, try using const char *name instead of Arduino's String class to know the name of each motor.
 
Yet another suggestion... or really a question, is regarding the use of pointers for StepPin and DirPin. Why do this?

Consider you're telling the compiler to go to the extra work of reading the pin number from somewhere else. At least from the code shown, my impression is you want each class to work with specific pins. If you want the compiler to generate the fastest code, just giving it the actual pin number would seem a better choice.

If you absolutely do need the value taken from somewhere else, consider using with a C++ reference syntax, or define the pointer with "const" like this:

Code:
const uint8_t * const StepPin;

This 2nd use of "const" is (sometimes) critically important for the compiler to generate faster code. The 1st "const" tells the compiler you won't use the pointer to alter memory, that it's read-only. The 2nd "const" tells the compiler you will never alter the point to access anywhere else. If you want the compiler to apply optimizations, you really want it to know some other code can't possibly cause the pointer to read-only access some other byte.

Using C++ reference rather than pointers always has this 2nd const property, so unless you really want old C style syntax, might be better to use the simple C++ reference syntax.

But unless there is some really compelling need for these to be pointers or references, which I can't understand from just reading the code fragments, you probably should just initialize each class instance with the actual pin numbers rather than using pointers.
 
Back
Top