From 347f9d220fdc6c83a7d25ff53b31faf0ca9f79f9 Mon Sep 17 00:00:00 2001 From: Luca Burelli Date: Tue, 31 Mar 2026 11:15:34 +0200 Subject: [PATCH 1/4] gen_provides: make no matching symbols just a warning There is no reason to fail the script if no symbols are found matching the criteria; simply log a warning and generate an empty provides file. Signed-off-by: Luca Burelli --- extra/gen_provides.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/extra/gen_provides.py b/extra/gen_provides.py index 39bfa8efd..b463d2351 100755 --- a/extra/gen_provides.py +++ b/extra/gen_provides.py @@ -220,10 +220,6 @@ def main(): if name in sized_syms: out_syms[name + "_size"] = (sym['st_size'], [f"size of {name}"]) - if not out_syms: - sys.stderr.write("No symbols found matching the criteria.\n") - fail = True - if fail: sys.exit(1) @@ -235,16 +231,21 @@ def main(): * SHA256: {elf_sha} */ """) - sym_comment = nul_comment = "" - for name, (value, comments) in sorted(out_syms.items(), key=lambda x: x[0]): - if args.verbose: - comment = ', '.join(sorted(comments)) - sym_comment = f"/* {comment} */" - nul_comment = f" ({comment})" - if value: - print(f"PROVIDE({name} = {value:#010x});{sym_comment}") - else: - print(f"/* NULL {name}{nul_comment} */") + + if not out_syms: + print("/* No symbols found matching the criteria */") + sys.stderr.write("warning: no symbols found matching the criteria.\n") + else: + sym_comment = nul_comment = "" + for name, (value, comments) in sorted(out_syms.items(), key=lambda x: x[0]): + if args.verbose: + comment = ', '.join(sorted(comments)) + sym_comment = f"/* {comment} */" + nul_comment = f" ({comment})" + if value: + print(f"PROVIDE({name} = {value:#010x});{sym_comment}") + else: + print(f"/* NULL {name}{nul_comment} */") #------------------------------------------------------------------------------- if __name__ == '__main__': From b22c6bf16efd7324e595d06a05c7e767e5b2f529 Mon Sep 17 00:00:00 2001 From: Luca Burelli Date: Tue, 31 Mar 2026 11:17:39 +0200 Subject: [PATCH 2/4] gen_provides: export per-variant TLS symbol offsets to sketches Add an option to export TLS symbol offsets to an assembly wrapper in each board's variant folder, so that they can be used at core compile time. This will allow 'errno' and other thread-local symbols to be properly resolved in the sketches. Also export the '__aebabi_read_tp' function on ARM targets, which is used by the compiler-generated TLS code to compute absolute symbol addresses. Signed-off-by: Luca Burelli --- .gitignore | 1 + cores/arduino/llext_wrappers.c | 5 +++ extra/build.sh | 1 + extra/gen_provides.py | 71 +++++++++++++++++++++++++++------- loader/llext_exports.c | 4 ++ 5 files changed, 68 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 5c3ff0e6e..fec4815fa 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ cxxflags.txt includes.txt syms-dynamic.ld syms-static.ld +tls-syms.S diff --git a/cores/arduino/llext_wrappers.c b/cores/arduino/llext_wrappers.c index 1bac447f1..2f36d5ad8 100644 --- a/cores/arduino/llext_wrappers.c +++ b/cores/arduino/llext_wrappers.c @@ -62,6 +62,11 @@ __real_##name(a); \ } +#ifdef CONFIG_ARM +/* ARM EABI thread pointer access */ +W0(size_t, __aeabi_read_tp) +#endif + /* string.h */ W3(void *, memcpy, void *, const void *, size_t) W3(void *, memmove, void *, const void *, size_t) diff --git a/extra/build.sh b/extra/build.sh index e490e1c3f..85753007d 100755 --- a/extra/build.sh +++ b/extra/build.sh @@ -102,6 +102,7 @@ cp ${BUILD_DIR}/zephyr/.config firmwares/zephyr-$variant.config # Generate the provides.ld file for linked builds echo "Generating exported symbol scripts" +extra/gen_provides.py "${BUILD_DIR}/zephyr/zephyr.elf" -T > ${VARIANT_DIR}/tls-syms.S extra/gen_provides.py "${BUILD_DIR}/zephyr/zephyr.elf" -L > ${VARIANT_DIR}/syms-dynamic.ld extra/gen_provides.py "${BUILD_DIR}/zephyr/zephyr.elf" -LF \ "+kheap_llext_heap" \ diff --git a/extra/gen_provides.py b/extra/gen_provides.py index b463d2351..47a303312 100755 --- a/extra/gen_provides.py +++ b/extra/gen_provides.py @@ -122,6 +122,9 @@ def main(): argparser.add_argument('-F', '--funcs', action='store_true', help='Extract all public functions') + argparser.add_argument('-T', '--tls-defs', + action='store_true', + help='Extract TLS symbol offsets to a .S file') argparser.add_argument('file', help='ELF file to parse') argparser.add_argument('syms', nargs='*', @@ -129,10 +132,19 @@ def main(): args = argparser.parse_intermixed_args() + wants_provides = bool(args.syms or args.funcs or args.llext) + if wants_provides and args.tls_defs: + sys.stderr.write("Cannot generate TLS defs when also generating PROVIDEs.\n") + sys.exit(1) + elif not wants_provides and not args.tls_defs: + sys.stderr.write("Nothing specified for export.\n") + sys.exit(1) + exact_syms = set() regex_syms = set() deref_syms = set() sized_syms = set() + tls_syms = set() # tuple of (name, value, size) rename_map = {} for sym in args.syms: sym_class = None @@ -184,6 +196,11 @@ def main(): fail = False for name, sym in all_syms.items(): + if sym['st_info']['type'] == 'STT_TLS': + # Collect TLS symbol information, ignore for PROVIDEs + tls_syms.add((name, sym['st_value'], sym['st_size'])) + continue + value = None comment = [] if name in exact_syms or any(re.match(r, name) for r in regex_syms): @@ -232,20 +249,46 @@ def main(): */ """) - if not out_syms: - print("/* No symbols found matching the criteria */") - sys.stderr.write("warning: no symbols found matching the criteria.\n") - else: - sym_comment = nul_comment = "" - for name, (value, comments) in sorted(out_syms.items(), key=lambda x: x[0]): - if args.verbose: - comment = ', '.join(sorted(comments)) - sym_comment = f"/* {comment} */" - nul_comment = f" ({comment})" - if value: - print(f"PROVIDE({name} = {value:#010x});{sym_comment}") - else: - print(f"/* NULL {name}{nul_comment} */") + if wants_provides: + if not out_syms: + print("/* No symbols found matching the criteria */") + sys.stderr.write("warning: no symbols found matching the criteria.\n") + else: + sym_comment = nul_comment = "" + for name, (value, comments) in sorted(out_syms.items(), key=lambda x: x[0]): + if args.verbose: + comment = ', '.join(sorted(comments)) + sym_comment = f"/* {comment} */" + nul_comment = f" ({comment})" + if value: + print(f"PROVIDE({name} = {value:#010x});{sym_comment}") + else: + print(f"/* NULL {name}{nul_comment} */") + + if args.tls_defs: + # ARM and AArch64 use '@' as a line-comment character in GAS, so the + # assembler requires '%' as the type prefix there. All other targets use '@'. + prefix = '%' if elf['e_machine'] in ('EM_ARM', 'EM_AARCH64') else '@' + + # The TLS Variant 1 layout (used by ARM/AArch64) places a Thread + # Control Block (TCB) before the TLS data. The thread pointer + # returned by __aeabi_read_tp() points to the TCB, so the actual + # runtime address of a TLS variable must be offset by that size. + # + # TCB is 2 pointers: 8 bytes on 32-bit, 16 bytes on 64-bit. + # See zephyr/arch/arm/core/tls.c: arch_tls_stack_setup(). + tcb_size = (elf.elfclass // 8) * 2 + print(f"/* Offsets include {tcb_size} bytes for TCB data */") + + # Sort by offset first, then size + for name, offset, size in sorted(tls_syms, key=lambda x: (x[1], x[2])): + offset += tcb_size + print( + f"\n/* TLS offset {offset:#x}: {name} ({size} bytes) */\n" + f".global {name}\n" + f".type {name}, {prefix}tls_object\n" + f".set {name}, {offset}" + ) #------------------------------------------------------------------------------- if __name__ == '__main__': diff --git a/loader/llext_exports.c b/loader/llext_exports.c index 57adc393a..02b25e028 100644 --- a/loader/llext_exports.c +++ b/loader/llext_exports.c @@ -297,6 +297,10 @@ EXPORT_SYMBOL(ring_buf_area_finish); #endif EXPORT_SYMBOL(sys_clock_cycle_get_32); + +#if defined(CONFIG_ARM) +extern uint32_t __aeabi_read_tp(void); +EXPORT_LIBC_SYM(__aeabi_read_tp); FORCE_EXPORT_SYM(__aeabi_dcmpun); FORCE_EXPORT_SYM(__aeabi_dcmple); FORCE_EXPORT_SYM(__aeabi_d2lz); From 538971e83be33f244c38c0f970c2215f77cf372f Mon Sep 17 00:00:00 2001 From: Gilberto Conti Date: Mon, 26 Jan 2026 12:37:12 +0100 Subject: [PATCH 3/4] variants: add arduino_nano_rp2040 Signed-off-by: Gilberto Conti --- boards.txt | 69 ++++- cores/arduino/llext_wrappers.c | 14 + extra/build.sh | 2 +- loader/fixups.c | 47 +++ loader/llext_exports.c | 12 + platform.txt | 10 +- .../arduino_nano_connect_rp2040.conf | 34 ++ .../arduino_nano_connect_rp2040.overlay | 290 ++++++++++++++++++ .../arduino_nano_connect_rp2040/nina_pins.cpp | 18 ++ .../arduino_nano_connect_rp2040/nina_pins.h | 72 +++++ .../pins_arduino.h | 10 + .../arduino_nano_connect_rp2040/variant.cpp | 28 ++ .../arduino_nano_connect_rp2040/variant.h | 135 ++++++++ 13 files changed, 738 insertions(+), 3 deletions(-) create mode 100644 variants/arduino_nano_connect_rp2040/arduino_nano_connect_rp2040.conf create mode 100644 variants/arduino_nano_connect_rp2040/arduino_nano_connect_rp2040.overlay create mode 100644 variants/arduino_nano_connect_rp2040/nina_pins.cpp create mode 100644 variants/arduino_nano_connect_rp2040/nina_pins.h create mode 100644 variants/arduino_nano_connect_rp2040/pins_arduino.h create mode 100644 variants/arduino_nano_connect_rp2040/variant.cpp create mode 100644 variants/arduino_nano_connect_rp2040/variant.h diff --git a/boards.txt b/boards.txt index 8a0b9dbf2..ebcd97bb2 100644 --- a/boards.txt +++ b/boards.txt @@ -757,4 +757,71 @@ unoq.bootloader.tool.default=remoteocd unoq.bootloader.file=zephyr-{build.variant}.elf unoq.bootloader.target=stm32u585zitxq -########################################################################################## +############################################################################################################## + +nano_connect.name=Arduino Nano RP2040 Connect +nano_connect.build.core=arduino +nano_connect.build.crossprefix=arm-zephyr-eabi- +nano_connect.build.compiler_path={runtime.tools.arm-zephyr-eabi-0.16.8.path}/bin/ + +nano_connect.menu.debug.false=Standard +nano_connect.menu.debug.true=Debug +nano_connect.menu.debug.true.build.zsk_args.debug=-debug + +nano_connect.menu.link_mode.dynamic=Dynamic +nano_connect.menu.link_mode.static=Static +nano_connect.menu.link_mode.static.build.link_mode=static +nano_connect.menu.link_mode.static.upload.extension=bin-zsk.bin + +nano_connect.build.zephyr_target=arduino_nano_connect +nano_connect.build.zephyr_args= +nano_connect.build.zephyr_hals=hal_rpi_pico +nano_connect.build.artifact=zephyr_main +nano_connect.build.variant=arduino_nano_connect_rp2040 +nano_connect.build.mcu=cortex-m0plus +nano_connect.build.fpu= +nano_connect.build.architecture=cortex-m0plus +nano_connect.compiler.zephyr.arch.define= + +nano_connect.build.float-abi=-mfloat-abi=soft +nano_connect.build.extra_flags= +nano_connect.build.postbuild.cmd="{tools.imgtool.path}/{tools.imgtool.cmd}" exit +nano_connect.recipe.hooks.objcopy.postobjcopy.3.pattern="{runtime.tools.bin2uf2.path}/bin2uf2" {upload.address} {upload.familyid} "{build.path}/{build.project_name}.{upload.extension}" "{build.path}/{build.project_name}.uf2" +nano_connect.build.board=NANO_RP2040_CONNECT +nano_connect.compiler.zephyr= +nano_connect.vid.0=0x2341 +nano_connect.pid.0=0x005E +nano_connect.upload_port.0.vid=0x2341 +nano_connect.upload_port.0.pid=0x005E + +nano_connect.upload.tool=picotool +nano_connect.upload.tool.default=picotool +nano_connect.upload.protocol= +nano_connect.upload.transport= +nano_connect.upload.vid=0x2341 +nano_connect.upload.pid=0x005E +nano_connect.upload.interface=0 +nano_connect.upload.use_1200bps_touch=true +nano_connect.upload.wait_for_upload_port=false +nano_connect.upload.native_usb=true +nano_connect.upload.maximum_size=16777216 +nano_connect.upload.maximum_data_size=270336 + +nano_connect.upload.address=0x100E0000 +nano_connect.upload.familyid=0xe48bff56 + +nano_connect.bootloader.tool=picotool +nano_connect.bootloader.tool.default=picotool +nano_connect.bootloader.vid=0x2341 +nano_connect.bootloader.pid=0x005E +nano_connect.bootloader.interface=0 +nano_connect.bootloader.file=zephyr-{build.variant} + +nano_connect.debug.tool=gdb +nano_connect.debug.server.openocd.scripts.0=interface/{programmer.protocol}.cfg +nano_connect.debug.server.openocd.scripts.1={programmer.transport_script} +nano_connect.debug.server.openocd.scripts.2=target/rp2040-core0.cfg +nano_connect.debug.cortex-debug.custom.request=attach +nano_connect.debug.svd_file={runtime.platform.path}/svd/rp2040.svd + +############################################################################################################## diff --git a/cores/arduino/llext_wrappers.c b/cores/arduino/llext_wrappers.c index 2f36d5ad8..fbb9a55a2 100644 --- a/cores/arduino/llext_wrappers.c +++ b/cores/arduino/llext_wrappers.c @@ -65,6 +65,20 @@ #ifdef CONFIG_ARM /* ARM EABI thread pointer access */ W0(size_t, __aeabi_read_tp) + +/* + * Thumb1 switch-dispatch helpers. + * + * These use a non-standard calling convention: BL sets LR to the jump table + * base (not a return address). With -mlong-calls the compiler lowers each + * void-void wrapper to a tail call (ldr r3, =addr; bx r3), leaving LR + * unchanged so the real function sees the original table pointer. + */ +V0(__gnu_thumb1_case_uqi) +V0(__gnu_thumb1_case_sqi) +V0(__gnu_thumb1_case_uhi) +V0(__gnu_thumb1_case_shi) +V0(__gnu_thumb1_case_si) #endif /* string.h */ diff --git a/extra/build.sh b/extra/build.sh index 85753007d..008195c73 100755 --- a/extra/build.sh +++ b/extra/build.sh @@ -91,7 +91,7 @@ line_continuation='\\$' # match lines ending with '\' c_comment='\s*\/\*.*?\*\/' # match C-style comments and any preceding space perl -i -pe "s/${c_comment}//gs unless /${line_preproc_ok}/ || (/${line_comment_only}/ && !/${line_continuation}/)" $(find ${VARIANT_DIR}/llext-edk/include/ -type f) -for ext in elf bin hex; do +for ext in elf bin hex uf2; do rm -f firmwares/zephyr-$variant.$ext if [ -f ${BUILD_DIR}/zephyr/zephyr.$ext ]; then cp ${BUILD_DIR}/zephyr/zephyr.$ext firmwares/zephyr-$variant.$ext diff --git a/loader/fixups.c b/loader/fixups.c index 1054933f3..d584eb266 100644 --- a/loader/fixups.c +++ b/loader/fixups.c @@ -53,6 +53,53 @@ SYS_INIT(disable_bootloader_mpu, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAU SYS_INIT(disable_mpu_rasr_xn, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); #endif +#if defined(CONFIG_BOARD_ARDUINO_NANO_RP2040_CONNECT) +#include +#include +#include + +/* + * Double-tap reset detection: if the board is reset twice within 500ms, + * enter USB bootloader (BOOTSEL) mode. This mirrors the original + * ArduinoCore-mbed NANO_RP2040_CONNECT behavior. + * + * A magic token is stored in uninitialized RAM (.noinit), which survives + * a warm reset but is lost on power cycle. On boot: + * - If the token is present: a second reset happened quickly, so we + * clear the token and enter USB boot mode (never returns). + * - If not: write the token, wait 500ms, then clear it and boot normally. + */ +static const uint32_t magic_token[] = { + 0xf01681de, + 0xbd729b29, + 0xd359be7a, +}; + +static uint32_t magic_location[3] __attribute__((section(".noinit.double_tap"))); + +#define NANO_RP2040_LED_PIN 6 + +int double_tap_check(void) { + if (magic_location[0] == magic_token[0] && magic_location[1] == magic_token[1] && + magic_location[2] == magic_token[2]) { + magic_location[0] = 0; + reset_usb_boot(1 << NANO_RP2040_LED_PIN, 0); + /* never returns */ + } + + for (int i = 0; i < 3; i++) { + magic_location[i] = magic_token[i]; + } + + k_busy_wait(500000); + + magic_location[0] = 0; + return 0; +} + +SYS_INIT(double_tap_check, POST_KERNEL, 0); +#endif + #if defined(CONFIG_INPUT) #include #include diff --git a/loader/llext_exports.c b/loader/llext_exports.c index 02b25e028..05ce3452b 100644 --- a/loader/llext_exports.c +++ b/loader/llext_exports.c @@ -301,6 +301,16 @@ EXPORT_SYMBOL(sys_clock_cycle_get_32); #if defined(CONFIG_ARM) extern uint32_t __aeabi_read_tp(void); EXPORT_LIBC_SYM(__aeabi_read_tp); +extern void __gnu_thumb1_case_uqi(void); +EXPORT_LIBC_SYM(__gnu_thumb1_case_uqi); +extern void __gnu_thumb1_case_sqi(void); +EXPORT_LIBC_SYM(__gnu_thumb1_case_sqi); +extern void __gnu_thumb1_case_uhi(void); +EXPORT_LIBC_SYM(__gnu_thumb1_case_uhi); +extern void __gnu_thumb1_case_shi(void); +EXPORT_LIBC_SYM(__gnu_thumb1_case_shi); +extern void __gnu_thumb1_case_si(void); +EXPORT_LIBC_SYM(__gnu_thumb1_case_si); FORCE_EXPORT_SYM(__aeabi_dcmpun); FORCE_EXPORT_SYM(__aeabi_dcmple); FORCE_EXPORT_SYM(__aeabi_d2lz); @@ -330,6 +340,8 @@ FORCE_EXPORT_SYM(__aeabi_idivmod); FORCE_EXPORT_SYM(__aeabi_ldivmod); FORCE_EXPORT_SYM(__aeabi_ul2f); FORCE_EXPORT_SYM(__aeabi_dcmpge); +FORCE_EXPORT_SYM(__aeabi_lmul); +#endif #if defined (CONFIG_CPP) FORCE_EXPORT_SYM(__cxa_pure_virtual); diff --git a/platform.txt b/platform.txt index ee295bd0a..493e6ba13 100644 --- a/platform.txt +++ b/platform.txt @@ -249,7 +249,15 @@ tools.picotool.path={runtime.tools.rp2040tools.path} tools.picotool.cmd=rp2040load tools.picotool.upload.params.verbose=-v tools.picotool.upload.params.quiet= -tools.picotool.upload.pattern="{path}/{cmd}" {upload.verbose} -D "{build.path}/{build.project_name}.elf" +tools.picotool.upload.pattern="{path}/{cmd}" {upload.verbose} -D "{build.path}/{build.project_name}" + +tools.picotool.erase.params.verbose= +tools.picotool.erase.params.quiet= +tools.picotool.erase.pattern= + +tools.picotool.bootloader.params.verbose=-v +tools.picotool.bootloader.params.quiet= +tools.picotool.bootloader.pattern="{path}/{cmd}" {upload.verbose} -D "{runtime.platform.path}/firmwares/{bootloader.file}" # # IMGTOOL diff --git a/variants/arduino_nano_connect_rp2040/arduino_nano_connect_rp2040.conf b/variants/arduino_nano_connect_rp2040/arduino_nano_connect_rp2040.conf new file mode 100644 index 000000000..c70fb0a44 --- /dev/null +++ b/variants/arduino_nano_connect_rp2040/arduino_nano_connect_rp2040.conf @@ -0,0 +1,34 @@ +# Copyright (c) Arduino s.r.l. and/or its affiliated companies +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_USB_DEVICE_STACK=y +CONFIG_USB_DEVICE_PRODUCT="Arduino Nano RP2040 Connect" +CONFIG_USB_DEVICE_MANUFACTURER="Arduino" +CONFIG_USB_DEVICE_VID=0x2341 +CONFIG_USB_DEVICE_PID=0x005E + +CONFIG_SERIAL=y +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_UART_LINE_CTRL=y + +CONFIG_USB_CDC_ACM=y +CONFIG_USB_CDC_ACM_RINGBUF_SIZE=1024 +CONFIG_UART_LINE_CTRL=y +CONFIG_CDC_ACM_DTE_RATE_CALLBACK_SUPPORT=y + +CONFIG_LLEXT_STORAGE_WRITABLE=n + +CONFIG_HEAP_MEM_POOL_SIZE=16384 +CONFIG_MAIN_STACK_SIZE=32768 +CONFIG_LLEXT_HEAP_SIZE=64 +CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=-1 + +CONFIG_LOG_BACKEND_UART=y +CONFIG_LOG_BACKEND_UART_AUTOSTART=n +CONFIG_LOG_DEFAULT_LEVEL=2 + +CONFIG_ADC=y +CONFIG_DAC=n +CONFIG_PWM=y + diff --git a/variants/arduino_nano_connect_rp2040/arduino_nano_connect_rp2040.overlay b/variants/arduino_nano_connect_rp2040/arduino_nano_connect_rp2040.overlay new file mode 100644 index 000000000..ceb001266 --- /dev/null +++ b/variants/arduino_nano_connect_rp2040/arduino_nano_connect_rp2040.overlay @@ -0,0 +1,290 @@ +/* + * Copyright (c) Arduino s.r.l. and/or its affiliated companies + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/ { + zephyr,user { + /* + * Arduino Nano RP2040 Connect pin mapping + * Arduino Pin -> RP2040 GPIO + */ + digital-pin-gpios = <&gpio0 1 0>, /* D0 - RX */ + <&gpio0 0 0>, /* D1 - TX */ + <&gpio0 25 0>, /* D2 */ + <&gpio0 15 0>, /* D3 */ + <&gpio0 16 0>, /* D4 */ + <&gpio0 17 0>, /* D5 */ + <&gpio0 18 0>, /* D6 */ + <&gpio0 19 0>, /* D7 */ + <&gpio0 20 0>, /* D8 */ + <&gpio0 21 0>, /* D9 */ + <&gpio0 5 0>, /* D10 - SPI SS */ + <&gpio0 7 0>, /* D11 - SPI MOSI */ + <&gpio0 4 0>, /* D12 - SPI MISO */ + <&gpio0 6 0>, /* D13 - SPI SCK / LED */ + <&gpio0 26 0>, /* D14 / A0 */ + <&gpio0 27 0>, /* D15 / A1 */ + <&gpio0 28 0>, /* D16 / A2 */ + <&gpio0 29 0>, /* D17 / A3 */ + <&gpio0 12 0>, /* D18 / A4 / SDA */ + <&gpio0 13 0>, /* D19 / A5 / SCL */ + <&gpio0 2 0>, /* D20 - NINA GPIO0 */ + <&gpio0 24 0>, /* D21 - IMU IRQ */ + <&gpio0 22 0>, /* D22 - PDM DIN */ + <&gpio0 23 0>, /* D23 - PDM CLK */ + <&gpio0 3 0>, /* D24 - NINA RESET */ + <&gpio0 8 0>, /* D25 - NINA SPI MISO */ + <&gpio0 9 0>, /* D26 - NINA SPI CS */ + <&gpio0 10 0>, /* D27 - NINA SPI ACK */ + <&gpio0 11 0>, /* D28 - NINA SPI MOSI */ + <&gpio0 14 0>; /* D29 - NINA SPI SCK */ + + builtin-led-gpios = <&gpio0 6 0>; /* D13 = GPIO6 */ + + /* + * PWM-capable pins on Arduino Nano RP2040 Connect + * Listed by Arduino pin number with corresponding GPIO + */ + pwm-pin-gpios = <&gpio0 25 0>, /* D2 -> GPIO25 -> PWM4B */ + <&gpio0 15 0>, /* D3 -> GPIO15 -> PWM7B */ + <&gpio0 16 0>, /* D4 -> GPIO16 -> PWM0A */ + <&gpio0 17 0>, /* D5 -> GPIO17 -> PWM0B */ + <&gpio0 18 0>, /* D6 -> GPIO18 -> PWM1A */ + <&gpio0 19 0>, /* D7 -> GPIO19 -> PWM1B */ + <&gpio0 20 0>, /* D8 -> GPIO20 -> PWM2A */ + <&gpio0 21 0>, /* D9 -> GPIO21 -> PWM2B */ + <&gpio0 5 0>, /* D10 -> GPIO5 -> PWM2B */ + <&gpio0 7 0>, /* D11 -> GPIO7 -> PWM3B */ + <&gpio0 4 0>, /* D12 -> GPIO4 -> PWM2A */ + <&gpio0 6 0>; /* D13 -> GPIO6 -> PWM3A (LED) */ + + adc-pin-gpios = <&gpio0 26 0>, /* A0 -> GPIO26 -> ADC0 */ + <&gpio0 27 0>, /* A1 -> GPIO27 -> ADC1 */ + <&gpio0 28 0>, /* A2 -> GPIO28 -> ADC2 */ + <&gpio0 29 0>; /* A3 -> GPIO29 -> ADC3 */ + + pwms = <&pwm 25 255 PWM_POLARITY_NORMAL>, /* D2 GPIO25 */ + <&pwm 15 255 PWM_POLARITY_NORMAL>, /* D3 GPIO15 */ + <&pwm 16 255 PWM_POLARITY_NORMAL>, /* D4 GPIO16 */ + <&pwm 17 255 PWM_POLARITY_NORMAL>, /* D5 GPIO17 */ + <&pwm 18 255 PWM_POLARITY_NORMAL>, /* D6 GPIO18 */ + <&pwm 19 255 PWM_POLARITY_NORMAL>, /* D7 GPIO19 */ + <&pwm 20 255 PWM_POLARITY_NORMAL>, /* D8 GPIO20 */ + <&pwm 21 255 PWM_POLARITY_NORMAL>, /* D9 GPIO21 */ + <&pwm 5 255 PWM_POLARITY_NORMAL>, /* D10 GPIO5 */ + <&pwm 7 255 PWM_POLARITY_NORMAL>, /* D11 GPIO7 */ + <&pwm 4 255 PWM_POLARITY_NORMAL>, /* D12 GPIO4 */ + <&pwm 6 255 PWM_POLARITY_NORMAL>; /* D13 GPIO6 */ + + io-channels = <&adc 0>, /* A0 */ + <&adc 1>, /* A1 */ + <&adc 2>, /* A2 */ + <&adc 3>; /* A3 */ + + serials = <&board_cdc_acm_uart>, <&uart0>, <&uart1>, <&uart2>; + cdc-acm = <&board_cdc_acm_uart>; + i2cs = <&i2c0>; + spis = <&spi0>, <&spi1>; + }; +}; + +&pinctrl { + /* + * Additional PWM pin control for Arduino analogWrite() + * PWM slice assignment: GPIO / 2 (integer division) + * PWM channel: A if GPIO even, B if GPIO odd + */ + + /* D4 -> GPIO16 -> PWM0A */ + pwm_ch0a_default: pwm_ch0a_default { + group1 { + pinmux = ; + }; + }; + + /* D5 -> GPIO17 -> PWM0B */ + pwm_ch0b_default: pwm_ch0b_default { + group1 { + pinmux = ; + }; + }; + + /* D6 -> GPIO18 -> PWM1A */ + pwm_ch1a_default: pwm_ch1a_default { + group1 { + pinmux = ; + }; + }; + + /* D7 -> GPIO19 -> PWM1B */ + pwm_ch1b_default: pwm_ch1b_default { + group1 { + pinmux = ; + }; + }; + + /* + * D8 -> GPIO20 -> PWM2A, + * D12 -> GPIO4 -> PWM2A + */ + pwm_ch2a_default: pwm_ch2a_default { + group1 { + pinmux = , ; + }; + }; + + /* D9 -> GPIO21 -> PWM2B, D10 -> GPIO5 -> PWM2B */ + pwm_ch2b_default: pwm_ch2b_default { + group1 { + pinmux = , ; + }; + }; + + /* D13 -> GPIO6 -> PWM3A (LED) */ + pwm_ch3a_default: pwm_ch3a_default { + group1 { + pinmux = ; + }; + }; + + /* D11 -> GPIO7 -> PWM3B */ + pwm_ch3b_default: pwm_ch3b_default { + group1 { + pinmux = ; + }; + }; + + /* D2 -> GPIO25 -> PWM4B */ + pwm_ch4b_default: pwm_ch4b_default { + group1 { + pinmux = ; + }; + }; + + /* D3 -> GPIO15 -> PWM7B */ + pwm_ch7b_default: pwm_ch7b_default { + group1 { + pinmux = ; + }; + }; + + /* + * UART1 pin control (NINA module) + * TX -> GPIO8 + * RX -> GPIO9 + * CTS -> GPIO10 + * RTS -> GPIO11 + */ + uart1_default: uart1_default { + group1 { + pinmux = ; + }; + group2 { + pinmux = ; + input-enable; + }; + group3 { + pinmux = ; + input-enable; + }; + group4 { + pinmux = ; + }; + }; + +}; + +&pwm { + status = "okay"; + pinctrl-0 = <&pwm_ch0a_default &pwm_ch0b_default + &pwm_ch1a_default &pwm_ch1b_default + &pwm_ch2a_default &pwm_ch2b_default + &pwm_ch3a_default &pwm_ch3b_default + &pwm_ch4b_default &pwm_ch7b_default>; + pinctrl-names = "default"; + divider-frac-4 = <15>; + divider-int-4 = <255>; +}; + +&adc { + #address-cells = <1>; + #size-cells = <0>; + + /* A0 -> GPIO26 -> ADC0 */ + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + /* A1 -> GPIO27 -> ADC1 */ + channel@1 { + reg = <1>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + /* A2 -> GPIO28 -> ADC2 */ + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; + + /* A3 -> GPIO29 -> ADC3 */ + channel@3 { + reg = <3>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; + +/* UART1 for NINA module */ +&uart1 { + status = "okay"; + pinctrl-0 = <&uart1_default>; + pinctrl-names = "default"; + current-speed = <115200>; + hw-flow-control; +}; + +&zephyr_udc0 { + board_cdc_acm_uart: board_cdc_acm_uart { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +&flash0 { + partitions { + user_sketch: partition@e0000 { + reg = <0x0E0000 0x20000>; + }; + }; +}; + +nina_spi: &spi1 { + status = "okay"; + /delete-node/ nina_w102@0; +}; + +/* + * Alias for NINA WiFi UART (uart1) without hardware flow control, + * used for firmware flashing mode. Normal operation uses uart1 directly + * (with hw-flow-control enabled above). Having both lets Arduino sketch + * code select the right configuration at compile time. + */ +uart2: &uart1 { + /delete-node/ nina_prog; +}; diff --git a/variants/arduino_nano_connect_rp2040/nina_pins.cpp b/variants/arduino_nano_connect_rp2040/nina_pins.cpp new file mode 100644 index 000000000..bdfb9da60 --- /dev/null +++ b/variants/arduino_nano_connect_rp2040/nina_pins.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (c) Arduino s.r.l. and/or its affiliated companies + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "nina_pins.h" + +// RGB LEDs on NINA module +NinaPin LEDR(27); +NinaPin LEDG(25); +NinaPin LEDB(26); + +// Analog pins on NINA module +NinaPin A4(34); +NinaPin A5(39); +NinaPin A6(36); +NinaPin A7(35); diff --git a/variants/arduino_nano_connect_rp2040/nina_pins.h b/variants/arduino_nano_connect_rp2040/nina_pins.h new file mode 100644 index 000000000..2c2d9198f --- /dev/null +++ b/variants/arduino_nano_connect_rp2040/nina_pins.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) Arduino s.r.l. and/or its affiliated companies + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _NINA_PINS_ +#define _NINA_PINS_ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include "Arduino.h" + +/****************************************************************************** + * PREPROCESSOR-MAGIC + ******************************************************************************/ + +#if __has_include("WiFiNINA.h") +# define NINA_ATTRIBUTE +#else +# define NINA_ATTRIBUTE __attribute__ ((error("Please include WiFiNINA.h to use this pin"))) +#endif + +/****************************************************************************** + * TYPEDEF + ******************************************************************************/ + +int getAnalogReadResolution(); + +class NinaPin { +public: + NinaPin(int _pin) : pin(_pin) {}; + int get() { + return pin; + }; + int analogReadResolution() { + return getAnalogReadResolution(); + }; + bool operator== (NinaPin const & other) const { + return pin == other.pin; + } + //operator int() = delete; + __attribute__ ((error("Change me to a #define"))) operator int(); +private: + int pin; +}; + +extern NinaPin LEDR; +extern NinaPin LEDG; +extern NinaPin LEDB; +extern NinaPin A4; +extern NinaPin A5; +extern NinaPin A6; +extern NinaPin A7; + +#define NINA_PINS_AS_CLASS + +/****************************************************************************** + * FUNCTION DECLARATION + ******************************************************************************/ + +void NINA_ATTRIBUTE pinMode (NinaPin pin, PinMode mode); +PinStatus NINA_ATTRIBUTE digitalRead (NinaPin pin); +void NINA_ATTRIBUTE digitalWrite(NinaPin pin, PinStatus value); +int NINA_ATTRIBUTE analogRead (NinaPin pin); +void NINA_ATTRIBUTE analogWrite (NinaPin pin, int value); + +#undef NINA_ATTRIBUTE + +#endif /* _NINA_PINS_ */ diff --git a/variants/arduino_nano_connect_rp2040/pins_arduino.h b/variants/arduino_nano_connect_rp2040/pins_arduino.h new file mode 100644 index 000000000..490e179d3 --- /dev/null +++ b/variants/arduino_nano_connect_rp2040/pins_arduino.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) Arduino s.r.l. and/or its affiliated companies + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +// For compatibility with Arduino mbed core +#include "variant.h" diff --git a/variants/arduino_nano_connect_rp2040/variant.cpp b/variants/arduino_nano_connect_rp2040/variant.cpp new file mode 100644 index 000000000..d98a78e33 --- /dev/null +++ b/variants/arduino_nano_connect_rp2040/variant.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) Arduino s.r.l. and/or its affiliated companies + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "Arduino.h" + +#define NANO_RP2040_LED_PIN 6 + +void _on_1200_bps() { + /* + * Call reset_usb_boot() directly via the RP2040 ROM lookup table. + * This avoids depending on rom_reset_usb_boot from libpico (not + * exported to LLEXT sketches). The ROM is always at 0x00000000: + * 0x14: 16-bit offset of the function table + * 0x18: 16-bit offset of the table lookup function + * 'UB' is the 2-byte code for reset_usb_boot (ROM_FUNC_RESET_USB_BOOT). + */ + typedef void *(*rom_lookup_fn)(uint16_t *table, uint32_t code); + typedef void __attribute__((noreturn)) (*reset_usb_boot_fn)(uint32_t, uint32_t); + + rom_lookup_fn lookup = (rom_lookup_fn)(uintptr_t)(*(volatile uint16_t *)0x18u); + uint16_t *table = (uint16_t *)(uintptr_t)(*(volatile uint16_t *)0x14u); + + reset_usb_boot_fn fn = (reset_usb_boot_fn) lookup(table, ('U' | ('B' << 8))); + fn(1u << NANO_RP2040_LED_PIN, 0u); +} diff --git a/variants/arduino_nano_connect_rp2040/variant.h b/variants/arduino_nano_connect_rp2040/variant.h new file mode 100644 index 000000000..6babd7cc0 --- /dev/null +++ b/variants/arduino_nano_connect_rp2040/variant.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) Arduino s.r.l. and/or its affiliated companies + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifndef __PINS_ARDUINO__ +#define __PINS_ARDUINO__ + +#include + +// Pin count +// ---- +#define PINS_COUNT (30u) +#define NUM_DIGITAL_PINS (30u) +#define NUM_ANALOG_INPUTS (4u) +#define NUM_ANALOG_OUTPUTS (0u) + +// LEDs +// ---- +#define PIN_LED (13u) +#define LED_BUILTIN PIN_LED + +// Note: RGB LEDs (LEDR, LEDG, LEDB) are on the NINA module +// and require WiFiNINA library to control + +// Analog pins +// ----------- +// Note: A0-A3 are defined as enum values in Arduino.h via devicetree +#define PIN_A0 (14u) +#define PIN_A1 (15u) +#define PIN_A2 (16u) +#define PIN_A3 (17u) + +#define ADC_RESOLUTION 12 + +// PDM Interfaces +// --------------- +#define PIN_PDM_CLK (23) +#define PIN_PDM_DIN (22) + +// IMU Interrupt +#define INT_IMU (21) + +// Serial +// ------ +#define PIN_SERIAL_RX (0ul) +#define PIN_SERIAL_TX (1ul) + +#define SERIAL_HOWMANY 3 + +// Serial1: Hardware UART (D0/D1) +#define SERIAL1_TX (1u) +#define SERIAL1_RX (0u) + +// Serial2: NINA HCI (D25/D26 with flow control) +#define SERIAL2_TX (25u) +#define SERIAL2_RX (26u) +#define SERIAL2_CTS (27u) +#define SERIAL2_RTS (28u) + +// Serial3: NINA communication (D25/D26) +#define SERIAL3_TX (25u) +#define SERIAL3_RX (26u) + +// USB CDC +#define SERIAL_CDC 1 +#define HAS_UNIQUE_ISERIAL_DESCRIPTOR +#define BOARD_VENDORID 0x2341 +#define BOARD_PRODUCTID 0x005E +#define BOARD_NAME "Arduino Nano RP2040 Connect" + +#define USB_MAX_POWER (500) + +// SPI +// --- +#define SPI_HOWMANY (2) + +#define PIN_SPI_MISO (12u) +#define PIN_SPI_MOSI (11u) +#define PIN_SPI_SCK (13u) +#define PIN_SPI_SS (10u) + +static const uint8_t SS = PIN_SPI_SS; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +// SPI1 (NINA WiFi module SPI bus) +#define PIN_SPI1_MISO (25u) // GPIO8 +#define PIN_SPI1_SS (26u) // GPIO9 +#define PIN_SPI1_MOSI (28u) // GPIO11 +#define PIN_SPI1_SCK (29u) // GPIO14 + +// Wire (I2C) +// ---------- +#define WIRE_HOWMANY (1) + +#define PIN_WIRE_SDA (18u) +#define PIN_WIRE_SCL (19u) + +static const uint8_t SDA = PIN_WIRE_SDA; +static const uint8_t SCL = PIN_WIRE_SCL; + +// NINA WiFi module pins +// --------------------- +#define NINA_RESETN (24u) +#define NINA_GPIO0 (20u) + +#define SPIWIFI_SS (26u) +#define SPIWIFI_ACK (27u) +#define SPIWIFI_RESET (NINA_RESETN) + +// Crypto (ATECC608A on I2C bus) +#define CRYPTO_WIRE Wire + +// NINA WiFi SPI interface +#define SPIWIFI SPI1 + +// Serial port definitions +#define SERIAL_PORT_USBVIRTUAL SerialUSB +#define SERIAL_PORT_MONITOR SerialUSB +#define SERIAL_PORT_HARDWARE Serial1 +#define SERIAL_PORT_HARDWARE_OPEN Serial2 + +// NINA Serial aliases +#define SerialNina Serial3 +#define SerialHCI Serial2 + +// Note: nina_pins.h should be included after Arduino.h +// It is automatically included by WiFiNINA.h when needed + +#endif /* __PINS_ARDUINO__ */ From 2052747b4f11034ed3f25df24baea994f526fc2a Mon Sep 17 00:00:00 2001 From: Gilberto Conti Date: Thu, 16 Apr 2026 17:58:49 +0200 Subject: [PATCH 4/4] libraries: spi: tmp fix for spi init Signed-off-by: Gilberto Conti --- libraries/SPI/SPI.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/SPI/SPI.cpp b/libraries/SPI/SPI.cpp index cbcaa1b22..509cfe99a 100644 --- a/libraries/SPI/SPI.cpp +++ b/libraries/SPI/SPI.cpp @@ -115,7 +115,9 @@ void arduino::ZephyrSPI::detachInterrupt() { } void arduino::ZephyrSPI::begin() { - spi_dev->ops.init(spi_dev); + if (!device_is_ready(spi_dev)) { + spi_dev->ops.init(spi_dev); + } } void arduino::ZephyrSPI::end() {