diff --git a/Makefile b/Makefile index 74bcb0665..991f75d3f 100755 --- a/Makefile +++ b/Makefile @@ -138,6 +138,11 @@ MPY_CFLAGS += -DMICROPY_PY_CSI=1 MPY_MKARGS += MICROPY_PY_CSI=1 endif +ifeq ($(MICROPY_PY_CSI_NG), 1) +MPY_CFLAGS += -DMICROPY_PY_CSI_NG=1 +MPY_MKARGS += MICROPY_PY_CSI_NG=1 +endif + ifeq ($(MICROPY_PY_FIR), 1) MPY_CFLAGS += -DMICROPY_PY_FIR=1 MPY_MKARGS += MICROPY_PY_FIR=1 diff --git a/boards/ARDUINO_GIGA/omv_boardconfig.mk b/boards/ARDUINO_GIGA/omv_boardconfig.mk index 968f68bba..35b720675 100755 --- a/boards/ARDUINO_GIGA/omv_boardconfig.mk +++ b/boards/ARDUINO_GIGA/omv_boardconfig.mk @@ -15,6 +15,7 @@ OMV_BOARD_CFLAGS = -DCORE_CM7 \ OMV_ENABLE_BL=0 OMV_ENABLE_UVC=1 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_ULAB = 1 MICROPY_PY_LWIP = 1 diff --git a/boards/ARDUINO_NANO_33_BLE_SENSE/omv_boardconfig.mk b/boards/ARDUINO_NANO_33_BLE_SENSE/omv_boardconfig.mk index 663040f99..7b9316aa0 100755 --- a/boards/ARDUINO_NANO_33_BLE_SENSE/omv_boardconfig.mk +++ b/boards/ARDUINO_NANO_33_BLE_SENSE/omv_boardconfig.mk @@ -14,6 +14,7 @@ OMV_BOARD_CFLAGS = -DNRF52_SERIES \ -DCONFIG_GPIO_AS_PINRESET DFU_DEVICE=0x0483:0xdf11 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_ULAB=1 MICROPY_PY_AUDIO=1 diff --git a/boards/ARDUINO_NANO_RP2040_CONNECT/dcmi.pio b/boards/ARDUINO_NANO_RP2040_CONNECT/dcmi.pio index 489f4eba8..83cd1aa21 100644 --- a/boards/ARDUINO_NANO_RP2040_CONNECT/dcmi.pio +++ b/boards/ARDUINO_NANO_RP2040_CONNECT/dcmi.pio @@ -73,7 +73,7 @@ PIXEL_LOOP: .wrap % c-sdk { -int omv_csi_config(omv_csi_config_t config) { +int omv_csi_config(omv_csi_t *csi, omv_csi_config_t config) { if (config == OMV_CSI_CONFIG_PIXFORMAT) { uint offset; pio_sm_config config; @@ -86,7 +86,7 @@ int omv_csi_config(omv_csi_config_t config) { } pio_sm_set_consecutive_pindirs(OMV_CSI_PIO, OMV_CSI_SM, OMV_CSI_D0_PIN, 7, false); - if (csi.pixformat == PIXFORMAT_GRAYSCALE) { + if (csi->pixformat == PIXFORMAT_GRAYSCALE) { offset = pio_add_program(OMV_CSI_PIO, &dcmi_odd_byte_program); config = dcmi_odd_byte_program_get_default_config(offset); } else { diff --git a/boards/ARDUINO_NANO_RP2040_CONNECT/omv_boardconfig.cmake b/boards/ARDUINO_NANO_RP2040_CONNECT/omv_boardconfig.cmake index a785d40a7..80c51d516 100644 --- a/boards/ARDUINO_NANO_RP2040_CONNECT/omv_boardconfig.cmake +++ b/boards/ARDUINO_NANO_RP2040_CONNECT/omv_boardconfig.cmake @@ -1,4 +1,5 @@ set(MICROPY_PY_CSI 1) +set(MICROPY_PY_CSI_NG 1) set(MICROPY_PY_FIR 1) set(MICROPY_PY_ULAB 1) set(MICROPY_PY_NINAW10 1) diff --git a/boards/ARDUINO_NICLA_VISION/omv_boardconfig.mk b/boards/ARDUINO_NICLA_VISION/omv_boardconfig.mk index c13a2d20c..f460bba2d 100755 --- a/boards/ARDUINO_NICLA_VISION/omv_boardconfig.mk +++ b/boards/ARDUINO_NICLA_VISION/omv_boardconfig.mk @@ -17,6 +17,7 @@ OMV_ENABLE_BL = 0 OMV_ENABLE_UVC = 1 OMV_LEPTON_SDK_ENABLE=1 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_ULAB = 1 MICROPY_PY_WINC1500 = 0 diff --git a/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.mk b/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.mk index 403d9b526..aa39a4614 100755 --- a/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.mk +++ b/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.mk @@ -16,6 +16,7 @@ OMV_ENABLE_BL=0 OMV_ENABLE_UVC=1 OMV_LEPTON_SDK_ENABLE=1 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_ULAB = 1 MICROPY_PY_WINC1500 = 0 diff --git a/boards/OPENMV1/omv_boardconfig.mk b/boards/OPENMV1/omv_boardconfig.mk index a6afbbbbb..60556de2f 100755 --- a/boards/OPENMV1/omv_boardconfig.mk +++ b/boards/OPENMV1/omv_boardconfig.mk @@ -11,5 +11,6 @@ OMV_ENABLE_BL=1 OMV_ENABLE_UVC=1 OMV_BOARD_CFLAGS=-DUSE_USB_FS MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_WINC1500 = 1 $(error "This camera is no longer supported :O Have fun hacking the code $(BRACKET)") diff --git a/boards/OPENMV2/omv_boardconfig.mk b/boards/OPENMV2/omv_boardconfig.mk index 36dc222e3..693b336a9 100755 --- a/boards/OPENMV2/omv_boardconfig.mk +++ b/boards/OPENMV2/omv_boardconfig.mk @@ -11,6 +11,7 @@ OMV_ENABLE_BL=1 OMV_ENABLE_UVC=1 OMV_LEPTON_SDK_ENABLE=1 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_WINC1500 = 1 MICROPY_PY_DISPLAY = 1 diff --git a/boards/OPENMV3/omv_boardconfig.mk b/boards/OPENMV3/omv_boardconfig.mk index 8d4e7bed1..8d196de72 100755 --- a/boards/OPENMV3/omv_boardconfig.mk +++ b/boards/OPENMV3/omv_boardconfig.mk @@ -11,6 +11,7 @@ OMV_ENABLE_BL=1 OMV_ENABLE_UVC=1 OMV_LEPTON_SDK_ENABLE=1 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_ULAB = 1 MICROPY_PY_WINC1500 = 1 diff --git a/boards/OPENMV4/omv_boardconfig.mk b/boards/OPENMV4/omv_boardconfig.mk index d8bea28d3..af82dc27e 100755 --- a/boards/OPENMV4/omv_boardconfig.mk +++ b/boards/OPENMV4/omv_boardconfig.mk @@ -12,6 +12,7 @@ OMV_ENABLE_BL=1 OMV_ENABLE_UVC=1 OMV_LEPTON_SDK_ENABLE=1 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_ULAB = 1 MICROPY_PY_WINC1500 = 1 diff --git a/boards/OPENMV4P/omv_boardconfig.mk b/boards/OPENMV4P/omv_boardconfig.mk index 099ec12df..384fb154f 100755 --- a/boards/OPENMV4P/omv_boardconfig.mk +++ b/boards/OPENMV4P/omv_boardconfig.mk @@ -13,6 +13,7 @@ OMV_ENABLE_UVC=1 OMV_GENX320_ENABLE=1 OMV_LEPTON_SDK_ENABLE=1 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_ULAB = 1 MICROPY_PY_WINC1500 = 1 diff --git a/boards/OPENMVPT/omv_boardconfig.mk b/boards/OPENMVPT/omv_boardconfig.mk index 0d4a8f8e3..bf3c96115 100755 --- a/boards/OPENMVPT/omv_boardconfig.mk +++ b/boards/OPENMVPT/omv_boardconfig.mk @@ -12,6 +12,7 @@ OMV_ENABLE_BL=1 OMV_ENABLE_UVC=1 OMV_LEPTON_SDK_ENABLE=1 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_ULAB = 1 MICROPY_PY_WINC1500 = 1 diff --git a/boards/OPENMV_AE3/omv_boardconfig.h b/boards/OPENMV_AE3/omv_boardconfig.h index 1c57fce5a..3c4ed5f82 100644 --- a/boards/OPENMV_AE3/omv_boardconfig.h +++ b/boards/OPENMV_AE3/omv_boardconfig.h @@ -249,6 +249,7 @@ extern unsigned char OMV_BOARD_UID_ADDR[12]; // Unique address. // CSI I2C bus #define OMV_CSI_I2C_ID (0) #define OMV_CSI_I2C_SPEED (OMV_I2C_SPEED_FULL) +#define OMV_CSI_I2C_REINIT (0) // FIR I2C bus #define OMV_FIR_I2C_ID (1) diff --git a/boards/OPENMV_AE3/omv_boardconfig.mk b/boards/OPENMV_AE3/omv_boardconfig.mk index 0497a6606..1f75f7886 100755 --- a/boards/OPENMV_AE3/omv_boardconfig.mk +++ b/boards/OPENMV_AE3/omv_boardconfig.mk @@ -14,6 +14,7 @@ OMV_HSE_VALUE=12000000 OMV_LEPTON_SDK_ENABLE=1 OMV_TOF_VL53L8CX_ENABLE=1 MICROPY_PY_CSI = $(CORE_M55_HP) +MICROPY_PY_CSI_NG = $(CORE_M55_HP) MICROPY_PY_FIR = 1 MICROPY_PY_TOF = 1 MICROPY_PY_ULAB = 1 diff --git a/boards/OPENMV_N6/omv_boardconfig.h b/boards/OPENMV_N6/omv_boardconfig.h index 107feeb2a..6be27022e 100644 --- a/boards/OPENMV_N6/omv_boardconfig.h +++ b/boards/OPENMV_N6/omv_boardconfig.h @@ -52,6 +52,7 @@ #define OMV_LEPTON_ENABLE (1) #define OMV_PAG7936_ENABLE (1) #define OMV_PAG7936_MIPI_CSI2 (1) +#define OMV_SOFTCSI_ENABLE (0) #define OMV_PS5520_ENABLE (1) #define OMV_GENX320_EHC_ENABLE (1) #define OMV_GENX320_HSYNC_VALUE (0x1) @@ -279,6 +280,7 @@ #define OMV_CSI_DMA_REQUEST (HPDMA1_REQUEST_DCMI_PSSI) #define OMV_CSI_DMA_MEMCPY_ENABLE (0) #define OMV_CSI_HW_CROP_ENABLE (1) +#define OMV_CSI_MAX_DEVICES (3) #define OMV_CSI_D0_PIN (&omv_pin_A1_DCMI) #define OMV_CSI_D1_PIN (&omv_pin_A10_DCMI) diff --git a/boards/OPENMV_N6/omv_boardconfig.mk b/boards/OPENMV_N6/omv_boardconfig.mk index 35cb8202d..4b2e25f9e 100755 --- a/boards/OPENMV_N6/omv_boardconfig.mk +++ b/boards/OPENMV_N6/omv_boardconfig.mk @@ -27,6 +27,7 @@ OMV_BOSON_ENABLE=1 OMV_GENX320_ENABLE=1 OMV_LEPTON_SDK_ENABLE=1 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_IMU = 1 MICROPY_PY_ULAB = 1 diff --git a/boards/OPENMV_RT1060/omv_boardconfig.h b/boards/OPENMV_RT1060/omv_boardconfig.h index 8fd690c79..9a53c3489 100644 --- a/boards/OPENMV_RT1060/omv_boardconfig.h +++ b/boards/OPENMV_RT1060/omv_boardconfig.h @@ -202,6 +202,7 @@ #define OMV_CSI_DMA_MEMCPY_ENABLE (1) #define OMV_CSI_HW_SWAP_ENABLE (1) #define OMV_CSI_HW_CROP_ENABLE (1) +#define OMV_CSI_MAX_DEVICES (2) #define OMV_CSI_D0_PIN (&omv_pin_DCMI_D0) #define OMV_CSI_D1_PIN (&omv_pin_DCMI_D1) diff --git a/boards/OPENMV_RT1060/omv_boardconfig.mk b/boards/OPENMV_RT1060/omv_boardconfig.mk index d4c4ae55f..6364ad530 100755 --- a/boards/OPENMV_RT1060/omv_boardconfig.mk +++ b/boards/OPENMV_RT1060/omv_boardconfig.mk @@ -12,6 +12,7 @@ OMV_BOSON_ENABLE=1 OMV_GENX320_ENABLE=1 OMV_LEPTON_SDK_ENABLE=1 MICROPY_PY_CSI = 1 +MICROPY_PY_CSI_NG = 1 MICROPY_PY_FIR = 1 MICROPY_PY_ULAB = 1 MICROPY_PY_LWIP = 1 diff --git a/boards/RPI_PICO/dcmi.pio b/boards/RPI_PICO/dcmi.pio index 39c4091eb..4d18ec702 100644 --- a/boards/RPI_PICO/dcmi.pio +++ b/boards/RPI_PICO/dcmi.pio @@ -57,7 +57,7 @@ PIXEL_LOOP: .wrap % c-sdk { -int omv_csi_config(omv_csi_config_t config) { +int omv_csi_config(omv_csi_t *csi, omv_csi_config_t config) { if (config == OMV_CSI_CONFIG_PIXFORMAT) { uint offset; pio_sm_config config; @@ -70,7 +70,7 @@ int omv_csi_config(omv_csi_config_t config) { } pio_sm_set_consecutive_pindirs(OMV_CSI_PIO, OMV_CSI_SM, OMV_CSI_D0_PIN, 8, false); - if (csi.pixformat == PIXFORMAT_GRAYSCALE) { + if (csi->pixformat == PIXFORMAT_GRAYSCALE) { offset = pio_add_program(OMV_CSI_PIO, &dcmi_odd_byte_program); config = dcmi_odd_byte_program_get_default_config(offset); } else { diff --git a/boards/RPI_PICO/omv_boardconfig.cmake b/boards/RPI_PICO/omv_boardconfig.cmake index 1ac704489..2e9175b36 100644 --- a/boards/RPI_PICO/omv_boardconfig.cmake +++ b/boards/RPI_PICO/omv_boardconfig.cmake @@ -1,4 +1,5 @@ set(MICROPY_PY_CSI 1) +set(MICROPY_PY_CSI_NG 1) set(MICROPY_PY_FIR 1) set(MICROPY_PY_ULAB 1) set(MICROPY_PY_NINAW10 0) diff --git a/common/omv_csi.c b/common/omv_csi.c index 6b32e984a..e42074a92 100644 --- a/common/omv_csi.c +++ b/common/omv_csi.c @@ -65,10 +65,6 @@ #include "framebuffer.h" #include "unaligned_memcpy.h" -#ifndef OMV_CSI_MAX_DEVICES -#define OMV_CSI_MAX_DEVICES (5) -#endif - #ifndef OMV_CSI_RESET_DELAY #define OMV_CSI_RESET_DELAY (10) #endif @@ -77,10 +73,24 @@ #define OMV_CSI_POWER_DELAY (10) #endif +#ifndef OMV_CSI_I2C_MAX_DEV +#define OMV_CSI_I2C_MAX_DEV (10) +#endif + +#ifndef OMV_CSI_I2C_REINIT +#define OMV_CSI_I2C_REINIT (1) +#endif + #ifndef __weak #define __weak __attribute__((weak)) #endif +// Used for scanning the I2C bus. +typedef struct _i2c_dev { + uint8_t slv_addr; // I2C address. + uint32_t chip_id; // Chip ID. +} i2c_dev_t; + // Sensor frame size/resolution table. uint16_t resolution[][2] = { {0, 0 }, @@ -129,197 +139,274 @@ uint16_t resolution[][2] = { {2592, 1944}, /* WQXGA2 */ }; -omv_csi_t csi = {0}; -omv_i2c_t csi_i2c = {0}; +omv_csi_t csi_all[OMV_CSI_MAX_DEVICES] = {0}; __weak void omv_csi_init0() { - // Reset the csi state - memset(&csi, 0, sizeof(omv_csi_t)); + for (size_t i=0; ii2c; + + // Abort ongoing transfer + omv_csi_abort(csi, true, false); + + // Reset delays + csi->disable_delays = false; + + omv_csi_set_vsync_callback(csi, (omv_csi_cb_t) { NULL, NULL }); + omv_csi_set_frame_callback(csi, (omv_csi_cb_t) { NULL, NULL }); + + // Re-init i2c bus to reset the bus state after soft reset, + // which could have interrupted the bus mid-transfer. + if (i2c && i2c->initialized) { + // Reinitialize the bus using the last used id and speed. + #if OMV_CSI_I2C_REINIT + // On some ports, this casues I2C/I3C to lock up. + omv_i2c_init(i2c, i2c->id, i2c->speed); + #endif + } + } } __weak int omv_csi_init() { // Reset the csi state - memset(&csi, 0, sizeof(omv_csi_t)); + memset(csi_all, 0, sizeof(csi_all)); return OMV_CSI_ERROR_CTL_UNSUPPORTED; } +omv_csi_t *omv_csi_get(int id) { + omv_csi_t *csi = NULL; + + for (size_t i=0; !csi && imatch != NULL) { + return csi->match(csi, id); + } + + return (csi->chip_id == id); +} + __weak int omv_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { - return OMV_CSI_ERROR_CTL_UNSUPPORTED; + // Call the sensor specific function. + if (csi->abort != NULL && + csi->abort(csi, fifo_flush, in_irq) != 0) { + return OMV_CSI_ERROR_CTL_FAILED; + } + + csi->first_line = false; + csi->drop_frame = false; + csi->last_frame_ms = 0; + csi->last_frame_ms_valid = false; + + if (csi->fb) { + if (fifo_flush) { + framebuffer_flush_buffers(csi->fb, true); + } else if (!csi->disable_full_flush) { + framebuffer_flush_buffers(csi->fb, false); + } + } + return 0; } -__weak int omv_csi_reset() { +__weak int omv_csi_reset(omv_csi_t *csi, bool hard) { // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); + omv_csi_abort(csi, true, false); // Reset the csi state - csi.sde = 0; - csi.pixformat = 0; - csi.framesize = 0; - csi.framerate = 0; - csi.first_line = false; - csi.drop_frame = false; - csi.last_frame_ms = 0; - csi.last_frame_ms_valid = false; - csi.gainceiling = 0; - csi.hmirror = false; - csi.vflip = false; - csi.transpose = false; + csi->sde = 0; + csi->pixformat = 0; + csi->framesize = 0; + csi->framerate = 0; + csi->first_line = false; + csi->drop_frame = false; + csi->last_frame_ms = 0; + csi->last_frame_ms_valid = false; + csi->gainceiling = 0; + csi->hmirror = false; + csi->vflip = false; + csi->transpose = false; #if MICROPY_PY_IMU - csi.auto_rotation = (csi.chip_id == OV7690_ID); + csi->auto_rotation = (csi->chip_id == OV7690_ID); #else - csi.auto_rotation = false; + csi->auto_rotation = false; #endif // MICROPY_PY_IMU - csi.vsync_callback = NULL; - csi.frame_callback = NULL; + + csi->vsync_cb = (omv_csi_cb_t) { NULL, NULL }; + csi->frame_cb = (omv_csi_cb_t) { NULL, NULL }; // Reset default color palette. - csi.color_palette = rainbow_table; - - csi.disable_full_flush = false; - + csi->color_palette = rainbow_table; + csi->disable_full_flush = false; + // Restore shutdown state on reset. - omv_csi_shutdown(false); + omv_csi_shutdown(csi, false); - // Disable the bus before reset. - omv_i2c_enable(csi.i2c, false); + if (hard) { + // Disable the bus before reset. + omv_i2c_enable(csi->i2c, false); - #if defined(OMV_CSI_RESET_PIN) - // Hard-reset the csi - if (csi.reset_pol == OMV_CSI_ACTIVE_HIGH) { - omv_gpio_write(OMV_CSI_RESET_PIN, 1); - mp_hal_delay_ms(10); - omv_gpio_write(OMV_CSI_RESET_PIN, 0); - } else { - omv_gpio_write(OMV_CSI_RESET_PIN, 0); - mp_hal_delay_ms(10); - omv_gpio_write(OMV_CSI_RESET_PIN, 1); + #if defined(OMV_CSI_RESET_PIN) + // Hard-reset the csi + if (csi->reset_pol == OMV_CSI_ACTIVE_HIGH) { + omv_gpio_write(OMV_CSI_RESET_PIN, 1); + mp_hal_delay_ms(10); + omv_gpio_write(OMV_CSI_RESET_PIN, 0); + } else { + omv_gpio_write(OMV_CSI_RESET_PIN, 0); + mp_hal_delay_ms(10); + omv_gpio_write(OMV_CSI_RESET_PIN, 1); + } + #endif + + mp_hal_delay_ms(OMV_CSI_RESET_DELAY); + + // Re-enable the bus. + omv_i2c_enable(csi->i2c, true); } - #endif - - mp_hal_delay_ms(OMV_CSI_RESET_DELAY); - - // Re-enable the bus. - omv_i2c_enable(csi.i2c, true); // Call csi-specific reset function - if (csi.reset != NULL && - csi.reset(&csi) != 0) { + if (csi->reset != NULL && + csi->reset(csi) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } // Reset framebuffers - framebuffer_flush_buffers(csi.fb, true); - + framebuffer_flush_buffers(csi->fb, true); return 0; } -static int omv_csi_detect() { - uint8_t devs_list[OMV_CSI_MAX_DEVICES]; - int n_devs = omv_i2c_scan(csi.i2c, devs_list, OMV_ARRAY_SIZE(devs_list)); +static size_t omv_csi_detect(omv_i2c_t *i2c, i2c_dev_t *dev_list) { + size_t dev_count = 0; + uint8_t addr_list[OMV_CSI_I2C_MAX_DEV]; + int addr_count = omv_i2c_scan(i2c, addr_list, OMV_ARRAY_SIZE(addr_list)); + + for (int i=0; i> 8), GENX320_CHIP_ID}; - omv_i2c_write_bytes(csi.i2c, slv_addr, buf, 2, OMV_I2C_XFER_NO_STOP); - omv_i2c_read_bytes(csi.i2c, slv_addr, (uint8_t *) &csi.chip_id, 4, OMV_I2C_XFER_NO_FLAGS); - csi.chip_id = __REV(csi.chip_id); + omv_i2c_write_bytes(i2c, slv_addr, buf, 2, OMV_I2C_XFER_NO_STOP); + omv_i2c_read_bytes(i2c, slv_addr, + (uint8_t *) &chip_id, 4, OMV_I2C_XFER_NO_FLAGS); + chip_id = __REV(chip_id); } #endif // (OMV_GENX320_ENABLE == 1) } - return slv_addr; + break; #endif // (OMV_OV5640_ENABLE == 1) || (OMV_GC2145_ENABLE == 1) || (OMV_GENX320_ENABLE == 1) #if (OMV_OV7725_ENABLE == 1) || (OMV_OV7670_ENABLE == 1) || (OMV_OV7690_ENABLE == 1) case OV7725_SLV_ADDR: // Or OV7690 or OV7670. - omv_i2c_readb(csi.i2c, slv_addr, OV_CHIP_ID, (uint8_t *) &csi.chip_id); - return slv_addr; + omv_i2c_readb(i2c, slv_addr, OV_CHIP_ID, (uint8_t *) &chip_id); + break; #endif //(OMV_OV7725_ENABLE == 1) || (OMV_OV7670_ENABLE == 1) || (OMV_OV7690_ENABLE == 1) #if (OMV_MT9V0XX_ENABLE == 1) case MT9V0XX_SLV_ADDR: - omv_i2c_readw(csi.i2c, slv_addr, ON_CHIP_ID, (uint16_t *) &csi.chip_id); - return slv_addr; + omv_i2c_readw(i2c, slv_addr, ON_CHIP_ID, (uint16_t *) &chip_id); + break; #endif //(OMV_MT9V0XX_ENABLE == 1) #if (OMV_MT9M114_ENABLE == 1) case MT9M114_SLV_ADDR: - omv_i2c_readw2(csi.i2c, slv_addr, ON_CHIP_ID, (uint16_t *) &csi.chip_id); - return slv_addr; + omv_i2c_readw2(i2c, slv_addr, ON_CHIP_ID, (uint16_t *) &chip_id); + break; #endif // (OMV_MT9M114_ENABLE == 1) #if (OMV_BOSON_ENABLE == 1) case BOSON_SLV_ADDR: - csi.chip_id = BOSON_ID; - return slv_addr; + chip_id = BOSON_ID; + break; #endif // (OMV_BOSON_ENABLE == 1) #if (OMV_LEPTON_ENABLE == 1) case LEPTON_SLV_ADDR: - csi.chip_id = LEPTON_ID; - return slv_addr; + chip_id = LEPTON_ID; + break; #endif // (OMV_LEPTON_ENABLE == 1) #if (OMV_HM01B0_ENABLE == 1) || (OMV_HM0360_ENABLE == 1) case HM0XX0_SLV_ADDR: - omv_i2c_readb2(csi.i2c, slv_addr, HIMAX_CHIP_ID, (uint8_t *) &csi.chip_id); - return slv_addr; + omv_i2c_readb2(i2c, slv_addr, HIMAX_CHIP_ID, (uint8_t *) &chip_id); + break; #endif // (OMV_HM01B0_ENABLE == 1) || (OMV_HM0360_ENABLE == 1) #if (OMV_FROGEYE2020_ENABLE == 1) case FROGEYE2020_SLV_ADDR: - csi.chip_id = FROGEYE2020_ID; - return slv_addr; + chip_id = FROGEYE2020_ID; + break; #endif // (OMV_FROGEYE2020_ENABLE == 1) #if (OMV_PAG7920_ENABLE == 1) case PAG7920_SLV_ADDR: - omv_i2c_readw2(csi.i2c, slv_addr, PIXART_CHIP_ID, (uint16_t *) &csi.chip_id); - csi.chip_id = ((csi.chip_id << 8) | (csi.chip_id >> 8)) & 0xFFFF; - return slv_addr; + omv_i2c_readw2(i2c, slv_addr, PIXART_CHIP_ID, (uint16_t *) &chip_id); + chip_id = ((chip_id << 8) | (chip_id >> 8)) & 0xFFFF; + break; #endif // (OMV_PAG7920_ENABLE == 1) #if (OMV_PAG7936_ENABLE == 1) case PAG7936_SLV_ADDR: - omv_i2c_readw2(csi.i2c, slv_addr, PIXART_CHIP_ID, (uint16_t *) &csi.chip_id); - csi.chip_id = ((csi.chip_id << 8) | (csi.chip_id >> 8)) & 0xFFFF; - return slv_addr; + case PAG7936_SLV_ADDR_ALT: + omv_i2c_readw2(i2c, slv_addr, PIXART_CHIP_ID, (uint16_t *) &chip_id); + chip_id = ((chip_id << 8) | (chip_id >> 8)) & 0xFFFF; + break; #endif // (OMV_PAG7936_ENABLE == 1) #if (OMV_PS5520_ENABLE == 1) case PS5520_SLV_ADDR: - omv_i2c_readw2(csi.i2c, slv_addr, PIXART_CHIP_ID, (uint16_t *) &csi.chip_id); - return slv_addr; + omv_i2c_readw2(i2c, slv_addr, PIXART_CHIP_ID, (uint16_t *) &chip_id); + break; #endif // (OMV_PS5520_ENABLE == 1) } + + if (chip_id && dev_count < OMV_CSI_MAX_DEVICES) { + dev_list[dev_count++] = (i2c_dev_t) { slv_addr, chip_id }; + } } - return 0; + return dev_count; } -int omv_csi_probe_init(uint32_t bus_id, uint32_t bus_speed) { +int omv_csi_probe(omv_i2c_t *i2c) { int init_ret = 0; + bool reset_pol = OMV_CSI_ACTIVE_HIGH; + bool power_pol = OMV_CSI_ACTIVE_HIGH; + + size_t dev_count = 0; + size_t aux_count = 0; + i2c_dev_t dev_list[OMV_CSI_MAX_DEVICES] = { 0 }; + #if defined(OMV_CSI_POWER_PIN) - csi.power_pol = OMV_CSI_ACTIVE_HIGH; // Do a power cycle omv_gpio_write(OMV_CSI_POWER_PIN, 1); mp_hal_delay_ms(10); @@ -329,7 +416,6 @@ int omv_csi_probe_init(uint32_t bus_id, uint32_t bus_speed) { #endif #if defined(OMV_CSI_RESET_PIN) - csi.reset_pol = OMV_CSI_ACTIVE_HIGH; // Reset the csi omv_gpio_write(OMV_CSI_RESET_PIN, 1); mp_hal_delay_ms(10); @@ -338,265 +424,313 @@ int omv_csi_probe_init(uint32_t bus_id, uint32_t bus_speed) { mp_hal_delay_ms(OMV_CSI_RESET_DELAY); #endif - // Initialize the camera bus. - omv_i2c_init(csi.i2c, bus_id, bus_speed); - mp_hal_delay_ms(10); - // Scan the bus multiple times using different reset and power-down // polarities, until a supported sensor is detected. - if ((csi.slv_addr = omv_csi_detect()) == 0) { + if (!(dev_count = omv_csi_detect(i2c, dev_list))) { // No devices were detected, try scanning the bus // again with different reset/power-down polarities. #if defined(OMV_CSI_RESET_PIN) - csi.reset_pol = OMV_CSI_ACTIVE_LOW; + reset_pol = OMV_CSI_ACTIVE_LOW; omv_gpio_write(OMV_CSI_RESET_PIN, 1); mp_hal_delay_ms(OMV_CSI_RESET_DELAY); #endif - if ((csi.slv_addr = omv_csi_detect()) == 0) { + if (!(dev_count = omv_csi_detect(i2c, dev_list))) { #if defined(OMV_CSI_POWER_PIN) - csi.power_pol = OMV_CSI_ACTIVE_LOW; + power_pol = OMV_CSI_ACTIVE_LOW; omv_gpio_write(OMV_CSI_POWER_PIN, 1); mp_hal_delay_ms(OMV_CSI_POWER_DELAY); #endif - if ((csi.slv_addr = omv_csi_detect()) == 0) { + if (!(dev_count = omv_csi_detect(i2c, dev_list))) { #if defined(OMV_CSI_RESET_PIN) - csi.reset_pol = OMV_CSI_ACTIVE_HIGH; + reset_pol = OMV_CSI_ACTIVE_HIGH; omv_gpio_write(OMV_CSI_RESET_PIN, 0); mp_hal_delay_ms(OMV_CSI_RESET_DELAY); #endif - csi.slv_addr = omv_csi_detect(); + dev_count = omv_csi_detect(i2c, dev_list); } } + } - // If no devices were detected on the I2C bus, try the SPI bus. - if (csi.slv_addr == 0) { - if (0) { - #if (OMV_PAJ6100_ENABLE == 1) - } else if (paj6100_detect(&csi)) { - // Found PixArt PAJ6100 - csi.chip_id = PAJ6100_ID; - csi.power_pol = OMV_CSI_ACTIVE_LOW; - csi.reset_pol = OMV_CSI_ACTIVE_LOW; - #endif - } else { - #if (OMV_SOFTCSI_ENABLE == 1) - csi.slv_addr = SOFTCSI_SLV_ADDR; - csi.chip_id = SOFTCSI_ID; - #else - return OMV_CSI_ERROR_ISC_UNDETECTED; + // Add special devices, such as SPI sensors, soft-CSI etc... + #if (OMV_SOFTCSI_ENABLE == 1) + if (dev_count < OMV_CSI_MAX_DEVICES) { + dev_list[dev_count++] = (i2c_dev_t) { 0, SOFTCSI_ID }; + } + #endif + + #if OMV_PAJ6100_ENABLE + if ((dev_count < OMV_CSI_MAX_DEVICES) && paj6100_detect(NULL)) { + // Found PixArt PAJ6100 + power_pol = OMV_CSI_ACTIVE_LOW; + reset_pol = OMV_CSI_ACTIVE_LOW; + dev_list[dev_count++] = (i2c_dev_t) { 0, PAJ6100_ID }; + } + #endif + + // Fail if no devices were detected and no special sensors enabled. + if (!dev_count) { + return OMV_CSI_ERROR_ISC_UNDETECTED; + } + + // Initialize detected sensors. + for (size_t i=0; ipower_pol = power_pol; + csi->reset_pol = reset_pol; + csi->chip_id = dev_list[i].chip_id; + csi->slv_addr = dev_list[i].slv_addr; + + switch (dev_list[i].chip_id) { + #if (OMV_OV2640_ENABLE == 1) + case OV2640_ID: + if (omv_csi_set_clk_frequency(OMV_OV2640_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = ov2640_init(csi); + break; + #endif // (OMV_OV2640_ENABLE == 1) + + #if (OMV_OV5640_ENABLE == 1) + case OV5640_ID: { + int freq = OMV_OV5640_CLK_FREQ; + #if (OMV_OV5640_REV_Y_CHECK == 1) + if (HAL_GetREVID() < 0x2003) { + // Is this REV Y? + freq = OMV_OV5640_REV_Y_FREQ; + } #endif + if (omv_csi_set_clk_frequency(freq) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = ov5640_init(csi); + break; + } + #endif // (OMV_OV5640_ENABLE == 1) + + #if (OMV_OV7670_ENABLE == 1) + case OV7670_ID: + if (omv_csi_set_clk_frequency(OMV_OV7670_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = ov7670_init(csi); + break; + #endif // (OMV_OV7670_ENABLE == 1) + + #if (OMV_OV7690_ENABLE == 1) + case OV7690_ID: + if (omv_csi_set_clk_frequency(OMV_OV7690_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = ov7690_init(csi); + break; + #endif // (OMV_OV7690_ENABLE == 1) + + #if (OMV_OV7725_ENABLE == 1) + case OV7725_ID: + init_ret = ov7725_init(csi); + break; + #endif // (OMV_OV7725_ENABLE == 1) + + #if (OMV_OV9650_ENABLE == 1) + case OV9650_ID: + init_ret = ov9650_init(csi); + break; + #endif // (OMV_OV9650_ENABLE == 1) + + #if (OMV_MT9V0XX_ENABLE == 1) + case MT9V0X2_ID_V_1: + case MT9V0X2_ID_V_2: + // Force old versions to the newest. + csi->chip_id = MT9V0X2_ID; + case MT9V0X2_ID: + case MT9V0X4_ID: + if (omv_csi_set_clk_frequency(OMV_MT9V0XX_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = mt9v0xx_init(csi); + break; + #endif //(OMV_MT9V0XX_ENABLE == 1) + + #if (OMV_MT9M114_ENABLE == 1) + case MT9M114_ID: + if (omv_csi_set_clk_frequency(OMV_MT9M114_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = mt9m114_init(csi); + break; + #endif //(OMV_MT9M114_ENABLE == 1) + + #if (OMV_BOSON_ENABLE == 1) + case BOSON_ID: + if (omv_csi_set_clk_frequency(OMV_BOSON_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = boson_init(csi); + break; + #endif // (OMV_BOSON_ENABLE == 1) + + #if (OMV_LEPTON_ENABLE == 1) + case LEPTON_ID: + if (dev_count == 1 && + omv_csi_set_clk_frequency(OMV_LEPTON_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = lepton_init(csi); + break; + #endif // (OMV_LEPTON_ENABLE == 1) + + #if (OMV_HM01B0_ENABLE == 1) + case HM01B0_ID: + if (omv_csi_set_clk_frequency(OMV_HM01B0_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = hm01b0_init(csi); + break; + #endif //(OMV_HM01B0_ENABLE == 1) + + #if (OMV_HM0360_ENABLE == 1) + case HM0360_ID: + if (omv_csi_set_clk_frequency(OMV_HM0360_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = hm0360_init(csi); + break; + #endif //(OMV_HM0360_ENABLE == 1) + + #if (OMV_GC2145_ENABLE == 1) + case GC2145_ID: + if (omv_csi_set_clk_frequency(OMV_GC2145_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = gc2145_init(csi); + break; + #endif //(OMV_GC2145_ENABLE == 1) + + #if (OMV_GENX320_ENABLE == 1) + case GENX320_ID_ES: + case GENX320_ID_MP: + if (omv_csi_set_clk_frequency(OMV_GENX320_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = genx320_init(csi); + break; + #endif // (OMV_GENX320_ENABLE == 1) + + #if (OMV_PAG7920_ENABLE == 1) + case PAG7920_ID: + if (omv_csi_set_clk_frequency(OMV_PAG7920_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = pag7920_init(csi); + break; + #endif // (OMV_PAG7920_ENABLE == 1) + + #if (OMV_PAG7936_ENABLE == 1) + case PAG7936_ID: + if (omv_csi_set_clk_frequency(OMV_PAG7936_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = pag7936_init(csi); + break; + #endif // (OMV_PAG7936_ENABLE == 1) + + #if (OMV_PAJ6100_ENABLE == 1) + case PAJ6100_ID: + if (omv_csi_set_clk_frequency(OMV_PAJ6100_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = paj6100_init(csi); + break; + #endif // (OMV_PAJ6100_ENABLE == 1) + + #if (OMV_PS5520_ENABLE == 1) + case PS5520_ID: + if (omv_csi_set_clk_frequency(OMV_PS5520_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = ps5520_init(csi); + break; + #endif // (OMV_PS5520_ENABLE == 1) + + #if (OMV_FROGEYE2020_ENABLE == 1) + case FROGEYE2020_ID: + if (omv_csi_set_clk_frequency(OMV_FROGEYE2020_CLK_FREQ) != 0) { + return OMV_CSI_ERROR_TIM_INIT_FAILED; + } + init_ret = frogeye2020_init(csi); + break; + #endif // (OMV_FROGEYE2020_ENABLE == 1) + + #if (OMV_SOFTCSI_ENABLE == 1) + case SOFTCSI_ID: + init_ret = softcsi_init(csi); + break; + #endif // (OMV_SOFTCSI_ENABLE == 1) + + default: + return OMV_CSI_ERROR_ISC_UNSUPPORTED; + break; + } + + if (init_ret != 0) { + // Sensor init failed. + return OMV_CSI_ERROR_ISC_INIT_FAILED; + } + + // Count aux devices. + aux_count += csi->auxiliary; + } + + // In case a single aux sensor was detected, clear + // the aux flag so it gets used as the main sensor. + if (dev_count == 1 && csi_all[0].auxiliary) { + csi_all[0].auxiliary = 0; + aux_count--; + } + + // Special case: two aux sensors detected, for example + // SoftCSI and Lepton. Use SoftCSI as the main sensor. + if (dev_count == aux_count) { + for (size_t i=0; ichip_id == SOFTCSI_ID) { + csi->auxiliary = 0; + aux_count--; + break; } } } - // A supported sensor was detected, try to initialize it. - switch (csi.chip_id) { - #if (OMV_OV2640_ENABLE == 1) - case OV2640_ID: - if (omv_csi_set_clk_frequency(OMV_OV2640_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = ov2640_init(&csi); - break; - #endif // (OMV_OV2640_ENABLE == 1) - - #if (OMV_OV5640_ENABLE == 1) - case OV5640_ID: { - int freq = OMV_OV5640_CLK_FREQ; - #if (OMV_OV5640_REV_Y_CHECK == 1) - if (HAL_GetREVID() < 0x2003) { - // Is this REV Y? - freq = OMV_OV5640_REV_Y_FREQ; - } - #endif - if (omv_csi_set_clk_frequency(freq) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = ov5640_init(&csi); - break; - } - #endif // (OMV_OV5640_ENABLE == 1) - - #if (OMV_OV7670_ENABLE == 1) - case OV7670_ID: - if (omv_csi_set_clk_frequency(OMV_OV7670_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = ov7670_init(&csi); - break; - #endif // (OMV_OV7670_ENABLE == 1) - - #if (OMV_OV7690_ENABLE == 1) - case OV7690_ID: - if (omv_csi_set_clk_frequency(OMV_OV7690_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = ov7690_init(&csi); - break; - #endif // (OMV_OV7690_ENABLE == 1) - - #if (OMV_OV7725_ENABLE == 1) - case OV7725_ID: - init_ret = ov7725_init(&csi); - break; - #endif // (OMV_OV7725_ENABLE == 1) - - #if (OMV_OV9650_ENABLE == 1) - case OV9650_ID: - init_ret = ov9650_init(&csi); - break; - #endif // (OMV_OV9650_ENABLE == 1) - - #if (OMV_MT9V0XX_ENABLE == 1) - case MT9V0X2_ID_V_1: - case MT9V0X2_ID_V_2: - // Force old versions to the newest. - csi.chip_id = MT9V0X2_ID; - case MT9V0X2_ID: - case MT9V0X4_ID: - if (omv_csi_set_clk_frequency(OMV_MT9V0XX_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = mt9v0xx_init(&csi); - break; - #endif //(OMV_MT9V0XX_ENABLE == 1) - - #if (OMV_MT9M114_ENABLE == 1) - case MT9M114_ID: - if (omv_csi_set_clk_frequency(OMV_MT9M114_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = mt9m114_init(&csi); - break; - #endif //(OMV_MT9M114_ENABLE == 1) - - #if (OMV_BOSON_ENABLE == 1) - case BOSON_ID: - if (omv_csi_set_clk_frequency(OMV_BOSON_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = boson_init(&csi); - break; - #endif // (OMV_BOSON_ENABLE == 1) - - #if (OMV_LEPTON_ENABLE == 1) - case LEPTON_ID: - if (omv_csi_set_clk_frequency(OMV_LEPTON_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = lepton_init(&csi); - break; - #endif // (OMV_LEPTON_ENABLE == 1) - - #if (OMV_HM01B0_ENABLE == 1) - case HM01B0_ID: - if (omv_csi_set_clk_frequency(OMV_HM01B0_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = hm01b0_init(&csi); - break; - #endif //(OMV_HM01B0_ENABLE == 1) - - #if (OMV_HM0360_ENABLE == 1) - case HM0360_ID: - if (omv_csi_set_clk_frequency(OMV_HM0360_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = hm0360_init(&csi); - break; - #endif //(OMV_HM0360_ENABLE == 1) - - #if (OMV_GC2145_ENABLE == 1) - case GC2145_ID: - if (omv_csi_set_clk_frequency(OMV_GC2145_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = gc2145_init(&csi); - break; - #endif //(OMV_GC2145_ENABLE == 1) - - #if (OMV_GENX320_ENABLE == 1) - case GENX320_ID_ES: - case GENX320_ID_MP: - if (omv_csi_set_clk_frequency(OMV_GENX320_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = genx320_init(&csi); - break; - #endif // (OMV_GENX320_ENABLE == 1) - - #if (OMV_PAG7920_ENABLE == 1) - case PAG7920_ID: - if (omv_csi_set_clk_frequency(OMV_PAG7920_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = pag7920_init(&csi); - break; - #endif // (OMV_PAG7920_ENABLE == 1) - - #if (OMV_PAG7936_ENABLE == 1) - case PAG7936_ID: - if (omv_csi_set_clk_frequency(OMV_PAG7936_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = pag7936_init(&csi); - break; - #endif // (OMV_PAG7936_ENABLE == 1) - - #if (OMV_PAJ6100_ENABLE == 1) - case PAJ6100_ID: - if (omv_csi_set_clk_frequency(OMV_PAJ6100_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = paj6100_init(&csi); - break; - #endif // (OMV_PAJ6100_ENABLE == 1) - - #if (OMV_PS5520_ENABLE == 1) - case PS5520_ID: - if (omv_csi_set_clk_frequency(OMV_PS5520_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = ps5520_init(&csi); - break; - #endif // (OMV_PS5520_ENABLE == 1) - - #if (OMV_FROGEYE2020_ENABLE == 1) - case FROGEYE2020_ID: - if (omv_csi_set_clk_frequency(OMV_FROGEYE2020_CLK_FREQ) != 0) { - return OMV_CSI_ERROR_TIM_INIT_FAILED; - } - init_ret = frogeye2020_init(&csi); - break; - #endif // (OMV_FROGEYE2020_ENABLE == 1) - - #if (OMV_SOFTCSI_ENABLE == 1) - case SOFTCSI_ID: - init_ret = softcsi_init(&csi); - break; - #endif // (OMV_SOFTCSI_ENABLE == 1) - - default: - return OMV_CSI_ERROR_ISC_UNSUPPORTED; - break; + // There should be exactly 1 main sensor left. Any other + // configuration is not supported. + if ((dev_count - aux_count) != 1) { + return -1; } - - if (init_ret != 0) { - // Sensor init failed. - return OMV_CSI_ERROR_ISC_INIT_FAILED; + + // Auxiliary sensors use dynamically allocated frame buffers. + for (size_t i=0; iauxiliary) { + csi->fb = NULL; + } } return 0; } -__weak int omv_csi_config(omv_csi_config_t config) { +__weak int omv_csi_config(omv_csi_t *csi, omv_csi_config_t config) { + // Call the sensor specific function. + if (csi->config != NULL && + csi->config(csi, config) != 0) { + return OMV_CSI_ERROR_CTL_FAILED; + } return 0; } -__weak int omv_csi_get_id() { - return csi.chip_id; +__weak int omv_csi_get_id(omv_csi_t *csi) { + return csi->chip_id; } __weak uint32_t omv_csi_get_clk_frequency() { @@ -607,38 +741,38 @@ __weak int omv_csi_set_clk_frequency(uint32_t frequency) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } -__weak bool omv_csi_is_detected() { - return csi.detected; +__weak bool omv_csi_is_detected(omv_csi_t *csi) { + return csi->detected; } -__weak int omv_csi_sleep(int enable) { +__weak int omv_csi_sleep(omv_csi_t *csi, int enable) { // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); + omv_csi_abort(csi, true, false); // Call the sensor specific function. - if (csi.sleep != NULL && - csi.sleep(&csi, enable) != 0) { + if (csi->sleep != NULL && + csi->sleep(csi, enable) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_shutdown(int enable) { +__weak int omv_csi_shutdown(omv_csi_t *csi, int enable) { int ret = 0; // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); + omv_csi_abort(csi, true, false); #if defined(OMV_CSI_POWER_PIN) if (enable) { - if (csi.power_pol == OMV_CSI_ACTIVE_HIGH) { + if (csi->power_pol == OMV_CSI_ACTIVE_HIGH) { omv_gpio_write(OMV_CSI_POWER_PIN, 1); } else { omv_gpio_write(OMV_CSI_POWER_PIN, 0); } } else { - if (csi.power_pol == OMV_CSI_ACTIVE_HIGH) { + if (csi->power_pol == OMV_CSI_ACTIVE_HIGH) { omv_gpio_write(OMV_CSI_POWER_PIN, 0); } else { omv_gpio_write(OMV_CSI_POWER_PIN, 1); @@ -651,141 +785,135 @@ __weak int omv_csi_shutdown(int enable) { return ret; } -__weak int omv_csi_read_reg(uint16_t reg_addr) { +__weak int omv_csi_read_reg(omv_csi_t *csi, uint16_t reg_addr) { int ret; // Check if the control is supported. - if (csi.read_reg == NULL) { + if (csi->read_reg == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if ((ret = csi.read_reg(&csi, reg_addr)) == -1) { + if ((ret = csi->read_reg(csi, reg_addr)) == -1) { return OMV_CSI_ERROR_IO_ERROR; } return ret; } -__weak int omv_csi_write_reg(uint16_t reg_addr, uint16_t reg_data) { +__weak int omv_csi_write_reg(omv_csi_t *csi, uint16_t reg_addr, uint16_t reg_data) { // Check if the control is supported. - if (csi.write_reg == NULL) { + if (csi->write_reg == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.write_reg(&csi, reg_addr, reg_data) == -1) { + if (csi->write_reg(csi, reg_addr, reg_data) == -1) { return OMV_CSI_ERROR_IO_ERROR; } return 0; } -__weak int omv_csi_set_pixformat(pixformat_t pixformat) { +__weak int omv_csi_set_pixformat(omv_csi_t *csi, pixformat_t pixformat) { // Check if the value has changed. - if (csi.pixformat == pixformat) { + if (csi->pixformat == pixformat) { return 0; } // Some sensor drivers automatically switch to BAYER to reduce the frame size if it does not fit in RAM. // If the current format is BAYER (1BPP), and the target format is color and (2BPP), and the frame does not // fit in RAM it will just be switched back again to BAYER, so we keep the current format unchanged. - uint32_t size = framebuffer_get_buffer_size(csi.fb); - if ((csi.pixformat == PIXFORMAT_BAYER) && + uint32_t size = framebuffer_get_buffer_size(csi->fb); + if ((csi->pixformat == PIXFORMAT_BAYER) && ((pixformat == PIXFORMAT_RGB565) || (pixformat == PIXFORMAT_YUV422)) && - (csi.fb->u * csi.fb->v * 2 > size) && - (csi.fb->u * csi.fb->v * 1 <= size)) { + (csi->fb->u * csi->fb->v * 2 > size) && + (csi->fb->u * csi->fb->v * 1 <= size)) { return 0; } // Cropping and transposing (and thus auto rotation) don't work in JPEG mode. - if (((pixformat == PIXFORMAT_YUV422) && (csi.transpose || csi.auto_rotation)) || - ((pixformat == PIXFORMAT_JPEG) && (omv_csi_get_cropped() || csi.transpose || csi.auto_rotation))) { + if (((pixformat == PIXFORMAT_YUV422) && (csi->transpose || csi->auto_rotation)) || + ((pixformat == PIXFORMAT_JPEG) && (omv_csi_get_cropped(csi) || csi->transpose || csi->auto_rotation))) { return OMV_CSI_ERROR_PIXFORMAT_UNSUPPORTED; } // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); - - // Flush previous frame. - framebuffer_update_jpeg_buffer(csi.fb); + omv_csi_abort(csi, true, false); // Check if the control is supported. - if (csi.set_pixformat == NULL) { + if (csi->set_pixformat == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_pixformat(&csi, pixformat) != 0) { + if (csi->set_pixformat(csi, pixformat) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } - if (!csi.disable_delays) { + if (!csi->disable_delays) { mp_hal_delay_ms(100); // wait for the camera to settle } // Set pixel format - csi.pixformat = pixformat; + csi->pixformat = pixformat; // Reset pixel format to skip the first frame. - csi.fb->pixfmt = PIXFORMAT_INVALID; + csi->fb->pixfmt = PIXFORMAT_INVALID; // Auto-adjust the number of frame buffers. - omv_csi_set_framebuffers(-1); + omv_csi_set_framebuffers(csi, -1); // Reconfigure the hardware if needed. - return omv_csi_config(OMV_CSI_CONFIG_PIXFORMAT); + return omv_csi_config(csi, OMV_CSI_CONFIG_PIXFORMAT); } -__weak int omv_csi_set_framesize(omv_csi_framesize_t framesize) { - if (csi.framesize == framesize) { +__weak int omv_csi_set_framesize(omv_csi_t *csi, omv_csi_framesize_t framesize) { + if (csi->framesize == framesize) { // No change return 0; } // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); - - // Flush previous frame. - framebuffer_update_jpeg_buffer(csi.fb); + omv_csi_abort(csi, true, false); // Call the sensor specific function - if (csi.set_framesize == NULL) { + if (csi->set_framesize == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } - if (csi.set_framesize(&csi, framesize) != 0) { + if (csi->set_framesize(csi, framesize) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } - if (!csi.disable_delays) { + if (!csi->disable_delays) { mp_hal_delay_ms(100); // wait for the camera to settle } // Set framebuffer size - csi.framesize = framesize; + csi->framesize = framesize; // Set x and y offsets. - csi.fb->x = 0; - csi.fb->y = 0; + csi->fb->x = 0; + csi->fb->y = 0; // Set width and height. - csi.fb->w = resolution[framesize][0]; - csi.fb->h = resolution[framesize][1]; + csi->fb->w = resolution[framesize][0]; + csi->fb->h = resolution[framesize][1]; // Set backup width and height. - csi.fb->u = resolution[framesize][0]; - csi.fb->v = resolution[framesize][1]; + csi->fb->u = resolution[framesize][0]; + csi->fb->v = resolution[framesize][1]; // Reset pixel format to skip the first frame. - csi.fb->pixfmt = PIXFORMAT_INVALID; + csi->fb->pixfmt = PIXFORMAT_INVALID; // Auto-adjust the number of frame buffers. - omv_csi_set_framebuffers(-1); + omv_csi_set_framebuffers(csi, -1); // Reconfigure the hardware if needed. - return omv_csi_config(OMV_CSI_CONFIG_FRAMESIZE); + return omv_csi_config(csi, OMV_CSI_CONFIG_FRAMESIZE); } -__weak int omv_csi_set_framerate(int framerate) { - if (csi.framerate == framerate) { +__weak int omv_csi_set_framerate(omv_csi_t *csi, int framerate) { + if (csi->framerate == framerate) { // No change return 0; } @@ -795,50 +923,50 @@ __weak int omv_csi_set_framerate(int framerate) { } // If the csi implements framerate control use it. - if (csi.set_framerate != NULL && - csi.set_framerate(&csi, framerate) != 0) { + if (csi->set_framerate != NULL && + csi->set_framerate(csi, framerate) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } else { // Otherwise use software framerate control. - csi.framerate = framerate; + csi->framerate = framerate; } return 0; } -__weak void omv_csi_throttle_framerate() { - if (!csi.first_line) { - csi.first_line = true; +__weak void omv_csi_throttle_framerate(omv_csi_t *csi) { + if (!csi->first_line) { + csi->first_line = true; uint32_t tick = mp_hal_ticks_ms(); - uint32_t framerate_ms = IM_DIV(1000, csi.framerate); + uint32_t framerate_ms = IM_DIV(1000, csi->framerate); - if (csi.last_frame_ms_valid && ((tick - csi.last_frame_ms) < framerate_ms)) { + if (csi->last_frame_ms_valid && ((tick - csi->last_frame_ms) < framerate_ms)) { // Drop the current frame to match the requested frame rate. Note that if the frame // is marked to be dropped, it should not be copied to SRAM/SDRAM to save CPU time. - csi.drop_frame = true; - } else if (csi.last_frame_ms_valid) { - csi.last_frame_ms += framerate_ms; + csi->drop_frame = true; + } else if (csi->last_frame_ms_valid) { + csi->last_frame_ms += framerate_ms; } else { - csi.last_frame_ms = tick; - csi.last_frame_ms_valid = true; + csi->last_frame_ms = tick; + csi->last_frame_ms_valid = true; } } } -__weak bool omv_csi_get_cropped() { - if (csi.framesize != OMV_CSI_FRAMESIZE_INVALID) { - return (csi.fb->x != 0) || - (csi.fb->y != 0) || - (csi.fb->u != resolution[csi.framesize][0]) || - (csi.fb->v != resolution[csi.framesize][1]); +__weak bool omv_csi_get_cropped(omv_csi_t *csi) { + if (csi->framesize != OMV_CSI_FRAMESIZE_INVALID) { + return (csi->fb->x != 0) || + (csi->fb->y != 0) || + (csi->fb->u != resolution[csi->framesize][0]) || + (csi->fb->v != resolution[csi->framesize][1]); } return false; } -__weak uint32_t omv_csi_get_src_bpp() { - if (csi.raw_output) { +__weak uint32_t omv_csi_get_src_bpp(omv_csi_t *csi) { + if (csi->raw_output) { return 1; } - switch (csi.pixformat) { + switch (csi->pixformat) { case PIXFORMAT_BAYER: case PIXFORMAT_JPEG: return 1; @@ -846,14 +974,14 @@ __weak uint32_t omv_csi_get_src_bpp() { case PIXFORMAT_YUV422: return 2; case PIXFORMAT_GRAYSCALE: - return csi.mono_bpp; + return csi->mono_bpp; default: return 0; } } -__weak uint32_t omv_csi_get_dst_bpp() { - switch (csi.pixformat) { +__weak uint32_t omv_csi_get_dst_bpp(omv_csi_t *csi) { + switch (csi->pixformat) { case PIXFORMAT_GRAYSCALE: case PIXFORMAT_BAYER: return 1; @@ -865,493 +993,487 @@ __weak uint32_t omv_csi_get_dst_bpp() { } } -__weak int omv_csi_set_windowing(int x, int y, int w, int h) { +__weak int omv_csi_set_windowing(omv_csi_t *csi, int x, int y, int w, int h) { // Check if the value has changed. - if ((csi.fb->x == x) && (csi.fb->y == y) && - (csi.fb->u == w) && (csi.fb->v == h)) { + if ((csi->fb->x == x) && (csi->fb->y == y) && + (csi->fb->u == w) && (csi->fb->v == h)) { return 0; } - if (csi.pixformat == PIXFORMAT_JPEG) { + if (csi->pixformat == PIXFORMAT_JPEG) { return OMV_CSI_ERROR_PIXFORMAT_UNSUPPORTED; } // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); - - // Flush previous frame. - framebuffer_update_jpeg_buffer(csi.fb); + omv_csi_abort(csi, true, false); // Set x and y offsets. - csi.fb->x = x; - csi.fb->y = y; + csi->fb->x = x; + csi->fb->y = y; // Set width and height. - csi.fb->w = w; - csi.fb->h = h; + csi->fb->w = w; + csi->fb->h = h; // Set backup width and height. - csi.fb->u = w; - csi.fb->v = h; + csi->fb->u = w; + csi->fb->v = h; // Reset pixel format to skip the first frame. - csi.fb->pixfmt = PIXFORMAT_INVALID; + csi->fb->pixfmt = PIXFORMAT_INVALID; // Auto-adjust the number of frame buffers. - omv_csi_set_framebuffers(-1); + omv_csi_set_framebuffers(csi, -1); // Reconfigure the hardware if needed. - return omv_csi_config(OMV_CSI_CONFIG_WINDOWING); + return omv_csi_config(csi, OMV_CSI_CONFIG_WINDOWING); } -__weak int omv_csi_set_contrast(int level) { +__weak int omv_csi_set_contrast(omv_csi_t *csi, int level) { // Check if the control is supported. - if (csi.set_contrast == NULL) { + if (csi->set_contrast == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_contrast(&csi, level) != 0) { + if (csi->set_contrast(csi, level) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_set_brightness(int level) { +__weak int omv_csi_set_brightness(omv_csi_t *csi, int level) { // Check if the control is supported. - if (csi.set_brightness == NULL) { + if (csi->set_brightness == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_brightness(&csi, level) != 0) { + if (csi->set_brightness(csi, level) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_set_saturation(int level) { +__weak int omv_csi_set_saturation(omv_csi_t *csi, int level) { // Check if the control is supported. - if (csi.set_saturation == NULL) { + if (csi->set_saturation == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_saturation(&csi, level) != 0) { + if (csi->set_saturation(csi, level) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_set_gainceiling(omv_csi_gainceiling_t gainceiling) { +__weak int omv_csi_set_gainceiling(omv_csi_t *csi, omv_csi_gainceiling_t gainceiling) { // Check if the value has changed. - if (csi.gainceiling == gainceiling) { + if (csi->gainceiling == gainceiling) { return 0; } // Check if the control is supported. - if (csi.set_gainceiling == NULL) { + if (csi->set_gainceiling == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_gainceiling(&csi, gainceiling) != 0) { + if (csi->set_gainceiling(csi, gainceiling) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } // Set the new control value. - csi.gainceiling = gainceiling; + csi->gainceiling = gainceiling; return 0; } -__weak int omv_csi_set_quality(int qs) { +__weak int omv_csi_set_quality(omv_csi_t *csi, int qs) { // Check if the control is supported. - if (csi.set_quality == NULL) { + if (csi->set_quality == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_quality(&csi, qs) != 0) { + if (csi->set_quality(csi, qs) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_set_colorbar(int enable) { +__weak int omv_csi_set_colorbar(omv_csi_t *csi, int enable) { // Check if the control is supported. - if (csi.set_colorbar == NULL) { + if (csi->set_colorbar == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_colorbar(&csi, enable) != 0) { + if (csi->set_colorbar(csi, enable) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_set_auto_gain(int enable, float gain_db, float gain_db_ceiling) { +__weak int omv_csi_set_auto_gain(omv_csi_t *csi, int enable, float gain_db, float gain_db_ceiling) { // Check if the control is supported. - if (csi.set_auto_gain == NULL) { + if (csi->set_auto_gain == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_auto_gain(&csi, enable, gain_db, gain_db_ceiling) != 0) { + if (csi->set_auto_gain(csi, enable, gain_db, gain_db_ceiling) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_get_gain_db(float *gain_db) { +__weak int omv_csi_get_gain_db(omv_csi_t *csi, float *gain_db) { // Check if the control is supported. - if (csi.get_gain_db == NULL) { + if (csi->get_gain_db == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.get_gain_db(&csi, gain_db) != 0) { + if (csi->get_gain_db(csi, gain_db) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_set_auto_exposure(int enable, int exposure_us) { +__weak int omv_csi_set_auto_exposure(omv_csi_t *csi, int enable, int exposure_us) { // Check if the control is supported. - if (csi.set_auto_exposure == NULL) { + if (csi->set_auto_exposure == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_auto_exposure(&csi, enable, exposure_us) != 0) { + if (csi->set_auto_exposure(csi, enable, exposure_us) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_get_exposure_us(int *exposure_us) { +__weak int omv_csi_get_exposure_us(omv_csi_t *csi, int *exposure_us) { // Check if the control is supported. - if (csi.get_exposure_us == NULL) { + if (csi->get_exposure_us == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.get_exposure_us(&csi, exposure_us) != 0) { + if (csi->get_exposure_us(csi, exposure_us) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_set_auto_whitebal(int enable, float r_gain_db, float g_gain_db, float b_gain_db) { +__weak int omv_csi_set_auto_whitebal(omv_csi_t *csi, int enable, float r_gain_db, float g_gain_db, float b_gain_db) { // Check if the control is supported. - if (csi.set_auto_whitebal == NULL) { + if (csi->set_auto_whitebal == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_auto_whitebal(&csi, enable, r_gain_db, g_gain_db, b_gain_db) != 0) { + if (csi->set_auto_whitebal(csi, enable, r_gain_db, g_gain_db, b_gain_db) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_get_rgb_gain_db(float *r_gain_db, float *g_gain_db, float *b_gain_db) { +__weak int omv_csi_get_rgb_gain_db(omv_csi_t *csi, float *r_gain_db, float *g_gain_db, float *b_gain_db) { // Check if the control is supported. - if (csi.get_rgb_gain_db == NULL) { + if (csi->get_rgb_gain_db == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.get_rgb_gain_db(&csi, r_gain_db, g_gain_db, b_gain_db) != 0) { + if (csi->get_rgb_gain_db(csi, r_gain_db, g_gain_db, b_gain_db) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_set_auto_blc(int enable, int *regs) { +__weak int omv_csi_set_auto_blc(omv_csi_t *csi, int enable, int *regs) { // Check if the control is supported. - if (csi.set_auto_blc == NULL) { + if (csi->set_auto_blc == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_auto_blc(&csi, enable, regs) != 0) { + if (csi->set_auto_blc(csi, enable, regs) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_get_blc_regs(int *regs) { +__weak int omv_csi_get_blc_regs(omv_csi_t *csi, int *regs) { // Check if the control is supported. - if (csi.get_blc_regs == NULL) { + if (csi->get_blc_regs == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.get_blc_regs(&csi, regs) != 0) { + if (csi->get_blc_regs(csi, regs) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_set_hmirror(int enable) { +__weak int omv_csi_set_hmirror(omv_csi_t *csi, int enable) { // Check if the value has changed. - if (csi.hmirror == ((bool) enable)) { + if (csi->hmirror == ((bool) enable)) { return 0; } // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); + omv_csi_abort(csi, true, false); // Check if the control is supported. - if (csi.set_hmirror == NULL) { + if (csi->set_hmirror == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_hmirror(&csi, enable) != 0) { + if (csi->set_hmirror(csi, enable) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } // Set the new control value. - csi.hmirror = enable; + csi->hmirror = enable; // Wait for the camera to settle - if (!csi.disable_delays) { + if (!csi->disable_delays) { mp_hal_delay_ms(100); } return 0; } -__weak bool omv_csi_get_hmirror() { - return csi.hmirror; +__weak bool omv_csi_get_hmirror(omv_csi_t *csi) { + return csi->hmirror; } -__weak int omv_csi_set_vflip(int enable) { +__weak int omv_csi_set_vflip(omv_csi_t *csi, int enable) { // Check if the value has changed. - if (csi.vflip == ((bool) enable)) { + if (csi->vflip == ((bool) enable)) { return 0; } // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); + omv_csi_abort(csi, true, false); // Check if the control is supported. - if (csi.set_vflip == NULL) { + if (csi->set_vflip == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_vflip(&csi, enable) != 0) { + if (csi->set_vflip(csi, enable) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } // Set the new control value. - csi.vflip = enable; + csi->vflip = enable; // Wait for the camera to settle - if (!csi.disable_delays) { + if (!csi->disable_delays) { mp_hal_delay_ms(100); } return 0; } -__weak bool omv_csi_get_vflip() { - return csi.vflip; +__weak bool omv_csi_get_vflip(omv_csi_t *csi) { + return csi->vflip; } -__weak int omv_csi_set_transpose(bool enable) { +__weak int omv_csi_set_transpose(omv_csi_t *csi, bool enable) { // Check if the value has changed. - if (csi.transpose == enable) { + if (csi->transpose == enable) { return 0; } // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); + omv_csi_abort(csi, true, false); - if ((csi.pixformat == PIXFORMAT_YUV422) || (csi.pixformat == PIXFORMAT_JPEG)) { + if ((csi->pixformat == PIXFORMAT_YUV422) || (csi->pixformat == PIXFORMAT_JPEG)) { return OMV_CSI_ERROR_PIXFORMAT_UNSUPPORTED; } // Set the new control value. - csi.transpose = enable; + csi->transpose = enable; return 0; } -__weak bool omv_csi_get_transpose() { - return csi.transpose; +__weak bool omv_csi_get_transpose(omv_csi_t *csi) { + return csi->transpose; } -__weak int omv_csi_set_auto_rotation(bool enable) { +__weak int omv_csi_set_auto_rotation(omv_csi_t *csi, bool enable) { // Check if the value has changed. - if (csi.auto_rotation == enable) { + if (csi->auto_rotation == enable) { return 0; } // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); + omv_csi_abort(csi, true, false); // Operation not supported on JPEG images. - if ((csi.pixformat == PIXFORMAT_YUV422) || (csi.pixformat == PIXFORMAT_JPEG)) { + if ((csi->pixformat == PIXFORMAT_YUV422) || (csi->pixformat == PIXFORMAT_JPEG)) { return OMV_CSI_ERROR_PIXFORMAT_UNSUPPORTED; } // Set the new control value. - csi.auto_rotation = enable; + csi->auto_rotation = enable; return 0; } -__weak bool omv_csi_get_auto_rotation() { - return csi.auto_rotation; +__weak bool omv_csi_get_auto_rotation(omv_csi_t *csi) { + return csi->auto_rotation; } -__weak int omv_csi_set_framebuffers(int count) { +__weak int omv_csi_set_framebuffers(omv_csi_t *csi, int count) { // Disable any ongoing frame capture. - omv_csi_abort(&csi, true, false); + omv_csi_abort(csi, true, false); - // Flush previous frame. - framebuffer_update_jpeg_buffer(csi.fb); - - if (csi.pixformat == PIXFORMAT_INVALID) { + if (csi->pixformat == PIXFORMAT_INVALID) { return OMV_CSI_ERROR_INVALID_PIXFORMAT; } - if (csi.framesize == OMV_CSI_FRAMESIZE_INVALID) { + if (csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { return OMV_CSI_ERROR_INVALID_FRAMESIZE; } #if OMV_CSI_HW_CROP_ENABLE // If hardware cropping is supported, use window size. - csi.fb->frame_size = csi.fb->u * csi.fb->v * 2; + csi->fb->frame_size = csi->fb->u * csi->fb->v * 2; #else // Otherwise, use the real frame size. - csi.fb->frame_size = resolution[csi.framesize][0] * resolution[csi.framesize][1] * 2; + csi->fb->frame_size = resolution[csi->framesize][0] * resolution[csi->framesize][1] * 2; #endif - return framebuffer_set_buffers(csi.fb, count); + return framebuffer_set_buffers(csi->fb, count); } -__weak int omv_csi_set_special_effect(omv_csi_sde_t sde) { +__weak int omv_csi_set_special_effect(omv_csi_t *csi, omv_csi_sde_t sde) { // Check if the value has changed. - if (csi.sde == sde) { + if (csi->sde == sde) { return 0; } // Check if the control is supported. - if (csi.set_special_effect == NULL) { + if (csi->set_special_effect == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_special_effect(&csi, sde) != 0) { + if (csi->set_special_effect(csi, sde) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } // Set the new control value. - csi.sde = sde; + csi->sde = sde; return 0; } -__weak int omv_csi_set_lens_correction(int enable, int radi, int coef) { +__weak int omv_csi_set_lens_correction(omv_csi_t *csi, int enable, int radi, int coef) { // Check if the control is supported. - if (csi.set_lens_correction == NULL) { + if (csi->set_lens_correction == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } // Call the sensor specific function. - if (csi.set_lens_correction(&csi, enable, radi, coef) != 0) { + if (csi->set_lens_correction(csi, enable, radi, coef) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } return 0; } -__weak int omv_csi_ioctl(int request, ... /* arg */) { +__weak int omv_csi_ioctl(omv_csi_t *csi, int request, ... /* arg */) { // Disable any ongoing frame capture. if (request & OMV_CSI_IOCTL_FLAGS_ABORT) { - omv_csi_abort(&csi, true, false); + omv_csi_abort(csi, true, false); } // Check if the control is supported. - if (csi.ioctl == NULL) { + if (csi->ioctl == NULL) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } va_list ap; va_start(ap, request); // Call the sensor specific function. - int ret = csi.ioctl(&csi, request, ap); + int ret = csi->ioctl(csi, request, ap); va_end(ap); return ((ret != 0) ? OMV_CSI_ERROR_CTL_FAILED : 0); } -__weak int omv_csi_set_vsync_callback(vsync_cb_t vsync_cb) { - csi.vsync_callback = vsync_cb; +__weak int omv_csi_set_vsync_callback(omv_csi_t *csi, omv_csi_cb_t cb) { + csi->vsync_cb = cb; return 0; } -__weak int omv_csi_set_frame_callback(frame_cb_t vsync_cb) { - csi.frame_callback = vsync_cb; +__weak int omv_csi_set_frame_callback(omv_csi_t *csi, omv_csi_cb_t cb) { + csi->frame_cb = cb; return 0; } -__weak int omv_csi_set_color_palette(const uint16_t *color_palette) { - csi.color_palette = color_palette; +__weak int omv_csi_set_color_palette(omv_csi_t *csi, const uint16_t *color_palette) { + csi->color_palette = color_palette; return 0; } -__weak const uint16_t *omv_csi_get_color_palette() { - return csi.color_palette; +__weak const uint16_t *omv_csi_get_color_palette(omv_csi_t *csi) { + return csi->color_palette; } -__weak int omv_csi_check_framebuffer_size() { - uint32_t bpp = omv_csi_get_dst_bpp(); - uint32_t size = framebuffer_get_buffer_size(csi.fb); - return (((csi.fb->u * csi.fb->v * bpp) <= size) ? 0 : -1); +__weak int omv_csi_check_framebuffer_size(omv_csi_t *csi) { + uint32_t bpp = omv_csi_get_dst_bpp(csi); + uint32_t size = framebuffer_get_buffer_size(csi->fb); + return (((csi->fb->u * csi->fb->v * bpp) <= size) ? 0 : -1); } -__weak int omv_csi_auto_crop_framebuffer() { - uint32_t bpp = omv_csi_get_dst_bpp(); - uint32_t size = framebuffer_get_buffer_size(csi.fb); +__weak int omv_csi_auto_crop_framebuffer(omv_csi_t *csi) { + uint32_t bpp = omv_csi_get_dst_bpp(csi); + uint32_t size = framebuffer_get_buffer_size(csi->fb); // If the pixformat is NULL/JPEG there we can't do anything to check if it fits before hand. if (!bpp) { return 0; } - // csi.fb fits, we are done. - if ((csi.fb->u * csi.fb->v * bpp) <= size) { + // csi->fb fits, we are done. + if ((csi->fb->u * csi->fb->v * bpp) <= size) { return 0; } - if ((csi.pixformat == PIXFORMAT_RGB565) || (csi.pixformat == PIXFORMAT_YUV422)) { + if ((csi->pixformat == PIXFORMAT_RGB565) || (csi->pixformat == PIXFORMAT_YUV422)) { // Switch to bayer for the quick 2x savings. - omv_csi_set_pixformat(PIXFORMAT_BAYER); + omv_csi_set_pixformat(csi, PIXFORMAT_BAYER); bpp = 1; - // csi.fb fits, we are done (bpp is 1). - if ((csi.fb->u * csi.fb->v) <= size) { + // csi->fb fits, we are done (bpp is 1). + if ((csi->fb->u * csi->fb->v) <= size) { return 0; } } - int window_w = csi.fb->u; - int window_h = csi.fb->v; + int window_w = csi->fb->u; + int window_h = csi->fb->v; // We need to shrink the frame buffer. We can do this by cropping. So, we will subtract columns // and rows from the frame buffer until it fits within the frame buffer. @@ -1392,76 +1514,76 @@ __weak int omv_csi_auto_crop_framebuffer() { } // Crop the frame buffer while keeping the aspect ratio and keeping the width/height even. - while (((csi.fb->u * csi.fb->v * bpp) > size) || (csi.fb->u % 2) || (csi.fb->v % 2)) { - csi.fb->u -= u_sub; - csi.fb->v -= v_sub; + while (((csi->fb->u * csi->fb->v * bpp) > size) || (csi->fb->u % 2) || (csi->fb->v % 2)) { + csi->fb->u -= u_sub; + csi->fb->v -= v_sub; } // Center the new window using the previous offset and keep the offset even. - csi.fb->x += (window_w - csi.fb->u) / 2; - csi.fb->y += (window_h - csi.fb->v) / 2; + csi->fb->x += (window_w - csi->fb->u) / 2; + csi->fb->y += (window_h - csi->fb->v) / 2; - if (csi.fb->x % 2) { - csi.fb->x -= 1; + if (csi->fb->x % 2) { + csi->fb->x -= 1; } - if (csi.fb->y % 2) { - csi.fb->y -= 1; + if (csi->fb->y % 2) { + csi->fb->y -= 1; } // Auto-adjust the number of frame buffers. - omv_csi_set_framebuffers(-1); + omv_csi_set_framebuffers(csi, -1); return 0; } #define copy_transposed_line(dstp, srcp) \ - for (int i = csi.fb->u, h = csi.fb->v; i; i--) { \ + for (int i = csi->fb->u, h = csi->fb->v; i; i--) { \ *dstp = *srcp++; \ dstp += h; \ } #define copy_transposed_line_rev16(dstp, srcp) \ - for (int i = csi.fb->u, h = csi.fb->v; i; i--) { \ + for (int i = csi->fb->u, h = csi->fb->v; i; i--) { \ *dstp = __REV16(*srcp++); \ dstp += h; \ } -__weak int omv_csi_copy_line(void *dma, uint8_t *src, uint8_t *dst) { +__weak int omv_csi_copy_line(omv_csi_t *csi, void *dma, uint8_t *src, uint8_t *dst) { uint16_t *src16 = (uint16_t *) src; uint16_t *dst16 = (uint16_t *) dst; #if OMV_CSI_DMA_MEMCPY_ENABLE - extern int omv_csi_dma_memcpy(void *dma, void *dst, void *src, int bpp, bool transposed); + extern int omv_csi_dma_memcpy(omv_csi_t *csi, void *dma, void *dst, void *src, int bpp, bool transposed); #endif - switch (csi.pixformat) { + switch (csi->pixformat) { case PIXFORMAT_BAYER: #if OMV_CSI_DMA_MEMCPY_ENABLE - if (!omv_csi_dma_memcpy(dma, dst, src, sizeof(uint8_t), csi.transpose)) { + if (!omv_csi_dma_memcpy(csi, dma, dst, src, sizeof(uint8_t), csi->transpose)) { break; } #endif - if (!csi.transpose) { - unaligned_memcpy(dst, src, csi.fb->u); + if (!csi->transpose) { + unaligned_memcpy(dst, src, csi->fb->u); } else { copy_transposed_line(dst, src); } break; case PIXFORMAT_GRAYSCALE: #if OMV_CSI_DMA_MEMCPY_ENABLE - if (!omv_csi_dma_memcpy(dma, dst, src, sizeof(uint8_t), csi.transpose)) { + if (!omv_csi_dma_memcpy(csi, dma, dst, src, sizeof(uint8_t), csi->transpose)) { break; } #endif - if (csi.mono_bpp == 1) { + if (csi->mono_bpp == 1) { // 1BPP GRAYSCALE. - if (!csi.transpose) { - unaligned_memcpy(dst, src, csi.fb->u); + if (!csi->transpose) { + unaligned_memcpy(dst, src, csi->fb->u); } else { copy_transposed_line(dst, src); } } else { // Extract Y channel from YUV. - if (!csi.transpose) { - unaligned_2_to_1_memcpy(dst, src16, csi.fb->u); + if (!csi->transpose) { + unaligned_2_to_1_memcpy(dst, src16, csi->fb->u); } else { copy_transposed_line(dst, src16); } @@ -1470,23 +1592,23 @@ __weak int omv_csi_copy_line(void *dma, uint8_t *src, uint8_t *dst) { case PIXFORMAT_RGB565: case PIXFORMAT_YUV422: #if OMV_CSI_DMA_MEMCPY_ENABLE - if (!omv_csi_dma_memcpy(dma, dst16, src16, sizeof(uint16_t), csi.transpose)) { + if (!omv_csi_dma_memcpy(csi, dma, dst16, src16, sizeof(uint16_t), csi->transpose)) { break; } #endif if (0) { #if !OMV_CSI_HW_SWAP_ENABLE - } else if ((csi.pixformat == PIXFORMAT_RGB565 && csi.rgb_swap) || - (csi.pixformat == PIXFORMAT_YUV422 && csi.yuv_swap)) { - if (!csi.transpose) { - unaligned_memcpy_rev16(dst16, src16, csi.fb->u); + } else if ((csi->pixformat == PIXFORMAT_RGB565 && csi->rgb_swap) || + (csi->pixformat == PIXFORMAT_YUV422 && csi->yuv_swap)) { + if (!csi->transpose) { + unaligned_memcpy_rev16(dst16, src16, csi->fb->u); } else { copy_transposed_line_rev16(dst16, src16); } #endif } else { - if (!csi.transpose) { - unaligned_memcpy(dst16, src16, csi.fb->u * sizeof(uint16_t)); + if (!csi->transpose) { + unaligned_memcpy(dst16, src16, csi->fb->u * sizeof(uint16_t)); } else { copy_transposed_line(dst16, src16); } @@ -1499,7 +1621,14 @@ __weak int omv_csi_copy_line(void *dma, uint8_t *src, uint8_t *dst) { } __weak int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { - return -1; + int ret = OMV_CSI_ERROR_CTL_UNSUPPORTED; + + // Call the sensor specific function. + if (csi->snapshot) { + ret = csi->snapshot(csi, image, flags); + } + + return ret; } const char *omv_csi_strerror(int error) { @@ -1525,6 +1654,7 @@ const char *omv_csi_strerror(int error) { "Frame buffer error.", "Frame buffer overflow, try reducing the frame size.", "JPEG frame buffer overflow.", + "The requested operation would block.", }; // Sensor errors are negative. diff --git a/common/omv_csi.h b/common/omv_csi.h index 141d9c5c3..e78a9ec71 100644 --- a/common/omv_csi.h +++ b/common/omv_csi.h @@ -50,7 +50,7 @@ #define FROGEYE2020_SLV_ADDR (0x6E) #define PAG7920_SLV_ADDR (0x80) #define PAG7936_SLV_ADDR (0x80) -#define SOFTCSI_SLV_ADDR (0x7f) +#define PAG7936_SLV_ADDR_ALT (0x2A) #define PS5520_SLV_ADDR (0x90) // Chip ID Registers @@ -104,6 +104,10 @@ #define OMV_CSI_CLK_SOURCE_TIM (1U) #define OMV_CSI_CLK_SOURCE_OSC (2U) +#ifndef OMV_CSI_MAX_DEVICES +#define OMV_CSI_MAX_DEVICES (1) +#endif + typedef enum { OMV_CSI_ACTIVE_LOW = 0, OMV_CSI_ACTIVE_HIGH = 1 @@ -189,6 +193,11 @@ typedef enum { OMV_CSI_IOCTL_FLAGS_ABORT = (1 << 8), } omv_csi_ioctl_flags_t; +typedef enum { + OMV_CSI_CAPTURE_FLAGS_UPDATE = (1 << 0), + OMV_CSI_CAPTURE_FLAGS_NBLOCK = (1 << 1), +} omv_csi_capture_flags_t; + typedef enum { OMV_CSI_IOCTL_SET_READOUT_WINDOW = 0x00 | OMV_CSI_IOCTL_FLAGS_ABORT, OMV_CSI_IOCTL_GET_READOUT_WINDOW = 0x01, @@ -250,6 +259,7 @@ typedef enum { OMV_CSI_ERROR_FRAMEBUFFER_ERROR = -18, OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW = -19, OMV_CSI_ERROR_JPEG_OVERFLOW = -20, + OMV_CSI_ERROR_WOULD_BLOCK = -21, } omv_csi_error_t; #if (OMV_GENX320_ENABLE == 1) @@ -270,10 +280,13 @@ typedef enum { } omv_csi_genx320_bias_t; #endif -typedef void (*vsync_cb_t) (uint32_t vsync); -typedef void (*frame_cb_t) (); typedef struct _omv_csi omv_csi_t; +typedef struct _omv_csi_callback_t { + void (*fun) (void *); + void *arg; +} omv_csi_cb_t; + typedef struct _omv_csi { uint32_t chip_id; // Sensor ID 32 bits. uint8_t slv_addr; // Sensor I2C slave address. @@ -296,14 +309,15 @@ typedef struct _omv_csi { uint32_t cfa_format : 3; // CFA format/pattern. uint32_t mipi_if : 1; // CSI-2 interface. uint32_t mipi_brate : 12; // CSI-2 interface bitrate. + uint32_t auxiliary : 1; // Indicates that the sensor can be used in dual-CSI config. }; const uint16_t *color_palette; // Color palette used for color lookup. bool disable_delays; // Set to true to disable all sensor settling time delays. bool disable_full_flush; // Turn off default frame buffer flush policy when full. - vsync_cb_t vsync_callback; // VSYNC callback. - frame_cb_t frame_callback; // Frame callback. + omv_csi_cb_t vsync_cb; // VSYNC callback + omv_csi_cb_t frame_cb; // Frame callback // Sensor state omv_csi_sde_t sde; // Special digital effects @@ -322,19 +336,22 @@ typedef struct _omv_csi { bool detected; // Set to true when the sensor is initialized. omv_i2c_t *i2c; // SCCB/I2C bus. - framebuffer_t *fb; // Frame buffer pointer #ifdef OMV_CSI_PORT_BITS // Additional port-specific members like device base pointer, - // dma handles, more I/Os etc... are included directly here, - // so that they can be accessible from this struct. + // DMA handles, and additional I/Os, are included directly here. OMV_CSI_PORT_BITS #endif + // Can be used by port or image sensor drivers to store private + // data, internal state, or additional operations. + void *priv; + // Sensor function pointers int (*reset) (omv_csi_t *csi); int (*sleep) (omv_csi_t *csi, int enable); + int (*match) (omv_csi_t *csi, size_t id); int (*read_reg) (omv_csi_t *csi, uint16_t reg_addr); int (*write_reg) (omv_csi_t *csi, uint16_t reg_addr, uint16_t reg_data); int (*set_pixformat) (omv_csi_t *csi, pixformat_t pixformat); @@ -359,36 +376,44 @@ typedef struct _omv_csi { int (*set_special_effect) (omv_csi_t *csi, omv_csi_sde_t sde); int (*set_lens_correction) (omv_csi_t *csi, int enable, int radi, int coef); int (*ioctl) (omv_csi_t *csi, int request, va_list ap); + int (*config) (omv_csi_t *csi, omv_csi_config_t config); + int (*abort) (omv_csi_t *csi, bool fifo_flush, bool in_irq); int (*snapshot) (omv_csi_t *csi, image_t *image, uint32_t flags); } omv_csi_t; -extern omv_csi_t csi; -extern omv_i2c_t csi_i2c; +// CSI array +extern omv_csi_t csi_all[OMV_CSI_MAX_DEVICES]; // Resolution table extern uint16_t resolution[][2]; -// Initialize the sensor state. +// Resets the sensor state on soft-reboots. void omv_csi_init0(); -// Initialize the sensor and probe the image sensor. +// Initializes CSI struct with default ops. int omv_csi_init(); +// Return CSI instance. +// If id == -1, return the main csi, otherwise look up csi by chip-id. +omv_csi_t *omv_csi_get(int id); + // Detect and initialize the image sensor. -int omv_csi_probe_init(uint32_t bus_id, uint32_t bus_speed); +int omv_csi_probe(omv_i2c_t *i2c); + +// Match CSI chip ID with provided ID. +int omv_csi_match(omv_csi_t *csi, size_t id); // This function is called after a setting that may require reconfiguring // the hardware changes, such as window size, frame size, or pixel format. -int omv_csi_config(omv_csi_config_t config); +int omv_csi_config(omv_csi_t *csi, omv_csi_config_t config); // Abort frame capture and disable IRQs, DMA etc.. int omv_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq); // Reset the sensor to its default state. -int omv_csi_reset(); +int omv_csi_reset(omv_csi_t *csi, bool hard); -// Return csi PID. -int omv_csi_get_id(); +int omv_csi_get_id(omv_csi_t *csi); // Returns the xclk freq in hz. uint32_t omv_csi_get_clk_frequency(); @@ -397,144 +422,144 @@ uint32_t omv_csi_get_clk_frequency(); int omv_csi_set_clk_frequency(uint32_t frequency); // Return true if the sensor was detected and initialized. -bool omv_csi_is_detected(); +bool omv_csi_is_detected(omv_csi_t *csi); // Sleep mode. -int omv_csi_sleep(int enable); +int omv_csi_sleep(omv_csi_t *csi, int enable); // Shutdown mode. -int omv_csi_shutdown(int enable); +int omv_csi_shutdown(omv_csi_t *csi, int enable); // Read a csi register. -int omv_csi_read_reg(uint16_t reg_addr); +int omv_csi_read_reg(omv_csi_t *csi, uint16_t reg_addr); // Write a csi register. -int omv_csi_write_reg(uint16_t reg_addr, uint16_t reg_data); +int omv_csi_write_reg(omv_csi_t *csi, uint16_t reg_addr, uint16_t reg_data); // Set the sensor pixel format. -int omv_csi_set_pixformat(pixformat_t pixformat); +int omv_csi_set_pixformat(omv_csi_t *csi, pixformat_t pixformat); // Set the sensor frame size. -int omv_csi_set_framesize(omv_csi_framesize_t framesize); +int omv_csi_set_framesize(omv_csi_t *csi, omv_csi_framesize_t framesize); // Set the sensor frame rate. -int omv_csi_set_framerate(int framerate); +int omv_csi_set_framerate(omv_csi_t *csi, int framerate); // Return the number of bytes per pixel to read from the image sensor. -uint32_t omv_csi_get_src_bpp(); +uint32_t omv_csi_get_src_bpp(omv_csi_t *csi); // Return the number of bytes per pixel to write to memory. -uint32_t omv_csi_get_dst_bpp(); +uint32_t omv_csi_get_dst_bpp(omv_csi_t *csi); // Returns true if a crop is being applied to the frame buffer. -bool omv_csi_get_cropped(); +bool omv_csi_get_cropped(omv_csi_t *csi); // Set window size. -int omv_csi_set_windowing(int x, int y, int w, int h); +int omv_csi_set_windowing(omv_csi_t *csi, int x, int y, int w, int h); // Set the sensor contrast level (from -3 to +3). -int omv_csi_set_contrast(int level); +int omv_csi_set_contrast(omv_csi_t *csi, int level); // Set the sensor brightness level (from -3 to +3). -int omv_csi_set_brightness(int level); +int omv_csi_set_brightness(omv_csi_t *csi, int level); // Set the sensor saturation level (from -3 to +3). -int omv_csi_set_saturation(int level); +int omv_csi_set_saturation(omv_csi_t *csi, int level); // Set the sensor AGC gain ceiling. // Note: This function has no effect when AGC (Automatic Gain Control) is disabled. -int omv_csi_set_gainceiling(omv_csi_gainceiling_t gainceiling); +int omv_csi_set_gainceiling(omv_csi_t *csi, omv_csi_gainceiling_t gainceiling); // Set the quantization scale factor, controls JPEG quality (quality 0-255). -int omv_csi_set_quality(int qs); +int omv_csi_set_quality(omv_csi_t *csi, int qs); // Enable/disable the colorbar mode. -int omv_csi_set_colorbar(int enable); +int omv_csi_set_colorbar(omv_csi_t *csi, int enable); // Enable auto gain or set value manually. -int omv_csi_set_auto_gain(int enable, float gain_db, float gain_db_ceiling); +int omv_csi_set_auto_gain(omv_csi_t *csi, int enable, float gain_db, float gain_db_ceiling); // Get the gain value. -int omv_csi_get_gain_db(float *gain_db); +int omv_csi_get_gain_db(omv_csi_t *csi, float *gain_db); // Enable auto exposure or set value manually. -int omv_csi_set_auto_exposure(int enable, int exposure_us); +int omv_csi_set_auto_exposure(omv_csi_t *csi, int enable, int exposure_us); // Get the exposure value. -int omv_csi_get_exposure_us(int *get_exposure_us); +int omv_csi_get_exposure_us(omv_csi_t *csi, int *get_exposure_us); // Enable auto white balance or set value manually. -int omv_csi_set_auto_whitebal(int enable, float r_gain_db, float g_gain_db, float b_gain_db); +int omv_csi_set_auto_whitebal(omv_csi_t *csi, int enable, float r_gain_db, float g_gain_db, float b_gain_db); // Get the rgb gain values. -int omv_csi_get_rgb_gain_db(float *r_gain_db, float *g_gain_db, float *b_gain_db); +int omv_csi_get_rgb_gain_db(omv_csi_t *csi, float *r_gain_db, float *g_gain_db, float *b_gain_db); // Enable auto blc (black level calibration) or set from previous calibration. -int omv_csi_set_auto_blc(int enable, int *regs); +int omv_csi_set_auto_blc(omv_csi_t *csi, int enable, int *regs); // Get black level valibration register values. -int omv_csi_get_blc_regs(int *regs); +int omv_csi_get_blc_regs(omv_csi_t *csi, int *regs); // Enable/disable the hmirror mode. -int omv_csi_set_hmirror(int enable); +int omv_csi_set_hmirror(omv_csi_t *csi, int enable); // Get hmirror status. -bool omv_csi_get_hmirror(); +bool omv_csi_get_hmirror(omv_csi_t *csi); // Enable/disable the vflip mode. -int omv_csi_set_vflip(int enable); +int omv_csi_set_vflip(omv_csi_t *csi, int enable); // Get vflip status. -bool omv_csi_get_vflip(); +bool omv_csi_get_vflip(omv_csi_t *csi); // Enable/disable the transpose mode. -int omv_csi_set_transpose(bool enable); +int omv_csi_set_transpose(omv_csi_t *csi, bool enable); // Get transpose mode state. -bool omv_csi_get_transpose(); +bool omv_csi_get_transpose(omv_csi_t *csi); // Enable/disable the auto rotation mode. -int omv_csi_set_auto_rotation(bool enable); +int omv_csi_set_auto_rotation(omv_csi_t *csi, bool enable); // Get transpose mode state. -bool omv_csi_get_auto_rotation(); +bool omv_csi_get_auto_rotation(omv_csi_t *csi); // Set the number of virtual frame buffers. -int omv_csi_set_framebuffers(int count); +int omv_csi_set_framebuffers(omv_csi_t *csi, int count); // Drop the next frame to match the current frame rate. -void omv_csi_throttle_framerate(); +void omv_csi_throttle_framerate(omv_csi_t *csi); // Set special digital effects (SDE). -int omv_csi_set_special_effect(omv_csi_sde_t sde); +int omv_csi_set_special_effect(omv_csi_t *csi, omv_csi_sde_t sde); // Set lens shading correction -int omv_csi_set_lens_correction(int enable, int radi, int coef); +int omv_csi_set_lens_correction(omv_csi_t *csi, int enable, int radi, int coef); // IOCTL function -int omv_csi_ioctl(int request, ...); +int omv_csi_ioctl(omv_csi_t *csi, int request, ...); // Set vsync callback function. -int omv_csi_set_vsync_callback(vsync_cb_t vsync_cb); +int omv_csi_set_vsync_callback(omv_csi_t *csi, omv_csi_cb_t cb); // Set frame callback function. -int omv_csi_set_frame_callback(frame_cb_t vsync_cb); +int omv_csi_set_frame_callback(omv_csi_t *csi, omv_csi_cb_t cb); // Set color palette -int omv_csi_set_color_palette(const uint16_t *color_palette); +int omv_csi_set_color_palette(omv_csi_t *csi, const uint16_t *color_palette); // Get color palette -const uint16_t *omv_csi_get_color_palette(); +const uint16_t *omv_csi_get_color_palette(omv_csi_t *csi); // Return true if the current frame size/format fits in RAM. -int omv_csi_check_framebuffer_size(); +int omv_csi_check_framebuffer_size(omv_csi_t *csi); // Auto-crop frame buffer until it fits in RAM (may switch pixel format to BAYER). -int omv_csi_auto_crop_framebuffer(); +int omv_csi_auto_crop_framebuffer(omv_csi_t *csi); // Copy a single line buffer to its destination. The copying process is // DMA-accelerated, if available, and falls back to slow software if not. -int omv_csi_copy_line(void *dma, uint8_t *src, uint8_t *dst); +int omv_csi_copy_line(omv_csi_t *csi, void *dma, uint8_t *src, uint8_t *dst); // Default snapshot function. int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags); diff --git a/common/usbdbg.c b/common/usbdbg.c index 9770bd20b..bc779b9eb 100644 --- a/common/usbdbg.c +++ b/common/usbdbg.c @@ -33,7 +33,7 @@ #include "py/runtime.h" #include "imlib.h" -#if MICROPY_PY_CSI +#if MICROPY_PY_CSI || MICROPY_PY_CSI_NG #include "omv_i2c.h" #include "omv_csi.h" #endif @@ -146,8 +146,9 @@ void usbdbg_data_in(uint32_t size, usbdbg_write_callback_t write_callback) { case USBDBG_SENSOR_ID: { uint32_t buffer = 0xFF; #if MICROPY_PY_CSI - if (omv_csi_is_detected() == true) { - buffer = omv_csi_get_id(); + omv_csi_t *csi = omv_csi_get(-1); + if (omv_csi_is_detected(csi) == true) { + buffer = omv_csi_get_id(csi); } #endif cmd = USBDBG_NONE; @@ -363,21 +364,24 @@ void usbdbg_data_out(uint32_t size, usbdbg_read_callback_t read_callback) { struct { int32_t name; int32_t value; - } - attr; + } attr; + + omv_csi_t *csi = omv_csi_get(-1); + read_callback(&attr, sizeof(attr)); + switch (attr.name) { case OMV_CSI_ATTR_CONTRAST: - omv_csi_set_contrast(attr.value); + omv_csi_set_contrast(csi, attr.value); break; case OMV_CSI_ATTR_BRIGHTNESS: - omv_csi_set_brightness(attr.value); + omv_csi_set_brightness(csi, attr.value); break; case OMV_CSI_ATTR_SATURATION: - omv_csi_set_saturation(attr.value); + omv_csi_set_saturation(csi, attr.value); break; case OMV_CSI_ATTR_GAINCEILING: - omv_csi_set_gainceiling(attr.value); + omv_csi_set_gainceiling(csi, attr.value); break; default: break; diff --git a/drivers/sensors/lepton.c b/drivers/sensors/lepton.c index 3a74f1142..e82e84e72 100644 --- a/drivers/sensors/lepton.c +++ b/drivers/sensors/lepton.c @@ -45,7 +45,7 @@ #define LEPTON_BOOT_TIMEOUT (1000) #define LEPTON_SNAPSHOT_RETRY (3) -#define LEPTON_SNAPSHOT_TIMEOUT (10000) +#define LEPTON_SNAPSHOT_TIMEOUT (5000) // Min/Max temperatures in Celsius. #define LEPTON_MIN_TEMP_NORM (-10.0f) @@ -73,18 +73,6 @@ static lepton_state_t lepton; static int lepton_reset(omv_csi_t *csi, bool measurement_mode, bool high_temp_mode); -static int sleep(omv_csi_t *csi, int enable) { - if (enable) { - omv_gpio_write(OMV_CSI_POWER_PIN, 0); - mp_hal_delay_ms(100); - } else { - omv_gpio_write(OMV_CSI_POWER_PIN, 1); - mp_hal_delay_ms(100); - } - - return 0; -} - static int read_reg(omv_csi_t *csi, uint16_t reg_addr) { uint16_t reg_data; if (omv_i2c_readw2(csi->i2c, csi->slv_addr, reg_addr, ®_data)) { @@ -281,39 +269,49 @@ static int ioctl(omv_csi_t *csi, int request, va_list ap) { } static int lepton_reset(omv_csi_t *csi, bool measurement_mode, bool high_temp_mode) { - omv_gpio_write(OMV_CSI_POWER_PIN, 0); - mp_hal_delay_ms(10); - - omv_gpio_write(OMV_CSI_POWER_PIN, 1); - mp_hal_delay_ms(10); - - omv_gpio_write(OMV_CSI_RESET_PIN, 0); - mp_hal_delay_ms(10); - - omv_gpio_write(OMV_CSI_RESET_PIN, 1); - mp_hal_delay_ms(1000); - LEP_RAD_ENABLE_E rad; LEP_AGC_ROI_T roi; memset(&lepton.port, 0, sizeof(LEP_CAMERA_PORT_DESC_T)); + if (!csi->auxiliary) { + omv_gpio_write(OMV_CSI_POWER_PIN, 0); + mp_hal_delay_ms(10); + + omv_gpio_write(OMV_CSI_POWER_PIN, 1); + mp_hal_delay_ms(10); + + omv_gpio_write(OMV_CSI_RESET_PIN, 0); + mp_hal_delay_ms(10); + + omv_gpio_write(OMV_CSI_RESET_PIN, 1); + mp_hal_delay_ms(1000); + } + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(1)) { if (LEP_OpenPort(csi->i2c, LEP_CCI_TWI, 0, &lepton.port) == LEP_OK) { break; } + if ((mp_hal_ticks_ms() - start) >= LEPTON_BOOT_TIMEOUT) { return -1; } } + if (csi->auxiliary) { + LEP_RunOemReboot(&lepton.port); + mp_hal_delay_ms(1000); + } + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_hal_delay_ms(1)) { LEP_SDK_BOOT_STATUS_E status; if (LEP_GetCameraBootStatus(&lepton.port, &status) != LEP_OK) { return -1; } + if (status == LEP_BOOT_STATUS_BOOTED) { break; } + if ((mp_hal_ticks_ms() - start) >= LEPTON_BOOT_TIMEOUT) { return -1; } @@ -324,9 +322,11 @@ static int lepton_reset(omv_csi_t *csi, bool measurement_mode, bool high_temp_mo if (LEP_DirectReadRegister(&lepton.port, LEP_I2C_STATUS_REG, &status) != LEP_OK) { return -1; } + if (!(status & LEP_I2C_STATUS_BUSY_BIT_MASK)) { break; } + if ((mp_hal_ticks_ms() - start) >= LEPTON_BOOT_TIMEOUT) { return -1; } @@ -357,6 +357,14 @@ static int lepton_reset(omv_csi_t *csi, bool measurement_mode, bool high_temp_mo return 0; } +static int sleep(omv_csi_t *csi, int enable) { + return 0; +} + +static int match(omv_csi_t *csi, size_t id) { + return (id == LEPTON_ID) || ((id >> 8) == LEPTON_ID); +} + static int reset(omv_csi_t *csi) { vospi_deinit(); @@ -380,7 +388,23 @@ static int reset(omv_csi_t *csi) { static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { framebuffer_t *fb = csi->fb; - framebuffer_update_jpeg_buffer(fb); + + if (flags & OMV_CSI_CAPTURE_FLAGS_NBLOCK) { + if (!vospi_active()) { + // Start the capture without blocking + vospi_snapshot(0); + return OMV_CSI_ERROR_WOULD_BLOCK; + } + + // If capture is running and no frames return. + if (!framebuffer_get_head(fb, FB_PEEK)) { + return OMV_CSI_ERROR_WOULD_BLOCK; + } + } + + if (flags & OMV_CSI_CAPTURE_FLAGS_UPDATE) { + framebuffer_update_jpeg_buffer(fb); + } if (csi->pixformat == PIXFORMAT_INVALID) { return OMV_CSI_ERROR_INVALID_PIXFORMAT; @@ -472,6 +496,9 @@ static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { int lepton_init(omv_csi_t *csi) { csi->reset = reset; csi->sleep = sleep; + csi->config = NULL; + csi->abort = NULL; + csi->match = match; csi->snapshot = snapshot; csi->read_reg = read_reg; csi->write_reg = write_reg; @@ -495,12 +522,16 @@ int lepton_init(omv_csi_t *csi) { csi->set_lens_correction = set_lens_correction; csi->ioctl = ioctl; + csi->auxiliary = 1; csi->vsync_pol = 1; csi->hsync_pol = 0; csi->pixck_pol = 0; csi->frame_sync = 0; csi->mono_bpp = 1; + // Extra delay after power-on + mp_hal_delay_ms(1000); + if (reset(csi) != 0) { return -1; } diff --git a/drivers/sensors/softcsi.c b/drivers/sensors/softcsi.c index 06be8630d..18db257cd 100644 --- a/drivers/sensors/softcsi.c +++ b/drivers/sensors/softcsi.c @@ -68,7 +68,9 @@ static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { return 0; } - framebuffer_update_jpeg_buffer(fb); + if (flags & OMV_CSI_CAPTURE_FLAGS_UPDATE) { + framebuffer_update_jpeg_buffer(fb); + } if (fb->n_buffers != 1) { framebuffer_set_buffers(fb, 1); @@ -149,12 +151,15 @@ static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { int softcsi_init(omv_csi_t *csi) { csi->reset = reset; + csi->abort = NULL; + csi->config = NULL; csi->set_pixformat = set_pixformat; csi->set_framesize = set_framesize; csi->set_hmirror = set_hmirror; csi->set_vflip = set_vflip; csi->snapshot = snapshot; + csi->auxiliary = 1; csi->vsync_pol = 1; csi->hsync_pol = 0; csi->pixck_pol = 0; diff --git a/lib/imlib/framebuffer.c b/lib/imlib/framebuffer.c index f0869a6e9..5cf3b54cc 100644 --- a/lib/imlib/framebuffer.c +++ b/lib/imlib/framebuffer.c @@ -42,26 +42,28 @@ extern char _jpeg_memory_end; jpegbuffer_t *jpegbuffer = (jpegbuffer_t *) &_jpeg_memory_start; void framebuffer_init0() { - // Save fb_enabled flag state + // Save enable flag. int fb_enabled = jpegbuffer->enabled; + uint32_t fb_size = (char *) &_fb_memory_end - (char *) framebuffer->data; - // Clear framebuffers - memset(framebuffer, 0, sizeof(*framebuffer)); + // Initialize frame buffer. + framebuffer_init_fb(framebuffer, fb_size, false); + + // Initialize jpeg buffer. memset(jpegbuffer, 0, sizeof(*jpegbuffer)); - mutex_init0(&jpegbuffer->lock); - - // Enable streaming. - framebuffer->streaming_enabled = true; // controlled by the OpenMV Cam. - - // Set default quality + jpegbuffer->enabled = fb_enabled; jpegbuffer->quality = ((OMV_JPEG_QUALITY_HIGH - OMV_JPEG_QUALITY_LOW) / 2) + OMV_JPEG_QUALITY_LOW; +} - // Set fb_enabled - jpegbuffer->enabled = fb_enabled; // controlled by the IDE. +void framebuffer_init_fb(framebuffer_t *fb, size_t size, bool dynamic) { + // Clear framebuffers + memset(fb, 0, sizeof(*fb)); - // Setup buffering. - framebuffer_set_buffers(framebuffer, 1); + fb->raw_size = size; + fb->streaming_enabled = true; + fb->dynamic = dynamic; + framebuffer_set_buffers(fb, 1); } void framebuffer_init_image(framebuffer_t *fb, image_t *img) { @@ -283,8 +285,14 @@ int framebuffer_encoded_size(framebuffer_t *fb, image_t *img) { // Returns the current frame buffer size, factoring in the space taken by fb_alloc. static uint32_t framebuffer_max_buffer_size(framebuffer_t *fb) { - uint32_t fb_total_size = FB_ALIGN_SIZE_ROUND_DOWN((char *) &_fb_memory_end - (char *) fb->data); + uint32_t fb_total_size = FB_ALIGN_SIZE_ROUND_DOWN(fb->raw_size); uint32_t fb_avail_size = FB_ALIGN_SIZE_ROUND_DOWN(fb_alloc_stack_pointer() - (char *) fb->data); + + // No fb_alloc on dynamic FBs. + if (fb->dynamic) { + fb_avail_size = fb_total_size; + } + return IM_MIN(fb_total_size, fb_avail_size); } diff --git a/lib/imlib/framebuffer.h b/lib/imlib/framebuffer.h index 117f0b7a9..0620db813 100644 --- a/lib/imlib/framebuffer.h +++ b/lib/imlib/framebuffer.h @@ -43,6 +43,7 @@ typedef struct framebuffer { int32_t u, v; PIXFORMAT_STRUCT; int32_t streaming_enabled; + uint32_t raw_size; uint32_t buff_size; uint32_t n_buffers; uint32_t frame_size; @@ -50,6 +51,7 @@ typedef struct framebuffer { volatile int32_t tail; bool check_head; int32_t sampled_head; + bool dynamic; OMV_ATTR_ALIGNED(uint8_t data[], FRAMEBUFFER_ALIGNMENT); } framebuffer_t; @@ -108,6 +110,9 @@ uint32_t framebuffer_get_buffer_size(framebuffer_t *fb); // Return the state of a buffer. vbuffer_t *framebuffer_get_buffer(framebuffer_t *fb, int32_t index); +// Initializes a frame buffer instance. +void framebuffer_init_fb(framebuffer_t *fb, size_t size, bool dynamic); + // Initializes an image from the frame buffer. void framebuffer_init_image(framebuffer_t *fb, image_t *img); diff --git a/lib/imlib/imlib.c b/lib/imlib/imlib.c index ad2765721..4a8f57a6b 100644 --- a/lib/imlib/imlib.c +++ b/lib/imlib/imlib.c @@ -284,7 +284,7 @@ void image_init(image_t *ptr, int w, int h, pixformat_t pixfmt, uint32_t size, v ptr->pixels = pixels; } -void image_copy(image_t *dst, image_t *src) { +void image_copy(image_t *dst, image_t *src, bool deep) { memcpy(dst, src, sizeof(image_t)); } diff --git a/lib/imlib/imlib.h b/lib/imlib/imlib.h index 618311a58..b0d8a3afe 100644 --- a/lib/imlib/imlib.h +++ b/lib/imlib/imlib.h @@ -570,7 +570,7 @@ typedef struct image { void image_xalloc(image_t *img, size_t size); void image_xalloc0(image_t *img, size_t size); void image_init(image_t *ptr, int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels); -void image_copy(image_t *dst, image_t *src); +void image_copy(image_t *dst, image_t *src, bool deep); size_t image_line_size(image_t *ptr); size_t image_size(image_t *ptr); bool image_get_mask_pixel(image_t *ptr, int x, int y); diff --git a/modules/py_csi.c b/modules/py_csi.c index 619ac2707..2307c5aab 100644 --- a/modules/py_csi.c +++ b/modules/py_csi.c @@ -38,6 +38,8 @@ #if MICROPY_PY_CSI #include "omv_csi.h" +#include "omv_gpio.h" + #include "imlib.h" #include "xalloc.h" #include "py_assert.h" @@ -50,7 +52,6 @@ #include "py_helper.h" #include "framebuffer.h" -extern omv_csi_t csi; static mp_obj_t vsync_callback = mp_const_none; static mp_obj_t frame_callback = mp_const_none; @@ -58,8 +59,8 @@ static mp_obj_t frame_callback = mp_const_none; #define omv_csi_print_error(op) printf("\x1B[31mWARNING: %s control is not supported by this image sensor.\x1B[0m\n", op); #if MICROPY_PY_IMU -static void do_auto_rotation(int pitch_deadzone, int roll_activezone) { - if (omv_csi_get_auto_rotation()) { +static void do_auto_rotation(omv_csi_t *csi, int pitch_deadzone, int roll_activezone) { + if (omv_csi_get_auto_rotation(csi)) { float pitch = py_imu_pitch_rotation(); if (((pitch <= (90 - pitch_deadzone)) || ((90 + pitch_deadzone) < pitch)) && ((pitch <= (270 - pitch_deadzone)) || ((270 + pitch_deadzone) < pitch))) { @@ -67,24 +68,24 @@ static void do_auto_rotation(int pitch_deadzone, int roll_activezone) { float roll = py_imu_roll_rotation(); if (((360 - roll_activezone) <= roll) || (roll < (0 + roll_activezone)) ) { // center is 0/360, upright - omv_csi_set_hmirror(false); - omv_csi_set_vflip(false); - omv_csi_set_transpose(false); + omv_csi_set_hmirror(csi, false); + omv_csi_set_vflip(csi, false); + omv_csi_set_transpose(csi, false); } else if (((270 - roll_activezone) <= roll) && (roll < (270 + roll_activezone))) { // center is 270, rotated right - omv_csi_set_hmirror(true); - omv_csi_set_vflip(false); - omv_csi_set_transpose(true); + omv_csi_set_hmirror(csi, true); + omv_csi_set_vflip(csi, false); + omv_csi_set_transpose(csi, true); } else if (((180 - roll_activezone) <= roll) && (roll < (180 + roll_activezone))) { // center is 180, upside down - omv_csi_set_hmirror(true); - omv_csi_set_vflip(true); - omv_csi_set_transpose(false); + omv_csi_set_hmirror(csi, true); + omv_csi_set_vflip(csi, true); + omv_csi_set_transpose(csi, false); } else if (((90 - roll_activezone) <= roll) && (roll < (90 + roll_activezone))) { // center is 90, rotated left - omv_csi_set_hmirror(false); - omv_csi_set_vflip(true); - omv_csi_set_transpose(true); + omv_csi_set_hmirror(csi, false); + omv_csi_set_vflip(csi, true); + omv_csi_set_transpose(csi, true); } } } @@ -92,10 +93,11 @@ static void do_auto_rotation(int pitch_deadzone, int roll_activezone) { #endif // MICROPY_PY_IMU static mp_obj_t py_omv_csi__init__() { + omv_csi_t *csi = omv_csi_get(-1); // This is the module init function, not the sensor init function. // This gets called when the module is imported, so it's a good // place to check if the sensor was detected or not. - if (omv_csi_is_detected() == false) { + if (omv_csi_is_detected(csi) == false) { omv_csi_raise_error(OMV_CSI_ERROR_ISC_UNDETECTED); } return mp_const_none; @@ -103,14 +105,16 @@ static mp_obj_t py_omv_csi__init__() { static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi__init__obj, py_omv_csi__init__); static mp_obj_t py_omv_csi_reset() { - int error = omv_csi_reset(); + omv_csi_t *csi = omv_csi_get(-1); + + int error = omv_csi_reset(csi, true); if (error != 0) { omv_csi_raise_error(error); } #if MICROPY_PY_IMU // +-10 degree dead-zone around pitch 90/270. // +-45 degree active-zone around roll 0/90/180/270/360. - do_auto_rotation(10, 45); + do_auto_rotation(csi, 10, 45); // We're setting the dead-zone on pitch because roll readings are invalid there. // We're setting the full range on roll to set the initial state. #endif // MICROPY_PY_IMU @@ -119,34 +123,41 @@ static mp_obj_t py_omv_csi_reset() { static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_reset_obj, py_omv_csi_reset); static mp_obj_t py_omv_csi_sleep(mp_obj_t enable) { - PY_ASSERT_FALSE_MSG(omv_csi_sleep(mp_obj_is_true(enable)) != 0, "Sleep Failed"); + omv_csi_t *csi = omv_csi_get(-1); + PY_ASSERT_FALSE_MSG(omv_csi_sleep(csi, mp_obj_is_true(enable)) != 0, "Sleep Failed"); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_sleep_obj, py_omv_csi_sleep); static mp_obj_t py_omv_csi_shutdown(mp_obj_t enable) { - PY_ASSERT_FALSE_MSG(omv_csi_shutdown(mp_obj_is_true(enable)) != 0, "Shutdown Failed"); + omv_csi_t *csi = omv_csi_get(-1); + PY_ASSERT_FALSE_MSG(omv_csi_shutdown(csi, mp_obj_is_true(enable)) != 0, "Shutdown Failed"); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_shutdown_obj, py_omv_csi_shutdown); static mp_obj_t py_omv_csi_flush() { - framebuffer_update_jpeg_buffer(csi.fb); + omv_csi_t *csi = omv_csi_get(-1); + framebuffer_update_jpeg_buffer(csi->fb); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_flush_obj, py_omv_csi_flush); static mp_obj_t py_omv_csi_snapshot(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + omv_csi_t *csi = omv_csi_get(-1); + #if MICROPY_PY_IMU // +-10 degree dead-zone around pitch 90/270. // +-35 degree active-zone around roll 0/90/180/270/360. - do_auto_rotation(10, 35); + do_auto_rotation(csi, 10, 35); // We're setting the dead-zone on pitch because roll readings are invalid there. // We're not setting the full range on roll to prevent oscillation. #endif // MICROPY_PY_IMU mp_obj_t image = py_image(0, 0, 0, 0, 0); - int error = csi.snapshot(&csi, (image_t *) py_image_cobj(image), 0); + uint32_t flags = OMV_CSI_CAPTURE_FLAGS_UPDATE; + + int error = omv_csi_snapshot(csi, (image_t *) py_image_cobj(image), flags); if (error != 0) { omv_csi_raise_error(error); } @@ -184,34 +195,39 @@ static mp_obj_t py_omv_csi_skip_frames(size_t n_args, const mp_obj_t *args, mp_m static MP_DEFINE_CONST_FUN_OBJ_KW(py_omv_csi_skip_frames_obj, 0, py_omv_csi_skip_frames); static mp_obj_t py_omv_csi_width() { - return mp_obj_new_int(resolution[csi.framesize][0]); + omv_csi_t *csi = omv_csi_get(-1); + return mp_obj_new_int(resolution[csi->framesize][0]); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_width_obj, py_omv_csi_width); static mp_obj_t py_omv_csi_height() { - return mp_obj_new_int(resolution[csi.framesize][1]); + omv_csi_t *csi = omv_csi_get(-1); + return mp_obj_new_int(resolution[csi->framesize][1]); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_height_obj, py_omv_csi_height); static mp_obj_t py_omv_csi_get_fb() { image_t image; + omv_csi_t *csi = omv_csi_get(-1); - if (framebuffer_get_depth(csi.fb) < 0) { + if (framebuffer_get_depth(csi->fb) < 0) { return mp_const_none; } - framebuffer_init_image(csi.fb, &image); + framebuffer_init_image(csi->fb, &image); return py_image_from_struct(&image); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_fb_obj, py_omv_csi_get_fb); static mp_obj_t py_omv_csi_get_id() { - return mp_obj_new_int(omv_csi_get_id()); + omv_csi_t *csi = omv_csi_get(-1); + return mp_obj_new_int(omv_csi_get_id(csi)); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_id_obj, py_omv_csi_get_id); static mp_obj_t py_omv_csi_get_frame_available() { - framebuffer_t *fb = framebuffer_get(0); + omv_csi_t *csi = omv_csi_get(-1); + framebuffer_t *fb = csi->fb; return mp_obj_new_bool(fb->tail != fb->head); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_frame_available_obj, py_omv_csi_get_frame_available); @@ -245,7 +261,8 @@ static mp_obj_t py_omv_csi_dealloc_extra_fb() { static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_dealloc_extra_fb_obj, py_omv_csi_dealloc_extra_fb); static mp_obj_t py_omv_csi_set_pixformat(mp_obj_t pixformat) { - int error = omv_csi_set_pixformat(mp_obj_get_int(pixformat)); + omv_csi_t *csi = omv_csi_get(-1); + int error = omv_csi_set_pixformat(csi, mp_obj_get_int(pixformat)); if (error != 0) { omv_csi_raise_error(error); } @@ -254,15 +271,17 @@ static mp_obj_t py_omv_csi_set_pixformat(mp_obj_t pixformat) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_pixformat_obj, py_omv_csi_set_pixformat); static mp_obj_t py_omv_csi_get_pixformat() { - if (csi.pixformat == PIXFORMAT_INVALID) { + omv_csi_t *csi = omv_csi_get(-1); + if (csi->pixformat == PIXFORMAT_INVALID) { omv_csi_raise_error(OMV_CSI_ERROR_INVALID_PIXFORMAT); } - return mp_obj_new_int(csi.pixformat); + return mp_obj_new_int(csi->pixformat); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_pixformat_obj, py_omv_csi_get_pixformat); static mp_obj_t py_omv_csi_set_framesize(mp_obj_t framesize) { - int error = omv_csi_set_framesize(mp_obj_get_int(framesize)); + omv_csi_t *csi = omv_csi_get(-1); + int error = omv_csi_set_framesize(csi, mp_obj_get_int(framesize)); if (error != 0) { omv_csi_raise_error(error); } @@ -271,15 +290,17 @@ static mp_obj_t py_omv_csi_set_framesize(mp_obj_t framesize) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_framesize_obj, py_omv_csi_set_framesize); static mp_obj_t py_omv_csi_get_framesize() { - if (csi.framesize == OMV_CSI_FRAMESIZE_INVALID) { + omv_csi_t *csi = omv_csi_get(-1); + if (csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { omv_csi_raise_error(OMV_CSI_ERROR_INVALID_FRAMESIZE); } - return mp_obj_new_int(csi.framesize); + return mp_obj_new_int(csi->framesize); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_framesize_obj, py_omv_csi_get_framesize); static mp_obj_t py_omv_csi_set_framerate(mp_obj_t framerate) { - int error = omv_csi_set_framerate(mp_obj_get_int(framerate)); + omv_csi_t *csi = omv_csi_get(-1); + int error = omv_csi_set_framerate(csi, mp_obj_get_int(framerate)); if (error != 0) { omv_csi_raise_error(error); } @@ -288,23 +309,25 @@ static mp_obj_t py_omv_csi_set_framerate(mp_obj_t framerate) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_framerate_obj, py_omv_csi_set_framerate); static mp_obj_t py_omv_csi_get_framerate() { - if (csi.framerate == 0) { + omv_csi_t *csi = omv_csi_get(-1); + if (csi->framerate == 0) { omv_csi_raise_error(OMV_CSI_ERROR_INVALID_FRAMERATE); } - return mp_obj_new_int(csi.framerate); + return mp_obj_new_int(csi->framerate); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_framerate_obj, py_omv_csi_get_framerate); static mp_obj_t py_omv_csi_set_windowing(size_t n_args, const mp_obj_t *args) { - if (csi.framesize == OMV_CSI_FRAMESIZE_INVALID) { + omv_csi_t *csi = omv_csi_get(-1); + if (csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { omv_csi_raise_error(OMV_CSI_ERROR_INVALID_FRAMESIZE); } rectangle_t temp; temp.x = 0; temp.y = 0; - temp.w = resolution[csi.framesize][0]; - temp.h = resolution[csi.framesize][1]; + temp.w = resolution[csi->framesize][0]; + temp.h = resolution[csi->framesize][1]; mp_obj_t *array = (mp_obj_t *) args; mp_uint_t array_len = n_args; @@ -339,7 +362,7 @@ static mp_obj_t py_omv_csi_set_windowing(size_t n_args, const mp_obj_t *args) { rectangle_intersected(&r, &temp); - int error = omv_csi_set_windowing(r.x, r.y, r.w, r.h); + int error = omv_csi_set_windowing(csi, r.x, r.y, r.w, r.h); if (error != 0) { omv_csi_raise_error(error); } @@ -349,18 +372,20 @@ static mp_obj_t py_omv_csi_set_windowing(size_t n_args, const mp_obj_t *args) { static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_omv_csi_set_windowing_obj, 1, 4, py_omv_csi_set_windowing); static mp_obj_t py_omv_csi_get_windowing() { - if (csi.framesize == OMV_CSI_FRAMESIZE_INVALID) { + omv_csi_t *csi = omv_csi_get(-1); + if (csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { omv_csi_raise_error(OMV_CSI_ERROR_INVALID_FRAMESIZE); } - return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(framebuffer_get_x(csi.fb)), - mp_obj_new_int(framebuffer_get_y(csi.fb)), - mp_obj_new_int(framebuffer_get_u(csi.fb)), - mp_obj_new_int(framebuffer_get_v(csi.fb))}); + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(framebuffer_get_x(csi->fb)), + mp_obj_new_int(framebuffer_get_y(csi->fb)), + mp_obj_new_int(framebuffer_get_u(csi->fb)), + mp_obj_new_int(framebuffer_get_v(csi->fb))}); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_windowing_obj, py_omv_csi_get_windowing); static mp_obj_t py_omv_csi_set_gainceiling(mp_obj_t gainceiling) { + omv_csi_t *csi = omv_csi_get(-1); omv_csi_gainceiling_t gain; switch (mp_obj_get_int(gainceiling)) { case 2: @@ -389,7 +414,7 @@ static mp_obj_t py_omv_csi_set_gainceiling(mp_obj_t gainceiling) { break; } - if (omv_csi_set_gainceiling(gain) != 0) { + if (omv_csi_set_gainceiling(csi, gain) != 0) { return mp_const_false; } return mp_const_true; @@ -397,7 +422,8 @@ static mp_obj_t py_omv_csi_set_gainceiling(mp_obj_t gainceiling) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_gainceiling_obj, py_omv_csi_set_gainceiling); static mp_obj_t py_omv_csi_set_brightness(mp_obj_t brightness) { - if (omv_csi_set_brightness(mp_obj_get_int(brightness)) != 0) { + omv_csi_t *csi = omv_csi_get(-1); + if (omv_csi_set_brightness(csi, mp_obj_get_int(brightness)) != 0) { return mp_const_false; } return mp_const_true; @@ -405,7 +431,8 @@ static mp_obj_t py_omv_csi_set_brightness(mp_obj_t brightness) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_brightness_obj, py_omv_csi_set_brightness); static mp_obj_t py_omv_csi_set_contrast(mp_obj_t contrast) { - if (omv_csi_set_contrast(mp_obj_get_int(contrast)) != 0) { + omv_csi_t *csi = omv_csi_get(-1); + if (omv_csi_set_contrast(csi, mp_obj_get_int(contrast)) != 0) { return mp_const_false; } return mp_const_true; @@ -413,7 +440,8 @@ static mp_obj_t py_omv_csi_set_contrast(mp_obj_t contrast) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_contrast_obj, py_omv_csi_set_contrast); static mp_obj_t py_omv_csi_set_saturation(mp_obj_t saturation) { - if (omv_csi_set_saturation(mp_obj_get_int(saturation)) != 0) { + omv_csi_t *csi = omv_csi_get(-1); + if (omv_csi_set_saturation(csi, mp_obj_get_int(saturation)) != 0) { return mp_const_false; } return mp_const_true; @@ -421,12 +449,13 @@ static mp_obj_t py_omv_csi_set_saturation(mp_obj_t saturation) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_saturation_obj, py_omv_csi_set_saturation); static mp_obj_t py_omv_csi_set_quality(mp_obj_t qs) { + omv_csi_t *csi = omv_csi_get(-1); int q = mp_obj_get_int(qs); PY_ASSERT_TRUE((q >= 0 && q <= 100)); q = 100 - q; //invert quality q = 255 * q / 100; //map to 0->255 - if (omv_csi_set_quality(q) != 0) { + if (omv_csi_set_quality(csi, q) != 0) { return mp_const_false; } return mp_const_true; @@ -434,7 +463,8 @@ static mp_obj_t py_omv_csi_set_quality(mp_obj_t qs) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_quality_obj, py_omv_csi_set_quality); static mp_obj_t py_omv_csi_set_colorbar(mp_obj_t enable) { - if (omv_csi_set_colorbar(mp_obj_is_true(enable)) != 0) { + omv_csi_t *csi = omv_csi_get(-1); + if (omv_csi_set_colorbar(csi, mp_obj_is_true(enable)) != 0) { return mp_const_false; } return mp_const_true; @@ -449,6 +479,7 @@ static mp_obj_t py_omv_csi_set_auto_gain(size_t n_args, const mp_obj_t *pos_args }; // Parse args. + omv_csi_t *csi = omv_csi_get(-1); int enable = mp_obj_get_int(pos_args[0]); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -456,7 +487,7 @@ static mp_obj_t py_omv_csi_set_auto_gain(size_t n_args, const mp_obj_t *pos_args float gain_db = py_helper_arg_to_float(args[ARG_gain_db].u_obj, NAN); float gain_db_ceiling = py_helper_arg_to_float(args[ARG_gain_db_ceiling].u_obj, NAN); - int error = omv_csi_set_auto_gain(enable, gain_db, gain_db_ceiling); + int error = omv_csi_set_auto_gain(csi, enable, gain_db, gain_db_ceiling); if (error != 0) { if (error != OMV_CSI_ERROR_CTL_UNSUPPORTED) { omv_csi_raise_error(error); @@ -468,8 +499,9 @@ static mp_obj_t py_omv_csi_set_auto_gain(size_t n_args, const mp_obj_t *pos_args static MP_DEFINE_CONST_FUN_OBJ_KW(py_omv_csi_set_auto_gain_obj, 1, py_omv_csi_set_auto_gain); static mp_obj_t py_omv_csi_get_gain_db() { + omv_csi_t *csi = omv_csi_get(-1); float gain_db; - int error = omv_csi_get_gain_db(&gain_db); + int error = omv_csi_get_gain_db(csi, &gain_db); if (error != 0) { omv_csi_raise_error(error); } @@ -484,11 +516,12 @@ static mp_obj_t py_omv_csi_set_auto_exposure(size_t n_args, const mp_obj_t *pos_ }; // Parse args. + omv_csi_t *csi = omv_csi_get(-1); int enable = mp_obj_get_int(pos_args[0]); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - int error = omv_csi_set_auto_exposure(enable, args[ARG_exposure_us].u_int); + int error = omv_csi_set_auto_exposure(csi, enable, args[ARG_exposure_us].u_int); if (error != 0) { if (error != OMV_CSI_ERROR_CTL_UNSUPPORTED) { omv_csi_raise_error(error); @@ -501,7 +534,8 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(py_omv_csi_set_auto_exposure_obj, 1, py_omv_cs static mp_obj_t py_omv_csi_get_exposure_us() { int exposure_us; - int error = omv_csi_get_exposure_us(&exposure_us); + omv_csi_t *csi = omv_csi_get(-1); + int error = omv_csi_get_exposure_us(csi, &exposure_us); if (error != 0) { omv_csi_raise_error(error); } @@ -516,6 +550,7 @@ static mp_obj_t py_omv_csi_set_auto_whitebal(size_t n_args, const mp_obj_t *pos_ }; // Parse args. + omv_csi_t *csi = omv_csi_get(-1); int enable = mp_obj_get_int(pos_args[0]); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); @@ -523,7 +558,7 @@ static mp_obj_t py_omv_csi_set_auto_whitebal(size_t n_args, const mp_obj_t *pos_ float rgb_gain_db[3] = {NAN, NAN, NAN}; py_helper_arg_to_float_array(args[ARG_rgb_gain_db].u_obj, rgb_gain_db, 3); - int error = omv_csi_set_auto_whitebal(enable, rgb_gain_db[0], rgb_gain_db[1], rgb_gain_db[2]); + int error = omv_csi_set_auto_whitebal(csi, enable, rgb_gain_db[0], rgb_gain_db[1], rgb_gain_db[2]); if (error != 0) { if (error != OMV_CSI_ERROR_CTL_UNSUPPORTED) { omv_csi_raise_error(error); @@ -536,7 +571,8 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(py_omv_csi_set_auto_whitebal_obj, 1, py_omv_cs static mp_obj_t py_omv_csi_get_rgb_gain_db() { float r_gain_db = 0.0, g_gain_db = 0.0, b_gain_db = 0.0; - int error = omv_csi_get_rgb_gain_db(&r_gain_db, &g_gain_db, &b_gain_db); + omv_csi_t *csi = omv_csi_get(-1); + int error = omv_csi_get_rgb_gain_db(csi, &r_gain_db, &g_gain_db, &b_gain_db); if (error != 0) { omv_csi_raise_error(error); } @@ -558,18 +594,20 @@ static mp_obj_t py_omv_csi_set_auto_blc(size_t n_args, const mp_obj_t *pos_args, mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + omv_csi_t *csi = omv_csi_get(-1); int enable = args[ARG_enable].u_int; - int regs[csi.blc_size]; + int regs[csi->blc_size]; bool regs_present = args[ARG_regs].u_obj != mp_const_none; + if (regs_present) { mp_obj_t *arg_array; - mp_obj_get_array_fixed_n(args[ARG_regs].u_obj, csi.blc_size, &arg_array); - for (uint32_t i = 0; i < csi.blc_size; i++) { + mp_obj_get_array_fixed_n(args[ARG_regs].u_obj, csi->blc_size, &arg_array); + for (uint32_t i = 0; i < csi->blc_size; i++) { regs[i] = mp_obj_get_int(arg_array[i]); } } - int error = omv_csi_set_auto_blc(enable, regs_present ? regs : NULL); + int error = omv_csi_set_auto_blc(csi, enable, regs_present ? regs : NULL); if (error != 0) { if (error != OMV_CSI_ERROR_CTL_UNSUPPORTED) { omv_csi_raise_error(error); @@ -581,14 +619,16 @@ static mp_obj_t py_omv_csi_set_auto_blc(size_t n_args, const mp_obj_t *pos_args, static MP_DEFINE_CONST_FUN_OBJ_KW(py_omv_csi_set_auto_blc_obj, 1, py_omv_csi_set_auto_blc); static mp_obj_t py_omv_csi_get_blc_regs() { - int regs[csi.blc_size]; - int error = omv_csi_get_blc_regs(regs); + omv_csi_t *csi = omv_csi_get(-1); + int regs[csi->blc_size]; + + int error = omv_csi_get_blc_regs(csi, regs); if (error != 0) { omv_csi_raise_error(error); } - mp_obj_list_t *l = mp_obj_new_list(csi.blc_size, NULL); - for (uint32_t i = 0; i < csi.blc_size; i++) { + mp_obj_list_t *l = mp_obj_new_list(csi->blc_size, NULL); + for (uint32_t i = 0; i < csi->blc_size; i++) { l->items[i] = mp_obj_new_int(regs[i]); } return l; @@ -596,7 +636,8 @@ static mp_obj_t py_omv_csi_get_blc_regs() { static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_blc_regs_obj, py_omv_csi_get_blc_regs); static mp_obj_t py_omv_csi_set_hmirror(mp_obj_t enable) { - int error = omv_csi_set_hmirror(mp_obj_is_true(enable)); + omv_csi_t *csi = omv_csi_get(-1); + int error = omv_csi_set_hmirror(csi, mp_obj_is_true(enable)); if (error != 0) { omv_csi_raise_error(error); } @@ -605,12 +646,14 @@ static mp_obj_t py_omv_csi_set_hmirror(mp_obj_t enable) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_hmirror_obj, py_omv_csi_set_hmirror); static mp_obj_t py_omv_csi_get_hmirror() { - return mp_obj_new_bool(omv_csi_get_hmirror()); + omv_csi_t *csi = omv_csi_get(-1); + return mp_obj_new_bool(omv_csi_get_hmirror(csi)); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_hmirror_obj, py_omv_csi_get_hmirror); static mp_obj_t py_omv_csi_set_vflip(mp_obj_t enable) { - int error = omv_csi_set_vflip(mp_obj_is_true(enable)); + omv_csi_t *csi = omv_csi_get(-1); + int error = omv_csi_set_vflip(csi, mp_obj_is_true(enable)); if (error != 0) { omv_csi_raise_error(error); } @@ -619,12 +662,14 @@ static mp_obj_t py_omv_csi_set_vflip(mp_obj_t enable) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_vflip_obj, py_omv_csi_set_vflip); static mp_obj_t py_omv_csi_get_vflip() { - return mp_obj_new_bool(omv_csi_get_vflip()); + omv_csi_t *csi = omv_csi_get(-1); + return mp_obj_new_bool(omv_csi_get_vflip(csi)); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_vflip_obj, py_omv_csi_get_vflip); static mp_obj_t py_omv_csi_set_transpose(mp_obj_t enable) { - int error = omv_csi_set_transpose(mp_obj_is_true(enable)); + omv_csi_t *csi = omv_csi_get(-1); + int error = omv_csi_set_transpose(csi, mp_obj_is_true(enable)); if (error != 0) { omv_csi_raise_error(error); } @@ -633,12 +678,14 @@ static mp_obj_t py_omv_csi_set_transpose(mp_obj_t enable) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_transpose_obj, py_omv_csi_set_transpose); static mp_obj_t py_omv_csi_get_transpose() { - return mp_obj_new_bool(omv_csi_get_transpose()); + omv_csi_t *csi = omv_csi_get(-1); + return mp_obj_new_bool(omv_csi_get_transpose(csi)); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_transpose_obj, py_omv_csi_get_transpose); static mp_obj_t py_omv_csi_set_auto_rotation(mp_obj_t enable) { - int error = omv_csi_set_auto_rotation(mp_obj_is_true(enable)); + omv_csi_t *csi = omv_csi_get(-1); + int error = omv_csi_set_auto_rotation(csi, mp_obj_is_true(enable)); if (error != 0) { omv_csi_raise_error(error); } @@ -647,15 +694,16 @@ static mp_obj_t py_omv_csi_set_auto_rotation(mp_obj_t enable) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_auto_rotation_obj, py_omv_csi_set_auto_rotation); static mp_obj_t py_omv_csi_get_auto_rotation() { - return mp_obj_new_bool(omv_csi_get_auto_rotation()); + omv_csi_t *csi = omv_csi_get(-1); + return mp_obj_new_bool(omv_csi_get_auto_rotation(csi)); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_auto_rotation_obj, py_omv_csi_get_auto_rotation); static mp_obj_t py_omv_csi_set_framebuffers(mp_obj_t count) { + omv_csi_t *csi = omv_csi_get(-1); mp_int_t c = mp_obj_get_int(count); - framebuffer_t *fb = framebuffer_get(0); - if (fb->n_buffers == c) { + if (c == csi->fb->n_buffers) { return mp_const_none; } @@ -663,7 +711,7 @@ static mp_obj_t py_omv_csi_set_framebuffers(mp_obj_t count) { omv_csi_raise_error(OMV_CSI_ERROR_INVALID_ARGUMENT); } - int error = omv_csi_set_framebuffers(c); + int error = omv_csi_set_framebuffers(csi, c); if (error != 0) { omv_csi_raise_error(error); } @@ -673,33 +721,36 @@ static mp_obj_t py_omv_csi_set_framebuffers(mp_obj_t count) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_framebuffers_obj, py_omv_csi_set_framebuffers); static mp_obj_t py_omv_csi_get_framebuffers() { - framebuffer_t *fb = framebuffer_get(0); - return mp_obj_new_int(fb->n_buffers); + omv_csi_t *csi = omv_csi_get(-1); + return mp_obj_new_int(csi->fb->n_buffers); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_framebuffers_obj, py_omv_csi_get_framebuffers); static mp_obj_t py_omv_csi_disable_delays(size_t n_args, const mp_obj_t *args) { + omv_csi_t *csi = omv_csi_get(-1); if (!n_args) { - return mp_obj_new_bool(csi.disable_delays); + return mp_obj_new_bool(csi->disable_delays); } - csi.disable_delays = mp_obj_get_int(args[0]); + csi->disable_delays = mp_obj_get_int(args[0]); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_omv_csi_disable_delays_obj, 0, 1, py_omv_csi_disable_delays); static mp_obj_t py_omv_csi_disable_full_flush(size_t n_args, const mp_obj_t *args) { + omv_csi_t *csi = omv_csi_get(-1); if (!n_args) { - return mp_obj_new_bool(csi.disable_full_flush); + return mp_obj_new_bool(csi->disable_full_flush); } - csi.disable_full_flush = mp_obj_get_int(args[0]); + csi->disable_full_flush = mp_obj_get_int(args[0]); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_omv_csi_disable_full_flush_obj, 0, 1, py_omv_csi_disable_full_flush); static mp_obj_t py_omv_csi_set_special_effect(mp_obj_t sde) { - if (omv_csi_set_special_effect(mp_obj_get_int(sde)) != 0) { + omv_csi_t *csi = omv_csi_get(-1); + if (omv_csi_set_special_effect(csi, mp_obj_get_int(sde)) != 0) { return mp_const_false; } return mp_const_true; @@ -707,7 +758,8 @@ static mp_obj_t py_omv_csi_set_special_effect(mp_obj_t sde) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_special_effect_obj, py_omv_csi_set_special_effect); static mp_obj_t py_omv_csi_set_lens_correction(mp_obj_t enable, mp_obj_t radi, mp_obj_t coef) { - if (omv_csi_set_lens_correction(mp_obj_is_true(enable), + omv_csi_t *csi = omv_csi_get(-1); + if (omv_csi_set_lens_correction(csi, mp_obj_is_true(enable), mp_obj_get_int(radi), mp_obj_get_int(coef)) != 0) { return mp_const_false; } @@ -715,45 +767,58 @@ static mp_obj_t py_omv_csi_set_lens_correction(mp_obj_t enable, mp_obj_t radi, m } static MP_DEFINE_CONST_FUN_OBJ_3(py_omv_csi_set_lens_correction_obj, py_omv_csi_set_lens_correction); -static void omv_csi_vsync_callback(uint32_t vsync) { +static void omv_csi_vsync_callback(void *data) { if (mp_obj_is_callable(vsync_callback)) { - mp_call_function_1(vsync_callback, mp_obj_new_int(vsync)); + uint32_t vsync_state = 0; + #ifdef OMV_CSI_VSYNC_PIN + vsync_state = omv_gpio_read(OMV_CSI_VSYNC_PIN); + #endif + mp_call_function_1(vsync_callback, mp_obj_new_int(vsync_state)); } } static mp_obj_t py_omv_csi_set_vsync_callback(mp_obj_t vsync_callback_obj) { + omv_csi_cb_t cb; + omv_csi_t *csi = omv_csi_get(-1); + if (!mp_obj_is_callable(vsync_callback_obj)) { vsync_callback = mp_const_none; - omv_csi_set_vsync_callback(NULL); + cb = (omv_csi_cb_t) { NULL, NULL }; } else { vsync_callback = vsync_callback_obj; - omv_csi_set_vsync_callback(omv_csi_vsync_callback); + cb = (omv_csi_cb_t) { omv_csi_vsync_callback, NULL }; } + omv_csi_set_vsync_callback(csi, cb); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_vsync_callback_obj, py_omv_csi_set_vsync_callback); -static void omv_csi_frame_callback() { +static void omv_csi_frame_callback(void *data) { if (mp_obj_is_callable(frame_callback)) { mp_call_function_0(frame_callback); } } static mp_obj_t py_omv_csi_set_frame_callback(mp_obj_t frame_callback_obj) { + omv_csi_cb_t cb; + omv_csi_t *csi = omv_csi_get(-1); + if (!mp_obj_is_callable(frame_callback_obj)) { frame_callback = mp_const_none; - omv_csi_set_frame_callback(NULL); + cb = (omv_csi_cb_t) { NULL, NULL }; } else { frame_callback = frame_callback_obj; - omv_csi_set_frame_callback(omv_csi_frame_callback); + cb = (omv_csi_cb_t) { omv_csi_frame_callback, NULL }; } + omv_csi_set_frame_callback(csi, cb); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_frame_callback_obj, py_omv_csi_set_frame_callback); static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { + omv_csi_t *csi = omv_csi_get(-1); mp_obj_t ret_obj = mp_const_none; int request = mp_obj_get_int(args[0]); int error = OMV_CSI_ERROR_INVALID_ARGUMENT; @@ -781,14 +846,14 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { MP_ERROR_TEXT("The tuple/list must either be (x, y, w, h) or (w, h)")); } - error = omv_csi_ioctl(request, x, y, w, h); + error = omv_csi_ioctl(csi, request, x, y, w, h); } break; } case OMV_CSI_IOCTL_GET_READOUT_WINDOW: { int x, y, w, h; - error = omv_csi_ioctl(request, &x, &y, &w, &h); + error = omv_csi_ioctl(csi, request, &x, &y, &w, &h); if (error == 0) { ret_obj = mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(x), mp_obj_new_int(y), @@ -802,7 +867,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_SET_FOV_WIDE: case OMV_CSI_IOCTL_SET_NIGHT_MODE: { if (n_args >= 2) { - error = omv_csi_ioctl(request, mp_obj_get_int(args[1])); + error = omv_csi_ioctl(csi, request, mp_obj_get_int(args[1])); } break; } @@ -811,7 +876,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_GET_FOV_WIDE: case OMV_CSI_IOCTL_GET_NIGHT_MODE: { int enabled; - error = omv_csi_ioctl(request, &enabled); + error = omv_csi_ioctl(csi, request, &enabled); if (error == 0) { ret_obj = mp_obj_new_bool(enabled); } @@ -822,18 +887,18 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_TRIGGER_AUTO_FOCUS: case OMV_CSI_IOCTL_PAUSE_AUTO_FOCUS: case OMV_CSI_IOCTL_RESET_AUTO_FOCUS: { - error = omv_csi_ioctl(request); + error = omv_csi_ioctl(csi, request); break; } case OMV_CSI_IOCTL_WAIT_ON_AUTO_FOCUS: { - error = omv_csi_ioctl(request, (n_args < 2) ? 5000 : mp_obj_get_int(args[1])); + error = omv_csi_ioctl(csi, request, (n_args < 2) ? 5000 : mp_obj_get_int(args[1])); break; } #endif case OMV_CSI_IOCTL_LEPTON_GET_WIDTH: { int width; - error = omv_csi_ioctl(request, &width); + error = omv_csi_ioctl(csi, request, &width); if (error == 0) { ret_obj = mp_obj_new_int(width); } @@ -842,7 +907,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_LEPTON_GET_HEIGHT: { int height; - error = omv_csi_ioctl(request, &height); + error = omv_csi_ioctl(csi, request, &height); if (error == 0) { ret_obj = mp_obj_new_int(height); } @@ -851,7 +916,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_LEPTON_GET_RADIOMETRY: { int radiometry; - error = omv_csi_ioctl(request, &radiometry); + error = omv_csi_ioctl(csi, request, &radiometry); if (error == 0) { ret_obj = mp_obj_new_int(radiometry); } @@ -860,7 +925,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_LEPTON_GET_REFRESH: { int refresh; - error = omv_csi_ioctl(request, &refresh); + error = omv_csi_ioctl(csi, request, &refresh); if (error == 0) { ret_obj = mp_obj_new_int(refresh); } @@ -869,7 +934,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_LEPTON_GET_RESOLUTION: { int resolution; - error = omv_csi_ioctl(request, &resolution); + error = omv_csi_ioctl(csi, request, &resolution); if (error == 0) { ret_obj = mp_obj_new_int(resolution); } @@ -878,7 +943,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_LEPTON_RUN_COMMAND: { if (n_args >= 2) { - error = omv_csi_ioctl(request, mp_obj_get_int(args[1])); + error = omv_csi_ioctl(csi, request, mp_obj_get_int(args[1])); } break; } @@ -889,7 +954,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { int command = mp_obj_get_int(args[1]); uint16_t *data = (uint16_t *) mp_obj_str_get_data(args[2], &data_len); PY_ASSERT_TRUE_MSG(data_len > 0, "0 bytes transferred!"); - error = omv_csi_ioctl(request, command, data, data_len / sizeof(uint16_t)); + error = omv_csi_ioctl(csi, request, command, data, data_len / sizeof(uint16_t)); } break; } @@ -900,7 +965,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { size_t data_len = mp_obj_get_int(args[2]); PY_ASSERT_TRUE_MSG(data_len > 0, "0 bytes transferred!"); uint16_t *data = xalloc(data_len * sizeof(uint16_t)); - error = omv_csi_ioctl(request, command, data, data_len); + error = omv_csi_ioctl(csi, request, command, data, data_len); if (error == 0) { ret_obj = mp_obj_new_bytearray_by_ref(data_len * sizeof(uint16_t), data); } @@ -911,7 +976,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_LEPTON_GET_FPA_TEMP: case OMV_CSI_IOCTL_LEPTON_GET_AUX_TEMP: { int temp; - error = omv_csi_ioctl(request, &temp); + error = omv_csi_ioctl(csi, request, &temp); if (error == 0) { ret_obj = mp_obj_new_float((((float) temp) / 100) - 273.15f); } @@ -921,13 +986,13 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_LEPTON_SET_MODE: if (n_args >= 2) { int high_temp = (n_args == 2) ? false : mp_obj_get_int(args[2]); - error = omv_csi_ioctl(request, mp_obj_get_int(args[1]), high_temp); + error = omv_csi_ioctl(csi, request, mp_obj_get_int(args[1]), high_temp); } break; case OMV_CSI_IOCTL_LEPTON_GET_MODE: { int enabled, high_temp; - error = omv_csi_ioctl(request, &enabled, &high_temp); + error = omv_csi_ioctl(csi, request, &enabled, &high_temp); if (error == 0) { ret_obj = mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_bool(enabled), mp_obj_new_bool(high_temp)}); } @@ -939,13 +1004,13 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { // GCC will not let us pass floats to ... so we have to pass float pointers instead. float min = mp_obj_get_float(args[1]); float max = mp_obj_get_float(args[2]); - error = omv_csi_ioctl(request, &min, &max); + error = omv_csi_ioctl(csi, request, &min, &max); } break; case OMV_CSI_IOCTL_LEPTON_GET_RANGE: { float min, max; - error = omv_csi_ioctl(request, &min, &max); + error = omv_csi_ioctl(csi, request, &min, &max); if (error == 0) { ret_obj = mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_float(min), mp_obj_new_float(max)}); } @@ -955,7 +1020,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { #if (OMV_HM01B0_ENABLE == 1) case OMV_CSI_IOCTL_HIMAX_MD_ENABLE: { if (n_args >= 2) { - error = omv_csi_ioctl(request, mp_obj_get_int(args[1])); + error = omv_csi_ioctl(csi, request, mp_obj_get_int(args[1])); } break; } @@ -982,26 +1047,26 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { MP_ERROR_TEXT("The tuple/list must either be (x, y, w, h) or (w, h)")); } - error = omv_csi_ioctl(request, x, y, w, h); + error = omv_csi_ioctl(csi, request, x, y, w, h); } break; } case OMV_CSI_IOCTL_HIMAX_MD_THRESHOLD: { if (n_args >= 2) { - error = omv_csi_ioctl(request, mp_obj_get_int(args[1])); + error = omv_csi_ioctl(csi, request, mp_obj_get_int(args[1])); } break; } case OMV_CSI_IOCTL_HIMAX_MD_CLEAR: { - error = omv_csi_ioctl(request); + error = omv_csi_ioctl(csi, request); break; } case OMV_CSI_IOCTL_HIMAX_OSC_ENABLE: { if (n_args >= 2) { - error = omv_csi_ioctl(request, mp_obj_get_int(args[1])); + error = omv_csi_ioctl(csi, request, mp_obj_get_int(args[1])); } break; } @@ -1009,7 +1074,7 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { case OMV_CSI_IOCTL_GET_RGB_STATS: { uint32_t r, gb, gr, b; - error = omv_csi_ioctl(request, &r, &gb, &gr, &b); + error = omv_csi_ioctl(csi, request, &r, &gb, &gr, &b); if (error == 0) { ret_obj = mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(r), mp_obj_new_int(gb), @@ -1022,21 +1087,21 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { #if (OMV_GENX320_ENABLE == 1) case OMV_CSI_IOCTL_GENX320_SET_BIASES: { if (n_args == 2) { - error = omv_csi_ioctl(request, mp_obj_get_int(args[1])); + error = omv_csi_ioctl(csi, request, mp_obj_get_int(args[1])); } break; } case OMV_CSI_IOCTL_GENX320_SET_BIAS: { if (n_args == 3) { - error = omv_csi_ioctl(request, mp_obj_get_int(args[1]), mp_obj_get_int(args[2])); + error = omv_csi_ioctl(csi, request, mp_obj_get_int(args[1]), mp_obj_get_int(args[2])); } break; } case OMV_CSI_IOCTL_GENX320_SET_AFK: { if (n_args == 2) { - error = omv_csi_ioctl(request, mp_obj_get_int(args[1])); + error = omv_csi_ioctl(csi, request, mp_obj_get_int(args[1])); } else if (n_args == 4) { - error = omv_csi_ioctl(request, mp_obj_get_int(args[1]), mp_obj_get_int(args[2]), + error = omv_csi_ioctl(csi, request, mp_obj_get_int(args[1]), mp_obj_get_int(args[2]), mp_obj_get_int(args[3])); } break; @@ -1058,25 +1123,26 @@ static mp_obj_t py_omv_csi_ioctl(size_t n_args, const mp_obj_t *args) { static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_omv_csi_ioctl_obj, 1, 5, py_omv_csi_ioctl); static mp_obj_t py_omv_csi_set_color_palette(mp_obj_t palette_obj) { + omv_csi_t *csi = omv_csi_get(-1); int palette = mp_obj_get_int(palette_obj); switch (palette) { case COLOR_PALETTE_RAINBOW: - omv_csi_set_color_palette(rainbow_table); + omv_csi_set_color_palette(csi, rainbow_table); break; case COLOR_PALETTE_IRONBOW: - omv_csi_set_color_palette(ironbow_table); + omv_csi_set_color_palette(csi, ironbow_table); break; #if (MICROPY_PY_TOF == 1) case COLOR_PALETTE_DEPTH: - omv_csi_set_color_palette(depth_table); + omv_csi_set_color_palette(csi, depth_table); break; #endif // MICROPY_PY_TOF == 1 #if (OMV_GENX320_ENABLE == 1) case COLOR_PALETTE_EVT_DARK: - omv_csi_set_color_palette(evt_dark_table); + omv_csi_set_color_palette(csi, evt_dark_table); break; case COLOR_PALETTE_EVT_LIGHT: - omv_csi_set_color_palette(evt_light_table); + omv_csi_set_color_palette(csi, evt_light_table); break; #endif // OMV_GENX320_ENABLE == 1 default: @@ -1088,7 +1154,8 @@ static mp_obj_t py_omv_csi_set_color_palette(mp_obj_t palette_obj) { static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_color_palette_obj, py_omv_csi_set_color_palette); static mp_obj_t py_omv_csi_get_color_palette() { - const uint16_t *palette = omv_csi_get_color_palette(); + omv_csi_t *csi = omv_csi_get(-1); + const uint16_t *palette = omv_csi_get_color_palette(csi); if (palette == rainbow_table) { return mp_obj_new_int(COLOR_PALETTE_RAINBOW); } else if (palette == ironbow_table) { @@ -1109,13 +1176,15 @@ static mp_obj_t py_omv_csi_get_color_palette() { static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_color_palette_obj, py_omv_csi_get_color_palette); static mp_obj_t py_omv_csi_write_reg(mp_obj_t addr, mp_obj_t val) { - omv_csi_write_reg(mp_obj_get_int(addr), mp_obj_get_int(val)); + omv_csi_t *csi = omv_csi_get(-1); + omv_csi_write_reg(csi, mp_obj_get_int(addr), mp_obj_get_int(val)); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_2(py_omv_csi_write_reg_obj, py_omv_csi_write_reg); static mp_obj_t py_omv_csi_read_reg(mp_obj_t addr) { - return mp_obj_new_int(omv_csi_read_reg(mp_obj_get_int(addr))); + omv_csi_t *csi = omv_csi_get(-1); + return mp_obj_new_int(omv_csi_read_reg(csi, mp_obj_get_int(addr))); } static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_read_reg_obj, py_omv_csi_read_reg); @@ -1323,6 +1392,5 @@ const mp_obj_module_t omv_csi_module = { .globals = (mp_obj_t) &globals_dict, }; -MP_REGISTER_MODULE(MP_QSTR_csi, omv_csi_module); MP_REGISTER_MODULE(MP_QSTR_sensor, omv_csi_module); #endif // MICROPY_PY_CSI diff --git a/modules/py_csi_ng.c b/modules/py_csi_ng.c new file mode 100644 index 000000000..a189001b4 --- /dev/null +++ b/modules/py_csi_ng.c @@ -0,0 +1,1511 @@ +/* + * Copyright (C) 2023-2024 OpenMV, LLC. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Any redistribution, use, or modification in source or binary form + * is done solely for personal benefit and not for any commercial + * purpose or for monetary gain. For commercial licensing options, + * please contact openmv@openmv.io + * + * THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND COPYRIGHT OWNER "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR COPYRIGHT + * OWNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * CSI Python module. + */ +#include +#include +#include "py/mphal.h" +#include "py/runtime.h" + +#if MICROPY_PY_CSI_NG + +#include "omv_csi.h" +#include "omv_gpio.h" + +#include "imlib.h" +#include "xalloc.h" +#include "py_assert.h" +#include "py_image.h" +#if MICROPY_PY_IMU +#include "py_imu.h" +#endif +#include "omv_boardconfig.h" +#include "omv_i2c.h" +#include "py_helper.h" +#include "framebuffer.h" + +#define omv_csi_raise_error(err) \ + mp_raise_msg(&mp_type_RuntimeError, (mp_rom_error_text_t) omv_csi_strerror(err)) + +#define omv_csi_print_error(op) \ + printf("\x1B[31mWARNING: %s control is not supported by this image sensor.\x1B[0m\n", op); + +typedef struct _py_csi_obj_t { + mp_obj_base_t base; + omv_csi_t *csi; + mp_obj_t vsync_cb; + mp_obj_t frame_cb; + framebuffer_t *fb; // Track dynamically allocated FBs. +} py_csi_obj_t; + +const mp_obj_type_t py_csi_type; + +#if MICROPY_PY_IMU +static void omv_csi_set_rotation(omv_csi_t *csi, int pitch_deadzone, int roll_activezone) { + if (omv_csi_get_auto_rotation(csi)) { + float pitch = py_imu_pitch_rotation(); + if (((pitch <= (90 - pitch_deadzone)) || ((90 + pitch_deadzone) < pitch)) + && ((pitch <= (270 - pitch_deadzone)) || ((270 + pitch_deadzone) < pitch))) { + // disable when 90 or 270 + float roll = py_imu_roll_rotation(); + if (((360 - roll_activezone) <= roll) || (roll < (0 + roll_activezone)) ) { + // center is 0/360, upright + omv_csi_set_hmirror(csi, false); + omv_csi_set_vflip(csi, false); + omv_csi_set_transpose(csi, false); + } else if (((270 - roll_activezone) <= roll) && (roll < (270 + roll_activezone))) { + // center is 270, rotated right + omv_csi_set_hmirror(csi, true); + omv_csi_set_vflip(csi, false); + omv_csi_set_transpose(csi, true); + } else if (((180 - roll_activezone) <= roll) && (roll < (180 + roll_activezone))) { + // center is 180, upside down + omv_csi_set_hmirror(csi, true); + omv_csi_set_vflip(csi, true); + omv_csi_set_transpose(csi, false); + } else if (((90 - roll_activezone) <= roll) && (roll < (90 + roll_activezone))) { + // center is 90, rotated left + omv_csi_set_hmirror(csi, false); + omv_csi_set_vflip(csi, true); + omv_csi_set_transpose(csi, true); + } + } + } +} +#endif // MICROPY_PY_IMU + +static mp_obj_t py_csi_devices() { + mp_obj_t dev_list = mp_obj_new_list(0, NULL); + + for (size_t i=0; idetected) { + mp_obj_list_append(dev_list, mp_obj_new_int(csi->chip_id)); + } + } + + return dev_list; +} +static MP_DEFINE_CONST_FUN_OBJ_0(py_csi_devices_obj, py_csi_devices); + +static mp_obj_t py_csi_deinit(mp_obj_t self_in) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Abort any ongoing capture. + omv_csi_abort(self->csi, true, false); + + // Reset FB pointer (realloc'd in make_new). + if (self->csi->fb->dynamic) { + self->fb = NULL; + self->csi->fb = NULL; + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_deinit_obj, py_csi_deinit); + +static mp_obj_t py_csi_reset(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_hard }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_hard, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, + }; + + // Parse args. + py_csi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int error = omv_csi_reset(self->csi, args[ARG_hard].u_bool); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_csi_reset_obj, 1, py_csi_reset); + +static mp_obj_t py_csi_shutdown(mp_obj_t self_in, mp_obj_t enable) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + int error = omv_csi_shutdown(self->csi, mp_obj_is_true(enable)); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(py_csi_shutdown_obj, py_csi_shutdown); + +static mp_obj_t py_csi_sleep(mp_obj_t self_in, mp_obj_t enable) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + int error = omv_csi_sleep(self->csi, mp_obj_is_true(enable)); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(py_csi_sleep_obj, py_csi_sleep); + +static mp_obj_t py_csi_flush(mp_obj_t self_in) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + framebuffer_update_jpeg_buffer(self->csi->fb); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_flush_obj, py_csi_flush); + +static mp_obj_t py_csi_snapshot(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_time, ARG_frames, ARG_update, ARG_blocking, ARG_image }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_time, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1} }, + { MP_QSTR_frames, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1} }, + { MP_QSTR_update, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, + { MP_QSTR_blocking, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, + { MP_QSTR_image, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + }; + + py_csi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + uint32_t flags = 0; + image_t image = {0}; + mp_int_t time = args[ARG_time].u_int; + mp_int_t frames = args[ARG_frames].u_int; + + if (args[ARG_update].u_bool) { + flags |= OMV_CSI_CAPTURE_FLAGS_UPDATE; + } + + if (!args[ARG_blocking].u_bool) { + flags |= OMV_CSI_CAPTURE_FLAGS_NBLOCK; + } + + if (time == -1 && frames == -1) { + int error = omv_csi_snapshot(self->csi, &image, flags); + if (error != 0) { + if (error == OMV_CSI_ERROR_WOULD_BLOCK && + (flags & OMV_CSI_CAPTURE_FLAGS_NBLOCK)) { + return mp_const_none; + } + omv_csi_raise_error(error); + } + + // If an image is provided update it and return. + if (args[ARG_image].u_obj != mp_const_none) { + image_copy(py_helper_arg_to_image(args[ARG_image].u_obj, ARG_IMAGE_ANY), &image, true); + return mp_const_none; + } + + #if MICROPY_PY_IMU + // +-10 degree dead-zone around pitch 90/270. + // +-35 degree active-zone around roll 0/90/180/270/360. + omv_csi_set_rotation(self->csi, 10, 35); + #endif // MICROPY_PY_IMU + + return py_image_from_struct(&image); + } else { + uint32_t millis = mp_hal_ticks_ms(); + + while (time != -1 || frames != -1) { + if (frames != -1 && frames-- < 0) { + break; + } + + if (time != -1 && mp_hal_ticks_ms() - millis > time) { + break; + } + + int error = omv_csi_snapshot(self->csi, NULL, flags); + if (error != 0) { + omv_csi_raise_error(error); + } + } + + return mp_const_none; + } +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_csi_snapshot_obj, 1, py_csi_snapshot); + +static mp_obj_t py_csi_width(mp_obj_t self_in) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return mp_obj_new_int(resolution[self->csi->framesize][0]); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_width_obj, py_csi_width); + +static mp_obj_t py_csi_height(mp_obj_t self_in) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return mp_obj_new_int(resolution[self->csi->framesize][1]); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_height_obj, py_csi_height); + +static mp_obj_t py_csi_fb(mp_obj_t self_in) { + image_t image; + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (framebuffer_get_depth(self->csi->fb) < 0) { + return mp_const_none; + } + + framebuffer_init_image(self->csi->fb, &image); + return py_image_from_struct(&image); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_fb_obj, py_csi_fb); + +static mp_obj_t py_csi_cid(mp_obj_t self_in) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return mp_obj_new_int(self->csi->chip_id); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_cid_obj, py_csi_cid); + +static mp_obj_t py_csi_readable(mp_obj_t self_in) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + vbuffer_t *head = framebuffer_get_head(self->csi->fb, FB_PEEK); + return mp_obj_new_bool(head != NULL); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_readable_obj, py_csi_readable); + +static mp_obj_t py_csi_pixformat(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + if (self->csi->pixformat == PIXFORMAT_INVALID) { + omv_csi_raise_error(OMV_CSI_ERROR_INVALID_PIXFORMAT); + } + return mp_obj_new_int(self->csi->pixformat); + } + + int error = omv_csi_set_pixformat(self->csi, mp_obj_get_int(args[1])); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_pixformat_obj, 1, 2, py_csi_pixformat); + +static mp_obj_t py_csi_framesize(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + if (self->csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { + omv_csi_raise_error(OMV_CSI_ERROR_INVALID_FRAMESIZE); + } + return mp_obj_new_int(self->csi->framesize); + } + + int error = omv_csi_set_framesize(self->csi, mp_obj_get_int(args[1])); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_framesize_obj, 1, 2, py_csi_framesize); + +static mp_obj_t py_csi_framerate(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + if (self->csi->framerate == 0) { + omv_csi_raise_error(OMV_CSI_ERROR_INVALID_FRAMERATE); + } + + return mp_obj_new_int(self->csi->framerate); + } + + int error = omv_csi_set_framerate(self->csi, mp_obj_get_int(args[1])); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_framerate_obj, 1, 2, py_csi_framerate); + +static mp_obj_t py_csi_window(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (self->csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { + omv_csi_raise_error(OMV_CSI_ERROR_INVALID_FRAMESIZE); + } + + if (n_args == 1) { + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(framebuffer_get_x(self->csi->fb)), + mp_obj_new_int(framebuffer_get_y(self->csi->fb)), + mp_obj_new_int(framebuffer_get_u(self->csi->fb)), + mp_obj_new_int(framebuffer_get_v(self->csi->fb))}); + } + + mp_obj_t *array; + mp_uint_t array_len; + mp_obj_get_array(args[1], &array_len, &array); + + rectangle_t r; + rectangle_t t = { + .x = 0, + .y = 0, + .w = resolution[self->csi->framesize][0], + .h = resolution[self->csi->framesize][1], + }; + + if (array_len == 2) { + r.w = mp_obj_get_int(array[0]); + r.h = mp_obj_get_int(array[1]); + r.x = (t.w / 2) - (r.w / 2); + r.y = (t.h / 2) - (r.h / 2); + } else if (array_len == 4) { + r.x = mp_obj_get_int(array[0]); + r.y = mp_obj_get_int(array[1]); + r.w = mp_obj_get_int(array[2]); + r.h = mp_obj_get_int(array[3]); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected (w, h) or (x, y, w, h) tuple/list.")); + } + + if ((r.w < 1) || (r.h < 1)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid ROI dimensions!")); + } + + if (!rectangle_overlap(&r, &t)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("ROI does not overlap on the image!")); + } + + rectangle_intersected(&r, &t); + + int error = omv_csi_set_windowing(self->csi, r.x, r.y, r.w, r.h); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_window_obj, 1, 2, py_csi_window); + +static mp_obj_t py_csi_gainceiling(mp_obj_t self_in, mp_obj_t gainceiling) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + omv_csi_gainceiling_t gain; + + switch (mp_obj_get_int(gainceiling)) { + case 2: + gain = OMV_CSI_GAINCEILING_2X; + break; + case 4: + gain = OMV_CSI_GAINCEILING_4X; + break; + case 8: + gain = OMV_CSI_GAINCEILING_8X; + break; + case 16: + gain = OMV_CSI_GAINCEILING_16X; + break; + case 32: + gain = OMV_CSI_GAINCEILING_32X; + break; + case 64: + gain = OMV_CSI_GAINCEILING_64X; + break; + case 128: + gain = OMV_CSI_GAINCEILING_128X; + break; + default: + omv_csi_raise_error(OMV_CSI_ERROR_INVALID_ARGUMENT); + break; + } + + if (omv_csi_set_gainceiling(self->csi, gain) != 0) { + return mp_const_false; + } + + return mp_const_true; +} +static MP_DEFINE_CONST_FUN_OBJ_2(py_csi_gainceiling_obj, py_csi_gainceiling); + +static mp_obj_t py_csi_brightness(mp_obj_t self_in, mp_obj_t brightness) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (omv_csi_set_brightness(self->csi, mp_obj_get_int(brightness)) != 0) { + return mp_const_false; + } + + return mp_const_true; +} +static MP_DEFINE_CONST_FUN_OBJ_2(py_csi_brightness_obj, py_csi_brightness); + +static mp_obj_t py_csi_contrast(mp_obj_t self_in, mp_obj_t contrast) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (omv_csi_set_contrast(self->csi, mp_obj_get_int(contrast)) != 0) { + return mp_const_false; + } + + return mp_const_true; +} +static MP_DEFINE_CONST_FUN_OBJ_2(py_csi_contrast_obj, py_csi_contrast); + +static mp_obj_t py_csi_saturation(mp_obj_t self_in, mp_obj_t saturation) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (omv_csi_set_saturation(self->csi, mp_obj_get_int(saturation)) != 0) { + return mp_const_false; + } + + return mp_const_true; +} +static MP_DEFINE_CONST_FUN_OBJ_2(py_csi_saturation_obj, py_csi_saturation); + +static mp_obj_t py_csi_quality(mp_obj_t self_in, mp_obj_t qs) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + int q = mp_obj_get_int(qs); + PY_ASSERT_TRUE((q >= 0 && q <= 100)); + + // Invert and map from 0 to 255 + q = 255 * (100 - q) / 100; + + if (omv_csi_set_quality(self->csi, q) != 0) { + return mp_const_false; + } + + return mp_const_true; +} +static MP_DEFINE_CONST_FUN_OBJ_2(py_csi_quality_obj, py_csi_quality); + +static mp_obj_t py_csi_colorbar(mp_obj_t self_in, mp_obj_t enable) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (omv_csi_set_colorbar(self->csi, mp_obj_is_true(enable)) != 0) { + return mp_const_false; + } + + return mp_const_true; +} +static MP_DEFINE_CONST_FUN_OBJ_2(py_csi_colorbar_obj, py_csi_colorbar); + +static mp_obj_t py_csi_auto_gain(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_enable, ARG_gain_db, ARG_gain_db_ceiling }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_enable, MP_ARG_BOOL | MP_ARG_REQUIRED }, + { MP_QSTR_gain_db, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_gain_db_ceiling, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + py_csi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + bool enable = args[ARG_enable].u_bool; + float gain_db = py_helper_arg_to_float(args[ARG_gain_db].u_obj, NAN); + float gain_db_ceiling = py_helper_arg_to_float(args[ARG_gain_db_ceiling].u_obj, NAN); + + int error = omv_csi_set_auto_gain(self->csi, enable, gain_db, gain_db_ceiling); + if (error != 0) { + if (error != OMV_CSI_ERROR_CTL_UNSUPPORTED) { + omv_csi_raise_error(error); + } + omv_csi_print_error("Auto Gain"); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_csi_auto_gain_obj, 1, py_csi_auto_gain); + +static mp_obj_t py_csi_gain_db(mp_obj_t self_in) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + float gain_db; + + int error = omv_csi_get_gain_db(self->csi, &gain_db); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_obj_new_float(gain_db); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_gain_db_obj, py_csi_gain_db); + +static mp_obj_t py_csi_auto_exposure(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_enable, ARG_exposure_us }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_enable, MP_ARG_BOOL | MP_ARG_REQUIRED }, + { MP_QSTR_exposure_us, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1} }, + }; + + // Parse args. + py_csi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int error = omv_csi_set_auto_exposure(self->csi, args[ARG_enable].u_bool, args[ARG_exposure_us].u_int); + if (error != 0) { + if (error != OMV_CSI_ERROR_CTL_UNSUPPORTED) { + omv_csi_raise_error(error); + } + omv_csi_print_error("Auto Exposure"); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_csi_auto_exposure_obj, 1, py_csi_auto_exposure); + +static mp_obj_t py_csi_exposure_us(mp_obj_t self_in) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + int exposure_us; + + int error = omv_csi_get_exposure_us(self->csi, &exposure_us); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_obj_new_int(exposure_us); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_exposure_us_obj, py_csi_exposure_us); + +static mp_obj_t py_csi_auto_whitebal(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_enable, ARG_rgb_gain_db }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_enable, MP_ARG_BOOL | MP_ARG_REQUIRED }, + { MP_QSTR_rgb_gain_db, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, + }; + + // Parse args. + py_csi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + bool enable = args[ARG_enable].u_bool; + float rgb_gain_db[3] = {NAN, NAN, NAN}; + py_helper_arg_to_float_array(args[ARG_rgb_gain_db].u_obj, rgb_gain_db, 3); + + int error = omv_csi_set_auto_whitebal(self->csi, enable, rgb_gain_db[0], rgb_gain_db[1], rgb_gain_db[2]); + if (error != 0) { + if (error != OMV_CSI_ERROR_CTL_UNSUPPORTED) { + omv_csi_raise_error(error); + } + omv_csi_print_error("Auto White Balance"); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_csi_auto_whitebal_obj, 1, py_csi_auto_whitebal); + +static mp_obj_t py_csi_rgb_gain_db(mp_obj_t self_in) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + float r_gain_db = 0.0, g_gain_db = 0.0, b_gain_db = 0.0; + + int error = omv_csi_get_rgb_gain_db(self->csi, &r_gain_db, &g_gain_db, &b_gain_db); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_obj_new_tuple(3, (mp_obj_t []) { + mp_obj_new_float(r_gain_db), + mp_obj_new_float(g_gain_db), + mp_obj_new_float(b_gain_db) + }); +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_rgb_gain_db_obj, py_csi_rgb_gain_db); + +static mp_obj_t py_csi_auto_blc(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_enable, ARG_regs }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_enable, MP_ARG_BOOL | MP_ARG_REQUIRED }, + { MP_QSTR_regs, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + }; + + py_csi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int regs[self->csi->blc_size]; + int enable = args[ARG_enable].u_bool; + bool regs_present = args[ARG_regs].u_obj != mp_const_none; + + if (regs_present) { + mp_obj_t *arg_array; + mp_obj_get_array_fixed_n(args[ARG_regs].u_obj, self->csi->blc_size, &arg_array); + for (uint32_t i = 0; i < self->csi->blc_size; i++) { + regs[i] = mp_obj_get_int(arg_array[i]); + } + } + + int error = omv_csi_set_auto_blc(self->csi, enable, regs_present ? regs : NULL); + if (error != 0) { + if (error != OMV_CSI_ERROR_CTL_UNSUPPORTED) { + omv_csi_raise_error(error); + } + omv_csi_print_error("Auto BLC"); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_csi_auto_blc_obj, 1, py_csi_auto_blc); + +static mp_obj_t py_csi_blc_regs(mp_obj_t self_in) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + int regs[self->csi->blc_size]; + + int error = omv_csi_get_blc_regs(self->csi, regs); + if (error != 0) { + omv_csi_raise_error(error); + } + + mp_obj_list_t *l = mp_obj_new_list(self->csi->blc_size, NULL); + for (uint32_t i = 0; i < self->csi->blc_size; i++) { + l->items[i] = mp_obj_new_int(regs[i]); + } + + return l; +} +static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_blc_regs_obj, py_csi_blc_regs); + +static mp_obj_t py_csi_hmirror(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + return mp_obj_new_bool(omv_csi_get_hmirror(self->csi)); + } + + int error = omv_csi_set_hmirror(self->csi, mp_obj_is_true(args[1])); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_hmirror_obj, 1, 2, py_csi_hmirror); + +static mp_obj_t py_csi_vflip(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + return mp_obj_new_bool(omv_csi_get_vflip(self->csi)); + } + + int error = omv_csi_set_vflip(self->csi, mp_obj_is_true(args[1])); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_vflip_obj, 1, 2, py_csi_vflip); + +static mp_obj_t py_csi_transpose(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + return mp_obj_new_bool(omv_csi_get_transpose(self->csi)); + } + + int error = omv_csi_set_transpose(self->csi, mp_obj_is_true(args[1])); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_transpose_obj, 1, 2, py_csi_transpose); + +static mp_obj_t py_csi_auto_rotation(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + return mp_obj_new_bool(omv_csi_get_auto_rotation(self->csi)); + } + + int error = omv_csi_set_auto_rotation(self->csi, mp_obj_is_true(args[1])); + if (error != 0) { + omv_csi_raise_error(error); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_auto_rotation_obj, 1, 2, py_csi_auto_rotation); + +static mp_obj_t py_csi_framebuffers(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + framebuffer_t *fb = self->csi->fb; + + if (n_args == 1) { + return mp_obj_new_int(fb->n_buffers); + } + + mp_int_t num = mp_obj_get_int(args[1]); + + if (num < 1) { + omv_csi_raise_error(OMV_CSI_ERROR_INVALID_ARGUMENT); + } + + if (num != fb->n_buffers) { + int error = omv_csi_set_framebuffers(self->csi, num); + if (error != 0) { + omv_csi_raise_error(error); + } + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_framebuffers_obj, 1, 2, py_csi_framebuffers); + +static mp_obj_t py_csi_special_effect(mp_obj_t self_in, mp_obj_t sde) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (omv_csi_set_special_effect(self->csi, mp_obj_get_int(sde)) != 0) { + return mp_const_false; + } + + return mp_const_true; +} +static MP_DEFINE_CONST_FUN_OBJ_2(py_csi_special_effect_obj, py_csi_special_effect); + +static mp_obj_t py_csi_lens_correction(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_enable, ARG_radi, ARG_coef }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_enable, MP_ARG_BOOL | MP_ARG_REQUIRED }, + { MP_QSTR_radi, MP_ARG_BOOL | MP_ARG_REQUIRED }, + { MP_QSTR_coef, MP_ARG_BOOL | MP_ARG_REQUIRED }, + }; + + py_csi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (omv_csi_set_lens_correction(self->csi, + args[ARG_enable].u_bool, + args[ARG_radi].u_int, + args[ARG_coef].u_int) != 0) { + return mp_const_false; + } + return mp_const_true; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_csi_lens_correction_obj, 1, py_csi_lens_correction); + +static void omv_csi_vsync_callback(void *data) { + py_csi_obj_t *self = data; + + if (mp_obj_is_callable(self->vsync_cb)) { + uint32_t vsync_state = 0; + #ifdef OMV_CSI_VSYNC_PIN + vsync_state = omv_gpio_read(OMV_CSI_VSYNC_PIN); + #endif + mp_call_function_1(self->vsync_cb, mp_obj_new_int(vsync_state)); + } +} + +static mp_obj_t py_csi_vsync_callback(size_t n_args, const mp_obj_t *args) { + omv_csi_cb_t cb; + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + return self->vsync_cb; + } + + if (!mp_obj_is_callable(args[1])) { + self->vsync_cb = mp_const_none; + cb = (omv_csi_cb_t) { NULL, NULL }; + } else { + self->vsync_cb = args[1]; + cb = (omv_csi_cb_t) { .fun = omv_csi_vsync_callback, .arg = self }; + } + + omv_csi_set_vsync_callback(self->csi, cb); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_vsync_callback_obj, 1, 2, py_csi_vsync_callback); + +static void omv_csi_frame_callback(void *data) { + py_csi_obj_t *self = data; + + if (mp_obj_is_callable(self->frame_cb)) { + mp_call_function_0(self->frame_cb); + } +} + +static mp_obj_t py_csi_frame_callback(size_t n_args, const mp_obj_t *args) { + omv_csi_cb_t cb; + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + return self->frame_cb; + } + + if (!mp_obj_is_callable(args[1])) { + self->frame_cb = mp_const_none; + cb = (omv_csi_cb_t) { NULL, NULL }; + } else { + self->frame_cb = args[1]; + cb = (omv_csi_cb_t) { .fun = omv_csi_frame_callback, .arg = self }; + } + + omv_csi_set_frame_callback(self->csi, cb); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_frame_callback_obj, 1, 2, py_csi_frame_callback); + +static mp_obj_t py_csi_ioctl(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + int request = mp_obj_get_int(args[1]); + + mp_obj_t ret_obj = mp_const_none; + int error = OMV_CSI_ERROR_INVALID_ARGUMENT; + + // Skip self + request so ioctl args start at args[0]. + args += 2; + n_args -= 2; + + switch (request) { + case OMV_CSI_IOCTL_SET_READOUT_WINDOW: { + if (n_args == 1) { + int x, y, w, h; + mp_obj_t *array; + mp_uint_t array_len; + mp_obj_get_array(args[0], &array_len, &array); + + if (array_len == 4) { + x = mp_obj_get_int(array[0]); + y = mp_obj_get_int(array[1]); + w = mp_obj_get_int(array[2]); + h = mp_obj_get_int(array[3]); + } else if (array_len == 2) { + w = mp_obj_get_int(array[0]); + h = mp_obj_get_int(array[1]); + x = 0; + y = 0; + } else { + mp_raise_msg(&mp_type_ValueError, + MP_ERROR_TEXT("Expected (w, h) or (x, y, w, h) tuple/list.")); + } + + error = omv_csi_ioctl(self->csi, request, x, y, w, h); + } + break; + } + + case OMV_CSI_IOCTL_GET_READOUT_WINDOW: { + int x, y, w, h; + error = omv_csi_ioctl(self->csi, request, &x, &y, &w, &h); + if (error == 0) { + ret_obj = mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(x), + mp_obj_new_int(y), + mp_obj_new_int(w), + mp_obj_new_int(h)}); + } + break; + } + + case OMV_CSI_IOCTL_SET_TRIGGERED_MODE: + case OMV_CSI_IOCTL_SET_FOV_WIDE: + case OMV_CSI_IOCTL_SET_NIGHT_MODE: { + if (n_args == 1) { + error = omv_csi_ioctl(self->csi, request, mp_obj_get_int(args[0])); + } + break; + } + + case OMV_CSI_IOCTL_GET_TRIGGERED_MODE: + case OMV_CSI_IOCTL_GET_FOV_WIDE: + case OMV_CSI_IOCTL_GET_NIGHT_MODE: { + int enabled; + error = omv_csi_ioctl(self->csi, request, &enabled); + if (error == 0) { + ret_obj = mp_obj_new_bool(enabled); + } + break; + } + + #if (OMV_OV5640_AF_ENABLE == 1) + case OMV_CSI_IOCTL_TRIGGER_AUTO_FOCUS: + case OMV_CSI_IOCTL_PAUSE_AUTO_FOCUS: + case OMV_CSI_IOCTL_RESET_AUTO_FOCUS: { + error = omv_csi_ioctl(self->csi, request); + break; + } + case OMV_CSI_IOCTL_WAIT_ON_AUTO_FOCUS: { + error = omv_csi_ioctl(self->csi, request, (n_args < 1) ? 5000 : mp_obj_get_int(args[0])); + break; + } + #endif + + case OMV_CSI_IOCTL_LEPTON_GET_WIDTH: { + int width; + error = omv_csi_ioctl(self->csi, request, &width); + if (error == 0) { + ret_obj = mp_obj_new_int(width); + } + break; + } + + case OMV_CSI_IOCTL_LEPTON_GET_HEIGHT: { + int height; + error = omv_csi_ioctl(self->csi, request, &height); + if (error == 0) { + ret_obj = mp_obj_new_int(height); + } + break; + } + + case OMV_CSI_IOCTL_LEPTON_GET_RADIOMETRY: { + int radiometry; + error = omv_csi_ioctl(self->csi, request, &radiometry); + if (error == 0) { + ret_obj = mp_obj_new_int(radiometry); + } + break; + } + + case OMV_CSI_IOCTL_LEPTON_GET_REFRESH: { + int refresh; + error = omv_csi_ioctl(self->csi, request, &refresh); + if (error == 0) { + ret_obj = mp_obj_new_int(refresh); + } + break; + } + + case OMV_CSI_IOCTL_LEPTON_GET_RESOLUTION: { + int resolution; + error = omv_csi_ioctl(self->csi, request, &resolution); + if (error == 0) { + ret_obj = mp_obj_new_int(resolution); + } + break; + } + + case OMV_CSI_IOCTL_LEPTON_RUN_COMMAND: { + if (n_args == 1) { + error = omv_csi_ioctl(self->csi, request, mp_obj_get_int(args[0])); + } + break; + } + + case OMV_CSI_IOCTL_LEPTON_SET_ATTRIBUTE: { + if (n_args == 2) { + size_t data_len; + int command = mp_obj_get_int(args[0]); + uint16_t *data = (uint16_t *) mp_obj_str_get_data(args[1], &data_len); + PY_ASSERT_TRUE_MSG(data_len > 0, "0 bytes transferred!"); + error = omv_csi_ioctl(self->csi, request, command, data, data_len / sizeof(uint16_t)); + } + break; + } + + case OMV_CSI_IOCTL_LEPTON_GET_ATTRIBUTE: { + if (n_args == 2) { + int command = mp_obj_get_int(args[0]); + size_t data_len = mp_obj_get_int(args[1]); + PY_ASSERT_TRUE_MSG(data_len > 0, "0 bytes transferred!"); + uint16_t *data = xalloc(data_len * sizeof(uint16_t)); + error = omv_csi_ioctl(self->csi, request, command, data, data_len); + if (error == 0) { + ret_obj = mp_obj_new_bytearray_by_ref(data_len * sizeof(uint16_t), data); + } + } + break; + } + + case OMV_CSI_IOCTL_LEPTON_GET_FPA_TEMP: + case OMV_CSI_IOCTL_LEPTON_GET_AUX_TEMP: { + int temp; + error = omv_csi_ioctl(self->csi, request, &temp); + if (error == 0) { + ret_obj = mp_obj_new_float((((float) temp) / 100) - 273.15f); + } + break; + } + + case OMV_CSI_IOCTL_LEPTON_SET_MODE: + if (n_args == 2) { + int high_temp = (n_args == 2) ? false : mp_obj_get_int(args[1]); + error = omv_csi_ioctl(self->csi, request, mp_obj_get_int(args[0]), high_temp); + } + break; + + case OMV_CSI_IOCTL_LEPTON_GET_MODE: { + int enabled, high_temp; + error = omv_csi_ioctl(self->csi, request, &enabled, &high_temp); + if (error == 0) { + ret_obj = mp_obj_new_tuple(2, (mp_obj_t []) { + mp_obj_new_bool(enabled), mp_obj_new_bool(high_temp) + }); + } + break; + } + + case OMV_CSI_IOCTL_LEPTON_SET_RANGE: + if (n_args == 2) { + // GCC will not let us pass floats to ... so we have to pass float pointers instead. + float min = mp_obj_get_float(args[0]); + float max = mp_obj_get_float(args[1]); + error = omv_csi_ioctl(self->csi, request, &min, &max); + } + break; + + case OMV_CSI_IOCTL_LEPTON_GET_RANGE: { + float min, max; + error = omv_csi_ioctl(self->csi, request, &min, &max); + if (error == 0) { + ret_obj = mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_float(min), mp_obj_new_float(max)}); + } + break; + } + + #if (OMV_HM01B0_ENABLE == 1) + case OMV_CSI_IOCTL_HIMAX_MD_ENABLE: { + if (n_args == 1) { + error = omv_csi_ioctl(self->csi, request, mp_obj_get_int(args[0])); + } + break; + } + + case OMV_CSI_IOCTL_HIMAX_MD_WINDOW: { + if (n_args == 1) { + int x, y, w, h; + mp_obj_t *array; + mp_uint_t array_len; + mp_obj_get_array(args[0], &array_len, &array); + + if (array_len == 4) { + x = mp_obj_get_int(array[0]); + y = mp_obj_get_int(array[1]); + w = mp_obj_get_int(array[2]); + h = mp_obj_get_int(array[3]); + } else if (array_len == 2) { + w = mp_obj_get_int(array[0]); + h = mp_obj_get_int(array[1]); + x = 0; + y = 0; + } else { + mp_raise_msg(&mp_type_ValueError, + MP_ERROR_TEXT("The tuple/list must either be (x, y, w, h) or (w, h)")); + } + + error = omv_csi_ioctl(self->csi, request, x, y, w, h); + } + break; + } + + case OMV_CSI_IOCTL_HIMAX_MD_THRESHOLD: { + if (n_args == 1) { + error = omv_csi_ioctl(self->csi, request, mp_obj_get_int(args[0])); + } + break; + } + + case OMV_CSI_IOCTL_HIMAX_MD_CLEAR: { + error = omv_csi_ioctl(self->csi, request); + break; + } + + case OMV_CSI_IOCTL_HIMAX_OSC_ENABLE: { + if (n_args == 1) { + error = omv_csi_ioctl(self->csi, request, mp_obj_get_int(args[0])); + } + break; + } + #endif // (OMV_HM01B0_ENABLE == 1) + + case OMV_CSI_IOCTL_GET_RGB_STATS: { + uint32_t r, gb, gr, b; + error = omv_csi_ioctl(self->csi, request, &r, &gb, &gr, &b); + if (error == 0) { + ret_obj = mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(r), + mp_obj_new_int(gb), + mp_obj_new_int(gr), + mp_obj_new_int(b)}); + } + break; + } + + #if (OMV_GENX320_ENABLE == 1) + case OMV_CSI_IOCTL_GENX320_SET_BIASES: { + if (n_args == 1) { + error = omv_csi_ioctl(self->csi, request, mp_obj_get_int(args[0])); + } + break; + } + case OMV_CSI_IOCTL_GENX320_SET_BIAS: { + if (n_args == 2) { + error = omv_csi_ioctl(self->csi, + request, + mp_obj_get_int(args[0]), + mp_obj_get_int(args[1])); + } + break; + } + case OMV_CSI_IOCTL_GENX320_SET_AFK: { + if (n_args == 1) { + error = omv_csi_ioctl(self->csi, request, mp_obj_get_int(args[0])); + } else if (n_args == 3) { + error = omv_csi_ioctl(self->csi, + request, + mp_obj_get_int(args[0]), + mp_obj_get_int(args[1]), + mp_obj_get_int(args[2])); + } + break; + } + #endif // (OMV_GENX320_ENABLE == 1) + + default: { + omv_csi_raise_error(OMV_CSI_ERROR_CTL_UNSUPPORTED); + break; + } + } + + if (error != 0) { + omv_csi_raise_error(error); + } + + return ret_obj; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_ioctl_obj, 2, 5, py_csi_ioctl); + +static mp_obj_t py_csi_color_palette(size_t n_args, const mp_obj_t *args) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + const uint16_t *palette = omv_csi_get_color_palette(self->csi); + if (palette == rainbow_table) { + return mp_obj_new_int(COLOR_PALETTE_RAINBOW); + } else if (palette == ironbow_table) { + return mp_obj_new_int(COLOR_PALETTE_IRONBOW); + #if (MICROPY_PY_TOF == 1) + } else if (palette == depth_table) { + return mp_obj_new_int(COLOR_PALETTE_DEPTH); + #endif // MICROPY_PY_TOF == 1 + #if (OMV_GENX320_ENABLE == 1) + } else if (palette == evt_dark_table) { + return mp_obj_new_int(COLOR_PALETTE_EVT_DARK); + } else if (palette == evt_light_table) { + return mp_obj_new_int(COLOR_PALETTE_EVT_LIGHT); + #endif // OMV_GENX320_ENABLE == 1 + } + } else { + int palette = mp_obj_get_int(args[1]); + switch (palette) { + case COLOR_PALETTE_RAINBOW: + omv_csi_set_color_palette(self->csi, rainbow_table); + break; + case COLOR_PALETTE_IRONBOW: + omv_csi_set_color_palette(self->csi, ironbow_table); + break; + #if (MICROPY_PY_TOF == 1) + case COLOR_PALETTE_DEPTH: + omv_csi_set_color_palette(self->csi, depth_table); + break; + #endif // MICROPY_PY_TOF == 1 + #if (OMV_GENX320_ENABLE == 1) + case COLOR_PALETTE_EVT_DARK: + omv_csi_set_color_palette(self->csi, evt_dark_table); + break; + case COLOR_PALETTE_EVT_LIGHT: + omv_csi_set_color_palette(self->csi, evt_light_table); + break; + #endif // OMV_GENX320_ENABLE == 1 + default: + omv_csi_raise_error(OMV_CSI_ERROR_INVALID_ARGUMENT); + break; + } + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_color_palette_obj, 1, 2, py_csi_color_palette); + +static mp_obj_t py_csi_write_reg(mp_obj_t self_in, mp_obj_t addr, mp_obj_t val) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + omv_csi_write_reg(self->csi, mp_obj_get_int(addr), mp_obj_get_int(val)); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_3(py_csi_write_reg_obj, py_csi_write_reg); + +static mp_obj_t py_csi_read_reg(mp_obj_t self_in, mp_obj_t addr) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return mp_obj_new_int(omv_csi_read_reg(self->csi, mp_obj_get_int(addr))); +} +static MP_DEFINE_CONST_FUN_OBJ_2(py_csi_read_reg_obj, py_csi_read_reg); + +mp_obj_t py_csi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_id, ARG_delays, ARG_fflush, ARG_fb_size }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_cid, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = -1 } }, + { MP_QSTR_delays, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, + { MP_QSTR_fflush, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, + { MP_QSTR_fb_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 2 * 1024 * 1024} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + omv_csi_t *csi = omv_csi_get(args[ARG_id].u_int); + + if (!csi || !csi->detected) { + omv_csi_raise_error(OMV_CSI_ERROR_ISC_UNDETECTED); + } + + py_csi_obj_t *self = mp_obj_malloc_with_finaliser(py_csi_obj_t, &py_csi_type); + self->csi = csi; + self->frame_cb = mp_const_none; + self->vsync_cb = mp_const_none; + csi->disable_delays = !args[ARG_delays].u_bool; + csi->disable_full_flush = !args[ARG_fflush].u_bool; + + if (csi->fb == NULL) { + size_t fb_size = args[ARG_fb_size].u_int; + csi->fb = (framebuffer_t *) m_new(uint8_t, fb_size + sizeof(framebuffer_t)); + framebuffer_init_fb(csi->fb, fb_size, true); + self->fb = csi->fb; // Track GC heap alloc + } + + #if MICROPY_PY_IMU + // +-10 degree dead-zone around pitch 90/270. + // +-45 degree active-zone around roll 0/90/180/270/360. + omv_csi_set_rotation(self->csi, 10, 45); + #endif // MICROPY_PY_IMU + + return MP_OBJ_FROM_PTR(self); +} + +static const mp_rom_map_elem_t py_csi_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_CSI) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&py_csi_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&py_csi_reset_obj) }, + { MP_ROM_QSTR(MP_QSTR_shutdown), MP_ROM_PTR(&py_csi_shutdown_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&py_csi_sleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&py_csi_flush_obj) }, + { MP_ROM_QSTR(MP_QSTR_snapshot), MP_ROM_PTR(&py_csi_snapshot_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_csi_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_csi_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_fb), MP_ROM_PTR(&py_csi_fb_obj) }, + { MP_ROM_QSTR(MP_QSTR_cid), MP_ROM_PTR(&py_csi_cid_obj) }, + { MP_ROM_QSTR(MP_QSTR_readable), MP_ROM_PTR(&py_csi_readable_obj) }, + { MP_ROM_QSTR(MP_QSTR_pixformat), MP_ROM_PTR(&py_csi_pixformat_obj) }, + { MP_ROM_QSTR(MP_QSTR_framesize), MP_ROM_PTR(&py_csi_framesize_obj) }, + { MP_ROM_QSTR(MP_QSTR_framerate), MP_ROM_PTR(&py_csi_framerate_obj) }, + { MP_ROM_QSTR(MP_QSTR_window), MP_ROM_PTR(&py_csi_window_obj) }, + { MP_ROM_QSTR(MP_QSTR_gainceiling), MP_ROM_PTR(&py_csi_gainceiling_obj) }, + { MP_ROM_QSTR(MP_QSTR_contrast), MP_ROM_PTR(&py_csi_contrast_obj) }, + { MP_ROM_QSTR(MP_QSTR_brightness), MP_ROM_PTR(&py_csi_brightness_obj) }, + { MP_ROM_QSTR(MP_QSTR_saturation), MP_ROM_PTR(&py_csi_saturation_obj) }, + { MP_ROM_QSTR(MP_QSTR_quality), MP_ROM_PTR(&py_csi_quality_obj) }, + { MP_ROM_QSTR(MP_QSTR_colorbar), MP_ROM_PTR(&py_csi_colorbar_obj) }, + { MP_ROM_QSTR(MP_QSTR_auto_gain), MP_ROM_PTR(&py_csi_auto_gain_obj) }, + { MP_ROM_QSTR(MP_QSTR_gain_db), MP_ROM_PTR(&py_csi_gain_db_obj) }, + { MP_ROM_QSTR(MP_QSTR_auto_exposure), MP_ROM_PTR(&py_csi_auto_exposure_obj) }, + { MP_ROM_QSTR(MP_QSTR_exposure_us), MP_ROM_PTR(&py_csi_exposure_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_auto_whitebal), MP_ROM_PTR(&py_csi_auto_whitebal_obj) }, + { MP_ROM_QSTR(MP_QSTR_rgb_gain_db), MP_ROM_PTR(&py_csi_rgb_gain_db_obj) }, + { MP_ROM_QSTR(MP_QSTR_auto_blc), MP_ROM_PTR(&py_csi_auto_blc_obj) }, + { MP_ROM_QSTR(MP_QSTR_blc_regs), MP_ROM_PTR(&py_csi_blc_regs_obj) }, + { MP_ROM_QSTR(MP_QSTR_hmirror), MP_ROM_PTR(&py_csi_hmirror_obj) }, + { MP_ROM_QSTR(MP_QSTR_vflip), MP_ROM_PTR(&py_csi_vflip_obj) }, + { MP_ROM_QSTR(MP_QSTR_transpose), MP_ROM_PTR(&py_csi_transpose_obj) }, + { MP_ROM_QSTR(MP_QSTR_auto_rotation), MP_ROM_PTR(&py_csi_auto_rotation_obj) }, + { MP_ROM_QSTR(MP_QSTR_framebuffers), MP_ROM_PTR(&py_csi_framebuffers_obj) }, + { MP_ROM_QSTR(MP_QSTR_lens_correction), MP_ROM_PTR(&py_csi_lens_correction_obj) }, + { MP_ROM_QSTR(MP_QSTR_special_effect), MP_ROM_PTR(&py_csi_special_effect_obj) }, + { MP_ROM_QSTR(MP_QSTR_vsync_callback), MP_ROM_PTR(&py_csi_vsync_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_frame_callback), MP_ROM_PTR(&py_csi_frame_callback_obj) }, + { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&py_csi_ioctl_obj) }, + { MP_ROM_QSTR(MP_QSTR_color_palette), MP_ROM_PTR(&py_csi_color_palette_obj) }, + { MP_ROM_QSTR(MP_QSTR___write_reg), MP_ROM_PTR(&py_csi_write_reg_obj) }, + { MP_ROM_QSTR(MP_QSTR___read_reg), MP_ROM_PTR(&py_csi_read_reg_obj) }, +}; +MP_DEFINE_CONST_DICT(py_csi_locals_dict, py_csi_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + py_csi_type, + MP_QSTR_CSI, + MP_TYPE_FLAG_NONE, + make_new, py_csi_make_new, + locals_dict, &py_csi_locals_dict + ); + +static const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_csi) }, + { MP_ROM_QSTR(MP_QSTR_CSI), MP_ROM_PTR(&py_csi_type) }, + { MP_ROM_QSTR(MP_QSTR_devices), MP_ROM_PTR(&py_csi_devices_obj) }, + + // Pixel Formats + { MP_ROM_QSTR(MP_QSTR_BINARY), MP_ROM_INT(PIXFORMAT_BINARY) }, /* 1BPP/BINARY*/ + { MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(PIXFORMAT_GRAYSCALE) }, /* 1BPP/GRAYSCALE*/ + { MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(PIXFORMAT_RGB565) }, /* 2BPP/RGB565*/ + { MP_ROM_QSTR(MP_QSTR_BAYER), MP_ROM_INT(PIXFORMAT_BAYER) }, /* 1BPP/RAW*/ + { MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422) }, /* 2BPP/YUV422*/ + { MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT(PIXFORMAT_JPEG) }, /* JPEG/COMPRESSED*/ + + // Image Sensors + { MP_ROM_QSTR(MP_QSTR_OV2640), MP_ROM_INT(OV2640_ID) }, + { MP_ROM_QSTR(MP_QSTR_OV5640), MP_ROM_INT(OV5640_ID) }, + { MP_ROM_QSTR(MP_QSTR_OV7670), MP_ROM_INT(OV7670_ID) }, + { MP_ROM_QSTR(MP_QSTR_OV7690), MP_ROM_INT(OV7690_ID) }, + { MP_ROM_QSTR(MP_QSTR_OV7725), MP_ROM_INT(OV7725_ID) }, + { MP_ROM_QSTR(MP_QSTR_OV9650), MP_ROM_INT(OV9650_ID) }, + { MP_ROM_QSTR(MP_QSTR_MT9V022), MP_ROM_INT(MT9V0X2_ID) }, + { MP_ROM_QSTR(MP_QSTR_MT9V024), MP_ROM_INT(MT9V0X4_ID) }, + { MP_ROM_QSTR(MP_QSTR_MT9V032), MP_ROM_INT(MT9V0X2_ID) }, + { MP_ROM_QSTR(MP_QSTR_MT9V034), MP_ROM_INT(MT9V0X4_ID) }, + { MP_ROM_QSTR(MP_QSTR_MT9M114), MP_ROM_INT(MT9M114_ID) }, + { MP_ROM_QSTR(MP_QSTR_BOSON320), MP_ROM_INT(BOSON_320_ID) }, + { MP_ROM_QSTR(MP_QSTR_BOSON640), MP_ROM_INT(BOSON_640_ID) }, + { MP_ROM_QSTR(MP_QSTR_LEPTON), MP_ROM_INT(LEPTON_ID) }, + { MP_ROM_QSTR(MP_QSTR_HM01B0), MP_ROM_INT(HM01B0_ID) }, + { MP_ROM_QSTR(MP_QSTR_HM0360), MP_ROM_INT(HM0360_ID) }, + { MP_ROM_QSTR(MP_QSTR_GC2145), MP_ROM_INT(GC2145_ID) }, + { MP_ROM_QSTR(MP_QSTR_GENX320ES), MP_ROM_INT(GENX320_ID_ES) }, + { MP_ROM_QSTR(MP_QSTR_GENX320), MP_ROM_INT(GENX320_ID_MP) }, + { MP_ROM_QSTR(MP_QSTR_PAG7920), MP_ROM_INT(PAG7920_ID) }, + { MP_ROM_QSTR(MP_QSTR_PAG7936), MP_ROM_INT(PAG7936_ID) }, + { MP_ROM_QSTR(MP_QSTR_PAJ6100), MP_ROM_INT(PAJ6100_ID) }, + { MP_ROM_QSTR(MP_QSTR_FROGEYE2020), MP_ROM_INT(FROGEYE2020_ID) }, + { MP_ROM_QSTR(MP_QSTR_SOFTCSI), MP_ROM_INT(SOFTCSI_ID) }, + + // Special effects + { MP_ROM_QSTR(MP_QSTR_NORMAL), MP_ROM_INT(OMV_CSI_SDE_NORMAL) }, /* Normal/No SDE */ + { MP_ROM_QSTR(MP_QSTR_NEGATIVE), MP_ROM_INT(OMV_CSI_SDE_NEGATIVE) }, /* Negative image */ + + // C/SIF Resolutions + { MP_ROM_QSTR(MP_QSTR_QQCIF), MP_ROM_INT(OMV_CSI_FRAMESIZE_QQCIF) }, /* 88x72 */ + { MP_ROM_QSTR(MP_QSTR_QCIF), MP_ROM_INT(OMV_CSI_FRAMESIZE_QCIF) }, /* 176x144 */ + { MP_ROM_QSTR(MP_QSTR_CIF), MP_ROM_INT(OMV_CSI_FRAMESIZE_CIF) }, /* 352x288 */ + { MP_ROM_QSTR(MP_QSTR_QQSIF), MP_ROM_INT(OMV_CSI_FRAMESIZE_QQSIF) }, /* 88x60 */ + { MP_ROM_QSTR(MP_QSTR_QSIF), MP_ROM_INT(OMV_CSI_FRAMESIZE_QSIF) }, /* 176x120 */ + { MP_ROM_QSTR(MP_QSTR_SIF), MP_ROM_INT(OMV_CSI_FRAMESIZE_SIF) }, /* 352x240 */ + // VGA Resolutions + { MP_ROM_QSTR(MP_QSTR_QQQQVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_QQQQVGA) }, /* 40x30 */ + { MP_ROM_QSTR(MP_QSTR_QQQVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_QQQVGA) }, /* 80x60 */ + { MP_ROM_QSTR(MP_QSTR_QQVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_QQVGA) }, /* 160x120 */ + { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_QVGA) }, /* 320x240 */ + { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_VGA) }, /* 640x480 */ + { MP_ROM_QSTR(MP_QSTR_HQQQQVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_HQQQQVGA) }, /* 40x20 */ + { MP_ROM_QSTR(MP_QSTR_HQQQVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_HQQQVGA) }, /* 80x40 */ + { MP_ROM_QSTR(MP_QSTR_HQQVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_HQQVGA) }, /* 160x80 */ + { MP_ROM_QSTR(MP_QSTR_HQVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_HQVGA) }, /* 240x160 */ + { MP_ROM_QSTR(MP_QSTR_HVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_HVGA) }, /* 480x320 */ + // FFT Resolutions + { MP_ROM_QSTR(MP_QSTR_B64X32), MP_ROM_INT(OMV_CSI_FRAMESIZE_64X32) }, /* 64x32 */ + { MP_ROM_QSTR(MP_QSTR_B64X64), MP_ROM_INT(OMV_CSI_FRAMESIZE_64X64) }, /* 64x64 */ + { MP_ROM_QSTR(MP_QSTR_B128X64), MP_ROM_INT(OMV_CSI_FRAMESIZE_128X64) }, /* 128x64 */ + { MP_ROM_QSTR(MP_QSTR_B128X128), MP_ROM_INT(OMV_CSI_FRAMESIZE_128X128) }, /* 128x128 */ + // Himax Resolutions + { MP_ROM_QSTR(MP_QSTR_B160X160), MP_ROM_INT(OMV_CSI_FRAMESIZE_160X160) }, /* 160x160 */ + { MP_ROM_QSTR(MP_QSTR_B320X320), MP_ROM_INT(OMV_CSI_FRAMESIZE_320X320) }, /* 320x320 */ + // Other Resolutions + { MP_ROM_QSTR(MP_QSTR_LCD), MP_ROM_INT(OMV_CSI_FRAMESIZE_LCD) }, /* 128x160 */ + { MP_ROM_QSTR(MP_QSTR_QQVGA2), MP_ROM_INT(OMV_CSI_FRAMESIZE_QQVGA2) }, /* 128x160 */ + { MP_ROM_QSTR(MP_QSTR_WVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_WVGA) }, /* 720x480 */ + { MP_ROM_QSTR(MP_QSTR_WVGA2), MP_ROM_INT(OMV_CSI_FRAMESIZE_WVGA2) }, /* 752x480 */ + { MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_SVGA) }, /* 800x600 */ + { MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_XGA) }, /* 1024x768 */ + { MP_ROM_QSTR(MP_QSTR_WXGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_WXGA) }, /* 1280x768 */ + { MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_SXGA) }, /* 1280x1024 */ + { MP_ROM_QSTR(MP_QSTR_SXGAM), MP_ROM_INT(OMV_CSI_FRAMESIZE_SXGAM) }, /* 1280x960 */ + { MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_UXGA) }, /* 1600x1200 */ + { MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT(OMV_CSI_FRAMESIZE_HD) }, /* 1280x720 */ + { MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT(OMV_CSI_FRAMESIZE_FHD) }, /* 1920x1080 */ + { MP_ROM_QSTR(MP_QSTR_QHD), MP_ROM_INT(OMV_CSI_FRAMESIZE_QHD) }, /* 2560x1440 */ + { MP_ROM_QSTR(MP_QSTR_QXGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_QXGA) }, /* 2048x1536 */ + { MP_ROM_QSTR(MP_QSTR_WQXGA), MP_ROM_INT(OMV_CSI_FRAMESIZE_WQXGA) }, /* 2560x1600 */ + { MP_ROM_QSTR(MP_QSTR_WQXGA2), MP_ROM_INT(OMV_CSI_FRAMESIZE_WQXGA2) }, /* 2592x1944 */ + + // OMV_CSI_IOCTLs + { MP_ROM_QSTR(MP_QSTR_IOCTL_SET_READOUT_WINDOW), MP_ROM_INT(OMV_CSI_IOCTL_SET_READOUT_WINDOW) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_GET_READOUT_WINDOW), MP_ROM_INT(OMV_CSI_IOCTL_GET_READOUT_WINDOW) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_SET_TRIGGERED_MODE), MP_ROM_INT(OMV_CSI_IOCTL_SET_TRIGGERED_MODE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_GET_TRIGGERED_MODE), MP_ROM_INT(OMV_CSI_IOCTL_GET_TRIGGERED_MODE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_SET_FOV_WIDE), MP_ROM_INT(OMV_CSI_IOCTL_SET_FOV_WIDE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_GET_FOV_WIDE), MP_ROM_INT(OMV_CSI_IOCTL_GET_FOV_WIDE) }, + #if (OMV_OV5640_AF_ENABLE == 1) + { MP_ROM_QSTR(MP_QSTR_IOCTL_TRIGGER_AUTO_FOCUS), MP_ROM_INT(OMV_CSI_IOCTL_TRIGGER_AUTO_FOCUS) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_PAUSE_AUTO_FOCUS), MP_ROM_INT(OMV_CSI_IOCTL_PAUSE_AUTO_FOCUS) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_RESET_AUTO_FOCUS), MP_ROM_INT(OMV_CSI_IOCTL_RESET_AUTO_FOCUS) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_WAIT_ON_AUTO_FOCUS), MP_ROM_INT(OMV_CSI_IOCTL_WAIT_ON_AUTO_FOCUS) }, + #endif + { MP_ROM_QSTR(MP_QSTR_IOCTL_SET_NIGHT_MODE), MP_ROM_INT(OMV_CSI_IOCTL_SET_NIGHT_MODE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_GET_NIGHT_MODE), MP_ROM_INT(OMV_CSI_IOCTL_GET_NIGHT_MODE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_WIDTH), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_GET_WIDTH) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_HEIGHT), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_GET_HEIGHT) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_RADIOMETRY), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_GET_RADIOMETRY) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_REFRESH), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_GET_REFRESH) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_RESOLUTION), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_GET_RESOLUTION) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_RUN_COMMAND), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_RUN_COMMAND) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_SET_ATTRIBUTE), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_SET_ATTRIBUTE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_ATTRIBUTE), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_GET_ATTRIBUTE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_FPA_TEMP), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_GET_FPA_TEMP) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_AUX_TEMP), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_GET_AUX_TEMP) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_SET_MODE), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_SET_MODE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_MODE), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_GET_MODE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_SET_RANGE), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_SET_RANGE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_LEPTON_GET_RANGE), MP_ROM_INT(OMV_CSI_IOCTL_LEPTON_GET_RANGE) }, + #if (OMV_HM01B0_ENABLE == 1) + { MP_ROM_QSTR(MP_QSTR_IOCTL_HIMAX_MD_ENABLE), MP_ROM_INT(OMV_CSI_IOCTL_HIMAX_MD_ENABLE) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_HIMAX_MD_WINDOW), MP_ROM_INT(OMV_CSI_IOCTL_HIMAX_MD_WINDOW) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_HIMAX_MD_THRESHOLD), MP_ROM_INT(OMV_CSI_IOCTL_HIMAX_MD_THRESHOLD) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_HIMAX_MD_CLEAR), MP_ROM_INT(OMV_CSI_IOCTL_HIMAX_MD_CLEAR) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_HIMAX_OSC_ENABLE), MP_ROM_INT(OMV_CSI_IOCTL_HIMAX_OSC_ENABLE) }, + #endif + { MP_ROM_QSTR(MP_QSTR_IOCTL_GET_RGB_STATS), MP_ROM_INT(OMV_CSI_IOCTL_GET_RGB_STATS) }, + + #if (OMV_GENX320_ENABLE == 1) + { MP_ROM_QSTR(MP_QSTR_IOCTL_GENX320_SET_BIASES), MP_ROM_INT(OMV_CSI_IOCTL_GENX320_SET_BIASES) }, + { MP_ROM_QSTR(MP_QSTR_GENX320_BIASES_DEFAULT), MP_ROM_INT(OMV_CSI_GENX320_BIASES_DEFAULT) }, + { MP_ROM_QSTR(MP_QSTR_GENX320_BIASES_LOW_LIGHT), MP_ROM_INT(OMV_CSI_GENX320_BIASES_LOW_LIGHT) }, + { MP_ROM_QSTR(MP_QSTR_GENX320_BIASES_ACTIVE_MARKER), MP_ROM_INT(OMV_CSI_GENX320_BIASES_ACTIVE_MARKER) }, + { MP_ROM_QSTR(MP_QSTR_GENX320_BIASES_LOW_NOISE), MP_ROM_INT(OMV_CSI_GENX320_BIASES_LOW_NOISE) }, + { MP_ROM_QSTR(MP_QSTR_GENX320_BIASES_HIGH_SPEED), MP_ROM_INT(OMV_CSI_GENX320_BIASES_HIGH_SPEED) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_GENX320_SET_BIAS), MP_ROM_INT(OMV_CSI_IOCTL_GENX320_SET_BIAS) }, + { MP_ROM_QSTR(MP_QSTR_GENX320_BIAS_DIFF_OFF), MP_ROM_INT(OMV_CSI_GENX320_BIAS_DIFF_OFF) }, + { MP_ROM_QSTR(MP_QSTR_GENX320_BIAS_DIFF_ON), MP_ROM_INT(OMV_CSI_GENX320_BIAS_DIFF_ON) }, + { MP_ROM_QSTR(MP_QSTR_GENX320_BIAS_FO), MP_ROM_INT(OMV_CSI_GENX320_BIAS_FO) }, + { MP_ROM_QSTR(MP_QSTR_GENX320_BIAS_HPF), MP_ROM_INT(OMV_CSI_GENX320_BIAS_HPF) }, + { MP_ROM_QSTR(MP_QSTR_GENX320_BIAS_REFR), MP_ROM_INT(OMV_CSI_GENX320_BIAS_REFR) }, + { MP_ROM_QSTR(MP_QSTR_IOCTL_GENX320_SET_AFK), MP_ROM_INT(OMV_CSI_IOCTL_GENX320_SET_AFK) }, + #endif +}; +static MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t csi_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict, +}; + +MP_REGISTER_MODULE(MP_QSTR_csi, csi_module); +#endif // MICROPY_PY_CSI_NG diff --git a/modules/py_helper.c b/modules/py_helper.c index 12ee83600..ffa44b004 100644 --- a/modules/py_helper.c +++ b/modules/py_helper.c @@ -558,11 +558,14 @@ void py_helper_update_framebuffer(image_t *img) { } void py_helper_set_to_framebuffer(image_t *img) { + #if MICROPY_PY_CSI + omv_csi_t *csi = omv_csi_get(-1); + framebuffer_t *fb = csi->fb; + + omv_csi_set_framebuffers(csi, 1); + #else framebuffer_t *fb = framebuffer_get(0); - #if MICROPY_PY_CSI - omv_csi_set_framebuffers(1); - #else framebuffer_set_buffers(fb, 1); #endif diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 01ab65211..732de7835 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -112,6 +112,7 @@ MPY_CFLAGS += -I$(TOP_DIR)/$(MICROPY_DIR)/ports/alif/tinyusb_port MPY_CFLAGS += -I$(TOP_DIR)/$(MICROPY_DIR)/ports/alif/lwip_inc MPY_CFLAGS += -DMICROPY_PY_CSI=$(MICROPY_PY_CSI) +MPY_CFLAGS += -DMICROPY_PY_CSI_NG=$(MICROPY_PY_CSI_NG) MPY_CFLAGS += -DMICROPY_PY_LWIP=$(MICROPY_PY_LWIP) MPY_CFLAGS += -DMICROPY_PY_SSL=$(MICROPY_PY_SSL) MPY_CFLAGS += -DMICROPY_PY_SSL_ECDSA_SIGN_ALT=$(MICROPY_PY_SSL_ECDSA_SIGN_ALT) @@ -126,6 +127,7 @@ MPY_MKARGS += MICROPY_FLOAT_IMPL=float MPY_MKARGS += ALIF_DFP_REL_HERE=$(TOP_DIR)/$(HAL_DIR) MPY_MKARGS += CMSIS_DIR=$(TOP_DIR)/$(HAL_DIR)/cmsis/inc MPY_MKARGS += MICROPY_PY_CSI=$(MICROPY_PY_CSI) +MPY_MKARGS += MICROPY_PY_CSI_NG=$(MICROPY_PY_CSI_NG) MPY_MKARGS += MICROPY_PY_LWIP=$(MICROPY_PY_LWIP) MPY_MKARGS += MICROPY_PY_SSL=$(MICROPY_PY_SSL) MPY_MKARGS += MICROPY_PY_SSL_ECDSA_SIGN_ALT=$(MICROPY_PY_SSL_ECDSA_SIGN_ALT) diff --git a/ports/alif/omv_csi.c b/ports/alif/omv_csi.c index 201fede4b..de43a9c6a 100644 --- a/ports/alif/omv_csi.c +++ b/ports/alif/omv_csi.c @@ -71,33 +71,9 @@ CAM_INTR_OUTFIFO_OVERRUN | \ CAM_INTR_BRESP_ERR) -static CPI_Type *cpi_get_base_addr(omv_csi_t *csi) { - return ((CPI_Type *) CPI_BASE); -} - -void omv_csi_init0() { - omv_csi_abort(&csi, true, false); - - // Re-init I2C to reset the bus state after soft reset, which - // could have interrupted the bus in the middle of a transfer. - if (csi.i2c->initialized) { - // Reinitialize the bus using the last used id and speed. - // TODO: Causes Alif's I3C to lock up. - //omv_i2c_init(csi.i2c, csi.i2c->id, csi.i2c->speed); - } - - csi.disable_delays = false; - - // Disable VSYNC IRQ and callback - omv_csi_set_vsync_callback(NULL); - - // Disable Frame callback. - omv_csi_set_frame_callback(NULL); -} - -int omv_csi_config(omv_csi_config_t config) { +int alif_csi_config(omv_csi_t *csi, omv_csi_config_t config) { if (config == OMV_CSI_CONFIG_INIT) { - CPI_Type *cpi = cpi_get_base_addr(&csi); + CPI_Type *cpi = csi->base; // Configure the FIFO. cpi->CAM_FIFO_CTRL &= ~CAM_FIFO_CTRL_RD_WMARK_Msk; @@ -113,9 +89,9 @@ int omv_csi_config(omv_csi_config_t config) { cpi->CAM_CFG |= (CPI_VSYNC_WAIT_DISABLE << CAM_CFG_VSYNC_WAIT_Pos); // Set VSYNC, HSYNC and PIXCLK polarities. - cpi->CAM_CFG |= (csi.vsync_pol << CAM_CFG_VSYNC_POL_Pos); - cpi->CAM_CFG |= (csi.hsync_pol << CAM_CFG_HSYNC_POL_Pos); - cpi->CAM_CFG |= (!csi.pixck_pol << CAM_CFG_PXCLK_POL_Pos); + cpi->CAM_CFG |= (csi->vsync_pol << CAM_CFG_VSYNC_POL_Pos); + cpi->CAM_CFG |= (csi->hsync_pol << CAM_CFG_HSYNC_POL_Pos); + cpi->CAM_CFG |= (!csi->pixck_pol << CAM_CFG_PXCLK_POL_Pos); // Configure the data bus width, mode, endianness. cpi->CAM_CFG |= (CPI_ROW_ROUNDUP_DISABLE << CAM_CFG_RW_ROUNDUP_Pos); @@ -133,25 +109,16 @@ int omv_csi_config(omv_csi_config_t config) { return 0; } -int omv_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { - CPI_Type *cpi = cpi_get_base_addr(csi); +int alif_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { + CPI_Type *cpi = csi->base; + + // Stop CPI cpi->CAM_CTRL = 0; + + // Disable IRQs. NVIC_DisableIRQ(CAM_IRQ_IRQn); cpi_disable_interrupt(cpi, CPI_IRQ_FLAGS); cpi_irq_handler_clear_intr_status(cpi, CPI_IRQ_FLAGS); - - csi->first_line = false; - csi->drop_frame = false; - csi->last_frame_ms = 0; - csi->last_frame_ms_valid = false; - - if (csi->fb) { - if (fifo_flush) { - framebuffer_flush_buffers(csi->fb, true); - } else if (!csi->disable_full_flush) { - framebuffer_flush_buffers(csi->fb, false); - } - } return 0; } @@ -179,26 +146,27 @@ uint32_t omv_csi_get_clk_frequency() { } } -uint32_t omv_csi_get_fb_offset(omv_csi_t *csi) { +static uint32_t omv_csi_get_fb_offset(omv_csi_t *csi) { uint32_t offset = 0; - uint32_t bytes_per_pixel = omv_csi_get_src_bpp(); + uint32_t bytes_per_pixel = omv_csi_get_src_bpp(csi); uint32_t line_size_bytes = resolution[csi->framesize][0] * bytes_per_pixel; + // Offset the pixels buffer for debayering. if (csi->raw_output && csi->pixformat == PIXFORMAT_RGB565) { offset += line_size_bytes * resolution[csi->framesize][1]; } + return offset; } // This is the default snapshot function, which can be replaced in omv_csi_init functions. -int omv_csi_snapshot(omv_csi_t *csi, image_t *dst_image, uint32_t flags) { +int alif_csi_snapshot(omv_csi_t *csi, image_t *dst_image, uint32_t flags) { + CPI_Type *cpi = csi->base; framebuffer_t *fb = csi->fb; static uint32_t frames = 0; static uint32_t r_stat, gb_stat, gr_stat, b_stat; - CPI_Type *cpi = cpi_get_base_addr(csi); - if (csi->pixformat == PIXFORMAT_INVALID) { return OMV_CSI_ERROR_INVALID_PIXFORMAT; } @@ -207,7 +175,7 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *dst_image, uint32_t flags) { return OMV_CSI_ERROR_INVALID_FRAMESIZE; } - if (omv_csi_check_framebuffer_size() != 0) { + if (omv_csi_check_framebuffer_size(csi) != 0) { return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; } @@ -220,7 +188,7 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *dst_image, uint32_t flags) { // Reconfigure and restart the CSI transfer if it's not running. if (!(cpi->CAM_CTRL & CAM_CTRL_BUSY)) { framebuffer_setup_buffers(fb); - uint32_t bytes_per_pixel = omv_csi_get_src_bpp(); + uint32_t bytes_per_pixel = omv_csi_get_src_bpp(csi); uint32_t line_size_bytes = resolution[csi->framesize][0] * bytes_per_pixel; // Error out if the transfer size is not compatible with DMA transfer restrictions. @@ -353,7 +321,7 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *dst_image, uint32_t flags) { } // Crop first to reduce the frame size before debayering. - if (omv_csi_get_cropped()) { + if (omv_csi_get_cropped(csi)) { image_t src_cimage = *dst_image; image_t dst_cimage = *dst_image; @@ -387,7 +355,7 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *dst_image, uint32_t flags) { // Update AWB stats every n frames. if ((frames++ % 100) == 0) { - omv_csi_ioctl(OMV_CSI_IOCTL_GET_RGB_STATS, &r_stat, &gb_stat, &gr_stat, &b_stat); + omv_csi_ioctl(csi, OMV_CSI_IOCTL_GET_RGB_STATS, &r_stat, &gb_stat, &gr_stat, &b_stat); } // Debayer frame. @@ -398,9 +366,10 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *dst_image, uint32_t flags) { int omv_csi_init() { int init_ret = 0; - CPI_Type *cpi = cpi_get_base_addr(&csi); + static omv_i2c_t i2c; + CPI_Type *base = ((CPI_Type *) CPI_BASE); - alif_hal_csi_init(cpi, 0); + alif_hal_csi_init(base, 0); #if defined(OMV_CSI_POWER_PIN) omv_gpio_write(OMV_CSI_POWER_PIN, 0); @@ -410,17 +379,20 @@ int omv_csi_init() { omv_gpio_write(OMV_CSI_RESET_PIN, 0); #endif - // Reset the csi state - memset(&csi, 0, sizeof(omv_csi_t)); + // Initialize the CSIs using this driver's ops as defaults, + // which can be overridden by sensor drivers during probe. + for (size_t i=0; ii2c = &i2c; + csi->fb = framebuffer_get(-1); + csi->base = base; + csi->abort = alif_csi_abort; + csi->config = alif_csi_config; + csi->snapshot = alif_csi_snapshot; + csi->color_palette = rainbow_table; + } // Configure the CSI external clock. if (omv_csi_set_clk_frequency(OMV_CSI_CLK_FREQUENCY) != 0) { @@ -428,36 +400,37 @@ int omv_csi_init() { return OMV_CSI_ERROR_TIM_INIT_FAILED; } + // Initialize the camera bus. + omv_i2c_init(&i2c, OMV_CSI_I2C_ID, OMV_CSI_I2C_SPEED); + // Detect and initialize the image csi. - if ((init_ret = omv_csi_probe_init(OMV_CSI_I2C_ID, OMV_CSI_I2C_SPEED)) != 0) { + if ((init_ret = omv_csi_probe(&i2c)) != 0) { // csi probe/init failed. return init_ret; } - // Configure the CSI interface. - if (omv_csi_config(OMV_CSI_CONFIG_INIT) != 0) { - // CSI config failed - return OMV_CSI_ERROR_CSI_INIT_FAILED; + // Configure the DCMI interface. + for (size_t i=0; idetected = true; } - // Set default color palette. - csi.color_palette = rainbow_table; - - // Disable VSYNC IRQ and callback - omv_csi_set_vsync_callback(NULL); - - // Disable Frame callback. - omv_csi_set_frame_callback(NULL); - - // All good! - csi.detected = true; + // Clear fb_enabled flag. + JPEG_FB()->enabled = 0; return 0; } void CAM_IRQHandler(void) { uint32_t mask = 0; - CPI_Type *cpi = cpi_get_base_addr(&csi); + omv_csi_t *csi = omv_csi_get(-1); + CPI_Type *cpi = csi->base; + uint32_t status = cpi_get_interrupt_status(cpi); if (status & CAM_INTR_VSYNC) { @@ -470,19 +443,19 @@ void CAM_IRQHandler(void) { if (status & CAM_INTR_INFIFO_OVERRUN) { mask |= CAM_INTR_INFIFO_OVERRUN; - omv_csi_abort(&csi, true, true); + omv_csi_abort(csi, true, true); printf("INFIFO_OVERRUN\n"); } if (status & CAM_INTR_OUTFIFO_OVERRUN) { mask |= CAM_INTR_OUTFIFO_OVERRUN; - omv_csi_abort(&csi, true, true); + omv_csi_abort(csi, true, true); printf("OUTFIFO_OVERRUN\n"); } if (status & CAM_INTR_BRESP_ERR) { mask |= CAM_INTR_BRESP_ERR; - omv_csi_abort(&csi, true, true); + omv_csi_abort(csi, true, true); printf("BRESP_ERR %lu\n", cpi->CAM_AXI_ERR_STAT); } @@ -491,24 +464,22 @@ void CAM_IRQHandler(void) { cpi->CAM_CTRL = 0; if (!(status & CPI_ERROR_FLAGS)) { // Release the current framebuffer. - framebuffer_get_tail(csi.fb, FB_NO_FLAGS); + framebuffer_get_tail(csi->fb, FB_NO_FLAGS); } // Get the current framebuffer (or new tail). - vbuffer_t *buffer = framebuffer_get_tail(csi.fb, FB_PEEK); + vbuffer_t *buffer = framebuffer_get_tail(csi->fb, FB_PEEK); if (buffer != NULL) { cpi->CAM_CTRL = 0; cpi->CAM_CTRL |= CAM_CTRL_SW_RESET; - cpi->CAM_FRAME_ADDR = LocalToGlobal(buffer->data + omv_csi_get_fb_offset(&csi)); + cpi->CAM_FRAME_ADDR = LocalToGlobal(buffer->data + omv_csi_get_fb_offset(csi)); cpi_irq_handler_clear_intr_status(cpi, mask); cpi->CAM_CTRL = (CAM_CTRL_SNAPSHOT | CAM_CTRL_START | CAM_CTRL_FIFO_CLK_SEL); } - if (!(status & CPI_ERROR_FLAGS)) { - if (csi.frame_callback) { - csi.frame_callback(); - } + if (csi->frame_cb.fun && !(status & CPI_ERROR_FLAGS)) { + csi->frame_cb.fun(csi->frame_cb.arg); } } diff --git a/ports/alif/omv_portconfig.h b/ports/alif/omv_portconfig.h index 24fe2fe38..122190db5 100644 --- a/ports/alif/omv_portconfig.h +++ b/ports/alif/omv_portconfig.h @@ -38,6 +38,7 @@ #include "i2c.h" #include "i3c.h" #include "spi.h" +#include "cpi.h" #include "gpio.h" #include "pinconf.h" @@ -127,12 +128,18 @@ struct { \ #define OMV_SPI_MAX_TIMEOUT (0xFFFFFFFF) #define OMV_SPI_NO_DMA (1) -#define OMV_SPI_PORT_BITS \ -struct { \ - SPI_Type *inst; \ - bool is_lp; \ - uint32_t spi_mode; \ - uint32_t bus_mode; \ - uint8_t datasize; \ - }; +#define OMV_SPI_PORT_BITS \ + struct { \ + SPI_Type *inst; \ + bool is_lp; \ + uint32_t spi_mode; \ + uint32_t bus_mode; \ + uint8_t datasize; \ + }; + +#define OMV_CSI_PORT_BITS \ + struct { \ + CPI_Type *base; \ + }; + #endif // __OMV_PORTCONFIG_H__ diff --git a/ports/mimxrt/mimxrt_hal.c b/ports/mimxrt/mimxrt_hal.c index ba6b61b08..6341e08c6 100644 --- a/ports/mimxrt/mimxrt_hal.c +++ b/ports/mimxrt/mimxrt_hal.c @@ -40,6 +40,7 @@ #include "omv_boardconfig.h" // Define pin objects in this file. #define OMV_GPIO_DEFINE_PINS (1) +#include "omv_csi.h" #include "omv_gpio.h" #include "mimxrt_hal.h" @@ -320,9 +321,11 @@ int mimxrt_hal_spi_deinit(uint32_t bus_id, uint32_t bus_mode) { } void CSI_IRQHandler(void) { + omv_csi_t *csi = omv_csi_get(-1); uint32_t csisr = CSI_REG_SR(CSI); - extern void omv_csi_sof_callback(); - extern void omv_csi_line_callback(uint32_t); + + extern void omv_csi_sof_callback(omv_csi_t *csi); + extern void omv_csi_line_callback(omv_csi_t *csi, uint32_t); // Clear interrupt flags. CSI_REG_SR(CSI) = csisr; @@ -330,11 +333,11 @@ void CSI_IRQHandler(void) { if (csisr & CSI_SR_SOF_INT_MASK) { // Clear the FIFO and re/enable DMA. CSI_REG_CR3(CSI) |= (CSI_CR3_DMA_REFLASH_RFF_MASK | CSI_CR3_DMA_REQ_EN_RFF_MASK); - omv_csi_sof_callback(); + omv_csi_sof_callback(csi); } else if (csisr & CSI_SR_DMA_TSF_DONE_FB1_MASK) { - omv_csi_line_callback(CSI_REG_DMASA_FB1(CSI)); + omv_csi_line_callback(csi, CSI_REG_DMASA_FB1(CSI)); } else if (csisr & CSI_SR_DMA_TSF_DONE_FB2_MASK) { - omv_csi_line_callback(CSI_REG_DMASA_FB2(CSI)); + omv_csi_line_callback(csi, CSI_REG_DMASA_FB2(CSI)); } // Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F Store immediate diff --git a/ports/mimxrt/omv_csi.c b/ports/mimxrt/omv_csi.c index 09dc130f0..2d8d5c560 100644 --- a/ports/mimxrt/omv_csi.c +++ b/ports/mimxrt/omv_csi.c @@ -54,26 +54,7 @@ extern uint8_t _line_buf[OMV_LINE_BUF_SIZE]; | CSI_CR1_FB2_DMA_DONE_INTEN_MASK \ | CSI_CR1_FB1_DMA_DONE_INTEN_MASK) -void omv_csi_init0() { - omv_csi_abort(&csi, true, false); - - // Re-init I2C to reset the bus state after soft reset, which - // could have interrupted the bus in the middle of a transfer. - if (csi.i2c->initialized) { - // Reinitialize the bus using the last used id and speed. - omv_i2c_init(csi.i2c, csi.i2c->id, csi.i2c->speed); - } - - csi.disable_delays = false; - - // Disable VSYNC IRQ and callback - omv_csi_set_vsync_callback(NULL); - - // Disable Frame callback. - omv_csi_set_frame_callback(NULL); -} - -int omv_csi_config(omv_csi_config_t config) { +int imx_csi_config(omv_csi_t *csi, omv_csi_config_t config) { if (config == OMV_CSI_CONFIG_INIT) { CSI_Reset(CSI); NVIC_DisableIRQ(CSI_IRQn); @@ -88,9 +69,9 @@ int omv_csi_config(omv_csi_config_t config) { // Configure VSYNC, HSYNC and PIXCLK signals. CSI_REG_CR1(CSI) |= CSI_CR1_EXT_VSYNC_MASK; - CSI_REG_CR1(CSI) |= !csi.vsync_pol ? CSI_CR1_SOF_POL_MASK : 0; - CSI_REG_CR1(CSI) |= !csi.hsync_pol ? CSI_CR1_HSYNC_POL_MASK : 0; - CSI_REG_CR1(CSI) |= csi.pixck_pol ? CSI_CR1_REDGE_MASK : 0; + CSI_REG_CR1(CSI) |= !csi->vsync_pol ? CSI_CR1_SOF_POL_MASK : 0; + CSI_REG_CR1(CSI) |= !csi->hsync_pol ? CSI_CR1_HSYNC_POL_MASK : 0; + CSI_REG_CR1(CSI) |= csi->pixck_pol ? CSI_CR1_REDGE_MASK : 0; // Stride config: No stride. CSI_REG_FBUF_PARA(CSI) = 0; @@ -112,28 +93,19 @@ int omv_csi_config(omv_csi_config_t config) { return 0; } -int omv_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { +static int imx_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { NVIC_DisableIRQ(CSI_IRQn); CSI_DisableInterrupts(CSI, CSI_IRQ_FLAGS); CSI_REG_CR3(CSI) &= ~CSI_CR3_DMA_REQ_EN_RFF_MASK; CSI_REG_CR18(CSI) &= ~CSI_CR18_CSI_ENABLE_MASK; - csi->dest_inc = 0; - csi->first_line = false; - csi->drop_frame = false; - csi->last_frame_ms = 0; - csi->last_frame_ms_valid = false; - - if (csi->fb) { - if (fifo_flush) { - framebuffer_flush_buffers(csi->fb, true); - } else if (!csi->disable_full_flush) { - framebuffer_flush_buffers(csi->fb, false); - } - } return 0; } +uint32_t omv_csi_get_clk_frequency() { + return 24000000 / (CLOCK_GetDiv(kCLOCK_CsiDiv) + 1); +} + int omv_csi_set_clk_frequency(uint32_t frequency) { if (frequency >= 24000000) { CLOCK_SetDiv(kCLOCK_CsiDiv, 0); @@ -151,46 +123,42 @@ int omv_csi_set_clk_frequency(uint32_t frequency) { return 0; } -uint32_t omv_csi_get_clk_frequency() { - return 24000000 / (CLOCK_GetDiv(kCLOCK_CsiDiv) + 1); -} - -void omv_csi_sof_callback() { - csi.first_line = false; - csi.drop_frame = false; +void omv_csi_sof_callback(omv_csi_t *csi) { + csi->first_line = false; + csi->drop_frame = false; // Get current framebuffer. - vbuffer_t *buffer = framebuffer_get_tail(csi.fb, FB_PEEK); + vbuffer_t *buffer = framebuffer_get_tail(csi->fb, FB_PEEK); if (buffer == NULL) { - omv_csi_abort(&csi, false, true); - } else if (buffer->offset < resolution[csi.framesize][1]) { + omv_csi_abort(csi, false, true); + } else if (buffer->offset < resolution[csi->framesize][1]) { // Missed a few lines, reset buffer state and continue. buffer->reset_state = true; } } #if defined(OMV_CSI_DMA) -int omv_csi_dma_memcpy(void *dma, void *dst, void *src, int bpp, bool transposed) { +int omv_csi_dma_memcpy(omv_csi_t *csi, void *dma, void *dst, void *src, int bpp, bool transposed) { + edma_handle_t *handle = dma; + edma_transfer_config_t config; + framebuffer_t *fb = csi->fb; + // EMDA will not perform burst transfers for anything less than 32-byte chunks of four 64-bit // beats. Additionally, the CSI hardware lacks cropping so we cannot align the source address. // Given this, performance will be lacking on cropped images. So much so that we do not use // the EDMA for anything less than 4-byte transfers otherwise you get sensor timeout errors. - if (csi.dest_inc < MIN_EDMA_DST_INC) { + if (csi->dest_inc < MIN_EDMA_DST_INC) { return -1; } - edma_handle_t *handle = dma; - edma_transfer_config_t config; - framebuffer_t *fb = csi.fb; - EDMA_PrepareTransferConfig(&config, src, // srcAddr - csi.src_size, // srcWidth - csi.src_inc, // srcOffset + csi->src_size, // srcWidth + csi->src_inc, // srcOffset dst, // destAddr - transposed ? bpp : csi.dest_inc, // destWidth - transposed ? (fb->v * bpp) : csi.dest_inc, // destOffset + transposed ? bpp : csi->dest_inc, // destWidth + transposed ? (fb->v * bpp) : csi->dest_inc, // destOffset fb->u * bpp, // bytesEachRequest fb->u * bpp); // transferBytes @@ -203,7 +171,7 @@ int omv_csi_dma_memcpy(void *dma, void *dst, void *src, int bpp, bool transposed } if (--retry == 0) { // Drop the frame if EDMA is not keeping up as the image will be corrupt. - csi.drop_frame = true; + csi->drop_frame = true; return 0; } } @@ -213,21 +181,21 @@ int omv_csi_dma_memcpy(void *dma, void *dst, void *src, int bpp, bool transposed } #endif -void omv_csi_line_callback(uint32_t addr) { - framebuffer_t *fb = csi.fb; +void omv_csi_line_callback(omv_csi_t *csi, uint32_t addr) { + framebuffer_t *fb = csi->fb; // Throttle frames to match the current frame rate. - omv_csi_throttle_framerate(); + omv_csi_throttle_framerate(csi); // Get current framebuffer. vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); - if (csi.pixformat == PIXFORMAT_JPEG) { - if (csi.drop_frame) { + if (csi->pixformat == PIXFORMAT_JPEG) { + if (csi->drop_frame) { return; } bool jpeg_end = false; - if (csi.jpg_format == 4) { + if (csi->jpg_format == 4) { // JPEG MODE 4: // // The width and height are fixed in each frame. The first two bytes are valid data @@ -257,7 +225,7 @@ void omv_csi_line_callback(uint32_t addr) { } buffer->offset += size; } - } else if (csi.jpg_format == 3) { + } else if (csi->jpg_format == 3) { // OV2640 JPEG TODO } // In JPEG mode the camera sensor will output some number of lines that doesn't match the @@ -267,16 +235,16 @@ void omv_csi_line_callback(uint32_t addr) { // Release the current framebuffer. framebuffer_get_tail(fb, FB_NO_FLAGS); CSI_REG_CR3(CSI) &= ~CSI_CR3_DMA_REQ_EN_RFF_MASK; - if (csi.frame_callback) { - csi.frame_callback(); + if (csi->frame_cb.fun) { + csi->frame_cb.fun(csi->frame_cb.arg); } - csi.drop_frame = true; + csi->drop_frame = true; } return; } - if (csi.drop_frame) { - if (++buffer->offset == resolution[csi.framesize][1]) { + if (csi->drop_frame) { + if (++buffer->offset == resolution[csi->framesize][1]) { buffer->offset = 0; CSI_REG_CR3(CSI) &= ~CSI_CR3_DMA_REQ_EN_RFF_MASK; } @@ -285,16 +253,16 @@ void omv_csi_line_callback(uint32_t addr) { if ((fb->y <= buffer->offset) && (buffer->offset < (fb->y + fb->v))) { // Copy from DMA buffer to framebuffer. - uint32_t bytes_per_pixel = omv_csi_get_src_bpp(); + uint32_t bytes_per_pixel = omv_csi_get_src_bpp(csi); uint8_t *src = ((uint8_t *) addr) + (fb->x * bytes_per_pixel); uint8_t *dst = buffer->data; // Adjust BPP for Grayscale. - if (csi.pixformat == PIXFORMAT_GRAYSCALE) { + if (csi->pixformat == PIXFORMAT_GRAYSCALE) { bytes_per_pixel = 1; } - if (csi.transpose) { + if (csi->transpose) { dst += bytes_per_pixel * (buffer->offset - fb->y); } else { dst += fb->u * bytes_per_pixel * (buffer->offset - fb->y); @@ -304,18 +272,18 @@ void omv_csi_line_callback(uint32_t addr) { // We're using multiple handles to give each channel the maximum amount of time possible to do the line // transfer. In most situations only one channel will be running at a time. However, if SDRAM is // backedup we don't have to disable the channel if it is flushing trailing data to SDRAM. - omv_csi_copy_line(&csi.dma_channels[buffer->offset % OMV_CSI_DMA_CHANNEL_COUNT], src, dst); + omv_csi_copy_line(csi, &csi->dma_channels[buffer->offset % OMV_CSI_DMA_CHANNEL_COUNT], src, dst); #else - omv_csi_copy_line(NULL, src, dst); + omv_csi_copy_line(csi, NULL, src, dst); #endif } - if (++buffer->offset == resolution[csi.framesize][1]) { + if (++buffer->offset == resolution[csi->framesize][1]) { // Release the current framebuffer. framebuffer_get_tail(fb, FB_NO_FLAGS); CSI_REG_CR3(CSI) &= ~CSI_CR3_DMA_REQ_EN_RFF_MASK; - if (csi.frame_callback) { - csi.frame_callback(); + if (csi->frame_cb.fun) { + csi->frame_cb.fun(csi->frame_cb.arg); } } } @@ -361,7 +329,7 @@ static void edma_config(omv_csi_t *csi, uint32_t bytes_per_pixel) { } #endif -int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { +int imx_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { framebuffer_t *fb = csi->fb; // Used to restore the frame buffer width and height. @@ -376,7 +344,7 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { return OMV_CSI_ERROR_INVALID_FRAMESIZE; } - if (omv_csi_check_framebuffer_size() != 0) { + if (omv_csi_check_framebuffer_size(csi) != 0) { return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; } @@ -390,7 +358,7 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { if (!(CSI->CR18 & CSI_CR18_CSI_ENABLE_MASK)) { framebuffer_setup_buffers(fb); - uint32_t bytes_per_pixel = omv_csi_get_src_bpp(); + uint32_t bytes_per_pixel = omv_csi_get_src_bpp(csi); uint32_t dma_line_bytes = resolution[csi->framesize][0] * bytes_per_pixel; uint32_t length = dma_line_bytes * h; @@ -530,10 +498,10 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { framebuffer_init_image(fb, image); return 0; } -#endif int omv_csi_init() { int init_ret = 0; + static omv_i2c_t i2c; mimxrt_hal_csi_init(CSI); @@ -545,18 +513,19 @@ int omv_csi_init() { omv_gpio_write(OMV_CSI_RESET_PIN, 1); #endif - // Reset the csi state - memset(&csi, 0, sizeof(omv_csi_t)); + // Initialize the CSIs using this driver's ops as defaults, + // which can be overridden by sensor drivers during probe. + for (size_t i=0; ii2c = &i2c; + csi->fb = framebuffer_get(-1); + csi->abort = imx_csi_abort; + csi->config = imx_csi_config; + csi->snapshot = imx_csi_snapshot; + csi->color_palette = rainbow_table; + } // Configure the csi external clock (XCLK). if (omv_csi_set_clk_frequency(OMV_CSI_CLK_FREQUENCY) != 0) { @@ -564,29 +533,28 @@ int omv_csi_init() { return OMV_CSI_ERROR_TIM_INIT_FAILED; } + // Initialize the camera bus. + omv_i2c_init(&i2c, OMV_CSI_I2C_ID, OMV_CSI_I2C_SPEED); + // Detect and initialize the image sensor. - if ((init_ret = omv_csi_probe_init(OMV_CSI_I2C_ID, OMV_CSI_I2C_SPEED)) != 0) { + if ((init_ret = omv_csi_probe(&i2c)) != 0) { // Sensor probe/init failed. return init_ret; } - // Configure the CSI interface. - if (omv_csi_config(OMV_CSI_CONFIG_INIT) != 0) { - // CSI config failed - return OMV_CSI_ERROR_CSI_INIT_FAILED; + // Configure the CSI interfaces. + for (size_t i=0; idetected = true; } - // Set default color palette. - csi.color_palette = rainbow_table; - - // Disable VSYNC IRQ and callback - omv_csi_set_vsync_callback(NULL); - - // Disable Frame callback. - omv_csi_set_frame_callback(NULL); - - // All good! - csi.detected = true; - + // Clear fb_enabled flag. + JPEG_FB()->enabled = 0; return 0; } +#endif // MICROPY_PY_CSI diff --git a/ports/nrf/omv_csi.c b/ports/nrf/omv_csi.c index 89c705fff..0473d86ec 100644 --- a/ports/nrf/omv_csi.c +++ b/ports/nrf/omv_csi.c @@ -61,7 +61,7 @@ static const volatile uint32_t *_pclkPort; extern void __fatal_error(const char *msg); -int omv_csi_config(omv_csi_config_t config) { +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, @@ -114,12 +114,12 @@ int omv_csi_set_clk_frequency(uint32_t frequency) { return 0; } -int omv_csi_set_windowing(int x, int y, int w, int h) { +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. -int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { +static int nrf_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { framebuffer_t *fb = csi->fb; // Compress the framebuffer for the IDE preview, only if it's not the first frame, @@ -132,7 +132,7 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { framebuffer_set_buffers(fb, 1); } - if (omv_csi_check_framebuffer_size(fb) != 0) { + if (omv_csi_check_framebuffer_size(csi) != 0) { return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; } @@ -188,8 +188,8 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { interrupts(); // Not useful for the NRF but must call to keep API the same. - if (csi->frame_callback) { - csi->frame_callback(); + if (csi->frame_cb.fun) { + csi->frame_cb.fun(csi->frame_cb.arg); } // Set framebuffer pixel format. @@ -208,6 +208,7 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { int omv_csi_init() { int init_ret = 0; + static omv_i2c_t i2c; #if defined(OMV_CSI_POWER_PIN) nrf_gpio_cfg_output(OMV_CSI_POWER_PIN); @@ -219,17 +220,19 @@ int omv_csi_init() { nrf_gpio_pin_write(OMV_CSI_RESET_PIN, 1); #endif - // Reset the csi state - memset(&csi, 0, sizeof(omv_csi_t)); + // Initialize the CSIs using this driver's ops as defaults, + // which can be overridden by sensor drivers during probe. + for (size_t i=0; ii2c = &i2c; + csi->fb = framebuffer_get(-1); + csi->abort = NULL; + csi->config = nrf_csi_config; + csi->snapshot = nrf_csi_snapshot; + csi->color_palette = rainbow_table; + } // Configure the csi external clock (XCLK). if (omv_csi_set_clk_frequency(OMV_CSI_CLK_FREQUENCY) != 0) { @@ -237,30 +240,28 @@ int omv_csi_init() { return OMV_CSI_ERROR_TIM_INIT_FAILED; } + // Initialize the camera bus. + omv_i2c_init(&i2c, OMV_CSI_I2C_ID, OMV_CSI_I2C_SPEED); + // Detect and initialize the image sensor. - if ((init_ret = omv_csi_probe_init(OMV_CSI_I2C_ID, OMV_CSI_I2C_SPEED)) != 0) { + if ((init_ret = omv_csi_probe(&i2c)) != 0) { // Sensor probe/init failed. return init_ret; } - // Configure the CSI interface. - if (omv_csi_config(OMV_CSI_CONFIG_INIT) != 0) { - // CSI config failed - return OMV_CSI_ERROR_CSI_INIT_FAILED; + // Configure the CSI interfaces. + for (size_t i=0; idetected = true; } // Clear fb_enabled flag - // This is executed only once to initialize the FB enabled flag. - //JPEG_FB()->enabled = 0; + JPEG_FB()->enabled = 0; - // Set default color palette. - csi.color_palette = rainbow_table; - - csi.detected = true; - - // Disable VSYNC IRQ and callback - omv_csi_set_vsync_callback(NULL); - - /* All good! */ return 0; } diff --git a/ports/rp2/omv_csi.c b/ports/rp2/omv_csi.c index 232f4db79..a706d0a2b 100644 --- a/ports/rp2/omv_csi.c +++ b/ports/rp2/omv_csi.c @@ -41,31 +41,56 @@ #include "unaligned_memcpy.h" #include "dcmi.pio.h" -static void dma_irq_handler(); extern void __fatal_error(const char *msg); -static void omv_csi_dma_config(int w, int h, int bpp, uint32_t *capture_buf, bool rev_bytes) { - dma_channel_abort(OMV_CSI_DMA_CHANNEL); - dma_irqn_set_channel_enabled(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL, false); +static void dma_irq_handler() { + omv_csi_t *csi = omv_csi_get(-1); + framebuffer_t *fb = csi->fb; - dma_channel_config c = dma_channel_get_default_config(OMV_CSI_DMA_CHANNEL); - channel_config_set_read_increment(&c, false); - channel_config_set_write_increment(&c, true); - channel_config_set_dreq(&c, pio_get_dreq(OMV_CSI_PIO, OMV_CSI_SM, false)); - channel_config_set_bswap(&c, rev_bytes); + if (dma_irqn_get_channel_status(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL)) { + // Clear the interrupt request. + dma_irqn_acknowledge_channel(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL); - dma_channel_configure(OMV_CSI_DMA_CHANNEL, &c, - capture_buf, // Destinatinon pointer. - &OMV_CSI_PIO->rxf[OMV_CSI_SM], // Source pointer. - (w * h * bpp) >> 2, // Number of transfers in words. - true // Start immediately, will block on SM. - ); + framebuffer_get_tail(fb, FB_NO_FLAGS); + vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); + if (buffer != NULL) { + // Set next buffer and retrigger the DMA channel. + dma_channel_set_write_addr(OMV_CSI_DMA_CHANNEL, buffer->data, true); - // Re-enable DMA IRQs. - dma_irqn_set_channel_enabled(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL, true); + // Unblock the state machine + pio_sm_restart(OMV_CSI_PIO, OMV_CSI_SM); + pio_sm_clear_fifos(OMV_CSI_PIO, OMV_CSI_SM); + pio_sm_put_blocking(OMV_CSI_PIO, OMV_CSI_SM, (fb->v - 1)); + pio_sm_put_blocking(OMV_CSI_PIO, OMV_CSI_SM, (fb->u * fb->bpp) - 1); + } + } } -int omv_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { +static int rp2_csi_config(omv_csi_t *csi, omv_csi_config_t config) { + if (config == OMV_CSI_CONFIG_INIT) { + // Install new DMA IRQ handler. + irq_set_enabled(OMV_CSI_DMA_IRQ, false); + + // Clear DMA interrupts. + dma_irqn_acknowledge_channel(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL); + + // Remove current handler if any + irq_handler_t irq_handler = irq_get_exclusive_handler(OMV_CSI_DMA_IRQ); + if (irq_handler != NULL) { + irq_remove_handler(OMV_CSI_DMA_IRQ, irq_handler); + } + + // Set new exclusive IRQ handler. + irq_set_exclusive_handler(OMV_CSI_DMA_IRQ, dma_irq_handler); + // Or set shared IRQ handler, but this needs to be called once. + // irq_add_shared_handler(OMV_CSI_DMA_IRQ, dma_irq_handler, PICO_DEFAULT_IRQ_PRIORITY); + + irq_set_enabled(OMV_CSI_DMA_IRQ, true); + } + return 0; +} + +static int rp2_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { // Disable DMA channel dma_channel_abort(OMV_CSI_DMA_CHANNEL); dma_irqn_set_channel_enabled(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL, false); @@ -107,39 +132,17 @@ int omv_csi_set_clk_frequency(uint32_t frequency) { return 0; } -int omv_csi_set_windowing(int x, int y, int w, int h) { +int omv_csi_set_windowing(omv_csi_t *csi, int x, int y, int w, int h) { return OMV_CSI_ERROR_CTL_UNSUPPORTED; } -static void dma_irq_handler() { - framebuffer_t *fb = csi.fb; - - if (dma_irqn_get_channel_status(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL)) { - // Clear the interrupt request. - dma_irqn_acknowledge_channel(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL); - - framebuffer_get_tail(fb, FB_NO_FLAGS); - vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); - if (buffer != NULL) { - // Set next buffer and retrigger the DMA channel. - dma_channel_set_write_addr(OMV_CSI_DMA_CHANNEL, buffer->data, true); - - // Unblock the state machine - pio_sm_restart(OMV_CSI_PIO, OMV_CSI_SM); - pio_sm_clear_fifos(OMV_CSI_PIO, OMV_CSI_SM); - pio_sm_put_blocking(OMV_CSI_PIO, OMV_CSI_SM, (fb->v - 1)); - pio_sm_put_blocking(OMV_CSI_PIO, OMV_CSI_SM, (fb->u * fb->bpp) - 1); - } - } -} - -int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { +static int rp2_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { framebuffer_t *fb = csi->fb; // Compress the framebuffer for the IDE preview. framebuffer_update_jpeg_buffer(fb); - if (omv_csi_check_framebuffer_size() != 0) { + if (omv_csi_check_framebuffer_size(csi) != 0) { return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; } @@ -164,10 +167,25 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { return OMV_CSI_ERROR_FRAMEBUFFER_ERROR; } - // Configure the DMA on the first frame, for later frames only the write is changed. - omv_csi_dma_config(fb->u, fb->v, fb->bpp, - (void *) buffer->data, - (csi->rgb_swap && fb->bpp == 2)); + // Configure the DMA on the first frame. + dma_channel_abort(OMV_CSI_DMA_CHANNEL); + dma_irqn_set_channel_enabled(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL, false); + + dma_channel_config c = dma_channel_get_default_config(OMV_CSI_DMA_CHANNEL); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, true); + channel_config_set_dreq(&c, pio_get_dreq(OMV_CSI_PIO, OMV_CSI_SM, false)); + channel_config_set_bswap(&c, csi->rgb_swap && (fb->bpp == 2)); + + dma_channel_configure(OMV_CSI_DMA_CHANNEL, &c, + (uint32_t *) buffer->data, // Destinatinon pointer. + &OMV_CSI_PIO->rxf[OMV_CSI_SM], // Source pointer. + (fb->u * fb->v * fb->bpp) >> 2, // Number of transfers in words. + true // Start immediately, will block on SM. + ); + + // Re-enable DMA IRQs. + dma_irqn_set_channel_enabled(OMV_CSI_DMA, OMV_CSI_DMA_CHANNEL, true); // Re-enable the state machine. pio_sm_clear_fifos(OMV_CSI_PIO, OMV_CSI_SM); @@ -194,10 +212,10 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { framebuffer_init_image(fb, image); return 0; } -#endif int omv_csi_init() { int init_ret = 0; + static omv_i2c_t i2c; // PIXCLK gpio_init(OMV_CSI_PXCLK_PIN); @@ -225,17 +243,19 @@ int omv_csi_init() { gpio_put(OMV_CSI_RESET_PIN, 1); #endif - // Reset the csi state - memset(&csi, 0, sizeof(omv_csi_t)); + // Initialize the CSIs using this driver's ops as defaults, + // which can be overridden by sensor drivers during probe. + for (size_t i=0; ii2c = &i2c; + csi->fb = framebuffer_get(-1); + csi->abort = rp2_csi_abort; + csi->config = rp2_csi_config; + csi->snapshot = rp2_csi_snapshot; + csi->color_palette = rainbow_table; + } // Configure the csi external clock (XCLK). if (omv_csi_set_clk_frequency(OMV_CSI_CLK_FREQUENCY) != 0) { @@ -243,43 +263,28 @@ int omv_csi_init() { return OMV_CSI_ERROR_TIM_INIT_FAILED; } + // Initialize the camera bus. + omv_i2c_init(&i2c, OMV_CSI_I2C_ID, OMV_CSI_I2C_SPEED); + // Detect and initialize the image sensor. - if ((init_ret = omv_csi_probe_init(OMV_CSI_I2C_ID, OMV_CSI_I2C_SPEED)) != 0) { + if ((init_ret = omv_csi_probe(&i2c)) != 0) { // Sensor probe/init failed. return init_ret; } - // Set default color palette. - csi.color_palette = rainbow_table; + // Configure the DCMI interface. + for (size_t i=0; idetected = true; } - // Set new exclusive IRQ handler. - irq_set_exclusive_handler(OMV_CSI_DMA_IRQ, dma_irq_handler); - // Or set shared IRQ handler, but this needs to be called once. - // irq_add_shared_handler(OMV_CSI_DMA_IRQ, dma_irq_handler, PICO_DEFAULT_IRQ_PRIORITY); - - irq_set_enabled(OMV_CSI_DMA_IRQ, true); - - // Disable VSYNC IRQ and callback - omv_csi_set_vsync_callback(NULL); - - // Disable Frame callback. - omv_csi_set_frame_callback(NULL); - - /* All good! */ - csi.detected = true; - + // Clear fb_enabled flag. + JPEG_FB()->enabled = 0; return 0; } +#endif // MICROPY_PY_CSI diff --git a/ports/stm32/omv_csi.c b/ports/stm32/omv_csi.c index 5498c0c7c..f10264869 100644 --- a/ports/stm32/omv_csi.c +++ b/ports/stm32/omv_csi.c @@ -70,94 +70,82 @@ extern uint32_t hal_get_exti_gpio(uint32_t line); #if USE_DCMI void DCMI_IRQHandler(void) { - HAL_DCMI_IRQHandler(&csi.dcmi); + omv_csi_t *csi = omv_csi_get(-1); + HAL_DCMI_IRQHandler(&csi->dcmi); } #endif #if USE_DCMIPP void CSI_IRQHandler(void) { - HAL_DCMIPP_CSI_IRQHandler(&csi.dcmi); + omv_csi_t *csi = omv_csi_get(-1); + HAL_DCMIPP_CSI_IRQHandler(&csi->dcmi); } void DCMIPP_IRQHandler(void) { - HAL_DCMIPP_IRQHandler(&csi.dcmi); + omv_csi_t *csi = omv_csi_get(-1); + HAL_DCMIPP_IRQHandler(&csi->dcmi); } #endif #if USE_MDMA void omv_csi_mdma_irq_handler(void) { + omv_csi_t *csi = omv_csi_get(-1); + if (MDMA->GISR0 & (1 << OMV_MDMA_CHANNEL_DCMI_0)) { - HAL_MDMA_IRQHandler(&csi.mdma0); + HAL_MDMA_IRQHandler(&csi->mdma0); } if (MDMA->GISR0 & (1 << OMV_MDMA_CHANNEL_DCMI_1)) { - HAL_MDMA_IRQHandler(&csi.mdma1); + HAL_MDMA_IRQHandler(&csi->mdma1); } } #endif -void omv_csi_init0() { - omv_csi_abort(&csi, true, false); - - // Disable callbacks - omv_csi_set_vsync_callback(NULL); - omv_csi_set_frame_callback(NULL); - - csi.disable_delays = false; - - // Re-init i2c bus to reset the bus state after soft reset, which - // could have interrupted the bus in the middle of a transfer. - if (csi.i2c->initialized) { - // Reinitialize the bus using the last used id and speed. - omv_i2c_init(csi.i2c, csi.i2c->id, csi.i2c->speed); - } -} - -int omv_csi_config(omv_csi_config_t config) { +static int stm_csi_config(omv_csi_t *csi, omv_csi_config_t config) { if (config == OMV_CSI_CONFIG_INIT) { #if USE_DMA // DMA Stream configuration - csi.dma.Instance = DMA2_Stream1; + csi->dma.Instance = DMA2_Stream1; #if defined(STM32H7) - csi.dma.Init.Request = DMA_REQUEST_DCMI; + csi->dma.Init.Request = DMA_REQUEST_DCMI; #else - csi.dma.Init.Channel = DMA_CHANNEL_1; + csi->dma.Init.Channel = DMA_CHANNEL_1; #endif - csi.dma.Init.Direction = DMA_PERIPH_TO_MEMORY; - csi.dma.Init.MemInc = DMA_MINC_ENABLE; - csi.dma.Init.PeriphInc = DMA_PINC_DISABLE; - csi.dma.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; - csi.dma.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; - csi.dma.Init.Mode = DMA_NORMAL; - csi.dma.Init.Priority = DMA_PRIORITY_HIGH; - csi.dma.Init.FIFOMode = DMA_FIFOMODE_ENABLE; - csi.dma.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; - csi.dma.Init.MemBurst = DMA_MBURST_INC4; - csi.dma.Init.PeriphBurst = DMA_PBURST_SINGLE; - + csi->dma.Init.Direction = DMA_PERIPH_TO_MEMORY; + csi->dma.Init.MemInc = DMA_MINC_ENABLE; + csi->dma.Init.PeriphInc = DMA_PINC_DISABLE; + csi->dma.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; + csi->dma.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; + csi->dma.Init.Mode = DMA_NORMAL; + csi->dma.Init.Priority = DMA_PRIORITY_HIGH; + csi->dma.Init.FIFOMode = DMA_FIFOMODE_ENABLE; + csi->dma.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; + csi->dma.Init.MemBurst = DMA_MBURST_INC4; + csi->dma.Init.PeriphBurst = DMA_PBURST_SINGLE; + // Initialize the DMA stream - HAL_DMA_DeInit(&csi.dma); - if (HAL_DMA_Init(&csi.dma) != HAL_OK) { - return -1; + HAL_DMA_DeInit(&csi->dma); + if (HAL_DMA_Init(&csi->dma) != HAL_OK) { + return OMV_CSI_ERROR_DMA_INIT_FAILED; } - + // Set DMA IRQ handle - dma_utils_set_irq_descr(DMA2_Stream1, &csi.dma); - + dma_utils_set_irq_descr(DMA2_Stream1, &csi->dma); + // Configure the DMA IRQ Channel NVIC_SetPriority(DMA2_Stream1_IRQn, IRQ_PRI_DMA21); - + #if USE_MDMA - csi.mdma0.Instance = MDMA_CHAN_TO_INSTANCE(OMV_MDMA_CHANNEL_DCMI_0); - csi.mdma1.Instance = MDMA_CHAN_TO_INSTANCE(OMV_MDMA_CHANNEL_DCMI_1); + csi->mdma0.Instance = MDMA_CHAN_TO_INSTANCE(OMV_MDMA_CHANNEL_DCMI_0); + csi->mdma1.Instance = MDMA_CHAN_TO_INSTANCE(OMV_MDMA_CHANNEL_DCMI_1); #endif - #endif // USE_DMA + #endif // USE_DMA // Configure DCMI/PP. #if USE_DCMIPP // Initialize the DCMIPP - csi.dcmi.Instance = DCMIPP; - if (HAL_DCMIPP_Init(&csi.dcmi) != HAL_OK) { + csi->dcmi.Instance = DCMIPP; + if (HAL_DCMIPP_Init(&csi->dcmi) != HAL_OK) { return -1; } @@ -169,21 +157,21 @@ int omv_csi_config(omv_csi_config_t config) { NVIC_SetPriority(CSI_IRQn, IRQ_PRI_DCMI); HAL_NVIC_EnableIRQ(CSI_IRQn); #else - csi.dcmi.Instance = DCMI; - csi.dcmi.Init.VSPolarity = csi.vsync_pol ? DCMI_VSPOLARITY_HIGH : DCMI_VSPOLARITY_LOW; - csi.dcmi.Init.HSPolarity = csi.hsync_pol ? DCMI_HSPOLARITY_HIGH : DCMI_HSPOLARITY_LOW; - csi.dcmi.Init.PCKPolarity = csi.pixck_pol ? DCMI_PCKPOLARITY_RISING : DCMI_PCKPOLARITY_FALLING; - csi.dcmi.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE; - csi.dcmi.Init.CaptureRate = DCMI_CR_ALL_FRAME; - csi.dcmi.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B; - csi.dcmi.Init.JPEGMode = DCMI_JPEG_DISABLE; + csi->dcmi.Instance = DCMI; + csi->dcmi.Init.VSPolarity = csi->vsync_pol ? DCMI_VSPOLARITY_HIGH : DCMI_VSPOLARITY_LOW; + csi->dcmi.Init.HSPolarity = csi->hsync_pol ? DCMI_HSPOLARITY_HIGH : DCMI_HSPOLARITY_LOW; + csi->dcmi.Init.PCKPolarity = csi->pixck_pol ? DCMI_PCKPOLARITY_RISING : DCMI_PCKPOLARITY_FALLING; + csi->dcmi.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE; + csi->dcmi.Init.CaptureRate = DCMI_CR_ALL_FRAME; + csi->dcmi.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B; + csi->dcmi.Init.JPEGMode = DCMI_JPEG_DISABLE; // Link the DMA handle to the DCMI handle - __HAL_LINKDMA(&csi.dcmi, DMA_Handle, csi.dma); + __HAL_LINKDMA(&csi->dcmi, DMA_Handle, csi->dma); // Initialize the DCMI - HAL_DCMI_DeInit(&csi.dcmi); - if (HAL_DCMI_Init(&csi.dcmi) != HAL_OK) { + HAL_DCMI_DeInit(&csi->dcmi); + if (HAL_DCMI_Init(&csi->dcmi) != HAL_OK) { return -1; } @@ -194,16 +182,16 @@ int omv_csi_config(omv_csi_config_t config) { } else if (config == OMV_CSI_CONFIG_PIXFORMAT) { #if USE_DCMI DCMI->CR &= ~(DCMI_CR_JPEG_Msk << DCMI_CR_JPEG_Pos); - DCMI->CR |= (csi.pixformat == PIXFORMAT_JPEG) ? DCMI_JPEG_ENABLE : DCMI_JPEG_DISABLE; + DCMI->CR |= (csi->pixformat == PIXFORMAT_JPEG) ? DCMI_JPEG_ENABLE : DCMI_JPEG_DISABLE; #else // Select and configure the DCMIPP source. - if (csi.mipi_if) { + if (csi->mipi_if) { DCMIPP_CSI_ConfTypeDef scfg = { .NumberOfLanes = DCMIPP_CSI_TWO_DATA_LANES, .DataLaneMapping = DCMIPP_CSI_PHYSICAL_DATA_LANES, - .PHYBitrate = (csi.mipi_brate == 850) ? DCMIPP_CSI_PHY_BT_850 : DCMIPP_CSI_PHY_BT_1200, + .PHYBitrate = (csi->mipi_brate == 850) ? DCMIPP_CSI_PHY_BT_850 : DCMIPP_CSI_PHY_BT_1200, }; - if (HAL_DCMIPP_CSI_SetConfig(&csi.dcmi, &scfg) != HAL_OK) { + if (HAL_DCMIPP_CSI_SetConfig(&csi->dcmi, &scfg) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } // Configure CSI virtual channel and pipe. @@ -212,61 +200,60 @@ int omv_csi_config(omv_csi_config_t config) { .DataTypeIDA = DCMIPP_DT_RAW10, .DataTypeIDB = DCMIPP_DT_RAW10, }; - if (HAL_DCMIPP_CSI_SetVCConfig(&csi.dcmi, DCMIPP_VIRTUAL_CHANNEL0, + if (HAL_DCMIPP_CSI_SetVCConfig(&csi->dcmi, DCMIPP_VIRTUAL_CHANNEL0, DCMIPP_CSI_DT_BPP10) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } - if (HAL_DCMIPP_CSI_PIPE_SetConfig(&csi.dcmi, DCMIPP_PIPE, &pcfg) != HAL_OK) { + if (HAL_DCMIPP_CSI_PIPE_SetConfig(&csi->dcmi, DCMIPP_PIPE, &pcfg) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } } else { DCMIPP_ParallelConfTypeDef scfg = { .SynchroMode = DCMIPP_SYNCHRO_HARDWARE, .ExtendedDataMode = DCMIPP_INTERFACE_8BITS, - .VSPolarity = csi.vsync_pol ? DCMIPP_VSPOLARITY_HIGH : DCMIPP_VSPOLARITY_LOW, - .HSPolarity = csi.hsync_pol ? DCMIPP_HSPOLARITY_HIGH : DCMIPP_HSPOLARITY_LOW, - .PCKPolarity = csi.pixck_pol ? DCMIPP_PCKPOLARITY_RISING : DCMIPP_PCKPOLARITY_FALLING, + .VSPolarity = csi->vsync_pol ? DCMIPP_VSPOLARITY_HIGH : DCMIPP_VSPOLARITY_LOW, + .HSPolarity = csi->hsync_pol ? DCMIPP_HSPOLARITY_HIGH : DCMIPP_HSPOLARITY_LOW, + .PCKPolarity = csi->pixck_pol ? DCMIPP_PCKPOLARITY_RISING : DCMIPP_PCKPOLARITY_FALLING, }; - if (csi.raw_output) { + if (csi->raw_output) { scfg.Format = DCMIPP_FORMAT_RAW8; - } else if (csi.pixformat == PIXFORMAT_RGB565) { + } else if (csi->pixformat == PIXFORMAT_RGB565) { scfg.Format = DCMIPP_FORMAT_RGB565; scfg.SwapCycles = DCMIPP_SWAPCYCLES_ENABLE; - } else if (csi.pixformat == PIXFORMAT_GRAYSCALE) { - scfg.Format = (csi.mono_bpp == 1) ? DCMIPP_FORMAT_MONOCHROME_8B : DCMIPP_FORMAT_YUV422; + } else if (csi->pixformat == PIXFORMAT_GRAYSCALE) { + scfg.Format = (csi->mono_bpp == 1) ? DCMIPP_FORMAT_MONOCHROME_8B : DCMIPP_FORMAT_YUV422; } else { return OMV_CSI_ERROR_PIXFORMAT_UNSUPPORTED; } - if (HAL_DCMIPP_PARALLEL_SetConfig(&csi.dcmi, &scfg) != HAL_OK) { + if (HAL_DCMIPP_PARALLEL_SetConfig(&csi->dcmi, &scfg) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } } // Configure the pixel processing pipeline. DCMIPP_PipeConfTypeDef pcfg = { .FrameRate = DCMIPP_FRAME_RATE_ALL }; - if (csi.pixformat == PIXFORMAT_RGB565) { + if (csi->pixformat == PIXFORMAT_RGB565) { pcfg.PixelPackerFormat = DCMIPP_PIXEL_PACKER_FORMAT_RGB565_1; - } else if (csi.pixformat == PIXFORMAT_GRAYSCALE) { + } else if (csi->pixformat == PIXFORMAT_GRAYSCALE) { pcfg.PixelPackerFormat = DCMIPP_PIXEL_PACKER_FORMAT_MONO_Y8_G8_1; } else { return OMV_CSI_ERROR_PIXFORMAT_UNSUPPORTED; } - if (HAL_DCMIPP_PIPE_SetConfig(&csi.dcmi, DCMIPP_PIPE, &pcfg) != HAL_OK) { + if (HAL_DCMIPP_PIPE_SetConfig(&csi->dcmi, DCMIPP_PIPE, &pcfg) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } // Swap RGB enabled. - if (csi.yuv_swap) { - HAL_DCMIPP_PIPE_EnableYUVSwap(&csi.dcmi, DCMIPP_PIPE); + if (csi->yuv_swap) { + HAL_DCMIPP_PIPE_EnableYUVSwap(&csi->dcmi, DCMIPP_PIPE); } // Swap YUV if enabled. - if (csi.rgb_swap) { - HAL_DCMIPP_PIPE_EnableRedBlueSwap(&csi.dcmi, DCMIPP_PIPE); + if (csi->rgb_swap) { + HAL_DCMIPP_PIPE_EnableRedBlueSwap(&csi->dcmi, DCMIPP_PIPE); } // Configure debayer. - if (csi.raw_output && csi.pixformat != PIXFORMAT_BAYER) { - + if (csi->raw_output && csi->pixformat != PIXFORMAT_BAYER) { DCMIPP_RawBayer2RGBConfTypeDef rawcfg = { .RawBayerType = DCMIPP_RAWBAYER_BGGR, .VLineStrength = DCMIPP_RAWBAYER_ALGO_NONE, @@ -274,8 +261,9 @@ int omv_csi_config(omv_csi_config_t config) { .PeakStrength = DCMIPP_RAWBAYER_ALGO_NONE, .EdgeStrength = DCMIPP_RAWBAYER_ALGO_NONE, }; - if (HAL_DCMIPP_PIPE_SetISPRawBayer2RGBConfig(&csi.dcmi, DCMIPP_PIPE, &rawcfg) != HAL_OK || - HAL_DCMIPP_PIPE_EnableISPRawBayer2RGB(&csi.dcmi, DCMIPP_PIPE) != HAL_OK) { + + if (HAL_DCMIPP_PIPE_SetISPRawBayer2RGBConfig(&csi->dcmi, DCMIPP_PIPE, &rawcfg) != HAL_OK || + HAL_DCMIPP_PIPE_EnableISPRawBayer2RGB(&csi->dcmi, DCMIPP_PIPE) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } @@ -288,8 +276,8 @@ int omv_csi_config(omv_csi_config_t config) { .MultiplierBlue = 128, }; - if (HAL_DCMIPP_PIPE_SetISPExposureConfig(&csi.dcmi, DCMIPP_PIPE, &expcfg) != HAL_OK || - HAL_DCMIPP_PIPE_EnableISPExposure(&csi.dcmi, DCMIPP_PIPE) != HAL_OK) { + if (HAL_DCMIPP_PIPE_SetISPExposureConfig(&csi->dcmi, DCMIPP_PIPE, &expcfg) != HAL_OK || + HAL_DCMIPP_PIPE_EnableISPExposure(&csi->dcmi, DCMIPP_PIPE) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } @@ -307,13 +295,13 @@ int omv_csi_config(omv_csi_config_t config) { } for (size_t i = DCMIPP_STATEXT_MODULE1; i <= DCMIPP_STATEXT_MODULE3; i++) { - if (HAL_DCMIPP_PIPE_SetISPStatisticExtractionConfig(&csi.dcmi, + if (HAL_DCMIPP_PIPE_SetISPStatisticExtractionConfig(&csi->dcmi, DCMIPP_PIPE, i, &statcfg[i - DCMIPP_STATEXT_MODULE1]) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } - if (HAL_DCMIPP_PIPE_EnableISPStatisticExtraction(&csi.dcmi, DCMIPP_PIPE, i) != HAL_OK) { + if (HAL_DCMIPP_PIPE_EnableISPStatisticExtraction(&csi->dcmi, DCMIPP_PIPE, i) != HAL_OK) { return OMV_CSI_ERROR_CSI_INIT_FAILED; } } @@ -324,73 +312,74 @@ int omv_csi_config(omv_csi_config_t config) { } // Stop the DCMI from generating more DMA requests, and disable the DMA. -int omv_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { - if (DCMI_IS_ACTIVE()) { - #if USE_DCMI - DCMI->CR &= ~DCMI_CR_ENABLE; - #endif - #if USE_DMA - if (in_irq) { - HAL_DMA_Abort_IT(&csi->dma); - } else { - HAL_DMA_Abort(&csi->dma); - } - HAL_NVIC_DisableIRQ(DMA2_Stream1_IRQn); - #endif - #if USE_MDMA - if (!in_irq) { - HAL_MDMA_Abort(&csi->mdma0); - HAL_MDMA_Abort(&csi->mdma1); - } - HAL_MDMA_DeInit(&csi->mdma0); - HAL_MDMA_DeInit(&csi->mdma1); - #endif - #if USE_DCMI - __HAL_DCMI_DISABLE_IT(&csi->dcmi, DCMI_IT_FRAME); - __HAL_DCMI_CLEAR_FLAG(&csi->dcmi, DCMI_FLAG_FRAMERI); - #else - if (!csi->mipi_if) { - HAL_DCMIPP_PIPE_Stop(&csi->dcmi, DCMIPP_PIPE); - } else { - HAL_DCMIPP_CSI_PIPE_Stop(&csi->dcmi, DCMIPP_PIPE, DCMIPP_VIRTUAL_CHANNEL0); - } - for (size_t i=0; idcmi.PipeState[i] = HAL_DCMIPP_PIPE_STATE_RESET; - } - #endif - csi->first_line = false; - csi->drop_frame = false; - csi->last_frame_ms = 0; - csi->last_frame_ms_valid = false; +static int stm_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { + if (!DCMI_IS_ACTIVE()) { + return 0; } - if (csi->fb) { - if (fifo_flush) { - framebuffer_flush_buffers(csi->fb, true); - } else if (!csi->disable_full_flush) { - framebuffer_flush_buffers(csi->fb, false); - } + #if USE_DCMI + DCMI->CR &= ~DCMI_CR_ENABLE; + #endif + + #if USE_DMA + if (in_irq) { + HAL_DMA_Abort_IT(&csi->dma); + } else { + HAL_DMA_Abort(&csi->dma); } + HAL_NVIC_DisableIRQ(DMA2_Stream1_IRQn); + #endif + + #if USE_MDMA + if (!in_irq) { + HAL_MDMA_Abort(&csi->mdma0); + HAL_MDMA_Abort(&csi->mdma1); + } + HAL_MDMA_DeInit(&csi->mdma0); + HAL_MDMA_DeInit(&csi->mdma1); + #endif + + #if USE_DCMI + __HAL_DCMI_DISABLE_IT(&csi->dcmi, DCMI_IT_FRAME); + __HAL_DCMI_CLEAR_FLAG(&csi->dcmi, DCMI_FLAG_FRAMERI); + #else + if (!csi->mipi_if) { + HAL_DCMIPP_PIPE_Stop(&csi->dcmi, DCMIPP_PIPE); + } else { + HAL_DCMIPP_CSI_PIPE_Stop(&csi->dcmi, DCMIPP_PIPE, DCMIPP_VIRTUAL_CHANNEL0); + } + for (size_t i=0; idcmi.PipeState[i] = HAL_DCMIPP_PIPE_STATE_RESET; + } + #endif return 0; } uint32_t omv_csi_get_clk_frequency() { - return (OMV_CSI_TIM_PCLK_FREQ() * 2) / (csi.tim.Init.Period + 1); + omv_csi_t *csi = omv_csi_get(-1); + + if (!csi->tim.Instance) { + return 0; + } + return (OMV_CSI_TIM_PCLK_FREQ() * 2) / (csi->tim.Init.Period + 1); } +// TODO save frequency. int omv_csi_set_clk_frequency(uint32_t frequency) { #if (OMV_CSI_CLK_SOURCE == OMV_CSI_CLK_SOURCE_TIM) + omv_csi_t *csi = omv_csi_get(-1); + if (frequency == 0) { - if (csi.tim.Init.Period) { - HAL_TIM_PWM_Stop(&csi.tim, OMV_CSI_TIM_CHANNEL); - HAL_TIM_PWM_DeInit(&csi.tim); - memset(&csi.tim, 0, sizeof(csi.tim)); + if (csi->tim.Init.Period) { + HAL_TIM_PWM_Stop(&csi->tim, OMV_CSI_TIM_CHANNEL); + HAL_TIM_PWM_DeInit(&csi->tim); + memset(&csi->tim, 0, sizeof(csi->tim)); } return 0; } - csi.tim.Instance = OMV_CSI_TIM; + csi->tim.Instance = OMV_CSI_TIM; // TCLK (PCLK * 2) int tclk = OMV_CSI_TIM_PCLK_FREQ() * 2; @@ -399,20 +388,20 @@ int omv_csi_set_clk_frequency(uint32_t frequency) { int period = fast_ceilf(tclk / ((float) frequency)) - 1; int pulse = (period + 1) / 2; - if (csi.tim.Init.Period && (csi.tim.Init.Period != period)) { - // __HAL_TIM_SET_AUTORELOAD sets csi.tim.Init.Period... - __HAL_TIM_SET_AUTORELOAD(&csi.tim, period); - __HAL_TIM_SET_COMPARE(&csi.tim, OMV_CSI_TIM_CHANNEL, pulse); + if (csi->tim.Init.Period && (csi->tim.Init.Period != period)) { + // __HAL_TIM_SET_AUTORELOAD sets csi->tim.Init.Period... + __HAL_TIM_SET_AUTORELOAD(&csi->tim, period); + __HAL_TIM_SET_COMPARE(&csi->tim, OMV_CSI_TIM_CHANNEL, pulse); return 0; } /* Timer base configuration */ - csi.tim.Init.Period = period; - csi.tim.Init.Prescaler = 0; - csi.tim.Init.CounterMode = TIM_COUNTERMODE_UP; - csi.tim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - csi.tim.Init.RepetitionCounter = 0; - csi.tim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; + csi->tim.Init.Period = period; + csi->tim.Init.Prescaler = 0; + csi->tim.Init.CounterMode = TIM_COUNTERMODE_UP; + csi->tim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + csi->tim.Init.RepetitionCounter = 0; + csi->tim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* Timer channel configuration */ TIM_OC_InitTypeDef TIMOCHandle; @@ -424,9 +413,9 @@ int omv_csi_set_clk_frequency(uint32_t frequency) { TIMOCHandle.OCIdleState = TIM_OCIDLESTATE_RESET; TIMOCHandle.OCNIdleState = TIM_OCNIDLESTATE_RESET; - if ((HAL_TIM_PWM_Init(&csi.tim) != HAL_OK) - || (HAL_TIM_PWM_ConfigChannel(&csi.tim, &TIMOCHandle, OMV_CSI_TIM_CHANNEL) != HAL_OK) - || (HAL_TIM_PWM_Start(&csi.tim, OMV_CSI_TIM_CHANNEL) != HAL_OK)) { + if ((HAL_TIM_PWM_Init(&csi->tim) != HAL_OK) + || (HAL_TIM_PWM_ConfigChannel(&csi->tim, &TIMOCHandle, OMV_CSI_TIM_CHANNEL) != HAL_OK) + || (HAL_TIM_PWM_Start(&csi->tim, OMV_CSI_TIM_CHANNEL) != HAL_OK)) { return -1; } #elif (OMV_CSI_CLK_SOURCE == OMV_CSI_CLK_SOURCE_MCO) @@ -442,13 +431,13 @@ int omv_csi_set_clk_frequency(uint32_t frequency) { return 0; } -int omv_csi_shutdown(int enable) { +int omv_csi_shutdown(omv_csi_t *csi, int enable) { int ret = 0; - omv_csi_abort(&csi, true, false); + omv_csi_abort(csi, true, false); if (enable) { #if defined(OMV_CSI_POWER_PIN) - if (csi.power_pol == OMV_CSI_ACTIVE_HIGH) { + if (csi->power_pol == OMV_CSI_ACTIVE_HIGH) { omv_gpio_write(OMV_CSI_POWER_PIN, 1); } else { omv_gpio_write(OMV_CSI_POWER_PIN, 0); @@ -456,39 +445,32 @@ int omv_csi_shutdown(int enable) { #endif #if USE_DCMI HAL_NVIC_DisableIRQ(DCMI_IRQn); - HAL_DCMI_DeInit(&csi.dcmi); + HAL_DCMI_DeInit(&csi->dcmi); #endif } else { #if defined(OMV_CSI_POWER_PIN) - if (csi.power_pol == OMV_CSI_ACTIVE_HIGH) { + if (csi->power_pol == OMV_CSI_ACTIVE_HIGH) { omv_gpio_write(OMV_CSI_POWER_PIN, 0); } else { omv_gpio_write(OMV_CSI_POWER_PIN, 1); } #endif - ret = omv_csi_config(OMV_CSI_CONFIG_INIT); + ret = omv_csi_config(csi, OMV_CSI_CONFIG_INIT); } mp_hal_delay_ms(10); return ret; } -static void omv_csi_vsync_callback(void *data) { - if (csi.vsync_callback != NULL) { - csi.vsync_callback(omv_gpio_read(OMV_CSI_VSYNC_PIN)); - } -} - -int omv_csi_set_vsync_callback(vsync_cb_t vsync_cb) { - csi.vsync_callback = vsync_cb; - if (csi.vsync_callback == NULL) { +int omv_csi_set_vsync_callback(omv_csi_t *csi, omv_csi_cb_t cb) { + if (cb.fun == NULL) { #if (DCMI_VSYNC_EXTI_SHARED == 0) // Disable VSYNC EXTI IRQ omv_gpio_irq_enable(OMV_CSI_VSYNC_PIN, false); #endif } else { // Enable VSYNC EXTI IRQ - omv_gpio_irq_register(OMV_CSI_VSYNC_PIN, omv_csi_vsync_callback, NULL); + omv_gpio_irq_register(OMV_CSI_VSYNC_PIN, cb.fun, cb.arg); omv_gpio_irq_enable(OMV_CSI_VSYNC_PIN, true); } return 0; @@ -498,17 +480,16 @@ int omv_csi_set_vsync_callback(vsync_cb_t vsync_cb) { // If the image is cropped by more than 1 word in width, align the line start to a word // address to improve copy performance. Do not crop by more than 1 word as this will // result in less time between DMA transfers complete interrupts on 16-byte boundaries. -static uint32_t get_dcmi_hw_crop(uint32_t bytes_per_pixel) { - framebuffer_t *fb = csi.fb; +static uint32_t get_dcmi_hw_crop(omv_csi_t *csi, uint32_t bytes_per_pixel) { + framebuffer_t *fb = csi->fb; uint32_t byte_x_offset = (fb->x * bytes_per_pixel) % sizeof(uint32_t); - uint32_t width_remainder = (resolution[csi.framesize][0] - (fb->x + fb->u)) * bytes_per_pixel; - uint32_t x_crop = 0; + uint32_t width_remainder = (resolution[csi->framesize][0] - (fb->x + fb->u)) * bytes_per_pixel; if (byte_x_offset && (width_remainder >= (sizeof(uint32_t) - byte_x_offset))) { - x_crop = byte_x_offset; + return byte_x_offset; } - return x_crop; + return 0; } #endif @@ -517,7 +498,9 @@ void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) { #else void HAL_DCMIPP_PIPE_FrameEventCallback(DCMIPP_HandleTypeDef *dcmipp, uint32_t pipe) { #endif - framebuffer_t *fb = csi.fb; + omv_csi_t *csi = omv_csi_get(-1); + framebuffer_t *fb = csi->fb; + #if USE_MDMA // Clear out any stale flags. DMA2->LIFCR = DMA_FLAG_TCIF1_5 | DMA_FLAG_HTIF1_5; @@ -526,9 +509,9 @@ void HAL_DCMIPP_PIPE_FrameEventCallback(DCMIPP_HandleTypeDef *dcmipp, uint32_t p #endif // Reset DCMI_DMAConvCpltUser frame drop state. - csi.first_line = false; - if (csi.drop_frame) { - csi.drop_frame = false; + csi->first_line = false; + if (csi->drop_frame) { + csi->drop_frame = false; // Reset the buffer's state if the frame was dropped. vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); if (buffer) { @@ -539,15 +522,15 @@ void HAL_DCMIPP_PIPE_FrameEventCallback(DCMIPP_HandleTypeDef *dcmipp, uint32_t p framebuffer_get_tail(fb, FB_NO_FLAGS); - if (csi.frame_callback) { - csi.frame_callback(); + if (csi->frame_cb.fun) { + csi->frame_cb.fun(csi->frame_cb.arg); } #if USE_DCMIPP // Get the destination buffer address. vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); if (buffer == NULL) { - omv_csi_abort(&csi, false, false); + omv_csi_abort(csi, false, false); } else { HAL_DCMIPP_PIPE_SetMemoryAddress(dcmipp, pipe, DCMIPP_MEMORY_ADDRESS_0, (uint32_t) buffer->data); } @@ -560,14 +543,15 @@ void HAL_DCMIPP_PIPE_FrameEventCallback(DCMIPP_HandleTypeDef *dcmipp, uint32_t p // Using line buffers allows performing post-processing before writing the frame to the // framebuffer, and help hide external RAM latency. void DCMI_DMAConvCpltUser(uint32_t addr) { - framebuffer_t *fb = csi.fb; + omv_csi_t *csi = omv_csi_get(-1); + framebuffer_t *fb = csi->fb; // Throttle frames to match the current frame rate. - omv_csi_throttle_framerate(); + omv_csi_throttle_framerate(csi); - if (csi.drop_frame) { + if (csi->drop_frame) { #if USE_MDMA - if (!csi.transpose) { + if (!csi->transpose) { HAL_NVIC_DisableIRQ(DMA2_Stream1_IRQn); } #endif @@ -576,17 +560,17 @@ void DCMI_DMAConvCpltUser(uint32_t addr) { vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); if (buffer == NULL) { - omv_csi_abort(&csi, false, true); + omv_csi_abort(csi, false, true); return; } - if (csi.pixformat == PIXFORMAT_JPEG) { - if (csi.jpg_format == 3) { + if (csi->pixformat == PIXFORMAT_JPEG) { + if (csi->jpg_format == 3) { // JPEG MODE 3: Variable line width per frame, with the last line potentially shorter and // no padding. `offset` is incremented once every max transfer, and the DMA counter holds // the total size. buffer->offset += 1; - } else if (csi.jpg_format == 4) { + } else if (csi->jpg_format == 4) { // JPEG MODE 4: Fixed width and height per frame. Each line starts with two bytes indicating // valid data length, followed by image data and optional padding (0xFF). `offset` holds the // total size. @@ -604,41 +588,41 @@ void DCMI_DMAConvCpltUser(uint32_t addr) { #if USE_MDMA // DCMI_DMAConvCpltUser is called with the other MAR register. // So, we have to fix the address in full MDMA offload mode. - if (!csi.transpose) { + if (!csi->transpose) { addr = (uint32_t) &_line_buf; } #endif - uint32_t bytes_per_pixel = omv_csi_get_src_bpp(); - uint8_t *src = ((uint8_t *) addr) + (fb->x * bytes_per_pixel) - get_dcmi_hw_crop(bytes_per_pixel); + uint32_t bytes_per_pixel = omv_csi_get_src_bpp(csi); + uint8_t *src = ((uint8_t *) addr) + (fb->x * bytes_per_pixel) - get_dcmi_hw_crop(csi, bytes_per_pixel); uint8_t *dst = buffer->data; - if (csi.pixformat == PIXFORMAT_GRAYSCALE) { + if (csi->pixformat == PIXFORMAT_GRAYSCALE) { bytes_per_pixel = sizeof(uint8_t); } // For all non-JPEG and non-transposed modes image capture can be completely offload to MDMA. #if USE_MDMA - if (!csi.transpose) { + if (!csi->transpose) { // NOTE: MDMA is started here, not in FRAME/VSYNC callbacks, to maximize the time before // the frame has to be dropped. uint32_t line_width_bytes = fb->u * bytes_per_pixel; // mdma0 will copy this line of the image to the final destination. - __HAL_UNLOCK(&csi.mdma0); - csi.mdma0.State = HAL_MDMA_STATE_READY; - HAL_MDMA_Start(&csi.mdma0, (uint32_t) src, (uint32_t) dst, + __HAL_UNLOCK(&csi->mdma0); + csi->mdma0.State = HAL_MDMA_STATE_READY; + HAL_MDMA_Start(&csi->mdma0, (uint32_t) src, (uint32_t) dst, line_width_bytes, 1); // mdma1 will copy all remaining lines of the image to the final destination. - __HAL_UNLOCK(&csi.mdma1); - csi.mdma1.State = HAL_MDMA_STATE_READY; - HAL_MDMA_Start(&csi.mdma1, (uint32_t) src, (uint32_t) (dst + line_width_bytes), + __HAL_UNLOCK(&csi->mdma1); + csi->mdma1.State = HAL_MDMA_STATE_READY; + HAL_MDMA_Start(&csi->mdma1, (uint32_t) src, (uint32_t) (dst + line_width_bytes), line_width_bytes, fb->v - 1); HAL_NVIC_DisableIRQ(DMA2_Stream1_IRQn); return; } #endif - if (!csi.transpose) { + if (!csi->transpose) { dst += fb->u * bytes_per_pixel * buffer->offset++; } else { dst += bytes_per_pixel * buffer->offset++; @@ -646,9 +630,9 @@ void DCMI_DMAConvCpltUser(uint32_t addr) { #if USE_MDMA // Two MDMA channels are used to maximize the time available for each channel to finish the transfer. - omv_csi_copy_line((buffer->offset % 2) ? &csi.mdma1 : &csi.mdma0, src, dst); + omv_csi_copy_line(csi, (buffer->offset % 2) ? &csi->mdma1 : &csi->mdma0, src, dst); #else - omv_csi_copy_line(NULL, src, dst); + omv_csi_copy_line(csi, NULL, src, dst); #endif } #endif @@ -676,7 +660,7 @@ static void omv_csi_mdma_config(omv_csi_t *csi, MDMA_InitTypeDef *init, uint32_t init->Endianness = MDMA_LITTLE_ENDIANNESS_PRESERVE; } - uint32_t line_offset_bytes = (fb->x * bytes_per_pixel) - get_dcmi_hw_crop(bytes_per_pixel); + uint32_t line_offset_bytes = (fb->x * bytes_per_pixel) - get_dcmi_hw_crop(csi, bytes_per_pixel); uint32_t line_width_bytes = fb->u * bytes_per_pixel; if (csi->transpose) { @@ -750,13 +734,13 @@ static void omv_csi_mdma_enable(omv_csi_t *csi, uint32_t bytes_per_pixel) { } } -int omv_csi_dma_memcpy(void *dma, void *dst, void *src, int bpp, bool transposed) { - framebuffer_t *fb = csi.fb; +int omv_csi_dma_memcpy(omv_csi_t *csi, void *dma, void *dst, void *src, int bpp, bool transposed) { + framebuffer_t *fb = csi->fb; MDMA_HandleTypeDef *handle = dma; // Drop the frame if MDMA is not keeping up as the image will be corrupted. if (handle->Instance->CCR & MDMA_CCR_EN) { - csi.drop_frame = true; + csi->drop_frame = true; return 0; } @@ -793,7 +777,7 @@ void omv_csi_update_awb(omv_csi_t *csi, uint32_t n_pixels) { //printf("Luminance: %f AVG_R: %lu, AVG_G: %lu, AVG_B: %lu\n", (double) luminance, avg[0], avg[1], avg[2]); if (csi->ioctl) { - omv_csi_ioctl(OMV_CSI_IOCTL_UPDATE_AGC_AEC, fast_floorf(luminance)); + omv_csi_ioctl(csi, OMV_CSI_IOCTL_UPDATE_AGC_AEC, fast_floorf(luminance)); } // Calculate average and exposure factors for each channel (R, G, B) @@ -820,8 +804,7 @@ void omv_csi_update_awb(omv_csi_t *csi, uint32_t n_pixels) { } #endif -// This is the default snapshot function, which can be replaced in omv_csi_init functions. -int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { +static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { uint32_t length = 0; framebuffer_t *fb = csi->fb; @@ -835,11 +818,13 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { // Compress the framebuffer for the IDE preview, if not the first frame, the // framebuffer is enabled, and the image sensor doesn't support JPEG encoding. - framebuffer_update_jpeg_buffer(fb); + if (flags & OMV_CSI_CAPTURE_FLAGS_UPDATE) { + framebuffer_update_jpeg_buffer(fb); + } // Ensure that the raw frame fits into the FB. It will be switched from RGB565 to BAYER // first to save space before being cropped until it fits. - omv_csi_auto_crop_framebuffer(); + omv_csi_auto_crop_framebuffer(csi); // Restore frame buffer width and height if they were changed before. BPP is restored later. // Note that JPEG compression is done first on the framebuffer with the user settings. @@ -864,8 +849,8 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { #if USE_DCMI // Setup the size and address of the transfer - uint32_t bytes_per_pixel = omv_csi_get_src_bpp(); - uint32_t x_crop = get_dcmi_hw_crop(bytes_per_pixel); + uint32_t bytes_per_pixel = omv_csi_get_src_bpp(csi); + uint32_t x_crop = get_dcmi_hw_crop(csi, bytes_per_pixel); uint32_t line_width_bytes = resolution[csi->framesize][0] * bytes_per_pixel; // Shrink the captured pixel count by one word to allow cropping to fix alignment. @@ -928,7 +913,7 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { (uint32_t) &_line_buf, length / sizeof(uint32_t), h); } #else - uint32_t bytes_per_pixel = omv_csi_get_dst_bpp(); + uint32_t bytes_per_pixel = omv_csi_get_dst_bpp(csi); uint32_t line_width_bytes = fb->u * bytes_per_pixel; if (!line_width_bytes || @@ -1089,7 +1074,8 @@ int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { } int omv_csi_init() { - int init_ret = 0; + int ret = 0; + static omv_i2c_t i2c; // List of I2C buses to scan. uint32_t buses[][2] = { @@ -1099,47 +1085,54 @@ int omv_csi_init() { #endif }; - // Reset the csi state - memset(&csi, 0, sizeof(omv_csi_t)); + // Initialize the CSIs using this driver's ops as defaults, + // which can be overridden by sensor drivers during probe. + for (size_t i=0; ii2c = &i2c; + csi->fb = framebuffer_get(-1); + csi->abort = stm_csi_abort; + csi->config = stm_csi_config; + csi->snapshot = stm_csi_snapshot; + csi->color_palette = rainbow_table; + } // Configure the csi external clock (XCLK). if (omv_csi_set_clk_frequency(OMV_CSI_CLK_FREQUENCY) != 0) { return OMV_CSI_ERROR_TIM_INIT_FAILED; } - // Detect and initialize the image sensor. + // Detect and initialize sensor(s). for (uint32_t i = 0, n_buses = OMV_ARRAY_SIZE(buses); i < n_buses; i++) { - uint32_t id = buses[i][0], speed = buses[i][1]; - if ((init_ret = omv_csi_probe_init(id, speed)) == 0) { + // Initialize the camera bus. + omv_i2c_init(&i2c, buses[i][0], buses[i][1]); + + if (!(ret = omv_csi_probe(&i2c))) { break; } - omv_i2c_deinit(csi.i2c); + + omv_i2c_deinit(&i2c); + // Scan the next bus or fail if this is the last one. if ((i + 1) == n_buses) { - return init_ret; + return ret; } } // Configure the DCMI interface. - if (omv_csi_config(OMV_CSI_CONFIG_INIT) != 0) { - return OMV_CSI_ERROR_CSI_INIT_FAILED; + for (size_t i=0; idetected = true; } // Clear fb_enabled flag. JPEG_FB()->enabled = 0; - - // Set default color palette. - csi.color_palette = rainbow_table; - - csi.detected = true; return 0; }