Some time ago I built an application to run on my Raspberry Pi 3. Among other things, it controlled a stepper motor using a stepper motor driver board. To make the motor move one "step", I had to set an output pin High and Low. The change in voltage would cause the motor to move into its next position. I needed to have a small time delay between the voltage changes for it to work properly.
My initial research showed that the best-practice way to get a time delay in a UWP app was to use the asynchronous Task.Delay() method. I didn't have access to my Thread.Sleep method in UWP, so I gave it a try. Also, since the method takes an integer as a parameter, 1 millisecond was the shortest delay I could use.
Here's an example of my first attempt, making 1600 successive "steps":
for (int i = 0; i < 1600; i++)
{
// (stepper driver board makes one step for every low-to-high transition)
StepperStepPin.Write(GpioPinValue.Low);
await Task.Delay(1); // wait 1 ms
StepperStepPin.Write(GpioPinValue.High);
await Task.Delay(1); // wait 1 ms
}
In theory, with 2ms delay in each iteration of the loop, this should have taken about 3.2 seconds. In reality, it ended up taking about 51 seconds. As best as I can tell, the act of calling this asynchronous delay method adds about 15 ms of overhead to start up the asynchronous thread. If I were using longer delays only once in awhile this wouldn't be noticeable. But when I have to do it hundreds or thousands of times, it adds up quickly.
After lots more digging, I found a solution that worked for me. I ditched the async method and went with a synchronous approach using the System.Diagnostics.Stopwatch class, and it also lets me have sub-millisecond delays:
private readonly Stopwatch _sw = new System.Diagnostics.Stopwatch();
private void ShortDelay(double milliseconds) {
_sw.Start();
while ((_sw.Elapsed).TotalMilliseconds < milliseconds) { }
_sw.Reset();
}
//////////////////////////////////////////
for (int i = 0; i < 1600; i++)
{
// (stepper driver board makes one step for every low-to-high transition)
StepperStepPin.Write(GpioPinValue.Low);
ShortDelay(0.5); // wait 0.5 ms
StepperStepPin.Write(GpioPinValue.High);
ShortDelay(0.5); // wait 0.5 ms
}
I would assume the little while loop could cause problems with a UI thread, but my app is headless, so it didn't really affect my particular application. But it still feels like a bit of a hack, like there should be a better solution here to get a reasonably accurate millisecond-or-less time delay.
I admit I feel like I don't fully understand async/await, and I'd like to know if there's a more appropriate solution here. So if you have some expertise here and can explain a better way, or can explain why this method is acceptable, any feedback would be appreciated.
Thanks.
there should be a better solution here to get a reasonably accurate millisecond-or-less time delay.
- only if you think .NET is a realtime environment, which it is not. – AdenectomyThread.SpinWait
instead of a loop, unfortunately it's not available on UWP – PreachThread.Sleep(1)
would sleep for 15 ms, after, it is actually able to sleep for roughly 1 ms. That won't help you if you need a smaller delay, but it's still an improvement. – BravoThread.Sleep(1)
. – LucioThread.Sleep(n)
should be that it sleeps for at least n ms. If it's less (and you're measuring time correctly), then that sounds like a bug to me. And I tried to find a citation, but couldn't find anything. – BravoThread.Sleep(n)
should be that it sleeps for at least n ms" -- no disagreement there. I'm just reporting what I see. 😊 It is likely that on earlier versions of .NET (which is what I have installed on my Win7 box),Thread.Sleep()
simply delegated to the nativeSleep()
function (orSleepEx()
). The documentation specifically says there: "If dwMilliseconds is less than the resolution of the system clock, the thread may sleep for less than the specified length of time". That Win10 behaves better could reflect a change in the .NET implementation or in the OS. – Lucio