Ben*_*ndt 7 c# async-await raspberry-pi windows-10-iot-core uwp
前段时间我在Raspberry Pi 3上构建了一个应用程序.其中,它使用步进电机驱动板控制步进电机.为了让电机向"步进"移动,我必须将输出引脚设置为高电平和低电平.电压的变化会导致电机进入下一个位置.我需要在电压变化之间有一个小的时间延迟才能正常工作.
我最初的研究表明,在UWP应用程序中获得时间延迟的最佳实践方法是使用异步Task.Delay()方法.我无法访问UWP中的Thread.Sleep方法,所以我试了一下.此外,由于该方法采用整数作为参数,因此1毫秒是我可以使用的最短延迟.
这是我第一次尝试的例子,制作了1600个连续的"步骤":
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
}
Run Code Online (Sandbox Code Playgroud)
理论上,在循环的每次迭代中延迟2ms,这应该花费大约3.2秒.实际上,它最终需要大约51秒.据我所知,调用此异步延迟方法的行为会增加大约15 ms的开销来启动异步线程.如果我只是偶尔使用更长的延迟,这将是不明显的.但是,当我不得不做成百次或数千次时,它会迅速增加.
经过多次挖掘,我发现了一个适合我的解决方案.我放弃了异步方法并使用System.Diagnostics.Stopwatch类进行同步方法,它也让我有亚毫秒延迟:
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
}
Run Code Online (Sandbox Code Playgroud)
我会假设while while循环可能会导致UI线程出现问题,但我的应用程序是无头的,所以它并没有真正影响我的特定应用程序.但它仍然感觉像是一个黑客,就像这里应该有一个更好的解决方案,以获得一个相当精确的毫秒或更短的时间延迟.
我承认我觉得我不完全理解异步/等待,我想知道这里是否有更合适的解决方案.因此,如果您在这里有一些专业知识并且可以解释更好的方法,或者可以解释为什么这种方法是可接受的,那么任何反馈都将受到赞
谢谢.
就我个人而言,我认为在代码中添加延迟并不是解决问题的正确方法。如果你需要应用延迟来让某些 UWP(最好是 Windows 10 IOT 核心)应用程序正常运行,那么可能可以采取更好的方法来避免延迟,因为延迟不仅是不准确且不可靠的了解方式这项工作已经完成,尤其是在物联网项目方面。事情随时可能出错,操作时间可能会更长。在这种情况下,你的延迟就会中断,你的物联网设置就会开始变得混乱。
话虽这么说: 我已经编写了一个课程,可以帮助您无延迟地控制步进电机,这是一件很快的事情,因此如果有任何问题请告诉我,但我已经对其进行了彻底的测试,而且我似乎没有查找功能或性能方面的任何问题。我的代码如下:
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
}`
Run Code Online (Sandbox Code Playgroud)
将其作为一个类,您可以从任何 CodeBehind 或 ViewModel 与它交互,如下所示:
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);
Run Code Online (Sandbox Code Playgroud)
现在您已完成设置,请使用上面的内容,如下所示:
Task.Run(async () =>
{
await _uln2003Driver.TurnAsync(180, TurnDirection.Left, DrivingMethod.FullStep);
await _uln2003Driver.TurnAsync(180, TurnDirection.Right, DrivingMethod.WaveDrive);
});
Run Code Online (Sandbox Code Playgroud)
上面的代码只是顺时针和逆时针方向旋转,但可以随意调整它,
注意:请记住_uln2003Driver?.Dispose();在页面卸载或作业完成后致电以释放资源。另外,?中还有一个空条件运算符c#6.0,我知道这是显而易见的,但在另一个答案中遇到了类似的问题。
如果您有任何需要,请随时使用评论部分