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 8 using System; 9 using System.Linq; 10 using System.Collections.Generic; 11 using Antmicro.Renode.Peripherals.Bus; 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.Peripherals.Sensor; 17 18 namespace Antmicro.Renode.Peripherals.I2C 19 { 20 public class BME280 : II2CPeripheral, IProvidesRegisterCollection<ByteRegisterCollection>, ITemperatureSensor 21 { BME280()22 public BME280() 23 { 24 RegistersCollection = new ByteRegisterCollection(this); 25 DefineRegisters(); 26 Reset(); 27 } 28 Reset()29 public void Reset() 30 { 31 RegistersCollection.Reset(); 32 selectedRegister = 0x0; 33 EncodeTemperature(); 34 EncodeHumidity(); 35 EncodePressure(); 36 state = State.Idle; 37 } 38 Write(byte[] data)39 public void Write(byte[] data) 40 { 41 this.Log(LogLevel.Noisy, "Write {0}", data.Select(x => x.ToString("X")).Aggregate((x, y) => x + " " + y)); 42 43 foreach(var b in data) 44 { 45 switch(state) 46 { 47 case State.Idle: 48 selectedRegister = (Registers)b; 49 state = State.ReceivedFirstByte; 50 break; 51 case State.ReceivedFirstByte: 52 case State.WritingWaitingForValue: 53 RegistersCollection.Write((byte)selectedRegister, b); //bme280 have 256 addressable registers the same as byte max value 54 state = State.WaitingForAddress; 55 break; 56 case State.WaitingForAddress: 57 selectedRegister = (Registers)b; 58 state = State.WritingWaitingForValue; 59 break; 60 case State.Reading: 61 //this isn't documented, but reads are able to use address set during write transfer, opposite isn't true 62 this.Log(LogLevel.Warning, "Trying to write without specifying address, byte is omitted"); 63 break; 64 } 65 } 66 } 67 Read(int count = 0)68 public byte[] Read(int count = 0) 69 { 70 state = State.Reading; //reading can be started regardless of state, last selectedRegister is used 71 byte[] buf = new byte[count]; 72 for(int i = 0; i < buf.Length; i++) 73 { 74 //bme280 have 256 addressable registers, byte covers them all and allows roll-over like in real hardware 75 buf[i] = RegistersCollection.Read((byte)selectedRegister); 76 selectedRegister++; 77 } 78 this.Log(LogLevel.Noisy, "Read {0}", buf.Select(x => x.ToString("X")).Aggregate((x, y) => x + " " + y)); 79 80 return buf; 81 } 82 FinishTransmission()83 public void FinishTransmission() 84 { 85 if(state != State.ReceivedFirstByte) //in case of reading we may (documentation permits this or repeated START) receive STOP before the read transfer 86 { 87 if(state == State.WritingWaitingForValue) 88 { 89 this.Log(LogLevel.Warning, "Trying to write odd amount of bytes, last register is missing its value"); 90 } 91 state = State.Idle; 92 } 93 } 94 95 public decimal Temperature 96 { 97 get 98 { 99 return temperature; 100 } 101 set 102 { 103 temperature = value; 104 EncodeTemperature(); 105 } 106 } 107 108 public double Pressure 109 { 110 get 111 { 112 return pressure; 113 } 114 set 115 { 116 pressure = value; 117 EncodePressure(); 118 } 119 } 120 121 public double Humidity 122 { 123 get 124 { 125 return humidity; 126 } 127 set 128 { 129 humidity = value; 130 EncodeHumidity(); 131 } 132 } 133 134 public ByteRegisterCollection RegistersCollection { get; } 135 DefineRegisters()136 private void DefineRegisters() 137 { 138 Registers.HumLsb.Define(this, 0x0) 139 .WithValueField(0, 8, out humLsb, FieldMode.Read); 140 Registers.HumMsb.Define(this, 0x80) 141 .WithValueField(0, 8, out humMsb, FieldMode.Read); 142 Registers.TempXlsb.Define(this, 0x0) 143 .WithValueField(0, 8, out tempXlsb, FieldMode.Read); 144 Registers.TempLsb.Define(this, 0x0) 145 .WithValueField(0, 8, out tempLsb, FieldMode.Read); 146 Registers.TempMsb.Define(this, 0x80) 147 .WithValueField(0, 8, out tempMsb, FieldMode.Read); 148 Registers.PressXlsb.Define(this, 0x0) 149 .WithValueField(0, 8, out pressXlsb, FieldMode.Read); 150 Registers.PressLsb.Define(this, 0x0) 151 .WithValueField(0, 8, out pressLsb, FieldMode.Read); 152 Registers.PressMsb.Define(this, 0x80) 153 .WithValueField(0, 8, out pressMsb, FieldMode.Read); 154 Registers.Config.Define(this, 0x0) 155 .WithValueField(0, 8, name: "Config"); //read by the software, we need to implement it as a field, and not a tag 156 Registers.CtrlMeas.Define(this, 0x0) 157 .WithValueField(0, 8, name: "CtrlMeas"); //read by the software, we need to implement it as a field, and not a tag 158 Registers.Status.Define(this, 0x0) 159 .WithValueField(0, 8, name: "Status"); //read by the software, we need to implement it as a field, and not a tag 160 Registers.CtrlHum.Define(this, 0x0) 161 .WithValueField(0, 8, name: "CtrlHum"); //read by the software, we need to implement it as a field, and not a tag 162 Registers.Reset.Define(this, 0x0) 163 .WithValueField(0, 8) 164 .WithWriteCallback((_, val) => 165 { 166 if(val == resetRequestVal) 167 { 168 Reset(); 169 } 170 }); 171 Registers.Id.Define(this, 0x60) 172 .WithValueField(0, 8, FieldMode.Read, valueProviderCallback: _ => 0x60); 173 174 const ushort digT1T2 = 2 << 14; 175 176 Registers.Calib0.Define(this, unchecked((byte)digT1T2)); 177 Registers.Calib1.Define(this, (byte)(digT1T2 >> 8)); 178 Registers.Calib2.Define(this, unchecked((byte)digT1T2)); 179 Registers.Calib3.Define(this, (byte)(digT1T2 >> 8)); 180 Registers.Calib4.Define(this, 0x0); 181 Registers.Calib5.Define(this, 0x0); 182 183 const ushort digP1P2 = 1; 184 const ushort digP8 = 2 << 13; 185 186 Registers.Calib6.Define(this, (byte)digP1P2); 187 Registers.Calib7.Define(this, (byte)(digP1P2 >> 8)); 188 Registers.Calib8.Define(this, (byte)digP1P2); 189 Registers.Calib9.Define(this, (byte)(digP1P2 >> 8)); 190 Registers.Calib10.Define(this, 0x0); 191 Registers.Calib11.Define(this, 0x0); 192 Registers.Calib12.Define(this, 0x0); 193 Registers.Calib13.Define(this, 0x0); 194 Registers.Calib14.Define(this, 0x0); 195 Registers.Calib15.Define(this, 0x0); 196 Registers.Calib16.Define(this, 0x0); 197 Registers.Calib17.Define(this, 0x0); 198 Registers.Calib18.Define(this, 0x0); 199 Registers.Calib19.Define(this, 0x0); 200 Registers.Calib20.Define(this, unchecked((byte)digP8)); 201 Registers.Calib21.Define(this, (byte)(digP8 >> 8)); 202 Registers.Calib22.Define(this, 0x0); 203 Registers.Calib23.Define(this, 0x0); 204 Registers.Calib24.Define(this, 0x0); 205 206 const short digH2 = 361; 207 const short digH4 = 321; 208 const short digH5 = 50; 209 const sbyte digH6 = 30; 210 211 Registers.Calib25.Define(this, 0x0); 212 Registers.Calib26.Define(this, unchecked((byte)digH2)); 213 Registers.Calib27.Define(this, (byte)(digH2 >> 8)); 214 Registers.Calib28.Define(this, 0x0); 215 Registers.Calib29.Define(this, (byte)(digH4 >> 4)); 216 Registers.Calib30.Define(this, (byte)((digH4 & 0x0F) | (digH5 & 0x0F) << 4)); 217 Registers.Calib31.Define(this, (byte)(digH5 >> 4)); 218 Registers.Calib32.Define(this, (byte)digH6); 219 Registers.Calib33.Define(this, 0x0); 220 Registers.Calib34.Define(this, 0x0); 221 Registers.Calib35.Define(this, 0x0); 222 Registers.Calib36.Define(this, 0x0); 223 Registers.Calib37.Define(this, 0x0); 224 Registers.Calib38.Define(this, 0x0); 225 Registers.Calib39.Define(this, 0x0); 226 Registers.Calib40.Define(this, 0x0); 227 Registers.Calib41.Define(this, 0x0); 228 } 229 RegistersToUShort(Registers lo, Registers hi)230 private ushort RegistersToUShort(Registers lo, Registers hi) 231 { 232 ushort val = RegistersCollection.Read((byte)lo); 233 val |= (ushort)(RegistersCollection.Read((byte)hi) << 8); 234 return val; 235 } 236 RegistersToShort(Registers lo, Registers hi)237 private short RegistersToShort(Registers lo, Registers hi) 238 { 239 return (short)RegistersToUShort(lo, hi); 240 } 241 GetAdcTemperature()242 private int GetAdcTemperature() 243 { 244 var digT1 = RegistersToUShort(Registers.Calib0, Registers.Calib1); 245 var digT2 = RegistersToShort(Registers.Calib2, Registers.Calib3); 246 247 //formula and constants derived from the compensation formula in datasheet 248 return (int)Math.Round(((Temperature * 100 * 256 - 128)/(5 * digT2) * 2048 + digT1 * 2) * 8); 249 } 250 EncodeTemperature()251 private void EncodeTemperature() 252 { 253 int t = GetAdcTemperature(); 254 255 tempXlsb.Value = (byte)((t & 0x0F) << 4); 256 tempLsb.Value = (byte)(t >> 4); 257 tempMsb.Value = (byte)(t >> 12); 258 } 259 EncodePressure()260 private void EncodePressure() 261 { 262 var digT1 = RegistersToUShort(Registers.Calib0, Registers.Calib1); 263 var digT2 = RegistersToShort(Registers.Calib2, Registers.Calib3); 264 var digP1 = RegistersToUShort(Registers.Calib6, Registers.Calib7); 265 var digP2 = RegistersToShort(Registers.Calib8, Registers.Calib9); 266 var digP8 = RegistersToShort(Registers.Calib20, Registers.Calib21); 267 268 int adcTemp = GetAdcTemperature(); 269 //formula and constants derived from the compensation formula in datasheet 270 long v1 = (((Int64)2 << 47) + (adcTemp / 8 - digT1 * 2) * digT2 / 2048 - 128000) * digP2 * 4096 * digP1 / ((Int64)2 << 33); 271 int p = (int)Math.Round(-((Pressure - 52) * (2 << 27) / (digP8 + 1) * v1) / (3125 * ((Int64)2 << 31)) * 2 + 1048576); 272 273 pressXlsb.Value = (byte)((p & 0x0F) << 4); 274 pressLsb.Value = (byte)(p >> 4); 275 pressMsb.Value = (byte)(p >> 12); 276 } 277 EncodeHumidity()278 private void EncodeHumidity() 279 { 280 const ushort h0 = 20650; 281 const ushort h100 = 38550; 282 ushort h = (ushort)(h0 + (h100 - h0) * Humidity / 100); 283 284 humLsb.Value = (byte)h; 285 humMsb.Value = (byte)(h >> 8); 286 } 287 288 private State state; 289 private Registers selectedRegister; 290 291 private decimal temperature; 292 private double pressure; 293 private double humidity; 294 295 private IValueRegisterField humLsb; 296 private IValueRegisterField humMsb; 297 private IValueRegisterField tempXlsb; 298 private IValueRegisterField tempLsb; 299 private IValueRegisterField tempMsb; 300 private IValueRegisterField pressLsb; 301 private IValueRegisterField pressMsb; 302 private IValueRegisterField pressXlsb; 303 304 private const byte resetRequestVal = 0xB6; 305 306 private enum Registers 307 { 308 Calib0 = 0x88, 309 Calib1 = 0x89, 310 Calib2 = 0x8A, 311 Calib3 = 0x8B, 312 Calib4 = 0x8C, 313 Calib5 = 0x8D, 314 Calib6 = 0x8E, 315 Calib7 = 0x8F, 316 Calib8 = 0x90, 317 Calib9 = 0x91, 318 Calib10 = 0x92, 319 Calib11 = 0x93, 320 Calib12 = 0x94, 321 Calib13 = 0x95, 322 Calib14 = 0x96, 323 Calib15 = 0x97, 324 Calib16 = 0x98, 325 Calib17 = 0x99, 326 Calib18 = 0x9A, 327 Calib19 = 0x9B, 328 Calib20 = 0x9C, 329 Calib21 = 0x9D, 330 Calib22 = 0x9E, 331 Calib23 = 0x9F, 332 Calib24 = 0xA0, 333 Calib25 = 0xA1, 334 Id = 0xD0, 335 Reset = 0xE0, 336 Calib26 = 0xE1, 337 Calib27 = 0xE2, 338 Calib28 = 0xE3, 339 Calib29 = 0xE4, 340 Calib30 = 0xE5, 341 Calib31 = 0xE6, 342 Calib32 = 0xE7, 343 Calib33 = 0xE8, 344 Calib34 = 0xE9, 345 Calib35 = 0xEA, 346 Calib36 = 0xEB, 347 Calib37 = 0xEC, 348 Calib38 = 0xED, 349 Calib39 = 0xEE, 350 Calib40 = 0xEF, 351 Calib41 = 0xF0, 352 CtrlHum = 0xF2, 353 Status = 0xF3, 354 CtrlMeas = 0xF4, 355 Config = 0xF5, 356 PressMsb = 0xF7, 357 PressLsb = 0xF8, 358 PressXlsb = 0xF9, 359 TempMsb = 0xFA, 360 TempLsb = 0xFB, 361 TempXlsb = 0xFC, 362 HumMsb = 0xFD, 363 HumLsb = 0xFE 364 } 365 366 private enum State 367 { 368 Idle, 369 ReceivedFirstByte, 370 WaitingForAddress, 371 WritingWaitingForValue, 372 Reading 373 } 374 } 375 } 376