Merge pull request #4880 from jepler/esp32s2-imagecapture

Esp32s2: implement ParallelImageCapture
This commit is contained in:
Limor "Ladyada" Fried 2021-06-19 16:17:56 -04:00 committed by GitHub
commit 6dbeb75a4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 898 additions and 31 deletions

View File

@ -115,6 +115,10 @@ msgstr ""
msgid "%q must be a tuple of length 2"
msgstr ""
#: ports/esp32s2/common-hal/imagecapture/ParallelImageCapture.c
msgid "%q must be between %d and %d"
msgstr ""
#: ports/raspberrypi/common-hal/rp2pio/StateMachine.c
#: shared-bindings/canio/Match.c
msgid "%q out of range"
@ -1280,6 +1284,11 @@ msgstr ""
msgid "Invalid data_count %d"
msgstr ""
#: ports/atmel-samd/common-hal/imagecapture/ParallelImageCapture.c
#, c-format
msgid "Invalid data_pins[%d]"
msgstr ""
#: shared-bindings/digitalio/DigitalInOut.c
msgid "Invalid direction."
msgstr ""
@ -1813,6 +1822,7 @@ msgid ""
"constructor"
msgstr ""
#: ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.c
#: ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c
msgid "Pins must be sequential"
msgstr ""

View File

@ -50,13 +50,16 @@
#define PIN_PCC_CLK (PIN_PA14)
void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_parallelimagecapture_obj_t *self,
const mcu_pin_obj_t *data0,
const uint8_t data_pins[],
uint8_t data_count,
const mcu_pin_obj_t *data_clock,
const mcu_pin_obj_t *vertical_sync,
const mcu_pin_obj_t *horizontal_reference,
int data_count) {
if (data0->number != PIN_PCC_D0) {
mp_raise_ValueError_varg(translate("Invalid %q pin"), MP_QSTR_data0);
const mcu_pin_obj_t *horizontal_reference) {
for (int i = 0; i < data_count; i++) {
if (data_pins[i] != PIN_PCC_D0 + i) {
mp_raise_ValueError_varg(translate("Invalid data_pins[%d]"), i);
}
}
// The peripheral supports 8, 10, 12, or 14 data bits, but the code only supports 8 at present
if (data_count != 8) {
@ -73,7 +76,7 @@ void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_paralle
}
// technically, 0 was validated as free already but check again
for (int i = 0; i < data_count; i++) {
if (!pin_number_is_free(data0->number + i)) {
if (!pin_number_is_free(data_pins[i])) {
mp_raise_ValueError_varg(translate("data pin #%d in use"), i);
}
}

View File

@ -176,6 +176,7 @@ CFLAGS += -DCFG_TUD_VENDOR_RX_BUFSIZE=128 -DCFG_TUD_VENDOR_TX_BUFSIZE=128
SRC_C += \
background.c \
cam.c \
fatfs_port.c \
mphalport.c \
bindings/espidf/__init__.c \
@ -337,6 +338,10 @@ flash: $(BUILD)/firmware.bin
flash-circuitpython-only: $(BUILD)/circuitpython-firmware.bin
esptool.py --chip esp32s2 -p $(PORT) $(ESPTOOL_FLAGS) write_flash $(FLASH_FLAGS) 0x10000 $^
monitor: $(BUILD)/firmware.elf
cp $< build/circuitpython.elf
idf.py monitor -p $(PORT)
include $(TOP)/py/mkrules.mk
# Print out the value of a make variable.

View File

@ -1,5 +1,21 @@
#include "py/objtuple.h"
#include "shared-bindings/board/__init__.h"
STATIC const mp_rom_obj_tuple_t camera_data_tuple = {
{&mp_type_tuple},
8,
{
MP_ROM_PTR(&pin_GPIO46),
MP_ROM_PTR(&pin_GPIO45),
MP_ROM_PTR(&pin_GPIO41),
MP_ROM_PTR(&pin_GPIO42),
MP_ROM_PTR(&pin_GPIO39),
MP_ROM_PTR(&pin_GPIO40),
MP_ROM_PTR(&pin_GPIO21),
MP_ROM_PTR(&pin_GPIO38),
}
};
STATIC const mp_rom_map_elem_t board_global_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_IO0), MP_ROM_PTR(&pin_GPIO0) },
{ MP_ROM_QSTR(MP_QSTR_IO1), MP_ROM_PTR(&pin_GPIO1) },
@ -54,6 +70,7 @@ STATIC const mp_rom_map_elem_t board_global_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_CAMERA_SIOC), MP_ROM_PTR(&pin_GPIO7) },
{ MP_ROM_QSTR(MP_QSTR_CAMERA_DATA), MP_ROM_PTR(&camera_data_tuple) },
{ MP_ROM_QSTR(MP_QSTR_CAMERA_D2), MP_ROM_PTR(&pin_GPIO46) },
{ MP_ROM_QSTR(MP_QSTR_CAMERA_D3), MP_ROM_PTR(&pin_GPIO45) },
{ MP_ROM_QSTR(MP_QSTR_CAMERA_D4), MP_ROM_PTR(&pin_GPIO41) },

502
ports/esp32s2/cam.c Normal file
View File

