開發(fā)環(huán)境:
MDK:Keil 5.30
開發(fā)板:GD32F207I-EVAL
MCU:GD32F207IK
上一章通過控制GPIO的高低電平實現(xiàn)了流水燈,但只是告訴了大家怎么做,如何實現(xiàn)流水燈,本文將深入剖析的GPIO流水燈的前生今世,深入研究流水燈的調(diào)用邏輯和數(shù)據(jù)結(jié)構(gòu)。
1 GPIO配置概述
前面一章大概講解GPIO的配置過程和核心的寄存器,當然啦,關(guān)于GPIO的寄存器遠不止我上一章列出來的,還有很多,具體請參看《GD32F20x_User_Manual》中GPIO相關(guān)的內(nèi)容吧。
根據(jù)前面實現(xiàn)的GPIO流水燈,本文將其歸納如下:
要想控制LED亮滅,就需要做以上三件事:使能時鐘,配置GPIO參數(shù),最后循環(huán)控制GPIO的高低電平就能實現(xiàn)流水燈的效果,GPIO的寄存器這里就不說了,更多詳細的寄存器描述看官方手冊就行,下面先來看看GD32的時鐘。
2 GD32的時鐘系統(tǒng)
2.1 GD32的系統(tǒng)架構(gòu)
GD32的系統(tǒng)架構(gòu)比51單片機強大很多了。首先我們看看GD32的系統(tǒng)架構(gòu)圖:
GD32F20x系列器件是基于Arm? Cortex?-M3處理器的32位通用微控制器。 Arm? Cortex?-M3處理器包括三條AHB總線分別稱為I-CODE總線、 D-Code總線和系統(tǒng)總線。
下面我們具體講解一下圖中幾個總線的知識:
① ICode 總線:該總線將 M3 內(nèi)核指令總線和閃存指令接口相連,指令的預取在該總線上面完成。
② DCode 總線:該總線將 M3 內(nèi)核的 DCode 總線與閃存存儲器的數(shù)據(jù)接口相連接,常量加載和調(diào)試訪問在該總線上面完成。
③ 系統(tǒng)總線:該總線連接 M3 內(nèi)核的系統(tǒng)總線到總線矩陣,總線矩陣協(xié)調(diào)內(nèi)核和 DMA 間訪問。
④ DMA 總線:該總線將 DMA 的 AHB 主控接口與總線矩陣相連,總線矩陣協(xié)調(diào) CPU 的DCode 和 DMA 到 SRAM,閃存和外設(shè)的訪問。
⑤ 總線矩陣:總線矩陣協(xié)調(diào)內(nèi)核系統(tǒng)總線和 DMA 主控總線之間的訪問仲裁,仲裁利用輪換算法。
⑥ AHB/APB 橋:這兩個橋在 AHB 和 2 個 APB 總線間提供同步連接,APB1 操作速度限于60MHz,APB2 操作速度全速。
對于系統(tǒng)架構(gòu)的知識,在剛開始學習 GD32 的時候只需要一個大概的了解,大致知道是個什么情況即可。
2.2 GD32時鐘架構(gòu)
時鐘是整個處理器運行的基礎(chǔ),時鐘信號推動處理器內(nèi)各個部分執(zhí)行相應(yīng)的指令。時鐘系統(tǒng)就是CPU的脈搏,決定CPU速率,像人的心跳一樣 只有有了心跳,人才能做其他的事情,而單片機有了時鐘,才能夠運行執(zhí)行指令,才能夠做其他的處理 (點燈,串口,ADC),時鐘的重要性不言而喻。
我們在學習51單片機時,其最小系統(tǒng)必有晶振電路,這塊電路就是單片機的時鐘來源,晶振的振蕩頻率直接影響單片機的處理速度。GD32相比51單片機就復雜得多,不僅是外設(shè)非常多,就連時鐘來源就有四個。但我們實際使用的時候只會用到有限的幾個外設(shè),使用任何外設(shè)都需要時鐘才能啟動,但并不是所有的外設(shè)都需要系統(tǒng)時鐘那么高的頻率,為了兼容不同速度的設(shè)備,有些高速,有些低速,如果都用高速時鐘,勢必造成浪費,而且,同一個電路,時鐘越快功耗越快,同時抗電磁干擾能力也就越弱,所以較為復雜的MCU都是采用多時鐘源的方法來解決這些問題,因此便有了GD32的時鐘系統(tǒng)和時鐘樹。
GD32三個不同的時鐘源可以用來驅(qū)動系統(tǒng)時鐘(CK_SYS):
● IRC8M晶振時鐘(高速內(nèi)部時鐘信號)
● HXTAL晶振時鐘(高速外部時鐘信號)
● PLL時鐘
GD32有兩個二級時鐘源:
● 40kHz的低速內(nèi)部IRC40K,它可以驅(qū)動獨立看門狗,還可選擇地通過程序選擇驅(qū)動RTC。 RTC用于從停機/待機模式下自動喚醒系統(tǒng)。
● 32.768kHz的低速外部晶振LXTAL,可選擇它用來驅(qū)動RTC。
每個時鐘源在不使用時都可以單獨被打開或關(guān)閉,這樣就可以優(yōu)化系統(tǒng)功耗。
2.3 GD32的時鐘系統(tǒng)
GD32 芯片為了實現(xiàn)低功耗,設(shè)計了一個功能完善但卻非常復雜的時鐘系統(tǒng)。普通的MCU 一般只要配置好 GPIO 的寄存器就可以使用了,但 GD32還有一個步驟,就是開啟外設(shè)時鐘。
在 GD32中,可分為五種時鐘源,為 IRC8M、HXTAL、IRC40K、LXTAL、PLL。從時鐘頻率來分可以分為高速時鐘源和低速時鐘源,其中 IRC8M, HXTAL以及 PLL 是高速時鐘,IRC40K和 LXTAL是低速時鐘。從來源可分為外部時鐘源和內(nèi)部時鐘源,外部時鐘源就是從外部通過接晶振的方式獲取時鐘源,其中 HXTAL和 LXTAL是外部時鐘源,其他的是內(nèi)部時鐘源。
下面我們看看 GD32 的 5 個時鐘源,我們講解順序是按圖中紅圈標示的順序:
①IRC8M是__高速內(nèi)部時鐘__,RC 振蕩器,頻率為 8MHz。
②HXTAL是__高速外部時鐘__,可接石英/陶瓷諧振器,或者接外部時鐘源,頻率范圍為4MHz~32MHz。我們的開發(fā)板接的是 25M 的晶振。當使用有源晶振時,時鐘從 OSC_IN 引腳進入, OSC_OUT 引腳懸空,當選用無源晶振時,時鐘從 OSC_IN 和 OSC_OUT 進入,并且要配諧振電容。當確定 PLL 時鐘來源的時候, HXTAL可以不分頻或者 2 分頻,這個由時鐘配置寄存器 CFG0 的位 17。
③IRC40K是__低速內(nèi)部時鐘__,RC 振蕩器,頻率為 40kHz。獨立看門狗的時鐘源只能是 IRC40K,同時 IRC40K還可以作為 RTC 的時鐘源。
④LXTAL是__低速外部時鐘__,接頻率為 32.768kHz 的石英晶體。這個主要是 RTC 的時鐘源。
⑤PLL 為鎖相環(huán)倍頻輸出,其時鐘輸入源可選擇為 IRC8M、HXTAL。倍頻可選擇為2~32倍,但是其輸出頻率最大不得超過 120MHz。
圖中我們用 A~E 標示我們要講解的地方。
A. OUT是 GD32 的一個時鐘輸出IO,它可以選擇一個時鐘信號輸出, 可以選擇為 PLL 輸出的 2 分頻、IRC8M、HXTAL、或者系統(tǒng)時鐘。這個時鐘可以用來給外部其他系統(tǒng)提供時鐘源。
B. 這里是 RTC 時鐘源,從圖上可以看出,RTC 的時鐘源可以選擇 IRC40K,以及HXTAL的 128 分頻。
C. 從圖中可以看出 C 處 USB 的時鐘是來自 PLL 時鐘源。 GD32 中有一個全速功能的 USB 模塊,其串行接口引擎需要一個頻率為 48MHz 的時鐘源。該時鐘源只能從 PLL 輸出端獲取,可以選擇為 1/1.5/2/2.5 分頻。
D. D 處就是 GD32 的系統(tǒng)時鐘 SYSCLK,它是供 GD32 中絕大部分部件工作的時鐘源。系統(tǒng)時鐘可選擇為 PLL 輸出、 IRC8M或者 HXTAL。系統(tǒng)時鐘最大頻率為 120MHz,當然你也可以超頻,不過一般情況為了系統(tǒng)穩(wěn)定性是沒有必要冒風險去超頻的。
E. 這里的 E 處是指其他所有外設(shè)了。從時鐘圖上可以看出,其他所有外設(shè)的時鐘最終來源都是 SYSCLK。SYSCLK 通過 AHB 分頻器分頻后送給各模塊使用。這些模塊包括:
①AHB 總線、內(nèi)核、內(nèi)存和 DMA 使用的 HCLK 時鐘。
②通過 8 分頻后送給 Cortex 的系統(tǒng)定時器時鐘,也就是 systick 了。
③直接送給 Cortex 的空閑運行時鐘 FCLK。
④送給 APB1 分頻器。APB1 分頻器輸出一路供 APB1 外設(shè)使用(PCLK1,最大頻率 60MHz),另一路送給定時器(Timer)使用。
⑤送給 APB2 分頻器。APB2 分頻器分頻輸出一路供APB2外設(shè)使用(PCLK2,最大頻率 120MHz),另一路送給定時器(Timer)使用。
其中需要理解的是 APB1 和 APB2 的區(qū)別, APB1 上面連接的是低速外設(shè),包括電源接口、備份接口、 CAN、 USB、 I2C0、 I2C1、 UART1、 UART2 等等, APB2 上面連接的是高速外設(shè)包括 UART0、 SPI0、 Timer0、 ADC0、 ADC1、所有普通 IO 口(PA~PG)、第二功能 IO 口等。
不同的總線有不同的頻率,不同的外設(shè)掛在不同的總線下,為了更適合初學者查閱,筆者把常用的外設(shè)與總線的對應(yīng)關(guān)系總結(jié)如下:
SystemInit()函數(shù)中設(shè)置的系統(tǒng)時鐘大?。?/p>
- SYSCLK(系統(tǒng)時鐘) =120MHz
- AHB 總線時鐘(使用 SYSCLK) =120MHz
- APB1 總線時鐘(PCLK1) =60MHz
- APB2 總線時鐘(PCLK2) =120MHz
- PLL 時鐘 =120MHz
值得注意的是,GD32F207系列有多個PLL,具體參看源碼。
2.4 GD32的時鐘配置剖析
既然時鐘搞清楚了,接下來回到上一章的配置時鐘的代碼:
/*enable the LED clock*/
rcu_periph_clock_enable(RCU_GPIOF );
rcu_periph_clock_enable就是配置時鐘的函數(shù),函數(shù)原型如下:
/*!
\\brief enable the peripherals clock
\\param[in] periph: RCU peripherals, refer to rcu_periph_enum
only one parameter can be selected which is shown as below:
\\arg RCU_GPIOx (x=A,B,C,D,E,F,G,H,I): GPIO ports clock
\\arg RCU_AF : alternate function clock
\\arg RCU_CRC: CRC clock
\\arg RCU_DMAx (x=0,1): DMA clock
\\arg RCU_ENET: ENET clock
\\arg RCU_ENETTX: ENETTX clock
\\arg RCU_ENETRX: ENETRX clock
\\arg RCU_USBFS: USBFS clock
\\arg RCU_EXMC: EXMC clock
\\arg RCU_TIMERx (x=0,1,2,3,4,5,6,7,8,9,10,11,12,13): TIMER clock
\\arg RCU_WWDGT: WWDGT clock
\\arg RCU_SPIx (x=0,1,2): SPI clock
\\arg RCU_USARTx (x=0,1,2,5): USART clock
\\arg RCU_UARTx (x=3,4,6,7): UART clock
\\arg RCU_I2Cx (x=0,1,2): I2C clock
\\arg RCU_CANx (x=0,1): CAN clock
\\arg RCU_PMU: PMU clock
\\arg RCU_DAC: DAC clock
\\arg RCU_RTC: RTC clock
\\arg RCU_ADCx (x=0,1,2): ADC clock
\\arg RCU_SDIO: SDIO clock
\\arg RCU_BKPI: BKP interface clock
\\arg RCU_TLI: TLI clock
\\arg RCU_DCI: DCI clock
\\arg RCU_CAU: CAU clock
\\arg RCU_HAU: HAU clock
\\arg RCU_TRNG: TRNG clock
\\param[out] none
\\retval none
*/
void rcu_periph_clock_enable(rcu_periph_enum periph)
{
RCU_REG_VAL(periph) |= BIT(RCU_BIT_POS(periph));
}
整個函數(shù)就一個參數(shù),其參數(shù)就是具體的外設(shè)時鐘,整個函數(shù)很簡單,就是打開具體的外設(shè)時鐘。
參數(shù)periph傳入值是通過宏來定義的,這樣的好處也是便于移植,如果換了MCU,架構(gòu)一樣,只需要就該底層驅(qū)動就行,不需要更改上層應(yīng)用,這樣就提高了開發(fā)效率。言歸正傳,我們傳入的RCU_GPIOC定義如下。
RCU_GPIOF是一個枚舉類型。我們繼續(xù)追溯以上的宏。
/* constants definitions */
/* define the peripheral clock enable bit position and its register index offset */
#define RCU_REGIDX_BIT(regidx, bitpos) (((uint32_t)(regidx) << 6) | (uint32_t)(bitpos))
#define RCU_REG_VAL(periph) (REG32(RCU + ((uint32_t)(periph) >> 6)))
#define RCU_BIT_POS(val) ((uint32_t)(val) & 0x1FU)
#define APB2EN_REG_OFFSET 0x18U /*!< APB2 enable register offset */
#define BIT(x) ((uint32_t)((uint32_t)0x01U<<(x)))
/* RCU definitions */
#define RCU RCU_BASE
#define RCU_BASE (AHB1_BUS_BASE + 0x00009000U) /*!< RCU base address */
#define AHB1_BUS_BASE ((uint32_t)0x40018000U) /*!< ahb1 base address */
以上宏定義就是整個時鐘初始化相關(guān)的宏定義了,將其帶入函數(shù)中。RCU的基地址就是0x40018000+0x9000。可以從GD32參考手冊中獲取。
AHB1總線的基地址是0x40018000。
RCU偏移是0x9000。
RCU_REG_VAL(RCU_GPIOF)最終的結(jié)果是0x40021018。
宏定義BIT就是獲取GPIO具體的使能位。
BIT(RCU_BIT_POS(RCU_GPIOF))最終的結(jié)果就是0x64。
最終的函數(shù)替換后如下:
0x40021018 |= 0x64;
都是宏定義直接替換就行,還是比較簡單。
這里需要注意RCU_REGIDX_BIT宏定義。
#define RCU_REGIDX_BIT(regidx, bitpos) (((uint32_t)(regidx) << 6) | (uint32_t)(bitpos))
該宏定義就是將要配置的寄存器偏移和bit位綁定在一起,然后再通過以下宏定義分開偏移和bit位。
#define RCU_REG_VAL(periph) (REG32(RCU + ((uint32_t)(periph) >> 6)))
#define RCU_BIT_POS(val) ((uint32_t)(val) & 0x1FU)
RCU的APB2使能寄存器如下:
這里配置GPIOF的時鐘,需要將第7位置1,因此轉(zhuǎn)換成10進制就是64,和代碼就匹配起來了。
GD32的固件庫和STM32的固件庫還是有一些差別的,但是不管如何,最終都是配置的寄存器,只是STM32通過結(jié)構(gòu)體對外設(shè)進行了封裝,GD32是通過宏定義直接替換,偏向于直接操作寄存器。
3 GD32的地址映射
我們先看看51 單片機中是怎么做的,51 單片機開發(fā)中會引用一個 reg51.h 的頭文件,51單片機是通過以下方式將名字和寄存器聯(lián)系起來的:
sfr P0 =0x80;
sfr 也是一種擴充數(shù)據(jù)類型,占用一個內(nèi)存單元,值域為 0~255。利用它可以訪問 51 單片機內(nèi)部的所有特殊功能寄存器。如用 sfr P1 = 0x90 這一句定義 P1 為 P1 端口在片內(nèi)的寄存器。然后我們往地址為 0x80 的寄存器設(shè)值的方法是: P0=value;通過改變value的值來控制單片機。
所謂地址映射,就是將芯片上的存儲器甚至 I/O 等資源與地址建立一一對應(yīng)的關(guān)系。如果某地址對應(yīng)著某寄存器,我們就可以運用 C 語言的指針來尋址并修改這個地址上的內(nèi)容,從而實現(xiàn)修改該寄存器的內(nèi)容。Cortex-M的地址映射也是類似的。Cortex-M有 32 根地址線,所以它的尋址空間大小為 2 32 bit=4 GB。ARM 公司設(shè)計時,預先把這 4 GB 的尋址空間大致地分配好了。它把從 0x40000000 至 0x5FFFFFFF( 512 MB)的地址分配給片上外設(shè)。通過把片上外設(shè)的寄存器映射到這個地址區(qū),就可以簡單地以訪問內(nèi)存的方式,訪問這些外設(shè)的寄存器,從而控制外設(shè)的工作。這樣,片上外設(shè)可以使用 C 語言來操作。
gd32f10x.h 這個文件中重要的內(nèi)容就是把 GD32 的所有寄存器進行地址映射。如同51 單片機的 頭文件一樣,gd32f10x.h 像一個大表格,我們在使用的時候就是通過宏定義進行類似查表的操作,但是這樣操作會很麻煩,而且32位的MCU寄存器很多,非常不方便。于是就有了現(xiàn)在的固件庫。
在這里我們以流水燈中的 GPIOF為例進行剖析,如果是其他的 IO 端口,則改成相應(yīng)的地址即可。在這個文件中一系列宏實現(xiàn)了地址映射。
#define APB2_BUS_BASE ((uint32_t)0x40010000U) /*!< apb2 base address */
#define GPIO_BASE (APB2_BUS_BASE + 0x00000800U) /*!< GPIO base address */
這幾個宏定義是從文件中的幾個部分抽離出來的,具體的內(nèi)容讀者可參考gd32f10x.h 源碼。
宏APB2_BUS_BASE指向的地址為 0x40010000。這個 APB2_BUS_BASE宏是什么地址呢?GD32 不同的外設(shè)是掛載在不同的總線上的。GD32 芯片有 AHB 總線、APB2總線和 APB1 總線,掛載在這些總線上的外設(shè)有特定的地址范圍。其中像 GPIO、串口 1、ADC 及部分定時器是掛載在稱為 APB2 的總線上,掛載到APB2 總 線上的外設(shè)地址空間是從0x40010000 至 0x40017FFF地址。這里的第一個地址,也就是 0x40010000,稱為 APB2_BUS_BASE(APB2 總線外設(shè)基地址)。
而 APB2 總線基地址相對于外設(shè)基地址的偏移量為 0x10000 個地址,即為 APB2 相對外設(shè)基地址的偏移地址。
最后到了宏 GPIO_BASE,宏展開為 APB2_BUS_BASE加上偏移量 0x1400得到了 GPIO端口的寄存器組的基地址。
在gd32f20x_gpio.h 文件,我們還可以發(fā)現(xiàn)有關(guān)各個 GPIO 基地址的宏。
/* GPIOx(x=A,B,C,D,E,F,G,H,I) definitions */
#define GPIOA (GPIO_BASE + 0x00000000U) /*!< GPIOA bsae address */
#define GPIOB (GPIO_BASE + 0x00000400U) /*!< GPIOB bsae address */
#define GPIOC (GPIO_BASE + 0x00000800U) /*!< GPIOC bsae address */
#define GPIOD (GPIO_BASE + 0x00000C00U) /*!< GPIOD bsae address */
#define GPIOE (GPIO_BASE + 0x00001000U) /*!< GPIOE bsae address */
#define GPIOF (GPIO_BASE + 0x00001400U) /*!< GPIOF bsae address */
#define GPIOG (GPIO_BASE + 0x00001800U) /*!< GPIOG bsae address */
#define GPIOH (GPIO_BASE + 0x00006C00U) /*!< GPIOH bsae address */
#define GPIOI (GPIO_BASE + 0x00007000U) /*!< GPIOI bsae address */
除了 GPIOF寄存器組的地址,還有 GPIOA、GPIOB等地址,并且這些地址是不一樣的。前面提到,每組 GPIO 都對應(yīng)著獨立的一組寄存器,查看 GD32 的數(shù)據(jù)手冊。
注意到這個說明中有一個偏移地址:0x1400,這里的偏移地址是相對哪個地址的偏移呢?下面進行舉例說明。
4 固件庫對寄存器的封裝
GD的工程師用結(jié)構(gòu)體的形式封裝了寄存器組,在gd32f20x_gpio.h文件定義的。
/* GPIOx(x=A,B,C,D,E,F,G,H,I) definitions */
#define GPIOA (GPIO_BASE + 0x00000000U) /*!< GPIOA bsae address */
#define GPIOB (GPIO_BASE + 0x00000400U) /*!< GPIOB bsae address */
#define GPIOC (GPIO_BASE + 0x00000800U) /*!< GPIOC bsae address */
#define GPIOD (GPIO_BASE + 0x00000C00U) /*!< GPIOD bsae address */
#define GPIOE (GPIO_BASE + 0x00001000U) /*!< GPIOE bsae address */
#define GPIOF (GPIO_BASE + 0x00001400U) /*!< GPIOF bsae address */
#define GPIOG (GPIO_BASE + 0x00001800U) /*!< GPIOG bsae address */
#define GPIOH (GPIO_BASE + 0x00006C00U) /*!< GPIOH bsae address */
#define GPIOI (GPIO_BASE + 0x00007000U) /*!< GPIOI bsae address */
有了這些宏,我們就可以定位到具體的寄存器地址,gd32f10x_gpio.h 文件中定義了以下類型的宏定義。
/* GPIO registers definitions */
#define GPIO_CTL0(gpiox) REG32((gpiox) + 0x00000000U) /*!< GPIO port control register 0 */
#define GPIO_CTL1(gpiox) REG32((gpiox) + 0x00000004U) /*!< GPIO port control register 1 */
#define GPIO_ISTAT(gpiox) REG32((gpiox) + 0x00000008U) /*!< GPIO port input status register */
#define GPIO_OCTL(gpiox) REG32((gpiox) + 0x0000000CU) /*!< GPIO port output control register */
#define GPIO_BOP(gpiox) REG32((gpiox) + 0x00000010U) /*!< GPIO port bit operation register */
#define GPIO_BC(gpiox) REG32((gpiox) + 0x00000014U) /*!< GPIO bit clear register */
#define GPIO_LOCK(gpiox) REG32((gpiox) + 0x00000018U) /*!< GPIO port configuration lock register */
這里定義了 7 個宏定義,兩個宏之間是4 個字節(jié)地址的偏移量。
0x010偏移量正是 GPIOx_BOP寄存器相對于所在寄存器組的偏移地址。
通過類似的方式,我們就可以給具體的寄存器寫上適當?shù)膮?shù)以控制 GD32 了。
這樣我們就可以通過庫函數(shù)實現(xiàn)了GPIO的初始化了。
/*!
\\brief GPIO parameter initialization
\\param[in] gpio_periph: GPIOx(x = A,B,C,D,E,F,G,H,I)
\\param[in] mode: gpio pin mode
only one parameter can be selected which is shown as below:
\\arg GPIO_MODE_AIN: analog input mode
\\arg GPIO_MODE_IN_FLOATING: floating input mode
\\arg GPIO_MODE_IPD: pull-down input mode
\\arg GPIO_MODE_IPU: pull-up input mode
\\arg GPIO_MODE_OUT_OD: GPIO output with open-drain
\\arg GPIO_MODE_OUT_PP: GPIO output with push-pull
\\arg GPIO_MODE_AF_OD: AFIO output with open-drain
\\arg GPIO_MODE_AF_PP: AFIO output with push-pull
\\param[in] speed: gpio output max speed value
only one parameter can be selected which is shown as below:
\\arg GPIO_OSPEED_10MHZ: output max speed 10MHz
\\arg GPIO_OSPEED_2MHZ: output max speed 2MHz
\\arg GPIO_OSPEED_50MHZ: output max speed 50MHz
\\param[in] pin: GPIO pin
one or more parameters can be selected which are shown as below:
\\arg GPIO_PIN_x(x=0..15), GPIO_PIN_ALL
\\param[out] none
\\retval none
*/
void gpio_init(uint32_t gpio_periph, uint32_t mode, uint32_t speed, uint32_t pin)
{
uint16_t i;
uint32_t temp_mode = 0U;
uint32_t reg = 0U;
/* GPIO mode configuration */
temp_mode = (uint32_t)(mode & ((uint32_t)0x0FU));
/* GPIO speed configuration */
if(((uint32_t)0x00U) != ((uint32_t)mode & ((uint32_t)0x10U))) {
/* output mode max speed: 10MHz, 2MHz, 50MHz */
temp_mode |= (uint32_t)speed;
}
/* configure the eight low port pins with GPIO_CTL0 */
for(i = 0U; i < 8U; i++) {
if((1U << i) & pin) {
reg = GPIO_CTL0(gpio_periph);
/* clear the specified pin mode bits */
reg &= ~GPIO_MODE_MASK(i);
/* set the specified pin mode bits */
reg |= GPIO_MODE_SET(i, temp_mode);
/* set IPD or IPU */
if(GPIO_MODE_IPD == mode) {
/* reset the corresponding OCTL bit */
GPIO_BC(gpio_periph) = (uint32_t)((1U << i) & pin);
} else {
/* set the corresponding OCTL bit */
if(GPIO_MODE_IPU == mode) {
GPIO_BOP(gpio_periph) = (uint32_t)((1U << i) & pin);
}
}
/* set GPIO_CTL0 register */
GPIO_CTL0(gpio_periph) = reg;
}
}
/* configure the eight high port pins with GPIO_CTL1 */
for(i = 8U; i < 16U; i++) {
if((1U << i) & pin) {
reg = GPIO_CTL1(gpio_periph);
/* clear the specified pin mode bits */
reg &= ~GPIO_MODE_MASK(i - 8U);
/* set the specified pin mode bits */
reg |= GPIO_MODE_SET(i - 8U, temp_mode);
/* set IPD or IPU */
if(GPIO_MODE_IPD == mode) {
/* reset the corresponding OCTL bit */
GPIO_BC(gpio_periph) = (uint32_t)((1U << i) & pin);
} else {
/* set the corresponding OCTL bit */
if(GPIO_MODE_IPU == mode) {
GPIO_BOP(gpio_periph) = (uint32_t)((1U << i) & pin);
}
}
/* set GPIO_CTL1 register */
GPIO_CTL1(gpio_periph) = reg;
}
}
}
然后再main函數(shù)中調(diào)用gpio\\_init\\(\\)函數(shù)接口對GPIO初始化了。
通過對時鐘和GPIO的分析,我想大家已經(jīng)對固件的邏輯有了一定的認識,從本質(zhì)上講,都是在配置寄存器,只是地址和值不同罷了,而固件庫就是對寄存器配置的封裝,便于開發(fā)者調(diào)用。
值得注意的是,GD32的固件庫并沒有使用結(jié)構(gòu)體來對寄存器組進行封裝,全程用的宏定義,這點和STM32有很大的不同。
-
mcu
+關(guān)注
關(guān)注
146文章
17148瀏覽量
351197 -
流水燈
+關(guān)注
關(guān)注
21文章
433瀏覽量
59712 -
開發(fā)板
+關(guān)注
關(guān)注
25文章
5050瀏覽量
97471 -
GPIO
+關(guān)注
關(guān)注
16文章
1204瀏覽量
52098 -
GD32
+關(guān)注
關(guān)注
7文章
403瀏覽量
24351
發(fā)布評論請先 登錄
相關(guān)推薦
評論