在WB32F103(ARM cortex m3內核,96Mhz)的gpio初始化中有一段代碼,充分的結合了硬件特征并使用C語言的技巧來快速的配置對應的GPIO的功能,堪稱經典和楷模,代碼異常簡潔,執行速度快,配置任意IO方便快捷。
我們先來看看這一段源代碼:
void GPIO_Init(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, uint32_t PinConfig)
{uint32_t tmp = PinConfig;/* Check the parameters */assert_param(IS_GPIO_ALL_PERIPH(GPIOx));assert_param(IS_GPIO_PIN(GPIO_Pin));GPIOx->CFGMSK = ~GPIO_Pin;GPIOx->MODER = ((tmp >> 28) & 0x3) * 0x55555555U;GPIOx->OTYPER = ((tmp >> 24) & 0x1) * 0xFFFFFFFFU;GPIOx->OSPEEDR = ((tmp >> 20) & 0x3) * 0x55555555U;GPIOx->PUPDR = ((tmp >> 16) & 0x3) * 0x55555555U;tmp = (tmp & 0xF) * 0x11111111U;GPIOx->AFRL = tmp;GPIOx->AFRH = tmp;
}
相應的調用方式如下:
GPIO_Init(GPIOB, GPIO_Pin_8|GPIO_Pin_9, GPIO_MODE_IN | GPIO_PUPD_UP|GPIO_SPEED_LOW);
一條簡單的函數調用代碼,就可以完成對同一組IO的多個相同功能IO的同時配置,代碼不可謂不經典和優雅,高效易讀易懂。
但是很多人在閱讀GPIO_Init函數的源代碼的時候,對里面的一些運算操作確感覺到懵懵懂懂,理解不了,搞不明白。下面我們結合該硬件的特征講解和說明一下(注意:不同的MCU的硬件寄存器會有不同的操作方式,需要緊密結合你使用的MCU的硬件寄存器的特征來理解),可以起到舉一反三和觸類旁通的作用:
要完成這個“一步到位”的操作,首先要對相關的宏進行合理的有技巧的定義,看看相關IO操作的宏定義如下:
/** @defgroup GPIO_MODE_define* @{*/
#define GPIO_MODE_IN 0x00000000U /*!< Input mode */
#define GPIO_MODE_OUT 0x10000000U /*!< Output mode */
#define GPIO_MODE_AF 0x20000000U /*!< Alternate function mode */
#define GPIO_MODE_ANA 0x30000000U /*!< Analog mode */
/*** @}*//** @defgroup GPIO_OTYPE_define* @{*/
#define GPIO_OTYPE_PP 0x00000000U /*!< Output push-pull */
#define GPIO_OTYPE_OD 0x01000000U /*!< Output open-drain */
/*** @}*//** @defgroup GPIO_SPEED_define* @{*/
#define GPIO_SPEED_LOW 0x00100000U /*!< Low speed */
#define GPIO_SPEED_HIGH 0x00000000U /*!< High speed */
/*** @}*//** @defgroup GPIO_PUPD_define* @{*/
#define GPIO_PUPD_NOPULL 0x00000000U /*!< No pull resistor */
#define GPIO_PUPD_UP 0x00010000U /*!< Pull up resistor enabled */
#define GPIO_PUPD_DOWN 0x00020000U /*!< Pull down resistor enabled */
/*** @}*//** @defgroup GPIO_Pin_sources * @{*/
#define GPIO_PinSource0 ((uint8_t)0x00)
#define GPIO_PinSource1 ((uint8_t)0x01)
#define GPIO_PinSource2 ((uint8_t)0x02)
#define GPIO_PinSource3 ((uint8_t)0x03)
#define GPIO_PinSource4 ((uint8_t)0x04)
#define GPIO_PinSource5 ((uint8_t)0x05)
#define GPIO_PinSource6 ((uint8_t)0x06)
#define GPIO_PinSource7 ((uint8_t)0x07)
#define GPIO_PinSource8 ((uint8_t)0x08)
#define GPIO_PinSource9 ((uint8_t)0x09)
#define GPIO_PinSource10 ((uint8_t)0x0A)
#define GPIO_PinSource11 ((uint8_t)0x0B)
#define GPIO_PinSource12 ((uint8_t)0x0C)
#define GPIO_PinSource13 ((uint8_t)0x0D)
#define GPIO_PinSource14 ((uint8_t)0x0E)
#define GPIO_PinSource15 ((uint8_t)0x0F)
/*** @}*/
宏里面分別定義了GPIO的pin引腳序號(GPIO_PinSource0-15),輸入輸入模式配置(輸入,輸出,特殊功能,模擬),輸出類型(推挽,開漏),IO速度(高速,低速),輸入電阻配置(無上下拉,上拉,下拉)。他們的定義bit位置是經過精心的安排和計算的(比如不同的功能定義占用的bit位置不重疊,方便進行移位運算,和對應的寄存器的操作有一一的對應關系),以便于后續代碼設計和簡化代碼的操作。好了,準備好這些原材料后,我們具體看看代碼的實現過程:
GPIOx->CFGMSK = ~GPIO_Pin;
這第一行代碼,非常關鍵,要明白它的作用,要對應的查看mcu的規格書,我們發現該mcu有一個操作gpio配置寄存器的特殊功能,其說明如下:
以上說明再翻譯一下:這一行代碼的作用,就是允許后續寫其他IO配置寄存器的時候,只對本次要配置的gpio的對應bit進行寫操作,不影響無需配置的其他bit(后面代碼解釋再說明)。
對應的寄存器定義如下:
好了,接下來看看第二行代碼的作用和操作技巧:
GPIOx->MODER = ((tmp >> 28) & 0x3) * 0x55555555U;
該代碼操作的對象是端口模式寄存器,對應的寄存器功能如上圖(用一個32bit的寄存器來表示16個io的模式配置,每一個io的模式配置位占2bit,并且按照順序排列).
#define GPIO_MODE_IN 0x00000000U /*!< Input mode */
#define GPIO_MODE_OUT 0x10000000U /*!< Output mode */
#define GPIO_MODE_AF 0x20000000U /*!< Alternate function mode */
#define GPIO_MODE_ANA 0x30000000U /*!< Analog mode */
結合前面模式定義的宏來理解:
(tmp >> 28) & 0x3-— 該代碼的作用就是取到傳入的模式配置數據(模式配置定義在最高4個bit,所以先右移位28bit,然后與3,取出來其值),其結果可能的數據為:0,1,2,3,剛好和寄存器的2個bit的4種組合對應:
00 --對應輸入模式
01–對應輸出模式
10–復用功能模式
11–模擬模式
比較讓人疑惑或者難以理解就是后續這個乘以0x55555555U,要理解這個作用,我們把代碼換一種寫法來看看:
GPIOx->MODER = 0x55555555U * ((tmp >> 28) & 0x3);
0x55555555U用二進制來看看是什么樣子:0101 0101 0101 0101 0101 0101 0101 0101
我們結合寄存器的每兩個bit表示一個gpio的模式配置來看看,也就是對應于每一個gpio的配置位初始值為01,如果把這個01和前面的模式值(((tmp >> 28) & 0x3))進行運算,我們發現得到如下結果:
0101 0101 0101 0101 0101 0101 0101 0101: 和0相乘,結果為0000 0000 0000 0000 0000 0000 0000 0000 ,所有GPIO的配置bit為輸入模式(00)
0101 0101 0101 0101 0101 0101 0101 0101: 和1相乘,結果不變,所有GPIO的配置bit為輸出模式(01)
0101 0101 0101 0101 0101 0101 0101 0101: 和2相乘,相當于左移1位,結果為:1010 1010 1010 1010 1010 1010 1010 1010 所有GPIO的配置bit為復用功能模式(10)
0101 0101 0101 0101 0101 0101 0101 0101: 和3相乘,結果為:1111 1111 1111 1111 1111 1111 1111 1111 ,所有GPIO的配置bit為模擬模式(11)
這真是一個非常高效和簡潔,優雅的設計技巧(包括硬件和軟件)。
關鍵點來了,這個值的修改是對所有16個gpio進行同時操作的,如果我只是設置某一個gpio,會不會影響到其他gpio的配置呢?答案是肯定不會。
回到前面我們看看有一個關鍵寄存器GPIOx->CFGMSK,英文全稱應該是config mask,中文翻譯為配置輔助寄存器,直譯為配置屏蔽寄存器可能更容易理解一些。
GPIOx->CFGMSK = ~GPIO_Pin;通過前面這一條代碼的操作,屏蔽了不需要操作的gpio配置位(也就是說關閉了對無關gpio的bit寫的作用),比如你本次只是操作gpio0,這條代碼就會把對gpio1-15的操作屏蔽,以后寫其他配置寄存器(比如前面的MODER寄存器),就只有gpio0對應的bit起作用,其他bit不會影響原來的值。
接下來的其他幾條語句的作用類似,參考規格書就可以分析和看明白。
這再一次說明了一個道理,嵌入式開發,軟件和硬件要充分結合,才能設計高效的代碼。
根據硬件特征,設計合理和簡潔的操作代碼,也算是一種算法–一種針對硬件進行操作優化的算法。
文章為原創,歡迎轉載,請注明出處