@ -0,0 +1,502 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
#include "esp_system.h"
#include "esp_log.h"
#include "soc/i2s_struct.h"
#include "soc/apb_ctrl_reg.h"
#include "esp32s2/rom/lldesc.h"
#include "esp32s2/rom/cache.h"
#include "soc/dport_access.h"
#include "soc/dport_reg.h"
#include "driver/ledc.h"
#include "cam.h"
#include "hal/gpio_ll.h"
static const char *TAG = "cam";
#define CAM_DMA_MAX_SIZE (4095)
typedef enum {
CAM_IN_SUC_EOF_EVENT = 0,
CAM_VSYNC_EVENT
} cam_event_t;
typedef struct {
uint8_t *frame_buffer;
size_t len;
} frame_buffer_event_t;
typedef struct {
uint32_t buffer_size;
uint32_t half_buffer_size;
uint32_t node_cnt;
uint32_t half_node_cnt;
uint32_t dma_size;
uint32_t cnt;
uint32_t total_cnt;
lldesc_t *dma;
uint8_t *buffer;
uint8_t *frame1_buffer;
uint8_t *frame2_buffer;
uint8_t frame1_buffer_en;
uint8_t frame2_buffer_en;
uint8_t jpeg_mode;
uint8_t vsync_pin;
uint8_t vsync_invert;
uint8_t hsync_invert;
QueueHandle_t event_queue;
QueueHandle_t frame_buffer_queue;
TaskHandle_t task_handle;
intr_handle_t intr_handle;
} cam_obj_t;
static cam_obj_t *cam_obj = NULL;
void IRAM_ATTR cam_isr(void *arg) {
cam_event_t cam_event = {0};
BaseType_t HPTaskAwoken = pdFALSE;
typeof(I2S0.int_st) int_st = I2S0.int_st;
I2S0.int_clr.val = int_st.val;
if (int_st.in_suc_eof) {
cam_event = CAM_IN_SUC_EOF_EVENT;
xQueueSendFromISR(cam_obj->event_queue, (void *)&cam_event, &HPTaskAwoken);
}
if (HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
void IRAM_ATTR cam_vsync_isr(void *arg) {
cam_event_t cam_event = {0};
BaseType_t HPTaskAwoken = pdFALSE;
/*!< filter */
ets_delay_us(1);
if (gpio_ll_get_level(&GPIO, cam_obj->vsync_pin) == !cam_obj->vsync_invert) {
cam_event = CAM_VSYNC_EVENT;
xQueueSendFromISR(cam_obj->event_queue, (void *)&cam_event, &HPTaskAwoken);
}
if (HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
static void cam_config(const cam_config_t *config) {
/*!<Enable I2S periph */
periph_module_enable(PERIPH_I2S0_MODULE);
/*!< Configure the clock */
#if 0
I2S0.clkm_conf.val = 0;
I2S0.clkm_conf.clkm_div_num = 2; // <--- this should set the low 8 bits, but doesn't somehow
I2S0.clkm_conf.clkm_div_b = 0;
I2S0.clkm_conf.clkm_div_a = 0;
I2S0.clkm_conf.clk_sel = 2;
I2S0.clkm_conf.clk_en = 1;
#else
// The above code should set the clock to this value .. however, it sets it to 0x0050_0000 instead, the "clkm_div_num" value missing!
// it's not clear why this behavior arises
I2S0.clkm_conf.val = 0x00500002;
#endif
/*!< Configuration sampling rate */
I2S0.sample_rate_conf.val = 0;
I2S0.sample_rate_conf.tx_bck_div_num = 2;
I2S0.sample_rate_conf.tx_bits_mod = 8;
I2S0.sample_rate_conf.rx_bck_div_num = 1;
I2S0.sample_rate_conf.rx_bits_mod = config->bit_width;
/*!< Configuration data format */
I2S0.conf.val = 0;
I2S0.conf.tx_right_first = 1;
I2S0.conf.tx_msb_right = 1;
I2S0.conf.tx_dma_equal = 1;
I2S0.conf.rx_right_first = 1;
I2S0.conf.rx_msb_right = 1;
I2S0.conf.rx_dma_equal = 1;
I2S0.conf1.val = 0;
I2S0.conf1.tx_pcm_bypass = 1;
I2S0.conf1.tx_stop_en = 1;
I2S0.conf1.rx_pcm_bypass = 1;
I2S0.conf2.val = 0;
I2S0.conf2.cam_sync_fifo_reset = 1;
I2S0.conf2.cam_sync_fifo_reset = 0;
I2S0.conf2.lcd_en = 1;
I2S0.conf2.camera_en = 1;
I2S0.conf2.i_v_sync_filter_en = 1;
I2S0.conf2.i_v_sync_filter_thres = 1;
I2S0.conf_chan.val = 0;
I2S0.conf_chan.tx_chan_mod = 1;
I2S0.conf_chan.rx_chan_mod = 1;
I2S0.fifo_conf.val = 0;
I2S0.fifo_conf.rx_fifo_mod_force_en = 1;
I2S0.fifo_conf.rx_data_num = 32;
I2S0.fifo_conf.rx_fifo_mod = 2;
I2S0.fifo_conf.tx_fifo_mod_force_en = 1;
I2S0.fifo_conf.tx_data_num = 32;
I2S0.fifo_conf.tx_fifo_mod = 2;
I2S0.fifo_conf.dscr_en = 1;
I2S0.lc_conf.out_rst = 1;
I2S0.lc_conf.out_rst = 0;
I2S0.lc_conf.in_rst = 1;
I2S0.lc_conf.in_rst = 0;
I2S0.timing.val = 0;
I2S0.int_ena.val = 0;
I2S0.int_clr.val = ~0;
I2S0.lc_conf.check_owner = 0;
I2S0.conf.rx_start = 1;
}
static void cam_set_pin(const cam_config_t *config) {
gpio_config_t io_conf = {0};
io_conf.intr_type = config->vsync_invert ? GPIO_PIN_INTR_NEGEDGE : GPIO_PIN_INTR_POSEDGE;
io_conf.pin_bit_mask = 1 << config->pin.vsync;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_up_en = 1;
io_conf.pull_down_en = 0;
gpio_config(&io_conf);
gpio_install_isr_service(0);
gpio_isr_handler_add(config->pin.vsync, cam_vsync_isr, NULL);
gpio_intr_disable(config->pin.vsync);
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin.pclk], PIN_FUNC_GPIO);
gpio_set_direction(config->pin.pclk, GPIO_MODE_INPUT);
gpio_set_pull_mode(config->pin.pclk, GPIO_FLOATING);
gpio_matrix_in(config->pin.pclk, I2S0I_WS_IN_IDX, false);
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin.vsync], PIN_FUNC_GPIO);
gpio_set_direction(config->pin.vsync, GPIO_MODE_INPUT);
gpio_set_pull_mode(config->pin.vsync, GPIO_FLOATING);
gpio_matrix_in(config->pin.vsync, I2S0I_V_SYNC_IDX, config->vsync_invert);
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin.hsync], PIN_FUNC_GPIO);
gpio_set_direction(config->pin.hsync, GPIO_MODE_INPUT);
gpio_set_pull_mode(config->pin.hsync, GPIO_FLOATING);
gpio_matrix_in(config->pin.hsync, I2S0I_H_SYNC_IDX, config->hsync_invert);
for (int i = 0; i < config->bit_width; i++) {
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_data[i]], PIN_FUNC_GPIO);
gpio_set_direction(config->pin_data[i], GPIO_MODE_INPUT);
gpio_set_pull_mode(config->pin_data[i], GPIO_FLOATING);
/*!< High position alignment, IN16 is always the highest bit */
/*!< Fifo accesses data by bit, and when rx_bits_mod is 8, the data needs to be aligned by 8 bits */
gpio_matrix_in(config->pin_data[i], I2S0I_DATA_IN0_IDX + (16 - config->bit_width) + i, false);
}
gpio_matrix_in(GPIO_FUNC_IN_HIGH, I2S0I_H_ENABLE_IDX, false);
ESP_LOGI(TAG, "cam_set_pin\n");
}
static void cam_vsync_intr_enable(uint8_t en) {
if (en) {
gpio_intr_enable(cam_obj->vsync_pin);
} else {
gpio_intr_disable(cam_obj->vsync_pin);
}
}
static void cam_dma_stop(void) {
if (I2S0.int_ena.in_suc_eof == 1) {
I2S0.conf.rx_start = 0;
I2S0.int_ena.in_suc_eof = 0;
I2S0.int_clr.in_suc_eof = 1;
I2S0.in_link.stop = 1;
}
}
static void cam_dma_start(void) {
if (I2S0.int_ena.in_suc_eof == 0) {
I2S0.int_clr.in_suc_eof = 1;
I2S0.int_ena.in_suc_eof = 1;
I2S0.conf.rx_reset = 1;
I2S0.conf.rx_reset = 0;
I2S0.conf.rx_fifo_reset = 1;
I2S0.conf.rx_fifo_reset = 0;
I2S0.lc_conf.in_rst = 1;
I2S0.lc_conf.in_rst = 0;
I2S0.lc_conf.ahbm_fifo_rst = 1;
I2S0.lc_conf.ahbm_fifo_rst = 0;
I2S0.lc_conf.ahbm_rst = 1;
I2S0.lc_conf.ahbm_rst = 0;
I2S0.in_link.start = 1;
I2S0.conf.rx_start = 1;
if (cam_obj->jpeg_mode) {
/*!< Manually give the vsync data for the first frame */
gpio_matrix_in(cam_obj->vsync_pin, I2S0I_V_SYNC_IDX, !cam_obj->vsync_invert);
gpio_matrix_in(cam_obj->vsync_pin, I2S0I_V_SYNC_IDX, cam_obj->vsync_invert);
}
}
}
void cam_stop(void) {
cam_vsync_intr_enable(0);
cam_dma_stop();
}
void cam_start(void) {
cam_vsync_intr_enable(1);
}
typedef enum {
CAM_STATE_IDLE = 0,
CAM_STATE_READ_BUF1 = 1,
CAM_STATE_READ_BUF2 = 2,
} cam_state_t;
/*!<Copy fram from DMA buffer to fram buffer */
static void cam_task(void *arg) {
int state = CAM_STATE_IDLE;
cam_event_t cam_event = {0};
frame_buffer_event_t frame_buffer_event = {0};
xQueueReset(cam_obj->event_queue);
while (1) {
xQueueReceive(cam_obj->event_queue, (void *)&cam_event, portMAX_DELAY);
switch (state) {
case CAM_STATE_IDLE: {
if (cam_event == CAM_VSYNC_EVENT) {
if (cam_obj->frame1_buffer_en) {
cam_dma_start();
cam_vsync_intr_enable(0);
state = CAM_STATE_READ_BUF1;
} else if (cam_obj->frame2_buffer_en) {
cam_dma_start();
cam_vsync_intr_enable(0);
state = CAM_STATE_READ_BUF2;
}
cam_obj->cnt = 0;
}
}
break;
case CAM_STATE_READ_BUF1: {
if (cam_event == CAM_IN_SUC_EOF_EVENT) {
if (cam_obj->cnt == 0) {
cam_vsync_intr_enable(1); /*!< CAM real start is required to receive the first buf data and then turn on the vsync interrupt */
}
memcpy(&cam_obj->frame1_buffer[cam_obj->cnt * cam_obj->half_buffer_size], &cam_obj->buffer[(cam_obj->cnt % 2) * cam_obj->half_buffer_size], cam_obj->half_buffer_size);
if (cam_obj->jpeg_mode) {
if (cam_obj->frame1_buffer_en == 0) {
cam_dma_stop();
}
} else {
if (cam_obj->cnt == cam_obj->total_cnt - 1) {
cam_obj->frame1_buffer_en = 0;
}
}
if (cam_obj->frame1_buffer_en == 0) {
frame_buffer_event.frame_buffer = cam_obj->frame1_buffer;
frame_buffer_event.len = (cam_obj->cnt + 1) * cam_obj->half_buffer_size;
xQueueSend(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, portMAX_DELAY);
state = CAM_STATE_IDLE;
} else {
cam_obj->cnt++;
}
} else if (cam_event == CAM_VSYNC_EVENT) {
if (cam_obj->jpeg_mode) {
cam_obj->frame1_buffer_en = 0;
}
}
}
break;
case CAM_STATE_READ_BUF2: {
if (cam_event == CAM_IN_SUC_EOF_EVENT) {
if (cam_obj->cnt == 0) {
cam_vsync_intr_enable(1); /*!< CAM real start is required to receive the first buf data and then turn on the vsync interrupt */
}
memcpy(&cam_obj->frame2_buffer[cam_obj->cnt * cam_obj->half_buffer_size], &cam_obj->buffer[(cam_obj->cnt % 2) * cam_obj->half_buffer_size], cam_obj->half_buffer_size);
if (cam_obj->jpeg_mode) {
if (cam_obj->frame2_buffer_en == 0) {
cam_dma_stop();
}
} else {
if (cam_obj->cnt == cam_obj->total_cnt - 1) {
cam_obj->frame2_buffer_en = 0;
}
}
if (cam_obj->frame2_buffer_en == 0) {
frame_buffer_event.frame_buffer = cam_obj->frame2_buffer;
frame_buffer_event.len = (cam_obj->cnt + 1) * cam_obj->half_buffer_size;
xQueueSend(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, portMAX_DELAY);
state = CAM_STATE_IDLE;
} else {
cam_obj->cnt++;
}
} else if (cam_event == CAM_VSYNC_EVENT) {
if (cam_obj->jpeg_mode) {
cam_obj->frame2_buffer_en = 0;
}
}
}
break;
}
}
}
size_t cam_take(uint8_t **buffer_p) {
frame_buffer_event_t frame_buffer_event;
xQueueReceive(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, portMAX_DELAY);
*buffer_p = frame_buffer_event.frame_buffer;
return frame_buffer_event.len;
}
bool cam_ready(void) {
frame_buffer_event_t frame_buffer_event;
BaseType_t result = xQueuePeek(cam_obj->frame_buffer_queue, (void *)&frame_buffer_event, 0);
return result != pdFALSE;
}
void cam_give(uint8_t *buffer) {
if (buffer == cam_obj->frame1_buffer) {
cam_obj->frame1_buffer_en = 1;
} else if (buffer == cam_obj->frame2_buffer) {
cam_obj->frame2_buffer_en = 1;
}
}
void cam_dma_config(const cam_config_t *config) {
int cnt = 0;
if (config->mode.jpeg) {
cam_obj->buffer_size = 2048;
cam_obj->half_buffer_size = cam_obj->buffer_size / 2;
cam_obj->dma_size = 1024;
} else {
for (cnt = 0;; cnt++) { /*!< Find the divisible buffer size */
if ((config->size * 2) % (config->max_buffer_size - cnt) == 0) {
break;
}
}
cam_obj->buffer_size = config->max_buffer_size - cnt;
cam_obj->half_buffer_size = cam_obj->buffer_size / 2;
for (cnt = 0;; cnt++) { /*!< Look for divisible dma sizes */
if ((cam_obj->half_buffer_size) % (CAM_DMA_MAX_SIZE - cnt) == 0) {
break;
}
}
cam_obj->dma_size = CAM_DMA_MAX_SIZE - cnt;
}
cam_obj->node_cnt = (cam_obj->buffer_size) / cam_obj->dma_size; /*!< Number of DMA nodes */
cam_obj->half_node_cnt = cam_obj->node_cnt / 2;
cam_obj->total_cnt = (config->size * 2) / cam_obj->half_buffer_size; /*!< Number of interrupt copies produced. Ping pong copies */
ESP_LOGI(TAG, "cam_buffer_size: %d, cam_dma_size: %d, cam_dma_node_cnt: %d, cam_total_cnt: %d\n", cam_obj->buffer_size, cam_obj->dma_size, cam_obj->node_cnt, cam_obj->total_cnt);
cam_obj->dma = (lldesc_t *)heap_caps_realloc(cam_obj->dma, cam_obj->node_cnt * sizeof(lldesc_t), MALLOC_CAP_DMA);
cam_obj->buffer = (uint8_t *)heap_caps_realloc(cam_obj->buffer, cam_obj->buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA);
for (uint32_t x = 0; x < cam_obj->node_cnt; x++) {
cam_obj->dma[x].size = cam_obj->dma_size;
cam_obj->dma[x].length = cam_obj->dma_size;
cam_obj->dma[x].eof = 0;
cam_obj->dma[x].owner = 1;
cam_obj->dma[x].buf = (cam_obj->buffer + cam_obj->dma_size * x);
cam_obj->dma[x].empty = (intptr_t)&cam_obj->dma[(x + 1) % cam_obj->node_cnt];
}
I2S0.in_link.addr = ((uint32_t)&cam_obj->dma[0]) & 0xfffff;
I2S0.rx_eof_num = cam_obj->half_buffer_size; /*!< Ping-pong operation */
}
esp_err_t cam_deinit() {
if (!cam_obj) {
return ESP_FAIL;
}
cam_stop();
gpio_isr_handler_remove(cam_obj->vsync_pin);
esp_intr_free(cam_obj->intr_handle);
vTaskDelete(cam_obj->task_handle);
vQueueDelete(cam_obj->event_queue);
vQueueDelete(cam_obj->frame_buffer_queue);
free(cam_obj->dma);
free(cam_obj->buffer);
free(cam_obj);
cam_obj = NULL;
return ESP_OK;
}
esp_err_t cam_init(const cam_config_t *config) {
cam_obj = (cam_obj_t *)heap_caps_calloc(1, sizeof(cam_obj_t), MALLOC_CAP_DMA);
if (!cam_obj) {
ESP_LOGI(TAG, "camera object malloc error\n");
return ESP_FAIL;
}
cam_obj->frame1_buffer = config->frame1_buffer;
cam_obj->frame2_buffer = config->frame2_buffer;
cam_obj->jpeg_mode = config->mode.jpeg;
cam_obj->vsync_pin = config->pin.vsync;
cam_obj->vsync_invert = config->vsync_invert;
cam_obj->hsync_invert = config->hsync_invert;
cam_set_pin(config);
cam_config(config);
cam_dma_config(config);
cam_obj->event_queue = xQueueCreate(2, sizeof(cam_event_t));
cam_obj->frame_buffer_queue = xQueueCreate(2, sizeof(frame_buffer_event_t));
if (cam_obj->frame1_buffer != NULL) {
ESP_LOGI(TAG, "frame1_buffer_en\n");
cam_obj->frame1_buffer_en = 1;
} else {
cam_obj->frame1_buffer_en = 0;
}
if (cam_obj->frame2_buffer != NULL) {
ESP_LOGI(TAG, "frame2_buffer_en\n");
cam_obj->frame2_buffer_en = 1;
} else {
cam_obj->frame2_buffer_en = 0;
}
esp_intr_alloc(ETS_I2S0_INTR_SOURCE, 0, cam_isr, NULL, &cam_obj->intr_handle);
xTaskCreate(cam_task, "cam_task", config->task_stack, NULL, config->task_pri, &cam_obj->task_handle);
return ESP_OK;
}

