CCS 开发环境配置

PFC 软件控制与 CCS 开发环境配置

在进行 PFC (功率因数校正) 的数字控制开发时,通常使用德州仪器 (TI) 官方提供的集成开发环境 Code Composer Studio (CCS)。本章将详细介绍 CCS 的基础功能、项目创建、库文件配置以及极其关键的安全调试规范。


CCS 基础操作与功能界面

在使用 CCS 软件时,必须严格遵守一个前提:软件的安装路径以及项目文件夹的路径中绝对不能包含任何中文字符,否则会导致各类未知的编译错误。

如果要导入已有的项目,可以通过菜单栏依次点击 Project -> Import CCS Projects 来完成。例如,本次 PFC 的项目名称为 Demo-PFC-CloseLoop。在 TI 的 C2000 系列开发中,我们通常会大量依赖官方提供的底层库文件,其中最核心的两个文件夹是 DSP2803x_commonDSP2803x_headers

结合 CCS 的功能界面,几个最常用的核心操作如下:

CCS功能界面

  • 编译程序:点击工具栏中类似“锤子”的图标即可进行项目编译。如果代码有语法错误,对应的文件图标上会出现红色的“❌”号,进入文件后具体的报错行也会有红叉提示。
  • 烧录/进入 Debug:点击带有绿色“小爬虫”的图标,即可将编译好的程序下载烧录到 DSP 芯片中,并进入仿真调试模式。
  • 程序运行控制:在 Debug 模式下,可以使用“运行(绿色播放键)”和“退出 Debug(红色方形停止键)”按钮来控制程序的启停。
  • 复位按钮:点击复位按钮,可以将程序指针强制跳转回代码的最开始处(通常是 main 函数入口)重新执行。
  • 变量观察窗口 (Variables):在调试过程中,可以将代码中的变量名称拖拽或复制到 Variables 窗口中,以实时观察其具体的数值变化。

⚠️ 极其重要的硬件调试安全警告:
在对开关电源进行在线仿真调试时,如果主功率电路已经接入了交流强电,绝对不允许在 CCS 中执行“暂停 -> 继续 -> 暂停”这样的断点或挂起操作!

原因分析:开关电源在正常工作时,程序是连续高速运行并不断更新占空比的。如果在调试时突然点击“暂停”,DSP 核心会停止执行代码,但其底层的 PWM 硬件外设可能会保持在暂停瞬间的状态(例如,某个 MOS 管的驱动信号恰好停留在高电平)。这会导致 MOS 管持续导通,母线瞬间直通短路,从而直接炸毁整个电源。
正确做法:在线调试或运行程序时,绝不能加主交流电;必须拔下控制板,在弱电环境下验证控制逻辑无误后,再脱机上强电运行。


新建项目与 C 文件生成

如果是从零开始搭建工程,需要新建 CCS 项目和源码文件。

新建项目

  1. 新建工程:选择新建 CCS Project。在弹出的配置窗口中,首先需要在 Target 栏选择对应的芯片型号(例如 TMS320F28034)。
  2. 配置烧录器:在 Connection 栏选择实际使用的仿真器型号,例如 Texas Instruments XDS100v2 USB Debug Probe
  3. 命名与模板:在 Project name 中输入项目名称(如 text),并在下方的项目模板和示例中选择 Empty Project (with main.c),这样系统会自动为你创建一个包含 main 函数的空项目。

新建c文件

  • 新建 C 文件:如果需要额外添加模块化的 C 语言源文件,可以在项目管理器中右键(或点击菜单栏 File -> New),依次选择 New -> Source File 即可建立新的代码文件。

库文件路径识别与 CMD 冲突解决

在新建工程并将 DSP2803x_commonDSP2803x_headers 这两个官方库文件夹复制进项目后,直接点击编译通常会引发大量报错。

无法识别库

如图所示,控制台(Console)和问题窗口(Problems)会提示大量 gmake 错误(如 22 errors),这通常是因为编译器无法找到头文件或全局变量定义。原因是新的工程不能自动识别我们导入的库文件路径。

解决方法如下

  1. 添加头文件搜索路径
    右键点击工程名称,依次进入 Properties -> Build -> C2000 Compiler -> Include Options
    Add dir to #include search path (--include_path, -I) 列表中,先删除原有不相关的默认路径,然后点击添加(Add)按钮,选择 Workspace...,从你的项目目录中,分别将 DSP2803x_commonDSP2803x_headers 文件夹下的 include 文件夹添加进去。
  2. 处理 CMD 链接文件冲突
    DSP 的编译需要 .cmd 文件来分配内存(RAM 或 Flash)。默认新建项目时,根目录下可能会自动生成一个 28034_RAM_lnk.cmd 文件。
    但是,在我们导入的 DSP2803x_common/cmd 文件夹下,已经包含了官方写好的 cmd 文件。如果项目中同时存在两个 cmd 文件,链接器会报错。
    • 首先,删除项目根目录下的 28034_RAM_lnk.cmd
    • 其次,展开 DSP2803x_common/cmd 文件夹,你会发现里面有两个文件:28034_RAM_lnk.cmd(代表程序在 RAM 中运行调试)和 F28034.cmd(代表程序烧录固化到 Flash 中)。一般实际应用中我们使用存入 Flash 的方式。
    • 因此,需要选中 DSP2803x_common/cmd 文件夹下的 28034_RAM_lnk.cmd,右键选择 Exclude from Build(从编译中排除),仅保留 F28034.cmd 参与编译。

TI 官方源码下载渠道

如果你需要获取更全面、底层的头文件、外设驱动代码或官方例程,可以直接从德州仪器 (TI) 的官网获取。

TI 将这些资源打包在一个名为 controlSUITE 的综合套件中。你可以在 TI 官网直接搜索 control suite,下载其离线安装包 CONTROLSUITE-ZIP-Offline(ZIP)Installer,安装后即可在本地浏览所有 C2000 系列的源代码。

PFC端口配置与代码架构

PFC端口配置与代码架构

PFC硬件框架与端口配置

基于 TMS320F28034 控制的 BOOST PFC 整体硬件框架及其信号连接如下图所示。

PFC硬件框架以及信号连接图

上图详细标出了 PFC 控制器与 PFC 主功率电路,以及与 LLC 信息交互辅助电路的信号连接定义。对应的控制板上 TMS320F28034 控制器的端口复用功能说明如下表所示:

信号名称内容定义控制板信号说明
ADC_IMOSMOS电流信号ADCINA0ADCA0通道
ADC_ACL输入L线对PGND电压信号ADCINA1ADCA1通道
ADC_ACN输入N线对PGND电压信号ADCINB1ADCB1通道
ADC_BUS直流母线电压信号ADCINB0ADCB0通道
PWM1A_DRIMOS驱动PWM信号PWM1AePWM模块1A通道
Relay继电器控制信号GPIO32GPIO32口
P_SCI_RXDSCI通信接收信号SCIARXDSCIA通信接收口
P_SCI_TXDSCI通信发送信号SCIATXDSCIA通信发送口
PFC_OK_SPFC状态IO口信号GPIO22GPIO22口

PFC软件架构与中断机制

在数字电源控制中,最好的源代码架构是定时的架构,即在某个固定的时间点触发相应的事件,程序进入处理,处理完毕后立刻退出来,而不是在主程序中一直死循环等待。这种架构能够极大地避免阻塞,从而更高效地利用 DSP 的时间资源。

为简化系统软件架构并增加软件系统的可移植性,本系统正是以这种中断事件触发的思想来设计整体 PFC 软件系统的。

PFC软件架构

  • 初始化与主循环:PFC 控制器在上电或复位后,首先初始化 DSP 的所有外设,打开各组模块功能所需的中断,随后程序进入空循环(空循环中无任何实质性处理)。
  • 中断触发运行:软件的主体分别由四个中断内容构成,分别为 ADC 中断、1kHz 中断(1ms定时)、200Hz 中断(5ms定时)、SCI 中断。
  • 中断优先级与嵌套:这四个中断的优先级从高到低依次为:ADC中断 > 1kHz中断 > 200Hz中断 > SCI中断。各个中断在运行过程中均开启了中断嵌套功能,这意味着优先级高的中断可以在任意时刻打断正在执行的低优先级中断,确保最紧急的控制任务(如采样与发波)被优先执行。

工程文件说明与函数架构

为了保证 PFC 软件整体编程遵循规范、简易的原则,系统针对不同的中断分别划分了不同的 C 语言文件,并在其中对应编写了该中断所需的执行函数。

整体工程文件划分

文件名说明
main.c主函数:初始化配置
InterruptADC.cADC中断函数文件:数据获取函数,锁相环设计,前馈环设计,参考电流计算,电流环计算,寄存器更新,DCM电流采样矫正,硬件过流保护设计
InterruptADC.hADC中断头文件:宏定义
Interrupt1kHz.c1kHz中断函数文件:电压平均值计算,电压环参数变增益,BUS参考电压处理,电压环函数,母线电压限制,UVP函数,AC有效值计算函数,DCM前馈参数计算,电流环便增益,前馈软启,电流参考值软启
Interrupt1kHz.h1kHz中断头文件:宏定义
Interrupt200Hz.c200Hz中断函数文件:PFC状态机函数,输入电压保护,SCI发送数据
Interrupt200Hz.h200Hz头文件:宏定义
SCIcom.cSCI通信功能函数文件:接收中断,SCI状态处理,发送等
SCIcom.hSCI通信功能函数头文件:宏定义
Table.h运算所需表

各文件内部函数功能详细定义

以下是各个源文件中封装的具体函数集合及其功能定义:

main.c 文件函数集合

函数名说明
main()主函数
SysCtrlInit()系统时钟初始化配置
Timer0Init()1kHz(1mS中断)定时器初始化配置
Timer1Init()200Hz(5mS中断)定时器初始化配置
GPIOInit()IO口功能配置函数
ePWMInit()ePWM(MOS驱动)初始化配置
ePWMDebugInit()ePWM 4A初始化配置,模拟DAC功能
ADCInit()ADC初始化配置
TZInit()TZ事件初始化配置,用以实现过流快速封波功能
SCIInit()SCI通信初始化配置

InterruptADC.c 文件函数集合

函数名说明
ISR_ADC()ADC中断函数,每个开关周期采样一次
ADCSample()ADC数据读取,获取电感电流和输入电压
PhaseLockLoop()锁相环函数
FeedCal()前馈环计算
IrefCal()参考电流计算
CurrentLoop()电流环计算,得到所需占空比
RegReflash()PWM寄存器更新
Icalibrate()DMC模式电流采样较正
HwOcp()硬件过流封波保护

Interrupt1kHz.c 文件函数集合

函数名说明
ISR_1kHz()1毫秒的中断函数
VbusAvgCal()输出电压采样求平均计算
VloopKpKiCal()电压环变增益函数计算
VrefCal()BUS参考电压处理
VoltageLoop()电压环计算函数
VbusLimit()母线电压限制
VbusUVP()母线电压欠压保护函数
VIacRmsCal()计算AC有效值计算函数
DCMFeedCal()DCM前馈参数计算
IloopKpKiCal()电流环便增益
Feedss()前馈占空比软启
Irefss()电流参考值软启

Interrupt200Hz.c 文件函数集合

函数名说明
ISR_200Hz()5毫秒定时中断函数
StateM()PFC状态机函数
StateMInit()初始化状态
StateMWait()等待状态
StateMRise()软启动状态
StateMRun()运行状态
StateMErr()故障状态
VariableInit()所有变量初始化
ResetVILoop()电压电流环相关参数复位
ImosOffsetCal()电流采样偏置计算
VacCheck()输入电压检测与保护
PWMDAC()用PWM口模拟DAC输出

SCIcom.c 文件函数集合

函数名说明
ISR_SCI()接收中断
MesRxCmd()接收数据转存至接收数据帧数组中
MesStore()中断接收数据处理函数,将SCI接收寄存器中的数据保存至ACK数组中,并对数据正确性校验
TxDataRecord()将需要发送的数据存储到TxData寄存器中
FrameSent()S发送一帧数据
FrameBuild()构建一帧数据,保存于Frame中
SCI_TX()调用SCI发送寄存器发送数据
SciCom()SCI内部通信主函数-主要执行发送操作
SCIValueInit()SCI相关变量初始化
PFC初始化(中断向量初始化)

PFC初始化-中断向量初始化

下面是 PFC 硬件配置、软件架构,DSP 中断分配表以及 main.c 核心代码进行从零开始的逐句拆解。


在高频数字电源控制中,我们需要极其严格的离散时间执行一致性。因此,最合理的源代码架构是基于定时中断的事件触发架构。程序在发生中断时进入处理(如采样、计算控制律、更新占空比),处理完毕后立刻退出,绝不在主程序中死循环等待。

四个主要中断的优先级从高到低依次为:ADC中断 > 1kHz中断 > 200Hz中断 > SCI中断。开启中断嵌套后,高优先级的采样与发波任务随时可以打断低优先级的通信或状态机任务,保证控制系统的实时性。


本项目 PIE 中断机制与向量分配表

TI 28x DSP 的外设中断扩展(PIE)模块像是一个“前台秘书”,负责将多达 96 个外设中断源分组并转交给 CPU。

一个中断信号想要最终到达 CPU 核心,必须经过以下图示中的重重“开关”:

  1. 外设级别(如 ADC 转换完成标志)
  2. PIE 级别通道开关 (PIEIERx)
  3. CPU 级别组别开关 (IER)
  4. 全局总开关 (INTM)

中断总图

根据官方数据手册截图,提取出代码中实际用到(标黄)的中断通道,整理如下表:

中断名称CPU 组别PIE 内部通道触发源说明代码中的向量定义
ADCINT1INT1INT1.1 (最高优先级)ADC 模块转换完成触发PieVectTable.ADCINT1
TINT0INT1INT1.7CPU 定时器 0 触发 (1ms)PieVectTable.TINT0
SCIRXINTAINT9INT9.1串口 SCI-A 接收到数据触发PieVectTable.SCIRXINTA
CPU-Timer1INT13直连 CPU (不经过 PIE)CPU 定时器 1 触发 (5ms)PieVectTable.TINT1

main.c 主程序全代码逐段精解

main.c 是整个系统的“总指挥部”。在执行高频数字控制律之前,总指挥必须先把所有的硬件外设(PWM死区、ADC采样通道、通信接口)全部配置妥当。以下为你将这篇代码拆解为 6 个核心步骤。

关门闭户,准备系统级初始化

1
2
3
4
5
6
7
8
int main(void)
{
DINT; // 关闭所有中断 (DSP特有汇编指令)
DisableDog(); // 关看门狗 (DSP特有)
SysCtrlInit(); // 初始化系统功能
InitPieCtrl(); // 复位中断向量
InitPieVectTable(); // 初始化中断向量表

  • DINT;:结合上面的中断总图,这句指令相当于直接拉下图中最右侧的 INTM 总闸,屏蔽一切外部中断请求,确保配置硬件时不被打断。
  • DisableDog();:看门狗是一个硬件计时器,程序如果跑飞不喂狗,它就会强制重启 DSP。因为初始化过程较长,为了防止被误判为死机,我们先将其关闭。
  • SysCtrlInit();:这是一个底层封装函数,主要用来配置 DSP 的“心脏”——锁相环倍频(将外部晶振倍频到系统主频),并开启需要用到的外设时钟。
  • 中断向量表初始化:清空旧的中断跳转“电话簿”,为下一步重新分配做准备。

映射中断服务函数(修改“电话簿”)

1
2
3
4
5
6
7
EALLOW;
PieVectTable.ADCINT1 = &ISR_ADC; // 绑定 ADC 中断函数
PieVectTable.TINT0 = &ISR_1kHz; // 绑定 1kHz 中断函数
PieVectTable.TINT1 = &ISR_200Hz; // 绑定 200Hz 中断函数
PieVectTable.SCIRXINTA = &ISR_SCI; // 绑定 SCI 接收中断函数
EDIS;

  • EALLOWEDIS:DSP 内部有很多极其危险的受保护寄存器(比如决定中断往哪跳的寄存器)。平时它们是硬件锁死的,必须先用 EALLOW(解除保护)才能修改,改完马上用 EDIS 重新锁死。
  • &ISR_ADC:利用 C 语言的取址符,告诉硬件:“当 ADCINT1 发生时,程序指针强制跳转到名为 ISR_ADC 的函数去执行”。

突破速度瓶颈(代码搬移至 RAM)

1
2
3
memcpy((Uint16 *)&RamfuncsRunStart, (Uint16 *)&RamfuncsLoadStart, (unsigned long)&RamfuncsLoadSize);
InitFlash();

  • 核心意义:由于 Flash(固态存储)读取速度极慢,每次读取代码都会产生等待周期(Wait States),这会导致数字电源控制环路出现严重的执行抖动(Jitter)。通过 C 标准库函数 memcpy,在开机瞬间将对时间极其敏感的核心控制代码从 Flash 复制到高速 RAM 中运行,彻底消除计算延迟。

逐个激活外设模块

1
2
3
4
5
6
7
8
9
Timer0Init();    // CPU TIMER0 初始化 1ms(1Khz)
Timer1Init(); // CPU TIMER1 初始化 5ms(200hz)
ePWMInit(); // ePWM1 初始化,配置开关频率、死区等
TZInit(); // 比较器1初始化,用于硬件快速封锁PWM
ePWMDebugInit(); // ePWM4 初始化,模拟 DAC 用来抓取内部变量
GPIOInit(); // 配置引脚的复用功能(如把引脚定为PWM输出还是ADC输入)
SCIInit(); // 串口初始化,配置波特率
ADCInit(); // ADC 初始化,配置采样触发源

  • 这里调用的是自定义的子函数,分别去设置具体的控制参数(例如在 ePWMInit 中写入控制母线电压的 PWM 互补发波逻辑和死区时间)。

逐层打通中断链路

1
2
3
4
5
6
7
8
9
10
11
EALLOW;
PieCtrlRegs.PIEIER1.bit.INTx7 = 1; // 开启 PIE 秘书的 Timer0 专属开关 (PIE组1, 通道7)
PieCtrlRegs.PIEIER1.bit.INTx1 = 1; // 开启 PIE 秘书的 ADC1 专属开关 (PIE组1, 通道1)
PieCtrlRegs.PIEIER9.bit.INTx1 = 1; // 开启 PIE 秘书的 SCI 专属开关 (PIE组9, 通道1)
EDIS;

PieCtrlRegs.PIEACK.all = 0xFFFF; // 清除所有历史遗留的中断挂起标志,打扫战场
IER = M_INT1 | M_INT9 | M_INT13; // 开启 CPU 的第1组、第9组以及直连的Timer1(第13组)大门
EINT; // Enable Global interrupt INTM (闭合总闸!)
ERTM; // 开启实时调试中断 (用于仿真器在线调试)

  • 结合上面新增的 中断总图 来看:
  • PIEIERx 配置的就是图中下半部分的 PIEIERx(8:1) 开关。
  • IER 配置的是图中上半部分的 IER(12:1) 开关。
  • EINT 配置的是最右侧连接到 CPU 的 INTM 那个总开关。
  • 中断就像水流,必须同时打开分支阀门 (PIEIER)主管道阀门 (IER)总水闸 (EINT),中断信号才能最终传达到 CPU 核心。

最终启停同步与主循环

1
2
3
4
5
6
7
8
9
    DELAY_US(5000);                         // 延时 5ms,等待所有电路与时钟彻底稳定
CpuTimer0Regs.TCR.bit.TSS = 0; // 启动 Timer0 (TSS=0 表示取消停止状态,开始计数)
CpuTimer1Regs.TCR.bit.TSS = 0; // 启动 Timer1

EALLOW;
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1; // 全局同步所有的 ePWM 时基
EDIS;
}

  • TBCLKSYNC = 1:这对于电源控制极为重要。它能确保芯片内多个独立的 PWM 模块时钟计数器在同一瞬间同时起跑,彻底避免发波相位的错乱。
  • 执行到这里,main 所有的准备工作均已结束。随后系统会完全依靠定时器和 ADC 的节拍,自动在各个中断之间有序跳转,执行精确的母线闭环控制。

  • 下面是这部分的完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78

    #include "DSP28x_Project.h"
    #include "InterruptADC.h"
    #include "Interrupt1kHz.h"
    #include "Interrupt200Hz.h"
    #include "SCIcom.h"
    /**
    * main.c
    */
    //系统时钟配置初始化
    void SysCtrlInit(void);
    //定时器0配置初始化 1kHz中断
    void Timer0Init(void);
    //定时器1配置初始化 200Hz中断
    void Timer1Init(void);
    //IO口功能配置函数
    void GPIOInit(void);
    //ePWM1初始化函数
    void ePWMInit(void);
    //ADC 初始化函数
    void ADCInit(void);
    //硬件过流,硬件过压TZ封波初始化函数
    void TZInit(void) ;
    //SCI通信初始化
    void SCIInit(void);
    //ePWM初始化函数(调试用)
    void ePWMDebugInit(void);

    extern Uint16 RamfuncsLoadStart;
    extern Uint16 RamfuncsLoadSize;
    extern Uint16 RamfuncsRunStart;

    int main(void)
    {
    DINT;//关闭所有中断
    DisableDog(); //关看门狗
    SysCtrlInit(); //初始化系统功能
    InitPieCtrl();//复位中断向量
    InitPieVectTable();//初始化中断向量表

    EALLOW;
    PieVectTable.ADCINT1 = &ISR_ADC;//ADC中断定义
    PieVectTable.TINT0 = &ISR_1kHz;//1kHz中断名定义
    PieVectTable.TINT1 = &ISR_200Hz;//200Hz中断名定义
    PieVectTable.SCIRXINTA=&ISR_SCI;//SCI接收中断
    EDIS;

    memcpy((Uint16 *)&RamfuncsRunStart,(Uint16 *)&RamfuncsLoadStart,(unsigned long)&RamfuncsLoadSize);
    InitFlash();

    Timer0Init();// CPU TIMER0 初始化 1ms(1Khz)
    Timer1Init(); // CPU TIMER1 初始化 5ms(200hz)
    ePWMInit();//ePWM1/初始化函数
    TZInit();//比较器1初始化
    ePWMDebugInit();//ePWM4初始化函数(调试用)
    GPIOInit();//GPIO口初始化
    SCIInit();//SCI初始化
    ADCInit();//ADC采样初始化

    EALLOW;
    PieCtrlRegs.PIEIER1.bit.INTx7 = 1; //Timer0 使能
    PieCtrlRegs.PIEIER1.bit.INTx1 = 1; //ADC中断1使能
    PieCtrlRegs.PIEIER9.bit.INTx1 = 1; // SCI中断使能;
    EDIS;

    PieCtrlRegs.PIEACK.all = 0xFFFF;
    IER = M_INT1|M_INT9|M_INT13; // 开中断INT1/ INT13
    EINT; // Enable Global interrupt INTM
    ERTM; // Enable Global realtime interrupt DBGM

    DELAY_US(5000);//延时
    CpuTimer0Regs.TCR.bit.TSS = 0; // Timer0使能;
    CpuTimer1Regs.TCR.bit.TSS = 0; // Timer1使能;

    EALLOW;
    SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1; // enable TBCLK within the ePWM
    EDIS;

PFC初始化(时钟与定时器初始化)

PFC初始化(时钟与定时器初始化)

本章的核心任务是为 PFC(功率因数校正)数字控制系统配置极其稳定的“心跳”(系统时钟),以及一个绝对准时的“闹钟”(定时器)。由于我们的核心目标是控制公共直流母线电压的稳定,这些底层时钟的精确度直接决定了电压环和电流环算法的执行精度。

在解析代码之前,我们首先依据官方数据手册,将代码中用到的所有底层硬件寄存器进行表格化汇总。


系统时钟控制 (CLKCTL) 寄存器全映射表

代码中对 SysCtrlRegs.CLKCTL 进行了十余项配置。以下汇总表将代码中的每一行设定,与官方手册中高亮的功能说明进行了严密的映射与中文翻译

寄存器与位 (Bit)代码设定值手册功能说明 (中文翻译)对应代码原句
Bit 15: NMIRESETSEL0无延迟驱动 MCLKRS 复位信号 (低电平有效,复位默认值)SysCtrlRegs.CLKCTL.bit.NMIRESETSEL = 0;
Bit 14: XTALOSCOFF1关闭外部晶体振荡器SysCtrlRegs.CLKCTL.bit.XTALOSCOFF = 1;
Bit 13: XCLKINOFF1关闭外部时钟输入引脚 (XCLKIN)SysCtrlRegs.CLKCTL.bit.XCLKINOFF = 1;
Bit 12: WDHALTI0进入暂停 (HALT) 模式时自动关闭看门狗 (复位默认值)SysCtrlRegs.CLKCTL.bit.WDHALTI = 0;
Bit 11: INTOSC2HALTI0进入暂停 (HALT) 模式时自动关闭内部振荡器 2 (复位默认值)SysCtrlRegs.CLKCTL.bit.INTOSC2HALTI = 0;
Bit 10: INTOSC2OFF1关闭内部振荡器 2SysCtrlRegs.CLKCTL.bit.INTOSC2OFF = 1;
Bit 9: INTOSC1HALTI0进入暂停 (HALT) 模式时自动关闭内部振荡器 1 (复位默认值)SysCtrlRegs.CLKCTL.bit.INTOSC1HALTI = 0;
Bit 8: INTOSC1OFF0开启内部振荡器 1SysCtrlRegs.CLKCTL.bit.INTOSC1OFF = 0;
Bits 7-5: TMR2CLKPRESCALE000 (0)CPU 定时器 2 时钟预分频值 (0代表不分频)SysCtrlRegs.CLKCTL.bit.TMR2CLKPRESCALE = 0;
Bits 4-3: TMR2CLKSRCSEL10 (2)选择内部振荡器 1SysCtrlRegs.CLKCTL.bit.TMR2CLKSRCSEL = 2;
Bit 2: WDCLKSRCSEL0选择内部振荡器 1 (复位默认值)SysCtrlRegs.CLKCTL.bit.WDCLKSRCSEL = 0;
Bit 1: OSCCLKSRC2SEL0选择外部振荡器 (复位默认值)SysCtrlRegs.CLKCTL.bit.OSCCLKSRC2SEL = 0;
Bit 0: OSCCLKSRCSEL0选择内部振荡器 1 (复位默认值)SysCtrlRegs.CLKCTL.bit.OSCCLKSRCSEL = 0;

锁相环 (PLL) 状态与配置寄存器全映射表

为了将 10MHz 的初始时钟安全地倍频至 60MHz,代码严格遵循了硬件倍频的安全规程。以下是相关寄存器的中文映射汇总:

寄存器与位 (Bit)设定值/操作手册功能说明 (中文翻译)对应代码原句
PLLSTS Bits 8-7: DIVSEL0 / 3选择时钟 1 分频先清零:SysCtrlRegs.PLLSTS.bit.DIVSEL = 0;
后写3:SysCtrlRegs.PLLSTS.bit.DIVSEL = 3;
PLLSTS Bit 6: MCLKOFF1 / 0禁用主振荡器故障检测逻辑,且 PLL 不会发出跛行模式 (limp-mode) 时钟。临时关闭:SysCtrlRegs.PLLSTS.bit.MCLKOFF = 1;
后重启:SysCtrlRegs.PLLSTS.bit.MCLKOFF = 0;
PLLSTS Bit 4: MCLKCLR1强制清除并复位时钟丢失检测电路。SysCtrlRegs.PLLSTS.bit.MCLKCLR = 1;
PLLSTS Bit 3: MCLKSTS读取判断指示检测到系统时钟 (OSCCLK) 发生丢失。if(SysCtrlRegs.PLLSTS.bit.MCLKSTS)
PLLSTS Bit 0: PLLLOCKS循环死等指示 PLL 已完成频率锁定,当前输出处于稳定状态。while(!SysCtrlRegs.PLLSTS.bit.PLLLOCKS);
PLLCR 组合设定DIV=6表格交叉项:0110 (对应十进制6) 结合 PLLSTS[DIVSEL] = 3,输出公式为 (OSCCLK * 6)/1SysCtrlRegs.PLLCR.bit.DIV = 6;

系统时钟初始化代码逐句精解 (SysCtrlInit)

结合上述两张翻译后的映射表,我们可以清晰地看出 SysCtrlInit 底层硬件配置的逻辑全貌:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

* 函 数:void SysCtrlInit(void)
* 功 能:系统时钟初始化
* 配置内容:
*
* 矫正时钟信号(必须先使能ADC时钟信号)
* 禁用时钟输出功能
* 系统时钟配置如下:
* 系统时钟1使用内部时钟1
* 系统时钟2使用外部时钟(实际不起作用)
* 看门狗使用内部时钟1
* CUP定时器2使用内部时钟1
* CPU定时器2不分频
* 使能内容部晶振1(时钟1)
* 暂停时,时钟1信号暂停(默认)
* 关闭内部晶振2(时钟2)
* 暂停时,时钟2信号暂停(默认)
* 暂停时,看门狗时钟信号暂停(默认)
* 禁止外部时钟输入功能
* 禁止外部晶振功能
* 时钟丢失时,立即无延时复位(默认)
*
* PLL设置如下:
* 确认系统时钟正常,系统时钟丢失将停止PLL设置;
* 确保PLL分频为0,如果非0,将其写0;
* 关闭丢失时钟检测 PLL切换时防止检测到系统时钟报错
* 9分频,设置时钟频率,CLKIN*6=60Mhz CLKIN = 10Mhz;
* 一直等待,直到PLL时钟锁存;
* 丢失时钟检测开启;
* 最终系统时钟: CLKIN*6/1 = 60Mhz;SYSCLKOUT=60Mhz
*
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
void SysCtrlInit(void)
{
EALLOW; // 解锁受保护的系统危险寄存器,允许配置时钟。

// 【阶段一:芯片硬件校准】
// 芯片出厂校准程序需要用到 ADC 模块,因此必须先给 ADC 模块接通时钟(通电)
SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1;
(*Device_cal)(); // 运行固化在芯片ROM里的校准程序
SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 0; // 校准完毕,立刻关闭 ADC 时钟以节省功耗

// 【阶段二:时钟源选择 - 严格对照 CLKCTL 映射表】
SysCtrlRegs.XCLK.bit.XCLKOUTDIV = 3; // 禁用外部时钟输出引脚

// 配置主备时钟源
SysCtrlRegs.CLKCTL.bit.OSCCLKSRCSEL = 0; // 主时钟源 = 内部晶振1
SysCtrlRegs.CLKCTL.bit.OSCCLKSRC2SEL = 0; // 备用时钟 = 外部晶振 (系统默认,本程序闲置)
SysCtrlRegs.CLKCTL.bit.WDCLKSRCSEL = 0; // 看门狗时钟 = 内部晶振1
SysCtrlRegs.CLKCTL.bit.TMR2CLKSRCSEL = 2; // 定时器2时钟 = 内部晶振1
SysCtrlRegs.CLKCTL.bit.TMR2CLKPRESCALE = 0; // 定时器2不进行降速分频

// 配置各个振荡器开关(后缀带 OFF 的,写 0 代表开启,写 1 代表关闭)
SysCtrlRegs.CLKCTL.bit.INTOSC1OFF = 0; // 开启内部晶振1 (系统的核心心跳源)
SysCtrlRegs.CLKCTL.bit.INTOSC1HALTI = 0; // Debug模式下,内部晶振1不暂停
SysCtrlRegs.CLKCTL.bit.INTOSC2OFF = 1; // 强制关闭内部晶振2 (省电)
SysCtrlRegs.CLKCTL.bit.INTOSC2HALTI = 0;
SysCtrlRegs.CLKCTL.bit.WDHALTI = 0; // Debug模式下,看门狗不暂停
SysCtrlRegs.CLKCTL.bit.XCLKINOFF = 1; // 强制关闭外部时钟输入引脚
SysCtrlRegs.CLKCTL.bit.XTALOSCOFF = 1; // 强制关闭外部晶体振荡器
SysCtrlRegs.CLKCTL.bit.NMIRESETSEL = 0; // 时钟信号如果断裂,系统无延时立即复位

// 【阶段三:PLL 锁相环倍频配置 - 严格对照 PLLSTS 映射表】
// 检查时钟是否丢失。MCLKSTS 为 1 说明时钟发生异常。
if(SysCtrlRegs.PLLSTS.bit.MCLKSTS)
{
SysCtrlRegs.PLLSTS.bit.MCLKCLR = 1; // 写入1,强制清除错误标志
ESTOP0; // 硬件级紧急停车,停止运行保护电路
}

// 在改变倍频系数之前,必须将后级分频器 DIVSEL 设为 0。防止输出频率瞬间爆炸导致烧毁。
if((SysCtrlRegs.PLLSTS.bit.DIVSEL == 2) || (SysCtrlRegs.PLLSTS.bit.DIVSEL == 3))
SysCtrlRegs.PLLSTS.bit.DIVSEL = 0;

// 临时关闭时钟丢失检测。因为改变频率瞬间会有剧烈波动,防止芯片误认为时钟坏了而报警。
SysCtrlRegs.PLLSTS.bit.MCLKOFF = 1;

// 设定倍频系数为 6。输入 10MHz * 6 = 目标频率 60MHz。
SysCtrlRegs.PLLCR.bit.DIV = 6;

// 【核心死等逻辑】PLL 是模拟升频电路,需要时间才能稳住 60MHz。
// 在 PLLLOCKS 变成 1(完全锁定并稳定)之前,程序在此处死循环等待。
while(!SysCtrlRegs.PLLSTS.bit.PLLLOCKS);

// 频率彻底稳定后,重新打开时钟丢失检测功能。
SysCtrlRegs.PLLSTS.bit.MCLKOFF = 0;

// 将后级分频设为 1 (根据手册,写入 3 代表 1 分频)。最终全速输出 60MHz。
SysCtrlRegs.PLLSTS.bit.DIVSEL = 3;

// 【阶段四:外设时钟门控按需供电】
// 为了降低芯片热噪声,对于系统中的外设模块,用哪个就给哪个通电(写1),不用的一律关断(写0)。
SysCtrlRegs.PCLKCR0.bit.ADCENCLK = 1; // 开启 ADC 采样时钟 (用于母线电压采样)
SysCtrlRegs.PCLKCR1.bit.EPWM7ENCLK = 0;
SysCtrlRegs.PCLKCR1.bit.EPWM6ENCLK = 0;
SysCtrlRegs.PCLKCR1.bit.EPWM5ENCLK = 0;
SysCtrlRegs.PCLKCR1.bit.EPWM4ENCLK = 1; // 开启 PWM4(预留调试功能)
SysCtrlRegs.PCLKCR1.bit.EPWM3ENCLK = 0;
SysCtrlRegs.PCLKCR1.bit.EPWM2ENCLK = 0;
SysCtrlRegs.PCLKCR1.bit.EPWM1ENCLK = 1; // 开启 PWM1(用于主电路开关管驱动发波)
SysCtrlRegs.PCLKCR3.bit.CPUTIMER1ENCLK = 1; // 开启 定时器1 (产生 5ms 中断)
SysCtrlRegs.PCLKCR3.bit.CPUTIMER0ENCLK = 1; // 开启 定时器0 (产生 1ms 中断)
SysCtrlRegs.PCLKCR3.bit.COMP1ENCLK = 1; // 开启 比较器1 (用于硬件极速过流保护)
SysCtrlRegs.PCLKCR0.bit.ECANAENCLK = 0;
SysCtrlRegs.PCLKCR0.bit.SCIAENCLK = 1; // 开启 SCI 串口通信 (用于和系统级交互数据)

SysCtrlRegs.PCLKCR0.bit.SPIAENCLK = 0;
// SPIB CLK关闭
SysCtrlRegs.PCLKCR0.bit.SPIBENCLK = 0;
// I2C CLK关闭
SysCtrlRegs.PCLKCR0.bit.I2CAENCLK = 0;
// TBC CLK关闭
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0;
// LINA CLK关闭
SysCtrlRegs.PCLKCR0.bit.LINAENCLK = 0;
// HRPWM CLK关闭
SysCtrlRegs.PCLKCR0.bit.HRPWMENCLK = 0;
// EQEP CLK关闭
SysCtrlRegs.PCLKCR1.bit.EQEP1ENCLK = 0;
// ECAP2 CLK关闭
SysCtrlRegs.PCLKCR1.bit.ECAP1ENCLK = 0;
// CPU Timer2 CLK关闭
SysCtrlRegs.PCLKCR3.bit.CPUTIMER2ENCLK = 0;
// COMP3 CLK关闭
SysCtrlRegs.PCLKCR3.bit.COMP3ENCLK = 0;
// COMP2 CLK关闭
SysCtrlRegs.PCLKCR3.bit.COMP2ENCLK =0;

EDIS; // 配置结束,重新上锁,防止核心寄存器被意外篡改。
}


定时器0 初始化与算法原理解析 (Timer0Init)

我们需要 Timer0 产生一个精确的 1kHz(1ms)的定时中断。这个定时器决定了母线电压环的运算节拍。

1
2
3
4
5
6
7
8
9
10
* 函    数:void Timer0Init(void)
* 功 能:1mS定时器,1kHz中断
* 配置情况:
* 关闭定时器0
* 清楚定时器0中断标志位
* 分频30,时钟分频为TPR+1;TDDR寄存器为29(0x1D)
* 时钟60Mhz , PRD = 60Mhz/30/1Khz = 2000(0x7D0)
* 将PRD数据载入TIM,TDDR数据载入PSC;
* 将PRD数据载入TIM,TDDR数据载入PSC;
* Timer0 中断使能开启
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void Timer0Init(void)
{
// 【1. 强制停止定时器 (TSS)】
// 定时器是高速运转的计数器。在给它更换计算周期时,必须先让它停转。
// TSS (Timer Stop Status) 填 1,强制定时器立刻停机,防止在配置过程中发生硬件逻辑错乱。
CpuTimer0Regs.TCR.bit.TSS = 1;

// 【2. 清除历史中断标志 (TIF)】
// TIF (Timer Interrupt Flag) 是中断标志位。写入 1 是一种硬件层面的“清零”操作。
// 这相当于把历史遗留的、可能存在的中断请求全部清除,保证我们是以一个极其干净的状态启动定时器。
CpuTimer0Regs.TCR.bit.TIF = 1;

// 【3. 定时器降速齿轮配置 (TDDR)】
// 系统主频 60MHz 太快了,所以需要预分频。
// 公式规定:实际分频倍数 = TDDR寄存器值 + 1。
// 写入十六进制 0x1D (即十进制 29)。实际分频 = 29 + 1 = 30 倍。
// 降速后定时器的滴答频率 = 60MHz / 30 = 2MHz(即定时器每秒倒数 2,000,000 下)。
CpuTimer0Regs.TPRH.bit.TDDRH = 0x00;
CpuTimer0Regs.TPR.bit.TDDR = 0x1D;

// 【4. 计算倒数起点的周期值 (PRD)】
// 目标是产生 1kHz (1毫秒) 的中断。
// 计算公式:周期值 (PRD) = 降速后的滴答频率 / 目标中断频率。
// PRD = 2,000,000 / 1000 = 2000。
// 把十进制 2000 转换成十六进制即 0x07D0。
// .all 属性允许我们把整个 32 位数据 0x000007D0 一次性灌入 PRD 寄存器。
CpuTimer0Regs.PRD.all = 0x000007D0;

// 【5. 装载起点并开启警报】
// TRB (Timer Reload Bit) 写 1,命令硬件把刚才算好的 2000 装填进实际倒数的计时器中。
CpuTimer0Regs.TCR.bit.TRB = 1;
// TIE (Timer Interrupt Enable) 写 1,允许定时器数到 0 时,正式向 CPU 发送中断警报。
CpuTimer0Regs.TCR.bit.TIE = 1;
}

定时器1 初始化与算法原理解析 (Timer1Init)

我们需要 Timer1 产生一个精确的 200Hz(5ms)的定时中断。

1
2
3
4
5
6
7
8
9
10
* 函    数:void Timer1Init(void)
* 功 能:5mS定时器,200Hz中断
* 配置情况:
* 关闭定时器0
* 清楚定时器0中断标志位
* 分频30,时钟分频为TPR+1;TDDR寄存器为29(0x1D)
* 时钟60Mhz , PRD = 60Mhz/30/200hz = 15000(0x2710)
* 将PRD数据载入TIM,TDDR数据载入PSC;
* 将PRD数据载入TIM,TDDR数据载入PSC;
* Timer0 中断使能开启
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Timer1Init(void)
{
//关闭定时器1
CpuTimer1Regs.TCR.bit.TSS = 1;
//清楚定时器1中断标志位
CpuTimer1Regs.TCR.bit.TIF = 1;
// 分频30,时钟分频为TPR+1;TDDR寄存器为29(0x1D)
CpuTimer1Regs.TPRH.bit.TDDRH = 0x00;
CpuTimer1Regs.TPR.bit.TDDR = 0x1D;
// 时钟60Mhz , PRD = 60Mhz/30/200hz = 10000(0x2710)
CpuTimer1Regs.PRD.all = 0x00002710;
// 将PRD数据载入TIM,TDDR数据载入PSC;
CpuTimer1Regs.TCR.bit.TRB = 1;
// Timer1 中断使能开启
CpuTimer1Regs.TCR.bit.TIE = 1;
}

底层硬件差异:中断寄存器路由图解

中断寄存器
结合《CPU-Timer 中断信号与输出信号》路由图,我们可以彻底弄懂为什么 Timer0 和 Timer1 在中断配置时有着本质区别。

  • 常规路径(CPU-TIMER 0):
    观察图表的最上方:CPU-TIMER 0 溢出产生的信号名叫 TINT0。顺着向左的箭头看去,这个信号必须先经过一个名为 PIE(外设中断扩展)的中间模块,才能最终变为 INT1 信号抵达 28x CPU。
  • 对应代码体现:正因为如此,凡是使用 Timer0 计算控制环路,在 main() 主函数里就必须显式地去配置 PIEIER 寄存器(相当于手动让 PIE 秘书放行)。
  • 直达专线(CPU-TIMER 1 和 2):
    观察图表的下方:CPU-TIMER 1CPU-TIMER 2 产生的信号叫 TINT1TINT2。这两条线直接绕过了 PIE 模块,作为高级别的专线,直接连到了 28x CPU 的 INT13INT14 引脚上。
  • 对应代码体现:所以,在使用 Timer1 的代码配置中,您绝对找不到任何关于 PIE 寄存器的操作,因为它有直达 CPU 的特权,只需打开 CPU 主管道(IER |= M_INT13)即可被立刻响应。
PFC初始化(普通 IO 口与 PWM 模块配置)

PFC初始化:普通 IO 口与 PWM 模块配置

在 PFC(功率因数校正)控制系统中,DSP 需要两种“手”来控制外部电路:

  1. 慢速的手(普通 IO 口): 负责开关继电器、点亮 LED 指示灯、输出简单的状态信号。
  2. 快速且精准的手(PWM 模块): 负责以几万赫兹的频率高频驱动开关管,并精准踩点触发 ADC 采样,这是维持母线电压稳定的核心。

普通 IO 口初始化 (GPIOInit)

普通 IO(General-Purpose Input/Output)也就是通用引脚。在配置一个引脚之前,DSP 默认它是断开或随机状态的。我们需要明确告诉芯片:这个引脚是做普通开关用,还是做特殊通信用?是负责输出电压,还是负责读取电压?

端口分配与设计要求

根据系统硬件设计,我们需要用到以下普通 IO 口:

  • 清理现场: 初始化前,清除 GPIOA(引脚0-31)和 GPIOB(引脚32-63)的所有历史状态,防止误动作。
  • GPIO32: 配置为输出,用于控制继电器的吸合与断开。
  • GPIO22: 配置为输出,用于向外发送 PFC-OK 状态信号。
  • GPIO23: 配置为输出,用于驱动控制板上的状态指示 LED 灯。
  • GPIO24 / GPIO33: 配置为输出,预留给后续的开发与调试使用。

代码逐句精解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void GPIOInit(void)
{
EALLOW; // 解锁受保护的系统寄存器,允许更改 IO 配置

// 1. 数据清零:不管引脚之前是高电平还是低电平,全部强行拉低(清0)
GpioDataRegs.GPADAT.all = 0ul;
GpioDataRegs.GPBDAT.all = 0ul;

// 2. 继电器控制 (GPIO32)
GpioCtrlRegs.GPBMUX1.bit.GPIO32=0; // MUX(复用选择): 0代表当作普通IO口使用
GpioCtrlRegs.GPBDIR.bit.GPIO32=1; // DIR(方向): 1代表作为输出口(向外发信号)
GpioDataRegs.GPBCLEAR.bit.GPIO32=1; // CLEAR: 再次确保上电瞬间继电器是断开的

// 3. PFC-OK信号输出 (GPIO22)
GpioCtrlRegs.GPAMUX2.bit.GPIO22=0;
GpioCtrlRegs.GPADIR.bit.GPIO22=1;

// 4. 控制板上LED (GPIO23)
GpioCtrlRegs.GPAMUX2.bit.GPIO23=0;
GpioCtrlRegs.GPADIR.bit.GPIO23=1;

// 5. 调试端口预留 (GPIO24 & GPIO33)
GpioCtrlRegs.GPAMUX2.bit.GPIO24=0;
GpioCtrlRegs.GPADIR.bit.GPIO24=1;
GpioCtrlRegs.GPBMUX1.bit.GPIO33=0;
GpioCtrlRegs.GPBDIR.bit.GPIO33=1;

EDIS; // 重新上锁,保护寄存器
}

⚙️ 运行逻辑小结:
配置 IO 口通常只需两步:先设定 MUX 决定它的身份(普通脚还是外设脚),再设定 DIR 决定它的方向(输入还是输出)。


核心 PWM 模块初始化 (ePWMInit)

为了将母线电压稳稳地控制在目标值,PFC 使用了单周期控制法。这意味着在极其短暂的一个 PWM 开关周期内,DSP 必须完成一次“电流采样 -> 环路计算 -> 更新占空比”的完整闭环。
PWM发波方式与ADC触发

PWM 发波与 ADC 采样的时序配合

我们采用的是平均电流控制模式。从物理规律上讲,在一个三角载波周期中,电感电流在三角波的顶点处刚好等于这段时间的平均电流。因此,我们必须精准地在三角波的顶点触发 ADC 采样

硬件配置参数计算:

  • 系统主时钟: 60MHz。
  • 目标 PWM 频率: 45kHz。
  • 发波模式: 上升下降模式(先往上数,再往下数,形成等腰三角形)。
  • 周期寄存器 (TBPRD) 值:
    由于是上下数数,相当于走了一个来回,所以要除以 2。

当计数器从 0 数到 667,再从 667 数回 0,刚好耗时 $\frac{1}{45\text{k}}$ 秒。

ePWM的功能有下面的功能

  • 功能1:TB功能,时钟功能和计时功能也就是产生载波的功能

  • 功能2:CC功能,也就是比较器的功能,也就是COMPA

  • 功能3:AQ功能,计时达到COMPA的时候PWM是下降还是上升

  • 功能4:DB功能,死区,在PWMA和PWMB两路中要有死区,但是我们这里只用了一路,就不需要死区

  • 功能5:TZ功能,快速触发信号,快速触发PWM进行过流保护的

  • 功能6:ET功能,事件功能,在载波的最高点产生一个事件,我们用这个事件触发ADC采样

为什么要有“镜像寄存器 (Shadow Register)”?

代码中多次出现了 SHADOW(镜像/影子)字眼。

  • 问题: 如果 DSP 正在发波的过程中,控制算法突然算出了一个新的占空比并直接写入,波形就会瞬间产生畸变,这可能导致母线电压炸机。
  • 解决: 引入“缓冲区”(镜像寄存器)。算法算出的新数据先放在缓冲区排队。等到当前这个周期彻底结束(比如计数器归 0 时),硬件才会自动把新数据放进去。这保证了波形的绝对平滑。

代码逐句精解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*****************************************************************************
* 函 数:void ePWMInit(void)
* 功 能:ePWM初始化函数
* 配置情况:
* ePWM1A配置
* 配置周期值,45kHz 计数上升下降方式 EPwm1Regs.TBPRD=60M/45K/2=667
* 不相移,计数器与时钟信号同步
* 计数上升下降模式
* 禁用相移功能 Master方式
* 周期寄存器使用镜像寄存器,不立即使用
* 同步模式,同步信号来自系统时钟,且在计数器为0时同步
* 使用系统时钟作为TB信号时钟
* CMPA使用镜像寄存器
* CMPA在计数器为0时装载PWM
* 向上计数时,计数器=CMPA时,置高电平
* 向下计数时,计数器=CMPA时,置低电平

/*****************************************************************************

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

#define PWM_PERIOD 667

void ePWMInit(void)
{
EALLOW;

// 1. 引脚身份分配:将 GPIO0 借给 ePWM1A 模块使用
GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 1;

// ----- TB模块 (时基基准:负责定频率和波形形状) -----
EPwm1Regs.TBPRD = PWM_PERIOD; // 填入计算好的 667,决定了 45kHz 频率
EPwm1Regs.TBPHS.half.TBPHS = 0; // 相位差为 0 (不需要和其他相错开)
EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; // 设定为:先上数再下数(三角波)
EPwm1Regs.TBCTL.bit.PHSEN = TB_DISABLE; // 作为主机发波,不需要听从别人的相位
EPwm1Regs.TBCTL.bit.PRDLD = TB_SHADOW; // 【重要】启用周期寄存器的镜像缓冲区
EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_CTR_ZERO; // 当数到 0 时,可以给其他模块发同步信号
EPwm1Regs.TBCTL.bit.CLKDIV = TB_DIV1; // 时钟不分频,全速使用 60MHz
EPwm1Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1;

// ----- CC模块 (比较器:负责划定占空比阈值) -----
EPwm1Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW; // 【重要】启用占空比的镜像缓冲区
EPwm1Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO;// 约定在计数器数到 0 时,才把新占空比生效

// ----- AQ模块 (动作限定:负责决定引脚何时变高/变低) -----
// 结合上方的图片理解:
EPwm1Regs.AQCTLA.bit.CAU = AQ_SET; // 往上数碰到了 CMPA 线,引脚拉高(导通)
EPwm1Regs.AQCTLA.bit.CAD = AQ_CLEAR; // 往下数碰到了 CMPA 线,引脚拉低(关断)
// (B通道这里仅仅是为了凑配,实际并没引出物理波形控制管子)
EPwm1Regs.AQCTLB.bit.CBU = AQ_SET;
EPwm1Regs.AQCTLB.bit.CBD = AQ_CLEAR;

// ----- 重点:提前触发 ADC 采样 (补偿延时) -----
// 理论上顶点是 667。但 ADC 硬件采样需要微小的反应时间。
// 因此我们在数到 650 的时候就提前下达“采样”指令,等 ADC 反应过来时,刚好处于真正的顶点。
EPwm1Regs.CMPB = 650;

// ----- ET模块 (事件触发:负责通知 ADC) -----
EPwm1Regs.ETSEL.bit.SOCAEN = 1; // 使能 SOCA (Start of Conversion A) 触发线
EPwm1Regs.ETSEL.bit.SOCASEL = 2; // 触发条件:当计数器遇到 CMPB (即数到 650 时)
EPwm1Regs.ETPS.bit.SOCAPRD = 1; // 触发频率:每次遇到都触发 (1st event)

// 初始化时,强行把占空比压到 0,防止刚上电时管子误导通
EPwm1Regs.CMPA.half.CMPA = 667;
EDIS;
}


调试用高频 PWM 初始化 (ePWMDebugInit)

在开发过程中,我们需要实时观察 DSP 内部的某些变量(比如数字环路计算出的母线电压参考值)。但 DSP 内部全是数字信号,用示波器看不到。

解决办法(DAC 模拟技术):
利用 ePWM4 模块输出一个高达 200kHz 的高频 PWM 波。通过改变这个高频波的占空比,再经过外部硬件电路上焊接的两级 RC 低通滤波器,就能把它“平滑”成一个模拟电压信号。我们用示波器量这个模拟电压,就能反推 DSP 内部的变量状态。

代码逐句精解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*****************************************************************************
* 函 数:void ePWMDebugInit(void)
* 功 能:ePWM用以配置用来模拟DAC的效果,用以调试用
* 配置情况:
* ePWM4A配置
* 配置周期值,200kHz 计数上升下降方式 EPwm1Regs.TBPRD=60M/200k=300
* 不相移,计数器与时钟信号同步
* 计数上升下降模式
* 禁用相移功能 Master方式
* 周期寄存器使用镜像寄存器,不立即使用
* 同步模式,同步信号来自系统时钟,且在计数器为0时同步
* 使用系统时钟作为TB信号时钟
* CMPA使用镜像寄存器
* CMPA在计数器为0时装载PWM
* 向上计数时,计数器=0时,置高电平
* 向下计数时,计数器=CMPA时,置低电平
* *****************************************************************************/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void ePWMDebugInit(void)
{
EALLOW;

// 1. 引脚分配:把 GPIO6 借给 ePWM4A 做调试输出口
GpioCtrlRegs.GPAMUX1.bit.GPIO6 = 1;

// 2. 高频配置:60MHz / 200kHz = 300
// 注意:这里用的是“上升模式(只往上数)”,所以不用除以 2。波形是锯齿波。
EPwm4Regs.TBPRD = 300;
EPwm4Regs.TBPHS.half.TBPHS = 0;
EPwm4Regs.TBCTL.bit.CTRMODE = TB_COUNT_UP; // 模式区别:这里是锯齿波 (UP)

EPwm4Regs.TBCTL.bit.PHSEN = TB_DISABLE;
EPwm4Regs.TBCTL.bit.PRDLD = TB_SHADOW;
EPwm4Regs.TBCTL.bit.SYNCOSEL = TB_CTR_ZERO;
EPwm4Regs.TBCTL.bit.CLKDIV = TB_DIV1;
EPwm4Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1;

// 3. 比较器缓冲区设置
EPwm4Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW;
EPwm4Regs.CMPCTL.bit.LOADAMODE = CC_CTR_PRD; // 在数到最大值(周期结束)时更新数据

// 4. 发波规则配置
EPwm4Regs.AQCTLA.bit.ZRO = AQ_SET; // 数到 0 的瞬间,拉高
EPwm4Regs.AQCTLA.bit.CAU = AQ_CLEAR; // 往上数遇到 CMPA 阈值时,拉低
// (B通道未用,配置为空动作)
EPwm4Regs.AQCTLB.bit.ZRO = AQ_NO_ACTION;
EPwm4Regs.AQCTLB.bit.CBU = AQ_NO_ACTION;

// 初始占空比设为 0
EPwm4Regs.CMPA.half.CMPA = 0;
EDIS;
}

PFC初始化(ADC模块精解)

PFC初始化:ADC (模数转换) 模块精解

对于初学者来说,ADC(Analog-to-Digital Converter,模数转换器)的配置往往看起来最复杂,因为它涉及到很多内部的“开关”和“通道”。但别担心,我们结合提供的结构图和时序图,一步步把它的底层逻辑拆解清楚。

在我们的 PFC 控制系统中,无论是控制母线电压,还是限制电感电流,DSP 都必须先“看”到这些真实的物理量。ADC 的作用,就是把电路中真实的模拟电压信号,转换成 DSP 能看懂的数字。


预备知识:理解 SOC 与“同时采样”机制

ADC

结合提供的内部架构图,我们需要先弄懂两个核心概念:

1. 什么是 SOC (Start of Conversion)?
SOC 可以理解为去银行办业务的“排队叫号系统”
DSP 内部有 SOC0 到 SOC15 共 16 个“号码牌”。在代码中,我们通过控制这 16 个号码牌来决定采样的顺序。比如,我们规定 SOC0 拥有最高优先级,并且把 SOC0 绑定到通道 ADCINA2,那么当触发信号到来时,ADC 就会优先去采集 A2 通道的电压。

2. 什么是“同时采样 (Simultaneous Mode)”?
观察图中间的部分,你会发现输入电路(Input Circuit)分为上、下两半,分别有两个采样保持器(S/H-AS/H-B)。

  • 这意味着 DSP 可以在同一微秒内,同时给两个通道“拍快照”
  • 在代码中,如果开启了同时采样,SOC0 和 SOC1 就会自动结对。只要触发 SOC0,硬件就会同时采集 A0 和 B0;触发 SOC2,就会同时采集 A1 和 B1。这对于需要严格时间同步的电压和电流环路计算极其重要。

核心控制流:PWM、ADC与中断的完美配合

ADC模块配置

控制代码并不是一直在死循环里计算的,它必须严格踩着节拍运行。请仔细提供的时序图,它揭示了整个系统最核心的心跳节奏:配合 EPWM1 模块触发 ADC 采样,并在采样完成后触发采样中断,以此来对采样数据进行处理。

  1. 触发点: 当 PWM 计数器达到顶点(TBPRD = 667)时,代表此时的电感电流恰好等于平均电流,此时 EPWM1 发出一个触发信号。
  2. ADC 采样: ADC 收到触发信号后,立刻开始采样并转换。
  3. 触发中断: 当 ADC 转换完成,把数据存入结果寄存器后,立刻向 CPU 申请中断(即图中的 ADC中断)。
  4. 算法计算: CPU 暂停手头工作,进入中断服务函数,拿刚刚采到的最新电压电流数据,计算出下一步需要的新 PWM 占空比
  5. 占空比生效: 算出的新占空比先放在镜像寄存器(缓冲区)里,等到 PWM 计数器落到谷底(0)时,新占空比正式更新生效,迎接下一个周期的到来。

ADC 初始化代码逐句精解 (ADCInit)

结合文档说明,我们将代码块拆解并详细解释其配置逻辑:

基础环境与时钟配置

1
2
3
4
5
6
7
8
9
10
11
12
void ADCInit(void)
{
EALLOW; // 解锁寄存器
// ADC 采集数据允许被覆盖 (如果上次的数据CPU没来得及读,新的数据可以直接覆盖旧的)
AdcRegs.ADCCTL2.bit.ADCNONOVERLAP = 0; //

// ADC时钟配置为系统时钟 60MHz,不分频
AdcRegs.ADCCTL2.bit.CLKDIV2EN=0; //

// ADC ONESHOT 模式不启动 (我们采用连续的周期性触发,不需要单次模式)
AdcRegs.SOCPRICTL.bit.ONESHOT = 0; //

配置“叫号系统”的优先级与采样模式

1
2
3
4
5
6
7
8
9
// SOC0-1 / SOC2-3 同时采样
// 写入1代表开启同时采样机制。此时配置SOC0就等于同时配置了SOC1。
AdcRegs.ADCSAMPLEMODE.bit.SIMULEN0=1; //
AdcRegs.ADCSAMPLEMODE.bit.SIMULEN2=1;

// SOC0~3优先级最高,其他的保持轮询顺序优先级
// 0x04代表前4个(即SOC0到SOC3)拥有绝对的优先处理权
AdcRegs.SOCPRICTL.bit.SOCPRIORITY = 0x04; //

通道绑定与触发源选择 (核心步骤)

这里是代码和硬件电路直接对话的地方。我们要告诉 SOC 号码牌对应去读取哪个物理引脚的电压。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SOC0 对应 ADCINTA0(电感电流)
// SOC1 对应 ADCINTB0(母线电压)
AdcRegs.ADCSOC0CTL.bit.CHSEL = 0; // CHSEL=0 意味着选中了 A0 和 B0 这对通道

// ePWM1 的触发 SOC0-SOC3 采样
// TRIGSEL=5 在手册中代表由 ePWM1 的 SOCA 信号来触发
AdcRegs.ADCSOC0CTL.bit.TRIGSEL = 5;

// 所有采样窗口配置为 (8+1)*时钟周期
// 就像相机快门,打开一段时间让电容充满电荷,保证采样准确度
AdcRegs.ADCSOC0CTL.bit.ACQPS = 8;

// SOC2 对应 ADCINTA1(输入电压L)
// SOC3 对应 ADCINTB1(输入电压N)
AdcRegs.ADCSOC2CTL.bit.CHSEL = 1; // CHSEL=1 意味着选中了 A1 和 B1 这对通道

AdcRegs.ADCSOC2CTL.bit.TRIGSEL = 5; // 同样由 ePWM1 的 SOCA 触发
AdcRegs.ADCSOC2CTL.bit.ACQPS = 8; // 采样窗口同样为 (8+1) 个时钟周期

中断配置 (让 ADC 采样完去叫醒 CPU)

采完数据必须及时处理,否则毫无意义。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 当采样数值保存至 Result 寄存器后触发中断
AdcRegs.ADCCTL1.bit.INTPULSEPOS = 1;

// ADCINT1 使能,用来触发中断给 CPU
AdcRegs.INTSEL1N2.bit.INT1E = 1;

// 只有在清中断标志位后才会触发下一次中断 (防止中断死锁)
AdcRegs.INTSEL1N2.bit.INT1CONT = 0;

// EOC3 (End of Conversion 3) 采样结束触发中断
// 我们的优先级配置中,SOC3 是最后一组采样的。当它采完,意味着前面的电流电压全都采齐了,这时候触发中断最合适。
AdcRegs.INTSEL1N2.bit.INT1SEL = 3;

ADC 内部电路上电

ADC 是一个精密的模拟电路,包含了运放、基准源等,需要给这些物理部件接通电源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    // ADC 模拟电路内部核模块上电
AdcRegs.ADCCTL1.bit.ADCPWDN = 1;

// ADC 参考电路内部核模块上电
AdcRegs.ADCCTL1.bit.ADCREFPWD = 1;

// ADC Bandgap电路内部核模块上电
AdcRegs.ADCCTL1.bit.ADCBGPWD = 1;

// 启动ADC,正式投入工作
AdcRegs.ADCCTL1.bit.ADCENABLE = 1;

EDIS; // 重新上锁
}

通过这一章的配置,系统不仅明确了“什么时候采”、“采哪条线的电压”,而且打通了从 PWM 发波到 ADC 采样,再到 CPU 中断算法计算的完整数据通路。

PFC初始化(过流保护和SCI初始化)

PFC初始化-过流保护和SCI初始化

在开发数字电源控制系统(特别是用于稳压公共直流母线的 PFC 级)时,除了常规的电压电流双闭环控制,系统底层的安全响应机制级联设备的通信连接是必不可少的两环。

这一章,我们将按照工程逻辑,为你详细拆解这两个独立且重要的硬件模块:

  1. 硬件过流保护 (TZ 模块):这相当于给你的功率开关管上了一份“硬件级”的物理保险,由于它不需要 CPU 介入计算,其反应速度远超代码算法。
  2. 串口通信 (SCI 模块):PFC 作为前级电源,需要与后级的 LLC 变换器交流信息(比如交互母线电压的状态或故障码),SCI 就是它们之间的“对讲机”。

硬件过流保护与比较器初始化 (TZInit)

为什么有了算法保护,还需要 TZ (Trip Zone) 保护?

在软件算法控制中,如果系统通过 ADC 采样 $\rightarrow$ CPU 计算闭环 $\rightarrow$ 发现严重过流 $\rightarrow$ 关断 PWM,这个全过程最快也需要几个微秒。但在实际的高频母线电压控制中,如果负载侧瞬间短路,MOS 管在一微秒内就会因瞬间大电流而炸毁。

因此,DSP 硬件架构中提供了一种纯硬件的物理保护机制——Trip Zone (TZ)。PFC 控制具有电流硬件过流快速关闭 PWM 的功能。它完全绕过了 CPU 的代码逻辑,在底层直接将电流传感器的模拟信号与我们设定的危险阈值进行比较,一旦超标,通过事件触发 TZ 模块直接关闭 PWM。

保护电路信号流向分析

TZ与比较器配合
我们结合原理图来理解这个物理连线过程:

  • 捕捉 MOS 电流:外围硬件采样电阻测到的 MOS 电流信号,在经过一级简单的 RC 低通滤波电路后,被直接送入了 DSP 内部比较器的正端 (+) 输入端口。
  • 划定危险红线 (DAC 参考值):我们在代码中利用 DSP 内部集成的 DAC(数模转换)模块,生成了一个固定的模拟基准电压,将其送入比较器的负端 (-)
  • 硬件触发锁死:MOS 电流与内置 DAC 产生的基准电压进行实时的模拟比较。当代表真实电流的电压转换量大于设定的 DAC 基准电压时,比较器输出发生硬件翻转。这个翻转信号经过反向后,产生 TZ 事件信号(即图中拉低的负脉冲 TZ1)。
  • 切断输出:PWM 模块的硬件机制检测到 TZ1 收到负脉冲,就会立刻强制触发 PWM 输出关闭,从而瞬间保护开关管免受损害。

代码逐句精解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void TZInit(void)
{
EALLOW; // 解锁底层保护寄存器

// 【1. 引脚功能分配:打通物理层连线】
// 依据硬件设计,配置 GPIO42 为比较器的输出端口
GpioCtrlRegs.GPBMUX1.bit.GPIO42 = 3;
// 配置模拟引脚 AIO2 为比较器的正端输入,用来接入经过滤波后的真实 MOS 电流信号
GpioCtrlRegs.AIOMUX1.bit.AIO2 = 2;
// 配置 GPIO15 作为 PWM 模块的 TZ (错误联锁) 事件输入端口
GpioCtrlRegs.GPAMUX1.bit.GPIO15 = 1;

// 【2. 比较器工作模式配置】
// 硬件防抖滤波功能:接受数据连续 5 个系统时钟 (SYSCLKOUT) 保持一致,比较器才正式输出信号。这主要是为了滤除开关瞬间的电磁高频尖峰干扰。

Comp1Regs.COMPCTL.bit.QUALSEL = 5;
// 配合 TZ 信号低电平有效的特性,设置输出信号反向
// 当没有过流时 (COMP+ < COMP-),反向输出高电平;当发生过流时 (COMP+ > COMP-),反向输出低电平去拉响警报
Comp1Regs.COMPCTL.bit.CMPINV = 1;
// 将比较器的负端 (COMP-) 硬件链接至内部的 DAC 模块,以此作为比较基准线
Comp1Regs.COMPCTL.bit.COMPSOURCE = 0;

// 【3. 保护阈值设定】
// 配置内部 DAC 有 DAC Valu 寄存器进行数值控制
Comp1Regs.DACCTL.bit.DACSOURCE = 0;
// 设定具体的硬件过流保护点阈值
// 这里的 1000 就是换算后的 DAC 数字量,代表了我们系统允许的最大安全电感电流的界限。
Comp1Regs.DACVAL.bit.DACVAL = 1000;

// 【4. 模块启动】
// 正式使能比较器和内部 DAC 模块,系统硬件保护机制开始巡逻
Comp1Regs.COMPCTL.bit.COMPDACEN = 1;

// 【5. TZ 模块配置 (收到警报后的动作)】
// 首先清空内部 TZ 寄存器中的历史错误标志,确保开机状态是纯净的
EPwm1Regs.TZCLR.bit.CBC = 1;
// 使能 TZ1 的单次触发功能 (One Shot Signal)。只要触发一次过流,管子就一直关闭直到人为复位
EPwm1Regs.TZSEL.bit.CBC1 = TZ_ENABLE;
// 【最关键的一步】当 TZ1 事件发生时,硬件强制将发波的 EPWM1A 信号拉低(切断 PWM)
EPwm1Regs.TZCTL.bit.TZA = TZ_FORCE_LO;

// 将所有相关的 TZ 中断标志位全部清 0
EPwm1Regs.TZCLR.all = 0xFFFF;
// 中断均不使能。因为硬件本身已经把门关死了,无需再产生中断去劳烦 CPU 处理。
EPwm1Regs.TZEINT.all = 0;

EDIS;
}


串口通信初始化 (SCIInit)

PFC 作为整个系统的前端功率校正级,承担着控制母线电压的任务。但在实际的高级电源架构中,PFC 的 SCI 通信功能主要用于实现与后级 LLC 模块相互进行信息交流。

串口通信波特率底层的计算数学

要保证两个芯片通过 TX/RX 顺利通讯,它们的编解码速率(Baud Rate,波特率)必须严丝合缝。你的代码中将 SCI 通信速率配置为 4800 bit/s。

在 TI DSP 中,波特率是通过内部的时钟预分频电路产生的,其数学推导公式如下:

已知我们设定的系统低速外设时钟频率 (LOSPCP) 同样跑在系统主频的 $60\text{MHz}$,将目标波特率 4800 代入公式,反推核心的分频寄存器值 ($\text{BRR}$):

把十进制的 390 转换为十六进制整数,刚好等于 0x0186。这就是代码注释中公式的来源,也是我们要在代码中填入 SCIHBAUDSCILBAUD 寄存器的数字。

代码逐句精解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void SCIInit(void)
{
EALLOW;

// 【1. 硬件引脚分配】
// 根据原理图,配置 GPIO12 为 SCI 通信功能的发送口 (TX)
GpioCtrlRegs.GPAMUX1.bit.GPIO12 = 2;
// 配置 GPIO7 为 SCI 通信功能的接收口 (RX)
GpioCtrlRegs.GPAMUX1.bit.GPIO7 = 2;

// 【2. 数据帧格式配置 (制定通信协议)】
// 这部分配置本质上与我们在电脑串口调试助手里设置的参数是完全对应的。
// 设置 SCI 通信数据 1 个停止位
SciaRegs.SCICCR.bit.STOPBITS = 0x0;
// 设置 SCI 通信数据为 8 个 bit 方式 (即 1 字节长)
SciaRegs.SCICCR.bit.SCICHAR = 0x7;
// 设置为无奇偶校验位 (No parity)
SciaRegs.SCICCR.bit.PARITYENA = 0x0;
// 禁用 loop 循环测试功能。该功能仅在芯片自己发给自己测试连通性时使用
SciaRegs.SCICCR.bit.LOOPBKENA = 0x0;
// 配置多机通信方式,使用地址寻址模式
SciaRegs.SCICCR.bit.ADDRIDLE_MODE = 0x1;

// 【3. 功能控制使能】
// 硬件级使能接收功能 (RX)
SciaRegs.SCICTL1.bit.RXENA = 0x1;
// 硬件级使能发送功能 (TX)
SciaRegs.SCICTL1.bit.TXENA = 0x1;
// 禁用通信数据错误时产生的中断
SciaRegs.SCICTL1.bit.RXERRINTENA = 0x0;
// 禁止模块进入睡眠模式,让其保持持续监听状态
SciaRegs.SCICTL1.bit.SLEEP = 0x0;

// 使能接收中断功能。这非常重要,这意味着一旦 LLC 后级发来最新的母线设定指令或状态信息,CPU 会立刻放下手中的活去读取。
SciaRegs.SCICTL2.bit.RXBKINTENA = 0x1;
// 关闭发送中断功能。因为发送数据通常扔给外设缓冲器去发就行了,不需要随时监控它什么时候发完。
SciaRegs.SCICTL2.bit.TXINTENA = 0x0;

// 【4. 通信波特率装载】
// 将上文计算出的分频常数 0x0186 装载进高低位寄存器,确立配置 SCI 通信速率为 4800 bit
// 高 8 位装载 0x0001
SciaRegs.SCIHBAUD = 0x0001;
// 低 8 位装载 0x0086
SciaRegs.SCILBAUD = 0x0086;

// 【5. 模块复位启动】
// 将 SWRESET 位置 1,复位 SCI 模块内部状态机,让串口正式投入通信运行
SciaRegs.SCICTL1.bit.SWRESET = 0x1;

EDIS;
}

ADC数据采集与数字量标幺化转换

ADC数据采集与数字量标幺化转换

在数字电源控制系统中,DSP 芯片无法直接“理解”外围的模拟电压和电流。必须通过硬件分压、运放调理后,交由 ADC(模数转换器)读取,最后通过严谨的定点数数学运算,才能转化为控制算法可用的数字量。本章将详细拆解这一从“模拟硬件”到“数字代码”的完整转换链路。

基础概念:定点数(Q格式)与系统参数定义

在数字控制中,为了避免极其消耗 CPU 资源的浮点数除法运算,我们通常采用定点数(Q格式)进行标幺化处理。DSP 中尽量不要用除法,而是利用移位操作:向左移位相当于乘法(如左移三位相当于 $\times 8$),向右移位相当于除法。

常规数字量定义:
内部运算中定义了以下 Q 格式基准:
$Q10 = 2^{10}$,$Q12 = 2^{12}$,$Q13 = 2^{13}$,$Q14 = 2^{14}$,$Q15 = 2^{15}$,$Q16 = 2^{16}$

在本 PFC 系统中,统一采用 Q15 格式作为运算标准,即内部数字 $32768$ 代表 ADC 的最大满量程(对应 DSP 引脚输入电压 $3.3\text{V}$)。

PFC工作整体参数定义:

  • 交流输入最低,额定,最高有效值:
    $V_{acmin} = 187\text{V}$
    $V_{acnom} = 220\text{V}$
    $V_{acmax} = 253\text{V}$
  • 定义 PFC 工作开关频率为:
    $f_s = 45\text{kHz}$
  • PFC 电感为:
    $L_{PFC} = 1.5\text{mH}$
  • 电网最小,额定,最大频率:
    $f_{lmin} = 45\text{Hz}$
    $f_{lnom} = 50\text{Hz}$
    $f_{lmax} = 70\text{Hz}$
  • 额定母线电压(本系统致力于稳定公共直流母线电压):
    $V_{bus} = 380\text{V}$

ADC 中断整体控制流

每个开关周期采样后会有一个 ADC 采样中断。ADC 中断在 SOC3 数据采集完且装载寄存器后触发,拥有最高的中断优先级(优先级 0,快速中断)。
ADC中断

正如上图的流程所示,ADC 中断函数是整个控制系统的心跳,内部包含了从采样到闭环计算再到更新发波的全过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//采样变量结构体
struct _ADI
{
long Imos; //MOS电流变量Q15
long ImosOffset; //MOS电流偏置变量Q15
long ImosK; //DCM模式下电流采样矫正系数
long VacL; //输入电压L变量 Q15
long VacN; //输入电压N变量 Q15
long Vac; //输入电压变量 Q15
long VacAbs; //输入电压绝对值变量 Q15
long Vbus; //母线电压变量 Q15
};

__interrupt void ISR_ADC(void)
{
// SET_DEBG_GPIO24();

// 1. ADC采样函数,获取电感电流和输入电压、母线电压
ADCSample();
// 2. 锁相环处理
PhaseLockLoop();
// 3. 前馈环计算
FeedCal();
// 4. 参考电流计算
IrefCal();
// 5. 电流环计算
CurrentLoop();
// 6. PWM寄存器更新,占空比更新
RegReflash();
// 7. DCM模式电流采样矫正
Icalibrate();
// 8. 硬件过流保护
HwOcp();

// CLR_DEBG_GPIO24();
// 清中断标志位,为下一次中断做准备
AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1;
// Acknowledge interrupt to PIE
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
return;
}

硬件转换逻辑与 ADC 采样函数解析

ADCSample() 函数的主要任务是从 ADC 结果寄存器中提取原始机器码,统一转换为 Q15 格式的数据,并转存至指定的结构体变量中。为了彻底理解代码中的比例关系,我们需要结合硬件原理图进行数学推导。

交流电网电压与母线电压采样

1. 交流电网电压采样硬件与公式推导
对于交流电网,VacL 是火线,VacN 是零线。由于是交流电,我们需要分别对火线和零线进行独立采样,而后在软件中做差分计算。
输入电压转换

根据电路原理图定义,数据交流电压采样电阻分别为:
$RA_{30} = 243\text{k}\Omega$
$RA_{31} = 243\text{k}\Omega$
$RA_{33} = 3.3\text{k}\Omega$

DSP 能够采样到的最高 ADC 口电压为:
$V_{dsp} = 3.3\text{V}$
采集的电压是小电阻RA33上的电压
根据分压原理反推出 DSP 能够采样到的输入最高电压为:

定义 Q15 数据为 DSP 最大量程电压数字量(对应 3.3V),则得到输入电压 L 线或者 N 线对应的数字量函数为:

输入电压 占最大可测电压 的比例,就等于 DSP 内部数字量占 Q15 最大量程的比例。
举例说明,输入 L 线或者 N 线对 PGND 的电压为 $220\text{V}$,则对应 DSP 内部的数字量为:

2. 母线电压采样硬件与公式推导
控制核心是稳定直流母线电压,其采样网络结构与交流输入完全一致。
母线电压转换

根据电路原理图定义,数据交流电压采样电阻分别为:
$RA_{17} = 243\text{k}\Omega$
$RA_{18} = 243\text{k}\Omega$
$RA_{21} = 3.3\text{k}\Omega$

根据分压原理反推出 DSP 能够采样到的 BUS 最高电压为:

同样,得到母线电压对应的数字量函数为:

举例说明,额定母线电压为 $380\text{V}$,则对应 DSP 内部的数字量为:

MOS 电流采样与硬件偏置

这是最容易踩坑的环节。电流是通过底部的精密检流电阻转化为电压,再经过运放放大的。
MOS电流
MOS电流转换

设计使用运放的 1 通道构建零偏置电压,定义偏置电路的电阻参数为:
$RA_{20} = 4.7\text{k}\Omega$
$RA_{16} = 1\text{k}\Omega$

计算偏置电压(避免运放零偏)为:

定义 MOS 电流采样电阻与差分放大电路的电阻为:
$RA_{13} = 0.01\Omega$
$RA_{22} = 2.2\text{k}\Omega$
$RA_{23} = 100\Omega$

则差分电流变换的运放增益为:

根据差分变换电路增益,采样电阻,和 DSP 标定电压 3.3V,计算 MOS 电流最大采样值为:

定义 Q15 数据对应的函数为:

举例,当 MOS 电流为 $0.15\text{A}$,对应内部数字量为:$I_{Ldsp}(0.15\text{A}) = 327.68$

数据转换代码实现 (ADCSample)

结合上面的硬件推导,我们再来看代码就会一目了然:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
** ===================================================================
** Funtion Name : void ADCSample(void)
** Description : 采样MOS电流和输入电压,BUS电压
** ===================================================================
*/
void ADCSample(void)
{
// 1. Mos电流处理
// DSP内置ADC本身为12位精度(Q12)。为了统一标量,将采样值更换成Q15,即左移3位 (相当于乘以8)。
// SADC.ImosOffset为电流偏置,对应上述计算出的 0.877V 的偏置电压。必须在软件中减去这个抬升量。
SADC.Imos = (AdcResult.ADCRESULT0<<3) - SADC.ImosOffset;

// 对处理后的MOS电流进行小于零限制,防止计算溢出引发控制发散
if(SADC.Imos<0)
SADC.Imos = 0;

// 2. DCM模式下电流采样矫正
// PFC采用平均电流控制。CCM模式时,上升沿中心点即代表平均值。
// 但在DCM(断续)模式下,占空比很小,电感电流早就下降到0,此时中心点偏左,采到的值偏大。
// 因此需要乘以矫正系数K (0.7)。这里的代码逻辑是:先乘0.7的Q15格式数据,再右移15位(等同于除法还原)。
SADC.Imos = SADC.Imos*SADC.ImosK>>15;

// 3. 母线电压与交流电压获取
// 同样左移3位,将Q12转存为Q15数据
SADC.Vbus = AdcResult.ADCRESULT1<<3; // 母线电压 Q15
SADC.VacL = AdcResult.ADCRESULT2<<3; // 输入电压L线 Q15
SADC.VacN = AdcResult.ADCRESULT3<<3; // 输入电压N线 Q15

// 计算实际输入电压VAC:根据全桥整流原理,火线与零线采样值相减即为真实输入交流电压
SADC.Vac = SADC.VacL - SADC.VacN;
}

通过这一套完整的“采集 $\rightarrow$ 格式扩位 $\rightarrow$ 扣除硬件偏置 $\rightarrow$ 模式矫正”流程,DSP 终于在内存中还原出了高精度的真实物理世界波形,为后续的数字环路运算打下了坚实基础。

PFC锁相环代码分析

PFC锁相环代码分析

在PFC(功率因数校正)控制中,如果直接使用ADC采样的电网电压作为参考,电网中的噪声、谐波和突变都会导致控制系统发散。

因此,软件锁相环(PLL) 的核心目的在于:通过内部算法,构建出一个与实际输入电网电压同相位、同频率的“完美”标准正弦波信号(峰峰值为1)。用这个纯净的信号构造电流的参考值。

下面我们将把代码、公式推导、定标原理彻底拆解,让你真正看懂每一步为什么这么做。

核心基石:标准化正弦数据表

为了避免在单片机中进行极其消耗CPU算力的浮点正弦函数计算,我们在程序中提前写死了一张包含 1024 个点的正弦数据表。这张表代表了完美的半个正弦波

这张表里的数值是用 Q12格式 存储的。所谓 Q12,就是把真实的小数放大了 $2^{12}$(即 4096)倍。由于正弦波的最大值是 1,所以表中峰值对应 $1 \times 4096 - 1 = 4095$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#ifndef TABLE_H_
#define TABLE_H_

//正弦表Q12
int sinetable[1024] =
{

0,12,25,37,50,62,75,87,100,113,125,138,150,163,175,188,200,213,226,238,251,263,276,288,301,313,
326,338,351,363,376,388,401,413,426,438,451,463,476,488,501,513,526,538,551,563,576,588,601,613,
625,638,650,663,675,687,700,712,725,737,749,762,774,786,799,811,823,836,848,860,872,885,897,909,
921,934,946,958,970,983,995,1007,1019,1031,1043,1056,1068,1080,1092,1104,1116,1128,1140,1152,
1164,1176,1189,1201,1213,1225,1237,1248,1260,1272,1284,1296,1308,1320,1332,1344,1356,1368,1379,1391,
1403,1415,1427,1438,1450,1462,1474,1485,1497,1509,1520,1532,1544,1555,1567,1579,1590,1602,1613,1625,
1636,1648,1659,1671,1682,1694,1705,1717,1728,1739,1751,1762,1773,1785,1796,1807,1819,1830,1841,1852,
1864,1875,1886,1897,1908,1919,1930,1941,1952,1964,1975,1986,1997,2007,2018,2029,2040,2051,2062,2073,
2084,2094,2105,2116,2127,2138,2148,2159,2170,2180,2191,2201,2212,2223,2233,2244,2254,2265,2275,2286,
2296,2306,2317,2327,2337,2348,2358,2368,2379,2389,2399,2409,2419,2429,2439,2450,2460,2470,2480,2490,
2500,2510,2519,2529,2539,2549,2559,2569,2578,2588,2598,2608,2617,2627,2637,2646,2656,2665,2675,2684,
2694,2703,2713,2722,2732,2741,2750,2760,2769,2778,2787,2796,2806,2815,2824,2833,2842,2851,2860,2869,
2878,2887,2896,2905,2914,2922,2931,2940,2949,2957,2966,2975,2983,2992,3000,3009,3018,3026,3034,3043,
3051,3060,3068,3076,3085,3093,3101,3109,3117,3126,3134,3142,3150,3158,3166,3174,3182,3190,3197,3205,
3213,3221,3229,3236,3244,3252,3259,3267,3274,3282,3289,3297,3304,3312,3319,3326,3334,3341,3348,3356,
3363,3370,3377,3384,3391,3398,3405,3412,3419,3426,3433,3440,3447,3453,3460,3467,3473,3480,3487,3493,
3500,3506,3513,3519,3526,3532,3538,3545,3551,3557,3563,3570,3576,3582,3588,3594,3600,3606,3612,3618,
3624,3629,3635,3641,3647,3652,3658,3664,3669,3675,3680,3686,3691,3697,3702,3708,3713,3718,3723,3729,
3734,3739,3744,3749,3754,3759,3764,3769,3774,3779,3784,3789,3793,3798,3803,3807,3812,3816,3821,3826,
3830,3834,3839,3843,3848,3852,3856,3860,3864,3869,3873,3877,3881,3885,3889,3893,3897,3900,3904,3908,
3912,3915,3919,3923,3926,3930,3933,3937,3940,3944,3947,3950,3954,3957,3960,3963,3967,3970,3973,3976,
3979,3982,3985,3988,3990,3993,3996,3999,4001,4004,4007,4009,4012,4014,4017,4019,4022,4024,4026,4029,
4031,4033,4035,4037,4039,4041,4043,4045,4047,4049,4051,4053,4055,4057,4058,4060,4062,4063,4065,4066,
4068,4069,4071,4072,4073,4075,4076,4077,4078,4079,4080,4081,4082,4083,4084,4085,4086,4087,4088,4089,
4089,4090,4091,4091,4092,4092,4093,4093,4094,4094,4094,4095,4095,4095,4095,4095,4095,4095,4095,4095,
4095,4095,4095,4095,4095,4095,4094,4094,4094,4093,4093,4092,4092,4091,4091,4090,4089,4089,4088,4087,
4086,4085,4084,4083,4082,4081,4080,4079,4078,4077,4076,4075,4073,4072,4071,4069,4068,4066,4065,4063,
4062,4060,4058,4057,4055,4053,4051,4049,4047,4045,4043,4041,4039,4037,4035,4033,4031,4029,4026,4024,
4022,4019,4017,4014,4012,4009,4007,4004,4001,3999,3996,3993,3990,3988,3985,3982,3979,3976,3973,3970,
3967,3963,3960,3957,3954,3950,3947,3944,3940,3937,3933,3930,3926,3923,3919,3915,3912,3908,3904,3900,
3897,3893,3889,3885,3881,3877,3873,3869,3864,3860,3856,3852,3848,3843,3839,3834,3830,3826,3821,3816,
3812,3807,3803,3798,3793,3789,3784,3779,3774,3769,3764,3759,3754,3749,3744,3739,3734,3729,3723,3718,
3713,3708,3702,3697,3691,3686,3680,3675,3669,3664,3658,3652,3647,3641,3635,3629,3624,3618,3612,3606,
3600,3594,3588,3582,3576,3570,3563,3557,3551,3545,3538,3532,3526,3519,3513,3506,3500,3493,3487,3480,
3473,3467,3460,3453,3447,3440,3433,3426,3419,3412,3405,3398,3391,3384,3377,3370,3363,3356,3348,3341,
3334,3326,3319,3312,3304,3297,3289,3282,3274,3267,3259,3252,3244,3236,3229,3221,3213,3205,3197,3190,
3182,3174,3166,3158,3150,3142,3134,3126,3117,3109,3101,3093,3085,3076,3068,3060,3051,3043,3034,3026,
3018,3009,3000,2992,2983,2975,2966,2957,2949,2940,2931,2922,2914,2905,2896,2887,2878,2869,2860,2851,
2842,2833,2824,2815,2806,2796,2787,2778,2769,2760,2750,2741,2732,2722,2713,2703,2694,2684,2675,2665,
2656,2646,2637,2627,2617,2608,2598,2588,2578,2569,2559,2549,2539,2529,2519,2510,2500,2490,2480,2470,
2460,2450,2439,2429,2419,2409,2399,2389,2379,2368,2358,2348,2337,2327,2317,2306,2296,2286,2275,2265,
2254,2244,2233,2223,2212,2201,2191,2180,2170,2159,2148,2138,2127,2116,2105,2094,2084,2073,2062,2051,
2040,2029,2018,2007,1997,1986,1975,1964,1952,1941,1930,1919,1908,1897,1886,1875,1864,1852,1841,1830,
1819,1807,1796,1785,1773,1762,1751,1739,1728,1717,1705,1694,1682,1671,1659,1648,1636,1625,1613,1602,
1590,1579,1567,1555,1544,1532,1520,1509,1497,1485,1474,1462,1450,1438,1427,1415,1403,1391,1379,1368,
1356,1344,1332,1320,1308,1296,1284,1272,1260,1248,1237,1225,1213,1201,1189,1176,1164,1152,1140,1128,
1116,1104,1092,1080,1068,1056,1043,1031,1019,1007,995,983,970,958,946,934,921,909,897,885,872,860,
848,836,823,811,799,786,774,762,749,737,725,712,700,687,675,663,650,638,625,613,601,588,576,563,
551,538,526,513,501,488,476,463,451,438,426,413,401,388,376,363,351,338,326,313,301,288,276,263,
251,238,226,213,200,188,175,163,150,138,125,113,100,87,75,62,50,37,25,12
};


查表步长的数学推导与 Q12 定标处理

问题来了:如果一次中断我们查表走 1 格,1024 个中断才能走完半个正弦波。但实际上我们进中断的次数是受开关频率控制的。

我们如何确保在“真正的半个电网周期内”,刚好把这 1024 个点匀速取完?这就是计算步长 (Step) 的核心逻辑。

若正弦表查表时间为 $T_{table_search}$(即我们的开关周期),电网周期时间为 $T_{line}$,则在半个工频周期内,单片机总共会进去查表的次数 $N_{total}$ 为:

其中 $n$ 代表每间隔 $n$ 个开关周期查表一次(在本程序中,每个周期均查表,即 $n=1$),$F_{switch}$ 为开关频率。计算每次查表步长 $Step$(即每次中断要在表中跨越多少个点)的公式为:

以工频 50Hz 为例进行推演:

软件自定义的半正弦表数据为 1024 个,即正弦表长度为:

若锁相环计算频率为 45kHz(开关周期),那么在半个工频周期(0.01秒)内总共可以计算 450 次(45kHz / 100Hz):

这个 0.01秒 是由我们日常使用的交流电标准频率推导出来的,计算过程非常简单。

  1. 确定工频频率:
    在我国(以及世界上绝大多数国家),标准的市电频率(即工频)是 50Hz。这意味着电网中的交流电每秒钟会完成 50 个完整的正弦波循环。

  2. 计算一个完整的周期时间:
    周期 $T$ 和频率 $f$ 互为倒数,公式为:

    所以,一个完整的 50Hz 工频周期时间为:

  3. 计算半个周期时间:
    既然一个完整的周期是 0.02秒,那么提到的半个工频周期自然就是:

💡 补充说明图片中为什么除以 100Hz:
这里的 100Hz 指的是“半周期的频率”。因为 1 秒钟内电网有 50 个完整的周期,也就意味着有 100 个“半周期”(一正一负交替,1秒钟过零 100 次)。
因此,DSP 的开关频率 45000Hz 也就是一秒钟计算 45000 次,平摊到这 100 个半周期里,每次半个周期(0.01秒)内刚好执行 $45000 / 100 = 450$ 次计算。

为了让 450 次计算刚好走完 1024 个数据点,每次从表中取数的步长应当是:

定标的必要性与 Q12 转化:
由于 2.276 是一个带小数的数字,DSP 如果直接做浮点累加会极耗资源且容易丢失精度(因为 $N_{total}$ 值越大,$Step$ 越小,锁相越需要精确)。
因此,我们对其进行 Q12 放大的定标处理,将小数化为大整数:

(注:也就是 $2.276 \times 4096 \approx 9322$)

同时,电网频率并不是绝对稳定在 50Hz 的。由锁相环的工作原理可知,在不同的输入电网频率下对应的锁相环计算步长不一样。由此可以计算出在极限情况下的步长边界:

这就解释了稍后代码中为何会有 >13005<8389 的步长限制保护参数。

这两个公式是为了推导锁相环步长在极端电网频率下的上下限保护值。在实际的工业环境中,电网频率不可能永远完美保持在 50Hz,它可能会因为电网负荷波动而升高或降低。为了防止软件因为误采样或频率突变算出极其离谱的步长导致系统崩溃,需要通过这些公式提前锁定一个允许的最大值和最小值。

逐个拆解公式里这些字母参数的物理和算法含义:


  • 这就是我们在代码里最终要使用的步长极限保护参数(经过定标放大后的纯整数)。图片中算出其分别对应 $1.305 \times 10^4$ (即 13005) 和 $8.389 \times 10^3$ (即 8389)。

  • $PLL_{num}$:
    正弦表的数据长度。在我们的程序中,半个标准正弦波由 1024 个离散点组成,所以这个常数固定为 1024

  • $f_s$:
    系统的开关频率(或中断计算频率)。代表 DSP 一秒钟内能够执行多少次锁相环代码。前面的设定中是 45000(45kHz)。

  • $f_{lmax}$ 与 $f_{lmin}$:
    系统允许的输入电网最高与最低极限频率(Line Frequency Max / Min)。

    • 如果把 $f_{lmax}$ 设为 70Hz 左右代入公式,算出来的步长就是 13005。意味着电网频率再怎么快,我的步子最大也就迈这么大。
    • 如果把 $f_{lmin}$ 设为 45Hz 左右代入公式,算出来的步长就是 8389。
  • $2 \cdot f_{lmax}$ (分母中的乘 2):
    为什么电网频率要乘 2?因为交流电的一个完整周期包含“正半周”和“负半周”两个部分。我们代码里用到的 1024 这个表,只代表了“半个波”。所以对应过零检测的频率,自然是实际电网频率的 2 倍。

  • 分母整体
    这一整块代表在极端频率下,半个电网周期内 DSP 到底能触发多少次中断(也就是总共能走多少步)。用总长度 1024 除以这个步数,得到的就是真实的物理步长(带小数)。

  • $Q12$:
    定标放大倍数。为了不让 DSP 去算耗时的小数,把前面算出来的带小数的真实步长,统一乘以 $2^{12}$(也就是 4096),放大成整数供代码直接使用。


步长的动态 PI 补偿(自适应频率变化)

如果电网永远是绝对的 50.00Hz,那步长永远是 2.276。但实际电网频率会漂移,如果固定步长,跑完 450 次中断后,我们累加出来的正弦表位置可能不到 1024,或者超过 1024。

为了修正误差,引入了 PI(比例积分)补偿的思想。 每半个工频周期结束(电网发生实际过零)时,我们进行一次“对账”:

PI调节步长

  1. 若 $T > T_{line}$(电网周期变短,频率升高): 真实的硬件电网已经过零了,但我们的锁相环累计还没跑到 1024。这说明原来的 Step 太小了,产生了正误差($E_{cnt} > 0$)。此时通过 PI 调节器将步长增大 (step ↑)。
  2. 若 $T < T_{line}$(电网周期变长,频率降低): 我们的锁相环早就提前跑完 1024 了,结果真实的硬件电网才发生过零。这说明 Step 太大了,产生了负误差($E_{cnt} < 0$)。此时通过 PI 调节器将步长减小 (step ↓)。

当电网周期量增大时,环路计算出的步长量增加;反之则减小。这使得锁相环能时刻贴合实际电网。


软件代码实现流程拆解

锁相环函数具体程序流程图如下图所示。函数首先判断输入电压大于零还是小于零,根据输入电压极性进入不同的分支。

锁相环流程图

过零防抖设计: 当电压大于0时,进入正半周期锁相处理。为消除误采样或电网畸变引起的过零误判断,程序必须判断前半周期的计数值(大于100)。这就意味着如果刚过零又马上因为噪声跳回负数,程序不会被骗。

如果在周期末尾(真正的过零瞬间),程序会计算上一周期的锁相环误差(1023 - 当前指针)。若误差大于零,增加步长;若误差小于零,减小步长。正半周期与负半周期代码完全对称。当在负半周期计数值达到100时,说明波形稳定,有效值计算标志位将被置1。

结合上面的逻辑,代码逐行解析如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/*
** ===================================================================
** Funtion Name : void PhaseLockLoop(void)
** Description : 输入锁相环函数,求取合适的步长,输出正弦表的位置
** Parameters :
** Returns :
** ===================================================================
*/
//锁相环路表为1023表 (即数组最大下标是 1024-1 = 1023)
#define MAX_SINE_CNT 1023
//锁相环路计算K值 (相当于PI调节中的比例参数P)
#define PLL_K 3
//有效值开始计算标志位,=1表明可以进行有效值计算
char RmsCalReadyFlag=0;

void PhaseLockLoop(void)
{
// 输入电压>0 ,输入相电压进入正半周期
if(SADC.Vac > 0)
{
// 关键逻辑:输入电压之前为负,现在由负转正,且前期负半周期计数值>100
// (>100的作用是防抖,确保真的是一个完整的半周期结束了,而不是毛刺)
if((PLL.Polar == 0)&&(PLL.NegCnt>100))
{
// 计数器清零,准备新的半周期
PLL.PosCnt=0;
PLL.NegCnt=0;
// 输入电压标志位设为正
PLL.Polar = 1;
// PLL查表指针求和清零,从头开始查表
PLL.StepSum= 0;

// 【核心PI计算】:看一眼周期结束时,虚拟指针离1023差多少
PLL.CntErr = MAX_SINE_CNT - PLL.Cnt;

// 闭环计算PLL的步长:将误差乘以系数K,补偿到原来的步长上
PLL.Step += PLL.CntErr * PLL_K;

// 步长极限保护:对应上面公式算出的最大13005和最小8389
if(PLL.Step >13005)
PLL.Step = 13005;
else if(PLL.Step < 8389)
PLL.Step = 8389;
}
// 正半周计数器计数累加
PLL.PosCnt ++;
// 电压,电流值累加Q15(为后续RMS计算做准备)
Vac.Sum += SADC.Vac;
Iac.Sum += SADC.Imos;
}
else if(SADC.Vac< 0) // 当输入电压<0 为输入相电压进入负半周期
{
// 输入电压首次由正转负,且前期正半周期计数值>100
if((PLL.Polar == 1)&&(PLL.PosCnt>100))
{
// 逻辑与上方正半周完全对称
PLL.NegCnt=0;
// 输入电压首次由负转正标志位清零
PLL.Polar = 0;
// PLL求和清零
PLL.StepSum = 0;
// 计算PLL误差
PLL.CntErr= MAX_SINE_CNT - PLL.Cnt;
// 闭环计算PLL的步长
PLL.Step += PLL.CntErr* PLL_K;

// 步长限制
if(PLL.Step >13005)
PLL.Step = 13005;
else if(PLL.Step < 8389)
PLL.Step = 8389;
}
// 负半周计数器计数累加
PLL.NegCnt++;

// 当进入负半周期后,在100次控制中断周期后,可以抗干扰地认为处于稳态
if(PLL.NegCnt == 100)
// 有效值计算标志位置1,可以计算有效值
Flag.RmsCalReady = 1;
}

// === 日常的中断累加位移操作 ===

// 锁相环累加 = 锁相环输出+步长 (此时StepSum是一个放大了4096倍的大整数)
PLL.StepSum+= PLL.Step;

// 最终锁相值:将大整数右移12位(相当于除以4096),抹去小数部分,得到真实的整数数组下标
PLL.Cnt= PLL.StepSum >> 12;

// 数组越界保护:超出1023数表时,截断返回从0开始
if(PLL.Cnt> MAX_SINE_CNT)
PLL.CntFianl = PLL.Cnt - MAX_SINE_CNT;
else
PLL.CntFianl = PLL.Cnt;

// 最终得到的 PLL.CntFianl 就是完美的正弦表下标!
// 拿它去查表就能生成纯净无畸变的参考信号了。
// PWMDAC(PLL.CntFianl*16);
}

❓ 核心疑问:直接输出 PLL.CntFianl 这个整数作为参考值不行吗?

答案是:绝对不行。

如果在控制代码里直接把 PLL.CntFianl 作为参考值,你的 PFC 控制系统不仅起不到功率因数校正的作用,甚至极有可能引发炸机。

要彻底理解为什么,我们必须搞清楚 “X 轴(时间/相位)”“Y 轴(电流幅值)” 在控制逻辑中的本质区别:

1. PLL.CntFianl 只是“X 轴”(时间坐标)
PLL.CntFianl 在代码中的作用是一个累加器,随着时间推移,它在一个半周期内会均匀地从 0 逐渐增加到 1023。
如果在示波器上把 PLL.CntFianl 的数值画出来,你会看到一条斜直向上的直线(即锯齿波)。它仅仅代表时间的流逝或者说“角度进度”(比如:当前相位是 0度、90度,还是 180度)。

2. 系统真正需要的是“Y 轴”(正弦波形)
PFC 的根本目的,是强迫电感里的电流波形完美跟随电网电压
既然电网电压是正弦波,那么提供给电流环的“电流参考值”也必须是一个完美的正弦波。如果你直接把代表时间的 PLL.CntFianl 丢给电流环,系统就会拼命把输入电流控制成一个锯齿波。这完全违背了 PFC 吸收正弦电流的初衷,会对电网造成极大的谐波污染。

3. 正弦表 sinetable 是从 X 到 Y 的“翻译官”
我们无法让时间(X 轴)直接变成正弦波的幅值,所以需要一个数学映射关系:$Y = \sin(X)$。

  • 输入 $X$: PLL.CntFianl 就是这个函数里的输入变量 $X$(当前相位)。
  • 执行 $\sin()$: 程序拿着 $X$ 去 sinetable 数组里查表,这个动作本身就是在执行正弦计算。
  • 输出 $Y$: 从表里查出来的值(0 ~ 4095),才是最终需要的 $Y$。它明确告诉系统,正弦波在当前这个角度下,幅值究竟有多高。

前馈环控制设计理论与数字逻辑实现

前馈环控制设计理论与数字逻辑实现

函数名:void FeedCal(void)

电源控制中的前馈(Feedforward)机制,其核心思想是预先计算出一个稳态情况下的控制量大小,并将其作为基础基准直接提供给控制系统。在数字控制领域,由于受限于 DSP 主频的运行速度、采样与环路计算不可避免的延时,通常电流环设计的带宽并不能达到理想状态。仅靠纯闭环(如 PI 调节器)的纠差控制,系统往往存在响应滞后,无法实现电感电流对电网电压相位的精确无静差跟踪。

为了补偿电流环控制带宽的不足,我们可以利用既定的物理定律:根据输入电网电压与最终需要稳定控制的 DC 母线电压,结合 Boost 电路在 DCM(断续导通模式)与 CCM(连续导通模式)下的工作特性,提前计算出系统在当前瞬间稳态下的占空比(即前馈控制量)。

加入前馈量后,电流环就如同有了一个精准的“领航员”,主动为环路控制输出提供一个稳态工作点的控制量。这样一来,电流调节器的负担大幅减轻,其调节范围被极大地缩小在稳态点周围(只负责微调和抑制扰动),从而让被控电流的特性能够更加接近理想的正弦波形。


理想工况下的输入特性假定

为了推导前馈公式,我们首先假定当前系统运行的实际工况。输入电压与电流的物理参数如下:

计算输入电压的峰值:

假定输入电流有效值:

计算系统的输入功率:

构建输入正弦电压的半边绝对值函数表达式(因为 PFC 后级面对的是整流后的馒头波):

根据上述函数,我们绘制出输入电压在一个工频周期内的波形如下:

输入电压波形


CCM 模式稳态占空比反推

根据 Boost 电路的基本工作原理,在电感电流连续的理想状态下(CCM 模式),占空比 $D$ 仅与输入电压和输出控制的母线电压相关。根据输出母线电压被控制在稳态,我们可以直接反推出系统在任何时刻需要的 CCM 占空比:

绘制出对应的 CCM 占空比波形如下:

反推计算稳态占空比


DCM 模式的引入与推导

上述占空比计算的前提是 Boost 电路始终工作于 CCM 模式下。但在真实的 PFC 运行场景中,情况并非如此单一。特别是在交流输入电压的过零点附近,或者系统处于轻载状态时,电感中的储能较少,电感电流在每个开关周期结束前就会降为零,进入断续导通状态(DCM),如下图所示:

DCM模式下电感电流波形

在 DCM 模式下,原有的 CCM 电压传输比公式失效,占空比不仅取决于输入输出电压,还与电感量、开关频率及负载功率强相关。根据 DCM 工作情况下的伏秒平衡与能量守恒定律,可以推论出 $D_{dcm}(t)$:

在上述推导公式中,各参数的物理意义如下:

  • $D_{ccm}(t)$:PFC 工作于连续导通模式下的占空比;
  • $D_{dcm}(t)$:PFC 工作于断续导通模式下的占空比;
  • $V_{acpeak}$:输入电压的峰值;
  • $P_{in}$:输入功率的平均值;
  • $f_s$:系统的开关频率;
  • $L$:PFC 主功率电感值。

全范围前馈占空比的融合策略

当 Boost 电路工作于我们设定的实际工况下时,其在 DCM 和 CCM 模式下所需的稳态占空比是不同的。我们将两种稳态工况下的占空比(前馈值)绘制在同一坐标系中进行对比:

CCM模式于DCM模式占空比

如上图所示,红色实线为 BOOST 电路工作于 CCM 模式下的稳态占空比,蓝色虚线为 BOOST 工作于 DCM 模式下的占空比。
事实上,BOOST 电路在系统运行中,当电流较大(如正弦波峰值区域)时往往自然工作于 CCM,而在电流较小(如过零点区域)时退化工作于 DCM。因此,控制策略上我们取两者计算出的极小值,作为 BOOST 在整个工频周期范围内的稳态最终占空比(前馈量),即图中的绿色虚线。

相应的,我们设计的前馈计算程序流程图如下:

前馈计算程序流程图

流程原理解析:

  1. 构建标准输入电压: 锁相环(PLL)输出的相位信号乘以峰值电压,即可构建出标准同步的正弦电压。
  2. CCM 前馈计算: 根据前面的公式,用 1 减去(正弦电压信号除以母线电压信号)获得 CCM 前馈占空比值。为了给闭环调节器留出微调空间,从而获取更好的 THD 与 PF 值,我们取理论 CCM 前馈值的 0.95 倍。
  3. DCM 前馈计算: DCM 前馈值等于在低频中断中算好的 DCM 前馈系数,乘以 CCM 前馈的开根号值。
  4. 软启动与择小输出: 比较两者大小取小者。同时,在启动阶段,为了抑制前馈量突然加入造成电感电流变化剧烈而误触保护,必须引入软启过程(乘以软启动系数)。

理论公式与 DSP 数字量定点换算验证

为了在 DSP 中执行上述物理公式,必须将其全部换算为无小数的定点数(Q格式)。下面是详细的换算推导与验证过程。

我们任意选取一个时刻 $tt$ 进行验证:

计算对应此时刻的输入电压物理值:

计算此时刻 CCM 模式下的理论占空比:

假设我们采用 Q15 格式(即 $100\%$ 满量程占空比对应的数字量为 32768),换算为 DSP 内部数字量:

对应此时的理论 DCM 占空比为:

DCM 占空比换算为数字量:

上面所有计算均是基于物理系统的实际标称值计算。但在代码中,所有变量都是通过 ADC 采样进来的数字量。下面推导如何通过 DSP 采样值直接得到前馈量。

原始 DCM 占空比物理计算公式为:

使用 DSP 采样的数字量并结合 Q15 格式进行映射表示:

对上述复杂公式进行常数项与变量项的剥离和化简:

提取出一个恒定的硬件环境常数 $K$:

代入对应的 ADC 采样基准与实际标称参数,我们可以得到验证方程:

可以看到,纯数字量映射计算出的结果($2.466 \times 10^4$)与直接用物理量算出来的 $D_{dspdcm}$ 完全一致,证明映射公式在逻辑上绝对成立。


规避 CPU 瓶颈:空间换时间的高速查表法

在实际应用过程的 DSP 计算中,sqrt 根号运算属于浮点库级别的复杂指令,极其消耗指令周期。如果在高频中断里直接开根号,会导致 CPU 严重过载甚至死机。为了避免时间资源消耗太大,我们在工程中大量使用根号查表法来获取对应的数据。

将上述计算 DCM 占空比数字量的公式进一步变形为适合查表的形式:

具体的定点移位与查表过程剖析:

  1. CCM 占空比开根号 (Q15格式):
    除以 $2^5$ (即右移 5 位) 是为了将数据压缩进 1024 长度的表格内:

    通过查表得到的放大数值:

  2. 输入电压开根号 (Q15格式):

    查表得到:

  3. 输入电流开根号 (Q15格式):

    查表得到:

  4. 计算系统功率开根号 (Q15格式):

  5. 计算峰值电压的倒数:
    利用倒数可以将除法化为乘法。

    放大的倒数值:

  6. 硬件常数 K 值的预计算:
    为了保留精度,将最终常数放大 Q10 倍(计算结果最后需要除以这个 Q10 还原):

  7. 最终纯定点前馈输出:
    根据上述离散化后的系数,可以直接进行简单的乘法和位移运算得到最终 DCM 占空比数字量:

    其结果与极尽耗时的浮点理论运算结果相差无几,但在 DSP 中的执行速度却呈指数级提升。


底层 C 语言控制程序代码展示与深度解析

为了支撑上述查表算法,我们在全局内存中预置了一个长度为 1024 的数组。其内容全部为提前运算好的 0-1 之间数值开根号后的定标整数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// 开根号查表 Q10 放大格式,数组大小1024,映射索引输入值与其对应的非线性根号值
int sqrttable[1024] =
{
0, 1024, 1448, 1774, 2048,
2290, 2508, 2709, 2896, 3072,
3238, 3396, 3547, 3692, 3831,
3966, 4096, 4222, 4344, 4464,
4579, 4693, 4803, 4911, 5017,
5120, 5221, 5321, 5418, 5514,
5609, 5701, 5793, 5882, 5971,
6058, 6144, 6229, 6312, 6395,
6476, 6557, 6636, 6715, 6792,
6869, 6945, 7020, 7094, 7168,
7241, 7313, 7384, 7455, 7525,
7594, 7663, 7731, 7799, 7865,
7932, 7998, 8063, 8128, 8192,
8256, 8319, 8382, 8444, 8506,
8567, 8628, 8689, 8749, 8809,
8868, 8927, 8986, 9044, 9102,
9159, 9216, 9273, 9329, 9385,
9441, 9496, 9551, 9606, 9660,
9715, 9768, 9822, 9875, 9928,
9981, 10033, 10085, 10137, 10189,
10240, 10291, 10342, 10392, 10443,
10493, 10543, 10592, 10642, 10691,
10740, 10789, 10837, 10885, 10933,
10981, 11029, 11076, 11123, 11171,
11217, 11264, 11310, 11357, 11403,
11449, 11494, 11540, 11585, 11630,
11675, 11720, 11765, 11809, 11854,
11898, 11942, 11986, 12029, 12073,
12116, 12159, 12202, 12245, 12288,
12331, 12373, 12415, 12457, 12500,
12541, 12583, 12625, 12666, 12708,
12749, 12790, 12831, 12871, 12912,
12953, 12993, 13033, 13074, 13114,
13154, 13193, 13233, 13273, 13312,
13351, 13391, 13430, 13469, 13507,
13546, 13585, 13623, 13662, 13700,
13738, 13777, 13815, 13852, 13890,
13928, 13965, 14003, 14040, 14078,
14115, 14152, 14189, 14226, 14263,
14299, 14336, 14373, 14409, 14445,
14482, 14518, 14554, 14590, 14626,
14661, 14697, 14733, 14768, 14804,
14839, 14874, 14910, 14945, 14980,
15015, 15050, 15084, 15119, 15154,
15188, 15223, 15257, 15292, 15326,
15360, 15394, 15428, 15462, 15496,
15530, 15563, 15597, 15631, 15664,
15698, 15731, 15764, 15798, 15831,
15864, 15897, 15930, 15963, 15995,
16028, 16061, 16093, 16126, 16158,
16191, 16223, 16255, 16288, 16320,
16352, 16384, 16416, 16448, 16480,
16512, 16543, 16575, 16606, 16638,
16670, 16701, 16732, 16764, 16795,
16826, 16857, 16888, 16919, 16950,
16981, 17012, 17043, 17073, 17104,
17135, 17165, 17196, 17226, 17257,
17287, 17317, 17348, 17378, 17408,
17438, 17468, 17498, 17528, 17558,
17588, 17618, 17647, 17677, 17707,
17736, 17766, 17795, 17825, 17854,
17883, 17913, 17942, 17971, 18000,
18029, 18058, 18087, 18116, 18145,
18174, 18203, 18232, 18261, 18289,
18318, 18346, 18375, 18404, 18432,
18460, 18489, 18517, 18545, 18574,
18602, 18630, 18658, 18686, 18714,
18742, 18770, 18798, 18826, 18854,
18882, 18909, 18937, 18965, 18992,
19020, 19048, 19075, 19102, 19130,
19157, 19185, 19212, 19239, 19266,
19294, 19321, 19348, 19375, 19402,
19429, 19456, 19483, 19510, 19537,
19563, 19590, 19617, 19644, 19670,
19697, 19724, 19750, 19777, 19803,
19830, 19856, 19882, 19909, 19935,
19961, 19988, 20014, 20040, 20066,
20092, 20118, 20144, 20170, 20196,
20222, 20248, 20274, 20300, 20326,
20352, 20377, 20403, 20429, 20454,
20480, 20506, 20531, 20557, 20582,
20608, 20633, 20658, 20684, 20709,
20734, 20760, 20785, 20810, 20835,
20860, 20886, 20911, 20936, 20961,
20986, 21011, 21036, 21061, 21085,
21110, 21135, 21160, 21185, 21209,
21234, 21259, 21283, 21308, 21333,
21357, 21382, 21406, 21431, 21455,
21480, 21504, 21528, 21553, 21577,
21601, 21626, 21650, 21674, 21698,
21722, 21746, 21771, 21795, 21819,
21843, 21867, 21891, 21915, 21938,
21962, 21986, 22010, 22034, 22058,
22081, 22105, 22129, 22153, 22176,
22200, 22223, 22247, 22271, 22294,
22318, 22341, 22364, 22388, 22411,
22435, 22458, 22481, 22505, 22528,
22551, 22574, 22598, 22621, 22644,
22667, 22690, 22713, 22736, 22760,
22783, 22806, 22829, 22851, 22874,
22897, 22920, 22943, 22966, 22989,
23012, 23034, 23057, 23080, 23102,
23125, 23148, 23170, 23193, 23216,
23238, 23261, 23283, 23306, 23328,
23351, 23373, 23396, 23418, 23440,
23463, 23485, 23507, 23530, 23552,
23574, 23596, 23619, 23641, 23663,
23685, 23707, 23729, 23752, 23774,
23796, 23818, 23840, 23862, 23884,
23906, 23927, 23949, 23971, 23993,
24015, 24037, 24059, 24080, 24102,
24124, 24146, 24167, 24189, 24211,
24232, 24254, 24275, 24297, 24319,
24340, 24362, 24383, 24405, 24426,
24448, 24469, 24491, 24512, 24533,
24555, 24576, 24597, 24619, 24640,
24661, 24682, 24704, 24725, 24746,
24767, 24788, 24810, 24831, 24852,
24873, 24894, 24915, 24936, 24957,
24978, 24999, 25020, 25041, 25062,
25083, 25104, 25125, 25145, 25166,
25187, 25208, 25229, 25249, 25270,
25291, 25312, 25332, 25353, 25374,
25394, 25415, 25436, 25456, 25477,
25497, 25518, 25538, 25559, 25580,
25600, 25620, 25641, 25661, 25682,
25702, 25723, 25743, 25763, 25784,
25804, 25824, 25845, 25865, 25885,
25905, 25926, 25946, 25966, 25986,
26006, 26027, 26047, 26067, 26087,
26107, 26127, 26147, 26167, 26187,
26207, 26227, 26247, 26267, 26287,
26307, 26327, 26347, 26367, 26387,
26406, 26426, 26446, 26466, 26486,
26506, 26525, 26545, 26565, 26585,
26604, 26624, 26644, 26663, 26683,
26703, 26722, 26742, 26761, 26781,
26801, 26820, 26840, 26859, 26879,
26898, 26918, 26937, 26957, 26976,
26996, 27015, 27034, 27054, 27073,
27092, 27112, 27131, 27150, 27170,
27189, 27208, 27228, 27247, 27266,
27285, 27305, 27324, 27343, 27362,
27381, 27400, 27420, 27439, 27458,
27477, 27496, 27515, 27534, 27553,
27572, 27591, 27610, 27629, 27648,
27667, 27686, 27705, 27724, 27743,
27762, 27780, 27799, 27818, 27837,
27856, 27875, 27893, 27912, 27931,
27950, 27969, 27987, 28006, 28025,
28043, 28062, 28081, 28099, 28118,
28137, 28155, 28174, 28193, 28211,
28230, 28248, 28267, 28285, 28304,
28322, 28341, 28359, 28378, 28396,
28415, 28433, 28452, 28470, 28489,
28507, 28525, 28544, 28562, 28580,
28599, 28617, 28635, 28654, 28672,
28690, 28709, 28727, 28745, 28763,
28782, 28800, 28818, 28836, 28854,
28872, 28891, 28909, 28927, 28945,
28963, 28981, 28999, 29017, 29035,
29053, 29072, 29090, 29108, 29126,
29144, 29162, 29180, 29197, 29215,
29233, 29251, 29269, 29287, 29305,
29323, 29341, 29359, 29376, 29394,
29412, 29430, 29448, 29466, 29483,
29501, 29519, 29537, 29554, 29572,
29590, 29608, 29625, 29643, 29661,
29678, 29696, 29714, 29731, 29749,
29767, 29784, 29802, 29819, 29837,
29854, 29872, 29890, 29907, 29925,
29942, 29960, 29977, 29995, 30012,
30030, 30047, 30064, 30082, 30099,
30117, 30134, 30152, 30169, 30186,
30204, 30221, 30238, 30256, 30273,
30290, 30308, 30325, 30342, 30359,
30377, 30394, 30411, 30428, 30446,
30463, 30480, 30497, 30515, 30532,
30549, 30566, 30583, 30600, 30617,
30635, 30652, 30669, 30686, 30703,
30720, 30737, 30754, 30771, 30788,
30805, 30822, 30839, 30856, 30873,
30890, 30907, 30924, 30941, 30958,
30975, 30992, 31009, 31026, 31043,
31059, 31076, 31093, 31110, 31127,
31144, 31161, 31177, 31194, 31211,
31228, 31245, 31261, 31278, 31295,
31312, 31328, 31345, 31362, 31379,
31395, 31412, 31429, 31445, 31462,
31479, 31495, 31512, 31529, 31545,
31562, 31578, 31595, 31612, 31628,
31645, 31661, 31678, 31694, 31711,
31727, 31744, 31761, 31777, 31794,
31810, 31826, 31843, 31859, 31876,
31892, 31909, 31925, 31942, 31958,
31974, 31991, 32007, 32024, 32040,
32056, 32073, 32089, 32105, 32122,
32138, 32154, 32171, 32187, 32203,
32219, 32236, 32252, 32268, 32284,
32301, 32317, 32333, 32349, 32366,
32382, 32398, 32414, 32430, 32446,
32463, 32479, 32495, 32511, 32527,
32543, 32559, 32575, 32592, 32608,
32624, 32640, 32656, 32672, 32688,
32704, 32720, 32736, 32752
};

在代码层面,FeedCal(void) 函数通过定标、位移以及预设常量的配合,巧妙地完成了理论中复杂的所有计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
** ===================================================================
** Funtion Name : void FeedCal(void)
** Description : DCM与CCM前馈计算
** Parameters :
** Returns :
** ===================================================================
*/
// 前馈减小系数 0.95,为了保留裕度让闭环生效
#define FEED_K 31214
// 380V母线电压倒数 V Q13定标。乘以此常数代替耗时的除法运算。
#define VBUS_OVER 10550
// 正弦表变量
static int *SinePoint;
// 开根号查表变量
int *sqrt_point=sqrttable;

void FeedCal(void)
{
// 1. 根据锁相环输出的点位,去 sinetable 数组里抓取当前的基准正弦量
SinePoint = PLL.CntFianl + sinetable;

// 2. 计算构建输入电压时刻绝对值
// = 锁相环输出正弦表位置 * 采样输入电压最大值(Q12) * (Q15) >> 12 = 恢复到Q15定点绝对值
Vac.Sine = ((long)(*SinePoint)) * Vac.Peak >> 12;

// PWMDAC(Vac.Sine); // 调试观测点

// 3. 计算 CCM 模式下的电流环前馈占空比
// = 1 - (电网电压 / 母线电压)
// 这里的 32767 就是 Q15 格式下的 "1"。通过乘以 VBUS_OVER 完成了除以母线电压的操作。
Feed.ccm = 32767 - (Vac.Sine * VBUS_OVER >> 13);

// PWMDAC(Feed.ccm); // 调试观测点

// 4. 电流环前馈参考量 = 电流前馈参考量 * FEED_K (为了获取更好的 THD 值,压低一点点前馈比重)
Feed.ccm = Feed.ccm * FEED_K >> 15;

// 5. 计算 DCM 模式下电流环前馈值
// Feed.ccm >> 5 相当于除以 32,适配 1024 长度的 sqrttable 索引,获得开根号后的结果
sqrt_point = sqrttable + (Feed.ccm >> 5);
// = K * 开根号CCM占空比(Q15)
Feed.dcm = Feed.dcmK * (*sqrt_point) >> 10;

// 6. 核心逻辑:比较 CCM 模式下前馈值与 DCM 模式下前馈值,取较小值
// 并且乘以 Feed.ssK (前馈软启动系数),在系统刚启动时平滑切入,避免电流尖峰
if(Feed.dcm > Feed.ccm)
Feed.final = Feed.ccm * Feed.ssK >> 15;
else
Feed.final = Feed.dcm * Feed.ssK >> 15;
}

整个流程没有任何浮点运算,全由移位和查表代替,使得 DSP 能够在微秒级的高频中断中轻松游刃有余地控制直流母线电压。

数字PFC开环传递函数模型和电流环设计及伯德图分析

数字PFC开环传递函数模型和电流环设计及伯德图分析

在数字功率因数校正(PFC)电源控制系统中,整体环路架构通常被设计为:慢速外环(母线电压环) + 快速内环(电感电流环) + 占空比前馈补偿 的复合控制结构。

其中,为了捕捉微秒级的电流瞬变,快速电流内环与占空比前馈计算都被布置在最高优先级的 ADC 硬件中断函数(ISR_ADC)中进行实时更新;而对外输出功率较为平缓的慢速电压外环,则被放置在 1kHz 的时间中断里进行计算。本章将详细拆解电流内环从“经典连续域控制理论模型”到“数字离散化 C 代码”的完整转换与实现过程。


系统常规数字标幺量与整体工作参数定义

在建立传递函数和展开数字算法之前,我们首先需将系统的标称参数进行物理和数字层面的统一。

常规数字量定义 (Q 格式基准)

DSP 在内部执行浮点除法极其消耗时钟周期,因此控制算法内部统一采用定点 Q 格式进行标幺化运算:

在本章节中,主要以 Q15 作为数据满量程运算标准,即 32768 代表 DSP 内部能处理的最大输入数字量。

PFC 工作整体硬件参数

  • 交流电网输入电压(最低、额定、最高有效值):
  • 定义 PFC 工作开关频率(对应采样与计算频率):
  • PFC 主功率电感值:
  • 额定母线控制电压与最大输出功率:
  • 计算额定稳态输出电流和等效负载电阻大小:
  • 定义输出直流母线滤波电容容值:
  • 计算额定输入工况下的稳定工作占空比:

PFC 电流环开环传递函数与连续域建模

为了评估系统的稳定性并设计合理的 PI 补偿器,必须先建立连续域下的系统传递函数。工程上系统稳定要求相位裕度在45度以上,增益裕度为10dB以上,或者7dB以上

BOOST 电路主功率级小信号开环传递函数

在忽略 parasitic 寄生参数的前提下,构建 BOOST 电路从占空比小扰动 $\hat{d}(s)$ 到电感电流小扰动 $\hat{i}_L(s)$ 的开环传递函数(暂不包含 DSP 内部数字采样与发波环节):

利用上述公式,我们可以绘制出 BOOST 电路从占空比到电感电流开环传递函数的增益曲线和相位曲线:

(注:此处对应文档图中的第一组开环传递函数原始波特图,表现出典型的二阶谐振峰特性,其谐振频率取决于 $L_{PFC}$ 和等效滤波参数。)


数字控制内部完整环路架构与模型修正

实际的数字 PFC 电流内环整体架构不仅仅包含主电路,还结合了底层硬件的采样衰减、发波延时以及数字调理环节,其实际架构如下图所示:

电流内环架构
图7.1 PFC电流内环整体架构与信号流向

如上图所示,各个子模块的传递函数物理意义定义如下:

  • $G_{id}(s)$ 为系统从占空比至电感电流的连续域传递函数;
  • $K_i(s)$ 为电感电流通过采样电阻、差分运放及 RC 低通滤波转换于 DSP 内部采样的传递函数;
  • $G_{ci}(s)$ 为我们需要针对性设计的电流环补偿器传递函数;
  • $G_{dly}(s)$ 为开关周期延时传递函数(在单周期控制中,由于 DSP 采样和算力耗时,PWM 寄存器更新通常会强行延时 1 个开关周期生效);
  • $F_M$ 为环路数字输出至实际占空比的转换传递函数;
  • $i_{ref}$ 为由外环和锁相环构建的参考电流指令。

因此,未补偿前整体系统的开环环路传递函数 $T_i(s)$ 为:

采样电路调理函数 $K_i(s)$ 的推导

定义 MOS 电流取样电阻、差分放大电路阻值、低通 RC 滤波参数如下:

由于 DSP 内部 ADC 采样的最高极值电压为 3.3V,在设计和数学推导中为了方便计算,将 DSP 内部满量程统一用标准值 1 代替 3.3V。由此归一化 MOS 电流采样与 DSP 内部采样的传递函数如下:

这里解释为什么公式最后乘以 $\frac{1}{3.3}$: 因为硬件采样电路将其转为了 0~3.3V 的模拟电压送入引脚,但在数学建模中,为了让传递函数与 Q15 标幺值(满量程为 1)对齐,必须除以 3.3 进行物理幅值的归一化。

采样计算延时与 PWM 更新传递函数 $G_{dly}(s)$

PFC 的 PWM 寄存器更新为了避免半周期竞争冒险,设定为一个开关周期的延时,定义延时传递函数为:

PWM 调制增益 $F_M$

DSP 内部所有计算均采用标定值 1,占空比输出也使用标定值,即 1 代表 100% 的满量程占空比,则:


剔除补偿器前的数字系统开环波特图与稳定性设计

设 $G_{ci}(s)$ 为需要设计的电流环补偿器。如果我们剔除该补偿器,统一将从占空比到电流 $G_{id}(s)$、电流采样转换 $K_i(s)$、占空比更新延时 $G_{dly}(s)$ 以及调制增益 $F_M$ 相乘,便可以得到补偿前的实际数字硬件开环传递函数

PI 补偿器参数的精准求解

为了保证电流内环对工频正弦波的高频跟踪能力,同时避免在高频段被开关噪声干扰,我们定义补偿后的电流环传递函数 $T_i(s)$ 的目标穿越频率为:

通过代入计算补偿前传递函数在穿越频率处的幅值增益和对应的实际放大倍数:

传统 PI 补偿器的标准连续域传递函数为:

其中,$K_{ci}$ 为 PI 的增益,$\omega_{zi}$ 为传递函数需要求解的转折零点。

为了保障系统的绝对鲁棒稳定性,定义补偿后的电流环目标相位裕度为 50 度(实际计算取近似设计值)一般大于45就认为稳定:

根据 PI 传递函数的特性,为了达到此目标,则需要放置零点 $\omega_{zi}$ 在穿越频率 $f_{ci}$ 处产生的相位超前角度应为:

由此可以几何反推计算得到目标零点的频率位置和角频率位置为:

根据闭环穿越频率的概念,系统最终开环传递函数 $T_i(s)$ 在穿越频率点 $f_{ci}$ 处的总增益必须为 0 dB(即放大倍数为 1),也即是原始传递函数 $G_{idDSP}(f_{ci})$ 的增益乘以 PI 补偿器 $G_{ci}(s)$ 的值等于 1。因此可以构建对应模值平衡的非线性方程:

通过预设迭代 K 值,使用牛顿迭代求解算法求得绝对增益 $K_{ci}$ 值:

以此,便完全解解出了补偿器最终的频率特性传递函数为:

最终环路补偿伯德图分析

将上述解得的 PI 补偿器 $G{ci}(f)$ 与未补偿前的硬件系统 $G{idDSP}(f)$ 逐点相乘,即可得到补偿后最终系统完整开环传递函数 $T_i(f)$:

下图展示了完整的系统传递函数优化演演进伯德图:

环路补偿伯德图

波特图深入解析与验证:

  • 第一组曲线 (极左上与右上) $G_{id}(f)$:仅仅包含了 BOOST 主电路。可以看到在 100~200Hz 之间有强烈的二阶谐振尖峰,超过此尖峰后以 -40dB/dec 的斜率迅速衰减,相位剧烈翻转至 -90° 以下。
  • 第二组曲线 (极中上) $G_{idDSP}(f)$:这是叠加了硬件低通滤波 $K_i(s)$ 和 1 个开关周期延时 $G_{dly}(s)$ 后的真实数字硬件模型。可以看到在高频段(>10kHz),由于采样延时的非最小相位特性,其相位曲线急剧恶化(发生 360° 剧烈下翻)。若不设计正确的补偿器,系统在交叉频率处极易因相位滞后超过 -180° 而产生剧烈自激震荡!
  • 第三组曲线 (极中下) $G_{ci}(f)$:这即是我们精心计算设计出的 PI 补偿器特性。他在中低频段提供了极高的积分增益(压制工频静差),并在 970Hz 左右注入了一个转折零点,把滞后的相位强行拉回升起。
  • 第四组曲线 (极左下与右下) $T_i(f)$:最终闭环系统的开环响应!根据数学验证公式:这就完美证明了:系统在 3.5kHz 准确穿越 0dB 轴,实现了足够的带宽,并且在穿越点保留了健康的 46° 相位裕度 和 9.4dB 的高频幅值裕度!系统既能迅猛跟踪电网正弦电流,又对高频开关噪声表现出极强的免疫力。

电流内环数字参考指令构建 (IrefCal)

为了让 PFC 控制的电感电流不仅在波形上能够完美跟随电网电压的正弦包络线,同时还能稳定输出直流母线电压,参考电流指令 $I_{ac.Ref}$ 必须由三个变量进行立体构建:

传递函数

  1. 输入电压相位信号 (Vac.Sine):由上一章的 PLL 锁相环查表生成,负责为电流指令赋予“纯净同相的正弦波形状”。
  2. 外环电压环输出 (Vloop.out):代表母线电压为了维持目标值所需要向电网索取的能量大小。当负载功率增加,Vloop.out 变大,正弦参考电流的振幅随之整体长高。
  3. 输入电压有效值平方的倒数 (Vac.Rmsover2):PFC 系统是一个恒功率控制系统 ($P_{in} \approx P_{out}$)。当用户端交流输入电压降低时,为了维持相同的输出功率,吸取输入电流的有效值必须成反比增大。因此在公式中,必须将参考值除以输入有效值的平方 $\frac{1}{V_{in.rms}^2}$。

综合上述规律,实际控制中的理论数学表达式为:

其中 $K_m$ 为乘法器校准系数,$K_{ss}$ 为抑制开机过冲的参考电流软启动系数。

代码具体逐行精析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*
** ===================================================================
** Funtion Name : void IrefCal(void)
** Description : 电流环计算 (准确说是电流环参考量构建函数)
** Parameters :
** Returns :
** ===================================================================
*/
// Km常数系数,在Q15格式下的标定缩放比
#define IREF_KM 3189
void IrefCal(void)
{
// 【核心算式】: Current reference = (voltage loop output)*Vac*(1/Vac.rms^2)*Iref_softstart
// 1. (Vloop.out * Vac.Sine>>15) : 将慢速电压外环输出(Q15) 与 PLL产出的正弦量(Q15) 相乘。
// 由于 Q15*Q15 = Q30,右移15位后将其完美还原规整为标准的 Q15 定点正弦包络。
// 2. (* Vac.Rmsover2>>12) : 乘以系统在慢速中断里提前算好的有效值倒数的平方(Q12格式)。
// 因为是 Q15*Q12 = Q27,将其右移 12 位,再次规整回 Q15 数据。
// 3. (* IREF_KM>>15) : 乘以硬件功率增益调准系数 Km(Q15),右移 15 位得到纯正的瞬时指令。
Iac.Ref = ((Vloop.out * Vac.Sine>>15) * Vac.Rmsover2>>12)*IREF_KM>>15 ;

// Iac.Ref = (Vloop.out * Vac.Sine>>15) * Vac.Rmsover2>>15 ; // 备用备选算法公式

// 参考电流软启和在过流情况下的降功软启补偿
// 乘以软启动系数 Iac.RefssK(Q15格式),防止开机瞬间或者负荷激增时,参考电流暴增引发硬件硬件过流炸管
Iac.Ref = Iac.Ref * Iac.RefssK>>15;

// 【安全极限钳位保护】
// 限制最大参考电流标幺值不超过 10920 (对应实际物理极值约 5A 瞬间峰值)
if(Iac.Ref >10920)
Iac.Ref =10920;

// PWMDAC(Iac.Ref); // 调试探针:可将算好的理想指令送给 DAC 引脚,由示波器抓取波形验证
}

PI 电流环闭环调控与前馈融合 (CurrentLoop)

在获得精确的参考指令 Iac.Ref 和高精度的实际 MOS 电流采样值 SADC.Imos 后,便进入了微秒级内环最核心的运算阶段:PI 闭环纠差 + 前馈融合输出

整个计算控制逻辑流程极其清晰严密,其标准程序流程如下图所示:

电流环计算程序流程图

代码具体逐行精析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
** ===================================================================
** Funtion Name : void CurrentLoop(void)
** Description : 电流环计算 (PI闭环与占空比计算)
** Parameters :
** Returns :
** ===================================================================
*/
// 电流环电流误差最大限制,对应输入电压最低、输出满载时瞬时电流差值的 0.15A 标幺换算
#define MAX_IEER 328
#define MIN_IEER -328

// 电流环积分最大值限制,防止积分饱和溢出(Windup),设置为对应 50% 最大占空比并扩大 Q13 倍,设置50%因为主要占空比由前馈获得,电流环只微调
#define MAX_ILOOP_IN 134217728
// 电流环积分最小值限制,防止负向饱和
#define MIN_ILOOP_IN -134217728

// 环路最大输出占空比限制 (Q15 格式下 31130 / 32768 ≈ 95% 的实际占空比上限,留出死区时间)
#define MAX_DUTY 31130

void CurrentLoop(void)
{
// 【1. 求取当前电流跟随误差】
// 电流误差 = 理想正弦参考量 - 实际采样绝对值
ILoop.Err = Iac.Ref - SADC.Imos;

// 【2. 动态抗过冲:误差幅值限制】
// 电流误差计算最大值限制。为什么这里需要限幅?因为在突加负载或电网扰动瞬间,会产生极大的瞬时误差。
// 巨大的误差如果在动态时直接进入 PI,会导致占空比突变过大,造成开关管承受致命的电流过冲。
if(ILoop.Err > MAX_IEER)
ILoop.Err = MAX_IEER;
if(ILoop.Err < MIN_IEER)
ILoop.Err = MIN_IEER;

// 【3. 积分环节累加 (Q28 高精度定点累加)】
// 电流环积分量 = 上一次积分量 + 电流误差 * 积分系数 KI
// 注:由于 ILoop.ki 使用了 Q13 定定标,而 Err 为 Q15,两乘积为 Q28 高精阶位。
// 使用 Q28 级的高精度累加,彻底避免了由于数字小数截断导致的“调节死区”或“低频震荡”。
ILoop.Inte += ILoop.ki * ILoop.Err;

// 【4. 抗积分饱和保护 (Anti-Windup)】
// 积分量限制:强行阻止积分器在动态饱和过程中的无限制疯狂累加
if(ILoop.Inte > MAX_ILOOP_IN)
ILoop.Inte = MAX_ILOOP_IN;
if(ILoop.Inte < MIN_ILOOP_IN)
ILoop.Inte = MIN_ILOOP_IN;

// 【5. 比例积分融合 + 前馈量占空比叠加】
// 电流环最终占空比输出 = ((比例P项 + 积分I项) >> 13) + 预推演稳态前馈量
// 1. (ILoop.kp * ILoop.Err) 算出比例项 (Q15*Q13 = Q28);
// 2. 加上同样为 Q28 格式的积分项 ILoop.Inte;
// 3. 将总和右移 13 位 (>>13),将闭环微扰动输出精准还原回标准 Q15 占空比格式;
// 4. 【灵魂一步】:加上由 FeedCal() 模块根据实时电网和母线算出来的前馈值 Feed.final!
// 闭环 PI 仅仅负责微幅修补前馈的残余静差,保证了惊人的瞬态跟随速度!
ILoop.out = ((ILoop.kp * ILoop.Err + ILoop.Inte )>>13) + Feed.final;

// PWMDAC(Feed.final); // 调试探针

// 【6. 寄存器安全限幅与溢出防护】
// 环路输出最大最小值限制:限制计算得到的 MOS 控制占空比
// 极小值不可小于 100 (保证底层驱动芯片能够发出最窄脉冲,避免缺失脉冲引发下跳异常);
// 极大值不可超过 MAX_DUTY (绝对锁定发波上限,留足电感磁复位与驱动死区保护时间)。
// 100对应最小占空比0.03,最大0.95
if(ILoop.out < 100)
ILoop.out = 0;
else if(ILoop.out > MAX_DUTY)
ILoop.out = MAX_DUTY;
}
数字PI环路参数自适应矫正与寄存器更新

数字 PI 环路参数自适应矫正与寄存器更新

在完成了电流内环开环频域建模、PI 补偿器基础参数推导以及占空比前馈计算之后,系统控制便进入了面向硬件底层的调试与适配阶段。

上一章求得的 PI 补偿器参数基于理想恒定电感下的额定工况。然而,在实际的高频大功率 PFC 运行中,主电路电感量并非绝对恒定,它会在不同电流偏置下发生动态非线性衰减;同时,为了结合占空比前馈量获取极致的总谐波失真(THD)与功率因数(PF)表现,我们需要让数字 PI 调节器的比例系数 $K_p$ 与积分系数 $K_i$ 具备跟随实时输入负载电流变化的变增益自适应矫正能力

本章将详细阐述 PI 连续域参数如何转换为离散数字量、变增益自适应函数的推导过程,最终把求解出的 Q15 标幺占空比安全写至底层的 EPWM 发波寄存器。


连续域 PI 参数的数字化与定点缩放

在将控制论中的 S 域 PI 补偿器植入微秒级运行的 C 语言中断前,必须通过差分方程原理进行离散化,并针对 DSP 内存的定点数进行 Q 格式定标。

连续域到离散域位置式 PI 转换

我们将 PI 补偿器传递函数表示为标准的物理位置式传递函数(其中 $K_p$ 为比例系数,$T_i$ 为积分时间常数):

根据代数恒等关系,可以提取得到对应的绝对 PI 比例系数:

积分的时间常数相应为:

已知 PFC 系统核心电流环的采样与计算执行周期为:

结合前向欧拉离散积分原理,可得到数字化的实际积分增益参数:

DSP 内部 Q13 定标处理

数字 PI 控制中,如果调节系数过小(如 0.17),在定点整数乘法运算中会被严重截断从而失去精度。因此,我们将 PI 控制的比例和积分系数统一放大 Q13 倍(即乘以 $2^{13} = 8192$),使其以无损整数形式参与硬件运算:


电流环非线性变增益 PI 矫正设计

上述计算出了基于 PI 环路控制的电流环标准基础补偿过程。但在实际工程应用中,为了达到较好的电流 THD 值并协同前馈控制发挥最大效能,必须对 PI 环路参数进行经验与物理特性的非线性矫正。

为什么需要变增益?核心物理背景分析:
在 PFC 实际运行过程中,铁粉芯或非晶磁粉芯主电感的感量会随着流过电流大小的变化而发生改变 —— 在重载(大瞬时电流)情况下,电感铁芯渐进饱和,实际电感量比轻载情况会有显著衰减
根据被控对象增益与电感成反比的物理规律(电感小,电流爬升快,开环增益大),如果全功率范围维持同一组强劲的 PI 参数,大电流区往往容易发生高频震荡或电流毛刺;相反,如果全区间使用稳妥保守的参数,轻载和过零点处电流环又显得无力,无法拉平交越失真。
因此,我们可以设置电流环的 $K_P, K_I$ 参数随着实时输入电流绝对大小进行相应的变增益动态衰减计算

变增益锚点设定

定义轻载(瞬时小电流)情况下的初始 $K_p$ 和 $K_i$ 基础参数:

我们挑选需要进行变增益线性插值的两个典型输入电流校准点位 0.5A 和 2A(以 10A 作为系统最大检流满量程标定值):

定义在这两个关键电流锚点上对于比例系数 $K_P$ 的目标期望值(大电流衰减为原来的二分之一):

同样,定义这两个电流锚点上对于积分系数 $K_I$ 的目标期望值:

变增益自适应线性方程求解

通过上述设定的物理坐标点,利用两点确定一条直线的原理,可构建 $K_P$ 随电流变化的线性矩阵方程:

运用矩阵求逆联立求解方程,得到直线的理论斜率 $K_{pK}$ 与截距 $K_{pB}$:

为了在 DSP 单片机中用整数乘法高效算出斜率,我们将理论斜率进一步放大 Q12 倍

同理,构建 $K_I$ 参数随电流变化的方程矩阵:

求解该直线对应的斜率与截距,同样将其斜率在 DSP 内存放 Q12 倍:

最终,我们便成功构造出了在 C 语言中断中调用的两个高动态变增益自适应函数:

将假设的输入电流点代入验证:当输入电流恰好为 $0.5\text{A}$ 时,

结果与期望初始锚点严丝合缝,验证了自适应数学转换方程的精准性!

动态增益极值安全钳位与特性曲线

为了保障系统绝对安全,避免电网突变瞬态产生极端的负向增益或过大增益,必须对计算出的变增益输出施加严格的最大、最小值上下限限制:

绘制出动态调校后,电流环比例系数与积分系数对瞬时输入采样电流变化的自适应响应曲线如下:

Kp和Ki数字量对输入电流数字量的曲线

从曲线上可直观看到:当电网正弦波处于过零点及轻载区间(横轴采样值极小),PI 调节器自动释放最大增益,强力拉紧波形;而随着正弦波走向峰值大电流区(横轴采样值变大),PI 增益如预期般平滑线性下移,精准适配了主电感在高磁密下的感量衰减,从根本上消除了峰值附近的电流毛刺与震荡!


PWM 寄存器更新控制 (RegReflash)

经过电流内环算法计算出来的最终输出量 ILoop.out 是一个标准的 Q15 标幺量(即数字量 $2^{15} = 32768$ 代表实际占空比 $100\%$)。微处理器硬件无法直接认识标幺值,我们须将占空比标量转换并映射成实际能够驱动外围管脚的高频 EPWM 比较寄存器值。

同时,为了支持各类软件保护(如硬件过流 OVP、软件过压、软关机等),系统环路中引入了独立的运行标志位 Flag.PWM 用以实时启动和关闭发波。

根据底层发波机理,控制规则为:Flag.PWM 标志位为 0 时,无论环路计算出的占空比 ILoop.out 为何值,发波寄存器的比较器门限必须立刻重置为满周期计数值,使高电平脉冲彻底消失,即发波强制关断

代码具体逐行精析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
** ===================================================================
** Funtion Name : void RegReflash(void)
** Description : PWM寄存器更新, 占空比向硬件底层的映射写入
** Parameters :
** Returns :
** ===================================================================
*/
void RegReflash(void)
{
// 【硬件保护解除】: 针对 ePWM 特殊核心寄存器的写操作,必须先解除系统只读保护 (Edit Allow)
EALLOW;

// Flag.PWM=0; // 调试观测位:手动强行闭锁 PWM 发波
// ILoop.out=16383; // 调试观测位:强制给入 50% 标幺占空比测试硬件开环发波

// 【核心算式】: adjust duty for output EPWM1A
// 逻辑解析:
// 1. Flag.PWM 为软件开关机及故障联动标志位:为 1 则发波正常;为 0 则把占空比瞬间清零。
// 2. (32767 - ILoop.out * Flag.PWM) :
// 这里的 32767 是 Q15 下的数字 "1"。由于 DSP 底层发波采用“增计数比较翻转”模式,
// 实际需要的导通时间与寄存器门限成反比,因此需通过 1 减去占空比,转换为下管导通或反相发波比较门限。
// 3. * 667 :
// 这里 667 对应当前开关频率下的 EPWM 周期寄存器设定终值(TBPRD,例如时钟下计满 667 为一个 45kHz 周期)。
// 4. >> 15 :
// 除以 Q15(32768) 标幺满量程,将乘法放大后的结果精确等比例缩放至 0 ~ 667 之间的纯真实硬件比较门限!
EPwm1Regs.CMPA.half.CMPA = (long)(32767 - ILoop.out * Flag.PWM) * 667 >> 15;

// 【硬件保护锁死】: 写入完毕后立即锁死保护寄存器,防止程序意外飞车篡改发波波形 (Edit Disable)
EDIS;
}
DCM电流采样矫正与硬件过流保护

DCM电流采样矫正与硬件过流保护

在 PFC 的控制策略中,我们采用的是平均电流控制法,其核心反馈量是电感电流的“平均值”。然而,在实际的单片机 ADC 采样中,我们往往只能在特定的时间点去“窥探”电流的瞬时值,进而推算出平均值。

这就引发了一个工程界极其经典的物理难题:在连续导通模式(CCM)下,采样逻辑是完美的;但在断续导通模式(DCM)下,由于电流波形发生了畸变,传统的采样点会抓取到错误的偏高数值。同时,复杂的矫正运算又会给 DSP 带来极大的算力负担。

本章将详细解析 DCM 采样的物理误差根源、倒数查表法的降维打击优化,以及周波级硬件过流保护(CBC-OCP)的软件接管逻辑


DCM 电流采样的物理困境与误差分析

在数字 PFC 控制中,ADC 的触发点通常设置在 PWM 发波开启时间(ON-time)的正中间。

  • 在 CCM(连续模式)下:电感电流是一个连续的梯形波(或完整三角形)。根据几何对称性,在其上升段的 50% 处进行采样,抓到的瞬时值 $i_{sense}$ 刚好等于整个开关周期 $T_s$ 内的电流平均值 $I_{in}$。
  • 在 DCM(断续模式)下:电感电流在开关周期结束前就已经掉到了 0,并保持了一段死区时间。

如下图所示:

DCM采样值并非平均值

从波形图上可以清晰看出,由于死区时间的存在,真实电流波形的“物理中心”向左偏移了(采样中心点会偏左)。此时如果我们依然死板地在 PWM 导通时间的 50% 处去采样得到 $i_{sense}$,这个采样值仅仅是那个“瘦高三角形”的平均值,而远大于整个周期 $T_s$ 平摊下来的真实平均电流 $I_{in}$。

如果 DSP 直接拿这个虚高的假信号去闭环,就会误以为输入电流已经很大了,从而错误地压低占空比,导致交越失真严重,THD(总谐波失真)急剧恶化!因此,必须通过数学手段将这个虚高的采样值“打折”矫正回真实的平均值。


矫正系数的数学推导与“查表代除法”优化

根据伏秒平衡与几何面积等效原理,可以推导出现实采样值与真实平均值之间的矫正系数 $I_{cal}$ 公式:

根据上述公式进行分类讨论:

  • 若系统工作于 CCM 模式: 此时实际占空比 $D = D_{ccm}(t)$。由于理论上 $D_{ccm}(t) = \frac{V_o - u_{in}(t)}{V_o}$,代入原式约分后 $\Rightarrow I_{cal} = 1$。说明采样值即为平均值,无需打折。
  • 若系统工作于 DCM 模式: 此时实际占空比 $D = D_{dcm}(t)$,代入公式后得到动态矫正系数:

工程痛点与神来之笔:
在 DSP 内部实时计算上述 $I_{cal}(t)$ 极其困难,因为公式中不仅包含实时的占空比变量,最要命的是它包含了一个极耗 CPU 时间的除法运算(分母是随时变化的输入电压)。

为了避免高频中断里的除法导致系统崩溃,程序员极其巧妙地利用了前面算好的前馈量,并结合倒数查表法进行了降维打击:
仔细观察公式:$\frac{V_o}{V_o - u_{in}(t)}$,这恰好是理论 CCM 占空比 $D_{ccm}(t)$ 的倒数
所以公式可以完美转换为:

为此,代码中预置了一个专门存放倒数放大值的 Q5 格式查表数组 DIV_TABLE。我们只需拿当前的理论前馈占空比 Feed.ccm 去查这个表,瞬间就能得到极其耗时的除法结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//倒数查表 Q5
int DIV_TABLE[1024] =
{
32767, 16384, 10923, 8192, 6554, 5461, 4681, 4096, 3641, 3277, 2979, 2731, 2521, 2341, 2185, 2048, 1928, 1820, 1725, 1638,
1560, 1489, 1425, 1365, 1311, 1260, 1214, 1170, 1130, 1092, 1057, 1024, 993, 964, 936, 910, 886, 862, 840, 819,
799, 780, 762, 745, 728, 712, 697, 683, 669, 655, 643, 630, 618, 607, 596, 585, 575, 565, 555, 546,
537, 529, 520, 512, 504, 496, 489, 482, 475, 468, 462, 455, 449, 443, 437, 431, 426, 420, 415, 410,
405, 400, 395, 390, 386, 381, 377, 372, 368, 364, 360, 356, 352, 349, 345, 341, 338, 334, 331, 328,
324, 321, 318, 315, 312, 309, 306, 303, 301, 298, 295, 293, 290, 287, 285, 282, 280, 278, 275, 273,
271, 269, 266, 264, 262, 260, 258, 256, 254, 252, 250, 248, 246, 245, 243, 241, 239, 237, 236, 234,
232, 231, 229, 228, 226, 224, 223, 221, 220, 218, 217, 216, 214, 213, 211, 210, 209, 207, 206, 205,
204, 202, 201, 200, 199, 197, 196, 195, 194, 193, 192, 191, 189, 188, 187, 186, 185, 184, 183, 182,
181, 180, 179, 178, 177, 176, 175, 174, 173, 172, 172, 171, 170, 169, 168, 167, 166, 165, 165, 164,
163, 162, 161, 161, 160, 159, 158, 158, 157, 156, 155, 155, 154, 153, 152, 152, 151, 150, 150, 149,
148, 148, 147, 146, 146, 145, 144, 144, 143, 142, 142, 141, 141, 140, 139, 139, 138, 138, 137, 137,
136, 135, 135, 134, 134, 133, 133, 132, 132, 131, 131, 130, 130, 129, 129, 128, 128, 127, 127, 126,
126, 125, 125, 124, 124, 123, 123, 122, 122, 121, 121, 120, 120, 120, 119, 119, 118, 118, 117, 117,
117, 116, 116, 115, 115, 115, 114, 114, 113, 113, 113, 112, 112, 111, 111, 111, 110, 110, 110, 109,
109, 109, 108, 108, 107, 107, 107, 106, 106, 106, 105, 105, 105, 104, 104, 104, 103, 103, 103, 102,
102, 102, 101, 101, 101, 101, 100, 100, 100, 99, 99, 99, 98, 98, 98, 98, 97, 97, 97, 96,
96, 96, 96, 95, 95, 95, 94, 94, 94, 94, 93, 93, 93, 93, 92, 92, 92, 92, 91, 91,
91, 91, 90, 90, 90, 90, 89, 89, 89, 89, 88, 88, 88, 88, 87, 87, 87, 87, 86, 86,
86, 86, 86, 85, 85, 85, 85, 84, 84, 84, 84, 84, 83, 83, 83, 83, 83, 82, 82, 82,
82, 82, 81, 81, 81, 81, 81, 80, 80, 80, 80, 80, 79, 79, 79, 79, 79, 78, 78, 78,
78, 78, 77, 77, 77, 77, 77, 77, 76, 76, 76, 76, 76, 76, 75, 75, 75, 75, 75, 74,
74, 74, 74, 74, 74, 73, 73, 73, 73, 73, 73, 72, 72, 72, 72, 72, 72, 72, 71, 71,
71, 71, 71, 71, 70, 70, 70, 70, 70, 70, 70, 69, 69, 69, 69, 69, 69, 69, 68, 68,
68, 68, 68, 68, 68, 67, 67, 67, 67, 67, 67, 67, 66, 66, 66, 66, 66, 66, 66, 66,
65, 65, 65, 65, 65, 65, 65, 65, 64, 64, 64, 64, 64, 64, 64, 64, 63, 63, 63, 63,
63, 63, 63, 63, 62, 62, 62, 62, 62, 62, 62, 62, 61, 61, 61, 61, 61, 61, 61, 61,
61, 60, 60, 60, 60, 60, 60, 60, 60, 60, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59,
58, 58, 58, 58, 58, 58, 58, 58, 58, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
55, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 53, 53, 53, 53, 53, 53, 53, 53,
53, 53, 53, 53, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 51, 51, 51, 51,
51, 51, 51, 51, 51, 51, 51, 51, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
50, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 48, 48, 48, 48, 48,
48, 48, 48, 48, 48, 48, 48, 48, 48, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, 47,
47, 47, 47, 47, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 44, 44, 44, 44,
44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 43, 43, 43, 43, 43, 43, 43,
43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
40, 40, 40, 40, 40, 40, 40, 40, 40, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39,
39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 38, 38, 38, 38, 38, 38, 38, 38, 38,
38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 37, 37, 37, 37, 37, 37, 37,
37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 37, 36, 36, 36,
36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
36, 36, 36, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35,
35, 35, 35, 35, 35, 35, 35, 35, 35, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 33, 33,
33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33,
33, 33, 33, 33, 33, 33, 33, 33, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32
};


软件矫正逻辑与代码精析

真实的矫正逻辑并不盲目,而是带有极强的工程经验考量:

  1. 软启动屏蔽:在软启动过程中,系统状态极不稳定,此时不计算 DCM 电流矫正值(系数强置为1),防止矫正干扰正常起机。
  2. 轻载屏蔽:由于轻载时电流本身就很小,微小的偏差对波形影响不大。出于效率和稳定考量,“轻载一般不矫正,一般都是半载以上”才介入矫正(即输入低功率标志位 Flag.LowP == 1 时系数置1)。
  3. 平滑过渡屏蔽:为了保证 DCM 向 CCM 模式跨越时的计算平滑,当算出的矫正系数超过 0.9 时,直接强制取 1。这一过程极大削弱了跨越边界时的电流过冲。

结合流程图,我们可以清晰看到这一判断逻辑:

DCM电流采样矫正

具体落实到 C 语言代码中,精简而高效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
** ===================================================================
** Funtion Name : void Icalibrate(void)
** Description : DCM模式下电流采样矫正
** Parameters :
** Returns :
** ===================================================================
*/
void Icalibrate(void)
{
// 软启动结束后,软件矫正和过流保护才正式起作用
if(Flag.ssFinsh == 1)
{
// 【核心矫正计算:完美替代浮点除法】
// 理论公式:Ical = D_actual / D_ccm_theory
// 1. Feed.ccm 为计算好的理论连续占空比(Q15)。右移5位(>>5)适配 1024 长度的查表索引。
// 2. DIV_TABLE 查出了 1 / D_ccm_theory 的放大值。
// 3. ILoop.out 是环路输出的当前实际占空比 D_actual。
// 4. 两者相乘并右移5位还原 Q15 标幺格式,就得到了矫正系数!
SADC.ImosK = ILoop.out * (DIV_TABLE[Feed.ccm>>5])>>5;

// 当输出功率较小时,轻载一般不矫正,DCM电流采样矫正系数强制 = 1 (即 Q15 下的 32767)
if(Flag.LowP==1)
SADC.ImosK = 32767;

// 平滑过渡机制:如果 DCM_calibrate >= 0.93 (30767/32768),强行设置为 1
// 这个过程(process)是为了减少当系统从 DCM 突步入 CCM 模式时引发的电流过冲(current overshot)
if(SADC.ImosK > 30767)
SADC.ImosK = 32767;
}
}
// 后续在真正读取 ADC 采样时,将用原始电流值乘以该 SADC.ImosK 即可获得被“打折”后的真实平均电流。

硬件周波级过流保护 (CBC-OCP) 软件接管

除了对电流进行精准的控制与矫正,在极端异常工况下(如电网浪涌、雷击瞬态或输出短路),纯软件代码的响应速度是绝对来不及保护 MOS 管的。
因此,我们采用了硬件级保护机制(TZ,Trip Zone)。当 MOS 电流触及硬件比较器阈值时,DSP 底层硬件会瞬间(纳秒级)自动切断 PWM 波,这就是所谓的“周期封波”或逐周期限流(Cycle-By-Cycle, CBC)。

但纯硬件封波也有弊端:它可能会被短暂的噪声毛刺误触发。因此,我们需要软件的 HwOcp(void) 函数来“监听”硬件的封波动作,进行智能的二次研判:是偶然的毛刺,还是真正的严重故障?

硬件封波保护功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*
** ===================================================================
** Funtion Name :void HwOcp(void)
** Description : 硬件过流保护
** Parameters :
** Returns :
** ===================================================================
*/
void HwOcp(void)
{
// 硬件周期封波次数计数器(静态变量,跨周期保持)
static int OpcCnt=0;
// 10MS 硬件封波计时时间窗口计数器
static long OcpTimeCnt=0;

// 当检测到发生过流周期封波时
// EPwm1Regs.TZFLG.bit.CBC == 1 代表底层硬件报告:刚刚发生了一次逐周期硬件限流动作!
if(( EPwm1Regs.TZFLG.bit.CBC==1) && (Flag.PFCState!=Init))
{
// 封波次数计数器累加
OpcCnt++;

// 清除封波标志位,允许底层硬件在下一个开关周期尝试重新发 PWM 波
// 这种允许下周期重启的机制赋予了系统强大的抗干扰自愈能力
EALLOW;
EPwm1Regs.TZCLR.bit.CBC=1;
EDIS;
}

// 【严重故障确诊】:如果在很短的时间内,连续封波次数大于 100 次,则认定发生不可自愈的严重过流故障
if(OpcCnt > 100)
{
// 计数器清0
OpcCnt=0;
// 告知后级 LLC 模块 PFC 出现故障,联动关闭系统
PFCokDisable() ;
// 状态机切入故障状态
Flag.PFCState = Err;
// 挂载硬件过流错误标志位(原注释可能笔误写为过压,实为过流ERR_OCP)
Flag.Err |= ERR_OCP;
// 彻底在软件层面上关闭 PWM
Flag.PWM = 0;
}

// 【偶发干扰清零机制】:当发生过哪怕1次封波时,启动时间窗口监测
if(OpcCnt > 0)
{
// 计时器开始随高频中断累加
OcpTimeCnt++;

// 10ms 计时结束(假定中断周期对应450次即为10ms窗口时间)
if(OcpTimeCnt > 450)
{
// 如果 10ms 内并未凑够 100 次以上的封波,说明这只是偶然的噪声毛刺,模块工作已经恢复正常
// 将封波计数器清0,原谅本次轻微扰动
OpcCnt = 0;
// 10ms 计时时间窗口清0,等待下一次可能发生的异常
OcpTimeCnt = 0;
}
}
}
电压环变增益与软启动

慢速外环1kHz中断处理:电压环变增益与软启动

在 PFC 数字控制系统中,通常采用双环控制架构:快速的电流内环慢速的电压外环。前面章节我们探讨了布置在 ADC 高频中断里的电流内环,而本章将把视角转向运行在 1kHz 定时器中断里的电压外环。

由于母线大电容的存在,直流输出电压的变化相对缓慢。因此,电压环无需以几十千赫兹的超高频率运行,1kHz 的处理频率足以兼顾控制精度与 CPU 算力分配。本章将详细解析 1kHz 中断的调度逻辑、母线电压滤波、应对负载突变的非线性变增益控制,以及保障系统安全开机的多重软启动机制


1kHz 中断架构与调度流程

在 DSP 的时基管理中,1kHz 中断由 Timer0 定时器周期性触发产生。为了保证电流内环(几十kHz)的绝对实时性,1kHz 的慢速中断被配置为可被 ADC 快速中断嵌套运行的低优先级中断。这意味着当 1kHz 中断正在执行复杂的电压环计算时,如果 ADC 采样完成,CPU 会立刻打断当前任务,优先去执行电流环闭环,等电流环算完再回来继续执行电压环任务。

下图展示了 1kHz 中断内部的完整调度时序:

1kHz中断流程图

具体的 C 语言中断入口函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include "Interrupt1kHz.h"
#include "InterruptADC.h"
#include "DSP28x_Project.h"

__interrupt void ISR_1kHz(void)
{
// 【中断嵌套使能】
IER = M_INT1; // 开中断INT1 (允许更高优先级的中断打断自己)
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; // 清除应答标志,使能同组其他中断
__asm(" NOP");
EINT; // 全局中断使能,正式允许 ADC 中断嵌套进入

// 【1. 输出电压采样求平均计算】
VbusAvgCal();

// 【2. 电压环变增益函数计算 (动态调整 KP/KI)】
VloopKpKiCal();

// 【3. BUS参考电压缓慢上升处理 (软启动)】
VrefCal();

// 【4. 电压环主计算函数 (PI闭环)】
VoltageLoop();

// 【5. 硬件与软件保护判定】
VbusLimit(); // OVP函数-母线过压限制
VbusUVP(); // UVP函数-母线欠压保护

// 【6. 计算AC有效值计算函数 (为下个周期的前馈做准备)】
VIacRmsCal();

// 【7. 其他环路参数计算与软启动】
DCMFeedCal(); // DCM前馈参数计算
IloopKpKiCal();// 电流环变增益
Feedss(); // 前馈占空比软启
Irefss(); // 电流参考值软启

// 【中断退出清场】
// 清除定时器中断标志位,重新赋值中断向量,等待下一次 1ms 触发
CpuTimer0Regs.TCR.bit.TIF = 1;
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}

母线电压的滑动平均滤波 (VbusAvgCal)

电压环作为外环结构,本质上是一个慢速环路,它对母线电压采样的实时性要求较低,反而对信号的平稳度要求极高。如果不加滤波直接送入 PI 调节器,母线上的高频开关纹波或工频纹波会被放大,导致电流参考值抖动。

因此,代码中对母线电压采样求取了 4 次滑动平均,以得到较为平稳的母线电压反馈值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void VbusAvgCal(void)
{
// 静态变量 VSum 跨周期保存历史求和数据
// 滑动平均求和量,正BUS滑动平均求和量,负BUS滑动平均求和量,双边电压平均量
static long VSum=0;

// 母线电压采样4次滑动平均算法核心:
// 新的求和值 = 旧的求和值 + 当前最新采样值 - 旧求和值的1/4 (相当于减去最老的数据)
// 这里的 >>2 就是极其高效的“除以4”操作
VSum = VSum + SADC.Vbus - (VSum>>2);

// 将求和值除以4,得到当前的平滑平均值 Vbus.Avg
Vbus.Avg = VSum>>2;
}

电压环非线性变增益控制 (VloopKpKiCal)

由于电压环运行速度较慢,当输出端发生剧烈的负载突变(如满载突然切空载,或空载突然加满载)时,正常的 PI 电压环往往无法快速响应并抑制母线电压的突增与突减。这种迟滞极易造成母线触发硬件过压(OVP)或欠压(UVP)保护,导致系统宕机。

为了解决这个痛点,引入了非线性变增益控制策略:

  1. 甩负载突变(重载突变至轻载):此时多余的能量无处释放,导致母线电压快速拉升。当检测到母线电压大于设定的上限阈值时,程序瞬间将电压环 PI 参数增加 4 倍,使电压环输出快速递减,通过快速降低电流参考值,强力抑制母线电压持续增加。
  2. 加负载突变(轻载突变至重载):此时能量供应不足,母线电压快速拉低。当检测到母线电压低于设定的下限阈值时,程序将电压环 PI 值增加 2 倍,使电压环输出快速递增,通过快速增加电流参考值使母线电压迅速恢复。
  3. 稳态恢复:需要特别注意的是,当输出电压重新回到特定的稳定偏差范围内时,必须立即恢复常规的电压环 PI 参数,以避免过大的 PI 增益引起电压环的低频振荡。

下图展示了这一逻辑的判断流程:

电压环变增益图

具体的工程代码实现如下(同时注意:在系统开机启动过程中,母线电压处于动态爬升状态,尚未工作于稳态,因此必须屏蔽变增益功能):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 定义常规状态下的 KP 和 KI 标幺值
#define VLOOP_KP 1000
#define VLOOP_KI 100
// 定义触发变增益的宽限门限 (30V对应标幺值)
#define V_OFFSET 2009
// 定义恢复正常增益的窄限死区门限 (20V对应标幺值)
#define V_OFFSET_RE 1339

void VloopKpKiCal(void)
{
// 只有在软启动结束后 (即系统进入稳态运行期),变增益功能才介入工作
if(Flag.ssFinsh == 1)
{
// 1. 稳态区间:当母线电压在正常范围内 (正负20V的死区内),退出变增益
if( (Vbus.Avg < (Vbus.Ref + V_OFFSET_RE)) && (Vbus.Avg > (Vbus.Ref - V_OFFSET_RE)) )
{
Vloop.kp = VLOOP_KP; // 恢复正常KP
Vloop.ki = VLOOP_KI; // 恢复正常KI
}
// 2. 甩负载极速抑制:电压过大(超出30V上限),启用4倍变增益
else if(Vbus.Avg > ( Vbus.Ref + V_OFFSET ))
{
Vloop.kp = VLOOP_KP*4; // 4倍变增益KP
Vloop.ki = VLOOP_KI*4; // 4倍变增益KI
}
// 3. 突加载极速恢复:电压过小(低于30V下限),启用2倍变增益
else if(Vbus.Avg < (Vbus.Ref - V_OFFSET))
{
Vloop.kp = VLOOP_KP*2; // 2倍变增益KP
Vloop.ki = VLOOP_KI*2; // 2倍变增益KI
}
}
else
{
// 如果处于软启动爬升期,屏蔽变增益,强制使用基础常规参数
Vloop.kp = VLOOP_KP;
Vloop.ki = VLOOP_KI;
}
}

渐进式多重软启动机制 (Soft-Start)

为了防止 PFC 在启动瞬间由于巨大的压差导致严重的浪涌电流(Inrush Current)和电压过冲,程序中分布了多维度的软启动逻辑。

母线参考电压渐进软启 (VrefCal)

该函数主要负责处理电压参考值缓慢增加的功能。如果开机瞬间直接将参考电压设为 380V,巨大的电压偏差会瞬间将 PI 调节器积分饱和,造成危险的电流指令脉冲。因此,通过软件代码让参考电压阶梯式慢慢爬升,能有效避免电压参考值跳变引起的开机振荡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
** ===================================================================
** Funtion Name : void VrefCal(void)
** Description : 输出参考电压计算,启动时参考电压慢慢增加
** Parameters : none
** Returns : none
** ===================================================================
*/
#define VBUS_REF 25448 // 对应物理电压 380V 的 Q15 标幺值
void VrefCal(void)
{
// 只有在 PFC 处于 Run (运行) 或 Rise (起机爬升) 状态下才进行处理
if(( Flag.PFCState == Run) || (Flag.PFCState ==Rise))
{
// 当当前的参考电压小于目标稳态值时,每个 1ms 中断仅缓慢增加 10 个单位 (极平滑的爬升斜率)
if( Vbus.Ref < VBUS_REF)
Vbus.Ref = Vbus.Ref + 10;

// 到达目标极值后进行钳位,防止溢出
if( Vbus.Ref > VBUS_REF )
Vbus.Ref = VBUS_REF;
}
}

电流参考值与前馈量比例软启 (Irefss & Feedss)

除了参考电压的柔性爬升,直接作用于占空比控制的底层变量——电流参考量 (Iac.RefssK)前馈占空比补偿量 (Feed.ssK),也加入了从 0 到 1 (标幺值 32767) 的乘法软启系数机制。

它们在每次 1ms 中断中以固定步长递增,最终放大至全量程,以此控制功率管从极窄脉冲开始慢慢张开,彻底杜绝了硬启动带来的声光火花。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 电流参考软启增量减量步长
#define IREF_SS_STEP 200
// 电流环前馈软启系数最大值限制 (Q15 格式下系数为 1,即满量程不打折)
#define MAX_IREF_SSK 32767

void Irefss(void)
{
// 只要还没达到 1 (32767),就在每次 1ms 中断里加 200
if(Iac.RefssK < MAX_IREF_SSK)
{
Iac.RefssK = Iac.RefssK + IREF_SS_STEP;
// 触顶钳位
if(Iac.RefssK > MAX_IREF_SSK)
Iac.RefssK = MAX_IREF_SSK;
}
}

// ---------------------------------------------------------
// 前馈占空比软启增量步长
#define FEED_SS_STEP 100
#define MAX_FEED_SSK 32767

void Feedss(void)
{
// 只有在主回路软启动(电压爬升)基本结束后,占空比的前馈软启动系数才开始逐渐介入增加
if(Flag.ssFinsh == 1)
{
// 软启动系数以步长 100 逐渐增加至全量程 1
if(Feed.ssK < MAX_FEED_SSK)
{
Feed.ssK = Feed.ssK + FEED_SS_STEP;
if(Feed.ssK > MAX_FEED_SSK)
Feed.ssK = MAX_FEED_SSK;
}
}
}
PFC数字电压环补偿器设计

PFC数字电压环补偿器设计

在数字 PFC 控制系统中,除了内环(电流环)需要保证输入电流具有较好的 THD 值和 PF 值之外,外环(电压环)的控制目的则是保证输出母线具有稳定的电压值。本章将详细解析从 PFC 功率级等效模型推导、外环传递函数构建,到 PI 补偿器参数计算及 C 语言代码实现的全过程。


电压环的控制逻辑与程序流程

电压环的核心任务是生成电流环的指令幅度基准。电压环的输出量被用以构建电流环的电流参考值:

  • 当母线电压低于参考值时,电压环输出增加,进而使内环电流环的参考量增加,促使输入电流增加以补充母线能量。
  • 当母线电压高于参考值时,电压环输出减小,内环电流环参考量随之减小,输入电流减小,从而保证母线电压最终等于目标参考值。

下图展示了电压环的基础程序判断与执行流程:

电压环程序流程图


PFC 电压 Control-to-Out 传递函数推导

为了设计电压环的 PI 补偿器,首先需要获取 PFC 主电路的功率级传递函数(即从控制量到输出电压的传递函数)。

PFC 功率级等效电路模型

PFC 的小信号等效电路模型如下图所示:

PFC的等效电路模型

模型中相关电路参数的定义如下表所示:

参数定义

根据等效模型提取出的核心参数表达式如下:

  • 电压传输比:$M = \frac{V_o}{V_{in}}$
  • 等效输入阻抗:$r_i = \frac{M^2}{r_o}$
  • 控制增益参数:$g_i = k \cdot V_{in}$
  • 控制增益参数:$g_f = \frac{2 \cdot V_c}{M}$
  • 输出受控流源增益:$g_c = \frac{k \cdot V_o}{M^2}$
  • 等效负载阻抗:$r_o = \frac{V_o}{I_o}$

功率级传递函数

当输入电压保持不变时(忽略前馈扰动),从控制量 $V_c$ 到输出电压的开环传递函数可以简化表示为:

将 $g_c$ 与 $M$ 的表达式代入,后续我们将通过整机控制架构推导出系数 $k$ 的物理意义。


PFC 电压环路控制架构与系数求解

电压环基础架构

在宏观层面,PFC 控制电路中的电压环路架构如下图所示:

PFC电压环路架构

其中:

  • $G_{vc}(s)$ 为从控制量到输出电压的传递函数。
  • $G_{cv}(s)$ 为我们需要设计的电压环补偿器传递函数。
  • 未补偿的系统环路传递函数:$T_v(s) := K_o(s) \cdot G_{vc}(s) \cdot G_{cv}(s)$。

内外环耦合控制架构

配合前面章节讲解的电流内环控制架构,整个 PFC 的电流内环与电压外环双闭环控制架构如下图所示:

电压外环的控制架构

从图中可以提取出以下关键转换环节:

  • $K_o$ 为输出电压的硬件分压与 DSP 采样传递函数。
  • $K_{in}$ 为输入电压的硬件分压与 DSP 采样传递函数。
  • 电压环路的输出量 $v_c$ 乘以输入电压得到电流环的参考电流 $i_{ref}$,其中 $K_m$ 为乘法器增益。
  • 为了加快电压环路对电网波动的抑制效果,引入了输入电压前馈控制,即除以输入电压有效值的平方(除法器系数 $K_{ff}$)。

未知系数 $k$ 的推导

根据 PFC 的工作原理,当系统工作于稳态时,输入电流平均值等于参考电路平均值。同时满足等效受控源公式:

(其中 $k$ 为待求解系数)。

从 PFC 整体的控制架构图中,根据信号流向可以写出电感电流指令的表达式:

(其中 $v_{in}$ 为输入电压瞬时值,$V_{in}$ 为有效值,$V_c$ 为电压环输出量,$K_i$ 为MOS电流转换和采样增益)。

在稳态下,使用平均值代替瞬时值,则推算出真实输入电流为:

将该式与 $I_{in} := k \cdot V_{in} \cdot V_c$ 对比,即可推算出系统核心系数 $k$ 为:

将解得的 $k$ 代入输出受控源增益 $g_c$:

最终带入传递函数,得到完整的电压极点传递函数:


实际数字量参数求解与模型实例化

下面开始求解上述传递函数中对应的相关硬件变量。由于电压环的处理速度(1kHz)比电流环(45kHz)慢数量级,其穿越频率通常在 10 ~ 30Hz 左右。因此在这个极低频段内,所有输入输出电压的采样硬件滤波延迟和运算延时均可以忽略不计,只取直流分量:

  • 输入电压采样增益:
  • 电流采样增益:
  • 输出母线电压采样增益:

根据输入功率的关系平衡式:

在实际的数字控制中,对相关的变量和参数进行标定的话,我们取电压环输出最大值 $V_c$ 的稳态工作点约为 0.7:

为了简化计算,设置前馈标幺基准 $K_{ff}$ 等于采样基准:

假定满载理想无效率损失,输入功率等于输出功率:

代入公式计算得到乘法器增益 $K_m$ 的绝对物理值为:

DSP代码映射:
由于在 DSP 中采用 Q15 定点格式代表小数(放大 32768 倍),我们在代码里使用的 IREF_KM 宏定义即由此而来:

同时,额定母线电压设定为:

代入后,我们便得到了基于实际电路参数的控制到输出电压功率级真实传递函数 $G_{vc}(s)$。


PI 电压环补偿器设计与零点极点配置

由于 PFC 系统固有的特点,为了不过度放大母线上的 100Hz 工频纹波,电压环带宽必须设置得很低。

  1. 定义补偿后的电压环传递函数 $T_v(s)$ 的目标穿越频率为:
  2. 计算补偿前原传递函数在目标频率的增益和对应的实际放大倍数:
  3. 设定传统 PI 补偿器的传递函数:(其中,$K_{cv}$ 为 PI 增益,$\omega_{zv}$ 为需要求解的零点角频率)
  4. 定义补偿后的目标相位裕度为 45 度:
  5. 根据 PI 传递函数特性,零点 $\omega_{zv}$ 在穿越频率处需产生的相位超前角度为:
  6. 计算得到零点频率的位置:
  7. 根据闭环穿越频率的概念,穿越频率点的总增益为 0dB(即放大倍数为1),据此构建方程求解出补偿器绝对增益:

结合上述计算,我们得出了完整的开环系统波特图,展示了系统补偿后在极低频(10Hz)依然具有出色的稳定性和 45 度的健康相位裕度:

电压环补偿器相关图


S域连续参数到数字定点变量的换算

为了在 DSP 中执行计算,需要将 S 域传递函数离散化,并缩放为 C 语言中的整数。

将 PI 补偿器转换为经典位置式传递函数公式:

提取对应的 PI 比例系数和积分时间常数:

系统电压环运行在 1kHz 中断中,因此其实际采样周期为:

得到数字化的实际积分常数:

DSP Q10 定标放大:
由于积分参数 $0.174$ 太小,不便于单片机计算,我们将其人为放大 Q10倍(即 $2^{10}=1024$),并四舍五入为纯整数,这便是代码宏定义中的最终参数源头:


C 语言代码实现与精析 (VoltageLoop)

经过复杂的建模和推导,最终在代码中的落地实现却非常简洁。通过 Q 格式的高精度移位,保证了积分的精度与计算速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 定义常规状态下的 KP 和 KI 标幺值 (基于上述推导理论值进行的工程微调)
#define VLOOP_KP 1000 // 约等于理论推导的标幺映射
#define VLOOP_KI 100 // 约等于理论推导的标幺映射

/*
** ===================================================================
** Funtion Name : void VoltageLoop(void)
** Description : 恒压环路计算
** Parameters : none
** Returns : none
** ===================================================================
*/
void VoltageLoop(void)
{
// 1. BUS电压环路误差计算
// 计算目标设定的参考量与当前滤波后的平均电压之间的误差 (两者均为 Q15 定点格式)
Vloop.Err = Vbus.Ref - Vbus.Avg;

// 2. BUS电压环积分量累加
// 积分量 = 历史积分量 + 当前误差 * 积分系数KI
Vloop.Inte += (Vloop.Err * Vloop.ki);

// 3. 对积分极值进行最大、最小值抗饱和限制 (Anti-Windup),防止内存溢出或超调
if(Vloop.Inte < 0)
Vloop.Inte = 0;

// 限制上限。由于 Vloop.ki 是 Q10 格式,Vloop.Err 是 Q15 格式,乘积是 Q25 阶的高精度长整型。
// 这里的 33550000 对应了系统允许的最大积分边界阈值
if(Vloop.Inte > 33550000)
Vloop.Inte = 33550000;

// 4. 计算BUS电压环最终输出量 (比例项 + 积分项)
// 误差*KP 也是 Q25 格式。两者相加后,右移 10 位 (>>10),将其除回基准的 Q15 标幺格式。
// 这个输出量即为构建下一级内环电流参考波形的核心振幅基准!
Vloop.out = (Vloop.Inte + Vloop.Err * Vloop.kp) >> 10;

// 5. 对环路输出结果进行最终的物理可行性限幅
if(Vloop.out < 0)
Vloop.out = 0;

// 最大输出不能超过 Q15 的满量程极值 32767
if(Vloop.out > 32767)
Vloop.out = 32767;
}
PFC过压保护与欠压保护

PFC 母线电压保护与交流有效值高精计算

在数字 PFC 电源控制系统中,除了实现高功率因数的核心闭环控制外,完善的安全保护机制与精准的系统状态感知同样不可或缺。
本章将深入解析三个关键的后处理模块:母线电压过压限制(OVP)母线欠压故障保护(UVP),以及基于半波积分法的交流电压与电流有效值(RMS)高精计算


软件级母线电压过压限制 (VbusLimit)

电压环的根本任务是保证公共直流母线电压稳定工作于规定范围内。然而,当电网发生剧烈波动或系统遭遇极端甩负载(如满载瞬间切空载)导致控制短暂失调时,母线电压可能会异常过高。
如果仅依靠外环 PI 的线性调节,可能无法及时压制这种上升趋势,从而对后级变换器及母线电容造成过压损坏危险。因此,需要引入独立于 PI 之外的滞回比较过压限制机制

过压抑制与恢复逻辑

  • 过压触发:当系统检测到母线电压大于设定的过压阈值时,程序立即将过压标志位置位,并瞬间强制关闭 PWM 发波。为了防止系统恢复时产生巨大的突变冲击,程序会同步将参考电流的软启动系数清 0,并将电流环的积分累加器清 0,确保系统复位时必须重新进行电流软起。
  • 安全恢复:当母线输出出现过压封 PWM 后,母线失去能量供给,输出电压因自然放电或负载消耗下降至合理安全范围,此时系统清除过压标志位,重新打开 PWM,恢复闭环控制。

其软件过压保护的程序判断流程图如下所示:

母线电压限制

C语言代码精析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*
** ===================================================================
** Funtion Name : void VbusLimit(void)
** Description : 母线电压限制
** Parameters : none
** Returns : none
** ===================================================================
*/
// 设定过压触发绝对上限阈值 (标幺值对应具体物理电压)
#define VBUS_LIMIT 28797
// 设定过压恢复下限阈值,形成滞回区间,防止在临界点频繁启停震荡
#define VBUS_LIMIT_RE 26788

void VbusLimit(void)
{
// 局部静态标志位,用于记录系统是否处于过压封波锁定状态
static char OVPFlag=0;

// 只有在 PFC 处于 Run (正常运行) 或 Rise (起机爬升) 状态下才进行处理
if(( Flag.PFCState == Run) || (Flag.PFCState ==Rise))
{
// 【1. 过压触发拦截】
// 当 bus 出现过压限制
if(Vbus.Avg > VBUS_LIMIT)
{
// 标志位置位,锁入过压状态
OVPFlag=1;
// 立即强制关闭底层 PWM 波输出,切断能量输入
Flag.PWM = 0;
// 核心安全操作:当出现过压时,电流参考量重新软起,强制下次恢复时从零缓升
Iac.RefssK = 0;
// 核心安全操作:当出现过压时,电流环积分量=0,防止 Windup 饱和导致复位瞬间炸管
ILoop.Inte = 0;
}

// 【2. 滞回安全恢复判定】
// 当发生过压后
if(OVPFlag==1)
{
// 当出现过压后,电压恢复到理想值后复位
// 当且仅当电压不仅低于极限值,还要跌落至恢复安全死区 VBUS_LIMIT_RE 以下
if(Vbus.Avg < VBUS_LIMIT_RE)
{
// 标志位清零解锁
OVPFlag=0;
// 重新允许发 PWM 波
Flag.PWM = 1;

// 恢复时再次确认清零状态,确保以软启动姿态接入电网
Iac.RefssK = 0;
ILoop.Inte = 0;
}
}
}
}

母线欠压故障保护 (VbusUVP)

除了电压过高,公共直流母线电压异常偏低同样属于严重故障。当输出载过重或 MOS 管发生损坏失效时,母线电压会过低。
PFC 具备输出母线电压检测欠压保护功能。正常工作状态下,Boost PFC 的输出直流母线电压绝对不可能低于输入交流电经过桥式整流后的自然直流峰值。一旦检测到母线电压不仅没升上去,反而跌破了自然整流的理论下限阈值,即可断定系统已严重故障。

当检测到电压过低(低于自然整流电压时),发生欠压保护,PFC 将关闭 PWM,置位欠压保护标志位,强制切入故障保护状态,并通知后级停机。

其程序流程图如下:

母线电压欠压保护

C语言代码精析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
** ===================================================================
** Funtion Name : void VbusUVP(void)
** Description : 母线欠压保护,防止驱动损坏的情况下输出电压不能满足要求
** Parameters : none
** Returns : none
** ===================================================================
*/
// 欠压偏移量设定,对应物理电压约 50V 的标幺差值
#define VBUS_UVP_OFFSET 3349

void VbusUVP(void)
{
// 只有在 PFC 运行状态下处理
// (因为开机起步阶段电压本身就很低,需避开误判)
if(Flag.PFCState ==Run)
{
// 动态跟踪阈值判定:
// 当 BUS电压 < 线电压计算的 BUS整流电压 - 50V 时,发生欠压保护,PFC关闭PWM
if(Vbus.Avg < (Vac.Rms - VBUS_UVP_OFFSET))
{
// 第一时间切断发波
Flag.PWM = 0;
// 硬件欠压错误标志位置位 (挂载 Error Code)
Flag.Err |= ERR_UVP;
// 进入故障状态,状态机死锁不再恢复
Flag.PFCState = Err;
// 告知后级 LLC PFC有故障,拉低 PFC_OK 硬件 IO 信号
PFCokDisable();
}
}
}

交流电压与电流有效值精准计算 (VIacRmsCal)

在闭环控制与前馈推演中,交流电压和电流的有效值(RMS)和峰值(Peak)是至关重要的参考基准。由于交流电是随时间不断波动的正弦波,传统方法如果强行在高速中断里离散开平方求有效值,会极大地拖垮 CPU。

为此,工程上采用了基于半周期离散积分的绝对平均值转换法

算法数学原理推导

有效值计算基于正弦波计算方式。在 ADC 快速中断锁相环处理中,我们在每个工频正半周期内对采样电压和电流进行累加求和。
若每个中断计数点 $n$ 对应的绝对采样值为 $X_n$,则半个周期内的数字平均值为:

基于标准正弦波计算理论(正弦波有效值与平均值的关系、有效值与峰值的关系),完美正弦波的绝对平均值 $\bar{X}$ 与有效值之间存在固定的比例转换系数($\frac{\pi}{2\sqrt{2}}$)。通过将累加求出的平均值乘以该固定的转换系数,可以直接计算得到电压与电流的有效值与峰值。随后,再额外对有效值进行 8 次滑动平均滤波以熨平抖动。

该算法巧妙避开了离散求平方再开根号的复杂计算,其计算流程如下图所示:

电压电流有效值计算

代码实现与 Q14 定标精析

在实际代码中,为了避免浮点运算,需要将转换系数转化为 DSP 可以高效处理的定点整数。代码采用 Q14 格式 进行定标(即放大 $2^{14} = 16384$ 倍):

  • 平均值转有效值系数:$\frac{\pi}{2\sqrt{2}} \times 16384 \approx 18198$
  • 有效值转最大值系数:$\sqrt{2} \times 16384 \approx 23166$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
** ===================================================================
** Funtion Name : void VIacRmsCal(void)
** Description : 输入电压,输入电流有效值计算
** Parameters : none
** Returns : none
** ===================================================================
*/
// 正弦平均值至有效值转换系数 Q14格式 [ PI/(2*1.414) * 16384 ]
#define AVG_TO_RMS 18198
// 正弦有效值至最大值(峰值)转换系数 Q14格式 [ 1.414 * 16384 ]
#define RMS_TO_PEAK 23166

void VIacRmsCal(void)
{
// 累加标定,常规数字平均值累加求平均公式分母
long couter=0;
// 电压电流有效值中间量
long VacRmsTemp=0, IacRmsTemp=0;
// 电压电流有效值的平均值滑动滤波求和
static long VacRmsSum=0, IacRmsSum=0;

// 有效值计算准备标志位置1?
// 在输入进入正半周期时,电压电流累加完成;进入负半周期>100次计数后该标志位置1
// 程序要求在正半周期进入数值累加,在负半周期进行结算计算
if(Flag.RmsCalReady == 1)
{
// 【1. 计算平均值分母的倒数倍数 (除法转乘法)】
// 累加标定,常规数字平均值累加求平均计算:(1 / Cnt) * 65536
couter= (long)65536 / PLL.PosCnt;

// 清底层高频计数器
PLL.PosCnt=0;

// 限幅保护:防止启动锁相环未完全工作时计算值异常
if(couter<131) couter=131; // 对应 45Hz 的极限频率保护
if(couter>204) couter=204; // 对应 70Hz 的极限频率保护

// 【2. 平均值与有效值转换】
// 电压平均值 = 电压累加值 / 累加计数量。
// 电压有效值 = 电压平均值 * 平均值至有效值转换系数 (AVG_TO_RMS)
VacRmsTemp = (Vac.Sum * couter >> 16) * AVG_TO_RMS >> 14;

// 电流平均值 = 电流累加值 / 累加计数量。
// 电流有效值 = 电流平均值 * 平均值至有效值转换系数
IacRmsTemp = (Iac.Sum * couter >> 16) * AVG_TO_RMS >> 14;

// 清电压、电流累加量,迎接下个正半周期的纯净累加
Vac.Sum = 0;
Iac.Sum = 0;

// 【3. 滑动滤波以获取平滑稳态值】
// 电压有效值滑动求平均(电压有效值的平均值)
VacRmsSum = VacRmsSum + VacRmsTemp - (VacRmsSum >> 3);
Vac.Rms = VacRmsSum >> 3;

// 计算R相电流有效值滑动求平均(电流有效值的平均值)
IacRmsSum = IacRmsSum + IacRmsTemp - (IacRmsSum >> 3);
Iac.Rms = IacRmsSum >> 3;

// 【4. 峰值推演】
// 电压最大值(正弦量峰值) = R相电压有效值 * 1.414 (Q14)
Vac.Peak= Vac.Rms * RMS_TO_PEAK >> 14;

// 清有效值计算标志位,等待锁相环在下个周期再次触发
Flag.RmsCalReady = 0;
}
}
DCM模式下占空比前馈系数计算

DCM模式下占空比前馈系数计算与数字验证

在之前的章节中,我们在高频的 ADC 中断内完成了电流环的瞬时闭环与占空比的前馈叠加。但为了极大地节省微秒级高频中断的 CPU 算力,我们将 DCM 占空比前馈公式中变化缓慢的系数部分(如功率平均值、电压峰值的倒数)抽离出来,放置在 1kHz 的慢速中断中进行计算。

本章将详细结合前馈计算的物理数学原理,深度剖析 DCM 模式下前馈系数(Feed.dcmK)的纯数字标幺化提取过程、查表法开根号验证、起机安全限幅机制以及轻载屏蔽逻辑。


前馈参数计算的数学理论与波形绘制

在进行数字代码推导前,我们首先从宏观物理公式上验证 DCM 前馈计算的准确性。计算输入电压的峰值和假定输入电流:

计算系统的输入功率:

构建输入正弦电压的半边绝对值函数表达式:

绘制出的半边正弦电压理论波形图如下:

半边正弦电压图

根据 Boost 电路工作原理,计算假设工作于 CCM 模式对应的稳态 CCM 占空比:

根据 Boost 电路工作于 DCM 状态下时,计算对应的 DCM 工作占空比:

在实际控制中,取两者的最小值作为系统控制的最终稳态前馈占空比:

分别绘制 BOOST 电路工作 CCM 占空比、DCM 占空比,与两者取最小值后的最终前馈对比图如下:

占空比


DSP 数字计算量标幺化推导与验证

上述计算的参数均为实际物理参数,下面我们将展示对应 DSP 内部标幺数字量的严格计算验证过程。

选取任意时间点 $tt$ 时刻,计算对应此时对应的输入电压:

计算此时的 CCM 模式下理论占空比:

在 DSP 中,如果 Q15 对应的占空比为 100%,则换算占空比的纯数字量:

对应此时的理论 DCM 占空比及其数字量为:

数字标幺化化简全过程

上面所有计算均基于物理实际值计算,下面推导如何通过 DSP 采样值的全数字标幺形式直接计算前馈。

原始计算公式为(参数为实际值):

将其完全使用 DSP 采样的数字量表示(引入 Q15 基准):

对方程进行移项与化简得到:

将常数部分提取为系数 $K$:

代入对应的硬件标称参数进行等式验证(结果与理论物理推演完美吻合):

规避开方运算:查表法转换

在实际应用过程的 DSP 计算中,使用 C 语言标准库的根号运算会占用极大的时间资源。为避免中断超时,必须使用根号查表法(利用上一章预设的 sqrttable)获得对应的数据。

将上述计算 DCM 占空比数字量的公式进一步变形为适合查表的格式:

根号查表法分步验证:
根号查表法计算 CCM 占空比开根号 Q15 (先右移5位适配 1024 长度数组):

根号查表法计算电压开根号 Q15:

根号查表法计算电流开根号 Q15:

根号查表法计算功率开根号 Q15:

计算峰值电压倒数:

计算公式中的常数部分 $K$ 值,并人为放大 Q10 倍(最终代码运算结果需除以该倍数):

最终,根据上述数字化的离散标幺参数,通过纯移位乘法得到 DCM 最终占空比量:

可以看到,最终得出的 $2.4379 \times 10^4$ 其结果与最初理论推导的 $2.466 \times 10^4$ 结果极其相近,完美验证了降维查表法的可行性!


软件保护流程设计:限幅与轻载屏蔽

在上一章节前馈计算原理分析中可知 PFC 工作 DCM 模式下的前馈量为:
我们将公式中剥离出的常数与慢变项(如功率平方根、电压峰值倒数)定义为 DCM 前馈系数,它与输入电压峰值与输入功率大小有关。

在工程实践中需要注意以下保护:

  1. 防爆计算限制:上电初期,有效值计算并非达到稳定值,电压峰值较小。由于电压峰值在除法计算的分母位置,为保证 DCM 倒数计算不失真而导致结果无限大(除以零或极小值),必须对起机时的有效值峰值进行强制下限限制。
  2. 轻载屏蔽前馈:同时在轻载的情况下,由于整个电网相角范围内电路均处于极深的 DCM 模式,信号信噪比差。因此,当检测到负载为轻载时,强制屏蔽前馈计算功能(赋值为0),交由 PI 环接管控制。

其完整的计算程序流程图如下:

DCM前馈计算流程图


C 语言核心代码精析 (DCMFeedCal)

根据上述推导,该函数位于 1kHz 的慢速中断中,利用查表、右移等手段高效合成了供底层微秒级中断使用的稳态 DCM 系数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
/*
** ===================================================================
** Funtion Name : void DCMFeedCal(void)
** Description : DCM模式下前馈系数计算
** Parameters : none
** Returns : none
** ===================================================================
*/
// DCM情况下计算电流前馈系数宏定义,对应理论推导的 K 常数,放大 Q10 倍
#define DCM_FEED_COEFF 2946

// 前馈加入功率恢复点 Q30格式 对应物理功率约为 50W (作为恢复前馈的滞回上限)
#define MIN_POWER_RE 7314816
// 当输出功率小时,去前馈功率动作点 Q30格式 对应物理功率约为 25W (作为切除前馈的滞回下限)
#define MIN_POWER 3657408

void DCMFeedCal(void)
{
// 静态变量,跨中断保留数据状态
static long SqrtVac=0, SqrtIac=0, SqrtP=0, SqrtPAvg; // 电压有效值、电流有效值、功率开根号,功率开根号平均
static long SqrtPSum=0; // 功率开根号求平均和
static long VacPeakTemp=0, VacPeakInv=0;
static long TempQ15 = 32767;
static long VacRmsTemp=0;
static long PowerAc=0;
static long temp=1073741824;
// static long temp=11385; // AC170V数字量

// 【1. 输入功率开根号的查表计算】
// 计算电压有效值开根号,查表法Q15。 (Vac.Rms >> 5) 将值除以32,刚好适配 1024 长度的 sqrttable 数组
SqrtVac = *(sqrttable + (int)(Vac.Rms>>5));
// 计算电流有效值开根号,同样查表得到 Q15 格式数值
SqrtIac = *(sqrttable + (int)(Iac.Rms>>5));

// 计算功率开根号: sqrt(P) = sqrt(V) * sqrt(I)
// 乘积为 Q30 格式,右移 15 位退回标准 Q15 格式
SqrtP = SqrtVac * SqrtIac >> 15;

// 【2. 功率开根号的滑动平滑】
// 将功率开根号求 32 次滑动平均 (右移5位),极大抑制电网波动带来的干扰
SqrtPSum = SqrtPSum + SqrtP - (SqrtPSum>>5);
SqrtPAvg = SqrtPSum>>5;

// 【3. 计算电压峰值倒数及限幅保护】
// 限制最小有效输入极值。16098 对应标称约 140V 的电网输入。
// 防止起机或掉电瞬间电压过小,倒数计算过大导致变量溢出!
if(Vac.Peak < 16098)
VacPeakTemp = 16098;
else
VacPeakTemp = Vac.Peak;

// 计算峰值倒数 (Q15格式): 将 1 放至极大后相除以保留小数精度
VacPeakInv = (TempQ15<<15) / VacPeakTemp;

// 【4. DCM 核心前馈系数 Feed.dcmK 的最终合成】
// 数学意义: Feed.dcmK = sqrt(P_avg) * (1 / V_peak) * 常数K
// SqrtPAvg * VacPeakInv (Q30) >> 15 = Q15
// 乘以 DCM_FEED_COEFF (Q10) = Q25,再 >> 15 = 最终还原为 Q10 的基础系数!
Feed.dcmK = ( ((SqrtPAvg * VacPeakInv) >> 15) * DCM_FEED_COEFF) >> 15;

// 【5. 电压有效值平方倒数的计算】
// 计算过程对电压进行限幅 170V (数字量为 11385), 避免求倒数值过大引起指令异常
if(Vac.Rms < 11385)
VacRmsTemp = 11385;
else
VacRmsTemp = Vac.Rms;

// Vac.Rmsover2 = 1 / (average phase voltage)^2
// 提供给电流环用于调节指令波形跟随输入电压的宏观反比系数
Vac.Rmsover2 = ((temp / VacRmsTemp) << 12) / VacRmsTemp;

// 【6. 轻载前馈屏蔽与滞回恢复逻辑】
// 计算单相功率平均值 (用于判断系统负载状况)
PowerAc = Vac.Rms * Iac.Rms;

// 当输出功率较小时 (低于 25W 动作点),全面取消前馈,依赖 PI 硬闭环运行
if(PowerAc < MIN_POWER)
{
Flag.LowP = 1; // 激活轻载标志位
Feed.ssK = 0; // 强制软启动系数归 0,切断前馈通道
}

// 滞回判断:若当前已处于轻载被屏蔽状态,必须等功率拉升超过 50W 阈值才准许解除屏蔽
// 这构成了一个天然的施密特触发器,防止在 25W 临界点发生前后馈跳变震荡
if( Flag.LowP == 1)
{
if(PowerAc > MIN_POWER_RE)
{
Flag.LowP = 0;
}
}
}
电流环变增益

PFC 电流环变增益与软启动控制设计

在数字 PFC 电源控制系统中,电流的参考指令时刻跟随着电网电压的相位进行快速的正弦变化。然而,受限于数字信号处理器(DSP)的主频算力、硬件采样延时以及环路本身的物理惯性,数字控制的带宽频率往往无法做到无限高。

为了在有限的控制带宽下,既能保证极低的输入电流总谐波失真(THD),又能防止系统在复杂工况下发生过冲震荡,我们需要引入两大核心高级控制策略:电流环 PI 变增益自适应控制前馈及参考指令的软启动控制。本章将对这两个模块的物理机理、数学推导及代码实现进行全方位深度解析。


电流环变增益的物理意义与数学推导

在实际应用中,由于磁芯材料的物理饱和特性,PFC 主电感在整个工作范围内的感量并非是完全恒定不变的,而是会随着流过电流的增加导致磁导率下降,进而使得感量逐渐减小。

当电感感量逐渐减小时,被控对象的开环增益会变大,这意味着如果维持原有的 PI 参数,电流闭环的相位裕度会逐渐减小,系统极易在正弦波峰值的大电流区域发生高频振荡。因此,我们必须根据输入电流的大小适当地动态减小电流环的 PI 参数。

当输入电压越低且输出满载时,输入电流越大,对应的电感感量也就越低;即电流环的 PI 参数必须根据输入电流的实时大小进行反比例的线性缩放变化。

初始轻载点位与标定锚点选取

根据先前的 PI 环路计算,我们首先定义在轻载(电流较小)情况下的初始 $K_P$ 和 $K_I$ 数字标幺参数:

由于系统设定的最大检流满量程为 10A,我们定义需要进行增益插值的两个关键输入电流点位(0.5A 和 2A)并转换为 Q15 标幺值:

分别定义这两个电流变增益点对应的 $K_P$ 目标值(大电流衰减至一半):

定义变增益点对应的 $K_I$ 目标值:

线性方程构建与斜率求解

为了得到连续的变增益函数,利用两点决定一条直线的原理,构建 $K_P$ 变增益点对应 $X$、$Y$ 矩阵:

通过矩阵求逆解算出该衰减直线对应的理论参数(斜率 $K_{pK}$ 与截距 $K_{pB}$):

由于斜率小于 1,在单片机中直接计算会丢失精度,因此将该参数放大 Q12 倍储存在 DSP 宏定义中:

同理,构建 $K_I$ 变增益点的对应矩阵并求解直线参数:

对应的 DSP 放大 Q12 倍参数为:

最终自适应函数与保护限幅

由此,构建出了跟随输入电流实时动态偏置的 $K_P, K_I$ 参数函数:

将 0.5A 假定值代入验证,完美契合初始目标点:

为防止计算溢出,对变增益的最大与最小值施加严格的安全限幅:

最终绘制出的电流环 $K_P$ 和 $K_I$ 数字量对输入电流数字量的动态平滑衰减曲线如下:

电流环和KI数字量对输入电流数字量的曲线


变增益控制代码精析 (IloopKpKiCal)

该函数放置在中低频的环路调度中,利用简单的移位与乘法,实现了上述复杂的理论自适应衰减数学模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
** ===================================================================
** Funtion Name : void IloopKpKiCal(void)
** Description : 电流环KP KI变增益
** Parameters : none
** Returns : none
** ===================================================================
*/
// kp变增益计算函数K值 (斜率,放大Q12倍)
#define DSP_ILOOP_KP_K -2850
// kp变增益计算函数B值 (截距)
#define DSP_ILOOP_KP_B 7979

// 电流环KP最大值限制 (轻载极值)
#define MAX_ILOOP_KP 6839
// 电流环KP最小限制 (重载极值)
#define MIN_ILOOP_KP 3420

// ki变增益计算函数K值 (斜率,放大Q12倍)
#define DSP_ILOOP_KI_K -2898
// kI变增益计算函数B值 (截距)
#define DSP_ILOOP_KI_B 8114

// 电流环KI最大值限制 (轻载极值)
#define MAX_ILOOP_KI 6955
// 电流环KI最小限制 (重载极值)
#define MIN_ILOOP_KI 3478

void IloopKpKiCal(void)
{
// static long VacRmsAvg=0,VacRmsSum=0; // (保留变量,可用于复合判定)
static long IacRmsAvg=0, IacRmsSum=0; // 电流有效值的滑动池

// 对电流有效值进行 8 次滑动求平均,极大消除交流电网的采样抖动
// 算法: 新Sum = 老Sum + 当前最新有效值 - 历史均值(老Sum/8)
IacRmsSum = IacRmsSum + Iac.Rms - (IacRmsSum >> 3);
IacRmsAvg = IacRmsSum >> 3;

// 【1. 计算电流环KI变增益】
// 逻辑原理: 随着电流(IacRmsAvg)越来越大,因为斜率 DSP_ILOOP_KI_K 是负数,
// 其乘积产生负偏置,从而抵消截距 B,导致最终算出的 ki 值越来越小。
// 右移 12 位 (>>12) 是为了抹去预先对斜率放大的 Q12 倍数,还原至真实标幺。
ILoop.ki = ((IacRmsAvg * DSP_ILOOP_KI_K) >> 12) + DSP_ILOOP_KI_B;

// kI 最大最小值钳位保护
if(ILoop.ki > MAX_ILOOP_KI)
ILoop.ki = MAX_ILOOP_KI;
if(ILoop.ki < MIN_ILOOP_KI)
ILoop.ki = MIN_ILOOP_KI;

// 【2. 计算电流环KP变增益】
// 计算逻辑同上,随电流增大,比例调节强度变弱以压制高频震荡
ILoop.kp = ((IacRmsAvg * DSP_ILOOP_KP_K) >> 12) + DSP_ILOOP_KP_B;

// kp 最大最小值钳位保护
if(ILoop.kp > MAX_ILOOP_KP)
ILoop.kp = MAX_ILOOP_KP;
if(ILoop.kp < MIN_ILOOP_KP)
ILoop.kp = MIN_ILOOP_KP;

// (调试备用测试点:强制写死恒定增益观察开环表现)
// ILoop.kp = 3700;
// ILoop.ki = 453;
}

前馈与电流参考的软启动控制 (Feedss & Irefss)

优秀的电源不仅要在稳态运行中表现优异,在起机瞬间更不能对电网和后端元器件造成致命的电流过冲冲击。

前馈软启 (Feedss)

精确的前馈量能够使得电流环的输出控制性能得到极大提升。由前述章节可知,前馈量为预先计算得到的理论稳态工作占空比。然而,在 PFC 冷启动瞬间,母线电容电压极低,理论前馈占空比会非常大。为了保证启动过程中电感电流不发生严重的过冲,必须人为压制并缓慢地释放这个控制前馈量。

软件巧妙地引入了前馈乘数系数 Feed.ssK,该系数在 PFC 启动过程中由 0 缓慢增加到 1 (Q15的 32767),保证输出给硬件的前馈占空比呈爬坡式缓慢增加。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
** ===================================================================
** Funtion Name : void Feedss(void)
** Description : 前馈系数软起
** Parameters : none
** Returns : none
** ===================================================================
*/
// 前馈软启每次中断的递增步长
#define FEED_SS_STEP 100
// 电流环前馈软启系数最大值限制,满量程 Q15 (即系数为 1,100%释放前馈量)
#define MAX_FEED_SSK 32767

void Feedss(void)
{
// 在系统基本的母线预充软启动过程结束后,才开始前馈占空比的缓慢介入
if(Flag.ssFinsh == 1)
{
// 软启动系数逐渐递增
if(Feed.ssK < MAX_FEED_SSK)
{
Feed.ssK = Feed.ssK + FEED_SS_STEP;

// 安全钳位,防止溢出
if(Feed.ssK > MAX_FEED_SSK)
Feed.ssK = MAX_FEED_SSK;
}
}
}

电流参考值软启 (Irefss)

同理,为了保证电流闭环控制在 PFC 启动过程中不引发严重过流,系统对生成目标正弦波形指令的核心变量——电流参考值也引入了软启动缩放系数 Iac.RefssK。该系数在启动过程中同样由零开始缓慢上升,确保闭环给出的电流指令像拉橡皮筋一样平滑过渡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
** ===================================================================
** Funtion Name : void Irefss(void)
** Description : 参考电流软起
** Parameters : none
** Returns : none
** ===================================================================
*/
// 参考电流软启递增步长 (上升速度比前馈略快,迅速建立基准)
#define IREF_SS_STEP 200
// 参考电流软启系数最大值限制,满量程 Q15 标幺值
#define MAX_IREF_SSK 32767

void Irefss(void)
{
// 如果软启系数未达到满量程,则按照固定步长不断累加
if(Iac.RefssK < MAX_IREF_SSK)
{
Iac.RefssK = Iac.RefssK + IREF_SS_STEP;

// 限幅保护
if(Iac.RefssK > MAX_IREF_SSK)
Iac.RefssK = MAX_IREF_SSK;
}
}
PFC状态机程序

200Hz 系统中低频中断事件与 PFC 运行状态机

在数字电源控制系统中,不同优先级的任务需要分配到不同频率的中断中执行。前述章节中,微秒级的电流环放置在最高频的 ADC 中断中,毫秒级的电压环放置在 1kHz 中断中;而对于系统宏观的运行状态调度(如开机自检、继电器吸合、软启动爬坡、故障恢复等)以及与上位机的通信任务,则不需要极高的刷新率。

因此,系统专门设立了一个 200Hz(即 5ms 周期)的低频中断来统筹管理 PFC 的全局状态机(State Machine)。本章将详细解析这一核心状态机的流转机制与底层 C 语言实现。


200Hz 中断事件概述

200Hz 硬件中断由内部定时器 Timer1 触发,其中断优先级配置为 Level3,在运行过程中可以被优先级更高的 ADC 快速中断与 1kHz 环路中断嵌套打断并运行。

这种嵌套中断架构保证了底层高频发波的绝对实时性,同时又让 CPU 能在空闲切片时间内有条不紊地处理系统状态机调度、输入电压宏观保护与外部 SCI 串口通信任务。

其 200Hz 中断服务程序的整体执行流程图如下:

200Hz程序流程图

中断入口核心代码精析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include "Interrupt200Hz.h"
#include "InterruptADC.h"
#include "SCIcom.h"
#include "DSP28x_Project.h"

/** ===================================================================
** Funtion Name : __interrupt void ISR_200HZ(void)
** Description :
** 5mS定时中断函数
** 中断优先级为Level3
** Parameters :
** Returns :
** ===================================================================*/
__interrupt void ISR_200Hz(void)
{
// 【1. 开启中断嵌套允许】
// 在进入低频中断后,立刻打开 M_INT1,允许优先级更高的 ADC 和 1kHz 中断打断自己
IER = M_INT1; // 开中断INT1

// 应答 PIE 组,清除中断标志,允许接收同组后续中断
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
__asm(" NOP"); // 插入空指令,等待硬件流水线同步

// 开启全局总中断 (Enable Global interrupt INTM)
// 此时系统正式允许进入嵌套模式
EINT;

// 【2. 核心任务调度】
// PFC 宏观运行状态机调度函数 (5ms执行一次)
StateM();

// 输入交流电压宏观保护 (如电网掉电、严重浪涌等低频检测)
VacCheck();

// SCI 内部串口通信处理 (收发上位机指令或与 LLC 后级握手)
SciCom();

// 【3. 中断退出清理】
// 清除 CPU Timer1 的定时器中断标志位,为下一次 5ms 定时做准备
CpuTimer1Regs.TCR.bit.TIF = 1;
}

PFC 运行状态机架构设计

系统软件采用标准的有限状态机(FSM)架构来标定和管理模块的每一个运行节点。程序状态机运行周期为严格的 5ms,整体生命周期被划分为五大核心状态:初始化状态(Init)、空闲等待状态(Wait)、软启动状态(Rise)、正常运行状态(Run)以及故障保护状态(Err)

定义状态机的 C 语言枚举量如下:

1
2
3
4
5
6
7
8
9
// 状态机枚举量定义
typedef enum
{
Init, // 初始化状态:上电自检与零点偏置校准
Wait, // 空闲等待状态:母线电容预充电与继电器吸合
Rise, // 软启状态:电压与电流环参考量平滑爬坡
Run, // 正常运行状态:稳态功率输出
Err // 故障状态:停机保护与故障恢复倒计时
} STATE_M;

各个状态之间的安全跳转条件与拓扑关系如下图所示:

PFC运行状态机

严格的状态跳转条件定义如下:

  • 初始化状态至空闲状态:上电完成程序参数初始化,并且在底层完成电流硬件零点偏置采样后,跳转至等待状态。
  • 空闲状态至软启动状态:等待 BUS 母线电容通过防浪涌电阻自然充电接近稳定极值,闭合主功率继电器后跳转至软起动状态。
  • 软启动状态至正常运行状态:正式开启 PWM 发波,随着参考电流软起、前馈软起结束且输出电压达标后,切入运行态。
  • 软启动状态至故障状态:在启动爬坡过程中若发生任何越限故障或触发保护,立即打断并跳入故障态。
  • 正常运行状态至故障状态:稳态正常运行过程中检测到电网或负载发生致命故障或保护动作。
  • 故障状态至空闲状态:故障根源消除且经过安全延时后自动跳转,模块执行自动恢复重新启动流程。

由 5ms 周期调度的状态机主函数结构极其精简清晰:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/** ===================================================================
** Funtion Name :void StateM()(void)
** Description : 状态机函数
** 初始化状态
** 等待启动状态
** 启动状态
** 运行状态
** 故障状态
** Parameters :
** Returns :
** ===================================================================*/
void StateM(void)
{
// 根据全局 Flag.PFCState 标志位进行分支跳转
switch(Flag.PFCState)
{
// 1. 初始化状态
case Init : StateMInit();
break;
// 2. 等待预充状态
case Wait : StateMWait();
break;
// 3. 闭环软启动爬坡状态
case Rise : StateMRise();
break;
// 4. 稳态运行状态
case Run : StateMRun();
break;
// 5. 故障保护死锁状态
case Err : StateMErr();
break;
}
}

初始化状态 (StateMInit) 与电流偏置采样

模块初上电或发生硬件复位后,程序首当其冲进入初始化状态。在此状态下,CPU 将完成程序运行相关参数的初始化配置,包含 PFC 控制变量初始化、环路变量初始化、SCI 通信配置以及极其关键的电流采样硬件零偏计算,同时对过流过压历史标志位进行清零。

由于运算放大器在实际电路中存在固有的温漂和零点电压偏置,在未发波(无电流)状态下,必须先读取 256 次 ADC 采样值求平均,将其作为后续运算的“绝对物理零点”,以此消除硬件误差。

当电流偏置采样完成后,程序完成初始化,状态机才会正式放行,进入空闲等待状态(Wait/Idle)。

其电流偏置采样的流程图如下:

电流偏置采样流程图

代码精析:初始化与零偏提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/** ===================================================================
** Funtion Name :void ValInit(void)
** Description : 相关参数初始化函数 (对应 Init 状态)
** Parameters :无
** Returns :无
** ===================================================================*/
void StateMInit(void)
{
static char FirstFlag=0;

// 强制拉低 PFC_OK 信号,明确告知后级 DC-DC (如 LLC), 当前 PFC 并未准备好能量,禁止其取电
PFCokDisable();

// 变量初始化防重入锁,确保上电只执行一次
if(FirstFlag==0)
{
FirstFlag=1;
// 所有环路与控制标幺变量初始化清零
VariableInit();
// SCI 串口通信变量及 FIFO 队列初始化
SCIValueInit();
}

// 持续调用电流硬件零点偏置采样函数
// 当该函数返回 1 (代表 256 次累加求平均已经完成) 时,才允许状态转移
if(ImosOffsetCal()==1)
Flag.PFCState = Wait; // 状态机跳转至等待预充软启状态

// PWMDAC(0); // 调试输出探针
}

/*
** ===================================================================
** Funtion Name :char ImosOffsetCal(void)
** Description :电流采样偏置精确计算
** Parameters : none
** Returns : 计算完成返回1,否则返回0
** ===================================================================
*/
char ImosOffsetCal(void)
{
// 静态计数器,记录当前累加次数
static int Cnt=0;
// 零偏校准结束标志位
static char FlagC=0;
// 256 次采样的滑动累加总和池
static long ImosOffsetSum=0;

// 抓取当前 ADC 通道 0 的电流采样值。
// 左移 3 位 (<<3) 是为了扩展精度,防止在后续大量累加中丢掉低位细节
ImosOffsetSum += AdcResult.ADCRESULT0<<3;

// 零偏累加计数递增
Cnt++;

// 严格累加满 256 次以求取高精度均值
if(Cnt>=256)
{
Cnt=0; // 累加计时清零

// 求 R相 电流采样 OFFSET 的 256 次平均值
// 右移 8 位 (>>8) 即等同于除以 256
SADC.ImosOffset = ImosOffsetSum>>8;

// 标记校准彻底完成
FlagC=1;
}

// 向系统主状态机返回零偏校准进度标志位
return FlagC;
}

空闲等待状态 (StateMWait) 与继电器控制

当初始化结束后,程序进入空闲(等待)状态。此时电源已接入电网,交流电通过防浪涌 NTC/PTC 电阻和整流桥向巨大的直流母线电容进行自然充电。

在此状态下,程序将:清软启动标志位准备后续软启,强制保持 PWM 关机状态,初始化电压环参考值与控制量。
核心判断逻辑为:通过侦测 BUS 母线电压的微分变化率来判断电容是否充满。当检测到 BUS 电压变化极小(两次检测相差不超过 10V)时,说明母线电容的自然整流预充电已接近稳定极值。
此时启动 BUS 电容自然整流充电保持计时器,若保持 500MS 电压均未快速增长,可笃定 BUS 电容已完全充满电。此时系统将下发指令闭合主继电器(短路掉防浪涌电阻),随后状态机跳转至正式的等待软启状态。

空闲状态保护继电器免受大电流电弧烧结的流程逻辑如下:

空闲状态程序流程图

代码精析:电压微分侦测与继电器驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/** ===================================================================
** Funtion Name :void StateMWait(void)
** Description : 等外/空闲预充状态机
** Parameters :无
** Returns :无
** ===================================================================*/
void StateMWait(void)
{
// 用于记录电压保持平稳的 5ms 中断节拍数 (100 次 = 500ms)
static unsigned char CapChargedCnt=0;
// 记录上一个调度周期的母线电压历史值,用于差分求导
static long VbusAvgPre=0;

// 清软启动标志位, 确保后续重头开始准备软启
Flag.ssFinsh=0;
// 严格封锁 PWM 关机状态,严禁在此阶段发波
Flag.PWM=0;
// 强制初始化目标电压参考值为 0
Vbus.Ref =0;
// 彻底清空电压电流双闭环的积分器与误差量
ResetVILoop();

// 【核心微分算法:检测母线电容充电拐点】
// 669 在此标幺系统中对应物理电压约 10V。
// 判断当前均值与上个周期历史值的绝对差值。若差值大于 10V,说明电容仍在剧烈爬坡充电中。
if((Vbus.Avg > (VbusAvgPre + 669)) || (Vbus.Avg < (VbusAvgPre - 669)))
{
CapChargedCnt=0; // 只要有剧烈波动,BUS电容自然充电保持计时器立即清 0,重新观察
}
else
{
// 若差值极小,说明充电曲线已平缓,进入饱和段。BUS电容自然整流充电保持开始计时
CapChargedCnt++;

// 保持 100个中断周期 (100 * 5ms = 500ms) 电压均未快速增长,可认为 BUS 电容已吸饱电能
if(CapChargedCnt > 100)
{
CapChargedCnt=0; // 计时器归零备用

// 【关键物理动作】:下发硬件 IO 驱动,吸合主功率继电器 (Relay),旁路掉防浪涌限流电阻
RelayOn();

// 硬件通路准备完毕,状态机安全跳转至正式的闭环软启状态
Flag.PFCState = Rise;
}
}

// 迭代更新历史值:BUS电压前值 = 当前值,供给下一次 5ms 中断差分比对使用
VbusAvgPre = Vbus.Avg;
}

闭环软启动爬坡状态 (StateMRise)

继电器吸合后,防浪涌电阻被切除,此时程序进入软启动状态机(Rise)。为了防止直接将目标电压设定为 380V 导致环路瞬间输出满载占空比炸管,必须让目标参考指令“温柔地长起来”。

环路控制量将在此进行最后一次初始化复位并正式开启 PWM 发波。系统巧妙地将初始电压参考量设定为当前实际采样到的自然 BUS 电压,随后在一个渐进的过程中让该参考电压递增爬坡至 380V。当参考电压达到目标设定电压,且实际反馈的 bus 电压也成功追随至目标电压的安全余量范围内时,判定起机成功。
此时,状态机平稳切换进入正常运行状态,PFC_OK 硬件信号拉高通知后级用电,软启结束标志位彻底置位。

软启动爬坡阶段的严谨流程图如下:

软启动程序流程图

代码精析:无缝衔接的软起动控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*
** ===================================================================
** Funtion Name : void StateMRise(void)
** Description :软启阶段
** Parameters : none
** Returns : none
** ===================================================================
*/
// 额定母线目标参考值标幺量 (25448 对应物理极值 380V)
#define INIT_BUS_REF 25448
// 母线追踪允许误差下限标幺量 (1339 对应物理极值约 20V)
#define BUS_V_OFSSET 1339

void StateMRise(void)
{
static char FirstFlag=0;

// 运行一次初始化赋初值逻辑 (通过 FirstFlag 互斥锁实现)
if(FirstFlag ==0)
{
FirstFlag =1;
// 环路控制量做发波前的最后一次清零,保证无历史包袱
ResetVILoop();

// 释放发波使能开关,底层的 ePWM 将在下个周期真正输出方波
Flag.PWM=1;

// 【核心无缝切换技巧】:初始化电压参考量,从当前真实的自然整流 BUS 电压开始起步递增,杜绝阶跃差
Vbus.Ref = Vbus.Avg;

// 安全限幅:防止当前自然母线电压由于某种干扰异常过高,超过了最终目标值
if(Vbus.Ref >= INIT_BUS_REF )
Vbus.Ref = INIT_BUS_REF;
}

// 电压软启动爬坡逻辑由外围 1kHz 慢速环处理,此处仅负责目标判定。
// 判断条件 1:指令参考电压 (Vbus.Ref) 已经爬升抵达了最终目标值 (380V / INIT_BUS_REF)
// 判断条件 2:实际物理反馈的母线电压 (Vbus.Avg) 也成功跟了上来,处于目标值减去 20V 误差允许带的上方
if((Vbus.Ref == INIT_BUS_REF) && (Vbus.Avg > (INIT_BUS_REF - BUS_V_OFSSET )))
{
// 起步完美结束,状态机切入长期的稳态正常运行状态
Flag.PFCState = Run;

// 置起 PFC ok 硬件引脚电平,授权后级 DC-DC (LLC) 模块解锁并开始满载抽电
PFCokEnable();

// 标记全系统软启成功结束标志位置位
Flag.ssFinsh=1;

// 复位状态机局部锁,供下一次热启动使用
FirstFlag =0;
}
}

/*
** ===================================================================
** Funtion Name :void ResetVILoop(void)
** Description :电压电流环相关参数、积分器彻底复位函数
** Parameters : none
** Returns :
** ===================================================================
*/
void ResetVILoop(void)
{
ILoop.out=0; // 清零电流环占空比输出
ILoop.Err=0; // 清零电流环瞬时误差
ILoop.Inte=0; // 彻底清空电流环积分池,防饱和过冲

Vloop.out=0; // 清零电压环基准输出
Vloop.Err=0; // 清零电压环偏差
Vloop.Inte=0; // 彻底清空电压环积分池

Iac.Ref=0; // 清空正弦电流瞬时参考指令
Iac.RefssK=0; // 清零电流参考值软起动系数,逼迫其重新慢慢增加
Vbus.Ref=0; // 清零电压参考值
Feed.ssK=0; // 清零占空比前馈软起动系数,启动时需缓慢增加介入比例
}

正常运行状态 (StateMRun) 与 故障状态 (StateMErr)

稳态运行 (StateMRun)

PFC 系统的稳态运行完全依赖于底层毫秒/微秒级硬件中断中的闭环矩阵运算。在正常运行状态(Run)的 200Hz 状态机分支中,主调度器事实上并不需要进行任何显式的干预与处理。
因此,StateMRun 函数内部为空,仅作为状态保持锚点存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
** ===================================================================
** Funtion Name :void StateMRun(void)
** Description :正常运行(空),主控制算法全交由高频中断在后台并发运行
** Parameters : none
** Returns : none
** ===================================================================
*/
void StateMRun(void)
{
// PWMDAC(20000); // 预留的数模转换调试探针口
// 此处无需代码,闭环数学模型在 ADC 中断中以 45kHz 狂奔
}

故障保护状态 (StateMErr)

电源系统面临的环境复杂恶劣,一旦检测到底层抛出硬件过流、软件过压、过热等越限错误,主状态机将毫无延迟地被强行切入故障状态(Err)。

在故障态下,首要任务是保命:立即清零 PFC 关机发波标志位(切断硬件驱动)、清零所有软启标志位、强行拉低关闭 PFC_OK 信号以掐断后级吸电,并彻底复位所有环路计算参数防止积分器在死锁期内疯狂累积导致爆表。

当引发保护的物理故障信息完全清除(如温度下降、浪涌消失等)后,程序将执行严谨的恢复出厂机制。需要特别注意的一个物理陷阱是:当 PFC 强制关闭后,母线电压会因为无法补充能量而自然跌落至网侧整流电压,这会必然诱发次生的“母线欠压保护(UVP)”。因此,在主状态机跳转回空闲状态(Wait)恢复重启动之前,必须预先人为介入,强行抹除因停机带来的虚假母线欠压保护故障标志位,最后重置控制变量以准备全新的生命周期。

故障发生与自愈流转逻辑如下:

故障状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
** ===================================================================
** Funtion Name :void StateMErr(void)
** Description :致命故障死锁保护状态
** Parameters : none
** Returns : none
** ===================================================================
*/
void StateMErr(void)
{
// 环路控制量执行紧急清零初始化,释放所有积分蓄力
ResetVILoop();
// 强制截断发波许可,彻底关闭 PWM,物理层级封锁功率流动
Flag.PWM=0;
// 软启动成功标志位清 0,剥夺已起机身份
Flag.ssFinsh=0;
// 撤销 PFC OK 授权,通知后级 LLC 电源立刻下电自保
PFCokDisable();

// 故障自动恢复侦测逻辑:
// 若 Flag.Err 中标志的各种实体故障 (过流/过热等,掩码 0x07 代表核心硬件错误) 已经物理消除归零
// 且按照外层逻辑等待了大于 1 秒的安全冷却期
if((Flag.Err & 0x07) == 0)
{
// 允许系统执行自愈,跳转至空闲等待状态 (Wait), 准备重新启动生命周期
Flag.PFCState = Wait;

// 【核心规避陷阱】:清空欠压保护标志位 (UVP)。
// 保护关机后 BUS 电压自然降得极低,必然会连锁触发 BUS 欠压保护标志位置位。
// 若不在此将其强行抹除,系统将永远无法跳出故障死循环。
if(Flag.Err & ERR_UVP)
Flag.Err &= ~ERR_UVP; // 位操作按位取反再与,精准抹除 UVP 故障位

// 所有变量进行深度初始化 (当前代码块将其注释,视具体安全策略由 StateMInit 分担)
// VariableInit();
}
}
输入过压欠压保护

交流输入过压与欠压保护及滞环逻辑设计

在数字 PFC 电源系统的实际工业运行中,交流电网(AC Grid)的电能质量往往难以保证,随时可能出现电压骤升(浪涌过压)或骤降(跌落欠压)。如果 PFC 控制器在极端恶劣的电网条件下依然强行全功率工作,极易导致占空比饱和、主电感过流饱和,最终引发炸机等毁灭性硬件故障。

因此,系统在 200Hz 的状态机调度中断中,专门设置了输入电压检测与保护模块(VacCheck)。本章将详细解析输入欠压与过压的数字定标推导、保护触发逻辑,并着重讲解电源控制中极为核心的安全概念——滞环(Hysteresis)机制。


核心概念深度解析:什么是“滞环”保护?

在电源保护逻辑中,滞环控制(Hysteresis Control)是一项不可或缺的设计哲学。

滞环的物理定义:
简单来说,滞环就是让“触发保护动作的门限”和“解除保护恢复工作的门限”不在同一个数值上,两者之间人为地拉开一个安全距离(被称为“死区”)。发生输入欠压保护和输入过压保护均具备滞环恢复的能力。

为什么必须要有滞环?(避免临界高频震荡)
假设系统设定交流过压保护点为 253V,如果不加滞环(即恢复开机点也是 253V):
当电网电压刚好在 253V 附近伴随电网杂波发生微小波动时(例如在 253.1V 与 252.9V 之间跳动),控制系统就会在极短的时间内疯狂地在“保护关机”和“恢复开机”之间来回切换。这种高频的启停震荡会产生极其恶劣的瞬态浪涌电流,瞬间击穿功率管。

引入滞环后的完美解决:
我们在系统里人为设定一个 15V 的滞环电压。

  • 过压触发点:253V
  • 过压恢复点:253V - 15V = 238V

当电压飙升越过 253V 触发停机后,哪怕电压立刻跌回 252V,系统也绝对不会恢复工作。必须等到电网电压彻底跌落并稳稳降至 238V 的安全线以下,系统才会解除过压警报。这就如同一道宽阔的护城河,彻底扼杀了临界点频繁启停震荡的隐患。


保护阈值物理量与 DSP 数字标幺换算

为了在 DSP 单片机中高效且精准地执行电压判定,我们需要将上述包含滞环的物理电压门限,严谨地映射转换为 Q15 格式的标幺化纯数字量。

定义交流电网的保护滞环电压偏移量为 15V:

1. 输入过压保护(OVP)对应的数字量:
根据系统标称的最高允许输入有效值 $V_{acmax}$,换算进入 DSP 的标幺门限:

2. 输入过压保护恢复对应的数字量:
触发过压后,必须减去滞环电压,才能进入恢复判定区间:

3. 输入欠压保护(UVP)对应的数字量:
根据系统标称的最低允许输入有效值 $V_{acmin}$,换算欠压停机门限:

4. 输入欠压保护恢复对应的数字量:
触发欠压后,电网电压必须回升加上滞环电压,才能确认电网已恢复可靠供电能力:

通过严密的数学计算,我们得到了 $16943$、$15939$、$12523$、$13528$ 这四个极为关键的定点数字。它们将直接以宏定义的形式固化在 C 语言代码中,充当安全守门员。


程序判定流程与 C 语言核心实现

系统的电压安全调度策略与流程执行如下:

  • 当检测电压有效值小于阈值,则触发输入欠压保护故障,置位输入欠压保护故障标志位,PFC关机标志位清零,关闭PFC_OK信号,跳转故障状态。
  • 当检测电压有效值大于阈值,则触发输入过压保护故障,置位输入过压保护故障标志位,PFC关机标志位清零,关闭PFC_OK信号,跳转故障状态。
  • 当发生输入欠压保护或输入过压保护后,若检测电压有效值均在合理范围内,则清过压和欠压故障标志位,等待状态机跳转空闲状态重启(无其他故障)。

其严密的程序流转判断逻辑如下图所示:

输入电压保护程序流程图

核心代码逐行深度精析

下面的代码完全映射了我们推导的四个数字标幺量,并使用按位操作(Bitwise Operations)安全地管理系统状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/*
** ===================================================================
** Funtion Name :void VacCheck(void)
** Description :输入电压检测与保护 (包含滞环抗震荡逻辑)
** Parameters : none
** Returns :
** ===================================================================
*/

// AC欠压保护动作点电压 (对应推导的数字量 1.2523 * 10^4)
#define MIN_AC_RMS 12523
// AC欠压保护恢复点电压 (对应推导的数字量 1.3528 * 10^4)
#define MIN_AC_RMS_RE 13528

// AC过压保护动作点电压 (对应推导的数字量 1.6943 * 10^4)
#define MAX_AC_RMS 16943
// AC过压保护恢复点电压 (对应推导的数字量 1.5939 * 10^4)
#define MAX_AC_RMS_RE 15939

void VacCheck(void)
{
// 【1. 输入欠压 (UVP) 触发检测】
// 电压有效值小于 AC 欠压保护值时,判定为 AC 欠压保护。
// 限制条件:系统不能处于 Init (初始化) 状态,因为刚上电时采样值默认从零开始爬升,需避免误杀。
if ( (Vac.Rms < MIN_AC_RMS) && ( Flag.PFCState != Init) )
{
// 欠压故障标志位按位置位 (|= 操作确保不破坏其他并存的错误状态码)
Flag.Err |= ERR_AC_LOW;
// 强制主状态机进入故障保护死锁状态 (Err)
Flag.PFCState = Err;
// 物理层切断发波,防止在极低电压下占空比满载引发电感严重饱和
Flag.PWM=0;
// PFC 握手信号拉低,禁止后级 DC-DC 负载抽取能量
PFCokDisable();
}

// 【2. 输入过压 (OVP) 触发检测】
// 电压有效值大于 AC 过压保护值时,判定为 AC 过压保护。
if ( (Vac.Rms > MAX_AC_RMS) && ( Flag.PFCState != Init) )
{
// 过压故障标志位按位置位
Flag.Err |= ERR_AC_HIGH;
// 同样切断一切工作,保护母线电容与功率 MOS 避免高压击穿
Flag.PFCState = Err;
Flag.PWM=0;
PFCokDisable();
}

// 【3. 滞回安全区间恢复检测】
// 当且仅当系统已经被打上了 AC 欠压或 AC 过压的错误烙印时,才执行恢复判定
if((Flag.Err & ERR_AC_LOW) || (Flag.Err & ERR_AC_HIGH))
{
// 【核心滞环逻辑】:
// 当三相AC电压均大于欠压恢复值且小于过压恢复值时,表明AC正常电压正常
// 即输入交流电压严格收敛于“欠压恢复值(13528)之上”且“过压恢复值(15939)之下”,彻底脱离死区边缘
if ( (Vac.Rms > MIN_AC_RMS_RE) && (Vac.Rms < MAX_AC_RMS_RE) )
{
// 使用按位取反与操作 (&=~) 清除指定的错误位,完美解开故障枷锁

// 清除 AC 欠压标志位
Flag.Err &=~ ERR_AC_LOW;
// 清除 AC 过压标志位
Flag.Err &=~ ERR_AC_HIGH;

// 注意:此处仅清除错误标志。真正的系统重启(从 Err 跳转到 Wait)
// 将交由 200Hz 状态机里的 StateMErr() 函数在确认无其他故障后统一调度。
}
}
}