IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Windows 10 IoT开发:步进马达的使用(树莓派3)

    汪宇杰发表于 2016-04-04 01:15:00
    love 0

    如果你需要精确控制设备的转动角度,普通电机是做不到的,通常我们会选用步进马达。比如28BYJ-48这个型号的,很容易买到。

    步进马达得配合驱动板使用,最常用的是ULN2003芯片的驱动板,就像下图。不过注意,你买到的驱动板长相可能不太一样,不过没关系,只要芯片上写的是ULN2003,就可以使用,它们的接口都是一样的。关于步进马达的原理,可以看这篇: https://en.wikipedia.org/wiki/Stepper_motor 

    拿到驱动板和步进马达后,将马达插入驱动板的白色插槽中,这个接口有防呆设计,所以不会插反。

    关于Windows 10 IoT如何驱动步进马达,有一篇很好的英文材料:

    https://www.hackster.io/erickbp/stepper-motor-and-windows-10-iot-core-d3c5d6

    我的例子就是基于上面这篇文章的改进和补充。

    一、物理连接

    首先,不建议把步进马达驱动板的5v电源接入树莓派的5v输出,运行时侯树莓派会报电压不足的提示的,如果你还有别的什么设备连接在树莓派上,很可能会导致机器重启。所以建议大家用外接的5v电源,正负极可以完全独立,负极是可以不接入树莓派的GND的,这和那篇英文资料里说的不太一样。不过我不清楚这样做会不会爆炸。反正我没爆。

    我用的外接电源是一根废旧的USB鼠标线改的,USB的输出就是5v,最方便了。

    接好电源以后,使用4根杜邦线,把IN1-IN4接入树莓派的GPIO端口,对应关系如下(当然你可以自己改,程序也要做相应的修改):

    驱动板端口 树莓派端口
    IN1 GPIO 26
    IN2 GPIO 13
    IN3  GPIO 6
    IN4 GPIO 5

    驱动板端:

    树莓派端:

    二、爆代码

    原版代码在这里:https://github.com/erickbp/IoT/blob/master/Stepper%20Motor/Stepper%20Motor/Uln2003Driver.cs

    我做了一些改进。先贴出完整代码:

    using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Threading.Tasks;
    using Windows.Devices.Gpio;
    
    namespace Uln2003StepMotor
    {
        public class Uln2003Driver : IDisposable
        {
            public int IntervalMs { get; set; }
    
            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[][] _halfStepSequence =
            {
                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(GpioController gpioController,
                int wireIn1, int wireIn2, int wireIn3, int wireIn4,
                GpioSharingMode sharingMode = GpioSharingMode.Exclusive, int intervalMs = 5)
            {
                var gpio = gpioController ?? GpioController.GetDefault();
    
                _gpioPins[0] = gpio.OpenPin(wireIn1, sharingMode);
                _gpioPins[1] = gpio.OpenPin(wireIn2, sharingMode);
                _gpioPins[2] = gpio.OpenPin(wireIn3, sharingMode);
                _gpioPins[3] = gpio.OpenPin(wireIn4, sharingMode);
    
                foreach (var gpioPin in _gpioPins)
                {
                    gpioPin.Write(GpioPinValue.Low);
                    gpioPin.SetDriveMode(GpioPinDriveMode.Output);
                }
    
                IntervalMs = intervalMs;
            }
    
            public async Task TurnAsync(TurnDirection direction, CancellationToken ct,
                DrivingMethod drivingMethod = DrivingMethod.FullStep)
            {
                bool stop = false;
                GpioPinValue[][] methodSequence;
                switch (drivingMethod)
                {
                    case DrivingMethod.WaveDrive:
                        methodSequence = _waveDriveSequence;
                        break;
                    case DrivingMethod.FullStep:
                        methodSequence = _fullStepSequence;
                        break;
                    case DrivingMethod.HalfStep:
                        methodSequence = _halfStepSequence;
                        break;
                    default:
                        throw new ArgumentOutOfRangeException(nameof(drivingMethod), drivingMethod, null);
                }
                while (!stop)
                {
                    for (var j = 0; j < methodSequence[0].Length; j++)
                    {
                        for (var i = 0; i < 4; i++)
                        {
                            _gpioPins[i].Write(methodSequence[direction == TurnDirection.Right ? i : 3 - i][j]);
                        }
    
                        // don't pass cancellation token, will blow up.
                        await Task.Delay(IntervalMs);
    
                        if (ct.IsCancellationRequested)
                        {
                            Debug.WriteLine("Cancel Requested, stop now.");
                            stop = true;
                            break;
                        }
                    }
                }
    
                Stop();
            }
    
            public async Task TurnAsync(int degree, TurnDirection direction, CancellationToken ct,
                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 = _halfStepSequence;
                        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.Right ? i : 3 - i][j]);
                        }
    
                        // don't pass cancellation token, will blow up.
                        await Task.Delay(IntervalMs);
    
                        if (ct.IsCancellationRequested)
                        {
                            Debug.WriteLine("Cancel Requested, stop now.");
                            counter = steps;
                        }
                        else
                        {
                            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
        }
    }
    

    改进的地方是:

    1. TurnAsync方法增加了CancellationToken,可以转动到一般的时候强制停止转动。

    public async Task TurnAsync(int degree, TurnDirection direction, CancellationToken ct,
        DrivingMethod drivingMethod = DrivingMethod.FullStep)
    
    // don't pass cancellation token, will blow up.
    await Task.Delay(IntervalMs);
    
    if (ct.IsCancellationRequested)
    {
        Debug.WriteLine("Cancel Requested, stop now.");
        counter = steps;
    }
    else
    {
        counter++;
    }
    

    2. TurnAsync增加一个重载,用途是不指定角度,不停的往一个方向转动。然后通过CancellationToken来停止。

    public async Task TurnAsync(TurnDirection direction, CancellationToken ct,
        DrivingMethod drivingMethod = DrivingMethod.FullStep)
    

    使用方法:

    XAML

    <Button x:Name="BtnLeftForever" Content="Trun Left Forever" Click="BtnLeftForever_OnClick" />
    
    <Button x:Name="BtnLeft90" Content="Turn Left 90" Click="BtnLeft90_OnClick" Margin="0,10,0,0" />
    <Button x:Name="BtnRight90" Content="Turn Right 90" Margin="0,10,0,0" Click="BtnRight90_OnClick" />
    <Button x:Name="BtnStop" Content="Stop" Margin="0,10,0,0" Click="BtnStop_OnClick"/>
    
    <TextBox Text="10" Header="Degree" x:Name="TxtDegree" />
    <Button x:Name="TurnDegree" Click="TurnDegree_OnClick" Content="Trun Left" />
    

    后台

    public sealed partial class MainPage : Page
    {
        public CancellationTokenSource Cts { get; private set; }
    
        public Uln2003Driver Uln2003Driver { get; set; }
    
        public MainPage()
        {
            this.InitializeComponent();
            var controller = GpioController.GetDefault();
            Uln2003Driver = new Uln2003Driver(controller, 26, 13, 6, 5);
        }
    
        private async Task TurnMotor(int degree, TurnDirection direction)
        {
            Cts = new CancellationTokenSource();
            await Uln2003Driver.TurnAsync(degree, direction, Cts.Token);
        }
    
        private async void BtnLeft90_OnClick(object sender, RoutedEventArgs e)
        {
            await TurnMotor(90, TurnDirection.Left);
        }
    
        private async void BtnRight90_OnClick(object sender, RoutedEventArgs e)
        {
            await TurnMotor(90, TurnDirection.Right);
        }
    
        private async void TurnDegree_OnClick(object sender, RoutedEventArgs e)
        {
            await TurnMotor(int.Parse(TxtDegree.Text), TurnDirection.Left);
        }
    
        private void BtnStop_OnClick(object sender, RoutedEventArgs e)
        {
            Cts.Cancel();
        }
    
        private async void BtnLeftForever_OnClick(object sender, RoutedEventArgs e)
        {
            Cts = new CancellationTokenSource();
            await Uln2003Driver.TurnAsync(TurnDirection.Left, Cts.Token);
        }
    }
    

    三、运行



沪ICP备19023445号-2号
友情链接