indrekluuk / LiveOV7670

A step-by-step guide to building the circuit for this project:
https://circuitjournal.com/arduino-ov7670-10fps
240 stars 91 forks source link

Arduino MEGA2560 connection #8

Open giancarlodomanico opened 6 years ago

giancarlodomanico commented 6 years ago

Hi, I would like to connect the OV7670 to Arduino MEGA but just looking at your define:

if defined(AVR_ATmega328P) || defined(AVR_ATmega168) || defined(AVR_ATmega1280) || defined(AVR_ATmega2560))

/ B (digital pin 8 to 13) C (analog input pins) D (digital pins 0 to 7) /

ifndef OV7670_VSYNC

define OV7670_VSYNC (PIND & 0b00000100) // PIN 2

endif

ifndef OV7670_PIXEL_CLOCK

define OV7670_PIXEL_CLOCK_PIN 12

define OV7670_PIXEL_CLOCK (PINB & 0b00010000) // PIN 12

endif

ifndef OV7670_READ_PIXEL_BYTE

// (PIN 4..7) | (PIN A0..A3)

define OV7670_READ_PIXEL_BYTE(b) \

                uint8_t pinc=PINC;\
                b=((PIND & 0b11110000) | (pinc & 0b00001111))

endif

// pin 3 to 8Mhz (LiveOV7670Library clock)

ifndef OV7670_INIT_CLOCK_OUT

define OV7670_INIT_CLOCK_OUT \

                pinMode(3, OUTPUT); \
                TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); \
                TCCR2B = _BV(WGM22) | _BV(CS20); \
                OCR2A = 1; \
                OCR2B = 0

endif

endif

I think there is something wrong.

For example A0..A3 are not on PORTC but on PORTF, Pin 4..7 are on PG5, PE3,PH3,PH4. SCL and SDA are not on PC4 and PC5 but on PD0 and PD1.

Because Arduino MEGA has a lot of extra port I think it is possible to use a differente wiring, so that 'standard' IO will stay free for other use. This is the wiring I think could work: SIOC -> PIN 21 - PD0 SIOD -> PIN 20 - PD1 VSYNC -> PIN 46 - PL5 HREF -> PIN47 - PL4 PCLK -> PIN49 - PL2 XCLK - > PIN48 - PL3 D0..D7 -> PIN37..PIN30 -> PC0..PC7 (in this way we do need to OR two different port).

Do you think I'm right ? If yes, what are the changes to your code ? Regards Giancarlo

indrekluuk commented 6 years ago

I have never tried it with MEGA myself, but it seems to me that you are mostly correct.

I added "#if defined" for "AVR_ATmega1280" and "AVR_ATmega2560" so it would compile, but there probably should be completely separate section for MEGA with its own definitions (just like for STM32)

But you can add your own definitions on top of the file before all "#include" statements since all the definitions are checked by "#ifndef" before defining them in the CameraOV7670.h

  1. SIOC -> PIN 21 - PD0 SIOD -> PIN 20 - PD1 I am using the Wire library to configure the camera. So this should work by default and no additional configuration is necessary.

  2. VSYNC -> PIN 46 - PL5 Should have definition:

    define OV7670_VSYNC (PINL & 0b00100000)

  3. HREF -> PIN47 - PL4 My code is not using HREF at all. I count pixels after the VSYNC signal. So there is no definition needed.

  4. PCLK -> PIN49 - PL2 Should have definitions:

    define OV7670_PIXEL_CLOCK_PIN 49

    define OV7670_PIXEL_CLOCK (PINL & 0b00000100)

  5. XCLK - > PIN48 - PL3 This is a bit more complicated. This needs to be a PWM pin. By Google'ing I found that PWM pins for MEGA are pins 2 - 13 and 44 - 46. Additionally you need to be able to configure it to emit 8Mhz pulses. For UNO/Nano I didn't actually do it myself but found the solution by googling.

It is probably something similar to this (as far as I know it uses timer2 to make the pulses): TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); TCCR2B = _BV(WGM22) | _BV(CS20); OCR2A = 1; OCR2B = 0

6. D0..D7 -> PIN37..PIN30 -> PC0..PC7 (in this way we do need to OR two different port). Yes, this is simpler than for Uno

define OV7670_READ_PIXEL_BYTE(b) b = PINC

Note that since it is faster it will mess up the timing for the pixel read cycle in the 10fps version in the "BufferedCameraOV7670_QQVGA_10hz.h" So you should first try to get it working with a slow frame rate (one or two frames per second). If that works then it is easier to get the faster frame rate working.


I have a MEGA so at some point I will try it myself also. But if you get it working please let me know.

giancarlodomanico commented 6 years ago

Hi, I have found this information:

Arduino mega pins and hardware Timers

Pin Timer 46 OC5C 45 OC5B 44 OC5A 13 OC0B //Caution: this one directly effects major timing { i.e delay and millis} 12 OC1B 11 OC1A 10 OC2A 9 OC2B 8 OC4C 7 OC4B 6 OC4A 5 OC3A 4 OC0B //Caution: this one directly effects major timing { i.e delay and millis} 3 OC3C 2 OC3B

