一、为什么要检测电流
电流 = 电机的"力量"表征
核心作用:
1.力矩控制直接反馈
-电流α输出扭矩
精准的力控制基础
2.系统保护与诊断
-过流保护:防止电机和驱动器损坏
-堵转检测:异常状态判断
3.能效优化
实时功率计算:P=V×I
优化控制策略,提高效率
应用场景:
过流保护 (防止烧毁MOS管或电机)
闭环控制(如FOC、PID调速、堵转检测)
能耗分析与效率优化
关键问题:电机电流直接反映了负载大小和运行状态。

二、电流检测方法
1.采样电阻法(本实验使用的方法)
-优点:成本低、米精度高响应快
-缺点:有功率损耗-适用:中小功率电机
2.霍尔电流传感器
-优点:隔离测量、无损耗
-缺点:成本高、有温漂
-适用:大功率、高压系统
3.电流互感器
-优点:完全隔离、耐高压
-缺点:仅适用交流
适用:工频交流系统

主要用的是采样电阻法:
流过电机的电流又通过R32(串联)流入GND
检测R32上流过的电流就是流过电机的电流
通过检测R32上的电压推算电流
R32上的电压太小,需要放大之后才能检测到
采样电阻的选型:
设计参数 :
最大电流:I_max= 10 A
目标压降:V_sense = 0.2 V(ADC测量范围优化,兼顾精度与噪声抑制)
1.电阻值计算根据欧姆定律:

2.最大功耗计算根据焦耳定律:

实际选型需预留安全余量(推荐≥3×功率裕量) 让电阻的标称额定功率 >= 实际功耗的3倍
放大电路设计:

为什么需要放大?
20mΩ电阻上流过10A电流→压降=0.2V
单片机ADC通常量程是0~3.3V→0.2V只占满量程的6%
如果ADC是12-bit→总共4096个点→0.2V对应约250个点
这意味着:每1mA电流变化,ADC值只变0.025个点!根本无法分辨!

R29=100Ω 作用:限流电阻,防止后级电路(如MCUADC)短路时烧毁运放
C45=10uF 作用:滤波电容,吸收高频噪声(特别是PWM开关噪声)位置:靠近运放输出端,形成"本地储能",稳定电压
D18(1N4148)+VD5(PESD3V3L1BA-N)
作用:钳位二极管+ESD保护二极管
工作原理:当CURT输出电压>3.3V+0.7V(二极管导通压降)→D18导通,把多余电压拉到
VCC3.3当CURT输出电压<OV→VD5导通,把负电压拉到GND
目的:保护MCU的ADC引I脚不被过压或负压损坏!
ADC的配置:

关键配置参数:
1采样时钟:确保转换速度
2采样时间:根据信号源阻抗调整
3.触发方式:软件触发/定时器触发
4数据对齐:右对齐便于计算
5.校准:启用内部校准提高精度
步骤:
1.初始化ADC参数
2配置规则通道
3启用DMA传输(多通道时)
4执行校准
5启动转换
三、电流采样问题
1.如果采样电阻的阻值选择过大,会有什么问题?
①功率损耗大:电阻发热严重,效率降低
②影响电机性能:过大压降导致电机供电电压不足。
③发热与稳定性:温升引起阻值漂移,测量不准。
2.如何在不增加硬件成本的情况下提高电流测量精度?
①软件校准:进行零点和增益校准。
②数字滤波:使用滑动平均或低通滤波平滑数据。
③过采样技术:通过多次采样提高分辨率
3.过流保护为什么要设置硬件和软件两级保护?
①硬件保护:响应快(微秒级),防止瞬时过流损坏器件。
②软件保护:处理复杂逻辑,实现分级限流和恢复。
③冗余安全:确保硬件失效时软件仍能提供保护。
四、具体代码的实现

