譯 對映裝置暫存器到記憶體

2021-07-09 22:24:39 字數 3186 閱讀 9261

分類:

記憶體對映i/o是能用標準c/c++做得相當好的乙個東東。

裝置驅動通過裝置暫存器來與外圍裝置通訊。驅動通過向裝置暫存器中寫入命令或者資料,或者通過讀取裝置暫存器來讀取裝置狀態或者資料。

許多處理器使用

記憶體對映i/o,就是把裝置的暫存器對映到常規記憶體空間的固定位址。對於乙個c/c++程式設計師來說,記憶體對映裝置的暫存器看起來非常像乙個普通的資料物件。程式可以使用普通的賦值操作符來向記憶體對映裝置的暫存器讀取或寫入值。

有些處理器使用

埠對映i/o,就是把裝置的暫存器對映到乙個單獨的位址空間,這個位址空間一般來說要比常規記憶體小。在這些處理器上,程式必須使用特殊的機器指令來向記憶體對映裝置的暫存器讀取或寫入值,比如在intel x86上使用in和out指令。對於乙個c/c++程式設計師來說,埠對映的裝置暫存器就不怎麼像普通資料物件了。

c/c++的標準沒有闡述埠對映i/o。執行

埠對映i/o的程式必須使用不標準的,與特定平台相關的語言或者庫擴充套件,或者更糟糕的是用彙編**。另一方面,記憶體對映i/o是能用標準c/c++做得相當好的乙個技術。

這篇文章將討論不同的方法來訪問記憶體對映裝置的暫存器。

裝置暫存器型別

有些裝置暫存器可能只有乙個位元組,其他的可能有乙個字或者更多。在c/c++中,對於乙個裝置暫存器最簡單的表示就是乙個大小合適的,有符號的整型物件。例如,你可能將乙個單位元組的暫存器宣告為char或者將乙個雙位元組的暫存器宣告為unsigned short。

例如,單板機arm evaluator-7t有乙個小類對映記憶體外設。該板子的文件將裝置暫存器引用為特殊暫存器。這些特殊的暫存器占有64k,從位址0x03ff0000開始。這段記憶體是基於位元組定址的,但是每個暫存器都是四位元組的字,並且按照4位元組對齊。可以把每個特殊暫存器當作乙個int或unsigned int來操作。有些程式設計師更喜歡使用指定了物理型別大小的型別,如int32_t或uint32_t。(類似int32_t和uint32_t的型別定義在c99的標頭檔案中)

筆者喜歡使用能表達型別的意義而不是物理長度的象徵型別(symbolic type ),如:

typedef unsigned int special_register;

特殊暫存器實際上是volatile實體——這種實體可以用編譯器無法檢測到的方式改變自己的狀態。因此typedef應該是乙個volatile修飾的型別的別名:

typedef unsigned int volatile special_register;

許多裝置通過乙個小的裝置暫存器集合來互動,而不是僅僅用乙個。例如,evaluator-7t板使用5個特殊暫存器來控制兩個整合定時器:

* tmod:時間模式暫存器

* tdata0:定時器0資料暫存器

* tdata1: 定時器1資料暫存器

* tcnt0: 定時器0計數暫存器

* tcnt1: 定時器1計數暫存器

可以用乙個如下定義的結構體來表示這些定時器暫存器:

typedef struct dual_timers dual_timers;

struct dual_timers

;//譯註:注意此處順序,每個成員大小及對齊,因為後續的訪問依賴於此。

在struct定義前的typedef使得dual_timers由乙個不完整的型別變成了乙個完整的型別。筆者更願意使用count0來標示tcnt0,但是tcnt0是整個產品文件所使用的名字,因此最好不要改變它。

在c++中,筆者將該struct定義為乙個有恰當的成員函式的class。無論dual_timers是c的struct還是c++的class不影響下面的討論。

安置裝置暫存器

unsigned short count _at(0xff08);

