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

系列文章目录


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


前言

需要注意的是目前我拿到的是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 < sizeof(ports_to_sample) / sizeof(ports_to_sample[0]); ++i) {
        port_samples_[i] = ports_to_sample[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冲突了,以后用的话 就得改改了。


总结

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

相关推荐

  1. ODrive学习笔记——编码器

    2024-07-17 08:08:03       31 阅读
  2. ODrive学习笔记三——串口

    2024-07-17 08:08:03       23 阅读
  3. ODrive学习笔记一:开发环境搭建

    2024-07-17 08:08:03       20 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-17 08:08:03       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-17 08:08:03       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-17 08:08:03       58 阅读
  4. Python语言-面向对象

    2024-07-17 08:08:03       69 阅读

热门阅读

  1. 基于深度学习的机器人控制

    2024-07-17 08:08:03       26 阅读
  2. C++ ‘##’ 运算符使用

    2024-07-17 08:08:03       20 阅读
  3. python3多线程用途和场景

    2024-07-17 08:08:03       20 阅读
  4. 2024年还能入局网络安全吗?

    2024-07-17 08:08:03       22 阅读
  5. 树莓派docker自制镜像

    2024-07-17 08:08:03       23 阅读
  6. React基础学习-Day06

    2024-07-17 08:08:03       22 阅读
  7. Oracle(6)什么是重做日志文件(Redo Log File)?

    2024-07-17 08:08:03       17 阅读
  8. el-table template slot-scope=“scope“ 不显示内容

    2024-07-17 08:08:03       25 阅读