Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions andino_base/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ The hardware-software-ros interaction in the `andino` project is developed using
This package:
- Implements `andino`'s [hardware interface](https://control.ros.org/master/doc/ros2_control/hardware_interface/doc/writing_new_hardware_interface.html).
- Provides a communication with microcontroller:
- `andino_base::MotorDriver` class is in charge of the Serial communication for commanding the motors.
- An application is provided for evaluating the communication: Check `applications/motor_driver_demo.cpp`. To use this application simply execute `motor_driver_demo --help` to see the options.
- `andino_base::SerialMcu` class (implementing the `andino_base::Mcu` interface) is in charge of the Serial communication with `andino_firmware`, exposing its full command set (motors, encoders, PID tuning, GPIOs and IMU).
- An application is provided for evaluating the communication: Check `applications/serial_mcu_demo.cpp`. To use this application simply execute `serial_mcu_demo --help` to see the options.
- This communication module is used by the hardware interface implementation.

## Hardware Interface
Expand Down Expand Up @@ -40,12 +40,12 @@ This hardware interface uses the following command interfaces per joint (for lef
- *Velocity*: The velocity received (rad/s) is traduced to microcontroller's velocity nomenclature for the motors.


## Motor Driver Application
## Serial MCU Application

An application for testing the connection with the microcontroller is provided.
After installing this package the application called `motor_driver_demo` can be used.
After installing this package the application called `serial_mcu_demo` can be used.
```
motor_driver_demo --help
serial_mcu_demo --help
```

This application allows verifying the communication with the microcontroller for controlling the motors. Commands for reading the encoders or individually setting a velocity for the motors is some of the possibilities.
Expand Down
10 changes: 5 additions & 5 deletions andino_base/applications/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
add_executable(motor_driver_demo motor_driver_demo.cpp)
add_executable(serial_mcu_demo serial_mcu_demo.cpp)

target_include_directories(motor_driver_demo
target_include_directories(serial_mcu_demo
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)

target_link_libraries(motor_driver_demo
target_link_libraries(serial_mcu_demo
PUBLIC
gflags
motor_driver
serial_mcu
)

install(
TARGETS
motor_driver_demo
serial_mcu_demo
EXPORT ${PROJECT_NAME}-targets
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,39 +32,40 @@

#include <gflags/gflags.h>

#include "andino_base/motor_driver.h"
#include "andino_base/serial_mcu.h"

DEFINE_string(serial_port, "/dev/ttyUSB0", "Serial port");
DEFINE_int32(baud_rate, 57600, "Baud rate");
DEFINE_int32(timeout_ms, 1000, "Timeout in milliseconds for receiving a response from the Microcontroller");

DEFINE_string(msg, "e", "Motor driver message(default read encoders)");
DEFINE_string(msg, "e", "MCU message(default read encoders)");

namespace andino_base {
namespace applications {

// Returns a string with the usage message.
std::string GetUsageMessage() {
std::stringstream ss;
ss << "CLI for easy test of the MotorDriver class" << std::endl << std::endl;
ss << " motor_driver_demo --serial_port=/dev/ttyUSB0 --msg='e' " << std::endl << std::endl;
ss << " motor_driver_demo --msg='o 255 255' " << std::endl << std::endl;
ss << "CLI for easy test of the SerialMcu class" << std::endl << std::endl;
ss << " serial_mcu_demo --serial_port=/dev/ttyUSB0 --msg='e' " << std::endl << std::endl;
ss << " serial_mcu_demo --msg='o 255 255' " << std::endl << std::endl;
return ss.str();
}

int Main(int argc, char* argv[]) {
gflags::SetUsageMessage(GetUsageMessage());
gflags::ParseCommandLineFlags(&argc, &argv, true);

MotorDriver motor_driver;
motor_driver.Setup(FLAGS_serial_port, FLAGS_baud_rate, FLAGS_timeout_ms);
SerialMcu serial_mcu;
serial_mcu.setup(FLAGS_serial_port, FLAGS_baud_rate, FLAGS_timeout_ms);

std::cout << "Motor driver is connected: " << (motor_driver.is_connected() ? "True" : "False") << std::endl;
std::cout << "MCU is connected: " << (serial_mcu.is_connected() ? "True" : "False") << std::endl;

// Send message
std::cout << "Sending message: " << FLAGS_msg << std::endl;
const std::string response = motor_driver.SendMsg(FLAGS_msg);
std::cout << "Response: " << response << std::endl;
// TODO(jballoffet): Change to use public API.
// std::cout << "Sending message: " << FLAGS_msg << std::endl;
// const std::string response = serial_mcu.SendMsg(FLAGS_msg);
// std::cout << "Response: " << response << std::endl;
return 0;
}

Expand Down
6 changes: 3 additions & 3 deletions andino_base/include/andino_base/diffdrive_andino.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
#include <rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp>
#include <rclcpp_lifecycle/state.hpp>

#include "andino_base/motor_driver.h"
#include "andino_base/serial_mcu.h"
#include "andino_base/wheel.h"

namespace andino_base {
Expand Down Expand Up @@ -92,8 +92,8 @@ class DiffDriveAndino : public hardware_interface::SystemInterface {

// Configuration parameters.
Config config_;
// Communication with the firmware in charge of controlling the motors.
MotorDriver motor_driver_;
// Communication with the microcontroller running andino_firmware.
SerialMcu serial_mcu_;
// Left wheel of the robot.
Wheel left_wheel_;
// Right wheel of the robot.
Expand Down
100 changes: 100 additions & 0 deletions andino_base/include/andino_base/mcu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// BSD 3-Clause License
//
// Copyright (c) 2026, Ekumen Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once

#include <array>

namespace andino_base {

/// \brief Interface to the microcontroller running andino_firmware.
/// It exposes the full set of commands supported by andino_firmware.
class Mcu {
public:
/// @brief Holds encoder sensors reading. First value is the left encoder, second is the right one.
using EncodersData = std::array<int, 2>;

/// @brief Holds IMU sensor reading.
struct ImuData {
/// @brief Absolute orientation as a quaternion, in (x, y, z, w) order.
std::array<double, 4> orientation{};
/// @brief Angular velocity [rad/s], in (x, y, z) order.
std::array<double, 3> angular_velocity{};
/// @brief Linear acceleration [m/s^2], in (x, y, z) order.
std::array<double, 3> linear_acceleration{};
};

/// @brief Holds encoders and IMU sensors reading.
struct EncodersAndImuData {
/// @brief The encoder values.
EncodersData encoders_data{};
/// @brief The IMU sensor reading.
ImuData imu_data{};
};

virtual ~Mcu() = default;

/// @brief Checks if the microcontroller is connected.
/// @return True if the microcontroller is connected, false otherwise.
virtual bool is_connected() const = 0;

/// @brief Resets the encoder sensors.
virtual void reset_encoders() = 0;

/// @brief Returns the encoder sensors data.
/// @returns The encoder sensors data.
virtual EncodersData read_encoders() = 0;

/// @brief Checks if there is an IMU sensor available.
/// @returns True if there is an IMU sensor available, false otherwise.
virtual bool is_imu_available() = 0;

/// @brief Returns the encoder sensors and IMU sensor data.
/// @returns The encoder sensors and IMU sensor data.
virtual EncodersAndImuData read_encoders_and_imu() = 0;

/// @brief Sets the motors speed [ticks/s].
/// @param left_motor_speed Speed value for the left motor.
/// @param right_motor_speed Speed value for the right motor.
virtual void set_motors_speed(int left_motor_speed, int right_motor_speed) = 0;

/// @brief Sets the motors PWM [duty range: 0-255].
/// @param left_motor_pwm PWM value for the left motor.
/// @param right_motor_pwm PWM value for the right motor.
virtual void set_motors_pwm(int left_motor_pwm, int right_motor_pwm) = 0;

/// @brief Sets the PID tuning gains.
/// @param kp Proportional gain.
/// @param kd Derivative gain.
/// @param ki Integral gain.
/// @param ko Offset gain.
virtual void set_pid_tuning_gains(float kp, float kd, float ki, float ko) = 0;
};

} // namespace andino_base
Original file line number Diff line number Diff line change
Expand Up @@ -29,65 +29,56 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once

#include <array>
#include <cstdint>
#include <string>

#include <libserial/SerialPort.h>

#include "andino_base/mcu.h"

namespace andino_base {

/// \brief Class to handle serial communication with the motor driver
/// It is used to send commands to the motor driver and read encoder values.
/// The use:
/// 1. Create an instance of the class.
/// 2. Call Setup() to initialize the serial connection.
/// 3. Use api to send commands to the motor driver.
class MotorDriver {
/// \brief This class provides a serial implementation of the MCU interface.
class SerialMcu : public Mcu {
public:
/// @brief Type to store the encoder values.
using Encoders = std::array<int, 2>;

/// @brief Default constructor.
MotorDriver() = default;
SerialMcu() = default;

/// @brief Configures the serial communication.
/// @param[in] serial_device Path to the serial device(eg. /dev/ttyACM0)
/// @param[in] baud_rate Baud rate of the serial connection(eg. 57600)
/// @param[in] timeout_ms Timeout in milliseconds.
void Setup(const std::string& serial_device, int32_t baud_rate, int32_t timeout_ms);

/// @brief Send an empty message to the motor driver. The use of this function is to
/// ensure that the motor driver is ready to receive a new command.
void SendEmptyMsg();

/// @brief Read the encoder values from the motor driver.
/// First value is the left encoder, second value is the right encoder.
/// @returns The encoder values.
Encoders ReadEncoderValues();

/// @brief Set the motor values.
/// The unit of the values is in encoder ticks per revolution.
/// @param val_1 Value for the first motor.
/// @param val_2 Value for the second motor.
void SetMotorValues(int val_1, int val_2);

/// @brief Set the PID values.
/// @param k_p Proportional gain.
/// @param k_d Derivative gain.
/// @param k_i Integral gain.
/// @param k_o Offset gain.
void SetPidValues(float k_p, float k_d, float k_i, float k_o);

/// @brief Check if the serial connection is open.
/// @return True if the serial connection is open, false otherwise.
bool is_connected() const;

/// @brief Send a message to the motor driver and read the response.
/// The message is sent with a carriage return appended to it.
/// @param[in] msg_to_send Message to send to the motor driver.
/// @returns The response from the motor driver.
std::string SendMsg(const std::string& msg_to_send);
void setup(const std::string& serial_device, int32_t baud_rate, int32_t timeout_ms);

/// @brief Implements Mcu interface class API.
bool is_connected() const override;

/// @brief Implements Mcu interface class API.
void reset_encoders() override;

/// @brief Implements Mcu interface class API.
EncodersData read_encoders() override;

/// @brief Implements Mcu interface class API.
bool is_imu_available() override;

/// @brief Implements Mcu interface class API.
EncodersAndImuData read_encoders_and_imu() override;

/// @brief Implements Mcu interface class API.
void set_motors_speed(int left_motor_speed, int right_motor_speed) override;

/// @brief Implements Mcu interface class API.
void set_motors_pwm(int left_motor_pwm, int right_motor_pwm) override;

/// @brief Implements Mcu interface class API.
void set_pid_tuning_gains(float kp, float kd, float ki, float ko) override;

private:
/// @brief Sends a message to the microcontroller and reads the response.
/// @param msg Message to send to the microcontroller.
/// @returns The response from the microcontroller.
std::string send_message(const std::string& msg);

// Underlying serial connection.
LibSerial::SerialPort serial_port_;

Expand Down
12 changes: 6 additions & 6 deletions andino_base/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
add_library(motor_driver SHARED motor_driver.cpp)
add_library(serial_mcu SHARED serial_mcu.cpp)

target_include_directories(motor_driver
target_include_directories(serial_mcu
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)

# Reference: See libserial example project for integration with CMake
target_include_directories(motor_driver PUBLIC ${SERIAL_INCLUDE_DIRS})
target_link_libraries(motor_driver PUBLIC ${SERIAL_LDFLAGS} ${CMAKE_THREAD_LIBS_INIT})
target_include_directories(serial_mcu PUBLIC ${SERIAL_INCLUDE_DIRS})
target_link_libraries(serial_mcu PUBLIC ${SERIAL_LDFLAGS} ${CMAKE_THREAD_LIBS_INIT})


add_library(diffdrive_andino SHARED diffdrive_andino.cpp wheel.cpp)
Expand All @@ -24,10 +24,10 @@ ament_target_dependencies(diffdrive_andino
rclcpp
)

target_link_libraries(diffdrive_andino PUBLIC motor_driver)
target_link_libraries(diffdrive_andino PUBLIC serial_mcu)

install(
TARGETS diffdrive_andino motor_driver
TARGETS diffdrive_andino serial_mcu
EXPORT ${PROJECT_NAME}-targets
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
Expand Down
Loading
Loading