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