Support PixArt image sensor PAJ6100 (#1365)

* Support PixArt image sensor PAJ6100
This commit is contained in:
Lake Fu 2021-06-17 04:45:14 +08:00 committed by GitHub
parent 42245a75cc
commit 15264b7dac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1193 additions and 4 deletions

View File

@ -57,6 +57,7 @@ WINC1500_DIR=drivers/winc1500
MLX90621_DIR=drivers/mlx90621
MLX90640_DIR=drivers/mlx90640
MLX90641_DIR=drivers/mlx90641
PIXART_DIR=drivers/pixart
LIBPDM_DIR=lib/libpdm
TENSORFLOW_DIR=lib/libtf
OMV_BOARD_CONFIG_DIR=$(TOP_DIR)/$(OMV_DIR)/boards/$(TARGET)/

View File

@ -0,0 +1,41 @@
###
# MIT License
#
# Copyright (c) 2021 Pixart Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
###
SRCS = $(wildcard src/*.c)
OBJS = $(addprefix $(BUILD)/, $(SRCS:.c=.o))
OBJ_DIRS = $(sort $(dir $(OBJS)))
all: | $(OBJ_DIRS) $(OBJS)
$(OBJ_DIRS):
$(MKDIR) -p $@
$(BUILD)/%.o : %.c
$(ECHO) "CC $<"
$(CC) $(CFLAGS) -c -o $@ $<
$(BUILD)/%.o : %.s
$(ECHO) "AS $<"
$(AS) $(AFLAGS) $< -o $@
-include $(OBJS:%.o=%.d)

View File

@ -0,0 +1,9 @@
#ifndef __PIXSPI__
#define __PIXSPI__
#include <stdbool.h>
#include <stdint.h>
bool pixspi_init();
void pixspi_release();
int pixspi_regs_read(uint8_t addr, uint8_t * data, uint16_t length);
int pixspi_regs_write(uint8_t addr, const uint8_t * data, uint16_t length);
#endif

View File

@ -0,0 +1,145 @@
#include "pixspi.h"
#include <stdbool.h>
#include STM32_HAL_H
#include <stdio.h>
#include <string.h>
#include "common.h"
#include "omv_boardconfig.h"
#include <py/mpconfig.h>
#ifdef ISC_SPI
#define SPI_TIMEOUT (5000) /* in ms */
#define CS_PORT ISC_SPI_SSEL_PORT
#define CS_PIN ISC_SPI_SSEL_PIN
#define W_CS_LOW() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET)
#define W_CS_HIGH() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET)
extern SPI_HandleTypeDef ISC_SPIHandle;
static bool spi_send(uint8_t *data, uint16_t len)
{
HAL_StatusTypeDef status = HAL_OK;
W_CS_LOW();
//mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
status = HAL_SPI_Transmit(
&ISC_SPIHandle, data, len, SPI_TIMEOUT);
//MICROPY_END_ATOMIC_SECTION(atomic_state);
W_CS_HIGH();
return (status == HAL_OK);
}
static bool spi_send_recv(uint8_t *txData, uint8_t *rxData, uint16_t len)
{
W_CS_LOW();
//mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
bool res = (HAL_SPI_Transmit(&ISC_SPIHandle, txData, 1, SPI_TIMEOUT) == HAL_OK);
res = (HAL_SPI_Receive(&ISC_SPIHandle, rxData, len, SPI_TIMEOUT) == HAL_OK);
//MICROPY_END_ATOMIC_SECTION(atomic_state);
W_CS_HIGH();
return res;
}
bool pixspi_init()
{
// Init SPI
memset(&ISC_SPIHandle, 0, sizeof(ISC_SPIHandle));
ISC_SPIHandle.Instance = ISC_SPI;
ISC_SPIHandle.Init.Mode = SPI_MODE_MASTER;
ISC_SPIHandle.Init.Direction = SPI_DIRECTION_2LINES;
ISC_SPIHandle.Init.DataSize = SPI_DATASIZE_8BIT;
ISC_SPIHandle.Init.CLKPolarity = SPI_POLARITY_HIGH;
ISC_SPIHandle.Init.CLKPhase = SPI_PHASE_2EDGE;
ISC_SPIHandle.Init.NSS = SPI_NSS_SOFT;
ISC_SPIHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32 ;
ISC_SPIHandle.Init.FirstBit = SPI_FIRSTBIT_MSB;
ISC_SPIHandle.Init.TIMode = SPI_TIMODE_DISABLED;
ISC_SPIHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
ISC_SPIHandle.Init.CRCPolynomial = 0;
if (HAL_SPI_Init(&ISC_SPIHandle) != HAL_OK)
{
/* Initialization Error */
return false;
}
GPIO_InitTypeDef GPIO_InitStructure;
// Re-Init GPIO for SPI SS(CS) pin, software control.
GPIO_InitStructure.Pin = ISC_SPI_SSEL_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Alternate = ISC_SPI_SSEL_AF;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(ISC_SPI_SSEL_PORT, &GPIO_InitStructure);
// Default high.
W_CS_HIGH();
return true;
}
void pixspi_release()
{
W_CS_LOW();
HAL_SPI_DeInit(&ISC_SPIHandle);
}
int pixspi_regs_read(uint8_t addr, uint8_t * data, uint16_t length)
{
if ((addr & 0x80))
{
#ifdef DEBUG
printf("pixspi_regs_read() address (0x%x) overflow.\n", addr);
#endif
return -1;
}
addr |= 0x80;
if (!spi_send_recv(&addr, data, length))
{
#ifdef DEBUG
printf("spi_send_recv() failed.\n");
#endif
return -1;
}
return 0;
}
int pixspi_regs_write(uint8_t addr, const uint8_t * data, uint16_t length)
{
uint8_t buff[64] = {};
if ((addr & 0x80) == 0x80)
{
#ifdef DEBUG
printf("pixspi_regs_read() address (0x%x) overflow.\n", addr);
#endif
return -1;
}
int32_t remaining = length;
const static uint16_t MAX_LENGTH = 255;
do
{
uint16_t len = remaining>MAX_LENGTH?MAX_LENGTH:remaining;
buff[0] = addr;
memcpy(buff+1, data, len);
bool res = spi_send(buff, len + 1);
if (!res)
{
#ifdef DEBUG
printf("spi_send() failed.\n");
#endif
return -1;
}
remaining = remaining - MAX_LENGTH;
}
while (remaining > 0);
return 0;
}
#else
bool pixspi_init() { return false; }
void pixspi_release() {}
int pixspi_regs_read(uint8_t addr, uint8_t * data, uint16_t length) { return -1; }
int pixspi_regs_write(uint8_t addr, const uint8_t * data, uint16_t length) { return -1; }
#endif

View File

@ -37,6 +37,7 @@ SRCS += $(addprefix sensors/, \
lepton.c \
hm01b0.c \
gc2145.c \
paj6100.c \
)
SRCS += $(addprefix modules/, \

View File

@ -46,6 +46,7 @@
#define OMV_ENABLE_MT9V034 (0)
#define OMV_ENABLE_LEPTON (0)
#define OMV_ENABLE_HM01B0 (0)
#define OMV_ENABLE_PAJ6100 (0)
// Set which OV767x sensor is used
#define OMV_OV7670_VERSION (75)

View File

@ -56,6 +56,7 @@
#define OMV_ENABLE_MT9V034 (0)
#define OMV_ENABLE_LEPTON (0)
#define OMV_ENABLE_HM01B0 (0)
#define OMV_ENABLE_PAJ6100 (0)
// Enable sensor features
#define OMV_ENABLE_OV5640_AF (0)

View File

@ -56,6 +56,7 @@
#define OMV_ENABLE_MT9V034 (0)
#define OMV_ENABLE_LEPTON (0)
#define OMV_ENABLE_HM01B0 (0)
#define OMV_ENABLE_PAJ6100 (0)
// Enable sensor features
#define OMV_ENABLE_OV5640_AF (0)

View File

@ -62,6 +62,7 @@
#define OMV_ENABLE_MT9V034 (1)
#define OMV_ENABLE_LEPTON (1)
#define OMV_ENABLE_HM01B0 (0)
#define OMV_ENABLE_PAJ6100 (1)
// Enable sensor features
#define OMV_ENABLE_OV5640_AF (0)

View File

@ -66,6 +66,7 @@
#define OMV_ENABLE_MT9V034 (1)
#define OMV_ENABLE_LEPTON (1)
#define OMV_ENABLE_HM01B0 (0)
#define OMV_ENABLE_PAJ6100 (1)
// Enable sensor features
#define OMV_ENABLE_OV5640_AF (0)

View File

@ -63,6 +63,7 @@
#define OMV_ENABLE_MT9V034 (0)
#define OMV_ENABLE_LEPTON (0)
#define OMV_ENABLE_HM01B0 (0)
#define OMV_ENABLE_PAJ6100 (0)
// Enable sensor features
#define OMV_ENABLE_OV5640_AF (1)

View File

@ -42,6 +42,8 @@
#define LEPTON_ID (0x54)
#define HM01B0_ID (0xB0)
#define GC2145_ID (0x21)
// Wide ID
#define PAJ6100_ID (0x6100)
typedef enum {
FRAMESIZE_INVALID = 0,

View File

@ -44,6 +44,7 @@ OMV_CFLAGS += -I$(TOP_DIR)/$(WINC1500_DIR)/include/
OMV_CFLAGS += -I$(TOP_DIR)/$(MLX90621_DIR)/include/
OMV_CFLAGS += -I$(TOP_DIR)/$(MLX90640_DIR)/include/
OMV_CFLAGS += -I$(TOP_DIR)/$(MLX90641_DIR)/include/
OMV_CFLAGS += -I$(TOP_DIR)/$(PIXART_DIR)/include/
OMV_CFLAGS += -I$(TOP_DIR)/$(TENSORFLOW_DIR)/$(CPU)/
OMV_CFLAGS += -I$(TOP_DIR)/$(LIBPDM_DIR)/
@ -113,6 +114,7 @@ endif
FIRM_OBJ += $(wildcard $(BUILD)/$(MLX90621_DIR)/src/*.o)
FIRM_OBJ += $(wildcard $(BUILD)/$(MLX90640_DIR)/src/*.o)
FIRM_OBJ += $(wildcard $(BUILD)/$(MLX90641_DIR)/src/*.o)
FIRM_OBJ += $(wildcard $(BUILD)/$(PIXART_DIR)/src/*.o)
#------------- OpenMV Objects ----------------#
FIRM_OBJ += $(addprefix $(BUILD)/$(CMSIS_DIR)/src/, \
@ -150,6 +152,7 @@ FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/sensors/, \
lepton.o \
hm01b0.o \
gc2145.o \
paj6100.o \
)
FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/modules/, \
@ -514,6 +517,7 @@ UVC_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/sensors/, \
lepton.o \
hm01b0.o \
gc2145.o \
paj6100.o \
)
UVC_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/imlib/,\
@ -541,6 +545,7 @@ endif
UVC_OBJ += $(wildcard $(BUILD)/$(MLX90621_DIR)/src/*.o)
UVC_OBJ += $(wildcard $(BUILD)/$(MLX90640_DIR)/src/*.o)
UVC_OBJ += $(wildcard $(BUILD)/$(MLX90641_DIR)/src/*.o)
UVC_OBJ += $(wildcard $(BUILD)/$(PIXART_DIR)/src/*.o)
endif
ifeq ($(OMV_ENABLE_CM4), 1)
@ -600,6 +605,7 @@ endif
$(MAKE) -C $(MLX90621_DIR) BUILD=$(BUILD)/$(MLX90621_DIR) CFLAGS="$(CFLAGS) -MMD"
$(MAKE) -C $(MLX90640_DIR) BUILD=$(BUILD)/$(MLX90640_DIR) CFLAGS="$(CFLAGS) -MMD"
$(MAKE) -C $(MLX90641_DIR) BUILD=$(BUILD)/$(MLX90641_DIR) CFLAGS="$(CFLAGS) -MMD"
$(MAKE) -C $(PIXART_DIR) BUILD=$(BUILD)/$(PIXART_DIR) CFLAGS="$(CFLAGS) -MMD"
$(MAKE) -C $(OMV_DIR) BUILD=$(BUILD)/$(OMV_DIR) CFLAGS="$(CFLAGS) -MMD"
ifeq ($(CUBEAI), 1)
$(MAKE) -C $(CUBEAI_DIR) BUILD=$(BUILD)/$(CUBEAI_DIR) CFLAGS="$(CFLAGS) -fno-strict-aliasing -MMD"

View File

@ -25,6 +25,7 @@
#include "mt9m114.h"
#include "lepton.h"
#include "hm01b0.h"
#include "paj6100.h"
#include "gc2145.h"
#include "systick.h"
#include "framebuffer.h"
@ -42,6 +43,10 @@
#define OMV_ENABLE_SENSOR_MDMA_TOTAL_OFFLOAD 1
#endif
#if (OMV_ENABLE_PAJ6100 == 1)
#define OMV_ENABLE_NONI2CIS
#endif
sensor_t sensor = {};
static TIM_HandleTypeDef TIMHandle = {.Instance = DCMI_TIM};
static DMA_HandleTypeDef DMAHandle = {.Instance = DMA2_Stream1};
@ -386,20 +391,35 @@ int sensor_init()
systick_sleep(10);
sensor.slv_addr = cambus_scan(&sensor.bus);
#ifndef OMV_ENABLE_NONI2CIS
if (sensor.slv_addr == 0) {
return -2;
}
#endif
}
}
}
// Clear sensor chip ID.
sensor.chip_id = 0;
sensor.chip_id_w = 0;
// Set default snapshot function.
sensor.snapshot = sensor_snapshot;
switch (sensor.slv_addr) {
#if (OMV_ENABLE_PAJ6100 == 1)
case 0:
if (paj6100_detect(&sensor)) {
// Found PixArt PAJ6100
sensor.chip_id_w = PAJ6100_ID;
sensor.pwdn_pol = ACTIVE_LOW;
sensor.reset_pol = ACTIVE_LOW;
break;
}
// Okay, there is not any sensor be detected.
return -2;
#endif
#if (OMV_ENABLE_OV2640 == 1)
case OV2640_SLV_ADDR: // Or OV9650.
cambus_readb(&sensor.bus, sensor.slv_addr, OV_CHIP_ID, &sensor.chip_id);
@ -452,8 +472,7 @@ int sensor_init()
return -3;
break;
}
switch (sensor.chip_id) {
switch (sensor.chip_id_w) {
#if (OMV_ENABLE_OV2640 == 1)
case OV2640_ID:
if (extclk_config(OV2640_XCLK_FREQ) != 0) {
@ -544,6 +563,15 @@ int sensor_init()
break;
#endif //(OMV_ENABLE_GC2145 == 1)
#if (OMV_ENABLE_PAJ6100 == 1)
case PAJ6100_ID:
if (extclk_config(PAJ6100_XCLK_FREQ) != 0) {
return -3;
}
init_ret = paj6100_init(&sensor);
break;
#endif // (OMV_ENABLE_PAJ6100 == 1)
default:
return -3;
break;
@ -639,7 +667,7 @@ int sensor_reset()
int sensor_get_id()
{
return sensor.chip_id;
return sensor.chip_id_w;
}
bool sensor_is_detected()

788
src/omv/sensors/paj6100.c Normal file
View File

@ -0,0 +1,788 @@
/*
* File: paj6100.c
* Created Date: Tuesday, May 25th 2021, 10:45:35 am
* Author: Lake Fu
* -----
* Last Modified: Tuesday June 15th 2021 2:54:43 pm
* Modified By: Lake Fu at <lake_fu@pixart.com>
* -----
* MIT License
*
* Copyright (c) 2021 Pixart Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* -----
* HISTORY:
* Date By Comments
* ---------- --- ----------------------------------------------------------
*/
#include "omv_boardconfig.h"
#if (OMV_ENABLE_PAJ6100 == 1)
#include <stdio.h>
#include <stdbool.h>
#include "systick.h"
#include "sensor.h"
#include "framebuffer.h"
#include "paj6100.h"
#include "paj6100_reg.h"
#include "pixspi.h"
#define CACHE_BANK
#ifdef DEBUG
// For dump initial result before connect to IDE,
// we use a global variable to store it.
static int8_t init_res;
#endif
static int16_t bank_cache = -1;
// Exposure time related paramaters +
#define QVGA_MAX_EXPO_PA 85161
#define QQVGA_MAX_EXPO_PA 25497
#define R_AE_MinGain 1
#define R_AE_MaxGain 8
static float R_FrameTime = 6000000/30;
static long R_AE_MaxExpoTime;
static long R_AE_MinExpoTime = 120/6 + 0.5f;
#define CAL_MAX_EXPO_TIME(PA) (fast_roundf(((float)(R_FrameTime-PA))/6))
#define L_TARGET 127
#define AE_LOCK_RANGE_IN 8
#define AE_LOCK_RANGE_OUT 16
static uint32_t l_total = 0;
static uint32_t l_target_total = 0;
static uint32_t lt_lockrange_in_ubound, lt_lockrange_in_lbound;
static uint32_t lt_lockrange_out_ubound, lt_lockrange_out_lbound;
static bool ae_converged_flag = false;
static uint8_t skip_frame = 0;
static bool is_ae_enabled = true;
static int exp_us_cache = -1;
// Exposure time related paramaters -
static int set_auto_gain(sensor_t *sensor, int enable,
float gain_db, float gain_db_ceiling);
static int get_gain_db(sensor_t *sensor, float *gain_db);
static int bank_switch(uint8_t bank)
{
#ifdef CACHE_BANK
if (bank_cache == bank)
return 0;
#endif
if (pixspi_regs_write(REG_BANK_SWITCH, &bank, 1))
{
printf("Bank switch failed.\n");
return -1;
}
bank_cache = bank;
return 0;
}
static int read_regs_w_bank(uint8_t bank, uint8_t addr, uint8_t * buff, uint16_t len)
{
if (bank_switch(bank)) return -1;
return pixspi_regs_read(addr, buff, len);
}
static int write_regs_w_bank(uint8_t bank, uint8_t addr, uint8_t * buff, uint16_t len)
{
if (bank_switch(bank)) return -1;
return pixspi_regs_write(addr, buff, len);
}
static int init_sensor(sensor_t * sensor)
{
#define ta_seq low_active_R_ABC_Avg_UBx50_T_BLACINV_EnHx1_T_SIG_REFx1
int arr_size = sizeof(ta_seq)/sizeof(ta_seq[0]);
for (int i=0; i<arr_size; i+=2)
{
#ifndef DEBUG
int
#endif
init_res = pixspi_regs_write(ta_seq[i], &ta_seq[i+1], 1);
if (init_res)
{
return init_res;
}
}
// Clear bank cache.
bank_cache = -1;
return 0;
}
//-----------------------------------------------------------------
static int set_exposure(sensor_t *sensor, int exp_us, bool protected)
{
// 3642T
static const uint32_t EXPOSURE_BASE = 3642 / (PAJ6100_XCLK_FREQ/1000000.0f) + 0.5f;
int ret;
uint32_t cmd_expo /* 24-bit available */;
uint16_t exp_offset;
#ifdef DEBUG_AE
printf("set_exposure() = %d\n", exp_us);
#endif
if (exp_us == 0)
return 0;
if (exp_us >= EXPOSURE_BASE)
{
int param;
if (sensor->framesize == FRAMESIZE_QVGA)
param = 88803;
else if (sensor->framesize == FRAMESIZE_QQVGA)
param = 29139; // QQVGA
else
param = 88803;
int max_cmd_expo = R_FrameTime - param;
cmd_expo = (exp_us - EXPOSURE_BASE) * ((float)PAJ6100_XCLK_FREQ/1000000) + 0.5f;
if (protected)
{
if (cmd_expo > max_cmd_expo)
{
cmd_expo = max_cmd_expo;
exp_us = (cmd_expo + 3642) / ((float)PAJ6100_XCLK_FREQ/1000000) + 0.5f;
printf("Exposure time overflow, reset to %dus.\n", exp_us);
}
}
exp_offset = 0;
}
else
{
cmd_expo = 0;
exp_offset = (EXPOSURE_BASE - exp_us) * ((float)PAJ6100_XCLK_FREQ/1000000) + 0.5;
}
#ifdef DEBUG_AE
printf("target - cmd_exp: %ld, exp_offset: %d\n", cmd_expo, exp_offset);
#endif
uint8_t buff[3] = {
(uint8_t)(cmd_expo & 0xff),
(uint8_t)((cmd_expo >> 8) & 0xff),
(uint8_t)((cmd_expo >> 16) & 0xff),
};
ret = write_regs_w_bank(BANK_0, REG_CMD_EXPO_L, buff, 3);
buff[0] = (uint8_t)(exp_offset & 0xff);
buff[1] = (uint8_t)((exp_offset >> 8) & 0xff);
ret |= write_regs_w_bank(BANK_0, REG_EXP_OFFSET_L, buff, 2);
buff[0] = 1;
ret |= write_regs_w_bank(BANK_1, REG_ISP_UPDATE, buff, 1);
if (ret)
{
printf("Failed to write cmd_expo or exp_offset.\n");
return -1;
}
exp_us_cache = exp_us;
return ret;
}
static int get_exposure(sensor_t *sensor)
{
int ret;
uint8_t buff[3] = {};
uint32_t cmd_expo /* 24-bit available */;
uint16_t exp_offset;
if (exp_us_cache >= 0)
return exp_us_cache;
ret = read_regs_w_bank(BANK_0, REG_CMD_EXPO_L, buff, 3);
if (ret)
{
printf("Read cmd_expo failed.\n");
return -1;
}
cmd_expo = buff[0] + (((uint32_t)buff[1]) << 8) + (((uint32_t)buff[2]) << 16);
ret = read_regs_w_bank(BANK_0, REG_EXP_OFFSET_L, buff, 2);
if (ret)
{
printf("Read exp_offset failed.\n");
return -1;
}
exp_offset = buff[0] + (((uint16_t)buff[1]) << 8);
#ifdef DEBUG_AE
printf("cmd_exp: %ld, exp_offset: %d\n", cmd_expo, exp_offset);
#endif
if (exp_offset == 0)
{
exp_us_cache = (cmd_expo + 3642) / (PAJ6100_XCLK_FREQ/1000000);
}
else
{
exp_us_cache = (3642 - exp_offset) / (PAJ6100_XCLK_FREQ/1000000);
}
return exp_us_cache;
}
static void blc_freeze(bool enable)
{
uint8_t tmp;
tmp = enable?2:0;
write_regs_w_bank(0, 0x0E, &tmp, 1);
tmp = 1;
write_regs_w_bank(1, 0x00, &tmp, 1); // Update flag
}
static void auto_exposure(sensor_t *sensor)
{
if (!is_ae_enabled)
return;
if (skip_frame)
{
--skip_frame;
return;
}
if ((lt_lockrange_in_lbound <= l_total) && (l_total <= lt_lockrange_in_ubound))
{
ae_converged_flag = true;
}
else if ((l_total > lt_lockrange_out_ubound) || (l_total < lt_lockrange_out_lbound))
{
ae_converged_flag = false;
}
blc_freeze(ae_converged_flag);
if (ae_converged_flag == false)
{
float gain_db = 0;
if (get_gain_db(sensor, &gain_db))
{
printf("Get Gain DB failed.\n");
return;
}
uint32_t gain;
if (gain_db < 6) {
// 1x gain
gain = 1;
} else if (gain_db < 12) {
// 2x gain
gain = 2;
} else if (gain_db < 18) {
// 4x gain
gain = 4;
} else {
// 8x gain
gain = 8;
}
int expo = get_exposure(sensor);
if (expo < 0)
{
printf("Get exposure failed.\n");
return;
}
#ifdef DEBUG_AE
printf("Current Gain: %ldx, Expo: %d\n", gain, expo);
#endif
uint32_t GEP = gain * expo;
float l_ratio = ((float)l_target_total) / l_total;
float GEP_target = GEP * l_ratio;
uint32_t expo_target = IM_MIN(IM_MAX(fast_roundf(GEP_target / R_AE_MinGain), R_AE_MinExpoTime), R_AE_MaxExpoTime);
#ifdef DEBUG_AE
printf("GEP: %ld ", GEP);
printf("GEP_target: %d (x1000), L_Ratio: %d (x1000)\n",
(int)(GEP_target*1000), (int)(l_ratio*1000));
printf("1st Expo Target: %ld (max: %ld) ", expo_target, R_AE_MaxExpoTime);
#endif
uint32_t gain_target;
float tmp_gain = GEP_target/expo_target;
if (tmp_gain <= 1) gain_target = 1;
else if (tmp_gain <= 2) gain_target = 2;
else if (tmp_gain <= 4) gain_target = 4;
else gain_target = 8;
//gain_target = IM_MIN(IM_MAX(fast_ceilf(GEP_target/expo_target), R_AE_MinGain), R_AE_MaxGain);
gain_db = 20 * (fast_log((float)gain_target)/fast_log(10.0));
expo_target = IM_MIN(IM_MAX(fast_roundf(GEP_target / gain_target), R_AE_MinExpoTime), R_AE_MaxExpoTime);
#ifdef DEBUG_AE
printf("Gain Target: %ld (%d DB (x1000))\n", gain_target, (int)(gain_db*1000));
printf("Final Expo Target: %ld (max: %ld) \n", expo_target, R_AE_MaxExpoTime);
#endif
set_exposure(sensor, expo_target, false);
set_auto_gain(sensor, false, gain_db, 0);
skip_frame = 0;
}
}
//-----------------------------------------------------------------
static int sleep(sensor_t *sensor, int enable)
{
int ret;
uint8_t val;
if (enable) {
ret = read_regs_w_bank(BANK_1, REG_CMD_SENSOR_MODE, &val, 1);
if (ret) {
printf("Failed to read REG_CMD_SENSOR_MODE.\n");
return -1;
}
val |= (1 << 7);
ret = write_regs_w_bank(BANK_1, REG_CMD_SENSOR_MODE, &val, 1);
if (ret) {
printf("Failed to write REG_CMD_SENSOR_MODE.\n");
return -1;
}
// Sleep 30ms
systick_sleep(30);
ret = read_regs_w_bank(BANK_1, REG_CMD_LPM_ENH, &val, 1);
if (ret) {
printf("Failed to read REG_CMD_SENSOR_MODE.\n");
return -1;
}
val |= (1 << 6);
ret = write_regs_w_bank(BANK_1, REG_CMD_LPM_ENH, &val, 1);
if (ret) {
printf("Failed to write REG_CMD_LPM_ENH.\n");
return -1;
}
} else {
ret = read_regs_w_bank(BANK_1, REG_CMD_SENSOR_MODE, &val, 1);
if (ret) {
#ifdef DEBUG
printf("Failed to read REG_CMD_SENSOR_MODE.\n");
#endif
return -1;
}
val &= ~(1 << 7);
val &= ~(1 << 6);
ret = write_regs_w_bank(BANK_1, REG_CMD_SENSOR_MODE, &val, 1);
if (ret) {
#ifdef DEBUG
printf("Failed to write REG_CMD_SENSOR_MODE.\n");
#endif
return -1;
}
}
return 0;
}
static int read_reg(sensor_t *sensor, uint16_t reg_addr)
{
uint8_t data;
if (pixspi_regs_read((uint8_t)reg_addr, &data, 1))
return -1;
return data;
}
static int write_reg(sensor_t *sensor, uint16_t reg_addr, uint16_t reg_data)
{
uint8_t data = reg_data;
// Clear bank cache.
bank_cache = -1;
return pixspi_regs_write((uint8_t)reg_addr, &data, 1);
}
static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
{
if (pixformat != PIXFORMAT_GRAYSCALE)
return -1;
return 0;
}
static int set_framesize(sensor_t *sensor, framesize_t framesize)
{
int ret = 0;
uint16_t w = resolution[framesize][0];
uint16_t h = resolution[framesize][1];
uint8_t aavg_VnH, abc_start_line, voffset, abc_sample_size;
ret |= read_regs_w_bank(BANK_0, REG_CMD_AAVG_V /* REG_CMD_AAVG_H */, &aavg_VnH, 1);
ret |= read_regs_w_bank(BANK_0, REG_ABC_START_LINE, &abc_start_line, 1);
ret |= read_regs_w_bank(BANK_1, REG_ABC_SAMPLE_SIZE, &abc_sample_size, 1);
if (ret)
{
printf("Failed to read Average mode registers.\n");
return -1;
}
switch (framesize)
{
case FRAMESIZE_QVGA:
aavg_VnH &= ~((1 << 3) | (1 << 2));
abc_start_line = (abc_start_line & ~(0x07 << 1)) | (2 << 1);
voffset = 2;
abc_sample_size = (abc_sample_size & ~(0x07)) | 6;
R_AE_MaxExpoTime = CAL_MAX_EXPO_TIME(QVGA_MAX_EXPO_PA);
break;
case FRAMESIZE_QQVGA:
aavg_VnH |= (1 << 3) | (1 << 2);
abc_start_line = (abc_start_line & ~(0x07 << 1)) | (1 << 1);
voffset = 1;
abc_sample_size = (abc_sample_size & ~(0x07)) | 5;
R_AE_MaxExpoTime = CAL_MAX_EXPO_TIME(QQVGA_MAX_EXPO_PA);
break;
default:
printf("PAJ6100 only support QVGA & QQVGA now.\n");
return -1;
}
ret |= write_regs_w_bank(BANK_0, REG_CMD_AAVG_V /* REG_CMD_AAVG_H */, &aavg_VnH, 1);
ret |= write_regs_w_bank(BANK_0, REG_ABC_START_LINE, &abc_start_line, 1);
ret |= write_regs_w_bank(BANK_1, REG_CP_WOI_VOFFSET, &voffset, 1);
ret |= write_regs_w_bank(BANK_1, REG_ABC_SAMPLE_SIZE, &abc_sample_size, 1);
if (ret)
{
printf("Failed to write Average mode registers.\n");
return -1;
}
l_target_total = L_TARGET * w * h;
lt_lockrange_in_ubound = (L_TARGET + AE_LOCK_RANGE_IN) * w * h;
lt_lockrange_in_lbound = (L_TARGET - AE_LOCK_RANGE_IN) * w * h;
lt_lockrange_out_ubound = (L_TARGET + AE_LOCK_RANGE_OUT) * w * h;
lt_lockrange_out_lbound = (L_TARGET - AE_LOCK_RANGE_OUT) * w * h;
return 0;
}
static int set_contrast(sensor_t *sensor, int level)
{
return 0;
}
static int set_brightness(sensor_t *sensor, int level)
{
return 0;
}
static int set_saturation(sensor_t *sensor, int level)
{
return 0;
}
static int set_gainceiling(sensor_t *sensor, gainceiling_t gainceiling)
{
return 0;
}
static int set_special_effect(sensor_t *sensor, sde_t sde)
{
return 0;
}
static int set_auto_gain(sensor_t *sensor, int enable,
float gain_db, float gain_db_ceiling)
{
if (enable)
return 0;
int ret = 0;
uint8_t val, fgh, ggh;
if (gain_db < 6) {
// 1x gain
fgh = 0;
ggh = 0;
} else if (gain_db < 12) {
// 2x gain
fgh = 0;
ggh = 1;
} else if (gain_db < 18) {
// 4x gain
fgh = 2;
ggh = 1;
} else {
// 8x gain
fgh = 3;
ggh = 1;
}
ret = read_regs_w_bank(BANK_0, REG_FGH, &val, 1);
fgh = (val & ~(3 << 4)) | (fgh << 4); // fgh[5:4] = 0
ret |= read_regs_w_bank(BANK_0, REG_GGH, &val, 1);
ggh = (val & ~(1 << 7)) | (ggh << 7); // ggh[7] = 1
if (ret) {
printf("Failed to read FGH or GGH.\n");
} else {
write_regs_w_bank(BANK_0, REG_FGH, &fgh, 1);
write_regs_w_bank(BANK_0, REG_GGH, &ggh, 1);
val = 1;
write_regs_w_bank(BANK_1, REG_ISP_UPDATE, &val, 1);
}
return 0;
}
static int get_gain_db(sensor_t *sensor, float *gain_db)
{
int ret = 0;
uint8_t val;
uint8_t fgh, ggh;
ret |= read_regs_w_bank(BANK_0, REG_FGH, &val, 1);
fgh = (val >> 4) & 0x03 ;
ret |= read_regs_w_bank(BANK_0, REG_GGH, &val, 1);
ggh = (val >> 7) & 0x01 ;
if (ret) {
printf("Failed to read FGH or GGH.\n");
return -1;
}
if (ggh == 0 && fgh == 0) {
*gain_db = 0;
} else if (ggh == 1) {
switch (fgh)
{
case 0:
*gain_db = 6;
break;
case 2:
*gain_db = 12;
break;
case 3:
*gain_db = 18;
break;
default:
{
printf("Read a undefined FGH value (%d).\n", fgh);
return -1;
}
}
}
return 0;
}
static int set_auto_exposure(sensor_t *sensor, int enable, int exposure_us)
{
if (enable) {
is_ae_enabled = true;
return 0;
} else {
is_ae_enabled = false;
return set_exposure(sensor, exposure_us, false);
}
}
static int get_exposure_us(sensor_t *sensor, int *exposure_us) {
int ret = get_exposure(sensor);
if (ret >= 0)
{
*exposure_us = ret;
return 0;
}
return ret;
}
static int set_auto_whitebal(sensor_t *sensor, int enable, float r_gain_db, float g_gain_db, float b_gain_db)
{
return 0;
}
static int get_rgb_gain_db(sensor_t *sensor, float *r_gain_db, float *g_gain_db, float *b_gain_db)
{
return 0;
}
static int set_hmirror(sensor_t *sensor, int enable)
{
int ret;
uint8_t val;
ret = read_regs_w_bank(BANK_0, REG_CMD_HSYNC_INV, &val, 1);
if (ret) {
printf("Failed to read REG_CMD_HSYNC_INV.\n");
return -1;
}
val = enable==1?val|(0x01):val&~(0x01);
ret = write_regs_w_bank(BANK_0, REG_CMD_HSYNC_INV, &val, 1);
ret = write_regs_w_bank(BANK_1, REG_ISP_UPDATE, &val, 1);
if (ret) {
printf("Failed to write REG_CMD_HSYNC_INV.\n");
}
return ret;
}
static int set_vflip(sensor_t *sensor, int enable)
{
int ret;
uint8_t val;
ret = read_regs_w_bank(BANK_0, REG_CMD_VSYNC_INV, &val, 1);
if (ret) {
printf("Failed to read REG_CMD_VSYNC_INV.\n");
return -1;
}
val = enable==1?val|(0x01<<1):val&~(0x01<<1);
ret = write_regs_w_bank(BANK_0, REG_CMD_VSYNC_INV, &val, 1);
ret = write_regs_w_bank(BANK_1, REG_ISP_UPDATE, &val, 1);
if (ret) {
printf("Failed to write REG_CMD_VSYNC_INV.\n");
}
return ret;
}
static int set_lens_correction(sensor_t *sensor, int enable, int radi, int coef)
{
return 0;
}
static int reset(sensor_t *sensor)
{
int ret;
bank_cache = -1;
exp_us_cache = -1;
#ifdef DEBUG
uint8_t part_id_l = 0, part_id_h = 0;
read_regs_w_bank(0, 0x00, &part_id_l, 1);
read_regs_w_bank(0, 0x01, &part_id_h, 1);
printf("Part ID 0x%x 0x%x\n", part_id_l, part_id_h);
printf("init_res: %d\n", init_res);
#endif
// Re-init sensor every time.
init_sensor(sensor);
// Fetch default R_Frame_Time
uint8_t buff[3] = {};
ret = read_regs_w_bank(BANK_0, REG_FRAME_TIME_L, buff, 3);
if (ret) {
printf("Read cmd_expo failed.\n");
} else {
R_FrameTime = buff[0] + (((uint32_t)buff[1]) << 8) + (((uint32_t)buff[2]) << 16);
}
// Default Gain 2x
uint8_t val, fgh, ggh;
ret = read_regs_w_bank(BANK_0, REG_FGH, &val, 1);
fgh = (val & ~(3 << 4)); // fgh[5:4] = 0
ret |= read_regs_w_bank(BANK_0, REG_GGH, &val, 1);
ggh = (val & ~(1 << 7)) | (0x01 << 7); // ggh[7] = 1
if (ret) {
printf("Failed to read FGH or GGH.\n");
} else {
write_regs_w_bank(BANK_0, REG_FGH, &fgh, 1);
write_regs_w_bank(BANK_0, REG_GGH, &ggh, 1);
val = 1;
write_regs_w_bank(BANK_1, REG_ISP_UPDATE, &val, 1);
}
return 0;
}
static int paj6100_snapshot(sensor_t *sensor, image_t *image, uint32_t flags)
{
int res = sensor_snapshot(sensor, image, flags);
if (res == 0)
{
l_total = 0;
int iml = image->h * image->w;
for (int i=0; i<iml; ++i)
l_total += image->data[i];
// PAJ6100 doesn't support HW auto-exposure,
// we provide a software implementation.
auto_exposure(sensor);
}
return res;
}
int paj6100_init(sensor_t *sensor)
{
// Initialize sensor structure.
sensor->gs_bpp = 1;
sensor->reset = reset;
sensor->sleep = sleep;
sensor->read_reg = read_reg;
sensor->write_reg = write_reg;
sensor->set_pixformat = set_pixformat;
sensor->set_framesize = set_framesize;
sensor->set_contrast = set_contrast;
sensor->set_brightness = set_brightness;
sensor->set_saturation = set_saturation;
sensor->set_gainceiling = set_gainceiling;
sensor->set_auto_gain = set_auto_gain;
sensor->get_gain_db = get_gain_db;
sensor->set_auto_exposure = set_auto_exposure;
sensor->get_exposure_us = get_exposure_us;
sensor->set_auto_whitebal = set_auto_whitebal;
sensor->get_rgb_gain_db = get_rgb_gain_db;
sensor->set_hmirror = set_hmirror;
sensor->set_vflip = set_vflip;
sensor->set_special_effect = set_special_effect;
sensor->set_lens_correction = set_lens_correction;
sensor->snapshot = paj6100_snapshot;
// Set sensor flags
SENSOR_HW_FLAGS_SET(sensor, SENSOR_HW_FLAGS_VSYNC, 1);
SENSOR_HW_FLAGS_SET(sensor, SENSOR_HW_FLAGS_HSYNC, 1);
SENSOR_HW_FLAGS_SET(sensor, SENSOR_HW_FLAGS_PIXCK, 1);
SENSOR_HW_FLAGS_SET(sensor, SENSOR_HW_FLAGS_FSYNC, 0);
SENSOR_HW_FLAGS_SET(sensor, SENSOR_HW_FLAGS_JPEGE, 0);
init_sensor(sensor);
return 0;
}
bool paj6100_detect(sensor_t *sensor)
{
int ret = 0;
uint8_t part_id_l, part_id_h;
DCMI_RESET_HIGH();
systick_sleep(10);
if (!pixspi_init())
{
printf("Initial pixspi failed.\n");
return false;
}
ret |= pixspi_regs_read(0x00, &part_id_l, 1);
ret |= pixspi_regs_read(0x01, &part_id_h, 1);
#ifdef DEBUG
printf("Part ID 0x%x 0x%x\n", part_id_l, part_id_h);
#endif
if (ret == 0 &&
(part_id_l == 0x00 && part_id_h == 0x61))
return true; // Got you.
pixspi_release();
return false;
}
#endif //(OMV_ENABLE_PAJ6100 == 1)

16
src/omv/sensors/paj6100.h Normal file
View File

@ -0,0 +1,16 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2019 Lake Fu <lake_fu@pixart.com>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* PAJ6100 driver.
*
*/
#ifndef __PAJ6100_H__
#define __PAJ6100_H__
#define PAJ6100_XCLK_FREQ 6000000
bool paj6100_detect(sensor_t *sensor);
int paj6100_init(sensor_t *sensor);
#endif

View File

@ -0,0 +1,146 @@
/*
* File: paj6100u6_reg.h
* Created Date: Tuesday, May 25th 2021, 10:45:35 am
* Author: Lake Fu
* -----
* Last Modified: Sunday May 30th 2021 2:39:45 pm
* Modified By: Lake Fu at <lake_fu@pixart.com>
* -----
* MIT License
*
* Copyright (c) 2021 Pixart Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* -----
* HISTORY:
* Date By Comments
* ---------- --- ----------------------------------------------------------
*/
#include <stdint.h>
#define REG_BANK_SWITCH 0x7F
// =======================================
#define BANK_0 0x00
// ---------------------------------------
#define REG_CMD_HSYNC_INV 0X07 // bit7
#define REG_CMD_VSYNC_INV 0X07 // bit6
#define REG_CMD_AAVG_V 0X07 // bit3
#define REG_CMD_AAVG_H 0X07 // bit2
#define REG_ABC_START_LINE 0x0D // bit3:1
#define REG_FGH 0x11 // bit5:4
#define REG_GGH 0x12 // bit7
#define REG_EXP_OFFSET_L 0x18
#define REG_EXP_OFFSET_H 0x19
#define REG_CMD_EXPO_L 0x48
#define REG_CMD_EXPO_H 0x49
#define REG_CMD_EXPO_2H 0x4A
#define REG_FRAME_TIME_L 0x7A
#define REG_FRAME_TIME_H 0x7B
#define REG_FRAME_TIME_2H 0x7C
// =======================================
#define BANK_1 0x01
// ---------------------------------------
#define REG_ISP_UPDATE 0x00
#define REG_CMD_SENSOR_MODE 0X23 // bit7
#define REG_CMD_LPM_ENH 0x23 // bit6
#define REG_CP_WOI_VOFFSET 0x45
#define REG_ABC_SAMPLE_SIZE 0x46 // bit2:0
// 20180504
static const uint8_t low_active_R_ABC_Avg_UBx50_T_BLACINV_EnHx1_T_SIG_REFx1[] = {
//address, value
0x7F, 0x00,
0x0C, 0x1D, // software reset
0x7F, 0x00,
0x07, 0x00, // low active
0x08, 0x03,
0x0B, 0x0E,
0x0D, 0xC5,
0x0F, 0x32, //R_ABC_Avg_UB=50
0x11, 0x41, //R_global=1
0x13, 0xD2,
0x14, 0xFE,
0x15, 0x00, // R_fg_fast=0
0x17, 0x02, //03
0x1A, 0x07,
0x1B, 0x08,
0x20, 0x00, //R_fast_powerdn_WakeupTime=0
0x25, 0x78,
0x29, 0x28,
0x2B, 0x06,
0x2F, 0x0E,
0x30, 0x0E,
0x34, 0x0F,
0x35, 0x0F,
0x3A, 0x28,
0x45, 0x17,
0x46, 0x17,
0x48, 0x10, //EXP
0x49, 0x27, //EXP
0x4A, 0x00, //EXP
0x4D, 0x0D,
0x4E, 0x20,
0x62, 0x12,
0x64, 0x02,
0x67, 0x0A,
0x69, 0x0A,
0x6C, 0x0B,
0x6E, 0x0B,
0x71, 0x0A,
0x73, 0x1D,
0x75, 0x1E,
0x77, 0x0B,
0x7F, 0x01,
0x01, 0x14,
0x02, 0x02, //
0x04, 0x96,
0x05, 0x03,
0x06, 0x46,
0x0D, 0x9F, //T_BLACINV_EnH=1
0x0E, 0x11, //T_SIG_REF=1
0x0F, 0x48,
0x10, 0x10, //T_vcom_lvl=0
0x11, 0x00,
0x12, 0x05,
0x15, 0x00,
0x16, 0x01, //schmitt trigger
0x17, 0x67,
0x18, 0xD0, //T_vrt_lvl=T_vrb_lvl=2
0x21, 0x14,
0x22, 0x80, //
0x2F, 0x30,
0x35, 0x64,
0x39, 0x03,
0x3A, 0x03,
0x46, 0x06,
0x4A, 0x00, //FX2
0x4B, 0x00, //FX2
0x00, 0x01, //updated flag
};