-
-
Notifications
You must be signed in to change notification settings - Fork 114
/
Copy pathAmg88xx.cs
502 lines (434 loc) · 18.2 KB
/
Amg88xx.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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
// 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.Collections;
using System.Device.I2c;
using nanoFramework.UI;
using UnitsNet;
namespace Iot.Device.Amg88xx
{
/// <summary>
/// AMG88xx - family of infrared array sensors.
/// </summary>
public class Amg88xx : IDisposable
{
/// <summary>
/// Standard device address
/// (AD_SELECT pin is low, c.f. reference specification, pg. 11).
/// </summary>
public const int DefaultI2cAddress = 0x68;
/// <summary>
/// Alternative device address
/// (AD_SELECT pin is high, c.f. reference specification, pg. 11).
/// </summary>
public const int AlternativeI2cAddress = 0x69;
/// <summary>
/// Number of sensor pixel array columns.
/// </summary>
public const int Width = 0x8;
/// <summary>
/// Number of sensor pixel array rows.
/// </summary>
public const int Height = 0x8;
/// <summary>
/// Total number of pixels.
/// </summary>
public const int PixelCount = Width * Height;
/// <summary>
/// Number of bytes per pixel.
/// </summary>
private const int BytesPerPixel = 2;
/// <summary>
/// Temperature resolution of thermistor (in degrees Celsius).
/// </summary>
private const double ThermistorTemperatureResolution = 0.0625;
/// <summary>
/// Internal storage for the most recently image read from the sensor.
/// </summary>
private readonly byte[] _imageData = new byte[PixelCount * BytesPerPixel];
private I2cDevice _i2cDevice;
/// <summary>
/// Initializes a new instance of the <see cref="Amg88xx"/> class.
/// </summary>
/// <param name="i2cDevice">I2C device.</param>
public Amg88xx(I2cDevice i2cDevice)
{
_i2cDevice = i2cDevice ?? throw new ArgumentNullException(nameof(i2cDevice));
}
#region Infrared sensor
/// <summary>
/// Gets temperature of the specified pixel froSm the current thermal image.
/// </summary>
/// <param name="pt">The x-y-coordinate of the pixel to retrieve.</param>
/// <exception cref="ArgumentException">X is less than 0, or greater than or equal to Width.</exception>
/// <exception cref="ArgumentException">Y is less than 0, or greater than or equal to Height.</exception>
/// <returns>Temperature of the specified pixel.</returns>
public Temperature this[Point pt]
{
get
{
if (pt.X < 0 || pt.X >= Width)
{
throw new ArgumentOutOfRangeException(nameof(pt.X));
}
if (pt.Y < 0 || pt.Y >= Height)
{
throw new ArgumentOutOfRangeException(nameof(pt.Y));
}
SpanByte buffer = _imageData;
return Amg88xxUtils.ConvertToTemperature(buffer.Slice(BytesPerPixel * ((Width * pt.Y) + pt.X), BytesPerPixel));
}
}
/// <summary>
/// Gets temperature for all pixels from the current thermal image as a two-dimensional array.
/// First index specifies the x-coordinate of the pixel and second index specifies y-coordinate of the pixel.
/// </summary>
/// <returns>Temperature as a two-dimensional array.</returns>
public Temperature[][] TemperatureImage
{
get
{
Temperature[][] temperatureImage = new Temperature[Width][];
for (int i = 0; i < Width; i++)
{
temperatureImage[i] = new Temperature[Height];
}
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
temperatureImage[x][y] = this[new Point(x, y)];
}
}
return temperatureImage;
}
}
/// <summary>
/// Gets raw reading (12-bit two's complement format) of the specified pixel from the current thermal image.
/// </summary>
/// <param name="n">The number of the pixel to retrieve.</param>
/// <exception cref="ArgumentException">N is less than 0, or greater than or equal to PixelCount.</exception>
/// <returns>Reading of the specified pixel.</returns>
public short this[int n]
{
get
{
if (n < 0 || n >= PixelCount)
{
throw new ArgumentOutOfRangeException(nameof(n));
}
SpanByte buffer = _imageData;
return BinaryPrimitives.ReadInt16LittleEndian(buffer.Slice(n * BytesPerPixel, BytesPerPixel));
}
}
/// <summary>
/// Reads the current image from the sensor.
/// </summary>
public void ReadImage()
{
// the readout process gets triggered by writing to pixel 0 of the sensor w/o any additional data
_i2cDevice.WriteByte((byte)Register.T01L);
_i2cDevice.Read(_imageData);
}
/// <summary>
/// Gets the temperature reading from the internal thermistor.
/// </summary>
/// <value>Temperature reading.</value>
public Temperature SensorTemperature
{
get
{
byte tthl = GetRegister(Register.TTHL);
byte tthh = GetRegister(Register.TTHH);
int reading = (tthh & 0x7) << 8 | tthl;
reading = tthh >> 3 == 0 ? reading : -reading;
// The LSB is equivalent to 0.0625℃.
return Temperature.FromDegreesCelsius(reading * ThermistorTemperatureResolution);
}
}
#endregion
#region Status
/// <summary>
/// Gets whether any pixel measured a temperature higher than the normal operation range.
/// The event of an overflow does not prevent from continuing reading the sensor.
/// The overflow indication will last even if all pixels are returned to readings within normal range.
/// The indicator is reset using <see cfref="ClearTemperatureOverflow"/>.
/// </summary>
/// <returns>True, if an overflow occured.</returns>
public bool HasTemperatureOverflow()
{
return GetBit(Register.STAT, (byte)StatusFlagBit.OVF_IRS);
}
/// <summary>
/// Clears the temperature overflow indication.
/// </summary>
public void ClearTemperatureOverflow()
{
// only the bit to be cleared is set, the other bits need to be 0
SetRegister(Register.SCLR, 1 << (byte)StatusClearBit.OVFCLR);
}
/// <summary>
/// Gets the thermistor overflow flag from the status register.
/// The overflow indication will last even if the thermistor temperature returned to normal range.
/// The event of an overflow does not prevent from continuing reading the sensor.
/// The indicator is reset using <see cfref="ClearThermistorOverflow"/>.
/// Note: the bit is only menthioned in early versions of the reference specification.
/// It is not clear whether this is a specification error or a change in a newer
/// revision of the sensor.
/// </summary>
/// <returns>True, if an overflow occured.</returns>
public bool HasThermistorOverflow()
{
return GetBit(Register.STAT, (byte)StatusFlagBit.OVF_THS);
}
/// <summary>
/// Clears the temperature overflow indication.
/// </summary>
public void ClearThermistorOverflow()
{
// only the bit to be cleared is set, the other bits need to be 0
SetRegister(Register.SCLR, 1 << (byte)StatusClearBit.OVFTHCLR);
}
/// <summary>
/// Gets the interrupt flag from the status register.
/// </summary>
/// <returns>Interrupt flag.</returns>
public bool HasInterrupt()
{
return GetBit(Register.STAT, (byte)StatusFlagBit.INTF);
}
/// <summary>
/// Clears the interrupt flag in the status register.
/// </summary>
public void ClearInterrupt()
{
// only the bit to be cleared is set, the other bits need to be 0
SetRegister(Register.SCLR, 1 << (byte)StatusClearBit.INTCLR);
}
/// <summary>
/// Clears all flags in the status register.
/// Note: it does not clear the interrupt flags of the individual pixels.
/// </summary>
public void ClearAllFlags()
{
// only the bit to be cleared is set, the other bits need to be 0
SetRegister(Register.SCLR, (1 << (byte)StatusClearBit.OVFCLR) | (1 << (byte)StatusClearBit.OVFTHCLR) | (1 << (byte)StatusClearBit.INTCLR));
}
#endregion
#region Moving average
/// <summary>
/// Gets or sets a value indicating whether use moving average mode
/// Important: the reference specification states that the current mode can be read,
/// but it doesn't seem to work at the time being.
/// In this case the property is always read as ```false```.
/// </summary>
/// <value>True if the moving average should be calculated; otherwise, false. The default is false.</value>
public bool UseMovingAverageMode
{
get => GetBit(Register.AVE, (byte)MovingAverageModeBit.MAMOD);
set => SetBit(Register.AVE, (byte)MovingAverageModeBit.MAMOD, value);
}
#endregion
#region Frame Rate
/// <summary>
/// Gets or sets the frame rate of the sensor internal thermal image update.
/// </summary>
/// <exception cref="ArgumentException">Thrown when attempting to set a frame rate other than 1 or 10 frames per second.</exception>
/// <value>The frame rate for the pixel update interval (either 1 or 10fps). The default is 10fps.</value>
public FrameRate FrameRate
{
get => GetBit(Register.FPSC, (byte)FrameRateBit.FPS) ? FrameRate.Rate1FramePerSecond : FrameRate.Rate10FramesPerSecond;
set
{
if (value != FrameRate.Rate1FramePerSecond && value != FrameRate.Rate10FramesPerSecond)
{
throw new ArgumentException("Frame rate must either be 1 or 10.");
}
SetBit(Register.FPSC, (byte)FrameRateBit.FPS, value == FrameRate.Rate1FramePerSecond);
}
}
#endregion
#region Operating Mode / Power Control
/// <summary>
/// Gets or sets the current operating mode
/// Refer to the sensor reference specification for a description of the mode
/// depending sensor bevaviour and the valid mode transistions.
/// </summary>
/// <value>The operating mode of the sensor. The default is Normal.</value>
public OperatingMode OperatingMode
{
get => (OperatingMode)GetRegister(Register.PCLT);
set => SetRegister(Register.PCLT, (byte)value);
}
#endregion
#region Reset
/// <summary>
/// Performs an reset of the sensor. The flags and all configuration registers
/// are reset to default values.
/// </summary>
public void Reset()
{
// a reset (factory defaults) is initiated by writing 0x3f into the reset register (RST)
SetRegister(Register.RST, (byte)ResetType.Initial);
}
/// <summary>
/// Performs a reset of all flags (status register, interrupt flag and interrupt table).
/// This method is useful, if using the interrupt mechanism for pixel temperatures.
/// If an upper and lower level has been set along with a hysteresis this reset can clear the interrupt state of all pixels
/// which are within the range between upper and lower level, but still above/below the hystersis level.
/// If this applies to ALL pixels the interrupt flag gets cleared as well.
/// Refer to the binding documentation for more details on interrupt level, hysteresis and flagging.
/// </summary>
public void ResetAllFlags()
{
// a reset of all flags (status register, interrupt flag and interrupt table) is initiated by writing 0x30
// into the reset register (RST)
SetRegister(Register.RST, (byte)ResetType.Flag);
}
#endregion
#region Interrupt control, levels and pixel flags
/// <summary>
/// Gets or sets the pixel temperature interrupt mode.
/// </summary>
/// <value>The interrupt mode, which is either aboslute or differential. The default is ```Difference```.</value>
public InterruptMode InterruptMode
{
get => GetBit(Register.INTC, (byte)InterruptModeBit.INTMODE) ? InterruptMode.Absolute : InterruptMode.Difference;
set => SetBit(Register.INTC, (byte)InterruptModeBit.INTMODE, value == InterruptMode.Absolute);
}
/// <summary>
/// Gets or sets a value indicating whether the interrupt output pin of the sensor is enabled.
/// If enabled, the pin is pulled down if an interrupt is active.
/// </summary>
/// <value>True, if the INT pin sould be enabled; otherwise false. The default is false.</value>
public bool InterruptPinEnabled
{
get => GetBit(Register.INTC, (byte)InterruptModeBit.INTEN);
set => SetBit(Register.INTC, (byte)InterruptModeBit.INTEN, value);
}
/// <summary>
/// Gets or sets the pixel temperature lower interrupt level.
/// </summary>
/// <value>Temperature level to trigger an interrupt if the any pixel falls below. The default is 0.</value>
public Temperature InterruptLowerLevel
{
get
{
byte tl = GetRegister(Register.INTLL);
byte th = GetRegister(Register.INTLH);
return Amg88xxUtils.ConvertToTemperature(tl, th);
}
set
{
var tlh = Amg88xxUtils.ConvertFromTemperature(value);
SetRegister(Register.INTLL, tlh.LowByte);
SetRegister(Register.INTLH, tlh.HighByte);
}
}
/// <summary>
/// Gets or sets the pixel temperature upper interrupt level.
/// </summary>
/// <value>Temperature level to trigger an interrupt if the any pixel exceeds. The default is 0.</value>
public Temperature InterruptUpperLevel
{
get
{
byte tl = GetRegister(Register.INTHL);
byte th = GetRegister(Register.INTHH);
return Amg88xxUtils.ConvertToTemperature(tl, th);
}
set
{
var tlh = Amg88xxUtils.ConvertFromTemperature(value);
SetRegister(Register.INTHL, tlh.LowByte);
SetRegister(Register.INTHH, tlh.HighByte);
}
}
/// <summary>
/// Gets or sets the pixel temperature interrupt hysteresis.
/// </summary>
/// <value>Temperature hysteresis for lower and upper interrupt triggering. The default is 0.</value>
public Temperature InterruptHysteresis
{
get
{
byte tl = GetRegister(Register.INTSL);
byte th = GetRegister(Register.INTSH);
return Amg88xxUtils.ConvertToTemperature(tl, th);
}
set
{
var tlh = Amg88xxUtils.ConvertFromTemperature(value);
SetRegister(Register.INTSL, tlh.LowByte);
SetRegister(Register.INTSH, tlh.HighByte);
}
}
/// <summary>
/// Gets the interrupt flags of all pixels.
/// </summary>
/// <returns>Interrupt flags.</returns>
public bool[][] GetInterruptFlagTable()
{
var registers = new Register[]
{
Register.INT0, Register.INT1, Register.INT2, Register.INT3,
Register.INT4, Register.INT5, Register.INT6, Register.INT7,
};
// read all registers from the sensor
var flagRegisters = new ArrayList();
foreach (Register register in registers)
{
flagRegisters.Add(GetRegister(register));
}
var flags = new bool[Width][];
for (int i = 0; i < Width; i++)
{
flags[i] = new bool[Height];
}
for (int row = 0; row < Height; row++)
{
var flagRegister = (byte)flagRegisters[row];
for (int col = 0; col < Width; col++)
{
flags[col][row] = (flagRegister & (1 << col)) > 0;
}
}
return flags;
}
#endregion
private byte GetRegister(Register register)
{
_i2cDevice.WriteByte((byte)register);
return _i2cDevice.ReadByte();
}
private bool GetBit(Register register, byte bit)
{
return (GetRegister(register) & (1 << bit)) > 0;
}
private void SetRegister(Register register, byte value)
{
SpanByte buffer = new byte[2]
{
(byte)register,
value
};
_i2cDevice.Write(buffer);
}
private void SetBit(Register register, byte bit, bool state)
{
var b = GetRegister(register);
b = (byte)(state ? (b | (1 << bit)) : (b & (~(1 << bit))));
SetRegister(register, b);
}
/// <inheritdoc />
public void Dispose()
{
if (_i2cDevice != null)
{
_i2cDevice?.Dispose();
_i2cDevice = null;
}
}
}
}