ODrive0.5.5源码分析(6) 时间戳

作者:沉尸([email protected])

前言:

本章重点研究时间戳(timestamp)在软件中的使用,可以先复习一下:

《ODrive0.5.5源码分析(2) 时钟和定时器》

软件中使用时间戳的地方比较多,这里将仅仅聚焦于FOC相关的代码部分。

1)

软件中的时间戳(timestamp)均以TIM8中的定时器计数值作为标准。

TIM8的定时器是从0开始计数的,而TIM1的计数初始值领先TIM8 “TIM1_INIT_COUNT”个计数值。

有些ADC的触发是被TIM1触发的,所以要注意软件中经常会对时间戳参数采取减去“TIM1_INIT_COUNT”的处理,比如:

 上图取自“ControlLoop_IRQHandler()”

参数“timestamp”对应着TIM8发生update中断这个时刻的“时间戳”,函数中要传入“电流测量这个时刻”的时间戳,对于M0来说,它的电流检测ADC是由TIM1触发的,时间上要稍微早一点,于是针对M0的计算,就要减去“TIM1_INIT_COUNT”。

软件中还有好几处类似的处理,以后就不再赘述。

2)

先从“timestamp_”开始,这是一个全局变量,记录的是“TIM8_UP_TIM13_IRQHandler”中断时的时间戳

ODrive0.5.5源码分析(6) 时间戳_第1张图片

 ODrive0.5.5源码分析(6) 时间戳_第2张图片

上图中“A”箭头所指,正是“TIM8_UP_TIM13_IRQHandler”中断的时间间隔,于是“timestamp_”可以描述成最近一次“TIM8_UP_TIM13_IRQHandler”中断对应的时间戳,直到又一次中断发生,“timestamp_”才会被更新。

现在开始看函数“ControlLoop_IRQHandler”

523

524

525

void ControlLoop_IRQHandler(void) {

    COUNT_IRQ(ControlLoop_IRQn);

    uint32_t timestamp = timestamp_;

刚才已经分析过了“timestamp_”, 而“TIM8_UP_TIM13_IRQHandler”下溢出中断处理结束后马上就会调用“ControlLoop_IRQHandler”。

这里将“timestamp_”赋值给了临时变量“timestamp”,然后后面好几个地方都用这个临时变量的时间戳作为实参进行传递。

549

550

    motors[0].current_meas_cb(timestamp - TIM1_INIT_COUNT, current0);

    motors[1].current_meas_cb(timestamp, current1);

这个是电流测量回调,电流在什么时候被测量?正是“TIM8_UP_TIM13_IRQHandler”的update事件触发电流ADC,而M0是TIM1触发的,TIM1比TIM8要提早“TIM1_INIT_COUNT”个计数值。

从“current_meas_cb”调用开始追溯,最后会调用“FieldOrientedController::on_measurement”

 

这里要注意变量“i_timestamp_”,在后面函数中会被用到,函数“current_meas_cb()”被执行完后,这个“i_timestamp_”就对应着相电流被采集时的时间戳了。

继续看函数“ControlLoop_IRQHandler”:

553

566

567

570

571

    odrv.control_loop_cb(timestamp);

...

    motors[0].dc_calib_cb(timestamp + TIM_1_8_PERIOD_CLOCKS * (TIM_1_8_RCR + 1) - TIM1_INIT_COUNT, current0);

    motors[1].dc_calib_cb(timestamp + TIM_1_8_PERIOD_CLOCKS * (TIM_1_8_RCR + 1), current1);

    ...

    motors[0].pwm_update_cb(timestamp + 3 * TIM_1_8_PERIOD_CLOCKS * (TIM_1_8_RCR + 1) - TIM1_INIT_COUNT);

    motors[1].pwm_update_cb(timestamp + 3 * TIM_1_8_PERIOD_CLOCKS * (TIM_1_8_RCR + 1));

Ln553:

继续使用“TIM8_UP_TIM13_IRQHandler”中断时的时间戳,分析函数“control_loop_cb”,传入的“timestamp”作为了一系列update操作的“时间戳”,比如“电角度”phase等等数据的更新。

Ln566 ~ Ln567

程序走到此处时,已经又一次遇到了“TIM8_UP_TIM13_IRQHandler”之update中断,具体分析见《ODrive0.5.5源码分析(2) 时钟和定时器》中的分析。

Ln570 ~ Ln571

乍一看有点令人迷惑: “3* TIM_1_8_PERIOD_CLOCKS * (TIM_1_8_RCR + 1)”,为啥要用3来乘?其实看前面一些文章,我们就可以知道:FOC的计算周期正是:“3* TIM_1_8_PERIOD_CLOCKS * (TIM_1_8_RCR + 1)

这里从“pwm_update_cb”入手,先来看调用层次:

ODrive0.5.5源码分析(6) 时间戳_第3张图片

 

于是引出了“FieldOrientedController::get_alpha_beta_output()”,这是第2个我们需要重点分析的函数

66

67

68

69

70

71

72

73

74

75

76

90

91

92

97

98

99

100

161

162

163

164

165

ODriveIntf::MotorIntf::Error FieldOrientedController::get_alpha_beta_output(

        uint32_t output_timestamp, std::optional* mod_alpha_beta,

        std::optional<float>* ibus) {

    if (!vbus_voltage_measured_.has_value() || !Ialpha_beta_measured_.has_value()) {

        // FOC didn't receive a current measurement yet.

        return Motor::ERROR_CONTROLLER_INITIALIZING;

    } else if (abs((int32_t)(i_timestamp_ - ctrl_timestamp_)) > MAX_CONTROL_LOOP_UPDATE_TO_CURRENT_UPDATE_DELTA) {

        // Data from control loop and current measurement are too far apart.

        return Motor::ERROR_BAD_TIMING;

    }

...

...

auto [Vd, Vq] = *Vdq_setpoint_;

float phase = *phase_;

    float phase_vel = *phase_vel_;

...

...

    // Park transform

    if (Ialpha_beta_measured_.has_value()) {

        auto [Ialpha, Ibeta] = *Ialpha_beta_measured_;

        float I_phase = phase + phase_vel * ((float)(int32_t)(i_timestamp_ - ctrl_timestamp_) / (float)TIM_1_8_CLOCK_HZ);

        ...

} else {

    ...

    }

    // Inverse park transform

    float pwm_phase = phase + phase_vel * ((float)(int32_t)(output_timestamp - ctrl_timestamp_) / (float)TIM_1_8_CLOCK_HZ);

    float c_p = our_arm_cos_f32(pwm_phase);

float s_p = our_arm_sin_f32(pwm_phase);

...

...

}

Ln73:

我们来看“ctrl_timestamp_”,只有一个地方进行了更新:

     ODrive0.5.5源码分析(6) 时间戳_第4张图片

“control_loop_cb()”中进行了update,于是我们可以知道:正常情况下,在执行到Ln73的时候,i_timestamp_ ctrl_timestamp_是一样大小。实际上Odrive的设计,它认为“ctrl_timestamp_”是一个即时更新的量,它对应着“phase”计算出来的时间点,只不过目前代码中简化了,让它等于了“i_timestamp_”,它应该要比“i_timestamp_”时间戳晚一点。因为:先是采集到了电流(对应“i_timestamp_”),然后各种update获取phase等值(对应“ctrl_timestamp_”),而调用到Ln73这里的时候,是因为“pwm_update_cb”的调用引起的,时间又过去了一小会了。这个分析很重要,否则后面的Park转换时的phase的计算就会迷惑。

Ln100

“phase_”的update获取,在电流的获取时间后面,它对应着时间戳“ctrl_timestamp_”, 而电流的获取对应着时间戳“i_timestamp_”,那么我们计算的时候显然要获的在电流采集时这个“瞬间”的“电角度”,这个“电角度”显然比起“phase_”要前一些,再来看计算:

 

通过“i_timestamp_ - ctrl_timestamp_”正好向前推导出电流测量瞬间的“电角度”,虽然我们会发现实际运行中这两个值刚好相等,但是我们的软件具有在不相等时的处理能力!

Ln161

     当前获得的“phase_”对应着的时间戳是“ctrl_timestamp_”,那么计算“output_timestamp”这个未来时间戳处的“电角度”,计算公式就没啥疑问了。

现在开始回到前面说的问题:为啥是3倍的时间间隔问题!

我们先来画出大概的时间戳时序图:

ODrive0.5.5源码分析(6) 时间戳_第5张图片

在上图中,我们进行计算的大概位置是在“C”处,再下一次进行计算的大概位置就在“B”时间戳附近了,从“C”->“B”这一段时间都会用同样方向的“矢量力矩”进行电机驱动,这个“矢量”的方向,我们就选择了未来“B”处的矢量。现在再来看这个“3倍”,问题就很清晰明了了。当然,如果我们就用“C”处的矢量,行不行呢?肯定也是可以的,不过选择“未来的”量,比起一个即将过时的量,我觉得当然是目前软件中选择的好!!! 

你可能感兴趣的:(ODrvie,单片机,stm32,嵌入式硬件)