STM32基本GPIO操作:點燈(庫函數+寄存器)

社團作業=_=
開發版上的LED燈負極連接在PB5口,正極串聯一510Ω電阻後與3.3V相連
若開發板不帶LED燈則需要自行連接,務必串聯一個合適的電阻防止LED燈燒壞

零、一個有趣的延時函數

來自於開發板配套資料當中的例程,第一次看到的時候覺得耳目一新,代碼如下:

void Delay(u32 count)
{
  u32 i = 0;
  for (; i < count; i++)
    ;
}

當中的u32類型是在stm32f10x.h當中的一個宏定義,對應uint32_t,表示32位無符號型整數,在我的開發板當中就是unsigned int類型。
因爲STM32的主頻比電腦CPU慢得多,因此可以通過這種循環的方式來達到延時的效果

一、庫函數版本

1.初始化

以下是初始化PB5端口的代碼

// 定義一個類型爲GPIO_InitTypeDef,名字叫做GPIO_InitStructure的結構體
GPIO_InitTypeDef GPIO_InitStructure;

// PORTB時鐘使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

// 配置結構體GPIO_InitStructure
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;           // 設置GPIO端口號爲5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    // 設置端口模式爲推輓輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   // 設置輸出速率爲50MHz

// 初始化
GPIO_Init(GPIOB, &GPIO_InitStructure);              // 傳入的是結構體的指針

初始化流程:
時鐘使能 → 創建一個含端口配置信息的結構體 → 使用該結構體初始化

初始化其它端口:
以PC2端口爲例,將以上代碼中的兩處GPIOB改爲GPIOC,把GPIO_Pin_5改爲GPIO_Pin_2即可

端口模式(STM32有8種):
輸入浮空(GPIO_Mode_IN_FLOATING)、輸入上拉(GPIO_Mode_IPU)、輸入下拉(GPIO_Mode_IPD)、模擬輸入(GPIO_Mode_AIN)、開漏輸出(GPIO_Mode_Out_OD)、開漏複用功能(GPIO_Mode_AF_OD)、推輓式輸出(GPIO_Mode_Out_PP)、推輓式複用功能(GPIO_Mode_AF_PP)
其他的目前沒弄懂,反正設置爲推輓輸出模式就能夠輸出高/低電平了

啓動同組的多個端口:
例如要同時啓用PB5,PB6端口,
第一種方案是在上面的代碼之後添加以下內容

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOB, &GPIO_InitStructure);

因爲PB5、PB6同屬於GPIOB組,GPIOB的時鐘已經使能且PB6端口的其他配置和PB5端口相同,因此改變結構體的端口號之後再次執行初始化函數即可

第二種方案是將上述代碼當中的

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; 

改爲

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6; 

因爲GPIO_Pin_0 ~ GPIO_Pin_15分別對應二進制數1、10、100、……,因此使用位運算當中的或運算即可將兩個參數疊加起來

同理,如果想同時使能PORTA、PORTB、PORTC時鐘,則可將

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);  

改爲

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);  

2.使用

由於PB5口與LED負極相連,因此僅當輸出低電平時LED燈纔會亮

設置爲低電平:

GPIO_ResetBits(GPIOB, GPIO_Pin_5);

設置爲高電平:

GPIO_SetBits(GPIOB, GPIO_Pin_5);

3.閃燈例程

代碼如下:

void Delay(unsigned int count)
{
  unsigned int i = 0;
  for (; i < count; i++)
    ;
}
int main(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
  GPIO_Init(GPIOB, &GPIO_InitStructure);

  while (1)
  {
    GPIO_SetBits(GPIOB, GPIO_Pin_5);
	Delay(10000000);
		
    GPIO_ResetBits(GPIOB, GPIO_Pin_5);
	Delay(10000000);
  }
}

Delay()當中的數字可根據實際設備的運行頻率做相應調整

二、寄存器版本(建議和庫函數版本對比異同)

這一部分需要對C語言的位運算有一定的瞭解

0.寄存器

以下是需要用到的寄存器,通過查詢STM32中文參考手冊7.3和8.2可獲得更加詳細的信息。

RCC寄存器:
APB2外設時鐘使能寄存器(RCC->APB2ENR)

GPIO寄存器:
端口配置低寄存器(GPIOx->CRL)(x=A…E)
端口輸出數據寄存器(GPIOx->ODR)(x=A…E)
端口位設置/清除寄存器(GPIOx->BSRR)(x=A…E)
端口位清除寄存器(GPIOx->BRR)(x=A…E)

1.初始化

以下是初始化PB5端口的代碼

// PORTB時鐘使能
RCC->APB2ENR |= 1<<3;      //將寄存器APB2ENR的第3位(與PORTB對應)設爲1

