STM32:構建庫函數

庫的封裝

在自己編寫庫的過程中,可以有多種封裝辦法。在這裏,主要根據GPIOB的庫封裝來介紹

注意庫一般都是寫在頭文件中

1.第一種:普通封裝

普通封裝就是根據地址,一個一個使用宏定義來封裝,在頭文件stm32f10x.h中來定義:

/*片上外設基地址 */
#define  PERIPH_BASE               ((unsigned int)0x40000000)
/*APB1 總線基地址 */
#define  APB1PERIPH_BASE           PERIPH_BASE
/*APB2 總線基地址 */
#define  APB2PERIPH_BASE          (PERIPH_BASE + 0x10000)
/*AHB 總線基地址 */
#define  AHBPERIPH_BASE           (PERIPH_BASE + 0x20000)

/*RCC外設基地址 */
#define  RCC_BASE                (AHBPERIPH_BASE + 0x1000)
/*GPIOB 總線基地址 */
#define  GPIOB_BASE              (APB2PERIPH_BASE + 0x0C00)


#define  RCC_APB2ENR            *(unsigned int*)(RCC_BASE + 0x18)
/*GPIOB 外設聲明*/	
#define  GPIOB_CRL              *(unsigned int*)(GPIOB_BASE + 0x00)
#define  GPIOB_CRH              *(unsigned int*)(GPIOB_BASE + 0x04)
#define  GPIOB_IDR              *(unsigned int*)(GPIOB_BASE + 0x08)
#define  GPIOB_ODR              *(unsigned int*)(GPIOB_BASE + 0x0C)
#define  GPIOB_BSRR             *(unsigned int*)(GPIOB_BASE + 0x10)
#define  GPIOB_BRR              *(unsigned int*)(GPIOB_BASE + 0x14)
#define  GPIOB_LCKR             *(unsigned int*)(GPIOB_BASE + 0x18)

2.第二種:結構體封裝

在第一種普通封裝裏面的GPIO外設聲明中,可以發現聲明的寄存器都是各相差4個字節,這裏還是在頭文件stm32f10x.h中來操作,由此我們還可以用結構體來封裝:

#define  PERIPH_BASE               ((unsigned int)0x40000000)
/*APB1 總線基地址 */
#define  APB1PERIPH_BASE           PERIPH_BASE
/*APB2 總線基地址 */
#define  APB2PERIPH_BASE          (PERIPH_BASE + 0x10000)
/*AHB 總線基地址 */
#define  AHBPERIPH_BASE           (PERIPH_BASE + 0x20000)

/*RCC外設基地址 */
#define  RCC_BASE                (AHBPERIPH_BASE + 0x1000)
/*GPIOB 總線基地址 */
#define  GPIOB_BASE              (APB2PERIPH_BASE + 0x0C00)


#define  RCC_APB2ENR            *(unsigned int*)(RCC_BASE + 0x18)

/*GPIOB 外設聲明*/	
typedef unsigned int      uint32_t;
typedef unsigned short    uint16_t;

typedef struct
{
	uint32_t CRL;
	uint32_t CRH;
	uint32_t IDR;
	uint32_t ODR;
	uint32_t BSRR;
	uint32_t BRR;
	uint32_t LCKR;
}GPIO_TypeDef;

//強制類型轉換,把GPIO基地轉換爲結構體類型的指針
#define GPIOB   ((GPIO_TypeDef*)GPIOB_BASE)

3.模塊化編程,函數調用

GPIO輸出低電平,可以用一個通用的函數的方式來實現。就是模塊化編程的思想,這裏可以建設驅動庫文件,來存放宏定義,針對外設寫的函數。在這裏,包括.h文件和.c文件。

在這裏我們還是寫針對GPIOB的端口的置位和復位函數

1.頭文件,頭文件定義爲stm3210x_goip.h,頭文件用來放函數的聲明和各種宏定義,如下所示:
頭文件

2.源文件.c文件,用來存放函數,定義爲stm3210x_goip.c,如下所示:
.c文件

