1 // 2 // Copyright (c) 2010-2023 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 Antmicro.Renode.Peripherals.Timers; 10 using Antmicro.Renode.Peripherals.Sensor; 11 using Antmicro.Renode.Peripherals.I2C; 12 using Antmicro.Renode.Logging; 13 using Antmicro.Renode.Core; 14 using Antmicro.Renode.Core.Structure.Registers; 15 using Antmicro.Renode.Utilities; 16 using Antmicro.Renode.Utilities.RESD; 17 18 namespace Antmicro.Renode.Peripherals.Sensors 19 { 20 public class AS6221 : II2CPeripheral, IProvidesRegisterCollection<WordRegisterCollection>, ITemperatureSensor 21 { AS6221(IMachine machine)22 public AS6221(IMachine machine) 23 { 24 RegistersCollection = new WordRegisterCollection(this); 25 26 this.machine = machine; 27 28 DefineRegisters(); 29 30 conversionTimer = new LimitTimer(machine.ClockSource, 1, this, "conv", autoUpdate: true, eventEnabled: true, enabled: true); 31 conversionTimer.LimitReached += HandleConversion; 32 33 Reset(); 34 } 35 FeedSamplesFromRESD(ReadFilePath filePath, uint channelId = 0, RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0)36 public void FeedSamplesFromRESD(ReadFilePath filePath, uint channelId = 0, 37 RESDStreamSampleOffset sampleOffsetType = RESDStreamSampleOffset.Specified, long sampleOffsetTime = 0) 38 { 39 resdStream = this.CreateRESDStream<TemperatureSample>(filePath, channelId, sampleOffsetType, sampleOffsetTime); 40 } 41 Write(byte[] data)42 public void Write(byte[] data) 43 { 44 if(data.Length == 0) 45 { 46 this.Log(LogLevel.Warning, "Unexpected write with no data"); 47 return; 48 } 49 50 var offset = 0; 51 if(state == States.Read) 52 { 53 this.Log(LogLevel.Warning, "Trying to write while in read mode; ignoring"); 54 return; 55 } 56 else if(state == States.Idle) 57 { 58 registerAddress = (Registers)data[0]; 59 offset = 1; 60 state = data.Length == 1 ? States.Read : States.Write; 61 } 62 63 if(data.Length > offset) 64 { 65 foreach(var item in data.Skip(offset).Select((value, index) => new { index, value })) 66 { 67 RegistersCollection.WriteWithOffset((long)registerAddress.Value, item.index, item.value, msbFirst: true); 68 } 69 } 70 } 71 Read(int count)72 public byte[] Read(int count) 73 { 74 if(!registerAddress.HasValue) 75 { 76 this.Log(LogLevel.Error, "Trying to read without setting address"); 77 return new byte[] {}; 78 } 79 80 if(state != States.Read) 81 { 82 this.Log(LogLevel.Error, "Trying to read while in write mode"); 83 return new byte[] {}; 84 } 85 86 var result = new byte[count]; 87 for(var i = 0; i < count; ++i) 88 { 89 result[i] = RegistersCollection.ReadWithOffset((long)registerAddress.Value, i, msbFirst: true); 90 } 91 return result; 92 } 93 FinishTransmission()94 public void FinishTransmission() 95 { 96 registerAddress = null; 97 state = States.Idle; 98 } 99 Reset()100 public void Reset() 101 { 102 RegistersCollection.Reset(); 103 conversionTimer.Reset(); 104 105 registerAddress = null; 106 state = States.Idle; 107 108 currentTemperature = 0; 109 temperatureLowThreshold = DefaultLowThreshold; 110 temperatureHighThreshold = DefaultHighThreshold; 111 112 UpdateConversionFrequency(); 113 } 114 115 public WordRegisterCollection RegistersCollection { get; } 116 117 public decimal Temperature 118 { 119 get => (decimal)currentTemperature / (decimal)Resolution; 120 set 121 { 122 if(value > MaximumTemperature) 123 { 124 currentTemperature = short.MaxValue; 125 this.Log(LogLevel.Warning, "{0} is higher than maximum of {1} and has been clamped", value, MaximumTemperature); 126 } 127 else if(value < MinimumTemperature) 128 { 129 currentTemperature = short.MinValue; 130 this.Log(LogLevel.Warning, "{0} is lower than minimum of {1} and has been clamped", value, MinimumTemperature); 131 } 132 else 133 { 134 currentTemperature = (short)(value * (decimal)Resolution); 135 } 136 } 137 } 138 UpdateConversionFrequency()139 private void UpdateConversionFrequency() 140 { 141 var frequencyAndLimit = ConversionRateToFrequencyAndLimit(conversionRate.Value); 142 conversionTimer.Frequency = frequencyAndLimit.Item1; 143 conversionTimer.Limit = frequencyAndLimit.Item2; 144 } 145 HandleConversion()146 private void HandleConversion() 147 { 148 if(resdStream != null) 149 { 150 switch(resdStream.TryGetCurrentSample(this, (sample) => sample.Temperature / 1000m, out var temperature, out _)) 151 { 152 case RESDStreamStatus.OK: 153 Temperature = temperature; 154 break; 155 case RESDStreamStatus.BeforeStream: 156 // Just ignore and return previously set value 157 break; 158 case RESDStreamStatus.AfterStream: 159 // No more samples in file 160 resdStream.Dispose(); 161 resdStream = null; 162 break; 163 } 164 } 165 166 if(interruptMode.Value) 167 { 168 alarmFlag = 169 currentTemperature > temperatureHighThreshold || currentTemperature < temperatureLowThreshold; 170 } 171 else 172 { 173 if(consecutiveFaults < consecutiveFaultsThreshold) 174 { 175 consecutiveFaults++; 176 } 177 alarmFlag = consecutiveFaults == consecutiveFaultsThreshold; 178 } 179 } 180 DefineRegisters()181 private void DefineRegisters() 182 { 183 Registers.Temperature.Define(this) 184 .WithValueField(0, 16, name: "TEMP.temp", 185 valueProviderCallback: _ => (uint)currentTemperature, 186 writeCallback: (_, value) => currentTemperature = (short)value) 187 ; 188 189 Registers.Configuration.Define(this, 0xA0) 190 .WithReservedBits(0, 5) 191 .WithFlag(5, FieldMode.Read, name: "CONF.alarm", 192 valueProviderCallback: _ => alarmFlag ? alarmPolarity.Value : !alarmPolarity.Value) 193 .WithEnumField<WordRegister, ConversionRate>(6, 2, out conversionRate, name: "CONF.conv_rate", 194 changeCallback: (_, __) => UpdateConversionFrequency()) 195 .WithTaggedFlag("CONF.sleep_mode", 8) 196 .WithFlag(9, out interruptMode, name: "CONF.interrupt_mode", 197 changeCallback: (_, value) => consecutiveFaults = 0) 198 .WithFlag(10, out alarmPolarity, name: "CONF.polarity") 199 .WithValueField(11, 2, name: "CONF.consecutive_faults", 200 valueProviderCallback: _ => consecutiveFaultsThreshold - 1, 201 writeCallback: (_, value) => 202 { 203 consecutiveFaultsThreshold = (uint)value + 1; 204 consecutiveFaults = 0; 205 }) 206 .WithReservedBits(13, 2) 207 .WithTaggedFlag("CONF.single_shot", 15) 208 ; 209 210 Registers.TemperatureLow.Define(this) 211 .WithValueField(0, 16, name: "TLOW.tlow", 212 valueProviderCallback: _ => (uint)temperatureLowThreshold, 213 writeCallback: (_, value) => 214 { 215 temperatureLowThreshold = (short)value; 216 consecutiveFaults = 0; 217 alarmFlag = false; 218 }) 219 ; 220 221 Registers.TemperatureHigh.Define(this) 222 .WithValueField(0, 16, name: "THIGH.thigh", 223 valueProviderCallback: _ => (uint)temperatureHighThreshold, 224 writeCallback: (_, value) => 225 { 226 temperatureHighThreshold = (short)value; 227 consecutiveFaults = 0; 228 alarmFlag = false; 229 }) 230 ; 231 } 232 ConversionRateToFrequencyAndLimit(ConversionRate cr)233 private static Tuple<int, ulong> ConversionRateToFrequencyAndLimit(ConversionRate cr) 234 { 235 switch(cr) 236 { 237 case ConversionRate.Quarter: 238 return new Tuple<int, ulong>(1, 4); 239 case ConversionRate.One: 240 return new Tuple<int, ulong>(1, 1); 241 case ConversionRate.Four: 242 return new Tuple<int, ulong>(4, 1); 243 case ConversionRate.Eight: 244 return new Tuple<int, ulong>(8, 1); 245 default: 246 throw new Exception("unreachable code"); 247 } 248 } 249 250 private Registers? registerAddress; 251 private States state; 252 private RESDStream<TemperatureSample> resdStream; 253 254 private IEnumRegisterField<ConversionRate> conversionRate; 255 256 private IFlagRegisterField alarmPolarity; 257 private IFlagRegisterField interruptMode; 258 259 private bool alarmFlag; 260 private short currentTemperature; 261 private short temperatureLowThreshold; 262 private short temperatureHighThreshold; 263 private uint consecutiveFaultsThreshold; 264 265 private uint consecutiveFaults; 266 267 private const short Resolution = 0x80; 268 private const short DefaultLowThreshold = 70 * Resolution; 269 private const short DefaultHighThreshold = 80 * Resolution; 270 private const decimal MaximumTemperature = (decimal)short.MaxValue / (decimal)Resolution; 271 private const decimal MinimumTemperature = (decimal)short.MinValue / (decimal)Resolution; 272 273 private readonly IMachine machine; 274 private readonly LimitTimer conversionTimer; 275 276 private enum States 277 { 278 Idle, 279 Write, 280 Read, 281 } 282 283 private enum ConversionRate 284 { 285 Quarter, 286 One, 287 Four, 288 Eight, 289 } 290 291 private enum Registers : byte 292 { 293 Temperature = 0x00, 294 Configuration = 0x01, 295 TemperatureLow = 0x02, 296 TemperatureHigh = 0x03, 297 } 298 } 299 } 300