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