系列回顾:

  • 第一篇:STM32H743选型 + 定时器1μs精度脉冲控制
  • 第二篇:GaN FET驱动电路 + OverDrive大电流脉冲输出
  • 本篇(第三篇):INA241A电流采样 + PID闭环恒流控制

一、为什么必须做闭环恒流?

前两篇我们解决了"如何产生大电流脉冲"的问题:STM32H743用1μs步进精度控制GaN FET的开关时序,EPC2302在LMG1020驱动下实现20A峰值输出。

但有一个根本问题还没解决:电流到底是多少?

LED是电流敏感器件,亮度与电流成正比。如果只靠开环PWM占空比控制,会遇到三个麻烦:

1. 电源电压波动直接影响电流 设电路等效为:I = (Vbus - Vled) / (Rds_on + R_trace),当输入电压从48V漂到46V,电流就跟着变,LED亮度不一致。

2. LED正向压降随温度漂移 LED结温每升高1°C,Vf约下降2mV。OverDrive模式下结温变化可达30°C,意味着Vf漂移60mV,直接导致电流误差。

3. 多通道一致性无法保证 4个通道的GaN FET Rds(on)有工艺散差(±20%),开环时各通道电流天然不一致。

结论:必须引入电流采样 + 闭环控制,才能做到真正的"恒流"。

本篇详细讲解 INA241A 高边采样方案的硬件设计和 STM32H743 的 PID 闭环实现。


二、电流采样方案选型:为什么选高边采样 + INA241A

2.1 高边采样 vs 低边采样

对比维度 低边采样(采样电阻在GND侧) 高边采样(采样电阻在VCC侧)
电路复杂度 简单,共模电压≈0V 稍复杂,共模电压=Vbus(48V)
接地干扰 采样电阻引入接地抬升,影响其他电路 不影响接地完整性
多通道隔离 各通道低边共地,有串扰风险 各通道独立高边,互不干扰
适用场景 低压、单通道 高压、多通道工业场合
本项目 ❌ 48V四通道,接地抬升问题严重 ✅ 首选方案

本项目选择高边采样,采样电阻放在正电源线上,INA241A检测其两端压差,换算出电流。

2.2 INA241A关键参数解析

INA241A 是 TI 专为高压工业场合设计的电流检测放大器,以下是选它的核心理由:

参数 规格 对本项目的意义
共模输入范围 -4V 至 +110V 覆盖48V系统,且支持电源反接的短暂负压
增益 20 V/V(A1型) Rshunt=5mΩ时,满量程20A→输出2V,完美匹配3.3V ADC
带宽 400 kHz 响应时间远小于1μs,满足100μs采样周期
增益误差 ±0.1% 电流测量精度优于0.5%,恒流精度有保障
封装 SOT-23-5 体积极小,紧贴GaN FET布局,缩短Kelvin走线
电源电压 2.7V - 5.5V 直接用3.3V供电,无需额外电源轨

型号说明: INA241A 系列按增益分档:A1=20V/V,A2=50V/V,A3=100V/V。本项目选 A1(20V/V),原因见下节计算。


三、硬件采样电路设计

3.1 采样电阻 Rshunt 选型计算

设计目标:20A满量程时,INA241A输出电压尽量接近(但不超过)ADC满量程3.3V。

INA241A输出电压公式:

Vout = (I × Rshunt) × Gain + Vref

本设计 Vref 接 GND(单端输出),增益选20V/V,则:

Vout = I × Rshunt × 20

当 I = 20A 时,希望 Vout ≤ 3.0V(留10%余量给ADC):

Rshunt = 3.0V / (20A × 20) = 7.5mΩ

考虑采购标准值,选 5mΩ(WSL2512,1%精度),满量程输出:

Vout = 20A × 5mΩ × 20 = 2.0V

2.0V / 3.3V = 60.6% 的ADC量程,有充足余量,同时采样电阻功耗:

P = I² × R = 20² × 5mΩ = 2W(峰值,OD瞬时)
    = 20² × 5mΩ × 15%(占空比) = 0.3W(平均)

WSL2512额定功率1W,峰值2W为瞬时脉冲,平均0.3W,完全安全。

3.2 完整采样电路

          +48V_Bus
              │
              │  ┌──────────────────────────────┐
              ├──┤ IN+    INA241A (SOT-23-5)     │
              │  │                               │──→ Vout(到STM32 ADC)
        5mΩ   │  │ IN-    REF=GND                │
    ┌──[Rshunt]──┤        VS=3.3V               │
    │         │  └──────────────────────────────┘
    │         │
    └──────→ GaN FET Drain(EPC2302)
              │
           GaN FET Source
              │
           GND

关键布局要点(Kelvin 四线连接):

  • IN+IN- 走线必须连接到采样电阻的焊盘内侧(Kelvin接法),而非大电流走线上的任意点
  • 大电流路径(粗铜皮)和信号采样路径(细线)在采样电阻焊盘处汇合后立即分开
  • INA241A 的 VS(3.3V)和 GND 引脚旁各放100nF去耦电容,贴片放置
正确的Kelvin连接(PCB视图示意):

粗铜皮(大电流): ═══════════[焊盘A]-[Rshunt]-[焊盘B]═══════════→ GaN FET
细走线(采样):              ↑ IN+          IN- ↑
                        连INA241A        连INA241A

四、STM32H743 ADC 配置实战

4.1 ADC选择与配置策略

STM32H743 内置 3 个 16bit ADC,最高采样率 3.6MSPS。本项目的需求:

  • 4通道电流,各自独立采样
  • 采样周期 100μs(10kHz),足够PID响应
  • 不占用CPU(DMA传输)

选择 ADC1 配合 DMA1,扫描模式下4通道轮询采样。

4.2 ADC初始化代码(HAL库)

/* adc.h - 宏定义 */
#define ADC_CHANNELS        4
#define ADC_SAMPLE_PERIOD   100   // μs,采样周期
#define VREF_MV             3300  // mV,ADC参考电压
#define ADC_RESOLUTION      65535 // 16bit满量程
#define INA241_GAIN         20    // V/V
#define RSHUNT_UOHM         5000  // μΩ,采样电阻

/* DMA目标缓冲区 */
volatile uint16_t adc_raw[ADC_CHANNELS];  // DMA写入此数组

/* 电流换算宏(结果单位:mA)
 * I(mA) = Vout(mV) / (Gain × Rshunt(Ω))
 *        = [raw × Vref / 65535] / [20 × 0.005]
 */
#define ADC_TO_CURRENT_MA(raw) \
    ((uint32_t)(raw) * VREF_MV / ADC_RESOLUTION * 1000 / (INA241_GAIN * (RSHUNT_UOHM / 1000)))
/* adc.c - ADC + DMA 初始化 */
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