104
ports/esp32s2/cam.h Normal file
View File

@ -0,0 +1,104 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint8_t bit_width;
union {
struct {
uint32_t pclk : 8;
uint32_t vsync : 8;
uint32_t hsync : 8;
};
uint32_t val;
} pin; /*!< Pin of camera */
uint8_t pin_data[16]; /*!< Data pin of camera*/
uint8_t vsync_invert;
uint8_t hsync_invert;
uint32_t size; /*!< size of camera frame (width times height) */
uint32_t max_buffer_size; /*!< DMA used */
uint32_t task_stack; /*!< The priority of the task at run time */
uint8_t task_pri;
union {
struct {
uint32_t jpeg : 1;
};
uint32_t val;
} mode;
uint8_t *frame1_buffer; /*!< PingPang buffers , cache the image*/
uint8_t *frame2_buffer; /*!< PingPang buffers , cache the image*/
} cam_config_t;
/**
* @brief enable camera
*/
void cam_start(void);
/**
* @brief disable camera
*/
void cam_stop(void);
/**
* @brief Accepts frame data and returns a pointer to the frame data.
*
* @param buffer_p The address of the frame buffer pointer
*
* @return - len of buffer
*/
size_t cam_take(uint8_t **buffer_p);
/**
* @brief Check whether a frame is available
*
* @return - true if cam_take() will complete immediately, false if it will block
*/
bool cam_ready(void);
/**
* @brief enable frame buffer to get the next frame data.
*
* @param buffer The address of the frame buffer pointer
*
*/
void cam_give(uint8_t *buffer);
/**
* @brief Initialize camera
*
* @param config Parameter configuration, including pin, buffer, output image size, and so on.
*
* @return - ESP_OK :Initialize success
* ESP_FAIL: Initialize fails
*/
esp_err_t cam_init(const cam_config_t *config);
/**
* @brief delete the camera
*
* @return - ESP_OK :Delete success
* ESP_FAIL: Delete fails
*/
esp_err_t cam_deinit(void);
#ifdef __cplusplus
}
#endif

