C51单片机——中断与定时器

这篇具有很好参考价值的文章主要介绍了C51单片机——中断与定时器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

中断是大多数CPU最精彩的部分之一,下面我们通过讲解和编程练习学习中断和定时器相关概念

目录

1.1.什么是中断

1.2.中断的种类

1.3中断的相关概念

1.4. 51单片机可用中断及相关引脚

1.4.寄存器

1.5.中断优先级

在未进行任何关于优先级的设置情况下,51 单片机(52 单片机)中断优先级如图所示。

2.1定时器与定时器中断

 2.1.1单片机的两个周期

2.1.2定时器原理

2.2相关寄存器

2.3.定时器的应用

2.3.1精准延时

2.3.2.定时器时钟

 2.3.3呼吸灯

2.3.4电机调速



1.1.什么是中断

中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

C51单片机——中断与定时器

简单来说,中断的概念非常好理解。当 CPU 在主函数 main 里执行任务 A 时,突然传感器甲发来信号说有件更紧急的事 B 需要马上处理一下。无奈,CPU 只能暂时停下手头的工作A,先去执行任务 B,想着等 B 处理完后再回来处理 A——这已经是一次中断了。

1.2.中断的种类

    51 单片机的中断主要有三类:
外部中断(External Interrupt): :在学习 IO 口“I”的功能,即检测功能时,
我们通过在 main 函数的 while(1)死循环里反反复复判断相应 IO 口电平状态来实
现。但是你有没有想过,如果我有 100 个传感器,是不是就要 100 个 if?由于
CPU 是串行,即一条指令一条指令往下执行的,如果 CPU 处理速度慢,在判断
第 3 个传感器时,第 100 个起反应了,可是在当执行到第 100 个 if 时,第 100
个传感器的信号已经消失了——说白了,CPU 脑子反应慢,处理不过来。另外,
100 个 if 放在主函数里,啥时候才能执行到除判断以外的更为重要任务?所以,
我们就想:能不能让传感器接一个特殊的引脚,只要传感器的输出引脚电平一改
变,CPU 立马触发中断,暂停主函数中更为重要的任务,去处理传感器起反应
后本应该执行的任务(原来 if 里面的语句),从而将软件判断改为硬件强制触
发呢?这就是外部中断!


定时器中断(Timer Interrupt): :定时器中断就非常好理解了:每隔一段
时间让 CPU 停下主函数的任务,去执行我们希望它定时执行的任务。举个例子,
我是个机器人爱好者,有时候需要测量机器人的轮速,通过一种名为编码器的传
感器,我可以得到车轮转过的圈数,如果我再加一个定时器,每隔一个固定的时
间段获取一次圈数,用圈数除以这个固定的时间段,不就得到了机器人车轮的转
速了吗?


串口中断(Serial Interrupt): :串口是芯片与芯片之间交流的通道。所谓“交
流”,无非指芯片与芯片之间相互发送数据或者指令。当发送端发送后,可以触
发中断,执行我们希望它发送后执行的任务,比如输出个“发送成功”;同理,
当接收端接收到数据或指令后,我们也可以让它触发中断执行某些任务,比如判
断一下接收到的是啥,然后执行相应操作。

1.3中断的相关概念

中断源:很好理解,就是引起中断的东西。对于外部中断来说,中断源
就是那些五花八门的传感器;对于定时器中断,中断源是内部的程序;对于串口
中断,中断源当然是其它芯片传来的数据或指令了。


中断服务子程序: :前面我们一直说 CPU 发生中断后去执行更为紧急的任
务,你可能很疑惑:在哪里执行?就是在中断服务子程序里执行的。后面我们会
单独写一个与你之前接触过的函数不太一样的函数专门作为中断触发后执行的
任务。


中断嵌套:在解释什么是中断时,我其实已经提到了中断嵌套这个问题
——暂停任务 A 转而执行任务 B 时,收到更为紧急的任务 C,这里第二级中断
就嵌套在第一级里。也许你会问:那是不是在执行一个中断服务子程序时又一个
中断被触发了,就一定要停下这个中断去执行新的中断服务子程序?当然不是
啦,凡是还有个等级优先嘛——这不,我之前不是一直强调“更为紧急”?这说
明中断之间还有个“优先级”呢!

1.4. 51单片机可用中断及相关引脚

