17using namespace std::chrono_literals;
19using namespace roboctrl;
23struct dji_upload_pkg {
32} __attribute__((packed));
34static_assert(
sizeof(dji_upload_pkg) == 8);
36struct dji_motor_measure {
39 int16_t given_current;
44 const auto pkg = utils::from_bytes<dji_upload_pkg>(data);
49 .temperature = pkg.temperature,
53constexpr fp32 _rpm_to_rad_s = 2.f *
Pi_f / 60.f;
54constexpr fp32 _ecd_8192_to_rad = 2.f *
Pi_f / 8192.f;
56static std::string __motor_tyep_to_string(dji_motor::type type){
58 case roboctrl::device::dji_motor::M2006:
60 case roboctrl::device::dji_motor::M3508:
62 case roboctrl::device::dji_motor::M6020:
74 log_info(
"Dji Motor Group created on {}",info.can_name);
77void dji_motor_group::start() {
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()));
93 motors_.push_back(
motor);
96std::pair<uint16_t, uint16_t> dji_motor::can_pkg_id()
const {
100 if (info_.id >= 1 && info_.id <= 4)
101 return {0x200,
static_cast<uint16_t
>(info_.id - 1)};
102 else if (info_.id >= 5 && info_.id <= 8)
103 return {0x1ff,
static_cast<uint16_t
>(info_.id - 5)};
105 log_error(
"invalid dji motor id: {}", info_.id);
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)};
114 log_error(
"invalid gm6020 id: {}", info_.id);
118 throw std::invalid_argument(
"unsupported DJI motor type");
122 std::array<std::byte,8> data{};
125 for (
auto motor : motors_) {
126 if (!motor)
continue;
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);
138 data[offset + 1] =
utils::to_byte(
static_cast<uint16_t
>(cur) & 0xff);
144 co_await roboctrl::get<io::can>(info_.can_name).send(can_id_, data);
156 co_await send_command(0x1ff);
157 co_await send_command(0x200);
158 co_await send_command(0x2ff);
163 pid_{info.pid_params},
166 if (info.name.empty()) {
167 throw std::invalid_argument(
"DJI motor name must not be empty");
169 if (info.can_name.empty()) {
170 throw std::invalid_argument(std::format(
"DJI motor {} has no CAN dependency", info.name));
172 if (!std::isfinite(info.radius) || info.radius <= 0.0f) {
173 throw std::invalid_argument(std::format(
"DJI motor {} has invalid radius", info.name));
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));
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));
186 case dji_motor::M2006:
187 reduction_ratio_ = 1.f / 36.f;
189 case dji_motor::M3508:
190 reduction_ratio_ = 1.f / 19.f;
192 case dji_motor::M6020:
193 reduction_ratio_ = 1.f;
196 throw std::invalid_argument(std::format(
197 "DJI motor {} has unsupported type {}", info.name,
198 static_cast<int>(info.type_)));
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_)));
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_)));
213 log_debug(
"Dji \"{}\" motor {} created on can \"{}\" with pid(p={},i={},d={},max iout={},max out={})",
214 __motor_tyep_to_string(info.type_),
220 info.pid_params.max_iout,
221 info.pid_params.max_out
226void dji_motor::connect() {
231 auto& group =
roboctrl::get(dji_motor_group::info_type::make(info_.can_name));
232 auto& can = roboctrl::get<io::can>(info_.can_name);
234 uint16_t fallback_canid;
237 case dji_motor::M2006:
238 fallback_canid = 0x200 + info_.id;
240 case dji_motor::M3508:
241 fallback_canid = 0x200 + info_.id;
243 case dji_motor::M6020:
244 fallback_canid = 0x204 + info_.id;
247 throw std::invalid_argument(std::format(
248 "DJI motor {} has unsupported type {}", info_.name,
249 static_cast<int>(info_.type_)));
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;
259 log_debug(
"angle:{}, speed:{}, torque:{} ,linear speed:{},target speed:{}",this->angle_,this->angle_speed_,this->torque_,
linear_speed(),pid_.target());
261 },
sizeof(dji_upload_pkg));
263 group.register_motor(
this);
267void dji_motor::start() {
269 throw std::logic_error(std::format(
"DJI motor {} must be connected before start", info_.name));
280 target_fresh_since_enable_ =
false;
283 last_control_at_ = {};
294 target_fresh_since_enable_ =
false;
296 last_control_at_ = {};
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);
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);
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;
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()) {
343 target_fresh_since_enable_ =
false;
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();
353 current_ = std::clamp(pid_.
state(), -max_current(), max_current());
356 co_await wait_for(info_.control_time);
基于 Linux SocketCAN 的 CAN 总线封装。
dji_motor_group(info_type info)
构造分组。
awaitable< void > task()
与调度器协同的任务,负责读取反馈等。
awaitable< void > flush_commands_once()
立即按当前安全门状态刷新一轮全部命令帧。
void register_motor(dji_motor *motor)
注册单个电机到分组内。
void set_enabled(bool enabled) override
awaitable< void > set(fp32 speed) override
设置线速度目标,单位 m/s;角速度与原始电流使用显式接口。
awaitable< void > set_angle_speed(fp32 speed) override
awaitable< void > set_current(fp32 command) override
void log_error(std::format_string< Args... > fmt, Args &&...args) const
输出error日志
void log_debug(std::format_string< Args... > fmt, Args &&...args) const
输出debug日志
void log_info(std::format_string< Args... > fmt, Args &&...args) const
输出info日志
std::span< const std::byte > byte_span
只读 byte span。
awaitable< void > wait_for(const duration &duration)
协程任务等待。
auto spawn(task_context::task_type &&task)
添加一个协程任务到全局任务上下文中执行。
asio::awaitable< T > awaitable
协程任务类型。
bool shutdown_requested()
查询全局任务上下文是否已经进入停机流程。
constexpr std::byte to_byte(std::integral auto v) noexcept
辅助将整数转换为 std::byte。
constexpr int16_t make_i16(int16_t high, int16_t low) noexcept
将高低字节组合成有符号 16 位整数。
constexpr uint16_t make_u16(uint16_t high, uint16_t low) noexcept
将高低字节组合成无符号 16 位整数。
bool offline() const
判断设备是否离线
fp32 linear_speed() const
获取电机线速度(单位为m/s)
fp32 angle_speed() const
获取电机角速度(单位为rad/s)
void clean()
清空积分项、误差缓存与输出。
T state() const
获取最新的控制输出。
void set_target(T target)
设置期望目标。
void update(T current, T dt)
根据当前值和采样周期更新 PID 输出。
constexpr auto Pi_f
单精度浮点类型的 Pi 常量。