Skip to content

sagar-koirala/CANbridge

Repository files navigation

CANbridge

FreeRTOS firmware for an STM32 USB-to-CAN SLCAN bridge with optional cellular telemetry.

Developed for the ATUv1.0 board (STM32L476), but designed with hardware-independent application layers. See Porting & CubeMX Integration.


Features

  • USB CDC Virtual COM Port: Native compatibility with standard SLCAN (Lawicel) tools like python-can, SavvyCAN, and BUSMASTER.
  • Optional Cellular Telemetry: Streams CAN frames over a UDP socket using a SIM7600 modem.
  • Modular Design: Cellular telemetry is completely optional. Set BOARD_HAS_MODEM to 0 in app_config.h to fully compile out all modem drivers, queues, and FreeRTOS tasks to run as a dedicated, lightweight USB-to-CAN bridge.
  • Auto-Baud Detection: Sweeps all 9 supported bitrates (500K → 250K → 125K → 1M → 100K → 50K → 20K → 10K → 800K) in listen-only mode on startup or via custom A command to automatically lock onto active bus traffic.
  • Data Batching & DMA: Uses DMA for non-blocking UART transmissions and batches packets to optimize network usage.
  • Modem Resync & LED Signals: Automatically synchronizes baud rates with the modem on boot to prevent desyncs and provides simple, clear status LED feedback.
  • Fully Static Memory: 100% statically allocated FreeRTOS resources—no dynamic heap usage.

Repository Structure

The firmware's hardware initialization, HAL drivers, and middleware stacks are generated using STM32CubeMX. The App/ directory contains all custom, hardware-independent application code. Other directories (Core/, Drivers/, Middlewares/, USB_DEVICE/) contain the autogenerated CubeMX code templates and should generally not be modified directly (unless you know what you're doing).

CANbridge/
├── ATUv1.0.ioc                 CubeMX project file
├── CMakeLists.txt
├── CMakePresets.json
├── startup_stm32l476xx.s       CubeMX-generated
├── STM32L476XX_FLASH.ld        CubeMX-generated
│
├── App/                        ← Main application directory
│   ├── app_config.h            Compile-time configuration (bitrates, APN/IP parameters, stack sizes)
│   ├── autobaud.c/h            Blocking bitrate scan in listen-only mode
│   ├── can_bridge.c/h          FreeRTOS task creation and orchestration
│   ├── can_driver.c/h          bxCAN HAL wrapper with FreeRTOS queue integration
│   ├── can_timing.c/h          Bit-timing parameter calculator (any APB1 frequency)
│   ├── slcan.c/h               SLCAN protocol parser and encoder
│   ├── modem.c/h               SIM7600 cellular modem driver and high-speed data loop
│   ├── panda_encode.c/h        comma.ai 16-byte packed Panda binary frame encoder
│   ├── usb_serial.c/h          USB CDC ring-buffer isolation layer
│   └── util_ringbuffer.c/h     SPSC byte ring buffer
│
├── Core/                       CubeMX-generated (main.c, FreeRTOSConfig.h, ISR handlers)
├── Drivers/                    CubeMX-generated (STM32L4 HAL + CMSIS)
├── Middlewares/                CubeMX-generated (FreeRTOS, STM32 USB Device Library)
└── USB_DEVICE/                 CubeMX-generated + modified
    └── App/
        └── usbd_cdc_if.c       CDC callbacks hook into usb_serial via USER CODE blocks

usbd_cdc_if.c and main.c are the only CubeMX-generated files with manual additions. The hooks are placed inside USER CODE BEGIN/END blocks and survive CubeMX regeneration.


Architecture

Five statically allocated FreeRTOS tasks and four static communication queues orchestrate data flow:

Task Model

Task Priority Role
CAN_RX 4 Pulls frames from bxCAN driver and forwards them to SLCAN and Modem queues
CAN_TX 4 Drains the Driver TX Queue and feeds them directly into bxCAN hardware mailboxes
SLCAN 3 Handles USB CDC character parsing, command decoding, and frame formatting
MODEM 2 Manages cellular modem power sequencing, auto-baud resync, registration, socket, and batching
LED 1 Manages system status and CAN activity LED feedback patterns

