??作者:一只大喵咪1201
??專欄:《理解ARM架構(gòu)》
??格言:你只管努力,剩下的交給時(shí)間!
??操作寄存器實(shí)現(xiàn)UART
??UART原理
UART的全稱是Universal Asynchronous Receiver and Transmitter,即異步發(fā)送和接收。
串口在嵌入式中用途非常的廣泛,主要的用途有:
- 打印調(diào)試信息;
- 外接各種模塊:GPS、藍(lán)牙;
串口因?yàn)榻Y(jié)構(gòu)簡單、穩(wěn)定可靠,廣受歡迎。
如上圖所示,串口通信只需要三根線,發(fā)送(TXD)、接收(RXD)、地線(GND)。
- 通信雙方的TXD與對方的RXD相連。
串口發(fā)送數(shù)據(jù)是以幀格式一幀一幀來發(fā)的,幀格式由1bit起始位,8或9bit數(shù)據(jù)位,1或1.5或2bit校驗(yàn)位,1bit停止位組成。
- 通常情況下都使用8bit數(shù)據(jù)位,不適用校驗(yàn)位,這樣的一幀數(shù)據(jù)有10個(gè)bit。
校驗(yàn)位又叫奇偶校驗(yàn)位,如果8個(gè)數(shù)據(jù)位加校驗(yàn)位中比特為位1的個(gè)數(shù)是奇數(shù),校驗(yàn)位就是1,否則就是0。
由于現(xiàn)在電子技術(shù)的逐漸成熟,串口通信很少出錯(cuò),所以校驗(yàn)位使用的不多。
如上圖所示是一幀數(shù)據(jù)傳送時(shí)的邏輯電平示意圖。
- 發(fā)送方將自己的TXD線從高電平拉到低電平,保持一段時(shí)間,接收方讀取到自己的RXD線由高到底以后就知道要接收數(shù)據(jù)了。
- 發(fā)送方按照自己發(fā)送的這個(gè)字節(jié),從低位開始,改變TXD線的電平,每改變一次保持一段時(shí)間,如此反復(fù)8次完成一字節(jié)數(shù)據(jù)的發(fā)送。
- 接收方在自己RXD線上的電平保持期間的中間時(shí)刻,根據(jù)電平狀態(tài)記錄該比特位的值,最后組合成一字節(jié)數(shù)據(jù)。
- 發(fā)送方將一字節(jié)數(shù)據(jù)發(fā)送完畢后,將自己的TXD線拉高方便下次發(fā)送數(shù)據(jù),接收方在接收到8bit數(shù)據(jù)以后,并且檢測到自己RXD線是高電平,就知道這一幀數(shù)據(jù)傳送完畢了。
上面描述數(shù)據(jù)發(fā)送過程中電平維持的時(shí)間,就是根據(jù)波特率來確定的,一般選波特率都會(huì)有9600,19200,115200等選項(xiàng)。
- 波特率:可以簡單理解為,串口通信過程中1秒鐘能發(fā)送的比特位個(gè)數(shù)。
- 波特率是通信雙方約定好的,一個(gè)按照這個(gè)速度發(fā)送數(shù)據(jù),另一個(gè)按照這個(gè)速度接收數(shù)據(jù)。
邏輯電平:
如上圖所示是本喵使用的ARM開發(fā)板串口發(fā)出的電平信號(hào),在xV至5V之間,就認(rèn)為是邏輯1,在0V至yV之間就為邏輯0,這叫做TTL/CMOS邏輯電平。
如上圖所示是RS-232邏輯電平,在-12V至-3V之間,就認(rèn)為是邏輯1,在+3V至+12V之間就為邏輯0,RS-232的電平比TTL/CMOS高,能傳輸更遠(yuǎn)的距離,在工業(yè)上用得比較多。
可以看到,RS-232與TTL/CMOS相同邏輯電平對應(yīng)的真實(shí)電壓正負(fù)是相反的。
如上圖所示,ARM芯片上的串口都是TTL電平的,通過板子上或者外接的電平轉(zhuǎn)換芯片,轉(zhuǎn)成RS232接口,連接到電腦的RS232串口上,實(shí)現(xiàn)兩者的數(shù)據(jù)傳輸。
如上圖所示,現(xiàn)在的電腦越來越少有RS232串口的接口,但USB是幾乎都有的。因此使用USB串口芯片將ARM芯片上的TTL電平轉(zhuǎn)換成USB串口協(xié)議,即可通過USB與電腦數(shù)據(jù)傳輸。
- 無論那種接口,板子上的芯片IO口輸出的都是TTL/CMOS電平,我們在寫程序時(shí)僅需要關(guān)心輸出的邏輯電平即可。
??編程
一款A(yù)RM芯片上會(huì)有多個(gè)USART串口,一般UART1用來輸出調(diào)試信息,這里本喵也使用USART1。
確定引腳:
如上圖,本喵使用的STMF103ZET6芯片上,USART1的USART1_RX、USART1_TX,接到了PA10、PA9。
將引腳配置為UART功能:
- 使能GPIOA/USART1模塊
如上圖是,RCC_APB2ENR
寄存器,GPIOA模塊、USART1模塊的使能都是在這一個(gè)寄存器里實(shí)現(xiàn)。
如上圖,從芯片手冊中查看Reset and clock control RCC
寄存器的基地址是0x40021000
,再根據(jù)RCC_APB2ENR
的偏移地址0x18
得到該寄存器的絕對地址是0x40021000 + 0x18
。
將該寄存器的bit2和bit14寫一,此時(shí)就使能了GPIOA和USART1模塊。
- 配置引腳功能
從上面的芯片原理圖可以知道,PA9、PA10有三種功能:GPIO、USART1、TIMER1,所以這里要將其配置為USAT1功能。
如上圖所示GPIOx_CRH
寄存器,該寄存器的絕對地址是0x40010800 + 0x04
,PA9配置為輸出,所以將MODE9
代表的bit4和bit5配置成01
,將CNF9
代表的bit6和bit7配置為10
。
PA10配置為輸入,將MODE10
代表的bit8和bit9配置為00
,再將CNF10
代表的bit10和bit11配置成01
。
由于這里僅使能了USART1,沒有使能定時(shí)器,所以PA9和PA10的默認(rèn)復(fù)用功能就是USART1,使用默認(rèn)值即可。
設(shè)置串口參數(shù):
- 設(shè)置波特率
如上圖所示是波特率的計(jì)算公式,USARTDIV由整數(shù)部分、小數(shù)部分組成,USARTDIV = DIV_Mantissa + (DIV_Fraction / 16)
。fck是內(nèi)部時(shí)鐘頻率,這里就使用默認(rèn)值,是8MHZ。
如上圖USART_BRR
寄存器,DIV_Mantissa表示整數(shù)部分,占用該寄存器的bit4~bit15
,DIV_Fraction表示小數(shù)部分,占用該寄存器的bit0~bit3
。
以常用的波特率115200為例,來計(jì)算該寄存器的值:
設(shè)置波特率
* 115200 = 8000000/16/USARTDIV
* USARTDIV = 4.34
* DIV_Mantissa = 4
* DIV_Fraction / 16 = 0.34
* DIV_Fraction = 16*0.34 = 5
所以給USART_BRR
寄存器的bit4~bit15
賦值4,bit0~bit3
賦值5,根據(jù)這兩個(gè)值再來倒推一下真實(shí)的波特率:
真實(shí)波特率:
* DIV_Fraction / 16 = 5/16=0.3125
* USARTDIV = DIV_Mantissa + DIV_Fraction / 16 = 4.3125
* baudrate = 8000000/16/4.3125 = 115942
可以看到,雖然和115200有點(diǎn)差距,但是并不影響。
- 設(shè)置數(shù)據(jù)格式
如上圖所示USART1_CR1
寄存器,本喵將幀格式設(shè)置為1個(gè)起始位,8個(gè)數(shù)據(jù)位,無校驗(yàn)位,1個(gè)停止位,所以將bit13
設(shè)置1,bit12
設(shè)置為0,bit10
設(shè)置為0,bit3
設(shè)置為1,bit2
設(shè)置為1。
但是此時(shí)并沒有設(shè)置幾個(gè)停止位,還需要設(shè)置另一個(gè)寄存器:
如上圖所示USART_CR2
寄存器,將bit12~bit13
設(shè)置為00,表示1個(gè)停止位。
根據(jù)狀態(tài)寄存器讀寫數(shù)據(jù):
如上圖所示串口模塊結(jié)構(gòu)圖,發(fā)送有一個(gè)發(fā)送數(shù)據(jù)寄存器和發(fā)送移位寄存器,接收有一個(gè)接收數(shù)據(jù)寄存器和接收移位寄存器。
發(fā)送數(shù)據(jù)時(shí),CPU將數(shù)據(jù)寫入到發(fā)送數(shù)據(jù)寄存器,然后由發(fā)送移位寄存器一位一位將數(shù)據(jù)通過TXD線發(fā)送出去。
接收數(shù)據(jù)時(shí),RXD線上的數(shù)據(jù)一位一位放入接收移位寄存器,該寄存器接收完畢后將整個(gè)字節(jié)數(shù)據(jù)放入到接收數(shù)據(jù)寄存器,CPU從接收數(shù)據(jù)寄存器中可以直接讀取數(shù)據(jù)。
- 狀態(tài)寄存器
如上圖所示USART_SR
狀態(tài)寄存器,TXE
表示發(fā)送數(shù)據(jù)寄存器是否為空,該位并不能說明數(shù)據(jù)已經(jīng)發(fā)送完了,因?yàn)檎嬲l(fā)送數(shù)據(jù)的是移位寄存器,只能說發(fā)送數(shù)據(jù)寄存器將數(shù)據(jù)給了移位寄存器,CPU可以再向數(shù)據(jù)寄存器中寫數(shù)據(jù)了。
TC
表示發(fā)送數(shù)據(jù)完成,即發(fā)送數(shù)據(jù)寄存器和移位寄存器中的數(shù)據(jù)都發(fā)送完畢了。RXNE
表示接收數(shù)據(jù)寄存器中有數(shù)據(jù)了,說明已經(jīng)接收到了數(shù)據(jù),CPU可以來讀取了。
- 數(shù)據(jù)寄存器
如上圖所示USART_DR
寄存器,寫、讀這個(gè)寄存器,就可以發(fā)送、讀取串口數(shù)據(jù)。
在配置完引腳和功能選擇以后,本喵在介紹USART_XXX
寄存器的時(shí)候并沒有說它的地址,因?yàn)闊o論是設(shè)置波特率的USART_BRR
,還是設(shè)置數(shù)據(jù)格式的USART_CR1
,再或者狀態(tài)寄存器USART_SR
,以及數(shù)據(jù)寄存器USART_DR
這些都是以USART
為基地址的。
如上圖所示USART1
的基地址是0x40013800
,上面本喵提到的這些寄存器都是在這個(gè)基地址的基礎(chǔ)上進(jìn)行偏移,也就是說它們都屬于USART1
模塊中的寄存器。
typedef unsigned int uint32_t;
typedef struct
{
volatile uint32_t SR; /*!< USART Status register, Address offset: 0x00 */
volatile uint32_t DR; /*!< USART Data register, Address offset: 0x04 */
volatile uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x08 */
volatile uint32_t CR1; /*!< USART Control register 1, Address offset: 0x0C */
volatile uint32_t CR2; /*!< USART Control register 2, Address offset: 0x10 */
volatile uint32_t CR3; /*!< USART Control register 3, Address offset: 0x14 */
volatile uint32_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x18 */
} USART_TypeDef;
如上面代碼所示,用一個(gè)結(jié)構(gòu)體來表示USART
模塊,里面的成員變量表示各個(gè)寄存器,讓它們在結(jié)構(gòu)體中的偏移量和寄存器相對于USART
模塊的偏移量相對應(yīng),此時(shí)就可以通過訪問這個(gè)結(jié)構(gòu)體訪問到各個(gè)寄存器。
如上圖所示是整個(gè)串口的初始化代碼,其中配置波特率等參數(shù)使用的是結(jié)構(gòu)體訪問的寄存器,串口結(jié)構(gòu)體是一個(gè)局部變量。
如上圖所示,定義發(fā)送一個(gè)字符和接收一個(gè)字符的函數(shù),通過判斷狀態(tài)寄存器的值,進(jìn)而讀寫DR
寄存器,也是通過結(jié)構(gòu)體訪問的寄存器,結(jié)構(gòu)體是一個(gè)局部變量。
如上圖,此時(shí)將程序燒錄到開發(fā)板以后,會(huì)通過串口發(fā)送Hello
字符串,在PC端發(fā)送一個(gè)字符,板子接收到以后返回該字符及下一個(gè)字符,此時(shí)我們的串口是配置好了。
問題:為什么每個(gè)函數(shù)中都得創(chuàng)建一個(gè)uart1結(jié)構(gòu)體局部變量,而不是創(chuàng)建全局變量供這些函數(shù)使用呢?
??段的概念
如上圖所示,增加三個(gè)函數(shù),用來打印字符串及變量的地址。
如上圖所示,創(chuàng)建四個(gè)全局變量,g_ConstChar
被const修飾,然后在mymain
中分別打印四個(gè)變量的地址及它們的值。
將程序編譯后燒錄到開發(fā)板中,通過串口工具來觀察輸出的內(nèi)容。
如上圖所示,來看這四個(gè)變量的地址,只有g_ConstChar
這個(gè)被const修飾的變量地址是位于Flash中的,其他幾個(gè)變量都是位于RAM中。
如上圖,keil中只能了Flash和RAM的起始地址,根據(jù)這兩個(gè)參數(shù)很容易判斷出這四個(gè)變量所處的位置。
如上圖,再來看輸出的這四個(gè)變量的值,可以看到,只有const修飾的g_ConstChar
變量輸出了B
,其他幾個(gè)變量都沒有輸出對應(yīng)的則,而是奇怪的東西。
- 其他變量輸出的奇怪值表明,這幾個(gè)變量地址處的值是亂碼。
g_ConstChar
變量位于Flash,也就是ROM,ROM是只讀的,不能寫,而其他三個(gè)變量位于RAM,RAM是可讀可寫的。
在編譯的時(shí)候,編譯器進(jìn)行了判斷處理,g_ConstChar
是只讀的,不會(huì)寫,所以把它放在Flash就可以。
- Flash上存放這種只讀數(shù)據(jù)的區(qū)域叫做只讀數(shù)據(jù)段。
其他三個(gè)變量會(huì)進(jìn)行讀和寫的操作,所以編譯器給了它們一個(gè)鏈接地址,這個(gè)地址對應(yīng)在RAM上,方便CPU進(jìn)行讀寫。
- RAM上存放這種可讀可寫全局變量的區(qū)域叫做可讀可寫數(shù)據(jù)段。
無論有沒有被const修飾的變量,它們都有初始值A
或者B
,這個(gè)兩個(gè)數(shù)值是不會(huì)變的,只是用來使用的,所以編譯器將這兩個(gè)值放在這兩個(gè)變量位于Flash上的地址處(加載地址)。
- 有幾個(gè)有初始值的全局變量,F(xiàn)lash中就會(huì)保存幾個(gè)初始值。
- Flash以及內(nèi)存中并沒有變量名,只會(huì)在變量的地址處直接存放數(shù)值。
像char g_A = 0
這種初始值為0的全局變量,以及char g_B
這種沒有初始值的全局變量,F(xiàn)lash上就沒有必要存放它們的初始值。
假設(shè)初始值為0的變量有一萬個(gè),F(xiàn)lash中難道要存放1萬個(gè)0嗎?肯定不會(huì)的,這樣浪費(fèi)內(nèi)存不說,還沒有任何意義。對于沒有初始值的全局變量Flash中更不會(huì)存放它的初始值了。
所以編譯器在編譯的時(shí)候,直接給這種初始值為0或者沒有初始值的全局變量分配一個(gè)鏈接地址,位于RAM中,CPU直接去鏈接地址讀寫就可以了。
- 這種存放初始值為0或者沒有初始值所在的RAM區(qū)域被叫做BSS段或者ZI段。
我們寫的代碼經(jīng)過編譯鏈接以后,會(huì)生成一個(gè)二進(jìn)制可執(zhí)行文件,里面全部都是機(jī)器碼,這部分代碼并不會(huì)改變,所以也存放到Flash上。
- 存放代碼的Flash區(qū)域被叫做代碼段。
至于棧以及堆本喵在前面的文章中就詳細(xì)講解過,這里就不再說了,有興趣的小伙伴可以移步單片機(jī)中的C語言。
所以,程序分為這幾個(gè)段:
- 代碼段(RO-CODE):就是程序本身,不會(huì)被修改
- 可讀可寫的數(shù)據(jù)段(RW-DATA):有初始值的全局變量、靜態(tài)變量,需要從ROM上復(fù)制到內(nèi)存
- 只讀的數(shù)據(jù)段(RO-DATA):可以放在ROM上,不需要復(fù)制到內(nèi)存
- BSS段或ZI段:
- 初始值為0的全局變量或靜態(tài)變量,沒必要放在ROM上,使用之前清零就可以
- 未初始化的全局變量或靜態(tài)變量,沒必要放在ROM上,使用之前清零就可以
- 局部變量:保存在棧中,運(yùn)行時(shí)生成
- 堆:一塊空閑空間,使用malloc函數(shù)來管理它,malloc函數(shù)可以自己寫
??IDE背后的命令
IDE指集成開發(fā)環(huán)境(Integrated Development Environment)。我們開發(fā)STM32F103等單片機(jī)程序時(shí)使用是keil5就是一種IDE。
使用IDE,很容易操作,點(diǎn)點(diǎn)鼠標(biāo)就可完成,添加文件,指定文件路徑(頭文件路徑、庫文件路徑),指定鏈接庫,編譯、鏈接,下載、調(diào)試等功能。
其實(shí)在我們點(diǎn)下某一個(gè)按鈕以后,IDE的背后會(huì)執(zhí)行一系列指令:
如上圖,在keil5的Output選擇中勾選Create Batch File
,然后重新全部編譯。
如上圖,此時(shí)在當(dāng)前工程的Objects
目錄下會(huì)多出上面紅色框中的四個(gè)文件。
如上圖所示分別是這幾個(gè)文件中的內(nèi)容,都是一系列的命令行指令,用來編譯和鏈接文件的指令,具體怎么用不用管,只需要知道有這些東西。
-
start._ia
中的命令行就是在讓start.s
匯編文件編譯成start.o
目標(biāo)文件。 -
main._i
中的命令行就是在讓main,c
源文件編譯成main.o
目標(biāo)文件。 -
uart._i
中的命令行就是在讓uart.c
源文件編譯成uart.o
目標(biāo)文件。 -
led.linp
中的命令行就是把這幾個(gè).o
目標(biāo)文件鏈接在一起形成一個(gè)二進(jìn)制可執(zhí)行文件led.axf
,我們燒錄的就是這個(gè)文件。
當(dāng)我們點(diǎn)下IDE上的編譯選項(xiàng)時(shí),IDE會(huì)自動(dòng)執(zhí)行上面四個(gè)文件中的內(nèi)容,最后生成我們需要的東西。
??總結(jié)
雖然配置串口已經(jīng)是一個(gè)老生常談的問題了,但是相信大家很少直接使用寄存器地址來配置吧,這個(gè)過程中可以加深對ARM架構(gòu)的理解。
串口配好后通過打印數(shù)據(jù)過程中出現(xiàn)的問題介紹了段的概念,編譯器不同類型的變量放在內(nèi)存中不同的位置。文章來源:http://www.zghlxwxcb.cn/news/detail-754619.html
要意識(shí)到,編譯一個(gè)工程的背后沒有那么簡單。文章來源地址http://www.zghlxwxcb.cn/news/detail-754619.html
到了這里,關(guān)于【理解ARM架構(gòu)】操作寄存器實(shí)現(xiàn)UART | 段的概念 | IDE背后的命令的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!