// 初始化
GPIOB->CRL &= 0XFF0FFFFF;  //清空寄存器CRL第20~23位(Pin5對應的參數)
GPIOB->CRL |= 0X00300000;  //將寄存器CRL第20~23位設爲0011

初始化流程:
時鐘使能 → 直接通過配置寄存器來初始化端口

關於APB2ENR寄存器:
APB2ENR寄存器的各位描述如下:
APB2ENR

由圖可知,若需要使能PORTC時鐘,則需要以下代碼

RCC->APB2ENR |= 1<<4;

與庫函數版本類似,若需要同時使能PORTA、PORTB、PORTC時鐘,則需要以下代碼

RCC->APB2ENR |= (1<<3) | (1<<4) | (1<<5);

關於CRL寄存器:
CRL寄存器的各位描述如下:
CRL

當中的每4個位對應1個輸出端口,查閱資料可得推輓輸出對應的配置位(CNFx)爲00,50MHz輸出速率對應的模式位(MODEx)爲11
4個位(bit)剛好與1個16進制數相對應(2^4 = 16^1 = 16),因此一個16進制數0 ~ F剛好對應了一個GPIO端口的配置。二進制數0011對應的16進制數爲0x3,因此上面的初始化代碼可將寄存器的第20 ~ 23位設爲0011

同理,若需要將PC2口初始化爲推輓輸出+50MHz輸出速率,則需要以下代碼

GPIOC->CRL &= 0XFFFFF0FF;  //清空CRL寄存器第8~11位(Pin2對應的參數)
GPIOC->CRL |= 0X00000300;  //將CRL寄存器第8~11位設爲0011

設置Pin0 ~ Pin7時用的是CRL寄存器,設置Pin8 ~ Pin15時用的是CRH寄存器,CRH寄存器的各位描述如下,具體設置方法和CRL寄存器類似
CRH

2.使用(多種方案)

2.1 傳統操作
2.1.1 通過ODR寄存器操作(麻煩)

ODR名爲端口輸出數據寄存器,向其0 ~ 16位寫入1則對應端口爲高電位,反之爲低電位
這種特性意味着每次設置ODR寄存器需要給出0號端口 ~ 15號端口的高低電位
由於看開發手冊沒有看全,首先想到的是這種操作方法

設置爲低電平:

GPIOB->ODR &= 0xffffffff-(1<<5); //將第5位(bit)清空
GPIOB->ODR |= 0<<5;              //將第5位(bit)設置爲0

設置爲高電平:

GPIOB->ODR &= 0xffffffff-(1<<5); //將第5位(bit)清空
GPIOB->ODR |= 1<<5;              //將第5位(bit)設置爲1
2.1.2 通過BSRR/BRR寄存器操作(簡單)

BSRR名爲端口位設置/清除寄存器,向其0 ~ 16位(bit)寫入1則ODR對應位變爲1(對應端口爲高電位),寫入0則不變
BSRR

BRR名爲端口位清除寄存器,向其0 ~ 16位寫入1則ODR對應位變爲0(對應端口爲低電位),寫入0則不變
BRR

後期發現了這種更方便的操作方法

設置爲低電平:

GPIOB->BRR &= 1<<5; //將第5位設置爲0

設置爲高電平:

GPIOB->BSRR &= 1<<5; //將第5位設置爲1

不使用BRR寄存器,僅使用BSRR寄存器來將ODR對應位變爲0/1也是可以的,具體方法可參考STM32中文參考手冊8.2.5

2.2 位帶操作

個人理解是通過訪問一個32位長度的地址區間(類似於直接操作一個unsigned int)來達到訪問1個位的效果,各種資料上說這種操作更優越
具體實現還不會,不過開發板資料中有現成的頭文件可供使用

頭文件中的代碼如下:

#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)) 

#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 

#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)   

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)   
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  

include該頭文件後即可使用位帶操作

設置爲低電平:

PBout(5) = 0;

設置爲高電平:

PBout(5) = 1;

3.閃燈例程

代碼如下:

void Delay(unsigned int count)
{
  unsigned int i = 0;
  for (; i < count; i++)
    ;
}
int main(void)
{
  RCC->APB2ENR |= 1<<3;

  GPIOB->CRL &= 0XFF0FFFFF;
  GPIOB->CRL |= 0X00300000;  

  while (1)
  {
    GPIOB->BSRR = 1<<5;
	Delay(10000000);
		
    GPIOB->BRR = 1<<5;
	Delay(10000000);
  }
}

Delay()當中的數字可根據實際設備的運行頻率做相應調整

2019.11.28

發佈了26 篇原創文章 · 獲贊 0 · 訪問量 4260
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章