cs
#define ADC_CHANNEL_NUM 2 // 单通道(电流传感器)
#define ADC_SAMPLE_COUNT 500 // 单次滤波的采样次数(可根据需求调整)
#define ADC_TOTAL_SAMPLES (ADC_CHANNEL_NUM * ADC_SAMPLE_COUNT) // 总采样数
#include "adc.h"
/**
* @brief ADC原始采样数据数组
* @note 存储ADC DMA采集的原始数据
* 数组大小 = ADC_SAMPLE_COUNT × ADC_CHANNEL_NUM
* 存储格式:[CH8_采样0, CH9_采样0, CH8_采样1, CH9_采样1, ...]
* 例:ADC_SAMPLE_COUNT=10, ADC_CHANNEL_NUM=2 → 20个元素
*/
uint16_t adc_raw_data[ADC_TOTAL_SAMPLES] = {0};
/**
* @brief ADC滤波后的数据数组(浮点型)
* @note 存储各通道的滤波平均值
* filtered_data[0] = 通道8的平均值(电流检测)
* filtered_data[1] = 通道9的平均值(电压检测)
* 使用浮点数保留小数精度
*/
float adc_filtered_data[ADC_CHANNEL_NUM] = {0.0f};
/**
* @brief ADC句柄(全局结构体)
* @note 包含ADC的所有配置信息
* 由HAL库管理
*/
ADC_HandleTypeDef hadc1;
/**
* @brief DMA句柄(全局结构体)
* @note 包含DMA的所有配置信息
* 用于ADC的DMA传输
*/
DMA_HandleTypeDef hdma_adc1;
/* ==================== ADC1初始化函数 ==================== */
/**
* @brief ADC1初始化函数
* @note 由CubeMX生成,配置ADC的基本参数
* 包括:时钟、分辨率、扫描模式、DMA等
*/
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0}; // 通道配置结构体
/**
* @brief ADC全局配置
* @note hadc1.Instance = ADC1: 使用ADC1外设
* ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4: ADC时钟 = PCLK/4
* Resolution = ADC_RESOLUTION_12B: 12位分辨率(0-4095)
* ScanConvMode = ENABLE: 使能扫描模式(多通道依次转换)
* ContinuousConvMode = ENABLE: 连续转换模式(不停采集)
* DiscontinuousConvMode = DISABLE: 禁用非连续模式
* ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE: 软件触发
* ExternalTrigConv = ADC_SOFTWARE_START: 软件启动转换
* DataAlign = ADC_DATAALIGN_RIGHT: 数据右对齐
* NbrOfConversion = 2: 转换通道数量(2个通道)
* DMAContinuousRequests = ENABLE: DMA连续请求使能
* EOCSelection = ADC_EOC_SINGLE_CONV: 单次转换结束标志
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 2;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
/**
* @brief HAL_ADC_Init() 初始化ADC
* @note 实际执行硬件寄存器配置
* 包括:时钟使能、GPIO配置、DMA配置(在MspInit中)
*/
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler(); // 初始化失败则进入错误处理
}
/**
* @brief 配置ADC通道
* @note 这里配置两个通道:CH8(PB0)和CH9(PB1)
* 每个通道配置其扫描顺序和采样时间
*/
// ========== 通道8配置(电流检测) ==========
sConfig.Channel = ADC_CHANNEL_8; // ADC通道8(对应PB0)
sConfig.Rank = 1; // 扫描序列第1位
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES; // 采样时间480周期
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
// ========== 通道9配置(电压检测) ==========
sConfig.Channel = ADC_CHANNEL_9; // ADC通道9(对应PB1)
sConfig.Rank = 2; // 扫描序列第2位
// sConfig.SamplingTime 保留上一次配置的值
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
/* ==================== ADC硬件初始化(MspInit) ==================== */
/**
* @brief ADC硬件初始化回调函数
* @param adcHandle ADC句柄指针
* @note 在HAL_ADC_Init()中被调用
* 负责:时钟使能、GPIO配置、DMA配置、中断配置
* "Msp" = MCU Specific Package(芯片特定配置)
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0}; // GPIO配置结构体
// 判断是否为ADC1
if(adcHandle->Instance == ADC1)
{
// ========== 1. 使能ADC时钟 ==========
__HAL_RCC_ADC1_CLK_ENABLE();
// ========== 2. 使能GPIOB时钟 ==========
__HAL_RCC_GPIOB_CLK_ENABLE();
/**
* ========== 3. 配置ADC引脚为模拟输入模式 ==========
* PB0 → ADC1_IN8(电流检测)
* PB1 → ADC1_IN9(电压检测)
* GPIO_MODE_ANALOG: 模拟模式,禁用数字功能
* GPIO_NOPULL: 无上下拉电阻
*/
GPIO_InitStruct.Pin = PM1_AMPU_Pin | PM1_VBUS_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// ========== 4. 配置DMA ==========
/**
* @brief DMA2_Stream0配置
* @note STM32F4的DMA2拥有8个数据流(Stream0-7)
* 每个数据流可连接不同的外设请求
*
* Channel = DMA_CHANNEL_0: DMA通道0(ADC1的DMA请求通道)
* Direction = DMA_PERIPH_TO_MEMORY: 外设→内存
* PeriphInc = DMA_PINC_DISABLE: 外设地址不递增(固定读取ADC_DR)
* MemInc = DMA_MINC_ENABLE: 内存地址递增(依次存入数组)
* PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD: 外设数据半字(16位)
* MemDataAlignment = DMA_MDATAALIGN_HALFWORD: 内存数据半字(16位)
* Mode = DMA_CIRCULAR: 循环模式(不断传输)
* Priority = DMA_PRIORITY_MEDIUM: DMA优先级中等
* FIFOMode = DMA_FIFOMODE_DISABLE: 禁用FIFO(直接模式)
*/
hdma_adc1.Instance = DMA2_Stream0;
hdma_adc1.Init.Channel = DMA_CHANNEL_0;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_MEDIUM;
hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
// 初始化DMA
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
Error_Handler();
}
/**
* @brief 将DMA句柄与ADC句柄关联
* @note __HAL_LINKDMA 是宏定义
* 将 adcHandle->DMA_Handle 指向 hdma_adc1
* 这样ADC就知道使用哪个DMA传输
*/
__HAL_LINKDMA(adcHandle, DMA_Handle, hdma_adc1);
// ========== 5. 配置ADC中断 ==========
/**
* @brief 配置ADC中断优先级
* @note ADC_IRQn: ADC中断号
* 优先级2:数字越小优先级越高
* 子优先级0
*/
HAL_NVIC_SetPriority(ADC_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(ADC_IRQn); // 使能ADC中断
/* USER CODE BEGIN ADC1_MspInit 1 */
/* USER CODE END ADC1_MspInit 1 */
}
}
/* ==================== ADC硬件去初始化(MspDeInit) ==================== */
/**
* @brief ADC硬件去初始化回调函数
* @param adcHandle ADC句柄指针
* @note 在HAL_ADC_DeInit()中被调用
* 负责:时钟关闭、GPIO释放、DMA释放、中断关闭
*/
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if(adcHandle->Instance == ADC1)
{
/* USER CODE BEGIN ADC1_MspDeInit 0 */
/* USER CODE END ADC1_MspDeInit 0 */
// ========== 1. 关闭ADC时钟 ==========
__HAL_RCC_ADC1_CLK_DISABLE();
// ========== 2. 释放GPIO配置 ==========
HAL_GPIO_DeInit(GPIOB, PM1_AMPU_Pin | PM1_VBUS_Pin);
// ========== 3. 释放DMA ==========
HAL_DMA_DeInit(adcHandle->DMA_Handle);
// ========== 4. 关闭中断 ==========
HAL_NVIC_DisableIRQ(ADC_IRQn);
/* USER CODE BEGIN ADC1_MspDeInit 1 */
/* USER CODE END ADC1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/**
* @brief 计算ADC平均值(滤波处理)
* @param filtered_data: 滤波后的值存储数组(浮点型)
* @note 对单通道连续采样值取平均,保留小数精度
*
* @details 数据存储格式:
* adc_raw_data[0] = CH8采样0
* adc_raw_data[1] = CH9采样0
* adc_raw_data[2] = CH8采样1
* adc_raw_data[3] = CH9采样1
* ...
* adc_raw_data[18] = CH8采样9
* adc_raw_data[19] = CH9采样9
*
* 计算方式:
* CH8平均值 = (采样0 + 采样1 + ... + 采样9) / 10
* CH9平均值 = (采样0 + 采样1 + ... + 采样9) / 10
*/
void adc_calculate_filtered(float *filtered_data)
{
uint32_t sum_ch8 = 0; // 通道8采样值累加和
uint32_t sum_ch9 = 0; // 通道9采样值累加和
int i;
/**
* @brief 遍历所有采样点
* @note adc_raw_data[i * ADC_CHANNEL_NUM] → 通道8数据
* adc_raw_data[i * ADC_CHANNEL_NUM + 1] → 通道9数据
* 每次循环处理一对采样值
*/
for (i = 0; i < ADC_SAMPLE_COUNT; i++)
{
sum_ch8 += adc_raw_data[i * ADC_CHANNEL_NUM]; // 累加通道8
sum_ch9 += adc_raw_data[i * ADC_CHANNEL_NUM + 1]; // 累加通道9
}
/**
* @brief 计算浮点数平均值
* @note (float)sum_ch8 将uint32_t转为浮点数
* 浮点除法保留小数部分(核心优化点)
* 例:sum=4095, count=10 → 409.5(保留.5)
*/
filtered_data[0] = (float)sum_ch8 / ADC_SAMPLE_COUNT; // 通道8平均值
filtered_data[1] = (float)sum_ch9 / ADC_SAMPLE_COUNT; // 通道9平均值
}
/**
* @brief ADC转换完成回调函数
* @param hadc ADC句柄指针
* @note 在ADC转换完成且DMA传输完成后触发
* 执行滤波计算,更新滤波后的数据
*
* @details 工作流程:
* 1. ADC连续采集 → DMA将数据搬运到adc_raw_data
* 2. 每次完成ADC_SAMPLE_COUNT次采样后触发回调
* 3. 执行adc_calculate_filtered()计算平均值
* 4. 更新adc_filtered_data供其他模块读取
*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
// 判断是否为ADC1(避免其他ADC误触发)
if (hadc->Instance == ADC1)
{
// 计算滤波值并更新到全局数组
adc_calculate_filtered(adc_filtered_data);
}
}

cs
#define CURRENT_ADC_REF_VOLTAGE 3.3f //ADC参考电压
#define CURRENT_ADC_RESOLUTION 4096.0f //12位ADC分辨率,最大值为2^12 - 1 = 4095
#define CURRENT_SENSOR_SENSITIVITY 0.12f //电流灵敏度,表示每1A电流的电压变化,实际硬件手册修改
/**
* @brief ADC值转电流值(mA)的转换因子
* @note 推导过程:
* 电压(V) = ADC值 / 4096 × 3.3V
* 电流(A) = 电压(V) / 灵敏度(V/A)
* 电流(mA) = 电流(A) × 1000
* 合并:电流(mA) = ADC值 × (3.3/4096) / 0.12 × 1000
* 例:ADC值=1000 → 电流≈6.714mA
* @warning 实际转换因子需要根据硬件参数校准
*/
#define ADC_TO_MA_FACTOR 6.714f //电流(mA) = (ADC值对应的电压-零漂电压)/灵敏度*1000
/**
* @file bsp_current_sensor.c
* @brief 直流有刷电机电流检测源文件
* @author Dr. GAO
* @date 2025-12-06
* @version V1.0
* @website https://genbotter.taobao.com
* @email mailto:yanzenggao@163.com
* @note 该文件适用于GenBotter Motor-1电机开发板
* 使用电机开发板上的PM1(如果要使用PM2,请修改ADC通道配置)
*/
#include "bsp_current_sensor.h" // 包含对应头文件
#include "adc.h" // 包含ADC全局变量
#include "tim.h" // 包含定时器定义
// ==================== 外部变量声明 ====================
/**
* @brief ADC原始采样数据数组
* @note 在adc.c中定义,存放多次采样的原始值
* 用于后续滤波处理
*/
extern uint16_t adc_raw_data[ADC_TOTAL_SAMPLES];
/**
* @brief ADC滤波后数据数组
* @note 在adc.c中定义,存放滤波后的各通道数据
* 索引0对应电流通道
*/
extern float adc_filtered_data[ADC_CHANNEL_NUM];
// ==================== 静态变量 ====================
/**
* @brief 零漂偏移(浮点型)
* @note 存储校准后的零漂值
* 用于消除ADC静态偏移误差
*/
static float current_offset_adc = 0.0f;
/**
* @brief 当前电流值(单位:mA)
* @note 存储最新的电流计算结果
* 由BSP_CurrentSensor_Update()更新
*/
static float current_mA = 0.0f;
/**
* @brief 初始化电流传感器
* @note 1. 清零静态变量
* 2. 执行零漂校准
* 3. ADC DMA采集由main.c启动(模块解耦)
*/
void BSP_CurrentSensor_Init(void)
{
// 初始化静态变量为0
current_offset_adc = 0.0f;
current_mA = 0.0f;
// 执行零漂校准
BSP_CurrentSensor_CalibrateOffset();
// ADC DMA采集在main.c中启动,保证模块间低耦合
}
/**
* @brief 校准电流传感器零漂
* @note 1. 采集10次滤波后ADC值
* 2. 取平均作为零漂偏移
* 3. 需在电机停止时执行
*/
void BSP_CurrentSensor_CalibrateOffset(void)
{
float sum_offset = 0.0f; // 累加和
uint8_t calibrate_count = 0; // 采样计数
/**
* @brief 采集10次滤波后的ADC值
* @note HAL_Delay(1) 等待1ms,确保数据已更新
* adc_filtered_data[0] 是电流通道的滤波后值
*/
while (calibrate_count < 10)
{
HAL_Delay(1); // 等待数据更新
sum_offset += adc_filtered_data[0]; // 累加滤波后ADC值
calibrate_count++;
}
/**
* @brief 计算零漂偏移平均值
* @note 浮点除法保留小数精度
* 例:10次采样的平均值作为零漂
*/
current_offset_adc = sum_offset / 10.0f;
}
/**
* @brief 获取滤波后ADC值
* @retval 电流通道的滤波后ADC值
* @note 直接返回adc_filtered_data[0]
*/
float BSP_CurrentSensor_GetADCValue(void)
{
return adc_filtered_data[0];
}
/**
* @brief 更新电流计算
* @note 在定时器中断中周期性调用(如10ms)
* 1. 减去零漂偏移
* 2. 乘以转换因子得到电流值(mA)
*/
void BSP_CurrentSensor_Update(void)
{
float adc_raw;
/**
* @brief 减去零漂偏移
* @note adc_filtered_data[0] 是滤波后ADC值
* current_offset_adc 是零漂偏移
* 差值即为实际电流对应的ADC值
*/
adc_raw = adc_filtered_data[0] - current_offset_adc;
/**
* @brief 转换为电流值(mA)
* @note ADC_TO_MA_FACTOR = 6.714
* 例:adc_raw = 1000 → 电流 ≈ 6714mA = 6.7A
*/
current_mA = adc_raw * ADC_TO_MA_FACTOR;
}
/**
* @brief 获取当前电流值
* @retval 电流值(单位:mA)
* @note 正值为正向电流,负值为反向电流
*/
float BSP_CurrentSensor_GetCurrent(void)
{
return current_mA;
}
/**
* @brief 获取零漂偏移值
* @retval 零漂偏移(ADC值)
* @note 用于调试和校准
*/
float BSP_CurrentSensor_GetOffset(void)
{
return current_offset_adc;
}
ADC的转换因子:

例:

主函数中:
