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 Antmicro.Renode.Core; 10 using Antmicro.Renode.Core.Structure.Registers; 11 using Antmicro.Renode.Logging; 12 using Antmicro.Renode.Exceptions; 13 using Antmicro.Renode.Peripherals.I2C; 14 using Antmicro.Renode.Peripherals.Sensor; 15 using Antmicro.Renode.Utilities; 16 17 namespace Antmicro.Renode.Peripherals.Sensors 18 { 19 public class ZMOD4xxx : II2CPeripheral, IProvidesRegisterCollection<ByteRegisterCollection>, ISensor 20 { ZMOD4xxx(Model model)21 public ZMOD4xxx(Model model) 22 { 23 this.model = model; 24 /* Below configurations are not a property of the sensor, but a fields that can vary between units. 25 Those are just one of the possible configurations that are proved to work */ 26 this.configuration = new byte[]{0x80,0x80,0x80,0x80,0x80,0x80}; 27 switch(model) 28 { 29 case Model.ZMOD4410: 30 productId = zmod4410_productId; 31 this.productionData = new byte[]{0x2D, 0xCF, 0x46, 0x29, 0x04, 0xB4}; 32 this.initConfigurationRField = new byte[] { 0x21, 0x48, 0x3B, 0xAE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 33 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 35 this.rField = new byte[] { 0x15, 0x48, 0xBF, 0x92, 0x8D, 0x59, 0xF2, 0x73, 0x42, 0xB5, 0x98, 0x1E, 0x8C, 0x09, 36 0x71, 0xDB, 0x51, 0x40, 0x64, 0x58, 0x4E, 0xBE, 0x14, 0xDF, 0xB7, 0xA2, 0x86, 0x9D, 37 0x4B, 0xB4, 0x02, 0x8D }; 38 break; 39 case Model.ZMOD4510: 40 productId = zmod4510_productId; 41 this.productionData = new byte[ProductionDataLengthInBytes]; 42 this.initConfigurationRField = new byte[] { 0x2A, 0xFC, 0xF3, 0xDF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 45 this.rField = new byte[] { 0x15, 0x6A, 0xBF, 0xC0, 0x1B, 0x48, 0x63, 0xA0, 0x84, 0xE8, 0xBF, 0x5C, 0x88, 0x18, 46 0x7E, 0xF4, 0x3F, 0x64, 0xCC, 0x47, 0xBC, 0x8A, 0x3C, 0x59, 0x33, 0xB8, 0x75, 0x88, 47 0x2C, 0x67, 0xC7, 0x5E }; 48 break; 49 default: 50 throw new ConstructionException($"This model ({model}) is not supported"); 51 } 52 53 RegistersCollection = new ByteRegisterCollection(this); 54 55 IRQ = new GPIO(); 56 57 DefineRegisters(); 58 Reset(); 59 } 60 Write(byte[] data)61 public void Write(byte[] data) 62 { 63 if(data.Length == 0) 64 { 65 this.DebugLog("Empty write. Ignoring"); 66 return; 67 } 68 currentRegister = data[0]; 69 this.DebugLog("Address set to 0x{0:X} [{1}]", currentRegister, (Registers)currentRegister); 70 foreach(var b in data.Skip(1)) 71 { 72 this.DebugLog("Writing 0x{0:x} to addr 0x{1:x}", b, currentRegister); 73 // Using TryWrite to avoid logs on unhandled writes 74 RegistersCollection.TryWrite(currentRegister, b); 75 currentRegister += 1; 76 } 77 } 78 Read(int count)79 public byte[] Read(int count) 80 { 81 var response = new byte[count]; 82 for(var index = 0; index < count; index++) 83 { 84 // Using TryRead to avoid logs on unhandled reads 85 RegistersCollection.TryRead(currentRegister, out response[index]); 86 this.DebugLog("Read 0x{0:x} from addr 0x{1:x}", response[index], currentRegister); 87 currentRegister++; 88 } 89 return response; 90 } 91 FinishTransmission()92 public void FinishTransmission() 93 { 94 this.DebugLog("Finished transmission"); 95 } 96 Reset()97 public void Reset() 98 { 99 RegistersCollection.Reset(); 100 sensorInMeasureMode = false; 101 currentRegister = 0; 102 } 103 104 public String RValue 105 { 106 get 107 { 108 return Misc.Stringify(rField); 109 } 110 set 111 { 112 if(!TryParseHexStringWithAssertedLength(value, ResultLengthInBytes, out rField, out var err)) 113 { 114 throw new RecoverableException(err); 115 } 116 } 117 } 118 119 public String InitConfigurationRValue 120 { 121 get 122 { 123 return Misc.Stringify(initConfigurationRField); 124 } 125 set 126 { 127 if(!TryParseHexStringWithAssertedLength(value, ResultLengthInBytes, out initConfigurationRField, out var err)) 128 { 129 throw new RecoverableException(err); 130 } 131 } 132 } 133 134 public String Configuration 135 { 136 get 137 { 138 return Misc.Stringify(configuration); 139 } 140 set 141 { 142 if(!TryParseHexStringWithAssertedLength(value, ConfigurationLengthInBytes, out configuration, out var err)) 143 { 144 throw new RecoverableException(err); 145 } 146 } 147 } 148 149 public String ProductionData 150 { 151 get 152 { 153 return Misc.Stringify(productionData); 154 } 155 set 156 { 157 if(!TryParseHexStringWithAssertedLength(value, ProductionDataLengthInBytes, out productionData, out var err)) 158 { 159 throw new RecoverableException(err); 160 } 161 } 162 } 163 164 public GPIO IRQ { get; } 165 166 public ByteRegisterCollection RegistersCollection { get; } 167 TryParseHexStringWithAssertedLength(string hexstring, int expectedLenghtInBytes, out byte[] byteArray, out string err)168 private bool TryParseHexStringWithAssertedLength(string hexstring, int expectedLenghtInBytes, out byte[] byteArray, out string err) 169 { 170 byteArray = new byte[expectedLenghtInBytes]; 171 err = ""; 172 173 if(hexstring.Length != (expectedLenghtInBytes * 2)) 174 { 175 err = $"Wrong hexsting length. Expected {expectedLenghtInBytes} bytes"; 176 return false; 177 } 178 179 if(!Misc.TryParseHexString(hexstring, out byteArray, elementSize: 1)) 180 { 181 err = "Unable to parse as a hexstring"; 182 return false; 183 } 184 return true; 185 } 186 EmulateMeasurementFinished()187 private void EmulateMeasurementFinished() 188 { 189 // Normally the sensor sets this line high when starting the measurements, and then sets is low when finished. 190 // We emulate the measurement as instantaneous 191 IRQ.Blink(); 192 } 193 DefineRegisters()194 private void DefineRegisters() 195 { 196 Registers.ProductID0.Define(this) 197 .WithValueField(0, 8, FieldMode.Read, valueProviderCallback: (_) => productId[0], name: "PID 0"); 198 199 Registers.ProductID1.Define(this) 200 .WithValueField(0, 8, FieldMode.Read, valueProviderCallback: (_) => productId[1], name: "PID 1"); 201 202 Registers.Configuration.DefineMany(this, ConfigurationLengthInBytes, (register, index) => 203 { 204 register 205 .WithValueField(0, 8, FieldMode.Read, valueProviderCallback: (_) => configuration[index], name: $"Configuration{index}"); 206 }); 207 208 Registers.ProductionData.DefineMany(this, ProductionDataLengthInBytes, (register, index) => 209 { 210 register 211 .WithValueField(0, 8, FieldMode.Read, valueProviderCallback: (_) => productionData[index], name: $"ProductionData{index}"); 212 }); 213 214 Registers.Tracking.DefineMany(this, TrackingLengthInBytes, (register, index) => 215 { 216 register 217 .WithTag($"Tracking{index}", 0, 8); 218 }); 219 220 Registers.H.DefineMany(this, HLengthInBytes, (register, index) => 221 { 222 register 223 .WithTag($"H{index}", 0, 8); 224 }); 225 226 Registers.D.DefineMany(this, DLengthInBytes, (register, index) => 227 { 228 register 229 .WithTag($"D{index}", 0, 8); 230 }); 231 232 Registers.M.DefineMany(this, MLengthInBytes, (register, index) => 233 { 234 register 235 .WithTag($"M{index}", 0, 8) 236 .WithWriteCallback((_, val) => 237 { 238 if(index == 0) 239 { 240 sensorInMeasureMode = (val != M0InitValue); 241 this.DebugLog("Sensor in measure state = {0}", sensorInMeasureMode); 242 } 243 }); 244 }); 245 246 Registers.S.DefineMany(this, SLengthInBytes, (register, index) => 247 { 248 register 249 .WithTag($"S{index}", 0, 8); 250 }); 251 252 Registers.Command.Define(this) 253 .WithReservedBits(0, 7) 254 .WithFlag(7, changeCallback: (_, val) => { 255 if(val) 256 { 257 this.DebugLog("Measurement trigerred"); 258 EmulateMeasurementFinished(); 259 } 260 else 261 { 262 this.DebugLog("Measurement stopped"); 263 } 264 }, name: "Start"); 265 266 Registers.Status0.Define(this) 267 .WithTag("Last executed sequencer step", 0, 5) 268 .WithTaggedFlag("Alarm", 5) 269 .WithTaggedFlag("Sleep Timer Enabled", 6) 270 .WithTaggedFlag("Sequencer Running", 7); 271 272 Registers.Result.DefineMany(this, ResultLengthInBytes, (register, index) => 273 { 274 register 275 .WithValueField(0, 8, FieldMode.Read, valueProviderCallback: (_) => sensorInMeasureMode ? rField[index] : initConfigurationRField[index], name: $"R{index}"); 276 }); 277 278 Registers.Error.Define(this) 279 .WithReservedBits(0, 6) 280 .WithTaggedFlag("Access Conflict", 6) 281 .WithTaggedFlag("POR Event", 7); 282 } 283 284 private readonly Model model; 285 286 private const int ResultLengthInBytes = 32; 287 private const int ConfigurationLengthInBytes = 6; 288 private const int ProductionDataLengthInBytes = 6; 289 private const int TrackingLengthInBytes = 6; 290 private const int HLengthInBytes = 10; 291 private const int DLengthInBytes = 6; 292 private const int MLengthInBytes = 1; 293 private const int SLengthInBytes = 30; 294 295 // There's no documentation for that, but this is what gets written in the init phase 296 private const int M0InitValue = 0xC3; 297 298 private readonly byte[] zmod4510_productId = new byte[] { 0x63, 0x20 }; 299 private readonly byte[] zmod4410_productId = new byte[] { 0x23, 0x10 }; 300 private readonly byte[] productId; 301 private byte[] configuration; 302 private byte[] productionData; 303 private byte[] initConfigurationRField; 304 private byte[] rField; 305 306 private byte currentRegister; 307 private bool sensorInMeasureMode; 308 309 public enum Model 310 { 311 // This is also the address on the I2C bus 312 ZMOD4510 = 0x33, 313 ZMOD4410 = 0x32, 314 } 315 316 private enum Registers : byte 317 { 318 ProductID0 = 0x0, 319 ProductID1 = 0x1, 320 Configuration = 0x20, 321 ProductionData = 0x26, 322 Tracking = 0x3A, 323 H = 0x40, 324 D = 0x50, 325 M = 0x60, 326 S = 0x68, 327 Command = 0x93, 328 Status0 = 0x94, 329 Result = 0x97, 330 Error = 0xB7, 331 } 332 } 333 } 334