Data Flow Architecture

The data pipelines operate asynchronously across thread-safe static queues to guarantee zero frame loss:

  • Data Transmission (Host ➔ CAN Bus):

    1. USB Host writes SLCAN ASCII commands to the Virtual COM Port.
    2. USB CDC Driver loads raw bytes into the usb_serial RX Ring Buffer.
    3. SLCAN Task processes bytes, parses Lawicel commands, and generates structured CAN frames.
    4. CAN frames are dispatched to the thread-safe Driver TX Queue.
    5. CAN_TX Task drains the queue and populates the bxCAN Hardware Mailboxes as they become available.
    6. bxCAN peripheral transmits the frames onto the physical CAN Bus.
  • Data Reception & Local Bridging (CAN Bus ➔ USB CDC):

    1. Incoming frames on the CAN Bus trigger the bxCAN RX FIFO0 interrupt.
    2. The ISR extracts the frame and pushes it to the thread-safe Driver RX Queue.
    3. CAN_RX Task drains the queue and forwards frames to the SLCAN TX Queue.
    4. SLCAN Task formats the frames into Lawicel-compliant ASCII strings and writes them to the usb_serial TX Ring Buffer.
    5. The USB CDC peripheral transmits data from the ring buffer back to the USB Host.
  • Asymmetric Cellular Telemetry Pipeline (CAN Bus ➔ Remote UDP Server):

    1. Incoming frames on the CAN Bus trigger the CAN_RX task.
    2. The CAN_RX task forwards the frame to the thread-safe Modem TX Queue (net_tx_queue).
    3. MODEM Task drains the queue and serializes CAN frames into packed 16-byte Panda binary frames.
    4. Encodings are batched into tx_batch_buf (416-byte maximum capacity).
    5. If the batch reaches the MODEM_UDP_FLUSH_BYTES limit (400 bytes) OR if a 25ms timeout expires, a preemptive transmission is initiated.
    6. The batch is dispatched via a non-blocking UART4 DMA TX channel to the SIM7600 cellular modem, and the MODEM task yields CPU execution efficiently using direct task notifications.
    7. The SIM7600 modem streams the UDP packet blindly at 921600 baud through the cellular network to the Remote UDP Server.

SLCAN Commands

Standard Lawicel SLCAN Commands

Command Description
Sn Set standard bitrate: S0=10K S1=20K S2=50K S3=100K S4=125K S5=250K S6=500K S7=800K S8=1M
O Open CAN channel in Normal mode
L Open CAN channel in Listen-Only (silent) mode
l Open CAN channel in Loopback mode
C Close CAN channel
tIIILDD... Transmit standard data frame (11-bit ID)
TIIIIIIIILDD... Transmit extended data frame (29-bit ID)
rIIIL Transmit standard Remote Transmission Request (RTR) frame
RIIIIIIIIL Transmit extended Remote Transmission Request (RTR) frame
F Read status / error flags (Warning / Passive / Bus-off state flags)
V Read hardware version
v Read firmware version
N Read hardware serial number
Z0 / Z1 Disable / enable 16-bit millisecond timestamps on received frames

ACK = \r, NACK = \x07. All commands are terminated with \r.

Custom Proprietary Extensions

Command Description
A Trigger an on-demand synchronous passive auto-baud scan (channel must be closed). Scans active traffic across all 9 supported bitrates. Returns An\r (where n is the detected bitrate code 0-8) on success, or NACK (\x07) if no active bus bitrate is found.

Unified Configuration Interface ($CONFIG)

The firmware features a single, unified configuration and control namespace ($CONFIG) utilizing standard space-separated, bash-style CLI arguments. Changes can be updated in memory, read back in a clean shell format, and committed persistently to internal Flash NVS.

