From f6477aff6569bc461c3837edea31a3c03edefb04 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Fri, 5 Jun 2026 19:19:49 -0700 Subject: [PATCH 1/4] stm32: Add support for FDCAN on the STM32N6. N6 is basically the same as H7 with respect to the FDCAN peripheral, and it is configured to use the same buffer settings as H7. That allows a second CAN peripheral to work (eventually, once support is added), with one CAN allocated 50% of buffer resources. Signed-off-by: Kwabena W. Agyeman Signed-off-by: Damien George --- ports/stm32/Makefile | 2 +- ports/stm32/boards/stm32n657_af.csv | 8 ++++---- ports/stm32/can.h | 4 ++-- ports/stm32/fdcan.c | 23 +++++++++++++++++------ ports/stm32/main.c | 1 + ports/stm32/mpconfigboard_common.h | 2 +- ports/stm32/mpconfigport.h | 2 +- ports/stm32/pin_defs_stm32.h | 10 ++++++++++ 8 files changed, 37 insertions(+), 15 deletions(-) diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index dfd268851e7..70f265f8d41 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -429,7 +429,7 @@ endif ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),f0 f4 f7)) HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_, hal_can.c) -else ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),g0 g4 h7)) +else ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),g0 g4 h7 n6)) HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/stm32$(MCU_SERIES)xx_, hal_fdcan.c) else ifeq ($(MCU_SERIES),$(filter $(MCU_SERIES),l4)) HAL_SRC_C += $(addprefix $(STM32LIB_HAL_BASE)/Src/Legacy/stm32$(MCU_SERIES)xx_, hal_can.c) diff --git a/ports/stm32/boards/stm32n657_af.csv b/ports/stm32/boards/stm32n657_af.csv index b269d0497c6..4a7ad20e6d6 100644 --- a/ports/stm32/boards/stm32n657_af.csv +++ b/ports/stm32/boards/stm32n657_af.csv @@ -8,8 +8,8 @@ PortA,PA5 , , , , PortA,PA8 , , , , , , , , , , , , , , , , ,ADC12_INP5 PortA,PA9 , , , , , , , , , , , , , , , , ,ADC12_INP10 PortA,PA10, , , , , , , , , , , , , , , , ,ADC12_INP11/ADC12_INN10 -PortA,PA11, , , , , ,SPI2_NSS ,CAN1_RXFD , ,UART4_RX , , , , , , , ,ADC12_INP12/ADC12_INN11 -PortA,PA12, , , , , ,SPI2_SCK ,CAN1_TXFD , ,UART4_TX , , , , , , , ,ADC12_INP13/ADC12_INN12 +PortA,PA11, , , , , ,SPI2_NSS ,CAN1_RX , ,UART4_RX , , , , , , , ,ADC12_INP12/ADC12_INN11 +PortA,PA12, , , , , ,SPI2_SCK ,CAN1_TX , ,UART4_TX , , , , , , , ,ADC12_INP13/ADC12_INN12 PortB,PB4 , , , , , , , , , , , ,SDMMC2_D3 , , , , , PortB,PB6 , , , , , ,SPI4_MISO , ,TIM15_CH1 , , , , , , , , , PortB,PB7 , , , , , ,SPI4_MOSI , ,TIM15_CH2 , , , , , , , , , @@ -36,8 +36,8 @@ PortE,PE5 , , , , PortE,PE6 , , , , , , , ,USART1_RX , , , , , , , , , PortE,PE7 , , , , , , , , ,UART7_RX , , , , , , , , PortE,PE8 , , , , , , , , ,UART7_TX , , , , , , , , -PortE,PE11, , , , , ,SPI4_NSS ,CAN3_TXFD , , , , , , , , , , -PortE,PE12, , , , , ,SPI4_SCK ,CAN3_RXFD , , , , , , , , , , +PortE,PE11, , , , , ,SPI4_NSS ,CAN3_TX , , , , , , , , , , +PortE,PE12, , , , , ,SPI4_SCK ,CAN3_RX , , , , , , , , , , PortE,PE13, , , , ,I2C4_SCL , , , , , , , , , , , , PortE,PE14, , , , ,I2C4_SDA , , , , , , , , , , , , PortE,PE15, , , , , ,SPI5_SCK , , , , , , , , , , , diff --git a/ports/stm32/can.h b/ports/stm32/can.h index ff73e1a74d2..588a225ef36 100644 --- a/ports/stm32/can.h +++ b/ports/stm32/can.h @@ -62,7 +62,7 @@ #if defined(STM32G4) #define CAN_HW_MAX_STD_FILTER 28 #define CAN_HW_MAX_EXT_FILTER 8 -#elif defined(STM32H7) +#elif defined(STM32H7) || defined(STM32N6) // The RAM filtering section is configured for 64 x 1 word elements for 11-bit standard // identifiers, and 31 x 2 words elements for 29-bit extended identifiers. // The total number of words reserved for the filtering per FDCAN instance is 126 words. @@ -123,7 +123,7 @@ typedef struct { unsigned rx_fifo1_pending; } can_counters_t; -#if defined(STM32H7) +#if defined(STM32H7) || defined(STM32N6) #define CAN_TX_QUEUE_LEN 16 #else // FDCAN STM32G4, bxCAN diff --git a/ports/stm32/fdcan.c b/ports/stm32/fdcan.c index a7fcf7330b1..8dba12f7c7a 100644 --- a/ports/stm32/fdcan.c +++ b/ports/stm32/fdcan.c @@ -53,17 +53,22 @@ #define FDCAN_IT_RX_FULL_MASK (FDCAN_IT_RX_FIFO0_FULL | FDCAN_IT_RX_FIFO1_FULL) #define FDCAN_IT_RX_MESSAGE_LOST_MASK (FDCAN_IT_RX_FIFO0_MESSAGE_LOST | FDCAN_IT_RX_FIFO1_MESSAGE_LOST) -#if defined(STM32H7) -// adaptations for H7 to G4 naming convention in HAL +#if defined(STM32H7) || defined(STM32N6) +// adaptations for H7/N6 to G4 naming convention in HAL #define FDCAN_IT_GROUP_RX_FIFO0 (FDCAN_ILS_RF0NL | FDCAN_ILS_RF0FL | FDCAN_ILS_RF0LL) +#if defined(STM32H7) #define FDCAN_IT_GROUP_BIT_LINE_ERROR (FDCAN_ILS_EPE | FDCAN_ILS_ELOE) #define FDCAN_IT_GROUP_PROTOCOL_ERROR (FDCAN_ILS_ARAE | FDCAN_ILS_PEDE | FDCAN_ILS_PEAE | FDCAN_ILS_WDIE | FDCAN_ILS_BOE | FDCAN_ILS_EWE) +#else +#define FDCAN_IT_GROUP_BIT_LINE_ERROR (FDCAN_ILS_EPL | FDCAN_ILS_ELOL) +#define FDCAN_IT_GROUP_PROTOCOL_ERROR (FDCAN_ILS_ARAL | FDCAN_ILS_PEDL | FDCAN_ILS_PEAL | FDCAN_ILS_WDIL | FDCAN_ILS_BOL | FDCAN_ILS_EWL) +#endif #define FDCAN_IT_GROUP_RX_FIFO1 (FDCAN_ILS_RF1NL | FDCAN_ILS_RF1FL | FDCAN_ILS_RF1LL) // The dedicated Message RAM should be 2560 words, but the way it's defined in stm32h7xx_hal_fdcan.c // as (SRAMCAN_BASE + FDCAN_MESSAGE_RAM_SIZE - 0x4U) limits the usable number of words to 2559 words. #define FDCAN_MESSAGE_RAM_SIZE (2560 - 1) -#endif // STM32H7 +#endif // STM32H7 || STM32N6 #if defined(STM32G4) // These HAL APIs are not implemented for STM32G4, so we implement them here... @@ -146,7 +151,7 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, can_tx_mode_t tx_mode, uint32_ init->DataTimeSeg1 = 1; init->DataTimeSeg2 = 1; init->TxFifoQueueMode = fifo_queue_mode; - #elif defined(STM32H7) + #elif defined(STM32H7) || defined(STM32N6) // The dedicated FDCAN RAM is 2560 32-bit words and shared between the FDCAN instances. // To support 2 FDCAN instances simultaneously, the Message RAM is divided in half by // setting the second FDCAN memory offset to half the RAM size. With this configuration, @@ -189,7 +194,7 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, can_tx_mode_t tx_mode, uint32_ init->RxFifo0ElmtSize = FDCAN_DATA_BYTES_64; init->RxFifo1ElmtsNbr = 24; init->RxFifo1ElmtSize = FDCAN_DATA_BYTES_64; - #endif // STM32H7 + #endif // STM32H7 || STM32N6 const machine_pin_obj_t *pins[2]; @@ -316,7 +321,9 @@ void can_clearfilter(FDCAN_HandleTypeDef *can, uint32_t f, bool is_extid) { uint32_t can_get_source_freq(void) { // Find CAN kernel clock - #if defined(STM32H7) + #if defined(STM32N6) + return HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_FDCAN); + #elif defined(STM32H7) switch (__HAL_RCC_GET_FDCAN_SOURCE()) { case RCC_FDCANCLKSOURCE_HSE: return HSE_VALUE; @@ -362,7 +369,11 @@ static void encode_datalength(CanTxMsgTypeDef *txmsg) { size_t len_bytes = txmsg->DataLength; for (mp_uint_t i = 0; i < MP_ARRAY_SIZE(DLCtoBytes); i++) { if (len_bytes <= DLCtoBytes[i]) { + #if defined(STM32N6) + txmsg->DataLength = i; + #else txmsg->DataLength = (i << 16); + #endif return; } } diff --git a/ports/stm32/main.c b/ports/stm32/main.c index 17111c6df98..c6b67696e54 100644 --- a/ports/stm32/main.c +++ b/ports/stm32/main.c @@ -420,6 +420,7 @@ void stm32_main(uint32_t reset_mode) { // Enable some APB peripherals during sleep. LL_APB1_GRP1_EnableClockLowPower(LL_APB1_GRP1_PERIPH_ALL); // I2C, I3C, LPTIM, SPI, TIM, UART, WWDG + LL_APB1_GRP2_EnableClockLowPower(LL_APB1_GRP2_PERIPH_FDCAN); // FDCAN LL_APB2_GRP1_EnableClockLowPower(LL_APB2_GRP1_PERIPH_ALL); // SAI, SPI, TIM, UART LL_APB4_GRP1_EnableClockLowPower(LL_APB4_GRP1_PERIPH_ALL); // I2C, LPTIM, LPUART, RTC, SPI #endif diff --git a/ports/stm32/mpconfigboard_common.h b/ports/stm32/mpconfigboard_common.h index e21f474d7af..36f16525056 100644 --- a/ports/stm32/mpconfigboard_common.h +++ b/ports/stm32/mpconfigboard_common.h @@ -742,7 +742,7 @@ // Enable CAN if there are any peripherals defined #if defined(MICROPY_HW_CAN1_TX) || defined(MICROPY_HW_CAN2_TX) || defined(MICROPY_HW_CAN3_TX) #define MICROPY_HW_ENABLE_CAN (1) -#if defined(STM32G0) || defined(STM32G4) || defined(STM32H7) +#if defined(STM32G0) || defined(STM32G4) || defined(STM32H7) || defined(STM32N6) #define MICROPY_HW_ENABLE_FDCAN (1) // define for MCUs with FDCAN #endif #else diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h index 19d4ef8ae0f..c2200e09370 100644 --- a/ports/stm32/mpconfigport.h +++ b/ports/stm32/mpconfigport.h @@ -123,7 +123,7 @@ #define MICROPY_PY_MACHINE_BITSTREAM (1) #endif #ifndef MICROPY_PY_MACHINE_CAN -#if defined(MICROPY_HW_CAN1_TX) || defined(MICROPY_HW_CAN2_TX) +#if defined(MICROPY_HW_CAN1_TX) || defined(MICROPY_HW_CAN2_TX) || defined(MICROPY_HW_CAN3_TX) #define MICROPY_PY_MACHINE_CAN (1) #else #define MICROPY_PY_MACHINE_CAN (0) diff --git a/ports/stm32/pin_defs_stm32.h b/ports/stm32/pin_defs_stm32.h index 645ec5b2df5..1020381abb8 100644 --- a/ports/stm32/pin_defs_stm32.h +++ b/ports/stm32/pin_defs_stm32.h @@ -133,6 +133,16 @@ enum { #define GPIO_AF9_CAN2 GPIO_AF9_FDCAN2 #endif +#if defined(STM32N6) +// Make N6 FDCAN more like CAN +#define CAN1 FDCAN1 +#define CAN2 FDCAN2 +#define CAN3 FDCAN3 +#define GPIO_AF6_CAN1 GPIO_AF6_FDCAN1 +#define GPIO_AF6_CAN2 GPIO_AF6_FDCAN2 +#define GPIO_AF6_CAN3 GPIO_AF6_FDCAN3 +#endif + enum { PIN_ADC1 = (1 << 0), PIN_ADC2 = (1 << 1), From 917afdc1b9895d74877746fdacf3d7515e3db65d Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Fri, 5 Jun 2026 19:20:17 -0700 Subject: [PATCH 2/4] stm32/boards/OPENMV_N6: Enable FDCAN on the OpenMV N6. Signed-off-by: Kwabena W. Agyeman Signed-off-by: Damien George --- ports/stm32/boards/OPENMV_N6/mpconfigboard.h | 9 +++++++++ ports/stm32/boards/OPENMV_N6/pins.csv | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/ports/stm32/boards/OPENMV_N6/mpconfigboard.h b/ports/stm32/boards/OPENMV_N6/mpconfigboard.h index 751da650bfe..cc8a79e8944 100644 --- a/ports/stm32/boards/OPENMV_N6/mpconfigboard.h +++ b/ports/stm32/boards/OPENMV_N6/mpconfigboard.h @@ -78,6 +78,15 @@ #define MICROPY_HW_SPI4_MISO (pyb_pin_SPI4_MISO) #define MICROPY_HW_SPI4_MOSI (pyb_pin_SPI4_MOSI) +// FDCAN bus +#define MICROPY_HW_CAN1_NAME "FDCAN1" +#define MICROPY_HW_CAN1_TX (pyb_pin_CAN1_TX) +#define MICROPY_HW_CAN1_RX (pyb_pin_CAN1_RX) +// Support is not yet added for FDCAN CAN3 (see fdcan.c for details) +// #define MICROPY_HW_CAN3_NAME "FDCAN3" +// #define MICROPY_HW_CAN3_TX (pyb_pin_CAN3_TX) +// #define MICROPY_HW_CAN3_RX (pyb_pin_CAN3_RX) + // USER is pulled high, and pressing the button makes the input go low. #define MICROPY_HW_USRSW_PIN (pyb_pin_BUTTON) #define MICROPY_HW_USRSW_PULL (GPIO_NOPULL) diff --git a/ports/stm32/boards/OPENMV_N6/pins.csv b/ports/stm32/boards/OPENMV_N6/pins.csv index 2ca075a154a..7c36181c016 100644 --- a/ports/stm32/boards/OPENMV_N6/pins.csv +++ b/ports/stm32/boards/OPENMV_N6/pins.csv @@ -13,6 +13,8 @@ SPI2_CS,PA11 SPI2_SCK,PA12 UART4_RX,PA11 UART4_TX,PA12 +CAN1_RX,PA11 +CAN1_TX,PA12 P3,PA11 P2,PA12 ,PA13 @@ -92,6 +94,8 @@ SPI4_CS,PE11 SPI4_SCK,PE12 P15,PE11 P16,PE12 +CAN3_TX,PE11 +CAN3_RX,PE12 I2C4_SCL,PE13 I2C4_SDA,PE14 ,PE15 From 3e1f00ec62b067fd08e7302cee64680b935678bb Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 12 Jun 2026 10:28:22 +1000 Subject: [PATCH 3/4] tests/ports/stm32/pyb_can.py: Update test to run on STM32N6. Changes: - Use the same deep-tx-queue logic as the STM32H7. - Use `time.sleep_ms()` instead of `pyb.delay()` (the latter is not available on all stm32 boards). This test now passes on OPENMV_N6. Signed-off-by: Damien George --- tests/ports/stm32/pyb_can.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/ports/stm32/pyb_can.py b/tests/ports/stm32/pyb_can.py index 8178d91fe74..fda04a38b5e 100644 --- a/tests/ports/stm32/pyb_can.py +++ b/tests/ports/stm32/pyb_can.py @@ -8,14 +8,16 @@ import micropython import pyb import sys +import time # Classic CAN (aka bxCAN) hardware has a different filter API # and some different behaviours to newer FDCAN hardware IS_CLASSIC = hasattr(CAN, "MASK16") -# STM32H7 series has a gold-plated FDCAN peripheral with much deeper TX Queue +# STM32H7/STM32N6 series have a gold-plated FDCAN peripheral with much deeper TX Queue # than all other parts -IS_H7 = (not IS_CLASSIC) and "STM32H7" in str(sys.implementation) +sys_impl = str(sys.implementation) +HAS_DEEP_TXQ = (not IS_CLASSIC) and ("STM32H7" in sys_impl or "STM32N6" in sys_impl) # test we can correctly create by id (2 handled in can2.py test) for bus in (-1, 0, 1, 3): @@ -52,7 +54,7 @@ can.setfilter(0, CAN.MASK, 0, (0, 0), extframe=False) can.send("abcd", 123, timeout=5000) -pyb.delay(10) # For FDCAN, needs some time to send +time.sleep_ms(10) # For FDCAN, needs some time to send print("any+info", can.any(0), can.info()) print(can.recv(0)) @@ -168,7 +170,7 @@ except ValueError: print("failed") else: - pyb.delay(10) + time.sleep_ms(10) r = can.recv(0) if r[0] == 0x7FF + 1 and r[4] == b"abcde": print("extframe passed") @@ -189,13 +191,13 @@ can.setfilter(0, CAN.MASK, 0, (filter_id, filter_mask), extframe=True) can.send("ok", id_ok, timeout=5, extframe=True) - pyb.delay(10) + time.sleep_ms(10) if can.any(0): msg = can.recv(0) print((hex(filter_id), hex(filter_mask), hex(msg[0]), msg[1], msg[4])) can.send("fail", id_fail, timeout=5, extframe=True) - pyb.delay(10) + time.sleep_ms(10) if can.any(0): msg = can.recv(0) print((hex(filter_id), hex(filter_mask), hex(msg[0]), msg[1], msg[4])) @@ -226,10 +228,10 @@ can.send("abcde", 2, timeout=0) can.send("abcde", 3, timeout=0) can.send("abcde", 4, timeout=0) - if not IS_H7: + if not HAS_DEEP_TXQ: can.send("abcde", 5, timeout=0) else: - # Hack around the STM32H7's deeper transmit queue by pretending this call failed + # Hack around a deep transmit queue by pretending this call failed # (STM32G4 will fail here, using otherwise the same code, so there is still some test coverage.) print("send fail ok") except OSError as e: @@ -239,7 +241,7 @@ else: print("send fail not ok", e) -pyb.delay(500) +time.sleep_ms(500) while can.any(0): print(can.recv(0)) From 34b14a6083afa9f5986bfc5d8494706c4350b831 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 12 Jun 2026 10:33:19 +1000 Subject: [PATCH 4/4] stm32/fdcan: Initialise all entries in FDCAN_InitTypeDef struct. These aren't technically needed, but good to make sure they are populated. Signed-off-by: Damien George --- ports/stm32/fdcan.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ports/stm32/fdcan.c b/ports/stm32/fdcan.c index 8dba12f7c7a..afec60e3c87 100644 --- a/ports/stm32/fdcan.c +++ b/ports/stm32/fdcan.c @@ -137,6 +137,11 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, can_tx_mode_t tx_mode, uint32_ init->NominalTimeSeg1 = bs1; // NominalTimeSeg1 = Propagation_segment + Phase_segment_1 init->NominalTimeSeg2 = bs2; + init->DataPrescaler = 1; + init->DataSyncJumpWidth = 1; + init->DataTimeSeg1 = 1; + init->DataTimeSeg2 = 1; + init->AutoRetransmission = ENABLE; init->TransmitPause = DISABLE; init->ProtocolException = ENABLE; @@ -146,10 +151,6 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, can_tx_mode_t tx_mode, uint32_ #if defined(STM32G4) init->ClockDivider = FDCAN_CLOCK_DIV1; - init->DataPrescaler = 1; - init->DataSyncJumpWidth = 1; - init->DataTimeSeg1 = 1; - init->DataTimeSeg2 = 1; init->TxFifoQueueMode = fifo_queue_mode; #elif defined(STM32H7) || defined(STM32N6) // The dedicated FDCAN RAM is 2560 32-bit words and shared between the FDCAN instances. @@ -190,6 +191,7 @@ bool can_init(CAN_HandleTypeDef *can, int can_id, can_tx_mode_t tx_mode, uint32_ // 2 words header + 16 words data field (to support up to 64 bytes of data). // The total number of words reserved for the Rx FIFOs per FDCAN instance is 864 words. init->RxBuffersNbr = 0; + init->RxBufferSize = FDCAN_DATA_BYTES_64; init->RxFifo0ElmtsNbr = 24; init->RxFifo0ElmtSize = FDCAN_DATA_BYTES_64; init->RxFifo1ElmtsNbr = 24;