stm32: Add support for STM32N6xx MCUs and two N6 boards#17171
Conversation
|
Code size report: |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #17171 +/- ##
=======================================
Coverage 98.23% 98.23%
=======================================
Files 171 171
Lines 22140 22140
=======================================
Hits 21749 21749
Misses 391 391 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| // TODO: if (HAL_PWREx_ConfigSupply(PWR_EXTERNAL_SOURCE_SUPPLY ) != HAL_OK) | ||
| //xspi_flash_init(); | ||
| } | ||
|
|
There was a problem hiding this comment.
| #define OMV_BOOT_MAGIC_ADDR (0x3401FFFCU) | |
| #define OMV_BOOT_MAGIC_VALUE (0xB00710ADU) | |
| void board_enter_bootloader(void) { | |
| *((uint32_t *) OMV_BOOT_MAGIC_ADDR) = OMV_BOOT_MAGIC_VALUE; | |
| SCB_CleanDCache(); | |
| NVIC_SystemReset(); | |
| } |
To enter our bootloader.
There was a problem hiding this comment.
Do you want to have the OPENMV_N6 board definition included in MicroPython's stm32 port (like with the OPENMV_AE3)? If so, it should work with mboot. But probably also a good idea to work with the OpenMV bootloader, which means adding this code.
Related: how did you choose address 0x3401FFFC? That's part way through SRAM1, in the FLEXRAM area. You'd need to make sure that isn't cleared on reset like the rest of SRAM1/2, and also make sure it's not overwritten by the bootloader.
There was a problem hiding this comment.
Do you want to have the OPENMV_N6 board definition included in MicroPython's stm32 port (like with the OPENMV_AE3)? If so, it should work with mboot. But probably also a good idea to work with the OpenMV bootloader, which means adding this code.
Either way is fine with me. If mboot support is kept, I can always use MP_CONFIGFILE to override and not build mboot when building our firmware.
Related: how did you choose address 0x3401FFFC? That's part way through SRAM1, in the FLEXRAM area. You'd need to make sure that isn't cleared on reset like the rest of SRAM1/2, and also make sure it's not overwritten by the bootloader.
Good point. The bootloader uses the 128K (or 64K) DTCM for its memory (heap, stack etc..), so this address is typically 0x2001FFFCU (last word of bootloader's memory) and it's the same for almost all boards. However, for some reason, this doesn't work on the N6, so I just used SRAM1. Note that SRAM1 and SRAM2 don't seem to get erased on reset, otherwise this wouldn't work, but it would still be better to use DTCM for this, but it's not working.
There was a problem hiding this comment.
Oh I think I just needed a DSB, seems the write might be buffered. The following works too:
#define OMV_BOOT_MAGIC_ADDR (0x3001FFFCU)
#define OMV_BOOT_MAGIC_VALUE (0xB00710ADU)
void board_enter_bootloader(void) {
*((uint32_t *) OMV_BOOT_MAGIC_ADDR) = OMV_BOOT_MAGIC_VALUE;
__DSB();
NVIC_SystemReset();
}There was a problem hiding this comment.
@dpgeorge There seems to be some issue with DTCM, I can't access any address above ~0x1000. This causes a fault on boot when it tries accesses the magic number address (oddly enough, it doesn't always happen). Perhaps it has something to do with security config, flexram, clocks or something else that gets enabled by the main firmware, but even from the main firmware I still can't access this memory from gdb. Anyway let's please keep the original boot address (with cache clean).
562ed7c to
6cb319e
Compare
| MICROPY_PY_NETWORK_CYW43 = 1 | ||
| MICROPY_PY_SSL = 1 | ||
| MICROPY_SSL_MBEDTLS = 1 | ||
| MICROPY_VFS_LFS2 = 1 |
There was a problem hiding this comment.
I need to be able to disable mboot USE_MBOOT ?= 1 and also we never use LFS2, just fat.
There was a problem hiding this comment.
I've made all these options use ?=.
But note that the way it's configured you'll probably want to enable USE_MBOOT because that puts the firmware in external flash. Otherwise MicroPython runs from RAM.
There was a problem hiding this comment.
Is there any way to build a flash-based image just without mboot? Like an option to do so, that gets forced to one if mboot is enabled, otherwise is user-defined?
There was a problem hiding this comment.
I think that's just USE_MBOOT=1. I guess it should be called USE_BOOTLOADER=1 but for consistency it's the mboot option.
That option is anyway local to the board (nothing outside the board uses this config option, except mboot itself). The option controls:
- which ld scripts to use
- location of
.text
There was a problem hiding this comment.
But what if you don't want to build mboot, yet still want a flash-based image? Can USE_MBOOT=1 define something like MICROPY_FLASH_BASED=1? For example:
USE_MBOOT ?= 1
MICROPY_FLASH_BASED ?= $(USE_MBOOT)
This way I can define USE_MBOOT=0 MICROPY_FLASH_BASED=1 to get a flash-based image without mboot.
There was a problem hiding this comment.
Mboot is not built unless you explicitly do make in the ports/stm32/mboot directory.
But, I can do what you suggest, it makes sense.
| #define MICROPY_HW_FLASH_MOUNT_AT_BOOT (1) // TODO enable | ||
| #define MICROPY_HW_ENABLE_RNG (0) | ||
| #define MICROPY_HW_ENABLE_RTC (0) | ||
| #define MICROPY_HW_ENABLE_RTC (1) |
There was a problem hiding this comment.
For our bootloader, we need the following:
#define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0)
extern void board_early_init(void);
#define MICROPY_BOARD_EARLY_INIT board_early_init
extern void board_enter_bootloader(void);
#define MICROPY_BOARD_ENTER_BOOTLOADER(nargs, args) board_enter_bootloader()With the following code in board.c (which could be gated if mboot is enabled):
#include STM32_HAL_H
#include "py/mphal.h"
#define OMV_BOOT_MAGIC_ADDR (0x3401FFFCU)
#define OMV_BOOT_MAGIC_VALUE (0xB00710ADU)
void board_early_init(void) {
}
void board_enter_bootloader(void) {
*((uint32_t *) OMV_BOOT_MAGIC_ADDR) = OMV_BOOT_MAGIC_VALUE;
SCB_CleanDCache();
NVIC_SystemReset();
}There was a problem hiding this comment.
I've now made the board work with both the OpenMV bootloader and mboot.
| LL_AHB5_GRP1_EnableClockLowPower(LL_AHB5_GRP1_PERIPH_OTGPHY1); | ||
|
|
||
| // Select 24MHz clock. | ||
| MODIFY_REG(USB1_HS_PHYC->USBPHYC_CR, USB_USBPHYC_CR_FSEL, 2 << USB_USBPHYC_CR_FSEL_Pos); |
There was a problem hiding this comment.
There's a proper init sequence for this in the examples in the HAL, if you want to take a look. I think it's more or less the same, but there were some delays, more force_reset/release etc...
Also, could you please add this? I use it in HS mode.
diff --git a/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h b/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h
index 3a87896b4..5906f95e0 100644
--- a/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h
+++ b/ports/stm32/usbdev/class/inc/usbd_cdc_msc_hid.h
@@ -11,7 +11,7 @@
// Work out if we should support USB high-speed device mode
#if MICROPY_HW_USB_HS \
- && (!MICROPY_HW_USB_HS_IN_FS || defined(STM32F723xx) || defined(STM32F733xx))
+ && (!MICROPY_HW_USB_HS_IN_FS || defined(STM32F723xx) || defined(STM32F733xx) || defined(STM32N6))
#define USBD_SUPPORT_HS_MODE (1)
#else
#define USBD_SUPPORT_HS_MODE (0)
There was a problem hiding this comment.
I've added the USBD_SUPPORT_HS_MODE option as above.
But testing it with #15909 shows that there is data corruption when HS is enabled. That needs investigation.
43eb2ba to
653f480
Compare
32f5690 to
5fb6eff
Compare
|
The N6 is missing from the FPU filter list in |
75dc026 to
5522b3b
Compare
OK, now fixed. |
dbb5086 to
5c6c893
Compare
|
I finally got the filesystem working. Needed to make sure all relevant code and data structures to erase/write SPI flash is in RAM, and that interrupts are fully disabled during erase/write (obvious but tricky to do). |
|
I pulled this PR and tested it again. Things still seem to be working, and I noticed some improvements (like no longer calling Update1: I get a hardfault on the first function call (to any function) after Update2: Yes it's the remap, if I change it to 888 it works but then it fails to create a filesystem (likely because it tries to read/write in 111). Also, I'm not able to move memcpy to RAM, why is this needed? Note that we link to libc, so memcpy likely has more dependencies that will also need to be moved to RAM. I'm not sure how to fix this one. As for CYW43, I just skipped testing it altogether, as it's still using SPI. Other than that, I don’t think this is ready to coexist with older MCUs yet, for example, it comments out a large chunk of powerctrl.c, so we should probably wait for now. |
| .isr_vector : | ||
| { | ||
| . = ALIGN(4); | ||
| __isr_vector_ram_start = .; | ||
|
|
||
| KEEP(*(.isr_vector)) /* Startup code */ | ||
|
|
||
| /* These functions need to run from ram to enable uart | ||
| reception during flash erase/write operations. | ||
| Defining them here ensures they're copied from | ||
| flash (in main.c) along with the isr_vector above. | ||
| */ | ||
| . = ALIGN(4); | ||
| *(.text.mp_sched_keyboard_interrupt) | ||
| *(.text.pendsv_schedule_dispatch) | ||
| *(.text.storage_systick_callback) | ||
| *(.text.SysTick_Handler) | ||
| *(.text.uart_irq_handler) | ||
| *(.text.UART*_IRQHandler) | ||
| *(.text.USART*_IRQHandler) | ||
| *(.text.HAL_GetTick) | ||
| *drivers/memory/spiflash.o(.text* .rodata*) | ||
| *boards*(.rodata.spiflash_config*) | ||
|
|
||
| . = ALIGN(4); |
There was a problem hiding this comment.
Can we use function attributes to put these in a ram_func section instead? There's an example for this in mimxrt, and then we just need to add asm code to copy this section to where it needs to go (also an example in mimxrt's startup code). This is much cleaner and will not need the code in main.c.
There was a problem hiding this comment.
There are quite a few functions that need to go in RAM, and even some HAL functions (which are inline... but the compiler still makes them non-inline). So I think it's simpler to configure that in a linker script.
There's already asm code in resethandler_m3_iram.s that does the copy. The code in main.c that copies to RAM was already there before N6 support.
There was a problem hiding this comment.
even some HAL functions (which are inline... but the compiler still makes them non-inline).
Could it be the -Os ?
There's already asm code in resethandler_m3_iram.s that does the copy. The code in main.c that copies to RAM was already there before N6 support.
So code in main is not really needed? Just for my own understanding, why do we need so much code (including HAL GPIO functions) in RAM? Is it needed when waking up from STOP mode? I assume standby doesn't really need that because it resets on wakeup.
There was a problem hiding this comment.
I've now cleaned this IRAM stuff up, and there's now only a single RAM section that needs to be copied. That copy is done in asm in resethandler_m3_iram.s.
Just for my own understanding, why do we need so much code (including HAL GPIO functions) in RAM? Is it needed when waking up from STOP mode? I assume standby doesn't really need that because it resets on wakeup.
Code in RAM is needed for two things:
- Writing to flash, which needs all the SPI flash logic from drivers/memory/spiflash.o and XSPI code
- Waking from standby! The N6 has SRAM1 retention during standby and a special SYSCFG register that you can set to tell the MCU where to resume when waking from standby. This allows it to wake up much faster from standby by executing code straightaway from SRAM1, instead of going through the bootloaders. So there needs to be enough code in RAM to be able to act like a mini bootloader that can do just enough to enable XSPI memory mapped mode.
There was a problem hiding this comment.
@dpgeorge - This is for light sleep or deepsleep? Deepsleep should always result in a reboot after waking up. Awesome for light sleep though.
There was a problem hiding this comment.
There was a problem hiding this comment.
@dpgeorge @kwagyeman Thanks for explaining and for the app note! The app note seems to imply that this is optional, so if not retained it should NVIC reset. Perhaps this is worth a support question? My goal here is to minimize the RAM code to just xspi.c, to eventually use gcc section attribute on all functions and data that file, and then all we need in our firmware is a ram_func section and copy code to the startup code.
Writing to flash, which needs all the SPI flash logic from drivers/memory/spiflash.o and XSPI code
Is spiflah.o really needed in RAM or is it a speed optimization? Could we get away with just the XSPI code as it does the actual erase/write?
There was a problem hiding this comment.
Is
spiflah.oreally needed in RAM or is it a speed optimization? Could we get away with just the XSPI code as it does the actual erase/write?
Yes it's needed. That code has all the logic to set WREN and wait for the erase to complete, for example.
Also, as mentioned, if the SPI flash is in DTR mode then it won't work with the ST ROM bootloader if the SPI flash is not fully reset upon waking from standby/deepsleep. And I don't think it is fully reset when waking from standby. If so, that would need code in SRAM1 in retention mode to reset it, before doing an NVIC reset (if that even works, I couldn't get it to bounce into the ST ROM bootloader).
There was a problem hiding this comment.
It's only using about 4k of SRAM1 at the moment. Is that really such an issue? We could try to optimise that down a bit. Eg maybe it doesn't need the app ISR moved there (although I'd suggest doing that regardless, for IRQ performance reasons.)
There was a problem hiding this comment.
if the SPI flash is in DTR mode then it won't work with the ST ROM bootloader if the SPI flash is not fully reset upon waking from standby/deepsleep
The boards are designed such that they reset the flash on NVIC reset (same should apply for devkits, we're following the reference design here), so it should fully reset the flash if an NVIC reset occurs. This feature is crucial for them to function, otherwise machine.reset() wouldn't work. So normally you just need to ensure an NVIC reset occurs somehow. How about using the address of something like this instead? Would this simplify things a bit?
__attribute__((noreturn, section(".ram_function"))) void board_reset(void) {
// NVIC_SystemReset doesn't get inlined here.
SCB->AIRCR = (uint32_t)((0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
(SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
SCB_AIRCR_SYSRESETREQ_Msk);
__DSB();
for (;;) {
__NOP();
}
}Is that really such an issue? We could try to optimise that down a bit. Eg maybe it doesn't need the app ISR moved there (although I'd suggest doing that regardless, for IRQ performance reasons.)
Probably not, it's not the memory size I'm concerned about, in fact I put all of that code in ITCM which is not used at all, it's just that I use a shared linker script so I'm trying to figure out the bare minimum customization needed for the N6.
|
|
||
| KEEP(*(.isr_vector)) /* Startup code */ | ||
|
|
||
| /* These functions need to run from ram to enable uart |
There was a problem hiding this comment.
I assume this is because IRQs are being disabled for too long? If that’s the case, would it help to re-enable IRQs after each block erase/write operation, rather than keeping them disabled across all blocks? If I’m not wrong, this is how it's done in the mimxrt and rp2 ports, and it might help avoid the need to move all those functions and their data to RAM.
There was a problem hiding this comment.
Actually, the UART functions don't need to go in RAM anymore. I've fully disabled IRQs during XSPI flash erase/write. It didn't seem to work if I just raised the IRQ priority level.
And the code in spibdev.c does enable IRQs after each block erase.
What read mode do you use for 888? Do you enable the flash in single or double data rate for octal mode? Note that all SPI flash data structures must be in RAM as well. Check that your
memcpy and memset are sometimes used by the compiler to optimise code, and it's hard to stop the compiler doing that. I'm not sure exactly where memcpy is used but that was necessary for me to get it waking up from deepsleep. |
The bootloader maps the flash in octal DTR. Yes, I copied the
|
5c6c893 to
70347c8
Compare
|
I nearly have 8-8-8 octal DTR mode working with the filesystem. Just a few things to tidy up... |
I have it working here, but I use HAL drivers: https://github.com/openmv/openmv/blob/2206dcb31c2a854c79e83cd62d6b55939f6c351a/boot/src/ports/stm32/stm32_xspi.c#L502 |
Yes, thanks, I was studying that code (you use |
Yes, I think you're right. It should have been a test like the others: (dtr) ? HAL_XSPI_INSTRUCTION_16_BITS : HAL_XSPI_INSTRUCTION_8_BITSIt's weird but it seems to be working fine either way, but I've fixed it anyway. Note that we'll be using an 8/24MBs split for the FS/ROMFS (for both N6 and AE3). It's just more useful to have a bigger ROMFS for models. I'll send a PR later to update the AE3. EDIT: For the N6, it's actually 4MBs (bootloader + firmware) 4MBs filesystem 24MBs ROMFS. |
70347c8 to
a05ace7
Compare
Yes I saw that. I don't think there's much choice, except to update to a non-broken compiler. I will make sure |
That would be anything less than the future 14.3 release. I enable it unconditionally for CM55 for now, but the following also works: ifeq ($(CPU),cortex-m55)
# Check if GCC version is less than 14.3
GCC_VERSION := $(shell arm-none-eabi-gcc -dumpversion | cut -d. -f1-2)
GCC_MAJOR := $(shell echo $(GCC_VERSION) | cut -d. -f1)
GCC_MINOR := $(shell echo $(GCC_VERSION) | cut -d. -f2)
# Convert to comparable number (14.3 becomes 1403)
GCC_VERSION_NUM := $(shell echo $$(($(GCC_MAJOR) * 100 + $(GCC_MINOR))))
# Only add the flag if version < 14.3 (1403)
ifeq ($(shell test $(GCC_VERSION_NUM) -lt 1403 && echo yes),yes)
$(warning )
$(warning *** WARNING ***)
$(warning GCC $(GCC_VERSION) has known issues with Cortex-M55)
$(warning Recommend upgrading to GCC 14.3+ for proper CM55 support)
$(warning )
CFLAGS += -fdisable-rtl-loop2_doloop
endif
endifAnyway, I've already merged this PR in our fork (with the fixed flash layout). Everything seems to be working fine so far. I don't have any more comments, I think this is ready to be merged. |
|
I've pushed a few new commits here:
I just need to retest it with the NUCLEO and DK boards, then it should be good to merge as preliminary N6 support. Any further improvements can be made in the future. |
|
I have improved a few things here (eg mboot now uses XSPI in octal mode for writing, which speeds up DFU deployment), fixed and tested NUCLEO_N657X0 and removed support for the STM32N6570_DK board (I don't have time to test, get this board working and document it, that can be done later if needed/desired). I've taken this PR out of draft/WIP, it should be pretty much ready to merge. |
|
Note GCC 14.3.1 is out and it does seem to fix the issue. It adds a bit of text size to the firmware, not sure why, but that's okay for us at least. I suggest making Makefile fail if an older toolchain is used (For example, https://github.com/openmv/openmv/blob/master/common/check_toolchain.mk) |
This is useful for interfaces that stay in memory-mapped mode by default. They can implement this method with a simple `memcpy()`. Signed-off-by: Damien George <[email protected]>
That's almost the same as FLT_EVAL_METHOD == 0, but indicates the presence of _Float16_t support. Signed-off-by: Damien George <[email protected]>
Changes in this new library version are: - Add N6 HAL at v1.1.0. Signed-off-by: Damien George <[email protected]>
Signed-off-by: Damien George <[email protected]>
This commit adds preliminary support for ST's new STM32N6xx MCUs. Supported features of this MCU so far are: - basic clock tree initialisation, running at 800MHz - fully working USB - XSPI in memory-mapped mode - machine.Pin - machine.UART - RTC and deepsleep support - SD card - filesystem - ROMFS - WiFi and BLE via cyw43-driver (SDIO backend) Note that the N6 does not have internal flash, and has some tricky boot sequence, so using a custom bootloader (mboot) is almost a necessity. Signed-off-by: Damien George <[email protected]>
See ST Errata ES0620 - Rev 0.2 section 2.1.2. Signed-off-by: iabdalkader <[email protected]>
Signed-off-by: Damien George <[email protected]>
Signed-off-by: Damien George <[email protected]>
Works in the usual USB DFU mode, and can program external SPI flash. It will enable XSPI memory-mapped mode before jumping to the application firmware in the external SPI flash. Signed-off-by: Damien George <[email protected]>
Follows the UART and I2C drivers. Signed-off-by: Damien George <[email protected]>
Signed-off-by: Damien George <[email protected]>
Signed-off-by: Damien George <[email protected]>
Signed-off-by: Damien George <[email protected]>
2143232 to
99740db
Compare
OK, good. I've tested it as well and it does fix the problem.
OK, I've made an addition for this, similar to what you suggested. |
|
Merged! |
|
@dpgeorge Amazing! Looking forward to Ethernet and ADC PRs now! |
|
1: For the ADC to work on STM32N6, the RIF(Resource Isolation Framework) attributes of ADC should be set in main.c 2: The current clock frequency of ADC is 100MHz (in powerctrlboot.c), but the sample time of ADC is only 11.5 clock (in adc.c) which is far too small. 3: Also the calling of ADC calibration function is missing for STM32N6 in adc.c |
|
@anchung-chen thanks for the hints! I've now fixed N6 ADC in PR #18009. |
Summary
This PR adds preliminary support for ST's new STM32N6xx MCUs.
Supported features of this MCU so far are:
machine.Pinmachine.UARTSupported boards:
STM32N6570_DKEdit: this board is no longer supported, can be added later if neededNote that the N6 does not have internal flash, and has some tricky boot sequence, so using a custom bootloader (mboot) is almost a necessity.
The ST CMSIS and HAL files are added verbatim here, but will eventually be moved intoEdit: N6 CMSIS and HAL files are now instm32lib.stm32lib.OpenMV have generously sponsored the development of this port.
Testing
This PR has been tested on the two N6 boards that are added here.