色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

如何基于標準庫構建STM32工程

STM32嵌入式開發 ? 來源:STM32嵌入式開發 ? 作者:STM32嵌入式開發 ? 2022-08-02 17:25 ? 次閱讀

本文將以STM32F10x為例,對標準庫開發進行概覽。主要分為三塊內容:

  • STM32系統結構

  • 寄存器

  • 通過點燈案例,詳解如何基于標準庫構建STM32工程

STM32系統結構

9d9bba00-1242-11ed-ba43-dac502259ad0.png

上圖,STM32f10xxx系統結構。

內核IP

從結構框圖上看,Cortex-M3內部有若干個總線接口,以使CM3能同時取址和訪內(訪問內存),它們是:指令存儲區總線(兩條)、系統總線、私有外設總線。有兩條代碼存儲區總線負責對代碼存儲區(即 FLASH 外設)的訪問,分別是I-Code 總線D-Code 總線

I-Code用于取指,D-Code用于查表等操作,它們按最佳執行速度進行優化。

系統總線(System)用于訪問內存和外設,覆蓋的區域包括SRAM,片上外設,片外RAM,片外擴展設備,以及系統級存儲區的部分空間。

私有外設總線負責一部分私有外設的訪問,主要就是訪問調試組件。它們也在系統級存儲區。

還有一個DMA總線,從字面上看,DMA是data memory access的意思,是一種連接內核和外設的橋梁,它可以訪問外設、內存,傳輸不受CPU的控制,并且是雙向通信。簡而言之,這個家伙就是一個速度很快的且不受老大控制的數據搬運工。相關文章:詳解STM32中的DMA原理

處理器外設(內核之外的外設)

從結構框圖上看,STM32的外設有串口、定時器、IO口、FSMC、SDIO、SPI、I2C等,這些外設按照速度的不同,分別掛載到AHB、APB2、APB1這三條總線上。

寄存器

什么是寄存器?寄存器是內置于各個IP外設中,是一種用于配置外設功能的存儲器,并且有想對應的地址。一切庫的封裝始于映射。

9db56b8a-1242-11ed-ba43-dac502259ad0.png

9dd8b1b2-1242-11ed-ba43-dac502259ad0.png

是不是看的眼都花了,如果進行寄存器開發,就需要懟地址以及對寄存器進行字節賦值,不僅效率低而且容易出錯。

庫的存在就是為了解決這類問題,將代碼語義化。語義化思想不僅僅是嵌入式有的,前端代碼也在追求語義特性。

從點燈開始學習STM32

9df605dc-1242-11ed-ba43-dac502259ad0.png

內核庫文件分析

cor_cm3.h

這個頭文件實現了:

  • 內核結構體寄存器定義。

  • 內核寄存器內存映射。

  • 內存寄存器位定義。跟處理器相關的頭文件stm32f10x.h實現的功能一樣,一個是針對內核的寄存器,一個是針對內核之外,即處理器的寄存器。

misc.h

內核應用函數庫頭文件,對應stm32f10x_xxx.h。

misc.c

內核應用函數庫文件,對應stm32f10x_xxx.c。在CM3這個內核里面還有一些功能組件,如NVIC、SCB、ITM、MPU、CoreDebug,CM3帶有非常豐富的功能組件,但是芯片廠商在設計MCU的時候有一些并不是非要不可的,是可裁剪的,比如MPU、ITM等在STM32里面就沒有。

其中NVIC在每一個CM3內核的單片機中都會有,但都會被裁剪,只能是CM3 NVIC的一個子集。在NVIC里面還有一個SysTick,是一個系統定時器,可以提供時基,一般為操作系統定時器所用。misc.h和mics.c這兩個文件提供了操作這些組件的函數,并可以在CM3內核單片機直接移植。

處理器外設庫文件分析

startup_stm32f10x_hd.s

這個是由匯編編寫的啟動文件,是STM32上電啟動的第一個程序,啟動文件主要實現了

  • 初始化堆棧指針 SP;
  • 設置 PC 指針=Reset_Handler ;
  • 設置向量表的地址,并 初始化向量表,向量表里面放的是 STM32 所有中斷函數的入口地址
  • 調用庫函數 SystemInit,把系統時鐘配置成 72M,SystemInit 在庫文件 stytem_stm32f10x.c 中定義;
  • 跳轉到標號_main,最終去到 C 的世界。

system_stm32f10x.c

這個文件的作用是里面實現了各種常用的系統時鐘設置函數,有72M,56M,48, 36,24,8M,我們使用的是是把系統時鐘設置成72M。

Stm32f10x.h

這個頭文件非常重要,這個頭文件實現了:

  • 處理器外設寄存器的結構體定義。

  • 處理器外設的內存映射。

  • 處理器外設寄存器的位定義。

