一、中断
首先要明白什么是中断程序。
书上说:中断程序是一种特殊的程序运行方式,中断向量是这个中断程序运行的入口,一个中断程序包括中断请求,中断响应,中断关闭三步走。定义很严谨,用我的理解来讲中断其实可以类比我们日常生活中的“随时响应”
比如说:目前正在努力学习(主函数执行其任务),突然肚子咕咕叫起来(中断请求),肚子叫是随时都有可能发生的,也有可能是每次饭后隔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;
}