From ab087d6f2744b405e8d38ab4a0bc8cacd3000303 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Fri, 20 Feb 2026 13:20:03 -0300 Subject: [PATCH 1/5] new CAN API demo --- src/machine/can.go | 38 ++++ src/machine/machine_stm32g0_can2.go | 278 ++++++++++++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 src/machine/can.go create mode 100644 src/machine/machine_stm32g0_can2.go diff --git a/src/machine/can.go b/src/machine/can.go new file mode 100644 index 0000000000..e5f4f91d26 --- /dev/null +++ b/src/machine/can.go @@ -0,0 +1,38 @@ +//go:build stm32g0 + +package machine + +// unexported functions here are implemented in the device file +// and added to the build tags of this file. + +// TxFIFOLevel returns amount of CAN frames stored for transmission and total Tx fifo length. +func (can *CAN) TxFIFOLevel() (level int, maxlevel int) { + return can.txFIFOLevel() +} + +// Tx puts a CAN frame in TxFIFO for transmission. Returns error if TxFIFO is full. +func (can *CAN) Tx(id uint32, extendedID bool, data []byte) error { + return can.tx(id, extendedID, data) +} + +// RxFIFOLevel returns amount of CAN frames received and stored and total Rx fifo length. +// If the hardware is interrupt driven RxFIFOLevel should return 0,0. +func (can *CAN) RxFIFOLevel() (level int, maxlevel int) { + return can.rxFIFOLevel() +} + +// SetRxCallback sets the receive callback. flags is a bitfield where bits set are: +// - bit 0: Is a FD frame. +// - bit 1: Is a RTR frame. +// - bit 2: Bitrate switch was active in frame. +// - bit 3: ESI error state indicator active. +func (can *CAN) SetRxCallback(cb func(data []byte, id uint32, extendedID bool, timestamp uint32, flags uint32)) { + can.setRxCallback(cb) +} + +// RxPoll is called periodically for poll driven drivers. If the driver is interrupt driven +// then RxPoll is a no-op and may return nil. Users may determine if a CAN is interrupt driven by +// checking if RxFIFOLevel returns 0,0. +func (can *CAN) RxPoll() error { + return can.rxPoll() +} diff --git a/src/machine/machine_stm32g0_can2.go b/src/machine/machine_stm32g0_can2.go new file mode 100644 index 0000000000..9f7c580c91 --- /dev/null +++ b/src/machine/machine_stm32g0_can2.go @@ -0,0 +1,278 @@ +//go:build stm32g0b1 + +package machine + +import ( + "device/stm32" + "unsafe" +) + +var canRxCB [2]func(data []byte, id uint32, extendedID bool, timestamp uint32, flags uint32) + +// Configure initializes the FDCAN peripheral and starts it. +func (can *CAN) Configure(config FDCANConfig) error { + if config.Standby != NoPin { + config.Standby.Configure(PinConfig{Mode: PinOutput}) + config.Standby.Low() + } + + enableFDCANClock() + + config.Tx.ConfigureAltFunc(PinConfig{Mode: PinOutput}, can.TxAltFuncSelect) + config.Rx.ConfigureAltFunc(PinConfig{Mode: PinInputFloating}, can.RxAltFuncSelect) + + // Exit sleep mode. + can.Bus.SetCCCR_CSR(0) + timeout := 10000 + for can.Bus.GetCCCR_CSA() != 0 { + timeout-- + if timeout == 0 { + return errFDCANTimeout + } + } + + // Request initialization. + can.Bus.SetCCCR_INIT(1) + timeout = 10000 + for can.Bus.GetCCCR_INIT() == 0 { + timeout-- + if timeout == 0 { + return errFDCANTimeout + } + } + + // Enable configuration change. + can.Bus.SetCCCR_CCE(1) + + if can.Bus == stm32.FDCAN1 { + can.Bus.SetCKDIV_PDIV(0) // No clock division. + } + + can.Bus.SetCCCR_DAR(0) // Enable auto retransmission. + can.Bus.SetCCCR_TXP(0) // Disable transmit pause. + can.Bus.SetCCCR_PXHD(0) // Enable protocol exception handling. + can.Bus.SetCCCR_FDOE(1) // FD operation. + can.Bus.SetCCCR_BRSE(1) // Bit rate switching. + + // Reset mode bits, then apply requested 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) + } + + // Nominal bit timing (64 MHz FDCAN clock, 16 tq/bit, ~80% sample point). + if config.TransferRate == 0 { + config.TransferRate = FDCANTransferRate500kbps + } + nbrp, ntseg1, ntseg2, nsjw, err := fdcanNominalBitTiming(config.TransferRate) + if err != nil { + return err + } + can.Bus.NBTP.Set(((nsjw - 1) << 25) | ((nbrp - 1) << 16) | ((ntseg1 - 1) << 8) | (ntseg2 - 1)) + + // Data bit timing (FD phase). + if config.TransferRateFD == 0 { + config.TransferRateFD = FDCANTransferRate1000kbps + } + if config.TransferRateFD < config.TransferRate { + return errFDCANInvalidTransferRateFD + } + dbrp, dtseg1, dtseg2, dsjw, err := fdcanDataBitTiming(config.TransferRateFD) + if err != nil { + return err + } + can.Bus.DBTP.Set(((dbrp - 1) << 16) | ((dtseg1 - 1) << 8) | ((dtseg2 - 1) << 4) | (dsjw - 1)) + + // Enable timestamp counter (internal, prescaler=1). + can.Bus.TSCC.Set(1) + + // Clear message RAM. + base := can.sramBase() + for addr := base; addr < base+sramcanSize; addr += 4 { + *(*uint32)(unsafe.Pointer(addr)) = 0 + } + + // Set filter list sizes: LSS[20:16], LSE[27:24]. + rxgfc := can.Bus.RXGFC.Get() + rxgfc &= ^uint32(0x0F1F0000) + rxgfc |= uint32(sramcanFLSNbr) << 16 + rxgfc |= uint32(sramcanFLENbr) << 24 + can.Bus.RXGFC.Set(rxgfc) + + // Start peripheral. + can.Bus.SetCCCR_CCE(0) + can.Bus.SetCCCR_INIT(0) + timeout = 10000 + for can.Bus.GetCCCR_INIT() != 0 { + timeout-- + if timeout == 0 { + return errFDCANTimeout + } + } + + return nil +} + +func (can *CAN) txFIFOLevel() (int, int) { + free := int(can.Bus.TXFQS.Get() & 0x07) // TFFL[2:0] + return sramcanTFQNbr - free, sramcanTFQNbr +} + +func (can *CAN) tx(id uint32, extendedID bool, data []byte) error { + if can.Bus.TXFQS.Get()&0x00200000 != 0 { // TFQF bit + return errFDCANTxFifoFull + } + + putIndex := (can.Bus.TXFQS.Get() >> 16) & 0x03 // TFQPI[1:0] + txAddr := can.sramBase() + sramcanTFQSA + uintptr(putIndex)*sramcanTFQSize + + // Header word 1: identifier and flags. + var w1 uint32 + if extendedID { + w1 = (id & 0x1FFFFFFF) | fdcanElementMaskXTD + } else { + w1 = (id & 0x7FF) << 18 + } + + // Header word 2: DLC only (classic CAN, no FD/BRS). + length := byte(len(data)) + if length > 8 { + length = 8 + } + w2 := uint32(length) << 16 + + *(*uint32)(unsafe.Pointer(txAddr)) = w1 + *(*uint32)(unsafe.Pointer(txAddr + 4)) = w2 + + // Copy data with 32-bit word access (Cortex-M0+). + for w := byte(0); w < (length+3)/4; w++ { + var word uint32 + base := w * 4 + for b := byte(0); b < 4 && base+b < length; b++ { + word |= uint32(data[base+b]) << (b * 8) + } + *(*uint32)(unsafe.Pointer(txAddr + 8 + uintptr(w)*4)) = word + } + + can.Bus.TXBAR.Set(1 << putIndex) + return nil +} + +func (can *CAN) rxFIFOLevel() (int, int) { + level := int(can.Bus.RXF0S.Get() & 0x0F) // F0FL[3:0] + return level, sramcanRF0Nbr +} + +func (can *CAN) setRxCallback(cb func(data []byte, id uint32, extendedID bool, timestamp uint32, flags uint32)) { + canRxCB[can.instance] = cb +} + +func (can *CAN) rxPoll() error { + cb := canRxCB[can.instance] + if cb == nil { + return nil + } + + for can.Bus.RXF0S.Get()&0x0F != 0 { + getIndex := (can.Bus.RXF0S.Get() >> 8) & 0x03 // F0GI[1:0] + rxAddr := can.sramBase() + sramcanRF0SA + uintptr(getIndex)*sramcanRF0Size + + w1 := *(*uint32)(unsafe.Pointer(rxAddr)) + w2 := *(*uint32)(unsafe.Pointer(rxAddr + 4)) + + extendedID := w1&fdcanElementMaskXTD != 0 + var id uint32 + if extendedID { + id = w1 & fdcanElementMaskEXTID + } else { + id = (w1 & fdcanElementMaskSTDID) >> 18 + } + + timestamp := w2 & fdcanElementMaskTS + dlc := byte((w2 & fdcanElementMaskDLC) >> 16) + + var flags uint32 + if w2&fdcanElementMaskFDF != 0 { + flags |= 1 // bit 0: FD frame + } + if w1&fdcanElementMaskRTR != 0 { + flags |= 2 // bit 1: RTR + } + if w2&fdcanElementMaskBRS != 0 { + flags |= 4 // bit 2: BRS + } + if w1&fdcanElementMaskESI != 0 { + flags |= 8 // bit 3: ESI + } + + dataLen := dlcToBytes[dlc&0x0F] + var buf [64]byte + for w := byte(0); w < (dataLen+3)/4; w++ { + word := *(*uint32)(unsafe.Pointer(rxAddr + 8 + uintptr(w)*4)) + base := w * 4 + for b := byte(0); b < 4 && base+b < dataLen; b++ { + buf[base+b] = byte(word >> (b * 8)) + } + } + + // Acknowledge before callback so the FIFO slot is freed. + can.Bus.RXF0A.Set(uint32(getIndex)) + cb(buf[:dataLen], id, extendedID, timestamp, flags) + } + return nil +} + +func (can *CAN) sramBase() uintptr { + if can.Bus == stm32.FDCAN2 { + return uintptr(sramcanBase) + sramcanSize + } + return uintptr(sramcanBase) +} + +// fdcanNominalBitTiming returns prescaler and segment values for the nominal (arbitration) phase. +// STM32G0 FDCAN clock = 64 MHz, 16 time quanta per bit, ~80% sample point. +func fdcanNominalBitTiming(rate FDCANTransferRate) (brp, tseg1, tseg2, sjw uint32, err error) { + 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 + default: + return 0, 0, 0, 0, errFDCANInvalidTransferRate + } +} + +// fdcanDataBitTiming returns prescaler and segment values for the data phase (FD). +func fdcanDataBitTiming(rate FDCANTransferRate) (brp, tseg1, tseg2, sjw uint32, err error) { + 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: + return 2, 13, 2, 4, nil + case FDCANTransferRate4000kbps: + return 1, 13, 2, 4, nil + default: + return 0, 0, 0, 0, errFDCANInvalidTransferRateFD + } +} From a5292ecb0fa85a1ed570cf78cca1317f53e61dcf Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Fri, 20 Feb 2026 14:01:27 -0300 Subject: [PATCH 2/5] full CAN API refactor --- src/machine/_old_stm32g0_can.go | 711 ++++++++++++++++++++++++++++ src/machine/board_amken_trio.go | 2 +- src/machine/board_nucleog0b1re.go | 4 +- src/machine/can.go | 34 ++ src/machine/machine_stm32g0_can.go | 610 ++++++++---------------- src/machine/machine_stm32g0_can2.go | 278 ----------- 6 files changed, 938 insertions(+), 701 deletions(-) create mode 100644 src/machine/_old_stm32g0_can.go delete mode 100644 src/machine/machine_stm32g0_can2.go diff --git a/src/machine/_old_stm32g0_can.go b/src/machine/_old_stm32g0_can.go new file mode 100644 index 0000000000..01bf523df8 --- /dev/null +++ b/src/machine/_old_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< 15 { + dlc = 15 + } + return dlcToBytes[dlc] +} + +// lengthToDLC converts a byte length to DLC value +func lengthToDLC(length uint8) (dlc byte) { + switch { + case length <= 8: + dlc = length + case length <= 12: + dlc = 9 + case length <= 16: + dlc = 10 + case length <= 20: + dlc = 11 + case length <= 24: + dlc = 12 + case length <= 32: + dlc = 13 + case length <= 48: + dlc = 14 + default: + dlc = 15 + } + return dlc +} diff --git a/src/machine/machine_stm32g0_can.go b/src/machine/machine_stm32g0_can.go index 01bf523df8..4faed37987 100644 --- a/src/machine/machine_stm32g0_can.go +++ b/src/machine/machine_stm32g0_can.go @@ -9,6 +9,8 @@ import ( "unsafe" ) +// Exported API in src/machine/can.go + // FDCAN Message RAM configuration // STM32G0B1 SRAMCAN base address: 0x4000B400 // Each FDCAN instance has its own message RAM area @@ -74,78 +76,51 @@ const ( FDCAN_IT_ERROR_PASSIVE = 0x00800000 ) -// FDCAN represents an FDCAN peripheral -type FDCAN struct { +// CAN is a STM32G0's CAN/FDCAN peripheral. +type CAN struct { Bus *stm32.FDCAN_Type TxAltFuncSelect uint8 RxAltFuncSelect uint8 Interrupt interrupt.Interrupt instance uint8 + alwaysFD bool } -// FDCANTransferRate represents CAN bus transfer rates -type FDCANTransferRate uint32 +// CANTransferRate represents CAN bus transfer rates +type CANTransferRate uint32 const ( - FDCANTransferRate125kbps FDCANTransferRate = 125000 - FDCANTransferRate250kbps FDCANTransferRate = 250000 - FDCANTransferRate500kbps FDCANTransferRate = 500000 - FDCANTransferRate1000kbps FDCANTransferRate = 1000000 - FDCANTransferRate2000kbps FDCANTransferRate = 2000000 // FD only - FDCANTransferRate4000kbps FDCANTransferRate = 4000000 // FD only + FDCANTransferRate125kbps CANTransferRate = 125000 + FDCANTransferRate250kbps CANTransferRate = 250000 + FDCANTransferRate500kbps CANTransferRate = 500000 + FDCANTransferRate1000kbps CANTransferRate = 1000000 + FDCANTransferRate2000kbps CANTransferRate = 2000000 // FD only + FDCANTransferRate4000kbps CANTransferRate = 4000000 // FD only ) -// FDCANMode represents the FDCAN operating mode -type FDCANMode uint8 +// CANMode represents the FDCAN operating mode +type CANMode uint8 const ( - FDCANModeNormal FDCANMode = 0 - FDCANModeBusMonitoring FDCANMode = 1 - FDCANModeInternalLoopback FDCANMode = 2 - FDCANModeExternalLoopback FDCANMode = 3 + CANModeNormal CANMode = 0 + CANModeBusMonitoring CANMode = 1 + CANModeInternalLoopback CANMode = 2 + CANModeExternalLoopback CANMode = 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 +// CANConfig holds FDCAN configuration parameters +type CANConfig struct { + TransferRate CANTransferRate // Nominal bit rate (arbitration phase) + TransferRateFD CANTransferRate // Data bit rate (data phase), must be >= TransferRate + Mode CANMode 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 + Standby Pin // Optional standby pin for CAN transceiver (set to NoPin if not used) + AlwaysFD bool // Always transmit as FD frames, even when data fits in classic CAN } -// FDCANFilterConfig represents a filter configuration -type FDCANFilterConfig struct { +// CANFilterConfig represents a message filter configuration +type CANFilterConfig 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 @@ -155,401 +130,301 @@ type FDCANFilterConfig struct { } 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") + errCANInvalidTransferRate = errors.New("CAN: invalid TransferRate") + errCANInvalidTransferRateFD = errors.New("CAN: invalid TransferRateFD") + errCANTimeout = errors.New("CAN: timeout") + errCANTxFifoFull = errors.New("CAN: Tx FIFO full") ) -// 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} +// enableFDCANClock enables the FDCAN peripheral clock +func enableFDCANClock() { + // FDCAN clock is on APB1 + stm32.RCC.SetAPBENR1_FDCANEN(1) +} + +// flags implemented as described in [CAN.SetRxCallback] +var canRxCB [2]func(data []byte, id uint32, extendedID bool, timestamp uint32, flags uint32) + +// Configure initializes the FDCAN peripheral and starts it. +func (can *CAN) Configure(config CANConfig) error { + can.alwaysFD = config.AlwaysFD -// 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 + // Exit 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 + return errCANTimeout } } - // Request initialization + // 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 + return errCANTimeout } } - // Enable configuration change + // 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 + can.Bus.SetCKDIV_PDIV(0) // No clock 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) + can.Bus.SetCCCR_DAR(0) // Enable auto retransmission. + can.Bus.SetCCCR_TXP(0) // Disable transmit pause. + can.Bus.SetCCCR_PXHD(0) // Enable protocol exception handling. + can.Bus.SetCCCR_FDOE(1) // FD operation. + can.Bus.SetCCCR_BRSE(1) // Bit rate switching. - // Enable FD mode with bit rate switching - can.Bus.SetCCCR_FDOE(1) - can.Bus.SetCCCR_BRSE(1) - - // Configure operating mode + // Reset mode bits, then apply requested 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: + case CANModeBusMonitoring: can.Bus.SetCCCR_MON(1) - case FDCANModeInternalLoopback: + case CANModeInternalLoopback: can.Bus.SetCCCR_TEST(1) can.Bus.SetCCCR_MON(1) can.Bus.SetTEST_LBCK(1) - case FDCANModeExternalLoopback: + case CANModeExternalLoopback: 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 + // Nominal bit timing (64 MHz FDCAN clock, 16 tq/bit, ~80% sample point). if config.TransferRate == 0 { config.TransferRate = FDCANTransferRate500kbps } - - nbrp, ntseg1, ntseg2, nsjw, err := can.calculateNominalBitTiming(config.TransferRate) + nbrp, ntseg1, ntseg2, nsjw, err := fdcanNominalBitTiming(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) + // Data bit timing (FD phase). if config.TransferRateFD == 0 { config.TransferRateFD = FDCANTransferRate1000kbps } if config.TransferRateFD < config.TransferRate { - return errFDCANInvalidTransferRateFD + return errCANInvalidTransferRateFD } - - dbrp, dtseg1, dtseg2, dsjw, err := can.calculateDataBitTiming(config.TransferRateFD) + dbrp, dtseg1, dtseg2, dsjw, err := fdcanDataBitTiming(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() + // Enable timestamp counter (internal, prescaler=1). + can.Bus.TSCC.Set(1) - return nil -} + // Clear message RAM. + base := can.sramBase() + for addr := base; addr < base+sramcanSize; addr += 4 { + *(*uint32)(unsafe.Pointer(addr)) = 0 + } -// Start enables the FDCAN peripheral for communication -func (can *FDCAN) Start() error { - // Disable configuration change - can.Bus.SetCCCR_CCE(0) + // Set filter list sizes: LSS[20:16], LSE[27:24]. + rxgfc := can.Bus.RXGFC.Get() + rxgfc &= ^uint32(0x0F1F0000) + rxgfc |= uint32(sramcanFLSNbr) << 16 + rxgfc |= uint32(sramcanFLENbr) << 24 + can.Bus.RXGFC.Set(rxgfc) - // Exit initialization mode + // Start peripheral. + can.Bus.SetCCCR_CCE(0) can.Bus.SetCCCR_INIT(0) - - // Wait for normal operation - timeout := 10000 - + timeout = 10000 for can.Bus.GetCCCR_INIT() != 0 { timeout-- if timeout == 0 { - return errFDCANTimeout + return errCANTimeout } } return nil } -// Stop disables the FDCAN peripheral -func (can *FDCAN) Stop() error { - // Request initialization +// Stop puts the FDCAN peripheral back into initialization mode. +func (can *CAN) Stop() error { can.Bus.SetCCCR_INIT(1) - - // Wait for init mode timeout := 10000 for can.Bus.GetCCCR_INIT() == 0 { timeout-- if timeout == 0 { - return errFDCANTimeout + return errCANTimeout } } - - // 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] +// txFIFOLevel implements [CAN.TxFIFOLevel]. +func (can *CAN) txFIFOLevel() (int, int) { + free := int(can.Bus.TXFQS.Get() & 0x07) // TFFL[2:0] + return sramcanTFQNbr - free, sramcanTFQNbr } -// RxFifoIsEmpty returns true if RX FIFO 0 is empty -func (can *FDCAN) RxFifoIsEmpty() bool { - return (can.Bus.RXF0S.Get() & 0x0F) == 0 -} +// tx implements [CAN.Tx]. +func (can *CAN) tx(id uint32, extendedID bool, data []byte) error { + if can.Bus.TXFQS.Get()&0x00200000 != 0 { // TFQF bit + return errCANTxFifoFull + } -// 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 + length := byte(len(data)) + if length > 64 { + length = 64 } - // Get put index - putIndex := (can.Bus.TXFQS.Get() >> 16) & 0x03 // TFQPI[1:0] + // Use FD framing if configured to always use FD, or if data exceeds classic CAN max. + isFD := can.alwaysFD || length > 8 - // Calculate TX buffer address - sramBase := can.getSRAMBase() - txAddress := sramBase + sramcanTFQSA + (uintptr(putIndex) * sramcanTFQSize) + putIndex := (can.Bus.TXFQS.Get() >> 16) & 0x03 // TFQPI[1:0] + txAddr := can.sramBase() + sramcanTFQSA + uintptr(putIndex)*sramcanTFQSize - // Build first word + // Header word 1: identifier and flags. 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 + if extendedID { + w1 = (id & 0x1FFFFFFF) | fdcanElementMaskXTD + } else { + w1 = (id & 0x7FF) << 18 } - // 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 + // Header word 2: DLC, FD/BRS flags. + dlc := lengthToDLC(length) + w2 := uint32(dlc) << 16 + if isFD { + w2 |= fdcanElementMaskFDF | fdcanElementMaskBRS } - w2 |= uint32(e.MM) << 24 - // Write to message RAM - *(*uint32)(unsafe.Pointer(txAddress)) = w1 - *(*uint32)(unsafe.Pointer(txAddress + 4)) = w2 + *(*uint32)(unsafe.Pointer(txAddr)) = w1 + *(*uint32)(unsafe.Pointer(txAddr + 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++ { + // Copy data with 32-bit word access (Cortex-M0+). + for w := byte(0); w < (length+3)/4; 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) + base := w * 4 + for b := byte(0); b < 4 && base+b < length; b++ { + word |= uint32(data[base+b]) << (b * 8) } - *(*uint32)(unsafe.Pointer(txAddress + 8 + uintptr(w)*4)) = word + *(*uint32)(unsafe.Pointer(txAddr + 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] - } +// rxFIFOLevel implements [CAN.RxFIFOLevel]. +func (can *CAN) rxFIFOLevel() (int, int) { + level := int(can.Bus.RXF0S.Get() & 0x0F) // F0FL[3:0] + return level, sramcanRF0Nbr +} - return can.TxRaw(&e) +// setRxCallback implements [CAN.SetRxCallback]. +func (can *CAN) setRxCallback(cb func(data []byte, id uint32, extendedID bool, timestamp uint32, flags uint32)) { + canRxCB[can.instance] = cb } -// RxRaw receives a CAN frame into the raw buffer element structure -func (can *FDCAN) RxRaw(e *FDCANRxBufferElement) error { - if can.RxFifoIsEmpty() { - return errFDCANRxFifoEmpty +// rxPoll implements [CAN.RxPoll]. +func (can *CAN) rxPoll() error { + cb := canRxCB[can.instance] + if cb == nil { + return nil } - // Get get index - getIndex := (can.Bus.RXF0S.Get() >> 8) & 0x03 // F0GI[1:0] + for can.Bus.RXF0S.Get()&0x0F != 0 { + getIndex := (can.Bus.RXF0S.Get() >> 8) & 0x03 // F0GI[1:0] + rxAddr := can.sramBase() + sramcanRF0SA + uintptr(getIndex)*sramcanRF0Size - // Calculate RX buffer address - sramBase := can.getSRAMBase() - rxAddress := sramBase + sramcanRF0SA + (uintptr(getIndex) * sramcanRF0Size) + w1 := *(*uint32)(unsafe.Pointer(rxAddr)) + w2 := *(*uint32)(unsafe.Pointer(rxAddr + 4)) - // 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)) + extendedID := w1&fdcanElementMaskXTD != 0 + var id uint32 + if extendedID { + id = w1 & fdcanElementMaskEXTID + } else { + id = (w1 & fdcanElementMaskSTDID) >> 18 } - } - - // 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) + timestamp := w2 & fdcanElementMaskTS + dlc := byte((w2 & fdcanElementMaskDLC) >> 16) + isFD := w2&fdcanElementMaskFDF != 0 - idx := can.instance - fdcanInstances[idx] = can + var flags uint32 + if isFD { + flags |= 1 // bit 0: FD frame + } + if w1&fdcanElementMaskRTR != 0 { + flags |= 2 // bit 1: RTR + } + if w2&fdcanElementMaskBRS != 0 { + flags |= 4 // bit 2: BRS + } + if w1&fdcanElementMaskESI != 0 { + flags |= 8 // bit 3: ESI + } - for i := uint(0); i < 32; i++ { - if ie&(1< 8 { + dataLen = 8 + } + var buf [64]byte + for w := byte(0); w < (dataLen+3)/4; w++ { + word := *(*uint32)(unsafe.Pointer(rxAddr + 8 + uintptr(w)*4)) + base := w * 4 + for b := byte(0); b < 4 && base+b < dataLen; b++ { + buf[base+b] = byte(word >> (b * 8)) + } } - } - can.Interrupt.Enable() + // Acknowledge before callback so the FIFO slot is freed. + can.Bus.RXF0A.Set(uint32(getIndex)) + cb(buf[:dataLen], id, extendedID, timestamp, flags) + } return nil } -// ConfigureFilter configures a message filter -func (can *FDCAN) ConfigureFilter(config FDCANFilterConfig) error { - sramBase := can.getSRAMBase() +// ConfigureFilter configures a message acceptance filter. +func (can *CAN) ConfigureFilter(config CANFilterConfig) error { + base := can.sramBase() if config.IsExtendedID { - // Extended filter if config.Index >= sramcanFLENbr { - return errors.New("FDCAN: filter index out of range") + return errors.New("CAN: filter index out of range") } - filterAddr := sramBase + sramcanFLESA + (uintptr(config.Index) * sramcanFLESize) + filterAddr := base + 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") + return errors.New("CAN: filter index out of range") } - filterAddr := sramBase + sramcanFLSSA + (uintptr(config.Index) * sramcanFLSSize) + filterAddr := base + sramcanFLSSA + (uintptr(config.Index) * sramcanFLSSize) - // Build filter element w := (uint32(config.Type) << 30) | (uint32(config.Config) << 27) | ((config.ID1 & 0x7FF) << 16) | @@ -561,56 +436,32 @@ func (can *FDCAN) ConfigureFilter(config FDCANFilterConfig) error { return nil } -func (can *FDCAN) getSRAMBase() uintptr { - base := uintptr(sramcanBase) +func (can *CAN) sramBase() uintptr { if can.Bus == stm32.FDCAN2 { - base += sramcanSize + return uintptr(sramcanBase) + 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) + return uintptr(sramcanBase) } -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 +// fdcanNominalBitTiming returns prescaler and segment values for the nominal (arbitration) phase. +// STM32G0 FDCAN clock = 64 MHz, 16 time quanta per bit, ~80% sample point. +func fdcanNominalBitTiming(rate CANTransferRate) (brp, tseg1, tseg2, sjw uint32, err error) { 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 + return 0, 0, 0, 0, errCANInvalidTransferRate } } -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 +// fdcanDataBitTiming returns prescaler and segment values for the data phase (FD). +func fdcanDataBitTiming(rate CANTransferRate) (brp, tseg1, tseg2, sjw uint32, err error) { switch rate { case FDCANTransferRate125kbps: return 32, 13, 2, 4, nil @@ -621,91 +472,10 @@ func (can *FDCAN) calculateDataBitTiming(rate FDCANTransferRate) (brp, tseg1, ts 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 + return 0, 0, 0, 0, errCANInvalidTransferRateFD } } - -// 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<> 16) & 0x03 // TFQPI[1:0] - txAddr := can.sramBase() + sramcanTFQSA + uintptr(putIndex)*sramcanTFQSize - - // Header word 1: identifier and flags. - var w1 uint32 - if extendedID { - w1 = (id & 0x1FFFFFFF) | fdcanElementMaskXTD - } else { - w1 = (id & 0x7FF) << 18 - } - - // Header word 2: DLC only (classic CAN, no FD/BRS). - length := byte(len(data)) - if length > 8 { - length = 8 - } - w2 := uint32(length) << 16 - - *(*uint32)(unsafe.Pointer(txAddr)) = w1 - *(*uint32)(unsafe.Pointer(txAddr + 4)) = w2 - - // Copy data with 32-bit word access (Cortex-M0+). - for w := byte(0); w < (length+3)/4; w++ { - var word uint32 - base := w * 4 - for b := byte(0); b < 4 && base+b < length; b++ { - word |= uint32(data[base+b]) << (b * 8) - } - *(*uint32)(unsafe.Pointer(txAddr + 8 + uintptr(w)*4)) = word - } - - can.Bus.TXBAR.Set(1 << putIndex) - return nil -} - -func (can *CAN) rxFIFOLevel() (int, int) { - level := int(can.Bus.RXF0S.Get() & 0x0F) // F0FL[3:0] - return level, sramcanRF0Nbr -} - -func (can *CAN) setRxCallback(cb func(data []byte, id uint32, extendedID bool, timestamp uint32, flags uint32)) { - canRxCB[can.instance] = cb -} - -func (can *CAN) rxPoll() error { - cb := canRxCB[can.instance] - if cb == nil { - return nil - } - - for can.Bus.RXF0S.Get()&0x0F != 0 { - getIndex := (can.Bus.RXF0S.Get() >> 8) & 0x03 // F0GI[1:0] - rxAddr := can.sramBase() + sramcanRF0SA + uintptr(getIndex)*sramcanRF0Size - - w1 := *(*uint32)(unsafe.Pointer(rxAddr)) - w2 := *(*uint32)(unsafe.Pointer(rxAddr + 4)) - - extendedID := w1&fdcanElementMaskXTD != 0 - var id uint32 - if extendedID { - id = w1 & fdcanElementMaskEXTID - } else { - id = (w1 & fdcanElementMaskSTDID) >> 18 - } - - timestamp := w2 & fdcanElementMaskTS - dlc := byte((w2 & fdcanElementMaskDLC) >> 16) - - var flags uint32 - if w2&fdcanElementMaskFDF != 0 { - flags |= 1 // bit 0: FD frame - } - if w1&fdcanElementMaskRTR != 0 { - flags |= 2 // bit 1: RTR - } - if w2&fdcanElementMaskBRS != 0 { - flags |= 4 // bit 2: BRS - } - if w1&fdcanElementMaskESI != 0 { - flags |= 8 // bit 3: ESI - } - - dataLen := dlcToBytes[dlc&0x0F] - var buf [64]byte - for w := byte(0); w < (dataLen+3)/4; w++ { - word := *(*uint32)(unsafe.Pointer(rxAddr + 8 + uintptr(w)*4)) - base := w * 4 - for b := byte(0); b < 4 && base+b < dataLen; b++ { - buf[base+b] = byte(word >> (b * 8)) - } - } - - // Acknowledge before callback so the FIFO slot is freed. - can.Bus.RXF0A.Set(uint32(getIndex)) - cb(buf[:dataLen], id, extendedID, timestamp, flags) - } - return nil -} - -func (can *CAN) sramBase() uintptr { - if can.Bus == stm32.FDCAN2 { - return uintptr(sramcanBase) + sramcanSize - } - return uintptr(sramcanBase) -} - -// fdcanNominalBitTiming returns prescaler and segment values for the nominal (arbitration) phase. -// STM32G0 FDCAN clock = 64 MHz, 16 time quanta per bit, ~80% sample point. -func fdcanNominalBitTiming(rate FDCANTransferRate) (brp, tseg1, tseg2, sjw uint32, err error) { - 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 - default: - return 0, 0, 0, 0, errFDCANInvalidTransferRate - } -} - -// fdcanDataBitTiming returns prescaler and segment values for the data phase (FD). -func fdcanDataBitTiming(rate FDCANTransferRate) (brp, tseg1, tseg2, sjw uint32, err error) { - 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: - return 2, 13, 2, 4, nil - case FDCANTransferRate4000kbps: - return 1, 13, 2, 4, nil - default: - return 0, 0, 0, 0, errFDCANInvalidTransferRateFD - } -} From 25f5f76a4811e3ec2d6f2e58cf778d735684bda9 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Sat, 21 Feb 2026 09:19:32 -0300 Subject: [PATCH 3/5] add interrupts --- src/machine/machine_stm32g0_can.go | 56 ++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/src/machine/machine_stm32g0_can.go b/src/machine/machine_stm32g0_can.go index 4faed37987..f7aa209c95 100644 --- a/src/machine/machine_stm32g0_can.go +++ b/src/machine/machine_stm32g0_can.go @@ -84,6 +84,7 @@ type CAN struct { Interrupt interrupt.Interrupt instance uint8 alwaysFD bool + rxInterrupt bool } // CANTransferRate represents CAN bus transfer rates @@ -115,8 +116,9 @@ type CANConfig struct { Mode CANMode Tx Pin Rx Pin - Standby Pin // Optional standby pin for CAN transceiver (set to NoPin if not used) - AlwaysFD bool // Always transmit as FD frames, even when data fits in classic CAN + Standby Pin // Optional standby pin for CAN transceiver (set to NoPin if not used) + AlwaysFD bool // Always transmit as FD frames, even when data fits in classic CAN + EnableRxInterrupt bool // Enable interrupt-driven receive (messages delivered via SetRxCallback) } // CANFilterConfig represents a message filter configuration @@ -145,6 +147,10 @@ func enableFDCANClock() { // flags implemented as described in [CAN.SetRxCallback] var canRxCB [2]func(data []byte, id uint32, extendedID bool, timestamp uint32, flags uint32) +// canInstances tracks CAN peripherals with interrupt-driven RX enabled. +// A non-nil entry means setRxCallback was called with a non-nil callback. +var canInstances [2]*CAN + // Configure initializes the FDCAN peripheral and starts it. func (can *CAN) Configure(config CANConfig) error { can.alwaysFD = config.AlwaysFD @@ -332,23 +338,50 @@ func (can *CAN) tx(id uint32, extendedID bool, data []byte) error { } // rxFIFOLevel implements [CAN.RxFIFOLevel]. +// Returns 0,0 when interrupt-driven (messages delivered via callback). func (can *CAN) rxFIFOLevel() (int, int) { + if canInstances[can.instance] != nil { + return 0, 0 + } level := int(can.Bus.RXF0S.Get() & 0x0F) // F0FL[3:0] return level, sramcanRF0Nbr } // setRxCallback implements [CAN.SetRxCallback]. +// When cb is non-nil, interrupt-driven receive is enabled on RX FIFO 0. +// The CAN.Interrupt field must be initialized with interrupt.New in the board file. func (can *CAN) setRxCallback(cb func(data []byte, id uint32, extendedID bool, timestamp uint32, flags uint32)) { canRxCB[can.instance] = cb + if cb != nil { + canInstances[can.instance] = can + // Enable RX FIFO 0 new message interrupt, routed to interrupt line 0. + can.Bus.SetIE_RF0NE(1) + can.Bus.SetILS_RxFIFO0(0) + can.Bus.SetILE_EINT0(1) + can.Interrupt.Enable() + } else { + can.Bus.SetIE_RF0NE(0) + canInstances[can.instance] = nil + } } // rxPoll implements [CAN.RxPoll]. +// No-op when interrupt-driven receive is active. func (can *CAN) rxPoll() error { + if canInstances[can.instance] != nil { + return nil + } cb := canRxCB[can.instance] if cb == nil { return nil } + processRxFIFO0(can, cb) + return nil +} +// processRxFIFO0 drains RX FIFO 0 and delivers each message to cb. +// Used by both rxPoll (poll mode) and canHandleInterrupt (interrupt mode). +func processRxFIFO0(can *CAN, cb func(data []byte, id uint32, extendedID bool, timestamp uint32, flags uint32)) { for can.Bus.RXF0S.Get()&0x0F != 0 { getIndex := (can.Bus.RXF0S.Get() >> 8) & 0x03 // F0GI[1:0] rxAddr := can.sramBase() + sramcanRF0SA + uintptr(getIndex)*sramcanRF0Size @@ -399,7 +432,24 @@ func (can *CAN) rxPoll() error { can.Bus.RXF0A.Set(uint32(getIndex)) cb(buf[:dataLen], id, extendedID, timestamp, flags) } - return nil +} + +// canHandleInterrupt is the shared interrupt handler for FDCAN interrupt line 0 (IRQ_TIM16). +// Both FDCAN1 and FDCAN2 share this IRQ vector. +func canHandleInterrupt(interrupt.Interrupt) { + for i := range canInstances { + can := canInstances[i] + if can == nil { + continue + } + ir := can.Bus.IR.Get() + if ir&FDCAN_IT_RX_FIFO0_NEW_MESSAGE != 0 { + can.Bus.IR.Set(FDCAN_IT_RX_FIFO0_NEW_MESSAGE) // Write 1 to clear + if cb := canRxCB[i]; cb != nil { + processRxFIFO0(can, cb) + } + } + } } // ConfigureFilter configures a message acceptance filter. From ef05ba2ea170844c7bd92baba75c8301401af3ba Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Sat, 21 Feb 2026 09:20:47 -0300 Subject: [PATCH 4/5] delete old can --- src/machine/_old_stm32g0_can.go | 711 -------------------------------- 1 file changed, 711 deletions(-) delete mode 100644 src/machine/_old_stm32g0_can.go diff --git a/src/machine/_old_stm32g0_can.go b/src/machine/_old_stm32g0_can.go deleted file mode 100644 index 01bf523df8..0000000000 --- a/src/machine/_old_stm32g0_can.go +++ /dev/null @@ -1,711 +0,0 @@ -//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< Date: Sun, 1 Mar 2026 20:52:00 -0300 Subject: [PATCH 5/5] take @knieriem suggestions and apply them to can.go --- src/machine/can.go | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/src/machine/can.go b/src/machine/can.go index a86f577bae..dcdff48988 100644 --- a/src/machine/can.go +++ b/src/machine/can.go @@ -5,13 +5,45 @@ package machine // unexported functions here are implemented in the device file // and added to the build tags of this file. +// These types are an alias for documentation purposes exclusively. We wish +// the interface to be used by other ecosystems besides TinyGo which is why +// we need these types to be a primitive types at the interface level. +// If these types are defined at machine or machine/can level they are not +// usable by non-TinyGo projects. This is not good news for fostering wider adoption +// of our API in "big-Go" embedded system projects like TamaGo and periph.io +type ( + // CAN IDs in tinygo are represented as 30 bit integers where + // bits 1..29 store the actual ID and the 30th bit stores the IDE bit (if extended ID). + // We include the extended ID bit in the ID itself to make comparison of IDs easier for users + // since two identical IDs where one is extended and one is not are NOT equivalent IDs. + canID = uint32 + // CAN flags bitmask are defined below. + canFlags = uint32 +) + +// CAN ID definitions. +const ( + canIDStdMask canID = (1 << 11) - 1 + canIDExtendedMask canID = (1 << 29) - 1 + canIDExtendedBit canID = 1 << 30 +) + +// CAN Flag bit definitions. +const ( + canFlagBRS canFlags = 1 << 0 // Bit Rate Switch active on tx/rx of frame. + canFlagFDF canFlags = 1 << 1 // Is a FD Frame. + canFlagRTR canFlags = 1 << 2 // is a retransmission request frame. + canFlagESI canFlags = 1 << 3 // Error status indicator active on tx/rx of frame. + canFlagIDE canFlags = 1 << 4 // Extended ID. +) + // TxFIFOLevel returns amount of CAN frames stored for transmission and total Tx fifo length. func (can *CAN) TxFIFOLevel() (level int, maxlevel int) { return can.txFIFOLevel() } // Tx puts a CAN frame in TxFIFO for transmission. Returns error if TxFIFO is full. -func (can *CAN) Tx(id uint32, extendedID bool, data []byte) error { +func (can *CAN) Tx(id canID, flags canFlags, data []byte) error { return can.tx(id, extendedID, data) } @@ -21,12 +53,8 @@ func (can *CAN) RxFIFOLevel() (level int, maxlevel int) { return can.rxFIFOLevel() } -// SetRxCallback sets the receive callback. flags is a bitfield where bits set are: -// - bit 0: Is a FD frame. -// - bit 1: Is a RTR frame. -// - bit 2: Bitrate switch was active in frame. -// - bit 3: ESI error state indicator active. -func (can *CAN) SetRxCallback(cb func(data []byte, id uint32, extendedID bool, timestamp uint32, flags uint32)) { +// SetRxCallback sets the receive callback. See [canFlags] for information on how bits are layed out. +func (can *CAN) SetRxCallback(cb func(data []byte, id canID, timestamp uint32, flags canFlags)) { can.setRxCallback(cb) }