我们用的这款 51 单片机,有 2 个外部中断(INT0、INT1),2 个定时器中
断(Timer0 与 Timer1)与 1 个串口中断(uart)。

C51单片机——中断与定时器

 把视线集中在 P0 组 IO 口。你会发现,这组 IO 口不仅可以做为正常的 IO口使用,似乎还可以用于别的功能。你瞧,P3.0 与 P3.1 是不是还附带着 RxD 与TxD?这些是与串口通信相关的引脚。同样,P3.2、P3.3 是与外部中断相关的引脚。P3.4、P3.5 是与定时器相关的引脚。

1.4.寄存器

寄存器的英文名是“register”,百度一查,发现它的中文意思是“注册,登记”。通俗地理解,我们要使用 51 单片机中某个功能,是不是得告诉单片机:我需要用你某某功能,具体怎么怎么用,某某参数是某值……那好,在哪里告诉51 单片机这些信息呢?当然是的相关的寄存器“登记”或“注册”了。喜欢打破砂锅问到底的你也许会问:为什么翻译成“寄存器”?我想,可能是在设置时候,某些参数暂时“寄存”在寄存器里吧。

1.5.中断优先级

在未进行任何关于优先级的设置情况下,51 单片机(52 单
片机)中断优先级如图所示。

C51单片机——中断与定时器

 图中的“中断向量地址”不用理会,因为这与汇编语言相关由图可见,优先级最高的是中断是外部中断 0,最低的是外部中断 3(如果有,取决于你用的单片机)。

2.1定时器与定时器中断

说到定时器,就得提一下他的总指挥——晶振。

C51单片机——中断与定时器

晶振过自己的振动为整个单片机提供时钟,如图下图所示。时钟都有了,芯片内部各
个电路可按时钟提供的节拍运作,那么还会紊乱吗?

C51单片机——中断与定时器

 2.1.1单片机的两个周期

时钟周期:这个好理解嘛,就是晶振产生的时钟的周期。你们手头常见的晶振有两种,一种上面写着“11.0592”,一种写着“12”或者什么也没有写,默认单位为“MHz”。这些参数是晶振的频率。如何通过频率得到周期呢?可以想到:1/f=T,不同频率的晶振的用途是不一样的,12MHz 的晶振一般用于定时器,而 11.0592MHz 的晶振一般用于串口通信。标准的51单片机晶振是1.2M-12M,一般由于一个机器周期是12个时钟周期,所以先12M时,一个机器周期是1US,好计算,而且速度相对是最高的(当然现在也有更高频率的单片机)。11.0592M是因为在进行通信时,12M频率进行串行通信不容易实现标准的波特率,比如9600,4800,而11.0592M计算时正好可以得到,因此在有通信接口的单片机中,一般选11.0592M。

机器周期 :执行一条基本指令所需要的时间。注意:我说的是基本指令,因为一次加法与一次乘法(少数乘法运算除外)所需要的时间当然得不一样吧?乘法运算的底层实现需要好多条基本指令,而加法一般就一条,这也就是为什么我们写程序要尽可能少用乘法运算的原因。对于 CPU 而言,机器周期由若干时钟周期组成。以你在用的 51 单片机为例,它的机器周期默认为 12 个时钟周期。当然了,既然说了默认情况下 12 个时钟周期构成一个机器周期,那么肯定可以更改了——事实上,你可以通过设置烧录软件将你的 51 单片机设置为6T 模式:6 个时钟周期构成一个机器周期,具体设置下图所示

C51单片机——中断与定时器

 6T显然相比于12T执行指令更快了。

2.1.2定时器原理

51 单片机里有一种寄存器专门用于数机器周期,它一共 16 位,由高 8 位TH(Timer High)与低 8 位 TL(Timer Low)组成,如下图所示。

C51单片机——中断与定时器

C51单片机——中断与定时器

​​​​​第一个机器周期后的值
C51单片机——中断与定时器
第二个机器周期后的值
C51单片机——中断与定时器
第65535个机器周期后的值
C51单片机——中断与定时器
溢出后自动清零

如上图的演示,这个寄存器一共有 16 位,所以可记 2^16=65536 个机器周期。当第 65535 个机器周期过后,寄存器的值全为“1”,再来一个机器周期(第 65536 个)后,便溢出清为 0,同时触发中断!

2.2相关寄存器

下面介绍一些常用的寄存器

