From 4286ab04ee5cc3c7277cf51b40a99057ca560c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wi=C5=9Bnia?= Date: Wed, 5 Mar 2025 18:10:36 +0100 Subject: [PATCH] Add pio i2c controller Add sccb basic init Add basic camera skeleton (no control, no frames, basically nothing) --- CMakeLists.txt | 73 ++++++++++ pico_sdk_import.cmake | 121 ++++++++++++++++ src/PicoIris.cpp | 36 +++++ src/ov2640/camera.cpp | 323 ++++++++++++++++++++++++++++++++++++++++++ src/ov2640/camera.hpp | 289 +++++++++++++++++++++++++++++++++++++ src/ov2640/sccb.cpp | 87 ++++++++++++ src/ov2640/sccb.hpp | 21 +++ src/pio/i2c/i2c.pio | 146 +++++++++++++++++++ src/pio/i2c/pio_i2c.c | 154 ++++++++++++++++++++ src/pio/i2c/pio_i2c.h | 31 ++++ 10 files changed, 1281 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 pico_sdk_import.cmake create mode 100644 src/PicoIris.cpp create mode 100644 src/ov2640/camera.cpp create mode 100644 src/ov2640/camera.hpp create mode 100644 src/ov2640/sccb.cpp create mode 100644 src/ov2640/sccb.hpp create mode 100644 src/pio/i2c/i2c.pio create mode 100644 src/pio/i2c/pio_i2c.c create mode 100644 src/pio/i2c/pio_i2c.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..229ec68 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,73 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Initialise pico_sdk from installed location +# (note this can come from environment, CMake cache etc) + +# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work == +if(WIN32) + set(USERHOME $ENV{USERPROFILE}) +else() + set(USERHOME $ENV{HOME}) +endif() +set(sdkVersion 2.1.1) +set(toolchainVersion 14_2_Rel1) +set(picotoolVersion 2.1.1) +set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake) +if (EXISTS ${picoVscode}) + include(${picoVscode}) +endif() +# ==================================================================================== +set(PICO_BOARD pico2 CACHE STRING "Board type") + +# Pull in Raspberry Pi Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(PicoIris C CXX ASM) + +# Initialise the Raspberry Pi Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +add_executable(PicoIris src/PicoIris.cpp ) + +pico_set_program_name(PicoIris "PicoIris") +pico_set_program_version(PicoIris "0.1") + +# Generate PIO header +pico_generate_pio_header(PicoIris ${CMAKE_CURRENT_LIST_DIR}/src/pio/i2c/i2c.pio) + +# Modify the below lines to enable/disable output over UART/USB +pico_enable_stdio_uart(PicoIris 0) +pico_enable_stdio_usb(PicoIris 1) + +# Add the standard library to the build +target_link_libraries(PicoIris + pico_stdlib) + +# Add the standard include files to the build +target_include_directories(PicoIris PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + src/ + src/ov2640/ +) + +# Add any user requested libraries +target_link_libraries(PicoIris + hardware_spi + hardware_i2c + hardware_dma + hardware_pio + hardware_interp + hardware_timer + hardware_clocks + ) + +pico_add_extra_outputs(PicoIris) + diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/src/PicoIris.cpp b/src/PicoIris.cpp new file mode 100644 index 0000000..9ec7006 --- /dev/null +++ b/src/PicoIris.cpp @@ -0,0 +1,36 @@ +#include +#include "pico/stdlib.h" +#include "hardware/spi.h" +#include "hardware/i2c.h" +#include "hardware/dma.h" +#include "hardware/pio.h" +#include "hardware/interp.h" +#include "hardware/timer.h" +#include "hardware/clocks.h" +#include "hardware/uart.h" + +#include + +camera_config_t left_eye_config = { + .pin_pwdn = -1, /*!< GPIO pin for camera power down line */ //? Also called PWDN, or set to -1 and tie to GND + .pin_reset = -1, /*!< GPIO pin for camera reset line */ //? Cam reset, or set to -1 and tie to 3.3V + .pin_xclk = 0, /*!< GPIO pin for camera XCLK line */ //? in theory could be shared or perhaps ommited? + .pin_sccb_sda = 0, /*!< GPIO pin for camera SDA line */ + .pin_sccb_scl = 0, /*!< GPIO pin for camera SCL line */ + .pin_data_base = 0, /*!< this pin + 7 consecutive will be used D0-D7 */ + .pin_vsync = 0, /*!< GPIO pin for camera VSYNC line */ + .pin_href = 0, /*!< GPIO pin for camera HREF line */ + .pin_pclk = 0, /*!< GPIO pin for camera PCLK line */ + .xclk_freq_hz = 0, /*!< Frequency of XCLK signal, in Hz. */ //! Figure out the highest it can go to + .sccb_ctrl = 0, /* Select i2c controller ctrl: 0 - i2c0, 1 - i2c1, 2 - pio0, 3 - pio1, 4 - pio2 */ +}; + +int main() +{ + stdio_init_all(); + + while (true) { + printf("Hello, world!\n"); + sleep_ms(1000); + } +} diff --git a/src/ov2640/camera.cpp b/src/ov2640/camera.cpp new file mode 100644 index 0000000..8ace50d --- /dev/null +++ b/src/ov2640/camera.cpp @@ -0,0 +1,323 @@ +#include +#include +#include "pico/stdlib.h" + +register_val_t OV2640_init[] = { + // Ideas from rp2040_ov2640-main repo + // OV2640 camera initialization after reset + // WIP STUFF, don't take this seriously yet + {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 + {0x2C, 0xFF}, // Reserved + {0x2E, 0xDF}, // Reserved + {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_SENSOR}, // Sensor bank sel 1 + {0x3C, 0x32}, // Reserved + {OV2640_REG1_CLKRC, 0x00}, // Clock doubler OFF + {OV2640_REG1_COM2, OV2640_COM2_DRIVE_2X}, // 2X drive select + {OV2640_REG1_REG04, // Mirror + ? + OV2640_REG04_HFLIP | 0x20 | OV2640_REG04_HREF0}, + {OV2640_REG1_COM8, 0xC0 | OV2640_COM8_BANDING | OV2640_COM8_AGC_AUTO | + OV2640_COM8_EXP_AUTO}, + {OV2640_REG1_COM9, OV2640_COM9_AGC_GAIN_8X | 0x08}, + {0x2C, 0x0c}, // Reserved + {0x33, 0x78}, // Reserved + {0x3A, 0x33}, // Reserved + {0x3B, 0xfB}, // Reserved + {0x3E, 0x00}, // Reserved + {0x43, 0x11}, // Reserved + {0x16, 0x10}, // Reserved + {0x4A, 0x81}, // Reserved + {0x21, 0x99}, // Reserved + //{OV2640_REG1_AEW, 0x40}, // High range for AEC/AGC + //{OV2640_REG1_AEB, 0x38}, // Low range for AEC/AGC + {OV2640_REG1_AEW, 0xFF}, // High range for AEC/AGC + {OV2640_REG1_AEB, 0x00}, // Low range for AEC/AGC + {OV2640_REG1_VV, 0x82}, // Fast mode thresholds + {0x5C, 0x00}, // Reserved + {0x63, 0x00}, // Reserved + {OV2640_REG1_FLL, 0x3F}, // Frame length adjustment LSBs + {OV2640_REG1_COM3, 0x38 | OV2640_COM3_BANDING_50HZ}, + {OV2640_REG1_HISTO_LOW, 0x70}, + {OV2640_REG1_HISTO_HIGH, 0x80}, + {0x7C, 0x05}, // Reserved + {0x20, 0x80}, // Reserved + {0x28, 0x30}, // Reserved + {0x6C, 0x00}, // Reserved + {0x6D, 0x80}, // Reserved + {0x6E, 0x00}, // Reserved + {0x70, 0x02}, // Reserved + {0x71, 0x94}, // Reserved + {0x73, 0xC1}, // Reserved + {0x3D, 0x34}, // Reserved + {0x5A, 0x57}, // Reserved + {OV2640_REG1_COM7, OV2640_COM7_RES_UXGA}, + {OV2640_REG1_CLKRC, 0x00}, // Clock doubler off + {OV2640_REG1_HREFST, 0x11}, // Horiz window start MSB + {OV2640_REG1_HREFEND, 0x75}, // Horiz window end MSB + {OV2640_REG1_VSTRT, 0x01}, // Vert window start MSB + {OV2640_REG1_VEND, 0x97}, // Vert window end MSB + {OV2640_REG1_REG32, 0x36}, // Horiz window LSBs + {OV2640_REG1_COM1, 0x0F}, // Vert window LSBs + {0x37, 0x40}, // Reserved + {OV2640_REG1_BD50, 0xBB}, // 50 Hz banding AEC MSBs + {OV2640_REG1_BD60, 0x9C}, // 60 Hz banding AEC MSBs + {0x5A, 0x57}, // Reserved + {0x6D, 0x80}, // Reserved + {0x6D, 0x38}, // Reserved (2nd ref in a row?) + {0x39, 0x02}, // Reserved + {0x35, 0x88}, // Reserved + {0x22, 0x0A}, // Reserved + {0x37, 0x40}, // Reserved + {0x23, 0x00}, // Reserved + {OV2640_REG1_ARCOM2, 0xA0}, // ? + {0x36, 0x1A}, // Reserved + {0x06, 0x02}, // Reserved + {0x07, 0xC0}, // Reserved + {OV2640_REG1_COM4, 0xB7}, + {0x0E, 0x01}, // Reserved + {0x4C, 0x00}, // Reserved + {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 + {0xE5, 0x7F}, // Reserved + {OV2640_REG0_MC_BIST, OV2640_MC_BIST_RESET | OV2640_MC_BIST_BOOTROM}, + {0x41, 0x24}, // Reserved + {OV2640_REG0_RESET, OV2640_RESET_JPEG | OV2640_RESET_DVP}, + {0x76, 0xFF}, // Reserved + {0x33, 0xA0}, // Reserved + {0x42, 0x20}, // Reserved + {0x43, 0x18}, // Reserved + {0x4C, 0x00}, // Reserved + {OV2640_REG0_CTRL3, OV2640_CTRL3_BPC | OV2640_CTRL3_WPC | 0x10}, + {0x88, 0x3F}, // Reserved + {0xD7, 0x03}, // Reserved + {0xD9, 0x10}, // Reserved + {OV2640_REG0_R_DVP_SP, OV2640_R_DVP_SP_AUTO | 0x02}, + {0xC8, 0x08}, // Reserved + {0xC9, 0x80}, // Reserved + {OV2640_REG0_BPDATA, 0x00}, + {OV2640_REG0_BPADDR, 0x03}, + {OV2640_REG0_BPDATA, 0x48}, + {OV2640_REG0_BPADDR, 0x08}, + {OV2640_REG0_BPDATA, 0x20}, + {OV2640_REG0_BPDATA, 0x10}, + {OV2640_REG0_BPDATA, 0x0E}, + {0x90, 0x00}, // Reserved (addr/data?) + {0x91, 0x0E}, // Reserved + {0x91, 0x1A}, // Reserved + {0x91, 0x31}, // Reserved + {0x91, 0x5A}, // Reserved + {0x91, 0x69}, // Reserved + {0x91, 0x75}, // Reserved + {0x91, 0x7E}, // Reserved + {0x91, 0x88}, // Reserved + {0x91, 0x8F}, // Reserved + {0x91, 0x96}, // Reserved + {0x91, 0xA3}, // Reserved + {0x91, 0xaf}, // Reserved + {0x91, 0xc4}, // Reserved + {0x91, 0xd7}, // Reserved + {0x91, 0xe8}, // Reserved + {0x91, 0x20}, // Reserved + {0x92, 0x00}, // Reserved (addr/data?) + {0x93, 0x06}, // Reserved + {0x93, 0xe3}, // Reserved + {0x93, 0x02}, // Reserved + {0x93, 0x02}, // Reserved + {0x93, 0x00}, // Reserved + {0x93, 0x04}, // Reserved + {0x93, 0x00}, // Reserved + {0x93, 0x03}, // Reserved + {0x93, 0x00}, // Reserved + {0x93, 0x00}, // Reserved + {0x93, 0x00}, // Reserved + {0x93, 0x00}, // Reserved + {0x93, 0x00}, // Reserved + {0x93, 0x00}, // Reserved + {0x93, 0x00}, // Reserved (end data?) + {0x96, 0x00}, // Reserved (addr/data?) + {0x97, 0x08}, // Reserved + {0x97, 0x19}, // Reserved + {0x97, 0x02}, // Reserved + {0x97, 0x0c}, // Reserved + {0x97, 0x24}, // Reserved + {0x97, 0x30}, // Reserved + {0x97, 0x28}, // Reserved + {0x97, 0x26}, // Reserved + {0x97, 0x02}, // Reserved + {0x97, 0x98}, // Reserved + {0x97, 0x80}, // Reserved + {0x97, 0x00}, // Reserved + {0x97, 0x00}, // Reserved + {OV2640_REG0_CTRL1, (uint8_t)~OV2640_CTRL1_DG}, + {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 + {0xBA, 0xDC}, // Reserved + {0xBB, 0x08}, // Reserved + {0xB6, 0x24}, // Reserved + {0xB8, 0x33}, // Reserved + {0xB7, 0x20}, // Reserved + {0xB9, 0x30}, // Reserved + {0xB3, 0xB4}, // Reserved + {0xB4, 0xCA}, // Reserved + {0xB5, 0x43}, // Reserved + {0xB0, 0x5C}, // Reserved + {0xB1, 0x4F}, // Reserved + {0xB2, 0x06}, // Reserved + {0xC7, 0x00}, // Reserved + {0xC6, 0x51}, // Reserved + {0xC5, 0x11}, // Reserved + {0xC4, 0x9C}, // Reserved + {0xBF, 0x00}, // Reserved + {0xBC, 0x64}, // Reserved + {0xA6, 0x00}, // Reserved (addr/data?) + {0xA7, 0x1E}, // Reserved + {0xA7, 0x6b}, // Reserved + {0xA7, 0x47}, // Reserved + {0xA7, 0x33}, // Reserved + {0xA7, 0x00}, // Reserved + {0xA7, 0x23}, // Reserved + {0xA7, 0x2E}, // Reserved + {0xA7, 0x85}, // Reserved + {0xA7, 0x42}, // Reserved + {0xA7, 0x33}, // Reserved + {0xA7, 0x00}, // Reserved + {0xA7, 0x23}, // Reserved + {0xA7, 0x1B}, // Reserved + {0xA7, 0x74}, // Reserved + {0xA7, 0x42}, // Reserved + {0xA7, 0x33}, // Reserved + {0xA7, 0x00}, // Reserved + {0xA7, 0x23}, // Reserved + {OV2640_REG0_HSIZE8, 0xC8}, // Horiz size MSBs + {OV2640_REG0_VSIZE8, 0x96}, // Vert size MSBs + {OV2640_REG0_SIZEL, 0x00}, // Size bits + {OV2640_REG0_CTRL2, OV2640_CTRL2_DCW | OV2640_CTRL2_SDE | + OV2640_CTRL2_UV_ADJ | OV2640_CTRL2_UV_AVG | + OV2640_CTRL2_CMX}, + {OV2640_REG0_CTRLI, OV2640_CTRLI_LP_DP | 0x82}, // H/V dividers + {OV2640_REG0_HSIZE, 0x90}, // H_SIZE low bits + {OV2640_REG0_VSIZE, 0x2C}, // V_SIZE low bits + {OV2640_REG0_XOFFL, 0x00}, // OFFSET_X LSBs + {OV2640_REG0_YOFFL, 0x00}, // OFFSET_Y LSBs + {OV2640_REG0_VHYX, 0x88}, // V/H/Y/X MSBs + {OV2640_REG0_ZMOW, 0x50}, // OUTW low bits + {OV2640_REG0_ZMOH, 0x3C}, // OUTH low bits + {OV2640_REG0_ZMHH, 0x00}, // OUTW/H high bits + {OV2640_REG0_R_DVP_SP, 0x04}, // Manual DVP PCLK + {0x7F, 0x00}, // Reserved + //{OV2640_REG0_IMAGE_MODE, 0x00}, // YUV MSB first + {0xE5, 0x1F}, // Reserved + {0xE1, 0x67}, // Reserved + {OV2640_REG0_RESET, 0x00}, // Reset nothing? + {0xDD, 0x7F}, // Reserved + {OV2640_REG0_R_BYPASS, OV2640_R_BYPASS_DSP_ENABLE}, + {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 + {OV2640_REG0_RESET, OV2640_RESET_DVP}, + {OV2640_REG0_HSIZE8, 0xC8}, // Image horiz size MSBs + {OV2640_REG0_VSIZE8, 0x96}, // Image vert size MSBs + {OV2640_REG0_CTRL2, OV2640_CTRL2_DCW | OV2640_CTRL2_SDE | + OV2640_CTRL2_UV_ADJ | OV2640_CTRL2_UV_AVG | + OV2640_CTRL2_CMX}, + {OV2640_REG0_CTRLI, OV2640_CTRLI_LP_DP | 0x12}, + {OV2640_REG0_HSIZE, 0x90}, // H_SIZE low bits + {OV2640_REG0_VSIZE, 0x2C}, // V_SIZE low bits + {OV2640_REG0_XOFFL, 0x00}, // OFFSET_X low bits + {OV2640_REG0_YOFFL, 0x00}, // OFFSET_y low bits + {OV2640_REG0_VHYX, 0x88}, // V/H/Y/X high bits + {OV2640_REG0_TEST, 0x00}, + {OV2640_REG0_ZMOW, 0x50}, // OUTW low bits + {OV2640_REG0_ZMOH, 0x3C}, // OUTH low bits + {OV2640_REG0_ZMHH, 0x00}, // OUTW/H high bits + {OV2640_REG0_R_DVP_SP, 0x04}, // Manual DVP PCLK + {0xE0, 0x00}, // Reset nothing? + {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 + {OV2640_REG0_R_BYPASS, OV2640_R_BYPASS_DSP_ENABLE}, + {OV2640_REG0_IMAGE_MODE, OV2640_IMAGE_MODE_DVP_RGB565}, + {OV2640_REG0_IMAGE_MODE, + OV2640_IMAGE_MODE_DVP_RGB565 | OV2640_IMAGE_MODE_BYTE_SWAP}, + {0x98, 0x00}, // Reserved + {0x99, 0x00}, // Reserved + {0x00, 0x00}, // Reserved + {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 + {OV2640_REG0_RESET, OV2640_RESET_DVP}, + {OV2640_REG0_HSIZE8, 0xC8}, // H_SIZE high bits + {OV2640_REG0_VSIZE8, 0x96}, // V_SIZE high bits + {OV2640_REG0_CTRL2, OV2640_CTRL2_DCW | OV2640_CTRL2_SDE | + OV2640_CTRL2_UV_ADJ | OV2640_CTRL2_UV_AVG | + OV2640_CTRL2_CMX}, + {OV2640_REG0_CTRLI, OV2640_CTRLI_LP_DP | 0x09}, + {OV2640_REG0_HSIZE, 0x90}, // H_SIZE low bits + {OV2640_REG0_VSIZE, 0x2C}, // V_SIZE low bits + {OV2640_REG0_XOFFL, 0x00}, // OFFSET_X low bits + {OV2640_REG0_YOFFL, 0x00}, // OFFSET_Y low bits + {OV2640_REG0_VHYX, 0x88}, // V/H/Y/X high bits + {OV2640_REG0_TEST, 0x00}, + {OV2640_REG0_ZMOW, 0xA0}, // OUTW low bits + {OV2640_REG0_ZMOH, 0x78}, // OUTH low bits + {OV2640_REG0_ZMHH, 0x00}, // OUTW/H high bits + {OV2640_REG0_R_DVP_SP, 0x02}, // Manual DVP PCLK setting + {OV2640_REG0_RESET, 0x00} +}; // Go +// TODO: figure out how to set to JPEG and 260x260 +/*OV2640_qqvga[] = + {// Configure OV2640 for QQVGA output + {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 + {OV2640_REG0_RESET, OV2640_RESET_DVP}, + {OV2640_REG0_HSIZE8, 0x64}, // HSIZE high bits + {OV2640_REG0_VSIZE8, 0x4B}, // VSIZE high bits + {OV2640_REG0_CTRL2, OV2640_CTRL2_DCW | OV2640_CTRL2_SDE | + OV2640_CTRL2_UV_AVG | OV2640_CTRL2_CMX}, + {OV2640_REG0_CTRLI, OV2640_CTRLI_LP_DP | 0x12}, + {OV2640_REG0_HSIZE, 0xC8}, // H_SIZE low bits + {OV2640_REG0_VSIZE, 0x96}, // V_SIZE low bits + {OV2640_REG0_XOFFL, 0x00}, // OFFSET_X low bits + {OV2640_REG0_YOFFL, 0x00}, // OFFSET_Y low bits + {OV2640_REG0_VHYX, 0x00}, // V/H/Y/X high bits + {OV2640_REG0_TEST, 0x00}, // ? + {OV2640_REG0_ZMOW, 0x28}, // OUTW low bits + {OV2640_REG0_ZMOH, 0x1E}, // OUTH low bits + {OV2640_REG0_ZMHH, 0x00}, // OUTW/H high bits + {OV2640_REG0_R_DVP_SP, 0x08}, // Manual DVP PCLK setting + {OV2640_REG0_RESET, 0x00}}, // Go +OV2640_rgb[] = {{OV2640_REG_RA_DLMT, + OV2640_RA_DLMT_DSP}, // DSP bank select 0 + {OV2640_REG0_RESET, OV2640_RESET_DVP}, + {OV2640_REG0_IMAGE_MODE, OV2640_IMAGE_MODE_DVP_RGB565 | + OV2640_IMAGE_MODE_BYTE_SWAP}, + {0xD7, 0x03}, // Mystery init values + {0xE1, 0x77}, // seen in other examples + {OV2640_REG0_RESET, 0x00}}, // Go +OV2640_yuv[] = { + {OV2640_REG_RA_DLMT, OV2640_RA_DLMT_DSP}, // DSP bank select 0 + {OV2640_REG0_RESET, OV2640_RESET_DVP}, + {OV2640_REG0_IMAGE_MODE, + OV2640_IMAGE_MODE_DVP_YUV | OV2640_IMAGE_MODE_BYTE_SWAP}, + {0xD7, 0x01}, // Mystery init values + {0xE1, 0x67}, // seen in other examples + {OV2640_REG0_RESET, 0x00} +}; // Go*/ + +OV2640::OV2640(camera_config_t config) { + this->config = config; +} + +int OV2640::begin() { + // Initialize peripherals for parallel+I2C camera: + //! TODO: start XCLK before SCCB init + this->sccb = SCCB(this->config.pin_sccb_sda,this->config.pin_sccb_scl); + this->sccb.begin(this->config.sccb_ctrl); + + // ENABLE AND/OR RESET CAMERA -------------------------------------------- + // Unsure of camera startup time from beginning of input clock. + // Let's guess it's similar to tS:REG (300 ms) from datasheet. + sleep_ms(300000); + + // Perform a soft reset (we dont wanna waste GPIO on wiring in reset pins) + this->sccb.writeRegister(OV2640_REG_RA_DLMT, OV2640_RA_DLMT_SENSOR); // Bank select 1 + this->sccb.writeRegister(OV2640_REG1_COM7, OV2640_COM7_SRST); // System reset + sleep_ms(1); // Datasheet: tS:RESET = 1 ms + + // Init main camera settings + this->sccb.writeList(OV2640_init, sizeof OV2640_init / sizeof OV2640_init[0]); + + return 0; +} + +// TODO: add all of the specific controls like brightness and all of that scheiße \ No newline at end of file diff --git a/src/ov2640/camera.hpp b/src/ov2640/camera.hpp new file mode 100644 index 0000000..2c84226 --- /dev/null +++ b/src/ov2640/camera.hpp @@ -0,0 +1,289 @@ + +// TODO: Add XCLK settings +typedef struct { + int pin_pwdn; /*!< GPIO pin for camera power down line */ //? Also called PWDN, or set to -1 and tie to GND + int pin_reset; /*!< GPIO pin for camera reset line */ //? Cam reset, or set to -1 and tie to 3.3V + int pin_xclk; /*!< GPIO pin for camera XCLK line */ //? in theory could be shared or perhaps ommited? + int pin_sccb_sda; /*!< GPIO pin for camera SDA line */ + int pin_sccb_scl; /*!< GPIO pin for camera SCL line */ + int pin_data_base; /*!< this pin + 7 consecutive will be used D0-D7 */ + int pin_vsync; /*!< GPIO pin for camera VSYNC line */ + int pin_href; /*!< GPIO pin for camera HREF line */ + int pin_pclk; /*!< GPIO pin for camera PCLK line */ + int xclk_freq_hz; /*!< Frequency of XCLK signal, in Hz. */ //! Figure out the highest it can go to + uint8_t sccb_ctrl; /* Select i2c controller ctrl: 0 - i2c0, 1 - i2c1, 2 - pio0, 3 - pio1, 4 - pio2 */ +} camera_config_t; + +typedef struct { + uint8_t reg; ///< Register address + uint8_t value; ///< Value to store +} register_val_t; + +class OV2640 { +public: + OV2640(camera_config_t config); + ~OV2640(); + + int begin(); +private: + camera_config_t config; + SCCB sccb; +}; + +//? Refer to https://github.com/adafruit/Adafruit_ImageCapture/blob/main/src/Adafruit_iCap_OV2640.h to check whatever this shit below even is?? + +#define OV2640_ADDR 0x30 + +#define OV2640_REG_RA_DLMT 0xFF //< Register bank select +#define OV2640_RA_DLMT_DSP 0x00 //< Bank 0 - DSP address +#define OV2640_RA_DLMT_SENSOR 0x01 //< Bank 1 - Sensor address + +// OV2640 register bank 0 -- DSP address +// These register names are preceded by 'REG0' as a bank-select reminder +#define OV2640_REG0_R_BYPASS 0x05 //< Bypass DSP +#define OV2640_R_RESERVED_MASK 0xFE //< Reserved bits +#define OV2640_R_BYPASS_MASK 0x01 //< DSP mask: +#define OV2640_R_BYPASS_DSP_ENABLE 0x00 //< Enable DSP +#define OV2640_R_BYPASS_DSP_BYPASS 0x01 //< Bypass DSP +#define OV2640_REG0_QS 0x44 //< Quantization scale factor +#define OV2640_REG0_CTRLI 0x50 //< ? +#define OV2640_CTRLI_LP_DP 0x80 //< LP_DP bit +#define OV2640_CTRLI_ROUND 0x40 //< Round bit +#define OV2640_CTRLI_V_DIV_MASK 0x38 //< V_DIVIDER mask (3 bits) +#define OV2640_CTRLI_H_DIV_MASK 0x07 //< H_DIVIDER mask (3 bits) +#define OV2640_REG0_HSIZE 0x51 //< H_SIZE[7:0] (real/4) +#define OV2640_REG0_VSIZE 0x52 //< V_SIZE[7:0] (real/4) +#define OV2640_REG0_XOFFL 0x53 //< OFFSET_X[7:0] +#define OV2640_REG0_YOFFL 0x54 //< OFFSET_Y[7:0] +#define OV2640_REG0_VHYX 0x55 //< V/H/X/Y size/offset high bits +#define OV2640_VHYX_V_SIZE_8 0x80 //< V_SIZE[8] bit +#define OV2640_VHYX_OFFSET_Y_MASK 0x70 //< OFFSET_Y[10:8] mask +#define OV2640_VHYX_H_SIZE_8 0x08 //< H_SIZE[8] bit +#define OV2640_VHYX_OFFSET_X_MASK 0x07 //< OFFSET_X[10:8] mask +#define OV2640_REG0_DPRP 0x56 //< ? +#define OV2640_DPRP_DP_SELY_MASK 0xF0 //< DP_SELY mask +#define OV2640_DPRP_DP_SELX_MASK 0x0F //< DP_SELX mask +#define OV2640_REG0_TEST 0x57 //< ? +#define OV2640_TEST_H_SIZE_9 0x80 //< H_SIZE[9] bit +#define OV2640_TEST_RESERVED_MASK 0x7F //< Reserved bits +#define OV2640_REG0_ZMOW 0x5A //< OUTW[7:0] (real/4) +#define OV2640_REG0_ZMOH 0x5B //< OUTH[7:0] (real/4) +#define OV2640_REG0_ZMHH 0x5C //< Zoom speed and more +#define OV2640_ZMHH_ZMSPD_MASK 0xF0 //< ZMSPD (zoom speed) +#define OV2640_ZMHH_OUTH_8 0x40 //< OUTH[8] bit +#define OV2640_ZMHH_OUTW_MASK 0x03 //< OUTW[9:8] +#define OV2640_REG0_BPADDR 0x7C //< SDE indirect reg access: addr +#define OV2640_REG0_BPDATA 0x7D //< SDE indirect reg access: data +#define OV2640_REG0_CTRL2 0x86 //< Module enable: +#define OV2640_CTRL2_DCW 0x20 //< DCW enable +#define OV2640_CTRL2_SDE 0x10 //< SDE enable +#define OV2640_CTRL2_UV_ADJ 0x08 //< UV_ADJ enable +#define OV2640_CTRL2_UV_AVG 0x04 //< UV_AVG enable +#define OV2640_CTRL2_CMX 0x01 //< CMX enable +#define OV2640_REG0_CTRL3 0x87 //< Module enable, continued +#define OV2640_CTRL3_BPC 0x80 //< BPC enable +#define OV2640_CTRL3_WPC 0x40 //< WPC enable +#define OV2640_CTRL3_RESERVED_MASK 0x3F //< Reserved bits +#define OV2640_REG0_SIZEL 0x8C //< HSIZE, VSIZE more bits +#define OV2640_SIZEL_HSIZE11 0x40 //< HSIZE[11] +#define OV2640_SIZEL_HSIZE_MASK 0x38 //< HSIZE[2:0] +#define OV2640_SIZEL_VSIZE_MASK 0x07 //< VSIZE[2:0] +#define OV2640_REG0_HSIZE8 0xC0 //< Image horiz size HSIZE[10:3] +#define OV2640_REG0_VSIZE8 0xC1 //< Image vert size VSIZE[10:3] +#define OV2640_REG0_CTRL0 0xC2 //< Module enable, continued +#define OV2640_CTRL0_AEC_EN 0x80 //< AEC_EN enable +#define OV2640_CTRL0_AEC_SEL 0x40 //< AEC_SEL enable +#define OV2640_CTRL0_STAT_SEL 0x20 //< STAT_SEL enable +#define OV2640_CTRL0_VFIRST 0x10 //< VFIRST enable +#define OV2640_CTRL0_YUV422 0x08 //< YUV422 enable +#define OV2640_CTRL0_YUV_EN 0x04 //< YUV_EN enable +#define OV2640_CTRL0_RGB_EN 0x02 //< RGB_EN enable +#define OV2640_CTRL0_RAW_EN 0x01 //< RAW_EN enable +#define OV2640_REG0_CTRL1 0xC3 //< Module enable, continued +#define OV2640_CTRL1_CIP 0x80 //< CIP enable +#define OV2640_CTRL1_DMY 0x40 //< DMY enable +#define OV2640_CTRL1_RAW_GMA 0x20 //< RAW_GMA enable +#define OV2640_CTRL1_DG 0x10 //< DG enable +#define OV2640_CTRL1_AWB 0x08 //< AWB enable +#define OV2640_CTRL1_AWB_GAIN 0x04 //< AWB_GAIN enable +#define OV2640_CTRL1_LENC 0x02 //< LENC enable +#define OV2640_CTRL1_PRE 0x01 //< PRE enable +#define OV2640_REG0_R_DVP_SP 0xD3 //< DVP selections +#define OV2640_R_DVP_SP_AUTO 0x80 //< Auto DVP mode +#define OV2640_R_DVP_SP_PCLK_MASK 0x7F //< Manual DVP PCLK mask +#define OV2640_REG0_IMAGE_MODE 0xDA //< Image output format select +#define OV2640_IMAGE_MODE_Y8 0x40 //< Y8 enable for DVP +#define OV2640_IMAGE_MODE_JPEG 0x10 //< JPEG output enable mask +#define OV2640_IMAGE_MODE_DVP_MASK 0x0C //< DVP output format mask +#define OV2640_IMAGE_MODE_DVP_YUV 0x00 //< YUV422 +#define OV2640_IMAGE_MODE_DVP_RAW10 0x04 //< RAW10 (DVP) +#define OV2640_IMAGE_MODE_DVP_RGB565 0x08 //< RGB565 +#define OV2640_IMAGE_MODE_JPEG_HREF 0x02 //< HREF timing select in JPEG mode +#define OV2640_IMAGE_MODE_BYTE_SWAP 0x01 //< Byte swap enable for DVP +#define OV2640_REG0_RESET 0xE0 //< Reset +#define OV2640_RESET_MCU 0x40 //< Microcontroller reset +#define OV2640_RESET_SCCB 0x20 //< SCCB reset +#define OV2640_RESET_JPEG 0x10 //< JPEG reset +#define OV2640_RESET_DVP 0x04 //< DVP reset +#define OV2640_RESET_IPU 0x02 //< IPU reset +#define OV2640_RESET_CIF 0x01 //< CIF reset +#define OV2640_REG0_MS_SP 0xF0 //< SCCB host speed +#define OV2640_REG0_SS_ID 0xF7 //< SCCB periph ID +#define OV2640_REG0_SS_CTRL 0xF8 //< SCCB periph control 1: +#define OV2640_SS_CTRL_ADDR 0x20 //< Address auto-increment +#define OV2640_SS_CTRL_SCCB 0x08 //< SCCB enable +#define OV2640_SS_CTRL_DELAY 0x04 //< Delay SCCB main clock +#define OV2640_SS_CTRL_ACCESS 0x02 //< Enable SCCB host access +#define OV2640_SS_CTRL_SENSOR 0x01 //< Enable sensor pass-through +#define OV2640_REG0_MC_BIST 0xF9 //< ? +#define OV2640_MC_BIST_RESET 0x80 //< MCU reset +#define OV2640_MC_BIST_BOOTROM 0x40 //< Boot ROM select +#define OV2640_MC_BIST_12K_1 0x20 //< R/W 1 error for 12KB mem +#define OV2640_MC_BIST_12K_0 0x10 //< R/W 0 error for 12KB mem +#define OV2640_MC_BIST_512_1 0x08 //< R/W 1 error for 512B mem +#define OV2640_MC_BIST_512_0 0x04 //< R/W 0 error for 512B mem +#define OV2640_MC_BIST_BUSY 0x02 //< R=BISY busy, W=MCU reset +#define OV2640_MC_BIST_LAUNCH 0x01 //< Launch BIST +#define OV2640_REG0_MC_AL 0xFA //< Program mem ptr addr low byte +#define OV2640_REG0_MC_AH 0xFB //< Program mem ptr addr high byte +#define OV2640_REG0_MC_D 0xFC //< Program mem ptr access address +#define OV2640_REG0_P_CMD 0xFD //< SCCB protocol command register +#define OV2640_REG0_P_STATUS 0xFE //< SCCB protocol status register + +// OV2640 register bank 1 -- Sensor address +// These register names are preceded by 'REG1' as a bank-select reminder +#define OV2640_REG1_GAIN 0x00 //< AGC gain control LSBs +#define OV2640_REG1_COM1 0x03 //< Common control 1 +#define OV2640_COM1_DFRAME_MASK 0xC0 //< Dummy frame control mask +#define OV2640_COM1_DFRAME_1 0x40 //< Allow 1 dummy frame +#define OV2640_COM1_DFRAME_4 0x80 //< Allow 4 dummy frames +#define OV2640_COM1_DFRAME_7 0xC0 //< Allow 7 dummy frames +#define OV2640_COM1_VEND_MASK 0x0C //< Vert window end line LSBs +#define OV2640_COM1_VSTRT_MASK 0x03 //< Vert window start line LSBs +#define OV2640_REG1_REG04 0x04 //< Register 04 +#define OV2640_REG04_HFLIP 0x80 //< Horizontal mirror +#define OV2640_REG04_VFLIP 0x40 //< Vertical mirror +#define OV2640_REG04_VREF0 0x10 //< VREF[0] +#define OV2640_REG04_HREF0 0x08 //< HREF[0] +#define OV2640_REG04_AEC_MASK 0x03 //< AEC[1:0] +#define OV2640_REG1_REG08 0x08 //< Register 08 (frame exposure) +#define OV2640_REG1_COM2 0x09 //< Common control 2 +#define OV2640_COM2_RESERVED_MASK 0xE8 //< Reserved bits +#define OV2640_COM2_STANDBY 0x10 //< Standby mode +#define OV2640_COM2_PINUSE 0x04 //< PWDN/RESETB as SLVS/SLHS mask +#define OV2640_COM2_DRIVE_MASK 0x03 //< Output drive select mask +#define OV2640_COM2_DRIVE_1X 0x00 //< 1x +#define OV2640_COM2_DRIVE_3X 0x01 //< 3x (sic) +#define OV2640_COM2_DRIVE_2X 0x02 //< 2x (sic) +#define OV2640_COM2_DRIVE_4X 0x03 //< 4x +#define OV2640_REG1_PIDH 0x0A //< Product ID MSB (read only) +#define OV2640_REG1_PIDL 0x0B //< Product ID LSB (read only) +#define OV2640_REG1_COM3 0x0C //< Common control 3 +#define OV2640_COM3_RESERVED_MASK 0xF8 //< Reserved bits +#define OV2640_COM3_BANDING_MASK 0x04 //< Manual banding bit mask +#define OV2640_COM3_BANDING_60HZ 0x00 //< 60 Hz +#define OV2640_COM3_BANDING_50HZ 0x04 //< 50 Hz +#define OV2640_COM3_AUTO_BANDING 0x02 //< Auto-set banding +#define OV2640_COM3_SNAPSHOT 0x01 //< Snapshot option +#define OV2640_REG1_COM4 0x0D //< Common control 4 +#define OV2640_COM4_RESERVED_MASK 0xF8 //< Reserved bits +#define OV2640_COM4_CLOCK_PIN_STATUS 0x04 //< Clock output power pin status +#define OV2640_REG1_AEC 0x10 //< AEC[9:2] auto exposure ctrl +#define OV2640_REG1_CLKRC 0x11 //< Clock rate control +#define OV2640_CLKRC_DOUBLE 0x80 //< Internal freq doubler on/off +#define OV2640_CLKRC_DIV_MASK 0x3F //< Clock divider mask +#define OV2640_REG1_COM7 0x12 //< Common control 7: +#define OV2640_COM7_SRST 0x80 //< System reset +#define OV2640_COM7_RES_MASK 0x70 //< Resolution mask +#define OV2640_COM7_RES_UXGA 0x00 //< UXGA (full size) mode +#define OV2640_COM7_RES_CIF 0x10 //< CIF mode +#define OV2640_COM7_RES_SVGA 0x40 //< SVGA mode +#define OV2640_COM7_ZOOM 0x04 //< Zoom mode +#define OV2640_COM7_COLORBAR 0x02 //< Color bar test pattern enable +#define OV2640_REG1_COM8 0x13 //< Common control 8: +#define OV2640_COM8_RESERVED_MASK 0xDA //< Reserved bits +#define OV2640_COM8_BANDING 0x20 //< Banding filter on +#define OV2640_COM8_AGC_AUTO 0x04 //< Auto gain +#define OV2640_COM8_EXP_AUTO 0x01 //< Auto exposure +#define OV2640_REG1_COM9 0x14 //< Common control 9: +#define OV2640_COM9_AGC_GAIN_MASK 0xE0 //< AGC gain ceiling mask, GH[2:0] +#define OV2640_COM9_AGC_GAIN_2X 0x00 //< 2x +#define OV2640_COM9_AGC_GAIN_4X 0x20 //< 4x +#define OV2640_COM9_AGC_GAIN_8X 0x40 //< 8x +#define OV2640_COM9_AGC_GAIN_16X 0x60 //< 16x +#define OV2640_COM9_AGC_GAIN_32X 0x80 //< 32x +#define OV2640_COM9_AGC_GAIN_64X 0xA0 //< 64x +#define OV2640_COM9_AGC_GAIN_128X 0xC0 //< 128x +#define OV2640_COM9_RESERVED_MASK 0x1F //< Reserved bits +#define OV2640_REG1_COM10 0x15 //< Common control 10: +#define OV2640_COM10_CHSYNC_SWAP_MASK 0x80 //< CHSYNC pin output swap mask +#define OV2640_COM10_CHSYNC_CHSYNC 0x00 //< CHSYNC +#define OV2640_COM10_CHSYNC_HREF 0x80 //< HREF +#define OV2640_COM10_HREF_SWAP_MASK 0x40 //< HREF pin output swap mask +#define OV2640_COM10_HREF_HREF 0x00 //< HREF +#define OV2640_COM10_HREF_CHSYNC 0x40 //< CHSYNC +#define OV2640_COM10_PCLK_MASK 0x20 //< PCLK output selection mask +#define OV2640_COM10_PCLK_ALWAYS 0x00 //< PCLK always output +#define OV2640_COM10_PCLK_HREF 0x20 //< PCLK qualified by HREF +#define OV2640_COM10_PCLK_EDGE_MASK 0x10 //< PCLK edge selection mask +#define OV2640_COM10_PCLK_FALLING 0x00 //< Data updated on falling PCLK +#define OV2640_COM10_PCLK_RISING 0x10 //< Data updated on rising PCLK +#define OV2640_COM10_HREF_MASK 0x08 //< HREF polarity mask +#define OV2640_COM10_HREF_POSITIVE 0x00 //< Positive HREF +#define OV2640_COM10_HREF_NEGATIVE 0x08 //< Negative HREF for data valid +#define OV2640_COM10_VSYNC_MASK 0x02 //< VSYNC polarity mask +#define OV2640_COM10_VSYNC_POSITIVE 0x00 //< Positive VSYNC +#define OV2640_COM10_VSYNC_NEGATIVE 0x02 //< Negative VSYNC +#define OV2640_COM10_HSYNC_MASK 0x01 //< HSYNC polarity mask +#define OV2640_COM10_HSYNC_POSITIVE 0x00 //< Positive HSYNC +#define OV2640_COM10_HSYNC_NEGATIVE 0x01 //< Negative HSYNC +#define OV2640_REG1_HREFST 0x17 //< Horizontal window start MSB +#define OV2640_REG1_HREFEND 0x18 //< Horizontal window end MSB +#define OV2640_REG1_VSTRT 0x19 //< Vertical window line start MSB +#define OV2640_REG1_VEND 0x1A //< Vertical window line end MSB +#define OV2640_REG1_MIDH 0x1C //< Manufacturer ID MSB (RO=0x7F) +#define OV2640_REG1_MIDL 0x1D //< Manufacturer ID LSB (RO=0xA2) +#define OV2640_REG1_AEW 0x24 //< Luminance signal high range +#define OV2640_REG1_AEB 0x25 //< Luminance signal low range +#define OV2640_REG1_VV 0x26 //< Fast mode large step threshold +#define OV2640_VV_HIGH_MASK 0xF0 //< High threshold mask +#define OV2640_VV_LOW_MASK 0x0F //< Low threshold mask +#define OV2640_REG1_REG2A 0x2A //< Register 2A +#define OV2640_REG2A_LINE_MASK 0xF0 //< Line interval adjust MSBs +#define OV2640_REG2A_HSYNC_END_MASK 0x0C //< HSYNC timing end point MSBs +#define OV2640_REG2A_HSYNC_START_MASK 0x03 //< HSYNC timing start point MSBs +#define OV2640_REG1_FRARL 0x2B //< Line interval adjust LSB +#define OV2640_REG1_ADDVSL 0x2D //< VSYNC pulse width LSB +#define OV2640_REG1_ADDVSH 0x2E //< VSYNC pulse width MSB +#define OV2640_REG1_YAVG 0x2F //< Luminance average +#define OV2640_REG1_HSDY 0x30 //< HSYNC pos+width start LSB +#define OV2640_REG1_HEDY 0x31 //< HSYNC pos+width end LSB +#define OV2640_REG1_REG32 0x32 //< Common control 32 +#define OV2640_REG32_PCLK_MASK 0xC0 //< Pixel clock divide option mask +#define OV2640_REG32_PCLK_DIV1 0x00 //< No effect on PCLK +#define OV2640_REG32_PCLK_DIV2 0x80 //< PCLK frequency / 2 +#define OV2640_REG32_PCLK_DIV4 0xC0 //< PCLK frequency / 4 +#define OV2640_REG32_HREFEND_MASK 0x38 //< HREFEND LSBs +#define OV2640_REG32_HREFST_MASK 0x07 //< HREFST LSBs +#define OV2640_REG1_ARCOM2 0x34 //< ? +#define OV2640_ARCOM2_ZOOM 0x04 //< Zoom window horiz start point +#define OV2640_REG1_REG45 0x45 //< Register 45: +#define OV2640_REG45_AGC_MASK 0xC0 //< AGC[9:8] highest gain control +#define OV2640_REG45_AEC_MASK 0x3F //< AEC[15:10] AEC MSBs +#define OV2640_REG1_FLL 0x46 //< Frame length adjustment LSBs +#define OV2640_REG1_FLH 0x47 //< Frame length adjustment MSBs +#define OV2640_REG1_COM19 0x48 //< Frame length adjustment MSBs +#define OV2640_COM19_ZOOM_MASK 0x03 //< Zoom mode vert window LSBs +#define OV2640_REG1_ZOOMS 0x49 //< Zoom mode vert window MSB +#define OV2640_REG1_COM22 0x4B //< Common control 22 (flash strobe) +#define OV2640_REG1_COM25 0x4E //< Common control 25 +#define OV2640_COM25_50HZ_MASK 0xC0 //< 50 Hz banding AEC MSBs +#define OV2640_COM25_60HZ_MASK 0x30 //< 60 Hz banding AEC MSBs +#define OV2640_REG1_BD50 0x4F //< 50 Hz banding AEC LSBs +#define OV2640_REG1_BD60 0x50 //< 60 Hz banding AEC LSBs +#define OV2640_REG1_REG5D 0x5D //< AVGsel[7:0] 16-zone avg weight +#define OV2640_REG1_REG5E 0x5E //< AVGsel[15:8] +#define OV2640_REG1_REG5F 0x5F //< AVGsel[23:16] +#define OV2640_REG1_REG60 0x60 //< AVGsel[31:24] +#define OV2640_REG1_HISTO_LOW 0x61 //< Histogram low level +#define OV2640_REG1_HISTO_HIGH 0x62 //< Histogram high level \ No newline at end of file diff --git a/src/ov2640/sccb.cpp b/src/ov2640/sccb.cpp new file mode 100644 index 0000000..8e9ebc7 --- /dev/null +++ b/src/ov2640/sccb.cpp @@ -0,0 +1,87 @@ +#include +#include "hardware/i2c.h" +#include "pico/stdlib.h" +#include "src/pio/i2c/pio_i2c.h" +SCCB::SCCB(uint8_t sda_pin, uint8_t scl_pin) { + this->sda = sda_pin; + this->scl = scl_pin; +} + +SCCB::~SCCB() {} + +// ctrl: 0 - i2c0, 1 - i2c1, 2 - pio0, 3 - pio1, 4 - pio2 +// TODO: add proper return values when failure +// TODO: panic if scl != sda + 1 +int SCCB::begin(uint8_t ctrl) +{ + // Set up XCLK out unless it's a self-clocking camera. This must be done + // BEFORE any I2C commands, as cam may require clock for I2C timing. + this->ctrl = ctrl; + if (ctrl < 2) { + this->i2cc = ctrl == 0 ? i2c0 : i2c1; + i2c_init(this->i2cc, 100 * 1000); + gpio_set_function(this->sda, GPIO_FUNC_I2C); + gpio_set_function(this->scl, GPIO_FUNC_I2C); + gpio_pull_up(this->sda); + gpio_pull_up(this->scl); + } + else { + PIO pio; + switch (ctrl) { + case 2: + this->pio = pio0; + break; + case 3: + this->pio = pio1; + break; + case 4: + this->pio = pio2; + break; + default: + this->pio = pio0; + break; + }; + + uint sm = pio_claim_unused_sm(this->pio, true); + uint offset = pio_add_program(this->pio, &i2c_program); + i2c_program_init(this->pio, this->sm, offset, this->sda, this->scl); + } + + return 0; +} + +int SCCB::readRegister(uint8_t reg) +{ + uint8_t buf[1]; + if (this->ctrl < 2) { + i2c_write_blocking(this->i2cc, OV2640_ADDR, ®, 1, true); // TODO: check if true or false is correct here + i2c_read_blocking(this->i2cc, OV2640_ADDR, buf, 1, false); // false - finished with bus + }else { + pio_i2c_write_blocking(this->pio, this->sm, OV2640_ADDR, ®, 1); + pio_i2c_read_blocking(this->pio, this->sm, OV2640_ADDR, buf, 1); + } + return buf[0]; +} + +void SCCB::writeRegister(uint8_t reg, uint8_t value) +{ + uint8_t buf[2]; + buf[0] = reg; + buf[1] = value; + + if (this->ctrl < 2) { + i2c_write_blocking(this->i2cc, OV2640_ADDR, buf, 2, false); + }else { + pio_i2c_write_blocking(this->pio, this->sm, OV2640_ADDR, buf, 2); + } +} + +void SCCB::writeList(const register_val_t *cfg, + uint16_t len) +{ + for (int i = 0; i < len; i++) + { + writeRegister(cfg[i].reg, cfg[i].value); + sleep_ms(1000); // Some cams require, else lockup on init + } +} \ No newline at end of file diff --git a/src/ov2640/sccb.hpp b/src/ov2640/sccb.hpp new file mode 100644 index 0000000..5eaa618 --- /dev/null +++ b/src/ov2640/sccb.hpp @@ -0,0 +1,21 @@ +#include + +class SCCB { +public: + SCCB(uint8_t sda_pin = 0, uint8_t scl_pin = 0); + ~SCCB(); // Destructor + + int begin(uint8_t ctrl); + + int readRegister(uint8_t reg); + void writeRegister(uint8_t reg, uint8_t value); + void writeList(const register_val_t *cfg, uint16_t len); + +private: + uint8_t sda; + uint8_t scl; + uint ctrl; + i2c_inst_t* i2cc; + pio_hw_t* pio; + uint sm; +}; \ No newline at end of file diff --git a/src/pio/i2c/i2c.pio b/src/pio/i2c/i2c.pio new file mode 100644 index 0000000..8e13d6a --- /dev/null +++ b/src/pio/i2c/i2c.pio @@ -0,0 +1,146 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; +.pio_version 0 // only requires PIO version 0 + +.program i2c +.side_set 1 opt pindirs + +; TX Encoding: +; | 15:10 | 9 | 8:1 | 0 | +; | Instr | Final | Data | NAK | +; +; If Instr has a value n > 0, then this FIFO word has no +; data payload, and the next n + 1 words will be executed as instructions. +; Otherwise, shift out the 8 data bits, followed by the ACK bit. +; +; The Instr mechanism allows stop/start/repstart sequences to be programmed +; by the processor, and then carried out by the state machine at defined points +; in the datastream. +; +; The "Final" field should be set for the final byte in a transfer. +; This tells the state machine to ignore a NAK: if this field is not +; set, then any NAK will cause the state machine to halt and interrupt. +; +; Autopull should be enabled, with a threshold of 16. +; Autopush should be enabled, with a threshold of 8. +; The TX FIFO should be accessed with halfword writes, to ensure +; the data is immediately available in the OSR. +; +; Pin mapping: +; - Input pin 0 is SDA, 1 is SCL (if clock stretching used) +; - Jump pin is SDA +; - Side-set pin 0 is SCL +; - Set pin 0 is SDA +; - OUT pin 0 is SDA +; - SCL must be SDA + 1 (for wait mapping) +; +; The OE outputs should be inverted in the system IO controls! +; (It's possible for the inversion to be done in this program, +; but costs 2 instructions: 1 for inversion, and one to cope +; with the side effect of the MOV on TX shift counter.) + +do_nack: + jmp y-- entry_point ; Continue if NAK was expected + irq wait 0 rel ; Otherwise stop, ask for help + +do_byte: + set x, 7 ; Loop 8 times +bitloop: + out pindirs, 1 [7] ; Serialise write data (all-ones if reading) + nop side 1 [2] ; SCL rising edge + wait 1 pin, 1 [4] ; Allow clock to be stretched + in pins, 1 [7] ; Sample read data in middle of SCL pulse + jmp x-- bitloop side 0 [7] ; SCL falling edge + + ; Handle ACK pulse + out pindirs, 1 [7] ; On reads, we provide the ACK. + nop side 1 [7] ; SCL rising edge + wait 1 pin, 1 [7] ; Allow clock to be stretched + jmp pin do_nack side 0 [2] ; Test SDA for ACK/NAK, fall through if ACK + +public entry_point: +.wrap_target + out x, 6 ; Unpack Instr count + out y, 1 ; Unpack the NAK ignore bit + jmp !x do_byte ; Instr == 0, this is a data record. + out null, 32 ; Instr > 0, remainder of this OSR is invalid +do_exec: + out exec, 16 ; Execute one instruction per FIFO word + jmp x-- do_exec ; Repeat n + 1 times +.wrap + +% c-sdk { + +#include "hardware/clocks.h" +#include "hardware/gpio.h" + + +static inline void i2c_program_init(PIO pio, uint sm, uint offset, uint pin_sda, uint pin_scl) { + assert(pin_scl == pin_sda + 1); + pio_sm_config c = i2c_program_get_default_config(offset); + + // IO mapping + sm_config_set_out_pins(&c, pin_sda, 1); + sm_config_set_set_pins(&c, pin_sda, 1); + sm_config_set_in_pins(&c, pin_sda); + sm_config_set_sideset_pins(&c, pin_scl); + sm_config_set_jmp_pin(&c, pin_sda); + + sm_config_set_out_shift(&c, false, true, 16); + sm_config_set_in_shift(&c, false, true, 8); + + float div = (float)clock_get_hz(clk_sys) / (32 * 100000); + sm_config_set_clkdiv(&c, div); + + // Try to avoid glitching the bus while connecting the IOs. Get things set + // up so that pin is driven down when PIO asserts OE low, and pulled up + // otherwise. + gpio_pull_up(pin_scl); + gpio_pull_up(pin_sda); + uint32_t both_pins = (1u << pin_sda) | (1u << pin_scl); + pio_sm_set_pins_with_mask(pio, sm, both_pins, both_pins); + pio_sm_set_pindirs_with_mask(pio, sm, both_pins, both_pins); + pio_gpio_init(pio, pin_sda); + gpio_set_oeover(pin_sda, GPIO_OVERRIDE_INVERT); + pio_gpio_init(pio, pin_scl); + gpio_set_oeover(pin_scl, GPIO_OVERRIDE_INVERT); + pio_sm_set_pins_with_mask(pio, sm, 0, both_pins); + + // Clear IRQ flag before starting, and make sure flag doesn't actually + // assert a system-level interrupt (we're using it as a status flag) + pio_set_irq0_source_enabled(pio, (enum pio_interrupt_source) ((uint) pis_interrupt0 + sm), false); + pio_set_irq1_source_enabled(pio, (enum pio_interrupt_source) ((uint) pis_interrupt0 + sm), false); + pio_interrupt_clear(pio, sm); + + // Configure and start SM + pio_sm_init(pio, sm, offset + i2c_offset_entry_point, &c); + pio_sm_set_enabled(pio, sm, true); +} + +%} + + +.program set_scl_sda +.side_set 1 opt + +; Assemble a table of instructions which software can select from, and pass +; into the FIFO, to issue START/STOP/RSTART. This isn't intended to be run as +; a complete program. + + set pindirs, 0 side 0 [7] ; SCL = 0, SDA = 0 + set pindirs, 1 side 0 [7] ; SCL = 0, SDA = 1 + set pindirs, 0 side 1 [7] ; SCL = 1, SDA = 0 + set pindirs, 1 side 1 [7] ; SCL = 1, SDA = 1 + +% c-sdk { +// Define order of our instruction table +enum { + I2C_SC0_SD0 = 0, + I2C_SC0_SD1, + I2C_SC1_SD0, + I2C_SC1_SD1 +}; +%} \ No newline at end of file diff --git a/src/pio/i2c/pio_i2c.c b/src/pio/i2c/pio_i2c.c new file mode 100644 index 0000000..7c342d0 --- /dev/null +++ b/src/pio/i2c/pio_i2c.c @@ -0,0 +1,154 @@ +/** + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + #include "pio_i2c.hpp" + + const int PIO_I2C_ICOUNT_LSB = 10; + const int PIO_I2C_FINAL_LSB = 9; + const int PIO_I2C_DATA_LSB = 1; + const int PIO_I2C_NAK_LSB = 0; + + + bool pio_i2c_check_error(PIO pio, uint sm) { + return pio_interrupt_get(pio, sm); + } + + void pio_i2c_resume_after_error(PIO pio, uint sm) { + pio_sm_drain_tx_fifo(pio, sm); + pio_sm_exec(pio, sm, (pio->sm[sm].execctrl & PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS) >> PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB); + pio_interrupt_clear(pio, sm); + } + + void pio_i2c_rx_enable(PIO pio, uint sm, bool en) { + if (en) + hw_set_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS); + else + hw_clear_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS); + } + + static inline void pio_i2c_put16(PIO pio, uint sm, uint16_t data) { + while (pio_sm_is_tx_fifo_full(pio, sm)) + ; + // some versions of GCC dislike this + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-aliasing" + #endif + *(io_rw_16 *)&pio->txf[sm] = data; + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + } + + + // If I2C is ok, block and push data. Otherwise fall straight through. + void pio_i2c_put_or_err(PIO pio, uint sm, uint16_t data) { + while (pio_sm_is_tx_fifo_full(pio, sm)) + if (pio_i2c_check_error(pio, sm)) + return; + if (pio_i2c_check_error(pio, sm)) + return; + // some versions of GCC dislike this + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-aliasing" + #endif + *(io_rw_16 *)&pio->txf[sm] = data; + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + } + + uint8_t pio_i2c_get(PIO pio, uint sm) { + return (uint8_t)pio_sm_get(pio, sm); + } + + void pio_i2c_start(PIO pio, uint sm) { + pio_i2c_put_or_err(pio, sm, 1u << PIO_I2C_ICOUNT_LSB); // Escape code for 2 instruction sequence + pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]); // We are already in idle state, just pull SDA low + pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]); // Also pull clock low so we can present data + } + + void pio_i2c_stop(PIO pio, uint sm) { + pio_i2c_put_or_err(pio, sm, 2u << PIO_I2C_ICOUNT_LSB); + pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]); // SDA is unknown; pull it down + pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]); // Release clock + pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]); // Release SDA to return to idle state + }; + + void pio_i2c_repstart(PIO pio, uint sm) { + pio_i2c_put_or_err(pio, sm, 3u << PIO_I2C_ICOUNT_LSB); + pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD1]); + pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]); + pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]); + pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]); + } + + static void pio_i2c_wait_idle(PIO pio, uint sm) { + // Finished when TX runs dry or SM hits an IRQ + pio->fdebug = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm); + while (!(pio->fdebug & 1u << (PIO_FDEBUG_TXSTALL_LSB + sm) || pio_i2c_check_error(pio, sm))) + tight_loop_contents(); + } + + int pio_i2c_write_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *txbuf, uint len) { + int err = 0; + pio_i2c_start(pio, sm); + pio_i2c_rx_enable(pio, sm, false); + pio_i2c_put16(pio, sm, (addr << 2) | 1u); + while (len && !pio_i2c_check_error(pio, sm)) { + if (!pio_sm_is_tx_fifo_full(pio, sm)) { + --len; + pio_i2c_put_or_err(pio, sm, (*txbuf++ << PIO_I2C_DATA_LSB) | ((len == 0) << PIO_I2C_FINAL_LSB) | 1u); + } + } + pio_i2c_stop(pio, sm); + pio_i2c_wait_idle(pio, sm); + if (pio_i2c_check_error(pio, sm)) { + err = -1; + pio_i2c_resume_after_error(pio, sm); + pio_i2c_stop(pio, sm); + } + return err; + } + + int pio_i2c_read_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *rxbuf, uint len) { + int err = 0; + pio_i2c_start(pio, sm); + pio_i2c_rx_enable(pio, sm, true); + while (!pio_sm_is_rx_fifo_empty(pio, sm)) + (void)pio_i2c_get(pio, sm); + pio_i2c_put16(pio, sm, (addr << 2) | 3u); + uint32_t tx_remain = len; // Need to stuff 0xff bytes in to get clocks + + bool first = true; + + while ((tx_remain || len) && !pio_i2c_check_error(pio, sm)) { + if (tx_remain && !pio_sm_is_tx_fifo_full(pio, sm)) { + --tx_remain; + pio_i2c_put16(pio, sm, (0xffu << 1) | (tx_remain ? 0 : (1u << PIO_I2C_FINAL_LSB) | (1u << PIO_I2C_NAK_LSB))); + } + if (!pio_sm_is_rx_fifo_empty(pio, sm)) { + if (first) { + // Ignore returned address byte + (void)pio_i2c_get(pio, sm); + first = false; + } + else { + --len; + *rxbuf++ = pio_i2c_get(pio, sm); + } + } + } + pio_i2c_stop(pio, sm); + pio_i2c_wait_idle(pio, sm); + if (pio_i2c_check_error(pio, sm)) { + err = -1; + pio_i2c_resume_after_error(pio, sm); + pio_i2c_stop(pio, sm); + } + return err; + } \ No newline at end of file diff --git a/src/pio/i2c/pio_i2c.h b/src/pio/i2c/pio_i2c.h new file mode 100644 index 0000000..615ac4b --- /dev/null +++ b/src/pio/i2c/pio_i2c.h @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef _PIO_I2C_H +#define _PIO_I2C_H + +#include "i2c.pio.h" + +// ---------------------------------------------------------------------------- +// Low-level functions + +void pio_i2c_start(PIO pio, uint sm); +void pio_i2c_stop(PIO pio, uint sm); +void pio_i2c_repstart(PIO pio, uint sm); + +bool pio_i2c_check_error(PIO pio, uint sm); +void pio_i2c_resume_after_error(PIO pio, uint sm); + +// If I2C is ok, block and push data. Otherwise fall straight through. +void pio_i2c_put_or_err(PIO pio, uint sm, uint16_t data); +uint8_t pio_i2c_get(PIO pio, uint sm); + +// ---------------------------------------------------------------------------- +// Transaction-level functions + +int pio_i2c_write_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *txbuf, uint len); +int pio_i2c_read_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *rxbuf, uint len); + +#endif \ No newline at end of file