├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── githubci.yml ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── cameratest │ └── cameratest.ino └── selfie │ └── selfie.ino ├── library.properties └── src ├── Adafruit_OV7670.cpp ├── Adafruit_OV7670.h ├── SPIBrute.cpp ├── SPIBrute.h ├── arch ├── samd51.c ├── samd51.h └── samd51_arduino.cpp ├── image_ops.c ├── image_ops.h ├── ov7670.c └── ov7670.h /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for opening an issue on an Adafruit Arduino library repository. To 2 | improve the speed of resolution please review the following guidelines and 3 | common troubleshooting steps below before creating the issue: 4 | 5 | - **Do not use GitHub issues for troubleshooting projects and issues.** Instead use 6 | the forums at http://forums.adafruit.com to ask questions and troubleshoot why 7 | something isn't working as expected. In many cases the problem is a common issue 8 | that you will more quickly receive help from the forum community. GitHub issues 9 | are meant for known defects in the code. If you don't know if there is a defect 10 | in the code then start with troubleshooting on the forum first. 11 | 12 | - **If following a tutorial or guide be sure you didn't miss a step.** Carefully 13 | check all of the steps and commands to run have been followed. Consult the 14 | forum if you're unsure or have questions about steps in a guide/tutorial. 15 | 16 | - **For Arduino projects check these very common issues to ensure they don't apply**: 17 | 18 | - For uploading sketches or communicating with the board make sure you're using 19 | a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes 20 | very hard to tell the difference between a data and charge cable! Try using the 21 | cable with other devices or swapping to another cable to confirm it is not 22 | the problem. 23 | 24 | - **Be sure you are supplying adequate power to the board.** Check the specs of 25 | your board and plug in an external power supply. In many cases just 26 | plugging a board into your computer is not enough to power it and other 27 | peripherals. 28 | 29 | - **Double check all soldering joints and connections.** Flakey connections 30 | cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints. 31 | 32 | - **Ensure you are using an official Arduino or Adafruit board.** We can't 33 | guarantee a clone board will have the same functionality and work as expected 34 | with this code and don't support them. 35 | 36 | If you're sure this issue is a defect in the code and checked the steps above 37 | please fill in the following fields to provide enough troubleshooting information. 38 | You may delete the guideline and text above to just leave the following details: 39 | 40 | - Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE** 41 | 42 | - Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO 43 | VERSION HERE** 44 | 45 | - List the steps to reproduce the problem below (if possible attach a sketch or 46 | copy the sketch code in too): **LIST REPRO STEPS BELOW** 47 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for creating a pull request to contribute to Adafruit's GitHub code! 2 | Before you open the request please review the following guidelines and tips to 3 | help it be more easily integrated: 4 | 5 | - **Describe the scope of your change--i.e. what the change does and what parts 6 | of the code were modified.** This will help us understand any risks of integrating 7 | the code. 8 | 9 | - **Describe any known limitations with your change.** For example if the change 10 | doesn't apply to a supported platform of the library please mention it. 11 | 12 | - **Please run any tests or examples that can exercise your modified code.** We 13 | strive to not break users of the code and running tests/examples helps with this 14 | process. 15 | 16 | Thank you again for contributing! We will try to test and integrate the change 17 | as soon as we can, but be aware we have many GitHub repositories to manage and 18 | can't immediately respond to every request. There is no need to bump or check in 19 | on a pull request (it will clutter the discussion of the request). 20 | 21 | Also don't be worried if the request is closed or not integrated--sometimes the 22 | priorities of Adafruit's GitHub code (education, ease of use) might not match the 23 | priorities of the pull request. Don't fret, the open source community thrives on 24 | forks and GitHub makes it easy to keep your changes in a forked repo. 25 | 26 | After reviewing the guidelines above you can delete this text from the pull request. 27 | -------------------------------------------------------------------------------- /.github/workflows/githubci.yml: -------------------------------------------------------------------------------- 1 | name: Arduino Library CI 2 | 3 | on: [pull_request, push, repository_dispatch] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/setup-python@v4 11 | with: 12 | python-version: '3.x' 13 | - uses: actions/checkout@v3 14 | - uses: actions/checkout@v3 15 | with: 16 | repository: adafruit/ci-arduino 17 | path: ci 18 | 19 | - name: pre-install 20 | run: bash ci/actions_install.sh 21 | 22 | - name: test platforms 23 | run: python3 ci/build_platform.py grand_central 24 | 25 | - name: clang 26 | run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r . 27 | 28 | - name: doxygen 29 | env: 30 | GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }} 31 | PRETTYNAME : "Adafruit OV7670" 32 | run: bash ci/doxy_gen_and_deploy.sh 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Our handy .gitignore for automation ease 2 | Doxyfile* 3 | doxygen_sqlite3.db 4 | html 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Adafruit Industries 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adafruit OV7670 Library [![Build Status](https://github.com/adafruit/Adafruit_OV7670/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_OV7670/actions)[![Documentation](https://github.com/adafruit/ci-arduino/blob/master/assets/doxygen_badge.svg)](http://adafruit.github.io/Adafruit_OV7670/html/index.html) 2 | 3 | Arduino library for OV7670 cameras, built over a C foundation common to 4 | both Arduino and (later) CircuitPython. 5 | 6 | PLEASE NOTE: I'm setting this repository public since some folks had a 7 | specific need and wanted early access. It is ABSOLUTELY 100% GUARANTEED 8 | that there will be BREAKING CHANGES before this library is firmed up and 9 | considered "official," so don't get too comfortable with things being as 10 | they are. 11 | 12 | These notes are primarily to assist with porting to new devices and in 13 | creating a CircuitPython library from the lower-level code. For using the 14 | library, see the included Arduino example(s). 15 | 16 | ## Terminology 17 | 18 | To understand, discuss and adapt the code, a basic vernacular should be 19 | established. The terms chosen here are probably not optimal, but applied 20 | consistently will suffice in allowing different elements of the code to 21 | be discussed relative to one another. These are the terms used in the 22 | comments and in naming of structures and types. 23 | 24 | An "architecture" refers to a related family of microcontroller devices 25 | where certain peripherals are implemented in-common. For example, SAMD51 26 | is an architecture distinct from SAMD21 (though both ARM-based), because 27 | these have a different range of peripherals, and even when there's 28 | functional overlap, the registers for configuring these peripherals are 29 | often a bit different. (Note that SAMD51 is initially the only supported 30 | architecture, and the mention of SAMD21 is purely hypothetical and might 31 | never happen. Others might be added in the future -- STM32, i.MX, etc. -- 32 | the code is written with that in mind even though there's currently just 33 | one architecture.) 34 | 35 | "Platform" as used in this code refers to a runtime environment, such 36 | as Arduino or CircuitPython. Could just as easily been "environment" or 37 | "language" or something else, but we'll use "platform," it's good enough. 38 | 39 | "Host" refers to a specific combination of architecture and platform (along 40 | with a list of pins to which an OV7670 camera is connected). Picture a table 41 | with architectures along one axis and platforms along the other. "SAMD51 42 | Arduino" is one host, "SAMD51 CircuitPython" is another. 43 | 44 | ## Code structure 45 | 46 | The project is written in a combination of C and C++. The C++ parts are 47 | currently intended for the Arduino platform. CircuitPython development 48 | requires C at present, so most lower-level functionality is implemented 49 | in that language. The C part itself has two layers...one "mid layer" set of 50 | functions that are common to all architectures, and a "lower" layer that 51 | does architecture-specific things such as accessing peripheral registers. 52 | CircuitPython support, when implemented, will probably have its own topmost 53 | C layer, akin to the Arduino C++ layer. 54 | 55 | The code makes a reasonable attempt to keep architecture and platform 56 | -neutral vs -dependent C or C++ code in separate files. There might be a 57 | few exceptions with #ifdefs around them, always documented in the source. 58 | Also in comments, the terms "neutral" or "agnostic" might be used in 59 | different places, but same intent: non-preferential. 60 | 61 | ## SPIBrute Arduino class 62 | 63 | An extra class in here called "SPIBrute" is NOT RELATED to the OV7670 64 | camera itself. This SAMD51-specific class can be used to issue data to 65 | SPI-connected displays when DMA is not enabled in Adafruit_SPITFT.h (part 66 | of Adafruit_GFX). It's just a constructor and a couple functions. These 67 | display writes are still blocking operations, but the timing is particularly 68 | tight and avoids small inter-byte delays sometimes seen with SPI.transfer(), 69 | that's all. 70 | 71 | ## Files 72 | 73 | examples/ 74 | cameratest/ 75 | cameratest.ino Grand Central demo, OV7670 to ILI9341 TFT shield 76 | selfie/ 77 | selfie.ino Grand Central demo, OV7670 to 1.8" TFT shield 78 | src/ 79 | Adafruit_OV7670.cpp Arduino C++ class functions 80 | Adafruit_OV7670.h Arduino C++ class header 81 | SPIBrute.cpp SAMD51-specific C++ class, see notes above 82 | SPIBrute.h C++ header for SPIBrute.cpp 83 | image_ops.c Postprocessing (not in-camera) effects 84 | image_ops.h C header for image_ops.c 85 | ov7670.c Architecture- and platform-neutral functions in C 86 | ov7670.h C header for ov7670.c 87 | src/arch/ Architecture-specific code 88 | samd51.c SAMD51 arch-specific, platform-neutral C functions 89 | samd51.h Header for samd51.c 90 | samd51_arduino.cpp SAMD51 arch- and Arduino-specific C++ functions 91 | 92 | Architecture- and/or platform-specific files should contain #ifdef checks 93 | since some platforms (e.g. Arduino) will wanton compile all source files in 94 | the directory and would otherwise break. 95 | 96 | **When adding support for a new device at the C level, you MUST #include 97 | the corresponding header file in ov7670.h, so the C and C++ code that build 98 | atop this will pick up the specifics for the device.** 99 | 100 | Finer details are in comments peppered throughout the source. 101 | 102 | The Arduino library and examples make use of an architecture-specific "arch" 103 | structure. This is kind of gross, but after much deliberation it seemed just 104 | slightly less gross than the alternative. There's some notes in the .cpp. 105 | 106 | The functions in image_ops.c perform postprocessing on a captured OV7670 107 | image. These are not in-camera effects, though some might be possible 108 | to implement as such. Image is overwritten -- destination buffer is 109 | always the same as the source buffer, same dimensions, same colorspace. 110 | 111 | ## OV7670 notes 112 | 113 | Data from the OV7670 is always in BIG-ENDIAN format. Most 16-bit TFT and 114 | OLED displays are also big-endian, so it's straightforward to move data 115 | directly from one to the other. If you processing the image though...most 116 | microcontrollers are little-endian, so anything interpreting colors of an 117 | image will usually need to byte-swap the data, e.g. for each pixel: 118 | 119 | le = __builtin_bswap16(be); 120 | 121 | RGR565 and YUV output are supported. RGB444 and RGB555 are of questionable 122 | utility so I'm not bothering. A function is provided to convert YUV to a 123 | grayscale big-endian RGB565 representation for output to TFT displays. 124 | If not displaying, if you only need the grayscale (0-255) value of a pixel, 125 | use the LSB of a 16-bit YUV pixel (don't covert to RGB565, as this will 126 | decimate the grayscale resolution). 127 | 128 | The library currently provides five resolution settings from full VGA 129 | (640x480 pixels, RAM permitting, which it isn't), and powers-of-two 130 | divisions of this, down to 1:16 (40x30 pixels). 131 | 132 | An intentional choice was made to ignore the camera's CIF resolution options 133 | for the sake of getting things done. CIF is an antiquated throwback to 134 | analog PAL video and doesn't really get us much, being just slightly larger 135 | than the nearest corresponding VGA division anyway. 136 | 137 | IN THEORY the camera can provide nearly any resolution from CIF down to 138 | 40x30, with independent scaling on each axis. In practice this has proven 139 | difficult to implement, as it's not well explained in documentation or 140 | example code. Maybe this will be revisited in the future if it's needed, 141 | at which point CIF (and any other resolution) will happen implicitly. 142 | But for now, just the VGA powers-of-two. 143 | 144 | At the smallest size (40x30), there are artifacts in the first row and 145 | column that I've not been able to eliminate. Any software using this 146 | setting should try to mask out or otherwise ignore those pixels. 147 | -------------------------------------------------------------------------------- /examples/cameratest/cameratest.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Example for Adafruit_OV7670 library. With an ILI9341 TFT shield and 3 | OV7670 camera connected to Grand Central, displays a continuous live 4 | feed. That's all, keeping this a fairly minimal example. 5 | US 6 | HARDWARE REQUIRED: 7 | - Adafruit Grand Central board 8 | - Adafruit 2.8" TFT Touch Shield (touch is NOT USED in this example) 9 | - OV7670 camera w/2.2K pullups to SDA+SCL, 3.3V+GND wires to shield 10 | */ 11 | 12 | #include // I2C comm to camera 13 | #include "Adafruit_OV7670.h" // Camera library 14 | #include "Adafruit_ILI9341.h" // TFT display library 15 | 16 | // CAMERA CONFIG ----------------------------------------------------------- 17 | 18 | #if defined(__SAMD51__) // Grand Central or other M4 boards 19 | // Set up arch and pins structures for Grand Central's SAMD51. 20 | // PCC data pins are not configurable and do not need specifying. 21 | OV7670_arch arch = {.timer = TCC1, .xclk_pdec = false}; 22 | OV7670_pins pins = {.enable = PIN_PCC_D8, .reset = PIN_PCC_D9, 23 | .xclk = PIN_PCC_XCLK}; 24 | #define CAM_I2C Wire1 // Second I2C bus next to PCC pins 25 | #endif // end __SAMD51__ 26 | 27 | #define CAM_SIZE OV7670_SIZE_DIV2 // QVGA (320x240 pixels) 28 | #define CAM_MODE OV7670_COLOR_RGB // RGB plz 29 | 30 | Adafruit_OV7670 cam(OV7670_ADDR, &pins, &CAM_I2C, &arch); 31 | 32 | // SHIELD AND DISPLAY CONFIG ----------------------------------------------- 33 | 34 | // TFT shield pinout. Switch these if using Feather (DC=10, CS=9) 35 | #define TFT_DC 9 36 | #define TFT_CS 10 37 | #define TFT_SPI SPI 38 | 39 | // Screen DMA allows faster updates, but USE_SPI_DMA must be 40 | // manually enabled in Adafruit_SPITFT.h (it's not on by default 41 | // on Grand Central). If DMA is not enabled there, a helper class 42 | // makes screen writes a little bit faster (but not in background 43 | // like with DMA). 44 | #if !defined(USE_SPI_DMA) 45 | #include "SPIBrute.h" // Direct-to-SPI-registers helper class for SAMD51 46 | #endif 47 | 48 | Adafruit_ILI9341 tft(&TFT_SPI, TFT_DC, TFT_CS); 49 | #if defined(USE_SPI_BRUTE) 50 | SPIBrute brute(&TFT_SPI); 51 | #endif 52 | 53 | // SETUP - RUNS ONCE ON STARTUP -------------------------------------------- 54 | 55 | void setup() { 56 | Serial.begin(9600); 57 | //while (!Serial); 58 | Serial.println("Hello"); 59 | 60 | // 50 MHz to screen is OK if wiring is clean (e.g. shield or FeatherWing). 61 | // Otherwise (if using jumper wires to screen), stick to 24 MHz. 62 | #if defined(__SAMD51__) 63 | TFT_SPI.setClockSource(SERCOM_CLOCK_SOURCE_100M); 64 | tft.begin(50000000); 65 | #else 66 | tft.begin(24000000); 67 | #endif 68 | tft.setRotation(3); // Match camera orientation on Grand Central 69 | tft.fillScreen(ILI9341_BLACK); 70 | #if defined(USE_SPI_BRUTE) 71 | brute.begin(); 72 | #endif 73 | 74 | // Once started, the camera continually fills a frame buffer 75 | // automagically; no need to request a frame. 76 | OV7670_status status = cam.begin(CAM_MODE, CAM_SIZE, 30.0); 77 | if (status != OV7670_STATUS_OK) { 78 | Serial.println("Camera begin() fail"); 79 | for(;;); 80 | } 81 | 82 | uint8_t pid = cam.readRegister(OV7670_REG_PID); // Should be 0x76 83 | uint8_t ver = cam.readRegister(OV7670_REG_VER); // Should be 0x73 84 | Serial.println(pid, HEX); 85 | Serial.println(ver, HEX); 86 | } 87 | 88 | // MAIN LOOP - RUNS REPEATEDLY UNTIL RESET OR POWER OFF -------------------- 89 | 90 | // TFT setAddrWindow() involves a lot of context switching that can slow 91 | // things down a bit, so we don't do it on every frame. Instead, it's only 92 | // set periodically, and we just keep writing data to the same area of the 93 | // screen (it wraps around automatically). We do need an OCCASIONAL 94 | // setAddrWindow() in case SPI glitches, as this syncs things up to a 95 | // known region of the screen again. 96 | #define KEYFRAME 30 // Number of frames between setAddrWindow commands 97 | uint16_t frame = KEYFRAME; // Force 1st frame as keyframe 98 | 99 | void loop() { 100 | // This was for empirically testing window settings in src/arch/ov7670.c. 101 | // Your code doesn't need this. Just keeping around for future reference. 102 | if(Serial.available()) { 103 | uint32_t vstart = Serial.parseInt(); 104 | uint32_t hstart = Serial.parseInt(); 105 | uint32_t edge_offset = Serial.parseInt(); 106 | uint32_t pclk_delay = Serial.parseInt(); 107 | OV7670_frame_control((void *)&cam, CAM_SIZE, vstart, hstart, 108 | edge_offset, pclk_delay); 109 | } 110 | 111 | if (++frame >= KEYFRAME) { // Time to sync up a fresh address window? 112 | frame = 0; 113 | #if defined(USE_SPI_DMA) 114 | tft.dmaWait(); // Wait for prior transfer to complete 115 | #elif defined(USE_SPI_BRUTE) 116 | brute.wait(); 117 | #endif 118 | tft.endWrite(); // Close out prior transfer 119 | tft.startWrite(); // and start a fresh one (required) 120 | // Address window centers QVGA image on screen. NO CLIPPING IS 121 | // PERFORMED, it is assumed here that the camera image is equal 122 | // or smaller than the screen. 123 | tft.setAddrWindow((tft.width() - cam.width()) / 2, 124 | (tft.height() - cam.height()) / 2, 125 | cam.width(), cam.height()); 126 | } 127 | 128 | // Pause the camera DMA - hold buffer steady to avoid tearing 129 | cam.suspend(); 130 | 131 | //cam.capture(); // Manual capture instead of PCC DMA 132 | 133 | // Postprocessing effects. These modify a previously-captured 134 | // image in memory, they are NOT in-camera effects. 135 | // Most only work in RGB mode (not YUV). 136 | //cam.image_negative(); 137 | //cam.image_threshold(150); 138 | //cam.image_posterize(5); // # of levels 139 | //cam.image_mosaic(21, 9); // Tile width, height 140 | //cam.image_median(); 141 | //cam.image_edges(4); // 0-31, smaller = more edges 142 | 143 | if(CAM_MODE == OV7670_COLOR_YUV) { 144 | cam.Y2RGB565(); // Convert grayscale for TFT preview 145 | } 146 | 147 | // Camera data arrives in big-endian order...same as the TFT, 148 | // so data can just be issued directly, no byte-swap needed. 149 | // Both the DMA and brute cases handle this. 150 | #if defined(USE_SPI_DMA) 151 | tft.dmaWait(); 152 | tft.writePixels(cam.getBuffer(), cam.width() * cam.height(), false, true); 153 | #elif defined(USE_SPI_BRUTE) 154 | brute.wait(); 155 | brute.write((uint8_t *)cam.getBuffer(), cam.width() * cam.height() * 2); 156 | #else 157 | tft.writePixels(cam.getBuffer(), cam.width() * cam.height(), false, true); 158 | #endif 159 | 160 | cam.resume(); // Resume DMA to camera buffer 161 | } 162 | -------------------------------------------------------------------------------- /examples/selfie/selfie.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Example for Adafruit_OV7670 library. With camera plugged directly 3 | into Grand Central PCC pins, it faces "up," same way as a TFT 4 | shield, so think of it as a selfie camera. Tap "A" button to save 5 | image to card. Use card slot on Grand Central, not the TFT shield! 6 | 7 | This is a simple demo, NOT something for actual productive use, and 8 | it's not especially thoughtful with your files. Existing images on 9 | the SD card WILL be overwritten if they exist! Files will be in the 10 | 'SELFIES' folder and increment from #0001. 11 | 12 | HARDWARE REQUIRED: 13 | - Adafruit Grand Central board 14 | - Adafruit 1.8" TFT shield V2 15 | - OV7670 camera w/2.2K pullups to SDA+SCL, 3.3V+GND wires to shield 16 | - microSD card (in card slot on Grand Central, not shield) 17 | 18 | Board/shield is held with screen to the left, camera to the right, 19 | buttons at bottom left (i.e. Grand Central silkscreen is readable). 20 | This is actually upside-down with respect to the camera's native 21 | orientation, but we compensate by rotating both the display output 22 | and BMP writing 180 degrees. 23 | */ 24 | 25 | #include // I2C comm to camera 26 | #include // SD card support 27 | #include "Adafruit_OV7670.h" // Camera library 28 | #include "Adafruit_ST7735.h" // TFT display library 29 | #include "Adafruit_seesaw.h" // For TFT shield 30 | #include "Adafruit_TFTShield18.h" // More TFT shield 31 | 32 | // SD CARD CONFIG ---------------------------------------------------------- 33 | 34 | // The SD card library expects to use the Grand Central's onboard microSD 35 | // slot, not the one on the shield. Being on a separate SPI bus, this avoids 36 | // some transactions that would be needed when sharing SD+TFT on the shield. 37 | #define SD_CS SDCARD_SS_PIN // Grand Central onboard SD card select 38 | 39 | // CAMERA CONFIG ----------------------------------------------------------- 40 | 41 | // Set up arch and pins structures for Grand Central's SAMD51. 42 | // PCC data pins are not configurable and do not need specifying. 43 | OV7670_arch arch = {.timer = TCC1, .xclk_pdec = false}; 44 | OV7670_pins pins = {.enable = PIN_PCC_D8, .reset = PIN_PCC_D9, 45 | .xclk = PIN_PCC_XCLK}; 46 | #define CAM_I2C Wire1 // Second I2C bus next to PCC pins 47 | #define CAM_SIZE OV7670_SIZE_DIV4 // QQVGA (160x120 pixels) 48 | #define CAM_MODE OV7670_COLOR_RGB // RGB plz 49 | 50 | Adafruit_OV7670 cam(OV7670_ADDR, &pins, &CAM_I2C, &arch); 51 | 52 | // SHIELD AND DISPLAY CONFIG ----------------------------------------------- 53 | 54 | Adafruit_TFTShield18 shield; 55 | 56 | #define TFT_CS 10 // Chip select for TFT on shield 57 | #define TFT_DC 8 // Data/command line for TFT on Shield 58 | #define TFT_RST -1 // TFT reset is handled by seesaw 59 | #define TFT_SPI SPI // 6-pin ICSP header 60 | 61 | // Screen DMA allows faster updates, but USE_SPI_DMA must be 62 | // manually enabled in Adafruit_SPITFT.h (it's not on by default 63 | // on Grand Central). If DMA is not enabled there, a helper class 64 | // makes screen writes a little bit faster (but not in background 65 | // like with DMA). 66 | #if !defined(USE_SPI_DMA) 67 | #include "SPIBrute.h" // Direct-to-SPI-registers helper for SAMD51 68 | #endif // end USE_SPI_DMA 69 | 70 | Adafruit_ST7735 tft(TFT_CS, TFT_DC, TFT_RST); 71 | #if defined(USE_SPI_BRUTE) 72 | SPIBrute brute(&TFT_SPI); 73 | #endif 74 | 75 | // SETUP - RUNS ONCE ON STARTUP -------------------------------------------- 76 | 77 | void setup() { 78 | pinMode(SD_CS, OUTPUT); 79 | digitalWrite(SD_CS, HIGH); // Disable SD card select during setup 80 | pinMode(4, OUTPUT); 81 | digitalWrite(4, HIGH); // Disable shield SD card select just in case 82 | 83 | Serial.begin(9600); 84 | //while (!Serial); 85 | 86 | if (!SD.begin(SD_CS)) { 87 | Serial.println("SD begin() fail (continuing without)"); 88 | } 89 | SD.mkdir("/selfies"); 90 | 91 | if (!shield.begin()) { // Start seesaw helper chip on shield 92 | Serial.println("seesaw begin() fail"); 93 | for(;;); 94 | } 95 | 96 | // TFT backlight OFF during setup 97 | shield.setBacklight(TFTSHIELD_BACKLIGHT_OFF); 98 | shield.tftReset(); 99 | 100 | tft.initR(INITR_BLACKTAB); // Initialize TFT on shield 101 | tft.setRotation(3); // See notes earlier re: orientation 102 | tft.fillScreen(ST77XX_BLACK); // Clear background 103 | #if defined(USE_SPI_BRUTE) 104 | brute.begin(); 105 | #endif 106 | 107 | // Once started, the camera continually fills a frame buffer 108 | // automagically; no need to request a frame. 109 | // Camera buffer is allocated for the larger 320x240 pixel size, 110 | // though the preview is only 160x120. This is to ensure that the 111 | // RAM is available later when we go to take a higher-resolution 112 | // still to save. 113 | OV7670_status status = cam.begin(CAM_MODE, CAM_SIZE, 30.0, 320 * 240 * 2); 114 | if (status != OV7670_STATUS_OK) { 115 | Serial.println("Camera begin() fail"); 116 | for(;;); 117 | } 118 | 119 | // Once everything's ready, turn TFT backlight on... 120 | shield.setBacklight(TFTSHIELD_BACKLIGHT_ON); 121 | tft.setTextColor(0xFFFF); 122 | tft.setTextSize(2); 123 | } 124 | 125 | // MAIN LOOP - RUNS REPEATEDLY UNTIL RESET OR POWER OFF -------------------- 126 | 127 | // TFT setAddrWindow() involves a lot of context switching that can slow 128 | // things down a bit, so we don't do it on every frame. Instead, it's only 129 | // set periodically, and we just keep writing data to the same area of the 130 | // screen (it wraps around automatically). We do need an OCCASIONAL 131 | // setAddrWindow() in case SPI glitches, as this syncs things up to a 132 | // known region of the screen again. 133 | #define KEYFRAME 30 // Number of frames between setAddrWindow commands 134 | uint16_t frame = KEYFRAME; // Force 1st frame as keyframe 135 | uint16_t bmp_num = 1; // Image number increments with each BMP saved 136 | 137 | void loop() { 138 | if (++frame >= KEYFRAME) { // Time to sync up a fresh address window? 139 | frame = 0; 140 | #if defined(USE_SPI_DMA) 141 | tft.dmaWait(); // Wait for prior transfer to complete 142 | #elif defined(USE_SPI_BRUTE) 143 | brute.wait(); 144 | #endif 145 | tft.endWrite(); // Close out prior transfer 146 | tft.startWrite(); // and start a fresh one (required) 147 | // Address window centers QQVGA image on screen. NO CLIPPING IS 148 | // PERFORMED, it is assumed here that the camera image is equal 149 | // or smaller than the screen. 150 | tft.setAddrWindow((tft.width() - cam.width()) / 2, 151 | (tft.height() - cam.height()) / 2, 152 | cam.width(), cam.height()); 153 | } 154 | 155 | // Pause the camera DMA - hold buffer steady to avoid tearing 156 | cam.suspend(); 157 | 158 | if(CAM_MODE == OV7670_COLOR_YUV) { 159 | cam.Y2RGB565(); // Convert grayscale for TFT preview 160 | } 161 | 162 | // Camera data arrives in big-endian order...same as the TFT, 163 | // so data can just be issued directly, no byte-swap needed. 164 | // Both the DMA and brute cases handle this. 165 | #if defined(USE_SPI_DMA) 166 | tft.dmaWait(); 167 | tft.writePixels(cam.getBuffer(), cam.width() * cam.height(), false, true); 168 | #elif defined(USE_SPI_BRUTE) 169 | brute.wait(); 170 | brute.write((uint8_t *)cam.getBuffer(), cam.width() * cam.height() * 2); 171 | #else 172 | tft.writePixels(cam.getBuffer(), cam.width() * cam.height(), false, true); 173 | #endif 174 | 175 | if(!(shield.readButtons() & TFTSHIELD_BUTTON_1)) { 176 | char filename[50]; 177 | sprintf(filename, "/selfies/img%04d.bmp", bmp_num++); 178 | #if defined(USE_SPI_DMA) 179 | tft.dmaWait(); // Wait for prior transfer to complete 180 | #elif defined(USE_SPI_BRUTE) 181 | brute.wait(); 182 | #endif 183 | // Set camera capture to larger size (320x240). The REALLOC_NONE 184 | // tells it to keep the original buffer in place, which we allocated 185 | // large enough in setup() to handle these stills. Continual 186 | // reallocation would just be asking for trouble. 187 | cam.setSize(OV7670_SIZE_DIV2, OV7670_REALLOC_NONE); 188 | delay(100); // Stabilize for a few frames 189 | tft.endWrite(); // Close out prior pixel write 190 | tft.setRotation(1); // Put text in readable orientation 191 | tft.fillRect(42, 58, 74, 18, 0x0000); 192 | tft.setCursor(44, 60); 193 | tft.print("SAVING"); 194 | tft.setRotation(3); // Go back to 180 degree screen rotation 195 | frame = 999; // Force keyframe on next update 196 | cam.capture(); // Manual (non-DMA) capture 197 | write_bmp(filename, cam.getBuffer(), cam.width(), cam.height()); 198 | // Restore the original preview size from camera. Again, use 199 | // REALLOC_NONE to maintain our original camera buffer. 200 | cam.setSize(CAM_SIZE, OV7670_REALLOC_NONE); 201 | } 202 | 203 | cam.resume(); // Resume DMA into camera buffer 204 | } 205 | 206 | // Write 16-bit value to BMP file in little-endian order 207 | void writeLE16(File *file, uint16_t value) { 208 | #if(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) 209 | file->write((char *)&value, sizeof value); 210 | #else 211 | file->write( value & 0xFF); 212 | file->write((value >> 8) & 0xFF); 213 | #endif 214 | } 215 | 216 | // Write 32-bit value to BMP file in little-endian order 217 | void writeLE32(File *file, uint32_t value) { 218 | #if(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) 219 | file->write((char *)&value, sizeof value); 220 | #else 221 | file->write( value & 0xFF); 222 | file->write((value >> 8) & 0xFF); 223 | file->write((value >> 16) & 0xFF); 224 | file->write((value >> 24) & 0xFF); 225 | #endif 226 | } 227 | 228 | // Minimalist RGB565 BMP-writing function. 229 | void write_bmp(char *filename, uint16_t *addr, 230 | uint16_t width, uint16_t height) { 231 | SD.remove(filename); // Delete existing file, if any 232 | File file = SD.open(filename, FILE_WRITE); 233 | if(file) { 234 | // BMP header, 14 bytes: 235 | file.write(0x42); // Windows BMP signature 236 | file.write(0x4D); // " 237 | writeLE32(&file, 14 + 56 + width * height * 2); // File size in bytes 238 | writeLE32(&file, 0); // Creator bytes (ignored) 239 | writeLE32(&file, 14 + 56); // Offset to pixel data 240 | 241 | // DIB header, 56 bytes "BITMAPV3INFOHEADER" type (for RGB565): 242 | writeLE32(&file, 56); // Header size in bytes 243 | writeLE32(&file, width); // Width in pixels 244 | writeLE32(&file, height); // Height in pixels (bottom-to-top) 245 | writeLE16(&file, 1); // Planes = 1 246 | writeLE16(&file, 16); // Bits = 16 247 | writeLE32(&file, 3); // Compression = bitfields 248 | writeLE32(&file, width * height); // Bitmap size (adobe adds 2 here also) 249 | writeLE32(&file, 2835); // Horiz resolution (72dpi) 250 | writeLE32(&file, 2835); // Vert resolution (72dpi) 251 | writeLE32(&file, 0); // Default # colors in palette 252 | writeLE32(&file, 0); // Default # "important" colors 253 | writeLE32(&file, 0b1111100000000000); // Red mask 254 | writeLE32(&file, 0b0000011111100000); // Green mask 255 | writeLE32(&file, 0b0000000000011111); // Blue mask 256 | writeLE32(&file, 0); // Alpha mask 257 | 258 | // As mentioned in beginning, the camera is upside-down when board is 259 | // held at the intended orientation. BMPs are normally stored bottom- 260 | // to-top, so walking through rows incrementally takes care of this 261 | // axis, and horizontal axis is manually flipped. 262 | for(int y=0; y= 0; x--) { // Flip X 265 | // Raw data from camera is big-endian, BMP needs little-endian 266 | uint16_t pixel = __builtin_bswap16(row[x]); 267 | file.write((char *)&pixel, 2); 268 | // I mean yeah, since we're flipping both the X axis and endian- 269 | // swapping, we COULD just write out the individual row bytes in 270 | // reverse order. Handling pixels & bytes separately here so 271 | // there's no confusion if this code gets used elsewhere and 272 | // changed with X in the + direction. 273 | } 274 | // No scanline pad is added. We know the OV7670 library image sizes 275 | // are always an even width (4 bytes). Something to keep in mind if 276 | // flexible sizing and/or cropping options are added later. 277 | } 278 | 279 | file.close(); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Adafruit OV7670 2 | version=1.2.4 3 | author=Adafruit 4 | maintainer=Adafruit 5 | sentence=A library for the OV7670 camera. 6 | paragraph=OV7670 camera. 7 | category=Sensors 8 | url=https://github.com/adafruit/Adafruit_OV7670 9 | architectures=samd 10 | depends=Adafruit Zero DMA Library,Adafruit ILI9341,SD,Adafruit ST7735 and ST7789 Library 11 | -------------------------------------------------------------------------------- /src/Adafruit_OV7670.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | /*! 6 | * @file Adafruit_OV7670.cpp 7 | * 8 | * @mainpage Adafruit OV7670 Camera Library 9 | * 10 | * @section intro_sec Introduction 11 | * 12 | * This is documentation for Adafruit's OV7670 camera driver for the 13 | * Arduino platform. 14 | * 15 | * Adafruit invests time and resources providing this open source code, 16 | * please support Adafruit and open-source hardware by purchasing 17 | * products from Adafruit! 18 | * 19 | * @section dependencies Dependencies 20 | * 21 | * This library depends on 22 | * 23 | * Adafruit_ZeroDMA being present on your system. Please make sure you 24 | * have installed the latest version before using this library. 25 | * 26 | * @section author Author 27 | * 28 | * Written by Phil "PaintYourDragon" Burgess for Adafruit Industries. 29 | * 30 | * @section license License 31 | * 32 | * MIT license, all text here must be included in any redistribution. 33 | */ 34 | 35 | #include "Adafruit_OV7670.h" 36 | #include 37 | #include 38 | 39 | Adafruit_OV7670::Adafruit_OV7670(uint8_t addr, OV7670_pins *pins_ptr, 40 | TwoWire *twi_ptr, OV7670_arch *arch_ptr) 41 | : i2c_address(addr & 0x7f), wire(twi_ptr), 42 | arch_defaults((arch_ptr == NULL)), buffer(NULL), buffer_size(0) { 43 | if (pins_ptr) { 44 | memcpy(&pins, pins_ptr, sizeof(OV7670_pins)); 45 | } 46 | if (arch_ptr) { 47 | memcpy(&arch, arch_ptr, sizeof(OV7670_arch)); 48 | } 49 | } 50 | 51 | Adafruit_OV7670::~Adafruit_OV7670() { 52 | if (buffer) { 53 | free(buffer); 54 | } 55 | // TO DO: arch-specific code should have a function to clean up DMA 56 | // and timer. No rush really, destructor is unlikely to ever be used. 57 | } 58 | 59 | // CAMERA INIT AND CONFIG FUNCTIONS ---------------------------------------- 60 | 61 | OV7670_status Adafruit_OV7670::begin(OV7670_colorspace colorspace, 62 | OV7670_size size, float fps, 63 | uint32_t bufsiz) { 64 | 65 | wire->begin(); 66 | wire->setClock(100000); // Datasheet claims 400 KHz, but no, use 100 KHz 67 | 68 | _width = 640 >> (int)size; // 640, 320, 160, 80, 40 69 | _height = 480 >> (int)size; // 480, 240, 120, 60, 30 70 | space = colorspace; 71 | 72 | // Allocate buffer for camera 73 | buffer_size = bufsiz ? bufsiz : _width * _height * sizeof(uint16_t); 74 | buffer = (uint16_t *)malloc(buffer_size); 75 | if (buffer == NULL) { 76 | buffer_size = 0; 77 | return OV7670_STATUS_ERR_MALLOC; 78 | } 79 | 80 | return arch_begin(colorspace, size, fps); // Device-specific setup 81 | } 82 | 83 | int Adafruit_OV7670::readRegister(uint8_t reg) { 84 | wire->beginTransmission(i2c_address); 85 | wire->write(reg); 86 | wire->endTransmission(); 87 | wire->requestFrom(i2c_address, (uint8_t)1); 88 | return wire->read(); 89 | } 90 | 91 | void Adafruit_OV7670::writeRegister(uint8_t reg, uint8_t value) { 92 | wire->beginTransmission(i2c_address); 93 | wire->write(reg); 94 | wire->write(value); 95 | wire->endTransmission(); 96 | } 97 | 98 | OV7670_status Adafruit_OV7670::setSize(OV7670_size size, OV7670_realloc allo) { 99 | uint16_t new_width = 640 >> (int)size; 100 | uint16_t new_height = 480 >> (int)size; 101 | uint32_t new_buffer_size = new_width * new_height * sizeof(uint16_t); 102 | bool ra = false; 103 | 104 | switch (allo) { 105 | case OV7670_REALLOC_NONE: 106 | if (new_buffer_size > buffer_size) { // New size won't fit 107 | // Don't realloc. Keep current camera settings, return error. 108 | return OV7670_STATUS_ERR_MALLOC; 109 | } 110 | break; 111 | case OV7670_REALLOC_CHANGE: 112 | ra = (new_buffer_size != buffer_size); // Realloc on size change 113 | break; 114 | case OV7670_REALLOC_LARGER: 115 | ra = (new_buffer_size > buffer_size); // Realloc on size increase 116 | break; 117 | } 118 | 119 | if (ra) { // Reallocate? 120 | uint16_t *new_buffer = (uint16_t *)realloc(buffer, new_buffer_size); 121 | if (new_buffer == NULL) { // FAIL 122 | _width = _height = buffer_size = 0; 123 | buffer = NULL; 124 | // Calling code had better poll width(), height() or getBuffer() in 125 | // this case so it knows the camera buffer is gone, doesn't attempt 126 | // to read camera data into unknown RAM. 127 | return OV7670_STATUS_ERR_MALLOC; 128 | } 129 | buffer_size = new_buffer_size; 130 | } 131 | 132 | _width = new_width; 133 | _height = new_height; 134 | 135 | OV7670_set_size(this, size); 136 | 137 | return OV7670_STATUS_OK; 138 | } 139 | 140 | void Adafruit_OV7670::Y2RGB565(void) { 141 | OV7670_Y2RGB565(buffer, _width * _height); 142 | } 143 | 144 | // C-ACCESSIBLE FUNCTIONS -------------------------------------------------- 145 | 146 | // These functions are declared in an extern "C" block in Adafruit_OV7670.h 147 | // so that arch/*.c code can access them here. The Doxygen comments in the 148 | // .h explain their use in more detail. 149 | 150 | void OV7670_print(char *str) { Serial.print(str); } 151 | 152 | int OV7670_read_register(void *obj, uint8_t reg) { 153 | return ((Adafruit_OV7670 *)obj)->readRegister(reg); 154 | } 155 | 156 | void OV7670_write_register(void *obj, uint8_t reg, uint8_t value) { 157 | ((Adafruit_OV7670 *)obj)->writeRegister(reg, value); 158 | } 159 | -------------------------------------------------------------------------------- /src/Adafruit_OV7670.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | /*! 6 | * @file Adafruit_OV7670.h 7 | * 8 | * This is documentation for Adafruit's OV7670 camera driver for the 9 | * Arduino platform. 10 | * 11 | * Adafruit invests time and resources providing this open source code, 12 | * please support Adafruit and open-source hardware by purchasing 13 | * products from Adafruit! 14 | * 15 | * Written by Phil "PaintYourDragon" Burgess for Adafruit Industries. 16 | * 17 | * MIT license, all text here must be included in any redistribution. 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "image_ops.h" 23 | #include "ov7670.h" 24 | #include 25 | #include 26 | 27 | /** Buffer reallocation behaviors requested of setSize() */ 28 | typedef enum { 29 | OV7670_REALLOC_NONE = 0, ///< No realloc, error if new size > current buffer 30 | OV7670_REALLOC_CHANGE, ///< Reallocate image buffer if size changes 31 | OV7670_REALLOC_LARGER, ///< Realloc only if new size is larger 32 | } OV7670_realloc; 33 | 34 | /*! 35 | @brief Class encapsulating OV7670 camera functionality. 36 | */ 37 | class Adafruit_OV7670 { 38 | public: 39 | /*! 40 | @brief Constructor for Adafruit_OV7670 class. 41 | @param addr I2C address of camera. 42 | @param pins_ptr Pointer to OV7670_pins structure, describing physical 43 | connection to the camera. 44 | @param twi_ptr Pointer to TwoWire instance (e.g. &Wire or &Wire1), 45 | used for I2C communication with camera. 46 | @param arch_ptr Pointer to structure containing architecture-specific 47 | settings. For example, on SAMD51, this structure 48 | includes a pointer to a timer peripheral's base address, 49 | used to generate the xclk signal. The structure is 50 | always of type OV7670_arch, but the specific elements 51 | within will vary with each supported architecture. 52 | */ 53 | Adafruit_OV7670(uint8_t addr = OV7670_ADDR, OV7670_pins *pins_ptr = NULL, 54 | TwoWire *twi_ptr = &Wire, OV7670_arch *arch_ptr = NULL); 55 | ~Adafruit_OV7670(); // Destructor 56 | 57 | /*! 58 | @brief Allocate and initialize resources behind an Adafruit_OV7670 59 | instance. 60 | @param colorspace OV7670_COLOR_RGB or OV7670_COLOR_YUV. 61 | @param size Frame size as a power-of-two reduction of VGA 62 | resolution. Available sizes are OV7670_SIZE_DIV1 63 | (640x480), OV7670_SIZE_DIV2 (320x240), 64 | OV7670_SIZE_DIV4 (160x120), OV7670_SIZE_DIV8 and 65 | OV7670_SIZE_DIV16. 66 | @param fps Desired capture framerate, in frames per second, 67 | as a float up to 30.0. Actual device frame rate may 68 | differ from this, depending on a host's available 69 | PWM timing. Generally, the actual device fps will 70 | be equal or nearest-available below the requested 71 | rate, only in rare cases of extremely low requested 72 | frame rates will a higher value be used. Since 73 | begin() only returns a status code, if you need to 74 | know the actual framerate you can call 75 | OV7670_set_fps(NULL, fps) at any time before or 76 | after begin() and that will return the actual 77 | resulting frame rate as a float. 78 | @param bufsiz Image buffer size, in bytes. This is configurable so 79 | code can do things like change image sizes without 80 | reallocating (which risks losing the existing buffer) 81 | or double-buffered transfers. Pass 0 to use default 82 | buffer size equal to 2 bytes per pixel times the 83 | number of pixels corresponding to the 'size' 84 | argument. If you later call setSize() with an image 85 | size exceeding the buffer size, it will fail. 86 | @return Status code. OV7670_STATUS_OK on successful init. 87 | */ 88 | OV7670_status begin(OV7670_colorspace colorspace = OV7670_COLOR_RGB, 89 | OV7670_size size = OV7670_SIZE_DIV4, float fps = 30.0, 90 | uint32_t bufsiz = 0); 91 | 92 | /*! 93 | @brief Reads value of one register from the OV7670 camera over I2C. 94 | @param reg Register to read, from values defined in src/arch/ov7670.h. 95 | @return Integer value: 0-255 (register contents) on successful read, 96 | -1 on error. 97 | */ 98 | int readRegister(uint8_t reg); 99 | 100 | /*! 101 | @brief Writes value of one register to the OV7670 camera over I2C. 102 | @param reg Register to read, from values defined in src/arch/ov7670.h. 103 | @param value Value to write, 0-255. 104 | 105 | */ 106 | void writeRegister(uint8_t reg, uint8_t value); 107 | 108 | /*! 109 | @brief Get address of image buffer being used by camera. 110 | @return uint16_t pointer to image data in RGB565 format. 111 | */ 112 | uint16_t *getBuffer(void) { return buffer; } 113 | 114 | /*! 115 | @brief Pause DMA background capture (if supported by architecture) 116 | before capturing, to avoid tearing. Returns as soon as the 117 | current frame has finished loading. If DMA background capture 118 | is not supported, this function has no effect. This is NOT a 119 | camera sleep function! 120 | */ 121 | void suspend(void); 122 | 123 | /*! 124 | @brief Resume DMA background capture after suspend. If DMA is not 125 | supported, this function has no effect. 126 | */ 127 | void resume(void); 128 | 129 | /*! 130 | @brief Get image width of camera's current resolution setting. 131 | @return Width in pixels. 132 | */ 133 | uint16_t width(void) { return _width; } 134 | 135 | /*! 136 | @brief Get image height of camera's current resolution setting. 137 | @return Height in pixels. 138 | */ 139 | uint16_t height(void) { return _height; } 140 | 141 | /*! 142 | @brief Change camera resolution post-begin(). Not yet implemented. 143 | @param size One of the OV7670_size values ranging from full VGA 144 | (640x480 pixels) to 1/16 VGA (40x30 pixels). 145 | @param allo Camera buffer reallocation behavior: 146 | - OV7670_REALLOC_NONE to not reallocate buffer. 147 | Function will return OV7670_STATUS_OK if new size fits 148 | in existing buffer, or OV7670_ERR_MALLOC if existing 149 | buffer is too small (buffer is not freed and current 150 | camera size is maintained). 151 | - OV7670_REALLOC_CHANGE to reallocate buffer on ANY 152 | size change, up or down. Function will return 153 | OV7670_STATUS_OK if reallocation was successful and 154 | camera size changed, or OV7670_ERR_MALLOC if 155 | reallocation failed (camera width and height will 156 | subsequently both poll as 0, or buffer to NULL, in 157 | this case). 158 | - OV7670_REALLOC_LARGER to reallocate buffer ONLY if new 159 | size exceeds current buffer size. Function will return 160 | OV7670_STATUS_OK if reallocation was successful and 161 | camera size changed, or OV7670_ERR_MALLOC if 162 | reallocation failed (camera width and height will 163 | subsequently both poll as 0 in this case). 164 | @return Status code. OV7670_STATUS_OK on success (image buffer 165 | successfully reallocated as requested, camera reconfigured), 166 | OV7670_STATUS_ERR_MALLOC in several situations explained above. 167 | @note Reallocating the camera buffer is fraught with peril and should 168 | only be done if you're prepared to handle any resulting error. 169 | In most cases, code should pass the size of LARGEST buffer it 170 | anticipates needing (including any double buffering, etc.) to 171 | begin(), which allocates it once on startup. Some RAM will go 172 | untilized at times, but it's favorable to entirely losing the 173 | camera mid-run. The default request here is CHANGE in case one 174 | passes an improper initial value to begin(). 175 | */ 176 | OV7670_status setSize(OV7670_size size, 177 | OV7670_realloc allo = OV7670_REALLOC_CHANGE); 178 | 179 | /*! 180 | @brief Capture still image to buffer. If background DMA capture is 181 | supported, this has no effect; latest image is always there. 182 | */ 183 | void capture(void); 184 | 185 | /*! 186 | @brief Select one of the camera's night modes. Images are less 187 | grainy in low light, tradeoff being a reduced frame rate. 188 | @param night One of the OV7670_night_mode types: 189 | OV7670_NIGHT_MODE_OFF Disable night mode, full frame rate 190 | OV7670_NIGHT_MODE_2 1/2 frame rate 191 | OV7670_NIGHT_MODE_4 1/4 frame rate 192 | OV7670_NIGHT_MODE_8 1/8 frame rate 193 | */ 194 | void night(OV7670_night_mode night) { OV7670_night(this, night); } 195 | 196 | /*! 197 | @brief Flip camera output on horizontal and/or vertical axes. 198 | Flipping both axes is equivalent to 180 degree rotation. 199 | @param flip_x If true, flip camera output on horizontal axis. 200 | @param flip_y If true, flip camera output on vertical axis. 201 | @note Datasheet refers to horizontal flip as "mirroring," but 202 | avoiding that terminology here that it might be mistaken for a 203 | split-down-middle-and-reflect funhouse effect, which it isn't. 204 | */ 205 | void flip(bool flip_x, bool flip_y) { OV7670_flip(this, flip_x, flip_y); } 206 | 207 | /*! 208 | @brief Enable/disable camera test pattern output. 209 | @param pattern One of the OV7670_pattern values: 210 | OV7670_TEST_PATTERN_NONE 211 | Disable test pattern, display normal camera video. 212 | OV7670_TEST_PATTERN_SHIFTING_1 213 | "Shifting 1" test pattern (seems to be single-column 214 | vertical RGB lines). 215 | OV7670_TEST_PATTERN_COLOR_BAR 216 | Eight color bars. 217 | OV7670_TEST_PATTERN_COLOR_BAR_FADE 218 | Eight color bars with fade to white. 219 | @note This basically works but has some artifacts...color bars are 220 | wrapped around such that a few pixels of the leftmost (white) 221 | bar appear at to the right of the rightmost (black) bar. It 222 | seems that the frame control settings need to be slightly 223 | different for image sensor vs test patterns (frame control 224 | that displays the bars correctly has green artifacts along 225 | right edge when using image sensor). Eventually will want to 226 | make this handle the different cases and sizes correctly. 227 | In the meantime, there's minor uglies in test mode. 228 | Similar issue occurs with image flips. 229 | */ 230 | void test_pattern(OV7670_pattern pattern) { 231 | OV7670_test_pattern(this, pattern); 232 | } 233 | 234 | /*! 235 | @brief Produces a negative image. This is a postprocessing effect, 236 | not in-camera, and must be applied to frame(s) manually. 237 | Image in memory will be overwritten. 238 | */ 239 | void image_negative(void) { OV7670_image_negative(buffer, _width, _height); }; 240 | 241 | /*! 242 | @brief Decimate an image to only it's min/max values (ostensibly 243 | "black and white," but works on color channels separately 244 | so that's not strictly the case). This is a postprocessing 245 | effect, not in-camera, and must be applied to frame(s) manually. 246 | Image in memory will be overwritten. 247 | @param threshold Threshold level, 0-255; pixel brightnesses at or 248 | above this level are set to the maximum, below this 249 | level are set to the minimum. Input value is scaled 250 | to accommodate the lower color fidelity of the RGB 251 | colorspace -- use 0 to 255, not 0 to 31 or 63. 252 | */ 253 | void image_threshold(uint8_t threshold = 128) { 254 | OV7670_image_threshold(space, buffer, _width, _height, threshold); 255 | }; 256 | 257 | /*! 258 | @brief Decimate an image to a limited number of brightness levels or 259 | steps. This is a postprocessing effect, not in-camera, and must 260 | be applied to frame(s) manually. Image in memory will be 261 | overwritten. 262 | @param levels Number of brightness levels -- 2 to 32 for RGB 263 | colorspace, 2 to 255 for YUV. 264 | */ 265 | void image_posterize(uint8_t levels = 4) { 266 | OV7670_image_posterize(space, buffer, _width, _height, levels); 267 | }; 268 | 269 | /*! 270 | @brief Mosaic or "shower door effect," downsamples an image into 271 | rectangular tiles, each tile's color being the average of all 272 | source image pixels within that tile's area. This is a 273 | postprocessing effect, not in-camera, and must be applied to 274 | frame(s) manually. Image in memory will be overwritten. If 275 | image size does not divide equally by tile size, fractional 276 | tiles will always be along the right and/or bottom edge(s); 277 | top left corner is always a full tile. 278 | YUV colorspace is not currently supported. 279 | @param tile_width Tile width in pixels (1 to 255) 280 | @param tile_height Tile height in pixels (1 to 255) 281 | */ 282 | void image_mosaic(uint8_t tile_width = 8, uint8_t tile_height = 8) { 283 | OV7670_image_mosaic(space, buffer, _width, _height, tile_width, 284 | tile_height); 285 | }; 286 | 287 | /*! 288 | @brief 3x3 pixel median filter, reduces visual noise in image. 289 | This is a postprocessing effect, not in-camera, and must be 290 | applied to frame(s) manually. Image in memory will be 291 | overwritten. YUV colorspace is not currently supported. 292 | */ 293 | void image_median(void) { 294 | OV7670_image_median(space, buffer, _width, _height); 295 | }; 296 | 297 | /*! 298 | @brief Edge detection filter. 299 | This is a postprocessing effect, not in-camera, and must be 300 | applied to frame(s) manually. Image in memory will be 301 | overwritten. YUV colorspace is not currently supported. 302 | @param sensitivity Smaller value = more sensitive to edge changes. 303 | */ 304 | void image_edges(uint8_t sensitivity = 7) { 305 | OV7670_image_edges(space, buffer, _width, _height, sensitivity); 306 | }; 307 | 308 | /*! 309 | @brief Convert Y (brightness) component YUV image in RAM to RGB565 310 | big-endian format for preview on TFT display. Camera buffer is 311 | overwritten in-place, Y is truncated and UV elements are lost. 312 | No practical use outside TFT preview. If you need actual 313 | grayscale 0-255 data, just access the low byte of each 16-bit 314 | YUV pixel. 315 | */ 316 | void Y2RGB565(void); 317 | 318 | private: 319 | OV7670_status arch_begin(OV7670_colorspace colorspace, OV7670_size size, 320 | float fps); 321 | TwoWire *wire; ///< I2C interface 322 | uint16_t *buffer; ///< Camera buffer allocated by lib 323 | uint32_t buffer_size; ///< Size of camera buffer, in bytes 324 | OV7670_pins pins; ///< Camera physical connections 325 | OV7670_arch arch; ///< Architecture-specific peripheral info 326 | uint16_t _width; ///< Current settings width in pixels 327 | uint16_t _height; ///< Current settings height in pixels 328 | OV7670_colorspace space; ///< RGB or YUV colorspace 329 | const uint8_t i2c_address; ///< I2C address 330 | const bool arch_defaults; ///< If set, ignore arch struct, use defaults 331 | }; 332 | 333 | // C-ACCESSIBLE FUNCTIONS -------------------------------------------------- 334 | 335 | // These functions are declared in an extern "C" so that arch/*.c code can 336 | // access them here. They provide a route from the mid- and low-level C code 337 | // back up to the Arduino level, so object-based functions like 338 | // Serial.print() and Wire.write() can be called. 339 | 340 | extern "C" { 341 | 342 | /*! 343 | @brief A Serial Console print function for crude debugging and status 344 | info -- simply forwards a string to Serial.print(). 345 | @param str String to print to the Serial Console. Must contain its 346 | own newline character if needed. 347 | */ 348 | void OV7670_print(char *str); 349 | 350 | /*! 351 | @brief Reads value of one register from the OV7670 camera over I2C. 352 | This is a C wrapper around the C++ readRegister() function. 353 | @param obj Pointer to Adafruit_OV7670 object (passed down to the C 354 | code on init, now passed back). Adafruit_OV7670 contains 355 | a pointer to a TwoWire object, allowing I2C operations 356 | (via C++ class) in the lower-level C code. 357 | @param reg Register to read, from values defined in src/arch/ov7670.h. 358 | @return Integer value: 0-255 (register contents) on successful read, 359 | -1 on error. 360 | */ 361 | int OV7670_read_register(void *obj, uint8_t reg); 362 | 363 | /*! 364 | @brief Writes value of one register to the OV7670 camera over I2C. 365 | This is a C wrapper around the C++ writeRegister() function. 366 | @param obj Pointer to Adafruit_OV7670 object (passed down to the C 367 | code on init, now passed back). Adafruit_OV7670 contains 368 | a pointer to a TwoWire object, allowing I2C operations 369 | (via C++ class) in the lower-level C code. 370 | @param reg Register to read, from values defined in src/arch/ov7670.h. 371 | @param value Value to write, 0-255. 372 | 373 | */ 374 | void OV7670_write_register(void *obj, uint8_t reg, uint8_t value); 375 | 376 | }; // end extern "C" 377 | -------------------------------------------------------------------------------- /src/SPIBrute.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // This class handles fast-as-possible writes to SPI, using direct register 6 | // accesses. It's used for TFT updates in the camera examples when 7 | // USE_SPI_DMA is not enabled in Adafruit_SPITFT.h (part of Adafruit_GFX). 8 | // DMA is only enabled by default on certain boards, mostly ones with 9 | // built-in screens (e.g. PyPortal), so it's worth editing if testing on 10 | // Grand Central, etc. The mentions of SERCOMs and registers in this code 11 | // are entirely SAMD-specific. 12 | 13 | #if defined(__SAMD51__) // SAMD51 only 14 | 15 | #include "SPIBrute.h" 16 | 17 | // Constructor. Pass SPIClass pointer (e.g. &SPI). 18 | SPIBrute::SPIBrute(SPIClass *s) : spi(s) {} 19 | 20 | // Call this function after the SPI peripheral has been started 21 | // (can't do this in the constructor). 22 | void SPIBrute::begin(void) { 23 | // List of SERCOM base addresses by SERCOM # 24 | static Sercom *sercomBase[] = { 25 | SERCOM0, 26 | SERCOM1, 27 | SERCOM2, 28 | SERCOM3, 29 | #if defined(SERCOM4) 30 | SERCOM4, 31 | #endif 32 | #if defined(SERCOM5) 33 | SERCOM5, 34 | #endif 35 | #if defined(SERCOM6) 36 | SERCOM6, 37 | #endif 38 | #if defined(SERCOM7) 39 | SERCOM7, 40 | #endif 41 | }; 42 | 43 | // There's no direct route from an SPI peripheral instance to a 44 | // SERCOM base pointer. There IS a function to get the INDEX of 45 | // a SERCOM associated with an SPI instance, so we use that, plus 46 | // the list above, to get the screen's SERCOM base address. 47 | sercom = sercomBase[spi->getSercomIndex()]; 48 | // We're done with the value of spi at this point. spi and sercom 49 | // are a union, so this overwrites one with the other. 50 | } 51 | 52 | // Brute force (non-DMA) fast SPI writing function, accesses SPI registers 53 | // directly. This is "mostly blocking" except for the last byte out. Call 54 | // wait() before performing another write() or any action on the SPI bus. 55 | void SPIBrute::write(uint8_t *addr, uint32_t len) { 56 | // Manual byte-by-byte SPI transfer. Have tested & confirmed on scope 57 | // that there is no inter-byte gap when running this loop...it's as fast 58 | // as can be. Tried 32-bit SPI transfers...wasn't any faster, in fact 59 | // required small delays when switching between 32- and 8-bit for TFT 60 | // setAddrWindow() calls. So keep this as-is... 61 | while (len--) { 62 | while (sercom->SPI.INTFLAG.bit.DRE == 0) 63 | ; // Wait for Data Register Empty 64 | sercom->SPI.DATA.reg = *addr++; // Write next byte 65 | } 66 | } 67 | 68 | // write() is not entirely blocking. Call wait() before more writes. 69 | void SPIBrute::wait(void) { 70 | while (sercom->SPI.INTFLAG.bit.DRE == 0) 71 | ; // Wait for Data Register Empty 72 | } 73 | 74 | #endif // __SAMD51__ 75 | -------------------------------------------------------------------------------- /src/SPIBrute.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | #if defined(__SAMD51__) // SAMD51 only 7 | #include 8 | #define USE_SPI_BRUTE 9 | 10 | class SPIBrute { 11 | public: 12 | SPIBrute(SPIClass *s); 13 | void begin(void); 14 | void wait(void); 15 | void write(uint8_t *addr, uint32_t len); 16 | 17 | private: 18 | union { 19 | SPIClass *spi; ///< Before begin(), holds SPI class pointer 20 | Sercom *sercom; ///< After begin(), holds SERCOM base address 21 | }; 22 | }; 23 | 24 | #endif // __SAMD51__ 25 | -------------------------------------------------------------------------------- /src/arch/samd51.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | // This is the SAMD51-specific parts of OV7670 camera interfacing (device- 6 | // agnostic parts are in ov7670.c). It configures and accesses hardware- 7 | // specific peripherals (timer PWM and the parallel capture controller). 8 | // It is mostly, but not entirely, platform agnostic. If compiling for the 9 | // Arduino platform, the pin MUXing function pinPeripheral() is used, and 10 | // other platforms will require different code or function calls in those 11 | // spots (commented throughout). Unlike some of the trivially-mapped 12 | // functions declared at the start of ov7670.h (e.g. OV7670_delay_ms()), 13 | // pinPeripheral() AND ESPECIALLY ITS ARGUMENTS are innately SAMD51- 14 | // Arduino-centric and it doesn't make sense to try to build a whole 15 | // platform-agnostic remap around that...instead, call or implement pin 16 | // MUXing functions "manually" in those few spots. 17 | 18 | #if defined(__SAMD51__) 19 | #include "ov7670.h" 20 | 21 | #if defined(ARDUINO) 22 | #include "wiring_private.h" // pinPeripheral() function 23 | #endif 24 | 25 | // Each supported architecture MUST provide this function with this name, 26 | // arguments and return type. It receives a pointer to a structure with 27 | // at least a list of pins, and usually additional device-specific data 28 | // attached to the 'arch' element. 29 | // SAMD51 host config sets up timer and parallel capture peripheral, 30 | // as these are mostly low-level register twiddles. It does NOT set up 31 | // DMA transfers, handled in higher-level calling code if needed. 32 | OV7670_status OV7670_arch_begin(OV7670_host *host) { 33 | 34 | // LOOK UP TIMER OR TCC BASED ON ADDRESS IN HOST STRUCT ------------------ 35 | 36 | static const struct { 37 | void *base; ///< TC or TCC peripheral base address 38 | uint8_t GCLK_ID; ///< Timer ID for GCLK->PCHCTRL 39 | const char *name; ///< Printable timer ID for debug use 40 | } timer[] = { 41 | #if defined(TC0) 42 | {TC0, TC0_GCLK_ID, "TC0"}, 43 | #endif 44 | #if defined(TC1) 45 | {TC1, TC1_GCLK_ID, "TC1"}, 46 | #endif 47 | #if defined(TC2) 48 | {TC2, TC2_GCLK_ID, "TC2"}, 49 | #endif 50 | #if defined(TC3) 51 | {TC3, TC3_GCLK_ID, "TC3"}, 52 | #endif 53 | #if defined(TC4) 54 | {TC4, TC4_GCLK_ID, "TC4"}, 55 | #endif 56 | #if defined(TC5) 57 | {TC5, TC5_GCLK_ID, "TC5"}, 58 | #endif 59 | #if defined(TC6) 60 | {TC6, TC6_GCLK_ID, "TC6"}, 61 | #endif 62 | #if defined(TC7) 63 | {TC7, TC7_GCLK_ID, "TC7"}, 64 | #endif 65 | #if defined(TC8) 66 | {TC8, TC8_GCLK_ID, "TC8"}, 67 | #endif 68 | #if defined(TC9) 69 | {TC9, TC9_GCLK_ID, "TC9"}, 70 | #endif 71 | #if defined(TC10) 72 | {TC10, TC10_GCLK_ID, "TC10"}, 73 | #endif 74 | #if defined(TC11) 75 | {TC11, TC11_GCLK_ID, "TC11"}, 76 | #endif 77 | #if defined(TC12) 78 | {TC12, TC12_GCLK_ID, "TC12"}, 79 | #endif 80 | {NULL, 0, NULL}, // NULL separator between TC and TCC lists 81 | #if defined(TCC0) 82 | {TCC0, TCC0_GCLK_ID, "TCC0"}, 83 | #endif 84 | #if defined(TCC1) 85 | {TCC1, TCC1_GCLK_ID, "TCC1"}, 86 | #endif 87 | #if defined(TCC2) 88 | {TCC2, TCC2_GCLK_ID, "TCC2"}, 89 | #endif 90 | #if defined(TCC3) 91 | {TCC3, TCC3_GCLK_ID, "TCC3"}, 92 | #endif 93 | #if defined(TCC4) 94 | {TCC4, TCC4_GCLK_ID, "TCC4"}, 95 | #endif 96 | }; 97 | 98 | uint8_t timer_list_index; 99 | bool is_tcc = false; // Initial part of list is timer/counters, not TCCs 100 | // Scan timer[] list until a matching timer/TCC is found... 101 | for (timer_list_index = 0; 102 | (timer_list_index < sizeof timer / sizeof timer[0]) && 103 | (timer[timer_list_index].base != host->arch->timer); 104 | timer_list_index++) { 105 | if (!timer[timer_list_index].base) { // NULL separator? 106 | is_tcc = true; // In the TCC (not TC) part of the list now 107 | } 108 | } 109 | if (timer_list_index >= sizeof timer / sizeof timer[0]) { 110 | return OV7670_STATUS_ERR_PERIPHERAL; // No matching TC/TCC found 111 | } 112 | 113 | // CONFIGURE TIMER FOR XCLK OUT ------------------------------------------ 114 | 115 | uint8_t id = timer[timer_list_index].GCLK_ID; 116 | 117 | // Route timer's peripheral channel control to GCLK1 (48 MHz) 118 | 119 | GCLK->PCHCTRL[id].bit.CHEN = 0; // Peripheral channel disable 120 | while (GCLK->PCHCTRL[id].bit.CHEN) // Wait for disable 121 | ; 122 | // Select generator 1, enable channel, use .reg so it's an atomic op 123 | GCLK->PCHCTRL[id].reg = GCLK_PCHCTRL_GEN_GCLK1 | GCLK_PCHCTRL_CHEN; 124 | while (!GCLK->PCHCTRL[id].bit.CHEN) // Wait for enable 125 | ; 126 | 127 | if (is_tcc) { // Is a TCC peripheral 128 | 129 | Tcc *tcc = (Tcc *)timer[timer_list_index].base; 130 | 131 | tcc->CTRLA.bit.ENABLE = 0; // Disable TCC before configuring 132 | while (tcc->SYNCBUSY.bit.ENABLE) 133 | ; 134 | tcc->CTRLA.bit.PRESCALER = TCC_CTRLA_PRESCALER_DIV1_Val; // 1:1 Prescale 135 | tcc->WAVE.bit.WAVEGEN = TCC_WAVE_WAVEGEN_NPWM_Val; // Normal PWM 136 | while (tcc->SYNCBUSY.bit.WAVE) 137 | ; 138 | 139 | uint16_t period = 48000000 / OV7670_XCLK_HZ - 1; 140 | tcc->PER.bit.PER = period; 141 | while (tcc->SYNCBUSY.bit.PER) 142 | ; 143 | tcc->CC[1].bit.CC = (period + 1) / 2; // Aim for ~50% duty cycle 144 | while (tcc->SYNCBUSY.bit.CC1) 145 | ; 146 | tcc->CTRLA.bit.ENABLE = 1; 147 | while (tcc->SYNCBUSY.bit.ENABLE) 148 | ; 149 | 150 | #if defined(ARDUINO) 151 | pinPeripheral(host->pins->xclk, 152 | host->arch->xclk_pdec ? PIO_TCC_PDEC : PIO_TIMER_ALT); 153 | #else 154 | // CircuitPython, etc. pin mux here 155 | #endif 156 | 157 | } else { // Is a TC peripheral 158 | 159 | Tc *tc = (Tc *)timer[timer_list_index].base; 160 | 161 | // TO DO: ADD TC PERIPHERAL (NOT TCC) CODE HERE 162 | 163 | #if defined(ARDUINO) 164 | pinPeripheral(host->pins->xclk, PIO_TIMER); 165 | #else 166 | // CircuitPython, etc. pin mux here 167 | #endif 168 | 169 | } // end TC/TCC 170 | 171 | // SET UP PCC PERIPHERAL ------------------------------------------------- 172 | 173 | PCC->MR.bit.PCEN = 0; // Make sure PCC is disabled before setting MR reg 174 | 175 | PCC->IDR.reg = 0b1111; // Disable all PCC interrupts 176 | MCLK->APBDMASK.bit.PCC_ = 1; // Enable PCC clock 177 | 178 | // Set up pin MUXes for the camera clock, sync and data pins. 179 | // I2C pins are already configured, XCLK was done above, and 180 | // the reset and enable pins are handled in the calling code. 181 | // Must do this before setting MR reg. 182 | #if defined(ARDUINO) 183 | // PCC pins are set in stone on SAMD51 184 | pinPeripheral(PIN_PCC_CLK, PIO_PCC); 185 | pinPeripheral(PIN_PCC_DEN1, PIO_PCC); // VSYNC 186 | pinPeripheral(PIN_PCC_DEN2, PIO_PCC); // HSYNC 187 | pinPeripheral(PIN_PCC_D0, PIO_PCC); 188 | pinPeripheral(PIN_PCC_D1, PIO_PCC); 189 | pinPeripheral(PIN_PCC_D2, PIO_PCC); 190 | pinPeripheral(PIN_PCC_D3, PIO_PCC); 191 | pinPeripheral(PIN_PCC_D4, PIO_PCC); 192 | pinPeripheral(PIN_PCC_D5, PIO_PCC); 193 | pinPeripheral(PIN_PCC_D6, PIO_PCC); 194 | pinPeripheral(PIN_PCC_D7, PIO_PCC); 195 | #else 196 | // CircuitPython, etc. pin mux here 197 | #endif 198 | 199 | // Accumulate 4 bytes into RHR register (two 16-bit pixels) 200 | PCC->MR.reg = PCC_MR_CID(0x1) | // Clear on falling DEN1 (VSYNC) 201 | PCC_MR_ISIZE(0x0) | // Input data bus is 8 bits 202 | PCC_MR_DSIZE(0x2); // "4 data" at a time (accumulate in RHR) 203 | 204 | PCC->MR.bit.PCEN = 1; // Enable PCC 205 | 206 | return OV7670_STATUS_OK; 207 | } 208 | 209 | // Non-DMA capture function using previously-initialized PCC peripheral. 210 | void OV7670_capture(uint32_t *dest, uint16_t width, uint16_t height, 211 | volatile uint32_t *vsync_reg, uint32_t vsync_bit, 212 | volatile uint32_t *hsync_reg, uint32_t hsync_bit) { 213 | 214 | while (*vsync_reg & vsync_bit) 215 | ; // Wait for VSYNC low (frame end) 216 | OV7670_disable_interrupts(); 217 | while (!*vsync_reg & vsync_bit) 218 | ; // Wait for VSYNC high (frame start) 219 | 220 | width /= 2; // PCC receives 2 pixels at a time 221 | for (uint16_t y = 0; y < height; y++) { // For each row... 222 | while (*hsync_reg & hsync_bit) 223 | ; // Wait for HSYNC low (row end) 224 | while (!*hsync_reg & hsync_bit) 225 | ; // Wait for HSYNC high (row start) 226 | for (int x = 0; x < width; x++) { // For each column pair... 227 | while (!PCC->ISR.bit.DRDY) 228 | ; // Wait for PCC data ready 229 | *dest++ = PCC->RHR.reg; // Store 2 pixels 230 | } 231 | } 232 | 233 | OV7670_enable_interrupts(); 234 | } 235 | 236 | // DEVICE-SPECIFIC FUNCTIONS FOR NON-ARDUINO PLATFORMS --------------------- 237 | 238 | // If a platform doesn't offer a device-agnostic function for certain 239 | // operations (i.e. can't be trivially remapped in ov7670.h as is done for 240 | // Arduino), those device-specific functions can be declared here, with a 241 | // platform-specific #ifdef around them. These functions may include: 242 | // OV7670_delay_ms(x) 243 | // OV7670_pin_output(pin) 244 | // OV7670_pin_write(pin, hi) 245 | // OV7670_disable_interrupts() 246 | // OV7670_enable_interrupts() 247 | // Also see notes at top regarding pin MUXing in this file. 248 | 249 | #endif // end __SAMD51__ 250 | 251 | // Notes from past self: early version of this code that I adopted used 252 | // GCLK5 to provide the XCLK signal to the camera, and was written before 253 | // the Grand Central SAMD51 Arduino definition was fully formed. GCLK5 254 | // became vital for other purposes (looks like it feeds PLLs that time 255 | // nearly everything else). Fortunately there's a timer peripheral (TCC1) 256 | // also available on that same pin, so that's what's now used to provide 257 | // PCC_XCLK. The choice of pin/timer for that will likely be different 258 | // for other SAMD51 boards (e.g. Feather, ItsyBitsy), but as written here 259 | // must be a TC or TCC, not a GCLK source. Also: early code required that 260 | // the cache be disabled. That no longer seems to be necessary, PCC and 261 | // related code runs fine even with cache. 262 | -------------------------------------------------------------------------------- /src/arch/samd51.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | #if defined(__SAMD51__) 7 | #if defined(ARDUINO) 8 | #include 9 | #else 10 | #include 11 | #include 12 | #endif // end platforms 13 | 14 | typedef int8_t OV7670_pin; 15 | 16 | // OV7670 datasheet claims 10-48 MHz clock input, with 24 MHz typical. 17 | // SAMD can do up to 24 MHz if camera connection is super clean. If any 18 | // trouble, try dialing this down to 16 or 12 MHz. Even 8 MHz is OK if 19 | // that's what's available. SAMD timer peripheral as used by this code 20 | // is clocked from a 48 MHz source, so it's always going to be some 21 | // integer divisor of that. OV7670 has internal PLL and can step up from 22 | // lower frequencies (to a point) if needed. 23 | #define OV7670_XCLK_HZ 24000000 ///< XCLK to camera, 8-24 MHz 24 | 25 | // Device-specific structure attached to the OV7670_host.arch pointer. 26 | typedef struct { 27 | void *timer; ///< TC or TCC peripheral base address for XCLK out 28 | bool xclk_pdec; ///< If true, XCLK needs special PDEC pin mux 29 | } OV7670_arch; 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | extern void OV7670_capture(uint32_t *dest, uint16_t width, uint16_t height, 36 | volatile uint32_t *vsync_reg, uint32_t vsync_bit, 37 | volatile uint32_t *hsync_reg, uint32_t hsync_bit); 38 | 39 | #ifdef __cplusplus 40 | }; 41 | #endif 42 | 43 | #endif // __SAMD51__ 44 | -------------------------------------------------------------------------------- /src/arch/samd51_arduino.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | /* 6 | This is the SAMD51- and Arduino-specific parts of OV7670 camera interfacing. 7 | Unlike the SAMD51-specific (but platform-agnostic) code in samd51.c, this 8 | adds a DMA layer. Camera data is continually read and then paused when 9 | needed, reducing capture latency (rather than waiting for a VSYNC before 10 | starting a capture of the next frame, VSYNC instead marks the end of the 11 | prior frame and data is ready). Code for non-DMA use of the SAMD51 PCC 12 | exists in samd51.c, but isn't really used right now, unless we add a 13 | DMA/non-DMA compile- or run-time capability. It's all DMA for now. 14 | 15 | Each architecture will have a .cpp file like this, to tie into the other 16 | Arduino code up a level and provide a couple of missing functions there. 17 | An intentional choice was made NOT to make a subclass for each architecture, 18 | overloaded or virtual functions, etc. even if that might be more Proper 19 | Computer Science(tm). It avoids more per-arch ifdefs in the examples, 20 | avoids an extra .h file to accompany each per-arch .cpp, etc. Instead, 21 | arch-specific constructs are handled through the 'arch' struct which gets 22 | passed around, and needed by the middle-layer C code anyway. Just provide 23 | arch_begin() and capture() in each .cpp and call it done. 24 | */ 25 | 26 | #if defined(__SAMD51__) && defined(ARDUINO) 27 | #include "Adafruit_OV7670.h" 28 | #include "wiring_private.h" // pinPeripheral() function 29 | #include 30 | #include 31 | 32 | // Because interrupts exist outside the class context, but our interrupt 33 | // needs to access to an active ZeroDMA object, a separate ZeroDMA pointer 34 | // is kept (initialized in the begin() function). This does mean that only 35 | // a single OV7670 can be active (probably no big deal, as there's only a 36 | // single parallel capture peripheral). 37 | 38 | static Adafruit_ZeroDMA dma; 39 | static DmacDescriptor *descriptor; ///< DMA descriptor 40 | static volatile bool frameReady = false; // true at end-of-frame 41 | static volatile bool suspended = false; 42 | 43 | // INTERRUPT HANDLING AND RELATED CODE ------------------------------------- 44 | 45 | // Pin interrupt on VSYNC calls this to start DMA transfer (unless suspended). 46 | static void startFrame(void) { 47 | if (!suspended) { 48 | frameReady = false; 49 | (void)dma.startJob(); 50 | } 51 | } 52 | 53 | // End-of-DMA-transfer callback 54 | static void dmaCallback(Adafruit_ZeroDMA *dma) { frameReady = true; } 55 | 56 | // Since ZeroDMA suspend/resume functions don't yet work, these functions 57 | // use static vars to indicate whether to trigger DMA transfers or hold off 58 | // (camera keeps running, data is simply ignored without a DMA transfer). 59 | 60 | // This is NOT a sleep function, it just pauses background DMA. 61 | 62 | void Adafruit_OV7670::suspend(void) { 63 | while (!frameReady) 64 | ; // Wait for current frame to finish loading 65 | suspended = true; // Don't load next frame (camera runs, DMA stops) 66 | } 67 | 68 | // NOT a wake function, just resumes background DMA. 69 | 70 | void Adafruit_OV7670::resume(void) { 71 | frameReady = false; 72 | suspended = false; // Resume DMA transfers 73 | } 74 | 75 | OV7670_status Adafruit_OV7670::arch_begin(OV7670_colorspace colorspace, 76 | OV7670_size size, float fps) { 77 | 78 | // BASE INITIALIZATION (PLATFORM-AGNOSTIC) ------------------------------- 79 | // This calls the device-neutral C init function OV7670_begin(), which in 80 | // turn calls the device-specific C init OV7670_arch_begin() in samd51.c. 81 | // So many layers. It's like an ogre. 82 | 83 | OV7670_host host; 84 | host.arch = &arch; // Point to struct in Adafruit_OV7670 class 85 | host.pins = &pins; // Point to struct in Adafruit_OV7670 class 86 | if (arch_defaults) { 87 | arch.timer = TCC1; // Use default timer 88 | arch.xclk_pdec = false; // and default pin MUX 89 | } 90 | host.platform = this; // Pointer back to Arduino_OV7670 object 91 | 92 | OV7670_status status; 93 | status = OV7670_begin(&host, colorspace, size, fps); 94 | if (status != OV7670_STATUS_OK) { 95 | return status; 96 | } 97 | 98 | // ARDUINO-SPECIFIC EXTRA INITIALIZATION --------------------------------- 99 | // Sets up DMA for the parallel capture controller. 100 | 101 | ZeroDMAstatus dma_status = dma.allocate(); 102 | // To do: handle dma_status here, map to OV7670_status on error 103 | dma.setAction(DMA_TRIGGER_ACTON_BEAT); 104 | dma.setTrigger(PCC_DMAC_ID_RX); 105 | dma.setCallback(dmaCallback); 106 | dma.setPriority(DMA_PRIORITY_3); 107 | 108 | // Use 32-bit PCC transfers (4 bytes accumulate in RHR.reg) 109 | descriptor = dma.addDescriptor((void *)(&PCC->RHR.reg), // Move from here 110 | (void *)buffer, // to here 111 | _width * _height / 2, // this many 112 | DMA_BEAT_SIZE_WORD, // 32-bit words 113 | false, // Don't src++ 114 | true); // Do dest++ 115 | 116 | // A pin FALLING interrupt is used to detect the start of a new frame. 117 | // Seems like the PCC RXBUFF and/or ENDRX interrupts could take care 118 | // of this, but in practice that didn't seem to work. 119 | // DEN1 is the PCC VSYNC pin. 120 | attachInterrupt(PIN_PCC_DEN1, startFrame, FALLING); 121 | 122 | return status; 123 | } 124 | 125 | void Adafruit_OV7670::capture(void) { 126 | volatile uint32_t *vsync_reg, *hsync_reg; 127 | uint32_t vsync_bit, hsync_bit; 128 | 129 | // Note to future self: might add DMA pause-and-return-immediately 130 | // into this function, so it's usable the same for both DMA and 131 | // non-DMA situations. Calling code would still need to resume DMA 132 | // when it's done with the data, wouldn't be completely transparent. 133 | 134 | vsync_reg = &PORT->Group[g_APinDescription[PIN_PCC_DEN1].ulPort].IN.reg; 135 | vsync_bit = 1ul << g_APinDescription[PIN_PCC_DEN1].ulPin; 136 | hsync_reg = &PORT->Group[g_APinDescription[PIN_PCC_DEN2].ulPort].IN.reg; 137 | hsync_bit = 1ul << g_APinDescription[PIN_PCC_DEN2].ulPin; 138 | 139 | OV7670_capture((uint32_t *)buffer, _width, _height, vsync_reg, vsync_bit, 140 | hsync_reg, hsync_bit); 141 | } 142 | 143 | #endif // __SAMD51__ && ARDUINO 144 | -------------------------------------------------------------------------------- /src/image_ops.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | #include "image_ops.h" 6 | 7 | // These functions are preceded by "OV7670" even though they're not tied to 8 | // the camera hardware, just that they're part of this lib. These are not 9 | // in-camera effects, though some might be possible to implement as such. 10 | 11 | // Negative image (avoiding 'invert' terminology as that could be confused 12 | // for an image flip operation, which is a different function). 13 | void OV7670_image_negative(uint16_t *pixels, uint16_t width, uint16_t height) { 14 | // Working 32 bits at a time is slightly faster. This is dirty pool, 15 | // relying on the fact that the camera lib currently only supports 16 | // even image sizes (the pixel count will always be a multiple of 2) 17 | // and that the image buffer, coming from malloc(), will always be 18 | // on a 32-bit-safe boundary. This is one of those operations that 19 | // can probably be implemented through the camera's gamma curve 20 | // settings, and if so this function will go away. 21 | uint32_t *p32 = (uint32_t *)pixels; 22 | uint32_t i, num_pairs = width * height / 2; 23 | for (i = 0; i < num_pairs; i++) { 24 | p32[i] ^= 0xFFFFFFFF; 25 | } 26 | } 27 | 28 | // Binary threshold, output is "black and white" per-channel. Pass in 29 | // threshold level as 0-255, this will be quantized to an appropriate 30 | // range for the colorspace. 31 | void OV7670_image_threshold(OV7670_colorspace space, uint16_t *pixels, 32 | uint16_t width, uint16_t height, 33 | uint8_t threshold) { 34 | uint32_t i, num_pixels = width * height; 35 | if (space == OV7670_COLOR_RGB) { 36 | // Testing RGB thresholds "in place" in the packed RGB565 value 37 | // avoids some bit-shifting on every pixel (just bit masking). 38 | uint16_t rlimit = (threshold >> 3) << 11; // In-place 565 red threshold 39 | uint16_t glimit = (threshold >> 2) << 5; // In-place 565 green threshold 40 | uint16_t blimit = (threshold >> 3); // In-place 565 blue threshold 41 | uint16_t rgb565in, rgb565out; // Packed RGB565 pixel values 42 | for (i = 0; i < num_pixels; i++) { // For each pixel... 43 | rgb565in = __builtin_bswap16(pixels[i]); // Swap endian from cam 44 | rgb565out = 0; // Start with 0 result 45 | if ((rgb565in & 0xF800) >= rlimit) { // If red exceeds limit 46 | rgb565out |= 0xF800; // Set all red bits 47 | } 48 | if ((rgb565in & 0x07E0) >= glimit) { // Ditto, green 49 | rgb565out |= 0x07E0; 50 | } 51 | if ((rgb565in & 0x001F) >= blimit) { // Ditto, blue 52 | rgb565out |= 0x001F; 53 | } 54 | pixels[i] = __builtin_bswap16(rgb565out); // Back to cam-native endian 55 | } 56 | } else { // YUV... 57 | uint8_t *p8 = (uint8_t *)pixels; // Separate Y's, U's, V's 58 | num_pixels *= 2; // Actually num bytes now 59 | for (i = 0; i < num_pixels; i++) { // For each byte... 60 | p8[i] = (p8[i] >= threshold) ? 255 : 0; // Threshold to 0 or 255 61 | } 62 | } 63 | } 64 | 65 | // Reduce color fidelity to a specified number of steps or levels. 66 | void OV7670_image_posterize(OV7670_colorspace space, uint16_t *pixels, 67 | uint16_t width, uint16_t height, uint8_t levels) { 68 | uint32_t i, num_pixels = width * height; 69 | 70 | if (levels < 1) { 71 | levels = 1; 72 | } 73 | uint8_t lm1 = levels - 1; // Values used in fixed- 74 | uint8_t lm1d2 = lm1 / 2; // point interpolation 75 | 76 | if (space == OV7670_COLOR_RGB) { 77 | if (levels >= 32) { 78 | return; 79 | } else { 80 | // Good posterization requires a fair bit of fixed-point math. 81 | // Rather than repeat all those steps over every pixel, lookup tables 82 | // are generated first, and pixels are quickly filtered through these. 83 | // Green is a special case here -- with RGB565 colors, the extra bit 84 | // of green would make for posterization thresholds that are not 85 | // uniform and may have weird halos. So the input is decimated to 86 | // RGB555, posterized, and result scaled to RGB565. 87 | uint16_t rtable[32], gtable[32], rgb; 88 | uint8_t btable[32]; 89 | for (i = 0; i < 32; i++) { // 5 bits each 90 | btable[i] = (((i * levels + lm1d2) / 32) * 31 + lm1d2) / lm1; 91 | rtable[i] = btable[i] << 11; 92 | gtable[i] = (btable[i] << 6) | ((btable[i] & 0x10) << 1); 93 | } 94 | for (i = 0; i < num_pixels; i++) { // For each pixel... 95 | rgb = __builtin_bswap16(pixels[i]); // Data from camera is big-endian 96 | // Dismantle RGB into components, remap each through color table 97 | rgb = rtable[rgb >> 11] | gtable[(rgb >> 6) & 31] | btable[rgb & 31]; 98 | pixels[i] = __builtin_bswap16(rgb); // Back to big-endian 99 | } 100 | } 101 | } else { // YUV 102 | if (levels == 255) { 103 | return; 104 | } else { 105 | uint8_t table[256]; 106 | for (i = 0; i < 256; i++) { 107 | table[i] = (((i * levels + lm1d2) / 256) * 255 + lm1d2) / lm1; 108 | } 109 | uint8_t *p8 = (uint8_t *)pixels; // Separate Ys, Us, Vs 110 | num_pixels *= 2; // Actually num bytes now 111 | for (i = 0; i < num_pixels; i++) { // For each byte... 112 | p8[i] = table[p8[i]]; // Remap through lookup table 113 | } 114 | } 115 | } 116 | } 117 | 118 | // Shower door effect. 119 | void OV7670_image_mosaic(OV7670_colorspace space, uint16_t *pixels, 120 | uint16_t width, uint16_t height, uint8_t tile_width, 121 | uint8_t tile_height) { 122 | if ((tile_width <= 1) && (tile_height <= 1)) { 123 | return; 124 | } 125 | if (tile_width < 1) { 126 | tile_width = 1; 127 | } 128 | if (tile_height < 1) { 129 | tile_height = 1; 130 | } 131 | 132 | uint8_t tiles_across = (width + (tile_width - 1)) / tile_width; 133 | uint8_t tiles_down = (height + (tile_height - 1)) / tile_height; 134 | uint16_t tile_x, tile_y; 135 | uint16_t x1, x2, y1, y2, xx, yy; // Tile bounds, counters 136 | uint32_t pixels_in_tile; 137 | 138 | if (space == OV7670_COLOR_RGB) { 139 | uint16_t rgb; 140 | uint32_t red_sum, green_sum, blue_sum; 141 | uint32_t yy1, yy2; 142 | for (y1 = tile_y = 0; tile_y < tiles_down; tile_y++) { // Each tile row... 143 | y2 = y1 + tile_height - 1; // Last pixel row in current tile row 144 | if (y2 >= height) { // Clip to bottom of image 145 | y2 = height - 1; 146 | } 147 | // Recalc this each tile row because tile x loop may alter it: 148 | pixels_in_tile = tile_width * (y2 - y1 + 1); 149 | for (x1 = tile_x = 0; tile_x < tiles_across; 150 | tile_x++) { // Each tile column... 151 | x2 = x1 + tile_width - 1; // Last pixel column in current tile column 152 | if (x2 >= width) { // Clip to right of image 153 | x2 = width - 1; 154 | pixels_in_tile = (x2 - x1 + 1) * (y2 - y1 + 1); 155 | } 156 | // Accumulate red, green, blue sums for all pixels in tile 157 | red_sum = green_sum = blue_sum = 0; 158 | yy2 = y1 * width; // Index of first pixel in tile 159 | for (yy = y1; yy <= y2; yy++) { // Each pixel row in tile... 160 | for (xx = x1; xx <= x2; xx++) { // Each pixel column in tile... 161 | rgb = __builtin_bswap16(pixels[yy2 + xx]); 162 | red_sum += rgb & 0b1111100000000000; // Accumulate in-place, 163 | green_sum += rgb & 0b0000011111100000; // no shift down needed 164 | blue_sum += rgb & 0b0000000000011111; 165 | } 166 | yy2 += width; // Advance by one image row 167 | } 168 | red_sum = (red_sum / pixels_in_tile) & 0b1111100000000000; 169 | green_sum = (green_sum / pixels_in_tile) & 0b0000011111100000; 170 | blue_sum = (blue_sum / pixels_in_tile) & 0b0000000000011111; 171 | rgb = __builtin_bswap16(red_sum | green_sum | blue_sum); 172 | yy2 = y1 * width; // Index of first pixel in tile 173 | for (xx = x1; xx <= x2; xx++) { // Overwrite top row of tile 174 | pixels[yy2 + xx] = rgb; // with averaged tile value 175 | } 176 | x1 += tile_width; // Advance pixel index by one tile column 177 | } 178 | // Duplicate scanlines to fill tiles on Y axis 179 | yy1 = y1 * width; // Index of first pixel in tile 180 | yy2 = yy1 + width; // Index of pixel one row down 181 | for (yy = y1 + 1; yy <= y2; 182 | yy++) { // Each subsequent pixel row in tiles... 183 | memcpy(&pixels[yy2], &pixels[yy1], width * 2); 184 | yy2 += width; // Advance destination index by one row 185 | } 186 | y1 += tile_height; // Advance pixel index by one tile row 187 | } 188 | } else { // YUV 189 | // YUV is not handled yet because it's weird, with U & V 190 | // on alternating pixels. This will require Some Doing. 191 | } 192 | } 193 | 194 | // 3X3 MEDIAN FILTER -------------------------------------------------------- 195 | 196 | // A median filter helps reduce pixel "snow" in an image while keeping 197 | // larger features intact. The process is pretty heinous (every pixel in 198 | // an image is reassigned the median value of itself and 8 neighboring 199 | // pixels -- separately for red, green and blue). The implementation here 200 | // duplicates edge pixels so output size matches input size (no black 201 | // border or other uglies). A few Clever Tricks(tm) are employed to try 202 | // to speed up the median calculations (the edge detect filter borrows a 203 | // lot of this too, and potentially any other 3x3 filters added later)... 204 | // 205 | // - A buffer is allocated and three rows of the image are unpacked from 206 | // RGB565 format into separate red, green and blue channels, to reduce 207 | // the number of bit-masking and shifting operations needed when 208 | // comparing colors in inner loops. The 3 rows cycle while working down 209 | // the image -- the 'current' row becomes the 'prior' row, the 'next' 210 | // row becomes the 'current' row, and a new 'next' row is converted. 211 | // Requires about 3.6K RAM overhead for a 320x240 pixel image. 212 | // Having these row buffers also means results can be written directly 213 | // back into the 'current' row of the image without corrupting the 214 | // results of 3x3 operations on the subsequent row. 215 | // 216 | // - The format of these rows is Truly Strange, to simplify inner loops 217 | // and potentially to help exploit spatial coherence (though not really 218 | // done here, but possible). For any two adjacent pixels, six of nine 219 | // values used are the same. May still require evaluating all nine 220 | // pixels, but we don't have to load up all nine bins every time. 221 | // Instead of the three rows being in the typical row-major format: 222 | // 0 1 2 3 4 5 223 | // 6 7 8 9 10 11 224 | // 12 13 14 15 16 17 225 | // They're instead in a column-major format, always three rows: 226 | // 0 3 6 9 12 15 227 | // 1 4 7 10 13 16 228 | // 2 5 8 11 14 17 229 | // Loading up the 3x3 pixel list while increasing X by 1 along the row 230 | // then just involves incrementing an index or pointer by 3's...the data 231 | // stays in-place, rather than individually copying nine pixels from 232 | // image space to a working buffer. That is, rather than the 9 pixels 233 | // around pixel 'p[x,y]' involving the following (where 'y-1' and 'y+1' 234 | // each require a multiply by the image width, because of the row-major 235 | // format): 236 | // p[x-1,y-1] p[x,y-1] p[x+1,y-1] 237 | // p[x-1,y] p[x,y] p[x+1,y] 238 | // p[x-1,y+1] p[x,y+1] p[x+1,y+1] 239 | // The nine pixels are instead addressed as: 240 | // p[0] p[3] p[6] 241 | // p[1] p[4] p[7] 242 | // p[2] p[5] p[8] 243 | // It takes some cycles to initially noodle an image into this format, 244 | // but the payoff is avoiding all those extra multiplies and adds inside 245 | // inner loops (as a filter works across each row). 246 | // Weirder though...to increment to the next row, rather than moving the 247 | // 'current' row data to the 'prior' row and the 'next' row data into the 248 | // 'current' row (or keeping a circular set of 3 pointers/indices), the 249 | // pixel buffer has a 'tail' such that we only need to increment the 250 | // buffer start index by 1, then reload the new 'next' row data (with 251 | // the pixel indices incrementing by 3), nothing else moves or changes. 252 | // If it helps to visualize this, picture you have a string (representing 253 | // our pixel noodle buffer) coiled around a wooden rod. The rod has a 254 | // circumference of exactly 3 pixels...so, coiling the string around it, 255 | // all the pixels in each of the 3 rows are aligned, and the horizontal 256 | // pixel positions in the buffer will increment by 3's. Turning the rod 257 | // 1/3 turn (1 pixel) cycles all the rows, and a new row can be loaded 258 | // affecting just those pixels (by 3's). The 'head' and 'tail' on this 259 | // string (equal to the image height) let us make the vertical change 260 | // without having to move or copy any existing data to new locations, 261 | // just load the new bytes. That's why the buffer allocation is somewhat 262 | // more than (width * 3). 263 | // 264 | // 0 <- first byte in buffer 265 | // | 266 | // __|__/_/_/_/_/|__ 267 | // |____/_/_/_/_/____| -> +X = column = increment by 3 268 | // |/ / / / / | | 269 | // | V +Y = row = increment by 1 270 | // | 271 | // N <- last byte in buffer 272 | // 273 | // 274 | // - Back to the median filter specifically: finding the median in a 275 | // 9-element (3x3) list doesn't require sorting the whole list as is 276 | // typically described. It's sufficient to identify the maximum of the 277 | // least five values, or minimum of largest five, same result all around. 278 | // 279 | // Thank you for coming to my TED Talk. 280 | 281 | // Preprocess one row of pixels in preparation for one of the 3x3 filters. 282 | // Input pixels are separated into red, green, blue buffers for quicker 283 | // access during the median calculations (avoids masking/shifting every 284 | // time a pixel is accessed for comparison). Destination buffer has 2 extra 285 | // pixels (1 ea. left and right), as edge pixels are duplicated to allow 286 | // median to operate on all source image pixels, no black border or other 287 | // uglies. Pixels within each channel are not sequential in memory, but 288 | // increment by 3's -- corresponding to the prior, current and next rows. 289 | static void OV7670_filter_row_prep(uint16_t *src, uint8_t *r_dst, 290 | uint16_t width, uint32_t channel_bytes) { 291 | uint8_t *g_dst = &r_dst[channel_bytes]; 292 | uint8_t *b_dst = &g_dst[channel_bytes]; 293 | 294 | uint16_t x, rgb, offset = 3; 295 | for (x = 0; x < width; x++) { // For each pixel in row... 296 | rgb = __builtin_bswap16(*src++); // Packed RGB565 pixel 297 | r_dst[offset] = rgb >> 11; // Extract 5 bits red, 298 | g_dst[offset] = (rgb >> 5) & 0x3F; // 6 bits green, 299 | b_dst[offset] = rgb & 0x1F; // 5 bits blue 300 | offset += 3; 301 | } 302 | r_dst[0] = r_dst[3]; // Duplicate leftmost pixel 303 | g_dst[0] = g_dst[3]; 304 | b_dst[0] = b_dst[3]; 305 | x = offset - 3; 306 | r_dst[offset] = r_dst[x]; // Duplicate rightmost pixel 307 | g_dst[offset] = g_dst[x]; 308 | b_dst[offset] = b_dst[x]; 309 | } 310 | 311 | // Copy a single row in the 3x3 filter weird increment-by-3 pixel format. 312 | static void OV7670_filter_row_copy(uint8_t *r_src, uint8_t *r_dst, 313 | uint16_t width, uint32_t channel_bytes) { 314 | uint8_t *g_src = &r_src[channel_bytes]; 315 | uint8_t *b_src = &g_src[channel_bytes]; 316 | uint8_t *g_dst = &r_dst[channel_bytes]; 317 | uint8_t *b_dst = &g_dst[channel_bytes]; 318 | uint16_t x, offset; 319 | 320 | for (x = offset = 0; x < width; x++, offset += 3) { 321 | r_dst[offset] = r_src[offset]; 322 | g_dst[offset] = g_src[offset]; 323 | b_dst[offset] = b_src[offset]; 324 | } 325 | } 326 | 327 | // Determine median value of 9-element list 328 | static inline uint8_t OV7670_med9(uint8_t *list) { 329 | 330 | // Find median value in a list of 9 (index 0 to 8). Ostensibly, 331 | // conceptually, this would involve sorting the list (ascending or 332 | // descending, doesn't matter) and returning the value at index 4. 333 | // Reality is simpler, it's sufficient to take the max of the 5 334 | // lowest values (or min of the 5 highest), same result. 335 | 336 | // Testing a couple of approaches here. Consider also, instead of 337 | // having these in an inline function, do this directly in the 338 | // median inner loop, with separate red/green/blue variables -- 339 | // should entail fewer loop iterations overall. 340 | 341 | #if 0 // Might be a tiny bit faster at higher optimization settings 342 | 343 | // Need to preserve initial pixel values -- 6 of 9 will carry over 344 | // from one pixel to the next, so don't modify or sort the pixels 345 | // in-place. Making a copy of the list is one option. Different 346 | // option, used here, is to maintain a bitmask of all 9 'available' 347 | // list positions and clear bits as each of the 5 minimums is found 348 | // (returning the last one). 349 | 350 | uint16_t min; 351 | uint8_t min_idx, i, n; 352 | uint16_t active_mask = ~0; // Bitmask of active values (initially all set) 353 | for (n = 0; n < 5; n++) { // Make 5 passes... 354 | min = ~0; // To force initial select of 1st active value 355 | for(i = 0; i < 9; i++) { // For each of 9 values... 356 | // If value #i is less than min and that spot's still active... 357 | if((list[i] < min) && (active_mask & (1 << i))) { 358 | min = list[i]; // Save value of new minimum 359 | min_idx = i; // Save index of new minimum 360 | } 361 | } 362 | active_mask &= ~(1 << min_idx); // Take element min_idx out of running 363 | } 364 | 365 | return min; 366 | 367 | #else // Might be faster at 'normal' optimization settings 368 | 369 | // Need to preserve initial pixel values -- 6 of 9 will carry over 370 | // from one pixel to the next, so don't modify or sort the pixels 371 | // in-place. A copy of the input list is made so elements can be 372 | // shuffled without corrupting the original. 373 | uint8_t buf[9]; 374 | memcpy(buf, list, 9); 375 | 376 | uint8_t n, i, min, min_idx; 377 | 378 | for (n = 0; n < 5; n++) { // 5 min-finding passes 379 | min = buf[n]; // Initial min guess... 380 | min_idx = n; // is at index 'n' (0-4) 381 | for (i = n + 1; i < 9; i++) { // Compare through end of list... 382 | if (buf[i] < min) { // Keep track of 383 | min = buf[i]; // min value and 384 | min_idx = i; // index of min in list 385 | } 386 | } 387 | // Item at min_idx is the nth-least value in list and no longer in 388 | // the running for subsequent passes...replace value currently in 389 | // that position with the value at position n (no swap needed, just 390 | // overwrite, because next pass starts at n+1). In some cases this 391 | // moves a value to itself, that's OK. 392 | buf[min_idx] = buf[n]; 393 | } 394 | 395 | return min; // min of 5th pass (max of 5 least values) 396 | 397 | #endif // end A/B testing 398 | } 399 | 400 | // 3x3 median filter for noise reduction. Even with Clever Optimizations(tm) 401 | // this is a tad slow, it's just the nature of the thing...lots and lots and 402 | // lots of pixel comparisons. Requires a chunk of RAM temporarily, 403 | // ((width + 2) * 3 + height - 1) * 3 bytes, or about 3.6K for a 320x240 404 | // RGB image. YUV is not currently supported. 405 | void OV7670_image_median(OV7670_colorspace space, uint16_t *pixels, 406 | uint16_t width, uint16_t height) { 407 | if (space == OV7670_COLOR_RGB) { 408 | uint8_t *buf; 409 | uint32_t buf_bytes_per_channel = (width + 2) * 3 + height - 1; 410 | if ((buf = (uint8_t *)malloc(buf_bytes_per_channel * 3))) { 411 | uint8_t *rptr = buf; // -> red buffer 412 | uint8_t *gptr = &rptr[buf_bytes_per_channel]; // -> green buffer 413 | uint8_t *bptr = &gptr[buf_bytes_per_channel]; // -> blue buffer 414 | 415 | // For each of the three channel pointers (rptr, gptr, bptr), 416 | // ptr[0] is the first pixel of the row ABOVE the current one, 417 | // ptr[1] is the first pixel of the current row (0 to height-1), 418 | // ptr[2] is the first pixel of the row BELOW the current one. 419 | // Horizontal pixel addresses then increment by 3's...for each 420 | // column (x) in row, pixel x = ptr[x * 3 + n], where n is 0, 1, 2 421 | // for the above, current, and below rows, respectively. 422 | 423 | // Convert pixel data into the initial 'current' (1) row buf 424 | OV7670_filter_row_prep(pixels, &rptr[1], width, buf_bytes_per_channel); 425 | 426 | // Copy pixel data from the initial (1) row to the prior (0) row buf 427 | // (Because edge pixels are repeated so we can 3x3 filter full image) 428 | OV7670_filter_row_copy(&rptr[1], rptr, width + 2, buf_bytes_per_channel); 429 | 430 | uint16_t *ptr = pixels; // Dest pointer, back into source image 431 | uint16_t x, y, offset, rgb; 432 | uint8_t r_med, g_med, b_med; 433 | for (y = 0; y < height; y++) { // For each row of image... 434 | // Set up 'below' row buffer... 435 | if (y < (height - 1)) { // If current row is 0 to height-2 436 | // Convert pixel data into the 'next' (2) row buf 437 | OV7670_filter_row_prep(&pixels[(y + 1) * width], &rptr[2], width, 438 | buf_bytes_per_channel); 439 | } else { // Last row, y = height-1 440 | // Copy pixel data from current (1) row to next (2) row buf 441 | // (Edge pixels are repeated so we can 3x3 filter full image) 442 | OV7670_filter_row_copy(&rptr[1], &rptr[2], width + 2, 443 | buf_bytes_per_channel); 444 | } 445 | 446 | for (x = offset = 0; x < width; x++, offset += 3) { // Each column... 447 | r_med = OV7670_med9(&rptr[offset]); // 3x3 median red 448 | g_med = OV7670_med9(&gptr[offset]); // " green 449 | b_med = OV7670_med9(&bptr[offset]); // " blue 450 | rgb = (r_med << 11) | (g_med << 5) | b_med; // Recombine 565 451 | *ptr++ = __builtin_bswap16(rgb); // back in image 452 | } 453 | rptr++; // Next row 454 | gptr++; 455 | bptr++; 456 | } 457 | 458 | free(buf); 459 | } 460 | } else { // YUV 461 | // Not yet supported. Tricky because of alternating U/V pixels. 462 | } 463 | } 464 | 465 | // EDGE DETECTION ----------------------------------------------------------- 466 | 467 | // Edge detection borrows a lot of code from the median function above... 468 | // same peculiar looped image format, primary change is just the function 469 | // called in the per-pixel loop. 470 | 471 | // Detect edges in 3x3 pixel square. Evaluates difference between current 472 | // pixel and the four pixels above, below, left and right, sets result 'on' 473 | // if any of those 4 exceeds a given threshold. Note to future self: might 474 | // instead evaluate sum-of-four rather than any-of-four. 475 | static inline bool OV7670_edge9(uint8_t *list, uint8_t sensitivity) { 476 | int8_t center = (int16_t)list[4]; // Must be signed! 477 | return ((abs(center - list[1]) >= sensitivity) || // left 478 | (abs(center - list[3]) >= sensitivity) || // up 479 | (abs(center - list[5]) >= sensitivity) || // down 480 | (abs(center - list[7]) >= sensitivity)); // right 481 | } 482 | 483 | // Edge detection filter. Requires a chunk of RAM temporarily, 484 | // ((width + 2) * 3 + height - 1) * 3 bytes, or about 3.6K for a 485 | // 320x240 RGB image. YUV is not currently supported. 486 | void OV7670_image_edges(OV7670_colorspace space, uint16_t *pixels, 487 | uint16_t width, uint16_t height, uint8_t sensitivity) { 488 | 489 | if (space == OV7670_COLOR_RGB) { 490 | uint8_t *buf; 491 | uint32_t buf_bytes_per_channel = (width + 2) * 3 + height - 1; 492 | if ((buf = (uint8_t *)malloc(buf_bytes_per_channel * 3))) { 493 | uint8_t *rptr = buf; // -> red buffer 494 | uint8_t *gptr = &rptr[buf_bytes_per_channel]; // -> green buffer 495 | uint8_t *bptr = &gptr[buf_bytes_per_channel]; // -> blue buffer 496 | 497 | // For each of the three channel pointers (rptr, gptr, bptr), 498 | // ptr[0] is the first pixel of the row ABOVE the current one, 499 | // ptr[1] is the first pixel of the current row (0 to height-1), 500 | // ptr[2] is the first pixel of the row BELOW the current one. 501 | // Horizontal pixel addresses then increment by 3's...for each 502 | // column (x) in row, pixel x = ptr[x * 3 + n], where n is 0, 1, 2 503 | // for the above, current, and below rows, respectively. 504 | 505 | // Convert pixel data into the initial 'current' (1) row buf 506 | OV7670_filter_row_prep(pixels, &rptr[1], width, buf_bytes_per_channel); 507 | 508 | // Copy pixel data from the initial (1) row to the prior (0) row buf 509 | // (Because edge pixels are repeated so we can 3x3 filter full image) 510 | OV7670_filter_row_copy(&rptr[1], rptr, width + 2, buf_bytes_per_channel); 511 | 512 | uint8_t s2 = sensitivity * 2; // Because green has extra bit 513 | 514 | uint16_t *ptr = pixels; // Dest pointer, back into source image 515 | uint16_t x, y, offset, rgb; 516 | for (y = 0; y < height; y++) { // For each row of image... 517 | // Set up 'below' row buffer... 518 | if (y < (height - 1)) { // If current row is 0 to height-2 519 | // Convert pixel data into the 'next' (2) row buf 520 | OV7670_filter_row_prep(&pixels[(y + 1) * width], &rptr[2], width, 521 | buf_bytes_per_channel); 522 | } else { // Last row, y = height-1 523 | // Copy pixel data from current (1) row to next (2) row buf 524 | // (Edge pixels are repeated so we can 3x3 filter full image) 525 | OV7670_filter_row_copy(&rptr[1], &rptr[2], width + 2, 526 | buf_bytes_per_channel); 527 | } 528 | 529 | for (x = offset = 0; x < width; x++, offset += 3) { 530 | rgb = ((OV7670_edge9(&rptr[offset], sensitivity) * 0xF800) | 531 | (OV7670_edge9(&gptr[offset], s2) * 0x07E0) | 532 | (OV7670_edge9(&bptr[offset], sensitivity) * 0x001F)); 533 | *ptr++ = __builtin_bswap16(rgb); 534 | } 535 | rptr++; // Next row 536 | gptr++; 537 | bptr++; 538 | } 539 | 540 | free(buf); 541 | } 542 | } else { // YUV 543 | // Not yet supported. Tricky because of alternating U/V pixels. 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /src/image_ops.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | #include "ov7670.h" 7 | #include 8 | 9 | // The "image ops" functions perform postprocessing on a captured OV7670 10 | // image. These are not in-camera effects, though some might be possible 11 | // to implement as such. Image is overwritten -- destination buffer is 12 | // always the same as the source buffer, same dimensions, same colorspace. 13 | 14 | // These are declared in an extern "C" so Arduino platform C++ code can 15 | // access them. 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | // Image invert -- produces a negative image. This could probably be done 22 | // in-camera with a different gamma curve or something, but for now it's 23 | // available as a postprocess filter. Works in RGB and YUV colorspaces. 24 | extern void OV7670_image_negative(uint16_t *pixels, uint16_t width, 25 | uint16_t height); 26 | 27 | // Image threshold -- decimates an image to only its min/max values 28 | // (ostensibly "black and white," but works on color channels separately 29 | // so that's not strictly the case). Works in RGB and YUV colorspaces. 30 | // Might be possible as an in-camera effect using gamma curve. 31 | extern void OV7670_image_threshold(OV7670_colorspace space, uint16_t *pixels, 32 | uint16_t width, uint16_t height, 33 | uint8_t threshold); 34 | 35 | // Image posterize -- decimates an image to a limited number of brightness 36 | // levels -- 2 to 32 levels in RGB colorspace, 2 to 255 levels in YUV. 37 | // As with threshold, color channels are separately processed. 38 | // Might be possible as an in-camera effect using gamma curve. 39 | extern void OV7670_image_posterize(OV7670_colorspace space, uint16_t *pixels, 40 | uint16_t width, uint16_t height, 41 | uint8_t levels); 42 | 43 | // Image mosaic -- or "shower door effect," downsamples an image into 44 | // rectangular "tiles" of selectable width and height, each tile's color 45 | // being the average of all source image pixels within that tile's area. 46 | // For some tile sizes (2x2, 4x4, etc.) it might be more efficient to use 47 | // a lower-resolution camera setting and upscale (if needed) in your own 48 | // code...but this function is easy if you need to maintain a specific 49 | // image size, or want arbitrary X/Y tile sizes. If image size does not 50 | // divide equally by tile size, fractional tiles will always be along the 51 | // right and/or bottom edge(s); top left corner is always a full tile. 52 | extern void OV7670_image_mosaic(OV7670_colorspace space, uint16_t *pixels, 53 | uint16_t width, uint16_t height, 54 | uint8_t tile_width, uint8_t tile_height); 55 | 56 | // 3x3 median filter, WIP, not yet available 57 | extern void OV7670_image_median(OV7670_colorspace space, uint16_t *pixels, 58 | uint16_t width, uint16_t height); 59 | 60 | // Edge detection, WIP, not yet available 61 | extern void OV7670_image_edges(OV7670_colorspace space, uint16_t *pixels, 62 | uint16_t width, uint16_t height, 63 | uint8_t sensitivity); 64 | 65 | #ifdef __cplusplus 66 | }; 67 | #endif 68 | -------------------------------------------------------------------------------- /src/ov7670.c: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | #include "ov7670.h" 6 | 7 | // REQUIRED EXTERN FUNCTIONS ----------------------------------------------- 8 | 9 | // Outside code MUST provide these functions with the same arguments and 10 | // return types (see end of Adafruit_OV7670.cpp for an example). These allow 11 | // basic debug-print and I2C operations without referencing C++ objects or 12 | // accessing specific peripherals from this code. That information is all 13 | // contained in the higher-level calling code, which can set up the platform 14 | // pointer for whatever objects and/or peripherals it needs. Yes this is 15 | // ugly and roundabout, but it makes this part of the code more reusable 16 | // (not having to re-implement for each platform/architecture) and the 17 | // functions in question aren't performance-oriented anyway (typically used 18 | // just once on startup, or during I2C transfers which are slow anyway). 19 | 20 | extern void OV7670_print(char *str); 21 | extern int OV7670_read_register(void *platform, uint8_t reg); 22 | extern void OV7670_write_register(void *platform, uint8_t reg, uint8_t value); 23 | 24 | // UTILITY FUNCTIONS ------------------------------------------------------- 25 | 26 | // Write a 0xFF-terminated list of commands to the camera. 27 | // First argument is a pointer to platform-specific data...e.g. on Arduno, 28 | // this points to a C++ object so we can find our way back to the correct 29 | // I2C peripheral (so this code doesn't have to deal with platform-specific 30 | // I2C calls). 31 | void OV7670_write_list(void *platform, OV7670_command *cmd) { 32 | for (int i = 0; cmd[i].reg <= OV7670_REG_LAST; i++) { 33 | #if 0 // DEBUG 34 | char buf[50]; 35 | sprintf(buf, "Write reg %02X = %02X\n", cmd[i].reg, cmd[i].value); 36 | OV7670_print(buf); 37 | #endif 38 | OV7670_write_register(platform, cmd[i].reg, cmd[i].value); 39 | OV7670_delay_ms(1); // Required, else lockup on init 40 | } 41 | } 42 | 43 | // CAMERA STARTUP ---------------------------------------------------------- 44 | 45 | static const OV7670_command 46 | OV7670_rgb[] = 47 | { 48 | // Manual output format, RGB, use RGB565 and full 0-255 output range 49 | {OV7670_REG_COM7, OV7670_COM7_RGB}, 50 | {OV7670_REG_RGB444, 0}, 51 | {OV7670_REG_COM15, OV7670_COM15_RGB565 | OV7670_COM15_R00FF}, 52 | {0xFF, 0xFF}}, 53 | OV7670_yuv[] = 54 | { 55 | // Manual output format, YUV, use full output range 56 | {OV7670_REG_COM7, OV7670_COM7_YUV}, 57 | {OV7670_REG_COM15, OV7670_COM15_R00FF}, 58 | {0xFF, 0xFF}}, 59 | OV7670_init[] = { 60 | {OV7670_REG_TSLB, OV7670_TSLB_YLAST}, // No auto window 61 | {OV7670_REG_COM10, OV7670_COM10_VS_NEG}, // -VSYNC (req by SAMD PCC) 62 | {OV7670_REG_SLOP, 0x20}, 63 | {OV7670_REG_GAM_BASE, 0x1C}, 64 | {OV7670_REG_GAM_BASE + 1, 0x28}, 65 | {OV7670_REG_GAM_BASE + 2, 0x3C}, 66 | {OV7670_REG_GAM_BASE + 3, 0x55}, 67 | {OV7670_REG_GAM_BASE + 4, 0x68}, 68 | {OV7670_REG_GAM_BASE + 5, 0x76}, 69 | {OV7670_REG_GAM_BASE + 6, 0x80}, 70 | {OV7670_REG_GAM_BASE + 7, 0x88}, 71 | {OV7670_REG_GAM_BASE + 8, 0x8F}, 72 | {OV7670_REG_GAM_BASE + 9, 0x96}, 73 | {OV7670_REG_GAM_BASE + 10, 0xA3}, 74 | {OV7670_REG_GAM_BASE + 11, 0xAF}, 75 | {OV7670_REG_GAM_BASE + 12, 0xC4}, 76 | {OV7670_REG_GAM_BASE + 13, 0xD7}, 77 | {OV7670_REG_GAM_BASE + 14, 0xE8}, 78 | {OV7670_REG_COM8, 79 | OV7670_COM8_FASTAEC | OV7670_COM8_AECSTEP | OV7670_COM8_BANDING}, 80 | {OV7670_REG_GAIN, 0x00}, 81 | {OV7670_COM2_SSLEEP, 0x00}, 82 | {OV7670_REG_COM4, 0x00}, 83 | {OV7670_REG_COM9, 0x20}, // Max AGC value 84 | {OV7670_REG_BD50MAX, 0x05}, 85 | {OV7670_REG_BD60MAX, 0x07}, 86 | {OV7670_REG_AEW, 0x75}, 87 | {OV7670_REG_AEB, 0x63}, 88 | {OV7670_REG_VPT, 0xA5}, 89 | {OV7670_REG_HAECC1, 0x78}, 90 | {OV7670_REG_HAECC2, 0x68}, 91 | {0xA1, 0x03}, // Reserved register? 92 | {OV7670_REG_HAECC3, 0xDF}, // Histogram-based AEC/AGC setup 93 | {OV7670_REG_HAECC4, 0xDF}, 94 | {OV7670_REG_HAECC5, 0xF0}, 95 | {OV7670_REG_HAECC6, 0x90}, 96 | {OV7670_REG_HAECC7, 0x94}, 97 | {OV7670_REG_COM8, OV7670_COM8_FASTAEC | OV7670_COM8_AECSTEP | 98 | OV7670_COM8_BANDING | OV7670_COM8_AGC | 99 | OV7670_COM8_AEC}, 100 | {OV7670_REG_COM5, 0x61}, 101 | {OV7670_REG_COM6, 0x4B}, 102 | {0x16, 0x02}, // Reserved register? 103 | {OV7670_REG_MVFP, 0x07}, // 0x07, 104 | {OV7670_REG_ADCCTR1, 0x02}, 105 | {OV7670_REG_ADCCTR2, 0x91}, 106 | {0x29, 0x07}, // Reserved register? 107 | {OV7670_REG_CHLF, 0x0B}, 108 | {0x35, 0x0B}, // Reserved register? 109 | {OV7670_REG_ADC, 0x1D}, 110 | {OV7670_REG_ACOM, 0x71}, 111 | {OV7670_REG_OFON, 0x2A}, 112 | {OV7670_REG_COM12, 0x78}, 113 | {0x4D, 0x40}, // Reserved register? 114 | {0x4E, 0x20}, // Reserved register? 115 | {OV7670_REG_GFIX, 0x5D}, 116 | {OV7670_REG_REG74, 0x19}, 117 | {0x8D, 0x4F}, // Reserved register? 118 | {0x8E, 0x00}, // Reserved register? 119 | {0x8F, 0x00}, // Reserved register? 120 | {0x90, 0x00}, // Reserved register? 121 | {0x91, 0x00}, // Reserved register? 122 | {OV7670_REG_DM_LNL, 0x00}, 123 | {0x96, 0x00}, // Reserved register? 124 | {0x9A, 0x80}, // Reserved register? 125 | {0xB0, 0x84}, // Reserved register? 126 | {OV7670_REG_ABLC1, 0x0C}, 127 | {0xB2, 0x0E}, // Reserved register? 128 | {OV7670_REG_THL_ST, 0x82}, 129 | {0xB8, 0x0A}, // Reserved register? 130 | {OV7670_REG_AWBC1, 0x14}, 131 | {OV7670_REG_AWBC2, 0xF0}, 132 | {OV7670_REG_AWBC3, 0x34}, 133 | {OV7670_REG_AWBC4, 0x58}, 134 | {OV7670_REG_AWBC5, 0x28}, 135 | {OV7670_REG_AWBC6, 0x3A}, 136 | {0x59, 0x88}, // Reserved register? 137 | {0x5A, 0x88}, // Reserved register? 138 | {0x5B, 0x44}, // Reserved register? 139 | {0x5C, 0x67}, // Reserved register? 140 | {0x5D, 0x49}, // Reserved register? 141 | {0x5E, 0x0E}, // Reserved register? 142 | {OV7670_REG_LCC3, 0x04}, 143 | {OV7670_REG_LCC4, 0x20}, 144 | {OV7670_REG_LCC5, 0x05}, 145 | {OV7670_REG_LCC6, 0x04}, 146 | {OV7670_REG_LCC7, 0x08}, 147 | {OV7670_REG_AWBCTR3, 0x0A}, 148 | {OV7670_REG_AWBCTR2, 0x55}, 149 | {OV7670_REG_MTX1, 0x80}, 150 | {OV7670_REG_MTX2, 0x80}, 151 | {OV7670_REG_MTX3, 0x00}, 152 | {OV7670_REG_MTX4, 0x22}, 153 | {OV7670_REG_MTX5, 0x5E}, 154 | {OV7670_REG_MTX6, 0x80}, // 0x40? 155 | {OV7670_REG_AWBCTR1, 0x11}, 156 | {OV7670_REG_AWBCTR0, 0x9F}, // Or use 0x9E for advance AWB 157 | {OV7670_REG_BRIGHT, 0x00}, 158 | {OV7670_REG_CONTRAS, 0x40}, 159 | {OV7670_REG_CONTRAS_CENTER, 0x80}, // 0x40? 160 | {OV7670_REG_LAST + 1, 0x00}, // End-of-data marker 161 | }; 162 | 163 | OV7670_status OV7670_begin(OV7670_host *host, OV7670_colorspace colorspace, 164 | OV7670_size size, float fps) { 165 | OV7670_status status; 166 | 167 | // I2C must already be set up and running (@ 100 KHz) in calling code 168 | 169 | // Do device-specific (but platform-agnostic) setup. e.g. on SAMD this 170 | // function will fiddle registers to start a timer for XCLK output and 171 | // enable the parallel capture peripheral. 172 | status = OV7670_arch_begin(host); 173 | if (status != OV7670_STATUS_OK) { 174 | return status; 175 | } 176 | 177 | // Unsure of camera startup time from beginning of input clock. 178 | // Let's guess it's similar to tS:REG (300 ms) from datasheet. 179 | OV7670_delay_ms(300); 180 | 181 | // ENABLE AND/OR RESET CAMERA -------------------------------------------- 182 | 183 | if (host->pins->enable >= 0) { // Enable pin defined? 184 | OV7670_pin_output(host->pins->enable) 185 | OV7670_pin_write(host->pins->enable, 0); // PWDN low (enable) 186 | OV7670_delay_ms(300); 187 | } 188 | 189 | if (host->pins->reset >= 0) { // Hard reset pin defined? 190 | OV7670_pin_output(host->pins->reset); 191 | OV7670_pin_write(host->pins->reset, 0); 192 | OV7670_delay_ms(1); 193 | OV7670_pin_write(host->pins->reset, 1); 194 | } else { // Soft reset, doesn't seem reliable, might just need more delay? 195 | OV7670_write_register(host->platform, OV7670_REG_COM7, OV7670_COM7_RESET); 196 | } 197 | OV7670_delay_ms(1); // Datasheet: tS:RESET = 1 ms 198 | 199 | (void)OV7670_set_fps(host->platform, fps); // Timing 200 | if (colorspace == OV7670_COLOR_RGB) { 201 | OV7670_write_list(host->platform, OV7670_rgb); 202 | } else { 203 | OV7670_write_list(host->platform, OV7670_yuv); 204 | } 205 | OV7670_write_list(host->platform, OV7670_init); // Other config 206 | OV7670_set_size(host->platform, size); // Frame size 207 | 208 | OV7670_delay_ms(300); // tS:REG = 300 ms (settling time = 10 frames) 209 | 210 | return OV7670_STATUS_OK; 211 | } 212 | 213 | // MISCELLANY AND CAMERA CONFIG FUNCTIONS ---------------------------------- 214 | 215 | // Configure camera frame rate. Actual resulting frame rate (returned) may 216 | // be different depending on available clock frequencies. Result will only 217 | // exceed input if necessary for minimum supported rate, but this is very 218 | // rare, typically below 1 fps. In all other cases, result will be equal 219 | // or less than the requested rate, up to a maximum of 30 fps (the "or less" 220 | // is because requested fps may be based on other host hardware timing 221 | // constraints (e.g. screen) and rounding up to a closer-but-higher frame 222 | // rate would be problematic). There is no hardcoded set of fixed frame 223 | // rates because it varies with architecture, depending on OV7670_XCLK_HZ. 224 | // If platform is NULL, no registers are set, a fps request/return can be 225 | // evaluated without reconfiguring the camera, or without it even started. 226 | 227 | float OV7670_set_fps(void *platform, float fps) { 228 | 229 | // Pixel clock (PCLK), which determines overall frame rate, is a 230 | // function of XCLK input frequency (OV7670_XCLK_HZ), a PLL multiplier 231 | // and then an integer division factor (1-32). These are the available 232 | // OV7670 PLL ratios: 233 | static const uint8_t pll_ratio[] = {1, 4, 6, 8}; 234 | const uint8_t num_plls = sizeof pll_ratio / sizeof pll_ratio[0]; 235 | 236 | // Constrain frame rate to upper and lower limits 237 | fps = (fps > 30) ? 30 : fps; // Max 30 FPS 238 | float pclk_target = fps * 4000000.0 / 5.0; // Ideal PCLK Hz for target FPS 239 | uint32_t pclk_min = OV7670_XCLK_HZ / 32; // Min PCLK determines min FPS 240 | if (pclk_target < (float)pclk_min) { // If PCLK target is below limit 241 | if (platform) { 242 | OV7670_write_register(platform, OV7670_REG_DBLV, 0); // 1:1 PLL 243 | OV7670_write_register(platform, OV7670_REG_CLKRC, 31); // 1/32 div 244 | } 245 | return (float)(pclk_min * 5 / 4000000); // Return min frame rate 246 | } 247 | 248 | // Find nearest available FPS without going over. This is done in a 249 | // brute-force manner, testing all 127 PLL-up by divide-down permutations 250 | // and tracking the best fit. I’m certain there are shortcuts but was 251 | // having trouble with my math, might revisit later. It's not a huge 252 | // bottleneck...MCUs are fast now, many cases are quickly discarded, and 253 | // this operation is usually done only once on startup (the I2C transfers 254 | // probably take longer). 255 | 256 | uint8_t best_pll = 0; // Index (not value) of best PLL match 257 | uint8_t best_div = 1; // Value of best division factor match 258 | float best_delta = 30.0; // Best requested vs actual FPS (init to "way off") 259 | 260 | for (uint8_t p = 0; p < num_plls; p++) { 261 | uint32_t xclk_pll = OV7670_XCLK_HZ * pll_ratio[p]; // PLL'd freq 262 | uint8_t first_div = p ? 2 : 1; // Min div is 1 for PLL 1:1, else 2 263 | for (uint8_t div = first_div; div <= 32; div++) { 264 | uint32_t pclk_result = xclk_pll / div; // PCLK-up-down permutation 265 | if (pclk_result > pclk_target) { // Exceeds target? 266 | continue; // Skip it 267 | } 268 | float fps_result = (float)pclk_result * 5.0 / 4000000.0; 269 | float delta = fps - fps_result; // How far off? 270 | if (delta < best_delta) { // Best match yet? 271 | best_delta = delta; // Save delta, 272 | best_pll = p; // pll and 273 | best_div = div; // div for later use 274 | } 275 | } 276 | } 277 | 278 | if (platform) { 279 | // Set up DBLV and CLKRC registers with best PLL and div values 280 | if (pll_ratio[best_pll] == best_div) { // If PLL and div are same (1:1) 281 | // Bypass PLL, use external clock directly 282 | OV7670_write_register(platform, OV7670_REG_DBLV, 0); 283 | OV7670_write_register(platform, OV7670_REG_CLKRC, 0x40); 284 | } else { 285 | // Set DBLV[7:6] for PLL, CLKRC[5:0] for div-1 (1-32 stored as 0-31) 286 | OV7670_write_register(platform, OV7670_REG_DBLV, best_pll << 6); 287 | OV7670_write_register(platform, OV7670_REG_CLKRC, best_div - 1); 288 | } 289 | } 290 | 291 | return fps - best_delta; // Return actual frame rate 292 | } 293 | 294 | // Sets up PCLK dividers and sets H/V start/stop window. Rather than 295 | // rolling this into OV7670_set_size(), it's kept separate so test code 296 | // can experiment with different settings to find ideal defaults. 297 | void OV7670_frame_control(void *platform, uint8_t size, uint8_t vstart, 298 | uint16_t hstart, uint8_t edge_offset, 299 | uint8_t pclk_delay) { 300 | uint8_t value; 301 | 302 | // Enable downsampling if sub-VGA, and zoom if 1:16 scale 303 | value = (size > OV7670_SIZE_DIV1) ? OV7670_COM3_DCWEN : 0; 304 | if (size == OV7670_SIZE_DIV16) 305 | value |= OV7670_COM3_SCALEEN; 306 | OV7670_write_register(platform, OV7670_REG_COM3, value); 307 | 308 | // Enable PCLK division if sub-VGA 2,4,8,16 = 0x19,1A,1B,1C 309 | value = (size > OV7670_SIZE_DIV1) ? (0x18 + size) : 0; 310 | OV7670_write_register(platform, OV7670_REG_COM14, value); 311 | 312 | // Horiz/vert downsample ratio, 1:8 max (H,V are always equal for now) 313 | value = (size <= OV7670_SIZE_DIV8) ? size : OV7670_SIZE_DIV8; 314 | OV7670_write_register(platform, OV7670_REG_SCALING_DCWCTR, value * 0x11); 315 | 316 | // Pixel clock divider if sub-VGA 317 | value = (size > OV7670_SIZE_DIV1) ? (0xF0 + size) : 0x08; 318 | OV7670_write_register(platform, OV7670_REG_SCALING_PCLK_DIV, value); 319 | 320 | // Apply 0.5 digital zoom at 1:16 size (others are downsample only) 321 | value = (size == OV7670_SIZE_DIV16) ? 0x40 : 0x20; // 0.5, 1.0 322 | // Read current SCALING_XSC and SCALING_YSC register values because 323 | // test pattern settings are also stored in those registers and we 324 | // don't want to corrupt anything there. 325 | uint8_t xsc = OV7670_read_register(platform, OV7670_REG_SCALING_XSC); 326 | uint8_t ysc = OV7670_read_register(platform, OV7670_REG_SCALING_YSC); 327 | xsc = (xsc & 0x80) | value; // Modify only scaling bits (not test pattern) 328 | ysc = (ysc & 0x80) | value; 329 | // Write modified result back to SCALING_XSC and SCALING_YSC 330 | OV7670_write_register(platform, OV7670_REG_SCALING_XSC, xsc); 331 | OV7670_write_register(platform, OV7670_REG_SCALING_YSC, ysc); 332 | 333 | // Window size is scattered across multiple registers. 334 | // Horiz/vert stops can be automatically calc'd from starts. 335 | uint16_t vstop = vstart + 480; 336 | uint16_t hstop = (hstart + 640) % 784; 337 | OV7670_write_register(platform, OV7670_REG_HSTART, hstart >> 3); 338 | OV7670_write_register(platform, OV7670_REG_HSTOP, hstop >> 3); 339 | OV7670_write_register(platform, OV7670_REG_HREF, 340 | (edge_offset << 6) | ((hstop & 0b111) << 3) | 341 | (hstart & 0b111)); 342 | OV7670_write_register(platform, OV7670_REG_VSTART, vstart >> 2); 343 | OV7670_write_register(platform, OV7670_REG_VSTOP, vstop >> 2); 344 | OV7670_write_register(platform, OV7670_REG_VREF, 345 | ((vstop & 0b11) << 2) | (vstart & 0b11)); 346 | 347 | OV7670_write_register(platform, OV7670_REG_SCALING_PCLK_DELAY, pclk_delay); 348 | } 349 | 350 | void OV7670_set_size(void *platform, OV7670_size size) { 351 | // Array of five window settings, index of each (0-4) aligns with the five 352 | // OV7670_size enumeration values. If enum changes, list must change! 353 | static struct { 354 | uint8_t vstart; 355 | uint8_t hstart; 356 | uint8_t edge_offset; 357 | uint8_t pclk_delay; 358 | } window[] = { 359 | // Window settings were tediously determined empirically. 360 | // I hope there's a formula for this, if a do-over is needed. 361 | {9, 162, 2, 2}, // SIZE_DIV1 640x480 VGA 362 | {10, 174, 4, 2}, // SIZE_DIV2 320x240 QVGA 363 | {11, 186, 2, 2}, // SIZE_DIV4 160x120 QQVGA 364 | {12, 210, 0, 2}, // SIZE_DIV8 80x60 ... 365 | {15, 252, 3, 2}, // SIZE_DIV16 40x30 366 | }; 367 | 368 | OV7670_frame_control(platform, size, window[size].vstart, window[size].hstart, 369 | window[size].edge_offset, window[size].pclk_delay); 370 | } 371 | 372 | // Select one of the camera's night modes (or disable). 373 | // Trades off frame rate for less grainy images in low light. 374 | // Note: seems that frame rate is somewhat automatic despite 375 | // the requested setting, i.e. if 1:8 is selected, might 376 | // still get normal frame rate or something higher than 377 | // 1:8, if the scene lighting permits. Also the setting 378 | // seems to 'stick' in some cases when trying to turn 379 | // this off. Might want to try always having night mode 380 | // enabled but using 1:1 frame setting as 'off'. 381 | void OV7670_night(void *platform, OV7670_night_mode night) { 382 | // Table of bit patterns for the different supported night modes. 383 | // There's a "same frame rate" option for OV7670 night mode but it 384 | // doesn't seem to do anything useful and can be skipped over. 385 | static const uint8_t night_bits[] = {0b00000000, 0b10100000, 0b11000000, 386 | 0b11100000}; 387 | // Read current COM11 register setting so unrelated bits aren't corrupted 388 | uint8_t com11 = OV7670_read_register(platform, OV7670_REG_COM11); 389 | com11 &= 0b00011111; // Clear night mode bits 390 | com11 |= night_bits[night]; // Set night bits for desired mode 391 | // Write modified result back to COM11 register 392 | OV7670_write_register(platform, OV7670_REG_COM11, com11); 393 | } 394 | 395 | // Flips camera output on horizontal and/or vertical axes. 396 | // Note: datasheet refers to horizontal flip as "mirroring," but 397 | // avoiding that terminology here that it might be mistaken for a 398 | // split-down-middle-and-reflect funhouse effect, which it isn't. 399 | // Also note: mirrored image isn't always centered quite the same, 400 | // looks like frame control settings might need to be tweaked 401 | // depending on flips. Similar issue to color bars? 402 | void OV7670_flip(void *platform, bool flip_x, bool flip_y) { 403 | // Read current MVFP register setting, so we don't corrupt any 404 | // reserved bits or the "black sun" bit if it was previously set. 405 | uint8_t mvfp = OV7670_read_register(platform, OV7670_REG_MVFP); 406 | if (flip_x) { 407 | mvfp |= OV7670_MVFP_MIRROR; // Horizontal flip 408 | } else { 409 | mvfp &= ~OV7670_MVFP_MIRROR; 410 | } 411 | if (flip_y) { 412 | mvfp |= OV7670_MVFP_VFLIP; // Vertical flip 413 | } else { 414 | mvfp &= ~OV7670_MVFP_VFLIP; 415 | } 416 | // Write modified result back to MVFP register 417 | OV7670_write_register(platform, OV7670_REG_MVFP, mvfp); 418 | } 419 | 420 | // Selects one of the camera's test patterns (or disable). 421 | // See Adafruit_OV7670.h for notes about minor visual bug here. 422 | void OV7670_test_pattern(void *platform, OV7670_pattern pattern) { 423 | // Read current SCALING_XSC and SCALING_YSC register settings, 424 | // so image scaling settings aren't corrupted. 425 | uint8_t xsc = OV7670_read_register(platform, OV7670_REG_SCALING_XSC); 426 | uint8_t ysc = OV7670_read_register(platform, OV7670_REG_SCALING_YSC); 427 | if (pattern & 1) { 428 | xsc |= 0x80; 429 | } else { 430 | xsc &= ~0x80; 431 | } 432 | if (pattern & 2) { 433 | ysc |= 0x80; 434 | } else { 435 | ysc &= ~0x80; 436 | } 437 | // Write modified results back to SCALING_XSC and SCALING_YSC registers 438 | OV7670_write_register(platform, OV7670_REG_SCALING_XSC, xsc); 439 | OV7670_write_register(platform, OV7670_REG_SCALING_YSC, ysc); 440 | } 441 | 442 | // Reformat YUV gray component to RGB565 for TFT preview. 443 | // Big-endian in and out. 444 | void OV7670_Y2RGB565(uint16_t *ptr, uint32_t len) { 445 | while (len--) { 446 | uint8_t y = *ptr & 0xFF; // Y (brightness) component of YUV 447 | uint16_t rgb = ((y >> 3) * 0x801) | ((y & 0xFC) << 3); // to RGB565 448 | *ptr++ = __builtin_bswap16(rgb); // Big-endianify RGB565 for TFT 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /src/ov7670.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 P Burgess for Adafruit Industries 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | 7 | // IMPORTANT: #include ALL of the arch-specific .h files here. 8 | // They have #ifdef checks to only take effect on the active architecture. 9 | #include "arch/samd51.h" 10 | 11 | #if defined(ARDUINO) 12 | #include 13 | #define OV7670_delay_ms(x) delay(x) 14 | #define OV7670_pin_output(pin) pinMode(pin, OUTPUT); 15 | #define OV7670_pin_write(pin, hi) digitalWrite(pin, hi ? 1 : 0) 16 | #define OV7670_disable_interrupts() noInterrupts() 17 | #define OV7670_enable_interrupts() interrupts() 18 | #else 19 | #include 20 | // If platform provides device-agnostic functions for millisecond delay, 21 | // set-pin-to-output, pin-write and/or interrupts on/off, those can be 22 | // #defined here as with Arduino above. If platform does NOT provide some 23 | // or all of these, they should go in the device-specific .c file with a 24 | // !defined(ARDUINO) around them. 25 | #endif // end platforms 26 | 27 | /** Status codes returned by some functions */ 28 | typedef enum { 29 | OV7670_STATUS_OK = 0, ///< Success 30 | OV7670_STATUS_ERR_MALLOC, ///< malloc() call failed 31 | OV7670_STATUS_ERR_PERIPHERAL, ///< Peripheral (e.g. timer) not found 32 | } OV7670_status; 33 | 34 | /** Supported color formats */ 35 | typedef enum { 36 | OV7670_COLOR_RGB = 0, ///< RGB565 big-endian 37 | OV7670_COLOR_YUV, ///< YUV/YCbCr 4:2:2 big-endian 38 | } OV7670_colorspace; 39 | 40 | /** Supported sizes (VGA division factor) for OV7670_set_size() */ 41 | typedef enum { 42 | OV7670_SIZE_DIV1 = 0, ///< 640 x 480 43 | OV7670_SIZE_DIV2, ///< 320 x 240 44 | OV7670_SIZE_DIV4, ///< 160 x 120 45 | OV7670_SIZE_DIV8, ///< 80 x 60 46 | OV7670_SIZE_DIV16, ///< 40 x 30 47 | } OV7670_size; 48 | 49 | typedef enum { 50 | OV7670_TEST_PATTERN_NONE = 0, ///< Disable test pattern 51 | OV7670_TEST_PATTERN_SHIFTING_1, ///< "Shifting 1" pattern 52 | OV7670_TEST_PATTERN_COLOR_BAR, ///< 8 color bars 53 | OV7670_TEST_PATTERN_COLOR_BAR_FADE, ///< Color bars w/fade to white 54 | } OV7670_pattern; 55 | 56 | typedef enum { 57 | OV7670_NIGHT_MODE_OFF = 0, ///< Disable night mode 58 | OV7670_NIGHT_MODE_2, ///< Night mode 1/2 frame rate 59 | OV7670_NIGHT_MODE_4, ///< Night mode 1/4 frame rate 60 | OV7670_NIGHT_MODE_8, ///< Night mode 1/8 frame rate 61 | } OV7670_night_mode; 62 | 63 | /** 64 | Defines physical connection to OV7670 camera, passed to constructor. 65 | On certain architectures, some of these pins are fixed in hardware and 66 | cannot be changed, e.g. SAMD51 has its Parallel Capture Controller in 67 | one specific place. In such cases, you can usually avoid declaring those 68 | values when setting up the structure. However, elements DO need to be 69 | declared in-order matching the structure and without gaps, and struct 70 | elements have been sorted with this in mind. You can cut off a declaration 71 | early, but if middle elements aren't needed must still be assigned some 72 | unused value (e.g. 0). On Arduino platform, SDA/SCL are dictated by the 73 | Wire instance and don't need to be set here. See example code. 74 | */ 75 | typedef struct { 76 | OV7670_pin enable; ///< Also called PWDN, or set to -1 and tie to GND 77 | OV7670_pin reset; ///< Cam reset, or set to -1 and tie to 3.3V 78 | OV7670_pin xclk; ///< MCU clock out / cam clock in 79 | OV7670_pin pclk; ///< Cam clock out / MCU clock in 80 | OV7670_pin vsync; ///< Also called DEN1 81 | OV7670_pin hsync; ///< Also called DEN2 82 | OV7670_pin data[8]; ///< Camera parallel data out 83 | OV7670_pin sda; ///< I2C data 84 | OV7670_pin scl; ///< I2C clock 85 | } OV7670_pins; 86 | 87 | /** Address/value combo for OV7670 camera commands. */ 88 | typedef struct { 89 | uint8_t reg; ///< Register address 90 | uint8_t value; ///< Value to store 91 | } OV7670_command; 92 | 93 | /** Architecture+platform combination structure. */ 94 | typedef struct { 95 | OV7670_arch *arch; ///< Architecture-specific config data 96 | OV7670_pins *pins; ///< Physical connection to camera 97 | void *platform; ///< Platform-specific data (e.g. Arduino C++ object) 98 | } OV7670_host; 99 | 100 | #define OV7670_ADDR 0x21 //< Default I2C address if unspecified 101 | 102 | #define OV7670_REG_GAIN 0x00 //< AGC gain bits 7:0 (9:8 in VREF) 103 | #define OV7670_REG_BLUE 0x01 //< AWB blue channel gain 104 | #define OV7670_REG_RED 0x02 //< AWB red channel gain 105 | #define OV7670_REG_VREF 0x03 //< Vert frame control bits 106 | #define OV7670_REG_COM1 0x04 //< Common control 1 107 | #define OV7670_COM1_R656 0x40 //< COM1 enable R656 format 108 | #define OV7670_REG_BAVE 0x05 //< U/B average level 109 | #define OV7670_REG_GbAVE 0x06 //< Y/Gb average level 110 | #define OV7670_REG_AECHH 0x07 //< Exposure value - AEC 15:10 bits 111 | #define OV7670_REG_RAVE 0x08 //< V/R average level 112 | #define OV7670_REG_COM2 0x09 //< Common control 2 113 | #define OV7670_COM2_SSLEEP 0x10 //< COM2 soft sleep mode 114 | #define OV7670_REG_PID 0x0A //< Product ID MSB (read-only) 115 | #define OV7670_REG_VER 0x0B //< Product ID LSB (read-only) 116 | #define OV7670_REG_COM3 0x0C //< Common control 3 117 | #define OV7670_COM3_SWAP 0x40 //< COM3 output data MSB/LSB swap 118 | #define OV7670_COM3_SCALEEN 0x08 //< COM3 scale enable 119 | #define OV7670_COM3_DCWEN 0x04 //< COM3 DCW enable 120 | #define OV7670_REG_COM4 0x0D //< Common control 4 121 | #define OV7670_REG_COM5 0x0E //< Common control 5 122 | #define OV7670_REG_COM6 0x0F //< Common control 6 123 | #define OV7670_REG_AECH 0x10 //< Exposure value 9:2 124 | #define OV7670_REG_CLKRC 0x11 //< Internal clock 125 | #define OV7670_CLK_EXT 0x40 //< CLKRC Use ext clock directly 126 | #define OV7670_CLK_SCALE 0x3F //< CLKRC Int clock prescale mask 127 | #define OV7670_REG_COM7 0x12 //< Common control 7 128 | #define OV7670_COM7_RESET 0x80 //< COM7 SCCB register reset 129 | #define OV7670_COM7_SIZE_MASK 0x38 //< COM7 output size mask 130 | #define OV7670_COM7_PIXEL_MASK 0x05 //< COM7 output pixel format mask 131 | #define OV7670_COM7_SIZE_VGA 0x00 //< COM7 output size VGA 132 | #define OV7670_COM7_SIZE_CIF 0x20 //< COM7 output size CIF 133 | #define OV7670_COM7_SIZE_QVGA 0x10 //< COM7 output size QVGA 134 | #define OV7670_COM7_SIZE_QCIF 0x08 //< COM7 output size QCIF 135 | #define OV7670_COM7_RGB 0x04 //< COM7 pixel format RGB 136 | #define OV7670_COM7_YUV 0x00 //< COM7 pixel format YUV 137 | #define OV7670_COM7_BAYER 0x01 //< COM7 pixel format Bayer RAW 138 | #define OV7670_COM7_PBAYER 0x05 //< COM7 pixel fmt proc Bayer RAW 139 | #define OV7670_COM7_COLORBAR 0x02 //< COM7 color bar enable 140 | #define OV7670_REG_COM8 0x13 //< Common control 8 141 | #define OV7670_COM8_FASTAEC 0x80 //< COM8 Enable fast AGC/AEC algo, 142 | #define OV7670_COM8_AECSTEP 0x40 //< COM8 AEC step size unlimited 143 | #define OV7670_COM8_BANDING 0x20 //< COM8 Banding filter enable 144 | #define OV7670_COM8_AGC 0x04 //< COM8 AGC (auto gain) enable 145 | #define OV7670_COM8_AWB 0x02 //< COM8 AWB (auto white balance) 146 | #define OV7670_COM8_AEC 0x01 //< COM8 AEC (auto exposure) enable 147 | #define OV7670_REG_COM9 0x14 //< Common control 9 - max AGC value 148 | #define OV7670_REG_COM10 0x15 //< Common control 10 149 | #define OV7670_COM10_HSYNC 0x40 //< COM10 HREF changes to HSYNC 150 | #define OV7670_COM10_PCLK_HB 0x20 //< COM10 Suppress PCLK on hblank 151 | #define OV7670_COM10_HREF_REV 0x08 //< COM10 HREF reverse 152 | #define OV7670_COM10_VS_EDGE 0x04 //< COM10 VSYNC chg on PCLK rising 153 | #define OV7670_COM10_VS_NEG 0x02 //< COM10 VSYNC negative 154 | #define OV7670_COM10_HS_NEG 0x01 //< COM10 HSYNC negative 155 | #define OV7670_REG_HSTART 0x17 //< Horiz frame start high bits 156 | #define OV7670_REG_HSTOP 0x18 //< Horiz frame end high bits 157 | #define OV7670_REG_VSTART 0x19 //< Vert frame start high bits 158 | #define OV7670_REG_VSTOP 0x1A //< Vert frame end high bits 159 | #define OV7670_REG_PSHFT 0x1B //< Pixel delay select 160 | #define OV7670_REG_MIDH 0x1C //< Manufacturer ID high byte 161 | #define OV7670_REG_MIDL 0x1D //< Manufacturer ID low byte 162 | #define OV7670_REG_MVFP 0x1E //< Mirror / vert-flip enable 163 | #define OV7670_MVFP_MIRROR 0x20 //< MVFP Mirror image 164 | #define OV7670_MVFP_VFLIP 0x10 //< MVFP Vertical flip 165 | #define OV7670_REG_LAEC 0x1F //< Reserved 166 | #define OV7670_REG_ADCCTR0 0x20 //< ADC control 167 | #define OV7670_REG_ADCCTR1 0x21 //< Reserved 168 | #define OV7670_REG_ADCCTR2 0x22 //< Reserved 169 | #define OV7670_REG_ADCCTR3 0x23 //< Reserved 170 | #define OV7670_REG_AEW 0x24 //< AGC/AEC upper limit 171 | #define OV7670_REG_AEB 0x25 //< AGC/AEC lower limit 172 | #define OV7670_REG_VPT 0x26 //< AGC/AEC fast mode op region 173 | #define OV7670_REG_BBIAS 0x27 //< B channel signal output bias 174 | #define OV7670_REG_GbBIAS 0x28 //< Gb channel signal output bias 175 | #define OV7670_REG_EXHCH 0x2A //< Dummy pixel insert MSB 176 | #define OV7670_REG_EXHCL 0x2B //< Dummy pixel insert LSB 177 | #define OV7670_REG_RBIAS 0x2C //< R channel signal output bias 178 | #define OV7670_REG_ADVFL 0x2D //< Insert dummy lines MSB 179 | #define OV7670_REG_ADVFH 0x2E //< Insert dummy lines LSB 180 | #define OV7670_REG_YAVE 0x2F //< Y/G channel average value 181 | #define OV7670_REG_HSYST 0x30 //< HSYNC rising edge delay 182 | #define OV7670_REG_HSYEN 0x31 //< HSYNC falling edge delay 183 | #define OV7670_REG_HREF 0x32 //< HREF control 184 | #define OV7670_REG_CHLF 0x33 //< Array current control 185 | #define OV7670_REG_ARBLM 0x34 //< Array ref control - reserved 186 | #define OV7670_REG_ADC 0x37 //< ADC control - reserved 187 | #define OV7670_REG_ACOM 0x38 //< ADC & analog common - reserved 188 | #define OV7670_REG_OFON 0x39 //< ADC offset control - reserved 189 | #define OV7670_REG_TSLB 0x3A //< Line buffer test option 190 | #define OV7670_TSLB_NEG 0x20 //< TSLB Negative image enable 191 | #define OV7670_TSLB_YLAST 0x04 //< TSLB UYVY or VYUY, see COM13 192 | #define OV7670_TSLB_AOW 0x01 //< TSLB Auto output window 193 | #define OV7670_REG_COM11 0x3B //< Common control 11 194 | #define OV7670_COM11_NIGHT 0x80 //< COM11 Night mode 195 | #define OV7670_COM11_NMFR 0x60 //< COM11 Night mode frame rate mask 196 | #define OV7670_COM11_HZAUTO 0x10 //< COM11 Auto detect 50/60 Hz 197 | #define OV7670_COM11_BAND 0x08 //< COM11 Banding filter val select 198 | #define OV7670_COM11_EXP 0x02 //< COM11 Exposure timing control 199 | #define OV7670_REG_COM12 0x3C //< Common control 12 200 | #define OV7670_COM12_HREF 0x80 //< COM12 Always has HREF 201 | #define OV7670_REG_COM13 0x3D //< Common control 13 202 | #define OV7670_COM13_GAMMA 0x80 //< COM13 Gamma enable 203 | #define OV7670_COM13_UVSAT 0x40 //< COM13 UV saturation auto adj 204 | #define OV7670_COM13_UVSWAP 0x01 //< COM13 UV swap, use w TSLB[3] 205 | #define OV7670_REG_COM14 0x3E //< Common control 14 206 | #define OV7670_COM14_DCWEN 0x10 //< COM14 DCW & scaling PCLK enable 207 | #define OV7670_REG_EDGE 0x3F //< Edge enhancement adjustment 208 | #define OV7670_REG_COM15 0x40 //< Common control 15 209 | #define OV7670_COM15_RMASK 0xC0 //< COM15 Output range mask 210 | #define OV7670_COM15_R10F0 0x00 //< COM15 Output range 10 to F0 211 | #define OV7670_COM15_R01FE 0x80 //< COM15 01 to FE 212 | #define OV7670_COM15_R00FF 0xC0 //< COM15 00 to FF 213 | #define OV7670_COM15_RGBMASK 0x30 //< COM15 RGB 555/565 option mask 214 | #define OV7670_COM15_RGB 0x00 //< COM15 Normal RGB out 215 | #define OV7670_COM15_RGB565 0x10 //< COM15 RGB 565 output 216 | #define OV7670_COM15_RGB555 0x30 //< COM15 RGB 555 output 217 | #define OV7670_REG_COM16 0x41 //< Common control 16 218 | #define OV7670_COM16_AWBGAIN 0x08 //< COM16 AWB gain enable 219 | #define OV7670_REG_COM17 0x42 //< Common control 17 220 | #define OV7670_COM17_AECWIN 0xC0 //< COM17 AEC window must match COM4 221 | #define OV7670_COM17_CBAR 0x08 //< COM17 DSP Color bar enable 222 | #define OV7670_REG_AWBC1 0x43 //< Reserved 223 | #define OV7670_REG_AWBC2 0x44 //< Reserved 224 | #define OV7670_REG_AWBC3 0x45 //< Reserved 225 | #define OV7670_REG_AWBC4 0x46 //< Reserved 226 | #define OV7670_REG_AWBC5 0x47 //< Reserved 227 | #define OV7670_REG_AWBC6 0x48 //< Reserved 228 | #define OV7670_REG_REG4B 0x4B //< UV average enable 229 | #define OV7670_REG_DNSTH 0x4C //< De-noise strength 230 | #define OV7670_REG_MTX1 0x4F //< Matrix coefficient 1 231 | #define OV7670_REG_MTX2 0x50 //< Matrix coefficient 2 232 | #define OV7670_REG_MTX3 0x51 //< Matrix coefficient 3 233 | #define OV7670_REG_MTX4 0x52 //< Matrix coefficient 4 234 | #define OV7670_REG_MTX5 0x53 //< Matrix coefficient 5 235 | #define OV7670_REG_MTX6 0x54 //< Matrix coefficient 6 236 | #define OV7670_REG_BRIGHT 0x55 //< Brightness control 237 | #define OV7670_REG_CONTRAS 0x56 //< Contrast control 238 | #define OV7670_REG_CONTRAS_CENTER 0x57 //< Contrast center 239 | #define OV7670_REG_MTXS 0x58 //< Matrix coefficient sign 240 | #define OV7670_REG_LCC1 0x62 //< Lens correction option 1 241 | #define OV7670_REG_LCC2 0x63 //< Lens correction option 2 242 | #define OV7670_REG_LCC3 0x64 //< Lens correction option 3 243 | #define OV7670_REG_LCC4 0x65 //< Lens correction option 4 244 | #define OV7670_REG_LCC5 0x66 //< Lens correction option 5 245 | #define OV7670_REG_MANU 0x67 //< Manual U value 246 | #define OV7670_REG_MANV 0x68 //< Manual V value 247 | #define OV7670_REG_GFIX 0x69 //< Fix gain control 248 | #define OV7670_REG_GGAIN 0x6A //< G channel AWB gain 249 | #define OV7670_REG_DBLV 0x6B //< PLL & regulator control 250 | #define OV7670_REG_AWBCTR3 0x6C //< AWB control 3 251 | #define OV7670_REG_AWBCTR2 0x6D //< AWB control 2 252 | #define OV7670_REG_AWBCTR1 0x6E //< AWB control 1 253 | #define OV7670_REG_AWBCTR0 0x6F //< AWB control 0 254 | #define OV7670_REG_SCALING_XSC 0x70 //< Test pattern X scaling 255 | #define OV7670_REG_SCALING_YSC 0x71 //< Test pattern Y scaling 256 | #define OV7670_REG_SCALING_DCWCTR 0x72 //< DCW control 257 | #define OV7670_REG_SCALING_PCLK_DIV 0x73 //< DSP scale control clock divide 258 | #define OV7670_REG_REG74 0x74 //< Digital gain control 259 | #define OV7670_REG_REG76 0x76 //< Pixel correction 260 | #define OV7670_REG_SLOP 0x7A //< Gamma curve highest seg slope 261 | #define OV7670_REG_GAM_BASE 0x7B //< Gamma register base (1 of 15) 262 | #define OV7670_GAM_LEN 15 //< Number of gamma registers 263 | #define OV7670_R76_BLKPCOR 0x80 //< REG76 black pixel corr enable 264 | #define OV7670_R76_WHTPCOR 0x40 //< REG76 white pixel corr enable 265 | #define OV7670_REG_RGB444 0x8C //< RGB 444 control 266 | #define OV7670_R444_ENABLE 0x02 //< RGB444 enable 267 | #define OV7670_R444_RGBX 0x01 //< RGB444 word format 268 | #define OV7670_REG_DM_LNL 0x92 //< Dummy line LSB 269 | #define OV7670_REG_LCC6 0x94 //< Lens correction option 6 270 | #define OV7670_REG_LCC7 0x95 //< Lens correction option 7 271 | #define OV7670_REG_HAECC1 0x9F //< Histogram-based AEC/AGC ctrl 1 272 | #define OV7670_REG_HAECC2 0xA0 //< Histogram-based AEC/AGC ctrl 2 273 | #define OV7670_REG_SCALING_PCLK_DELAY 0xA2 //< Scaling pixel clock delay 274 | #define OV7670_REG_BD50MAX 0xA5 //< 50 Hz banding step limit 275 | #define OV7670_REG_HAECC3 0xA6 //< Histogram-based AEC/AGC ctrl 3 276 | #define OV7670_REG_HAECC4 0xA7 //< Histogram-based AEC/AGC ctrl 4 277 | #define OV7670_REG_HAECC5 0xA8 //< Histogram-based AEC/AGC ctrl 5 278 | #define OV7670_REG_HAECC6 0xA9 //< Histogram-based AEC/AGC ctrl 6 279 | #define OV7670_REG_HAECC7 0xAA //< Histogram-based AEC/AGC ctrl 7 280 | #define OV7670_REG_BD60MAX 0xAB //< 60 Hz banding step limit 281 | #define OV7670_REG_ABLC1 0xB1 //< ABLC enable 282 | #define OV7670_REG_THL_ST 0xB3 //< ABLC target 283 | #define OV7670_REG_SATCTR 0xC9 //< Saturation control 284 | 285 | #define OV7670_REG_LAST OV7670_REG_SATCTR //< Maximum register address 286 | 287 | // C++ ACCESSIBLE FUNCTIONS ------------------------------------------------ 288 | 289 | // These are declared in an extern "C" so Arduino platform C++ code can 290 | // access them. 291 | 292 | #ifdef __cplusplus 293 | extern "C" { 294 | #endif 295 | 296 | // Architecture- and platform-neutral initialization function. 297 | // Called by the platform init function, this in turn may call an 298 | // architecture-specific init function. 299 | OV7670_status OV7670_begin(OV7670_host *host, OV7670_colorspace colorspace, 300 | OV7670_size size, float fps); 301 | 302 | // Configure camera frame rate. Actual resulting frame rate (returned) may 303 | // be different depending on available clock frequencies. Result will only 304 | // exceed input if necessary for minimum supported rate, but this is very 305 | // rare, typically below 1 fps. In all other cases, result will be equal 306 | // or less than the requested rate, up to a maximum of 30 fps (the "or less" 307 | // is because requested fps may be based on other host hardware timing 308 | // constraints (e.g. screen) and rounding up to a closer-but-higher frame 309 | // rate would be problematic). There is no hardcoded set of fixed frame 310 | // rates because it varies with architecture, depending on OV7670_XCLK_HZ. 311 | float OV7670_set_fps(void *platform, float fps); 312 | 313 | // Configure camera resolution to one of the supported frame sizes 314 | // (powers-of-two divisions of VGA -- 640x480 down to 40x30). 315 | void OV7670_set_size(void *platform, OV7670_size size); 316 | 317 | // Lower-level resolution register fiddling function, exposed so dev code 318 | // can test variations for OV7670_set_size() windowing defaults. 319 | void OV7670_frame_control(void *platform, uint8_t size, uint8_t vstart, 320 | uint16_t hstart, uint8_t edge_offset, 321 | uint8_t pclk_delay); 322 | 323 | // Select one of the camera's night modes (or disable). 324 | // Trades off frame rate for less grainy images in low light. 325 | void OV7670_night(void *platform, OV7670_night_mode night); 326 | 327 | // Flips camera output on horizontal and/or vertical axes. 328 | void OV7670_flip(void *platform, bool flip_x, bool flip_y); 329 | 330 | // Selects one of the camera's test patterns (or disable). 331 | // See Adafruit_OV7670.h for notes about minor visual bug here. 332 | void OV7670_test_pattern(void *platform, OV7670_pattern pattern); 333 | 334 | // Convert Y (brightness) component YUV image in RAM to RGB565 big- 335 | // endian format for preview on TFT display. Data is overwritten in-place, 336 | // Y is truncated and UV elements are lost. No practical use outside TFT 337 | // preview. If you need actual grayscale 0-255 data, just access the low 338 | // byte of each 16-bit YUV pixel. 339 | void OV7670_Y2RGB565(uint16_t *ptr, uint32_t len); 340 | 341 | #ifdef __cplusplus 342 | }; 343 | #endif 344 | --------------------------------------------------------------------------------