View File

@ -41,6 +41,15 @@ static i2s_t *i2s_instance[I2S_NUM_MAX];
static QueueHandle_t i2s_queues[I2S_NUM_MAX];
static TaskHandle_t i2s_tasks[I2S_NUM_MAX];
void port_i2s_allocate_i2s0(void) {
if (!i2s_instance[0]) {
i2s_instance[0] = (void *)~(intptr_t)0;
return;
}
mp_raise_RuntimeError(translate("Peripheral in use"));
}
static int8_t port_i2s_allocate(void) {
#if defined(I2S_NUM_1)
if (!i2s_instance[1]) {

View File

@ -59,3 +59,6 @@ bool port_i2s_playing(i2s_t *self);
bool port_i2s_paused(i2s_t *self);
void port_i2s_pause(i2s_t *self);
void port_i2s_resume(i2s_t *self);
// some uses (imagecapture) can only operate on i2s0 and need their own init code
void port_i2s_allocate_i2s0(void);

View File

@ -0,0 +1,131 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Jeff Epler for Adafruit Industries
*
* 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.
*/
#include <string.h>
#include "py/obj.h"
#include "py/runtime.h"
#include "common-hal/audiobusio/__init__.h"
#include "common-hal/imagecapture/ParallelImageCapture.h"
#include "cam.h"
void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_parallelimagecapture_obj_t *self,
const uint8_t data_pins[],
uint8_t data_count,
const mcu_pin_obj_t *data_clock,
const mcu_pin_obj_t *vertical_sync,
const mcu_pin_obj_t *horizontal_reference) {
// only 8 bits is supported at present
if (data_count < 8 || data_count > 16) {
mp_raise_ValueError_varg(translate("%q must be between %d and %d"), MP_QSTR_data_count, 8, 16);
}
// This will throw if unsuccessful. Everything following is guaranteed to succeed.
port_i2s_allocate_i2s0();
claim_pin(data_clock);
claim_pin(vertical_sync);
claim_pin(horizontal_reference);
self->data_count = data_count;
self->data_clock = data_clock->number;
self->vertical_sync = vertical_sync->number;
self->horizontal_reference = horizontal_reference->number;
self->config = (cam_config_t) {
.bit_width = data_count,
.pin = {
.pclk = data_clock->number,
.vsync = vertical_sync->number,
.hsync = horizontal_reference->number,
},
.vsync_invert = true,
.hsync_invert = false,
.size = 0,
.max_buffer_size = 8 * 1024,
.task_stack = 1024,
.task_pri = configMAX_PRIORITIES
};
for (int i = 0; i < data_count; i++) {
claim_pin_number(data_pins[i]);
self->config.pin_data[i] = data_pins[i];
}
}
void common_hal_imagecapture_parallelimagecapture_deinit(imagecapture_parallelimagecapture_obj_t *self) {
reset_pin_number(self->data_clock);
self->data_clock = NO_PIN;
reset_pin_number(self->vertical_sync);
self->vertical_sync = NO_PIN;
reset_pin_number(self->horizontal_reference);
self->horizontal_reference = NO_PIN;
for (int i = 0; i < self->data_count; i++) {
if (self->config.pin_data[i] != NO_PIN) {
reset_pin_number(self->config.pin_data[i]);
self->config.pin_data[i] = NO_PIN;
}
}
port_i2s_reset_instance(0);
}
bool common_hal_imagecapture_parallelimagecapture_deinited(imagecapture_parallelimagecapture_obj_t *self) {
return self->data_clock == NO_PIN;
}
void common_hal_imagecapture_parallelimagecapture_capture(imagecapture_parallelimagecapture_obj_t *self, void *buffer, size_t bufsize) {
size_t size = bufsize / 2; // count is in pixels
if (size != self->config.size || buffer != self->config.frame1_buffer) {
cam_deinit();
self->config.size = bufsize / 2; // count is in pixels(?)
self->config.frame1_buffer = buffer;
cam_init(&self->config);
} else {
cam_give(buffer);
}
cam_start();
while (!cam_ready()) {
RUN_BACKGROUND_TASKS;
if (mp_hal_is_interrupted()) {
self->config.size = 0; // force re-init next time
cam_stop();
return;
}
}
uint8_t *unused;
cam_take(&unused); // this just "returns" buffer
cam_stop();
}

View File

@ -0,0 +1,39 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Jeff Epler for Adafruit Industries
*
* 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.
*/
#pragma once
#include "shared-bindings/imagecapture/ParallelImageCapture.h"
#include "cam.h"
struct imagecapture_parallelimagecapture_obj {
mp_obj_base_t base;
cam_config_t config;
gpio_num_t data_clock;
gpio_num_t vertical_sync;
gpio_num_t horizontal_reference;
uint8_t data_count;
};

View File

@ -96,6 +96,13 @@ void reset_all_pins(void) {
in_use[1] = 0;
}
void claim_pin_number(gpio_num_t pin_number) {
if (pin_number == NO_PIN) {
return;
}
in_use[pin_number / 32] |= (1 << (pin_number % 32));
}
void claim_pin(const mcu_pin_obj_t *pin) {
in_use[pin->number / 32] |= (1 << (pin->number % 32));
}

View File

@ -37,6 +37,7 @@ void reset_all_pins(void);
void reset_pin_number(gpio_num_t pin_number);
void common_hal_reset_pin(const mcu_pin_obj_t *pin);
void claim_pin(const mcu_pin_obj_t *pin);
void claim_pin_number(gpio_num_t pin_number);
bool pin_number_is_free(gpio_num_t pin_number);
void never_reset_pin_number(gpio_num_t pin_number);

View File

@ -23,6 +23,7 @@ CIRCUITPY_COUNTIO = 1
CIRCUITPY_DUALBANK = 1
CIRCUITPY_FRAMEBUFFERIO = 1
CIRCUITPY_FREQUENCYIO = 1
CIRCUITPY_IMAGECAPTURE ?= 1
CIRCUITPY_I2CPERIPHERAL = 0
CIRCUITPY_RGBMATRIX = 1
CIRCUITPY_ROTARYIO = 1

View File

@ -66,6 +66,10 @@
#include "common-hal/audiobusio/__init__.h"
#endif
#if CIRCUITPY_IMAGECAPTURE
#include "cam.h"
#endif
#define HEAP_SIZE (48 * 1024)
uint32_t *heap;
@ -155,6 +159,10 @@ safe_mode_t port_init(void) {
}
void reset_port(void) {
#if CIRCUITPY_IMAGECAPTURE
cam_deinit();
#endif
reset_all_pins();
// A larger delay so the idle task can run and do any IDF cleanup needed.

View File

@ -68,12 +68,33 @@
/* .wrap */ \
}
mcu_pin_obj_t *pin_from_number(uint8_t number) {
const mp_map_t *mcu_map = &mcu_pin_globals.map;
for (uint8_t i = 0; i < mcu_map->alloc; i++) {
mp_obj_t val = mcu_map->table[i].value;
if (!mp_obj_is_type(val, &mcu_pin_type)) {
continue;
}
mcu_pin_obj_t *pin = MP_OBJ_TO_PTR(val);
if (pin->number == number) {
return pin;
}
}
return NULL;
}
void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_parallelimagecapture_obj_t *self,
const mcu_pin_obj_t *data0,
const uint8_t data_pins[],
uint8_t data_count,
const mcu_pin_obj_t *data_clock,
const mcu_pin_obj_t *vertical_sync,
const mcu_pin_obj_t *horizontal_reference,
int data_count) {
const mcu_pin_obj_t *horizontal_reference) {
for (int i = 1; i < data_count; i++) {
if (data_pins[i] - data_pins[0] != i) {
mp_raise_RuntimeError(translate("Pins must be sequential"));
}
}
uint16_t imagecapture_code[] = IMAGECAPTURE_CODE(data_count, data_clock->number, vertical_sync->number, horizontal_reference->number);
@ -82,7 +103,7 @@ void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_paralle
common_hal_mcu_processor_get_frequency(), // full speed (4 instructions per loop -> max pclk 30MHz @ 120MHz)
0, 0, // init
NULL, 0, 0, 0, // out pins
data0, data_count, // in pins
pin_from_number(data_pins[0]), data_count, // in pins
0, 0, // in pulls
NULL, 0, 0, 0, // set pins
#if DEBUG_STATE_MACHINE

View File

@ -39,15 +39,14 @@
//| def __init__(
//| self,
//| *,
//| data0: microcontroller.Pin,
//| data_count: int=8,
//| data_pins: List[microcontroller.Pin],
//| clock: microcontroller.Pin,
//| vsync: Optional[microcontroller.Pin],
//| href: Optional[microcontroller.Pin],
//| ):
//| """Create a parallel image capture object
//| :param microcontroller.Pin data0: The first data pin. Additional data pins are assumed to follow this pin directly in the microcontroller's standard pin ordering.
//| :param int data_count: The number of data pins.
//|
//| :param List[microcontroller.Pin] data_pins: The data pins.
//| :param microcontroller.Pin clock: The pixel clock input.
//| :param microcontroller.Pin vsync: The vertical sync input, which has a negative-going pulse at the beginning of each frame.
//| :param microcontroller.Pin href: The horizontal reference input, which is high whenever the camera is transmitting valid pixel information.
@ -55,11 +54,10 @@
//| ...
//|
STATIC mp_obj_t imagecapture_parallelimagecapture_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_data0, ARG_data_count, ARG_clock, ARG_vsync, ARG_href,
enum { ARG_data_pins, ARG_clock, ARG_vsync, ARG_href,
NUM_ARGS };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_data0, MP_ARG_OBJ | MP_ARG_REQUIRED | MP_ARG_KW_ONLY },
{ MP_QSTR_data_count, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 8 } },
{ MP_QSTR_data_pins, MP_ARG_OBJ | MP_ARG_KW_ONLY, { .u_obj = MP_ROM_NONE } },
{ MP_QSTR_clock, MP_ARG_OBJ | MP_ARG_REQUIRED | MP_ARG_KW_ONLY },
{ MP_QSTR_vsync, MP_ARG_OBJ | MP_ARG_REQUIRED | MP_ARG_KW_ONLY },
{ MP_QSTR_href, MP_ARG_OBJ | MP_ARG_REQUIRED | MP_ARG_KW_ONLY },
@ -68,7 +66,10 @@ STATIC mp_obj_t imagecapture_parallelimagecapture_make_new(const mp_obj_type_t *
MP_STATIC_ASSERT(MP_ARRAY_SIZE(allowed_args) == NUM_ARGS);
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
mcu_pin_obj_t *data0 = validate_obj_is_free_pin(args[ARG_data0].u_obj);
uint8_t pins[32];
uint8_t pin_count;
validate_pins(MP_QSTR_data, pins, MP_ARRAY_SIZE(pins), args[ARG_data_pins].u_obj, &pin_count);
mcu_pin_obj_t *clock = validate_obj_is_free_pin(args[ARG_clock].u_obj);
mcu_pin_obj_t *vsync = validate_obj_is_free_pin_or_none(args[ARG_vsync].u_obj);
mcu_pin_obj_t *href = validate_obj_is_free_pin_or_none(args[ARG_href].u_obj);
@ -76,7 +77,7 @@ STATIC mp_obj_t imagecapture_parallelimagecapture_make_new(const mp_obj_type_t *
imagecapture_parallelimagecapture_obj_t *self = m_new_obj(imagecapture_parallelimagecapture_obj_t);
self->base.type = &imagecapture_parallelimagecapture_type;
common_hal_imagecapture_parallelimagecapture_construct(self, data0, clock, vsync, href, args[ARG_data_count].u_int);
common_hal_imagecapture_parallelimagecapture_construct(self, pins, pin_count, clock, vsync, href);
return self;
}

View File

@ -31,12 +31,13 @@
typedef struct imagecapture_parallelimagecapture_obj imagecapture_parallelimagecapture_obj_t;
extern const mp_obj_type_t imagecapture_parallelimagecapture_type;
// if only the first element of data_pins is non-NULL, the pins are sequential in microcontroller pin numbering.
void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_parallelimagecapture_obj_t *self,
const mcu_pin_obj_t *data0,
const uint8_t data_pins[],
uint8_t data_count,
const mcu_pin_obj_t *data_clock,
const mcu_pin_obj_t *vertical_sync,
const mcu_pin_obj_t *horizontal_sync,
int data_count);
const mcu_pin_obj_t *horizontal_sync);
void common_hal_imagecapture_parallelimagecapture_deinit(imagecapture_parallelimagecapture_obj_t *self);
bool common_hal_imagecapture_parallelimagecapture_deinited(imagecapture_parallelimagecapture_obj_t *self);
void common_hal_imagecapture_parallelimagecapture_capture(imagecapture_parallelimagecapture_obj_t *self, void *buffer, size_t bufsize);

