51單片機中斷
- 51單片機中斷原理
-
-
-
- 中斷的概念:
- 中斷作用
- 中斷源及相關寄存器
-
- 中斷源及優先級
- 定時器/計數器控制寄存器 TCON
- 中斷允許寄存器 IE
- 中斷優先寄存器 IP
- 工作方式寄存器TMOD
- 定時器初值寄存器THx 和 TLx
-
- 計數器初值的計算:
-
-
- 外部中斷
-
-
-
- 操作步驟(INT0 INT1)
- 如何配置外部中斷
- 程式示例
-
-
- 定時器/計數器中斷
-
-
-
- 需要了解的知識
- 工作原理
- 定時器結構
- 操作步驟(T0 T1)
- 如何配置定時器
- 程式示例
- 最終項目展示
-
-
51單片機中斷原理
中斷的概念:
CPU在處理某一事件A時,發生了另一事件B請求CPU迅速去處理(中斷發生),那麼CPU就會暫停目前的工作(A事件),去執行B事件(中斷響應和中斷服務),然後B事件做完之後,再回到原來的事件(A事件)中繼續工作。(中斷的傳回)。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPR9ENZpnTxEleOBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0gzNzUDMwQTM3ITMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
中斷作用
随着計算機技術的應用,人們發現中斷技術不僅解決了快速主機與I/O裝置的資料傳送問題,而且還有具有如下的優點:
1. 分時操作:CPU可以分時為多個I/O裝置服務,提高了計算機的使用率。
2. 實時操作:CPU能夠及時處理應用系統的随機事件,系統的實時性大大增強。
3. 可靠性高:CPU具有處理裝置故障及掉電等突發性事件能力,進而使系統可靠性更高。
中斷源及相關寄存器
中斷源及優先級
中斷源符号 | 名稱 | 中斷标志 | 中斷引起原因 | 中斷号 | 優先級 |
---|---|---|---|---|---|
/INT0 | 外部中斷0 | IE0 | 低電平或下降沿信号 | 最高 | |
T0 | 定時器中斷0 | TF0 | 定時/計數器0 計數回0溢出 | 1 | ↓ |
/INT1 | 外部中斷1 | IE1 | 定電平或下降沿信号 | 2 | ↓ |
T1 | 定時器中斷1 | TF1 | 定時/計數器1 計數回0溢出 | 3 | ↓ |
TX/RX | 串行口中斷 | TI/RI | 串行通信完成一幀資料發送或接收 | 4 | 最低 |
定時器/計數器控制寄存器 TCON
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
功能 | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
TF0、 TF1: 是定時器中斷标志(定時器0溢出标志位、定時器1溢出标志位)
TR0 、TR1: 打開相應的定時器(定時器0運作控制位,=1時啟動定時器0、定時器1運作控制位,=1時啟動定時器1)
由軟體清0關閉定時器0/1。當GATE=1,且INIT為高電平時,TR1置1啟動定時器1;當GATE=0時,TR1置1啟動定時器0/1。
IT0、IT1: 是外部中斷的觸發方式。 =0時 低電平觸發,=1時負跳變觸發。
IE0、IE1: 是外部中斷的标志位
中斷允許寄存器 IE
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
功能 | EA | —— | —— | ES | ET1 | EX1 | ET0 | EX0 |
EA: 總中斷允許。 EA=0;CPU屏蔽所有中斷的請求 EA=1;開放所有中斷。
ES:串行口中斷允許位。ES=0; 禁止串行中斷。ES=1; 允許序列槽中斷。
ET0、ET1: 定時器/計數器0 和 定時器/計數器 1 中斷允許位 =0時 禁止相應的定時器中斷。 =1 允許相應的定時器中斷。
EX0、EX1: 外部中斷0 和 外部中斷 1 中斷允許位。=0時 禁止相應的外部中斷。 =1時 允許相應的外部中斷。
——:無效位
中斷優先寄存器 IP
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
功能 | —— | —— | —— | PS | PT1 | PX1 | PT0 | PX0 |
PS: 串行口中斷優先級 PS = 1;(高) 。PS = 0; (低)。
PT0:定時器0中斷優先級 PT0 = 1;(高) PT0 = 0;(低)。
PT1:定時器1中斷優先級 PT1 = 1;(高) PT1 = 0;(低)。
PX0:外部中斷0中斷優先級 PX0 = 1;(高) PX0 = 0;(低)。
PX1:外部中斷1中斷優先級 PX1 = 1;(高) PX1 = 0;(低)。
——:無效位
IP寄存器不做設定,上電複位後為00H,預設是為低優先級。
不設定預設優先級是(由高到低):
外部中斷0→定時器0→外部中斷1→定時器1→序列槽
如果我們把IP寄存器設定為:(IP = 0X10)
PS = 1;
PT1 = 0;
PX1 = 0;
PT0 = 0;
PX0 = 0;
如下表:
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
功能 | —— | —— | —— | 1 |
那麼優先級從高到低是:
序列槽→外部中斷0→定時器0→外部中斷1→定時器1
工作方式寄存器TMOD
位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
功能 | GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
GATE:門控制
=0:僅有運作控制位TRx來控制定時/計數器的開啟。
=1:由TRx和外部中斷脈沖計數。(用于計算外部中斷 負跳變 的次數)
C/T:計數器模式和定時器模式選擇
=0:選擇定時器模式
=1:選擇計數器模式
M1、M0:選擇定時/計數器的工作方式
M1 | M0 | 工作方式 |
---|---|---|
方式0:為13位定時/計數器 | ||
1 | 方式1:為16位定時/計數器 | |
1 | 方式2:為8位初值自動重裝定時/計數器 | |
1 | 1 | 方式3:僅适用于T0,分成兩個8位計數器,T1停止計數。 |
方式0
方式0為13位計數,由TL0的低5位(高3位未用)和TH0的8位組成。TL0的低5位溢出時向TH0進位,TH0溢出時,置位TCON中的TF0标志,向CPU發出中斷請求。
方式1
方式1的計數位數是16位,由TL0作為低8位,TH0作為高8位,組成了16位加1計數器 。
方式2
方式2為自動重裝初值的8位計數方式。
方式3
方式3隻适用于定時/計數器T0,定時器T1處于方式3時相當于TR1=0,停止計數。
定時器初值寄存器THx 和 TLx
首先先了解一下CPU時序有關知識:
振蕩周期: 為單片機提供定時信号的振蕩源的周期(晶振周期或外加振蕩周期)
狀态周期:2個振蕩周期為1個狀态周期,用S表示。振蕩周期又稱S周期或時鐘周期。
機器周期:1個機器周期含6個狀态周期,12個振蕩周期。
指令周期:完成1條指令所占用的全部時間,它以機器周期為機關。
例如:外接晶振為12MHz時,51單片機相關周期的具體值為:
振蕩周期=1/12us;
狀态周期=1/6us;
機器周期=1us;
指令周期=1~4us;
計數器初值的計算:
機器周期就是CPU完成一個基本操作所需要得時間。
機器周期 = 1 /單片機的時鐘頻率
51單片機内部時鐘頻率是外部時鐘的12分頻,也就是當外部晶振的頻率輸入到單片機裡面
的時候要進行12分頻。
比如:你用的是12MHZ的晶振,當你使用12MHZ的外部晶振的時候,
機器周期 = 1 / 1M = 1us。(選擇定時器工作方式1 16位)
我們2的16次方等于65536,也就是最大值為65536(溢出)
如果定時1ms
初值就為:1ms / 1us = 1000。也就是要計數1000個數, 初值 = 65535-1000+1 = 64536,65536才會溢出。 是以初值即FC18H(十進制為64536)
如果定時50ms
50ms/1us=50000;
初值 = 65535-50000+1=15536;
定時為50ms 初值為15536 即3CB0(十六進制)
對于每個不同的方式計算初值數有公式去計算的,但是我不會哈哈,我是用一個軟體來計算的,軟體名字 mcuelf這樣也比較快
外部中斷
操作步驟(INT0 INT1)
1 設定 外部中斷中斷源号及觸發方式。設定IT0或IT1(TCON寄存器)
2 打開相應的外部中斷允許。設定EX0或EX1(IE寄存器)
3 打開總中斷。設定EA(IE寄存器)
如何配置外部中斷
這裡就用外部中斷0開示例吧
//配置外部中斷0
void initInterrupt0()
{
IT0 = 1; //觸發方式為負跳變觸發
EX0 = 1; //打開外部中斷0允許
EA = 1; //總中斷打開
}
中斷服務函數(發生中斷你想做什麼?)
無傳回值 函數名(随意起) interrupt 中斷号
void interrupt0ServiceFun() interrupt 0
{
//編寫你要做的事情
}
外部中斷1也是差不多的,這裡就不寫了。
程式示例
實作按鍵按下開燈/關燈
#include "reg52.h"
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long int u32;
sbit KEY = P3^2; //定義中斷按鍵引腳
sbit LED = P2^0; //定義LED1引腳
//配置中斷0
void initInterrupt0()
{
EX0 = 1;
IT0 = 1;
EA = 1;
}
//延時函數
void delay(u8 i)
{
while(i--);
}
void main()
{
initInterrupt0(); //調用中斷
while(1);
}
//發生中斷執行函數
void interruptHandler() interrupt 0
{
delay(12000); //延遲 因為當進入外部中斷函數的時候按鍵時已經按下了這裡是消抖作用
if(KEY == 0) //再次确認是否真的按鍵被按下
{
LED = ~LED; //開燈/關燈
}
}
定時器/計數器中斷
需要了解的知識
51單片機有兩組定時器/計數器,因為既可以定時,又可以計數,故稱之為定時器/計數器。
定時器/計數器和單片機的CPU是互相獨立的。定時器/計數器工作的過程是自動完成的,不需要CPU的參與。
51單片機中的定時器/計數器是根據機器内部的時鐘或者是外部的脈沖信号對寄存器中的資料加1。
有了定時器/計數器之後,可以增加單片機的效率,一些簡單的重複加1的工作可以交給定時器/計數器處理。CPU轉而處理一些複雜的事情。同時可以實作精确定時作用。
工作原理
實質上是加1計數器,随着輸入脈沖,計數器自動加1,
溢出的時候會回0.,且計數器的溢出使相應的中斷标志位 置1.
向CPU發出中斷請求。如果定時/計數器工作于定時模式,則表示定時時間已到;如果工作于計數模式,則表示計數值已滿。
可見,由溢出時計數器的值減去計數初值才是加1計數器的計數值。
定時器結構
定時/計數器的實質是加1計數器(16位),由高8位和低8位兩個寄存器THx和TLx組成。TMOD是定時/計數器的工作方式寄存器,确定工作方式和功能;TCON是控制寄存器,控制T0、T1的啟動和停止及設定溢出标志。
操作步驟(T0 T1)
1.選擇工作方式。設定M1、M0 (TMOD寄存器)
2.選擇控制方式。設定GATE(TMOD寄存器)
3.選擇定時器還是計數器模式。設定C/T(TMOD寄存器)
4.給定時/計數器賦初值。設定THx 和 TLx(定時器初值寄存器)
5.開啟總中斷。設定EA(IE寄存器)
6.打開相應定時器中斷允許。 設定ET0或ET1(IE寄存器)
7.啟動定時器。設定TR1或TR0(TCON寄存器)
如何配置定時器
這裡就選擇定時器0吧 選擇方式1(16位)進行定時示例吧
1.選擇工作方式1(16位)M1=0;M0=1;
2.控制方式 :僅有運作控制位TRx來控制定時/計數器的開啟。GATE=0;
3.選擇定時器模式 C/T=0;
4.賦初值 這裡選擇定時為50ms 我的闆子晶振是11.0592 具體怎麼算我不會,推薦跟我一樣不會的用軟體mcuelf計算出以下結果
TH0 = 0x4C;
TL0 = 0x00;
5.打開總中斷(總開關)
6.打開T0中斷開關
7.啟動定時器0
//配置定時器函數
void time0Config()
{
TMOD=0x01; //設定T0定時器,選擇工作方式1(16位),定時器模式 僅有運作控制位TRx來控制定時/計數器的開啟。
TH0 = 0x4C; //初值設定
TL0 = 0x00; //初值設定
EA=1; //打開總中斷開關
ET0=1; //打開T0中斷開關
TR0=1; //啟動定時器0
}
//發生中斷執行函數(這樣就是50ms)
void time0() interrupt 1 //T0中斷号為1
{
/*要注意這裡不會自動重裝,是以要再次設定回初值(除工作方式2)*/
TH0 = 0x4C; //初值重新設定
TL0 = 0x00; //初值重新設定
//編寫你要做的事
}
程式示例
這裡就寫一個用定時器來做一個簡單的時鐘顯示在LCD1602上
LCD1602我昨天釋出了一個LCD1602的使用和顯示hello word。這裡就是詳細講LCD1602具體的操作了。
引腳定義
//引腳定義
#define LCD P0
sbit E = P2^7; //使能
sbit RS = P2^6; //資料/指令(H/L)
sbit RW = P2^5; //讀寫(H/L)
lcd1602.h
void write_com(unsigned char command); //寫指令函數
void write_data(unsigned char dat); //寫資料函數
void init_lcd(); //初始化LCD1602函數
void delay5ms(); //延時5ms函數
lcd1602.c
#include <reg52.h>
#include "lcd1602.h"
#define LCD P0
sbit E = P2^7;
sbit RS = P2^6;
sbit RW = P2^5;
/******延遲5毫秒函數********/
void delay5ms() //誤差 -0.000000000001us
{
unsigned char a,b;
for(b=15;b>0;b--)
for(a=152;a>0;a--);
}
/******LCD1602寫指令函數********/
void write_com(unsigned char command)
{
RS = 0;
RW = 0; //高讀低寫
LCD = command;
delay5ms(); //這裡延時最低要30納秒 我們直接給5ms
E = 1; //使能拉高
delay5ms(); //最低要求延遲150納秒 我們直接給5ms
E = 0;
}
/******LCD1602寫資料函數********/
void write_data(unsigned char dat)
{
RS = 1;
RW = 0;
LCD = dat;
delay5ms(); //這裡延時最低要30納秒 我們直接給5ms
E = 1; //使能拉高
delay5ms(); //最低要求延遲150納秒 我們直接給5ms
E = 0;
}
/******初始化LCD1602********/
void init_lcd()
{
write_com(0x06); //寫入資料後光标自動右移 整屏不移動。
write_com(0x0c); //開顯示功能 無光标 不閃爍
write_com(0x38); //資料總線8位 16X2顯示 5*7點陣
write_com(0x01); //清屏 0000 0001
}
main.c
#include <reg52.h>
#include "lcd1602.h"
unsigned char t = 0; //用來計數時間
unsigned char i=0;
unsigned char hours = 23; //小時
unsigned char minutes = 59; //分鐘
unsigned char seconds = 0; //秒
unsigned char date[16] = {"2021-01-27 WED"};
unsigned char time[5] = {"time:"};
//配置定時器函數
void time0Config()
{
TMOD=0x01; //設定T0定時器,選擇工作方式1(16位),定時器模式 僅有運作控制位TRx來控制定時/計數器的開啟。
TH0 = 0x4C; //初值設定
TL0 = 0x00; //初值設定
EA=1; //打開總中斷開關
ET0=1; //打開T0中斷開關
TR0=1; //啟動定時器0
}
void main()
{
init_lcd(); //1.初始化lcd1602
write_com(0x80); //設定顯示日期位置 (第一行第一個開始)
for(i=0;i<16;i++)
{
write_data(date[i]);
}
write_com(0xc0); //設定顯示time:位置(第二行第一個開始)
for(i=0;i<5;i++)
{
write_data(time[i]);
}
time0Config();//調用定時器中斷
while(1)
{
write_com(0xc6); //設定顯示小時的十位 位置
write_data(hours/10+'0'); //小時十位
write_com(0xc7); //設定顯示小時的個位 位置
write_data(hours%10+'0'); //小時個位
write_com(0xc8); //設定顯示: 位置
write_data(':'); //顯示 :
write_com(0xc9); //設定顯示分鐘的十位 位置
write_data(minutes/10+'0'); //分鐘十位
write_com(0xca); //設定顯示分鐘的個位 位置
write_data(minutes%10+'0'); //分值個位
write_com(0xcb); //設定顯示: 位置
write_data(':'); //顯示 :
write_com(0xcc); //設定顯示秒的十位 位置
write_data(seconds/10+'0'); //秒十位
write_com(0xcd); //設定顯示秒的 個位 位置
write_data(seconds%10+'0'); //秒個位
}
}
//發生中斷執行函數(一次就是50ms)
void time0() interrupt 1 //T0中斷号為1
{
/*要注意這裡不會自動重裝,是以要再次設定回初值(除工作方式2)*/
TH0 = 0x4C; //初值重新設定
TL0 = 0x00; //初值重新設定
t++;
if(t == 20) //證明夠1秒了 20 X 50ms = 1000ms = 1s
{
seconds++; //秒+1
t=0; //夠了1秒設定為0 重新計數
}
if(seconds == 60) //如果秒到60
{
minutes++; //分鐘+1
seconds = 0; //秒回0
}
if(minutes == 60) //如果分鐘到60
{
hours++; //小時+1
minutes = 0; //分鐘回0
}
if(hours == 24 ) //如果小時到24
{
hours = 0; //小時回0
}
}