這用於把count宣告為放置在0xff08上的記憶體對映裝置暫存器。其他的編譯器提供#pragma指示來做相同的事。然而,_at屬性和#pragma指示不是標準的。每個有類似擴充套件的編譯器更可能支援一些不同的東西。 標準c/c++不會讓你宣告乙個放置在特定位址上的變數。訪問裝置暫存器的乙個通用習慣是用指標,該指標的值就是暫存器的位址。如:evaluator-7t板上的定時器暫存器放置在位址0x03ff6000。程式可以通過指向該位址的指標訪問這些暫存器。可以定義這樣的指標為乙個巨集:

#define timers ((dual_timers *)0x03ff6000)

或者定義為乙個const指標:

dual_timers *const timers = (dual_timers *)0x03ff6000;

用其中任一種方式來定義定時器,就能夠使用它來訪問定時器暫存器。如:tmod暫存器含有允許啟用和禁用定時器的位,可以設定或者清除該位來達到目的。因此可以用列舉為這些位定義一些掩碼:

enum ;

同時禁用這兩個定時器

timers->tmod &= ~(te0 | te1);

比較兩者

這兩個關於指標的定義——巨集和const物件——很大程度上是可以互換的。然而,它們在行為上有少許不同,而且在某些平台上會產生少許不同的機器碼。

筆者在先前的乙個專欄裡解釋過,預處理器是乙個截然不同的編譯階段。預處理器在編譯器做其它符號處理之前執行巨集替換。例如,給定timers的巨集定義,預處理器把

timers->tmod &= ~(te0 | te1);

翻譯成:

((dual_timers *)0x03ff6000)->tmod &= ~(te0 | te1);

其後的編譯階段永遠都看不到timers的符號巨集(dual_timers),他們僅僅看到替換後的文字。許多編譯器不會將巨集名傳遞給他們的偵錯程式,這樣巨集名在偵錯程式中是不可見的。

使用巨集還會帶來乙個更嚴重的問題:巨集名不遵守作用域規則。例如,不能將乙個巨集限制在乙個區域性的作用域。在函式中定義巨集:

void timer_handler()

不能使該巨集成為區域性巨集。該巨集的作用於是全域性的。同樣,不能把乙個巨集定義為c++類或者名字空間的乙個成員。

實際上,巨集名比全域性名字還要糟糕。內層域的名字會暫時覆蓋掉外層的名字,但是不能覆蓋掉巨集名。從而導致在你不希望發生替換的時候,巨集替換發生了。

dan saks saks is president of saks & associates, a c/c++ training and consulting

company. you can write to him at

[email protected].

暫存器對映

在block2這塊區域,設計的是片上外設,它們以4個位元組為1個單元,共32位,每乙個單元對應不同的功能,當控制這些單元時,就可以驅動外設工作,可以找到每個單元的起始位址,然後通過c語言指標的操作方式來訪問這些單元。根據每個單元功能的不同,以功能為名給這個記憶體單元取乙個別名,這個別名就是暫存器。給...

暫存器位址對映

目錄 暫存器位址對映 暫存器位址對映關係 mdk5 中的位址關係以及查詢方法 gpiob的埠位址 埠位址如何對映到具體暫存器的位址?暫存器位址與埠位址的關係 apb2外圍裝置匯流排的位址 外圍裝置基位址 綜上所述 如上圖所示,進行了一次強制型別轉換,其實位址這個東西就是代表儲存的地點,任何型別對他來...

暫存器對映與直接操作暫存器

一 儲存器對映 與重對映 儲存器本身不具有位址資訊,它的位址是由晶元廠商或使用者分配,給 物理儲存器分配 邏輯位址的過程就稱為儲存器對映 通過這些邏輯位址就可以訪問到相應的儲存器的物理儲存單元。如果給儲存器再分配乙個位址就叫儲存器重對映。如stm32,對於 片上外設,它們以四個位元組為乙個單元,共3...