View File

@ -137,3 +137,11 @@ void assert_pin_free(const mcu_pin_obj_t *pin) {
mp_raise_ValueError_varg(translate("%q in use"), name);
}
}
void validate_pins(qstr what, uint8_t *pin_nos, mp_int_t max_pins, mp_obj_t seq, uint8_t *count_out) {
mcu_pin_obj_t *pins[max_pins];
validate_list_is_free_pins(what, pins, max_pins, seq, count_out);
for (mp_int_t i = 0; i < *count_out; i++) {
pin_nos[i] = common_hal_mcu_pin_number(pins[i]);
}
}

View File

@ -38,6 +38,7 @@ mcu_pin_obj_t *validate_obj_is_pin_or_none(mp_obj_t obj);
mcu_pin_obj_t *validate_obj_is_free_pin(mp_obj_t obj);
mcu_pin_obj_t *validate_obj_is_free_pin_or_none(mp_obj_t obj);
void validate_list_is_free_pins(qstr what, mcu_pin_obj_t **pins_out, mp_int_t max_pins, mp_obj_t seq, uint8_t *count_out);
void validate_pins(qstr what, uint8_t *pin_nos, mp_int_t max_pins, mp_obj_t seq, uint8_t *count_out);
void assert_pin_free(const mcu_pin_obj_t *pin);

