INA241A高边电流采样 + STM32H743 PID闭环恒流控制实战——4通道OverDrive光源控制器系列(三)
系列回顾:
- 第一篇: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恒流光源控制器设计全记录
更多推荐


所有评论(0)