another way to look at this is: timer 0 —– pin 4, 13 *there’s alot of misinfo about Pin-13 but after checking 13 timer 1 —– pin 11, 12, 13 it is in fact on Timer 0 “Thx Max K.” timer 2 —– pin 9, 10 timer 3 —– pin 2, 3, 5 timer 4 —– pin 6, 7, 8 timer 5 —– pin 44, 45, 46

Timer 0,2 are 8bits, and timer 1,3,4,5 are 16bits.

So I think I will change the setting in this way

SIOC -> PIN 21 - PD0
SIOD -> PIN 20 - PD1 No define but this will be the wiring

VSYNC -> PIN 48 - PL3 Should have definition:

define OV7670_VSYNC (PINL & 0b00001000)

//HREF -> no connection

XCLK - > PIN47 - PL4 TCCR4A = _BV(COM4A1) | _BV(COM4B1) | _BV(WGM21) | _BV(WGM20); TCCR4B = _BV(WGM22) | _BV(CS20); OCR4A = 1; OCR4B = 0;

I will test it I will tell you Regards Giancarlo

indrekluuk commented 6 years ago

I googled "arduino mega 8mhz output" and the first result gave me this:

pinMode(11, OUTPUT); // select Pin as ch-A TCCR1A = B01000011; // Fast PWM change at OCR1A TCCR1B = B11001; // System clock OCR1A = 0; // 8 MHz

I tried it and checked it with Oscilloscope. This makes pin 11 to pulse at 8Mhz on Arduino Mega

giancarlodomanico commented 6 years ago

Hi, there are some news but not completle good news. I have used this setting

if defined(AVR_ATmega2560) || defined(AVR_ATmega1280)

// VSYNC -> PIN42 - PL7

ifndef OV7670_VSYNC

define OV7670_VSYNC (PINL & 0b10000000)

endif

//PCLK -> PIN43 - PL6

define OV7670_PIXEL_CLOCK_PIN 43

define OV7670_PIXEL_CLOCK (PINL & 0b01000000)

// (PIN 22..29)

ifndef OV7670_READ_PIXEL_BYTE

define OV7670_READ_PIXEL_BYTE(b) b = PINA

endif

// pin 46 to 8Mhz (LiveOV7670Library clock) //XCLK - > PIN46 - PL3

ifndef OV7670_INIT_CLOCK_OUT

define OV7670_INIT_CLOCK_OUT \

                pinMode(45, OUTPUT); \
                TCCR5A = B01000011; \
                TCCR5B = B11001; \
                OCR5A = 0; \
                OCR5B = 0

endif

endif

I have got to move from PIN45 to PIN46. I don't know why but I do not have the Oscilloscope so I cannot understand it I have used wrong setting or it doesn' work at all. Now the camera inizialize and start grabbing. But I do not get a image, or better I'm not yet able to see a image, because I'm using a a TFT LCD shield with 8bit data and I'm using MCUFRIEND_kbv to display the image. I will try to save the image to see if it a grabbing issue or a displaying issue. Regards Giancarlo

indrekluuk commented 6 years ago

I tried and checked with Oscilloscope. It works. This puts PIN 46 to 8Mhz (in the code you pasted here had "pinMode(45, OUTPUT);" I changed it to 46): pinMode(46, OUTPUT); TCCR5A = B01000011; TCCR5B = B11001; OCR5A = 0; OCR5B = 0;

Did camera.init(); return true? If yes then that means that configuration was successful. If camera init is successful then you should try to check if you get VSYNC. If you get VSYNC then the camera should be working.

How do you send pixel data to the screen? Do you use "drawPixel" method from MCUFRIEND_kbv? If yes then it is way too slow. I wrote bytes directly to SPI, but since your screen takes 8 bits in parallel then maybe pushColors is fast enough.

giancarlodomanico commented 6 years ago

Hi. the camera init and also I get the VSYNC. I have also moved from drawPixel to pushColors. Right now I see something in the display and it refresh fast (I do not know the real speed), but is is not a image. I think the issue is the time for write the pixel. If the time for writing a pixel is more that a pixelclock, the next pixel read will not be the next pixel in the image. Is it right ?

indrekluuk commented 6 years ago

Do you read and write pixels one by one or line by line? Can you show the code you have done? It probably is timing issue.

giancarlodomanico commented 6 years ago

LiveOV7670_AS8.zip

this is all the code. I'm sure that it is timing issue, I will try to check how much time I spend to read a full image to see if I'm in time :-)

indrekluuk commented 6 years ago

You can delete the "asm volatile("nop");" lines in "sendPixel". I needed them since I wrote directly to the SPI register and didn't want to waist time on checking SPI status. So I just waited a constant time after sending a byte before sending the next one. But pushColors does all the checking so no extra delay is necessary.

You can make the pixels sending faster by calling pushColors with line buffer instead of doing one pixel at the time. Something like this: tft.pushColors((uint16_t *)camera.getPixelBuffer(), camera.getPixelBufferLength() / 2, first); (divided by 2 since it gives byte count not pixel count)

