通用I/O
本文從兩個方面剖析GPIO:
- 結合《STM32F4xx中文參考手冊》,從硬件和寄存器的角度剖析GPIO
- 結合庫例程,從軟件角度剖析GPIO如何配置,同時剖析嵌入式代碼規範
閱讀完本文,要能回答以下問題:
- 簡述GPIO口的硬件架構與配置寄存器
- 配置GPIO口寄存器的代碼流程
- 然後就是結合配置GPIO的代碼回答一些嵌入式代碼規範的問題:
- 寄存器前面的類型修飾符的含義,例如__IO uint32_t ODR
- 爲啥要用volatile類型修飾符?
- 使用unsigned int與int聲明後續配置有什麼區別?
- #define與typedef有什麼區別?
- 爲什麼嵌入式代碼中有這麼多宏定義?
- GPIO定義的結構體中的這些配置寄存器變量是怎麼映射到實際地址的?
- 嵌入式代碼中常常需要進行位操作,結合配置GPIO的代碼,談談如何進行位操作?
- 嵌入式代碼中常常需要對多位進行位操作,結合配置GPIO的代碼,談談如何進行位的遍歷?
- M3M4內核通過位帶映射支持直接對位進行操作,談談你對位帶操作的認識?
- 嵌入式代碼進入帶參數的函數調用中去時,常常會先使用assert_param()函數,結合代碼談談==assert_param()==的作用
文章最後,列出了一些值得進一步研究的內容,例如GPIO如何做輸入使用,GPIO口中斷的相關概念,詳情閱讀文章《初探STM32F4(4)–GPIO(2)》。
GPIO概述
每組GPIO口包括4個32位的配置寄存器、2個32位的數據寄存器、1個32位的置位/復位寄存器、1個32位鎖定寄存器和2個32位複用功能選擇寄存器,一共10個寄存器。研究清楚這些寄存器有什麼用、以及如何配置,也就搞懂了GPIO。
GPIO硬件架構與寄存器
GPIO硬件架構
GPIO工作模式
GPIO有八種工作模式,四種輸入工作模式:
1.== 輸入浮空模式==。上拉/下拉電阻不起作用,外部電平直接輸入到芯片內部,適合電流要求小的場合。信號送至輸入數據寄存器
2.== 輸入上拉模式==。上拉電阻起作用。低電平輸入時對芯片外部有灌電流。信號送至輸入數據寄存器
3. 輸入下拉模式。下拉電阻起作用。高電平輸入時對芯片外部有拉電流。信號送至輸入數據寄存器
4. 模擬模式。上拉/下拉電阻不起作用,信號沿模擬輸入線輸入到芯片內部ADC
四種輸出工作模式:
- 開漏輸出模式。CPU通過置位/復位寄存器或直接寫操作,設置好輸出數據寄存器。輸出驅動器只有一個MOS管工作,當輸出1時,MOS管截止,通過外接上拉電阻實現輸出高電平。當輸出0時,MOS管導通,輸出低電平。
- 開漏複用輸出模式。輸出的數據來自CPU的片上覆用外設。
- 推輓式輸出。輸出驅動器的NMOS與PMOS管均工作,當輸出1時,PMOS導通,輸出高電平,當輸出0時,NMOS導通,輸出低電平。
- 推輓式複用輸出模式。輸出的數據來自CPU的片上覆用外設。
GPIO上電覆位後,默認工作在輸入浮空狀態。
GPIO相關寄存器
注意,每組GPIO口通過10個相關寄存器管理好,一組包括16個GPIO口,那麼,如果某個寄存器需要2位配置一個口,那剛好均分位,如果某個寄存器只需要1位配置一個口,那一般高16位保留。
4個配置寄存器
1、端口模式寄存器(GPIOx_MODER)
- 每兩個位控制一個IO口:00—輸入浮空模式(復位狀態);01—通用輸出模式;10—複用功能模式;11—模擬模式。不用記憶、會查DATASHEET看懂就行
2、端口輸出類型寄存器(GPIOx_OTYPER)
- 配置成推輓或開漏輸出,每1位就能控制一個IO口,所以高16位保留
3、端口輸出速度寄存器(GPIOx_OSPEEDR)
- 有四種輸出速度,每兩個位控制一個IO口
4、端口上拉下拉寄存器(GPIOx_PUPDR)
- 有三種情況(無上拉下拉、上拉、下拉),每兩個位控制一個IO口
2個數據寄存器
5、端口輸入數據寄存器(GPIOx_IDR)
- 每個GPIO的輸入有兩種情況,用一位來記錄,所以高16位保留,這些位是隻讀模式的。
6、端口輸出數據寄存器(GPIOx_ODR)
- 每個GPIO的輸出有兩種情況,用一位來記錄,所以高16位保留,這些位是可讀可寫的。
1個置位/復位寄存器
7、端口置位/復位寄存器(GPIOx_BSRR)
- 高16位爲復位位、低16位爲置位位。這些位爲只寫,0無影響,1有作用。
- ==已經有數據寄存器了,爲啥還要這個寄存器?==如果只有數據寄存器,每次設置某一個IO口的取值時,需要先進行讀操作,保證其他IO口不受影響,再設置這個IO口。有了置位/復位寄存器,無需進行讀操作了,把不用操作的IO口寫0就不會影響到那些IO口了。
1個鎖存寄存器
8、端口鎖存寄存器(GPIOx_LCKR)
- 用的不多,當IO口被鎖住時,無法執行CPU的寫操作了。
2個複用功能寄存器
9、複用功能低位寄存器(GPIOx_AFRL)
每4位控制一個IO口的複用功能,低位寄存器控制低8位的8個GPIO口的複用功能。一個GPIO口可以複用16個複用功能
10、複用功能高位寄存器(GPIOx_AFRH)
每4位控制一個IO口的複用功能,高位寄存器控制高8位的8個GPIO口的複用功能。
GPIO庫文件架構與代碼剖析
兩個層次,首先是寄存器版本例程,學習如何操作相應寄存器實現操控GPIO口。
寄存器版本
GPIO結構體定義
根據上文所講的,每組GPIO口對應的10個相關寄存器,定義了以下結構體:
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
看了上面的結構體定義,我產生了幾個疑問:
(1)數據類型_IO uint32_t是什麼聲明類型?
#define __IO volatile /*!< Defines 'read / write' permissions */
typedef unsigned int uint32_t;
_IO是volatile類型修飾符,uint32_t是unsigned int。
(2)爲啥要用volatile類型修飾符?
volatile的意思是告訴編譯器,在編譯源代碼時,對這個變量不要使用優化。具體看這篇文章.
比如寫這個io端口的時候,如果沒有這個volatile,很可能由於編譯器的優化,會先把值先寫到一個緩衝區,到一定時候
再寫到io端口,這樣就不能使數據及時的寫到io端口,有了volatile說明以後,就不會再經過cache,write buffer這種,而是直接寫到io端口,從而避免了讀寫io端口的延時
(3)使用unsigned int與int聲明的區別?
unsigned int佔四個字節,int是默認有符號的即signed int也佔4個字節。有符號無符號在使用上有區別,例如我要把寄存器ODR的32位全置1,十進制下,按照無符號類型我要賦值232-1,有符號類型我要賦值-231
(4)#define與typedef有什麼區別?
- 在編程中使用typedef目的一般有兩個,一個是給變量一個易記且意義明確的新名字,另一個是簡化一些比較複雜的類型聲明。
- #define 宏名 字符串 。其中的“#”表示這是一條預處理命令。凡是以“#”開頭的均爲預處理命令。“define”爲宏定義命令。“標識符”爲所定義的宏名。“字符串”可以是常數、表達式、格式串等。
- 關鍵字typedef在編譯階段有效,由於是在編譯階段,因此typedef有類型檢查的功能。
define則是宏定義,發生在預處理階段,也就是編譯之前,它只進行簡單而機械的字符串替換,而不進行任何檢查。具體看這篇文章.
(5)結構體中的這些變量是怎麼跟實際地址映射到一起的呢?
GPIO地址映射
使用上述聲明的結構體定義了11組GPIO
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)
#define GPIOI ((GPIO_TypeDef *) GPIOI_BASE)
#define GPIOJ ((GPIO_TypeDef *) GPIOJ_BASE)
#define GPIOK ((GPIO_TypeDef *) GPIOK_BASE)
這時候又有疑問了,GPIO的地址映射是什麼樣的?
每組GPIO的地址是用外設總線基地址+偏移量定義好的
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400)
#define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800)
#define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00)
#define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000)
#define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)
#define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800)
#define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00)
#define GPIOI_BASE (AHB1PERIPH_BASE + 0x2000)
#define GPIOJ_BASE (AHB1PERIPH_BASE + 0x2400)
#define GPIOK_BASE (AHB1PERIPH_BASE + 0x2800)
外設總線基地址是用外設基地址+偏移量定義好的
/*!< Peripheral memory map */
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
#define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000)
外設基地址是絕對地址了。
#define SRAM2_BASE ((uint32_t)0x2001C000) /*!< SRAM2(16 KB) base address in the alias region */
#define SRAM3_BASE ((uint32_t)0x20020000) /*!< SRAM3(64 KB) base address in the alias region */
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region
GPIO初始化(寄存器配置)
下面這些是sys.h裏面的代碼塊,也就是正點原子自己寫的,但位操作的思想值得學習一番。
GPIO初始化分爲三步:使能IO口時鐘、配置IO口寄存器、配置初始值。
void LED_Init(void)
{
RCC->AHB1ENR|=1<<1;//使能PORTB時鐘
GPIO_Set(GPIOB,PIN0|PIN1,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU); //PB0,PB1ÉèÖÃ
LED0=1;//LED0關閉
LED1=1;//LED1關閉
}
(1)位或操作|=的作用:進行位操作,讓指定的位置1。
(2)左移<<優先級比位或|=高,因此這裏是將AHB1ENR的第二位置1
void GPIO_Set(GPIO_TypeDef* GPIOx,u32 BITx,u32 MODE,u32 OTYPE,u32 OSPEED,u32 PUPD)
{
u32 pinpos=0,pos=0,curpin=0;
for(pinpos=0;pinpos<16;pinpos++)
{
pos=1<<pinpos; //一個個引腳遍歷
curpin=BITx&pos;//檢查該位是否需要設置
if(curpin==pos) //如果需要設置,開始設置
{
GPIOx->MODER&=~(3<<(pinpos*2)); //先清除原本的設置
GPIOx->MODER|=MODE<<(pinpos*2); //設置新的模式
if((MODE==0X01)||(MODE==0X02)) //如果是輸出模式/外設輸出模式
{
//設置輸出模式/外設輸出模式下需要配置的寄存器
GPIOx->OSPEEDR&=~(3<<(pinpos*2)); //清零
GPIOx->OSPEEDR|=(OSPEED<<(pinpos*2));//置位
GPIOx->OTYPER&=~(1<<pinpos) ; //清零
GPIOx->OTYPER|=OTYPE<<pinpos; //置位
}
GPIOx->PUPDR&=~(3<<(pinpos*2)); //清零
GPIOx->PUPDR|=PUPD<<(pinpos*2); //置位
}
}
}
GPIO_Set(GPIOB,PIN0|PIN1,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU);//配置兩個GPIO口
上面定義的函數配置了IO口所需的4個配置寄存器,配置IO口的規範操作是進行位操作。
- 接口參數:GPIO_TypeDef* GPIOx指定哪一組GPIO口,u32 BITx指定該組GPIO口配置哪些位,以及4個配置寄存器該配置好的值。
- 如何定義配置的GPIO口,以及如何遍歷IO口展開配置過程?
//GPIO口的引腳編號定義
#define PIN0 1<<0
#define PIN1 1<<1
#define PIN2 1<<2
#define PIN3 1<<3
#define PIN4 1<<4
#define PIN5 1<<5
#define PIN6 1<<6
#define PIN7 1<<7
#define PIN8 1<<8
#define PIN9 1<<9
#define PIN10 1<<10
#define PIN11 1<<11
#define PIN12 1<<12
#define PIN13 1<<13
#define PIN14 1<<14
#define PIN15 1<<15
定義好GPIO口的引腳編號標誌符,需要配置哪些位就把哪些位的標誌符進行位或操作。
遍歷的實現,是定義一個可變索引pos=1<<pinpos,當標誌符curpin=BITx&pos的值與索引pos相同時,鎖定到了要操作的位。這裏也可以發現&操作是儘可能讓位置0的。
- 我們常常需要對某些位復位,怎麼進行呢?
以GPIOx->MODER&=~(3<<(pinpos*2))爲例,由於MODER是兩位定義一個IO口的功能,所以操作的基本單元是二進制11,將11左移到需要設置的IO口的那些位<<(pinpos * 2)。然後取反再位與,保證需要設置的位清零,其它位保持不變。 - 置位就是進行位或操作。
- 其它寄存器同樣按照先清零(=取反再位與)再設置(位或)的邏輯來配置。
GPIO賦值操作
對ODR、IDR的操作進行宏定義
//IO口操作,只對單一IO口
//確保n小於16
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //輸出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //輸入
#define LED0 PAout(1) // DS0
#define LED1 PAout(0) // DS1
LED0=1;
LED1=1;//給ODR寄存器相應位映射的地址寫1賦值。
其中位地址映射BIT_ADDR()的宏定義如下:
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
使用GPIOA的結構體基地址+偏移量,定位到了ODR寄存器
#define GPIOA_ODR_Addr (GPIOA_BASE+20) //0x40020014
同樣產生了一些疑問:
(1)內存都是字節地址,這爲什麼能定位到字節中的某一位,是我理解出問題了?
-
對於F1、F4(7系列沒有),內核架構支持位地址映射(位帶操作),可以把每一個比特位膨脹(映射)爲一個字(32位、4字節),訪問這個字地址就達到訪問位的目的。
-
映射關係是人爲規定的,但能夠映射的位帶區地址範圍是有約束的,SRAM區最低1MB(0X20000000~0X200FFFFF)與片外外設區最低1MB範圍是位帶區地址。
-
此代碼定義的映射關係爲(addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2),把GPIOA_ODR的地址中的指定位bitnum通過基地址+偏移量的方式映射到SRAM區0X20000000中去。
(2)爲什麼要有位帶操作?
- 如果沒有位帶操作,只能先把某個地址的32位的字數據全部讀出來,再掩蔽不需要的位,再置位,再把置位後的字寫入該地址。有了位帶操作,可以直接操作需要的位了。
- 在多任務中,位帶操作用於實現共享資源在任務間的“互鎖操作”,以前的讀-改-寫需要3條指令,導致中間有兩個能被中斷的空當,而位帶操作是一種原子操作。
(3)取址符號*的使用把人搞糊塗了,怎麼理解清楚?
- 看這篇文章.
(4)爲啥有這麼多宏定義?
- 宏是產生內嵌代碼的唯一方法。如果你在你的代碼裏面定義了大量函數,且這些函數需要反覆調用,那你最好把這些函數改成宏定義的形式,提高性能要求!
- 但是要注意,宏定義只是簡單的字符串替換,注意好括號的使用。
HAL庫版本
首先比較下使用寄存器(位帶操作)與使用KAL庫的代碼結構有什麼不同
- 代碼架構一致,可以認爲在寄存器的基礎上,又加了一層函數封裝成了HAL庫的庫函數,供人使用。
- 標準庫與HAL庫並沒啥本質區別,API的不同表現形式與實現手段。重點學習HAL(hardware abstract layer)如何對具體硬件層面進行抽象的。
- 具體不同點有:HAL庫的初始化、使用HAL庫函數配置GPIO口、使用HAL庫函數讀寫GPIO口。
//使用HAL庫
int main(void)
{
HAL_Init(); //初始化HAL
Stm32_Clock_Init(360,25,2,8); //設置時鐘,180MHZ
delay_init(180); //初始化延時函數
LED_Init(); //設置GPIO口
while(1)
{
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);
delay_ms(1000);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);
delay_ms(1000);
}
}
//操作寄存器
int main(void)
{
Stm32_Clock_Init(360,25,2,8); //ÉèÖÃʱÖÓ,180Mhz
delay_init(180); //³õʼ»¯ÑÓʱº¯Êý
LED_Init(); //³õʼ»¯LED while(1)
{
LED0=0; //LED0ÁÁ
LED1=1; //LED1Ãð
delay_ms(500);
LED0=1; //LED0Ãð
LED1=0; //LED1ÁÁ
delay_ms(500);
}
}
下面學習HAL庫怎麼封裝寄存器操作的。
GPIO結構體定義
typedef struct
{
uint32_t Pin; /*!< Specifies the GPIO pins to be configured.
This parameter can be any value of @ref GPIO_pins_define */
uint32_t Mode; /*!< Specifies the operating mode for the selected pins.
This parameter can be a value of @ref GPIO_mode_define */
uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
This parameter can be a value of @ref GPIO_pull_define */
uint32_t Speed; /*!< Specifies the speed for the selected pins.
This parameter can be a value of @ref GPIO_speed_define */
uint32_t Alternate; /*!< Peripheral to be connected to the selected pins.
This parameter can be a value of @ref GPIO_Alternate_function_selection */
}GPIO_InitTypeDef;
跟寄存器形式相比較,似乎少了一些定義,然後發現定義的Mode變量既可以設置端口模式,還可以設置端口輸出類型。
#define GPIO_MODE_INPUT ((uint32_t)0x00000000) /*!< Input Floating Mode */
#define GPIO_MODE_OUTPUT_PP ((uint32_t)0x00000001) /*!< Output Push Pull Mode */
#define GPIO_MODE_OUTPUT_OD ((uint32_t)0x00000011) /*!< Output Open Drain Mode */
#define GPIO_MODE_AF_PP ((uint32_t)0x00000002) /*!< Alternate Function Push Pull Mode */
#define GPIO_MODE_AF_OD ((uint32_t)0x00000012) /*!< Alternate Function Open Drain Mode */
怎麼實現一個32位的變量去設置兩個32位的寄存器的呢?那就要看後續GPIO初始化的程序。
GPIO初始化設置
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE(); //使能IO時鐘
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1; //PB1,0
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推輓輸出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //¸高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET); //PB0置1
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET); //PB1置1
}
首先是對GPIO_InitTypeDef的成員變量賦值,然後進入HAL_GPIO_Init()函數
HAL庫版本的GPIO初始化代碼有100多行,比寄存器版本的代碼多了10倍,爲什麼?我們接着研究
1、首先,HAL_GPIO_Init()接收兩個入口參數,第一個變量是寄存器版本定義的GPIO_TypeDef變量類型,由此我們知道,GPIO原始的寄存器地址聲明在這個變量裏面,實際上還是要操作這些配置寄存器。第二個變量是GPIO_InitTypeDef類型,是我們新定義的類型
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
2、進入函數內部的第一步,就是進行入口參數的有效性判斷。幾乎是帶參數的函數前面都有調用assert_param()
/* Check the parameters */
assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Init->Pin));
assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
assert_param(IS_GPIO_PULL(GPIO_Init->Pull));
assert_param()可以對錶達式進行判斷,如果表達式爲真,什麼都不執行,如果表達式爲假,進入錯誤日誌函數。這個函數常用於調試階段檢查是否有參數輸入錯誤。
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
#define IS_GPIO_SPEED(SPEED) (((SPEED) == GPIO_SPEED_FREQ_LOW) || ((SPEED) == GPIO_SPEED_FREQ_MEDIUM) || \
((SPEED) == GPIO_SPEED_FREQ_HIGH) || ((SPEED) == GPIO_SPEED_FREQ_VERY_HIGH))
3、有效性判斷後,開始進行遍歷尋找需要設置的IO口,當IO口被索引到之後,開始正式配置寄存器。
for(position = 0; position < GPIO_NUMBER; position++)
{
/* Get the IO position */
ioposition = ((uint32_t)0x01) << position;
/* Get the current IO position */
iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;
if(iocurrent == ioposition)
{
//開始配置寄存器
}
4、配置寄存器的操作同上,按照先清零(=取反再位與)再設置(位或)的先後順序來配置
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
/* Configure the IO Speed */
temp = GPIOx->OSPEEDR;
temp &= ~(GPIO_OSPEEDER_OSPEEDR0 << (position * 2));
temp |= (GPIO_Init->Speed << (position * 2));
GPIOx->OSPEEDR = temp;
GPIO賦值操作
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET); //PB0置1
同樣,第一個入口參數是寄存器版本定義的GPIO_TypeDef變量類型,由此我們知道,實際上還是操作這些配置寄存器。函數內部就是設置BSRR寄存器,很容易看明白。
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
if(PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16;
}
}