1 // 2 // Copyright (c) 2010-2021 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.IO; 10 using Antmicro.Renode.Core; 11 using Antmicro.Renode.Core.Structure.Registers; 12 using Antmicro.Renode.Exceptions; 13 using Antmicro.Renode.Logging; 14 using Antmicro.Renode.Peripherals.Sensor; 15 using Antmicro.Renode.Peripherals.I2C; 16 using Antmicro.Renode.Peripherals.SPI; 17 using Antmicro.Renode.Utilities; 18 using Antmicro.Renode.HostInterfaces.Camera; 19 20 namespace Antmicro.Renode.Peripherals.Sensors 21 { 22 public class ArduCAMMini2MPPlus: ISPIPeripheral, II2CPeripheral, IGPIOReceiver, IProvidesRegisterCollection<ByteRegisterCollection>, ISensor 23 { ArduCAMMini2MPPlus()24 public ArduCAMMini2MPPlus() 25 { 26 sensor = new OV2640(this); 27 28 RegistersCollection = new ByteRegisterCollection(this); 29 DefineRegisters(); 30 } 31 32 #region SPI_Interface 33 Transmit(byte data)34 public byte Transmit(byte data) 35 { 36 byte result = 0; 37 38 switch(state) 39 { 40 case State.Idle: 41 { 42 selectedRegister = (Register)BitHelper.GetValue(data, 0, 7); 43 var isWrite = BitHelper.IsBitSet(data, 7); 44 45 this.NoisyLog("Decoded register {0} (0x{0:X}) and isWrite bit as {1}", selectedRegister, isWrite); 46 state = isWrite 47 ? State.Writing 48 : State.Reading; 49 50 break; 51 } 52 53 case State.Reading: 54 this.NoisyLog("Reading register {0} (0x{0:X})", selectedRegister); 55 result = RegistersCollection.Read((long)selectedRegister); 56 break; 57 58 case State.Writing: 59 this.NoisyLog("Writing 0x{0:X} to register {1} (0x{1:X})", data, selectedRegister); 60 RegistersCollection.Write((long)selectedRegister, data); 61 break; 62 63 default: 64 this.Log(LogLevel.Error, "Received byte in an unexpected state: {0}", state); 65 break; 66 } 67 68 this.NoisyLog("Received byte 0x{0:X}, returning 0x{1:X}", data, result); 69 return result; 70 } 71 ISPIPeripheral.FinishTransmission()72 void ISPIPeripheral.FinishTransmission() 73 { 74 this.NoisyLog("Finishing transmission, going idle"); 75 state = State.Idle; 76 } 77 OnGPIO(int number, bool value)78 public void OnGPIO(int number, bool value) 79 { 80 if(number != 0) 81 { 82 this.Log(LogLevel.Warning, "This model supports only CS on pin 0, but got signal on pin {0}", number); 83 return; 84 } 85 86 // value is the negated CS 87 if(chipSelected && value) 88 { 89 ((ISPIPeripheral)this).FinishTransmission(); 90 } 91 chipSelected = !value; 92 } 93 94 #endregion 95 96 #region I2C_Interface 97 Write(byte[] data)98 public void Write(byte[] data) 99 { 100 sensor.Write(data); 101 } 102 Read(int count)103 public byte[] Read(int count) 104 { 105 return sensor.Read(count); 106 } 107 II2CPeripheral.FinishTransmission()108 void II2CPeripheral.FinishTransmission() 109 { 110 sensor.FinishTransmission(); 111 } 112 113 #endregion 114 Reset()115 public void Reset() 116 { 117 RegistersCollection.Reset(); 118 119 sensor.Reset(); 120 chipSelected = false; 121 selectedRegister = Register.Test; 122 state = State.Idle; 123 } 124 125 public ByteRegisterCollection RegistersCollection { get; } 126 127 public string ImageSource 128 { 129 get 130 { 131 return imageSource; 132 } 133 134 set 135 { 136 if(value == null) 137 { 138 preloadedImageData = null; 139 imageSource = null; 140 return; 141 } 142 143 try 144 { 145 preloadedImageData = File.ReadAllBytes(value); 146 imageSource = value; 147 148 this.NoisyLog("Loaded {0} bytes of image data to buffer", preloadedImageData.Length); 149 } 150 catch(Exception e) 151 { 152 throw new RecoverableException($"Could not load image {value}: {(e.Message)}"); 153 } 154 } 155 } 156 AttachToExternalCamera(HostCamera camera)157 public void AttachToExternalCamera(HostCamera camera) 158 { 159 #if PLATFORM_LINUX 160 externalCamera = camera; 161 #else 162 throw new RecoverableException("The external camera integration is currently available on Linux only!"); 163 #endif 164 } 165 DetachFromExternalCamera()166 public void DetachFromExternalCamera() 167 { 168 #if PLATFORM_LINUX 169 externalCamera = null; 170 #else 171 throw new RecoverableException("The external camera integration is currently available on Linux only!"); 172 #endif 173 } 174 DefineRegisters()175 private void DefineRegisters() 176 { 177 Register.Test.Define(this) 178 .WithValueField(0, 8, name: "Test field") 179 ; 180 181 Register.FifoControl.Define(this) 182 .WithFlag(0, FieldMode.Write, name: "Clear FIFO write done flag", writeCallback: (_, val) => 183 { 184 if(val) 185 { 186 fifoDone.Value = false; 187 } 188 }) 189 .WithFlag(1, FieldMode.Write, name: "Start capture", writeCallback: (_, val) => 190 { 191 if(!val) 192 { 193 return; 194 } 195 196 this.NoisyLog("Capturing frame"); 197 #if PLATFORM_LINUX 198 var ec = externalCamera; 199 if(ec != null) 200 { 201 ec.SetImageSize((int)sensor.OutputWidth, (int)sensor.OutputHeight); 202 imageData = ec.GrabFrame(); 203 } 204 else if(preloadedImageData != null) 205 #else 206 if(preloadedImageData != null) 207 #endif 208 { 209 imageData = preloadedImageData; 210 } 211 else 212 { 213 this.Log(LogLevel.Warning, "No image source set. Generating an empty frame"); 214 imageData = new byte[0]; 215 } 216 217 imageDataPointer = 0; 218 fifoDone.Value = true; 219 }) 220 .WithReservedBits(2, 2) 221 .WithTaggedFlag("Reset FIFO read pointer", 4) 222 .WithTaggedFlag("Reset FIFO write pointer", 5) 223 .WithReservedBits(6, 1) 224 ; 225 226 Register.BurstRead.Define(this) 227 .WithValueField(0, 8, FieldMode.Read, valueProviderCallback: _ => 228 { 229 if(imageDataPointer >= imageData.Length) 230 { 231 this.Log(LogLevel.Warning, "Tried to read outside the image buffer"); 232 return 0; 233 } 234 235 return imageData[imageDataPointer++]; 236 }) 237 ; 238 239 Register.TriggerSource.Define(this) 240 .WithTaggedFlag("Camera vsync pin status", 0) 241 .WithReservedBits(1, 2) 242 .WithFlag(3, out fifoDone, name: "Camera write FIFO done") 243 .WithReservedBits(4, 3) 244 ; 245 246 Register.CameraWriteFifoSize0.Define(this) 247 .WithValueField(0, 8, valueProviderCallback: _ => (byte)(imageData?.Length ?? 0)) 248 ; 249 250 Register.CameraWriteFifoSize1.Define(this) 251 .WithValueField(0, 8, valueProviderCallback: _ => (byte)((imageData?.Length ?? 0) >> 8)) 252 ; 253 254 Register.CameraWriteFifoSize2.Define(this) 255 .WithValueField(0, 8, valueProviderCallback: _ => (byte)((imageData?.Length ?? 0) >> 16)) 256 ; 257 } 258 259 private bool chipSelected; 260 private Register selectedRegister; 261 private State state; 262 263 private int imageDataPointer; 264 private byte[] imageData; 265 private byte[] preloadedImageData; 266 private string imageSource; 267 268 private IFlagRegisterField fifoDone; 269 270 private readonly OV2640 sensor; 271 272 #if PLATFORM_LINUX 273 private HostCamera externalCamera; 274 #endif 275 276 private enum State 277 { 278 Idle, 279 Reading, 280 Writing 281 } 282 283 private enum Register 284 { 285 Test = 0x00, 286 CaptureControl = 0x01, 287 Reserved02 = 0x02, 288 SensorInterfaceTiming = 0x03, 289 FifoControl = 0x04, 290 GPIODirection = 0x05, 291 GPIOWrite = 0x06, 292 // this gap is not covered by the documentation 293 Reserved3B = 0x3B, 294 BurstRead = 0x3C, 295 SingleRead = 0x3D, 296 Reserved3E = 0x3E, 297 Reserved3F = 0x3F, 298 ArduChipVersion = 0x40, 299 TriggerSource = 0x41, 300 CameraWriteFifoSize0 = 0x42, 301 CameraWriteFifoSize1 = 0x43, 302 CameraWriteFifoSize2 = 0x44, 303 GPIOReadRegister = 0x45 304 } 305 } 306 } 307