Skip to content

Commit cfa05ed

Browse files
forrisdahljorgenfj
andauthored
feat: thruster interface use uart instead of i2c (#715)
* feat: thruster interface use uart instead of i2c Adds publishing for powergood, current measurements and option to control camera lights * fix: run pre-commit * fix: double include * fix: alphabetic include order * include utility before asio pre-commit bypass * add asio to ci_install_deps script --------- Co-authored-by: Jorgen Fjermedal <jorgen.fjermedal@hotmail.com>
1 parent 3a11679 commit cfa05ed

6 files changed

Lines changed: 776 additions & 211 deletions

File tree

motion/thruster_interface_auv/config/thruster_interface_auv_config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
# LEFT: [2.55792, 27.51178, 147.67001, 1472.23069]
2121
# RIGHT: [1.33421, -18.75129, 121.29079, 1528.99886]
2222

23-
i2c:
24-
bus: 7
25-
address: 0x21
23+
uart:
24+
device: "/dev/serial0"
25+
baud_rate: 115200
2626

2727
debug:
28-
flag: False
28+
flag: True
Lines changed: 149 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,48 @@
11
#ifndef THRUSTER_INTERFACE_AUV__THRUSTER_INTERFACE_AUV_DRIVER_HPP_
22
#define THRUSTER_INTERFACE_AUV__THRUSTER_INTERFACE_AUV_DRIVER_HPP_
33

4-
#include <fcntl.h>
5-
#include <linux/i2c-dev.h>
6-
#include <sys/ioctl.h>
7-
#include <unistd.h>
8-
#include <algorithm>
94
#include <array>
10-
#include <cmath>
5+
// Need to include utility before asio
6+
// clang-format off
7+
#include <utility>
8+
#include <asio.hpp>
9+
// clang-format on
1110
#include <cstdint>
12-
#include <cstring>
13-
#include <iostream>
14-
#include <map>
11+
#include <optional>
1512
#include <string>
13+
#include <thread>
1614
#include <vector>
1715

1816
/**
1917
* @brief struct to hold the parameters for a single thruster
2018
*/
2119
struct ThrusterParameters {
22-
uint8_t mapping;
23-
int8_t direction;
24-
uint16_t pwm_min;
25-
uint16_t pwm_max;
20+
std::uint8_t mapping;
21+
std::int8_t direction;
22+
std::uint16_t pwm_min;
23+
std::uint16_t pwm_max;
2624
};
2725

2826
enum PolySide {
2927
LEFT = 0,
3028
RIGHT = 1
3129
}; // vector index for the position of the coefficients in the coeff vector
3230

31+
using FaultEventCallback =
32+
std::function<void(std::uint8_t channel, std::uint8_t code)>;
33+
34+
using PGoodEventCallback =
35+
std::function<void(std::uint8_t channel, std::uint8_t code)>;
36+
37+
using KillswitchEventCallback = std::function<void()>;
38+
39+
using CurrentMeasurementsCallback =
40+
std::function<void(const std::array<float, 8>& currents)>;
41+
3342
/**
3443
* @brief class instantiated by ThrusterInterfaceAUVNode to control the
3544
* thrusters, takes the thruster forces and converts them to PWM signals to be
36-
* sent via I2C to the ESCs (PCA9685 Adafruit 16-Channel 12-bit PWM/Servo
37-
* Driver)
45+
* sent via UART to the ESC controller.
3846
*
3947
* @details Based on the datasheets found in /resources, approximate the map
4048
* with a piecewise (>0 and <0) third order polynomial.
@@ -44,6 +52,9 @@ enum PolySide {
4452
* all the handling of the other voltages to save resources. Could be
4553
* re-implemented in the future for more flexibility if we ever need it to
4654
* operate at different voltages in different situations.
55+
*
56+
* @note Over UART, the PWM values are packed into a framed packet:
57+
* [magic][id][length][payload][checksum], where the payload is 8 uint16_t.
4758
*/
4859
class ThrusterInterfaceAUVDriver {
4960
public:
@@ -53,41 +64,57 @@ class ThrusterInterfaceAUVDriver {
5364
* @brief called from ThrusterInterfaceAUVNode .cpp when instantiating the
5465
* object, initializes all the params.
5566
*
56-
* @param i2c_bus bus number used to communicate
57-
* @param pico_i2c_address i2c address of the ESC that drive the
67+
* @param serial_device serial device used to communicate
68+
* (for example /dev/ttyUSB0)
69+
* @param baud_rate UART baud rate
70+
* @param packet_id packet ID sent in the UART frame
5871
* @param thruster_parameters describe mapping, direction, min and max pwm
59-
* value for each thruster
60-
* @param poly_coeffs LEFT(<0) and RIGHT(>0) third order
61-
* polynomial coefficients
72+
* value for each thruster
73+
* @param right_coeffs RIGHT(>0) third order polynomial
74+
* coefficients
75+
* @param left_coeffs LEFT(<0) third order polynomial coefficients
6276
*/
6377
ThrusterInterfaceAUVDriver(
64-
std::int16_t i2c_bus,
65-
int pico_i2c_address,
78+
const std::string& serial_device,
79+
unsigned int baud_rate,
6680
const std::vector<ThrusterParameters>& thruster_parameters,
67-
const std::vector<std::vector<double>>& poly_coeffs);
81+
const std::vector<double>& right_coeffs,
82+
const std::vector<double>& left_coeffs);
83+
84+
/**
85+
* @brief initializes UART
86+
* @return 0 on success, negative number on failure
87+
*/
88+
int init_uart();
89+
6890
/**
6991
* @brief calls both 1) interpolate_forces_to_pwm() to
7092
* convert the thruster forces to PWM values and 2) send_data_to_escs() to
71-
* send them to the ESCs via I2C
93+
* send them over UART
7294
*
7395
* @param thruster_forces_array vector of forces for each thruster
7496
*
75-
* @return std::vector<uint16_t> vector of pwm values sent to each thruster
97+
* @return std::optional<std::vector<uint16_t>> vector of pwm values sent to
98+
* each thruster, or std::nullopt on failure
7699
*/
77-
std::vector<uint16_t> drive_thrusters(
100+
std::optional<std::vector<std::uint16_t>> drive_thrusters(
78101
const std::vector<double>& thruster_forces_array);
79102

80-
private:
81-
int bus_fd_; ///< file descriptor for the I2C bus (integer >0 that uniquely
82-
///< identifies the device. -1 if it fails)
83-
84-
int i2c_bus_;
85-
int pico_i2c_address_;
86-
std::vector<ThrusterParameters> thruster_parameters_;
87-
std::vector<std::vector<double>> poly_coeffs_;
103+
/**
104+
* @brief Sets the camera light intensity
105+
*
106+
* @param[in] percentage float in the range 0-1
107+
* @return 0 on success -1 on failure
108+
*/
109+
int set_camera_light(float percentage);
88110

89-
uint16_t idle_pwm_value_; ///< pwm value when force = 0.00
111+
void set_fault_event_callback(FaultEventCallback callback);
112+
void set_pgood_event_callback(PGoodEventCallback callback);
113+
void set_killswitch_event_callback(KillswitchEventCallback callback);
114+
void set_current_measurements_callback(
115+
CurrentMeasurementsCallback callback);
90116

117+
private:
91118
/**
92119
* @brief only take the thruster forces and return PWM values
93120
*
@@ -96,24 +123,22 @@ class ThrusterInterfaceAUVDriver {
96123
* @return std::vector<uint16_t> vector of pwm values sent to each thruster
97124
* if we want to publish them for debug purposes
98125
*/
99-
std::vector<uint16_t> interpolate_forces_to_pwm(
126+
std::vector<std::uint16_t> interpolate_forces_to_pwm(
100127
const std::vector<double>& thruster_forces_array);
101128

102129
/**
103130
* @brief scalar map from force to pwm x->y. Choose coefficients [LEFT] or
104131
* [RIGHT] based on sign(force)
105132
*
106-
* @param force scalar force value
107-
* @param coeffs std::vector<std::vector<double>> coeffs contains the pair
108-
* of coefficients
133+
* @param force scalar force value
109134
*
110135
* @return std::uint16_t scalar pwm value
111136
*/
112-
std::uint16_t force_to_pwm(double force,
113-
const std::vector<std::vector<double>>& coeffs);
137+
std::uint16_t force_to_pwm(double force);
114138

115139
/**
116140
* @brief compute y = a*x^3 + b*x^2 + c*x + d
141+
*
117142
* @param force x
118143
* @param coeffs a,b,c,d
119144
*
@@ -123,11 +148,25 @@ class ThrusterInterfaceAUVDriver {
123148

124149
/**
125150
* @brief only takes the pwm values computed and sends them
126-
* to the ESCs via I2C
151+
* over UART as a framed packet
127152
*
128153
* @param thruster_pwm_array vector of pwm values to send
154+
* @return 0 on success, -1 on failure
155+
*/
156+
int send_data_to_escs(const std::vector<std::uint16_t>& thruster_pwm_array);
157+
158+
/**
159+
* @brief create UART packet with format:
160+
* [magic][id][length][payload][checksum]
161+
*
162+
* @param id packet ID
163+
* @param thruster_pwm_array vector of 8 pwm values to send as payload
164+
*
165+
* @return std::vector<uint8_t> serialized packet bytes
129166
*/
130-
void send_data_to_escs(const std::vector<uint16_t>& thruster_pwm_array);
167+
std::vector<std::uint8_t> create_packet(
168+
std::uint8_t id,
169+
const std::vector<std::uint16_t>& thruster_pwm_array) const;
131170

132171
/**
133172
* @brief convert Newtons to Kg
@@ -139,17 +178,76 @@ class ThrusterInterfaceAUVDriver {
139178
static constexpr double to_kg(double force) { return force / 9.80665; }
140179

141180
/**
142-
* @brief convert pwm values to i2c bytes
181+
* @brief start the asynchronous UART receive loop
182+
*/
183+
void start_receive();
184+
185+
/**
186+
* @brief issue one asynchronous read on the UART port
187+
*/
188+
void do_receive();
189+
190+
/**
191+
* @brief process the accumulated receive buffer and extract valid frames
192+
*/
193+
void process_receive_buffer();
194+
195+
/**
196+
* @brief handle one decoded UART frame
197+
*
198+
* @param frame_bytes complete frame bytes including header and checksum
199+
*/
200+
void handle_received_frame(const std::vector<std::uint8_t>& frame_bytes);
201+
202+
/**
203+
* @brief compute checksum for framed UART packets
143204
*
144-
* @param pwm pwm value
205+
* @param msg_id message id
206+
* @param length payload length
207+
* @param payload pointer to payload bytes
145208
*
146-
* @return std::array<std::uint8_t, 2> i2c data
209+
* @return checksum byte
147210
*/
148-
static constexpr std::array<std::uint8_t, 2> pwm_to_i2c_data(
149-
std::uint16_t pwm) {
150-
return {static_cast<std::uint8_t>((pwm >> 8) & 0xFF),
151-
static_cast<std::uint8_t>(pwm & 0xFF)};
152-
}
211+
static std::uint8_t compute_checksum(std::uint8_t msg_id,
212+
std::uint8_t length,
213+
const std::uint8_t* payload);
214+
215+
private:
216+
static constexpr std::uint8_t UART_START_BYTE = 0xAA;
217+
static constexpr std::size_t MAX_PAYLOAD_SIZE = 64;
218+
static constexpr std::size_t READ_CHUNK_SIZE = 256;
219+
220+
// Outgoing
221+
static constexpr std::uint8_t MSG_TURN_THRUSTERS_OFF = 0x01U;
222+
static constexpr std::uint8_t MSG_TURN_LIGHTS_OFF = 0x02U;
223+
static constexpr std::uint8_t MSG_RESET = 0x03;
224+
static constexpr std::uint8_t MSG_SET_THRUSTER_PWM = 0x04;
225+
static constexpr std::uint8_t MSG_SET_LIGHT_PWM = 0x05;
226+
// Incoming
227+
static constexpr std::uint8_t MSG_FLT_EVENT = 0x10;
228+
static constexpr std::uint8_t MSG_PGOOD_EVENT = 0x11;
229+
static constexpr std::uint8_t MSG_KILLSWITCH_EVENT = 0x12;
230+
static constexpr std::uint8_t MSG_CURRENT_MEASUREMENTS = 0x13;
231+
232+
std::string serial_device_;
233+
unsigned int baud_rate_;
234+
235+
asio::io_context io_;
236+
asio::serial_port serial_{io_};
237+
std::thread io_thread_;
238+
239+
std::vector<ThrusterParameters> thruster_parameters_;
240+
std::vector<double> right_coeffs_;
241+
std::vector<double> left_coeffs_;
242+
std::uint16_t idle_pwm_value_{1500};
243+
244+
std::array<std::uint8_t, READ_CHUNK_SIZE> read_buf_{};
245+
std::vector<std::uint8_t> receive_buffer_;
246+
247+
FaultEventCallback fault_event_callback_;
248+
PGoodEventCallback pgood_event_callback_;
249+
KillswitchEventCallback killswitch_event_callback_;
250+
CurrentMeasurementsCallback current_measurements_callback_;
153251
};
154252

155253
#endif // THRUSTER_INTERFACE_AUV__THRUSTER_INTERFACE_AUV_DRIVER_HPP_

0 commit comments

Comments
 (0)