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

imlib: Rework frame buffer management.
This commit is contained in:
Ibrahim Abdelkader 2025-07-24 15:43:42 +03:00 committed by GitHub
commit 040624374a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 1037 additions and 957 deletions

View File

@ -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))

View File

@ -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;

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -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
View 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
View 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__

View File

@ -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

View File

@ -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__

View File

@ -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,

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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__ */

View File

@ -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);

View File

@ -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
);

View File

@ -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;
}

View File

@ -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)

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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;

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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)

View File

@ -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;
}