庫的封裝
在自己編寫庫的過程中,可以有多種封裝辦法。在這裏,主要根據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,如下所示:
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
後記
這片文章說的就是固件庫了,而調用函數就是使用固件庫來編程,需要較高的閱讀代碼能力,當然懂寄存器原理也很好。