File ns_camera.c
File List > neuralSPOT > neuralspot > ns-camera > src > ns_camera.c
Go to the documentation of this file
#include "ns_camera.h"
#include "ArducamCamera.h"
#include "arm_math.h"
#include "jpeg-decoder/jpeg_decoder.h"
#include "jpeg-decoder/picojpeg.h"
#include "ns_spi.h"
#include "ns_ambiqsuite_harness.h"
const ns_core_api_t ns_camera_V1_0_0 = {.apiId = NS_CAMERA_API_ID, .version = NS_CAMERA_V1_0_0};
const ns_core_api_t ns_camera_oldest_supported_version = {
.apiId = NS_CAMERA_API_ID, .version = NS_CAMERA_V1_0_0};
const ns_core_api_t ns_camera_current_version = {
.apiId = NS_CAMERA_API_ID, .version = NS_CAMERA_V1_0_0};
#define MAX_SPI_BUF_LEN 64
jpeg_decoder_context_t jpegCtx = {};
ArducamCamera camera; // Arducam driver assumes this is a global, so :shrug:
ns_spi_config_t *spiHandle = NULL;
ns_camera_config_t ns_camera_config;
bool nsCameraPictureBeingTaken = false;
// Some Arducam functions are re-written in this file, make aliases of registers
#define ARDU_BURST_FIFO_READ 0x3C // Burst FIFO read operation
#define NS_CAMERA_ARDUCHIP_TRIG 0x44 // Trigger source
#define NS_CAMERA_VSYNC_MASK 0x01
#define NS_CAMERA_SHUTTER_MASK 0x02
#define NS_CAMERA_CAP_DONE_MASK 0x04
#define NS_CAM_REG_FORMAT 0X20
#define NS_CAM_REG_CAPTURE_RESOLUTION 0X21
#define NS_CAM_SET_CAPTURE_MODE (0 << 7)
// DMA read state
// SPI DMA max xfer size is 4095 on AP4, so we have to chunk it
#define MAX_SPI_DMA_LEN 4095
static uint32_t dma_total_requested_length;
static uint32_t dma_current_chunk_length;
static uint32_t dma_offset;
static uint8_t *dma_cambuf;
bool ns_read_done = false;
static void ns_camera_buff_read_done(ns_spi_config_t *cfg) {
// ns_printf("Chunk Read done for chunk starting at offset %d\n", dma_offset);
// Calculate next chunk offset
dma_offset += dma_current_chunk_length;
// Check if we have more to read
if (dma_offset < dma_total_requested_length) {
// Calculate next chunk length
dma_current_chunk_length = (dma_total_requested_length - dma_offset) > MAX_SPI_DMA_LEN
? MAX_SPI_DMA_LEN
: (dma_total_requested_length - dma_offset);
// Read next chunk
// ns_lp_printf("CAMERA Starting next dma addr 0x%x offset %d len %d\n", dma_offset +
// dma_cambuf, dma_offset, dma_current_chunk_length); ns_delay_us(1000);
ns_spi_read_dma(
spiHandle, dma_offset + dma_cambuf, dma_current_chunk_length, ARDU_BURST_FIFO_READ, 1,
camera.csPin);
} else {
ns_read_done = true;
// ns_lp_printf("DMA Read done\n");
if (ns_camera_config.dmaCompleteCb) {
ns_camera_config.dmaCompleteCb(&ns_camera_config);
}
if (camera.burstFirstFlag == 0) {
camera.burstFirstFlag = 1;
}
camera.receivedLength -= dma_total_requested_length;
}
}
void ns_camera_check_picture_completion(ns_timer_config_t *timer) {
if (!nsCameraPictureBeingTaken) {
// ns_lp_printf("Not taking picture\n");
return;
}
if (getBit(&camera, NS_CAMERA_ARDUCHIP_TRIG, NS_CAMERA_CAP_DONE_MASK) == 0) {
// ns_lp_printf("Picture not done fifo is %d\n", readFifoLength(&camera));
return;
}
// ns_lp_printf("Picture done\n");
camera.receivedLength = readFifoLength(&camera);
camera.totalLength = camera.receivedLength;
camera.burstFirstFlag = 0;
if (ns_camera_config.pictureTakenCb) {
// ns_lp_printf("Calling picture taken CB\n");
ns_camera_config.pictureTakenCb(&ns_camera_config);
}
nsCameraPictureBeingTaken = false;
}
ns_timer_config_t timerCfg = {
.api = &ns_timer_V1_0_0,
.timer = NS_TIMER_INTERRUPT,
.enableInterrupt = true,
.periodInMicroseconds = 80000,
.callback = ns_camera_check_picture_completion,
};
uint32_t ns_camera_init(ns_camera_config_t *cfg) {
// Check API
// Only IOM1 is supported currently - check it
// Check that buff size is sane
// Check that picture mode is sane
// Check that pix format is sane
spiHandle = &cfg->spiConfig;
cfg->spiConfig.cb = ns_camera_buff_read_done;
memcpy(&ns_camera_config, cfg, sizeof(ns_camera_config_t));
am_hal_pwrctrl_periph_enable(AM_HAL_PWRCTRL_PERIPH_IOM1);
if (ns_spi_interface_init(&cfg->spiConfig, cfg->spiSpeed, AM_HAL_IOM_SPI_MODE_0)) {
return NS_STATUS_INIT_FAILED;
}
createArducamCamera(AM_BSP_IOM1_CS_CHNL); // inits camera global, other stuff
// Start a polling timer is needed
if (cfg->pictureTakenCb != NULL) {
// ns_lp_printf("Starting camera timer\n");
NS_TRY(ns_timer_init(&timerCfg), "Failed to init camera timer\n");
}
begin(&camera);
lowPowerOn(&camera);
return NS_STATUS_SUCCESS;
}
// Functions used by Arducam driver
int arducam_spi_read(
const void *buf, uint32_t bufLen, uint64_t reg, uint32_t regLen, uint32_t csPin) {
uint32_t err = 0;
uint8_t *bufPtr = (uint8_t *)buf;
uint32_t bytesLeft = bufLen;
uint32_t chunkSize;
while (bytesLeft) {
chunkSize = bytesLeft > MAX_SPI_BUF_LEN ? MAX_SPI_BUF_LEN : bytesLeft;
err = ns_spi_read(spiHandle, bufPtr, chunkSize, reg, regLen, csPin);
bufPtr += chunkSize;
bytesLeft -= chunkSize;
}
return err;
}
int arducam_spi_write(
const void *buf, uint32_t bufLen, uint64_t reg, uint32_t regLen, uint32_t csPin) {
uint8_t *bufPtr = (uint8_t *)buf;
uint32_t bytesLeft = bufLen;
uint32_t chunkSize;
while (bytesLeft) {
chunkSize = bytesLeft > MAX_SPI_BUF_LEN ? MAX_SPI_BUF_LEN : bytesLeft;
uint32_t ret = ns_spi_write(spiHandle, bufPtr, chunkSize, reg, regLen, csPin);
if (ret)
ns_lp_printf("spi write ret %d\n", ret);
bufPtr += chunkSize;
bytesLeft -= chunkSize;
}
return 0;
}
void arducam_delay_ms(uint16_t delay) { ns_delay_us(1000 * delay); }
void arducam_delay_us(uint16_t delay) { ns_delay_us(delay); }
// Re-implementation of Arducam readBuf using SPI DMA. This just kicks off the DMA.
uint32_t ns_read_buff(ArducamCamera *camera, uint8_t *buff, uint32_t length) {
if (cameraImageAvailable(camera) == 0 || (length == 0)) {
return 0;
}
if (camera->receivedLength < length) {
length = camera->receivedLength;
}
// ns_lp_printf("CAMERA Starting DMA2 to 0x%x len %d\n", buff, length);
ns_spi_read_dma(spiHandle, buff, length, ARDU_BURST_FIFO_READ, 1, camera->csPin);
if (camera->burstFirstFlag == 0) {
camera->burstFirstFlag = 1;
}
camera->receivedLength -= length;
return length;
}
uint32_t ns_start_camera(ns_camera_config_t *cfg) {
lowPowerOff(&camera);
ns_delay_us(1000);
setBrightness(&camera, CAM_BRIGHTNESS_LEVEL_DEFAULT);
// setAutoExposure(&camera, true);
setAutoFocus(&camera, true);
// setAutoISOSensitive(&camera, true);
return NS_STATUS_SUCCESS;
}
uint32_t ns_stop_camera(ns_camera_config_t *cfg) {
lowPowerOn(&camera);
return NS_STATUS_SUCCESS;
}
uint32_t ns_take_picture(ns_camera_config_t *cfg) {
// This version blocks...
return takePicture(&camera, cfg->imageMode, cfg->imagePixFmt);
}
// Arducam needs to be polled to check when a capture is done.
// Instead of a tight loop, we use a timer to poll the camera.
// This function just starts the timer and sets state.
uint32_t ns_press_shutter_button(ns_camera_config_t *cfg) {
ns_image_pix_fmt_e pixel_format = cfg->imagePixFmt;
ns_image_mode_e mode = cfg->imageMode;
if (cfg->pictureTakenCb == NULL) {
return NS_STATUS_INVALID_CONFIG;
}
if (camera.currentPixelFormat != pixel_format) {
camera.currentPixelFormat = pixel_format;
// ns_lp_printf("Setting pixel format to %d\n", pixel_format);
writeReg(&camera, NS_CAM_REG_FORMAT, pixel_format); // set the data format
waitI2cIdle(&camera); // Wait I2c Idle
}
if (camera.currentPictureMode != mode) {
camera.currentPictureMode = mode;
// ns_lp_printf("Setting capture mode to %d\n", mode);
writeReg(&camera, NS_CAM_REG_CAPTURE_RESOLUTION, NS_CAM_SET_CAPTURE_MODE | mode);
waitI2cIdle(&camera); // Wait I2c Idle
}
cameraClearFifoFlag(&camera);
cameraStartCapture(&camera);
nsCameraPictureBeingTaken = true;
return NS_STATUS_SUCCESS;
}
int ns_is_camera_capturing() {
return !cameraImageAvailable(&camera);
}
static uint8_t ns_mapCameraValuesToArducamScale(int8_t in) {
// 0 is 0, negative numbers map to positive even uints, positive map to odd
if (in > 3) {
in = 3;
} else if (in < -3) {
in = -3;
}
if (in == 0) {
return 0;
} else if (in < 0) {
return abs(in) * 2;
} else {
return (abs(in) * 2) - 1;
}
}
void ns_camera_adjust_settings(int8_t contrast, int8_t brightness, int8_t ev) {
setContrast(&camera, (CAM_CONTRAST_LEVEL)ns_mapCameraValuesToArducamScale(contrast));
setBrightness(&camera, (CAM_BRIGHTNESS_LEVEL)ns_mapCameraValuesToArducamScale(brightness));
setEV(&camera, (CAM_EV_LEVEL)ns_mapCameraValuesToArducamScale(ev));
}
uint32_t ns_start_dma_read(
ns_camera_config_t *cfg, uint8_t *camBuf, uint32_t *buffer_offset, uint32_t bufLen) {
// Wait for capture to complete
while (ns_is_camera_capturing()) {
ns_delay_us(10000);
ns_lp_printf("Waiting for camera capture\n"); // should never be here
}
// Get FIFO length
dma_total_requested_length = cameraReadFifoLength(&camera);
// ns_lp_printf("CAMERA FIFO length: %u\n", dma_total_requested_length);
if (cfg->imagePixFmt == NS_CAM_IMAGE_PIX_FMT_JPEG) {
*buffer_offset = 1;
} else {
*buffer_offset = 0;
}
// Kick off DMA read
ns_read_done = false;
dma_cambuf = camBuf;
dma_offset = 0;
dma_current_chunk_length = (dma_total_requested_length > MAX_SPI_DMA_LEN)
? MAX_SPI_DMA_LEN
: dma_total_requested_length;
// ns_lp_printf("CAMERA Starting DMA read of chunk size %d to 0x%x\n", dma_current_chunk_length,
// camBuf);
ns_spi_read_dma(
spiHandle, camBuf, dma_current_chunk_length, ARDU_BURST_FIFO_READ, 1, camera.csPin);
return dma_total_requested_length;
}
uint32_t ns_transfer_picture(
ns_camera_config_t *cfg, uint8_t *camBuf, uint32_t *buffer_offset, uint32_t bufLen) {
uint32_t length;
uint32_t index;
// Wait for capture to complete
while (ns_is_camera_capturing()) {
ns_delay_us(10000);
}
// Get FIFO length
length = cameraReadFifoLength(&camera);
// if (length == 0 || length >= bufLen) {
// return 0;
// }
// ns_lp_printf("FIFO length: %u\n", length);
readBuff(&camera, camBuf, length);
// Remove trailing zeros from image
for (index = length - 1; index >= 0; index--) {
if (camBuf[index] != 0) {
break;
}
}
length = index + 1;
// ns_lp_printf("Clipped FIFO length: %u\n", length);
if (cfg->imagePixFmt == NS_CAM_IMAGE_PIX_FMT_JPEG) {
*buffer_offset = 1;
} else {
*buffer_offset = 0;
}
return length;
}
uint32_t ns_camera_capture(ns_camera_config_t *cfg, uint8_t *camBuf, uint32_t bufLen) {
// ns_trigger_camera_capture(cfg);
// return transfer_camera_capture(camBuf, bufLen);
return 0;
}
void ns_rgb565_to_rgb888(uint16_t rgb565Pixel, uint8_t *r, uint8_t *g, uint8_t *b) {
uint8_t r5 = (rgb565Pixel & 0xF800) >> 11;
uint8_t g6 = (rgb565Pixel & 0x07E0) >> 5;
uint8_t b5 = (rgb565Pixel & 0x001F);
*r = (r5 * 527 + 23) >> 6;
*g = (g6 * 259 + 33) >> 6;
*b = (b5 * 527 + 23) >> 6;
}
uint32_t ns_chop_off_trailing_zeros(uint8_t *buff, uint32_t length) {
uint32_t index;
// ns_lp_printf("Chopping off trailing zeros len %d addr 0x%x\n", length, buff);
for (index = length - 1; index >= 0; index--) {
if (buff[index] != 0) {
break;
}
}
return index + 1;
}
int camera_decode_image(
uint8_t *camBuf, uint32_t camLen, uint8_t *imgBuf, uint32_t imgWidth, uint32_t imgHeight,
uint32_t scaleFactor) {
uint16_t *pImg;
uint16_t color;
jpeg_decoder_init(&jpegCtx, camBuf, camLen);
const int keep_x_mcus = scaleFactor * imgWidth / jpegCtx.imgInfo.m_MCUWidth;
const int keep_y_mcus = scaleFactor * imgHeight / jpegCtx.imgInfo.m_MCUHeight;
const int skip_x_mcus = jpegCtx.imgInfo.m_MCUSPerRow - keep_x_mcus;
const int skip_start_x_mcus = skip_x_mcus / 2;
const int skip_end_x_mcu_index = skip_start_x_mcus + keep_x_mcus;
const int skip_y_mcus = jpegCtx.imgInfo.m_MCUSPerCol - keep_y_mcus;
const int skip_start_y_mcus = skip_y_mcus / 2;
const int skip_end_y_mcu_index = skip_start_y_mcus + keep_y_mcus;
const int scaleImageSize = imgHeight * imgWidth * 2;
for (int i = 0; i < scaleImageSize; i++) {
imgBuf[i] = 0;
}
while (jpeg_decoder_read(&jpegCtx)) {
// Out of height bounds
if (jpegCtx.MCUy < skip_start_y_mcus || jpegCtx.MCUy >= skip_end_y_mcu_index) {
continue;
}
// Out of width bounds
if (jpegCtx.MCUx < skip_start_x_mcus || jpegCtx.MCUx >= skip_end_x_mcu_index) {
continue;
}
pImg = jpegCtx.pImage;
int relMcuX = jpegCtx.MCUx - skip_start_x_mcus;
int relMcuY = jpegCtx.MCUy - skip_start_y_mcus;
int xOrigin = relMcuX * jpegCtx.imgInfo.m_MCUWidth;
int yOrigin = relMcuY * jpegCtx.imgInfo.m_MCUHeight;
// ns_lp_printf("Writing to imgBuf at 0x%x\n", imgBuf);
for (int mcuRow = 0; mcuRow < jpegCtx.imgInfo.m_MCUHeight; mcuRow++) {
int currentY = yOrigin + mcuRow;
for (int mcuCol = 0; mcuCol < jpegCtx.imgInfo.m_MCUWidth; mcuCol++) {
int currentX = xOrigin + mcuCol;
color = *pImg++;
if (scaleFactor != 1 &&
(currentY % scaleFactor != 0 || currentX % scaleFactor != 0)) {
continue;
}
int index = (currentY / scaleFactor) * (imgWidth / 1) + currentX / scaleFactor;
imgBuf[index * 2 + 1] = (color & 0xFF00) >> 8;
imgBuf[index * 2 + 0] = (color & 0x00FF);
}
}
}
return 0;
}