void ADC1_Init(void)
{
    ADC_MultiModeTypeDef multimode = {0};
    ADC_ChannelConfTypeDef sConfig = {0};

    /* ADC1 基础配置 */
    hadc1.Instance                      = ADC1;
    hadc1.Init.ClockPrescaler           = ADC_CLOCK_ASYNC_DIV2;   // 异步时钟,避免与APB干扰
    hadc1.Init.Resolution               = ADC_RESOLUTION_16B;
    hadc1.Init.ScanConvMode             = ADC_SCAN_ENABLE;         // 扫描4通道
    hadc1.Init.EOCSelection             = ADC_EOC_SEQ_CONV;        // 序列完成后触发DMA
    hadc1.Init.LowPowerAutoWait         = DISABLE;
    hadc1.Init.ContinuousConvMode       = DISABLE;                 // 由TIM6触发,非连续模式
    hadc1.Init.NbrOfConversion          = ADC_CHANNELS;            // 4次转换
    hadc1.Init.DiscontinuousConvMode    = DISABLE;
    hadc1.Init.ExternalTrigConv         = ADC_EXTERNALTRIG_T6_TRGO; // TIM6每100μs触发一次
    hadc1.Init.ExternalTrigConvEdge     = ADC_EXTERNALTRIGCONVEDGE_RISING;
    hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR; // DMA循环模式
    hadc1.Init.OversamplingMode         = ENABLE;
    hadc1.Init.Oversampling.Ratio       = ADC_OVERSAMPLING_RATIO_4; // 4次过采样,降低噪声
    hadc1.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_2;
    hadc1.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;
    HAL_ADC_Init(&hadc1);

    /* 多模式:本项目仅用ADC1 */
    multimode.Mode = ADC_MODE_INDEPENDENT;
    HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode);

    /* 配置4个采样通道(对应INA241A输出引脚)*/
    /* CH1电流:PA0 → ADC1_IN0 */
    sConfig.Channel      = ADC_CHANNEL_0;
    sConfig.Rank         = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_387CYCLES_5; // 采样时间,保证精度
    sConfig.SingleDiff   = ADC_SINGLE_ENDED;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    /* CH2电流:PA1 → ADC1_IN1 */
    sConfig.Channel = ADC_CHANNEL_1;
    sConfig.Rank    = ADC_REGULAR_RANK_2;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    /* CH3电流:PA2 → ADC1_IN2 */
    sConfig.Channel = ADC_CHANNEL_2;
    sConfig.Rank    = ADC_REGULAR_RANK_3;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    /* CH4电流:PA3 → ADC1_IN3 */
    sConfig.Channel = ADC_CHANNEL_3;
    sConfig.Rank    = ADC_REGULAR_RANK_4;
    HAL_ADC_ConfigChannel(&hadc1, &sConfig);

    /* 启动DMA传输 */
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_raw, ADC_CHANNELS);
}
/* TIM6 配置:每100μs触发ADC采样 */
void TIM6_Init(void)
{
    TIM_HandleTypeDef htim6 = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};

    /* TIM6 时钟 = APB1×2 = 240MHz
     * PSC=23, ARR=999 → 240MHz/24/1000 = 10kHz → 100μs */
    htim6.Instance               = TIM6;
    htim6.Init.Prescaler         = 23;
    htim6.Init.CounterMode       = TIM_COUNTERMODE_UP;
    htim6.Init.Period            = 999;
    htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_Base_Init(&htim6);

    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // TRGO触发ADC
    sMasterConfig.MasterSlaveMode     = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig);

    HAL_TIM_Base_Start(&htim6); // 启动定时器
}

4.3 DMA完成回调:读取电流值

/* DMA传输完成回调(每100μs触发一次)*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if (hadc->Instance == ADC1)
    {
        /* 将ADC原始值转换为mA,更新全局电流反馈 */
        for (uint8_t ch = 0; ch < ADC_CHANNELS; ch++)
        {
            g_current_feedback_ma[ch] = ADC_TO_CURRENT_MA(adc_raw[ch]);
        }

        /* 触发PID计算(设置标志位,在主循环或低优先级任务中执行)*/
        g_pid_update_flag = 1;
    }
}

五、PID闭环恒流控制实现

5.1 为什么用增量式PID而非位置式PID

位置式PID 增量式PID
输出含义 直接输出控制量绝对值 输出控制量的变化量
积分项 累积历史误差,有溢出风险 只累积相邻两次差值,无溢出
手动→自动切换 可能发生电流突变("扰动") 平滑切换,无突变
限幅处理 需对积分项单独限幅(反积分饱和) 直接对输出增量限幅即可
适用场景 简单控制,快速响应 工业恒流,稳定性优先

本项目选择增量式PID,每次调节 CCR(PWM占空比寄存器)的变化量,而非直接设定绝对值。

5.2 增量式PID数学公式

ΔU(k) = Kp × [e(k) - e(k-1)]
       + Ki × e(k)
       + Kd × [e(k) - 2×e(k-1) + e(k-2)]

