Skip to content

Commit 3982d38

Browse files
authored
New Binding: Ina236 (#2459)
* New Binding for Ina236 with many bells and whistles * Add generic helper classes to simulate I2C devices Generic implementation is difficult, one problem is the different argument sizes and the fact that the endianess on the bus may change.
1 parent 90d8965 commit 3982d38

20 files changed

Lines changed: 1186 additions & 103 deletions

src/devices/Common/Common.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<ItemGroup>
1313
<Compile Include="ClassVisibility.cs" />
1414
<Compile Include="MathExtensions.cs" />
15+
<Compile Include="System\Device\I2c\I2cSimulatedDeviceBase.cs" />
1516
<Compile Include="Iot/Device/Common/*.cs" />
1617
<Compile Include="Iot/Device/Graphics/*.cs" />
1718
<Compile Include="Iot/Device/Multiplexing/*.cs" />
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Globalization;
6+
using System.IO;
7+
using System.Numerics;
8+
9+
namespace System.Device.I2c;
10+
11+
/// <summary>
12+
/// This class can be used to create a simulated I2C device.
13+
/// Derive from it and implement the <see cref="Write"/> and <see cref="Read"/> commands
14+
/// to behave as expected.
15+
/// Can also serve as base for a testing mock.
16+
/// </summary>
17+
public abstract class I2cSimulatedDeviceBase : I2cDevice
18+
{
19+
private bool _disposed;
20+
private Dictionary<byte, RegisterBase> _registerMap;
21+
private byte _currentRegister;
22+
23+
/// <summary>
24+
/// Default constructor
25+
/// </summary>
26+
/// <param name="settings">The connection settings for this device.</param>
27+
public I2cSimulatedDeviceBase(I2cConnectionSettings settings)
28+
{
29+
ConnectionSettings = settings;
30+
_registerMap = new Dictionary<byte, RegisterBase>();
31+
_disposed = false;
32+
_currentRegister = 0;
33+
}
34+
35+
/// <summary>
36+
/// The registermap of this device.
37+
/// This should only be accessed from a derived class, except for test purposes.
38+
/// </summary>
39+
public Dictionary<byte, RegisterBase> RegisterMap => _registerMap;
40+
41+
/// <summary>
42+
/// The active connection settings
43+
/// </summary>
44+
public override I2cConnectionSettings ConnectionSettings { get; }
45+
46+
/// <summary>
47+
/// The active register.
48+
/// Can be set to mimic some non-standard behavior of setting a register (or if reading increases
49+
/// the register pointer, which is the case on some chips)
50+
/// </summary>
51+
protected byte CurrentRegister
52+
{
53+
get
54+
{
55+
return _currentRegister;
56+
}
57+
set
58+
{
59+
_currentRegister = value;
60+
}
61+
}
62+
63+
/// <summary>
64+
/// Reads a byte from the bus
65+
/// </summary>
66+
/// <returns></returns>
67+
/// <exception cref="ObjectDisposedException">The instance is disposed already</exception>
68+
/// <exception cref="IOException"></exception>
69+
public override byte ReadByte()
70+
{
71+
if (_disposed)
72+
{
73+
throw new ObjectDisposedException("This instance is disposed");
74+
}
75+
76+
byte[] buffer = new byte[1];
77+
WriteRead([], buffer);
78+
79+
return buffer[0];
80+
}
81+
82+
/// <summary>
83+
/// This method implements the read operation from the device.
84+
/// </summary>
85+
/// <param name="inputBuffer">Buffer with input data to the device, buffer[0] is usually the command byte</param>
86+
/// <param name="outputBuffer">The return data from the device</param>
87+
/// <remarks>This doesn't use <see cref="Span{T}"/> as argument type to be mockable. Be sure
88+
/// to use this method in mocks, not any that take <see cref="Span{T}"/> or <see cref="Memory{T}"/>, as that
89+
/// will cause runtime exceptions</remarks>
90+
public abstract void WriteRead(byte[] inputBuffer, byte[] outputBuffer);
91+
92+
/// <inheritdoc />
93+
public override void Read(Span<byte> buffer)
94+
{
95+
byte[] buffer2 = new byte[buffer.Length];
96+
WriteRead([], buffer2);
97+
buffer2.CopyTo(buffer);
98+
}
99+
100+
/// <inheritdoc />
101+
public override void WriteByte(byte value)
102+
{
103+
WriteRead([value], []);
104+
}
105+
106+
/// <inheritdoc />
107+
public override void Write(ReadOnlySpan<byte> buffer)
108+
{
109+
WriteRead(buffer.ToArray(), []);
110+
}
111+
112+
/// <inheritdoc />
113+
public override void WriteRead(ReadOnlySpan<byte> writeBuffer, Span<byte> readBuffer)
114+
{
115+
byte[] outBuffer = new byte[readBuffer.Length];
116+
WriteRead(writeBuffer.ToArray(), outBuffer);
117+
outBuffer.CopyTo(readBuffer);
118+
}
119+
120+
/// <inheritdoc />
121+
protected override void Dispose(bool disposing)
122+
{
123+
_disposed = true;
124+
base.Dispose(disposing);
125+
}
126+
127+
/// <inheritdoc />
128+
public override ComponentInformation QueryComponentInformation()
129+
{
130+
var self = new ComponentInformation(this, "Simulated I2C Device");
131+
self.Properties["BusNo"] = ConnectionSettings.BusId.ToString(CultureInfo.InvariantCulture);
132+
self.Properties["DeviceAddress"] = $"0x{ConnectionSettings.DeviceAddress:x2}";
133+
return self;
134+
}
135+
136+
/// <summary>
137+
/// Base class for generic register access
138+
/// </summary>
139+
public abstract record class RegisterBase : IComparable
140+
{
141+
/// <summary>
142+
/// Writes the register, regardless of its actual type
143+
/// </summary>
144+
/// <param name="value">The value to write</param>
145+
public abstract void WriteRegister(int value);
146+
147+
/// <summary>
148+
/// Reads the register value regardless of its actual type
149+
/// </summary>
150+
/// <returns>The register value, sign-extended to int</returns>
151+
public abstract int ReadRegister();
152+
153+
/// <inheritdoc />
154+
public abstract int CompareTo(object? obj);
155+
}
156+
157+
/// <summary>
158+
/// Represents a register value
159+
/// </summary>
160+
/// <typeparam name="T">Size of the register, usually byte or int</typeparam>
161+
public record class Register<T> : RegisterBase
162+
where T : struct, IEquatable<T>, INumber<T>, IComparable
163+
{
164+
/// <summary>
165+
/// Event that is raised when the register is written
166+
/// </summary>
167+
private readonly Func<T, T>? _registerUpdateHandler;
168+
169+
/// <summary>
170+
/// Event that is raised to read the register. Gets the internal value of the register
171+
/// and returns the value the client should see (e.g a random measurement value)
172+
/// </summary>
173+
private readonly Func<T, T>? _registerReadHandler;
174+
175+
private T _value;
176+
177+
/// <summary>
178+
/// Create a new register
179+
/// </summary>
180+
public Register()
181+
: this(default(T))
182+
{
183+
}
184+
185+
/// <summary>
186+
/// Creates a new register
187+
/// </summary>
188+
/// <param name="initialValue">The initial (power-on-reset) value of the register</param>
189+
public Register(T initialValue)
190+
{
191+
_value = initialValue;
192+
}
193+
194+
/// <summary>
195+
/// Creates a new register with handlers
196+
/// </summary>
197+
/// <param name="initialValue">The initial value of the register at power-up</param>
198+
/// <param name="updateHandler">A handler for a register write. Can be null.</param>
199+
/// <param name="readHandler">A handler for a register read. Can be null.</param>
200+
public Register(T initialValue, Func<T, T>? updateHandler, Func<T, T>? readHandler)
201+
{
202+
_value = initialValue;
203+
_registerUpdateHandler = updateHandler;
204+
_registerReadHandler = readHandler;
205+
}
206+
207+
/// <summary>
208+
/// The current value of the register
209+
/// </summary>
210+
public T Value
211+
{
212+
get
213+
{
214+
if (_registerReadHandler != null)
215+
{
216+
return _registerReadHandler(_value);
217+
}
218+
219+
return _value;
220+
}
221+
set
222+
{
223+
if (_registerUpdateHandler != null)
224+
{
225+
_value = _registerUpdateHandler(value);
226+
return;
227+
}
228+
229+
_value = value;
230+
}
231+
}
232+
233+
/// <inheritdoc />
234+
public override void WriteRegister(int value)
235+
{
236+
Value = T.CreateChecked(value);
237+
}
238+
239+
/// <inheritdoc />
240+
public override int ReadRegister()
241+
{
242+
return int.CreateChecked(Value);
243+
}
244+
245+
/// <inheritdoc />
246+
public override int CompareTo(object? obj)
247+
{
248+
if (obj == null)
249+
{
250+
return 1;
251+
}
252+
253+
if (obj is Register<T> t1)
254+
{
255+
return _value.CompareTo(t1._value);
256+
}
257+
258+
throw new ArgumentException("These types can't be compared");
259+
}
260+
}
261+
}

0 commit comments

Comments
 (0)