1 //
2 // Copyright (c) 2010-2020 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.Collections.Generic;
10 using System.IO;
11 using System.Linq;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Core.Structure.Registers;
14 using Antmicro.Renode.Logging;
15 using Antmicro.Renode.Peripherals.I2C;
16 using Antmicro.Renode.Peripherals.Sensor;
17 using Antmicro.Renode.Utilities;
18 using Antmicro.Renode.Exceptions;
19 
20 namespace Antmicro.Renode.Peripherals.Sensors
21 {
22     public class LIS2DS12 : II2CPeripheral, IProvidesRegisterCollection<ByteRegisterCollection>, ISensor, ITemperatureSensor
23     {
LIS2DS12()24         public LIS2DS12()
25         {
26             RegistersCollection = new ByteRegisterCollection(this);
27             IRQ = new GPIO();
28             DefineRegisters();
29         }
30 
FinishTransmission()31         public void FinishTransmission(){}
32 
Reset()33         public void Reset()
34         {
35             RegistersCollection.Reset();
36             IRQ.Set(false);
37             regAddress = 0;
38         }
39 
Write(byte[] data)40         public void Write(byte[] data)
41         {
42             if(data.Length == 0)
43             {
44                 this.Log(LogLevel.Warning, "Unexpected write with no data");
45                 return;
46             }
47 
48             this.Log(LogLevel.Noisy, "Write with {0} bytes of data: {1}", data.Length, Misc.PrettyPrintCollectionHex(data));
49             regAddress = (Registers)data[0];
50 
51             if(data.Length > 1)
52             {
53                 // skip the first byte as it contains register address
54                 foreach(var b in data.Skip(1))
55                 {
56                     this.Log(LogLevel.Noisy, "Writing 0x{0:X} to register {1} (0x{1:X})", b, regAddress);
57                     RegistersCollection.Write((byte)regAddress, b);
58                 }
59             }
60             else
61             {
62                 this.Log(LogLevel.Noisy, "Preparing to read register {0} (0x{0:X})", regAddress);
63                 readyPending.Value = true;
64                 UpdateInterrupts();
65             }
66         }
67 
Read(int count)68         public byte[] Read(int count)
69         {
70             this.Log(LogLevel.Noisy, "Reading {0} bytes from register {1} (0x{1:X})", count, regAddress);
71             var result = new byte[count];
72             readyPending.Value = false;
73             UpdateInterrupts();
74             for(var i = 0; i < result.Length; i++)
75             {
76                 result[i] = RegistersCollection.Read((byte)regAddress);
77                 this.Log(LogLevel.Noisy, "Read value {0}", result[i]);
78                 RegistersAutoIncrement();
79             }
80             return result;
81         }
82 
83         public decimal AccelerationX
84         {
85             get => accelarationX;
86             set
87             {
88                 if (!IsAccelerationOutOfRange(value))
89                 {
90                     accelarationX = value;
91                     this.Log(LogLevel.Noisy, "AccelerationX set to {0}", accelarationX);
92                 }
93             }
94         }
95 
96         public decimal AccelerationY
97         {
98             get => accelarationY;
99             set
100             {
101                 if (!IsAccelerationOutOfRange(value))
102                 {
103                     accelarationY = value;
104                     this.Log(LogLevel.Noisy, "AccelerationY set to {0}", accelarationY);
105                 }
106             }
107         }
108 
109         public decimal AccelerationZ
110         {
111             get => accelarationZ;
112             set
113             {
114                 if (!IsAccelerationOutOfRange(value))
115                 {
116                     accelarationZ = value;
117                     this.Log(LogLevel.Noisy, "AccelerationZ set to {0}", accelarationZ);
118                 }
119             }
120         }
121 
122         public decimal Temperature { get; set; }
123 
124         public GPIO IRQ { get; }
125         public ByteRegisterCollection RegistersCollection { get; }
126 
DefineRegisters()127         private void DefineRegisters()
128         {
129             Registers.WhoAmI.Define(this, 0x43);
130 
131             Registers.Control1.Define(this) //RW
132                 .WithTaggedFlag("BLOCK_DATA_UPDATE", 0)
133                 .WithFlag(1, out highFreqDataRateMode, name: "HIGH_FREQ_MODE_ENABLE")
134                 .WithValueField(2, 2, out fullScale, name: "FULL_SCALE_SELECT")
135                 .WithValueField(4, 4, out outDataRate, name: "OUTPUT_DATA_RATE");
136 
137             Registers.Control4.Define(this, 0x01) //RW
138                 .WithFlag(0, out readyEnabled, name: "DATA_READY_IRQ1_ENABLE")
139                 .WithTaggedFlag("FIFO_THRESHOLD_IRQ1_ENABLE", 1)
140                 .WithTaggedFlag("6D_RECON_IRQ1_ENABLE", 2)
141                 .WithTaggedFlag("DOUBLE_TAP_RECON_IRQ1_ENABLE", 3)
142                 .WithTaggedFlag("FREE_FALL_RECON_IRQ1_ENABLE", 4)
143                 .WithTaggedFlag("WAKEUP_RECON_IRQ1_ENABLE", 5)
144                 .WithTaggedFlag("SINGLE_TAP_RECON_IRQ1_ENABLE", 6)
145                 .WithTaggedFlag("MASTER_DATA_READY_IRQ1_ENABLE", 7)
146                 .WithWriteCallback((_, __) => UpdateInterrupts());
147 
148             Registers.TemperatureOut.Define(this)
149                 .WithValueField(0, 8, FieldMode.Read, name: "TEMPERATURE_SENSOR", valueProviderCallback: _ => TwoComplementSignConvert(Temperature));
150 
151             Registers.Status.Define(this) //RO
152                 .WithFlag(0, out readyPending, FieldMode.Read, name: "XYZ_DATA_AVAILABLE")
153                 .WithTaggedFlag("FREE_FALL_EVENT_DETECT", 1)
154                 .WithTaggedFlag("CHANGE_IN_POSITION_DETECT", 2)
155                 .WithTaggedFlag("SINGLE_TAP_EVENT_DETECT", 3)
156                 .WithTaggedFlag("DOUBLE_TAP_EVENT_DETECT", 4)
157                 .WithTaggedFlag("SLEEP_EVENT_DETECT", 5)
158                 .WithTaggedFlag("WAKEUP_EVENT_DETECT", 6)
159                 .WithTaggedFlag("FIFO_REACH_THRESHOLD", 7);
160 
161             Registers.DataOutXLow.Define(this)
162                 .WithValueField(0, 8, FieldMode.Read, name: "X_ACCEL_DATA[7:2]", valueProviderCallback: _ => Convert(AccelerationX, upperByte: false));
163 
164             Registers.DataOutXHigh.Define(this)
165                 .WithValueField(0, 8, FieldMode.Read, name: "X_ACCEL_DATA[15:8]", valueProviderCallback: _ => Convert(AccelerationX, upperByte: true));
166 
167             Registers.DataOutYLow.Define(this)
168                 .WithValueField(0, 8, FieldMode.Read, name: "Y_ACCEL_DATA[7:2]", valueProviderCallback: _ => Convert(AccelerationY, upperByte: false));
169 
170             Registers.DataOutYHigh.Define(this)
171                 .WithValueField(0, 8, FieldMode.Read, name: "Y_ACCEL_DATA[15:8]", valueProviderCallback: _ => Convert(AccelerationY, upperByte: true));
172 
173             Registers.DataOutZLow.Define(this)
174                 .WithValueField(0, 8, FieldMode.Read, name: "Z_ACCEL_DATA[7:2]", valueProviderCallback: _ => Convert(AccelerationZ, upperByte: false));
175 
176             Registers.DataOutZHigh.Define(this)
177                 .WithValueField(0, 8, FieldMode.Read, name: "Z_ACCEL_DATA[15:8]", valueProviderCallback: _ => Convert(AccelerationZ, upperByte: true));
178 
179             Registers.StatusEventDetection.Define(this) //RO
180                 .WithFlag(0, out readyPending, FieldMode.Read, name: "XYZ_DATA_AVAILABLE")
181                 .WithTaggedFlag("FREE_FALL_EVENT_DETECT", 1)
182                 .WithTaggedFlag("CHANGE_IN_POSITION_DETECT", 2)
183                 .WithTaggedFlag("SINGLE_TAP_EVENT_DETECT", 3)
184                 .WithTaggedFlag("DOUBLE_TAP_EVENT_DETECT", 4)
185                 .WithTaggedFlag("SLEEP_EVENT_DETECT", 5)
186                 .WithTaggedFlag("WAKEUP_EVENT_DETECT", 6)
187                 .WithTaggedFlag("FIFO_REACH_THRESHOLD", 7);
188         }
189 
RegistersAutoIncrement()190         private void RegistersAutoIncrement()
191         {
192             if(regAddress >= Registers.DataOutXLow && regAddress < Registers.DataOutZHigh)
193             {
194                 regAddress = (Registers)((int)regAddress + 1);
195                 this.Log(LogLevel.Noisy, "Auto-incrementing to the next register 0x{0:X} - {0}", regAddress);
196             }
197         }
198 
UpdateInterrupts()199         private void UpdateInterrupts()
200         {
201             var status = readyEnabled.Value && readyPending.Value;
202             this.Log(LogLevel.Noisy, "Setting IRQ to {0}", status);
203             IRQ.Set(status);
204         }
205 
GetSenesorSensitivity()206         private decimal GetSenesorSensitivity()
207         {
208             decimal gain = SensorSensitivity;
209             switch(fullScale.Value)
210             {
211                 case (uint)FullScaleSelect.fullScale2g:
212                     gain = SensorSensitivity;
213                     break;
214                 case (uint)FullScaleSelect.fullScale16g:
215                     gain = 8 * SensorSensitivity;
216                     break;
217                 case (uint)FullScaleSelect.fullScale4g:
218                     gain = 2 * SensorSensitivity;
219                     break;
220                 case (uint)FullScaleSelect.fullScale8g:
221                     gain = 4 * SensorSensitivity;
222                     break;
223                 default:
224                     gain = SensorSensitivity;
225                     break;
226             }
227             return gain;
228         }
229 
IsAccelerationOutOfRange(decimal acceleration)230         private bool IsAccelerationOutOfRange(decimal acceleration)
231         {
232             // This range protects from the overflow of the short variables in the 'Convert' function.
233             if (acceleration < MinAcceleration || acceleration > MaxAcceleration)
234             {
235                 this.Log(LogLevel.Warning, "Acceleration is out of range, use value from the range <-19.5;19.5>");
236                 return true;
237             }
238             return false;
239         }
240 
Convert(decimal value, bool upperByte)241         private byte Convert(decimal value, bool upperByte)
242         {
243             byte result = 0;
244             decimal gain = GetSenesorSensitivity();
245             value = (value * 1000 / gain) / GravitationalConst;
246             var valueAsShort = (short)value;
247 
248             if(upperByte)
249             {
250                 result = (byte)(valueAsShort >> 8);
251             }
252             else
253             {
254                 if(highFreqDataRateMode.Value &&
255                     outDataRate.Value >= (byte)DataRateModeStartRange.HighFreqDataRateStartRange &&
256                     outDataRate.Value < (byte)DataRateModeStartRange.LowPowerDataRateStartRange)
257                 {
258                     result = (byte)(valueAsShort & (byte)CoverBytes.HighFreqMode);
259                     this.Log(LogLevel.Noisy, "High frequencies mode is selected.");
260                 }
261                 else if(outDataRate.Value >= (byte)DataRateModeStartRange.LowPowerDataRateStartRange)
262                 {
263                     result = (byte)(valueAsShort & (byte)CoverBytes.LowPowerMode);
264                     this.Log(LogLevel.Noisy, "Low power mode is selected.");
265                 }
266                 else
267                 {
268                     result = (byte)(valueAsShort & (byte)CoverBytes.NoneExtraModes);
269                     this.Log(LogLevel.Noisy, "High frequencies and low power modes aren't selected.");
270                 }
271             }
272             return result;
273         }
274 
TwoComplementSignConvert(decimal temp)275         private byte TwoComplementSignConvert(decimal temp)
276         {
277             byte tempAsByte = Decimal.ToByte(temp);
278             if(temp < 0)
279             {
280                 byte twoComplementTemp = (byte)(~tempAsByte + 1);
281                 return twoComplementTemp;
282             }
283             return tempAsByte;
284         }
285 
286         private IFlagRegisterField readyPending;
287         private IFlagRegisterField readyEnabled;
288         private IFlagRegisterField highFreqDataRateMode;
289         private IValueRegisterField outDataRate;
290         private IValueRegisterField fullScale;
291         private Registers regAddress;
292 
293         private decimal accelarationX;
294         private decimal accelarationY;
295         private decimal accelarationZ;
296 
297         private const decimal MinAcceleration = -19.5m;
298         private const decimal MaxAcceleration = 19.5m;
299         private const decimal GravitationalConst = 9.806650m; // [m/s^2]
300         private const decimal SensorSensitivity = 0.061m; // [mg/digit]
301 
302         private enum FullScaleSelect : byte
303         {
304             fullScale2g = 0x00,
305             fullScale16g = 0x01,
306             fullScale4g = 0x02,
307             fullScale8g = 0x03,
308         }
309 
310         private enum DataRateModeStartRange : byte
311         {
312             HighFreqDataRateStartRange = 0x05,
313             LowPowerDataRateStartRange = 0x08,
314         }
315 
316         private enum CoverBytes : byte
317         {
318             LowPowerMode = 0xC0,
319             HighFreqMode = 0xF0,
320             NoneExtraModes = 0xFA,
321         }
322 
323         private enum Registers : byte
324         {
325             // Reserved: 0x00 - 0x05
326             // Reserved: 0x0D - 0x0E
327             WhoAmI = 0x0F,
328             // Reserved: 0x10 - 0x1F
329             Control1 = 0x20,
330             Control4 = 0x23,
331             TemperatureOut = 0x26,
332             Status = 0x27,
333             DataOutXLow = 0x28,
334             DataOutXHigh = 0x29,
335             DataOutYLow = 0x2A,
336             DataOutYHigh = 0x2B,
337             DataOutZLow = 0x2C,
338             DataOutZHigh = 0x2D,
339             StatusEventDetection = 0x36,
340         }
341     }
342 }
343