start10 电机电流检测方法

一、为什么要检测电流

电流 = 电机的"力量"表征

核心作用:

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的转换因子:

例:

主函数中:

相关推荐
茯苓gao2 小时前
嵌入式开发笔记:QSerialPort 完整使用指南——从基础 API 到工程实战
笔记·嵌入式硬件·qt·学习
wuyk5552 小时前
67.嵌入式C语言进阶:结构体指针实战指南——STM32外设、传感器数据访问的高效技巧
c语言·开发语言·stm32·单片机
西城微科方案开发3 小时前
LCD智能婴儿秤方案
单片机·嵌入式硬件
三佛科技-134163842124 小时前
灭蚊灯MCU方案开发,FT60E112-RB作为灭蚊灯主控芯片优势
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
Mr+范4 小时前
电源诱骗芯片CH224K
单片机·学习
起床学FPGA4 小时前
I2C、SPI
单片机·嵌入式硬件
风雨中的蜜蜂5 小时前
CMT2310A 超低功耗高性能射频收发器概述
stm32·单片机·嵌入式硬件
LCG元5 小时前
STM32 ADC 多通道 DMA 采样与数字滤波实战
stm32·嵌入式硬件·microsoft
VALENIAN瓦伦尼安教学设备5 小时前
转子/行星/平行轴齿轮箱综合故障模拟实验台可复现常见机械问题
大数据·数据库·人工智能·嵌入式硬件·算法