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

評論