<center id="qkqgy"><optgroup id="qkqgy"></optgroup></center>
  • <menu id="qkqgy"></menu>
    <nav id="qkqgy"></nav>
    <xmp id="qkqgy"><nav id="qkqgy"></nav>
  • <xmp id="qkqgy"><menu id="qkqgy"></menu>
    <menu id="qkqgy"><menu id="qkqgy"></menu></menu>
    <tt id="qkqgy"><tt id="qkqgy"></tt></tt>

  • <>什么是寄存器?

    我們現在在開發STM32時,已經很少用到寄存器編程,更多的使用ST公司所提供的標準庫和最新的HAL庫進行編程實現,但是不管是標準庫還是HAL庫都是在原來的寄存器層面上進行了封裝,知道寄存器是什么,還是很重要的,了解寄存器的原來,對我們使用標準庫和HAL庫也是有很大的幫助。我下面會以STM32F103VET6為例,解釋寄存器到底是什么?

    在STM32編程,實際上就是通過程序控制這些引腳輸出高低電平來控制各種傳感器工作

    下圖是STM32F103VET6的引腳分布圖

    我們常見的STM32芯片是已經封裝好的成品,主要是由內核和片上外設組成,就像電腦中的CPU和主板、內存、顯卡、硬盤的關系。

    STM32F103采用的是Cortex-M3內核,內核就是CPU,它由ARM公司設計,ST公司生產,ST負責設計內核之外的整個芯片,這些內核之外的部件,被稱為片上外設。例如GPIO、USART、I2C、SPI等都叫做片上外設

    上圖是STM32芯片架構簡圖

    內核和各種外設之間是通過各種總線連接,聽到總線,應該不少人聽過電腦中的南橋北橋總線,其實我們STM32中也是一樣的,STM32中的驅動單元是4個,被動單元也是4個,我們把驅動單元當成CPU的一部分,同樣可以將被動單元當成外設,下面我將介紹驅動單元和被動單元的各個部件。

    <>ICode總線

    I代表Instruction,即命令,我們程序編譯后都是一條條指令,存于FLASH中,內核通過ICode總線來讀取這些指令,進而根據這些指令執行程序,它是專門用來讀取指令的。

    <>驅動單元

    <>DCode總線

    D是數據Data,即數據,說明這條總線是用來讀取數據的,我們知道數據分為常量和變量,常量是固定不變的,在C語言中常量是用const關鍵字修飾的,它存放在FLASH中,變量是可變的,不管是局部變量還是全局變量,都存放在SRAM中,數據可以被DCode總線和DMA總線訪問,為了避免沖突,在讀取的時候需要一個總線矩陣來進行總裁,決定哪個總線在讀取數據。

    <>系統總線

    系統總線主要是用于訪問外設的寄存器,我們經常說的寄存器編程,即讀寫寄存器,都是通過這根系統總線來完成的。

    <>DMA總線

    DMA總線主要用于傳輸數據,數據可以是某個外設的數據寄存器,也可以是SRAM,也可以是內部的FLASH,因為數據可以被 Dcode 總線和 DMA
    總線訪問,所以為了避免訪問沖突,在取數的時候需要經過一個總線矩陣來仲裁,決定哪個總線在取數。

    <>被動單元

    <>內部的閃存存儲器

    即FALSH,用于存放編寫好的程序,內核通過ICode總線來取里面的指令

    <>內部的SRAM

    就是我們常說的RAM,程序的變量,以及堆棧的開銷都是基于SRAM的。內核通過DCode總線來訪問它

    <>FSMC

    全稱,靈活的靜態的存儲器控制器,也是STM32的一個外設,通過該外設我們可以擴展內存,如外部的SRAM等,但是FSMC只能擴展靜態的內存,像動態內存SDRAM就不能擴展

    <>AHB到APB的橋

    從AHB總線延伸出來的兩條APB2和APB1總線,上面掛在這STM32的各種各樣的外設,就是我們經常說的GPIO、串口、I2C、SPI這些外設就掛載在這兩條總線上,我們學習STM32,就是學會利用編程這些外設去驅動外部的各種設備。

    ? STM32F10XX系統框圖

    在上圖中,被控單元的FLASH,RAM,FSMC和AHB到APB2的橋,這些功能部件共同排列在一個4GB的地址空間中,在編程時,我們可以通過它們的地址找到它們,然后操作。

    <>存儲器映射

    存儲器本身不具有地址信息,它的地址是由芯片廠商或者用戶分配的,給存儲器分配地址的過程就稱為存儲器映射。如果給存儲器再分配一個地址就叫做存儲器重映射。

    在這4GB的地址空間中,ARM被粗細線條分成了8個塊,每塊512M,每塊都規定了用途,芯片廠商在每個塊的范圍內設計外設時并不一定用得完,都只是用了其中的一部分。

    <>寄存器映射

    鋪墊了那么多,是為了更好的理解寄存器是什么,簡而言之寄存器就是內存,我們都知道,寄存器本身沒有地址,給寄存器分配地址的過程叫寄存器映射,那么什么是寄存器映射呢?

    在存儲器中的某片區域,設計的是片上外設,它們以4個字節為一個單元,共32bit,每一個單元對應不同的功能,我們通過控制這些單元就可以驅動外設工作。我們可以先找到每個單元的起始地址,然后通過C語言的指針的操作方式來訪問這些單元,但是每次都要通過地址的方式訪問,難記憶也容易出錯,于是,我們可以根據每個單元功能的不同,以功能為名給這個內存單元取一個別名,這個別名就是我們常說的寄存器,給已經分配好地址的有特定功能的內存單元取別名的過程就叫做寄存器映射。

    比如,我們找到 GPIOB 端口的輸出數據寄存器 ODR 的地址是 0x4001 0C0CODR ,寄存器是 32bit,低 16bit有效,對應著 16
    個外部 IO,寫 0/1 對應的的 IO 則輸出低/高電平。現在我們通過 C 語言指針的操作方式,讓 GPIOB 的 16 個 IO 都輸出高電平
    // GPIOB 端口全部輸出 高電平 *(unsigned int*)(0x4001 0C0C) = 0xFFFF;
    0x4001 0C0C 在我們看來是 GPIOB 端口 ODR
    的地址,但是在編譯器看來,這只是一個普通的變量,是一個立即數,要想讓編譯器也認為是指針,我們得進行強制類型轉換,把它轉換成指針,即(unsigned int
    *)0x4001 0C0C,然后再對這個指針進行 * 操作。

    剛才我們說了,通過絕對地址訪問內存單元不好記憶且容易出錯,我們可以通過寄存器的方式來操作
    // GPIOB 端口全部輸出 高電平 #define GPIOB_ODR //(unsigned int*)(GPIOB_BASE+0x0C) *
    GPIOB_ODR= 0xFF;
    為了操作方便,我們也直接把指針操作的“*”也定義到寄存器中
    // GPIOB 端口全部輸出 高電平 #define GPIOB_ODR *(unsigned int*)(GPIOB_BASE+0x0C)
    GPIOB_ODR= 0xFF;
    <>STM的外設地址映射

    片上外設分為三條線,根據外設速度的不同,不同總線掛載著不同的外設,APB1掛載低速外設,APB2和AHB掛在高速外設。對應總線的最低地址稱為總線的基地址,總線基地址也是掛載在這個總線上的首個外設的地址,其中APB1總線地址最低,片上外設從這里開始,也叫做外設基地址

    <>外設寄存器

    在 XX 外設的地址范圍內,分布著的就是該外設的寄存器。以 GPIO 外設為例, GPIO是通用輸入輸出端口的簡稱,簡單來說就是 STM32
    可控制的引腳,基本功能是控制引腳輸出高電平或者低電平。最簡單的應用就是把 GPIO 的引腳連接到 LED 燈的陰極, LED 燈的陽極接電源,然后通過
    STM32 控制該引腳的電平,從而實現控制 LED 燈的亮滅。GPIO 有很多個寄存器,每一個都有特定的功能。每個寄存器為
    32bit,占四個字節,在該外設的基地址上按照順序排列,寄存器的位置都以相對該外設基地址的偏移地址來描
    述。這里我們以 GPIOB 端口為例,來說明 GPIO 都有哪些寄存器

    這里以端口置位/復位寄存器為例

    (GPIOx_BSSR)(x=A…E)這段的意思是該寄存器為GPIOx_BSSR其中的“x”可以是A-E,也就是這個寄存器適用于GPIOA、GPIOB至GPIOE,所有GPIO端口都有這樣的一個寄存器

    偏移地址是指本寄存器相對于這個外設的基地址的偏移。本寄存器的偏移地址是 0x18,從參考手冊中我們可以查到 GPIOA 外設的基地址為 0x4001
    0800 ,我們就可以算出GPIOA 的這個 GPIOA_BSRR 寄存器的地址為: 0x4001 0800+0x18 ;同理,由于 GPIOB
    的外設基地址為 0x4001 0C00,可算出 GPIOB_BSRR 寄存器的地址為: 0x4001 0C00+0x18 。其他 GPIO 端口以此類推即可。

    緊接著的是本寄存器的位表,表中列出它的 0-31 位的名稱及權限。表上方的數字為位編號,中間為位名稱,最下方為讀寫權限,其中 w 表示只寫, r 表示只讀,
    rw 表示可讀寫。本寄存器中的位權限都是 w,所以只能寫,如果讀本寄存器,是無法保證讀取到它真正內容的。而有的寄存器位只讀,一般是用于表示 STM32
    外設的某種工作狀態的,由
    STM32硬件自動更改,程序通過讀取那些寄存器位來判斷外設的工作狀態位功能是寄存器說明中最重要的部分,它詳細介紹了寄存器每一個位的功能。例如本寄存器中有兩種寄存器位,分別為
    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 是相反的操作

    <>封裝總線和外設基地址

    在編程上為了方便記憶,我們把總線基地址和外設基地址都以相應的宏定義起來,總線或者外設都以它們的名字作為宏名

    首先定義了 “片上外設”基地址 PERIPH_BASE,接著在 PERIPH_BASE 上
    加入各個 總線 的地址 偏移, 得到 APB1、 APB2 總線 的地址 APB1PERIPH_BASE
    APB2PERIPH_BASE,在其之上加入外設地址的偏移,得到 GPIOA-G
    的外設地址,最后在外設地址上加入各寄存器的地址偏移,得到特定寄存器的地址。一旦有了具體地址,就可以用指針讀寫。

    該代碼使用 (unsigned int ) 把 GPIOB_BSRR 宏的數值強制轉換成了地址,然后再用
    “”號做取指針操作,對該地址的賦值,從而實現了寫寄存器的功能。同樣,讀寄存器也是用取指針操作,把寄存器中的數據取到變量里,從而獲取 STM32 外設的狀態

    用上面的方法去定義地址,還是稍顯繁瑣,例如 GPIOA-GPIOE 都各有一組功能相同的寄存器,如
    GPIOA_ODR/GPIOB_ODR/GPIOC_ODR 等等,它們只是地址不一樣,但卻要為每個寄存器都定義它的地址。為了更方便地訪問寄存器,我們引入 C
    語言中的結構體語法對寄存器進行封裝,

    這段代碼用 typedef 關鍵字聲明了名為 GPIO_TypeDef 的結構體類型,結構體內有 7 個成員變量,變量名正好對應寄存器的名字。 C
    語言的語法規定,結構體內變量的存儲空間是連續的,其中 32 位的變量占用 4 個字節, 16 位的變量占用 2 個字節

    也就是說,我們定義的這個 GPIO_TypeDef , 假如這個結構體的首地址為 0x4001
    0C00( 這也是第一個成員變量 CRL 的地址) , 那么結構體中第二個成員變量 CRH 的地址即為 0x4001 0C00 +0x04 , 加上的這個
    0x04 ,正是代表 CRL 所占用的 4 個字節地址的偏移量,其它成員變量相對于結構體首地址的偏移

    最后,我們更進一步,直接使用宏定義好 GPIO_TypeDef 類型的指針,而且指針指向各個 GPIO 端口的首地址,使用時我們直接用該宏訪問寄存器即可

    這里只是以GPIO這個外設為例,講解了C語言對寄存器的封裝,以此類推,其他外設也可以用這種方法來封裝,不過現在這部分工作都由固件庫幫我們完
    成了,這里我們只是分析了下這個封裝的過程,讓大家知其然,也只其所以然
    本文主要參考了野火的資料

    技術
    下載桌面版
    GitHub
    百度網盤(提取碼:draw)
    Gitee
    云服務器優惠
    阿里云優惠券
    騰訊云優惠券
    華為云優惠券
    站點信息
    問題反饋
    郵箱:ixiaoyang8@qq.com
    QQ群:766591547
    關注微信
    巨胸美乳无码人妻视频