0%

FOC电流采样

采样点

在基尔霍夫定律下,三相电流的合应该等于0,因此只需要获取亮相电流就可以重构出完整的三相电流。

对于STM32一般通过高级定时器的channel4作为ADC的触发源,对于下桥臂电流采样,需要在下桥臂MOS管导筒的时候才能去采样,而且需要在MOS管导通时间以及电流稳定后才能采样到比较可靠的电流。

  • \(\Delta_1\) 大于两倍的 $T_{dead} + T_{on} + T_{ring} + T_{conv} $ 那么在PWM Freq Middle 时刻出发ADC转换也是足够转换3相中任意的一相
  • \(\Delta_1\) 大于$T_{dead} + T_{on} + T_{ring} + T_{conv} 那么从 CCRmax之后开始转换也是可以转换3相中任意的一相
  • \(\Delta_2\) > \(\Delta_0\) > \(\Delta_1\) 那么导通最短的那一相就没有足够的转换时间了,那么只能转换导通时间长的那两相
  • \(\Delta_1\) < \(\Delta_0\)\(\Delta_2\) < \(\Delta_0\) 没有足够的转换时间,有两相电流不可获取将无法重建三相电流

因此需要根据u、v、w三相pwm的占空比来调整timer的channel 4比较值来调整触发时刻完成三相电流的重建工作。

相电流重构

  • 对于Sector4和5,C相导通时间最长,相应的就是下桥臂导通时间最短,这个时候应该采集A和B相电流
  • 对于Sector2和3,B相导通时间最长,相应的就是下桥臂导通时间最短,这个时候应该采集A和C相电流
  • 对于Sector1和6,A相导通时间最长,相应的就是下桥臂导通时间最短,这个时候应该采集B和C相电流

常用芯片

MP6540电流采样

MP6540H 的内部电流采样电路。其输出可通过外部电阻器 (RTERM) 和参考电压 (VREF) 来设置。两个等值电阻连接到 ADC 电源和地,接地用于终止输出。

MP6540 的电流采样计算比较简单,这里就不再详细说明了。

DRV8301/DRV8302 电流采样

首先将DRV8301的框图列出来,大致了解下结构,主要就是通过SNx和SPx四个引脚之间的电阻来采样电流。

如果我们需要采样一个桥臂的电流,那传统的方式自然是直接在电路上串联一个采样电阻,然后采样两端电压即可。由于电阻是串联进电路的,所以阻值当然是要越低越好。但是这又会带来另一个问题,低阻值导致的采样精度不高,因此需要运放来实现电流的采样。

我们以odriver原理图为例进行计算,如下:

这里R27和R28电阻就是采样电阻,\(500u\Omega(500 * 10^{-6})\Omega\).

下面是官方手册给出的电流计算方式。

这里做如下说明: \(SN_x - SN_x\) 是采样电阻两端的电压值,也是我们需要测量的值,得到该值后除以电阻就得到对应的电流值。Vo是我们ADC得到的值。针对官方给出的公式,我们做如下计算:

\[ V_o = \frac{V_{REF}}{2} - G \times (SN_x - SP_x) \]

化简如下:

\[ (SN_x - SP_x) = \frac{(\frac{V_{REF}}{2} - V_o )}{G} \]

这里G是10、20、40、80. \(V_{REF}\)一般是3.3v,那么如下:

\[ (\frac{V_{REF}}{2} - V_o ) = (\frac{3.3}{2} - \frac{ADC}{2^{12}} \times 3.3 ) = \frac{2^{11} - ADC}{2^{12}} \times 3.3 \]

得到电流值如下:

\[ (SN_x - SP_x) = I_{measure} \times R_{Isensor} = \frac{2^{11} - ADC}{2^{12}} \times \frac{3.3}{G} \]

得到最终结果为:

\[ I_{measure} = \frac{2^{11} - ADC}{2^{12}} \times \frac{3.3}{G} \times \frac{1}{R_{Isensor}} \]

死区时间的计算

我们用下列公式计算控制死区时间:

\[ t_{dead} = [(td_{offmax} - td_{onmin}) + (tpdd_{max} - tpdd_{min})] * 1.2 \]

  • \(td_{offmax}\) :MOS管最大关断延迟时间
  • \(td_{onmin}\):MOS管最小开通延迟时间
  • \(tpdd_{max}\):驱动器最大传输延迟时间
  • \(tpdd_{min}\):驱动器最小传输延迟时间

