說到MCU的復位肯定是不陌生了,但究竟其怎么工作的,設計其目的和作用是什么呢?其實我們程序最初的加載就與復位有關,比如一上電,MCU就自動執行我們設計的程序,復位有很多種,比如異常復位(程序跑飛阿,電源不穩定阿,看門狗喂狗超時阿),但不管哪種復位,其做的大多工作基本類似,大多包括以下幾點:1.將所有寄存器恢復成默認值 -> 2.確認MCU工作模式 ->3. 關全局中斷 ->4. 關閉外設 ->5. 將IO置為高阻輸入狀態 ->6. 等待時鍾震盪趨於穩定 ->7. 從固定地址取復位向量的第一條指令開始執行。這前面幾步大致就是使整個CPU恢復成出廠設置把(暫時簡單可以這么理解)。最后一步可關鍵了,它是加載我們用戶程序的關鍵一步呢,前面講到了中斷向量表,里面第二個表項對應的就是復位向量,既然復位時執行的是我們用戶程序,很容易就想到這里,應該存的是我們用戶程序的main函數的第一條指令的地址。事實上,它沒這么干,它指向的是由開發工具提供的幫助生成的startup.s匯編啟動代碼(這部分啟動代碼有些開發工具會幫助生成,例如Keil,但有些開發工具不會幫助生成,這時需要確保工程里有這個啟動代碼存在)的第一條指令,在這個啟動代碼的最后寫了一個jump到main函數,這里多提一下,為何需要啟動代碼以及為何啟動代碼又用匯編來寫呢,網上給的說法是由於CPU上電時候需要准備好硬件執行環境和軟件環境,硬件環境就是中斷,寄存器,堆棧什么的,甚至有些包括用到的外部RAM還是內部RAM也還沒確定,軟件環境就是C語言的執行環境,所以需要用啟動代碼(Bootloader)來完成初始化,而且必須用匯編來寫,C程序的環境那時還沒准備好,堆棧指針和PC指針還沒有找到正確值,啟動代碼相當於給后面C程序的執行鋪平了道路。再回來看一下前面說的復位,,復位用到了中斷向量表項的復位向量,在Cortex內核的48個中斷向量表里,前16個都是異常向量都與復位有關(例如看門狗阿,電源阿等等)。剛說到了復位向量(即中斷向量表的第二個),復位時候,由電路將該表項里為2的對應的指令地址值加載到PC寄存器里,這樣程序就可以開始跑了,但還沒完,要知道程序執行還需用到堆棧的,那堆棧寄存器SP里的初始值怎么知道呢,這也是在復位時就確定下的,一復位,SP和PC里就有東西了,一個確定主程序在哪一個,一個確定可以往哪里堆。
都知道了MCU里的時鍾就像心臟,不過這個心臟頻率就值得探討下,MCU由於考慮到CPU的不同外設和用戶需要,就將時鍾設計成了時鍾樹,這很有意思,可總的來源只能有一個(要么片內,要么片外,片內的穩定程度不夠,如果可靠的話還是片外ok,這樣確定好了來源,具體分配下來就要看各自需要了,就好像樹枝一樣,不斷發叉,就開出不同的大小花(不同頻率))
前面講的這些還都是CPU里面的東西,真正開始MCU的,還得是存儲器的集成,CPU里面也是有存儲的東西的,寄存器就是的,每個寄存器都需要特定指令操作,我們寫的程序無非也就是往不同寄存器里寫東西來操作CPU幫我們實現想要的功能,但當程序一旦復雜,比如涉及到運行的中間數據存儲或程序執行時程序放在哪呢,這就需要更大容量存儲東西,這時RAM和ROM(FLASH)就出現了,RAM用來放堆棧和運行的程序和數據,ROM(FLASH)用來存放用戶已編譯的code,但是怎樣CPU才能完成對存儲器的訪問的,前面也差不多了解了大小存儲器就好像一段段小格子一樣,不過每個都是編了號的,從CPU角度向他看,那就是通過地址的形式去訪問,存儲器上也規定哪段地址用來放向量表,哪段用來作實際的RAM.
再將整個CPU,存儲器,時鍾向外延申下,這時就會發現世界更大,各種千奇百怪的外設開始出現了,要想去接觸這些東西並干掉它,就得用總線去降伏他們(有數據總線,地址總線,控制總線這個篇章已經講解了)
MCU的總體結構
前面介紹了MCU的種種,現在就大致形成了下面這一幅方框圖
CPU以外都相當於CPU的外設,在CPU的內部其是通過指令操作寄存器來實現,對於CPU以外的外設,其是通過操作存儲器上的地址空間來完成控制的,SRAM,ROM,FLASH,這很明顯是操作其地址的,這點前面已提過,在這就不贅述了,但對於其他的外設,例如AD接口,或者其他LCD什么的,它們大多也有寄存器,對這些寄存器里進行0或1的讀寫就能實現對這些外設的控制。但這里需要注意一點,外設里的寄存器和CPU內部的寄存器,盡管名稱一樣,但對CPU來說是有巨大區別的,首先一個在里,一個在外,另外操作方式也有很大區別,對於CPU內部的寄存器,往往寫的指令(往寄存器寫0,1字符串),這些字符串代表了CPU內部的某些結構的開或關(或者一些相關操作),而對於外設中的寄存器,這部分也是寫指令,不過這些指令表示對特定的引腳寫高低電平(高為1,低為0),而且也不是直接操作的,都是間接的去更改其外設的那個寄存器對應地址空間的值,也就是間接的去修改外部寄存器的值以達到控制外設的目的,這些也都是靠我們用戶去加以編程實現的。
下面放上一張ARM Cortex內核里的地址划分,由於其是32位的,其地址線就為32位,從0x0000_0000一直到0xFFFF_FFFF
左邊這張圖是ARM設計公司設計好的其32位Cortex內核的地址划分,規定了在哪些地址空間里可以干嘛,規定的地址空間基本給的很足,對於一般廠家或生產一般的MCU並不需要這么大的空間,所以其地址有些廠家可根據需要是否使用,正如右邊那幅圖那樣,實際使用的可能只是其中一部分,Cortex划分給了RAM為512M,實際我們使用就用了幾K,廠家在設計時也是這樣的,例如,MKL25Z128里就只設置了16K的RAM,那么我們的編程最終也是來操控這些對應的地址(或者說是存儲器),所以說,單片機的編程精髓也在於對存儲器的使用,具體怎么使用存儲器,就是來操作地址,地址里有程序,有堆棧,有外設寄存器對應的內容等等,更高級的還有地址映射的概念如將實際的物理地址映射到一個虛擬的地址。
那么既然編程是對這些存儲器進行操作,那不妨來探討一個真正的程序從上電開始是如何運行的,這里就拿MKL25Z128作簡要分析,在上電之前,我們的中斷向量表和程序是存儲在非易失存儲器里的(例如FLASH),當系統上電后,CPU將中斷向量表里的第一項的值賦值給堆棧指針作為堆棧起始地址,這時SP指向了那個12K的RAM部分,復位向量將其程序函數入口地址賦給PC指針,程序開始一條條往下運行,程序在運行過程中的一些變量(局部或全局)放在堆里(比如4k區域),棧放在12K的RAM里,當程序里跑到對外設的一段程序時,程序會轉到外設寄存器所對應的那個地址里,通過往該地址進行讀或寫來達到對特定外設的控制,當程序執行完子程序,又經過出棧,返回到原來地址位置一條條的向下執行,講到這里,歸總一下,不管是哪家的MCU,只要內核一致(指令集+寄存器),再了解一下對應的存儲空間,任何MCU基本可以手到擒來。