【85.1 寄存器配置的本質。】
單片機內部可供我們選擇的資源非常豐富,有定時器,有串口,有外部中斷,等等。這些豐富的資源,就像你進入一家超市,你只需選擇你所需要的東西就可以了,所以配置寄存器的關鍵在于選擇,所謂選擇就是往寄存器里面做填空題,單片機系統內部再根據你的“選擇清單”,去啟動對應的資源。那么我們怎么知道某個型號的單片機內部有哪些資源呢?看該型號“單片機的說明書”呀,“單片機的說明書”就是我們通常所說的“芯片的datasheet”,或者說是“芯片的數據手冊”,這些資料單片機廠家會提供的。
跟單片機打交道,其實跟人打交道沒什么區別,你要讓單片機按照你的“意愿”走,你首先要把你的“意愿”表達清楚,這個“意愿”就是信息,信息要具備準確性和唯一性,不能模凌兩可。比如,現在要讓單片機“每1ms產生一次中斷”,你想想你可能需要給單片機提供哪些信息?
(1)51單片機有2個定時器,一個是0號定時器,一個是1號定時器,我們要面臨“二選一”的選擇,本例子中用的是“0號定時器”。
(2)0號定時器內部又有4種工作方式:方式0,方式1,方式2,方式3,本例子中用的是“方式1”。
(3)定時器到底多長時間中斷一次,這就涉及到填充與中斷時間有關的寄存器的數值,該數值是跟時間成比例關系,本例子中配置的是1ms中斷,就要填充對應的數值。
(4)默認狀態下,定時器是不會被開啟的,如果要開啟,這里就是涉及到定時器的“開關”,本例子要開啟此開關。
(5)定時器時間到了就要產生中斷,中斷也有“總開關”和“定時器的局部開關”,這兩個開關都必須同時打開,中斷才會有效。
要配置定時器“每1ms產生一次中斷”,大概就上述這些信息,根據這些信息提示,下面開始講解一下寄存器的具體內容。
【85.2 定時器/計數器的模式控制寄存器TMOD。】
寄存器TMOD是一個8位的特殊變量,里面每一位都代表了不同的功能選擇。根據芯片的說明書,TMOD的8位從左到右依次對應從D7到D0(左高位,右低位),定義如下:
GATE C/T M1 M0 GATE C/T M1 M0
仔細觀察,發現左4位與右4位是對稱的,分別都是“GATE,C/T , M1 , M0”,左4位控制的是“定時器1”,右4位控制的是“定時器0”,因為本例子用的是“定時器0”,因此“定時器1”的左4位都設置為0的默認數值,我們只需重點關注右4位的“定時器0”即可。
GATE:定時器是否受“其它外部開關”的影響的標志位。定時器的開啟或者停止,受到兩個開關的影響,第一個開關是“自身原配開關”,第二個開關是“其它外部開關”。GATE取1代表定時器受“其它外部開關”的影響,取0代表定時器不受“其它外部開關”的影響。本例子中,定時器只受到“自身原配開關”的影響,而不受到“其它外部開關”的影響,因此,GATE取0。
C/T:定時器有兩種模式,該位取1代表“計數器模式”,取0代表“定時器模式”。本例子是“定時器模式”,因此,C/T取0。
M1與M0:工作方式的選擇。M1與M0這兩位的01搭配,可以有4種組合(00,01,10,11),每一種組合就代表一種工作方式。本例子選用“方式1”,因此M1與M0取“01”的組合。
綜上所述,TMOD的配置代碼是:TMOD=0x01;
【85.3 決定時間長度的寄存器TH0與TL0。】
TH0與TL0,T代表定時器英文單詞TIME的T,H代表高位,L代表低位,0代表定時器0。
TH0是一個8位寬度的寄存器,TL0也是一個8位寬度的寄存器,兩者合并起來成為一個整體,實際上就是一個16位寬度的寄存器,TH0是高8位,TL0是低8位,它們合并后的數值范圍是:0到65535。該16位寄存器取值越大,定時中斷一次的時間反倒越小,為什么?TH0與TL0的初始值,就像一個水桶里裝的水。如果這個桶是空桶(取值為0),“雨水”想把這個桶“滴滿溢出”所需要的時間就很大。如果里面已經裝了大半的水(取值為大于32767),“雨水”想把這個桶“滴滿溢出”所需要的時間就很小。這里的關鍵詞“滴滿溢出”的“滴”與“滿溢出”,“滴”的速度是由單片機晶振決定的,而“滿溢出”一次就代表產生一次中斷,執行完中斷函數在即將返回主函數之前,我們重新裝入特定容量的水(重裝初值),為下一次的“滴滿溢出”做準備,依次循環,從而連續不斷地產生間歇的定時中斷。
配置中斷時間的大小是需要經驗的,因為,每次定時中斷的時間太長,就意味著時間的可分度太粗,而如果每次定時中斷的時間太短,則會產生很頻繁的中斷,勢必會影響主函數main()的執行效率,而且累記中斷次數的時間誤差也會增大。因此,配置中斷時間是需要經驗的,根據經驗,定時中斷取1ms一次,是幾乎所有單片機項目的最佳選擇,按我的理解,“1ms定時中斷一次”已經是單片機界公認的一種“標配”。
要配置1ms定時中斷,TH0與TL0如何取值?剛才提到一個形象的例子“桶,滴,滿溢出”。TH0與TL0的最大取值范圍是65535,可以理解成為最大65535“滴”,如果超過65535“滴”(比如加1“滴”后變成65536“滴”)就會“滿溢出”,從而產生一次中斷(65536是中斷發生的臨界值)。而“滴一次的時間”就剛好是單片機執行“一次單指令的時間”,“一次單指令的時間”等于12個晶振周期,比如12MHz的晶振,晶振周期是(1/12000000)秒,而“一次單指令的時間”就等于12乘以(1/12000000)秒,等于0.000001秒,也就是1us。1us“滴”一次,要產生1ms的時間就需要“滴”1000次。“滿溢出”的前提條件是“桶里”一共需要裝入65536滴才溢出,因此,在12MHz的晶振下要產生1ms的定時中斷,TH0與TL0的初值應該是64536(65536減去1000等于64536),而64536變成十六進制0xfc17,再分解到高8位TH0為0xfc,低8位TL0為0x17。
剛才的例子是假如晶振在12MHz的情況下所計算出來的結果,而本教程所用的晶振是11.0592MHz,根據11.0592MHz產生1ms的定時中斷,TH0與TL0應該取值多少?根據剛才的計算方式:
初值=\[溢出值\]-(\[0.001秒\]/(\[晶振周期的12個\]\*(\[1秒\]/\[晶振頻率\])))
初值=65536-(0.001/(12\*(1/11059200)))
初值=65536-922 (注:922是921.6的四舍五入)
初值=64614
初值=0xfc66
初值TH0=0xfc
初值TL0=0x66
【85.4 中斷的總開關EA與局部開關ET0。】
EA:中斷的總開關。寬度是1位的位變量。此開關如果取0,就會強行屏蔽所有的中斷,因此,只要用到中斷,此開關必須取1。
ET0:專門針對定時器0中斷的局部開關。寬度是1位的位變量。此開關如果取0,則會屏蔽定時器0的中斷,如果取1則允許定時器0中斷。如果要定時器0能產生中斷,那么總開關EA與ET0必須同時都打開(都取1),兩者缺一不可。
【85.5 定時器0的“自身原配開關”TR0。】
TR0:定時器的“自身原配開關”。寬度是1位的位變量。很多初學者會把EA,ET0,TR0三者搞不清。定時器可以工作在“查詢標志位”和“中斷”這兩種狀態,也就是說在沒有中斷的情況下定時器也可以單獨使用的。TR0是定時器0自身的發動引擎,要不要把這個發動引擎所產生的能量傳輸到中斷的渠道,則取決于中斷開關EA和ET0。TR0是源頭開關,EA是中斷總渠道開關,ET0是中斷分支渠道的定時器0開關。TR0取1表示啟動定時器0,取0表示關閉定時器0。
【85.6 定時器0的中斷函數的書寫格式。】
void 函數名() interrupt 1
{
...中斷程序內容;
...此處省去若干代碼
...中斷程序內容;
...最后面的代碼,要記得重裝TH0與TL0的初值;
}
函數名可以隨便取,只要不是編譯器已經征用的關鍵字。這里的1是定時器0的中斷號。不同的中斷號代表不同類型的中斷,至于哪類中斷對應哪個中斷號,大家可以查找相關書籍和資料。本節用的定時器0處于工作方式1的情況下,在即將退出中斷之前,需要重裝TH0與TL0的初始值。
【85.7 寄存器的名字來源。】
前面講的寄存器都有固定的名字,而且這些名字都是唯一的,拼寫的時候少一個字母或者多一個字母,C編譯器都會報錯不讓你通過,因此問題來了,初學者剛接觸一款單片機的時候,如何知道某個寄存器它特定的唯一的名字?有兩個來源。
第一個來源,可以打開C編譯器的某個頭文件(.h格式)查看這些寄存器的名字。比如51單片機可以查看REG52.H這個頭文件。如何打開REG52.H這個文件?在Keil源代碼編輯器界面下,選中上面REG52.H這幾個字符,在右鍵彈出的菜單下點擊Open ducument“REG52.H”即可。
第二個來源是直接參考一些現成的范例程序,這些范例程序網上很多,有的是原廠提供的,有的是熱心網友的分享,有的是技術書籍或者學習板開發板廠家提供的。
【85.8 如何快速配置寄存器。】
建議一邊閱讀芯片的數據手冊,一邊參考一些現成的范例程序,這些范例程序網上很多,有的是原廠提供的,有的是熱心網友的分享,有的是技術書籍或者學習板開發板廠家提供的。
【85.9 練習例程。】
現在編寫一個定時中斷程序,讓兩個LED燈閃爍,一個是在主函數里用累計主循環次數的方式實現(P0.0控制),另一個是在定時中斷函數里用累計定時中斷次數的方式實現(P0.1控制)。這兩個閃爍的LED燈,一個在main函數,一個是在中斷函數,兩路任務互不干涉獨立運行,并行處理的“雛形”略顯出來。

