3. 实现电机相电流采样

任务目标

本案例实现BLDC电机驱动中的三路相电流采样:配置MCU的ADC模块采集三相低边采样电阻上的电压,经过标定(偏置校准与灵敏度换算)和滤波处理后,输出实际的相电流值(单位:A)。

硬件与依赖

  • MCU:STM32F405(12位ADC,3个采样通道)
  • 采样方式:三路低边采样电阻(10mΩ),经运放放大后接入ADC引脚
  • 运放增益:20倍(由硬件决定,代码中作为常量配置)
  • ADC参考电压:3.3V

实现原理

相电流采样链路为:电机相电流 → 采样电阻(10mΩ)→ 差分运放(20倍)→ ADC引脚。ADC采集到的原始数字量需经过两级处理:标定(减去零电流偏置,再乘以电流换算系数)和滤波(一阶低通,抑制开关噪声)。

关键完整代码

// ============ current_sense.h ============
#ifndef CURRENT_SENSE_H
#define CURRENT_SENSE_H

#include <stdint.h>

#define ADC_CHANNEL_COUNT  3
#define ADC_REF_VOLTAGE    3.3f
#define ADC_RESOLUTION     4095.0f          // 12位
#define SHUNT_RESISTOR     0.01f            // 10mΩ
#define OP_AMP_GAIN        20.0f            // 硬件放大倍数
#define SAMPLE_BUFFER_SIZE 8                // 滑动滤波窗口

// 电流换算系数: (V_ref / ADC_res) / (R_shunt * Gain)
#define CURRENT_SCALE      (ADC_REF_VOLTAGE / ADC_RESOLUTION / (SHUNT_RESISTOR * OP_AMP_GAIN))

typedef struct {
    float offset[ADC_CHANNEL_COUNT];        // 零电流偏置(ADC原始值)
    float filtered_raw[ADC_CHANNEL_COUNT];  // 滤波后的ADC原始值
    float current[ADC_CHANNEL_COUNT];       // 最终相电流(A)
    uint16_t buffer[ADC_CHANNEL_COUNT][SAMPLE_BUFFER_SIZE];
    uint8_t buf_index;
} CurrentSense_t;

void CurrentSense_Init(CurrentSense_t *cs);
void CurrentSense_CalibrateOffset(CurrentSense_t *cs);
void CurrentSense_Update(CurrentSense_t *cs, uint16_t *adc_raw);

#endif
// ============ current_sense.c ============
#include "current_sense.h"

// 初始化:清零状态
void CurrentSense_Init(CurrentSense_t *cs) {
    for (int i = 0; i < ADC_CHANNEL_COUNT; i++) {
        cs->offset[i] = 0.0f;
        cs->filtered_raw[i] = 0.0f;
        cs->current[i] = 0.0f;
        for (int j = 0; j < SAMPLE_BUFFER_SIZE; j++) {
            cs->buffer[i][j] = 0;
        }
    }
    cs->buf_index = 0;
}

// 标定:电机静止时采集N次取平均,作为零电流偏置
void CurrentSense_CalibrateOffset(CurrentSense_t *cs) {
    const int CAL_SAMPLES = 100;
    uint32_t sum[ADC_CHANNEL_COUNT] = {0, 0, 0};

    for (int n = 0; n < CAL_SAMPLES; n++) {
        // 实际项目中此处调用ADC_GetRawValue(ch)获取各通道原始值
        // 此处用模拟输入演示逻辑(真实运行时替换为ADC读取)
        uint16_t raw[ADC_CHANNEL_COUNT];
        for (int ch = 0; ch < ADC_CHANNEL_COUNT; ch++) {
            raw[ch] = 2048;  // 假设零电流时ADC读数约2048(1.65V中点)
        }
        for (int ch = 0; ch < ADC_CHANNEL_COUNT; ch++) {
            sum[ch] += raw[ch];
        }
    }
    for (int ch = 0; ch < ADC_CHANNEL_COUNT; ch++) {
        cs->offset[ch] = (float)sum[ch] / CAL_SAMPLES;
        cs->filtered_raw[ch] = cs->offset[ch];
    }
}

// 更新:滑动平均滤波 + 标定换算
void CurrentSense_Update(CurrentSense_t *cs, uint16_t *adc_raw) {
    // 1. 写入滑动窗口
    for (int ch = 0; ch < ADC_CHANNEL_COUNT; ch++) {
        cs->buffer[ch][cs->buf_index] = adc_raw[ch];
    }
    cs->buf_index = (cs->buf_index + 1) % SAMPLE_BUFFER_SIZE;

    // 2. 滑动平均滤波
    for (int ch = 0; ch < ADC_CHANNEL_COUNT; ch++) {
        uint32_t sum = 0;
        for (int j = 0; j < SAMPLE_BUFFER_SIZE; j++) {
            sum += cs->buffer[ch][j];
        }
        cs->filtered_raw[ch] = (float)sum / SAMPLE_BUFFER_SIZE;

        // 3. 标定:减去偏置,乘以换算系数得到电流(A)
        float raw_diff = cs->filtered_raw[ch] - cs->offset[ch];
        cs->current[ch] = raw_diff * CURRENT_SCALE;
    }
}

ADC配置与调用示例

// ============ ADC初始化(仅关键配置) ============
void ADC_Init(void) {
    // 使能GPIOA时钟,配置PA0-PA2为模拟输入
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    GPIOA->MODER |= (3U << 0) | (3U << 2) | (3U << 4);  // PA0,PA1,PA2模拟模式

    // 使能ADC1时钟
    RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
    ADC1->CR2 = 0;                         // 关闭ADC
    ADC1->SQR3 = (0 << 0) | (1 << 5) | (2 << 10);  // 通道0,1,2
    ADC1->SQR1 = (2 << 20);                // 3个转换
    ADC1->CR2 |= ADC_CR2_ADON;             // 开启ADC
}

// ============ 主循环调用 ============
int main(void) {
    CurrentSense_t cs;
    CurrentSense_Init(&cs);
    ADC_Init();

    // 上电标定(电机静止)
    CurrentSense_CalibrateOffset(&cs);

    while (1) {
        uint16_t raw[3];
        // 启动ADC转换并读取三通道结果(实际项目实现)
        // raw[0] = ADC_Read(0); raw[1] = ADC_Read(1); raw[2] = ADC_Read(2);

        CurrentSense_Update(&cs, raw);
        // 使用 cs.current[0], cs.current[1], cs.current[2] 作为相电流反馈
    }
}

运行与验证

  • 编译:使用arm-none-eabi-gcc编译,链接STM32F405标准外设库或HAL库。
  • 标定验证:电机静止时调用CurrentSense_CalibrateOffset(),此时cs.current[]应接近0A(误差<0.05A)。
  • 动态验证:给电机施加已知直流电流(如1A),观察cs.current[]输出值应在0.95~1.05A范围内。
  • 滤波效果:在PWM开关频率下,对比滤波前后的ADC原始值波动,滤波后波动应明显减小。

判定标准

满足以下条件即通过:① 零电流时三路输出均在±0.05A以内;② 施加1A直流电流时,输出误差不超过±5%;③ 滤波后电流波形无明显开关毛刺。