單片機實現寄存器點亮LED實驗


為了順利過渡到庫開發,在STM32編程的開始,我們對照51點亮一個LED的方法,給大家演示一下STM32如何用操作寄存器的方法點亮一個LED,然后再慢慢講解到底什么是庫,讓大家知道庫跟寄存器的關系。

1. 用51點亮一個LED

  在用STM32點亮一個LED之前,我們先來復習下用51如何點亮一個LED。

硬件上我們假設51單片機的P0口的第0位接了一個LED,負邏輯亮。如果我們要點亮這個LED,代碼上我們會這么寫:

P0 = 0XFE;//總線操作點亮 LED

這時候我們就把LED點亮了,如果要關掉LED ,則是:

P0 = 0XFF;//總線操作關閉 LED

這里面我們用的是總線操作的方法,即是對P0口的8個IO同時操作,但起作用的只是P0^0。

除了這種總線操作的方法,我們還學習過位操作,利用51編譯器的關鍵字sbit,我們可以定義一個位變量:

sbit LED = P0^0;

那么LED = 0,就點亮了LED;

LED = 1,就關閉了 LED。

為了讓程序看起來見名知義,我們定義兩個宏:

#define ON 0
#define OFF 1

點亮和關閉LED的代碼就變成了:

LED = ON;//位操作點亮LED
LED = OFF;//位操作關閉LED

稍微整理一下代碼,整體效果就是:

//假設51單片機的PO~0口接LED,負邏輯點亮
#define ON 0
#define OFF 1

sbit LED = P0^0;

void main(void)
{
  P0 = 0XFE;//總線操作點亮LED
  P0 = 0XEF;//總線操作關閉LED

  LED = ON;//位操作點亮LED
  LED = OFF;//位操作關閉LED
}

上面總線和位操作的的方法,學過51的朋友是非常熟悉的,也很容易理解。那么我們再說一下大家容易忽略的幾個知識點。

1. 什么是寄存器

在點亮 LED 的時候,我們都是用操作寄存器的方法來實現的,那大家是否想過,這個寄存器到底是什么?為什么我們可以直接操作P0口?

解答上面的問題之前,我們先簡單介紹下51單片機的主要組成部分,這對我們學習其他單片機也有好處。

我們以國內的STC89C51為例,該單片機主要由51內核、外設IP、和總線這三大部分組成。內核是由 Intel 公司生產的,外設 IP 就是 STC 公司在內核的基礎上添加的諸如定時器、串口、IO 口等這些東西,總線就是用來連接內核和外設的接口單元。Intel 在這里屬於IP核設計公司,STC 屬於 IC 設計公司。世界上能設計 IP 核的公司屈指可數。我們非常熟悉的ARM公司就屬於IP核設計公司,ARM 給其他公司授權,其他IC公司就在ARM內核上設計出各具特色的MCU,我們后面要學習的STM32就是屬於一中基於ARM內核的MCU。

寄存器則是內置於各個 IP 外設中,是一種用於配置外設功能的存儲器,就是一種內存,並且有想對應的地址。學過C語言我們就知道,要操作這些內存就可以使用C語言中的指針,通過尋址的方式來操作這些具有特殊功能的內存一寄存器。比如 P0 口對應的地址是0X80,那么我們要修改 0X80 這個地址對應的內存的內容的話,按照常理可以這樣操作:

*(*0X80) = 0XFE;//點亮LED

可當我們編譯的時候,編譯器會報錯,在51里面只能通過sfr和sbit這兩個關鍵字來實現寄存器映象,不能直接操作寄存器對應的地址,這是51相較於STM32不同的地方。

51單片機的這些寄存器位於地址80H~FFH中,對應着128個地址,但不是每個地址都是有效的,51系列的單片機有21個,52系列的則有26個,其他的都是保留區。

 

寄存器映射

2. 寄存器映射

  實際上我們在編程的時候並不是通過指針來操作寄存器的,而是直接給P0、P1這些端口寄存器賦值。那么這些外設資源是如何與地址建立一一對應的關系(寄存器映射定義),這得益與51特有的兩個關鍵字:sfr和sbit,其他單片機沒有,只能用其他的方式來實現寄存器映射。這兩個關鍵字幫我們實現了所有寄存器的定義,所以我們才可以像操作普通變量一樣來操作寄存器。其實我們一開始提到的點亮LED的代碼,全貌應該是這樣的:

 

sfr P0 = 0x80;//寄存器定義

P0 = 0XFE;//總線操作點亮LED

