图1是一个锯齿波幅值为1,载波比N=16,正弦幅值0.5,正弦与锯齿波相差为半个锯齿波周期;图2是图1水平翻转的结果;图3是图1和图2的叠加结果。图三中看到三角波形的spwm数据了吗?没错就这么简单,锯齿波正弦幅值比为2:1,相差半个锯齿波周期,计算出来的数据首尾组合成三角波数据。算法上就很简单了,假设数组中存放上述的锯齿波spwm数据,编号0~15共16个,依次取0,1,2,…15为三角波形开点输出数据,则反向取15,14,13,…0为三角波形关点数据即可。特别的如果载波比为奇数时三角波也为奇数,中间的数自然和自己组合的数据仍然是正确的。
注意这里提及的方法可以把三角波形的计算转换为锯齿波,但并不能减少计算量,因为如果是偶数个三角波只要计算四分之一周期就够了其他的是对称的,而锯齿波形数据需要计算半个周期。至此我们可以使用锯齿波的方法计算按三角波的数据输出。
(2)spwm迭代运算
为计算spwm占空比首先要求得锯齿波斜线与正弦交点,即方程KX+B=Y与Sin(X)=Y的解。这个方程是一个超越方程,只能通过迭代的方法计算。我们将直线方程变为X=(Y-B)/K,首先任取一个X值(这个值就是迭代初值),将它带入Sin(X)求一个Y值再将Y值代入(Y-B)/K求一次X值,再将X带入Sin(X)求一个Y值…如此反复若干次后可以得到一个结果就是方程式的解,这个就叫做迭代法。迭代次数越多;迭代初值越接近结果精度越高。每一组数据计算有这样几个参数1:正弦幅值(三角幅值与之成比例)2:载波比N值即半周期中三角波个数。另外pwm的占空比即定时器的通道值是和pwm的周期值有关系的,因此为了计算定时器通道值还需要一个周期值,对于stm32f这个值就是定时器ARR寄存器的值,它决定pwm周期(或频率)。附件中有个matlab_spwm.rar,matlab下计算定时器spwm数值和绘图的小工具上面几个图就是用它画的,开始部分可以置参数
s_M=32768/65536 %正弦波幅值比0~1
s_N=16 %半周期三角波个数
s_Pre=16384 %单片机定时器模数值
执行分为三部分,计算spwm数据;将数据按周期值换算为定时器设定值;画图;
计算定时器设定结果在TimerSetting中,复制粘贴替换tab字符成逗号就行了,下面是上述参数的计算结果:
1780 5246 8444 11221 13461 15088 16063 16384 16075 15182 13764 11893 9645 7102 4346 1463
(3)spwm实时运算的优化
如前所述简单的应用查表法就可以解决了但是复杂一点的功能就不能满足要求了,比如步进电机大范围调速、不同转速下恒力矩输出、恒加速运动等等。网上有很多文章介绍自然采样法的数学方法,并给出了各种优化算法,这些算法力图精确求解三角方程与正弦方程的交点,由于运算中带有大量的浮点运算若没有dsp或高速浮点处理芯片的支持必然会造成运算时间过长对实时调控产生影响。实际上我们需要的计算精度和每载波周期可能的开关点数量有关系,此数值用C来表示,称其为控制比(下文同)数值上=载波周期/pwm周期,同步调制方式中此值为整数,可以理解为用多少个pwm周期控制一个载波周期。pwm频率实际上是开关电路的极限频率或最理想工作的频率,假设每载波周期可能的开关点数量为512个则需要二进制的9位计算精度如果再加一位存疑位最多计算10位就够了。如果采用数据类型IEEE32浮点数迭代运算将得到24位(二进制)精度的计算结果,与实际需要相差甚远,也就是说你算了半天大部分是没有意义的计算,这种计算资源浪费发生在每一次运算中,因此累计起来就比较惊人了。从另一个角度看由于pwm频率的限制有高精度的计算结果也无法实施高精度的开关控制,这么说就好理解了。对计算采取一定的优化是必须的它将直接影响系统的实时性能。一个简单的方法就是在计算有初值后确定数据变动方向逐个可能值比较,另一个方法就是去浮点迭代计算,这两个方法在单片机上实现都可行有机会再发文讨论不再详述了。
4步进电机运行控制
至此假设我们可以很快的在单片机上进行实时的迭代运算了。迭代计算一个半周期的spwm其输入的原始参数只和三个数值有关:
1.M正弦的幅值这个值将决定步进电机的相电流大小,也就是步进电机的输出力矩。步进电机的优点之一是它的低速性能,当步进电机低速运转时转子始终受到磁场力的牵引转动,这个力的大小直接取决于励磁电流的大小,很小的速度下却可以用很大的力牵引转动。而直流电机的低速运动只能靠减小励磁电流实现,实际上就是小力矩实现低速,这样控制就不可能很精确特别是启停阶段尤其麻烦。步进电机在高速时力矩下降很快这个原因也不难理解,因为在步进电机励磁线圈里有多组磁极快速划过产生很大的感生电动势抵消了驱动的电压致使励磁电流变小力矩变小。为了改善高速性能解决办法只有一个提高工作电压。根据电机转速自动调整相电流的大小就可以实现恒定的力矩输出了,即低转速小幅值高转速大幅值。
2.载波比N和控制比C,这两个参数和调制频率F的关系是:
F*2C*2N=TF(TF是定时器的时钟频率)
我们慢慢来解释一下这个式子,调制频率就是我们实际想要的电机转速,从上面式子可以看出要让电机速度增加有两个方法即减小C或减小N(TF也是可以变的暂不考虑);
C实际上就是定时器的模数值(ARR),他的含义是使用几个定时器时钟周期产生一个pwm周期,前面的2是由于定时器工作在中央对齐模式下,定时器+-计数一轮产生一个完整的三角波周期。ARR的取值范围不可以太小,因为需要定时器中断来更新个通道的值,太小的数值两次更新时间过短而无法实现计算和更新步计数等操作。ARR的值如果太大则输出的pwm频率过低效果不佳。
3. N是载波比也就是半周期的三角波的数量,他的含义是使用几个pwm周期调制出一个正弦周期,其实也就是我们常说的细分数,它决定一个正弦周期(一个步距角)内可以控制的位置点的数量。在常见的驱动器中这个数值都是由拨码开关事先设定的,工作中是一个固定值,原因是硬件电路无缝的调整细分度几乎是不可能的。软件运算则没有这个问题,N的取值可以是任意的,唯一受影响的就是极性控制,上面算式里N前面的2含义是正弦正半周期和负半周期。N的取值还要考虑内存和计算占用;迭代算法如果有接近结果的初始值将使得运算效率大幅提高,因此对于有初始值的运算每一个计算点都要有存储空间占用,过大的N值要考虑内存资源,如果无初值的计算则要考虑计算资源。特别的当N值变化时初值会与真实值有差距,所以应尽量减少N的变动。
和步进电机转速有关系的参量在运行时都是已知的,因此任意时刻电机的转速都是可以计算的,如果电机能够平稳运行(没有堵转或丢步的情况)是不需要其它测速码盘装置的,闭环控制就更加没必要了。话又说回来如果丢步或堵转了闭环能解决吗?
5步值计数产生AB极性逻辑和正反转
网上看到的步进电机驱动程序千篇一律的都是数组存储io状态查表输出,带细分更少。先来梳理一下目前已经现在做到的内容,内存中有一个数组存放整个正弦半周期的实时运算的spwm数据,这个数据是根据当前的pwm周期折算过,因此每个pwm周期依次将数组内容赋值给定时器通道值就可以在定时器通道管脚输出正弦变化的pwm了。另外使用一个(l6205是两个,也可以用非门)io口来控制极性输出,比如高电平输出正弦负半周,低电平输出正弦正半周。
接下来需要安排一个合理而简单的数据结构把步进计数、细分和极性控制合为一体。首先我们用一个s32 stepcounter全局量来做步进计数,它的数值与步进电机的实时位置对应,这个变量是一个很关键的变量,因为任意时刻的AB两相spwm数据输出点和极性控制信号都由它产生。假设我们把它的低八位视为细分步计数(256为最大细分),则这个计步值除256对应整步位置。另外安排一个u8 microstep用来控制细分步进,它的取值和当前的细分度有关,如果256细分则microstep=1,128细分microstep=2,以此类推.如果电机正转前进一个微步则stepcounter+=microstep,如果反转一个微步则stepcounter-=microstep(微步进这部分可以放到中断程序里),OK正反转很简单,微步前进自动更新整步。关键点在于如何使用这个计数值产生两个相位的极性信号输出控制和A相B相的spwm数据位置,这里解释一下为什么会希望控制都由这一个变量产生:因为这样的程序最简单,虽然这里讲一大堆但是在编程实现时你就看到了就几行搞定;不容易出错,效率最高,你可以想象的到如果涉及的变量越多操作的代码越多需要考虑的可能性越多也越容易错;便于封装和功能扩展,比如你想做一个AD采样值与电机位置按一个比例同步的程序即转滑阻电机跟着动的小玩意儿,稍微改改把AD采样值赋给计步值其它都不用管了。
先说第一个数据的输出,spwm数组256个,如果不考虑极性则数据位置只和stepcounter的低8位有关,因此A相数据用stepcounter的低8位作指针从数组取数就可以(A相0值点为计步0值点),B相与A相相差为90度,所以A=0,1,2,。。。255,0 。。。则B=128,129,130,。。。127,128。。。能看出来吗?(A的数据指针+128)%256等同于 A的数据指针^128就是B的数据指针。
程序上这样写:
A通道值=spwm数组[(u8)stepcounter];
B通道值=spwm数组[(u8)stepcounter^128];
这里数组大小是256,所以一个逻辑异或就解决,如果你非要取大小是100个的话你就得(point+50)%100才能找到B相位点了。
第二个是极性控制,假定初始时A=0,B=0,C8=0,C7=0,A=0 B=0表示A相B相正半周的极性,C8是stepcounter第8位的值(最低位为0),之所以为8是前面提到过的条件即视低8位为微步,C7第7位,现在stepcounter计数到0和256,A相应该翻转一次,计数到128和128+256,B相翻转一次,由此列出真值表把逻辑关系提取出来:
C8 C7 A B counter值
0 0 0 0 0
0 1 0 1 128
1 0 1 1 256
1 1 1 0 256+128
0 0 0 0 512
A相的逻辑和C8相同,B相的逻辑能看出来吗?B相逻辑是C8异或C7。
程序上很简单,每次stepcounter值变化时执行:
A相IO控制位值=setpcounter第7位值;
B相IO控制位值=A相IO控制位值^setpcounter第8位值;
两句极性就被更新了,不管正转、反转极性输出总是对的,这里结合stm32的位带操作更好。上述内容对细分度为256,128,64...时成立,如果N不为2整数幂则会有余数个错位,N取值(即细分度)很小时影响比较大,但不会产生极性错误。