giancarlodomanico commented 6 years ago

Hi, I have tested how much time I need for a single frame. Using BufferedCameraOV7670_QVGA camera(CameraOV7670::PIXEL_RGB565, BufferedCameraOV7670_QVGA::FPS_1p25_Hz);

I have got to reenable interrupts otherwise millis() doesn't work. Based on my test I need 2380 ms to capture and display all the pixel. But if the FPS is 1.25 every 800 ms I will have a new frame. This means that MCUFRIENDkbv is not fast enought. I have also tested your suggestion of displaying a full line with tft.pushColors, but it doesn't change the time is the same. Does you have any suggestion on how I can try to display a image for now 1 image every 3 or 4 sec will be fine, becuase first of all I would like to be sure that the camera works. Regards

indrekluuk commented 6 years ago

You can decrease FPS by increasing camera pre-scaler. In the BufferedCameraOV7670_QVGA file there is this function

  static uint8_t getPreScalerForFps(FramesPerSecond fps) {
    switch (fps) {
      default:
      case FPS_2p5_Hz:
        return 3;
      case FPS_2_Hz:
        return 4;
      case FPS_1p66_Hz:
        return 5;
      case FPS_1p43_Hz:
        return 6;
      case FPS_1p25_Hz:
        return 7;
    }
  }

If you return a bigger number then the frame rate should be lower.

Another option is to try first with smaller resolution (QQVGA - 160x120). You can display the image in the corner of the screen. This reduces pixel count four times.

giancarlodomanico commented 6 years ago

Hi, I will try your suggestion. I would like also to test a different way but before start coding I would like to ask you if you already has tried it. I have same experience interfacing cameras but of course on PC and using framegrabbers, and I would like to use a similar interface. I would like to attach a interrupt to PCLK so that every time that it change, I will call the function to read the pixel, and display it. Of course read the pixel and display it need to be run in a PCLK cycle. I could also attach a interrupt to HREF so that I know when start a new line and on VSYNC to know when start a new frame. Have you tested the interrupt approch ? what do you think ? Regards

indrekluuk commented 6 years ago

You might be able to use interrupts with slow frame rates.

Here is an oscilloscope image of QQVGA(160x120)@10fps. Yellow is pixel clock from the camera and blue is SPI clock to the screen. It is line by line reading and writing. 10fps There is no way to fit this into one pixel cycle on Arduino at that frame rate. Also to get that speed I read the pixels blindly without even checking pixel clock because this waists too much clock cycles.

I have read that reaction time for Arduino input PIN's interrupt is 4 clock cycles. You probably have to set up the interrupt yourself to get that fast reaction time. It is likely that Arduino framework adds its own code before and after interrupt call if you use "attatchInterrupt" method.

Another thing is that if you use smaller resolution than 640x480 then you are better off reading the line first and then writing to the screen. This camera reduces resolution by adding blank lines. For example with 320x240 resolution you get an image line and then there is pause for the equivalent time of a line. You can see the same thing in the above image. Since it is QQVGA the resolution is reduced four times. As you can see by the yellow line it sends a line and then there is pause for next three lines. It is good idea to use that for sending data to screen. Of course if you use full 640x480 resolution then you have to read pixel and send it immeiately.

giancarlodomanico commented 6 years ago

Hi, I have done some progress but not yet a image from the real world. I have added some function to your code to be able to see the color bar and also to disable HREF on blank line. You can find the code in this attachment and if you want you can include in your code. LiveOV7670Library.zip

This is the image of the color bar that I get p_20180624_002630_1_p

and this is my code LiveOV7670_AS8.zip

I'm not sure that it is the right color bar because if I turn off the color bar I do not get a good image from the real world. Could you take a image of your color bar.

indrekluuk commented 6 years ago

Thanks! I added a method to display color bars to the library in my github repository.

I actually discovered that you can either have color bars with solid colors or transparently overlayed on the image. So I made the configuration function like this:

void CameraOV7670Registers::setShowColorBar(bool transparent) {
  if (transparent) {
    setRegisterBitsOR(REG_COM7, COM7_COLOR_BAR);
  } else {
    setRegisterBitsOR(REG_COM17, COM17_CBAR);
  }
}

Color bars: img_1497

Color bars with transparent overlay: img_1496

giancarlodomanico commented 6 years ago

Hi, I haven't seen the trasparent color bar becuase I'm not able to see a real image, this is why I set both register. Your color bar is : white yellow cyan green magenta violet red blu black Is It right ? My color bar has not the right color. I need to investigate about it. Thank you for your help.

indrekluuk commented 6 years ago

Yes. It looks like your color bars are horizontal. They are vertical on my screen. It seems like height and width are switched.

What happens if you set color bars to be transparent? does it show random garbage under the colors? Maybe you should switch pixel byte order?

zoomx commented 6 years ago

indrekluuk, have you added the new method in the STM32 version too?

indrekluuk commented 6 years ago

zoomx, I have now updated the STM32 project also.

zoomx commented 6 years ago

Wow thanks!