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.Exceptions; 12 using Antmicro.Renode.Logging; 13 using Antmicro.Renode.Peripherals.I2C; 14 using Antmicro.Renode.Peripherals.Sensor; 15 using Antmicro.Renode.Utilities; 16 using Antmicro.Renode.Utilities.RESD; 17 18 namespace Antmicro.Renode.Peripherals.Sensors 19 { 20 public class ICP_101xx : II2CPeripheral, ISensor, ITemperatureSensor, IUnderstandRESD 21 { ICP_101xx(IMachine machine)22 public ICP_101xx(IMachine machine) 23 { 24 crcEngine = new CRCEngine(0x31, 8, false, false, 0xFF, 0x00); 25 writeHandlers = new Dictionary<Command, Action<byte[], int>>(); 26 readHandlers = new Dictionary<Command, Func<int, IEnumerable<byte>>>(); 27 DefaultPressure = MinPressure; 28 this.machine = machine; 29 DefineCommandHandlers(); 30 } 31 SoftwareReset()32 public void SoftwareReset() 33 { 34 command = null; 35 } 36 Reset()37 public void Reset() 38 { 39 SoftwareReset(); 40 41 temperatureResdStream?.Dispose(); 42 temperatureResdStream = null; 43 44 pressureResdStream?.Dispose(); 45 pressureResdStream = null; 46 } 47 Write(byte[] data)48 public void Write(byte[] data) 49 { 50 if(data.Length == 0) 51 { 52 this.Log(LogLevel.Error, "Unexpected write with no data."); 53 return; 54 } 55 if(data.Length < 2) 56 { 57 this.Log(LogLevel.Warning, "Malformed command."); 58 return; 59 } 60 this.Log(LogLevel.Debug, "Received command: {0}", Misc.PrettyPrintCollectionHex(data)); 61 command = (Command)BitHelper.ToUInt16(data, 0, reverse: false); 62 HandleWrite(data, 2); 63 } 64 Read(int count = 1)65 public byte[] Read(int count = 1) 66 { 67 if(!command.HasValue) 68 { 69 this.Log(LogLevel.Error, "Attempted read, but Command not specified."); 70 return Enumerable.Empty<byte>().ToArray(); 71 } 72 // Concat to handle cases, where HandleRead could return 0 bytes 73 var rets = InsertCrc(HandleRead(count)).Concat(new byte[]{ 0, 0 }).Take(count).ToArray(); 74 this.Log(LogLevel.Debug, "Reading data with CRC: {0}, current command: {1}, requested: {2} bytes", Misc.PrettyPrintCollectionHex(rets), GetCommandString(), count); 75 return rets; 76 } 77 FeedTemperatureSamplesFromRESD(ReadFilePath path, uint channelId = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)78 public void FeedTemperatureSamplesFromRESD(ReadFilePath path, uint channelId = 0, 79 RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0) 80 { 81 temperatureResdStream?.Dispose(); 82 temperatureResdStream = this.CreateRESDStream<TemperatureSample>(path, channelId, sampleOffsetType, sampleOffsetTime); 83 } 84 FeedPressureSamplesFromRESD(ReadFilePath path, uint channelId = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)85 public void FeedPressureSamplesFromRESD(ReadFilePath path, uint channelId = 0, 86 RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0) 87 { 88 pressureResdStream?.Dispose(); 89 pressureResdStream = this.CreateRESDStream<PressureSample>(path, channelId, sampleOffsetType, sampleOffsetTime); 90 } 91 FinishTransmission()92 public void FinishTransmission() 93 { 94 // Don't reset state machine (current command) here - instead the software driver should use Soft Reset command 95 } 96 SetCalibrationValue(uint index, ushort value)97 public void SetCalibrationValue(uint index, ushort value) 98 { 99 if(index >= CalibrationValues.Length) 100 { 101 throw new RecoverableException($"Index has to be in range 0 - {CalibrationValues.Length - 1}"); 102 } 103 CalibrationValues[index] = value; 104 } 105 106 // Sensor calibration values, used when reading the pressure 107 // This calibration values have been determined using statistical solution search with the following goals in mind: 108 // * decrease probability that the raw pressure value returned from the sensor is negative 109 // * make sure the raw pressure value fits in 3 bytes of return value 110 // * minimize error margin between set pressure and reported raw pressure (post-conversion) as much as possible, taking into account temperature changes 111 // in real HW these are definitely different, but for our use case they are sufficient 112 public ushort[] CalibrationValues { get; private set; } = new ushort[4] { 620, 17540, 9475, 2945 }; 113 // Another good vectors: 114 // { 15320, 15780, 32302, 3497 }; 115 // { 30261, -20852 /*44684*/, -24209 /*41327*/, 3107 }; 116 117 // Pressure is specified in Pascals 118 public long DefaultPressure 119 { 120 get => defaultPressure; 121 set 122 { 123 defaultPressure = value; 124 ClampPressureAndLogWarning(ref defaultPressure); 125 } 126 } 127 128 public long Pressure 129 { 130 get 131 { 132 UpdateCurrentPressureSample(); 133 ClampPressureAndLogWarning(ref pressure); 134 return pressure; 135 } 136 set 137 { 138 throw new RecoverableException("Explicitly setting pressure is not supported by this model. " + 139 $"Pressure should be provided from a RESD file or set via the '{nameof(DefaultPressure)}' property"); 140 } 141 } 142 143 // Temperature is specified in degrees Celsius 144 public decimal DefaultTemperature 145 { 146 get => defaultTemperature; 147 set 148 { 149 defaultTemperature = value; 150 ClampTemperatureAndLogWarning(ref defaultTemperature); 151 } 152 } 153 154 public decimal Temperature 155 { 156 get 157 { 158 UpdateCurrentTemperatureSample(); 159 ClampTemperatureAndLogWarning(ref temperature); 160 return temperature; 161 } 162 set 163 { 164 throw new RecoverableException("Explicitly setting temperature is not supported by this model. " + 165 $"Temperature should be provided from a RESD file or set via the '{nameof(DefaultTemperature)}' property"); 166 } 167 } 168 169 public const decimal MinTemperature = -45; 170 public const decimal MaxTemperature = 85; 171 172 public const uint MinPressure = 30000; 173 public const uint MaxPressure = 110000; 174 ClampPressureAndLogWarning(ref long pressure)175 private void ClampPressureAndLogWarning(ref long pressure) 176 { 177 if(pressure < MinPressure || pressure > MaxPressure) 178 { 179 this.Log(LogLevel.Warning, "Pressure value: {0} is out of range and it will be clamped. Supported range: {1} - {2}", pressure, MinPressure, MaxPressure); 180 pressure = pressure.Clamp(MinPressure, MaxPressure); 181 } 182 } 183 ClampTemperatureAndLogWarning(ref decimal temperature)184 private void ClampTemperatureAndLogWarning(ref decimal temperature) 185 { 186 if(temperature < MinTemperature || temperature > MaxTemperature) 187 { 188 this.Log(LogLevel.Warning, "Temperature value: {0} is out of range and it will be clamped. Supported range: {1} - {2}", temperature, MinTemperature, MaxTemperature); 189 temperature = temperature.Clamp(MinTemperature, MaxTemperature); 190 } 191 } 192 DefineCommandHandlers()193 private void DefineCommandHandlers() 194 { 195 RegisterCommand(Command.LowPowerTemperaturePressure, 196 readHandler: _ => Enumerable.Concat( 197 GetTemperatureBytes(OperationMode.LowPower), 198 GetPressureBytes(OperationMode.LowPower) 199 ) 200 ); 201 202 RegisterCommand(Command.LowPowerPressureTemperature, 203 readHandler: _ => Enumerable.Concat( 204 GetPressureBytes(OperationMode.LowPower), 205 GetTemperatureBytes(OperationMode.LowPower) 206 ) 207 ); 208 209 RegisterCommand(Command.NormalTemperaturePressure, 210 readHandler: _ => Enumerable.Concat( 211 GetTemperatureBytes(OperationMode.Normal), 212 GetPressureBytes(OperationMode.Normal) 213 ) 214 ); 215 216 RegisterCommand(Command.NormalPressureTemperature, 217 readHandler: _ => Enumerable.Concat( 218 GetPressureBytes(OperationMode.Normal), 219 GetTemperatureBytes(OperationMode.Normal) 220 ) 221 ); 222 223 RegisterCommand(Command.LowNoiseTemperaturePressure, 224 readHandler: _ => Enumerable.Concat( 225 GetTemperatureBytes(OperationMode.LowNoise), 226 GetPressureBytes(OperationMode.LowNoise) 227 ) 228 ); 229 230 RegisterCommand(Command.LowNoisePressureTemperature, 231 readHandler: _ => Enumerable.Concat( 232 GetPressureBytes(OperationMode.LowNoise), 233 GetTemperatureBytes(OperationMode.LowNoise) 234 ) 235 ); 236 237 RegisterCommand(Command.UltraLowNoiseTemperaturePressure, 238 readHandler: _ => Enumerable.Concat( 239 GetTemperatureBytes(OperationMode.UltraLowNoise), 240 GetPressureBytes(OperationMode.UltraLowNoise) 241 ) 242 ); 243 244 RegisterCommand(Command.UltraLowNoisePressureTemperature, 245 readHandler: _ => Enumerable.Concat( 246 GetPressureBytes(OperationMode.UltraLowNoise), 247 GetTemperatureBytes(OperationMode.UltraLowNoise) 248 ) 249 ); 250 251 RegisterCommand(Command.SoftReset, 252 writeHandler: (_, __) => 253 { 254 SoftwareReset(); 255 } 256 ); 257 258 RegisterCommand(Command.Id, 259 readHandler: _ => BitHelper.GetBytesFromValue(0b001000, 2, reverse: false) // It's already in the correct byte order 260 ); 261 262 RegisterCommand(Command.CalibrationParametersPointer, 263 writeHandler: (data, offset) => 264 { 265 if(data.Length <= offset + 2) 266 { 267 this.Log(LogLevel.Error, "Read from OTP received not enough bytes, got: {0}, total of: {0} are expected", data.Length, offset + 3); 268 return; 269 } 270 var addr = data[offset] << 16 | data[offset + 1] << 8 | data[offset + 2]; 271 if(addr != CalibrationAddress) 272 { 273 this.Log(LogLevel.Error, "Read from OTP from offset 0x{0:X}, different than 0x{1:X} is unsupported!", addr, CalibrationAddress); 274 return; 275 } 276 // Just reset the current calibration value number, as there is no data 277 // other than calibration values to be read from the sensor memory 278 // and the docs don't mention another usage for this command at this moment (rev 1.2) 279 calibrationValueIndex = 0; 280 } 281 ); 282 283 RegisterCommand(Command.CalibrationParameters, 284 readHandler: _ => 285 { 286 ushort rets = CalibrationValues[calibrationValueIndex]; 287 calibrationValueIndex = (calibrationValueIndex + 1) % CalibrationValues.Length; 288 return BitHelper.GetBytesFromValue(rets, 2, reverse: false); 289 } 290 ); 291 } 292 RegisterCommand(Command command, Action<byte[], int> writeHandler = null, Func<int, IEnumerable<byte>> readHandler = null)293 private void RegisterCommand(Command command, Action<byte[], int> writeHandler = null, Func<int, IEnumerable<byte>> readHandler = null) 294 { 295 if(writeHandlers.ContainsKey(command) || readHandlers.ContainsKey(command)) 296 { 297 throw new RecoverableException("Attempted to register command twice"); 298 } 299 300 this.Log(LogLevel.Noisy, "Registered command 0x{0:X}.", command); 301 302 writeHandlers.Add(command, writeHandler ?? ((data, offset) => 303 { 304 if(offset != data.Length) 305 { 306 this.Log(LogLevel.Warning, "Attempted write while handling read-only command '{0}'.", command); 307 } 308 })); 309 readHandlers.Add(command, readHandler ?? (count => 310 { 311 if(count != 0) 312 { 313 this.Log(LogLevel.Warning, "Attempted read while handling write-only command '{0}'.", command); 314 } 315 return Enumerable.Empty<byte>(); 316 })); 317 } 318 UpdateCurrentTemperatureSample()319 private void UpdateCurrentTemperatureSample() 320 { 321 if(TryGetSampleFromRESDStream<TemperatureSample>(temperatureResdStream, out var sample)) 322 { 323 temperature = (decimal)sample.Temperature / 1e3m; 324 } 325 else 326 { 327 temperature = DefaultTemperature; 328 } 329 } 330 UpdateCurrentPressureSample()331 private void UpdateCurrentPressureSample() 332 { 333 if(TryGetSampleFromRESDStream<PressureSample>(pressureResdStream, out var sample)) 334 { 335 pressure = (long)(sample.Pressure / 1000); 336 } 337 else 338 { 339 pressure = DefaultPressure; 340 } 341 } 342 343 private bool TryGetSampleFromRESDStream<T>(RESDStream<T> stream, out T sample) where T : RESDSample, new() 344 { 345 sample = null; 346 if(stream == null) 347 { 348 return false; 349 } 350 351 if(machine.SystemBus.TryGetCurrentCPU(out var cpu)) 352 { 353 cpu.SyncTime(); 354 } 355 356 var currentTimestamp = machine.ClockSource.CurrentValue.TotalNanoseconds; 357 if(stream.TryGetSample(currentTimestamp, out sample) == RESDStreamStatus.OK) 358 { 359 return true; 360 } 361 return false; 362 } 363 GetTemperatureBytes(OperationMode mode)364 private IEnumerable<byte> GetTemperatureBytes(OperationMode mode) 365 { 366 return BitHelper.GetBytesFromValue(GetTemperature(mode), 2, reverse: false); 367 } 368 GetTemperature(OperationMode _)369 private ushort GetTemperature(OperationMode _) 370 { 371 return (ushort)((Temperature + 45) * (1 << 16) / 175); 372 } 373 GetPressure(OperationMode mode)374 private uint GetPressure(OperationMode mode) 375 { 376 GetCoefficients(mode, out long A, out long B, out long C); 377 378 var decimalPressure = (B / (Pressure - A)) - C; 379 // Detect and report that the sensor might be miscalibrated 380 if(decimalPressure < 0 || decimalPressure > 0xFFFFFF) 381 { 382 this.Log(LogLevel.Warning, "Raw pressure value is invalid: {0}. The sensor might be miscalibrated.", decimalPressure); 383 } 384 uint rawPressure = (uint)decimalPressure; 385 386 this.Log(LogLevel.Noisy, "p_raw={0}, p_long={1}", rawPressure, decimalPressure); 387 return rawPressure; 388 } 389 GetPressureBytes(OperationMode mode)390 private IEnumerable<byte> GetPressureBytes(OperationMode mode) 391 { 392 // Shift value left, since LLSB is discarded (has no meaning in pressure calculation) 393 return BitHelper.GetBytesFromValue((uint)(GetPressure(mode) << 8), 4, reverse: false); 394 } 395 GetCoefficients(OperationMode mode, out long A, out long B, out long C)396 private void GetCoefficients(OperationMode mode, out long A, out long B, out long C) 397 { 398 long t = GetTemperature(mode) - 32768; 399 long p_LUT0 = LUT_lower + (CalibrationValues[0] * t * t) / InverseQuadraticFactor; 400 long p_LUT1 = OffsetFactor * CalibrationValues[3] + (CalibrationValues[1] * t * t) / InverseQuadraticFactor; 401 long p_LUT2 = LUT_upper + (CalibrationValues[2] * t * t) / InverseQuadraticFactor; 402 403 C = (p_LUT0 * p_LUT1 * (p_Pa[0] - p_Pa[1]) + 404 p_LUT1 * p_LUT2 * (p_Pa[1] - p_Pa[2]) + 405 p_LUT2 * p_LUT0 * (p_Pa[2] - p_Pa[0])) / 406 (p_LUT2 * (p_Pa[0] - p_Pa[1]) + 407 p_LUT0 * (p_Pa[1] - p_Pa[2]) + 408 p_LUT1 * (p_Pa[2] - p_Pa[0])); 409 410 A = (p_Pa[0] * p_LUT0 - p_Pa[1] * p_LUT1 - (p_Pa[1] - p_Pa[0]) * C) / (p_LUT0 - p_LUT1); 411 412 B = (p_Pa[0] - A) * (p_LUT0 + C); 413 414 this.Log(LogLevel.Noisy, "A={0} B={1} C={2}, p_LUT0={3}, p_LUT1={4}, p_LUT2={5}", A, B, C, p_LUT0, p_LUT1, p_LUT2); 415 } 416 InsertCrc(IEnumerable<byte> data)417 private IEnumerable<byte> InsertCrc(IEnumerable<byte> data) 418 { 419 var wordsNumber = 0; 420 var word = new byte[2]; 421 foreach(var b in data) 422 { 423 yield return b; 424 word[wordsNumber % 2] = b; 425 if(wordsNumber % 2 == 1) 426 { 427 yield return (byte)crcEngine.Calculate(word); 428 } 429 ++wordsNumber; 430 } 431 if(wordsNumber % 2 == 1) 432 { 433 throw new InvalidOperationException("Data length has to be multiple of 2 bytes"); 434 } 435 } 436 GetCommandString()437 private string GetCommandString() => command != null ? $"0x{command:X} ({command})" : "None"; 438 HandleWriteDefault(byte[] data, int offset)439 private void HandleWriteDefault(byte[] data, int offset) 440 { 441 this.Log(LogLevel.Warning, "Unhandled write, command: {0}.", GetCommandString()); 442 } 443 HandleReadDefault(int count)444 private IEnumerable<byte> HandleReadDefault(int count) 445 { 446 this.Log(LogLevel.Warning, "Unhandled read, command: {0}.", GetCommandString()); 447 return Enumerable.Empty<byte>(); 448 } 449 450 private Action<byte[], int> HandleWrite => writeHandlers.TryGetValue(command.Value, out var handler) ? handler : HandleWriteDefault; 451 private Func<int, IEnumerable<byte>> HandleRead => readHandlers.TryGetValue(command.Value, out var handler) ? handler : HandleReadDefault; 452 453 private Command? command; 454 455 private long defaultPressure; 456 private long pressure; 457 private decimal defaultTemperature; 458 private decimal temperature; 459 460 private readonly CRCEngine crcEngine; 461 private readonly IMachine machine; 462 private readonly Dictionary<Command, Action<byte[], int>> writeHandlers; 463 private readonly Dictionary<Command, Func<int, IEnumerable<byte>>> readHandlers; 464 465 // Which calibration value is read in incremental read-from OTP 466 private int calibrationValueIndex = 0; 467 468 private RESDStream<TemperatureSample> temperatureResdStream; 469 private RESDStream<PressureSample> pressureResdStream; 470 471 // Configuration constants, taken directly from the datasheet (https://invensense.tdk.com/wp-content/uploads/2021/06/DS-000408-ICP-10101-v1.2.pdf) 472 private readonly int[] p_Pa = new int [] { 45000, 80000, 105000 }; 473 private const int LUT_lower = 3670016; //3.5 * (1 << 20); 474 private const int LUT_upper = 12058624; //11.5 * (1 << 20); 475 private const int InverseQuadraticFactor = 16777216; 476 private const int OffsetFactor = 2048; 477 478 private const int CalibrationAddress = 0x00669C; 479 480 public enum OperationMode 481 { 482 LowPower, 483 Normal, 484 LowNoise, 485 UltraLowNoise, 486 } 487 488 public enum Command : ushort 489 { 490 LowPowerTemperaturePressure = 0x609C, 491 LowPowerPressureTemperature = 0x401A, 492 NormalTemperaturePressure = 0x6825, 493 NormalPressureTemperature = 0x48A3, 494 LowNoiseTemperaturePressure = 0x70DF, 495 LowNoisePressureTemperature = 0x5059, 496 UltraLowNoiseTemperaturePressure = 0x7866, 497 UltraLowNoisePressureTemperature = 0x58E0, 498 SoftReset = 0x805D, 499 Id = 0xEFC8, 500 CalibrationParametersPointer = 0xC595, 501 CalibrationParameters = 0xC7F7, 502 } 503 } 504 } 505