關于 1 和 2 我們在用寄存器點亮 LED 的時候有講解。

其中 3:處理器外設寄存器的位定義,這個非常重要,具體是什么意思?

我們知道一個寄存器有很多個位,每個位寫 1 或者寫 0 的功能都是不一樣的,處理器外設寄存器的位定義就是把外設的每個寄存器的每一個位寫 1 的 16 進制數定義成一個宏,宏名即用該位的名稱表示,如果我們操作寄存器要開啟某一個功能的話,就不用自己親自去算這個值是多少,可以直接到這個頭文件里面找。相關文章:C語言操作寄存器的常見手法

我們以片上外設 ADC 為例,假設我們要啟動 ADC 開始轉換,根據手冊我們知道是要控制 ADC_CR2 寄存器的位 0:ADON,即往位 0 寫 1,即:

ADC->CR2=0x00000001;

這是一般的操作方法。現在這個頭文件里面有關于 ADON 位的位定義:

#define ADC_CR2_ADON ((uint32_t)0x00000001)

有了這個位定義,我們剛剛的代碼就變成了:

ADC->CR2=ADC_CR2_ADON

stm32f10x_xxx.h

外設 xxx 應用函數庫頭文件,這里面主要定義了實現外設某一功能的結構體,比如通用定時器有很多功能,有定時功能,有輸出比較功能,有輸入捕捉功能,而通用定時器有非常多的寄存器要實現某一個功能。

比如定時功能,我們根本不知道具體要操作哪些寄存器,這個頭文件就為我們打包好了要實現某一個功能的寄存器,是以機構體的形式定義的,比如通用定時器要實現一個定時的功能,我們只需要初始化 TIM_TimeBaseInitTypeDef 這個結構體里面的成員即可,里面的成員就是定時所需要操作的寄存器。

有了這個頭文件,我們就知道要實現某個功能需要操作哪些寄存器,然后再回手冊中精度這些寄存器的說明即可。

stm32f10x_xxx.c

stm32f10x_xxx.c:外設 xxx 應用函數庫,這里面寫好了操作 xxx 外設的所有常用的函數,我們使用庫編程的時候,使用的最多的就是這里的函數。

SystemInit

工程中新建main.c 。

在此文件中編寫main函數后直接編譯會報錯:

Undefined symbol SystemInit (referred from startup_stm32f10x_hd.o).

錯誤提示說SystemInit沒有定義。從分析啟動文件startup_stm32f10x_hd.s時我們知道。

;Reset handlerReset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT __main;IMPORT SystemInit;LDR R0, =SystemInitBLX R0LDR R0, =__mainBX R0ENDP
匯編中;分號是注釋的意思

第五行第六行代碼Reset_Handler調用了SystemInit該函數用來初始化系統時鐘,而該函數是在庫文件system_stm32f10x.c中實現的。我們重新寫一個這樣的函數也可以,把功能完整實現一遍,但是為了簡單起見,我們在main文件里面定義一個SystemInit空函數,為的是騙過編譯器,把這個錯誤去掉。

關于配置系統時鐘之后會出文章RCC時鐘樹詳細介紹,主要配置時鐘控制寄存器(RCC_CR)和時鐘配置寄存器(RCC_CFGR)這兩個寄存器,但最好是直接使用CubeMX直接生成,因為它的配置過程有些冗長。

如果我們用的是庫,那么有個庫函數SystemInit,會幫我們把系統時鐘設置成72M。

現在我們沒有使用庫,那現在時鐘是多少?答案是8M,當外部HSE沒有開啟或者出現故障的時候,系統時鐘由內部低速時鐘LSI提供,現在我們是沒有開啟HSE,所以系統默認的時鐘是LSI=8M。

庫封裝層級

9e0d9350-1242-11ed-ba43-dac502259ad0.png

如圖,達到第四層級便是我們所熟知的固件庫或HAL庫的效果。當然庫的編寫還需要考慮許多問題,不止于這些內容。我們需要的是了解庫封裝的大概過程。

將庫封裝等級分為四級來介紹是為了有層次感,就像打怪升級一樣,進行認知理解的升級。

我們都知道,操作GPIO輸出分三大步:

時鐘控制:

STM32 外設很多,為了降低功耗,每個外設都對應著一個時鐘,在系統復位的時候這些時鐘都是被關閉的,如果想要外設工作,必須把相應的時鐘打開。

STM32 的所有外設的時鐘由一個專門的外設來管理,叫RCC(reset and clockcontrol),RCC 在STM32 參考手冊的第六章。

STM32 的外設因為速率的不同,分別掛載到三條總系上:AHB、APB2、APB1,AHB為高速總線,APB2 次之,APB1 再次之。所以的IO 口都掛載到APB2 總線上,屬于高速外設。

模式配置:

