一、前言
? ? ? ? 學習STM32一陣子以后,相信大家對STM32 GPIO的控制也有一定的了解了。之前在STM32 LED的教程中也教了大家如何使用寄存器以及庫函數控制STM32的引腳從而點亮一個LED,之前的寄存器只是作為一個引入,并沒有深層次的講解,在教程中,也沒讓大家有太多的了解。既然學習STM32有一段時間了,那我們能不能從現象推導到本質,去了解一下STM32 引腳控制的本質原理。那么本次教程,就來為大家講解一下STM32引腳驅動的三個關鍵寄存器BSRR、BRR、ODR,相信看了本次教程以后,你對STM32會有更深層次的理解。如果你準備好了,那就讓我們開始吧!
二、誰適合本次教程
? ? ? ? 因為已經涉及到寄存器的操作與講解了,所以請學習本次教程的小伙伴需要具備一定的STM32基礎以及對十六進制轉二進制比較熟悉。
三、資料的準備
? ? ? ? 本次使用STM32F103C8T6進行演示,所以請大家自己準備一塊STM32F103的開發板。本次教程中,我們需要去翻閱STM32F103的手冊,所以大家準備一份STM32F103的數據手冊,相信學習過一陣子STM32的小伙伴應該都有這個手冊吧,這里我就不放了。
四、BSRR、BRR、ODR寄存器講解
? ? ? ? 在我們日常的代碼或者一些模塊的代碼中,可能經常會看到下面這樣的寫法:
GPIOC->BSRR=0x00002000;
又或者是這種寫法:
GPIOC->ODR=0x00002000;
我們可以很明顯的看到,這些寫法都是在控制GPIO的電平,那么這些寫法之間都有什么區別呢?哪種寫法更好呢?
1.BSRR
? ? ? ? 這里我們首先來看BSRR,這里我們打開STM32F10的中文手冊,找到BSRR寄存器處:
下面我們就來解釋一下這個寄存器的作用,很明顯的看到,這里寄存器的位下面只有一個W,表示這個寄存器只是可寫的:
這里大家只需要記住這個寄存器只能寫就可以了,后面會為大家講解為什么。
我們再往下看,下面的寄存器詳細描述中,將寄存器分為了兩個部分來講,分別是0-15位,以及16-31位,總的來說就是將這個寄存器分為了高十六位和低十六位:
這里我們先來看位16-31,這些位都被叫做BR位,這里的R即Reset,手冊中的描述是這些位是用于清除端口0-15:
簡單來說,16-31位就是用于將GPIO口拉低的。假如這里我想控制GPIOC,那么我就可以使用BSRR中的16-31位將GPIPC的0-16引腳全部拉低。
在下面也提到了,這些位只能寫入并且只能一十六位的形式寫入:
我們繼續往下看,如果BSRR的16-31位寫0不會對ODR產生影響,如果寫1,則ODR的對應位就會為0,從而引腳電平被拉低。至于這里為什么我們寫ODR的位就可以直接控制引腳我們后面講解ODR寄存器的時候會講。
然后我們來看BSRR的0-15位,這些位從手冊中可以看到,是用于置位GPIO口的某一位的,0-15位被叫做BS位,這里的S即Set:
同樣的,假如我還是控制GPIOC,那我就可以通過BSRR下的0-15位將GPIOC的0-15引腳都置高。同樣的,下面也提到了,如果給0-15位的某一位置0,則ODR對應的位不產生影響。如果給0-15位置1那么對應的ODR位就會置1從而對應的引腳置1:
這里大家就可能有疑惑了,如果我將一個引腳對應BS位和BR位都置位,會怎么樣?其實在手冊中已經提到了,如果這樣做的話,只有BR位會起作用:
看了上面的內容,相信大家對BSRR寄存器有一定的了解了,簡單來說,它就是一個控制ODR寄存器對應位高低電平從而控制引腳的一個寄存器,在官方的庫函數中,也使用到了BSRR寄存器來控制引腳電平:
這里大家可能又有疑問了,為什么我這里要通過BSRR來控制ODR從而來控制引腳的電平,我不能直接控制ODR嗎?這個問題,同樣也留到我們講解ODR的時候再做講解。
2.BRR
? ? ? ? BRR的功能和BSRR非常接近,以至于現在一些高端的芯片已經閹割了BRR寄存器,不過我們現在還是可以來看看,我們在手冊中找到BRR對應的描述:
我們可以看到BRR寄存器的高十六位是沒有作用的:
并且低十六位和BSRR一樣,只能寫:
這里我們直接看寄存器描述,這里提到了,0-15位主要用于清除端口的位,也就是為指定端口拉低。這里的用法其實和BSRR的高十六位一樣,都是通過給對應的位置1從而給ORD對應的位置0從而控制引腳電平。如果你理解BSRR的話,理解BRR也不是什么難題,這里就不多說了:
3.ODR
? ? ? ? 現在我們來講解ODR,前面已經為大家留了許多問題在ODR這里了,現在我們一一來解決。
我們同樣先在手冊中找到ODR所在的位置:
這里我們可以看到,ODR的高十六位同樣作為保留位。然后就是低十六位,這里的低十六位我們簡單來說就是,對應了GPIO的十六個引腳。我們給ODR對應的位置高或者置低,那么對應的GPIO引腳就會被置對應的電平。假如我么就將ODR的第12位置1,那么對應的GPIO12就會被置1。在手冊中也提到了,我們的BSRR寄存器可以對每個ODR位進行獨立的設置和清除:
這就是為什么我們要通過BSRR來操作ODR從而來操作GPIO。因為BSRR可以對ODR的每一位進行操作并且不影響別的位。如果我們直接操作ODR的話,要實現不影響別的位的效果就需要將ODR的值先讀出來然后再寫入對應的值最后整體寫入ODR,這也是我們常用的讀-改-寫的操作流程。那么大家可能又有疑問了,為什么我們的BSRR可以直接操作ODR。
這里我們在手冊中,找到“8.1.8輸出配置”,這里我們主要是需要下面的圖:
我們將下面的圖單獨拿出來:
這里我們可以看到,我們的寫入可以寫到“位設置/清除寄存器”,這個“位設置/清除寄存器”是什么?這不就是BSRR嗎?:
然后,我們寫入BSRR以后,BSRR直接就寫入了一個名為“輸出數據寄存器”的地方,這個輸出數據寄存器,不就是我們的ODR嗎?:
相當于,BSRR一旦收到數據,就會發送給ODR,從而對ODR的位進行操作。BSRR發送完數據以后,就直接將內部存儲的值扔掉了,本身就不保存值,所以讀取BSRR本很就沒有意義。這樣也印證了為什么之前我們說BSRR不能讀取。
最后,我們可以看到,我們的ODR上有一條單獨的線,是用于讀寫ODR的,這些表示ODR可以被直接讀寫:
其實,總的來說,為什么我們不直接操作ODR呢?因為我們操作BSRR可以直接操作ODR的值從而不影響別的引腳。為什么我們操作BSRR可以直接操作ODR呢?因為它們在物理總線層面被鏈接在了一起,并且操作BSRR可以直接操作ODR的位。為什么BSRR不能讀取呢?因為BSRR有了值以后直接就拿給ODR了,本身不存儲值,沒有讀取的必要。
假如我們執行下面兩段代碼,在實際效果上應該是一樣的:
#include "stm32f10x.h"
int main(void)
{RCC->APB2ENR=0x00000010;GPIOC->CRH=0x00300000;GPIOC->BSRR=0x00002000;while(1){}
}
#include "stm32f10x.h"
int main(void)
{RCC->APB2ENR=0x00000010;GPIOC->CRH=0x00300000;GPIOC->ODR=0x00002000;while(1){}
}
但是,最后的最后,仍然建議大家在操作引腳時不直接操作ODR,雖然我們可以通過讀-改-寫的方法保留原本的值并且寫入新值,但是,這樣會增加我們的代碼量并且增加出錯的概率。
五、結語
? ? ? ? 以上就是我對GPIO相關的寄存器的一些見解,如果有講得不對的地方,還請大家指正,最后感謝大家的觀看!