diff --git a/.gitignore b/.gitignore index 3e0a236d..f17d38d4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ **/*.eggs/* **/.ipynb_checkpoints/* .venv +.python-version # Build & debug cruft **/.build*/* @@ -17,7 +18,6 @@ *.dylib *.xe *.vcd -*.s *.xi *.i *.xmt @@ -41,6 +41,9 @@ *.swmem *.bin *.fs +*.exe +.editorconfig +*.csv # Test cruft **/.pytest_cache/* @@ -77,9 +80,24 @@ tools/ai/ai_tools # temp folder temp - !CMakeLists.txt +# images +*.png + # dotenv .env -.png \ No newline at end of file + +# ninja +.ninja_deps +.ninja_log +/build.ninja +*.exe + +# Images +*.bmp +*.raw +*.png + +#documentation +!**/images/*.png diff --git a/.gitmodules b/.gitmodules index cb0fc2c7..675d2264 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,16 +1,12 @@ [submodule "xmos_cmake_toolchain"] path = xmos_cmake_toolchain - url = git@github.com:xalbertoisorna/xmos_cmake_toolchain # grouping some CMAKE utilities + url = git@github.com:xmos/xmos_cmake_toolchain # grouping some CMAKE utilities branch = main -[submodule "modules/i2c"] - path = modules/i2c - url = git@github.com:xalbertoisorna/lib_i2c # adding lib assert and cmake to the repo +[submodule "tests/Unity"] + path = tests/Unity + url = git@github.com:xmos/Unity # unit testing branch = develop -[submodule "modules/mipi"] - path = modules/mipi - url = git@github.com:xalbertoisorna/lib_mipi # same adding cmake to the repo - branch = master -[submodule "modules/core"] - path = modules/core - url = git@github.com:xmos/fwk_core # xcore math inside the repo +[submodule "utils/xscope_fileio"] + path = utils/xscope_fileio + url = https://github.com/xmos/xscope_fileio branch = develop diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 00000000..7aaa9386 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,36 @@ +fwk_camera change log +===================== + +0.2.0 +----- + + * ADDED: Dynamic configuration for the resolution, pixel format, binning mode + and frame offsets + * CHANGED: Mipi has been reimplemented to C + * CHANGED: I2c library version (from xc version to c version from fwk_io) + * CHANGED: Sensor control has been reimplemented + * CHANGED: "assert" function replaced with "xassert" from + * CHANGED: streaming channels of camera_api.c have been replaced with channels + * ADDED: new supported mode, MODE_1280x960 + * CHANGED: value of PLL_VT_MPY from 0x0047 to 0x0027 due to timing in + MODE_1280x960 + * CHANGED: default AWB static values in order to match sony sensor + +0.1.0 +----- + + * ADDED: Sony IMX219 support (Raspberry Pi Camera V2). + * ADDED: RAW8 and Raw10 capture support using only internal RAM. + * ADDED: Raw8 capture example (640X480). + * ADDED: Raw8 + Downsample exmaple (160x120x3) RGB. + * ADDED: Xscope_fileio suport. + * ADDED: AE control: auto exposure based on histogram skewness. + * ADDED: AWB control: auto white balance. Combination of grey world assumption + and percentile white balance. + * ADDED: AWB control: auto white balance based on a combination of grey world + assumption and percentile. + * ADDED: Image rotation: image rotation capabilities (sensor and ISP) + * ADDED: cropping / scaling : image scalling, cropping. + * ADDED: Image statistics: histogram, mean, variance, skewness. + * ADDED: sensor control, start, stop functons. + diff --git a/CMakeLists.txt b/CMakeLists.txt index 377a89d8..739f188c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,48 +1,48 @@ -# edit here -------------------------------- -set(PROJECT_NAME first_project) - -# do not edit ---------------------------------------------------------------- cmake_minimum_required(VERSION 3.21) +## Set the target +set(XCORE_TARGET "XCORE-AI-EXPLORER") + ## Disable in-source build. if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") - message(FATAL_ERROR "In-source build is not allowed! Please specify a build folder.\n\tex:cmake -B build") + message(FATAL_ERROR "In-source build is not allowed! Please specify a build folder.\n\tex:cmake -B build") endif() ## Project declaration -project(PROJECT_NAME) +project(fwk_camera) ## Enable languages for project enable_language(CXX C ASM) -## Project options -option(XCORE_VOICE_TESTS "Enable XCORE-VOICE tests" OFF) +## Fixes unnecessary recompilation of .xc files in Windows builds. +string(REPLACE "-MD" "-MMD" CMAKE_DEPFILE_FLAGS_C ${CMAKE_DEPFILE_FLAGS_C}) -## Import some helpful macros -include(xmos_cmake_toolchain/xmos_macros.cmake) - -## Setup a root path -set(first_project_PATH ${PROJECT_SOURCE_DIR} CACHE STRING "Root folder of sln_voice in this cmake project tree") +if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL XCORE_XS3A) +set(CONFIG_RAW8 0x2A) +set(CONFIG_RAW10 0x2B) +set(CONFIG_MIPI_FORMAT ${CONFIG_RAW8}) ## Add frameworks -if(IS_DIRECTORY modules) - add_subdirectory(modules) - message("Adding modules/") -endif() +add_subdirectory(modules) +add_subdirectory(external_deps) ## Add sensors add_subdirectory(sensors) -message("Adding sensors/") ## Add cameras add_subdirectory(camera) -message("Adding camera/") +if(PROJECT_IS_TOP_LEVEL) +## utils for examples and testing +add_subdirectory(utils) +## Add tests +add_subdirectory(tests) -## Add top level project targets -if(PROJECT_IS_TOP_LEVEL) - include(examples/examples.cmake) -endif() +## Add examples +add_subdirectory(examples) + +endif() # top level +endif() # xs3a diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..e868f5cc --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,122 @@ +@Library('xmos_jenkins_shared_library@v0.25.0') + +def runningOn(machine) { + println "Stage running on:" + println machine +} + +getApproval() +pipeline { + agent none + + parameters { + string( + name: 'TOOLS_VERSION', + defaultValue: '15.2.1', + description: 'The XTC tools version' + ) + } // parameters + options { + skipDefaultCheckout() + } // options + + stages { + stage('Builds') { + parallel { + stage ('Build and Unit test') { + agent { + label 'linux&&x86_64' + } + stages { + stage ('Build') { + steps { + runningOn(env.NODE_NAME) + dir('fwk_camera') { + checkout scm + // fetch submodules + sh 'git submodule update --init --recursive --jobs 4' + // build examples and tests + withTools(params.TOOLS_VERSION) { + sh 'cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake' + sh 'make -C build -j4' + } + } + } + } // Build + + stage('Create Python enviroment') { + steps { + // Clone infrastructure repos + sh "git clone git@github.com:xmos/infr_apps" + sh "git clone git@github.com:xmos/infr_scripts_py" + // can't use createVenv on the top level yet + dir('fwk_camera') { + createVenv() + withVenv { + sh "pip install -e ../infr_scripts_py" + sh "pip install -e ../infr_apps" + } + } + } + } // Create Python enviroment + + stage('Source check') { + steps { + // bit weird for now but should changed after the next xjsl release + dir('fwk_camera') { + withVenv { + dir('tests/lib_checks') { + sh "pytest -s" + } + } + } + } + } // Source check + + stage('Unit tests') { + steps { + dir('fwk_camera/build/tests/unit_tests') { + withTools(params.TOOLS_VERSION) { + sh 'xsim --xscope "-offline trace.xmt" test_camera.xe' + } + } + } + } // Unit tests + + } // stages + post { + cleanup { + cleanWs() + } + } + } // Build and Unit test + + stage ('Build Documentation') { + agent { + label 'docker' + } + stages { + stage ('Build Docs') { + steps { + runningOn(env.NODE_NAME) + checkout scm + sh """docker run --user "\$(id -u):\$(id -g)" \ + --rm \ + -v ${WORKSPACE}:/build \ + -e EXCLUDE_PATTERNS="/build/doc/exclude_patterns.inc" \ + -e PDF=1 \ + ghcr.io/xmos/doc_builder:v3.0.0""" + archiveArtifacts artifacts: "doc/_build/**", allowEmptyArchive: true + } + } // Build Docs + } // stages + post { + cleanup { + cleanWs() + } + } + } // Build Documentation + } // parallel + } // Builds + } // stages +} // pipeline diff --git a/LICENSE.rst b/LICENSE.rst new file mode 100644 index 00000000..ca48f20f --- /dev/null +++ b/LICENSE.rst @@ -0,0 +1,84 @@ +******************************* +XMOS PUBLIC LICENCE: Version 1 +******************************* + +Subject to the conditions and limitations below, permission is hereby granted by XMOS LIMITED (“XMOS”), free of charge, to any person or entity obtaining a copy of the XMOS Software. + +**1. Definitions** + +**“Applicable Patent Rights”** means: (a) where XMOS is the grantor of the rights, (i) claims of patents that are now or in future owned by or assigned to XMOS and (ii) that cover subject matter contained in the Software, but only to the extent it is necessary to use, reproduce or distribute the Software without infringement; and (b) where you are the grantor of the rights, (i) claims of patents that are now or in future owned by or assigned to you and (ii) that cover the subject matter contained in your Derivatives, taken alone or in combination with the Software. + +**“Compiled Code”** means any compiled, binary, machine readable or executable version of the Source Code. + +**“Contributor”** means any person or entity that creates or contributes to the creation of Derivatives. + +**“Derivatives”** means any addition to, deletion from and/or change to the substance, structure of the Software, any previous Derivatives, the combination of the Derivatives and the Software and/or any respective portions thereof. + +**“Source Code”** means the human readable code that is suitable for making modifications but excluding any Compiled Code. + +**“Software”** means the software and associated documentation files which XMOS makes available and which contain a notice identifying the software as original XMOS software and referring to the software being subject to the terms of this XMOS Public Licence. + +This Licence refers to XMOS Software and does not relate to any XMOS hardware or devices which are protected by intellectual property rights (including patent and trade marks) which may be sold to you under a separate agreement. + + +**2. Licence** + +**Permitted Uses, Conditions and Restrictions.** Subject to the conditions below, XMOS grants you a worldwide, royalty free, non-exclusive licence, to the extent of any Patent Rights to do the following: + +2.1 **Unmodified Software.** You may use, copy, display, publish, distribute and make available unmodified copies of the Software: + +2.1.1 for personal or academic, non-commercial purposes; or + +2.1.2 for commercial purposes provided the Software is at all times used on a device designed, licensed or developed by XMOS and, provided that in each instance (2.1.1 and 2.1.2): + +(a) you must retain and reproduce in all copies of the Software the copyright and proprietary notices and disclaimers of XMOS as they appear in the Software, and keep intact all notices and disclaimers in the Software files that refer to this Licence; and + +(b) you must include a copy of this Licence with every copy of the Software and documentation you publish, distribute and make available and you may not offer or impose any terms on such Software that alter or restrict this Licence or the intent of such Licence, except as permitted below (Additional Terms). + +The licence above does not include any Compiled Code which XMOS may make available under a separate support and licence agreement. + +2.2 **Derivatives.** You may create and modify Derivatives and use, copy, display, publish, distribute and make available Derivatives: + +2.2.1 for personal or academic, non-commercial purposes; or + +2.2.2 for commercial purposes, provided the Derivatives are at all times used on a device designed, licensed or developed by XMOS and, provided that in each instance (2.2.1 and 2.2.2): + +(a) you must comply with the terms of clause 2.1 with respect to the Derivatives; + +(b) you must copy (to the extent it doesn’t already exist) the notice below in each file of the Derivatives, and ensure all the modified files carry prominent notices stating that you have changed the files and the date of any change; and + +(c) if you sublicence, distribute or otherwise make the Software and/or the Derivatives available for commercial purposes, you must provide that the Software and Derivatives are at all times used on a device designed, licensed or developed by XMOS. + +Without limitation to these terms and clause 3 below, the Source Code and Compiled Code to your Derivatives may at your discretion (but without obligation) be released, copied, displayed, published, distributed and made available; and if you elect to do so, it must be under the terms of this Licence including the terms of the licence at clauses 2.2.1, 2.2.2 and clause 3 below. + +2.3 **Distribution of Executable Versions.** If you distribute or make available Derivatives, you must include a prominent notice in the code itself as well as in all related documentation, stating that the Source Code of the Software from which the Derivatives are based is available under the terms of this Licence, with information on how and where to obtain such Source Code. + +**3. Your Grant of Rights.** In consideration and as a condition to this Licence, you grant to any person or entity receiving or distributing any Derivatives, a non-exclusive, royalty free, perpetual, irrevocable license under your Applicable Patent Rights and all other intellectual property rights owned or controlled by you, to use, copy, display, publish, distribute and make available your Derivatives of the same scope and extent as XMOS’s licence under clause 2.2 above. + +**4. Combined Products.** You may create a combined product by combining Software, Derivatives and other code not covered by this Licence as a single application or product. In such instance, you must comply with the requirements of this Licence for any portion of the Software and/or Derivatives. + +**5. Additional Terms.** You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations and/or other rights consistent with the term of this Licence (“Additional Terms”) to any legitimate recipients of the Software and/or Derivatives. The terms on which you provide such Additional Terms are on your sole responsibility and you shall indemnify, defend and hold XMOS harmless against any claims asserted against XMOS. + +**6. New Versions.** XMOS may publish revised and/or new versions of this Licence from time to time to accommodate changes to the Licence terms, new versions, updates and bug fixes of the Software. Each version will be given a distinguishing version number. Once Software has been published under a particular version of this Licence, you may continue to use it under the terms of that version. You may also choose to use the latest version of the Software under any subsequent version published by XMOS. Only XMOS shall have the right to modify these terms. + +**7. IPR and Ownership** +Any rights, including all intellectual property rights and all trademarks not expressly granted herein are reserved in full by the authors or copyright holders. Any requests for additional permissions by XMOS including any rights to use XMOS trademarks, should be made (without obligation) to XMOS at **support@xmos.com** + +Nothing herein shall limit any rights that XMOS is otherwise entitled to under the doctrines of patent exhaustion, implied license, or legal estoppel. Neither the name of the authors, the copyright holders or any contributors may be used to endorse or promote any Derivatives from this Software without specific written permission. Any attempt to deal with the Software which does not comply with this Licence shall be void and shall automatically terminate any rights granted under this licence (including any licence of any intellectual property rights granted herein). +Subject to the licences granted under this Licence any Contributor retains all rights, title and interest in and to any Derivatives made by Contributor subject to the underlying rights of XMOS in the Software. XMOS shall retain all rights, title and interest in the Software and any Derivatives made by XMOS (“XMOS Derivatives”). XMOS Derivatives will not automatically be subject to this Licence and XMOS shall be entitled to licence such rights on any terms (without obligation) as it sees fit. + +**8. Termination** + +8.1 This Licence will automatically terminate immediately, without notice to you, if: + +(a) you fail to comply with the terms of this Licence; and/or + +(b) you directly or indirectly commence any action for patent or intellectual property right infringement against XMOS, or any parent, group, affiliate or subsidiary of XMOS; provided XMOS did not first commence an action or patent infringement against you in that instance; and/or + +(c) the terms of this Licence are held by any court of competent jurisdiction to be unenforceable in whole or in part. + +**9. Critical Applications.** Unless XMOS has agreed in writing with you an agreement specifically governing use of the Goods in military, aerospace, automotive or medically related functions (collectively and individually hereinafter referred to as "Special Use"), any permitted use of the Software excludes Special Use. Notwithstanding any agreement between XMOS and you for Special Use, Special Use shall be at your own risk, and you shall fully indemnify XMOS against any damages, losses, costs and claims (direct and indirect) arising out of any Special Use. + +**10. NO WARRANTY OR SUPPORT.** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL XMOS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, WARRANTY, CIVIL TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE INCLUDING GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES EVEN IF SUCH PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND NOT WITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE. IN SOME JURISDICTIONS PARTIES ARE UNABLE TO LIMIT LIABILTY IN THIS WAY, IF THIS APPLIES TO YOUR JURISDICTION THIS LIABILITY CLAUSE ABOVE MAY NOT APPLY. NOTWITHSTANDING THE ABOVE, IN NO EVENT SHALL XMOS’s TOTAL LIABILITY TO YOU FOR ALL DAMAGES, LOSS OR OTHERWISE EXCEED $50. + +**11. Governing Law and Jurisdiction.** This Licence constitutes the entire agreement between the parties with respect to the subject matter hereof. The Licence shall be governed by the laws of England and the conflict of laws and UN Convention on Contracts for the International Sale of Goods, shall not apply. diff --git a/README.md b/README.md deleted file mode 100644 index b776d222..00000000 --- a/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Camera framework -Repository to manipulate cameras using the XCORE.AI sensor - -## Structure -- **examples** : examples for taking pictures with the explorer board -- **lib_camera** : useful functions to manipulate images -- **modules** : dependencies folder -- **sensors** : camera sensors and API for controlling any camera sensor -- **python** : python functions to decode RAW8, RAW10 pictures and other utilities to treat images - -## Requirements -- CMAKE -- XMOS tools -- git submodules - -## Installation -Some dependent components are included as git submodules. These can be obtained by cloning this repository with the following command: -(make sure you have the correct ssh access to clone) -``` -git clone --recurse-submodules git@github.com:xmos/fwk_camera.git -``` - -## Build -``` -sh launch_cmake.sh -``` - -## Useful commands -- run (explorer board): ```xrun --xscope example_take_picture.xe``` -- run (simulation): ```xsim --xscope "-offline trace.xmt" build/example_take_picture.xe``` - -## Supported Cameras - -| Model | Max Resolution | Output Formats -| ------------- | ------------- | ------------- | -| IMX219 | 640Hx480V == VGA (2x2 binning) | RAW8 -| GC2145 | 1600H x 1200V == 2MPX | YUV422 - -## How to configure your sensor or add a new one -TODO diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..e7a976b1 --- /dev/null +++ b/README.rst @@ -0,0 +1,55 @@ +Camera framework +================== + +This repository serves as a comprehensive software solution for camera manipulation using the XCORE.AI sensor. + +Repository Structure +-------------------- + +- **examples** : examples for taking pictures with the explorer board +- **camera** : useful functions to manipulate images +- **modules** : dependencies folder +- **sensors** : configuration and control of the sensors +- **python** : python functions to decode RAW8, RAW10 pictures and other utilities to treat images + +Requirements +------------ + +- CMAKE +- XMOS tools +- git submodules +- Ninja (Windows) + +Installation +------------ + +Some dependent components are included as git submodules. These can be obtained by cloning this repository with the following command: +(make sure you have the correct ssh access to clone) + +.. code-block:: console + + git clone --recurse-submodules https://github.com/xmos/fwk_camera.git + +Build instructions +------------------ + +The instructions below will build all modules, examples and tests. +For building a specific example refer to examples/readme.rst. + +Linux, Mac +~~~~~~~~~~ + +.. code-block:: console + + cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + cd build/ + make + +Windows +~~~~~~~ + +.. code-block:: console + + cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + cd build + ninja diff --git a/camera/CMakeLists.txt b/camera/CMakeLists.txt index 8a874a45..eeb64655 100644 --- a/camera/CMakeLists.txt +++ b/camera/CMakeLists.txt @@ -1,37 +1,42 @@ # ############################################################################## # CMake configuration stuff -cmake_minimum_required(VERSION 3.14) -cmake_policy(SET CMP0057 NEW) enable_language(C CXX ASM) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -string(REPLACE "-MD" "-MMD" CMAKE_DEPFILE_FLAGS_C ${CMAKE_DEPFILE_FLAGS_C}) - # ############################################################################## # Target name set(LIB_NAME lib_camera) -set(LIB_NAME_ALIAS lib_camera_general) # Source files file(GLOB_RECURSE SOURCES_C "*.c") -file(GLOB_RECURSE SOURCES_XC "*.xc") file(GLOB_RECURSE SOURCES_CPP "*.cpp") file(GLOB_RECURSE SOURCES_ASM "*.S") add_library(${LIB_NAME} STATIC) -target_include_directories(${LIB_NAME} PUBLIC . api) - -target_sources(${LIB_NAME} PRIVATE ${SOURCES_C} ${SOURCES_XC} ${SOURCES_CPP} - $<$:${SOURCES_ASM}>) - - -# ############################################################################## -# Add and link the library -add_library(${LIB_NAME_ALIAS} INTERFACE) - -target_link_libraries(${LIB_NAME_ALIAS} INTERFACE ${LIB_NAME}) - -add_library(camera::lib_camera ALIAS ${LIB_NAME_ALIAS}) +target_include_directories(${LIB_NAME} PUBLIC api) + +target_sources(${LIB_NAME} + PRIVATE + ${SOURCES_C} + ${SOURCES_CPP} + ${SOURCES_ASM} +) + +target_compile_options(${LIB_NAME} + PRIVATE + -Os + -Wall + -Werror + -g + -fxscope + -mcmodel=large +) + +target_link_libraries(${LIB_NAME} PUBLIC + fwk_camera::mipi + fwk_camera::sensors) + +add_library(fwk_camera::camera ALIAS ${LIB_NAME}) diff --git a/camera/api/camera_api.h b/camera/api/camera_api.h new file mode 100644 index 00000000..130c19d9 --- /dev/null +++ b/camera/api/camera_api.h @@ -0,0 +1,151 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +// xcore +#include "xccompat.h" +// user +#include "sensor.h" + + +#if defined(__XC__) || defined(__cplusplus) +extern "C" { +#endif + +/** + * CLIENT SIDE + * + * Initialize the camera API. Must be called before any other API functions. + */ +void camera_init(); + +/** + * CLIENT SIDE + * + * Stop the camera API. Must be called before exiting the program. + */ +void camera_stop(); + +/** + * SERVER SIDE + * + * Check if the client has requested to stop the camera. + * + * @return 1 if the client has requested to stop the camera, 0 otherwise + */ +unsigned camera_check_stop(); + +/** + * SERVER SIDE + * + * Called by the packet handler when a new row of raw image data is + * available. Typically this will be Bayered image data. + */ +void camera_new_row( + const int8_t pixel_data[H_RAW], + const unsigned row_index); + +/** + * SERVER SIDE + * + * Called by the packet handler when a new row of decimated image data is + * available. + */ +void camera_new_row_decimated( + const int8_t pixel_data[CH][W], + const unsigned row_index); + +/** + * CLIENT SIDE + * + * Called by the client to capture a row of raw image data. + * + * @param pixel_data The buffer to store the row of pixels in + * + * @return The row index of the captured row of pixels + */ +unsigned camera_capture_row( + int8_t pixel_data[W_RAW]); + +/** + * CLIENT SIDE + * + * Called by the client to capture a row of decimated image data. + * + * @param pixel_data The buffer to store the row of pixels in + * + * @return The row index of the captured row of pixels + */ +unsigned camera_capture_row_decimated( + int8_t pixel_data[CH][W]); + +/** + * CLIENT SIDE + * + * Called by the client to capture a raw image. + * + * @param image_buff The buffer to store the image in + * + * @return Returns 0 on success, non-zero on failure + */ +unsigned camera_capture_image_raw( + int8_t image_buff[H_RAW][W_RAW]); + +/** + * CLIENT SIDE + * + * Called by the client to capture a decimated image in [channel][height][width] format. + * + * @param image_buff The buffer to store the image in + * + * @return Returns 0 on success, non-zero on failure + */ +unsigned camera_capture_image_transpose( + int8_t image_buff[CH][H][W]); + +/** + * CLIENT SIDE + * + * Called by the client to capture a decimated image in [height][width][channel] format. + * + * @param image_buff The buffer to store the image in + * + * @return Returns 0 on success, non-zero on failure + */ +unsigned camera_capture_image( + int8_t image_buff[H][W][CH]); + +typedef struct { + struct { + unsigned row; + unsigned col; + } origin; + struct { + unsigned height; + unsigned width; + } shape; +} image_crop_params_t; + +/** + * CLIENT SIDE + * + * Called by the client to capture a portion of a decimated image. If only a + * portion of the decimated image is required, using this function avoids the + * need to store the entire decimated image in memory. + * + * `image_buff` must be a 3D array of + * size `[CH][crop_params.shape.height][crop_params.shape.width]`. + * + * @param image_buff The buffer to store the image in + * @param crop_params The parameters of the crop + * + * @return Returns 0 on success, non-zero on failure + */ +unsigned camera_capture_image_cropped( + int8_t* image_buff, + const image_crop_params_t crop_params); + +#if defined(__XC__) || defined(__cplusplus) +} +#endif diff --git a/camera/api/camera_main.h b/camera/api/camera_main.h new file mode 100644 index 00000000..784e7d07 --- /dev/null +++ b/camera/api/camera_main.h @@ -0,0 +1,72 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include + +#include "xs1.h" +#include "xccompat.h" + +#include "mipi.h" + +#ifndef MIPI_CLKBLK +#define MIPI_CLKBLK XS1_CLKBLK_1 +#endif + +#define MIPI_TILE 1 + +#include "image_hfilter.h" +#include "image_vfilter.h" +#include "packet_handler.h" +#include "statistics.h" +#include "camera_api.h" +#include "camera_utils.h" +#include "isp.h" + +// MIPI Shim configuration register layout (MIPI_SHIM_CFG0) +#define MIPI_SHIM_BIAS_ENABLE 1 // Offset output pixels [1] +#define MIPI_SHIM_STUFF_ENABLE 0 // Add a zero byte after every RGB pixel [2] +#define MIPI_SHIM_DEMUX_MODE 0 // demux mode (see xMIPI_DemuxMode_t), Unused if MIPI_SHIM_DEMUX_EN = 0 +#define MIPI_SHIM_DEMUX_DATATYPE 0 // CSI-2 packet type to demux, Unused if MIPI_SHIM_DEMUX_EN = 0 +#define MIPI_SHIM_DEMUX_EN 0 // MIPI shim 0 = disabled, 1 = enabled + +// Mipi shim clock settings +#define MIPI_CLK_DIV 1 // CLK DIVIDER +#define MIPI_CFG_CLK_DIV 3 // CFG DIVIDER + +/** + * Thread entry point for interfacing with the camera sensor. + * + * This function will configure mipi and initialize the camera sensor + * + * @param p_mipi_clk The MIPI clock input port + * @param p_mipi_rxa The MIPI active input port + * @param p_mipi_rxv The MIPI valid input port + * @param p_mipi_rxd The MIPI data input port + * @param clk_mipi The MIPI clock block + */ +void camera_mipi_init( + port_t p_mipi_clk, + port_t p_mipi_rxa, + port_t p_mipi_rxv, + in_buffered_port_32_t p_mipi_rxd, + xclock_t clk_mipi); + +/* Notes + +[1] +Indicates whether the MIPI shim should apply a bias to received pixel data. +0 = disabled, 1 = enabled (does not require MIPI_SHIM_DEMUX_EN = 1) +The bias subtracts 128 from each received pixel value, converting the +pixel data from uint8 to int8. +TODO: astew: This is currently ignored -- the bias is always enabled in the + downsample app, and disabled in the raw capture app. + +[2] +Indicates whether the MIPI shim should stuff an extra byte into each triplet +of received pixel data, in order to achieve 4-byte alignment of pixel +values. +0 = disabled, 1 = enabled (does not require MIPI_SHIM_DEMUX_EN = 1) + +*/ diff --git a/camera/api/camera_utils.h b/camera/api/camera_utils.h new file mode 100644 index 00000000..60693c93 --- /dev/null +++ b/camera/api/camera_utils.h @@ -0,0 +1,43 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include +#include + +#include "sensor.h" + +#if defined(__XC__) || defined(__cplusplus) +extern "C" { +#endif + +// Print macros +#define PRINT_TIME(a,b) printf("%d\n", b - a); +#define PRINT_NAME_TIME(name,time) \ + printf("\t%s: %u ticks, %.3fms\n", name, time, (float)time * 0.00001); + +/** + * Measure the cpu ticks + * + * @return ticks - Number of ticks + */ +unsigned measure_time(); + +/** + * Convert an array of int8 to an array of uint8. + * + * Data can be updated in-place. + * + * @param output - Array of uint8_t that will contain the output + * @param input - Array of int8_t that contains the input + * @param length - Length of the input and output arrays + */ +void vect_int8_to_uint8( + uint8_t output[], + int8_t input[], + const unsigned length); + +#if defined(__XC__) || defined(__cplusplus) +} +#endif diff --git a/camera/api/image_hfilter.h b/camera/api/image_hfilter.h new file mode 100644 index 00000000..a2bbf1b8 --- /dev/null +++ b/camera/api/image_hfilter.h @@ -0,0 +1,71 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include + +// The FIR filter used for horizontal filtering has 3 taps, and is +// (0.20872991, 0.58254019, 0.20872991) +// Because of symmetry we can use a single coefficient for the even taps. +// The filter coefficients are scaled by a gain derived from the white balance +// algorithm, and then scaled to an appropriate fixed-point format. +#define COEF_B0 (0.58254019f) +#define COEF_B1 (0.20872991f) + +/** + * This structure holds the state for the horizontal filter. + */ +typedef struct { + /// @brief The initial value for the accumulator + int32_t acc_init; + /// @brief The filter coefficients + int8_t coef[32]; + /// @brief The shift applied to the accumulator to get the output + unsigned shift; +} hfilter_state_t; + +/** + * This function performs a horizontal filtering operation on a single row + * of pixels. + * + * The input and output arrays are assumed to be aligned to word boundaries. + * + * The output pixels will be adjacent in memory, so this function is used to + * split an image with interleaved color planes into separate color planes. + * + * @param output The output array of pixels + * @param input The input array of pixels + * @param coef The filter coefficients + * @param acc_init The initial value for the accumulator + * @param shift The shift applied to the accumulator to get the output + * @param input_stride The number of input pixels to skip between output pixels + * @param output_count The number of output pixels to generate + */ +void pixel_hfilter( + int8_t output[], + const int8_t input[], + const int8_t coef[32], + const int32_t acc_init, + const unsigned shift, + const int32_t input_stride, + const unsigned output_count); + +/** + * This function is used to update the filter parameters based on a gain to be + * applied to the color channel represented by `state`. + * + * `offset` is the byte offset (from the beginning of a row of pixels) of the + * first pixel in the color channel represented by `state`. So, for a Bayered + * RGGB image, the offset for the red channel would be 0, and the offset for + * the blue channel would be 1. The offset for the green channel depends on + * whether the red-adjascent or blue-adjascent green pixel is used. + * + * @param state The filter state to update + * @param gain The gain to apply to the filter coefficients + * @param offset The offset into the filter coefficient array to start at. + */ +void pixel_hfilter_update_scale( + hfilter_state_t* state, + const float gain, + const unsigned offset); diff --git a/camera/api/image_vfilter.h b/camera/api/image_vfilter.h new file mode 100644 index 00000000..3329c815 --- /dev/null +++ b/camera/api/image_vfilter.h @@ -0,0 +1,174 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include + +#include "sensor.h" + +// The number of non-zero taps in the vertical filter. +#define VFILTER_TAP_COUNT (5) + +// The effective decimation factor of the vertical filter. +// Note that because of Bayering, this is half the horizontal decimation factor. +#define VFILTER_DEC_FACTOR (APP_DECIMATION_FACTOR / 2) + +// Required: Low-res image width must be multiple of 16 pixels +#if (((APP_IMAGE_WIDTH_PIXELS) >> 4) << 4) != (APP_IMAGE_WIDTH_PIXELS) +# error APP_IMAGE_WIDTH_PIXELS (width of decimated image) must be multiple of 16 pixels. +#endif + +// The number of accumulators required for vertical filtering. +// ceil(tap_count / dec_factor) +#define VFILTER_ACC_COUNT ((VFILTER_TAP_COUNT + VFILTER_DEC_FACTOR - 1)\ + / (VFILTER_DEC_FACTOR)) + +// The value that the next_tap gets reset to after outputting a row. +#define VFILTER_RESET_INDEX ((VFILTER_TAP_COUNT) - \ + ( (VFILTER_DEC_FACTOR) * (VFILTER_ACC_COUNT) )) + +#define VFILTER_ACC_WIDTH_SHORTS (2 * APP_IMAGE_WIDTH_PIXELS) + +#if defined(__XC__) || defined(__cplusplus) +extern "C" { +#endif + +typedef struct { + int next_tap; + int16_t buff[VFILTER_ACC_WIDTH_SHORTS]; +} vfilter_acc_t; + +/** + * Initialize a vector of 32-bit split accumulators to a given value. + * + * This function is used to initialize a set of accumulators to an offset value + * to prepare for filtering. + * + * The `pixel_vfilter_*()` functions use the VPU in 8-bit mode. All accumulators + * are initialized to the same 32-bit value, with the values split as required + * by the VPU. + * + * Accordingly, `pix_count` must be a multiple of 16. + * + * @param accs The vector of accumulators to initialize. + * @param acc_value The value to initialize the accumulators to. + * @param pix_count The number of accumulators to initialize. + */ +void pixel_vfilter_acc_init( + int16_t *accs, + const int32_t acc_value, + const unsigned pix_count); + +/** + * Generate output pixel values from a vector of accumulators. + * + * This function is to be called on a row of accumulators after the final filter + * tap has been applied. The 32-bit accumulators are right-shifted by the values + * in `shifts[]` (with rounding), saturated to 8-bit symmetric bounds, and + * dropped from int32_t to int8_t before being written to `pix_out[]`. + * + * `pix_out[]`, `accs` and `shifts` must each be aligned to a 4-byte boundary. + * + * `accs` is expected to be formatted according to the VPU's split 32-bit + * accumulator format. + * + * `shifts[k]` will be applied to accumulators with indices `k mod 16`. Usually, + * each `shift[k]` will be the same. + * + * Due to VPU limitations, `pix_count` must be a multiple of 16. + * + * `pix_count` bytes will be written to `pix_out[]`. + * + * @param pix_out The output pixel values. + * @param accs The vector of accumulators to generate output from. + * @param shifts The right-shifts to apply to each accumulator. + * @param pix_count The number of output pixels. + */ +void pixel_vfilter_complete( + int8_t *pix_out, + const int16_t *accs, + const int16_t shifts[16], + const unsigned pix_count); + +/** + * Apply a filter tap to a vector of accumulators using the supplied input + * pixels. + * + * This function is used to apply a single filter tap to a vector of + * accumulators (i.e. `pix_count` separate filters are updated). + * + * If `ACC32[k]` is the 32-bit accumulator associated with output pixel `k`, + * then this function computes: + * + * ACC32[k] += (pix_in[k] * filter[k % 16]) + * + * Because this function is used for incrementally applying the same vertical + * filter across many columns of an image, the values `filter[k]` will typically + * all be the same value, `COEF` for a given call to this function, simplifying + * to: + * + * ACC32[k] += (pix_in[k] * COEF) + * + * The `pix_in[]` array is expected to contain `pix_count` 8-bit pixels. + * + * `accs` is expected to be formatted according to the VPU's split 32-bit + * accumulator format. + * + * `accs`, `pix_in` and `filter[]` must each be 4-byte aligned. + * + * Due to VPU limitations, `pix_count` must be a multiple of 16. + * + * When the final filter tap is applied, `pixel_vfilter_complete()` should be + * called to generate output pixels from the accumulators. + * + * @param accs The vector of accumulators to apply the filter tap to. + * @param pix_in The input pixels to apply to the filter. + * @param filter Vector containing filter coefficients. + * @param pix_count The number of pixels to apply the filter to. + */ +void pixel_vfilter_macc( + int16_t *accs, + const int8_t *pix_in, + const int8_t filter[16], + const unsigned pix_count); + +/** + * @brief Initialize a vector of vertical filter accumulators. + * + * @param accs The vector of accumulators to initialize. + */ +void image_vfilter_frame_init( + vfilter_acc_t accs[]); + +/** + * @brief Apply a filter tap to a vector of accumulators using the supplied input + * + * @param output output vector after the filtering process + * @param acc vector of accumulators + * @param pixel_data input data + * @return unsigned 1 if rows are finished + */ +unsigned image_vfilter_process_row( + int8_t output[], + vfilter_acc_t acc[], + const int8_t pixel_data[]); + +/** + * After the last line of the image, some of the accumulators will be midway + * through processing the image but still need to be output without maccing + * any more inputs. + * + * Keep calling this until it returns 0. + * + * @param output output vector after the filtering process + * @param acc array of accumulators + * @return unsigned 0 when finished + */ +unsigned image_vfilter_drain( + int8_t output[], + vfilter_acc_t acc[]); + +#if defined(__XC__) || defined(__cplusplus) +} +#endif diff --git a/camera/api/isp.h b/camera/api/isp.h new file mode 100644 index 00000000..ce756254 --- /dev/null +++ b/camera/api/isp.h @@ -0,0 +1,215 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include // memset +#include // null +#include + +#include "statistics.h" // needed for global_stats_t + +// ISP settings +#define AE_MARGIN 0.1 // default marging for the auto exposure error +#define AE_INITIAL_EXPOSURE 35 // initial exposure value +#define AWB_gain_RED 1.538 +#define AWB_gain_GREEN 1.0 +#define AWB_gain_BLUE 1.587 +#define AWB_MAX 1.7 +#define AWB_MIN 0.8 +#define APPLY_GAMMA 1 +#define ENABLE_PRINT_STATS 0 + +// ---------------------------------- ISP PIPELINE ---------------------------------- +#if defined(__XC__) +extern "C" { +#endif + +void isp_pipeline( + streaming_chanend_t c_img_in, + chanend c_control); + +#if defined(__XC__) +} +#endif + +// ---------------------------------- AE/AGC ------------------------------ + +/** + * @brief auto exposure control funciton + * + * @param global_stats structure containing the global statistics + */ +uint8_t AE_control_exposure( + global_stats_t *global_stats, + chanend c_exp); + +/** + * @brief aux function to print the skewness + * + * @param gstats structure containing the global statistics + */ +void AE_print_skewness(global_stats_t *gstats); + +/** + * @brief function to compute the mean skewness + * + * @param gstats structure containing the global statistics + * @return float skewness normalized from (-1,1) + */ +float AE_compute_mean_skewness(global_stats_t *gstats); + +/** + * @brief checks if the skewness is inside the desired interval + * + * @param sk skewness + * @return uint8_t 1 if the skewness is inside the interval, 0 otherwise + */ +uint8_t AE_is_adjusted(float sk); + +/** + * @brief computes the new exposure value + * it uses false position step for computing the exposure + * https://research.ijcaonline.org/volume83/number14/pxc3892895.pdf + * @param exposure + * @param skewness + * @return uint8_t + */ +uint8_t AE_compute_new_exposure(float exposure, float skewness); + +// ---------------------------------- AWB ------------------------------ + +/** + * struct to hold the calculated parameters for the ISP + */ +typedef struct { + float channel_gain[APP_IMAGE_CHANNEL_COUNT]; +} isp_params_t; + +// current isp parameters for white balancing +extern isp_params_t isp_params; + +/** + * @brief auto white balance control function + * + * @param isp_params structure containing the current isp parameters + */ +void AWB_compute_gains_static(isp_params_t *isp_params); + +/** + * @brief auto white balance control function based on white patch algorithm + * + * @param gstats structure containing the global statistics + * @param isp_params structure containing the current isp parameters + */ +void AWB_compute_gains_white_patch(global_stats_t *gstats, isp_params_t *isp_params); + +/** + * @brief auto white balance control function based on gray world algorithm + * + * @param gstats structure containing the global statistics + * @param isp_params structure containing the current isp parameters + */ +void AWB_compute_gains_gray_world(global_stats_t *gstats, isp_params_t *isp_params); + +/** + * @brief auto white balance control function based on white max algorithm + * + * @param gstats structure containing the global statistics + * @param isp_params structure containing the current isp parameters + */ +void AWB_compute_gains_white_max(global_stats_t *gstats, isp_params_t *isp_params); + +/** + * @brief auto white balance control function based on percentile + * + * @param gstats structure containing the global statistics + * @param percentile percentile to compute + */ +void AWB_compute_gains_percentile(global_stats_t *gstats, isp_params_t *isp_params); + +/** + * @brief aux function to print the auto white balancing gains + * + * @param isp_params structure containing the current isp parameters + */ +void AWB_print_gains(isp_params_t *isp_params); + +// ---------------------------------- GAMMA ------------------------------ +// Gamma correction table +extern const uint8_t gamma_uint8[256]; +extern const int8_t gamma_int8[256]; + +/** + * @brief compute gamma curve given an image and the gamma adjustement + * + * @param img_in pointer to the image + * @param gamma_curve pointer to the gamma curve + * @param height image height + * @param width image width + * @param channels channels in the image + */ +void isp_gamma(uint8_t *img_in, const uint8_t *gamma_curve, const size_t height, const size_t width, const size_t channels); + +// -------------------------- ROTATE/RESIZE ------------------------------------- + +/** + * @brief bilinear interpolation function + * + * @param in_width width of the input image + * @param in_height height of the input image + * @param img pointer to the input image + * @param out_width output width + * @param out_height output height + * @param out_img pointer to the output image + */ +void isp_bilinear_resize( + const uint16_t in_width, + const uint16_t in_height, + uint8_t *img, + const uint16_t out_width, + const uint16_t out_height, + uint8_t *out_img); + +/** +* Rotate the image by 90 degrees. This is useful for rotating images that are stored in a 3x3 array of uint8_t +* +* @param image - Array of uint8_t that is to be +*/ +void rotate_image_90(uint8_t image_buffer[APP_IMAGE_CHANNEL_COUNT][APP_IMAGE_HEIGHT_PIXELS][APP_IMAGE_WIDTH_PIXELS]); + +// -------------------------- COLOR CONVERSION ------------------------------------- +// Macro arguments to get color components from packed result in the assembly program +#define GET_R(rgb) (rgb & 0xFF) +#define GET_G(rgb) ((rgb >> 8) & 0xFF) +#define GET_B(rgb) ((rgb >> 16)& 0xFF) + +#define GET_Y(yuv) GET_R(yuv) +#define GET_U(yuv) GET_G(yuv) +#define GET_V(yuv) GET_B(yuv) + +/** + * @brief converts a YUV pixel to RGB + * + * @param y Y component + * @param u U component + * @param v V component + * @return int result of rgb conversion (need macros to decode output) + */ +int yuv_to_rgb( + int y, + int u, + int v); + +/** + * @brief converts a RGB pixel to YUV + * + * @param r red component + * @param g green component + * @param b blue component + * @return int result of yuv conversion (need macros to decode output) + */ +int rgb_to_yuv( + int r, + int g, + int b); diff --git a/camera/api/packet_handler.h b/camera/api/packet_handler.h new file mode 100644 index 00000000..955a525c --- /dev/null +++ b/camera/api/packet_handler.h @@ -0,0 +1,36 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include + +#include "xccompat.h" + +#include "camera_main.h" +#include "isp.h" + +/** + * Represents a received MIPI packet. + */ +typedef struct +{ + mipi_header_t header; + uint8_t payload[MIPI_MAX_PKT_SIZE_BYTES]; +} mipi_packet_t; + +/** + * isp_params_t instance owned by the packet handler. + */ +extern +isp_params_t isp_params; + +/** + * Thread entry point for packet handling when decimation and demosaicing are + * desired. + */ +void mipi_packet_handler( + streaming_chanend_t c_pkt, + streaming_chanend_t c_ctrl, + streaming_chanend_t c_out_row); + diff --git a/camera/api/statistics.h b/camera/api/statistics.h new file mode 100644 index 00000000..812c2379 --- /dev/null +++ b/camera/api/statistics.h @@ -0,0 +1,89 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include +#include // memset +#include // null +#include // free, alloc +#include + +#include "xccompat.h" + +#include "sensor.h" +#include "image_hfilter.h" + +#define CH_CARD (1<> (HIST_QUANT_BITS)) // Number of histogram bins +#define APP_WB_PERCENTILE (0.94) + +#if (HISTOGRAM_BIN_COUNT != 64) + #error HISTOGRAM_BIN_COUNT value not currently supported. +#endif + +// ---------- Objects definitions ---------- +typedef struct { + int8_t pixels[APP_IMAGE_CHANNEL_COUNT][APP_IMAGE_WIDTH_PIXELS]; +} low_res_image_row_t; + +typedef struct { + uint32_t bins[HISTOGRAM_BIN_COUNT]; +} channel_histogram_t; + +typedef struct { + uint8_t min; + uint8_t max; + uint8_t percentile; + float skewness; + float mean; + uint32_t max_count; + uint32_t min_count; + uint32_t per_count; + channel_histogram_t histogram; +} channel_stats_t; + +typedef channel_stats_t global_stats_t[APP_IMAGE_CHANNEL_COUNT]; + +// ---------- Api functions ---------- + +/** + * @brief Update the histogram given a row of pixels + * + * @param hist current histogram + * @param pix row of pixels + */ +void stats_update_histogram(channel_histogram_t *hist, const int8_t pix[]); + +/** +* Compute simple statistics for a set of data. +* @param stats - * Pointer to the channel statistics to be computed +*/ +void stats_simple(channel_stats_t *stats); + +/** + * @brief Compute the skewness of a channel + * + * @param stats + */ +void stats_skewness(channel_stats_t *stats); + +/** + * Find the value for which (fraction) portion of pixels fall below that value. + */ +void stats_percentile(channel_stats_t *stats, const float fraction); + +/** + * @brief Compute the volume of a channel after its percentile + * + * @param stats channel statistics + */ +void stats_percentile_volume(channel_stats_t *stats); + +/** + * @brief print the statistics of a channel + * + * @param stats + * @param channel + */ +void stats_print(channel_stats_t *stats, unsigned channel); diff --git a/camera/isp.c b/camera/isp.c deleted file mode 100644 index b4643272..00000000 --- a/camera/isp.c +++ /dev/null @@ -1,233 +0,0 @@ -#include "isp.h" - -// ---------------------------------- AE / AGC ------------------------------ -uint8_t csign(float x) { - return (x > 0) - (x < 0); -} - -uint8_t isp_false_position_step(float exposure, float skewness) -{ - static float a = 0; - static float fa = -1; - static float b = 80; - static float fb = 1; - static int count = 0; - float c = exposure; - float fc = skewness; - - if(csign(fc) == csign(fa)){ - a = c; fa = fc; - } - else{ - b = c; fb = fc; - } - c = b - fb*((b - a)/(fb - fa)); - - // each X samples, restart AE algorithm - if (count < 20){ - count = count + 1; - } - else{ - // restart auto exposure - count = 0; - a = 0; - fa = -1; - b = 80; - fb = 1; - } - return c; -} - - -// ---------------------------------- AWB ------------------------------ -void isp_AWB_static(const uint32_t buffsize, - uint8_t *red, - uint8_t *green, - uint8_t *blue, - float alfa, - float beta, - float delta) -{ - if (delta == 0){ // just red and blue - for (uint32_t i=0; i < buffsize; i++){ - red[i] = (uint8_t) alfa*red[i]; - blue[i] = (uint8_t) beta*blue[i]; - } - } - else{ - for (uint32_t i=0; i < buffsize; i++){ - red[i] = (uint8_t) alfa*red[i]; - blue[i] = (uint8_t) beta*blue[i]; - green[i] = (uint8_t) delta*green[i]; - } - } -} - -void isp_AWB_gray_world(const uint32_t buffsize, - uint8_t *red, - uint8_t *green, - uint8_t *blue) -{ - // compute stadistics by channel - Stadistics st_red; - Stadistics st_green; - Stadistics st_blue; - uint8_t step = 1; - - Stadistics_compute_all(buffsize, step, red, &st_red); - Stadistics_compute_all(buffsize, step, red, &st_green); - Stadistics_compute_all(buffsize, step, red, &st_blue); - - // apply white balancing - float alfa = (st_green.mean)/(st_red.mean); - float beta = (st_green.mean)/(st_blue.mean); - isp_AWB_static(buffsize, red, green, blue, alfa, beta, 0); -} - -void isp_AWB_percentile(const uint32_t buffsize, - uint8_t *red, - uint8_t *green, - uint8_t *blue) -{ - // compute stadistics by channel - Stadistics st_red; - Stadistics st_green; - Stadistics st_blue; - uint8_t step = 1; - - Stadistics_compute_all(buffsize, step, red, &st_red); - Stadistics_compute_all(buffsize, step, red, &st_green); - Stadistics_compute_all(buffsize, step, red, &st_blue); - - // apply white balancing - float alfa = 255/(st_red.percentile); - float beta = 255/(st_blue.percentile); - float delta = 255/(st_red.percentile); - - isp_AWB_static(buffsize, red, green, blue, alfa, beta, delta); -} - - - -// ---------------------------------- GAMMA ------------------------------ -/** -* Apply 1.8 gamma to each pixel in an image using stride 1 -* it take 255 bytes of memory, 0MUL, 0DIV, 2MEM ACCESS -* @param buffsize - Size of the buffer to operate on. -* @param img - * Pointer to the image to operate on. Modified -*/ -void isp_gamma_stride1(const uint32_t buffsize, uint8_t *img){ - // gamma naming: 1p8_s1 = gamma 1.8 , with a stride of 1 - // 1p8_s4 => img^(1/1.8) (in a normalizeed 0-1 image) - const uint8_t gamma_1p8_s1[255] = { - 0,12,17,22,25,29,32,35,37,40,42,44,47,49,51,53,55,57,58,60,62,64,65,67,69, - 70,72,73,75,76,78,79,80,82,83,85,86,87,89,90,91,92,94,95,96,97,98,100,101, - 102,103,104,105,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121, - 122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,139, - 140,141,142,143,144,145,146,146,147,148,149,150,151,152,152,153,154,155,156, - 157,157,158,159,160,161,161,162,163,164,165,165,166,167,168,169,169,170,171, - 172,172,173,174,175,175,176,177,178,178,179,180,181,181,182,183,183,184,185, - 186,186,187,188,188,189,190,191,191,192,193,193,194,195,195,196,197,198,198, - 199,200,200,201,202,202,203,204,204,205,206,206,207,208,208,209,209,210,211, - 211,212,213,213,214,215,215,216,217,217,218,218,219,220,220,221,222,222,223, - 223,224,225,225,226,226,227,228,228,229,230,230,231,231,232,233,233,234,234, - 235,236,236,237,237,238,238,239,240,240,241,241,242,243,243,244,244,245,245, - 246,247,247,248,248,249,249,250,251,251,252,252,253,253,254,255}; - - for(uint32_t i=0; i img^(1/1.8) (in a normalizeed 0-1 image) - const uint8_t gamma_1p8_s4[64] = { - 0,6,9,12,14,15,17,19,20,21,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38, - 39,39,40,41,42,42,43,44,45,45,46,47,48,48,49,50,50,51,52,52,53,54,54,55,55, - 56,57,57,58,58,59,60,60,61,61,62,62,63 - }; - static const uint8_t stride = 4; - - for(uint32_t i=0; i // memset -#include // null -#include - -#include "stadistics.h" // for dynamic AWB - -// ----------------- ISP SETTINGS ------------------------------- -#define DEFAULT_ALFA 1.58682 // default red gain (not used for the moment) -#define DEFAULT_BETA 1.52535 // default blue gain (not used for the moment) -#define ENABLE_AE 1 // enable auto exposure -#define AE_MARGIN 0.1 // default marging for the auto exposure error -#define STEP 16 // histogram step size - - -// ---------------------------------- AE/AGC ------------------------------ -uint8_t csign(float x); -uint8_t isp_false_position_step(float exposure, float skewness); - -// ---------------------------------- AWB ------------------------------ -void isp_AWB_gray_world(const uint32_t buffsize, uint8_t *red, uint8_t *green, uint8_t *blue); -void isp_AWB_percentile(const uint32_t buffsize, uint8_t *red, uint8_t *green, uint8_t *blue); -void isp_AWB_static(const uint32_t buffsize, uint8_t *red, uint8_t *green, uint8_t *blue, float alfa, float beta, float delta); - -// ---------------------------------- GAMMA ------------------------------ -void isp_gamma_stride1(const uint32_t buffsize, uint8_t *img); -void isp_gamma_stride4(const uint32_t buffsize, uint8_t *img); - -// -------------------------- ROTATE/RESIZE ------------------------------------- -void isp_bilinear_resize(const uint16_t in_width, const uint16_t in_height, - uint8_t *img, - const uint16_t out_width, const uint16_t out_height, - uint8_t *out_img); - -void isp_rotate_image(const uint8_t* src, uint8_t* dest, int width, int height); - -#endif \ No newline at end of file diff --git a/camera/src/asm/pixel_hfilter.S b/camera/src/asm/pixel_hfilter.S new file mode 100644 index 00000000..3c52c81e --- /dev/null +++ b/camera/src/asm/pixel_hfilter.S @@ -0,0 +1,142 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +.issue_mode dual + +#define FUNCTION_NAME pixel_hfilter +#define NSTACKWORDS 36 + +.globl FUNCTION_NAME.nstackwords +.globl FUNCTION_NAME.maxthreads +.globl FUNCTION_NAME.maxtimers +.globl FUNCTION_NAME.maxchanends + +.linkset FUNCTION_NAME.nstackwords, NSTACKWORDS +.linkset FUNCTION_NAME.maxchanends, 0 +.linkset FUNCTION_NAME.maxtimers, 0 +.linkset FUNCTION_NAME.maxthreads, 0 + +.globl FUNCTION_NAME +.type FUNCTION_NAME, @function +.text +.cc_top FUNCTION_NAME.func, FUNCTION_NAME + +/* + **************************************************** + **************************************************** + +void pixel_hfilter( + int8_t output[], + const int8_t input[], + const int8_t coef[32], + const int32_t acc_init, + const unsigned shift, + const int32_t input_stride, + const unsigned output_count); + + **************************************************** + **************************************************** +*/ + +#define STK_SHIFT (NSTACKWORDS+1) +#define STK_IN_STR (NSTACKWORDS+2) +#define STK_OUT_LEN (NSTACKWORDS+3) + +#define STK_VEC_ACC_HI (NSTACKWORDS-8) +#define STK_VEC_ACC_LO (NSTACKWORDS-16) +#define STK_VEC_SHIFT (NSTACKWORDS-24) + +#define output r0 +#define input r1 +#define coef r2 +#define acc_init r3 + +#define shift r5 +#define in_str r6 +#define len r7 +#define _16 r8 +#define mask r9 + + +.align 4 +.skip 0 +FUNCTION_NAME: + dualentsp NSTACKWORDS + std r4, r5, sp[1] + std r6, r7, sp[2] + std r8, r9, sp[3] + +// First, broadcast the acc_init and shift values to the vector registers +{ mov r11, acc_init ; ldaw r4, sp[STK_VEC_ACC_HI] } + zip r11, acc_init, 4 + std r11, r11, r4[0] + std r11, r11, r4[1] + std r11, r11, r4[2] + std r11, r11, r4[3] +{ ldaw r4, sp[STK_VEC_ACC_LO] ; ldw r11, sp[STK_SHIFT] } + std acc_init, acc_init, r4[0] + std acc_init, acc_init, r4[1] + std acc_init, acc_init, r4[2] + std acc_init, acc_init, r4[3] +{ shl acc_init, r11, 16 ; ldaw r4, sp[STK_VEC_SHIFT] } +{ or acc_init, acc_init, r11 ; } + std acc_init, acc_init, r4[0] + std acc_init, acc_init, r4[1] + std acc_init, acc_init, r4[2] + std acc_init, acc_init, r4[3] + + + ldc r11, 0x200 +{ ldc _16, 16 ; vsetc r11 } +{ mkmsk mask, 4 ; ldw len, sp[STK_OUT_LEN] } +{ and mask, len, mask ; ldaw r11, sp[STK_VEC_ACC_LO]} +// if len isn't a multiple of 16 we're gonna do a bad thing. +{ ecallt mask ; ldaw shift, sp[STK_VEC_SHIFT]} +{ mkmsk mask, 16 ; ldw in_str, sp[STK_IN_STR] } +// Start at the end so we can proceed monotonically +// input <-- input + (len*in_str) +// output <-- output + (len) +{ add output, output, len ; vldc coef[0] } +maccu coef, input, len, in_str +ldaw acc_init, sp[STK_VEC_ACC_HI] + +// We subtract at the beginning of the loop so it should work out correctly. + +.L_loop_top: + { sub input, input, in_str ; vldd acc_init[0] } + { sub output, output, _16 ; vldr r11[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { sub input, input, in_str ; vlmaccr input[0] } + { ; vlmaccr input[0] } + { sub len, len, _16 ; vlsat shift[0] } + vstrpv output[0], mask + { ; bt len, .L_loop_top } + + + + ldd r8, r9, sp[3] + ldd r6, r7, sp[2] + ldd r4, r5, sp[1] + retsp NSTACKWORDS + +.size FUNCTION_NAME, .-FUNCTION_NAME +.cc_bottom FUNCTION_NAME.func + + + diff --git a/camera/src/asm/pixel_vfilter_acc_init.S b/camera/src/asm/pixel_vfilter_acc_init.S new file mode 100644 index 00000000..046482c9 --- /dev/null +++ b/camera/src/asm/pixel_vfilter_acc_init.S @@ -0,0 +1,98 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +.issue_mode dual + +#define FUNCTION_NAME pixel_vfilter_acc_init +#define NSTACKWORDS 12 + +.globl FUNCTION_NAME.nstackwords +.globl FUNCTION_NAME.maxthreads +.globl FUNCTION_NAME.maxtimers +.globl FUNCTION_NAME.maxchanends + +.linkset FUNCTION_NAME.nstackwords, NSTACKWORDS +.linkset FUNCTION_NAME.maxchanends, 0 +.linkset FUNCTION_NAME.maxtimers, 0 +.linkset FUNCTION_NAME.maxthreads, 0 + +.globl FUNCTION_NAME +.type FUNCTION_NAME, @function +.text +.cc_top FUNCTION_NAME.func, FUNCTION_NAME + +/* + **************************************************** + **************************************************** + + Initialize a row of accumulators for vertical decimation. + + void pixel_vfilter_acc_init( + int16_t* accs, + const int32_t acc_value, + const unsigned pix_count); + + **************************************************** + **************************************************** +*/ + +#define STK_VEC_TMP (NSTACKWORDS-8) + +#define accs r0 +#define value r1 +#define len r2 +#define tmp r3 + +#define _32 r4 + + +.align 4 +.skip 0 +FUNCTION_NAME: + dualentsp NSTACKWORDS + std r4, r5, sp[1] + +// broadcast the high and low accumulator initialization values into vectors + +{ ; mov tmp, value } + zip tmp, value, 4 +{ ldaw r11, sp[STK_VEC_TMP] ; ldc _32, 32 } + std tmp, tmp, r11[0] + std tmp, tmp, r11[1] + std tmp, tmp, r11[2] + std tmp, tmp, r11[3] +{ shr r5, len, 4 ; vldd r11[0] } + std value, value, r11[0] + std value, value, r11[1] + std value, value, r11[2] + std value, value, r11[3] +{ zext len, 4 ; vldr r11[0] } + +// for now, assume the len is multiple of 16 and raise an exception if not. +// Part of the problem with supporting a tail is that if it is supported here +// it must also be supported everywhere else. Because the high and low half +// words are split into groups of 16 (required by VPU), if the last group has +// for example, 4, then how do we place those in memory? like: +// acc_hi,acc_hi,acc_hi,acc_hi,acc_lo,acc_lo,acc_lo,acc_lo ? +// or like: +// acc_hi,acc_hi,acc_hi,acc_hi,0,0,0,0,0,0,0,0,0,0,0,0,acc_lo,acc_lo,acc_lo,acc_lo,0,0,0,0,0,0,0,0,0,0,0,0,? +// if the former, then all the other functions need to know that spacing. If +// the latter, then we're effectively already requiring that space be +// allocated for a multiple of 16 anyway. + ecallt len + +.L_loop_top: + { add accs, accs, _32 ; vstd accs[0] } + { sub r5, r5, 1 ; vstr accs[0] } + { add accs, accs, _32 ; bt r5, .L_loop_top } + + +.L_finish: + ldd r4, r5, sp[1] + retsp NSTACKWORDS + +.size FUNCTION_NAME, .-FUNCTION_NAME +.cc_bottom FUNCTION_NAME.func diff --git a/camera/src/asm/pixel_vfilter_complete.S b/camera/src/asm/pixel_vfilter_complete.S new file mode 100644 index 00000000..37e45e63 --- /dev/null +++ b/camera/src/asm/pixel_vfilter_complete.S @@ -0,0 +1,86 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +.issue_mode dual + +#define FUNCTION_NAME pixel_vfilter_complete +#define NSTACKWORDS 6 + +.globl FUNCTION_NAME.nstackwords +.globl FUNCTION_NAME.maxthreads +.globl FUNCTION_NAME.maxtimers +.globl FUNCTION_NAME.maxchanends + +.linkset FUNCTION_NAME.nstackwords, NSTACKWORDS +.linkset FUNCTION_NAME.maxchanends, 0 +.linkset FUNCTION_NAME.maxtimers, 0 +.linkset FUNCTION_NAME.maxthreads, 0 + +.globl FUNCTION_NAME +.type FUNCTION_NAME, @function +.text +.cc_top FUNCTION_NAME.func, FUNCTION_NAME + +/* + **************************************************** + **************************************************** + + Call after accumulation to output a line of pixels. + + pix_count must be a multiple of 4. + + void pixel_vfilter_complete( + int8_t* pix_out, + const int16_t* accs, + const int16_t shifts[16], + const unsigned pix_count); + + **************************************************** + **************************************************** +*/ + +#define pix_out r0 +#define accs r1 +#define shifts r2 +#define len r3 + +#define _32 r4 +#define _16 r5 + +#define mask r6 +#define tmp r7 + + +.align 4 +.skip 0 +FUNCTION_NAME: + dualentsp NSTACKWORDS + std r4, r5, sp[1] + std r6, r7, sp[2] + + ldc r11, 0x200 +{ ldc _32, 32 ; vsetc r11 } +{ ldc _16, 16 ; mov tmp, len } +{ shr len, len, 4 ; zext tmp, 4 } +{ mkmsk mask, _16 ; bu .L_loop } + +// len must be a multiple of 16 + ecallt tmp + +.align 16 +.L_loop: + { add r11, accs, _32 ; vldd accs[0] } + { add accs, r11, _32 ; vldr r11[0] } + { sub len, len, 1 ; vlsat shifts[0] } + vstrpv pix_out[0], mask + { add pix_out, pix_out, _16 ; bt len, .L_loop } + + ldd r6, r7, sp[2] + ldd r4, r5, sp[1] + retsp NSTACKWORDS + +.size FUNCTION_NAME, .-FUNCTION_NAME +.cc_bottom FUNCTION_NAME.func diff --git a/camera/src/asm/pixel_vfilter_macc.S b/camera/src/asm/pixel_vfilter_macc.S new file mode 100644 index 00000000..d316e903 --- /dev/null +++ b/camera/src/asm/pixel_vfilter_macc.S @@ -0,0 +1,75 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +.issue_mode dual + +#define FUNCTION_NAME pixel_vfilter_macc +#define NSTACKWORDS 4 + +.globl FUNCTION_NAME.nstackwords +.globl FUNCTION_NAME.maxthreads +.globl FUNCTION_NAME.maxtimers +.globl FUNCTION_NAME.maxchanends + +.linkset FUNCTION_NAME.nstackwords, NSTACKWORDS +.linkset FUNCTION_NAME.maxchanends, 0 +.linkset FUNCTION_NAME.maxtimers, 0 +.linkset FUNCTION_NAME.maxthreads, 0 + +.globl FUNCTION_NAME +.type FUNCTION_NAME, @function +.text +.cc_top FUNCTION_NAME.func, FUNCTION_NAME + +/* + **************************************************** + **************************************************** + + void pixel_vfilter_macc( + int16_t* accs, + const int8_t* pix_in, + const int8_t filter[16], + const unsigned length_bytes); + + **************************************************** + **************************************************** +*/ + +#define accs r0 +#define pix_in r1 +#define filter r2 +#define len r3 + +#define _32 r4 + + +.align 4 +.skip 0 +FUNCTION_NAME: + dualentsp NSTACKWORDS + std r4, r5, sp[1] + + ldc r11, 0x200 +{ mov r4, len ; shr len, len, 4 } +{ zext r4, 4 ; vsetc r11 } +//len must be multiple of 16 +{ ecallt r4 ; vldc filter[0] } +{ ldc _32, 32 ; bu .L_acc_loop } + +.align 16 +.L_acc_loop: + { add r11, accs, _32 ; vldd accs[0] } + { sub len, len, 1 ; vldr r11[0] } + { add pix_in, pix_in, 8 ; vlmacc pix_in[0] } + { add accs, r11, _32 ; vstd accs[0] } + { add pix_in, pix_in, 8 ; vstr r11[0] } + { ; bt len, .L_acc_loop } + + ldd r4, r5, sp[1] + retsp NSTACKWORDS + +.size FUNCTION_NAME, .-FUNCTION_NAME +.cc_bottom FUNCTION_NAME.func diff --git a/camera/src/asm/rgb_to_yuv.S b/camera/src/asm/rgb_to_yuv.S new file mode 100644 index 00000000..43ab01d5 --- /dev/null +++ b/camera/src/asm/rgb_to_yuv.S @@ -0,0 +1,81 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +/* +Converts signed rgb (-127..127, -127..127, -127..127) into signed yuv. +Returns an int with 3 x int8_t + + int rgb_to_yuv( + int r, + int g, + int b); +*/ + +.cc_top rgb_to_yuv.func, rgb_to_yuv +.align 4 +.globl rgb_to_yuv +.globl rgb_to_yuv.nstackwords +.linkset rgb_to_yuv.nstackwords, 4 +.issue_mode dual + + +#define r r0 +#define g r1 +#define b r2 +#define _22 r3 +#define nstackwords 8 + +rgb_to_yuv: + +// r4 - r10 are not used here + +{ ldc r11, 0 ; dualentsp nstackwords } // Enable dual instructions +{ ldc _22, 22 ; vsetc r11 } // Load 22 in R3 ; control register and the headroom in the vector unit. Signed 32-bit integer +{ shl r, r, _22 ; ldaw r11, sp[0] } // Multiply by 2^22; load word in the Sp + +{ shl g, g, _22 ; stw r, r11[0] } // Multiply by 2^22; store y in the vpu +{ shl b, b, _22 ; stw g, r11[1] } // Multiply by 2^22; store u in the vpu +{ ldc r3, 32 ; stw b, r11[2] } // Load constant; store v in the vpu + +#undef _22 +#define _32 r3 +#define _24 r1 + +#undef y +#undef u +#undef v + +{ ldc _24, 24 ; vclrdr } // load in R1 24 ; Sets the contents of vD and vR in the vector unit to all zeroes. +{ neg _24, _24 ; vldc r11[0] } // signed negation ; load first opdfand +ldap r11, Vconv // Load effective address relative to the program counter. +{ add r11, r11, _32 ; vlmaccr r11[0] } +{ add r11, r11, _32 ; vlmaccr r11[0] } +{ ldaw r11, sp[0] ; vlmaccr r11[0] } + +{ ; vstr r11[0] } + vlashr r11[0], _24 +{ ; vdepth8 } +{ ; vstr r11[0] } +{ ; ldw r0, sp[0] } +retsp nstackwords + +// the values correspond to the matrix values *255 +/* +Vconv: .word 157, -132, -26, 0, 0, 0, 0, 0 +Uconv: .word -38, -74, 112, 0, 0, 0, 0, 0 +Yconv: .word 77, 150, 29, 0, 0, 0, 0, 0 +*/ + +Vconv: .word 128, -107, -21, 0, 0, 0, 0, 0 +Uconv: .word -43, -85, 128, 0, 0, 0, 0, 0 +Yconv: .word 77, 150, 29, 0, 0, 0, 0, 0 + + +.cc_bottom rgb_to_yuv.func + + +// References +// https://en.wikipedia.org/wiki/YUV +// https://softpixel.com/~cwright/programming/colorspace/yuv/ +// https://www.mikekohn.net/file_formats/yuv_rgb_converter.php +// diff --git a/camera/src/asm/yuv_to_rgb.S b/camera/src/asm/yuv_to_rgb.S new file mode 100644 index 00000000..4de2f307 --- /dev/null +++ b/camera/src/asm/yuv_to_rgb.S @@ -0,0 +1,68 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +/* +Converts signed yuv (-127..127, -127..127, -127..127) into signed rgb. +Returns an int with 3 x int8_t + + int yuv_to_rgb( + int y, + int u, + int v); +*/ + +.cc_top yuv_to_rgb.func, yuv_to_rgb +.align 4 +.globl yuv_to_rgb +.globl yuv_to_rgb.nstackwords +.linkset yuv_to_rgb.nstackwords, 4 +.issue_mode dual + + +#define y r0 +#define u r1 +#define v r2 +#define _22 r3 +#define nstackwords 8 + +yuv_to_rgb: + +// r4 - r10 are not used here + +{ ldc r11, 0 ; dualentsp nstackwords } // Enable dual instructions +{ ldc _22, 22 ; vsetc r11 } // Load 22 in R3 ; control register and the headroom in the vector unit. Signed 32-bit integer +{ shl y, y, _22 ; ldaw r11, sp[0] } // Multiply by 2^22; load word in the Sp + +{ shl u, u, _22 ; stw y, r11[0] } // Multiply by 2^22; store y in the vpu +{ shl v, v, _22 ; stw u, r11[1] } // Multiply by 2^22; store u in the vpu +{ ldc r3, 32 ; stw v, r11[2] } // Load constant; store v in the vpu + +#undef _22 +#define _32 r3 +#define _24 r1 + +#undef y +#undef u +#undef v + +{ ldc _24, 24 ; vclrdr } // load in R1 24 ; Sets the contents of vD and vR in the vector unit to all zeroes. +{ neg _24, _24 ; vldc r11[0] } // signed negation ; load first opdfand +ldap r11, bluConv // Load effective address relative to the program counter. +{ add r11, r11, _32 ; vlmaccr r11[0] } +{ add r11, r11, _32 ; vlmaccr r11[0] } +{ ldaw r11, sp[0] ; vlmaccr r11[0] } + +{ ; vstr r11[0] } + vlashr r11[0], _24 +{ ; vdepth8 } +{ ; vstr r11[0] } +{ ; ldw r0, sp[0] } +retsp nstackwords + +// the values correspond to the matrix values *255 +// https://en.wikipedia.org/wiki/YUV +bluConv: .word 256, 520, 0, 0, 0, 0, 0, 0 +grnConv: .word 256, -101, -149, 0, 0, 0, 0, 0 +redConv: .word 256, 0, 292, 0, 0, 0, 0, 0 + +.cc_bottom yuv_to_rgb.func diff --git a/camera/src/camera_api.c b/camera/src/camera_api.c new file mode 100644 index 00000000..4991bf04 --- /dev/null +++ b/camera/src/camera_api.c @@ -0,0 +1,230 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +// std +#include +#include +// xcore +#include +#include +// user +#include "mipi.h" +#include "camera_utils.h" +#include "camera_api.h" +#include "isp.h" + +#define CHAN_RAW 0 +#define CHAN_DEC 1 +#define CHAN_STOP 2 + +// In order to interface the handler and api +channel_t c_user_api[3]; + +void camera_init() +{ + c_user_api[CHAN_RAW] = chan_alloc(); + c_user_api[CHAN_DEC] = chan_alloc(); + c_user_api[CHAN_STOP] = chan_alloc(); +} + +unsigned camera_check_stop(){ + SELECT_RES( + CASE_THEN(c_user_api[CHAN_STOP].end_b, user_handler), + DEFAULT_THEN(default_handler)) + { + user_handler: + return chan_in_word(c_user_api[CHAN_STOP].end_b); + default_handler: + return 0; + } +} + +void camera_stop(){ + chan_out_word(c_user_api[CHAN_STOP].end_a, (unsigned) 1); + //chan_free(c_user_api[CHAN_RAW]); + //chan_free(c_user_api[CHAN_DEC]); + //chan_free(c_user_api[CHAN_STOP]); +} + +void camera_new_row( + const int8_t pixel_data[W_RAW], + const unsigned row_index){ + int8_t* user_pixel_data; + + SELECT_RES( + CASE_THEN(c_user_api[CHAN_RAW].end_a, user_handler), + DEFAULT_THEN(default_handler)) + { + user_handler: + user_pixel_data = (int8_t*) chan_in_word(c_user_api[CHAN_RAW].end_a); + memcpy(user_pixel_data, (void*) pixel_data, W_RAW); + chan_out_word(c_user_api[CHAN_RAW].end_a, row_index); + break; + default_handler: + break; + } +} + +void camera_new_row_decimated( + const int8_t pixel_data[CH][W], + const unsigned row_index) +{ + int8_t *user_pixel_data; + + SELECT_RES( + CASE_THEN(c_user_api[CHAN_DEC].end_a, user_handler), + DEFAULT_THEN(default_handler)) + { + user_handler: + user_pixel_data = (int8_t *)chan_in_word(c_user_api[CHAN_DEC].end_a); + memcpy(user_pixel_data, (void *)pixel_data, CH * W); + chan_out_word(c_user_api[CHAN_DEC].end_a, row_index); + break; + default_handler: + break; + } +} + +unsigned camera_capture_row( + int8_t pixel_data[W_RAW]) +{ + chan_out_word(c_user_api[CHAN_RAW].end_b, (unsigned) &pixel_data[0]); + unsigned sdf = chan_in_word(c_user_api[CHAN_RAW].end_b); + return sdf; +} + +unsigned camera_capture_row_decimated( + int8_t pixel_data[CH][W]) +{ + chan_out_word(c_user_api[CHAN_DEC].end_b, (unsigned) &pixel_data[0][0]); + return chan_in_word(c_user_api[CHAN_DEC].end_b); +} + +unsigned camera_capture_image_raw( + int8_t image_buff[H_RAW][W_RAW]) +{ + unsigned row_index; + + // Loop, capturing rows until we get one with row_index==0 + do { + row_index = camera_capture_row(&image_buff[0][0]); + } while (row_index != 0); + + // Now capture the rest of the rows + for (unsigned i=1; i +#include +#include + +#include +#include // for ports +#include + +#include "camera_main.h" + +#include "mipi.h" +#include "isp.h" +#include "packet_handler.h" + + +void camera_mipi_init( + port_t p_mipi_clk, + port_t p_mipi_rxa, + port_t p_mipi_rxv, + in_buffered_port_32_t p_mipi_rxd, + xclock_t clk_mipi) +{ + // Tile ids have weird values, so we get them with this API + unsigned tileid = get_local_tile_id(); + // Assign lanes and polarities + //write_node_config_reg(mipi_tile, XS1_SSWITCH_MIPI_DPHY_CFG3_NUM, DEFAULT_MIPI_DPHY_CFG3); + write_sswitch_reg(tileid, XS1_SSWITCH_MIPI_DPHY_CFG3_NUM, DEFAULT_MIPI_DPHY_CFG3); + + // Configure MIPI shim + unsigned mipi_shim_cfg0 = MIPI_SHIM_CFG0_PACK(MIPI_SHIM_DEMUX_EN, + MIPI_SHIM_DEMUX_DATATYPE, + MIPI_SHIM_DEMUX_MODE, + MIPI_SHIM_STUFF_ENABLE, + 1); // enable bias + // Initialize MIPI receiver + MipiPacketRx_init(tileid, + p_mipi_rxd, + p_mipi_rxv, + p_mipi_rxa, + p_mipi_clk, + clk_mipi, + mipi_shim_cfg0, + MIPI_CLK_DIV, + MIPI_CFG_CLK_DIV); +} diff --git a/camera/src/camera_utils.c b/camera/src/camera_utils.c new file mode 100644 index 00000000..70bb07c9 --- /dev/null +++ b/camera/src/camera_utils.c @@ -0,0 +1,25 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include + +#include "camera_utils.h" + +inline unsigned measure_time() +{ + unsigned y = 0; + asm volatile("gettime %0": "=r"(y)); + return y; +} + +void vect_int8_to_uint8( + uint8_t output[], + int8_t input[], + const size_t length) +{ + for (size_t k = 0; k < length; k++) + output[k] = input[k] + 128; +} diff --git a/camera/src/image_hfilter.c b/camera/src/image_hfilter.c new file mode 100644 index 00000000..4b12efcf --- /dev/null +++ b/camera/src/image_hfilter.c @@ -0,0 +1,43 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +#include "image_hfilter.h" +#include "isp.h" + +//Note: for filter coefficients reference : python/filters.txt + +void pixel_hfilter_update_scale( + hfilter_state_t* state, + const float gain, + const unsigned offset) +{ + float sc_b0 = COEF_B0 * gain; + float sc_b1 = COEF_B1 * gain; + + // Faster than computing ceil(log2(__)) + if(sc_b0 <= 0.25f) state->shift = 9; + else if(sc_b0 <= 0.5f) state->shift = 8; + else if(sc_b0 <= 1.0f) state->shift = 7; + else if(sc_b0 <= 2.0f) state->shift = 6; + else state->shift = 5; + + const int shift_scale = 1 << state->shift; + + const float b0 = (sc_b0 * shift_scale); + const float b1 = (sc_b1 * shift_scale); + + const int8_t b0_s8 = (b0 >= INT8_MAX) ? INT8_MAX : (b0 + 0.5f); + const int8_t b1_s8 = (b1 >= INT8_MAX) ? INT8_MAX : (b1 + 0.5f); + + const unsigned s = offset; + + state->coef[0+s] = state->coef[4+s] = b1_s8; + state->coef[2+s] = b0_s8; + + const float sum_b = b0 + 2*b1; + + state->acc_init = 128 * (sum_b - shift_scale) - SENSOR_BLACK_LEVEL * shift_scale; +} diff --git a/camera/src/image_vfilter.c b/camera/src/image_vfilter.c new file mode 100644 index 00000000..5e499b40 --- /dev/null +++ b/camera/src/image_vfilter.c @@ -0,0 +1,118 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +#include "image_vfilter.h" + +//Note: for filter coefficients reference : python/filters.txt +static +const int32_t vfilter_acc_offset = 0; + +static +const int8_t vfilter_coef[5][16] = { + { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,}, + { 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,}, + {114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,114,}, + { 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,}, + { 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,}, +}; + +static +const int16_t vfilter_shift[16] = {8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,}; + +/** + * Prepare the provided accumulator struct for accumulation. + */ +static inline +void image_vfilter_reset( + vfilter_acc_t* acc) +{ + acc->next_tap = VFILTER_RESET_INDEX; + pixel_vfilter_acc_init(acc->buff, vfilter_acc_offset, + APP_IMAGE_WIDTH_PIXELS); +} + +/** + * Call this once at the start of each frame. The accumulator next_tap values + * are set somewhat differently than image_vfilter_reset(), because the behavior + * at the start of the image is a little different. + * + * `acc_count` is the number of ROWS of accumulators, whereas + * `image_width_lores` is the number of pixels per low-resolution image row + * (which is the number of individual accumulators PER ROW). + * + * `image_width_lores` must be a multiple of 16 (atm) + * + * NOTE: THE VFILTER FUNCTIONS ARE CHANNEL AGNOSTIC, SO IF THE IMAGE IS + * SEPARATED INTO DIFFERENT COLOR PLANES, THIS WILL NEED TO BE CALLED ONCE + * FOR EACH COLOR PLANE. IF THEY'RE INTERLEAVED, IT CAN BE CALLED ONCE, + * BUT THE IMAGE WIDTH MUST THEN BE THE WIDTH IN _BYTES_. AND IT MUST + * STILL BE A MULTIPLE OF 16. + */ +void image_vfilter_frame_init( + vfilter_acc_t accs[]) +{ + for(int k = 0; k < VFILTER_ACC_COUNT; k++){ + accs[k].next_tap = -(VFILTER_DEC_FACTOR) * k + (VFILTER_TAP_COUNT/2); + pixel_vfilter_acc_init(accs[k].buff, vfilter_acc_offset, + APP_IMAGE_WIDTH_PIXELS); + } +} + +/** + * + * + */ +unsigned image_vfilter_process_row( + int8_t output[], + vfilter_acc_t acc[], + const int8_t pixel_data[]) +{ + for(int k = 0; k < VFILTER_ACC_COUNT; k++){ + if(acc[k].next_tap >= 0){ + pixel_vfilter_macc(acc[k].buff, + pixel_data, + &vfilter_coef[acc[k].next_tap][0], + APP_IMAGE_WIDTH_PIXELS); + } + acc[k].next_tap++; + } + + for(int k = 0; k < VFILTER_ACC_COUNT; k++){ + if(acc[k].next_tap != VFILTER_TAP_COUNT) continue; + + // produce an output row from accumulator + pixel_vfilter_complete(output, + acc[k].buff, + vfilter_shift, + APP_IMAGE_WIDTH_PIXELS); + + // reset the accumulator + image_vfilter_reset(&acc[k]); + + return 1; + } + + return 0; +} + +unsigned image_vfilter_drain( + int8_t output[], + vfilter_acc_t acc[]) +{ + for(int k = 0; k < VFILTER_ACC_COUNT; k++){ + if(acc[k].next_tap <= 0) continue; + + pixel_vfilter_complete(output, + acc[k].buff, + vfilter_shift, + APP_IMAGE_WIDTH_PIXELS); + acc[k].next_tap = 0; + + return 1; + } + + return 0; +} diff --git a/camera/src/isp_functions.c b/camera/src/isp_functions.c new file mode 100644 index 00000000..bbb0d6f4 --- /dev/null +++ b/camera/src/isp_functions.c @@ -0,0 +1,408 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include + +#include +#include // includes streaming channel and channend +#include "xccompat.h" + +#include "isp.h" +#include "sensor_control.h" + +#define INCLUDE_ABS 0 + +// ---------------------------------- utils ------------------------------ +static +int8_t csign(float x) { + return (x > 0) - (x < 0); +} + +#if INCLUDE_ABS +static +float cabs(float x) { + return x * csign(x); +} +#endif +// ---------------------------------- AE / AGC ------------------------------ +#if ENABLE_PRINT_STATS +static +void print_info_exposure(uint8_t val) +{ + static uint8_t printf_info = 1; + // reset + if (val == 0) { + printf_info = 1; + } + else { + // set + if (printf_info) { + printf("-----> adjustement done\n"); + printf_info = 0; + } + } +} +#endif + + +uint8_t AE_control_exposure( + global_stats_t *global_stats, + chanend c_control) +{ + // Initial exposure + static uint8_t new_exp = AE_INITIAL_EXPOSURE; + static uint8_t skip_ae_control = 0; // if too dark for a ceertain frames, skip AE control + + // Compute skewness and adjust exposure if needed + float sk = AE_compute_mean_skewness(global_stats); + if (AE_is_adjusted(sk)){ + #if ENABLE_PRINT_STATS + print_info_exposure(1); + #endif + return 1; + } + else{ + // Adjust exposure + new_exp = AE_compute_new_exposure((float)new_exp, sk); + // Send new exposure + uint32_t encoded_cmd = ENCODE(SENSOR_SET_EXPOSURE, new_exp); + chan_out_word(c_control, encoded_cmd); + chan_in_word(c_control); + + #if ENABLE_PRINT_STATS + print_info_exposure(0); + #endif + if (new_exp > 70){ + skip_ae_control++; + if (skip_ae_control > 5){ + skip_ae_control = 0; + return 1; + } + } + } + return 0; +} + + +void AE_print_skewness(global_stats_t *gstats){ + printf("skewness:%f,%f,%f\n", + (*gstats)[0].skewness, + (*gstats)[1].skewness, + (*gstats)[2].skewness); +} + +float AE_compute_mean_skewness(global_stats_t *gstats){ + float mean = ( + (*gstats)[0].skewness + \ + (*gstats)[1].skewness + \ + (*gstats)[2].skewness)/3; + return mean; +} + +inline uint8_t AE_is_adjusted(float sk) { + return (sk < AE_MARGIN && sk > -AE_MARGIN) ? 1 : 0; +} + +uint8_t AE_compute_new_exposure(float exposure, float skewness) +{ + static float a = 0; // minimum value for exposure + static float fa = -1; // minimimum skewness + static float b = 80; // maximum value for exposure + static float fb = 1; // minimum skewness + static int count = 0; + float c = exposure; + float fc = skewness; + + if(csign(fc) == csign(fa)){ + a = c; fa = fc; + } + else{ + b = c; fb = fc; + } + c = b - fb*((b - a)/(fb - fa)); + + // each X samples, restart AE algorithm + if (count < 5){ + count = count + 1; + } + else{ + // restart auto exposure + count = 0; + a = 0; + fa = -1; + b = 80; + fb = 1; + } + return c; +} + + +// ---------------------------------- AWB ------------------------------ +isp_params_t isp_params = { + .channel_gain = { + AWB_gain_RED, + AWB_gain_GREEN, + AWB_gain_BLUE + } +}; + +static +float AWB_clip_value(float tmp){ + if (tmp > AWB_MAX){ + tmp = AWB_MAX; + } + else if (tmp < AWB_MIN){ + tmp = AWB_MIN; + } + return tmp; +} + +void AWB_compute_gains_percentile(global_stats_t *gstats, isp_params_t *isp_params){ + // Adjust AWB + float tmp0=1; + float tmp1=1; + float tmp2=1; + + uint8_t red_p = (*gstats)[0].percentile; + uint8_t green_p = (*gstats)[1].percentile; + uint8_t blue_p = (*gstats)[2].percentile; + + tmp0 = green_p/(float)red_p; + tmp1 = 1; + tmp2 = green_p/(float)blue_p; + + uint32_t r_per_count = (*gstats)[0].per_count; + uint32_t g_per_count = (*gstats)[1].per_count; + uint32_t b_per_count = (*gstats)[2].per_count; + + float tmpA = (float)g_per_count/(float)r_per_count; + float tmpC = (float)g_per_count/(float)b_per_count; + + tmp0 = (tmp0 + tmpA)/2.0; + tmp2 = (tmp2 + tmpC)/2.0; + + tmp0 = AWB_clip_value(tmp0); + tmp1 = AWB_clip_value(tmp1); + tmp2 = AWB_clip_value(tmp2); + + isp_params->channel_gain[0] = tmp0; + isp_params->channel_gain[1] = tmp1; + isp_params->channel_gain[2] = tmp2; +} + +void AWB_compute_gains_static(isp_params_t *isp_params){ + isp_params->channel_gain[0] = AWB_gain_RED; + isp_params->channel_gain[1] = AWB_gain_GREEN; + isp_params->channel_gain[2] = AWB_gain_BLUE; +} + +void AWB_compute_gains_white_patch(global_stats_t *gstats, isp_params_t *isp_params){ + float Rmax = (*gstats)[0].max; // RED + float Gmax = (*gstats)[1].max; // GREEN + float Bmax = (*gstats)[2].max; // BLUE + + float alfa = Gmax/Rmax; + const float beta = 0.9; + float gamma = Gmax/Bmax; + + alfa = AWB_clip_value(alfa); + gamma = AWB_clip_value(gamma); + + // apply params + isp_params->channel_gain[0] = alfa; + isp_params->channel_gain[1] = beta; + isp_params->channel_gain[2] = gamma; +} + +void AWB_compute_gains_white_max(global_stats_t *gstats, isp_params_t *isp_params){ + // we assume green constant + const float beta = 1.0; + float alfa = 1.3; + float gamma = 1.3; + + // 1 - Grey world + float Ravg = (*gstats)[0].mean; // RED + float Gavg = (*gstats)[1].mean; // GREEN + float Bavg = (*gstats)[2].mean; // BLUE + + if(Gavg > Ravg) + alfa = Gavg/Ravg; + if(Gavg > Bavg) + gamma = Gavg/Bavg; + + // 2 - Percentile volumne + uint32_t r_per_count = (*gstats)[0].per_count; + uint32_t g_per_count = (*gstats)[1].per_count; + uint32_t b_per_count = (*gstats)[2].per_count; + + float alfa2 = (float)g_per_count/(float)r_per_count; + float gamma2 = (float)g_per_count/(float)b_per_count; + + // Weighted mean + float gww = 0.5; // grey world weight + alfa = gww*alfa + (1-gww)*alfa2; + gamma = gww*gamma + (1-gww)*gamma2; + + // clip the values + alfa = AWB_clip_value(alfa); + gamma = AWB_clip_value(gamma); + + // apply params + isp_params->channel_gain[0] = alfa; + isp_params->channel_gain[1] = beta; + isp_params->channel_gain[2] = gamma; +} + +void AWB_compute_gains_gray_world(global_stats_t *gstats, isp_params_t *isp_params){ + printf("AWB --->"); + float Ravg = (*gstats)[0].mean; // RED + float Gavg = (*gstats)[1].mean; // GREEN + float Bavg = (*gstats)[2].mean; // BLUE + + float alfa = Gavg/Ravg; + const float beta = 1; + float gamma = Gavg/Bavg; + + alfa = AWB_clip_value(alfa); + gamma = AWB_clip_value(gamma); + + isp_params->channel_gain[0] = alfa; + isp_params->channel_gain[1] = beta; + isp_params->channel_gain[2] = gamma; +} + +void AWB_print_gains(isp_params_t *isp_params){ + printf("awb:%f,%f,%f\n", + isp_params->channel_gain[0], + isp_params->channel_gain[1], + isp_params->channel_gain[2]); +} + +// ---------------------------------- GAMMA ------------------------------ + +// gamma 1.8, with substract 10 and 1.05 multiplier +const uint8_t gamma_uint8[256] = { +0, 0, 0, 0, 0, 0, 0, 2, 5, 8, 11, 14, 16, +19, 21, 23, 26, 28, 30, 32, 34, 36, 38, 40, 42, 43, +45, 47, 49, 50, 52, 54, 55, 57, 59, 60, 62, 63, 65, +66, 68, 69, 71, 72, 73, 75, 76, 77, 79, 80, 82, 83, +84, 85, 87, 88, 89, 91, 92, 93, 94, 95, 97, 98, 99, +100, 101, 103, 104, 105, 106, 107, 108, 109, 110, 112, 113, 114, +115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, +128, 130, 131, 132, 133, 134, 134, 135, 136, 137, 138, 139, 140, +141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 152, +153, 154, 155, 156, 157, 158, 159, 160, 161, 161, 162, 163, 164, +165, 166, 167, 167, 168, 169, 170, 171, 172, 173, 173, 174, 175, +176, 177, 178, 178, 179, 180, 181, 182, 182, 183, 184, 185, 186, +186, 187, 188, 189, 190, 190, 191, 192, 193, 194, 194, 195, 196, +197, 197, 198, 199, 200, 200, 201, 202, 203, 203, 204, 205, 206, +206, 207, 208, 209, 209, 210, 211, 212, 212, 213, 214, 215, 215, +216, 217, 217, 218, 219, 220, 220, 221, 222, 222, 223, 224, 225, +225, 226, 227, 227, 228, 229, 229, 230, 231, 232, 232, 233, 234, +234, 235, 236, 236, 237, 238, 238, 239, 240, 240, 241, 242, 242, +243, 244, 244, 245, 246, 246, 247, 248, 248, 249, 250, 250, 251, +252, 252, 253, 254, 254, 255, 255, 255, 255}; + +// gamma 1.8, with substract 10 and 1.05 multiplier (int8 version) +const int8_t gamma_int8[256] = { +-128, -128, -128, -128, -128, -128, -128, -126, -123, -120, -117, -114, -112, -109, -107, -105, +-102, -100, -98, -96, -94, -92, -90, -88, -86, -85, -83, -81, -79, -78, -76, -74, +-73, -71, -69, -68, -66, -65, -63, -62, -60, -59, -57, -56, -55, -53, -52, -51, +-49, -48, -46, -45, -44, -43, -41, -40, -39, -37, -36, -35, -34, -33, -31, -30, +-29, -28, -27, -25, -24, -23, -22, -21, -20, -19, -18, -16, -15, -14, -13, -12, +-11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 2, 3, 4, 5, +6, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, +21, 22, 23, 24, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 33, 34, +35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 45, 46, 47, 48, +49, 50, 50, 51, 52, 53, 54, 54, 55, 56, 57, 58, 58, 59, 60, 61, +62, 62, 63, 64, 65, 66, 66, 67, 68, 69, 69, 70, 71, 72, 72, 73, +74, 75, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 84, 85, +86, 87, 87, 88, 89, 89, 90, 91, 92, 92, 93, 94, 94, 95, 96, 97, +97, 98, 99, 99, 100, 101, 101, 102, 103, 104, 104, 105, 106, 106, 107, 108, +108, 109, 110, 110, 111, 112, 112, 113, 114, 114, 115, 116, 116, 117, 118, 118, +119, 120, 120, 121, 122, 122, 123, 124, 124, 125, 126, 126, 127, 127, 127, 127, +}; + +void isp_gamma( + uint8_t * img_in, + const uint8_t *gamma_curve, + const size_t height, + const size_t width, + const size_t channels) +{ + xassert((gamma_curve[255] != 0) && "Gamma curve is not filled correctly"); // ensure all values are filles up + size_t buffsize = height * width * channels; + for(size_t idx = 0; idx < buffsize; idx++){ + img_in[idx] = gamma_curve[img_in[idx]]; + } +} + +// -------------------------- ROTATE/RESIZE ------------------------------------- +#define img(row, col, WIDTH) img[(WIDTH) * (row) + (col)] +#define out_img(row, col, WIDTH) out_img[(WIDTH) * (row) + (col)] + +static void xmodf(float a, int *b, float *c, int *bp) +{ + // split integer and decimal part + *b = (int)(a); + *c = a - *b; + // last operand for convinience + *bp = *b + 1; +} + +void isp_bilinear_resize( + const uint16_t in_width, + const uint16_t in_height, + uint8_t *img, + const uint16_t out_width, + const uint16_t out_height, + uint8_t *out_img) +{ + // https://chao-ji.github.io/jekyll/update/2018/07/19/BilinearResize.html + const float x_ratio = ((in_width - 1) / (float)(out_width - 1)); + const float y_ratio = ((in_height - 1) / (float)(out_height - 1)); + + int x_l, y_l, x_h, y_h; + float xw, yw; + uint8_t a,b,c,d; + + for (uint16_t i = 0; i < out_height; i++) + { + for (uint16_t j = 0; j < out_width; j++) + { + + float incrx = (x_ratio * j); + float incry = (y_ratio * i); + + xmodf(incrx, &x_l, &xw, &x_h); + xmodf(incry, &y_l, &yw, &y_h); + + a = img(y_l, x_l, in_width); + b = img(y_l, x_h, in_width); + c = img(y_h, x_l, in_width); + d = img(y_h, x_h, in_width); + + uint8_t pixel = (uint8_t)(a * (1 - xw) * (1 - yw) + + b * xw * (1 - yw) + + c * yw * (1 - xw) + + d * xw * yw); + + out_img(i, j, out_width) = pixel; + printf("%d,", pixel); + } + } +} + +void rotate_image_90(uint8_t image_buffer[APP_IMAGE_CHANNEL_COUNT][APP_IMAGE_HEIGHT_PIXELS][APP_IMAGE_WIDTH_PIXELS]) +{ + for(int c = 0; c < APP_IMAGE_CHANNEL_COUNT; c++) { + for(int k = 0; k < APP_IMAGE_HEIGHT_PIXELS/2; k++) { + for(int j = 0; j < APP_IMAGE_WIDTH_PIXELS; j++) { + uint8_t a = image_buffer[c][k][j]; + uint8_t b = image_buffer[c][APP_IMAGE_HEIGHT_PIXELS-k-1][APP_IMAGE_WIDTH_PIXELS-j-1]; + image_buffer[c][k][j] = b; + image_buffer[c][APP_IMAGE_HEIGHT_PIXELS-k-1][APP_IMAGE_WIDTH_PIXELS-j-1] = a; + } + } + } +} diff --git a/camera/src/isp_pipeline.c b/camera/src/isp_pipeline.c new file mode 100644 index 00000000..65a5284d --- /dev/null +++ b/camera/src/isp_pipeline.c @@ -0,0 +1,78 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include // includes streaming channel and channend +#include +#include "xccompat.h" + +#include "isp.h" +#include "statistics.h" +#include "sensor_control.h" + +/** + * Thread that computes the ISP pipeline for each pixel in the image. + * The statistics are stored in a struct which is used to perform ISP corrections. + * @param c_img_in - Streaming channel end of the image. + * @param c_control - channel end for camera control. + */ +void isp_pipeline(streaming_chanend_t c_img_in, chanend_t c_control) +{ + // Outer loop iterates over frames + while (1) { + global_stats_t global_stats = {{0}}; + + // Inner loop iterates over rows within a frame + while (1) { + low_res_image_row_t* row = (low_res_image_row_t*) s_chan_in_word(c_img_in); + + if (row == NULL) { // Signal end of frame [1] + break; + } + + if (row == (low_res_image_row_t *) 1) { + uint32_t encoded_cmd = ENCODE(SENSOR_STREAM_STOP, 0); + chan_out_word(c_control, encoded_cmd); + chan_in_word(c_control); + return; + } + + // Update histogram + for (uint8_t channel = 0; channel < APP_IMAGE_CHANNEL_COUNT; channel++) { + stats_update_histogram( + &global_stats[channel].histogram, + &row->pixels[channel][0]); + } + } + + // End of frame, compute statistics (order is important) + for (uint8_t channel = 0; channel < APP_IMAGE_CHANNEL_COUNT; channel++) { + stats_simple(&global_stats[channel]); + stats_skewness(&global_stats[channel]); + stats_percentile(&global_stats[channel], APP_WB_PERCENTILE); + stats_percentile_volume(&global_stats[channel]); + #if ENABLE_PRINT_STATS + stats_print(&global_stats[channel], channel); // print channel stats + #endif + } + + // Adjust AE + uint8_t ae_done = AE_control_exposure(&global_stats, c_control); + + + // Adjust AWB + static unsigned run_once = 0; + if (ae_done == 1 && run_once == 0) + { + AWB_compute_gains_static(&isp_params); + run_once = 1; // Set to 1 to run only once + } + + // Apply gamma curve + // gamma curve is applied to the image in the camera API + + // Print ISP info + #if ENABLE_PRINT_STATS + AWB_print_gains(&isp_params); + #endif + } +} diff --git a/camera/src/packet_handler.c b/camera/src/packet_handler.c new file mode 100644 index 00000000..88290202 --- /dev/null +++ b/camera/src/packet_handler.c @@ -0,0 +1,311 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include + +#include +#include +#include + +#include "packet_handler.h" +#include "image_vfilter.h" +#include "image_hfilter.h" +#include "camera_api.h" +#include "camera_utils.h" +#include "sensor.h" + +// Filter stride +#define HFILTER_INPUT_STRIDE (APP_DECIMATION_FACTOR) + +// State needed for the vertical filter +static +vfilter_acc_t vfilter_accs[APP_IMAGE_CHANNEL_COUNT][VFILTER_ACC_COUNT]; + +// Contains the local state info for the packet handler thread. +static struct { + unsigned wait_for_frame_start; + unsigned frame_number; + unsigned in_line_number; + unsigned out_line_number; +} ph_state = { + .wait_for_frame_start = 1, + .frame_number = 0, + .in_line_number = 0, + .out_line_number = 0, +}; + +hfilter_state_t hfilter_state[APP_IMAGE_CHANNEL_COUNT]; + +static +void handle_frame_start() +{ + // New frame is starting, reset the vertical filter accumulator states. + for(int c = 0; c < APP_IMAGE_CHANNEL_COUNT; c++){ + // printf("isp params = %f\n", isp_params.channel_gain[c]); + pixel_hfilter_update_scale(&hfilter_state[c], + isp_params.channel_gain[c], + (c == 0)? 0 : 1); + + image_vfilter_frame_init(&vfilter_accs[c][0]); + } +} + +static +void handle_unknown_packet( + const mipi_packet_t* pkt) +{ + const mipi_header_t header = pkt->header; + const mipi_data_type_t data_type = MIPI_GET_DATA_TYPE(header); + // uknown packets could be the following: + // 1 - sensor specific packets (we let continue the app, uncomment print here for debug) + // printf("Unknown packet type: %d\n", data_type); + // 2 - invalid packets + xassert(data_type < 0x3F && "Packet non valid"); +} + +/** + * Handle a row of pixel data. + * + * This function handles horizontal and vertical decimation of the image data. + * + * Returns true iff output_buffer[][] has been filled and is ready to be sent to + * the next thread. + */ +static +unsigned handle_pixel_data( + const mipi_packet_t* pkt, + int8_t output_buffer[APP_IMAGE_CHANNEL_COUNT][APP_IMAGE_WIDTH_PIXELS]) +{ + + // First, service any raw requests. + camera_new_row((int8_t*) &pkt->payload[0], ph_state.in_line_number); + + // Bayer pattern is RGGB; even index rows have RG data, + // odd index rows have GB data. + unsigned pattern = ph_state.in_line_number % 2; + + // Temporary buffer to store horizontally-filtered row data. [1] + int8_t hfilt_row[APP_IMAGE_WIDTH_PIXELS]; + + if(pattern == 0){ // Packet contains RGRGRGRGRGRGRGRGRG... + ////// RED + pixel_hfilter(&hfilt_row[0], + (int8_t*) &pkt->payload[0], + &hfilter_state[CHAN_RED].coef[0], + hfilter_state[CHAN_RED].acc_init, + hfilter_state[CHAN_RED].shift, + HFILTER_INPUT_STRIDE, + APP_IMAGE_WIDTH_PIXELS); + + + image_vfilter_process_row(&output_buffer[CHAN_RED][0], + &vfilter_accs[CHAN_RED][0], + &hfilt_row[0]); + + ////// GREEN + pixel_hfilter(&hfilt_row[0], + (int8_t*) &pkt->payload[0], + &hfilter_state[CHAN_GREEN].coef[0], + hfilter_state[CHAN_GREEN].acc_init, + hfilter_state[CHAN_GREEN].shift, + HFILTER_INPUT_STRIDE, + APP_IMAGE_WIDTH_PIXELS); + + // we now it is not the las row [2] + image_vfilter_process_row(&output_buffer[CHAN_GREEN][0], + &vfilter_accs[CHAN_GREEN][0], + &hfilt_row[0]); + + } + else { // Packet contains GBGBGBGBGBGBGBGBGBGB... + ////// BLUE + pixel_hfilter(&hfilt_row[0], + (int8_t*) &pkt->payload[0], + &hfilter_state[CHAN_BLUE].coef[0], + hfilter_state[CHAN_BLUE].acc_init, + hfilter_state[CHAN_BLUE].shift, + HFILTER_INPUT_STRIDE, + APP_IMAGE_WIDTH_PIXELS); + + unsigned new_row = image_vfilter_process_row( + &output_buffer[CHAN_BLUE][0], + &vfilter_accs[CHAN_BLUE][0], + &hfilt_row[0]); + + // If new_row is true, then the vertical decimator has output a new row for + // each of the three color channels, and so we should signal this upwards. + if(new_row){ + return 1; + } + } + return 0; +} + +static +void on_new_output_row( + const int8_t pix_out[APP_IMAGE_CHANNEL_COUNT][APP_IMAGE_WIDTH_PIXELS], + streaming_chanend_t c_stats) +{ + // Pass the output row along for statistics processing + s_chan_out_word(c_stats, (unsigned) &pix_out[0][0] ); + + // Service and user requests for decimated output + camera_new_row_decimated(pix_out, ph_state.out_line_number); + + ph_state.out_line_number++; +} + +static +void handle_frame_end( + int8_t pix_out[APP_IMAGE_CHANNEL_COUNT][APP_IMAGE_WIDTH_PIXELS], + streaming_chanend_t c_stats) +{ + // Drain the vertical filter's accumulators + image_vfilter_drain(&pix_out[CHAN_RED][0], &vfilter_accs[CHAN_RED][0]); + image_vfilter_drain(&pix_out[CHAN_GREEN][0], &vfilter_accs[CHAN_GREEN][0]); + if(image_vfilter_drain(&pix_out[CHAN_BLUE][0], &vfilter_accs[CHAN_BLUE][0])){ + // Pass final row(s) to the statistics thread + on_new_output_row(pix_out, c_stats); + } + + // Signal statistics thread to do frame-end work by sending NULL. + s_chan_out_word(c_stats, (unsigned) NULL); +} + +void handle_no_expected_lines() +{ + if(ph_state.in_line_number >= SENSOR_RAW_IMAGE_HEIGHT_PIXELS){ + // We've received more lines of image data than we expected. +#ifdef ASSERT_ON_TOO_MANY_LINES + xassert(0 && "Recieved too many lines"); +#endif + } +} + +/** + * Process a single packet. + * + * This function keeps track of where we are within the input and output image + * frames. It also passes the packet along to a function for processing + * depending upon the data type. + */ +static +void handle_packet( + const mipi_packet_t* pkt, + streaming_chanend_t c_stats) +{ + /* + * These buffers store rows of the fully decimated image. They are passed + * along to the statistics thread once the packet handler thread no longer + * needs them. + * + * Two are needed -- the one the decimator is currently filling, and the one + * that the statistics thread is currently using. + */ + __attribute__((aligned(8))) + static int8_t output_buff[2][APP_IMAGE_CHANNEL_COUNT][APP_IMAGE_WIDTH_PIXELS]; + static unsigned out_dex = 0; + + + // definitions + const mipi_header_t header = pkt->header; + const mipi_data_type_t data_type = MIPI_GET_DATA_TYPE(header); + + // At start-up we usually want to wait for a new frame before processing + // anything + if(ph_state.wait_for_frame_start + && data_type != MIPI_DT_FRAME_START) return; + + /* + The idea here is that logic that keeps the packet handler in a coherent + state, like tracking frame and line numbers, should go directly in here, but + logic that actually interprets, processes or reacts to packet data should go + into the individual functions. + */ + switch(data_type) + { + case MIPI_DT_FRAME_START: + ph_state.wait_for_frame_start = 0; + ph_state.in_line_number = 0; + ph_state.out_line_number = 0; + ph_state.frame_number++; + + handle_frame_start(); + break; + + case MIPI_DT_FRAME_END: + handle_frame_end(output_buff[out_dex], c_stats); + out_dex = 1 - out_dex; + break; + + case MIPI_EXPECTED_FORMAT: + handle_no_expected_lines(); + + if(handle_pixel_data(pkt, output_buff[out_dex])){ + on_new_output_row(output_buff[out_dex], c_stats); + out_dex = 1 - out_dex; + } + + ph_state.in_line_number++; + break; + + default: + // We've received a packet we don't know how to interpret. + handle_unknown_packet(pkt); + break; + } +} + +/** + * Top level of the packet handling thread. Receives MIPI packets from the + * packet receiver and passes them to `handle_packet()` for parsing and + * processing. + */ +void mipi_packet_handler( + streaming_chanend_t c_pkt, + streaming_chanend_t c_ctrl, + streaming_chanend_t c_stats) +{ + /* + * These buffers will be used to hold received MIPI packets while they're + * being processed. + */ + __attribute__((aligned(8))) + mipi_packet_t packet_buffer[MIPI_PKT_BUFFER_COUNT]; + unsigned pkt_idx = 0; + + // Give the MIPI packet receiver a first buffer + s_chan_out_word(c_pkt, (unsigned) &packet_buffer[pkt_idx] ); + + while(1) { + pkt_idx = (pkt_idx + 1) & (MIPI_PKT_BUFFER_COUNT-1); + + // Swap buffers with the receiver thread. Give it the next buffer + // to fill and take the last filled buffer from it. + mipi_packet_t * pkt = (mipi_packet_t*) s_chan_in_word(c_pkt); + + // Check is we are supose to stop or continue + unsigned stop = camera_check_stop(); + + if (stop == 1){ + // send stop to MipiReciever + s_chan_out_word(c_pkt, (unsigned) NULL); + // send stop to statistics + s_chan_out_word(c_stats, (unsigned) 1); + // end thread + puts("\nMipiPacketHandler: stop\n"); + return; + } + else{ + // send info to MipiReciever + s_chan_out_word(c_pkt, (unsigned) &packet_buffer[pkt_idx]); + } + // Process the packet + //const mipi_header_t header = pkt->header; + //const mipi_data_type_t data_type = MIPI_GET_DATA_TYPE(header); + + // unsigned time_start = measure_time(); + handle_packet(pkt, c_stats); + // unsigned time_proc = measure_time() - time_start; + } +} diff --git a/camera/src/statistics.c b/camera/src/statistics.c new file mode 100644 index 00000000..72f3ff98 --- /dev/null +++ b/camera/src/statistics.c @@ -0,0 +1,153 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include + +#include "statistics.h" +#include "isp.h" +#include "print.h" + +// Number of samples taken each row +#define HISTOGRAM_SAMPLE_PER_ROW ((APP_IMAGE_WIDTH_PIXELS + APP_HISTOGRAM_SAMPLE_STEP - 1) / (APP_HISTOGRAM_SAMPLE_STEP)) +// Number of samples taken in an image +#define HISTOGRAM_TOTAL_SAMPLES (HISTOGRAM_SAMPLE_PER_ROW * APP_IMAGE_HEIGHT_PIXELS ) +// This is the normalization factor +static const float histogram_norm_factor = (1.0 / (float) HISTOGRAM_TOTAL_SAMPLES); + +/** +* Update histogram based on pixel values. +* +* @param hist - * pointer to the histogram to update. Must be large enough to accommodate the number of pixels in the image. +* @param pix - array of pixel values to update the histogram with +*/ +void stats_update_histogram( + channel_histogram_t* hist, + const int8_t pix[]) +{ + for(int k = 0; k < APP_IMAGE_WIDTH_PIXELS; k += APP_HISTOGRAM_SAMPLE_STEP){ + int val = pix[k]; + val += 128; // convert from int8_t to uint8_t + val >>= HIST_QUANT_BITS; + hist->bins[val]++; + } +} + +void stats_skewness(channel_stats_t *stats) +{ + const float zk_values[] = { + -1.000000, -0.907753, -0.821362, -0.740633, -0.665375, -0.595396, + -0.530504, -0.470508, -0.415214, -0.364431, -0.317968, -0.275632, + -0.237231, -0.202574, -0.171468, -0.143721, -0.119142, -0.097538, + -0.078717, -0.062488, -0.048659, -0.037037, -0.027431, -0.019648, + -0.013497, -0.008786, -0.005323, -0.002915, -0.001372, -0.000005, + -0.000108, -4e-06, 4e-06, 0.000108, 0.0005, 0.001372, + 0.002915, 0.005323, 0.008786, 0.013497, 0.019648, 0.027431, + 0.037037, 0.048659, 0.062488, 0.078717, 0.097538, 0.119142, + 0.143721, 0.171468, 0.202574, 0.237231, 0.275632, 0.317968, + 0.364431, 0.415214, 0.470508, 0.530504, 0.595396, 0.665375, + 0.740633, 0.821362, 0.907753, 1.0}; + + float skew = 0.0; + for(int k = 0; k < HISTOGRAM_BIN_COUNT; k++){ + skew += zk_values[k] * stats->histogram.bins[k]; + } + + // Normnalization [3] + stats -> skewness = skew * histogram_norm_factor; +} + +void stats_simple(channel_stats_t *stats) +{ + // Calculate the histogram + uint8_t temp_min = 0; + uint8_t temp_max = 0; + float temp_mean = 0; + + for(int k = 0; k < HISTOGRAM_BIN_COUNT; k++){ + uint32_t bin_count = stats->histogram.bins[k]; + // mean + temp_mean += bin_count * k; + // max and min + if (bin_count != 0){ // the last that is not zero + temp_max = k; + if (temp_min == 0){ // first time is zero, then min is set + temp_min = k; + } + } + } + // max and min count + stats->max_count = stats->histogram.bins[temp_max]; + stats->min_count = stats->histogram.bins[temp_min]; + + // biased downwards due to truncation + stats->max = (temp_max << HIST_QUANT_BITS); + stats->min = (temp_min << HIST_QUANT_BITS); + stats->mean = (temp_mean) *(1 << HIST_QUANT_BITS) * histogram_norm_factor; +} + +void stats_print(channel_stats_t *stats, unsigned channel){ + const char* formattedString = "\nch:%d,Mi:%d,Ma:%d,Mean:%f,Sk:%f,pct:%d,mi_c:%lu,ma_c:%lu,pc:%lu\n"; + char output[255]; // Assuming a maximum length for the formatted string + sprintf(output, formattedString, + channel, + stats->min, + stats->max, + stats->mean, + stats->skewness, + stats->percentile, + stats->min_count, + stats->max_count, + stats->per_count); + + printstr(output); +} + +void stats_percentile(channel_stats_t *stats, const float fraction) +{ + const unsigned threshold = fraction * HISTOGRAM_TOTAL_SAMPLES; + // Could be optimized but fkeep it like this for timing reasons [2] + uint8_t result; + unsigned total = 0; + + for(int k = 0; k < HISTOGRAM_BIN_COUNT; k++){ + unsigned new_total = total + stats->histogram.bins[k]; + if(total < threshold && new_total >= threshold) + result = (k << HIST_QUANT_BITS); + total = new_total; + } + stats -> percentile = (uint8_t) result; +} + +void stats_percentile_volume(channel_stats_t *stats) +{ + uint32_t bin_count = 0; + uint8_t percentile_point = stats -> percentile / 4; + for(int k = percentile_point; k < HISTOGRAM_BIN_COUNT; k++){ + bin_count += stats->histogram.bins[k]; + } + stats->per_count = bin_count; +} + +// Notes +/* +[1] +The packet handler thread signals end-of-frame by sending a NULL +pointer to the statistics thread. Break out of inner loop on +end-of-frame. + +[2] +This code can be written to exit early once the threshold is reached, +but that leads to a variable run time, depending upon the +characteristics of the image itself, which is highly undesirable. So, +I (astew) am writing it to have a run-time that doesn't depend on the +content of the image. + +[3] +The skewness calculation assumes the histogram has been normalized into a +probability density-like distribution whose sum across all bins is 1.0. +That is just a matter of dividing each histogram bin by the total number of +sampled pixels (which is known a priori). Because every bin is adjusted by +the same factor, we can just wait to apply the adjustment until we get +here. histogram_norm_factor is just the inverse of the total number of +sampled pixels. +*/ diff --git a/camera/stadistics.c b/camera/stadistics.c deleted file mode 100644 index 38406da7..00000000 --- a/camera/stadistics.c +++ /dev/null @@ -1,146 +0,0 @@ -#include "stadistics.h" - -#define BINS 64 // changing the number of bins leads to change in implementation -#define PERCENTILE_VALUE 0.05 // value selected for the percentile distribution - -#define row(x,w) (x / w) -#define col(x,w) (x % w) - -#define RED 0 -#define GREEN 1 -#define BLUE 2 - -Stadistics *Stadistics_alloc(void) { - Stadistics *point; - point = malloc(sizeof(*point)); - if (point == NULL) { - return NULL; - } - memset(point, 0, sizeof(*point)); - return point; -} - -void Stadistics_free(Stadistics *self) { - free(self); -} - -Stadistics Stadistics_initialize(void) { - Stadistics s = {{0}}; - return s; -} - -void Stadistics_compute_histogram(const uint32_t buffsize, const uint8_t step, uint8_t *buffer, Stadistics *stadistics) -{ - // fill the histogram - for (uint32_t i=0; i< buffsize; i = i + step){ - stadistics->histogram[(buffer[i] >> 2)] += 1; // because 255/4 = 64 - } - - // normalize - float inv_factor = (float)step / (float)buffsize; - for (uint8_t j=0; j < BINS; j++){ - stadistics->histogram[j] *= inv_factor; - } -} - -void Stadistics_compute_skewness(Stadistics *stadistics) -{ - const float zk_values[] = { - -1.0,-0.907753,-0.821362,-0.740633,-0.665375,-0.595396,-0.530504,-0.470508,-0.415214,-0.364431, - -0.317968,-0.275632,-0.237231,-0.202574,-0.171468,-0.143721,-0.119142,-0.097538,-0.078717,-0.062488, - -0.048659,-0.037037,-0.027431,-0.019648,-0.013497,-0.008786,-0.005323,-0.002915,-0.001372,-0.0005, - -0.000108,-4e-06,4e-06,0.000108,0.0005,0.001372,0.002915,0.005323,0.008786,0.013497, - 0.019648,0.027431,0.037037,0.048659,0.062488,0.078717,0.097538,0.119142,0.143721,0.171468, - 0.202574,0.237231,0.275632,0.317968,0.364431,0.415214,0.470508,0.530504,0.595396,0.665375, - 0.740633,0.821362,0.907753,1.0}; - - float skew = 0.0; - for (int k = 0; k < BINS; k++) - { - float pzk = stadistics->histogram[k]; // we asssumed values are normalized - skew += zk_values[k] * pzk; - } - stadistics -> skewness = skew; -} - - -void Stadistics_compute_minmaxavg(Stadistics *stadistics){ - - float temp_mean = 0; - uint8_t temp_min = 0; - uint8_t temp_max = 0; - - // mean - for (uint8_t k = 1; k < BINS; k++) // k=0 does not contribute to mean value - { - temp_mean += stadistics->histogram[k] * k; // assuming histogram is normalized - } - - // min - for (uint8_t k = 0; k < BINS; k++) - { - if (stadistics->histogram[k]){ - temp_min = k; - break; - } - } - - // max - for (uint8_t k = BINS -1; k > 0; k--) - { - if (stadistics->histogram[k]){ - temp_max = k; - break; - } - } - - // Values *4 to return to 0-255 - stadistics -> mean = (uint8_t) (temp_mean+0.5) << 2; // +0.5 to avoid ceiling - stadistics -> min = (uint8_t) temp_min << 2; - stadistics -> max = (uint8_t) temp_max << 2; -} - -void Stadistics_compute_percentile(Stadistics *stadistics){ - float sump = 0.0; - uint8_t k = 0; - // percentile - for (k = BINS - 1; k > 0; k--) - { - sump += stadistics->histogram[k]; - if (sump > PERCENTILE_VALUE){ // I assume histogram is normalized to 0-1 - break; - } - } - stadistics -> percentile = (k << 2); // Values *4 to return to 0-255 -} - -uint16_t Stadistics_compute_variance(Stadistics *stadistics){ - uint8_t mean = stadistics->mean >> 2; // /4 to return to histogram range 0-64 - float diff = 0.0; - double variance = 0; - - for (uint8_t k = 0; k < BINS; k++){ - if (stadistics->histogram[k]){ - diff = (k - mean); - diff = diff*diff; - variance += stadistics->histogram[k]*diff; - } - } - variance = (uint16_t)(variance * 16); // Values *16 to return to 0-255 - return variance; -} - -void Stadistics_compute_all(const uint32_t buffsize, const uint8_t step, uint8_t *buffer, Stadistics *stadistics){ - Stadistics_compute_histogram(buffsize, step, buffer, stadistics); - Stadistics_compute_skewness(stadistics); - Stadistics_compute_minmaxavg(stadistics); - Stadistics_compute_percentile(stadistics); -} - -static char get_rgb_color(uint32_t pos, uint16_t width) { - static const char color_table[4] = {RED, GREEN, GREEN, BLUE}; - uint32_t x = pos % width; - uint32_t y = pos / width; - uint8_t index = ((y & 1) << 1) | (x & 1); - return color_table[index]; -} \ No newline at end of file diff --git a/camera/stadistics.h b/camera/stadistics.h deleted file mode 100644 index 5686f031..00000000 --- a/camera/stadistics.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef STADISTICS_H -#define STADISTICS_H - -#include -#include // memset -#include // null -#include // free, alloc - -typedef struct Stadistics{ - float histogram[64]; - float skewness; - uint8_t mean; - uint8_t max; - uint8_t min; - uint8_t percentile; -} Stadistics; - -Stadistics *Stadistics_alloc(void); -Stadistics Stadistics_initialize(void); -void Stadistics_free(Stadistics *self); - -void Stadistics_compute_all(const uint32_t buffsize, const uint8_t step, uint8_t *buffer, Stadistics *stadistics); -void Stadistics_compute_histogram(const uint32_t buffsize, const uint8_t step, uint8_t *buffer, Stadistics *stadistics); -void Stadistics_compute_skewness(Stadistics *stadistics); -void Stadistics_compute_minmaxavg(Stadistics *stadistics); -void Stadistics_compute_percentile(Stadistics *stadistics); -uint16_t Stadistics_compute_variance(Stadistics *stadistics); - -// aux functions -static char get_rgb_color(uint32_t pos, uint16_t width); - -#endif \ No newline at end of file diff --git a/camera/utils.h b/camera/utils.h deleted file mode 100644 index 5605973a..00000000 --- a/camera/utils.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -#define PRINT_TIME(a,b) printf("%d\n", b - a); - -int measure_time(){ - int y = 0; - asm volatile("gettime %0": "=r"(y)); - return y; -} diff --git a/doc/Doxyfile.inc b/doc/Doxyfile.inc new file mode 100644 index 00000000..065539e2 --- /dev/null +++ b/doc/Doxyfile.inc @@ -0,0 +1,10 @@ +# This file provides overrides to the Doxyfile configuration +PROJECT_NAME = FWK_CAMERA +PROJECT_BRIEF = "Camera interface for Xcore AI" +PREDEFINED = __DOXYGEN__=1 +PREDEFINED += DWORD_ALIGNED= +PREDEFINED += __attribute__((weak))= +PREDEFINED += C_API= MA_C_API= C_API_START= C_API_END= EXTERN_C= +INPUT += camera/ +ALIASES += "beginrst=^^\verbatim embed:rst^^" +ALIASES += "endrst=\endverbatim" diff --git a/doc/exclude_patterns.inc b/doc/exclude_patterns.inc new file mode 100644 index 00000000..d28b501b --- /dev/null +++ b/doc/exclude_patterns.inc @@ -0,0 +1,5 @@ +# The following patterns are to be excluded from the documentation build +tests +modules +sensors +build diff --git a/doc/programming_guide/01_Introduction.rst b/doc/programming_guide/01_Introduction.rst new file mode 100644 index 00000000..2e01b416 --- /dev/null +++ b/doc/programming_guide/01_Introduction.rst @@ -0,0 +1,63 @@ +Introduction +============= + +.. include:: ../substitutions.rst + +The purpose of this programming guide is to provide developers with a comprehensive understanding +of the ``fwk_camera`` architecture and guide them on how to effectively interact with cameras using the XCORE-AI-EXPLORER board. + +Conventions and Terminology +--------------------------- +To ensure clarity and consistency throughout this guide, the following conventions and terminology are used: + +- MIPI: Mobile Industry Processor Interface. It is a standard interface specification for mobile devices, including cameras. The ``fwk_camera`` architecture utilises MIPI specifications for camera communication. +- Xcore: XMOS proprietary event-driven processor architecture. It provides high-performance parallel processing capabilities and is used in XMOS devices to handle camera interface and data processing. +- Channels: In the context of XMOS devices, channels are communication pathways that allow data exchange between different components. Channels play a crucial role in camera control and data transfer within the ``fwk_camera`` architecture. +- I2C: Inter-Integrated Circuit. It is a widely used serial communication protocol for controlling and configuring devices. Within the ``fwk_camera`` architecture, I2C is utilised for camera control operations, such as adjusting settings and retrieving sensor information. + +Features +--------- +The XCORE-AI-EXPLORER board features an 15-pin MIPI CSI2 port (compatible with Raspberry PI). +This port is connected to the xcore.ai processor, so we can directly processing an image from an external sensor and performing various operations, +such as converting a RAW image to an RGB image (applying ISP functions), +analyzing the image using AI models with xmos-ai-tools, +converting a MIPI camera to other interfaces as USB, SPI, etc. + +The ``fwk_camera`` alongside with the Explorer board architecture provides the following features: + +- MIPI CSI2 interface +- Up to 1 Gbps per lane +- Low-resolution filtering +- Supported cameras: `IMX219`_ + +This repository contains a set of tools for image acquisition, processing, and transmission. +The architecture, viewed from a high level, is composed of the following elements: + +.. figure:: images/1_high_level_view.png + :alt: High-level block diagram + :align: center + + High-level block diagram of the ``fwk_camera``. + +1. Camera hardware and interface +2. Camera drivers +3. Camera application +4. Sensor configuration +5. Camera output + +Each of these elements is described in detail in the following sections. + +Getting Started +---------------- + +To start using the ``fwk_camera``, you can proceed to the Quick Start Guide, go to: + +:ref:`QS_FWKC`. + +Additional Resources +--------------------- + +- MIPI CSI-2 specification: `MIPI`_ +- XMOS I2C library user guide: `XMOSI2C`_ +- XMOS Programming Guide: `XMOSProgrammingGuide`_ +- IMX219 datasheet: `IMX219`_ diff --git a/doc/programming_guide/02_Architecture_and_Design.rst b/doc/programming_guide/02_Architecture_and_Design.rst new file mode 100644 index 00000000..8ba09647 --- /dev/null +++ b/doc/programming_guide/02_Architecture_and_Design.rst @@ -0,0 +1,39 @@ +Architecture and Design +======================= + +.. include:: ../substitutions.rst + +In this section we describe the main components of ``fwk_camera`` and how they interact with each other. +We also describe the design decisions that were made and the reasoning behind them. +The main components of ``fwk_camera`` are the following: + + +Hardware Architecture +--------------------- +From a hardware point of view, ``fwk_camera`` is composed of the following modules: + +#. Camera connector : 15-PIN MIPI CSI connector for the camera module. (compatible with Raspberry Pi). +#. MIPI Shim : Xmos MIPI hardware to convert MIPI signals to Xcore ports. +#. Xcore : Xmos Xcore processor to process the MIPI signals. +#. xTag Host connector : USB connector to connect to the host. + +Software Architecture +--------------------- +From a software point of view, ``fwk_camera`` is composed of the following modules: + +.. _obj_diagram: + +.. figure:: images/2_object_diagram.png + :alt: ``fwk_camera`` object diagram + :figclass: custom-class + + Object Diagram + +Module description: + +#. Camera section: the camera section takes care of starting, processing and stopping the camera. It has to be aware of the sensor configuration (in the sensor section) and meet the user demands (in the user section). +#. User section: the user section is the interface between the user and the camera, where we define what we want to do with the frames. +#. Sensor section: configuration and control of the sensor. +#. Host section: provide the interface to write the frames or files to the host. + +There are other sections not mentioned in the diagram, such as the test section, which is used to test the camera. diff --git a/doc/programming_guide/03_Building_and_testing_the_Software.rst b/doc/programming_guide/03_Building_and_testing_the_Software.rst new file mode 100644 index 00000000..7b020915 --- /dev/null +++ b/doc/programming_guide/03_Building_and_testing_the_Software.rst @@ -0,0 +1,95 @@ +Building and running the Software +================================= + +.. include:: ../substitutions.rst + +This section will provide details on how the software can be build and run. You will go through the full process from +the requirements to seing images taked from xcore on your screen. + +Requirements +------------ + +Hardware requirements +^^^^^^^^^^^^^^^^^^^^^ +- XCORE.AI EVALUATION KIT (XK-EVK-XU316) +- Camera module +- Camera ribbon connector +- 2x Micro USB cable (Power supply and xTag) +- xTag debugger and cable + +Software requirements +^^^^^^^^^^^^^^^^^^^^^ +- XTC tools (15.2.1): `SW_TOOLS`_ +- CMake, Ninja (Windows) +- Python 3.7 or later + +Building the firmware and the examples +-------------------------------------- + +``fwk_camera`` is intended to be used as part of an application, therefore depending if you have it on its own or not, you will +be able to build examples. By following the instructions below, you'll be able to build all possible targets. + +.. tab:: Linux and Mac + + .. code-block:: console + + >> Linux and Mac + cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + make -C build + +.. tab:: Windows + + .. code-block:: console + + >> Windows + cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + ninja -C build + +Building the host app (xscope_fileio) +------------------------------------- + +All ``fwk_camera`` examples do ``fileio`` over a tool called ``xscope_fileio``. There is a python wrapper script ``python/run_xcore_bin.py`` +which will run the example binary of your choice using ``xscope_fileio``. This script requires some ``xscope_fileio`` +host binaries. To get host tools you will need to follow the steps below: + +1. Make sure you have a C compiler installed. If you are developing on Windows, we recomend using VS tools with a ``cl`` compiler. +2. Install the xscope_fileio package: + +.. tab:: Linux and Mac + + .. code-block:: console + + >> Linux and Mac + pip install -e utils/xscope_fileio + +.. tab:: Windows + + .. code-block:: console + + >> Windows + pip install -e utils/xscope_fileio + cd utils/xscope_fileio/host + cmake -G Ninja . && ninja + +After you've gone through those instructions once, all you'll need to do is activate your python virtual environment. + +Running the examples +-------------------- + +1. Make sure you have activated your python environment with installed ``xscope_fileio``. +2. Run the example with the following command: + +.. code-block:: console + + python python/run_xscope_bin.py + # or + python python/run_xscope_bin.py # and chose a binary in the dropdown menu + +3. Depending on the example you ran, you can get ``.bmp``, ``.raw`` or ``.bin`` files. With ``.bmp`` the image can be looked at straight +away, whereas other formats need some more processing. You can use one of the following python scripts to decode and see your image: + +.. code-block:: console + + python/decode_downsampled.py + python/decode_raw8.py + python/decode_raw10.py diff --git a/doc/programming_guide/04_Developing_custom_software.rst b/doc/programming_guide/04_Developing_custom_software.rst new file mode 100644 index 00000000..448831c0 --- /dev/null +++ b/doc/programming_guide/04_Developing_custom_software.rst @@ -0,0 +1,39 @@ +Software customisation +====================== + +.. include:: ../substitutions.rst + +Adding your own sensor +---------------------- + +Hardware +^^^^^^^^ + +First you'll need to see if your sensor if compatible with XCORE.AI EVALUATION KIT (XK-EVK-XU316). +Your sensor has to: + +- Support MIPI CSI2 protocol +- Be driven from 3.3V source + +Software +^^^^^^^^ + +If you navigate to ``sensors/api/SensorBase.hpp``, you will find the ``SensorBase`` class which is intended to be derived from. +It doesn't have anything to do with a particular sensor, it only provides an API to do basic I2C communication with the sensor. +Inside ``SensorBase`` class you can also find some public virtual methods which will **have to** be implemented in the derived class. + +In order to implement your own sensor you will need to create a directory in ``sensors/src/_your_sensor``, implement a derived class with +``initialize()``, ``stream_start()``, ``stream_stop()``, ``set_exposure()``, ``configure()`` and ``control()`` methods. When you have your +sensor class implemented you can add its header file into ``sensors/src/sensor_control.cpp`` and have ``sensor_control()`` calling +your sensor API like this: + +.. code-block:: C++ + + sensor_control(chanend_t c_control) { + // i2c_conf definition first + sensor::YOUR_SENSOR snsr(i2c_conf, other_arguments); + snsr.control(c_control); + } + +After you've done that, you will need to put it into the build system by adding your sources and includes to the following cmake +file ``sensors/CMakeLists.txt``. Then rerunning your build tool (``make`` or ``ninja``) will rerun the cmake and rebuild the project. diff --git a/doc/programming_guide/05_Resource_usage.rst b/doc/programming_guide/05_Resource_usage.rst new file mode 100644 index 00000000..ff6bb813 --- /dev/null +++ b/doc/programming_guide/05_Resource_usage.rst @@ -0,0 +1,37 @@ +Resource usage +============== + +Resource usage will be dependant on your application. Here we'll take ``take_picture_downsample`` as an example. It should demonstrate the +minimum resource usage for taking a picture and streaming it to the host machine. + +Core usage +---------- + +You can find the ``take_picture_downsample`` application thread diagram below. For more detals about thread commutication, please refer to +:ref:`Software Architecture` section. + +.. figure:: images/3_v0.2.0_thread_diagram.png + :alt: ``fwk_camera`` thread diagram + :align: center + + Resource usage + +Here you will notice that the ``xscope_fileio`` related resources are dashed. The reason for that is that you are not likely to use ``xscope_fileio`` +in your application. We use it in this example because it's a good testing/debugging tool. So, realistically, for the minimum camera application +you will need 4 cores on tile 1. + +Memory Usage +------------ + +More accurate memory report can be found below. + +.. code-block:: console + + Constraint check for tile[0]: + Memory available: 524288, used: 14084 . OKAY + (Stack: 748, Code: 11692, Data: 1644) + Constraints checks PASSED. + Constraint check for tile[1]: + Memory available: 524288, used: 88896 . OKAY + (Stack: 62900, Code: 16284, Data: 9712) + Constraints checks PASSED. diff --git a/doc/programming_guide/images/1_high_level_view.png b/doc/programming_guide/images/1_high_level_view.png new file mode 100644 index 00000000..7e76285c Binary files /dev/null and b/doc/programming_guide/images/1_high_level_view.png differ diff --git a/doc/programming_guide/images/2_object_diagram.png b/doc/programming_guide/images/2_object_diagram.png new file mode 100644 index 00000000..2b616d34 Binary files /dev/null and b/doc/programming_guide/images/2_object_diagram.png differ diff --git a/doc/programming_guide/images/3_v0.2.0_thread_diagram.png b/doc/programming_guide/images/3_v0.2.0_thread_diagram.png new file mode 100644 index 00000000..73c0fae1 Binary files /dev/null and b/doc/programming_guide/images/3_v0.2.0_thread_diagram.png differ diff --git a/doc/programming_guide/index.rst b/doc/programming_guide/index.rst new file mode 100644 index 00000000..b48722a1 --- /dev/null +++ b/doc/programming_guide/index.rst @@ -0,0 +1,14 @@ +**************************** +FWK-CAMERA Programming Guide +**************************** + +.. include:: ../substitutions.rst + +.. toctree:: + :maxdepth: 1 + + 01_Introduction + 02_Architecture_and_Design + 03_Building_and_testing_the_Software + 04_Developing_custom_software + 05_Resource_usage diff --git a/doc/quick_start_guide/index.rst b/doc/quick_start_guide/index.rst new file mode 100644 index 00000000..f1166be3 --- /dev/null +++ b/doc/quick_start_guide/index.rst @@ -0,0 +1,78 @@ +.. _QS_FWKC: + +Quick Start Guide +------------------- + +.. include:: ../substitutions.rst + +This page will provide a quick tour through the ``fwk_camera`` repository. You will go through the process +of building the example application and taking a photo in RAW8 format. + +Hardware requirements +^^^^^^^^^^^^^^^^^^^^^ +- XCORE.AI EVALUATION KIT (XK-EVK-XU316) +- Camera module +- Camera ribbon connector +- 2x Micro USB cable (Power supply and xTag) +- xTag debugger and cable + +Software requirements +^^^^^^^^^^^^^^^^^^^^^ +- XTC tools (15.2.1): `SW_TOOLS`_ +- CMake, Ninja (Windows) +- Python 3.7 or later + +Make sure all submodules are imported: + +.. code-block:: console + + git clone --recurse-submodules https://github.com/xmos/fwk_camera.git + +Run the RAW camera demo +^^^^^^^^^^^^^^^^^^^^^^^ +This demo uses the RAW camera module to capture a RAW8 image and save it to a .raw file. +Then, this image can be decoded using the python script ``python decode_raw8.py``. + +1. Ensure that the camera is connected to the board +2. Connect Power Supply and xTag debugger +3. Install python requirements. From the root of the repository run: + +.. code-block:: console + + pip install -r requirements.txt + +4. Build the example using the following commands: + +.. tab:: Linux and Mac + + .. code-block:: console + + >> Linux and Mac + cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + make -C build example_take_picture_raw + +.. tab:: Windows + + .. code-block:: console + + >> Windows + cmake -G Ninja -B build --toolchain=xmos_cmake_toolchain\xs3a.cmake + ninja -C build example_take_picture_raw + cd utils/xscope_fileio/host # Windows requires some extra steps + cmake -G Ninja . && ninja + +5. Run the example using the following command: + +.. code-block:: console + + python python/run_xscope_bin.py build/examples/take_picture_raw/example_take_picture_raw.xe + +6. You should see the camera communicating with the host and the image being saved to a .raw file. + +7. To decode the image use the following command: + +.. code-block:: console + + python python/decode_raw8.py + +8. You should see the decoded image displayed on the screen diff --git a/doc/release_notes/index.rst b/doc/release_notes/index.rst new file mode 100644 index 00000000..6fbb91cb --- /dev/null +++ b/doc/release_notes/index.rst @@ -0,0 +1,70 @@ +Release Notes +============= + +Version 0.2.0 +------------- + +Description +*********** + +In this release we've added a support for a larger resolution and reimplemented sensor control to be more flexible. + +New Features +************ + +Added support for 1280x960 frame with the downsampling factor of 8. Sensor component has been reimplemented to be more flexible with the resolution and the RAW format. +Now it will take arguments into a class constructor rather then relying on pre-processor definitions. + +Limitations +*********** + +- RAW10 downsample: RAW10 downsample is not supported. +- 1280x960 in RAW: 1280x960 in RAW8 ar RAW10 can't be fitted in RAM. +- Still heavily relying on the pre-processor values. + +Known Issues +************ + +- Artefacts: + + - AE: + Auto exposure can struggle in environment with high dynamic range. + It will choose to privilege the majority of the image, leaving some areas underexposed or overexposed. + + - AWB: + Due to the automatic white balancing algorithm, the ISP will try to compensate the image's illuminance. If the environment is a pure colour pure red or pure blue, it can appear more white than expected. + In this case, AWB can be turned off, or changed manually to be adequate to a specific scene by adjusting the static AWB values. + +Version 0.1.0 +------------- + +Description +*********** +This is an initital release of the ``fwk_camera`` repo. It contains a basic interface for acquiring images, processing them and sending them to the host. +It also contains a basic interface for controlling the camera ISP features. + +New Features +************ + +In this first release we include two basic examples to capture an image from the Sony IMX219 sensor (Raspberry Pi Camera V2). +For the first examples the image is directly captured as raw and saved as a .raw file. +The second example is a more complex pipeline that involves the ISP. The image is captured as raw, processed by the ISP and saved as a .bmp and .bin file. + +Limitations +*********** + +- RAW10 downsample: RAW10 downsample is not supported. +- Galaxy core sensor pipeline is not yet supported. + +Known Issues +************ + +- Artefacts: + + - AE: + Auto exposure can struggle in environment with high dynamic range. + It will choose to privilege the majority of the image, leaving some areas underexposed or overexposed. + + - AWB: + Due to the automatic white balancing algorithm, the ISP will try to compensate the image's illuminance. If the environment is a pure colour pure red or pure blue, it can appear more white than expected. + In this case, AWB can be turned off, or changed manually to be adequate to a specific scene by adjusting the static AWB values. diff --git a/doc/substitutions.rst b/doc/substitutions.rst new file mode 100644 index 00000000..a6cb894a --- /dev/null +++ b/doc/substitutions.rst @@ -0,0 +1,10 @@ + +.. _MIPI: https://www.mipi.org/specifications/csi-2 +.. _XMOS: https://www.xmos.ai/ +.. _XMOSI2C: https://www.xmos.ai/download/lib_i2c-%5Buserguide%5D(5.0.0rc3).pdf +.. _XMOSProgrammingGuide: https://www.xmos.ai/download/XMOS-Programming-Guide-(documentation)(E).pdf +.. _IMX219: https://www.opensourceinstruments.com/Electronics/Data/IMX219PQ.pdf +.. _GH_FWK_CAMERA: https://github.com/xmos/fwk_camera +.. _SW_TOOLS: https://www.xmos.ai/software-tools/ +.. _XSCOPE_FILEIO: https://github.com/xmos/xscope_fileio +.. _UTILS_README: https://github.com/xmos/fwk_camera/blob/develop/utils/README.rst diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..65da05ab --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,39 @@ +# set xscope config file path +set(CONFIG_XSCOPE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +# set compiler/linker flags and liked libraries to be used by all examples +set(EXAMPLE_APP_COMPILER_FLAGS + -Os + -g + -report + -Wall + -Werror + -fxscope + -mcmodel=large + -target=${XCORE_TARGET} + ${CONFIG_XSCOPE_PATH}/config.xscope +) + +set(EXAMPLE_APP_LINK_OPTIONS + "-report" + "-target=${XCORE_TARGET}" + "${CONFIG_XSCOPE_PATH}/config.xscope" +) + +set(EXAMPLE_APP_LINK_LIBRARIES + fwk_camera::mipi + fwk_camera::sensors + fwk_camera::camera + fwk_camera::utils + fwk_camera::xscope_fileio + lib_i2c +) + +# add the examples +add_subdirectory(take_picture_raw) +add_subdirectory(take_picture_local) + +if(${CONFIG_MIPI_FORMAT} STREQUAL ${CONFIG_RAW8}) + # we only support RAW8 images in this example for now + add_subdirectory(take_picture_downsample) +endif() diff --git a/examples/README.rst b/examples/README.rst new file mode 100644 index 00000000..a022cab6 --- /dev/null +++ b/examples/README.rst @@ -0,0 +1,10 @@ +Examples +=========== + +This folder contains two examples for taking pictures: "take_picture_downsample" and "take_picture_raw". + +.. toctree:: + :maxdepth: 1 + + take_picture_downsample/README + take_picture_raw/README diff --git a/examples/take_picture_dynamic/src/config.xscope b/examples/config.xscope similarity index 58% rename from examples/take_picture_dynamic/src/config.xscope rename to examples/config.xscope index 69236026..d81166cf 100644 --- a/examples/take_picture_dynamic/src/config.xscope +++ b/examples/config.xscope @@ -15,8 +15,11 @@ - - - - + + + + + + + diff --git a/examples/examples.cmake b/examples/examples.cmake deleted file mode 100644 index 84e94c42..00000000 --- a/examples/examples.cmake +++ /dev/null @@ -1,33 +0,0 @@ - -# edit subfolder, put your examples here -#TODO replace by glob -set(EXAMPLES - simple_timing - take_picture - take_picture_dynamic - take_picture_dynamic_crop - ) - -# add all the examples that you need -if(${CMAKE_SYSTEM_NAME} STREQUAL XCORE_XS3A) - foreach(EXAMPLE ${EXAMPLES}) - include(${CMAKE_CURRENT_LIST_DIR}/${EXAMPLE}/${EXAMPLE}.cmake) - endforeach(EXAMPLE) -else() - # Get the "version" value from the JSON element - file(READ settings.json JSON_STRING) - string(JSON SDK_VERSION GET ${JSON_STRING} ${IDX} version) - - # Determine OS, set up install dir - if(${CMAKE_SYSTEM_NAME} STREQUAL Windows) - set(HOST_INSTALL_DIR "$ENV{USERPROFILE}\\.xmos\\bin") - else() - set(HOST_INSTALL_DIR "/opt/xmos/bin") - endif() - - add_subdirectory(modules/xscope_fileio/xscope_fileio/host) - add_subdirectory(../../) - install(TARGETS xscope_host_endpoint DESTINATION ${HOST_INSTALL_DIR}) -endif() - - diff --git a/examples/simple_timing/README.md b/examples/simple_timing/README.md deleted file mode 100644 index 55573c8c..00000000 --- a/examples/simple_timing/README.md +++ /dev/null @@ -1,13 +0,0 @@ -## Example: Simple timing - -This example measures some basic timing information while taking a single frame -(see take picutre example) - -### Time measurements: -* T1 == time of a frame (without blank) -* T2 == time doing operations in a line -* T3 == time between frames -* T4 == time between lines - -## Build example -Run the following command: ```make example_timing``` diff --git a/examples/simple_timing/XCORE-AI-EXPLORER.xn b/examples/simple_timing/XCORE-AI-EXPLORER.xn deleted file mode 100644 index b7d3cd93..00000000 --- a/examples/simple_timing/XCORE-AI-EXPLORER.xn +++ /dev/null @@ -1,109 +0,0 @@ - - - Board - xcore.ai Explorer Kit - - - tileref tile[2] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/simple_timing/simple_timing.cmake b/examples/simple_timing/simple_timing.cmake deleted file mode 100644 index f4e65119..00000000 --- a/examples/simple_timing/simple_timing.cmake +++ /dev/null @@ -1,77 +0,0 @@ -#********************** -# Gather Sources -#********************** - -# <--- Set the executable -set(TARGET example_timing) - -file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.*c) -set(APP_INCLUDES - ${CMAKE_CURRENT_LIST_DIR}/src -) - - -#********************** -# Flags -#********************** -set(APP_COMPILER_FLAGS - -Os - -g - -report - -fxscope - -mcmodel=large - -Wno-xcore-fptrgroup - ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope - ${CMAKE_CURRENT_LIST_DIR}/XCORE-AI-EXPLORER.xn -) -set(APP_COMPILE_DEFINITIONS - configENABLE_DEBUG_PRINTF=1 - PLATFORM_SUPPORTS_TILE_0=1 - PLATFORM_SUPPORTS_TILE_1=1 - PLATFORM_SUPPORTS_TILE_2=0 - PLATFORM_SUPPORTS_TILE_3=0 - PLATFORM_USES_TILE_0=1 - PLATFORM_USES_TILE_1=1 - XUD_CORE_CLOCK=600 -) - -set(APP_LINK_OPTIONS - -report - ${CMAKE_CURRENT_LIST_DIR}/XCORE-AI-EXPLORER.xn - ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope -) - -# <--- Link libraries -set(APP_COMMON_LINK_LIBRARIES - core::general - mipi::lib_mipi - i2c::lib_i2c - sensors::lib_imx - ) - - -#********************** -# Tile Targets -#********************** -add_executable(${TARGET} EXCLUDE_FROM_ALL) -target_sources(${TARGET} PUBLIC ${APP_SOURCES}) -target_include_directories(${TARGET} PUBLIC ${APP_INCLUDES}) -target_compile_definitions(${TARGET} PRIVATE ${APP_COMPILE_DEFINITIONS}) -target_compile_options(${TARGET} PRIVATE ${APP_COMPILER_FLAGS}) -target_link_libraries(${TARGET} PUBLIC ${APP_COMMON_LINK_LIBRARIES}) -target_link_options(${TARGET} PRIVATE ${APP_LINK_OPTIONS}) - - -#********************** -# Create run and debug targets -#********************** -create_run_target(${TARGET}) -create_debug_target(${TARGET}) -create_flash_app_target(${TARGET}) -create_install_target(${TARGET}) - -# then custom command line -add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_BINARY_DIR}/*.xe /mnt/c/Users/albertoisorna/exec/) - diff --git a/examples/simple_timing/src/main.xc b/examples/simple_timing/src/main.xc deleted file mode 100644 index 77977616..00000000 --- a/examples/simple_timing/src/main.xc +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2020, XMOS Ltd, All rights reserved -#include -#include -#include - -#include "i2c.h" -#include "mipi_timing.h" - -// I2C interface ports -#define Kbps 400 -on tile[0]: port p_scl = XS1_PORT_1N; -on tile[0]: port p_sda = XS1_PORT_1O; - -int main(void) -{ - i2c_master_if i2c[1]; - par { - on tile[0]: i2c_master(i2c, 1, p_scl, p_sda, Kbps); - on tile[MIPI_TILE]: mipi_main(i2c[0]); - } - return 0; -} diff --git a/examples/simple_timing/src/mipi_timing.h b/examples/simple_timing/src/mipi_timing.h deleted file mode 100644 index 4b02d074..00000000 --- a/examples/simple_timing/src/mipi_timing.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include -#include "mipi.h" -#include "sensor.h" -#include -///TODO this is inside lib_mipi -#ifndef MIPI_CLKBLK -#define MIPI_CLKBLK XS1_CLKBLK_1 -#endif - -// Packets definitions -typedef struct -{ - mipi_header_t header; - uint8_t payload[MIPI_LINE_WIDTH_BYTES]; -} mipi_data_t; - -typedef struct -{ - mipi_header_t header; - uint8_t payload[MIPI_MAX_PKT_SIZE_BYTES]; -} mipi_packet_t; - -typedef struct -{ - unsigned frame_number; - unsigned line_number; -} image_rx_t; - - -static mipi_packet_t packet_buffer[MIPI_PKT_BUFFER_COUNT]; - -// Functions definitions -#define gMipiPacketRx(...) MipiPacketRxnrev(__VA_ARGS__) // define your own function to use - -#ifdef __XC__ - -void mipi_main(client interface i2c_master_if i2c); - -#endif diff --git a/examples/simple_timing/src/mipi_timing.xc b/examples/simple_timing/src/mipi_timing.xc deleted file mode 100644 index 266ca648..00000000 --- a/examples/simple_timing/src/mipi_timing.xc +++ /dev/null @@ -1,301 +0,0 @@ -#include -#include -#include -#include // for ports -#include -#include // exit status -#include -#include -#include - -#include - - -// ********** input with measurement do you want to perform -// T1 == time of a frame (without blank) -// T2 == time doing operations in a line -// T3 == time between frames -// T4 == time between lines -#define MEASURE_T4 -#define DO_MEMCPY 0 -#define TICKS_DELAY 1 -// *********** - -// I2C -#include "i2c.h" - -// MIPI -#include "mipi_timing.h" -#include "MipiPacket.h" - -// Sensor -#define MSG_SUCCESS "Stream start OK\n" -#define MSG_FAIL "Stream start Failed\n" - -// timing information -#define TIME_MSG_1 "elapsed\t%.6f\t[ms]\t%d\t[ticks]\n" -#define PRINT_TIME(y) printf("%d\n", y) -#define MS_MULTIPLIER 0.00001 - -// Image -#include "process_frame.h" - -// Globals -char end_transmission = 0; -char found = 0; -mipi_data_type_t p_data_type = 0; -int t1, t2, t3, t4, t5, t6; - -/** -* Declaration of the MIPI interface ports: -* Clock, receiver active, receiver data valid, and receiver data -*/ -on tile[MIPI_TILE] : in port p_mipi_clk = XS1_PORT_1O; -on tile[MIPI_TILE] : in port p_mipi_rxa = XS1_PORT_1E; // activate -on tile[MIPI_TILE] : in port p_mipi_rxv = XS1_PORT_1I; // valid -on tile[MIPI_TILE] : buffered in port:32 p_mipi_rxd = XS1_PORT_8A; // data -on tile[MIPI_TILE] : clock clk_mipi = MIPI_CLKBLK; - -/** - * The packet buffer is where the packet decoupler will tell the MIPI receiver - * thread to store received packets. - */ -#define DEMUX_DATATYPE 0 // RESERVED -#define DEMUX_MODE 0x00 // no demux -#define DEMUX_EN 0 // DISABLE DEMUX -#define MIPI_CLK_DIV 1 // CLK DIVIDER -#define MIPI_CFG_CLK_DIV 3 // CFG DIVIDER -#define REV(n) ((n << 24) | (((n>>16)<<24)>>16) | (((n<<16)>>24)<<16) | (n>>24)) - - -// Saves the image to a file. This is a bit tricky because we don't want to use an endless loop -void save_image_to_file(chanend flag) -{ - select - { - case flag :> int i: - { - // write to a file - write_image(); - delay_microseconds(200); // for stability - exit(1); // end the program here - break; - } - } -} - -//TODO this should be in utils -int measure_time(){ - int y = 0; - asm volatile("gettime %0": "=r"(y)); - return y; -} - -unsafe { -static -void handle_packet( - image_rx_t* img_rx, - const mipi_packet_t* unsafe pkt, - chanend flag) - { - - // definitions - const mipi_header_t header = pkt->header; - const mipi_data_type_t data_type = MIPI_GET_DATA_TYPE(header); - const unsigned is_long = MIPI_IS_LONG_PACKET(header); - const unsigned word_count = MIPI_GET_WORD_COUNT(header); - static uint8_t wait_for_clean_frame = 1; // static because it will change in the future - //[debug] printf("packet header = 0x%08x, wc=%d \n", REV(header), word_count); - - - // We return until the start of frame is reached - if (wait_for_clean_frame == 1){ - if (data_type != MIPI_DT_FRAME_START){ - return; - } - else{ - wait_for_clean_frame = 0; - } - } - // Use case by data type - switch (data_type) - { - case MIPI_DT_FRAME_START: { // Start of frame. Just reset line number. - #ifdef MEASURE_T3 - PRINT_TIME((measure_time() - t3)); - t3 = measure_time(); - #endif - - #ifdef MEASURE_T1 - t1 = measure_time(); - #endif - - img_rx->frame_number++; - img_rx->line_number = 0; - break; - } - - case EXPECTED_FORMAT:{ // save it in SRAM and increment line - - #ifdef MEASURE_T4 - PRINT_TIME((measure_time() - t4)); - t4 = measure_time(); - #endif - - // if line number is grater than expected, just reset the line number - if (img_rx->line_number >= MIPI_IMAGE_HEIGHT_PIXELS) - { - break; // let pass the rest until next frame - } - - #if DO_MEMCPY - // then copy - not_silly_memcpy( - &FINAL_IMAGE[img_rx->line_number][0], - &pkt->payload[0], - MIPI_LINE_WIDTH_BYTES); // here is data width - #endif - - // burn some cycles - delay_ticks(TICKS_DELAY); - - // printf("0x%04x,",pkt->payload[0]); - - // go for next line and exit - img_rx->line_number++; - - break; - } - - case MIPI_DT_EOT:{ - - break; - } - - case MIPI_DT_FRAME_END:{ // we signal that the frame is finish so we can write it to a file - // printf("--------->> End\n"); - #ifdef MEASURE_T1 - PRINT_TIME((measure_time() - t1)); - #endif - break; - } - - default: // error with frame type or protected types - { - break; - } - } - } - - -#pragma unsafe arrays -static -void mipi_packet_handler( - streaming chanend c_pkt, - streaming chanend c_ctrl, - chanend flag - ) -{ - image_rx_t img_rx = {0,0}; // stores the coordinates X, Y of the image - unsigned pkt_idx = 0; // packet index - - // Give the MIPI packet receiver a buffer - outuint((chanend) c_pkt, (unsigned) &packet_buffer[pkt_idx]); - pkt_idx = (pkt_idx + 1) & (MIPI_PKT_BUFFER_COUNT-1); - - while(1) { - - #ifdef MEASURE_T6 - t6 = measure_time(); - #endif - - - // Wait for the receiver thread to tell us a new packet was completed. - mipi_packet_t * unsafe pkt = (mipi_packet_t*unsafe) inuint((chanend) c_pkt); - - - // Give it a new buffer before processing the received one - outuint((chanend) c_pkt, (unsigned) &packet_buffer[pkt_idx]); - pkt_idx = (pkt_idx + 1) & (MIPI_PKT_BUFFER_COUNT-1); - - #ifdef MEASURE_T6 - PRINT_TIME((measure_time() - t6)); - #endif - - // Process the packet. We need to be finished with this and looped - // back up to grab the next MIPI packet BEFORE the receiver thread - // tries to give us the next packet. - #ifdef MEASURE_T2 - t4 = measure_time(); - #endif - - handle_packet(&img_rx, pkt, flag); - - - #ifdef MEASURE_T2 - PRINT_TIME((measure_time() - t4)); - #endif - } -} -} - - -void mipi_main(client interface i2c_master_if i2c) -{ - printf("< Start of APP capture application >\n"); - streaming chan c_pkt; - streaming chan c_ctrl; - chan flag; - - - // See AN for MIPI shim - // 0x7E42 >> 0111 1110 0100 0010 - // in the explorer BOARD DPDN is swap - write_node_config_reg(tile[MIPI_TILE], - XS1_SSWITCH_MIPI_DPHY_CFG3_NUM, - 0x7E42); //TODO decompose into different values - - // send packet to MIPI shim - MipiPacketRx_init(tile[MIPI_TILE], - p_mipi_rxd, - p_mipi_rxv, - p_mipi_rxa, - p_mipi_clk, - clk_mipi, - DEMUX_EN, - DEMUX_DATATYPE, - DEMUX_MODE, - MIPI_CLK_DIV, - MIPI_CFG_CLK_DIV); - - // Start camera and its configurations - int r = 0; - r |= camera_init(i2c); - delay_milliseconds(100); //TODO include this inside the function - r |= camera_configure(i2c); - delay_milliseconds(500); - - // Start streaming mode - r |= camera_start(i2c); - delay_milliseconds(2000); - - if (r != 0){ - printf(MSG_FAIL); - } - else{ - printf(MSG_SUCCESS); - } - - // ask the user to press the key "c" to capture the frame - // user_input(); //TODO not working with a par job - - - // start the different jobs (packet controller, handler, and post_process) - par - { - gMipiPacketRx(p_mipi_rxd, p_mipi_rxa, c_pkt, c_ctrl); - mipi_packet_handler(c_pkt, c_ctrl, flag); - save_image_to_file(flag); - } -} - diff --git a/examples/simple_timing/src/process_frame.c b/examples/simple_timing/src/process_frame.c deleted file mode 100644 index 0b75aa72..00000000 --- a/examples/simple_timing/src/process_frame.c +++ /dev/null @@ -1,43 +0,0 @@ - -#include "process_frame.h" -#include -#include -#include - -#define FINAL_IMAGE_FILENAME "out.raw" -uint8_t FINAL_IMAGE[MIPI_IMAGE_HEIGHT_PIXELS][MIPI_LINE_WIDTH_BYTES]; - -// Write image to disk. This is called by camera main () to do the work -void write_image() -{ - static FILE* img_file = NULL; - img_file = fopen(FINAL_IMAGE_FILENAME, "wb"); - for(int k = 0; k < MIPI_IMAGE_HEIGHT_PIXELS; k++){ - for(int j = 0; j < MIPI_IMAGE_WIDTH_BYTES; j++){ - fwrite(&FINAL_IMAGE[k][j], sizeof(uint8_t), 1, img_file); - } - } - fclose(img_file); - printf("Outfile %s\n", FINAL_IMAGE_FILENAME); - printf("image size (%dx%d)\n", MIPI_LINE_WIDTH_BYTES, MIPI_IMAGE_HEIGHT_PIXELS); -} - - -// This is called when want to memcpy from Xc to C -void not_silly_memcpy( - void* dst, - void* src, - size_t size) -{ - memcpy(dst, src, size); -} - -/* -void user_input(void){ - printf("Enter the character 'c' to capture the frame : "); - int c = getchar(); - if(c != 99){ // == ascii "c" - exit(0); - } -} -*/ \ No newline at end of file diff --git a/examples/simple_timing/src/process_frame.h b/examples/simple_timing/src/process_frame.h deleted file mode 100644 index 2b6b7519..00000000 --- a/examples/simple_timing/src/process_frame.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include "mipi_timing.h" - -#ifdef __XC__ -extern "C" { -#endif - -void write_image(); -void not_silly_memcpy(void *dst, void *src, size_t size); -// void user_input(void); - -#ifdef __XC__ -} -#endif \ No newline at end of file diff --git a/examples/take_picture/README.md b/examples/take_picture/README.md deleted file mode 100644 index cf6ae175..00000000 --- a/examples/take_picture/README.md +++ /dev/null @@ -1,13 +0,0 @@ -## Example: Take picture - -This example set the basic settings for the sony sensor and grab a single frame. -By default the format is the following: -* 640x480 RAW8 - -### File Description -* Sensor.h : configures and set the basic settings for the sensor -* Main.xc : starts 2 threads, i2C control and mipi capture -* Process_frame.c : write the image to a file - -## Build example -Run the following command: ```make example_take_picture``` \ No newline at end of file diff --git a/examples/take_picture/XCORE-AI-EXPLORER.xn b/examples/take_picture/XCORE-AI-EXPLORER.xn deleted file mode 100644 index b7d3cd93..00000000 --- a/examples/take_picture/XCORE-AI-EXPLORER.xn +++ /dev/null @@ -1,109 +0,0 @@ - - - Board - xcore.ai Explorer Kit - - - tileref tile[2] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/take_picture/src/main.xc b/examples/take_picture/src/main.xc deleted file mode 100644 index 47a72322..00000000 --- a/examples/take_picture/src/main.xc +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2020, XMOS Ltd, All rights reserved -#include -#include -#include - -#include "i2c.h" -#include "mipi_main.h" - -// I2C interface ports -#define Kbps 400 -on tile[0]: port p_scl = XS1_PORT_1N; -on tile[0]: port p_sda = XS1_PORT_1O; - -int main(void) -{ - i2c_master_if i2c[1]; - par { - on tile[0]: i2c_master(i2c, 1, p_scl, p_sda, Kbps); - on tile[MIPI_TILE]: mipi_main(i2c[0]); - } - return 0; -} diff --git a/examples/take_picture/src/mipi_main.h b/examples/take_picture/src/mipi_main.h deleted file mode 100644 index 089a1a14..00000000 --- a/examples/take_picture/src/mipi_main.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include "mipi.h" - -#include "sensor.h" - -///TODO this is inside lib_mipi -#ifndef MIPI_CLKBLK -#define MIPI_CLKBLK XS1_CLKBLK_1 -#endif - -// Packets definitions -typedef struct -{ - mipi_header_t header; - uint8_t payload[MIPI_LINE_WIDTH_BYTES]; -} mipi_data_t; - -typedef struct -{ - mipi_header_t header; - uint8_t payload[MIPI_MAX_PKT_SIZE_BYTES]; -} mipi_packet_t; - -typedef struct -{ - unsigned frame_number; - unsigned line_number; -} image_rx_t; - - -static mipi_packet_t packet_buffer[MIPI_PKT_BUFFER_COUNT]; - -// Functions definitions -#define gMipiPacketRx(...) MipiPacketRxnrev(__VA_ARGS__) // define your own function to use - -#ifdef __XC__ -void mipi_main(client interface i2c_master_if i2c); -#endif diff --git a/examples/take_picture/src/mipi_main.xc b/examples/take_picture/src/mipi_main.xc deleted file mode 100644 index 99bce5ad..00000000 --- a/examples/take_picture/src/mipi_main.xc +++ /dev/null @@ -1,232 +0,0 @@ -#include -#include -#include -#include // for ports -#include -#include // exit status -#include -#include -#include - -#include - -// I2C -#include "i2c.h" - -// MIPI -#include "mipi_main.h" -#include "MipiPacket.h" - -// Sensor -#define MSG_SUCCESS "Stream start OK\n" -#define MSG_FAIL "Stream start Failed\n" - -// Image -#include "process_frame.h" - -// Globals -char end_transmission = 0; -mipi_data_type_t p_data_type = 0; - - -/** -* Declaration of the MIPI interface ports: -* Clock, receiver active, receiver data valid, and receiver data -*/ -on tile[MIPI_TILE] : in port p_mipi_clk = XS1_PORT_1O; -on tile[MIPI_TILE] : in port p_mipi_rxa = XS1_PORT_1E; // activate -on tile[MIPI_TILE] : in port p_mipi_rxv = XS1_PORT_1I; // valid -on tile[MIPI_TILE] : buffered in port:32 p_mipi_rxd = XS1_PORT_8A; // data -on tile[MIPI_TILE] : clock clk_mipi = MIPI_CLKBLK; - -/** - * The packet buffer is where the packet decoupler will tell the MIPI receiver - * thread to store received packets. - */ -#define DEMUX_DATATYPE 0 // RESERVED -#define DEMUX_MODE 0x00 // no demux -#define DEMUX_EN 0 // DISABLE DEMUX -#define MIPI_CLK_DIV 1 // CLK DIVIDER -#define MIPI_CFG_CLK_DIV 3 // CFG DIVIDER -#define REV(n) ((n << 24) | (((n>>16)<<24)>>16) | (((n<<16)>>24)<<16) | (n>>24)) - - -// Saves the image to a file. This is a bit tricky because we don't want to use an endless loop -void save_image_to_file(chanend flag) -{ - select - { - case flag :> int i: - { - // write to a file - write_image(); - delay_microseconds(200); // for stability //TODO maybe inside the function - exit(1); // end the program here - break; - } - } -} - -unsafe { -static -void handle_packet( - image_rx_t* img_rx, - const mipi_packet_t* unsafe pkt, - chanend flag) - { - // End here if just one transmission - if (end_transmission == 1){ - return; - } - - // definitions - const mipi_header_t header = pkt->header; - const mipi_data_type_t data_type = MIPI_GET_DATA_TYPE(header); - const unsigned is_long = MIPI_IS_LONG_PACKET(header); // not used for the moment - const unsigned word_count = MIPI_GET_WORD_COUNT(header); // not used for the moment - static uint8_t wait_for_clean_frame = 1; // static because it will change in the future - // printf("packet header = 0x%08x, wc=%d \n", REV(header), word_count); - - // We return until the start of frame is reached - if (wait_for_clean_frame == 1){ - if (data_type != MIPI_DT_FRAME_START){ - return; - } - else{ - wait_for_clean_frame = 0; - } - } - - // Use case by data type - switch (data_type) - { - case MIPI_DT_FRAME_START: { // Start of frame. Just reset line number. - img_rx->frame_number++; - img_rx->line_number = 0; - break; - } - - case EXPECTED_FORMAT:{ // save it in SRAM and increment line - // if line number is grater than expected, just reset the line number - if (img_rx->line_number >= MIPI_IMAGE_HEIGHT_PIXELS){ - break; // let pass the rest until next frame - } - // then copy - not_silly_memcpy( - &FINAL_IMAGE[img_rx->line_number][0], - &pkt->payload[0], - MIPI_LINE_WIDTH_BYTES); // here is data width - img_rx->line_number++; - break; - } - - case MIPI_DT_FRAME_END:{ // we signal that the frame is finish so we can write it to a file - if (end_transmission == 0){ //TODO not needed if and the end of transmission I just return - flag <: 1; - end_transmission = 1; - } - break; - } - - default:{ // error with frame type or protected types - break; - } - } - } - - -#pragma unsafe arrays -static -void mipi_packet_handler( - streaming chanend c_pkt, - streaming chanend c_ctrl, - chanend flag - ) -{ - image_rx_t img_rx = {0,0}; // stores the coordinates X, Y of the image - unsigned pkt_idx = 0; // packet index - - // Give the MIPI packet receiver a buffer - outuint((chanend) c_pkt, (unsigned) &packet_buffer[pkt_idx]); - pkt_idx = (pkt_idx + 1) & (MIPI_PKT_BUFFER_COUNT-1); - - while(1) { - // Wait for the receiver thread to tell us a new packet was completed. - mipi_packet_t * unsafe pkt = (mipi_packet_t*unsafe) inuint((chanend) c_pkt); - - // Give it a new buffer before processing the received one - outuint((chanend) c_pkt, (unsigned) &packet_buffer[pkt_idx]); - pkt_idx = (pkt_idx + 1) & (MIPI_PKT_BUFFER_COUNT-1); - - // Process the packet. We need to be finished with this and looped - // back up to grab the next MIPI packet BEFORE the receiver thread - // tries to give us the next packet. - handle_packet(&img_rx, pkt, flag); - - if (end_transmission == 1){ - return; - } - } -} -} - - -void mipi_main(client interface i2c_master_if i2c) -{ - printf("< Start of APP capture application >\n"); - streaming chan c_pkt; - streaming chan c_ctrl; - chan flag; - - - // See AN for MIPI shim - // 0x7E42 >> 0111 1110 0100 0010 - // in the explorer BOARD DPDN is swap - write_node_config_reg(tile[MIPI_TILE], - XS1_SSWITCH_MIPI_DPHY_CFG3_NUM, - 0x7E42); //TODO decompose into different values - - // send packet to MIPI shim - MipiPacketRx_init(tile[MIPI_TILE], - p_mipi_rxd, - p_mipi_rxv, - p_mipi_rxa, - p_mipi_clk, - clk_mipi, - DEMUX_EN, - DEMUX_DATATYPE, - DEMUX_MODE, - MIPI_CLK_DIV, - MIPI_CFG_CLK_DIV); - - // Start camera and its configurations - int r = 0; - r |= camera_init(i2c); - delay_milliseconds(100); //TODO include this inside the function - r |= camera_configure(i2c); - delay_milliseconds(500); - - // Start streaming mode - r |= camera_start(i2c); - delay_milliseconds(2000); - - if (r != 0){ - printf(MSG_FAIL); - } - else{ - printf(MSG_SUCCESS); - } - - // ask the user to press the key "c" to capture the frame - // user_input(); //TODO not working with a par job - - - // start the different jobs (packet controller, handler, and post_process) - par - { - gMipiPacketRx(p_mipi_rxd, p_mipi_rxa, c_pkt, c_ctrl); - mipi_packet_handler(c_pkt, c_ctrl, flag); - save_image_to_file(flag); - } -} - diff --git a/examples/take_picture/src/process_frame.c b/examples/take_picture/src/process_frame.c deleted file mode 100644 index 9e933afa..00000000 --- a/examples/take_picture/src/process_frame.c +++ /dev/null @@ -1,43 +0,0 @@ - -#include "process_frame.h" -#include -#include -#include - -#define FINAL_IMAGE_FILENAME "img_raw.bin" -uint8_t FINAL_IMAGE[MIPI_IMAGE_HEIGHT_PIXELS][MIPI_LINE_WIDTH_BYTES]; - -// Write image to disk. This is called by camera main () to do the work -void write_image() -{ - static FILE* img_file = NULL; - img_file = fopen(FINAL_IMAGE_FILENAME, "wb"); - for(int k = 0; k < MIPI_IMAGE_HEIGHT_PIXELS; k++){ - for(int j = 0; j < MIPI_IMAGE_WIDTH_BYTES; j++){ - fwrite(&FINAL_IMAGE[k][j], sizeof(uint8_t), 1, img_file); - } - } - fclose(img_file); - printf("Outfile %s\n", FINAL_IMAGE_FILENAME); - printf("image size (%dx%d)\n", MIPI_LINE_WIDTH_BYTES, MIPI_IMAGE_HEIGHT_PIXELS); -} - - -// This is called when want to memcpy from Xc to C -void not_silly_memcpy( - void* dst, - void* src, - size_t size) -{ - memcpy(dst, src, size); -} - -/* -void user_input(void){ - printf("Enter the character 'c' to capture the frame : "); - int c = getchar(); - if(c != 99){ // == ascii "c" - exit(0); - } -} -*/ \ No newline at end of file diff --git a/examples/take_picture/src/process_frame.h b/examples/take_picture/src/process_frame.h deleted file mode 100644 index 635e6f2a..00000000 --- a/examples/take_picture/src/process_frame.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include "mipi_main.h" - -#ifdef __XC__ -extern "C" { -#endif - -// Store the image -extern uint8_t FINAL_IMAGE[MIPI_IMAGE_HEIGHT_PIXELS][MIPI_LINE_WIDTH_BYTES]; - -void write_image(); -void not_silly_memcpy(void *dst, void *src, size_t size); -// void user_input(void); - -#ifdef __XC__ -} -#endif \ No newline at end of file diff --git a/examples/take_picture/take_picture.cmake b/examples/take_picture/take_picture.cmake deleted file mode 100644 index be6c7a9b..00000000 --- a/examples/take_picture/take_picture.cmake +++ /dev/null @@ -1,77 +0,0 @@ -#********************** -# Gather Sources -#********************** - -# <--- Set the executable -set(TARGET example_take_picture) - -file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.*c) -set(APP_INCLUDES - ${CMAKE_CURRENT_LIST_DIR}/src -) - - -#********************** -# Flags -#********************** -set(APP_COMPILER_FLAGS - -Os - -g - -report - -fxscope - -mcmodel=large - -Wno-xcore-fptrgroup - ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope - ${CMAKE_CURRENT_LIST_DIR}/XCORE-AI-EXPLORER.xn -) -set(APP_COMPILE_DEFINITIONS - configENABLE_DEBUG_PRINTF=1 - PLATFORM_SUPPORTS_TILE_0=1 - PLATFORM_SUPPORTS_TILE_1=1 - PLATFORM_SUPPORTS_TILE_2=0 - PLATFORM_SUPPORTS_TILE_3=0 - PLATFORM_USES_TILE_0=1 - PLATFORM_USES_TILE_1=1 - XUD_CORE_CLOCK=600 -) - -set(APP_LINK_OPTIONS - -report - ${CMAKE_CURRENT_LIST_DIR}/XCORE-AI-EXPLORER.xn - ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope -) - -# <--- Link libraries -set(APP_COMMON_LINK_LIBRARIES - core::general - mipi::lib_mipi - i2c::lib_i2c - sensors::lib_imx - ) - - -#********************** -# Tile Targets -#********************** -add_executable(${TARGET} EXCLUDE_FROM_ALL) -target_sources(${TARGET} PUBLIC ${APP_SOURCES}) -target_include_directories(${TARGET} PUBLIC ${APP_INCLUDES}) -target_compile_definitions(${TARGET} PRIVATE ${APP_COMPILE_DEFINITIONS}) -target_compile_options(${TARGET} PRIVATE ${APP_COMPILER_FLAGS}) -target_link_libraries(${TARGET} PUBLIC ${APP_COMMON_LINK_LIBRARIES}) -target_link_options(${TARGET} PRIVATE ${APP_LINK_OPTIONS}) - - -#********************** -# Create run and debug targets -#********************** -create_run_target(${TARGET}) -create_debug_target(${TARGET}) -create_flash_app_target(${TARGET}) -create_install_target(${TARGET}) - -# then custom command line -add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_BINARY_DIR}/*.xe /mnt/c/Users/albertoisorna/exec/) - diff --git a/examples/take_picture_downsample/CMakeLists.txt b/examples/take_picture_downsample/CMakeLists.txt new file mode 100644 index 00000000..e8848f84 --- /dev/null +++ b/examples/take_picture_downsample/CMakeLists.txt @@ -0,0 +1,17 @@ +# Executable name +set(TARGET example_take_picture_downsample) + +#********************** +# Targets +#********************** +add_executable(${TARGET}) +target_sources(${TARGET} + PRIVATE + src/app.c + src/main.xc +) + +target_include_directories(${TARGET} PUBLIC src) +target_compile_options(${TARGET} PRIVATE ${EXAMPLE_APP_COMPILER_FLAGS}) +target_link_libraries(${TARGET} PUBLIC ${EXAMPLE_APP_LINK_LIBRARIES}) +target_link_options(${TARGET} PRIVATE ${EXAMPLE_APP_LINK_OPTIONS}) diff --git a/examples/take_picture_downsample/README.rst b/examples/take_picture_downsample/README.rst new file mode 100644 index 00000000..3e0ce4d9 --- /dev/null +++ b/examples/take_picture_downsample/README.rst @@ -0,0 +1,45 @@ +================================ +Example: Take picture downsample +================================ + +This example set the basic settings for the sony sensor and grab a single frame. +By default the format is the following: +- 160x120x3 RGB + +************* +Build example +************* +Run the following commands from the top level: + +Linux, Mac +~~~~~~~~~~ + +.. code-block:: console + + cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + make -C build example_take_picture_downsample + +Windows +~~~~~~~ + +.. code-block:: console + + cmake -G "Ninja" -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + ninja -C build example_take_picture_downsample + +*************** +Running example +*************** + +From the top level. +Make sure ``xscope_fileio`` is installed. See /utils/README.rst section for more details. + +.. code-block:: console + + python python/run_xscope_bin.py build/examples/take_picture_downsample/example_take_picture_downsample.xe + +****** +Output +****** + +The output files ``capture.bin`` and ``capture.bmp`` will be generated at the top level the repository. ``capture.bin`` can be further processed using ``python/decode_downsampled.py`` script. diff --git a/examples/take_picture_downsample/src/app.c b/examples/take_picture_downsample/src/app.c new file mode 100644 index 00000000..8e6cdf15 --- /dev/null +++ b/examples/take_picture_downsample/src/app.c @@ -0,0 +1,61 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +// std +#include +#include +#include +#include + +#include + +#include "io_utils.h" +#include "app.h" + +void user_app() +{ + // Initialize camera api + camera_init(); + + int8_t image_buffer[H][W][CH]; + uint8_t * image_ptr = (uint8_t *) &image_buffer[0][0][0]; + + // set the input image to 0 + memset(image_buffer, -128, sizeof(image_buffer)); + + // Wait for the image to set exposure + delay_milliseconds(4000); + + // grab a frame + printf("Requesting image...\n"); + xassert((camera_capture_image(image_buffer) == 0) && "Could not capture an image"); + printf("Image captured...\n"); + + // stop the threads and camera stream + camera_stop(); + delay_milliseconds(100); + + + // convert to uint8 + vect_int8_to_uint8(image_ptr, + &image_buffer[0][0][0], + sizeof(image_buffer)); + + // Write binary file + write_image_file("capture.bin", + image_ptr, + APP_IMAGE_HEIGHT_PIXELS, + APP_IMAGE_WIDTH_PIXELS, + APP_IMAGE_CHANNEL_COUNT); + + // Write bmp file + write_bmp_file("capture.bmp", + image_ptr, + APP_IMAGE_HEIGHT_PIXELS, + APP_IMAGE_WIDTH_PIXELS, + APP_IMAGE_CHANNEL_COUNT); + + printf("Images saved. Exiting.\n"); + xscope_close_all_files(); + exit(0); +} diff --git a/examples/take_picture_downsample/src/app.h b/examples/take_picture_downsample/src/app.h new file mode 100644 index 00000000..7dfead4a --- /dev/null +++ b/examples/take_picture_downsample/src/app.h @@ -0,0 +1,12 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include "xs1.h" +#include "platform.h" +#include "xccompat.h" + +#include "camera_main.h" + +void user_app(); diff --git a/examples/take_picture_downsample/src/main.xc b/examples/take_picture_downsample/src/main.xc new file mode 100644 index 00000000..39b7d849 --- /dev/null +++ b/examples/take_picture_downsample/src/main.xc @@ -0,0 +1,75 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include + +#include +#include +#include +#include + +#include "app.h" +#include "sensor_control.h" + +/** +* Declaration of the MIPI interface ports: +* Clock, receiver active, receiver data valid, and receiver data +*/ +on tile[MIPI_TILE] : in port p_mipi_clk = XS1_PORT_1O; +on tile[MIPI_TILE] : in port p_mipi_rxa = XS1_PORT_1E; // activate +on tile[MIPI_TILE] : in port p_mipi_rxv = XS1_PORT_1I; // valid +on tile[MIPI_TILE] : buffered in port:32 p_mipi_rxd = XS1_PORT_8A; // data +on tile[MIPI_TILE] : clock clk_mipi = MIPI_CLKBLK; + +typedef chanend chanend_t; + +extern "C" { +#include "xscope_io_device.h" +} + +// Camera control channels +void main_tile0(chanend_t c_control){ + sensor_control(c_control); +} + +// Camera image processing channels +void main_tile1(chanend_t c_control) +{ + streaming chan c_stat_thread; + streaming chan c_pkt; + streaming chan c_ctrl; + + camera_mipi_init( + p_mipi_clk, + p_mipi_rxa, + p_mipi_rxv, + p_mipi_rxd, + clk_mipi); + + par{ + MipiPacketRx(p_mipi_rxd, p_mipi_rxa, c_pkt, c_ctrl); + mipi_packet_handler(c_pkt, c_ctrl, c_stat_thread); + isp_pipeline(c_stat_thread, c_control); + user_app(); + } +} + + +int main(void) +{ + // Channel declarations + chan xscope_chan; + chan c_control; + + // Parallel jobs + par{ + on tile[0]: main_tile0(c_control); + on tile[1]: main_tile1(c_control); + // xscope + xscope_host_data(xscope_chan); + on tile[1]: xscope_io_init(xscope_chan); + } + return 0; +} diff --git a/examples/take_picture_dynamic/XCORE-AI-EXPLORER.xn b/examples/take_picture_dynamic/XCORE-AI-EXPLORER.xn deleted file mode 100644 index b7d3cd93..00000000 --- a/examples/take_picture_dynamic/XCORE-AI-EXPLORER.xn +++ /dev/null @@ -1,109 +0,0 @@ - - - Board - xcore.ai Explorer Kit - - - tileref tile[2] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/take_picture_dynamic/src/main.xc b/examples/take_picture_dynamic/src/main.xc deleted file mode 100644 index 47a72322..00000000 --- a/examples/take_picture_dynamic/src/main.xc +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2020, XMOS Ltd, All rights reserved -#include -#include -#include - -#include "i2c.h" -#include "mipi_main.h" - -// I2C interface ports -#define Kbps 400 -on tile[0]: port p_scl = XS1_PORT_1N; -on tile[0]: port p_sda = XS1_PORT_1O; - -int main(void) -{ - i2c_master_if i2c[1]; - par { - on tile[0]: i2c_master(i2c, 1, p_scl, p_sda, Kbps); - on tile[MIPI_TILE]: mipi_main(i2c[0]); - } - return 0; -} diff --git a/examples/take_picture_dynamic/src/mipi_main.h b/examples/take_picture_dynamic/src/mipi_main.h deleted file mode 100644 index 34cc0926..00000000 --- a/examples/take_picture_dynamic/src/mipi_main.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include "mipi.h" - -#include "sensor.h" - -///TODO this is inside lib_mipi -#ifndef MIPI_CLKBLK -#define MIPI_CLKBLK XS1_CLKBLK_1 -#endif - -/** - * The packet buffer is where the packet decoupler will tell the MIPI receiver - * thread to store received packets. - */ -#define DEMUX_DATATYPE 0 // RESERVED -#define DEMUX_MODE 0x00 // no demux -#define DEMUX_EN 0 // DISABLE DEMUX -#define MIPI_CLK_DIV 1 // CLK DIVIDER -#define MIPI_CFG_CLK_DIV 3 // CFG DIVIDER -#define REV(n) ((n << 24) | (((n>>16)<<24)>>16) | (((n<<16)>>24)<<16) | (n>>24)) - -// Packets definitions -typedef struct -{ - mipi_header_t header; - uint8_t payload[MIPI_LINE_WIDTH_BYTES]; -} mipi_data_t; - -typedef struct -{ - mipi_header_t header; - uint8_t payload[MIPI_MAX_PKT_SIZE_BYTES]; -} mipi_packet_t; - -typedef struct -{ - unsigned frame_number; - unsigned line_number; -} image_rx_t; - - -static mipi_packet_t packet_buffer[MIPI_PKT_BUFFER_COUNT]; - -// Functions definitions -#define gMipiPacketRx(...) MipiPacketRxnrev(__VA_ARGS__) // define your own function to use - -#ifdef __XC__ - -void mipi_main(client interface i2c_master_if i2c); - -#endif diff --git a/examples/take_picture_dynamic/src/mipi_main.xc b/examples/take_picture_dynamic/src/mipi_main.xc deleted file mode 100644 index a630ff7c..00000000 --- a/examples/take_picture_dynamic/src/mipi_main.xc +++ /dev/null @@ -1,249 +0,0 @@ -#include -#include -#include -#include // for ports -#include -#include // exit status and malloc -#include -#include -#include - -#include - -// I2C -#include "i2c.h" - -// MIPI -#include "mipi_main.h" -#include "MipiPacket.h" - -// Sensor -#define MSG_SUCCESS "Stream start OK\n" -#define MSG_FAIL "Stream start Failed\n" - -// Image -#include "process_frame.h" - -// Globals -char end_transmission = 0; - -typedef chanend chanend_t; - - -/** -* Declaration of the MIPI interface ports: -* Clock, receiver active, receiver data valid, and receiver data -*/ -on tile[MIPI_TILE] : in port p_mipi_clk = XS1_PORT_1O; -on tile[MIPI_TILE] : in port p_mipi_rxa = XS1_PORT_1E; // activate -on tile[MIPI_TILE] : in port p_mipi_rxv = XS1_PORT_1I; // valid -on tile[MIPI_TILE] : buffered in port:32 p_mipi_rxd = XS1_PORT_8A; // data -on tile[MIPI_TILE] : clock clk_mipi = MIPI_CLKBLK; - - -extern "C" -{ - void process_image(uint8_t *image, chanend_t c); -} - - -void control_exposure(chanend_t ch, client interface i2c_master_if i2c){ - while(1){ - select { - case ch :> unsigned exposure: - //printf("Exposure :> %d\n", exposure); - imx219_set_gain_dB(i2c, exposure); - break ; - } - } -} - - - - -unsafe { - -static -void handle_packet( - image_rx_t* img_rx, - const mipi_packet_t* unsafe pkt, - chanend flag, - uint8_t* unsafe img_raw_ptr, - chanend_t ch_exp) - { - // End here if just one transmission - //if (end_transmission == 1){ - // return; - //} - - // definitions - const mipi_header_t header = pkt->header; - const mipi_data_type_t data_type = MIPI_GET_DATA_TYPE(header); - const unsigned is_long = MIPI_IS_LONG_PACKET(header); // not used for the moment - const unsigned word_count = MIPI_GET_WORD_COUNT(header); // not used for the moment - static uint8_t wait_for_clean_frame = 1; // static because it will change in the future - // printf("packet header = 0x%08x, wc=%d \n", REV(header), word_count); - - // We return until the start of frame is reached - if (wait_for_clean_frame == 1){ - if (data_type != MIPI_DT_FRAME_START){ - return; - } - else{ - wait_for_clean_frame = 0; - } - } - //printf("-- Im here 4--\n"); - // printf("dat type: 0x%08x\n",data_type); - // Use case by data type - switch (data_type) - { - case MIPI_DT_FRAME_START: { // Start of frame. Just reset line number. - img_rx->frame_number++; - img_rx->line_number = 0; - break; - } - - case EXPECTED_FORMAT:{ // save it in SRAM and increment line - // if line number is grater than expected, just reset the line number - if (img_rx->line_number >= MIPI_IMAGE_HEIGHT_PIXELS){ - break; // let pass the rest until next frame - } - // then copy - uint32_t pos = (img_rx->line_number) * MIPI_LINE_WIDTH_BYTES; - // *** horizontal / vertical filtering **** - - - // ******************************* - c_memcpy(&img_raw_ptr[pos], &pkt->payload[0], MIPI_LINE_WIDTH_BYTES); - // increase line number - img_rx->line_number++; - break; - } - - case MIPI_DT_FRAME_END:{ // we signal that the frame is finish so we can write it to a file - //if (end_transmission == 0){ //TODO not needed if and the end of transmission I just return - - //flag <: 1; - // wait_for_clean_frame = 1; // in case - // end_transmission = 1; - //} - process_image(img_raw_ptr, ch_exp); - //printf("-- Im here 3--\n"); - break; - } - - default:{ // error with frame type or protected types or blank fields - break; - } - } - } - -#pragma unsafe arrays -static void mipi_packet_handler( - streaming chanend c_pkt, - streaming chanend c_ctrl, - chanend flag, - uint8_t * unsafe image_ptr, - chanend_t ch_exp) -{ - image_rx_t img_rx = {0, 0}; // stores the coordinates X, Y of the image - unsigned pkt_idx = 0; // packet index - - // Give the MIPI packet receiver a buffer - outuint((chanend)c_pkt, (unsigned)&packet_buffer[pkt_idx]); - pkt_idx = (pkt_idx + 1) & (MIPI_PKT_BUFFER_COUNT - 1); - - while (1) - { - // Wait for the receiver thread to tell us a new packet was completed. - mipi_packet_t *unsafe pkt = (mipi_packet_t * unsafe) inuint((chanend)c_pkt); - - // Give it a new buffer before processing the received one - outuint((chanend)c_pkt, (unsigned)&packet_buffer[pkt_idx]); - pkt_idx = (pkt_idx + 1) & (MIPI_PKT_BUFFER_COUNT - 1); - - // Process the packet. We need to be finished with this and looped - // back up to grab the next MIPI packet BEFORE the receiver thread - // tries to give us the next packet. - handle_packet(&img_rx, pkt, flag, image_ptr, ch_exp); - - //if (end_transmission == 1) - //{ - // return; - //} - } -} - -} // unsafe region - - -void mipi_main(client interface i2c_master_if i2c) -{ - printf("< Start of APP capture application >\n"); - streaming chan c_pkt; - streaming chan c_ctrl; - chan flag; - chan ch_exp; - - // allocate espace for the image buffer - uint8_t* unsafe img_raw_ptr = malloc(MIPI_IMAGE_HEIGHT_PIXELS * MIPI_IMAGE_WIDTH_PIXELS * sizeof(uint8_t)); - - // See AN for MIPI shim - // 0x7E42 >> 0111 1110 0100 0010 - // in the explorer BOARD DPDN is swap - write_node_config_reg(tile[MIPI_TILE], - XS1_SSWITCH_MIPI_DPHY_CFG3_NUM, - 0x7E42); //TODO decompose into different values - - // send packet to MIPI shim - MipiPacketRx_init(tile[MIPI_TILE], - p_mipi_rxd, - p_mipi_rxv, - p_mipi_rxa, - p_mipi_clk, - clk_mipi, - DEMUX_EN, - DEMUX_DATATYPE, - DEMUX_MODE, - MIPI_CLK_DIV, - MIPI_CFG_CLK_DIV); - - // Start camera and its configurations - int r = 0; - r |= camera_init(i2c); - delay_milliseconds(100); //TODO include this inside the function - r |= camera_configure(i2c); - delay_milliseconds(500); - r |= camera_start(i2c); - delay_milliseconds(2000); - - if (r != 0){ - printf(MSG_FAIL); - } - else{ - printf(MSG_SUCCESS); - } - - // start the different jobs (packet controller, handler, and post_process) - par - { - gMipiPacketRx(p_mipi_rxd, p_mipi_rxa, c_pkt, c_ctrl); - mipi_packet_handler(c_pkt, c_ctrl, flag, img_raw_ptr, ch_exp); - //save_image_to_file(flag, img_raw_ptr); - control_exposure(ch_exp, i2c); - } -} - - -/* - //uint16_t val = imx219_read(i2c, 0x0174); - //printf("read value = %d\n", val); - - uint16_t gain_values[5]; - imx219_read_gains(i2c, gain_values); - for (int i = 0; i < 5; i++) - { - printf("gain value = %d\n", gain_values[i]); - } -*/ \ No newline at end of file diff --git a/examples/take_picture_dynamic/src/process_frame.c b/examples/take_picture_dynamic/src/process_frame.c deleted file mode 100644 index b6d82022..00000000 --- a/examples/take_picture_dynamic/src/process_frame.c +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "process_frame.h" -#include "stadistics.h" // for skewness and -#include "utils.h" // for measuring time -#include "isp.h" // setting auto_exposure, AWB - -#define FINAL_IMAGE_FILENAME "img_raw.bin" - - -const uint32_t img_len = MIPI_LINE_WIDTH_BYTES*MIPI_IMAGE_HEIGHT_PIXELS; -float new_exp = 35; -Stadistics st = {{0}}; - -// Write image to disk. This is called by camera main () to do the work -void write_image(uint8_t *image) -{ - static FILE* img_file = NULL; - img_file = fopen(FINAL_IMAGE_FILENAME, "wb"); - for(uint16_t k = 0; k < MIPI_IMAGE_HEIGHT_PIXELS; k++){ - for(uint16_t j = 0; j < MIPI_IMAGE_WIDTH_BYTES; j++){ - uint32_t pos = k * MIPI_LINE_WIDTH_BYTES + j; - fwrite(&image[pos], sizeof(uint8_t), 1, img_file); - } - } - fclose(img_file); - printf("Outfile %s\n", FINAL_IMAGE_FILENAME); - printf("image size (%dx%d)\n", MIPI_LINE_WIDTH_BYTES, MIPI_IMAGE_HEIGHT_PIXELS); - free(image); - exit(1); -} - - - -void process_image(uint8_t *image, chanend_t c){ - static int initial_t = 0; - if (initial_t == 0){ - initial_t = measure_time(); - } - static int print_msg = 0; - - // compute stadistics - Stadistics_compute_all(img_len, STEP, image, (Stadistics *) &st); - float sk = st.skewness; - - // print information - printf("min:%d, max:%d, mean:%d, percentile:%d, exposure:%f, skewness:%f\n", - st.min, st.max, st.mean, st.percentile, new_exp, sk); - - // exit condition - if (sk < AE_MARGIN && sk > -AE_MARGIN){ - if (print_msg == 0){ - printf("-----> adjustement done\n"); - print_msg = 1; - } - } - else{ - // adjust exposure - new_exp = isp_false_position_step(new_exp, sk); - - // put exposure - #if ENABLE_AE - chan_out_word(c, (unsigned) new_exp); - #endif - // cancel output message - print_msg = 0; - } - - // write it to a file - if (measure_time() - initial_t >= 600000000){ - write_image(image); - } -} - -// This is called when want to memcpy from Xc to C -void c_memcpy( - void* dst, - void* src, - size_t size) -{ - memcpy(dst, src, size); -} diff --git a/examples/take_picture_dynamic/src/process_frame.h b/examples/take_picture_dynamic/src/process_frame.h deleted file mode 100644 index e17714e9..00000000 --- a/examples/take_picture_dynamic/src/process_frame.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include "mipi_main.h" - - -#ifdef __XC__ -extern "C" { -#endif - -void write_image(uint8_t *image); -void c_memcpy(void *dst, void *src, size_t size); -//void process_image(uint8_t *image, chanend_t c); - - -// void user_input(void); - -#ifdef __XC__ -} -#endif \ No newline at end of file diff --git a/examples/take_picture_dynamic/take_picture_dynamic.cmake b/examples/take_picture_dynamic/take_picture_dynamic.cmake deleted file mode 100644 index 80a9e3c5..00000000 --- a/examples/take_picture_dynamic/take_picture_dynamic.cmake +++ /dev/null @@ -1,81 +0,0 @@ -#********************** -# Gather Sources -#********************** - -# <--- Set the executable -set(TARGET example_take_picture_dynamic) -set(DEST_FOLDER /mnt/c/Users/albertoisorna/exec/) # to post build command - -file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.*c) -set(APP_INCLUDES - ${CMAKE_CURRENT_LIST_DIR}/src -) - - -#********************** -# Flags -#********************** -set(APP_COMPILER_FLAGS - -Os - -g - -report - -fxscope - -mcmodel=large - -Wno-xcore-fptrgroup - ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope - ${CMAKE_CURRENT_LIST_DIR}/XCORE-AI-EXPLORER.xn -) -set(APP_COMPILE_DEFINITIONS - configENABLE_DEBUG_PRINTF=1 - PLATFORM_SUPPORTS_TILE_0=1 - PLATFORM_SUPPORTS_TILE_1=1 - PLATFORM_SUPPORTS_TILE_2=0 - PLATFORM_SUPPORTS_TILE_3=0 - PLATFORM_USES_TILE_0=1 - PLATFORM_USES_TILE_1=1 - XUD_CORE_CLOCK=600 -) - -set(APP_LINK_OPTIONS - -report - ${CMAKE_CURRENT_LIST_DIR}/XCORE-AI-EXPLORER.xn - ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope -) - -# <--- Link libraries -set(APP_COMMON_LINK_LIBRARIES - core::general - mipi::lib_mipi - i2c::lib_i2c - sensors::lib_imx - camera::lib_camera - ) - - -#********************** -# Tile Targets -#********************** -add_executable(${TARGET} EXCLUDE_FROM_ALL) -target_sources(${TARGET} PUBLIC ${APP_SOURCES}) -target_include_directories(${TARGET} PUBLIC ${APP_INCLUDES}) -target_compile_definitions(${TARGET} PRIVATE ${APP_COMPILE_DEFINITIONS}) -target_compile_options(${TARGET} PRIVATE ${APP_COMPILER_FLAGS}) -target_link_libraries(${TARGET} PUBLIC ${APP_COMMON_LINK_LIBRARIES}) -target_link_options(${TARGET} PRIVATE ${APP_LINK_OPTIONS}) - - -#********************** -# Create run and debug targets -#********************** -create_run_target(${TARGET}) -create_debug_target(${TARGET}) -create_flash_app_target(${TARGET}) -create_install_target(${TARGET}) - -# then custom command line to copy .xe file to custom folder -if(EXISTS ${DEST_FOLDER}) - add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_BINARY_DIR}/*.xe ${DEST_FOLDER}) -endif() - diff --git a/examples/take_picture_dynamic_crop/XCORE-AI-EXPLORER.xn b/examples/take_picture_dynamic_crop/XCORE-AI-EXPLORER.xn deleted file mode 100644 index b7d3cd93..00000000 --- a/examples/take_picture_dynamic_crop/XCORE-AI-EXPLORER.xn +++ /dev/null @@ -1,109 +0,0 @@ - - - Board - xcore.ai Explorer Kit - - - tileref tile[2] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/take_picture_dynamic_crop/src/config.xscope b/examples/take_picture_dynamic_crop/src/config.xscope deleted file mode 100644 index 69236026..00000000 --- a/examples/take_picture_dynamic_crop/src/config.xscope +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/take_picture_dynamic_crop/src/main.xc b/examples/take_picture_dynamic_crop/src/main.xc deleted file mode 100644 index 47a72322..00000000 --- a/examples/take_picture_dynamic_crop/src/main.xc +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2020, XMOS Ltd, All rights reserved -#include -#include -#include - -#include "i2c.h" -#include "mipi_main.h" - -// I2C interface ports -#define Kbps 400 -on tile[0]: port p_scl = XS1_PORT_1N; -on tile[0]: port p_sda = XS1_PORT_1O; - -int main(void) -{ - i2c_master_if i2c[1]; - par { - on tile[0]: i2c_master(i2c, 1, p_scl, p_sda, Kbps); - on tile[MIPI_TILE]: mipi_main(i2c[0]); - } - return 0; -} diff --git a/examples/take_picture_dynamic_crop/src/mipi_main.h b/examples/take_picture_dynamic_crop/src/mipi_main.h deleted file mode 100644 index 34cc0926..00000000 --- a/examples/take_picture_dynamic_crop/src/mipi_main.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include "mipi.h" - -#include "sensor.h" - -///TODO this is inside lib_mipi -#ifndef MIPI_CLKBLK -#define MIPI_CLKBLK XS1_CLKBLK_1 -#endif - -/** - * The packet buffer is where the packet decoupler will tell the MIPI receiver - * thread to store received packets. - */ -#define DEMUX_DATATYPE 0 // RESERVED -#define DEMUX_MODE 0x00 // no demux -#define DEMUX_EN 0 // DISABLE DEMUX -#define MIPI_CLK_DIV 1 // CLK DIVIDER -#define MIPI_CFG_CLK_DIV 3 // CFG DIVIDER -#define REV(n) ((n << 24) | (((n>>16)<<24)>>16) | (((n<<16)>>24)<<16) | (n>>24)) - -// Packets definitions -typedef struct -{ - mipi_header_t header; - uint8_t payload[MIPI_LINE_WIDTH_BYTES]; -} mipi_data_t; - -typedef struct -{ - mipi_header_t header; - uint8_t payload[MIPI_MAX_PKT_SIZE_BYTES]; -} mipi_packet_t; - -typedef struct -{ - unsigned frame_number; - unsigned line_number; -} image_rx_t; - - -static mipi_packet_t packet_buffer[MIPI_PKT_BUFFER_COUNT]; - -// Functions definitions -#define gMipiPacketRx(...) MipiPacketRxnrev(__VA_ARGS__) // define your own function to use - -#ifdef __XC__ - -void mipi_main(client interface i2c_master_if i2c); - -#endif diff --git a/examples/take_picture_dynamic_crop/src/mipi_main.xc b/examples/take_picture_dynamic_crop/src/mipi_main.xc deleted file mode 100644 index 0d239f9c..00000000 --- a/examples/take_picture_dynamic_crop/src/mipi_main.xc +++ /dev/null @@ -1,254 +0,0 @@ -#include -#include -#include -#include // for ports -#include -#include // exit status and malloc -#include -#include -#include - -#include - -// I2C -#include "i2c.h" - -// MIPI -#include "mipi_main.h" -#include "MipiPacket.h" - -// Sensor -#define MSG_SUCCESS "Stream start OK\n" -#define MSG_FAIL "Stream start Failed\n" - -// Image -#include "process_frame.h" - -// Globals -char end_transmission = 0; - -typedef chanend chanend_t; - - -/** -* Declaration of the MIPI interface ports: -* Clock, receiver active, receiver data valid, and receiver data -*/ -on tile[MIPI_TILE] : in port p_mipi_clk = XS1_PORT_1O; -on tile[MIPI_TILE] : in port p_mipi_rxa = XS1_PORT_1E; // activate -on tile[MIPI_TILE] : in port p_mipi_rxv = XS1_PORT_1I; // valid -on tile[MIPI_TILE] : buffered in port:32 p_mipi_rxd = XS1_PORT_8A; // data -on tile[MIPI_TILE] : clock clk_mipi = MIPI_CLKBLK; - - -extern "C" -{ - void process_image(uint8_t *image, chanend_t c); -} - - -void control_exposure(chanend_t ch, client interface i2c_master_if i2c){ - while(1){ - select { - case ch :> unsigned exposure: - //printf("Exposure :> %d\n", exposure); - imx219_set_gain_dB(i2c, exposure); - break ; - } - } -} - - - - -unsafe { - -static -void handle_packet( - image_rx_t* img_rx, - const mipi_packet_t* unsafe pkt, - uint8_t* unsafe img_raw_ptr, - chanend_t ch_exp) - { - // End here if just one transmission - //if (end_transmission == 1){ - // return; - //} - - // definitions - const mipi_header_t header = pkt->header; - const mipi_data_type_t data_type = MIPI_GET_DATA_TYPE(header); - const unsigned is_long = MIPI_IS_LONG_PACKET(header); // not used for the moment - const unsigned word_count = MIPI_GET_WORD_COUNT(header); // not used for the moment - static uint8_t wait_for_clean_frame = 1; // static because it will change in the future - // printf("packet header = 0x%08x, wc=%d \n", REV(header), word_count); - - // We return until the start of frame is reached - if (wait_for_clean_frame == 1){ - if (data_type != MIPI_DT_FRAME_START){ - return; - } - else{ - wait_for_clean_frame = 0; - } - } - //printf("-- Im here 4--\n"); - // printf("dat type: 0x%08x\n",data_type); - // Use case by data type - switch (data_type) - { - case MIPI_DT_FRAME_START: { // Start of frame. Just reset line number. - img_rx->frame_number++; - img_rx->line_number = 0; - break; - } - - case EXPECTED_FORMAT:{ // save it in SRAM and increment line - // if line number is grater than expected, just reset the line number - if (img_rx->line_number >= MIPI_IMAGE_HEIGHT_PIXELS){ - break; // let pass the rest until next frame - } - - #if (CROP_ENABLED) - // check if y axis in range - if (img_rx->line_number >= Y_START_CROP){ - if (img_rx->line_number < (Y_START_CROP + CROP_HEIGHT_PIXELS)){ - // compute x axis - uint32_t pos = ((img_rx->line_number) - Y_START_CROP)* CROP_WIDTH_PIXELS; - // printf("pos = %d\n", pos); - c_memcpy(&img_raw_ptr[pos], &pkt->payload[X_START_CROP], CROP_WIDTH_PIXELS); - } - } - #else - uint32_t pos = (img_rx->line_number) * MIPI_LINE_WIDTH_BYTES; - c_memcpy(&img_raw_ptr[pos], &pkt->payload[0], MIPI_LINE_WIDTH_BYTES); - #endif - - // increase line number - img_rx->line_number++; - break; - } - - case MIPI_DT_FRAME_END:{ // we signal that the frame is finish so we can write it to a file - //process_image(img_raw_ptr, ch_exp); - write_image(img_raw_ptr); - break; - } - - default:{ // error with frame type or protected types or blank fields - break; - } - } - } - -#pragma unsafe arrays -static void mipi_packet_handler( - streaming chanend c_pkt, - streaming chanend c_ctrl, - uint8_t * unsafe image_ptr, - chanend_t ch_exp) -{ - image_rx_t img_rx = {0, 0}; // stores the coordinates X, Y of the image - unsigned pkt_idx = 0; // packet index - - // Give the MIPI packet receiver a buffer - outuint((chanend)c_pkt, (unsigned)&packet_buffer[pkt_idx]); - pkt_idx = (pkt_idx + 1) & (MIPI_PKT_BUFFER_COUNT - 1); - - while (1) - { - // Wait for the receiver thread to tell us a new packet was completed. - mipi_packet_t *unsafe pkt = (mipi_packet_t * unsafe) inuint((chanend)c_pkt); - - // Give it a new buffer before processing the received one - outuint((chanend)c_pkt, (unsigned)&packet_buffer[pkt_idx]); - pkt_idx = (pkt_idx + 1) & (MIPI_PKT_BUFFER_COUNT - 1); - - // Process the packet. We need to be finished with this and looped - // back up to grab the next MIPI packet BEFORE the receiver thread - // tries to give us the next packet. - handle_packet(&img_rx, pkt, image_ptr, ch_exp); - - //if (end_transmission == 1) - //{ - // return; - //} - } -} - -} // unsafe region - - -void mipi_main(client interface i2c_master_if i2c) -{ - printf("< Start of APP capture application >\n"); - streaming chan c_pkt; - streaming chan c_ctrl; - //chan flag; - chan ch_exp; - - // allocate espace for the image buffer - #if (CROP_ENABLED) - uint8_t* unsafe img_raw_ptr = malloc(CROP_WIDTH_PIXELS * CROP_HEIGHT_PIXELS * sizeof(uint8_t)); - #else - uint8_t* unsafe img_raw_ptr = malloc(MIPI_IMAGE_HEIGHT_PIXELS * MIPI_IMAGE_WIDTH_PIXELS * sizeof(uint8_t)); - #endif - - // See AN for MIPI shim - // 0x7E42 >> 0111 1110 0100 0010 - // in the explorer BOARD DPDN is swap - write_node_config_reg(tile[MIPI_TILE], - XS1_SSWITCH_MIPI_DPHY_CFG3_NUM, - 0x7E42); //TODO decompose into different values - - // send packet to MIPI shim - MipiPacketRx_init(tile[MIPI_TILE], - p_mipi_rxd, - p_mipi_rxv, - p_mipi_rxa, - p_mipi_clk, - clk_mipi, - DEMUX_EN, - DEMUX_DATATYPE, - DEMUX_MODE, - MIPI_CLK_DIV, - MIPI_CFG_CLK_DIV); - - // Start camera and its configurations - int r = 0; - r |= camera_init(i2c); - delay_milliseconds(100); //TODO include this inside the function - r |= camera_configure(i2c); - delay_milliseconds(500); - r |= camera_start(i2c); - delay_milliseconds(2000); - - if (r != 0){ - printf(MSG_FAIL); - } - else{ - printf(MSG_SUCCESS); - } - - // start the different jobs (packet controller, handler, and post_process) - par - { - gMipiPacketRx(p_mipi_rxd, p_mipi_rxa, c_pkt, c_ctrl); - mipi_packet_handler(c_pkt, c_ctrl, img_raw_ptr, ch_exp); - //save_image_to_file(flag, img_raw_ptr); - control_exposure(ch_exp, i2c); - } -} - - -/* - //uint16_t val = imx219_read(i2c, 0x0174); - //printf("read value = %d\n", val); - - uint16_t gain_values[5]; - imx219_read_gains(i2c, gain_values); - for (int i = 0; i < 5; i++) - { - printf("gain value = %d\n", gain_values[i]); - } -*/ \ No newline at end of file diff --git a/examples/take_picture_dynamic_crop/src/process_frame.c b/examples/take_picture_dynamic_crop/src/process_frame.c deleted file mode 100644 index 18b2b41e..00000000 --- a/examples/take_picture_dynamic_crop/src/process_frame.c +++ /dev/null @@ -1,95 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "process_frame.h" -#include "stadistics.h" // for skewness and -#include "utils.h" // for measuring time -#include "isp.h" // setting auto_exposure, AWB - -#define FINAL_IMAGE_FILENAME "img_raw.bin" - -#if (CROP_ENABLED) - #define IMG_W CROP_WIDTH_PIXELS - #define IMG_H CROP_HEIGHT_PIXELS -#else - #define IMG_W MIPI_LINE_WIDTH_BYTES - #define IMG_H MIPI_IMAGE_HEIGHT_PIXELS -#endif - -// Global definitions -const uint32_t img_len = IMG_W*IMG_H; -float new_exp = 35; -Stadistics st = {{0}}; - -// Write image to disk. This is called by camera main () to do the work -void write_image(uint8_t *image) -{ - static FILE* img_file = NULL; - img_file = fopen(FINAL_IMAGE_FILENAME, "wb"); - for(uint16_t k = 0; k < IMG_H; k++){ - for(uint16_t j = 0; j < IMG_W; j++){ - uint32_t pos = k * IMG_W + j; - fwrite(&image[pos], sizeof(uint8_t), 1, img_file); - } - } - fclose(img_file); - printf("Outfile %s\n", FINAL_IMAGE_FILENAME); - printf("image size (%dx%d)\n", IMG_W, IMG_H); - free(image); - exit(1); -} - - - -void process_image(uint8_t *image, chanend_t c){ - static int initial_t = 0; - if (initial_t == 0){ - initial_t = measure_time(); - } - static int print_msg = 0; - - // compute stadistics - Stadistics_compute_all(img_len, STEP, image, (Stadistics *) &st); - float sk = st.skewness; - - // print information - printf("min:%d, max:%d, mean:%d, percentile:%d, exposure:%f, skewness:%f\n", - st.min, st.max, st.mean, st.percentile, new_exp, sk); - - // exit condition - if (sk < AE_MARGIN && sk > -AE_MARGIN){ - if (print_msg == 0){ - printf("-----> adjustement done\n"); - print_msg = 1; - } - } - else{ - // adjust exposure - new_exp = isp_false_position_step(new_exp, sk); - - // put exposure - #if ENABLE_AE - chan_out_word(c, (unsigned) new_exp); - #endif - // cancel output message - print_msg = 0; - } - - // write it to a file - if (measure_time() - initial_t >= 600000000){ - write_image(image); - } -} - -// This is called when want to memcpy from Xc to C -void c_memcpy( - void* dst, - void* src, - size_t size) -{ - memcpy(dst, src, size); -} diff --git a/examples/take_picture_dynamic_crop/src/process_frame.h b/examples/take_picture_dynamic_crop/src/process_frame.h deleted file mode 100644 index e17714e9..00000000 --- a/examples/take_picture_dynamic_crop/src/process_frame.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include "mipi_main.h" - - -#ifdef __XC__ -extern "C" { -#endif - -void write_image(uint8_t *image); -void c_memcpy(void *dst, void *src, size_t size); -//void process_image(uint8_t *image, chanend_t c); - - -// void user_input(void); - -#ifdef __XC__ -} -#endif \ No newline at end of file diff --git a/examples/take_picture_dynamic_crop/take_picture_dynamic_crop.cmake b/examples/take_picture_dynamic_crop/take_picture_dynamic_crop.cmake deleted file mode 100644 index 23f308ce..00000000 --- a/examples/take_picture_dynamic_crop/take_picture_dynamic_crop.cmake +++ /dev/null @@ -1,81 +0,0 @@ -#********************** -# Gather Sources -#********************** - -# <--- Set the executable -set(TARGET example_take_picture_crop) -set(DEST_FOLDER /mnt/c/Users/albertoisorna/exec/) # to post build command - -file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.*c) -set(APP_INCLUDES - ${CMAKE_CURRENT_LIST_DIR}/src -) - - -#********************** -# Flags -#********************** -set(APP_COMPILER_FLAGS - -Os - -g - -report - -fxscope - -mcmodel=large - -Wno-xcore-fptrgroup - ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope - ${CMAKE_CURRENT_LIST_DIR}/XCORE-AI-EXPLORER.xn -) -set(APP_COMPILE_DEFINITIONS - configENABLE_DEBUG_PRINTF=1 - PLATFORM_SUPPORTS_TILE_0=1 - PLATFORM_SUPPORTS_TILE_1=1 - PLATFORM_SUPPORTS_TILE_2=0 - PLATFORM_SUPPORTS_TILE_3=0 - PLATFORM_USES_TILE_0=1 - PLATFORM_USES_TILE_1=1 - XUD_CORE_CLOCK=600 -) - -set(APP_LINK_OPTIONS - -report - ${CMAKE_CURRENT_LIST_DIR}/XCORE-AI-EXPLORER.xn - ${CMAKE_CURRENT_LIST_DIR}/src/config.xscope -) - -# <--- Link libraries -set(APP_COMMON_LINK_LIBRARIES - core::general - mipi::lib_mipi - i2c::lib_i2c - sensors::lib_imx - camera::lib_camera - ) - - -#********************** -# Tile Targets -#********************** -add_executable(${TARGET} EXCLUDE_FROM_ALL) -target_sources(${TARGET} PUBLIC ${APP_SOURCES}) -target_include_directories(${TARGET} PUBLIC ${APP_INCLUDES}) -target_compile_definitions(${TARGET} PRIVATE ${APP_COMPILE_DEFINITIONS}) -target_compile_options(${TARGET} PRIVATE ${APP_COMPILER_FLAGS}) -target_link_libraries(${TARGET} PUBLIC ${APP_COMMON_LINK_LIBRARIES}) -target_link_options(${TARGET} PRIVATE ${APP_LINK_OPTIONS}) - - -#********************** -# Create run and debug targets -#********************** -create_run_target(${TARGET}) -create_debug_target(${TARGET}) -create_flash_app_target(${TARGET}) -create_install_target(${TARGET}) - -# then custom command line to copy .xe file to custom folder -if(EXISTS ${DEST_FOLDER}) - add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_BINARY_DIR}/*.xe ${DEST_FOLDER}) -endif() - diff --git a/examples/take_picture_local/CMakeLists.txt b/examples/take_picture_local/CMakeLists.txt new file mode 100644 index 00000000..eed065d0 --- /dev/null +++ b/examples/take_picture_local/CMakeLists.txt @@ -0,0 +1,17 @@ +# Executable name +set(TARGET example_take_picture_local) + +#********************** +# Targets +#********************** +add_executable(${TARGET}) +target_sources(${TARGET} + PRIVATE + src/app.c + src/main.xc +) + +target_include_directories(${TARGET} PUBLIC src) +target_compile_options(${TARGET} PRIVATE ${EXAMPLE_APP_COMPILER_FLAGS}) +target_link_libraries(${TARGET} PUBLIC ${EXAMPLE_APP_LINK_LIBRARIES}) +target_link_options(${TARGET} PRIVATE ${EXAMPLE_APP_LINK_OPTIONS}) diff --git a/examples/take_picture_local/README.rst b/examples/take_picture_local/README.rst new file mode 100644 index 00000000..77e91282 --- /dev/null +++ b/examples/take_picture_local/README.rst @@ -0,0 +1,54 @@ +========================= +Example: Take picture Local +========================= + +This example set the basic settings for inject a RAW image +to the explorer board an run the ISP pipeline. + +If you do not have a RAW8 image, you can produce it with +the utility encode_raw8 from the python folder. + +The image default name is ``temp.raw `` + +By default the format is the following: +- 640x480 RAW8 + +************* +Build example +************* +Run the following commands from the top level: + +Linux, Mac +~~~~~~~~~~ + +.. code-block:: console + + cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + make -C build example_take_picture_local + +Windows +~~~~~~~ + +.. code-block:: console + + cmake -G "Ninja" -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + ninja -C build example_take_picture_local + +*************** +Running example +*************** + +From the top level +Make sure ``xscope_fileio`` is installed. See /utils/README.rst section for more details. + +.. code-block:: console + + python python/run_xscope_bin.py build/examples/take_picture_local/example_take_picture_local.xe + + +****** +Output +****** + +The output files ``capture.bin`` and ``capture.bmp`` will be generated at the top level the repository. +``capture.bin`` can be further processed using ``python/decode_downsampled.py`` script. diff --git a/examples/take_picture_local/src/app.c b/examples/take_picture_local/src/app.c new file mode 100644 index 00000000..78a9ae49 --- /dev/null +++ b/examples/take_picture_local/src/app.c @@ -0,0 +1,61 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +// std +#include +#include +#include +#include + +#include + +#include "io_utils.h" +#include "app.h" + +void user_app() +{ + // Initialize camera api + camera_init(); + + int8_t image_buffer[H][W][CH]; + uint8_t * image_ptr = (uint8_t *) &image_buffer[0][0][0]; + + // set the input image to 0 + memset(image_buffer, -128, sizeof(image_buffer)); + + // Wait for the image to set exposure + delay_milliseconds(1000); + + // grab a frame + printf("Requesting image...\n"); + xassert((camera_capture_image(image_buffer) == 0) && "Could not capture an image"); + printf("Image captured...\n"); + + // stop the threads and camera stream + camera_stop(); + delay_milliseconds(100); + + // convert to uint8 + vect_int8_to_uint8(image_ptr, + &image_buffer[0][0][0], + sizeof(image_buffer)); + + // Write binary file + write_image_file("capture.bin", + image_ptr, + APP_IMAGE_HEIGHT_PIXELS, + APP_IMAGE_WIDTH_PIXELS, + APP_IMAGE_CHANNEL_COUNT); + + // Write bmp file + write_bmp_file("capture.bmp", + image_ptr, + APP_IMAGE_HEIGHT_PIXELS, + APP_IMAGE_WIDTH_PIXELS, + APP_IMAGE_CHANNEL_COUNT); + + printf("Images saved. Exiting.\n"); + xscope_close_all_files(); + // end here + exit(0); +} diff --git a/examples/take_picture_local/src/app.h b/examples/take_picture_local/src/app.h new file mode 100644 index 00000000..7dfead4a --- /dev/null +++ b/examples/take_picture_local/src/app.h @@ -0,0 +1,12 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include "xs1.h" +#include "platform.h" +#include "xccompat.h" + +#include "camera_main.h" + +void user_app(); diff --git a/examples/take_picture_local/src/main.xc b/examples/take_picture_local/src/main.xc new file mode 100644 index 00000000..64359741 --- /dev/null +++ b/examples/take_picture_local/src/main.xc @@ -0,0 +1,76 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include + +#include +#include +#include +#include + +#include "app.h" +#include "sensor_control.h" +#include "packet_rx_simulate.h" + +/** +* Declaration of the MIPI interface ports: +* Clock, receiver active, receiver data valid, and receiver data +*/ +on tile[MIPI_TILE] : in port p_mipi_clk = XS1_PORT_1O; +on tile[MIPI_TILE] : in port p_mipi_rxa = XS1_PORT_1E; // activate +on tile[MIPI_TILE] : in port p_mipi_rxv = XS1_PORT_1I; // valid +on tile[MIPI_TILE] : buffered in port:32 p_mipi_rxd = XS1_PORT_8A; // data +on tile[MIPI_TILE] : clock clk_mipi = MIPI_CLKBLK; + +typedef chanend chanend_t; + +extern "C" { +#include "xscope_io_device.h" +} + +// Camera control channels +void main_tile0(chanend_t c_control){ + sensor_control(c_control); +} + +// Camera image processing channels +void main_tile1(chanend_t c_control) +{ + streaming chan c_stat_thread; + streaming chan c_pkt; + streaming chan c_ctrl; + + camera_mipi_init( + p_mipi_clk, + p_mipi_rxa, + p_mipi_rxv, + p_mipi_rxd, + clk_mipi); + + par{ + MipiPacketRx_simulate(p_mipi_rxd, p_mipi_rxa, c_pkt, c_ctrl); + mipi_packet_handler(c_pkt, c_ctrl, c_stat_thread); + isp_pipeline(c_stat_thread, c_control); + user_app(); + } +} + + +int main(void) +{ + // Channel declarations + chan xscope_chan; + chan c_control; + + // Parallel jobs + par{ + on tile[0]: main_tile0(c_control); + on tile[1]: main_tile1(c_control); + // xscope + xscope_host_data(xscope_chan); + on tile[1]: xscope_io_init(xscope_chan); + } + return 0; +} diff --git a/examples/take_picture_raw/CMakeLists.txt b/examples/take_picture_raw/CMakeLists.txt new file mode 100644 index 00000000..9c9a0562 --- /dev/null +++ b/examples/take_picture_raw/CMakeLists.txt @@ -0,0 +1,17 @@ +# Executable name +set(TARGET example_take_picture_raw) + +#********************** +# Targets +#********************** +add_executable(${TARGET}) +target_sources(${TARGET} + PRIVATE + src/app_raw.c + src/main.xc +) + +target_include_directories(${TARGET} PUBLIC src) +target_compile_options(${TARGET} PRIVATE ${EXAMPLE_APP_COMPILER_FLAGS}) +target_link_libraries(${TARGET} PUBLIC ${EXAMPLE_APP_LINK_LIBRARIES}) +target_link_options(${TARGET} PRIVATE ${EXAMPLE_APP_LINK_OPTIONS}) diff --git a/examples/take_picture_raw/README.rst b/examples/take_picture_raw/README.rst new file mode 100644 index 00000000..313f57da --- /dev/null +++ b/examples/take_picture_raw/README.rst @@ -0,0 +1,46 @@ +========================= +Example: Take picture RAW +========================= + +This example set the basic settings for the sony sensor and grab a single frame. +By default the format is the following: +- 640x480 RAW8 + +************* +Build example +************* +Run the following commands from the top level: + +Linux, Mac +~~~~~~~~~~ + +.. code-block:: console + + cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + make -C build example_take_picture_raw + +Windows +~~~~~~~ + +.. code-block:: console + + cmake -G "Ninja" -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + ninja -C build example_take_picture_raw + +*************** +Running example +*************** + +From the top level +Make sure ``xscope_fileio`` is installed. See /utils/README.rst section for more details. + +.. code-block:: console + + python python/run_xscope_bin.py build/examples/take_picture_raw/example_take_picture_raw.xe + + +****** +Output +****** + +The output file ``capture.bin`` will be generated at the top level the repository. It can be further processed using ``python/decode_raw8.py`` script. diff --git a/examples/take_picture_raw/src/app_raw.c b/examples/take_picture_raw/src/app_raw.c new file mode 100644 index 00000000..72cd90be --- /dev/null +++ b/examples/take_picture_raw/src/app_raw.c @@ -0,0 +1,50 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +// std +#include +#include +// xcore +#include +#include +#include +// user +#include "mipi.h" +#include "camera_api.h" +#include "app_raw.h" +#include "io_utils.h" + +void user_app() +{ + // Initialize camera api + camera_init(); + + // set the input image to 0 + int8_t image_buffer[H_RAW][W_RAW]; + memset(image_buffer, -128, H_RAW * W_RAW); + + // wait for the camera to set I2C parameters + delay_milliseconds(4000); + + // Request an image + printf("Requesting image...\n"); + xassert((camera_capture_image_raw(image_buffer) == 0) && "Could not capture an image"); + printf("Image captured...\n"); + + // stop the threads and camera stream + camera_stop(); + delay_milliseconds(100); + + // Convert image from int8 to uint8 in-place + vect_int8_to_uint8((uint8_t*) image_buffer, + (int8_t*) image_buffer, + sizeof(image_buffer)); + + // Save the image to a file + write_image_file("capture.raw", (uint8_t * ) &image_buffer[0][0], + MIPI_IMAGE_HEIGHT_PIXELS, MIPI_IMAGE_WIDTH_BYTES, 1); + + printf("Image saved. Exiting.\n"); + xscope_close_all_files(); + exit(0); +} diff --git a/examples/take_picture_raw/src/app_raw.h b/examples/take_picture_raw/src/app_raw.h new file mode 100644 index 00000000..7dfead4a --- /dev/null +++ b/examples/take_picture_raw/src/app_raw.h @@ -0,0 +1,12 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include "xs1.h" +#include "platform.h" +#include "xccompat.h" + +#include "camera_main.h" + +void user_app(); diff --git a/examples/take_picture_raw/src/main.xc b/examples/take_picture_raw/src/main.xc new file mode 100644 index 00000000..9ddc63e8 --- /dev/null +++ b/examples/take_picture_raw/src/main.xc @@ -0,0 +1,75 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include + +#include +#include +#include +#include + +#include "app_raw.h" +#include "sensor_control.h" + +/** +* Declaration of the MIPI interface ports: +* Clock, receiver active, receiver data valid, and receiver data +*/ +on tile[MIPI_TILE] : in port p_mipi_clk = XS1_PORT_1O; +on tile[MIPI_TILE] : in port p_mipi_rxa = XS1_PORT_1E; // activate +on tile[MIPI_TILE] : in port p_mipi_rxv = XS1_PORT_1I; // valid +on tile[MIPI_TILE] : buffered in port:32 p_mipi_rxd = XS1_PORT_8A; // data +on tile[MIPI_TILE] : clock clk_mipi = MIPI_CLKBLK; + +typedef chanend chanend_t; + +extern "C" { +#include "xscope_io_device.h" +} + +// Camera control channels +void main_tile0(chanend_t c_control){ + sensor_control(c_control); +} + +// Camera image processing channels +void main_tile1(chanend_t c_control) +{ + streaming chan c_stat_thread; + streaming chan c_pkt; + streaming chan c_ctrl; + + camera_mipi_init( + p_mipi_clk, + p_mipi_rxa, + p_mipi_rxv, + p_mipi_rxd, + clk_mipi); + + par{ + MipiPacketRx(p_mipi_rxd, p_mipi_rxa, c_pkt, c_ctrl); + mipi_packet_handler(c_pkt, c_ctrl, c_stat_thread); + isp_pipeline(c_stat_thread, c_control); + user_app(); + } +} + + +int main(void) +{ + // Channel declarations + chan xscope_chan; + chan c_control; + + // Parallel jobs + par{ + on tile[0]: main_tile0(c_control); + on tile[1]: main_tile1(c_control); + // xscope + xscope_host_data(xscope_chan); + on tile[1]: xscope_io_init(xscope_chan); + } + return 0; +} diff --git a/external_deps/CMakeLists.txt b/external_deps/CMakeLists.txt new file mode 100644 index 00000000..bc9c22cf --- /dev/null +++ b/external_deps/CMakeLists.txt @@ -0,0 +1,18 @@ +include(FetchContent) + +# Fetch library +message(STATUS "Fetching: fwk_camera_deps/fwk_io") +FetchContent_Declare( + fwk_io + GIT_REPOSITORY https://github.com/xmos/fwk_io + GIT_TAG cf70def312a43c011e1f6b37cededdc379ca3bf4 # v3.1.0 + GIT_SHALLOW TRUE + SOURCE_DIR ${CMAKE_BINARY_DIR}/fwk_camera_deps/fwk_io +) +FetchContent_Populate(fwk_io) + +# Add library +add_subdirectory( + ${CMAKE_BINARY_DIR}/fwk_camera_deps/fwk_io/modules/i2c + ${CMAKE_SOURCE_DIR}/build_deps +) diff --git a/index.rst b/index.rst new file mode 100644 index 00000000..a0da2d7f --- /dev/null +++ b/index.rst @@ -0,0 +1,12 @@ +################ +Framework Camera +################ + +Framework of camera processing libraries for XCORE.AI. + +.. toctree:: + :maxdepth: 1 + + ./doc/quick_start_guide/index + ./doc/programming_guide/index + ./doc/release_notes/index diff --git a/launch_cmake.sh b/launch_cmake.sh deleted file mode 100644 index ce6b0057..00000000 --- a/launch_cmake.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# clean everything -sudo rm -r build/ - - -# build again -echo "Building everything up" -sleep 3 -cmake -B build -DCMAKE_TOOLCHAIN_FILE=xmos_cmake_toolchain/xs3a.cmake \ No newline at end of file diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 0e5e53ec..c75e91a0 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -1,4 +1 @@ -add_subdirectory(core) add_subdirectory(mipi) -add_subdirectory(i2c) - diff --git a/modules/core b/modules/core deleted file mode 160000 index da12554b..00000000 --- a/modules/core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit da12554b3556b0114302ab9c37f6a602b4b5baf5 diff --git a/modules/i2c b/modules/i2c deleted file mode 160000 index 05952714..00000000 --- a/modules/i2c +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 05952714dc0092f87f6610098ad3bbaea8b277fb diff --git a/modules/mipi b/modules/mipi deleted file mode 160000 index d124e8e6..00000000 --- a/modules/mipi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d124e8e6a3607273252a820023b1c1154e886337 diff --git a/modules/mipi/CMakeLists.txt b/modules/mipi/CMakeLists.txt new file mode 100644 index 00000000..169f3998 --- /dev/null +++ b/modules/mipi/CMakeLists.txt @@ -0,0 +1,22 @@ +## Target name +set( LIB_NAME lib_mipi ) + +add_library(${LIB_NAME} STATIC) + +target_sources(${LIB_NAME} + PRIVATE + src/MipiPacketRx.S + src/MipiPacketRx.c +) + +target_include_directories(${LIB_NAME} + PUBLIC + api +) + +target_compile_options(${LIB_NAME} + PRIVATE + -Os -g -Wall -Werror +) + +add_library(fwk_camera::mipi ALIAS ${LIB_NAME}) diff --git a/modules/mipi/README.md b/modules/mipi/README.md new file mode 100644 index 00000000..9a8068dc --- /dev/null +++ b/modules/mipi/README.md @@ -0,0 +1,3 @@ +# MIPI interface + +This directory contains APIs for initialising MIPI slave communication and receiving data from the camera. \ No newline at end of file diff --git a/modules/mipi/api/mipi.h b/modules/mipi/api/mipi.h new file mode 100644 index 00000000..59dfeda9 --- /dev/null +++ b/modules/mipi/api/mipi.h @@ -0,0 +1,63 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include +#include + +#include "xccompat.h" + +#include "mipi_defines.h" + +#ifdef __XC__ +typedef tileref tileref_t; +typedef clock xclock_t; +typedef in port port_t; +#else +#include +#include +#include +typedef unsigned tileref_t; +typedef port_t in_buffered_port_32_t; +#endif + +/** + * @brief Initialize the MIPI packet handler. + * + * @param tile Tile where the MIPI packet handler is running + * @param p_mipi_rxd High-Speed Receive Data + * @param p_mipi_rxv High-Speed Receive Data Valid. This active high signal indicates that the lane module is driving valid data to the protocol on the RxDataHS output. + * @param p_mipi_rxa RxActiveHS (Output): High-Speed Reception Active. This active high signal indicates that the lane module is actively receiving a high-speed transmission from the lane interconnect. + * @param p_mipi_clk High-Speed Receive Byte Clock. This signal is used to synchronize signals in the high-speed receive clock domain. + * @param clk_mipi Clock block used to generate the MIPI clock + * @param mipi_shim_cfg0 Configuration of the mipi shim + * @param mipiClkDiv MIPI clock divider + * @param cfgClkDiv Configuration clock divider + */ +void MipiPacketRx_init( + tileref_t tile, + in_buffered_port_32_t p_mipi_rxd, + port_t p_mipi_rxv, + port_t p_mipi_rxa, + port_t p_mipi_clk, + xclock_t clk_mipi, + unsigned mipi_shim_cfg0, + uint32_t mipiClkDiv, + uint32_t cfgClkDiv); + +/** + * @brief Mipi packet reciever + * + * @param p_mipi_rxd High-Speed Receive Data + * @param p_mipi_rxa RxActiveHS (Output): High-Speed Reception Active. This active high signal indicates that the lane module is actively receiving a high-speed transmission from the lane interconnect. + * @param c_pkt Channel to send packets to + * @param c_ctrl Channel to send control messages to + */ +void MipiPacketRx( + in_buffered_port_32_t p_mipi_rxd, + port_t p_mipi_rxa, + streaming_chanend_t c_pkt, + streaming_chanend_t c_ctrl); + + diff --git a/modules/mipi/api/mipi_defines.h b/modules/mipi/api/mipi_defines.h new file mode 100644 index 00000000..a9f56d29 --- /dev/null +++ b/modules/mipi/api/mipi_defines.h @@ -0,0 +1,186 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +typedef unsigned mipi_header_t; + +#define MIPI_HAS_PACKET_ERROR(STATUS) ((STATUS) & (0xFE)) + +#define MIPI_LONG_PACKET_MASK (0x30) +#define MIPI_IS_LONG_PACKET(HEADER) ((HEADER) & (MIPI_LONG_PACKET_MASK)) + +#define MIPI_DATA_TYPE_MASK (0x0000003F) +#define MIPI_GET_DATA_TYPE(HEADER) ((HEADER) & (MIPI_DATA_TYPE_MASK)) + +#define MIPI_GET_WORD_COUNT(HEADER) ( ((HEADER) >> 8) & 0xFFFF ) + + + +/** + * 0x00 to 0x07 - Synchronization Short Packet Data Types + * 0x08 to 0x0F - Generic Short Packet Data Types + * 0x10 to 0x17 - Generic Long Packet Data Types + * 0x18 to 0x1F - YUV Data + * 0x20 to 0x26 - RGB Data + * 0x27 to 0x2F - RAW Data + * 0x30 to 0x37 - User Defined Byte-based Data + * 0x38 - USL Commands + * 0x39 to 0x3E - Reserved for future use + * 0x3F - Unavailable (0x3F is used for LRTE EPD Spacer) + * + */ + +typedef enum { + // 0x00 to 0x07 - Synchronization Short Packet Data Types + MIPI_DT_FRAME_START = 0x00, + MIPI_DT_FRAME_END = 0x01, + MIPI_DT_LINE_START = 0x02, + MIPI_DT_LINE_END = 0x03, + MIPI_DT_EOT = 0x04, + // MIPI_DT_RESERVED_0x05 = 0x05, + // MIPI_DT_RESERVED_0x06 = 0x06, + // MIPI_DT_RESERVED_0x07 = 0x07, + + // 0x08 to 0x0F - Generic Short Packet Data Types + MIPI_DT_GENERIC_SHORT1 = 0x08, + MIPI_DT_GENERIC_SHORT2 = 0x09, + MIPI_DT_GENERIC_SHORT3 = 0x0A, + MIPI_DT_GENERIC_SHORT4 = 0x0B, + MIPI_DT_GENERIC_SHORT5 = 0x0C, + MIPI_DT_GENERIC_SHORT6 = 0x0D, + MIPI_DT_GENERIC_SHORT7 = 0x0E, + MIPI_DT_GENERIC_SHORT8 = 0x0F, + + // 0x10 to 0x17 - Generic Long Packet Data Types + MIPI_DT_NULL = 0x10, + MIPI_DT_BLANKING_DATA = 0x11, + MIPI_DT_8BIT_NONIMAGE = 0x12, + MIPI_DT_GENERIC_LONG1 = 0x13, + MIPI_DT_GENERIC_LONG2 = 0x14, + MIPI_DT_GENERIC_LONG3 = 0x15, + MIPI_DT_GENERIC_LONG4 = 0x16, + MIPI_DT_RESERVED_0x17 = 0x17, + + // 0x18 to 0x1F - YUV Data + MIPI_DT_YUV420_8BIT = 0x18, + MIPI_DT_YUV420_10BIT = 0x19, + MIPI_DT_YUV420_8BIT_LEGACY = 0x1A, + MIPI_DT_YUV420_8BIT_CHROMA_SHIFTED = 0x1C, + MIPI_DT_YUV420_10BIT_CHROMA_SHIFTED = 0x1D, + MIPI_DT_YUV422_8BIT = 0x1E, + MIPI_DT_YUV422_10BIT = 0x1F, + + // 0x20 to 0x26 - RGB Data + MIPI_DT_RGB444 = 0x20, + MIPI_DT_RGB555 = 0x21, + MIPI_DT_RGB565 = 0x22, + MIPI_DT_RGB666 = 0x23, + MIPI_DT_RGB888 = 0x24, + // MIPI_DT_RESERVED_0x25 = 0x25, + // MIPI_DT_RESERVED_0x26 = 0x26, + + // 0x27 to 0x2F - RAW Data + MIPI_DT_RAW24 = 0x27, + MIPI_DT_RAW6 = 0x28, + MIPI_DT_RAW7 = 0x29, + MIPI_DT_RAW8 = 0x2A, + MIPI_DT_RAW10 = 0x2B, + MIPI_DT_RAW12 = 0x2C, + MIPI_DT_RAW14 = 0x2D, + MIPI_DT_RAW16 = 0x2E, + MIPI_DT_RAW20 = 0x2F, + + // 0x30 to 0x37 - User Defined Byte-based Data + MIPI_DT_USER_DEFINED1 = 0x30, + MIPI_DT_USER_DEFINED2 = 0x31, + MIPI_DT_USER_DEFINED3 = 0x32, + MIPI_DT_USER_DEFINED4 = 0x33, + MIPI_DT_USER_DEFINED5 = 0x34, + MIPI_DT_USER_DEFINED6 = 0x35, + MIPI_DT_USER_DEFINED7 = 0x36, + MIPI_DT_USER_DEFINED8 = 0x37, + + // 0x38 - USL Commands + MIPI_DT_USL = 0x38, + + // 0x39 to 0x3E - Reserved for future use + // MIPI_DT_RESERVED_0x39 = 0x39, + // MIPI_DT_RESERVED_0x3A = 0x3A, + // MIPI_DT_RESERVED_0x3B = 0x3B, + // MIPI_DT_RESERVED_0x3C = 0x3C, + // MIPI_DT_RESERVED_0x3D = 0x3D, + // MIPI_DT_RESERVED_0x3E = 0x3E, + + // 0x3F - Unavailable (0x3F is used for LRTE EPD Spacer) + +} mipi_data_type_t; + +typedef enum xMIPI_DemuxMode_t { + XMIPI_DEMUXMODE_RESERVED = 0, + XMIPI_DEMUXMODE_10TO16 = 1, + XMIPI_DEMUXMODE_12TO16 = 2, + XMIPI_DEMUXMODE_14TO16 = 3, + XMIPI_DEMUXMODE_565TO88 = 4, + XMIPI_DEMUXMODE_8TO10 = 5, + XMIPI_DEMUXMODE_12TO8 = 6, // These are not in the shim documentation + XMIPI_DEMUXMODE_14TO8 = 7 // These are not in the shim documentation +} xMIPI_DemuxMode_t; + +/* + MIPI DPHY configuration register layout (MIPI_DPHY_CFG3) + + Bits Name Meaning + 14 _ENABLE_LAN1 Set to 0 to disable lane 1 receiver + 13 _ENABLE_LAN0 Set to 0 to disable lane 0 receiver + 12 _ENABLE_CLK Set to 0 to disable clock receiver + 11 _DPDN_SWAP_LAN1 Set to 1 to swap lane 1 polarity + 10 _DPDN_SWAP_LAN0 Set to 1 to swap lane 0 polarity + 9 _DPDN_SWAP_CLK Set to 1 to swap clock polarity + 8:6 _LANE_SWAP_LAN1 The pin over which to input lane 1 + 5:3 _LANE_SWAP_LAN0 The pin over which to input lane 0 + 2:0 _LANE_SWAP_CLK The pin over which to input clock +*/ + +/* + MIPI Shim configuration register layout (MIPI_SHIM_CFG0) + + Bits Name Meaning + 23 _BIAS Bias output pixels for VPU usage + 22 _DEMUX_STUFF Add zero byte after every RGB pixel + 21:16 _PIXEL_DEMUX_MODE Set demuxing mode + 15:8 _PIXEL_DEMUX_DATATYPE CSI-2 packet type to demux + 0 _PIXEL_DEMUX_EN Enable pixel demuxing +*/ +#define MIPI_SHIM_CFG0_PACK(DMUX_EN, DMUX_DT, DMUX_MODE, DMUX_STFF, DMUX_BIAS) \ + ( (((DMUX_EN != 0) & 0x1 ) << 0) \ + | (((DMUX_DT) & 0xFF ) << 8) \ + | (((DMUX_MODE) & 0x3F ) << 16) \ + | (((DMUX_STFF != 0) & 0x1 ) << 22) \ + | (((DMUX_BIAS != 0) & 0x1 ) << 23)) + + + +// Mipi shim configuration +#define _ENABLE_LAN1 (1 << 14) +#define _ENABLE_LAN0 (1 << 13) +#define _ENABLE_CLK (1 << 12) +#define _DPDN_SWAP_LAN1 (1 << 11) +#define _DPDN_SWAP_LAN0 (1 << 10) +#define _DPDN_SWAP_CLK (1 << 9) + +#define _LANE_SWAP_LAN1_DEFAULT (1 << 6) +#define _LANE_SWAP_LAN0_DEFAULT (0 << 3) +#define _LANE_SWAP_CLK_DEFAULT (2 << 0) + +#define DEFAULT_MIPI_DPHY_CFG3 ( \ + _ENABLE_LAN1 |\ + _ENABLE_LAN0 |\ + _ENABLE_CLK |\ + _DPDN_SWAP_LAN1 |\ + _DPDN_SWAP_LAN0 |\ + _DPDN_SWAP_CLK |\ + _LANE_SWAP_LAN1_DEFAULT |\ + _LANE_SWAP_LAN0_DEFAULT |\ + _LANE_SWAP_CLK_DEFAULT \ +) diff --git a/modules/mipi/src/MipiPacketRx.S b/modules/mipi/src/MipiPacketRx.S new file mode 100644 index 00000000..a808eea5 --- /dev/null +++ b/modules/mipi/src/MipiPacketRx.S @@ -0,0 +1,300 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +// XOR DISABLED +// BYTEREV DISABLED + +/**************************************************** + **************************************************** + +Then to receive a packet: + +- Load context data from global memory (RxA, RxD) +- If RxA is high, wait for it to go low. (so we don't start mid-packet) + - Initial version can just do this with polling so we don't need to do any + weird jiggery pokery with the RxA low event vector (which we're already + using for a different purpose). +- Once RxA is low, use IN to wait for the first word of the packet +- Put the first word in the buffer +- If it's a short packet, return + - Maybe it's a good idea to return the data type? +- Otherwse, do the data loop +- Return + - Maybe it's a good idea to return the data type? + +**************************************************** +****************************************************/ + +/* +In case we need byte reversal or xor for some reason, here's how to do it: + // { byterev S0, S0 ; } + // { xor S0, S0, r8 ; } +After .L_RX_LONG_LOOP: +*/ + +/* + See MIPI CSI section 9.1.2 + + typedef struct { + uint8_t data_id; + uint16_t word_count; // In MIPI a word is a byte + uint8_t vcx_ecc; + } mipi_packet_header_t; + + Note: When that 32-bit header is read in on the buffered port, the data_id + will be the *least* significant 8 bits because of the direction of this port's + shift register. + + Bits: + data_id[7:6] - least significant two bits of 4-bit Virtual Channel ID + data_id[5:0] - data_type + vcx_ecc[7:6] - most significant two bits of 4-bit Virtual Channel ID + vcx_ecc[5:0] - error correction code +*/ + +.issue_mode dual + +#define FUNCTION_NAME MipiPacketRx +#define NSTACKWORDS 8 + +.globl FUNCTION_NAME.nstackwords +.globl FUNCTION_NAME.maxthreads +.globl FUNCTION_NAME.maxtimers +.globl FUNCTION_NAME.maxchanends + +.linkset FUNCTION_NAME.nstackwords, NSTACKWORDS +.linkset FUNCTION_NAME.maxchanends, 0 +.linkset FUNCTION_NAME.maxtimers, 0 +.linkset FUNCTION_NAME.maxthreads, 0 + +.globl FUNCTION_NAME +.type FUNCTION_NAME, @function +.text +.cc_top FUNCTION_NAME.func, FUNCTION_NAME + +#define P_RXD r0 +#define P_RXA r1 +#define C_PKT r2 +#define BUFF r3 + +#define HEADER r4 +#define S0 r5 +#define S1 r6 +#define BUFF_HEAD r7 + + +/**************************************************** + **************************************************** + + extern struct { + uint32_t RxD; + uint32_t RxA; + } mipi_context; + + Non-zero return signals error. + + unsigned MipiPacketRx(uint8_t packet_buffer[]); + + void MipiPacketRx2( + const unsigned RxD, + const unsigned RxA, + streaming chanend c_pkt, + streaming chanend c_ctrl) + + **************************************************** + **************************************************** +*/ +.align 4 +.skip 0 +FUNCTION_NAME: + dualentsp NSTACKWORDS + std r4, r5, sp[1] + std r6, r7, sp[2] + std r8, r9, sp[3] + + +// Enable MIPI shim hardware +{ ldc r4, 0x01 ; ldc r9, 8 } +{ shl r4, r4, r9 ; } + ldc r9, XS1_PS_XCORE_CTRL0 + get r11, ps[r9] +{ or r4, r4, r11 ; } + set ps[r9], r4 + + +// Enable RxA event + setc res[P_RXA], XS1_SETC_COND_EQ // <-- event on edge of RxA + setc res[P_RXA], XS1_SETC_IE_MODE_EVENT // <-- do event, not interrupt + ldc r11, 0 + setd res[P_RXA], r11 // <-- falling edge + ldap r11, .L_MIPI_RXA_LOW + setv res[P_RXA], r11 + +// Enable events for the thread + setsr 1 // Sets the EEBLE bit (I couldn't find a #define!) + +// Set up interrupt for c_ctrl +// Note: has to be interrupt because we can't have events always enabled. +// Wait, no, events can be enabled/disabled per resource. I should be able to +// use an event... +/* +//////// TODO /////// +*/ + + +#if 0 // astew: I'm not sure if we need this, since we just turned on the shim + // but I don't see why we couldn't start receiving a packet while + // setting up the RxA event or whatever. + + +// Check whether the RxA signal is currently high. +// If so, we need to wait until it is low or we will start reading mid-packet + +.L_WAIT_LOOP: +{ peek S0, res[P_RXA] ; } +{ ecallt S0 ; } +{ ; bt S0, .L_WAIT_LOOP } + +#endif + +ldc r8, 0x80 +shl r9, r8, 8 +or r8, r8, r9 +shl r9, r8, 16 +or r8, r8, r9 + + +.L_GET_NEXT_BUFFER: + +{ in BUFF, res[C_PKT] ; } +{ mov BUFF_HEAD, BUFF ; bf BUFF, .L_EXIT_THREAD } // <-- exit if null buffer + +.L_WAIT_FOR_NEXT_PACKET: + +// peek S0, res[P_RXA] +// ecallt S0 + +// Receive packet header from data port +{ in HEADER, res[P_RXD] ; ldc S1, 0x00000030 } +{ and S1, S1, HEADER ; stw HEADER, BUFF[0] } + +// Check whether the header indicates it is a long or short packet. +// If long, enable the falling-edge event on RxA. +// If short, we already have the whole packet +{ eet S1, res[P_RXA] ; bt S1, .L_RX_LONG } + + +.L_RX_SHORT: +// A short packet was received. +{ out res[C_PKT], BUFF_HEAD ; bu .L_GET_NEXT_BUFFER } + + +.L_RX_LONG: +// Long packet incoming. The header has been placed into the buffer, but hold on +// to it until we get the rest of the packet. + +{ in S0, res[P_RXD] ; add BUFF, BUFF, 4 } +.L_RX_LONG_LOOP: + { add BUFF, BUFF, 4 ; stw S0, BUFF[0] } + { in S0, res[P_RXD] ; bu .L_RX_LONG_LOOP } + + /**** + * RxA event vector target. + * + * RxA is a signal coming from the xcore.ai hardware's MIPI shim layer, + * signaling high while a packet is being received. On the falling edge of the + * signal, the packet has been fully received, and this event fires, breaking + * the thread out of the receive loop. + * + * If the received packet was not a multiple of 4 bytes, there will still be + * data left in the shift register. This event grabs any remaining data and puts + * it at the end of the received frame. + * + * When the data has been grabbed, it jumps down to .L_RxLONG_DataEnd + * + * This is only used for LONG frames. Short frames are always exactly 32 bits. + ***** + */ +.align 32 +.skip 20 +.L_MIPI_RXA_LOW: + +// Clear event on RxA port +{ in r11, res[P_RXA] ; } // <-- aligned 20 mod 32 +// End input on P_RXD, so we can pull out any remaining bytes without blocking. +{ endin r11, res[P_RXD] ; } // <-- aligned 24 mod 32 + +// NOTE: r11 will contain a number of BITS remaining, instead of bytes. The +// .align 32's below just avoid the need to change it to dual-issue instructions +// for the BRU instruction. r11 will be one of {0,8,16,24} +{ in S1, res[P_RXD] ; bru r11 } // <-- aligned 28 mod 32 + + +// In each of the 4 cases, the very last byte of the transmission should end +// up in S2. The last byte contains error bits and whatnot + +// In the 0 case, the byte with the error details is already in S2, but in the +// most significant bits, so pull it down. +//.... is it already in S2..? If r11 is zero, what did the IN instruction paired +// with BRU do? I suspect this is wrong.. +// +.L_RXA_OUT_TAIL0: // (0 byte tail) +{ shr S0, S0, 24 ; } // <-- aligned 0 mod 32 + bu .L_RXA_DATA_END + +.align 32 +.L_RXA_OUT_TAIL1: // (1 Tail Byte; the status byte) +{ shr S0, S1, 24 ; } +{ ; stw S0, BUFF[0] } +{ ; bu .L_RXA_DATA_END } + +.align 32 +.L_RXA_OUT_TAIL2: // (2 Tail Bytes) +{ shr S1, S1, 16 ; } +{ shr S0, S1, 8 ; stw S1, BUFF[0] } +{ ; bu .L_RXA_DATA_END } + +.align 32 +.L_RXA_OUT_TAIL3: // (3 Tail Bytes) +{ shr S1, S1, 8 ; } +{ shr S0, S1, 16 ; stw S1, BUFF[0] } +{ ; bu .L_RXA_DATA_END } + +.align 8 +.L_RXA_DATA_END: + +// We've received the full packet and S0 contains the status byte + +// Disable the RxA event +// (any bits set in S2 other than LSb indicate an error) +{ edu res[P_RXA] ; shr S0, S0, 1 } + +// Here's how we'll deal with received packet errors for now: If there is a +// packet error (if S0 is nonzero right here) in this long packet, change the +// data length in the packet header to 0. The header is already in the buffer, +// so if the next layer really needs to know what we saw for a packet length, +// it can just look in the buffer. +// +// Actually, for now, just assert on any errors. +{ out res[C_PKT], BUFF_HEAD ; } + +// And now we need a new buffer +// Also reenable events. +{ setsr 1 ; bu .L_GET_NEXT_BUFFER } + + +.L_EXIT_THREAD: + + ldd r8, r9, sp[3] + ldd r6, r7, sp[2] + ldd r4, r5, sp[1] + retsp NSTACKWORDS + +.size FUNCTION_NAME, .-FUNCTION_NAME +.cc_bottom FUNCTION_NAME.func + + + diff --git a/modules/mipi/src/MipiPacketRx.c b/modules/mipi/src/MipiPacketRx.c new file mode 100644 index 00000000..90c963e5 --- /dev/null +++ b/modules/mipi/src/MipiPacketRx.c @@ -0,0 +1,61 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include + +#include "mipi.h" + +// This API has been translated from XC +// original code is kept uncommented + +void MipiPacketRx_init( + unsigned tile, + port_t p_mipi_rxd, + port_t p_mipi_rxv, + port_t p_mipi_rxa, + port_t p_mipi_clk, + xclock_t clk_mipi, + unsigned mipi_shim_cfg0, + uint32_t mipiClkDiv, + uint32_t cfgClkDiv) +{ + //configure_in_port_strobed_slave(p_mipi_rxd, p_mipi_rxv, clk_mipi); + port_protocol_in_strobed_slave(p_mipi_rxd, p_mipi_rxv, clk_mipi); + + //set_clock_src(clk_mipi, p_mipi_clk); + clock_set_source_port(clk_mipi, p_mipi_clk); + + // Sample on falling edge - shim outputting on rising + //set_clock_rise_delay(clk_mipi, 1); + // 0x9007 is a RISE_DELAY config, see XU316 documentation + __xcore_resource_setc(clk_mipi, XS1_SETC_VALUE_SET(0x9007, 1)); + + //set_pad_delay(p_mipi_rxa, 1); + // 0x7007 is a PAD_DELAY config, see XU316 documentation + __xcore_resource_setc(p_mipi_rxa, XS1_SETC_VALUE_SET(0x7007, 1)); + + //start_clock(clk_mipi); + clock_start(clk_mipi); + + // take DPHY out of reset + + //write_node_config_reg(tile, XS1_SSWITCH_MIPI_DPHY_CFG0_NUM, 3); + write_sswitch_reg(tile, XS1_SSWITCH_MIPI_DPHY_CFG0_NUM, 3); + + // set clock dividers to create suitable frequency + //write_node_config_reg(tile, XS1_SSWITCH_MIPI_CLK_DIVIDER_NUM, mipiClkDiv); + write_sswitch_reg(tile, XS1_SSWITCH_MIPI_CLK_DIVIDER_NUM, mipiClkDiv); + + //write_node_config_reg(tile, XS1_SSWITCH_MIPI_CFG_CLK_DIVIDER_NUM, cfgClkDiv); + write_sswitch_reg(tile, XS1_SSWITCH_MIPI_CFG_CLK_DIVIDER_NUM, cfgClkDiv); + + // set shim config register + //write_node_config_reg(tile, XS1_SSWITCH_MIPI_SHIM_CFG0_NUM, mipi_shim_cfg0); + write_sswitch_reg(tile, XS1_SSWITCH_MIPI_SHIM_CFG0_NUM, mipi_shim_cfg0); + + // Connect the xcore-ports to the MIPI demuxer/PHY. + int val = getps(XS1_PS_XCORE_CTRL0); + setps(XS1_PS_XCORE_CTRL0, val | 0x100); +} diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..8c823a0a --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +addopts = -s +testpaths = "tests/hardware_tests" diff --git a/python/FIR_pipeline.py b/python/FIR_pipeline.py index 01f83534..3bea216b 100644 --- a/python/FIR_pipeline.py +++ b/python/FIR_pipeline.py @@ -1,10 +1,21 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + import cv2 import matplotlib.pyplot as plt import numpy as np from PIL import Image # just to avoid color BGR issues when writting from scipy import signal -from utils import * +from utils import ( + peak_signal_noise_ratio, + pipeline, + normalize, + gray_world, + get_real_image, + new_color_correction, + run_histogram_equalization, + iterative_wb) OFF = 0 STEP = 4 @@ -56,6 +67,7 @@ def intercalate_zeros(filter): def convolve_h(j:int, i:int, img, filter:list): sump = 0 + KH = (len(filter) -1) //2 for u in range(-KH, KH + 1, 1): if(i+u >= img.shape[1]): u = 0 @@ -67,6 +79,7 @@ def convolve_h(j:int, i:int, img, filter:list): def convolve_v(j:int, i:int, img, filter:list): sump = 0 + KV = (len(filter) -1) //2 for u in range(-KV, KV + 1, 1): if(j+u >= img.shape[0]): u = 0 @@ -88,7 +101,7 @@ def create_filter(): v1 = [0.08626086, 0.23894391, 0.34959045, 0.23894391, 0.08626086] v2 = [0.0248892, 0.2528858, 0.44445 , 0.2528858, 0.0248892] - h_filter = h1 + h_filter = h2 v_filter = v2 @@ -99,22 +112,10 @@ def create_filter(): return h_filter, v_filter, KV, KH -if __name__ == '__main__': - - ENABLE_IMSHOW = True - - # get test_image - img, (height, width) = get_real_image() - img = img.reshape(height, width) - - imgh = np.zeros((height//2, width//4, 3)) - imgv = np.zeros((height//4, width//4, 3)) - - # create the filters - h_filter, v_filter, KV, KH = create_filter() - print(h_filter, v_filter) - +def horizontal_filer(img, h_filter, height, width): + KH = (len(h_filter) -1) //2 # horizontal filtering + imgh = np.zeros((height//2, width//4, 3)) for j in range(height): for i in range(0, width, STEP): pos = i//STEP @@ -127,14 +128,13 @@ def create_filter(): imgh[j//2, pos, BLUE] = convolve_h(j, i+1, img, h_filter) imgh = np.clip(imgh, 0, 255).astype(np.uint8) + return imgh - # vertical filter + +def vertical_filer(imgh,height, width, v_filter, red, green, blue): +# vertical filter new_h, new_w, ch = imgh.shape - - # white balancing - imgh = gray_world(imgh) - red, green, blue = imgh[:,:,RED], imgh[:,:,GREEN], imgh[:,:,BLUE] - + imgv = np.zeros((height//4, width//4, 3)) for i in range(new_w): for j in range(0, new_h , STEP_V): red_p = convolve_v(j, i, red, v_filter) @@ -146,16 +146,97 @@ def create_filter(): imgv = np.clip(imgv, 0, 255).astype(np.uint8) - img = imgv + return imgv + + +def FIR_pipeline_func(img, height, width, show=False): + # create the filters + h_filter, v_filter, KV, KH = create_filter() + # horizontal filter + imgh = horizontal_filer(img, h_filter, height, width) + + # white balancing + #imgh = gray_world(imgh) + imgh = iterative_wb(imgh) + + red, green, blue = imgh[:,:,RED], imgh[:,:,GREEN], imgh[:,:,BLUE] + + # vertical filtering + img = vertical_filer(imgh,height, width, v_filter, red, green, blue) + + ########### post processing ############## + # black level substraction + BLACK_LEVEL = 16 + img = normalize(img, BLACK_LEVEL, 254, np.uint8) + + # gamma + img = img ** (1.0 / 1.8) + + # sharpen (optional) + #kernel_sharpen = kernel_sharpen/np.sum(kernel_sharpen) + #img = cv2.filter2D(src=img, ddepth=-1, kernel=kernel_sharpen_5) + + # Color correction (optional) + # img = new_color_correction(img) + + # clip the image + img = np.clip(255*img, 0, 255).astype(np.uint8) + + # image stretch + # img = stretch_histogram(img) + + # histeq + # img = run_histogram_equalization(img) + + return img + +def stretch_histogram(image): + stretched_image = np.zeros_like(image) + + for i in range(3): # Iterate over color channels (R, G, B) + channel = image[:, :, i] + + # Calculate the minimum and maximum pixel values + min_val = np.min(channel) + max_val = np.max(channel) + + # Apply contrast stretching to the channel + stretched_channel = ((channel - min_val) * (255.0 / (max_val - min_val))).astype(np.uint8) + + # Assign the stretched channel to the output image + stretched_image[:, :, i] = stretched_channel + + return stretched_image + +if __name__ == '__main__': + + ENABLE_IMSHOW = True + + # get test_image + img, (height, width) = get_real_image() + img = img.reshape(height, width) + + # create the filters + h_filter, v_filter, KV, KH = create_filter() + print(h_filter, v_filter) + + # horizontal filter + imgh = horizontal_filer(img, h_filter, height, width) + + # white balancing + imgh = gray_world(imgh) + red, green, blue = imgh[:,:,RED], imgh[:,:,GREEN], imgh[:,:,BLUE] + + # vertical filtering + img = vertical_filer(imgh,height, width, v_filter, red, green, blue) - # ########### post processing ############## # black level substraction img = normalize(img, 15, 254, np.uint8) # gamma - img = img ** (1.0 / 2) + img = img ** (1.0 / 1.8) # sharpen (optional) kernel_sharpen = kernel_sharpen/np.sum(kernel_sharpen) @@ -166,6 +247,8 @@ def create_filter(): # clip the image img = np.clip(255*img, 0, 255).astype(np.uint8) + + ########### post processing ############## @@ -189,5 +272,3 @@ def create_filter(): img_psnr = peak_signal_noise_ratio(ref_image, img) print(img_psnr) - - diff --git a/python/filters.txt b/python/_filters.txt similarity index 100% rename from python/filters.txt rename to python/_filters.txt diff --git a/python/decode_downsampled.py b/python/decode_downsampled.py new file mode 100644 index 00000000..559eb887 --- /dev/null +++ b/python/decode_downsampled.py @@ -0,0 +1,37 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +""" +Downsampled image info: + * 160x120 + * R G B +""" +import os +import cv2 +import matplotlib.pyplot as plt +import numpy as np +from PIL import Image # just to avoid color BGR issues when writting +from dotenv import load_dotenv +load_dotenv() # take environment variables from .env. + +from utils import show_histogram_by_channel + +input_name = os.getenv('BINARY_IMG_PATH') or "capture.bin" + +# read the data +with open(input_name, "rb") as f: + data = f.read() + +# unpack +width, height = 160, 120 +buffer = np.frombuffer(data, dtype=np.uint8) +img = buffer.reshape(height, width, 3) +print("unpacked_data") + +# show image +plt.figure() +plt.imshow(img) +plt.show() + +# show histograms +show_histogram_by_channel(img, 3000) diff --git a/python/decode_raw10.py b/python/decode_raw10.py index 7f6a126e..3df0d04b 100644 --- a/python/decode_raw10.py +++ b/python/decode_raw10.py @@ -1,3 +1,6 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + # Raw stream: 640x480 stride 800 format SBGGR10_CSI2P """ that means : @@ -6,28 +9,34 @@ BGGR is the order of the Bayer pattern few padding bytes on the end of every row to match bits """ -import matplotlib.pyplot as plt +import os import cv2 +import matplotlib.pyplot as plt import numpy as np -from PIL import Image # just to avoid color BGR issues when writting -from utils import * - -image_name = "img_raw10.bin" -path = "/home/albertoisorna/xalbertoisorna/rawtreatment/newimgs/" -path='/mnt/c/Users/albertoisorna/exec/' +from PIL import Image # just to avoid color BGR issues when writting +from dotenv import load_dotenv +load_dotenv() # take environment variables from .env. + +from utils import ( + remove_padding_buffer_no_align, + unpack_mipi_raw10_buffer, + old_normalize, + white_balance, + demosaic, + new_color_correction, + plot_imgs +) + +input_name = os.getenv('BINARY_IMG_PATH') or "capture.raw" -image_name = "img_640_480_10_0008.raw" -path = "/home/albertoisorna/xalbertoisorna/rawtreatment/images_binning/adquisition_2_raw/" - -input_name = path+image_name width = 640 height = 480 bit_width = 10 flip = False as_shot_neutral = [0.5666090846, 1, 0.7082979679] as_shot_neutral = [0.6301882863, 1, 0.6555861831] -#cfa_pattern = [0, 1, 1, 2] -cfa_pattern = [2, 1, 1, 0] +#cfa_pattern = [2, 1, 1, 0] # raspberry +cfa_pattern = [0, 1, 1, 2] # explorer board # read the data with open(input_name, "rb") as f: @@ -59,18 +68,11 @@ img = demosaic(img, cfa_pattern, output_channel_order='RGB', alg_type='VNG') img_demoisaic = img -# color space transformation -color_matrix_1 = [0.9762914777, -0.2504389584, -0.1018426344, - -0.1751390547, 0.9807397723, 0.1705771685, - 0.04482413828, 0.1344814152, 0.4878755212] - -#img = old_apply_color_space_transform(img, color_matrix_1) - -# color transform 2 -# img = old_transform_xyz_to_srgb(img) +# color transforms +img = new_color_correction(img) # gamma -img = img ** (1.0 / 2.2) +img = img ** (1.0 / 1.8) # clip the image img = np.clip(255*img, 0, 255).astype(np.uint8) @@ -99,4 +101,4 @@ blue = int(img_demoisaic[:,:,2].sum()) txt = f"blue: {blue}, green: {green}, red: {red}" -print(txt) \ No newline at end of file +print(txt) diff --git a/python/decode_raw8.py b/python/decode_raw8.py index a33ccec7..67329d7c 100644 --- a/python/decode_raw8.py +++ b/python/decode_raw8.py @@ -1,3 +1,6 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + """ Info of RAW streams @@ -11,7 +14,7 @@ BGGR is the order of the Bayer pattern few padding bytes on the end of every row to match bits """ - +import os import cv2 import matplotlib.pyplot as plt import numpy as np @@ -19,9 +22,15 @@ from dotenv import load_dotenv load_dotenv() # take environment variables from .env. -from utils import * +from utils import ( + normalize, + simple_white_balance, + demosaic, + new_color_correction, + plot_imgs +) -input_name = os.getenv('BINARY_IMG_PATH') +input_name = os.getenv('BINARY_IMG_PATH') or "capture.raw" width, height = 640, 480 @@ -47,7 +56,10 @@ # unpack +#buffer = np.frombuffer(data, dtype=np.int8) + 128 # convert to uint8 +#buffer = buffer.astype(np.uint8) buffer = np.frombuffer(data, dtype=np.uint8) + img = buffer.reshape(height, width, 1) print("unpacked_data") @@ -67,28 +79,19 @@ # demosaic img = demosaic(img, cfa_pattern, output_channel_order='RGB', alg_type='VNG') img_demoisaic = img -# wb -#img = gray_world(img) # color transforms img = new_color_correction(img) - - - # gamma img = img ** (1.0 / 1.8) # clip the image img = np.clip(255*img, 0, 255).astype(np.uint8) - # hist equalization (optional) -# img = run_histogram_equalization(img) +# img = run_histogram_equalization(img) # resize bilinear (optional) kfactor = 1 img = cv2.resize(img, (width // kfactor, height // kfactor), interpolation=cv2.INTER_AREA) # ------ The ISP pipeline ------------------------- - - -###################################################### ################# PLOT ############################## # save image @@ -96,6 +99,7 @@ imgs = cv2.flip(img, 0) else: imgs = img + name = f"{input_name}_postprocess_.png" Image.fromarray(imgs).save(name) print(name) @@ -110,4 +114,4 @@ blue = int(img_demoisaic[:,:,2].sum()) txt = f"blue: {blue}, green: {green}, red: {red}" -print(txt) \ No newline at end of file +print(txt) diff --git a/python/encode_raw8.py b/python/encode_raw8.py new file mode 100644 index 00000000..b394aa5f --- /dev/null +++ b/python/encode_raw8.py @@ -0,0 +1,69 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +# load a png or jpg image and encodes to RAW8 format +import os +import cv2 +import matplotlib.pyplot as plt +import numpy as np +from PIL import Image # just to avoid color BGR issues when writting +import sys + +# append current path to sys.path +from pathlib import Path +root = (str(Path(__file__).resolve().parent)) + +def encode_raw8(in_filename, out_filename, uint8=False): + # reads an image using pillow + img = Image.open(in_filename) + img = img.convert('RGB') + img = img.resize((640//2, 480//2)) + + # convert to numpy array + img = np.array(img).astype(np.uint8) + raw_img = np.zeros((480, 640), dtype=np.uint8) + + # split in color planes + raw_img[0::2, 0::2] = img[:,:,0] + raw_img[0::2, 1::2] = img[:,:,1] + raw_img[1::2, 0::2] = img[:,:,1] + raw_img[1::2, 1::2] = img[:,:,2] + raw_img = raw_img.clip(0, 255).astype(np.uint8) + + # 1 gamma + raw_img = raw_img/255.0 + raw_img = np.power(raw_img, 1.8) + raw_img = raw_img*255.0 + raw_img = raw_img.clip(0, 255).astype(np.uint8) + + # 2 awb + rgb_gain = [0.5, 0.9, 0.5] + raw_img[0::2, 0::2] = raw_img[0::2, 0::2]*rgb_gain[0] + raw_img[0::2, 1::2] = raw_img[0::2, 1::2]*rgb_gain[1] + raw_img[1::2, 0::2] = raw_img[1::2, 0::2]*rgb_gain[1] + raw_img[1::2, 1::2] = raw_img[1::2, 1::2]*rgb_gain[2] + raw_img = raw_img.clip(0, 255).astype(np.uint8) + + # 3 black level substraction + BLACK_LEVEL = 16 + raw_img += BLACK_LEVEL + raw_img = raw_img.astype(np.uint8) + + # save as a binary file (uint8) + if uint8: + with open(out_filename, "wb") as img: + img.write(raw_img) + + else: + # save as a binary file (int8) + raw_img = raw_img.astype(np.int16) + raw_img -= 128 + raw_img = raw_img.astype(np.int8) + with open(out_filename, "wb") as img: + img.write(raw_img) + + print(f"Image encoded and saved to {out_filename}, unsigned = {uint8}") + + +if __name__ == "__main__": + pass diff --git a/python/requirements.txt b/python/requirements.txt deleted file mode 100644 index bb265a70..00000000 --- a/python/requirements.txt +++ /dev/null @@ -1,21 +0,0 @@ -contourpy==1.0.7 -cycler==0.11.0 -ExifRead==3.0.0 -fonttools==4.39.3 -imageio==2.28.1 -kiwisolver==1.4.4 -lazy_loader==0.2 -matplotlib==3.7.1 -networkx==3.1 -numpy==1.24.3 -opencv-python==4.7.0.72 -packaging==23.1 -Pillow==9.5.0 -pyparsing==3.0.9 -python-dateutil==2.8.2 -python-dotenv==1.0.0 -PyWavelets==1.4.1 -scikit-image==0.20.0 -scipy==1.10.1 -six==1.16.0 -tifffile==2023.4.12 diff --git a/python/run_xscope_bin.py b/python/run_xscope_bin.py new file mode 100644 index 00000000..943da019 --- /dev/null +++ b/python/run_xscope_bin.py @@ -0,0 +1,98 @@ +# Copyright 2022-2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +import xscope_fileio +import argparse +import shutil +import subprocess +import glob +from pathlib import Path + + +def get_adapter_id(): + try: + xrun_out = subprocess.check_output(['xrun', '-l'], text=True, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + print('Error: %s' % e.output) + assert False + except FileNotFoundError: + msg = ("please ensure you have XMOS tools activated in your environment") + assert False, msg + + xrun_out = xrun_out.split('\n') + # Check that the first 4 lines of xrun_out match the expected lines + expected_header = ["", "Available XMOS Devices", "----------------------", ""] + if len(xrun_out) < len(expected_header): + raise RuntimeError( + f"Error: xrun output:\n{xrun_out}\n" + f"does not contain expected header:\n{expected_header}" + ) + + header_match = True + for i, expected_line in enumerate(expected_header): + if xrun_out[i] != expected_line: + header_match = False + break + + if not header_match: + raise RuntimeError( + f"Error: xrun output header:\n{xrun_out[:4]}\n" + f"does not match expected header:\n{expected_header}" + ) + + try: + if "No Available Devices Found" in xrun_out[4]: + raise RuntimeError(f"Error: No available devices found\n") + + except IndexError: + raise RuntimeError(f"Error: xrun output is too short:\n{xrun_out}\n") + + for line in xrun_out[6:]: + if line.strip(): + adapterID = line[26:34].strip() + status = line[34:].strip() + else: + continue + print("adapter_id = ",adapterID) + return adapterID + + +def parse_arguments(): + parser = argparse.ArgumentParser() + parser.add_argument("xe", nargs='?', + help=".xe file to run") + args = parser.parse_args() + return args + +def run(xe, return_stdout=False): + + adapter_id = get_adapter_id() + print("Running on adapter_id ",adapter_id) + if return_stdout == False: + xscope_fileio.run_on_target(adapter_id, xe) + else: + with open("prof.txt", "w+") as ff: + xscope_fileio.run_on_target(adapter_id, xe, stdout=ff) + ff.seek(0) + stdout = ff.readlines() + return stdout + +def choose_file_with_extension(folder_path, extension): + # Get a list of files with the specified extension in the folder + files = list(Path(folder_path).rglob(f"*{extension}")) + files = list(map(str, files)) # i dont like to much this map here but it works + assert len(files) > 0 , (f"No {extension} files found in the folder.") + + [print(i,file) for i,file in enumerate(files)] + + choose = input("Choose the file to run: \n") + if int(choose) in range(len(files)): + run(files[int(choose)]) + +if __name__ == "__main__": + args = parse_arguments() + if (args.xe is None): + build_folder = Path(__file__).parent.parent.resolve() / "build" + choose_file_with_extension(build_folder, ".xe") + else: + run(args.xe) diff --git a/python/test_imgs/test.jpg b/python/test_imgs/test.jpg new file mode 100644 index 00000000..dd6df8df Binary files /dev/null and b/python/test_imgs/test.jpg differ diff --git a/python/utils.py b/python/utils.py index 062bb29e..c77e3069 100644 --- a/python/utils.py +++ b/python/utils.py @@ -1,3 +1,6 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + import os import cv2 import matplotlib.pyplot as plt @@ -462,17 +465,19 @@ def run_histogram_equalization(img_bgr): return colorimage_clahe -def show_histogram_by_channel(image): +def show_histogram_by_channel(image, ylim=None): # Set the histogram bins to 256, the range to 0-255 - hist_size = 256 - hist_range = (0, 256) + hist_size = 265 + hist_range = (0, 255) # Plot the histograms using plt.hist plt.figure(figsize=(10, 5)) for i, col in enumerate(['r', 'g', 'b']): plt.subplot(1, 3, i+1) plt.title(f'{col.upper()} Histogram') - plt.xlim([0, 255]) + plt.xlim([0, hist_size]) + if ylim is not None: + plt.ylim([0, ylim]) plt.hist(image[:,:,i].ravel(), bins=hist_size, range=hist_range, color=col) plt.show() @@ -480,7 +485,7 @@ def show_histogram_by_channel(image): def show_histogram_by_channel_ax(image, ax): # Set the histogram bins to 256, the range to 0-255 hist_size = 256 - hist_range = (0, 256) + hist_range = (0, hist_size) # Plot the histograms using plt.hist for i, col in enumerate(['r', 'g', 'b']): @@ -489,6 +494,7 @@ def show_histogram_by_channel_ax(image, ax): def plot_imgs(img, img_raw_RGB, flip, ax=None): + hist_size = 256 if ax is None: fig, ax = plt.subplots(2, 2, figsize=(16, 8)) if flip: @@ -503,9 +509,9 @@ def plot_imgs(img, img_raw_RGB, flip, ax=None): # plt.show() # plot histogram - ax3.hist(img.mean(axis=2).flatten(), 255) - ax3.hist(img_raw_RGB.mean(axis=2).flatten(), 255) - ax3.axis(xmin=0,xmax=255) + ax3.hist(img.mean(axis=2).flatten(), hist_size) + ax3.hist(img_raw_RGB.mean(axis=2).flatten(), hist_size) + ax3.axis(xmin=0,xmax=hist_size) ax3.legend(["processed", "unprocessed"]) # show histogram for 3 channels @@ -817,6 +823,32 @@ def pipeline(img, demosaic_opt=True): #it takes a RAW IMAGE # img = run_histogram_equalization(img) return img +def pipeline_raw8(img, demosaic_opt=True): #it takes a RAW IMAGE + as_shot_neutral = [0.6301882863, 1, 0.6555861831] + width, height = 640, 480 + cfa_pattern = [0, 1, 1, 2] # explorer board + + # ------ The ISP pipeline ------------------------- + # black level substraction + img = normalize(img, 15, 254, np.uint8) + # white balancing + img = simple_white_balance(img, as_shot_neutral, cfa_pattern) + # demosaic + img = demosaic(img, cfa_pattern, output_channel_order='RGB', alg_type='VNG') + img_demoisaic = img + # color transforms + img = new_color_correction(img) + # gamma + img = img ** (1.0 / 1.8) + # clip the image + img = np.clip(255*img, 0, 255).astype(np.uint8) + # hist equalization (optional) + # img = run_histogram_equalization(img) + # resize bilinear (optional) + kfactor = 1 + img = cv2.resize(img, (width // kfactor, height // kfactor), interpolation=cv2.INTER_AREA) + # ------ The ISP pipeline ------------------------- + return img def pipeline_nodemosaic(img): @@ -887,5 +919,53 @@ def gray_world(img): # img[:,:,1] = return img + +def iterative_wb(img): + img = img/255.0 + + R = img[:,:,0] + G = img[:,:,1] + B = img[:,:,2] + + # to YUV + a,b,c = 0.299,0.587,0.114 + d,e,f = -0.147,-0.289,0.436 + g,h,i = 0.615,-0.515,-0.100 + + y = a*R + b*G + c*B + u = d*R + e*G + f*B + v = g*R + h*G + i*B + + loc = np.where(y > 0.4) # find high luminance values + + # compute luminance region + yl = y[loc] + ul = u[loc] + vl = v[loc] + + # local to RGB + R = yl + 1.140*vl + G = yl - 0.395*ul - 0.581*vl + B = yl + 2.032*ul + + img[:,:,0] /= R.mean() + img[:,:,1] /= G.mean() + img[:,:,2] /= B.mean() + + img = img.clip(0,1)*255.0 + return img + + +def compute_score(img_ref, img): + # if image is color, convert to gray + if img_ref.ndim == 3: + img_ref = cv2.cvtColor(img_ref, cv2.COLOR_RGB2GRAY) + if img.ndim == 3: + img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) + + score = ssim(img_ref, img) + return score + + if __name__ == '__main__': - pass \ No newline at end of file + pass diff --git a/requirements.txt b/requirements.txt index f0f52b81..ed4704b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,23 @@ -colorama==0.4.6 -docopt==0.6.2 -packaging==23.0 -pykwalify==1.8.0 -python-dateutil==2.8.2 -PyYAML==6.0 -ruamel.yaml==0.17.21 -ruamel.yaml.clib==0.2.7 -six==1.16.0 -west==1.0.0 +contourpy==1.0.7 +cycler==0.11.0 +ExifRead==3.0.0 +fonttools==4.39.3 +imageio==2.28.1 +kiwisolver==1.4.4 +lazy_loader==0.2 +matplotlib==3.7.1 +networkx==3.1 +numpy==1.24.3 +opencv-python==4.7.0.72 +packaging==23.1 +Pillow==9.5.0 +pyparsing==3.0.9 +python-dateutil==2.8.2 +python-dotenv==1.0.0 +PyWavelets==1.4.1 +scikit-image==0.20.0 +scipy +six==1.16.0 +tifffile==2023.4.12 + +-e ./utils/xscope_fileio diff --git a/sensors/.gitkeep b/sensors/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/sensors/CMakeLists.txt b/sensors/CMakeLists.txt index 7ba28292..a84b8899 100644 --- a/sensors/CMakeLists.txt +++ b/sensors/CMakeLists.txt @@ -1,37 +1,40 @@ # ############################################################################## # CMake configuration stuff -cmake_minimum_required(VERSION 3.14) -cmake_policy(SET CMP0057 NEW) enable_language(C CXX ASM) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -string(REPLACE "-MD" "-MMD" CMAKE_DEPFILE_FLAGS_C ${CMAKE_DEPFILE_FLAGS_C}) - # ############################################################################## # Target name -set(LIB_NAME lib_imx) - -# Source files -file(GLOB_RECURSE SOURCES_C "*.c") -file(GLOB_RECURSE SOURCES_XC "*.xc") -file(GLOB_RECURSE SOURCES_CPP "*.cpp") -file(GLOB_RECURSE SOURCES_ASM "*.S") +set(LIB_NAME lib_sensors) add_library(${LIB_NAME} STATIC) -target_include_directories(${LIB_NAME} PUBLIC . api) - -target_sources(${LIB_NAME} PRIVATE ${SOURCES_C} ${SOURCES_XC} ${SOURCES_CPP} - $<$:${SOURCES_ASM}>) +target_compile_definitions(${LIB_NAME} + PUBLIC + CONFIG_MIPI_FORMAT=${CONFIG_MIPI_FORMAT} +) + +target_include_directories(${LIB_NAME} + PUBLIC + api + src/_sony_imx219 +) + +target_sources(${LIB_NAME} + PRIVATE + src/SensorBase.cpp + src/sensor_control.cpp + src/_sony_imx219/imx219.cpp +) + +target_compile_options(${LIB_NAME} + PRIVATE + -Wall + -Werror +) target_link_libraries(${LIB_NAME} PUBLIC lib_i2c) -# ############################################################################## -# Add and link the library -add_library(lib_imx_general INTERFACE) - -target_link_libraries(lib_imx_general INTERFACE ${LIB_NAME}) - -add_library(sensors::lib_imx ALIAS lib_imx_general) +add_library(fwk_camera::sensors ALIAS ${LIB_NAME}) diff --git a/sensors/api/SensorBase.hpp b/sensors/api/SensorBase.hpp new file mode 100644 index 00000000..539396e7 --- /dev/null +++ b/sensors/api/SensorBase.hpp @@ -0,0 +1,159 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus + +extern "C" { +# include "i2c.h" +} + +namespace sensor { + +typedef struct +{ + uint16_t reg_addr; + uint16_t reg_val; +} i2c_line_t; + +typedef struct +{ + i2c_line_t * table; + size_t num_lines; +} i2c_table_t; + +typedef struct +{ + uint8_t device_addr; + uint16_t speed; + port_t p_scl; + port_t p_sda; + i2c_master_t * i2c_ctx_ptr; +} i2c_config_t; + +#define GET_NUM_LINES(regs_arr) (sizeof(regs_arr) / sizeof(i2c_line_t)) +#define GET_TABLE(regs_arr) (i2c_table_t){regs_arr, GET_NUM_LINES(regs_arr)} + +/** + * @brief Base class for implementing Sensor control from + */ +class SensorBase { + + private: + + /** + * @brief I2C master config to be used for the sensor control + */ + i2c_config_t i2c_cfg; + + /** + * @brief Intialise I2C master interface + */ + void i2c_init(); + + protected: + + /** + * @brief Read from a 16-bit register + * + * @param reg Register to read from + * @returns Register value + */ + uint16_t i2c_read(uint16_t reg); + + /** + * @brief Write to a single register + * + * @param line I2C line config to write + * @returns 0 if succeeded, -1 if failed + */ + int i2c_write_line(i2c_line_t line); + + /** + * @brief Write to a single register + * + * @param reg Register value to write to + * @param val Value to write + * @returns 0 if succeeded, -1 if failed + */ + int i2c_write_line(uint16_t reg, uint8_t val); + + /** + * @brief Write a table of register values + * + * @param table I2C table config to write + * @returns 0 if succeeded, -1 if failed + */ + int i2c_write_table(i2c_table_t table); + + public: + + /** + * @brief Construct new `SensorBase` + * + * @param _conf I2C master config to use for the sensor control + * @note This will initialize I2C interface + */ + SensorBase(i2c_config_t _conf); + + /** + * @brief Initialize sensor + * + * @note This is a virtual function, and will have to be implemented in the derived class + * @note Can't make it a pure virtual method as it adds 18 kB of memory in the current (15.2.1) XTC version + */ + virtual int initialize(); + + /** + * @brief Start data stream + * + * @note This is a virtual function, and will have to be implemented in the derived class + * @note Can't make it a pure virtual method as it adds 18 kB of memory in the current (15.2.1) XTC version + */ + virtual int stream_start(); + + /** + * @brief Stop data stream + * + * @note This is a virtual function, and will have to be implemented in the derived class + * @note Can't make it a pure virtual method as it adds 18 kB of memory in the current (15.2.1) XTC version + */ + virtual int stream_stop(); + + /** + * @brief Set sensor exposure + * + * @param dBGain Exposure gain in dB, can enable different types of camera gain + * @note This is a virtual function, and will have to be implemented in the derived class + * @note Can't make it a pure virtual method as it adds 18 kB of memory in the current (15.2.1) XTC version + */ + virtual int set_exposure(uint32_t dBGain); + + /** + * @brief Set sensor resolution, binning mode, and RAW format + * + * @note This is a virtual function, and will have to be implemented in the derived class + * @note Can't make it a pure virtual method as it adds 18 kB of memory in the current (15.2.1) XTC version + */ + virtual int configure(); + + /** + * @brief Control thread intry, will initialise and configure sensor inside + * + * @param c_control Control channel + * @note This is a virtual function, and will have to be implemented in the derived class + * @note Can't make it a pure virtual method as it adds 18 kB of memory in the current (15.2.1) XTC version + */ + virtual void control(chanend_t c_control); + +}; // SensorBase + +} // sensor + +#endif // __cplusplus diff --git a/sensors/api/sensor.h b/sensors/api/sensor.h index 59726203..8a1ac84e 100644 --- a/sensors/api/sensor.h +++ b/sensors/api/sensor.h @@ -1,121 +1,188 @@ -// Sensor.h settings needed for custom sensor configuration - -#pragma once - -#define XSTR(x) STR(x) -#define STR(x) #x - -// ----- minimal comom definitions -#define ON 1 -#define OFF 0 - -#define MODE_VGA_RAW10 0 // 640x480 -#define MODE_VGA_RAW8 1 // 640x480 -#define MODE_UXGA_RAW8 2 // 1640x1232 -#define MODE_WQSXGA_RAW8 3 // 3280x2464 -#define MODE_1920_1080 4 - -#define MIPI_DT_RAW8 0x2A -#define MIPI_DT_RAW10 0x2B - -// -------------- Sensor abstraction layer. -------------- -// This is user defined -#define CONFIG_IMX219_SUPPORT ON -#define CONFIG_GC2145_SUPPORT OFF -#define CONFIG_MODE MODE_VGA_RAW8 -#define MIPI_PKT_BUFFER_COUNT 4 -#define CROP_ENABLED OFF - -// FPS settings -// allowed values: [FPS_13, FPS_24, FPS_30, FPS_53, FPS_76] -#define FPS_13 - -// Inlcude custom libraries -#if CONFIG_IMX219_SUPPORT - #include "imx219.h" -#endif - -#if CONFIG_GC2145_SUPPORT - #include "gc2145.h" -#endif - - -// Modes definition -#if (CONFIG_MODE == MODE_VGA_RAW10) - #define EXPECTED_FORMAT MIPI_DT_RAW10 // RAW 10 bit data identifier - #define MIPI_IMAGE_WIDTH_PIXELS 640 // csi2 packed (stride 800) - #define MIPI_IMAGE_HEIGHT_PIXELS 480 - -#elif (CONFIG_MODE == MODE_VGA_RAW8) - #define EXPECTED_FORMAT MIPI_DT_RAW8 // RAW 8 bit data identifier - #define MIPI_IMAGE_WIDTH_PIXELS 640 - #define MIPI_IMAGE_HEIGHT_PIXELS 480 - -#elif (CONFIG_MODE == MODE_UXGA_RAW8) - #define EXPECTED_FORMAT MIPI_DT_RAW8 // RAW 8 bit data identifier - #define MIPI_IMAGE_WIDTH_PIXELS 1640 // csi2 packed (stride 800) - #define MIPI_IMAGE_HEIGHT_PIXELS 1232 - -#elif (CONFIG_MODE == MODE_WQSXGA_RAW8) - #define EXPECTED_FORMAT MIPI_DT_RAW8 // RAW 8 bit data identifier - #define MIPI_IMAGE_WIDTH_PIXELS 3280 // csi2 packed (stride 800) - #define MIPI_IMAGE_HEIGHT_PIXELS 2464 - -#elif (CONFIG_MODE == MODE_1920_1080) - #define EXPECTED_FORMAT MIPI_DT_RAW8 // RAW 8 bit data identifier - #define MIPI_IMAGE_WIDTH_PIXELS 1920 // csi2 packed (stride 800) - #define MIPI_IMAGE_HEIGHT_PIXELS 1080 - -#endif - - -// Cropping mode -#if (CROP_ENABLED) - #define CROP_WIDTH_PIXELS 320 - #define CROP_HEIGHT_PIXELS 240 - #define X_START_CROP 0 - #define Y_START_CROP 0 - // crop checks - #if (X_START_CROP + CROP_WIDTH_PIXELS) > MIPI_IMAGE_WIDTH_PIXELS - #error Crop X dimensions must be inside bounds - #endif - // crop checks - #if (Y_START_CROP + CROP_HEIGHT_PIXELS) > MIPI_IMAGE_HEIGHT_PIXELS - #error Crop Y dimensions must be inside bounds - #endif - -#endif - - -// ----------------------- Settings dependant of each sensor library - -// Camera functions to be called from main program -#define camera_init(x) imx219_init(x) -#define camera_start(x) imx219_stream_start(x) -#define camera_configure(x) imx219_configure_mode(x) - - -// ------------------------------------------------------------------------------- -#if EXPECTED_FORMAT == MIPI_DT_RAW10 - #define MIPI_IMAGE_WIDTH_BYTES (((MIPI_IMAGE_WIDTH_PIXELS) >> 2) * 5) // by 5/4 - -#elif EXPECTED_FORMAT == MIPI_DT_RAW8 - #define MIPI_IMAGE_WIDTH_BYTES MIPI_IMAGE_WIDTH_PIXELS // same size -#else - #error Unknown format specified -#endif - -// Camera dependant (do not edit) -#define MIPI_LINE_WIDTH_BYTES MIPI_IMAGE_WIDTH_BYTES -#define MIPI_MAX_PKT_SIZE_BYTES ((MIPI_LINE_WIDTH_BYTES) + 4) -#define MIPI_TILE 1 - -// SRAM Image storage (do not edit) -//TODO check maximum storage size for the image -#define MAX_MEMORY_SIZE 500000 << 2 //becasue half needed is code - -#if MIPI_IMAGE_WIDTH_BYTES*MIPI_IMAGE_HEIGHT_PIXELS > MAX_MEMORY_SIZE - #warning "The image appears to be too large for the available internal RAM.!" -#endif - - +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +// Sensor.h settings needed for custom sensor configuration +#pragma once + +#define XSTR(x) STR(x) +#define STR(x) #x + +// ----- minimal commom definitions +#define DISABLED 0 +#define ENABLED 1 + +#define MODE_VGA_640x480 0x01 +#define MODE_UXGA_1640x1232 0x02 +#define MODE_WQSXGA_3280x2464 0x03 +#define MODE_FHD_1920x1080 0x04 +#define MODE_1280x960 0x05 + +typedef enum { + RES_640_480 = MODE_VGA_640x480, + RES_1280_960 = MODE_1280x960 +} resolution_t; + +#define _MIPI_DT_RAW8 0x2A +#define _MIPI_DT_RAW10 0x2B + +typedef enum { + FMT_RAW8 = _MIPI_DT_RAW8, + FMT_RAW10 = _MIPI_DT_RAW10 +} pixel_format_t; + +#define BIAS_DISABLED 0x00 // no demux +#define BIAS_ENABLED 0x80 // bias + +// -------------- Sensor abstraction layer. -------------- +// Camera support +#define CONFIG_IMX219_SUPPORT ENABLED +#define CONFIG_GC2145_SUPPORT DISABLED + +// Crop selection +#define CROP_ENABLED DISABLED +#define CONFIG_MODE MODE_VGA_640x480 + +// Mipi format and mode +#ifndef CONFIG_MIPI_FORMAT +#define CONFIG_MIPI_FORMAT _MIPI_DT_RAW8 +#endif +#define MIPI_PKT_BUFFER_COUNT 4 + +// Black level settings +#define SENSOR_BLACK_LEVEL 16 +// -------------------------------------------------------- + +// Modes configurations +#ifndef CONFIG_MODE +# error CONFIG_MODE has to be defined +#endif + +#if (CONFIG_MODE == MODE_VGA_640x480) +# define MIPI_IMAGE_WIDTH_PIXELS 640 // csi2 packed (stride 800) +# define MIPI_IMAGE_HEIGHT_PIXELS 480 + +#elif (CONFIG_MODE == MODE_UXGA_1640x1232) +# define MIPI_IMAGE_WIDTH_PIXELS 1640 // csi2 packed (stride 800) +# define MIPI_IMAGE_HEIGHT_PIXELS 1232 + +#elif (CONFIG_MODE == MODE_WQSXGA_3280x2464) +# define MIPI_IMAGE_WIDTH_PIXELS 3280 // csi2 packed (stride 800) +# define MIPI_IMAGE_HEIGHT_PIXELS 2464 + +#elif (CONFIG_MODE == MODE_FHD_1920x1080) +# define MIPI_IMAGE_WIDTH_PIXELS 1920 // csi2 packed (stride 800) +# define MIPI_IMAGE_HEIGHT_PIXELS 1080 + +#elif (CONFIG_MODE == MODE_1280x960) +# define MIPI_IMAGE_WIDTH_PIXELS 1280 +# define MIPI_IMAGE_HEIGHT_PIXELS 960 + +#else +# error Unknown configuration mode +#endif + +// Pixel format configurations +#ifndef CONFIG_MIPI_FORMAT +# error CONFIG_MIPI_FORMAT has to be specified +#else + #if (CONFIG_MIPI_FORMAT == _MIPI_DT_RAW10) + # define MIPI_IMAGE_WIDTH_BYTES (((MIPI_IMAGE_WIDTH_PIXELS) >> 2) * 5) // by 5/4 + + #elif (CONFIG_MIPI_FORMAT == _MIPI_DT_RAW8) + # define MIPI_IMAGE_WIDTH_BYTES MIPI_IMAGE_WIDTH_PIXELS // same size + + #else + # error CONFIG_MIPI_FORMAT not supported + #endif +#endif + +// Cropping configurations +#if (CROP_ENABLED) +# define CROP_WIDTH_PIXELS 320 +# define CROP_HEIGHT_PIXELS 240 +# define X_START_CROP 0 +# define Y_START_CROP 0 +// crop checks + #if (X_START_CROP + CROP_WIDTH_PIXELS) > MIPI_IMAGE_WIDTH_PIXELS + # error Crop X dimensions must be inside bounds + #endif + // crop checks + #if (Y_START_CROP + CROP_HEIGHT_PIXELS) > MIPI_IMAGE_HEIGHT_PIXELS + # error Crop Y dimensions must be inside bounds + #endif +#endif + +// ----------------------- Settings dependant of each sensor library + +// Camera dependant (do not edit) +#define MIPI_MAX_PKT_SIZE_BYTES ((MIPI_IMAGE_WIDTH_BYTES) + 4) +#define MIPI_TILE 1 +#define EXPECTED_FORMAT CONFIG_MIPI_FORMAT //backward compatibility +#define MIPI_EXPECTED_FORMAT CONFIG_MIPI_FORMAT //backward compatibility + +// ---------------------------------------------------------------- +// ---------------------------------------------------------------- + +#define SENSOR_BIT_DEPTH (8) +#define SENSOR_RAW_IMAGE_WIDTH_PIXELS MIPI_IMAGE_WIDTH_PIXELS +#define SENSOR_RAW_IMAGE_HEIGHT_PIXELS MIPI_IMAGE_HEIGHT_PIXELS + +#if (CONFIG_MODE == MODE_VGA_640x480) +# define APP_DECIMATION_FACTOR (4) +#elif (CONFIG_MODE == MODE_1280x960) +# define APP_DECIMATION_FACTOR (8) +#else +# error "Given CONFIG_MODE is not currently suported" +#endif + +#define VPU_SIZE_16B (16) + +#define NOT_PADDED_WIDTH_PIXELS (SENSOR_RAW_IMAGE_WIDTH_PIXELS \ + / APP_DECIMATION_FACTOR) + +#define PADDED_WIDTH_PIXELS (((NOT_PADDED_WIDTH_PIXELS / VPU_SIZE_16B) + 1) \ + * VPU_SIZE_16B) + + +#if ((NOT_PADDED_WIDTH_PIXELS % VPU_SIZE_16B) != 0) +# define APP_IMAGE_WIDTH_PIXELS PADDED_WIDTH_PIXELS +#else +# define APP_IMAGE_WIDTH_PIXELS NOT_PADDED_WIDTH_PIXELS +#endif + +#define APP_IMAGE_HEIGHT_PIXELS (SENSOR_RAW_IMAGE_HEIGHT_PIXELS \ + / APP_DECIMATION_FACTOR) + +#define APP_IMAGE_CHANNEL_COUNT (3) + +#define CHAN_RED 0 +#define CHAN_GREEN 1 +#define CHAN_BLUE 2 + +// Number of bits to collapse channel cardinality (larger value results in fewer +// histogram bins) +#ifndef HIST_QUANT_BITS +#define HIST_QUANT_BITS (2) +#endif + +// Not every pixel of the image will be sampled. This is the distance between +// sampled values in a row. +#ifndef APP_HISTOGRAM_SAMPLE_STEP +#define APP_HISTOGRAM_SAMPLE_STEP (1) +#endif + +// For simplicity here +#define CH (APP_IMAGE_CHANNEL_COUNT) +#define H (APP_IMAGE_HEIGHT_PIXELS) +#define W (APP_IMAGE_WIDTH_PIXELS) + +#define H_RAW (MIPI_IMAGE_HEIGHT_PIXELS) +#define W_RAW (MIPI_IMAGE_WIDTH_BYTES) + +typedef enum { + SENSOR_INIT = 0, + SENSOR_CONFIG, + SENSOR_STREAM_START, + SENSOR_STREAM_STOP, + SENSOR_SET_EXPOSURE +} sensor_control_t; diff --git a/sensors/api/sensor_control.h b/sensors/api/sensor_control.h new file mode 100644 index 00000000..3ef3bea6 --- /dev/null +++ b/sensors/api/sensor_control.h @@ -0,0 +1,30 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include +#include + +// xcore +#include + +#include "sensor.h" + +#define I2C_DEV_ADDR 0x10 +#define I2C_DEV_SPEED 400 +#define PRINT_I2C_REG 0 +#define ENABLE_PRINT_SENSOR_CONTROL 0 +#define ENCODE(cmd, arg) (((uint32_t)(cmd) << 16) | (uint32_t)(arg & 0xFFFF)) +#define DECODE_CMD(value) ((uint16_t)((value) >> 16)) +#define DECODE_ARG(value) ((uint16_t)(value)) + +#if defined(__XC__) || defined(__cplusplus) +extern "C" { +#endif + +void sensor_control(chanend c_control); + +#if defined(__XC__) || defined(__cplusplus) +} +#endif diff --git a/sensors/gc2145.h b/sensors/gc2145.h deleted file mode 100644 index 9dc53ffe..00000000 --- a/sensors/gc2145.h +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include "i2c.h" - -#define GAIN_MIN_DB 0 -#define GAIN_MAX_DB 84 -#define GAIN_DEFAULT_DB 50 - -#define GC2145_I2C_ADDR (0x3C) - -#define GC2145MIPI_2Lane -#define GC2145MIPI_TEST_PATTERN_CHECKSUM 0x54ddf19b - -/* GC2145 supported geometry */ -#define GC2145_WIDTH (1600) -#define GC2145_HEIGHT (1200) - -int gc2145_stream_start(client interface i2c_master_if i2c); -int gc2145_stream_stop(client interface i2c_master_if i2c); -int gc2145_init(client interface i2c_master_if i2c); -int gc2145_set_gain_dB(client interface i2c_master_if i2c, - uint32_t dBGain); - - -typedef struct { - uint16_t addr; - uint16_t val; -} gc_settings_t; - -extern const -gc_settings_t chip_set_up[]; - -extern const -size_t chip_set_up_length; - -#define SLEEP 0xFFFF -#define TRSTUS 200 \ No newline at end of file diff --git a/sensors/gc2145.xc b/sensors/gc2145.xc deleted file mode 100644 index 12b2e3a6..00000000 --- a/sensors/gc2145.xc +++ /dev/null @@ -1,160 +0,0 @@ -#include -#include -#include -#include "i2c.h" -#include "gc2145.h" - -//#define SWIDTH 3296 -//#define SHEIGHT 2480 - -#define GC2145_I2C_ADDR (0x3C) - -#define GC2145MIPI_2Lane -#define GC2145MIPI_TEST_PATTERN_CHECKSUM 0x54ddf19b - -void exit(int); - -static -int i2c_write( - client interface i2c_master_if i2c, - int reg, - int value) -{ - i2c_regop_res_t result, res2; - result = i2c.write_reg(GC2145_I2C_ADDR, reg, value); - if (result != I2C_REGOP_SUCCESS) { - printf("Failed on address %02x value %02x\n", reg, value); - uint8_t val2; - val2 = i2c.read_reg(GC2145_I2C_ADDR, reg, res2); - printf("%02x %02x %02x %02x %d %d\n", GC2145_I2C_ADDR, reg, value, val2, res2, result); - } - return result != I2C_REGOP_SUCCESS ? (reg == 0xfe ? 0 : -1) : 0; -} - -static -int i2c_write_table( - client interface i2c_master_if i2c, - const gc_settings_t table[], - int N) -{ - int ret; - for(int i = 0; i < N; i++) { - uint32_t address = table[i].addr; - uint32_t value = table[i].val; - if (address == SLEEP) { - timer tmr; - int t; - tmr :> t; - tmr when timerafter(t + TRSTUS * 100) :> void; - } - if (address & 0x8000) { - address &= 0x7fff; - ret = i2c_write(i2c, address, value >> 8); - ret |= i2c_write(i2c, address+1, value & 0xff); - } else { - ret = i2c_write(i2c, address, value); - } - if (ret < 0) { - return ret; - } - } - for(int i = 240; i < 256; i++) { - } - return 0; -} - -static -uint8_t i2c_read( - client interface i2c_master_if i2c, - unsigned page, - uint8_t reg_addr) -{ - uint8_t val; - i2c_regop_res_t res; - res = i2c.write_reg(GC2145_I2C_ADDR, 0xFE, (page & 0x03)); - if(res) { printf("Register write error. Reg: 0xFE; Page: %u\n", page & 0x03); exit(1); } - val = i2c.read_reg(GC2145_I2C_ADDR, reg_addr, res); - if(res) { printf("Register read error. Reg: 0x%02X; Page: %u\n", (unsigned) reg_addr, page & 0x03); exit(1); } - res = i2c.write_reg(GC2145_I2C_ADDR, 0xFE, 0); - if(res) { printf("Register write error. Reg: 0xFE; Page: 0\n"); exit(1); } - return val; -} - -void gc2145_print_info( - client interface i2c_master_if i2c) -{ - i2c_regop_res_t result; - uint8_t value; - unsigned rewq; - -#define PRINT_REG(PAGE, REG_ADDR, TEXT) do { \ - value = i2c_read(i2c, PAGE, REG_ADDR); \ - printf("%s: 0x%02X (%u)\n", TEXT, (unsigned) value, (unsigned) value); \ - } while(0) - - #define PRINT_WIDE_REG(PAGE, REG_ADDR, TEXT) do { \ - value = i2c_read(i2c, PAGE, REG_ADDR); \ - rewq = ((unsigned)value) << 8; \ - value = i2c_read(i2c, PAGE, REG_ADDR+1); \ - rewq = (rewq | value); \ - printf("%s: 0x%04X (%u)\n", TEXT, rewq, rewq); \ - } while(0) - - PRINT_WIDE_REG(0, 0xF0, "Chip ID"); - PRINT_REG(0, 0xFB, "I2C Address"); - PRINT_REG(0, 0xF2, "Pad IO"); - PRINT_WIDE_REG(0, 0x03, "Exposure"); - PRINT_WIDE_REG(0, 0x05, "H Blanking"); - PRINT_WIDE_REG(0, 0x07, "V Blanking"); - PRINT_WIDE_REG(0, 0x0D, "Window Height"); - PRINT_WIDE_REG(0, 0x0F, "Window Width");; - PRINT_REG(0, 0x83, "Special effect"); - PRINT_REG(0, 0x84, "Output Format"); - PRINT_REG(0, 0x85, "Frame start num"); - PRINT_REG(0, 0x90, "Crop enable"); - PRINT_WIDE_REG(0, 0x91, "out_win.y[1]"); - PRINT_WIDE_REG(0, 0x93, "out_win.x[1]"); - PRINT_WIDE_REG(0, 0x95, "out_win.height"); - PRINT_WIDE_REG(0, 0x97, "out_win.width"); - -#undef PRINT_REG -} - - -int gc2145_stream_stop(client interface i2c_master_if i2c) { - int ret; - ret = i2c_write(i2c, 0xFE, 0x00); - ret = ret | i2c_write(i2c, 0xF2, 0x00); - return ret; -} - -int gc2145_stream_start(client interface i2c_master_if i2c) { - int ret; - ret = i2c_write(i2c, 0xFE, 0x00); - ret = ret | i2c_write(i2c, 0xF2, 0x0F); - return ret; -} - -int gc2145_init( - client interface i2c_master_if i2c) -{ - int ret; - printf("gc2145_stream_init()...\n"); - // gc2145_print_info(i2c); - - - ret = i2c_write_table(i2c, chip_set_up, chip_set_up_length); - if(ret) - printf("Failed to set all GC2145 registers.\n"); - - i2c_write(i2c, 0x84, 0x6); - - // i2c_write(i2c, 0xFE, 0xC0); - - gc2145_stream_stop(i2c); - - gc2145_print_info(i2c); - - return ret; -} - diff --git a/sensors/gc2145_setup.xc b/sensors/gc2145_setup.xc deleted file mode 100644 index 2fd29b77..00000000 --- a/sensors/gc2145_setup.xc +++ /dev/null @@ -1,856 +0,0 @@ -#include -#include -#include -#include "i2c.h" -#include "gc2145.h" - - -void exit(int); - -// astew: Some register names come from https://lwn.net/Articles/905641/ because they're not in the datasheet -// they will have the form e.g. 'GC2145_P0_REG_NAME' - -const -gc_settings_t chip_set_up[] = { - - {0xfe, 0xf0}, // "Reset related" (soft_reset|cm_reset|mipi_reset|CISCTL_restart_n) (page_select=0) - {0xfe, 0xf0}, - {0xfe, 0xf0}, - {0xfc, 0x06}, // "analog_pwc" (vpll_en|vpix_en) - {0xf6, 0x00}, // "Up_dn"/"Pwd_dn" (no pull) - {0xf7, 0x1d}, // "PLL_mode1" (serial_clk_double=1 | clk_double | NA | pll_en) //astew: is this right? I assume NA means.. not applicable? - {0xf8, 0x84}, // "PLL_mode2" (pll_dgdiv_en | divx4=4) - {0xfa, 0x00}, // "clk_div_mode" (0) - {0xf9, 0x8e}, // "cm_mode" (regf_clk_enable | isp_all_clk_enable | serial_clk_enable | re_lock_pll ) - {0xf2, 0x00}, // "pad_vb_hiz_mode"/"data_pad_io"/"sync_pad_io" ( 0 ) - - ///////////////////////////////////////////////// - ////////////////// "System Register" ////////////////////// - //////////////////////////////////////////////////// - {0xfe , 0x00}, // "Reset related" ( page_select=0 ) - {0x03 , 0x04}, // [with next line] - {0x04 , 0xe2}, // "Exposure[12:0]" ( 0x4e2 = 1250 ) - {0x09 , 0x00}, // [with next line] - {0x0a , 0x00}, // "buf_CISCTL_capt_row_start[10:0]" ( 0 ) - {0x0b , 0x00}, // [with next line] - {0x0c , 0x00}, // "buf_CISCTL_capt_col_start[10:0]" ( 0 ) - {0x0d , 0x04}, // [with next line] - {0x0e , 0xc0}, // "buf_CISCTL_capt_win_height" ( 1216 ) - {0x0f , 0x06}, // [with next line] - {0x10 , 0x52}, // "buf_CISCTL_capt_win_width" ( 1618 ) - {0x12 , 0x2e}, // 'GC2145_P0_SH_DELAY_LO' - {0x17 , 0x14}, // "Analog mode1" [sets reserved bits...(only)] - {0x18 , 0x22}, // "Analog mode2" [sets only reserved and "NA" bits] - {0x19 , 0x0e}, // "Analog mode3" [sets only reserved bits] - {0x1a , 0x01}, // ?? - {0x1b , 0x4b}, // ?? - {0x1c , 0x07}, // ?? - {0x1d , 0x10}, // ?? - {0x1e , 0x88}, // ?? - {0x1f , 0x78}, // ?? - {0x20 , 0x03}, // "Analog mode3" [no description] - {0x21 , 0x40}, // [not going to bother saying 'reg not in datasheet' anymore] - {0x22 , 0xa0}, // - {0x24 , 0x16}, // "Driver mode" ( pclk_drv = 16mA | drv_high_data = 12mA | sync_drv = 8mA | drv_low_data = 8mA ) - {0x25 , 0x01}, // ?? - {0x26 , 0x10}, // ?? - {0x2d , 0x60}, // ?? - {0x30 , 0x01}, // ?? - {0x31 , 0x90}, // ?? - {0x33 , 0x06}, // ?? - {0x34 , 0x01}, // ?? - ///////////////////////////////////////////////// - //////////////////ISP reg//////////////////// - ///////////////////////////////////////////////// - {0xfe , 0x00}, // select register page 0 - {0x80 , 0x7f}, // "Block_enable1" - {0x81 , 0x26}, // "Block_enable2" - {0x82 , 0xfa}, // "Block enable" - {0x83 , 0x00}, // "Special effect" - // {0x84 , 0x03}, // "Output format" YUV422 - {0x84 , 0x06}, // "Output format" RGB565 (doesn't actually change output format ofr some reason) - {0x86 , 0x02}, // "Sync mode" - {0x88 , 0x03}, // "module_gating" - {0x89 , 0x03}, // "bypass_mode" - // {0x89 , 0x0B}, // "bypass_mode" - {0x85 , 0x08}, // "Frame start" - {0x8a , 0x00}, // ?? - {0x8b , 0x00}, // ?? - {0xb0 , 0x55}, // "Global_gain" ( 85 ) [datasheet says value is "float 4.4".. UQ4.4?] - {0xc3 , 0x00}, // ?? - {0xc4 , 0x80}, // ?? - {0xc5 , 0x90}, // ?? - {0xc6 , 0x3b}, // ?? - {0xc7 , 0x46}, // ?? - {0xec , 0x06}, // "C_big_win_x0" ( 6 ) // astew: not sure what these actually mean.. - {0xed , 0x04}, // "C_big_win_y0" ( 4 ) - {0xee , 0x60}, // "C_big_win_x1" ( 96 ) - {0xef , 0x90}, // "C_big_win_y1" ( 144 ) - {0xb6 , 0x01}, // ?? - {0x90 , 0x01}, // "Crop enable" (crop out Window mode) - {0x91 , 0x00}, // [with next] - {0x92 , 0x00}, // "out_win_y1[10:0]" ( 0 ) - {0x93 , 0x00}, // [with_next] - {0x94 , 0x00}, // "out_win_x1[10:0]" (0) - {0x95 , 0x04}, // [with next] - {0x96 , 0xb0}, // "out_win_height[10:0]" ( 1200 ) - {0x97 , 0x06}, // [with next] - {0x98 , 0x40}, // "out_win_width[10:0]" ( 1600 ) - ///////////////////////////////////////// - /////////// BLK //////////////////////// - ///////////////////////////////////////// - {0xfe , 0x00}, // select register page 0 - {0x40 , 0x42}, // "Blk_mode1" - {0x41 , 0x00}, // ?? - {0x43 , 0x5b}, // "BLK_fame_cnt_TH" - {0x5e , 0x00}, // "current_G1_offset_odd_ratio" - {0x5f , 0x00}, // "current_G1_offset_even_ratio" - {0x60 , 0x00}, // "current_R1_offset_odd_ratio" - {0x61 , 0x00}, // "current_R1_offset_even_ratio" - {0x62 , 0x00}, // "current_B1_offset_odd_ratio" - {0x63 , 0x00}, // "current_B1_offset_even_ratio" - {0x64 , 0x00}, // "current_G2_offset_odd_ratio" - {0x65 , 0x00}, // "current_G2_offset_even_ratio" - {0x66 , 0x20}, // "Dark_current_G1_ratio" - {0x67 , 0x20}, // "Dark_current_R_ratio" - {0x68 , 0x20}, // "Dark_current_B_ratio" - {0x69 , 0x20}, // "Dark_current_G2_ratio" - {0x76 , 0x00}, // ?? - {0x6a , 0x08}, // "manual_G1_odd_offset" - {0x6b , 0x08}, // "manual_G1_even_offset" - {0x6c , 0x08}, // "manual_R1_odd_offset" - {0x6d , 0x08}, // "manual_R1_even_offset" - {0x6e , 0x08}, // "manual_B2_odd_offset" - {0x6f , 0x08}, // "manual_B2_even_offset" - {0x70 , 0x08}, // "manual_G2_odd_offset" - {0x71 , 0x08}, // "manual_G2_even_offset" - {0x76 , 0x00}, // still ??, as above - {0x72 , 0xf0}, // "BLK_DD_th"/"BLK_various_th" - {0x7e , 0x3c}, // ?? - {0x7f , 0x00}, // ?? - {0xfe , 0x02}, // select register page 2 - {0x48 , 0x15}, // ?? - {0x49 , 0x00}, // ?? - {0x4b , 0x0b}, // ?? - {0xfe , 0x00}, // select register page 0 - //////////////////////////////////////// - /////////// AEC //////////////////////// - //////////////////////////////////////// - {0xfe , 0x01}, // select register page 1 - {0x01 , 0x04}, // "AEC_x1" - {0x02 , 0xc0}, // "AEC_x2" - {0x03 , 0x04}, // "AEC_y1" - {0x04 , 0x90}, // "AEC_y2" - {0x05 , 0x30}, // "AEC_center_x1" - {0x06 , 0x90}, // "AEC_center_x2" - {0x07 , 0x30}, // "AEC_center_y1" - {0x08 , 0x80}, // "AEC_center_y2" - {0x09 , 0x00}, // ?? - {0x0a , 0x82}, // "AEC_mode1" - {0x0b , 0x11}, // "AEC_mode2" - {0x0c , 0x10}, // "AEC_mode3" - {0x11 , 0x10}, // ?? - {0x13 , 0x7b}, // "AEC_target_Y" - {0x17 , 0x00}, // ?? - {0x1c , 0x11}, // ?? - {0x1e , 0x61}, // ?? - {0x1f , 0x35}, // "AEC_max_pre_dg_gain" - {0x20 , 0x40}, // "AEC_max_post_dg_gain" - {0x22 , 0x40}, // ?? - {0x23 , 0x20}, // ?? - {0xfe , 0x02}, // select register page 2 - {0x0f , 0x04}, // ?? - {0xfe , 0x01}, // select register page 1 - {0x12 , 0x35}, // ?? - {0x15 , 0xb0}, // "target_Y_limit_from_histogram" - {0x10 , 0x31}, // ?? - {0x3e , 0x28}, // ?? - {0x3f , 0xb0}, // ?? - {0x40 , 0x90}, // ?? - {0x41 , 0x0f}, // ?? - - ///////////////////////////// - //////// INTPEE ///////////// - ///////////////////////////// - {0xfe , 0x02}, // select register page 2 - {0x90 , 0x6c}, // "EEINTP_mode1" - {0x91 , 0x03}, // "EEINTP_mode2" - {0x92 , 0xcb}, // "direction_TH1" - {0x94 , 0x33}, // "diff_HV_mode" - {0x95 , 0x84}, // "direction_diff_TH_mode" - {0x97 , 0x65}, // "Edge1 effect"/"Edge2 effect" - {0xa2 , 0x11}, // ?? - {0xfe , 0x00}, // select register page 0 - ///////////////////////////// - //////// DNDD/////////////// - ///////////////////////////// - {0xfe , 0x02}, // select register page 2 - {0x80 , 0xc1}, // ?? - {0x81 , 0x08}, // ?? - {0x82 , 0x05}, // ?? - {0x83 , 0x08}, // ?? - {0x84 , 0x0a}, // "DD_dark_th" - {0x86 , 0xf0}, // ?? - {0x87 , 0x50}, // ?? - {0x88 , 0x15}, // ?? - {0x89 , 0xb0}, // "ASDE_low_luma_value_DD_th2" - {0x8a , 0x30}, // "ASDE_low_luma_value_DD_th3" - {0x8b , 0x10}, // "ASDE_low_luma_value_DD_th4" - ///////////////////////////////////////// - /////////// ASDE //////////////////////// - ///////////////////////////////////////// - {0xfe , 0x01}, // select register page 1 - {0x21 , 0x04}, // ?? - {0xfe , 0x02}, // select register page 2 - {0xa3 , 0x50}, // ?? - {0xa4 , 0x20}, // ?? - {0xa5 , 0x40}, // ?? - {0xa6 , 0x80}, // ?? - {0xab , 0x40}, // ?? - {0xae , 0x0c}, // ?? - {0xb3 , 0x46}, // ?? - {0xb4 , 0x64}, // ?? - {0xb6 , 0x38}, // ?? - {0xb7 , 0x01}, // ?? - {0xb9 , 0x2b}, // ?? - {0x3c , 0x04}, // ?? - {0x3d , 0x15}, // ?? - {0x4b , 0x06}, // ?? - {0x4c , 0x20}, // ?? - {0xfe , 0x00}, // select register page 0 - ///////////////////////////////////////// - /////////// GAMMA //////////////////////// - ///////////////////////////////////////// - - ///////////////////gamma1//////////////////// - {0xfe , 0x02}, // select register page 2 - {0x10 , 0x09}, // "Gamma_out1" - {0x11 , 0x0d}, // "Gamma_out2" - {0x12 , 0x13}, // "Gamma_out3" - {0x13 , 0x19}, // "Gamma_out4" - {0x14 , 0x27}, // "Gamma_out5" - {0x15 , 0x37}, // "Gamma_out6" - {0x16 , 0x45}, // "Gamma_out7" - {0x17 , 0x53}, // "Gamma_out8" - {0x18 , 0x69}, // "Gamma_out9" - {0x19 , 0x7d}, // "Gamma_out10" - {0x1a , 0x8f}, // "Gamma_out11" - {0x1b , 0x9d}, // "Gamma_out12" - {0x1c , 0xa9}, // "Gamma_out13" - {0x1d , 0xbd}, // "Gamma_out14" - {0x1e , 0xcd}, // "Gamma_out15" - {0x1f , 0xd9}, // "Gamma_out16" - {0x20 , 0xe3}, // "Gamma_out17" - {0x21 , 0xea}, // "Gamma_out18" - {0x22 , 0xef}, // "Gamma_out19" - {0x23 , 0xf5}, // "Gamma_out20" - {0x24 , 0xf9}, // "Gamma_out21" - {0x25 , 0xff}, // "Gamma_out22" - {0xfe , 0x00}, // select register page 0 - {0xc6 , 0x20}, // ?? - {0xc7 , 0x2b}, // ?? - ///////////////////gamma2//////////////////// - {0xfe , 0x02}, // select register page 2 - {0x26 , 0x0f}, // ?? - {0x27 , 0x14}, // ?? - {0x28 , 0x19}, // ?? - {0x29 , 0x1e}, // ?? - {0x2a , 0x27}, // ?? - {0x2b , 0x33}, // ?? - {0x2c , 0x3b}, // ?? - {0x2d , 0x45}, // ?? - {0x2e , 0x59}, // ?? - {0x2f , 0x69}, // ?? - {0x30 , 0x7c}, // ?? - {0x31 , 0x89}, // ?? - {0x32 , 0x98}, // ?? - {0x33 , 0xae}, // ?? - {0x34 , 0xc0}, // ?? - {0x35 , 0xcf}, // ?? - {0x36 , 0xda}, // ?? - {0x37 , 0xe2}, // ?? - {0x38 , 0xe9}, // ?? - {0x39 , 0xf3}, // ?? - {0x3a , 0xf9}, // ?? - {0x3b , 0xff}, // ?? - /////////////////////////////////////////////// - ///////////YCP /////////////////////// - /////////////////////////////////////////////// - {0xfe , 0x02}, // select register page 2 - {0xd1 , 0x32}, // "saturation_Cb" - {0xd2 , 0x32}, // "saturation_Cr" - {0xd3 , 0x40}, // "luma_contrast" - {0xd6 , 0xf0}, // "skin_Cb_center" - {0xd7 , 0x10}, // "skin_Cr_center" - {0xd8 , 0xda}, // ?? - {0xdd , 0x14}, // ?? - {0xde , 0x86}, // ?? - {0xed , 0x80}, // "C_big_win_y0" - {0xee , 0x00}, // "C_big_win_x1" - {0xef , 0x3f}, // "C_big_win_y1" - {0xd8 , 0xd8}, // ?? - ///////////////////abs///////////////// - {0xfe , 0x01}, // select register page 1 - {0x9f , 0x40}, // ?? - ///////////////////////////////////////////// - //////////////////////// LSC /////////////// - ////////////////////////////////////////// - {0xfe , 0x01}, // select register page 1 - {0xc2 , 0x14}, // LSC_up_red_b2 - {0xc3 , 0x0d}, // LSC_up_green_b2 - {0xc4 , 0x0c}, // LSC_up_blue_b2 - {0xc8 , 0x15}, // LSC_down_red_b2 - {0xc9 , 0x0d}, // LSC_down_green_b2 - {0xca , 0x0a}, // LSC_down_blue_b2 - {0xbc , 0x24}, // LSC_left_red_b2 - {0xbd , 0x10}, // LSC_left_green_b2 - {0xbe , 0x0b}, // LSC_left_blue_b2 - {0xb6 , 0x25}, // LSC_right_red_b2 - {0xb7 , 0x16}, // LSC_right_green_b2 - {0xb8 , 0x15}, // LSC_right_blue_b2 - {0xc5 , 0x00}, // LSC_up_red_b4 - {0xc6 , 0x00}, // LSC_up_green_b4 - {0xc7 , 0x00}, // LSC_up_blue_b4 - {0xcb , 0x00}, // LSC_down_red_b4 - {0xcc , 0x00}, // - {0xcd , 0x00}, // - {0xbf , 0x07}, // - {0xc0 , 0x00}, // - {0xc1 , 0x00}, // - {0xb9 , 0x00}, // - {0xba , 0x00}, // - {0xbb , 0x00}, // - {0xaa , 0x01}, // - {0xab , 0x01}, // - {0xac , 0x00}, // - {0xad , 0x05}, // - {0xae , 0x06}, // - {0xaf , 0x0e}, // - {0xb0 , 0x0b}, // - {0xb1 , 0x07}, // - {0xb2 , 0x06}, // - {0xb3 , 0x17}, // - {0xb4 , 0x0e}, // - {0xb5 , 0x0e}, // - {0xd0 , 0x09}, // - {0xd1 , 0x00}, // - {0xd2 , 0x00}, // - {0xd6 , 0x08}, // - {0xd7 , 0x00}, // - {0xd8 , 0x00}, // - {0xd9 , 0x00}, // - {0xda , 0x00}, // - {0xdb , 0x00}, // - {0xd3 , 0x0a}, // - {0xd4 , 0x00}, // - {0xd5 , 0x00}, // - {0xa4 , 0x00}, // - {0xa5 , 0x00}, // - {0xa6 , 0x77}, // - {0xa7 , 0x77}, // - {0xa8 , 0x77}, // - {0xa9 , 0x77}, // - {0xa1 , 0x80}, // - {0xa2 , 0x80}, // - - {0xfe , 0x01}, // select register page 1 - {0xdf , 0x0d}, - {0xdc , 0x25}, - {0xdd , 0x30}, - {0xe0 , 0x77}, - {0xe1 , 0x80}, - {0xe2 , 0x77}, - {0xe3 , 0x90}, - {0xe6 , 0x90}, - {0xe7 , 0xa0}, - {0xe8 , 0x90}, - {0xe9 , 0xa0}, - {0xfe , 0x00}, // select register page 0 - /////////////////////////////////////////////// - /////////// AWB//////////////////////// - /////////////////////////////////////////////// - {0xfe , 0x01}, // select register page 1 - {0x4f , 0x00}, - {0x4f , 0x00}, - {0x4b , 0x01}, - {0x4f , 0x00}, - - {0x4c , 0x01}, // D75 - {0x4d , 0x71}, - {0x4e , 0x01}, - {0x4c , 0x01}, - {0x4d , 0x91}, - {0x4e , 0x01}, - {0x4c , 0x01}, - {0x4d , 0x70}, - {0x4e , 0x01}, - - {0x4c , 0x01}, // D65 - {0x4d , 0x90}, - {0x4e , 0x02}, - - - {0x4c , 0x01}, - {0x4d , 0xb0}, - {0x4e , 0x02}, - {0x4c , 0x01}, - {0x4d , 0x8f}, - {0x4e , 0x02}, - {0x4c , 0x01}, - {0x4d , 0x6f}, - {0x4e , 0x02}, - {0x4c , 0x01}, - {0x4d , 0xaf}, - {0x4e , 0x02}, - - {0x4c , 0x01}, - {0x4d , 0xd0}, - {0x4e , 0x02}, - {0x4c , 0x01}, - {0x4d , 0xf0}, - {0x4e , 0x02}, - {0x4c , 0x01}, - {0x4d , 0xcf}, - {0x4e , 0x02}, - {0x4c , 0x01}, - {0x4d , 0xef}, - {0x4e , 0x02}, - - {0x4c , 0x01},//D50 - {0x4d , 0x6e}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0x8e}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0xae}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0xce}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0x4d}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0x6d}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0x8d}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0xad}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0xcd}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0x4c}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0x6c}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0x8c}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0xac}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0xcc}, - {0x4e , 0x03}, - - {0x4c , 0x01}, - {0x4d , 0xcb}, - {0x4e , 0x03}, - - {0x4c , 0x01}, - {0x4d , 0x4b}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0x6b}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0x8b}, - {0x4e , 0x03}, - {0x4c , 0x01}, - {0x4d , 0xab}, - {0x4e , 0x03}, - - {0x4c , 0x01},//CWF - {0x4d , 0x8a}, - {0x4e , 0x04}, - {0x4c , 0x01}, - {0x4d , 0xaa}, - {0x4e , 0x04}, - {0x4c , 0x01}, - {0x4d , 0xca}, - {0x4e , 0x04}, - {0x4c , 0x01}, - {0x4d , 0xca}, - {0x4e , 0x04}, - {0x4c , 0x01}, - {0x4d , 0xc9}, - {0x4e , 0x04}, - {0x4c , 0x01}, - {0x4d , 0x8a}, - {0x4e , 0x04}, - {0x4c , 0x01}, - {0x4d , 0x89}, - {0x4e , 0x04}, - {0x4c , 0x01}, - {0x4d , 0xa9}, - {0x4e , 0x04}, - - - - {0x4c , 0x02},//tl84 - {0x4d , 0x0b}, - {0x4e , 0x05}, - {0x4c , 0x02}, - {0x4d , 0x0a}, - {0x4e , 0x05}, - - {0x4c , 0x01}, - {0x4d , 0xeb}, - {0x4e , 0x05}, - - {0x4c , 0x01}, - {0x4d , 0xea}, - {0x4e , 0x05}, - - {0x4c , 0x02}, - {0x4d , 0x09}, - {0x4e , 0x05}, - {0x4c , 0x02}, - {0x4d , 0x29}, - {0x4e , 0x05}, - - {0x4c , 0x02}, - {0x4d , 0x2a}, - {0x4e , 0x05}, - - {0x4c , 0x02}, - {0x4d , 0x4a}, - {0x4e , 0x05}, - - //{0x4c , 0x02}, //A - //{0x4d , 0x6a}, - //{0x4e , 0x06}, - - {0x4c , 0x02}, - {0x4d , 0x8a}, - {0x4e , 0x06}, - - {0x4c , 0x02}, - {0x4d , 0x49}, - {0x4e , 0x06}, - {0x4c , 0x02}, - {0x4d , 0x69}, - {0x4e , 0x06}, - {0x4c , 0x02}, - {0x4d , 0x89}, - {0x4e , 0x06}, - {0x4c , 0x02}, - {0x4d , 0xa9}, - {0x4e , 0x06}, - - {0x4c , 0x02}, - {0x4d , 0x48}, - {0x4e , 0x06}, - {0x4c , 0x02}, - {0x4d , 0x68}, - {0x4e , 0x06}, - {0x4c , 0x02}, - {0x4d , 0x69}, - {0x4e , 0x06}, - - {0x4c , 0x02},//H - {0x4d , 0xca}, - {0x4e , 0x07}, - {0x4c , 0x02}, - {0x4d , 0xc9}, - {0x4e , 0x07}, - {0x4c , 0x02}, - {0x4d , 0xe9}, - {0x4e , 0x07}, - {0x4c , 0x03}, - {0x4d , 0x09}, - {0x4e , 0x07}, - {0x4c , 0x02}, - {0x4d , 0xc8}, - {0x4e , 0x07}, - {0x4c , 0x02}, - {0x4d , 0xe8}, - {0x4e , 0x07}, - {0x4c , 0x02}, - {0x4d , 0xa7}, - {0x4e , 0x07}, - {0x4c , 0x02}, - {0x4d , 0xc7}, - {0x4e , 0x07}, - {0x4c , 0x02}, - {0x4d , 0xe7}, - {0x4e , 0x07}, - {0x4c , 0x03}, - {0x4d , 0x07}, - {0x4e , 0x07}, - - {0x4f , 0x01}, - {0x50 , 0x80}, - {0x51 , 0xa8}, - {0x52 , 0x47}, - {0x53 , 0x38}, - {0x54 , 0xc7}, - {0x56 , 0x0e}, - {0x58 , 0x08}, - {0x5b , 0x00}, - {0x5c , 0x74}, - {0x5d , 0x8b}, - {0x61 , 0xdb}, - {0x62 , 0xb8}, - {0x63 , 0x86}, - {0x64 , 0xc0}, - {0x65 , 0x04}, - - {0x67 , 0xa8}, - {0x68 , 0xb0}, - {0x69 , 0x00}, - {0x6a , 0xa8}, - {0x6b , 0xb0}, - {0x6c , 0xaf}, - {0x6d , 0x8b}, - {0x6e , 0x50}, - {0x6f , 0x18}, - {0x73 , 0xf0}, - {0x70 , 0x0d}, - {0x71 , 0x60}, - {0x72 , 0x80}, - {0x74 , 0x01}, - {0x75 , 0x01}, - {0x7f , 0x0c}, - {0x76 , 0x70}, - {0x77 , 0x58}, - {0x78 , 0xa0}, - {0x79 , 0x5e}, - {0x7a , 0x54}, - {0x7b , 0x58}, - {0xfe , 0x00}, // select register page 0 - ////////////////////////////////////////// - ///////////CC//////////////////////// - ////////////////////////////////////////// - {0xfe , 0x02}, // select register page 2 - {0xc0 , 0x01}, - {0xc1 , 0x44}, - {0xc2 , 0xfd}, - {0xc3 , 0x04}, - {0xc4 , 0xf0}, - {0xc5 , 0x48}, - {0xc6 , 0xfd}, - {0xc7 , 0x46}, - {0xc8 , 0xfd}, - {0xc9 , 0x02}, - {0xca , 0xe0}, - {0xcb , 0x45}, - {0xcc , 0xec}, - {0xcd , 0x48}, - {0xce , 0xf0}, - {0xcf , 0xf0}, - {0xe3 , 0x0c}, - {0xe4 , 0x4b}, - {0xe5 , 0xe0}, - ////////////////////////////////////////// - ///////////ABS //////////////////// - ////////////////////////////////////////// - {0xfe , 0x01}, // select register page 1 - {0x9f , 0x40}, - {0xfe , 0x00}, // select register page 0 - ////////////////////////////////////// - /////////// OUTPUT //////////////// - ////////////////////////////////////// - {0xfe, 0x00}, - {0xf2, 0x00}, - - //////////////frame rate 50Hz///////// - {0xfe , 0x00}, // select register page 0 - {0x05 , 0x01}, - {0x06 , 0x56}, - {0x07 , 0x00}, - {0x08 , 0x32}, - {0xfe , 0x01}, // select register page 1 - {0x25 , 0x00}, - {0x26 , 0xfa}, - {0x27 , 0x04}, - {0x28 , 0xe2}, //20fps - {0x29 , 0x06}, - {0x2a , 0xd6}, //14fps - {0x2b , 0x07}, - {0x2c , 0xd0}, //12fps - {0x2d , 0x0b}, - {0x2e , 0xb8}, //8fps - {0xfe , 0x00}, - - ///////////////dark sun//////////////////// - {0xfe , 0x02}, // select register page 2 - {0x40 , 0xbf}, - {0x46 , 0xcf}, - {0xfe , 0x00}, - ///////////////////////////////////////////////////// - ////////////////////// MIPI ///////////////////// - ///////////////////////////////////////////////////// - {0xfe, 0x03}, // select register page 3 - {0x02, 0x22}, // "DPHY_analog_mode2" (lane0_diff=2; clk_diff=2) - {0x03, 0x10}, // "DPHY_analog_mode3" (clk_delay=1) // 0x12 20140821 - {0x04, 0x10}, // "FIFO_prog_full_level[7:0]" (0x10) // 0x01 - {0x05, 0x00}, // "FIFO_prog_full_level[11:8]" (0x00) - {0x06, 0x88}, // "FIFO_mode" (MIPI_CLK_MODULE | 'read gate') - #ifdef GC2145MIPI_2Lane - {0x01, 0x87}, // "DPHY_analog_mode1" ('clk lane_p2s_sel' | phy_lane1_en | phy_lane0_en | phy_clk_en) - {0x10, 0x95}, // "BUF_CSI2_mode" (lane_enable|MIPI_enable|RAW8|double_lane) - // {0x10, 0x91}, // "BUF_CSI2_mode" (lane_enable|MIPI_enable|double_lane) - #else - {0x01, 0x83}, - {0x10, 0x94}, - #endif - {0x11, 0x1e}, // "LDI_set" - {0x12, 0x80}, // "LWC_set[7:0]" - {0x13, 0x0c}, // "LWC_set[15:8]" - {0x15, 0x10}, // "DPHY_mode" - {0x17, 0xf0}, // "fifo_gate_mode"/"MIPI_wdiv_set" - - {0x21, 0x10}, // "T_LPX_set" - - - {0x22, 0x04}, // "T_CLK_HS_PREPARE_set" - {0x23, 0x10}, // "T_CLK_zero_set" - {0x24, 0x10}, // "T_CLK_PRE_set" - {0x25, 0x10}, // "T_CLK_POST_set" - {0x26, 0x05}, // "T_CLK_TRAIL_set" - {0x29, 0x03}, // "T_HS_PREPARE_set" - {0x2a, 0x0a}, // "T_HS_Zero_set" - {0x2b, 0x06}, // "T_HS_TRAIL_set" - {0xfe, 0x00}, // select register page 0 - - }; - -const size_t chip_set_up_length = sizeof(chip_set_up) / sizeof(chip_set_up[0]); - - -/* -#define INTEGRATION_TIMES 41 -#define ANALOGUE_GAINS 20 -#define DIGITAL_GAINS 25 - -static uint16_t gain_integration_times[INTEGRATION_TIMES] = { - 0x00a, 0x00b, 0x00c, 0x00e, 0x010, 0x012, 0x014, 0x016, 0x019, - 0x01c, 0x020, 0x024, 0x028, 0x02d, 0x033, 0x039, 0x040, 0x048, - 0x051, 0x05b, 0x066, 0x072, 0x080, 0x090, 0x0a2, 0x0b6, 0x0cc, - 0x0e5, 0x101, 0x120, 0x143, 0x16b, 0x197, 0x1c9, 0x201, 0x23f, - 0x286, 0x2d4, 0x32d, 0x390, 0x400, -}; - -static uint8_t gain_analogue_gains[ANALOGUE_GAINS+1] = { - 0, 28, 53, 75, 95, 112, 128, 142, 155, 166, 175, 184, - 192, 199, 205, 211, 215, 220, 224, 227, 231, -}; - -static uint16_t gain_digital_gains[DIGITAL_GAINS+1] = { - 0x0100, 0x011f, 0x0142, 0x0169, 0x0195, 0x01c7, 0x01fe, 0x023d, - 0x0283, 0x02d1, 0x0329, 0x038c, 0x03fb, 0x0477, 0x0503, 0x059f, - 0x064f, 0x0714, 0x07f1, 0x08e9, 0x0a00, 0x0b38, 0x0c96, 0x0e20, - 0x0fd9, -}; - -int gc0310_set_gain_dB(client interface i2c_master_if i2c, - uint32_t dBGain) { - return 0; - uint32_t time, again, dgain; - if (dBGain > GAIN_MAX_DB) { - dBGain = GAIN_MAX_DB; - } - if (dBGain < INTEGRATION_TIMES) { - time = gain_integration_times[dBGain]; - again = gain_analogue_gains[0]; - dgain = gain_digital_gains[0]; - } else { - time = gain_integration_times[INTEGRATION_TIMES-1]; - if (dBGain < INTEGRATION_TIMES + ANALOGUE_GAINS) { - again = gain_analogue_gains[dBGain - INTEGRATION_TIMES + 1]; - dgain = gain_digital_gains[0]; - } else { - again = gain_analogue_gains[ANALOGUE_GAINS]; - dgain = gain_digital_gains[dBGain - INTEGRATION_TIMES - ANALOGUE_GAINS + 1]; - } - } - int ret = i2c_write(i2c, 0x0157, again); - ret |= i2c_write(i2c, 0x0158, dgain >> 8); - ret |= i2c_write(i2c, 0x0159, dgain & 0xff); - ret |= i2c_write(i2c, 0x015A, time >> 8); - ret |= i2c_write(i2c, 0x015B, time & 0xff); - - return ret; -} - -static void GC2145MIPI_Sensor_2M(client interface i2c_master_if i2c) -{ - SENSORDB("GC2145MIPI_Sensor_2M"); - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x00); - GC2145MIPI_write_cmos_sensor(i2c, 0xfd, 0x00); -#ifdef GC2145MIPI_2Lane - GC2145MIPI_write_cmos_sensor(i2c, 0xfa, 0x00); -#else - GC2145MIPI_write_cmos_sensor(i2c, 0xfa, 0x11); -#endif - //// crop window - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x00); - GC2145MIPI_write_cmos_sensor(i2c, 0x90, 0x01); - GC2145MIPI_write_cmos_sensor(i2c, 0x91, 0x00); - GC2145MIPI_write_cmos_sensor(i2c, 0x92, 0x00); - GC2145MIPI_write_cmos_sensor(i2c, 0x93, 0x00); - GC2145MIPI_write_cmos_sensor(i2c, 0x94, 0x00); - GC2145MIPI_write_cmos_sensor(i2c, 0x95, 0x04); - GC2145MIPI_write_cmos_sensor(i2c, 0x96, 0xb0); - GC2145MIPI_write_cmos_sensor(i2c, 0x97, 0x06); - GC2145MIPI_write_cmos_sensor(i2c, 0x98, 0x40); - GC2145MIPI_write_cmos_sensor(i2c, 0x99, 0x11); - GC2145MIPI_write_cmos_sensor(i2c, 0x9a, 0x06); - //// AWB - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x00); - GC2145MIPI_write_cmos_sensor(i2c, 0xec, 0x06); - GC2145MIPI_write_cmos_sensor(i2c, 0xed, 0x04); - GC2145MIPI_write_cmos_sensor(i2c, 0xee, 0x60); - GC2145MIPI_write_cmos_sensor(i2c, 0xef, 0x90); - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x01); - GC2145MIPI_write_cmos_sensor(i2c, 0x74, 0x01); - //// AEC - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x01); - GC2145MIPI_write_cmos_sensor(i2c, 0x01, 0x04); - GC2145MIPI_write_cmos_sensor(i2c, 0x02, 0xc0); - GC2145MIPI_write_cmos_sensor(i2c, 0x03, 0x04); - GC2145MIPI_write_cmos_sensor(i2c, 0x04, 0x90); - GC2145MIPI_write_cmos_sensor(i2c, 0x05, 0x30); - GC2145MIPI_write_cmos_sensor(i2c, 0x06, 0x90); - GC2145MIPI_write_cmos_sensor(i2c, 0x07, 0x30); - GC2145MIPI_write_cmos_sensor(i2c, 0x08, 0x80); - GC2145MIPI_write_cmos_sensor(i2c, 0x0a, 0x82); -#ifdef GC2145MIPI_2Lane - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x01); - GC2145MIPI_write_cmos_sensor(i2c, 0x21, 0x04); - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x00); - GC2145MIPI_write_cmos_sensor(i2c, 0x20, 0x03); -#else - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x01); - GC2145MIPI_write_cmos_sensor(i2c, 0x21, 0x15); - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x00); - GC2145MIPI_write_cmos_sensor(i2c, 0x20, 0x15); -#endif - //// mipi - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x03); - GC2145MIPI_write_cmos_sensor(i2c, 0x12, 0x80); - GC2145MIPI_write_cmos_sensor(i2c, 0x13, 0x0c); - GC2145MIPI_write_cmos_sensor(i2c, 0x04, 0x01); - GC2145MIPI_write_cmos_sensor(i2c, 0x05, 0x00); - GC2145MIPI_write_cmos_sensor(i2c, 0xfe, 0x00); - -} - -*/ diff --git a/sensors/imx219.h b/sensors/imx219.h deleted file mode 100644 index 90570549..00000000 --- a/sensors/imx219.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include "sensor.h" - -#include -#include "i2c.h" - -// I2C adress -#define IMX219_I2C_ADDR 0x10 - -// TODO maybe out of here -typedef struct -{ - uint16_t addr; - uint16_t val; -} imx219_settings_t; - -#ifdef __XC__ - -// configure registers -#if ((CONFIG_MODE == MODE_VGA_RAW8) || (CONFIG_MODE == MODE_VGA_RAW10)) - #define CONFIG_REG mode_640_480_regs -#elif (CONFIG_MODE == MODE_UXGA_RAW8) - #define CONFIG_REG mode_1640_1232_regs -#elif (CONFIG_MODE == MODE_WQSXGA_RAW8) - #define CONFIG_REG mode_3280x2464_regs -#elif (CONFIG_MODE == MODE_1920_1080) - #define CONFIG_REG mode_1920_1080_regs -#else - #error "Invalid configuration mode" -#endif - -// Configure formats -#if EXPECTED_FORMAT == MIPI_DT_RAW10 - #define DATA_FORMAT_REGS raw10_framefmt_regs - -#elif EXPECTED_FORMAT == MIPI_DT_RAW8 - #define DATA_FORMAT_REGS raw8_framefmt_regs - -#endif - -// configure FPS -#if defined(FPS_13) - #define PLL_VT_MPY 0x0020 -#elif defined(FPS_24) - #define PLL_VT_MPY 0x0047 -#elif defined(FPS_30) - #define PLL_VT_MPY 0x0058 -#elif defined(FPS_38) - #define PLL_VT_MPY 0x0070 -#elif defined(FPS_53) - #define PLL_VT_MPY 0x009C -#elif defined(FPS_76) - #define PLL_VT_MPY 0x00E0 -#else - #warning fps not defined, selecting default value - #define PLL_VT_MPY 0x0027 -#endif - - -// functions -int imx219_init(client interface i2c_master_if i2c); -int imx219_stream_start(client interface i2c_master_if i2c); -int imx219_configure_mode(client interface i2c_master_if i2c); -int imx219_stream_stop(client interface i2c_master_if i2c); -int imx219_set_gain_dB(client interface i2c_master_if i2c, uint32_t dBGain); -int imx219_set_binning(client interface i2c_master_if i2c, uint32_t H_binning, uint32_t V_binning); -int imx219_read(client interface i2c_master_if i2c, uint16_t addr); -int imx219_set_flip(client interface i2c_master_if i2c, uint8_t flip_mode); - -// read -void imx219_read_gains(client interface i2c_master_if i2c, uint16_t values[5]); - - -#endif \ No newline at end of file diff --git a/sensors/imx219.xc b/sensors/imx219.xc deleted file mode 100644 index 22023df3..00000000 --- a/sensors/imx219.xc +++ /dev/null @@ -1,198 +0,0 @@ -#include -#include -#include - -#include -#include "i2c.h" -#include "imx219.h" -#include "imx219_reg.h" - - - -static int i2c_write(client interface i2c_master_if i2c, int reg, int value) -{ - i2c_regop_res_t result; - // Write an 8-bit register on a slave device from a 16-bit register address. - result = i2c.write_reg8_addr16(IMX219_I2C_ADDR, reg, value); - if (result != I2C_REGOP_SUCCESS) - { - // printf("Failed on address %02x value %02x\n", reg, value); - // TODO FIXME - } - return result != I2C_REGOP_SUCCESS ? -1 : 0; -} - -static int i2c_write_table(client interface i2c_master_if i2c, - imx219_settings_t table[], - int N) { - int ret; - for(int i = 0; i < N; i++) { - uint32_t address = table[i].addr; - uint32_t value = table[i].val; - if (address == SLEEP) { - timer tmr; - int t; - tmr :> t; - tmr when timerafter(t + TRSTUS * 100) :> void; - } - if (address & 0x8000) { - address &= 0x7fff; - ret = i2c_write(i2c, address, value >> 8); // B1 B2 B3 B4 -> B1 B2 - ret |= i2c_write(i2c, address+1, value & 0xff); // B1 B2 B3 B4 -> B3 B4 - } else { - ret = i2c_write(i2c, address, value); - } - if (ret < 0) { - return ret; - } - } - return 0; -} - - - -static int i2c_write_table_val(client interface i2c_master_if i2c, - imx219_settings_t table[], - int N) { - int ret; - char mode = 's'; - - for(int i = 0; i < N; i++) { - uint32_t address = table[i].addr; - uint32_t value = table[i].val; - if (address == SLEEP) { - timer tmr; - int t; - tmr :> t; - tmr when timerafter(t + TRSTUS * 100) :> void; - } - - if ((value & 0xFF00) || (address & 0x8000)){ - if (address & 0x8000) { - address &= 0x7fff; - } - mode = 'c'; - printf("mode=%c , adress = 0x%04x, value = 0x%02x\n", mode, address, value >> 8); - printf("mode=%c , adress+ = 0x%04x, value = 0x%02x\n", mode, address+1, value & 0xff); - // continous writte - ret = i2c_write(i2c, address, value >> 8); // B1 B2 B3 B4 -> B1 B2 - ret |= i2c_write(i2c, address+1, value & 0xff); // B1 B2 B3 B4 -> B3 B4 - } - else - { - // single writte - mode = 's'; - ret = i2c_write(i2c, address, value); - printf("mode=%c , adress = 0x%04x, value = 0x%02x\n", mode, address, value); - } - if (ret < 0) { - return ret; - } - } - return 0; -} - - - -int imx219_read(client interface i2c_master_if i2c, uint16_t addr){ - i2c_regop_res_t res; - uint16_t val = i2c.read_reg16(IMX219_I2C_ADDR, addr, res); - assert(res == 0); - return val; -} - -void imx219_read_gains(client interface i2c_master_if i2c, uint16_t values[5]){ - values[0] = imx219_read(i2c, 0x0157); - values[1] = imx219_read(i2c, 0x0158); - values[2] = imx219_read(i2c, 0x0159); - values[3] = imx219_read(i2c, 0x015A); - values[4] = imx219_read(i2c, 0x015B); -} - - -/// ------------------------------------------------------------------------------- - -int imx219_init(client interface i2c_master_if i2c) -{ - int ret = 0; - // Send all registers that are common to all modes - ret = i2c_write_table_val(i2c, imx219_common_regs, sizeof(imx219_common_regs) / sizeof(imx219_common_regs[0])); - // Configure two or four Lane mode - ret = i2c_write_table_val(i2c, imx219_lanes_regs, sizeof(imx219_lanes_regs) / sizeof(imx219_lanes_regs[0])); - // set gain - ret = imx219_set_gain_dB(i2c, GAIN_DB); - return ret; -} - -int imx219_configure_mode(client interface i2c_master_if i2c) -{ - int ret = 0; - // Apply default values of current mode - ret = i2c_write_table_val(i2c, CONFIG_REG, sizeof(CONFIG_REG) / sizeof(CONFIG_REG[0])); - // set frame format register - ret = i2c_write_table_val(i2c, DATA_FORMAT_REGS, sizeof(DATA_FORMAT_REGS) / sizeof(DATA_FORMAT_REGS[0])); - // set binning - ret = i2c_write_table_val(i2c, binning_regs, sizeof(binning_regs) / sizeof(binning_regs[0])); - // set flip - ret = imx219_set_flip(i2c, SELECTED_FLIP); - return ret; - -} - - -int imx219_stream_start(client interface i2c_master_if i2c){ - int ret = 0; - /* set stream on register */ - ret = i2c_write_table_val(i2c, start_regs, sizeof(start_regs) / sizeof(start_regs[0])); - return ret; -} - -int imx219_stream_stop(client interface i2c_master_if i2c){ - return i2c_write_table(i2c, stop, sizeof(stop) / sizeof(stop[0])); -} - -int imx219_set_gain_dB(client interface i2c_master_if i2c, - uint32_t dBGain) -{ - uint32_t time, again, dgain; - if (dBGain > GAIN_MAX_DB) - { - dBGain = GAIN_MAX_DB; - } - if (dBGain < INTEGRATION_TIMES) - { - time = gain_integration_times[dBGain]; - again = gain_analogue_gains[0]; - dgain = gain_digital_gains[0]; - } - else - { - time = gain_integration_times[INTEGRATION_TIMES - 1]; - if (dBGain < INTEGRATION_TIMES + ANALOGUE_GAINS) - { - again = gain_analogue_gains[dBGain - INTEGRATION_TIMES + 1]; - dgain = gain_digital_gains[0]; - } - else - { - again = gain_analogue_gains[ANALOGUE_GAINS]; - dgain = gain_digital_gains[dBGain - INTEGRATION_TIMES - ANALOGUE_GAINS + 1]; - } - } - int ret = i2c_write(i2c, 0x0157, again); - ret |= i2c_write(i2c, 0x0158, dgain >> 8); - ret |= i2c_write(i2c, 0x0159, dgain & 0xff); - ret |= i2c_write(i2c, 0x015A, time >> 8); - ret |= i2c_write(i2c, 0x015B, time & 0xff); - - return ret; -} - - -int imx219_set_flip(client interface i2c_master_if i2c, uint8_t flip_mode){ - int ret = 0; - ret |= i2c_write(i2c, ORIENTATION_REG, flip_mode); - return ret; -} - -// https://github.com/torvalds/linux/blob/63355b9884b3d1677de6bd1517cd2b8a9bf53978/drivers/media/i2c/imx219.c#L993 diff --git a/sensors/imx219_reg.h b/sensors/imx219_reg.h deleted file mode 100644 index b3bc9e1c..00000000 --- a/sensors/imx219_reg.h +++ /dev/null @@ -1,327 +0,0 @@ - -// --------- REG definitions --------------------------------------------------------- - -// Sleep values and registers -#define SLEEP 0xFFFF -#define TRSTUS 200 - -// CSI LANE -#define CSI_LANE_MODE_REG 0x0114 -#define CSI_LANE_MODE_2_LANES 1 -#define CSI_LANE_MODE_4_LANES 3 - -// BINNING -#define BINNING_MODE_REG 0x0174 -#define BINNING_NONE 0x0000 -#define BINNING_2X2 0x0101 -#define BINNING_4X4 0x0202 -#define BINNING_2X2_AN 0x0303 - -#if ((CONFIG_MODE == MODE_1920_1080) || (CONFIG_MODE == MODE_WQSXGA_RAW8)) - #define BINNING_MODE BINNING_NONE - #define VTPXCK_DIV 0x05 -#else - #define BINNING_MODE BINNING_2X2_AN - #define VTPXCK_DIV 0x0A -#endif - -// PLL settings -#define PREPLLCK_VT_DIV_REG 0x0304 -#define PLL_VT_MPY_REG 0x0306 - -// Gain params -#define GAIN_MIN_DB 0 -#define GAIN_MAX_DB 84 -#define GAIN_DEFAULT_DB 28 -#define GAIN_DB GAIN_DEFAULT_DB - -// Orientation -#define ORIENTATION_REG 0x0172 - // H | V -#define FLIP_NONE (0 | (0 << 1)) -#define FLIP_HORIZONTAL (1 | (0 << 1)) -#define FLIP_VERTICAL (0 | (1 << 1)) -#define FLIP_BOTH (1 | (1 << 1)) -#define SELECTED_FLIP FLIP_NONE - - - - -// --------- REG GROUP definitions ---------------------------------------------------- -static imx219_settings_t imx219_common_regs[] = { - {0x0103, 0x01}, /* software_reset 1, reset the chip */ - {SLEEP, TRSTUS}, /* software_reset 1, reset the chip */ - - - {0x0100, 0x00}, /* Mode Select */ - - /* To Access Addresses 3000-5fff, send the following commands */ - {0x30eb, 0x0c}, - {0x30eb, 0x05}, - {0x300a, 0xff}, - {0x300b, 0xff}, - {0x30eb, 0x05}, - {0x30eb, 0x09}, - - /* PLL Clock Table */ - { 0x812A, 0x1800 }, // EXCK_FREQ 24.00, for 24 Mhz - { 0x0304, 0x02 }, // PREPLLCK_VT_DIV 2, for pre divide by 2 - { 0x0305, 0x02 }, // PREPLLCK_OP_DIV 2, for pre divide by 2 - { 0x8306, 0x0027}, // PLL_VT_MPY 0x27, for multiply by 39, pixclk=187.2 MHz - { 0x830C, 0x40}, // PLL_OP_MPY 0x40, for multiply by 64, MIPI clk=768 MHz - { 0x0301, VTPXCK_DIV}, // VTPXCK_DIV 5, // Options: 05, 08 or 0A (higher the slowest) - { 0x0303, 0x01 }, // VTSYCK_DIV 1, ? - { 0x0309, 0x08 }, // OPPXCK_DIV 8, has to match RAW8 if you have raw8 - { 0x030B, 0x01 }, // OPSYCK_DIV 1, has to be 1? - - // min_line_blanking_pck - {0x1148, 0x00}, - {0x1149, 0xA8}, - - /* Undocumented registers */ - {0x455e, 0x00}, - {0x471e, 0x4b}, - {0x4767, 0x0f}, - {0x4750, 0x14}, - {0x4540, 0x00}, - {0x47b4, 0x14}, - {0x4713, 0x30}, - {0x478b, 0x10}, - {0x478f, 0x10}, - {0x4793, 0x10}, - {0x4797, 0x0e}, - {0x479b, 0x0e}, - - /* Frame Bank Register Group "A" */ - {0x0162, 0x0d}, /* Line_Length_A */ - {0x0163, 0x78}, - {0x0170, 0x01}, /* X_ODD_INC_A */ - {0x0171, 0x01}, /* Y_ODD_INC_A */ - - /* Output setup registers */ - {0x0114, 0x01}, /* CSI 2-Lane Mode */ - {0x0128, 0x00}, /* DPHY Auto Mode */ - {0x012a, 0x18}, /* EXCK_Freq */ - {0x012b, 0x00}, -}; - - -static imx219_settings_t imx219_lanes_regs[] = { - {CSI_LANE_MODE_REG, CSI_LANE_MODE_2_LANES} -}; - - -static imx219_settings_t mode_640_480_regs[] = { - {0x0164, 0x03}, - {0x0165, 0xe8}, - {0x0166, 0x08}, - {0x0167, 0xe7}, - {0x0168, 0x02}, - {0x0169, 0xf0}, - {0x016a, 0x06}, - {0x016b, 0xaf}, - {0x016c, 0x02}, - {0x016d, 0x80}, - {0x016e, 0x01}, - {0x016f, 0xe0}, - {0x0624, 0x06}, - {0x0625, 0x68}, - {0x0626, 0x04}, - {0x0627, 0xd0}, -}; - -static imx219_settings_t mode_1640_1232_regs[] = { - {0x0164, 0x00}, - {0x0165, 0x00}, - {0x0166, 0x0c}, - {0x0167, 0xcf}, - {0x0168, 0x00}, - {0x0169, 0x00}, - {0x016a, 0x09}, - {0x016b, 0x9f}, - {0x016c, 0x06}, - {0x016d, 0x68}, - {0x016e, 0x04}, - {0x016f, 0xd0}, - {0x0624, 0x06}, - {0x0625, 0x68}, - {0x0626, 0x04}, - {0x0627, 0xd0}, -}; - -static imx219_settings_t mode_1920_1080_regs[] = { - {0x0164, 0x02}, - {0x0165, 0xa8}, - {0x0166, 0x0a}, - {0x0167, 0x27}, - {0x0168, 0x02}, - {0x0169, 0xb4}, - {0x016a, 0x06}, - {0x016b, 0xeb}, - {0x016c, 0x07}, - {0x016d, 0x80}, - {0x016e, 0x04}, - {0x016f, 0x38}, - {0x0624, 0x07}, - {0x0625, 0x80}, - {0x0626, 0x04}, - {0x0627, 0x38}, -}; - -static imx219_settings_t mode_3280x2464_regs[] = { - {0x0164, 0x00}, - {0x0165, 0x00}, - {0x0166, 0x0c}, - {0x0167, 0xcf}, - {0x0168, 0x00}, - {0x0169, 0x00}, - {0x016a, 0x09}, - {0x016b, 0x9f}, - {0x016c, 0x0c}, - {0x016d, 0xd0}, - {0x016e, 0x09}, - {0x016f, 0xa0}, - {0x0624, 0x0c}, - {0x0625, 0xd0}, - {0x0626, 0x09}, - {0x0627, 0xa0}, -}; - -static imx219_settings_t raw10_framefmt_regs[] = { - {0x018c, 0x0a}, - {0x018d, 0x0a}, - {0x0309, 0x0a}, -}; - -static imx219_settings_t raw8_framefmt_regs[] = { - {0x018c, 0x08}, - {0x018d, 0x08}, - {0x0309, 0x08}, -}; - - -static imx219_settings_t binning_regs[] = { - {BINNING_MODE_REG, BINNING_MODE} -}; - - -static imx219_settings_t start[] = { - {0x0100, 0x01}, /* mode select streaming on */ -}; - -static imx219_settings_t stop[] = { - {0x0100, 0x00}, /* mode select streaming off */ -}; - -static imx219_settings_t start_regs[] = { - {0x0100, 0x01}, /* mode select streaming on */ -}; - -static imx219_settings_t stop_regs[] = { - {0x0100, 0x00}, /* mode select streaming off */ -}; - - - -// GAIN related settings -#define INTEGRATION_TIMES 41 -#define ANALOGUE_GAINS 20 -#define DIGITAL_GAINS 25 - -static uint16_t gain_integration_times[INTEGRATION_TIMES] = { - 0x00a, - 0x00b, - 0x00c, - 0x00e, - 0x010, - 0x012, - 0x014, - 0x016, - 0x019, - 0x01c, - 0x020, - 0x024, - 0x028, - 0x02d, - 0x033, - 0x039, - 0x040, - 0x048, - 0x051, - 0x05b, - 0x066, - 0x072, - 0x080, - 0x090, - 0x0a2, - 0x0b6, - 0x0cc, - 0x0e5, - 0x101, - 0x120, - 0x143, - 0x16b, - 0x197, - 0x1c9, - 0x201, - 0x23f, - 0x286, - 0x2d4, - 0x32d, - 0x390, - 0x400, -}; - -static uint8_t gain_analogue_gains[ANALOGUE_GAINS + 1] = { - 0, - 28, - 53, - 75, - 95, - 112, - 128, - 142, - 155, - 166, - 175, - 184, - 192, - 199, - 205, - 211, - 215, - 220, - 224, - 227, - 231, -}; - -static uint16_t gain_digital_gains[DIGITAL_GAINS + 1] = { - 0x0100, - 0x011f, - 0x0142, - 0x0169, - 0x0195, - 0x01c7, - 0x01fe, - 0x023d, - 0x0283, - 0x02d1, - 0x0329, - 0x038c, - 0x03fb, - 0x0477, - 0x0503, - 0x059f, - 0x064f, - 0x0714, - 0x07f1, - 0x08e9, - 0x0a00, - 0x0b38, - 0x0c96, - 0x0e20, - 0x0fd9, -}; - - diff --git a/sensors/src/SensorBase.cpp b/sensors/src/SensorBase.cpp new file mode 100644 index 00000000..43c97500 --- /dev/null +++ b/sensors/src/SensorBase.cpp @@ -0,0 +1,123 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include + +#include "SensorBase.hpp" + +using namespace sensor; + +SensorBase::SensorBase(i2c_config_t _conf) : i2c_cfg(_conf) { + this->i2c_init(); +} + +void SensorBase::i2c_init() { + i2c_master_init( + this->i2c_cfg.i2c_ctx_ptr, + this->i2c_cfg.p_scl, 0, 0, + this->i2c_cfg.p_sda, 0, 0, + this->i2c_cfg.speed); + delay_milliseconds(100); + puts("\nI2C initialized..."); +} + +uint16_t SensorBase::i2c_read(uint16_t reg) { + i2c_regop_res_t op_code; + + uint16_t result = read_reg16( + this->i2c_cfg.i2c_ctx_ptr, + this->i2c_cfg.device_addr, + reg, + &op_code); + + xassert((op_code == I2C_REGOP_SUCCESS) && "Could not read from I2C"); + return result; +} + +int SensorBase::i2c_write_line(i2c_line_t line) { + i2c_regop_res_t op_code = write_reg8_addr16( + this->i2c_cfg.i2c_ctx_ptr, + this->i2c_cfg.device_addr, + line.reg_addr, + line.reg_val); + return op_code != I2C_REGOP_SUCCESS ? -1 : 0; +} + +int SensorBase::i2c_write_line(uint16_t reg, uint8_t val) { + i2c_regop_res_t op_code = write_reg8_addr16( + this->i2c_cfg.i2c_ctx_ptr, + this->i2c_cfg.device_addr, + reg, + val); + return op_code != I2C_REGOP_SUCCESS ? -1 : 0; +} + +int SensorBase::i2c_write_table(i2c_table_t table) { + const unsigned sleep_adr = 0xFFFF; + const unsigned sleep_ticks = 200 * 100; + int ret = 0; + + for (size_t i = 0; i < table.num_lines; i++) { + uint16_t address = table.table[i].reg_addr; + uint16_t value = table.table[i].reg_val; + + // pause if we reset the device + if (address == sleep_adr) { + delay_ticks(sleep_ticks); + #if PRINT_I2C_REG + printf("sleeping...\n"); + #endif + } + + // if continuous mode + if ((value & 0xFF00) || (address & 0x8000)) { + if (address & 0x8000) { + address &= 0x7fff; + } + #if PRINT_I2C_REG + printf("mode=%c , address = 0x%04x, value = 0x%02x\n", 'c', address, value >> 8); + printf("mode=%c , address+ = 0x%04x, value = 0x%02x\n", 'c', address+1, value & 0xff); + #endif + ret |= this->i2c_write_line(address, value >> 8); // B1 B2 B3 B4 -> B1 B2 + ret |= this->i2c_write_line(address+1, value & 0xff); // B1 B2 B3 B4 -> B3 B4 + } + else { + #if PRINT_I2C_REG + printf("mode=%c , address = 0x%04x, value = 0x%02x\n", 's', address, value); + #endif + ret |= this->i2c_write_line(address, (uint8_t)value); + } + } + return ret != I2C_REGOP_SUCCESS ? -1 : 0; +} + +int SensorBase::initialize() { + xassert(0 && "Sensor Exception: Make sure your initialize() method is implemented and called from the derived class"); + return -1; +} + +int SensorBase::stream_start() { + xassert(0 && "Sensor Exception: Make sure your stream_start() method is implemented and called from the derived class"); + return -1; +} + +int SensorBase::stream_stop() { + xassert(0 && "Sensor Exception: Make sure your stream_stop() method is implemented and called from the derived class"); + return -1; +} + +int SensorBase::set_exposure(uint32_t dBGain) { + (void)dBGain; + xassert(0 && "Sensor Exception: Make sure your set_exposure() method is implemented and called from the derived class"); + return -1; +} + +int SensorBase::configure() { + xassert(0 && "Sensor Exception: Make sure your configure() method is implemented and called from the derived class"); + return -1; +} + +void SensorBase::control(chanend_t c_control) { + (void)c_control; + xassert(0 && "Sensor Exception: Make sure your control() method is implemented and called from the derived class"); +} diff --git a/sensors/src/_sony_imx219/README.rst b/sensors/src/_sony_imx219/README.rst new file mode 100644 index 00000000..a29bd666 --- /dev/null +++ b/sensors/src/_sony_imx219/README.rst @@ -0,0 +1,25 @@ +=============== +Sony IMX219 Sensor +=============== + +The Sony IMX219 sensor is a widely used image sensor developed by Sony Corporation. It is commonly employed in camera modules for mobile phones and compact electronic devices. + +Key Features +------------ + +- Optical Format: 1/4-inch +- Resolution: 8.3 megapixels +- Technology: Sony Exmor R back-illuminated +- Image Processing: Advanced algorithms for enhanced image quality +- Application: Raspberry Pi Camera Module (versions V2 and V2.1) + +Description +------------ + +The Sony IMX219 sensor offers a compact form factor combined with impressive performance capabilities. Its 1/4-inch optical format allows for integration into small camera modules without compromising image quality. + +With a resolution of 8.3 megapixels, the IMX219 sensor captures detailed images and videos. The Sony Exmor R back-illuminated technology enhances light sensitivity and reduces noise, resulting in improved low-light performance. + +Additionally, the IMX219 sensor incorporates advanced image processing algorithms, ensuring high-quality output. These algorithms optimize color reproduction, sharpness, and dynamic range, delivering visually pleasing results. + +One notable implementation of the Sony IMX219 sensor is in the Raspberry Pi Camera Module. Various versions of the module, such as V2 and V2.1, utilize the IMX219 sensor due to its compact size, power efficiency, and excellent imaging capabilities. diff --git a/sensors/src/_sony_imx219/imx219.cpp b/sensors/src/_sony_imx219/imx219.cpp new file mode 100644 index 00000000..88241739 --- /dev/null +++ b/sensors/src/_sony_imx219/imx219.cpp @@ -0,0 +1,247 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include + +#include + +#include "imx219.hpp" + +using namespace sensor; + +// This header has to be after imx219.hpp and the namespace +#include "imx219_reg.h" + +IMX219::IMX219(i2c_config_t _conf,resolution_t _res, pixel_format_t _pix_fmt, bool _binning, bool _centralize) + : sensor::SensorBase(_conf), frame_res(_res), pix_fmt(_pix_fmt), + binning_2x2(_binning) { + this->get_x_y_len(); + this->get_offsets_and_check_ranges(_centralize); + this->adjust_offsets(); +} + +IMX219::IMX219(i2c_config_t _conf,resolution_t _res, pixel_format_t _pix_fmt, bool _binning, uint16_t _x_offset, uint16_t _y_offset) + : sensor::SensorBase(_conf), frame_res(_res), pix_fmt(_pix_fmt), + binning_2x2(_binning), x_offset(_x_offset), y_offset(_y_offset) { + this->get_x_y_len(); + this->check_ranges(); + this->adjust_offsets(); +} + +int IMX219::initialize() { + int ret = 0; + // Send all registers that are common to all modes + ret |= this->i2c_write_table(GET_TABLE(imx219_common_regs)); + // Configure two or four Lane mode + ret |= this->i2c_write_table(GET_TABLE(imx219_lanes_regs)); + // set gain + ret |= this->set_exposure(GAIN_DB); + return ret; +} + +int IMX219::stream_start() { + return this->i2c_write_table(GET_TABLE(start_regs)); +} + +int IMX219::stream_stop() { + return this->i2c_write_table(GET_TABLE(stop_regs)); +} + +int IMX219::set_exposure(uint32_t dBGain) { + i2c_table_t exposure_regs = this->get_exp_gains_table(dBGain); + return this->i2c_write_table(exposure_regs); +} + +int IMX219::configure() { + i2c_table_t frame_size_regs = this->get_res_table(); + i2c_table_t pix_format_regs = this->get_pxl_fmt_table(); + i2c_line_t binning_reg[1] = {{BINNING_MODE_REG, 0}}; + binning_reg[0].reg_val = (this->binning_2x2) ? BINNING_2X2 : BINNING_NONE; + + int ret = 0; + // Apply default values of current mode + ret |= this->i2c_write_table(frame_size_regs); + // set frame format register + ret |= this->i2c_write_table(pix_format_regs); + // set binning + ret |= this->i2c_write_table(GET_TABLE(binning_reg)); + return ret; +} + +void IMX219::control(chanend_t c_control) { + // Init the I2C sensor first configuration + int ret = 0; + ret |= this->initialize(); + delay_milliseconds(100); + ret |= this->configure(); + delay_milliseconds(600); + ret |= this->stream_start(); + delay_milliseconds(600); + xassert((ret == 0) && "Could not initialise camera"); + puts("\nCamera_started and configured..."); + + // store the response + uint32_t encoded_response; + sensor_control_t cmd; + uint8_t arg; + + // sensor control logic + while(1) { + encoded_response = chan_in_word(c_control); + chan_out_word(c_control, 0); + cmd = (sensor_control_t) DECODE_CMD(encoded_response); + + #if ENABLE_PRINT_SENSOR_CONTROL + printf("--------------- Received command %d\n", cmd); + #endif + + switch (cmd) + { + case SENSOR_INIT: + ret = this->initialize(); + break; + case SENSOR_CONFIG: + //TODO reimplement when dynamic configuration is supported + ret = this->configure(); + break; + case SENSOR_STREAM_START: + ret = this->stream_start(); + break; + case SENSOR_STREAM_STOP: + ret = this->stream_stop(); + break; + case SENSOR_SET_EXPOSURE: + arg = DECODE_ARG(encoded_response); + ret = this->set_exposure(arg); + break; + default: + break; + } + xassert((ret == 0) && "Could not perform I2C write"); + } +} + +i2c_table_t IMX219::get_exp_gains_table(uint32_t dBGain) { + static i2c_line_t exposure_regs[5]; + static i2c_table_t exposure_table = {exposure_regs, 5}; + uint16_t time, dgain; + uint8_t again; + if (dBGain > GAIN_MAX_DB) + { + dBGain = GAIN_MAX_DB; + } + if (dBGain < INTEGRATION_TIMES) + { + time = gain_integration_times[dBGain]; + again = gain_analogue_gains[0]; + dgain = gain_digital_gains[0]; + } + else + { + time = gain_integration_times[INTEGRATION_TIMES - 1]; + if (dBGain < INTEGRATION_TIMES + ANALOGUE_GAINS) + { + again = gain_analogue_gains[dBGain - INTEGRATION_TIMES + 1]; + dgain = gain_digital_gains[0]; + } + else + { + again = gain_analogue_gains[ANALOGUE_GAINS]; + dgain = gain_digital_gains[dBGain - INTEGRATION_TIMES - ANALOGUE_GAINS + 1]; + } + } + exposure_regs[0] = {0x0157, again}; + exposure_regs[1] = {0x0158, (uint8_t)(dgain >> 8)}; + exposure_regs[2] = {0x0159, (uint8_t)(dgain)}; + exposure_regs[3] = {0x015A, (uint8_t)(time >> 8)}; + exposure_regs[4] = {0x015B, (uint8_t)(time)}; + return exposure_table; +} + +i2c_table_t IMX219::get_pxl_fmt_table() { + static i2c_line_t format_regs[3]; + static i2c_table_t format_table = {format_regs, 3}; + uint16_t val = 0; + if(pix_fmt == FMT_RAW8) { + val = 0x08; + } else if(pix_fmt == FMT_RAW10) { + val = 0x0a; + } else { + xassert(0 && "Pixel format has to be either RAW8 or RAW10"); + } + format_regs[0] = {0x018c, val}; + format_regs[1] = {0x018d, val}; + format_regs[2] = {0x0309, val}; + return format_table; +} + +i2c_table_t IMX219::get_res_table() { + static i2c_line_t resolution_regs[12]; + static i2c_table_t resolution_table = {resolution_regs, 12}; + uint16_t x_full_len = (this->binning_2x2) ? this->x_len * 2 : this->x_len; + uint16_t y_full_len = (this->binning_2x2) ? this->y_len * 2 : this->y_len; + uint16_t x_end = this->x_offset + x_full_len - 1; + uint16_t y_end = this->y_offset + y_full_len - 1; + // x start - end + resolution_regs[0] = {0x0164, (uint8_t)(this->x_offset >> 8)}; + resolution_regs[1] = {0x0165, (uint8_t)(this->x_offset)}; + resolution_regs[2] = {0x0166, (uint8_t)(x_end >> 8)}; + resolution_regs[3] = {0x0167, (uint8_t)(x_end)}; + // y start - end + resolution_regs[4] = {0x0168, (uint8_t)(this->y_offset >> 8)}; + resolution_regs[5] = {0x0169, (uint8_t)(this->y_offset)}; + resolution_regs[6] = {0x016a, (uint8_t)(y_end >> 8)}; + resolution_regs[7] = {0x016b, (uint8_t)(y_end)}; + // x, y len (non binned) + resolution_regs[8] = {0x016c, (uint8_t)(this->x_len >> 8)}; + resolution_regs[9] = {0x016d, (uint8_t)(this->x_len)}; + resolution_regs[10] = {0x016e, (uint8_t)(this->y_len >> 8)}; + resolution_regs[11] = {0x016f, (uint8_t)(this->y_len)}; + return resolution_table; +} + +void IMX219::get_x_y_len() { + if(this->frame_res == RES_640_480) { + this->x_len = 640; + this->y_len = 480; + } else if (this->frame_res == RES_1280_960) { + this->x_len = 1280; + this->y_len = 960; + } else { + xassert(0 && "Given resolution format is not supported"); + } +} + +void IMX219::get_offsets_and_check_ranges(bool centralize) { + uint16_t x_full_len = (this->binning_2x2) ? this->x_len * 2 : this->x_len; + uint16_t y_full_len = (this->binning_2x2) ? this->y_len * 2 : this->y_len; + if((x_full_len > SENSOR_X_LIM) || (y_full_len > SENSOR_Y_LIM)) { + xassert(0 && "Given resolution and binnig mode exceed sensor frame limits"); + } + if(centralize) { + this->x_offset = (SENSOR_X_LIM / 2) - (x_full_len / 2); + this->y_offset = (SENSOR_Y_LIM / 2) - (y_full_len / 2); + } else { + this->x_offset = 0; + this->y_offset = 0; + } +} + +void IMX219::adjust_offsets() { + if(this->x_offset % 2) { + this->x_offset -= 1; + puts("Warning: X offset has been adjusted to be even"); + } + if(this->y_offset % 2) { + this->y_offset -= 1; + puts("Warning: Y offset has been adjusted to be even"); + } +} + +void IMX219::check_ranges() { + uint16_t x_full_len = (this->binning_2x2) ? this->x_len * 2 : this->x_len; + uint16_t y_full_len = (this->binning_2x2) ? this->y_len * 2 : this->y_len; + if(((this->x_offset + x_full_len) >= SENSOR_X_LIM) || ((this->y_offset + y_full_len) >= SENSOR_Y_LIM)) { + xassert(0 && "Given resolution and binnig mode exceed sensor frame limits"); + } +} diff --git a/sensors/src/_sony_imx219/imx219.hpp b/sensors/src/_sony_imx219/imx219.hpp new file mode 100644 index 00000000..c4223470 --- /dev/null +++ b/sensors/src/_sony_imx219/imx219.hpp @@ -0,0 +1,148 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#ifdef __cplusplus + +#include "SensorBase.hpp" +#include "sensor.h" +#include "sensor_control.h" + +namespace sensor { + +class IMX219 : public SensorBase { + + private: + + /** + * @brief Sensor resolution, RAW format and binning mode + */ + resolution_t frame_res; + pixel_format_t pix_fmt; + bool binning_2x2; + + /** + * @brief X and Y lenghts and offsets + */ + uint16_t x_len, y_len; + uint16_t x_offset, y_offset; + + /** + * @brief Get X and Y lenghts + */ + void get_x_y_len(); + + /** + * @brief Calculates offsets + * + * @param centralise If set, offsets will be calculated to centralise the frame, otherwise will start from (0, 0) + */ + void get_offsets_and_check_ranges(bool centralize); + + /** + * @brief Checks that given resolution, binning mode and offsets are within sensor limits + */ + void check_ranges(); + + /** + * @brief Adjusts offsets so they will be even numbers + */ + void adjust_offsets(); + + /** + * @brief Get pixel format register table + * + * @param format_regs Pointer to I2C line structure to fill + */ + i2c_table_t get_pxl_fmt_table(); + + /** + * @brief Get resolution register table + * + * @param resolution_regs Pointer to I2C line structure to fill + */ + i2c_table_t get_res_table(); + + /** + * @brief Get exposure gains register table + * + * @param dBGain Exposure gain in dB, can enable different types of camera gain + */ + i2c_table_t get_exp_gains_table(uint32_t dBGain); + + public: + + /** + * @brief Construct new `IMX219` + * + * @param _conf I2C master config to use for the sensor control + * @param _res Resolution config + * @param _pix_fmt RAW format + * @param _binning 2x2 binning mode + * @param _centralize If set, offsets will be calculated to centralise the frame, otherwise will start from (0, 0) + * @note This will initialize I2C interface + */ + IMX219(i2c_config_t _conf, resolution_t _res, pixel_format_t _pix_fmt, bool _binning, bool _centralize); + + /** + * @brief Construct new `IMX219` + * + * @param _conf I2C master config to use for the sensor control + * @param _res Resolution config + * @param _pix_fmt RAW format + * @param _binning 2x2 binning mode + * @param _x_offset X offset + * @param _y_offset Y offset + * @note This will initialize I2C interface + */ + IMX219(i2c_config_t _conf, resolution_t _res, pixel_format_t _pix_fmt, bool _binning, uint16_t _x_offset, uint16_t _y_offset); + + /** + * @brief Initialise sensor + * + * @returns 0 if succeeded, -1 if failed + */ + int initialize(); + + /** + * @brief Start data stream + * + * @returns 0 if succeeded, -1 if failed + */ + int stream_start(); + + /** + * @brief Stop data stream + * + * @returns 0 if succeeded, -1 if failed + */ + int stream_stop(); + + /** + * @brief Set sensor exposure + * + * @param dBGain Exposure gain in dB, can enable different types of camera gain + * @returns 0 if succeeded, -1 if failed + */ + int set_exposure(uint32_t dBGain); + + /** + * @brief Set sensor resolution, binning mode, and RAW format + * + * @returns 0 if succeeded, -1 if failed + */ + int configure(); + + /** + * @brief Control thread intry, will initialise and configure sensor inside + * + * @param c_control Control channel + */ + void control(chanend_t c_control); + +}; // IMX219 + +} // sensor + +#endif // __cplusplus diff --git a/sensors/src/_sony_imx219/imx219_reg.h b/sensors/src/_sony_imx219/imx219_reg.h new file mode 100644 index 00000000..54e45fb0 --- /dev/null +++ b/sensors/src/_sony_imx219/imx219_reg.h @@ -0,0 +1,150 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +// --------- REG definitions --------------------------------------------------------- + +// Sleep values and registers +#define SLEEP 0xFFFF +#define TRSTUS 200 + +// CSI LANE +#define CSI_LANE_MODE_REG 0x0114 +#define CSI_LANE_MODE_2_LANES 1 +#define CSI_LANE_MODE_4_LANES 3 + +// BINNING +#define BINNING_MODE_REG 0x0174 +#define BINNING_NONE 0x0000 +#define BINNING_2X2 0x0101 + +// Sensor limits +#define SENSOR_X_LIM 3280 +#define SENSOR_Y_LIM 2464 + +// PLL settings +#define PREPLLCK_VT_DIV_REG 0x0304 +#define PREPLLCK_OP_DIV 0x0305 +#define PREDVIDE_2 0x02 +#define PLL_VT_MPY_REG 0x0306 +#define PLL_OP_MPY 0x0010 // no effect in timing performance + +#ifndef PLL_VT_MPY +#define PLL_VT_MPY 0x0027 // pll multiplier +#endif + +// Gain params +#define GAIN_MIN_DB 0 +#define GAIN_MAX_DB 84 + +#ifndef GAIN_DB +#define GAIN_DB 40 +#endif + +// --------- REG GROUP definitions ---------------------------------------------------- +static i2c_line_t imx219_common_regs[] = { + {0x0103, 0x01}, /* software_reset 1, reset the chip */ + {SLEEP, TRSTUS}, /* software_reset 1, reset the chip */ + + {0x0100, 0x00}, /* Mode Select */ + + /* To Access Addresses 3000-5fff, send the following commands */ + {0x30eb, 0x0c}, + {0x30eb, 0x05}, + {0x300a, 0xff}, + {0x300b, 0xff}, + {0x30eb, 0x05}, + {0x30eb, 0x09}, + + /* PLL Clock Table */ + { 0x812A, 0x1800 }, /* EXCK_FREQ 24.00, for 24 Mhz */ + { 0x0304, 0x02 }, /* PREPLLCK_VT_DIV 2, for pre divide by 2 */ + { 0x0305, 0x02 }, /* PREPLLCK_OP_DIV 2, for pre divide by 2 */ + { 0x8306, PLL_VT_MPY}, /* PLL_VT_MPY 0x27, for multiply by 39, pixclk=187.2 MHz */ + { 0x830C, PLL_OP_MPY}, /* PLL_OP_MPY 0x40, for multiply by 64, MIPI clk=768 MHz */ + { 0x0301, 0x0A }, /* VTPXCK_DIV 5, ? */ + { 0x0303, 0x01 }, /* VTSYCK_DIV 1, ? */ + { 0x0309, 0x0A }, /* OPPXCK_DIV 8, has to match RAW8 if you have raw8*/ + { 0x030B, 0x01 }, /* OPSYCK_DIV 1, has to be 1? */ + + // pck clock + {0x1148, 0x00}, + {0x1149, 0xF0}, + + /* Undocumented registers */ + {0x455e, 0x00}, + {0x471e, 0x4b}, + {0x4767, 0x0f}, + {0x4750, 0x14}, + {0x4540, 0x00}, + {0x47b4, 0x14}, + {0x4713, 0x30}, + {0x478b, 0x10}, + {0x478f, 0x10}, + {0x4793, 0x10}, + {0x4797, 0x0e}, + {0x479b, 0x0e}, + + /* Frame Bank Register Group "A" */ + {0x0162, 0x0d}, /* Line_Length_A */ + {0x0163, 0x78}, + {0x0170, 0x01}, /* X_ODD_INC_A */ + {0x0171, 0x01}, /* Y_ODD_INC_A */ + + /* Output setup registers */ + {0x0114, 0x01}, /* CSI 2-Lane Mode */ + {0x0128, 0x00}, /* DPHY Auto Mode */ + {0x012a, 0x18}, /* EXCK_Freq */ + {0x012b, 0x00}, +}; + +static i2c_line_t imx219_lanes_regs[] = { + {CSI_LANE_MODE_REG, CSI_LANE_MODE_2_LANES} +}; + +static i2c_line_t start_regs[] = { + {0x0100, 0x01}, /* mode select streaming on */ +}; + +static i2c_line_t stop_regs[] = { + {0x0100, 0x00}, /* mode select streaming off */ +}; + +// GAIN related settings +#define INTEGRATION_TIMES 41 +#define ANALOGUE_GAINS 20 +#define DIGITAL_GAINS 25 + +static uint16_t gain_integration_times[INTEGRATION_TIMES] = { + 0x00a, 0x00b, 0x00c, 0x00e, + 0x010, 0x012, 0x014, 0x016, + 0x019, 0x01c, 0x020, 0x024, + 0x028, 0x02d, 0x033, 0x039, + 0x040, 0x048, 0x051, 0x05b, + 0x066, 0x072, 0x080, 0x090, + 0x0a2, 0x0b6, 0x0cc, 0x0e5, + 0x101, 0x120, 0x143, 0x16b, + 0x197, 0x1c9, 0x201, 0x23f, + 0x286, 0x2d4, 0x32d, 0x390, + 0x400, +}; + +static uint8_t gain_analogue_gains[ANALOGUE_GAINS + 1] = { + 0, 28, 53, 75, + 95, 112, 128, 142, + 155, 166, 175, 184, + 192, 199, 205, 211, + 215, 220, 224, 227, + 231, +}; + +static uint16_t gain_digital_gains[DIGITAL_GAINS + 1] = { + 0x0100, 0x011f, 0x0142, 0x0169, + 0x0195, 0x01c7, 0x01fe, 0x023d, + 0x0283, 0x02d1, 0x0329, 0x038c, + 0x03fb, 0x0477, 0x0503, 0x059f, + 0x064f, 0x0714, 0x07f1, 0x08e9, + 0x0a00, 0x0b38, 0x0c96, 0x0e20, + 0x0fd9, +}; diff --git a/sensors/src/sensor_control.cpp b/sensors/src/sensor_control.cpp new file mode 100644 index 00000000..35b8401f --- /dev/null +++ b/sensors/src/sensor_control.cpp @@ -0,0 +1,23 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "sensor_control.h" +#include "imx219.hpp" + +using namespace sensor; + +i2c_master_t i2c_ctx; +i2c_config_t i2c_conf; + +void sensor_control(chanend_t c_control) { + // I2C settings + i2c_conf.device_addr = I2C_DEV_ADDR; + i2c_conf.speed = I2C_DEV_SPEED; + i2c_conf.p_scl = XS1_PORT_1N; + i2c_conf.p_sda = XS1_PORT_1O; + i2c_conf.i2c_ctx_ptr = &i2c_ctx; + + IMX219 snsr(i2c_conf, (resolution_t)CONFIG_MODE, (pixel_format_t)CONFIG_MIPI_FORMAT, true, true); + + snsr.control(c_control); +} diff --git a/settings.json b/settings.json new file mode 100644 index 00000000..da2f12b1 --- /dev/null +++ b/settings.json @@ -0,0 +1,5 @@ +{ + "title": "XMOS FWK Camera", + "project": "fwk_camera", + "version": "0.2.0" +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..06a230e4 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,6 @@ +# set xscope config file path for tests +set(CONFIG_XSCOPE_PATH_TEST ${CMAKE_CURRENT_SOURCE_DIR}) + +add_subdirectory(unit_tests) +add_subdirectory(hardware_tests) +include(unity.cmake) # custom cmake file diff --git a/tests/CMakelists.txt b/tests/CMakelists.txt deleted file mode 100644 index f599bce0..00000000 --- a/tests/CMakelists.txt +++ /dev/null @@ -1,34 +0,0 @@ -if( NOT ${Python3_FOUND} ) - message(FATAL_ERROR "Python3 not found for running . ") -endif() - -#copy conftest.py in the build directory since pytest_collect_file only collects tests from the directory tree where conftest.py is present -configure_file( conftest.py conftest.py COPYONLY ) - -add_executable(fwk_voice_ns_unit_tests) -file( GLOB_RECURSE SOURCES_C src/*.c ) -target_sources(fwk_voice_ns_unit_tests - PRIVATE - ${SOURCES_C} - ${UNITY_PATH}/extras/fixture/src/unity_fixture.c) -target_include_directories(fwk_voice_ns_unit_tests - PRIVATE - src) -target_link_libraries(fwk_voice_ns_unit_tests - PUBLIC - fwk_voice::ns - fwk_voice::test::shared::test_utils - fwk_voice::test::shared::unity) -if(${CMAKE_SYSTEM_NAME} STREQUAL XCORE_XS3A) - target_compile_options(fwk_voice_ns_unit_tests - PRIVATE - "-DUNITY_SUPPORT_64" - "-Wno-xcore-fptrgroup" - "-report") - - target_link_options(fwk_voice_ns_unit_tests - PRIVATE - "-target=${XCORE_TARGET}") -else() - target_link_libraries(fwk_voice_ns_unit_tests m) -endif() \ No newline at end of file diff --git a/tests/README.rst b/tests/README.rst new file mode 100644 index 00000000..e89fddcd --- /dev/null +++ b/tests/README.rst @@ -0,0 +1,52 @@ +================================ +Test Folder +================================ + +This folder contains various types of tests for the project. +It contains two types of tests: + 1. Unit tests + 2. Hardware tests + +Build Tests +============= + +Run the following commands from the top level: + +Linux, Mac +---------- + +.. code-block:: console + + cmake -B build --toolchain=xmos_cmake_toolchain/xs3a.cmake + make -C build tests + +Windows +------- + +.. code-block:: console + + cmake -G "Ninja" -B build --toolchain=xmos_cmake_toolchain\xs3a.cmake + ninja -C build tests + +Running the tests +================= + +.. note:: + 1. Hardware tests require `xscope_fileio` to be installed. + 2. Run the following commands from the `fwk_camera` top level. + +Run unit tests +-------------- + +.. code-block:: console + + xsim --xscope "-offline trace.xmt" build/tests/unit_tests/test_camera.xe + # or + xrun --xscope build/tests/unit_tests/test_camera.xe + +Run hardware tests +------------------ + +.. code-block:: console + + pytest diff --git a/tests/Unity b/tests/Unity new file mode 160000 index 00000000..f23d8b25 --- /dev/null +++ b/tests/Unity @@ -0,0 +1 @@ +Subproject commit f23d8b25cd7e1fb89cb87e910ee2bed011d4ec2e diff --git a/examples/simple_timing/src/config.xscope b/tests/config.xscope similarity index 100% rename from examples/simple_timing/src/config.xscope rename to tests/config.xscope diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 91b85d75..00000000 --- a/tests/conftest.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright 2022 XMOS LIMITED. -# This Software is subject to the terms of the XMOS Public Licence: Version 1. -from builtins import str -import os.path -import pytest -import subprocess -import xtagctl - - -def pytest_collect_file(parent, path): - if(path.ext == ".xe"): - print('path = ',path) - return UnityTestSource.from_parent(parent, fspath=path) - -class UnityTestSource(pytest.File): - def collect(self): - # Find the binary built from the runner for this test file - # - # Assume the following directory layout: - # unit_tests/ <- Test root directory - # |-- bin/ <- Compiled binaries of the test runners - # |-- conftest.py <- This file - # |-- runners/ <- Auto-generated buildable source of test binaries - # |-- src/ <- Unity test functions - # `-- wscript <- Build system file used to generate/build runners - print("self.name ", self.fspath) - yield UnityTestExecutable.from_parent(self, fspath=self.fspath, name=self.name) - - -class UnityTestExecutable(pytest.Item): - def __init__(self, fspath, name, parent): - super(UnityTestExecutable, self).__init__(name, parent) - self.fspath = fspath - self._nodeid = self.name # Override the naming to suit C better - - def runtest(self): - # Run the binary in the simulator - simulator_fail = False - test_output = None - try: - print("run xrun for executable ", self.fspath) - with xtagctl.acquire("XCORE-AI-EXPLORER") as adapter_id: - test_output = subprocess.check_output(['xrun', '--xscope', '--io', '--adapter-id', adapter_id, self.fspath], text=True, stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as e: - # Unity exits non-zero if an assertion fails - simulator_fail = True - test_output = e.output - - # Parse the Unity output - unity_pass = False - test_output = test_output.split('\n') - for line in test_output: - if 'test' in line: - test_report = line.split(':') - # Unity output is as follows: - # :::PASS - # :::FAIL: - test_source = test_report[0] - line_number = test_report[1] - test_case = test_report[2] - result = test_report[3] - failure_reason = None - print(('\n {}()'.format(test_case)), end=' ') - if result == 'PASS': - unity_pass = True - continue - if result == 'FAIL': - failure_reason = test_report[4] - print('') # Insert line break after test_case print - raise UnityTestException(self, {'test_source': test_source, - 'line_number': line_number, - 'test_case': test_case, - 'failure_reason': - failure_reason}) - - if simulator_fail: - raise Exception(self, "Simulation failed.") - #if not unity_pass: - # raise Exception(self, "Unity test output not found.") - print('') # Insert line break after final test_case which passed - - def repr_failure(self, excinfo): - if isinstance(excinfo.value, UnityTestException): - return '\n'.join([str(self.parent).strip('<>'), - '{}:{}:{}()'.format( - excinfo.value[1]['test_source'], - excinfo.value[1]['line_number'], - excinfo.value[1]['test_case']), - 'Failure reason:', - excinfo.value[1]['failure_reason']]) - else: - return str(excinfo.value) - - def reportinfo(self): - # It's not possible to give sensible line number info for an executable - # so we return it as 0. - # - # The source line number will instead be recovered from the Unity print - # statements. - return self.fspath, 0, self.name - - -class UnityTestException(Exception): - pass \ No newline at end of file diff --git a/tests/hardware_tests/CMakeLists.txt b/tests/hardware_tests/CMakeLists.txt new file mode 100644 index 00000000..61b3c277 --- /dev/null +++ b/tests/hardware_tests/CMakeLists.txt @@ -0,0 +1,2 @@ +# Add tests directories +add_subdirectory(test_timing) diff --git a/tests/hardware_tests/inject_images/README.rst b/tests/hardware_tests/inject_images/README.rst new file mode 100644 index 00000000..1b056ccb --- /dev/null +++ b/tests/hardware_tests/inject_images/README.rst @@ -0,0 +1,11 @@ +Run inject image test +--------------------- + +From top level directory of the repo, run the following command: + +.. code-block:: console + + pytest -v -s tests/hardware_tests/inject_images/test_emulated_images.py + +The images will be injected from the ``input`` folder into the device, processed and downsampled. +The output of the pipeline will be saved in the ``output`` directory. diff --git a/tests/hardware_tests/inject_images/test_emulated_images.py b/tests/hardware_tests/inject_images/test_emulated_images.py new file mode 100644 index 00000000..2cd67d3a --- /dev/null +++ b/tests/hardware_tests/inject_images/test_emulated_images.py @@ -0,0 +1,69 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +import os +import time +import glob + +from pathlib import Path +import sys +import cv2 +import numpy as np + +from matplotlib import pyplot as plt +from PIL import Image + +# load dotenv +from dotenv import load_dotenv +load_dotenv() + +# path definitions +cwd = Path(__file__).parent.resolve() +top_level = str(cwd.parent.parent.parent.resolve()) +examples = top_level + "\\build\\examples" +python_path = top_level + "\\python" +TARGET_NAME = "/take_picture_local/example_take_picture_local.xe" + +# python dependencies +sys.path.append(str(python_path)) +from encode_raw8 import encode_raw8 +from run_xscope_bin import * + + +def test_pipeline_image(): + # set the test folder directory + test_imgs_dir = os.getenv("TEST_IMAGES_DIR") + assert test_imgs_dir is not None + + # 1 - create dir if they dont exist + dirs = ["input", "raw8", "output"] + full_paths = [os.path.join(test_imgs_dir, dir) for dir in dirs] + + for p in full_paths: + Path(p).mkdir(parents=False, exist_ok=True) + + # 2 - Take all input images from input folder + glob_pattern = os.path.join(test_imgs_dir, "input", "*.png") + files = glob.glob(glob_pattern) + + # 3 - Encode them to raw8 in the raw8 folder + out_paths = [] + + for file in files: + img_name = os.path.basename(file).replace(".png", ".raw") + out_name = os.path.join(test_imgs_dir, "raw8", img_name) + out_paths.append(out_name) + encode_raw8(file, out_name) + + # 4 - Then run the pipeline on the raw8 images + for raw_img in out_paths: + print("running pipeline on: ", raw_img) + # copy the image to tmp file + shutil.copy(raw_img, "tmp.raw") + # run the inference + run(examples + TARGET_NAME) + # Save capture.bmp to output folder + img_name = os.path.basename(raw_img).replace(".raw", ".bmp") + shutil.copy("capture.bmp", os.path.join(test_imgs_dir, "output", img_name)) + + time.sleep(0.2) diff --git a/tests/hardware_tests/raw_vs_downsampled/README.rst b/tests/hardware_tests/raw_vs_downsampled/README.rst new file mode 100644 index 00000000..5890f079 --- /dev/null +++ b/tests/hardware_tests/raw_vs_downsampled/README.rst @@ -0,0 +1,8 @@ +Run raw vs downsampled image test +--------------------------------- + +From top level directory of the repo, run the following command: + +.. code-block:: console + + pytest -v -s tests/hardware_tests/raw_vs_downsampled/test_downs_vs_raw.py diff --git a/tests/hardware_tests/raw_vs_downsampled/test_downs_vs_raw.py b/tests/hardware_tests/raw_vs_downsampled/test_downs_vs_raw.py new file mode 100644 index 00000000..bb579c07 --- /dev/null +++ b/tests/hardware_tests/raw_vs_downsampled/test_downs_vs_raw.py @@ -0,0 +1,107 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +import os +import time +from pathlib import Path +import sys +import cv2 +import numpy as np + +from matplotlib import pyplot as plt +from PIL import Image + +# path definitions +cwd = Path(__file__).parent.resolve() +top_level = str(cwd.parent.parent.parent.resolve()) +examples = top_level + "\\build\\examples" +python_path = top_level + "\\python" + +# globals +ENABLE_IMSHOW = True +RUN_XE = True +MIN_PSNR = 10 # DB, peak signal to noise ratio +MIN_SCORE = 0.7 # 70% of similarity + +# python dependencies +sys.path.append(str(python_path)) +from FIR_pipeline import FIR_pipeline_func +from utils import compute_score, peak_signal_noise_ratio, pipeline_raw8 +from run_xscope_bin import * + +def load_image(input_name, height, width, ch=0): + buffer = np.fromfile(input_name, dtype=np.uint8) + shape = (height, width) if ch == 0 else (height, width, ch) + buffer = buffer.reshape(shape) + return buffer + + +def plot_images(imgs, results): + # Show the 3 imageslist( + fig, axes = plt.subplots(2, 3) + titles = [ + "RAW decode8", + "RAW python FIR", + "Downsampled\nXcore pipeline" + ] + def add_caption(ax, caption): + axes[0, i].text(0, -0.1, caption, transform=ax.transAxes, + ha='left', va='top', fontsize=8) + + for i, title in enumerate(titles): + axes[0,i].set_title(title) + axes[0,i].imshow(imgs[i]) + axes[1,i].set_ylim([0, 3000]) + # histogram + for j, col in enumerate(['r', 'g', 'b']): + axes[1,i].hist(imgs[i][:,:,j].ravel(), bins=256, alpha=0.5, color=col) + + # text + img_psnr = results[0][i] + score = results[1][i] + + text = f"SSI: {score:.2f}, \nPSNR: {img_psnr:.2f}" + add_caption(axes[0,i], text) + + plt.subplots_adjust(wspace=0.05) + list(map(lambda axi: axi.set_axis_off(), axes.ravel()[0:3])) + plt.show() + + +def test_downsampled_vs_raw(): + if RUN_XE: + run(examples + '/take_picture_raw/example_take_picture_raw.xe') + time.sleep(1) + run(examples + '/take_picture_downsample/example_take_picture_downsample.xe') + time.sleep(1) + + # 0 - base image raw 640x480 + width, height, input_name = 640, 480, 'capture.raw' + img_raw = load_image(input_name, height, width) + # 1 - raw with decode 8 pipeline + ref_image = pipeline_raw8(img_raw) + ref_image = cv2.resize(ref_image, (width // 4, height // 4), interpolation=cv2.INTER_AREA) + # 2 - raw with simplified FIR pipeline + img_raw_FIR = FIR_pipeline_func(img_raw, height, width, ENABLE_IMSHOW) + # 3 - downsampled image in the explorer board + input_name_dwn = 'capture.bin' + width, height = 160, 120 + img_dwn = load_image(input_name_dwn, height, width, 3) + # compute results + imgs = [ref_image, img_raw_FIR, img_dwn] + img_psnrs = [peak_signal_noise_ratio(ref_image, x) for x in imgs] + scores = [compute_score(ref_image, x) for x in imgs] + results = (img_psnrs, scores) + print(results) + # if we want to plot the images + if ENABLE_IMSHOW: + plot_images(imgs, results) + # assert results are within the expected range + error_msg1 = f"PSNR should be greater than {MIN_PSNR} DB" + error_msg2 = f"SSI should be greater than {MIN_SCORE} %" + assert all(psnr > MIN_PSNR for psnr in img_psnrs), error_msg1+str(img_psnrs) + assert all(score > MIN_SCORE for score in scores), error_msg2+str(scores) + +if __name__ == '__main__': + test_downsampled_vs_raw() + diff --git a/tests/hardware_tests/test_timing/CMakeLists.txt b/tests/hardware_tests/test_timing/CMakeLists.txt new file mode 100644 index 00000000..cf3f0fee --- /dev/null +++ b/tests/hardware_tests/test_timing/CMakeLists.txt @@ -0,0 +1,39 @@ +# ############################################################################## +set(TARGET test_timing) + +set(SOURCES + src/app.c + src/main.xc + src/MipiGatherTiming.S +) + +set(APP_COMPILER_FLAGS + -Os + -g + -report + -Wall + -Werror + -fxscope + -target=${XCORE_TARGET} + ${CONFIG_XSCOPE_PATH_TEST}/config.xscope +) + +set(APP_LINK_OPTIONS + "-report" + "-target=${XCORE_TARGET}" + "${CONFIG_XSCOPE_PATH_TEST}/config.xscope" +) + +set(APP_COMMON_LINK_LIBRARIES + fwk_camera::mipi + lib_i2c + fwk_camera::sensors + fwk_camera::camera +) + +# ############################################################################## +add_executable(${TARGET}) +target_sources(${TARGET} PUBLIC ${SOURCES}) +target_compile_options(${TARGET} PRIVATE ${APP_COMPILER_FLAGS}) +target_link_libraries(${TARGET} PUBLIC ${APP_COMMON_LINK_LIBRARIES}) +target_link_options(${TARGET} PRIVATE ${APP_LINK_OPTIONS}) diff --git a/tests/hardware_tests/test_timing/README.rst b/tests/hardware_tests/test_timing/README.rst new file mode 100644 index 00000000..4e02bc81 --- /dev/null +++ b/tests/hardware_tests/test_timing/README.rst @@ -0,0 +1,17 @@ +Test timing +----------- + +This test is used to measure the time of RAW data stream from mipi. + +Legend: + +* SoF : Start of frame +* EoF : End of frame +* SoL : Start of Line +* EoL : End of Line + +To run this test, from top level directory of the repo, run the following command: + +.. code-block:: console + + xrun --xscope build/tests/hardware_tests/test_timing/test_timing.xe diff --git a/tests/hardware_tests/test_timing/src/MipiGatherTiming.S b/tests/hardware_tests/test_timing/src/MipiGatherTiming.S new file mode 100644 index 00000000..157ad051 --- /dev/null +++ b/tests/hardware_tests/test_timing/src/MipiGatherTiming.S @@ -0,0 +1,203 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +/** + * + * This function is a way to gather timing information about a device so that + * its behavior can be reasoned about. + * + * The output is a table uint32_t[N][3], where each row corresponds to one + * received packet. The first column is the packet header (containing data type + * and packet length). The second column is when the header was received. The + * third column is when the end of the packet is received. + * + * This function returns when the table has been filled. + * + * This function throws away all packet data other than the headers. + * +*/ + +// Wait for EOF before populating the packet log. Useful to ensure a clean log. +#ifndef WAIT_FOR_EOF +#define WAIT_FOR_EOF (1) +#endif + + +.issue_mode dual + +#define FUNCTION_NAME MipiGatherTiming +#define NSTACKWORDS 8 + +.globl FUNCTION_NAME.nstackwords +.globl FUNCTION_NAME.maxthreads +.globl FUNCTION_NAME.maxtimers +.globl FUNCTION_NAME.maxchanends + +.linkset FUNCTION_NAME.nstackwords, NSTACKWORDS +.linkset FUNCTION_NAME.maxchanends, 0 +.linkset FUNCTION_NAME.maxtimers, 0 +.linkset FUNCTION_NAME.maxthreads, 0 + +.globl FUNCTION_NAME +.type FUNCTION_NAME, @function +.text +.cc_top FUNCTION_NAME.func, FUNCTION_NAME + +#define P_RXD r0 +#define P_RXA r1 +#define TABLE r2 +#define LEN r3 + +#define HEADER r4 +#define S0 r5 +#define S1 r6 +#define _12 r7 + + +/**************************************************** + **************************************************** + + typedef struct { + uint32_t header; + uint32_t start_time; + uint32_t end_time; + } packet_timing_t; + + + void MipiGatherTiming( + const unsigned RxD, + const unsigned RxA, + packet_timing_t table[], + const unsigned N); + + **************************************************** + **************************************************** +*/ + +.align 4 +.skip 0 +FUNCTION_NAME: + dualentsp NSTACKWORDS + std r4, r5, sp[1] + std r6, r7, sp[2] + std r8, r9, sp[3] + + +// Enable MIPI shim hardware +{ ldc r4, 0x01 ; ldc r9, 8 } +{ shl r4, r4, r9 ; sub LEN, LEN, 1 } + ldc r9, XS1_PS_XCORE_CTRL0 + get r11, ps[r9] +{ or r4, r4, r11 ; ldc _12, 12 } + set ps[r9], r4 + + +// Enable RxA event + setc res[P_RXA], XS1_SETC_COND_EQ // <-- event on logic value of RxA + setc res[P_RXA], XS1_SETC_IE_MODE_EVENT // <-- do event, not interrupt + ldc r11, 0 + setd res[P_RXA], r11 // <-- condition is low logic value + ldap r11, .L_MIPI_RXA_LOW + setv res[P_RXA], r11 + +// Enable events for the thread + setsr 1 // Sets the EEBLE bit (I couldn't find a #define!) + +#if (WAIT_FOR_EOF) + + ldap r11, .L_MULP + setv res[P_RXA], r11 + +// Wait to receive an end-of-frame packet before collecting data +.L_BLORT: +{ in HEADER, res[P_RXD] ; ldc S1, 0x00000030 } +{ zext HEADER, 6 ; and S1, S1, HEADER } +{ eet S1, res[P_RXA] ; bt S1, .L_SPOOT } +{ eq S1, HEADER, 1 ; } +{ ; bf S1, .L_BLORT } +// It is an EOF packet + ldap r11, .L_MIPI_RXA_LOW +{ setv res[P_RXA], r11 ; bu .L_GOT_EOF } + + +.L_SPOOT: // long packet. Just burn the data + // an RxA low event will break us out + { in S0, res[P_RXD] ; } + { ; bu .L_SPOOT } + + +.align 32 +.L_MULP: + +{ in r11, res[P_RXA] ; } +{ endin r11, res[P_RXD] ; } +{ in S1, res[P_RXD] ; } +{ edu res[P_RXA] ; } +{ setsr 1 ; bu .L_BLORT } + +.L_GOT_EOF: + +#else + +// Check whether the RxA signal is currently high. +// If so, we need to wait until it is low or we will start reading mid-packet +.L_WAIT_LOOP: +{ peek S0, res[P_RXA] ; } +{ ; bt S0, .L_WAIT_LOOP } + +#endif + +.L_WAIT_FOR_NEXT_PACKET: + +// Receive packet header from data port +{ in HEADER, res[P_RXD] ; ldc S1, 0x00000030 } +{ gettime r11 ; stw HEADER, TABLE[0] } +{ and S1, S1, HEADER ; stw r11, TABLE[1] } + +// If short packet, duplicate timestamp into column 3. +// Otherwise, wait for end of packet. +{ eet S1, res[P_RXA] ; bt S1, .L_RX_LONG } + +.L_RX_SHORT: + +{ add TABLE, TABLE, _12 ; stw r11, TABLE[2] } +{ sub LEN, LEN, 1 ; bf LEN, .L_EXIT_FUNC } +{ ; bu .L_WAIT_FOR_NEXT_PACKET } + + +.L_RX_LONG: +// Burn through the rest of the packet. We don't care about the payload. +.L_RX_LONG_LOOP: + { in S0, res[P_RXD] ; } + { ; bu .L_RX_LONG_LOOP } + + +.align 32 +.L_MIPI_RXA_LOW: + +{ gettime r11 ; } +{ in r11, res[P_RXA] ; stw r11, TABLE[2] } +{ endin r11, res[P_RXD] ; add TABLE, TABLE, _12 } +{ in S1, res[P_RXD] ; } +{ edu res[P_RXA] ; } +{ sub LEN, LEN, 1 ; bf LEN, .L_EXIT_FUNC } +{ setsr 1 ; bu .L_WAIT_FOR_NEXT_PACKET } + + +.L_EXIT_FUNC: + + // This doesn't actually undo the event enabling and whatnot, but that's fine. + + ldd r8, r9, sp[3] + ldd r6, r7, sp[2] + ldd r4, r5, sp[1] + retsp NSTACKWORDS + +.size FUNCTION_NAME, .-FUNCTION_NAME +.cc_bottom FUNCTION_NAME.func + + + diff --git a/tests/hardware_tests/test_timing/src/app.c b/tests/hardware_tests/test_timing/src/app.c new file mode 100644 index 00000000..d7cca5f4 --- /dev/null +++ b/tests/hardware_tests/test_timing/src/app.c @@ -0,0 +1,523 @@ +// Copyright 2020-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +#include +#include +#include +#include + +#include "i2c.h" +#include "app.h" +#include "sensor.h" +#include "sensor_control.h" +#include "mipi.h" + +//////////////////////////////////////////////////////////////// +// if true, print warnings when unexpected packet sequences are observed +#ifndef SEQUENCE_WARNINGS +# define SEQUENCE_WARNINGS (0) +#endif + +// if true, don't assume we started listening for packets outside of a frame, +// so suppress any warnings until we see an SoF. +#ifndef IGNORE_SEQ_WARN_BEFORE_SOF +# define IGNORE_SEQ_WARN_BEFORE_SOF (0) +#endif + +/* + astew: I'm just trying out the stuff below to decide whether I think it's + awful +*/ +#define WARNING(COND) for(int fgsfds = 0; (COND) && (fgsfds < 1); fgsfds++) +#define ONLY_IF(COND) if(!(COND)) break +#define NOT_IF(COND) if((COND)) break + + +#ifndef REBASE_TIMESTAMPS +# define REBASE_TIMESTAMPS (1) +#endif + +#ifndef PRINT_LOG_SUMMARY +# define PRINT_LOG_SUMMARY (1) +#endif + +#ifndef PRINT_TIMING_STATS +# define PRINT_TIMING_STATS (1) +#endif + +#ifndef WRITE_LOG_TO_FILE +# define WRITE_LOG_TO_FILE (1) +#endif + + +#ifndef PACKET_LOG_FILE +# define PACKET_LOG_FILE "mipi_packet_log.csv" +#endif + + +#define TABLE_ROWS (12020) + +#define CSV_FORMATTING "0x%08X,0x%08X,0x%08X\n" +#define CSV_FORMATTING_LEN 33 +#define CSV_HEADER "HEADER,START,END\n" +#define CSV_HEADER_LEN 17 + +typedef struct { + uint32_t header; + uint32_t start_time; + uint32_t end_time; +} packet_timing_t; + +typedef struct { + uint32_t min; + uint32_t max; + uint64_t total; + uint32_t count; +} timing_stats_t; + +typedef struct { + struct { + uint32_t start_of_frame; + uint32_t end_of_frame; + uint32_t yuv_data; + uint32_t raw_data; + uint32_t generic_long; + uint32_t other_short; + uint32_t other_long; + } packet_count; + + struct { + // Time between Start of Frame packet and End of Frame packet + timing_stats_t SOF_to_EOF; + // Time between the beginning of Start of Frame packet and the next + // Start of Frame packet + timing_stats_t SOF_to_SOF; + // Time between the beginning of one packet of line data and the + // beginning of the next packet of line data + timing_stats_t SOL_to_SOL; + // Time between line data packet header and end of line data packet + timing_stats_t SOL_to_EOL; + } timing; +} mipi_timing_info_t; + +// Function to grab timing info +void MipiGatherTiming( + in_buffered_port_32_t p_mipi_rxd, + port_t p_mipi_rxa, + packet_timing_t table[], + const unsigned N); + +// Inclusive between +static inline +unsigned between(unsigned low, unsigned x, unsigned high){ + return (x >= low) && (x <= high); +} + +// Basically just subtract the first start time from every timestamp so that +// the first packet says it starts at 0. +static +uint32_t rebaseTimestamps( + packet_timing_t packet[], + unsigned N) +{ + const uint32_t time_offset = packet[0].start_time; + for(int k = 0; k < N; k++){ + packet[k].start_time -= time_offset; + packet[k].end_time -= time_offset; + } + + return time_offset; +} + +static +void updateTimingStats( + timing_stats_t* stats, + uint32_t timespan) +{ + if(stats->count == 0){ + stats->min = stats->max = timespan; + } else { + stats->min = (timespan < stats->min)? timespan : stats->min; + stats->max = (timespan > stats->max)? timespan : stats->max; + } + + stats->count++; + stats->total += timespan; +} + +static +mipi_timing_info_t extractTimingInfo( + packet_timing_t packet[], + unsigned N) +{ + // Used for tracking inter-packet timing. + // If any are 0, that means they haven't been observed yet. + struct { + unsigned SoF; + unsigned EoF; + unsigned SoL; + unsigned EoL; + } last = {0}; + + mipi_timing_info_t result; + memset(&result, 0, sizeof(mipi_timing_info_t)); + + // Warn if we're seeing an odd sequence + unsigned inside_frame = 0; + + for(int k = 0; k < N; k++){ + + packet_timing_t* pkt = &packet[k]; + + unsigned is_long = MIPI_IS_LONG_PACKET(pkt->header)? 1:0; + unsigned data_type = MIPI_GET_DATA_TYPE(pkt->header); + + unsigned duration = pkt->end_time - pkt->start_time; + + if(!is_long){ + // Short packet + xassert(duration == 0); + + if(data_type == MIPI_DT_FRAME_START){ + + ///// Start of Frame packet + + // for(int fgsfds = 0; (inside_frame) && (fgsfds < 1); fgsfds++) { + WARNING(inside_frame) { + ONLY_IF (SEQUENCE_WARNINGS); + + printf("Warning: Observed SoF while inside frame." + " (Log Index: %u)\n", k); + } + + if(last.SoF){ + unsigned delta = pkt->start_time - last.SoF; + updateTimingStats(&result.timing.SOF_to_SOF, delta); + } + + last.SoF = pkt->start_time; + result.packet_count.start_of_frame++; + inside_frame = 1; + + } else if(data_type == MIPI_DT_FRAME_END) { + + ///// End of Frame packet + + WARNING(!inside_frame) { + ONLY_IF (SEQUENCE_WARNINGS); + NOT_IF (last.SoF == 0 && IGNORE_SEQ_WARN_BEFORE_SOF); + + printf("Warning: Observed EoF while outside frame." + " (Log Index: %u)\n", k); + } + + if(last.SoF){ + unsigned delta = pkt->start_time - last.SoF; + updateTimingStats(&result.timing.SOF_to_EOF, delta); + } + + last.EoF = pkt->start_time; + result.packet_count.end_of_frame++; + inside_frame = last.SoL = last.EoL = 0; + + } else { + + ///// Some other short packet + result.packet_count.other_short++; + } + + + } else { + // Long packet + xassert(duration); + + WARNING(!inside_frame) { + ONLY_IF (SEQUENCE_WARNINGS); + NOT_IF (last.SoF == 0 && IGNORE_SEQ_WARN_BEFORE_SOF); + + printf("Warning: Observed long packet while outside frame." + " (Log Index: %u)\n", k); + } + + + if(between(MIPI_DT_YUV420_8BIT, data_type, MIPI_DT_YUV422_10BIT) ){ + // yuv packet + result.packet_count.yuv_data++; + } else if(between(MIPI_DT_NULL, data_type, MIPI_DT_RESERVED_0x17)){ + // Generic long packet + result.packet_count.generic_long++; + } else if(between(MIPI_DT_RAW24, data_type, MIPI_DT_RAW20)){ + // raw packet + result.packet_count.raw_data++; + } else { + // other long packet + result.packet_count.other_long++; + } + + if(last.SoL){ + unsigned delta = pkt->start_time - last.SoL; + updateTimingStats(&result.timing.SOL_to_SOL, delta); + } + + updateTimingStats(&result.timing.SOL_to_EOL, duration); + + last.SoL = pkt->start_time; + last.EoL = pkt->end_time; + } + } + + return result; +} + +static +void printTimingInfo( + mipi_timing_info_t* info) +{ + unsigned total_pkt = info->packet_count.start_of_frame + + info->packet_count.end_of_frame + + info->packet_count.yuv_data + + info->packet_count.generic_long + + info->packet_count.raw_data + + info->packet_count.other_short + + info->packet_count.other_long; + + printf("#### Packet Counts ####\n"); + printf(" Total: %5u\n", total_pkt); + printf("\n"); + printf(" ## Short ##\n"); + printf(" Frame Start: %5lu\n", info->packet_count.start_of_frame); + printf(" Frame End: %5lu\n", info->packet_count.end_of_frame ); + printf(" Other Short: %5lu\n", info->packet_count.other_short ); + printf("\n"); + printf(" ## Long ##\n"); + printf(" YUV Data: %5lu\n", info->packet_count.yuv_data ); + printf(" Generic Long:%5lu\n", info->packet_count.generic_long ); + printf(" RAW Data: %5lu\n", info->packet_count.raw_data ); + printf(" Other Long: %5lu\n", info->packet_count.other_long ); + + printf("\n\n"); + printf("#### Timing Info ####\n\n"); + + printf(" | Min (us) | Ave (us) | Max (us) \n"); + printf("-------------|--------------|--------------|--------------\n"); + + #define FMT "| % 12.2f | % 12.2f | % 12.2f" + + printf(" SoF to SoF " FMT "\n", + info->timing.SOF_to_SOF.min * 0.01, + info->timing.SOF_to_SOF.total / (100.0 * info->timing.SOF_to_SOF.count), + info->timing.SOF_to_SOF.max * 0.01); + + printf(" SoF to EoF " FMT "\n", + info->timing.SOF_to_EOF.min * 0.01, + info->timing.SOF_to_EOF.total / (100.0 * info->timing.SOF_to_EOF.count), + info->timing.SOF_to_EOF.max * 0.01); + + printf(" SoL to SoL " FMT "\n", + info->timing.SOL_to_SOL.min * 0.01, + info->timing.SOL_to_SOL.total / (100.0 * info->timing.SOL_to_SOL.count), + info->timing.SOL_to_SOL.max * 0.01); + + printf(" SoL to EoL " FMT "\n", + info->timing.SOL_to_EOL.min * 0.01, + info->timing.SOL_to_EOL.total / (100.0 * info->timing.SOL_to_EOL.count), + info->timing.SOL_to_EOL.max * 0.01); + + #undef FMT + + printf("\n Note: Thread MIPS is also the number of instructions per microsecond.\n"); + printf( " Scaling above times by thread MIPS gives instruction budget.\n"); + +} + +static +void writePacketLog( + const char* filename, + packet_timing_t packet[], + unsigned N) +{ + FILE * log_file = fopen(filename, "w"); + + if(!log_file){ + printf("\n\nWARNING: Couldn't open '%s' to write packet log.\n\n", filename); + return; + } + + // WRITE HEADER + fwrite(CSV_HEADER, CSV_HEADER_LEN, 1, log_file); + + for(int k = 0; k < N; k++){ + fprintf(log_file, CSV_FORMATTING, + (unsigned int)packet[k].header, (unsigned int)packet[k].start_time, (unsigned int)packet[k].end_time); + } + + fclose(log_file); +} + +static inline +unsigned can_aggregate(mipi_header_t a, mipi_header_t b) +{ + return (MIPI_GET_DATA_TYPE(a) == MIPI_GET_DATA_TYPE(b)) + && (MIPI_GET_WORD_COUNT(a) == MIPI_GET_WORD_COUNT(b)); +} + +// Instead of printing full log, summarize it.. +// - roll up a sequence of consecutive "line" packets into a single line of text +static +void printPacketLogSummary( + packet_timing_t packet[], + unsigned N) +{ + + printf("#### Packet Log Summary ####\n\n"); + + printf(" Packet | Data | Word | Gap | Start | Duration | End | Packet | \n"); + printf(" Index | Type | Count | Time (us) | Time (us) | (us) | Time (us) | Count | Misc\n"); + printf("--------|------|-------|------------|------------|------------|------------|--------|-----\n"); + +#define FMT " %5u | 0x%02X | %5u | %10.2f | %10.2f | %10.2f | %10.2f | %6u | %s\n" + + double prev_end_time_us = 0; + + char str_extra[100] = {0}; + + int k = 0; + while(k < N) { + + if(!(MIPI_IS_LONG_PACKET(packet[k].header))) { + + packet_timing_t* pkt = &packet[k]; + + //unsigned is_long = MIPI_IS_LONG_PACKET(pkt->header)? 1:0; + unsigned data_type = MIPI_GET_DATA_TYPE(pkt->header); + unsigned word_count = MIPI_GET_WORD_COUNT(pkt->header); + + double start_time_us = 0.01 * pkt->start_time; + double end_time_us = 0.01 * pkt->end_time; + double duration_us = 0; + double gap_time_us = start_time_us - prev_end_time_us; + + if(data_type == MIPI_DT_FRAME_START) sprintf(str_extra, "Frame Start" ); + else if(data_type == MIPI_DT_FRAME_END) sprintf(str_extra, "Frame End" ); + else sprintf(str_extra, "Unknown Short Packet"); + + + + printf(FMT, k, data_type, word_count, gap_time_us, start_time_us, duration_us, end_time_us, 1, str_extra); + + prev_end_time_us = start_time_us; + k++; + } else { + + // It's a long packet. + // Aggregate packets until we encounter one with a different + // (data type, payload size) than the current one. + + int i = k + 1; + while((i < N) && can_aggregate(packet[k].header, packet[i].header)) + i++; + + unsigned pkt_count = i - k; + unsigned dt = MIPI_GET_DATA_TYPE(packet[k].header); + unsigned wc = MIPI_GET_WORD_COUNT(packet[k].header); + uint32_t start_time = packet[k].start_time; + uint32_t end_time = packet[i-1].end_time; + double start_time_us = 0.01 * start_time; + double end_time_us = 0.01 * end_time; + double duration_us = end_time_us - start_time_us; + double gap_time_us = start_time_us - prev_end_time_us; + + if( between(0x10, dt, 0x17)) sprintf(str_extra, "Generic Long Packet"); + else if(between(0x18, dt, 0x1F)) sprintf(str_extra, "YUV Data"); + else if(between(0x20, dt, 0x26)) sprintf(str_extra, "RGB Data"); + else if(between(0x27, dt, 0x2F)) sprintf(str_extra, "RAW Data"); + else sprintf(str_extra, "Unknown Long Packet"); + + printf(FMT, k, dt, wc, gap_time_us, start_time_us, duration_us, end_time_us, + pkt_count, str_extra); + + + prev_end_time_us = end_time_us; + k = i; + } + + #undef FMT + } +} + +static +packet_timing_t packet_log[TABLE_ROWS]; + +void mipi_main(chanend_t c_control) +{ + port_t p_mipi_clk = XS1_PORT_1O; + port_t p_mipi_rxa = XS1_PORT_1E; + port_t p_mipi_rxv = XS1_PORT_1I; + in_buffered_port_32_t p_mipi_rxd = XS1_PORT_8A; + xclock_t clk_mipi = MIPI_CLKBLK; + + port_enable(p_mipi_clk); + port_enable(p_mipi_rxa); + port_enable(p_mipi_rxv); + port_start_buffered(p_mipi_rxd, 32); + clock_enable(clk_mipi); + + // Tile ids have weird values, so we get them with this API + unsigned tileid = get_local_tile_id(); + + write_sswitch_reg(tileid, XS1_SSWITCH_MIPI_DPHY_CFG3_NUM, 0x7E42); + + unsigned mipi_shim_cfg0 = MIPI_SHIM_CFG0_PACK(0,0,0,0,0); + + MipiPacketRx_init(tileid, + p_mipi_rxd, + p_mipi_rxv, + p_mipi_rxa, + p_mipi_clk, + clk_mipi, + mipi_shim_cfg0, + MIPI_CLK_DIV, + MIPI_CFG_CLK_DIV); + + printf("Waiting for %u MIPI packets...\n", TABLE_ROWS); + MipiGatherTiming(p_mipi_rxd, p_mipi_rxa, packet_log, TABLE_ROWS); + printf(" ... done.\n\n"); + + if(REBASE_TIMESTAMPS) { + printf("Rebasing timestamps...\n"); + uint32_t time_offset = rebaseTimestamps(packet_log, TABLE_ROWS); + printf(" ... time offset is %.2f us.\n\n\n", 0.01 * time_offset); + } + + if(PRINT_LOG_SUMMARY) { + printPacketLogSummary(packet_log, TABLE_ROWS); + printf("\n\n"); + } + + if(PRINT_TIMING_STATS){ + mipi_timing_info_t timing = extractTimingInfo(packet_log, TABLE_ROWS); + printTimingInfo(&timing); + printf("\n\n"); + } + + if(WRITE_LOG_TO_FILE){ + printf("Writing packet log to %s..\n", PACKET_LOG_FILE); + writePacketLog(PACKET_LOG_FILE, packet_log, TABLE_ROWS); + printf(" ...done.\n\n"); + } + + uint32_t encoded_cmd = ENCODE(SENSOR_STREAM_STOP, 0); + chan_out_word(c_control, encoded_cmd); + chan_in_word(c_control); + + port_disable(p_mipi_clk); + port_disable(p_mipi_rxa); + port_disable(p_mipi_rxv); + port_disable(p_mipi_rxd); + clock_disable(clk_mipi); + + exit(0); +} diff --git a/tests/hardware_tests/test_timing/src/app.h b/tests/hardware_tests/test_timing/src/app.h new file mode 100644 index 00000000..76af74fd --- /dev/null +++ b/tests/hardware_tests/test_timing/src/app.h @@ -0,0 +1,22 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include "xs1.h" +#include "platform.h" +#include "xccompat.h" + +#include "camera_main.h" + +#ifdef __XC__ +typedef chanend chanend_t; +#endif + +#define MIPI_TILE 1 + +#ifndef MIPI_CLKBLK +# define MIPI_CLKBLK XS1_CLKBLK_1 +#endif + +void mipi_main(chanend_t c_control); diff --git a/tests/hardware_tests/test_timing/src/main.xc b/tests/hardware_tests/test_timing/src/main.xc new file mode 100644 index 00000000..37bc8868 --- /dev/null +++ b/tests/hardware_tests/test_timing/src/main.xc @@ -0,0 +1,20 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include "app.h" +#include "sensor_control.h" + +// Camera control channels +void main_tile0(chanend_t c_control){ + sensor_control(c_control); +} + +int main(void) +{ + chan c_control; + par { + on tile[0]: main_tile0(c_control); + on tile[MIPI_TILE]: mipi_main(c_control); + } + return 0; +} diff --git a/tests/lib_checks/test_lib_checks.py b/tests/lib_checks/test_lib_checks.py new file mode 100644 index 00000000..0e1dd79a --- /dev/null +++ b/tests/lib_checks/test_lib_checks.py @@ -0,0 +1,58 @@ +# Copyright 2023 XMOS LIMITED. +# This Software is subject to the terms of the XMOS Public Licence: Version 1. + +""" +Tests that check the contents of the files meet our standards +""" + +from subprocess import run +from pathlib import Path +import json +import re +import pytest + +REPO_ROOT = (Path(__file__).parent/"../..").resolve() + +def test_source_check(): + """ + runs xmos_source_check and asserts on the exit code + + To fix a failure run `xmos_source_check update . xmos_public_v1` from the repo root. + """ + ret = run(f"xmos_source_check check {REPO_ROOT} xmos_public_v1".split()) + assert 0 == ret.returncode + +def test_license_check(): + """ + runs xmos_license_check and asserts on the exit code + """ + ret = run(f"xmos_license_check check {REPO_ROOT} xmos_public_v1".split()) + assert 0 == ret.returncode + +#@pytest.mark.skip(reason = "infr_apps do not support fwk_ and sln_ repo types yet") +def test_changelog_check(): + """ + checks changelog conforms with the standards + """ + ret = run(f"xmos_changelog_check check {REPO_ROOT}".split()) + assert 0 == ret.returncode + +def test_version_matches(): + """ + check the JSON version matches the changelog + """ + with open('../../settings.json', 'r') as f: + json_version = json.load(f)["version"] + + with open('../../CHANGELOG.rst', 'r') as f: + changelog = f.readlines() + rm = r'(\d+\.\d+\.\d+)' + changelog_version = None + for line in changelog: + match = re.match(rm, line) + if match: + changelog_version = match.group(0) + break + assert changelog_version is not None, "Version not found in changelog" + + assert changelog_version == json_version, f"Versions do not match - changelog {changelog_version}, json {json_version}" diff --git a/tests/src/main.c b/tests/src/main.c deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/src/test_return_0/test_return_0.c b/tests/src/test_return_0/test_return_0.c deleted file mode 100644 index 71e3c64b..00000000 --- a/tests/src/test_return_0/test_return_0.c +++ /dev/null @@ -1,7 +0,0 @@ -#include "unity.h" - -void test_dummy_init(void) -{ - TEST_ASSERT_EQUAL_INT8( -1, 1); -} - diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt new file mode 100644 index 00000000..2d0e9f7a --- /dev/null +++ b/tests/unit_tests/CMakeLists.txt @@ -0,0 +1,47 @@ +# ############################################################################## +# Sources and definitions +# ############################################################################## +set(TARGET test_camera) + +file(GLOB_RECURSE APP_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/*.*c) + +set(APP_COMMON_LINK_LIBRARIES + Unity + fwk_camera::camera +) + +# ############################################################################## +# Flags +# ############################################################################## +set(APP_LINK_OPTIONS + "-report" + "-target=${XCORE_TARGET}" + -fcmdline-buffer-bytes=1024 + ${CMAKE_CURRENT_LIST_DIR}/config.xscope +) + +set(APP_COMPILER_FLAGS + -Os + -g + -report + -Wall + -Werror + -fxscope + -mcmodel=large + ${CMAKE_CURRENT_LIST_DIR}/config.xscope +) + +set(APP_INCLUDES api) + +# ############################################################################## +# Create executable +# ############################################################################## +add_executable(${TARGET} ) +target_sources(${TARGET} PUBLIC ${APP_SOURCES}) +target_include_directories(${TARGET} PUBLIC ${APP_INCLUDES}) +target_compile_options(${TARGET} PUBLIC -g -Os -Wall -Wno-xcore-fptrgroup) +target_link_libraries(${TARGET} PUBLIC ${APP_COMMON_LINK_LIBRARIES}) +target_link_options(${TARGET} PRIVATE ${APP_LINK_OPTIONS}) + +# enable testing functionality +add_test(NAME ${TARGET} COMMAND ${TARGET}) diff --git a/tests/unit_tests/api/_helpers.h b/tests/unit_tests/api/_helpers.h new file mode 100644 index 00000000..55af43a4 --- /dev/null +++ b/tests/unit_tests/api/_helpers.h @@ -0,0 +1,56 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include +#include +#include + +// Decimator factor for tests +#define K 4 // downsampled image / 4*4 + +// Print formatting +#define print_separator(x) printf("\n---------- %s -------------\n", x) + +// Common difinitions +#define CT_INT 127 // int conversion + +// Store the RGB color and corresponding values +typedef struct +{ + uint8_t R, G, B; + uint8_t Y, U, V; +} color_table_t; + +typedef enum +{ + RGB_TO_YUV, + YUV_TO_RGB +} color_conversion_t; + +typedef struct +{ + uint8_t y; + uint8_t u; + uint8_t v; +} YuvValues; + +typedef struct +{ + uint8_t r; + uint8_t g; + uint8_t b; +} RgbValues; + +// ----------------------------------------------------- +void fill_array_rand_int8(int8_t *image, size_t size); +void fill_array_rand_uint8(uint8_t *image, size_t size); + +void fill_color_table_uint8(color_table_t table[], const size_t size, color_conversion_t conversion); +void printColorTable(color_table_t *table, uint8_t ref); +void yuv_to_rgb_ct(color_table_t *ct_ref, color_table_t *ct_res); +void rgb_to_yuv_ct(color_table_t *ct_ref, color_table_t *ct_res); + +RgbValues yuvToRgb(uint8_t y, uint8_t u, uint8_t v); +YuvValues rgbToYuv(uint8_t r, uint8_t g, uint8_t b); diff --git a/examples/take_picture/src/config.xscope b/tests/unit_tests/config.xscope similarity index 100% rename from examples/take_picture/src/config.xscope rename to tests/unit_tests/config.xscope diff --git a/tests/unit_tests/src/main.c b/tests/unit_tests/src/main.c new file mode 100644 index 00000000..913a0645 --- /dev/null +++ b/tests/unit_tests/src/main.c @@ -0,0 +1,26 @@ +// Copyright 2020-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include "unity_fixture.h" + +int main( + int argc, + const char* argv[]) +{ + xscope_config_io(XSCOPE_IO_BASIC); + + UnityGetCommandLineOptions(argc, argv); + UnityBegin(argv[0]); + + printf("\n"); + + RUN_TEST_GROUP(pixel_hfilter); + RUN_TEST_GROUP(pixel_vfilter); + RUN_TEST_GROUP(color_conversion); + RUN_TEST_GROUP(gamma_timing); + RUN_TEST_GROUP(stats_test); + + return UNITY_END(); +} diff --git a/tests/unit_tests/src/test/_helpers.c b/tests/unit_tests/src/test/_helpers.c new file mode 100644 index 00000000..34efae7f --- /dev/null +++ b/tests/unit_tests/src/test/_helpers.c @@ -0,0 +1,127 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +#include "_helpers.h" +#include "camera_main.h" + +// Random generators +int8_t generate_rand_int8(){ + return (rand() % 256) - 128; +} +uint8_t generate_rand_uint8(){ + return (rand() % 256); +} + +// Single array fill functions +void fill_array_rand_uint8(uint8_t *image, const size_t size){ + for(size_t idx = 0; idx < size; idx++){ + image[idx] = generate_rand_uint8(); + } +} + +void fill_array_rand_int8(int8_t *image, const size_t size){ + for(size_t idx = 0; idx < size; idx++){ + image[idx] = generate_rand_int8(); + } +} + + +// Color conversion functions +YuvValues rgbToYuv(uint8_t r, uint8_t g, uint8_t b) { + float fr = (float)r; + float fg = (float)g; + float fb = (float)b; + + YuvValues yuv; + yuv.y = (uint8_t)(0.299f * fr + 0.587f * fg + 0.114f * fb + 0.5f); + yuv.u = (uint8_t)(-0.168736 * fr -0.331264 * fg + 0.500000 * fb + 128.0f); + yuv.v = (uint8_t)(0.500000 * fr -0.418688 * fg -0.081312 * fb + 128.0f); + + return yuv; +} + +RgbValues yuvToRgb(uint8_t y, uint8_t u, uint8_t v) { + RgbValues rgb; + + float fy = (float)y; + float fu = (float)(u - 128); + float fv = (float)(v - 128); + + float red = fy + 1.1406f * fv; + float green = fy - 0.3960f * fu - 0.5843f * fv; + float blue = fy + 2.0392f * fu; + + rgb.r = (uint8_t)(red > 255.0f ? 255 : (red < 0.0f ? 0 : red)); + rgb.g = (uint8_t)(green > 255.0f ? 255 : (green < 0.0f ? 0 : green)); + rgb.b = (uint8_t)(blue > 255.0f ? 255 : (blue < 0.0f ? 0 : blue)); + + return rgb; +} + +void fill_color_table_uint8(color_table_t table[], const size_t size, color_conversion_t conversion){ + if (conversion == RGB_TO_YUV){ + for(size_t idx = 0; idx < size; idx++){ + table[idx].R = generate_rand_uint8(); + table[idx].G = generate_rand_uint8(); + table[idx].B = generate_rand_uint8(); + YuvValues yuv = rgbToYuv(table[idx].R, table[idx].G, table[idx].B); + table[idx].Y = yuv.y; + table[idx].U = yuv.u; + table[idx].V = yuv.v; + } + } + else if (conversion == YUV_TO_RGB){ + for(size_t idx = 0; idx < size; idx++){ + table[idx].Y = generate_rand_uint8(); + table[idx].U = generate_rand_uint8(); + table[idx].V = generate_rand_uint8(); + RgbValues rgb = yuvToRgb(table[idx].Y, table[idx].U, table[idx].V); + table[idx].R = rgb.r; + table[idx].G = rgb.g; + table[idx].B = rgb.b; + } + } + else{ + printf("Invalid color conversion type\n"); + assert(0); + } + + +} + +void printColorTable(color_table_t* table, uint8_t ref) { + if(ref) + { + printf("Expected "); + } + else + { + printf("Resulted "); + } + printf("Color Table:\n"); + printf("R: %d, G: %d, B: %d\n", table->R, table->G, table->B); + printf("Y: %d, U: %d, V: %d\n", table->Y, table->U, table->V); +} + +void yuv_to_rgb_ct(color_table_t* ct_ref, color_table_t* ct_res){ + ct_res -> Y = ct_ref -> Y; + ct_res -> U = ct_ref -> U; + ct_res -> V = ct_ref -> V; + uint32_t result = yuv_to_rgb(ct_ref->Y - CT_INT, ct_ref->U - CT_INT, ct_ref->V - CT_INT); + ct_res -> R = (uint8_t)(GET_R(result) + CT_INT); + ct_res -> G = (uint8_t)(GET_G(result) + CT_INT); + ct_res -> B = (uint8_t)(GET_B(result) + CT_INT); +} + +void rgb_to_yuv_ct(color_table_t* ct_ref, color_table_t* ct_res){ + ct_res -> R = ct_ref -> R; + ct_res -> G = ct_ref -> G; + ct_res -> B = ct_ref -> B; + uint32_t result = rgb_to_yuv(ct_ref->R - CT_INT, ct_ref->G - CT_INT, ct_ref->B - CT_INT); + ct_res -> Y = (uint8_t)(GET_Y(result) + CT_INT); + ct_res -> U = (uint8_t)(GET_U(result) + CT_INT); + ct_res -> V = (uint8_t)(GET_V(result) + CT_INT); +} diff --git a/tests/unit_tests/src/test/color_conversion_test.c b/tests/unit_tests/src/test/color_conversion_test.c new file mode 100644 index 00000000..f799fb4f --- /dev/null +++ b/tests/unit_tests/src/test/color_conversion_test.c @@ -0,0 +1,119 @@ +// Copyright 2020-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include +#include +#include + +#include "unity_fixture.h" + +#include "camera_main.h" +#include "_helpers.h" + +#define INV_DELTA 20 // error allowed in YUV RGB color conversion +#define num_tests 3 + +// Define color conversion table and initialize random +color_table_t ct_test_vector[num_tests]; + +// Unity +TEST_GROUP_RUNNER(color_conversion) { + RUN_TEST_CASE(color_conversion, conversion__yuv_to_rgb); + RUN_TEST_CASE(color_conversion, conversion__rgb_to_yuv); + RUN_TEST_CASE(color_conversion, conversion__timming); +} +TEST_GROUP(color_conversion); +TEST_SETUP(color_conversion) { fflush(stdout); print_separator("color_conversion");} +TEST_TEAR_DOWN(color_conversion) {} + +// Tests +TEST(color_conversion, conversion__yuv_to_rgb) +{ + // initialize with random values + fill_color_table_uint8(&ct_test_vector[0], num_tests, YUV_TO_RGB); + + for(size_t i = 0; i < num_tests; i++) + { + // Define color table + color_table_t ct_ref = ct_test_vector[i]; + color_table_t ct_result = {0}; + + // Test the converison + yuv_to_rgb_ct(&ct_ref, &ct_result); + + printColorTable(&ct_ref, 1); + printColorTable(&ct_result, 0); + + // Ensure conversion is correct + TEST_ASSERT_INT_WITHIN(INV_DELTA, ct_ref.R, ct_result.R); + TEST_ASSERT_INT_WITHIN(INV_DELTA, ct_ref.G, ct_result.G); + TEST_ASSERT_INT_WITHIN(INV_DELTA, ct_ref.B, ct_result.B); + } +} + + +TEST(color_conversion, conversion__rgb_to_yuv) +{ + // initialize with random values + fill_color_table_uint8(&ct_test_vector[0], num_tests, RGB_TO_YUV); + + for(size_t i = 0; i < num_tests; i++) + { + // Define color table + color_table_t ct_ref = ct_test_vector[i]; + color_table_t ct_result = {0}; + + // Test the converison + rgb_to_yuv_ct(&ct_ref, &ct_result); + + printColorTable(&ct_ref, 1); + printColorTable(&ct_result, 0); + + // Printing the extracted bytes + TEST_ASSERT_INT_WITHIN(INV_DELTA, ct_ref.Y, ct_result.Y); + TEST_ASSERT_INT_WITHIN(INV_DELTA, ct_ref.U, ct_result.U); + TEST_ASSERT_INT_WITHIN(INV_DELTA, ct_ref.V, ct_result.V); + } +} + + +TEST(color_conversion, conversion__timming) +{ + // Define number of tests + const unsigned num_tests_timing = 10; + color_table_t ct_timing_array[num_tests_timing]; + fill_color_table_uint8(&ct_timing_array[0], num_tests_timing, RGB_TO_YUV); + + // VPU conversion + unsigned start = measure_time(); + for(size_t i = 0; i < num_tests_timing; i++) + { + rgb_to_yuv( + ct_timing_array[i].R, + ct_timing_array[i].G, + ct_timing_array[i].B); + } + unsigned vpu_conv_time = measure_time() - start; + + // Non VPU conversion + start = measure_time(); + for (size_t i = 0; i < num_tests_timing; i++) + { + rgbToYuv( + ct_timing_array[i].R, + ct_timing_array[i].G, + ct_timing_array[i].B); + } + unsigned non_vpu_conv_time = measure_time() - start; + + // Compare the time + printf("\tnumber of conversions: %d\n", num_tests_timing); + static const char func_name[] = "Color conversion VPU"; + PRINT_NAME_TIME(func_name, vpu_conv_time); + + static const char func_name2[] = "Color conversion non VPU"; + PRINT_NAME_TIME(func_name2, non_vpu_conv_time); +} diff --git a/tests/unit_tests/src/test/gamma_timing_test.c b/tests/unit_tests/src/test/gamma_timing_test.c new file mode 100644 index 00000000..08468f34 --- /dev/null +++ b/tests/unit_tests/src/test/gamma_timing_test.c @@ -0,0 +1,60 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include +#include +#include +#include + +#include "unity_fixture.h" + +#include "_helpers.h" +#include "isp.h" // gamma +#include "camera_utils.h" // time + +// Unity +TEST_GROUP(gamma_timing); +TEST_SETUP(gamma_timing) { fflush(stdout); print_separator("gamma_timing");} +TEST_TEAR_DOWN(gamma_timing) {} +TEST_GROUP_RUNNER(gamma_timing) { + RUN_TEST_CASE(gamma_timing, gamma__basic); +} + +// Tests +void test_gamma_size( + const char* func_name, + size_t height, + size_t width, + size_t channels) +{ + uint8_t image_buffer[channels][height][width]; + + // Seed the random number generator with the current time + srand(time(NULL)); + + // generate random numbers for the image buffer + size_t buffsize = height * width * channels; + fill_array_rand_uint8((uint8_t *) &image_buffer[0][0][0], buffsize); + + // then measure and apply gamma + unsigned ts = measure_time(); + isp_gamma((uint8_t *) &image_buffer[0][0][0], gamma_uint8, height, width, channels); + unsigned tdiff = measure_time() - ts; + + // print info + printf("\tbuffsize: %d\n", buffsize); + PRINT_NAME_TIME(func_name, tdiff); +} + + +TEST(gamma_timing, gamma__basic) +{ + static const char func_name[] = "gamma downsampled"; + const size_t height = APP_IMAGE_HEIGHT_PIXELS / K; + const size_t width = APP_IMAGE_WIDTH_PIXELS / K; + const size_t channels = APP_IMAGE_CHANNEL_COUNT; + test_gamma_size(func_name, height, width, channels); +} diff --git a/tests/unit_tests/src/test/pixel_hfilter_test.c b/tests/unit_tests/src/test/pixel_hfilter_test.c new file mode 100644 index 00000000..1f184076 --- /dev/null +++ b/tests/unit_tests/src/test/pixel_hfilter_test.c @@ -0,0 +1,387 @@ +// Copyright 2020-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include +#include +#include + +#include "unity_fixture.h" + +#include "camera_main.h" +#include "camera_utils.h" + +TEST_GROUP_RUNNER(pixel_hfilter) { + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter__basic); + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter__acc_init); + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter__apply_shift); + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter__input_stride); + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter__out_count); + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter__alt_coef); + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter__timing); + + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter_update_scale__case1); + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter_update_scale__case2); + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter_update_scale__case3); + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter_update_scale__case4); + RUN_TEST_CASE(pixel_hfilter, pixel_hfilter_update_scale__timing); + + // RUN_TEST_CASE(pixel_hfilter, pixel_hfilter__case1); +} + +TEST_GROUP(pixel_hfilter); +TEST_SETUP(pixel_hfilter) { fflush(stdout); } +TEST_TEAR_DOWN(pixel_hfilter) {} + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter__basic) +{ + static const int8_t coef_count = 32; + static const unsigned output_count = 16; + static const int32_t input_stride = 4; + static const unsigned input_count = 32 + (output_count-1)*input_stride; + + int8_t coef[coef_count] = {0}; + coef[0] = 1; + + int8_t input[input_count] = {0}; + int8_t output[output_count] = {0}; + + for(int k = 0; k < input_count; k++) + input[k] = k>>2; + + const int32_t acc_init = 0; + const unsigned shift = 0; + + pixel_hfilter(output, input, coef, + acc_init, shift, input_stride, output_count); + + for(int k = 0; k < output_count; k++) + TEST_ASSERT_EQUAL_INT8(k, output[k]); +} + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter__acc_init) +{ + static const int8_t coef_count = 32; + static const unsigned output_count = 16; + static const int32_t input_stride = 4; + static const unsigned input_count = 32 + (output_count-1)*input_stride; + + int8_t coef[coef_count] = {0}; + coef[0] = 1; + + int8_t input[input_count] = {0}; + int8_t output[output_count] = {0}; + + for(int k = 0; k < input_count; k++) + input[k] = k>>2; + + const int32_t acc_init = 10; + const unsigned shift = 0; + + pixel_hfilter(output, input, coef, + acc_init, shift, input_stride, output_count); + + + for(int k = 0; k < output_count; k++) + TEST_ASSERT_EQUAL_INT8(acc_init+k, output[k]); +} + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter__apply_shift) +{ + static const int8_t coef_count = 32; + static const unsigned output_count = 16; + static const int32_t input_stride = 4; + static const unsigned input_count = 32 + (output_count-1)*input_stride; + + int8_t coef[coef_count] = {0}; + coef[0] = 1; + + int8_t input[input_count] = {0}; + int8_t output[output_count] = {0}; + + for(int k = 0; k < input_count; k++) + input[k] = k; + + const int32_t acc_init = 32; + const unsigned shift = 2; + + pixel_hfilter(output, input, coef, + acc_init, shift, input_stride, output_count); + + for(int k = 0; k < output_count; k++) + TEST_ASSERT_EQUAL_INT8( (acc_init>>2)+k, + output[k] ); +} + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter__input_stride) +{ + static const int8_t coef_count = 32; + static const unsigned output_count = 16; + static const int32_t input_stride = 8; + static const unsigned input_count = 32 + (output_count-1)*input_stride; + + int8_t coef[coef_count] = {0}; + coef[0] = 1; + + int8_t input[input_count] = {0}; + int8_t output[output_count] = {0}; + + for(int k = 0; k < input_count; k++) + input[k] = k>>2; + + const int32_t acc_init = 10; + const unsigned shift = 0; + + pixel_hfilter(output, input, coef, + acc_init, shift, input_stride, output_count); + + for(int k = 0; k < output_count; k++) + TEST_ASSERT_EQUAL_INT8(acc_init+2*k, output[k]); +} + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter__out_count) +{ + static const int8_t coef_count = 32; + static const unsigned output_count = 64; + static const int32_t input_stride = 4; + static const unsigned input_count = 32 + (output_count-1)*input_stride; + + int8_t coef[coef_count] = {0}; + coef[0] = 1; + + int8_t input[input_count] = {0}; + int8_t output[output_count] = {0}; + + for(int k = 0; k < input_count; k++) + input[k] = k>>2; + + const int32_t acc_init = 10; + const unsigned shift = 0; + + pixel_hfilter(output, input, coef, + acc_init, shift, input_stride, output_count); + + for(int k = 0; k < output_count; k++) + TEST_ASSERT_EQUAL_INT8(acc_init+k, output[k]); +} + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter__alt_coef) +{ + static const int8_t coef_count = 32; + static const unsigned output_count = 32; + static const int32_t input_stride = 4; + static const unsigned input_count = 32 + (output_count-1)*input_stride; + + int8_t coef[coef_count] = {0}; + coef[0] = 2; + coef[1] = 4; + + int8_t input[input_count] = {0}; + int8_t output[output_count] = {0}; + int8_t expected[output_count] = {0}; + + for(int k = 0; k < input_count; k++) + input[k] = k >> 2; + + const int32_t acc_init = 16; + const unsigned shift = 0; + + for(int k = 0; k < output_count; k++){ + int expected_val = acc_init; + for(int j = 0; j < coef_count; j++) + expected_val += coef[j] * input[k*input_stride + j]; + + expected[k] = (expected_val >= INT8_MAX) ? INT8_MAX + : (expected_val <= INT8_MIN) ? INT8_MIN + : expected_val; + } + + pixel_hfilter(output, input, coef, + acc_init, shift, input_stride, output_count); + + TEST_ASSERT_EQUAL_INT8_ARRAY(expected, output, output_count); + +} + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter__timing) +{ + static const unsigned max_blocks = 8; + + int8_t coef[32] = {0}; + int8_t input[32] = {0}; + int8_t output[16*max_blocks] = {0}; + + unsigned timing[max_blocks]; + + static const char func_name[] = "pixel_hfilter()"; + static const char row1_head[] = "out_count:"; + static const char row2_head[] = "ticks: "; + + for(int k = 0; k < max_blocks; k++){ + unsigned ts = measure_time(); + pixel_hfilter(output, input, coef, 0, 0 , 0, 16*(k+1)); + unsigned te = measure_time(); + timing[k] = te - ts; + } + + printf("\n\t%s timing:\n", func_name); + printf("\t\t%s", row1_head); + for(int k = 0; k < max_blocks; k++) printf("%8u", 16*(k+1)); + printf("\n\t\t%s", row2_head); + for(int k = 0; k < max_blocks; k++) printf("%8u", timing[k]); + printf("\n\n"); + +} + + + + + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter_update_scale__case1) +{ + hfilter_state_t state; + + memset(&state, 0, sizeof(state)); + + const float gain = 0.0f; + const size_t offset = 0; + + pixel_hfilter_update_scale(&state, gain, offset); + + const unsigned exp_shift = 9; + const int32_t shift_scale = 1 << exp_shift; + const int32_t exp_acc_init = - 128 * shift_scale - SENSOR_BLACK_LEVEL * shift_scale; + + TEST_ASSERT_EQUAL_INT8(0, state.coef[0 + offset]); + TEST_ASSERT_EQUAL_INT8(0, state.coef[2 + offset]); + TEST_ASSERT_EQUAL_INT8(0, state.coef[4 + offset]); + + TEST_ASSERT_EQUAL_UINT(exp_shift, state.shift); + TEST_ASSERT_EQUAL_INT32(exp_acc_init, state.acc_init); +} + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter_update_scale__case2) +{ + hfilter_state_t state; + + memset(&state, 0, sizeof(state)); + + const float gain = 1.0f; + const size_t offset = 1; + + pixel_hfilter_update_scale(&state, gain, offset); + + const unsigned exp_shift = 7; + const int32_t shift_scale = 1 << exp_shift; + const int32_t exp_acc_init = - SENSOR_BLACK_LEVEL * shift_scale; + + TEST_ASSERT_EQUAL_INT8(0x1B, state.coef[0 + offset]); + TEST_ASSERT_EQUAL_INT8(0x4B, state.coef[2 + offset]); + TEST_ASSERT_EQUAL_INT8(0x1B, state.coef[4 + offset]); + + TEST_ASSERT_EQUAL_UINT(exp_shift, state.shift); + TEST_ASSERT_EQUAL_INT32(exp_acc_init, state.acc_init); +} + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter_update_scale__case3) +{ + hfilter_state_t state; + + memset(&state, 0, sizeof(state)); + + const float gain = 1.2f; + const size_t offset = 0; + + pixel_hfilter_update_scale(&state, gain, offset); + + const unsigned exp_shift = 7; + const int32_t shift_scale = 1 << exp_shift; + const int32_t exp_acc_init = 128 * (gain - 1.0f) * shift_scale - SENSOR_BLACK_LEVEL * shift_scale; + + TEST_ASSERT_EQUAL_INT8(0x20, state.coef[0 + offset]); + TEST_ASSERT_EQUAL_INT8(0x59, state.coef[2 + offset]); + TEST_ASSERT_EQUAL_INT8(0x20, state.coef[4 + offset]); + + TEST_ASSERT_EQUAL_UINT(exp_shift, state.shift); + TEST_ASSERT_EQUAL_INT32(exp_acc_init, state.acc_init); +} + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter_update_scale__case4) +{ + hfilter_state_t state; + + memset(&state, 0, sizeof(state)); + + const float gain = 0.8f; + const size_t offset = 1; + + pixel_hfilter_update_scale(&state, gain, offset); + + const unsigned exp_shift = 8; + const int32_t shift_scale = 1 << exp_shift; + const int32_t exp_acc_init = 128 * (gain - 1.0f) * shift_scale - SENSOR_BLACK_LEVEL * shift_scale; + + TEST_ASSERT_EQUAL_INT8(0x2B, state.coef[0 + offset]); + TEST_ASSERT_EQUAL_INT8(0x77, state.coef[2 + offset]); + TEST_ASSERT_EQUAL_INT8(0x2B, state.coef[4 + offset]); + + TEST_ASSERT_EQUAL_UINT(exp_shift, state.shift); + TEST_ASSERT_EQUAL_INT32(exp_acc_init, state.acc_init); +} + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_hfilter, pixel_hfilter_update_scale__timing) +{ + hfilter_state_t state; + + memset(&state, 0, sizeof(state)); + + const float gain = 1.0f; + + unsigned ts = measure_time(); + pixel_hfilter_update_scale(&state, gain, 1); + unsigned te = measure_time(); + + static const char func_name[] = "pixel_hfilter_update_scale()"; + + printf("\n\t%s timing: %u ticks\n\n", func_name, te - ts); +} diff --git a/tests/unit_tests/src/test/pixel_vfilter_test.c b/tests/unit_tests/src/test/pixel_vfilter_test.c new file mode 100644 index 00000000..83391330 --- /dev/null +++ b/tests/unit_tests/src/test/pixel_vfilter_test.c @@ -0,0 +1,593 @@ +// Copyright 2020-2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include +#include +#include + +#include "unity_fixture.h" + +#include "camera_main.h" + +TEST_GROUP_RUNNER(pixel_vfilter) { + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_acc_init__case0); + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_acc_init__case1); + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_acc_init__timing); + + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_complete__case0); + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_complete__case1); + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_complete__timing); + + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_macc__case0); + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_macc__case1); + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_macc__case2); + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_macc__case3); + RUN_TEST_CASE(pixel_vfilter, pixel_vfilter_macc__timing); +} + +#define ACC_HI(X) (((X)>>16)&0xFFFF) +#define ACC_LO(X) (((X)>> 0)&0xFFFF) + + + +static +int debug_iter = -1; + + +TEST_GROUP(pixel_vfilter); +TEST_SETUP(pixel_vfilter) { + fflush(stdout); + debug_iter = -1; +} + +TEST_TEAR_DOWN(pixel_vfilter) { + if(debug_iter != -1){ + printf("\nDEBUG:\n"); + printf("\tdebug_iter = %d\n", debug_iter); + } + debug_iter = -1; +} + + + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_acc_init__case0) +{ + static const unsigned acc_blocks = 1; + + struct { + uint32_t pre_padding; + struct { + int16_t hi[16]; + int16_t lo[16]; + } accs[acc_blocks]; + uint32_t post_padding; + } thing; + + // These ensure that the function doesn't write outside of the bounds of the + // array + thing.pre_padding = 0x52C6ABE2; + thing.post_padding = 0xABCD1234; + + static const + int32_t test_cases[] = { + 0x00000000, 0x00000001, 0x00010000, -0x00000001, 0x12345678, + }; + + static const + unsigned n_cases = sizeof(test_cases) / sizeof(test_cases[0]); + + for(int iter = 0; iter < n_cases; iter++){ + debug_iter = iter; // For debug on failure + + const int32_t acc_value = test_cases[iter]; + + memset(&thing.accs, 0, sizeof(thing.accs)); + + pixel_vfilter_acc_init(&thing.accs[0].hi[0], acc_value, 16*acc_blocks); + + for(int k = 0; k < acc_blocks; k++){ + for(int i = 0; i < 16; i++){ + TEST_ASSERT_EQUAL_INT16(ACC_HI(acc_value), thing.accs[k].hi[i]); + TEST_ASSERT_EQUAL_INT16(ACC_LO(acc_value), thing.accs[k].lo[i]); + } + } + TEST_ASSERT_EQUAL_UINT32(0x52C6ABE2, thing.pre_padding); + TEST_ASSERT_EQUAL_UINT32(0xABCD1234, thing.post_padding); + } + + debug_iter = -1; // signals not to print debug on teardown +} + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_acc_init__case1) +{ + static const unsigned acc_blocks = 4; + + struct { + uint32_t pre_padding; + struct { + int16_t hi[16]; + int16_t lo[16]; + } accs[acc_blocks]; + uint32_t post_padding; + } thing; + + // These ensure that the function doesn't write outside of the bounds of the + // array + thing.pre_padding = 0x52C6ABE2; + thing.post_padding = 0xABCD1234; + + if(1){ + const int32_t acc_value = 0x12345678; + memset(&thing.accs, 0, sizeof(thing.accs)); + pixel_vfilter_acc_init(&thing.accs[0].hi[0], acc_value, 16*acc_blocks); + for(int k = 0; k < acc_blocks; k++){ + for(int i = 0; i < 16; i++){ + TEST_ASSERT_EQUAL_INT16(ACC_HI(acc_value), thing.accs[k].hi[i]); + TEST_ASSERT_EQUAL_INT16(ACC_LO(acc_value), thing.accs[k].lo[i]); + } + } + TEST_ASSERT_EQUAL_UINT32(0x52C6ABE2, thing.pre_padding); + TEST_ASSERT_EQUAL_UINT32(0xABCD1234, thing.post_padding); + } +} + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_acc_init__timing) +{ + static const unsigned acc_blocks = 8; + + int32_t accs[16*acc_blocks]; + + unsigned timing[acc_blocks]; + + static const char func_name[] = "pixel_vfilter_acc_init()"; + static const char row1_head[] = "out_count:"; + static const char row2_head[] = "ticks: "; + + for(int k = 0; k < acc_blocks; k++){ + unsigned ts = measure_time(); + pixel_vfilter_acc_init((int16_t*) &accs, 0, 16*(k+1)); + unsigned te = measure_time(); + timing[k] = te - ts; + } + + printf("\n\t%s timing:\n", func_name); + printf("\t\t%s", row1_head); + for(int k = 0; k < acc_blocks; k++) printf("%8u", 16*(k+1)); + printf("\n\t\t%s", row2_head); + for(int k = 0; k < acc_blocks; k++) printf("%8u", timing[k]); + printf("\n\n"); +} + + + + + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_complete__case0) +{ + static const unsigned acc_per_vec = 16; + static const unsigned acc_blocks = 1; + static const unsigned output_pixels = acc_blocks*acc_per_vec; + + struct { + int16_t hi[acc_per_vec]; + int16_t lo[acc_per_vec]; + } accs[acc_blocks]; + + int8_t output[output_pixels]; + int16_t shifts[acc_per_vec]; + + int8_t expected_out[output_pixels] = {0}; + + struct { + int32_t acc_value; + int16_t shift; + int8_t expected_out; + } test_cases[] = { + // accumulator shift output + { 0x00000000, 0, 0x00}, + { 0x00000001, 0, 0x01}, + { -0x00000001, 0, -0x01}, + { 0x00000002, 1, 0x01}, + { 0x00000004, 1, 0x02}, + { 0x00000004, 2, 0x01}, + { -0x00000004, 1, -0x02}, + { 0x0000007F, 0, 0x7F}, + { -0x0000007F, 0, -0x7F}, + { -0x00000080, 0, -0x7F}, + { 0x00000100, 0, 0x7F}, + { 0x007E0000, 16, 0x7E}, + }; + + static const unsigned n_cases = sizeof(test_cases)/sizeof(test_cases[0]); + + for(int iter = 0; iter < n_cases; iter++){ + debug_iter = iter; // For debug on failure + + const int32_t acc_value = test_cases[iter].acc_value; + + for(int k = 0; k < acc_per_vec; k++) + shifts[k] = test_cases[iter].shift; + + for(int k = 0; k < output_pixels; k++) + expected_out[k] = test_cases[iter].expected_out; + + pixel_vfilter_acc_init(&accs[0].hi[0], acc_value, output_pixels); + + pixel_vfilter_complete(output, &accs[0].hi[0], shifts, output_pixels); + + TEST_ASSERT_EQUAL_INT8_ARRAY(expected_out, output, output_pixels); + } + + debug_iter = -1; // signals not to print debug on teardown +} + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_complete__case1) +{ + static const unsigned acc_per_vec = 16; + static const unsigned acc_blocks = 6; + static const unsigned output_pixels = acc_blocks*acc_per_vec; + + struct { + int16_t hi[acc_per_vec]; + int16_t lo[acc_per_vec]; + } accs[acc_blocks]; + + pixel_vfilter_acc_init(&accs[0].hi[0], 0x007E0000, output_pixels); + + int16_t shifts[acc_per_vec]; + for(int k = 0; k < acc_per_vec; k++) shifts[k] = 16; + + // Add 16 bytes of padding around either side of the array to ensure that the + // function doesn't write outside of the bounds. + int8_t output[output_pixels + 32]; + int8_t expected_out[output_pixels + 32]; + + const int8_t expected = 0x7E; + + for(int blocks = 1; blocks < acc_blocks; blocks++){ + debug_iter = blocks; // For debug on failure + + memset(output, 0, sizeof(output)); + memset(expected_out, 0, sizeof(expected_out)); + + for(int k = 0; k < 16*blocks; k++) + expected_out[k+16] = expected; + + pixel_vfilter_complete(&output[16], &accs[0].hi[0], shifts, 16*blocks); + + TEST_ASSERT_EQUAL_INT8_ARRAY(expected_out, output, output_pixels + 32); + } + + debug_iter = -1; // signals not to print debug on teardown +} + + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_complete__timing) +{ + static const unsigned max_blocks = 8; + + int32_t accs[16*max_blocks]; + int8_t output[16*max_blocks]; + int16_t shifts[16] = {0}; + + unsigned timing[max_blocks]; + + static const char func_name[] = "pixel_vfilter_complete()"; + static const char row1_head[] = "out_count:"; + static const char row2_head[] = "ticks: "; + + for(int k = 0; k < max_blocks; k++){ + unsigned ts = measure_time(); + pixel_vfilter_complete(output, (int16_t*) &accs, shifts, 16*(k+1)); + unsigned te = measure_time(); + timing[k] = te - ts; + } + + printf("\n\t%s timing:\n", func_name); + printf("\t\t%s", row1_head); + for(int k = 0; k < max_blocks; k++) printf("%8u", 16*(k+1)); + printf("\n\t\t%s", row2_head); + for(int k = 0; k < max_blocks; k++) printf("%8u", timing[k]); + printf("\n\n"); +} + + + + + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_macc__case0) +{ + static const unsigned acc_per_vec = 16; + static const unsigned acc_blocks = 1; + static const unsigned out_count = acc_per_vec*acc_blocks; + + int32_t accs[out_count]; + int8_t coef[16]; + int8_t pixels_in[out_count]; + + int32_t expected_accs[out_count]; + + struct { + int32_t acc_init; + int8_t coef; + int8_t pixel; + int32_t expected_acc; + } test_case[] = { + // acc_init, coef, pixel, expected, + { 0x00000000, 0x00, 0x00, 0x00000000, }, + { 0x00000001, 0x00, 0x00, 0x00000001, }, + { 0x00001234, 0x00, 0x01, 0x00001234, }, + { 0x00001234, 0x01, 0x00, 0x00001234, }, + { 0x00001234, 0x01, 0x01, 0x00001235, }, + { -0x00001234, 0x01, 0x02, -0x00001232, }, + { 0x00010000, 0x0A, 0x10, 0x000100A0, }, + }; + + static const + unsigned n_cases = sizeof(test_case)/sizeof(test_case[0]); + + for(int iter = 0; iter < n_cases; iter++){ + debug_iter = iter; // For debug on failure + + int32_t acc_init = test_case[iter].acc_init; + int8_t coeff = test_case[iter].coef; + int8_t pixel = test_case[iter].pixel; + int32_t expected_acc = test_case[iter].expected_acc; + + pixel_vfilter_acc_init((int16_t*) &accs[0], acc_init, out_count); + pixel_vfilter_acc_init((int16_t*) &expected_accs[0], expected_acc, out_count); + + memset(coef, coeff, sizeof(coef)); + memset(pixels_in, pixel, sizeof(pixels_in)); + + pixel_vfilter_macc((int16_t*) &accs[0], pixels_in, coef, out_count); + + TEST_ASSERT_EQUAL_INT32_ARRAY(expected_accs, accs, out_count); + + } + + debug_iter = -1; // signals not to print debug on teardown +} + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_macc__case1) +{ + static const unsigned acc_per_vec = 16; + static const unsigned max_blocks = 4; + static const unsigned max_out_count = acc_per_vec*max_blocks; + + int32_t accs[max_out_count]; + int8_t coef[16]; + int8_t pixels_in[max_out_count]; + + int32_t expected_accs[max_out_count]; + + + for(int blocks = 1; blocks <= max_blocks; blocks++){ + debug_iter = blocks-1; // For debug on failure + + unsigned out_count = acc_per_vec*blocks; + + int32_t acc_init = 0x00010000; + int8_t coeff = 0x05; + int8_t pixel = 0x20; + int32_t expected_acc = 0x000100A0; + + memset(accs, 0, sizeof(accs)); + memset(expected_accs, 0, sizeof(expected_accs)); + pixel_vfilter_acc_init((int16_t*) &accs[0], acc_init, out_count); + pixel_vfilter_acc_init((int16_t*) &expected_accs[0], expected_acc, out_count); + + memset(coef, coeff, sizeof(coef)); + memset(pixels_in, pixel, sizeof(pixels_in)); + + pixel_vfilter_macc((int16_t*) &accs[0], pixels_in, coef, out_count); + + // Compare all the way to end to make sure it doesn't write past end + TEST_ASSERT_EQUAL_INT32_ARRAY(expected_accs, accs, max_out_count); + + } + + debug_iter = -1; // signals not to print debug on teardown +} + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_macc__case2) +{ + static const unsigned acc_per_vec = 16; + + int16_t accs[2][acc_per_vec]; + int8_t coef[acc_per_vec]; + int8_t pixels_in[acc_per_vec]; + + int32_t expected_accs[acc_per_vec]; + + int32_t result_accs[acc_per_vec]; + + struct { + int32_t acc_init; + int8_t coef; + int8_t pixel; + } test_case[] = { + // acc_init, coef, pixel + { 0x00000000, 0x00, 0x00}, + { 0x00000001, 0x00, 0x00}, + { 0x00001234, 0x00, 0x01}, + { 0x00001234, 0x01, 0x00}, + { 0x00001234, 0x01, 0x01}, + { -0x00001234, 0x01, 0x02}, + { 0x00010000, 0x0A, 0x10}, + }; + + static const + unsigned n_cases = sizeof(test_case)/sizeof(test_case[0]); + + for(int iter = 0; iter < n_cases; iter++){ + debug_iter = iter; // For debug on failure + + int32_t acc_init = test_case[iter].acc_init; + int8_t coeff = test_case[iter].coef; + int8_t pixel = test_case[iter].pixel; + + pixel_vfilter_acc_init(&accs[0][0], acc_init, acc_per_vec); + + memset(coef, coeff, sizeof(coef)); + for(int k = 0; k < acc_per_vec; k++) + pixels_in[k] = pixel + k; + + for(int k = 0; k < acc_per_vec; k++) + expected_accs[k] = acc_init + coeff * (pixel + k); + + pixel_vfilter_macc((int16_t*) &accs[0], pixels_in, coef, acc_per_vec); + + for(int k = 0; k < acc_per_vec; k++){ + result_accs[k] = (accs[0][k] << 16) | (accs[1][k]); + } + + TEST_ASSERT_EQUAL_INT32_ARRAY(expected_accs, result_accs, acc_per_vec); + + } + + debug_iter = -1; // signals not to print debug on teardown +} + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_macc__case3) +{ + static const unsigned acc_per_vec = 16; + + int16_t accs[2][acc_per_vec]; + int8_t coef[acc_per_vec]; + int8_t pixels_in[acc_per_vec]; + + int32_t expected_accs[acc_per_vec]; + + int32_t result_accs[acc_per_vec]; + + struct { + int32_t acc_init; + int8_t coef; + int8_t pixel; + } test_case[] = { + // acc_init, coef, pixel + { 0x00000000, 0x00, 0x00}, + { 0x00000001, 0x00, 0x00}, + { 0x00001234, 0x00, 0x01}, + { 0x00001234, 0x01, 0x00}, + { 0x00001234, 0x01, 0x01}, + { -0x00001234, 0x01, 0x02}, + { 0x00010000, 0x0A, 0x10}, + }; + + static const + unsigned n_cases = sizeof(test_case)/sizeof(test_case[0]); + + for(int iter = 0; iter < n_cases; iter++){ + debug_iter = iter; // For debug on failure + + int32_t acc_init = test_case[iter].acc_init; + int8_t coeff = test_case[iter].coef; + int8_t pixel = test_case[iter].pixel; + + for(int k = 0; k < acc_per_vec; k++){ + accs[0][k] = ACC_HI(acc_init+7*k); + accs[1][k] = ACC_LO(acc_init+7*k); + } + + memset(coef, coeff, sizeof(coef)); + for(int k = 0; k < acc_per_vec; k++) + pixels_in[k] = pixel + k; + + for(int k = 0; k < acc_per_vec; k++) + expected_accs[k] = acc_init + 7*k + coeff * (pixel + k); + + pixel_vfilter_macc((int16_t*) &accs[0], pixels_in, coef, acc_per_vec); + + for(int k = 0; k < acc_per_vec; k++){ + result_accs[k] = (accs[0][k] << 16) | (accs[1][k]); + } + + TEST_ASSERT_EQUAL_INT32_ARRAY(expected_accs, result_accs, acc_per_vec); + + } + + debug_iter = -1; // signals not to print debug on teardown +} + + +/////////////////////////////////////////////// +/////////////////////////////////////////////// +/////////////////////////////////////////////// +TEST(pixel_vfilter, pixel_vfilter_macc__timing) +{ + static const unsigned acc_per_vec = 16; + static const unsigned max_blocks = 8; + static const unsigned max_out_count = acc_per_vec*max_blocks; + + int32_t accs[max_out_count]; + int8_t coef[16]; + int8_t pixels_in[max_out_count]; + + unsigned timing[max_blocks]; + + static const char func_name[] = "pixel_vfilter_macc()"; + static const char row1_head[] = "out_count:"; + static const char row2_head[] = "ticks: "; + + for(int k = 0; k < max_blocks; k++){ + unsigned blocks = k+1; + unsigned out_count = acc_per_vec*blocks; + + unsigned ts = measure_time(); + pixel_vfilter_macc((int16_t*) &accs[0], pixels_in, coef, out_count); + unsigned te = measure_time(); + timing[k] = te - ts; + } + + printf("\n\t%s timing:\n", func_name); + printf("\t\t%s", row1_head); + for(int k = 0; k < max_blocks; k++) printf("%8u", 16*(k+1)); + printf("\n\t\t%s", row2_head); + for(int k = 0; k < max_blocks; k++) printf("%8u", timing[k]); + printf("\n\n"); +} diff --git a/tests/unit_tests/src/test/statistics_test.c b/tests/unit_tests/src/test/statistics_test.c new file mode 100644 index 00000000..ebf49ef6 --- /dev/null +++ b/tests/unit_tests/src/test/statistics_test.c @@ -0,0 +1,88 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unity_fixture.h" + +#include "_helpers.h" +#include "statistics.h" +#include "camera_utils.h" // time + +// Unity +TEST_GROUP(stats_test); +TEST_SETUP(stats_test) { fflush(stdout); print_separator("stats_test");} +TEST_TEAR_DOWN(stats_test) {} +TEST_GROUP_RUNNER(stats_test) { + RUN_TEST_CASE(stats_test, stats_test__basic); +} + + +TEST(stats_test, stats_test__basic){ + // create a random array + const size_t height = APP_IMAGE_HEIGHT_PIXELS / K; + const size_t width = APP_IMAGE_WIDTH_PIXELS / K; + const size_t channels = APP_IMAGE_CHANNEL_COUNT; + const size_t buffsize = height * width * channels; + + int8_t image_buffer[channels][height][width]; + fill_array_rand_int8((int8_t *) &image_buffer[0][0][0], buffsize); + + // create empty stats + global_stats_t global_stats = {{0}}; + + // compute histogram + unsigned ts = measure_time(); + unsigned tdiff_internal = 0; + + for (uint16_t h=0; h> Linux and Mac + pip install -e utils/xscope_fileio + +.. tab:: Windows + + .. code-block:: console + + >> Windows + pip install -e utils/xscope_fileio + cd utils/xscope_fileio/host + cmake -G Ninja . && ninja + +Your ``xscope_fileio`` host app is now ready to use. diff --git a/utils/io_utils/io_utils.c b/utils/io_utils/io_utils.c new file mode 100644 index 00000000..212d2ccc --- /dev/null +++ b/utils/io_utils/io_utils.c @@ -0,0 +1,147 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include + +#include "io_utils.h" + +xscope_file_t file_read; + +void io_open_file(const char *filename) +{ + file_read = xscope_open_file(filename, "rb"); +} + +void io_fill_array_from_file(uint8_t *data, const size_t size) +{ + xscope_fread(&file_read, data, size); +} + +void io_rewind_file() +{ + xscope_fseek(&file_read, 0, SEEK_SET); +} + +// ------------------------------------------------ +void swap_dimensions(uint8_t * image_in, uint8_t * image_out, const size_t height, const size_t width, const size_t channels) +{ + printf("Swapping image dimensions...\n"); + for(size_t k = 0; k < height; k++) + { + for(size_t j = 0; j < width; j++) + { + for(size_t c = 0; c < channels; c++) + { + size_t index_in = c * (height * width) + k * width + j; + size_t index_out = k * (width * channels) + j * channels + c; + image_out[index_out] = image_in[index_in]; + } + } + } +} + +void write_file(char * filename, uint8_t * data, const size_t size) +{ + xscope_file_t fp = xscope_open_file(filename, "wb"); + + xscope_fwrite(&fp, data, size); +} + +void write_image_file(char * filename, uint8_t * image, const size_t height, const size_t width, const size_t channels) +{ + printf("Writing image...\n"); + + const size_t img_size = height * width * channels * sizeof(uint8_t); + write_file(filename, image, img_size); + + printf("Image written into file: %s\n", filename); + printf("Image dimentions: %d x %d\n", width, height); +} + +void write_bmp_file(char * filename, uint8_t * image, const size_t height, const size_t width, const size_t channels) +{ + const size_t file_header_len = 14; + const size_t info_header_len = 40; + const size_t img_offset = file_header_len + info_header_len; + + // Define BMP file header and info header + unsigned char bmpFileHeader[file_header_len] = { + 'B', 'M', // Signature + 0, 0, 0, 0, // File size (to be filled later) + 0, 0, // Reserved + 0, 0, // Reserved + img_offset, 0, 0, 0 // Offset to image data + }; + + unsigned char bmpInfoHeader[info_header_len] = { + info_header_len, 0, 0, 0, // Info header size + 0, 0, 0, 0, // Image width (to be filled later) + 0, 0, 0, 0, // Image height (to be filled later) + 1, 0, // Number of color planes + 8 * channels, 0, // Bits per pixel + 0, 0, 0, 0, // Compression method + 0, 0, 0, 0, // Image size (to be filled later) + 0, 0, 0, 0, // Horizontal resolution (pixel per meter) + 0, 0, 0, 0, // Vertical resolution (pixel per meter) + 0, 0, 0, 0, // Number of colors in the palette + 0, 0, 0, 0, // Number of important colors + }; + + // Calculate the row size (including padding) + size_t rowSize = width * channels; + size_t paddingSize = (4 - (rowSize % 4)) % 4; + size_t rowSizeWithPadding = rowSize + paddingSize; + + // Calculate the file size + size_t fileSize = img_offset + (rowSizeWithPadding * height); + + // Update the file size in the BMP file header + bmpFileHeader[2] = (unsigned char)(fileSize); + bmpFileHeader[3] = (unsigned char)(fileSize >> 8); + bmpFileHeader[4] = (unsigned char)(fileSize >> 16); + bmpFileHeader[5] = (unsigned char)(fileSize >> 24); + + // Update the image width in the BMP info header + bmpInfoHeader[4] = (unsigned char)(width); + bmpInfoHeader[5] = (unsigned char)(width >> 8); + bmpInfoHeader[6] = (unsigned char)(width >> 16); + bmpInfoHeader[7] = (unsigned char)(width >> 24); + + // Update the image height in the BMP info header + bmpInfoHeader[8] = (unsigned char)(height); + bmpInfoHeader[9] = (unsigned char)(height >> 8); + bmpInfoHeader[10] = (unsigned char)(height >> 16); + bmpInfoHeader[11] = (unsigned char)(height >> 24); + + printf("Writing bmp image...\n"); + + xscope_file_t fp = xscope_open_file(filename, "wb"); + + xscope_fwrite(&fp, bmpFileHeader, file_header_len * sizeof(unsigned char)); + xscope_fwrite(&fp, bmpInfoHeader, info_header_len * sizeof(unsigned char)); + + for(int64_t i = height - 1; i >= 0; i--) + { + for(size_t j = 0; j < width; j++) + { + // Write the pixel data (assuming RGB order) + size_t offset = i * (channels * width) + j * channels; + xscope_fwrite(&fp, &image[offset + 2], 1 * sizeof(uint8_t)); // Blue + xscope_fwrite(&fp, &image[offset + 1], 1 * sizeof(uint8_t)); // Green + xscope_fwrite(&fp, &image[offset + 0], 1 * sizeof(uint8_t)); // Red + // For 4-channel images, you can write the alpha channel here + // not sure about the comemnt below + //xscope_fwrite(&fp, &image[offset + 3], 1 * sizeof(uint8_t)); // Alpha + } + if(paddingSize) + { + unsigned char padding_array[paddingSize]; + memset(padding_array, (int)'\0', paddingSize); + xscope_fwrite(&fp, padding_array, paddingSize * sizeof(unsigned char)); + } + } + + printf("Image written into file: %s\n", filename); + printf("Image dimentions: %d x %d\n", width, height); +} diff --git a/utils/io_utils/io_utils.h b/utils/io_utils/io_utils.h new file mode 100644 index 00000000..eee3d211 --- /dev/null +++ b/utils/io_utils/io_utils.h @@ -0,0 +1,89 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include +#include +#include + +#ifdef __XC__ +extern "C" { +#endif + +#include "xscope_io_device.h" + + +/** + * @brief Opens a file + * + * @param filename Name of the file + * @note The application has to end with xscope_close_all_files() + */ +void io_open_file(const char* filename); + +/** + * @brief Fills an array with data from a file opened with io_open_file() + * + * @param data Pointer to the data to be filled + * @param size Size of the data + */ +void io_fill_array_from_file(uint8_t* data, const size_t size); + +/** + * @brief Rewinds a file opened with io_open_file() (seek 0) + * + */ +void io_rewind_file(); + +/** + * @brief Dumps data into a file + * + * @param filename Name of the file + * @param data Data to write + * @param size Size of the data + * @note The application has to end with xscope_close_all_files() + */ +void write_file(char * filename, uint8_t * data, const size_t size); + +/** + * @brief Swaps image dimensions from [channel][height][width] + * to [height][width][channel] + * + * @param image_in Input image + * @param image_out Output image + * @param height Image height + * @param width Image width + * @param channels Number of channels + */ +void swap_dimensions(uint8_t * image_in, uint8_t * image_out, const size_t height, const size_t width, const size_t channels); + +/** + * @brief Writes binary image file + * + * @param filename Name of the image + * @param image Pointer to the image data + * @param height Image height + * @param width Image width + * @param channels Number of channels + * @note Image has to be in [height][width][channel] format + * @note The application has to end with xscope_close_all_files() + */ +void write_image_file(char * filename, uint8_t * image, const size_t height, const size_t width, const size_t channels); + +/** + * @brief Writes bmp image file + * + * @param filename Name of the image + * @param image Pointer to the image data + * @param height Image height + * @param width Image width + * @param channels Number of channels + * @note Image has to be in [height][width][channel] format + * @note The application has to end with xscope_close_all_files() + */ +void write_bmp_file(char * filename, uint8_t * image, const size_t height, const size_t width, const size_t channels); + +#ifdef __XC__ +} +#endif diff --git a/utils/io_utils/packet_rx_simulate.c b/utils/io_utils/packet_rx_simulate.c new file mode 100644 index 00000000..5f566aa2 --- /dev/null +++ b/utils/io_utils/packet_rx_simulate.c @@ -0,0 +1,55 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#include +#include +#include + +#include "mipi.h" +#include "packet_handler.h" +#include "io_utils.h" + +#include "packet_rx_simulate.h" + + +void MipiPacketRx_simulate( + in_buffered_port_32_t p_mipi_rxd, + port_t p_mipi_rxa, + streaming_chanend_t c_pkt, + streaming_chanend_t c_ctrl) +{ + const char* filename = "tmp.raw"; + io_open_file(filename); + + while (1) { + // send data + for (uint16_t row = 0; row < MIPI_IMAGE_HEIGHT_PIXELS + 2; row++) { + mipi_packet_t* pkt = (mipi_packet_t*)s_chan_in_word(c_pkt); + // if null pointer return + if (pkt == NULL) { + return; + } + // else continue + switch (row) + { + case 0: + pkt->header = (uint32_t)MIPI_DT_FRAME_START; + break; + + case MIPI_IMAGE_HEIGHT_PIXELS + 1: + pkt->header = (uint32_t)MIPI_DT_FRAME_END; + break; + + default: + pkt->header = (uint32_t)MIPI_DT_RAW8; // header type RAW8 + io_fill_array_from_file((uint8_t*)&pkt->payload[0], MIPI_IMAGE_WIDTH_BYTES); + break; + } + // send back the data + s_chan_out_word(c_pkt, (unsigned)pkt); + delay_milliseconds(2); // LB + } + io_rewind_file(); + delay_milliseconds(10); // FB + } +} diff --git a/utils/io_utils/packet_rx_simulate.h b/utils/io_utils/packet_rx_simulate.h new file mode 100644 index 00000000..5c1c9751 --- /dev/null +++ b/utils/io_utils/packet_rx_simulate.h @@ -0,0 +1,22 @@ +// Copyright 2023 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#pragma once + +#include +#include +#include + +/** + * @brief Mipi packet reciever that takes an image from a file and injects to the board + * + * @param p_mipi_rxd High-Speed Receive Data + * @param p_mipi_rxa RxActiveHS (Output): High-Speed Reception Active. This active high signal indicates that the lane module is actively receiving a high-speed transmission from the lane interconnect. + * @param c_pkt Channel to send packets to + * @param c_ctrl Channel to send control messages to + */ +void MipiPacketRx_simulate( + in_buffered_port_32_t p_mipi_rxd, + port_t p_mipi_rxa, + streaming_chanend_t c_pkt, + streaming_chanend_t c_ctrl); diff --git a/utils/xscope_fileio b/utils/xscope_fileio new file mode 160000 index 00000000..63bce94d --- /dev/null +++ b/utils/xscope_fileio @@ -0,0 +1 @@ +Subproject commit 63bce94d5e80a294e8fc38d1482913af5bac2a14 diff --git a/utils/xscope_fileio.cmake b/utils/xscope_fileio.cmake new file mode 100644 index 00000000..f7c275d0 --- /dev/null +++ b/utils/xscope_fileio.cmake @@ -0,0 +1,18 @@ + +set(XSCOPE_FILEIO_PATH ${CMAKE_CURRENT_LIST_DIR}/xscope_fileio) + +set(LIB_NAME fwk_camera_xscope_fileio) +add_library(${LIB_NAME} INTERFACE) + +target_sources(${LIB_NAME} + INTERFACE + ${XSCOPE_FILEIO_PATH}/xscope_fileio/src/xscope_io_device.c +) + +target_include_directories(${LIB_NAME} + INTERFACE + ${XSCOPE_FILEIO_PATH}/xscope_fileio + ${XSCOPE_FILEIO_PATH}/xscope_fileio/api +) + +add_library(fwk_camera::xscope_fileio ALIAS ${LIB_NAME}) diff --git a/xmos_cmake_toolchain b/xmos_cmake_toolchain index 166cc638..e577fbcf 160000 --- a/xmos_cmake_toolchain +++ b/xmos_cmake_toolchain @@ -1 +1 @@ -Subproject commit 166cc6389005cb6cc3539f8766399ff81366842f +Subproject commit e577fbcf147346264dbe25f82c5cc0e46e648cd6