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.Collections.Generic;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Exceptions;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Core.Structure.Registers;
14 using Antmicro.Renode.Peripherals.SPI;
15 using Antmicro.Renode.Utilities;
16 using Antmicro.Renode.Utilities.RESD;
17 
18 namespace Antmicro.Renode.Peripherals.Sensors
19 {
20     public class MAX86171 : ISPIPeripheral, IProvidesRegisterCollection<ByteRegisterCollection>
21     {
MAX86171(IMachine machine)22         public MAX86171(IMachine machine)
23         {
24             this.machine = machine;
25             this.resdFrequencyMultiplier = 1;
26 
27             Interrupt1 = new GPIO();
28             Interrupt2 = new GPIO();
29 
30             UpdateDefaultMeasurements();
31 
32             circularFifo = new AFESampleFIFO(this);
33             measurementEnabled = new bool[MeasurementRegisterCount];
34 
35             measurementLEDASource = new byte[MeasurementRegisterCount];
36             measurementLEDBSource = new byte[MeasurementRegisterCount];
37             measurementLEDCSource = new byte[MeasurementRegisterCount];
38 
39             measurementLEDACurrent = new IValueRegisterField[MeasurementRegisterCount];
40             measurementLEDBCurrent = new IValueRegisterField[MeasurementRegisterCount];
41             measurementLEDCCurrent = new IValueRegisterField[MeasurementRegisterCount];
42 
43             measurementPDARange = new uint[MeasurementRegisterCount];
44             measurementPDBRange = new uint[MeasurementRegisterCount];
45 
46             measurementPDAOffset = new ushort[MeasurementRegisterCount];
47             measurementPDBOffset = new ushort[MeasurementRegisterCount];
48 
49             ledRange = new IValueRegisterField[MeasurementRegisterCount];
50 
51             RegistersCollection = new ByteRegisterCollection(this, BuildRegisterMap());
52         }
53 
FeedSamplesFromRESD(ReadFilePath filePath, uint channelId = 0, ulong startTimestamp = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)54         public void FeedSamplesFromRESD(ReadFilePath filePath, uint channelId = 0, ulong startTimestamp = 0,
55             RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)
56         {
57             lock(feederThreadLock)
58             {
59                 feedingSamplesFromFile = true;
60                 feederThread?.Stop();
61                 resdStream?.Dispose();
62                 resdStream = this.CreateRESDStream<MAX86171_AFESample>(filePath, channelId, sampleOffsetType, sampleOffsetTime);
63                 resdStream.MetadataChanged += () =>
64                 {
65                     staleConfiguration = true;
66                 };
67 
68                 // The initial value is set to `RESDFrequencyMultiplier - 1`
69                 // in order to load a sample at the first callback instead of waiting for `RESDFrequencyMultiplier` of them
70                 var count = RESDFrequencyMultiplier - 1;
71                 feederThread = resdStream.StartSampleFeedThread(this, RESDFrequencyMultiplier * CalculateCurrentFrequency(), (sample, ts, status) =>
72                 {
73                     count++;
74 
75                     if(status == RESDStreamStatus.AfterStream)
76                     {
77                         feedingSamplesFromFile = false;
78                         TryFeedDefaultSample();
79                         return;
80                     }
81 
82                     if(count != RESDFrequencyMultiplier)
83                     {
84                         return;
85                     }
86                     count = 0;
87 
88                     if(status == RESDStreamStatus.OK)
89                     {
90                         circularFifo.EnqueueFrame(new AFESampleFrame(sample.Frame.Select(v => new AFESample(v)).ToArray()));
91                     }
92                     else
93                     {
94                         circularFifo.EnqueueFrame(defaultMeasurements);
95                     }
96                 }, startTime: startTimestamp);
97                 this.Log(LogLevel.Info, "Started feeding samples from RESD file at {0}Hz", CalculateCurrentFrequency());
98             }
99         }
100 
WriteByte(long offset, byte value)101         public void WriteByte(long offset, byte value)
102         {
103             RegistersCollection.Write(offset, value);
104         }
105 
ReadByte(long offset)106         public byte ReadByte(long offset)
107         {
108             return RegistersCollection.Read(offset);
109         }
110 
FinishTransmission()111         public void FinishTransmission()
112         {
113             chosenRegister = null;
114             state = null;
115         }
116 
Reset()117         public void Reset()
118         {
119             RegistersCollection.Reset();
120             chosenRegister = null;
121             state = null;
122             circularFifo.Reset();
123             previousFifoTresholdReached = false;
124             staleConfiguration = true;
125 
126             for(var i = 0; i < MeasurementRegisterCount; ++i)
127             {
128                 measurementEnabled[i] = false;
129 
130                 measurementLEDASource[i] = 0;
131                 measurementLEDBSource[i] = 0;
132                 measurementLEDCSource[i] = 0;
133 
134                 measurementPDARange[i] = 0;
135                 measurementPDBRange[i] = 0;
136 
137                 measurementPDAOffset[i] = 0;
138                 measurementPDBOffset[i] = 0;
139             }
140             UpdateInterrupts();
141 
142             feederThread?.Stop();
143             feederThread = null;
144             feedingSamplesFromFile = false;
145         }
146 
Transmit(byte data)147         public byte Transmit(byte data)
148         {
149             byte output = 0x00;
150             if(chosenRegister == null)
151             {
152                 // In first byte, we are choosing register to read or write
153                 chosenRegister = (Registers)data;
154                 return output;
155             }
156 
157             if(state == null)
158             {
159                 // In second byte, we are choosing transaction type
160                 state = (States)data;
161                 return output;
162             }
163 
164             switch(state.Value)
165             {
166                 case States.Write:
167                     WriteByte((long)chosenRegister.Value, data);
168                     break;
169                 case States.Read:
170                     output = ReadByte((long)chosenRegister.Value);
171                     break;
172                 default:
173                     throw new Exception("unreachable code");
174             }
175 
176             return output;
177         }
178 
179         public ByteRegisterCollection RegistersCollection { get; }
180 
181         public GPIO Interrupt1 { get; }
182         public GPIO Interrupt2 { get; }
183 
184         public int Measurement1ADCValue
185         {
186             get => measurement1ADCValue;
187             set
188             {
189                 measurement1ADCValue = value;
190                 UpdateDefaultMeasurements();
191             }
192         }
193 
194         public int Measurement2ADCValue
195         {
196             get => measurement2ADCValue;
197             set
198             {
199                 measurement2ADCValue = value;
200                 UpdateDefaultMeasurements();
201             }
202         }
203 
204         public int Measurement3ADCValue
205         {
206             get => measurement3ADCValue;
207             set
208             {
209                 measurement3ADCValue = value;
210                 UpdateDefaultMeasurements();
211             }
212         }
213 
214         public int Measurement4ADCValue
215         {
216             get => measurement4ADCValue;
217             set
218             {
219                 measurement4ADCValue = value;
220                 UpdateDefaultMeasurements();
221             }
222         }
223 
224         public int Measurement5ADCValue
225         {
226             get => measurement5ADCValue;
227             set
228             {
229                 measurement5ADCValue = value;
230                 UpdateDefaultMeasurements();
231             }
232         }
233 
234         public int Measurement6ADCValue
235         {
236             get => measurement6ADCValue;
237             set
238             {
239                 measurement6ADCValue = value;
240                 UpdateDefaultMeasurements();
241             }
242         }
243 
244         public int Measurement7ADCValue
245         {
246             get => measurement7ADCValue;
247             set
248             {
249                 measurement7ADCValue = value;
250                 UpdateDefaultMeasurements();
251             }
252         }
253 
254         public int Measurement8ADCValue
255         {
256             get => measurement8ADCValue;
257             set
258             {
259                 measurement8ADCValue = value;
260                 UpdateDefaultMeasurements();
261             }
262         }
263 
264         public int Measurement9ADCValue
265         {
266             get => measurement9ADCValue;
267             set
268             {
269                 measurement9ADCValue = value;
270                 UpdateDefaultMeasurements();
271             }
272         }
273 
274         // This propery allows RESD data to be sampled at N times frequency from the input file,
275         // but still loading them every N'th sample into the FIFO (effectively keeping the initial frequency).
276         // This mechanism may help with synchronization in some specific cases, especially when data from multiple sensors needs to be read precisely.
277         // In general there is no need to change the multiplier from the default value of 1, though.
278         public uint RESDFrequencyMultiplier
279         {
280             get => resdFrequencyMultiplier;
281             set
282             {
283                 if(feedingSamplesFromFile)
284                 {
285                     throw new RecoverableException("Cannot change the RESD frequency multiplier while samples are being fed from a file");
286                 }
287 
288                 if(value < 1)
289                 {
290                     throw new RecoverableException($"Attempted to set RESD frequency multiplier to {value}. The multiplier has to be greater or equal to 1");
291                 }
292 
293                 resdFrequencyMultiplier = value;
294             }
295         }
296 
297         public event Action OnFifoFull;
298 
UpdateStatus()299         private void UpdateStatus()
300         {
301             statusFifoFull.Value |= FifoThresholdReached && (!fifoAssertThresholdOnce.Value || (previousFifoTresholdReached != FifoThresholdReached));
302             previousFifoTresholdReached = FifoThresholdReached;
303 
304             if(statusFifoFull.Value)
305             {
306                 OnFifoFull?.Invoke();
307             }
308         }
309 
UpdateInterrupts()310         private void UpdateInterrupts()
311         {
312             // Currently, only A_FULL interrupt is supported for both INT1 and INT2
313             // GPIO ports.
314 
315             var interrupt1 = false;
316             interrupt1 = interrupt1FullEnabled.Value && statusFifoFull.Value;;
317 
318             var interrupt2 = false;
319             interrupt2 = interrupt2FullEnabled.Value && statusFifoFull.Value;
320 
321             Interrupt1.Set(ApplyInterruptPolarity(interrupt1, polarityInterrupt1.Value));
322             Interrupt2.Set(ApplyInterruptPolarity(interrupt2, polarityInterrupt2.Value));
323         }
324 
CheckIfConfigurationMatches()325         private bool CheckIfConfigurationMatches()
326         {
327             if(!staleConfiguration)
328             {
329                 return true;
330             }
331 
332             var currentSample = resdStream?.CurrentSample;
333             if(currentSample == null)
334             {
335                 return true;
336             }
337 
338             staleConfiguration = false;
339 
340             var metadataMatches = true;
341             foreach(var channel in ActiveChannels.Cast<int>())
342             {
343                 var channelId = channel + 1;
344                 var nonMatchingMetadata = new List<String>();
345                 if(currentSample.ConfigLedAExposure[channelId].HasValue && CalculateExposure((uint)measurementLEDACurrent[channel].Value, (uint)ledRange[channel].Value, out var ledAExposure) != currentSample.ConfigLedAExposure[channelId])
346                 {
347                     nonMatchingMetadata.Add($"LED A exposure (expected: {currentSample.ConfigLedAExposure[channelId]}, got: {ledAExposure})");
348                 }
349                 if(currentSample.ConfigLedBExposure[channelId].HasValue && CalculateExposure((uint)measurementLEDBCurrent[channel].Value, (uint)ledRange[channel].Value, out var ledBExposure) != currentSample.ConfigLedBExposure[channelId])
350                 {
351                     nonMatchingMetadata.Add($"LED B exposure (expected: {currentSample.ConfigLedBExposure[channelId]}, got: {ledBExposure})");
352                 }
353                 if(currentSample.ConfigLedCExposure[channelId].HasValue && CalculateExposure((uint)measurementLEDCCurrent[channel].Value, (uint)ledRange[channel].Value, out var ledCExposure) != currentSample.ConfigLedCExposure[channelId])
354                 {
355                     nonMatchingMetadata.Add($"LED C exposure (expected: {currentSample.ConfigLedCExposure[channelId]}, got: {ledCExposure})");
356                 }
357                 if(currentSample.ConfigLedASource[channelId].HasValue && measurementLEDASource[channel] != currentSample.ConfigLedASource[channelId])
358                 {
359                     nonMatchingMetadata.Add($"LED A source (expected: {measurementLEDASource[channel]}, got: {currentSample.ConfigLedASource[channelId]})");
360                 }
361                 if(currentSample.ConfigLedBSource[channelId].HasValue && measurementLEDBSource[channel] != currentSample.ConfigLedBSource[channelId])
362                 {
363                     nonMatchingMetadata.Add($"LED B source (expected: {measurementLEDBSource[channel]}, got: {currentSample.ConfigLedBSource[channelId]})");
364                 }
365                 if(currentSample.ConfigLedCSource[channelId].HasValue && measurementLEDCSource[channel] != currentSample.ConfigLedCSource[channelId])
366                 {
367                     nonMatchingMetadata.Add($"LED C source (expected: {measurementLEDCSource[channel]}, got: {currentSample.ConfigLedCSource[channelId]})");
368                 }
369                 if(currentSample.ConfigPD1ADCRange[channelId].HasValue && measurementPDARange[channel] != currentSample.ConfigPD1ADCRange[channelId])
370                 {
371                     nonMatchingMetadata.Add($"PD A ADC range (expected: {measurementPDARange[channel]}, got: {currentSample.ConfigPD1ADCRange[channel]})");
372                 }
373                 if(currentSample.ConfigPD2ADCRange[channelId].HasValue && measurementPDBRange[channel] != currentSample.ConfigPD2ADCRange[channelId])
374                 {
375                     nonMatchingMetadata.Add($"PD B ADC range (expected: {measurementPDBRange[channel]}, got: {currentSample.ConfigPD2ADCRange[channelId]})");
376                 }
377                 if(currentSample.ConfigPD1DACOffset[channelId].HasValue && measurementPDAOffset[channel] != currentSample.ConfigPD1DACOffset[channelId])
378                 {
379                     nonMatchingMetadata.Add($"PD A DAC offset (expected: {measurementPDAOffset[channel]}, got: {currentSample.ConfigPD1DACOffset[channelId]})");
380                 }
381                 if(currentSample.ConfigPD2DACOffset[channelId].HasValue && measurementPDBOffset[channel] != currentSample.ConfigPD2DACOffset[channelId])
382                 {
383                     nonMatchingMetadata.Add($"PD B DAC offset (expected: {measurementPDBOffset[channel]}, got: {currentSample.ConfigPD2DACOffset[channelId]})");
384                 }
385 
386                 if(nonMatchingMetadata.Count > 0)
387                 {
388                     metadataMatches = false;
389                     this.Log(LogLevel.Warning, "Measurement {0} has non-matching configuration, found differences in: {1}",
390                         channel + 1,
391                         string.Join(", ", nonMatchingMetadata));
392                 }
393             }
394 
395             return metadataMatches;
396         }
397 
CalculateExposure(uint driveCurrent, uint ledRange, out uint exposure)398         private uint CalculateExposure(uint driveCurrent, uint ledRange, out uint exposure)
399         {
400             uint multiplier;
401             switch(ledRange)
402             {
403                 case 0:
404                     multiplier = 125;
405                     break;
406                 case 1:
407                     multiplier = 250;
408                     break;
409                 case 2:
410                     multiplier = 375;
411                     break;
412                 case 3:
413                     multiplier = 500;
414                     break;
415                 default:
416                     throw new Exception("unreachable code");
417             }
418             exposure = driveCurrent * multiplier;
419             return exposure;
420         }
421 
ApplyInterruptPolarity(bool value, OutputPinPolarity polarity)422         private bool ApplyInterruptPolarity(bool value, OutputPinPolarity polarity)
423         {
424             switch(polarity)
425             {
426                 case OutputPinPolarity.OpenDrainActiveLow:
427                 case OutputPinPolarity.ActiveLow:
428                     return !value;
429                 case OutputPinPolarity.ActiveHigh:
430                     return value;
431                 default:
432                     return value;
433             }
434         }
435 
GetDefaultValueForSampleSource(SampleSource ss)436         private int GetDefaultValueForSampleSource(SampleSource ss)
437         {
438             switch(ss)
439             {
440                 case SampleSource.PPGMeasurement1:
441                     return Measurement1ADCValue;
442                 case SampleSource.PPGMeasurement2:
443                     return Measurement2ADCValue;
444                 case SampleSource.PPGMeasurement3:
445                     return Measurement3ADCValue;
446                 case SampleSource.PPGMeasurement4:
447                     return Measurement4ADCValue;
448                 case SampleSource.PPGMeasurement5:
449                     return Measurement5ADCValue;
450                 case SampleSource.PPGMeasurement6:
451                     return Measurement6ADCValue;
452                 case SampleSource.PPGMeasurement7:
453                     return Measurement7ADCValue;
454                 case SampleSource.PPGMeasurement8:
455                     return Measurement8ADCValue;
456                 case SampleSource.PPGMeasurement9:
457                     return Measurement9ADCValue;
458                 default:
459                     this.Log(LogLevel.Warning, "No default value specified for {0}, returning 0");
460                     return 0;
461             }
462         }
463 
CreateDummyRegister(string name, byte defaultValue = 0x00, FieldMode fieldMode = FieldMode.Read | FieldMode.Write, bool verbose = true)464         private ByteRegister CreateDummyRegister(string name, byte defaultValue = 0x00, FieldMode fieldMode = FieldMode.Read | FieldMode.Write, bool verbose = true)
465         {
466             // As software could potentially want to check if writes to given register were successful,
467             // we will be using this method to mock unimplemented registers to allow for persistent
468             // writes and reads
469             if(verbose)
470             {
471                 return new ByteRegister(this, defaultValue)
472                     .WithValueField(0, 8, fieldMode, name: name,
473                         valueProviderCallback: value =>
474                         {
475                             this.Log(LogLevel.Warning, "Unhandled read from {0}; returning 0x{1:X02}", name, value);
476                             return value;
477                         },
478                         writeCallback: (_, value) =>
479                         {
480                             this.Log(LogLevel.Warning, "Unhandled write to {0}; written 0x{1:X02}", name, value);
481                         });
482             }
483             else
484             {
485                 return new ByteRegister(this, defaultValue)
486                     .WithValueField(0, 8, fieldMode, name: name);
487             }
488         }
489 
BuildRegisterMap()490         private Dictionary<long, ByteRegister> BuildRegisterMap()
491         {
492             var registerMap = new Dictionary<long, ByteRegister>()
493             {
494                 {(long)Registers.Status1, new ByteRegister(this)
495                     .WithFlag(0, out statusFifoFull, FieldMode.ReadToClear, name: "STATUS1.a_full")
496                     .WithTaggedFlag("STATUS1.frame_rdy", 1)
497                     .WithTaggedFlag("STATUS1.fifo_data_rdy", 2)
498                     .WithTaggedFlag("STATUS1.alc_ovf", 3)
499                     .WithTaggedFlag("STATUS1.exp_ovf", 4)
500                     .WithTaggedFlag("STATUS1.thresh2_hilo", 5)
501                     .WithTaggedFlag("STATUS1.thresh1_hilo", 6)
502                     .WithTaggedFlag("STATUS1.pwr_rdy", 7)
503                 },
504                 // Maked as non-verbose to limit the amount of log messages
505                 {(long)Registers.Status2, CreateDummyRegister("STATUS2.data", verbose: false)},
506                 // Maked as non-verbose to limit the amount of log messages
507                 {(long)Registers.Status3, CreateDummyRegister("STATUS3.data", verbose: false)},
508                 {(long)Registers.FIFOWritePointer, CreateDummyRegister("FIFO_WR_PTR.data", fieldMode: FieldMode.Read)},
509                 {(long)Registers.FIFOReadPointer, CreateDummyRegister("FIFO_RD_PTR.data", fieldMode: FieldMode.Read)},
510                 {(long)Registers.FIFOCounter1, new ByteRegister(this)
511                     .WithValueField(0, 7, FieldMode.Read, name: "FIFO_CNT1.OVF_COUNTER",
512                         valueProviderCallback: _ => 0)
513                     .WithFlag(7, FieldMode.Read, name: "FIFO_CNT1.FIFO_DATA_COUNT_MSB",
514                         valueProviderCallback: _ => (circularFifo.Count & 0x100) != 0)
515                 },
516                 {(long)Registers.FIFOCounter2, new ByteRegister(this)
517                     .WithValueField(0, 8, FieldMode.Read, name: "FIFO_CNT2.fifo_data_count_lsb",
518                         valueProviderCallback: _ => circularFifo.Count)
519                 },
520                 {(long)Registers.FIFOData, new ByteRegister(this)
521                     .WithValueField(0, 8, FieldMode.Read, name: "FIFO_DATA.data",
522                         valueProviderCallback: _ =>
523                         {
524                             CheckIfConfigurationMatches();
525                             var output = circularFifo.DequeueByte();
526                             if(clearFlagsOnRead.Value)
527                             {
528                                 statusFifoFull.Value = false;
529                                 UpdateInterrupts();
530                             }
531                             return output;
532                         })
533                 },
534                 {(long)Registers.FIFOConfiguration1, new ByteRegister(this)
535                     .WithValueField(0, 8, out fifoFullThreshold, name: "FIFO_CONF1.fifo_a_full")
536                     .WithChangeCallback((_, __) => UpdateInterrupts())
537                 },
538                 {(long)Registers.FIFOConfiguration2, new ByteRegister(this)
539                     .WithReservedBits(0, 1)
540                     .WithFlag(1, name: "FIFO_CONF2.fifo_ro",
541                         valueProviderCallback: _ => circularFifo.Rollover,
542                         writeCallback: (_, value) => circularFifo.Rollover = value)
543                     .WithFlag(2, out fifoAssertThresholdOnce, name: "FIFO_CONF2.a_full_type")
544                     .WithFlag(3, out clearFlagsOnRead, name: "FIFO_CONF2.fifo_stat_clr")
545                     .WithFlag(4, FieldMode.WriteOneToClear | FieldMode.Read, name: "FIFO_CONF2.flush_fifo",
546                         writeCallback: (_, value) =>
547                         {
548                             if(value)
549                             {
550                                 circularFifo.Clear();
551 
552                                 statusFifoFull.Value = false;
553                                 UpdateInterrupts();
554                             }
555                         })
556                     .WithReservedBits(5, 3)
557                 },
558                 {(long)Registers.SystemConfiguration1, new ByteRegister(this)
559                     .WithFlag(0, name: "SYSTEM_CONF1.reset",
560                         valueProviderCallback: _ => false,
561                         writeCallback: (_, value) => { if(value) Reset(); })
562                     .WithFlag(1, name: "SYSTEM_CONF1.shdn",
563                         // While completely disabling clocks used for feeding samples to FIFO
564                         // would be more in line with what actual software is doing, disabling writes
565                         // to FIFO is easier to implement while also being agnostic to samples source
566                         valueProviderCallback: _ => !circularFifo.Enabled,
567                         writeCallback: (_, value) => circularFifo.Enabled = !value)
568                     // Using Flag instead of TaggedFlag for persistancy of data
569                     // written by software
570                     .WithFlag(2, name: "SYSTEM_CONF1.ppg1_pwrdn")
571                     .WithFlag(3, name: "SYSTEM_CONF1.ppg2_pwrdn")
572                     .WithValueField(4, 2, name: "SYSTEM_CONF1.sync_mode")
573                     .WithFlag(6, name: "SYSTEM_CONF1.sw_force_sync")
574                     .WithFlag(7, name: "SYSTEM_CONF1.meas9_en",
575                         valueProviderCallback: _ => measurementEnabled[(int)Channel.Measurement9],
576                         changeCallback: (_, value) =>
577                         {
578                             measurementEnabled[(int)Channel.Measurement9] = value;
579                             TryFeedDefaultSample();
580                         })
581                 },
582                 {(long)Registers.SystemConfiguration2, new ByteRegister(this)
583                     .WithFlags(0, 8, name: "SYSTEM_CONF2.MEASX_EN",
584                         valueProviderCallback: (idx, _) => measurementEnabled[idx],
585                         changeCallback: (idx, _, value) =>
586                         {
587                             measurementEnabled[idx] = value;
588                             TryFeedDefaultSample();
589                         })
590                 },
591                 {(long)Registers.SystemConfiguration3, CreateDummyRegister("SYSTEM_CONF3.data")},
592                 {(long)Registers.PhotodiodeBias, CreateDummyRegister("PHOTO_BIAS.data")},
593                 {(long)Registers.PinFunctionalConfiguration, CreateDummyRegister("PIN_FUNC_CONF.data")},
594                 {(long)Registers.OutputPinConfiguration, new ByteRegister(this)
595                     .WithReservedBits(0, 1)
596                     .WithEnumField<ByteRegister, OutputPinPolarity>(1, 2, out polarityInterrupt1, name: "OUT_PIN_CONF.int1_ocfg")
597                     .WithEnumField<ByteRegister, OutputPinPolarity>(3, 2, out polarityInterrupt2,  name: "OUT_PIN_CONF.int2_ocfg")
598                     .WithReservedBits(5, 3)
599                     .WithChangeCallback((_, __) => UpdateInterrupts())
600                 },
601                 {(long)Registers.FrameRateClockFrequency, new ByteRegister(this, 0x20)
602                     .WithTag("FR_CLK.fine_tune", 0, 5)
603                     .WithFlag(5, out clockSelect, name: "FR_CLK.sel")
604                     .WithReservedBits(6, 2)
605                     .WithChangeCallback((_, __) => UpdateFrequency())
606                 },
607                 {(long)Registers.FrameRateClockDividerMSB, new ByteRegister(this, 0x1)
608                     .WithValueField(0, 7, out clockDividerHigh, name: "FR_CLK.div_h")
609                     .WithReservedBits(7, 1)
610                     .WithChangeCallback((_, __) => UpdateFrequency())
611                 },
612                 {(long)Registers.FrameRateClockDividerLSB, new ByteRegister(this)
613                     .WithValueField(0, 8, out clockDividerLow, name: "FR_CLK.div_l")
614                     .WithChangeCallback((_, __) => UpdateFrequency())
615                 },
616                 {(long)Registers.ThresholdMeasurementSelect, CreateDummyRegister("THRESH_MEAS_SEL.data")},
617                 {(long)Registers.ThresholdHysteresis, CreateDummyRegister("THRESH_HYST.data")},
618                 {(long)Registers.PPGHiThreshold1, CreateDummyRegister("PPG_HI_THRESH1.data")},
619                 {(long)Registers.PPGLoThreshold1, CreateDummyRegister("PPG_LO_THRESH1.data")},
620                 {(long)Registers.PPGHiThreshold2, CreateDummyRegister("PPG_HI_THRESH2.data")},
621                 {(long)Registers.PPGLoThreshold2, CreateDummyRegister("PPG_LO_THRESH2.data")},
622                 {(long)Registers.PicketFenceMeasurementSelect, CreateDummyRegister("PICKET_FENCE_MEAS_SEL.data")},
623                 {(long)Registers.PicketFenceConfiguration, CreateDummyRegister("PICKET_FENCE_CONF.data")},
624                 {(long)Registers.Interrupt1Enable1, new ByteRegister(this)
625                     // Using Flag instead of TaggedFlag for persistancy of data
626                     // written by software
627                     .WithFlag(0, name: "INT1_ENABLE1.led_tx_en")
628                     .WithFlag(1, name: "INT1_ENABLE1.thresh1_hilo_en")
629                     .WithFlag(2, name: "INT1_ENABLE1.thresh2_hilo_en")
630                     .WithFlag(3, name: "INT1_ENABLE1.exp_ovf_en")
631                     .WithFlag(4, name: "INT1_ENABLE1.alc_ovf_en")
632                     .WithFlag(5, name: "INT1_ENABLE1.fifo_data_rdy_en")
633                     .WithFlag(6, name: "INT1_ENABLE1.framerdy_en")
634                     .WithFlag(7, out interrupt1FullEnabled, name: "INT1_ENABLE1.a_full_en")
635                     .WithChangeCallback((_, __) => UpdateInterrupts())
636                 },
637                 {(long)Registers.Interrupt1Enable2, CreateDummyRegister("INT1_ENABLE2.data")},
638                 {(long)Registers.Interrupt1Enable3, CreateDummyRegister("INT1_ENABLE3.data")},
639                 {(long)Registers.Interrupt2Enable1, new ByteRegister(this)
640                     // Using Flag instead of TaggedFlag for persistancy of data
641                     // written by software
642                     .WithFlag(0, name: "INT2_ENABLE1.led_tx_en")
643                     .WithFlag(1, name: "INT2_ENABLE1.thresh1_hilo_en")
644                     .WithFlag(2, name: "INT2_ENABLE1.thresh2_hilo_en")
645                     .WithFlag(3, name: "INT2_ENABLE1.exp_ovf_en")
646                     .WithFlag(4, name: "INT2_ENABLE1.alc_ovf_en")
647                     .WithFlag(5, name: "INT2_ENABLE1.fifo_data_rdy_en")
648                     .WithFlag(6, name: "INT2_ENABLE1.framerdy_en")
649                     .WithFlag(7, out interrupt2FullEnabled, name: "INT2_ENABLE1.a_full_en")
650                     .WithChangeCallback((_, __) => UpdateInterrupts())
651                 },
652                 {(long)Registers.Interrupt2Enable2, CreateDummyRegister("INT2_ENABLE2.data")},
653                 {(long)Registers.Interrupt2Enable3, CreateDummyRegister("INT2_ENABLE3.data")},
654                 {(long)Registers.PartID, new ByteRegister(this)
655                     .WithValueField(0, 8, FieldMode.Read, name: "PART_ID.part_id",
656                         valueProviderCallback: _ => 0x2C)
657                 }
658             };
659 
660             for(var i = 0; i < MeasurementRegisterCount; ++i)
661             {
662                 var offset = i * 0x8;
663                 var j = i;
664 
665                 registerMap.Add((long)Registers.Measurement1Select + offset, new ByteRegister(this)
666                     .WithValueField(0, 2, name: $"MEAS{i+1}_SELECTS.meas{i+1}_drva",
667                         writeCallback: (_, value) =>
668                         {
669                             // Map register value to LED source index
670                             switch(value)
671                             {
672                                 case 0:
673                                     measurementLEDASource[j] = 1;
674                                     break;
675                                 case 1:
676                                     measurementLEDASource[j] = 2;
677                                     break;
678                                 case 2:
679                                     measurementLEDASource[j] = 4;
680                                     break;
681                                 case 3:
682                                     measurementLEDASource[j] = 7;
683                                     break;
684                                 default:
685                                     throw new Exception("unreachable code");
686                             }
687                         })
688                     .WithValueField(2, 2, name: $"MEAS{i+1}_SELECTS.meas{i+1}_drvb",
689                         writeCallback: (_, value) =>
690                         {
691                             // Map register value to LED source index
692                             switch(value)
693                             {
694                                 case 0:
695                                     measurementLEDBSource[j] = 2;
696                                     break;
697                                 case 1:
698                                     measurementLEDBSource[j] = 3;
699                                     break;
700                                 case 2:
701                                     measurementLEDBSource[j] = 5;
702                                     break;
703                                 case 3:
704                                     measurementLEDBSource[j] = 8;
705                                     break;
706                                 default:
707                                     throw new Exception("unreachable code");
708                             }
709                         })
710                     .WithValueField(4, 2, name: $"MEAS{i+1}_SELECTS.meas{i+1}_drvc",
711                         writeCallback: (_, value) =>
712                         {
713                             // Map register value to LED source index
714                             switch(value)
715                             {
716                                 case 0:
717                                     measurementLEDCSource[j] = 1;
718                                     break;
719                                 case 1:
720                                     measurementLEDCSource[j] = 3;
721                                     break;
722                                 case 2:
723                                     measurementLEDCSource[j] = 6;
724                                     break;
725                                 case 3:
726                                     measurementLEDCSource[j] = 9;
727                                     break;
728                                 default:
729                                     throw new Exception("unreachable code");
730                             }
731                         })
732                     .WithFlag(6, name: $"MEAS{i+1}_SELECTS.meas{i+1}_amb")
733                     .WithReservedBits(7, 1)
734                     .WithChangeCallback((_, __) => staleConfiguration = true));
735                 registerMap.Add((long)Registers.Measurement1Configuration1 + offset, CreateDummyRegister($"MEAS{i+1}_CONF1.data"));
736                 registerMap.Add((long)Registers.Measurement1Configuration2 + offset, new ByteRegister(this, 0x3A)
737                     .WithValueField(0, 2, name: $"MEAS{i+1}_CONF2.meas{i+1}_ppg1_adc_rge",
738                         writeCallback: (_, value) => measurementPDARange[j] = (uint)(4 << (int)value))
739                     .WithValueField(2, 2, name: $"MEAS{i+1}_CONF2.meas{i+1}_ppg2_adc_rge",
740                         writeCallback: (_, value) => measurementPDBRange[j] = (uint)(4 << (int)value))
741                     .WithValueField(4, 2, out ledRange[i], name: $"MEAS{i+1}_CONF2.meas{i+1}_led_rge")
742                     .WithFlag(6, name: $"MEAS{i+1}_CONF2.meas{i+1}_filt_sel")
743                     .WithFlag(7, name: $"MEAS{i+1}_CONF2.meas{i+1}_sinc3_sel")
744                     .WithChangeCallback((_, __) => staleConfiguration = true));
745                 registerMap.Add((long)Registers.Measurement1Configuration3 + offset, new ByteRegister(this, 0x50)
746                     .WithValueField(0, 2, name: $"MEAS{i+1}_CONF3.meas{i+1}_ppg1_dacoff",
747                         writeCallback: (_, value) => measurementPDAOffset[j] = (ushort)(8 * value))
748                     .WithValueField(2, 2, name: $"MEAS{i+1}_CONF3.meas{i+1}_ppg2_dacoff",
749                         writeCallback: (_, value) => measurementPDBOffset[j] = (ushort)(8 * value))
750                     .WithValueField(4, 2, name: $"MEAS{i+1}_CONF3.meas{i+1}_led_setlng")
751                     .WithValueField(6, 2, name: $"MEAS{i+1}_CONF3.meas{i+1}_pd_setlng")
752                     .WithChangeCallback((_, __) => staleConfiguration = true));
753                 registerMap.Add((long)Registers.Measurement1DriverACurrent + offset, new ByteRegister(this)
754                     .WithValueField(0, 8, out measurementLEDACurrent[i], name: $"MEAS{i+1}_DRVA_CURRENT.data")
755                     .WithChangeCallback((_, __) => staleConfiguration = true));
756                 registerMap.Add((long)Registers.Measurement1DriverBCurrent + offset, new ByteRegister(this)
757                     .WithValueField(0, 8, out measurementLEDBCurrent[i], name: $"MEAS{i+1}_DRVB_CURRENT.data")
758                     .WithChangeCallback((_, __) => staleConfiguration = true));
759                 registerMap.Add((long)Registers.Measurement1DriverCCurrent + offset, new ByteRegister(this)
760                     .WithValueField(0, 8, out measurementLEDCCurrent[i], name: $"MEAS{i+1}_DRVC_CURRENT.data")
761                     .WithChangeCallback((_, __) => staleConfiguration = true));
762             }
763 
764             return registerMap;
765         }
766 
TryFeedDefaultSample()767         private bool TryFeedDefaultSample()
768         {
769             lock(feederThreadLock)
770             {
771                 if(feedingSamplesFromFile)
772                 {
773                     return false;
774                 }
775 
776                 feederThread?.Stop();
777                 if(measurementEnabled.Any(x => x))
778                 {
779                     var freq = CalculateCurrentFrequency();
780                     this.Log(LogLevel.Info, "Starting the default sample feeding at {0}Hz", freq);
781 
782                     Action feedSample = () =>
783                     {
784                         circularFifo.EnqueueFrame(defaultMeasurements);
785                     };
786 
787                     Func<bool> stopCondition = () =>
788                     {
789                         return feedingSamplesFromFile;
790                     };
791 
792                     feederThread = machine.ObtainManagedThread(feedSample, freq, "default_sample_afe", this, stopCondition);
793                     feederThread.Start();
794 
795                     return true;
796                 }
797 
798                 return false;
799             }
800         }
801 
UpdateDefaultMeasurements()802         private void UpdateDefaultMeasurements()
803         {
804             var ch1 = new AFESample(SampleSource.PPGMeasurement1, Measurement1ADCValue);
805             var ch2 = new AFESample(SampleSource.PPGMeasurement2, Measurement2ADCValue);
806             var ch3 = new AFESample(SampleSource.PPGMeasurement3, Measurement3ADCValue);
807             var ch4 = new AFESample(SampleSource.PPGMeasurement4, Measurement4ADCValue);
808             var ch5 = new AFESample(SampleSource.PPGMeasurement5, Measurement5ADCValue);
809             var ch6 = new AFESample(SampleSource.PPGMeasurement6, Measurement6ADCValue);
810             var ch7 = new AFESample(SampleSource.PPGMeasurement7, Measurement7ADCValue);
811             var ch8 = new AFESample(SampleSource.PPGMeasurement8, Measurement8ADCValue);
812             var ch9 = new AFESample(SampleSource.PPGMeasurement9, Measurement9ADCValue);
813 
814             // fifo requires two samples per channel
815             defaultMeasurements = new AFESampleFrame(new []
816             {
817                 ch1, ch1,
818                 ch2, ch2,
819                 ch3, ch3,
820                 ch4, ch4,
821                 ch5, ch5,
822                 ch6, ch6,
823                 ch7, ch7,
824                 ch8, ch8,
825                 ch9, ch9
826             });
827         }
828 
UpdateFrequency()829         private void UpdateFrequency()
830         {
831             if(feederThread != null)
832             {
833                 feederThread.Frequency = CalculateCurrentFrequency();
834             }
835         }
836 
CalculateCurrentFrequency()837         public uint CalculateCurrentFrequency()
838         {
839             var clockBaseFrequency = clockSelect.Value
840                 ? 32768u
841                 : 32000u;
842             return (uint)(clockBaseFrequency / ((clockDividerHigh.Value << 8) + clockDividerLow.Value));
843         }
844 
845         private IEnumerable<Channel> ActiveChannels =>
846             measurementEnabled.Select((active, idx) => active ? idx : -1).Where(x => x != -1).Select(x => (Channel)x);
847 
848         private bool FifoThresholdReached =>
849             (MaximumFIFOCount - circularFifo.Count) <= fifoFullThreshold.Value;
850 
851         private readonly IMachine machine;
852         private readonly AFESampleFIFO circularFifo;
853         private readonly bool[] measurementEnabled;
854         private readonly object feederThreadLock = new object();
855 
856         private readonly byte[] measurementLEDASource;
857         private readonly byte[] measurementLEDBSource;
858         private readonly byte[] measurementLEDCSource;
859 
860         private readonly uint[] measurementPDARange;
861         private readonly uint[] measurementPDBRange;
862 
863         private readonly ushort[] measurementPDAOffset;
864         private readonly ushort[] measurementPDBOffset;
865 
866         private bool feedingSamplesFromFile;
867         private bool staleConfiguration;
868 
869         private int measurement1ADCValue;
870         private int measurement2ADCValue;
871         private int measurement3ADCValue;
872         private int measurement4ADCValue;
873         private int measurement5ADCValue;
874         private int measurement6ADCValue;
875         private int measurement7ADCValue;
876         private int measurement8ADCValue;
877         private int measurement9ADCValue;
878 
879         private uint resdFrequencyMultiplier;
880 
881         private AFESampleFrame defaultMeasurements;
882         private IManagedThread feederThread;
883         private RESDStream<MAX86171_AFESample> resdStream;
884         private States? state;
885         private Registers? chosenRegister;
886         private bool previousFifoTresholdReached;
887 
888         private IFlagRegisterField statusFifoFull;
889 
890         private IFlagRegisterField clockSelect;
891         private IValueRegisterField clockDividerHigh;
892         private IValueRegisterField clockDividerLow;
893 
894         private IValueRegisterField[] measurementLEDACurrent;
895         private IValueRegisterField[] measurementLEDBCurrent;
896         private IValueRegisterField[] measurementLEDCCurrent;
897 
898         private IValueRegisterField[] ledRange;
899 
900         private IFlagRegisterField interrupt1FullEnabled;
901         private IFlagRegisterField interrupt2FullEnabled;
902 
903         private IFlagRegisterField fifoAssertThresholdOnce;
904         private IFlagRegisterField clearFlagsOnRead;
905 
906         private IValueRegisterField fifoFullThreshold;
907 
908         private IEnumRegisterField<OutputPinPolarity> polarityInterrupt1;
909         private IEnumRegisterField<OutputPinPolarity> polarityInterrupt2;
910 
911         private const int MeasurementRegisterCount = 9;
912         private const int MaximumFIFOCount = 256;
913 
914         [SampleType(SampleType.Custom)]
915         private class MAX86171_AFESample : RESDSample
916         {
917             public override int? Width => null;
918 
919             public int[] Frame { get; private set; }
920 
921             public ushort?[] ConfigLedAExposure => Enumerable.Range(0, MaxChannels).Select(i =>
922                 Metadata.TryGetValue($"meas{i}_led_a_exposure", out var value) ? value.As<ushort?>() : null
923             ).ToArray();
924 
925             public byte?[] ConfigLedASource => Enumerable.Range(0, MaxChannels).Select(i =>
926                 Metadata.TryGetValue($"meas{i}_led_a_source", out var value) ? value.As<byte?>() : null
927             ).ToArray();
928 
929             public ushort?[] ConfigLedBExposure => Enumerable.Range(0, MaxChannels).Select(i =>
930                 Metadata.TryGetValue($"meas{i}_led_b_exposure", out var value) ? value.As<ushort?>() : null
931             ).ToArray();
932 
933             public byte?[] ConfigLedBSource => Enumerable.Range(0, MaxChannels).Select(i =>
934                 Metadata.TryGetValue($"meas{i}_led_b_source", out var value) ? value.As<byte?>() : null
935             ).ToArray();
936 
937             public ushort?[] ConfigLedCExposure => Enumerable.Range(0, MaxChannels).Select(i =>
938                 Metadata.TryGetValue($"meas{i}_led_c_exposure", out var value) ? value.As<ushort?>() : null
939             ).ToArray();
940 
941             public byte?[] ConfigLedCSource => Enumerable.Range(0, MaxChannels).Select(i =>
942                 Metadata.TryGetValue($"meas{i}_led_c_source", out var value) ? value.As<byte?>() : null
943             ).ToArray();
944 
945             public byte?[] ConfigPD1SourceFlags => Enumerable.Range(0, MaxChannels).Select(i =>
946                 Metadata.TryGetValue($"meas{i}_pd_a_source_flags", out var value) ? value.As<byte?>() : null
947             ).ToArray();
948 
949             public uint?[] ConfigPD1ADCRange => Enumerable.Range(0, MaxChannels).Select(i =>
950                 Metadata.TryGetValue($"meas{i}_pd_a_adc_range", out var value) ? value.As<uint?>() : null
951             ).ToArray();
952 
953             public short?[] ConfigPD1DACOffset => Enumerable.Range(0, MaxChannels).Select(i =>
954                 Metadata.TryGetValue($"meas{i}_pd_a_dac_offset", out var value) ? value.As<short?>() : null
955             ).ToArray();
956 
957             public byte?[] ConfigPD2SourceFlags => Enumerable.Range(0, MaxChannels).Select(i =>
958                 Metadata.TryGetValue($"meas{i}_pd_b_source_flags", out var value) ? value.As<byte?>() : null
959             ).ToArray();
960 
961             public uint?[] ConfigPD2ADCRange => Enumerable.Range(0, MaxChannels).Select(i =>
962                 Metadata.TryGetValue($"meas{i}_pd_b_adc_range", out var value) ? value.As<uint?>() : null
963             ).ToArray();
964 
965             public short?[] ConfigPD2DACOffset => Enumerable.Range(0, MaxChannels).Select(i =>
966                 Metadata.TryGetValue($"meas{i}_pd_b_dac_offset", out var value) ? value.As<short?>() : null
967             ).ToArray();
968 
TryReadFromStream(SafeBinaryReader reader)969             public override bool TryReadFromStream(SafeBinaryReader reader)
970             {
971                 var frameLength = reader.ReadByte();
972                 var currentFrame = new int[frameLength];
973 
974                 for(var i = 0; i < frameLength; ++i)
975                 {
976                     currentFrame[i] = reader.ReadInt32();
977                 }
978 
979                 Frame = currentFrame;
980                 return true;
981             }
982 
Skip(SafeBinaryReader reader, int count)983             public override bool Skip(SafeBinaryReader reader, int count)
984             {
985                 for(; count > 0 && !reader.EOF; count--)
986                 {
987                     var frameLength = reader.ReadByte();
988                     reader.SkipBytes(frameLength * 4);
989                 }
990                 return count == 0;
991             }
992 
ToString()993             public override string ToString()
994             {
995                 var sampleFrame = Frame.Select(sample => new AFESample(sample));
996                 return String.Join(", ", sampleFrame.Select(frame => $"[{frame}]"));
997             }
998 
999             private const int MaxChannels = 9;
1000         }
1001 
1002         private class AFESampleFIFO
1003         {
AFESampleFIFO(MAX86171 parent)1004             public AFESampleFIFO(MAX86171 parent)
1005             {
1006                 samples = new Queue<AFESample>();
1007                 this.parent = parent;
1008                 Reset();
1009             }
1010 
EnqueueFrame(AFESampleFrame frame)1011             public void EnqueueFrame(AFESampleFrame frame)
1012             {
1013                 var activeChannels = parent.ActiveChannels.ToList();
1014                 var channelsInData = new List<Channel>();
1015                 foreach(var samplePair in frame.SamplePairs)
1016                 {
1017                     var channel = SampleSourceToChannel(samplePair[0].Tag);
1018                     if(!channel.HasValue || !activeChannels.Contains(channel.Value))
1019                     {
1020                         continue;
1021                     }
1022                     channelsInData.Add(channel.Value);
1023 
1024                     foreach(var sample in samplePair)
1025                     {
1026                         parent.circularFifo.EnqueueSample(sample);
1027                     }
1028                 }
1029 
1030                 var missingChannels = activeChannels.Except(channelsInData).ToList();
1031                 if(missingChannels.Count > 0)
1032                 {
1033                     parent.Log(LogLevel.Warning, "Provided sample data is missing samples for {0} channels: {1}",
1034                         missingChannels.Count,
1035                         string.Join(", ", missingChannels.Select(chan => chan.ToString())));
1036                 }
1037 
1038                 parent.UpdateStatus();
1039                 parent.UpdateInterrupts();
1040             }
1041 
EnqueueSample(AFESample sample)1042             public void EnqueueSample(AFESample sample)
1043             {
1044                 if(!Enabled)
1045                 {
1046                     return;
1047                 }
1048 
1049                 if(samples.Count == MaximumFIFOCount)
1050                 {
1051                     parent.Log(LogLevel.Warning, "Sample FIFO overrun");
1052                     if(Rollover)
1053                     {
1054                         samples.Dequeue();
1055                     }
1056                     else
1057                     {
1058                         return;
1059                     }
1060                 }
1061                 samples.Enqueue(sample);
1062             }
1063 
DequeueByte()1064             public byte DequeueByte()
1065             {
1066                 byte output = default(byte);
1067                 if(currentSampleEnumerator == null || !currentSampleEnumerator.TryGetNext(out output))
1068                 {
1069                     if(samples.TryDequeue(out var currentSample))
1070                     {
1071                         currentSampleEnumerator = currentSample.Enumerator;
1072                         currentSampleEnumerator.TryGetNext(out output);
1073                     }
1074                     else
1075                     {
1076                         return (byte)SampleSource.InvalidData;
1077                     }
1078                 }
1079                 // output variable is always set to proper value
1080                 return output;
1081             }
1082 
Clear()1083             public void Clear()
1084             {
1085                 samples.Clear();
1086             }
1087 
Reset()1088             public void Reset()
1089             {
1090                 Enabled = true;
1091                 Rollover = false;
1092                 Clear();
1093             }
1094 
SampleSourceToChannel(SampleSource ss)1095             private Channel? SampleSourceToChannel(SampleSource ss)
1096             {
1097                 switch(ss)
1098                 {
1099                     case SampleSource.PPGMeasurement1:
1100                         return Channel.Measurement1;
1101                     case SampleSource.PPGMeasurement2:
1102                         return Channel.Measurement2;
1103                     case SampleSource.PPGMeasurement3:
1104                         return Channel.Measurement3;
1105                     case SampleSource.PPGMeasurement4:
1106                         return Channel.Measurement4;
1107                     case SampleSource.PPGMeasurement5:
1108                         return Channel.Measurement5;
1109                     case SampleSource.PPGMeasurement6:
1110                         return Channel.Measurement6;
1111                     case SampleSource.PPGMeasurement7:
1112                         return Channel.Measurement7;
1113                     case SampleSource.PPGMeasurement8:
1114                         return Channel.Measurement8;
1115                     case SampleSource.PPGMeasurement9:
1116                         return Channel.Measurement9;
1117                     default:
1118                         return null;
1119                 }
1120             }
1121 
1122             public bool Enabled { get; set; }
1123             public bool Rollover { get; set; }
1124             public uint Count => (uint)samples.Count;
1125 
1126             private IEnumerator<byte> currentSampleEnumerator;
1127 
1128             private readonly Queue<AFESample> samples;
1129             private readonly MAX86171 parent;
1130         }
1131 
1132         private struct AFESample
1133         {
AFESampleAntmicro.Renode.Peripherals.Sensors.MAX86171.AFESample1134             public AFESample(decimal packet)
1135             {
1136                 innerPacket = (int)packet;
1137             }
1138 
AFESampleAntmicro.Renode.Peripherals.Sensors.MAX86171.AFESample1139             public AFESample(SampleSource tag, int value)
1140             {
1141                 innerPacket = (((int)tag & 0xF) << 20) | (value & 0x0FFFFF);
1142             }
1143 
1144             public int Value => ((innerPacket & 0x0FFFFF) << 12) >> 12;
1145             public SampleSource Tag => (SampleSource)((innerPacket & 0xF00000) >> 20);
1146             public byte Byte1 => (byte)((innerPacket & 0xFF0000) >> 16);
1147             public byte Byte2 => (byte)((innerPacket & 0x00FF00) >> 8);
1148             public byte Byte3 => (byte)(innerPacket & 0x0000FF);
1149             public byte[] Bytes => new byte[] { Byte1, Byte2, Byte3 };
1150             public IEnumerator<byte> Enumerator => Bytes.OfType<byte>().GetEnumerator();
1151 
ToStringAntmicro.Renode.Peripherals.Sensors.MAX86171.AFESample1152             public override string ToString()
1153             {
1154                 return $"{Tag}: {Value}";
1155             }
1156 
1157             private readonly int innerPacket;
1158         }
1159 
1160         private class AFESampleFrame
1161         {
AFESampleFrame(AFESample[] samples)1162             public AFESampleFrame(AFESample[] samples)
1163             {
1164                 this.samples = samples;
1165             }
1166 
1167             public IEnumerable<AFESample[]> SamplePairs
1168             {
1169                 get
1170                 {
1171                     var i = 0;
1172                     while(i < samples.Length)
1173                     {
1174                         if((i + 1) >= samples.Length || samples[i].Tag != samples[i + 1].Tag)
1175                         {
1176                             Logger.Log(LogLevel.Warning, "Missing second sample for {0}", samples[i].Tag);
1177                             i += 1;
1178                             continue;
1179                         }
1180 
1181                         if(i > 0 && samples[i - 1].Tag > samples[i].Tag)
1182                         {
1183                             Logger.Log(LogLevel.Warning, "Invalid order of samples: {0} is before {1}", samples[i - 1].Tag, samples[i].Tag);
1184                         }
1185 
1186                         yield return new AFESample[2] { samples[i], samples[i + 1] };
1187                         i += 2;
1188                     }
1189                 }
1190             }
1191 
1192             private AFESample[] samples;
1193         }
1194 
1195         private enum SampleSource : byte
1196         {
1197             Reserved1,
1198             PPGMeasurement1,
1199             PPGMeasurement2,
1200             PPGMeasurement3,
1201             PPGMeasurement4,
1202             PPGMeasurement5,
1203             PPGMeasurement6,
1204             PPGMeasurement7,
1205             PPGMeasurement8,
1206             PPGMeasurement9,
1207             PPGDarkData,
1208             PPGALCOverflow,
1209             PPGExposureOverflow,
1210             PPGPicketFenceData,
1211             InvalidData,
1212             Reaserved2,
1213         }
1214 
1215         private enum Channel
1216         {
1217             Measurement1,
1218             Measurement2,
1219             Measurement3,
1220             Measurement4,
1221             Measurement5,
1222             Measurement6,
1223             Measurement7,
1224             Measurement8,
1225             Measurement9,
1226         }
1227 
1228         private enum OutputPinPolarity
1229         {
1230             OpenDrainActiveLow = 0,
1231             ActiveHigh = 1,
1232             ActiveLow = 2,
1233             NotDefined = 3,
1234         }
1235 
1236         private enum States : byte
1237         {
1238             Write = 0x00,
1239             Read = 0x80,
1240         }
1241 
1242         private enum Registers : byte
1243         {
1244             Status1 = 0x00,
1245             Status2,
1246             Status3,
1247 
1248             FIFOWritePointer = 0x04,
1249             FIFOReadPointer,
1250             FIFOCounter1,
1251             FIFOCounter2,
1252             FIFOData,
1253             FIFOConfiguration1,
1254             FIFOConfiguration2,
1255 
1256             SystemConfiguration1 = 0x0C,
1257             SystemConfiguration2,
1258             SystemConfiguration3,
1259             PhotodiodeBias,
1260             PinFunctionalConfiguration,
1261             OutputPinConfiguration,
1262 
1263             FrameRateClockFrequency = 0x15,
1264             FrameRateClockDividerMSB,
1265             FrameRateClockDividerLSB,
1266 
1267             Measurement1Select = 0x18,
1268             Measurement1Configuration1,
1269             Measurement1Configuration2,
1270             Measurement1Configuration3,
1271             Measurement1DriverACurrent,
1272             Measurement1DriverBCurrent,
1273             Measurement1DriverCCurrent,
1274 
1275             Measurement2Select = 0x20,
1276             Measurement2Configuration1,
1277             Measurement2Configuration2,
1278             Measurement2Configuration3,
1279             Measurement2DriverACurrent,
1280             Measurement2DriverBCurrent,
1281             Measurement2DriverCCurrent,
1282 
1283             Measurement3Select = 0x28,
1284             Measurement3Configuration1,
1285             Measurement3Configuration2,
1286             Measurement3Configuration3,
1287             Measurement3DriverACurrent,
1288             Measurement3DriverBCurrent,
1289             Measurement3DriverCCurrent,
1290 
1291             Measurement4Select = 0x30,
1292             Measurement4Configuration1,
1293             Measurement4Configuration2,
1294             Measurement4Configuration3,
1295             Measurement4DriverACurrent,
1296             Measurement4DriverBCurrent,
1297             Measurement4DriverCCurrent,
1298 
1299             Measurement5Select = 0x38,
1300             Measurement5Configuration1,
1301             Measurement5Configuration2,
1302             Measurement5Configuration3,
1303             Measurement5DriverACurrent,
1304             Measurement5DriverBCurrent,
1305             Measurement5DriverCCurrent,
1306 
1307             Measurement6Select = 0x40,
1308             Measurement6Configuration1,
1309             Measurement6Configuration2,
1310             Measurement6Configuration3,
1311             Measurement6DriverACurrent,
1312             Measurement6DriverBCurrent,
1313             Measurement6DriverCCurrent,
1314 
1315             Measurement7Select = 0x48,
1316             Measurement7Configuration1,
1317             Measurement7Configuration2,
1318             Measurement7Configuration3,
1319             Measurement7DriverACurrent,
1320             Measurement7DriverBCurrent,
1321             Measurement7DriverCCurrent,
1322 
1323             Measurement8Select = 0x50,
1324             Measurement8Configuration1,
1325             Measurement8Configuration2,
1326             Measurement8Configuration3,
1327             Measurement8DriverACurrent,
1328             Measurement8DriverBCurrent,
1329             Measurement8DriverCCurrent,
1330 
1331             Measurement9Select = 0x58,
1332             Measurement9Configuration1,
1333             Measurement9Configuration2,
1334             Measurement9Configuration3,
1335             Measurement9DriverACurrent,
1336             Measurement9DriverBCurrent,
1337             Measurement9DriverCCurrent,
1338 
1339             ThresholdMeasurementSelect = 0x68,
1340             ThresholdHysteresis,
1341             PPGLoThreshold1,
1342             PPGHiThreshold1,
1343             PPGLoThreshold2,
1344             PPGHiThreshold2,
1345 
1346             PicketFenceMeasurementSelect = 0x70,
1347             PicketFenceConfiguration,
1348 
1349             Interrupt1Enable1 = 0x78,
1350             Interrupt1Enable2,
1351             Interrupt1Enable3,
1352             Interrupt2Enable1 = 0x7C,
1353             Interrupt2Enable2,
1354             Interrupt2Enable3,
1355 
1356             PartID = 0xFF,
1357         }
1358     }
1359 }
1360