赞
踩
0、前言
永磁同步电机(PMSM)位置、速度、电流三闭环矢量控制(FOC)软件(以下简称“软件”)是为学习矢量控制算法而编写的,针对的是永磁同步电机,BLDC也可用,但是淘宝上面的电机大多数都是BLDC,传感器都是霍尔的(如果BLDC用FOC控制的话,扭矩是要比方波六步换相稍微低一些,但是声音会小很多而且扭矩平稳,所以我最喜欢玩霍尔BLDC的FOC控制),PMSM要比BLDC贵一些,理论上ABZ编码器foc最简单,hall_foc次之,无感foc转起来简单,控制得好比较难,当确认硬件板子没问题后,所有的电机控制都是参数问题了(建议一边学foc,一边理解这些参数)。
我有一套驱控(双驱,还顺便放了一个有刷控制接口和舵机控制接口)准备有偿提供,它既可以ABZ编码器控制,也可以BLDC方波控制,又可以BLDC霍尔FOC控制,又可以无感FOC控制 ,整套硬件采用模块化设计,驱控分离,预留多个冗余接口,单个驱动12个大mos管(单个 85V 160A),带有定时器BKE刹车和泄放电阻,最大可以支持功率为2KW的电机稳定运行(如果功率差距太大,采样电阻最好改一下),如果你不想管FOC的底层原理,只要求拿来就可调可用还有人指导,那这个再合适不过了,如果需要的朋友可以私信我,看最近是否有时间,本人有点考虑提供一对一的代码梳理服务。
首先说明,出此教程的目的,是为了让广大初学者可以挣脱淘宝CSDN海量教程的毒害,因为经常看到一些初学者搞了一堆资料,学了大半年,结果连程序都看不懂,实践的时候最多也就是按照教程接好线,通电看电机转一转,浪费的大量的时间和金钱,很多细节问题不清楚,没有解决问题的能力。
所以,我想以一套完整的、成熟的PMSM的FOC控制方案为例,从各个方面详细讲解了FOC的原理、实现方法等知识,几乎是逐条的分析了程序,又兼顾程序的系统层面,目的是让初学者看完我的资料,可以对FOC控制有一个类似游戏里面的“大地图”的概念,能达到这个目的,我就很满足了。知识是个触类旁通的东西,相信大家学完这个FOC算法后,肯定会对电机控制有一个更深的认识,其实也就是那么回事儿。
本文以程序中采用的最常见的id=0电流控制策略为例进行说明。
学习矢量控制,PMSM调速系统的框图必须要熟悉,如图1所示。其中红色虚线包围的为电流环。
图1 PMSM调速系统结构框图
从框图中可以看出,FOC运算,需要知道电机的三相电流和转子位置,因此我们在设计中,需要采集、解算这两个量。另外,三闭环控制时,位置环的输出作为速度环的指令,速度环的输出作为电流环的指令,当然,这些指令,都可以来自上位机输入,形成单电流闭环、或者速度电流双闭环控制的形式。
硬件平台我们采用常见的DSP,资源如下:
软件的构成如图2所示。
图2 软件结构图
软件通过RS232与上位机实时通讯交互,在主循环中采用查询的方式接收上位机发送的系统工作模式、伺服指令,并将系统相关状态监测量实时反馈给上位机;为了保障系统安全,软件对输入指令进行了限幅,避免指令超限。
软件的具体功能及实现需求如下:
a)在控制电源上电后,完成各功能模块初始化;
b)通过串口与上位机通讯。通过查询方式接收上位机数据,并进行解析;
c)利用TI最新的SVPWM算法,产生占空比,驱动功率板;
d)通过DSP内部自带ADC采集母线电压信号、相电流信号;
e)完成位置闭环、速度闭环、电流闭环调节;
f)系统状态监测、故障诊断与处理(工业应用中故障诊断、系统检测是很复杂的,为了突出本教程重点,故程序仅保留过压、欠压、过流保护功能);
控制软件全部功能描述见表1和表2所示 。
序号 | 功能名称 | 说明 |
1 | 初始化功能 | 控制电源上电后对DSP相关硬件配置及变量进行初始化 |
2 | SCI通信功能 | 与上位机通过RS232串口线进行实时通信 |
3 | 定时器功能 | 通过配置EPWM1获取125us周期定时,通过分频获取250us、1ms、10ms等周期定时 |
4 | 位置、速度、电流闭环调节 | 完成电机位置闭环、速度闭环、电流的闭环调节 |
5 | 状态监测与故障诊断 | 对系统工作状态,故障状态实时监测 |
表2 软件性能描述
序号 | 性能名称 | 说明 |
1 | 串口数据发送周期 | 通过串口向上位机发送数据的周期为1ms。 |
2 | 脉宽调制信号 | PWM波载波频率为8kHz,死区时间为2us |
3 | MainPwmISR()中断程序执行时间 | 中断执行一次时间不能超过总中断时间的80%,即不超过100us。 |
与FOC控制软件相关的硬件接口,包括RS232串口,片内ADC,外设I/O,数字I/O等,连接关系见图3所示,具体的IO配置表见附录I所示。
暂且把这个架构叫《伺服软件迭代进化架构》吧。
伺服软件代码架构千奇百怪,初学者甚至是一些“老师傅”都很难辨别优劣,差的架构不利于代码的理解,更不利于知识的积累,跨平台移植代码更是劳民伤财,不利于个人、企业的发展。
故本人根据自己的理解,提出本架构,买家可以依托自己原有的技术积累,以此架构为平台,进行自身技术的积累,软件的迭代、进化,通过不断的学习、实践,凝结核心技术,形成多种成熟可靠的电机控制软件平台,逐步扩充自己的伺服软件型谱,提高个人薪资竞争力,同时,打造出值得信赖的伺服软件品牌。
软件采用分层架构设计思想,分5个层次。
(1)系统配置层(CMD文件、LIB文件、启动文件、官方库文件……)。
(2)外设配置层(时钟、GPIO分配、ADC配置、PWM配置、SCI配置……)。
和硬件确定标准接口,明确所有接口的首选、备选方案。
根据无刷驱动特点,设置标准的底层配置模式,可实现快速切换。
(3)模块驱动层(速度反馈解算、位置反馈解算、各种滤波、指令斜坡处理、SVPWM计算、ClarkPark变换、各种PID运算、串口通信、总线、故障诊断……)。
以功能划分模块,各模块之间采用水平逻辑,模块内采用自上而下的垂直逻辑。
每个模块自成体系,具有明确的输入、输出,设置自己的参数结构体,表征所有用到的中间变量、标志位信息,整个生命周期不能直接影响模块以外的任何变量、参数,必须采用参数传递的形式。
模块采用统一写法,设计时必须附带调用说明,参数设置说明,功能说明等文档。
模块之间相互独立,便于更新、升级。
通过项目验证的,进入CBB货架管理,通过多次飞试验证等,进行封包,进入系统配置层的LIB中。
(4)控制算法层
a)前馈补偿、抗负载扰动、参数惯量(电机极对数、旋变极对数、旋变安装偏差、电阻、电感、转动惯量、负载转矩、反电动势系数、摩擦系数)、参数自整定(电流环、速度环PI参数)、电流解耦、死区补偿、执行机构末端抖振抑制、母线电压波动拟制补偿、再生制动、能量回馈、故障保护(常规故障保护、缺相运行、异常状态的再启动等技术)……
b)弱磁、直接转矩……
c)无位置算法(观测器、锁相环、高频注入……)
d)先进智能控制算法的研究应用(自抗扰、滑模变结构)……
根据算法需求,调用模块驱动层的函数,进行算法设计。
同样,算法之间平行、独立,自成体系,采用标准写法。
软件人员利用实验平台,验证先进的智能控制算法,不断的扩充算法层、优化、升级现有算法层中内容。
成熟稳定的算法可进入模块驱动层,无需修改,供直接调用。
(5)任务调度层(实时任务采用中断机制,执行快速、慢速流程任务,非实时任务采用多任务调度机制,整个软件采用实时操作系统进行任务调度)。
中断任务中完成实时性较强的电流电压采集、控制算法和故障保护等任务,不同的实时任务运行于不同的控制周期;
多任务调度系统中完成通信、系统检测等实时性要求较弱的任务。
(1)跨平台开发时,仅需移植系统配置层、外设配置层,内容很少,而核心的模块驱动层、软件算法层、任务调度层可无需修改,直接复制。
(2)模块按最小化功能划分,输入、输出、中间变量明确,各模块之间完全独立,使得功能函数的迁移完全自由,极大方便了协同工作,同时,给同一功能不同实现方法的对比分析提供了便利条件,有利于整个软件的迭代、进化。
(3)算法层模块化处理,使得软件可以很方便的借助Simulink工具对已有c形式的算法进行脱离硬件平台的仿真验证,模拟出很多硬件难以达到的边界条件,扩充了算法验证手段。同时,也更方便借助Simulink代码生成技术,对新算法先仿真,然后直接生成可用的c代码,加快了新算法、新控制策略的落地、应用。
(4)任务调度层模块化处理,单线程中任务跳转逻辑可采用Stateflow进行设计、验证,复杂系统的多进程任务,可采用RTOS进行调度、管理。
(5)组织架构清爽,模块界线清晰,方便排查问题。
(6)优秀的架构,使得软件的质量和可靠性得到保证。
选择1个具有代表性的型号,按以下步骤展开工作:
(1)以任务书为指导,按架构设计伺服软件框架。
输出:软件架构以及功能说明。
(2)从标准和产品特点出发,形成功能函数的编写模板。
输出:功能函数模板。
(3)梳理软硬件接口推荐表,进行软件底层配置的首选配置以及备选配置的快速切换设计,封装固定模式,预留可调模式的对外接口。
输出:推荐接口表;底层架构。
(4)按软件功能划分模块,梳理模块驱动层中所有函数的输入、输出、中间变量、激励要素等,并按c语言模板进行初次定型。
输出:功能函数表;每个函数的输入、输出要素表。
(5)实时任务、非实时任务流程设计,完成项目调试、交付,对软件进行评审。
输出:软件改进建议表。
(6)改进软件、新项目中继续验证优化,算法迭代、进化……
同时,梳理需求,搭建实验平台,帮助算法验证。
(1)软件模块的组织形式见图4所示。
图4 代码组织形式
1_SystemConfigs(系统配置层)
01_CMD
02_LIB (TI自带库、自行封装库)
03_Ti_Include
04_Ti_Source
2_PeripheralConfigs(外设配置层)(时钟、片内外的外设、GPIO分配及状态)
01_ADC_Init.c
01_ADC_Init.h
02_ePWM_Init.c
02_ePWM_Init.h
03_SCI_Init.c
03_SCI_Init.h
04_GPIO_Init.c
04_GPIO_Init.h
……….
3_Module_Drive(模块驱动层)
01_Interface (转子位置角获取,三相电流获取,电压、温度等的获取)
02_Driver(速度解算、PI运算、SVPWM计算、ClarkPark变换……)
03_Diagnosis (故障诊断)
04_Communication(串口通信、总线通信协议……)
05_MathFunction(斜坡处理、各种滤波、数学运算……)
……….
4_Algorithm(控制算法层)
01_ParaEstimate(极对数、电阻、电感、负载、转动惯量、反电势系数等辨识)
02_Self_Tuning(机械电气零位自整定,电流环、速度环等参数自整定)
03_Control(FOC算法,解耦、补偿、抑制抖动、弱磁、DTC、自抗扰、滑模……)
04_Sensorless(观测器、PLL、高频注入……)
……….
5_Multitask_Scheduing(任务调度层)
01_Scheduing (实时任务中断内快速、慢速流程调度)
02_OS-II (非实时任务,采用操作系统进行调度)
(移植的实时操作系统放置在系统配置层中)
(2)模块编写参考形式:
模块驱动层可参考TI的宏程序写法,采用1个H文件即可完成一个功能的编写,规范后直接调用即可。
/* =============File name:RAMPGEN.H ===========*/
#ifndef __RAMPGEN_H__
#define __RAMPGEN_H__
typedef struct { _iq Freq; // Input: Ramp frequency (pu)
_iq StepAngleMax; //Maximum step angle (pu)
_iq Angle; // Variable: Step angle (pu)
_iq Gain; // Input: Ramp gain (pu)
_iq Out; // Output: Ramp signal (pu)
_iq Offset; // Input: Ramp offset (pu)
} RAMPGEN;
/*--------------- Object Initializers-----------------*/
#define RAMPGEN_DEFAULTS {0, \
0, \
0, \
_IQ(1), \
0, \
_IQ(1), \
}
/*--------RAMP(Sawtooh) Generator Macro Definition------*/
#define RG_MACRO(v) \
/* Compute the angle rate */ \
v.Angle += _IQmpy(v.StepAngleMax,v.Freq); \
/* Saturate the angle rate within (-1,1) */ \
if (v.Angle>_IQ(1.0)) \
v.Angle -= _IQ(1.0); \
else if (v.Angle<_IQ(-1.0)) \
v.Angle += _IQ(1.0); \
v.Out=v.Angle;
#endif // __RAMPGEN_H__
也可参考以下写法:
H文件:定义自己的结构体,设置结构体变量初始值,声明对应函数。
/*
* CurrentGet.h
*/
#ifndef IPHASEGET_H_
#define IPHASEGET_H_
//======= 定义 三相电流采样 结构体变量类型 ===========================================
typedef struct
{
float IaTemp; //A相电流,临时变量
float IbTemp; //B相电流,临时变量
float IcTemp; //C相电流,临时变量
float IaOffset; //iA零偏
float IbOffset; //iB零偏
float IcOffset; //iC零偏
float Ia; //iA
float Ib; //iB
float Ic; //iC
}IPhaseStr;
#define IPhaseStr_DEFAULTS {0,0,0,\
0,0,0,\
0,0,0,\
}
//======= 函数声明 ===========================================
void GetIPhase(void);
#endif /* CURRENTGET_H_ */
对应的C文件:初始化结构体,函数自上而下垂直结构,输入、输出明确,不能直接使用其他函数的量,必须进行参数传递。对所有会用到、需观测的中间变量设置出来,供使用。
/*
* CurrentGet.c
*/
#include "CurrentGet.h"
#include "Para_Settings.h"
IPhaseStr IPhase = IPhaseStr_DEFAULTS;
//************************************************************
@ Description: 电机三相电流采样
@ Param
@ Return
//************************************************************
void GetIPhase()
{
IPhase.IaTemp = ((AdcRegs.ADCRESULT0>>4)*0.0182-44.46)*0.586;
IPhase.IcTemp = ((AdcRegs.ADCRESULT1>>4)*0.0182-44.46)*0.586;
IPhase.IaNew = (IPhase.IaNew*CURR_K1 + IPhase.IaTemp*CURR_K2);
IPhase.IcNew = (IPhase.IcNew*CURR_K1 + IPhase.IcTemp*CURR_K2);
IPhase.Ia = IPhase.IaNew-IPhase.IaOffset; //得到iA
IPhase.Ic = IPhase.IcNew-IPhase.IcOffset; //得到iC
IPhase.Ib = 0 - IPhase.Ia - IPhase.Ic; //得到iB
}
// End of file.
//============================================================
对于采用增量式编码器的电机,初次使用时,需要知道编码器零位和电机零位之间的安装偏差,具体原理和操作步骤见《02_ABZ编码器校准原理及教程》,本文档中默认,大家已经做好了校准工作了。
主循环模块是程序运行时的主控流程,控制程序的流程走向。
软件在控制电源上电复位后执行主程序,主程序完成初始化后,开中断(触发周期125us),然后进入主循环等待中断的发生,同时在主循环中完成与上位机的实时通讯,翻转LED,提示用户系统正在正常运行中。
主程序主要完成初始化(相关功能模块初始配置,控制变量、结构体的初始化),开主中断,然后进入主循环等待中断发生。主循环中完成与上位机的实时通讯交互(查询接收上位机输入的工作指令),具体流程见图5所示。
增量编码器,每次一上电,控制器不知道此时的转子位置,因此,每次重新上电,程序会先进入lsw=0的条件语句:
//------------转子定位模式----------------------------
if(lsw == 0) //lsw为0时,抱轴,开环的,此时给定指令清0
{
lsw_cnt++;
eQEP1.ElecTheta = 0; //电角度为0
EQep1Regs.QEPCTL.bit.SWI=1; //转子定位过程编码器将转动,在定位结束后对eQEP模块重新初始化
MotorCtrl();
if(lsw_cnt > 3500) //抱轴时间到了,就切换lsw=1,进入控制模式
{
lsw_cnt = 0;
lsw = 1;
// CalibrateFlag = 1; //校准电机零位时释放,正常工作时屏蔽此句。
}
}
在lsw=0的条件下,程序流程为:拖动电机(ud给定一个安全值,uq=0,反Park角度设置为0),把电机的d轴拉到和电机A相对齐。这个过程持续3500*125us=437.5ms,然后程序会自动切换到lsw=1的条件,保证了编码器从“零”开始进行增量计数,就获得了准确的转子位置角。
完成这个过程后,程序的执行流程则见图6所示。
矢量控制中,一般需要六路(3组)PWM输出,配置为两两互补带死区的导通形式。
void InitEPWM(void)
{
EALLOW;
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 0; //所有ePWM的TBCLK信号停止
EDIS;
InitEPwm1();
InitEPwm2();
InitEPwm3();
EALLOW;
SysCtrlRegs.PCLKCR0.bit.TBCLKSYNC = 1; //所有ePWM模块的时钟都开始于TBCLK的第一个上升沿
EDIS;
}
以上函数完成ePWM模块的配置:PWM初始化,配置PWM频率、死区,载波频率8kHz,死区时间为2us,对PWM1启动125us中断以及ePWM模块的错误联防保护功能。
TBCLKSYNC位可以用来同步所有使能的ePWM模块的基准时钟,在配置ePWM模块的过程中,遵循以下步骤:
(1)使能各个ePWM模块的时钟;
(2)将TBCLKSYNC清零,从而停止所有ePWM模块的时钟;
(3)对ePWM模块进行配置;
(4)将TBCLKSYNC置位。
ePWM模块一共有7个子模块,分别为:时间基准子模块(TB)、比较功能子模块(CC)、动作限定子模块(AQ)、死区产生子模块(DB)、斩波控制子模块(PC)、故障捕获子模块(TZ)、事件触发子模块(ET)。各子模块的主要功能如下:
a.时间基准(TB)
设定基准时钟TBCLK与时钟SYSCLKOUT之间的关系;
设定PWM时间基准计数器TBCLK的频率和周期;
设定时间基准计数器的工作模式:增计数、减计数、增减计数;
设定与其他ePWM模块之间的相位关系;
通过软件或者硬件方式同步所有ePWM模块的时间基准计数器,并设定同步后计数器的方向(增计数或者减计数);
设定时间基准计数器在仿真器挂起时的工作方式;
指定ePWM的同步输出信号的信号源:同步输入信号、时间计数器归零、时间计数器等于比较器B(CMPB)、不产生同步信号。
b.比较功能(CC)
指定EPWMxA和EPWMxB的占空比;
指定EPWMxA和EPWMxB输出脉冲发生状态翻转的时间。
c.动作限定(AQ)
无反应;
EPWMxA和/或EPWMxB得输出切换到高电平;
EPWMxA和/或EPWMxB得输出切换到低电平;
EPWMxA和/或EPWMxB得输出进行状态翻转。
d.死区产生(DB)
控制上下两个互补脉冲之间的死区时间;
设定上升沿延时时间;
设定下降沿延时时间;
不做处理。
e.斩波控制(PC)
产生斩波频率;
设定脉冲序列中第一个脉冲的宽度;
设定第二个及其以后脉冲的脉冲宽度;
不做处理。
f.故障捕获(TZ)
配置ePWM模块响应一个、全部或者不响应外部故障触发信号;
设定当外部故障触发信号出现时,强制EPWMxA和/或EPWMxB为高电平、低电平、高阻态、不做反应;
设定ePWM对外部故障触发信号的响应频率:单次响应,周期性响应;
使能外部故障触发信号产生中断;
不做处理。
g.事件触发(ET)
使能ePWM模块的中断功能;
使能ePWM模块产生ADC启动信号;
设定触发事件触发中断或ADC启动信号的频率:每次都触发、2次才触发、3次才触发;
挂起、置位或清除事件标志位。
在配置ePWM模块时,按照图7所示流程进行。
图7 EPWM模块
图6中,虚线包围的模块,可以根据实际需求,选择使用或者让PWM直接通过该模块。事件触发子模块用来处理事件基准计数器、比较功能子模块产生的时间,从而向CPU发出中断请求或者产生ADC启动信号SOCA或SOCB。
这里注意:配置PWM时,需要注意硬件上是否对PWM电平进行了翻转,高电平管子开通时采用“AHC”的死区方案,低电平管子开通时采用“ALC”的死区配置方案。
下面以一个例子(注意:例子和源代码配置的不一样,原理相同的)对上述内容进行说明,从程序解读,到配置,到波形占空比计算,到实验,供理解上述注意事项。
以ePWM1为例,程序如下:
EALLOW;
EPwm1Regs.TBPRD = PWM_PeriodMax; //9375
EPwm1Regs.TBPHS.half.TBPHS = 0; //相位寄存器清0
EPwm1Regs.TBCTR = 0; //计数器清0
EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; //增减计数
EPwm1Regs.TBCTL.bit.PHSEN = TB_DISABLE; //Master Mode,禁止相位装载功能
EPwm1Regs.TBCTL.bit.PRDLD = TB_SHADOW; //CTR=0时将映射寄存器中的值装入当前寄存器
EPwm1Regs.TBCTL.bit.SYNCOSEL = TB_CTR_ZERO; //CTR=0时发出同步信号
EPwm1Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1; //1分频
EPwm1Regs.TBCTL.bit.CLKDIV = TB_DIV1; //1分频
//CC
EPwm1Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; //CTR=0时将映射寄存器中的值装入当前寄存器
EPwm1Regs.CMPCTL.bit.LOADBMODE = CC_CTR_ZERO; //CTR=0时将映射寄存器中的值装入当前寄存器
EPwm1Regs.CMPA.half.CMPA = PWM_HalfPerMax; //50%;
EPwm1Regs.CMPB = PWM_HalfPerMax; //50%;
//AQ
EPwm1Regs.AQCSFRC.bit.CSFA = AQ_NO_ACTION; //软件强制时,0:无动作,1:强制为低;2:强制为高;3:禁止强制,无动作。
EPwm1Regs.AQCSFRC.bit.CSFB = AQ_NO_ACTION; //软件强制时,0:无动作,1:强制为低;2:强制为高;3:禁止强制,无动作。
EPwm1Regs.AQCTLA.bit.CAU = AQ_CLEAR;
EPwm1Regs.AQCTLA.bit.CAD = AQ_SET;
EPwm1Regs.AQCTLB.bit.CBU = AQ_CLEAR;
EPwm1Regs.AQCTLB.bit.CBD = AQ_SET;
//DB
EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; //使能上升沿和下降沿的延时信号
EPwm1Regs.DBCTL.bit.POLSEL = DB_ACTV_HIC;// AHC:EPWM1B反转极性。即高电平有效,互补输出
EPwm1Regs.DBCTL.bit.IN_MODE = DBB_ALL; //EPWM1B作为上升沿及下降沿延时的信号源。
EPwm1Regs.DBFED = PWM_Deadband;
EPwm1Regs.DBRED = PWM_Deadband; //2us=2*150*1/150M
//PC
EPwm1Regs.PCCTL.bit.CHPEN = CHP_DISABLE; //禁止PWM斩波控制
ePWM2和ePWM3的配置相似。注意:ePWM1为主模块,发出同步信号,ePWM2和ePWM3为从模块,接收同步信号,从而实现3个逆变桥的同步控制。
程序由ePWM1模块产生中断信号,故它还需要如下配置:
EPwm1Regs.ETSEL.bit.INTEN = 1; //使能
EPwm1Regs.ETSEL.bit.INTSEL = ET_CTR_ZERO; //TBCTR=0时产生EPWMx_INT中断
EPwm1Regs.ETPS.bit.INTPRD = ET_1ST; //每发生1次事件,产生中断信号EPWMx_INT
EPwm1Regs.ETCLR.bit.INT = 1; // Enable more interrupts
值得注意的是,本程序的配置方法是Epwm1B的波形来自ePWM1A的反转,并加入死区时间,我们给EPwm1Regs.CMPA.half.CMPA中装入值,产生ePWM1A波形(其实不用配置1B),然后通过死区模块,就产生了对称互补带死区的两路PWM波。死区采用AHC方式,即EPWM1b反转极性。高电平有效,互补输出。具体波形如图8所示:
图8 带死区的对称互补PWM波形产生原理
注意:该方式适用于低电平有效的驱动芯片。配好完成,主要保证比较值CMPA增大的时候,EPWM1A的占空比应该是增大的。
增减计数模式下,可以根据自己系统的中断周期和CPU主频计算出寄存器TBPRD中应该装入的值。
EPwm1Regs.TBPRD=9375; //150000k/9375/2=8k
配置好ePWM模块后,应该对其进行验证,确保配置的正确无误。
我们直接给3相桥臂进行占空比赋值:
Svpwmdq.Ta = 0.5;
Svpwmdq.Tb = -0.2;
Svpwmdq.Tc = 0;
即给ePWM模块的比较寄存器CMPA中设定值为:
EPwm1Regs. CMPA.half.CMPA = (int)(PWM_PERIOD_DEFAULT*(0.5 + 1)*0.5)=9375*75%.
EPwm2Regs. CMPA.half.CMPA =3750(40%);
EPwm3Regs. CMPA.half.CMPA =4688(50%);
此时不考虑死区,只考虑对称互补,6路PWM的占空比应该为:
表3 PWM的占空比
EPwm1A | EPwm1B | EPwm2A | EPwm2B | EPwm3A | EPwm3B |
75% | 25% | 40% | 60% | 50% | 50% |
程序中通过死区模块,采用ALC:EPWMA反转极性,即低电平有效,互补输出的方式(该方式用于低电平有效的驱动芯片),产生了带死区的互补PWM。
死区占空比为:150/9375=1.6%
因此最终的6路PWM占空比应该为:
表4 PWM的占空比
EPwm1A | EPwm1B | EPwm2A | EPwm2B | EPwm3A | EPwm3B |
26.6% | 76.6% | 61.6% | 41.6% | 51.6% | 51.6% |
将程序烧录到硬件中,利用示波器观察波形分别如图9-图14所示:
图9 EPWM1A波形
图10 EPWM1B波形
图11 EPWM2A波形
图12 EPWM2B波形
图13 EPWM3A波形
图14 EPWM3B波形
可见,实验和理论分析相符,配置的波形正确。
串口主要完成和上位机的通信,接收上位机的控制指令,并将一些需要监视的变量回传给上位机。
本程序中设置波特率为460800pbs,8位数据位,1位停止位;无奇偶校验,全双工传送,设置RX FIFO中断深度1,TX FIFO中断深度0。
SCI内部串行时钟信号由低速外设时钟信号LSPCLK及波特率选择寄存器共同决定。BRR为16为波特率设定值,其高8位装入SCIHBAUD中,低8位装入SCILBAUD中,即可配制出不同的波特率。
若BBR=0,
若1≤BRR≤65535,
因此,
这里对串口通信协议进行分析:
上位机给DSP发送9个字节,DSP给上位机回传14个字节的数据。这里仅分析烧录在DSP中的通信部分的代码,上位机中的通信代码原理和这个一样。
(1)先收上位机发来的数据(9个字节)
如果帧头是AA或者BB或者55,则接收数据。
RecData_ctrl_tmp[0],放置帧头;
RecData_ctrl_tmp[1]~ [7],放置数据;
RecData_ctrl_tmp[8],放置校验和;
(2)数据拼接
Rec_mode.Head = RecData_ctrl_tmp[0],放置帧头
如果帧头是AA、BB:工作模式
Rec_mode.Data0=RecData_ctrl_tmp[1]、[2]、[3]; //数据(暂未用)
Rec_mode.Data1=RecData_ctrl_tmp[4]、[5]、[6]; //数据
temp = (int32)(([4]*65536) + ([5]*256) + [6]); //这是拼接过程
Rec_mode.Data1 = temp*0.01-10000; //数据处理下,原因见下文分析
Rec_mode.CmdMode = RecData_ctrl_tmp[7]; //[7]是工作模式
如果帧头是55:参数配置模式
Rec_mode.FrameCnt = RecData_ctrl_tmp[2]; //帧计数
Rec_mode.ParaSet = RecData_ctrl_tmp[3]; //指令
Rec_mode.CmdMode = 0x04; //参数配置时应停机
根据Rec_mode.ParaSet对不同的参数进行调整。
(3)解析上位机指令
根据Rec_mode.CmdMode选择执行不同的流程。
0停机,1电流环,2速度环,3位置环,4参数配置,并将模式回传给上位机Snd_mode.CmdModeFdb。
Rec_mode.Data1为上位机发来的具体指令值。
(4)准备好要回传的数据
Snd_mode.Data0,Data1,Data2,Data3,Data4。
可以将电机运行过程中需要监测的数据赋给Data0,Data1,Data2,Data3,Data4,就能将数据传送给上位机,并用上位机保存下来,以便后续分析数据,处理问题,保存的数据利用Matlab画图分析特别方便,教程中给出了画图的M文件了。
这里一个有符号的数据占用了2个字节,可表示的数据范围为-32768~32768,为了能表示小数点后2位,故回传时将数据*100处理了,因此要回传的数据的大小范围在-32.768~32.768。
//32位系统,short为2个字节。因此,Snd_mode.Data0只能是-327.68~326.68,小数点后两位。
//如果*10,则Snd_mode.Data0只能是-3276.8~3276.8,小数点后1位。
//如果*1,则Snd_mode.Data0只能是-32768~32668,只能是整数。
ftemp1=(short)(Snd_mode.Data0 *100.0);
SndData_ctrl_tmp[3]=(ftemp1>>8)&0x00FF; //高8位放在[3]
SndData_ctrl_tmp[4]=ftemp1&0xFF;
(5)数据处理
如果收到的帧头是AA或BB:
SndData_ctrl_tmp[0]=0xCC;
SndData_ctrl_tmp[1] = Snd_mode.CmdModeFdb; //工作模式回传
SndData_ctrl_tmp[2] = Snd_mode.FaultStatus; //故障模式回传
ftemp1=(short)(Snd_mode.Data0 *100.0); SndData_ctrl_tmp[3]=(ftemp1>>8)&0x00FF; //高8位放在[3]
SndData_ctrl_tmp[4]=ftemp1&0xFF;
[5\6]放Data1,[7\8]放Data2,[9\10]放Data3,[11\12] 放Data4。
SndData_ctrl_tmp[13]放数据[1]~[12]的校验和。
如果收到的帧头是55:
SndData_ctrl_tmp[0] = 0x55;
[1]~ [9]放PI参数
SndData_ctrl_tmp[10] = Snd_mode.FaultStatus;故障码
SndData_ctrl_tmp[11] = Rec_mode.FrameCnt; 调参次数
SndData_ctrl_tmp[12] = Snd_mode.CmdModeFdb; //工作模式回传
SndData_ctrl_tmp[13]放数据[1]~[12]的校验和。
(6)回传数据
每1ms,向上位机回传SndData_ctrl_tmp[1]~[14]。
程序中设计的标志位,比如Rec_mode.flag_New、Snd_mode.flag_New等,是为了让数据传送有条不紊的进行,即以下几个流程按顺序执行。
scic_ctrl_RecData(); //从测控串口接收数据
RecData_Ctrl_Update(); //接收数据更新
Message_Ctrl_Decode(); //数据解码
Message_Ctrl_Encode(); //要发送的数据及回传数据编码
SndData_Ctrl_Update(); //发送数据更新
以上即为DSP程序中的通信部分,上位机中的代码和这个模式一模一样,学习时只需要掌握这个原理,就可以很方便的将代码移植到自己的程序中,实现电机运行过程数据的监控、分析,这对学习很有帮助。
最基本的FOC只需要两相电流即可,为了硬件保护等,我们可能还要采集母线电压、电流、温度等信号。并对采样值进行滤波数据处理。
ADC模块具有多个分频器,以产生任何需要的ADC操作时钟,图15为ADC的时钟链结构。
图15 ADC时钟链结构图
片内AD一共采集了以下物理量:
AdcRegs.ADCCHSELSEQ1.bit.CONV01 = 1; // A1-> Phase A Current
AdcRegs.ADCCHSELSEQ1.bit.CONV02 = 9; // B1-> Phase B Current
AdcRegs.ADCCHSELSEQ1.bit.CONV03 = 3; // A3-> Phase C Current
AdcRegs.ADCCHSELSEQ2.bit.CONV04 = 15; // B7-> Phase A Voltage
AdcRegs.ADCCHSELSEQ2.bit.CONV05 = 14; // B6-> Phase B Voltage
AdcRegs.ADCCHSELSEQ2.bit.CONV06 = 12; // B4-> Phase C Voltage
AdcRegs.ADCCHSELSEQ2.bit.CONV07 = 7; // A7-> DC Bus Voltage
CONV01]~CONV07是要转换的顺序,后面的数字代含义为:0~7代表片内F28335芯片的ADCINA0~A7,8~15代表B0~B7。这个程序所使用的硬件部分,把电机的A相电流接在了DSP芯片的ADCINA1引脚,B相电流接在了DSP芯片的ADCINB1引脚,以此类推。
在主中断中,对采集的量进行处理。
三相电流处理程序为:
//**********************************************************************
/*
@ Description: 电机三相电流采样
@ Param
@ Return
*/
//**********************************************************************
void GetIPhase()
{
IPhase.IaTemp = (AdcRegs.ADCRESULT1>>4)*0.00024414; //iA
IPhase.IbTemp = (AdcRegs.ADCRESULT2>>4)*0.00024414; //iB
IPhase.IcTemp = (AdcRegs.ADCRESULT3>>4)*0.00024414; //iC
if(MultCtrl.mult_mode == POWEROFF)
{
IPhase.IaOffset = IPhase.IaOffset*CURR_K1 + IPhase.IaTemp*CURR_K2; //iA偏置电流
IPhase.IbOffset = IPhase.IbOffset*CURR_K1 + IPhase.IbTemp*CURR_K2; //iB偏置电流
IPhase.IcOffset = IPhase.IcOffset*CURR_K1 + IPhase.IcTemp*CURR_K2; //iC偏置电流
}
IPhase.IaNew = (IPhase.IaNew*CURR_K1 + IPhase.IaTemp*CURR_K2);
IPhase.IbNew = (IPhase.IbNew*CURR_K1 + IPhase.IbTemp*CURR_K2);
IPhase.IcNew = (IPhase.IcNew*CURR_K1 + IPhase.IcTemp*CURR_K2);
IPhase.Ia = (IPhase.IaNew-IPhase.IaOffset)*2*0.909; //得到iA
IPhase.Ic = (IPhase.IcNew-IPhase.IcOffset)*2*0.909; //得到iC
IPhase.Ib = 0 - IPhase.Ia - IPhase.Ic; //得到iB
}
最终得到电机工作时的三相电流Ia、Ib、Ic,用于后续的Clark变换。
AD采样结果寄存器是12位的,即AdcMirror.ADCRESULT1范围为0-4095,相当于有4096个数字。
2^12=4096,给采样结果除以4096,也就是乘以0.00024414(对于整数,右移12位,也相当于除以4096),就把(0-4095)标幺为了0~1。
对于电机的电流是有正有负的,于是电流采样硬件电路设计的时候,理论上,会让电机电流为0时,调理电路输出的那个电压为AD量程的中位,系统上电后,电机未运行时,即在MultCtrl.mult_mode = POWEROFF时采集零偏电流并滤波,于是,电机没有电流时,采集的offsetA会得到0.5。
然后, IPhase.IaNew-IPhase.IaOffset,将(0-1)变换到(-0.5~0.5);然后乘以2,变换成(-1~1)。
0.909是与280xx的芯片进行统一,因为那些芯片是0~3.3V的采样范围,而F28335的AD采样范围为0-3V。
标幺化是使不同的产品系列,不同的控制芯片使用同一套程序的必要手段,当如果你只想研究单个平台,不标幺也是可以的,只要在平台转移的时候会很费劲。
这里注意:AD采样的寄存器AdcMirror和AdcRegs差别是,AdcRegs不支持DMA访问,而AdcMirror支持。
母线电压的处理程序为:
//**********************************************************************
/*
@ Description: 母线电压采集
@ Param
@ Return
*/
//**********************************************************************
void GetVbus()
{
Volt.Vbus=((AdcRegs.ADCRESULT7>>4)*0.00024414)*0.909*74.53; //AD获取直流母线电压
}
原理一样的。
序号 | 数据名称 | 数据标识 | 数据类型 | |
输出数据 | 1 | A相电流 | IPhase.Ia | float |
2 | B相电流 | IPhase.Ib | float | |
3 | C相电流 | IPhase.Ic | float | |
4 | A相电压 | 程序中未使用 | ||
5 | B相电压 | |||
6 | C相电压 | |||
7 | 母线电压 | Volt.Vbus | float |
EPWM1产生的主中断,每次事件触发1次中断,因此中断触发周期为125us,该模块执行时间不能超过125us的80%即100us,因为还要留时间去执行非中断中的任务。
该模块完成状态机机制下各子函数定时计数器时间分配任务(根据上位机指令,选择要执行的流程)。
中断服务程序流程图见图16所示,中断服务程序中主要完成(故障保护不分析):
(a)获取转子位置角度(每120us);
(b)获取电机三相电流(每120us);
(c)通过分频获取其他周期定时;
(d)每120us执行电流闭环控制;
(e)每500us执行速度闭环控制(如果被选择);
(f)每1ms执行位置闭环控制(如果被选择);
其中电流闭环、速度电流双闭环、位置速度电流3闭环执行流程分别如图16.1、图16.2、图16.3所示。
图16 中断服务程序流程图
图16.1 电流环子程序流程图
图16.2 速度环子程序流程图
图16.3 位置闭环子程序
其中三个环中使用的MotorCtrl()流程如图16.4所示。
图16.4 子程序
MotorCtrl函数中,对变换使用的转子位置角进行了判断,目的是,在开环拉电机时,直接使用给定的theta = 0.0(调试时,也可以改变theta值,观察电机转向)将电机拉到指定的位置。正常工作时,再使用编码器计算得到的eQEP1.ElecTheta。
同理,下面的这两段代码也是为了开环拉电机和校准编码器零位使用的,正常工作时,程序不会执行这两段代码。
if(lsw==0)
{
IparkU.Ds = 0.15; //d轴给定安全电压
IparkU.Qs = 0.0; //q轴给定电压为0
}
//校准零偏
if(CalibrateFlag==1)
{
IparkU.Ds = 0.0; //d轴给定安全电压
IparkU.Qs = 0.0; //q轴给定电压为0
}
定时计数器配置功能:使用EPWM1产生的主中断周期作为时间基准,使用TimerCtrl结构体定义的变量TimerCnt自加,得到125us到500ms不同的定时计数器,当TimerCnt变量累加到所需定时计数器的预设值时,设置时间标识位变量置1,执行相应的功能函数,函数执行完成后该变量清零。
软件定时器功能见表6。
表6 定时器功能
功能名称 | 定时器功能 | |
输入 | 激励事件 | ePWM1产生中断(125us) |
数据 | 无 | |
处理 | 125us周期定时到,标志位置1;定时计数器值累加,判断其他如250us、1ms、5ms、10ms、500ms等周期定时是否到。 | |
输出 | 各定时器标志位 | |
说明 | 标志位会在对应函数执行后清0 |
电机用的是增量式正交编码器,因此本程序采用的EQEP模块来采集电机的转子位置角,该模块就是一个片内外设的配置应用问题,没有技术上的难度。
(1)EQEP模块的初始化配置
void InitEqep1()
{
EQep1Regs.QUPRD = 1500000;// Unit Timer for 100Hz at 150 MHz SYSCLKOUT
EQep1Regs.QDECCTL.bit.QSRC = 00; // 00:正交计数,01:方向计数,10:增计数,11:减计数
EQep1Regs.QDECCTL.bit.XCR=0;// 0:上升沿和下降沿都计数,1:上升沿计数
EQep1Regs.QEPCTL.bit.FREE_SOFT=2; // 仿真挂起对其无影响
EQep1Regs.QEPCTL.bit.PCRM = 00;// 00:索引事件发生时复位,01:计数最大时复位 // 10:第一次索引事件时复位,11:单位时间输出时间时复位
EQep1Regs.QEPCTL.bit.IEI=0x10; // 10:在QEPI上升沿初始化位置计数器,11:在下降沿,0x:无动作
EQep1Regs.QEPCTL.bit.IEL=0x01; // 01:在QEPI上升沿将QPOSCNT的值锁存到QPOSILAT中,10:下降沿,11:软件发起一次事件
EQep1Regs.QEPCTL.bit.QPEN=1; // 使能QEP位置计数器
EQep1Regs.QEPCTL.bit.QCLM=1; // 0:在CPU读取位置计数器的值时锁存,1:定时器基准单元超时事件时锁存
EQep1Regs.QEPCTL.bit.UTE=1; // 使能eQEP定时器基准单元
EQep1Regs.QPOSCTL.all=0x0000; // eQEP位置比较单元寄存器
EQep1Regs.QCAPCTL.bit.UPPS = 5; // 单元位置事件预分频,UPEVNT=QCLK/(2的5次方)
EQep1Regs.QCAPCTL.bit.CCPS = 7; // 捕获单元定时器时钟预分频,CAPCLK=SYSCLKOUT/(2的7次方)
EQep1Regs.QCAPCTL.bit.CEN = 1; // 捕获单元使能
EQep1Regs.QPOSMAX = eQEP1.Encoder_N; // 码盘一周脉冲数的4倍(根据倍频的倍数而定,这里用4倍频)
InitEqep1Gpio();
}
(2)转子位置角的获取
void eQEP1_pos_get()
{
//Check the rotational direction QEPSTS为QEP的状态寄存器
eQEP1.DirectionQep = EQep1Regs.QEPSTS.bit.QDF; //正交方向标志
//Check the position counter for EQep1 QPOSCNT为QEP的位置计数器,32位,根据方向增减。
//根据编码器零位和电机转子零位的角度偏差,对位置计数器的值进行补偿
eQEP1.RawTheta = EQep1Regs.QPOSCNT + eQEP1.CalibrateAngle;
//RawTheta为电机实际角度,用码盘计数值表示,范围0-4000。
//RawTheta只能在(0-QPOSMAX)之间,超过了则进行处理。
if(eQEP1.RawTheta < 0)
{
eQEP1.RawTheta = eQEP1.RawTheta - EQep1Regs.QPOSMAX;
}
else if(eQEP1.RawTheta > EQep1Regs.QPOSMAX)
{
eQEP1.RawTheta = eQEP1.RawTheta - EQep1Regs.QPOSMAX;
}
//Compute the mechanical angle
eQEP1.MechTheta = eQEP1.Mech_Scaler * eQEP1.RawTheta; //码盘计数值,归一化处理为0-1
//Compute the electrical angle floor(x)返回不大于X的最大整数.
//以码盘归一化读数形式表示的电机的电角度0-1
eQEP1.RawElecTheta = (eQEP1.PolePairs * eQEP1.MechTheta)-floor(eQEP1.PolePairs * eQEP1.MechTheta);
// Check an index occurrence //索引事件锁存中断发生了
if (EQep1Regs.QFLG.bit.IEL == 1)
{
eQEP1.Index_sync_flag = 0x00F0; //输出标志位
eQEP1.QepCountIndex = EQep1Regs.QPOSILAT;
EQep1Regs.QCLR.bit.IEL=1; //Clear interrupt flag 清除中断标志
}
//得到-pi~+pi形式的转子位置角
eQEP1.ElecTheta = eQEP1.RawElecTheta * PI2;
if(eQEP1.ElecTheta > PI)
{
eQEP1.ElecTheta -= PI2;
}
}
看以上程序过程中,注意下,获取的转子位置角度是以哪种形式表示的,程序最后将电角度转化成了-pi~+pi。
(3)转速的计算
采用正交编码器的转速解算,有M法测速和T法测速,也有M/T法测速。TI官方对此有很完整的注释了,这里不再赘述。
不懂的同学,可以百度,也可以参看《深入理解无刷直流电机矢量控制技术》这本书的63-68页。
我这里粘贴下具体内容。
本程序包含了M法测速和T法测速,使用的是M法测速。
void eQEP1_speed_get(EQEP_POS_SPEED_GET *pV)
{
//M法,适用于高速,固定时间读位置变化
float tmp1;
// unsigned long t2_t1;
if(EQep1Regs.QFLG.bit.UTO==1) //If unit timeout 单位时间事件中断发生
{
pV->Position_k = 1.0 * EQep1Regs.QPOSLAT; //当索引事件发生时,位置计数器中的值会加载到QPOSLAT中
if(pV->DirectionQep == 0) // POSCNT is counting down 减计数
{
if(pV->Position_k > pV->Position_k_1)
{
tmp1 = -(pV->Encoder_N - (pV->Position_k - pV->Position_k_1));
}
else
{
tmp1 = pV->Position_k - pV->Position_k_1;
}// x2-x1 should be negative 减计数时,差值应该是负值
}
else if(pV->DirectionQep==1) // POSCNT is counting up 增计数
{
if(pV->Position_k < pV->Position_k_1)
{
tmp1 = pV->Encoder_N - (pV->Position_k_1 - pV->Position_k);
}
else
{
tmp1 = pV->Position_k - pV->Position_k_1;
}// x2-x1 should be positive 增计数时,差值应该是正值
}
if(tmp1 > pV->Encoder_N * 1.0)
{
pV->Speed_Mr_Rpm = pV->BaseRpm;
}
else if(tmp1 < pV->Encoder_N * (-1.0))
{
pV->Speed_Mr_Rpm = -pV->BaseRpm;
}
else
{
pV->Speed_Mr_Rpm = tmp1 * pV->Speed_Mr_Rpm_Scaler; //得到实际转速rpm
}
pV->Speed_Mr_Rpm = 0.25 * pV->Speed_Mr_Rpm_Last + 0.75 * pV->Speed_Mr_Rpm; //转速滤波
pV->Speed_Mr = pV->Speed_Mr_Rpm / pV->BaseRpm;
pV->Speed_Mr_Rpm_Last = pV->Speed_Mr_Rpm; //保存当前转速,用于下一个周期的计算
pV->Position_k_1 = pV->Position_k; //更新码盘值
EQep1Regs.QCLR.bit.UTO = 1; // Clear interrupt flag
}
方法和TI官方的一样,我是参考了符晓的代码。
解释如下:
#if FLASH
MemCopy(&RamfuncsLoadStart, &RamfuncsLoadEnd, &RamfuncsRunStart);
InitFlash(); // Call the flash wrapper init function
#endif
这几句是将FLASH中的程序COPY到RAM中运行,通常的目的是加快程序的运行速度,通常有两种情况需要这样去操作:
1、程序中对基要求比较高的函数,如中断;
2、程序需要对FLASH进行操作,这时就要把程序先复制到RAM中运行然后才能对FLASH操作。
RamfuncsLoadStart、RamfuncsLoadEnd、RamfuncsRunStart这三个变量是在CMD文件中创建的,创建方式如下:
LOAD_START(RamfuncsLoadStart),
LOAD_END(RamfuncsLoadEnd),
RUN_START(RamfuncsRunStart),
分别表示了装载函数的首地址,装载函数的结束地址和装载函数的运行地址;
执行完MemCopy(&RamfuncsLoadStart, &RamfuncsLoadEnd, &RamfuncsRunStart);后,便将FLASH中相关的程序COPY到了RAM中,之后的程序运行时,只要调用FLASH中RamfuncsLoadStart地址开始的相关函数,系统都会自动地指向RAM中相应的函数入口地址运行。
为方便不同控制板开发程序,梳理程序IO接口以及配置表7所示。
表7 控制软件接口描述
序号 | 接口 | 说明 | 引脚设置 | |
1 | 外设GPIO0~GPIO5 | 电机6路PWM波输出(低电平有效) | 上拉,作为PWM功能引脚 A作为信号输入,AHC互补带死区 | |
2 | 数字GPIO8 | Led | D400,系统工作指示灯,高熄灭 | |
3 | 数字GPIO10 | Led | D401,LED,高熄灭 | |
4 | 外设GPIO12(TZ1) | IR2136的fault引脚 | 上拉,作为TZ功能引脚,异步输入 | |
5 | 数字GPIO21 | Led | D402,LED,高熄灭 | |
6 | 数字GPIO52 | IR2136使能引脚 | 低电平使能 | |
7 | 外设GPIO18 | SCITX | 用于串口(SCI-b)通讯 | 上拉、作为功能引脚 |
外设GPIO19 | SCIRX | 上拉、异步输入、作为功能引脚 | ||
8 | 外设GPIO30 | CANrx | CAN通信 | |
外设GPIO31 | CANtx | |||
9 | 外设GPIO50 | EQEP1A | EQEP | |
外设GPIO52 | EQEP1B | |||
外设GPIO53 | EQEP1I | |||
10 | ADINA0 | 电流IA采样通道 | 片内ADC | |
ADINB1 | 电流IB采样通道 | |||
ADINA3 | 电流IC采样通道 | |||
ADINB7 | A相电压 | |||
ADINB6 | B相电压 | |||
ADINB4 | C相电压 | |||
ADINA7 | 母线电压 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。