為了方便起見,我們可以把寄存器映射全部寫好封裝在一個頭文件里面,不用每用一個寄存器就定義一次。其實這方面的工作不用我們做,我們在編程的時候都會在開始的地方添加一個頭文件:

#include <reg51.h>

這個頭文件已經實現了全部寄存器的定義,該文件是keil自帶,在安裝目錄:
  Keil\C51\INC下可以找到。這個文件實現了字節寄存器和位寄存器的定義。

  1 /*-----------------------------------------------------------------
  2 
  3     REG51.H
  4     
  5     Header file for generic 80C51 and 80C31 microcontroller.
  6     
  7     Copyright(c)1988-2002 Keil Elektronik GmbH and Keil Software,Inc.
  8     
  9     All rights reserved.
 10 
 11 -------------------------------------------------------------------*/
 12 
 13 #ifndef __REG51_H_
 14 
 15 #define __REG51_H_
 16 
 17  /*BYTE Register */
 18 
 19 sfr P0 = 0x80 20 
 21 sfr P1 = 0×90 22 
 23 sfr P2 = 0xA0 24 
 25 sfr P3 = 0xB0 26 
 27 sfr PSW = 0xD0 28 
 29 sfr ACC = 0xE0 30 
 31 sfr B = 0XF0 32 
 33 sfr SP= 0×81 34 
 35 sfr DPL= 0×82 36 
 37 sfr DPH = 0×83 38 
 39 sfr PCON = 0×87 40 
 41 sfr TCON = 0x88 42 
 43 sfr TMOD = 0×89 44 
 45 sfr TL0 = 0×8A;
 46 
 47 sfr TL1 = 0×8B;
 48 
 49 sfr TH0 = 0×8C;
 50 
 51 sfr TH1 = 0×8D;
 52 
 53 sfr IE = 0xA8 54 
 55 sfr IP = 0×B8;
 56 
 57 sfr SCON = 0x98 58 
 59 sfr SBUF = 0x99 60 
 61 /*BIT Register */
 62 
 63 /*PSW*/
 64 
 65 sbit CY = 0xD7 66 
 67 sbit AC = 0xD6 68 
 69 sbit F0 =0xD5
 70 
 71 sbit RS1 = 0xD4 72 
 73 sbit RS0 = 0xD3 74 
 75 sbit OV = 0xD2 76 
 77 sbit P = 0xD0 78 
 79 
 80 
 81 /*TCON*/
 82 
 83 sbit TF1 = 0x8F;
 84 
 85 sbit TR1 = 0x8E;
 86 
 87 sbit TF0 = 0×8D;
 88 
 89 sbit TR0 = 0×8C;
 90 
 91 sbit IE1 = 0×8B;
 92 
 93 sbit IT1 = 0×8A;
 94 
 95 sbit IE0 = 0×89;
 96 
 97 sbit IT0 = 0×88;
 98 
 99 
100 
101 /*IE*/
102 
103 sbit EA = 0XAF;
104 
105 sbit ES = 0xAC;
106 
107 sbit ET1 = 0xAB;
108 
109 sbit EX1 = 0xAA;
110 
111 sbit ET0 = 0xA9;
112 
113 sbit EX0 = 0xA8;
114 
115 
116 
117 /*IP*/
118 
119 sbit PS = 0xBC;
120 
121 sbit PT1 =0xBB;
122 
123 sbit PX1 = 0xBA;
124 
125 sbit PT0 = 0xB9;
126 
127 sbit PX0 = 0xB8;
128 
129 
130 
131 /*P3*/
132 
133 sbit RD = 0xB7;
134 
135 sbit WR = 0xB6;
136 
137 sbit T1 = 0xB5;
138 
139 sbit T0 = 0xB4;
140 
141 sbit INT1 = 0xB3;
142 
143 sbit INT0 = 0xB2;
144 
145 sbit TXD = 0xB1;
146 
147 sbit RXD = 0xBO;
148 
149 
150 
151 /*SCON*/
152 
153 sbit SM0 = 0x9F;
154 
155 sbit SM1 = 0x9E;
156 
157 sbit SM2 = 0x9D;
158 
159 sbit REN = 0x9C;
160 
161 sbit TB8 = 0x9B;
162 
163 sbit RB8 = 0×9A;
164 
165 sbit TI = 0x99;
166 
167 sbit RI = 0x98;
168 
169 #endif

3. 啟動文件一STARTUP.A51

  還有一個就是啟動代碼,這個也是很多初學者容易忽略的地方,對於這部分我們主要總結下它的功能,不詳解講解里面的代碼。

