OV7670 module with the Due

Anti Hijack thread for specifically the use of an OV7670 (non FIFO) module on an arduino Due.
Continued from post #562
OV7670 mega and nano thread

EvIl_DeViL:
I updated my code with some of the suggestion stoney reported and indeed I got some improvements.

  1. vsync edge detection is not influent in my case. I got the same image so I think it depends on how the code is written so for now I let it be as it was but I might consider changing this behavior accordingly to the code as it comes out. Thanks for pointing out and clarifying this issue.

  2. I implemented HREF functionality but I couldn't get it working with interrupts; the same goes for PCLK so I needed to poll with a while loop (DAMN! :D)

  3. I tryed to wait either for PCLK low and high before reading the pixel and I got these two versions; the first waiting for low as MrArduino did and the second waiting for high accordingly to my interpretation of the scheme I found on the DS which I post herein. (I'd go for the second version)

  4. @stoney: why you think Hstart/Hend and Vstart/Vend are relevant? It's just to check if the resolution is ok, isn't it? Would you mind giving a shot to my updated code? maybe we can figure something else out and close this!

I really hope I'm only missing some color settings and someone can point out some error(s) or a stupid register setting because it looks to me I'm quite there but I can't grasp the last handhold.

You all can find the updated code on the usual repo on github!

  1. discussed below..

  2. me either .. yet

  3. the 16 bit data is clocked out as 2 bytes, I think with your two schemes you are simply reversing the order of the 2 bytes.

  4. I realised after last post that you are leaving most of the registers in the default state and also using VGA mode, I am using QVGA, so setting pretty much all of the timing registers as I am using an LCD with that resolution to test with.
    This makes a huge difference as the module is VGA resolution and the windowing becomes important to me but not for you so much. VGA is much much easier and almost nobody is using QVGA. The register settings I have found online in assorted codes are not ideal and still leave you with massive lag due to clocking out more pixels than are relevant each line.

the HSTART and HSTOP basically set the portion of the image output for which the HREF line is high. The registers can also be changed so that the pixel clock is only output when HREF is active.
See COM10[5] and COM12[7] with those two set you should only get a pixel clock when there is active data.
This I will use later but for now I need to get the window and clock registers correct. You could use them with your code as is though if they are not the register defaults ..

The clocking out of the image data across the array is controlled by all the various dividers and dividers of dividers and also the actual clock speed used ( I think ) all i know is that simply slowing the clock divider changes the image, so I think internally the clock divider is used for output only and the XCLK is used undivided on the image array. just be aware that changing the clock divider (register 0x11) needs to be done with some thought..

Hi @Stoney,

Although I don't have a Due and I am working with the FIFO version, I have been following your progress in the other thread and I followed you here to learn and share relevant knowledge. :slight_smile:

Regarding the Windowing, I'm sure you have read the Implementation Guide's take on the default values and have compared them to the linux driver's "default" values. Have you found any set of values that make sensical changes to the output image? Below is the extent of my research on the windowing registers:

Datasheet Defaults:

HSTART 11-bits.  Last 3 bits from HREF[0:2]
BIN 00010001 000  (space added for clarity)
DEC 135

HSTOP
01100001 000
776

VSTRT 10-bits.  Last 2 bits from VREF[0:1]
00000011 11
15

VSTOP
01111011 00
492


Linux driver defaults (supposedly from OV)

HSTART 11-bits.  Last 3 bits from HREF[0:2]
BIN 00010011 110  (space added for clarity)
DEC 158

HSTOP
00000001 110
14

VSTRT 10-bits.  Last 2 bits from VREF[0:1]
00000010 10
10

VSTOP
01111010 10
490

If we are to believe the decimal representation of the start/stop values determines the window x,y offset and size:
The datasheet defaults yield a window size of: 641x477
The linux driver defaults yield a window size of: -144x480

When using the predefined CIF format, using the datasheet window values or the linux driver values results in the same image. I was suspicious so I made new values for HSTART/STOP, VSTART/STOP that started at 0 and ended at 640 and 480 respectively. This also resulted in the same image/field-of-view. Given these results, I have decided not to try to change the windowing registers and leave it as another "mystery value" that just works. Besides, I don't see myself needing to crop the sensor's field-of-view in any of my applications regardless of image resolution.

It's really confounding when the deeper I delve into the datasheet registers, the less I feel I can trust them. I'm probably missing something obvious that will explain this behavior, but I lose more and more confidence in the accuracy of the official datasheets and guides when the camera doesn't behave according to the documentation. The amount of typographical and proofreading mistakes left in the documentation also doesn't inspire any confidence.

This leads me to my next observation/question. As previously stated, I have the FIFO version, but the OV7670 register settings for image resolution and color should be common between our projects. When first starting with the camera module, I started with qqvga resolution. I used the dividers to downsample the vga image into the desired size.

The next step for me was qcif resolution. I quickly realized that to manually get qcif from vga using the dividers and scalers was not trivial. Lucky for me it was not necessary as qcif is a predefined setting in the OV7670. Setting COM7 to qcif mode seemed to take care of all the downsampling and scaling automatically. This was also true for the cif setting.

When I got to the next predefined setting of qvga (the current issue I am dealing with), I could not get a recognizable image. The sync and timing looked way off. It seems like the predefined qvga setting is broken, or behaves differently than the qcif and cif settings.

By any chance, have you tried the predefined resolution settings? I'm looking for someone who can confirm my observations. (I really hope I'm just wrong and I missed something.) Since you are trying for qvga, cif is very close in size. If you decide to try it, I found that although the frame is the expected size (3522882 bytes) one column of image data is missing. This results in an image size of 351x288 pixels.