C51单片机——中断与定时器
IE(Interrupt Enable)

 IE 寄存器负责各种中断的使能,下面逐个介绍 IE 寄存器每一位的作用:

  • EA(EnableAll) :使能所有中断。通俗地说,此位是 51 单片机中断系统的总闸,要使用中断功能,必须 EA=1。
  • ET2(Enable Timer2) :使能定时器 2 中断。要使用定时器 2 中断,应该使ET2=1。
  • ES(Enable Serial) :使能串口中断。要使用串口中断功能,令 ES=1。
  • ET1(Enable Timer1) :使能定时器 1 中断。要使用定时器 1 中断功能,令ET1=1。
  • EX1(Enable eXternal1) :使能外部中断 1。要使用外部中断 1,令 EX1=1。
  • ET0(Enable Timer0) :使能定时器中断 0。要使用定时器中断 0,令 ET0=1。
  • EX0(Enable eXternal0) :使能外部中断 0。要使用外部中断 0,令 EX0=1。
C51单片机——中断与定时器
TCON(Timer Controller) 寄存器

 TCON 寄存器主要用于控制定时器,也有部分与外部中断有关。在此,我只
介绍控制定时器的相关位:

  • TF1(Timer1 Flag) :定时器 1(计数器 1)溢出标志位。前面分析定时器理时我有原说过,当第 65535 个机器周期到来后,由 TH 与 TL 组成的 16 位“计分器”全部为 1,再来一个(即第 65536 个)机器周期后,这个“计分器”是不是就计不下了?这个时候 TF 标志位变为 1,同时触发中断,“计分器”置 0。
  • TR1(Timer1 Run) :定时器 1(计数器 1)的运行控制位。当 TR1=1 时,定时器 1(计数器 1)才开始工作,即那个所谓的“计分器”才开始数机器周期的个数。那你可能会问了:前面那个 IE 寄存器有个 ET1,不是开启定时器 1 的嘛?注意了,ET1 是开启定时器 1 中断的位。如果 ET1=0,那么定时器还能不能用呢?能!只是当“计分器”溢出后不会触发中断而已。中断是干什么的?中断是我们希望“计分器”溢出后 CPU 执行的任务,如果你没有任务给 CPU 执行,大可关了。
C51单片机——中断与定时器
TMOD(Timer Mode) 寄存器

顾名思义,此寄存器用于配置定时器的工作模式,一共 8 位,高 4 位(B7-B4)用于配置定时器 1 的工作模式,低 4 位(B3-B0)用于配置定时器 0 的工作模式。
 

 这里,我仅以高 4 位为例:

  • GATE :门控位。当这一位为 0 时,直接 TR1=1 就可启动定时器 1;当这一位为 1 时,不仅 TR1 得为 1,而且还需要用外部中断信号才能开启定时器 1。
  • C/T(Counter/Timer): :计数器与定时器选择位。你若希望它为定时器,就令这一位为 0(T 上面不是有条横线嘛,代表低电平有效嘛);反之,你若希望它为计数器,就令这一位为 1。
  • M1 、M0(Mode1, Mode0) :工作模式设置位,大有说头。M1 与 M0 一共有四种组合:00、01、10、11,分别代表工作模式 0、工作模式 1、工作模式 2 与工作模式 3。其工作特点具体见下表
M1 M0                           模式特点
0 0 13 位定时器(计数器),即由 TH 与 TL 构成的
16 位“计分器”中只用 13 位。这里用了 TL 的低
5 位与整个 TH。此模式不常用。
0 1 16 位定时器(计数器),就像我在介绍定时器原
理讲的那样,TH 与 TL 全用上。当“计分器”计
满后自动置 0,需要人为重新赋初值,否则从 0
开始计起。此模式最常用。
1 0 8 位自动重载定时器,仅将 TL 当成“计分器”,
当 TL 溢出后 TH 的值自动赋给 TL 而无须人为再
给初值。此模式在串口通信时常用。
1 1 此模式略复杂,不常用,也不介绍。
C51单片机——中断与定时器
TH(Timer High) 、TL(Timer Low)寄存器



C51单片机——中断与定时器

 这个寄存器就没啥好说的了,前面反反复复提到它。这里要提醒的是:每一个定时器(计数器)都有各自的 TH 与 TL,而并非共用一个。

2.3.定时器的应用

2.3.1精准延时

实现小灯闪烁时,我们用到了延时。当时,我们是通过让 CPU 原地数数来实现的。这种延时方式称为“软件延时”,它是不准确的,因为还有一些冗余的时间我们没有考虑到。因此,本小节我们讨论如何通过定时器来实现精准延时。

先给出代码,同样以小灯闪烁为例,闪烁周期为 1s(500ms 亮,500ms灭),电路接法和你前面做的一样。

#include<reg51.h>
sbit LED = P1^0;
unsigned char cnt = 0; //记录发生中断的次数
void main(void)
{
EA= 1; // 打开全局中断
ET0 = 1; // 打开定时器 0 中断功能
TMOD = 0x01; // 将定时器 0 的工作模式设置为工作模式 1
TH0 = 0x3c; // 给“计数器”高 8 位赋初值
TL0 = 0xb0; // 给“计数器”低 8 位赋初值
TR0 = 1; // 开启定时器 0
while(1)
{
// 这里面可以执行你想让 CPU 执行的主要任务
}
}
/*
功能:定时器 0 中断服务子程序,每当“计分器”溢出就来执行此函数。
注意:中断服务子程序的函数定义与普通函数不一样,interrupt 是关键
词,必须得有;后面的序号是定时器 0 的优先级序号,在第六章
我有提到,如果你改变了优先级,则此序号也应该相应改变。
*/
void timer0() interrupt 1
{
cnt++;
// 每隔 50ms 发生一次中断(见代码讲解),故 10 次为 500ms
if(cnt == 10)
{
cnt = 0; // 必变量必须清 0
LED = ~LED; // 每 500ms 改变一次 LED 状态
}
TH0 = 0x3c; // 溢出后“计分器”自动清 0,故需要重新设定初值
TL0 = 0xb0;
}

2.3.2.定时器时钟

这里采用模块化编程

1.main.c

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec,Min,Hour;
void main()
{
	LCD_Init();
	Timer0Init();
	LCD_ShowString(1,1,"Clock:");
	LCD_ShowString(2,1,"  :  :");
    while(1)
    {
          LCD_ShowNum(2,1,Hour,2);
		  LCD_ShowNum(2,4,Min,2);
		  LCD_ShowNum(2,7,Sec,2);
    }
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初始值
	TH0 = 0xFC;		//设置定时初始值
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		Sec++;
		if(Sec>=60)
		{
			Sec=0; 
			Min++;
			if(Min>=60)
			{
				Min=0;
				Hour++;
				if(Hour>=24)
				{
					Hour=0;
				}
			}
		}
	}
}

2.1LCD1602.c

#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

2.2LCD1602.h

#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

3.1Timer0.c

#include <REGX52.H>

/**
  * @brief 定时器0初始化
  * @param无
  * @retval 无  
  */
void Timer0Init()//1毫秒@11.0592MHz
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初始值
	TH0 = 0xFC;		//设置定时初始值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;			//打开定时器0中断
	EA=1;			//开总中断
	PT0=0;
}

/*定时器终端模板函数
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初始值
	TH0 = 0xFC;		//设置定时初始值
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		P2_0=~P2_0;
	}
}
*/

3.2.Timer0.h

#ifndef __TIMER0_H__//防重复定义
#define __TIMER0_H__


void Timer0Init();

#endif

4.1Delay.c

​
#include <REGX52.H>
void Delay(unsigned int xms)		
{
	unsigned char i, j;
	while(xms--){
	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
  }
}

​

4.2Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);


#endif

 2.3.3呼吸灯

1)原理
所谓呼吸灯,是指 LED 灯逐渐从灭到亮或从亮到灭。不同的占空比对应不同的模拟电压值,也就对应不同的 LED 亮度。当 PWM 波的占空比变化得很快时,从宏观上来看,LED 灯就是“连续”变化的——本质上,它还是离散的,只是变化得太快,欺骗了我们的眼睛罢了。

2)代码实现

