I have a arduino uno rev2 wifi board (with 2 UARTS) and Serail1 is connected to a rapsberry Pi. The arduino is also driving two nema 17 steppers for a high precision rover robot. Serial1 is used to transmit odometery (every time a step happens).
Now the issue which I’m having trouble debugging due to the Heisenberg principle (trying to output to serial to debug exacerbates the problem!) is that whenever I write the serial the stepper motor timing is severely affected. I haven’t been able to measure this yet but it is significant enough to make it go a bit zig zag. RPMs drop considerably as well. Without the Serial1.write everything works peachy!
Seems like Serial interrupts might be causing the issue - but I’m usually running the motors at 200rpm, which gives me 1500us in the delay. odom_struct is a 8-byte buffer.
What am I doing wrong and what are some good options to debug this?
Yep, looks as if everything is wrong. Using delay instructions, printing multiple characters in bursts.
For the performance you seek, you will need to write code that interleaves the step timing and printing characters, rapidly alternating between determining whether the necessary time has elapsed for the next step, and whether the UART is ready for another character.
I have some difficulty understanding what "every time a step happens" means; if steps are fast, then you would not expect to have sufficient time to print in any case.
Step happens every 1500us. Serial.write is interrupt driven right? So how would you interleave that not knowing when the interrupt might happen?
The uno wifi rev 2 has a hardware uart on serial1.
Or maybe it'll help to use the non interrupt driven version in this case?
What would be your approach without using a delay? Use a real timer? Every single stepper library I've seen actually used delay so thought it would be okay.
I was sharing snippets since my code is terribly messy and didn't wanna put you through that.
Arduino delay() and delayMicroseconds() BLOCK EXECUTION of the rest of your sketch while they wait. NOTHING ELSE RUNS THEN.
1500 us = 24000 cpu cycles that other routines like printing could be done if those cycles are not wasted.
There is a way to not block and get those cycles that uses a fraction of those to work.
I like Nick Gammon’s (I am not him!) tutorial on the subject, it is simple, practical and complete. http://gammon.com.au/blink
Thanks! The documentation states that delayMicroseconds does not disable interrupts - I am assuming that my Serial1.write returns instantly and the actual transfer is interrupt based from there on, hopefully occurring mostly in the delayMicroseconds. The state machine might work if the Serial1.write was a blocking call - but its not.
frk1206:
Step happens every 1500us. Serial.write is interrupt driven right? So how would you interleave that not knowing when the interrupt might happen?
Doesn't make any difference. The interrupt will cause the same - random - timing error no matter how you code it, whether delayMicroseconds is happening or the state machine is running.
frk1206:
What would be your approach without using a delay? Use a real timer? Every single stepper library I've seen actually used delay so thought it would be okay.
You just read micros() on each loop and act when it advances by the desired amount. You already have the timer provided for you, no need to complicate matters further.
Stepper libraries are of course for people who want to move steppers and do nothing else of significance at the same time. Same applies for serial libraries/ functions.
frk1206:
Thanks! The documentation states that delayMicroseconds does not disable interrupts
Neither delayMicroseconds or delay() would or could disable interrupts since delay() depends on them. delayMicroseconds simply spins a loop waiting for micros() to advance a given amount - so you might as well do that for yourself and do other things while you are at it.
frk1206:
I am assuming that my Serial1.write returns instantly and the actual transfer is interrupt based from there on, hopefully occurring mostly in the delayMicroseconds.
No. There are no delays written into the serial code. Serial.write() puts a byte into the serial output buffer and Serial puts the next outgoing byte into the UART register then starts it off.
At 115200 baud a byte is sent about every 1388 cycles. If your sketch tries to overfill the output buffer your write or print will have to wait until there's room.
The state machine might work if the Serial1.write was a blocking call - but its not.
Why do you believe that the state machine needs a blocking call to work? Just the opposite is true.
State machines take working with to realize what they can do. State machines are a fantastic coding technique.
frk1206:
Thanks! The documentation states that delayMicroseconds does not disable interrupts - I am assuming that my Serial1.write returns instantly and the actual transfer is interrupt based from there on, hopefully occurring mostly in the delayMicroseconds. The state machine might work if the Serial1.write was a blocking call - but its not.
Yeah, interrupts can run while delay(), etc, hold up execution of the sketch. Thing is that interrupt code needs to be short and usually can't do everything so it sets a flag and maybe stores the time (micros() or millis()) so that the sketch can process that info... if the sketch is blocked then that processing will have to wait and the cycles it waits are lost.
To an Arduino, 1500 usecs is like 15 minutes to you.
So the correct approach in my case is to disable interrupts where timing is important and only enable them in the delayMicroseconds so that Serial write can write itself out. Modified this code works:
int delay_ms = 0;
unsigned long now = micros();
if((now - last_step_) < step_time_us_) {
interrupts();
delay_ms = step_time_us_ - (now - last_step_);
delayMicroseconds(delay_ms);
noInterrupts();
}
Might be helpful for you guys / others. There’s nothing wrong with using delays and you can write to Serial simultaneously while doing so if you simply control when the interrupts happen. Luckily with a microcontroller this is possible