U(k) = U(k-1) + ΔU(k)

其中:
  e(k)   = 当前误差 = 目标电流 - 实测电流
  e(k-1) = 上次误差
  e(k-2) = 上上次误差
  U(k)   = 当前CCR寄存器值(PWM占空比)

5.3 完整PID实现代码

/* pid.h */
#ifndef __PID_H
#define __PID_H

#include <stdint.h>

/* PID参数结构体(每通道独立一个实例)*/
typedef struct {
    /* 整定参数 */
    float Kp;           // 比例系数
    float Ki;           // 积分系数
    float Kd;           // 微分系数

    /* 误差历史 */
    float e_k;          // 当前误差 e(k)
    float e_k1;         // 上次误差 e(k-1)
    float e_k2;         // 上上次误差 e(k-2)

    /* 输出状态 */
    float output;       // 当前输出值(CCR)
    float output_max;   // 输出上限(15%占空比硬限)
    float output_min;   // 输出下限(0)

    /* 目标与反馈 */
    uint16_t target_ma; // 目标电流(mA)
} PID_t;

void  PID_Init(PID_t *pid, float kp, float ki, float kd,
               float out_max, float out_min);
float PID_Update(PID_t *pid, uint16_t measured_ma);
void  PID_Reset(PID_t *pid);

#endif
/* pid.c */
#include "pid.h"

void PID_Init(PID_t *pid, float kp, float ki, float kd,
              float out_max, float out_min)
{
    pid->Kp         = kp;
    pid->Ki         = ki;
    pid->Kd         = kd;
    pid->output_max = out_max;
    pid->output_min = out_min;
    PID_Reset(pid);
}

void PID_Reset(PID_t *pid)
{
    pid->e_k    = 0.0f;
    pid->e_k1   = 0.0f;
    pid->e_k2   = 0.0f;
    pid->output = 0.0f;
}

float PID_Update(PID_t *pid, uint16_t measured_ma)
{
    /* 计算当前误差(mA) */
    pid->e_k = (float)pid->target_ma - (float)measured_ma;

    /* 增量式PID计算 */
    float delta_u = pid->Kp * (pid->e_k  - pid->e_k1)
                  + pid->Ki *  pid->e_k
                  + pid->Kd * (pid->e_k  - 2.0f * pid->e_k1 + pid->e_k2);

    /* 更新输出(累加增量) */
    pid->output += delta_u;

    /* 输出限幅(防止CCR超出OD占空比上限 15%)*/
    if (pid->output > pid->output_max) pid->output = pid->output_max;
    if (pid->output < pid->output_min) pid->output = pid->output_min;

    /* 误差历史滚动 */
    pid->e_k2 = pid->e_k1;
    pid->e_k1 = pid->e_k;

    return pid->output;
}

5.4 PID与PWM的联动:在ADC回调中闭环

/* current_control.c */
#include "pid.h"
#include "tim.h"   // HAL定时器接口

/* 4通道PID实例 */
static PID_t g_pid[4];

/* OD模式下:最大允许CCR = ARR × 15% */
#define OD_MAX_DUTY_RATIO   0.15f

/* 初始化4路恒流PID */
void CurrentControl_Init(void)
{
    uint32_t arr = __HAL_TIM_GET_AUTORELOAD(&htim1); // 读取TIM1的ARR值

    for (uint8_t ch = 0; ch < 4; ch++)
    {
        PID_Init(&g_pid[ch],
                 /* Kp */  0.5f,    // 初始整定值,需实测调整
                 /* Ki */  0.05f,
                 /* Kd */  0.01f,
                 /* max */ arr * OD_MAX_DUTY_RATIO,  // CCR上限
                 /* min */ 0.0f);
        g_pid[ch].target_ma = 0; // 初始目标电流为0
    }
}

/* 设置某通道目标电流(mA) */
void CurrentControl_SetTarget(uint8_t ch, uint16_t ma)
{
    if (ch < 4) {
        g_pid[ch].target_ma = ma;
    }
}

/* PID主循环(在ADC DMA回调中触发,100μs周期)*/
void CurrentControl_Update(void)
{
    for (uint8_t ch = 0; ch < 4; ch++)
    {
        if (g_pid[ch].target_ma == 0) {
            /* 目标为0:直接关闭输出,复位PID */
            SetChannelPWM(ch, 0);
            PID_Reset(&g_pid[ch]);
            continue;
        }

        /* PID计算新的CCR值 */
        float new_ccr = PID_Update(&g_pid[ch], g_current_feedback_ma[ch]);

        /* 写入对应TIM通道的CCR寄存器 */
        SetChannelPWM(ch, (uint32_t)new_ccr);
    }
}

/* 将CCR值写入对应TIM通道 */
static void SetChannelPWM(uint8_t ch, uint32_t ccr)
{
    switch (ch) {
        case 0: __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, ccr); break;
        case 1: __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, ccr); break;
        case 2: __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, ccr); break;
        case 3: __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4, ccr); break;
    }
}

六、PID参数整定指南

PID参数整定是恒流控制成败的关键。本项目用**临界比例度法(Ziegler-Nichols)**进行初步整定,再手动微调。

6.1 整定步骤

Step 1:纯比例控制,寻找临界振荡

将 Ki = 0,Kd = 0,逐步增大 Kp,观察电流波形(示波器接INA241A输出):

Kp太小:                Kp接近临界值:          Kp过大:
                        ┌──────────────          ┌┐┌┐┌┐
电流稳定但有静差         │ 开始等幅振荡           ││││││ 发散振荡
────────────────        ┘                       ┘└┘└┘└

记录临界增益 Ku 和振荡周期 Tu。

Step 2:按Z-N公式计算初始参数

恒流控制建议用 PID(有积分消除稳态误差):
  Kp = 0.6 × Ku
  Ki = 2 × Kp / Tu  (离散化:Ki_discrete = Ki × T_sample)
  Kd = Kp × Tu / 8  (离散化:Kd_discrete = Kd / T_sample)

本项目 T_sample = 100μs,实测 Ku ≈ 1.2,Tu ≈ 800μs,计算得:

Kp = 0.72
Ki = 0.072 × 100μs = 0.0072  → 取 0.05(偏保守)
Kd = 0.012 / 100μs = 120     → 取 0.01(减小超调)

Step 3:上机微调,观察阶跃响应

设目标电流从0阶跃到1000mA,用示波器或串口打印观察:

现象 调整方向
上升慢,静差大 增大 Kp
超调大,振荡 减小 Kp 或增大 Kd
稳态有残差 增大 Ki
响应抖动明显 减小 Ki,检查ADC噪声

6.2 实测波形分析

调整完成后,用示波器(CH1接INA241A输出,CH2接GaN FET Gate)观察稳态效果:

理想的闭环恒流波形(1000mA目标,OD模式):

INA241A输出(V)
   2.0V ──────────────────────────────────────────
   1.8V     ┌─────────────────────────────────────
            │  ← 目标2A→INA241输出=2A×5mΩ×20=200mV对应
            │    实际电流纹波 < ±2% (≈±20mA)
   0.0V ────┘
            ↑ 阶跃后约 500μs 内稳定(5个PID周期)

GaN FET Gate
   5V ─┐  ┌─┐  ┌─┐  ┌─  ← PWM自动调整占空比保持恒流
   0V  └──┘ └──┘ └──┘

七、常见问题与避坑

7.1 采样值抖动严重

现象: 电流读数在±50mA之间跳动,PID调节紊乱。

原因排查:

  • INA241A的 VS 引脚去耦不足(100nF不够,补10μF钽电容)
  • PCB上 IN+/IN- 走线过长,拾取了GaN FET开关噪声
  • ADC参考电压 VREF+ 没有单独去耦

解决方案:

/* 在ADC回调中加一阶低通滤波,截止频率约500Hz */
#define LPF_ALPHA   0.1f   // α越小,滤波越强,响应越慢

g_current_filtered_ma[ch] = LPF_ALPHA * g_current_feedback_ma[ch]
                           + (1.0f - LPF_ALPHA) * g_current_filtered_ma[ch];

7.2 上电瞬间电流冲击

现象: 刚使能PWM时电流短暂超调,触发过流保护。

原因: PID从0开始积分,初始增量过大。

解决方案: 软启动——让目标电流线性爬坡:

/* 软启动:在100ms内将目标电流从0线性爬升到设定值 */
void SoftStart(uint8_t ch, uint16_t final_ma, uint16_t ramp_ms)
{
    uint16_t steps = ramp_ms * 10; // 100μs步进
    uint16_t delta = final_ma / steps;

    for (uint16_t i = 0; i <= steps; i++) {
        g_pid[ch].target_ma = delta * i;
        HAL_Delay_us(100); // 等待一个PID周期
    }
    g_pid[ch].target_ma = final_ma;
}

7.3 多通道之间电流互相干扰

现象: 某通道电流变化时,其他通道读数也跟着抖动。

原因: 低边接地路径共用,大电流回路耦合进ADC采样通路。

解决方案:

  • PCB上各通道 GND 星形汇聚(Star Ground),不共用大电流回流路径
  • ADC采样使用 ADC 内部的序列扫描,不在大电流切换的时间窗口内采样(可用 TIM 注入触发,精确避开 GaN FET 开关边沿)

八、与Modbus TCP的联动

闭环恒流的目标电流最终来自 Modbus TCP 寄存器,完整数据流如下:

上位机(PC/Orange Pi)
        │
        │ Modbus TCP(以太网)
        ▼
W5500 以太网芯片
        │
        │ SPI(最高80MHz)
        ▼
STM32H743 Modbus_Task(1ms轮询)
        │
        │ 写入 g_pid[ch].target_ma
        ▼
PID_Update()(100μs,ADC DMA回调)
        │
        │ 输出新CCR值
        ▼
TIM1 CCR寄存器 → GaN FET PWM占空比
        │
        ▼
INA241A 采样实际电流 → 反馈给PID

对应寄存器(来自设计方案):

寄存器 功能 PID中的对应变量
0x0001-0x0004 CH1-CH4额定电流(mA) g_pid[ch].target_ma
0x0100-0x0103 CH1-CH4实际电流反馈(mA) g_current_feedback_ma[ch]
0x0105 故障状态 过流标志位

九、总结

本篇完整实现了 OverDrive 光源控制器的精准电流闭环控制,核心要点:

模块 关键决策 理由
采样方案 高边采样(INA241A) 不干扰接地,多通道隔离
采样电阻 5mΩ × 增益20V/V 满量程输出2V,ADC量程合理
Kelvin接法 IN+/IN-连到焊盘内侧 消除接触电阻引起的测量误差
ADC驱动 TIM6触发 + DMA循环 精确定时,零CPU占用
PID类型 增量式PID 无溢出风险,手动切换平滑
限幅机制 CCR上限 = ARR × 15% 与硬件OD占空比保护联动

至此,系列前三篇已经覆盖了控制器的核心电路:MCU + 定时器 → GaN驱动 → 电流闭环,整个功率通道的闭环已经完整。


下一篇预告(第四篇): 《触发输入/输出电路设计实战:HCPL-314J高速光耦隔离 + 5V-24V宽压兼容 + 延迟精确透传》

光源控制器最重要的功能之一就是精确响应外部相机快门触发信号。下篇将详细讲解触发输入的隔离设计、宽压恒流驱动方案,以及 MCU 如何实现纳秒级抖动的精确延迟透传。


作者:工程师在路上 | CSDN 系列文章:4通道OverDrive恒流光源控制器设计全记录

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