openmv/ports/nrf/omv_csi.c
iabdalkader 66c8e7e8d5 ports/all: Update framebuffer API.
Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
2025-09-02 16:05:55 +02:00

213 lines
6.8 KiB
C

/*
* 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 <string.h>
#include <stdint.h>
#include <stdbool.h>
#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;
}