Hello everyone, I'm new on this forum and I need your help to optimize my Arduino code:
I'm building a "barn door astrophotography tracker" which allows me to perform long exposures to the night sky by avoiding the star-trail effect on the image.
The rotation is done with a NEMA-17 step motor driven by a TB6600 driver, connected to an Arduino UNO R3 board, and here below you can see the code I'm using for it:
cconst int PUL = 7; // Pulse pin
const int DIR = 6; // Direction pin
const int ENB = 5; // Enable Pin
const float STP = 6400; // Motor Steps per Revolution
const float MULT = 3; // Gears multiplier
float HalfStepDelay = 1;
void setup() {
pinMode (PUL, OUTPUT);
pinMode (DIR, OUTPUT);
pinMode (ENB, OUTPUT);
digitalWrite(ENB, HIGH);
digitalWrite(DIR, LOW); // Forward Direction
float StepDelay = 60000000 / (STP * MULT); // 3125
HalfStepDelay = (int)(StepDelay / 2); // 1562
}
void loop() {
digitalWrite(PUL, LOW);
delayMicroseconds(HalfStepDelay);
digitalWrite(PUL, HIGH);
delayMicroseconds(HalfStepDelay - 35 );
}
I want to have the final gear to make 1 turn per minute, so (as the gear reduction is 1:3), the motor should do 3 turn per minute.
Everything works fine (but suggestions to optimize it are always welcome), but you'll notice that I had to remove those "35" microseconds to the last command in order to have the correct rotation speed (I got this value just by empirically testing various values) because, I think, those 35us are the ones "stolen" by the execution of the program itself.
So this means that if I add more code lines (i.e.: let's say I want to add some input button check, or maybe an output display to show the current speed, or stuff like that) the code will take more time to run and the final speed of the motor will be impacted (and '35' will need a different value).
This causes the 'HalfStepDelay' value to become hard to calculate, so I'm looking a better solution to keep my rotation speed stable, predictable, and not impacted by the code itself.
For example:
Implement something different than Delay to separate the motor's steps
Make the Delay to rely on system's clock (i.e.: change the motor's state if 'x' clock tiks have been elapsed since last step)
The only way you will achieve a truly consistent, accurate step rate, limited only by the accuracy of your Arduinos CPU clock frequency, is to use a hardware timer to issue the step pulses. Any other approach, and you will be at the mercy of interrupts, which will delay some of your pulses. Using a timer, they WILL occur at the precise interval you program the timer for.
And, many Arduinos use ceramic resonators, rather than precision quartz crystals, for their clocks, which can both have significant frequency error, and the frequency can change significantly with temperature. So, for best performance, you should use an Arduino with a crystal, not a resonator.
Using a hardware-timer rises another question. You can't sweep through all frequencies with a hardware-timer
because the timer has a pre-scaler.
3 turns in one minute in halfstep-mode means:
3*400 = 1200 steps per minute
1200/ 60 = 20 steps per second.
Assuming you need exact 20 steps per second: can the hardware-timer be setup to create exact 20 pulses per second?
This all is about longterm-stability. A simple step that is gliched for some microseconds doesn't play a role.
So using micros() might be a solution too because the waiting-time is 25000 microseconds
A chrystal will be sure more precise than a ceramic resonator.
How about using a microcontroller with a higher cpu-frequency?
StefanL38:
Using a hardware-timer rises another question. You can't sweep through all frequencies with a hardware-timer
because the timer has a pre-scaler.
3 turns in one minute in halfstep-mode means:
3*400 = 1200 steps per minute
1200/ 60 = 20 steps per second.
Assuming you need exact 20 steps per second: can the hardware-timer be setup to create exact 20 pulses per second?
This all is about longterm-stability. A simple step that is gliched for some microseconds doesn't play a role.
So using micros() might be a solution too because the waiting-time is 25000 microseconds
A chrystal will be sure more precise than a ceramic resonator.
How about using a microcontroller with a higher cpu-frequency?
best regards Stefan
First, that is not an issue at all for something that is running at constant speed. Second, it is not a limitation even if you are doing acceleration ramps. You can use a single, fixed divider, and skip the output of some steps to stretch the time between actual step outputs. If you need a step rate that is twice the full duration of the counter, you only actually output a step pulse every other time the counter rolls over. You can cover the entire range of speeds from barely a crawl to the full speed the motor can handle with a single well-chosen divider setting. At the slower speeds you just might be only outputting a step pulse every 16, 32 or more counter cycles. This is one the many reasons the timers can be set to NOT change the output pin on rollover or compare match. You can also change the counter compare value on every cycle, by enabling the compare interrupt. That is how you generate acceleration and deceleration ramps where the step time changes on every single step.
This tutorial Multi-tasking in Arduino covers driving a stepper motor at a constant speed using the AccelStepper library.
That tutorial includes details on how to measure the time the rest of your code is taking and how to 'compensate' for that.
Basically for 'stable' speed you need to call the AccelStepper.run() method often and then just let it do its work.
The tutorial covers using the loopTimer class (available in my SafeString library, from the library manager) to measure the variation the stepper speed.
Really thanks to everyone for your precious help: a lot of good ideas and things to try.
As you already understood, even if I'm using the 35 µsecs adjustment, my "real" target is to feel free to add more lines of code (let's say I want to add buttons to increase/decrease the speed and a display for speed monitoring) without impacting the "real" speed of the motor itself caused by other commands execution time.
I'll do some test following your suggestions, and for sure I'll follow-up in this thread soon with my findings, so thanks again everyone!
Robin, actually the solution that I'm testing now is the one suggested by you, by using micros() for motor's step triggering, so I'll be able to give a feedback about it quite soon
Thank you
A.
I fail to understand the resistance to simply using a hardware timer to issue the step pulses. It gives you dead accurate, consistent, jitter-free step pulses, and leaves your "foreground" code fully available to do whatever other work you please, even using delay(), Serial, etc., without having to worry at all about screwing up the step timing. The timer code is not at all complex. Being able to start a move, then go away and have it carry on totally in the background makes the whole application much simpler, and you'll never have to worry about a mis-timed step causing the motor to stall. And, if it matters (perhaps not in this application, since the motor is presumably turning rather slowly), it is easy to add any desired acceleration and deceleration curves.
Right: No problem on using the timer HW (really I just ordered one a couple of days ago, which may help with this or maybe future projects) but I wanted to explore the possible solutions with the hardware I have right now, so I'm not excluding it at all, I'm just saving it for last.