ODrive学习笔记四——编码器流

07-17 1512阅读

系列文章目录


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

ODrive学习笔记四——编码器流
(图片来源网络,侵删)

文章目录

  • 系列文章目录
  • 前言
  • 一、Encoder 初始化
  • 二、Encoder采集
  • 三、额外的一些 :温度传感器
  • 总结

    前言

    需要注意的是目前我拿到的是ODrive3.6的硬件版本。再往上就不开源了,很难。ODrvie S1 PRO等版本的资料也拿不到。不巧的是目前电机使用的编码器是485版本的,没办法那就只能先研究一下编码器了。


    一、Encoder 初始化

    首先在rtos_main中找到encoder初始化的入口。

        for(auto& axis: axes){
            axis.encoder_.setup();
        }
    
    void Encoder::setup() {
        HAL_TIM_Encoder_Start(timer_, TIM_CHANNEL_ALL);
        set_idx_subscribe();
        mode_ = config_.mode;
        spi_task_.config = {
            .Mode = SPI_MODE_MASTER,
            .Direction = SPI_DIRECTION_2LINES,
            .DataSize = SPI_DATASIZE_16BIT,
            .CLKPolarity = (mode_ == MODE_SPI_ABS_AEAT || mode_ == MODE_SPI_ABS_MA732) ? SPI_POLARITY_HIGH : SPI_POLARITY_LOW,
            .CLKPhase = SPI_PHASE_2EDGE,
            .NSS = SPI_NSS_SOFT,
            .BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16,
            .FirstBit = SPI_FIRSTBIT_MSB,
            .TIMode = SPI_TIMODE_DISABLE,
            .CRCCalculation = SPI_CRCCALCULATION_DISABLE,
            .CRCPolynomial = 10,
        };
        if (mode_ == MODE_SPI_ABS_MA732) {
            abs_spi_dma_tx_[0] = 0x0000;
        }
        if(mode_ & MODE_FLAG_ABS){
            abs_spi_cs_pin_init();
            if (axis_->controller_.config_.anticogging.pre_calibrated) {
                axis_->controller_.anticogging_valid_ = true;
            }
        }
    }
    

    setup接口里面,更多的是底层驱动的初始化。像是TIMER,GPIO中断,SPI

    二、Encoder采集

    void TIM8_UP_TIM13_IRQHandler(void) {
        COUNT_IRQ(TIM8_UP_TIM13_IRQn);
        
        // Entry into this function happens at 21-23 clock cycles after the timer
        // update event.
        __HAL_TIM_CLEAR_IT(&htim8, TIM_IT_UPDATE);
        // If the corresponding timer is counting up, we just sampled in SVM vector 0, i.e. real current
        // If we are counting down, we just sampled in SVM vector 7, with zero current
        bool counting_down = TIM8->CR1 & TIM_CR1_DIR;
        bool timer_update_missed = (counting_down_ == counting_down);
        if (timer_update_missed) {
            motors[0].disarm_with_error(Motor::ERROR_TIMER_UPDATE_MISSED);
            motors[1].disarm_with_error(Motor::ERROR_TIMER_UPDATE_MISSED);
            return;
        }
        counting_down_ = counting_down;
        timestamp_ += TIM_1_8_PERIOD_CLOCKS * (TIM_1_8_RCR + 1);
        if (!counting_down) {
            TaskTimer::enabled = odrv.task_timers_armed_;
            // Run sampling handlers and kick off control tasks when TIM8 is
            // counting up.
            odrv.sampling_cb();
            NVIC->STIR = ControlLoop_IRQn;
        } else {
            // Tentatively reset all PWM outputs to 50% duty cycles. If the control
            // loop handler finishes in time then these values will be overridden
            // before they go into effect.
            TIM1->CCR1 =
            TIM1->CCR2 =
            TIM1->CCR3 =
            TIM8->CCR1 =
            TIM8->CCR2 =
            TIM8->CCR3 =
                TIM_1_8_PERIOD_CLOCKS / 2;
        }
    }
    

    是利用定时器中断 回调了 odrv.sampling_cb();

    void Encoder::sample_now() {
        switch (mode_) {
            case MODE_INCREMENTAL: {
                tim_cnt_sample_ = (int16_t)timer_->Instance->CNT;
            } break;
            case MODE_HALL: {
                // do nothing: samples already captured in general GPIO capture
            } break;
            case MODE_SINCOS: {
                sincos_sample_s_ = get_adc_relative_voltage(get_gpio(config_.sincos_gpio_pin_sin)) - 0.5f;
                sincos_sample_c_ = get_adc_relative_voltage(get_gpio(config_.sincos_gpio_pin_cos)) - 0.5f;
            } break;
            case MODE_SPI_ABS_AMS:
            case MODE_SPI_ABS_CUI:
            case MODE_SPI_ABS_AEAT:
            case MODE_SPI_ABS_RLS:
            case MODE_SPI_ABS_MA732:
            {
                abs_spi_start_transaction();
                // Do nothing
            } break;
            default: {
               set_error(ERROR_UNSUPPORTED_ENCODER_MODE);
            } break;
        }
        // Sample all GPIO digital input data registers, used for HALL sensors for example.
        for (size_t i = 0; i IDR;
        }
    }
    

    这里面根据编码器格式的不同,进行了分别的处理。如果想添加485的编码器,就需要仿照SPI类型的编码器,在这里添加对应的读取接口。

    先看下SPI是如何进行读取的。

    bool Encoder::abs_spi_start_transaction() {
        if (mode_ & MODE_FLAG_ABS){
            if (Stm32SpiArbiter::acquire_task(&spi_task_)) {
                spi_task_.ncs_gpio = abs_spi_cs_gpio_;
                spi_task_.tx_buf = (uint8_t*)abs_spi_dma_tx_;
                spi_task_.rx_buf = (uint8_t*)abs_spi_dma_rx_;
                spi_task_.length = 1;
                spi_task_.on_complete = [](void* ctx, bool success) { ((Encoder*)ctx)->abs_spi_cb(success); };
                spi_task_.on_complete_ctx = this;
                spi_task_.next = nullptr;
                
                spi_arbiter_->transfer_async(&spi_task_);
            } else {
                return false;
            }
        }
        return true;
    }
    

    通过接口启动了一个SPI的DMA传输。发送的数据存放在abs_spi_dma_tx_ 收到的数据存储在abs_spi_dma_rx_。

    没注意看目前支持的几个SPI传感器的通信协议,但是没有找到对abs_spi_dma_tx特殊的赋值,除了

        if (mode_ == MODE_SPI_ABS_MA732) {
            abs_spi_dma_tx_[0] = 0x0000;
        }
    

    可能其他编码器直接读就可以嘛。读取完成之后,通过abs_spi_cb回调,将获得编码器值反馈到rawVal变量,部分编码器需要判断一下方向,之后传给pos_abs_。

    void Encoder::abs_spi_cb(bool success) {
        uint16_t pos;
        if (!success) {
            goto done;
        }
        switch (mode_) {
            case MODE_SPI_ABS_AMS: {
                uint16_t rawVal = abs_spi_dma_rx_[0];
                // check if parity is correct (even) and error flag clear
                if (ams_parity(rawVal) || ((rawVal >> 14) & 1)) {
                    goto done;
                }
                pos = rawVal & 0x3fff;
            } break;
            case MODE_SPI_ABS_CUI: {
                uint16_t rawVal = abs_spi_dma_rx_[0];
                // check if parity is correct
                if (cui_parity(rawVal)) {
                    goto done;
                }
                pos = rawVal & 0x3fff;
            } break;
            case MODE_SPI_ABS_RLS: {
                uint16_t rawVal = abs_spi_dma_rx_[0];
                pos = (rawVal >> 2) & 0x3fff;
            } break;
            case MODE_SPI_ABS_MA732: {
                uint16_t rawVal = abs_spi_dma_rx_[0];
                pos = (rawVal >> 2) & 0x3fff;
            } break;
            default: {
               set_error(ERROR_UNSUPPORTED_ENCODER_MODE);
               goto done;
            } break;
        }
        pos_abs_ = pos;
        abs_spi_pos_updated_ = true;
        if (config_.pre_calibrated) {
            is_ready_ = true;
        }
    done:
        Stm32SpiArbiter::release_task(&spi_task_);
    }
    

    最后在update中更新编码器值,并给系统其他方面调用。

    bool Encoder::update() {
        // update internal encoder state.
        int32_t delta_enc = 0;
        int32_t pos_abs_latched = pos_abs_; //LATCH
    

    过程中还有一些校准的操作需要再研究一下。

    三、额外的一些 :温度传感器

    进入入口之后前几条是温度传感器的刷新操作

    // @brief Set up the gate drivers
    bool Motor::setup() {
        fet_thermistor_.update();
        motor_thermistor_.update();
    

    然后先看一下这两个传感器所使用的I/O口。

    在board.cpp中

    OnboardThermistorCurrentLimiter fet_thermistors[AXIS_COUNT] = {
        {
            15, // adc_channel
            &fet_thermistor_poly_coeffs[0], // coefficients
            fet_thermistor_num_coeffs // num_coeffs
        }, {
    #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3
            4, // adc_channel
    #else
            1, // adc_channel
    #endif
            &fet_thermistor_poly_coeffs[0], // coefficients
            fet_thermistor_num_coeffs // num_coeffs
        }
    };
    

    板载的传感器用的是ADC 15通道和4通道。另外还有1路板外的可接入的温度传感器。

    class OffboardThermistorCurrentLimiter : public ThermistorCurrentLimiter, public ODriveIntf::OffboardThermistorCurrentLimiterIntf {
    public:
        static const size_t num_coeffs_ = 4;
        struct Config_t {
            float thermistor_poly_coeffs[num_coeffs_];
    #if HW_VERSION_MAJOR == 3
            uint16_t gpio_pin = 4;
    #elif HW_VERSION_MAJOR == 4
            uint16_t gpio_pin = 2;
    #endif
            float temp_limit_lower = 100;
            float temp_limit_upper = 120;
            bool enabled = false;
            // custom setters
            OffboardThermistorCurrentLimiter* parent;
            void set_gpio_pin(uint16_t value) { gpio_pin = value; parent->decode_pin(); }
        };
        virtual ~OffboardThermistorCurrentLimiter() = default;
        OffboardThermistorCurrentLimiter();
        Config_t config_;
        bool apply_config();
    private:
        void decode_pin();
    };
    

    这次初始化是从GPIO数组里取得,看了下是PA3。这样就跟UART2冲突了,以后用的话 就得改改了。


    总结

    提示:这里对文章进行总结:

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]