Below are my settings for cif mode. I didn't include the default regs because they are the same as those found in Mr.Arduino's code and the same found here: linux driver

const struct regval_list cif_yuv_ov7670[] PROGMEM = {
      {REG_COM17, COM17_CBAR}, // enable DSP color bars

      // 12MHz xtal * 8x PLL / 2* (CLKRC +1) = 24MHz
      //{REG_CLKRC, /*CLK_EXT*/ 0x01},  // clock divider default:  0x80
      //{DBLV, DBLV_X8}, // PLL multiplier
      
      	{REG_HREF,0xF6},	// was B6  
	{0x17,0x13},		// HSTART
	{0x18,0x01},		// HSTOP
	{0x19,0x02},		// VSTART
	{0x1a,0x7a},		// VSTOP
	{REG_VREF,0x0a},	// VREF  
      
      //{REG_COM10, COM10_PCLK_HB},  
      {REG_MVFP, 0x01},  // MVFP_FLIP MVFP_MIRROR, was 0x07.  bit 0-1,3 reserved.  default: 0x01
      
	{REG_COM7, COM7_FMT_CIF},	/* Selects CIF mode */
	{REG_RGB444, 0},	/* No RGB444 please */
	{REG_COM1, 0},
	{REG_COM15, COM15_R00FF},
	{REG_COM9, 0x6A},	/* 128x gain ceiling; 0x8 is reserved bit */
	{0x4f, 0x80},		/* "matrix coefficient 1" */
	{0x50, 0x80},		/* "matrix coefficient 2" */
	{0x51, 0},		/* vb */
	{0x52, 0x22},		/* "matrix coefficient 4" */
	{0x53, 0x5e},		/* "matrix coefficient 5" */
	{0x54, 0x80},		/* "matrix coefficient 6" */
	{REG_COM13,/*COM13_GAMMA|*/COM13_UVSAT},
	{0xff, 0xff},		/* END MARKER */  

};

I have attached the binary and png files for a captured cif YUV frame. Don't mind the strange colors as I am only sampling the first 5 MSB of the 8 data pins. Later I only plan to use 4 bits-per-pixel of the luminance values, so color is not that important to me. This will also halve the amount of transmitted image data.

test.RAW.png

test.zip (1.17 KB)

@stoney: so what if I'm just missing color settings or byte order? I got a slightly different result using MrArduino's converter

edit: problem is mostly due to the fact that I'm using default color scheme values which is YUV but I'm reading as RGB. Anyway I got wrong colors anyway with half height colorbars; at least I'm getting the right amount of bars now... I'll post an updated screenshot soon

grr, clicked wrong tab .. closed long post reply..
yeh I just felt like I was intruding a bit in the other thread, it has its own repo etc.. we can natter away here.

I am where you are with the datasheet, take it with a grain of salt.
page 8 of v1.0 mentions COM7[3] is used for predefined register useage. yet even up to v1.4 the same bit is 'reserved'.
also on page 8 is a mention that certain modes are only available in certain output modes. eg .. raw bayer limits output to VGA .. I am not sure how truthful yet, I know changing between raw bayer and processed bayer changes the output timing.