3.main函數:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"

int main (void)
{
	GPIO_SetBits(GPIOB,GPIO_Pin_0);//GPIOB口端口0置1
	GPIO_ResetBits( GPIOB,GPIO_Pin_0 );//GPIOB口端口0清零
}

4.寫庫,完善初始化部分

完善GPIO的初始化部分,這一部分,可以說是和官方固件庫最爲接近的庫。使用GPIO接口只需調用一個函數即可。這一部分涉及到了c語言的一些結構體定義。

1.在頭文件stm3210x_goip.h中,這裏的頭文件在第三小節的頭文件中加了後面的代碼:

//限制結構體裏面的成員只能取某個特定的值:枚舉定義
//限制速率,enum裏面是逗號
typedef enum
{ 
  GPIO_Speed_10MHz = 1,         // 10MHZ        (01)b
  GPIO_Speed_2MHz,              // 2MHZ         (10)b,enum後面的值都自動加一
  GPIO_Speed_50MHz              // 50MHZ        (11)b,沒有必要再配置2,3
}GPIOSpeed_TypeDef;
//限制模式,GPIO的八種工作模式,在上一篇中說過
typedef enum
{ GPIO_Mode_AIN = 0x0,           // 模擬輸入     (0000 0000)b
  GPIO_Mode_IN_FLOATING = 0x04,  // 浮空輸入     (0000 0100)b
  GPIO_Mode_IPD = 0x28,          // 下拉輸入     (0010 1000)b,上拉下拉還與BRR,BSRR有關
  GPIO_Mode_IPU = 0x48,          // 上拉輸入     (0100 1000)b
  
  GPIO_Mode_Out_OD = 0x14,       // 開漏輸出     (0001 0100)b
  GPIO_Mode_Out_PP = 0x10,       // 推輓輸出     (0001 0000)b
  GPIO_Mode_AF_OD = 0x1C,        // 複用開漏輸出 (0001 1100)b
  GPIO_Mode_AF_PP = 0x18         // 複用推輓輸出 (0001 1000)b
}GPIOMode_TypeDef;

//定義GPIO初始化結構體
typedef struct
{
	uint16_t GPIO_Pin;//選擇要配置的GPIO引腳
	uint16_t GPIO_Speed;//選擇GPIO引腳的速率
	uint16_t GPIO_Mode;//選擇GPIO引腳的工作模式
}GPIO_InitTypeDef;

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);//初始化函數聲明

2.源文件.c文件,用來存放函數,定義爲stm3210x_goip.c,這時函數內容爲:
這是STM官方固件庫內的GPIO調用函數,只理解就行.

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
	//下面定義都是暫存數據的
  uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
  uint32_t tmpreg = 0x00, pinmask = 0x00;
  
/*---------------------- GPIO 模式配置 --------------------------*/
  // 把輸入參數GPIO_Mode的低四位暫存在currentmode
  currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
	
  // bit4是1表示輸出,bit4是0則是輸入 
  // 判斷bit4是1還是0,即首選判斷是輸入還是輸出模式
  if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
  { 
	// 輸出模式則要設置輸出速度
    currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
  }
/*-------------GPIO CRL 寄存器配置 CRL寄存器控制着低8位IO- -------*/
  // 配置端口低8位,即Pin0~Pin7
  if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
  {
	// 先備份CRL寄存器的值
    tmpreg = GPIOx->CRL;
		
	// 循環,從Pin0開始配對,找出具體的Pin
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
	 // pos的值爲1左移pinpos位
      pos = ((uint32_t)0x01) << pinpos;
      
	  // 令pos與輸入參數GPIO_PIN作位與運算,爲下面的判斷作準備
      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
			
	  //若currentpin=pos,則找到使用的引腳
      if (currentpin == pos)
      {
		// pinpos的值左移兩位(乘以4),因爲寄存器中4個寄存器位配置一個引腳
        pos = pinpos << 2;
       //把控制這個引腳的4個寄存器位清零,其它寄存器位不變
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
				
        // 向寄存器寫入將要配置的引腳的模式
        tmpreg |= (currentmode << pos);  
				
		// 判斷是否爲下拉輸入模式
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
		  // 下拉輸入模式,引腳默認置0,對BRR寄存器寫1可對引腳置0
          GPIOx->BRR = (((uint32_t)0x01) << pinpos);
        }				
        else
        {
          // 判斷是否爲上拉輸入模式
          if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
          {
		    // 上拉輸入模式,引腳默認值爲1,對BSRR寄存器寫1可對引腳置1
            GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
          }
        }
      }
    }
		// 把前面處理後的暫存值寫入到CRL寄存器之中
    GPIOx->CRL = tmpreg;
  }
