目录
LED呼吸灯
直流电机调速
模型结构
波形
定时器初始化函数
中断函数
主程序
上一节讲了电机的工作原理,这一节开始代码演示!
我们上一篇说Ton的时间长Toff时间短电机会快,Ton的时间短Toff时间长电机会慢
并且我们还要保证无论Ton和Toff哪个时间比较长,Ts都得是固定值,因为为了保证周期一定。
下面我们先做一个“呼吸灯”感受一下上一篇博客中提到的PWM的意义:
LED呼吸灯
新创建本节第一个工程:LED呼吸灯
开始代码讲解:
首先根据LED的原理图定义一下引脚,P20(LED)为0时,灯亮。
#include <REGX52.H>
sbit LED=P2^0;//D1号LED的所接的单片机引脚
如果LED亮的时长比LED灭的时长要久,那么灯看上去就比较亮,反之就比较暗。这和前面说的Ton的时间长Toff时间短电机会快,Ton的时间短Toff时间长电机会慢是类似的。并且我们要保证LED亮灭的时间是一定的,即亮灭周期一定。
void Delay(unsigned int t)//传过来一个时间
{
while(t--);//传过来一个时间,每一次while循环都减1
}
void main()
{
unsigned char Time,i;
while(1)
{
for(Time=0;Time<100;Time++) //改变亮灭时间,由暗到亮
{
//比如TIME=1时,100-Time=99,然后进去循环20次再出来
//这时TIME=2,100-Time=98......如此循环
for(i=0;i<20;i++) //计次延时
{
//TIME+(100-TIME)等于固定值是为了保持周期是一样的
LED=0; //LED亮
Delay(Time); //延时Time
LED=1; //LED灭
Delay(100-Time); //延时100-Time
}
}
for(Time=100;Time>0;Time--) //改变亮灭时间,由亮到暗
{
for(i=0;i<20;i++) //计次延时
{
LED=0; //LED亮
Delay(Time); //延时Time
LED=1; //LED灭
Delay(100-Time); //延时100-Time
}
}
}
}
效果请看视频:
LED呼吸灯
呼吸灯就做成了,但是这种方式的缺点是显而易见的。它需要占用主循环不断地来翻转IO口来延时,在这一段时间内,也就是呼吸灯的整个过程中,主循环是没办法做其他事情的。
我们通常将PWM写到定时器里面,而且现在稍微高级一点的单片机,像STC的12系列或15系列及最新的系列都会有硬件的PWM,或者STM32单片机也会有硬件的PWM。因为不断地翻转IO口是一种比较简单但是比较占用CPU的一种操作,所以通常我们会用硬件来实现。而且硬件实现通常会寄生到定时器里面去,就是定时器既可以定时还可以兼具PWM的任务。但是我们STC89C52是没有这种功能的。所以我们就用定时器中断来实现这个功能。
直流电机调速
接下来我们开始演示第二个代码,并且是用定时器的这种方式来实现PWM。
写这个程序之前,我们先来看看产生PWM的方法。
模型结构
图中的比较值在硬件PWM里面是一个寄存器,我们可以从这个寄存器写一个固定值,那这个比较值就决定了我们的占空比(占空比 = TON / TS)。在我们的程序中可以定义一个变量,把它当做一个比较值,如果占空比不变的话,这个比较值通常也是固定的。
最终我们如何翻转IO口呢?我们就通过一个比较大小(在硬件是硬件比较器,在软件是 if 判断),将计数器的值和比较值进行比较。如果这个计数器的值小于比较值,就会输出0;如果计数器的值大于等于比较值,则输出1。当然,到底是大于输出0还是小于输出0呢,这就涉及到PWM的输出极性,在程序中是可以调节的,在硬件中也有这样相应的配置。
这个模型结构和单片机里面硬件的定时器以及PWM的结构是相似的,如果理解了这个结构,以后再学那些定时器PWM就会比较简单。
我们即将会写程序来模拟这个结构,来看看它是如果产生PWM的。
我们有了这个结构之后,如果知道它怎么工作的呢?主要是看这个波形
波形
PS:蓝色的波形对应的是模型结构图中的计数器那部分,它会定时自增,假设它是从0自增到100再回到0,它的波形就是这样的:
这个比较值是设置值,比如说我们想设置为60(即图中的红线所示),它在时间上就是固定肯定为60的。它会和计数器的值有一个交点。如果我们再把这两个值比较大小的话,那它的输出波形,就会呈现下面这个效果:
比如第一个交点前,计数器的值小于比较值,则输出0,在PWM传输的波形图上就是0。然后第一个交点过后,计数器的值大于比较值,则输出1,在PWM传输的波形图上就是1。
如果将比较值设置为10,则PWM输出的波形也发生相应的变化,占空比会变得高一些。
因此我们通过控制这个比较值就可以控制占空比。
比较值越大,占空比越小的;比较值越小,占空比越大。我们就利用这一点来控制电机的速度。
下面开始代码讲解:
新创建一个工程:直流电机调速
把这几个程序文件添加进来(下面每一个程序在之前的博客中都讲过了,如果不懂的可以翻之前的博客来看看)
然后新创建主程序main.c
根据原理图定义引脚,如果p10(Motor)=1,则电机旋转
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Timer0.h"
sbit Motor=P1^0;
定义四个变量
unsigned char Counter,Compare; //计数值和比较值,用于输出PWM
unsigned char KeyNum,Speed;//键码和速度
定时器初始化函数
然后初始化定时器,但是我们之前写的定时器初始化函数是1ms中断一次太慢了,我们重新生成一个100微秒中断一次的函数。
将生成的这个重装载值复制下来替换掉我们原先Timer0.c中的重装载值。
void Timer0_Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x9C; //设置定时初值
TH0 = 0xFF; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
中断函数
相应的,我们写好的中断函数里的重装载值也要改。(中断函数在之前的博客中也讲过了)
PS:这个PWM驱动电机在一定范围内是越快越好,越快越稳定,但是过快的话也没必要,这会占用资源,并且会增加开关损耗。我们通常设置在10K到20KHz的频率。如果频率比较低的话电机会抖动。如果频率在1K附近的话,电机可能会产生鸣叫。
根据模型结构图中的计数器那部分,设置它会定时自增,假设它是从0自增到100再回到0
所以将Counter设置为Counter%=100;
如果Counter=99,那99对100取余是99,
如果Counter=100,那99对100取余是0,
所以Counter%=100和If(Counter>100)Counter=0是一个效果
然后我们将计数器的值和比较值进行比较。
void Timer0_Routine() interrupt 1 //每100微秒进来一次
{
TL0 = 0x9C; //设置定时初值
TH0 = 0xFF; //设置定时初值
Counter++;
Counter%=100; //计数值变化范围限制在0~99
//如果Counter等于100,Counter自动清零
if(Counter<Compare) //计数值小于比较值
{
Motor=1; //输出1,电机旋转
}
else //计数值大于比较值
{
Motor=0; //输出0,电机停止
}
}
注意:我们这里设置的极性和模型结构图上的例子是不一样的,根据原理图Motor(p10)为高电平1时,电机转动,所以我们这里设置成:当计数值小于比较值时输出1,电机旋转,反之则输出0,电机停止。所以如果比较值越大,则输出1的之间就越大,电机速度越快。
主程序
那我们要设置占空比的话只需要设置比较值Compare
void main()
{
Timer0_Init();//定时器初始化
while(1)
{
KeyNum=Key();
if(KeyNum==1)//如果K1按下
{
Speed++;
Speed%=4;//如果speed等于4,speed自动清零
//设置速度的档次为0,1,2,3
//比较值compare越大,速度越大
if(Speed==0){Compare=0;} //设置比较值,改变PWM占空比
if(Speed==1){Compare=50;}
if(Speed==2){Compare=75;}
if(Speed==3){Compare=100;}
}
Nixie(1,Speed);//在第一个数码管上显示速度档次
}
}
刚上电的时候电机会转一下是因为单片机默认上电时IO口都是高电平。
效果请看视频:
直流电机调速
以上就是本篇的内容,源码会放在评论区,如有问题可评论区留言!