在该公式中,第一项\(td_{offmax}-td_{onmin}\)为最大关断延迟时间和最小开通延迟时间之差。这一项主要描述MOS管器件结合所用的门极电阻的特性。由于上升和下降时间通常比延迟时间短很多,这里就不考虑它们。另一项\(tpdd_{max} - tpdd_{min}\)为由驱动器决定的传输延迟时间之差(延迟时间不匹配)。该参数通常可在驱动器制造商提供的驱动器数据表中查找到。对于基于光耦合器的驱动器,该参数值通常很大。

对于csd18540q:

\(td_{offmax}\) = 20ns \(td_{onmin}\) = 6ns

STM32实现双ADC采样三电阻电流

重构采样点

STM32的高级定时器有6个pwm channel,我们可以使用前三个channel输出互补的pwm,第四个channel用于触发adc的采样时刻。这里主要是根据扇区以及三通道的pwm占空比来判断电流采样点时刻。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* @brief Configure the ADC for the current sampling related to sector 1.
* It means set the sampling point via TIMx_Ch4 value and polarity
* ADC sequence length and channels.
* And call the WriteTIMRegisters method.
* @retval none
*/
uint16_t R3_2_SetADCSampPointSectX(void)
{
uint16_t sampling_point;
uint16_t DeltaDuty;

/* 通过检查最小占空比验证是否可以在 PWM 中间进行采样 */
if ((uint16_t)(kMCLowLevelInstance.half_pwm_period - kMCLowLevelInstance.low_duty) > kMCLowLevelInstance.t_after) {
/* 当可以在 PWM 周期的中间进行采样时,始终对所有扇区的相同相位(选择 AB)进行采样,以便在偏移量之间存在差异时不会引起电流不连续 */

/* GetPhaseCurrent 需要的扇区号,采样A相和B相对应扇区4或5 */
kMCLowLevelInstance.sector = SECTOR_5;

/* 在 PWM 周期中间设置采样点触发器 */
sampling_point = kMCLowLevelInstance.half_pwm_period - (uint16_t)1;
} else {
/* 在这种情况下,有必要转换具有最大和可变互补占空比的相位.*/
DeltaDuty = (uint16_t)(kMCLowLevelInstance.low_duty - kMCLowLevelInstance.mid_duty);

/* Definition of crossing point */
if (DeltaDuty > (uint16_t)(kMCLowLevelInstance.half_pwm_period - kMCLowLevelInstance.low_duty) * 2u) {
sampling_point = kMCLowLevelInstance.low_duty - kMCLowLevelInstance.t_before;
} else {
sampling_point = kMCLowLevelInstance.low_duty + kMCLowLevelInstance.t_after;

if (sampling_point >= kMCLowLevelInstance.half_pwm_period) {
/* ADC trigger edge must be changed from positive to negative */
kMCLowLevelInstance.adc_external_polarity_injected = (uint16_t)LL_ADC_INJ_TRIG_EXT_FALLING;
sampling_point = (2u * kMCLowLevelInstance.half_pwm_period) - sampling_point - (uint16_t)1;
}
}
}
return R3_2_WriteTIMRegisters(sampling_point);
}

重构电流采样

