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%;③ 滤波后电流波形无明显开关毛刺。