测量程序入口:
bool mcpwm_foc_measure_res_ind(float *res, float *ind) {
const float f_sw_old = m_conf->foc_f_sw;
const float kp_old = m_conf->foc_current_kp;
const float ki_old = m_conf->foc_current_ki;
m_conf->foc_f_sw = 10000.0;
m_conf->foc_current_kp = 0.001;
m_conf->foc_current_ki = 1.0;
uint32_t top = SYSTEM_CORE_CLOCKA / (int)m_conf->foc_f_sw;
TIMER_UPDATE_SAMP_TOP(MCPWM_FOC_CURRENT_SAMP_OFFSET, top);
float i_last = 0.0;
// 测量出一个大致的期望电流
// 这个期望电流大概是在1V的电压下,电机的电流(无反电动势)
for (float i = 2.0;i < (m_conf->l_current_max / 2.0);i *= 1.5) {
if (i > (1.0 / mcpwm_foc_measure_resistance(i, 20))) {
i_last = i;
break;
}
}
if (i_last < 0.01) {
i_last = (m_conf->l_current_max / 2.0);
}
#ifdef HW_AXIOM_FORCE_HIGH_CURRENT_MEASUREMENTS
i_last = (m_conf->l_current_max / 2.0);
#endif
// 用刚刚测量出的期望电流去测量电阻和电感,第二个参数是采样次数
*res = mcpwm_foc_measure_resistance(i_last, 200);
*ind = mcpwm_foc_measure_inductance_current(i_last, 200, 0);
m_conf->foc_f_sw = f_sw_old;
m_conf->foc_current_kp = kp_old;
m_conf->foc_current_ki = ki_old;
top = SYSTEM_CORE_CLOCK / (int)m_conf->foc_f_sw;
TIMER_UPDATE_SAMP_TOP(MCPWM_FOC_CURRENT_SAMP_OFFSET, top);
return true;
}
接下来,跳进主要函数mcpwm_foc_measure_inductance_current里看看:
float mcpwm_foc_measure_inductance_current(float curr_goal, int samples, float *curr) {
const float f_sw_old = m_conf->foc_f_sw;
m_conf->foc_f_sw = 3000.0; // 注意这里FOC的频率变成了3k
uint32_t top = SYSTEM_CORE_CLOCK / (int)m_conf->foc_f_sw;
TIMER_UPDATE_SAMP_TOP(MCPWM_FOC_CURRENT_SAMP_OFFSET, top);
float duty_last = 0.0;
// 注意这里的i 并不是指代电流,而是指占空比
// 这里占空比从2%开始递增,到50%结束,每次增大1.5倍
// 如果电机电流大于上面给的期望电流,那么就跳出,保存此时的占空比作为期望占空比
for (float i = 0.02;i < 0.5;i *= 1.5) {
float i_tmp;
mcpwm_foc_measure_inductance(i, 10, &i_tmp);
duty_last = i;
if (i_tmp >= curr_goal) {
break;
}
}
// 使用刚刚测量好的期望占空比来实际测量电感
// 再重复一下,在电机加上该占空比的电压,可以使电机电流大致等于期望电流
float ind = mcpwm_foc_measure_inductance(duty_last, samples, curr);
m_conf->foc_f_sw = f_sw_old;
top = SYSTEM_CORE_CLOCK / (int)m_conf->foc_f_sw;
TIMER_UPDATE_SAMP_TOP(MCPWM_FOC_CURRENT_SAMP_OFFSET, top);
return ind;
}
可以看到,电感值是在mcpwm_foc_measure_inductance中计算的,继续跳入
float mcpwm_foc_measure_inductance(float duty, int samples, float *curr) {
m_samples.avg_current_tot = 0.0;
m_samples.avg_voltage_tot = 0.0;
m_samples.sample_num = 0;
m_samples.measure_inductance_duty = duty;
// Disable timeout
systime_t tout = timeout_get_timeout_msec();
float tout_c = timeout_get_brake_current();
timeout_reset();
timeout_configure(60000, 0.0);
mc_interface_lock();
CURRENT_FILTER_OFF();
int to_cnt = 0;
for (int i = 0;i < samples;i++) {
m_samples.measure_inductance_now = true; // 这里只是设置了一下标志位,采样的地方不在这里
do {
chThdSleepMicroseconds(100); // 100us
to_cnt++;
if (to_cnt > 50000) { // 100us * 50000 = 5s
break;
}
} while (m_samples.measure_inductance_now); // 总采样时间如果超过5s,跳出
if (to_cnt > 50000) {
break;
}
}
CURRENT_FILTER_ON();
// Enable timeout
timeout_configure(tout, tout_c);
mc_interface_unlock();
float avg_current = m_samples.avg_current_tot / (float)m_samples.sample_num;
float avg_voltage = m_samples.avg_voltage_tot / (float)m_samples.sample_num;
float t = (float)TIM1->ARR * m_samples.measure_inductance_duty / (float)SYSTEM_CORE_CLOCK -
(float)(MCPWM_FOC_INDUCTANCE_SAMPLE_CNT_OFFSET + MCPWM_FOC_INDUCTANCE_SAMPLE_RISE_COMP) / (float)SYSTEM_CORE_CLOCK;
// ARR / CLOCK = T(周期,频率的倒数)
// 这里他减去了两个补偿时间,分别是MCPWM_FOC_INDUCTANCE_SAMPLE_CNT_OFFSET = 10
// MCPWM_FOC_INDUCTANCE_SAMPLE_RISE_COMP = 50
// 具体这两个时间是指什么下面再仔细讲
if (curr) {
*curr = avg_current;
}
return ((avg_voltage * t) / avg_current) * 1e6 * (2.0 / 3.0);
}
找到真实采样的地方,就是ADC的采样中断那里(mcpwm_foc_adc_int_handler):
整个函数太长了,截取重要的部分:
if (!m_samples.measure_inductance_now) { // 在非电感采样模式下
#ifdef HW_HAS_PHASE_SHUNTS
if (!m_conf->foc_sample_v0_v7 && is_v7) {
return;
}
#else
if (is_v7) { // 判断当前是上溢中断还是下溢中断,正常情况应该在上管关闭(下管导通)的时候采样
return; // 因此这里is_v7代表的情况是,上管导通,这里的v7的意思是指七步SVPWM中的第七状态
}
#endif
}
这一部分就在整个函数的入口处,主要就是判断一下当前是不是V7时间段,众所周知,一般情况下电流采样应该在V0时间段采样。而由于本杰明中配置定时器的中央对齐模式时,并没有配置重复计数器,因此每一个周期会有两次中断,分别是上溢中断和下溢中断。这个代码就是判断当前到底是处于哪个中断的,如果在非电感采样模式下,且处于V7(上溢中断),则直接返回。
if (m_samples.measure_inductance_now) {
if (!is_v7) { // is_v7 代表上管导通
return;
}
static int inductance_state = 0;
const uint32_t duty_cnt = (uint32_t)((float)TIM1->ARR * m_samples.measure_inductance_duty);
const uint32_t samp_time = duty_cnt - MCPWM_FOC_INDUCTANCE_SAMPLE_CNT_OFFSET;
// 注意这里更新了电流的采样点,将其往前挪了MCPWM_FOC_INDUCTANCE_SAMPLE_CNT_OFFSET个定时器的CLK
if (inductance_state == 0) {
TIMER_UPDATE_DUTY_SAMP(0, 0, 0, samp_time);
start_pwm_hw();
} else if (inductance_state == 2) {
TIMER_UPDATE_DUTY(duty_cnt, 0, duty_cnt); // 打开AC上桥,B下桥,导通时间duty_cnt
} else if (inductance_state == 3) {
m_samples.avg_current_tot += -((float)curr1 * FAC_CURRENT); // 测量A的电流
m_samples.avg_voltage_tot += GET_INPUT_VOLTAGE(); // 测量电源电压
m_samples.sample_num++;
TIMER_UPDATE_DUTY(0, 0, 0);
} else if (inductance_state == 5) {
TIMER_UPDATE_DUTY(0, duty_cnt, duty_cnt); // 打开BC上桥,A下桥,导通时间duty_cnt
} else if (inductance_state == 6) {
m_samples.avg_current_tot += -((float)curr0 * FAC_CURRENT); // 测量C电流
m_samples.avg_voltage_tot += GET_INPUT_VOLTAGE();
m_samples.sample_num++;
TIMER_UPDATE_DUTY(0, 0, 0);
} else if (inductance_state == 8) {
#ifdef HW_HAS_3_SHUNTS
TIMER_UPDATE_DUTY(duty_cnt, duty_cnt, 0);
#else
TIMER_UPDATE_DUTY(0, 0, duty_cnt); // 打开C上桥,AB下桥
#endif
} else if (inductance_state == 9) {
#ifdef HW_HAS_3_SHUNTS
m_samples.avg_current_tot += -((float)curr2 * FAC_CURRENT);
#else
m_samples.avg_current_tot += -((float)curr0 * FAC_CURRENT + (float)curr1 * FAC_CURRENT); // 测量B电流
#endif
m_samples.avg_voltage_tot += GET_INPUT_VOLTAGE();
m_samples.sample_num++;
stop_pwm_hw();
TIMER_UPDATE_SAMP(MCPWM_FOC_CURRENT_SAMP_OFFSET);
} else if (inductance_state == 10) {
inductance_state = 0;
m_samples.measure_inductance_now = false; // 一次循环结束,采样了三次
return;
}
inductance_state++;
return;
}
现在可以知道,VESC中测量电感的方式了:
利用公式:U=L*(di/dt) ,利用短脉冲电压,测量该电压下,电感的电流。需要注意的是,实际上电机线圈还有电阻,因此完整的公式应该是:
U=L(di/dt) + IR,电阻一般是mR级,再加上电流较小,因此本杰明直接忽略掉电阻的影响可能误差也不是很大。
再来讨论一下上面说的减去的补偿时间:
MCPWM_FOC_INDUCTANCE_SAMPLE_CNT_OFFSET(10) 和 MCPWM_FOC_INDUCTANCE_SAMPLE_RISE_COMP(50)
前者在设置电流采样点的时候和计算最终dt的时候都用到了,设置采样点的时候将其往前挪,我自己的推测是:
本杰明的电流采样通道优先级低于反电动势的采样通道,因此在采样电流前,需要先采样反电动势,这样会导致等电流开始采样的时候,已经过了一段时间,导致电流已经开始变小,从而影响电流的精度。
但这个推测的问题在于,如果要补偿的话,也应该补偿:(15+12)个ADCCLK,15是设置的ADC采样周期个数,12是ADC转换的周期个数,再加上本杰明设置的ADCCLK为42MHZ,而定时器频率为168MHZ,因此显然应该补偿(15+12)*4左右才对。
另一个补偿时间是MCPWM_FOC_INDUCTANCE_SAMPLE_RISE_COMP,因为其中带了个Rise,我猜测这是为了补偿MOS的打开延迟,包括STM32引脚拉高到DRV8302输出高,到MOS实际导通之间的延迟。这个时间可能是本杰明测出来的。