Sunday, 18 January 2015

Connecting to an OV7670 camera with a PIC microcontroller

Before even starting with this little module (that arrived just after Xmas, so we've had to wait a couple of weeks before we got our hands dirty with it!) we probably spent about four hours trawling the 'net looking for ways of not only setting the device up over the I2C-alike interface, but also getting the image data out.
There are plenty of websites which describe sending commands to the camera over I2C (it's actually called SCCB but it is pretty much I2C in all but name) but there's very little about getting at the actual image data. There are a few websites which mention accessing the image data via the RX/TX pins - but our module doesn't have these.

It turn out - after much research - that there are two versions of this camera module. One has a FIFO/eeprom chip onboard which captures the image data and streams it out over standard UART/serial pins. The cheaper modules (so, obviously, the type we have) doesn't support this - you need to grab the image data off the pins yourself.

Again, information on how to do this over the I2C bus is pretty thin on the ground. Then we found this: http://core-electronics.com.au/vga-ov7670-camera-module-i2c-640x480.html

Under "reviews" it says:


The I2C like interface is only for camera control. The camera image data is output over a parallel bus. Now that starts to explain things a little....

Another website gives pretty in-depth details about grabbing the image data: http://embeddedprogrammer.blogspot.co.uk/2012/07/hacking-ov7670-camera-module-sccb-cheat.html

Of particular interest to us on this page is the following timing chart:


Now, we're not too bothered about capturing a full 640x480 pixels at VGA 30fps speed. We're planning on capturing a scaled-down version of the image (usually called QQCIF) which is 88 x 72 pixels. We're planning on driving the PCLK line ourselves from a microcontroller pin (rather than derive it from the main clock source) so we don't have to worry about complex timing protocols or making sure we can buffer or send the image data in chunks between the VSYNC/HREF pulses. If we control the PCLK (pixel clock) we effectively control when the VSYNC/HREF lines go high/low.


If we could just find a way of setting those pesky register values.....

Before we can use the camera module, even to talk to it over the I2C bus, we need to provide it with a clock signal. This is not the "pixel clock" - but a generic clock, to get it up and running (much like you need to give a microcontroller a crystal or other clock signal, as well as any I2C/SPI clock signals). Since we're using a crystal for our PIC, we can also put the CLKOUT pin onto the camera's XCLK (external clock) pin. We're using a 20Mhz crystal for our PIC, which means the camera module is also getting a 20Mhz clock pulse (the camera module datasheet recommends a 10Mhz-48mhz main clock source - some users have reported success with 8Mhz or lower and some people have reported problems if the clock source is too slow).

Because we're expecting the image data to appear on the D0-D7 pins of the camera module (rather than over a serial-type connection, which the FIFO versions support) as well as needing to drive/monitor a number of other pins on the camera, we're using a beefy 40-pin PIC; we could get away with a 28-pin variety, like an 18F2455 but sticking with a 40-pin chip, like an 18F4550 or even the lowly 16F877A means we've plenty of spare I/O and onboard peripherals, should we need them in future.

The only thing we were particular about with this set-up is that the D0-D7 pins of the camera module are mapped to the PORTx0 - PORTx7 pins on the PIC. This allows us to read the image data byte directly off an input PORT, instead of having to build up the byte from individual bits scattered across a number of different input port pins. Because we can't be sure whether the camera module uses an open collector on the D0-D7 pins (effectively driving them low and letting them float otherwise) or actively drives them both high and low to represent the image data, we're using an input port with active pull-up resistors. Similarly, because the SCCB/I2C lines use open collectors to drive the pins low and then float when released, we need to make sure they're connected to Vcc through pull-up resistors (4K7 or 10K should suffice).

Define CONFIG = 0x3f32
Define CLOCK_FREQUENCY = 20
Define STRING_MAX_LENGTH = 30

AllDigital
Symbol led_pin = PORTC.5
Symbol tx = PORTC.6
Symbol rx = PORTC.7

Symbol sda = PORTC.5
Symbol scl = PORTC.4

definitions:
     Dim state As Byte
     Dim i As Byte
     Dim j As Byte
     Dim k As Byte
     Dim data As Byte
   
     Const i2c_read_addr = 0x43
     Const i2c_write_addr = 0x42
   
initialise:

     state = 0
     Hseropen 9600

bootup:
     WaitMs 1000
     High led_pin
     WaitMs 1000
     Low led_pin
     Hserout "READY", CrLf
     WaitMs 1000

loop:

     Select Case state
   
           Case 0 'after bootup
           High led_pin
           For i = 0 To 0xc9
                 I2CRead sda, scl, i2c_read_addr, i, data
                 Hserout "ADDR ["
                 k = i
                 Gosub k_to_hex
                 Hserout "] VALUE "
                 k = data
                 Gosub k_to_hex
                 Hserout CrLf
           Next i
           state = 1
         
         
           Case 1 'we've just read the register values back
           Low led_pin

         
     EndSelect
Goto loop
End

k_to_hex:
     j = ShiftRight(k, 4)
     j = j And 15
     If j = 15 Then Hserout "F"
     If j = 14 Then Hserout "E"
     If j = 13 Then Hserout "D"
     If j = 12 Then Hserout "C"
     If j = 11 Then Hserout "B"
     If j = 10 Then Hserout "A"
     If j < 10 Then Hserout #j
   
     j = k And 15
     If j = 15 Then Hserout "F"
     If j = 14 Then Hserout "E"
     If j = 13 Then Hserout "D"
     If j = 12 Then Hserout "C"
     If j = 11 Then Hserout "B"
     If j = 10 Then Hserout "A"
     If j < 10 Then Hserout #j
Return


A simple I2C to UART/serial routine enabled us to read back the settings from the camera device. Some of the values were not quite the same as the default values in the datasheet, but we demonstrated that we were actually communicating with the device (and not just reading static) by removing the XCLK line and receiving all 0xFF as expected. Our default register values are listed below:

ADDR [00] VALUE FF
ADDR [01] VALUE 80
ADDR [02] VALUE 8C
ADDR [03] VALUE 00
ADDR [04] VALUE 01
ADDR [05] VALUE 77
ADDR [06] VALUE 35
ADDR [07] VALUE 40
ADDR [08] VALUE 78
ADDR [09] VALUE 01
ADDR [0A] VALUE 76
ADDR [0B] VALUE 73
ADDR [0C] VALUE 00
ADDR [0D] VALUE 00
ADDR [0E] VALUE 01
ADDR [0F] VALUE 43
ADDR [10] VALUE 7F
ADDR [11] VALUE 80
ADDR [12] VALUE 00
ADDR [13] VALUE 8F
ADDR [14] VALUE 4A
ADDR [15] VALUE 00
ADDR [16] VALUE 00
ADDR [17] VALUE 11
ADDR [18] VALUE 61
ADDR [19] VALUE 03
ADDR [1A] VALUE 7B
ADDR [1B] VALUE 00
ADDR [1C] VALUE 7F
ADDR [1D] VALUE A2
ADDR [1E] VALUE 01
ADDR [1F] VALUE 00
ADDR [20] VALUE 04
ADDR [21] VALUE 02
ADDR [22] VALUE 01
ADDR [23] VALUE 00
ADDR [24] VALUE 75
ADDR [25] VALUE 63
ADDR [26] VALUE D4
ADDR [27] VALUE 80
ADDR [28] VALUE 80
ADDR [29] VALUE 07
ADDR [2A] VALUE 00
ADDR [2B] VALUE 00
ADDR [2C] VALUE 80
ADDR [2D] VALUE 00
ADDR [2E] VALUE 00
ADDR [2F] VALUE 36
ADDR [30] VALUE 08
ADDR [31] VALUE 30
ADDR [32] VALUE 80
ADDR [33] VALUE 08
ADDR [34] VALUE 11
ADDR [35] VALUE 1A
ADDR [36] VALUE 00
ADDR [37] VALUE 3F
ADDR [38] VALUE 01
ADDR [39] VALUE 00
ADDR [3A] VALUE 0D
ADDR [3B] VALUE 00
ADDR [3C] VALUE 68
ADDR [3D] VALUE 88
ADDR [3E] VALUE 00
ADDR [3F] VALUE 00
ADDR [40] VALUE C0
ADDR [41] VALUE 08
ADDR [42] VALUE 00
ADDR [43] VALUE 14
ADDR [44] VALUE F0
ADDR [45] VALUE 45
ADDR [46] VALUE 61
ADDR [47] VALUE 51
ADDR [48] VALUE 79
ADDR [49] VALUE 00
ADDR [4A] VALUE 00
ADDR [4B] VALUE 00
ADDR [4C] VALUE 00
ADDR [4D] VALUE 04
ADDR [4E] VALUE 00
ADDR [4F] VALUE 40
ADDR [50] VALUE 34
ADDR [51] VALUE 0C
ADDR [52] VALUE 17
ADDR [53] VALUE 29
ADDR [54] VALUE 40
ADDR [55] VALUE 00
ADDR [56] VALUE 40
ADDR [57] VALUE 80
ADDR [58] VALUE 1E
ADDR [59] VALUE 91
ADDR [5A] VALUE 94
ADDR [5B] VALUE AA
ADDR [5C] VALUE 71
ADDR [5D] VALUE 8D
ADDR [5E] VALUE 0F
ADDR [5F] VALUE F0
ADDR [60] VALUE F0
ADDR [61] VALUE F0
ADDR [62] VALUE 00
ADDR [63] VALUE 00
ADDR [64] VALUE 50
ADDR [65] VALUE 30
ADDR [66] VALUE 00
ADDR [67] VALUE 80
ADDR [68] VALUE 80
ADDR [69] VALUE 00
ADDR [6A] VALUE 94
ADDR [6B] VALUE 0A
ADDR [6C] VALUE 02
ADDR [6D] VALUE 55
ADDR [6E] VALUE C0
ADDR [6F] VALUE 9A
ADDR [70] VALUE 3A
ADDR [71] VALUE 35
ADDR [72] VALUE 11
ADDR [73] VALUE 00
ADDR [74] VALUE 00
ADDR [75] VALUE 0F
ADDR [76] VALUE 01
ADDR [77] VALUE 10
ADDR [78] VALUE 00
ADDR [79] VALUE 00
ADDR [7A] VALUE 24
ADDR [7B] VALUE 04
ADDR [7C] VALUE 07
ADDR [7D] VALUE 10
ADDR [7E] VALUE 28
ADDR [7F] VALUE 36
ADDR [80] VALUE 44
ADDR [81] VALUE 52
ADDR [82] VALUE 60
ADDR [83] VALUE 6C
ADDR [84] VALUE 78
ADDR [85] VALUE 8C
ADDR [86] VALUE 9E
ADDR [87] VALUE BB
ADDR [88] VALUE D2
ADDR [89] VALUE E5
ADDR [8A] VALUE 00
ADDR [8B] VALUE 00
ADDR [8C] VALUE 00
ADDR [8D] VALUE 0F
ADDR [8E] VALUE 00
ADDR [8F] VALUE 00
ADDR [90] VALUE 00
ADDR [91] VALUE 00
ADDR [92] VALUE 00
ADDR [93] VALUE 00
ADDR [94] VALUE 50
ADDR [95] VALUE 50
ADDR [96] VALUE 01
ADDR [97] VALUE 01
ADDR [98] VALUE 10
ADDR [99] VALUE 40
ADDR [9A] VALUE 40
ADDR [9B] VALUE 20
ADDR [9C] VALUE 00
ADDR [9D] VALUE 99
ADDR [9E] VALUE 7F
ADDR [9F] VALUE C0
ADDR [A0] VALUE 90
ADDR [A1] VALUE 03
ADDR [A2] VALUE 02
ADDR [A3] VALUE 03
ADDR [A4] VALUE 00
ADDR [A5] VALUE 0F
ADDR [A6] VALUE F0
ADDR [A7] VALUE C1
ADDR [A8] VALUE F0
ADDR [A9] VALUE C1
ADDR [AA] VALUE 14
ADDR [AB] VALUE 0F
ADDR [AC] VALUE 00
ADDR [AD] VALUE 80
ADDR [AE] VALUE 80
ADDR [AF] VALUE 80
ADDR [B0] VALUE 00
ADDR [B1] VALUE 00
ADDR [B2] VALUE 00
ADDR [B3] VALUE 80
ADDR [B4] VALUE 00
ADDR [B5] VALUE 04
ADDR [B6] VALUE 00
ADDR [B7] VALUE 66
ADDR [B8] VALUE 00
ADDR [B9] VALUE 06
ADDR [BA] VALUE 00
ADDR [BB] VALUE 00
ADDR [BC] VALUE 00
ADDR [BD] VALUE 00
ADDR [BE] VALUE 00
ADDR [BF] VALUE 00
ADDR [C0] VALUE 00
ADDR [C1] VALUE 00
ADDR [C2] VALUE 00
ADDR [C3] VALUE 00
ADDR [C4] VALUE 00
ADDR [C5] VALUE 00
ADDR [C6] VALUE FF
ADDR [C7] VALUE FF
ADDR [C8] VALUE 06
ADDR [C9] VALUE C0

There are enough recognisable "default" values consistently being read back to convince us that we're talking to the camera device (some register values vary a little from read-to-read, but these are values like "white balance" which, if the module is working under "auto-balance" mode, you'd expect to be changing over time anyway). But there are also enough that vary quite a bit from the datasheet to make us wonder whether we're working with a newer/older model, or whether the data is getting messed up somewhere along the way. So far, we're not sure - it could be either; the next job is to set some register values and see if they are retained (strangely, to set a register value, you write to SPI address 0x42 but for reading, you read from address 0x43 - there's probably a reason for this to do with full-duplex communication or something, but it does seem a little strange!).

There's a quick-and-simple test for these modules that a lot of people mention - cover the lens with the lens cap and read back the image data. A fully black image should be a sequence of 0x00 and 0x80 bytes, apparently.

Let's get this thing wired up and find out.....