學習板:STM32F103ZET6
跑馬燈實驗
- 前言
- 一、GPIO
- 1、GPIO種類
- 2、GPIO輸入輸出的8種模式:
- 3、模式用途
- 二、點亮LED(庫函式版)
- 1、庫函式的由來
- 2、點亮LED庫函式版撰寫順序
- 1、GPIO_Init()
- 2、RCC_APB2PeriphClockCmd()
- 3、將配置函式放在led.c和led.h檔案
- 三、點亮LED(暫存器版本)
- 1、新建工程、添加led檔案
- 2、配置時鐘
- 3、GPIO配置
- 4、主函式代碼撰寫
- 附1 PXout()與PXin()
前言
本來準備先總結一下GPIO、幾種輸入輸出模式以及配置方法、暫存器種類、時鐘及分頻等,但是想了想,還是算了,一步步的來,到時候用到再總結吧 ,不然前面總結了,后面用到還得回顧,
至于前面的安裝庫、安裝軟體、Keil中添加檔案等,就不在總結了,因為這些根本并不需要記的,新手可以去跟著視頻走一遍,學習程序中,不用每次都自己去新建工程,直接將官方給的模板拷過來,修改一下檔案夾名稱即可,

一、GPIO
1、GPIO種類
本博板子STM32F103ZET6共有7組IO口,每組16個,共16×7=112個,分別為:
GPIOA——>PA0、PA1、PA2…PA15
GPIOB——>PB0、PB1、PB2…PB15
.
.
.
GPIOG——>PG0、PG1、PG2、…PG15
2、GPIO輸入輸出的8種模式:
| 程式中標識 | 模式 |
|---|---|
| GPIO_Mode_AIN | 模擬輸入 |
| GPIO_Mode_IN_FLOATING | 浮空輸入 |
| GPIO_Mode_IPD | 下拉輸入 |
| GPIO_Mode_IPU | 上拉輸入 |
| GPIO_Mode_Out_OD | 開漏輸出 |
| GPIO_Mode_Out_PP | 推挽輸出 |
| GPIO_Mode_AF_OD | 復用開漏輸出 |
| GPIO_Mode_AF_PP | 復用推挽輸出 |
3、模式用途
1、 GPIO_Mode_AIN :模擬輸入
一般用于ADC模擬輸入
2、GPIO_Mode_IN_FLOATING :浮空輸入
可用于按鍵KEY實驗、發送接收信號RX、TX等,不過這些實驗可以不用浮空輸入,如KEY用到上拉和下拉
3、GPIO_Mode_IPD:下拉輸入
4、GPIO_Mode_IPU:上拉輸入
IO內部上拉電阻、下拉電組輸入,使情況而定,比如剛剛說的key按鍵實驗,原理圖如下:

可以看到KEY_UP按下后,IO口應該是3V3電平輸入,使用上拉輸入模式,接到GPIO的上拉電阻,芯片可以更好的檢查到高電平輸入,
KEY0~2按下后,IO口是低電平輸入,使用下拉輸入模式,接到GPIO的下拉電阻后,芯片可以更好的檢查到低電平輸入,
5、GPIO_Mode_Out_OD:開漏輸出
IO 輸出 0 接 GND,IO 輸出 1,懸空,需要外接上拉電阻,才能實作輸出 高電平,當輸出為 1 時,IO 口的狀態由上拉電阻拉高電平,但由于是開漏輸出模式,這樣 IO 口也就可以 由外部電路改變為低電平或不變,該模式適合做電流型的驅動,吸收電流能力比較強,
6、GPIO_Mode_Out_PP:推挽輸出
可以輸出高、低電平,導通損耗小、效率高,既提高電路的負載能力,又提高開關速度,廣泛各種實驗,比如接下來要總結的LED,
7、GPIO_Mode_AF_OD:復用開漏輸出
當GPIO為復用IO時的開漏輸出模式,一般用于外設功能,如TX1
8、GPIO_Mode_AF_PP:復用推挽輸出
當GPIO為復用IO時的推挽輸出模式,一般用于外設功能,如I2C
二、點亮LED(庫函式版)
1、庫函式的由來
在學習庫函式之前,應該明白,STM32F1用的是Cortex-M3芯片,是由ARM公司設計的,所以芯片的標準是由ARM公司制定的,芯片內核架構有ARM公司提供,而我們現在用的STM32由ST公司生產,所以關系是:ARM制定內核架構,ST等芯片公司根據ARM公司的標準設計了芯片,ST等公司設計的芯片,不同的是存盤容量、外設、串口數量等等,
以本博的學習板STM32F103ZET6為例,韌體庫(庫函式的集合)是由官方提供的,這個官方是ST公司,而不是正點原子官方,也就是說不僅僅這一型號單片機,ST系列其他型號的單片機庫函式依舊可以適用,所以不必擔心更換板子后不知如何去編程,
ST公司推出官方韌體庫,將底層暫存器操作都封裝起來,形成一套介面(API)供我們使用,大多數情況下我們不必去考慮底層暫存器,比如本博的LED,只需呼叫GPIO配置函式、時鐘配置函式,然后主函式初始化后,直接給引腳賦值就可以實作LED的亮滅,而不用去考慮暫存器如何作業的,當然本博會把暫存器版的LED也總結一下,畢竟想要真正理解單片機,還得去真正理解暫存器,庫函式版只是讓我們停留在“會使用”,當然,對應大多數人來講,“會使用”已經完全足夠了,
2、點亮LED庫函式版撰寫順序
1、設定時鐘
2、設定GPIO
只要這兩步的配置,再在主函式中給對應引腳傳輸高低電平即可,
打開原理圖檔案(下圖我打開了6個檔案,都是需要的,而且大部分情況下,有這6個檔案足以,都在板子附帶資料的檔案夾里)


從原理圖中得到以下資訊:
①DS0 LED0陽極接+,陰極接PB5;DS1 LED1陽極接+,陰極接PE5,
②SYS LED由名稱“PWR”顧名思義,為電源指示燈,所以單片機接通電源后,電源指示燈常亮,
③芯片的PB5引腳軟體置0后,LED0亮;PE5引腳軟體置0后,LED1亮,
所以要配置GPIOB(因為PB5)和GPIOE(因為PE5),
然后是時鐘設定,只要是對GPIO操作,就必須進行時鐘配置(而且時鐘配置在前),GPIO是掛載在APB2總線上的外設,所以在對GPIO的時鐘進行設定時,通過函式RCC_APB2PeriphClockCmd()來實作,
下面進入實戰:
打開模板檔案:(時間久遠了,不知道模板檔案原來放哪個檔案夾下,找不到的話可以把LED官方例程打開,關于LED的.c和.h檔案刪掉,主函式清空,就可以當以后的模板來用了,不用每次都創建工程)


首先查看GPIO配置函式,既然是GPIO,那么先找一下頭檔案,在main.c下找GPIO頭檔案,并點擊進入,


找到對應函式:(下一博客總結所有GPIO函式的用法、以及延時函式)

上圖示注,GPIO_Init()函式初始化,進行設定GPIO,GPIO_SetBits()函式給對應引腳置1,GPIO_ReSetBits()函式給對應引腳置0,
1、GPIO_Init()
右鍵選中函式,點擊【Go to Definition of …】,進入函式詳細說明


可以看到,函式的形參有兩個,而且都是指標,進入第一個形參“型別”


看到GPIOx指標是指向上圖這個結構體的,也就是每組GPIO都包含的7個暫存器,
比如LED實驗,傳遞GPIOB(PB5)過來后,*GPIOB就指向這七個暫存器,初始化函式就是對七個暫存器的操作,不過被庫函式封裝起來了,emmmm…說太多了,只要知道GPIO_Init()傳過來的第一個引數表示對該組GPIO配置就行了,
察看第二組形參“型別”


看到第二個形參也是結構體指標,指向的結構體含有三個引數GPIO_Pin、GPIO_Speed、GPIO_Mode
到這里就可以用C++語法來說明了,比如第二個形參是a(注意是指標),那么:
a.GPIO_Pin=…
a.GPIO_Speed=…
a.GPIO_Mode=…
就完成了對引數GPIO引數的設定,
接下來我們再看看上面三個賦值陳述句的右邊究竟是什么東西:
轉回到初始化函式:

1表示第二個形參
2表示對 GPIO_Init()的第一個形參的處理(就是那個結構體里有7個暫存器的東西)
3表示mode的配置
4表示pin的配置
5表示速speed的配置,
點開GPIO_Mode設定函式:

可以看到就是我們第一大部分總結的8中輸入輸出模式

點開pin設定函式

可以看到pin是我們第一大部分總結的一組GPIO的15個IO口

點開速度設定函式

可以看到速度可設定的值:

到這里,GPIO的設定函式應該會寫了:
第一步:設定形參1和形參2
第二步:上面那三個賦值陳述句的設定
第三步:運行GPIO_Init()函式
程式:(先在主函式中書寫,.c檔案中書寫接下來會總結)
GPIO_TypeDef GPIO_B;//形參1
GPIO_InitTypeDef GPIO_InitStruct;//形參2
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//形參2.mode=推挽輸出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//形參2.pin=5
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//形參2.speed=50MHZ
GPIO_Init(&GPIO_B,&GPIO_InitStruct);
注意GPIO_Init()傳遞的是指標,所以應該用取地址符“&”,
上面的程式還是有問題的,因為定義了GPIO_B為第一個引數,但是程式并不知道GPIO_B是對GPIOB的操作,所以在 GPIO_Init(&GPIO_B,&GPIO_InitStruct);陳述句中,“&GPIO_B”應該是真正的、物理上的地址,而不能像引數2一樣,只是程式定義引數時分配的地址,
輸入“GPIOB”,并進入

發現官方真的定義了GPIOB,而且還是真正的、物理層的地址



所以之前程式中的GPIO_B可以刪掉了,不是物理層的地址,定義了、傳遞給GPIO_Init()函式也沒用,
正確完整程式:
//GPIO_TypeDef GPIO_B;//形參1
GPIO_InitTypeDef GPIO_InitStruct;//形參2
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//形參2.mode=推挽輸出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//形參2.pin=5
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//形參2.speed=50MHZ
GPIO_Init(GPIOB,&GPIO_InitStruct);
//注意GPIOB是地址!GPIO_InitStruct是指標,傳遞過去后的倆個實引數都是指標,
以上配置了GPIOB是為了點亮LED0,現配置LED1(PE5)的GPIO,參考上面的程式:
GPIO_InitTypeDef GPIO_InitStruct;//形參2
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//形參2.mode=推挽輸出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//形參2.pin=5
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//形參2.speed=50MHZ
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOE,&GPIO_InitStruct);
因為之前設定了mode和speed,而實參2是沒有指向的,即并不能知道實參2屬于實參1,所以哪怕再重新定義一個GPIOE的實參2,重新定義mode和speed也沒有意義,所以就可以省略了,
接下來可以將LED引腳置高電平,熄滅LED,使初始狀態下LED是滅的,
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct;//形參2
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//形參2.mode=推挽輸出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//形參2.pin=5
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//形參2.speed=50MHZ
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOE,&GPIO_InitStruct);
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
}
2、RCC_APB2PeriphClockCmd()
之前說過,GPIO是掛載在APB2總線上的外設,所以在對GPIO的時鐘進行設定時,通過函式RCC_APB2PeriphClockCmd()來實作,打開RCC.h頭檔案,找到時鐘函式

同樣的方法確定形參型別


形參1:

形參2:

所以程式:(注意時鐘配置函式應該放在最前面)
GPIO_InitTypeDef GPIO_InitStruct;//形參2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOE , ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//形參2.mode=推挽輸出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//形參2.pin=5
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//形參2.speed=50MHZ
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOE,&GPIO_InitStruct);
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
加入延時函式,形成流水燈:(只在Main.c檔案編程)
其中GPIO_SetBits(GPIOB, GPIO_Pin_5)是將PB5引腳置1;GPIO_ReSetBits(GPIOB, GPIO_Pin_5)是將PB5引腳置0
(是通過庫函式對BSRR和BRR暫存器操作完成置0置1,下一博客會涉及到)
#include "stm32f10x.h"
#include "delay.h"
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct;//形參2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOE , ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//形參2.mode=推挽輸出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//形參2.pin=5
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//形參2.speed=50MHZ
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOE,&GPIO_InitStruct);
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
delay_init(); //延時函式初始化
while(1)
{
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
delay_ms(1000); //注意包含頭檔案delay.h,這個好像是正點原子官方寫的
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
GPIO_ResetBits(GPIOE, GPIO_Pin_5);
delay_ms(1000);
}
}
3、將配置函式放在led.c和led.h檔案
在LED使用的工程檔案夾新建一個LED檔案夾