/*-------------GPIO CRH 寄存器配置 CRH寄存器控制着高8位IO- -----------*/
  // 配置端口高8位,即Pin8~Pin15
  if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
  {
		// // 先備份CRH寄存器的值
    tmpreg = GPIOx->CRH;
		
	// 循環,從Pin8開始配對,找出具體的Pin
    for (pinpos = 0x00; pinpos < 0x08; pinpos++)
    {
      pos = (((uint32_t)0x01) << (pinpos + 0x08));
			
      // pos與輸入參數GPIO_PIN作位與運算
      currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
			
	 //若currentpin=pos,則找到使用的引腳
      if (currentpin == pos)
      {
		//pinpos的值左移兩位(乘以4),因爲寄存器中4個寄存器位配置一個引腳
        pos = pinpos << 2;
        
	    //把控制這個引腳的4個寄存器位清零,其它寄存器位不變
        pinmask = ((uint32_t)0x0F) << pos;
        tmpreg &= ~pinmask;
				
        // 向寄存器寫入將要配置的引腳的模式
        tmpreg |= (currentmode << pos);
        
		// 判斷是否爲下拉輸入模式
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
        {
		  // 下拉輸入模式,引腳默認置0,對BRR寄存器寫1可對引腳置0
          GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
         // 判斷是否爲上拉輸入模式
        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
        {
		  // 上拉輸入模式,引腳默認值爲1,對BSRR寄存器寫1可對引腳置1
          GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
        }
      }
    }
	// 把前面處理後的暫存值寫入到CRH寄存器之中
    GPIOx->CRH = tmpreg;
  }
}

上面就是GPIO的初始化函數。

3.這時候我們可以在main函數中調用這個函數:

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"

int main (void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;//聲明變量必須在大括號之後
	
	// 打開 GPIOB 端口的時鐘
	RCC->APB2ENR  |=  ( (1) << 3 );
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//選擇GPIO端口0
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//選擇爲推輓輸出模式
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//選擇速率
	GPIO_Init(GPIOB, &GPIO_InitStructure);//調用GPIO初始化函數,初始化代碼上電之後只運行一次
}

	GPIO_SetBits(GPIOB,GPIO_Pin_0);//GPIOB端口0輸出高電平
	GPIO_ResetBits( GPIOB,GPIO_Pin_0 );//GPIOB端口0置位爲低電平

5.提高程序的可移植性

板子不一樣,各個引腳所接的設備不同,移植的話要修改很多參數,特別不方便
這時候的解決方法就是在程序中宏定義相關硬件,在移植的時候只需要修改宏定義中的參數即可
如在這片文章中,PB是接綠色的燈,至於是PB那個端口,這裏是PB0端口
可以在main函數中這樣來宏定義:

#define   LED_G_GPIO_CLK_ENABLE         (RCC->APB2ENR  |=  ( (1) << 3 ))//時鐘
#define   LED_G_GPIO_PORT               GPIB//綠色LED燈用的是GPIOB
#define   LED_G_GPIO_PIN                GPIO_Pin_0//使用端口0

後記

這片文章說的就是固件庫了,而調用函數就是使用固件庫來編程,需要較高的閱讀代碼能力,當然懂寄存器原理也很好。

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