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.
This commit is contained in:
Amken USA
2026-01-06 17:12:02 -05:00
committed by GitHub
parent 9bcca974ba
commit cfd74c2954
23 changed files with 2021 additions and 9 deletions
+153
View File
@@ -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
}
+131
View File
@@ -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
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx
//go:build stm32 && !stm32f1 && !stm32l5 && !stm32wlx && !stm32g0
package machine
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx
//go:build stm32 && !stm32l4 && !stm32l5 && !stm32wlx && !stm32g0
package machine
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx
//go:build stm32l5 || stm32f7 || stm32l4 || stm32l0 || stm32wlx || stm32g0
package machine
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build stm32 && !(stm32f103 || stm32l0x1)
//go:build stm32 && !(stm32f103 || stm32l0x1 || stm32g0)
package machine
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build stm32 && !stm32f7x2 && !stm32l5x2
//go:build stm32 && !stm32f7x2 && !stm32l5x2 && !stm32g0
package machine
+2 -2
View File
@@ -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"
+567
View File
@@ -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
}
+711
View File
@@ -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<<i) != 0 {
fdcanCallbacks[idx][i] = callback
}
}
can.Interrupt.Enable()
return nil
}
// ConfigureFilter configures a message filter
func (can *FDCAN) ConfigureFilter(config FDCANFilterConfig) error {
sramBase := can.getSRAMBase()
if config.IsExtendedID {
// Extended filter
if config.Index >= 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<<i) != 0 && fdcanCallbacks[idx][i] != nil {
fdcanCallbacks[idx][i](can)
}
}
}
// Data returns the received data as a slice
func (e *FDCANRxBufferElement) Data() []byte {
return e.DB[:FDCANDlcToLength(e.DLC, e.FDF)]
}
// Length returns the actual data length
func (e *FDCANRxBufferElement) Length() byte {
return FDCANDlcToLength(e.DLC, e.FDF)
}
// enableFDCANClock enables the FDCAN peripheral clock
func enableFDCANClock() {
// FDCAN clock is on APB1
stm32.RCC.SetAPBENR1_FDCANEN(1)
}
+92
View File
@@ -0,0 +1,92 @@
//go:build stm32g0
package machine
import (
"device/stm32"
)
// This variant of the GPIO input interrupt logic is for
// STM32G0 chips which use a different EXTI register structure
// with IMR1, RTSR1, FTSR1, and separate RPR1/FPR1 pending registers.
// Callbacks for pin interrupt events
var pinCallbacks [16]func(Pin)
// The pin currently associated with interrupt callback
// for a given slot.
var interruptPins [16]Pin
// SetInterrupt sets an interrupt to be executed when a particular pin changes
// state. The pin should already be configured as an input, including a pull up
// or down if no external pull is provided.
//
// This call will replace a previously set callback on this pin. You can pass a
// nil func to unset the pin change interrupt. If you do so, the change
// parameter is ignored and can be set to any value (such as 0).
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
port := uint32(uint8(p) / 16)
pin := uint8(p) % 16
enableEXTIConfigRegisters()
if callback == nil {
stm32.EXTI.IMR1.ClearBits(1 << pin)
pinCallbacks[pin] = nil
return nil
}
if pinCallbacks[pin] != nil {
// The pin was already configured.
// To properly re-configure a pin, unset it first and set a new
// configuration.
return ErrNoPinChangeChannel
}
// Set the callback now (before the interrupt is enabled) to avoid
// possible race condition
pinCallbacks[pin] = callback
interruptPins[pin] = p
crReg := getEXTIConfigRegister(pin)
shift := (pin & 0x3) * 4
crReg.ReplaceBits(port, 0xf, shift)
if (change & PinRising) != 0 {
stm32.EXTI.RTSR1.SetBits(1 << pin)
}
if (change & PinFalling) != 0 {
stm32.EXTI.FTSR1.SetBits(1 << pin)
}
stm32.EXTI.IMR1.SetBits(1 << pin)
intr := p.registerInterrupt()
intr.SetPriority(0)
intr.Enable()
return nil
}
func handlePinInterrupt(pin uint8) {
// STM32G0 has separate rising and falling pending registers
// Check both and clear the appropriate one
mask := uint32(1 << pin)
if stm32.EXTI.RPR1.HasBits(mask) {
// Writing 1 to the pending register clears the pending flag
stm32.EXTI.RPR1.Set(mask)
callback := pinCallbacks[pin]
if callback != nil {
callback(interruptPins[pin])
}
}
if stm32.EXTI.FPR1.HasBits(mask) {
// Writing 1 to the pending register clears the pending flag
stm32.EXTI.FPR1.Set(mask)
callback := pinCallbacks[pin]
if callback != nil {
callback(interruptPins[pin])
}
}
}
+85
View File
@@ -0,0 +1,85 @@
//go:build stm32g0
package machine
// SPI on STM32G0 uses 16-bit registers
import (
"device/stm32"
"unsafe"
)
// SPIConfig is used to store config info for SPI.
type SPIConfig struct {
Frequency uint32
SCK Pin
SDO Pin
SDI Pin
LSBFirst bool
Mode uint8
}
// Configure is intended to setup the STM32 SPI peripheral
func (spi *SPI) Configure(config SPIConfig) error {
// enable clock for SPI
enableAltFuncClock(unsafe.Pointer(spi.Bus))
// Get SPI baud rate divisor
conf := spi.getBaudRate(config)
// set polarity and phase on the SPI interface
switch config.Mode {
case Mode1:
conf |= stm32.SPI_CR1_CPHA
case Mode2:
conf |= stm32.SPI_CR1_CPOL
case Mode3:
conf |= stm32.SPI_CR1_CPOL | stm32.SPI_CR1_CPHA
}
// set bit transfer order
if config.LSBFirst {
conf |= stm32.SPI_CR1_LSBFIRST
}
// set SPI master
conf |= stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI
// enable the SPI interface
conf |= stm32.SPI_CR1_SPE
// use software CS (GPIO) by default
conf |= stm32.SPI_CR1_SSM
// now set the configuration (note: STM32G0 uses 16-bit SPI registers)
spi.Bus.CR1.Set(uint16(conf))
// Series-specific configuration to set 8-bit transfer mode
spi.config8Bits()
// enable SPI
spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE)
return nil
}
// Transfer writes/reads a single byte using the SPI interface.
func (spi *SPI) Transfer(w byte) (byte, error) {
// Write data to be transmitted to the SPI data register
spi.Bus.DR.Set(uint16(w))
// Wait until transmit complete
for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) {
}
// Wait until receive complete
for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) {
}
// Wait until SPI is not busy
for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) {
}
// Return received data from SPI data register
return byte(spi.Bus.DR.Get()), nil
}
+86
View File
@@ -0,0 +1,86 @@
//go:build stm32g0
package machine
// Peripheral abstraction layer for UARTs on the stm32g0 family.
import (
"device/stm32"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)
// UART representation
type UART struct {
Buffer *RingBuffer
Bus *stm32.USART_Type
Interrupt interrupt.Interrupt
TxAltFuncSelector uint8
RxAltFuncSelector uint8
// Registers specific to the chip
rxReg *volatile.Register32
txReg *volatile.Register32
statusReg *volatile.Register32
txEmptyFlag uint32
}
// Configure the UART.
func (uart *UART) Configure(config UARTConfig) {
// Default baud rate to 115200.
if config.BaudRate == 0 {
config.BaudRate = 115200
}
// Set the GPIO pins to defaults if they're not set
if config.TX == 0 && config.RX == 0 {
config.TX = UART_TX_PIN
config.RX = UART_RX_PIN
}
// STM32 families have different, but compatible, registers for
// basic UART functions. For each family populate the registers
// into `uart`.
uart.setRegisters()
// Enable USART clock
enableAltFuncClock(unsafe.Pointer(uart.Bus))
uart.configurePins(config)
// Set baud rate
uart.SetBaudRate(config.BaudRate)
// Enable USART port, tx, rx and rx interrupts
// STM32G0 uses CR1_FIFO_ENABLED register
uart.Bus.CR1_FIFO_ENABLED.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)
// Enable RX IRQ
uart.Interrupt.SetPriority(0xc0)
uart.Interrupt.Enable()
}
// handleInterrupt should be called from the appropriate interrupt handler for
// this UART instance.
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
uart.Receive(byte((uart.rxReg.Get() & 0xFF)))
}
// SetBaudRate sets the communication speed for the UART. Defer to chip-specific
// routines for calculation
func (uart *UART) SetBaudRate(br uint32) {
divider := uart.getBaudRateDivisor(br)
uart.Bus.BRR.Set(divider)
}
// WriteByte writes a byte of data to the UART.
func (uart *UART) writeByte(c byte) error {
uart.txReg.Set(uint32(c))
for !uart.statusReg.HasBits(uart.txEmptyFlag) {
}
return nil
}
func (uart *UART) flush() {}
+26
View File
@@ -0,0 +1,26 @@
//go:build stm32g0
package machine
import (
"device/stm32"
"runtime/volatile"
)
func getEXTIConfigRegister(pin uint8) *volatile.Register32 {
switch (pin & 0xf) / 4 {
case 0:
return &stm32.EXTI.EXTICR1
case 1:
return &stm32.EXTI.EXTICR2
case 2:
return &stm32.EXTI.EXTICR3
case 3:
return &stm32.EXTI.EXTICR4
}
return nil
}
func enableEXTIConfigRegisters() {
// EXTI configuration is in the EXTI peripheral on STM32G0, no enable needed
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350)
//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350)
// If you update the above build constraint, you'll probably also need to update
// src/crypto/rand/rand_baremetal.go.
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350)
//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32g0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350)
package runtime
+97
View File
@@ -0,0 +1,97 @@
//go:build stm32g0
package runtime
import (
"device/stm32"
"machine"
)
func putchar(c byte) {
machine.Serial.WriteByte(c)
}
func getchar() byte {
for machine.Serial.Buffered() == 0 {
Gosched()
}
v, _ := machine.Serial.ReadByte()
return v
}
func buffered() int {
return machine.Serial.Buffered()
}
func initCLK() {
// Initialize clock to 64MHz using PLL with HSI16 as source
// PLL configuration: HSI16 (16MHz) / PLLM(1) * PLLN(8) / PLLR(2) = 64MHz
// Enable PWR clock
stm32.RCC.SetAPBENR1_PWREN(1)
// Read back to ensure the write is complete (memory barrier)
_ = stm32.RCC.APBENR1.Get()
// Set Power Regulator to enable max performance (Range 1)
// VOS = 01 for Range 1 (high performance, up to 64 MHz)
stm32.PWR.SetCR1_VOS(1)
// Wait for voltage scaling to be ready (VOSF = 0 means ready)
for stm32.PWR.SR2.HasBits(stm32.PWR_SR2_VOSF) {
}
// Enable HSI16
stm32.RCC.SetCR_HSION(1)
for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSIRDY) {
}
// Set HSI16 division factor to 1 (no division) - HSIDIV = 000
stm32.RCC.SetCR_HSIDIV(0)
// Disable PLL before configuration
stm32.RCC.SetCR_PLLON(0)
for stm32.RCC.CR.HasBits(stm32.RCC_CR_PLLRDY) {
}
// Configure PLL: HSI16 / 1 * 8 / 2 = 64 MHz
// PLLSRC = HSI16 (2)
// PLLM = 0 (divide by 1)
// PLLN = 8 (multiply by 8) -> 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) {
}
}
+15
View File
@@ -0,0 +1,15 @@
//go:build stm32g0b1
package runtime
import (
"machine"
)
func init() {
initCLK()
machine.InitSerial()
initTickTimer(&machine.TIM3)
}
+7
View File
@@ -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"]
}
+6
View File
@@ -0,0 +1,6 @@
{
"inherits": ["stm32g0b1"],
"build-tags": ["nucleog0b1re"],
"serial": "uart",
"openocd-interface": "stlink"
}
+16
View File
@@ -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"
}
+10
View File
@@ -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"
+10
View File
@@ -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"