GKD.RoboCtrl
RoboMaster Linux 电控:异步 IO、设备驱动与机器人控制
载入中...
搜索中...
未找到
dji.cpp
1#include <algorithm>
2#include <chrono>
3#include <cmath>
4#include <cstddef>
5#include <cstdint>
6#include <limits>
7#include <stdexcept>
8#include <sys/types.h>
9
10#include "device/motor/dji.h"
11#include "core/async.hpp"
12#include "device/motor/base.hpp"
13#include "io/base.hpp"
14#include "io/can.h"
15#include "utils/utils.hpp"
16
17using namespace std::chrono_literals;
18using namespace roboctrl::device;
19using namespace roboctrl;
20
21namespace {
22
23struct dji_upload_pkg {
24 uint8_t angle_h;
25 uint8_t angle_l;
26 uint8_t speed_h;
27 uint8_t speed_l;
28 uint8_t current_h;
29 uint8_t current_l;
30 uint8_t temperature;
31 uint8_t unused;
32} __attribute__((packed));
33
34static_assert(sizeof(dji_upload_pkg) == 8);
35
36struct dji_motor_measure {
37 uint16_t ecd;
38 int16_t speed_rpm;
39 int16_t given_current;
40 uint8_t temperature;
41};
42
43dji_motor_measure parse_dji_upload_pkg(io::byte_span data) {
44 const auto pkg = utils::from_bytes<dji_upload_pkg>(data);
45 return {
46 .ecd = utils::make_u16(pkg.angle_h, pkg.angle_l),
47 .speed_rpm = utils::make_i16(pkg.speed_h, pkg.speed_l),
48 .given_current = utils::make_i16(pkg.current_h, pkg.current_l),
49 .temperature = pkg.temperature,
50 };
51}
52
53constexpr fp32 _rpm_to_rad_s = 2.f * Pi_f / 60.f;
54constexpr fp32 _ecd_8192_to_rad = 2.f * Pi_f / 8192.f;
55
56static std::string __motor_tyep_to_string(dji_motor::type type){
57 switch(type){
58 case roboctrl::device::dji_motor::M2006:
59 return "M2006";
60 case roboctrl::device::dji_motor::M3508:
61 return "M3508";
62 case roboctrl::device::dji_motor::M6020:
63 return "M6020";
64 default:
65 return "Unknown";
66 }
67}
68
69} // namespace
70
72 info_{info}
73{
74 log_info("Dji Motor Group created on {}",info.can_name);
75}
76
77void dji_motor_group::start() {
78 if (started_) {
79 return;
80 }
81 started_ = true;
83}
84
86 for(auto m : motors_){
87 if(m->can_pkg_id() == motor->can_pkg_id()){
88 throw std::runtime_error(std::format(
89 "DJI command slot conflict between {} and {}", m->desc(), motor->desc()));
90 }
91 }
92
93 motors_.push_back(motor);
94}
95
96std::pair<uint16_t, uint16_t> dji_motor::can_pkg_id() const {
97 switch(info_.type_) {
98 case M2006:
99 case M3508:
100 if (info_.id >= 1 && info_.id <= 4)
101 return {0x200, static_cast<uint16_t>(info_.id - 1)}; // index: 0..3
102 else if (info_.id >= 5 && info_.id <= 8)
103 return {0x1ff, static_cast<uint16_t>(info_.id - 5)}; // index: 0..3
104 else {
105 log_error("invalid dji motor id: {}", info_.id);
106 return {0x200, 0};
107 }
108 case M6020:
109 if (info_.id >= 1 && info_.id <= 4)
110 return {0x1ff, static_cast<uint16_t>(info_.id - 1)};
111 else if (info_.id >= 5 && info_.id <= 7)
112 return {0x2ff, static_cast<uint16_t>(info_.id - 5)};
113 else {
114 log_error("invalid gm6020 id: {}", info_.id);
115 return {0x1ff, 0};
116 }
117 }
118 throw std::invalid_argument("unsupported DJI motor type");
119}
120
121roboctrl::awaitable<void> dji_motor_group::send_command(uint16_t can_id_) {
122 std::array<std::byte,8> data{};
123 bool flag = false;
124
125 for (auto motor : motors_) {
126 if (!motor) continue;
127
128 auto [can_id, index] = motor->can_pkg_id();
129 if (can_id == can_id_) {
130 auto cur = motor->current();
131 std::size_t offset = static_cast<std::size_t>(index) * 2;
132 if (offset + 1 >= data.size()) {
133 log_error("dji current index out of range: id={}, index={}", motor->info_.can_name, index);
134 continue;
135 }
136
137 data[offset] = utils::to_byte(static_cast<uint16_t>(cur) >> 8);
138 data[offset + 1] = utils::to_byte(static_cast<uint16_t>(cur) & 0xff);
139 flag = true;
140 }
141 }
142
143 if (flag)
144 co_await roboctrl::get<io::can>(info_.can_name).send(can_id_, data);
145};
146
148 while(true){
149 co_await flush_commands_once();
150
151 co_await wait_for(1ms);
152 }
153}
154
156 co_await send_command(0x1ff);
157 co_await send_command(0x200);
158 co_await send_command(0x2ff);
159}
160
161dji_motor::dji_motor(dji_motor::info_type info)
162 :info_{info},
163 pid_{info.pid_params},
164 motor_base{2ms,info.radius}
165{
166 if (info.name.empty()) {
167 throw std::invalid_argument("DJI motor name must not be empty");
168 }
169 if (info.can_name.empty()) {
170 throw std::invalid_argument(std::format("DJI motor {} has no CAN dependency", info.name));
171 }
172 if (!std::isfinite(info.radius) || info.radius <= 0.0f) {
173 throw std::invalid_argument(std::format("DJI motor {} has invalid radius", info.name));
174 }
175 if (info.control_time <= std::chrono::steady_clock::duration::zero()) {
176 throw std::invalid_argument(std::format("DJI motor {} has invalid control period", info.name));
177 }
178 const auto& pid = info.pid_params;
179 if (!std::isfinite(pid.kp) || !std::isfinite(pid.ki) || !std::isfinite(pid.kd) ||
180 !std::isfinite(pid.max_out) || !std::isfinite(pid.max_iout) ||
181 pid.max_out < 0.0f || pid.max_iout < 0.0f) {
182 throw std::invalid_argument(std::format("DJI motor {} has invalid PID parameters", info.name));
183 }
184
185 switch(info_.type_){
186 case dji_motor::M2006:
187 reduction_ratio_ = 1.f / 36.f;
188 break;
189 case dji_motor::M3508:
190 reduction_ratio_ = 1.f / 19.f;
191 break;
192 case dji_motor::M6020:
193 reduction_ratio_ = 1.f;
194 break;
195 default:
196 throw std::invalid_argument(std::format(
197 "DJI motor {} has unsupported type {}", info.name,
198 static_cast<int>(info.type_)));
199 }
200
201 if (info.id < 1 || info.id > max_device_id(info.type_)) {
202 throw std::invalid_argument(std::format(
203 "DJI motor {} has invalid id {} for type {}",
204 info.name, info.id, __motor_tyep_to_string(info.type_)));
205 }
206 const auto current_limit = command_current_limit(info.type_);
207 if (pid.max_out > current_limit || pid.max_iout > current_limit) {
208 throw std::invalid_argument(std::format(
209 "DJI motor {} PID limit exceeds {} command range",
210 info.name, __motor_tyep_to_string(info.type_)));
211 }
212
213 log_debug("Dji \"{}\" motor {} created on can \"{}\" with pid(p={},i={},d={},max iout={},max out={})",
214 __motor_tyep_to_string(info.type_),
215 info.name,
216 info.can_name,
217 info.pid_params.kp,
218 info.pid_params.ki,
219 info.pid_params.kd,
220 info.pid_params.max_iout,
221 info.pid_params.max_out
222 );
223
224}
225
226void dji_motor::connect() {
227 if (connected_) {
228 return;
229 }
230
231 auto& group = roboctrl::get(dji_motor_group::info_type::make(info_.can_name));
232 auto& can = roboctrl::get<io::can>(info_.can_name);
233
234 uint16_t fallback_canid;
235
236 switch(info_.type_){
237 case dji_motor::M2006:
238 fallback_canid = 0x200 + info_.id;
239 break;
240 case dji_motor::M3508:
241 fallback_canid = 0x200 + info_.id;
242 break;
243 case dji_motor::M6020:
244 fallback_canid = 0x204 + info_.id;
245 break;
246 default:
247 throw std::invalid_argument(std::format(
248 "DJI motor {} has unsupported type {}", info_.name,
249 static_cast<int>(info_.type_)));
250 }
251
252 can.on_data(fallback_canid, [this](io::byte_span data) {
253 const auto measure = parse_dji_upload_pkg(data);
254 if (measure.ecd >= 8192) return;
255 angle_ = _ecd_8192_to_rad * static_cast<float>(measure.ecd);
256 angle_speed_ = _rpm_to_rad_s * static_cast<float>(measure.speed_rpm) * reduction_ratio_;
257 torque_ = measure.given_current;
258
259 log_debug("angle:{}, speed:{}, torque:{} ,linear speed:{},target speed:{}",this->angle_,this->angle_speed_,this->torque_,linear_speed(),pid_.target());
260 tick();
261 }, sizeof(dji_upload_pkg));
262
263 group.register_motor(this);
264 connected_ = true;
265}
266
267void dji_motor::start() {
268 if (!connected_) {
269 throw std::logic_error(std::format("DJI motor {} must be connected before start", info_.name));
270 }
271 if (started_) {
272 return;
273 }
274 started_ = true;
275 roboctrl::spawn(task());
276}
277
279 enabled_ = false;
280 target_fresh_since_enable_ = false;
281 current_ = 0;
282 pid_.clean();
283 last_control_at_ = {};
284}
285
286void dji_motor::set_enabled(bool enabled) {
287 if (enabled) {
289 disable();
290 return;
291 }
292 if (!enabled_) {
293 current_ = 0;
294 target_fresh_since_enable_ = false;
295 pid_.clean();
296 last_control_at_ = {};
297 }
298 enabled_ = true;
299 } else {
300 disable();
301 }
302}
303
305 if (mode_ != control_mode::linear) pid_.clean();
306 mode_ = control_mode::linear;
307 target_fresh_since_enable_ = enabled_ && std::isfinite(speed);
308 if (!target_fresh_since_enable_) { current_ = 0; pid_.clean(); }
309 pid_.set_target(target_fresh_since_enable_ ? speed : 0.f);
310
311 log_debug("target set to :{}",speed);
312
313 co_return;
314}
315
317 if (mode_ != control_mode::angular) pid_.clean();
318 mode_ = control_mode::angular;
319 target_fresh_since_enable_ = enabled_ && std::isfinite(speed);
320 if (!target_fresh_since_enable_) { current_ = 0; pid_.clean(); }
321 pid_.set_target(target_fresh_since_enable_ ? speed : 0.f);
322 co_return;
323}
324
326 if (mode_ != control_mode::current) pid_.clean();
327 mode_ = control_mode::current;
328 target_fresh_since_enable_ = enabled_ && std::isfinite(command);
329 current_ = target_fresh_since_enable_ && !offline()
330 ? std::clamp(command, -max_current(), max_current()) : 0.f;
331 co_return;
332}
333
334roboctrl::awaitable<void> dji_motor::task(){
335 while(true){
336 const auto now = std::chrono::steady_clock::now();
337 const auto elapsed = now - last_control_at_;
338 const bool valid_elapsed = last_control_at_ != std::chrono::steady_clock::time_point{} &&
339 elapsed > std::chrono::steady_clock::duration::zero() && elapsed <= info_.control_time * 5;
340 last_control_at_ = now;
341 if (!enabled_ || !target_fresh_since_enable_ || offline()) {
342 current_ = 0.f;
343 target_fresh_since_enable_ = false;
344 pid_.clean();
345 } else if (mode_ != control_mode::current) {
346 const fp32 dt = valid_elapsed ? std::chrono::duration<fp32>(elapsed).count() : 0.f;
347 if (!valid_elapsed) {
348 const auto target = pid_.target();
349 pid_.clean();
350 pid_.set_target(target);
351 }
352 pid_.update(mode_ == control_mode::linear ? linear_speed() : angle_speed(), dt);
353 current_ = std::clamp(pid_.state(), -max_current(), max_current());
354 }
355
356 co_await wait_for(info_.control_time);
357 }
358}
异步任务上下文组件。
基于 Linux SocketCAN 的 CAN 总线封装。
dji_motor_group(info_type info)
构造分组。
Definition dji.cpp:71
awaitable< void > task()
与调度器协同的任务,负责读取反馈等。
Definition dji.cpp:147
awaitable< void > flush_commands_once()
立即按当前安全门状态刷新一轮全部命令帧。
Definition dji.cpp:155
void register_motor(dji_motor *motor)
注册单个电机到分组内。
Definition dji.cpp:85
DJI 系列电机。
Definition dji.h:25
void set_enabled(bool enabled) override
Definition dji.cpp:286
awaitable< void > set(fp32 speed) override
设置线速度目标,单位 m/s;角速度与原始电流使用显式接口。
Definition dji.cpp:304
void disable() override
Definition dji.cpp:278
awaitable< void > set_angle_speed(fp32 speed) override
Definition dji.cpp:316
awaitable< void > set_current(fp32 command) override
Definition dji.cpp:325
void log_error(std::format_string< Args... > fmt, Args &&...args) const
输出error日志
Definition logger.h:238
void log_debug(std::format_string< Args... > fmt, Args &&...args) const
输出debug日志
Definition logger.h:202
void log_info(std::format_string< Args... > fmt, Args &&...args) const
输出info日志
Definition logger.h:214
电机基础组件。
DJI 电机及分组抽象。
IO的基础组件。
std::span< const std::byte > byte_span
只读 byte span。
Definition base.hpp:45
awaitable< void > wait_for(const duration &duration)
协程任务等待。
Definition async.hpp:262
auto spawn(task_context::task_type &&task)
添加一个协程任务到全局任务上下文中执行。
Definition async.hpp:191
asio::awaitable< T > awaitable
协程任务类型。
Definition async.hpp:46
bool shutdown_requested()
查询全局任务上下文是否已经进入停机流程。
Definition async.hpp:226
auto get() -> T &
获取单例实例
Definition multiton.hpp:230
constexpr std::byte to_byte(std::integral auto v) noexcept
辅助将整数转换为 std::byte。
Definition utils.hpp:180
constexpr int16_t make_i16(int16_t high, int16_t low) noexcept
将高低字节组合成有符号 16 位整数。
Definition utils.hpp:173
constexpr uint16_t make_u16(uint16_t high, uint16_t low) noexcept
将高低字节组合成无符号 16 位整数。
Definition utils.hpp:166
void tick()
更新心跳时间
Definition base.hpp:61
bool offline() const
判断设备是否离线
Definition base.hpp:56
电机初始化参数。
Definition dji.h:61
fp32 linear_speed() const
获取电机线速度(单位为m/s)
Definition base.hpp:95
fp32 angle_speed() const
获取电机角速度(单位为rad/s)
Definition base.hpp:74
void clean()
清空积分项、误差缓存与输出。
Definition pid.h:106
T state() const
获取最新的控制输出。
Definition pid.h:116
void set_target(T target)
设置期望目标。
Definition pid.h:54
void update(T current, T dt)
根据当前值和采样周期更新 PID 输出。
Definition pid.h:67
常用数值与字节工具集合。
constexpr auto Pi_f
单精度浮点类型的 Pi 常量。
Definition utils.hpp:34
float fp32
单精度浮点别名。
Definition utils.hpp:22