Odrive内部存在一些非常好的设计,为了通篇掌握Odrive的设计理念和算法,对项目中采用的设计模式,数据结构和算法必须了解,本文梳理下Odrive中涉及到的一些重要的数据结构。

1. ComponentBase

所有支持update回调的类基类,支持传入一个时间戳用于实时更新对象内部状态。

class ComponentBase {
public:
    virtual void update(uint32_t timestamp) = 0;
};

子类有:

开环控制器类:OpenLoopController

FOC控制器类:FieldOrientedController

2. InputPort和OutputPort模板类

这两个类非常重要,是Odrive为了解耦各对象之间交互的桥梁,可以简化理解为两个对象之间通讯的中间件(只是类比),有了这个设计,模块可以很方便的连接外部输入源,还能管理输入源的生命周期。

template<typename T>
class OutputPort {
public:
    OutputPort(T val) : content_(val) {}
    void operator=(T value) {
        //关联数据
        content_ = value;
        age_ = 0;
    }
    void reset() {
        //强制让数据过期
        age_++;
    }
    std::optional<T> present() {
        //正值"壮年",数据是刚刚更新有效
        if (age_ == 0) {
            return content_;
        } else {
            return std::nullopt;
        }
    }
    std::optional<T> previous() {
        //上一个周期的数据
        if (age_ == 1) {
            return content_;
        } else {
            return std::nullopt;
        }
    }
    std::optional<T> any() {
        return content_;
    }
    
private:
    uint32_t age_ = 2; 
    T content_;
};

template<typename T>
class InputPort {
public:
    //将输入关联到特定的输入源--模板包装
    void connect_to(OutputPort<T>* input_port) {
        content_ = input_port;
    }
    //将输入关联到特定的输入源-原始指针
    void connect_to(T* input_ptr) {
        content_ = input_ptr;
    }
    //断开数据源
    void disconnect() {
        content_ = (OutputPort<T>*)nullptr;
    }
    //更新数据,实际上是获取的输入源数据
    std::optional<T> present() {
        if (content_.index() == 2) {
            OutputPort<T>* ptr = std::get<2>(content_);
            return ptr ? ptr->present() : std::nullopt;
        } else if (content_.index() == 1) {
            T* ptr = std::get<1>(content_);
            return ptr ? std::make_optional(*ptr) : std::nullopt;
        } else {
            return std::get<0>(content_);
        }
    }
    
    std::optional<T> any() {
        if (content_.index() == 2) {
            OutputPort<T>* ptr = std::get<2>(content_);
            return ptr ? ptr->any() : std::nullopt;
        } else if (content_.index() == 1) {
            T* ptr = std::get<1>(content_);
            return ptr ? std::make_optional(*ptr) : std::nullopt;
        } else {
            return std::get<0>(content_);
        }
    }
    
private:
    std::variant<T, T*, OutputPort<T>*> content_;
};

这两个类在很对地方都有用到,比如下面的代码就是把FOC控制的输入源关联到开关控制器的输出中:

axis_->motor_.current_control_.enable_current_control_src_ = (axis_->motor_.config_.motor_type != Motor::MOTOR_TYPE_GIMBAL);
axis_>motor_.current_control_.Idq_setpoint_src_.connect_to(&axis_>open_loop_controller_.Idq_setpoint_);
axis_->motor_.current_control_.Vdq_setpoint_src_.connect_to(&axis_->open_loop_controller_.Vdq_setpoint_);
     
axis_->motor_.current_control_.phase_src_.connect_to(&axis_->open_loop_controller_.phase_);
axis_->acim_estimator_.rotor_phase_src_.connect_to(&axis_->open_loop_controller_.phase_);

axis_->motor_.phase_vel_src_.connect_to(&axis_->open_loop_controller_.phase_vel_);
axis_->motor_.current_control_.phase_vel_src_.connect_to(&axis_->open_loop_controller_.phase_vel_);
axis_->acim_estimator_.rotor_phase_vel_src_.connect_to(&axis_->open_loop_controller_.phase_vel_);

再比如下面代码将编码器的输出关联到控制器的相关输入上

controller_.pos_estimate_circular_src_.connect_to(&ax->encoder_.pos_circular_);
controller_.pos_estimate_linear_src_.connect_to(&ax->encoder_.pos_estimate_);
controller_.vel_estimate_src_.connect_to(&ax->encoder_.vel_estimate_);
3. Timer类,用于做相关计时
template <class T>
class Timer {
   public:
    void setTimeout(const T timeout) {
        timeout_ = timeout;
    }

    void setIncrement(const T increment) {
        increment_ = increment;
    }

    void start() {
        running_ = true;
    }

    void stop() {
        running_ = false;
    }

    // If the timer is started, increment the timer
    void update() {
        if (running_)
            timer_ = std::min<T>(timer_ + increment_, timeout_);
    }

    void reset() {
        timer_ = static_cast<T>(0);
    }

    bool expired() {
        return timer_ >= timeout_;
    }

   private:
    T timer_ = static_cast<T>(0);     // Current state
    T timeout_ = static_cast<T>(0);   // Time to count
    T increment_ = static_cast<T>(0);  // Amount to increment each time update() is called
    bool running_ = false;            // update() only increments if runing_ is true
};

这个类配合下面的类和宏可以测量任意代码块的执行时间:

struct TaskTimerContext {
    TaskTimerContext(const TaskTimerContext&) = delete;
    TaskTimerContext(const TaskTimerContext&&) = delete;
    void operator=(const TaskTimerContext&) = delete;
    void operator=(const TaskTimerContext&&) = delete;
    TaskTimerContext(TaskTimer& timer) : timer_(timer), start_time(timer.start()) {}
    ~TaskTimerContext() { timer_.stop(start_time); }
    
    TaskTimer& timer_;
    uint32_t start_time;
    bool exit_ = false;
};

#define MEASURE_TIME(timer) for (TaskTimerContext __task_timer_ctx{timer}; !__task_timer_ctx.exit_; __task_timer_ctx.exit_ = true)

比如下面的代码就是测量热敏电阻算法更新的耗时:

MEASURE_TIME(axis.task_times_.thermistor_update) {
    axis.motor_.fet_thermistor_.update();
    axis.motor_.motor_thermistor_.update();
}
4. Endpoint。用于对输入信号的极性检测进行封装

该类处理GPIO的输入状态进行拦截,同时考虑抖动的处理。

class Endstop {
   public:
    struct Config_t {
        float offset = 0;
        uint32_t debounce_ms = 50;
        uint16_t gpio_num = 0;
        bool enabled = false;
        bool is_active_high = false;

        // custom setters
        Endstop* parent = nullptr;
        void set_gpio_num(uint16_t value) { gpio_num = value; parent->apply_config(); }
        void set_enabled(uint32_t value) { enabled = value; parent->apply_config(); }
        void set_debounce_ms(uint32_t value) { debounce_ms = value; parent->apply_config(); }
    };


    Endstop::Config_t config_;
    Axis* axis_ = nullptr;

    bool apply_config();

    void update();
    constexpr bool get_state(){
        return endstop_state_;
    }

    constexpr bool rose(){
        return (endstop_state_ != last_state_) && endstop_state_;
    }

    constexpr bool fell(){
        return (endstop_state_ != last_state_) && !endstop_state_;
    }

    bool endstop_state_ = false;

   private:
    bool last_state_ = false;
    bool pin_state_ = false;
    float pos_when_pressed_ = 0.0f;
    Timer<float> debounceTimer_;
};

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