mirror of
https://github.com/openmv/openmv.git
synced 2025-11-04 14:49:50 +08:00
Merge pull request #2766 from openmv/refactor_framebuffer
Some checks failed
🔥 Firmware Build / build-firmware (ARDUINO_GIGA) (push) Has been cancelled
🔥 Firmware Build / build-firmware (ARDUINO_NANO_33_BLE_SENSE) (push) Has been cancelled
🔥 Firmware Build / build-firmware (ARDUINO_NANO_RP2040_CONNECT) (push) Has been cancelled
🔥 Firmware Build / build-firmware (ARDUINO_NICLA_VISION) (push) Has been cancelled
🔥 Firmware Build / build-firmware (ARDUINO_PORTENTA_H7) (push) Has been cancelled
🔥 Firmware Build / build-firmware (DOCKER) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV2) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV3) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV4) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV4P) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMVPT) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV_AE3) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV_N6) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV_RT1060) (push) Has been cancelled
🔥 Firmware Build / code-size-report (push) Has been cancelled
🔥 Firmware Build / stable-release (push) Has been cancelled
🔥 Firmware Build / development-release (push) Has been cancelled
Some checks failed
🔥 Firmware Build / build-firmware (ARDUINO_GIGA) (push) Has been cancelled
🔥 Firmware Build / build-firmware (ARDUINO_NANO_33_BLE_SENSE) (push) Has been cancelled
🔥 Firmware Build / build-firmware (ARDUINO_NANO_RP2040_CONNECT) (push) Has been cancelled
🔥 Firmware Build / build-firmware (ARDUINO_NICLA_VISION) (push) Has been cancelled
🔥 Firmware Build / build-firmware (ARDUINO_PORTENTA_H7) (push) Has been cancelled
🔥 Firmware Build / build-firmware (DOCKER) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV2) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV3) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV4) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV4P) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMVPT) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV_AE3) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV_N6) (push) Has been cancelled
🔥 Firmware Build / build-firmware (OPENMV_RT1060) (push) Has been cancelled
🔥 Firmware Build / code-size-report (push) Has been cancelled
🔥 Firmware Build / stable-release (push) Has been cancelled
🔥 Firmware Build / development-release (push) Has been cancelled
imlib: Rework frame buffer management.
This commit is contained in:
commit
040624374a
@ -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))
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
190
common/queue.c
Normal file
190
common/queue.c
Normal file
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#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;
|
||||
}
|
||||
61
common/queue.h
Normal file
61
common/queue.h
Normal file
@ -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 <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#if __STDC_VERSION__ < 201112L
|
||||
typedef size_t queue_index_t;
|
||||
#warning "Atomics not supported"
|
||||
#else
|
||||
#include <stdatomic.h>
|
||||
#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__
|
||||
157
common/vospi.c
157
common/vospi.c
@ -28,6 +28,8 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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
|
||||
|
||||
@ -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__
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -24,54 +24,53 @@
|
||||
* Framebuffer functions.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#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; i<fb->buf_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);
|
||||
}
|
||||
|
||||
@ -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__ */
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user