Argument Syntax / Values Description
None $CONFIG Reads and prints the current active runtime configuration from memory.
--apn --apn=internet Updates Cellular Context APN string in memory (max 31 chars).
--ip --ip=34.101.176.84 Updates Remote UDP Server IP/Domain string in memory (max 63 chars).
--rport --rport=1338 Updates Remote target UDP port in memory.
--lport --lport=8888 Updates Local modem source UDP port in memory.
--auto --auto=0|1 Configures cellular bridge auto-connect on boot (1 = auto, 0 = off/disabled).
--canscan --canscan=0|1 Configures CAN boot-time auto-scanning (1 = scan, 0 = start with default speed).
--canbaud --canbaud=0..8 Sets default CAN bitrate index (08, matching standard Lawicel index).
--save --save Commits all active memory settings persistently to the STM32 Flash NVS page.
--reset --reset Reverts all settings in memory back to compile-time defaults.

Multiple arguments can be combined and executed in a single command line (e.g. $CONFIG --apn=hologram --auto=0 --save which updates settings in memory, commits them to Flash NVS, and returns $OK\r\n on success).


Status LEDs

The firmware provides highly optimized, event-driven status LED visual telemetry.

  • LED1 (Cellular & Network Status):
    • OFF (Dark): Power-on sequencing, re-probing, or auto-baud synchronization in progress.
    • Fast Blinking (100ms): Active GSM network registration, APN attachment, or UDP tunnel negotiation.
    • Solid ON: Socket connected, transparent high-speed telemetry streaming active.
    • Triple-Pulse Heartbeat (Heartbeat flash + pause): Connection dropped, UDP socket failure, or data mode fault cooldown.
  • LED2 (CAN Status / Activity):
    • OFF (Dark): CAN bus is closed/idle.
    • Fast Blinking (100ms): Auto-baud scan or search in progress.
    • Solid ON: CAN bus is open and active.
    • Flicker (50ms toggle): Pin toggles on active transmit/receive activity, and automatically restores to the persistent state (ON when open, OFF when closed).

Configuration

All software tuning is centralized in app_config.h.

Define Default Description
CAN_SCAN_ENABLED 1 Enable/disable boot-time auto-baud sweep
CAN_SCAN_DWELL_MS 300 Passive listening time (ms) spent per bitrate during scan
DEFAULT_CAN_BITRATE CAN_BITRATE_250K Default bitrate if an O command is received without prior Sn setup
CAN_RX_QUEUE_LEN 512 Driver RX queue depth (frames)
CAN_TX_QUEUE_LEN 512 Driver TX queue depth (frames)
SLCAN_TX_QUEUE_LEN 512 SLCAN TX queue depth (frames)
NET_TX_QUEUE_LEN 64 Cellular telemetry queue depth (frames)
BOARD_HAS_LEDS 1 Enable/disable status LED visual feedback compile-time
DEFAULT_MODEM_APN "internet" Context APN for cellular network registration
DEFAULT_MODEM_REMOTE_IP "35.197.154.247" Target server destination IP address for UDP streaming
DEFAULT_MODEM_REMOTE_PORT 1338 Destination port for UDP streaming
MODEM_AT_BAUDRATE 115200 Baud rate for AT command configuration phase
MODEM_DATA_BAUDRATE 921600 Baud rate for high-speed transparent data streaming
MODEM_UDP_FLUSH_BYTES 400 MTU batching size threshold (bytes) before flushing
HW_VERSION_STR "1010" Hardware version string returned by V command
FW_VERSION_STR "0101" Firmware version string returned by v command

USB ring-buffer sizes are defined in usb_serial.c (USB_RX_BUF_SIZE = 2048 bytes, USB_TX_BUF_SIZE = 8192 bytes).


Dedicated SLCAN Release Tag

If you only require the local USB-to-CAN bridge functionality and do not have a cellular modem on your board, you can check out our dedicated, lightweight release tag:

git checkout slcan-only

Alternatively, you can run the master branch and simply set BOARD_HAS_MODEM to 0 in app_config.h to cleanly compile out the cellular modules.


