单片机攻略4——中断和串口

一、中断

首先要明白什么是中断程序

书上说:中断程序是一种特殊的程序运行方式,中断向量是这个中断程序运行的入口,一个中断程序包括中断请求,中断响应,中断关闭三步走。定义很严谨,用我的理解来讲中断其实可以类比我们日常生活中的“随时响应”

比如说:目前正在努力学习(主函数执行其任务),突然肚子咕咕叫起来(中断请求),肚子叫是随时都有可能发生的,也有可能是每次饭后隔3个小时又饿了,反正就是在满足肚子饿了的条件下才会叫(中断开关和条件),提醒我们要进食了(中断响应)。吃完饭后我们回去继续学习(中断返回)。

从程序的角度来看,一旦打开中断开关,中断就会开始判断条件,满足条件之后执行中断里面的程序内容。理清楚先后顺序对于理解后面的大规模程序非常重要。


二、中断几种类型

在C51/C52中有3大类中断服务:定时器计数器(简称定计,计数最小单位为1次,计时单位为1us)、串口通信(串口)、功率控制。

七个中断控制寄存器:TCON是控制定时器的启停,溢出和中断情况、SCON串口控制、PCON功率控制、中断允许IE、中断优先级IP、TMOD定计控制端口、SMOD串口控制。其中:TCON、IE最好位寻址,其余的最好还是字节寻址。

控制这几个口直接采用字节寻址的方式比如:TMOD=0x01代表设置定时器T0工作于方式1。

位寻址那就是直接 TR0=1;ET0=1; //对TCON的TR0位直接控制置1,ET0=1打开定时器0的开关

注意:C52/51的P3端口就是不同中断的控制口(见前几篇文章的C51引脚配图)。

0和1:以定时器为例,51系列的52RC单片机有三个定时器/计数器,分别叫做T0,T1,T2,T2和T0T1对应的控制寄存器名字也不尽相同,需要网上查资料或商家的手册。

2.1 TCON和TMOD

1.控制寄存器:TCON是定时器计数器的控制器,里面叶存了内外中断的标志。

2.中断源:INT0/1:外部中断0和1;T0/T1:内部晶振中断

3.定时和计数的区别:定时通过单片机的晶振产生时间基准,常需要和中断一起进行配合;而计数功能需要在定时器引脚(见引脚图)连接要被计数的波,才能运行。

细节看这个(9条消息) 51单片机---IE寄存器,TCON寄存器,TMOD寄存器_IMET-的博客-CSDN博客_ie寄存器

——1.TMOD的Gate位一般用来测量时序长度,所以大部分时间不用,赋0。

——2.写程序时:一般先开启TMOD,设置好计数值TH和TL,选择TCON和IE要打开的开关,最后一个EA开启总中断即可完成。

——3.计数值的设置问题:1.用STC-ISP提供的功能计算   2.TH=(当前选择的工作方式的计数/计时最大值 - 目标计数/计时值)/256     TL = (当前选择的工作方式的计数/计时最大值 - 目标计数/计时值)% 256。写程序时,除了方式2,计数值的设置需要在初始化和中断函数两个地方都写上。

——4.TCON中的IE/TF:定计计数满溢出TF变成1,使用中断会自动清零,比较方便。但是要是用查询的方式(也就是自己写的函数)完成定时器计数器任务就需要人工清零。

——5.TR是定时器的开关;ET是定时器的中断开关!如果不用定时器的中断可以不开ET,但是用到了定时器就一定要开TR!

——6.四种计数方式的对比:

方式0:13位定时计数方式,最大计数值为2^13=8192,定时8192个机器周期。此方式已经不再用了,是为了和以前的单片机兼容,初学者不用掌握。
方式1:16位定时计数方式,最大计数值为2^16=65536,定时65536个机器周期。此方式可实现最大的定时时间和最大计数次数。是最常用方式之一。
方式2:8位自动重装计数方式,最大计数值为2^8=256,定时256个机器周期。此方式工作时定时或计数到了不用重装初值,而且TH和TL设置的数据大小一样。另外在串口通讯时常用此方式。是最常用方式。
方式3:特殊工作方式。将定时器0分成两个8位功能不全的定时计数器,要占用T1部分功能。也不常用。

main(){
TMOD = 0X01; 
TH0 = (65535 - 50000) / 256; //计时50000us,50ms
TL0 = (65535 - 50000) % 256; 
EA = 1; //总中断
 ET0 = 1; //T0定计中断
 TR0 = 1; //开始工作
while(1);
}

2.2 SCON

数据传输:对于数据的发送,我们都是把数据转换成一串串的字节。比如发送“HelloWorld”,计算机会先对其采用ASCALL编码(不同的编码方式),比如变成:0xb3(H),0x23(e),0x43(e),0xf6(l),0x7d(l)...发送一个字节最后再组装。而衡量发送字节的快慢这是波特率。

