單芯片解決方案,開啟全新體驗(yàn)——W55MH32 高性能以太網(wǎng)單片機(jī)
W55MH32是WIZnet重磅推出的高性能以太網(wǎng)單片機(jī),它為用戶帶來前所未有的集成化體驗(yàn)。這顆芯片將強(qiáng)大的組件集于一身,具體來說,一顆W55MH32內(nèi)置高性能Arm? Cortex-M3核心,其主頻最高可達(dá)216MHz;配備1024KB FLASH與96KB SRAM,滿足存儲(chǔ)與數(shù)據(jù)處理需求;集成TOE引擎,包含WIZnet全硬件TCP/IP協(xié)議棧、內(nèi)置MAC以及PHY,擁有獨(dú)立的32KB以太網(wǎng)收發(fā)緩存,可供8個(gè)獨(dú)立硬件socket使用。如此配置,真正實(shí)現(xiàn)了All-in-One解決方案,為開發(fā)者提供極大便利。
在封裝規(guī)格上,W55MH32 提供了兩種選擇:QFN100和QFN68。
W55MH32L采用QFN100封裝版本,尺寸為12x12mm,其資源豐富,專為各種復(fù)雜工控場(chǎng)景設(shè)計(jì)。它擁有66個(gè)GPIO、3個(gè)ADC、12通道DMA、17個(gè)定時(shí)器、2個(gè)I2C、5個(gè)串口、2個(gè)SPI接口(其中1個(gè)帶I2S接口復(fù)用)、1個(gè)CAN、1個(gè)USB2.0以及1個(gè)SDIO接口。如此豐富的外設(shè)資源,能夠輕松應(yīng)對(duì)工業(yè)控制中多樣化的連接需求,無論是與各類傳感器、執(zhí)行器的通信,還是對(duì)復(fù)雜工業(yè)協(xié)議的支持,都能游刃有余,成為復(fù)雜工控領(lǐng)域的理想選擇。 同系列還有QFN68封裝的W55MH32Q版本,該版本體積更小,僅為8x8mm,成本低,適合集成度高的網(wǎng)關(guān)模組等場(chǎng)景,軟件使用方法一致。更多信息和資料請(qǐng)進(jìn)入http://www.w5500.com/網(wǎng)站或者私信獲取。
此外,本W(wǎng)55MH32支持硬件加密算法單元,WIZnet還推出TOE+SSL應(yīng)用,涵蓋TCP SSL、HTTP SSL以及 MQTT SSL等,為網(wǎng)絡(luò)通信安全再添保障。
為助力開發(fā)者快速上手與深入開發(fā),基于W55MH32L這顆芯片,WIZnet精心打造了配套開發(fā)板。開發(fā)板集成WIZ-Link芯片,借助一根USB C口數(shù)據(jù)線,就能輕松實(shí)現(xiàn)調(diào)試、下載以及串口打印日志等功能。開發(fā)板將所有外設(shè)全部引出,拓展功能也大幅提升,便于開發(fā)者全面評(píng)估芯片性能。
若您想獲取芯片和開發(fā)板的更多詳細(xì)信息,包括產(chǎn)品特性、技術(shù)參數(shù)以及價(jià)格等,歡迎訪問官方網(wǎng)頁:http://www.w5500.com/,我們期待與您共同探索W55MH32的無限可能。
第四章 什么是寄存器
本章參考資料:《W55MH32_數(shù)據(jù)手冊(cè)_V1.0.0》、《W55MH 32參考手冊(cè)_V1.0.0》
學(xué)習(xí)本章時(shí),配合《W55MH 32參考手冊(cè)_V1.0.0》“存儲(chǔ)器和總線架構(gòu)”及“通用I/O(GPIO)”章節(jié)一起閱讀,效果會(huì)更佳,特別是涉及到寄存器說明的部分。
1 什么是寄存器
我們經(jīng)常說寄存器,那么什么是寄存器?這是我們本章需要講解的內(nèi)容,在學(xué)習(xí)的過程中,大家?guī)е@個(gè)疑問好好思考下,到最后看看大家能否用一句話給寄存器下一個(gè)定義。
2 W55MH32
我們開發(fā)板中使用的芯片是100pin的W55MH32L,具體見下圖。這個(gè)就是我們接下來要學(xué)習(xí)的高性能以太網(wǎng)單片機(jī),它將帶領(lǐng)我們進(jìn)入嵌入式的殿堂。
芯片四周是引腳,開發(fā)板中把芯片的引腳引出來,連接到各種傳感器上,然后在W55MH32L上編程(實(shí)際就是通過程序控制這些引腳輸出高電平或者低電平)來控制各種傳感器工作, 通過做實(shí)驗(yàn)的方式來學(xué)習(xí)W55MH32芯片的各個(gè)資源。開發(fā)板是一種評(píng)估板,板載資源非常豐富,引腳復(fù)用比較多, 力求在一個(gè)板子上驗(yàn)證芯片的全部功能。
3 芯片里面有什么
我們看到的芯片是已經(jīng)封裝好的成品,主要由內(nèi)核和片上外設(shè)組成。若與電腦類比,內(nèi)核與外設(shè)就如同電腦上的CPU與主板、內(nèi)存、顯卡、硬盤的關(guān)系。
W55MH32采用的是Cortex-M3內(nèi)核,內(nèi)核即CPU,負(fù)責(zé)在內(nèi)核之外設(shè)計(jì)部件并生產(chǎn)整個(gè)芯片,這些內(nèi)核之外的部件被稱為核外外設(shè)或片上外設(shè)。 如GPIO、USART(串口)、I2C、SPI等都叫做片上外設(shè)。
芯片(這里指內(nèi)核,或者叫CPU)和外設(shè)之間通過各種總線連接,其中驅(qū)動(dòng)單元有4個(gè),被動(dòng)單元也有4個(gè), 具體見圖 W55MH32系統(tǒng)框圖 。為了方便理解,我們都可以把驅(qū)動(dòng)單元理解成是CPU部分,被動(dòng)單元都理解成外設(shè)。 下面我們簡(jiǎn)單介紹下驅(qū)動(dòng)單元和被動(dòng)單元的各個(gè)部件。
3.1 ICode總線
ICode中的I表示Instruction,即指令。我們寫好的程序編譯之后都是一條條指令,存放在FLASH中, 內(nèi)核要讀取這些指令來執(zhí)行程序就必須通過ICode總線,它幾乎每時(shí)每刻都需要被使用,它是專門用來取指的。
3.2 驅(qū)動(dòng)單元
3.2.1 DCode總線
DCode中的D表示Data,即數(shù)據(jù),那說明這條總線是用來取數(shù)的。我們?cè)趯懗绦虻臅r(shí)候,數(shù)據(jù)有常量和變量?jī)煞N, 常量就是固定不變的,用C語言中的const關(guān)鍵字修飾,是放到內(nèi)部的FLASH當(dāng)中的,變量是可變的,不管是全局變量還是局部變量都放在內(nèi)部的SRAM。 因?yàn)閿?shù)據(jù)可以被Dcode總線和DMA總線訪問,所以為了避免訪問沖突,在取數(shù)的時(shí)候需要經(jīng)過一個(gè)總線矩陣來仲裁,決定哪個(gè)總線在取數(shù)。
3.2.2 系統(tǒng)總線
系統(tǒng)總線主要是訪問外設(shè)的寄存器,我們通常說的寄存器編程,即讀寫寄存器都是通過這根系統(tǒng)總線來完成的。
3.2.3 DMA總線
DMA總線也主要是用來傳輸數(shù)據(jù),這個(gè)數(shù)據(jù)可以是在某個(gè)外設(shè)的數(shù)據(jù)寄存器,可以在SRAM,可以在內(nèi)部的FLASH。 因?yàn)閿?shù)據(jù)可以被Dcode總線和DMA總線訪問,所以為了避免訪問沖突,在取數(shù)的時(shí)候需要經(jīng)過一個(gè)總線矩陣來仲裁,決定哪個(gè)總線在取數(shù)。
3.3 被動(dòng)單元
3.3.1 內(nèi)部的閃存存儲(chǔ)器
內(nèi)部的閃存存儲(chǔ)器即FLASH,我們編寫好的程序就放在這個(gè)地方。內(nèi)核通過ICode總線來取里面的指令。
3.3.2 內(nèi)部的SRAM
內(nèi)部的SRAM,即我們通常說的RAM,程序的變量,堆棧等的開銷都是基于內(nèi)部的SRAM。內(nèi)核通過DCode總線來訪問它。
3.3.3 FSMC
FSMC的英文全稱是Flexible static memory controller,叫靈活的靜態(tài)的存儲(chǔ)器控制器, 是W55MH32中一個(gè)很有特色的外設(shè), 通過FSMC,我們可以擴(kuò)展內(nèi)存,如外部的SRAM,NANDFLASH和NORFLASH。但有一點(diǎn)我們要注意的是,F(xiàn)SMC只能擴(kuò)展靜態(tài)的內(nèi)存, 即名稱里面的S:static,不能是動(dòng)態(tài)的內(nèi)存,比如SDRAM就不能擴(kuò)展。
3.3.4 AHB到APB的橋
從AHB總線延伸出來的兩條APB2和APB1總線,上面掛載著W55MH32各種各樣的特色外設(shè)。我們經(jīng)常說的GPIO、串口、I2C、SPI這些外設(shè)就掛載在這兩條總線上, 這個(gè)是我們學(xué)習(xí)W55MH32的重點(diǎn),就是要學(xué)會(huì)編程這些外設(shè)去驅(qū)動(dòng)外部的各種設(shè)備。
4 存儲(chǔ)器映射
在W55MH32系統(tǒng)框圖中,程序存儲(chǔ)器、數(shù)據(jù)存儲(chǔ)器、寄存器和輸入輸出端口被組織在同一個(gè) 4GB 的線性地址空間內(nèi)。數(shù)據(jù)字節(jié)以小端格式存放在存儲(chǔ)器中。一個(gè)字里的最低地址字節(jié)被認(rèn)為是該字的最低有效字節(jié),而最高地址字節(jié)是最高有效字節(jié)??稍L問的存儲(chǔ)器空間被分成 8 個(gè)主要塊,每個(gè)塊為 512MB。我們?cè)诰幊痰臅r(shí)候,可以通過他們的地址找到他們,然后來操作他們(通過C語言對(duì)它們進(jìn)行數(shù)據(jù)的讀和寫)。
4.1 存儲(chǔ)器映射
存儲(chǔ)器本身不具有地址信息,它的地址是由芯片廠商或用戶分配,給存儲(chǔ)器分配地址的過程就稱為存儲(chǔ)器映射, 具體見圖 存儲(chǔ)器映射 。如果給存儲(chǔ)器再分配一個(gè)地址就叫存儲(chǔ)器重映射。
4.1.1 存儲(chǔ)器區(qū)域功能劃分
在這4GB的地址空間中,ARM已經(jīng)粗線條的平均分成了8個(gè)塊,每塊512MB,每個(gè)塊也都規(guī)定了用途,具體分類見表格 存儲(chǔ)器功能分類 。 每個(gè)塊的大小都有512MB,顯然這是非常大的,芯片廠商在每個(gè)塊的范圍內(nèi)設(shè)計(jì)各具特色的外設(shè)時(shí)并不一定都用得完,都是只用了其中的一部分而已。
序號(hào) | 用途 | 地址范圍 |
Block 0 | Code | 0x0000 0000 ~ 0x1FFF FFFF(512MB) |
Block 1 | SRAM | 0x2000 0000 ~ 0x3FFF FFFF(512MB) |
Block 2 | 片上外設(shè) | 0x4000 0000 ~ 0x5FFF FFFF(512MB) |
Block 3 | FSMC 的 bank1 ~ bank2 | 0x6000 0000 ~ 0x7FFF FFFF(512MB) |
Block 4 | FSMC 的 bank3 ~ bank4 | 0x8000 0000 ~ 0x9FFF FFFF(512MB) |
Block 5 | FSMC 寄存器 | 0xA000 0000 ~ 0xCFFF FFFF(512MB) |
Block 6 | 沒有使用 | 0xD000 0000 ~ 0xDFFF FFFF(512MB) |
Block 7 | Cortex-M3 內(nèi)部外設(shè) | 0xE000 0000 ~ 0xFFFF FFFF(512MB) |
在這8個(gè)Block里面,有3個(gè)塊非常重要,也是我們最關(guān)心的三個(gè)塊。Block0用來設(shè)計(jì)成內(nèi)部FLASH,Block1用來設(shè)計(jì)成內(nèi)部RAM, Block2用來設(shè)計(jì)成片上的外設(shè),下面我們簡(jiǎn)單的介紹下這三個(gè)Block里面的具體區(qū)域的功能劃分。
4.1.1.1 存儲(chǔ)器Block0內(nèi)部區(qū)域功能劃分
Block0主要用于設(shè)計(jì)片內(nèi)的FLASH,要在芯片內(nèi)部集成更大的FLASH或者SRAM都意味著芯片成本的增加,往往片內(nèi)集成的FLASH都不會(huì)太大, W55MH32能在追求性價(jià)比的同時(shí)做到512KB,實(shí)乃良心之舉。Block內(nèi)部區(qū)域的功能劃分具體見表格 存儲(chǔ)器Block0內(nèi)部區(qū)域功能劃分 。
塊 | 用途說明 | 地址范圍 |
Block0 | 預(yù)留 | 0x1FFE C008 ~ 0x1FFF FFFF |
選項(xiàng)字節(jié):用于配置讀寫保護(hù)、BOR 級(jí)別、軟件 / 硬件看門狗以及器件處于待機(jī)或停止模式下的復(fù)位。當(dāng)芯片被鎖住后,可從 RAM 啟動(dòng)修改對(duì)應(yīng)寄存器位。 | 0x1FFF F800 - 0x1FFF F80F | |
系統(tǒng)存儲(chǔ)器:存放 ST 出廠燒寫的 ISP 自舉程序(Bootloader),用戶無法改動(dòng),串口下載需用此程序。 | 0x1FFF F000 - 0x1FFF F7FF | |
預(yù)留 | 0x0808 0000 ~ 0x1FFF EFFF | |
FLASH:程序存放位置 | 0x0800 0000 ~ 0x0807 FFFF (512KB) | |
預(yù)留 | 0x0008 0000 ~ 0x07FF FFFF | |
取決于 BOOT 引腳,為 FLASH、系統(tǒng)存儲(chǔ)器、SRAM 的別名。 | 0x0000 0000 ~ 0x0007 FFFF |
4.1.1.2 儲(chǔ)存器Block1內(nèi)部區(qū)域功能劃分
Block1用于設(shè)計(jì)片內(nèi)的SRAM,Block內(nèi)部區(qū)域的功能劃分具體見表格 存儲(chǔ)器Block1內(nèi)部區(qū)域功能劃分 。
塊 | 用途說明 | 地址范圍 |
Block1 | 預(yù)留 | 0x2001 0000 ~ 0x3FFF FFFF |
SRAM 64KB | 0x2000 0000 ~ 0x2000 FFFF |
4.1.1.3 儲(chǔ)存器Block2內(nèi)部區(qū)域功能劃分
Block2用于設(shè)計(jì)片內(nèi)的外設(shè),根據(jù)外設(shè)的總線速度不同,Block被分成了APB和AHB兩部分,其中APB又被分為APB1和APB2, 具體見表格 存儲(chǔ)器Block2內(nèi)部區(qū)域功能劃分 。
塊 | 用途說明 | 地址范圍 |
Block2 | APB1 總線外設(shè) | 0x4000 0000 ~ 0x4000 77FF |
APB2 總線外設(shè) | 0x4001 0000 ~ 0x4001 3FFF | |
AHB 總線外設(shè) | 0x4001 8000 ~ 0x5003 FFFF |
5 寄存器映射
我們知道,存儲(chǔ)器本身沒有地址,給存儲(chǔ)器分配地址的過程叫存儲(chǔ)器映射,那什么叫寄存器映射?寄存器到底是什么?
在存儲(chǔ)器Block2這塊區(qū)域,設(shè)計(jì)的是片上外設(shè),它們以四個(gè)字節(jié)為一個(gè)單元,共32bit,每一個(gè)單元對(duì)應(yīng)不同的功能, 當(dāng)我們控制這些單元時(shí)就可以驅(qū)動(dòng)外設(shè)工作。我們可以找到每個(gè)單元的起始地址,然后通過C語言指針的操作方式來訪問這些單元, 如果每次都是通過這種地址的方式來訪問,不僅不好記憶還容易出錯(cuò),這時(shí)我們可以根據(jù)每個(gè)單元功能的不同,以功能為名給這個(gè)內(nèi)存單元取一個(gè)別名, 這個(gè)別名就是我們經(jīng)常說的寄存器,這個(gè)給已經(jīng)分配好地址的有特定功能的內(nèi)存單元取別名的過程就叫寄存器映射。
比如,我們找到GPIOB端口的輸出數(shù)據(jù)寄存器ODR的地址是0x40010C0C(至于這個(gè)地址如何找到可以先跳過,后面我們會(huì)有詳細(xì)的講解), ODR寄存器是32bit,低16bit有效,對(duì)應(yīng)著16個(gè)外部IO,寫0/1對(duì)應(yīng)的的IO則輸出低/高電平?,F(xiàn)在我們通過C語言指針的操作方式, 讓GPIOB的16個(gè)IO都輸出高電平,具體見代碼清單:寄存器-1。
代碼清單:寄存器-1 通過絕對(duì)地址訪問內(nèi)存單元
// GPIOB 端口全部輸出 高電平 *(unsigned int*)(0x4001 0C0C) = 0xFFFF;
0x4001 0C0C在我們看來是GPIOB端口ODR的地址,但是在編譯器看來,這只是一個(gè)普通的變量,是一個(gè)立即數(shù), 要想讓編譯器也認(rèn)為是指針,我們得進(jìn)行強(qiáng)制類型轉(zhuǎn)換,把它轉(zhuǎn)換成指針, 即(unsigned int *)0x4001 0C0C,然后再對(duì)這個(gè)指針進(jìn)行 * 操作。
剛剛我們說了,通過絕對(duì)地址訪問內(nèi)存單元不好記憶且容易出錯(cuò),我們可以通過寄存器的方式來操作,具體見代碼清單:寄存器-2。
代碼清單:寄存器-2 通過寄存器別名方式訪問內(nèi)存單元
// GPIOB 端口全部輸出 高電平 #define GPIOB_ODR (unsigned int*)(GPIOB_BASE+0x0C) * GPIOB_ODR = 0xFF;
為了方便操作,我們干脆把指針操作“*”也定義到寄存器別名里面,具體見代碼清單:寄存器-3。
代碼清單:寄存器-3 通過寄存器別名訪問內(nèi)存單元
// GPIOB 端口全部輸出 高電平 #define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C) GPIOB_ODR = 0xFF;
5.1 W55MH32的外設(shè)地址映射
片上外設(shè)區(qū)分為三條總線,根據(jù)外設(shè)速度的不同,不同總線掛載著不同的外設(shè),APB1掛載低速外設(shè),APB2和AHB掛載高速外設(shè)。 相應(yīng)總線的最低地址我們稱為該總線的基地址,總線基地址也是掛載在該總線上的首個(gè)外設(shè)的地址。其中APB1總線的地址最低,片上外設(shè)從這里開始,也叫外設(shè)基地址。
5.1.1 總線基地址
總線名稱 | 總線基地址 | 相對(duì)外設(shè)基地址的偏移 |
APB1 | 0x4000 0000 | 0x0 |
APB2 | 0x4001 0000 | 0x0001 0000 |
AHB | 0x4001 8000 | 0x0001 8000 |
表格 總線基地址 的“相對(duì)外設(shè)基地址偏移”即該總線地址與“片上外設(shè)”基地址0x4000 0000的差值。關(guān)于地址的偏移我們后面還會(huì)講到。
5.1.2 外設(shè)基地址
在XX外設(shè)的地址范圍內(nèi),分布著的就是該外設(shè)的寄存器。以GPIO外設(shè)為例,GPIO是通用輸入輸出端口的簡(jiǎn)稱, 簡(jiǎn)單來說就是W55MH32可控制的引腳,基本功能是控制引腳輸出高電平或者低電平。最簡(jiǎn)單的應(yīng)用就是把GPIO的引腳連接到LED燈的陰極, LED燈的陽極接電源,然后通過W55MH32控制該引腳的電平,從而實(shí)現(xiàn)控制LED燈的亮滅。
GPIO有很多個(gè)寄存器,每一個(gè)都有特定的功能。每個(gè)寄存器為32bit,占四個(gè)字節(jié),在該外設(shè)的基地址上按照順序排列, 寄存器的位置都以相對(duì)該外設(shè)基地址的偏移地址來描述。這里我們以GPIOB端口為例,來說明GPIO都有哪些寄存器, 具體見表格 GPIOB端口的寄存器地址列表:
寄存器名稱 | 寄存器地址 | 相對(duì) GPIOB 基址的偏移 |
GPIOB_CRL | 0x4001 0C00 | 0x00 |
GPIOB_CRH | 0x4001 0C04 | 0x04 |
GPIOB_IDR | 0x4001 0C08 | 0x08 |
GPIOB_ODR | 0x4001 0C0C | 0x0C |
GPIOH_BSRR | 0x4001 0C10 | 0x10 |
GPIOH_BRR | 0x4001 0C14 | 0x14 |
GPIOH_LOCKR | 0x4001 0C18 | 0x18 |
有關(guān)外設(shè)的寄存器說明可參考《W55MH32參考手冊(cè)》中具體章節(jié)的寄存器描述部分,在編程的時(shí)候我們需要反復(fù)的查閱外設(shè)的寄存器說明。
這里我們以“GPIO端口置位/復(fù)位寄存器”為例,教大家如何理解寄存器的說明, 具體見圖 GPIO端口置位_復(fù)位寄存器說明 。
1 名稱
寄存器說明中首先列出了該寄存器中的名稱,“(GPIOx_BSRR)(x=A…E)”這段的意思是該寄存器名為“GPIOx_BSRR”其中的“x”可以為A-E, 也就是說這個(gè)寄存器說明適用于GPIOA、GPIOB至GPIOE,這些GPIO端口都有這樣的一個(gè)寄存器。
2 偏移地址
偏移地址是指本寄存器相對(duì)于這個(gè)外設(shè)的基地址的偏移。本寄存器的偏移地址是0x10, 從參考手冊(cè)中我們可以查到GPIOA外設(shè)的基地址為0x4001 0800 , 我們就可以算出GPIOA的這個(gè)GPIOA_BSRR寄存器的地址為:0x4001 0800+0x10;同理, 由于GPIOB的外設(shè)基地址為0x4001 0C00, 可算出GPIOB_BSRR寄存器的地址為:0x4001 0C00+0x10 。其他GPIO端口以此類推即可。
3 寄存器位表
緊接著的是本寄存器的位表,表中列出它的0-31位的名稱及權(quán)限。表上方的數(shù)字為位編號(hào),中間為位名稱,最下方為讀寫權(quán)限,其中w表示只寫, r表示只讀,rw表示可讀寫。本寄存器中的位權(quán)限都是w,所以只能寫,如果讀本寄存器,是無法保證讀取到它真正內(nèi)容的。而有的寄存器位只讀, 一般是用于表示W(wǎng)55MH32外設(shè)的某種工作狀態(tài)的,由W55MH32硬件自動(dòng)更改,程序通過讀取那些寄存器位來判斷外設(shè)的工作狀態(tài)。
4 位功能說明
位功能是寄存器說明中最重要的部分,它詳細(xì)介紹了寄存器每一個(gè)位的功能。例如本寄存器中有兩種寄存器位,分別為BRy及BSy, 其中的y數(shù)值可以是0-15,這里的0-15表示端口的引腳號(hào),如BR0、BS0用于控制GPIOx的第0個(gè)引腳,若x表示GPIOA,那就是控制GPIOA的第0引腳, 而BR1、BS1就是控制GPIOA第1個(gè)引腳。
其中BRy引腳的說明是“0:不會(huì)對(duì)相應(yīng)的ODRx位執(zhí)行任何操作;1:對(duì)相應(yīng)ODRx位進(jìn)行復(fù)位”。這里的“復(fù)位”是將該位設(shè)置為0的意思, 而“置位”表示將該位設(shè)置為1;說明中的ODRx是另一個(gè)寄存器的寄存器位,我們只需要知道ODRx位為1的時(shí)候,對(duì)應(yīng)的引腳x輸出高電平, 為0的時(shí)候?qū)?yīng)的引腳輸出低電平即可(感興趣的讀者可以查詢?cè)摷拇嫫鱃PIOx_ODR的說明了解)。所以,如果對(duì)BR0寫入“1”的話, 那么GPIOx的第0個(gè)引腳就會(huì)輸出“低電平”,但是對(duì)BR0寫入“0”的話,卻不會(huì)影響ODR0位,所以引腳電平不會(huì)改變。要想該引腳輸出“高電平”, 就需要對(duì)“BS0”位寫入“1”,寄存器位BSy與BRy是相反的操作。
5.2 C語言對(duì)寄存器的封裝
以上所有的關(guān)于存儲(chǔ)器映射的內(nèi)容,最終都是為大家更好地理解如何用C語言控制讀寫外設(shè)寄存器做準(zhǔn)備,此處是本章的重點(diǎn)內(nèi)容。
5.2.1 封裝總線和外設(shè)基地址
在編程上為了方便理解和記憶,我們把總線基地址和外設(shè)基地址都以相應(yīng)的宏定義起來,總線或者外設(shè)都以他們的名字作為宏名, 具體見代碼清單:寄存器-4 。
代碼清單:寄存器-4 總線和外設(shè)基址宏定義
/* 外設(shè)基地址 */ #define PERIPH_BASE ((unsigned int)0x40000000) /* 總線基地址 */ #define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) #define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000) /* GPIO外設(shè)基地址 */ #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00) #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000) #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400) #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800) #define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00) #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000) /* 寄存器基地址,以GPIOB為例 */ #define GPIOB_CRL (GPIOB_BASE+0x00) #define GPIOB_CRH (GPIOB_BASE+0x04) #define GPIOB_IDR (GPIOB_BASE+0x08) #define GPIOB_ODR (GPIOB_BASE+0x0C) #define GPIOB_BSRR (GPIOB_BASE+0x10) #define GPIOB_BRR (GPIOB_BASE+0x14) #define GPIOB_LCKR (GPIOB_BASE+0x18)
代碼清單:寄存器-4 首先定義了 “片上外設(shè)”基地址PERIPH_BASE,接著在PERIPH_BASE上加入各個(gè)總線的地址偏移, 得到APB1、APB2總線的地址APB1PERIPH_BASE、APB2PERIPH_BASE,在其之上加入外設(shè)地址的偏移,得到GPIOA-G的外設(shè)地址, 最后在外設(shè)地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具體地址,就可以用指針讀寫, 具體見代碼清單:寄存器-5 。
代碼清單:寄存器-5 使用指針控制BSRR寄存器
/* 控制GPIOB 引腳0輸出低電平(BSRR寄存器的BR0置1) */ *(unsigned int *)GPIOB_BSRR = (0x01<(16+0)); /* 控制GPIOB 引腳0輸出高電平(BSRR寄存器的BS0置1) */ *(unsigned int *)GPIOB_BSRR = 0x01<0; unsigned int temp; /* 讀取GPIOB 端口所有引腳的電平(讀IDR寄存器) */ temp = *(unsigned int *)GPIOB_IDR;
該代碼使用 (unsigned int *) 把GPIOB_BSRR宏的數(shù)值強(qiáng)制轉(zhuǎn)換成了地址,然后再用“*”號(hào)做取指針操作,對(duì)該地址的賦值, 從而實(shí)現(xiàn)了寫寄存器的功能。同樣,讀寄存器也是用取指針操作,把寄存器中的數(shù)據(jù)取到變量里,從而獲取W55MH32外設(shè)的狀態(tài)。
5.2.2 封裝寄存器列表
用上面的方法去定義地址,還是稍顯繁瑣,例如GPIOA-GPIOE都各有一組功能相同的寄存器,如GPIOA_ODR/GPIOB_ODR/GPIOC_ODR等等, 它們只是地址不一樣,但卻要為每個(gè)寄存器都定義它的地址。為了更方便地訪問寄存器,我們引入C語言中的結(jié)構(gòu)體語法對(duì)寄存器進(jìn)行封裝, 具體見 代碼清單:寄存器-6 。
代碼清單:寄存器-6 使用結(jié)構(gòu)體對(duì)GPIO寄存器組的封裝
typedef unsigned int uint32_t; /*無符號(hào)32位變量*/ typedef unsigned short int uint16_t; /*無符號(hào)16位變量*/ /* GPIO寄存器列表 */ typedef struct { uint32_t CRL; /*GPIO端口配置低寄存器 地址偏移: 0x00 */ uint32_t CRH; /*GPIO端口配置高寄存器 地址偏移: 0x04 */ uint32_t IDR; /*GPIO數(shù)據(jù)輸入寄存器 地址偏移: 0x08 */ uint32_t ODR; /*GPIO數(shù)據(jù)輸出寄存器 地址偏移: 0x0C */ uint32_t BSRR; /*GPIO位設(shè)置/清除寄存器 地址偏移: 0x10 */ uint32_t BRR; /*GPIO端口位清除寄存器 地址偏移: 0x14 */ uint16_t LCKR; /*GPIO端口配置鎖定寄存器 地址偏移: 0x18 */ } GPIO_TypeDef;
這段代碼用typedef 關(guān)鍵字聲明了名為GPIO_TypeDef的結(jié)構(gòu)體類型,結(jié)構(gòu)體內(nèi)有7個(gè) 成員變量,變量名正好對(duì)應(yīng)寄存器的名字。 C語言的語法規(guī)定,結(jié)構(gòu)體內(nèi)變量的存儲(chǔ)空間是連續(xù)的,其中32位的變量占用4個(gè)字節(jié),16位的變量占用2個(gè)字節(jié), 具體見圖 GPIO_TypeDef結(jié)構(gòu)體成員的地址偏移 。
也就是說,我們定義的這個(gè)GPIO_TypeDef ,假如這個(gè)結(jié)構(gòu)體的首地址為0x4001 0C00(這也是第一個(gè)成員變量CRL的地址), 那么結(jié)構(gòu)體中第二個(gè)成員變量CRH的地址即為0x4001 0C00 +0x04 ,加上的這個(gè)0x04,正是代表CRL所占用的4個(gè)字節(jié)地址的偏移量, 其它成員變量相對(duì)于結(jié)構(gòu)體首地址的偏移,在上述代碼右側(cè)注釋已給。
這樣的地址偏移與W55MH32 GPIO外設(shè)定義的寄存器地址偏移一一對(duì)應(yīng),只要給結(jié)構(gòu)體設(shè)置好首地址,就能把結(jié)構(gòu)體內(nèi)成員的地址確定下來, 然后就能以結(jié)構(gòu)體的形式訪問寄存器,具體見 代碼清單:寄存器-7 。
代碼清單:寄存器-7 通過結(jié)構(gòu)體指針訪問寄存器
GPIO_TypeDef * GPIOx; //定義一個(gè)GPIO_TypeDef型結(jié)構(gòu)體指針GPIOx GPIOx = GPIOB_BASE; //把指針地址設(shè)置為宏GPIOB_BASE地址 GPIOx->IDR = 0xFFFF; GPIOx->ODR = 0xFFFF; uint32_t temp; temp = GPIOx->IDR; //讀取GPIOB_IDR寄存器的值到變量temp中
這段代碼先用GPIO_TypeDef類型定義一個(gè)結(jié)構(gòu)體指針GPIOx,并讓指針指向地址GPIOB_BASE(0x4001 0C00),使用地址確定下來, 然后根據(jù)C語言訪問結(jié)構(gòu)體的語法,用GPIOx->ODR及GPIOx->IDR等方式讀寫寄存器。
最后,我們更進(jìn)一步,直接使用宏定義好GPIO_TypeDef類型的指針,而且指針指向各個(gè)GPIO端口的首地址, 使用時(shí)我們直接用該宏訪問寄存器即可,具體 代碼清單:寄存器-8 。
代碼清單:寄存器-8 定義好GPIO端口首地址址針
/*使用GPIO_TypeDef把地址強(qiáng)制轉(zhuǎn)換成指針*/ #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE) #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE) #define GPIOD ((GPIO_TypeDef *) GPIOD_BASE) #define GPIOE ((GPIO_TypeDef *) GPIOE_BASE) #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE) #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE) #define GPIOH ((GPIO_TypeDef *) GPIOH_BASE) /*使用定義好的宏直接訪問*/ /*訪問GPIOB端口的寄存器*/ GPIOB->BSRR = 0xFFFF; //通過指針訪問并修改GPIOB_BSRR寄存器 GPIOB->CRL = 0xFFFF; //修改GPIOB_CRL寄存器 GPIOB->ODR =0xFFFF; //修改GPIOB_ODR寄存器 uint32_t temp; temp = GPIOB->IDR; //讀取GPIOB_IDR寄存器的值到變量temp中 /*訪問GPIOA端口的寄存器*/ GPIOA->BSRR = 0xFFFF; GPIOA->CRL = 0xFFFF; GPIOA->ODR =0xFFFF; uint32_t temp; temp = GPIOA->IDR; //讀取GPIOA_IDR寄存器的值到變量temp中
這里我們僅是以GPIO這個(gè)外設(shè)為例,給大家講解了C語言對(duì)寄存器的封裝。以此類推,其他外設(shè)也同樣可以用這種方法來封裝。好消息是, 這部分工作都由固件庫幫我們完成了,這里我們只是分析了下這個(gè)封裝的過程,讓大家知其然,也只其所以然。
5.3 修改寄存器的位操作方法
使用C語言對(duì)寄存器賦值時(shí),我們常常要求只修改該寄存器的某幾位的值,且其它的寄存器位不變,這個(gè)時(shí)候我們就需要用到C語言的位操作方法了。
5.3.1 把變量的某位清零
此處我們以變量a代表寄存器,并假設(shè)寄存器中本來已有數(shù)值,此時(shí)我們需要把變量a的某一位清零,且其它位不變, 方法見 代碼清單:寄存器-9 。
代碼清單:寄存器-9 對(duì)某位清零
//定義一個(gè)變量a = 1001 1111 b (二進(jìn)制數(shù)) unsigned char a = 0x9f; //對(duì)bit2 清零 a &= ~(1<2); //括號(hào)中的1左移兩位,(1<2)得二進(jìn)制數(shù):0000 0100 b //按位取反,~(1<2)得1111 1011 b //假如a中原來的值為二進(jìn)制數(shù): a = 1001 1111 b //所得的數(shù)與a作”位與&”運(yùn)算,a = (1001 1111 b)&(1111 1011 b), //經(jīng)過運(yùn)算后,a的值 a=1001 1011 b // a的bit2 位被清零,而其它位不變。
5.3.2 把變量的某幾個(gè)連續(xù)位清零
由于寄存器中有時(shí)會(huì)有連續(xù)幾個(gè)寄存器位用于控制某個(gè)功能,現(xiàn)假設(shè)我們需要把寄存器的某幾個(gè)連續(xù)位清零, 且其它位不變,方法見 代碼清單:寄存器-10 。
代碼清單:寄存器-10 對(duì)某幾個(gè)連續(xù)位清零
//若把a(bǔ)中的二進(jìn)制位分成2個(gè)一組 //即bit0、bit1為第0組,bit2、bit3為第1組, // bit4、bit5為第2組,bit6、bit7為第3組 //要對(duì)第1組的bit2、bit3清零 a &= ~(3<2*1); //括號(hào)中的3左移兩位,(3<2*1)得二進(jìn)制數(shù):0000 1100 b //按位取反,~(3<2*1)得1111 0011 b //假如a中原來的值為二進(jìn)制數(shù): a = 1001 1111 b //所得的數(shù)與a作”位與&”運(yùn)算,a = (1001 1111 b)&(1111 0011 b), //經(jīng)過運(yùn)算后,a的值 a=1001 0011 b // a的第1組的bit2、bit3被清零,而其它位不變。 //上述(~(3<2*1))中的(1)即為組編號(hào);如清零第3組bit6、bit7此處應(yīng)為3 //括號(hào)中的(2)為每組的位數(shù),每組有2個(gè)二進(jìn)制位;若分成4個(gè)一組,此處即為4 //括號(hào)中的(3)是組內(nèi)所有位都為1時(shí)的值;若分成4個(gè)一組,此處即為二進(jìn)制數(shù)“1111 b” //例如對(duì)第2組bit4、bit5清零 a &= ~(3<2*2);
5.3.3 對(duì)變量的某幾位進(jìn)行賦值。
寄存器位經(jīng)過上面的清零操作后,接下來就可以方便地對(duì)某幾位寫入所需要的數(shù)值了,且其它位不變, 方法見 代碼清單:寄存器-11 ,這時(shí)候?qū)懭氲臄?shù)值一般就是需要設(shè)置寄存器的位參數(shù)。
代碼清單:寄存器-11 對(duì)某幾位進(jìn)行賦值
//a = 1000 0011 b //此時(shí)對(duì)清零后的第2組bit4、bit5設(shè)置成二進(jìn)制數(shù)“01 b ” a |= (1<2*2); //a = 1001 0011 b,成功設(shè)置了第2組的值,其它組不變
5.3.4 對(duì)變量的某位取反
某些情況下,我們需要對(duì)寄存器的某個(gè)位進(jìn)行取反操作,即 1變0 ,0變1,這可以直接用如下操作,其它位不變, 見代碼清單:寄存器-12 。
代碼清單:寄存器-12 對(duì)某位進(jìn)行取反操作
//a = 1001 0011 b //把bit6取反,其它位不變 a ^=(1<6); //a = 1101 0011 b
WIZnet 是一家無晶圓廠半導(dǎo)體公司,成立于 1998 年。產(chǎn)品包括互聯(lián)網(wǎng)處理器 iMCU?,它采用 TOE(TCP/IP 卸載引擎)技術(shù),基于獨(dú)特的專利全硬連線 TCP/IP。iMCU? 面向各種應(yīng)用中的嵌入式互聯(lián)網(wǎng)設(shè)備。
WIZnet 在全球擁有 70 多家分銷商,在香港、韓國、美國設(shè)有辦事處,提供技術(shù)支持和產(chǎn)品營銷。
香港辦事處管理的區(qū)域包括:澳大利亞、印度、土耳其、亞洲(韓國和日本除外)。
審核編輯 黃宇
-
單片機(jī)
+關(guān)注
關(guān)注
6067文章
44992瀏覽量
650445 -
以太網(wǎng)
+關(guān)注
關(guān)注
41文章
5635瀏覽量
175946 -
寄存器
+關(guān)注
關(guān)注
31文章
5434瀏覽量
124485 -
WIZnet
+關(guān)注
關(guān)注
3文章
20瀏覽量
42451
發(fā)布評(píng)論請(qǐng)先 登錄
初級(jí)工程師PCB設(shè)計(jì)技巧(PPT版)
使用寄存器點(diǎn)亮LED燈

【第四章 定時(shí)任務(wù)】手把手教你玩轉(zhuǎn)新版正點(diǎn)原子云
汽車電路初識(shí)
XILINX FPGA CLB單元之移位寄存器

中科芯CKS32K148 MCU SCG時(shí)鐘工作頻率范圍和寄存器設(shè)置

接口的控制與狀態(tài)寄存器什么作用
【「嵌入式Hypervisor:架構(gòu)、原理與應(yīng)用」閱讀體驗(yàn)】+第三四章閱讀報(bào)告
寄存器間接尋址和寄存器尋址的區(qū)別
ARM寄存器的分類及功能
通用寄存器是什么意思
寄存器的類型和作用
寄存器的輸入輸出方式
寄存器故障分析
寄存器是什么意思?寄存器是如何構(gòu)成的?

評(píng)論