diff --git a/Source/Meadow.Foundation.Peripherals/Batteries.Voltaic.V10x/Datasheet/Voltaic Charge Controller RS-485 Documentation.pdf b/Source/Meadow.Foundation.Peripherals/Batteries.Voltaic.V10x/Datasheet/Voltaic Charge Controller RS-485 Documentation.pdf
new file mode 100644
index 000000000..a7f491552
Binary files /dev/null and b/Source/Meadow.Foundation.Peripherals/Batteries.Voltaic.V10x/Datasheet/Voltaic Charge Controller RS-485 Documentation.pdf differ
diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Sc16is7x2/Driver/Sc16is7x2.Sc16is7x2Channel.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Sc16is7x2/Driver/Sc16is7x2.Sc16is7x2Channel.cs
index 68cff5bb2..8190c68c4 100644
--- a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Sc16is7x2/Driver/Sc16is7x2.Sc16is7x2Channel.cs
+++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Sc16is7x2/Driver/Sc16is7x2.Sc16is7x2Channel.cs
@@ -24,8 +24,8 @@ public class Sc16is7x2Channel : ISerialPort
///
public int BytesToRead => (_irqReadBuffer == null)
- ? GetReadHwFifoCount()
- : GetReadHwFifoCount() + _irqReadBuffer.Count;
+ ? _controller.GetReadFifoCount(_channel)
+ : _controller.GetReadFifoCount(_channel) + _irqReadBuffer.Count;
///
public bool IsOpen { get; private set; }
@@ -49,16 +49,7 @@ public class Sc16is7x2Channel : ISerialPort
private readonly Channels _channel;
private readonly IDigitalInterruptPort? _irq;
- private readonly CircularBuffer _irqReadBuffer;
-
- public bool IsIrqDriven => _irq != null;
-
- // Optimizations for fast read. Precalculated values for this channel.
- private byte _rhrAddress;
- private byte _lsrAddress;
- private byte _rxlvlAddress;
- private byte _txlvlAddress;
- private IByteCommunications _comms;
+ private readonly CircularBuffer? _irqReadBuffer;
///
/// This method is never called directly from user code.
@@ -80,17 +71,10 @@ internal Sc16is7x2Channel(Sc16is7x2 controller, string portName, Channels channe
_controller = controller;
_channel = channel;
- _rhrAddress = CalculateChannelAddress((byte)Registers.RHR);
- _lsrAddress = CalculateChannelAddress((byte)Registers.LSR);
- _rxlvlAddress = CalculateChannelAddress((byte)Registers.RXLVL);
- _txlvlAddress = CalculateChannelAddress((byte)Registers.TXLVL);
- _comms = controller.Comms;
-
- InitChannel(baudRate, dataBits, parity, stopBits);
-
+ Initialize(baudRate, dataBits, parity, stopBits);
if (isRS485)
{
- EnableRS485(invertDE);
+ InitializeRS485(invertDE);
}
if (irq != null)
@@ -98,9 +82,8 @@ internal Sc16is7x2Channel(Sc16is7x2 controller, string portName, Channels channe
// Setting up IRQ read with a large software FIFO buffer to offload the hardware FIFO of only 64 bytes.
_irq = irq;
_irqReadBuffer = new CircularBuffer(readBufferSize);
- // 21.04.2024, KW: We'll try to move this to Open(). And we'll remove this handler in Close().
- //EnableReceiveInterrupts();
- //_irq.Changed += UartChannelInterruptHandler;
+ _controller.EnableReceiveInterrupts(_channel);
+ _irq.Changed += OnInterruptLineChanged;
}
// https://github.com/WildernessLabs/Meadow_Issues/issues/74
@@ -115,19 +98,17 @@ internal Sc16is7x2Channel(Sc16is7x2 controller, string portName, Channels channe
// a.RaiseInterrupt(this, new DigitalPortResult());
}
- internal void UartChannelInterruptHandler(object sender, DigitalPortResult e)
+ internal void OnInterruptLineChanged(object sender, DigitalPortResult e)
{
// If the first message after reboot is longer than the FIFO buffer, we already have a buffer overrun at this point.
// For any consecutive messages, it works fine.
// Ref: https://github.com/WildernessLabs/Meadow_Issues/issues/74
- int count = GetReadHwFifoCount();
- //Resolver.Log.Info($"->UART interrupt. Channel={_channel} port HW FIFO: {count} bytes");
-
- if (count == 0) return; // The IRQ was not for us.
+ //int count = _controller.GetReadFifoCount(_channel);
+ //Resolver.Log.Info($"Channel {_channel} port FIFO: {count} bytes");
if (_irqReadBuffer == null)
{
- if (ReceiveInterruptPending())
+ if (_controller.ReceiveInterruptPending(_channel))
this.DataReceived?.Invoke(this, new SerialDataReceivedEventArgs(SerialDataType.Chars));
}
else
@@ -143,7 +124,7 @@ internal void UartChannelInterruptHandler(object sender, DigitalPortResult e)
public int BaudRate
{
get => _baudRate;
- set => _baudRate = SetBaudRate(value);
+ set => _baudRate = _controller.SetBaudRate(_channel, value);
}
///
@@ -152,7 +133,7 @@ public int DataBits
get => _dataBits;
set
{
- SetLineSettings(value, Parity, StopBits);
+ _controller.SetLineSettings(_channel, value, Parity, StopBits);
_dataBits = value;
}
}
@@ -163,7 +144,7 @@ public Parity Parity
get => _parity;
set
{
- SetLineSettings(DataBits, value, StopBits);
+ _controller.SetLineSettings(_channel, DataBits, value, StopBits);
_parity = value;
}
}
@@ -174,17 +155,24 @@ public StopBits StopBits
get => _stopBits;
set
{
- SetLineSettings(DataBits, Parity, value);
+ _controller.SetLineSettings(_channel, DataBits, Parity, value);
_stopBits = value;
}
}
- private void InitChannel(int baudRate, int dataBits, Parity parity, StopBits stopBits)
+ private void Initialize(int baudRate, int dataBits, Parity parity, StopBits stopBits)
{
- //_controller.Reset();
- EnableFifo();
- _baudRate = SetBaudRate(baudRate);
- SetLineSettings(dataBits, parity, stopBits);
+ _dataBits = dataBits;
+ _stopBits = stopBits;
+ _controller.Reset();
+ _controller.EnableFifo(_channel);
+ _baudRate = _controller.SetBaudRate(_channel, baudRate);
+ _controller.SetLineSettings(_channel, dataBits, parity, stopBits);
+ }
+
+ private void InitializeRS485(bool invertDE)
+ {
+ _controller.EnableRS485(_channel, invertDE);
}
///
@@ -193,42 +181,26 @@ private void InitChannel(int baudRate, int dataBits, Parity parity, StopBits sto
internal void ReadAllIrqFifo()
{
if (_irqReadBuffer == null) return;
-
int totalRead = 0;
- int count = GetReadHwFifoCount(); // How may bytes to read.
- //int count = _comms.ReadRegister(_rxlvlAddress); // How may bytes to read.
+ int count = _controller.GetReadFifoCount(_channel); // How may bytes to read.
while (count > 0)
{
for (int i = 0; i < count; i++)
- {
- byte b = _comms.ReadRegister(_rhrAddress);
- try
- {
- _irqReadBuffer.Append(b);
- }
- catch (Exception ex)
- {
- Resolver.Log.Error($"!!!!!---> ReadAllIrqFifo: Channel={_channel} Exception: {ex.Message}");
- BufferOverrun?.Invoke(this, new ThreadExceptionEventArgs(ex));
- }
- }
+ _irqReadBuffer.Append(_controller.ReadByte(_channel));
totalRead += count;
- //Resolver.Log.Info($"---> ReadAllIrqFifo: Channel={_channel} Read {count}/{totalRead}");
+ count = _controller.GetReadFifoCount(_channel); // Check that we're all done. To make sure IRQ is reset.
- //byte lsr = _controller.ReadChannelRegister(Registers.LSR, _channel);
- byte lsr = _comms.ReadRegister(_lsrAddress);
+ byte lsr = _controller.ReadChannelRegister(Registers.LSR, _channel);
if ((lsr & RegisterBits.LSR_OVERRUN_ERROR) > 0)
{
- Resolver.Log.Error("[OVERRUN]"); // Not sure to keep this, but nice when debugging.
+ Resolver.Log.Warn("[BUFFER OVERRUN]", this.GetType().Name);
+ //_irqReadBuffer.WriteString("[BUFFER OVERRUN]"); // Not sure to keep this, but nice when debugging.
BufferOverrun?.Invoke(this, EventArgs.Empty);
}
-
- count = GetReadHwFifoCount(); // Check that we're all done. To make sure IRQ is reset.
- //count = _comms.ReadRegister(_rxlvlAddress); // Check that we're all done. To make sure IRQ is reset.
}
if (totalRead > 0)
{
- //Resolver.Log.Info($"---> ReadAllIrqFifo: Channel={_channel} Done {count}/{totalRead}");
+ Resolver.Log.Info($"---> ReadAllIrqFifo: Channel {_channel} port read {totalRead} bytes");
}
}
@@ -239,17 +211,17 @@ public int ReadByte()
{
// The normal way...
// check if data is available
- if (!IsHwFifoDataAvailable())
+ if (!_controller.IsFifoDataAvailable(_channel))
{
return -1;
}
// read the data
- return ReadHwFifoByte();
+ return _controller.ReadByte(_channel);
}
else
{
- // IRQ fast read from software FIFO....
+ // IRQ fast read....
ReadAllIrqFifo();
if (_irqReadBuffer.Count == 0)
return -1;
@@ -270,13 +242,13 @@ public int Read(byte[] buffer, int offset, int count)
if (_irqReadBuffer == null)
{
// The normal way...
- var available = GetReadHwFifoCount();
+ var available = _controller.GetReadFifoCount(_channel);
// read either the available or count, whichever is less, unless available is 0, in which case we wait until timeout
while (available == 0)
{
Thread.Sleep(10);
- available = GetReadHwFifoCount();
+ available = _controller.GetReadFifoCount(_channel);
if (timeout > 0)
{
@@ -291,7 +263,7 @@ public int Read(byte[] buffer, int offset, int count)
for (var i = 0; i < toRead; i++)
{
- buffer[i + offset] = ReadHwFifoByte();
+ buffer[i + offset] = _controller.ReadByte(_channel);
}
return toRead;
@@ -335,7 +307,7 @@ public byte[] ReadAll()
if (_irqReadBuffer == null)
{
// The normal way...
- var available = GetReadHwFifoCount();
+ var available = _controller.GetReadFifoCount(_channel);
var buffer = new byte[available];
Read(buffer, 0, available);
return buffer;
@@ -346,11 +318,10 @@ public byte[] ReadAll()
ReadAllIrqFifo(); // Always read whatever is in the FIFO.
var available = _irqReadBuffer.Count;
var buffer = new byte[available];
- //for (int i = 0; i < available; i++)
- //{
- // buffer[i] = _irqReadBuffer.Read();
- //}
- _irqReadBuffer.MoveItemsTo(buffer, 0, available);
+ for (int i = 0; i < available; i++)
+ {
+ buffer[i] = _irqReadBuffer.Remove();
+ }
return buffer;
}
}
@@ -358,41 +329,29 @@ public byte[] ReadAll()
///
public void ClearReceiveBuffer()
{
- ResetReadHwFifo();
- if (IsIrqDriven)
+ _controller.ResetReadFifo(_channel);
+ if (_irqReadBuffer != null)
_irqReadBuffer.Clear();
}
///
- public void Dispose()
+ public void Close()
{
- // nop
+ IsOpen = false;
+ if (_irqReadBuffer != null)
+ _irqReadBuffer.Clear();
}
///
- public void Open()
+ public void Dispose()
{
- if (!IsOpen)
- {
- IsOpen = true;
- ResetWriteHwFifo();
- ClearReceiveBuffer();
- if (IsIrqDriven)
- _irq.Changed += UartChannelInterruptHandler;
- EnableReceiveInterrupts();
- }
+ // nop
}
///
- public void Close()
+ public void Open()
{
- if (IsOpen)
- {
- IsOpen = false;
- DisableReceiveInterrupts();
- if (IsIrqDriven)
- _irq.Changed -= UartChannelInterruptHandler;
- }
+ IsOpen = true;
}
///
@@ -420,13 +379,13 @@ public int Write(byte[] buffer, int offset, int count)
timeout = Environment.TickCount + (int)ReadTimeout.TotalMilliseconds;
}
// wait for THR to be empty
- while (!IsTransmitHoldingRegisterEmpty())
+ while (!_controller.IsTransmitHoldingRegisterEmpty(_channel))
{
Thread.Sleep(10);
}
// write until we're either written all or the THR is full
- var available = GetWriteHwFifoSpace();
+ var available = _controller.GetWriteFifoSpace(_channel);
while (remaining > 0)
{
@@ -434,7 +393,7 @@ public int Write(byte[] buffer, int offset, int count)
while (available <= 0)
{
Thread.Sleep(10);
- available = GetWriteHwFifoSpace();
+ available = _controller.GetWriteFifoSpace(_channel);
if (timeout > 0)
{
@@ -445,266 +404,18 @@ public int Write(byte[] buffer, int offset, int count)
}
}
- WriteHwFifoByte(buffer[index]);
+ _controller.WriteByte(_channel, buffer[index]);
index++;
available--;
remaining--;
if (available == 0)
{
- available = GetWriteHwFifoSpace();
+ available = _controller.GetWriteFifoSpace(_channel);
}
}
return count;
}
-
-
- // ******************* Hardware communication methods *******************
- // Moved here from Sc16is7x2.cs so we can ommit the channel parameter
- // and unnecessary calls to parent class. (We keep calls local)
-
- internal void EnableFifo()
- {
- var fcr = ReadChannelRegister(Registers.FCR);
- fcr |= RegisterBits.FCR_FIFO_ENABLE;
- WriteChannelRegister(Registers.FCR, fcr);
- }
-
- internal void EnableReceiveInterrupts()
- {
- //var ier = ReadChannelRegister(Registers.IER);
- //ier |= RegisterBits.IER_RHR_ENABLE;
- //WriteChannelRegister(Registers.IER, ier);
- SetChannelRegisterBits(Registers.IER, RegisterBits.IER_RHR_ENABLE);
- }
-
- internal void DisableReceiveInterrupts()
- {
- //var ier = ReadChannelRegister(Registers.IER);
- //ier &= (byte)~(RegisterBits.IER_RHR_ENABLE);
- //WriteChannelRegister(Registers.IER, ier);
- ClearChannelRegisterBits(Registers.IER, RegisterBits.IER_RHR_ENABLE);
- }
-
- internal bool ReceiveInterruptPending()
- {
- // IIR[0] is 0 for any pending interrupt
- // RHR will be IIR[2] *exclusively*
- var iir = ReadChannelRegister(Registers.IIR);
- return (iir & RegisterBits.IIR_RHR_INTERRUPT) == RegisterBits.IIR_RHR_INTERRUPT;
- }
-
- internal void SetLineSettings(int dataBits, Parity parity, StopBits stopBits)
- {
- var lcr = ReadChannelRegister(Registers.LCR);
- lcr &= unchecked((byte)~0x3f); // clear all of the line setting bits for simplicity
-
- switch (dataBits)
- {
- case 5:
- lcr |= RegisterBits.LCR_5_DATA_BITS;
- break;
- case 6:
- lcr |= RegisterBits.LCR_6_DATA_BITS;
- break;
- case 7:
- lcr |= RegisterBits.LCR_7_DATA_BITS;
- break;
- case 8:
- lcr |= RegisterBits.LCR_8_DATA_BITS;
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(dataBits));
-
- }
-
- if (stopBits == StopBits.Two)
- {
- lcr |= RegisterBits.LCR_2_STOP_BITS;
- }
-
- switch (parity)
- {
- case Parity.None:
- lcr |= RegisterBits.LCR_PARITY_NONE;
- break;
- case Parity.Odd:
- lcr |= RegisterBits.LCR_PARITY_ODD;
- break;
- case Parity.Even:
- lcr |= RegisterBits.LCR_PARITY_EVEN;
- break;
- // device supports mark and space, but Meadow doesn't have values for it
- }
-
- WriteChannelRegister(Registers.LCR, lcr);
- }
-
- internal int SetBaudRate(int baudRate)
- {
- // the part baud rate is a division of the oscillator frequency, not necessarily the value requested
- var mcr = ReadChannelRegister(Registers.MCR);
- var prescaler = ((mcr & RegisterBits.MCR_CLOCK_DIVISOR) == 0) ? 1 : 4;
- var divisor1 = _controller.OscillatorFrequency.Hertz / prescaler;
- var divisor2 = baudRate * 16;
-
- if (divisor2 > divisor1) throw new ArgumentOutOfRangeException(nameof(baudRate), "Oscillator does not allow requested baud rate");
-
- var divisor = (ushort)Math.Ceiling(divisor1 / divisor2);
-
- // enable the divisor latch
- var lcr = ReadChannelRegister(Registers.LCR);
- lcr |= RegisterBits.LCR_DIVISOR_LATCH_ENABLE;
- WriteChannelRegister(Registers.LCR, lcr);
-
- // set the baud rate
- WriteChannelRegister(Registers.DLL, (byte)(divisor & 0xff));
- WriteChannelRegister(Registers.DLH, (byte)(divisor >> 8));
-
- // disable the divisor latch
- lcr &= unchecked((byte)~RegisterBits.LCR_DIVISOR_LATCH_ENABLE);
- WriteChannelRegister(Registers.LCR, lcr);
-
- // return the actual baud rate achieved
- return (int)(divisor1 / divisor / 16);
- }
-
- internal void EnableRS485(bool invertDE)
- {
- var efcr = ReadChannelRegister(Registers.EFCR);
- efcr |= RegisterBits.EFCR_9BITMODE | RegisterBits.EFCR_RTSCON;
-
- if (invertDE)
- {
- efcr |= RegisterBits.EFCR_RTSINVER;
- }
- else
- {
- efcr &= unchecked((byte)~RegisterBits.EFCR_RTSINVER);
- }
-
- WriteChannelRegister(Registers.EFCR, efcr);
- }
-
-
- // ******************* UART read methods *******************
-
- ///
- /// Returns the number of bytes in the receive FIFO.
- ///
- ///
- internal int GetReadHwFifoCount()
- {
- return _comms.ReadRegister(_rxlvlAddress);
- }
-
- ///
- /// Returns true if there is data available in the receive FIFO.
- ///
- ///
- internal bool IsHwFifoDataAvailable()
- {
- return GetReadHwFifoCount() > 0;
- }
-
- internal void ResetReadHwFifo()
- {
- var fcr = ReadChannelRegister(Registers.FCR);
- fcr |= RegisterBits.FCR_RX_FIFO_RESET;
- WriteChannelRegister(Registers.FCR, fcr);
- }
-
- internal byte ReadHwFifoByte()
- {
- return ReadChannelRegister(Registers.RHR);
- }
-
-
- // ******************* UART write methods *******************
-
- ///
- /// Returns the empty space in the transmit FIFO.
- ///
- ///
- internal int GetWriteHwFifoSpace()
- {
- return _comms.ReadRegister(_txlvlAddress);
- }
-
- ///
- /// Reading status from the THR bit in the LSR register.
- ///
- ///
- internal bool IsTransmitHoldingRegisterEmpty()
- {
- var lsr = ReadChannelRegister(Registers.LSR);
- return (lsr & RegisterBits.LSR_THR_EMPTY) == RegisterBits.LSR_THR_EMPTY;
- }
-
- internal void ResetWriteHwFifo()
- {
- var fcr = ReadChannelRegister(Registers.FCR);
- fcr |= RegisterBits.FCR_TX_FIFO_RESET;
- WriteChannelRegister(Registers.FCR, fcr);
- }
-
- internal void WriteHwFifoByte(byte data)
- {
- WriteChannelRegister(Registers.THR, data);
- }
-
-
- // ******************* Channel communication methods *******************
-
- private byte CalculateChannelAddress(byte register)
- {
- // see page 40 of the data sheet for explanation of this
- var subaddress = (byte)((register << 3) | ((byte)_channel << 1));
- return subaddress;
- }
-
- private byte ReadChannelRegister(Registers register)
- {
- // see page 40 of the data sheet for explanation of this
- var subaddress = (byte)(((byte)register << 3) | ((byte)_channel << 1));
- byte v = _comms.ReadRegister(subaddress);
- return v;
- }
-
- private void WriteChannelRegister(Registers register, byte value)
- {
- // see page 40 of the data sheet for explanation of this
- var subaddress = (byte)(((byte)register << 3) | ((byte)_channel << 1));
- _comms.WriteRegister(subaddress, value);
-
- //int b = GetWriteHwFifoSpace();
- //int c = GetReadHwFifoCount();
- //Resolver.Log.Info($"->UART write. Channel={_channel} port HW FIFO: {b} bytes Read FIFO: {c}");
- }
-
- ///
- /// Sets bits in the register if the corresponding bit in the value parameter is set.
- ///
- ///
- ///
- private void SetChannelRegisterBits(Registers register, byte value)
- {
- byte currentValue = ReadChannelRegister(register);
- currentValue |= value; // Set the bits we're going to change
- WriteChannelRegister(register, currentValue);
- }
-
- ///
- /// Clears bits in the register if the corresponding bit in the mask parameter is set.
- ///
- ///
- ///
- private void ClearChannelRegisterBits(Registers register, byte mask)
- {
- byte currentValue = ReadChannelRegister(register);
- currentValue &= (byte)~mask; // Flip all bits in mask, then AND with currentValue
- WriteChannelRegister(register, currentValue);
- }
}
}
diff --git a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Sc16is7x2/Driver/Sc16is7x2.cs b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Sc16is7x2/Driver/Sc16is7x2.cs
index 47abf097d..593ab0e18 100644
--- a/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Sc16is7x2/Driver/Sc16is7x2.cs
+++ b/Source/Meadow.Foundation.Peripherals/ICs.IOExpanders.Sc16is7x2/Driver/Sc16is7x2.cs
@@ -22,7 +22,7 @@ public abstract partial class Sc16is7x2 : ISerialController, IDigitalInputOutput
private Sc16is7x2Channel? _channelA;
private Sc16is7x2Channel? _channelB;
- public Frequency OscillatorFrequency { get; private set; }
+ private Frequency _oscillatorFrequency;
private IDigitalInterruptPort? _irq;
private bool _latchGpioInterrupt;
@@ -33,17 +33,16 @@ public abstract partial class Sc16is7x2 : ISerialController, IDigitalInputOutput
internal Sc16is7x2(Frequency oscillatorFrequency, IDigitalInterruptPort? irq, bool latchGpioInterrupt = false)
{
- OscillatorFrequency = oscillatorFrequency;
+ _oscillatorFrequency = oscillatorFrequency;
_irq = irq;
_latchGpioInterrupt = latchGpioInterrupt;
Pins = new PinDefinitions(this);
- // This has to move to GPIO init. We don't know yet if comms init will succeed.
- //if (irq != null)
- //{
- // irq.Changed += GpioInterruptHandler;
- //}
+ if (irq != null)
+ {
+ irq.Changed += GpioInterruptHandler;
+ }
}
private IByteCommunications Comms
@@ -67,10 +66,10 @@ private IByteCommunications Comms
/// The software FIFO read buffer size. (Not the 64 bytes on chip FIFO)
public ISerialPort CreateSerialPort(SerialPortName portName, int baudRate = 9600, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One, int readBufferSize = 1024)
{
- if (_irq != null && _irq.InterruptMode != InterruptMode.EdgeFalling)
- {
- Resolver.Log.Warn($"Warning: You have specified InterruptMode={_irq.InterruptMode}. SC16IS7x2 have a falling edge IRQ signal.");
- }
+ //if (_irq != null && _irq.InterruptMode != InterruptMode.EdgeRising)
+ //{
+ // throw new ArgumentException("If an interrupt port is provided, it must be a rising edge interrupt");
+ //}
switch (portName.SystemName)
{
@@ -130,101 +129,305 @@ public ISerialPort CreateRs485SerialPort(Sc16SerialPortName portName, int baudRa
throw new Exception("Unknown port");
}
+ internal bool ReceiveInterruptPending(Channels channel)
+ {
+ // IIR[0] is 0 for any pending interrupt
+ // RHR will be IIR[2] *exclusively*
+ var iir = ReadChannelRegister(Registers.IIR, channel);
+ return (iir & RegisterBits.IIR_RHR_INTERRUPT) == RegisterBits.IIR_RHR_INTERRUPT;
+ }
+
+ internal void EnableReceiveInterrupts(Channels channel)
+ {
+ var ier = ReadChannelRegister(Registers.IER, channel);
+ ier |= RegisterBits.IER_RHR_ENABLE;
+ WriteChannelRegister(Registers.IER, channel, ier);
+ }
+
+ internal void EnableRS485(Channels channel, bool invertDE)
+ {
+ var efcr = ReadChannelRegister(Registers.EFCR, channel);
+ efcr |= RegisterBits.EFCR_9BITMODE | RegisterBits.EFCR_RTSCON;
+
+ if (invertDE)
+ {
+ efcr |= RegisterBits.EFCR_RTSINVER;
+ }
+ else
+ {
+ efcr &= unchecked((byte)~RegisterBits.EFCR_RTSINVER);
+ }
+
+ WriteChannelRegister(Registers.EFCR, channel, efcr);
+ }
+
+ internal void WriteByte(Channels channel, byte data)
+ {
+ WriteChannelRegister(Registers.THR, channel, data);
+ }
+
///
- /// Reset the device
+ /// Reads the empty space in the transmit fifo
///
+ ///
+ ///
+ internal int GetWriteFifoSpace(Channels channel)
+ {
+ return ReadChannelRegister(Registers.TXLVL, channel);
+ }
+
+ internal int GetReadFifoCount(Channels channel)
+ {
+ return ReadChannelRegister(Registers.RXLVL, channel);
+ }
+
+ internal void ResetReadFifo(Channels channel)
+ {
+ var fcr = ReadChannelRegister(Registers.FCR, channel);
+ fcr |= RegisterBits.FCR_RX_FIFO_RESET;
+ WriteChannelRegister(Registers.FCR, channel, fcr);
+ }
+
+ internal bool IsTransmitHoldingRegisterEmpty(Channels channel)
+ {
+ var thr = ReadChannelRegister(Registers.LSR, channel);
+ return (thr & RegisterBits.LSR_THR_EMPTY) == RegisterBits.LSR_THR_EMPTY;
+ }
+
+ internal bool IsFifoDataAvailable(Channels channel)
+ {
+ return GetReadFifoCount(channel) > 0;
+ }
+
+ internal byte ReadByte(Channels channel)
+ {
+ return ReadChannelRegister(Registers.RHR, channel);
+ }
+
+ internal void SetLineSettings(Channels channel, int dataBits, Parity parity, StopBits stopBits)
+ {
+ var lcr = ReadChannelRegister(Registers.LCR, channel);
+ lcr &= unchecked((byte)~0x3f); // clear all of the line setting bits for simplicity
+
+ switch (dataBits)
+ {
+ case 5:
+ lcr |= RegisterBits.LCR_5_DATA_BITS;
+ break;
+ case 6:
+ lcr |= RegisterBits.LCR_6_DATA_BITS;
+ break;
+ case 7:
+ lcr |= RegisterBits.LCR_7_DATA_BITS;
+ break;
+ case 8:
+ lcr |= RegisterBits.LCR_8_DATA_BITS;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(dataBits));
+
+ }
+
+ if (stopBits == StopBits.Two)
+ {
+ lcr |= RegisterBits.LCR_2_STOP_BITS;
+ }
+
+ switch (parity)
+ {
+ case Parity.None:
+ lcr |= RegisterBits.LCR_PARITY_NONE;
+ break;
+ case Parity.Odd:
+ lcr |= RegisterBits.LCR_PARITY_ODD;
+ break;
+ case Parity.Even:
+ lcr |= RegisterBits.LCR_PARITY_EVEN;
+ break;
+ // device supports mark and space, but Meadow doesn't have values for it
+ }
+
+ WriteChannelRegister(Registers.LCR, channel, lcr);
+ }
+
+ internal int SetBaudRate(Channels channel, int baudRate)
+ {
+ // the part baud rate is a division of the oscillator frequency, not necessarily the value requested
+ var mcr = ReadChannelRegister(Registers.MCR, channel);
+ var prescaler = ((mcr & RegisterBits.MCR_CLOCK_DIVISOR) == 0) ? 1 : 4;
+ var divisor1 = _oscillatorFrequency.Hertz / prescaler;
+ var divisor2 = baudRate * 16;
+
+ if (divisor2 > divisor1) throw new ArgumentOutOfRangeException(nameof(baudRate), "Oscillator does not allow requested baud rate");
+
+ var divisor = (ushort)Math.Ceiling(divisor1 / divisor2);
+
+ // enable the divisor latch
+ var lcr = ReadChannelRegister(Registers.LCR, channel);
+ lcr |= RegisterBits.LCR_DIVISOR_LATCH_ENABLE;
+ WriteChannelRegister(Registers.LCR, channel, lcr);
+
+ // set the baud rate
+ WriteChannelRegister(Registers.DLL, channel, (byte)(divisor & 0xff));
+ WriteChannelRegister(Registers.DLH, channel, (byte)(divisor >> 8));
+
+ // disable the divisor latch
+ lcr &= unchecked((byte)~RegisterBits.LCR_DIVISOR_LATCH_ENABLE);
+ WriteChannelRegister(Registers.LCR, channel, lcr);
+
+ // return the actual baud rate achieved
+ return (int)(divisor1 / divisor / 16);
+ }
+
internal void Reset()
{
- var value = ReadRegister(Registers.IOControl);
+ var value = ReadChannelRegister(Registers.IOControl, Channels.Both);
value |= RegisterBits.IOCTL_RESET;
try
{
- WriteRegister(Registers.IOControl, value);
+ WriteChannelRegister(Registers.IOControl, Channels.Both, value);
}
- catch (Exception ex)
+ catch
{
// we expect to get a NACK on this. Very confusing
- // 06.10.2024, KW: Ignore this error. We get it even when the device works fine. ("Error code 70")
- Resolver.Log.Trace($"Ignoring SC16IS7x2 error on reset: {ex.Message}");
}
}
- /********************* GPIO **********************/
+ internal void EnableFifo(Channels channel)
+ {
+ var fcr = ReadChannelRegister(Registers.FCR, channel);
+ fcr |= RegisterBits.FCR_FIFO_ENABLE;
+ WriteChannelRegister(Registers.FCR, channel, fcr);
+ }
- private IDigitalPort[] gpioPorts = new IDigitalPort[8];
+ private byte ReadChannelRegister(Registers register, Channels channel)
+ {
+ // see page 40 of the data sheet for explanation of this
+ var subaddress = (byte)(((byte)register << 3) | ((byte)channel << 1));
+ byte v = Comms.ReadRegister(subaddress);
+ if (register == Registers.IOState)
+ {
+ //Resolver.Log.Info($"ReadChannelRegister: {register} {channel} {v:X2}");
+ if (v == 0)
+ {
+ byte v2 = Comms.ReadRegister(subaddress);
+ if (v2 != v)
+ {
+ v = v2; // Fix buggy value!
+ Resolver.Log.Info($"ReadChannelRegister: {register} {channel} {v2:X2} (Corrected value!)");
+ }
+ }
+ }
+ return v;
+ }
- ///
- /// Is the pin valid for this device instance
- ///
- /// The IPin to validate
- /// True if pin is valid
- protected bool IsValidPin(IPin pin) => Pins.AllPins.Contains(pin);
+ private void WriteChannelRegister(Registers register, Channels channel, byte value)
+ {
+ // see page 40 of the data sheet for explanation of this
+ var subaddress = (byte)(((byte)register << 3) | ((byte)channel << 1));
+ //if (register == Registers.IOState)
+ //{
+ // Resolver.Log.Info($"WriteChannelRegister: {register} {channel} {value:X2}");
+ //}
+ Comms.WriteRegister(subaddress, value);
+ }
- private bool initGpioDone = false;
+ private void SetChannelRegisterBits(Registers register, Channels channel, byte value)
+ {
+ byte currentValue = ReadChannelRegister(register, channel);
+ currentValue |= value; // Set the bits we're going to change
+ WriteChannelRegister(register, channel, currentValue);
+ }
+
+ private void ClearChannelRegisterBits(Registers register, Channels channel, byte mask)
+ {
+ byte currentValue = ReadChannelRegister(register, channel);
+ currentValue &= (byte)~mask; // Flip all bits in value, then AND with currentValue
+ WriteChannelRegister(register, channel, currentValue);
+ }
+
+
+ /********************* GPIO **********************/
+
+ private bool initDone = false;
private void InitGpio()
{
- if (initGpioDone) return;
+ if (initDone) return;
+ var a = ReadChannelRegister(Registers.IER, Channels.A);
+ a &= unchecked((byte)~RegisterBits.IER_SLEEP_MODE_ENABLE);
+ WriteChannelRegister(Registers.IER, Channels.A, a);
+ var b = ReadChannelRegister(Registers.IER, Channels.B);
+ b &= unchecked((byte)~RegisterBits.IER_SLEEP_MODE_ENABLE);
+ WriteChannelRegister(Registers.IER, Channels.B, b);
+
+ byte ioControlBefore = ReadGpioRegister(Registers.IOControl);
if (_latchGpioInterrupt)
- SetRegisterBit(Registers.IOControl, RegisterBits.IOCTL_IO_LATCH);
+ SetGpioRegisterBit(Registers.IOControl, RegisterBits.IOCTL_IO_LATCH);
else
- ClearRegisterBit(Registers.IOControl, RegisterBits.IOCTL_IO_LATCH);
- ClearRegisterBit(Registers.IOControl, RegisterBits.IOCTL_GPIO_7to4);
- ClearRegisterBit(Registers.IOControl, RegisterBits.IOCTL_GPIO_3to0);
+ ClearGpioRegisterBits(Registers.IOControl, RegisterBits.IOCTL_IO_LATCH);
+ ClearGpioRegisterBits(Registers.IOControl, RegisterBits.IOCTL_GPIO_7to4);
+ ClearGpioRegisterBits(Registers.IOControl, RegisterBits.IOCTL_GPIO_3to0);
+ byte ioControlAfter = ReadGpioRegister(Registers.IOControl);
+ //Resolver.Log.Info($"ioControl: {ioControlBefore} -> {ioControlAfter}");
// Set direction of all GPIO's to input
- WriteRegister(Registers.IODir, ioDir);
+ WriteGpioRegister(Registers.IODir, ioDir);
- // 22.04.2024, KW: This has been moved here from the constructor. Add the handler _after_ comms init is OK.
- if (_irq != null)
- {
- _irq.Changed += GpioInterruptHandler;
- }
-
- initGpioDone = true;
- }
-
- ///
- /// Get the Interrupt Identification Register (IIR) value.
- ///
- ///
- public byte GetInterruptSource()
- {
- byte iir = ReadRegister(Registers.IIR);
- iir &= RegisterBits.IIR_Id0 + RegisterBits.IIR_Id1 + RegisterBits.IIR_Id2 +
- RegisterBits.IIR_Id3 + RegisterBits.IIR_Id4 + RegisterBits.IIR_Id5;
- return iir;
+ initDone = true;
}
+ int debugId = 0;
byte lastInputState = 0;
private void GpioInterruptHandler(object sender, DigitalPortResult e)
{
try
{
- //byte iir = GetInterruptSource();
-
- //Resolver.Log.Info($"HandleGpioInterrupt. Interrupt pin state: {e.Old?.State} {e.New.State} {e.New.Time.ToString("hh:mm:ss.fffffff")} {_irq?.State}");
- byte state = ReadIoState();
- byte dirMask = (byte)~ioDir; // Only look at the input pins
- byte masked = (byte)(state & dirMask);
- //Resolver.Log.Info($"State: {state:X2} {dirMask:X2} {masked:X2}");
- //Resolver.Log.Info($"LastState: {lastState} NewState: {state}");
- if (masked == lastInputState) return;
-
- byte diff = (byte)(masked ^ lastInputState);
- //Resolver.Log.Info($"GPIO state: {lastInputState} -> {state}");
- for (byte i = 0; i < 8; i++)
+ //lock(this)
{
- if ((diff & (1 << i)) == 0) continue; // No change on this pin
- if (gpioPorts[i] == null) continue; // No port defined for this pin
- if (gpioPorts[i] is DigitalInputPort port)
+ //int id = debugId++;
+ //Resolver.Log.Info($"GpioInterruptHandler... {id}");
+
+ //// Prioritize reading the IRQ FIFO's
+ //if (_channelA?.IsOpen ?? false)
+ // _channelA.OnInterruptLineChanged(sender, e);
+ //if (_channelB?.IsOpen ?? false)
+ // _channelB.OnInterruptLineChanged(sender, e);
+
+ byte iir = GetInterruptSource();
+
+ //Resolver.Log.Info($"HandleGpioInterrupt. Interrupt pin state: {e.Old?.State} {e.New.State} {e.New.Time.ToString("hh:mm:ss.fffffff")} {_irq?.State}");
+ byte state = ReadGpioRegister(Registers.IOState);
+ byte dirMask = (byte)~ioDir; // Only look at the input pins
+ byte masked = (byte)(state & dirMask);
+ //Resolver.Log.Info($"State: {state:X2} {dirMask:X2} {masked:X2}");
+ //Resolver.Log.Info($"LastState: {lastState} NewState: {state}");
+ if (masked == lastInputState) return;
+
+ byte diff = (byte)(masked ^ lastInputState);
+ //Resolver.Log.Info($"GPIO state: {lastState} -> {state}");
+ for (byte i = 0; i < 8; i++)
{
- var newState = BitHelpers.GetBitValue(state, i);
- //Resolver.Log.Info($"Pin {i} new state: {newState}");
- port.Update(newState);
+ if ((diff & (1 << i)) == 0) continue; // No change on this pin
+ if (gpioPorts[i] == null) continue; // No input port defined for this pin
+ if (gpioPorts[i] is DigitalInputPort port)
+ {
+ var newState = BitHelpers.GetBitValue(state, i);
+ port.Update(newState);
+ }
}
+ lastInputState = masked;
+
+ //PrintAddressContent();
+ //byte irqTest = ReadGpioRegister(Registers.IOIntEna);
+ //if (irqTest != irqEna)
+ //{
+ // Resolver.Log.Info($"irqTest: {irqTest} irqEna: {irqEna}");
+ // WriteGpioRegister(Registers.IOIntEna, irqEna);
+ //}
+
+ //Resolver.Log.Info($"DONE. {id}");
}
- lastInputState = masked;
}
catch (Exception ex)
{
@@ -232,9 +435,10 @@ private void GpioInterruptHandler(object sender, DigitalPortResult e)
}
}
-
/****************** INPUT PORTS ******************/
+ private IDigitalPort[] gpioPorts = new IDigitalPort[8];
+
///
/// Create a digital input port on a SC16IS7x2 IO expander.
///
@@ -260,7 +464,7 @@ public IDigitalInputPort CreateDigitalInputPort(
throw new Exception("Only ResistorMode.Disabled is supported. The GPIO ports of SC16IS7x2 does not need/support external pull-up or pull-down resistors.");
}
- var state = ReadIoState();
+ var state = ReadGpioRegister(Registers.IOState);
var initState = BitHelpers.GetBitValue(state, pinIndex);
var port = new DigitalInputPort(pin, initState);
gpioPorts[pinIndex] = port;
@@ -274,6 +478,8 @@ public IDigitalInputPort CreateDigitalInputPort(
throw new Exception("Pin is out of range");
}
+ byte irqEna = 0;
+
///
/// Configure the hardware port settings on the SC16IS7x2
///
@@ -290,11 +496,12 @@ private void ConfigureInputPort(IPin pin)
if (_irq != null)
{
- SetRegisterBit(Registers.IOIntEna, bitIndex);
+ irqEna = BitHelpers.SetBit(irqEna, bitIndex, true);
+ SetGpioRegisterBit(Registers.IOIntEna, bitIndex);
}
else
{
- ClearRegisterBit(Registers.IOIntEna, bitIndex);
+ ClearGpioRegisterBit(Registers.IOIntEna, bitIndex);
}
}
else
@@ -317,7 +524,7 @@ private void PreValidatedSetPortDirection(IPin pin, PortDirectionType direction)
if (newIoDir == ioDir) return;
//Resolver.Log.Info($"newIoDir: {newIoDir}");
- WriteRegister(Registers.IODir, newIoDir);
+ WriteGpioRegister(Registers.IODir, newIoDir);
ioDir = newIoDir;
}
@@ -353,7 +560,7 @@ public bool ReadPort(IPin pin)
if (portDir == true)
throw new Exception($"Cant read from port {pin.Name}. It is not configured as input");
- var gpio = ReadIoState();
+ var gpio = ReadGpioRegister(Registers.IOState);
// Return the value on that port
return BitHelpers.GetBitValue(gpio, (byte)pin.Key);
@@ -420,105 +627,111 @@ private void WriteToGpioPort(IPin pin, bool value)
{
byte bitIndex = (byte)pin.Key;
if (value)
- SetIoStateBit(bitIndex);
+ SetGpioRegisterBit(Registers.IOState, bitIndex);
else
- ClearIoStateBit(bitIndex);
+ ClearGpioRegisterBit(Registers.IOState, bitIndex);
}
-
/*********** INPUT/OUTPUT PORT HELPERS ***********/
- private void SetRegisterBit(Registers register, int bitIndex)
+ ///
+ /// Get the Interrupt Identification Register (IIR) value.
+ ///
+ ///
+ public byte GetInterruptSource()
{
- if (bitIndex > 7 || bitIndex < 0) { throw new ArgumentOutOfRangeException(); }
-
- var oldValue = ReadRegister(register);
- byte newValue = (byte)(oldValue | ((byte)(1 << bitIndex)));
- WriteRegister(register, newValue);
+ byte iir = ReadGpioRegister(Registers.IIR);
+ iir &= RegisterBits.IIR_Id0 +
+ RegisterBits.IIR_Id1 +
+ RegisterBits.IIR_Id2 +
+ RegisterBits.IIR_Id3 +
+ RegisterBits.IIR_Id4 +
+ RegisterBits.IIR_Id5;
+ return iir;
}
- private void ClearRegisterBit(Registers register, int bitIndex)
+ ///
+ /// Get the interrupt source type from the IIR register value. (Interrupt Identification Register)
+ /// Ref. page 14 and 24 in datasheet. Mostly for debugging.
+ /// Not sure if multiple sources can be active at the same time. If so, this will need to be modified.
+ ///
+ /// The IIR register value. (Interrupt Identification Register)
+ ///
+ public InterruptSourceType GetInterruptSourceType(byte iir)
{
- if (bitIndex > 7 || bitIndex < 0) { throw new ArgumentOutOfRangeException(); }
- var oldValue = ReadRegister(register);
- var newValue = (byte)(oldValue & ((byte)~(1 << bitIndex)));
- WriteRegister(register, newValue);
+ iir &= (RegisterBits.IIR_Id0 +
+ RegisterBits.IIR_Id1 +
+ RegisterBits.IIR_Id2 +
+ RegisterBits.IIR_Id3 +
+ RegisterBits.IIR_Id4 +
+ RegisterBits.IIR_Id5);
+ if (iir == RegisterBits.IIR_IdNone) return InterruptSourceType.None;
+ if (iir == RegisterBits.IIR_IdReceiverLineStatus) return InterruptSourceType.ReceiverLineStatus;
+ if (iir == RegisterBits.IIR_IdRxTimeout) return InterruptSourceType.RxTimeout;
+ if (iir == RegisterBits.IIR_IdRHR) return InterruptSourceType.RHR;
+ if (iir == RegisterBits.IIR_IdTHR) return InterruptSourceType.THR;
+ if (iir == RegisterBits.IIR_IdModemStatus) return InterruptSourceType.ModemStatus;
+ if (iir == RegisterBits.IIR_IdGpioPins) return InterruptSourceType.GpioPins;
+ if (iir == RegisterBits.IIR_IdXoff) return InterruptSourceType.Xoff;
+ if (iir == RegisterBits.IIR_IdCtsRts) return InterruptSourceType.CtsRts;
+ Resolver.Log.Info($"UNKNOWN INTERRUPT SOURCE (or combination): {iir}");
+ return InterruptSourceType.Unknown;
}
- // Read a register that is not channel specific.
- private byte ReadRegister(Registers register)
+ ///
+ /// Is the pin valid for this device instance
+ ///
+ /// The IPin to validate
+ /// True if pin is valid
+ protected bool IsValidPin(IPin pin) => Pins.AllPins.Contains(pin);
+
+ private byte ReadGpioRegister(Registers register)
{
- byte v = Comms.ReadRegister((byte)((byte)register << 3));
- return v;
+ return ReadChannelRegister(register, Channels.Both);
}
- // Write to a register that is not channel specific.
- private void WriteRegister(Registers register, byte value)
+ private void WriteGpioRegister(Registers register, byte value)
{
- Comms.WriteRegister((byte)((byte)register << 3), value);
+ WriteChannelRegister(register, Channels.Both, value);
}
-
- /********************* OPTIMIZATIONS **********************/
-
- ///
- /// Specific method to read the IOState register. This is an optimization.
- /// We don't want to slow down all other register reads with the validation
- /// for this specific register.
- ///
- ///
- private byte ReadIoState()
+ private void SetGpioRegisterBits(Registers register, byte value)
{
- // See page 40 of the data sheet for explanation of this
- byte v = Comms.ReadRegister((byte)Registers.IOState << 3);
- if (v == 0)
- {
- byte v2 = Comms.ReadRegister((byte)Registers.IOState << 3);
- if (v2 != v)
- {
- v = v2; // Fix buggy value!
- Resolver.Log.Info($"ReadIoState: {v2:X2} (Corrected value!)");
- }
- }
- return v;
+ SetChannelRegisterBits(register, Channels.Both, value);
}
- private void WriteIoState(byte value)
+ private void ClearGpioRegisterBits(Registers register, byte mask)
{
- // see page 40 of the data sheet for explanation of this
- Comms.WriteRegister((byte)Registers.IOState << 3, value);
+ ClearChannelRegisterBits(register, Channels.Both, mask);
}
- // This is an optimization for the GPIO ports.
- private void ClearIoStateBit(int bitIndex)
+ private void SetGpioRegisterBit(Registers register, int bitIndex)
{
- if (bitIndex > 7 || bitIndex < 0) { throw new ArgumentOutOfRangeException(); }
-
- var oldValue = ReadIoState();
- byte newValue = (byte)(oldValue & ((byte)~(1 << bitIndex)));
- if (oldValue != newValue)
- WriteIoState(newValue);
+ SetChannelRegisterBit(register, Channels.Both, bitIndex);
}
- // This is an optimization for the IOState register.
- private void SetIoStateBit(int bitIndex)
+ private void SetChannelRegisterBit(Registers register, Channels channel, int bitIndex)
{
if (bitIndex > 7 || bitIndex < 0) { throw new ArgumentOutOfRangeException(); }
- var oldValue = ReadIoState();
+ var oldValue = ReadChannelRegister(register, channel);
byte newValue = (byte)(oldValue | ((byte)(1 << bitIndex)));
- if (oldValue != newValue)
- WriteIoState(newValue);
+ WriteChannelRegister(register, channel, newValue);
}
- /*********************** NICE TO HAVE METHODS ***********************/
+ private void ClearGpioRegisterBit(Registers register, int bitIndex)
+ {
+ ClearChannelRegisterBit(register, Channels.Both, bitIndex);
+ }
- private byte DebugReadChannelRegister(Registers register, Channels channel)
+ private void ClearChannelRegisterBit(Registers register, Channels channel, int bitIndex)
{
- // see page 40 of the data sheet for explanation of this
- var subaddress = (byte)(((byte)register << 3) | ((byte)channel << 1));
- byte v = Comms.ReadRegister(subaddress);
- return v;
+ if (bitIndex > 7 || bitIndex < 0) { throw new ArgumentOutOfRangeException(); }
+ var oldValue = ReadChannelRegister(register, channel);
+ var newValue = (byte)(oldValue & ((byte)~(1 << bitIndex)));
+ WriteChannelRegister(register, channel, newValue);
+ //var check = ReadChannelRegister(register, channel);
+ //Resolver.Log.Info($"ClearChannelRegisterBit: Old={oldValue} New={newValue} Check={check}");
}
///
@@ -529,52 +742,19 @@ public void PrintAddressContent()
Resolver.Log.Info($"Register: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
string a = $"Channel A: ";
for (int r = 0; r < 16; r++)
- a += $"{DebugReadChannelRegister((Registers)r, Channels.A):X2} ";
+ a += $"{ReadChannelRegister((Registers)r, Channels.A):X2} ";
Resolver.Log.Info(a);
string b = $"Channel B: ";
for (int r = 0; r < 16; r++)
- b += $"{DebugReadChannelRegister((Registers)r, Channels.B):X2} ";
+ b += $"{ReadChannelRegister((Registers)r, Channels.B):X2} ";
Resolver.Log.Info(b);
}
- ///
- /// Nice-to-have conversion from byte to binary string.
- ///
- ///
- ///
public string ByteToBinaryString(byte b)
{
// Format the byte as a binary string and pad it with zeroes
string binaryString = $"0b{Convert.ToString(b, 2).PadLeft(8, '0')}";
return binaryString;
}
-
- ///
- /// Get the interrupt source type from the IIR register value. (Interrupt Identification Register)
- /// Ref. page 14 and 24 in datasheet. Mostly for debugging.
- /// Not sure if multiple sources can be active at the same time. If so, this will need to be modified.
- ///
- /// The IIR register value. (Interrupt Identification Register)
- ///
- public InterruptSourceType GetInterruptSourceType(byte iir)
- {
- iir &= (RegisterBits.IIR_Id0 +
- RegisterBits.IIR_Id1 +
- RegisterBits.IIR_Id2 +
- RegisterBits.IIR_Id3 +
- RegisterBits.IIR_Id4 +
- RegisterBits.IIR_Id5);
- if (iir == RegisterBits.IIR_IdNone) return InterruptSourceType.None;
- if (iir == RegisterBits.IIR_IdReceiverLineStatus) return InterruptSourceType.ReceiverLineStatus;
- if (iir == RegisterBits.IIR_IdRxTimeout) return InterruptSourceType.RxTimeout;
- if (iir == RegisterBits.IIR_IdRHR) return InterruptSourceType.RHR;
- if (iir == RegisterBits.IIR_IdTHR) return InterruptSourceType.THR;
- if (iir == RegisterBits.IIR_IdModemStatus) return InterruptSourceType.ModemStatus;
- if (iir == RegisterBits.IIR_IdGpioPins) return InterruptSourceType.GpioPins;
- if (iir == RegisterBits.IIR_IdXoff) return InterruptSourceType.Xoff;
- if (iir == RegisterBits.IIR_IdCtsRts) return InterruptSourceType.CtsRts;
- Resolver.Log.Info($"UNKNOWN INTERRUPT SOURCE (or combination): {iir}");
- return InterruptSourceType.Unknown;
- }
}
-}
+}
\ No newline at end of file
diff --git a/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Datasheet/BLD-510B_manual.pdf b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Datasheet/BLD-510B_manual.pdf
new file mode 100644
index 000000000..816ab29dd
Binary files /dev/null and b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Datasheet/BLD-510B_manual.pdf differ
diff --git a/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Controllers/BLD510B.cs b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Controllers/BLD510B.cs
new file mode 100644
index 000000000..00f6b8b1b
--- /dev/null
+++ b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Controllers/BLD510B.cs
@@ -0,0 +1,278 @@
+using Meadow.Modbus;
+using Meadow.Peripherals;
+using System;
+using System.Threading.Tasks;
+
+namespace Meadow.Foundation.MotorControllers.StepperOnline;
+
+public class BLD510B : ModbusPolledDevice
+{
+ public event EventHandler ErrorConditionsChanged;
+
+ private ErrorConditions _lastError;
+ private ushort _state;
+
+ ///
+ /// The default Modbus address for the V10x device.
+ ///
+ public const int DefaultModbusAddress = 1;
+
+ ///
+ /// The default baud rate for communication with the V10x device.
+ ///
+ public const int DefaultBaudRate = 9600;
+
+ public BLD510B(ModbusRtuClient client, byte modbusAddress = 0x01, TimeSpan? refreshPeriod = null)
+ : base(client, modbusAddress, refreshPeriod)
+ {
+ MapHoldingRegistersToField(
+ startRegister: 0x801b,
+ registerCount: 1,
+ fieldName: nameof(_state),
+ conversionFunction: StateCheckerFunction
+ );
+ }
+
+ public async Task GetStartStopTerminal()
+ {
+ var r = await ReadHoldingRegisters(0x8000, 1);
+
+ return ((r[0] >> 8) & 1) != 0;
+ }
+
+ public async Task SetStartStopTerminal(bool startEnabled)
+ {
+ var current = (int)(await ReadHoldingRegisters(0x8000, 1))[0];
+ if (startEnabled)
+ { // set bit 0
+ current = current | (1 << 8);
+ }
+ else
+ {
+ current = current & ~(1 << 8);
+ }
+ await WriteHoldingRegister(0x8000, (ushort)current);
+ }
+
+ public async Task GetDirectionTerminal()
+ {
+ var r = await ReadHoldingRegisters(0x8000, 1);
+ return ((r[0] >> 9) & 1) == 0 ? RotationDirection.Clockwise : RotationDirection.CounterClockwise;
+ }
+
+ public async Task SetDirectionTerminal(RotationDirection direction)
+ {
+ int current = ReadHoldingRegisters(0x8000, 1).Result[0];
+ if (direction == RotationDirection.Clockwise)
+ { // clear bit 1
+ current = current & ~(1 << 9);
+ }
+ else
+ {
+ current = current | (1 << 9);
+ }
+ await WriteHoldingRegister(0x8000, (ushort)current);
+ }
+
+ public async Task GetBrakeTerminal()
+ {
+ var r = await ReadHoldingRegisters(0x8000, 1);
+ return ((r[0] >> 10) & 1) != 0;
+ }
+
+ public async Task SetBrakeTerminal(bool brakeEnabled)
+ {
+ int current = ReadHoldingRegisters(0x8000, 1).Result[0];
+ if (brakeEnabled)
+ { // set bit 2
+ current = current | (1 << 10);
+ }
+ else
+ {
+ current = current & ~(1 << 10);
+ }
+ await WriteHoldingRegister(0x8000, (ushort)current);
+ }
+
+ public async Task GetSpeedControl()
+ {
+ var r = await ReadHoldingRegisters(0x8000, 1);
+ return (SpeedControl)((r[0] >> 11) & 1);
+ }
+
+ public async Task SetSpeedControl(SpeedControl speedControl)
+ {
+ int current = ReadHoldingRegisters(0x8000, 1).Result[0];
+ if (speedControl == SpeedControl.AnalogPot)
+ { // clear bit 4
+ current = current & ~(1 << 11);
+ }
+ else
+ {
+ current = current | (1 << 11);
+ }
+ await WriteHoldingRegister(0x8000, (ushort)current);
+ }
+
+ public async Task GetNumberOfMotorPolePairs()
+ {
+ var r = await ReadHoldingRegisters(0x8000, 1);
+ return (byte)(r[0] & 0xff);
+ }
+
+ public async Task SetNumberOfMotorPolePairs(byte numberOfMotorPolePairs)
+ {
+ var current = (int)(await ReadHoldingRegisters(0x8000, 1))[0];
+ current &= 0xff00;
+ current |= numberOfMotorPolePairs;
+ // always disable EN if we're doing this operation
+ current &= ~(1 << 8);
+ await WriteHoldingRegister(0x8000, (ushort)current);
+ }
+
+ public async Task GetStartupTorque()
+ {
+ var r = await ReadHoldingRegisters(0x8002, 1);
+ return (byte)(r[0] >> 8);
+ }
+
+ public async Task SetStartupTorque(byte value)
+ {
+ var r = await ReadHoldingRegisters(0x8002, 1);
+ var current = r[0] & 0x00ff;
+ current |= value << 8;
+ await WriteHoldingRegister(0x8002, (ushort)current);
+ }
+
+ public async Task GetStartupSpeed()
+ {
+ var r = await ReadHoldingRegisters(0x8002, 1);
+ return (byte)(r[0] & 0xff);
+ }
+
+ public async Task SetStartupSpeed(byte value)
+ {
+ var r = await ReadHoldingRegisters(0x8002, 1);
+ var current = r[0] & 0xff00;
+ current |= value;
+ await WriteHoldingRegister(0x8002, (ushort)current);
+ }
+
+ public async Task GetAccelerationTime()
+ {
+ var r = await ReadHoldingRegisters(0x8003, 1);
+ return TimeSpan.FromSeconds((r[0] >> 8) / 10d);
+ }
+
+ public async Task SetAccelerationTime(TimeSpan value)
+ {
+ if (value.TotalSeconds < 0 || value.TotalSeconds > 25.5) throw new ArgumentOutOfRangeException();
+ var r = await ReadHoldingRegisters(0x8002, 1);
+ var current = r[0] & 0x00ff;
+ current |= (byte)(value.TotalSeconds * 10) << 8;
+ await WriteHoldingRegister(0x8003, (ushort)current);
+ }
+
+ public async Task GetDecelerationTime()
+ {
+ var r = await ReadHoldingRegisters(0x8003, 1);
+ return TimeSpan.FromSeconds((r[0] & 0xff) / 10d);
+ }
+
+ public async Task SetDecelerationTime(TimeSpan value)
+ {
+ if (value.TotalSeconds < 0 || value.TotalSeconds > 25.5) throw new ArgumentOutOfRangeException();
+ var r = await ReadHoldingRegisters(0x8003, 1);
+ var current = r[0] & 0xff00;
+ current |= (byte)(value.TotalSeconds * 10);
+ await WriteHoldingRegister(0x8003, (ushort)current);
+ }
+
+ public async Task GetMaxCurrent()
+ {
+ var r = await ReadHoldingRegisters(0x8004, 1);
+ return (byte)(r[0] >> 8);
+ }
+
+ public async Task SetMaxCurrent(byte value)
+ {
+ var r = await ReadHoldingRegisters(0x8004, 1);
+ var current = r[0] & 0x00ff;
+ current |= value << 8;
+ await WriteHoldingRegister(0x8004, (ushort)current);
+ }
+
+ public async Task GetMotorType()
+ {
+ var r = await ReadHoldingRegisters(0x8004, 1);
+ return (MotorType)(r[0] & 0xff);
+ }
+
+ public async Task SetMotorType(MotorType value)
+ {
+ var r = await ReadHoldingRegisters(0x8004, 1);
+ var current = r[0] & 0xff00;
+ current |= (int)value;
+ await WriteHoldingRegister(0x8004, (ushort)current);
+ }
+
+ ///
+ /// The desired speed when using control mode of RS485, this is ignored when control is analog
+ ///
+ public async Task GetDesiredSpeed()
+ {
+ var r = await ReadHoldingRegisters(0x8005, 1);
+ return r[0];
+ }
+
+ public async Task SetDesiredSpeed(ushort speed)
+ {
+ // swap endianness
+ var s = speed << 8 | speed >> 8;
+ await WriteHoldingRegister(0x8005, (ushort)s);
+ }
+
+ public async Task GetModbusAddress()
+ {
+ var r = await ReadHoldingRegisters(0x8007, 1);
+ return (byte)r[0];
+ }
+
+ public async Task SetModbusAddress(byte value)
+ {
+ if (value <= 0 || value > 250) throw new ArgumentOutOfRangeException();
+
+ await WriteHoldingRegister(0x8007, value);
+ }
+
+ public async Task GetActualSpeed()
+ {
+ ushort[] data;
+ do
+ {
+ data = await ReadHoldingRegisters(0x8018, 1);
+ } while (data.Length == 0);
+
+ // swap endianness
+ return (ushort)(data[0] >> 8 | data[0] << 8);
+ }
+
+ public ErrorConditions ErrorConditions
+ {
+ get => _lastError;
+ }
+
+ private object StateCheckerFunction(ushort[] data)
+ {
+ // we use this function to set events
+ var state = (ErrorConditions)(data[0] >> 8);
+
+ if (state != _lastError)
+ {
+ _lastError = state;
+ ErrorConditionsChanged?.Invoke(this, state);
+ }
+
+ return data[0];
+ }
+}
diff --git a/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/ErrorConditions.cs b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/ErrorConditions.cs
new file mode 100644
index 000000000..1fa080e36
--- /dev/null
+++ b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/ErrorConditions.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Meadow.Foundation.MotorControllers.StepperOnline;
+
+[Flags]
+public enum ErrorConditions
+{
+ None = 0x00,
+ LockedRotor = 0x01,
+ OverCurrent = 0x02,
+ HallValueAbnormal = 0x04,
+ BusVoltageLow = 0x08,
+ BusVoltageHigh = 0x10,
+ CurrentPeak = 0x20
+}
diff --git a/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/MotorType.cs b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/MotorType.cs
new file mode 100644
index 000000000..c71b3aee7
--- /dev/null
+++ b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/MotorType.cs
@@ -0,0 +1,7 @@
+namespace Meadow.Foundation.MotorControllers.StepperOnline;
+
+public enum MotorType
+{
+ Sensored = 0x0f,
+ Sensorless = 0x10
+}
diff --git a/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Motors.StepperOnline.csproj b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Motors.StepperOnline.csproj
new file mode 100644
index 000000000..49a8eb422
--- /dev/null
+++ b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Motors.StepperOnline.csproj
@@ -0,0 +1,33 @@
+
+
+
+ netstandard2.1
+ enable
+ 11
+ Motors.StepperOnline
+
+ Apache-2.0
+ true
+ icon.png
+ Wilderness Labs, Inc
+ netstandard2.1
+ Library
+ Wilderness Labs, Inc
+ http://developer.wildernesslabs.co/Meadow/Meadow.Foundation/
+ Meadow.Foundation.Motors.StepperOnline
+ https://github.com/WildernessLabs/Meadow.Foundation
+ Meadow.Foundation,Stepper Online,BLDC Motor
+ true
+ Drivers for motors and moto controllers from Stepper Online
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Motors/F55B150_24GL_30S.cs b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Motors/F55B150_24GL_30S.cs
new file mode 100644
index 000000000..5c7914e9d
--- /dev/null
+++ b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Motors/F55B150_24GL_30S.cs
@@ -0,0 +1,109 @@
+using Meadow.Foundation.MotorControllers.StepperOnline;
+using Meadow.Peripherals;
+using Meadow.Peripherals.Motors;
+using Meadow.Units;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Meadow.Foundation.Motors.StepperOnline;
+
+///
+/// A 24V, 100RPM, 30:1 gear-reduction BLDC motor
+///
+public class F55B150_24GL_30S : IMotor
+{
+ private BLD510B controller;
+
+ public const RotationDirection DefaultRotationDirection = RotationDirection.Clockwise;
+ public static AngularVelocity DefaultSpeed = new AngularVelocity(100, AngularVelocity.UnitType.RevolutionsPerMinute);
+
+ ///
+ public RotationDirection Direction { get; private set; }
+
+ ///
+ public bool IsMoving
+ {
+ get => controller.GetActualSpeed().Result > 0;
+ }
+
+ public F55B150_24GL_30S(BLD510B controller)
+ {
+ this.controller = controller;
+
+ Initialize().Wait();
+ }
+
+ public Task SetSpeed(AngularVelocity desiredSpeed)
+ {
+ var val = desiredSpeed.RevolutionsPerMinute * 75;
+ if (val > 65535) throw new ArgumentOutOfRangeException(nameof(desiredSpeed));
+ return controller.SetDesiredSpeed((ushort)val);
+ }
+
+ private async Task Initialize()
+ {
+ var succeeded = false;
+
+ while (!succeeded)
+ {
+ try
+ {
+ await controller.SetStartStopTerminal(false);
+ await controller.SetNumberOfMotorPolePairs(10);
+ await controller.SetSpeedControl(SpeedControl.RS485);
+ Direction = DefaultRotationDirection;
+ await controller.SetDirectionTerminal(Direction);
+ await SetSpeed(DefaultSpeed);
+ succeeded = true;
+ }
+ catch (TimeoutException)
+ {
+ Resolver.Log.Warn("Timeout initializing");
+ await Task.Delay(500);
+ }
+ }
+ }
+
+ public async Task Run(RotationDirection direction, CancellationToken cancellationToken = default)
+ {
+ Direction = direction;
+ await controller.SetDirectionTerminal(direction);
+ await controller.SetStartStopTerminal(true);
+ }
+
+ public async Task RunFor(TimeSpan runTime, RotationDirection direction, CancellationToken cancellationToken = default)
+ {
+ Direction = direction;
+ await controller.SetDirectionTerminal(direction);
+ await controller.SetStartStopTerminal(true);
+ await Task.Delay(runTime, cancellationToken);
+ await controller.SetStartStopTerminal(true);
+ }
+
+ public Task Stop(CancellationToken cancellationToken = default)
+ {
+ return controller.SetStartStopTerminal(false);
+ }
+
+ public Task SetBrakeState(bool enabled)
+ {
+ return controller.SetBrakeTerminal(enabled);
+ }
+
+ public Task SetAccelerationTime(TimeSpan time)
+ {
+ return controller.SetAccelerationTime(time);
+ }
+
+ public Task SetDecelerationTime(TimeSpan time)
+ {
+ return controller.SetDecelerationTime(time);
+ }
+
+ public async Task GetActualSpeed()
+ {
+ var rawSpeed = await controller.GetActualSpeed();
+ return new AngularVelocity(rawSpeed * 0.0133, AngularVelocity.UnitType.RevolutionsPerMinute);
+ }
+}
diff --git a/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Readme.md b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Readme.md
new file mode 100644
index 000000000..daa640481
--- /dev/null
+++ b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/Readme.md
@@ -0,0 +1,142 @@
+# Meadow.Foundation.Sensors.Atmospheric.Bme68x
+
+**Bosch BME68x SPI / I2C humidity, barometric pressure, ambient temperature and gas (VOC) sensor**
+
+The **Bme68x** library is included in the **Meadow.Foundation.Sensors.Atmospheric.Bme68x** nuget package and is designed for the [Wilderness Labs](www.wildernesslabs.co) Meadow .NET IoT platform.
+
+This driver is part of the [Meadow.Foundation](https://developer.wildernesslabs.co/Meadow/Meadow.Foundation/) peripherals library, an open-source repository of drivers and libraries that streamline and simplify adding hardware to your C# .NET Meadow IoT applications.
+
+For more information on developing for Meadow, visit [developer.wildernesslabs.co](http://developer.wildernesslabs.co/).
+
+To view all Wilderness Labs open-source projects, including samples, visit [github.com/wildernesslabs](https://github.com/wildernesslabs/).
+
+## Installation
+
+You can install the library from within Visual studio using the the NuGet Package Manager or from the command line using the .NET CLI:
+
+`dotnet add package Meadow.Foundation.Sensors.Atmospheric.Bme68x`
+## Usage
+
+```csharp
+Bme680? sensor;
+
+public override Task Initialize()
+{
+ Resolver.Log.Info("Initializing...");
+
+ //CreateSpiSensor();
+ CreateI2CSensor();
+
+ //uncomment to enable on sensor heater for gas readings
+ //EnableGasHeater();
+
+ var consumer = Bme680.CreateObserver(
+ handler: result =>
+ {
+ Resolver.Log.Info($"Observer: Temp changed by threshold; new temp: {result.New.Temperature?.Celsius:N2}C, old: {result.Old?.Temperature?.Celsius:N2}C");
+ },
+ filter: result =>
+ {
+ if (result.Old?.Temperature is { } oldTemp &&
+ result.Old?.Humidity is { } oldHumidity &&
+ result.New.Temperature is { } newTemp &&
+ result.New.Humidity is { } newHumidity)
+ {
+ return ((newTemp - oldTemp).Abs().Celsius > 0.5 &&
+ (newHumidity - oldHumidity).Percent > 0.05);
+ }
+ return false;
+ }
+ );
+
+ sensor?.Subscribe(consumer);
+
+ if (sensor != null)
+ {
+ sensor.Updated += (sender, result) =>
+ {
+ Resolver.Log.Info($" Temperature: {result.New.Temperature?.Celsius:N2}C");
+ Resolver.Log.Info($" Relative Humidity: {result.New.Humidity:N2}%");
+ Resolver.Log.Info($" Pressure: {result.New.Pressure?.Millibar:N2}mbar ({result.New.Pressure?.Pascal:N2}Pa)");
+ if (sensor.GasConversionIsEnabled)
+ {
+ Resolver.Log.Info($" Gas Resistance: {result.New.GasResistance:N0}Ohms");
+ }
+ };
+ }
+
+ sensor?.StartUpdating(TimeSpan.FromSeconds(2));
+
+ ReadConditions().Wait();
+
+ return base.Initialize();
+}
+
+void EnableGasHeater()
+{
+ if (sensor != null)
+ {
+ sensor.GasConversionIsEnabled = true;
+ sensor.HeaterIsEnabled = true;
+ sensor.ConfigureHeatingProfile(Bme688.HeaterProfileType.Profile1, new Meadow.Units.Temperature(300), TimeSpan.FromMilliseconds(100), new Meadow.Units.Temperature(22));
+ sensor.HeaterProfile = Bme688.HeaterProfileType.Profile1;
+ }
+}
+
+void CreateSpiSensor()
+{
+ Resolver.Log.Info("Create BME680 sensor with SPI...");
+
+ var spiBus = Device.CreateSpiBus();
+ sensor = new Bme680(spiBus, Device.CreateDigitalOutputPort(Device.Pins.D14));
+}
+
+void CreateI2CSensor()
+{
+ Resolver.Log.Info("Create BME680 sensor with I2C...");
+
+ var i2c = Device.CreateI2cBus();
+ sensor = new Bme680(i2c, (byte)Bme688.Addresses.Address_0x76);
+}
+
+async Task ReadConditions()
+{
+ if (sensor == null) { return; }
+
+ var (Temperature, Humidity, Pressure, Resistance) = await sensor.Read();
+
+ Resolver.Log.Info("Initial Readings:");
+ Resolver.Log.Info($" Temperature: {Temperature?.Celsius:N2}C");
+ Resolver.Log.Info($" Pressure: {Pressure?.Hectopascal:N2}hPa");
+ Resolver.Log.Info($" Relative Humidity: {Humidity?.Percent:N2}%");
+ Resolver.Log.Info($" Gas Resistance: {Resistance?.Ohms:N0}Ohms");
+}
+
+```
+## How to Contribute
+
+- **Found a bug?** [Report an issue](https://github.com/WildernessLabs/Meadow_Issues/issues)
+- Have a **feature idea or driver request?** [Open a new feature request](https://github.com/WildernessLabs/Meadow_Issues/issues)
+- Want to **contribute code?** Fork the [Meadow.Foundation](https://github.com/WildernessLabs/Meadow.Foundation) repository and submit a pull request against the `develop` branch
+
+
+## Need Help?
+
+If you have questions or need assistance, please join the Wilderness Labs [community on Slack](http://slackinvite.wildernesslabs.co/).
+## About Meadow
+
+Meadow is a complete, IoT platform with defense-grade security that runs full .NET applications on embeddable microcontrollers and Linux single-board computers including Raspberry Pi and NVIDIA Jetson.
+
+### Build
+
+Use the full .NET platform and tooling such as Visual Studio and plug-and-play hardware drivers to painlessly build IoT solutions.
+
+### Connect
+
+Utilize native support for WiFi, Ethernet, and Cellular connectivity to send sensor data to the Cloud and remotely control your peripherals.
+
+### Deploy
+
+Instantly deploy and manage your fleet in the cloud for OtA, health-monitoring, logs, command + control, and enterprise backend integrations.
+
+
diff --git a/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/SpeedControl.cs b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/SpeedControl.cs
new file mode 100644
index 000000000..916b632a9
--- /dev/null
+++ b/Source/Meadow.Foundation.Peripherals/Motors.StepperOnline/Driver/SpeedControl.cs
@@ -0,0 +1,7 @@
+namespace Meadow.Foundation.MotorControllers.StepperOnline;
+
+public enum SpeedControl
+{
+ AnalogPot = 0,
+ RS485 = 1
+}
diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Keller.XLine/Driver/IKellerTransducer.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Keller.XLine/Driver/IKellerTransducer.cs
index 1a05fe693..b22dfa384 100644
--- a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Keller.XLine/Driver/IKellerTransducer.cs
+++ b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Keller.XLine/Driver/IKellerTransducer.cs
@@ -3,10 +3,34 @@
namespace Meadow.Foundation.Sensors.Environmental;
+///
+/// Defines the interface for Keller pressure transducers and transmitters.
+///
public interface IKellerTransducer
{
+ ///
+ /// Reads the temperature from the specified temperature channel.
+ ///
+ /// The temperature channel to read from.
+ /// A task that represents the asynchronous operation. The value is the temperature reading.
Task ReadTemperature(TemperatureChannel channel);
+
+ ///
+ /// Reads the pressure from the specified pressure channel.
+ ///
+ /// The pressure channel to read from.
+ /// A task that represents the asynchronous operation. The value is the pressure reading.
Task ReadPressure(PressureChannel channel);
+
+ ///
+ /// Reads the Modbus address of the device.
+ ///
+ /// A task that represents the asynchronous operation. The value is the Modbus address as a byte.
Task ReadModbusAddress();
+
+ ///
+ /// Reads the serial number of the device.
+ ///
+ /// A task that represents the asynchronous operation. The value is the device's serial number.
Task ReadSerialNumber();
-}
+}
\ No newline at end of file
diff --git a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Keller.XLine/Driver/KellerTransducer.cs b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Keller.XLine/Driver/KellerTransducer.cs
index 8c0c54b57..554a98c42 100644
--- a/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Keller.XLine/Driver/KellerTransducer.cs
+++ b/Source/Meadow.Foundation.Peripherals/Sensors.Environmental.Keller.XLine/Driver/KellerTransducer.cs
@@ -5,6 +5,14 @@
namespace Meadow.Foundation.Sensors.Environmental;
+///
+/// Represents a Keller pressure transducer or transmitter that communicates via Modbus RTU.
+///
+///
+/// Keller transducers provide high-precision pressure and temperature measurements
+/// for industrial and scientific applications. This implementation supports reading
+/// pressure, temperature, device address, and serial number via Modbus RTU protocol.
+///
public class KellerTransducer : IKellerTransducer
{
private ModbusRtuClient modbusClient;
@@ -12,6 +20,21 @@ public class KellerTransducer : IKellerTransducer
private ushort? activeTemperatureChannels;
private byte communicationAddress;
+ ///
+ /// The default Modbus address for the device.
+ ///
+ public const int DefaultModbusAddress = 1;
+
+ ///
+ /// The default baud rate for communication with the device.
+ ///
+ public const int DefaultBaudRate = 9600;
+
+ ///
+ /// Creates a new instance of the KellerTransducer connected via Modbus RTU.
+ ///
+ /// The Modbus RTU client used to communicate with the device.
+ /// The Modbus address of the device. Defaults to 1 if not specified.
public KellerTransducer(ModbusRtuClient modbus, byte modbusAddress = 1)
{
communicationAddress = modbusAddress;
@@ -29,8 +52,11 @@ private async Task ReadConfiguration()
try
{
var registers = await modbusClient.ReadHoldingRegisters(communicationAddress, 0x0204, 4);
- activePressureChannels = registers[0];
- activeTemperatureChannels = registers[1];
+ if (registers.Length == 2)
+ {
+ activePressureChannels = registers[0];
+ activeTemperatureChannels = registers[1];
+ }
}
catch (Exception ex)
{
@@ -50,21 +76,21 @@ public async Task ReadModbusAddress()
return (byte)registers[0];
}
+ ///
public async Task ReadSerialNumber()
{
var registers = await modbusClient.ReadHoldingRegisters(communicationAddress, 0x0202, 2);
return registers.ExtractInt32();
}
- public Task WriteModbusAddress(byte address)
+ internal Task WriteModbusAddress(byte address)
{
return modbusClient.WriteHoldingRegister(communicationAddress, 0x020D, address);
}
+ ///
public async Task ReadTemperature(TemperatureChannel channel)
{
- var count = 6;
-
if (activeTemperatureChannels == null)
{
await ReadConfiguration();
@@ -88,6 +114,7 @@ public Task WriteModbusAddress(byte address)
return new Units.Temperature(temp, Units.Temperature.UnitType.Celsius);
}
+ ///
public async Task ReadPressure(PressureChannel channel)
{
if (activePressureChannels == null)
diff --git a/Source/Meadow.Foundation.sln b/Source/Meadow.Foundation.sln
index 5a12d6827..71ef82f41 100644
--- a/Source/Meadow.Foundation.sln
+++ b/Source/Meadow.Foundation.sln
@@ -1629,6 +1629,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICs.IOExpanders.Ads1263", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ads1263_Sample", "Meadow.Foundation.Peripherals\ICs.IOExpanders.Ads1263\Samples\Ads1263_Sample\Ads1263_Sample.csproj", "{3A402704-9390-4492-93D4-B377BE8F034C}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StepperOnline", "StepperOnline", "{CC1FB401-1B19-4254-9208-339661134D9B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Motors.StepperOnline", "Meadow.Foundation.Peripherals\Motors.StepperOnline\Driver\Motors.StepperOnline.csproj", "{D8D26232-ECA0-4042-88F4-7ECF1BC3E62F}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Keller XLine", "Keller XLine", "{2240AB38-3CC6-4800-8556-D3BB5C44B720}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sensors.Environmental.Keller.XLine", "Meadow.Foundation.Peripherals\Sensors.Environmental.Keller.XLine\Driver\Sensors.Environmental.Keller.XLine.csproj", "{729B86E1-49EE-4668-A599-8D7C28842F9E}"
@@ -3947,6 +3951,10 @@ Global
{3A402704-9390-4492-93D4-B377BE8F034C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A402704-9390-4492-93D4-B377BE8F034C}.Release|Any CPU.Build.0 = Release|Any CPU
{3A402704-9390-4492-93D4-B377BE8F034C}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ {D8D26232-ECA0-4042-88F4-7ECF1BC3E62F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D8D26232-ECA0-4042-88F4-7ECF1BC3E62F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D8D26232-ECA0-4042-88F4-7ECF1BC3E62F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D8D26232-ECA0-4042-88F4-7ECF1BC3E62F}.Release|Any CPU.Build.0 = Release|Any CPU
{729B86E1-49EE-4668-A599-8D7C28842F9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{729B86E1-49EE-4668-A599-8D7C28842F9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{729B86E1-49EE-4668-A599-8D7C28842F9E}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
@@ -4776,6 +4784,8 @@ Global
{BEAD51B2-CF51-4836-ACBA-0911841E00F6} = {6D5016B9-7BF5-4E9D-BBA4-A621BAE0E638}
{B414A340-EA69-4CD3-AB8B-D0B3835527B2} = {6D5016B9-7BF5-4E9D-BBA4-A621BAE0E638}
{3A402704-9390-4492-93D4-B377BE8F034C} = {BEAD51B2-CF51-4836-ACBA-0911841E00F6}
+ {CC1FB401-1B19-4254-9208-339661134D9B} = {2486B48D-D4A2-4505-BF50-F33B2E15DA97}
+ {D8D26232-ECA0-4042-88F4-7ECF1BC3E62F} = {CC1FB401-1B19-4254-9208-339661134D9B}
{2240AB38-3CC6-4800-8556-D3BB5C44B720} = {78E463DA-0FA1-4AAE-A281-D3297C9388C9}
{729B86E1-49EE-4668-A599-8D7C28842F9E} = {2240AB38-3CC6-4800-8556-D3BB5C44B720}
{EA8D8CA1-BB32-49D6-88CF-9EB50ABDC44B} = {7311794D-7D2F-47E8-A5B0-C216CBD64A13}