diff --git a/Source/Meadow.Foundation.Core/Sensors/Buttons/PushButton.cs b/Source/Meadow.Foundation.Core/Sensors/Buttons/PushButton.cs index f55a992056..56493ba697 100644 --- a/Source/Meadow.Foundation.Core/Sensors/Buttons/PushButton.cs +++ b/Source/Meadow.Foundation.Core/Sensors/Buttons/PushButton.cs @@ -76,4 +76,14 @@ private void DigitalInChanged(object sender, DigitalPortResult result) { UpdateEvents(GetNormalizedState(result.New.State)); } + + /// + protected override bool GetNormalizedState(bool state) + { + return DigitalIn.Resistor switch + { + ResistorMode.ExternalPullUp or ResistorMode.InternalPullUp => !state, + _ => state, + }; + } } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Core/Sensors/Buttons/PushButtonBase.cs b/Source/Meadow.Foundation.Core/Sensors/Buttons/PushButtonBase.cs index c51dad3897..bdc740ff43 100644 --- a/Source/Meadow.Foundation.Core/Sensors/Buttons/PushButtonBase.cs +++ b/Source/Meadow.Foundation.Core/Sensors/Buttons/PushButtonBase.cs @@ -148,7 +148,7 @@ protected PushButtonBase(IDigitalInputPort inputPort) /// Returns the sanitized state of the button /// Inverts the state when using a pull-up resistor /// - protected bool GetNormalizedState(bool state) + protected virtual bool GetNormalizedState(bool state) { return DigitalIn.Resistor switch { diff --git a/Source/Meadow.Foundation.Core/Sensors/Hid/DigitalJoystick.cs b/Source/Meadow.Foundation.Core/Sensors/Hid/DigitalJoystick.cs index cf4472e522..99d8241189 100644 --- a/Source/Meadow.Foundation.Core/Sensors/Hid/DigitalJoystick.cs +++ b/Source/Meadow.Foundation.Core/Sensors/Hid/DigitalJoystick.cs @@ -3,187 +3,186 @@ using Meadow.Peripherals.Sensors.Hid; using System; -namespace Meadow.Foundation.Sensors.Hid +namespace Meadow.Foundation.Sensors.Hid; + +/// +/// Represents a 4 switch digital joystick / directional pad (D-pad) +/// +public class DigitalJoystick : IDigitalJoystick, IDisposable { /// - /// Represents a 4 switch digital joystick / directional pad (D-pad) + /// Get the current digital joystick position + /// + public DigitalJoystickPosition? Position { get; protected set; } = DigitalJoystickPosition.Center; + + /// + /// Raised when the digital joystick position changes /// - public class DigitalJoystick : IDigitalJoystick, IDisposable + public event EventHandler> Updated = default!; + + /// + /// The PushButton class for the up digital joystick switch + /// + public PushButton ButtonUp { get; protected set; } + /// + /// The PushButton class for the down digital joystick switch + /// + public PushButton ButtonDown { get; protected set; } + /// + /// The PushButton class for the left digital joystick switch + /// + public PushButton ButtonLeft { get; protected set; } + /// + /// The PushButton class for the right digital joystick switch + /// + public PushButton ButtonRight { get; protected set; } + + /// + /// Is the object disposed + /// + public bool IsDisposed { get; private set; } + + /// + /// Did we create the port(s) used by the peripheral + /// + private readonly bool createdPorts = false; + private readonly IDigitalInterruptPort portUp; + private readonly IDigitalInterruptPort portDown; + private readonly IDigitalInterruptPort portLeft; + private readonly IDigitalInterruptPort portRight; + + /// + /// Create a new DigitalJoystick object + /// + /// The pin connected to the up switch + /// The pin connected to the down switch + /// The pin connected to the left switch + /// The pin connected to the right switch + /// The resistor mode for all pins + public DigitalJoystick(IPin pinUp, IPin pinDown, IPin pinLeft, IPin pinRight, ResistorMode resistorMode) + : this(pinUp.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, resistorMode), + pinDown.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, resistorMode), + pinLeft.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, resistorMode), + pinRight.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, resistorMode)) { - /// - /// Get the current digital joystick position - /// - public DigitalJoystickPosition? Position { get; protected set; } = DigitalJoystickPosition.Center; - - /// - /// Raised when the digital joystick position changes - /// - public event EventHandler> Updated = default!; - - /// - /// The PushButton class for the up digital joystick switch - /// - public PushButton ButtonUp { get; protected set; } - /// - /// The PushButton class for the down digital joystick switch - /// - public PushButton ButtonDown { get; protected set; } - /// - /// The PushButton class for the left digital joystick switch - /// - public PushButton ButtonLeft { get; protected set; } - /// - /// The PushButton class for the right digital joystick switch - /// - public PushButton ButtonRight { get; protected set; } - - /// - /// Is the object disposed - /// - public bool IsDisposed { get; private set; } - - /// - /// Did we create the port(s) used by the peripheral - /// - readonly bool createdPorts = false; - - readonly IDigitalInterruptPort portUp; - readonly IDigitalInterruptPort portDown; - readonly IDigitalInterruptPort portLeft; - readonly IDigitalInterruptPort portRight; - - /// - /// Create a new DigitalJoystick object - /// - /// The pin connected to the up switch - /// The pin connected to the down switch - /// The pin connected to the left switch - /// The pin connected to the right switch - /// The resistor mode for all pins - public DigitalJoystick(IPin pinUp, IPin pinDown, IPin pinLeft, IPin pinRight, ResistorMode resistorMode) - : this(pinUp.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, resistorMode), - pinDown.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, resistorMode), - pinLeft.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, resistorMode), - pinRight.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, resistorMode)) - { - createdPorts = true; - } + createdPorts = true; + } - /// - /// Create a new DigitalJoystick object - /// - /// The digital port for the up switch - /// The digital port for the down switch - /// The digital port for the left switch - /// The digital port for the right switch - public DigitalJoystick(IDigitalInterruptPort portUp, - IDigitalInterruptPort portDown, - IDigitalInterruptPort portLeft, - IDigitalInterruptPort portRight) - { - ButtonUp = new PushButton(this.portUp = portUp); - ButtonDown = new PushButton(this.portDown = portDown); - ButtonLeft = new PushButton(this.portLeft = portLeft); - ButtonRight = new PushButton(this.portRight = portRight); - - ButtonUp.PressStarted += PressStarted; - ButtonDown.PressStarted += PressStarted; - ButtonLeft.PressStarted += PressStarted; - ButtonRight.PressStarted += PressStarted; - - ButtonUp.PressEnded += PressEnded; - ButtonDown.PressEnded += PressEnded; - ButtonLeft.PressEnded += PressEnded; - ButtonUp.PressEnded += PressEnded; - } + /// + /// Create a new DigitalJoystick object + /// + /// The digital port for the up switch + /// The digital port for the down switch + /// The digital port for the left switch + /// The digital port for the right switch + public DigitalJoystick(IDigitalInterruptPort portUp, + IDigitalInterruptPort portDown, + IDigitalInterruptPort portLeft, + IDigitalInterruptPort portRight) + { + Resolver.Log.Info($"DJ Up resistor: {portUp.Resistor}"); + ButtonUp = new PushButton(this.portUp = portUp); + ButtonDown = new PushButton(this.portDown = portDown); + ButtonLeft = new PushButton(this.portLeft = portLeft); + ButtonRight = new PushButton(this.portRight = portRight); + + ButtonUp.PressStarted += PressStarted; + ButtonDown.PressStarted += PressStarted; + ButtonLeft.PressStarted += PressStarted; + ButtonRight.PressStarted += PressStarted; + + ButtonUp.PressEnded += PressEnded; + ButtonDown.PressEnded += PressEnded; + ButtonLeft.PressEnded += PressEnded; + ButtonRight.PressEnded += PressEnded; + } - private void PressEnded(object sender, EventArgs e) - => Update(); + private void PressEnded(object sender, EventArgs e) + => Update(); - private void PressStarted(object sender, EventArgs e) - => Update(); + private void PressStarted(object sender, EventArgs e) + => Update(); - private void Update() - { - var isLeftPressed = ButtonLeft.State; - var isRightPressed = ButtonRight.State; - var isUpPressed = ButtonUp.State; - var isDownPressed = ButtonDown.State; + private void Update() + { + var isLeftPressed = ButtonLeft.State; + var isRightPressed = ButtonRight.State; + var isUpPressed = ButtonUp.State; + var isDownPressed = ButtonDown.State; - var newPosition = GetDigitalPosition(isLeftPressed, isRightPressed, isUpPressed, isDownPressed); + var newPosition = GetDigitalPosition(isLeftPressed, isRightPressed, isUpPressed, isDownPressed); - if (newPosition != Position) - { - Updated?.Invoke(this, new ChangeResult(newPosition, Position)); - Position = newPosition; - } + if (newPosition != Position) + { + Updated?.Invoke(this, new ChangeResult(newPosition, Position)); + Position = newPosition; } + } - private DigitalJoystickPosition GetDigitalPosition(bool isLeftPressed, bool isRightPressed, bool isUpPressed, bool isDownPressed) - { - if (isRightPressed) - { //Right - if (isUpPressed) - { - return DigitalJoystickPosition.UpRight; - } - if (isDownPressed) - { - return DigitalJoystickPosition.DownRight; - } - return DigitalJoystickPosition.Right; - } - else if (isLeftPressed) - { //Left - if (isUpPressed) - { - return DigitalJoystickPosition.UpLeft; - } - if (isDownPressed) - { - return DigitalJoystickPosition.DownLeft; - } - return DigitalJoystickPosition.Left; + private DigitalJoystickPosition GetDigitalPosition(bool isLeftPressed, bool isRightPressed, bool isUpPressed, bool isDownPressed) + { + if (isRightPressed) + { //Right + if (isUpPressed) + { + return DigitalJoystickPosition.UpRight; } - else if (isUpPressed) - { //Up - return DigitalJoystickPosition.Up; + if (isDownPressed) + { + return DigitalJoystickPosition.DownRight; } - else if (isDownPressed) - { //Down - return DigitalJoystickPosition.Down; + return DigitalJoystickPosition.Right; + } + else if (isLeftPressed) + { //Left + if (isUpPressed) + { + return DigitalJoystickPosition.UpLeft; } - else - { //Center - return DigitalJoystickPosition.Center; + if (isDownPressed) + { + return DigitalJoystickPosition.DownLeft; } + return DigitalJoystickPosition.Left; } - - /// - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); + else if (isUpPressed) + { //Up + return DigitalJoystickPosition.Up; } + else if (isDownPressed) + { //Down + return DigitalJoystickPosition.Down; + } + else + { //Center + return DigitalJoystickPosition.Center; + } + } - /// - /// Dispose of the object - /// - /// Is disposing - protected virtual void Dispose(bool disposing) + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose of the object + /// + /// Is disposing + protected virtual void Dispose(bool disposing) + { + if (!IsDisposed) { - if (!IsDisposed) + if (disposing && createdPorts) { - if (disposing && createdPorts) - { - portDown?.Dispose(); - portLeft?.Dispose(); - portRight?.Dispose(); - portUp?.Dispose(); - } - - IsDisposed = true; + portDown?.Dispose(); + portLeft?.Dispose(); + portRight?.Dispose(); + portUp?.Dispose(); } + + IsDisposed = true; } } } \ No newline at end of file diff --git a/Source/Meadow.Foundation.Core/Sensors/Hid/DigitalPushButtonJoystick.cs b/Source/Meadow.Foundation.Core/Sensors/Hid/DigitalPushButtonJoystick.cs new file mode 100644 index 0000000000..5468802b47 --- /dev/null +++ b/Source/Meadow.Foundation.Core/Sensors/Hid/DigitalPushButtonJoystick.cs @@ -0,0 +1,57 @@ +using Meadow.Foundation.Sensors.Buttons; +using Meadow.Hardware; +using Meadow.Peripherals.Sensors.Hid; +using System; +using System.Threading.Tasks; + +namespace Meadow.Foundation.Sensors.Hid; + +/// +/// Represents a 4 switch digital joystick / directional pad (D-pad) with a center push button +/// +public class DigitalPushButtonJoystick : DigitalJoystick, IDigitalPushButtonJoystick +{ + /// + public TimeSpan LongClickedThreshold { get => _centerButton.LongClickedThreshold; set => _centerButton.LongClickedThreshold = value; } + + /// + public bool State => _centerButton.State; + + /// + public event EventHandler? PressStarted; + /// + public event EventHandler? PressEnded; + /// + public event EventHandler? Clicked; + /// + public event EventHandler? LongClicked; + + private readonly PushButton _centerButton; + + /// + /// Create a new DigitalJoystick object + /// + /// The pin connected to the up switch + /// The pin connected to the down switch + /// The pin connected to the left switch + /// The pin connected to the right switch + /// The pin connected to the center switch + /// The resistor mode for all pins + public DigitalPushButtonJoystick(IPin pinUp, IPin pinDown, IPin pinLeft, IPin pinRight, IPin pinCenter, ResistorMode resistorMode) + : base(pinUp, pinDown, pinLeft, pinRight, resistorMode) + { + var centerPort = pinCenter.CreateDigitalInterruptPort(InterruptMode.EdgeBoth, resistorMode); + _centerButton = new PushButton(centerPort); + + _centerButton.PressStarted += (s, e) => { PressStarted?.Invoke(this, e); }; + _centerButton.PressEnded += (s, e) => { PressEnded?.Invoke(this, e); }; + _centerButton.Clicked += (s, e) => { Clicked?.Invoke(this, e); }; + _centerButton.LongClicked += (s, e) => { LongClicked?.Invoke(this, e); }; + } + + /// + public Task Read() + { + return _centerButton.Read(); + } +} diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroGraphics/Driver/Graphics.MicroGraphics.csproj b/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroGraphics/Driver/Graphics.MicroGraphics.csproj index 93b8b180a7..e1bfe7e86f 100644 --- a/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroGraphics/Driver/Graphics.MicroGraphics.csproj +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroGraphics/Driver/Graphics.MicroGraphics.csproj @@ -18,6 +18,7 @@ Meadow,Meadow.Foundation,Display,Graphics true Lightweight integer accurate 2d graphics drawing system designed for embedded applications + True diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroGraphics/Driver/GraphicsPath.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroGraphics/Driver/GraphicsPath.cs index bbcb07bb28..a91819ca98 100644 --- a/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroGraphics/Driver/GraphicsPath.cs +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroGraphics/Driver/GraphicsPath.cs @@ -54,6 +54,11 @@ public class GraphicsPath /// public int PointCount => PathActions.Count; + /// + /// The collection of points + /// + public Point[] Points { get; private set; } = Array.Empty(); + /// /// The number of verbs/actions used /// @@ -381,12 +386,12 @@ public void Close() PathActions.Add(new PathAction(GetPathStart().PathPoint, VerbType.Close)); } - PathAction GetLastAction() + private PathAction GetLastAction() { return PathActions.Last(); } - PathAction GetPathStart() + private PathAction GetPathStart() { var action = PathActions.Where(p => p.Verb == VerbType.Close).LastOrDefault(); diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroLayout/Driver/Charts/HistogramChart.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroLayout/Driver/Charts/HistogramChart.cs index d5f81c012e..beecacb50e 100644 --- a/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroLayout/Driver/Charts/HistogramChart.cs +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroLayout/Driver/Charts/HistogramChart.cs @@ -170,6 +170,9 @@ private void DrawSeries(MicroGraphics graphics, List serie var barHeight = (int)(heightScale * pair.Y); + // make sure we don't draw off-chart for over-scale values + if (barHeight > ChartAreaHeight) { barHeight = ChartAreaHeight; } + DrawValueBar( graphics, s, @@ -180,13 +183,6 @@ private void DrawSeries(MicroGraphics graphics, List serie barHeight, seriesList[s].ForeColor, true); - //graphics.DrawRectangle( - // x - halfWidth, - // ChartAreaBottom - barHeight, - // barWidth, - // barHeight, - // color: seriesList[s].ForeColor, - // filled: true); } } diff --git a/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroLayout/Driver/Hmi/TouchscreenCalibrationService.cs b/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroLayout/Driver/Hmi/TouchscreenCalibrationService.cs index 4451dc0eea..69ca8939e6 100644 --- a/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroLayout/Driver/Hmi/TouchscreenCalibrationService.cs +++ b/Source/Meadow.Foundation.Libraries_and_Frameworks/Graphics.MicroLayout/Driver/Hmi/TouchscreenCalibrationService.cs @@ -29,6 +29,11 @@ public class TouchscreenCalibrationService public TouchscreenCalibrationService(DisplayScreen screen, FileInfo calibrationDataFile) { + if (screen.TouchScreen == null) + { + throw new ArgumentException("DisplayScreen.TouchScreen must not be null"); + } + if (screen?.TouchScreen is ICalibratableTouchscreen cts) { _touchscreen = cts; @@ -104,8 +109,9 @@ public Task Calibrate(bool saveCalibrationData = true) _screen.Controls.Clear(); if (saveCalibrationData) { - _instruction.Text = "Saving Calibration Data..."; + Resolver.Log.Info($"Saving Calibration Data..."); SaveCalibrationData(_calPoints); + Resolver.Log.Info($"Saved"); } CalibrationComplete?.Invoke(this, _calPoints); }); diff --git a/Source/Meadow.Foundation.Peripherals/Displays.WinForms/Driver/WinForms.cs b/Source/Meadow.Foundation.Peripherals/Displays.WinForms/Driver/WinForms.cs index f40481a355..96d47ff795 100644 --- a/Source/Meadow.Foundation.Peripherals/Displays.WinForms/Driver/WinForms.cs +++ b/Source/Meadow.Foundation.Peripherals/Displays.WinForms/Driver/WinForms.cs @@ -71,7 +71,7 @@ public WinFormsDisplay(int width = 800, int height = 600, ColorMode colorMode = } /// - public void Resize(int width, int height, float displayScale = 1) + void IResizablePixelDisplay.Resize(int width, int height, float displayScale) { lock (buffer) { diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Datasheet/MCP2515.pdf b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Datasheet/MCP2515.pdf new file mode 100644 index 0000000000..dc179bfeb4 Binary files /dev/null and b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Datasheet/MCP2515.pdf differ diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/ICs.CAN.Mcp2515.csproj b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/ICs.CAN.Mcp2515.csproj new file mode 100644 index 0000000000..d3c1e99f6c --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/ICs.CAN.Mcp2515.csproj @@ -0,0 +1,26 @@ + + + true + icon.png + Wilderness Labs, Inc + netstandard2.1 + Library + Mcp2515 + Wilderness Labs, Inc + http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/ + Meadow.Foundation.ICs.CAN.Mcp2515 + https://github.com/WildernessLabs/Meadow.Foundation + Meadow.Foundation, CAN, MCP2515 + 0.1.45 + true + Microchip MCP2515 CAN Controller + 10 + enable + + + + + + + + \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.BitCalc.cs b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.BitCalc.cs new file mode 100644 index 0000000000..dd0a035318 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.BitCalc.cs @@ -0,0 +1,115 @@ +using Meadow.Hardware; + +namespace Meadow.Foundation.ICs.CAN; + +public partial class Mcp2515 +{ + private (byte CFG1, byte CFG2, byte CFG3) GetConfigForOscillatorAndBitrate(CanOscillator oscillator, CanBitrate bitrate) + { + switch (oscillator) + { + case CanOscillator.Osc_8MHz: + switch (bitrate) + { + case CanBitrate.Can_5kbps: + return (0x1F, 0xBF, 0x87); + case CanBitrate.Can_10kbps: + return (0x0F, 0xBF, 0x87); + case CanBitrate.Can_20kbps: + return (0x07, 0xBF, 0x87); + case CanBitrate.Can_33kbps: + return (0x47, 0xE2, 0x85); + case CanBitrate.Can_40kbps: + return (0x03, 0xBF, 0x87); + case CanBitrate.Can_50kbps: + return (0x03, 0xB4, 0x86); + case CanBitrate.Can_80kbps: + return (0x01, 0xBF, 0x87); + case CanBitrate.Can_100kbps: + return (0x01, 0xB4, 0x86); + case CanBitrate.Can_125kbps: + return (0x01, 0xB1, 0x85); + case CanBitrate.Can_200kbps: + return (0x00, 0xB4, 0x86); + case CanBitrate.Can_250kbps: + return (0x00, 0xB1, 0x85); + case CanBitrate.Can_500kbps: + return (0x00, 0x90, 0x82); + case CanBitrate.Can_1Mbps: + return (0x00, 0x80, 0x80); + } + break; + + case CanOscillator.Osc_10MHz: + // TODO: add supported things here + break; + + case CanOscillator.Osc_16MHz: + switch (bitrate) + { + case CanBitrate.Can_5kbps: + return (0x3f, 0xff, 0x87); + case CanBitrate.Can_10kbps: + return (0x1f, 0xff, 0x87); + case CanBitrate.Can_20kbps: + return (0x0f, 0xff, 0x87); + case CanBitrate.Can_33kbps: + return (0x4e, 0xf1, 0x85); + case CanBitrate.Can_40kbps: + return (0x07, 0xff, 0x87); + case CanBitrate.Can_50kbps: + return (0x07, 0xfa, 0x87); + case CanBitrate.Can_80kbps: + return (0x03, 0xff, 0x87); + case CanBitrate.Can_83kbps: + return (0x03, 0xbe, 0x07); + case CanBitrate.Can_95kbps: + return (0x03, 0xad, 0x07); + case CanBitrate.Can_100kbps: + return (0x03, 0xfa, 0x87); + case CanBitrate.Can_125kbps: + return (0x03, 0xf0, 0x86); + case CanBitrate.Can_200kbps: + return (0x01, 0xfa, 0x87); + case CanBitrate.Can_250kbps: + return (0x41, 0xf1, 0x85); + case CanBitrate.Can_500kbps: + return (0x00, 0xf0, 0x86); + case CanBitrate.Can_1Mbps: + return (0x00, 0xd0, 0x82); + } + break; + + case CanOscillator.Osc_20MHz: + switch (bitrate) + { + case CanBitrate.Can_33kbps: + return (0x0b, 0xff, 0x87); + case CanBitrate.Can_40kbps: + return (0x09, 0xff, 0x87); + case CanBitrate.Can_50kbps: + return (0x09, 0xfa, 0x87); + case CanBitrate.Can_80kbps: + return (0x04, 0xff, 0x87); + case CanBitrate.Can_83kbps: + return (0x04, 0xfe, 0x87); + case CanBitrate.Can_100kbps: + return (0x04, 0xfa, 0x87); + case CanBitrate.Can_125kbps: + return (0x03, 0xfa, 0x87); + case CanBitrate.Can_200kbps: + return (0x01, 0xff, 0x87); + case CanBitrate.Can_250kbps: + return (0x41, 0xfb, 0x86); + case CanBitrate.Can_500kbps: + return (0x00, 0xfa, 0x87); + case CanBitrate.Can_1Mbps: + return (0x00, 0xd9, 0x82); + } + break; + + } + + throw new System.NotSupportedException("Provided Bitrate and Oscillator frequency is not supported"); + } +} diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.CanBus.cs b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.CanBus.cs new file mode 100644 index 0000000000..7c759413f5 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.CanBus.cs @@ -0,0 +1,91 @@ +using Meadow.Hardware; +using System; +using System.Threading.Tasks; + +namespace Meadow.Foundation.ICs.CAN; + +public partial class Mcp2515 +{ + public class Mcp2515CanBus : ICanBus + { + /// + public event EventHandler? FrameReceived; + + private Mcp2515 Controller { get; } + + internal Mcp2515CanBus(Mcp2515 controller) + { + Controller = controller; + + if (Controller.InterruptPort != null) + { + Controller.InterruptPort.Changed += OnInterruptPortChanged; + } + } + + private void OnInterruptPortChanged(object sender, DigitalPortResult e) + { + // TODO: check why the interrupt happened (error, frame received, etc) + + if (FrameReceived != null) + { + var frame = ReadFrame(); + Task.Run(() => FrameReceived.Invoke(this, frame)); + } + } + + /// + public bool IsFrameAvailable() + { + var status = Controller.GetStatus(); + + if ((status & Status.RX0IF) == Status.RX0IF) + { + return true; + } + else if ((status & Status.RX1IF) == Status.RX1IF) + { + return true; + } + + return false; + } + + /// + public void WriteFrame(ICanFrame frame) + { + Controller.WriteFrame(frame, 0); + } + + /// + public ICanFrame? ReadFrame() + { + var status = Controller.GetStatus(); + + if ((status & Status.RX0IF) == Status.RX0IF) + { // message in buffer 0 + return Controller.ReadDataFrame(RxBufferNumber.RXB0); + } + else if ((status & Status.RX1IF) == Status.RX1IF) + { // message in buffer 1 + return Controller.ReadDataFrame(RxBufferNumber.RXB1); + } + else + { // no messages available + return null; + } + } + + /// + public void SetFilter(int filter) + { + throw new NotImplementedException(); + } + + /// + public void SetMask(int filter) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.Enums.cs b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.Enums.cs new file mode 100644 index 0000000000..e8ba1c8e30 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.Enums.cs @@ -0,0 +1,223 @@ +using System; + +namespace Meadow.Foundation.ICs.CAN; + +public partial class Mcp2515 +{ + public enum CanOscillator + { + Osc_8MHz, + Osc_10MHz, + Osc_16MHz, + Osc_20MHz, + } + + private enum Register : byte + { + RXF0SIDH = 0x00, + RXF0SIDL = 0x01, + RXF0EID8 = 0x02, + RXF0EID0 = 0x03, + RXF1SIDH = 0x04, + RXF1SIDL = 0x05, + RXF1EID8 = 0x06, + RXF1EID0 = 0x07, + RXF2SIDH = 0x08, + RXF2SIDL = 0x09, + RXF2EID8 = 0x0A, + RXF2EID0 = 0x0B, + BFPCTRL = 0x0C, + TXRTSCTRL = 0x0D, + CANSTAT = 0x0E, + CANCTRL = 0x0F, + RXF3SIDH = 0x10, + RXF3SIDL = 0x11, + RXF3EID8 = 0x12, + RXF3EID0 = 0x13, + RXF4SIDH = 0x14, + RXF4SIDL = 0x15, + RXF4EID8 = 0x16, + RXF4EID0 = 0x17, + RXF5SIDH = 0x18, + RXF5SIDL = 0x19, + RXF5EID8 = 0x1A, + RXF5EID0 = 0x1B, + TEC = 0x1C, + REC = 0x1D, + RXM0SIDH = 0x20, + RXM0SIDL = 0x21, + RXM0EID8 = 0x22, + RXM0EID0 = 0x23, + RXM1SIDH = 0x24, + RXM1SIDL = 0x25, + RXM1EID8 = 0x26, + RXM1EID0 = 0x27, + CNF3 = 0x28, + CNF2 = 0x29, + CNF1 = 0x2A, + CANINTE = 0x2B, + CANINTF = 0x2C, + EFLG = 0x2D, + TXB0CTRL = 0x30, + TXB0SIDH = 0x31, + TXB0SIDL = 0x32, + TXB0EID8 = 0x33, + TXB0EID0 = 0x34, + TXB0DLC = 0x35, + TXB0DATA = 0x36, + TXB1CTRL = 0x40, + TXB1SIDH = 0x41, + TXB1SIDL = 0x42, + TXB1EID8 = 0x43, + TXB1EID0 = 0x44, + TXB1DLC = 0x45, + TXB1DATA = 0x46, + TXB2CTRL = 0x50, + TXB2SIDH = 0x51, + TXB2SIDL = 0x52, + TXB2EID8 = 0x53, + TXB2EID0 = 0x54, + TXB2DLC = 0x55, + TXB2DATA = 0x56, + RXB0CTRL = 0x60, + RXB0SIDH = 0x61, + RXB0SIDL = 0x62, + RXB0EID8 = 0x63, + RXB0EID0 = 0x64, + RXB0DLC = 0x65, + RXB0DATA = 0x66, + RXB1CTRL = 0x70, + RXB1SIDH = 0x71, + RXB1SIDL = 0x72, + RXB1EID8 = 0x73, + RXB1EID0 = 0x74, + RXB1DLC = 0x75, + RXB1DATA = 0x76 + } + + private enum Command : byte + { + Write = 0x02, + Read = 0x03, + Bitmod = 0x05, + LoadTX0 = 0x40, + LoadTX1 = 0x42, + LoadTX2 = 0x44, + RTS_TX0 = 0x81, + RTS_TX1 = 0x82, + RTS_TX2 = 0x84, + RTSALL = 0x87, + ReadRX0 = 0x90, + ReadRX1 = 0x94, + ReadStatus = 0xA0, + RX_Status = 0xB0, + Reset = 0xC0 + } + + private enum RxBufferNumber + { + RXB0 = 0, + RXB1 = 1, + } + + private enum Mode : byte + { + Normal = 0x00, + Sleep = 0x20, + Loopback = 0x40, + ListenOnly = 0x60, + Configure = 0x80, + PowerUp = 0xE0 + } + + private enum Control : byte + { + REQOP = 0xE0, + ABAT = 0x10, + OSM = 0x08, + CLKEN = 0x04, + CLKPRE = 0x03 + } + + [Flags] + private enum Status : byte + { + NONE = 0, + RX0IF = (1 << 0), + RX1IF = (1 << 1) + } + + private enum Result : byte + { + Ok = 0, + Failed = 1, + TransmitBusy = 2, + FailToInit = 3, + FailToSend = 4, + NoMessage = 5 + } + + [Flags] + private enum InterruptFlag : byte + { + RX0IF = 0x01, + RX1IF = 0x02, + TX0IF = 0x04, + TX1IF = 0x08, + TX2IF = 0x10, + ERRIF = 0x20, + WAKIF = 0x40, + MERRF = 0x80 + } + + [Flags] + private enum InterruptEnable : byte + { + DisableAll = 0, + RXB0 = 0x01, + RXB1 = 0x02, + TXB0 = 0x04, + TXB1 = 0x08, + TXB2 = 0x10, + ERR = 0x20, + WAKE = 0x40, + MSG_ERR = 0x80 + } + + private enum RxPinSettings : byte + { + DISABLE = 0x00, + RX0B_EN_INT = 0x05, + RX0B_EN_DIG_OUT_HIGH = 0x14, + RX0B_EN_DIG_OUT_LOW = 0x04, + RX1B_EN_INT = 0x0A, + RX1B_EN_DIG_OUT_HIGH = 0x18, + RX1B_EN_DIG_OUT_LOW = 0x08, + } + + [Flags] + private enum TxRtsSettings : byte + { + RTS_PINS_DIG_IN = 0x00, + TX0RTS = 0x01, + TX1RTS = 0x02, + TX2RTS = 0x04, + } + + private const byte MCP_SIDH = 0; + private const byte MCP_SIDL = 1; + private const byte MCP_EID8 = 2; + private const byte MCP_EID0 = 3; + private const byte MCP_DLC = 4; + private const byte MCP_DATA = 5; + + private const byte TXB_EXIDE_MASK = 0x08; + private const byte DLC_MASK = 0x0F; + private const byte RTR_MASK = 0x40; + + private const uint CAN_EFF_FLAG = 0x80000000; + private const int CAN_RTR_FLAG = 0x40000000; + private const int CAN_ERR_FLAG = 0x20000000; + + private const byte RXBnCTRL_RTR = 0x08; +} diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.cs b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.cs new file mode 100644 index 0000000000..25cf416467 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Driver/Mcp2515.cs @@ -0,0 +1,403 @@ +using Meadow.Hardware; +using Meadow.Logging; +using System; +using System.Threading; + +namespace Meadow.Foundation.ICs.CAN; + +/// +/// Encapsulation for the Microchip MCP2515 CAN controller +/// +public partial class Mcp2515 : ICanController +{ + public const SpiClockConfiguration.Mode DefaultSpiMode = SpiClockConfiguration.Mode.Mode0; + + private byte BRP_Default = 0x01; + private byte SJW_Default = 0x01; + private byte SAM_1x = 0x00; + private byte SAM_3x = 0x40; + private byte PHASE_SEG1_Default = 0x04;// = 0x01; + private byte PHASE_SEG2_Default = 0x03;//0x02; + private byte PROP_SEG_Default = 0x02;// 0x01; + + private ICanBus? _busInstance; + private CanOscillator _oscillator; + + private ISpiBus SpiBus { get; } + private IDigitalOutputPort ChipSelect { get; } + private Logger? Logger { get; } + private IDigitalInterruptPort? InterruptPort { get; } + + public Mcp2515( + ISpiBus bus, + IDigitalOutputPort chipSelect, + CanOscillator oscillator = CanOscillator.Osc_8MHz, + IDigitalInterruptPort? interruptPort = null, + Logger? logger = null) + { + if (interruptPort != null) + { + if (interruptPort.InterruptMode != InterruptMode.EdgeFalling) + { + throw new ArgumentException("InterruptPort must be a falling-edge interrupt"); + } + } + + SpiBus = bus; + ChipSelect = chipSelect; + Logger = logger; + InterruptPort = interruptPort; + _oscillator = oscillator; + } + + /// + public ICanBus CreateCanBus(CanBitrate bitrate, int busNumber = 0) + { + if (_busInstance == null) + { + Initialize(bitrate, _oscillator); + + _busInstance = new Mcp2515CanBus(this); + } + + return _busInstance; + } + + private void Initialize(CanBitrate bitrate, CanOscillator oscillator) + { + Reset(); + + Thread.Sleep(10); + + // put the chip into config mode + var mode = GetMode(); + if (mode != Mode.Configure) + { + SetMode(Mode.Configure); + } + + ClearFiltersAndMasks(); + + ClearControlBuffers(); + + if (InterruptPort != null) + { + // TODO: add error condition handling + //ConfigureInterrupts(InterruptEnable.RXB0 | InterruptEnable.RXB1 | InterruptEnable.ERR | InterruptEnable.MSG_ERR); + ConfigureInterrupts(InterruptEnable.RXB0 | InterruptEnable.RXB1); + ClearInterrupt((InterruptFlag)0xff); + } + else + { + ConfigureInterrupts(InterruptEnable.DisableAll); + } + + ModifyRegister(Register.RXB0CTRL, + 0x60 | 0x04 | 0x07, + 0x00 | 0x04 | 0x00); + ModifyRegister(Register.RXB1CTRL, + 0x60 | 0x07, + 0x00 | 0x01); + + DisableFilters(); + + LogRegisters(Register.RXF0SIDH, 14); + LogRegisters(Register.CANSTAT, 2); + LogRegisters(Register.RXF3SIDH, 14); + LogRegisters(Register.RXM0SIDH, 8); + LogRegisters(Register.CNF3, 6); + + var cfg = GetConfigForOscillatorAndBitrate(oscillator, bitrate); + WriteRegister(Register.CNF1, cfg.CFG1); + WriteRegister(Register.CNF2, cfg.CFG2); + WriteRegister(Register.CNF3, cfg.CFG3); + LogRegisters(Register.CNF3, 3); + + SetMode(Mode.Normal); + } + + private void DisableFilters() + { + ModifyRegister(Register.RXB0CTRL, + 0x60, + 0x60); + ModifyRegister(Register.RXB1CTRL, + 0x60, + 0x60); + } + + private void ClearInterrupt(InterruptFlag flag) + { + ModifyRegister(Register.CANINTF, (byte)flag, 0); + + LogRegisters(Register.CANINTF, 1); + } + + private void WriteFrame(ICanFrame frame, int bufferNumber) + { + if (frame is DataFrame df) + { + var ctrl_reg = bufferNumber switch + { + 0 => Register.TXB0CTRL, + 1 => Register.TXB1CTRL, + 2 => Register.TXB2CTRL, + _ => throw new ArgumentOutOfRangeException() + }; + + if (frame is ExtendedDataFrame edf) + { + var eid0 = (byte)(edf.ID & 0xff); + var eid8 = (byte)(edf.ID >> 8); + var id = edf.ID >> 16; + var sidh = (byte)(id >> 5); + var sidl = (byte)(id & 3); + sidl += (byte)((id & 0x1c) << 3); + sidl |= TXB_EXIDE_MASK; + + WriteRegister(ctrl_reg + 1, sidh); + WriteRegister(ctrl_reg + 2, sidl); + WriteRegister(ctrl_reg + 3, eid8); + WriteRegister(ctrl_reg + 4, eid0); + } + else if (frame is StandardDataFrame sdf) + { + // put the frame data into a buffer (0-2) + var sidh = (byte)(sdf.ID >> 3); + var sidl = (byte)(sdf.ID << 5 & 0xe0); + WriteRegister(ctrl_reg + 1, sidh); + WriteRegister(ctrl_reg + 2, sidl); + } + // TODO: handle RTR + + WriteRegister(ctrl_reg + 5, (byte)df.Payload.Length); + byte i = 0; + foreach (var b in df.Payload) + { + WriteRegister(ctrl_reg + 6 + i, b); + i++; + } + + // transmit the buffer + WriteRegister(ctrl_reg, 0x08); + } + else + { + throw new NotSupportedException($"Sending frames of type {frame.GetType().Name} is not supported"); + } + } + + private void Reset() + { + Span tx = stackalloc byte[1]; + Span rx = stackalloc byte[1]; + + tx[0] = (byte)Command.Reset; + + SpiBus.Exchange(ChipSelect, tx, rx); + } + + private void LogRegisters(Register start, byte count) + { + var values = ReadRegister(start, count); + + Resolver.Log.Info($"{(byte)start:X2} ({start}): {BitConverter.ToString(values)}"); + } + + private Mode GetMode() + { + return (Mode)(ReadRegister(Register.CANSTAT)[0] | 0xE0); + } + + private void SetMode(Mode mode) + { + ModifyRegister(Register.CANCTRL, (byte)Control.REQOP, (byte)mode); + } + + private Status GetStatus() + { + Span tx = stackalloc byte[2]; + Span rx = stackalloc byte[2]; + + tx[0] = (byte)Command.ReadStatus; + tx[1] = 0xff; + + SpiBus.Exchange(ChipSelect, tx, rx); + + return (Status)rx[1]; + } + + private void WriteRegister(Register register, byte value) + { + Span tx = stackalloc byte[3]; + Span rx = stackalloc byte[3]; + + tx[0] = (byte)Command.Write; + tx[1] = (byte)register; + tx[2] = value; + + SpiBus.Exchange(ChipSelect, tx, rx); + } + + private void WriteRegister(Register register, Span data) + { + Span tx = stackalloc byte[data.Length + 2]; + Span rx = stackalloc byte[data.Length + 2]; + + tx[0] = (byte)Command.Write; + tx[1] = (byte)register; + data.CopyTo(tx.Slice(2)); + + SpiBus.Exchange(ChipSelect, tx, rx); + } + + private byte[] ReadRegister(Register register, byte length = 1) + { + Span tx = stackalloc byte[2 + length]; + Span rx = stackalloc byte[2 + length]; + + tx[0] = (byte)Command.Read; + tx[1] = (byte)register; + + SpiBus.Exchange(ChipSelect, tx, rx); + + return rx.Slice(2).ToArray(); + } + + private void ModifyRegister(Register register, byte mask, byte value) + { + Span tx = stackalloc byte[4]; + Span rx = stackalloc byte[4]; + + tx[0] = (byte)Command.Bitmod; + tx[1] = (byte)register; + tx[2] = mask; + tx[3] = value; + + SpiBus.Exchange(ChipSelect, tx, rx); + } + + private void EnableMasksAndFilters(bool enable) + { + if (enable) + { + ModifyRegister(Register.RXB0CTRL, 0x64, 0x00); + ModifyRegister(Register.RXB1CTRL, 0x60, 0x00); + } + else + { + ModifyRegister(Register.RXB0CTRL, 0x64, 0x60); + ModifyRegister(Register.RXB1CTRL, 0x60, 0x60); + } + } + + private void ConfigureInterrupts(InterruptEnable interrupts) + { + WriteRegister(Register.CANINTE, (byte)interrupts); + } + + private void ClearFiltersAndMasks() + { + Span zeros12 = stackalloc byte[12]; + WriteRegister(Register.RXF0SIDH, zeros12); + WriteRegister(Register.RXF3SIDH, zeros12); + + Span zeros8 = stackalloc byte[8]; + WriteRegister(Register.RXM0SIDH, zeros8); + } + + private void ClearControlBuffers() + { + Span zeros14 = stackalloc byte[14]; + WriteRegister(Register.TXB0CTRL, zeros14); + WriteRegister(Register.TXB1CTRL, zeros14); + WriteRegister(Register.TXB2CTRL, zeros14); + + WriteRegister(Register.RXB0CTRL, 0); + WriteRegister(Register.RXB1CTRL, 0); + } + + private DataFrame ReadDataFrame(RxBufferNumber bufferNumber) + { + var sidh_reg = bufferNumber == RxBufferNumber.RXB0 ? Register.RXB0SIDH : Register.RXB1SIDH; + var ctrl_reg = bufferNumber == RxBufferNumber.RXB0 ? Register.RXB0CTRL : Register.RXB1CTRL; + var data_reg = bufferNumber == RxBufferNumber.RXB0 ? Register.RXB0DATA : Register.RXB1DATA; + var int_flag = bufferNumber == RxBufferNumber.RXB0 ? InterruptFlag.RX0IF : InterruptFlag.RX1IF; + + // read 5 bytes + var buffer = ReadRegister(sidh_reg, 5); + + int id = (buffer[MCP_SIDH] << 3) + (buffer[MCP_SIDL] >> 5); + + bool isExtended = false; + + // check to see if it's an extended ID + if ((buffer[MCP_SIDL] & TXB_EXIDE_MASK) == TXB_EXIDE_MASK) + { + id = (id << 2) + (buffer[MCP_SIDL] & 0x03); + id = (id << 8) + buffer[MCP_EID8]; + id = (id << 8) + buffer[MCP_EID0]; + isExtended = true; + } + + byte dataLengthCode = (byte)(buffer[MCP_DLC] & DLC_MASK); + if (dataLengthCode > 8) throw new Exception($"DLC of {dataLengthCode} is > 8 bytes"); + + // see if it's a remote transmission request + var isRemoteTransmitRequest = false; + var ctrl = ReadRegister(ctrl_reg)[0]; + if ((ctrl & RXBnCTRL_RTR) == RXBnCTRL_RTR) + { + isRemoteTransmitRequest = true; + } + + // create the frame + DataFrame frame; + + if (isExtended) + { + if (isRemoteTransmitRequest) + { + frame = new ExtendedRtrFrame + { + ID = id, + }; + } + else + { + frame = new ExtendedDataFrame + { + ID = id, + }; + } + } + else + { + if (isRemoteTransmitRequest) + { + frame = new StandardRtrFrame + { + ID = id, + }; + } + else + { + frame = new StandardDataFrame + { + ID = id, + }; + } + } + + // read the frame data + frame.Payload = ReadRegister(data_reg, dataLengthCode); + + // clear the interrupt flag + if (InterruptPort != null) + { + ModifyRegister(Register.CANINTF, (byte)int_flag, 0); + } + + return frame; + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Samples/Mcp2515_Sample/Mcp2515_Sample.csproj b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Samples/Mcp2515_Sample/Mcp2515_Sample.csproj new file mode 100644 index 0000000000..18fc6c39fd --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Samples/Mcp2515_Sample/Mcp2515_Sample.csproj @@ -0,0 +1,24 @@ + + + https://github.com/WildernessLabs/Meadow.Foundation + Wilderness Labs, Inc + Wilderness Labs, Inc + true + netstandard2.1 + Library + App + 10 + + + + + + + PreserveNewest + + + + + + + diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Samples/Mcp2515_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Samples/Mcp2515_Sample/MeadowApp.cs new file mode 100644 index 0000000000..7e74039156 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Samples/Mcp2515_Sample/MeadowApp.cs @@ -0,0 +1,76 @@ +using Meadow; +using Meadow.Devices; +using Meadow.Foundation.ICs.CAN; +using Meadow.Hardware; +using System; +using System.Threading.Tasks; + +namespace MeadowApp; + +public class F7FeatherV1App : MeadowApp { } +public class F7FeatherV2App : MeadowApp { } + +public class MeadowApp : App + where T : F7FeatherBase +{ + private Mcp2515 expander; + + // + + public override Task Initialize() + { + Resolver.Log.Info("Initialize..."); + + expander = new Mcp2515( + Device.CreateSpiBus(), + Device.Pins.D05.CreateDigitalOutputPort(true), + Mcp2515.CanOscillator.Osc_8MHz, + Device.Pins.D05.CreateDigitalInterruptPort(InterruptMode.EdgeFalling), + Resolver.Log); + + + return base.Initialize(); + } + + public override async Task Run() + { + var bus = expander.CreateCanBus(CanBitrate.Can_250kbps); + + Console.WriteLine($"Listening for CAN data..."); + + var tick = 0; + + while (true) + { + var frame = bus.ReadFrame(); + if (frame != null) + { + if (frame is StandardDataFrame sdf) + { + Console.WriteLine($"Standard Frame: {sdf.ID:X3} {BitConverter.ToString(sdf.Payload)}"); + } + else if (frame is ExtendedDataFrame edf) + { + Console.WriteLine($"Extended Frame: {edf.ID:X8} {BitConverter.ToString(edf.Payload)}"); + } + } + else + { + await Task.Delay(100); + } + + if (tick++ % 50 == 0) + { + Console.WriteLine($"Sending Standard Frame..."); + + bus.WriteFrame(new StandardDataFrame + { + ID = 0x700, + Payload = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, (byte)(tick & 0xff) } + }); + } + } + } + + // +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Samples/Mcp2515_Sample/appconfig.yaml b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Samples/Mcp2515_Sample/appconfig.yaml new file mode 100644 index 0000000000..1e279a0e04 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2515/Samples/Mcp2515_Sample/appconfig.yaml @@ -0,0 +1,5 @@ +Logging: + LogLevel: + Default: "Trace" +Lifecycle: + ResetOnAppFailure: false \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Datasheet/MCP2542.pdf b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Datasheet/MCP2542.pdf new file mode 100644 index 0000000000..37d0a32032 Binary files /dev/null and b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Datasheet/MCP2542.pdf differ diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Driver/ICs.CAN.Mcp2542.csproj b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Driver/ICs.CAN.Mcp2542.csproj new file mode 100644 index 0000000000..8f7426d1c4 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Driver/ICs.CAN.Mcp2542.csproj @@ -0,0 +1,29 @@ + + + true + icon.png + Wilderness Labs, Inc + netstandard2.1 + Library + Mcp2542 + Wilderness Labs, Inc + http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/ + Meadow.Foundation.ICs.CAN.Mcp2542 + https://github.com/WildernessLabs/Meadow.Foundation + Meadow.Foundation, CAN, MCP2542 + 0.1.45 + true + Microchip MCP2542 CAN Transceiver + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Driver/Mcp2542.Enums.cs b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Driver/Mcp2542.Enums.cs new file mode 100644 index 0000000000..4d6e3cd466 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Driver/Mcp2542.Enums.cs @@ -0,0 +1,192 @@ +using System; +using System.Runtime.InteropServices; + +namespace Meadow.Foundation.ICs.CAN +{ + public partial class Mcp2515 + { + [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 16)] + public struct Frame + { + [FieldOffset(0)] + public uint ID; + [FieldOffset(4)] + public byte PayloadLength; + [FieldOffset(8)] + public byte[] Payload; + } + + private enum Register : byte + { + RXF0SIDH = 0x00, + RXF0SIDL = 0x01, + RXF0EID8 = 0x02, + RXF0EID0 = 0x03, + RXF1SIDH = 0x04, + RXF1SIDL = 0x05, + RXF1EID8 = 0x06, + RXF1EID0 = 0x07, + RXF2SIDH = 0x08, + RXF2SIDL = 0x09, + RXF2EID8 = 0x0A, + RXF2EID0 = 0x0B, + CANSTAT = 0x0E, + CANCTRL = 0x0F, + RXF3SIDH = 0x10, + RXF3SIDL = 0x11, + RXF3EID8 = 0x12, + RXF3EID0 = 0x13, + RXF4SIDH = 0x14, + RXF4SIDL = 0x15, + RXF4EID8 = 0x16, + RXF4EID0 = 0x17, + RXF5SIDH = 0x18, + RXF5SIDL = 0x19, + RXF5EID8 = 0x1A, + RXF5EID0 = 0x1B, + TEC = 0x1C, + REC = 0x1D, + RXM0SIDH = 0x20, + RXM0SIDL = 0x21, + RXM0EID8 = 0x22, + RXM0EID0 = 0x23, + RXM1SIDH = 0x24, + RXM1SIDL = 0x25, + RXM1EID8 = 0x26, + RXM1EID0 = 0x27, + CNF3 = 0x28, + CNF2 = 0x29, + CNF1 = 0x2A, + CANINTE = 0x2B, + CANINTF = 0x2C, + EFLG = 0x2D, + TXB0CTRL = 0x30, + TXB0SIDH = 0x31, + TXB0SIDL = 0x32, + TXB0EID8 = 0x33, + TXB0EID0 = 0x34, + TXB0DLC = 0x35, + TXB0DATA = 0x36, + TXB1CTRL = 0x40, + TXB1SIDH = 0x41, + TXB1SIDL = 0x42, + TXB1EID8 = 0x43, + TXB1EID0 = 0x44, + TXB1DLC = 0x45, + TXB1DATA = 0x46, + TXB2CTRL = 0x50, + TXB2SIDH = 0x51, + TXB2SIDL = 0x52, + TXB2EID8 = 0x53, + TXB2EID0 = 0x54, + TXB2DLC = 0x55, + TXB2DATA = 0x56, + RXB0CTRL = 0x60, + RXB0SIDH = 0x61, + RXB0SIDL = 0x62, + RXB0EID8 = 0x63, + RXB0EID0 = 0x64, + RXB0DLC = 0x65, + RXB0DATA = 0x66, + RXB1CTRL = 0x70, + RXB1SIDH = 0x71, + RXB1SIDL = 0x72, + RXB1EID8 = 0x73, + RXB1EID0 = 0x74, + RXB1DLC = 0x75, + RXB1DATA = 0x76 + } + + private enum Command : byte + { + Write = 0x02, + Read = 0x03, + Bitmod = 0x05, + LoadTX0 = 0x40, + LoadTX1 = 0x42, + LoadTX2 = 0x44, + RTS_TX0 = 0x81, + RTS_TX1 = 0x82, + RTS_TX2 = 0x84, + RTSALL = 0x87, + ReadRX0 = 0x90, + ReadRX1 = 0x94, + ReadStatus = 0xA0, + RX_Status = 0xB0, + Reset = 0xC0 + } + + private enum RxBufferNumber + { + RXB0 = 0, + RXB1 = 1, + } + + private enum Mode : byte + { + Normal = 0x00, + Sleep = 0x20, + Loopback = 0x40, + ListenOnly = 0x60, + Configure = 0x80, + PowerUp = 0xE0 + } + + private enum Control : byte + { + REQOP = 0xE0, + ABAT = 0x10, + OSM = 0x08, + CLKEN = 0x04, + CLKPRE = 0x03 + } + + [Flags] + private enum Status : byte + { + NONE = 0, + RX0IF = (1 << 0), + RX1IF = (1 << 1) + } + + private enum Result : byte + { + Ok = 0, + Failed = 1, + TransmitBusy = 2, + FailToInit = 3, + FailToSend = 4, + NoMessage = 5 + } + + [Flags] + private enum InterruptFlag : byte + { + RX0IF = 0x01, + RX1IF = 0x02, + TX0IF = 0x04, + TX1IF = 0x08, + TX2IF = 0x10, + ERRIF = 0x20, + WAKIF = 0x40, + MERRF = 0x80 + } + + private const byte MCP_SIDH = 0; + private const byte MCP_SIDL = 1; + private const byte MCP_EID8 = 2; + private const byte MCP_EID0 = 3; + private const byte MCP_DLC = 4; + private const byte MCP_DATA = 5; + + private const byte TXB_EXIDE_MASK = 0x08; + private const byte DLC_MASK = 0x0F; + private const byte RTR_MASK = 0x40; + + private const uint CAN_EFF_FLAG = 0x80000000; + private const int CAN_RTR_FLAG = 0x40000000; + private const int CAN_ERR_FLAG = 0x20000000; + + private const byte RXBnCTRL_RTR = 0x08; + } +} diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Driver/Mcp2542.cs b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Driver/Mcp2542.cs new file mode 100644 index 0000000000..723f90e808 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Driver/Mcp2542.cs @@ -0,0 +1,54 @@ +using Meadow.Hardware; +using Meadow.Logging; +using System; + +namespace Meadow.Foundation.ICs.CAN +{ + /// + /// Encapsulation for the Microchip MCP2542 CAN FD transceiver + /// + public partial class Mcp2542 + { + public const int DefaultBaudRate = 9600; + + private ISerialPort Port { get; } + private IDigitalOutputPort? STBYPort { get; } + private Logger? Logger { get; } + + public Mcp2542(ISerialPort port, IDigitalOutputPort? standby = null, Logger? logger = null) + { + Port = port; + Logger = logger; + STBYPort = standby; + + Port.Open(); + } + + public bool Standby + { + set + { + if (STBYPort == null) throw new Exception("No standby port provided"); + STBYPort.State = value; + } + get + { + if (STBYPort == null) return false; + return STBYPort.State; + } + } + + public byte[] Read() + { + Logger.Trace($"{Port.BytesToRead} bytes available"); + + byte[] buffer = new byte[8]; + + var read = Port.Read(buffer, 0, 8); + + Logger.Trace($"RX {read} bytes"); + + return buffer; + } + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Meadow.ProjLab/Meadow.ProjLab.csproj b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Meadow.ProjLab/Meadow.ProjLab.csproj new file mode 100644 index 0000000000..5d39ef5b41 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Meadow.ProjLab/Meadow.ProjLab.csproj @@ -0,0 +1,38 @@ + + + true + icon.png + Wilderness Labs, Inc + netstandard2.1 + Library + Meadow.ProjLab + Wilderness Labs, Inc + http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/ + Meadow.ProjLab + https://github.com/WildernessLabs/Meadow.Foundation + Meadow.ProjLab + 0.1.45 + true + Base convenience library for the Meadow ProjLab board + enable + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Meadow.ProjLab/ProjLab.cs b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Meadow.ProjLab/ProjLab.cs new file mode 100644 index 0000000000..2a0927c76b --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Meadow.ProjLab/ProjLab.cs @@ -0,0 +1,159 @@ +using Meadow.Foundation.Audio; +using Meadow.Foundation.Displays.TftSpi; +using Meadow.Foundation.Graphics; +using Meadow.Foundation.Sensors.Atmospheric; +using Meadow.Foundation.Sensors.Buttons; +using Meadow.Foundation.Sensors.Light; +using Meadow.Hardware; +using Meadow.Units; +using System; + +namespace Meadow.Devices +{ + public class ProjLab + { + public ISpiBus SpiBus { get; } + public II2cBus I2CBus { get; } + + private readonly Lazy _display; + private readonly Lazy _lightSensor; + private readonly Lazy _upButton; + private readonly Lazy _downButton; + private readonly Lazy _leftButton; + private readonly Lazy _rightButton; + private readonly Lazy _bme680; + private readonly Lazy _speaker; + // onboardLed = new RgbPwmLed(device: Device, + //redPwmPin: Device.Pins.OnboardLedRed, + // greenPwmPin: Device.Pins.OnboardLedGreen, + // bluePwmPin: Device.Pins.OnboardLedBlue); + + public St7789 Display => _display.Value; + public Bh1750 LightSensor => _lightSensor.Value; + public PushButton UpButton => _upButton.Value; + public PushButton DownButton => _downButton.Value; + public PushButton LeftButton => _leftButton.Value; + public PushButton RightButton => _rightButton.Value; + public Bme680 EnvironmentalSensor => _bme680.Value; + public PiezoSpeaker Speaker => _speaker.Value; + + public ProjLab() + { + // create our busses + var config = new SpiClockConfiguration( + new Frequency(48000, Frequency.UnitType.Kilohertz), + SpiClockConfiguration.Mode.Mode3); + + SpiBus = Resolver.Device.CreateSpiBus( + Resolver.Device.GetPin("SCK"), + Resolver.Device.GetPin("MOSI"), + Resolver.Device.GetPin("MISO"), + config); + + I2CBus = Resolver.Device.CreateI2cBus(); + + // lazy load all components + _display = new Lazy(() => + new St7789( + device: Resolver.Device, + spiBus: SpiBus, + chipSelectPin: Resolver.Device.GetPin("A03"), + dcPin: Resolver.Device.GetPin("A04"), + resetPin: Resolver.Device.GetPin("A05"), + width: 240, height: 240, + displayColorMode: ColorType.Format16bppRgb565)); + + _lightSensor = new Lazy(() => + new Bh1750( + i2cBus: I2CBus, + measuringMode: Bh1750.MeasuringModes.ContinuouslyHighResolutionMode, // the various modes take differing amounts of time. + lightTransmittance: 0.5, // lower this to increase sensitivity, for instance, if it's behind a semi opaque window + address: (byte)Bh1750.Addresses.Address_0x23)); + + _upButton = new Lazy(() => + new PushButton( + Resolver.Device.CreateDigitalInputPort( + Resolver.Device.GetPin("D15"), + InterruptMode.EdgeBoth, + ResistorMode.InternalPullDown))); + + _downButton = new Lazy(() => + new PushButton( + Resolver.Device.CreateDigitalInputPort( + Resolver.Device.GetPin("D02"), + InterruptMode.EdgeBoth, + ResistorMode.InternalPullDown))); + + _leftButton = new Lazy(() => + new PushButton( + Resolver.Device.CreateDigitalInputPort( + Resolver.Device.GetPin("D10"), + InterruptMode.EdgeBoth, + ResistorMode.InternalPullDown))); + + _rightButton = new Lazy(() => + new PushButton( + Resolver.Device.CreateDigitalInputPort( + Resolver.Device.GetPin("D05"), + InterruptMode.EdgeBoth, + ResistorMode.InternalPullDown))); + + _bme680 = new Lazy(() => + new Bme680(I2CBus, (byte)Bme680.Addresses.Address_0x76)); + + _speaker = new Lazy(() => + new PiezoSpeaker(Resolver.Device, Resolver.Device.GetPin("D11"))); + } + + public static ( + IPin MB1_CS, + IPin MB1_INT, + IPin MB1_PWM, + IPin MB1_AN, + IPin MB1_SO, + IPin MB1_SI, + IPin MB1_SCK, + IPin MB1_SCL, + IPin MB1_SDA, + + IPin MB2_CS, + IPin MB2_INT, + IPin MB2_PWM, + IPin MB2_AN, + IPin MB2_SO, + IPin MB2_SI, + IPin MB2_SCK, + IPin MB2_SCL, + IPin MB2_SDA, + + IPin A0, + IPin D03, + IPin D04 + ) Pins = ( + Resolver.Device.GetPin("D14"), + Resolver.Device.GetPin("D03"), + Resolver.Device.GetPin("D04"), + Resolver.Device.GetPin("A00"), + Resolver.Device.GetPin("CIPO"), + Resolver.Device.GetPin("COPI"), + Resolver.Device.GetPin("SCK"), + Resolver.Device.GetPin("D08"), + Resolver.Device.GetPin("D07"), + + Resolver.Device.GetPin("A02"), + Resolver.Device.GetPin("D04"), + Resolver.Device.GetPin("D03"), + Resolver.Device.GetPin("A01"), + Resolver.Device.GetPin("CIPO"), + Resolver.Device.GetPin("COPI"), + Resolver.Device.GetPin("SCK"), + Resolver.Device.GetPin("D08"), + Resolver.Device.GetPin("D07"), + + Resolver.Device.GetPin("A00"), + Resolver.Device.GetPin("D03"), + Resolver.Device.GetPin("D04") + ); + } +} + diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Samples/Mcp2542_Sample/Mcp2542_Sample.csproj b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Samples/Mcp2542_Sample/Mcp2542_Sample.csproj new file mode 100644 index 0000000000..6af85f4d27 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Samples/Mcp2542_Sample/Mcp2542_Sample.csproj @@ -0,0 +1,23 @@ + + + https://github.com/WildernessLabs/Meadow.Foundation + Wilderness Labs, Inc + Wilderness Labs, Inc + true + netstandard2.1 + Exe + App + + + + + + + PreserveNewest + + + + + + + diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Samples/Mcp2542_Sample/MeadowApp.cs b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Samples/Mcp2542_Sample/MeadowApp.cs new file mode 100644 index 0000000000..a5a467705d --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Samples/Mcp2542_Sample/MeadowApp.cs @@ -0,0 +1,62 @@ +using Meadow; +using Meadow.Devices; +using Meadow.Foundation.ICs.CAN; +using System; +using System.Threading.Tasks; + +namespace MeadowApp +{ + public class MeadowApp : App + { + public static async Task Main(string[] args) + { + Console.WriteLine("+Main"); + await MeadowOS.Main(args); + Console.WriteLine("-Main"); + } + + // + + Mcp2542 _can; + + public override Task Initialize() + { + Resolver.Log.Info("Initialize..."); + + Resolver.Log.Loglevel = Meadow.Logging.LogLevel.Trace; + + var port = Device.CreateSerialPort(Device.SerialPortNames.Com1, Mcp2542.DefaultBaudRate); + var standby = Device.CreateDigitalOutputPort(ProjLab.Pins.MB1_AN); + _can = new Mcp2542(port, standby, Resolver.Log); + + return base.Initialize(); + } + + public override async Task Run() + { + _can.Standby = false; + + while (true) + { + try + { + var frame = _can.Read(); + + if (frame == null) + { + Resolver.Log.Info("No frames available"); + } + } + catch (Exception ex) + { + Resolver.Log.Error(ex.Message); + } + + await Task.Delay(1000); + + } + } + + // + } +} \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Samples/Mcp2542_Sample/appconfig.yaml b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Samples/Mcp2542_Sample/appconfig.yaml new file mode 100644 index 0000000000..1e279a0e04 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.CAN.Mcp2542/Samples/Mcp2542_Sample/appconfig.yaml @@ -0,0 +1,5 @@ +Logging: + LogLevel: + Default: "Trace" +Lifecycle: + ResetOnAppFailure: false \ No newline at end of file diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Mcp23xxx/Driver/Mcp23xxx.DigitalInterruptPort.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Mcp23xxx/Driver/Mcp23xxx.DigitalInterruptPort.cs index 561c0cd99e..b47b68636a 100644 --- a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Mcp23xxx/Driver/Mcp23xxx.DigitalInterruptPort.cs +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Mcp23xxx/Driver/Mcp23xxx.DigitalInterruptPort.cs @@ -34,6 +34,9 @@ public override TimeSpan GlitchDuration set => _ = value; //fail silently } + /// + public override InterruptMode InterruptMode { get; set; } + /// /// Create a new DigitalInterruptPort object /// @@ -41,8 +44,9 @@ public override TimeSpan GlitchDuration /// The interrupt mode used for the interrupt pin /// The resistor mode used by the interrupt pin public DigitalInterruptPort(IPin pin, InterruptMode interruptMode = InterruptMode.None, ResistorMode resistorMode = ResistorMode.Disabled) - : base(pin, (IDigitalChannelInfo)pin.SupportedChannels![0], interruptMode) + : base(pin, (IDigitalChannelInfo)pin.SupportedChannels![0]) { + InterruptMode = interruptMode; portResistorMode = resistorMode; // seed the initial state diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/ICs.IOExpanders.PCanBasic.csproj b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/ICs.IOExpanders.PCanBasic.csproj new file mode 100644 index 0000000000..4a4fedc3b7 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/ICs.IOExpanders.PCanBasic.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanBus.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanBus.cs new file mode 100644 index 0000000000..e2c4545ae1 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanBus.cs @@ -0,0 +1,130 @@ +using Meadow.Hardware; +using Peak.Can.Basic.BackwardCompatibility; + +namespace ICs.IOExpanders.PCanBasic; + +public class PCanBus : ICanBus +{ + /// + public event EventHandler? FrameReceived; + + private PCanConfiguration configuration; + + internal PCanBus(PCanConfiguration configuration) + { + var result = PCANBasic.Initialize( + configuration.BusHandle, + configuration.Bitrate.ToPCANBaudrate()); + + if (result != TPCANStatus.PCAN_ERROR_OK) + { + throw new Exception($"{result}"); + } + + this.configuration = configuration; + } + + /// + public bool IsFrameAvailable() + { + return false; + } + + private void WriteStandard(StandardDataFrame frame) + { + var msgCanMessage = new TPCANMsg + { + DATA = new byte[frame.Payload.Length], + ID = (uint)frame.ID, + LEN = (byte)frame.Payload.Length, + MSGTYPE = TPCANMessageType.PCAN_MESSAGE_STANDARD + }; + Array.Copy(frame.Payload, msgCanMessage.DATA, frame.Payload.Length); + var result = PCANBasic.Write(configuration.BusHandle, ref msgCanMessage); + if (result != TPCANStatus.PCAN_ERROR_OK) + { + throw new Exception($"{result}"); + } + } + + private void WriteExtended(ExtendedDataFrame frame) + { + var msgCanMessage = new TPCANMsg + { + DATA = new byte[frame.Payload.Length], + ID = (uint)frame.ID, + LEN = (byte)frame.Payload.Length, + MSGTYPE = TPCANMessageType.PCAN_MESSAGE_EXTENDED + }; + Array.Copy(frame.Payload, msgCanMessage.DATA, frame.Payload.Length); + var result = PCANBasic.Write(configuration.BusHandle, ref msgCanMessage); + if (result != TPCANStatus.PCAN_ERROR_OK) + { + throw new Exception($"{result}"); + } + } + + /// + public void WriteFrame(ICanFrame frame) + { + if (frame is ExtendedDataFrame edf) + { + WriteExtended(edf); + } + else if (frame is StandardDataFrame sdf) + { + WriteStandard(sdf); + } + else + { + throw new Exception($"Frame type {frame.GetType().Name} is not supported."); + } + } + + /// + public ICanFrame? ReadFrame() + { + var result = PCANBasic.Read( + configuration.BusHandle, + out TPCANMsg message, + out TPCANTimestamp timeStamp); + + if (result != TPCANStatus.PCAN_ERROR_QRCVEMPTY) + { + DataFrame frame; + + switch (message.MSGTYPE) + { + case TPCANMessageType.PCAN_MESSAGE_STANDARD: + frame = new StandardDataFrame + { + ID = (short)message.ID, + Payload = new byte[message.LEN] + }; + Array.Copy(message.DATA, 0, frame.Payload, 0, message.LEN); + return frame; + case TPCANMessageType.PCAN_MESSAGE_EXTENDED: + frame = new ExtendedDataFrame + { + ID = (int)message.ID, + Payload = new byte[message.LEN] + }; + Array.Copy(message.DATA, 0, frame.Payload, 0, message.LEN); + return frame; + } + } + return null; + } + + /// + public void SetFilter(int filter) + { + throw new NotImplementedException(); + } + + /// + public void SetMask(int filter) + { + throw new NotImplementedException(); + } +} diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanConfiguration.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanConfiguration.cs new file mode 100644 index 0000000000..1053ab5dae --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanConfiguration.cs @@ -0,0 +1,10 @@ +using Meadow.Hardware; +using Peak.Can.Basic.BackwardCompatibility; + +namespace ICs.IOExpanders.PCanBasic; + +public class PCanConfiguration +{ + public ushort BusHandle { get; set; } = PCANBasic.PCAN_USBBUS1; + public CanBitrate Bitrate { get; set; } = CanBitrate.Can_250kbps; +} diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanExtensions.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanExtensions.cs new file mode 100644 index 0000000000..e74803967f --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanExtensions.cs @@ -0,0 +1,29 @@ +using Meadow.Hardware; +using Peak.Can.Basic.BackwardCompatibility; + +namespace ICs.IOExpanders.PCanBasic; + +internal static class PCanExtensions +{ + internal static TPCANBaudrate ToPCANBaudrate(this CanBitrate bitrate) + { + return bitrate switch + { + CanBitrate.Can_1Mbps => TPCANBaudrate.PCAN_BAUD_1M, + CanBitrate.Can_800kbps => TPCANBaudrate.PCAN_BAUD_800K, + CanBitrate.Can_500kbps => TPCANBaudrate.PCAN_BAUD_500K, + CanBitrate.Can_250kbps => TPCANBaudrate.PCAN_BAUD_250K, + CanBitrate.Can_125kbps => TPCANBaudrate.PCAN_BAUD_125K, + CanBitrate.Can_47kbps => TPCANBaudrate.PCAN_BAUD_47K, + CanBitrate.Can_100kbps => TPCANBaudrate.PCAN_BAUD_100K, + CanBitrate.Can_50kbps => TPCANBaudrate.PCAN_BAUD_50K, + CanBitrate.Can_20kbps => TPCANBaudrate.PCAN_BAUD_20K, + CanBitrate.Can_10kbps => TPCANBaudrate.PCAN_BAUD_10K, + CanBitrate.Can_5kbps => TPCANBaudrate.PCAN_BAUD_5K, + CanBitrate.Can_83kbps => TPCANBaudrate.PCAN_BAUD_83K, + CanBitrate.Can_33kbps => TPCANBaudrate.PCAN_BAUD_33K, + CanBitrate.Can_95kbps => TPCANBaudrate.PCAN_BAUD_95K, + _ => throw new NotSupportedException() + }; + } +} diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanFdBus.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanFdBus.cs new file mode 100644 index 0000000000..96b2435df6 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanFdBus.cs @@ -0,0 +1,38 @@ +using Meadow.Hardware; + +namespace ICs.IOExpanders.PCanBasic; + +public class PCanFdBus : ICanBus +{ + internal PCanFdBus(PCanConfiguration configuration) + { + throw new NotImplementedException(); + } + + public event EventHandler? FrameReceived; + + public bool IsFrameAvailable() + { + throw new NotImplementedException(); + } + + public ICanFrame? ReadFrame() + { + throw new NotImplementedException(); + } + + public void SetFilter(int filter) + { + throw new NotImplementedException(); + } + + public void SetMask(int filter) + { + throw new NotImplementedException(); + } + + public void WriteFrame(ICanFrame frame) + { + throw new NotImplementedException(); + } +} diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanUsb.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanUsb.cs new file mode 100644 index 0000000000..c7d0c93f1d --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Driver/PCanUsb.cs @@ -0,0 +1,32 @@ +using Meadow.Hardware; +using Peak.Can.Basic.BackwardCompatibility; + +namespace ICs.IOExpanders.PCanBasic; + +public class PCanUsb : ICanController +{ + public PCanUsb() + { + // TODO: only supported on Windows + // TODO: check for PCANBasic DLL + } + + /// + public ICanBus CreateCanBus(CanBitrate bitrate, int busNumber = 0) + { + var config = new PCanConfiguration + { + Bitrate = bitrate, + BusHandle = (ushort)(PCANBasic.PCAN_USBBUS1 + busNumber) + }; + + if (bitrate == CanBitrate.Can_FD) + { + return new PCanFdBus(config); + } + else + { + return new PCanBus(config); + } + } +} diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Samples/PCanBasic_Sample/PCanBasic_Sample.csproj b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Samples/PCanBasic_Sample/PCanBasic_Sample.csproj new file mode 100644 index 0000000000..ae878308db --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Samples/PCanBasic_Sample/PCanBasic_Sample.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Samples/PCanBasic_Sample/Program.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Samples/PCanBasic_Sample/Program.cs new file mode 100644 index 0000000000..42c16a1611 --- /dev/null +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.PCanBasic/Samples/PCanBasic_Sample/Program.cs @@ -0,0 +1,51 @@ +using ICs.IOExpanders.PCanBasic; +using Meadow.Hardware; + +namespace PCanBasic_ReadSample; + +internal class Program +{ + private static async Task Main(string[] _) + { + // + var expander = new PCanUsb(); + + var bus = expander.CreateCanBus(CanBitrate.Can_250kbps); + + Console.WriteLine($"Listening for CAN data..."); + + var tick = 0; + + while (true) + { + var frame = bus.ReadFrame(); + if (frame != null) + { + if (frame is StandardDataFrame sdf) + { + Console.WriteLine($"Standard Frame: {sdf.ID:X3} {BitConverter.ToString(sdf.Payload)}"); + } + else if (frame is ExtendedDataFrame edf) + { + Console.WriteLine($"Extended Frame: {edf.ID:X8} {BitConverter.ToString(edf.Payload)}"); + } + } + else + { + await Task.Delay(100); + } + + if (tick++ % 50 == 0) + { + Console.WriteLine($"Sending Standard Frame..."); + + bus.WriteFrame(new StandardDataFrame + { + ID = 0x700, + Payload = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, (byte)(tick & 0xff)] + }); + } + } + // + } +} diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Pcx857x/Driver/Pcx857x.DigitalInterruptPort.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Pcx857x/Driver/Pcx857x.DigitalInterruptPort.cs index 1f56366e13..ba928c4b76 100644 --- a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Pcx857x/Driver/Pcx857x.DigitalInterruptPort.cs +++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Pcx857x/Driver/Pcx857x.DigitalInterruptPort.cs @@ -10,6 +10,9 @@ public partial class Pcx857x /// public class DigitalInterruptPort : DigitalInterruptPortBase { + /// + public override InterruptMode InterruptMode { get; set; } + /// public override ResistorMode Resistor { @@ -57,7 +60,7 @@ public override TimeSpan GlitchDuration /// The interrupt mode used for the interrupt pin /// The resistor mode used by the interrupt pin public DigitalInterruptPort(IPin pin, InterruptMode interruptMode = InterruptMode.None, ResistorMode resistorMode = ResistorMode.Disabled) - : base(pin, (IDigitalChannelInfo)pin.SupportedChannels![0], interruptMode) + : base(pin, (IDigitalChannelInfo)pin.SupportedChannels![0]) { this.resistorMode = resistorMode; } diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Hid.Keyboard/Driver/Keyboard.KeyboardKey.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Hid.Keyboard/Driver/Keyboard.KeyboardKey.cs index 43afbe3828..cefa83a277 100644 --- a/Source/Meadow.Foundation.Peripherals/Sensors.Hid.Keyboard/Driver/Keyboard.KeyboardKey.cs +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Hid.Keyboard/Driver/Keyboard.KeyboardKey.cs @@ -29,9 +29,13 @@ public class KeyboardKey : DigitalInterruptPortBase /// public override TimeSpan GlitchDuration { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } + /// + public override InterruptMode InterruptMode { get; set; } + internal KeyboardKey(KeyboardKeyPin pin, IDigitalChannelInfo info, InterruptMode interruptMode) - : base(pin, info, interruptMode) + : base(pin, info) { + InterruptMode = interruptMode; } internal void SetState(bool newState) diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Volume.ResistiveTankLevelSender/Driver/ResistiveTankLevelSender.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Volume.ResistiveTankLevelSender/Driver/ResistiveTankLevelSender.cs index d6b91ac6a4..f72b377a6a 100644 --- a/Source/Meadow.Foundation.Peripherals/Sensors.Volume.ResistiveTankLevelSender/Driver/ResistiveTankLevelSender.cs +++ b/Source/Meadow.Foundation.Peripherals/Sensors.Volume.ResistiveTankLevelSender/Driver/ResistiveTankLevelSender.cs @@ -141,8 +141,12 @@ private int GetFillLevelForResistance(double resistance) private void OnInputUpdated(object? sender, IChangeResult e) { - var sensedVoltage = e.New; - var gaugeResistance = ((VRef.Volts * Resistor2.Ohms) / sensedVoltage.Volts) - Resistor1.Ohms - Resistor2.Ohms; + SetFillLevelForVoltage(e.New); + } + + private void SetFillLevelForVoltage(Voltage voltage) + { + var gaugeResistance = ((VRef.Volts * Resistor2.Ohms) / voltage.Volts) - Resistor1.Ohms - Resistor2.Ohms; FillLevelPercent = GetFillLevelForResistance(gaugeResistance); } @@ -153,8 +157,12 @@ protected override Task ReadSensor() } /// - public override void StartUpdating(TimeSpan? updateInterval = null) + public override async void StartUpdating(TimeSpan? updateInterval = null) { + // run an initial read (otherwise we wait for the ADC update loop to finish) + var adc = await AnalogInput.Read(); + SetFillLevelForVoltage(adc); + AnalogInput.StartUpdating(updateInterval); } diff --git a/Source/Meadow.Foundation.sln b/Source/Meadow.Foundation.sln index f9b83c6b4b..33aac31cc0 100644 --- a/Source/Meadow.Foundation.sln +++ b/Source/Meadow.Foundation.sln @@ -1563,6 +1563,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Meadow.Desktop", "..\..\Mea EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Meadow.Mac", "..\..\Meadow.Core\Source\implementations\mac\Meadow.Mac\Meadow.Mac.csproj", "{FD7E56EE-6BBE-44BB-BB8B-8DB0647CDD0D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICs.IOExpanders.PCanBasic", "Meadow.Foundation.Peripherals\ICs.IOExpanders.PCanBasic\Driver\ICs.IOExpanders.PCanBasic.csproj", "{C7C2B091-E66B-4A83-84E4-9E6024981D41}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICs.CAN.Mcp2515", "Meadow.Foundation.Peripherals\ICs.CAN.Mcp2515\Driver\ICs.CAN.Mcp2515.csproj", "{18785D65-35BB-4DCD-89EF-0D1DFA3F1463}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CAN", "CAN", "{5C9C0932-CA09-4599-8546-8373F79B37B7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mcp2515", "Mcp2515", "{066DBCFD-A21D-4FD2-87A5-B88363158149}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mcp2515_Sample", "Meadow.Foundation.Peripherals\ICs.CAN.Mcp2515\Samples\Mcp2515_Sample\Mcp2515_Sample.csproj", "{489028C0-5B3C-46E1-800E-A0359E815CF9}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Volume", "Volume", "{8A0FAC4E-473E-4CEC-9325-71E294343D34}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sensors.Volume.ResistiveTankLevelSender", "Meadow.Foundation.Peripherals\Sensors.Volume.ResistiveTankLevelSender\Driver\Sensors.Volume.ResistiveTankLevelSender.csproj", "{74834576-1C4C-41F0-8F0A-C2A34021665B}" @@ -1585,6 +1595,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sensors.Color.Tcs3472x", "M EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tcs3472x_Sample", "Meadow.Foundation.Peripherals\Sensors.Color.Tcs3472x\Samples\Tcs3472x_Sample\Tcs3472x_Sample.csproj", "{08BECE38-EBCB-4A38-9F9B-0914F3C7592E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PCan", "PCan", "{48CA0124-A227-4F35-BEFB-FF07F1CE6D20}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{3D40446F-F902-4C89-93B9-2BE094BDE136}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PCanBasic_Sample", "Meadow.Foundation.Peripherals\ICs.IOExpanders.PCanBasic\Samples\PCanBasic_Sample\PCanBasic_Sample.csproj", "{506044BB-BEC1-42D1-AF84-CD28D4E2911A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -3807,6 +3823,22 @@ Global {FD7E56EE-6BBE-44BB-BB8B-8DB0647CDD0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {FD7E56EE-6BBE-44BB-BB8B-8DB0647CDD0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {FD7E56EE-6BBE-44BB-BB8B-8DB0647CDD0D}.Release|Any CPU.Build.0 = Release|Any CPU + {C7C2B091-E66B-4A83-84E4-9E6024981D41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7C2B091-E66B-4A83-84E4-9E6024981D41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7C2B091-E66B-4A83-84E4-9E6024981D41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7C2B091-E66B-4A83-84E4-9E6024981D41}.Release|Any CPU.Build.0 = Release|Any CPU + {18785D65-35BB-4DCD-89EF-0D1DFA3F1463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18785D65-35BB-4DCD-89EF-0D1DFA3F1463}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18785D65-35BB-4DCD-89EF-0D1DFA3F1463}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {18785D65-35BB-4DCD-89EF-0D1DFA3F1463}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18785D65-35BB-4DCD-89EF-0D1DFA3F1463}.Release|Any CPU.Build.0 = Release|Any CPU + {18785D65-35BB-4DCD-89EF-0D1DFA3F1463}.Release|Any CPU.Deploy.0 = Release|Any CPU + {489028C0-5B3C-46E1-800E-A0359E815CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {489028C0-5B3C-46E1-800E-A0359E815CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {489028C0-5B3C-46E1-800E-A0359E815CF9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {489028C0-5B3C-46E1-800E-A0359E815CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {489028C0-5B3C-46E1-800E-A0359E815CF9}.Release|Any CPU.Build.0 = Release|Any CPU + {489028C0-5B3C-46E1-800E-A0359E815CF9}.Release|Any CPU.Deploy.0 = Release|Any CPU {74834576-1C4C-41F0-8F0A-C2A34021665B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {74834576-1C4C-41F0-8F0A-C2A34021665B}.Debug|Any CPU.Build.0 = Debug|Any CPU {74834576-1C4C-41F0-8F0A-C2A34021665B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU @@ -3837,6 +3869,10 @@ Global {08BECE38-EBCB-4A38-9F9B-0914F3C7592E}.Release|Any CPU.ActiveCfg = Release|Any CPU {08BECE38-EBCB-4A38-9F9B-0914F3C7592E}.Release|Any CPU.Build.0 = Release|Any CPU {08BECE38-EBCB-4A38-9F9B-0914F3C7592E}.Release|Any CPU.Deploy.0 = Release|Any CPU + {506044BB-BEC1-42D1-AF84-CD28D4E2911A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {506044BB-BEC1-42D1-AF84-CD28D4E2911A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {506044BB-BEC1-42D1-AF84-CD28D4E2911A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {506044BB-BEC1-42D1-AF84-CD28D4E2911A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -4615,6 +4651,11 @@ Global {2BB9FA79-851A-48EE-A2E1-91AE3F2B61EF} = {E0384B86-37FC-403C-B1F7-AA5D1B869EB1} {84CEDB6D-C9B1-4317-8A4E-84785ACCA6D1} = {2BB9FA79-851A-48EE-A2E1-91AE3F2B61EF} {F531D6BC-D3F2-4AFF-A84B-59351E2FA462} = {2BB9FA79-851A-48EE-A2E1-91AE3F2B61EF} + {C7C2B091-E66B-4A83-84E4-9E6024981D41} = {48CA0124-A227-4F35-BEFB-FF07F1CE6D20} + {18785D65-35BB-4DCD-89EF-0D1DFA3F1463} = {066DBCFD-A21D-4FD2-87A5-B88363158149} + {5C9C0932-CA09-4599-8546-8373F79B37B7} = {A1917BD0-881F-4775-88D9-38D42D448CF5} + {066DBCFD-A21D-4FD2-87A5-B88363158149} = {5C9C0932-CA09-4599-8546-8373F79B37B7} + {489028C0-5B3C-46E1-800E-A0359E815CF9} = {066DBCFD-A21D-4FD2-87A5-B88363158149} {8A0FAC4E-473E-4CEC-9325-71E294343D34} = {9F4EEBFB-F2B6-4B28-ABAD-D219F4AB15F3} {74834576-1C4C-41F0-8F0A-C2A34021665B} = {7C30F342-6DD6-430C-B872-4515D01F2D4E} {7C30F342-6DD6-430C-B872-4515D01F2D4E} = {8A0FAC4E-473E-4CEC-9325-71E294343D34} @@ -4626,6 +4667,9 @@ Global {1192A825-2AC9-497E-BA96-289F33A72EFB} = {9F4EEBFB-F2B6-4B28-ABAD-D219F4AB15F3} {57F6D3C1-07D5-49AC-9087-7DB505B522D1} = {C44E659B-2A58-4F22-B812-AA2994ABF33C} {08BECE38-EBCB-4A38-9F9B-0914F3C7592E} = {C883EE04-FAC0-4734-9983-5BBF8E817ECF} + {48CA0124-A227-4F35-BEFB-FF07F1CE6D20} = {5C9C0932-CA09-4599-8546-8373F79B37B7} + {3D40446F-F902-4C89-93B9-2BE094BDE136} = {48CA0124-A227-4F35-BEFB-FF07F1CE6D20} + {506044BB-BEC1-42D1-AF84-CD28D4E2911A} = {3D40446F-F902-4C89-93B9-2BE094BDE136} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AF7CA16F-8C38-4546-87A2-5DAAF58A1520}