進行下一步驟:




創建一個text檔案,命名為led.h,保存在LED group中

將頭檔案添加進來




同理,新建一個led.c檔案,將.c檔案也添加進來

上述步驟是創建一個LED Group,現在將頭檔案添加進來:




找到剛剛創建的檔案夾并添加

上述步驟是為了之后呼叫里面生成的led.c和led.h
編輯頭檔案:
固定格式:
#ifndef 一個未定義字串
#define 一個未定義字串
#include ...
#include ... //各種需要在本.h檔案中用到的頭檔案
...
...//一些函式宣告、甚至定義
#endif
本實驗led.h檔案可這樣寫:
#ifndef __LED_H //led.h檔案
#define __LED_H
void LED_Init(void);//初始化
#endif
接下來編輯led.c檔案
需要有本.c檔案用到的頭檔案,如果要用到別的檔案中定義的變數,可以采用外部宣告重新宣告該變數,在.c檔案實作.h檔案宣告的函式
將我們之前main函式中關于GPIO配置和時鐘宣告的函式移植過來得到完整的LED程式:



/**led.h**/
#ifndef __LED_H //led.h檔案
#define __LED_H
void LED_Init(void);//初始化
#endif
/**led.c**/
#include "led.h"
#include "stm32f10x.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;//形參2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOE , ENABLE);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//形參2.mode=推挽輸出
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//形參2.pin=5
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;//形參2.speed=50MHZ
GPIO_Init(GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOE,&GPIO_InitStruct);
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
}
/**main.c**/
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
int main(void)
{
LED_Init();
delay_init(); //延時函式初始化
while(1)
{
GPIO_SetBits(GPIOB, GPIO_Pin_5);
GPIO_SetBits(GPIOE, GPIO_Pin_5);
delay_ms(1000); //注意包含頭檔案delay.h,這個好像是正點原子官方寫的
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
GPIO_ResetBits(GPIOE, GPIO_Pin_5);
delay_ms(1000);
}
}
三、點亮LED(暫存器版本)
1、新建工程、添加led檔案
畢竟是32系列第一個博客,還是希望詳細一點,之后的實驗就會慢慢省略一部分東西,暫存器版本完整來一遍
打開模板檔案,如果沒有,就把LED庫函式例程打開,刪掉led.c和led.h,當做以后所有實驗的模板就行了,
一般情況下,應該【HARDWARE】中創建.c檔案,也可以在檔案【HARDWARE】之外建,看個人習慣,
如果懶得去搞模板,就用我這個吧
先將模板檔案拷過來,在HARDWARE檔案夾下創建LED檔案夾

打開模板檔案

新建兩個text檔案,保存在剛剛創建的LED檔案夾下,并改名為led.c、led.h

將led.c檔案添加進工程


將LED檔案目錄添加進來





現在進入實戰
led.h編輯,之前庫函式版本講過了,直接附代碼:
#ifndef LED_H //led.h檔案
#define LED_H
int LED_Init(void);
#endif
led.c檔案編輯
開始還是老規矩:
#include "led.h"
#include "stm32F10x.h"
int LED_Init(void)
{
}
2、配置時鐘
打開《STM32中文參考手冊》7.3.7 APB2外設時鐘使能暫存器(RCC_APB2ENR)


暫存器下一博客總結,現在只需知道,APB2外設時鐘使能暫存器的第3位和第6位分別對應GPIOB(LED0、DS0)和GPIOE(LED1、DS1)
時鐘使能代碼如下:
RCC->APB2ENR|=1<<3;
RCC->APB2ENR|=1<<6;
解釋一下:
首先RCC->APB2ENR是對外設時鐘使能暫存器的訪問

RCC->APB2ENR|=1的意思是:RCC->APB2ENR=RCC->APB2ENR|0x00000001(32位暫存器),也就是說將該暫存器的第0位軟體置1,其它位保持不變,"<<3"是將剛剛設定的那個第0位的1左移3位,也就是此時第3位為1.同理“<<6”是將第6位設定為1;此時就使能了GPIOB和GPIOE的時鐘