這個由端口配置寄存器來控制。端口配置寄存器分為高低兩個,每4bit 控制一個IO 口,所以端口配置低寄存器:CRL 控制這IO 口的低8 位,端口配置高寄存器:CRH控制這IO 口的高8bit。

在4 位一組的控制位中,CNFy[1:0] 用來控制端口的輸入輸出,MODEy[1:0]用來控制輸出模式的速率,又稱驅動電路的響應速度,注意此處速率與程序無關,GPIO引腳速度、翻轉速度、輸出速度區別輸入有4種模式,輸出有4種模式,我們在控制LED 的時候選擇通用推挽輸出。

輸出速率有三種模式:2M、10M、50M,這里我們選擇2M。

電平控制:

STM32的IO口比較復雜,如果要輸出1和0,則要通過控制:端口輸出數據寄存器ODR來實現,ODR 是:Output data register的簡寫,在STM32里面,其寄存器的命名名稱都是英文的簡寫,很容易記住。

從手冊上我們知道ODR是一個32位的寄存器,低16位有效,高16位保留。低16位對應著IO0~IO16,只要往相應的位置寫入0或者1就可以輸出低或者高電平。

第一層級:基地址宏定義

9e1e5a14-1242-11ed-ba43-dac502259ad0.png

時鐘控制:

9e38950a-1242-11ed-ba43-dac502259ad0.png

在STM32中,每個外設都有一個起始地址,叫做外設基地址,外設的寄存器就以這個基地址為標準按照順序排列,且每個寄存器32位,(后面作為結構體里面的成員正好內存對齊)。

查表看到時鐘由APB2外設時鐘使能寄存器(RCC_APB2ENR)來控制,其中PB端口的時鐘由該寄存器的位3寫1使能。我們可以通過基地址+偏移量0x18,算出RCC_APB2ENR的地址為:0x40021018。那么使能PB口的時鐘代碼則如下所示:

 #define RCC_APB2ENR *(volatile unsigned long *)0x40021018
 // 開啟端口B 時鐘 RCC_APB2ENR |= 1<<3;

模式配置:

9e4cc804-1242-11ed-ba43-dac502259ad0.png

同RCC_APB2ENR一樣,GPIOB的起始地址是:0X4001 0C00,我們也可以算出GPIO_CRL的地址為:0x40010C00。那么設置PB0為通用推挽輸出,輸出速率為2M的代碼則如下所示:

9e624f58-1242-11ed-ba43-dac502259ad0.png

同上,從手冊中我們看到ODR寄存器的地址偏移是:0CH,可以算出GPIOB_ODR寄存器的地址是:0X4001 0C00 + 0X0C = 0X4001 0C0C。現在我們就可以定義GPIOB_ODR這個寄存器了,代碼如下:

#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
//PB0 輸出低電平GPIOB_ODR = 0<<0;

第一層級:基地址宏定義完成用STM32控制一個LED的完整代碼:

#define RCC_APB2ENR *(volatile unsigned long *)0x40021018#define GPIOB_CRL *(volatile unsigned long *)0x40010C00#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
int main(void){ // 開啟端口B 的時鐘 RCC_APB2ENR |= 1<<3;
 // 配置PB0 為通用推挽輸出模式,速率為2M GPIOB_CRL = (2<<0) | (0<<2);
 // PB0 輸出低電平,點亮LED GPIOB_ODR = 0<<0;}
void SystemInit(void){}

第二層級:基地址宏定義+結構體封裝

外設寄存器結構體封裝

上面我們在操作寄存器的時候,操作的是寄存器的絕對地址,如果每個寄存器都這樣操作,那將非常麻煩。我們考慮到外設寄存器的地址都是基于外設基地址的偏移地址,都是在外設基地址上逐個連續遞增的,每個寄存器占32個或者16個字節,這種方式跟結構體里面的成員類似。

所以我們可以定義一種外設結構體,結構體的地址等于外設的基地址,結構體的成員等于寄存器,成員的排列順序跟寄存器的順序一樣。這樣我們操作寄存器的時候就不用每次都找到絕對地址,只要知道外設的基地址就可以操作外設的全部寄存器,即操作結構體的成員即可。

下面我們先定義一個GPIO寄存器結構體,結構體里面的成員是GPIO的寄存器,成員的順序按照寄存器的偏移地址從低到高排列,成員類型跟寄存器類型一樣。

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

在《STM32 中文參考手冊》8.2 寄存器描述章節,我們可以找到結構體里面的7個寄存器描述。在點亮LED的時候我們只用了CRL和ODR這兩個寄存器,至于其他寄存器的功能大家可以自行看手冊了解。

在GPIO結構體里面我們用了兩個數據類型,一個是uint32_t,表示無符號的32位整型,因為GPIO的寄存器都是32位的。這個類型聲明在標準頭文件stdint.h 里面使用typedef對unsigned int重命名,我們在程序上只要包含這個頭文件即可。

另外一個是volatile作用就是告訴編譯器這里的變量會變化不因優化而省略此指令,必須每次都直接讀寫其值,這樣就能確保每次讀或者寫寄存器都真正執行到位。

外設封裝

9e723ef4-1242-11ed-ba43-dac502259ad0.png

STM32F1系列的GPIO端口分A~G,即GPIOA、GPIOB。。。。。。GPIOG。每個端口都含有GPIO_TypeDef結構體里面的寄存器,我們可以根據手冊各個端口的基地址把GPIO的各個端口定義成一個GPIO_TypeDef類型指針,然后我們就可以根據端口名(實際上現在是結構體指針了)來操作各個端口的寄存器,代碼實現如下:

#define GPIOA ((GPIO_TypeDef *) 0X4001 0800)#define GPIOB ((GPIO_TypeDef *) 0X4001 0C00)#define GPIOC ((GPIO_TypeDef *) 0X4001 1000)#define GPIOD ((GPIO_TypeDef *) 0X4001 1400)#define GPIOE ((GPIO_TypeDef *) 0X4001 1800)#define GPIOF ((GPIO_TypeDef *) 0X4001 1C00)#define GPIOG ((GPIO_TypeDef *) 0X4001 2000)

外設內存映射

講到基地址的時候我們再引人一個知識點:Cortex-M3存儲器系統,這個知識點在《Cortex-M3權威指南》第5章里面講到。CM3的地址空間是4GB,如下圖所示:

9e8fcc8a-1242-11ed-ba43-dac502259ad0.png

我們這里要講的是片上外設,就是我們所說的寄存器的根據地,其大小總共有512MB,512MB是其極限空間,并不是每個單片機都用得完,實際上各個MCU廠商都只是用了一部分而已。STM32F1系列用到了:0x4000 0000 ~0x5003 FFFF。現在我們說的STM32的寄存器就是位于這個區域。

APB1、APB2、AHB 總線基地址

現在我們說的STM32的寄存器就是位于這個區域,這里面ST設計了三條總線:AHB、APB2和APB1,其中AHB和APB2是高速總線,APB1是低速總線。不同的外設根據速度不同分別掛載到這三條總線上。

從下往上依次是:APB1、APB2、AHB,每個總線對應的地址分別是:APB1:0x40000000,APB2:0x4001 0000,AHB:0x4001 8000。

這三條總線的基地址我們是從《STM32 中文參考手冊》2.3小節—存儲器映像得到的:APB1的基地址是TIM2定時器的起始地址,APB2的基地址是AFIO的起始地址,AHB的基地址是SDIO的起始地址。其中APB1地址又叫做外設基地址,是所有外設的基地址,叫做PERIPH_BASE。

現在我們把這三條總線地址用宏定義出來,以后我們在定義其他外設基地址的時候,只需要在這三條總線的基址上加上偏移地址即可,代碼如下:


#define PERIPH_BASE ((uint32_t)0x40000000)#define APB1PERIPH_BASE PERIPH_BASE#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)

GPIO 端口基地址

因為GPIO掛載到APB2總線上,那么現在我們就可以根據APB2的基址算出各個GPIO端口的基地址,用宏定義實現代碼如下:


#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)

第二層級:基地址宏定義+結構體封裝完成用STM32控制一個LED的完整代碼:


#include #define __IO volatile
typedef struct { __IO uint32_t CRL; __IO uint32_t CRH; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t BRR; __IO uint32_t LCKR;} GPIO_TypeDef;
typedef struct { __IO uint32_t CR; __IO uint32_t CFGR; __IO uint32_t CIR; __IO uint32_t APB2RSTR; __IO uint32_t APB1RSTR; __IO uint32_t AHBENR; __IO uint32_t APB2ENR; __IO uint32_t APB1ENR; __IO uint32_t BDCR; __IO uint32_t CSR;} RCC_TypeDef;
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#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 RCC ((RCC_TypeDef *) RCC_BASE)

#define RCC_APB2ENR *(volatile unsigned long *)0x40021018#define GPIOB_CRL *(volatile unsigned long *)0x40010C00#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
int main(void){ // 開啟端口B 的時鐘 RCC->APB2ENR |= 1<<3;
 // 配置PB0 為通用推挽輸出模式,速率為2M GPIOB->CRL = (2<<0) | (0<<2);
 // PB0 輸出低電平,點亮LED GPIOB->ODR = 0<<0;}
void SystemInit(void){}

第二層級變化:

①、定義一個外設(GPIO)寄存器結構體,結構體的成員包含該外設的所有寄存器,成員的排列順序跟寄存器偏移地址一樣,成員的數據類型跟寄存器的一樣。

②外設內存映射,即把地址跟外設建立起一一對應的關系。

③外設聲明,即把外設的名字定義成一個外設寄存器結構體類型的指針。

④通過結構體操作寄存器,實現點亮LED。

第三層級:基地址宏定義+結構體封裝+“位封裝”(每一位的對應字節封裝)

上面我們在控制GPIO輸出內容的時候控制的是ODR(Output data register)寄存器,ODR是一個16位的寄存器,必須以字的形式控制其實我們還可以控制BSRR和BRR這兩個寄存器來控制IO的電平,下面我們簡單介紹下BRR寄存器的功能,BSRR自行看手冊研究。

9ec9f900-1242-11ed-ba43-dac502259ad0.png

位清除寄存器BRR只能實現位清0操作,是一個32位寄存器,低16位有效,寫0沒影響,寫1清0。現在我們要使PB0輸出低電平,點亮LED,則只要往BRR的BR0位寫1即可,其他位為0,代碼如下:

GPIOB->BRR = 0X0001;

這時PB0就輸出了低電平,LED就被點亮了。

如果要PB2輸出低電平,則是:

GPIOB->BRR = 0X0004;

如果要PB3/4/5/6。。。。。。這些IO輸出低電平呢?

道理是一樣的,只要往BRR的相應位置賦不同的值即可。因為BRR是一個16位的寄存器,位數比較多,賦值的時候容易出錯,而且從賦值的16進制數字我們很難清楚的知道控制的是哪個IO。

這時,我們是否可以把BRR的每個位置1都用宏定義來實現,如GPIO_Pin_0就表示0X0001,GPIO_Pin_2就表示0X0004。只要我們定義一次,以后都可以使用,而且還見名知意。“位封裝”(每一位的對應字節封裝) 代碼如下:


#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */

這時PB0就輸出了低電平的代碼就變成了:


GPIOB->BRR = GPIO_Pin_0;

如果同時讓PB0/PB15輸出低電平,用或運算,代碼:


GPIOB->BRR = GPIO_Pin_0|GPIO_Pin_15;

為了不使main函數看起來冗余,上述庫封裝 的代碼不應該放在main里面,因為其是跟GPIO相關的,我們可以把這些宏放在一個單獨的頭文件里面。

在工程目錄下新建stm32f10x_gpio.h,把封裝代碼放里面,然后把這個文件添加到工程里面。這時我們只需要在main.c里面包含這個頭文件即可。

第四層級:基地址宏定義+結構體封裝+“位封裝”+函數封裝

我們點亮LED的時候,控制的是PB0這個IO,如果LED接到的是其他IO,我們就需要把GPIOB修改成其他的端口,其實這樣修改起來也很快很方便。

但是為了提高程序的可讀性和可移植性,我們是否可以編寫一個專門的函數用來復位GPIO的某個位,這個函數有兩個形參,一個是GPIOX(X=A...G),另外一個是GPIO_Pin(0...15),函數的主體則是根據形參GPIOX 和GPIO_Pin來控制BRR寄存器,代碼如下:


void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){ GPIOx->BRR = GPIO_Pin;}

這時,PB0輸出低電平,點亮LED的代碼就變成了:


GPIO_ResetBits(GPIOB,GPIO_Pin_0);

同理, 我們可以控制BSRR這個寄存器來實現關閉LED,代碼如下:


// GPIO 端口置位函數void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){ GPIOx->BSRR = GPIO_Pin;}

這時,PB0輸出高電平,關閉LED的代碼就變成了:


GPIO_SetBits(GPIOB,GPIO_Pin_0);

同樣,因為這個函數是控制GPIO的函數,我們可以新建一個專門的文件來放跟gpio有關的函數。相關文章:STM32中GPIO工作原理詳解

在工程目錄下新建stm32f10x_gpio.c,把GPIO相關的函數放里面。這時我們是否發現剛剛新建了一個頭文件stm32f10x_gpio.h,這兩個文件存放的都是跟外設GPIO相關的。

C文件里面的函數會用到h頭文件里面的定義,這兩個文件是相輔相成的,故我們在stm32f10x_gpio.c 文件中也包含stm32f10x_gpio.h這個頭文件。別忘了把stm32f10x.h這個頭文件也包含進去,因為有關寄存器的所有定義都在這個頭文件里面。

如果我們寫其他外設的函數,我們也應該跟GPIO一樣,新建兩個文件專門來存函數,比如RCC這個外設我們可以新建stm32f10x_rcc.c和stm32f10x_rcc.h。其他外依葫蘆畫瓢即可。

實例編寫

以上,是對庫封住過程的概述,下面我們正在地使用庫函數編寫LED程序。

①管理庫的頭文件

當我們開始調用庫函數寫代碼的時候,有些庫我們不需要,在編譯的時候可以不編譯,可以通過一個總的頭文件stm32f10x_conf.h來控制,該頭文件主要代碼如下:

9eeb65cc-1242-11ed-ba43-dac502259ad0.png

這里面包含了全部外設的頭文件,點亮一個LED我們只需要RCC和GPIO 這兩個外設的庫函數即可,其中RCC控制的是時鐘,GPIO控制的具體的IO口。所以其他外設庫函數的頭文件我們注釋掉,當我們需要的時候就把相應頭文件的注釋去掉即可。

stm32f10x_conf.h這個頭文件在stm32f10x.h這個頭文件的最后面被包含,在第8296行:


#ifdef USE_STDPERIPH_DRIVER#include "stm32f10x_conf.h"#endif

代碼的意思是,如果定義了USE_STDPERIPH_DRIVER這個宏的話,就包含stm32f10x_conf.h這個頭文件。

我們在新建工程的時候,在魔術棒選項卡C/C++中,我們定義了USE_STDPERIPH_DRIVER 這個宏,所以stm32f10x_conf.h 這個頭文件就被stm32f10x.h包含了,我們在寫程序的時候只需要調用一個頭文件:stm32f10x.h即可。

②編寫LED初始化函數

經過寄存器點亮LED的操作,我們知道操作一個GPIO輸出的編程要點大概如下:

1、開啟GPIO的端口時鐘

2、選擇要具體控制的IO口,即pin

3、選擇IO口輸出的速率,即speed

4、選擇IO口輸出的模式,即mode

5、輸出高/低電平

STM32的時鐘功能非常豐富,配置靈活,為了降低功耗,每個外設的時鐘都可以獨自的關閉和開啟。STM32中跟時鐘有關的功能都由RCC這個外設控制,RCC中有三個寄存器控制著所以外設時鐘的開啟和關閉:RCC_APHENR、RCC_APB2ENR和RCC_APB1ENR,AHB、APB2和APB1代表著三條總線,所有的外設都是掛載到這三條總線上,GPIO屬于高速的外設,掛載到APB2總線上,所以其時鐘有RCC_APB2ENR控制。

GPIO 時鐘控制

固件庫函數:RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE)函數的原型為:


void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph,                              FunctionalState NewState){/* Check the parameters */ assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph)); assert_param(IS_FUNCTIONAL_STATE(NewState));if (NewState != DISABLE)  {  RCC->APB2ENR |= RCC_APB2Periph; } else {  RCC->APB2ENR &= ~RCC_APB2Periph; }}

當程序編譯一次之后,把光標定位到函數/變量/宏定義處,按鍵盤的F12或鼠標右鍵的Go to definition of,就可以找到原型。固件庫的底層操作的就是RCC外設的APB2ENR這個寄存器,宏RCC_APB2Periph_GPIOB的原型是:0x00000008,即(1<<3),還原成存器操作就是:RCC->APB2ENR |= 1<<<3。相比固件庫操作,寄存器操作的代碼可讀性就很差,只有才查閱寄存器配置才知道具體代碼的功能,而固件庫操作恰好相反,見名知意。

GPIO 端口配置

GPIO的pin,速度,模式,都由GPIO的端口配置寄存器來控制,其中IO0~IO7由端口配置低寄存器CRL控制,IO8~IO15由端口配置高寄存器CRH配置。固件庫把端口配置的pin,速度和模式封裝成一個結構體:


typedef struct{uint16_t GPIO_Pin; GPIOSpeed_TypeDef GPIO_Speed; GPIOMode_TypeDef GPIO_Mode;} GPIO_InitTypeDef;

