diff --git a/Source/Meadow.Foundation.Core.Samples/Sensors.Rotary.RotaryEncoderWithButton_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Core.Samples/Sensors.Rotary.RotaryEncoderWithButton_Sample/MeadowApp.cs index 7b610746e7..73e16dd073 100644 --- a/Source/Meadow.Foundation.Core.Samples/Sensors.Rotary.RotaryEncoderWithButton_Sample/MeadowApp.cs +++ b/Source/Meadow.Foundation.Core.Samples/Sensors.Rotary.RotaryEncoderWithButton_Sample/MeadowApp.cs @@ -1,6 +1,7 @@ using Meadow; using Meadow.Devices; using Meadow.Foundation.Sensors.Rotary; +using Meadow.Peripherals; using Meadow.Peripherals.Sensors.Rotary; using System.Threading.Tasks; diff --git a/Source/Meadow.Foundation.Core.Samples/Sensors.Rotary.RotaryEncoder_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Core.Samples/Sensors.Rotary.RotaryEncoder_Sample/MeadowApp.cs index c22fe4aba7..2d414d144d 100644 --- a/Source/Meadow.Foundation.Core.Samples/Sensors.Rotary.RotaryEncoder_Sample/MeadowApp.cs +++ b/Source/Meadow.Foundation.Core.Samples/Sensors.Rotary.RotaryEncoder_Sample/MeadowApp.cs @@ -1,6 +1,7 @@ using Meadow; using Meadow.Devices; using Meadow.Foundation.Sensors.Rotary; +using Meadow.Peripherals; using Meadow.Peripherals.Sensors.Rotary; using System.Threading.Tasks; @@ -37,7 +38,7 @@ public override Task Initialize() return Task.CompletedTask; } - void RotaryEncoder_Rotated(object sender, RotaryChangeResult e) + private void RotaryEncoder_Rotated(object sender, RotaryChangeResult e) { switch (e.New) { diff --git a/Source/Meadow.Foundation.Core/Sensors/Rotary/RotaryEncoder.cs b/Source/Meadow.Foundation.Core/Sensors/Rotary/RotaryEncoder.cs index ecc8c5b9fa..08daf3a9cc 100644 --- a/Source/Meadow.Foundation.Core/Sensors/Rotary/RotaryEncoder.cs +++ b/Source/Meadow.Foundation.Core/Sensors/Rotary/RotaryEncoder.cs @@ -1,142 +1,142 @@ using Meadow.Hardware; +using Meadow.Peripherals; using Meadow.Peripherals.Sensors.Rotary; using System; -namespace Meadow.Foundation.Sensors.Rotary +namespace Meadow.Foundation.Sensors.Rotary; + +/// +/// Digital rotary encoder that uses two-bit Gray Code to encode rotation. +/// +public class RotaryEncoder : IRotaryEncoder { /// - /// Digital rotary encoder that uses two-bit Gray Code to encode rotation. + /// Raised when the rotary encoder is rotated and returns a RotaryTurnedEventArgs object which describes the direction of rotation. /// - public class RotaryEncoder : IRotaryEncoder - { - /// - /// Raised when the rotary encoder is rotated and returns a RotaryTurnedEventArgs object which describes the direction of rotation. - /// - public event EventHandler Rotated = delegate { }; - - /// - /// Returns the pin connected to the A-phase output on the rotary encoder. - /// - protected IDigitalInterruptPort APhasePort { get; } - - /// - /// Returns the pin connected to the B-phase output on the rotary encoder. - /// - protected IDigitalInterruptPort BPhasePort { get; } - - /// - /// Gets the last direction of rotation - /// - public RotationDirection? LastDirectionOfRotation { get; protected set; } - - /// - /// Contains the previous offset used to find direction information - /// - private int dynamicOffset = 0; - - /// - /// The rotary encoder has 2 inputs, called A and B. Because of its design - /// either A or B changes but not both on each notification when the encoder - /// is rotated. Because of this the direction to be determined. If A goes - /// High before B then we are rotating one direction if B goes high before A - /// we are rotating the other direction. For each change we must consider the - /// previous state of A and B and the current state of A and B. This can be - /// represented as 4-bit number. - /// 3 2 1 0 - /// |old|new| - /// |A|B|A|B| - /// - /// Bits 0 and 1 represent the current state of A and B while bits 2 and 3 - /// represent previous states of A and B. This 4-bit number yields 16 possible - /// combination, however, there are combination that for which no change is - /// represent. For example, the if bits 0-3 are all 0, this would mean that A - /// was Low and is Low, and that B was Low and is Low (nothing changed.) - /// - private readonly int[] RotEncLookup = { 0, -1, 1, 0, - 1, 0, 0, -1, - -1, 0, 0, 1, - 0, 1, -1, 0 }; - - /// - /// Instantiate a new RotaryEncoder on the specified pins - /// - /// Pin A - /// Pin B - /// Do the encode pins use a common ground (true) or common positive (false) - public RotaryEncoder(IPin aPhasePin, IPin bPhasePin, bool isCommonGround = false) : - this(aPhasePin.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, isCommonGround ? ResistorMode.InternalPullUp : ResistorMode.InternalPullDown, TimeSpan.Zero, TimeSpan.FromMilliseconds(0.1)), - bPhasePin.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, isCommonGround ? ResistorMode.InternalPullUp : ResistorMode.InternalPullDown, TimeSpan.Zero, TimeSpan.FromMilliseconds(0.1))) - { } - - /// - /// Instantiate a new RotaryEncoder on the specified ports - /// - /// - /// - public RotaryEncoder(IDigitalInterruptPort aPhasePort, IDigitalInterruptPort bPhasePort) - { - APhasePort = aPhasePort; - BPhasePort = bPhasePort; + public event EventHandler Rotated = delegate { }; - APhasePort.Changed += PhaseAPinChanged; - BPhasePort.Changed += PhaseBPinChanged; - } + /// + /// Returns the pin connected to the A-phase output on the rotary encoder. + /// + protected IDigitalInterruptPort APhasePort { get; } - private void PhaseAPinChanged(object s, DigitalPortResult result) - { - // Clear bit A bit - int new2LsBits = dynamicOffset & 0x02; // Save bit 2 (B) + /// + /// Returns the pin connected to the B-phase output on the rotary encoder. + /// + protected IDigitalInterruptPort BPhasePort { get; } - if (result.New.State) - { - new2LsBits |= 0x01; // Set bit 1 (A) - } + /// + /// Gets the last direction of rotation + /// + public RotationDirection? LastDirectionOfRotation { get; protected set; } - FindDirection(new2LsBits); - } + /// + /// Contains the previous offset used to find direction information + /// + private int dynamicOffset = 0; - private void PhaseBPinChanged(object s, DigitalPortResult result) - { - // Clear bit B bit - int new2LsBits = dynamicOffset & 0x01; // Save bit 1 (A) + /// + /// The rotary encoder has 2 inputs, called A and B. Because of its design + /// either A or B changes but not both on each notification when the encoder + /// is rotated. Because of this the direction to be determined. If A goes + /// High before B then we are rotating one direction if B goes high before A + /// we are rotating the other direction. For each change we must consider the + /// previous state of A and B and the current state of A and B. This can be + /// represented as 4-bit number. + /// 3 2 1 0 + /// |old|new| + /// |A|B|A|B| + /// + /// Bits 0 and 1 represent the current state of A and B while bits 2 and 3 + /// represent previous states of A and B. This 4-bit number yields 16 possible + /// combination, however, there are combination that for which no change is + /// represent. For example, the if bits 0-3 are all 0, this would mean that A + /// was Low and is Low, and that B was Low and is Low (nothing changed.) + /// + private readonly int[] RotEncLookup = { 0, -1, 1, 0, + 1, 0, 0, -1, + -1, 0, 0, 1, + 0, 1, -1, 0 }; - if (result.New.State) - { - new2LsBits |= 0x02; // Set bit 2 (B) - } + /// + /// Instantiate a new RotaryEncoder on the specified pins + /// + /// Pin A + /// Pin B + /// Do the encode pins use a common ground (true) or common positive (false) + public RotaryEncoder(IPin aPhasePin, IPin bPhasePin, bool isCommonGround = false) : + this(aPhasePin.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, isCommonGround ? ResistorMode.InternalPullUp : ResistorMode.InternalPullDown, TimeSpan.Zero, TimeSpan.FromMilliseconds(0.1)), + bPhasePin.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, isCommonGround ? ResistorMode.InternalPullUp : ResistorMode.InternalPullDown, TimeSpan.Zero, TimeSpan.FromMilliseconds(0.1))) + { } - FindDirection(new2LsBits); + /// + /// Instantiate a new RotaryEncoder on the specified ports + /// + /// + /// + public RotaryEncoder(IDigitalInterruptPort aPhasePort, IDigitalInterruptPort bPhasePort) + { + APhasePort = aPhasePort; + BPhasePort = bPhasePort; + + APhasePort.Changed += PhaseAPinChanged; + BPhasePort.Changed += PhaseBPinChanged; + } + + private void PhaseAPinChanged(object s, DigitalPortResult result) + { + // Clear bit A bit + int new2LsBits = dynamicOffset & 0x02; // Save bit 2 (B) + + if (result.New.State) + { + new2LsBits |= 0x01; // Set bit 1 (A) } - private void FindDirection(int new2LsBits) + FindDirection(new2LsBits); + } + + private void PhaseBPinChanged(object s, DigitalPortResult result) + { + // Clear bit B bit + int new2LsBits = dynamicOffset & 0x01; // Save bit 1 (A) + + if (result.New.State) { - dynamicOffset <<= 2; // Move previous A & B to bits 2 & 3 - dynamicOffset |= new2LsBits; // Set the current A & B states in bits 0 & 1 - dynamicOffset &= 0x0000000f; // Save only lowest 4 bits - - int direction = RotEncLookup[dynamicOffset]; - - // save state - var oldRotation = LastDirectionOfRotation; - - switch (direction) - { - case 0: // no valid change - return; - case 1: // clockwise - LastDirectionOfRotation = RotationDirection.Clockwise; - RaiseRotatedAndNotify(new RotaryChangeResult(RotationDirection.Clockwise, oldRotation)); - break; - default: // counter clockwise - LastDirectionOfRotation = RotationDirection.CounterClockwise; - RaiseRotatedAndNotify(new RotaryChangeResult(RotationDirection.CounterClockwise, oldRotation)); - break; - } + new2LsBits |= 0x02; // Set bit 2 (B) } - private void RaiseRotatedAndNotify(RotaryChangeResult result) + FindDirection(new2LsBits); + } + + private void FindDirection(int new2LsBits) + { + dynamicOffset <<= 2; // Move previous A & B to bits 2 & 3 + dynamicOffset |= new2LsBits; // Set the current A & B states in bits 0 & 1 + dynamicOffset &= 0x0000000f; // Save only lowest 4 bits + + int direction = RotEncLookup[dynamicOffset]; + + // save state + var oldRotation = LastDirectionOfRotation; + + switch (direction) { - Rotated?.Invoke(this, result); + case 0: // no valid change + return; + case 1: // clockwise + LastDirectionOfRotation = RotationDirection.Clockwise; + RaiseRotatedAndNotify(new RotaryChangeResult(RotationDirection.Clockwise, oldRotation)); + break; + default: // counter clockwise + LastDirectionOfRotation = RotationDirection.CounterClockwise; + RaiseRotatedAndNotify(new RotaryChangeResult(RotationDirection.CounterClockwise, oldRotation)); + break; } } + + private void RaiseRotatedAndNotify(RotaryChangeResult result) + { + Rotated?.Invoke(this, result); + } } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Core/Sensors/Rotary/RotaryEncoderWithButton.cs b/Source/Meadow.Foundation.Core/Sensors/Rotary/RotaryEncoderWithButton.cs index ab289eeafe..2cd19c666d 100644 --- a/Source/Meadow.Foundation.Core/Sensors/Rotary/RotaryEncoderWithButton.cs +++ b/Source/Meadow.Foundation.Core/Sensors/Rotary/RotaryEncoderWithButton.cs @@ -4,108 +4,107 @@ using System; using System.Threading.Tasks; -namespace Meadow.Foundation.Sensors.Rotary +namespace Meadow.Foundation.Sensors.Rotary; + +/// +/// Digital rotary encoder that uses two-bit Gray Code to encode rotation and has an integrated push button +/// +public class RotaryEncoderWithButton : RotaryEncoder, IRotaryEncoderWithButton { /// - /// Digital rotary encoder that uses two-bit Gray Code to encode rotation and has an integrated push button + /// Returns the PushButton that represents the integrated button /// - public class RotaryEncoderWithButton : RotaryEncoder, IRotaryEncoderWithButton - { - /// - /// Returns the PushButton that represents the integrated button - /// - public PushButton Button { get; private set; } - - /// - /// Returns the push button's state - /// - public bool State => Button.State; + public PushButton Button { get; private set; } - /// - /// Raised when the button circuit is re-opened after it has been closed - /// - public event EventHandler Clicked = delegate { }; + /// + /// Returns the push button's state + /// + public bool State => Button.State; - /// - /// Raised when a press ends - /// - public event EventHandler PressEnded = delegate { }; + /// + /// Raised when the button circuit is re-opened after it has been closed + /// + public event EventHandler Clicked = delegate { }; - /// - /// Raised when a press starts - /// - public event EventHandler PressStarted = delegate { }; + /// + /// Raised when a press ends + /// + public event EventHandler PressEnded = delegate { }; - /// - /// Raised when the button circuit is pressed for LongPressDuration - /// - public event EventHandler LongClicked = delegate { }; + /// + /// Raised when a press starts + /// + public event EventHandler PressStarted = delegate { }; - /// - /// The minimum duration for a long press - /// - public TimeSpan LongClickedThreshold - { - get => Button.LongClickedThreshold; - set => Button.LongClickedThreshold = value; - } + /// + /// Raised when the button circuit is pressed for LongPressDuration + /// + public event EventHandler LongClicked = delegate { }; - /// - /// Instantiates a new RotaryEncoder on the specified pins that has an integrated button. - /// - /// - /// - /// - /// - public RotaryEncoderWithButton(IPin aPhasePin, IPin bPhasePin, IPin buttonPin, ResistorMode buttonResistorMode = ResistorMode.InternalPullDown) - : base(aPhasePin, bPhasePin) - { - Button = new PushButton(buttonPin, buttonResistorMode); + /// + /// The minimum duration for a long press + /// + public TimeSpan LongClickedThreshold + { + get => Button.LongClickedThreshold; + set => Button.LongClickedThreshold = value; + } - Button.Clicked += ButtonClicked; - Button.PressEnded += ButtonPressEnded; - Button.PressStarted += ButtonPressStarted; - Button.LongClicked += ButtonLongClicked; - } + /// + /// Instantiates a new RotaryEncoder on the specified pins that has an integrated button. + /// + /// + /// + /// + /// + public RotaryEncoderWithButton(IPin aPhasePin, IPin bPhasePin, IPin buttonPin, ResistorMode buttonResistorMode = ResistorMode.InternalPullDown) + : base(aPhasePin, bPhasePin) + { + Button = new PushButton(buttonPin, buttonResistorMode); - private void ButtonLongClicked(object sender, EventArgs e) - { - LongClicked(this, e); - } + Button.Clicked += ButtonClicked; + Button.PressEnded += ButtonPressEnded; + Button.PressStarted += ButtonPressStarted; + Button.LongClicked += ButtonLongClicked; + } - /// - /// Method when button is clicked (down then up) - /// - /// sender object - /// event arguments - protected void ButtonClicked(object sender, EventArgs e) - { - Clicked(this, e); - } + private void ButtonLongClicked(object sender, EventArgs e) + { + LongClicked(this, e); + } - /// - /// Method called when button press is started (up state) - /// - /// sender object - /// event arguments - protected void ButtonPressEnded(object sender, EventArgs e) - { - PressEnded(this, e); - } + /// + /// Method when button is clicked (down then up) + /// + /// sender object + /// event arguments + protected void ButtonClicked(object sender, EventArgs e) + { + Clicked(this, e); + } - /// - /// Method called when button press is started (down state) - /// - /// sender object - /// event arguments - protected void ButtonPressStarted(object sender, EventArgs e) - { - PressStarted(this, e); - } + /// + /// Method called when button press is started (up state) + /// + /// sender object + /// event arguments + protected void ButtonPressEnded(object sender, EventArgs e) + { + PressEnded(this, e); + } - /// - /// Convenience method to get the current sensor reading - /// - public Task Read() => Button.Read(); + /// + /// Method called when button press is started (down state) + /// + /// sender object + /// event arguments + protected void ButtonPressStarted(object sender, EventArgs e) + { + PressStarted(this, e); } + + /// + /// Convenience method to get the current sensor reading + /// + public Task Read() => Button.Read(); } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/Motors.ElectronicSpeedController/Samples/ElectronicSpeedController_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Peripherals/Motors.ElectronicSpeedController/Samples/ElectronicSpeedController_Sample/MeadowApp.cs index 761926f1f5..1afdb35f7d 100644 --- a/Source/Meadow.Foundation.Peripherals/Motors.ElectronicSpeedController/Samples/ElectronicSpeedController_Sample/MeadowApp.cs +++ b/Source/Meadow.Foundation.Peripherals/Motors.ElectronicSpeedController/Samples/ElectronicSpeedController_Sample/MeadowApp.cs @@ -3,6 +3,7 @@ using Meadow.Foundation; using Meadow.Foundation.Motors; using Meadow.Foundation.Sensors.Rotary; +using Meadow.Peripherals; using Meadow.Peripherals.Sensors.Rotary; using Meadow.Units; using System.Threading.Tasks; @@ -13,12 +14,11 @@ public class MeadowApp : App { // - readonly Frequency frequency = new Frequency(50, Frequency.UnitType.Hertz); - const float armMs = 0.5f; - const float powerIncrement = 0.05f; - - ElectronicSpeedController esc; - RotaryEncoderWithButton rotary; + private readonly Frequency frequency = new Frequency(50, Frequency.UnitType.Hertz); + private const float armMs = 0.5f; + private const float powerIncrement = 0.05f; + private ElectronicSpeedController esc; + private RotaryEncoderWithButton rotary; public override Task Initialize() { @@ -44,7 +44,7 @@ private void RotaryRotated(object sender, RotaryChangeResult e) esc.Power += (e.New == RotationDirection.Clockwise) ? powerIncrement : -powerIncrement; DisplayPowerOnLed(esc.Power); - Resolver.Log.Info($"New Power: {esc.Power * (float)100:n0}%"); + Resolver.Log.Info($"New Power: {esc.Power * 100:n0}%"); } /// @@ -52,7 +52,7 @@ private void RotaryRotated(object sender, RotaryChangeResult e) /// blue @ `0%`, and a proportional mix, in between those speeds. /// /// - void DisplayPowerOnLed(float power) + private void DisplayPowerOnLed(float power) { // `0.0` - `1.0` int r = (int)ExtensionMethods.Map(power, 0f, 1f, 0f, 255f); diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Driver/A4988.cs b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Driver/A4988.cs index 168976159b..bda3dfcede 100644 --- a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Driver/A4988.cs +++ b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Driver/A4988.cs @@ -1,4 +1,5 @@ using Meadow.Hardware; +using Meadow.Peripherals; using Meadow.Units; using System; using System.Linq; @@ -38,7 +39,8 @@ public int RotationSpeedDivisor rotationSpeedDivisor = value; } } - int rotationSpeedDivisor; + + private int rotationSpeedDivisor; /// /// Sets or gets the direction of rotation used for Step or Rotate methods @@ -102,16 +104,15 @@ public int StepsPerRevolution } } - readonly IDigitalOutputPort stepPort; - readonly IDigitalOutputPort directionPort; - readonly IDigitalOutputPort? enablePort; - readonly IDigitalOutputPort? ms1Port; - readonly IDigitalOutputPort? ms2Port; - readonly IDigitalOutputPort? ms3Port; - readonly object syncRoot = new object(); - - StepDivisor divisor; - Angle stepAngle; + private readonly IDigitalOutputPort stepPort; + private readonly IDigitalOutputPort directionPort; + private readonly IDigitalOutputPort enablePort; + private readonly IDigitalOutputPort ms1Port; + private readonly IDigitalOutputPort ms2Port; + private readonly IDigitalOutputPort ms3Port; + private readonly object syncRoot = new object(); + private StepDivisor divisor; + private Angle stepAngle; /// /// Creates an instance of the A4988 Stepper Motor Driver @@ -156,7 +157,7 @@ public A4988(IPin step, IPin direction, IPin enable) /// The (optional) Meadow pin connected to the MS2 pin of the A4988 /// The (optional) Meadow pin connected to the MS3 pin of the A4988 /// You must provide either all of the micro-step (MS) lines or none of them - public A4988(IPin step, IPin direction, IPin? enablePin, IPin? ms1Pin, IPin? ms2Pin, IPin? ms3Pin) + public A4988(IPin step, IPin direction, IPin enablePin, IPin ms1Pin, IPin ms2Pin, IPin ms3Pin) { stepPort = step.CreateDigitalOutputPort(); @@ -168,13 +169,13 @@ public A4988(IPin step, IPin direction, IPin? enablePin, IPin? ms1Pin, IPin? ms2 } // micro-step lines (for now) are all-or-nothing TODO: rethink this? - if (new IPin?[] { ms1Pin, ms2Pin, ms3Pin }.All(p => p != null)) + if (new IPin[] { ms1Pin, ms2Pin, ms3Pin }.All(p => p != null)) { - ms1Port = ms1Pin?.CreateDigitalOutputPort(); - ms2Port = ms2Pin?.CreateDigitalOutputPort(); - ms3Port = ms3Pin?.CreateDigitalOutputPort(); + ms1Port = ms1Pin.CreateDigitalOutputPort(); + ms2Port = ms2Pin.CreateDigitalOutputPort(); + ms3Port = ms3Pin.CreateDigitalOutputPort(); } - else if (new IPin?[] { ms1Pin, ms2Pin, ms3Pin }.All(p => p == null)) + else if (new IPin[] { ms1Pin, ms2Pin, ms3Pin }.All(p => p == null)) { // nop } else diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Driver/RotationDirection.cs b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Driver/RotationDirection.cs deleted file mode 100644 index 815cc39b88..0000000000 --- a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Driver/RotationDirection.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Meadow.Foundation.Motors.Stepper -{ - /// - /// Stepper motor direction - /// - public enum RotationDirection - { - /// - /// Clockwise - /// - Clockwise, - /// - /// Counter-clockwise - /// - CounterClockwise, - } -} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Samples/A4988_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Samples/A4988_Sample/MeadowApp.cs index 5b1e289631..829a5d07ec 100644 --- a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Samples/A4988_Sample/MeadowApp.cs +++ b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.A4988/Samples/A4988_Sample/MeadowApp.cs @@ -1,6 +1,7 @@ using Meadow; using Meadow.Devices; using Meadow.Foundation.Motors.Stepper; +using Meadow.Peripherals; using System; using System.Threading; using System.Threading.Tasks; @@ -11,7 +12,7 @@ public class MeadowApp : App { // - A4988 a4988; + private A4988 a4988; public override Task Initialize() { diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Datasheet/dm542e.pdf b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Datasheet/dm542e.pdf new file mode 100644 index 0000000000..fc56e535d9 Binary files /dev/null and b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Datasheet/dm542e.pdf differ diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Driver/Em542s.cs b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Driver/Em542s.cs new file mode 100644 index 0000000000..f1cff5ceab --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Driver/Em542s.cs @@ -0,0 +1,157 @@ +using Meadow.Hardware; +using Meadow.Peripherals; +using Meadow.Peripherals.Motors; +using Meadow.Units; +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Meadow.Foundation.Motors.Stepper; + +/// +/// This class is for the A4988 Stepper Motor Driver +/// +public class Em542s : IStepperMotor +{ + private readonly IDigitalOutputPort _pulsePort; + private readonly IDigitalOutputPort _directionPort; + private readonly IDigitalOutputPort? _enablePort; + private const int MinimumStartupDelayMicroseconds = 50; + private const int LinearAccelerationConstant = 40; + private const int MinimumMicrosecondDelayRequiredByDrive = 5; + private float _usPerCall; + + private readonly object _syncRoot = new(); + + /// + public RotationDirection Direction { get; set; } + + /// + public int StepsPerRevolution { get; } + + /// + /// Gets a value indicating whether the logic for the stepper motor driver is inverted. + /// + public bool InverseLogic { get; } + + /// + /// Initializes a new instance of the class with the specified parameters. + /// + /// The digital output port for controlling the pulse signal. + /// The digital output port for controlling the direction of rotation. + /// The optional digital output port for enabling or disabling the motor (if available). + /// The number of steps per revolution for the stepper motor (default is 200). + /// A value indicating whether the logic for the stepper motor driver is inverted (default is false). + + public Em542s( + IDigitalOutputPort pulse, + IDigitalOutputPort direction, + IDigitalOutputPort? enable = null, + int stepsPerRevolution = 200, + bool inverseLogic = false + ) + { + StepsPerRevolution = stepsPerRevolution; + InverseLogic = inverseLogic; + + _pulsePort = pulse; + _directionPort = direction; + _enablePort = enable; + + _pulsePort.State = !InverseLogic; + _directionPort.State = InverseLogic; + + if (_enablePort != null) + { + _enablePort.State = false; + } + + CalculateCallDuration(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private void CalculateCallDuration() + { + Task.Run(() => + { + var temp = 0; + var calls = 100000; + + var start = Environment.TickCount; + + for (var i = 0; i < calls; i++) + { + temp = i; + } + + var et = Environment.TickCount - start; + + _usPerCall = et * 1000 / (float)calls; + + Resolver.Log.Info($"us per call: {calls} / {et} = {_usPerCall}"); + }); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private void MicrosecondSleep(int microseconds) + { + var temp = 0; + + for (var i = 0; i < microseconds / _usPerCall; i++) + { + temp = i; + } + } + + /// + public void Step(int steps, RotationDirection direction, Frequency rate) + { + while (_usPerCall == 0) + { + Thread.Sleep(10); + } + + lock (_syncRoot) + { + var directionState = direction == RotationDirection.Clockwise; + if (InverseLogic) directionState = !directionState; + _directionPort.State = directionState; + + if (_enablePort != null) + { + _enablePort.State = !InverseLogic; + } + + var targetus = (int)(1000000d / rate.Hertz); + + if (targetus < MinimumMicrosecondDelayRequiredByDrive) throw new ArgumentOutOfRangeException( + "Rate cannot have pulses shorter than 5us. Use 200KHz or less."); + + var us = targetus < MinimumStartupDelayMicroseconds ? MinimumStartupDelayMicroseconds : targetus; + + for (var step = 0; step < steps; step++) + { + _pulsePort.State = InverseLogic; // low means "step" + + MicrosecondSleep(us); + + _pulsePort.State = !InverseLogic; + + MicrosecondSleep(us); + + // DEV NOTE: + // naive linear acceleration tested only with STP-MTR-34066 motor + if (us > targetus && step % LinearAccelerationConstant == 0) + { + us--; + } + } + + if (_enablePort != null) + { + _enablePort.State = !InverseLogic; + } + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Driver/Motors.Stepper.Em542s.csproj b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Driver/Motors.Stepper.Em542s.csproj new file mode 100644 index 0000000000..585d10884a --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Driver/Motors.Stepper.Em542s.csproj @@ -0,0 +1,25 @@ + + + enable + 10.0 + Apache-2.0 + true + icon.png + Wilderness Labs, Inc + netstandard2.1 + Library + Em542s + Wilderness Labs, Inc + http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/ + Meadow.Foundation.Motors.Stepper.Em542s + https://github.com/WildernessLabs/Meadow.Foundation + Meadow, Meadow.Foundation, Motor, Stepper, Leadshine, Em542s + 0.1.49 + true + The Leadshine EM542S Stepper Motor Drive + + + + + + diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/Em542s_Sample.csproj b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/Em542s_Sample.csproj new file mode 100644 index 0000000000..500f53f3d4 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/Em542s_Sample.csproj @@ -0,0 +1,20 @@ + + + https://github.com/WildernessLabs/Meadow.Foundation + Wilderness Labs, Inc + Wilderness Labs, Inc + true + netstandard2.1 + Library + App + + + + + + + + Always + + + diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/MeadowApp.cs new file mode 100644 index 0000000000..18c4d0cc16 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/MeadowApp.cs @@ -0,0 +1,52 @@ +using Meadow; +using Meadow.Devices; +using Meadow.Foundation.Motors.Stepper; +using Meadow.Hardware; +using Meadow.Peripherals; +using Meadow.Peripherals.Motors; +using System.Threading; +using System.Threading.Tasks; + +namespace MeadowApp +{ + public class MeadowApp : App + { + // + private IStepperMotor stepper; + + public override Task Initialize() + { + stepper = new Em542s( + Device.Pins.D15.CreateDigitalOutputPort(), + Device.Pins.D14.CreateDigitalOutputPort(), + inverseLogic: true); + + return base.Initialize(); + } + + public override Task Run() + { + var direction = RotationDirection.Clockwise; + + // max rate for this drive + var rate = new Meadow.Units.Frequency(200, Meadow.Units.Frequency.UnitType.Kilohertz); + + while (true) + { + Resolver.Log.Info($"{direction}"); + + stepper.Rotate(360f, direction, rate); + Thread.Sleep(1000); + + direction = direction switch + { + RotationDirection.CounterClockwise => RotationDirection.Clockwise, + _ => RotationDirection.CounterClockwise + }; + } + } + + // + + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/meadow.config.yaml b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/meadow.config.yaml new file mode 100644 index 0000000000..32363cb69c --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/meadow.config.yaml @@ -0,0 +1,2 @@ +MonoControl: + Options: --jit \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/wiring_example.png b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/wiring_example.png new file mode 100644 index 0000000000..39bdfe5b4a Binary files /dev/null and b/Source/Meadow.Foundation.Peripherals/Motors.Stepper.Em542s/Samples/Em542s_Sample/wiring_example.png differ diff --git a/Source/Meadow.Foundation.Peripherals/Motors.Tb67h420ftg/Samples/Tb67h420ftg_Encoder_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Peripherals/Motors.Tb67h420ftg/Samples/Tb67h420ftg_Encoder_Sample/MeadowApp.cs index bd74a0840b..20e5ac9b0a 100644 --- a/Source/Meadow.Foundation.Peripherals/Motors.Tb67h420ftg/Samples/Tb67h420ftg_Encoder_Sample/MeadowApp.cs +++ b/Source/Meadow.Foundation.Peripherals/Motors.Tb67h420ftg/Samples/Tb67h420ftg_Encoder_Sample/MeadowApp.cs @@ -6,6 +6,7 @@ using Meadow.Foundation.Sensors.Buttons; using Meadow.Foundation.Sensors.Rotary; using Meadow.Hardware; +using Meadow.Peripherals; using System; using System.Threading.Tasks; @@ -13,12 +14,11 @@ namespace Motors.Tb67h420ftg_Encoder_Sample { public class MeadowApp : App { - Tb67h420ftg motorDriver; - RotaryEncoder encoder; - MicroGraphics display; - - PushButton button1; - PushButton button2; + private Tb67h420ftg motorDriver; + private RotaryEncoder encoder; + private MicroGraphics display; + private PushButton button1; + private PushButton button2; public override Task Initialize() { @@ -64,11 +64,11 @@ public override Task Initialize() return base.Initialize(); } - int forwardCount = 0; - int backwardsCount = 0; - private void Encoder_Rotated(object sender, Meadow.Peripherals.Sensors.Rotary.RotaryChangeResult e) + private int forwardCount = 0; + private int backwardsCount = 0; + private void Encoder_Rotated(object _, Meadow.Peripherals.Sensors.Rotary.RotaryChangeResult e) { - if (e.New == Meadow.Peripherals.Sensors.Rotary.RotationDirection.Clockwise) + if (e.New == RotationDirection.Clockwise) { forwardCount++; } @@ -83,7 +83,7 @@ private void Encoder_Rotated(object sender, Meadow.Peripherals.Sensors.Rotary.Ro // Resolver.Log.Info($"{++count} - {e.Direction}"); } - void UpdateDisplay(string line1, string line2) + private void UpdateDisplay(string line1, string line2) { display.Clear(); display.DrawText(0, 0, line1); @@ -91,8 +91,8 @@ void UpdateDisplay(string line1, string line2) display.Show(); } - long pressed; - int count; + private long pressed; + private int count; private void Button1_PressStarted(object sender, EventArgs e) { count = forwardCount + backwardsCount; diff --git a/Source/Meadow.Foundation.sln b/Source/Meadow.Foundation.sln index 6fd073dfd9..90a95377f0 100644 --- a/Source/Meadow.Foundation.sln +++ b/Source/Meadow.Foundation.sln @@ -1389,7 +1389,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICs.DigiPots.Mcp4xxx", "Mea EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mcp4162_Sample", "Meadow.Foundation.Peripherals\ICs.DigiPots.Mcp4xxx\Samples\Mcp4162_Sample\Mcp4162_Sample.csproj", "{E175FDF8-103D-4920-9E46-CC180AF703E7}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Displays.Uc1609c", "Meadow.Foundation.Peripherals\Displays.Uc1609c\Driver\Displays.Uc1609c.csproj", "{8B397E9F-61BF-4499-B703-9C78A81A9FD7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Motors.Stepper.Em542s", "Meadow.Foundation.Peripherals\Motors.Stepper.Em542s\Driver\Motors.Stepper.Em542s.csproj", "{5A071DD9-8943-421C-81CF-0B7E91A24B57}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Stepper.Em542s", "Stepper.Em542s", "{FC63A488-29DD-4E30-89BB-507923BC2392}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{91F3576B-CD16-4364-84E6-3F205F6D9A37}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Em542s_Sample", "Meadow.Foundation.Peripherals\Motors.Stepper.Em542s\Samples\Em542s_Sample\Em542s_Sample.csproj", "{0BB431FD-EDD2-47EB-A49F-2F3954C4357B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Displays.Uc1609c", "Meadow.Foundation.Peripherals\Displays.Uc1609c\Driver\Displays.Uc1609c.csproj", "{68D57BFA-2EE8-4EC6-83C9-B95F97FD5CE1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -3351,12 +3359,24 @@ Global {E175FDF8-103D-4920-9E46-CC180AF703E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {E175FDF8-103D-4920-9E46-CC180AF703E7}.Release|Any CPU.Build.0 = Release|Any CPU {E175FDF8-103D-4920-9E46-CC180AF703E7}.Release|Any CPU.Deploy.0 = Release|Any CPU - {8B397E9F-61BF-4499-B703-9C78A81A9FD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B397E9F-61BF-4499-B703-9C78A81A9FD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B397E9F-61BF-4499-B703-9C78A81A9FD7}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {8B397E9F-61BF-4499-B703-9C78A81A9FD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B397E9F-61BF-4499-B703-9C78A81A9FD7}.Release|Any CPU.Build.0 = Release|Any CPU - {8B397E9F-61BF-4499-B703-9C78A81A9FD7}.Release|Any CPU.Deploy.0 = Release|Any CPU + {5A071DD9-8943-421C-81CF-0B7E91A24B57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A071DD9-8943-421C-81CF-0B7E91A24B57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A071DD9-8943-421C-81CF-0B7E91A24B57}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {5A071DD9-8943-421C-81CF-0B7E91A24B57}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A071DD9-8943-421C-81CF-0B7E91A24B57}.Release|Any CPU.Build.0 = Release|Any CPU + {5A071DD9-8943-421C-81CF-0B7E91A24B57}.Release|Any CPU.Deploy.0 = Release|Any CPU + {0BB431FD-EDD2-47EB-A49F-2F3954C4357B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BB431FD-EDD2-47EB-A49F-2F3954C4357B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BB431FD-EDD2-47EB-A49F-2F3954C4357B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {0BB431FD-EDD2-47EB-A49F-2F3954C4357B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BB431FD-EDD2-47EB-A49F-2F3954C4357B}.Release|Any CPU.Build.0 = Release|Any CPU + {0BB431FD-EDD2-47EB-A49F-2F3954C4357B}.Release|Any CPU.Deploy.0 = Release|Any CPU + {68D57BFA-2EE8-4EC6-83C9-B95F97FD5CE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68D57BFA-2EE8-4EC6-83C9-B95F97FD5CE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68D57BFA-2EE8-4EC6-83C9-B95F97FD5CE1}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {68D57BFA-2EE8-4EC6-83C9-B95F97FD5CE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68D57BFA-2EE8-4EC6-83C9-B95F97FD5CE1}.Release|Any CPU.Build.0 = Release|Any CPU + {68D57BFA-2EE8-4EC6-83C9-B95F97FD5CE1}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -4050,7 +4070,11 @@ Global {91EE39A7-2A2B-4584-A341-52F5143E4ABA} = {CD113034-407F-4FA7-9706-8004406551D2} {68AA3433-B429-4462-B4F4-7D7E163DE8EB} = {CD113034-407F-4FA7-9706-8004406551D2} {E175FDF8-103D-4920-9E46-CC180AF703E7} = {91EE39A7-2A2B-4584-A341-52F5143E4ABA} - {8B397E9F-61BF-4499-B703-9C78A81A9FD7} = {F962EC81-2B65-4187-B93A-F49BA004A7BA} + {5A071DD9-8943-421C-81CF-0B7E91A24B57} = {FC63A488-29DD-4E30-89BB-507923BC2392} + {FC63A488-29DD-4E30-89BB-507923BC2392} = {2486B48D-D4A2-4505-BF50-F33B2E15DA97} + {91F3576B-CD16-4364-84E6-3F205F6D9A37} = {FC63A488-29DD-4E30-89BB-507923BC2392} + {0BB431FD-EDD2-47EB-A49F-2F3954C4357B} = {91F3576B-CD16-4364-84E6-3F205F6D9A37} + {68D57BFA-2EE8-4EC6-83C9-B95F97FD5CE1} = {F962EC81-2B65-4187-B93A-F49BA004A7BA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AF7CA16F-8C38-4546-87A2-5DAAF58A1520}