setting it to 1 on mine generates a washed out image with the same resolution so it appears to be an analog setting and a typo.

i have the windowing working now, at least I can set the registers, I am thinking it is less useful than I thought and I am concentrating more on the clock dividers at the moment, trying to work out exactly which bits do what I expect and which do not.
Although ultimately I want to play with simple image processing on board. where I think the window registers may come in handy is by using them to control the HREF pulse and indicate to software where I want to be concentrating on.
My defaults come in at ..

H start:136
H end:776
V start:12
V end:492

I use this to set them..

#define OV_HSTART 0x17
#define OV_HEND 0x18
#define OV_HREF 0x32
#define OV_VSTART 0x19
#define OV_VEND 0x1a
#define OV_VREF  0x03
#define OV_TSLB 0x3a

  wrReg(OV_HSTART, H_START >>3);
  wrReg(OV_HEND, H_END >>3);
  tmp = rdReg(OV_HREF);
  tmp &= 0xc0;
  wrReg(OV_HREF, tmp | (H_END & 0x07)<<3 | (H_START & 0x03));

  wrReg(OV_VSTART, V_START>>2);
  wrReg(OV_VEND,V_END>>2);
  tmp = rdReg(OV_VREF);
  tmp &= 0xf0;
  wrReg(OV_VREF,tmp | (V_END & 0x03)<<2 | (V_START & 0x03));

// and to display

  Serial.print("H start:");
  Serial.println((rdReg(OV_HSTART) << 3) | (rdReg(OV_HREF) & 0x07));
  Serial.print("H end:");
  Serial.println((rdReg(OV_HEND) << 3) | ((rdReg(OV_HREF) & 0x38) >> 3));
  Serial.print("V start:");
  Serial.println((rdReg(OV_VSTART) << 2) | (rdReg(OV_VREF) & 0x03));
  Serial.print("V end:");
  Serial.println((rdReg(OV_VEND) << 2) | ((rdReg(OV_VREF) & 0x0c) >> 2));
  Serial.print("dummy pixels: 0x");
  Serial.println(rdReg(0x2a) << 8 | rdReg(0x2b), HEX);

I am not conversant with C++ so sticking with C currently, I have taken some of the more useful bits and turned them into macros for experimenting. will share later when i get the bugs out.
at the moment I have a long line then a short line again..

EvIl_DeViL:
@stoney: so what if I'm just missing color settings or byte order? I got a slightly different result using MrArduino's converter

edit: problem is mostly due to the fact that I'm using default color scheme values which is YUV but I'm reading as RGB. Anyway I got wrong colors anyway with half height colorbars; at least I'm getting the right amount of bars now... I'll post an updated screenshot soon

I was saying that was why they looked different is all. I did not notice a size difference ?
I am getting perfect colour bars at 320x240 now displayed on my TFT with either the colour bar settings using
SCALING_XSC and SCALING_YSC so it can do fade to grey as well etc or the processed one at COM17

I GOT IT WORKING ON DUE! REPO ALREADY UPDATED! I'll post optimization soon but at least you can now study even on this board! there were issues related to registry configurations. I imported MrArduino's list and now it's working :wink:

EvIl_DeViL:
I GOT IT WORKING ON DUE! REPO ALREADY UPDATED! I'll post optimization soon but at least you can now study even on this board! there were issues related to registry configurations. I imported MrArduino's list and now it's working :wink:

Good job on getting it to work! :slight_smile:

I'm still trying to study the register values and I was wondering if you could help me out.

I'm studying this part of the default regs:

static const struct regval_list ov7670_default_regs[] = {//from the linux driver
	{ REG_COM7, COM7_RESET },
	{ REG_TSLB, 0x04 },	/* OV */

I have been going over my register values with a fine-tooth comb because of some nonsensical behavior from the camera. What I found after writing my regs and reading them back was REG_TSLB was not being set to the expected value of 0x04. Instead it was the camera-default value of 0x0D. I suspected the COM7_RESET was taking too long and REG_TSLB write timing was overlapping with the reset so I removed the reset to a separate routine with sufficient delay. After doing this and reading back the register values, REG_TSLB was the expected value of 0x04.

If possible, can you tell me what value you have in the REG_TSLB register after writing your regs?

This little code is the result of a great deal of research...
Bear in mind....the 7 pixels to the left and the right may look like good pixels...they are not. The true purpose of these pixles should be obvious when downsampled and averaged at QQQVGA using the averaging avg feature. these pixles protect the pixle data when zoomed in at 8x. But if you do not use those features then you can use those pixles. THEY ARE NOT FOR ADJUSTING...off center will distort the edge colors when at max zoom.
Some of the variables are global control variables in my program. Sorry I will not reveal more of the code..or edit it further....or reveal my purpose.

This function is used to zoom when using 160x120 QQVGA
if: screenZoomCenterX = 320, screenZoomCenterY = 240
then: then screen will be centered when zooming,

void ov7670QQVGAzoom(byte level)
{
  int32_t hstart, hstop;
  byte hEdgeOffset = 3;
  int32_t ystart, ystop;
  switch ( level )
  {
    case 0b00:
      { //max depth
        WriteOV7670(0x0C, 0x00);//COM3 - Enable Scaling
        WriteOV7670(0x3E, 0b00011000 | level); //COM14
        hstart = 240;
        hstop = 400;
        hEdgeOffset = 3;
        ystart = 180;
        ystop = 300;
        WriteOV7670(0x72, level | (level << 4));
        WriteOV7670(0x73, 0b11110000 | level);
        WriteOV7670(0xA2, 0x02);//scaling Pixel Clock Delay
        WriteOV7670(0x11, 0b0);
        break;
      }
    case 0b01:
      {
        WriteOV7670(0x0C, 0x04);//COM3 - Enable Scaling
        WriteOV7670(0x3E, 0b00011000 | level); //COM14
        hstart = 160;
        hstop = 480;
        hEdgeOffset = 2;
        ystart = 120;
        ystop = 360;
        WriteOV7670(0x72, level | (level << 4));
        WriteOV7670(0x73, 0b11110000 | level);
        WriteOV7670(0xA2, 0x02);//scaling Pixel Clock Delay
        WriteOV7670(0x11, 0b0);
        break;
      }
    case 0b10:
      { //normal view
        WriteOV7670(0x0C, 0b00000100);//COM3 - Enable Scaling
        WriteOV7670(0x3E, 0b00011000 | level); //COM14
        hstart = 0;
        hstop = 640;
        hEdgeOffset = 2;
        ystart = 0;
        ystop = 480;
        WriteOV7670(0x72, level | (level << 4));
        WriteOV7670(0x73, 0b11110000 | level);
        WriteOV7670(0xA2, 0x02);//scaling Pixel Clock Delay
        WriteOV7670(0x11, 0b0);
        break;
      }
  }
  hstart += (screenZoomCenterX-320);
  hstop += (screenZoomCenterX-320);
  ystart += (screenZoomCenterY-240);
  ystop += (screenZoomCenterY-240);
  if(hstart < 0)
  {
    hstop += (0-hstart);
    hstart = 0;
  }
  if(hstop > 640)
  {
    hstart -= (hstop-640);
    hstop = 640;
  }
  if(ystart < 0)
  {
    ystop += (0-ystart);
    ystart = 0;
  }
  if(ystop > 480)
  {
    ystart -= (ystop-480);
    ystop = 480;
  }
  hstart += (imageColors==0?174:188);
  if(hstart >= 784){hstart-=784;}
  hstop += (imageColors==0?174:188);
  if(hstop >= 784){hstop-=784;}
  ystart += 10;
  ystop += 10;
  Serial.print( hstart );
  Serial.print( ", " );
  Serial.print( hstop );
  Serial.print( ", " );
  Serial.print( ystart );
  Serial.print( ", " );
  Serial.println( ystop );
  WriteOV7670(0x17, hstart >> 3); //hstop
  WriteOV7670(0x18, hstop >> 3); //HSTOP
  WriteOV7670(0x32, (hEdgeOffset << 6) | ((0b111 & hstop) << 3) | (0b111 & hstop)); //HREF
  WriteOV7670(0x19, ystart >> 2); //VSTART
  WriteOV7670(0x1a, ystop >> 2); //VSTOP
  WriteOV7670(0x03, (0b00001111 & (((0b11 & ystop) << 2) | (0b11 & ystart)))); //VREF
}