本文主要是介绍一起玩儿Proteus仿真(C51)——04. 直流电机的启停、加减速和正反转仿真(L298)(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
摘要:本文介绍PWM信号的产生办法和直流电机的启停、加减速和正反转的仿真程序的编写方法
前边已经介绍了2中生成PWM信号的方法了。那么怎样才能节省一下资源,只使用一个定时器呢?这就是介绍的第三种方法,单定时器中断法生成PWM信号,这也是我们这次仿真程序所使用的方法。其基本原理就是通过一个定时器来不停的翻转输出引脚的电平高低来达到输出PWM信号的目的,但是如何控制频率和占空比呢?这就需要不断的改变定时器的初值了。下面先看一下原理图。
这个方法的难点就在于定时器初值的计算和设置。在讲解具体的计算方法之前,首先要明白一个道理,就是定时器的初值是怎样影响定时器中断时间的。定时器并不是初值越大定时器的周期越大,而是恰恰相反,定时器的初值越大,定时器的中断周期越短。这时因为定时器的中断是一种溢出中断,是在初值的基础上在脉冲信号的驱动下不断的累加,直到寄存器发生溢出,才会产生中断。所以,是初值越大,溢出越快,中断的周期也就越小。
明白了这个中断初值与中断周期之间的关系后,就可以来计算中断的初值了,在程序中用INTERVAL变量来表示。首先,要根据需要生成的PWM信号的周期,来计算出来一个PWM信号的初值,也就是定时器使用这个初值为开始,到溢出产生中断,是PWM信号的一个周期。接下来就是将这个周期分成2份,让定时器的一次中断,变成两次,这样就可以产生正确的PWM信号了。
那么,怎么来分割这个周期呢?在这里再定义一个DUTY变量,这个DUTY变量,用来控制输出信号的占空比。定时器是2字节的寄存器,那么其最大值就是0xFFFF,0xFFFF-INTERVAL就是我们之前计算的输出PWM信号周期对应的定时器计时周期的个数,而DUTY是高电平的定时器计时周期的个数,这样,就得到了高电平状态的定时器初值为0xFFFF-DUTY,同样的,0xFFFF-INTERVAL-DUTY就是低电平状态的定时器计时周期的个数,INTERVAL+DUTY就是低电平状态的初值。这样,通过调整DUTY的值,就可以改变PWM信号的占空比了(提示一下:DUTY的值应该在0~0xFFFF-INTERVAL之间)。
下面就是定时器初始化和中断函数的代码,可以对着上面的解释看一下。
sbit ENA = P2^2; // L298使能端 uint DUTY = 200; // PWM输出阈值 uint INTERVAL = 0xFC66; // PWM输出周期 void TIMER1_Init(void) // 定时器初始化 { TMOD &= 0x0F; TMOD |= 0x10; TL1 = (INTERVAL+DUTY); TH1 = (INTERVAL+DUTY)>>8; EA = 1; ET1 = 1; TR1 = 1; ENA = 0; } void timer1() interrupt 3 { TR1 = 0; if( ENA==1 ) { TL1 = (INTERVAL+DUTY); TH1 = (INTERVAL+DUTY)>>8; ENA = 0; } else { TL1 = (0xFFFF-DUTY); TH1 = (0xFFFF-DUTY)>>8; ENA = 1; } TR1 = 1; } |
这就是单定时器中断的PWM信号输出程序。PWM信号除了可以做直流电机调速外,还有很多用途,例如,控制LED灯的闪烁,这个时候与电机调速最大的区别是要求PWM的信号的周期要大得多,可以1秒甚至更长。C51单片机的定时器在12MHz的工作频率下,最大的定时时间也不到0.2s,那么这个时候怎么输出PWM信号呢?
这时候就需要设置一个变量,当每次中断的时候,给这个变量做累加,当累加到一定数值的时候,改变输出端的电平,在累加到一定值的时候,再次改变输出端电平,并将该变量归零,重新开始累加。
下面来看一个简单的例子,假设我要生成一个周期是2秒,占空比是75%的PWM信号。那么我可以设置一个unsigned char类型的变量,该变量值可以在累加过程中在0~255之间不断的循环,那么就将定时器的定时时间设置成:2秒/256=7.8125毫秒。每次中断给这个变量加1。因为占空比是75%,那么可以设置当这个变量值小于256*75%=192时,输出高电平,大于192时,输出高电平。这样就是一个占空比75%的PWM信号了。
具体代码如下所示:
unsigned char PWM = 0; // 中断计数器 unsigned char DUTY = 192; // 控制占空比 void timer1() interrupt 3 { TR1 = 0; TH1 = 0xF0; TL1 = 0xBE; PWM++; P1^0 = PWM<DUTY; TR1 = 1; } |
好了,这个仿真程序最难的部分PWM信号输出功能就介绍到这里了。在开发C51程序时,有一个很好的习惯就是把程序中用到的引脚都在程序的开始部分定义成变量,这样的好处是一行的操作都可以通过这个变量来进行,将来万一需要更换使用的引脚,那么只修改这个变量的定义就可以了,而不需要去程序中到处查找哪里用到了这个引脚。
完整的程序代码如下:
#include "reg51.h" #define uint unsigned int #define uchar unsigned char sbit IN1 = P2^0; // L298控制端IN1 sbit IN2 = P2^1; // L298控制端IN2 sbit ENA = P2^2; // L298使能端 sbit KON = P1^0; // 开关 sbit KFAST = P1^1; // 加速 sbit KSLOW = P1^2; // 减速 sbit KDIR = P1^3; // 方向 uint DUTY = 200; // PWM输出阈值 uint INTERVAL = 0xFC66; // PWM输出周期 void TIMER1_Init(void) // 定时器初始化 { TMOD &= 0x0F; TMOD |= 0x10; TL1 = (INTERVAL+DUTY); TH1 = (INTERVAL+DUTY)>>8; EA = 1; ET1 = 1; TR1 = 1; ENA = 0; } void timer1() interrupt 3 { TR1 = 0; if( ENA==1 ) { TL1 = (INTERVAL+DUTY); TH1 = (INTERVAL+DUTY)>>8; ENA = 0; } else { TL1 = (0xFFFF-DUTY); TH1 = (0xFFFF-DUTY)>>8; ENA = 1; } TR1 = 1; } void delay(void) { uchar i,j; for(i=0; i<50; i++) for(j=0; j<20; j++); } void main() { TIMER1_Init(); IN1 = 0; IN2 = 0; while(1) { if(KON == 0) { IN1 = KDIR; IN2 = ~KDIR; } else { IN1 = 0; IN2 = 0; } if(KFAST == 0) { delay(); if(KFAST == 0) { while(KFAST==0); if(DUTY<0xFFFF-INTERVAL-16) DUTY += 16; } } if(KSLOW == 0) { delay(); if(KSLOW == 0) { while(KSLOW==0); if(DUTY>15) DUTY -= 16; } } } } |
在这个程序中还有一个需要注意的地方就是按键的处理。对于加减速按键的处理程序,可以说是兼顾了现实中的情况。现实中的机械按键,由于是机械触点,所以在按下的过程中,会存在瞬间的连接不牢固的情况,这样就会出现瞬间的高低电平来回跳动,这个东西有一个专有名词就是按键抖动,所以在使用机械按键的时候,一定要有去抖动措施,可以是硬件的,也可以是软件的。软件的处理方法是,在检测到按键被按下后,不要着急进行处理,要延迟100毫秒左右(不同的设备会略有差异),再进行一次按键状态检测,如果仍然是按下状态,那么说明按键确实按下了。否则,就认为按键没有按下。这样处理就避免了按键抖动过程中将一次按键按下识别为多次的情况。这就是按键中有两个if判断和一个延时的原因。
在仿真中是没有按键抖动和接触不良这个情况的,所以不这样处理也可以。可以去掉其中的延时和一个if语句。
另外在按键的处理中,还有一个while循环语句,这个语句的目的是为了等按键抬起,也就是按键抬起之后,再做响应和处理。这样做的目的是为了避免把一次按键当成多次按键。因为单片机的处理速度很快,这里如果不做等待处理,你会发现你的DUTY值,瞬间就到达边界值了。如果说,你就想把长按当作连续按下,那也要加入一定的延迟,控制一下重复的频率,避免这个按键程序瞬间就被执行很多次,应该是在一种受控的状态下重复才行。
仿真结果如下图所示:
好了,这个直流电机启停、加减速和正反转的仿真实验就介绍到这里了。下个实验再见!
这篇关于一起玩儿Proteus仿真(C51)——04. 直流电机的启停、加减速和正反转仿真(L298)(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!