#include<reg51.h>
void delay_100us(unsigned int time);
void setPWM(unsigned char IO, unsigned char level);
void main(void)
{
unsigned char level = 0; // 占空比等级
unsigned char step = 1; // 步长
unsigned char LED = 0;
while(1)
{
// 逐亮
for(level = 0; level <= 100; level += step)
setPWM(LED, level);
// 逐灭
for(level = 100; level > 0; level -= step)
setPWM(LED, level);
}
}
/*
由于占空比分为 100 级,周期为 10ms,所以 1 级应该对应 100us。
*/
void delay_100us(unsigned int time)
{
TMOD = 0x01;
TR0 = 1;
for(; time > 0;time--)
{
TH0 = 0xff;
TL0 = 0x9c;
while(TF0 == 0);
TF0 = 0;
}
TR0 = 0;
}
void setPWM(unsigned char IO, unsigned char level)
{
unsigned char group_num = IO/10;
unsigned char IO_num = IO % 10;
switch(group_num)
{
case 0:
P0 |= 0x01 << IO_num;
delay_100us(level);
P0 &= ~(0x01 << IO_num);
delay_100us(100-level);
break;
case 1:
P1 |= (0x01 << IO_num);
delay_100us(level);
P1 &= ~(0x01 << IO_num);
delay_100us(100-level);
break;
case 2:
P2 |= 0x01 << IO_num;
delay_100us(level);
P2 &= ~(0x01 << IO_num);
delay_100us(100-level);
break;
case 3:
P3 |= 0x01 << IO_num;
delay_100us(level);
P3 &= ~(0x01 << IO_num);
delay_100us(100-level);
break;
}
}

2.3.4电机调速

1) 电机调速原理
你手头用的较多的电机,是如图 7-6 所示的直流电机(就是你小时候玩的四驱车马达)。这种电机不分正负极,只要加上电池(当然你电池电压不能太大)就能够转动。当然,加不同方面和电压可实现不同方向的转动。

C51单片机——中断与定时器

然而,等你慢慢地接触更高级的电机时,你会发现你用的电机通常有多个引脚,如下图 所示。

C51单片机——中断与定时器

这种电机是可以调速的。它的三个引脚的作用,想必你都可以猜得出来:Gnd 与Vcc 肯定是少不了,用于供电;另外一个引脚当然得用于接收调速的信号。而调速信号则是由 pwm 波产生的模拟值。
2)代码实现

这里,我们实现一个可调速的风扇,风扇由 4 个按键控制,其中 3 个用于调整风扇转速,另一个用于使风扇停止转动。

#include<reg51.h>
sbit IN1 = P0^6; // L298N 信号输入 1
sbit IN2 = P0^7; // L298N 信号输入 2
sbit stop = P0^0; // 停止按键
sbit vel1 = P0^1; // 1 档按键
sbit vel2 = P0^2; // 2 档按键
sbit vel3 = P0^3; // 3 档按键
void delay_100us(unsigned int time);
void setPWM(unsigned char IO, unsigned char level);
void main(void)
{
// 起始时让风扇不转
unsigned char velocity = 0;
IN1 = 0;
IN2 = 0;
while(1)
{
// 检测按键,每个按键对应一个速度
if(stop == 0)
velocity = 0;
else if(vel1 == 0)
velocity = 30;
else if(vel2 == 0)
velocity = 60;
else if(vel3 == 0)
velocity = 90;
// 为电机设定 PWM 值
setPWM(6, velocity);
}
}
void setPWM(unsigned char IO, unsigned char level)
{
unsigned char group_num = IO/10;
unsigned char IO_num = IO % 10;
switch(group_num)
{
case 0:
P0 |= 0x01 << IO_num;
delay_100us(level);
P0 &= ~(0x01 << IO_num);
delay_100us(100-level);
break;
case 1:
P1 |= (0x01 << IO_num);
delay_100us(level);
P1 &= ~(0x01 << IO_num);
delay_100us(100-level);
break;
case 2:
P2 |= 0x01 << IO_num;
delay_100us(level);
P2 &= ~(0x01 << IO_num);
delay_100us(100-level);
break;
case 3:
P3 |= 0x01 << IO_num;
delay_100us(level);
P3 &= ~(0x01 << IO_num);
delay_100us(100-level);
break;
}
}
void delay_100us(unsigned int time)
{
TMOD = 0x01;
TR0 = 1;
for(; time > 0;time--)
{
TH0 = 0xff;
TL0 = 0x9c;
while(TF0 == 0);
TF0 = 0;
}
TR0 = 0;
}

以上就是关于中断和定时器的全部内容。

C51单片机——中断与定时器文章来源地址https://www.toymoban.com/news/detail-436662.html

