Skip to content

Commit 4152fe3

Browse files
committed
Add basic vesc hardware interface
1 parent 0b98b7c commit 4152fe3

8 files changed

Lines changed: 810 additions & 6 deletions

File tree

.github/workflows/ros2-ci.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ jobs:
2020
matrix:
2121
config:
2222
- {
23-
ros_distro: "humble"
23+
ros_distro: "jazzy"
24+
}
25+
- {
26+
ros_distro: "kilted"
2427
}
2528
runs-on: ubuntu-latest
2629
container:

.pre-commit-config.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ repos:
1717
- id: check-json
1818
exclude: ^\.clang-format$
1919

20-
- repo: https://github.com/pre-commit/mirrors-clang-format
21-
rev: v16.0.3
22-
hooks:
23-
- id: clang-format
24-
2520
- repo: https://github.com/PyCQA/flake8
2621
rev: 6.0.0
2722
hooks:

vesc_hardware/CMakeLists.txt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
cmake_minimum_required(VERSION 3.8)
2+
project(vesc_hardware)
3+
4+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
5+
add_compile_options(-Wall -Wextra -Wpedantic)
6+
endif()
7+
8+
# find dependencies
9+
find_package(ament_cmake REQUIRED)
10+
find_package(rclcpp REQUIRED)
11+
find_package(hardware_interface REQUIRED)
12+
find_package(pluginlib REQUIRED)
13+
find_package(rclcpp_lifecycle REQUIRED)
14+
find_package(vesc_driver REQUIRED)
15+
16+
## COMPILE
17+
add_library(
18+
vesc_hardware
19+
SHARED
20+
src/vesc_hardware.cpp
21+
)
22+
target_include_directories(vesc_hardware PUBLIC
23+
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
24+
$<INSTALL_INTERFACE:include>
25+
)
26+
target_link_libraries(vesc_hardware PUBLIC
27+
hardware_interface::hardware_interface
28+
pluginlib::pluginlib
29+
rclcpp::rclcpp
30+
rclcpp_lifecycle::rclcpp_lifecycle
31+
vesc_driver::vesc_driver
32+
)
33+
34+
# Export hardware plugins
35+
pluginlib_export_plugin_description_file(hardware_interface vesc_hardware.xml)
36+
37+
# INSTALL
38+
install(
39+
DIRECTORY include
40+
DESTINATION include
41+
)
42+
install(TARGETS vesc_hardware
43+
EXPORT export_vesc_hardware
44+
ARCHIVE DESTINATION lib
45+
LIBRARY DESTINATION lib
46+
RUNTIME DESTINATION bin
47+
)
48+
49+
## EXPORTS
50+
ament_export_targets(export_vesc_hardware HAS_LIBRARY_TARGET)
51+
ament_export_dependencies(
52+
hardware_interface
53+
pluginlib
54+
rclcpp
55+
rclcpp_lifecycle
56+
vesc_driver
57+
)
58+
59+
if(BUILD_TESTING)
60+
find_package(ament_lint_auto REQUIRED)
61+
ament_lint_auto_find_test_dependencies()
62+
endif()
63+
64+
ament_package()