pin可以是GPIO_Pin_0~GPIO_Pin_15或者是GPIO_Pin_All,這些都是庫預先定義好的宏。speed也被封裝成一個結構體:


typedef enum{ GPIO_Speed_10MHz = 1, GPIO_Speed_2MHz, GPIO_Speed_50MHz} GPIOSpeed_TypeDef;

速度可以是10M,2M或者50M,這個由端口配置寄存器的MODE位控制,速度是針對IO口輸出的時候而言,在輸入的時候可以不用設置。mode也被封裝成一個結構體:


typedef enum{ GPIO_Mode_AIN = 0x0, // 模擬輸入 GPIO_Mode_IN_FLOATING = 0x04, // 浮空輸入(復位后的狀態) GPIO_Mode_IPD = 0x28, // 下拉輸入 GPIO_Mode_IPU = 0x48, // 上拉輸入 GPIO_Mode_Out_OD = 0x14, // 通用開漏輸出 GPIO_Mode_Out_PP = 0x10, // 通用推挽輸出 GPIO_Mode_AF_OD = 0x1C, // 復用開漏輸出 GPIO_Mode_AF_PP = 0x18 // 復用推挽輸出} GPIOMode_TypeDef;

IO口的模式有8種,輸入輸出各4種,由端口配置寄存器的CNF配置。相關文章:STM32中GPIO工作原理詳解。平時用的最多的就是通用推挽輸出,可以輸出高低電平,驅動能力大,一般用于接數字器件。

最終用固件庫實現就變成這樣:


// 定義一個GPIO_InitTypeDef 類型的結構體GPIO_InitTypeDef GPIO_InitStructure;
// 選擇要控制的IO 口GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 設置引腳為推挽輸出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 設置引腳速率為50MHzGPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
/*調用庫函數,初始化GPIOB0*/GPIO_Init(GPIOB, &GPIO_InitStructure);

倘若同一端口下不同引腳有不同的模式配置,每次對每個引腳配置完成后都要調用GPIO初始化函數,代碼如下:


GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 ;                      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;          //上拉輸入GPIO_Init(GPIOB, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ;                     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;       //推挽輸出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO 輸出控制

GPIO輸出控制,可以通過端口數據輸出寄存器ODR、端口位設置/清除寄存器BSRR和端口位清除寄存器BRR這三個來控制。端口輸出寄存器ODR是一個32位的寄存器,低16位有效,對應著IO0~IO15,只能以字的形式操作,一般使用寄存器操作,相關文章請移步此處:C語言操作寄存器的常見手法


// PB0 輸出高電平,點亮LEDGPIOB->ODR = 1<<0;

端口位清除寄存器BRR是一個32位的寄存器,低十六位有效,對應著IO0~IO15,只能以字的形式操作,可以單獨對某一個位操作,寫1清0。


// PB0 輸出低電平,點亮LEDGPIO_ResetBits(GPIOB, GPIO_Pin_0);

BSRR是一個32位的寄存器,低16位用于置位,寫1有效,高16位用于復位,寫1有效,相當于BRR寄存器。高16位我們一般不用,而是操作BRR這個寄存器,所以BSRR這個寄存器一般用來置位操作。


// PB0 輸出高電平,熄滅LEDGPIO_SetBits(GPIOB, GPIO_Pin_0);

綜上:固件庫LED GPIO初始化函數。


void LED_GPIO_Config(void){// 定義一個GPIO_InitTypeDef 類型的結構體 GPIO_InitTypeDef GPIO_InitStructure;
// 開啟GPIOB 的時鐘 RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
// 選擇要控制的IO 口 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 設置引腳為推挽輸出 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 設置引腳速率為50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/*調用庫函數,初始化GPIOB0*/ GPIO_Init(GPIOB, &GPIO_InitStructure);
// 關閉LED GPIO_SetBits(GPIOB, GPIO_Pin_0);}

主函數


#include "stm32f10x.h"

void SOFT_Delay(__IO uint32_t nCount);void LED_GPIO_Config(void);
int main(void){// 程序來到main 函數之前,啟動文件:statup_stm32f10x_hd.s 已經調用// SystemInit()函數把系統時鐘初始化成72MHZ// SystemInit()在system_stm32f10x.c 中定義// 如果用戶想修改系統時鐘,可自行編寫程序修改
 LED_GPIO_Config();
while ( 1 )  {// 點亮LED  GPIO_ResetBits(GPIOB, GPIO_Pin_0);  Time_Delay(0x0FFFFF);
// 熄滅LED  GPIO_SetBits(GPIOB, GPIO_Pin_0);  Time_Delay(0x0FFFFF); }}// 簡陋的軟件延時函數void Time_Delay(volatile uint32_t Count){for (; Count != 0; Count--);}

注意void Time_Delay(volatile uint32_t Count)只是一個簡陋的軟件延時函數,如果小伙伴們有興趣可以看一看MultiTimer,它是一個軟件定時器擴展模塊,可無限擴展所需的定時器任務,取代傳統的標志位判斷方式, 更優雅更便捷地管理程序的時間觸發時序。


審核編輯 :李倩


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 單片機
    +關注

    關注

    6037

    文章

    44563

    瀏覽量

    635858
  • 寄存器
    +關注

    關注

    31

    文章

    5355

    瀏覽量

    120513
  • STM32
    +關注

    關注

    2270

    文章

    10904

    瀏覽量

    356324

原文標題:入手STM32單片機的知識點總結

文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    HAL標準的區別 HAL與CMSIS的關系

    在嵌入式系統開發中,HAL(硬件抽象層)標準是兩種常用的軟件,它們在功能和使用場景上有所不同。 1. 標準
    的頭像 發表于 12-02 14:02 ?763次閱讀

    HALSTM32開發中的重要性

    HAL(Hardware Abstraction Layer Library,硬件抽象層)在STM32開發中扮演著至關重要的角色。以下是HAL
    的頭像 發表于 12-02 13:35 ?372次閱讀

    使用STM32CubeIDE建的MP257F工程會出現大量缺現象,為什么?

    在使用STM32MP257開發板和STM32CubeIDE創建工程時,我遇到了在導入OpenAMP后編譯工程出現的問題。編譯時提示找不到某
    發表于 07-24 07:23

    STM32F10x標準外設參考手冊

    STM32F10x標準外設參考手冊
    發表于 06-24 09:26 ?11次下載

    STM32F0標準中缺失core_cm0.0是怎么回事?

    從論壇的資料區下載了STM32F0xx_StdPeriph_Lib_V1.5.0的固件。新建工程的時候發現找不到core_cm0.c的文件,只在
    發表于 05-15 06:39

    System Workbench for STM32軟件添加文件,點擊【Download target firmware】錯誤的原因?

    目前剛接觸System Workbench for STM32,建立工程中,添加標準文件,點擊【Download target firmware】錯誤,不知道怎樣添加離線的
    發表于 05-09 07:56

    STM32F0的SPL能在STM32G0上用嗎?

    原來用的STM32F0系列,使用的V3.5.0的標準外設。現在改用STM32G0系列,發現官網提供的固件
    發表于 04-08 08:29

    STM32的SBSFU安全用戶工程編譯報錯的原因?

    STM32的SBSFU安全用戶工程編譯報錯,有沒有大神搞過
    發表于 04-07 08:03

    STM32標準程序與HAL程序如何合并的?

    請問大家STM32標準款與HAL程序有沒有合并的教程,或是合并的方法?
    發表于 04-03 08:02

    STM32CubeMX創建STM32U5系列工程,DEBUG調試下為什么沒有ST-LINK選項?

    臺電腦上,打開之前用標準開發的工程STM32F407系列的工程)就有ST-LINK的選項,這個是什么原因?跟CubeMx有關系嗎?個人覺
    發表于 03-27 06:40

    STM32L471RE同樣的硬件使用LL和HAL編譯的工程運行功耗不一樣?為什么?

    項目在使用STM32L471RE開發,涉及到低功耗的處理。在測試過程中發現同樣的硬件結構(PCB上僅焊接mcu的最小系統),使用LL和HAL生產的兩個工程運行功耗不一樣,
    發表于 03-21 06:17

    STM32F429的工程標準移植到HAL后中斷向量表異常的原因?

    STM32F429的工程標準移植到HAL后中斷向量表異常
    發表于 03-20 06:02

    NUCLEO-U575ZI-Q開發板如何在keil5上建立工程,以及能否使用標準

    NUCLEO-U575ZI-Q開發板,如何在keil5上建立工程,以及能否使用標準(不能的情況下如何解決)
    發表于 03-19 06:42

    移植標準CLASSB中的STM32F10x_SelfTestLib時報錯是什么原因導致的?

    軟件安全相關認證,在移植標準CLASSB中的STM32F10x_SelfTestLib時,報以下兩個錯誤,請問是什么原因導致的? 對應這兩句匯編語言:
    發表于 03-18 08:30

    基于LL實現STM32U5 LPTIM功能

    有人打算使用STM32U5系列片內LPTIM做低功耗模式喚醒。基于STM32 LL組織代碼,折騰幾天后似乎連中斷都進不了,想找找是否有現存LL例程可以參考。
    的頭像 發表于 01-07 14:12 ?1587次閱讀
    基于LL<b class='flag-5'>庫</b>實現<b class='flag-5'>STM32</b>U5 LPTIM功能
    主站蜘蛛池模板: 99无码熟妇丰满人妻啪啪| 韩国无遮羞禁动漫在线观看| 亚洲字幕久久| 夫妻日本换H视频| 欧美一区二区视频高清专区| 伊人影院亚洲| 男人网站在线| 3344永久在线观看视频免费| 久久精品一区| 啊…嗯啊好深男男小黄文| 久青草国产在视频在线观看| chinesetoilet美女沟| 麻豆国产精品久久人妻| 影音先锋色av男人资源网| 嗯啊快拔出来我是你老师视频| 草699一码二码三码四码| 亚洲AV无码乱码A片无码蜜桃| 国产精品久久久久久久久无码 | 别插我B嗯啊视频免费| 亚洲地址一地址二地址三| 麻花传媒XK在线观看| 国产精品久久久久久久久免费下载 | 日本电影护士| 成年视频国产免费观看| 亚洲国产果果在线播放在线| 免费三级黄色| 国产情侣真实露脸在线| 亚洲国产精品天堂在线播放| 男男高h浪荡受h| 国产亚洲精品网站在线视频 | 九九99亚洲精品久久久久| 诱人的女邻居9中文观看| 日日噜噜夜夜狠狠视频| 成 人 网 站毛片| 伊人久久青青| 天美麻豆成人AV精品| 蜜桃最新网址| 久草大| 国产做国产爱免费视频| 曰本女人牲交视频免费| 午夜天堂一区人妻|