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