/* * SPDX-License-Identifier: MIT * * Copyright (C) 2020-2024 OpenMV, LLC. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * 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 THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * Sensor driver for nRF port. */ #include #include #include #include "py/mphal.h" #include "omv_i2c.h" #include "omv_csi.h" #include "omv_boardconfig.h" #include "unaligned_memcpy.h" #include "nrf_i2s.h" #include "hal/nrf_gpio.h" static uint32_t _vsyncMask; static uint32_t _hrefMask; static uint32_t _pclkMask; static const volatile uint32_t *_vsyncPort; static const volatile uint32_t *_hrefPort; static const volatile uint32_t *_pclkPort; #define interrupts() __enable_irq() #define noInterrupts() __disable_irq() #define digitalPinToPort(P) (P / 32) #ifndef digitalPinToBitMask #define digitalPinToBitMask(P) (1 << (P % 32)) #endif #ifndef portInputRegister #define portInputRegister(P) ((P == 0) ? &NRF_P0->IN : &NRF_P1->IN) #endif #ifndef I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV2 // Note this define is out of spec and has been removed from hal. #define I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV2 (0x80000000UL) /*!< 32 MHz / 2 = 16.0 MHz */ #endif extern void __fatal_error(const char *msg); static int nrf_csi_config(omv_csi_t *csi, omv_csi_config_t config) { if (config == OMV_CSI_CONFIG_INIT) { uint32_t csi_pins[] = { OMV_CSI_D0_PIN, OMV_CSI_D1_PIN, OMV_CSI_D2_PIN, OMV_CSI_D3_PIN, OMV_CSI_D4_PIN, OMV_CSI_D5_PIN, OMV_CSI_D6_PIN, OMV_CSI_D7_PIN, OMV_CSI_VSYNC_PIN, OMV_CSI_HSYNC_PIN, OMV_CSI_PXCLK_PIN, }; // Configure CSI input pins for (int i = 0; i < sizeof(csi_pins) / sizeof(csi_pins[0]); i++) { nrf_gpio_cfg_input(csi_pins[i], NRF_GPIO_PIN_PULLUP); } _vsyncMask = digitalPinToBitMask(OMV_CSI_VSYNC_PIN); _hrefMask = digitalPinToBitMask(OMV_CSI_HSYNC_PIN); _pclkMask = digitalPinToBitMask(OMV_CSI_PXCLK_PIN); _vsyncPort = portInputRegister(digitalPinToPort(OMV_CSI_VSYNC_PIN)); _hrefPort = portInputRegister(digitalPinToPort(OMV_CSI_HSYNC_PIN)); _pclkPort = portInputRegister(digitalPinToPort(OMV_CSI_PXCLK_PIN)); } return 0; } static uint32_t nrf_clk_get_frequency(omv_clk_t *clk) { return OMV_CSI_CLK_FREQUENCY; } static int nrf_clk_set_frequency(omv_clk_t *clk, uint32_t frequency) { nrf_gpio_cfg_output(OMV_CSI_MXCLK_PIN); // Generates 16 MHz signal using I2S peripheral NRF_I2S->CONFIG.MCKEN = (I2S_CONFIG_MCKEN_MCKEN_ENABLE << I2S_CONFIG_MCKEN_MCKEN_Pos); NRF_I2S->CONFIG.MCKFREQ = I2S_CONFIG_MCKFREQ_MCKFREQ_32MDIV2 << I2S_CONFIG_MCKFREQ_MCKFREQ_Pos; NRF_I2S->CONFIG.MODE = I2S_CONFIG_MODE_MODE_MASTER << I2S_CONFIG_MODE_MODE_Pos; NRF_I2S->PSEL.MCK = (OMV_CSI_MXCLK_PIN << I2S_PSEL_MCK_PIN_Pos); NRF_I2S->ENABLE = 1; NRF_I2S->TASKS_START = 1; return 0; } int omv_csi_set_windowing(omv_csi_t *csi, int x, int y, int w, int h) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // This is the default snapshot function, which can be replaced in omv_csi_init functions. static int nrf_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { vbuffer_t *buffer = NULL; framebuffer_t *fb = csi->fb; // This driver supports a single buffer. if (fb->buf_count != 1) { omv_csi_set_framebuffers(csi, 1, false); } // Acquire a buffer from the free queue. if ((buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK))) { return OMV_CSI_ERROR_FRAMEBUFFER_ERROR; } uint8_t *b = buffer->data; uint32_t _width = fb->w; uint32_t _height = fb->h; int bytesPerRow = _width * 2; // Always read 2 BPP bool _grayscale = (csi->pixformat == PIXFORMAT_GRAYSCALE); uint32_t ulPin = 32; // P1.xx set of GPIO is in 'pin' 32 and above NRF_GPIO_Type *port = nrf_gpio_pin_port_decode(&ulPin); noInterrupts(); // Falling edge indicates start of frame while ((*_vsyncPort & _vsyncMask) == 0) { // Wait for high } while ((*_vsyncPort & _vsyncMask) != 0) { // Wait for low } for (int i = 0; i < _height; i++) { // rising edge indicates start of line while ((*_hrefPort & _hrefMask) == 0) { // Wait for high } for (int j = 0; j < bytesPerRow; j++) { while ((*_pclkPort & _pclkMask) != 0) { // Wait for low } uint32_t in = port->IN; // read all bits in parallel if (!_grayscale || !(j & 1)) { // Note D0 & D1 are swapped on the ML kit. *b++ = ((in >> 8) | ((in >> 3) & 1) | ((in >> 1) & 2)); } while ((*_pclkPort & _pclkMask) == 0) { // Wait for high } } while ((*_hrefPort & _hrefMask) != 0) { // Wait for low } } interrupts(); // Not useful for the NRF but must call to keep API the same. if (csi->frame_cb.fun) { csi->frame_cb.fun(csi->frame_cb.arg); } // Set framebuffer pixel format. fb->pixfmt = csi->pixformat; // Swap bytes if set. if ((fb->pixfmt == PIXFORMAT_RGB565 && csi->rgb_swap) || (fb->pixfmt == PIXFORMAT_YUV422 && csi->yuv_swap)) { unaligned_memcpy_rev16(buffer->data, buffer->data, _width * _height); } // Release the current framebuffer. framebuffer_release(fb, FB_FLAG_FREE); // Set the user image. framebuffer_to_image(fb, image); return 0; } int omv_csi_ops_init(omv_csi_t *csi) { // Set CSI ops. csi->config = nrf_csi_config; csi->snapshot = nrf_csi_snapshot; // Set CSI clock ops. csi->clk->freq = OMV_CSI_CLK_FREQUENCY; csi->clk->set_freq = nrf_clk_set_frequency; csi->clk->get_freq = nrf_clk_get_frequency; return 0; }