波特率:单位时间内(一般1s)发送的二进制数据位数。例如9600b/s,每秒发9600位,算成字节就是9600/8=1200个字节。

SCON的产生和定计T1密切相关,有一个SCON对应的定计方式选择和初值选择表,可以直接参考。也可以使用波特率计算器,我放上来,不要积分。

(9条消息) 51波特率初值设定计算器-嵌入式文档类资源-CSDN文库

细节参考这个博主的文章。 

(9条消息) TMOD、SCON、PCON寄存器的配置_逸凌Time的博客-CSDN博客_scon寄存器

——1.每个串口的工作方式发送的帧不同,直接影响TB8和RB8。方式0:扩展并行IO口;方式1:双机通信;方式2:双机和多机通信;方式3:同上。

——2.实际使用起来,用定计T1方式2;且发送数据用查询方式,接收数据用中断方式。如下:

——3.五个中断源的自然优先级:外中断0、定时器0、外中断1、定时器1、串口,它们的自然优先级由高到低排列。

——4.发送接收标志:RI是接收标志,接收完成后置1,需要手动清零;TI是发送标志,发送完成后置1,手动清零。

接收数据用中断:

#include "reg52.h"			 //接收数据程序

sbit led=P0^0;   //将单片机的P0.0端口定义为led

void UsartInit()        //串口初始化函数,新手可以直接写在main函数里,熟悉再模块化
{
	SCON=0X50;			//设置为工作方式1
	TMOD=0X20;			//设置计数器工作方式2
	PCON=0X80;			//波特率加倍
	TH1=0XF4;				//计数器初始值设置,注意波特率是4800的
	TL1=0XF4;
	ES=1;						//打开接收中断
	EA=1;						//打开总中断
	TR1=1;					//打开计数器
}

void main()
{	
	UsartInit();  //	串口初始化
	while(1);		
}

void Usart() interrupt 4	//进入中断服务函数
{
	char receiveData;

	receiveData=SBUF;//出去接收到的数据,此时SBUF存放接收数据
	RI = 0;//清除接收中断标志位
	if(receiveData=='1') 
	{
		led=1;			 //接收1时,打开LED灯
	}
	if(receiveData=='0') 
	{
		led=0;			 //接收0时,关闭LED灯
	}
    SBUF=receiveData;
	while(!TI);			 //等待发送数据完成
	TI=0;						 //清除发送完成标志位
}

发送数据用查询方法(就是在main的while函数一直转): 

#include <reg52.h>
//发送数据查询方式 
void delay(unsigned char x)
{
  int i,j;
  for(i=0;i<x;i++)
    for(j=0;j<110;j++);
}
 
/*波特率为9600*/
void UART_init(void)
{
    SCON = 0x50;        //串口方式1
    TMOD = 0x20;        // 定时器使用方式2自动重载
    TH1 = 0xFD;    //9600波特率对应的预设数,定时器方式2下,TH1=TL1
    TL1 = 0xFD;
    TR1 = 1;//开启定时器,开始产生波特率
}
 
/*发送一个字符*/
void UART_send_byte(unsigned char dat)
{
	SBUF = dat;       //把数据放到SBUF中
	while (TI == 0);//未发送完毕就等待
	TI = 0;    //发送完毕后,要把TI重新置0
}
 
/*发送一个字符串*/
void UART_send_string(unsigned char *buf)
{
	while (*buf != '\0')
	{
		UART_send_byte(*buf++);
	}
}
 
main()
{
	UART_init();
	
	while (1)
	{
		UART_send_string(Buf);
		delay(20000);
	}
 
}

同时收发程序:

#include <reg52.h>
unsigned char UART_buff;
bit New_rec = 0; //receive newdata 1 yes 0 no
bit Send_ed = 1; //send is done 1 yes 0 no
 
void delay(unsigned char n)
{
  int i,j;
  for(i=0;i<n;i++)
    for(j=0;j<114;j++);
}
/*interruptong initialize*/
void Serial_Init()
{
	SCON = 0x50;
	TMOD = 0x20;//T1 way 2
	TH1 = 0xFD; //9600bps@11.0592MHz
	TL1 = 0xFD;
	TR1 = 1;
	ES= 1;   //serial.
	EA= 1;
}
/*send 1 bytedata*/
void UART_SendByte(unsigned char txd)
{
	SBUF=txd;
	while(!TI);
	TI=0;
}
/*send string*/
void UART_SendString(unsigned char *buf)
{
	while (*buf != '\0')
	{
		UART_SendByte(*buf++);
	}
	Send_ed=1;
}
/*receive newdata*/
void UART_Receive(unsigned char newdata)
{
		if(newdata=='1') 
		{
			led=1;			 //??1?,??LED?
		}
		if(newdata=='0') 
		{
			led=0;			 //??0?,??LED?
		}
}
void main(void)
{
	Serial_Init();
	Servo_Init();
	while(1)
	{
		delay(100);
		if ((New_rec == 1) && (Send_ed == 1)) //receive newdata and send is done 
		 {
			 UART_SendByte();
			 New_rec = 0;
			 Send_ed = 1;
		 }
	}
}
/***************serial interrupt*********************/
void UART_Serial(void) interrupt 4
{
	if(RI == 1)     
	{      
			UART_buff = SBUF; 
			New_rec = 1;
			RI = 0;
			UART_Receive(UART_buff);
	}
	delay(500);
}