3、GPIO配置
用到埠配置暫存器,由于是對PB5、PE5的配置,是低位IO口(Px0~Px7是低位、Px8 ~Px15是高位),所以用到埠配置低暫存器GPIOx_CRL
打開《STM332中文參考手冊》

看到第21、20位控制模式和速度,為50M輸出,所以這兩位是11;23 、22控制哪種輸出,為推挽輸出,所以這兩位為00,所以GPIOx_CRL的狀態值為:0x00300000;程式如下:
GPIOB->CRL&=0xff0fffff; //PB5
GPIOB->CRL|=0x00300000;
GPIOE->CRL&=0xff0fffff; //PE5
GPIOE->CRL|=0x00300000;
解釋一下:
GPIOB->CRL&=0xff0fffff 是將GPIOB的20、21、22、23這四位置0,其它位保持不變;
GPIOB->CRL|=0x00300000是將GPIOB的20、21、22、23這四位置1,其它位保持不變;
此時配置好了,然后可以給IO口賦初值,如開始時讓LED處于熄滅狀態,則PB5、PE5均置1,用到的暫存器:埠輸出資料暫存器GPIOx_ODR

代碼:
GPIOB->ODR|=1<<5;
GPIOE->ODR|=1<<5;
led.c檔案完整代碼:
#include "led.h" //led.c檔案
#include "stm32F10x.h"
int LED_Init(void)
{
RCC->APB2ENR|=1<<3;
RCC->APB2ENR|=1<<6;
GPIOB->CRL&=0xff0fffff; //PB5
GPIOB->CRL|=0x00300000;
GPIOE->CRL&=0xff0fffff; //PE5
GPIOE->CRL|=0x00300000;
GPIOB->ODR|=1<<5;
GPIOE->ODR|=1<<5;
}
4、主函式代碼撰寫
進入主函式后,首先應呼叫剛剛寫的LED初始化函式,完成GPIO配置;程式會用到延時函式,將延時函式也初始化,代碼:(頭檔案包含led.h)
#include "sys.h"
#include "delay.h"
#include "led.h"
int main(void)
{
LED_Init();
delay_init();
}
然后在死回圈中,對PB5和PE5 IO口賦值就行了,還是用到埠輸出資料暫存器GPIOx_ODR
不過給IO口置0時,需要注意,應該和0xffffffdf進行與運算

GPIOB->ODR&=0xffffffdf;//置0
GPIOE->ODR&=0xffffffdf;//置0
或者移位運算,將第0位置0再向左移5位
GPIOB->ODR&=0xfffffffe<<5;
GPIOE->ODR&=0xfffffffe<<5;
給IO口置1就和0x00000020進行或運算

GPIOB->ODR|=0x00000020;
GPIOE->ODR|=0x00000020;
或者直接位移運算,先和0x00000001進行或運算,使第0位置1,再將第0位向左移動5,代碼:
GPIOB->ODR|=1<<5;
GPIOE->ODR|=1<<5;
main.c檔案完整程式:
#include "sys.h" //main.c檔案
#include "delay.h"
#include "led.h"
int main(void)
{
LED_Init();
delay_init();
while(1)
{
GPIOB->ODR&=0xffffffdf;//置0
GPIOE->ODR&=0xffffffdf;//置0
//GPIOB->ODR&=0xfffffffe<<5;
//GPIOE->ODR&=0xfffffffe<<5;
delay_ms(1000);
GPIOB->ODR|=0x00000020;
GPIOE->ODR|=0x00000020;
//GPIOB->ODR|=1<<5;
//GPIOE->ODR|=1<<5;
delay_ms(1000);
}
}
附1 PXout()與PXin()
打開sys.h頭檔案

定義了PXout()與PXin()函式,用法為:
點亮熄滅DS0、DS1,只需:
PBout(5)=0;//點亮
PEout(5)=0;//點亮
delay_ms(1000);
PBout(5)=1;//熄滅
PEout(5)=1;//熄滅
delay_ms(1000);
或者定義:
#define LED0 PBout(5)
#define LED1 PEout(5)
LED0=0;//點亮
LED1=0;//點亮
delay_ms(1000);
LED0=1;//熄滅
LED1=1;//熄滅
delay_ms(1000);
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/281630.html
標籤:其他
