初探STM32F4(1)--GPIO(1)


本文從兩個方面剖析GPIO:

  • 結合《STM32F4xx中文參考手冊》,從硬件和寄存器的角度剖析GPIO
  • 結合庫例程,從軟件角度剖析GPIO如何配置,同時剖析嵌入式代碼規範

閱讀完本文,要能回答以下問題:

  1. 簡述GPIO口的硬件架構與配置寄存器
  2. 配置GPIO口寄存器的代碼流程
  3. 然後就是結合配置GPIO的代碼回答一些嵌入式代碼規範的問題:
    1. 寄存器前面的類型修飾符的含義,例如__IO uint32_t ODR
    2. 爲啥要用volatile類型修飾符
    3. 使用unsigned int與int聲明後續配置有什麼區別?
    4. #define與typedef有什麼區別?
    5. 爲什麼嵌入式代碼中有這麼多宏定義?
    6. GPIO定義的結構體中的這些配置寄存器變量是怎麼映射到實際地址的?
    7. 嵌入式代碼中常常需要進行位操作,結合配置GPIO的代碼,談談如何進行位操作?
    8. 嵌入式代碼中常常需要對多位進行位操作,結合配置GPIO的代碼,談談如何進行位的遍歷?
    9. M3M4內核通過位帶映射支持直接對位進行操作,談談你對位帶操作的認識?
    10. 嵌入式代碼進入帶參數的函數調用中去時,常常會先使用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

四種輸出工作模式:

  1. 開漏輸出模式。CPU通過置位/復位寄存器或直接寫操作,設置好輸出數據寄存器。輸出驅動器只有一個MOS管工作,當輸出1時,MOS管截止,通過外接上拉電阻實現輸出高電平。當輸出0時,MOS管導通,輸出低電平。
  2. 開漏複用輸出模式。輸出的數據來自CPU的片上覆用外設。
  3. 推輓式輸出。輸出驅動器的NMOS與PMOS管均工作,當輸出1時,PMOS導通,輸出高電平,當輸出0時,NMOS導通,輸出低電平。
  4. 推輓式複用輸出模式。輸出的數據來自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口的規範操作是進行位操作。

  1. 接口參數:GPIO_TypeDef* GPIOx指定哪一組GPIO口,u32 BITx指定該組GPIO口配置哪些位,以及4個配置寄存器該配置好的值。
  2. 如何定義配置的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的。

  1. 我們常常需要對某些位復位,怎麼進行呢?
    以GPIOx->MODER&=~(3<<(pinpos*2))爲例,由於MODER是兩位定義一個IO口的功能,所以操作的基本單元是二進制11,將11左移到需要設置的IO口的那些位<<(pinpos * 2)。然後取反再位與,保證需要設置的位清零,其它位保持不變。
  2. 置位就是進行位或操作。
  3. 其它寄存器同樣按照先清零(=取反再位與)再設置(位或)的邏輯來配置。

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;
  }
}

GPIO外部中斷

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章