diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5954c67eb6..9fe3d2127a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -54,18 +54,6 @@ stages: /p:BuildPackages=true displayName: Build - - script: powershell -ExecutionPolicy ByPass -NoProfile eng\common\msbuild.ps1 -warnaserror:0 -ci - eng/sendToHelix.proj - /t:Test - /p:TestOS=Windows_NT - /p:Configuration=$(BuildConfiguration) - /p:HelixBuild=$(Build.BuildNumber) - /bl:$(Build.SourcesDirectory)/artifacts/log/$(BuildConfiguration)/SendToHelix.binlog - displayName: Run Helix Tests - condition: eq(variables['build.reason'], 'PullRequest') - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - - task: PublishBuildArtifacts@1 displayName: Publish Build logs condition: always() diff --git a/eng/sendToHelix.proj b/eng/sendToHelix.proj index d37984dbc0..b0b76763f0 100644 --- a/eng/sendToHelix.proj +++ b/eng/sendToHelix.proj @@ -30,25 +30,6 @@ - - - - - netcoreapp3.1 - netcoreapp2.0 - - - - - - - - win-arm - $(XUnitArguments) -notrait "SkipOnTestRun=Windows_NT" - - $(XUnitArguments) -notrait feature=pwm - - diff --git a/src/System.Device.Gpio/System/Device/Gpio/RaspberryBoardInfo.cs b/src/System.Device.Gpio/System/Device/Gpio/RaspberryBoardInfo.cs index 1f1656d0cf..3afc6c71bb 100644 --- a/src/System.Device.Gpio/System/Device/Gpio/RaspberryBoardInfo.cs +++ b/src/System.Device.Gpio/System/Device/Gpio/RaspberryBoardInfo.cs @@ -164,7 +164,7 @@ public Model BoardModel 0x20E0 => Model.RaspberryPi3APlus, 0x20A0 or 0x2100 => Model.RaspberryPiComputeModule3, 0x3111 or 0x3112 or 0x3114 or 0x3115 => Model.RaspberryPi4, - 0x3140 => Model.RaspberryPiComputeModule4, + 0x3140 or 0x3141 => Model.RaspberryPiComputeModule4, 0x3130 => Model.RaspberryPi400, _ => Model.Unknown, }; diff --git a/src/devices/Mcp25xxx/FrequencyAndSpeed.cs b/src/devices/Mcp25xxx/FrequencyAndSpeed.cs new file mode 100644 index 0000000000..26a4af3899 --- /dev/null +++ b/src/devices/Mcp25xxx/FrequencyAndSpeed.cs @@ -0,0 +1,176 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Iot.Device.Mcp25xxx +{ + /// + /// Standard Frequency and Speed for CAN bus + /// + public enum FrequencyAndSpeed + { + /// + /// 8MHz 1000kBPS + /// + _8MHz1000KBps, + + /// + /// 8MHz 500kBPS + /// + _8MHz500KBps, + + /// + /// 8MHz 250kBPS + /// + _8MHz250KBps, + + /// + /// 8MHz 200kBPS + /// + _8MHz200KBps, + + /// + /// 8MHz 125kBPS + /// + _8MHz125KBps, + + /// + /// 8MHz 100kBPS + /// + _8MHz100KBps, + + /// + /// 8MHz 80kBPS + /// + _8MHz80KBps, + + /// + /// 8MHz 50kBPS + /// + _8MHz50KBps, + + /// + /// 8MHz 40kBPS + /// + _8MHz40KBps, + + /// + /// 8MHz 20kBPS + /// + _8MHz20KBps, + + /// + /// 8MHz 10kBPS + /// + _8MHz10KBps, + + /// + /// 8MHz 5kBPS + /// + _8MHz5KBps, + + /// + /// 16MHz 1000kBPS + /// + _16MHz1000KBps, + + /// + /// 16MHz 500kBPS + /// + _16MHz500KBps, + + /// + /// 16MHz 250kBPS + /// + _16MHz250KBps, + + /// + /// 16MHz 200kBPS + /// + _16MHz200KBps, + + /// + /// 16MHz 125kBPS + /// + _16MHz125KBps, + + /// + /// 16MHz 100kBPS + /// + _16MHz100KBps, + + /// + /// 16MHz 80kBPS + /// + _16MHz80KBps, + + /// + /// 16MHz 50kBPS + /// + _16MHz50KBps, + + /// + /// 16MHz 40kBPS + /// + _16MHz40KBps, + + /// + /// 16MHz 20kBPS + /// + _16MHz20KBps, + + /// + /// 16MHz 10kBPS + /// + _16MHz10KBps, + + /// + /// 16MHz 5kBPS + /// + _16MHz5KBps, + + /// + /// 20MHz 1000kBPS + /// + _20MHz1000KBps, + + /// + /// 20MHz 500kBPS + /// + _20MHz500KBps, + + /// + /// 20MHz 250kBPS + /// + _20MHz250KBps, + + /// + /// 20MHz 200kBPS + /// + _20MHz200KBps, + + /// + /// 20MHz 125kBPS + /// + _20MHz125KBps, + + /// + /// 20MHz 100kBPS + /// + _20MHz100KBps, + + /// + /// 20MHz 80kBPS + /// + _20MHz80KBps, + + /// + /// 20MHz 50kBPS + /// + _20MHz50KBps, + + /// + /// 20MHz 40kBPS + /// + _20MHz40KBps, + } +} diff --git a/src/devices/Mcp25xxx/Mcp25xxx.cs b/src/devices/Mcp25xxx/Mcp25xxx.cs index b3db42b5b2..107ce940c4 100644 --- a/src/devices/Mcp25xxx/Mcp25xxx.cs +++ b/src/devices/Mcp25xxx/Mcp25xxx.cs @@ -4,7 +4,14 @@ using System; using System.Device.Gpio; using System.Device.Spi; +using System.IO; +using System.Threading; +using Iot.Device.Mcp25xxx.Models; using Iot.Device.Mcp25xxx.Register; +using Iot.Device.Mcp25xxx.Register.CanControl; +using Iot.Device.Mcp25xxx.Register.MessageReceive; +using Iot.Device.Mcp25xxx.Register.MessageTransmit; +using Iot.Device.Mcp25xxx.Tests.Register.CanControl; namespace Iot.Device.Mcp25xxx { @@ -13,6 +20,8 @@ namespace Iot.Device.Mcp25xxx /// public abstract class Mcp25xxx : IDisposable { + /// The MCP2515 implements three transmit buffers. Each of these buffers occupies 14 bytes of SRAM + private const int TransmitBufferMaxSize = 14; private readonly int _reset; private readonly int _tx0rts; private readonly int _tx1rts; @@ -241,6 +250,75 @@ public void Reset() _spiDevice.WriteByte((byte)InstructionFormat.Reset); } + /// + /// If RXB0 contains a valid message and another valid message is received, + /// an overflow error will not occur and the new message will be moved into RXB1 + /// + public void EnableRollover() + { + WriteByte( + new RxB0Ctrl( + false, + true, + false, + OperatingMode.TurnsMaskFiltersOff)); + } + + /// + /// The configuration registers (CNF1, CNF2, CNF3) control the bit timing for the CAN bus interface. + /// + /// CAN bus frequency and speed + public void SetBitrate(FrequencyAndSpeed frequencyAndSpeed) + { + var (cnf1Config, cnf2Config, cnf3Config) = McpBitrate.GetBitTimingConfiguration(frequencyAndSpeed); + WriteByte(Address.Cnf1, cnf1Config); + WriteByte(Address.Cnf2, cnf2Config); + WriteByte(Address.Cnf3, cnf3Config); + } + + /// + /// Set mode of operation + /// + /// type of operation Mode + public void SetMode(OperationMode operationMode) + { + WriteByte( + new CanCtrl( + CanCtrl.PinPrescaler.ClockDivideBy8, + true, + false, + false, + operationMode)); + } + + /// + /// Read arrived messages + /// + /// List of messages received + public ReceivedCanMessage[] ReadMessages() + { + var rxStatusResponse = RxStatus(); + + switch (rxStatusResponse.ReceivedMessage) + { + case RxStatusResponse.ReceivedMessageType.MessageInRxB0: + byte[] messageRxB0 = ReadRxBuffer(RxBufferAddressPointer.RxB0Sidh, TransmitBufferMaxSize); + return new[] { new ReceivedCanMessage(ReceiveBuffer.RxB0, messageRxB0) }; + case RxStatusResponse.ReceivedMessageType.MessageInRxB1: + byte[] messageRxB1 = ReadRxBuffer(RxBufferAddressPointer.RxB1Sidh, TransmitBufferMaxSize); + return new[] { new ReceivedCanMessage(ReceiveBuffer.RxB1, messageRxB1) }; + case RxStatusResponse.ReceivedMessageType.MessagesInBothBuffers: + var firstMessage = ReadRxBuffer(RxBufferAddressPointer.RxB0Sidh, TransmitBufferMaxSize); + var secondMessage = ReadRxBuffer(RxBufferAddressPointer.RxB1Sidh, TransmitBufferMaxSize); + return new[] { new ReceivedCanMessage(ReceiveBuffer.RxB0, firstMessage), new ReceivedCanMessage(ReceiveBuffer.RxB1, secondMessage) }; + case RxStatusResponse.ReceivedMessageType.NoRxMessage: + return Array.Empty(); + default: + throw new Exception( + $"Invalid value for {nameof(rxStatusResponse.ReceivedMessage)}: {rxStatusResponse.ReceivedMessage}."); + } + } + /// /// Reads data from the register beginning at the selected address. /// @@ -315,6 +393,159 @@ public void WriteByte(IRegister register) }); } + /// + /// Send message + /// + /// CAN message + public void SendMessage(SendingCanMessage message) + { + var txBuffer = GetEmptyTxBuffer(); + SendMessageFromBuffer(txBuffer, message); + const int tries = 10; + for (var i = 0; i < tries; i++) + { + if (IsMessageSend(txBuffer)) + { + return; + } + + Thread.Sleep(TimeSpan.FromMilliseconds(5)); + } + + AbortAllPendingTransmissions(); + throw new IOException($"Cannot Send: {message.Id}#{string.Join(";", message.Data)}"); + } + + /// + /// Get witch buffer empty now + /// + /// Buffer + public TransmitBuffer GetEmptyTxBuffer() + { + var readStatusResponse = ReadStatus(); + var tx0Full = readStatusResponse.HasFlag(ReadStatusResponse.Tx0Req); + var tx1Full = readStatusResponse.HasFlag(ReadStatusResponse.Tx1Req); + var tx2Full = readStatusResponse.HasFlag(ReadStatusResponse.Tx2Req); + + if (!tx0Full) + { + return TransmitBuffer.Tx0; + } + + if (!tx1Full) + { + return TransmitBuffer.Tx1; + } + + if (!tx2Full) + { + return TransmitBuffer.Tx2; + } + + return TransmitBuffer.None; + } + + /// + /// Send message from specific buffer + /// + /// Buffer + /// CAN message + public void SendMessageFromBuffer(TransmitBuffer transmitBuffer, SendingCanMessage message) + { + var (instructionsAddress, dataAddress) = GetInstructionsAddress(transmitBuffer); + var txBufferNumber = TxBxSidh.GetTxBufferNumber(instructionsAddress); + ReadOnlySpan buffer = stackalloc byte[5] + { + new TxBxSidh(txBufferNumber, message.Id[0]).ToByte(), + new TxBxSidl(txBufferNumber, message.Id[1]).ToByte(), + new TxBxEid8(txBufferNumber, message.Id[2]).ToByte(), + new TxBxEid0(txBufferNumber, message.Id[3]).ToByte(), + new TxBxDlc(txBufferNumber, message.Data.Length, false).ToByte() + }; + + Write(instructionsAddress, buffer); + Write(dataAddress, (byte[]?)message.Data); + SendFromBuffer(transmitBuffer); + } + + /// + /// Get instructions address from buffer + /// + /// Type of transmit buffer + /// Instructions for specific buffer + public Tuple GetInstructionsAddress(TransmitBuffer transmitBuffer) + { + return transmitBuffer switch + { + TransmitBuffer.Tx0 => new Tuple(Address.TxB0Sidh, Address.TxB0D0), + TransmitBuffer.Tx1 => new Tuple(Address.TxB1Sidh, Address.TxB1D0), + TransmitBuffer.Tx2 => new Tuple(Address.TxB2Sidh, Address.TxB2D0), + TransmitBuffer.None => throw new ArgumentException("Can not use this Tx buffer", nameof(transmitBuffer), null), + _ => throw new ArgumentOutOfRangeException(nameof(transmitBuffer), transmitBuffer, null) + }; + } + + /// + /// Command to mcp25xx to send bytes from specific buffer + /// + /// Type of transmit buffer + public void SendFromBuffer(TransmitBuffer transmitBuffer) + { + switch (transmitBuffer) + { + case TransmitBuffer.Tx0: + RequestToSend(true, false, false); + break; + case TransmitBuffer.Tx1: + RequestToSend(false, true, false); + break; + case TransmitBuffer.Tx2: + RequestToSend(false, false, true); + break; + case TransmitBuffer.None: + throw new ArgumentException("Can not use this Tx buffer", nameof(transmitBuffer), null); + default: + throw new ArgumentOutOfRangeException(nameof(transmitBuffer), transmitBuffer, null); + } + } + + /// + /// Check is buffer empty + /// + /// buffer type + /// + public bool IsMessageSend(TransmitBuffer transmitBuffer) + { + var readStatusResponse = ReadStatus(); + + switch (transmitBuffer) + { + case TransmitBuffer.Tx0 when readStatusResponse.HasFlag(ReadStatusResponse.Tx0If) && + !readStatusResponse.HasFlag(ReadStatusResponse.Tx0Req): + case TransmitBuffer.Tx1 when readStatusResponse.HasFlag(ReadStatusResponse.Tx1If) && + !readStatusResponse.HasFlag(ReadStatusResponse.Tx1Req): + case TransmitBuffer.Tx2 when readStatusResponse.HasFlag(ReadStatusResponse.Tx2If) && + !readStatusResponse.HasFlag(ReadStatusResponse.Tx2Req): + return true; + default: + return false; + } + } + + /// + /// Abort send all messages from buffers, buffers will be empty + /// + public void AbortAllPendingTransmissions() + { + WriteByte( + new CanCtrl( + CanCtrl.PinPrescaler.ClockDivideBy8, + true, + false, + true, + OperationMode.NormalOperation)); + } + /// /// Writes data to the register beginning at the selected address. /// diff --git a/src/devices/Mcp25xxx/Mcp25xxx.csproj b/src/devices/Mcp25xxx/Mcp25xxx.csproj index da9be4129e..a96ed41b05 100644 --- a/src/devices/Mcp25xxx/Mcp25xxx.csproj +++ b/src/devices/Mcp25xxx/Mcp25xxx.csproj @@ -4,13 +4,19 @@ false + + + + + + diff --git a/src/devices/Mcp25xxx/McpBitrate.cs b/src/devices/Mcp25xxx/McpBitrate.cs new file mode 100644 index 0000000000..142c1bb267 --- /dev/null +++ b/src/devices/Mcp25xxx/McpBitrate.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Iot.Device.Mcp25xxx +{ + /// + /// Bit Timing Configuration Registers + /// + public static class McpBitrate + { + private static readonly Dictionary> s_bitTimingConfiguration = new() + { + { FrequencyAndSpeed._8MHz1000KBps, new Tuple(0x00, 0x80, 0x80) }, + { FrequencyAndSpeed._8MHz500KBps, new Tuple(0x00, 0x90, 0x82) }, + { FrequencyAndSpeed._8MHz250KBps, new Tuple(0x00, 0xB1, 0x85) }, + { FrequencyAndSpeed._8MHz200KBps, new Tuple(0x00, 0xB4, 0x86) }, + { FrequencyAndSpeed._8MHz125KBps, new Tuple(0x01, 0xB1, 0x85) }, + { FrequencyAndSpeed._8MHz100KBps, new Tuple(0x01, 0xB4, 0x86) }, + { FrequencyAndSpeed._8MHz80KBps, new Tuple(0x01, 0xBF, 0x87) }, + { FrequencyAndSpeed._8MHz50KBps, new Tuple(0x03, 0xB4, 0x86) }, + { FrequencyAndSpeed._8MHz40KBps, new Tuple(0x03, 0xBF, 0x87) }, + { FrequencyAndSpeed._8MHz20KBps, new Tuple(0x07, 0xBF, 0x87) }, + { FrequencyAndSpeed._8MHz10KBps, new Tuple(0x0F, 0xBF, 0x87) }, + { FrequencyAndSpeed._8MHz5KBps, new Tuple(0x1F, 0xBF, 0x87) }, + { FrequencyAndSpeed._16MHz1000KBps, new Tuple(0x00, 0xD0, 0x82) }, + { FrequencyAndSpeed._16MHz500KBps, new Tuple(0x00, 0xF0, 0x86) }, + { FrequencyAndSpeed._16MHz250KBps, new Tuple(0x41, 0xF1, 0x85) }, + { FrequencyAndSpeed._16MHz200KBps, new Tuple(0x01, 0xFA, 0x87) }, + { FrequencyAndSpeed._16MHz125KBps, new Tuple(0x03, 0xF0, 0x86) }, + { FrequencyAndSpeed._16MHz100KBps, new Tuple(0x03, 0xFA, 0x87) }, + { FrequencyAndSpeed._16MHz80KBps, new Tuple(0x03, 0xFF, 0x87) }, + { FrequencyAndSpeed._16MHz50KBps, new Tuple(0x07, 0xFA, 0x87) }, + { FrequencyAndSpeed._16MHz40KBps, new Tuple(0x07, 0xFF, 0x87) }, + { FrequencyAndSpeed._16MHz20KBps, new Tuple(0x0F, 0xFF, 0x87) }, + { FrequencyAndSpeed._16MHz10KBps, new Tuple(0x1F, 0xFF, 0x87) }, + { FrequencyAndSpeed._16MHz5KBps, new Tuple(0x3F, 0xFF, 0x87) }, + { FrequencyAndSpeed._20MHz1000KBps, new Tuple(0x00, 0xD9, 0x82) }, + { FrequencyAndSpeed._20MHz500KBps, new Tuple(0x00, 0xFA, 0x87) }, + { FrequencyAndSpeed._20MHz250KBps, new Tuple(0x41, 0xFB, 0x86) }, + { FrequencyAndSpeed._20MHz200KBps, new Tuple(0x01, 0xFF, 0x87) }, + { FrequencyAndSpeed._20MHz125KBps, new Tuple(0x03, 0xFA, 0x87) }, + { FrequencyAndSpeed._20MHz100KBps, new Tuple(0x04, 0xFA, 0x87) }, + { FrequencyAndSpeed._20MHz80KBps, new Tuple(0x04, 0xFF, 0x87) }, + { FrequencyAndSpeed._20MHz50KBps, new Tuple(0x09, 0xFA, 0x87) }, + { FrequencyAndSpeed._20MHz40KBps, new Tuple(0x09, 0xFF, 0x87) } + }; + + /// + /// Get bit timing configuration for specific CAN Bus frequency and speed + /// + /// One of CAN Bus frequency and speed + /// The configuration for registers (CNF1, CNF2, CNF3) + public static Tuple GetBitTimingConfiguration(FrequencyAndSpeed frequencyAndSpeed) + { + return s_bitTimingConfiguration[frequencyAndSpeed]; + } + } +} diff --git a/src/devices/Mcp25xxx/Models/ReceivedCanMessage.cs b/src/devices/Mcp25xxx/Models/ReceivedCanMessage.cs new file mode 100644 index 0000000000..8f8562f2f2 --- /dev/null +++ b/src/devices/Mcp25xxx/Models/ReceivedCanMessage.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Linq; + +namespace Iot.Device.Mcp25xxx.Models +{ + /// + /// CAN bus message + /// + public class ReceivedCanMessage + { + /// + /// Buffer received this message + /// + public ReceiveBuffer Buffer { get; } + + /// + /// Received CAN message + /// + public byte[] RawData { get; } + + /// + /// Received CAN message constructor + /// + /// buffer received this message + /// data from buffer + public ReceivedCanMessage(ReceiveBuffer buffer, byte[] rawData) + { + Buffer = buffer; + RawData = rawData; + } + + /// + /// CAN message id + /// + /// message id + /// Raw data can contain not valid id + public byte[] GetId() + { + if (RawData.Length < 4) + { + throw new InvalidDataException($"Raw data size {RawData.Length} must be greater then 4 bytes."); + } + + return new[] { RawData[0], RawData[1], RawData[2], RawData[3] }; + } + + /// + /// CAN message data + /// + /// message data (max 8 bytes) + /// Raw data can contain not valid message + public byte[] GetData() + { + const int messageDataStartIndex = 5; + if (RawData.Length < messageDataStartIndex) + { + throw new InvalidDataException($"Raw data size {RawData.Length} must be greater then {messageDataStartIndex} bytes."); + } + + var messageLength = RawData[4] & 0x0F; + return RawData.Skip(messageDataStartIndex).Take(messageLength).ToArray(); + } + } +} diff --git a/src/devices/Mcp25xxx/Models/SendingCanMessage.cs b/src/devices/Mcp25xxx/Models/SendingCanMessage.cs new file mode 100644 index 0000000000..a672203d24 --- /dev/null +++ b/src/devices/Mcp25xxx/Models/SendingCanMessage.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Iot.Device.Mcp25xxx.Models +{ + /// + /// CAN bus message + /// + public class SendingCanMessage + { + /// + /// Four bytes CAN id + /// + public byte[] Id { get; } + + /// + /// CAN data (max 8 bytes) + /// + public byte[] Data { get; } + + private SendingCanMessage(byte[] id, byte[] data) + { + Id = id; + Data = data; + } + + /// + /// Create new standard CAN message + /// + /// Two bytes id + /// message data max 8 bytes + public static SendingCanMessage CreateStandard(byte[] shortId, byte[] data) + { + if (shortId.Length != 2) + { + throw new ArgumentException($"Id size {shortId.Length} must be 2 bytes.", nameof(shortId)); + } + + if (data.Length > 8) + { + throw new ArgumentException($"Data size {data.Length} more than 8 bytes.", nameof(data)); + } + + var id = new byte[] { shortId[0], shortId[1], 0, 0 }; + return new SendingCanMessage(id, data); + } + + /// + /// Create new extended CAN message + /// + /// Four bytes id + /// message data max 8 bytes + public static SendingCanMessage CreateExtended(byte[] id, byte[] data) + { + if (id.Length != 4) + { + throw new ArgumentException($"Id size {id.Length} must be 4 bytes.", nameof(id)); + } + + if (data.Length > 8) + { + throw new ArgumentException($"Data size {data.Length} more than 8 bytes.", nameof(data)); + } + + return new SendingCanMessage(id, data); + } + } +} diff --git a/src/devices/Mcp25xxx/README.md b/src/devices/Mcp25xxx/README.md index 8f959b9654..0ac35999a4 100644 --- a/src/devices/Mcp25xxx/README.md +++ b/src/devices/Mcp25xxx/README.md @@ -80,31 +80,34 @@ Console.WriteLine($"Tx2If: {readStatusResponse.HasFlag(ReadStatusResponse.Tx2If) You can transmit a message like this: ```csharp -Console.WriteLine("Transmit Message"); +Console.WriteLine("Send simple message"); +const int id = 1; +var message = new[] { (byte)1 }; +var twoByteId = new Tuple((id >> 3), (id << 5)); +mcp25xxx.SendMessage(twoByteId, message); +``` -mcp25xxx.WriteByte( - new CanCtrl(CanCtrl.PinPrescaler.ClockDivideBy8, - false, - false, - false, - OperationMode.NormalOperation)); +### Read messages from all buffers -byte[] data = new byte[] { 0b0000_0001, 0b0010_0011, 0b0100_0101, 0b0110_0111, 0b1000_1001 }; +You can save all message from buffer to memmory like this: -mcp25xxx.Write( - Address.TxB0Sidh, - new byte[] +```csharp +ConcurrentQueue readBuffer = new(); +Console.WriteLine("Start read from buffer mcp25xx buffer to memory buffer"); +while (!ct.IsCancellationRequested) +{ + var messages = mcp25xxx.ReadMessages(10); + foreach (var message in messages) { - new TxBxSidh(0, 0b0000_1001).ToByte(), new TxBxSidl(0, 0b001, false, 0b00).ToByte(), - new TxBxEid8(0, 0b0000_0000).ToByte(), new TxBxEid0(0, 0b0000_0000).ToByte(), - new TxBxDlc(0, data.Length, false).ToByte() - }); + readBuffer.Enqueue(message); + } +} +``` -mcp25xxx.Write(Address.TxB0D0, data); +You can read messaged from memmory like this: -// Send with TxB0 buffer. -mcp25xxx.RequestToSend(true, false, false); -cp25xxx.RequestToSend(false, false, true); +```csharp +var messageOrNull = readBuffer.TryDequeue(out var bytes) ? bytes : null; ``` **Note**: You will find detailed way of using this binding in the [sample file](samples) diff --git a/src/devices/Mcp25xxx/ReceiveBuffer.cs b/src/devices/Mcp25xxx/ReceiveBuffer.cs new file mode 100644 index 0000000000..2ccfa36900 --- /dev/null +++ b/src/devices/Mcp25xxx/ReceiveBuffer.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Iot.Device.Mcp25xxx +{ + /// + /// Receive buffers + /// + public enum ReceiveBuffer + { + /// + /// Receive buffer 0 + /// + RxB0 = 0, + + /// + /// Receive buffer 1 + /// + RxB1 = 1, + + /// + /// No one transmit buffer + /// + None = 3 + } +} diff --git a/src/devices/Mcp25xxx/TransmitBuffer.cs b/src/devices/Mcp25xxx/TransmitBuffer.cs new file mode 100644 index 0000000000..cef9de1f81 --- /dev/null +++ b/src/devices/Mcp25xxx/TransmitBuffer.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Iot.Device.Mcp25xxx +{ + /// + /// Transmit buffers + /// + public enum TransmitBuffer + { + /// + /// Transmit buffer 0 + /// + Tx0 = 0, + + /// + /// Transmit buffer 1 + /// + Tx1 = 1, + + /// + /// Transmit buffer 2 + /// + Tx2 = 2, + + /// + /// No one transmit buffer + /// + None = 3 + } +} diff --git a/src/devices/Mcp25xxx/samples/Program.cs b/src/devices/Mcp25xxx/samples/Program.cs index 92839b4175..7112df0d24 100644 --- a/src/devices/Mcp25xxx/samples/Program.cs +++ b/src/devices/Mcp25xxx/samples/Program.cs @@ -2,8 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Concurrent; using System.Device.Spi; +using System.Threading; using Iot.Device.Mcp25xxx; +using Iot.Device.Mcp25xxx.Models; using Iot.Device.Mcp25xxx.Register; using Iot.Device.Mcp25xxx.Register.AcceptanceFilter; using Iot.Device.Mcp25xxx.Register.BitTimeConfiguration; @@ -14,10 +17,14 @@ using Iot.Device.Mcp25xxx.Register.MessageReceive; using Iot.Device.Mcp25xxx.Register.MessageTransmit; -Console.WriteLine("Hello Mcp25xxx Sample!"); +ConcurrentQueue readBuffer = new(); +Console.WriteLine("Hello Mcp25xxx Sample!"); using Mcp25xxx mcp25xxx = GetMcp25xxxDevice(); Reset(mcp25xxx); +SetBitrate(mcp25xxx); +EnableRollover(mcp25xxx); +SetNormalMode(mcp25xxx); // ReadAllRegisters(mcp25xxx); ReadAllRegistersWithDetails(mcp25xxx); @@ -31,6 +38,9 @@ // TransmitMessage(mcp25xxx); // LoopbackMode(mcp25xxx); // ReadAllRegisters(mcp25xxx); +// Task.Run(() => ReadToBufferLoop(mcp25xxx, CancellationToken.None), CancellationToken.None); +SendMessage(mcp25xxx); + // Methods Mcp25xxx GetMcp25xxxDevice() { @@ -45,6 +55,52 @@ void Reset(Mcp25xxx mcp25xxx) mcp25xxx.Reset(); } +void SetBitrate(Mcp25xxx mcp25xxx) +{ + Console.WriteLine("Set bitrate 16MHz 500kBPS"); + mcp25xxx.SetBitrate(FrequencyAndSpeed._16MHz500KBps); +} + +void EnableRollover(Mcp25xxx mcp25xxx) +{ + Console.WriteLine("Enable rollover to second buffer"); + mcp25xxx.EnableRollover(); +} + +void SetNormalMode(Mcp25xxx mcp25xxx) +{ + Console.WriteLine("Set normal work mode"); + mcp25xxx.SetMode(OperationMode.NormalOperation); +} + +void ReadToBufferLoop(Mcp25xxx mcp25xxx, CancellationToken ct) +{ + Console.WriteLine("Start read from buffer mcp25xx buffer to memory buffer"); + while (!ct.IsCancellationRequested) + { + var messages = mcp25xxx.ReadMessages(); + foreach (var message in messages) + { + readBuffer.Enqueue(message); + } + } +} + +ReceivedCanMessage? GetBufferMessageOrNull() +{ + return readBuffer.TryDequeue(out var message) ? message : null; +} + +void SendMessage(Mcp25xxx mcp25xxx) +{ + Console.WriteLine("Send simple message"); + const int id = 1; + var messageData = new[] { (byte)1 }; + var twoByteId = new byte[] { id >> 3, id << 5 }; + var message = SendingCanMessage.CreateStandard(twoByteId, messageData); + mcp25xxx.SendMessage(message); +} + void ReadAllRegisters(Mcp25xxx mcp25xxx) { Console.WriteLine("Read Instruction for All Registers"); diff --git a/src/devices/Mcp25xxx/tests/Mcp25xxxSpiDevice.cs b/src/devices/Mcp25xxx/tests/Mcp25xxxSpiDevice.cs index 0dbcbfe33e..f6d880c732 100644 --- a/src/devices/Mcp25xxx/tests/Mcp25xxxSpiDevice.cs +++ b/src/devices/Mcp25xxx/tests/Mcp25xxxSpiDevice.cs @@ -8,10 +8,12 @@ namespace Iot.Device.Mcp25xxx.Tests { public class Mcp25xxxSpiDevice : SpiDevice { + public event Action? TransferCompleted; + public override SpiConnectionSettings ConnectionSettings => throw new NotImplementedException(); - public byte[]? LastReadBuffer { get; set; } + public byte[]? NextReadBuffer { get; set; } - public byte LastReadByte { get; set; } + public byte NextReadByte { get; set; } public byte[]? LastWriteBuffer { get; private set; } @@ -19,15 +21,16 @@ public class Mcp25xxxSpiDevice : SpiDevice public override void Read(Span buffer) { - LastReadBuffer = buffer.ToArray(); + NextReadBuffer = buffer.ToArray(); } - public override byte ReadByte() => LastReadByte; + public override byte ReadByte() => NextReadByte; public override void TransferFullDuplex(ReadOnlySpan writeBuffer, Span readBuffer) { LastWriteBuffer = writeBuffer.ToArray(); - LastReadBuffer = readBuffer.ToArray(); + NextReadBuffer.CopyTo(readBuffer); + TransferCompleted?.Invoke(); } public override void Write(ReadOnlySpan buffer) diff --git a/src/devices/Mcp25xxx/tests/Mcp25xxxTests.cs b/src/devices/Mcp25xxx/tests/Mcp25xxxTests.cs index 36265933c4..551262590e 100644 --- a/src/devices/Mcp25xxx/tests/Mcp25xxxTests.cs +++ b/src/devices/Mcp25xxx/tests/Mcp25xxxTests.cs @@ -1,7 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Device.Spi; using Iot.Device.Mcp25xxx.Register; +using Iot.Device.Mcp25xxx.Tests.Register.CanControl; +using Moq; using Xunit; namespace Iot.Device.Mcp25xxx.Tests @@ -23,11 +27,16 @@ public void Send_Reset_Instruction() public void Send_Read_Instruction_By_Address(Address address) { byte[] expectedWriteBuffer = new byte[] { 0b0000_0011, (byte)address, 0b0000_0000 }; + byte[] reply = new byte[] + { + 0, 0, 0xff + }; var mcp25xxxSpiDevice = new Mcp25xxxSpiDevice(); + mcp25xxxSpiDevice.NextReadBuffer = reply; Mcp25xxx mcp25xxx = new Mcp25625(mcp25xxxSpiDevice); - mcp25xxx.Read(address); + byte b = mcp25xxx.Read(address); Assert.Equal(expectedWriteBuffer, mcp25xxxSpiDevice?.LastWriteBuffer); - Assert.Equal(3, mcp25xxxSpiDevice?.LastReadBuffer?.Length); + Assert.Equal(0xff, b); } [Theory] @@ -41,9 +50,9 @@ public void Send_ReadRxBuffer_Instruction(byte instructionFormat, RxBufferAddres expectedWriteBuffer[0] = instructionFormat; var mcp25xxxSpiDevice = new Mcp25xxxSpiDevice(); Mcp25xxx mcp25xxx = new Mcp25625(mcp25xxxSpiDevice); + mcp25xxxSpiDevice.NextReadBuffer = new byte[byteCount]; mcp25xxx.ReadRxBuffer(addressPointer, byteCount); Assert.Equal(expectedWriteBuffer, mcp25xxxSpiDevice.LastWriteBuffer); - Assert.Equal(byteCount, mcp25xxxSpiDevice.LastReadBuffer?.Length - 1); } [Theory] @@ -95,20 +104,32 @@ public void Send_ReadStatus_Instruction() byte[] expectedWriteBuffer = new byte[] { 0b1010_0000, 0b0000_0000 }; var mcp25xxxSpiDevice = new Mcp25xxxSpiDevice(); Mcp25xxx mcp25xxx = new Mcp25625(mcp25xxxSpiDevice); - mcp25xxx.ReadStatus(); + mcp25xxxSpiDevice.NextReadBuffer = new byte[] + { + 0, 3 + }; + + var response = mcp25xxx.ReadStatus(); + Assert.Equal(ReadStatusResponse.Rx0If | ReadStatusResponse.Rx1If, response); Assert.Equal(expectedWriteBuffer, mcp25xxxSpiDevice.LastWriteBuffer); - Assert.Equal(1, mcp25xxxSpiDevice.LastReadBuffer?.Length - 1); } [Fact] public void Send_RxStatus_Instruction() { byte[] expectedWriteBuffer = new byte[] { 0b1011_0000, 0b0000_0000 }; + byte[] reply = new byte[] + { + 0, 0xC2 + }; var mcp25xxxSpiDevice = new Mcp25xxxSpiDevice(); + mcp25xxxSpiDevice.NextReadBuffer = reply; Mcp25xxx mcp25xxx = new Mcp25625(mcp25xxxSpiDevice); - mcp25xxx.RxStatus(); + var status = mcp25xxx.RxStatus(); Assert.Equal(expectedWriteBuffer, mcp25xxxSpiDevice.LastWriteBuffer); - Assert.Equal(1, mcp25xxxSpiDevice.LastReadBuffer?.Length - 1); + Assert.Equal(RxStatusResponse.MessageReceivedType.StandardDataFrame, status.MessageTypeReceived); + Assert.Equal(RxStatusResponse.ReceivedMessageType.MessagesInBothBuffers, status.ReceivedMessage); + Assert.Equal(RxStatusResponse.FilterMatchType.RxF2, status.FilterMatch); } [Theory] @@ -122,5 +143,62 @@ public void Send_BitModify_Instruction(Address address, byte mask, byte value) mcp25xxx.BitModify(address, mask, value); Assert.Equal(expectedWriteBuffer, mcp25xxxSpiDevice.LastWriteBuffer); } + + [Fact] + public void Send_EnableRollover_Instruction() + { + byte[] expectedWriteBuffer = { 0b0000_0010, (byte)Address.RxB0Ctrl, 0b0110_0110 }; + var mcp25xxxSpiDevice = new Mcp25xxxSpiDevice(); + Mcp25xxx mcp25xxx = new Mcp25625(mcp25xxxSpiDevice); + mcp25xxx.EnableRollover(); + Assert.Equal(expectedWriteBuffer, mcp25xxxSpiDevice.LastWriteBuffer); + } + + [Fact] + public void Send_SetBitrate_Instruction() + { + byte[] lastExpectedWriteBuffer = { 0b0000_0010, (byte)Address.Cnf3, 0x86 }; + var mcp25xxxSpiDevice = new Mcp25xxxSpiDevice(); + Mcp25xxx mcp25xxx = new Mcp25625(mcp25xxxSpiDevice); + mcp25xxx.SetBitrate(FrequencyAndSpeed._16MHz500KBps); + Assert.Equal(lastExpectedWriteBuffer, mcp25xxxSpiDevice.LastWriteBuffer); + } + + [Fact] + public void Send_SetMode_Instruction() + { + byte[] lastExpectedWriteBuffer = { 0b0000_0010, (byte)Address.CanCtrl, 0b0000_0111 }; + var mcp25xxxSpiDevice = new Mcp25xxxSpiDevice(); + Mcp25xxx mcp25xxx = new Mcp25625(mcp25xxxSpiDevice); + mcp25xxx.SetMode(OperationMode.NormalOperation); + Assert.Equal(lastExpectedWriteBuffer, mcp25xxxSpiDevice.LastWriteBuffer); + } + + [Fact] + public void ReceiveMessagesSuccess() + { + var mcp25xxxSpiDevice = new Mcp25xxxSpiDevice(); + mcp25xxxSpiDevice.TransferCompleted += () => + { + // The second reply; + mcp25xxxSpiDevice.NextReadBuffer = new byte[] + { + 0 /* dummy */, 0xb, 0xa, 0, 0, 0x4 /* Msg length*/, 1, 2, 3, 4 + }; + }; + + byte[] reply = new byte[] + { + 0, 0x42 + }; + Mcp25xxx mcp25xxx = new Mcp25625(mcp25xxxSpiDevice); + mcp25xxxSpiDevice.NextReadBuffer = reply; + var msg = mcp25xxx.ReadMessages(); + Assert.Single(msg); + + Assert.Equal(0xa0b, BitConverter.ToInt32(msg[0].GetId(), 0)); + Assert.Equal(4, msg[0].GetData().Length); + Assert.Equal(1, msg[0].GetData()[0]); + } } } diff --git a/src/devices/Mcp25xxx/tests/Models/ReceivedCanMessageTests.cs b/src/devices/Mcp25xxx/tests/Models/ReceivedCanMessageTests.cs new file mode 100644 index 0000000000..7299e129f2 --- /dev/null +++ b/src/devices/Mcp25xxx/tests/Models/ReceivedCanMessageTests.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using Iot.Device.Mcp25xxx.Models; +using Xunit; + +namespace Iot.Device.Mcp25xxx.Tests.Models +{ + public class ReceivedCanMessageTests + { + [Theory] + [InlineData(new byte[] { 0, 1, 2, 3 }, new byte[] { 0, 1, 2, 3 })] + [InlineData(new byte[] { 4, 3, 2, 1, 0 }, new byte[] { 4, 3, 2, 1 })] + [InlineData(new byte[] { 10, 11, 12, 13, 14, 15 }, new byte[] { 10, 11, 12, 13 })] + public void Get_Message_Id(byte[] rawData, byte[] id) + { + var message = new ReceivedCanMessage(ReceiveBuffer.RxB0, rawData); + Assert.Equal(id, message.GetId()); + } + + [Theory] + [InlineData(new byte[0])] + [InlineData(new byte[] { 1 })] + [InlineData(new byte[] { 1, 2 })] + [InlineData(new byte[] { 1, 2, 3 })] + public void When_Invalid_RawData_Get_Message_Id_Thrown(byte[] rawData) + { + var message = new ReceivedCanMessage(ReceiveBuffer.RxB0, rawData); + Assert.Throws(() => message.GetId()); + } + + [Theory] + [InlineData(new byte[] { 0, 1, 2, 3, 0 }, new byte[0])] + [InlineData(new byte[] { 0, 1, 2, 3, 1, 14 }, new byte[] { 14 })] + [InlineData(new byte[] { 0, 1, 2, 3, 2, 14, 15 }, new byte[] { 14, 15 })] + [InlineData(new byte[] { 0, 1, 2, 3, 3, 14, 15, 16 }, new byte[] { 14, 15, 16 })] + [InlineData(new byte[] { 0, 1, 2, 3, 4, 14, 15, 16, 17 }, new byte[] { 14, 15, 16, 17 })] + [InlineData(new byte[] { 0, 1, 2, 3, 5, 14, 15, 16, 17, 18 }, new byte[] { 14, 15, 16, 17, 18 })] + [InlineData(new byte[] { 0, 1, 2, 3, 6, 14, 15, 16, 17, 18, 19 }, new byte[] { 14, 15, 16, 17, 18, 19 })] + [InlineData(new byte[] { 0, 1, 2, 3, 7, 14, 15, 16, 17, 18, 19, 20 }, new byte[] { 14, 15, 16, 17, 18, 19, 20 })] + [InlineData(new byte[] { 0, 1, 2, 3, 8, 14, 15, 16, 17, 18, 19, 20, 21 }, new byte[] { 14, 15, 16, 17, 18, 19, 20, 21 })] + public void Get_Message_Data(byte[] rawData, byte[] id) + { + var message = new ReceivedCanMessage(ReceiveBuffer.RxB0, rawData); + Assert.Equal(id, message.GetData()); + } + + [Theory] + [InlineData(new byte[0])] + [InlineData(new byte[] { 1 })] + [InlineData(new byte[] { 2, 3 })] + [InlineData(new byte[] { 1, 2, 3 })] + [InlineData(new byte[] { 1, 2, 3, 4 })] + public void When_Invalid_RawData_Get_Message_Data_Thrown(byte[] rawData) + { + var message = new ReceivedCanMessage(ReceiveBuffer.RxB0, rawData); + Assert.Throws(() => message.GetData()); + } + + } +} diff --git a/src/devices/Mcp25xxx/tests/Models/SendingCanMessageTests.cs b/src/devices/Mcp25xxx/tests/Models/SendingCanMessageTests.cs new file mode 100644 index 0000000000..6f91282d12 --- /dev/null +++ b/src/devices/Mcp25xxx/tests/Models/SendingCanMessageTests.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Iot.Device.Mcp25xxx.Models; +using Xunit; + +namespace Iot.Device.Mcp25xxx.Tests.Models +{ + public class SendingCanMessageTests + { + [Theory] + [InlineData(new byte[] { 1, 2 }, new byte[] { 1, 2, 0, 0 })] + [InlineData(new byte[] { 3, 4 }, new byte[] { 3, 4, 0, 0 })] + public void Create_Standard_Message_Id(byte[] id, byte[] resultId) + { + var message = SendingCanMessage.CreateStandard(id, Array.Empty()); + Assert.Equal(resultId, message.Id); + } + + [Theory] + [InlineData(new byte[0])] + [InlineData(new byte[] { 1 })] + [InlineData(new byte[] { 1, 2, 3 })] + [InlineData(new byte[] { 1, 2, 3, 4 })] + public void When_Invalid_Id_Create_Standard_Message_Thrown(byte[] id) + { + Assert.Throws(() => SendingCanMessage.CreateStandard(id, Array.Empty())); + } + + [Theory] + [InlineData(new byte[0], new byte[0])] + [InlineData(new byte[] { 1 }, new byte[] { 1 })] + [InlineData(new byte[] { 1, 2 }, new byte[] { 1, 2 })] + [InlineData(new byte[] { 1, 2, 3, 4 }, new byte[] { 1, 2, 3, 4 })] + [InlineData(new byte[] { 1, 2, 3, 4, 5, 6 }, new byte[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 })] + public void Create_Standard_Message_With_Data(byte[] data, byte[] resultData) + { + var message = SendingCanMessage.CreateStandard(new byte[2], data); + Assert.Equal(resultData, message.Data); + } + + [Theory] + [InlineData(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })] + [InlineData(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 })] + public void When_Invalid_Data_Create_Standard_Message_Thrown(byte[] data) + { + Assert.Throws(() => SendingCanMessage.CreateStandard(new byte[2], data)); + } + + [Theory] + [InlineData(new byte[] { 1, 2, 3, 4 }, new byte[] { 1, 2, 3, 4 })] + [InlineData(new byte[] { 3, 4, 5, 6 }, new byte[] { 3, 4, 5, 6 })] + public void Create_Extended_Message_Id(byte[] id, byte[] resultId) + { + var message = SendingCanMessage.CreateExtended(id, Array.Empty()); + Assert.Equal(resultId, message.Id); + } + + [Theory] + [InlineData(new byte[0])] + [InlineData(new byte[] { 1 })] + [InlineData(new byte[] { 1, 2 })] + [InlineData(new byte[] { 1, 2, 3 })] + [InlineData(new byte[] { 1, 2, 3, 4, 5 })] + public void When_Invalid_Id_Create_Extended_Message_Thrown(byte[] id) + { + Assert.Throws(() => SendingCanMessage.CreateExtended(id, Array.Empty())); + } + + [Theory] + [InlineData(new byte[0], new byte[0])] + [InlineData(new byte[] { 1 }, new byte[] { 1 })] + [InlineData(new byte[] { 1, 2 }, new byte[] { 1, 2 })] + [InlineData(new byte[] { 1, 2, 3, 4 }, new byte[] { 1, 2, 3, 4 })] + [InlineData(new byte[] { 1, 2, 3, 4, 5, 6 }, new byte[] { 1, 2, 3, 4, 5, 6 })] + [InlineData(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 })] + public void Create_Extended_Message_With_Data(byte[] data, byte[] resultData) + { + var message = SendingCanMessage.CreateStandard(new byte[2], data); + Assert.Equal(resultData, message.Data); + } + + [Theory] + [InlineData(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 })] + [InlineData(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 })] + public void When_Invalid_Data_Create_Extended_Message_Thrown(byte[] data) + { + Assert.Throws(() => SendingCanMessage.CreateStandard(new byte[2], data)); + } + } +}