Async Task.Delay overhead in UWP app, is there a better solution?

Some time ago, I built an application to run on my raspberry Pi 3. Among other things, he controlled a stepper motor using a stepper motor driver board. To make the motor move one step, I had to set the output terminal to High and Low. A change in voltage will cause the motor to move to the next position. I needed to have a slight delay between voltage changes in order for it to work correctly.

My initial research showed that the best way to get time delay in a UWP application was to use the asynchronous Task.Delay () method. I did not have access to my Thread.Sleep method in UWP, so I tried. Also, since the method takes an integer as a parameter, 1 millisecond was the shortest delay I could use.

Here is an example of my first attempt, taking 1600 consecutive β€œ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 a delay of 2 ms at each iteration of the loop, this should have taken 3.2 seconds . In fact, in the end it took about 51 seconds . As far as I can tell, the action of calling this asynchronous delay method adds about 15 ms of overhead to start the asynchronous stream. If I used longer delays only once in a while, that would not be noticeable. But when I have to do it hundreds or thousands of times, it folds quickly.

After digging multiple times, I found a solution that worked for me. I removed the asynchronous method and went with the synchronous approach using the System.Diagnostics.Stopwatch class, and it also allows me to execute delays in milliseconds:

 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 suggest that the while loop might cause problems with the UI thread, but my application is headless, so this really didn't affect my specific application. But it still looks a bit hacky, as there should be a better solution for getting a reasonable accurate delay in milliseconds or less.

I admit that I feel that I do not quite understand async / wait, and I would like to know if there is a more suitable solution here. Therefore, if you have any experience here and you can explain the best way or can explain why this method is acceptable, any feedback will be appreciated.

Thanks.

+7
c # uwp async-await raspberry-pi windows-10-iot-core
source share
1 answer

Personally . I don't think adding Delays to your code is the right way to fix it. If you need to apply a delay in order to get the correct UWP application (Windows 10 IOT core), then there might be something that can be done better to avoid delays, since the delays are not only inaccurate but also unreliable. that the work is being done, especially when it comes to the IoT project. Everything may go wrong at any time, and the operation may take longer. In this case, your delay is interrupted, and your IoT installation begins to ruin it.

It says: I wrote a class that can help you control your stepper motor without delay, that would be fast, so if there are any problems let me know, but I checked it completely and I didn't seem to find any problems on the functioning or on the execution side. My code is as follows:

 public class Uln2003Driver : IDisposable { private readonly GpioPin[] _gpioPins = new GpioPin[4]; private readonly GpioPinValue[][] _waveDriveSequence = { new[] {GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low}, new[] {GpioPinValue.Low, GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low}, new[] {GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High, GpioPinValue.Low}, new[] {GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High} }; private readonly GpioPinValue[][] _fullStepSequence = { new[] {GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High}, new[] {GpioPinValue.High, GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low}, new[] {GpioPinValue.Low, GpioPinValue.High, GpioPinValue.High, GpioPinValue.Low}, new[] {GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High, GpioPinValue.High } }; private readonly GpioPinValue[][] _haveStepSequence = { new[] {GpioPinValue.High, GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High}, new[] {GpioPinValue.Low, GpioPinValue.High, GpioPinValue.High, GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low}, new[] {GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High, GpioPinValue.High, GpioPinValue.High, GpioPinValue.Low, GpioPinValue.Low}, new[] {GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.Low, GpioPinValue.High, GpioPinValue.High, GpioPinValue.High } }; public Uln2003Driver(int blueWireToGpio, int pinkWireToGpio, int yellowWireToGpio, int orangeWireToGpio) { var gpio = GpioController.GetDefault(); _gpioPins[0] = gpio.OpenPin(blueWireToGpio); _gpioPins[1] = gpio.OpenPin(pinkWireToGpio); _gpioPins[2] = gpio.OpenPin(yellowWireToGpio); _gpioPins[3] = gpio.OpenPin(orangeWireToGpio); foreach (var gpioPin in _gpioPins) { gpioPin.Write(GpioPinValue.Low); gpioPin.SetDriveMode(GpioPinDriveMode.Output); } } public async Task TurnAsync(int degree, TurnDirection direction, DrivingMethod drivingMethod = DrivingMethod.FullStep) { var steps = 0; GpioPinValue[][] methodSequence; switch (drivingMethod) { case DrivingMethod.WaveDrive: methodSequence = _waveDriveSequence; steps = (int) Math.Ceiling(degree/0.1767478397486253); break; case DrivingMethod.FullStep: methodSequence = _fullStepSequence; steps = (int) Math.Ceiling(degree/0.1767478397486253); break; case DrivingMethod.HalfStep: methodSequence = _haveStepSequence; steps = (int) Math.Ceiling(degree/0.0883739198743126); break; default: throw new ArgumentOutOfRangeException(nameof(drivingMethod), drivingMethod, null); } var counter = 0; while (counter < steps) { for (var j = 0; j < methodSequence[0].Length; j++) { for (var i = 0; i < 4; i++) { _gpioPins[i].Write(methodSequence[direction == TurnDirection.Left ? i : 3 - i][j]); } await Task.Delay(5); counter ++; if (counter == steps) break; } } Stop(); } public void Stop() { foreach (var gpioPin in _gpioPins) { gpioPin.Write(GpioPinValue.Low); } } public void Dispose() { foreach (var gpioPin in _gpioPins) { gpioPin.Write(GpioPinValue.Low); gpioPin.Dispose(); } } } public enum DrivingMethod { WaveDrive, FullStep, HalfStep } public enum TurnDirection { Left, Right }` 

Put this as a class, and you can interact with it from any CodeBehind or ViewModel, as shown below:

  private readonly Uln2003Driver _uln2003Driver; //The Declaration on top of the Class to make it global. //In the constructor of the Page CodeBehind or ViewModel. The arguments are the GPIO pins to which your stepper is connected. _uln2003Driver = new Uln2003Driver(26, 13, 6, 5); 

Now that you have done the setup, use the above value:

  Task.Run(async () => { await _uln2003Driver.TurnAsync(180, TurnDirection.Left, DrivingMethod.FullStep); await _uln2003Driver.TurnAsync(180, TurnDirection.Right, DrivingMethod.WaveDrive); }); 

The code above just rotates clockwise and counterclockwise, but feel free to

Note. Remember to call _uln2003Driver?.Dispose(); after unloading a page or completing a task to free resources. Also ? is a null conditional statement available in c#6.0 , I know it is obvious, but ran into a similar problem in another answer.

Feel free to use the comments section if you need anything.

0
source share

All Articles