vesc_hardware/README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# vesc_hardware
2+
3+
ROS 2 hardware interface component for VESC (Vedder Electronic Speed Controller) motor controllers, compatible with the `ros2_control` framework.
4+
5+
## Overview
6+
7+
This package provides a hardware interface plugin that allows VESC motor controllers to be used seamlessly with `ros2_control`. It handles communication with the VESC, converts between VESC units (degrees, ERPM) and standard ROS units (radians, radians/second), and supports both position and velocity control modes.
8+
9+
## Hardware Parameters
10+
11+
The following parameters must be configured in your robot's URDF/XACRO file within the `<hardware>` tag:
12+
13+
### Required Parameters
14+
15+
| Parameter | Type | Description |
16+
|-----------|------|-------------|
17+
| `device` | string | Serial port device path for VESC communication (e.g., `/dev/ttyACM0`, `/dev/ttyUSB0`) |
18+
19+
### Optional Parameters
20+
21+
| Parameter | Type | Default | Description |
22+
|-----------|------|---------|-------------|
23+
| `gear_ratio` | double | `1.0` | Gear ratio between motor and output shaft. For a reduction gearbox with ratio N:1, set this to N. Used to convert between motor position/velocity and mechanical output. |
24+
| `pole_pairs` | int | `1` | Number of motor pole pairs. Used to convert between ERPM (Electrical RPM) and mechanical RPM. For a motor with P poles, pole_pairs = P/2. |
25+
26+
## Supported Interfaces
27+
28+
The hardware component supports a single joint with the following interfaces:
29+
30+
### State Interfaces
31+
32+
- `position` - Joint position in radians (mechanical output)
33+
- `velocity` - Joint velocity in radians/second (mechanical output)
34+
35+
### Command Interfaces
36+
37+
- `position` - Position command in radians (mechanical output)
38+
- `velocity` - Velocity command in radians/second (mechanical output)
39+
40+
**Note:** At least one state interface and one command interface must be specified in the URDF.
41+
42+
## URDF Configuration Example
43+
44+
### Basic Configuration (Direct Drive Motor)
45+
46+
```xml
47+
<ros2_control name="vesc_system" type="system">
48+
<hardware>
49+
<plugin>vesc_hardware/VescHardware</plugin>
50+
<param name="device">/dev/ttyACM0</param>
51+
</hardware>
52+
53+
<joint name="wheel_joint">
54+
<command_interface name="velocity"/>
55+
<state_interface name="position"/>
56+
<state_interface name="velocity"/>
57+
</joint>
58+
</ros2_control>
59+
```
60+
61+
### Advanced Configuration (Geared Motor)
62+
63+
```xml
64+
<ros2_control name="vesc_system" type="system">
65+
<hardware>
66+
<plugin>vesc_hardware/VescHardware</plugin>
67+
<param name="device">/dev/ttyACM0</param>
68+
<param name="gear_ratio">14.0</param> <!-- 14:1 reduction gearbox -->
69+
<param name="pole_pairs">7</param> <!-- 14-pole motor -->
70+
</hardware>
71+
72+
<joint name="wheel_joint">
73+
<command_interface name="velocity"/>
74+
<state_interface name="position"/>
75+
<state_interface name="velocity"/>
76+
</joint>
77+
</ros2_control>
78+
```
79+
80+
### Position Control Example
81+
82+
```xml
83+
<ros2_control name="vesc_system" type="system">
84+
<hardware>
85+
<plugin>vesc_hardware/VescHardware</plugin>
86+
<param name="device">/dev/ttyACM0</param>
87+
<param name="gear_ratio">50.0</param> <!-- High reduction for position control -->
88+
<param name="pole_pairs">7</param>
89+
</hardware>
90+
91+
<joint name="actuator_joint">
92+
<command_interface name="position"/>
93+
<state_interface name="position"/>
94+
<state_interface name="velocity"/>
95+
</joint>
96+
</ros2_control>
97+
```
98+
99+
## License
100+
101+
Copyright 2024 Ekumen, Inc.
102+
103+
This software is licensed under the BSD 3-Clause License. See the LICENSE file for details.
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright 2024 Ekumen, Inc.
2+
//
3+
// Redistribution and use in source and binary forms, with or without
4+
// modification, are permitted provided that the following conditions are met:
5+
//
6+
// * Redistributions of source code must retain the above copyright
7+
// notice, this list of conditions and the following disclaimer.
8+
//
9+
// * Redistributions in binary form must reproduce the above copyright
10+
// notice, this list of conditions and the following disclaimer in the
11+
// documentation and/or other materials provided with the distribution.
12+
//
13+
// * Neither the name of the Ekumen, Inc. nor the names of its
14+
// contributors may be used to endorse or promote products derived from
15+
// this software without specific prior written permission.
16+
//
17+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
// POSSIBILITY OF SUCH DAMAGE.
28+
29+
#ifndef VESC_HARDWARE__VESC_HARDWARE_HPP_
30+
#define VESC_HARDWARE__VESC_HARDWARE_HPP_
31+
32+
#include <atomic>
33+
#include <memory>
34+
#include <string>
35+
#include <vector>
36+
37+
#include "hardware_interface/handle.hpp"
38+
#include "hardware_interface/hardware_info.hpp"
39+
#include "hardware_interface/system_interface.hpp"
40+
#include "hardware_interface/types/hardware_interface_return_values.hpp"
41+
#include "rclcpp/macros.hpp"
42+
#include "rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp"
43+
#include "rclcpp_lifecycle/state.hpp"
44+
#include "vesc_driver/vesc_interface.hpp"
45+
46+
namespace vesc_hardware
47+
{
48+
class VescHardware : public hardware_interface::SystemInterface {
49+
public:
50+
RCLCPP_SHARED_PTR_DEFINITIONS(VescHardware)
51+
52+
/**
53+
* \brief Initialization of the hardware interface from data parsed from the
54+
* robot's URDF. \param params Hardware component interface parameters
55+
* containing executor and HardwareInfo. \return CallbackReturn::SUCCESS if
56+
* initialization was successful, CallbackReturn::ERROR otherwise.
57+
*
58+
* This method is called once during the initialization phase. It should:
59+
* - Initialize all member variables
60+
* - Process parameters from the HardwareInfo structure
61+
* - Validate that all required parameters are present and valid
62+
*/
63+
hardware_interface::CallbackReturn
64+
on_init(const hardware_interface::HardwareComponentInterfaceParams & params)
65+
override;
66+
67+
/**
68+
* \brief Configuration of the hardware interface.
69+
* \param previous_state The previous lifecycle state.
70+
* \return CallbackReturn::SUCCESS if configuration was successful,
71+
* CallbackReturn::ERROR otherwise.
72+
*
73+
* This method is called when the hardware component is configured. It should:
74+
* - Setup communication to the hardware
75+
* - Prepare everything so that the hardware can be activated
76+
* - Allocate resources needed for communication
77+
*/
78+
hardware_interface::CallbackReturn
79+
on_configure(const rclcpp_lifecycle::State & previous_state) override;
80+
81+
/**
82+
* \brief Activation of the hardware interface.
83+
* \param previous_state The previous lifecycle state.
84+
* \return CallbackReturn::SUCCESS if activation was successful,
85+
* CallbackReturn::ERROR otherwise.
86+
*
87+
* This method is called when the hardware component is activated. It should:
88+
* - Enable hardware "power"
89+
* - Start any background processes needed for operation
90+
* - Prepare the hardware to accept commands
91+
*/
92+
hardware_interface::CallbackReturn
93+
on_activate(const rclcpp_lifecycle::State & previous_state) override;
94+
95+
/**
96+
* \brief Deactivation of the hardware interface.
97+
* \param previous_state The previous lifecycle state.
98+
* \return CallbackReturn::SUCCESS if deactivation was successful,
99+
* CallbackReturn::ERROR otherwise.
100+
*
101+
* This method is called when the hardware component is deactivated. It
102+
* should:
103+
* - Disable hardware "power"
104+
* - Stop any background processes
105+
* - Put hardware in a safe state
106+
*/
107+
hardware_interface::CallbackReturn
108+
on_deactivate(const rclcpp_lifecycle::State & previous_state) override;
109+
110+
/**
111+
* \brief Read the current state from the hardware.
112+
* \param time Current time.
113+
* \param period Time elapsed since the last read.
114+
* \return return_type::OK if the read was successful, return_type::ERROR
115+
* otherwise.
116+
*
117+
* This method is called periodically to get the states from the hardware and
118+
* store them to internal variables that were defined in
119+
* export_state_interfaces. This method must be real-time safe.
120+
*/
121+
hardware_interface::return_type read(
122+
const rclcpp::Time & time,
123+
const rclcpp::Duration & period) override;
124+
125+
/**
126+
* \brief Write commands to the hardware.
127+
* \param time Current time.
128+
* \param period Time elapsed since the last write.
129+
* \return return_type::OK if the write was successful, return_type::ERROR
130+
* otherwise.
131+
*
132+
* This method is called periodically to command the hardware based on the
133+
* values stored in internal variables that were defined in
134+
* export_command_interfaces. This method must be real-time safe.
135+
*/
136+
hardware_interface::return_type
137+
write(const rclcpp::Time & time, const rclcpp::Duration & period) override;
138+
139+
private:
140+
// VESC callback handlers
141+
void vescPacketCallback(const vesc_driver::VescPacketConstPtr & packet);
142+
void vescErrorCallback(const std::string & error);
143+
144+
// VESC packet processing
145+
void processValuesPacket(const vesc_driver::VescPacketValues *values_packet);
146+
147+
// Conversion functions for VESC values to mechanical values (for reading
148+
// state)
149+
double convertDegToMechanicalRad(double vesc_position_deg) const;
150+
double convertERPMtoMechanicalRadSec(double vesc_erpm) const;
151+
152+
// Conversion functions for mechanical values to VESC values (for writing
153+
// commands)
154+
double convertMechanicalRadToDeg(double mechanical_position_rad) const;
155+
double convertMechanicalRadSecToERPM(double mechanical_velocity_rad_s) const;
156+
157+
// Interface tracking structure
158+
struct InterfaceInfo
159+
{
160+
std::string name; // Interface name (position or velocity)
161+
std::string type; // Interface type string
162+
bool requested; // Whether this interface was requested in URDF
163+
};
164+
165+
// Hardware parameters
166+
std::string device_;
167+
double gear_ratio_; // Gear ratio between motor and output (default: 1.0)
168+
int pole_pairs_; // Motor pole pairs (default: 1)
169+
170+
// VESC interface
171+
std::unique_ptr<vesc_driver::VescInterface> vesc_interface_;
172+
173+
// Interface availability tracking
174+
std::vector<InterfaceInfo> state_interfaces_;
175+
std::vector<InterfaceInfo> command_interfaces_;
176+
177+
// State storage (atomic for thread-safe access from callback)
178+
std::atomic<double> hw_state_position_;
179+
std::atomic<double> hw_state_velocity_;
180+
181+
// Command storage
182+
double hw_command_position_;
183+
double hw_command_velocity_;
184+
};
185+
186+
} // namespace vesc_hardware
187+
188+
#endif // VESC_HARDWARE__VESC_HARDWARE_HPP_

0 commit comments

Comments
 (0)