單片機在上電復位后,首先執行的是啟動文件一STARTUP.A51,而不是我們通常看到的main函數。我們新建51工程的時候會有一個提示:是否拷貝啟動代碼到當前的工程,我們一般選擇是。

是否添加啟動代碼

啟動代碼用匯編語言編寫,主要實現了以下功能:

  清除內部數據存儲器、清除外部數據存儲器、清除外部頁儲存器、初始化small模式下的可重入棧和指針、初始化large模式下可重入棧和指針、初始化compact模式下的可重入棧和指針、初始化8051硬件棧指針、傳遞初始化全局變量的控制命令或者在沒有初始化全局變量時給main函數傳遞命令。然后程序就跳轉到main函數,來到我們熟知的C世界。

2. 用STM32點亮一個LED

1. 新建工程

  用KEIL5新建一個工程,把工程放在一個事先建好的文件夾內,工程命名為REG后保存。然后在工程目錄下添加啟動文件:startup_stm32fl0x_hd.s,該文件可以從KEIL5安裝目錄找到,也可以從ST庫里面找到,然后把啟動文件添加到工程里面。

2.啟動文件一startup_stm32f10x_hd.s

啟動文件由匯編語言編寫,具體功能跟51里面的啟動文件:STARTUP.A51差不多。

STM32的啟動文件主要實現了:

1、設置初始SP。

2、設置初始PC = Reset_Handler

3、設置向量表入口地址,並初始化向量表。

4、調用庫函數SystemInit,把系統時鍾配置成72M,SystemInit在庫文件system_stm32f10.c定義。

5、跳轉到標號_main,最終來到C的世界。

這里我們先去除繁枝細節,挑重點的講,主要理解第4和第5點,在啟動文件的147~155行,是復位處理函數,代碼如下:

 1 ;Reset handler
 2 Reset Handler PROC
 3 EXPORT Reset_Handler    [WEAK]
 4 IMPORT __main
 5 IMPORT SystemInit
 6 LDR R0, =SystemInit
 7 BLX R08LDRRO,=main
 8 LDR R0, =__main
 9 BX R0
10 ENDP

 

這里我們簡單介紹下這10行代碼。

第1行是程序注釋,在匯編里面注釋用的是“;”,跟C語言不一樣。

第2行是定義了一個子程序:Reset_Handler。PROC是子程序定義偽指令。一般用法為:

1 子程序名 PROC NEAR(或FAR)
2 
3 ......
4 
5 ret
6 
7 子程序名 ENDP

 

  其中NEAR和FAR是屬性詞。

  NEAR屬性(段內近調用):調用程序和子程序在同一代碼段中,只能被相同代碼段的其他程序調用。

  FAR屬性(段間遠調用):調用程序和子程序不在同一代碼段中,可以被相同或不同代碼段的程序調用。

第3行EXPORT表示Reset_Handler這個子程序可供其他模塊調用。關鍵字[WEAK]表示弱定義,如果編譯器發現在別處定義了同名的函數,則在鏈接時用別處的地址進行鏈接,如果其它地方沒有定義,編譯器也不報錯,以此處地址進行鏈接。

第4行和第5行IMPORT說明Systemlnit和__main這兩個標號在其他文件,在鏈接的時候需要到其他文件去尋找。

  Systemlnit在庫文件system_stm32f10x.c實現,用來初始化STM32的一系列時鍾,把系統時鍾設置為72MHZ。STM32的時鍾比51單片機復雜,需要經過一系列的配置才能達到穩定運行的狀態。

  __main其實不是我們定義的,當編譯器編譯時,只要遇到這個標號就會定義這個函數,該函數的主要功能是:負責初始化棧、堆,配置系統環境,並在最后跳轉到用戶自定義的main函數,從此來到C的世界。

第6行把Systemlnit的地址加載到寄存器R0。

第7行程序跳轉到R0中的地址執行程序,之后系統的時鍾就被設置成72MHZ。

第8行把__main的地址加載到寄存器R0。

第9行程序跳轉到R0中的地址執行程序,執行完畢之后就去到我們熟知的C世界。

第10行表示子程序的結束。

總結下就是,Reset_Handler這個函數執行了兩個函數調用,一個是Systemlnit,把系統時鍾設置成72M,另一個是__main,初始化好系統環境,最終調用C的main,從此去到C的世界。

__main函數由編譯器生成,負責初始化棧、堆等,並在最后跳轉到用戶自定義的main()函數,來到C的世界。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM