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}