View File

@ -49,14 +49,6 @@ STATIC uint8_t validate_pin(mp_obj_t obj) {
return common_hal_mcu_pin_number(result);
}
STATIC void validate_pins(qstr what, uint8_t *pin_nos, mp_int_t max_pins, mp_obj_t seq, uint8_t *count_out) {
mcu_pin_obj_t *pins[max_pins];
validate_list_is_free_pins(what, pins, max_pins, seq, count_out);
for (mp_int_t i = 0; i < *count_out; i++) {
pin_nos[i] = common_hal_mcu_pin_number(pins[i]);
}
}
STATIC void claim_and_never_reset_pin(mp_obj_t pin) {
common_hal_mcu_pin_claim(pin);
common_hal_never_reset_pin(pin);

View File

@ -236,6 +236,7 @@ size_t get_allocation_length(supervisor_allocation *allocation) {
return ALLOCATION_NODE(allocation)->length & ~FLAGS;
}
void supervisor_move_memory(void) {
// This whole function is not needed when there are no movable allocations, let it be optimized
// out.
@ -244,7 +245,9 @@ void supervisor_move_memory(void) {
}
// This must be called exactly after freeing the heap, so that the embedded allocations, if any,
// are now in the free region.
assert(MP_STATE_VM(first_embedded_allocation) == NULL || (low_head < MP_STATE_VM(first_embedded_allocation) && MP_STATE_VM(first_embedded_allocation) < high_head));
assert(MP_STATE_VM(first_embedded_allocation) == NULL || (
(low_head == NULL || low_head < MP_STATE_VM(first_embedded_allocation)) &&
(high_head == NULL || MP_STATE_VM(first_embedded_allocation) < high_head)));
// Save the old pointers for allocation_from_ptr().
supervisor_allocation old_allocations_array[CIRCUITPY_SUPERVISOR_ALLOC_COUNT];