我们可以根据当前扇区以及三通道的pwm占空比来选择采样哪两相电流,通常不采样占空比最小的那一通道,而通过基尔霍夫定律来算第三通道的电流,避免采样区间太小造成的采样电流不稳定。具体算法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* @brief It computes and return latest converted motor phase currents motor
* @param pHdl: handler of the current instance of the PWM component
* @retval Ia and Ib current in Curr_Components format
*/
void R3_2_PhaseCurrentsUpdate(void)
{
int16_t i_a,i_b;
int32_t aux;
uint32_t adc_data_reg1, adc_data_reg2;

uint8_t sector = (uint8_t)kMCLowLevelInstance.sector;
TIM_TypeDef* TIMx = kMCLowLevelInstance.TIMx;
adc_data_reg1 = *kMCLowLevelInstance.adc_data_reg1[sector];
adc_data_reg2 = *kMCLowLevelInstance.adc_data_reg2[sector];

/* disable ADC trigger source */
// LL_TIM_CC_DisableChannel(TIMx, LL_TIM_CHANNEL_CH4);
LL_TIM_SetTriggerOutput(TIMx, TIM_TRGO_RESET);

switch (sector) {
case SECTOR_4:
case SECTOR_5:
/* Current on Phase C is not accessible */
/* Ia = phase_a_offset - ADC converted value) */
aux = (int32_t)(kMCLowLevelInstance.phase_a_offset) - (int32_t)(adc_data_reg1);

/* Saturation of Ia */
if (aux < -INT16_MAX) {
i_a = -INT16_MAX;
} else if (aux > INT16_MAX) {
i_a = INT16_MAX;
} else {
i_a = (int16_t)aux;
}

/* Ib = phase_b_offset - ADC converted value) */
aux = (int32_t)(kMCLowLevelInstance.phase_b_offset) - (int32_t)(adc_data_reg2);

/* Saturation of Ib */
if (aux < -INT16_MAX) {
i_b = -INT16_MAX;
} else if (aux > INT16_MAX) {
i_b = INT16_MAX;
} else {
i_b = (int16_t)aux;
}
break;

case SECTOR_6:
case SECTOR_1:
/* Current on Phase A is not accessible */
/* Ib = phase_b_offset - ADC converted value) */
aux = (int32_t)(kMCLowLevelInstance.phase_b_offset) - (int32_t)(adc_data_reg1);

/* Saturation of Ib */
if (aux < -INT16_MAX) {
i_b = -INT16_MAX;
} else if (aux > INT16_MAX) {
i_b = INT16_MAX;
} else {
i_b = (int16_t)aux;
}

/* Ia = -Ic -Ib */
aux = (int32_t)(adc_data_reg2) - (int32_t)(kMCLowLevelInstance.phase_c_offset); /* -Ic */
aux -= (int32_t)i_b; /* Ia */

/* Saturation of Ia */
if (aux > INT16_MAX) {
i_a = INT16_MAX;
} else if (aux < -INT16_MAX) {
i_a = -INT16_MAX;
} else {
i_a = (int16_t)aux;
}
break;

case SECTOR_2:
case SECTOR_3:
/* Current on Phase B is not accessible */
/* Ia = phase_a_offset - ADC converted value) */
aux = (int32_t)(kMCLowLevelInstance.phase_a_offset) - (int32_t)(adc_data_reg1);

/* Saturation of Ia */
if (aux < -INT16_MAX) {
i_a = -INT16_MAX;
} else if (aux > INT16_MAX) {
i_a = INT16_MAX;
} else {
i_a = (int16_t)aux;
}

/* Ib = -Ic -Ia */
aux = (int32_t)(adc_data_reg2) - (int32_t)(kMCLowLevelInstance.phase_c_offset); /* -Ic */
aux -= (int32_t)i_a; /* Ib */

/* Saturation of Ib */
if (aux > INT16_MAX) {
i_b = INT16_MAX;
} else if (aux < -INT16_MAX) {
i_b = -INT16_MAX;
} else {
i_b = (int16_t)aux;
}
break;

default:
break;
}
kMCLowLevelInstance.current_a = i_a;
kMCLowLevelInstance.current_b = i_b;
}

...
static MotorControlLowLevel_t kMCLowLevelInstance = {
.adc_config1 = {MC_ADC_CHANNEL_2 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT),
MC_ADC_CHANNEL_1 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT),
MC_ADC_CHANNEL_1 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT),
MC_ADC_CHANNEL_1 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT),
MC_ADC_CHANNEL_1 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT),
MC_ADC_CHANNEL_2 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT)},
.adc_config2 = {MC_ADC_CHANNEL_12 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT),
MC_ADC_CHANNEL_12 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT),
MC_ADC_CHANNEL_12 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT),
MC_ADC_CHANNEL_2 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT),
MC_ADC_CHANNEL_2 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT),
MC_ADC_CHANNEL_12 << ADC_JSQR_JSQ1_Pos | (LL_ADC_INJ_TRIG_EXT_TIM1_TRGO & ~ADC_INJ_TRIG_EXT_EDGE_DEFAULT)},
...
};

注意这里sector 1的采样通道是ADC1的MC_ADC_CHANNEL_2和ADC2的MC_ADC_CHANNEL_12,sector2的采样通道是ADC1的MC_ADC_CHANNEL_1和ADC2的MC_ADC_CHANNEL_12。这个表格是根据前面的理论部分以及原理图来计算得到的通道。

在得到原始的ADC值之后可以通过电路原理将ADC转换为对应的电流值,这个就跟具体的设计有关了,这里不过多赘述。

参考文献

https://www.monolithicpower.cn/cn/designing-a-brushless-motor-driver-circuit-with-the-mp6540
https://www.ti.com/lit/ds/symlink/drv8301.pdf?ts=1664978601316&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252FDRV8301