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.Collections.Generic;
9 using System.Linq;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Core.Structure.Registers;
12 using Antmicro.Renode.Exceptions;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Peripherals.I2C;
15 using Antmicro.Renode.Peripherals.Sensor;
16 using Antmicro.Renode.Time;
17 using Antmicro.Renode.Utilities;
18 using Antmicro.Renode.Utilities.RESD;
19 
20 namespace Antmicro.Renode.Peripherals.Sensors
21 {
22     /// <summary>
23     /// This model supports different ways to provide sensor samples. Setting one of those overrides the other method.
24     /// 1. Explicit samples are fed when they are set manually, <see cref="Temperature"/> and <see cref="Humidity"/>.
25     /// 2. RESD samples are fed when the RESD file is loaded, <see cref="FeedTemperatureSamplesFromRESD"/> and <see cref="FeedHumiditySamplesFromRESD"/>.
26     /// </summary>
27     public class HS3001 : II2CPeripheral, IProvidesRegisterCollection<WordRegisterCollection>, ITemperatureSensor, IHumiditySensor
28     {
HS3001(IMachine machine, ushort sensorIdHigh = 0x0, ushort sensorIdLow = 0x0)29         public HS3001(IMachine machine, ushort sensorIdHigh = 0x0, ushort sensorIdLow = 0x0)
30         {
31             this.machine = machine;
32             this.registerWriteBuffer = new List<byte>();
33             this.sensorIdHigh = sensorIdHigh;
34             this.sensorIdLow = sensorIdLow;
35 
36             temperatureResolution = MeasurementResolution.Bits14;
37             humidityResolution = MeasurementResolution.Bits14;
38 
39             RegistersCollection = new WordRegisterCollection(this);
40             DefineRegisters();
41             Reset();
42         }
43 
FeedTemperatureSamplesFromRESD(ReadFilePath filePath, uint channelId = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)44         public void FeedTemperatureSamplesFromRESD(ReadFilePath filePath, uint channelId = 0,
45             RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)
46         {
47             resdTemperatureStream?.Dispose();
48             resdTemperatureStream = this.CreateRESDStream<TemperatureSample>(filePath, channelId, sampleOffsetType, sampleOffsetTime);
49         }
50 
FeedHumiditySamplesFromRESD(ReadFilePath filePath, uint channelId = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)51         public void FeedHumiditySamplesFromRESD(ReadFilePath filePath, uint channelId = 0,
52             RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)
53         {
54             resdHumidityStream?.Dispose();
55             resdHumidityStream = this.CreateRESDStream<HumiditySample>(filePath, channelId, sampleOffsetType, sampleOffsetTime);
56         }
57 
Write(params byte[] data)58         public void Write(params byte[] data)
59         {
60             if(isReconfiguringSensor)
61             {
62                 if(ExitReconfigurationSequence.SequenceEqual(data))
63                 {
64                     isReconfiguringSensor = false;
65                     currentRegister = null;
66                     registerWriteBuffer.Clear();
67                     this.DebugLog("Exiting sensor reconfiguration mode");
68                     return;
69                 }
70 
71                 registerWriteBuffer.AddRange(data);
72                 if(registerWriteBuffer.Count < RegisterAccessByteCount)
73                 {
74                     return;
75                 }
76 
77                 if(registerWriteBuffer.Count > RegisterAccessByteCount)
78                 {
79                     this.WarningLog("Register write received more than {0} bytes. Only the first {0} bytes will be taken", RegisterAccessByteCount);
80                 }
81 
82                 var registerWriteData = registerWriteBuffer.Take(RegisterAccessByteCount).ToArray();
83                 registerWriteBuffer.RemoveRange(0, RegisterAccessByteCount);
84 
85                 if(currentRegister.HasValue)
86                 {
87                     var value = BitHelper.ToUInt16(registerWriteData, 1, true);
88                     this.DebugLog("Writing 0x{0:X} to register {1}", value, (Registers)registerWriteData[0]);
89                     RegistersCollection.Write(registerWriteData[0], value);
90                     currentRegister = null;
91                 }
92                 else
93                 {
94                     // The rest of the data is intentionally dropped, as according to the datasheet
95                     // (HS300x datasheet chapter 4.9) the remaining two bytes should be set to 0
96                     currentRegister = (Registers)registerWriteData[0];
97 
98                     if(registerWriteData[1] != 0x0 || registerWriteData[2] != 0x0)
99                     {
100                         this.WarningLog("Expected the remaining bytes to be 0x0 when setting register address. Got 0x{0:X} and 0x{1:X} instead",
101                             registerWriteData[1], registerWriteData[2]);
102                     }
103                 }
104             }
105             else
106             {
107                 if(EnterReconfigurationSequence.SequenceEqual(data))
108                 {
109                     if(canReconfigureSensor)
110                     {
111                         isReconfiguringSensor = true;
112                         this.DebugLog("Entering sensor reconfiguration mode");
113                     }
114                     else
115                     {
116                         this.WarningLog("Attempted to reconfigure sensor with the ability to reconfigure the sensor disabled");
117                     }
118                 }
119                 else
120                 {
121                     this.WarningLog("Unexpected write to the sensor: {0}. Ignoring", Misc.PrettyPrintCollectionHex(data));
122                 }
123             }
124         }
125 
Read(int count = 1)126         public byte[] Read(int count = 1)
127         {
128             if(isReconfiguringSensor)
129             {
130                 var registerReadBuffer = new byte[RegisterAccessByteCount];
131 
132                 var registerToRead = currentRegister;
133                 currentRegister = null;
134 
135                 if(!registerToRead.HasValue)
136                 {
137                     this.ErrorLog("Register read address has not been set, returning default value");
138                     registerReadBuffer[0] = RegisterReadFail;
139                     return registerReadBuffer;
140                 }
141 
142                 if(!RegistersCollection.TryRead((long)registerToRead, out var value))
143                 {
144                     this.LogUnhandledRead((long)registerToRead);
145                     registerReadBuffer[0] = RegisterReadFail;
146                     return registerReadBuffer;
147                 }
148 
149                 registerReadBuffer[0] = RegisterReadSuccess;
150                 BitHelper.GetBytesFromValue(registerReadBuffer, 1, value, 2);
151 
152                 return registerReadBuffer;
153             }
154 
155             // Convertions based on HS300x datasheet chapter 5
156             var humidity = ConvertMeasurement(Humidity, humidityResolution, value => value * ((1 << 14) - 1) / 100);
157             var temperature = ConvertMeasurement(Temperature, temperatureResolution, value => ((1 << 14) - 1) * (value + 40) / 165, shift: 2);
158 
159             var humidityBytes = BitHelper.GetBytesFromValue(humidity, 2, true);
160             var temperatureBytes = BitHelper.GetBytesFromValue(temperature, 2, true);
161 
162             // Returns (HS300x datasheet chapter 4.6):
163             // | status, humidity[14:8] | humidity[7:0] | temperature[15:8] | temperature[7:2], mask |
164             var measurement = new byte[MeasurementByteCount] {
165                 humidityBytes[1], humidityBytes[0], temperatureBytes[1], temperatureBytes[0]
166             };
167 
168             SetStatusBits(ref measurement[0], MeasurementStatus.Valid);
169             return measurement;
170         }
171 
FinishTransmission()172         public void FinishTransmission()
173         {
174             currentRegister = null;
175             registerWriteBuffer.Clear();
176         }
177 
Reset()178         public void Reset()
179         {
180             RegistersCollection.Reset();
181             canReconfigureSensor = true;
182             isReconfiguringSensor = false;
183             registerWriteBuffer.Clear();
184             // From the HS300x datasheet chapter 4.8. Sensor can only be placed into the programming mode
185             // in the first 10ms after applying power to it
186             machine.ScheduleAction(TimeInterval.FromMilliseconds(10), _ => canReconfigureSensor = false);
187         }
188 
189         public WordRegisterCollection RegistersCollection { get; }
190 
191         public decimal Temperature
192         {
193             get => GetSampleFromRESDStream(ref resdTemperatureStream, (sample) => sample.Temperature / 1000, temperature);
194             set
195             {
196                 resdTemperatureStream?.Dispose();
197                 resdTemperatureStream = null;
198                 temperature = value;
199             }
200         }
201 
202         public decimal Humidity
203         {
204             get => GetSampleFromRESDStream(ref resdHumidityStream, (sample) => sample.Humidity / 1000, humidity);
205             set
206             {
207                 resdHumidityStream?.Dispose();
208                 resdHumidityStream = null;
209                 humidity = value;
210             }
211         }
212 
213         private decimal GetSampleFromRESDStream<T>(ref RESDStream<T> stream, Func<T, decimal> transformer, decimal defaultValue)
214             where T: RESDSample, new()
215         {
216             if(stream == null)
217             {
218                 return defaultValue;
219             }
220 
221             switch(stream.TryGetCurrentSample(this, transformer, out var sample, out _))
222             {
223             case RESDStreamStatus.OK:
224                 return sample;
225             case RESDStreamStatus.BeforeStream:
226                 return defaultValue;
227             case RESDStreamStatus.AfterStream:
228                 stream.Dispose();
229                 stream = null;
230                 return defaultValue;
231             default:
232                 throw new Exception("Unreachable");
233             }
234         }
235 
ConvertMeasurement(decimal value, MeasurementResolution resolution, Func<decimal, decimal> converter, int shift = 0)236         private ushort ConvertMeasurement(decimal value, MeasurementResolution resolution, Func<decimal, decimal> converter, int shift = 0)
237         {
238             var converted = converter(UpdateMeasurementResolution(resolution, value));
239             var clamped = converted.Clamp(0, MaxMeasurementValue);
240             return (ushort)(BitHelper.GetValue((uint)Math.Round(clamped), 0, MeasurementBits) << shift);
241         }
242 
SetStatusBits(ref byte data, MeasurementStatus status)243         private void SetStatusBits(ref byte data, MeasurementStatus status)
244         {
245             var d = (uint)data;
246             BitHelper.UpdateWith(ref d, (byte)status, 6, 2);
247             data = (byte)d;
248         }
249 
UpdateMeasurementResolution(MeasurementResolution resolution, decimal value)250         private decimal UpdateMeasurementResolution(MeasurementResolution resolution, decimal value)
251         {
252             if(value == 0)
253             {
254                 return 0;
255             }
256 
257             var valueCount = 1 << ResolutionToBitCount(resolution);
258             var step = Math.Floor(valueCount / value);
259             return step == 0 ? 0 : valueCount / step;
260         }
261 
ResolutionToBitCount(MeasurementResolution resolution)262         private byte ResolutionToBitCount(MeasurementResolution resolution)
263         {
264             switch(resolution)
265             {
266             case MeasurementResolution.Bits8:
267                 return 8;
268             case MeasurementResolution.Bits10:
269                 return 10;
270             case MeasurementResolution.Bits12:
271                 return 12;
272             case MeasurementResolution.Bits14:
273                 return 14;
274             default:
275                 throw new ArgumentException($"Invalid MeasurementResolution: {resolution}");
276             }
277         }
278 
DefineRegisters()279         private void DefineRegisters()
280         {
281             Registers.HumiditySensorResolutionRead.Define(this)
282                 .WithReservedBits(0, 10)
283                 .WithEnumField<WordRegister, MeasurementResolution>(10, 2, FieldMode.Read,
284                     name: "Humidity Sensor Resolution Read",
285                     valueProviderCallback: _ => humidityResolution)
286                 .WithReservedBits(12, 4);
287 
288             Registers.HumiditySensorResolutionWrite.Define(this)
289                 .WithReservedBits(0, 10)
290                 .WithEnumField<WordRegister, MeasurementResolution>(10, 2, FieldMode.Write,
291                     name: "Humidity Sensor Resolution Write",
292                     writeCallback: (_, value) => humidityResolution = value)
293                 .WithReservedBits(12, 4);
294 
295             Registers.TemperatureSensorResolutionRead.Define(this)
296                 .WithReservedBits(0, 10)
297                 .WithEnumField<WordRegister, MeasurementResolution>(10, 2, FieldMode.Read,
298                     name: "Temperature Sensor Resolution Read",
299                     valueProviderCallback: _ => temperatureResolution)
300                 .WithReservedBits(12, 4);
301 
302             Registers.TemperatureSensorResolutionWrite.Define(this)
303                 .WithReservedBits(0, 10)
304                 .WithEnumField<WordRegister, MeasurementResolution>(10, 2, FieldMode.Write,
305                     name: "Temperature Sensor Resolution Write",
306                     writeCallback: (_, value) => temperatureResolution = value)
307                 .WithReservedBits(12, 4);
308 
309             Registers.ReadSensorIDHigh.Define(this)
310                 .WithValueField(0, 16, FieldMode.Read, name: "Sensor ID High",
311                     valueProviderCallback: _ => sensorIdHigh);
312 
313             Registers.ReadSensorIDLow.Define(this)
314                 .WithValueField(0, 16, FieldMode.Read, name: "Sensor ID Low",
315                     valueProviderCallback: _ => sensorIdLow);
316         }
317 
318         private readonly IMachine machine;
319         private readonly List<byte> registerWriteBuffer;
320 
321         private readonly ushort sensorIdHigh;
322         private readonly ushort sensorIdLow;
323 
324         private Registers? currentRegister;
325         private RESDStream<TemperatureSample> resdTemperatureStream;
326         private RESDStream<HumiditySample> resdHumidityStream;
327 
328         private bool canReconfigureSensor;
329         private bool isReconfiguringSensor;
330 
331         private MeasurementResolution temperatureResolution;
332         private MeasurementResolution humidityResolution;
333 
334         private decimal temperature;
335         private decimal humidity;
336 
337         private const byte MeasurementBits = 14;
338         private const ushort MaxMeasurementValue = (1 << MeasurementBits) - 1;
339         private const byte RegisterAccessByteCount = 3;
340         private const byte MeasurementByteCount = 4;
341         private const byte RegisterReadSuccess = 0x81;
342         // Documentation only specifies the success status code. The fail
343         // code is defined under the assumption that any other value should
344         // be considered a fail status
345         private const byte RegisterReadFail = 0x0;
346 
347         private static readonly byte[] EnterReconfigurationSequence = new byte[]{ 0xA0, 0x00, 0x00 };
348         private static readonly byte[] ExitReconfigurationSequence = new byte[]{ 0x80, 0x00, 0x00 };
349 
350         private enum Registers : byte
351         {
352             HumiditySensorResolutionRead        = 0x6,
353             TemperatureSensorResolutionRead     = 0x11,
354             ReadSensorIDHigh                    = 0x1E,
355             ReadSensorIDLow                     = 0x1F,
356             HumiditySensorResolutionWrite       = 0x46,
357             TemperatureSensorResolutionWrite    = 0x51
358         }
359 
360         private enum MeasurementStatus : byte
361         {
362             Valid = 0x0,
363             Stale = 0x1,
364         }
365 
366         private enum MeasurementResolution: byte
367         {
368             Bits8 = 0b00,
369             Bits10 = 0b01,
370             Bits12 = 0b10,
371             Bits14 = 0b11,
372         }
373     }
374 }
375