From cfd74c2954fd0916a4559c0677ba0473f2262494 Mon Sep 17 00:00:00 2001 From: Amken USA <166057890+amken3d@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:12:02 -0500 Subject: [PATCH] Add stm32g0b1 support (#5150) * Add STM32G0B1 target support Introduce support for STM32G0B1 microcontrollers, including target-specific JSON files, linker scripts, and runtime initialization. This update adds hardware support for GPIO, UART, SPI, I2C, timers, and additional board-specific configurations like Nucleo-G0B1RE. * Update STM32G0 clock initialization to 64MHz and adjust related configurations Reconfigure STM32G0 to use a 64MHz system clock via PLL with HSI16 as the source. Update flash latency, prescaler settings, and I2C timing values to reflect the new frequency. * Cleanup * Cleanup * Add STM32G0-specific UART implementation Introduce a new UART implementation for the STM32G0 series with chip-specific setup and configuration methods. Update the generic STM32 UART code to exclude STM32G0. * Refactor STM32G0 runtime and machine code to utilize chip-specific register access functions Simplify and standardize register operations with dedicated setter methods in the STM32G0 runtime and machine code and cleanup redundant syntax. * Remove redundant commented-out APBENR1 register operations in STM32G0 machine code * Introduce FDCAN support for STM32G0B1 series Add FDCAN peripheral implementation targeting STM32G0B1, including support for standard, extended identifiers, and bit rate configuration. Update board files to include FDCAN pins, instances, and clock configuration for Nucleo-G0B1RE and Amken Trio boards. --- src/machine/board_amken_trio.go | 153 +++++ src/machine/board_nucleog0b1re.go | 131 +++++ src/machine/machine_stm32_exti_syscfg.go | 2 +- src/machine/machine_stm32_gpio_reva.go | 2 +- src/machine/machine_stm32_i2c_revb.go | 2 +- src/machine/machine_stm32_rng.go | 2 +- src/machine/machine_stm32_spi.go | 2 +- src/machine/machine_stm32_uart.go | 4 +- src/machine/machine_stm32g0.go | 567 ++++++++++++++++++ src/machine/machine_stm32g0_can.go | 711 +++++++++++++++++++++++ src/machine/machine_stm32g0_gpio.go | 92 +++ src/machine/machine_stm32g0_spi.go | 85 +++ src/machine/machine_stm32g0_uart.go | 86 +++ src/machine/machine_stm32go_exti.go | 26 + src/runtime/rand_hwrng.go | 2 +- src/runtime/rand_norng.go | 2 +- src/runtime/runtime_stm32g0.go | 97 ++++ src/runtime/runtime_stm32g0b1.go | 15 + targets/amken-trio.json | 7 + targets/nucleo-g0b1re.json | 6 + targets/stm32g0b1.json | 16 + targets/stm32g0b1.ld | 10 + targets/stm32g0b1cb.ld | 10 + 23 files changed, 2021 insertions(+), 9 deletions(-) create mode 100644 src/machine/board_amken_trio.go create mode 100644 src/machine/board_nucleog0b1re.go create mode 100644 src/machine/machine_stm32g0.go create mode 100644 src/machine/machine_stm32g0_can.go create mode 100644 src/machine/machine_stm32g0_gpio.go create mode 100644 src/machine/machine_stm32g0_spi.go create mode 100644 src/machine/machine_stm32g0_uart.go create mode 100644 src/machine/machine_stm32go_exti.go create mode 100644 src/runtime/runtime_stm32g0.go create mode 100644 src/runtime/runtime_stm32g0b1.go create mode 100644 targets/amken-trio.json create mode 100644 targets/nucleo-g0b1re.json create mode 100644 targets/stm32g0b1.json create mode 100644 targets/stm32g0b1.ld create mode 100644 targets/stm32g0b1cb.ld diff --git a/src/machine/board_amken_trio.go b/src/machine/board_amken_trio.go new file mode 100644 index 000000000..3382085bc --- /dev/null +++ b/src/machine/board_amken_trio.go @@ -0,0 +1,153 @@ +//go:build amken_trio + +// RabbitPNP Toolhead Board +// MCU: STM32G0B1CBTx (LQFP48, 128KB Flash, 144KB RAM) + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +// Vacuum sensors (PWM input via TIM2) +const ( + VAC1 = PA0 // TIM2_CH1 + VAC2 = PA1 // TIM2_CH2 +) + +// Motor 1 pins (stepper driver) +const ( + M1_CS = PC7 + M1_DIR = PB13 // REFL + M1_STEP = PB14 // REFR + M1_ENN = PA10 // Enable (active low) +) + +// Motor 2 pins (stepper driver) +const ( + M2_CS = PA9 + M2_DIR = PB12 // REFL + M2_STEP = PB11 // REFR + M2_ENN = PC6 // Enable (active low) +) + +// Motor 3 pins (stepper driver) +const ( + M3_CS = PB15 + M3_DIR = PB2 // REFL + M3_STEP = PB1 // REFR + M3_ENN = PA8 // Enable (active low) +) + +// LED +const ( + LED = LED1 + LED_BUILTIN = LED1 + LED1 = PB7 +) + +// Solenoid and Neopixel (PWM via TIM4) +const ( + SOLENOID = PB8 // TIM4_CH3 + NEOPIXEL = PB9 // TIM4_CH4 +) + +// Endstops +const ( + ENDSTOP_IN1 = PC13 + ENDSTOP_IN2 = PC14 +) + +// Magnetic sensor +const ( + MAG1 = PB3 +) + +// Accelerometer chip select (LIS2D on SPI2) +const ( + LIS2D_CS = PB5 +) + +// SPI1 pins (motor drivers) +const ( + SPI1_SCK_PIN = PA5 + SPI1_SDO_PIN = PA2 // MOSI + SPI1_SDI_PIN = PA6 // MISO + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN +) + +// SPI2 pins (accelerometer) +const ( + SPI2_SCK_PIN = PB10 + SPI2_SDO_PIN = PA4 // MOSI + SPI2_SDI_PIN = PA3 // MISO +) + +// I2C2 pins +const ( + I2C2_SCL_PIN = PA7 + I2C2_SDA_PIN = PB4 + I2C0_SCL_PIN = I2C2_SCL_PIN + I2C0_SDA_PIN = I2C2_SDA_PIN +) + +// FDCAN1 pins +const ( + CAN_RX = PD0 + CAN_TX = PD1 +) + +// USB pins +const ( + USB_DM = PA11 + USB_DP = PA12 +) + +// UART pins (not directly connected but required by machine package) +const ( + UART_TX_PIN = NoPin + UART_RX_PIN = NoPin +) + +var ( + // SPI1 for motor drivers + SPI1 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF0_SYSTEM, + } + SPI0 = SPI1 + + // SPI2 for accelerometer + SPI2 = &SPI{ + Bus: stm32.SPI2, + AltFuncSelector: AF1_TIM1_TIM2_TIM3_LPTIM1, + } + + // I2C2 + I2C2 = &I2C{ + Bus: stm32.I2C2, + AltFuncSelector: AF6_SPI2_USART3_USART4_I2C1, + } + I2C0 = I2C2 + + // FDCAN1 on PD0 (RX) / PD1 (TX) with onboard transceiver + CAN1 = &_CAN1 + _CAN1 = FDCAN{ + Bus: stm32.FDCAN1, + TxAltFuncSelect: AF3_FDCAN1_FDCAN2, + RxAltFuncSelect: AF3_FDCAN1_FDCAN2, + instance: 0, + } + // Alias for convenience + CAN0 = CAN1 +) + +// Suppress unused import warning for interrupt package +var _ = interrupt.New + +func init() { + // No UART configured on this board - uses USB or CAN for communication +} diff --git a/src/machine/board_nucleog0b1re.go b/src/machine/board_nucleog0b1re.go new file mode 100644 index 000000000..8c048fd0f --- /dev/null +++ b/src/machine/board_nucleog0b1re.go @@ -0,0 +1,131 @@ +//go:build nucleog0b1re + +// Schematic: https://www.st.com/resource/en/user_manual/um2324-stm32-nucleo64-boards-mb1360-stmicroelectronics.pdf +// Datasheet: https://www.st.com/resource/en/datasheet/stm32g0b1re.pdf + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + // Arduino Pins + A0 = PA0 + A1 = PA1 + A2 = PA4 + A3 = PB1 + A4 = PA11 + A5 = PA12 + + D0 = PB7 + D1 = PB6 + D2 = PA10 + D3 = PB3 + D4 = PB5 + D5 = PB4 + D6 = PB10 + D7 = PA8 + D8 = PA9 + D9 = PC7 + D10 = PB0 + D11 = PA7 + D12 = PA6 + D13 = PA5 + D14 = PB9 + D15 = PB8 +) + +// User LD4: the green LED is a user LED connected to ARDUINO signal D13 corresponding +// to STM32 I/O PA5. +const ( + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN + LED_GREEN = PA5 +) + +// User B1: the user button is connected to PC13. +const ( + BUTTON = PC13 +) + +const ( + // UART pins + // PA2 and PA3 are connected to the ST-Link Virtual Com Port (VCP) + UART_TX_PIN = PA2 + UART_RX_PIN = PA3 + + // I2C pins + // PB8 is SCL (connected to Arduino connector D15) + // PB9 is SDA (connected to Arduino connector D14) + I2C0_SCL_PIN = PB8 + I2C0_SDA_PIN = PB9 + + // SPI pins + SPI1_SCK_PIN = PA5 + SPI1_SDI_PIN = PA6 + SPI1_SDO_PIN = PA7 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN + + // CAN pins (directly accessible on Nucleo-G0B1RE board) + // FDCAN1: PA11 (TX) / PA12 (RX) using AF9 + // FDCAN2: PD12 (TX) / PD13 (RX) using AF3 + CAN1_TX_PIN = PA11 + CAN1_RX_PIN = PA12 + CAN2_TX_PIN = PD12 + CAN2_RX_PIN = PD13 +) + +var ( + // USART2 is the hardware serial port connected to the onboard ST-LINK + // debugger to be exposed as virtual COM port over USB on Nucleo boards. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: AF1_TIM1_TIM2_TIM3_LPTIM1, + RxAltFuncSelector: AF1_TIM1_TIM2_TIM3_LPTIM1, + } + DefaultUART = UART1 + + // I2C1 is documented, alias to I2C0 as well + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF6_SPI2_USART3_USART4_I2C1, + } + I2C0 = I2C1 + + // SPI1 is documented, alias to SPI0 as well + SPI1 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF0_SYSTEM, + } + SPI0 = SPI1 + + // FDCAN1 on PA11 (TX) / PA12 (RX) + CAN1 = &_CAN1 + _CAN1 = FDCAN{ + Bus: stm32.FDCAN1, + TxAltFuncSelect: AF9_FDCAN1_FDCAN2, + RxAltFuncSelect: AF9_FDCAN1_FDCAN2, + instance: 0, + } + + // FDCAN2 on PD12 (TX) / PD13 (RX) + CAN2 = &_CAN2 + _CAN2 = FDCAN{ + Bus: stm32.FDCAN2, + TxAltFuncSelect: AF3_FDCAN1_FDCAN2, + RxAltFuncSelect: AF3_FDCAN1_FDCAN2, + instance: 1, + } +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART2_LPUART2, _UART1.handleInterrupt) + // Note: FDCAN interrupts share with USB (IRQ_UCPD1_UCPD2_USB = 8) + // User should configure interrupts via SetInterrupt method if needed +} diff --git a/src/machine/machine_stm32_exti_syscfg.go b/src/machine/machine_stm32_exti_syscfg.go index f7c91f81f..51e1e1a57 100644 --- a/src/machine/machine_stm32_exti_syscfg.go +++ b/src/machine/machine_stm32_exti_syscfg.go @@ -1,4 +1,4 @@ -//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx +//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx && !stm32g0 package machine diff --git a/src/machine/machine_stm32_gpio_reva.go b/src/machine/machine_stm32_gpio_reva.go index 2fb5a0349..d7eaad0ac 100644 --- a/src/machine/machine_stm32_gpio_reva.go +++ b/src/machine/machine_stm32_gpio_reva.go @@ -1,4 +1,4 @@ -//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx +//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx && !stm32g0 package machine diff --git a/src/machine/machine_stm32_i2c_revb.go b/src/machine/machine_stm32_i2c_revb.go index 006661f9a..9e184e1be 100644 --- a/src/machine/machine_stm32_i2c_revb.go +++ b/src/machine/machine_stm32_i2c_revb.go @@ -1,4 +1,4 @@ -//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx +//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx || stm32g0 package machine diff --git a/src/machine/machine_stm32_rng.go b/src/machine/machine_stm32_rng.go index 5eaff9612..b7f8ea046 100644 --- a/src/machine/machine_stm32_rng.go +++ b/src/machine/machine_stm32_rng.go @@ -1,4 +1,4 @@ -//go:build stm32 && !(stm32f103 || stm32l0x1) +//go:build stm32 && !(stm32f103 || stm32l0x1 || stm32g0) package machine diff --git a/src/machine/machine_stm32_spi.go b/src/machine/machine_stm32_spi.go index 3c6e0b6a8..3fb9af7fb 100644 --- a/src/machine/machine_stm32_spi.go +++ b/src/machine/machine_stm32_spi.go @@ -1,4 +1,4 @@ -//go:build stm32 && !stm32f7x2 && !stm32l5x2 +//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0 package machine diff --git a/src/machine/machine_stm32_uart.go b/src/machine/machine_stm32_uart.go index 6e8806c87..6d3758244 100644 --- a/src/machine/machine_stm32_uart.go +++ b/src/machine/machine_stm32_uart.go @@ -1,8 +1,8 @@ -//go:build stm32 +//go:build stm32 && !stm32g0 package machine -// Peripheral abstraction layer for UARTs on the stm32 family. +// Peripheral abstraction layer for UARTs on the stm32 family (except stm32g0). import ( "device/stm32" diff --git a/src/machine/machine_stm32g0.go b/src/machine/machine_stm32g0.go new file mode 100644 index 000000000..b338faef1 --- /dev/null +++ b/src/machine/machine_stm32g0.go @@ -0,0 +1,567 @@ +//go:build stm32g0 + +package machine + +// Peripheral abstraction layer for the stm32g0 + +import ( + "device/stm32" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +const ( + // CPU frequency for STM32G0 (64MHz via PLL: HSI16 / 1 * 8 / 2) + cpuFreq = 64000000 +) + +func CPUFrequency() uint32 { + return cpuFreq +} + +var deviceIDAddr = []uintptr{0x1FFF7590, 0x1FFF7594, 0x1FFF7598} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 64e6 // 64MHz (PLL: HSI16 / 1 * 8 / 2) +const APB2_TIM_FREQ = 64e6 // 64MHz (PLL: HSI16 / 1 * 8 / 2) + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PA8 = portA + 8 + PA9 = portA + 9 + PA10 = portA + 10 + PA11 = portA + 11 + PA12 = portA + 12 + PA13 = portA + 13 + PA14 = portA + 14 + PA15 = portA + 15 + + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PB8 = portB + 8 + PB9 = portB + 9 + PB10 = portB + 10 + PB11 = portB + 11 + PB12 = portB + 12 + PB13 = portB + 13 + PB14 = portB + 14 + PB15 = portB + 15 + + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PC8 = portC + 8 + PC9 = portC + 9 + PC10 = portC + 10 + PC11 = portC + 11 + PC12 = portC + 12 + PC13 = portC + 13 + PC14 = portC + 14 + PC15 = portC + 15 + + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 + PD8 = portD + 8 + PD9 = portD + 9 + PD10 = portD + 10 + PD11 = portD + 11 + PD12 = portD + 12 + PD13 = portD + 13 + PD14 = portD + 14 + PD15 = portD + 15 + + PE0 = portE + 0 + PE1 = portE + 1 + PE2 = portE + 2 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PE7 = portE + 7 + PE8 = portE + 8 + PE9 = portE + 9 + PE10 = portE + 10 + PE11 = portE + 11 + PE12 = portE + 12 + PE13 = portE + 13 + PE14 = portE + 14 + PE15 = portE + 15 + + PF0 = portF + 0 + PF1 = portF + 1 + PF2 = portF + 2 + PF3 = portF + 3 + PF4 = portF + 4 + PF5 = portF + 5 + PF6 = portF + 6 + PF7 = portF + 7 + PF8 = portF + 8 + PF9 = portF + 9 + PF10 = portF + 10 + PF11 = portF + 11 + PF12 = portF + 12 + PF13 = portF + 13 + PF14 = portF + 14 + PF15 = portF + 15 +) + +func (p Pin) getPort() *stm32.GPIO_Type { + switch p / 16 { + case 0: + return stm32.GPIOA + case 1: + return stm32.GPIOB + case 2: + return stm32.GPIOC + case 3: + return stm32.GPIOD + case 4: + return stm32.GPIOE + case 5: + return stm32.GPIOF + default: + panic("machine: unknown port") + } +} + +// enableClock enables the clock for this desired GPIO port. +func (p Pin) enableClock() { + switch p / 16 { + case 0: + stm32.RCC.SetIOPENR_GPIOAEN(1) + case 1: + stm32.RCC.SetIOPENR_GPIOBEN(1) + case 2: + stm32.RCC.SetIOPENR_GPIOCEN(1) + case 3: + stm32.RCC.SetIOPENR_GPIODEN(1) + case 4: + stm32.RCC.SetIOPENR_GPIOEEN(1) + case 5: + stm32.RCC.SetIOPENR_GPIOFEN(1) + default: + panic("machine: unknown port") + } +} + +func (p Pin) registerInterrupt() interrupt.Interrupt { + pin := uint8(p) % 16 + + switch pin { + case 0: + return interrupt.New(stm32.IRQ_EXTI0_1, func(interrupt.Interrupt) { handlePinInterrupt(0) }) + case 1: + return interrupt.New(stm32.IRQ_EXTI0_1, func(interrupt.Interrupt) { handlePinInterrupt(1) }) + case 2: + return interrupt.New(stm32.IRQ_EXTI2_3, func(interrupt.Interrupt) { handlePinInterrupt(2) }) + case 3: + return interrupt.New(stm32.IRQ_EXTI2_3, func(interrupt.Interrupt) { handlePinInterrupt(3) }) + case 4: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(4) }) + case 5: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(5) }) + case 6: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(6) }) + case 7: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(7) }) + case 8: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(8) }) + case 9: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(9) }) + case 10: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(10) }) + case 11: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(11) }) + case 12: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(12) }) + case 13: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(13) }) + case 14: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(14) }) + case 15: + return interrupt.New(stm32.IRQ_EXTI4_15, func(interrupt.Interrupt) { handlePinInterrupt(15) }) + } + + return interrupt.Interrupt{} +} + +//---------- UART related types and code + +// Configure the UART. +func (uart *UART) configurePins(config UARTConfig) { + // enable the alternate functions on the TX and RX pins + config.TX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTTX}, uart.TxAltFuncSelector) + config.RX.ConfigureAltFunc(PinConfig{Mode: PinModeUARTRX}, uart.RxAltFuncSelector) +} + +// UART baudrate calc based on the bus and clockspeed +func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 { + return CPUFrequency() / baudRate +} + +// Register names vary by ST processor, these are for STM G0 family +func (uart *UART) setRegisters() { + uart.rxReg = &uart.Bus.RDR + uart.txReg = &uart.Bus.TDR + uart.statusReg = &uart.Bus.ISR_FIFO_ENABLED + uart.txEmptyFlag = stm32.USART_ISR_TXE +} + +//---------- SPI related types and code + +// SPI on the STM32G0 using MODER / alternate function pins +type SPI struct { + Bus *stm32.SPI_Type + AltFuncSelector uint8 +} + +func (spi *SPI) config8Bits() { + // Set rx threshold to 8-bits, so RXNE flag is set for 1 byte + spi.Bus.SetCR2_FRXTH(1) +} + +// Set baud rate for SPI +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { + var conf uint32 + + localFrequency := config.Frequency + + // Default + if localFrequency == 0 { + localFrequency = 4e6 + } + + // set frequency dependent on PCLK prescaler + switch { + case localFrequency < 250000: + conf = stm32.SPI_CR1_BR_Div256 + case localFrequency < 500000: + conf = stm32.SPI_CR1_BR_Div128 + case localFrequency < 1000000: + conf = stm32.SPI_CR1_BR_Div64 + case localFrequency < 2000000: + conf = stm32.SPI_CR1_BR_Div32 + case localFrequency < 4000000: + conf = stm32.SPI_CR1_BR_Div16 + case localFrequency < 8000000: + conf = stm32.SPI_CR1_BR_Div8 + case localFrequency < 16000000: + conf = stm32.SPI_CR1_BR_Div4 + case localFrequency < 32000000: + conf = stm32.SPI_CR1_BR_Div2 + default: + // None of the specific baudrates were selected; choose the lowest speed + conf = stm32.SPI_CR1_BR_Div256 + } + + return conf << stm32.SPI_CR1_BR_Pos +} + +// Configure SPI pins for input output and clock +func (spi *SPI) configurePins(config SPIConfig) { + config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) + config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) + config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) +} + +//---------- I2C related types and code + +// Gets the value for TIMINGR register +func (i2c *I2C) getFreqRange(br uint32) uint32 { + // These are 'magic' values calculated by STM32CubeMX + // for 64MHz PCLK1 (PLL: HSI16 / 1 * 8 / 2). + // TODO: Do calculations based on PCLK1 + switch br { + case 10 * KHz: + return 0xF010F3FE // 64MHz, 10kHz I2C + case 100 * KHz: + return 0x30A0A7FB // 64MHz, 100kHz I2C (Standard mode) + case 400 * KHz: + return 0x10802D9B // 64MHz, 400kHz I2C (Fast mode) + case 500 * KHz: + return 0x00802172 // 64MHz, 500kHz I2C + default: + return 0 + } +} + +// Enable peripheral clock +func enableAltFuncClock(bus unsafe.Pointer) { + switch bus { + case unsafe.Pointer(stm32.PWR): // Power interface clock enable + stm32.RCC.SetAPBENR1_PWREN(1) + case unsafe.Pointer(stm32.I2C1): // I2C1 clock enable + stm32.RCC.SetAPBENR1_I2C1EN(1) + case unsafe.Pointer(stm32.I2C2): // I2C2 clock enable + stm32.RCC.SetAPBENR1_I2C2EN(1) + case unsafe.Pointer(stm32.USART2): // USART2 clock enable + stm32.RCC.SetAPBENR1_USART2EN(1) + case unsafe.Pointer(stm32.USART3): // USART3 clock enable + stm32.RCC.SetAPBENR1_USART3EN(1) + case unsafe.Pointer(stm32.USART4): // USART4 clock enable + stm32.RCC.SetAPBENR1_USART4EN(1) + case unsafe.Pointer(stm32.SPI2): // SPI2 clock enable + stm32.RCC.SetAPBENR1_SPI2EN(1) + case unsafe.Pointer(stm32.WWDG): // Window watchdog clock enable + stm32.RCC.SetAPBENR1_WWDGEN(1) + case unsafe.Pointer(stm32.TIM2): // TIM2 clock enable + stm32.RCC.SetAPBENR1_TIM2EN(1) + case unsafe.Pointer(stm32.TIM3): // TIM3 clock enable + stm32.RCC.SetAPBENR1_TIM3EN(1) + case unsafe.Pointer(stm32.TIM6): // TIM6 clock enable + stm32.RCC.SetAPBENR1_TIM6EN(1) + case unsafe.Pointer(stm32.TIM7): // TIM7 clock enable + stm32.RCC.SetAPBENR1_TIM7EN(1) + case unsafe.Pointer(stm32.LPUART1): // LPUART1 clock enable + stm32.RCC.SetAPBENR1_LPUART1EN(1) + case unsafe.Pointer(stm32.TIM1): // TIM1 clock enable + stm32.RCC.SetAPBENR2_TIM1EN(1) + case unsafe.Pointer(stm32.SPI1): // SPI1 clock enable + stm32.RCC.SetAPBENR2_SPI1EN(1) + case unsafe.Pointer(stm32.USART1): // USART1 clock enable + stm32.RCC.SetAPBENR2_USART1EN(1) + case unsafe.Pointer(stm32.TIM14): // TIM14 clock enable + stm32.RCC.SetAPBENR2_TIM14EN(1) + case unsafe.Pointer(stm32.TIM15): // TIM15 clock enable + stm32.RCC.SetAPBENR2_TIM15EN(1) + case unsafe.Pointer(stm32.TIM16): // TIM16 clock enable + stm32.RCC.SetAPBENR2_TIM16EN(1) + case unsafe.Pointer(stm32.TIM17): // TIM17 clock enable + stm32.RCC.SetAPBENR2_TIM17EN(1) + case unsafe.Pointer(stm32.ADC): // ADC clock enable + stm32.RCC.SetAPBENR2_ADCEN(1) + case unsafe.Pointer(stm32.FDCAN1), unsafe.Pointer(stm32.FDCAN2): // FDCAN clock enable + stm32.RCC.SetAPBENR1_FDCANEN(1) + } +} + +//---------- Timer related code + +// Alternate function constants for STM32G0 +const ( + AF0_SYSTEM = 0 + AF1_TIM1_TIM2_TIM3_LPTIM1 = 1 + AF2_TIM1_TIM2_TIM3_TIM14_I2C2 = 2 + AF3_USART5_USART6_LPUART2 = 3 + AF3_FDCAN1_FDCAN2 = 3 // FDCAN on PC2/PC3/PC4/PC5, PD12/PD13/PD14/PD15 + AF4_USART1_USART2_TIM14 = 4 + AF5_SPI1_SPI2_TIM16_TIM17 = 5 + AF6_SPI2_USART3_USART4_I2C1 = 6 + AF7_USART1_USART2_COMP1_COMP2 = 7 + AF8_I2C1_I2C2_UCPD1_UCPD2 = 8 + AF9_SPI2_TIM14_TIM15 = 9 + AF9_FDCAN1_FDCAN2 = 9 // FDCAN on PA11/PA12, PB8/PB9 +) + +var ( + TIM1 = TIM{ + EnableRegister: &stm32.RCC.APBENR2, + EnableFlag: stm32.RCC_APBENR2_TIM1EN, + Device: stm32.TIM1, + Channels: [4]TimerChannel{ + {Pins: []PinFunction{{PA8, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}}, + {Pins: []PinFunction{{PA9, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}}, + {Pins: []PinFunction{{PA10, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}}, + {Pins: []PinFunction{{PA11, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM2 = TIM{ + EnableRegister: &stm32.RCC.APBENR1, + EnableFlag: stm32.RCC_APBENR1_TIM2EN, + Device: stm32.TIM2, + Channels: [4]TimerChannel{ + {Pins: []PinFunction{{PA0, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}, {PA5, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}, {PA15, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}}, + {Pins: []PinFunction{{PA1, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}, {PB3, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}}, + {Pins: []PinFunction{{PA2, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}, {PB10, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}}, + {Pins: []PinFunction{{PA3, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}, {PB11, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM3 = TIM{ + EnableRegister: &stm32.RCC.APBENR1, + EnableFlag: stm32.RCC_APBENR1_TIM3EN, + Device: stm32.TIM3, + Channels: [4]TimerChannel{ + {Pins: []PinFunction{{PA6, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PB4, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PC6, AF1_TIM1_TIM2_TIM3_LPTIM1}}}, + {Pins: []PinFunction{{PA7, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PB5, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PC7, AF1_TIM1_TIM2_TIM3_LPTIM1}}}, + {Pins: []PinFunction{{PB0, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PC8, AF1_TIM1_TIM2_TIM3_LPTIM1}}}, + {Pins: []PinFunction{{PB1, AF1_TIM1_TIM2_TIM3_LPTIM1}, {PC9, AF1_TIM1_TIM2_TIM3_LPTIM1}}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM6 = TIM{ + EnableRegister: &stm32.RCC.APBENR1, + EnableFlag: stm32.RCC_APBENR1_TIM6EN, + Device: stm32.TIM6, + Channels: [4]TimerChannel{ + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM7 = TIM{ + EnableRegister: &stm32.RCC.APBENR1, + EnableFlag: stm32.RCC_APBENR1_TIM7EN, + Device: stm32.TIM7, + Channels: [4]TimerChannel{ + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + }, + busFreq: APB1_TIM_FREQ, + } + + TIM14 = TIM{ + EnableRegister: &stm32.RCC.APBENR2, + EnableFlag: stm32.RCC_APBENR2_TIM14EN, + Device: stm32.TIM14, + Channels: [4]TimerChannel{ + {Pins: []PinFunction{{PA4, AF4_USART1_USART2_TIM14}, {PA7, AF4_USART1_USART2_TIM14}, {PB1, AF0_SYSTEM}}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM15 = TIM{ + EnableRegister: &stm32.RCC.APBENR2, + EnableFlag: stm32.RCC_APBENR2_TIM15EN, + Device: stm32.TIM15, + Channels: [4]TimerChannel{ + {Pins: []PinFunction{{PA2, AF5_SPI1_SPI2_TIM16_TIM17}, {PB14, AF5_SPI1_SPI2_TIM16_TIM17}}}, + {Pins: []PinFunction{{PA3, AF5_SPI1_SPI2_TIM16_TIM17}, {PB15, AF5_SPI1_SPI2_TIM16_TIM17}}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM16 = TIM{ + EnableRegister: &stm32.RCC.APBENR2, + EnableFlag: stm32.RCC_APBENR2_TIM16EN, + Device: stm32.TIM16, + Channels: [4]TimerChannel{ + {Pins: []PinFunction{{PA6, AF5_SPI1_SPI2_TIM16_TIM17}, {PB8, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } + + TIM17 = TIM{ + EnableRegister: &stm32.RCC.APBENR2, + EnableFlag: stm32.RCC_APBENR2_TIM17EN, + Device: stm32.TIM17, + Channels: [4]TimerChannel{ + {Pins: []PinFunction{{PA7, AF5_SPI1_SPI2_TIM16_TIM17}, {PB9, AF2_TIM1_TIM2_TIM3_TIM14_I2C2}}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + {Pins: []PinFunction{}}, + }, + busFreq: APB2_TIM_FREQ, + } +) + +func (t *TIM) registerUPInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_BRK_UP_TRG_COM, TIM1.handleUPInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleUPInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3_TIM4, TIM3.handleUPInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleUPInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleUPInterrupt) + case &TIM14: + return interrupt.New(stm32.IRQ_TIM14, TIM14.handleUPInterrupt) + case &TIM15: + return interrupt.New(stm32.IRQ_TIM15, TIM15.handleUPInterrupt) + case &TIM16: + return interrupt.New(stm32.IRQ_TIM16, TIM16.handleUPInterrupt) + case &TIM17: + return interrupt.New(stm32.IRQ_TIM17, TIM17.handleUPInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) registerOCInterrupt() interrupt.Interrupt { + switch t { + case &TIM1: + return interrupt.New(stm32.IRQ_TIM1_CC, TIM1.handleOCInterrupt) + case &TIM2: + return interrupt.New(stm32.IRQ_TIM2, TIM2.handleOCInterrupt) + case &TIM3: + return interrupt.New(stm32.IRQ_TIM3_TIM4, TIM3.handleOCInterrupt) + case &TIM6: + return interrupt.New(stm32.IRQ_TIM6_DAC, TIM6.handleOCInterrupt) + case &TIM7: + return interrupt.New(stm32.IRQ_TIM7, TIM7.handleOCInterrupt) + case &TIM14: + return interrupt.New(stm32.IRQ_TIM14, TIM14.handleOCInterrupt) + case &TIM15: + return interrupt.New(stm32.IRQ_TIM15, TIM15.handleOCInterrupt) + case &TIM16: + return interrupt.New(stm32.IRQ_TIM16, TIM16.handleOCInterrupt) + case &TIM17: + return interrupt.New(stm32.IRQ_TIM17, TIM17.handleOCInterrupt) + } + + return interrupt.Interrupt{} +} + +func (t *TIM) enableMainOutput() { + t.Device.SetBDTR_MOE(1) +} + +type arrtype = uint32 +type arrRegType = volatile.Register32 + +const ( + ARR_MAX = 0x10000 + PSC_MAX = 0x10000 +) + +func initRNG() { + // STM32G0B1 does not have a hardware RNG peripheral + // RNG is available on some other STM32G0 variants +} diff --git a/src/machine/machine_stm32g0_can.go b/src/machine/machine_stm32g0_can.go new file mode 100644 index 000000000..01bf523df --- /dev/null +++ b/src/machine/machine_stm32g0_can.go @@ -0,0 +1,711 @@ +//go:build stm32g0b1 + +package machine + +import ( + "device/stm32" + "errors" + "runtime/interrupt" + "unsafe" +) + +// FDCAN Message RAM configuration +// STM32G0B1 SRAMCAN base address: 0x4000B400 +// Each FDCAN instance has its own message RAM area +const ( + sramcanBase = 0x4000B400 + + // Message RAM layout sizes (matching STM32 HAL) + sramcanFLSNbr = 28 // Max. Filter List Standard Number + sramcanFLENbr = 8 // Max. Filter List Extended Number + sramcanRF0Nbr = 3 // RX FIFO 0 Elements Number + sramcanRF1Nbr = 3 // RX FIFO 1 Elements Number + sramcanTEFNbr = 3 // TX Event FIFO Elements Number + sramcanTFQNbr = 3 // TX FIFO/Queue Elements Number + + // Element sizes in bytes + sramcanFLSSize = 1 * 4 // Filter Standard Element Size + sramcanFLESize = 2 * 4 // Filter Extended Element Size + sramcanRF0Size = 18 * 4 // RX FIFO 0 Element Size (for 64-byte data) + sramcanRF1Size = 18 * 4 // RX FIFO 1 Element Size + sramcanTEFSize = 2 * 4 // TX Event FIFO Element Size + sramcanTFQSize = 18 * 4 // TX FIFO/Queue Element Size + + // Start addresses (offsets from base) + sramcanFLSSA = 0 + sramcanFLESA = sramcanFLSSA + (sramcanFLSNbr * sramcanFLSSize) + sramcanRF0SA = sramcanFLESA + (sramcanFLENbr * sramcanFLESize) + sramcanRF1SA = sramcanRF0SA + (sramcanRF0Nbr * sramcanRF0Size) + sramcanTEFSA = sramcanRF1SA + (sramcanRF1Nbr * sramcanRF1Size) + sramcanTFQSA = sramcanTEFSA + (sramcanTEFNbr * sramcanTEFSize) + sramcanSize = sramcanTFQSA + (sramcanTFQNbr * sramcanTFQSize) +) + +// FDCAN element masks (for parsing message RAM) +const ( + fdcanElementMaskSTDID = 0x1FFC0000 // Standard Identifier + fdcanElementMaskEXTID = 0x1FFFFFFF // Extended Identifier + fdcanElementMaskRTR = 0x20000000 // Remote Transmission Request + fdcanElementMaskXTD = 0x40000000 // Extended Identifier flag + fdcanElementMaskESI = 0x80000000 // Error State Indicator + fdcanElementMaskTS = 0x0000FFFF // Timestamp + fdcanElementMaskDLC = 0x000F0000 // Data Length Code + fdcanElementMaskBRS = 0x00100000 // Bit Rate Switch + fdcanElementMaskFDF = 0x00200000 // FD Format + fdcanElementMaskEFC = 0x00800000 // Event FIFO Control + fdcanElementMaskMM = 0xFF000000 // Message Marker + fdcanElementMaskFIDX = 0x7F000000 // Filter Index + fdcanElementMaskANMF = 0x80000000 // Accepted Non-matching Frame +) + +// Interrupt flags +const ( + FDCAN_IT_RX_FIFO0_NEW_MESSAGE = 0x00000001 + FDCAN_IT_RX_FIFO0_FULL = 0x00000002 + FDCAN_IT_RX_FIFO0_MSG_LOST = 0x00000004 + FDCAN_IT_RX_FIFO1_NEW_MESSAGE = 0x00000010 + FDCAN_IT_RX_FIFO1_FULL = 0x00000020 + FDCAN_IT_RX_FIFO1_MSG_LOST = 0x00000040 + FDCAN_IT_TX_COMPLETE = 0x00000200 + FDCAN_IT_TX_ABORT_COMPLETE = 0x00000400 + FDCAN_IT_TX_FIFO_EMPTY = 0x00000800 + FDCAN_IT_BUS_OFF = 0x02000000 + FDCAN_IT_ERROR_WARNING = 0x01000000 + FDCAN_IT_ERROR_PASSIVE = 0x00800000 +) + +// FDCAN represents an FDCAN peripheral +type FDCAN struct { + Bus *stm32.FDCAN_Type + TxAltFuncSelect uint8 + RxAltFuncSelect uint8 + Interrupt interrupt.Interrupt + instance uint8 +} + +// FDCANTransferRate represents CAN bus transfer rates +type FDCANTransferRate uint32 + +const ( + FDCANTransferRate125kbps FDCANTransferRate = 125000 + FDCANTransferRate250kbps FDCANTransferRate = 250000 + FDCANTransferRate500kbps FDCANTransferRate = 500000 + FDCANTransferRate1000kbps FDCANTransferRate = 1000000 + FDCANTransferRate2000kbps FDCANTransferRate = 2000000 // FD only + FDCANTransferRate4000kbps FDCANTransferRate = 4000000 // FD only +) + +// FDCANMode represents the FDCAN operating mode +type FDCANMode uint8 + +const ( + FDCANModeNormal FDCANMode = 0 + FDCANModeBusMonitoring FDCANMode = 1 + FDCANModeInternalLoopback FDCANMode = 2 + FDCANModeExternalLoopback FDCANMode = 3 +) + +// FDCANConfig holds FDCAN configuration parameters +type FDCANConfig struct { + TransferRate FDCANTransferRate // Nominal bit rate (arbitration phase) + TransferRateFD FDCANTransferRate // Data bit rate (data phase), must be >= TransferRate + Mode FDCANMode + Tx Pin + Rx Pin + Standby Pin // Optional standby pin for CAN transceiver (set to NoPin if not used) +} + +// FDCANTxBufferElement represents a transmit buffer element +type FDCANTxBufferElement struct { + ESI bool // Error State Indicator + XTD bool // Extended ID flag + RTR bool // Remote Transmission Request + ID uint32 // CAN identifier (11-bit or 29-bit) + MM uint8 // Message Marker + EFC bool // Event FIFO Control + FDF bool // FD Frame indicator + BRS bool // Bit Rate Switch + DLC uint8 // Data Length Code (0-15) + DB [64]byte // Data buffer +} + +// FDCANRxBufferElement represents a receive buffer element +type FDCANRxBufferElement struct { + ESI bool // Error State Indicator + XTD bool // Extended ID flag + RTR bool // Remote Transmission Request + ID uint32 // CAN identifier + ANMF bool // Accepted Non-matching Frame + FIDX uint8 // Filter Index + FDF bool // FD Frame + BRS bool // Bit Rate Switch + DLC uint8 // Data Length Code + RXTS uint16 // RX Timestamp + DB [64]byte // Data buffer +} + +// FDCANFilterConfig represents a filter configuration +type FDCANFilterConfig struct { + Index uint8 // Filter index (0-27 for standard, 0-7 for extended) + Type uint8 // 0=Range, 1=Dual, 2=Classic (ID/Mask) + Config uint8 // 0=Disable, 1=FIFO0, 2=FIFO1, 3=Reject + ID1 uint32 // First ID or filter + ID2 uint32 // Second ID or mask + IsExtendedID bool // true for 29-bit ID, false for 11-bit +} + +var ( + errFDCANInvalidTransferRate = errors.New("FDCAN: invalid TransferRate") + errFDCANInvalidTransferRateFD = errors.New("FDCAN: invalid TransferRateFD") + errFDCANTimeout = errors.New("FDCAN: timeout") + errFDCANTxFifoFull = errors.New("FDCAN: Tx FIFO full") + errFDCANRxFifoEmpty = errors.New("FDCAN: Rx FIFO empty") + errFDCANNotStarted = errors.New("FDCAN: not started") +) + +// DLC to bytes lookup table +var dlcToBytes = [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64} + +// Configure initializes the FDCAN peripheral +func (can *FDCAN) Configure(config FDCANConfig) error { + // Configure standby pin if specified (for CAN transceivers with standby control) + // Setting it low enables the transceiver + if config.Standby != NoPin { + config.Standby.Configure(PinConfig{Mode: PinOutput}) + config.Standby.Low() + } + + // Enable FDCAN clock + enableFDCANClock() + + // Configure TX and RX pins + config.Tx.ConfigureAltFunc(PinConfig{Mode: PinOutput}, can.TxAltFuncSelect) + config.Rx.ConfigureAltFunc(PinConfig{Mode: PinInputFloating}, can.RxAltFuncSelect) + + // Exit from sleep mode + can.Bus.SetCCCR_CSR(0) + + // Wait for sleep mode exit + timeout := 10000 + for can.Bus.GetCCCR_CSA() != 0 { + timeout-- + if timeout == 0 { + return errFDCANTimeout + } + } + + // Request initialization + can.Bus.SetCCCR_INIT(1) + + // Wait for init mode + timeout = 10000 + for can.Bus.GetCCCR_INIT() == 0 { + timeout-- + if timeout == 0 { + return errFDCANTimeout + } + } + + // Enable configuration change + can.Bus.SetCCCR_CCE(1) + + // Configure clock divider (only for FDCAN1) + if can.Bus == stm32.FDCAN1 { + can.Bus.SetCKDIV_PDIV(0) + //can.Bus.CKDIV.Set(0) // No division + } + + // Enable automatic retransmission + can.Bus.SetCCCR_DAR(0) + + // Disable transmit pause + can.Bus.SetCCCR_TXP(0) + + // Enable protocol exception handling + can.Bus.SetCCCR_PXHD(0) + + // Enable FD mode with bit rate switching + can.Bus.SetCCCR_FDOE(1) + can.Bus.SetCCCR_BRSE(1) + + // Configure operating mode + can.Bus.SetCCCR_TEST(0) + can.Bus.SetCCCR_MON(0) + can.Bus.SetCCCR_ASM(0) + can.Bus.SetTEST_LBCK(0) + + switch config.Mode { + case FDCANModeBusMonitoring: + can.Bus.SetCCCR_MON(1) + case FDCANModeInternalLoopback: + can.Bus.SetCCCR_TEST(1) + can.Bus.SetCCCR_MON(1) + can.Bus.SetTEST_LBCK(1) + case FDCANModeExternalLoopback: + can.Bus.SetCCCR_TEST(1) + can.Bus.SetTEST_LBCK(1) + } + + // Set nominal bit timing + // STM32G0 runs at 64MHz, FDCAN clock = PCLK = 64MHz + // Bit time = (1 + NTSEG1 + NTSEG2) * tq + // tq = (NBRP + 1) / fCAN_CLK + if config.TransferRate == 0 { + config.TransferRate = FDCANTransferRate500kbps + } + + nbrp, ntseg1, ntseg2, nsjw, err := can.calculateNominalBitTiming(config.TransferRate) + if err != nil { + return err + } + can.Bus.NBTP.Set(((nsjw - 1) << 25) | ((nbrp - 1) << 16) | ((ntseg1 - 1) << 8) | (ntseg2 - 1)) + + // Set data bit timing (for FD mode) + if config.TransferRateFD == 0 { + config.TransferRateFD = FDCANTransferRate1000kbps + } + if config.TransferRateFD < config.TransferRate { + return errFDCANInvalidTransferRateFD + } + + dbrp, dtseg1, dtseg2, dsjw, err := can.calculateDataBitTiming(config.TransferRateFD) + if err != nil { + return err + } + can.Bus.DBTP.Set(((dbrp - 1) << 16) | ((dtseg1 - 1) << 8) | ((dtseg2 - 1) << 4) | (dsjw - 1)) + + // Configure message RAM + can.configureMessageRAM() + + return nil +} + +// Start enables the FDCAN peripheral for communication +func (can *FDCAN) Start() error { + // Disable configuration change + can.Bus.SetCCCR_CCE(0) + + // Exit initialization mode + can.Bus.SetCCCR_INIT(0) + + // Wait for normal operation + timeout := 10000 + + for can.Bus.GetCCCR_INIT() != 0 { + timeout-- + if timeout == 0 { + return errFDCANTimeout + } + } + + return nil +} + +// Stop disables the FDCAN peripheral +func (can *FDCAN) Stop() error { + // Request initialization + can.Bus.SetCCCR_INIT(1) + + // Wait for init mode + timeout := 10000 + for can.Bus.GetCCCR_INIT() == 0 { + timeout-- + if timeout == 0 { + return errFDCANTimeout + } + } + + // Enable configuration change + can.Bus.SetCCCR_CCE(1) + + return nil +} + +// TxFifoIsFull returns true if the TX FIFO is full +func (can *FDCAN) TxFifoIsFull() bool { + return (can.Bus.TXFQS.Get() & 0x00200000) != 0 // TFQF bit +} + +// TxFifoFreeLevel returns the number of free TX FIFO elements +func (can *FDCAN) TxFifoFreeLevel() int { + return int(can.Bus.TXFQS.Get() & 0x07) // TFFL[2:0] +} + +// RxFifoSize returns the number of messages in RX FIFO 0 +func (can *FDCAN) RxFifoSize() int { + return int(can.Bus.RXF0S.Get() & 0x0F) // F0FL[3:0] +} + +// RxFifoIsEmpty returns true if RX FIFO 0 is empty +func (can *FDCAN) RxFifoIsEmpty() bool { + return (can.Bus.RXF0S.Get() & 0x0F) == 0 +} + +// TxRaw transmits a CAN frame using the raw buffer element structure +func (can *FDCAN) TxRaw(e *FDCANTxBufferElement) error { + // Check if TX FIFO is full + if can.TxFifoIsFull() { + return errFDCANTxFifoFull + } + + // Get put index + putIndex := (can.Bus.TXFQS.Get() >> 16) & 0x03 // TFQPI[1:0] + + // Calculate TX buffer address + sramBase := can.getSRAMBase() + txAddress := sramBase + sramcanTFQSA + (uintptr(putIndex) * sramcanTFQSize) + + // Build first word + var w1 uint32 + id := e.ID + if !e.XTD { + // Standard ID - shift to bits [28:18] + id = (id & 0x7FF) << 18 + } + w1 = id & 0x1FFFFFFF + if e.ESI { + w1 |= fdcanElementMaskESI + } + if e.XTD { + w1 |= fdcanElementMaskXTD + } + if e.RTR { + w1 |= fdcanElementMaskRTR + } + + // Build second word + var w2 uint32 + w2 = uint32(e.DLC) << 16 + if e.FDF { + w2 |= fdcanElementMaskFDF + } + if e.BRS { + w2 |= fdcanElementMaskBRS + } + if e.EFC { + w2 |= fdcanElementMaskEFC + } + w2 |= uint32(e.MM) << 24 + + // Write to message RAM + *(*uint32)(unsafe.Pointer(txAddress)) = w1 + *(*uint32)(unsafe.Pointer(txAddress + 4)) = w2 + + // Copy data bytes - must use 32-bit word access on Cortex-M0+ + dataLen := dlcToBytes[e.DLC&0x0F] + numWords := (dataLen + 3) / 4 + for w := byte(0); w < numWords; w++ { + var word uint32 + baseIdx := w * 4 + for b := byte(0); b < 4 && baseIdx+b < dataLen; b++ { + word |= uint32(e.DB[baseIdx+b]) << (b * 8) + } + *(*uint32)(unsafe.Pointer(txAddress + 8 + uintptr(w)*4)) = word + } + + // Request transmission + can.Bus.TXBAR.Set(1 << putIndex) + + return nil +} + +// Tx transmits a CAN frame with the specified ID and data +func (can *FDCAN) Tx(id uint32, data []byte, isFD, isExtendedID bool) error { + length := byte(len(data)) + if length > 64 { + length = 64 + } + if !isFD && length > 8 { + length = 8 + } + + e := FDCANTxBufferElement{ + ESI: false, + XTD: isExtendedID, + RTR: false, + ID: id, + MM: 0, + EFC: false, + FDF: isFD, + BRS: isFD, + DLC: FDCANLengthToDlc(length, isFD), + } + + for i := byte(0); i < length; i++ { + e.DB[i] = data[i] + } + + return can.TxRaw(&e) +} + +// RxRaw receives a CAN frame into the raw buffer element structure +func (can *FDCAN) RxRaw(e *FDCANRxBufferElement) error { + if can.RxFifoIsEmpty() { + return errFDCANRxFifoEmpty + } + + // Get get index + getIndex := (can.Bus.RXF0S.Get() >> 8) & 0x03 // F0GI[1:0] + + // Calculate RX buffer address + sramBase := can.getSRAMBase() + rxAddress := sramBase + sramcanRF0SA + (uintptr(getIndex) * sramcanRF0Size) + + // Read first word + w1 := *(*uint32)(unsafe.Pointer(rxAddress)) + e.ESI = (w1 & fdcanElementMaskESI) != 0 + e.XTD = (w1 & fdcanElementMaskXTD) != 0 + e.RTR = (w1 & fdcanElementMaskRTR) != 0 + + if e.XTD { + e.ID = w1 & fdcanElementMaskEXTID + } else { + e.ID = (w1 & fdcanElementMaskSTDID) >> 18 + } + + // Read second word + w2 := *(*uint32)(unsafe.Pointer(rxAddress + 4)) + e.RXTS = uint16(w2 & fdcanElementMaskTS) + e.DLC = uint8((w2 & fdcanElementMaskDLC) >> 16) + e.BRS = (w2 & fdcanElementMaskBRS) != 0 + e.FDF = (w2 & fdcanElementMaskFDF) != 0 + e.FIDX = uint8((w2 & fdcanElementMaskFIDX) >> 24) + e.ANMF = (w2 & fdcanElementMaskANMF) != 0 + + // Copy data bytes - must use 32-bit word access on Cortex-M0+ + dataLen := dlcToBytes[e.DLC&0x0F] + numWords := (dataLen + 3) / 4 + for w := byte(0); w < numWords; w++ { + word := *(*uint32)(unsafe.Pointer(rxAddress + 8 + uintptr(w)*4)) + baseIdx := w * 4 + for b := byte(0); b < 4 && baseIdx+b < dataLen; b++ { + e.DB[baseIdx+b] = byte(word >> (b * 8)) + } + } + + // Acknowledge the read + can.Bus.RXF0A.Set(uint32(getIndex)) + + return nil +} + +// Rx receives a CAN frame and returns its components +func (can *FDCAN) Rx() (id uint32, dlc byte, data []byte, isFD, isExtendedID bool, err error) { + e := FDCANRxBufferElement{} + err = can.RxRaw(&e) + if err != nil { + return 0, 0, nil, false, false, err + } + + length := FDCANDlcToLength(e.DLC, e.FDF) + return e.ID, length, e.DB[:length], e.FDF, e.XTD, nil +} + +// SetInterrupt configures interrupt handling for the FDCAN peripheral +func (can *FDCAN) SetInterrupt(ie uint32, callback func(*FDCAN)) error { + if callback == nil { + can.Bus.IE.ClearBits(ie) + return nil + } + + can.Bus.IE.SetBits(ie) + + idx := can.instance + fdcanInstances[idx] = can + + for i := uint(0); i < 32; i++ { + if ie&(1<= sramcanFLENbr { + return errors.New("FDCAN: filter index out of range") + } + + filterAddr := sramBase + sramcanFLESA + (uintptr(config.Index) * sramcanFLESize) + + // Build filter elements + w1 := (uint32(config.Config) << 29) | (config.ID1 & 0x1FFFFFFF) + w2 := (uint32(config.Type) << 30) | (config.ID2 & 0x1FFFFFFF) + + *(*uint32)(unsafe.Pointer(filterAddr)) = w1 + *(*uint32)(unsafe.Pointer(filterAddr + 4)) = w2 + } else { + // Standard filter + if config.Index >= sramcanFLSNbr { + return errors.New("FDCAN: filter index out of range") + } + + filterAddr := sramBase + sramcanFLSSA + (uintptr(config.Index) * sramcanFLSSize) + + // Build filter element + w := (uint32(config.Type) << 30) | + (uint32(config.Config) << 27) | + ((config.ID1 & 0x7FF) << 16) | + (config.ID2 & 0x7FF) + + *(*uint32)(unsafe.Pointer(filterAddr)) = w + } + + return nil +} + +func (can *FDCAN) getSRAMBase() uintptr { + base := uintptr(sramcanBase) + if can.Bus == stm32.FDCAN2 { + base += sramcanSize + } + return base +} + +func (can *FDCAN) configureMessageRAM() { + sramBase := can.getSRAMBase() + + // Clear message RAM + for addr := sramBase; addr < sramBase+sramcanSize; addr += 4 { + *(*uint32)(unsafe.Pointer(addr)) = 0 + } + + // Configure filter counts (using RXGFC register) + // LSS = number of standard filters, LSE = number of extended filters + rxgfc := can.Bus.RXGFC.Get() + rxgfc &= ^uint32(0xFF000000) // Clear LSS and LSE + rxgfc |= (sramcanFLSNbr << 24) // Standard filters + rxgfc |= (sramcanFLENbr << 24) & 0xFF00 // Extended filters (shifted) + can.Bus.RXGFC.Set(rxgfc) +} + +func (can *FDCAN) calculateNominalBitTiming(rate FDCANTransferRate) (brp, tseg1, tseg2, sjw uint32, err error) { + // STM32G0 FDCAN clock = 64MHz + // Target: 80% sample point + // Bit time = (1 + TSEG1 + TSEG2) time quanta + switch rate { + case FDCANTransferRate125kbps: + // 64MHz / 32 = 2MHz, 16 tq per bit = 125kbps + return 32, 13, 2, 4, nil + case FDCANTransferRate250kbps: + // 64MHz / 16 = 4MHz, 16 tq per bit = 250kbps + return 16, 13, 2, 4, nil + case FDCANTransferRate500kbps: + // 64MHz / 8 = 8MHz, 16 tq per bit = 500kbps + return 8, 13, 2, 4, nil + case FDCANTransferRate1000kbps: + // 64MHz / 4 = 16MHz, 16 tq per bit = 1Mbps + return 4, 13, 2, 4, nil + default: + return 0, 0, 0, 0, errFDCANInvalidTransferRate + } +} + +func (can *FDCAN) calculateDataBitTiming(rate FDCANTransferRate) (brp, tseg1, tseg2, sjw uint32, err error) { + // STM32G0 FDCAN clock = 64MHz + // For data phase, we need higher bit rates + switch rate { + case FDCANTransferRate125kbps: + return 32, 13, 2, 4, nil + case FDCANTransferRate250kbps: + return 16, 13, 2, 4, nil + case FDCANTransferRate500kbps: + return 8, 13, 2, 4, nil + case FDCANTransferRate1000kbps: + return 4, 13, 2, 4, nil + case FDCANTransferRate2000kbps: + // 64MHz / 2 = 32MHz, 16 tq per bit = 2Mbps + return 2, 13, 2, 4, nil + case FDCANTransferRate4000kbps: + // 64MHz / 1 = 64MHz, 16 tq per bit = 4Mbps + return 1, 13, 2, 4, nil + default: + return 0, 0, 0, 0, errFDCANInvalidTransferRateFD + } +} + +// FDCANDlcToLength converts a DLC value to actual byte length +func FDCANDlcToLength(dlc byte, isFD bool) byte { + if dlc > 15 { + dlc = 15 + } + length := dlcToBytes[dlc] + if !isFD && length > 8 { + return 8 + } + return length +} + +// FDCANLengthToDlc converts a byte length to DLC value +func FDCANLengthToDlc(length byte, isFD bool) byte { + if !isFD { + if length > 8 { + return 8 + } + return length + } + + switch { + case length <= 8: + return length + case length <= 12: + return 9 + case length <= 16: + return 10 + case length <= 20: + return 11 + case length <= 24: + return 12 + case length <= 32: + return 13 + case length <= 48: + return 14 + default: + return 15 + } +} + +// Interrupt handling +var ( + fdcanInstances [2]*FDCAN + fdcanCallbacks [2][32]func(*FDCAN) +) + +func fdcanHandleInterrupt(idx int) { + if fdcanInstances[idx] == nil { + return + } + + can := fdcanInstances[idx] + ir := can.Bus.IR.Get() + can.Bus.IR.Set(ir) // Clear interrupt flags + + for i := uint(0); i < 32; i++ { + if ir&(1< VCO = 16 * 8 = 128 MHz + // PLLR = 0 (divide by 2) -> SYSCLK = 128 / 2 = 64 MHz + // PLLREN = 1 (enable R output for SYSCLK) + const ( + PLLSRC_HSI16 = 2 // HSI16 as PLL source + PLLM_DIV1 = 0 // /1 + PLLN_MUL8 = 8 // *8 + PLLR_DIV2 = 0 // /2 (0 = divide by 2) + ) + stm32.RCC.PLLCFGR.Set( + (PLLSRC_HSI16 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | + (PLLM_DIV1 << stm32.RCC_PLLCFGR_PLLM_Pos) | + (PLLN_MUL8 << stm32.RCC_PLLCFGR_PLLN_Pos) | + (PLLR_DIV2 << stm32.RCC_PLLCFGR_PLLR_Pos) | + stm32.RCC_PLLCFGR_PLLREN) // Enable PLLR output + + // Enable PLL + stm32.RCC.SetCR_PLLON(1) + for !stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) { + } + + // Set flash latency to 2 wait states (required for 64MHz in Range 1) + // Must be set BEFORE switching to higher frequency clock + const FLASH_LATENCY_2 = 2 + stm32.FLASH.SetACR_LATENCY(FLASH_LATENCY_2) + for (stm32.FLASH.ACR.Get() & stm32.Flash_ACR_LATENCY_Msk) != FLASH_LATENCY_2 { + } + + // Set AHB prescaler to 1 (no division) + stm32.RCC.SetCFGR_HPRE(0) + // Set APB prescaler to 1 (no division) + stm32.RCC.SetCFGR_PPRE(0) + + // Switch system clock to PLL (SW = 010) + const RCC_CFGR_SW_PLL = 2 + stm32.RCC.SetCFGR_SW(RCC_CFGR_SW_PLL) + // Wait for PLL to be used as system clock (SWS = 010) + for (stm32.RCC.CFGR.Get() & stm32.RCC_CFGR_SWS_Msk) != (RCC_CFGR_SW_PLL << stm32.RCC_CFGR_SWS_Pos) { + } +} diff --git a/src/runtime/runtime_stm32g0b1.go b/src/runtime/runtime_stm32g0b1.go new file mode 100644 index 000000000..990f346f7 --- /dev/null +++ b/src/runtime/runtime_stm32g0b1.go @@ -0,0 +1,15 @@ +//go:build stm32g0b1 + +package runtime + +import ( + "machine" +) + +func init() { + initCLK() + + machine.InitSerial() + + initTickTimer(&machine.TIM3) +} diff --git a/targets/amken-trio.json b/targets/amken-trio.json new file mode 100644 index 000000000..d801b88c9 --- /dev/null +++ b/targets/amken-trio.json @@ -0,0 +1,7 @@ +{ + "inherits": ["stm32g0b1"], + "build-tags": ["amken_trio"], + "linkerscript": "targets/stm32g0b1cb.ld", + "openocd-interface": "stlink", + "openocd-commands": ["reset_config srst_only connect_assert_srst"] +} \ No newline at end of file diff --git a/targets/nucleo-g0b1re.json b/targets/nucleo-g0b1re.json new file mode 100644 index 000000000..46c0306cc --- /dev/null +++ b/targets/nucleo-g0b1re.json @@ -0,0 +1,6 @@ +{ + "inherits": ["stm32g0b1"], + "build-tags": ["nucleog0b1re"], + "serial": "uart", + "openocd-interface": "stlink" +} \ No newline at end of file diff --git a/targets/stm32g0b1.json b/targets/stm32g0b1.json new file mode 100644 index 000000000..c77aa3fea --- /dev/null +++ b/targets/stm32g0b1.json @@ -0,0 +1,16 @@ +{ + "inherits": [ + "cortex-m0plus" + ], + "build-tags": [ + "stm32g0b1", + "stm32g0", + "stm32" + ], + "extra-files": [ + "src/device/stm32/stm32g0b1.s" + ], + "linkerscript": "targets/stm32g0b1.ld", + "flash-method": "openocd", + "openocd-target": "stm32g0x" +} \ No newline at end of file diff --git a/targets/stm32g0b1.ld b/targets/stm32g0b1.ld new file mode 100644 index 000000000..80b95437f --- /dev/null +++ b/targets/stm32g0b1.ld @@ -0,0 +1,10 @@ + +MEMORY +{ + FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 512K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 144K +} + +_stack_size = 4K; + +INCLUDE "targets/arm.ld" \ No newline at end of file diff --git a/targets/stm32g0b1cb.ld b/targets/stm32g0b1cb.ld new file mode 100644 index 000000000..b3b25c90a --- /dev/null +++ b/targets/stm32g0b1cb.ld @@ -0,0 +1,10 @@ + +MEMORY +{ + FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 128K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 144K +} + +_stack_size = 4K; + +INCLUDE "targets/arm.ld" \ No newline at end of file