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}