4. 电流采样与重构
任务目标
本案例实现基于STM32的FOC控制器双电阻电流采样方案:在PWM周期内两个特定时刻分别采样A、B相电流,并通过基尔霍夫定律计算C相电流,最终输出三相对称电流值供后续Clark变换使用。
采样原理与关键时序
双电阻采样利用下桥臂串联采样电阻,在PWM周期内两个不同开关状态下采集电流:
- 采样点1:当A相下桥臂导通且B、C相上桥臂导通时(对应空间矢量000状态),采样A相电流
- 采样点2:当B相下桥臂导通且A、C相上桥臂导通时(对应空间矢量100状态),采样B相电流
- C相计算:
i_c = -(i_a + i_b)
两个采样点必须位于PWM周期内有效矢量区间,且避开开关死区与换相噪声。本实现使用中心对齐PWM,采样点配置在计数器上溢/下溢中断附近。
完整实现代码
// current_sampling.h
#ifndef CURRENT_SAMPLING_H
#define CURRENT_SAMPLING_H
#include <stdint.h>
typedef struct {
float i_a; // A相电流 (A)
float i_b; // B相电流 (A)
float i_c; // C相电流 (A)
} PhaseCurrents;
// 初始化ADC与采样定时器
void CurrentSampling_Init(void);
// 在PWM中断中调用,执行双电阻采样与重构
PhaseCurrents CurrentSampling_Update(void);
#endif
// current_sampling.c
#include "current_sampling.h"
#include "stm32f4xx_hal.h"
// 内部变量
static ADC_HandleTypeDef hadc1;
static TIM_HandleTypeDef htim1;
static uint16_t adc_buffer[2]; // 依次存储A相、B相采样值
static float v_offset = 1.65f; // 运放中点电压 (V)
static float gain = 5.0f; // 采样电路增益
static float r_shunt = 0.01f; // 采样电阻 (Ω)
// 采样时刻标志(由PWM中断置位)
volatile uint8_t sample_phase = 0; // 0=采样A相, 1=采样B相
void CurrentSampling_Init(void) {
// 配置ADC1: 注入组双通道采样
__HAL_RCC_ADC1_CLK_ENABLE();
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
HAL_ADC_Init(&hadc1);
// 注入通道: A相=ADC1_IN0(PA0), B相=ADC1_IN1(PA1)
ADC_InjectionConfTypeDef sConfigInjected = {0};
sConfigInjected.InjectedChannel = ADC_CHANNEL_0;
sConfigInjected.InjectedRank = 1;
sConfigInjected.InjectedNbrOfConversion = 2;
sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_84CYCLES;
sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNAL_TRIGINJECCONV_T1_CC4;
sConfigInjected.AutoInjectedConv = DISABLE;
sConfigInjected.InjectedDiscontinuousConvMode = DISABLE;
sConfigInjected.InjectedOffset = 0;
HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected, ADC_INJECTED_RANK_1);
sConfigInjected.InjectedChannel = ADC_CHANNEL_1;
sConfigInjected.InjectedRank = 2;
HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected, ADC_INJECTED_RANK_2);
// 配置PWM定时器TIM1: 中心对齐模式, 触发ADC注入采样
__HAL_RCC_TIM1_CLK_ENABLE();
htim1.Instance = TIM1;
htim1.Init.Prescaler = 84 - 1; // 84MHz/84 = 1MHz
htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;
htim1.Init.Period = 1000 - 1; // PWM频率 = 1MHz/1000 = 1kHz
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
HAL_TIM_PWM_Init(&htim1);
// 配置PWM通道: CH1=A相上桥, CH2=B相上桥, CH3=C相上桥
TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 初始占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2);
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3);
// 配置触发输出: TRGO2用于ADC注入触发
TIM_MasterConfigTypeDef sMasterConfig = {0};
sMasterConfig.MasterOutputTrigger = TIM_TRGO2_OC4REF;
sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_OC4REF;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
// 配置OC4: 用于产生ADC触发信号
sConfigOC.Pulse = 250; // 触发点1: 25%周期处
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_4);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
// 启动PWM与ADC
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
HAL_ADCEx_InjectedStart(&hadc1);
}
PhaseCurrents CurrentSampling_Update(void) {
PhaseCurrents currents;
uint32_t raw_a, raw_b;
float v_a, v_b;
// 读取注入组转换结果(按注入顺序)
raw_a = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_1);
raw_b = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_2);
// 转换为电压: V = raw/4096 * 3.3V
v_a = (float)raw_a * 3.3f / 4096.0f;
v_b = (float)raw_b * 3.3f / 4096.0f;
// 计算电流: I = (V - V_offset) / (gain * R_shunt)
currents.i_a = (v_a - v_offset) / (gain * r_shunt);
currents.i_b = (v_b - v_offset) / (gain * r_shunt);
// 重构C相电流
currents.i_c = -(currents.i_a + currents.i_b);
return currents;
}
// 在TIM1更新中断中调用,用于同步采样时刻
void TIM1_UP_TIM10_IRQHandler(void) {
if (__HAL_TIM_GET_FLAG(&htim1, TIM_FLAG_UPDATE) != RESET) {
__HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_UPDATE);
// 此处可添加采样完成后的处理逻辑
}
}
运行与验证
硬件连接:
- PA0 → A相下桥采样运放输出
- PA1 → B相下桥采样运放输出
- TIM1_CH1/CH2/CH3 → 三相逆变桥上桥驱动
- TIM1_CH4 → 用于产生ADC注入触发(内部连接)
验证步骤:
- 编译烧录后,在
CurrentSampling_Update()返回处设置断点 - 给电机施加一个固定负载(或用手缓慢转动电机轴)
- 观察
currents.i_a与currents.i_b是否随转子位置变化 - 验证
currents.i_c = -(currents.i_a + currents.i_b)是否始终成立(误差<5%) - 用示波器测量PA0/PA1波形,确认采样点位于PWM周期内有效区间
通过条件:三相电流之和始终接近零(|i_a+i_b+i_c| < 0.1A),且电流波形呈正弦变化(当电机匀速旋转时)。
关键配置说明
- 采样点位置由TIM1_CH4的脉冲值决定:当前配置在25%周期处触发第一次注入转换(A相),第二次注入转换在ADC注入序列中自动连续执行(B相),间隔约2μs(由ADC采样时间决定)
- 若PWM频率高于10kHz,需减小采样保持时间或调整触发点位置,确保两次采样均落在有效矢量区间内
- 运放偏移电压
v_offset需根据实际电路测量校准,否则直流偏置会导致电流计算误差