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注入触发(内部连接)

验证步骤:

  1. 编译烧录后,在CurrentSampling_Update()返回处设置断点
  2. 给电机施加一个固定负载(或用手缓慢转动电机轴)
  3. 观察currents.i_a与currents.i_b是否随转子位置变化
  4. 验证currents.i_c = -(currents.i_a + currents.i_b)是否始终成立(误差<5%)
  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需根据实际电路测量校准,否则直流偏置会导致电流计算误差