圖85.9.1 灌入式驅動8個LED
\#include "REG52.H"
\#define CYCLE\_SUM 5000 //主循環的次數
\#define INTERRUPT\_SUM 500 //中斷的次數
sbit P0\_0=P0^0; //在主循環里的LED燈
sbit P0\_1=P0^1; //在定時中斷里的LED燈
unsigned char Gu8CycleStep=0;
unsigned long Gu32CycleCnt=0; //累計主循環的計數器
unsigned char Gu8InterruptStep=0;
unsigned long Gu32InterruptCnt=0; //累計定時中斷次數的計數器
void main()
{
TMOD=0x01; //設置定時器0為工作方式1
TH0=0xfc; //產生1ms中斷的TH0初始值
TL0=0x66; //產生1ms中斷的TL0初始值
EA=1; //開總中斷
ET0=1; //允許定時0的中斷
TR0=1; //啟動定時0的中斷
while(1) //主循環
{
switch(Gu8CycleStep)
{
case 0:
Gu32CycleCnt++;
if(Gu32CycleCnt>=CYCLE\_SUM)
{
Gu32CycleCnt=0;
P0\_0=0; //主循環的LED燈亮。
Gu8CycleStep=1;
}
break;
case 1:
Gu32CycleCnt++;
if(Gu32CycleCnt>=CYCLE\_SUM)
{
Gu32CycleCnt=0;
P0\_0=1; //主循環的LED燈滅。
Gu8CycleStep=0;
}
break;
}
}
}
void T0\_time() interrupt 1 //定時器0的中斷函數,每1ms單片機自動執行一次此函數
{
switch(Gu8InterruptStep)
{
case 0:
Gu32InterruptCnt++; //累計中斷次數的次數
if(Gu32InterruptCnt>=INTERRUPT\_SUM) //次數達到設定值就跳到下一步驟
{
Gu32InterruptCnt=0; //及時清零計數器,為下一步驟的新一輪計數準備
P0\_1=0; //定時中斷的LED燈亮。
Gu8InterruptStep=1; //跳到下一步驟
}
break;
case 1:
Gu32InterruptCnt++; //累計中斷次數的次數
if(Gu32InterruptCnt>=INTERRUPT\_SUM) //次數達到設定值就返回上一步驟
{
Gu32InterruptCnt=0; //及時清零計數器,為返回上一步驟的新一輪計數準備
P0\_1=1; //定時中斷的LED燈滅。
Gu8InterruptStep=0; //返回到上一個步驟
}
break;
}
TH0=0xfc; //重裝初值,不能忘。
TL0=0x66; //重裝初值,不能忘。
}
- 首頁
- 第一節:我的價值觀
- 第二節:初學者的疑惑
- 第三節:單片機最重要的一個特性
- 第四節:平臺軟件和編譯器軟件的簡介
- 第五節:用Keil2軟件關閉,新建,打開一個工程的操作流程
- 第六節:把.c源代碼編譯成.hex機器碼的操作流程
- 第七節:本節預留
- 第八節:把.hex機器碼程序燒錄到單片機的操作流程
- 第九節:本節預留
- 第十節:程序從哪里開始,要到哪里去?
- 第十一節:一個在單片機上練習C語言的模板程序
- 第十二節:變量的定義和賦值
- 【TODO】第十三節:賦值語句的覆蓋性
- 【TODO】第十四節:二進制與字節單位,以及常用三種變量的取值范圍
- 【TODO】第十五節:二進制與十六進制
- 【TODO】第十六節:十進制與十六進制
- 【TODO】第十七節:加法運算的5種常用組合
- 【TODO】第十八節:連加、自加、自加簡寫、自加1
- 【TODO】第十九節:加法運算的溢出
- 【TODO】第二十節:隱藏中間變量為何物?
- 【TODO】第二十一節:減法運算的5種常用組合。
- 【TODO】第二十二節:連減、自減、自減簡寫、自減1
- 【TODO】第二十三節:減法溢出與假想借位
- 【TODO】第二十四節:借用unsigned long類型的中間變量可以減少溢出現象
- 【TODO】第二十五節:乘法運算中的5種常用組合
- 【TODO】第二十六節:連乘、自乘、自乘簡寫,溢出
- 【TODO】第二十七節:整除求商
- 【TODO】第二十八節:整除求余
- 【TODO】第二十九節:“先余后商”和“先商后余”提取數據某位,哪家強?
- 【TODO】第三十節:邏輯運算符的“與”運算
- 【TODO】第三十一節:邏輯運算符的“或”運算
- 【TODO】第三十二節:邏輯運算符的“異或”運算
- 【TODO】第三十三節:邏輯運算符的“按位取反”和“非”運算
- 【TODO】第三十四節:移位運算的左移
- 【TODO】第三十五節:移位運算的右移
- 【TODO】第三十六節:括號的強制功能---改變運算優先級
- 【TODO】第三十七節:單字節變量賦值給多字節變量的疑惑
- 【TODO】第三十八節:第二種解決“運算過程中意外溢出”的便捷方法
- 【TODO】第三十九節:if判斷語句以及常量變量的真假判斷
- 【TODO】第四十節:關系符的等于“==”和不等于“!=”
- 【TODO】第四十一節:關系符的大于“>”和大于等于“>=”
- 【TODO】第四十二節:關系符的小于“<”和小于等于“<=”
- 【TODO】第四十三節:關系符中的關系符:與“&&”,或“||”
- 【TODO】第四十四節:小括號改變判斷優先級
- 【TODO】第四十五節: 組合判斷if...else if...else
- 【TODO】第四十六節: 一維數組
- 【TODO】第四十七節: 二維數組
- 【TODO】第四十八節: while循環語句
- 【TODO】第四十九節: 循環語句do while和for
- 【TODO】第五十節: 循環體內的continue和break語句
- 【TODO】第五十一節: for和while的循環嵌套
- 【TODO】第五十二節: 支撐程序框架的switch語句
- 【TODO】第五十三節: 使用函數的三要素和執行順序
- 【TODO】第五十四節: 從全局變量和局部變量中感悟“棧”為何物
- 【TODO】第五十五節: 函數的作用和四種常見書寫類型
- 【TODO】第五十六節: return在函數中的作用以及四個容易被忽略的功能
- 【TODO】第五十七節: static的重要作用
- 【TODO】第五十八節: const(./book/或code)在定義數據時的作用
- 【TODO】第五十九節: 全局“一鍵替換”功能的#define
- 【TODO】第六十節: 指針在變量(./book/或常量)中的基礎知識
- 【TODO】第六十一節: 指針的中轉站作用,地址自加法,地址偏移法
- 【TODO】第六十二節: 指針,大小端,化整為零,化零為整
- 【TODO】第六十三節: 指針“化整為零”和“化零為整”的“靈活”應用
- 【TODO】第六十四節: 指針讓函數具備了多個相當于return的輸出口
- 【TODO】第六十五節: 指針作為數組在函數中的入口作用
- 【TODO】第六十六節: 指針作為數組在函數中的出口作用
- 【TODO】第六十七節: 指針作為數組在函數中既“入口”又“出口”的作用
- 【TODO】第六十八節: 為函數接口指針“定向”的const關鍵詞
- 【TODO】第六十九節: 宏函數sizeof(./book/)
- 【TODO】第七十節: “萬能數組”的結構體
- 【TODO】第七十一節: 結構體的內存和賦值
- 【TODO】第七十二節: 結構體的指針
- 【TODO】第七十三節: 結構體數據的傳輸存儲和還原
- 【TODO】第七十四節: 結構體指針在函數接口處的頻繁應用
- 【TODO】第七十五節: 指針的名義(例:一維指針操作二維數組)
- 【TODO】第七十六節: 二維數組的指針
- 【TODO】第七十七節: 指針唯一的“單向輸出”通道return
- 【TODO】第七十八節: typedef和#define和enum
- 【TODO】第七十九節: 各種變量常量的命名規范
- 【TODO】第八十節: 單片機IO口驅動LED
- 【TODO】第八十一節: 時間和速度的起源(指令周期和晶振頻率)
- 【TODO】第八十二節: Delay“阻塞”延時控制LED閃爍
- 【TODO】第八十三節: 累計主循環的“非阻塞”延時控制LED閃爍
- 【TODO】第八十四節: 中斷與中斷函數
- 【TODO】第八十五節: 定時中斷的寄存器配置
- 【TODO】第八十六節: 定時中斷的“非阻塞”延時控制LED閃爍
- 【TODO】第八十七節: 一個定時中斷產生N個軟件定時器
- 【TODO】第八十八節: 兩大核心框架理論(四區一線,switch外加定時中斷)
- 【TODO】第八十九節: 跑馬燈的三種境界
- 【TODO】第九十節: 多任務并行處理兩路跑馬燈
- 【TODO】第九十一節: 蜂鳴器的“非阻塞”驅動
- 【TODO】第九十二節: 獨立按鍵的四大要素(自鎖,消抖,非阻塞,清零式濾波)
- 【TODO】第九十三節: 獨立按鍵鼠標式的單擊與雙擊
- 【TODO】第九十四節: 兩個獨立按鍵構成的組合按鍵
- 【TODO】第九十五節: 兩個獨立按鍵的“電腦鍵盤式”組合按鍵
- 【TODO】第九十六節: 獨立按鍵“一鍵兩用”的短按與長按
- 【TODO】第九十七節: 獨立按鍵按住不松手的連續均勻觸發
- 【TODO】第九十八節: 獨立按鍵按住不松手的“先加速后勻速”的觸發
- 【TODO】第九十九節: “行列掃描式”矩陣按鍵的單個觸發(原始版)
- 【TODO】第一百節: “行列掃描式”矩陣按鍵的單個觸發(優化版)
- 【TODO】第一百零一節: 矩陣按鍵鼠標式的單擊與雙擊
- 【TODO】第一百零二節: 兩個“任意行輸入”矩陣按鍵的“有序”組合觸發
- 【TODO】第一百零三節: 兩個“任意行輸入”矩陣按鍵的“無序”組合觸發
- 【TODO】第一百零四節: 矩陣按鍵“一鍵兩用”的短按與長按
- 【TODO】第一百零五節: 矩陣按鍵按住不松手的連續均勻觸發
- 【TODO】第一百零六節: 矩陣按鍵按住不松手的“先加速后勻速”觸發
- 【TODO】第一百零七節: 開關感應器的識別與軟件濾波
- 【TODO】第一百零八節: 按鍵控制跑馬燈的啟動和暫停和停止
- 【TODO】第一百零九節: 按鍵控制跑馬燈的方向
- 【TODO】第一百一十節: 按鍵控制跑馬燈的速度
- 第一百一十一節: 工業自動化設備的開關信號的運動控制
- 【TODO】第一百一十二節: 數碼管顯示的基礎知識
- 【TODO】第一百一十三節: 動態掃描的數碼管顯示數字
- 【TODO】第一百一十四節: 動態掃描的數碼管顯示小數點
- 【TODO】第一百一十五節: 按鍵控制數碼管的秒表
- 【TODO】第一百一十六節: 按鍵控制數碼管的倒計時
- 【TODO】第一百一十七節: 按鍵切換數碼管窗口來設置參數
- 【TODO】第一百一十八節: 按鍵讓某位數碼管閃爍跳動來設置參數
- 【TODO】第一百一十九節: 一個完整的人機界面的程序框架的脈絡
- 【TODO】第一百二十節: 按鍵切換窗口切換局部來設置參數
- 【TODO】第一百二十一節: 可調參數的數碼管倒計時
- 【TODO】第一百二十二節: 利用定時中斷做的“時分秒”數顯時鐘
- 【TODO】第一百二十三節: 一種能省去一個lock自鎖變量的按鍵驅動程序
- 【TODO】第一百二十四節: 數顯儀表盤顯示“速度、方向、計數器”的跑馬燈
- 【TODO】第一百二十五節: “雙線”的肢體接觸通信
- 【TODO】第一百二十六節: “單線”的肢體接觸通信
- 【TODO】第一百二十七節: 單片機串口接收數據的機制
- 【TODO】第一百二十八節: 接收“固定協議”的串口程序框架
- 【TODO】第一百二十九節: 接收帶“動態密匙”與“累加和”校驗數據的串口程序框架
- 【TODO】第一百三十節: 接收帶“動態密匙”與“異或”校驗數據的串口程序框架
- 【TODO】第一百三十一節: 靈活切換各種不同大小“接收內存”的串口程序框架
- 【TODO】第一百三十二節:“轉發、透傳、多種協議并存”的雙緩存串口程序框架
- 【TODO】第一百三十三節:常用的三種串口發送函數
- 【TODO】第一百三十四節:“應用層半雙工”雙機串口通訊的程序框架