mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-09 21:43:40 +00:00
cfd74c2954
* 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.
712 lines
19 KiB
Go
712 lines
19 KiB
Go
//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)
|
|
}
|