From 8e543c2a9600e5cf211ae90bef76f9729c47bab8 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Fri, 20 Jun 2025 22:01:19 -0700 Subject: [PATCH 1/6] lib/imlib: Refactor fill_image_from_lepton out of py_fir_lepton. --- lib/imlib/imlib.c | 70 ++++++++++++++++++++++++++++++++++++ lib/imlib/imlib.h | 3 ++ modules/py_fir.c | 7 ++-- modules/py_fir_lepton.c | 80 ++++------------------------------------- modules/py_fir_lepton.h | 5 +-- 5 files changed, 86 insertions(+), 79 deletions(-) diff --git a/lib/imlib/imlib.c b/lib/imlib/imlib.c index 43afe8b4d..ad2765721 100644 --- a/lib/imlib/imlib.c +++ b/lib/imlib/imlib.c @@ -456,6 +456,76 @@ void imlib_fill_image_from_float(image_t *img, int w, int h, float *data, float } } +// This function fills a grayscale image from an array of lepton 8/14/16-bit values that are scaled +// between min and max. The image w*h must equal the floating point array w*h. +void imlib_fill_image_from_lepton(image_t *img, int w, int h, uint16_t *data, float min, float max, + bool auto_range, bool radiometric, int kelvin_offset, + bool mirror, bool flip, bool transpose) { + int new_min; + int new_max; + + if (auto_range) { + new_min = INT_MAX; + new_max = INT_MIN; + + for (int i = 0; i < w * h; i++) { + int temp = data[i]; + + if (!radiometric) { + temp = (temp - 8192) + kelvin_offset; + } + + if (temp < new_min) { + new_min = temp; + } + + if (temp > new_max) { + new_max = temp; + } + } + } else { + float tmp = min; + min = (min < max) ? min : max; + max = (max > tmp) ? max : tmp; + new_min = fast_roundf((min + 273.15f) * 100.f); // to kelvin + new_max = fast_roundf((max + 273.15f) * 100.f); // to kelvin + } + + float diff = 255.f / (new_max - new_min); + + for (int y = 0; y < h; y++) { + int y_dst = flip ? (h - 1 - y) : y; + const uint16_t *raw_row = data + (y * w); + uint8_t *row_pointer = ((uint8_t *) img->data) + (y_dst * w); + uint8_t *t_row_pointer = ((uint8_t *) img->data) + y_dst; + + for (int x = 0; x < w; x++) { + int x_dst = mirror ? (w - 1 - x) : x; + int raw = raw_row[x]; + + if (!radiometric) { + raw = (raw - 8192) + kelvin_offset; + } + + if (raw < new_min) { + raw = new_min; + } + + if (raw > new_max) { + raw = new_max; + } + + int pixel = __USAT(fast_roundf((raw - new_min) * diff), 8); + + if (!transpose) { + row_pointer[x_dst] = pixel; + } else { + t_row_pointer[x_dst * h] = pixel; + } + } + } +} + int8_t imlib_rgb565_to_l(uint16_t pixel) { float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)]; float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)]; diff --git a/lib/imlib/imlib.h b/lib/imlib/imlib.h index 91f09de98..618311a58 100644 --- a/lib/imlib/imlib.h +++ b/lib/imlib/imlib.h @@ -1187,6 +1187,9 @@ void imlib_deinit_all(); // Generic Helper Functions void imlib_fill_image_from_float(image_t *img, int w, int h, float *data, float min, float max, bool mirror, bool flip, bool dst_transpose, bool src_transpose); +void imlib_fill_image_from_lepton(image_t *img, int w, int h, uint16_t *data, float min, float max, + bool auto_range, bool radiometric, int kelvin_offset, + bool mirror, bool flip, bool transpose); // Bayer Image Processing pixformat_t imlib_bayer_shift(pixformat_t pixfmt, int x, int y, bool transpose); diff --git a/modules/py_fir.c b/modules/py_fir.c index a9abf43b7..edfda7e93 100644 --- a/modules/py_fir.c +++ b/modules/py_fir.c @@ -1089,9 +1089,10 @@ mp_obj_t py_fir_snapshot(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_a #if (OMV_FIR_LEPTON_ENABLE == 1) case FIR_LEPTON: { bool auto_range = args[ARG_scale].u_obj == mp_const_none; - fir_lepton_fill_image(&src_img, fir_width, fir_height, auto_range, min, max, - args[ARG_hmirror].u_bool, args[ARG_vflip].u_bool, - args[ARG_transpose].u_bool, args[ARG_timeout].u_int); + imlib_fill_image_from_lepton(&src_img, fir_width, fir_height, + fir_lepton_get_frame(args[ARG_timeout].u_int), min, max, auto_range, + fir_lepton_get_radiometry_enabled(), fir_lepton_get_temperature(), + args[ARG_hmirror].u_bool, args[ARG_vflip].u_bool, args[ARG_transpose].u_bool); break; } #endif diff --git a/modules/py_fir_lepton.c b/modules/py_fir_lepton.c index 9891f466b..8346090cb 100644 --- a/modules/py_fir_lepton.c +++ b/modules/py_fir_lepton.c @@ -441,7 +441,7 @@ mp_obj_t fir_lepton_get_frame_available() { return mp_obj_new_bool(framebuffer_tail != framebuffer_head); } -static const uint16_t *fir_lepton_get_frame(int timeout) { +uint16_t *fir_lepton_get_frame(int timeout) { int sampled_framebuffer_tail = framebuffer_tail; if (timeout >= 0) { @@ -464,7 +464,11 @@ static const uint16_t *fir_lepton_get_frame(int timeout) { return framebuffers[sampled_framebuffer_tail]; } -static int fir_lepton_get_temperature() { +bool fir_lepton_get_radiometry_enabled() { + return fir_lepton_rad_en; +} + +int fir_lepton_get_temperature() { LEP_SYS_FPA_TEMPERATURE_KELVIN_T kelvin; if (LEP_GetSysFpaTemperatureKelvin(&fir_lepton_handle, &kelvin) != LEP_OK) { @@ -529,78 +533,6 @@ mp_obj_t fir_lepton_read_ir(int w, int h, bool mirror, bool flip, bool transpose return mp_obj_new_tuple(4, tuple); } -void fir_lepton_fill_image(image_t *img, int w, int h, bool auto_range, float min, float max, - bool mirror, bool flip, bool transpose, int timeout) { - int kelvin = fir_lepton_get_temperature(); - const uint16_t *data = fir_lepton_get_frame(timeout); - int new_min; - int new_max; - - if (auto_range) { - new_min = INT_MAX; - new_max = INT_MIN; - - for (int i = 0, ii = w * h; i < ii; i++) { - int temp = data[i]; - - if (!fir_lepton_rad_en) { - temp = (temp - 8192) + kelvin; - } - - if (temp < new_min) { - new_min = temp; - } - - if (temp > new_max) { - new_max = temp; - } - } - } else { - float tmp = min; - min = (min < max) ? min : max; - max = (max > tmp) ? max : tmp; - new_min = fast_roundf((min + 273.15f) * 100.f); // to kelvin - new_max = fast_roundf((max + 273.15f) * 100.f); // to kelvin - } - - float diff = 255.f / (new_max - new_min); - int w_1 = w - 1; - int h_1 = h - 1; - - for (int y = 0; y < h; y++) { - int y_dst = flip ? (h_1 - y) : y; - const uint16_t *raw_row = data + (y * w); - uint8_t *row_pointer = ((uint8_t *) img->data) + (y_dst * w); - uint8_t *t_row_pointer = ((uint8_t *) img->data) + y_dst; - - for (int x = 0; x < w; x++) { - int x_dst = mirror ? (w_1 - x) : x; - int raw = raw_row[x]; - - if (!fir_lepton_rad_en) { - raw = (raw - 8192) + kelvin; - } - - if (raw < new_min) { - raw = new_min; - } - - if (raw > new_max) { - raw = new_max; - } - - int pixel = fast_roundf((raw - new_min) * diff); - pixel = __USAT(pixel, 8); - - if (!transpose) { - row_pointer[x_dst] = pixel; - } else { - t_row_pointer[x_dst * h] = pixel; - } - } - } -} - void fir_lepton_trigger_ffc(int timeout) { if (LEP_RunSysFFCNormalization(&fir_lepton_handle) != LEP_OK) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("FFC Error!")); diff --git a/modules/py_fir_lepton.h b/modules/py_fir_lepton.h index 2cae91929..d02bad003 100644 --- a/modules/py_fir_lepton.h +++ b/modules/py_fir_lepton.h @@ -31,9 +31,10 @@ void fir_lepton_register_vsync_cb(mp_obj_t cb); mp_obj_t fir_lepton_get_radiometry(); void fir_lepton_register_frame_cb(mp_obj_t cb); mp_obj_t fir_lepton_get_frame_available(); +uint16_t *fir_lepton_get_frame(int timeout); +bool fir_lepton_get_radiometry_enabled(); +int fir_lepton_get_temperature(); mp_obj_t fir_lepton_read_ta(); mp_obj_t fir_lepton_read_ir(int w, int h, bool mirror, bool flip, bool transpose, int timeout); -void fir_lepton_fill_image(image_t *img, int w, int h, bool auto_range, float min, float max, - bool mirror, bool flip, bool transpose, int timeout); void fir_lepton_trigger_ffc(int timeout); #endif // __PY_FIR_LEPTON_H__ From c758b7104026478e8dcd214b7e562f29e71b2655 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Fri, 20 Jun 2025 22:03:18 -0700 Subject: [PATCH 2/6] drivers/sensors/lepton: Fix refresh and resolution attributes. --- drivers/sensors/lepton.c | 2 +- modules/py_fir_lepton.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/sensors/lepton.c b/drivers/sensors/lepton.c index b90f228e3..a89abf83d 100644 --- a/drivers/sensors/lepton.c +++ b/drivers/sensors/lepton.c @@ -202,7 +202,7 @@ static int ioctl(omv_csi_t *csi, int request, va_list ap) { } case OMV_CSI_IOCTL_LEPTON_GET_RESOLUTION: { int *resolution = va_arg(ap, int *); - *resolution = 14; + *resolution = lepton.radiometry ? 16 : 14; break; } case OMV_CSI_IOCTL_LEPTON_RUN_COMMAND: { diff --git a/modules/py_fir_lepton.c b/modules/py_fir_lepton.c index 8346090cb..81270af03 100644 --- a/modules/py_fir_lepton.c +++ b/modules/py_fir_lepton.c @@ -390,7 +390,7 @@ int fir_lepton_init(omv_i2c_t *bus, int *w, int *h, int *refresh, int *resolutio fir_lepton_rad_en = rad == LEP_RAD_ENABLE; *w = flir_w; *h = flir_h; - *refresh = fir_lepton_3 ? 27 : 9; + *refresh = fir_lepton_3 ? 9 : 27; *resolution = fir_lepton_rad_en ? 16 : 14; #if defined(OMV_FIR_LEPTON_VSYNC_PIN) From fc0cc40a3180a769c2f13316ac7173e71bc47da2 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Fri, 20 Jun 2025 22:30:05 -0700 Subject: [PATCH 3/6] drivers/sensors/lepton: Update lepton driver with asynchronous fb. The FLIR Lepton driver now continously receives frames in the background that are stored in allocated frame buffers. On snapshot the stored frame is post-processed to produce a color an image. * Transpose now works allowing for image rotation with IMU. * Bilinear scaling is now used when drawing at a higher res. --- common/vospi.c | 74 +++++++++++++--------- common/vospi.h | 5 +- drivers/sensors/lepton.c | 128 +++++++++++++++------------------------ ports/mimxrt/main.c | 4 ++ ports/stm32/main.c | 4 ++ 5 files changed, 107 insertions(+), 108 deletions(-) diff --git a/common/vospi.c b/common/vospi.c index 1807e8327..76e0804e9 100644 --- a/common/vospi.c +++ b/common/vospi.c @@ -60,7 +60,7 @@ typedef enum { typedef struct _vospi_state { int pid; int sid; - uint16_t *framebuffer; + framebuffer_t *fb; bool lepton_3; omv_spi_t spi_bus; volatile uint32_t flags; @@ -143,33 +143,39 @@ void vospi_callback(omv_spi_t *spi, void *userdata, void *buf) { return; } - memcpy(vospi.framebuffer - + (vospi.pid * VOSPI_PID_SIZE_PIXELS) - + (vospi.sid * VOSPI_SID_SIZE_PIXELS), - base + VOSPI_HEADER_WORDS, VOSPI_PID_SIZE_PIXELS * sizeof(uint16_t)); + vbuffer_t *buffer = framebuffer_get_tail(vospi.fb, FB_PEEK); - vospi.pid += 1; - if (vospi.pid == VOSPI_PIDS_PER_SID) { - vospi.pid = 0; + 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)); - // 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; - vospi.flags &= ~VOSPI_FLAGS_CAPTURE; + 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); } - // For the FLIR Lepton 1/2 we just have to receive all the pids. - } else { - vospi.flags &= ~VOSPI_FLAGS_CAPTURE; } + } else { + vospi.flags &= ~VOSPI_FLAGS_CAPTURE; } } -int vospi_init(uint32_t n_packets, void *buffer) { +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.framebuffer = buffer; + vospi.fb = fb; // resync on first snapshot. vospi.flags = VOSPI_FLAGS_RESYNC; @@ -183,15 +189,26 @@ int vospi_init(uint32_t n_packets, void *buffer) { spi_config.clk_pha = OMV_SPI_CPHA_2EDGE; spi_config.dma_flags = OMV_SPI_DMA_CIRCULAR | OMV_SPI_DMA_DOUBLE; - if (omv_spi_init(&vospi.spi_bus, &spi_config) != 0) { - return -1; - } - return 0; + return omv_spi_init(&vospi.spi_bus, &spi_config); +} + +int vospi_deinit() { + return omv_spi_deinit(&vospi.spi_bus); +} + +bool vospi_active(void) { + return vospi.flags & VOSPI_FLAGS_CAPTURE; } int vospi_snapshot(uint32_t timeout_ms) { - // Restart counters to capture a new frame. - vospi.flags |= VOSPI_FLAGS_CAPTURE; + framebuffer_free_current_buffer(vospi.fb); + + if (!(vospi.flags & VOSPI_FLAGS_CAPTURE)) { + framebuffer_setup_buffers(vospi.fb); + + // Restart counters to capture a new frame. + vospi.flags |= VOSPI_FLAGS_CAPTURE; + } // Snapshot start tick mp_uint_t tick_start = mp_hal_ticks_ms(); @@ -202,6 +219,10 @@ int vospi_snapshot(uint32_t timeout_ms) { vospi_resync(); } + if (timeout_ms == 0) { + return -1; + } + if ((mp_hal_ticks_ms() - tick_start) > timeout_ms) { omv_spi_transfer_abort(&vospi.spi_bus); vospi.pid = 0; @@ -211,9 +232,8 @@ int vospi_snapshot(uint32_t timeout_ms) { } MICROPY_EVENT_POLL_HOOK - } while (vospi.flags & VOSPI_FLAGS_CAPTURE); + } while (!framebuffer_get_head(vospi.fb, FB_NO_FLAGS)); return 0; } - #endif diff --git a/common/vospi.h b/common/vospi.h index 15c6e7144..d4820a95c 100644 --- a/common/vospi.h +++ b/common/vospi.h @@ -25,6 +25,9 @@ */ #ifndef __VOSPI_H__ #define __VOSPI_H__ -int vospi_init(uint32_t n_packets, void *buffer); +#include "framebuffer.h" +int vospi_init(uint32_t n_packets, framebuffer_t *fb); +int vospi_deinit(); +bool vospi_active(void); int vospi_snapshot(uint32_t timeout_ms); #endif // __VOSPI_H__ diff --git a/drivers/sensors/lepton.c b/drivers/sensors/lepton.c index a89abf83d..3a74f1142 100644 --- a/drivers/sensors/lepton.c +++ b/drivers/sensors/lepton.c @@ -69,7 +69,6 @@ typedef struct lepton_state { LEP_CAMERA_PORT_DESC_T port; } lepton_state_t; -extern uint16_t _vospi_buf[]; static lepton_state_t lepton; static int lepton_reset(omv_csi_t *csi, bool measurement_mode, bool high_temp_mode); @@ -359,21 +358,21 @@ static int lepton_reset(omv_csi_t *csi, bool measurement_mode, bool high_temp_mo } static int reset(omv_csi_t *csi) { - static bool vospi_initialized = false; + vospi_deinit(); memset(&lepton, 0, sizeof(lepton_state_t)); lepton.min_temp = LEPTON_MIN_TEMP_DEFAULT; lepton.max_temp = LEPTON_MAX_TEMP_DEFAULT; + // Extra delay after power-on + mp_hal_delay_ms(1000); + if (lepton_reset(csi, false, false) != 0) { return OMV_CSI_ERROR_CTL_FAILED; } - if (vospi_initialized == false) { - if (vospi_init(lepton.v_res, _vospi_buf) != 0) { - return OMV_CSI_ERROR_CTL_FAILED; - } - vospi_initialized = true; + if (csi->fb && vospi_init(lepton.v_res, csi->fb) != 0) { + return OMV_CSI_ERROR_CTL_FAILED; } return 0; @@ -383,10 +382,6 @@ static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { framebuffer_t *fb = csi->fb; framebuffer_update_jpeg_buffer(fb); - if (fb->n_buffers != 1) { - framebuffer_set_buffers(fb, 1); - } - if (csi->pixformat == PIXFORMAT_INVALID) { return OMV_CSI_ERROR_INVALID_PIXFORMAT; } @@ -399,15 +394,12 @@ static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { return OMV_CSI_ERROR_INVALID_FRAMESIZE; } - if (omv_csi_check_framebuffer_size(csi) == -1) { - return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; + if (resolution[csi->framesize][0] < lepton.h_res || resolution[csi->framesize][1] < lepton.v_res) { + return OMV_CSI_ERROR_INVALID_FRAMESIZE; } - framebuffer_free_current_buffer(fb); - vbuffer_t *buffer = framebuffer_get_tail(fb, FB_NO_FLAGS); - - if (!buffer) { - return OMV_CSI_ERROR_FRAMEBUFFER_ERROR; + if (omv_csi_check_framebuffer_size(csi) == -1) { + return OMV_CSI_ERROR_FRAMEBUFFER_OVERFLOW; } for (int i = 0; i < LEPTON_SNAPSHOT_RETRY; i++) { @@ -423,20 +415,18 @@ static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { } } - fb->w = fb->u; - fb->h = fb->v; + if (!csi->transpose) { + fb->w = fb->u; + fb->h = fb->v; + } else { + fb->w = fb->v; + fb->h = fb->u; + } + fb->pixfmt = csi->pixformat; - framebuffer_init_image(fb, image); - - float x_scale = resolution[csi->framesize][0] / ((float) lepton.h_res); - float y_scale = resolution[csi->framesize][1] / ((float) lepton.v_res); - // MAX == KeepAspectRationByExpanding - MIN == KeepAspectRatio - float scale = IM_MAX(x_scale, y_scale), scale_inv = 1.0f / scale; - int x_offset = (resolution[csi->framesize][0] - (lepton.h_res * scale)) / 2; - int y_offset = (resolution[csi->framesize][1] - (lepton.v_res * scale)) / 2; - // The code below upscales the source image to the requested frame size - // and then crops it to the window set by the user. + image_t fb_image; + framebuffer_init_image(fb, &fb_image); LEP_SYS_FPA_TEMPERATURE_KELVIN_T kelvin; if (lepton.measurement_mode && (!lepton.radiometry)) { @@ -444,60 +434,38 @@ static int snapshot(omv_csi_t *csi, image_t *image, uint32_t flags) { return OMV_CSI_ERROR_IO_ERROR; } } + + fb_alloc_mark(); + image_t temp = { + .w = (!csi->transpose) ? lepton.h_res : lepton.v_res, + .h = (!csi->transpose) ? lepton.v_res : lepton.h_res, + .pixfmt = PIXFORMAT_GRAYSCALE, + .data = fb_alloc(lepton.h_res * lepton.v_res, FB_ALLOC_CACHE_ALIGN), + }; - for (int y = y_offset, yy = fast_ceilf(lepton.v_res * scale) + y_offset; y < yy; y++) { - if ((fb->y <= y) && (y < (fb->y + fb->v))) { - // user window cropping + // 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 - uint16_t *row_ptr = _vospi_buf + (fast_floorf(y * scale_inv) * lepton.h_res); - - for (int x = x_offset, xx = fast_ceilf(lepton.h_res * scale) + x_offset; x < xx; x++) { - if ((fb->x <= x) && (x < (fb->x + fb->u))) { - // user window cropping - - // Value is the 14/16-bit value from the FLIR IR camera. - // However, with AGC enabled only the bottom 8-bits are non-zero. - int value = row_ptr[fast_floorf(x * scale_inv)]; - - if (lepton.measurement_mode) { - // Need to convert 14/16-bits to 8-bits ourselves... - if (!lepton.radiometry) { - value = (value - 8192) + kelvin; - } - float celsius = (value * 0.01f) - 273.15f; - celsius = IM_CLAMP(celsius, lepton.min_temp, lepton.max_temp); - value = __USAT(IM_DIV(((celsius - lepton.min_temp) * 255), - (lepton.max_temp - lepton.min_temp)), 8); - } - - int t_x = x - fb->x; - int t_y = y - fb->y; - - if (lepton.hmirror) { - t_x = fb->u - t_x - 1; - } - if (lepton.vflip) { - t_y = fb->v - t_y - 1; - } - - switch (csi->pixformat) { - case PIXFORMAT_GRAYSCALE: { - IMAGE_PUT_GRAYSCALE_PIXEL(image, t_x, t_y, value & 0xFF); - break; - } - case PIXFORMAT_RGB565: { - IMAGE_PUT_RGB565_PIXEL(image, t_x, t_y, csi->color_palette[value & 0xFF]); - break; - } - default: { - break; - } - } - } - } - } + // 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_draw_image(&fb_image, &temp, 0, 0, 1.0f, 1.0f, NULL, -1, 255, + (csi->pixformat == PIXFORMAT_RGB565) ? csi->color_palette : NULL, NULL, + IMAGE_HINT_BILINEAR | IMAGE_HINT_CENTER | IMAGE_HINT_SCALE_ASPECT_EXPAND, + NULL, NULL, NULL); + + fb_alloc_free_till_mark(); + framebuffer_init_image(fb, image); return 0; } diff --git a/ports/mimxrt/main.c b/ports/mimxrt/main.c index 0db6537e6..8c20cd7b6 100644 --- a/ports/mimxrt/main.c +++ b/ports/mimxrt/main.c @@ -75,6 +75,7 @@ #include "file_utils.h" #include "mp_utils.h" #include "mimxrt_hal.h" +#include "vospi.h" int main(void) { bool first_soft_reset = true; @@ -222,6 +223,9 @@ soft_reset: soft_reset_exit: mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); + #if OMV_ENABLE_VOSPI || OMV_LEPTON_ENABLE + vospi_deinit(); + #endif #if MICROPY_PY_MACHINE_CAN machine_can_irq_deinit(); #endif diff --git a/ports/stm32/main.c b/ports/stm32/main.c index b1328d9ba..ca744fb05 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -88,6 +88,7 @@ #include "fb_alloc.h" #include "dma_alloc.h" #include "file_utils.h" +#include "vospi.h" #include "py_image.h" #include "py_fir.h" @@ -373,6 +374,9 @@ soft_reset_exit: // soft reset mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); + #if OMV_ENABLE_VOSPI || OMV_LEPTON_ENABLE + vospi_deinit(); + #endif #if MICROPY_PY_LWIP systick_disable_dispatch(SYSTICK_DISPATCH_LWIP); #endif From 1321c5d1a153bc47db039895b6bdf03eda60c9ac Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Fri, 20 Jun 2025 22:41:49 -0700 Subject: [PATCH 4/6] common: Remove legacy fixed vospi memory. --- boards/ARDUINO_GIGA/omv_boardconfig.h | 2 -- boards/ARDUINO_PORTENTA_H7/omv_boardconfig.h | 2 -- boards/OPENMV4/omv_boardconfig.h | 4 +--- boards/OPENMV4P/omv_boardconfig.h | 2 -- boards/OPENMVPT/omv_boardconfig.h | 2 -- boards/OPENMV_N6/omv_boardconfig.h | 2 -- boards/OPENMV_RT1060/omv_boardconfig.h | 4 +--- common/common.ld.S | 11 ----------- 8 files changed, 2 insertions(+), 27 deletions(-) diff --git a/boards/ARDUINO_GIGA/omv_boardconfig.h b/boards/ARDUINO_GIGA/omv_boardconfig.h index cbf379a2b..1df679c13 100644 --- a/boards/ARDUINO_GIGA/omv_boardconfig.h +++ b/boards/ARDUINO_GIGA/omv_boardconfig.h @@ -141,8 +141,6 @@ #define OMV_FB_OVERLAY_SIZE (448K) // Fast fb_alloc memory size. #define OMV_JPEG_MEMORY DRAM // JPEG buffer memory buffer. #define OMV_JPEG_SIZE (1M) // IDE JPEG buffer (header + data). -#define OMV_VOSPI_MEMORY DTCM // VoSPI buffer memory. -#define OMV_VOSPI_SIZE (38K) #define OMV_DMA_MEMORY SRAM3 // Misc DMA buffers memory. #define OMV_DMA_MEMORY_D1 SRAM0 // Domain 1 DMA buffers. #define OMV_DMA_MEMORY_D2 SRAM3 // Domain 2 DMA buffers. diff --git a/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.h b/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.h index 35d42a800..cd5c20860 100644 --- a/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.h +++ b/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.h @@ -139,8 +139,6 @@ #define OMV_FB_OVERLAY_SIZE (480K) // Fast fb_alloc memory size. #define OMV_JPEG_MEMORY DRAM // JPEG buffer memory buffer. #define OMV_JPEG_SIZE (1M) // IDE JPEG buffer (header + data). -#define OMV_VOSPI_MEMORY SRAM4 // VoSPI buffer memory. -#define OMV_VOSPI_SIZE (38K) #define OMV_DMA_MEMORY SRAM3 // Misc DMA buffers memory. #define OMV_DMA_MEMORY_D1 SRAM0 // Domain 1 DMA buffers. #define OMV_DMA_MEMORY_D2 SRAM3 // Domain 2 DMA buffers. diff --git a/boards/OPENMV4/omv_boardconfig.h b/boards/OPENMV4/omv_boardconfig.h index a1121e51a..3bdc15d6f 100644 --- a/boards/OPENMV4/omv_boardconfig.h +++ b/boards/OPENMV4/omv_boardconfig.h @@ -138,11 +138,9 @@ #define OMV_FB_ALLOC_SIZE (80K) // minimum fb alloc size #define OMV_JPEG_MEMORY SRAM0 // JPEG buffer memory. #define OMV_JPEG_SIZE (32K) // IDE JPEG buffer (header + data). -#define OMV_VOSPI_MEMORY SRAM4 // VoSPI buffer memory. -#define OMV_VOSPI_SIZE (38K) #define OMV_DMA_MEMORY SRAM2 // Misc DMA buffers memory. #define OMV_GC_BLOCK0_MEMORY SRAM4 // Main GC block. -#define OMV_GC_BLOCK0_SIZE (26K) +#define OMV_GC_BLOCK0_SIZE (64K) #define OMV_GC_BLOCK1_MEMORY SRAM1 // Extra GC block 0. #define OMV_GC_BLOCK1_SIZE (267K) #define OMV_MSC_BUF_SIZE (2K) // USB MSC bot data diff --git a/boards/OPENMV4P/omv_boardconfig.h b/boards/OPENMV4P/omv_boardconfig.h index c664d2a81..5f7de326f 100644 --- a/boards/OPENMV4P/omv_boardconfig.h +++ b/boards/OPENMV4P/omv_boardconfig.h @@ -126,8 +126,6 @@ #define OMV_FB_OVERLAY_SIZE (496K) // Fast fb_alloc memory size. #define OMV_JPEG_MEMORY DRAM // JPEG buffer memory buffer. #define OMV_JPEG_SIZE (1M) // IDE JPEG buffer (header + data). -#define OMV_VOSPI_MEMORY SRAM4 // VoSPI buffer memory. -#define OMV_VOSPI_SIZE (38K) #define OMV_DMA_MEMORY SRAM3 // Misc DMA buffers memory. #define OMV_DMA_MEMORY_D1 SRAM0 // Domain 1 DMA buffers. #define OMV_DMA_MEMORY_D2 SRAM3 // Domain 2 DMA buffers. diff --git a/boards/OPENMVPT/omv_boardconfig.h b/boards/OPENMVPT/omv_boardconfig.h index f95daf25c..c933513ad 100644 --- a/boards/OPENMVPT/omv_boardconfig.h +++ b/boards/OPENMVPT/omv_boardconfig.h @@ -117,8 +117,6 @@ #define OMV_FB_OVERLAY_SIZE (496K) // Fast fb_alloc memory size. #define OMV_JPEG_MEMORY DRAM // JPEG buffer memory buffer. #define OMV_JPEG_SIZE (1M) // IDE JPEG buffer (header + data). -#define OMV_VOSPI_MEMORY SRAM4 // VoSPI buffer memory. -#define OMV_VOSPI_SIZE (38K) #define OMV_DMA_MEMORY SRAM3 // DMA buffers memory. #define OMV_DMA_MEMORY_D1 SRAM0 // Domain 1 DMA buffers. #define OMV_DMA_MEMORY_D2 SRAM3 // Domain 2 DMA buffers. diff --git a/boards/OPENMV_N6/omv_boardconfig.h b/boards/OPENMV_N6/omv_boardconfig.h index 875fdfeab..52a06419d 100644 --- a/boards/OPENMV_N6/omv_boardconfig.h +++ b/boards/OPENMV_N6/omv_boardconfig.h @@ -153,8 +153,6 @@ #define OMV_FB_ALLOC_SIZE (11M) // minimum fb_alloc size #define OMV_JPEG_MEMORY DRAM // JPEG buffer memory buffer. #define OMV_JPEG_SIZE (1M) // IDE JPEG buffer (header + data). -#define OMV_VOSPI_MEMORY SRAM1 // VoSPI buffer memory. -#define OMV_VOSPI_SIZE (64K) #define OMV_DMA_MEMORY SRAM1 // Misc DMA buffers memory. #define OMV_GC_BLOCK0_MEMORY SRAM2 // Main GC block #define OMV_GC_BLOCK0_SIZE (1M) diff --git a/boards/OPENMV_RT1060/omv_boardconfig.h b/boards/OPENMV_RT1060/omv_boardconfig.h index 687be0dc3..9f40f21ed 100644 --- a/boards/OPENMV_RT1060/omv_boardconfig.h +++ b/boards/OPENMV_RT1060/omv_boardconfig.h @@ -84,11 +84,9 @@ #define OMV_FB_ALLOC_SIZE (10M) // minimum fb alloc size #define OMV_FB_OVERLAY_MEMORY OCRM1 // Fast fb_alloc memory. #define OMV_FB_OVERLAY_SIZE (512K) -#define OMV_VOSPI_MEMORY OCRM2 // VoSPI buffer memory. -#define OMV_VOSPI_SIZE (38K) #define OMV_DMA_MEMORY DTCM // Misc DMA buffers memory. #define OMV_GC_BLOCK0_MEMORY OCRM2 // Extra GC block 0. -#define OMV_GC_BLOCK0_SIZE (26K) +#define OMV_GC_BLOCK0_SIZE (64K) #define OMV_GC_BLOCK1_MEMORY DTCM // Main GC block #define OMV_GC_BLOCK1_SIZE (288K) #define OMV_GC_BLOCK2_MEMORY DRAM // Extra GC block 1. diff --git a/common/common.ld.S b/common/common.ld.S index 7ac266baf..e048dbf0a 100644 --- a/common/common.ld.S +++ b/common/common.ld.S @@ -133,17 +133,6 @@ PROVIDE(__stack = __StackTop); } >OMV_JPEG_MEMORY #endif -/* VOSPI framebuffer memory */ -#if defined(OMV_VOSPI_MEMORY) -.vospi_memory (NOLOAD) : -{ - . = ALIGN(4); - _vospi_buf = .; - . = . + OMV_VOSPI_SIZE; - . = ALIGN(4); -} >OMV_VOSPI_MEMORY -#endif - /* GPU memory */ #if defined(OMV_GPU_MEMORY) .gpu_memory (NOLOAD) : From 3984ec783f94be4ea5249990a6a24d946cb5d9b5 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 22 Jun 2025 00:05:10 -0700 Subject: [PATCH 5/6] ports/stm32: Move VOSPI memory near DMA controller. On the STM32N6 the DMA memory meant to be used with the SPI bus is in the low-performance memory domain. Moving the DMA buffers and linked list buffers there reduces the AHB cycle time by the DMA controller making it more stable. --- boards/ARDUINO_NICLA_VISION/omv_boardconfig.h | 1 + boards/ARDUINO_PORTENTA_H7/omv_boardconfig.h | 1 + boards/OPENMV2/omv_boardconfig.h | 1 + boards/OPENMV3/omv_boardconfig.h | 1 + boards/OPENMV4/omv_boardconfig.h | 1 + boards/OPENMV4P/omv_boardconfig.h | 1 + boards/OPENMVPT/omv_boardconfig.h | 1 + boards/OPENMV_N6/omv_boardconfig.h | 2 ++ boards/OPENMV_RT1060/omv_boardconfig.h | 1 + common/vospi.c | 2 +- modules/py_fir_lepton.c | 2 +- ports/stm32/omv_spi.c | 2 +- 12 files changed, 13 insertions(+), 3 deletions(-) diff --git a/boards/ARDUINO_NICLA_VISION/omv_boardconfig.h b/boards/ARDUINO_NICLA_VISION/omv_boardconfig.h index 6ae50cc65..1f7ecde85 100644 --- a/boards/ARDUINO_NICLA_VISION/omv_boardconfig.h +++ b/boards/ARDUINO_NICLA_VISION/omv_boardconfig.h @@ -139,6 +139,7 @@ #define OMV_GC_BLOCK2_SIZE (276K) #define OMV_MSC_BUF_SIZE (2K) // USB MSC bot data #define OMV_LINE_BUF_SIZE (3 * 1024) // Image line buffer round(640 * 2BPP * 2 buffers). +#define OMV_VOSPI_DMA_BUFFER ".dma_buffer" // Memory map. #define OMV_FLASH_ORIGIN 0x08000000 diff --git a/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.h b/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.h index cd5c20860..1b0c6f476 100644 --- a/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.h +++ b/boards/ARDUINO_PORTENTA_H7/omv_boardconfig.h @@ -154,6 +154,7 @@ #define OMV_MSC_BUF_SIZE (2K) // USB MSC bot data #define OMV_SDRAM_SIZE (8 * 1024 * 1024) // This needs to be here for UVC firmware. #define OMV_LINE_BUF_SIZE (11 * 1024) // Image line buffer round(2592 * 2BPP * 2 buffers). +#define OMV_VOSPI_DMA_BUFFER ".dma_buffer" // Memory map. #define OMV_FLASH_ORIGIN 0x08000000 diff --git a/boards/OPENMV2/omv_boardconfig.h b/boards/OPENMV2/omv_boardconfig.h index 1128ad77f..c63d94dd9 100644 --- a/boards/OPENMV2/omv_boardconfig.h +++ b/boards/OPENMV2/omv_boardconfig.h @@ -93,6 +93,7 @@ #define OMV_MSC_BUF_SIZE (2K) // USB MSC bot data #define OMV_FFS_BUF_SIZE (16K) // Flash filesystem cache #define OMV_LINE_BUF_SIZE (2 * 1024) // Image line buffer round(320 * 2BPP * 2 buffers). +#define OMV_VOSPI_DMA_BUFFER ".dma_buffer" // Memory map. #define OMV_FLASH_ORIGIN 0x08000000 diff --git a/boards/OPENMV3/omv_boardconfig.h b/boards/OPENMV3/omv_boardconfig.h index 0d027d9ee..fd3541710 100644 --- a/boards/OPENMV3/omv_boardconfig.h +++ b/boards/OPENMV3/omv_boardconfig.h @@ -92,6 +92,7 @@ #define OMV_MSC_BUF_SIZE (2K) // USB MSC bot data #define OMV_FFS_BUF_SIZE (32K) // Flash filesystem cache #define OMV_LINE_BUF_SIZE (3 * 1024) // Image line buffer round(640 * 2BPP * 2 buffers). +#define OMV_VOSPI_DMA_BUFFER ".dma_buffer" // Memory map. #define OMV_FLASH_ORIGIN 0x08000000 diff --git a/boards/OPENMV4/omv_boardconfig.h b/boards/OPENMV4/omv_boardconfig.h index 3bdc15d6f..b51a2fc85 100644 --- a/boards/OPENMV4/omv_boardconfig.h +++ b/boards/OPENMV4/omv_boardconfig.h @@ -145,6 +145,7 @@ #define OMV_GC_BLOCK1_SIZE (267K) #define OMV_MSC_BUF_SIZE (2K) // USB MSC bot data #define OMV_LINE_BUF_SIZE (3 * 1024) // Image line buffer round(640 * 2BPP * 2 buffers). +#define OMV_VOSPI_DMA_BUFFER ".dma_buffer" // Memory map. #define OMV_FLASH_ORIGIN 0x08000000 diff --git a/boards/OPENMV4P/omv_boardconfig.h b/boards/OPENMV4P/omv_boardconfig.h index 5f7de326f..9627461ad 100644 --- a/boards/OPENMV4P/omv_boardconfig.h +++ b/boards/OPENMV4P/omv_boardconfig.h @@ -137,6 +137,7 @@ #define OMV_MSC_BUF_SIZE (2K) // USB MSC bot data #define OMV_SDRAM_SIZE (32 * 1024 * 1024) // This needs to be here for UVC firmware. #define OMV_LINE_BUF_SIZE (11 * 1024) // Image line buffer round(2592 * 2BPP * 2 buffers). +#define OMV_VOSPI_DMA_BUFFER ".dma_buffer" // Memory map. #define OMV_FLASH_ORIGIN 0x08000000 diff --git a/boards/OPENMVPT/omv_boardconfig.h b/boards/OPENMVPT/omv_boardconfig.h index c933513ad..7f5e345ce 100644 --- a/boards/OPENMVPT/omv_boardconfig.h +++ b/boards/OPENMVPT/omv_boardconfig.h @@ -128,6 +128,7 @@ #define OMV_SDRAM_SIZE (64 * 1024 * 1024) // This needs to be here for UVC firmware. #define OMV_MSC_BUF_SIZE (2K) // USB MSC bot data #define OMV_LINE_BUF_SIZE (11 * 1024) // Image line buffer round(2592 * 2BPP * 2 buffers). +#define OMV_VOSPI_DMA_BUFFER ".dma_buffer" // Memory map. #define OMV_FLASH_ORIGIN 0x08000000 diff --git a/boards/OPENMV_N6/omv_boardconfig.h b/boards/OPENMV_N6/omv_boardconfig.h index 52a06419d..107feeb2a 100644 --- a/boards/OPENMV_N6/omv_boardconfig.h +++ b/boards/OPENMV_N6/omv_boardconfig.h @@ -154,11 +154,13 @@ #define OMV_JPEG_MEMORY DRAM // JPEG buffer memory buffer. #define OMV_JPEG_SIZE (1M) // IDE JPEG buffer (header + data). #define OMV_DMA_MEMORY SRAM1 // Misc DMA buffers memory. +#define OMV_DMA_MEMORY_D2 SRAM7 // Domain 2 DMA buffers. #define OMV_GC_BLOCK0_MEMORY SRAM2 // Main GC block #define OMV_GC_BLOCK0_SIZE (1M) #define OMV_GC_BLOCK1_MEMORY DRAM // Main GC block #define OMV_GC_BLOCK1_SIZE (24M) #define OMV_MSC_BUF_SIZE (4K) // USB MSC bot data +#define OMV_VOSPI_DMA_BUFFER ".d2_dma_buffer" // Memory map. #define OMV_DTCM_ORIGIN 0x30000000 diff --git a/boards/OPENMV_RT1060/omv_boardconfig.h b/boards/OPENMV_RT1060/omv_boardconfig.h index 9f40f21ed..8fd690c79 100644 --- a/boards/OPENMV_RT1060/omv_boardconfig.h +++ b/boards/OPENMV_RT1060/omv_boardconfig.h @@ -93,6 +93,7 @@ #define OMV_GC_BLOCK2_SIZE (8M) #define OMV_RAMFUNC_MEMORY ITCM2 // RAM code memory. #define OMV_LINE_BUF_SIZE (11 * 1024) // Image line buffer. +#define OMV_VOSPI_DMA_BUFFER ".dma_buffer" // Memory configuration. #define OMV_DTCM_ORIGIN 0x20000000 diff --git a/common/vospi.c b/common/vospi.c index 76e0804e9..03de484ea 100644 --- a/common/vospi.c +++ b/common/vospi.c @@ -68,7 +68,7 @@ typedef struct _vospi_state { static vospi_state_t vospi; -static uint16_t OMV_ATTR_SECTION(OMV_ATTR_ALIGNED_DMA(vospi_buf[VOSPI_BUFFER_SIZE]), ".dma_buffer"); +static uint16_t OMV_ATTR_SECTION(OMV_ATTR_ALIGNED_DMA(vospi_buf[VOSPI_BUFFER_SIZE]), OMV_VOSPI_DMA_BUFFER); static void vospi_callback(omv_spi_t *spi, void *userdata, void *buf); static void vospi_resync() { diff --git a/modules/py_fir_lepton.c b/modules/py_fir_lepton.c index 81270af03..c5e8948c0 100644 --- a/modules/py_fir_lepton.c +++ b/modules/py_fir_lepton.c @@ -81,7 +81,7 @@ static soft_timer_entry_t flir_lepton_spi_rx_timer = {}; static int fir_lepton_spi_rx_cb_tail = 0; static int fir_lepton_spi_rx_cb_expected_pid = 0; static int fir_lepton_spi_rx_cb_expected_sid = 0; -static uint16_t OMV_ATTR_SECTION(OMV_ATTR_ALIGNED_DMA(fir_lepton_buf[VOSPI_BUFFER_SIZE]), ".dma_buffer"); +static uint16_t OMV_ATTR_SECTION(OMV_ATTR_ALIGNED_DMA(fir_lepton_buf[VOSPI_BUFFER_SIZE]), OMV_VOSPI_DMA_BUFFER); static void fir_lepton_spi_callback(omv_spi_t *spi, void *userdata, void *buf); static mp_obj_t fir_lepton_spi_resync_callback(mp_obj_t unused) { diff --git a/ports/stm32/omv_spi.c b/ports/stm32/omv_spi.c index 686d716fa..0abd6bedd 100644 --- a/ports/stm32/omv_spi.c +++ b/ports/stm32/omv_spi.c @@ -95,7 +95,7 @@ DEFINE_SPI_INSTANCE(6) static omv_spi_t *omv_spi_descr_all[6] = { NULL }; #if defined(STM32N6) -static DMA_NodeTypeDef OMV_ATTR_SECTION(OMV_ATTR_ALIGNED(omv_dma_nodes_all[6 * 2], 32), ".dma_buffer"); +static DMA_NodeTypeDef OMV_ATTR_SECTION(OMV_ATTR_ALIGNED(omv_dma_nodes_all[6 * 2], 32), OMV_VOSPI_DMA_BUFFER); #endif static uint32_t omv_spi_clocksource(SPI_TypeDef *spi) { From c4795d319179d7e7f6bf55625da7e8c4bd8053e7 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 22 Jun 2025 00:08:44 -0700 Subject: [PATCH 6/6] ports/stm32: Increase SPI DMA efficency. The SPI fifo triggers a request when it has 4-bytes of data allowing for more efficent bus usage. The same technique is used on the H7 to reduce system bus matrix resources. --- ports/stm32/omv_spi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ports/stm32/omv_spi.c b/ports/stm32/omv_spi.c index 0abd6bedd..60c61e930 100644 --- a/ports/stm32/omv_spi.c +++ b/ports/stm32/omv_spi.c @@ -330,8 +330,8 @@ static int omv_spi_dma_init(omv_spi_t *spi, uint32_t direction, omv_spi_config_t dma_init->Direction = direction; dma_init->SrcInc = (direction == DMA_PERIPH_TO_MEMORY) ? DMA_SINC_FIXED : DMA_SINC_INCREMENTED; dma_init->DestInc = (direction == DMA_MEMORY_TO_PERIPH) ? DMA_DINC_FIXED : DMA_DINC_INCREMENTED; - dma_init->SrcDataWidth = (config->datasize == 8) ? DMA_SRC_DATAWIDTH_BYTE : DMA_SRC_DATAWIDTH_WORD; - dma_init->DestDataWidth = (config->datasize == 8) ? DMA_DEST_DATAWIDTH_BYTE : DMA_DEST_DATAWIDTH_WORD; + dma_init->SrcDataWidth = DMA_SRC_DATAWIDTH_WORD; + dma_init->DestDataWidth = DMA_DEST_DATAWIDTH_WORD; dma_init->SrcBurstLength = 1; dma_init->DestBurstLength = 1; dma_init->TransferEventMode = DMA_TCEM_BLOCK_TRANSFER; @@ -368,7 +368,7 @@ static int omv_spi_dma_init(omv_spi_t *spi, uint32_t direction, omv_spi_config_t return -1; } - dma_descr->InitLinkedList.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT; + dma_descr->InitLinkedList.Priority = DMA_HIGH_PRIORITY; dma_descr->InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION; dma_descr->InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR; dma_descr->InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT0;