From a7033e1b3adbe700d739f6efbaaa31739f39b50b Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Wed, 15 Mar 2023 19:19:55 +0100 Subject: [PATCH] ports/mimxrt: Add MIMXRT OMV port. --- src/micropython | 2 +- src/omv/ports/mimxrt/main.c | 366 ++++++++++++++ src/omv/ports/mimxrt/mimxrt.ld.S | 250 ++++++++++ src/omv/ports/mimxrt/mimxrt_hal.c | 254 ++++++++++ src/omv/ports/mimxrt/mimxrt_hal.h | 19 + src/omv/ports/mimxrt/omv_gpio.c | 193 ++++++++ src/omv/ports/mimxrt/omv_i2c.c | 309 ++++++++++++ src/omv/ports/mimxrt/omv_portconfig.h | 121 +++++ src/omv/ports/mimxrt/omv_portconfig.mk | 637 +++++++++++++++++++++++++ src/omv/ports/mimxrt/omv_spi.c | 238 +++++++++ src/omv/ports/mimxrt/sensor.c | 379 +++++++++++++++ tools/imxrt_pins_gen.py | 121 +++++ 12 files changed, 2888 insertions(+), 1 deletion(-) create mode 100644 src/omv/ports/mimxrt/main.c create mode 100644 src/omv/ports/mimxrt/mimxrt.ld.S create mode 100644 src/omv/ports/mimxrt/mimxrt_hal.c create mode 100644 src/omv/ports/mimxrt/mimxrt_hal.h create mode 100644 src/omv/ports/mimxrt/omv_gpio.c create mode 100644 src/omv/ports/mimxrt/omv_i2c.c create mode 100644 src/omv/ports/mimxrt/omv_portconfig.h create mode 100644 src/omv/ports/mimxrt/omv_portconfig.mk create mode 100644 src/omv/ports/mimxrt/omv_spi.c create mode 100644 src/omv/ports/mimxrt/sensor.c create mode 100644 tools/imxrt_pins_gen.py diff --git a/src/micropython b/src/micropython index 832393b52..93d0a29ef 160000 --- a/src/micropython +++ b/src/micropython @@ -1 +1 @@ -Subproject commit 832393b522acfaca4ccc60a8ee200087aa6ca345 +Subproject commit 93d0a29efaa830f3be2ab5b7dd47110c814ca9d4 diff --git a/src/omv/ports/mimxrt/main.c b/src/omv/ports/mimxrt/main.c new file mode 100644 index 000000000..e7616a79d --- /dev/null +++ b/src/omv/ports/mimxrt/main.c @@ -0,0 +1,366 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * main function. + */ + +#include "py/compile.h" +#include "py/runtime.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "py/mphal.h" +#include "py/stackctrl.h" +#include "shared/readline/readline.h" +#include "shared/runtime/gchelper.h" +#include "shared/runtime/pyexec.h" +#include "shared/runtime/softtimer.h" +#include "ticks.h" +#include "tusb.h" +#include "led.h" +#include "pendsv.h" +#include "modmachine.h" +#include "sdcard.h" +#include "systick.h" +#include "modmimxrt.h" + +#if MICROPY_PY_LWIP +#include "lwip/init.h" +#include "lwip/apps/mdns.h" +#if MICROPY_PY_NETWORK_CYW43 +#include "lib/cyw43-driver/src/cyw43.h" +#endif +#endif + +#if MICROPY_PY_BLUETOOTH +#include "mpbthciport.h" +#include "extmod/modbluetooth.h" +#endif + +#if MICROPY_PY_NETWORK +#include "extmod/modnetwork.h" +#endif + +#include "extmod/vfs.h" +#include "extmod/vfs_fat.h" +#include "common/factoryreset.h" + +#include "omv_boardconfig.h" +#include "framebuffer.h" +#include "sensor.h" +#include "usbdbg.h" +#include "tinyusb_debug.h" +#include "fb_alloc.h" +#include "dma_alloc.h" +#include "ff_wrapper.h" +#include "mimxrt_hal.h" + +extern uint8_t _sstack, _estack, _heap_start, _heap_end; + +void exec_boot_script(const char *path, bool interruptible) +{ + nlr_buf_t nlr; + bool interrupted = false; + if (nlr_push(&nlr) == 0) { + // Enable IDE interrupts if allowed. + if (interruptible) { + usbdbg_set_irq_enabled(true); + usbdbg_set_script_running(true); + } + + // Parse, compile and execute the script. + pyexec_file_if_exists(path, true); + nlr_pop(); + } else { + interrupted = true; + } + + // Disable IDE interrupts + usbdbg_set_irq_enabled(false); + usbdbg_set_script_running(false); + + if (interrupted) { + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } +} + +int main(void) { + bool sdcard_mounted = false; + bool first_soft_reset = true; + + mimxrt_hal_init(); + ticks_init(); + led_init(); + pendsv_init(); + +soft_reset: + #if defined(MICROPY_HW_LED1) + led_init(); + #endif + + // Initialise stack extents and GC heap. + mp_stack_set_top(&_estack); + mp_stack_set_limit(&_estack - &_sstack - 1024); + gc_init(&_heap_start, &_heap_end); + + // Initialise MicroPython runtime. + mp_init(); + + // Initialise low-level sub-systems. + #if MICROPY_PY_LCD + py_lcd_init0(); + #endif + //py_fir_init0(); + #if MICROPY_PY_TV + py_tv_init0(); + #endif + #if MICROPY_PY_BUZZER + py_buzzer_init0(); + #endif // MICROPY_PY_BUZZER + imlib_init_all(); + readline_init0(); + #if MICROPY_HW_ENABLE_CAN + can_init0(); + #endif + fb_alloc_init0(); + framebuffer_init0(); + sensor_init0(); + //dma_alloc_init0(); + #ifdef IMLIB_ENABLE_IMAGE_FILE_IO + file_buffer_init0(); + #endif + #if MICROPY_HW_ENABLE_SERVO + servo_init(); + #endif + usbdbg_init(); + machine_adc_init(); + #if MICROPY_PY_MACHINE_SDCARD + machine_sdcard_init0(); + sdcard_init_pins(&mimxrt_sdcard_objs[MICROPY_HW_SDCARD_SDMMC - 1]); + #endif + #if MICROPY_PY_MACHINE_I2S + machine_i2s_init0(); + #endif + machine_rtc_start(); + + #if MICROPY_PY_LWIP + // lwIP doesn't allow to reinitialise itself by subsequent calls to this function + // because the system timeout list (next_timeout) is only ever reset by BSS clearing. + // So for now we only init the lwIP stack once on power-up. + if (first_soft_reset) { + lwip_init(); + #if LWIP_MDNS_RESPONDER + mdns_resp_init(); + #endif + } + systick_enable_dispatch(SYSTICK_DISPATCH_LWIP, mod_network_lwip_poll_wrapper); + #endif + #if MICROPY_PY_BLUETOOTH + mp_bluetooth_hci_init(); + #endif + + #if MICROPY_PY_NETWORK_CYW43 + if (first_soft_reset) { + cyw43_init(&cyw43_state); + uint8_t buf[8]; + memcpy(&buf[0], "PYBD", 4); + mp_hal_get_mac_ascii(MP_HAL_MAC_WLAN0, 8, 4, (char *)&buf[4]); + cyw43_wifi_ap_set_ssid(&cyw43_state, 8, buf); + cyw43_wifi_ap_set_password(&cyw43_state, 8, (const uint8_t *)"pybd0123"); + } + #endif + + #if MICROPY_PY_NETWORK + mod_network_init(); + #endif + + #if MICROPY_PY_SENSOR + if (first_soft_reset + && sensor_init() != 0) { + printf("sensor init failed!\n"); + } + #endif + + // Mount or create a fresh filesystem. + mp_obj_t mount_point = MP_OBJ_NEW_QSTR(MP_QSTR__slash_); + #if MICROPY_PY_MACHINE_SDCARD + if (sdcard_detect(&mimxrt_sdcard_objs[MICROPY_HW_SDCARD_SDMMC - 1])) { + mp_obj_t args[] = { MP_OBJ_NEW_SMALL_INT(MICROPY_HW_SDCARD_SDMMC) }; + mp_obj_t bdev = MP_OBJ_TYPE_GET_SLOT(&machine_sdcard_type, make_new)(&machine_sdcard_type, 1, 0, args); + if (mp_vfs_mount_and_chdir_protected(bdev, mount_point) == 0) { + mimxrt_msc_medium = &machine_sdcard_type; + sdcard_mounted = true; + } + } + #endif + + if (sdcard_mounted == false) { + mp_obj_t bdev = MP_OBJ_TYPE_GET_SLOT(&mimxrt_flash_type, make_new)(&mimxrt_flash_type, 0, 0, NULL); + if (mp_vfs_mount_and_chdir_protected(bdev, mount_point) == 0) { + mimxrt_msc_medium = &mimxrt_flash_type; + } else { + // Create a fresh filesystem. + fs_user_mount_t *vfs = MP_OBJ_TYPE_GET_SLOT(&mp_fat_vfs_type, make_new)(&mp_fat_vfs_type, 1, 0, &bdev); + if (factoryreset_create_filesystem(vfs) == 0) { + if (mp_vfs_mount_and_chdir_protected(bdev, mount_point) == 0) { + mimxrt_msc_medium = &mimxrt_flash_type; + } + } + } + } + + // Deferred tusb_init to allow the filesystem to be mounted/created before MSC. + if (!tusb_inited()) { + tusb_init(); + } + + // Execute user scripts. + exec_boot_script("boot.py", false); + + if (first_soft_reset) { + exec_boot_script("main.py", true); + } + + // If there's no script ready, just re-exec REPL + while (!usbdbg_script_ready()) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + // enable IDE interrupt + usbdbg_set_irq_enabled(true); + // run REPL + if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) { + if (pyexec_raw_repl() != 0) { + break; + } + } else { + if (pyexec_friendly_repl() != 0) { + break; + } + } + nlr_pop(); + } + } + + if (usbdbg_script_ready()) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + // Enable IDE interrupt + usbdbg_set_irq_enabled(true); + // Execute the script. + pyexec_str(usbdbg_get_script(), true); + // Disable IDE interrupts + usbdbg_set_irq_enabled(false); + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } + + if (usbdbg_is_busy() && nlr_push(&nlr) == 0) { + // Enable IDE interrupt + usbdbg_set_irq_enabled(true); + // Wait for the current command to finish. + usbdbg_wait_for_command(1000); + // Disable IDE interrupts + usbdbg_set_irq_enabled(false); + nlr_pop(); + } + } + + mp_printf(MP_PYTHON_PRINTER, "MPY: soft reboot\n"); + machine_pin_irq_deinit(); + #if MICROPY_PY_LWIP + systick_disable_dispatch(SYSTICK_DISPATCH_LWIP); + #endif + #if MICROPY_PY_BLUETOOTH + mp_bluetooth_deinit(); + #endif + #if MICROPY_PY_NETWORK + mod_network_deinit(); + #endif + #if MICROPY_PY_MACHINE_I2S + machine_i2s_deinit_all(); + #endif + machine_pwm_deinit_all(); + soft_timer_deinit(); + gc_sweep_all(); + mp_deinit(); + first_soft_reset = false; + goto soft_reset; + return 0; +} + +void gc_collect(void) { + gc_collect_start(); + gc_helper_collect_regs_and_stack(); + gc_collect_end(); +} + +void nlr_jump_fail(void *val) { + for (;;) { + } +} + +void __fatal_error() +{ + nlr_jump_fail(NULL); +} + + +#ifndef NDEBUG +void MP_WEAK __assert_func(const char *file, int line, const char *func, const char *expr) { + mp_printf(MP_PYTHON_PRINTER, "Assertion '%s' failed, at file %s:%d\n", expr, file, line); + for (;;) { + } +} +#endif + +const char mimxrt_help_text[] = + "Welcome to MicroPython!\n" + "\n" + "For online help please visit https://micropython.org/help/.\n" + "\n" + "For access to the hardware use the 'machine' module. \n" + "\n" + "Quick overview of some objects:\n" + " machine.Pin(pin) -- get a pin, eg machine.Pin(0)\n" + " machine.Pin(pin, m, [p]) -- get a pin and configure it for IO mode m, pull mode p\n" + " methods: init(..), value([v]), high(), low())\n" + "\n" + " Pins are numbered board specific, either 0-n, or 'D0'-'Dn', or 'A0' - 'An',\n" + " according to the boards's pinout sheet.\n" + " Pin IO modes are: Pin.IN, Pin.OUT, Pin.OPEN_DRAIN\n" + " Pin pull modes are: Pin.PULL_UP, Pin.PULL_UP_47K, Pin.PULL_UP_22K, Pin.PULL_DOWN, Pin.PULL_HOLD\n" + " machine.ADC(pin) -- make an analog object from a pin\n" + " methods: read_u16()\n" + " machine.UART(id, baudrate=115200) -- create an UART object (id=1 - 8, board-specific)\n" + " methods: init(), write(buf), any()\n" + " buf=read(n), readinto(buf), buf=readline()\n" + " The RX and TX pins are fixed and board-specific.\n" + " machine.SoftI2C() -- create a Soft I2C object\n" + " machine.I2C(id) -- create a HW I2C object\n" + " methods: readfrom(addr, buf, stop=True), writeto(addr, buf, stop=True)\n" + " readfrom_mem(addr, memaddr, arg), writeto_mem(addr, memaddr, arg)\n" + " SoftI2C allows to use any pin for sda and scl, HW I2C id's and pins are fixed\n" + " machine.SoftSPI(baudrate=1000000) -- create a Soft SPI object\n" + " machine.SPI(id, baudrate=1000000) -- create a HW SPI object\n" + " methods: read(nbytes, write=0x00), write(buf), write_readinto(wr_buf, rd_buf)\n" + " SoftSPI allows to use any pin for SPI, HW SPI id's and pins are fixed\n" + " machine.Timer(id, freq, callback) -- create a hardware timer object (id=0,1,2)\n" + " eg: machine.Timer(freq=1, callback=lambda t:print(t))\n" + " machine.RTC() -- create a Real Time Clock object\n" + " methods: init(), datetime([dateime_tuple]), now()\n" + " machine.PWM(pin, freq, duty_u16[, kw_opts]) -- create a PWM object\n" + " methods: init(), duty_u16([value]), duty_ns([value]), freq([value])\n" + "\n" + "Useful control commands:\n" + " CTRL-C -- interrupt a running program\n" + " CTRL-D -- on a blank line, do a soft reset of the board\n" + " CTRL-E -- on a blank line, enter paste mode\n" + "\n" + "For further help on a specific object, type help(obj)\n" + "For a list of available modules, type help('modules')\n" +; diff --git a/src/omv/ports/mimxrt/mimxrt.ld.S b/src/omv/ports/mimxrt/mimxrt.ld.S new file mode 100644 index 000000000..04bd16a29 --- /dev/null +++ b/src/omv/ports/mimxrt/mimxrt.ld.S @@ -0,0 +1,250 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Linker script for MIMXRT Devices. + */ + +/* Entry Point */ +ENTRY(Reset_Handler) + +#include "omv_boardconfig.h" + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx): ORIGIN = OMV_FLASH_ORIGIN, LENGTH = OMV_FLASH_LENGTH + FLASH_FCB (RX): ORIGIN = OMV_FLASH_APP_ORIGIN + 0x0000, LENGTH = 0x00001000 + FLASH_IVT (RX): ORIGIN = OMV_FLASH_APP_ORIGIN + 0x1000, LENGTH = 0x00001000 + FLASH_ISR (RX): ORIGIN = OMV_FLASH_APP_ORIGIN + 0x2000, LENGTH = 0x00001000 + FLASH_TEXT (RX): ORIGIN = OMV_FLASH_TXT_ORIGIN, LENGTH = OMV_FLASH_TXT_LENGTH + FLASH_VFS (RX): ORIGIN = OMV_FLASH_FFS_ORIGIN, LENGTH = OMV_FLASH_FFS_LENGTH + #if defined(OMV_ITCM1_ORIGIN) + ITCM1 (RWX): ORIGIN = OMV_ITCM1_ORIGIN, LENGTH = OMV_ITCM1_LENGTH + #endif + #if defined(OMV_ITCM2_ORIGIN) + ITCM2 (RWX): ORIGIN = OMV_ITCM2_ORIGIN, LENGTH = OMV_ITCM2_LENGTH + #endif + #if defined(OMV_DTCM_ORIGIN) + DTCM (RWX): ORIGIN = OMV_DTCM_ORIGIN, LENGTH = OMV_DTCM_LENGTH + #endif + #if defined(OMV_OCRM1_ORIGIN) + OCRM1 (RWX): ORIGIN = OMV_OCRM1_ORIGIN, LENGTH = OMV_OCRM1_LENGTH + #endif + #if defined(OMV_OCRM2_ORIGIN) + OCRM2 (RWX): ORIGIN = OMV_OCRM2_ORIGIN, LENGTH = OMV_OCRM2_LENGTH + #endif + #if defined(OMV_DRAM_ORIGIN) + DRAM (RWX): ORIGIN = OMV_DRAM_ORIGIN, LENGTH = OMV_DRAM_LENGTH + #endif +} + +_start = main; +__flash_start = ORIGIN(FLASH); +__sdram_start = ORIGIN(DRAM); +__vfs_start = ORIGIN(FLASH_VFS); +__vfs_end = ORIGIN(FLASH_VFS) + LENGTH(FLASH_VFS); + +// FlexRAM configuration addresses and settings +__iomux_gpr14_adr = 0x400AC038; +__iomux_gpr16_adr = 0x400AC040; +__iomux_gpr17_adr = 0x400AC044; +__iomux_gpr17_value = OMV_FLEXRAM_CONFIG; + +#if defined(OMV_JPEG_MEMORY) +#if !defined(OMV_JPEG_MEMORY_OFFSET) +#define OMV_JPEG_MEMORY_OFFSET (0) +#endif +_jpeg_buf = ORIGIN(OMV_JPEG_MEMORY) + OMV_JPEG_MEMORY_OFFSET; +#endif + +#if defined(OMV_VOSPI_MEMORY) +#if !defined(OMV_VOSPI_MEMORY_OFFSET) +#define OMV_VOSPI_MEMORY_OFFSET (0) +#endif +_vospi_buf = ORIGIN(OMV_VOSPI_MEMORY) + OMV_VOSPI_MEMORY_OFFSET; +#endif + +// Location of filesystem flash storage +// Not used in MIMXRT port. +// _micropy_hw_internal_flash_storage_start = ORIGIN(FLASH_FFS); +// _micropy_hw_internal_flash_storage_end = ORIGIN(FLASH_FFS) + LENGTH(FLASH_FFS); + +/* Define output sections */ +SECTIONS +{ + .fcb : + { + . = ALIGN(4); + __FLASH_BASE = .; + KEEP(* (.boot_hdr.conf)) /* flash config section */ + . = ALIGN(4); + } > FLASH_FCB + + .ivt : + { + . = ALIGN(4); + KEEP(* (.boot_hdr.ivt)) /* ivt section */ + KEEP(* (.boot_hdr.boot_data)) /* boot section */ + KEEP(* (.boot_hdr.dcd_data)) /* dcd section */ + . = ALIGN(4); + } > FLASH_IVT + + /* The startup code goes first into internal RAM */ + .isr : + { + __VECTOR_TABLE = .; + __Vectors = .; + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + . = ALIGN(4); + } > FLASH_ISR + + /* The program code and other data goes into internal RAM */ + .text : + { + . = ALIGN(4); + *(EXCLUDE_FILE(*fsl_flexspi.o *gc.o *vm.o *parse*.o *runtime*.o *map.o *mpirq.o ) .text*) + *(.rodata) + *(.rodata*) + *(.glue_7) + *(.glue_7t) + *(.eh_frame) + KEEP (*(.init)) + KEEP (*(.fini)) + . = ALIGN(4); + } > FLASH_TEXT + + .ARM : + { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } > FLASH_TEXT + + __etext = .; /* define a global symbol at end of code */ + + /* Initialized data sections */ + .data : + { + . = ALIGN(4); + _ram_start = .; + __DATA_RAM = .; + __data_start__ = .; /* create a global symbol at data start */ + *(m_usb_dma_init_data) + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + KEEP(*(.jcr*)) + . = ALIGN(4); + __data_end__ = .; /* define a global symbol at data end */ + } > OMV_MAIN_MEMORY AT> FLASH_TEXT + + .ram_function : + { + . = ALIGN(4); + __ram_function_start__ = .; + *(.ram_functions*) + *(.text*) + . = ALIGN(4); + __ram_function_end__ = .; + } > OMV_RAMFUNC_MEMORY AT> FLASH_TEXT + + /* Uninitialized data section */ + .bss (NOLOAD) : + { + /* This is used by the startup in order to initialize the .bss section */ + . = ALIGN(4); + __START_BSS = .; + __bss_start__ = .; + *(m_usb_dma_noninit_data) + *(.bss) + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; + __bss_end__ = .; + __END_BSS = .; + } > OMV_MAIN_MEMORY + + ._heap (NOLOAD) : + { + . = ALIGN(4); + _heap_start = .; + . = . + OMV_HEAP_SIZE; + + . = ALIGN(4); + _heap_end = .; + + } > OMV_MAIN_MEMORY + + ._stack (NOLOAD) : + { + . = ALIGN(8); + _sstack = .; + . = . + OMV_STACK_SIZE; + + . = ALIGN(8); + _estack = .; + __stack = .; + } >OMV_STACK_MEMORY + + /* Main framebuffer memory */ + .fb_memory (NOLOAD) : + { + . = ALIGN(4); + _fb_base = .; + . += OMV_FB_SIZE; + + _fb_end = .; + . += OMV_FB_ALLOC_SIZE; + + . = ALIGN(4); + _fballoc = .; + . = ALIGN(4); + } >OMV_FB_MEMORY + + #if defined(OMV_FB_OVERLAY_MEMORY) + .fb_overlay_memory (NOLOAD) : + { + . = ALIGN(4); + _fballoc_overlay_start = .; + . = . + OMV_FB_OVERLAY_SIZE; + _fballoc_overlay_end = .; + } >OMV_FB_OVERLAY_MEMORY + #endif + + /* Misc DMA buffers section */ + .dma_memory (NOLOAD) : + { + . = ALIGN(8); + _line_buf = .; // Image line buffer. + . = . + OMV_LINE_BUF_SIZE; + + . = ALIGN(16); + _msc_buf = .; // USB MSC bot data (2K) + . = . + OMV_MSC_BUF_SIZE; + + . = ALIGN(16); + _vfs_buf = .; // VFS sturct + FATFS file buffer (around 624 bytes) + . = . + OMV_VFS_BUF_SIZE; + + . = ALIGN(16); + _fir_lepton_buf = .; // FIR Lepton Packet Double Buffer (328 bytes) + . = . + OMV_FIR_LEPTON_BUF_SIZE; + + #if !defined(OMV_JPEG_MEMORY) + . = ALIGN(16); + _jpeg_buf = .; // IDE JPEG buffer + . = . + OMV_JPEG_BUF_SIZE; + #endif + + . = ALIGN(16); + *(.dma_buffer) + } >OMV_DMA_MEMORY + + .ARM.attributes 0 : { *(.ARM.attributes) } +} diff --git a/src/omv/ports/mimxrt/mimxrt_hal.c b/src/omv/ports/mimxrt/mimxrt_hal.c new file mode 100644 index 000000000..4479bf80b --- /dev/null +++ b/src/omv/ports/mimxrt/mimxrt_hal.c @@ -0,0 +1,254 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * MIMXRT HAL. + */ + +#include "fsl_gpio.h" +#include "fsl_csi.h" +#include "fsl_iomuxc.h" +#include "fsl_clock.h" +#include "fsl_lpuart.h" +#include "fsl_lpi2c.h" +#include "fsl_romapi.h" +#include "fsl_dmamux.h" +#include "fsl_usb_phy.h" +#include "fsl_device_registers.h" +#include CLOCK_CONFIG_H +#include "irq.h" + +#include "omv_boardconfig.h" +// Define pin objects in this file. +#define OMV_GPIO_DEFINE_PINS (1) +#include "omv_gpio.h" +#include "mimxrt_hal.h" + +const uint8_t dcd_data[] = {0}; + +void mimxrt_hal_init() +{ + // Configure and enable clocks. + BOARD_BootClockRUN(); + SystemCoreClockUpdate(); + + // Default priority grouping. + NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4); + + // Configure Systick at 1Hz. + SysTick_Config(SystemCoreClock / 1000); + NVIC_SetPriority(SysTick_IRQn, IRQ_PRI_SYSTICK); + + // Enable I/O clocks. + CLOCK_EnableClock(kCLOCK_Iomuxc); + CLOCK_EnableClock(kCLOCK_IomuxcSnvs); + + // Enable I cache and D cache + SCB_EnableDCache(); + SCB_EnableICache(); + + // SDRAM + #ifdef OMV_SDRAM_SIZE + extern void mimxrt_sdram_init(void); + mimxrt_sdram_init(); + #endif + + // Configure USB physical + usb_phy_config_struct_t phyConfig = { + OMV_USB_PHY_D_CAL, + OMV_USB_PHY_TXCAL45DP, + OMV_USB_PHY_TXCAL45DM, + }; + + if (OMV_USB_PHY_ID == kUSB_ControllerEhci0) { + CLOCK_EnableUsbhs0PhyPllClock(kCLOCK_Usbphy480M, 480000000U); + CLOCK_EnableUsbhs0Clock(kCLOCK_Usb480M, 480000000U); + } else { + CLOCK_EnableUsbhs1PhyPllClock(kCLOCK_Usbphy480M, 480000000U); + CLOCK_EnableUsbhs1Clock(kCLOCK_Usb480M, 480000000U); + } + USB_EhciPhyInit(OMV_USB_PHY_ID, BOARD_XTAL0_CLK_HZ, &phyConfig); + NVIC_SetPriority(USB_OTG1_IRQn, IRQ_PRI_OTG_HS); + + // Configure shared IO multiplexers. + #if defined(OMV_IOMUXC_GPR26_CONFIG) + IOMUXC_GPR->GPR26 = ((IOMUXC_GPR->GPR26 & + (~(OMV_IOMUXC_GPR26_CONFIG))) + | IOMUXC_GPR_GPR26_GPIO_MUX1_GPIO_SEL(0x00U) + ); + #endif + #if defined(OMV_IOMUXC_GPR27_CONFIG) + IOMUXC_GPR->GPR27 = ((IOMUXC_GPR->GPR27 & + (~(OMV_IOMUXC_GPR27_CONFIG))) + | IOMUXC_GPR_GPR27_GPIO_MUX2_GPIO_SEL(0x00U) + ); + #endif + + // Confgure and enable EDMA + edma_config_t edma_config = {0}; + EDMA_GetDefaultConfig(&edma_config); + EDMA_Init(DMA0, &edma_config); +} + +void mimxrt_hal_bootloader() +{ + #if FSL_ROM_HAS_RUNBOOTLOADER_API + // Enter ROM bootloader, with primary image active. + uint32_t arg = 0xEB000000; + ROM_RunBootloader(&arg); + #endif +} + +int mimxrt_hal_csi_init(CSI_Type *inst) +{ + CLOCK_EnableClock(kCLOCK_Csi); + + // Configure DCMI pins. + omv_gpio_config(DCMI_D0_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(DCMI_D1_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(DCMI_D2_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(DCMI_D3_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(DCMI_D4_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(DCMI_D5_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(DCMI_D6_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(DCMI_D7_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + + omv_gpio_config(DCMI_MCLK_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(DCMI_HSYNC_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(DCMI_VSYNC_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(DCMI_PXCLK_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + + // Configure DCMI GPIOs + #if defined(DCMI_RESET_PIN) + omv_gpio_config(DCMI_RESET_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_DOWN, OMV_GPIO_SPEED_LOW, -1); + #endif + #if defined(DCMI_FSYNC_PIN) + omv_gpio_config(DCMI_FSYNC_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_DOWN, OMV_GPIO_SPEED_LOW, -1); + #endif + #if defined(DCMI_POWER_PIN) + omv_gpio_config(DCMI_POWER_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_LOW, -1); + #endif + + // Configure IRQ priority. + NVIC_SetPriority(CSI_IRQn, IRQ_PRI_CSI); + + return 0; +} + +int mimxrt_hal_i2c_init(uint32_t bus_id) +{ + omv_gpio_t scl_pin = NULL; + omv_gpio_t sda_pin = NULL; + + switch (bus_id) { + #if defined(LPI2C1_ID) + case LPI2C1_ID: { + scl_pin = LPI2C1_SCL_PIN; + sda_pin = LPI2C1_SDA_PIN; + break; + } + #endif + #if defined(LPI2C2_ID) + case LPI2C2_ID: { + scl_pin = LPI2C2_SCL_PIN; + sda_pin = LPI2C2_SDA_PIN; + break; + } + #endif + #if defined(LPI2C3_ID) + case LPI2C3_ID: { + scl_pin = LPI2C3_SCL_PIN; + sda_pin = LPI2C3_SDA_PIN; + break; + } + #endif + #if defined(LPI2C4_ID) + case LPI2C4_ID: { + scl_pin = LPI2C4_SCL_PIN; + sda_pin = LPI2C4_SDA_PIN; + break; + } + #endif + default: + return -1; + } + + omv_gpio_config(scl_pin, OMV_GPIO_MODE_ALT_OD, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_config(sda_pin, OMV_GPIO_MODE_ALT_OD, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_LOW, -1); + + return 0; +} + +int mimxrt_hal_spi_init(uint32_t bus_id, bool nss_enable, uint32_t nss_pol) +{ + typedef struct { + omv_gpio_t sclk_pin; + omv_gpio_t miso_pin; + omv_gpio_t mosi_pin; + omv_gpio_t ssel_pin; + } spi_pins_t; + + spi_pins_t spi_pins = { NULL, NULL, NULL, NULL }; + + switch (bus_id) { + #if defined(LPSPI1_ID) + case LPSPI1_ID: { + spi_pins = (spi_pins_t) { LPSPI1_SCLK_PIN, LPSPI1_MISO_PIN, LPSPI1_MOSI_PIN, LPSPI1_SSEL_PIN }; + break; + } + #endif + #if defined(LPSPI4_ID) + case LPSPI4_ID: { + spi_pins = (spi_pins_t) { LPSPI4_SCLK_PIN, LPSPI4_MISO_PIN, LPSPI4_MOSI_PIN, LPSPI4_SSEL_PIN }; + break; + } + #endif + default: + return -1; + } + + omv_gpio_config(spi_pins.sclk_pin, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(spi_pins.miso_pin, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + omv_gpio_config(spi_pins.mosi_pin, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MED, -1); + if (nss_enable) { + omv_gpio_config(spi_pins.ssel_pin, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_MED, -1); + } else { + omv_gpio_config(spi_pins.ssel_pin, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_MED, -1); + if (nss_pol == OMV_SPI_NSS_LOW) { + omv_gpio_write(spi_pins.ssel_pin, 1); + } else { + omv_gpio_write(spi_pins.ssel_pin, 0); + } + } + return 0; +} + +void CSI_IRQHandler(void) +{ + uint32_t csisr = CSI_REG_SR(CSI); + extern void sensor_sof_callback(); + extern void sensor_line_callback(uint32_t); + + // Clear interrupt flags. + CSI_REG_SR(CSI) = csisr; + + if (csisr & CSI_SR_SOF_INT_MASK) { + // Clear the FIFO and re/enable DMA. + CSI_REG_CR3(CSI) |= (CSI_CR3_DMA_REFLASH_RFF_MASK | CSI_CR3_DMA_REQ_EN_RFF_MASK); + sensor_sof_callback(); + } else if (csisr & CSI_SR_DMA_TSF_DONE_FB1_MASK) { + sensor_line_callback(CSI_REG_DMASA_FB1(CSI)); + } else if (csisr & CSI_SR_DMA_TSF_DONE_FB2_MASK) { + sensor_line_callback(CSI_REG_DMASA_FB2(CSI)); + } + + // Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F Store immediate + // overlapping exception return operation might vector to incorrect interrupt + #if defined __CORTEX_M && (__CORTEX_M >= 4U) + __DSB(); + #endif +} diff --git a/src/omv/ports/mimxrt/mimxrt_hal.h b/src/omv/ports/mimxrt/mimxrt_hal.h new file mode 100644 index 000000000..2c9e19b83 --- /dev/null +++ b/src/omv/ports/mimxrt/mimxrt_hal.h @@ -0,0 +1,19 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * MIMXRT HAL. + */ +#ifndef __MIMXRT_HAL_H__ +#define __MIMXRT_HAL_H__ + +void mimxrt_hal_init(); +void mimxrt_hal_bootloader(); +int mimxrt_hal_csi_init(CSI_Type *inst); +int mimxrt_hal_i2c_init(uint32_t bus_id); +int mimxrt_hal_spi_init(uint32_t bus_id, bool nss_enable, uint32_t nss_pol); +#endif //__MIMXRT_HAL_H__ diff --git a/src/omv/ports/mimxrt/omv_gpio.c b/src/omv/ports/mimxrt/omv_gpio.c new file mode 100644 index 000000000..1ecb3d3bd --- /dev/null +++ b/src/omv/ports/mimxrt/omv_gpio.c @@ -0,0 +1,193 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * GPIO port for imxrt. + */ +#include +#include +#include + +#include "py/mphal.h" + +#include "fsl_gpio.h" +#include "fsl_iomuxc.h" + +#include "mimxrt_hal.h" +#include "omv_boardconfig.h" +#include "omv_gpio.h" +#include "common.h" + +#define IOMUXC_SET_REG(base, reg, val) *((volatile uint32_t *) ((((uint32_t) base) << 16) | reg)) = (val) + +extern omv_gpio_irq_descr_t *const gpio_irq_descr_table[MIMXRT_PAD_COUNT]; + +static uint32_t omv_gpio_irq_get_gpio(omv_gpio_t pin) +{ + const imxrt_pad_t *pad = pin->pad; + GPIO_Type *gpio_all[] = GPIO_BASE_PTRS; + + for (size_t i = 1; i < OMV_ARRAY_SIZE(gpio_all); i++) { + if (pad->port == gpio_all[i]) { + return i; + } + } + return 0; +} + +static uint32_t omv_gpio_irq_get_irqn(omv_gpio_t pin) +{ + const imxrt_pad_t *pad = pin->pad; + uint32_t gpio = omv_gpio_irq_get_gpio(pin); + uint8_t low_irqs[] = GPIO_COMBINED_LOW_IRQS; + uint8_t high_irqs[] = GPIO_COMBINED_HIGH_IRQS; + return (pad->pin < 16) ? low_irqs[gpio] : high_irqs[gpio]; +} + +static omv_gpio_irq_descr_t *omv_gpio_irq_get_descr(omv_gpio_t pin) +{ + const imxrt_pad_t *pad = pin->pad; + uint32_t gpio = omv_gpio_irq_get_gpio(pin); + if (gpio && gpio_irq_descr_table[gpio]) { + return &gpio_irq_descr_table[gpio][pad->pin]; + } + return NULL; +} + +static const imxrt_pad_af_t *omv_gpio_find_af(omv_gpio_t pin, uint32_t af) +{ + static const imxrt_pad_af_t af_gpio = {5, 0, 0}; + if (af == 5) { + return &af_gpio; + } + + const imxrt_pad_t *pad = pin->pad; + for (size_t i = 0; i < pad->af_count; i++) { + if (af == pad->af_list[i].idx) { + return &pad->af_list[i]; + } + } + return NULL; +} + +void omv_gpio_config(omv_gpio_t pin, uint32_t mode, uint32_t pull, uint32_t speed, uint32_t af) +{ + const imxrt_pad_t *pad = pin->pad; + af = ((af == -1) ? pin->af : af); + const imxrt_pad_af_t *pad_af = omv_gpio_find_af(pin, af); + + if (pad_af == NULL) { + return; + } + + uint16_t pad_config = (mode | pull | speed) & 0x1FFFF; + uint16_t mux_config = IOMUXC_MUX_CTL(MUX_MODE, af) | IOMUXC_MUX_CTL(SION, pin->sion); + + IOMUXC_SET_REG(pad->iomux_base, pad->mux_register, mux_config); + if (pad_af->input_register) { + // Input register always uses IOMUXC_BASE + uint32_t iomux_base = ((uint32_t) IOMUXC_BASE) >> 16; + IOMUXC_SET_REG(iomux_base, pad_af->input_register, pad_af->input_daisy); + } + IOMUXC_SET_REG(pad->iomux_base, pad->cfg_register, pad_config); + + // If pad was configured for GPIO module, set the pin config. + gpio_pin_config_t pin_config = { 0 }; + switch (mode) { + case OMV_GPIO_MODE_ALT: + case OMV_GPIO_MODE_ALT_OD: { + // No GPIO config. + return; + } + case OMV_GPIO_MODE_INPUT: { + pin_config.direction = kGPIO_DigitalInput; + GPIO_PinInit(pad->port, pad->pin, &pin_config); + break; + } + case OMV_GPIO_MODE_OUTPUT: + case OMV_GPIO_MODE_OUTPUT_OD: { + pin_config.direction = kGPIO_DigitalOutput; + pin_config.outputLogic = 1; + break; + } + case OMV_GPIO_MODE_IT_FALL: { + pin_config.interruptMode = kGPIO_IntFallingEdge; + break; + } + case OMV_GPIO_MODE_IT_RISE: { + pin_config.interruptMode = kGPIO_IntRisingEdge; + break; + } + case OMV_GPIO_MODE_IT_BOTH: { + pin_config.interruptMode = kGPIO_IntRisingOrFallingEdge; + break; + } + } + + GPIO_PinInit(pad->port, pad->pin, &pin_config); +} + +void omv_gpio_deinit(omv_gpio_t pin) +{ + +} + +bool omv_gpio_read(omv_gpio_t pin) +{ + const imxrt_pad_t *pad = pin->pad; + return GPIO_PinRead(pad->port, pad->pin); +} + +void omv_gpio_write(omv_gpio_t pin, bool value) +{ + const imxrt_pad_t *pad = pin->pad; + GPIO_PinWrite(pad->port, pad->pin, value); +} + +void omv_gpio_irq_register(omv_gpio_t pin, omv_gpio_callback_t callback, void *data) +{ + omv_gpio_irq_descr_t *irq_descr = omv_gpio_irq_get_descr(pin); + if (irq_descr) { + irq_descr->enabled = true; + irq_descr->data = data; + irq_descr->callback = callback; + } +} + +void omv_gpio_irq_enable(omv_gpio_t pin, bool enable) +{ + const imxrt_pad_t *pad = pin->pad; + uint32_t irqn = omv_gpio_irq_get_irqn(pin); + omv_gpio_irq_descr_t *irq_descr = omv_gpio_irq_get_descr(pin); + + if (irq_descr == NULL) { + return; + } + + if (!enable) { + GPIO_PortDisableInterrupts(pad->port, 1U << pad->pin); + GPIO_PortClearInterruptFlags(pad->port, ~(0U)); + DisableIRQ(irqn); + } else { + GPIO_PortClearInterruptFlags(pad->port, ~(0U)); + GPIO_PortEnableInterrupts(pad->port, 1U << pad->pin); + NVIC_SetPriority(irqn, IRQ_PRI_EXTINT); + EnableIRQ(irqn); + } + irq_descr->enabled = enable; +} + +void omv_gpio_irq_handler(GPIO_Type *gpio, uint32_t gpio_nr, uint32_t pin_nr) +{ + omv_gpio_irq_descr_t *irq_descr = NULL; + if (gpio_irq_descr_table[gpio_nr]) { + irq_descr = &gpio_irq_descr_table[gpio_nr][pin_nr]; + if (irq_descr->enabled && irq_descr->callback) { + irq_descr->callback(irq_descr->data); + } + } +} diff --git a/src/omv/ports/mimxrt/omv_i2c.c b/src/omv/ports/mimxrt/omv_i2c.c new file mode 100644 index 000000000..83a14b37f --- /dev/null +++ b/src/omv/ports/mimxrt/omv_i2c.c @@ -0,0 +1,309 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * I2C port for mimxrt. + */ + +#include +#include +#include "py/mphal.h" + +#include "fsl_gpio.h" +#include "fsl_lpi2c.h" +#include "fsl_iomuxc.h" +#include CLOCK_CONFIG_H + +#include "omv_portconfig.h" +#include "omv_boardconfig.h" +#include "mimxrt_hal.h" +#include "omv_gpio.h" +#include "omv_i2c.h" + +#define I2C_TIMEOUT (3*1000) +#define I2C_SCAN_TIMEOUT (1*1000) + +typedef struct { + volatile uint32_t flags; + volatile status_t error; +} lpi2c_transfer_status_t; + +typedef enum { + LPI2C_TRANSFER_COMPLETE = (1 << 0), + LPI2C_TRANSFER_ERROR = (1 << 1), +} lpi2c_xfer_flags_t; + +int omv_i2c_init(omv_i2c_t *i2c, uint32_t bus_id, uint32_t speed) +{ + i2c->id = bus_id; + i2c->initialized = false; + + switch (speed) { + case OMV_I2C_SPEED_STANDARD: + i2c->speed = 100 * 1000; ///< 100 kbps + break; + case OMV_I2C_SPEED_FULL: + i2c->speed = 400 * 1000; ///< 400 kbps + break; + case OMV_I2C_SPEED_FAST: + i2c->speed = 1000 * 1000; ///< 1000 kbps + break; + default: + return -1; + } + + switch (bus_id) { + case LPI2C1_ID: { + i2c->inst = LPI2C1; + i2c->scl_pin = LPI2C1_SCL_PIN; + i2c->sda_pin = LPI2C1_SDA_PIN; + break; + } + #if defined(LPI2C2_ID) + case LPI2C2_ID: { + i2c->inst = LPI2C2; + i2c->scl_pin = LPI2C2_SCL_PIN; + i2c->sda_pin = LPI2C2_SDA_PIN; + break; + } + #endif + #if defined(LPI2C3_ID) + case LPI2C3_ID: { + i2c->inst = LPI2C3; + i2c->scl_pin = LPI2C3_SCL_PIN; + i2c->sda_pin = LPI2C3_SDA_PIN; + break; + } + #endif + #if defined(LPI2C4_ID) + case LPI2C4_ID: { + i2c->inst = LPI2C4; + i2c->scl_pin = LPI2C4_SCL_PIN; + i2c->sda_pin = LPI2C4_SDA_PIN; + break; + } + #endif + default: + return -1; + } + + lpi2c_master_config_t lpi2c_config = {0}; + LPI2C_MasterGetDefaultConfig(&lpi2c_config); + + lpi2c_config.ignoreAck = true; + lpi2c_config.baudRate_Hz = i2c->speed; + + mimxrt_hal_i2c_init(bus_id); + LPI2C_MasterInit(i2c->inst, &lpi2c_config, BOARD_BOOTCLOCKRUN_LPI2C_CLK_ROOT); + i2c->initialized = true; + return 0; +} + +int omv_i2c_deinit(omv_i2c_t *i2c) +{ + if (i2c->initialized) { + // TODO + i2c->initialized = false; + } + return 0; +} + +int omv_i2c_scan(omv_i2c_t *i2c, uint8_t *list, uint8_t size) +{ + uint32_t idx = 0; + lpi2c_master_transfer_t xfer = {0}; + xfer.direction = kLPI2C_Write; + xfer.flags = kLPI2C_TransferDefaultFlag; + + for (uint8_t addr=0x20; addr<0x78; addr++) { + xfer.slaveAddress = addr; + if (LPI2C_MasterTransferBlocking(i2c->inst, &xfer) == kStatus_Success) { + if (list == NULL || size == 0) { + return (addr << 1); + } else if (idx < size) { + list[idx++] = (addr << 1); + } else { + break; + } + } + } + return idx; +} + +int omv_i2c_enable(omv_i2c_t *i2c, bool enable) +{ + return 0; +} + +int omv_i2c_gencall(omv_i2c_t *i2c, uint8_t cmd) +{ + int ret = 0; + ret |= omv_i2c_write_bytes(i2c, 0, &cmd, 1, OMV_I2C_XFER_NO_FLAGS); + return ret; + +} + +int omv_i2c_readb(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint8_t *reg_data) +{ + int ret = 0; + ret |= omv_i2c_write_bytes(i2c, slv_addr, ®_addr, 1, OMV_I2C_XFER_NO_FLAGS); + ret |= omv_i2c_read_bytes(i2c, slv_addr, reg_data, 1, OMV_I2C_XFER_NO_FLAGS); + return ret; +} + +int omv_i2c_writeb(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint8_t reg_data) +{ + int ret = 0; + uint8_t buf[] = {reg_addr, reg_data}; + ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 2, OMV_I2C_XFER_NO_FLAGS); + return ret; +} + +int omv_i2c_readb2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint8_t *reg_data) +{ + int ret = 0; + uint8_t buf[] = {(reg_addr >> 8), reg_addr}; + ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 2, OMV_I2C_XFER_NO_STOP); + ret |= omv_i2c_read_bytes(i2c, slv_addr, reg_data, 1, OMV_I2C_XFER_NO_FLAGS); + return ret; +} + +int omv_i2c_writeb2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint8_t reg_data) +{ + int ret = 0; + uint8_t buf[] = {(reg_addr >> 8), reg_addr, reg_data}; + ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 3, OMV_I2C_XFER_NO_FLAGS); + return ret; +} + +int omv_i2c_readw(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint16_t *reg_data) +{ + int ret = 0; + ret |= omv_i2c_write_bytes(i2c, slv_addr, ®_addr, 1, OMV_I2C_XFER_NO_STOP); + ret |= omv_i2c_read_bytes(i2c, slv_addr, (uint8_t *) reg_data, 2, OMV_I2C_XFER_NO_FLAGS); + *reg_data = (*reg_data << 8) | (*reg_data >> 8); + return ret; +} + +int omv_i2c_writew(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t reg_addr, uint16_t reg_data) +{ + int ret = 0; + uint8_t buf[] = {reg_addr, (reg_data >> 8), reg_data}; + ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 3, OMV_I2C_XFER_NO_FLAGS); + return ret; +} + +int omv_i2c_readw2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint16_t *reg_data) +{ + int ret = 0; + uint8_t buf[] = {(reg_addr >> 8), reg_addr}; + ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 2, OMV_I2C_XFER_NO_STOP); + ret |= omv_i2c_read_bytes(i2c, slv_addr, (uint8_t *) reg_data, 2, OMV_I2C_XFER_NO_FLAGS); + *reg_data = (*reg_data << 8) | (*reg_data >> 8); + return ret; +} + +int omv_i2c_writew2(omv_i2c_t *i2c, uint8_t slv_addr, uint16_t reg_addr, uint16_t reg_data) +{ + int ret = 0; + uint8_t buf[] = {(reg_addr >> 8), reg_addr, (reg_data >> 8), reg_data}; + ret |= omv_i2c_write_bytes(i2c, slv_addr, buf, 4, OMV_I2C_XFER_NO_FLAGS); + return ret; +} + +static void lpi2c_transfer_callback(LPI2C_Type *base, + lpi2c_master_handle_t *handle, status_t status, void *data) +{ + lpi2c_transfer_status_t *xfer_status = (lpi2c_transfer_status_t *) data; + xfer_status->flags |= LPI2C_TRANSFER_COMPLETE; + if (status != kStatus_Success) { + xfer_status->flags |= LPI2C_TRANSFER_ERROR; + xfer_status->error = status; + } +} + +static int omv_i2c_transfer_timeout(omv_i2c_t *i2c, lpi2c_master_transfer_t *transfer) +{ + lpi2c_master_handle_t lpi2c_handle; + lpi2c_transfer_status_t xfer_status = {0}; + + // Create the handle, needed for non-blocking mode. + LPI2C_MasterTransferCreateHandle(i2c->inst, &lpi2c_handle, lpi2c_transfer_callback, &xfer_status); + + // Start non-blocking transfer. + if (LPI2C_MasterTransferNonBlocking(i2c->inst, &lpi2c_handle, transfer) != kStatus_Success) { + return -1; + } + + // Wait for the transfer to finish. + mp_uint_t tick_start = mp_hal_ticks_ms(); + while (!(xfer_status.flags & LPI2C_TRANSFER_COMPLETE)) { + if ((mp_hal_ticks_ms() - tick_start) >= I2C_TIMEOUT) { + xfer_status.flags |= LPI2C_TRANSFER_ERROR; + break; + } + MICROPY_EVENT_POLL_HOOK + } + + // Terminate non-blocking transfer. + if (xfer_status.flags & LPI2C_TRANSFER_ERROR) { + LPI2C_MasterTransferAbort(i2c->inst, &lpi2c_handle); + return -1; + } + + return 0; +} + +int omv_i2c_read_bytes(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t *buf, int len, uint32_t flags) +{ + lpi2c_master_transfer_t xfer = { + .data = buf, + .dataSize = len, + .flags = kLPI2C_TransferDefaultFlag, + .direction = kLPI2C_Read, + .subaddress = 0, + .subaddressSize = 0, + .slaveAddress = (slv_addr >> 1) + }; + if (flags & (OMV_I2C_XFER_NO_STOP | OMV_I2C_XFER_SUSPEND)) { + xfer.flags |= kLPI2C_TransferNoStopFlag; + } + return omv_i2c_transfer_timeout(i2c, &xfer); +} + +int omv_i2c_write_bytes(omv_i2c_t *i2c, uint8_t slv_addr, uint8_t *buf, int len, uint32_t flags) +{ + lpi2c_master_transfer_t xfer = { + .data = buf, + .dataSize = len, + .flags = kLPI2C_TransferDefaultFlag, + .direction = kLPI2C_Write, + .subaddress = 0, + .subaddressSize = 0, + .slaveAddress = (slv_addr >> 1) + }; + if (flags & (OMV_I2C_XFER_NO_STOP | OMV_I2C_XFER_SUSPEND)) { + xfer.flags |= kLPI2C_TransferNoStopFlag; + } + return omv_i2c_transfer_timeout(i2c, &xfer); +} + +int omv_i2c_pulse_scl(omv_i2c_t *i2c) +{ + if (i2c->initialized && i2c->scl_pin) { + omv_i2c_deinit(i2c); + omv_gpio_config(i2c->scl_pin, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + // Pulse SCL to recover stuck device. + for (int i=0; i<10000; i++) { + omv_gpio_write(i2c->scl_pin, 1); + mp_hal_delay_us(10); + omv_gpio_write(i2c->scl_pin, 0); + mp_hal_delay_us(10); + } + } + return 0; +} diff --git a/src/omv/ports/mimxrt/omv_portconfig.h b/src/omv/ports/mimxrt/omv_portconfig.h new file mode 100644 index 000000000..c8fdac376 --- /dev/null +++ b/src/omv/ports/mimxrt/omv_portconfig.h @@ -0,0 +1,121 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OpenMV MIMXRT port abstraction layer. + */ +#ifndef __OMV_PORTCONFIG_H__ +#define __OMV_PORTCONFIG_H__ + +#include "fsl_gpio.h" +#include "fsl_lpi2c.h" +#include "fsl_lpspi.h" +#include "fsl_iomuxc.h" +#include "fsl_edma.h" +#include "fsl_lpspi_edma.h" + +#define IOMUXC_PAD_CTL(opt, val) (IOMUXC_SW_PAD_CTL_PAD_##opt(val)) +#define IOMUXC_MUX_CTL(opt, val) (IOMUXC_SW_MUX_CTL_PAD_##opt(val)) + +// omv_gpio_t definitions + +// GPIO speeds. +#define OMV_GPIO_SPEED_LOW ((1 << 17) | IOMUXC_PAD_CTL(SPEED, 0U) | IOMUXC_PAD_CTL(SRE, 0U)) // 50MHz +#define OMV_GPIO_SPEED_MED ((2 << 17) | IOMUXC_PAD_CTL(SPEED, 1U) | IOMUXC_PAD_CTL(SRE, 0U)) // 100MHz +#define OMV_GPIO_SPEED_HIGH ((3 << 17) | IOMUXC_PAD_CTL(SPEED, 2U) | IOMUXC_PAD_CTL(SRE, 1U)) // 150MHz +#define OMV_GPIO_SPEED_MAX ((4 << 17) | IOMUXC_PAD_CTL(SPEED, 3U) | IOMUXC_PAD_CTL(SRE, 1U)) // 200MHz + +// GPIO pull. +#define OMV_GPIO_PULL_NONE ((1 << 17) | IOMUXC_PAD_CTL(PKE, 0U) | IOMUXC_PAD_CTL(PUE, 0) | IOMUXC_PAD_CTL(PUS, 0)) +#define OMV_GPIO_PULL_UP ((2 << 17) | IOMUXC_PAD_CTL(PKE, 1U) | IOMUXC_PAD_CTL(PUE, 1) | IOMUXC_PAD_CTL(PUS, 2)) +#define OMV_GPIO_PULL_DOWN ((3 << 17) | IOMUXC_PAD_CTL(PKE, 1U) | IOMUXC_PAD_CTL(PUE, 1) | IOMUXC_PAD_CTL(PUS, 0)) + +// GPIO modes. +#define OMV_GPIO_MODE_INPUT ((1 << 17) | IOMUXC_PAD_CTL(DSE, 0U) | IOMUXC_PAD_CTL(HYS, 1U) | IOMUXC_PAD_CTL(ODE, 0U)) +#define OMV_GPIO_MODE_OUTPUT ((2 << 17) | IOMUXC_PAD_CTL(DSE, 6U) | IOMUXC_PAD_CTL(HYS, 0U) | IOMUXC_PAD_CTL(ODE, 0U)) +#define OMV_GPIO_MODE_OUTPUT_OD ((3 << 17) | IOMUXC_PAD_CTL(DSE, 6U) | IOMUXC_PAD_CTL(HYS, 0U) | IOMUXC_PAD_CTL(ODE, 1U)) +#define OMV_GPIO_MODE_ALT ((4 << 17) | IOMUXC_PAD_CTL(DSE, 6U) | IOMUXC_PAD_CTL(HYS, 0U) | IOMUXC_PAD_CTL(ODE, 0U)) +#define OMV_GPIO_MODE_ALT_OD ((5 << 17) | IOMUXC_PAD_CTL(DSE, 6U) | IOMUXC_PAD_CTL(HYS, 0U) | IOMUXC_PAD_CTL(ODE, 1U)) + +// GPIO IT modes. +#define OMV_GPIO_MODE_IT_FALL ((6 << 17) | IOMUXC_PAD_CTL(DSE, 0U) | IOMUXC_PAD_CTL(HYS, 1U)) +#define OMV_GPIO_MODE_IT_RISE ((7 << 17) | IOMUXC_PAD_CTL(DSE, 0U) | IOMUXC_PAD_CTL(HYS, 1U)) +#define OMV_GPIO_MODE_IT_BOTH ((8 << 17) | IOMUXC_PAD_CTL(DSE, 0U) | IOMUXC_PAD_CTL(HYS, 1U)) + +typedef struct { + uint8_t idx; + uint16_t input_register; + uint8_t input_daisy; +} imxrt_pad_af_t; + +typedef struct { + GPIO_Type *port; + uint8_t pin; + uint16_t iomux_base; + uint16_t mux_register; + uint16_t cfg_register; + uint8_t af_count; + const imxrt_pad_af_t *af_list; +} imxrt_pad_t; +#include "mimxrt_pads.h" + +typedef struct _omv_gpio { + const imxrt_pad_t *pad; // IMXRT pad. + uint8_t af; // Default AF. + uint8_t sion; // Software input on. +} imxrt_gpio_t; + +typedef const imxrt_gpio_t *omv_gpio_t; + +// For board config files. +#if OMV_GPIO_DEFINE_PINS +#define OMV_GPIO_DEFINE(pin, pad, af, sion) \ + const imxrt_gpio_t omv_pin_##pin = {&imxrt_pad_##pad, pad##_AF_##af, sion}; +#else +#define OMV_GPIO_DEFINE(pin, pad, af, sion) \ + extern const imxrt_gpio_t omv_pin_##pin; +#endif +#include "omv_pins.h" + +// omv_i2c_t definitions +typedef LPI2C_Type *omv_i2c_dev_t; + +// omv_spi_t definitions +#define OMV_SPI_MODE_SLAVE (0) +#define OMV_SPI_MODE_MASTER (1) + +#define OMV_SPI_LSB_FIRST (kLPSPI_LsbFirst) +#define OMV_SPI_MSB_FIRST (kLPSPI_MsbFirst) + +#define OMV_SPI_BUS_TX (1 << 0) +#define OMV_SPI_BUS_RX (1 << 1) +#define OMV_SPI_BUS_TX_RX (OMV_SPI_BUS_TX | OMV_SPI_BUS_RX) + +#define OMV_SPI_CPOL_LOW (kLPSPI_ClockPolarityActiveLow) +#define OMV_SPI_CPOL_HIGH (kLPSPI_ClockPolarityActiveHigh) + +#define OMV_SPI_CPHA_1EDGE (kLPSPI_ClockPhaseFirstEdge) +#define OMV_SPI_CPHA_2EDGE (kLPSPI_ClockPhaseSecondEdge) + +#define OMV_SPI_NSS_LOW (kLPSPI_PcsActiveLow) +#define OMV_SPI_NSS_HIGH (kLPSPI_PcsActiveHigh) + +#define OMV_SPI_PORT_BITS \ +struct { \ + LPSPI_Type *inst; \ + edma_handle_t dma_descr_tx; \ + edma_handle_t dma_descr_rx; \ + union { \ + lpspi_slave_handle_t descr_slave; \ + lpspi_master_handle_t descr_master; \ + lpspi_slave_edma_handle_t descr_slave_edma; \ + lpspi_master_edma_handle_t descr_master_edma; \ + }; \ + lpspi_transfer_t xfer_descr; \ +}; + +#endif // __OMV_PORTCONFIG_H__ diff --git a/src/omv/ports/mimxrt/omv_portconfig.mk b/src/omv/ports/mimxrt/omv_portconfig.mk new file mode 100644 index 000000000..f5b0966c5 --- /dev/null +++ b/src/omv/ports/mimxrt/omv_portconfig.mk @@ -0,0 +1,637 @@ +# Set startup and system files based on MCU. +SYSTEM ?= mimxrt/system_$(MCU_SERIES) +STARTUP ?= mimxrt/startup_$(MCU_SERIES) +LDSCRIPT ?= mimxrt + +# Compiler Flags +# TODO: -Wdouble-promotion +CFLAGS += -std=gnu99 -Wall -Werror -Warray-bounds -nostartfiles -fdata-sections -ffunction-sections \ + -fno-inline-small-functions -mfloat-abi=$(FABI) -mthumb -mcpu=$(CPU) -mtune=$(CPU) -mfpu=$(FPU) +# TODO: FIX HSE +CFLAGS += -DCPU_$(MCU_VARIANT)\ + -D$(TARGET)\ + -D$(ARM_MATH) -DARM_NN_TRUNCATE\ + -D__FPU_PRESENT=1 -D__VFP_FP__\ + -DHSE_VALUE=$(OMV_HSE_VALUE) \ + -DMICROPY_PY_MACHINE_SDCARD=1 \ + -DXIP_EXTERNAL_FLASH=1 \ + -DXIP_BOOT_HEADER_ENABLE=1 \ + -DFSL_SDK_ENABLE_DRIVER_CACHE_CONTROL=1 \ + -DCFG_TUSB_MCU=OPT_MCU_MIMXRT \ + -DCPU_HEADER_H='<$(MCU_SERIES).h>' \ + -DCLOCK_CONFIG_H='' \ + -DCSI_DRIVER_FRAG_MODE=1\ + -D__START=main\ + -D__STARTUP_CLEAR_BSS\ + -D__STARTUP_INITIALIZE_RAMFUNCTION\ + $(OMV_BOARD_EXTRA_CFLAGS)\ + +HAL_CFLAGS += -I$(TOP_DIR)/$(CMSIS_DIR)/include/ +HAL_CFLAGS += -I$(TOP_DIR)/$(CMSIS_DIR)/include/mimxrt +HAL_CFLAGS += -I$(TOP_DIR)/$(HAL_DIR)/ +HAL_CFLAGS += -I$(TOP_DIR)/$(HAL_DIR)/drivers + +MPY_CFLAGS += -I$(MP_BOARD_CONFIG_DIR) +MPY_CFLAGS += -I$(BUILD)/$(MICROPY_DIR)/ +MPY_CFLAGS += -I$(TOP_DIR)/$(MICROPY_DIR)/ +MPY_CFLAGS += -I$(TOP_DIR)/$(MICROPY_DIR)/py/ +MPY_CFLAGS += -I$(TOP_DIR)/$(MICROPY_DIR)/lib/oofatfs +MPY_CFLAGS += -I$(TOP_DIR)/$(MICROPY_DIR)/lib/tinyusb/src +MPY_CFLAGS += -I$(TOP_DIR)/$(MICROPY_DIR)/lib/lwip/src/include/ +MPY_CFLAGS += -I$(TOP_DIR)/$(MICROPY_DIR)/ports/mimxrt/ +MPY_CFLAGS += -I$(TOP_DIR)/$(MICROPY_DIR)/ports/mimxrt/lwip_inc/ + +ifeq ($(MICROPY_PY_LWIP), 0) +MICROPY_ARGS += MICROPY_PY_LWIP=0 MICROPY_PY_USSL=0 +else +MPY_CFLAGS += -DMICROPY_PY_USSL=1 -DMICROPY_SSL_MBEDTLS=1 +MICROPY_ARGS += MICROPY_PY_USSL=1 MICROPY_SSL_MBEDTLS=1 +endif +MICROPY_ARGS += MCU_DIR=$(TOP_DIR)/$(HAL_DIR) CMSIS_DIR=$(TOP_DIR)/$(CMSIS_DIR)\ + SUPPORTS_HARDWARE_FP_SINGLE=1 MICROPY_VFS_LFS2=0 + +OMV_CFLAGS += -I$(OMV_BOARD_CONFIG_DIR) +OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/ +OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/alloc/ +OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/common/ +OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/imlib/ +OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/modules/ +OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/sensors/ +OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/templates/ +OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/ports/$(PORT)/ +OMV_CFLAGS += -I$(TOP_DIR)/$(OMV_DIR)/ports/$(PORT)/modules/ + +OMV_CFLAGS += -I$(TOP_DIR)/$(LEPTON_DIR)/include/ +OMV_CFLAGS += -I$(TOP_DIR)/$(LSM6DS3_DIR)/include/ +OMV_CFLAGS += -I$(TOP_DIR)/$(LSM6DSOX_DIR)/include/ +OMV_CFLAGS += -I$(TOP_DIR)/$(VL53L5CX_DIR)/include/ +OMV_CFLAGS += -I$(TOP_DIR)/$(WINC1500_DIR)/include/ +OMV_CFLAGS += -I$(TOP_DIR)/$(MLX90621_DIR)/include/ +OMV_CFLAGS += -I$(TOP_DIR)/$(MLX90640_DIR)/include/ +OMV_CFLAGS += -I$(TOP_DIR)/$(MLX90641_DIR)/include/ +OMV_CFLAGS += -I$(TOP_DIR)/$(PIXART_DIR)/include/ +OMV_CFLAGS += -I$(TOP_DIR)/$(TENSORFLOW_DIR)/ +OMV_CFLAGS += -I$(BUILD)/$(TENSORFLOW_DIR)/ +OMV_CFLAGS += -I$(TOP_DIR)/$(LIBPDM_DIR)/ + +ifeq ($(OMV_ENABLE_UVC), 1) +UVC_CFLAGS := $(CFLAGS) $(HAL_CFLAGS) +UVC_CFLAGS += -I$(OMV_BOARD_CONFIG_DIR) +UVC_CFLAGS += -I$(TOP_DIR)/$(UVC_DIR)/include/ +UVC_CFLAGS += $(OMV_CFLAGS) $(MPY_CFLAGS) +# Linker Flags +UVC_LDFLAGS = -mcpu=$(CPU) -mabi=aapcs-linux -mthumb -mfpu=$(FPU) -mfloat-abi=hard -nostdlib \ + -Wl,--print-memory-usage -Wl,--gc-sections -Wl,-T$(BUILD)/$(UVC_DIR)/$(LD_SCRIPT).lds +endif + +CFLAGS += $(HAL_CFLAGS) $(MPY_CFLAGS) $(OMV_CFLAGS) +# Linker Flags +LDFLAGS = -mcpu=$(CPU) -mabi=aapcs-linux -mthumb -mfpu=$(FPU) -mfloat-abi=hard -nostdlib \ + -Wl,--print-memory-usage -Wl,--gc-sections -Wl,-T$(BUILD)/$(LDSCRIPT).lds + +#------------- Libraries ----------------# +LIBS += $(TOP_DIR)/$(TENSORFLOW_DIR)/$(CPU)/libtf*.a +ifeq ($(MICROPY_PY_AUDIO), 1) +LIBS += $(TOP_DIR)/$(LIBPDM_DIR)/libPDMFilter_CM7_GCC_wc32.a +endif + +#------------- Firmware Objects ----------------# +FIRM_OBJ += $(wildcard $(BUILD)/$(CMSIS_DIR)/src/dsp/CommonTables/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(CMSIS_DIR)/src/dsp/FastMathFunctions/*.o) + +FIRM_OBJ += $(wildcard $(BUILD)/$(HAL_DIR)/drivers/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(LEPTON_DIR)/src/*.o) +ifeq ($(MICROPY_PY_IMU), 1) +FIRM_OBJ += $(wildcard $(BUILD)/$(LSM6DS3_DIR)/src/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(LSM6DSOX_DIR)/src/*.o) +endif +FIRM_OBJ += $(wildcard $(BUILD)/$(MLX90621_DIR)/src/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(MLX90640_DIR)/src/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(MLX90641_DIR)/src/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(VL53L5CX_DIR)/src/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(PIXART_DIR)/src/*.o) + +#------------- OpenMV Objects ----------------# +FIRM_OBJ += $(addprefix $(BUILD)/$(CMSIS_DIR)/src/, \ + $(STARTUP).o \ + $(SYSTEM).o \ + ) + +FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/alloc/, \ + xalloc.o \ + fb_alloc.o \ + umm_malloc.o \ + unaligned_memcpy.o \ + ) + +FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/common/, \ + array.o \ + ff_wrapper.o \ + ini.o \ + ringbuf.o \ + trace.o \ + mutex.o \ + vospi.o \ + usbdbg.o \ + tinyusb_debug.o \ + sensor_utils.o \ + factoryreset.o \ + ) + +FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/sensors/, \ + ov2640.o \ + ov5640.o \ + ov7670.o \ + ov7690.o \ + ov7725.o \ + ov9650.o \ + mt9v0xx.o \ + mt9m114.o \ + lepton.o \ + hm01b0.o \ + hm0360.o \ + gc2145.o \ + paj6100.o \ + frogeye2020.o \ + ) + +FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/imlib/, \ + agast.o \ + apriltag.o \ + bayer.o \ + binary.o \ + blob.o \ + bmp.o \ + clahe.o \ + collections.o \ + dmtx.o \ + draw.o \ + edge.o \ + eye.o \ + fast.o \ + fft.o \ + filter.o \ + fmath.o \ + font.o \ + framebuffer.o \ + fsort.o \ + gif.o \ + haar.o \ + hog.o \ + hough.o \ + imlib.o \ + integral.o \ + integral_mw.o \ + jpegd.o \ + jpeg.o \ + lodepng.o \ + png.o \ + kmeans.o \ + lab_tab.o \ + lbp.o \ + line.o \ + lsd.o \ + mathop.o \ + mjpeg.o \ + orb.o \ + phasecorrelation.o \ + point.o \ + pool.o \ + ppm.o \ + qrcode.o \ + qsort.o \ + rainbow_tab.o \ + rectangle.o \ + selective_search.o \ + sincos_tab.o \ + stats.o \ + stereo.o \ + template.o \ + xyz_tab.o \ + yuv.o \ + zbar.o \ + ) + +FIRM_OBJ += $(wildcard $(BUILD)/$(TENSORFLOW_DIR)/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(OMV_DIR)/ports/$(PORT)/*.o) + +#------------- MicroPy Objects -------------------# +FIRM_OBJ += $(wildcard $(BUILD)/$(MICROPY_DIR)/modules/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(MICROPY_DIR)/ports/$(PORT)/modules/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(MICROPY_DIR)/py/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(MICROPY_DIR)/boards/$(TARGET)/*.o) + +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/,\ + boards/$(MCU_SERIES)_clock_config.o \ + dma_manager.o \ + eth.o \ + fatfs_port.o \ + frozen_content.o \ + flash.o \ + led.o \ + machine_adc.o \ + machine_bitstream.o \ + machine_i2c.o \ + machine_i2s.o \ + machine_led.o \ + machine_pin.o \ + machine_rtc.o \ + machine_sdcard.o \ + machine_spi.o \ + machine_uart.o \ + machine_wdt.o \ + mbedtls/mbedtls_port.o \ + mimxrt_flash.o \ + mimxrt_sdram.o \ + modmachine.o \ + modmimxrt.o \ + modutime.o \ + mpnetworkport.o \ + msc_disk.o \ + network_lan.o \ + mphalport.o \ + pendsv.o \ + pin.o \ + pins_gen.o \ + sdcard.o \ + sdio.o \ + systick.o \ + ticks.o \ + tusb_port.o \ + ) + +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/hal/,\ + pwm_backport.o \ + flexspi_nor_flash.o \ + qspi_nor_flash_config.o \ + ) + +ifeq ($(MICROPY_PY_LWIP), 1) +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/hal/,\ + phy/mdio/enet/fsl_enet_mdio.o \ + phy/device/phydp83825/fsl_phydp83825.o \ + phy/device/phydp83848/fsl_phydp83848.o \ + phy/device/phyksz8081/fsl_phyksz8081.o \ + phy/device/phylan8720/fsl_phylan8720.o \ + phy/device/phyrtl8211f/fsl_phyrtl8211f.o\ + ) +endif + + +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/shared/,\ + libc/printf.o \ + libc/string0.o \ + netutils/netutils.o \ + netutils/trace.o \ + netutils/dhcpserver.o \ + runtime/gchelper_thumb2.o \ + runtime/gchelper_native.o \ + runtime/interrupt_char.o \ + runtime/mpirq.o \ + runtime/pyexec.o \ + runtime/stdout_helpers.o \ + runtime/sys_stdio_mphal.o \ + runtime/softtimer.o \ + timeutils/timeutils.o \ + readline/readline.o \ + ) + +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/lib/libm/,\ + math.o \ + roundf.o \ + asinfacosf.o \ + atanf.o \ + atan2f.o \ + fmodf.o \ + log1pf.o \ + acoshf.o \ + asinhf.o \ + atanhf.o \ + kf_rem_pio2.o \ + kf_sin.o \ + kf_cos.o \ + kf_tan.o \ + ef_rem_pio2.o \ + erf_lgamma.o \ + sf_sin.o \ + sf_cos.o \ + sf_tan.o \ + sf_frexp.o \ + sf_modf.o \ + sf_ldexp.o \ + sf_erf.o \ + wf_lgamma.o \ + wf_tgamma.o \ + nearbyintf.o \ + thumb_vfp_sqrtf.o \ + ) + +FIRM_OBJ += $(wildcard $(BUILD)/$(MICROPY_DIR)/lib/mbedtls/library/*.o) +FIRM_OBJ += $(wildcard $(BUILD)/$(MICROPY_DIR)/lib/mbedtls_errors/*.o) + +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/lib/tinyusb/src/, \ + class/cdc/cdc_device.o \ + class/dfu/dfu_rt_device.o \ + class/hid/hid_device.o \ + class/midi/midi_device.o \ + class/msc/msc_device.o \ + class/usbtmc/usbtmc_device.o \ + class/vendor/vendor_device.o \ + common/tusb_fifo.o \ + device/usbd.o \ + device/usbd_control.o \ + tusb.o \ + portable/chipidea/ci_hs/dcd_ci_hs.o \ + ) + +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/extmod/,\ + machine_bitstream.o \ + machine_i2c.o \ + machine_mem.o \ + machine_pinbase.o \ + machine_pulse.o \ + machine_pwm.o \ + machine_signal.o \ + machine_spi.o \ + machine_timer.o \ + modframebuf.o \ + modnetwork.o \ + modonewire.o \ + moduasyncio.o \ + modubinascii.o \ + moducryptolib.o \ + moductypes.o \ + moduhashlib.o \ + moduheapq.o \ + modujson.o \ + moduos.o \ + moduplatform.o \ + modurandom.o \ + modure.o \ + moduselect.o \ + modusocket.o \ + modussl_axtls.o \ + modussl_mbedtls.o \ + modutimeq.o \ + moduzlib.o \ + uos_dupterm.o \ + utime_mphal.o \ + vfs_blockdev.o \ + vfs_fat_diskio.o \ + vfs_fat_file.o \ + vfs_fat.o \ + vfs.o \ + vfs_posix_file.o \ + vfs_posix.o \ + vfs_reader.o \ + virtpin.o \ + ) + +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/lib/oofatfs/,\ + ff.o \ + ffunicode.o \ + ) + +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/drivers/,\ + bus/softspi.o \ + dht/dht.o \ + ) + +ifeq ($(MICROPY_PY_ULAB), 1) +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/modules/ulab/,\ + code/ndarray.o \ + code/ndarray_operators.o \ + code/ndarray_properties.o \ + code/numpy/approx.o \ + code/numpy/carray/carray.o \ + code/numpy/carray/carray_tools.o \ + code/numpy/compare.o \ + code/numpy/create.o \ + code/numpy/fft/fft.o \ + code/numpy/fft/fft_tools.o \ + code/numpy/filter.o \ + code/numpy/io/io.o \ + code/numpy/linalg/linalg.o \ + code/numpy/linalg/linalg_tools.o \ + code/numpy/ndarray/ndarray_iter.o \ + code/numpy/numerical.o \ + code/numpy/numpy.o \ + code/numpy/poly.o \ + code/numpy/stats.o \ + code/numpy/transform.o \ + code/numpy/vector.o \ + code/scipy/linalg/linalg.o \ + code/scipy/optimize/optimize.o \ + code/scipy/scipy.o \ + code/scipy/signal/signal.o \ + code/scipy/special/special.o \ + code/ulab.o \ + code/ulab_tools.o \ + code/user/user.o \ + code/utils/utils.o \ + ) +endif + +ifeq ($(MICROPY_PY_LWIP), 1) +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/,\ + lib/lwip/src/core/*.o \ + lib/lwip/src/core/*/*.o \ + lib/lwip/src/netif/*.o \ + lib/lwip/src/apps/*/*.o \ + extmod/modlwip.o \ + extmod/moduwebsocket.o \ + extmod/modwebrepl.o \ + extmod/network_lwip.o \ + ) +endif + +ifeq ($(MICROPY_PY_NETWORK_CYW43), 1) +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/,\ + lib/cyw43-driver/src/*.o \ + extmod/network_cyw43.o \ + ) +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/drivers/,\ + cyw43/cywbt.o \ + ) +endif + +ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1) +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/lib/mynewt-nimble/,\ + ext/tinycrypt/src/*.o \ + nimble/host/services/gap/src/*.o \ + nimble/host/services/gatt/src/*.o \ + nimble/host/src/*.o \ + nimble/host/util/src/*.o \ + nimble/transport/uart/src/*.o \ + porting/nimble/src/*.o \ + ) + +FIRM_OBJ += $(addprefix $(BUILD)/$(MICROPY_DIR)/,\ + mpbthciport.o \ + mpnimbleport.o \ + extmod/nimble/modbluetooth_nimble.o \ + extmod/nimble/nimble/nimble_npl_os.o \ + extmod/nimble/hal/hal_uart.o \ + extmod/modbluetooth.o \ + ) +endif + +ifeq ($(OMV_ENABLE_UVC), 1) +UVC = uvc +# UVC object files +UVC_OBJ += $(wildcard $(BUILD)/$(UVC_DIR)/src/*.o) +UVC_OBJ += $(wildcard $(BUILD)/$(HAL_DIR)/drivers/*.o) +UVC_OBJ += $(addprefix $(BUILD)/$(CMSIS_DIR)/src/,\ + $(STARTUP).o \ + $(SYSTEM).o \ + ) + +UVC_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/alloc/, \ + fb_alloc.o \ + unaligned_memcpy.o \ + ) + +UVC_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/common/, \ + array.o \ + trace.o \ + mutex.o \ + vospi.o \ + sensor_utils.o \ + ) + +UVC_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/sensors/, \ + ov2640.o \ + ov5640.o \ + ov7690.o \ + ov7670.o \ + ov7725.o \ + ov9650.o \ + mt9v0xx.o \ + mt9m114.o \ + lepton.o \ + hm01b0.o \ + hm0360.o \ + gc2145.o \ + paj6100.o \ + frogeye2020.o \ + ) + +UVC_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/imlib/,\ + lab_tab.o \ + xyz_tab.o \ + rainbow_tab.o \ + jpeg.o \ + fmath.o \ + imlib.o \ + framebuffer.o \ + ) + +UVC_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/ports/$(PORT),\ + sensor.o \ + soft_i2c.o \ + ulpi.o \ + omv_spi.o \ + omv_i2c.o \ + omv_gpio.o \ + mimxrt_hal.o \ + ) + +UVC_OBJ += $(wildcard $(BUILD)/$(LEPTON_DIR)/src/*.o) +ifeq ($(MICROPY_PY_IMU), 1) +UVC_OBJ += $(wildcard $(BUILD)/$(LSM6DS3_DIR)/src/*.o) +UVC_OBJ += $(wildcard $(BUILD)/$(LSM6DSOX_DIR)/src/*.o) +endif +UVC_OBJ += $(wildcard $(BUILD)/$(MLX90621_DIR)/src/*.o) +UVC_OBJ += $(wildcard $(BUILD)/$(MLX90640_DIR)/src/*.o) +UVC_OBJ += $(wildcard $(BUILD)/$(MLX90641_DIR)/src/*.o) +UVC_OBJ += $(wildcard $(BUILD)/$(VL53L5CX_DIR)/src/*.o) +UVC_OBJ += $(wildcard $(BUILD)/$(PIXART_DIR)/src/*.o) +endif + +################################################### +#Export Variables +export Q +export CC +export AS +export LD +export AR +export SIZE +export OBJCOPY +export OBJDUMP +export MKDIR +export ECHO +export CFLAGS +export AFLAGS +export LDFLAGS +export TOP_DIR +export BUILD +export TOOLS +export TARGET +export STARTUP +export SYSTEM +export FROZEN_MANIFEST +export PORT +export HAL_DIR +export CMSIS_DIR +export PYTHON +export TFLITE2C +################################################### +all: $(OPENMV) + +$(BUILD): + $(MKDIR) -p $@ + +$(FW_DIR): + $(MKDIR) -p $@ + +FIRMWARE_OBJS: | $(BUILD) $(FW_DIR) + $(MAKE) -C $(CMSIS_DIR) BUILD=$(BUILD)/$(CMSIS_DIR) CFLAGS="$(CFLAGS) -fno-strict-aliasing -MMD" + $(MAKE) -C $(TENSORFLOW_DIR) BUILD=$(BUILD)/$(TENSORFLOW_DIR) CFLAGS="$(CFLAGS) -MMD" + $(MAKE) -C $(MICROPY_DIR)/ports/$(PORT) BUILD=$(BUILD)/$(MICROPY_DIR) $(MICROPY_ARGS) + $(MAKE) -C $(HAL_DIR) BUILD=$(BUILD)/$(HAL_DIR) CFLAGS="$(CFLAGS) -MMD" + $(MAKE) -C $(LEPTON_DIR) BUILD=$(BUILD)/$(LEPTON_DIR) CFLAGS="$(CFLAGS) -MMD" +ifeq ($(MICROPY_PY_IMU), 1) + $(MAKE) -C $(LSM6DS3_DIR) BUILD=$(BUILD)/$(LSM6DS3_DIR) CFLAGS="$(CFLAGS) -MMD" + $(MAKE) -C $(LSM6DSOX_DIR) BUILD=$(BUILD)/$(LSM6DSOX_DIR) CFLAGS="$(CFLAGS) -MMD" +endif + $(MAKE) -C $(MLX90621_DIR) BUILD=$(BUILD)/$(MLX90621_DIR) CFLAGS="$(CFLAGS) -MMD" + $(MAKE) -C $(MLX90640_DIR) BUILD=$(BUILD)/$(MLX90640_DIR) CFLAGS="$(CFLAGS) -MMD" + $(MAKE) -C $(MLX90641_DIR) BUILD=$(BUILD)/$(MLX90641_DIR) CFLAGS="$(CFLAGS) -MMD" + $(MAKE) -C $(VL53L5CX_DIR) BUILD=$(BUILD)/$(VL53L5CX_DIR) CFLAGS="$(CFLAGS) -MMD" + $(MAKE) -C $(PIXART_DIR) BUILD=$(BUILD)/$(PIXART_DIR) CFLAGS="$(CFLAGS) -MMD" + $(MAKE) -C $(OMV_DIR) BUILD=$(BUILD)/$(OMV_DIR) CFLAGS="$(CFLAGS) -MMD" +ifeq ($(OMV_ENABLE_UVC), 1) +UVC_OBJS: FIRMWARE_OBJS + $(MAKE) -C $(UVC_DIR) BUILD=$(BUILD)/$(UVC_DIR) CFLAGS="$(UVC_CFLAGS) -MMD" +endif + +# This target generates the main/app firmware image located at 0x08010000 +$(FIRMWARE): FIRMWARE_OBJS + $(CPP) -P -E -DLINKER_SCRIPT -I$(OMV_BOARD_CONFIG_DIR) $(OMV_DIR)/ports/$(PORT)/$(LDSCRIPT).ld.S > $(BUILD)/$(LDSCRIPT).lds + $(CC) $(LDFLAGS) $(FIRM_OBJ) -o $(FW_DIR)/$(FIRMWARE).elf $(LIBS) -lgcc + $(OBJCOPY) -Obinary -R .big_const* $(FW_DIR)/$(FIRMWARE).elf $(FW_DIR)/$(FIRMWARE).bin +# $(PYTHON) $(MKDFU) -D $(DFU_DEVICE) -b $(MAIN_APP_ADDR):$(FW_DIR)/$(FIRMWARE).bin $(FW_DIR)/$(FIRMWARE).dfu + +ifeq ($(OMV_ENABLE_UVC), 1) +# This target generates the UVC firmware. +$(UVC): FIRMWARE_OBJS UVC_OBJS + $(CPP) -P -E -I$(OMV_BOARD_CONFIG_DIR) $(UVC_DIR)/$(LD_SCRIPT).ld.S > $(BUILD)/$(UVC_DIR)/$(LD_SCRIPT).lds + $(CC) $(UVC_LDFLAGS) $(UVC_OBJ) -o $(FW_DIR)/$(UVC).elf -lgcc + $(OBJCOPY) -Obinary $(FW_DIR)/$(UVC).elf $(FW_DIR)/$(UVC).bin + $(PYTHON) $(MKDFU) -D $(DFU_DEVICE) -b $(MAIN_APP_ADDR):$(FW_DIR)/$(UVC).bin $(FW_DIR)/$(UVC).dfu +endif + +# This target generates the uvc and firmware images. +$(OPENMV): $(UVC) $(FIRMWARE) + $(SIZE) $(FW_DIR)/$(FIRMWARE).elf + +size: + $(SIZE) --format=SysV $(FW_DIR)/$(FIRMWARE).elf + +# Flash the main firmware image +flash_image:: + $(PYDFU) -u $(FW_DIR)/$(FIRMWARE).dfu + +# Flash the main firmware image using dfu_util +flash_image_dfu_util:: + dfu-util -a 0 -d $(DFU_DEVICE) -D $(FW_DIR)/$(FIRMWARE).dfu diff --git a/src/omv/ports/mimxrt/omv_spi.c b/src/omv/ports/mimxrt/omv_spi.c new file mode 100644 index 000000000..eb6892ed8 --- /dev/null +++ b/src/omv/ports/mimxrt/omv_spi.c @@ -0,0 +1,238 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2023 Ibrahim Abdelkader + * Copyright (c) 2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OMV SPI bus port for mimxrt. + */ +#include "omv_boardconfig.h" +#include +#include "py/mphal.h" + +#include "fsl_gpio.h" +#include "fsl_lpspi.h" +#include "fsl_iomuxc.h" +#include "fsl_lpspi_edma.h" +#include "fsl_dmamux.h" +#include CLOCK_CONFIG_H + +#include "mimxrt_hal.h" +#include "common.h" +#include "omv_spi.h" + +typedef struct dma_descr { + DMA_Type *dma_inst; + DMAMUX_Type *dma_mux; + uint32_t channel; + uint32_t request; +} dma_descr_t; + +typedef struct omv_spi_descr { + LPSPI_Type *inst; + omv_gpio_t cs; + dma_descr_t dma_descr_tx; + dma_descr_t dma_descr_rx; +} omv_spi_descr_t; + +static const omv_spi_descr_t omv_spi_descr_all[] = { + #if defined(LPSPI1_ID) + { LPSPI1, LPSPI1_SSEL_PIN, + { DMA0, DMAMUX, LPSPI1_DMA_TX_CHANNEL, kDmaRequestMuxLPSPI1Tx }, + { DMA0, DMAMUX, LPSPI1_DMA_RX_CHANNEL, kDmaRequestMuxLPSPI1Rx } }, + #else + { NULL, NULL, { NULL, NULL, 0, 0 }, { NULL, NULL, 0, 0 } }, + #endif + #if defined(LPSPI2_ID) + { LPSPI2, LPSPI2_SSEL_PIN, + { DMA0, DMAMUX, LPSPI2_DMA_TX_CHANNEL, kDmaRequestMuxLPSPI2Tx }, + { DMA0, DMAMUX, LPSPI2_DMA_RX_CHANNEL, kDmaRequestMuxLPSPI2Rx } }, + #else + { NULL, NULL, { NULL, NULL, 0, 0 }, { NULL, NULL, 0, 0 } }, + #endif + + #if defined(LPSPI3_ID) + { LPSPI3, LPSPI3_SSEL_PIN, + { DMA0, DMAMUX, LPSPI3_DMA_TX_CHANNEL, kDmaRequestMuxLPSPI3Tx }, + { DMA0, DMAMUX, LPSPI3_DMA_RX_CHANNEL, kDmaRequestMuxLPSPI3Rx } }, + #else + { NULL, NULL, { NULL, NULL, 0, 0 }, { NULL, NULL, 0, 0 } }, + #endif + + #if defined(LPSPI4_ID) + { LPSPI4, LPSPI4_SSEL_PIN, + { DMA0, DMAMUX, LPSPI4_DMA_TX_CHANNEL, kDmaRequestMuxLPSPI4Tx }, + { DMA0, DMAMUX, LPSPI4_DMA_RX_CHANNEL, kDmaRequestMuxLPSPI4Rx } }, + #else + { NULL, NULL, { NULL, NULL, 0, 0 }, { NULL, NULL, 0, 0 } }, + #endif +}; + +static void spi_master_callback(LPSPI_Type *base, void *handle, status_t status, void *user) +{ + omv_spi_t *spi = (omv_spi_t *) user; + + if (status == kStatus_Success) { + spi->xfer_flags |= OMV_SPI_XFER_COMPLETE; + } else { + spi->xfer_flags |= OMV_SPI_XFER_FAILED; + spi->xfer_error = status; + if (spi->xfer_flags & OMV_SPI_XFER_DMA) { + LPSPI_MasterTransferAbortEDMA(spi->inst, &spi->descr_master_edma); + } else { + LPSPI_MasterTransferAbort(spi->inst, &spi->descr_master); + } + } + + if (spi->callback) { + spi->callback(spi, spi->userdata); + } + + uint32_t flags = (OMV_SPI_XFER_DMA | OMV_SPI_XFER_CIRCULAR | OMV_SPI_XFER_COMPLETE); + if ((spi->xfer_flags & flags) == flags) { + // Restart transfer for circular transfers. + spi->xfer_flags &= ~(OMV_SPI_XFER_COMPLETE); + LPSPI_MasterTransferEDMA(spi->inst, &spi->descr_master_edma, &spi->xfer_descr); + } +} + +int omv_spi_transfer_start(omv_spi_t *spi, omv_spi_transfer_t *xfer) +{ + spi->callback = xfer->callback; + spi->userdata = xfer->userdata; + spi->xfer_flags = xfer->flags; + // Duplicate & stash transfer in spi struct, to avoid recreating + // it for circular transfers. NOTE: If circular transfers ever + // works this can be removed. + spi->xfer_descr.txData = xfer->txbuf; + spi->xfer_descr.rxData = xfer->rxbuf; + spi->xfer_descr.dataSize = xfer->size; + spi->xfer_descr.configFlags = kLPSPI_MasterPcs0 | kLPSPI_MasterPcsContinuous | kLPSPI_MasterByteSwap; + spi->xfer_flags &= ~(OMV_SPI_XFER_FAILED | OMV_SPI_XFER_COMPLETE); + + if (spi->xfer_flags & OMV_SPI_XFER_DMA) { + // DMA transfer (circular or one-shot) + if (LPSPI_MasterTransferEDMA(spi->inst, &spi->descr_master_edma, &spi->xfer_descr) != kStatus_Success) { + return -1; + } + } else if (spi->xfer_flags & (OMV_SPI_XFER_BLOCKING | OMV_SPI_XFER_NONBLOCK)) { + // Use non-blocking mode for both non-blocking and blocking + // transfers, this way we can control the timeout better. + if (LPSPI_MasterTransferNonBlocking(spi->inst, &spi->descr_master, &spi->xfer_descr) != kStatus_Success) { + return -1; + } + + // Non-blocking transfetr, return immediately and the user + // callback will be called when the transfer is done. + if (spi->xfer_flags & OMV_SPI_XFER_NONBLOCK) { + return 0; + } + + // Blocking transfetr, wait for transfer complete or timeout. + mp_uint_t start = mp_hal_ticks_ms(); + while (!(spi->xfer_flags & OMV_SPI_XFER_COMPLETE)) { + if ((spi->xfer_flags & OMV_SPI_XFER_FAILED) || + ((mp_hal_ticks_ms() - start) > xfer->timeout)) { + LPSPI_MasterTransferAbort(spi->inst, &spi->descr_master); + return -1; + } + MICROPY_EVENT_POLL_HOOK + } + } else { + return -1; + } + return 0; +} + +int omv_spi_transfer_abort(omv_spi_t *spi) +{ + LPSPI_MasterTransferAbortEDMA(spi->inst, &spi->descr_master_edma); + return 0; +} + +static int omv_spi_dma_init(edma_handle_t *dma_handle, const dma_descr_t *dma_descr) +{ + DMAMUX_SetSource(dma_descr->dma_mux, dma_descr->channel, dma_descr->request); + DMAMUX_EnableChannel(dma_descr->dma_mux, dma_descr->channel); + EDMA_CreateHandle(dma_handle, dma_descr->dma_inst, dma_descr->channel); + return 0; +} + +int omv_spi_init(omv_spi_t *spi, omv_spi_config_t *config) +{ + // Configure clocks, pins and DMA MUX. + mimxrt_hal_spi_init(config->id, config->nss_enable, config->nss_pol); + + const omv_spi_descr_t *spi_descr = &omv_spi_descr_all[config->id - 1]; + if (spi_descr->inst == NULL) { + return -1; + } + + memset(spi, 0, sizeof(omv_spi_t)); + spi->id = config->id; + spi->inst = spi_descr->inst; + spi->cs = spi_descr->cs; + spi->dma_enabled = config->dma_enable; + + lpspi_master_config_t spi_config; + LPSPI_MasterGetDefaultConfig(&spi_config); + + spi_config.whichPcs = kLPSPI_Pcs0; + spi_config.baudRate = config->baudrate; + spi_config.bitsPerFrame = config->datasize; + spi_config.direction = config->bit_order; + spi_config.pcsActiveHighOrLow = config->nss_pol; + spi_config.cpol = config->clk_pol; + spi_config.cpha = config->clk_pha; + spi_config.dataOutConfig = config->data_retained ? kLpspiDataOutRetained : kLpspiDataOutTristate; + LPSPI_MasterInit(spi->inst, &spi_config, BOARD_BOOTCLOCKRUN_LPSPI_CLK_ROOT); + + if (config->dma_enable) { + // Configure DMA. + // Note the FSL driver doesn't support half-duplex, so the both + // TX/RX channels, descriptors etc... must be initialized. + omv_spi_dma_init(&spi->dma_descr_tx, &spi_descr->dma_descr_tx); + omv_spi_dma_init(&spi->dma_descr_rx, &spi_descr->dma_descr_rx); + + // Link TX/RX DMA descriptors to SPI descriptor. + LPSPI_MasterTransferCreateHandleEDMA( + spi->inst, + &spi->descr_master_edma, + (lpspi_master_edma_transfer_callback_t) spi_master_callback, + spi, + &spi->dma_descr_rx, + &spi->dma_descr_tx); + } else { + LPSPI_MasterTransferCreateHandle( + spi->inst, + &spi->descr_master, + (lpspi_master_transfer_callback_t) spi_master_callback, + spi); + } + spi->initialized = true; + return 0; +} + +int omv_spi_deinit(omv_spi_t *spi) +{ + return 0; +} + +int omv_spi_default_config(omv_spi_config_t *config, uint32_t bus_id) +{ + config->id = bus_id; + config->baudrate = 10000000; + config->datasize = 8; + config->spi_mode = OMV_SPI_MODE_MASTER; + config->bus_mode = OMV_SPI_BUS_TX_RX; + config->bit_order = OMV_SPI_MSB_FIRST; + config->clk_pol = OMV_SPI_CPOL_HIGH; + config->clk_pha = OMV_SPI_CPHA_2EDGE; + config->nss_pol = OMV_SPI_NSS_LOW; + config->nss_enable = true; + config->dma_enable = false; + config->data_retained = true; + return 0; +} diff --git a/src/omv/ports/mimxrt/sensor.c b/src/omv/ports/mimxrt/sensor.c new file mode 100644 index 000000000..0adc529b3 --- /dev/null +++ b/src/omv/ports/mimxrt/sensor.c @@ -0,0 +1,379 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2023 Ibrahim Abdelkader + * Copyright (c) 2013-2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Sensor abstraction layer for nRF port. + */ +#if MICROPY_PY_SENSOR +#include +#include +#include +#include "py/mphal.h" + +#include "fsl_csi.h" +#include "mimxrt_hal.h" + +#include "omv_boardconfig.h" +#include "omv_gpio.h" +#include "omv_i2c.h" +#include "sensor.h" +#include "framebuffer.h" +#include "unaligned_memcpy.h" + +// Sensor struct. +sensor_t sensor = {}; +extern uint8_t _line_buf[OMV_LINE_BUF_SIZE]; + +#define CSI_IRQ_FLAGS ( CSI_CR1_SOF_INTEN_MASK \ + | CSI_CR1_FB2_DMA_DONE_INTEN_MASK \ + | CSI_CR1_FB1_DMA_DONE_INTEN_MASK ) + //CSI_CR1_RF_OR_INTEN_MASK + +#define copy_line(dstp, srcp) \ + for (int i = MAIN_FB()->u, h = MAIN_FB()->v; i; i--) { \ + *dstp = *srcp++; \ + dstp += h; \ + } + +#define copy_line_rev(dstp, srcp) \ + for (int i = MAIN_FB()->u, h = MAIN_FB()->v; i; i--) { \ + *dstp = __REV16(*srcp++); \ + dstp += h; \ + } + +void sensor_init0() +{ + sensor_abort(); + + // Re-init I2C to reset the bus state after soft reset, which + // could have interrupted the bus in the middle of a transfer. + if (sensor.i2c_bus.initialized) { + // Reinitialize the bus using the last used id and speed. + omv_i2c_init(&sensor.i2c_bus, sensor.i2c_bus.id, sensor.i2c_bus.speed); + } + + // Disable VSYNC IRQ and callback + sensor_set_vsync_callback(NULL); + + // Disable Frame callback. + sensor_set_frame_callback(NULL); +} + +int sensor_init() +{ + int init_ret = 0; + + mimxrt_hal_csi_init(CSI); + + #if defined(DCMI_POWER_PIN) + omv_gpio_write(DCMI_POWER_PIN, 1); + #endif + + #if defined(DCMI_RESET_PIN) + omv_gpio_write(DCMI_RESET_PIN, 1); + #endif + + // Reset the sesnor state + memset(&sensor, 0, sizeof(sensor_t)); + + // Set default snapshot function. + // Some sensors need to call snapshot from init. + sensor.snapshot = sensor_snapshot; + + // Configure the sensor external clock (XCLK). + if (sensor_set_xclk_frequency(OMV_XCLK_FREQUENCY) != 0) { + // Failed to initialize the sensor clock. + return SENSOR_ERROR_TIM_INIT_FAILED; + } + + // Detect and initialize the image sensor. + if ((init_ret = sensor_probe_init(ISC_I2C_ID, ISC_I2C_SPEED)) != 0) { + // Sensor probe/init failed. + return init_ret; + } + + // Set default color palette. + sensor.color_palette = rainbow_table; + + // Disable VSYNC IRQ and callback + sensor_set_vsync_callback(NULL); + + // Disable Frame callback. + sensor_set_frame_callback(NULL); + + // All good! + sensor.detected = true; + + return 0; +} + +int sensor_dcmi_config(uint32_t pixformat) +{ + CSI_Reset(CSI); + NVIC_DisableIRQ(CSI_IRQn); + + // CSI mode: HSYNC, VSYNC, and PIXCLK signals are used. + CSI_REG_CR1(CSI) |= CSI_CR1_GCLK_MODE(1U); + // Synchronous FIFO clear. + // RXFIFO and STATFIFO are cleared on every SOF. + CSI_REG_CR1(CSI) |= CSI_CR1_FCC_MASK; + + // Configure VSYNC, HSYNC and PIXCLK signals. + CSI_REG_CR1(CSI) |= CSI_CR1_EXT_VSYNC_MASK; + CSI_REG_CR1(CSI) |= !sensor.hw_flags.vsync ? CSI_CR1_SOF_POL_MASK : 0; + CSI_REG_CR1(CSI) |= !sensor.hw_flags.hsync ? CSI_CR1_HSYNC_POL_MASK : 0; + CSI_REG_CR1(CSI) |= sensor.hw_flags.pixck ? CSI_CR1_REDGE_MASK : 0; + + // Stride config: No stride. + CSI_REG_FBUF_PARA(CSI) = 0; + // Reset frame counter + CSI_REG_CR3(CSI) |= CSI_CR3_FRMCNT_RST_MASK; + + // Configure CSI FIFO depth and DMA burst size. + CSI_REG_CR2(CSI) |= CSI_CR2_DMA_BURST_TYPE_RFF(3U); + CSI_REG_CR3(CSI) |= 7U << CSI_CR3_RxFF_LEVEL_SHIFT; + + // Configure DMA buffers. + CSI_REG_DMASA_FB1(CSI) = (uint32_t) (&_line_buf[OMV_LINE_BUF_SIZE * 0]); + CSI_REG_DMASA_FB2(CSI) = (uint32_t) (&_line_buf[OMV_LINE_BUF_SIZE / 2]); + + // Write to memory from first completed frame. + // DMA CSI addr switch at dma transfer done. + CSI_REG_CR18(CSI) |= CSI_CR18_MASK_OPTION(0); + return 0; +} + +int sensor_abort() +{ + NVIC_DisableIRQ(CSI_IRQn); + CSI_DisableInterrupts(CSI, CSI_IRQ_FLAGS); + CSI_REG_CR18(CSI) &= ~(CSI_CR18_CSI_ENABLE_MASK | CSI_CR3_DMA_REQ_EN_RFF_MASK); + framebuffer_reset_buffers(); + return 0; +} + +int sensor_set_xclk_frequency(uint32_t frequency) +{ + if (frequency == 24000000) { + CLOCK_SetDiv(kCLOCK_CsiDiv, 0); + } else if (frequency == 12000000) { + CLOCK_SetDiv(kCLOCK_CsiDiv, 1); + } else if (frequency == 8000000) { + CLOCK_SetDiv(kCLOCK_CsiDiv, 2); + } else if (frequency == 6000000) { + CLOCK_SetDiv(kCLOCK_CsiDiv, 3); + } else if (frequency == 4000000) { + CLOCK_SetDiv(kCLOCK_CsiDiv, 5); + } else { + return -1; + } + return 0; +} + +uint32_t sensor_get_xclk_frequency() +{ + return 24000000 / (CLOCK_GetDiv(kCLOCK_CsiDiv) + 1); +} + +void sensor_sof_callback() +{ + // Get current framebuffer. + vbuffer_t *buffer = framebuffer_get_tail(FB_PEEK); + if (buffer == NULL) { + sensor_abort(); + } if (buffer->offset < MAIN_FB()->v) { + // Missed a few lines, reset buffer state and continue. + buffer->reset_state = true; + } +} + +void sensor_line_callback(uint32_t addr) +{ + // Get current framebuffer. + vbuffer_t *buffer = framebuffer_get_tail(FB_PEEK); + + // Copy from DMA buffer to framebuffer. + uint32_t bytes_per_pixel = sensor_get_src_bpp(); + uint8_t *src = ((uint8_t *) addr) + (MAIN_FB()->x * bytes_per_pixel); + uint8_t *dst = buffer->data; + + // Adjust BPP for Grayscale. + if (sensor.pixformat == PIXFORMAT_GRAYSCALE) { + bytes_per_pixel = 1; + } + + if (sensor.transpose) { + dst += bytes_per_pixel * buffer->offset; + } else { + dst += MAIN_FB()->u * bytes_per_pixel * buffer->offset; + } + + // Implement per line, per pixel cropping, and image transposing (for image rotation) in + // in software using the CPU to transfer the image from the line buffers to the frame buffer. + uint16_t *src16 = (uint16_t *) src; + uint16_t *dst16 = (uint16_t *) dst; + + switch (sensor.pixformat) { + case PIXFORMAT_BAYER: + #if (OMV_ENABLE_SENSOR_EDMA == 1) + edma_memcpy(buffer, dst, src, sizeof(uint8_t), sensor.transpose); + #else + if (!sensor.transpose) { + unaligned_memcpy(dst, src, MAIN_FB()->u); + } else { + copy_line(dst, src); + } + #endif + break; + case PIXFORMAT_GRAYSCALE: + #if (OMV_ENABLE_SENSOR_EDMA == 1) + edma_memcpy(buffer, dst, src, sizeof(uint8_t), sensor.transpose); + #else + if (sensor.hw_flags.gs_bpp == 1) { + // 1BPP GRAYSCALE. + if (!sensor.transpose) { + unaligned_memcpy(dst, src, MAIN_FB()->u); + } else { + copy_line(dst, src); + } + } else { + // Extract Y channel from YUV. + if (!sensor.transpose) { + unaligned_2_to_1_memcpy(dst, src16, MAIN_FB()->u); + } else { + copy_line(dst, src16); + } + } + #endif + break; + case PIXFORMAT_RGB565: + case PIXFORMAT_YUV422: + #if (OMV_ENABLE_SENSOR_EDMA == 1) + edma_memcpy(buffer, dst16, src16, sizeof(uint16_t), sensor.transpose); + #else + if ((sensor.pixformat == PIXFORMAT_RGB565 && sensor.hw_flags.rgb_swap) + || (sensor.pixformat == PIXFORMAT_YUV422 && sensor.hw_flags.yuv_swap)) { + if (!sensor.transpose) { + unaligned_memcpy_rev16(dst16, src16, MAIN_FB()->u); + } else { + copy_line_rev(dst16, src16); + } + } else { + if (!sensor.transpose) { + unaligned_memcpy(dst16, src16, MAIN_FB()->u * sizeof(uint16_t)); + } else { + copy_line(dst16, src16); + } + } + #endif + break; + default: + break; + } + + if (++buffer->offset == MAIN_FB()->v) { + // Release the current framebuffer. + framebuffer_get_tail(FB_NO_FLAGS); + CSI_REG_CR3(CSI) &= ~CSI_CR3_DMA_REQ_EN_RFF_MASK; + } +} + +// This is the default snapshot function, which can be replaced in sensor_init functions. +int sensor_snapshot(sensor_t *sensor, image_t *image, uint32_t flags) +{ + // Used to restore MAIN_FB's width and height. + uint32_t w = MAIN_FB()->u; + uint32_t h = MAIN_FB()->v; + + if (sensor->pixformat == PIXFORMAT_INVALID) { + return SENSOR_ERROR_INVALID_PIXFORMAT; + } + + if (sensor->framesize == FRAMESIZE_INVALID) { + return SENSOR_ERROR_INVALID_FRAMESIZE; + } + + if (sensor_check_framebuffer_size() != 0) { + return SENSOR_ERROR_FRAMEBUFFER_OVERFLOW; + } + + // Compress the framebuffer for the IDE preview. + framebuffer_update_jpeg_buffer(); + + // Free the current FB head. + framebuffer_free_current_buffer(); + + // If the DMA is Not currently transferring a new buffer, + // reconfigure and restart the CSI transfer. + if (!(CSI->CR18 & CSI_CR18_CSI_ENABLE_MASK)) { + framebuffer_setup_buffers(); + + vbuffer_t *buffer = framebuffer_get_tail(FB_PEEK); + if (buffer == NULL) { + return SENSOR_ERROR_FRAMEBUFFER_ERROR; + } + // Re/configure and re/start the CSI. + uint32_t bytes_per_pixel = sensor_get_src_bpp(); + uint32_t dma_line_bytes = resolution[sensor->framesize][0] * bytes_per_pixel; + CSI_REG_IMAG_PARA(CSI) = + (dma_line_bytes << CSI_IMAG_PARA_IMAGE_WIDTH_SHIFT ) | + (1 << CSI_IMAG_PARA_IMAGE_HEIGHT_SHIFT); + + // Configure and enable CSI interrupts. + CSI_EnableInterrupts(CSI, CSI_IRQ_FLAGS); + NVIC_EnableIRQ(CSI_IRQn); + + // Enable CSI + CSI_REG_CR18(CSI) |= CSI_CR18_CSI_ENABLE_MASK; + } + + vbuffer_t *buffer = framebuffer_get_head(FB_NO_FLAGS); + // Wait for the DMA to finish the transfer. + for (mp_uint_t ticks = mp_hal_ticks_ms(); buffer == NULL;) { + buffer = framebuffer_get_head(FB_NO_FLAGS); + if ((mp_hal_ticks_ms() - ticks) > 3000) { + sensor_abort(); + return SENSOR_ERROR_CAPTURE_TIMEOUT; + } + } + + if (!sensor->transpose) { + MAIN_FB()->w = w; + MAIN_FB()->h = h; + } else { + MAIN_FB()->w = h; + MAIN_FB()->h = w; + } + + // Fix the BPP. + switch (sensor->pixformat) { + case PIXFORMAT_GRAYSCALE: + MAIN_FB()->pixfmt = PIXFORMAT_GRAYSCALE; + break; + case PIXFORMAT_RGB565: + MAIN_FB()->pixfmt = PIXFORMAT_RGB565; + break; + case PIXFORMAT_BAYER: + MAIN_FB()->pixfmt = PIXFORMAT_BAYER; + MAIN_FB()->subfmt_id = sensor->hw_flags.bayer; + break; + case PIXFORMAT_YUV422: { + bool yuv_order = sensor->hw_flags.yuv_order == SENSOR_HW_FLAGS_YUV422; + int even = yuv_order ? PIXFORMAT_YUV422 : PIXFORMAT_YVU422; + int odd = yuv_order ? PIXFORMAT_YVU422 : PIXFORMAT_YUV422; + MAIN_FB()->pixfmt = (MAIN_FB()->x % 2) ? odd : even; + break; + } + default: + break; + } + + // Set the user image. + framebuffer_init_image(image); + return 0; +} +#endif diff --git a/tools/imxrt_pins_gen.py b/tools/imxrt_pins_gen.py new file mode 100644 index 000000000..2ee6809eb --- /dev/null +++ b/tools/imxrt_pins_gen.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# This file is part of the OpenMV project. +# +# Copyright (c) 2023 Ibrahim Abdelkader +# Copyright (c) 2023 Kwabena W. Agyeman +# +# This work is licensed under the MIT license, see the file LICENSE for details. + +import sys +import re +import argparse +from collections import defaultdict + +def parse_header_file(file_path): + pads = defaultdict(dict) + gpios = {} + for i in range(0, 11): + gpios[i] = 0 + + af_pattern = "\s([0x]*[0-9a-fA-F]+)U*,*" * 5 + pad_pattern = [ + re.compile("#define IOMUXC_GPIO_(\w+_\d\d)_([a-zA-Z0-9]+)_(\w+)" + af_pattern), + re.compile("#define IOMUXC_SNVS_(PMIC\w+_REQ)_([a-zA-Z0-9]+)_(\w+)"+ af_pattern), + re.compile("#define IOMUXC_SNVS_(WAKEUP)_([a-zA-Z0-9]+)_*(\w*)"+ af_pattern) + ] + with open(file_path, "r") as file: + for line in file: + for r in pad_pattern: + match = re.match(r, line) + if match: + match = match.groups() + if (match[1].startswith("GPIO")): + gpios[int(match[1][4:])] += 1 + pads[match[0]][int(match[-4], 16)] = match[1:] + break + return pads, gpios + +def main(): + parser = argparse.ArgumentParser(description = "Usage: python gen_iomux_pins.py .") + parser.add_argument("--iomux", action = "store", help = "Input iomux header files.", required=True) + parser.add_argument("--header", action = "store_true", help = "Generate header file.", required=False, default=False) + + args = parser.parse_args() + + print("// THIS FILE IS AUTO-GENERATED DO NOT EDIT.\n") + print("/*") + print(" * This file is part of the OpenMV project.") + print(" *") + print(" * Copyright (c) 2023 Ibrahim Abdelkader ") + print(" * Copyright (c) 2023 Kwabena W. Agyeman ") + print(" *") + print(" * This work is licensed under the MIT license, see the file LICENSE for details.") + print(" */") + + pads, gpios = parse_header_file(args.iomux) + + if (args.header): + # Generate the header file + print("#ifndef __IMXRT_PADS_H__") + print("#define __IMXRT_PADS_H__\n") + print("#define MIMXRT_PAD_COUNT {:d}\n".format(len(pads))) + + for pad_name, pad_afs in pads.items(): + af_gpio = pad_afs[5] # GPIO AF is always 5 + for af_idx, af in pad_afs.items(): + if af_idx == 5: + af_name = "GPIO" + elif af[0] in ["ENET", "ENET2", "SEMC"]: + af_name = "_".join(af[0:2]) + else: + af_name = af[0] + print("#define {:s}_AF_{:s} {:s}".format(pad_name, af_name, af[-4])) + print() + + for pad_name, pad_afs in pads.items(): + afs_count = len(pad_afs.keys()) - 1 # remove gpio AF. + print("extern const imxrt_pad_t imxrt_pad_{:s};".format(pad_name)) + + print("#endif // __IMXRT_PADS_H__") + else: + print("#include \"omv_gpio.h\"") + print("#include \"fsl_gpio.h\"") + print("#include \"fsl_iomuxc.h\"") + print("#include \"mimxrt_pads.h\"") + + print() + irq_handlers_list = [] + for port, pin_count in gpios.items(): + if (pin_count): + print("static omv_gpio_irq_descr_t gpio_irq_descr_gpio{:d}[{:d}] = {{ NULL }};".format(port, pin_count)) + irq_handlers_list.append(" [{:d}] = gpio_irq_descr_gpio{:d},\n".format(port, port)) + print() + print("omv_gpio_irq_descr_t *const gpio_irq_descr_table[{:d}] = {{\n{:s}}};".format( + len(gpios.keys()), "".join(irq_handlers_list))) + + # Generate the C file + reg_mask = lambda r: int(r, 16) & 0x0000FFFF + reg_base = lambda r: (int(r, 16) & 0xFFFF0000) >> 16 + for pad_name, pad_afs in pads.items(): + print() + pad_afs_list = [] + for af_idx, af in pad_afs.items(): + if (af_idx == 5): + continue + mux_reg, mux_mode, input_reg, input_daisy, config_reg = af[-5:] + pad_afs_list.append("\n {{ {:d}, 0x{:04X}, {:d} }}, // {:s}".format( + af_idx, reg_mask(input_reg), int(input_daisy, 16), af[0])) + print("static const imxrt_pad_af_t imxrt_pad_{:s}_AF[{:d}] = {{{:s}\n}};".format( + pad_name, len(pad_afs_list), "".join(pad_afs_list))) + + # GPIO AF is always 5, use it to get mux_reg and config_reg which + # are the same for all AFs, and 0, 0, for input register/daisy + gpio, pin, mux_reg, mux_mode, input_reg, input_daisy, config_reg = pad_afs[5][-7:] + afs_count = len(pad_afs.keys()) - 1 # remove GPIO AF. + print("const imxrt_pad_t imxrt_pad_{:s} =" + " {{ {:s}, {:d}, 0x{:04X}, 0x{:04X}, 0x{:04X}, {:d}, imxrt_pad_{:s}_AF }};". + format(pad_name, gpio, int(pin[2:]), reg_base(mux_reg), + reg_mask(mux_reg), reg_mask(config_reg), afs_count, pad_name)) + +if __name__ == "__main__": + main()