diff --git a/drivers/am13x/README.md b/drivers/am13x/README.md new file mode 100644 index 0000000..ca493a7 --- /dev/null +++ b/drivers/am13x/README.md @@ -0,0 +1,274 @@ +# Libcanard Driver for TI AM13E230x MCU + +This is a compact driver that connects the +[libcanard](https://github.com/dronecan/libcanard) library to +the TI MCAN peripheral on AM13E230x microcontrollers. It has no +dependencies beyond a small part of the standard C library and the TI +DriverLib, and can be used with any operating system or on bare metal. + +--- + +## How It All Fits Together + +`canard_am13x.c` knows nothing about DroneCAN +protocol. It only knows how to move raw CAN frames in and out of the +TI MCAN peripheral registers. `app_mcan.c` sits above both and calls +whichever it needs at each step. + +--- + +## Project Structure + +This is what the CCS workspace looks like for any given example. + +``` +example_root_folder +│ +├── example.syscfg ← Hardware configuration. Open this +│ in CCS SysConfig editor to change +│ MCAN settings, GPIO, timers. +│ +├── am13e230x_lp/ +│ └── app_cfg.h ← Application constants: node ID, +│ memory pool size, DTID values, +│ timer instance, tick rate, etc. +│ +├── libcanard/ ← Migrate the entire libcanard +│ ├── canard.c directory into the project root. +│ ├── canard.h Do not modify these files. +│ ├── canard_internals.h +│ └── drivers/ +│ ├── canard_am13x.c ← TI driver layer implementation. +│ └── canard_am13x.h ← TI driver layer header. +│ +├── app_mcan.c ← Application layer. Your DroneCAN +│ encode/decode logic lives here. +│ +├── main.c ← Entry point. +│ +└── targetConfigs/ ← CCS debug configuration. +``` + +### Adding libcanard to the Build + +After copying the `libcanard` directory into the project root, you need +to tell the compiler where to find the headers. Go to: + +**Project Properties → Build → Arm Compiler → Include Options** + +Add these two paths: + +``` +-I"${PROJECT_ROOT}/libcanard" +-I"${PROJECT_ROOT}/libcanard/drivers/" +``` + +--- + +## SysConfig Setup + +TI toolchain uses sysconfig tool to handle peripheral initialization via a no-code GUI interface. This readme also includes a sample example.syscfg file that can be used as a starting point for DroneCAN applications. +can use it as a starting point. + +### MCAN Settings (both TX and RX) + +- **CAN FD Mode**: Enable +- **Bit Rate Switching**: Enable +- The driver transmits classic CAN frames (`fdf=0`, `brs=0`), so only + the **arbitration bit rate** is used for actual transmission and + reception. Note this value down; it must match on all nodes. +- Typical arbitration bit rate: **1 Mbps** or **500 Kbps** + +### RX Example: Additional SysConfig Setting + +In the MCAN filter configuration, set: + +> **Accept Non-Matching Frames** → **Store in RX FIFO 0** + +Do this for both Standard and Extended frames, this ensures frames that do not match any programmed filter still reach +the application. Without this, the hardware silently drops them. + +### TX Example: Additional SysConfig Setting + +Under the Timer (TIMG) configuration: + +> Set a **custom timer profile** and configure the period to **1000 ms** +> or **500 ms** depending on how often you want NodeStatus to broadcast. + +This timer drives the `g_tick` flag in `app_mcan.c` which triggers the +periodic TX. + +#### Adjusting Hardware TX Buffers + +If your application demands heavy packet transmission or needs to hold more frames in the hardware queue: +1. Open `example.syscfg` in the GUI interface. +2. Navigate to **MCAN** on the left panel. +3. Drop down the **MCAN Basic Configuration** section. +4. Expand the **Message RAM Configuration** subsection. +5. Locate **Number of Dedicated TX Buffers** and increase the value if needed (default configuration is 1). + +### Reference `example.syscfg` File + +Below is the complete SysConfig source configuration file used for generating this driver environment: + +```javascript +/** + * Import the modules used in this configuration. + */ +const config = scripting.addModule("/drivers/config"); +const gpio = scripting.addModule("/drivers/gpio"); +const gpio1 = gpio.addInstance(); +const mcan = scripting.addModule("/drivers/mcan", {}, false); +const mcan1 = mcan.addInstance(); +const sysctl = scripting.addModule("/drivers/sysctl"); +const timer = scripting.addModule("/drivers/timer", {}, false); +const timer1 = timer.addInstance(); +const uart = scripting.addModule("/drivers/uart", {}, false); +const uart1 = uart.addInstance(); + +/** + * Write custom configuration values to the imported modules. + */ +gpio1.$name = "GPIO_GRP_0"; +gpio1.port = "PORTA"; +gpio1.associatedPins[0].$name = "TCAN_STB"; +gpio1.associatedPins[0].pin.$assign = "PA24"; + +mcan1.$name = "MCAN"; +mcan1.wkupReqEnable = true; +mcan1.emulationEnable = true; +mcan1.autoWkupEnable = true; +mcan1.tdcEnable = true; +mcan1.additionalCoreConfig = true; +mcan1.rrfe = true; +mcan1.rrfs = true; +mcan1.txEventFIFOWaterMark = 0; +mcan1.txEventFIFOSize = 0; +mcan1.txBufNum = 1; +mcan1.txBufElemSize = "DL_MCAN_ELEM_SIZE_8BYTES"; +mcan1.nomTimeSeg1_manual = 126; +mcan1.nomTimeSeg2_manual = 31; +mcan1.nomSynchJumpWidth_manual = 31; +mcan1.dataTimeSeg1_manual = 14; +mcan1.dataTimeSeg2_manual = 3; +mcan1.dataSynchJumpWidth_manual = 3; +mcan1.spPercent = 80; +mcan1.desiredNomRate = 250; +mcan1.rxFIFO1startAddr = 0; +mcan1.rxFIFO1size = 0; +mcan1.rxFIFO1waterMark = 0; +mcan1.m0interrupts = ["DL_MCAN_INTERRUPT_LINE1"]; +mcan1.registerInterrupt = true; +mcan1.enableInterrupt = true; +mcan1.interruptLine = ["DL_MCAN_INTR_LINE_NUM_1"]; +mcan1.interruptLine1Flag = ["DL_MCAN_INTR_SRC_BUS_OFF_STATUS","DL_MCAN_INTR_SRC_ERR_PASSIVE","DL_MCAN_INTR_SRC_PROTOCOL_ERR_ARB","DL_MCAN_INTR_SRC_PROTOCOL_ERR_DATA","DL_MCAN_INTR_SRC_RX_FIFO0_FULL","DL_MCAN_INTR_SRC_RX_FIFO0_NEW_MSG","DL_MCAN_INTR_SRC_RX_FIFO0_WATERMARK","DL_MCAN_INTR_SRC_TRANS_COMPLETE","DL_MCAN_INTR_SRC_WARNING_STATUS"]; +mcan1.interruptFlags = ["DL_MCAN_INTR_SRC_BUS_OFF_STATUS","DL_MCAN_INTR_SRC_ERR_PASSIVE","DL_MCAN_INTR_SRC_PROTOCOL_ERR_ARB","DL_MCAN_INTR_SRC_PROTOCOL_ERR_DATA","DL_MCAN_INTR_SRC_RX_FIFO0_FULL","DL_MCAN_INTR_SRC_RX_FIFO0_NEW_MSG","DL_MCAN_INTR_SRC_RX_FIFO0_WATERMARK","DL_MCAN_INTR_SRC_TRANS_COMPLETE","DL_MCAN_INTR_SRC_WARNING_STATUS"]; +mcan1.lss = 2; +mcan1.lse = 2; +mcan1.flesa = 8; +mcan1.txStartAddr = 24; +mcan1.txEventFIFOStartAddr = 96; +mcan1.rxBufStartAddr = 96; +mcan1.rxFIFO0startAddr = 112; +mcan1.rxFIFO0size = 10; +mcan1.rxFIFO0waterMark = 8; +mcan1.stdFiltType = "10"; +mcan1.stdFiltElem = "001"; +mcan1.extFiltElem = "001"; +mcan1.extFiltType = "00"; +mcan1.extFiltID2 = 536870911; +mcan1.stdFiltID1 = 3; +mcan1.stdFiltID2 = 4; +mcan1.peripheral.$assign = "MCAN0"; +mcan1.peripheral.txPin.$assign = "PA12"; +mcan1.peripheral.rxPin.$assign = "PA11"; +mcan1.txPinConfig.direction = scripting.forceWrite("OUTPUT"); +mcan1.txPinConfig.hideOutputInversion = scripting.forceWrite(false); +mcan1.txPinConfig.onlyInternalResistor = scripting.forceWrite(false); +mcan1.txPinConfig.passedPeripheralType = scripting.forceWrite("Digital"); +mcan1.txPinConfig.$name = "ti_driverlib_gpio_GPIOPinGeneric0"; +mcan1.rxPinConfig.hideOutputInversion = scripting.forceWrite(false); +mcan1.rxPinConfig.onlyInternalResistor = scripting.forceWrite(false); +mcan1.rxPinConfig.passedPeripheralType = scripting.forceWrite("Digital"); +mcan1.rxPinConfig.$name = "ti_driverlib_gpio_GPIOPinGeneric1"; + +sysctl.useHFCLK = true; +sysctl.SYSPLLSource = "DL_SYSCTL_SYSPLL_REF_HFCLK"; +sysctl.SYSPLL_Qdiv = 32; + +timer1.$name = "APP_TIMER_0"; +timer1.registerInterrupt = true; +timer1.timerClkDiv = "DL_TIMER_CLOCK_DIVIDE_8"; +timer1.timerClkPrescale = 256; +timer1.timerMode = "DL_TIMER_TIMER_MODE_PERIODIC"; +timer1.interrupts = ["DL_TIMER_INTERRUPT_ZERO_EVENT"]; +timer1.timerPeriod = "1000 ms"; + +uart1.$name = "APP_MCANTX_LOGUART_0"; +uart1.targetBaudRate = 115200; +uart1.peripheral.$assign = "UC4"; +uart1.peripheral.rxPin.$assign = "PA1"; +uart1.peripheral.txPin.$assign = "PA0"; +uart1.txPinConfig.direction = scripting.forceWrite("OUTPUT"); +uart1.txPinConfig.hideOutputInversion = scripting.forceWrite(false); +uart1.txPinConfig.onlyInternalResistor = scripting.forceWrite(false); +uart1.txPinConfig.passedPeripheralType = scripting.forceWrite("Digital"); +uart1.txPinConfig.$name = "drivers_gpio_v0_gpioPinConfig0"; +uart1.rxPinConfig.hideOutputInversion = scripting.forceWrite(false); +uart1.rxPinConfig.onlyInternalResistor = scripting.forceWrite(false); +uart1.rxPinConfig.passedPeripheralType = scripting.forceWrite("Digital"); +uart1.rxPinConfig.$name = "drivers_gpio_v0_gpioPinConfig1"; + +/** + * Pinmux solution for unlocked pins/peripherals. This ensures that minor changes to the automatic solver in a future + * version of the tool will not impact the pinmux you originally saw. These lines can be completely deleted in order to + * re-solve from scratch. + */ +sysctl.peripheral.$suggestSolution = "SYSCTL"; +sysctl.peripheral.x1Pin.$suggestSolution = "PC16_X1"; +sysctl.peripheral.x2Pin.$suggestSolution = "PC17_X2"; +timer1.peripheral.$suggestSolution = "TIMG4_0"; + +``` +--- + +## Initialisation Sequence + +``` +main() + │ + ├── Device_Init() + │ TI device support layer. Sets up clocks and power domains. + │ + ├── SYSCFG_DL_init() + │ Generated by example.syscfg. Configures MCAN peripheral, + │ GPIO pins, and timers. Must run before canardTI_MCAN_Init(). + │ + ├── canardTI_MCAN_Init(MCAN0, CanardTI_MCAN_IfaceModeNormal) + │ Verifies MCAN is running, releases TCAN + │ transceiver from standby, applies bus mode. + │ + ├── canardTI_MCAN_ConfigureAcceptanceFilters(filters, count) + │ Programmes hardware RX filters. + │ + ├── canardInit(...) + │ libcanard. Initialises instance, assigns memory pool, + │ registers shouldAccept and onReception callbacks. + │ + ├── canardSetLocalNodeID(&g_canard, APP_NODE_ID) + │ libcanard. Assigns this node's DroneCAN ID. + │ + ├── DL_TimerG_startCounter(APP_TIMER_INST) + │ Starts the periodic tick timer configured in example.syscfg. + │ + └── loop: App_McanTx_Run() or App_McanRx_Run() +``` +--- + +### WARNING: Not Thread-Safe + +This driver does not use IRQ or critical sections. It is safe to call +its API functions from IRQ context **provided the application ensures +that no concurrent calls occur from thread and IRQ context +simultaneously**. The application is responsible for all guarding. + diff --git a/drivers/am13x/canard_am13x.c b/drivers/am13x/canard_am13x.c new file mode 100644 index 0000000..7121cf1 --- /dev/null +++ b/drivers/am13x/canard_am13x.c @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2026 Texas Instruments Incorporated + * + * Distributed under the MIT License, available in the file LICENSE. + * + * + * + * Libcanard interface layer for TI MCAN peripheral. + * + * WARNING: This driver is NOT thread-safe. See canard_am13x.h. + * + * SysConfig-generated includes: + * ti_sdk_dl_config.h — generated from example.syscfg in the TI + * project. Provides MCAN_INST, GPIO port/pin + * macros, and peripheral init declarations. + * Do not edit manually. + * + * device.h — provides DEVICE_DELAY_MS() used for the + * transceiver STB pin settling delay. + * Part of the TI DriverLib device support layer. + */ + +#include "canard_am13x.h" +#include "canard.h" +#include "ti_sdk_dl_config.h" +#include "device.h" +#include "dl_gpio.h" +#include + +/* ========================================================================== */ +/* Internal Constants */ +/* ========================================================================== */ + +#define MCAN_TX_BUFFER_INDEX (0U) +#define MCAN_RX_FIFO0_INDEX (0U) +#define MCAN_MODE_TIMEOUT_LOOPS (1000000U) +#define MCAN_TRANSCEIVER_DELAY_MS (10U) +#define MCAN_MAX_EXT_FILTERS (8U) + +/* ========================================================================== */ +/* GPIO Macros — SysConfig Generated */ +/* ========================================================================== */ + +/* + * GPIO_GRP_0_PORT and GPIO_GRP_0_TCAN_STB_PIN are generated by + * example.syscfg in the TI Toolchain. They identify the GPIO port and + * pin connected to the TCAN transceiver STB (standby) input. + * + * STB is active-low: + * Low (pin cleared) → transceiver active, CAN bus connected + * High (pin set) → transceiver in standby, CAN bus disconnected + * + * To change the STB pin assignment, update the GPIO configuration in + * example.syscfg and regenerate ti_sdk_dl_config.h. + */ + +/* ========================================================================== */ +/* Module State */ +/* ========================================================================== */ + +static CanardTI_MCAN_Stats gStats; +static bool gAbortTxOnError = false; +static bool gInitialized = false; +static MCAN_Regs* gMcan = NULL; +static uint32_t gLastTxCanardId = 0U; +static bool gTxPending = false; + +/* ========================================================================== */ +/* Private: Frame ID Conversion */ +/* ========================================================================== */ + +static void convertFrameIdCanardToMcan(uint32_t canardId, + uint32_t* outId, + uint32_t* outXtd, + uint32_t* outRtr) +{ + if (canardId & CANARD_CAN_FRAME_EFF) + { + *outId = canardId & CANARD_CAN_EXT_ID_MASK; + *outXtd = 1U; + } + else + { + *outId = (canardId & CANARD_CAN_STD_ID_MASK) << 18U; + *outXtd = 0U; + } + + *outRtr = (canardId & CANARD_CAN_FRAME_RTR) ? 1U : 0U; +} + +/* + * convertFrameIdMcanToCanard + * + * Maps TI MCAN RxBufElement fields back to a libcanard frame->id word. + * Restores CANARD_CAN_FRAME_EFF for extended frames and + * CANARD_CAN_FRAME_RTR for remote frames. + */ +static uint32_t convertFrameIdMcanToCanard(uint32_t mcanId, + uint32_t xtd, + uint32_t rtr) +{ + uint32_t canardId = 0U; + + if (xtd) + { + canardId = (mcanId & CANARD_CAN_EXT_ID_MASK) | CANARD_CAN_FRAME_EFF; + } + else + { + canardId = (mcanId >> 18U) & CANARD_CAN_STD_ID_MASK; + } + + if (rtr) + { + canardId |= CANARD_CAN_FRAME_RTR; + } + + return canardId; +} + +/* ========================================================================== */ +/* Private: Error Processing */ +/* ========================================================================== */ + +static void processErrorStatus(void) +{ + if (gMcan == NULL) { return; } + + DL_MCAN_ProtocolStatus ps; + DL_MCAN_getProtocolStatus(gMcan, &ps); + + if (ps.lastErrCode != 0U) + { + gStats.errorCount++; + + if (gAbortTxOnError || ps.busOffStatus) + { + DL_MCAN_txBufCancellationReq(gMcan, MCAN_TX_BUFFER_INDEX); + gTxPending = false; + } + } +} + +/* ========================================================================== */ +/* Public: Initialisation */ +/* ========================================================================== */ + +int16_t canardTI_MCAN_Init(void* mcanInstance, + CanardTI_MCAN_IfaceMode ifaceMode) +{ + if (mcanInstance == NULL) + { + return -CANARD_TI_MCAN_ERROR_INVALID_ARGUMENT; + } + + if ((ifaceMode != CanardTI_MCAN_IfaceModeNormal) && + (ifaceMode != CanardTI_MCAN_IfaceModeSilent) && + (ifaceMode != CanardTI_MCAN_IfaceModeLoopback) && + (ifaceMode != CanardTI_MCAN_IfaceModeAutomaticTxAbortOnError)) + { + return -CANARD_TI_MCAN_ERROR_INVALID_ARGUMENT; + } + + /* Reset all module state */ + memset(&gStats, 0, sizeof(gStats)); + gMcan = (MCAN_Regs*)mcanInstance; + gAbortTxOnError = (ifaceMode == CanardTI_MCAN_IfaceModeAutomaticTxAbortOnError); + gTxPending = false; + gLastTxCanardId = 0U; + gInitialized = false; + + /* + * Wait for MCAN to reach Normal operating mode. + * SysConfig (SYSCFG_DL_init) must have already started the peripheral. + * If this times out, example.syscfg MCAN configuration should be checked. + * Syscfg is set to accept non matching frames in RX FIFO 0 + */ + uint32_t timeout = MCAN_MODE_TIMEOUT_LOOPS; + while (DL_MCAN_OPERATION_MODE_NORMAL != DL_MCAN_getOpMode(gMcan)) + { + if (--timeout == 0U) + { + gMcan = NULL; + return -CANARD_TI_MCAN_ERROR_MODE_NOT_SET; + } + } + + /* + * Release TCAN transceiver from standby by driving STB pin low. + * GPIO_GRP_0_PORT and GPIO_GRP_0_TCAN_STB_PIN are generated by + * example.syscfg. STB is active-low: clearing the pin enables the + * transceiver and connects the node to the CAN bus. + */ + DL_GPIO_clearPins(GPIO_GRP_0_PORT, GPIO_GRP_0_TCAN_STB_PIN); + + /* Allow transceiver to exit standby before verifying pin state */ + DEVICE_DELAY_MS(MCAN_TRANSCEIVER_DELAY_MS); + + /* + * Verify STB pin is actually low. If it reads high, the transceiver + * is not responding — check hardware connections and SysConfig GPIO + * assignment for GPIO_GRP_0_TCAN_STB_PIN. + */ + if (DL_GPIO_readPins(GPIO_GRP_0_PORT, GPIO_GRP_0_TCAN_STB_PIN) != 0U) + { + gMcan = NULL; + return -CANARD_TI_MCAN_ERROR_TRANSCEIVER; + } + + if (ifaceMode == CanardTI_MCAN_IfaceModeLoopback) + { + DL_MCAN_lpbkModeEnable(gMcan, DL_MCAN_LPBK_MODE_INTERNAL, true); + } + else if (ifaceMode == CanardTI_MCAN_IfaceModeSilent) + { + /* External loopback = monitor mode: receives but does not transmit */ + DL_MCAN_lpbkModeEnable(gMcan, DL_MCAN_LPBK_MODE_EXTERNAL, true); + } + + gInitialized = true; + return 0; +} + +/* ========================================================================== */ +/* Public: Acceptance Filters */ +/* ========================================================================== */ + +int16_t canardTI_MCAN_ConfigureAcceptanceFilters( + const CanardTI_MCAN_AcceptanceFilterConfig* filterConfigs, + uint8_t numFilterConfigs) +{ + if (!gInitialized || gMcan == NULL) + { + return -CANARD_TI_MCAN_ERROR_NOT_INIT; + } + + if ((filterConfigs == NULL) && (numFilterConfigs > 0U)) + { + return -CANARD_TI_MCAN_ERROR_INVALID_ARGUMENT; + } + + if (numFilterConfigs > MCAN_MAX_EXT_FILTERS) + { + return -CANARD_TI_MCAN_ERROR_INVALID_ARGUMENT; + } + + /* TI MCAN filters can only be modified in SW_INIT mode */ + DL_MCAN_setOpMode(gMcan, DL_MCAN_OPERATION_MODE_SW_INIT); + + uint32_t timeout = MCAN_MODE_TIMEOUT_LOOPS; + while (DL_MCAN_OPERATION_MODE_SW_INIT != DL_MCAN_getOpMode(gMcan)) + { + if (--timeout == 0U) + { + DL_MCAN_setOpMode(gMcan, DL_MCAN_OPERATION_MODE_NORMAL); + return -CANARD_TI_MCAN_ERROR_MODE_NOT_SET; + } + } + + /* Programme requested filters */ + for (uint8_t i = 0U; i < numFilterConfigs; i++) + { + bool isExt = ((filterConfigs[i].id & CANARD_CAN_FRAME_EFF) != 0U) || + ((filterConfigs[i].mask & CANARD_CAN_FRAME_EFF) != 0U); + + if (isExt) + { + /* Extended frame filter — strip EFF flag, keep 29-bit ID */ + DL_MCAN_ExtMsgIDFilterElement fe; + memset(&fe, 0, sizeof(fe)); + fe.efid1 = filterConfigs[i].id & CANARD_CAN_EXT_ID_MASK; + fe.efid2 = filterConfigs[i].mask & CANARD_CAN_EXT_ID_MASK; + fe.efec = 0x1U; /* Store matching frames in RX FIFO 0 */ + fe.eft = 0x2U; /* Classic bit-mask filter */ + DL_MCAN_addExtMsgIDFilter(gMcan, i, &fe); + } + else + { + /* Standard frame filter — 11-bit ID lives in bits [28:18] */ + DL_MCAN_StdMsgIDFilterElement fe; + memset(&fe, 0, sizeof(fe)); + fe.sfid1 = (filterConfigs[i].id >> 18U) & 0x7FFU; + fe.sfid2 = (filterConfigs[i].mask >> 18U) & 0x7FFU; + fe.sfec = 0x1U; /* Store matching frames in RX FIFO 0 */ + fe.sft = 0x2U; /* Classic bit-mask filter */ + DL_MCAN_addStdMsgIDFilter(gMcan, i, &fe); + } + } + + /* Disable any remaining extended filter slots */ + for (uint8_t i = numFilterConfigs; i < MCAN_MAX_EXT_FILTERS; i++) + { + DL_MCAN_ExtMsgIDFilterElement fe; + memset(&fe, 0, sizeof(fe)); + fe.efec = 0x0U; /* Disabled */ + fe.eft = 0x2U; + DL_MCAN_addExtMsgIDFilter(gMcan, i, &fe); + } + + DL_MCAN_setOpMode(gMcan, DL_MCAN_OPERATION_MODE_NORMAL); + + timeout = MCAN_MODE_TIMEOUT_LOOPS; + while (DL_MCAN_OPERATION_MODE_NORMAL != DL_MCAN_getOpMode(gMcan)) + { + if (--timeout == 0U) + { + return -CANARD_TI_MCAN_ERROR_MODE_NOT_SET; + } + } + + return 0; +} + +/* ========================================================================== */ +/* Public: Transmit */ +/* ========================================================================== */ + +int16_t canardTI_MCAN_Transmit(const CanardCANFrame* frame) +{ + if (frame == NULL) + { + return -CANARD_TI_MCAN_ERROR_INVALID_ARGUMENT; + } + + if (!gInitialized || gMcan == NULL) + { + return -CANARD_TI_MCAN_ERROR_NOT_INIT; + } + + if (frame->id & CANARD_CAN_FRAME_ERR) + { + return -CANARD_TI_MCAN_ERROR_INVALID_ARGUMENT; + } + + processErrorStatus(); + + bool txBufferBusy = + (DL_MCAN_getTxBufReqPend(gMcan) & (1U << MCAN_TX_BUFFER_INDEX)) != 0U; + + if (txBufferBusy) + { + /* + * TX buffer is occupied. Return 0 so the caller retries on the + * next cycle. With a single TX buffer, priority preemption is + * not possible without cancellation support. + */ + return 0; + } + + DL_MCAN_TxBufElement txElem; + memset(&txElem, 0, sizeof(txElem)); + + convertFrameIdCanardToMcan(frame->id, + &txElem.id, + &txElem.xtd, + &txElem.rtr); + + txElem.dlc = frame->data_len; + txElem.fdf = 0U; /* Classic CAN frame — no CAN FD bit rate switch */ + txElem.brs = 0U; /* No bit rate switching */ + txElem.esi = 0U; + txElem.efc = 1U; /* Store TX event in TX event FIFO */ + txElem.mm = 0xAAU; /* Message marker — identifies this driver */ + + memcpy(txElem.data, frame->data, frame->data_len); + + DL_MCAN_writeMsgRam(gMcan, DL_MCAN_MEM_TYPE_BUF, + MCAN_TX_BUFFER_INDEX, &txElem); + + DL_MCAN_TXBufTransIntrEnable(gMcan, MCAN_TX_BUFFER_INDEX, true); + DL_MCAN_TXBufAddReq(gMcan, MCAN_TX_BUFFER_INDEX); + + gLastTxCanardId = frame->id; + gTxPending = true; + + gStats.txCount++; + return 1; +} + +/* ========================================================================== */ +/* Public: TX Ready Query */ +/* ========================================================================== */ + +bool canardTI_MCAN_IsTxReady(void) +{ + if (!gInitialized || gMcan == NULL) + { + return false; + } + + return (DL_MCAN_getTxBufReqPend(gMcan) & + (1U << MCAN_TX_BUFFER_INDEX)) == 0U; +} + +/* ========================================================================== */ +/* Public: TX Completion Check */ +/* ========================================================================== */ + +bool canardTI_MCAN_IsTxComplete(void) +{ + if (!gInitialized || gMcan == NULL) + { + return false; + } + + bool complete = (DL_MCAN_getTxBufTransmissionStatus(gMcan) & + (1U << MCAN_TX_BUFFER_INDEX)) != 0U; + + if (complete) + { + gTxPending = false; + } + + return complete; +} + +/* ========================================================================== */ +/* Public: Receive */ +/* ========================================================================== */ + +int16_t canardTI_MCAN_Receive(CanardCANFrame* outFrame) +{ + if (outFrame == NULL) + { + return -CANARD_TI_MCAN_ERROR_INVALID_ARGUMENT; + } + + if (!gInitialized || gMcan == NULL) + { + return -CANARD_TI_MCAN_ERROR_NOT_INIT; + } + + processErrorStatus(); + + /* Poll RX FIFO 0 fill level */ + DL_MCAN_RxFIFOStatus fifoStatus; + memset(&fifoStatus, 0, sizeof(fifoStatus)); + fifoStatus.num = DL_MCAN_RX_FIFO_NUM_0; + DL_MCAN_getRxFIFOStatus(gMcan, &fifoStatus); + + if (fifoStatus.fillLvl == 0U) + { + return 0; /* FIFO empty */ + } + + if (fifoStatus.fifoFull) + { + gStats.rxOverflowCount++; + } + + DL_MCAN_RxBufElement rxElem; + memset(&rxElem, 0, sizeof(rxElem)); + + DL_MCAN_readMsgRam(gMcan, + DL_MCAN_MEM_TYPE_FIFO, + MCAN_RX_FIFO0_INDEX, + fifoStatus.num, + &rxElem); + + DL_MCAN_writeRxFIFOAck(gMcan, DL_MCAN_RX_FIFO_NUM_0, fifoStatus.getIdx); + + outFrame->id = convertFrameIdMcanToCanard(rxElem.id, + rxElem.xtd, + rxElem.rtr); + outFrame->data_len = (uint8_t)rxElem.dlc; + outFrame->iface_id = 0U; + + memcpy(outFrame->data, rxElem.data, outFrame->data_len); + + gStats.rxCount++; + return 1; +} + +/* ========================================================================== */ +/* Public: RX Available Query */ +/* ========================================================================== */ + +bool canardTI_MCAN_IsRxAvailable(void) +{ + if (!gInitialized || gMcan == NULL) + { + return false; + } + + DL_MCAN_RxFIFOStatus fifoStatus; + memset(&fifoStatus, 0, sizeof(fifoStatus)); + fifoStatus.num = DL_MCAN_RX_FIFO_NUM_0; + DL_MCAN_getRxFIFOStatus(gMcan, &fifoStatus); + + return (fifoStatus.fillLvl > 0U); +} + +/* ========================================================================== */ +/* Public: Statistics */ +/* ========================================================================== */ + +CanardTI_MCAN_Stats canardTI_MCAN_GetStats(void) +{ + return gStats; +} + +void canardTI_MCAN_ResetStats(void) +{ + memset(&gStats, 0, sizeof(gStats)); +} diff --git a/drivers/am13x/canard_am13x.h b/drivers/am13x/canard_am13x.h new file mode 100644 index 0000000..043460d --- /dev/null +++ b/drivers/am13x/canard_am13x.h @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2026 Texas Instruments Incorporated + * + * Distributed under the MIT License, available in the file LICENSE. + * + * + * + * Libcanard interface layer for TI MCAN peripheral. + * WARNING: This driver is NOT thread-safe. It does not use IRQ or + * critical sections. It is safe to call its API functions + * from IRQ context provided the application ensures that + * no concurrent calls occur from thread and IRQ context + * simultaneously. + * + * SysConfig dependency: + * CAN bit timing, message RAM layout, GPIO pin assignments, and + * peripheral clocks must all be configured via the project's + * example.syscfg file before calling canardTI_MCAN_Init(). + * This driver does not configure timing registers at runtime. + * The following SysConfig-generated file must be included by the + * application build: + * ti_sdk_dl_config.h / ti_sdk_dl_config.c + */ + +#ifndef CANARD_AM13X_H +#define CANARD_AM13X_H + +#include +#include +#include "canard.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ========================================================================== */ +/* Error Codes */ +/* ========================================================================== */ + +/* + * Error code values are defined as positive integers and returned negated by all + * API functions. This avoids collision with libcanard error codes which + * are small negative integers (e.g. -CANARD_ERROR_OUT_OF_MEMORY = -2). + * + * Example check: + * if (ret == -CANARD_TI_MCAN_ERROR_INVALID_ARGUMENT) { ... } + */ +#define CANARD_TI_MCAN_ERROR_INVALID_ARGUMENT 1000 +#define CANARD_TI_MCAN_ERROR_NOT_INIT 1001 +#define CANARD_TI_MCAN_ERROR_MODE_NOT_SET 1002 +#define CANARD_TI_MCAN_ERROR_TRANSCEIVER 1003 + +/* ========================================================================== */ +/* Operating Modes */ +/* ========================================================================== */ + +/* + * CanardTI_MCAN_IfaceMode + * IfaceModeNormal: + * Normal bus operation. Use for all production node operation. + * + * IfaceModeSilent: + * Monitor mode. The node receives frames but does not transmit, + * including no ACK bits. Use for automatic bitrate detection or + * passive bus monitoring. + * + * IfaceModeLoopback: + * Internal loopback. TX frames are received locally without + * appearing on the physical bus. Useful for self-test. + * + * IfaceModeAutomaticTxAbortOnError: + * Cancels any pending TX on a bus error event. Required during + * dynamic node ID allocation per the DroneCAN specification. + */ +typedef enum +{ + CanardTI_MCAN_IfaceModeNormal = 0, + CanardTI_MCAN_IfaceModeSilent = 1, + CanardTI_MCAN_IfaceModeLoopback = 2, + CanardTI_MCAN_IfaceModeAutomaticTxAbortOnError = 3 +} CanardTI_MCAN_IfaceMode; + +/* ========================================================================== */ +/* Acceptance Filter Configuration */ +/* ========================================================================== */ + +/* + * CanardTI_MCAN_AcceptanceFilterConfig + * The id and mask fields support the CANARD_CAN_FRAME_EFF flag, + * CANARD_CAN_FRAME_EFF set in id and mask → extended (29-bit) filter + * CANARD_CAN_FRAME_EFF clear → standard (11-bit) filter + * + * The EFF flag is stripped before writing to hardware registers. + * TI MCAN has separate filter RAM for standard and extended frames. + * canardTI_MCAN_ConfigureAcceptanceFilters() routes each entry to the + * correct hardware filter RAM automatically based on the EFF flag. + * + * Examples: + * + * Accept all extended frames (DroneCAN default): + * id = CANARD_CAN_FRAME_EFF + * mask = CANARD_CAN_FRAME_EFF + * + * Accept NodeStatus (DTID 341) from any node at any priority: + * id = CANARD_CAN_FRAME_EFF | 0x00015500U + * mask = CANARD_CAN_FRAME_EFF | 0x00FFFF80U + * + * Accept all standard frames: + * id = 0U + * mask = 0U + */ +typedef struct +{ + uint32_t id; + uint32_t mask; +} CanardTI_MCAN_AcceptanceFilterConfig; + +/* ========================================================================== */ +/* Statistics */ +/* ========================================================================== */ + +typedef struct +{ + uint32_t txCount; + uint32_t rxCount; + uint32_t errorCount; + uint32_t rxOverflowCount; +} CanardTI_MCAN_Stats; + +/* ========================================================================== */ +/* Initialisation */ +/* ========================================================================== */ + +/** + * canardTI_MCAN_Init + * + * Initialises the TI MCAN peripheral for use with libcanard. + * - Takes an mcan_instance pointer. TI devices may have multiple + * MCAN peripherals; pass the one configured in example.syscfg + * (e.g. MCAN0). + * - No timing parameter. Bit timing is configured entirely by + * SysConfig in example.syscfg and applied before this call. + * + * Prerequisites: + * - SYSCFG_DL_init() must have been called before this function. + * - The MCAN peripheral must be in Normal operating mode. + * - The TCAN transceiver STB GPIO pin must be configured in + * example.syscfg as GPIO_GRP_0_TCAN_STB_PIN (active-low standby). + * + * This function can be invoked any number of times; every invocation + * re-initialises everything from scratch. + * + * @param mcanInstance Pointer to MCAN_Regs peripheral. Must not be NULL. + * @param ifaceMode Operating mode. + * @return 0 Success. + * @return negative Negated CANARD_TI_MCAN_ERROR_* code on failure. + */ +int16_t canardTI_MCAN_Init(void* mcanInstance, + CanardTI_MCAN_IfaceMode ifaceMode); + +/* ========================================================================== */ +/* Acceptance Filters */ +/* ========================================================================== */ + +/** + * canardTI_MCAN_ConfigureAcceptanceFilters + * Programmes hardware acceptance filters from the provided list. + * Entries with CANARD_CAN_FRAME_EFF set are routed to extended filter + * RAM. Entries without it are routed to standard filter RAM. + * + * Setting zero filters rejects all frames + * To accept all extended frames pass one filter with: + * id = CANARD_CAN_FRAME_EFF, mask = CANARD_CAN_FRAME_EFF + * + * Note: During filter reconfiguration the peripheral briefly enters + * SW_INIT mode. Frames arriving during this window may be lost. + * + * @param filterConfigs Array of id/mask filter descriptors. + * @param numFilterConfigs Number of entries. Maximum 8. + * @return 0 Success. + * @return negative Negated CANARD_TI_MCAN_ERROR_* code on failure. + */ +int16_t canardTI_MCAN_ConfigureAcceptanceFilters( + const CanardTI_MCAN_AcceptanceFilterConfig* filterConfigs, + uint8_t numFilterConfigs); + +/* ========================================================================== */ +/* Transmit */ +/* ========================================================================== */ + +/** + * canardTI_MCAN_Transmit + * Writes one libcanard frame to the MCAN TX buffer and requests + * transmission. Priority inversion is checked: if a lower-priority + * frame is pending and the new frame has higher priority, the pending + * frame is superseded. + * + * This function never blocks. + * + * @param frame Frame to transmit. Must not be NULL. + * @return 1 Frame accepted and queued for TX. + * @return 0 TX buffer busy. Caller should retry on next cycle. + * @return negative Negated CANARD_TI_MCAN_ERROR_* code on failure. + */ +int16_t canardTI_MCAN_Transmit(const CanardCANFrame* frame); + +/** + * canardTI_MCAN_IsTxReady + * + * Returns true when the TX buffer is free and a new frame can be + * submitted via canardTI_MCAN_Transmit(). + */ +bool canardTI_MCAN_IsTxReady(void); + +/** + * canardTI_MCAN_IsTxComplete + * + * Returns true once the last submitted frame has been successfully + * transmitted on the bus (TXBTO register bit set). + * Call this after canardTI_MCAN_Transmit() returns 1 to confirm + * the frame has left the node. + */ +bool canardTI_MCAN_IsTxComplete(void); + +/* ========================================================================== */ +/* Receive */ +/* ========================================================================== */ + +/** + * canardTI_MCAN_Receive + * Reads one frame from MCAN RX FIFO 0 into out_frame. + * This function never blocks. + * + * @param outFrame Destination frame buffer. Must not be NULL. + * @return 1 Frame read successfully. + * @return 0 No frame available (FIFO empty). + * @return negative Negated CANARD_TI_MCAN_ERROR_* code on failure. + */ +int16_t canardTI_MCAN_Receive(CanardCANFrame* outFrame); + +/** + * canardTI_MCAN_IsRxAvailable + * + * Returns true when at least one frame is waiting in RX FIFO 0. + * Use as a lightweight check before calling canardTI_MCAN_Receive(). + */ +bool canardTI_MCAN_IsRxAvailable(void); + +/* ========================================================================== */ +/* Statistics */ +/* ========================================================================== */ + +CanardTI_MCAN_Stats canardTI_MCAN_GetStats(void); + +void canardTI_MCAN_ResetStats(void); + +#ifdef __cplusplus +} +#endif +#endif /* CANARD_AM13X_H */