A lightweight, extensible CAN bus protocol library implementing a client-server model over CAN 2.0B (29-bit extended identifiers). Designed for deterministic, low-latency communication on resource-constrained embedded systems with no heap allocation.
can-lite provides a minimal CAN protocol framework with a clear client-server architecture:
- Server — Listens for commands from clients, processes them via category-based dispatch, and sends acknowledgement responses. Each server has a unique 12-bit node ID.
- Client — Initiates all requests and queries. A single client can communicate with multiple servers on the same CAN bus.
The protocol ships with a built-in System category (heartbeat, command acknowledgement, status request). Applications extend it by registering custom category handlers for domain-specific messages.
- Client-Server Architecture: Clear separation of roles — client initiates, server responds
- Multi-Server Support: One client can address multiple servers via node IDs
- Category-Based Dispatch: Extensible message routing via pluggable category handlers (server/client pairs)
- Built-in System Category: Heartbeat, command acknowledgement, status request, and category discovery out of the box
- Sequence Validation: 8-bit sequence counter with per-category opt-in
- Rate Limiting: Configurable message rate enforcement on the server
- Fixed-Point Codec: Saturation-clamped encoding for float-to-integer conversion
- ISO-TP (ISO 15765-2): Optional multi-frame segmentation and reassembly for payloads > 8 bytes
- Zero Heap Allocation: All memory statically allocated at compile time
- Fixed Memory Footprint: Uses
infra::BoundedVector,infra::BoundedDeque,infra::Functionfrom embedded-infra-lib - Deterministic Execution: No dynamic allocation or unbounded loops
- CAN 2.0B Only: 29-bit extended identifiers; 11-bit frames silently discarded
- CMake 3.24 or later
- C++20 compatible compiler
- embedded-infra-lib (fetched automatically when building standalone)
- Clone the repository
git clone --recursive https://github.com/embedded-pro/can-lite.git
cd can-lite- Configure and build
cmake --preset host
cmake --build --preset host-Debug- Run tests
ctest --preset host-DebugAdd can-lite as a subdirectory or FetchContent dependency in your CMake project:
FetchContent_Declare(
can-lite
GIT_REPOSITORY https://github.com/embedded-pro/can-lite.git
GIT_TAG main
)
FetchContent_MakeAvailable(can-lite)
target_link_libraries(your_target PRIVATE can_lite.core can_lite.server can_lite.client)├── can-lite/
│ ├── core/ # Protocol definitions, frame codec, transport
│ │ ├── CanProtocolDefinitions.hpp # Enums, constants, CAN ID layout
│ │ ├── CanFrameCodec.hpp # Fixed-point encoding/decoding
│ │ ├── CanFrameTransport.hpp # Async frame queue and send
│ │ ├── CanCategory.hpp # Base category hierarchy (Server/Client)
│ │ ├── CanMessageType.hpp # Message handler interface
│ │ └── test/ # Core unit tests
│ ├── categories/ # Category implementations (server/client pairs)
│ │ ├── system/ # Built-in System category (heartbeat, ack, discovery)
│ │ ├── firmware_upgrade/ # Firmware Upgrade category (extension example)
│ │ └── foc_motor/ # FOC Motor Control category (extension example)
│ ├── server/ # Server implementation
│ │ ├── CanProtocolServer.hpp # Server with dispatch & observer
│ │ └── test/ # Server unit tests
│ ├── client/ # Client implementation
│ │ ├── CanProtocolClient.hpp # Client with discovery & observer
│ │ └── test/ # Client unit tests
│ ├── transport/ # ISO-TP segmentation layer (ISO 15765-2)
│ │ ├── IsoTpTransport.hpp # Abstract interface
│ │ ├── IsoTpTransportImpl.hpp/cpp # Concrete impl (WithStorage, zero-heap)
│ │ ├── iso-tp/ # ISO 15765-2 internals (all non-template with WithStorage)
│ │ └── test/ # Transport unit tests
│ └── drivers/ # Hardware driver adapters
├── documents/
│ ├── design/ # Architecture & design decisions
│ ├── requirements/ # Protocol requirements (YAML)
│ └── spec/ # Protocol specification (Markdown)
└── CMakeLists.txt
- No Heap Allocation: All memory is statically allocated at compile time
- Bounded Containers: Uses
infra::BoundedVector,infra::BoundedDequeinstead of STL containers - Fixed Memory Footprint: Predictable RAM usage for resource-constrained systems
- Dependency Injection:
hal::Canand category handlers injected via constructor - Observer Pattern: Decouples protocol events from application logic using
infra::Subject/infra::SingleObserver - Interface-Driven Design: Pure virtual interfaces (
CanProtocolServer,CanProtocolClient) enable mocking and testing - Type-Safe Categories: Compile-time separation via
CanCategoryServer/CanCategoryClientprevents cross-registration - Category Extensibility: Register custom category implementations (server/client pairs) for application-specific messages
- Deterministic Execution: No dynamic allocation or unbounded loops in message paths
- Input Validation: All payloads length-checked before parsing
- Rate Limiting: Configurable per-server message rate enforcement
- Sequence Validation: Per-category opt-in replay protection
- Architecture & Design — Architecture decisions and design patterns
- Protocol Specification — Full wire-format specification
- FOC Motor Control Specification — FOC Motor Control category specification
- Firmware Upgrade Specification — Firmware Upgrade category specification
- Protocol Requirements — Formal requirements
- Copilot Instructions — Development guidelines
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Follow the coding standards in copilot-instructions.md
- Ensure all tests pass
- Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- NO heap allocation (
new,delete,malloc,free) - NO standard containers (use
infra::Bounded*alternatives) - Use fixed-size types (
uint8_t,int32_t, etc.) - Mark all non-mutating methods as
const - Write unit tests for new functionality
- Follow
.clang-formatrules for code formatting
This project is licensed under the MIT License - see the LICENSE file for details.