diff --git a/common/common.mk b/common/common.mk index 71949aaec..c5c9c05ff 100644 --- a/common/common.mk +++ b/common/common.mk @@ -38,6 +38,7 @@ COMMON_SRC_C += \ unaligned_memcpy.c \ usbdbg.c \ vospi.c \ + queue.c \ CFLAGS += -I$(TOP_DIR)/common OMV_FIRM_OBJ += $(addprefix $(BUILD)/common/, $(COMMON_SRC_C:.c=.o)) diff --git a/common/fb_alloc.c b/common/fb_alloc.c index 3d82a164a..9333e4cfe 100644 --- a/common/fb_alloc.c +++ b/common/fb_alloc.c @@ -48,7 +48,7 @@ static char *pointer_overlay = &_fballoc_overlay_end; // Use fb_alloc_free_till_mark_permanent() instead. #define FB_PERMANENT_FLAG 0x2 -char *fb_alloc_stack_pointer() { +char *fb_alloc_sp() { return pointer; } @@ -65,7 +65,7 @@ void fb_alloc_init0() { uint32_t fb_avail() { framebuffer_t *fb = framebuffer_get(0); - uint32_t temp = pointer - framebuffer_get_buffers_end(fb) - sizeof(uint32_t); + uint32_t temp = pointer - framebuffer_pool_end(fb) - sizeof(uint32_t); return (temp < sizeof(uint32_t)) ? 0 : temp; } @@ -74,7 +74,7 @@ void fb_alloc_mark() { char *new_pointer = pointer - sizeof(uint32_t); // Check if allocation overwrites the framebuffer pixels - if (new_pointer < framebuffer_get_buffers_end(fb)) { + if (new_pointer < framebuffer_pool_end(fb)) { nlr_jump(MP_OBJ_TO_PTR(mp_obj_new_exception_msg(&mp_type_MemoryError, MP_ERROR_TEXT("Out of fast frame buffer stack memory")))); } @@ -154,7 +154,7 @@ void *fb_alloc(uint32_t size, int hints) { char *new_pointer = result - sizeof(uint32_t); // Check if allocation overwrites the framebuffer pixels - if (new_pointer < framebuffer_get_buffers_end(fb)) { + if (new_pointer < framebuffer_pool_end(fb)) { fb_alloc_fail(); } @@ -199,7 +199,7 @@ void *fb_alloc0(uint32_t size, int hints) { void *fb_alloc_all(uint32_t *size, int hints) { framebuffer_t *fb = framebuffer_get(0); - uint32_t temp = pointer - framebuffer_get_buffers_end(fb) - sizeof(uint32_t); + uint32_t temp = pointer - framebuffer_pool_end(fb) - sizeof(uint32_t); if (temp < sizeof(uint32_t)) { *size = 0; diff --git a/common/fb_alloc.h b/common/fb_alloc.h index 973148871..1bbc2b98e 100644 --- a/common/fb_alloc.h +++ b/common/fb_alloc.h @@ -35,7 +35,7 @@ #define OMV_ALLOC_ALIGNMENT (OMV_CACHE_LINE_SIZE) #endif -char *fb_alloc_stack_pointer(); +char *fb_alloc_sp(); void fb_alloc_fail(); void fb_alloc_init0(); uint32_t fb_avail(); diff --git a/common/omv_common.h b/common/omv_common.h index 654c9c328..472b54e98 100644 --- a/common/omv_common.h +++ b/common/omv_common.h @@ -46,6 +46,9 @@ #define OMV_ALIGN_TO(x, alignment) \ ((((uintptr_t)(x)) + (alignment) - 1) & ~((uintptr_t)((alignment) - 1))) +#define OMV_ALIGN_DOWN(x, alignment) \ + ((uintptr_t)(x) & ~((uintptr_t)(alignment) - 1)) + #ifdef OMV_DEBUG_PRINTF #define debug_printf(fmt, ...) \ do { printf("%s(): " fmt, __func__, ##__VA_ARGS__);} while (0) diff --git a/common/omv_csi.c b/common/omv_csi.c index e116ff511..676148138 100644 --- a/common/omv_csi.c +++ b/common/omv_csi.c @@ -260,13 +260,16 @@ __weak int omv_csi_abort(omv_csi_t *csi, bool fifo_flush, bool in_irq) { 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); - } + if (csi->fb && fifo_flush && !csi->disable_full_flush) { + framebuffer_flush(csi->fb); } + + #if defined(OMV_CSI_FSYNC_PIN) + if (csi->frame_sync) { + omv_gpio_write(OMV_CSI_FSYNC_PIN, 0); + } + #endif + return 0; } @@ -346,7 +349,7 @@ __weak int omv_csi_reset(omv_csi_t *csi, bool hard) { } // Reset framebuffers - framebuffer_flush_buffers(csi->fb, true); + framebuffer_flush(csi->fb); return 0; } @@ -767,7 +770,7 @@ __weak int omv_csi_set_pixformat(omv_csi_t *csi, pixformat_t pixformat) { csi->fb->pixfmt = PIXFORMAT_INVALID; // Auto-adjust the number of frame buffers. - omv_csi_set_framebuffers(csi, -1); + omv_csi_set_framebuffers(csi, -1, false); // Reconfigure the hardware if needed. return omv_csi_config(csi, OMV_CSI_CONFIG_PIXFORMAT); @@ -811,7 +814,7 @@ __weak int omv_csi_set_framesize(omv_csi_t *csi, omv_csi_framesize_t framesize) csi->fb->pixfmt = PIXFORMAT_INVALID; // Auto-adjust the number of frame buffers. - omv_csi_set_framebuffers(csi, -1); + omv_csi_set_framebuffers(csi, -1, false); // Reconfigure the hardware if needed. return omv_csi_config(csi, OMV_CSI_CONFIG_FRAMESIZE); @@ -925,7 +928,7 @@ __weak int omv_csi_set_windowing(omv_csi_t *csi, int x, int y, int w, int h) { csi->fb->pixfmt = PIXFORMAT_INVALID; // Auto-adjust the number of frame buffers. - omv_csi_set_framebuffers(csi, -1); + omv_csi_set_framebuffers(csi, -1, false); // Reconfigure the hardware if needed. return omv_csi_config(csi, OMV_CSI_CONFIG_WINDOWING); @@ -1249,7 +1252,7 @@ __weak bool omv_csi_get_auto_rotation(omv_csi_t *csi) { return csi->auto_rotation; } -__weak int omv_csi_set_framebuffers(omv_csi_t *csi, int count) { +__weak int omv_csi_set_framebuffers(omv_csi_t *csi, size_t count, bool expand) { // Disable any ongoing frame capture. omv_csi_abort(csi, true, false); @@ -1268,7 +1271,17 @@ __weak int omv_csi_set_framebuffers(omv_csi_t *csi, int count) { // Otherwise, use the real frame size. csi->fb->frame_size = resolution[csi->framesize][0] * resolution[csi->framesize][1] * 2; #endif - return framebuffer_set_buffers(csi->fb, count); + + if (count == -1) { + for (size_t i=3; i>0; i--) { + if (!framebuffer_resize(csi->fb, i, expand)) { + return 0; + } + } + return -1; + } + + return framebuffer_resize(csi->fb, count, expand); } __weak int omv_csi_set_special_effect(omv_csi_t *csi, omv_csi_sde_t sde) { @@ -1436,7 +1449,7 @@ __weak int omv_csi_auto_crop_framebuffer(omv_csi_t *csi) { } // Auto-adjust the number of frame buffers. - omv_csi_set_framebuffers(csi, -1); + omv_csi_set_framebuffers(csi, -1, false); return 0; } @@ -1526,11 +1539,61 @@ __weak int omv_csi_copy_line(omv_csi_t *csi, void *dma, uint8_t *src, uint8_t *d } __weak int omv_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { - int ret = OMV_CSI_ERROR_CTL_UNSUPPORTED; + vbuffer_t *buffer = NULL; + + if (!csi->snapshot) { + return OMV_CSI_ERROR_CTL_UNSUPPORTED; + } + + if (csi->pixformat == PIXFORMAT_INVALID) { + return OMV_CSI_ERROR_INVALID_PIXFORMAT; + } + + if (csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { + return OMV_CSI_ERROR_INVALID_FRAMESIZE; + } + + if (omv_csi_check_framebuffer_size(csi) == -1) { + return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; + } + + buffer = framebuffer_acquire(csi->fb, FB_FLAG_USED | FB_FLAG_PEEK); + + // Compress the previous framebuffer for the IDE preview and release it. + // Note: We must check if the buffer has been used before releasing it, + // as it might have been captured in non-blocking mode but not used yet. + if (buffer && (buffer->flags & VB_FLAG_USED)) { + if (flags & OMV_CSI_CAPTURE_FLAGS_UPDATE) { + image_t tmp; + framebuffer_init_image(csi->fb, &tmp); + framebuffer_update_jpeg_buffer(&tmp); + } + + // Release the previous buffer from used queue -> free queue. + framebuffer_release(csi->fb, FB_FLAG_USED | FB_FLAG_INVALIDATE); + } + + // Toggle FSYNC. + #if defined(OMV_CSI_FSYNC_PIN) + if (csi->frame_sync) { + omv_gpio_write(OMV_CSI_FSYNC_PIN, 1); + } + #endif // Call the sensor specific function. - if (csi->snapshot) { - ret = csi->snapshot(csi, image, flags); + int ret = csi->snapshot(csi, image, flags); + + // Toggle FSYNC. + #if defined(OMV_CSI_FSYNC_PIN) + if (csi->frame_sync) { + omv_gpio_write(OMV_CSI_FSYNC_PIN, 0); + } + #endif + + if (ret == 0) { + // Mark this buffer to be released on the next call. + buffer = framebuffer_acquire(csi->fb, FB_FLAG_USED | FB_FLAG_PEEK); + buffer->flags |= VB_FLAG_USED; } return ret; diff --git a/common/omv_csi.h b/common/omv_csi.h index e9b09057e..3184d3f79 100644 --- a/common/omv_csi.h +++ b/common/omv_csi.h @@ -550,7 +550,7 @@ int omv_csi_set_auto_rotation(omv_csi_t *csi, bool enable); bool omv_csi_get_auto_rotation(omv_csi_t *csi); // Set the number of virtual frame buffers. -int omv_csi_set_framebuffers(omv_csi_t *csi, int count); +int omv_csi_set_framebuffers(omv_csi_t *csi, size_t count, bool expand); // Drop the next frame to match the current frame rate. void omv_csi_throttle_framerate(omv_csi_t *csi); diff --git a/common/queue.c b/common/queue.c new file mode 100644 index 000000000..14232c833 --- /dev/null +++ b/common/queue.c @@ -0,0 +1,190 @@ +/* + * SPDX-License-Identifier: MIT + * + * Copyright (C) 2013-2024 OpenMV, LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Single-producer, single-consumer, lock-free bounded Queue. + * + * ARM processors have weak memory ordering and may reorder memory operations + * for performance. The compiler can also reorder operations. This queue uses + * acquire/release semantics to ensure proper synchronization between producer + * and consumer threads. + */ +#include +#include +#include "queue.h" + +void queue_init(queue_t **q, size_t capacity, void *buffer) { + if (!q || !buffer) { + return; + } + + *q = (queue_t *) buffer; + (*q)->capacity = capacity; + queue_flush(*q); +} + +queue_t *queue_alloc(size_t capacity) { + if (capacity == 0) { + return NULL; + } + + void *buffer = malloc(queue_calc_size(capacity)); + if (!buffer) { + return NULL; + } + + queue_t *q; + queue_init(&q, capacity, buffer); + return q; +} + +void queue_destroy(queue_t *q) { + free(q); +} + +void queue_flush(queue_t *q) { + if (!q) { + return; + } + + #ifndef HAVE_STDATOMIC_H + q->head = 0; + q->tail = 0; + #else + atomic_store_explicit(&q->head, 0, memory_order_relaxed); + atomic_store_explicit(&q->tail, 0, memory_order_relaxed); + #endif +} + +bool queue_push(queue_t *q, void *item) { + if (!q || !item) { + return false; + } + + #ifndef HAVE_STDATOMIC_H + size_t new_tail = (q->tail + 1) % (q->capacity + 1); + + if (new_tail == q->head) { + return false; // Queue full + } + q->items[q->tail] = item; + q->tail = new_tail; + #else + size_t old_tail = atomic_load_explicit(&q->tail, memory_order_relaxed); + size_t new_tail = (old_tail + 1) % (q->capacity + 1); + + // Ensure that the previous head is consumed. + if (new_tail == atomic_load_explicit(&q->head, memory_order_acquire)) { + return false; // Queue full + } + + q->items[old_tail] = item; + // Release ensures the write completes before advancing tail + atomic_store_explicit(&q->tail, new_tail, memory_order_release); + #endif + + return true; +} + +void *queue_pop(queue_t *q, bool peek) { + if (queue_is_empty(q)) { + return NULL; + } + + #ifndef HAVE_STDATOMIC_H + void *item = q->items[q->head]; + if (!peek) { + q->head = (q->head + 1) % (q->capacity + 1); + } + #else + size_t old_head = atomic_load_explicit(&q->head, memory_order_relaxed); + size_t new_head = (old_head + 1) % (q->capacity + 1); + void *item = q->items[old_head]; + + if (!peek) { + // Release ensures the read completes before advancing head + atomic_store_explicit(&q->head, new_head, memory_order_release); + } + #endif + + return item; +} + +bool queue_is_empty(const queue_t *q) { + if (!q) { + return true; + } + + #ifndef HAVE_STDATOMIC_H + size_t head = q->head; + size_t tail = q->tail; + #else + // Ensure the current thread sees all prior updates to head and tail + size_t head = atomic_load_explicit(&q->head, memory_order_acquire); + size_t tail = atomic_load_explicit(&q->tail, memory_order_acquire); + #endif + return head == tail; +} + +size_t queue_size(const queue_t *q) { + if (!q) { + return 0; + } + + #ifndef HAVE_STDATOMIC_H + size_t head = q->head; + size_t tail = q->tail; + #else + // Ensure the current thread sees all prior updates to head and tail + size_t head = atomic_load_explicit(&q->head, memory_order_acquire); + size_t tail = atomic_load_explicit(&q->tail, memory_order_acquire); + #endif + + // Calculate size considering the circular buffer + if (tail >= head) { + return tail - head; + } else { + return (q->capacity + 1) - head + tail; + } +} + +// Swap the last item of q0 (tail - 1) with the first item of q1 (head). +// Currently, this is only used for a special case in the framebuffer, +// and it's only safe under very specific conditions. +void *queue_swap(queue_t *q0, queue_t *q1) { + #ifndef HAVE_STDATOMIC_H + size_t tail = (q0->tail == 0) ? q0->capacity : q0->tail - 1; + size_t head = q1->head; + #else + size_t tail = atomic_load_explicit(&q0->tail, memory_order_acquire); + size_t head = atomic_load_explicit(&q1->head, memory_order_acquire); + tail = (tail == 0) ? q0->capacity : tail - 1; + #endif + + void *item0 = q0->items[tail]; + void *item1 = q1->items[head]; + + q0->items[tail] = item1; + q1->items[head] = item0; + + return item0; +} diff --git a/common/queue.h b/common/queue.h new file mode 100644 index 000000000..7bca4b724 --- /dev/null +++ b/common/queue.h @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: MIT + * + * Copyright (C) 2013-2024 OpenMV, LLC. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Single-producer, single-consumer, lock-free bounded Queue. + */ +#ifndef __QUEUE_H__ +#define __QUEUE_H__ + +#include +#include + +#if __STDC_VERSION__ < 201112L +typedef size_t queue_index_t; +#warning "Atomics not supported" +#else +#include +#define HAVE_STDATOMIC_H +typedef atomic_size_t queue_index_t; +#endif + +typedef struct { + size_t capacity; + queue_index_t head; + queue_index_t tail; // TODO pad with cache line if needed. + void *items[]; +} queue_t; + +// One extra slot is used to distinguish full from empty. +#define queue_calc_size(capacity) \ + (sizeof(queue_t) + ((capacity) + 1) * sizeof(void *)) + +void queue_init(queue_t **q, size_t capacity, void *buffer); +void queue_flush(queue_t *q); +queue_t *queue_alloc(size_t capacity); +void queue_destroy(queue_t *q); +bool queue_is_empty(const queue_t *q); +bool queue_push(queue_t *q, void *item); +void *queue_pop(queue_t *q, bool peek); +size_t queue_size(const queue_t *q); +void *queue_swap(queue_t *q0, queue_t *q1); +#endif // __QUEUE_H__ diff --git a/common/vospi.c b/common/vospi.c index 08da11c16..a9f588a7f 100644 --- a/common/vospi.c +++ b/common/vospi.c @@ -28,6 +28,8 @@ #include #include +#include + #include "py/mphal.h" #include "vospi.h" @@ -44,17 +46,17 @@ #define VOSPI_BUFFER_SIZE (VOSPI_PACKET_SIZE * 2) // 16-bits #define VOSPI_CLOCK_SPEED 20000000 // hz -#define VOSPI_SYNC_MS 200 // ms +#define VOSPI_SYNC_MS 250 // ms -#define VOSPI_SPECIAL_PACKET (20) #define VOSPI_DONT_CARE_PACKET (0x0F00) #define VOSPI_HEADER_DONT_CARE(x) (((x) & VOSPI_DONT_CARE_PACKET) == VOSPI_DONT_CARE_PACKET) #define VOSPI_HEADER_PID(id) ((id) & 0x0FFF) #define VOSPI_HEADER_SID(id) (((id) >> 12) & 0x7) +#define VOSPI_IS_SPECIAL_PACKET(pid) (vospi.lepton_3 && pid == 20) typedef enum { - VOSPI_FLAGS_CAPTURE = (1 << 0), - VOSPI_FLAGS_RESYNC = (1 << 1), + VOSPI_FLAG_STREAM = (1 << 0), + VOSPI_FLAG_CAPTURE = (1 << 1), } vospi_flags_t; typedef struct _vospi_state { @@ -67,24 +69,10 @@ typedef struct _vospi_state { } vospi_state_t; static vospi_state_t vospi; - static uint16_t OMV_ATTR_SEC_ALIGN(vospi_buf[VOSPI_BUFFER_SIZE], OMV_VOSPI_DMA_BUFFER, OMV_DMA_ALIGNMENT); -static void vospi_callback(omv_spi_t *spi, void *userdata, void *buf); -static void vospi_resync() { - omv_spi_transfer_t spi_xfer = { - .rxbuf = vospi_buf, - .size = VOSPI_BUFFER_SIZE, - .flags = OMV_SPI_XFER_DMA, - .callback = vospi_callback, - }; - - mp_hal_delay_ms(VOSPI_SYNC_MS); - omv_spi_transfer_start(&vospi.spi_bus, &spi_xfer); -} - -#if defined(OMV_ENABLE_VOSPI_CRC) static bool vospi_check_crc(const uint16_t *base) { + #if defined(OMV_ENABLE_VOSPI_CRC) int id = base[0]; int packet_crc = base[1]; int crc = ByteCRC16((id >> 8) & 0x0F, 0); @@ -99,11 +87,14 @@ static bool vospi_check_crc(const uint16_t *base) { } return packet_crc == crc; + #else + return true; + #endif } -#endif -void vospi_callback(omv_spi_t *spi, void *userdata, void *buf) { - if (!(vospi.flags & VOSPI_FLAGS_CAPTURE)) { +static void vospi_callback(omv_spi_t *spi, void *userdata, void *buf) { + if (!(vospi.flags & VOSPI_FLAG_CAPTURE) || + !(vospi.flags & VOSPI_FLAG_STREAM)) { return; } @@ -120,62 +111,58 @@ void vospi_callback(omv_spi_t *spi, void *userdata, void *buf) { int sid = VOSPI_HEADER_SID(id) - 1; // Discard packets with a pid != 0 when waiting for the first packet. - if ((vospi.pid == 0) && (pid != 0)) { + if (vospi.pid == 0 && pid != 0) { return; } - // Discard sidments with a sid != 0 when waiting for the first segment. - if (vospi.lepton_3 && (pid == VOSPI_SPECIAL_PACKET) && (vospi.sid == 0) && (sid != 0)) { + // Discard segments with a sid != 0 when waiting for the first segment. + if (VOSPI_IS_SPECIAL_PACKET(pid) && vospi.sid == 0 && sid != 0) { vospi.pid = 0; return; } // Are we in sync with the flir lepton? - if ((pid != vospi.pid) - #if defined(OMV_ENABLE_VOSPI_CRC) - || (!vospi_check_crc(base)) - #endif - || (vospi.lepton_3 && (pid == VOSPI_SPECIAL_PACKET) && (sid != vospi.sid))) { + if (pid != vospi.pid || + !vospi_check_crc(base) || + (VOSPI_IS_SPECIAL_PACKET(pid) && sid != vospi.sid)) { vospi_abort(); - vospi.flags |= VOSPI_FLAGS_CAPTURE; return; } - vbuffer_t *buffer = framebuffer_get_tail(vospi.fb, FB_PEEK); + vbuffer_t *buffer = framebuffer_acquire(vospi.fb, FB_FLAG_FREE | FB_FLAG_PEEK); - if (buffer) { - memcpy(((uint16_t *) buffer->data) - + (vospi.pid * VOSPI_PID_SIZE_PIXELS) - + (vospi.sid * VOSPI_SID_SIZE_PIXELS), - base + VOSPI_HEADER_WORDS, VOSPI_PID_SIZE_PIXELS * sizeof(uint16_t)); - - vospi.pid += 1; - if (vospi.pid == VOSPI_PIDS_PER_SID) { - vospi.pid = 0; - - // For the FLIR Lepton 3 we have to receive all the pids in all the segments. - if (vospi.lepton_3) { - vospi.sid += 1; - if (vospi.sid == VOSPI_SIDS_PER_FRAME) { - vospi.sid = 0; - framebuffer_get_tail(vospi.fb, FB_NO_FLAGS); - } - // For the FLIR Lepton 1/2 we just have to receive all the pids. - } else { - framebuffer_get_tail(vospi.fb, FB_NO_FLAGS); - } - } - } else { - vospi.flags &= ~VOSPI_FLAGS_CAPTURE; + if (!buffer) { + vospi.flags &= ~VOSPI_FLAG_CAPTURE; + return; } + + memcpy(((uint16_t *) buffer->data) + + (vospi.pid * VOSPI_PID_SIZE_PIXELS) + + (vospi.sid * VOSPI_SID_SIZE_PIXELS), + base + VOSPI_HEADER_WORDS, VOSPI_PID_SIZE_PIXELS * sizeof(uint16_t)); + + if (++vospi.pid == VOSPI_PIDS_PER_SID) { + vospi.pid = 0; + + // For the FLIR Lepton 3 we have to receive all the pids in all the segments. + if (vospi.lepton_3) { + if (++vospi.sid == VOSPI_SIDS_PER_FRAME) { + vospi.sid = 0; + framebuffer_release(vospi.fb, FB_FLAG_FREE); + } + // For the FLIR Lepton 1/2 we just have to receive all the pids. + } else { + // Move the buffer from free queue -> used queue. + framebuffer_release(vospi.fb, FB_FLAG_FREE); + } + } + } int vospi_init(uint32_t n_packets, framebuffer_t *fb) { memset(&vospi, 0, sizeof(vospi_state_t)); - vospi.lepton_3 = n_packets > VOSPI_PIDS_PER_SID; vospi.fb = fb; - // resync on first snapshot. - vospi.flags = VOSPI_FLAGS_RESYNC; + vospi.lepton_3 = n_packets > VOSPI_PIDS_PER_SID; omv_spi_config_t spi_config; omv_spi_default_config(&spi_config, OMV_CSI_SPI_ID); @@ -194,50 +181,34 @@ int vospi_deinit() { return omv_spi_deinit(&vospi.spi_bus); } +bool vospi_active(void) { + return vospi.flags & VOSPI_FLAG_CAPTURE; +} + int vospi_abort(void) { - vospi.flags &= ~VOSPI_FLAGS_CAPTURE; + vospi.flags = 0; int ret = omv_spi_transfer_abort(&vospi.spi_bus); vospi.pid = 0; vospi.sid = 0; - vospi.flags |= VOSPI_FLAGS_RESYNC; return ret; } -bool vospi_active(void) { - return vospi.flags & VOSPI_FLAGS_CAPTURE; -} +void vospi_restart(void) { + omv_spi_transfer_t spi_xfer = { + .rxbuf = vospi_buf, + .size = VOSPI_BUFFER_SIZE, + .flags = OMV_SPI_XFER_DMA, + .callback = vospi_callback, + }; -int vospi_snapshot(uint32_t timeout_ms) { - framebuffer_free_current_buffer(vospi.fb); + // Resume streaming. + vospi.flags |= VOSPI_FLAG_CAPTURE; - if (!(vospi.flags & VOSPI_FLAGS_CAPTURE)) { - framebuffer_setup_buffers(vospi.fb); - - // Restart counters to capture a new frame. - vospi.flags |= VOSPI_FLAGS_CAPTURE; + if (!(vospi.flags & VOSPI_FLAG_STREAM)) { + mp_hal_delay_ms(VOSPI_SYNC_MS); + omv_spi_transfer_start(&vospi.spi_bus, &spi_xfer); + vospi.flags |= VOSPI_FLAG_STREAM; } - // Snapshot start tick - mp_uint_t tick_start = mp_hal_ticks_ms(); - - do { - if (vospi.flags & VOSPI_FLAGS_RESYNC) { - vospi.flags &= ~VOSPI_FLAGS_RESYNC; - vospi_resync(); - } - - if (timeout_ms == 0) { - return -1; - } - - if ((mp_hal_ticks_ms() - tick_start) > timeout_ms) { - vospi_abort(); - return -1; - } - - MICROPY_EVENT_POLL_HOOK - } while (!framebuffer_get_head(vospi.fb, FB_NO_FLAGS)); - - return 0; } #endif diff --git a/common/vospi.h b/common/vospi.h index 08499cba2..b643a7cb9 100644 --- a/common/vospi.h +++ b/common/vospi.h @@ -30,5 +30,5 @@ int vospi_init(uint32_t n_packets, framebuffer_t *fb); int vospi_deinit(); int vospi_abort(void); bool vospi_active(void); -int vospi_snapshot(uint32_t timeout_ms); +void vospi_restart(void); #endif // __VOSPI_H__ diff --git a/drivers/sensors/lepton.c b/drivers/sensors/lepton.c index dc6340f6b..137bb4d99 100644 --- a/drivers/sensors/lepton.c +++ b/drivers/sensors/lepton.c @@ -399,68 +399,50 @@ static int config(omv_csi_t *csi, omv_csi_config_t config) { } static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { + vbuffer_t *buffer = NULL; + size_t reset_retry = 0; framebuffer_t *fb = csi->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) { - image_t tmp; - framebuffer_init_image(fb, &tmp); - framebuffer_update_jpeg_buffer(&tmp); - } - - if (csi->pixformat == PIXFORMAT_INVALID) { - return OMV_CSI_ERROR_INVALID_PIXFORMAT; - } - - if (csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { - return OMV_CSI_ERROR_INVALID_FRAMESIZE; - } - if (!lepton.h_res || !lepton.v_res) { return OMV_CSI_ERROR_INVALID_FRAMESIZE; } - if (resolution[csi->framesize][0] < lepton.h_res || resolution[csi->framesize][1] < lepton.v_res) { + if (resolution[csi->framesize][0] < lepton.h_res || + resolution[csi->framesize][1] < lepton.v_res) { return OMV_CSI_ERROR_INVALID_FRAMESIZE; } - if (omv_csi_check_framebuffer_size(csi) == -1) { - return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; - } - - for (int i = 0; i < LEPTON_SNAPSHOT_RETRY; i++) { - if (vospi_snapshot(LEPTON_SNAPSHOT_TIMEOUT) == 0) { + for (mp_uint_t tick_start = mp_hal_ticks_ms(); ; mp_event_handle_nowait()) { + // Restart first (if needed) before returning the frame or timing out. + if (!vospi_active()) { + vospi_restart(); + } + + if ((buffer = framebuffer_acquire(csi->fb, FB_FLAG_USED | FB_FLAG_PEEK))) { break; } - if (i + 1 == LEPTON_SNAPSHOT_RETRY) { - return OMV_CSI_ERROR_CAPTURE_TIMEOUT; + + if (flags & OMV_CSI_CAPTURE_FLAGS_NBLOCK) { + return OMV_CSI_ERROR_WOULD_BLOCK; } - // The FLIR lepton might have crashed so reset it (it does this). - if (lepton_reset(csi, lepton.measurement_mode, lepton.high_temp_mode) != 0) { - return OMV_CSI_ERROR_CTL_FAILED; + + // Reset the FLIR lepton on timeout, up to LEPTON_SNAPSHOT_RETRY. + if ((mp_hal_ticks_ms() - tick_start) > LEPTON_SNAPSHOT_TIMEOUT) { + if (reset_retry++ == LEPTON_SNAPSHOT_RETRY) { + vospi_abort(); + return OMV_CSI_ERROR_CAPTURE_TIMEOUT; + } + + if (lepton_reset(csi, lepton.measurement_mode, lepton.high_temp_mode) != 0) { + return OMV_CSI_ERROR_CTL_FAILED; + } + + tick_start = mp_hal_ticks_ms(); } } - if (!csi->transpose) { - fb->w = fb->u; - fb->h = fb->v; - } else { - fb->w = fb->v; - fb->h = fb->u; - } - + fb->w = csi->transpose ? fb->v : fb->u; + fb->h = csi->transpose ? fb->u : fb->v; fb->pixfmt = csi->pixformat; image_t fb_image; @@ -475,27 +457,29 @@ static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { fb_alloc_mark(); image_t temp = { - .w = (!csi->transpose) ? lepton.h_res : lepton.v_res, - .h = (!csi->transpose) ? lepton.v_res : lepton.h_res, + .w = csi->transpose ? lepton.v_res : lepton.h_res, + .h = csi->transpose ? lepton.h_res : lepton.v_res, .pixfmt = PIXFORMAT_GRAYSCALE, .data = fb_alloc(lepton.h_res * lepton.v_res, FB_ALLOC_CACHE_ALIGN), }; - // When not in measurment mode set the min and max temperatures such that 0-255 values from the - // sensor, which are grayscale pixels 0-255, are not clipped when interpreted as Kelvin values. - float min = -273.15f; // in Celsius -> 0.0f in Kelvin - float max = -270.6f; // in Celsius -> 2.55f in Kelvin + // When not in measurment mode set the min and max temperatures such + // that 0-255 values from the sensor, which are grayscale pixels 0-255, + // are not clipped when interpreted as Kelvin values. + float min = -273.15f; // In Celsius -> 0.0f in Kelvin + float max = -270.6f; // In Celsius -> 2.55f in Kelvin - // When in measurment mode the lepton provides 14-bit or 16-bit values that must be clamped - // between the min and max temperatures in celsius and scaled to 0-255 values. + // When in measurment mode the lepton provides 14-bit or 16-bit values + // that must be clamped between the min and max temperatures in celsius + // and scaled to 0-255 values. if (lepton.measurement_mode) { min = lepton.min_temp; max = lepton.max_temp; } - imlib_fill_image_from_lepton(&temp, lepton.h_res, lepton.v_res, (uint16_t *) fb_image.data, min, max, - false, (!lepton.measurement_mode) || lepton.radiometry, kelvin, - lepton.hmirror, lepton.vflip, csi->transpose); + imlib_fill_image_from_lepton(&temp, lepton.h_res, lepton.v_res, (uint16_t *) fb_image.data, + min, max, false, (!lepton.measurement_mode) || lepton.radiometry, + kelvin, lepton.hmirror, lepton.vflip, csi->transpose); imlib_draw_image(&fb_image, &temp, 0, 0, 1.0f, 1.0f, NULL, -1, 255, (csi->pixformat == PIXFORMAT_RGB565) ? csi->color_palette : NULL, NULL, diff --git a/drivers/sensors/softcsi.c b/drivers/sensors/softcsi.c index 00d928d1d..952b0b3e8 100644 --- a/drivers/sensors/softcsi.c +++ b/drivers/sensors/softcsi.c @@ -64,52 +64,32 @@ static int set_vflip(omv_csi_t *csi, int enable) { static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { framebuffer_t *fb = csi->fb; + // This driver can't use handle NULL images. if (!image) { return 0; } - if (flags & OMV_CSI_CAPTURE_FLAGS_UPDATE) { - image_t tmp; - framebuffer_init_image(fb, &tmp); - framebuffer_update_jpeg_buffer(&tmp); - } - - if (fb->n_buffers != 1) { - framebuffer_set_buffers(fb, 1); - } - - if (csi->pixformat == PIXFORMAT_INVALID) { - return OMV_CSI_ERROR_INVALID_PIXFORMAT; - } - - if (csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { - return OMV_CSI_ERROR_INVALID_FRAMESIZE; - } - - if (omv_csi_check_framebuffer_size(csi) == -1) { - return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; - } - - framebuffer_free_current_buffer(fb); - vbuffer_t *buffer = framebuffer_get_tail(fb, FB_NO_FLAGS); + // Acquire a new free buffer. + vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); if (!buffer) { return OMV_CSI_ERROR_FRAMEBUFFER_ERROR; } - if (!csi->transpose) { - fb->w = fb->u; - fb->h = fb->v; - } else { - fb->w = fb->v; - fb->h = fb->u; - } - + // Set the framebuffer pixel format. fb->pixfmt = csi->pixformat; + + // Set the framebuffer width/height. + fb->w = csi->transpose ? fb->v : fb->u; + fb->h = csi->transpose ? fb->u : fb->v; + + // The new buffer hasn't been released yet, so the data pointer + // has to be set manually after calling framebuffer_init_image. framebuffer_init_image(fb, image); + image->pixels = buffer->data; static uint32_t step = 0; - uint32_t offset = (step / 4); + uint32_t offset = (step++ / 4); for (size_t y = 0; y < image->h; y++) { for (size_t x = 0; x < image->w; x++) { @@ -147,7 +127,9 @@ static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { } } - step++; + // Move the buffer from free queue -> used queue. + framebuffer_release(fb, FB_FLAG_FREE); + framebuffer_init_image(fb, image); return 0; } diff --git a/lib/imlib/framebuffer.c b/lib/imlib/framebuffer.c index 5f932a59b..45629c151 100644 --- a/lib/imlib/framebuffer.c +++ b/lib/imlib/framebuffer.c @@ -24,54 +24,53 @@ * Framebuffer functions. */ #include +#include "py/mphal.h" #include "mpprint.h" #include "fmath.h" #include "framebuffer.h" #include "omv_boardconfig.h" -#define FB_ALIGN_SIZE_ROUND_DOWN(x) (((x) / FRAMEBUFFER_ALIGNMENT) * FRAMEBUFFER_ALIGNMENT) -#define FB_ALIGN_SIZE_ROUND_UP(x) FB_ALIGN_SIZE_ROUND_DOWN(((x) + FRAMEBUFFER_ALIGNMENT - 1)) -#define OMV_JPEG_BUFFER_SIZE_MAX ((&_jpeg_memory_end - &_jpeg_memory_start) - sizeof(jpegbuffer_t)) - -extern char _fb_memory_start[]; -extern char _fb_memory_end[]; -static framebuffer_t *framebuffer = (framebuffer_t *) &_fb_memory_start; +extern char _fb_memory_start; +extern char _fb_memory_end; +static framebuffer_t framebuffer; extern char _jpeg_memory_start; extern char _jpeg_memory_end; -jpegbuffer_t *jpegbuffer = (jpegbuffer_t *) &_jpeg_memory_start; +jpegbuffer_t jpegbuffer; void framebuffer_init0() { - // Save enable flag. - int fb_enabled = jpegbuffer->enabled; - uint32_t fb_size = (char *) &_fb_memory_end - (char *) framebuffer->data; + // Save enable flag before resetting the state. + int fb_enabled = jpegbuffer.enabled; - // Initialize frame buffer. - framebuffer_init_fb(framebuffer, fb_size, false); + // Initialize the static frame buffer. + framebuffer_init(&framebuffer, &_fb_memory_start, &_fb_memory_end - &_fb_memory_start, false); // Initialize jpeg buffer. - memset(jpegbuffer, 0, sizeof(*jpegbuffer)); - mutex_init0(&jpegbuffer->lock); - jpegbuffer->enabled = fb_enabled; - jpegbuffer->quality = ((OMV_JPEG_QUALITY_HIGH - OMV_JPEG_QUALITY_LOW) / 2) + OMV_JPEG_QUALITY_LOW; + memset(&jpegbuffer, 0, sizeof(jpegbuffer_t)); + mutex_init0(&jpegbuffer.lock); + jpegbuffer.enabled = fb_enabled; + jpegbuffer.pixels = (uint8_t *) &_jpeg_memory_start; + jpegbuffer.quality = ((OMV_JPEG_QUALITY_HIGH - OMV_JPEG_QUALITY_LOW) / 2) + OMV_JPEG_QUALITY_LOW; } -void framebuffer_init_fb(framebuffer_t *fb, size_t size, bool dynamic) { +void framebuffer_init(framebuffer_t *fb, void *buff, size_t size, bool dynamic) { // Clear framebuffers - memset(fb, 0, sizeof(*fb)); + memset(fb, 0, sizeof(framebuffer_t)); fb->raw_size = size; + fb->raw_base = buff; fb->dynamic = dynamic; - framebuffer_set_buffers(fb, 1); } void framebuffer_init_image(framebuffer_t *fb, image_t *img) { if (img != NULL) { + vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_USED | FB_FLAG_PEEK); + img->w = fb->w; img->h = fb->h; img->size = fb->size; img->pixfmt = fb->pixfmt; - img->pixels = framebuffer_get_buffer(fb, fb->head)->data; + img->pixels = (buffer == NULL) ? NULL : buffer->data; } } @@ -84,364 +83,254 @@ void framebuffer_init_from_image(framebuffer_t *fb, image_t *img) { static void jpegbuffer_init_from_image(image_t *img) { if (img == NULL) { - jpegbuffer->w = 0; - jpegbuffer->h = 0; - jpegbuffer->size = 0; + jpegbuffer.w = 0; + jpegbuffer.h = 0; + jpegbuffer.size = 0; } else { - jpegbuffer->w = img->w; - jpegbuffer->h = img->h; - jpegbuffer->size = img->size; - } -} - -void framebuffer_update_jpeg_buffer(image_t *src) { - static int overflow_count = 0; - - if (src->pixfmt != PIXFORMAT_INVALID && jpegbuffer->enabled) { - if (src->is_compressed) { - if (mutex_try_lock_alternate(&jpegbuffer->lock, MUTEX_TID_OMV)) { - if (OMV_JPEG_BUFFER_SIZE_MAX < src->size) { - jpegbuffer_init_from_image(NULL); - mp_printf(MP_PYTHON_PRINTER, "\x1b[40O\n"); - } else { - jpegbuffer_init_from_image(src); - memcpy(jpegbuffer->pixels, src->pixels, src->size); - } - - mutex_unlock(&jpegbuffer->lock, MUTEX_TID_OMV); - } - } else if (src->pixfmt != PIXFORMAT_INVALID) { - if (mutex_try_lock_alternate(&jpegbuffer->lock, MUTEX_TID_OMV)) { - image_t dst = { - .w = src->w, - .h = src->h, - .pixfmt = PIXFORMAT_JPEG, - .size = OMV_JPEG_BUFFER_SIZE_MAX, - .pixels = jpegbuffer->pixels - }; - - bool compress = true; - bool overflow = false; - - #if OMV_RAW_PREVIEW_ENABLE - if (src->is_mutable) { - // Down-scale the frame (if necessary) and send the raw frame. - dst.size = src->bpp; - dst.pixfmt = src->pixfmt; - if (src->w <= OMV_RAW_PREVIEW_WIDTH && src->h <= OMV_RAW_PREVIEW_HEIGHT) { - if (image_size(&dst) <= OMV_JPEG_BUFFER_SIZE_MAX) { - memcpy(dst.pixels, src->pixels, image_size(src)); - compress = false; - } - } else { - float x_scale = OMV_RAW_PREVIEW_WIDTH / (float) src->w; - float y_scale = OMV_RAW_PREVIEW_HEIGHT / (float) src->h; - float scale = IM_MIN(x_scale, y_scale); - dst.w = fast_floorf(src->w * scale); - dst.h = fast_floorf(src->h * scale); - if (image_size(&dst) <= OMV_JPEG_BUFFER_SIZE_MAX) { - imlib_draw_image(&dst, src, 0, 0, scale, scale, NULL, -1, 255, NULL, NULL, - IMAGE_HINT_BILINEAR | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL, NULL); - compress = false; - } - } - } - #endif - - if (compress) { - // For all other formats, send a compressed frame. - overflow = jpeg_compress(src, &dst, jpegbuffer->quality, false, JPEG_SUBSAMPLING_AUTO); - } - - if (overflow) { - // JPEG buffer overflowed, reduce JPEG quality for the next frame - // and skip the current frame. The IDE doesn't receive this frame. - if (jpegbuffer->quality > 1) { - // Keep this quality for the next n frames - overflow_count = 60; - jpegbuffer->quality = IM_MAX(1, (jpegbuffer->quality / 2)); - } - - jpegbuffer_init_from_image(NULL); - } else { - if (overflow_count) { - overflow_count--; - } - - // Dynamically adjust our quality if the image is huge. - bool big_frame_buffer = image_size(src) > OMV_JPEG_QUALITY_THRESHOLD; - int jpeg_quality_max = big_frame_buffer ? OMV_JPEG_QUALITY_LOW : OMV_JPEG_QUALITY_HIGH; - - // No buffer overflow, increase quality up to max quality based on frame size... - if ((!overflow_count) && (jpegbuffer->quality < jpeg_quality_max)) { - jpegbuffer->quality++; - } - - jpegbuffer_init_from_image(&dst); - } - - mutex_unlock(&jpegbuffer->lock, MUTEX_TID_OMV); - } - } + jpegbuffer.w = img->w; + jpegbuffer.h = img->h; + jpegbuffer.size = img->size; } } framebuffer_t *framebuffer_get(size_t id) { - return framebuffer; + return &framebuffer; } -int32_t framebuffer_get_x(framebuffer_t *fb) { - return fb->x; +char *framebuffer_pool_start(framebuffer_t *fb, size_t buf_count) { + size_t qsize = (buf_count <= 3) ? 0 : queue_calc_size(buf_count); + return fb->raw_base + OMV_ALIGN_TO(qsize * 2, FRAMEBUFFER_ALIGNMENT); } -int32_t framebuffer_get_y(framebuffer_t *fb) { - return fb->y; +char *framebuffer_pool_end(framebuffer_t *fb) { + char *pool_start = framebuffer_pool_start(fb, fb->buf_count); + return pool_start + ((fb->buf_size + sizeof(vbuffer_t)) * fb->buf_count); } -int32_t framebuffer_get_u(framebuffer_t *fb) { - return fb->u; +void *framebuffer_pool_get(framebuffer_t *fb, int32_t index) { + char *pool_start = framebuffer_pool_start(fb, fb->buf_count); + return pool_start + ((fb->buf_size + sizeof(vbuffer_t)) * index); } -int32_t framebuffer_get_v(framebuffer_t *fb) { - return fb->v; -} +void framebuffer_flush(framebuffer_t *fb) { + // Invalidate the frame buffer. + fb->pixfmt = PIXFORMAT_INVALID; -int32_t framebuffer_get_width(framebuffer_t *fb) { - return fb->w; -} - -int32_t framebuffer_get_height(framebuffer_t *fb) { - return fb->h; -} - -int32_t framebuffer_get_depth(framebuffer_t *fb) { - return fb->bpp; -} - -// 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(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; + // Drop all frame buffers. + if (fb->buf_count) { + queue_flush(fb->free_queue); + queue_flush(fb->used_queue); } - return IM_MIN(fb_total_size, fb_avail_size); + for (size_t i=0; ibuf_count; i++) { + vbuffer_t *buffer = framebuffer_pool_get(fb, i); + + // Reset the buffer's state. + framebuffer_reset(buffer); + + // Discard any cached CPU writes. + #ifdef __DCACHE_PRESENT + SCB_InvalidateDCache_by_Addr(buffer->data, fb->buf_size); + #endif + + // Push it back the free queue. + queue_push(fb->free_queue, buffer); + } } -uint32_t framebuffer_get_buffer_size(framebuffer_t *fb) { - uint32_t size; +int framebuffer_resize(framebuffer_t *fb, size_t count, bool expand) { + size_t buf_size = 0; + // Queue size given the requested buffer count. + size_t queue_size = queue_calc_size(count); - if (fb->n_buffers == 1) { - // With only 1 vbuffer the frame buffer size can change given fb_alloc(). - size = framebuffer_max_buffer_size(fb); + // Maximum usable memory size without queues. + size_t max_size = fb->raw_size - queue_size * 2; + size_t min_size = fb->frame_size + sizeof(vbuffer_t); + + // Use the frame buffer memory for big queues. + char *queue_memory = (count > 3) ? fb->raw_base : fb->raw_static; + + // Calculate a single buffer size (including vbuffer header). + if (!expand) { + // No expansion: buffer size equals frame size plus header. + buf_size = OMV_ALIGN_TO(min_size, FRAMEBUFFER_ALIGNMENT); + } else if (fb->dynamic) { + // Expanding a dynamic FB: divide the raw buffer size evenly. + buf_size = OMV_ALIGN_DOWN(max_size / count, FRAMEBUFFER_ALIGNMENT); } else { - // Whatever the raw size was when the number of buffers were set is locked in. - size = fb->buff_size; + // Expanding a static FB: calculate the free FB memory size. + size_t fb_size = fb_alloc_sp() - framebuffer_pool_start(fb, count); + max_size = IM_MIN(max_size, fb_size); + buf_size = OMV_ALIGN_DOWN(max_size / count, FRAMEBUFFER_ALIGNMENT); } - // Remove the size of the state header plus alignment padding. - size -= sizeof(vbuffer_t); - - // Needs to be a multiple of FRAMEBUFFER_ALIGNMENT for DMA transfers. - return FB_ALIGN_SIZE_ROUND_DOWN(size); -} - -// Each raw frame buffer is split into two parts. The vbuffer_t struct followed by -// padding and then the pixel array starting at the next 32-byte offset. -vbuffer_t *framebuffer_get_buffer(framebuffer_t *fb, int32_t index) { - uint32_t fbsize = framebuffer_get_buffer_size(fb); - uint32_t offset = (sizeof(vbuffer_t) + fbsize) * index; - return (vbuffer_t *) (fb->data + offset); -} - -void framebuffer_flush_buffers(framebuffer_t *fb, bool fifo_flush) { - if (fifo_flush) { - // Drop all frame buffers. - for (uint32_t i = 0; i < fb->n_buffers; i++) { - memset(framebuffer_get_buffer(fb, i), 0, sizeof(vbuffer_t)); - } - } - // Move the tail pointer to the head which empties the virtual fifo while keeping the same - // position of the current frame for the rest of the code. - fb->tail = fb->head; - fb->check_head = true; - fb->sampled_head = 0; -} - -int framebuffer_set_buffers(framebuffer_t *fb, int32_t n_buffers) { - uint32_t avail_size = FB_ALIGN_SIZE_ROUND_DOWN(framebuffer_max_buffer_size(fb)); - uint32_t frame_size = FB_ALIGN_SIZE_ROUND_UP(fb->frame_size + sizeof(vbuffer_t)); - uint32_t vbuff_size = (n_buffers == 1) ? avail_size : frame_size; - uint32_t vbuff_count = IM_MIN((avail_size / vbuff_size), (n_buffers == -1) ? 3 : (uint32_t) n_buffers); - - if (vbuff_count == 0 || vbuff_size < sizeof(vbuffer_t)) { + // Ensure that the buffer size is reasonable. + if (buf_size < min_size || buf_size * count > max_size) { return -1; } - fb->head = 0; - fb->buff_size = vbuff_size; - fb->n_buffers = vbuff_count; - fb->pixfmt = PIXFORMAT_INVALID; + // Initialize the frame buffer. + fb->expanded = expand; + fb->buf_count = count; + fb->buf_size = buf_size - sizeof(vbuffer_t); - framebuffer_flush_buffers(fb, true); + // Initialize the buffer queues. + queue_init(&fb->free_queue, count, &queue_memory[queue_size * 0]); + queue_init(&fb->used_queue, count, &queue_memory[queue_size * 1]); + + // Flush and reset the queues. + framebuffer_flush(fb); return 0; } -// Returns the real size of bytes in the frame buffer. -static uint32_t framebuffer_total_buffer_size(framebuffer_t *fb) { - if (fb->n_buffers == 1) { - // Allow fb_alloc to use frame buffer space up until the image size. - image_t img; - framebuffer_init_image(fb, &img); - return sizeof(vbuffer_t) + FB_ALIGN_SIZE_ROUND_UP(image_size(&img)); - } else { - // fb_alloc may only use up to the size of all the virtual buffers. - uint32_t fbsize = framebuffer_get_buffer_size(fb); - return (sizeof(vbuffer_t) + fbsize) * fb->n_buffers; - } +bool framebuffer_writable(framebuffer_t *fb) { + return !queue_is_empty(fb->free_queue); } -void framebuffer_free_current_buffer(framebuffer_t *fb) { - vbuffer_t *buffer = framebuffer_get_buffer(fb, fb->head); +bool framebuffer_readable(framebuffer_t *fb) { + return !queue_is_empty(fb->used_queue); +} + +vbuffer_t *framebuffer_acquire(framebuffer_t *fb, uint32_t flags) { + queue_t *queue = (flags & FB_FLAG_USED) ? fb->used_queue : fb->free_queue; + vbuffer_t *buffer = queue_pop(queue, (flags & FB_FLAG_PEEK)); #ifdef __DCACHE_PRESENT - // Make sure all cached CPU writes are discarded before returning the buffer. - SCB_InvalidateDCache_by_Addr(buffer->data, framebuffer_get_buffer_size(fb)); - #endif - - // Invalidate frame. - fb->pixfmt = PIXFORMAT_INVALID; - - // Allow frame to be updated in single buffer mode... - if (fb->n_buffers == 1) { - buffer->waiting_for_data = true; - } -} - -void framebuffer_setup_buffers(framebuffer_t *fb) { - #ifdef __DCACHE_PRESENT - for (int32_t i = 0; i < fb->n_buffers; i++) { - if (i != fb->head) { - vbuffer_t *buffer = framebuffer_get_buffer(fb, i); - // Make sure all cached CPU writes are discarded before returning the buffer. - SCB_InvalidateDCache_by_Addr(buffer->data, framebuffer_get_buffer_size(fb)); - } + // Discard any cached CPU writes. + if (buffer && (flags & FB_FLAG_INVALIDATE)) { + SCB_InvalidateDCache_by_Addr(buffer->data, fb->buf_size); } #endif + + return buffer; } -vbuffer_t *framebuffer_get_head(framebuffer_t *fb, framebuffer_flags_t flags) { - int32_t new_head = (fb->head + 1) % fb->n_buffers; +vbuffer_t *framebuffer_release(framebuffer_t *fb, uint32_t flags) { + vbuffer_t *buffer = NULL; - // Single Buffer Mode. - if (fb->n_buffers == 1) { - if (framebuffer_get_buffer(fb, fb->head)->waiting_for_data) { + if ((flags & FB_FLAG_CHECK_LAST) && queue_size(fb->free_queue) == 1) { + if (fb->buf_count == 2) { + // Double buffer: Reset but do Not release the buffer. + vbuffer_t *buffer = queue_pop(fb->free_queue, true); + framebuffer_reset(buffer); + return NULL; + } else if (fb->buf_count == 3) { + // Triple buffer: Swap the old buffer with the latest. + vbuffer_t *buffer = queue_swap(fb->used_queue, fb->free_queue); + framebuffer_reset(buffer); return NULL; } - // Double Buffer Mode. - } else if (fb->n_buffers == 2) { - if (fb->head == fb->tail) { - return NULL; - } - // Triple Buffer Mode. - } else if (fb->n_buffers == 3) { - int32_t sampled_tail = fb->tail; - if (fb->head == sampled_tail) { - return NULL; + } + + if ((buffer = framebuffer_acquire(fb, flags))) { + if (flags & FB_FLAG_USED) { + // Invalidate the frame buffer. + fb->pixfmt = PIXFORMAT_INVALID; + // Move the buffer back to the free queue. + framebuffer_reset(buffer); + queue_push(fb->free_queue, buffer); } else { - new_head = sampled_tail; - } - // Video FIFO Mode. - } else { - if (fb->head == fb->tail) { - return NULL; + // Move the buffer back to the used queue. + queue_push(fb->used_queue, buffer); } } - if (!(flags & FB_PEEK)) { - fb->head = new_head; - } - - vbuffer_t *buffer = framebuffer_get_buffer(fb, new_head); - - #ifdef __DCACHE_PRESENT - if (flags & FB_INVALIDATE) { - // Make sure any cached CPU reads are dropped before returning the buffer. - SCB_InvalidateDCache_by_Addr(buffer->data, framebuffer_get_buffer_size(fb)); - } - #endif - return buffer; } -vbuffer_t *framebuffer_get_tail(framebuffer_t *fb, framebuffer_flags_t flags) { - // Sample head on the first line of a new frame. - if (fb->check_head) { - fb->check_head = false; - fb->sampled_head = fb->head; +void framebuffer_update_jpeg_buffer(image_t *src) { + static int overflow_count = 0; + const size_t max_size = (&_jpeg_memory_end - &_jpeg_memory_start) - sizeof(jpegbuffer_t); + + // Check if JPEG buffer is disabled, image is NULL or format is not set. + if (!jpegbuffer.enabled || !src->data || src->pixfmt == PIXFORMAT_INVALID) { + return; } - int32_t new_tail = (fb->tail + 1) % fb->n_buffers; + // Lock the JPEG buffer. + if (!mutex_try_lock_alternate(&jpegbuffer.lock, MUTEX_TID_OMV)) { + return; + } - // Single Buffer Mode. - if (fb->n_buffers == 1) { - if (!framebuffer_get_buffer(fb, new_tail)->waiting_for_data) { - // Setup to check head again. - fb->check_head = true; - return NULL; + if (src->is_compressed) { + if (max_size < src->size) { + jpegbuffer_init_from_image(NULL); + mp_printf(MP_PYTHON_PRINTER, "\x1b[40O\n"); + } else { + jpegbuffer_init_from_image(src); + memcpy(jpegbuffer.pixels, src->pixels, src->size); } - // Double Buffer Mode. - } else if (fb->n_buffers == 2) { - if (new_tail == fb->sampled_head) { - // Setup to check head again. - fb->check_head = true; - return NULL; - } - // Triple Buffer Mode. - } else if (fb->n_buffers == 3) { - // For triple buffering we are never writing where tail or head - // (which may instantly update to be equal to tail) is. - if (new_tail == fb->sampled_head) { - new_tail = (new_tail + 1) % fb->n_buffers; - } - // Video FIFO Mode. } else { - if (new_tail == fb->sampled_head) { - // Setup to check head again. - fb->check_head = true; - return NULL; + image_t dst = { + .w = src->w, + .h = src->h, + .pixfmt = PIXFORMAT_JPEG, + .size = max_size, + .pixels = jpegbuffer.pixels + }; + + bool compress = true; + bool overflow = false; + + #if OMV_RAW_PREVIEW_ENABLE + if (src->is_mutable) { + // Down-scale the frame (if necessary) and send the raw frame. + dst.size = src->bpp; + dst.pixfmt = src->pixfmt; + if (src->w <= OMV_RAW_PREVIEW_WIDTH && src->h <= OMV_RAW_PREVIEW_HEIGHT) { + if (image_size(&dst) <= max_size) { + memcpy(dst.pixels, src->pixels, image_size(src)); + compress = false; + } + } else { + float x_scale = OMV_RAW_PREVIEW_WIDTH / (float) src->w; + float y_scale = OMV_RAW_PREVIEW_HEIGHT / (float) src->h; + float scale = IM_MIN(x_scale, y_scale); + dst.w = fast_floorf(src->w * scale); + dst.h = fast_floorf(src->h * scale); + if (image_size(&dst) <= max_size) { + imlib_draw_image(&dst, src, 0, 0, scale, scale, NULL, -1, 255, NULL, NULL, + IMAGE_HINT_BILINEAR | IMAGE_HINT_BLACK_BACKGROUND, NULL, + NULL, NULL); + compress = false; + } + } } - } + #endif - vbuffer_t *buffer = framebuffer_get_buffer(fb, new_tail); - - // Reset on start versus the end so offset and jpeg_buffer_overflow are valid after FB_COMMIT. - if (buffer->reset_state) { - buffer->reset_state = false; - buffer->offset = 0; - buffer->jpeg_buffer_overflow = false; - } - - if (!(flags & FB_PEEK)) { - // Trigger reset on the frame buffer the next time it is used. - buffer->reset_state = true; - - // Mark the frame buffer ready in single buffer mode. - if (fb->n_buffers == 1) { - buffer->waiting_for_data = false; + if (compress) { + // For all other formats, send a compressed frame. + overflow = jpeg_compress(src, &dst, jpegbuffer.quality, false, JPEG_SUBSAMPLING_AUTO); } - fb->tail = new_tail; + if (overflow) { + // JPEG buffer overflowed, reduce JPEG quality for the next frame + // and skip the current frame. The IDE doesn't receive this frame. + if (jpegbuffer.quality > 1) { + // Keep this quality for the next n frames + overflow_count = 60; + jpegbuffer.quality = IM_MAX(1, (jpegbuffer.quality / 2)); + } - // Setup to check head again. - fb->check_head = true; + jpegbuffer_init_from_image(NULL); + } else { + if (overflow_count) { + overflow_count--; + } + + // Dynamically adjust our quality if the image is huge. + bool big_frame = image_size(src) > OMV_JPEG_QUALITY_THRESHOLD; + int quality_max = big_frame ? OMV_JPEG_QUALITY_LOW : OMV_JPEG_QUALITY_HIGH; + + // No buffer overflow, increase quality up to max quality based on frame size... + if ((!overflow_count) && (jpegbuffer.quality < quality_max)) { + jpegbuffer.quality++; + } + + jpegbuffer_init_from_image(&dst); + } } - return buffer; -} - -char *framebuffer_get_buffers_end(framebuffer_t *fb) { - return (char *) (fb->data + framebuffer_total_buffer_size(fb)); + + // Unlock the JPEG buffer. + mutex_unlock(&jpegbuffer.lock, MUTEX_TID_OMV); } diff --git a/lib/imlib/framebuffer.h b/lib/imlib/framebuffer.h index a2c2bf4a0..cb5bfcffe 100644 --- a/lib/imlib/framebuffer.h +++ b/lib/imlib/framebuffer.h @@ -29,46 +29,91 @@ #include "imlib.h" #include "mutex.h" #include "omv_common.h" +#include "common/queue.h" -// DMA Buffers need to be aligned by cache lines or 16 bytes. -#ifndef __DCACHE_PRESENT -#define FRAMEBUFFER_ALIGNMENT 16 -#else -#define FRAMEBUFFER_ALIGNMENT __SCB_DCACHE_LINE_SIZE +#ifndef FRAMEBUFFER_ALIGNMENT +#define FRAMEBUFFER_ALIGNMENT OMV_CACHE_LINE_SIZE #endif -typedef struct framebuffer { - int32_t x, y; - int32_t w, h; - int32_t u, v; - PIXFORMAT_STRUCT; - uint32_t raw_size; - uint32_t buff_size; - uint32_t n_buffers; - uint32_t frame_size; - int32_t head; - volatile int32_t tail; - bool check_head; - int32_t sampled_head; - bool dynamic; - OMV_ATTR_ALIGNED(uint8_t data[], FRAMEBUFFER_ALIGNMENT); -} framebuffer_t; +// TODO these should just be removed. +#define framebuffer_get_width(fb) (fb->w) +#define framebuffer_get_height(fb) (fb->h) +#define framebuffer_get_depth(fb) (fb->bpp) +#define framebuffer_get_buffer_size(fb) (fb->buf_size) +// If FB_FLAG_CHECK_LAST is set and this is the last buffer in +// the free queue the release logic depends on the buffer mode: +// +// - Single/FIFO: The buffer is released. +// - Double buffer: The buffer is not released. +// - Triple buffer: The last used buffer is released first. typedef enum { - FB_NO_FLAGS = (0 << 0), - FB_PEEK = (1 << 0), // If set, will not move the head/tail. - FB_INVALIDATE = (1 << 1), // If set, invalidate the buffer on return. + FB_FLAG_NONE = (1 << 0), // No special flags. + FB_FLAG_USED = (1 << 1), // Acquire from used / Release to free. + FB_FLAG_FREE = (1 << 2), // Acquire from free / Release to used. + FB_FLAG_PEEK = (1 << 3), // Acquire a buffer and keep it in the queue. + FB_FLAG_CHECK_LAST = (1 << 6), // Check if last buffer before releasing. + FB_FLAG_INVALIDATE = (1 << 7), // Invalidate buffer when acquired/released. } framebuffer_flags_t; +// The frame buffer memory is used for the following: +// +// - Buffer queues: If the number of video buffers exceeds 3. +// - Video buffers: Consisting of a header followed by the buffer. +// - Unused memory: Available for buffer expansion or fb_alloc. +// - fb_alloc memory: Only for statically allocated frame buffers. +// +// Dynamic Frame Buffer Memory Layout +// raw_base pool_start pool_end raw_end +// ▼ ▼ ▼ ▼ +// ┌────────────────────────────────────────────────────────────┐ +// │ Queues¹ | Frame Buffers Memory | Unused FB Memory² │ +// └────────────────────────────────────────────────────────────┘ +// +// For static frame buffers, fb_alloc uses a fixed end region and +// may use the free space for transient allocations if available. +// +// Static Frame Buffer Memory Layout +// fb_start pool_start pool_end fb_alloc_sp fb_alloc_end +// ▼ ▼ ▼ ▼ ▼ +// ┌────────────────────────────────────────────────────────────┐ +// │ Queues¹ | Buffers | Unused FB Memory² | Fixed FB Alloc │ +// └────────────────────────────────────────────────────────────┘ +// ¹ Queues use frame buffer memory only if count > 3, otherwise +// they're statically allocated to keep small buffers in SRAM. +// +// ² Unused frame buffer space can be used to expand buffers up +// to the maximum available size (raw size minus queue size). +typedef struct framebuffer { + int32_t x, y, w, h, u, v; + PIXFORMAT_STRUCT; + bool dynamic; // Dynamically allocated or not. + bool expanded; // True if buffers were expanded. + size_t raw_size; // Raw buffer size and address. + char *raw_base; + size_t buf_size; // Buffers size and count + size_t buf_count; + size_t frame_size; // Actual frame size + queue_t *used_queue; + queue_t *free_queue; + // Static memory for small queues. + char raw_static[queue_calc_size(3) * 2]; +} framebuffer_t; + +// Drivers can add more flags: +// VB_FLAG_EXAMPLE1 (VB_FLAG_LAST << 0) +// VB_FLAG_EXAMPLE2 (VB_FLAG_LAST << 1) +typedef enum { + VB_FLAG_NONE = (1 << 0), + VB_FLAG_USED = (1 << 1), + VB_FLAG_OVERFLOW = (1 << 2), + VB_FLAG_LAST = (1 << 3), +} vbuffer_flags_t; + typedef struct vbuffer { - // Used by snapshot code to figure out the jpeg size (bpp). - int32_t offset; - bool jpeg_buffer_overflow; - // Used internally by frame buffer code. - volatile bool waiting_for_data; - bool reset_state; - // Image data array. - OMV_ATTR_ALIGNED(uint8_t data[], FRAMEBUFFER_ALIGNMENT); + int32_t offset; // Write offset into the buffer (used by some drivers). + uint32_t flags; // Flags, see above. + OMV_ATTR_ALIGNED(uint8_t data[], FRAMEBUFFER_ALIGNMENT); // Data. } vbuffer_t; typedef struct jpegbuffer { @@ -76,33 +121,14 @@ typedef struct jpegbuffer { int32_t size; int32_t enabled; int32_t quality; + uint8_t *pixels; omv_mutex_t lock; - OMV_ATTR_ALIGNED(uint8_t pixels[], FRAMEBUFFER_ALIGNMENT); } jpegbuffer_t; -extern jpegbuffer_t *jpegbuffer; - void framebuffer_init0(); -framebuffer_t *framebuffer_get(size_t id); - -int32_t framebuffer_get_x(framebuffer_t *fb); -int32_t framebuffer_get_y(framebuffer_t *fb); -int32_t framebuffer_get_u(framebuffer_t *fb); -int32_t framebuffer_get_v(framebuffer_t *fb); - -int32_t framebuffer_get_width(framebuffer_t *fb); -int32_t framebuffer_get_height(framebuffer_t *fb); -int32_t framebuffer_get_depth(framebuffer_t *fb); - -// Return the number of bytes in the current buffer. -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); +void framebuffer_init(framebuffer_t *fb, void *buff, size_t size, bool dynamic); // Initializes an image from the frame buffer. void framebuffer_init_image(framebuffer_t *fb, image_t *img); @@ -110,38 +136,46 @@ void framebuffer_init_image(framebuffer_t *fb, image_t *img); // Sets the frame buffer from an image. void framebuffer_init_from_image(framebuffer_t *fb, image_t *img); -// Compress src image to the JPEG buffer if src is mutable, otherwise copy src to the JPEG buffer -// if the src is JPEG and fits in the JPEG buffer, or encode and stream src image to the IDE if not. -void framebuffer_update_jpeg_buffer(image_t *src); - -// Clear the framebuffer FIFO. If fifo_flush is true, reset and discard all framebuffers, -// otherwise, retain the last frame in the fifo. -void framebuffer_flush_buffers(framebuffer_t *fb, bool fifo_flush); - -// Set the number of virtual buffers in the frame buffer. -// If n_buffers = -1 the number of virtual buffers will be set to 3 each if possible. -// If n_buffers = 1 the whole framebuffer is used. In this case, `frame_size` is ignored. -int framebuffer_set_buffers(framebuffer_t *fb, int32_t n_buffers); - -// Call when done with the current vbuffer to mark it as free. -void framebuffer_free_current_buffer(framebuffer_t *fb); - -// Call to do any heavy setup before frame capture. -void framebuffer_setup_buffers(framebuffer_t *fb); - -// Sets the current frame buffer to the latest virtual frame buffer. -// Returns the buffer if it is ready or NULL if not... -// Pass FB_PEEK to get the next buffer but not take it. -vbuffer_t *framebuffer_get_head(framebuffer_t *fb, framebuffer_flags_t flags); - -// Return the next vbuffer to store image data to or NULL if none. -// Pass FB_PEEK to get the next buffer but not commit it. -vbuffer_t *framebuffer_get_tail(framebuffer_t *fb, framebuffer_flags_t flags); +// Return the static frame buffer instance. +framebuffer_t *framebuffer_get(size_t id); // Returns a pointer to the end of the framebuffer(s). -char *framebuffer_get_buffers_end(framebuffer_t *fb); +char *framebuffer_pool_end(framebuffer_t *fb); + +// Clear the framebuffer FIFO. +void framebuffer_flush(framebuffer_t *fb); + +// Change the number of buffers in the frame buffer. +// If expand is true, the buffer size will expand to use all of the +// available memory, otherwise it will equal the current frame size. +int framebuffer_resize(framebuffer_t *fb, size_t count, bool expand); + +// Return true if free queue is not empty. +bool framebuffer_writable(framebuffer_t *fb); + +// Return true if used queue is not empty. +bool framebuffer_readable(framebuffer_t *fb); + +// FB_FLAG_USED: acquire buffer from used queue. +// FB_FLAG_FREE: acquire buffer from free queue. +vbuffer_t *framebuffer_acquire(framebuffer_t *fb, uint32_t flags); + +// FB_FLAG_USED: release buffer from used queue. +// FB_FLAG_FREE: release buffer from free queue. +// Note: Returns NULL if the buffer was Not released. +vbuffer_t *framebuffer_release(framebuffer_t *fb, uint32_t flags); + +// Reset a vbuffer state. +static inline void framebuffer_reset(vbuffer_t *buffer) { + memset(buffer, 0, offsetof(vbuffer_t, data)); +} + +// Compress src image to the JPEG buffer if src is mutable, +// otherwise copy src to the JPEG buffer. +void framebuffer_update_jpeg_buffer(image_t *src); // Use this macro to get a pointer to the JPEG buffer. -#define JPEG_FB() (jpegbuffer) +extern jpegbuffer_t jpegbuffer; +#define JPEG_FB() (&jpegbuffer) #endif /* __FRAMEBUFFER_H__ */ diff --git a/modules/py_csi.c b/modules/py_csi.c index a2a735e35..333222e7f 100644 --- a/modules/py_csi.c +++ b/modules/py_csi.c @@ -211,7 +211,7 @@ 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 (csi->fb->bpp < 0) { return mp_const_none; } @@ -229,7 +229,8 @@ 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() { omv_csi_t *csi = omv_csi_get(-1); framebuffer_t *fb = csi->fb; - return mp_obj_new_bool(fb->tail != fb->head); + + return mp_obj_new_bool(framebuffer_readable(fb)); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_frame_available_obj, py_omv_csi_get_frame_available); @@ -378,10 +379,10 @@ static mp_obj_t py_omv_csi_get_windowing() { 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(csi->fb->x), + mp_obj_new_int(csi->fb->y), + mp_obj_new_int(csi->fb->u), + mp_obj_new_int(csi->fb->v)}); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_windowing_obj, py_omv_csi_get_windowing); @@ -704,7 +705,7 @@ 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); - if (c == csi->fb->n_buffers) { + if (c == csi->fb->buf_count) { return mp_const_none; } @@ -712,7 +713,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(csi, c); + int error = omv_csi_set_framebuffers(csi, c, false); if (error != 0) { omv_csi_raise_error(error); } @@ -723,7 +724,7 @@ static MP_DEFINE_CONST_FUN_OBJ_1(py_omv_csi_set_framebuffers_obj, py_omv_csi_set static mp_obj_t py_omv_csi_get_framebuffers() { omv_csi_t *csi = omv_csi_get(-1); - return mp_obj_new_int(csi->fb->n_buffers); + return mp_obj_new_int(csi->fb->buf_count); } static MP_DEFINE_CONST_FUN_OBJ_0(py_omv_csi_get_framebuffers_obj, py_omv_csi_get_framebuffers); diff --git a/modules/py_csi_ng.c b/modules/py_csi_ng.c index 1010ba7e2..5c740c1fe 100644 --- a/modules/py_csi_ng.c +++ b/modules/py_csi_ng.c @@ -62,7 +62,6 @@ typedef struct _py_csi_obj_t { 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; @@ -123,7 +122,6 @@ static mp_obj_t py_csi_deinit(mp_obj_t self_in) { // Reset FB pointer (realloc'd in make_new). if (self->csi->fb->dynamic) { - self->fb = NULL; self->csi->fb = NULL; } @@ -284,8 +282,9 @@ 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); + framebuffer_t *fb = self->csi->fb; + + return mp_obj_new_bool(framebuffer_readable(fb)); } static MP_DEFINE_CONST_FUN_OBJ_1(py_csi_readable_obj, py_csi_readable); @@ -355,10 +354,10 @@ static mp_obj_t py_csi_window(size_t n_args, const mp_obj_t *args) { } 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))}); + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(self->csi->fb->x), + mp_obj_new_int(self->csi->fb->y), + mp_obj_new_int(self->csi->fb->u), + mp_obj_new_int(self->csi->fb->v)}); } mp_obj_t *array; @@ -750,19 +749,26 @@ static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_auto_rotation_obj, 1, 2, py_cs 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; + bool expand = false; if (n_args == 1) { - return mp_obj_new_int(fb->n_buffers); + return mp_obj_new_int(fb->buf_count); } mp_int_t num = mp_obj_get_int(args[1]); + if (n_args == 3) { + expand = mp_obj_is_true(args[2]); + } + 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); + // Reconfigure the FB only if changing the number + // of buffers or reconfiguring the memory expansion. + if (fb->expanded != expand || num != fb->buf_count) { + int error = omv_csi_set_framebuffers(self->csi, num, expand); if (error != 0) { omv_csi_raise_error(error); } @@ -770,7 +776,7 @@ static mp_obj_t py_csi_framebuffers(size_t n_args, const mp_obj_t *args) { return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_framebuffers_obj, 1, 2, py_csi_framebuffers); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_csi_framebuffers_obj, 1, 3, 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); @@ -1281,9 +1287,8 @@ mp_obj_t py_csi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, 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 + csi->fb = (framebuffer_t *) m_malloc(sizeof(framebuffer_t)); + framebuffer_init(csi->fb, m_malloc(fb_size), fb_size, true); } #if MICROPY_PY_IMU @@ -1295,6 +1300,35 @@ mp_obj_t py_csi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, return MP_OBJ_FROM_PTR(self); } +static void py_csi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + py_csi_obj_t *self = MP_OBJ_TO_PTR(self_in); + omv_csi_t *csi = self->csi; + + mp_printf(print, "CSI {\n"); + mp_printf(print, " Name : %s\n", omv_csi_name(csi)); + mp_printf(print, " Chip ID : 0x%04X\n", csi->chip_id); + mp_printf(print, " Address : 0x%02X\n", csi->slv_addr); + mp_printf(print, " Powered On : %s\n", csi->power_on ? "true" : "false"); + mp_printf(print, " Auxiliary : %s\n", csi->auxiliary ? "true" : "false"); + mp_printf(print, " Raw Output : %s\n", csi->raw_output ? "true" : "false"); + if (csi->mipi_if) { + mp_printf(print, " MIPI Bitrate : %uMbps\n", csi->mipi_brate); + } + mp_printf(print, " Frame Rate : %d\n", csi->framerate); + mp_printf(print, " Clock Frequency : %luMHz\n", csi->clk_hz / 1000000); + mp_printf(print, " Framebuffer {\n"); + mp_printf(print, " Dynamic : %s\n", csi->fb->dynamic ? "true" : "false"); + mp_printf(print, " Expanded : %s\n", csi->fb->expanded ? "true" : "false"); + mp_printf(print, " Raw Buffer Size : %u\n", (unsigned) csi->fb->raw_size); + mp_printf(print, " Raw Buffer Addr : 0x%p\n", csi->fb->raw_base); + mp_printf(print, " Frame Size : %u\n", (unsigned) csi->fb->frame_size); + mp_printf(print, " Vbuffer Size : %u\n", (unsigned) csi->fb->buf_size); + mp_printf(print, " Vbuffer Count : %u\n", (unsigned) csi->fb->buf_count); + mp_printf(print, " Used Queue Size : %u\n", queue_size(csi->fb->used_queue)); + mp_printf(print, " Free Queue Size : %u\n", queue_size(csi->fb->free_queue)); + mp_printf(print, " }\n}\n"); +} + 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) }, @@ -1346,6 +1380,7 @@ MP_DEFINE_CONST_OBJ_TYPE( MP_QSTR_CSI, MP_TYPE_FLAG_NONE, make_new, py_csi_make_new, + print, py_csi_print, locals_dict, &py_csi_locals_dict ); diff --git a/modules/py_helper.c b/modules/py_helper.c index 9e6a69d7b..7ecede2bf 100644 --- a/modules/py_helper.c +++ b/modules/py_helper.c @@ -548,7 +548,9 @@ const uint8_t *py_helper_keyword_alpha_palette(size_t n_args, const mp_obj_t *ar bool py_helper_is_equal_to_framebuffer(image_t *img) { framebuffer_t *fb = framebuffer_get(0); - return framebuffer_get_buffer(fb, fb->head)->data == img->data; + vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_USED | FB_FLAG_PEEK); + + return (buffer != NULL) && (img->data == buffer->data); } void py_helper_update_framebuffer(image_t *img) { @@ -557,20 +559,27 @@ void py_helper_update_framebuffer(image_t *img) { } } +// TODO need to pass a CSI here. 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); + omv_csi_abort(csi, true, false); #else framebuffer_t *fb = framebuffer_get(0); - - framebuffer_set_buffers(fb, 1); #endif + + // Resize the frame buffer to use all memory for one buffer. + framebuffer_resize(fb, 1, true); + // This should never be NULL after resizing the frame buffer. + vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); + + PY_ASSERT_TRUE_MSG(buffer, "No free buffers!"); PY_ASSERT_TRUE_MSG((image_size(img) <= framebuffer_get_buffer_size(fb)), "The image doesn't fit in the frame buffer!"); + framebuffer_init_from_image(fb, img); - img->data = framebuffer_get_buffer(fb, fb->head)->data; + img->data = buffer->data; } diff --git a/ports/alif/alif.mk b/ports/alif/alif.mk index 2cae2c17e..8b9775885 100644 --- a/ports/alif/alif.mk +++ b/ports/alif/alif.mk @@ -92,10 +92,11 @@ CLANG_FLAGS = -fshort-enums \ -DALIF_CMSIS_H=$(CMSIS_MCU_H) \ $(filter-out -march% -fdisable-rtl%,$(CFLAGS)) -OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/$(COMMON_DIR) -OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/modules/ -OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/ports/$(PORT)/ -OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/ports/$(PORT)/modules/ +OMV_CFLAGS += -I$(TOP_DIR) +OMV_CFLAGS += -I$(TOP_DIR)/$(COMMON_DIR) +OMV_CFLAGS += -I$(TOP_DIR)/modules/ +OMV_CFLAGS += -I$(TOP_DIR)/ports/$(PORT)/ +OMV_CFLAGS += -I$(TOP_DIR)/ports/$(PORT)/modules/ OMV_CFLAGS += -I$(OMV_BOARD_CONFIG_DIR) MPY_CFLAGS += -I$(MP_BOARD_CONFIG_DIR) diff --git a/ports/alif/omv_csi.c b/ports/alif/omv_csi.c index 51732f366..ac6110f4f 100644 --- a/ports/alif/omv_csi.c +++ b/ports/alif/omv_csi.c @@ -106,13 +106,14 @@ void CAM_IRQHandler(void) { if (status & CAM_INTR_STOP) { mask |= CAM_INTR_STOP; cpi->CAM_CTRL = 0; + if (!(status & CPI_ERROR_FLAGS)) { - // Release the current framebuffer. - framebuffer_get_tail(csi->fb, FB_NO_FLAGS); + // Release the buffer from free queue -> used queue. + framebuffer_release(csi->fb, FB_FLAG_FREE | FB_FLAG_CHECK_LAST); } - // Get the current framebuffer (or new tail). - vbuffer_t *buffer = framebuffer_get_tail(csi->fb, FB_PEEK); + // Acquire a buffer from the free queue. + vbuffer_t *buffer = framebuffer_acquire(csi->fb, FB_FLAG_FREE | FB_FLAG_PEEK); if (buffer != NULL) { cpi->CAM_CTRL = 0; @@ -131,6 +132,12 @@ void CAM_IRQHandler(void) { cpi_irq_handler_clear_intr_status(cpi, mask); } +static bool alif_csi_is_active(omv_csi_t *csi) { + CPI_Type *cpi = csi->base; + + return (cpi->CAM_CTRL & CAM_CTRL_BUSY); +} + int alif_csi_config(omv_csi_t *csi, omv_csi_config_t config) { if (config == OMV_CSI_CONFIG_INIT) { CPI_Type *cpi = csi->base; @@ -224,38 +231,25 @@ 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; + vbuffer_t *buffer = NULL; static uint32_t frames = 0; static uint32_t r_stat, gb_stat, gr_stat, b_stat; - if (csi->pixformat == PIXFORMAT_INVALID) { - return OMV_CSI_ERROR_INVALID_PIXFORMAT; - } - - if (csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { - return OMV_CSI_ERROR_INVALID_FRAMESIZE; - } - - if (omv_csi_check_framebuffer_size(csi) != 0) { - return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; - } - - // Compress the framebuffer for the IDE preview. - if (flags & OMV_CSI_CAPTURE_FLAGS_UPDATE) { - image_t tmp; - framebuffer_init_image(fb, &tmp); - framebuffer_update_jpeg_buffer(&tmp); - } - - // Free the current FB head. - framebuffer_free_current_buffer(fb); - - // Reconfigure and restart the CSI transfer if it's not running. - if (!(cpi->CAM_CTRL & CAM_CTRL_BUSY)) { - framebuffer_setup_buffers(fb); + // Configure and re/start the capture if it's not alrady active + // and there are no pending buffers (from non-blocking capture). + if (!alif_csi_is_active(csi) && !framebuffer_readable(fb)) { 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. + // Acquire a buffer from the free queue. + buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); + + // Check if buffer is not ready or is not 64-bit aligned. + if ((!buffer) || (LocalToGlobal(buffer->data) & 0x7)) { + return OMV_CSI_ERROR_FRAMEBUFFER_ERROR; + } + + // Ensure that the transfer size is compatible with DMA restrictions. if ((!line_size_bytes) || (line_size_bytes % sizeof(uint64_t)) || csi->transpose || @@ -263,14 +257,6 @@ int alif_csi_snapshot(omv_csi_t *csi, image_t *dst_image, uint32_t flags) { return OMV_CSI_ERROR_INVALID_FRAMESIZE; } - // Get the destination buffer address. - vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); - - // Check if buffer is not ready or is not 64-bit aligned. - if ((!buffer) || (LocalToGlobal(buffer->data) & 0x7)) { - return OMV_CSI_ERROR_FRAMEBUFFER_ERROR; - } - if (!csi->raw_output && ((csi->pixformat == PIXFORMAT_RGB565 && csi->rgb_swap) || (csi->pixformat == PIXFORMAT_YUV422 && csi->yuv_swap))) { @@ -315,43 +301,30 @@ int alif_csi_snapshot(omv_csi_t *csi, image_t *dst_image, uint32_t flags) { } #endif - vbuffer_t *buffer = framebuffer_get_head(fb, FB_INVALIDATE); - // Wait for the DMA to finish the transfer. - for (mp_uint_t ticks = mp_hal_ticks_ms(); buffer == NULL;) { - mp_event_handle_nowait(); + // One shot DMA transfers must be invalidated. + framebuffer_flags_t fb_flags = FB_FLAG_USED | FB_FLAG_PEEK | FB_FLAG_INVALIDATE; - if ((mp_hal_ticks_ms() - ticks) > 3000) { - omv_csi_abort(csi, true, false); - - #if defined(OMV_CSI_FSYNC_PIN) - if (csi->frame_sync) { - omv_gpio_write(OMV_CSI_FSYNC_PIN, 0); - } - #endif - - return OMV_CSI_ERROR_CAPTURE_TIMEOUT; + // Wait for a frame to be ready. + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_event_handle_nowait()) { + if ((buffer = framebuffer_acquire(fb, fb_flags))) { + break; } - buffer = framebuffer_get_head(fb, FB_INVALIDATE); + if (flags & OMV_CSI_CAPTURE_FLAGS_NBLOCK) { + return OMV_CSI_ERROR_WOULD_BLOCK; + } + + if ((mp_hal_ticks_ms() - start) > OMV_CSI_TIMEOUT_MS) { + omv_csi_abort(csi, true, false); + return OMV_CSI_ERROR_CAPTURE_TIMEOUT; + } } - // We're done receiving data. - #if defined(OMV_CSI_FSYNC_PIN) - if (csi->frame_sync) { - omv_gpio_write(OMV_CSI_FSYNC_PIN, 0); - } - #endif + // Set the framebuffer width/height. + fb->w = csi->transpose ? fb->v : fb->u; + fb->h = csi->transpose ? fb->u : fb->v; - // Restore the frame buffer width and height. - if (!csi->transpose) { - fb->w = fb->u; - fb->h = fb->v; - } else { - fb->w = fb->v; - fb->h = fb->u; - } - - // Reset the frame buffer's pixel format. + // Set the framebuffer pixel format. switch (csi->pixformat) { case PIXFORMAT_GRAYSCALE: fb->pixfmt = PIXFORMAT_GRAYSCALE; diff --git a/ports/mimxrt/omv_csi.c b/ports/mimxrt/omv_csi.c index 12eda5606..7f5c912dd 100644 --- a/ports/mimxrt/omv_csi.c +++ b/ports/mimxrt/omv_csi.c @@ -130,13 +130,13 @@ void omv_csi_sof_callback(omv_csi_t *csi) { csi->drop_frame = false; // Get current framebuffer. - vbuffer_t *buffer = framebuffer_get_tail(csi->fb, FB_PEEK); + vbuffer_t *buffer = framebuffer_acquire(csi->fb, FB_FLAG_FREE | FB_FLAG_PEEK); if (buffer == NULL) { 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; + framebuffer_reset(buffer); } } @@ -190,30 +190,23 @@ void omv_csi_line_callback(omv_csi_t *csi, uint32_t addr) { omv_csi_throttle_framerate(csi); // Get current framebuffer. - vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); + vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); if (csi->pixformat == PIXFORMAT_JPEG) { + bool jpeg_end = false; + if (csi->drop_frame) { return; } - bool jpeg_end = false; + if (csi->jpg_format == 4) { - // JPEG MODE 4: - // - // The width and height are fixed in each frame. The first two bytes are valid data - // length in every line, followed by valid image data. Dummy data (0xFF) may be used as - // padding at each line end if the current valid image data is less than the line width. - // - // In this mode `offset` holds the size of all jpeg data transferred. - // - // Note: We are using this mode for the OV5640 because it allows us to use the line - // buffers to fifo the JPEG image data input so we can handle SDRAM refresh hiccups - // that will cause data loss if we make the DMA hardware write directly to the FB. - // + // 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. uint16_t size = __REV16(*((uint16_t *) addr)); // Prevent a buffer overflow when writing the jpeg data. if (buffer->offset + size > framebuffer_get_buffer_size(fb)) { - buffer->jpeg_buffer_overflow = true; + buffer->flags |= VB_FLAG_OVERFLOW; jpeg_end = true; } else { unaligned_memcpy(buffer->data + buffer->offset, ((uint16_t *) addr) + 1, size); @@ -230,12 +223,13 @@ void omv_csi_line_callback(omv_csi_t *csi, uint32_t addr) { } 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 - // the current framesize. Since we don't have an end-of-frame interrupt on the mimxrt we - // detect the end of the frame when there's no more jpeg data. + + // In JPEG mode, the camera will output a number of lines that doesn't + // match the current frame size. Since we don't have an end-of-frame + // interrupt on the MIMXRT, the frame ends when there's no more data. if (jpeg_end) { // Release the current framebuffer. - framebuffer_get_tail(fb, FB_NO_FLAGS); + framebuffer_release(fb, FB_FLAG_FREE | FB_FLAG_CHECK_LAST); CSI_REG_CR3(CSI) &= ~CSI_CR3_DMA_REQ_EN_RFF_MASK; if (csi->frame_cb.fun) { csi->frame_cb.fun(csi->frame_cb.arg); @@ -282,7 +276,7 @@ void omv_csi_line_callback(omv_csi_t *csi, uint32_t addr) { if (++buffer->offset == resolution[csi->framesize][1]) { // Release the current framebuffer. - framebuffer_get_tail(fb, FB_NO_FLAGS); + framebuffer_release(fb, FB_FLAG_FREE | FB_FLAG_CHECK_LAST); CSI_REG_CR3(CSI) &= ~CSI_CR3_DMA_REQ_EN_RFF_MASK; if (csi->frame_cb.fun) { csi->frame_cb.fun(csi->frame_cb.arg); @@ -332,41 +326,15 @@ static void edma_config(omv_csi_t *csi, uint32_t bytes_per_pixel) { #endif int imx_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { + vbuffer_t *buffer = NULL; framebuffer_t *fb = csi->fb; - // Used to restore the frame buffer width and height. - uint32_t w = fb->u; - uint32_t h = fb->v; - - if (csi->pixformat == PIXFORMAT_INVALID) { - return OMV_CSI_ERROR_INVALID_PIXFORMAT; - } - - if (csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { - return OMV_CSI_ERROR_INVALID_FRAMESIZE; - } - - if (omv_csi_check_framebuffer_size(csi) != 0) { - return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; - } - - // Compress the framebuffer for the IDE preview. - if (flags & OMV_CSI_CAPTURE_FLAGS_UPDATE) { - image_t tmp; - framebuffer_init_image(fb, &tmp); - framebuffer_update_jpeg_buffer(&tmp); - } - - // Free the current FB head. - framebuffer_free_current_buffer(fb); - - // If the DMA is not active, reconfigure and restart the CSI transfer. - if (!(CSI->CR18 & CSI_CR18_CSI_ENABLE_MASK)) { - framebuffer_setup_buffers(fb); - + // Configure and re/start the capture if it's not alrady active + // and there are no pending buffers (from non-blocking capture). + if (!(CSI->CR18 & CSI_CR18_CSI_ENABLE_MASK) && !framebuffer_readable(fb)) { 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; + uint32_t length = dma_line_bytes * fb->v; // Error out if the transfer size is not compatible with DMA transfer restrictions. if ((!dma_line_bytes) @@ -411,61 +379,41 @@ int imx_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { CSI_REG_CR18(CSI) |= CSI_CR18_CSI_ENABLE_MASK; } - // Let the camera know we want to trigger it now. - #if defined(OMV_CSI_FSYNC_PIN) - if (csi->frame_sync) { - omv_gpio_write(OMV_CSI_FSYNC_PIN, 1); - } - #endif - - framebuffer_flags_t fb_flags = FB_NO_FLAGS; + framebuffer_flags_t fb_flags = FB_FLAG_USED | FB_FLAG_PEEK; #if defined(OMV_CSI_DMA) - // dest_inc will be less than MIN_EDMA_DST_INC if the EDMA is not initialized or unusable. + // If the EDMA is used, the transfers must be invalidated. if (csi->dest_inc >= MIN_EDMA_DST_INC) { - fb_flags = FB_INVALIDATE; + fb_flags |= FB_FLAG_INVALIDATE; } #endif - vbuffer_t *buffer = framebuffer_get_head(fb, fb_flags); - // Wait for the DMA to finish the transfer. - for (mp_uint_t ticks = mp_hal_ticks_ms(); buffer == NULL;) { - MICROPY_EVENT_POLL_HOOK - if ((mp_hal_ticks_ms() - ticks) > OMV_CSI_TIMEOUT_MS) { + // Wait for a frame to be ready. + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_event_handle_nowait()) { + if ((buffer = framebuffer_acquire(fb, fb_flags))) { + break; + } + + if (flags & OMV_CSI_CAPTURE_FLAGS_NBLOCK) { + return OMV_CSI_ERROR_WOULD_BLOCK; + } + + if ((mp_hal_ticks_ms() - start) > OMV_CSI_TIMEOUT_MS) { omv_csi_abort(csi, true, false); - - #if defined(OMV_CSI_FSYNC_PIN) - if (csi->frame_sync) { - omv_gpio_write(OMV_CSI_FSYNC_PIN, 0); - } - #endif - return OMV_CSI_ERROR_CAPTURE_TIMEOUT; } - buffer = framebuffer_get_head(fb, fb_flags); } - // We're done receiving data. - #if defined(OMV_CSI_FSYNC_PIN) - if (csi->frame_sync) { - omv_gpio_write(OMV_CSI_FSYNC_PIN, 0); - } - #endif - // The JPEG in the frame buffer is actually invalid. - if (buffer->jpeg_buffer_overflow) { + if (buffer->flags & VB_FLAG_OVERFLOW) { return OMV_CSI_ERROR_JPEG_OVERFLOW; } - if (!csi->transpose) { - fb->w = w; - fb->h = h; - } else { - fb->w = h; - fb->h = w; - } + // Set the framebuffer width/height. + fb->w = csi->transpose ? fb->v : fb->u; + fb->h = csi->transpose ? fb->u : fb->v; - // Fix the BPP. + // Set the framebuffer pixel format. switch (csi->pixformat) { case PIXFORMAT_GRAYSCALE: fb->pixfmt = PIXFORMAT_GRAYSCALE; diff --git a/ports/mimxrt/omv_portconfig.mk b/ports/mimxrt/omv_portconfig.mk index a4315187d..e1e0a7a6e 100644 --- a/ports/mimxrt/omv_portconfig.mk +++ b/ports/mimxrt/omv_portconfig.mk @@ -31,7 +31,7 @@ ROMFS_IMAGE := $(FW_DIR)/romfs.stamp ROMFS_CONFIG := $(OMV_BOARD_CONFIG_DIR)/romfs.json # Compiler Flags -CFLAGS += -std=gnu99 \ +CFLAGS += -std=gnu11 \ -Wall \ -Werror \ -Warray-bounds \ @@ -83,6 +83,7 @@ LDFLAGS = -mthumb \ -Wl,-T$(BUILD)/$(LDSCRIPT).lds \ -Wl,-Map=$(BUILD)/$(FIRMWARE).map +OMV_CFLAGS += -I$(TOP_DIR) OMV_CFLAGS += -I$(TOP_DIR)/$(COMMON_DIR) OMV_CFLAGS += -I$(TOP_DIR)/modules OMV_CFLAGS += -I$(TOP_DIR)/ports/$(PORT) diff --git a/ports/nrf/omv_csi.c b/ports/nrf/omv_csi.c index 75f626a7a..6de592b69 100644 --- a/ports/nrf/omv_csi.c +++ b/ports/nrf/omv_csi.c @@ -120,31 +120,16 @@ int omv_csi_set_windowing(omv_csi_t *csi, int x, int y, int w, int h) { // This is the default snapshot function, which can be replaced in omv_csi_init functions. static int nrf_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { + vbuffer_t *buffer = NULL; framebuffer_t *fb = csi->fb; - // Compress the framebuffer for the IDE preview, only if it's not the first frame, - // the framebuffer is enabled and the image sensor does not support JPEG encoding. - // Note: This doesn't run unless the IDE is connected and the framebuffer is enabled. - if (flags & OMV_CSI_CAPTURE_FLAGS_UPDATE) { - image_t tmp; - framebuffer_init_image(fb, &tmp); - framebuffer_update_jpeg_buffer(&tmp); - } - // This driver supports a single buffer. - if (fb->n_buffers != 1) { - framebuffer_set_buffers(fb, 1); + if (fb->buf_count != 1) { + omv_csi_set_framebuffers(csi, 1, false); } - if (omv_csi_check_framebuffer_size(csi) != 0) { - return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; - } - - framebuffer_free_current_buffer(fb); - framebuffer_setup_buffers(fb); - vbuffer_t *buffer = framebuffer_get_tail(fb, FB_NO_FLAGS); - - if (!buffer) { + // Acquire a buffer from the free queue. + if ((buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK))) { return OMV_CSI_ERROR_FRAMEBUFFER_ERROR; } @@ -205,6 +190,9 @@ static int nrf_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { unaligned_memcpy_rev16(buffer->data, buffer->data, _width * _height); } + // Release the current framebuffer. + framebuffer_release(fb, FB_FLAG_FREE); + // Set the user image. framebuffer_init_image(fb, image); return 0; diff --git a/ports/nrf/omv_portconfig.mk b/ports/nrf/omv_portconfig.mk index f5c716271..d8630aa96 100644 --- a/ports/nrf/omv_portconfig.mk +++ b/ports/nrf/omv_portconfig.mk @@ -72,6 +72,7 @@ LDFLAGS = -mcpu=$(CPU) \ -Wl,-Map=$(BUILD)/$(FIRMWARE).map \ -Wl,-T$(BUILD)/$(LDSCRIPT).lds +OMV_CFLAGS += -I$(TOP_DIR) OMV_CFLAGS += -I$(TOP_DIR)/$(COMMON_DIR) OMV_CFLAGS += -I$(TOP_DIR)/modules OMV_CFLAGS += -I$(TOP_DIR)/ports/$(PORT) diff --git a/ports/rp2/omv_csi.c b/ports/rp2/omv_csi.c index 31166f068..c61d582cf 100644 --- a/ports/rp2/omv_csi.c +++ b/ports/rp2/omv_csi.c @@ -51,8 +51,10 @@ static void dma_irq_handler() { // 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); + // Release the current buffer and acquire a new one. + framebuffer_release(fb, FB_FLAG_FREE | FB_FLAG_CHECK_LAST); + vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); + if (buffer != NULL) { // Set next buffer and retrigger the DMA channel. dma_channel_set_write_addr(OMV_CSI_DMA_CHANNEL, buffer->data, true); @@ -66,6 +68,10 @@ static void dma_irq_handler() { } } +static bool rp2_csi_is_active(omv_csi_t *csi) { + return dma_channel_is_busy(OMV_CSI_DMA_CHANNEL); +} + static int rp2_csi_config(omv_csi_t *csi, omv_csi_config_t config) { if (config == OMV_CSI_CONFIG_INIT) { // PIXCLK @@ -153,37 +159,14 @@ int omv_csi_set_windowing(omv_csi_t *csi, int x, int y, int w, int h) { } static int rp2_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { + vbuffer_t *buffer = NULL; framebuffer_t *fb = csi->fb; - // Compress the framebuffer for the IDE preview. - if (flags & OMV_CSI_CAPTURE_FLAGS_UPDATE) { - image_t tmp; - framebuffer_init_image(fb, &tmp); - framebuffer_update_jpeg_buffer(&tmp); - } - - if (omv_csi_check_framebuffer_size(csi) != 0) { - return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; - } - - // Free the current FB head. - framebuffer_free_current_buffer(fb); - - // Set framebuffer pixel format. - if (csi->pixformat == PIXFORMAT_INVALID) { - return OMV_CSI_ERROR_INVALID_PIXFORMAT; - } - fb->pixfmt = csi->pixformat; - - vbuffer_t *buffer = framebuffer_get_head(fb, FB_NO_FLAGS); - // If there's no ready buffer in the fifo, and the DMA is Not currently // transferring a new buffer, reconfigure and restart the DMA transfer. - if (buffer == NULL && !dma_channel_is_busy(OMV_CSI_DMA_CHANNEL)) { - framebuffer_setup_buffers(fb); - - buffer = framebuffer_get_tail(fb, FB_PEEK); - if (buffer == NULL) { + if (!rp2_csi_is_active(csi) && !framebuffer_readable(fb)) { + // Acquire a buffer from the free queue. + if (!(buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK))) { return OMV_CSI_ERROR_FRAMEBUFFER_ERROR; } @@ -216,17 +199,30 @@ static int rp2_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { pio_sm_put_blocking(OMV_CSI_PIO, OMV_CSI_SM, (fb->u * fb->bpp) - 1); } - // Wait for the DMA to finish the transfer. - for (mp_uint_t ticks = mp_hal_ticks_ms(); buffer == NULL;) { - buffer = framebuffer_get_head(fb, FB_NO_FLAGS); - if ((mp_hal_ticks_ms() - ticks) > 3000) { + // One shot DMA transfers must be invalidated. + framebuffer_flags_t fb_flags = FB_FLAG_USED | FB_FLAG_PEEK | FB_FLAG_INVALIDATE; + + // Wait for a frame to be ready. + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_event_handle_nowait()) { + if ((buffer = framebuffer_acquire(fb, fb_flags))) { + break; + } + + if (flags & OMV_CSI_CAPTURE_FLAGS_NBLOCK) { + return OMV_CSI_ERROR_WOULD_BLOCK; + } + + if ((mp_hal_ticks_ms() - start) > OMV_CSI_TIMEOUT_MS) { omv_csi_abort(csi, true, false); return OMV_CSI_ERROR_CAPTURE_TIMEOUT; } } + // Set the framebuffer width/height. fb->w = fb->u; fb->h = fb->v; + // Set the framebuffer pixel format. + fb->pixfmt = csi->pixformat; // Set the user image. framebuffer_init_image(fb, image); diff --git a/ports/rp2/omv_portconfig.cmake b/ports/rp2/omv_portconfig.cmake index ec79981e1..979f89b37 100644 --- a/ports/rp2/omv_portconfig.cmake +++ b/ports/rp2/omv_portconfig.cmake @@ -122,6 +122,7 @@ target_sources(${MICROPY_TARGET} PRIVATE ${TOP_DIR}/common/umm_malloc.c ${TOP_DIR}/common/dma_alloc.c ${TOP_DIR}/common/unaligned_memcpy.c + ${TOP_DIR}/common/queue.c ${TOP_DIR}/drivers/sensors/ov2640.c ${TOP_DIR}/drivers/sensors/ov5640.c diff --git a/ports/rp2/omv_portconfig.mk b/ports/rp2/omv_portconfig.mk index 7e68714b5..51e71d9a3 100644 --- a/ports/rp2/omv_portconfig.mk +++ b/ports/rp2/omv_portconfig.mk @@ -28,7 +28,7 @@ export CXX= # Note this overrides USER_C_MODULES. MPY_MKARGS += BOARD=$(TARGET) BUILD=$(BUILD)/rp2 USER_C_MODULES="" \ - OMV_CMAKE=$(TOP_DIR)/$(OMV_DIR)/ports/$(PORT)/omv_portconfig.cmake + OMV_CMAKE=$(TOP_DIR)/ports/$(PORT)/omv_portconfig.cmake ################################################### all: $(OPENMV) diff --git a/ports/stm32/omv_csi.c b/ports/stm32/omv_csi.c index ba1b1f543..41a86accd 100644 --- a/ports/stm32/omv_csi.c +++ b/ports/stm32/omv_csi.c @@ -378,28 +378,27 @@ static void stm_csi_frame_event(omv_csi_t *csi, uint32_t pipe) { HAL_NVIC_EnableIRQ(csi->dma_irqn); #endif - // Reset DCMI_DMAConvCpltUser frame drop state. 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) { - buffer->reset_state = true; - } + vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); + framebuffer_reset(buffer); return; } - framebuffer_get_tail(fb, FB_NO_FLAGS); + // Release the buffer from free queue -> used queue. + framebuffer_release(fb, FB_FLAG_FREE | FB_FLAG_CHECK_LAST); if (csi->frame_cb.fun) { csi->frame_cb.fun(csi->frame_cb.arg); } #if defined(STM32N6) - // Get the destination buffer address. - vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); - if (buffer == NULL) { // FIFO is FULL + // Acquire a buffer from the free queue. + vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); + + if (buffer == NULL) { omv_csi_abort(csi, false, false); } else if (csi->mipi_if) { HAL_DCMIPP_PIPE_SetMemoryAddress(&csi->dcmipp, pipe, @@ -440,7 +439,8 @@ void DCMI_DMAConvCpltUser(DCMI_HandleTypeDef *hdcmi, uint32_t addr) { return; } - vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); + // Acquire a buffer from the free queue. + vbuffer_t *buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK); if (buffer == NULL) { omv_csi_abort(csi, false, true); return; @@ -458,7 +458,7 @@ void DCMI_DMAConvCpltUser(DCMI_HandleTypeDef *hdcmi, uint32_t addr) { // data and optional padding (0xFF). `offset` holds the total size. uint16_t size = __REV16(*((uint16_t *) addr)); if (buffer->offset + size > framebuffer_get_buffer_size(fb)) { - buffer->jpeg_buffer_overflow = true; + buffer->flags |= VB_FLAG_OVERFLOW; return; } unaligned_memcpy(buffer->data + buffer->offset, ((uint16_t *) addr) + 1, size); @@ -510,46 +510,14 @@ void DCMI_DMAConvCpltUser(DCMI_HandleTypeDef *hdcmi, uint32_t addr) { #endif // #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { + vbuffer_t *buffer = NULL; framebuffer_t *fb = csi->fb; - if (csi->pixformat == PIXFORMAT_INVALID) { - return OMV_CSI_ERROR_INVALID_PIXFORMAT; - } - - if (csi->framesize == OMV_CSI_FRAMESIZE_INVALID) { - return OMV_CSI_ERROR_INVALID_FRAMESIZE; - } - - // 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. - if (flags & OMV_CSI_CAPTURE_FLAGS_UPDATE) { - image_t tmp; - framebuffer_init_image(fb, &tmp); - framebuffer_update_jpeg_buffer(&tmp); - } - - // 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(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. - uint32_t w = fb->u; - uint32_t h = fb->v; - - // If DCMI_DMAConvCpltUser() happens before framebuffer_free_current_buffer(); - // below then the transfer is stopped and it will be re-enabled again right - // afterwards in the single vbuffer case. - framebuffer_free_current_buffer(fb); - - // Configure and start the capture. - if (!stm_csi_is_active(csi)) { - framebuffer_setup_buffers(fb); - - // Get the destination buffer address. - vbuffer_t *buffer = framebuffer_get_tail(fb, FB_PEEK); - if (buffer == NULL) { + // Configure and re/start the capture if it's not alrady active + // and there are no pending buffers (from non-blocking capture). + if (!stm_csi_is_active(csi) && !framebuffer_readable(fb)) { + // Acquire a buffer from the free queue. + if (!(buffer = framebuffer_acquire(fb, FB_FLAG_FREE | FB_FLAG_PEEK))) { return OMV_CSI_ERROR_FRAMEBUFFER_ERROR; } @@ -597,7 +565,7 @@ static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { line_width -= 4; } - csi->dma_size = line_width * h / 4; + csi->dma_size = line_width * fb->v / 4; // Error out if the transfer size is not compatible with DMA transfer restrictions. if ((!line_width) || (line_width % 4) || @@ -611,7 +579,7 @@ static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { HAL_DCMI_DisableCrop(&csi->dcmi); if (csi->pixformat != PIXFORMAT_JPEG) { // Vertically crop the image. Horizontal cropping is done in software. - HAL_DCMI_ConfigCrop(&csi->dcmi, x_crop, fb->y, line_width - 1, h - 1); + HAL_DCMI_ConfigCrop(&csi->dcmi, x_crop, fb->y, line_width - 1, fb->v - 1); HAL_DCMI_EnableCrop(&csi->dcmi); } @@ -648,7 +616,7 @@ static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { } else { #if defined(STM32F4) || defined(STM32F7) || defined(STM32H7) // Start a multibuffer (line by line) transfer. - HAL_DCMI_Start_DMA_MB(&csi->dcmi, DCMI_MODE_CONTINUOUS, (uint32_t) &_line_buf, csi->dma_size, h); + HAL_DCMI_Start_DMA_MB(&csi->dcmi, DCMI_MODE_CONTINUOUS, (uint32_t) &_line_buf, csi->dma_size, fb->v); #else csi->one_shot = true; HAL_DCMI_Start_DMA(&csi->dcmi, DCMI_MODE_SNAPSHOT, (uint32_t) buffer->data, csi->dma_size); @@ -657,36 +625,27 @@ static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { } } - // Trigger the camera if FSYNC is enabled. - #if defined(OMV_CSI_FSYNC_PIN) - if (csi->frame_sync) { - omv_gpio_write(OMV_CSI_FSYNC_PIN, 1); - } - #endif - // In JPEG mode, enable the end of frame interrupt. if (!csi->mipi_if && csi->pixformat != PIXFORMAT_JPEG) { __HAL_DCMI_ENABLE_IT(&csi->dcmi, DCMI_IT_FRAME); } - framebuffer_flags_t fb_flags = FB_NO_FLAGS; - // One shot DMA transfers must be invalidated. - if (csi->one_shot) { - fb_flags |= FB_INVALIDATE; - } + framebuffer_flags_t fb_flags = FB_FLAG_USED | FB_FLAG_PEEK | + ((csi->one_shot) ? FB_FLAG_INVALIDATE : 0); // Wait for a frame to be ready. - vbuffer_t *buffer = NULL; - for (uint32_t tick_start = HAL_GetTick(); !(buffer = framebuffer_get_head(fb, fb_flags)); ) { - __WFI(); - if ((HAL_GetTick() - tick_start) > OMV_CSI_TIMEOUT_MS) { + for (mp_uint_t start = mp_hal_ticks_ms(); ; mp_event_handle_nowait()) { + if ((buffer = framebuffer_acquire(fb, fb_flags))) { + break; + } + + if (flags & OMV_CSI_CAPTURE_FLAGS_NBLOCK) { + return OMV_CSI_ERROR_WOULD_BLOCK; + } + + if ((mp_hal_ticks_ms() - start) > OMV_CSI_TIMEOUT_MS) { omv_csi_abort(csi, true, false); - #if defined(OMV_CSI_FSYNC_PIN) - if (csi->frame_sync) { - omv_gpio_write(OMV_CSI_FSYNC_PIN, 0); - } - #endif return OMV_CSI_ERROR_CAPTURE_TIMEOUT; } } @@ -696,28 +655,16 @@ static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { omv_csi_abort(csi, true, false); } - // We're done receiving data. - #if defined(OMV_CSI_FSYNC_PIN) - if (csi->frame_sync) { - omv_gpio_write(OMV_CSI_FSYNC_PIN, 0); - } - #endif - - // The JPEG in the frame buffer is actually invalid. - if (buffer->jpeg_buffer_overflow) { + // The JPEG in the framebuffer is actually invalid. + if (buffer->flags & VB_FLAG_OVERFLOW) { return OMV_CSI_ERROR_JPEG_OVERFLOW; } - // Prepare the frame buffer w/h/bpp values given the image type. - if (!csi->transpose) { - fb->w = w; - fb->h = h; - } else { - fb->w = h; - fb->h = w; - } + // Set the framebuffer width/height. + fb->w = csi->transpose ? fb->v : fb->u; + fb->h = csi->transpose ? fb->u : fb->v; - // Fix the BPP. + // Set the framebuffer pixel format. switch (csi->pixformat) { case PIXFORMAT_GRAYSCALE: fb->pixfmt = PIXFORMAT_GRAYSCALE; @@ -759,17 +706,18 @@ static int stm_csi_snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { break; } - // Set the user image. - framebuffer_init_image(fb, image); #if USE_DCMIPP if (csi->raw_output) { - float luminance = stm_isp_update_awb(&csi->dcmipp, DCMIPP_PIPE, w * h); + float luminance = stm_isp_update_awb(&csi->dcmipp, DCMIPP_PIPE, fb->u * fb->v); if (csi->ioctl) { omv_csi_ioctl(csi, OMV_CSI_IOCTL_UPDATE_AGC_AEC, fast_floorf(luminance)); } } #endif + + // Set the user image. + framebuffer_init_image(fb, image); return 0; }