Skip to content

Commit 06809fa

Browse files
authored
✨ Add USB-to-CAN adapter driver (canusb) (#21)
Adds a driver for Lawicel CANUSB-compatible USB-to-CAN adapters. The driver takes a serial port interface (connected to the USB-to-CAN hardware) and provides: - 1x can_transceiver - for sending/receiving CAN messages - 1x v5::can_bus_manager - for managing CAN bus resources This enables libhal applications to communicate over CAN bus through USB-to-CAN adapter hardware.
1 parent ee57745 commit 06809fa

5 files changed

Lines changed: 675 additions & 3 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ libhal_test_and_make_library(
1919
LIBRARY_NAME libhal-expander
2020

2121
SOURCES
22+
src/canusb.cpp
2223
src/pca9685.cpp
2324
src/tla2528.cpp
2425
src/tla2528_adapters.cpp

conanfile.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,21 @@ class libhal_expander_conan(ConanFile):
2626
topics = ("expander", "libhal", "driver")
2727
settings = "compiler", "build_type", "os", "arch"
2828

29-
python_requires = "libhal-bootstrap/[>=4.3.0 <5]"
29+
python_requires = "libhal-bootstrap/[>=4.4.0 <5]"
3030
python_requires_extend = "libhal-bootstrap.library"
3131

3232
def requirements(self):
3333
# Adds libhal and libhal-util as transitive headers, meaning library
3434
# consumers get the libhal and libhal-util headers downstream.
3535
bootstrap = self.python_requires["libhal-bootstrap"]
36-
bootstrap.module.add_library_requirements(self)
36+
bootstrap.module.add_library_requirements(
37+
self,
38+
override_libhal_version="4.12.1",
39+
override_libhal_util_version="5.5.0")
3740

3841
def package_info(self):
3942
self.cpp_info.libs = ["libhal-expander"]
4043
self.cpp_info.set_property("cmake_target_name", "libhal::expander")
44+
45+
def package_id(self):
46+
self.info.python_requires.major_mode()

demos/conanfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616

1717
class demos(ConanFile):
18-
python_requires = "libhal-bootstrap/[>=4.3.0 <5]"
18+
python_requires = "libhal-bootstrap/[>=4.4.0 <5]"
1919
python_requires_extend = "libhal-bootstrap.demo"
2020

2121
def requirements(self):

include/libhal-expander/canusb.hpp

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Copyright 2024 - 2025 Khalil Estell and the libhal contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* @file canusb.hpp
17+
* @brief Driver for Lawicel CANUSB-compatible USB-to-CAN adapters
18+
*
19+
* This file provides a driver implementation for USB-to-CAN adapters that
20+
* use the Lawicel CANUSB protocol. The driver converts a serial port interface
21+
* (connected to the USB-to-CAN hardware) into CAN bus functionality.
22+
*/
23+
24+
#pragma once
25+
26+
#include <libhal/can.hpp>
27+
#include <libhal/circular_buffer.hpp>
28+
#include <libhal/pointers.hpp>
29+
#include <libhal/serial.hpp>
30+
#include <libhal/units.hpp>
31+
#include <memory_resource>
32+
33+
namespace hal::expander {
34+
35+
/**
36+
* @brief Driver for Lawicel CANUSB-compatible USB-to-CAN adapters
37+
*
38+
* The canusb driver enables libhal applications to communicate over CAN bus
39+
* through USB-to-CAN adapter hardware. It takes a serial port interface that
40+
* is physically connected to a CANUSB-compatible device and provides:
41+
*
42+
* - CAN transceiver functionality for sending/receiving CAN messages
43+
* - CAN bus manager for configuring bus parameters and managing resources
44+
*
45+
* The driver implements the Lawicel CANUSB protocol, which uses ASCII commands
46+
* sent over the serial interface to control the CAN adapter.
47+
*
48+
* Example usage:
49+
* @code
50+
* // Create the driver with a serial port connected to USB-to-CAN hardware
51+
* auto canusb_driver = hal::expander::canusb::create(allocator, serial_port);
52+
*
53+
* // Acquire CAN resources
54+
* auto bus_manager = hal::acquire_can_bus_manager(allocator, canusb_driver);
55+
* auto transceiver = hal::acquire_can_transceiver(allocator, canusb_driver,
56+
* 64);
57+
*
58+
* // Configure and use CAN bus
59+
* bus_manager->configure({.baud_rate = 500_kHz});
60+
* // ... use transceiver for CAN communication
61+
* @endcode
62+
*
63+
* @note Only one CAN bus manager and one CAN transceiver can be acquired
64+
* from a single canusb instance at a time.
65+
* @note Even though the protocol is called canusb, it can work over UART and
66+
* RS232.
67+
*/
68+
class canusb : public hal::v5::enable_strong_from_this<canusb>
69+
{
70+
public:
71+
/**
72+
* @brief Factory method to create a new canusb driver instance
73+
*
74+
* Creates and initializes a new CANUSB driver instance that will communicate
75+
* with the USB-to-CAN adapter through the provided serial port.
76+
*
77+
* @param p_allocator Memory allocator for creating the driver instance
78+
* @param p_serial Serial port interface connected to the USB-to-CAN hardware.
79+
* This should be configured with appropriate baud rate
80+
* (typically 115200) and other settings for the specific
81+
* USB-to-CAN adapter being used.
82+
*
83+
* @return Strong pointer to the created canusb driver instance
84+
*
85+
* @throws hal::io_error If communication with the USB-to-CAN adapter fails
86+
* during initialization
87+
* @throws std::bad_alloc If memory allocation fails
88+
*/
89+
static hal::v5::strong_ptr<canusb> create(
90+
std::pmr::polymorphic_allocator<> p_allocator,
91+
hal::v5::strong_ptr<hal::v5::serial> const& p_serial);
92+
93+
/**
94+
* @brief Constructor for canusb driver
95+
*
96+
* @warning This constructor should not be called directly. Use the static
97+
* create() method instead to ensure proper initialization.
98+
*
99+
* @param p_serial Serial port interface connected to the USB-to-CAN hardware
100+
*/
101+
canusb(hal::v5::strong_ptr_only_token,
102+
hal::v5::strong_ptr<hal::v5::serial> const& p_serial);
103+
104+
private:
105+
// Friend declarations for resource acquisition functions
106+
friend hal::v5::strong_ptr<hal::v5::can_bus_manager> acquire_can_bus_manager(
107+
std::pmr::polymorphic_allocator<> p_allocator,
108+
hal::v5::strong_ptr<canusb> const& p_manager);
109+
110+
friend hal::v5::strong_ptr<hal::can_transceiver> acquire_can_transceiver(
111+
std::pmr::polymorphic_allocator<> p_allocator,
112+
hal::v5::strong_ptr<canusb> const& p_manager,
113+
hal::usize p_buffer_size);
114+
115+
// Friend declarations for implementation classes
116+
friend class canusb_bus_manager;
117+
friend class canusb_transceiver;
118+
119+
/// Serial port interface for communicating with the USB-to-CAN adapter
120+
hal::v5::strong_ptr<hal::v5::serial> m_serial;
121+
122+
/// Flag indicating if the CAN bus manager has been acquired
123+
bool m_bus_manager_acquired = false;
124+
125+
/// Flag indicating if the CAN transceiver has been acquired
126+
bool m_transceiver_acquired = false;
127+
128+
/// Flag indicating if the CAN channel is currently open
129+
bool m_is_open = false;
130+
131+
/// Current configured baud rate in Hz (default: 125kHz)
132+
hal::u32 m_current_baud_rate = 125000;
133+
};
134+
135+
/**
136+
* @brief Acquire a CAN bus manager from a canusb driver
137+
*
138+
* Creates a CAN bus manager that can configure the CAN bus parameters and
139+
* manage the overall bus state through the USB-to-CAN adapter.
140+
*
141+
* @param p_allocator Memory allocator for creating the bus manager
142+
* @param p_manager The canusb driver instance to acquire the manager from
143+
*
144+
* @return Strong pointer to the CAN bus manager interface
145+
*
146+
* @throws hal::device_or_resource_busy If a bus manager has already been
147+
* acquired from this canusb instance
148+
* @throws std::bad_alloc If memory allocation fails
149+
*
150+
* @note Only one bus manager can be acquired per canusb instance. The bus
151+
* manager must be destroyed before another can be acquired.
152+
* @note APIs `on_bus_off` and `filter_mode` both do nothing. The default filter
153+
* mode is "accept::all". The bus off event will not be called if the
154+
* device goes into bus off state. Do not use this for serious projects
155+
*. until this notice is removed.
156+
*/
157+
hal::v5::strong_ptr<hal::v5::can_bus_manager> acquire_can_bus_manager(
158+
std::pmr::polymorphic_allocator<> p_allocator,
159+
hal::v5::strong_ptr<canusb> const& p_manager);
160+
161+
/**
162+
* @brief Acquire a CAN transceiver from a canusb driver
163+
*
164+
* Creates a CAN transceiver that can send and receive CAN messages through
165+
* the USB-to-CAN adapter. The transceiver includes internal buffering for
166+
* received messages.
167+
*
168+
* @param p_allocator Memory allocator for creating the transceiver
169+
* @param p_manager The canusb driver instance to acquire the transceiver from
170+
* @param p_buffer_size Size of the internal receive buffer for CAN messages.
171+
* Larger buffers can handle bursts of incoming messages
172+
* better but consume more memory. If passed 0, will
173+
* become 1.
174+
*
175+
* @return Strong pointer to the CAN transceiver interface
176+
*
177+
* @throws hal::device_or_resource_busy If a transceiver has already been
178+
* acquired from this canusb instance
179+
* @throws std::bad_alloc If memory allocation fails
180+
*
181+
* @note Only one transceiver can be acquired per canusb instance. The
182+
* transceiver must be destroyed before another can be acquired.
183+
*/
184+
hal::v5::strong_ptr<hal::can_transceiver> acquire_can_transceiver(
185+
std::pmr::polymorphic_allocator<> p_allocator,
186+
hal::v5::strong_ptr<canusb> const& p_manager,
187+
hal::usize p_buffer_size);
188+
189+
} // namespace hal::expander
190+
191+
namespace hal {
192+
// Import acquisition functions into the hal namespace for convenience
193+
using hal::expander::acquire_can_bus_manager;
194+
using hal::expander::acquire_can_transceiver;
195+
} // namespace hal

0 commit comments

Comments
 (0)