到了这里,关于C51单片机——中断与定时器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 51单片机 | 定时器中断实验

      这一节介绍51单片机的定时器中断。 STC89C5X 含有 3 个定时器:定时器 0、定时器 1、定时器 2(注意: 51 系列单片机一定有基本的 2 个定时器(定时器 0 和定时器 1),但不全有 3 个中断,需要查看芯片手册,通常我们使用的是基本的 2 个定时器:定时器 0/1)。本节要实现

    2024年02月06日
    浏览(104)
  • C51单片机按键控制流水灯模式(定时器版本)以及定时器时钟

      上篇文章我们学了关于定时器的三大组成部分及许多寄存器的概念问题,这篇文章我们就要开始讲解实操部分。 首先,我们先来看看本文最后写成的代码:      以上三张是代码的主函数,此外,代码中还需用到的独立按键检测代码在下面:  注意:头文件中#ifndef和#def

    2023年04月17日
    浏览(46)
  • 51 单片机【外部中断、定时器中断、回调函数】

    ​这里的外部中断类似监听器,时时刻刻监视某引脚的电平变化;这里的定时器中断类似于定时任务,可以定时执行某函数;这里将回调函数和中断结合起来,案例里有点设计模式的味道(忘了哪个了,也可能就是感觉,关于高层不能调用低层的解决),也有点函数式编程的

    2024年02月04日
    浏览(62)
  • C51单片机——通过使用定时器控制LED闪灭

    目录 0 引言 1 定时的原理 1.1 生活中的定时 1.2单片机中的定时器 2 C语言程序设计 2.1 定时器的内部功能 2.2 设置寄存器 2.3 实验程序 3 有两个小问题(算是延伸吧) 3.1 只能0.05s闪一次吗? 3.2 每次都要在草稿纸手算初始值好麻烦

    2024年02月06日
    浏览(68)
  • 51单片机定时器中断TMOD

    最近在学定时器中断TMOD,写程序时,要对定时器进行配置 对TMOD进行赋值时,拌了好久。。。。 如何根据所用的      定时器0/1 ,  工作方式0/1/2/3      对TMOD赋值呢? 我们知道: 1.TMOD长这样 :  由八位组成,前四位是T1的参数;后四位是T0的参数;GATE 和 C/T\\\' 不用多讲,

    2024年02月12日
    浏览(41)
  • 51单片机中断定时器1用法

    中断为使单片机具有对内部或外部随机发生的事件实时处理而设置的。 中断技术不仅解决了快速主机与I/O设备的数据传送问题,而且还有具有如下的优点: 1. 分时操作:CPU可以分时为多个I/O设备服务,提高了计算机的利用率。 2. 实时操作:CPU能够及时处理应用系统的随机事

    2024年02月06日
    浏览(56)
  • 51单片机:中断系统(外部中断,定时器中断,串口通信)

    目录 中断系统简介: 中断的优先级和嵌套: 8个中断请求源及其优先级: 中断的分别介绍: 1、外部中断0:INT0   2、外部中断1  3、T0和 T1:定时计数器的功能 4、串口中断(串口为什么使用定时器后面讲) 中断寄存器 (1)中断允许控制(IE) (2)中断请求标志(TCON) (

    2024年01月25日
    浏览(44)
  • C51单片机定时器2实现SG90舵机控制

    谈起舵机最初的了解应用于航模,偶然的机会在网上看到有牛人使用多个舵机做人形机器人的关节,感觉这小东西很有趣,蒙发了买来学习学习的想法,这已经是多年前的事了,后来由于某些原因,这个想法直到今年才落地。小东西到手后,把玩了解后,本人把学习过程记录

    2023年04月24日
    浏览(49)
  • 【51单片机】:定时器的详解(包括对单片机定时解释、各类定时方式,以及中断方式)

              51定时/计数器的详解。                   码字不易,如有帮助请收藏,点赞哦。         前提:首先我们知道51单片机内部有21~26个特殊功能寄存器: P.x口寄存器:P0、P1、P2、P3 数据指针寄存器:DP0H、DP0L、DP1H、DP1L 定时器: TH1、TL1、TH0、TL0、TMOD、TCON 串口:

    2024年02月07日
    浏览(95)
  • 51单片机定时器中断按键消抖(无延时)

    单片机入门学习记录(二) 在机械按键的触点闭合和断开时,都会产生抖动,为了保证系统能正确识别按键的开关,就必须对按键的抖动进行处理。按键的抖动对于人类来说是感觉不到的,但对单片机来说,则是完全可以感应到的,而且还是一个很“漫长”的过程,因为单片

    2024年02月14日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包