1 //
2 // Copyright (c) 2010-2024 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.Linq;
9 using System.IO;
10 using System.Collections;
11 using System.Collections.Generic;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Time;
14 using Antmicro.Renode.Core.Structure.Registers;
15 using Antmicro.Renode.Exceptions;
16 using Antmicro.Renode.Logging;
17 using Antmicro.Renode.Peripherals.I2C;
18 using Antmicro.Renode.Peripherals.Sensor;
19 using Antmicro.Renode.Utilities;
20 using Antmicro.Renode.Utilities.RESD;
21 
22 namespace Antmicro.Renode.Peripherals.Sensors
23 {
24     // TODO: FIFO for other data than the accelerometer
25     public class LIS2DW12 : II2CPeripheral, IProvidesRegisterCollection<ByteRegisterCollection>, ITemperatureSensor, IUnderstandRESD
26     {
LIS2DW12(IMachine machine)27         public LIS2DW12(IMachine machine)
28         {
29             RegistersCollection = new ByteRegisterCollection(this);
30             Interrupt1 = new GPIO();
31             Interrupt2 = new GPIO();
32             readyEnabledAcceleration = new IFlagRegisterField[2];
33             fifoThresholdEnabled = new IFlagRegisterField[2];
34             fifoFullEnabled = new IFlagRegisterField[2];
35             DefineRegisters();
36 
37             accelerationFifo = new LIS2DW12_FIFO(this, "fifo", MaxFifoSize);
38             accelerationFifo.OnOverrun += UpdateInterrupts;
39             // Set the default acceleration here, it needs to be preserved across resets
40             DefaultAccelerationZ = 1m;
41 
42             this.machine = machine;
43         }
44 
FeedAccelerationSample(decimal x, decimal y, decimal z, uint repeat = 1)45         public void FeedAccelerationSample(decimal x, decimal y, decimal z, uint repeat = 1)
46         {
47             FeedAccelerationSampleInner(x, y, z, keepOnReset: true, repeat: repeat);
48         }
49 
50         [OnRESDSample(SampleType.Acceleration)]
51         [BeforeRESDSample(SampleType.Acceleration)]
HandleAccelerationSample(AccelerationSample sample, TimeInterval timestamp)52         private void HandleAccelerationSample(AccelerationSample sample, TimeInterval timestamp)
53         {
54             if(sample != null)
55             {
56                 // Divide by 10^6 as RESD specification says AccelerationSamples are in μg,
57                 // while the peripheral's measurement unit is g.
58                 FeedAccelerationSampleInner(
59                     sample.AccelerationX / 1e6m,
60                     sample.AccelerationY / 1e6m,
61                     sample.AccelerationZ / 1e6m,
62                     keepOnReset: false
63                 );
64             }
65             else
66             {
67                 FeedAccelerationSampleInner(
68                     DefaultAccelerationX,
69                     DefaultAccelerationY,
70                     DefaultAccelerationZ,
71                     keepOnReset: false
72                 );
73             }
74         }
75 
76         [AfterRESDSample(SampleType.Acceleration)]
HandleAccelerationSampleEnded(AccelerationSample sample, TimeInterval timestamp)77         private void HandleAccelerationSampleEnded(AccelerationSample sample, TimeInterval timestamp)
78         {
79             feederThread?.Stop();
80             feederThread = null;
81             accelerationFifo.KeepFifoOnReset = true;
82         }
83 
FeedAccelerationSamplesFromRESD(string path, uint channel = 0, ulong startTime = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0, RESDType type = RESDType.Normal)84         public void FeedAccelerationSamplesFromRESD(string path, uint channel = 0, ulong startTime = 0,
85             RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0,
86             RESDType type = RESDType.Normal)
87         {
88             if(type == RESDType.MultiFrequency)
89             {
90                 FeedMultiFrequencyAccelerationSamplesFromRESD(path, startTime);
91                 return;
92             }
93             else if(type != RESDType.Normal)
94             {
95                 throw new RecoverableException($"Unhandled RESD type {type}");
96             }
97 
98             resdStream = this.CreateRESDStream<AccelerationSample>(path, channel, sampleOffsetType, sampleOffsetTime);
99             accelerationFifo.KeepFifoOnReset = false;
100             feederThread?.Stop();
101             feederThread = resdStream.StartSampleFeedThread(this,
102                 SampleRate,
103                 startTime: startTime
104             );
105         }
106 
FeedAccelerationSample(string path)107         public void FeedAccelerationSample(string path)
108         {
109             accelerationFifo.KeepFifoOnReset = true;
110             accelerationFifo.FeedSamplesFromFile(path);
111             UpdateInterrupts();
112         }
113 
FinishTransmission()114         public void FinishTransmission()
115         {
116             regAddress = 0;
117             state = State.WaitingForRegister;
118             this.Log(LogLevel.Noisy, "Transmission finished");
119         }
120 
Reset()121         public void Reset()
122         {
123             RegistersCollection.Reset();
124             SetScaleDivider();
125             state = State.WaitingForRegister;
126             regAddress = 0;
127             Interrupt1.Set(false);
128             Interrupt2.Set(false);
129         }
130 
Write(byte[] data)131         public void Write(byte[] data)
132         {
133             if(data.Length == 0)
134             {
135                 this.Log(LogLevel.Warning, "Unexpected write with no data");
136                 return;
137             }
138             this.Log(LogLevel.Noisy, "Write with {0} bytes of data: {1}", data.Length, Misc.PrettyPrintCollectionHex(data));
139             if(state == State.WaitingForRegister)
140             {
141                 regAddress = (Registers)data[0];
142                 this.Log(LogLevel.Noisy, "Preparing to access register {0} (0x{0:X})", regAddress);
143                 state = State.WaitingForData;
144                 data = data.Skip(1).ToArray();
145             }
146             if(state == State.WaitingForData)
147             {
148                 InternalWrite(data);
149             }
150         }
151 
Read(int count)152         public byte[] Read(int count)
153         {
154             if(state == State.WaitingForRegister)
155             {
156                 this.Log(LogLevel.Warning, "Unexpected read in state {0}", state);
157                 return new byte[count];
158             }
159             this.Log(LogLevel.Noisy, "Reading {0} bytes from register {1} (0x{1:X})", count, regAddress);
160             var result = new byte[count];
161             for(var i = 0; i < result.Length; i++)
162             {
163                 result[i] = RegistersCollection.Read((byte)regAddress);
164                 this.Log(LogLevel.Noisy, "Read value 0x{0:X}", result[i]);
165                 if(autoIncrement.Value)
166                 {
167                     RegistersAutoIncrement();
168                 }
169             }
170             return result;
171         }
172 
173         public decimal AccelerationX
174         {
175             get => CurrentSample.X;
176             set
177             {
178                 if(IsAccelerationOutOfRange(value))
179                 {
180                     return;
181                 }
182                 CurrentSample.X = value;
183                 this.Log(LogLevel.Noisy, "AccelerationX set to {0}", value);
184                 UpdateInterrupts();
185             }
186         }
187 
188         public decimal AccelerationY
189         {
190             get => CurrentSample.Y;
191             set
192             {
193                 if(IsAccelerationOutOfRange(value))
194                 {
195                     return;
196                 }
197                 CurrentSample.Y = value;
198                 this.Log(LogLevel.Noisy, "AccelerationY set to {0}", value);
199                 UpdateInterrupts();
200             }
201         }
202 
203         public decimal AccelerationZ
204         {
205             get => CurrentSample.Z;
206             set
207             {
208                 if(IsAccelerationOutOfRange(value))
209                 {
210                     return;
211                 }
212                 CurrentSample.Z = value;
213                 this.Log(LogLevel.Noisy, "AccelerationZ set to {0}", value);
214                 UpdateInterrupts();
215             }
216         }
217 
218         public decimal DefaultAccelerationX
219         {
220             get => DefaultSample.X;
221             set
222             {
223                 if(IsAccelerationOutOfRange(value))
224                 {
225                     return;
226                 }
227                 DefaultSample.X = value;
228                 this.Log(LogLevel.Noisy, "DefaultAccelerationX set to {0}", value);
229                 UpdateInterrupts();
230             }
231         }
232 
233         public decimal DefaultAccelerationY
234         {
235             get => DefaultSample.Y;
236             set
237             {
238                 if(IsAccelerationOutOfRange(value))
239                 {
240                     return;
241                 }
242                 DefaultSample.Y = value;
243                 this.Log(LogLevel.Noisy, "DefaultAccelerationY set to {0}", value);
244                 UpdateInterrupts();
245             }
246         }
247 
248         public decimal DefaultAccelerationZ
249         {
250             get => DefaultSample.Z;
251             set
252             {
253                 if(IsAccelerationOutOfRange(value))
254                 {
255                     return;
256                 }
257                 DefaultSample.Z = value;
258                 this.Log(LogLevel.Noisy, "DefaultAccelerationZ set to {0}", value);
259                 UpdateInterrupts();
260             }
261         }
262 
263         public decimal Temperature
264         {
265             get => temperature;
266             set
267             {
268                 if(IsTemperatureOutOfRange(value))
269                 {
270                     return;
271                 }
272                 temperature = value;
273                 this.Log(LogLevel.Noisy, "Temperature set to {0}", temperature);
274                 UpdateInterrupts();
275             }
276         }
277 
278         public GPIO Interrupt1 { get; }
279         public GPIO Interrupt2 { get; }
280         public ByteRegisterCollection RegistersCollection { get; }
281         public uint SampleRate
282         {
283             get => sampleRate;
284             private set
285             {
286                 sampleRate = value;
287                 if(feederThread != null)
288                 {
289                     feederThread.Frequency = sampleRate;
290                 }
291                 this.Log(LogLevel.Debug, "Sampling rate set to {0}", SampleRate);
292                 SampleRateChanged?.Invoke(value);
293             }
294         }
295 
FeedMultiFrequencyAccelerationSamplesFromRESD(string path, ulong startTime)296         private void FeedMultiFrequencyAccelerationSamplesFromRESD(string path, ulong startTime)
297         {
298             var parser = new LowLevelRESDParser(path);
299             var mapping = parser.GetDataBlockEnumerator<AccelerationSample>()
300                 .OfType<ConstantFrequencySamplesDataBlock<AccelerationSample>>()
301                 .Select(block => new { Channel = block.ChannelId, block.Frequency, block.StartTime, block.SamplesCount })
302                 .ToList();
303 
304             // Count samples in blocks with numbers between blockFirst and blockLast (both exclusive)
305             Func<int, int, ulong> countSkippedSamples = (blockFirst, blockLast) =>
306             {
307                 var skippedSamples = mapping.Skip(blockFirst).Take(blockLast - blockFirst - 1)
308                     .Aggregate(0UL, (samplesCount, block) => samplesCount + block.SamplesCount);
309                 return skippedSamples;
310             };
311 
312             // Keep track of additional statistics for improved logging
313             var previousBlockNumber = 0;
314             var numberOfSampleInBlock = 0ul;
315             var samplesInBlock = 0ul;
316             var repeatCounter = 0ul;
317             var prevSampleRate = 0u;
318             var afterSampleRateChange = false;
319             var previousRESDStreamStatus = RESDStreamStatus.BeforeStream;
320 
321             Action<ulong> resetCurrentBlockStats = (samplesCnt) =>
322             {
323                 samplesInBlock = samplesCnt;
324                 numberOfSampleInBlock = 0;
325                 repeatCounter = 0;
326             };
327 
328             // We keep the number of the last block used so far so that we can skip already used blocks on each event
329             var blockNumber = 0;
330             var init = true;
331             Action<uint> findAndActivate = null;
332             findAndActivate = (sampleRate) =>
333             {
334                 if(sampleRate != prevSampleRate)
335                 {
336                     afterSampleRateChange = true;
337                 }
338                 else if(afterSampleRateChange)
339                 {
340                     if(fifoModeSelection.Value == FIFOModeSelection.FIFOMode)
341                     {
342                         // FIFO mode entered after changing sample rate. Repeat the previous block to synchronize with FIFO operation.
343                         blockNumber = previousBlockNumber;
344                     }
345                     afterSampleRateChange = false;
346                 }
347                 previousBlockNumber = blockNumber;
348                 var currentEntry = mapping
349                     .Select((item, i) => new { Item = item, Index = i })
350                     .Skip(blockNumber)
351                     .FirstOrDefault(o => o.Item.Frequency == sampleRate);
352 
353                 if(currentEntry == null)
354                 {
355                     // No more blocks at this sample rate
356                     this.Log(LogLevel.Debug, "No more blocks for the sample rate {0}Hz in the RESD file", sampleRate);
357                     feederThread?.Stop();
358                     feederThread = null;
359                     resdStream?.Dispose();
360                     // If there are no more blocks, even without taking the sample rate
361                     // into account, then we can unregister this sample rate change handler
362                     if(blockNumber == mapping.Count)
363                     {
364                         this.Log(LogLevel.Debug, "No more blocks in the RESD file");
365                         SampleRateChanged -= findAndActivate;
366                         FIFOModeEntered -= findAndActivate;
367                     }
368                     return;
369                 }
370 
371                 blockNumber = currentEntry.Index + 1;
372                 var samplesSkippedInLastBlock = previousRESDStreamStatus == RESDStreamStatus.OK ? samplesInBlock - numberOfSampleInBlock : 0;
373                 this.Log(LogLevel.Noisy, "Skipped {0} blocks while moving from the RESD block with the number {1} to {2}. Skipped {3} samples from the last active block and {4} samples in total",
374                     blockNumber - previousBlockNumber - 1, previousBlockNumber, blockNumber, samplesSkippedInLastBlock, samplesSkippedInLastBlock + countSkippedSamples(previousBlockNumber, blockNumber));
375                 var block = currentEntry.Item;
376 
377                 feederThread?.Stop();
378                 resdStream?.Dispose();
379 
380                 if(machine.SystemBus.TryGetCurrentCPU(out var cpu))
381                 {
382                     cpu.SyncTime();
383                 }
384 
385                 // Offset the start time so that we start reading the block from the beginning even if
386                 // the sample rate was switched at a time different from the exact start time of the block
387                 resdStream = this.CreateRESDStream<AccelerationSample>(
388                     path,
389                     block.Channel,
390                     RESDStreamSampleOffset.Specified,
391                     (long)machine.ClockSource.CurrentValue.TotalMicroseconds * -1000L + (long)block.StartTime - (init ? (long)startTime : 0),
392                     b => (b as ConstantFrequencySamplesDataBlock<AccelerationSample>)?.Frequency == sampleRate
393                 );
394                 resdStream.Owner = null; // turn off verbose internal RESD logging and use custom logs
395                 accelerationFifo.KeepFifoOnReset = false;
396                 resetCurrentBlockStats(block.SamplesCount);
397                 previousRESDStreamStatus = RESDStreamStatus.BeforeStream;
398 
399                 // We start the thread with shouldStop: false so that it will keep feeding the last
400                 // sample into the FIFO at the specified frequency until it's stopped after a sample
401                 // rate switch
402                 AccelerationSample previousSample = null;
403                 // The same indexing as in 'ResdCommand' is used to log block's number
404                 this.Log(LogLevel.Noisy, "New RESD stream for the sample rate {0}Hz with the first block with the number {1} delayed by {2}ns", sampleRate, blockNumber, init ? (long)startTime : 0);
405                 feederThread = resdStream.StartSampleFeedThread(this, sampleRate, (sample, ts, status) =>
406                 {
407                     switch(status)
408                     {
409                         case RESDStreamStatus.OK:
410                         {
411                             if(blockNumber < resdStream.CurrentBlockNumber)
412                             {
413                                 // We received the first sample from the next block in the RESD stream
414                                 // as a result of internal RESD operation, not due to a triggered event
415                                 if(fifoModeSelection.Value == FIFOModeSelection.FIFOMode)
416                                 {
417                                     // In FIFO mode we don't allow automatic passtrough to the next block,
418                                     // because it would interfere with event driven FIFOModeEntered logic.
419                                     goto case RESDStreamStatus.BeforeStream;
420                                 }
421                                 resetCurrentBlockStats(resdStream.CurrentBlock.SamplesCount);
422                                 blockNumber = (int)resdStream.CurrentBlockNumber;
423                                 this.Log(LogLevel.Noisy, "Beginning of the new RESD block ({0}Hz) with the number {1}", sampleRate, blockNumber);
424                             }
425 
426                             numberOfSampleInBlock++;
427                             previousSample = sample;
428                             this.Log(LogLevel.Noisy, "Fed current sample from the RESD block ({0}Hz) with the number {1}: {2} at {3} ({4}/{5})", sampleRate, blockNumber, previousSample, ts, numberOfSampleInBlock, samplesInBlock);
429                             break;
430                         }
431                         case RESDStreamStatus.BeforeStream:
432                         {
433                             repeatCounter++;
434                             this.Log(LogLevel.Noisy, "Repeated the last sample from the previous RESD block ({0}Hz) with the number {1}: {2} at {3} - {4} times", sampleRate, blockNumber, previousSample, ts, repeatCounter);
435                             break;
436                         }
437                         case RESDStreamStatus.AfterStream:
438                         {
439                             repeatCounter++;
440                             this.Log(LogLevel.Noisy, "No more samples in the RESD stream for the sample rate {0}Hz. Repeated the last sample from the previous RESD block with the number {1}: {2} at {3} - {4} times", sampleRate, blockNumber, previousSample, ts, repeatCounter);
441                             break;
442                         }
443                     }
444                     previousRESDStreamStatus = status;
445 
446                     if(previousSample != null)
447                     {
448                         HandleAccelerationSample(previousSample, ts);
449                     }
450                 }, startTime: init ? startTime : 0, shouldStop: false);
451 
452                 init = false;
453                 prevSampleRate = sampleRate;
454             };
455 
456             findAndActivate(SampleRate);
457             SampleRateChanged += findAndActivate;
458             FIFOModeEntered += findAndActivate;
459         }
460 
FeedAccelerationSampleInner(decimal x, decimal y, decimal z, bool keepOnReset, uint repeat = 1)461         private void FeedAccelerationSampleInner(decimal x, decimal y, decimal z, bool keepOnReset, uint repeat = 1)
462         {
463             if(keepOnReset)
464             {
465                 // this is a simplified implementation
466                 // that assumes that the `keepOnReset`
467                 // status applies to all samples;
468                 // might not work when mixing feeding
469                 // samples from RESD and manually
470                 accelerationFifo.KeepFifoOnReset = true;
471             }
472 
473             var sample = new Vector3DSample(x, y, z);
474 
475             if(fifoModeSelection.Value == FIFOModeSelection.Bypass && !keepOnReset)
476             {
477                 CurrentSample = sample;
478             }
479             else
480             {
481                 for(var i = 0; i < repeat; i++)
482                 {
483                     accelerationFifo.FeedSample(sample);
484                 }
485             }
486 
487             UpdateInterrupts();
488         }
489 
LoadNextSample()490         private void LoadNextSample()
491         {
492             if(fifoModeSelection.Value != FIFOModeSelection.Bypass || accelerationFifo.KeepFifoOnReset)
493             {
494                 accelerationFifo.TryDequeueNewSample();
495                 CurrentSample = accelerationFifo.Sample;
496             }
497             this.Log(LogLevel.Noisy, "Acquired sample {0} during {1} operation", CurrentSample, fifoModeSelection.Value);
498             UpdateInterrupts();
499         }
500 
DefineRegisters()501         private void DefineRegisters()
502         {
503             Registers.TemperatureOutLow.Define(this)
504                 .WithValueField(0, 8, FieldMode.Read, name: "Temperature output register in 12-bit resolution (OUT_T_L)",
505                     valueProviderCallback: _ => (byte)(TwoComplementSignConvert(ReportedTemperature * TemperatureLsbsPerDegree) << 4));
506 
507             Registers.TemperatureOutHigh.Define(this)
508                 .WithValueField(0, 8, FieldMode.Read, name: "Temperature output register in 12-bit resolution (OUT_T_H)",
509                     valueProviderCallback: _ => (byte)(TwoComplementSignConvert(ReportedTemperature * TemperatureLsbsPerDegree) >> 4));
510 
511             Registers.WhoAmI.Define(this, 0x44);
512 
513             Registers.Control1.Define(this)
514                 .WithEnumField(0, 2, out lowPowerModeSelection, name: "Low-power mode selection (LP_MODE)")
515                 .WithEnumField(2, 2, out modeSelection, name: "Mode selection (MODE)")
516                 .WithEnumField(4, 4, out outDataRate, writeCallback: (_, __) =>
517                     {
518                         switch(outDataRate.Value)
519                         {
520                             case DataRateConfig.HighPerformanceLowPower1_6Hz:
521                                 SampleRate = 2;
522                                 break;
523                             case DataRateConfig.HighPerformanceLowPower12_5Hz:
524                                 SampleRate = 13;
525                                 break;
526                             case DataRateConfig.HighPerformanceLowPower25Hz:
527                                 SampleRate = 25;
528                                 break;
529                             case DataRateConfig.HighPerformanceLowPower50Hz:
530                                 SampleRate = 50;
531                                 break;
532                             case DataRateConfig.HighPerformanceLowPower100Hz:
533                                 SampleRate = 100;
534                                 break;
535                             case DataRateConfig.HighPerformanceLowPower200Hz:
536                                 SampleRate = 200;
537                                 break;
538                             case DataRateConfig.HighPerformanceLowPower400Hz:
539                                 SampleRate = 400;
540                                 break;
541                             case DataRateConfig.HighPerformanceLowPower800Hz:
542                                 SampleRate = 800;
543                                 break;
544                             case DataRateConfig.HighPerformanceLowPower1600Hz:
545                                 SampleRate = 1600;
546                                 break;
547                             default:
548                                 SampleRate = 0;
549                                 break;
550                         }
551                     }, name: "Output data rate and mode selection (ODR)");
552 
553             Registers.Control2.Define(this, 0x4)
554                 .WithTaggedFlag("SPI serial interface mode selection (SIM)", 0)
555                 .WithTaggedFlag("Disable I2C communication protocol (I2C_DISABLE)", 1)
556                 .WithFlag(2, out autoIncrement, name: "Register address automatically incremented during multiple byte access with a serial interface (FF_ADD_INC)")
557                 .WithTaggedFlag("Block data update (BDU)", 3)
558                 .WithTaggedFlag("Disconnect CS pull-up (CS_PU_DISC)", 4)
559                 .WithReservedBits(5, 1)
560                 .WithFlag(6, writeCallback: (_, value) =>
561                     {
562                         if(value)
563                         {
564                             Reset();
565                         }
566                     }, name: "Acts as reset for all control register (SOFT_RESET)")
567                 .WithTaggedFlag("Enables retrieving the correct trimming parameters from nonvolatile memory into registers (BOOT)", 7);
568 
569             Registers.Control3.Define(this)
570                 .WithTaggedFlag("Single data conversion on demand mode enable (SLP_MODE_1)", 0)
571                 .WithTaggedFlag("Single data conversion on demand mode selection (SLP_MODE_SEL)", 1)
572                 .WithReservedBits(2, 1)
573                 .WithTaggedFlag("Interrupt active high, low (HL_ACTIVE)", 3)
574                 .WithTaggedFlag("Latched Interrupt (LIR)", 4)
575                 .WithTaggedFlag("Push-pull/open-drain selection on interrupt pad (PP_OD)", 5)
576                 .WithEnumField(6, 2, out selfTestMode, name: "Self-test enable (ST)");
577 
578             Registers.Control4.Define(this)
579                 .WithFlag(0, out readyEnabledAcceleration[0], name: "Data-Ready is routed to INT1 pad (INT1_DRDY)")
580                 .WithFlag(1, out fifoThresholdEnabled[0], name: "FIFO threshold interrupt is routed to INT1 pad (INT1_FTH)")
581                 .WithFlag(2, out fifoFullEnabled[0], name: "FIFO full recognition is routed to INT1 pad (INT1_DIFF5)")
582                 .WithTaggedFlag("Double-tap recognition is routed to INT1 pad (INT1_TAP)", 3)
583                 .WithTaggedFlag("Free-fall recognition is routed to INT1 pad (INT1_FF)", 4)
584                 .WithTaggedFlag("Wakeup recognition is routed to INT1 pad (INT1_WU)", 5)
585                 .WithTaggedFlag("Single-tap recognition is routed to INT1 pad (INT1_SINGLE_TAP)", 6)
586                 .WithTaggedFlag("6D recognition is routed to INT1 pad (INT1_6D)", 7)
587                 .WithWriteCallback((_, __) => UpdateInterrupts());
588 
589             Registers.Control5.Define(this)
590                 .WithFlag(0, out readyEnabledAcceleration[1], name: "Data-ready is routed to INT2 pad (INT2_DRDY)")
591                 .WithFlag(1, out fifoThresholdEnabled[1], name: "FIFO threshold interrupt is routed to INT2 pad (INT2_FTH)")
592                 .WithFlag(2, out fifoFullEnabled[1], name: "FIFO full recognition is routed to INT2 pad (INT2_DIFF5)")
593                 .WithFlag(3, out fifoOverrunEnabled, name: "FIFO overrun interrupt is routed to INT2 pad (INT2_OVR)")
594                 .WithFlag(4, out readyEnabledTemperature, name: "Temperature data-ready is routed to INT2 (INT2_DRDY_T)")
595                 .WithTaggedFlag("Boot state routed to INT2 pad (INT2_BOOT)", 5)
596                 .WithTaggedFlag("Sleep change status routed to INT2 pad (INT2_SLEEP_CHG)", 6)
597                 .WithTaggedFlag("Enable routing of SLEEP_STATE on INT2 pad (INT2_SLEEP_STATE)", 7);
598 
599             Registers.Control6.Define(this)
600                 .WithReservedBits(0, 2)
601                 .WithTaggedFlag("Low-noise configuration (LOW_NOISE)", 2)
602                 .WithTaggedFlag("Filtered data type selection (FDS)", 3)
603                 .WithEnumField(4, 2, out fullScale, writeCallback: (_, __) => SetScaleDivider(), name: "Full-scale selection (FS)")
604                 .WithTag("Bandwidth selection (BW_FILT1)", 6, 2);
605 
606             Registers.TemperatureOut.Define(this)
607                 .WithValueField(0, 8, FieldMode.Read, name: "Temperature output register in 8-bit resolution (OUT_T)",
608                     valueProviderCallback: _ => (byte)TwoComplementSignConvert(ReportedTemperature));
609 
610             Registers.Status.Define(this)
611                 .WithFlag(0, valueProviderCallback: _ => outDataRate.Value != DataRateConfig.PowerDown, name: "Data-ready status (DRDY)")
612                 .WithTaggedFlag("Free-fall event detection status (FF_IA)", 1)
613                 .WithTaggedFlag("Source of change in position portrait/landscape/face-up/face-down (6D_IA)", 2)
614                 .WithTaggedFlag("Single-tap event status (SINGLE_TAP)", 3)
615                 .WithTaggedFlag("Double-tap event status (DOUBLE_TAP)", 4)
616                 .WithTaggedFlag("Sleep event status (SLEEP_STATE)", 5)
617                 .WithTaggedFlag("Wakeup event detection status (WU_IA)", 6)
618                 .WithFlag(7, FieldMode.Read, name: "FIFO threshold status flag (FIFO_THS)",
619                     valueProviderCallback: _ => FifoThresholdReached);
620 
621             Registers.DataOutXLow.Define(this)
622                 .WithValueField(0, 8, FieldMode.Read, name: "X-axis LSB output register (OUT_X_L)",
623                     valueProviderCallback: _ =>
624                     {
625                         LoadNextSample();
626                         return Convert(ReportedAccelerationX, upperByte: false);
627                     });
628 
629             Registers.DataOutXHigh.Define(this)
630                 .WithValueField(0, 8, FieldMode.Read, name: "X-axis MSB output register (OUT_X_H)",
631                     valueProviderCallback: _ => Convert(ReportedAccelerationX, upperByte: true));
632 
633             Registers.DataOutYLow.Define(this)
634                 .WithValueField(0, 8, FieldMode.Read, name: "Y-axis LSB output register (OUT_Y_L)",
635                     valueProviderCallback: _ => Convert(ReportedAccelerationY, upperByte: false));
636 
637             Registers.DataOutYHigh.Define(this)
638                 .WithValueField(0, 8, FieldMode.Read, name: "Y-axis MSB output register (OUT_Y_H)",
639                     valueProviderCallback: _ => Convert(ReportedAccelerationY, upperByte: true));
640 
641             Registers.DataOutZLow.Define(this)
642                 .WithValueField(0, 8, FieldMode.Read, name: "Z-axis LSB output register (OUT_Z_L)",
643                     valueProviderCallback: _ => Convert(ReportedAccelerationZ, upperByte: false));
644 
645             Registers.DataOutZHigh.Define(this)
646                 .WithValueField(0, 8, FieldMode.Read, name: "Z-axis MSB output register (OUT_Z_H)",
647                     valueProviderCallback: _ => Convert(ReportedAccelerationZ, upperByte: true));
648 
649             Registers.FifoControl.Define(this)
650                 .WithValueField(0, 5, out fifoThreshold, name: "FIFO threshold level setting (FTH)")
651                 .WithEnumField(5, 3, out fifoModeSelection, writeCallback: (_, newMode) =>
652                     {
653                         if(newMode == FIFOModeSelection.Bypass)
654                         {
655                             accelerationFifo.Reset();
656                         }
657                     }, changeCallback: (oldMode, newMode) =>
658                     {
659                         if(oldMode == FIFOModeSelection.Bypass && newMode == FIFOModeSelection.FIFOMode)
660                         {
661                             FIFOModeEntered?.Invoke(SampleRate);
662                         }
663                     }, name: "FIFO mode selection bits (FMode)");
664 
665             Registers.FifoSamples.Define(this)
666                 .WithValueField(0, 6, FieldMode.Read, name: "Number of unread samples stored in FIFO (DIFF)",
667                     valueProviderCallback: _ => accelerationFifo.Disabled ? 0 : accelerationFifo.SamplesCount)
668                 .WithFlag(6, FieldMode.Read, name: "FIFO overrun status (FIFO_OVR)",
669                     valueProviderCallback: _ => FifoOverrunOccurred)
670                 .WithFlag(7, FieldMode.Read, valueProviderCallback: _ => FifoThresholdReached,
671                     name: "FIFO threshold status flag (FIFO_FTH)");
672 
673             Registers.TapThreshholdX.Define(this)
674                 .WithTag("Threshold for tap recognition on X direction (TAP_THSX)", 0, 5)
675                 .WithTag("Thresholds for 4D/6D function (6D_THS)", 5, 2)
676                 .WithTag("4D detection portrait/landscape position enable (4D_EN)", 7, 1);
677 
678             Registers.TapThreshholdY.Define(this)
679                 .WithTag("Threshold for tap recognition on Y direction (TAP_THSY)", 0, 5)
680                 .WithTag("Selection of priority axis for tap detection (TAP_PRIOR)", 5, 3);
681 
682             Registers.TapThreshholdZ.Define(this)
683                 .WithTag("Threshold for tap recognition on Z direction (TAP_THSZ)", 0, 5)
684                 .WithTag("Enables Z direction in tap recognition (TAP_Z_EN)", 5, 1)
685                 .WithTag("Enables Y direction in tap recognition (TAP_Y_EN)", 6, 1)
686                 .WithTag("Enables X direction in tap recognition (TAP_X_EN)", 7, 1);
687 
688             Registers.InterruptDuration.Define(this)
689                 .WithTag("Maximum duration of over-threshold event (SHOCK)", 0, 2)
690                 .WithTag("Expected quiet time after a tap detection (QUIET)", 2, 2)
691                 .WithTag("Duration of maximum time gap for double-tap recognition (LATENCY)", 4, 4);
692 
693             Registers.WakeupThreshhold.Define(this)
694                 .WithTag("Wakeup threshold  (WK_THS)", 0, 6)
695                 .WithTag("Sleep (inactivity) enable (SLEEP_ON)", 6, 1)
696                 .WithTag("Enable single/double-tap event (SINGLE_DOUBLE_TAP)", 7, 1);
697 
698             Registers.WakeupAndSleepDuration.Define(this)
699                 .WithTag("Duration to go in sleep mode (SLEEP_DUR)", 0, 4)
700                 .WithTag("Enable stationary detection / motion detection (STATIONARY)", 4, 1)
701                 .WithTag("Wakeup duration (WAKE_DUR)", 5, 2)
702                 .WithTag("Free-fall duration (FF_DUR5)", 7, 1);
703 
704             Registers.FreeFall.Define(this)
705                 .WithTag("Free-fall threshold (FF_THS)", 0, 3)
706                 .WithTag("Free-fall duration (FF_DUR)", 3, 5);
707 
708             Registers.StatusEventDetection.Define(this)
709                 .WithFlag(0, valueProviderCallback: _ => outDataRate.Value != DataRateConfig.PowerDown, name: "Data-ready status (DRDY)")
710                 .WithTaggedFlag("Free-fall event detection status (FF_IA)", 1)
711                 .WithTaggedFlag("Source of change in position portrait/landscape/face-up/face-down (6D_IA)", 2)
712                 .WithTaggedFlag("Single-tap event status (SINGLE_TAP)", 3)
713                 .WithTaggedFlag("Double-tap event status (DOUBLE_TAP)", 4)
714                 .WithTaggedFlag("Sleep event status (SLEEP_STATE_IA)", 5)
715                 .WithFlag(6, valueProviderCallback: _ => outDataRate.Value != DataRateConfig.PowerDown, name: "Temperature status (DRDY_T)")
716                 .WithFlag(7, name: "FIFO overrun status flag (OVR)",
717                     valueProviderCallback: _ => FifoOverrunOccurred);
718 
719             Registers.WakeupSource.Define(this)
720                 .WithTaggedFlag("Wakeup event detection status on Z-axis (Z_WU)", 0)
721                 .WithTaggedFlag("Wakeup event detection status on Y-axis (Y_WU)", 1)
722                 .WithTaggedFlag("Wakeup event detection status on X-axis (X_WU)", 2)
723                 .WithTaggedFlag("Wakeup event detection status (WU_IA)", 3)
724                 .WithTaggedFlag("Sleep event status (SLEEP_STATE_IA)", 4)
725                 .WithTaggedFlag("Free-fall event detection status (FF_IA)", 5)
726                 .WithReservedBits(6, 2);
727 
728             Registers.TapSource.Define(this)
729                 .WithTaggedFlag("Tap event detection status on Z-axis (Z_TAP)", 0)
730                 .WithTaggedFlag("Tap event detection status on Y-axis (Y_TAP)", 1)
731                 .WithTaggedFlag("Tap event detection status on X-axis (X_TAP)", 2)
732                 .WithTaggedFlag("Sign of acceleration detected by tap event (TAP_SIGN)", 3)
733                 .WithTaggedFlag("Double-tap event status (DOUBLE_TAP)", 4)
734                 .WithTaggedFlag("Single-tap event status (SINGLE_TAP)", 5)
735                 .WithTaggedFlag("Tap event status (TAP_IA)", 6)
736                 .WithReservedBits(7, 1);
737 
738             Registers.SourceOf6D.Define(this)
739                 .WithTaggedFlag("XL over threshold (XL)", 0)
740                 .WithTaggedFlag("XH over threshold (XH)", 1)
741                 .WithTaggedFlag("YL over threshold (YL)", 2)
742                 .WithTaggedFlag("YH over threshold (YH)", 3)
743                 .WithTaggedFlag("ZL over threshold (ZL)", 4)
744                 .WithTaggedFlag("ZH over threshold (ZL)", 5)
745                 .WithTaggedFlag("Source of change in position portrait/landscape/face-up/face-down (6D_IA)", 6)
746                 .WithReservedBits(7, 1);
747 
748             Registers.InterruptReset.Define(this)
749                 .WithTaggedFlag("Free-fall event detection status (FF_IA)", 0)
750                 .WithTaggedFlag("Wakeup event detection status (WU_IA)", 1)
751                 .WithTaggedFlag("Single-tap event status (SINGLE_TAP)", 2)
752                 .WithTaggedFlag("Double-tap event status (DOUBLE_TAP)", 3)
753                 .WithTaggedFlag("Source of change in position portrait/landscape/face-up/face-down (6D_IA)", 4)
754                 .WithTaggedFlag("Sleep change status (SLEEP_CHANGE_IA)", 5)
755                 .WithReservedBits(6, 2);
756 
757             Registers.UserOffsetX.Define(this)
758                 .WithTag("Two's complement user offset value on X-axis data, used for wakeup function (X_OFS_USR)", 0, 8);
759 
760             Registers.UserOffsetY.Define(this)
761                 .WithTag("Two's complement user offset value on Y-axis data, used for wakeup function (Y_OFS_USR)", 0, 8);
762 
763             Registers.UserOffsetZ.Define(this)
764                 .WithTag("Two's complement user offset value on Z-axis data, used for wakeup function (Z_OFS_USR)", 0, 8);
765 
766             Registers.Control7.Define(this)
767                 .WithTaggedFlag("Low pass filtered data sent to 6D interrupt function (LPASS_ON6D)", 0)
768                 .WithTaggedFlag("High-pass filter reference mode enable (HP_REF_MODE)", 1)
769                 .WithTaggedFlag("Selects the weight of the user offset words (USR_OFF_W)", 2)
770                 .WithTaggedFlag("Enable application of user offset value on XL data for wakeup function only (USR_OFF_ON_WU)", 3)
771                 .WithTaggedFlag("Enable application of user offset value on XL output data registers (USR_OFF_ON_OUT)", 4)
772                 .WithFlag(5, out eventInterruptEnable, name: "Enable interrupts (INTERRUPTS_ENABLE)")
773                 .WithTaggedFlag("Signal routing (INT2_ON_INT1)", 6)
774                 .WithTaggedFlag("Switches between latched and pulsed mode for data ready interrupt (DRDY_PULSED)", 7)
775                 .WithChangeCallback((_,__) => UpdateInterrupts());
776         }
777 
RegistersAutoIncrement()778         private void RegistersAutoIncrement()
779         {
780             if(regAddress >= Registers.DataOutXLow && regAddress < Registers.DataOutZHigh
781                 || regAddress == Registers.TemperatureOutLow)
782             {
783                 regAddress = (Registers)((int)regAddress + 1);
784                 this.Log(LogLevel.Noisy, "Auto-incrementing to the next register 0x{0:X} - {0}", regAddress);
785             }
786             else if((fifoModeSelection.Value == FIFOModeSelection.FIFOMode) && (regAddress == Registers.DataOutZHigh))
787             {
788                 regAddress = Registers.DataOutXLow;
789             }
790         }
791 
UpdateInterrupts()792         private void UpdateInterrupts()
793         {
794             if(outDataRate.Value == DataRateConfig.PowerDown)
795             {
796                 Interrupt1.Unset();
797                 Interrupt2.Unset();
798                 return;
799             }
800 
801             var int1Status =
802                 readyEnabledAcceleration[0].Value ||
803                 fifoThresholdEnabled[0].Value && FifoThresholdReached ||
804                 fifoFullEnabled[0].Value && FifoFull;
805             var int2Status =
806                 readyEnabledAcceleration[1].Value ||
807                 fifoThresholdEnabled[1].Value && FifoThresholdReached ||
808                 fifoOverrunEnabled.Value && FifoOverrunOccurred ||
809                 fifoFullEnabled[1].Value && FifoFull;
810 
811             this.Log(LogLevel.Noisy, "Setting interrupts: INT1 = {0}, INT2 = {1}", int1Status, int2Status);
812             Interrupt1.Set(int1Status);
813             Interrupt2.Set(int2Status);
814         }
815 
IsTemperatureOutOfRange(decimal temperature)816         private bool IsTemperatureOutOfRange(decimal temperature)
817         {
818             // This range protects from the overflow of the short variables in the 'Convert' function.
819             if (temperature < MinTemperature || temperature > MaxTemperature)
820             {
821                 this.Log(LogLevel.Warning, "Temperature {0} is out of range, use value from the range <{1:F2};{2:F2}>", temperature, MinTemperature, MaxTemperature);
822                 return true;
823             }
824             return false;
825         }
826 
IsAccelerationOutOfRange(decimal acceleration)827         private bool IsAccelerationOutOfRange(decimal acceleration)
828         {
829             // This range protects from the overflow of the short variables in the 'Convert' function.
830             if (acceleration < MinAcceleration || acceleration > MaxAcceleration)
831             {
832                 this.Log(LogLevel.Warning, "Acceleration {0} is out of range, use value from the range <{1:F2};{2:F2}>", acceleration, MinAcceleration, MaxAcceleration);
833                 return true;
834             }
835             return false;
836         }
837 
Convert(decimal value, bool upperByte)838         private byte Convert(decimal value, bool upperByte)
839         {
840             byte result = 0;
841             if(outDataRate.Value == DataRateConfig.PowerDown)
842             {
843                 return result;
844             }
845 
846             var minValue = MinValue14Bit;
847             var maxValue = MaxValue14Bit;
848             var shift = 2;
849             // Divide the divider by 4 because the divider for full scale = 2 g
850             // is 4 and the base sensitivity is given for this scale setting
851             var sensitivity = BaseSensitivity * (scaleDivider / 4);
852 
853             if(modeSelection.Value == ModeSelection.HighPerformance)
854             {
855                 this.Log(LogLevel.Noisy, "High performance (14-bit resolution) mode is selected.");
856             }
857             else if((modeSelection.Value == ModeSelection.LowPower) && (lowPowerModeSelection.Value != LowPowerModeSelection.LowPowerMode1_12bitResolution))
858             {
859                 this.Log(LogLevel.Noisy, "Low power (14-bit resolution) mode is selected.");
860             }
861             else if((modeSelection.Value == ModeSelection.LowPower) && (lowPowerModeSelection.Value == LowPowerModeSelection.LowPowerMode1_12bitResolution))
862             {
863                 this.Log(LogLevel.Noisy, "Low power (12-bit resolution) mode is selected.");
864                 minValue = MinValue12Bit;
865                 maxValue = MaxValue12Bit;
866                 shift = 4;
867                 sensitivity *= 4;
868             }
869             else
870             {
871                 this.Log(LogLevel.Noisy, "Other conversion mode selected.");
872             }
873 
874             var valueAsInt = ((int)(value * 1000 / sensitivity)).Clamp(minValue, maxValue);
875             var valueAsUshort = (ushort)(valueAsInt << shift); // left-align
876             this.Log(LogLevel.Noisy, "Conversion done with sensitivity: {0:F3}, result: 0x{1:X4}", sensitivity, valueAsUshort);
877 
878             if(upperByte)
879             {
880                 return (byte)(valueAsUshort >> 8);
881             }
882             result = (byte)(valueAsUshort);
883             UpdateInterrupts();
884 
885             return result;
886         }
887 
InternalWrite(byte[] data)888         private void InternalWrite(byte[] data)
889         {
890             for(var i = 0; i < data.Length; i++)
891             {
892                 this.Log(LogLevel.Noisy, "Writing 0x{0:X} to register {1} (0x{1:X})", data[i], regAddress);
893                 RegistersCollection.Write((byte)regAddress, data[i]);
894                 if(autoIncrement.Value)
895                 {
896                     RegistersAutoIncrement();
897                 }
898             }
899         }
900 
TwoComplementSignConvert(decimal temp)901         private ushort TwoComplementSignConvert(decimal temp)
902         {
903             var tempAsUshort = Decimal.ToUInt16(Math.Abs(temp));
904             if(temp < 0)
905             {
906                 var twoComplementTemp = (ushort)(~tempAsUshort + 1);
907                 return twoComplementTemp;
908             }
909             return tempAsUshort;
910         }
911 
SetScaleDivider()912         private void SetScaleDivider()
913         {
914             switch(fullScale.Value)
915             {
916                 case FullScaleSelect.FullScale4g:
917                     scaleDivider = 8;
918                     break;
919                 case FullScaleSelect.FullScale8g:
920                     scaleDivider = 16;
921                     break;
922                 case FullScaleSelect.FullScale16g:
923                     scaleDivider = 32;
924                     break;
925                 default:
926                     scaleDivider = 4;
927                     break;
928             }
929         }
930 
931         public Vector3DSample DefaultSample { get; } = new Vector3DSample();
932         private Vector3DSample CurrentSample { get; set; } = new Vector3DSample();
933         private decimal SelfTestAccelerationOffset =>
934             SelfTestAcceleration * (selfTestMode.Value == SelfTestMode.PositiveSign ? 1 : selfTestMode.Value == SelfTestMode.NegativeSign ? -1 : 0);
935         private decimal ReportedAccelerationX => AccelerationX + SelfTestAccelerationOffset;
936         private decimal ReportedAccelerationY => AccelerationY + SelfTestAccelerationOffset;
937         private decimal ReportedAccelerationZ => AccelerationZ + SelfTestAccelerationOffset;
938 
939         private decimal ReportedTemperature => Temperature - TemperatureBias;
940 
941         private bool FifoThresholdReached => fifoThreshold.Value != 0 && accelerationFifo.SamplesCount >= fifoThreshold.Value;
942         private bool FifoFull => accelerationFifo.Full;
943         private bool FifoOverrunOccurred => accelerationFifo.OverrunOccurred;
944 
945         private IFlagRegisterField autoIncrement;
946         private IFlagRegisterField[] readyEnabledAcceleration;
947         private IFlagRegisterField[] fifoThresholdEnabled;
948         private IFlagRegisterField[] fifoFullEnabled;
949         // Interrupt on overrun is only avaliable on INT2
950         private IFlagRegisterField fifoOverrunEnabled;
951         private IFlagRegisterField readyEnabledTemperature;
952         // This flag controls whether 6D, tap, fall, etc. interrupts are enabled and is not a global
953         // flag for all interrupts (FIFO and others)
954         private IFlagRegisterField eventInterruptEnable;
955         private IValueRegisterField fifoThreshold;
956         private IEnumRegisterField<LowPowerModeSelection> lowPowerModeSelection;
957         private IEnumRegisterField<DataRateConfig> outDataRate;
958         private IEnumRegisterField<ModeSelection> modeSelection;
959         private IEnumRegisterField<FIFOModeSelection> fifoModeSelection;
960         private IEnumRegisterField<FullScaleSelect> fullScale;
961         private IEnumRegisterField<SelfTestMode> selfTestMode;
962         private uint sampleRate;
963 
964         private Registers regAddress;
965         private State state;
966         private int scaleDivider;
967 
968         private IManagedThread feederThread;
969         private RESDStream<AccelerationSample> resdStream;
970 
971         private event Action<uint> SampleRateChanged;
972         // This event is used in MultiFrequency RESD to precisely match RESD behavior with FIFO operation
973         private event Action<uint> FIFOModeEntered;
974 
975         private decimal temperature;
976 
977         private readonly LIS2DW12_FIFO accelerationFifo;
978         private readonly IMachine machine;
979 
980         private const decimal MinTemperature = -40.0m;
981         // The temperature register has the value 0 at this temperature
982         private const decimal TemperatureBias = 25.0m;
983         private const decimal MaxTemperature = 85.0m;
984         private const decimal MinAcceleration = -16m;
985         private const decimal MaxAcceleration = 16m;
986         private const decimal SelfTestAcceleration = 1m;
987         // Calculated as floor(1000 / 0xfff, 3), 1000 mg / 12-bit max value, used as a base
988         // to calculate the sensitivity for other ranges/resolutions, multiplying it by 2^n
989         // to match the tables in the datasheet, appnote, and drivers
990         private const decimal BaseSensitivity = 0.244m; // mg / digit
991         private const int MaxFifoSize = 32;
992         private const int MinValue14Bit = -0x2000;
993         private const int MaxValue14Bit = 0x1FFF;
994         private const int MinValue12Bit = -0x0800;
995         private const int MaxValue12Bit = 0x07FF;
996         private const int TemperatureLsbsPerDegree = 16;
997 
998         public enum RESDType
999         {
1000             Normal,
1001             MultiFrequency,
1002         }
1003 
1004         private class LIS2DW12_FIFO
1005         {
LIS2DW12_FIFO(LIS2DW12 owner, string name, uint capacity)1006             public LIS2DW12_FIFO(LIS2DW12 owner, string name, uint capacity)
1007             {
1008                 Capacity = capacity;
1009                 this.name = name;
1010                 this.owner = owner;
1011                 this.locker = new object();
1012                 this.queue = new Queue<Vector3DSample>();
1013             }
1014 
FeedAccelerationSample(decimal x, decimal y, decimal z)1015             public void FeedAccelerationSample(decimal x, decimal y, decimal z)
1016             {
1017                 var sample = new Vector3DSample(x, y, z);
1018                 FeedSample(sample);
1019             }
1020 
FeedSample(Vector3DSample sample)1021             public void FeedSample(Vector3DSample sample)
1022             {
1023                 lock(locker)
1024                 {
1025                     latestSample = sample;
1026 
1027                     if(KeepFifoOnReset)
1028                     {
1029                         queue.Enqueue(sample);
1030                         return;
1031                     }
1032 
1033                     if(Mode == FIFOModeSelection.FIFOMode && Full)
1034                     {
1035                         return;
1036                     }
1037                     else if(Mode == FIFOModeSelection.Continuous && Full)
1038                     {
1039                         if(!OverrunOccurred)
1040                         {
1041                             owner.Log(LogLevel.Debug, "{0}: Overrun", name);
1042                             OverrunOccurred = true;
1043                             OnOverrun?.Invoke();
1044                         }
1045 
1046                         owner.Log(LogLevel.Noisy, "{0}: Fifo filled up. Dumping the oldest sample.", name);
1047                         queue.TryDequeue<Vector3DSample>(out _);
1048                     }
1049                     owner.Log(LogLevel.Noisy, "Enqueued sample {0} at index {1}", sample, queue.Count);
1050                     queue.Enqueue(sample);
1051                 }
1052             }
1053 
1054             // Old, deprecated API. Added because this peripheral already included a public FeedSamplesFromFile method.
FeedSamplesFromFile(string path)1055             public void FeedSamplesFromFile(string path)
1056             {
1057                 try
1058                 {
1059                     using(var reader = File.OpenText(path))
1060                     {
1061                         string line;
1062                         while((line = reader.ReadLine()) != null)
1063                         {
1064                             line = line.Trim();
1065 
1066                             if(line.StartsWith("#"))
1067                             {
1068                                 continue;
1069                             }
1070 
1071                             var numbers = line.Split(new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray();
1072                             var sample = new Vector3DSample();
1073                             if(!sample.TryLoad(numbers))
1074                             {
1075                                 sample = null;
1076                             }
1077                             FeedSample(sample);
1078                         }
1079                     }
1080                 }
1081                 catch(Exception e)
1082                 {
1083                     if(e is RecoverableException)
1084                     {
1085                         throw;
1086                     }
1087 
1088                     throw new RecoverableException($"There was a problem when reading samples file: {e.Message}");
1089                 }
1090             }
1091 
Reset()1092             public void Reset()
1093             {
1094                 owner.Log(LogLevel.Debug, "Resetting FIFO");
1095                 OverrunOccurred = false;
1096 
1097                 if(KeepFifoOnReset)
1098                 {
1099                     owner.Log(LogLevel.Debug, "Keeping existing FIFO content");
1100                     return;
1101                 }
1102 
1103                 queue.Clear();
1104                 latestSample = null;
1105             }
1106 
TryDequeueNewSample()1107             public bool TryDequeueNewSample()
1108             {
1109                 lock(locker)
1110                 {
1111                     if(CheckEnabled() && queue.TryDequeue(out var sample))
1112                     {
1113                         owner.Log(LogLevel.Noisy, "New sample dequeued: {0}", sample);
1114                         latestSample = sample;
1115                         return true;
1116                     }
1117                 }
1118                 // If we're feeding from a file, the sensor might have been polled between
1119                 // two samples - in this case, the earlier of these should be returned. Otherwise,
1120                 // clearing the FIFO means we ran out of data and so we should go to the default
1121                 // sample.
1122                 if(!Disabled)
1123                 {
1124                     latestSample = null;
1125                 }
1126                 owner.Log(LogLevel.Noisy, "Dequeueing new sample failed.");
1127                 return false;
1128             }
1129 
1130             public bool KeepFifoOnReset { get; set; }
1131 
1132             public uint SamplesCount => (uint)Math.Min(queue.Count, Capacity);
1133             public bool Disabled => Mode == FIFOModeSelection.Bypass && !KeepFifoOnReset;
1134             public bool Empty => SamplesCount == 0;
1135             public bool Full => SamplesCount >= Capacity;
1136 
1137             public FIFOModeSelection Mode => owner.fifoModeSelection.Value;
1138 
1139             public bool OverrunOccurred { get; private set; }
1140             public Vector3DSample Sample => latestSample ?? owner.DefaultSample;
1141 
1142             public event Action OnOverrun;
1143 
CheckEnabled()1144             private bool CheckEnabled()
1145             {
1146                 if(Disabled)
1147                 {
1148                     owner.Log(LogLevel.Debug, "Sample unavailable -- FIFO disabled.");
1149                     return false;
1150                 }
1151                 return true;
1152             }
1153 
1154             private Vector3DSample latestSample;
1155 
1156             private readonly object locker;
1157             private readonly Queue<Vector3DSample> queue;
1158             private readonly string name;
1159             private readonly LIS2DW12 owner;
1160             private readonly uint Capacity;
1161         }
1162 
1163         private enum State
1164         {
1165             WaitingForRegister,
1166             WaitingForData
1167         }
1168 
1169         private enum FullScaleSelect : byte
1170         {
1171             FullScale2g = 0x00,
1172             FullScale4g = 0x01,
1173             FullScale8g = 0x02,
1174             FullScale16g = 0x03,
1175         }
1176 
1177         private enum DataRateConfig : byte
1178         {
1179             PowerDown = 0x00,
1180             HighPerformanceLowPower1_6Hz = 0x01,
1181             HighPerformanceLowPower12_5Hz = 0x02,
1182             HighPerformanceLowPower25Hz = 0x03,
1183             HighPerformanceLowPower50Hz = 0x04,
1184             HighPerformanceLowPower100Hz = 0x05,
1185             HighPerformanceLowPower200Hz = 0x06,
1186             HighPerformanceLowPower400Hz = 0x07,
1187             HighPerformanceLowPower800Hz = 0x08,
1188             HighPerformanceLowPower1600Hz = 0x09,
1189         }
1190 
1191         private enum ModeSelection : byte
1192         {
1193             LowPower = 0x00,
1194             HighPerformance = 0x01,
1195             SingleDataConversionOnDemand = 0x02,
1196             Reserved = 0x03
1197         }
1198 
1199         private enum FIFOModeSelection : byte
1200         {
1201             Bypass = 0x00,
1202             FIFOMode = 0x01,
1203             // Reserved: 0x02
1204             ContinuousToFIFO = 0x03,
1205             BypassToContinuous = 0x04,
1206             // Reserved: 0x05
1207             Continuous = 0x06,
1208             // Reserved: 0x07
1209         }
1210 
1211         private enum LowPowerModeSelection : byte
1212         {
1213             LowPowerMode1_12bitResolution = 0x00,
1214             LowPowerMode2_14bitResolution = 0x01,
1215             LowPowerMode3_14bitResolution = 0x02,
1216             LowPowerMode4_14bitResolution = 0x03
1217         }
1218 
1219         private enum SelfTestMode : byte
1220         {
1221             Disabled = 0x00,
1222             PositiveSign = 0x01,
1223             NegativeSign = 0x02,
1224             // Reserved: 0x03
1225         }
1226 
1227         private enum Registers : byte
1228         {
1229             // Reserved: 0x0 - 0xC
1230             TemperatureOutLow = 0xD,
1231             TemperatureOutHigh = 0xE,
1232             WhoAmI = 0x0F,
1233             // Reserved: 0x10 - 0x1F
1234             Control1 = 0x20,
1235             Control2 = 0x21,
1236             Control3 = 0x22,
1237             Control4 = 0x23,
1238             Control5 = 0x24,
1239             Control6 = 0x25,
1240             TemperatureOut = 0x26,
1241             Status = 0x27,
1242             DataOutXLow = 0x28,
1243             DataOutXHigh = 0x29,
1244             DataOutYLow = 0x2A,
1245             DataOutYHigh = 0x2B,
1246             DataOutZLow = 0x2C,
1247             DataOutZHigh = 0x2D,
1248             FifoControl = 0x2E,
1249             FifoSamples = 0x2F,
1250             TapThreshholdX = 0x30,
1251             TapThreshholdY = 0x31,
1252             TapThreshholdZ = 0x32,
1253             InterruptDuration = 0x33,
1254             WakeupThreshhold = 0x34,
1255             WakeupAndSleepDuration = 0x35,
1256             FreeFall = 0x36,
1257             StatusEventDetection = 0x37,
1258             WakeupSource = 0x38,
1259             TapSource = 0x39,
1260             SourceOf6D = 0x3A,
1261             InterruptReset = 0x3B,
1262             UserOffsetX = 0x3C,
1263             UserOffsetY = 0x3D,
1264             UserOffsetZ = 0x3E,
1265             Control7 = 0x3F
1266         }
1267     }
1268 }
1269