A.R.G.U.S 2.1.0
Adaptive Real-Time Guardian for Unsafe Situations
Loading...
Searching...
No Matches
MotionController.cpp
Go to the documentation of this file.
1
7#include "CppTimerStdFuncCallback.h"
8
9#include <array>
10#include <cmath>
11#include <condition_variable>
12#include <cstdint>
13#include <fcntl.h>
14#include <mutex>
15#include <sys/ioctl.h>
16#include <unistd.h>
17
18namespace {
19constexpr unsigned long kI2cSlaveRequest = 0x0703;
20
21constexpr std::uint8_t kMode1Register = 0x00;
22constexpr std::uint8_t kMode2Register = 0x01;
23constexpr std::uint8_t kLed0OnLowRegister = 0x06;
24constexpr std::uint8_t kAllLedOnLowRegister = 0xFA;
25constexpr std::uint8_t kPrescaleRegister = 0xFE;
26
27constexpr std::uint8_t kMode1SleepBit = 0x10;
28constexpr std::uint8_t kMode1AutoIncrementBit = 0x20;
29constexpr std::uint8_t kMode2TotemPoleBit = 0x04;
30constexpr std::uint8_t kFullOffBit = 0x10;
31
32constexpr float kPca9685OscillatorHz = 25000000.0f;
33constexpr std::uint8_t kMinimumPrescale = 3;
34constexpr std::uint8_t kMaximumPrescale = 255;
35constexpr useconds_t kWakeDelayUs = 5000;
36
37bool waitForWakeDelay(useconds_t delay_us) noexcept {
38 if (delay_us == 0U) {
39 return true;
40 }
41
42 std::mutex mutex;
43 std::condition_variable condition;
44 bool fired = false;
45
46 CppTimerCallback timer;
47 timer.registerEventCallback([&]() {
48 std::lock_guard<std::mutex> lock(mutex);
49 fired = true;
50 condition.notify_one();
51 });
52
53 const std::uint64_t delay_ns =
54 static_cast<std::uint64_t>(delay_us) * 1000ULL;
55 try {
56 timer.startns(static_cast<long>(delay_ns), ONESHOT);
57 } catch (...) {
58 return false;
59 }
60
61 std::unique_lock<std::mutex> lock(mutex);
62 condition.wait(lock, [&]() { return fired; });
63 timer.stop();
64 return true;
65}
66} // namespace
67
69 : i2c_fd_(-1),
70 channel_map_(),
71 cached_targets_(),
72 output_state_(MotionOutputState::UNINITIALISED),
73 last_error_(MotionControllerError::NONE),
74 frozen_(true) {}
75
79
80bool MotionController::initialise(const char* i2c_device_path,
81 std::uint8_t device_address,
82 float pwm_frequency_hz,
83 MotionChannelMap channel_map) noexcept {
84 shutdown();
85
86 if (i2c_device_path == nullptr || !hasValidChannelMap(channel_map) ||
87 pwm_frequency_hz <= 0.0f) {
88 output_state_ = MotionOutputState::FAULT;
90 frozen_ = true;
91 return false;
92 }
93
94 i2c_fd_ = ::open(i2c_device_path, O_RDWR | O_CLOEXEC);
95 if (i2c_fd_ < 0) {
96 output_state_ = MotionOutputState::FAULT;
98 frozen_ = true;
99 return false;
100 }
101
102 if (::ioctl(i2c_fd_,
103 kI2cSlaveRequest,
104 static_cast<unsigned long>(device_address)) < 0) {
106 ::close(i2c_fd_);
107 i2c_fd_ = -1;
108 return false;
109 }
110
111 channel_map_ = channel_map;
112
113 if (!configurePca9685(pwm_frequency_hz)) {
114 if (last_error_ == MotionControllerError::INVALID_ARGUMENT) {
116 } else {
118 }
119 ::close(i2c_fd_);
120 i2c_fd_ = -1;
121 return false;
122 }
123
124 if (!setAllOutputsEnabled(false)) {
126 ::close(i2c_fd_);
127 i2c_fd_ = -1;
128 return false;
129 }
130
131 cached_targets_ = {};
132 output_state_ = MotionOutputState::DISABLED;
133 last_error_ = MotionControllerError::NONE;
134 frozen_ = true;
135 return true;
136}
137
138bool MotionController::setTargets(const MeArmJointTargets& targets) noexcept {
139 if (output_state_ == MotionOutputState::UNINITIALISED) {
141 return false;
142 }
143
144 if (output_state_ == MotionOutputState::FAULT) {
145 return false;
146 }
147
148 if (targets.base_ticks > kMaxPulseTicks ||
149 targets.lower_ticks > kMaxPulseTicks ||
150 targets.upper_ticks > kMaxPulseTicks ||
151 targets.gripper_ticks > kMaxPulseTicks) {
153 return false;
154 }
155
156 cached_targets_ = targets;
157
158 if (frozen_ || output_state_ != MotionOutputState::ENABLED) {
159 last_error_ = MotionControllerError::NONE;
160 return true;
161 }
162
163 if (!writeTargetsToHardware(cached_targets_)) {
165 return false;
166 }
167
168 last_error_ = MotionControllerError::NONE;
169 return true;
170}
171
173 frozen_ = true;
174
175 if (i2c_fd_ < 0) {
176 return;
177 }
178
179 if (!setAllOutputsEnabled(false)) {
181 return;
182 }
183
184 if (output_state_ != MotionOutputState::FAULT) {
185 output_state_ = MotionOutputState::DISABLED;
186 last_error_ = MotionControllerError::NONE;
187 }
188}
189
191 if (output_state_ == MotionOutputState::UNINITIALISED) {
193 return false;
194 }
195
196 if (output_state_ == MotionOutputState::FAULT) {
197 return false;
198 }
199
200 if (!setAllOutputsEnabled(true) || !writeTargetsToHardware(cached_targets_)) {
202 return false;
203 }
204
205 frozen_ = false;
206 output_state_ = MotionOutputState::ENABLED;
207 last_error_ = MotionControllerError::NONE;
208 return true;
209}
210
212 if (i2c_fd_ >= 0) {
213 (void)setAllOutputsEnabled(false);
214 ::close(i2c_fd_);
215 i2c_fd_ = -1;
216 }
217
218 cached_targets_ = {};
219 output_state_ = MotionOutputState::UNINITIALISED;
220 last_error_ = MotionControllerError::NONE;
221 frozen_ = true;
222}
223
224bool MotionController::isInitialised() const noexcept {
225 return output_state_ != MotionOutputState::UNINITIALISED &&
226 output_state_ != MotionOutputState::FAULT;
227}
228
229bool MotionController::isEnabled() const noexcept {
230 return output_state_ == MotionOutputState::ENABLED;
231}
232
233bool MotionController::isFrozen() const noexcept {
234 return frozen_;
235}
236
238 return output_state_;
239}
240
242 return last_error_;
243}
244
245const char* MotionController::lastErrorString() const noexcept {
246 switch (last_error_) {
248 return "no error";
250 return "invalid argument";
252 return "controller not initialised";
254 return "failed to open I2C device";
256 return "failed to select I2C slave address";
258 return "PCA9685 I/O failed";
259 default:
260 return "unknown error";
261 }
262}
263
264bool MotionController::configurePca9685(float pwm_frequency_hz) noexcept {
265 const float prescale_value =
266 (kPca9685OscillatorHz / (4096.0f * pwm_frequency_hz)) - 1.0f;
267 const long prescale_long = std::lround(prescale_value);
268
269 if (prescale_long < kMinimumPrescale || prescale_long > kMaximumPrescale) {
271 return false;
272 }
273
274 if (!writeRegister(kMode1Register, kMode1SleepBit)) {
275 return false;
276 }
277
278 if (!writeRegister(kPrescaleRegister,
279 static_cast<std::uint8_t>(prescale_long))) {
280 return false;
281 }
282
283 if (!writeRegister(kMode2Register, kMode2TotemPoleBit)) {
284 return false;
285 }
286
287 if (!writeRegister(kMode1Register, kMode1AutoIncrementBit)) {
288 return false;
289 }
290
291 if (!waitForWakeDelay(kWakeDelayUs)) {
292 return false;
293 }
294
295 return true;
296}
297
298bool MotionController::writeTargetsToHardware(
299 const MeArmJointTargets& targets) noexcept {
300 return writeChannelPulse(channel_map_.base, targets.base_ticks) &&
301 writeChannelPulse(channel_map_.lower, targets.lower_ticks) &&
302 writeChannelPulse(channel_map_.upper, targets.upper_ticks) &&
303 writeChannelPulse(channel_map_.gripper, targets.gripper_ticks);
304}
305
306bool MotionController::writeChannelPulse(std::uint8_t channel,
307 std::uint16_t pulse_ticks) noexcept {
308 if (channel >= kPca9685ChannelCount || pulse_ticks > kMaxPulseTicks) {
310 return false;
311 }
312
313 const std::uint8_t register_address = static_cast<std::uint8_t>(
314 kLed0OnLowRegister + (4U * channel));
315 const std::uint8_t payload[4] = {
316 0x00,
317 0x00,
318 static_cast<std::uint8_t>(pulse_ticks & 0xFFU),
319 static_cast<std::uint8_t>((pulse_ticks >> 8U) & 0x0FU)};
320
321 return writeRegisters(register_address, payload, 4);
322}
323
324bool MotionController::writeRegister(std::uint8_t reg,
325 std::uint8_t value) noexcept {
326 return writeRegisters(reg, &value, 1);
327}
328
329bool MotionController::writeRegisters(std::uint8_t start_reg,
330 const std::uint8_t* values,
331 std::uint8_t value_count) noexcept {
332 if (i2c_fd_ < 0 || values == nullptr || value_count == 0 || value_count > 4) {
334 return false;
335 }
336
337 std::uint8_t buffer[5] = {};
338 buffer[0] = start_reg;
339
340 for (std::uint8_t index = 0; index < value_count; ++index) {
341 buffer[index + 1] = values[index];
342 }
343
344 const std::size_t bytes_to_write =
345 static_cast<std::size_t>(value_count) + 1U;
346 const ssize_t bytes_written =
347 ::write(i2c_fd_, buffer, bytes_to_write);
348
349 return bytes_written == static_cast<ssize_t>(bytes_to_write);
350}
351
352bool MotionController::setAllOutputsEnabled(bool enabled) noexcept {
353 // Uses the PCA9685 full-off bit as the fast software inhibit path.
354 const std::uint8_t payload[4] = {
355 0x00,
356 0x00,
357 0x00,
358 static_cast<std::uint8_t>(enabled ? 0x00 : kFullOffBit)};
359
360 return writeRegisters(kAllLedOnLowRegister, payload, 4);
361}
362
363bool MotionController::hasValidChannelMap(
364 const MotionChannelMap& channel_map) const noexcept {
365 const std::array<std::uint8_t, kServoCount> channels = {
366 channel_map.base,
367 channel_map.lower,
368 channel_map.upper,
369 channel_map.gripper};
370
371 for (std::size_t i = 0; i < channels.size(); ++i) {
372 if (channels[i] >= kPca9685ChannelCount) {
373 return false;
374 }
375
376 for (std::size_t j = i + 1; j < channels.size(); ++j) {
377 if (channels[i] == channels[j]) {
378 return false;
379 }
380 }
381 }
382
383 return true;
384}
385
386void MotionController::setFault(MotionControllerError error) noexcept {
387 last_error_ = error;
388 frozen_ = true;
389 output_state_ = MotionOutputState::FAULT;
390
391 if (i2c_fd_ >= 0) {
392 (void)setAllOutputsEnabled(false);
393 }
394}
@ NONE
No hardware action required.
PCA9685-based servo output controller for ARGUS.
MotionControllerError
Error codes reported by MotionController operations.
@ NOT_INITIALISED
Controller is not initialised.
@ DEVICE_SELECT_FAILED
Failed to select I2C slave address.
@ DEVICE_IO_FAILED
I/O transaction failed.
@ DEVICE_OPEN_FAILED
Failed to open I2C device.
@ INVALID_ARGUMENT
Invalid input argument.
MotionOutputState
High-level state of motion output availability.
@ UNINITIALISED
Hardware not initialised.
@ DISABLED
Initialised but outputs disabled.
@ FAULT
Fault state; output path blocked until reset/re-init.
@ ENABLED
Outputs enabled and can accept target writes.
MotionControllerError lastError() const noexcept
Get last controller error code.
MotionController() noexcept
Construct motion controller in uninitialised state.
void freeze() noexcept
Freeze/disable motion output immediately.
bool isEnabled() const noexcept
Check whether motion output is currently enabled.
const char * lastErrorString() const noexcept
Get human-readable last error string.
~MotionController() noexcept
Destroy motion controller and release resources.
bool initialise(const char *i2c_device_path="/dev/i2c-1", std::uint8_t device_address=0x40, float pwm_frequency_hz=50.0f, MotionChannelMap channel_map={}) noexcept
Initialise PCA9685 output path.
bool isFrozen() const noexcept
Check whether output is currently frozen.
bool isInitialised() const noexcept
Check whether controller was successfully initialised.
bool setTargets(const MeArmJointTargets &targets) noexcept
Write joint targets to hardware when motion is enabled.
void shutdown() noexcept
Disable outputs and close hardware resources.
MotionOutputState outputState() const noexcept
Get current output state.
bool enable() noexcept
Enable motion output after successful initialisation.
Raw PWM tick targets for each MeArm joint.
Mapping of logical MeArm joints to PCA9685 channels.