Porting & CubeMX Integration

The application layer (App/) is hardware-independent. To port this codebase to another STM32 microcontroller or integrate it into a freshly generated CubeMX project, follow this step-by-step guide:

1. STM32CubeMX Configuration Mandates

Configure the hardware peripherals in CubeMX exactly as follows:

  • System Core → SYS: Set the Timebase Source to TIM6 (or any basic timer other than SysTick) to prevent conflicts between the HAL delay utility and FreeRTOS.
  • Connectivity → CAN1 (or bxCAN instance):
    • Set the operating mode to Master (or Normal/Listen-Only/Loopback).
    • Under NVIC Settings, enable the CAN1 TX, CAN1 RX0, and CAN1 SCE (Status Change Error) interrupts. Interrupt priority should be set to 5 or lower to prevent kernel conflicts.
  • Connectivity → USB_OTG_FS:
    • Set Mode to Device_Only.
  • Middleware → USB_DEVICE:
    • Select class for IP: Communication Device Class (Virtual Port Com).
    • In USB_DEVICE/App/usbd_cdc_if.h, set both APP_RX_DATA_SIZE and APP_TX_DATA_SIZE to 2048 to define the underlying transmit/receive packet allocation arrays (UserRxBufferFS and UserTxBufferFS) for the USB CDC middleware.
  • Middleware → FREERTOS:
    • Set Interface to CMSIS_V2 (the codebase utilizes CMSIS-RTOS v2 APIs such as osThreadNew and osPriorityNormal).
    • Under Config parameters, ensure Use Static Allocation is set to Enabled.

2. Copy the Application Code

Copy the entire App/ directory into your new project's root folder.

3. Update CMakeLists.txt

Add the App/ files and include paths to your CMake build configuration:

# Add App sources to the executable target
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    App/usb_serial.c
    App/util_ringbuffer.c
    App/can_driver.c
    App/can_timing.c
    App/autobaud.c
    App/slcan.c
    App/can_bridge.c
    App/nvs.c
    App/app_cmd.c
)

# Add App include directories
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    App
)

4. Hook the USB CDC Driver

Inject the usb_serial callbacks into the CubeMX-generated USB_DEVICE/App/usbd_cdc_if.c file inside the marked USER CODE sections:

  • Under USER CODE BEGIN INCLUDE (line 24-26):
    #include "usb_serial.h"
  • In CDC_Receive_FS() inside USER CODE BEGIN 6:
    /* Hand received data over to the custom RX ring buffer */
    usb_serial_rx_callback(Buf, *Len);
    
    USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
    USBD_CDC_ReceivePacket(&hUsbDeviceFS);
    return (USBD_OK);
  • In CDC_TransmitCplt_FS() inside USER CODE BEGIN 13:
    /* Inform the ring buffer driver that transmission is complete */
    usb_serial_tx_complete_callback();

5. Initialize & Start the Scheduler

Hook the main task orchestration in your Core/Src/main.c file:

  • Under USER CODE Includes:
    #include "can_bridge.h"
  • Inside the main() function under USER CODE BEGIN 2 (before osKernelInitialize()):
    /* Initialize static tasks and queues, then start the scheduler */
    can_bridge_init();
  • Let osKernelStart() or vTaskStartScheduler() take over. The static CAN bridge tasks will execute automatically.

Porting to Other Architectures

  • To another bxCAN STM32 (F1/F2/F4/F7/L1): Simply update the HAL family header in can_driver.c. No timing recalculations are needed as the APB1 clock frequency is read dynamically at runtime via HAL_RCC_GetPCLK1Freq().
  • To an FDCAN STM32 (G0/G4/H7/U5): Re-implement can_driver.c against the FDCAN HAL API and adapt can_timing.c to compute FDCAN register fields. The protocol, ring buffer, and orchestration modules are unchanged.

About

FreeRTOS firmware for an STM32 bxCAN-to-USB adapter implementing the SLCAN (Lawicel) protocol and cellular UDP bridging.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages