ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SVPWM原理与STM32实现:从扇区判断到七段式PWM的FOC关键解析

SVPWM原理与STM32实现:从扇区判断到七段式PWM的FOC关键解析 做电机FOC控制的同学十有八九都会在SVPWM这一关卡一下。我见过不少刚接触无感FOC、PMSM控制的工程师前面Clark变换、Park变换、PI调节器都写出来了一到SVPWM看到一堆扇区判断、X/Y/Z中间变量、七段式占空比映射整个人就开始发麻。这篇文章就把标准SVPWM这条线完整串一遍从三相逆变桥的8种开关状态讲起讲到扇区判断、伏秒平衡推导、X/Y/Z中间变量怎么算、七段式PWM怎么映射到STM32定时器最后再聊几个调试现场最容易踩的坑。适合正在做STM32电机驱动、PMSM无感FOC控制或者单纯想把SVPWM原理彻底吃透的入门读者。先把话放在前面SVPWM并没有多玄乎它本质上是“用一组固定方向的电压矢量在一个控制周期内拼出一个任意方向和幅值的平均电压矢量”。你把它理解成拼图就没那么可怕了。但拼图怎么拼、每块拼多久、拼完怎么送到定时器里这中间确实有不少细节。下面我就按实际调试时的思考顺序来写。1. 为什么FOC最后一步要看懂SVPWM1.1 SVPWM在整个FOC链路里的位置一套常见的PMSM FOC控制链路大概是这样的电流采样得到三相电流Ia、Ib、IcClark变换得到αβ轴电流Park变换得到dq轴电流然后和Id、Iq参考值分别做PI调节输出Vd、Vq再经过反Park得到Uα、Uβ。到这里后面的动作就全部交给SVPWM了。也就是说SVPWM是电流环和三相逆变桥之间的“最后一道翻译官”。前面算出来的Uα、Uβ是静止坐标系里一个电压矢量的分量但逆变桥不认识什么αβ坐标它只认“上下管什么时候开、什么时候关”。SVPWM要解决的问题就是怎样用六个功率管子的开关组合把Uα、Uβ这个矢量在时间平均意义上真实地输出到电机端。我实际调试中经常看到有人前面电流环调得还行一到SVPWM就开始乱试参数。这里有个基本认知要先建立SVPWM不是控制环节它是执行环节。它的输入是电压指令输出是三相PWM占空比。如果这一层波形不对电流环再努力也白搭。1.2 为什么要用SVPWM而不是SPWM很多新手会问我用最简单的SPWM正弦脉宽调制不行吗当然能转但性能和SVPWM差一截。最直观的一个差距是直流母线电压利用率。SPWM三相调制方式下相电压基波幅值最大只能到Udc/2线电压基波幅值对应是√3/2倍的Udc大约0.866倍的母线电压。而SVPWM在线性调制区可以达到相电压基波幅值为Udc/√3线电压基波幅值就是Udc。同样是310V的直流母线用SPWM能输出的最大线电压幅值大约268V用SVPWM能到310V差了大概15%。这个差距在电动工具、无人机电调这种追求功率密度的场景里是决定性的。另一个原因是波形质量。SVPWM内部实际上是在SPWM里注入了一个零序分量调制波变成马鞍形。这个零序分量不会在线电压里体现也不会产生额外转矩脉动但能让调制度更高、电流谐波更小。再加上SVPWM天然和空间矢量概念挂钩配合FOC的矢量思想非常顺所以现在商用电机驱动方案里几乎都是SVPWMST、TI、Microchip的电机库底层也都是这套东西。2. 先把电压空间矢量这层窗户纸捅破2.1 三相逆变器的8种开关状态三相全桥逆变器每一相就是一个半桥上管导通记为1、下管导通记为0。三个半桥组合起来一共2的3次方等于8种状态000、100、110、010、011、001、101、111。这8个状态里000和111是零矢量也就是三相都接到同一个电位上电机端实际合成电压是0。剩下6个非零矢量是100、110、010、011、001、101它们把αβ平面均匀分成6个60度的扇区。这6个非零矢量的长度都是2/3 Udc。比如100状态表示A相上管导通、B和C相下管导通这时候电机端线电压Uab等于Udc、Ubc等于0、Uca等于负Udc换算到αβ坐标系里就是一个长度2/3 Udc、方向为0度的矢量。110状态就对应方向60度。以此类推间隔60度转一圈。这里有个细节容易懵为什么是2/3而不是其他数字原因是在三相逆变桥这种结构里任意非零开关状态对应的相电压是一个固定的向量组合幅值和接线方式有关。你只需要记住结论6个非零矢量长度都是2/3 Udc方向从0度开始每60度一个。这个2/3在后面的伏秒平衡推导里会直接用到。2.2 伏秒平衡用平均值拼出任意矢量既然只有8个固定方向的电压矢量怎么输出一个任意方向和任意大小的电压指令答案就是伏秒平衡本质上和“用10块钱和1块钱的硬币凑出7块5”一个道理。在一个控制周期Ts里让两个相邻的非零矢量分别作用T1和T2时间再让零矢量作用剩下的T0Ts-T1-T2时间。把这三个时间段的电压效果叠加起来看平均就是一个指向任意方向、幅值可调的合成电压矢量。这就像用几块固定挡位的积木在一个小周期里快速切换宏观上看起来就是一块连续可变的积木。所以SVPWM实际上是个时间平均的过程。PWM频率越高一个周期越小平均值贴合得越准电流纹波也越小代价是开关损耗变大。这也是为什么电机控制频率一般取10kHz到20kHz平衡点就在这里。2.3 SVPWM的输入反Park出来的Uα、Uβ到底代表什么SVPWM函数接到的两个输入Uα、Uβ是反Park变换的输出。在FOC里电流环PI调节器输出的是dq旋转坐标系下的电压Vd和Vq它们分别对应励磁轴和转矩轴上的电压需求。反Park变换用转子角度θ把它们变回静止αβ坐标系得到Uα和Uβ。从物理意义上说Uα和Uβ就是“这个控制周期里我希望电机端感受到的平均电压矢量在α轴和β轴上的分量”。这个矢量的幅值就是电压指令的幅值它的方向由转子角度和电流环输出共同决定。需要留意的是SVPWM本身完全不关心电机是PMSM还是异步机也不关心角度来自编码器还是观测器。给它一个正确的Uα、Uβ它就能输出对应的PWM。这也是为什么无感FOC里无论你用滑模观测器还是磁链观测器最后电压指令都要交给同一个SVPWM模块来执行。搞清楚这一点你就不会被“无感FOC和SVPWM有什么关系”这种问题绕晕了。3. 标准SVPWM算法推导扇区判断和X/Y/Z中间变量到底怎么来3.1 扇区判断不用反三角函数也能知道在哪一格要合成一个任意角度的电压矢量第一步是判断它落在6个扇区里的哪一个。最朴素的办法是用反正切函数先算出角度再除以60度取整。但工程上没人这么写因为反正切运算耗时、占用单片机资源而且角度边界容易出精度问题。标准做法是直接观察Uα、Uβ之间的关系。6个扇区在αβ坐标系里是6个60度的楔形判断一个点落在哪个楔形里本质上就是判断这个点相对于6条扇区边界线在哪一侧。三个比较量就可以搞定令V1 Uβ令V2 (√3/2)Uα - (1/2)Uβ令V3 -(√3/2)Uα - (1/2)Uβ这三个量和6个扇区的边界线是一一对应的。只要判断V1、V2、V3的符号就能确定扇区号。通常用类似下面的方式编码sector (Uβ 0 ? 1 : 0) (V2 0 ? 2 : 0) (V3 0 ? 4 : 0)算出来的sector会在1到6之间分布和0到360度的6个扇区是一一对应的。这个方法的好处是只有比较和定点运算实时性好在STM32这种Cortex-M系列芯片上跑起来非常快。3.2 X/Y/Z中间变量的完整推导以第I扇区为例很多资料直接扔出X、Y、Z三个公式和一张查表让人看得一头雾水。这里我把第I扇区也就是0到60度扇区的推导过程完整写一遍原理通了其他扇区就是照葫芦画瓢。假设期望电压矢量的αβ分量是Uα和Uβ母线电压是Udc控制周期是Ts。第I扇区里合成这个矢量要用到的相邻非零矢量是100方向0度和110方向60度。设100作用时间为T1、110作用时间为T2零矢量作用时间T0Ts-T1-T2。在每个开关周期内U4和U6两个非零矢量的电压值分别是100矢量(2/3)Udc在α轴方向所以U4_α (2/3)UdcU4_β 0110矢量方向60度所以U6_α (1/3)UdcU6_β (√3/3)Udc按照伏秒平衡一个周期内的α轴和β轴电压平均值要等于期望矢量所以有Uα·Ts U4_α·T1 U6_α·T2 Uβ·Ts U4_β·T1 U6_β·T2代入数值Uα·Ts (2/3)Udc·T1 (1/3)Udc·T2 Uβ·Ts (√3/3)Udc·T2从第二个式子直接解出T2T2 (√3·Ts/Udc)·Uβ再把T2代回第一个式子T1 (√3·Ts/Udc)·((√3/2)Uα - (1/2)Uβ)到这里如果我定义三个中间变量X (√3·Ts/Udc)·Uβ Y (√3·Ts/Udc)·((√3/2)Uα (1/2)Uβ) Z (√3·Ts/Udc)·((-√3/2)Uα (1/2)Uβ)那第I扇区的结果就是T1 -ZT2 X。是不是一下子就对上很多工程代码里case 3里那行“T1 -Z; T2 X;”了这个推导过程我建议你一定要自己动笔走一遍能走通的人通常不会再被SVPWM难倒。很多网上的代码直接用X/Y/Z查表但具体哪个扇区对应哪个变量组合不同资料写法不完全一样只有亲自推过才不会被版本差异坑到。3.3 六个扇区的T1/T2查表与过调制处理其他5个扇区的推导和上面完全同理只需要把目标矢量换成对应的相邻非零矢量再做伏秒平衡。工程上通常直接给出一张查表。我这里给一组经典搭配注意这组表和上面扇区判断方式是配套的扇区T1T21ZY2Y-X3-ZX4-XZ5X-Y6-Y-Z这里的扇区编号就是3.1节里那个sector变量直接算出来后的数值。我提醒一句不同文档、不同代码库定义的X/Y/Z可能略有差别有的会把系数√3·Ts/Udc直接乘进去有的会在最后统一乘甚至有的把Y和Z的正负号反过来。所以你抄代码前一定要对照自己的推导过程看一遍别直接把一张表从网上搬过来就用。算完T1和T2之后马上要检查一个边界情况如果T1T2Ts说明电压指令超出了SVPWM的线性调制范围进入了过调制区。此时零矢量时间为负物理上不可能实现。工程做法是对T1和T2做等比例缩小让T1T2刚好等于Ts。很多新手忘记处理这一步结果大电压指令下电流波形严重畸变还以为是电流环没调好。实际上过调制区域里电压波形本身就会发生非线性变形电机会产生额外谐波、噪声和转矩脉动。控制策略上要尽量避免长期工作在深度过调制状态但作为SVPWM函数本身限幅保护是必须有的。4. 七段式SVPWM与STM32落地从三个比较值到互补PWM4.1 七段式开关顺序的核心逻辑知道了每个扇区T1、T2、T0的时长之后下一步是把这些时间排成实际的开关序列。最常见的排法就是七段式一个控制周期内开关状态按照“零矢量-第一个非零矢量-第二个非零矢量-零矢量-第二个非零矢量-第一个非零矢量-零矢量”的顺序走一遍。以第I扇区0到60度为例一个周期的状态顺序是000 → 100 → 110 → 111 → 110 → 100 → 000。七段式的好处是每个开关周期内三相桥臂各自只动作一次开关损耗均衡电流纹波也相对小谐波性能好。还有五段式也叫不连续PWM每个周期里有一相桥臂保持不动开关损耗可以降低约三分之一但谐波会变大。伺服、机器人这类对电流质量要求高的场景基本都用七段式一些对成本效率敏感的电动工具或大功率场合五段式才更有优势。这里有一个初学者容易忽略的点第I扇区第一个非零矢量是000到100还是111到110取决于你起始的零矢量选000还是111。七段式为了保证对称性会在周期的起点和终点放000中间放111。到其他扇区也是同样逻辑只是非零矢量组合变了。在做代码时你不需要真的逐个状态切换只需要算出三相每个半桥的翻转时刻也就是三个比较值即可。4.2 三个比较值的计算与扇区映射先定义三个基本时间点Ta T0/4 Tb Ta T1/2 Tc Tb T2/2这里把Ts归一化成1来理解最直观T0、T1、T2都是占空比意义下的时间总和为1个控制周期。Ta是周期开始后第一次翻转的时刻Tb是第二次翻转的时刻Tc是第三次翻转的时刻。在第I扇区三相切换顺序是A相先变、B相再变、C相最后变对应到中心对齐的PWM比较值就是A相用Tb、B相用Ta、C相用Tc。其他扇区的映射逻辑一样只是哪一相先切换、哪一相后切换不同。经典映射表如下扇区A相比较值B相比较值C相比较值1TbTaTc2TaTcTb3TaTbTc4TcTbTa5TcTaTb6TbTcTa这里特别强调一下这张表和扇区判断、T1/T2的查表是配套的你要是把扇区判断方式改成了其他写法这张映射表也必须重新推。我身边好几个同事就是单独换了别人的扇区判断代码结果PWM占空比在扇区边界出现跳变电机声音突然变大。4.3 一个能直接跑的SVPWM函数下面写一个相对完整的SVPWM计算函数输入是Uα、Uβ输出是三相比较值。代码里我做了归一化把总控制周期Ts归一化成1输入的Uα、Uβ也按对应比例归一化。实际工程里具体怎么归一化取决于你的坐标变换和电流环标幺值核心逻辑不变。// svpwm_calc输入Ualpha、Ubeta输出三相比较值Ta、Tb、Tc // 注意Ualpha、Ubeta已归一化到[-1,1]1对应最大线性区电压 #define SQRT3 1.7320508f void svpwm_calc(float Ualpha, float Ubeta, float *Ta, float *Tb, float *Tc) { float X, Y, Z, T1, T2; int sector 0; float V2, V3; X Ubeta; Y SQRT3 * 0.5f * Ualpha 0.5f * Ubeta; Z -SQRT3 * 0.5f * Ualpha 0.5f * Ubeta; V2 SQRT3 * 0.5f * Ualpha - 0.5f * Ubeta; V3 -SQRT3 * 0.5f * Ualpha - 0.5f * Ubeta; if (Ubeta 0.0f) sector 1; if (V2 0.0f) sector 2; if (V3 0.0f) sector 4; switch (sector) { case 1: T1 Z; T2 Y; break; case 2: T1 Y; T2 -X; break; case 3: T1 -Z; T2 X; break; case 4: T1 -X; T2 Z; break; case 5: T1 X; T2 -Y; break; case 6: T1 -Y; T2 -Z; break; default: T1 0.0f; T2 0.0f; break; } // 过调制保护 if (T1 T2 1.0f) { float scale 1.0f / (T1 T2); T1 * scale; T2 * scale; } float T0 1.0f - T1 - T2; float ta 0.25f * T0; float tb ta 0.5f * T1; float tc tb 0.5f * T2; switch (sector) { case 1: *Ta tb; *Tb ta; *Tc tc; break; case 2: *Ta ta; *Tb tc; *Tc tb; break; case 3: *Ta ta; *Tb tb; *Tc tc; break; case 4: *Ta tc; *Tb tb; *Tc ta; break; case 5: *Ta tc; *Tb ta; *Tc tb; break; case 6: *Ta tb; *Tb tc; *Tc ta; break; default: *Ta *Tb *Tc 0.5f; break; } }注意这个函数用了浮点运算在STM32F103这类M3内核上跑一次大概几个微秒对10kHz到20kHz的控制频率完全够用。如果想再快一点可以把Uα、Uβ先乘上一个比例系数转成定点Q15格式但可读性会下降。我的建议是先用浮点把逻辑跑通确认波形正确之后再做定点优化。4.4 STM32高级定时器配置与ADC采样对齐STM32做FOC通常用高级定时器TIM1或者TIM8输出三相互补PWM。关键配置点有三个中心对齐模式、互补通道、死区时间。中心对齐模式对应计时器从0向上计数到ARR再从ARR向下计数到0。比较值在上升沿和下降沿各匹配一次正好天然形成对称的七段式波形。选择中心对齐模式1还是模式2决定匹配时输出是高电平还是低电平需要和PWM极性、驱动电路逻辑配合起来调。互补通道要提前设置好死区时间。死区时间根据功率管参数来定不能太大也不能太小。太大导致波形失真、电流畸变太小容易上下管直通炸管。我一般先用示波器看开通关断延迟留够裕量再定典型IGBT死区时间在1到3微秒MOS管在200到500纳秒。STM32的TIM_BDTR寄存器里可以直接配置死区生成器很方便。ADC采样对齐是很多人忽略的点。在中心对齐模式下计数器值等于0或者等于ARR的时刻三相下桥臂要么全部导通、要么全部关断这个时间窗口里采样母线电流是最准的。尤其是单电阻电流重构方案必须在零矢量出现的中间时刻触发ADC采样否则重构出来的电流全是毛刺。所以SVPWM不只是输出占空比还决定了你的电流采样点放在哪里。这是系统级协同设计的问题不是单独调一个模块能解决的。5. 调试实录波形判断、常见坑与无感启动中的SVPWM5.1 上板前先仿真我强烈建议别一上来就烧板子先在仿真环境里把SVPWM看一遍。最轻量的方式是写个独立小工程把上面那个svpwm_calc函数放进去循环给不同的Uα、Uβ打印或者看T1、T2、三相占空比的变化。你会发现当电压矢量旋转时比较值是连续变化的扇区交界处有平滑衔接这就基本说明核心逻辑是对的。如果要用SimulinkPMSM的FOC仿真模型里也有现成的SVPWM模块可以对照。不过仿真软件里的SVPWM模块封装得太好反而不利于理解内部流程。我更推荐自己写C代码加一组纯数学测试给定幅值恒定、角度线性增加的电压矢量看三相占空比是否也单调连续。这个测试几行代码就能搞定对新手建立感觉特别有帮助。我在实际项目里还习惯把SVPWM算出来的占空比通过DAC或串口发出去配合虚拟示波器在电脑上观察。这样不接功率部分也能快速回归验证改完代码心里有底。5.2 怎么判断SVPWM写对了上板之后第一件事不是看转速而是先开环给一个固定的小电压矢量用示波器量相电压和相电流。正常情况下相电压相对母线中点是马鞍波形状叠加了高频开关分量线电压是阶梯状包络相电流接近正弦。如果你看到的是明显不对称或者六步换相那种平顶波多半是SVPWM的扇区判断或者比较值映射有问题。还有一个很实用的判断方法在开环状态下电压矢量按恒定速度旋转这时用示波器看相电流的包络如果电流波形在一个电周期内出现6次明显脉动大概率是扇区交界处处理得不对或者过调制限幅没做对。正常情况下开环电流应该是平滑正弦状不会出现60度一档的跳变感。这时候还可以算一下电机是否真的转起来了但转速本身不能说明SVPWM正确因为SPWM也能转。关键要看电流谐波大小和母线电压利用率这也是为什么很多老工程师一听电机声音就知道是不是SVPWM的问题——声音毛糙、有刺耳感多半就是PWM时序有问题。5.3 常见问题速查表现象可能原因排查方向电机不转但有嗡嗡声扇区判断错误或比较值映射反了开环给固定电压矢量量PWM输出和相电压电机一转就抖动T1/T2查表与扇区编号不配套对照推导检查X/Y/Z定义和映射表相电流过零畸变明显死区时间太大或未做死区补偿减小死区按电流方向做占空比补偿电压一大波形就乱过调制保护没有做检查T1T2Ts的限幅分支单电阻采样时而正常时而乱采样点不在零矢量中间检查ADC触发是否对齐计数器周期点上电瞬间电流冲击大占空比初始值太偏初始占空比设为50%再通过闭环慢慢提升这个表格里第七行那个“上电电流冲击”我单独说一下。很多时候不是SVPWM算法本身问题而是控制器刚上电时反Park出来的角度还没稳定占空比突然给得很大。我自己的习惯是上电后先让PWM输出50%占空比把母线电压通过上下管平均分到三相等母线电容充好电、状态变量初始化完成之后再切换到正常FOC控制。这个小细节能避免很多莫名其妙的过流故障。5.4 无感FOC启动阶段SVPWM要注意什么无感FOC和无感启动这两个词经常一起出现很多人会误以为无感算法和SVPWM有什么特殊绑定。其实没有。无感FOC里观测器估算转子位置和速度最终输出的还是Vd、Vq反Park之后照样进SVPWM。SVPWM并不关心角度来自哪里。但无感启动阶段确实有一个SVPWM需要格外注意的地方启动初期没有可靠的反电动势控制策略通常是开环强拉电压指令幅值会比较激进。这时候如果指令幅值超出了SVPWM线性区过调制限幅就会频繁介入。轻度过调制问题不大但深度过调制会导致电流畸变和转矩脉动反而干扰转子位置观测。所以无感启动策略里通常会让电压矢量幅值缓慢爬升或者在加速过程中动态限制电压指令上限。这不是SVPWM本身能解决的但它和SVPWM的过调制区间强相关。想调好无感FOC必须先对SVPWM的极限工作范围有清晰认识。6. 最后几个关键细节死区、限幅与参考代码的坑6.1 死区补偿和最小脉宽只要功率器件有开通关断时间死区就一定存在。死区会引入一个和电流方向相关的电压误差表现为电流过零附近的一小段失真。电机空载或轻载时这个失真会让电流波形出现平台声音变得毛糙。基础补偿思路是根据三相电流方向判断死区是让实际输出电压变高还是变低然后给该相占空比加上或减去一个修正量。修正量的大小通常等于死区时间占控制周期的比例乘以100%比如死区1微秒、PWM周期50微秒20kHz修正量就是2%。但直接按电流符号补偿会遇到一个问题电流过零时刻符号抖动补偿忽然切换反而引起新的畸变。我建议对电流符号判断加一个小的滞环区间比如电流绝对值小于某个阈值时不补偿大于阈值时才按符号补偿。这个阈值要根据负载电流大小和经验来调不是越大越好。最小脉宽问题主要出现在IPM模块或某些驱动芯片上比如IGBT导通时间太短时驱动芯片来不及正常响应。当你算出某一相的占空比太小比如小于1%或0.5微秒对应的时间实际输出已经不准确了。处理办法是做脉宽扩展或脉宽裁剪也就是把过短的脉冲时间合并到相邻扇区的矢量里保证一个周期内开关动作有效。这个优化起来比死区补偿更琐碎但只要电机在低速轻载下运行早晚会碰到。6.2 网上的X/Y/Z和扇区表别乱抄网上SVPWM资料确实多但错得也多。最典型的就是扇区编号不统一。有人用1到6表示从0度开始顺时针排列有人用二进制编码直接映射还有资料把Uα、Uβ的定义顺序对调了。你用A教程的X/Y/Z定义配B教程的扇区查表大概率会得到错误占空比。我自己的经验是拿到任何参考代码第一件事不是烧录而是把它的扇区判断、X/Y/Z定义、T1/T2映射、比较值映射这四段逻辑统一到一张推导表里验证。具体方法就是拿第I扇区0到60度手动算一下看它输出的T1、T2和比较值是否和理论一致。只要第一扇区对得上后面5个扇区基本不会乱。这个方法听起来麻烦实际上熟练之后五分钟就能验完但能帮你省下至少半天的调试时间。我自己当年就是直接抄了一份看起来没问题的表上电后电机剧烈抖动查了一天最后发现是X/Y/Z正负号定义不同导致第II和第VI扇区反了。6.3 从SVPWM往复盘整个FOC调试顺序很多人一上来就把无感观测器、电流环、速度环全堆上去调结果出了问题无从下手。我的建议是严格按照从底层到上层的顺序来先把SVPWM开环跑通确认相电压波形正常然后手动给一个固定角度做开环强拉看相电流是正弦且角度可控再切到电流闭环让Id、Iq能跟踪参考最后才上观测器或无感算法做速度闭环。你会发现只要SVPWM这层没问题后面每一层的问题都能定位得很快。反过来如果SVPWM没验证好后面所有环节的异常现象都可能误导你让你去调PI参数、调观测器增益结果全是无用功。我个人在实际操作中的体会是SVPWM代码量并不大真正难的不是背公式而是理解为什么要有X/Y/Z中间变量、为什么每个扇区的映射长那样。当你能从伏秒平衡方程出发自然推出T1、T2再看到那份经典查表时会有一种“原来如此”的通透感。这种通透感在后续调死区、做过调制、上无感FOC时会持续带来回报。最后再分享一个小技巧调试SVPWM时手里常备一张画好6个扇区和8个开关状态的小图比什么都管用。遇到问题就往图上标标完方向基本就清楚了。
返回列表