1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 
8 using System;
9 using System.Collections.Generic;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Debugging;
12 using Antmicro.Renode.Time;
13 using Antmicro.Renode.Core.Structure.Registers;
14 using Antmicro.Renode.Logging;
15 using Antmicro.Renode.Peripherals.Sensor;
16 using Antmicro.Renode.Peripherals.SPI;
17 using Antmicro.Renode.Utilities;
18 using Antmicro.Renode.Utilities.RESD;
19 
20 namespace Antmicro.Renode.Peripherals.Sensors
21 {
22     public class LSM6DSO_IMU : BasicBytePeripheral, ISPIPeripheral, IProvidesRegisterCollection<ByteRegisterCollection>, ITemperatureSensor, IUnderstandRESD
23     {
LSM6DSO_IMU(IMachine machine)24         public LSM6DSO_IMU(IMachine machine) : base(machine)
25         {
26             Interrupt1 = new GPIO();
27 
28             commonFifo = new LSM6DSO_FIFO(machine, this, "fifo", MaxFifoWords);
29             commonFifo.OnOverrun += UpdateInterrupts;
30 
31             DefineRegisters();
32             Reset();
33         }
34 
FeedAccelerationSample(decimal x, decimal y, decimal z, uint repeat = 1)35         public void FeedAccelerationSample(decimal x, decimal y, decimal z, uint repeat = 1)
36         {
37             for(var i = 0; i < repeat; i++)
38             {
39                 commonFifo.FeedAccelerationSample(x, y, z);
40             }
41         }
42 
FeedAccelerationSamplesFromRESD(string path, uint channel = 0, ulong startTime = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)43         public void FeedAccelerationSamplesFromRESD(string path, uint channel = 0, ulong startTime = 0,
44             RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)
45         {
46             accelerometerResdStream = this.CreateRESDStream<AccelerationSample>(path, channel, sampleOffsetType, sampleOffsetTime);
47             accelerometerFeederThread?.Stop();
48             accelerometerFeederThread = accelerometerResdStream.StartSampleFeedThread(this,
49                 DataRateToFrequency(accelerometerFifoBatchingDataRateSelection.Value),
50                 startTime: startTime
51             );
52         }
53 
FeedAngularRateSample(decimal x, decimal y, decimal z, uint repeat = 1)54         public void FeedAngularRateSample(decimal x, decimal y, decimal z, uint repeat = 1)
55         {
56             for(var i = 0; i < repeat; i++)
57             {
58                 commonFifo.FeedAngularRateSample(x, y, z);
59             }
60         }
61 
FeedAngularRateSamplesFromRESD(string path, uint channel = 0, ulong startTime = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)62         public void FeedAngularRateSamplesFromRESD(string path, uint channel = 0, ulong startTime = 0,
63             RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)
64         {
65             gyroResdStream = this.CreateRESDStream<AngularRateSample>(path, channel, sampleOffsetType, sampleOffsetTime);
66             gyroFeederThread?.Stop();
67             gyroFeederThread = gyroResdStream.StartSampleFeedThread(this,
68                 DataRateToFrequency(gyroscopeFifoBatchingDataRateSelection.Value),
69                 startTime: startTime
70             );
71         }
72 
FeedTemperatureSamplesFromRESD(string path, uint channel)73         public void FeedTemperatureSamplesFromRESD(string path, uint channel)
74         {
75             temperatureResdStream = this.CreateRESDStream<TemperatureSample>(path, channel);
76         }
77 
FinishTransmission()78         public void FinishTransmission()
79         {
80             this.Log(LogLevel.Noisy, "Finishing transmission, going to the Idle state");
81             commandInProgress = CommandTypes.None;
82         }
83 
Reset()84         public override void Reset()
85         {
86             SoftwareReset();
87 
88             accelerometerFeederThread?.Stop();
89             accelerometerResdStream?.Dispose();
90             accelerometerResdStream = null;
91             accelerometerFeederThread = null;
92 
93             gyroFeederThread?.Stop();
94             gyroResdStream?.Dispose();
95             gyroResdStream = null;
96             gyroFeederThread = null;
97 
98             temperatureResdStream?.Dispose();
99             temperatureResdStream = null;
100         }
101 
Transmit(byte data)102         public byte Transmit(byte data)
103         {
104             var value = (byte)0;
105             // Datasheet page 20
106             switch(commandInProgress)
107             {
108                 case CommandTypes.None:
109                     // The first transmission byte:
110                     // b0-b6: address
111                     // b7: 0 -- write; 1 -- read
112                     address = BitHelper.GetValue(data, offset: 0, size: 7);
113                     commandInProgress = (CommandTypes)BitHelper.GetValue(data, offset: IOTypeFlagPosition, size: 1);
114                     this.Log(LogLevel.Noisy, "Received 0x{0:X2}; setting commandInProgress to {1} and address to 0x{2:X2}", data, commandInProgress, address);
115                     break;
116                 case CommandTypes.Read:
117                     value = ReadByte(address);
118                     this.Log(LogLevel.Noisy, "Read from 0x{0:X2} ({1}): returning 0x{2:X2}", address, (Registers)address, value);
119                     TryIncrementAddress();
120                     break;
121                 case CommandTypes.Write:
122                     this.Log(LogLevel.Noisy, "Write to 0x{0:X2} ({1}): 0x{2:X2}", address, (Registers)address, data);
123                     WriteByte(address, data);
124                     TryIncrementAddress();
125                     break;
126                 default:
127                     throw new ArgumentException($"Invalid commandInProgress: {commandInProgress}");
128             }
129             return value;
130         }
131 
132         public bool FifoOverrunStatus => commonFifo.OverrunOccurred;
133         public uint FifoWatermarkThreshold => (uint)fifoThresholdBits0_7.Value | (fifoThresholdBit8.Value ? 0x100u : 0x0u);
134         public bool IsAccelerometerDataBatchedInFifo => IsDataRateEnabledAndDefined(accelerometerFifoBatchingDataRateSelection.Value);
135         public bool IsAccelerometerPoweredOn => IsDataRateEnabledAndDefined(accelerometerOutputDataRateSelection.Value);
136         public bool IsGyroscopeDataBatchedInFifo => IsDataRateEnabledAndDefined(gyroscopeFifoBatchingDataRateSelection.Value);
137         public bool IsGyroscopePoweredOn => IsDataRateEnabledAndDefined(gyroscopeOutputDataRateSelection.Value, isGyroscopeOutputDataRate: true);
138         public GPIO Interrupt1 { get; }
139 
140         public decimal DefaultAccelerationX
141         {
142             get => defaultAccelerationX;
143             set => defaultAccelerationX = value;
144         }
145 
146         public decimal DefaultAccelerationY
147         {
148             get => defaultAccelerationY;
149             set => defaultAccelerationY = value;
150         }
151 
152         public decimal DefaultAccelerationZ
153         {
154             get => defaultAccelerationZ;
155             set => defaultAccelerationZ = value;
156         }
157 
158         public decimal DefaultAngularRateX
159         {
160             get => defaultAngularRateX;
161             set => defaultAngularRateX = value;
162         }
163 
164         public decimal DefaultAngularRateY
165         {
166             get => defaultAngularRateY;
167             set => defaultAngularRateY = value;
168         }
169 
170         public decimal DefaultAngularRateZ
171         {
172             get => defaultAngularRateZ;
173             set => defaultAngularRateZ = value;
174         }
175 
176         public decimal Temperature { get; set; }
177 
DefineRegisters()178         protected override void DefineRegisters()
179         {
180             Registers.PinControl.Define(this)
181                 // These bits are always set.
182                 .WithValueField(0, 6, FieldMode.Read, valueProviderCallback: _ => 0x3F)
183                 .WithTaggedFlag("SDO_PU_EN", 6)
184                 .WithTaggedFlag("OIS_PU_DIS", 7)
185                 ;
186 
187             Registers.WhoAmI.Define(this)
188                 .WithValueField(0, 8, FieldMode.Read, name: "WHO_AM_I", valueProviderCallback: _ => 0x6C)
189                 ;
190 
191             Registers.Control1_Accelerometer.Define(this)
192                 .WithReservedBits(0, 1)
193                 .WithTaggedFlag("LPF2_XL_EN: Accelerometer high-resolution selection", 1)
194                 .WithEnumField(2, 2, out accelerationFullScaleSelection, name: "FS_XL: Accelerometer full-scale selection",
195                     changeCallback: (_, __) => UpdateAccelerationSensitivity())
196                 .WithEnumField(4, 4, out accelerometerOutputDataRateSelection, name: "ODR_XL: Accelerometer Output Data Rate")
197                 ;
198 
199             Registers.Control2_Gyroscope.Define(this)
200                 .WithReservedBits(0, 1)
201                 .WithFlag(1, out angularRate125DPSSelection, name: "FS_125: Selects gyro UI chain full-scale 125 dps",
202                     changeCallback: (_, __) => UpdateAngularRateSensitivity())
203                 .WithEnumField(2, 2, out angularRateFullScaleSelection, name: "FS_G: Gyroscope UI chain full-scale selection",
204                     changeCallback: (_, __) => UpdateAngularRateSensitivity())
205                 .WithEnumField(4, 4, out gyroscopeOutputDataRateSelection, name: "ODR_G: Gyroscope Output Data Rate")
206                 ;
207 
208             Registers.Control3.Define(this, resetValue: 0b00000100)
209                 .WithFlag(0, FieldMode.WriteOneToClear, name: "SW_RESET", changeCallback: (_, newValue) => { if(newValue) SoftwareReset(); })
210                 .WithReservedBits(1, 1)
211                 .WithFlag(2, out addressAutoIncrement, name: "IF_INC")
212                 .WithTaggedFlag("SIM", 3)
213                 .WithTaggedFlag("PP_OD", 4)
214                 .WithTaggedFlag("H_LACTIVE", 5)
215                 .WithTaggedFlag("BDU", 6)
216                 .WithTaggedFlag("BOOT", 7)
217                 ;
218 
219             Registers.Control8_Accelerometer.Define(this)
220                 .WithTaggedFlag("LOW_PASS_ON_6D", 0)
221                 .WithFlag(1, out accelerationFullScaleMode, name: "XL_FS_MODE: Accelerometer full-scale management between UI chain and OIS chain",
222                     changeCallback: (_, __) => UpdateAccelerationSensitivity())
223                 .WithTaggedFlag("HP_SLOPE_XL_EN", 2)
224                 .WithTaggedFlag("FASTSETTL_MODE_XL", 3)
225                 .WithTaggedFlag("HP_REF_MODE_XL", 4)
226                 .WithTag("HPCF_XL", 5, 3)
227                 ;
228 
229             Registers.TemperatureLow.Define(this)
230                 .WithValueField(0, 8, FieldMode.Read, name: "OUT_TEMP_L", valueProviderCallback: _ =>
231                 {
232                     TryUpdateCurrentTemperatureSample();
233                     return GetScaledTemperatureValue(upperByte: false);
234                 })
235                 ;
236 
237             Registers.TemperatureHigh.Define(this)
238                 .WithValueField(0, 8, FieldMode.Read, name: "OUT_TEMP_H", valueProviderCallback: _ => GetScaledTemperatureValue(upperByte: true))
239                 ;
240 
241             Registers.FifoStatus1.Define(this)
242                 .WithValueField(0, 8, FieldMode.Read, name: "DIFF_FIFO_0-7", valueProviderCallback: _ => BitHelper.GetValue(commonFifo.SamplesCount, 0, 8))
243                 ;
244 
245             Registers.FifoStatus2.Define(this)
246                 .WithValueField(0, 2, FieldMode.Read, name: "DIFF_FIFO_8-9", valueProviderCallback: _ => BitHelper.GetValue(commonFifo.SamplesCount, 8, 2))
247                 .WithReservedBits(2, 1)
248                 // "This bit is reset when this register is read."
249                 .WithFlag(3, FieldMode.Read, name: "FIFO_OVR_LATCHED", valueProviderCallback: _ => FifoOverrunStatus && !previousFifoOverrunStatus)
250                 .WithTaggedFlag("COUNTER_BDR_IA", 4)
251                 // "FIFO will be full at the next Output Data Rate."
252                 // Let's check which sensor provider has both power and FIFO batching enabled. Providers are accelerometer and gyroscope.
253                 // Return true if there are more than 'MAX - (number of such providers)' samples in FIFO.
254                 .WithFlag(5, FieldMode.Read, name: "FIFO_FULL_IA", valueProviderCallback: _ =>
255                 {
256                     var samplesAddedToFifoEveryODR = (IsAccelerometerDataBatchedInFifo && IsAccelerometerPoweredOn ? 1u : 0u) + (IsGyroscopeDataBatchedInFifo && IsGyroscopePoweredOn ? 1u : 0u);
257                     return commonFifo.CountReached(MaxFifoWords - samplesAddedToFifoEveryODR);
258                 })
259                 .WithFlag(6, FieldMode.Read, name: "FIFO_OVR_IA", valueProviderCallback: _ => FifoOverrunStatus)
260                 .WithFlag(7, FieldMode.Read, name: "FIFO_WTM_IA", valueProviderCallback: _ => FifoWatermarkThreshold != 0 && commonFifo.CountReached(FifoWatermarkThreshold))
261                 .WithReadCallback((_, __) => previousFifoOverrunStatus = FifoOverrunStatus)
262                 ;
263 
264             Registers.FifoControl1.Define(this)
265                 .WithValueField(0, 8, out fifoThresholdBits0_7, name: "WTM0-7")
266                 ;
267 
268             Registers.FifoControl2.Define(this)
269                 .WithFlag(0, out fifoThresholdBit8, name: "WTM8")
270                 .WithTag("UNCOPTR_RATE", 1, 2)
271                 .WithReservedBits(3, 1)
272                 .WithTaggedFlag("ODRCHG_EN", 4)
273                 .WithReservedBits(5, 1)
274                 .WithTaggedFlag("FIFO_COMPR_RT_EN", 6)
275                 .WithTaggedFlag("STOP_ON_WTM", 7)
276                 ;
277 
278             Registers.FifoControl3.Define(this)
279                 .WithEnumField(0, 4, out accelerometerFifoBatchingDataRateSelection, name: "BDR_XL",
280                     changeCallback: (_, __) =>
281                     {
282                         UpdateAccelerationSampleFrequency();
283                         if(accelerometerFifoBatchingDataRateSelection.Value != DataRates.Disabled && accelerometerFeederThread == null)
284                         {
285                             accelerometerFeederThread = CreateAccelerationDefaultSampleFeeder();
286                         }
287                     }
288                 )
289                 .WithEnumField(4, 4, out gyroscopeFifoBatchingDataRateSelection, name: "BDR_GY",
290                     changeCallback: (_, __) =>
291                     {
292                         UpdateAngularRateSampleFrequency();
293                         if(gyroscopeFifoBatchingDataRateSelection.Value != DataRates.Disabled && gyroFeederThread == null)
294                         {
295                             gyroFeederThread = CreateAngularRateDefaultSampleFeeder();
296                         }
297                     }
298                 )
299                 ;
300 
301             Registers.FifoControl4.Define(this)
302                 .WithEnumField<ByteRegister, FifoModes>(0, 3, name: "FIFO_MODE", writeCallback: (_, newValue) => commonFifo.Mode = newValue, valueProviderCallback: _ => commonFifo.Mode)
303                 .WithReservedBits(3, 1)
304                 .WithTag("ODR_T_BATCH", 4, 2)
305                 .WithTag("DEC_TS_BATCH", 6, 2)
306                 ;
307 
308             // New sample is dequeued when this register is read as it's the first "FifoOutput" register. There's no information on when samples are dequeued in the datasheet.
309             Registers.FifoOutputTag.Define(this)
310                 .WithTaggedFlag("TAG_PARITY", 0)
311                 .WithTag("TAG_CNT", 1, 2)
312                 .WithEnumField<ByteRegister, FifoTag>(3, 5, FieldMode.Read, valueProviderCallback: _ => commonFifo.TryDequeueNewSample() ? commonFifo.Sample.Tag : FifoTag.UNKNOWN)
313                 ;
314 
315             DefineOutputRegistersGroup(Registers.AccelerometerXLow, "OUT{0}_A", () => commonFifo.AccelerationSample);
316             DefineOutputRegistersGroup(Registers.FifoOutputXLow, "FIFO_DATA_OUT_{0}", () => commonFifo.Sample);
317             DefineOutputRegistersGroup(Registers.GyroscopeXLow, "OUT{0}_G", () => commonFifo.AngularRateSample);
318 
319             Registers.Int1PinControl.Define(this)
320                 .WithTaggedFlag("INT1_DRDY_XL", 0)
321                 .WithTaggedFlag("INT1_DRDY_G", 1)
322                 .WithTaggedFlag("INT1_BOOT", 2)
323                 .WithTaggedFlag("INT1_TH", 3)
324                 .WithFlag(4, out interrupt1EnableFifoOverrun, name: "INT1_FIFO_OVR")
325                 .WithTaggedFlag("INT1_FULL", 5)
326                 .WithTaggedFlag("INT1_CNT_BDR", 6)
327                 .WithTaggedFlag("DEN_DRDY", 7)
328                 .WithWriteCallback((_, __) => UpdateInterrupts())
329                 ;
330         }
331 
DefineOutputRegistersGroup(Registers firstGroupRegister, string nameFormat, Func<LSM6DSO_Vector3DSample> sampleProvider)332         private void DefineOutputRegistersGroup(Registers firstGroupRegister, string nameFormat, Func<LSM6DSO_Vector3DSample> sampleProvider)
333         {
334             var subsequentRegistersInfo = new SampleReadingRegisterInfo[]
335             {
336                 new SampleReadingRegisterInfo { Axis = LSM6DSO_Vector3DSample.Axes.X, NameSuffix = "X_L", UpperByte = false},
337                 new SampleReadingRegisterInfo { Axis = LSM6DSO_Vector3DSample.Axes.X, NameSuffix = "X_H", UpperByte = true},
338                 new SampleReadingRegisterInfo { Axis = LSM6DSO_Vector3DSample.Axes.Y, NameSuffix = "Y_L", UpperByte = false},
339                 new SampleReadingRegisterInfo { Axis = LSM6DSO_Vector3DSample.Axes.Y, NameSuffix = "Y_H", UpperByte = true},
340                 new SampleReadingRegisterInfo { Axis = LSM6DSO_Vector3DSample.Axes.Z, NameSuffix = "Z_L", UpperByte = false},
341                 new SampleReadingRegisterInfo { Axis = LSM6DSO_Vector3DSample.Axes.Z, NameSuffix = "Z_H", UpperByte = true},
342             };
343 
344             firstGroupRegister.DefineMany(this, 6, (register, registerOffset) =>
345             {
346                 var registerInfo = subsequentRegistersInfo[registerOffset];
347                 var name = string.Format(nameFormat, registerInfo.NameSuffix);
348                 register.WithValueField(0, 8, FieldMode.Read, name: name,
349                     valueProviderCallback: _ =>
350                     {
351                         var sample = sampleProvider();
352                         if(sample == null)
353                         {
354                             return 0u;
355                         }
356                         DebugHelper.Assert(sample.IsAccelerationSample || sample.IsAngularRateSample, $"Invalid sample: {sample}");
357 
358                         var sensitivity = sample.IsAccelerationSample ? accelerationSensitivity : angularRateSensitivity;
359                         var _byte = sample.GetScaledValueByte(registerInfo.Axis, sensitivity, registerInfo.UpperByte, out var realScaledValue);
360 
361                         // Log only when reading the lower byte to avoid logging it twice for each value.
362                         if(realScaledValue.HasValue && !registerInfo.UpperByte)
363                         {
364                             var fullScaleSelection = sample.IsAccelerationSample ? $"{GetAccelerationFullScaleValue()}G" : $"{GetAngularRateFullScaleValue()}DPS";
365                             this.Log(LogLevel.Debug, "Invalid value for the current full scale selection ({0}): {1}", fullScaleSelection, realScaledValue.Value);
366                         }
367                         return _byte;
368                     }
369                 );
370             });
371         }
372 
373         [OnRESDSample(SampleType.Acceleration)]
374         [BeforeRESDSample(SampleType.Acceleration)]
HandleAccelerationSample(AccelerationSample sample, TimeInterval timestamp)375         private void HandleAccelerationSample(AccelerationSample sample, TimeInterval timestamp)
376         {
377             if(sample != null)
378             {
379                 commonFifo.FeedAccelerationSample(
380                     (decimal)sample.AccelerationX / 1e6m,
381                     (decimal)sample.AccelerationY / 1e6m,
382                     (decimal)sample.AccelerationZ / 1e6m
383                 );
384             }
385             else
386             {
387                 commonFifo.FeedAccelerationSample(
388                     DefaultAccelerationX,
389                     DefaultAccelerationY,
390                     DefaultAccelerationZ
391                 );
392             }
393         }
394 
395         [AfterRESDSample(SampleType.Acceleration)]
HandleAccelerationSampleEnded(AccelerationSample sample, TimeInterval timestamp)396         private void HandleAccelerationSampleEnded(AccelerationSample sample, TimeInterval timestamp)
397         {
398             accelerometerFeederThread?.Stop();
399             accelerometerFeederThread = CreateAccelerationDefaultSampleFeeder();
400         }
401 
402         [OnRESDSample(SampleType.AngularRate)]
403         [BeforeRESDSample(SampleType.AngularRate)]
HandleAngularRateSample(AngularRateSample sample, TimeInterval timestamp)404         private void HandleAngularRateSample(AngularRateSample sample, TimeInterval timestamp)
405         {
406             if(sample != null)
407             {
408                 commonFifo.FeedAngularRateSample(
409                     RadiansToDegrees * (decimal)sample.AngularRateX / 1e5m,
410                     RadiansToDegrees * (decimal)sample.AngularRateY / 1e5m,
411                     RadiansToDegrees * (decimal)sample.AngularRateZ / 1e5m
412                 );
413             }
414             else
415             {
416                 commonFifo.FeedAngularRateSample(
417                     DefaultAngularRateX,
418                     DefaultAngularRateY,
419                     DefaultAngularRateZ
420                 );
421             }
422         }
423 
424         [AfterRESDSample(SampleType.AngularRate)]
HandleAngularRateSampleEnded(AngularRateSample sample, TimeInterval timestamp)425         private void HandleAngularRateSampleEnded(AngularRateSample sample, TimeInterval timestamp)
426         {
427             gyroFeederThread.Stop();
428             gyroFeederThread = CreateAngularRateDefaultSampleFeeder();
429         }
430 
431 
GetAccelerationFullScaleValue()432         private short GetAccelerationFullScaleValue()
433         {
434             switch(accelerationFullScaleSelection.Value)
435             {
436                 case AccelerationFullScaleSelection.Mode0_2G:
437                     return 2;
438                 case AccelerationFullScaleSelection.Mode1_16G_2G:
439                     return (short)(accelerationFullScaleMode.Value ? 2 : 16);
440                 case AccelerationFullScaleSelection.Mode2_4G:
441                     return 4;
442                 case AccelerationFullScaleSelection.Mode3_8G:
443                     return 8;
444                 default:
445                     throw new Exception("Wrong acceleration full scale selection");
446             }
447         }
448 
449         // Sensitivity values come directly from the datasheet. The unit is 'mDPS/LSB' which is different than typical
450         // 'LSB/DPS' or 'LSB/G' sensitivity is expected to be for proper conversions (e.g. in 'GetScaledValueByte').
GetAngularRateSensitivityInMilliDPSPerLSB()451         private decimal GetAngularRateSensitivityInMilliDPSPerLSB()
452         {
453             var fullScaleValue = GetAngularRateFullScaleValue();
454             switch(fullScaleValue)
455             {
456                 case 125:
457                     return 4.375m;
458                 case 250:
459                     return 8.75m;
460                 case 500:
461                     return 17.5m;
462                 case 1000:
463                     return 35m;
464                 case 2000:
465                     return 70m;
466                 default:
467                     throw new ArgumentException($"Invalid angular rate full scale value: {fullScaleValue}");
468             }
469         }
470 
GetAngularRateFullScaleValue()471         private short GetAngularRateFullScaleValue()
472         {
473             if(angularRate125DPSSelection.Value)
474             {
475                 return 125;
476             }
477 
478             switch(angularRateFullScaleSelection.Value)
479             {
480                 case AngularRateFullScaleSelection.Mode0_250DPS:
481                     return 250;
482                 case AngularRateFullScaleSelection.Mode1_500DPS:
483                     return 500;
484                 case AngularRateFullScaleSelection.Mode2_1000DPS:
485                     return 1000;
486                 case AngularRateFullScaleSelection.Mode3_2000DPS:
487                     return 2000;
488                 default:
489                     throw new Exception("Wrong angular rate full scale selection");
490             }
491         }
492 
GetScaledTemperatureValue(bool upperByte)493         private byte GetScaledTemperatureValue(bool upperByte)
494         {
495             var scaled = (short)((Temperature - TemperatureOffset) * TemperatureSensitivity);
496             return upperByte
497                 ? (byte)(scaled >> 8)
498                 : (byte)scaled;
499         }
500 
IsDataRateEnabledAndDefined(DataRates value, bool isGyroscopeOutputDataRate = false)501         private bool IsDataRateEnabledAndDefined(DataRates value, bool isGyroscopeOutputDataRate = false)
502         {
503             var enabled = value != DataRates.Disabled && Enum.IsDefined(typeof(DataRates), value);
504             if(isGyroscopeOutputDataRate && enabled)
505             {
506                 // This specific selection is invalid for the Gyroscope's Output Data Rate.
507                 enabled = value != DataRates._1_6HzOr6_5HzOr12_5Hz;
508             }
509             return enabled;
510         }
511 
SoftwareReset()512         private void SoftwareReset()
513         {
514             address = 0x0;
515             commandInProgress = CommandTypes.None;
516             previousFifoOverrunStatus = false;
517 
518             commonFifo.Reset();
519 
520             base.Reset();
521 
522             UpdateAccelerationSensitivity();
523             UpdateAngularRateSensitivity();
524             UpdateInterrupts();
525         }
526 
TryUpdateCurrentTemperatureSample()527         private bool TryUpdateCurrentTemperatureSample()
528         {
529             if(temperatureResdStream == null)
530             {
531                 return false;
532             }
533 
534             if(machine.SystemBus.TryGetCurrentCPU(out var cpu))
535             {
536                 cpu.SyncTime();
537             }
538 
539             var currentTimestamp = machine.ClockSource.CurrentValue.TotalNanoseconds;
540             if(temperatureResdStream.TryGetSample(currentTimestamp, out var sample) == RESDStreamStatus.OK)
541             {
542                 Temperature = (decimal)sample.Temperature / 1e3m;
543             }
544 
545             return true;
546         }
547 
TryIncrementAddress()548         private void TryIncrementAddress()
549         {
550             // automatic rounding when accessing FIFO registers
551             if(address == (byte)Registers.FifoOutputZHigh)
552             {
553                 address = (byte)Registers.FifoOutputTag;
554                 return;
555             }
556 
557             if(!addressAutoIncrement.Value)
558             {
559                 return;
560             }
561 
562             // It's undocumented whether 0x0 should really be accessed after 0x7F.
563             address = (byte)((address + 1) % 0x80);
564         }
565 
UpdateAccelerationSensitivity()566         private void UpdateAccelerationSensitivity()
567         {
568             accelerationSensitivity = decimal.Divide(MaxAbsoluteShortValue, GetAccelerationFullScaleValue());
569         }
570 
UpdateAngularRateSensitivity()571         private void UpdateAngularRateSensitivity()
572         {
573             // Gyroscope's sensitivity deviates from a typical '2**15 / full_scale' calculation.
574             // Sensitivities in the datasheet are in milliDPS per LSB so let's convert to LSB per DPS.
575             angularRateSensitivity = 1000m / GetAngularRateSensitivityInMilliDPSPerLSB();
576         }
577 
UpdateInterrupts()578         private void UpdateInterrupts()
579         {
580             var newInt1Status = false;
581             if(interrupt1EnableFifoOverrun.Value && FifoOverrunStatus)
582             {
583                 newInt1Status = true;
584             }
585 
586             if(Interrupt1.IsSet != newInt1Status)
587             {
588                 this.Log(LogLevel.Debug, "New INT1 state: {0}", newInt1Status ? "set" : "reset");
589                 Interrupt1.Set(newInt1Status);
590             }
591         }
592 
DataRateToFrequency(DataRates dr)593         private uint DataRateToFrequency(DataRates dr)
594         {
595             switch(dr)
596             {
597                 // here we select the middle value, rounded down as currently we don't support fractions of Hz when declaring frequencies
598                 case DataRates._1_6HzOr6_5HzOr12_5Hz:
599                     return 5;
600                 case DataRates._12_5Hz:
601                     // here we need to round the value down as currently we don't support fractions of Hz when declaring frequencies
602                     return 12;
603                 case DataRates._26Hz:
604                     return 26;
605                 case DataRates._52Hz:
606                     return 52;
607                 case DataRates._104Hz:
608                     return 104;
609                 case DataRates._208Hz:
610                     return 208;
611                 case DataRates._416Hz:
612                     return 416;
613                 case DataRates._833Hz:
614                     return 833;
615                 case DataRates._1_66kHz:
616                     return 1660;
617                 case DataRates._3_33kHz:
618                     return 3330;
619                 case DataRates._6_66kHz:
620                     return 6660;
621                 case DataRates.Disabled:
622                     return 0;
623                 default:
624                     throw new Exception($"Unexpected data rate: {dr}");
625             }
626         }
627 
628         private RESDStream<AccelerationSample> accelerometerResdStream;
629         private RESDStream<AngularRateSample> gyroResdStream;
630         private RESDStream<TemperatureSample> temperatureResdStream;
631         private IManagedThread accelerometerFeederThread;
632         private IManagedThread gyroFeederThread;
633 
634         private decimal accelerationSensitivity;
635         private byte address;
636         private decimal angularRateSensitivity;
637         private CommandTypes commandInProgress;
638         private bool previousFifoOverrunStatus;
639 
640         private IEnumRegisterField<AccelerationFullScaleSelection> accelerationFullScaleSelection;
641         private IFlagRegisterField accelerationFullScaleMode;
642         private IEnumRegisterField<DataRates> accelerometerFifoBatchingDataRateSelection;
643         private IEnumRegisterField<DataRates> accelerometerOutputDataRateSelection;
644         private IFlagRegisterField addressAutoIncrement;
645         private IFlagRegisterField angularRate125DPSSelection;
646         private IEnumRegisterField<AngularRateFullScaleSelection> angularRateFullScaleSelection;
647         private IValueRegisterField fifoThresholdBits0_7;
648         private IFlagRegisterField fifoThresholdBit8;
649         private IEnumRegisterField<DataRates> gyroscopeFifoBatchingDataRateSelection;
650         private IEnumRegisterField<DataRates> gyroscopeOutputDataRateSelection;
651         private IFlagRegisterField interrupt1EnableFifoOverrun;
652 
653         private readonly LSM6DSO_FIFO commonFifo;
654 
655         private const int IOTypeFlagPosition = 7;
656         private const int MaxAbsoluteShortValue = 32768;
657         private const uint MaxFifoWords = 512;
658         private const short TemperatureOffset = 25;
659         private const short TemperatureSensitivity = 256;
660         private const decimal RadiansToDegrees = 180m / (decimal)Math.PI;
661 
662         private class LSM6DSO_FIFO
663         {
LSM6DSO_FIFO(IMachine machine, LSM6DSO_IMU owner, string name, uint capacity)664             public LSM6DSO_FIFO(IMachine machine, LSM6DSO_IMU owner, string name, uint capacity)
665             {
666                 Capacity = capacity;
667                 this.machine = machine;
668                 this.name = name;
669                 this.owner = owner;
670                 this.locker = new object();
671                 this.queue = new Queue<LSM6DSO_Vector3DSample>();
672             }
673 
CountReached(uint value)674             public bool CountReached(uint value)
675             {
676                 return SamplesCount >= value;
677             }
678 
FeedAccelerationSample(decimal x, decimal y, decimal z)679             public void FeedAccelerationSample(decimal x, decimal y, decimal z)
680             {
681                 var sample = new LSM6DSO_Vector3DSample(DefaultAccelerometerTag, x, y, z);
682                 FeedSample(sample);
683             }
684 
FeedAngularRateSample(decimal x, decimal y, decimal z)685             public void FeedAngularRateSample(decimal x, decimal y, decimal z)
686             {
687                 var sample = new LSM6DSO_Vector3DSample(DefaultGyroscopeTag, x, y, z);
688                 FeedSample(sample);
689             }
690 
FeedSample(LSM6DSO_Vector3DSample sample)691             public void FeedSample(LSM6DSO_Vector3DSample sample)
692             {
693                 lock(locker)
694                 {
695                     if(Mode == FifoModes.Bypass)
696                     {
697                         // In bypass mode don't add samples to queue, just keep like the latest sample.
698                         KeepSample(sample);
699                         return;
700                     }
701 
702                     if(Mode == FifoModes.Continuous && Full)
703                     {
704                         if(!OverrunOccurred)
705                         {
706                             owner.Log(LogLevel.Debug, $"{name}: Overrun");
707                             OverrunOccurred = true;
708                             OnOverrun?.Invoke();
709                         }
710 
711                         owner.Log(LogLevel.Noisy, $"{name}: Fifo filled up. Dumping the oldest sample.");
712                         queue.TryDequeue<LSM6DSO_Vector3DSample>(out _);
713                     }
714 
715                     queue.Enqueue(sample);
716                 }
717             }
718 
Reset()719             public void Reset()
720             {
721                 owner.Log(LogLevel.Debug, "Resetting FIFO");
722 
723                 queue.Clear();
724                 accelerationSample = null;
725                 angularRateSample = null;
726                 mode = FifoModes.Bypass;
727                 OverrunOccurred = false;
728             }
729 
TryDequeueNewSample()730             public bool TryDequeueNewSample()
731             {
732                 lock(locker)
733                 {
734                     if(CheckEnabled() && queue.TryDequeue(out var sample))
735                     {
736                         owner.Log(LogLevel.Noisy, "New sample dequeued: {0}", sample);
737                         KeepSample(sample);
738                         return true;
739                     }
740                 }
741                 owner.Log(LogLevel.Noisy, "Dequeueing new sample failed.");
742                 return false;
743             }
744 
745             public LSM6DSO_Vector3DSample AccelerationSample => GetSample(DefaultAccelerometerTag);
746             public LSM6DSO_Vector3DSample AngularRateSample => GetSample(DefaultGyroscopeTag);
747             public uint SamplesCount => (uint)queue.Count;
748             public bool Disabled => Mode == FifoModes.Bypass;
749             public bool Empty => SamplesCount == 0;
750             public bool Full => SamplesCount >= Capacity;
751 
752             public FifoModes Mode
753             {
754                 get => mode;
755                 set
756                 {
757                     if(value == FifoModes.Bypass)
758                     {
759                         Reset();
760                     }
761                     mode = value;
762                 }
763             }
764 
765             public bool OverrunOccurred { get; private set; }
766             public LSM6DSO_Vector3DSample Sample => latestSample?.Tag != FifoTag.UNKNOWN ? latestSample : null;
767 
768             public event Action OnOverrun;
769 
770             // Currently all Accelerometer/Gyroscope tags are treated the same so it doesn't matter which ones are default.
771             public const FifoTag DefaultAccelerometerTag = FifoTag.AccelerometerNC;
772             public const FifoTag DefaultGyroscopeTag = FifoTag.GyroscopeNC;
773 
CheckEnabled()774             private bool CheckEnabled()
775             {
776                 if(Disabled)
777                 {
778                     owner.Log(LogLevel.Debug, "Sample unavailable -- FIFO disabled.");
779                     return false;
780                 }
781                 return true;
782             }
783 
GetSample(FifoTag tag)784             private LSM6DSO_Vector3DSample GetSample(FifoTag tag)
785             {
786                 LSM6DSO_Vector3DSample sample;
787                 switch(tag)
788                 {
789                     case DefaultAccelerometerTag:
790                         sample = accelerationSample;
791                         break;
792                     case DefaultGyroscopeTag:
793                         sample = angularRateSample;
794                         break;
795                     default:
796                         throw new ArgumentException($"Tried to get sample for an unsupported tag: {tag}");
797                 }
798 
799                 if(sample == null)
800                 {
801                     owner.Log(LogLevel.Warning, "{0}: No sample found with {1} tag", name, tag);
802                 }
803                 return sample;
804             }
805 
KeepSample(LSM6DSO_Vector3DSample sample)806             private void KeepSample(LSM6DSO_Vector3DSample sample)
807             {
808                 latestSample = sample;
809                 if(sample.IsAccelerationSample)
810                 {
811                     accelerationSample = sample;
812                 }
813                 else if(sample.IsAngularRateSample)
814                 {
815                     angularRateSample = sample;
816                 }
817                 else
818                 {
819                     throw new Exception($"Invalid sample to keep: tag={sample.Tag}");
820                 }
821             }
822 
823             private uint Capacity { get; }
824 
825             private LSM6DSO_Vector3DSample accelerationSample;
826             private LSM6DSO_Vector3DSample angularRateSample;
827             private LSM6DSO_Vector3DSample latestSample;
828             private FifoModes mode;
829 
830             private readonly object locker;
831             private readonly Queue<LSM6DSO_Vector3DSample> queue;
832             private readonly IMachine machine;
833             private readonly string name;
834             private readonly LSM6DSO_IMU owner;
835         }
836 
837         private class LSM6DSO_Vector3DSample
838         {
LSM6DSO_Vector3DSample(FifoTag tag)839             public LSM6DSO_Vector3DSample(FifoTag tag)
840             {
841                 Tag = tag;
842             }
843 
LSM6DSO_Vector3DSample(FifoTag tag, decimal x, decimal y, decimal z)844             public LSM6DSO_Vector3DSample(FifoTag tag, decimal x, decimal y, decimal z) : this(tag)
845             {
846                 X = x;
847                 Y = y;
848                 Z = z;
849             }
850 
GetAxisValue(Axes axis)851             public decimal GetAxisValue(Axes axis)
852             {
853                 switch(axis)
854                 {
855                     case Axes.X:
856                         return X;
857                     case Axes.Y:
858                         return Y;
859                     case Axes.Z:
860                         return Z;
861                     default:
862                         throw new Exception($"Invalid Axis: {axis}");
863                 }
864             }
865 
GetScaledValueByte(Axes axis, decimal sensitivity, bool upperByte, out long? realScaledValue)866             public byte GetScaledValueByte(Axes axis, decimal sensitivity, bool upperByte, out long? realScaledValue)
867             {
868                 realScaledValue = null;
869                 var unscaledValue = GetAxisValue(axis);
870                 var scaledValue = (long)decimal.Round(unscaledValue * sensitivity, 0);
871                 short value;
872                 if(scaledValue > short.MaxValue)
873                 {
874                     realScaledValue = scaledValue;
875                     value = short.MaxValue;
876                 }
877                 else if(scaledValue < short.MinValue)
878                 {
879                     realScaledValue = scaledValue;
880                     value = short.MinValue;
881                 }
882                 else
883                 {
884                     value = (short)scaledValue;
885                 }
886 
887                 return upperByte
888                     ? (byte)(value >> 8)
889                     : (byte)value;
890             }
891 
ToString()892             public override string ToString()
893             {
894                 return $"[Tag: {Tag}, X: {X:F6}, Y: {Y:F6}, Z: {Z:F6}]";
895             }
896 
897             public bool IsAccelerationSample { get; private set; }
898             public bool IsAngularRateSample { get; private set; }
899 
900             public decimal X { get; set; }
901             public decimal Y { get; set; }
902             public decimal Z { get; set; }
903 
904             public FifoTag Tag
905             {
906                 get => tag;
907                 private set
908                 {
909                     tag = value;
910                     switch(tag)
911                     {
912                         case FifoTag.Accelerometer2xC:
913                         case FifoTag.Accelerometer3xC:
914                         case FifoTag.AccelerometerNC:
915                         case FifoTag.AccelerometerNC_T_1:
916                         case FifoTag.AccelerometerNC_T_2:
917                             IsAccelerationSample = true;
918                             break;
919                         case FifoTag.Gyroscope2xC:
920                         case FifoTag.Gyroscope3xC:
921                         case FifoTag.GyroscopeNC:
922                         case FifoTag.GyroscopeNC_T_1:
923                         case FifoTag.GyroscopeNC_T_2:
924                             IsAngularRateSample = true;
925                             break;
926                     }
927                 }
928             }
929 
930             public enum Axes
931             {
932                 X,
933                 Y,
934                 Z,
935             }
936 
937             private FifoTag tag;
938         }
939 
CreateAccelerationDefaultSampleFeeder()940         private IManagedThread CreateAccelerationDefaultSampleFeeder()
941         {
942             if(accelerometerFifoBatchingDataRateSelection.Value == DataRates.Disabled)
943             {
944                 return null;
945             }
946 
947             return CreateDefaultSampleFeeder(
948                 () => commonFifo.FeedAccelerationSample(DefaultAccelerationX, DefaultAccelerationY, DefaultAccelerationZ),
949                 DataRateToFrequency(accelerometerFifoBatchingDataRateSelection.Value),
950                 "acceleration");
951         }
952 
CreateAngularRateDefaultSampleFeeder()953         private IManagedThread CreateAngularRateDefaultSampleFeeder()
954         {
955             if(gyroscopeFifoBatchingDataRateSelection.Value == DataRates.Disabled)
956             {
957                 return null;
958             }
959 
960             return CreateDefaultSampleFeeder(
961                 () => commonFifo.FeedAngularRateSample(DefaultAngularRateX, DefaultAngularRateY, DefaultAngularRateZ),
962                 DataRateToFrequency(gyroscopeFifoBatchingDataRateSelection.Value),
963                 "gyro");
964         }
965 
CreateDefaultSampleFeeder(Action action, uint frequency, String name)966         private IManagedThread CreateDefaultSampleFeeder(Action action, uint frequency, String name)
967         {
968             var feeder = machine.ObtainManagedThread(action, frequency, name: $"{name} default feeder", owner: this);
969 
970             action();
971             feeder.Start();
972             return feeder;
973         }
974 
UpdateAccelerationSampleFrequency()975         private void UpdateAccelerationSampleFrequency()
976         {
977             if(accelerometerFeederThread == null)
978             {
979                 return;
980             }
981 
982             if(accelerometerFifoBatchingDataRateSelection.Value != DataRates.Disabled)
983             {
984                 var freq = DataRateToFrequency(accelerometerFifoBatchingDataRateSelection.Value);
985                 accelerometerFeederThread.Frequency = freq;
986                 accelerometerFeederThread.Start();
987             }
988             else
989             {
990                 accelerometerFeederThread.Stop();
991             }
992         }
993 
UpdateAngularRateSampleFrequency()994         private void UpdateAngularRateSampleFrequency()
995         {
996             if(gyroFeederThread == null)
997             {
998                 return;
999             }
1000 
1001             if(gyroscopeFifoBatchingDataRateSelection.Value != DataRates.Disabled)
1002             {
1003                 var freq = DataRateToFrequency(gyroscopeFifoBatchingDataRateSelection.Value);
1004                 gyroFeederThread.Frequency = freq;
1005                 gyroFeederThread.Start();
1006             }
1007             else
1008             {
1009                 accelerometerFeederThread.Stop();
1010             }
1011         }
1012 
1013         private decimal defaultAccelerationX;
1014         private decimal defaultAccelerationY;
1015         private decimal defaultAccelerationZ;
1016         private decimal defaultAngularRateX;
1017         private decimal defaultAngularRateY;
1018         private decimal defaultAngularRateZ;
1019 
1020         private struct SampleReadingRegisterInfo
1021         {
1022             public LSM6DSO_Vector3DSample.Axes Axis;
1023             public string NameSuffix;
1024             public bool UpperByte;
1025         }
1026 
1027         private enum AccelerationFullScaleSelection : ushort
1028         {
1029             Mode0_2G = 0,
1030             Mode1_16G_2G = 1,
1031             Mode2_4G = 2,
1032             Mode3_8G = 3
1033         }
1034 
1035         private enum AngularRateFullScaleSelection : ushort
1036         {
1037             Mode0_250DPS = 0,
1038             Mode1_500DPS = 1,
1039             Mode2_1000DPS = 2,
1040             Mode3_2000DPS = 3
1041         }
1042 
1043         // Write == 0 and Read == 1 match the first transmission's byte R/W bit value.
1044         private enum CommandTypes
1045         {
1046             Write = 0,
1047             Read = 1,
1048             None,
1049         }
1050 
1051         private enum DataRates : ushort
1052         {
1053             Disabled = 0x0,
1054             _12_5Hz = 0x1,
1055             _26Hz = 0x2,
1056             _52Hz = 0x3,
1057             _104Hz = 0x4,
1058             _208Hz = 0x5,
1059             _416Hz = 0x6,
1060             _833Hz = 0x7,
1061             _1_66kHz = 0x8,
1062             _3_33kHz = 0x9,
1063             _6_66kHz = 0xA,
1064             _1_6HzOr6_5HzOr12_5Hz = 0xB,
1065         }
1066 
1067         private enum FifoModes : byte
1068         {
1069             Bypass = 0b000,
1070             StoppingFifo = 0b001,
1071             ContinuousToFifo = 0b011,
1072             BypassToContinuous = 0b100,
1073             Continuous = 0b110,
1074             BypassToFifo = 0b111,
1075         }
1076 
1077         private enum FifoTag : byte
1078         {
1079             UNKNOWN = 0x00,  // Undefined in the datasheet.
1080             GyroscopeNC = 0x01,
1081             AccelerometerNC = 0x02,
1082             Temperature = 0x03,
1083             Timestamp = 0x04,
1084             ConfigurationChange = 0x05,
1085             AccelerometerNC_T_2 = 0x06,
1086             AccelerometerNC_T_1 = 0x07,
1087             Accelerometer2xC = 0x08,
1088             Accelerometer3xC = 0x09,
1089             GyroscopeNC_T_2 = 0x0A,
1090             GyroscopeNC_T_1 = 0x0B,
1091             Gyroscope2xC = 0x0C,
1092             Gyroscope3xC = 0x0D,
1093             SensorHubSlave0 = 0x0E,
1094             SensorHubSlave1 = 0x0F,
1095             SensorHubSlave2 = 0x10,
1096             SensorHubSlave3 = 0x11,
1097             StepCounter = 0x12,
1098             SensorHubNack = 0x19
1099         }
1100 
1101         private enum Registers : byte
1102         {
1103             RegistersAccessConfiguration = 0x01,
1104             PinControl = 0x02,
1105             // Reserved: 0x03 - 0x06
1106             FifoControl1 = 0x07,
1107             FifoControl2 = 0x08,
1108             FifoControl3 = 0x09,
1109             FifoControl4 = 0x0A,
1110             CounterBatchDataRate1 = 0x0B,
1111             CounterBatchDataRate2 = 0x0C,
1112             Int1PinControl = 0x0D,
1113             Int2PinControl = 0x0E,
1114             WhoAmI = 0x0F,
1115             Control1_Accelerometer = 0x10,
1116             Control2_Gyroscope = 0x11,
1117             Control3 = 0x12,
1118             Control4 = 0x13,
1119             Control5 = 0x14,
1120             Control6 = 0x15,
1121             Control7_Gyroscope = 0x16,
1122             Control8_Accelerometer = 0x17,
1123             Control9_Accelerometer = 0x18,
1124             Control10 = 0x19,
1125             AllInterruptSource = 0x1A,
1126             WakeUpInterruptSource = 0x1B,
1127             TapInterruptSource = 0x1C,
1128             PositionInterruptSource = 0x1D,
1129             Status = 0x1E,  // Works differently when read by the auxiliary SPI.
1130             // Reserved: 0x1F
1131             TemperatureLow = 0x20,
1132             TemperatureHigh = 0x21,
1133             GyroscopeXLow = 0x22,
1134             GyroscopeXHigh = 0x23,
1135             GyroscopeYLow = 0x24,
1136             GyroscopeYHigh = 0x25,
1137             GyroscopeZLow = 0x26,
1138             GyroscopeZHigh = 0x27,
1139             AccelerometerXLow = 0x28,
1140             AccelerometerXHigh = 0x29,
1141             AccelerometerYLow = 0x2A,
1142             AccelerometerYHigh = 0x2B,
1143             AccelerometerZLow = 0x2C,
1144             AccelerometerZHigh = 0x2D,
1145             // Reserved: 0x2E - 0x34
1146             EmbeddedFunctionInterruptStatus = 0x35,
1147             FiniteStateMachineInterruptStatusA = 0x36,
1148             FiniteStateMachineInterruptStatusB = 0x37,
1149             // Reserved: 0x38
1150             SensorHubInterruptStatus = 0x39,
1151             FifoStatus1 = 0x3A,
1152             FifoStatus2 = 0x3B,
1153             // Reserved: 0x3C - 0x3F
1154             Timestamp0 = 0x40,
1155             Timestamp1 = 0x41,
1156             Timestamp2 = 0x42,
1157             Timestamp3 = 0x43,
1158             // Reserved: 0x44 - 0x55
1159             TapConfiguration0 = 0x56,
1160             TapConfiguration1 = 0x57,
1161             TapConfiguration2 = 0x58,
1162             TapThreshold6D = 0x59,
1163             IntervalDuration2 = 0x5A,
1164             WakeUpThreshold = 0x5B,
1165             WakeUpDuration = 0x5C,
1166             FreeFallDuration = 0x5D,
1167             Int1FunctionRouting = 0x5E,
1168             Int2FunctionRouting = 0x5F,
1169             // Reserved: 0x60 - 0x61
1170             I3CBusAvailable = 0x62,
1171             InternalFrequencyFine = 0x63,
1172             // Reserved: 0x64 - 0x6E
1173             OISInterrupt = 0x6F,
1174             OISControl1 = 0x70,
1175             OISControl2 = 0x71,
1176             OISControl3 = 0x72,
1177             AccelerometerUserOffsetX = 0x73,
1178             AccelerometerUserOffsetY = 0x74,
1179             AccelerometerUserOffsetZ = 0x75,
1180             // Reserved: 0x76 - 0x77
1181             FifoOutputTag = 0x78,
1182             FifoOutputXLow = 0x79,
1183             FifoOutputXHigh = 0x7A,
1184             FifoOutputYLow = 0x7B,
1185             FifoOutputYHigh = 0x7C,
1186             FifoOutputZLow = 0x7D,
1187             FifoOutputZHigh = 0x7E,
1188         }
1189     }
1190 }
1191