-
-
Notifications
You must be signed in to change notification settings - Fork 110
/
Tcs3472x.cs
270 lines (243 loc) · 9.3 KB
/
Tcs3472x.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// 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.Buffers.Binary;
using System.Device.I2c;
using System.Device.Model;
using System.Drawing;
using System.Threading;
namespace Iot.Device.Tcs3472x
{
/// <summary>
/// Tcs3472x - color sensor.
/// </summary>
[Interface("Tcs3472x - color sensor")]
public class Tcs3472x : IDisposable
{
/// <summary>
/// Default I2C address for TCS3472x familly.
/// </summary>
public const byte DefaultI2cAddress = 0x29;
private I2cDevice _i2cDevice;
private byte _integrationTimeByte;
private double _integrationTime;
private bool _isLongTime;
private bool _shouldDispose;
private Gain _gain;
/// <summary>
/// Gets or sets the time to wait for the sensor to read the data
/// Minimum time is 0.0024 s
/// Maximum time is 7.4 s
/// Be aware that it is not a linear function.
/// </summary>
[Property]
public double IntegrationTime
{
get => _integrationTime;
set
{
_integrationTime = value;
SetIntegrationTime(_integrationTime);
}
}
/// <summary>
/// Gets or sets the gain.
/// </summary>
[Property]
public Gain Gain
{
get => _gain;
set
{
_gain = value;
WriteRegister(Registers.CONTROL, (byte)_gain);
}
}
/// <summary>
/// Gets the type of sensor.
/// </summary>
[Property]
public TCS3472Type ChipId { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="Tcs3472x" /> class.
/// </summary>
/// <param name="i2cDevice">The I2C Device class.</param>
/// <param name="integrationTime">The time to wait for sensor to read the data, minimum is 0.024 seconds, maximum in the constructor is 0.7 seconds.</param>
/// <param name="gain">The gain when integrating the color measurement.</param>
/// <param name="shouldDispose">True to dispose the I2C Device class at dispose.</param>
public Tcs3472x(I2cDevice i2cDevice, double integrationTime = 0.0024, Gain gain = Gain.Gain16X, bool shouldDispose = true)
{
_i2cDevice = i2cDevice ?? throw new ArgumentNullException(nameof(i2cDevice));
// Maximum is 700 ms for the initialization. Value can be changed for a long one but not during this initialization phase
_shouldDispose = shouldDispose;
_i2cDevice.WriteByte((byte)(Registers.COMMAND_BIT | Registers.ID));
ChipId = (TCS3472Type)_i2cDevice.ReadByte();
_isLongTime = false;
IntegrationTime = MathExtensions.Clamp(integrationTime, 0.0024, 0.7);
SetIntegrationTime(integrationTime);
Gain = gain;
PowerOn();
}
/// <summary>
/// Gets a value indicating whether data is valid.
/// </summary>
public bool IsValidData
{
get
{
_i2cDevice.WriteByte((byte)(Registers.COMMAND_BIT | Registers.STATUS));
var stat = _i2cDevice.ReadByte();
return (Registers)(stat & (byte)Registers.STATUS_AVALID) == Registers.STATUS_AVALID;
}
}
/// <summary>
/// Gets a value indicating whether RGBC is clear channel interrupt.
/// </summary>
public bool IsClearInterrupt
{
get
{
_i2cDevice.WriteByte((byte)(Registers.COMMAND_BIT | Registers.STATUS));
var stat = _i2cDevice.ReadByte();
return (Registers)(stat & (byte)Registers.STATUS_AINT) == Registers.STATUS_AINT;
}
}
/// <summary>
/// Set the integration (sampling) time for the sensor.
/// </summary>
/// <param name="timeSeconds">Time in seconds for each sample. 0.0024 second(2.4ms) increments.Clipped to the range of 0.0024 to 0.6144 seconds.</param>
private void SetIntegrationTime(double timeSeconds)
{
if (timeSeconds <= 700)
{
if (_isLongTime)
{
SetConfigLongTime(false);
}
_isLongTime = false;
var timeByte = MathExtensions.Clamp((int)(0x100 - (timeSeconds / 0.0024)), 0, 255);
WriteRegister(Registers.ATIME, (byte)timeByte);
_integrationTimeByte = (byte)timeByte;
}
else
{
if (!_isLongTime)
{
SetConfigLongTime(true);
}
_isLongTime = true;
var timeByte = (int)(0x100 - (timeSeconds / 0.029));
timeByte = MathExtensions.Clamp(timeByte, 0, 255);
WriteRegister(Registers.WTIME, (byte)timeByte);
_integrationTimeByte = (byte)timeByte;
}
}
private void SetConfigLongTime(bool setLong)
{
WriteRegister(Registers.CONFIG, setLong ? (byte)Registers.CONFIG_WLONG : (byte)0x00);
}
private void PowerOn()
{
WriteRegister(Registers.ENABLE, (byte)Registers.ENABLE_PON);
Thread.Sleep(10);
WriteRegister(Registers.ENABLE, (byte)(Registers.ENABLE_PON | Registers.ENABLE_AEN));
}
private void PowerOff()
{
var powerState = I2cRead8(Registers.ENABLE);
powerState = (byte)(powerState & ~(byte)(Registers.ENABLE_PON | Registers.ENABLE_AEN));
WriteRegister(Registers.ENABLE, powerState);
}
/// <summary>
/// Set/Clear the colors and clear interrupts.
/// </summary>
/// <param name="state">True to set all interrupts, false to clear.</param>
public void SetInterrupt(bool state)
{
SetInterrupt(InterruptState.All, state);
}
/// <summary>
/// Set/clear a specific interrupt persistence
/// This is used to have more than 1 cycle before generating an
/// interruption.
/// </summary>
/// <param name="interupt">The percistence cycles.</param>
/// <param name="state">True to set the interrupt, false to clear.</param>
public void SetInterrupt(InterruptState interupt, bool state)
{
WriteRegister(Registers.PERS, (byte)interupt);
var enable = I2cRead8(Registers.ENABLE);
enable = state
? enable |= (byte)Registers.ENABLE_AIEN
: enable = (byte)(enable & ~(byte)Registers.ENABLE_AIEN);
WriteRegister(Registers.ENABLE, enable);
}
/// <summary>
/// Get the color.
/// </summary>
/// <param name="delay">Wait to read the data that the integration time is passed.</param>
/// <returns>Current color reading.</returns>
public Color GetColor(bool delay = true)
{
// To have a new reading, you need to wait for integration time to happen
// If you don't wait, then you'll read the previous value
if (delay)
{
Thread.Sleep((int)(IntegrationTime * 1000));
}
var divide = (256 - _integrationTimeByte) * 1024 * 12;
// If we are in long wait, we'll need to divide even more
if (_isLongTime)
{
divide *= 12;
}
var rdata = I2cRead16(Registers.RDATAL);
int r = (int)(rdata * 255 / divide);
r = MathExtensions.Clamp(r, 0, 255);
var gdata = I2cRead16(Registers.GDATAL);
int g = (int)(gdata * 255 / divide);
g = MathExtensions.Clamp(g, 0, 255);
var bdata = I2cRead16(Registers.BDATAL);
int b = (int)(bdata * 255 / divide);
b = MathExtensions.Clamp(b, 0, 255);
var adata = I2cRead16(Registers.CDATAL);
int a = (int)(adata * 255 / divide);
a = MathExtensions.Clamp(a, 0, 255);
return Color.FromArgb(a, r, g, b);
}
/// <summary>
/// Get the color.
/// </summary>
[Telemetry]
public Color Color => GetColor();
private ushort I2cRead16(Registers reg)
{
_i2cDevice.WriteByte((byte)(Registers.COMMAND_BIT | reg));
SpanByte outArray = new byte[2]
{
0, 0
};
_i2cDevice.Read(outArray);
return BinaryPrimitives.ReadUInt16BigEndian(outArray);
}
private byte I2cRead8(Registers reg)
{
_i2cDevice.WriteByte((byte)(Registers.COMMAND_BIT | reg));
return _i2cDevice.ReadByte();
}
private void WriteRegister(Registers reg, byte data)
{
_i2cDevice.Write(new byte[] { (byte)(Registers.COMMAND_BIT | reg), data });
}
/// <inheritdoc/>
public void Dispose()
{
PowerOff();
if (_shouldDispose)
{
_i2cDevice?.Dispose();
_i2cDevice = null;
}
}
}
}