2.3 中断的使用方法概括

void nameoffunction() interrupt n using m
{}

中断函数必须void;name就是中断函数的名字;interrupt 是关键字不能少,后面的n是中断号;using m是用几号工作寄存器。

中断号:n=0:INT0外部中断0控制号;n=1:定计T0中断控制口;n=2:外部中断INT1控制;n=3:T1定计控制口;n=4:串口中断控制口;【n=5:T2定计控制口,T2定时计数器其C51/52单片机都没有】

工作寄存器:将工作寄存器内容保存到堆栈中,中断函数返回前进行恢复。m=0~3,有四个,不能混用,可以省略不指定,程序会自动分配。


三、程序实例(以C51为例)

1.定时器/计数器案例

1.1对T1外部引脚连接脉冲计数,每一百个对P1.0取反

#include "reg52.h"			 

sbit P10=P1^0;  

void UsartInit()        //串口初始化函数,新手可以直接写在main函数里,熟悉再模块化
{
	TMOD=0X60;		
	TH1=(256-100)/256;				//计数器初始值设置
	TL1=(256-100)%256;
	ET1=1;						//打开T1中断
	EA=1;						//打开总中断
	IT1=1;					//边缘触发计数:碰到一个下降沿计数器累加1次,外部中断专用
    TR1=1;
}

void main()
{	
	UsartInit();  //	串口初始化
	while(1);		
}

void Usart() interrupt 3	//进入中断服务函数
{
	P10=!P10;
}

1.2用T0,从P10口,输出T=0.5ms的PWM波(小于65536us最大计数上限)

#include "reg52.h"			 

sbit P10=P1^0;  

void UsartInit()        //串口初始化函数,新手可以直接写在main函数里,熟悉再模块化
{
	TMOD=0X02;		
	TH1=(65535-500)/256;				//计数器初始值设置
	TL1=(65525-500)%256;
	ET0=1;						//打开T1中断
	EA=1;						//打开总中断
    TR0=1;
}

void main()
{	
	UsartInit();  //	串口初始化
	while(1);
    /*用查询的方法也可以
    for(;;)
    {
        if(TF0) //计数容器溢出否?
        {
             TF0=0;
             P10=!P10;    
        }
    }
    */		
}

void Usart() interrupt 3	//进入中断服务函数
{
	TH1=(65535-500)/256; //重载
	TL1=(65525-500)%256;
	P10=!P10;
}

1.3用T0,从P10口,输出T=1s,占空比20%的PWM波(大于65536us最大计数上限)

设计思想:用一个中断计时,用一个计数量i,到达设定的计时进行电平改变,同时对清零i重新开始计时。

#include "reg52.h"			 

sbit P10=P1^0;  

//设置50ms一个基本计时单位,i在第四次和第20次进行取反可得到占空比20%T=1s的PWM

void UsartInit()        //串口初始化函数,新手可以直接写在main函数里,熟悉再模块化
{
	TMOD=0X01;		
	TH0=(65536-50000)/256;				//计数器初始值设置
	TL0=(65536-50000)%256;
	ET0=1;					
	EA=1;						//打开总中断
    TR0=1;
}

void main()
{	
	UsartInit();  //	串口初始化
	while(1);		
}

void Usart() interrupt 1	//进入中断服务函数
{
	TH0=(65536-50000)/256;	//重载计数值
	TL0=(65536-50000)%256;
    if(i==4)
        {P11=0};
    if(i==20)
        {P11=1;i=0};
}

2运行过程中读取定/计方法:需要用到TMOD的GATE位

3.双机通信:甲乙两个通信,f=12MHz,波特率设置2400,SMOD0,甲乙TXDRXD交替,甲P3.3接按键(外部中断1),乙P1口接8个灯。要实现:按下按键,甲发送0x55给乙,不按发送0xff给乙,从而实现灯的开关。

//甲发送
sbit P33=P3^3;
void main(void)
{
SCON=0x50;
PCON=0x00;
TMOD=0x20;
TH1=...;
TL1=...; //设置波特率
TR1=1;
if(P33==0)
    {
        SBUF=0x55;
        while(TI==0);
        TI=0;    
    }
else
    {
        SBUF=0xff;
        while(TI==0);
        TI=0;
    }
}

//乙接收
void main(void)
{
SCON=0x50;
PCON=0x00;
TMOD=0x20;
TH1=...;
TL1=...;
TR1=1;
EA=1;
ES=1;
while(1);
}

void inter() interrupt 4
{
RI=0;
P1=SBUF;
}

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值