3 GPIO通用輸入輸出口
注:筆記主要參考B站 江科大自化協(xié) 教學(xué)視頻“STM32入門教程-2023持續(xù)更新中”。
注:工程及代碼文件放在了本人的Github倉庫。
3.1 GPIO輸入輸出原理
GPIO(General Purpose Input Output)通用輸入輸出口 可配置為8種輸入輸出模式。引腳電平范圍為0V~3.3V,部分引腳可容忍5V(圖1-6中IO口電平為FT標(biāo)識的)。輸出模式 下可控制端口輸出高低電平,用以驅(qū)動LED、控制蜂鳴器、模擬通信協(xié)議輸出時序等,當(dāng)然若驅(qū)動大功率設(shè)備還需要添加驅(qū)動電路。輸入模式 下可讀取端口的高低電平或電壓,用于讀取按鍵輸入、外接模塊電平信號輸入、ADC電壓采集、模擬通信協(xié)議接收數(shù)據(jù)等。

上圖給出了GPIO的基本結(jié)構(gòu)圖。在STM32中,所有的GPIO都掛載在APB2外設(shè)總線上。命名方式采用GPIOA、GPIOB、GPIOC…的方式來命名。每個GPIO模塊內(nèi),主要包括寄存器、驅(qū)動器等。
- 寄存器就是一段特殊的存儲器,內(nèi)核可以通過APB2總線對寄存器進(jìn)行讀寫,從而完成輸出電平和讀取電平的功能。該寄存器的每一位都對應(yīng)一個引腳,由于stm32是32位的單片機(jī),所以所有的寄存器都是32位的,也就是說只有寄存器的低16位對應(yīng)上了相應(yīng)的GPIO口。
- 驅(qū)動器就是增加信號的驅(qū)動能力的。
注:stm32f103c8t6芯片上48個引腳,除了基本的電源和晶振等維持系統(tǒng)正常運(yùn)行的引腳外,分別包括PA0~PA15、PB0~PB15、PC13~PC15。

上圖就是將“GPIO的基本結(jié)構(gòu)”進(jìn)行放大,得到的實(shí)際的位結(jié)構(gòu)。
輸入部分:
- 整體框架從左到右依次是寄存器、驅(qū)動器、IO引腳,從上到下分為“輸入”、“輸出”。
- 最右側(cè)的IO引腳上兩個保護(hù)二極管,其作用是對IO引腳的輸出電壓進(jìn)行限幅在0~3.3V之間,進(jìn)而可以避免過高的IO引腳輸入電壓對電路內(nèi)部造成傷害。VDD=3.3V,VSS=0V。
- 輸入驅(qū)動器的上、下拉電阻:相應(yīng)的兩個開關(guān)可以通過程序進(jìn)行配置,分別有上拉輸入模式(上開關(guān)導(dǎo)通&下開關(guān)斷開)、下拉輸入模式(下開關(guān)導(dǎo)通&上開關(guān)斷開)、浮空輸入模式(兩個開關(guān)都斷開)。上下拉電阻的作用就是給引腳輸入提供一個默認(rèn)的輸入電平,進(jìn)而避免引腳懸空導(dǎo)致的不確定。都屬于弱上拉、弱下拉。
- 輸入驅(qū)動器的觸發(fā)器:這里是用肖特基管構(gòu)成的施密特觸發(fā)器。只有高于上限、低于下限電壓才進(jìn)行變化,作用是對輸入電壓進(jìn)行整形,可以消除電壓波紋、使電壓的上升沿/下降沿更加陡峭。也就是說,stm32的GPIO端口會自動對輸入的數(shù)字電壓進(jìn)行整形。
- “模擬輸入”、“復(fù)用功能輸入”:都是連接到片上外設(shè)的一些端口,前者用于ADC等需要模擬輸入的外設(shè),后者用于串口輸入引腳等需要數(shù)字量的外設(shè)。
輸出部分:
- 輸出數(shù)據(jù):可以由輸出數(shù)據(jù)寄存器(普通的IO口輸出)、片上外設(shè)來指定,數(shù)據(jù)選擇器控制數(shù)據(jù)來源。
- 位設(shè)置/清除寄存器:單獨(dú)操作輸出數(shù)據(jù)的某一位,而不影響其他位。
- 驅(qū)動器中的MOS管:MOS管相當(dāng)于一種開關(guān),輸出信號來控制這兩個MOS管的開啟狀態(tài),進(jìn)而輸出信號。可以選擇推挽、開漏、關(guān)閉三種輸出方式。
- 推挽輸出模式:兩個MOS管均有效,stm32對IO口有絕對的控制權(quán),也稱為強(qiáng)推輸出模式。
- 開漏輸出模式:P-MOS無效。只有低電平有驅(qū)動能力,高電平輸出高阻。
- 關(guān)閉模式:兩個MOS管均無效,端口電平由外部信號控制。
額外補(bǔ)充:stm32如何將數(shù)據(jù)寫入寄存器?
- 通過軟件的方式。由于stm32的寄存器只能進(jìn)行整體讀寫,所以可以先將數(shù)據(jù)全部讀出,然后代碼中用
&=
清零、|=
置位的方式改變單獨(dú)某一位的數(shù)據(jù),再將改寫后的數(shù)據(jù)寫回寄存器。此方法比較麻煩、效率不高,對于IO口進(jìn)行操作不合適。- 通過位設(shè)置/清除寄存器。若對某一位 置1,只需對位設(shè)置寄存器的相應(yīng)位 置1;若對某一位 清零,則對清除寄存器相應(yīng)位 清零。這種方式通過內(nèi)置電路完成操作,一步到位。
- 通過讀寫STM32中的“位帶”區(qū)域。在STM32中,專門分配有一段地址區(qū)域,該區(qū)域映射了RAM和外設(shè)寄存器所有的位。讀寫這段地址中的數(shù)據(jù),就相當(dāng)于讀寫所映射位置的某一位。整體流程與51單片機(jī)中的位尋址作用差不多。本教程不涉及。
模式名稱 | 性質(zhì) | 特征 |
---|---|---|
浮空輸入 | 數(shù)字輸入 | 可讀取引腳電平,若引腳懸空則電平不確定,需要連續(xù)驅(qū)動源 |
上拉輸入 | 數(shù)字輸入 | 可讀取引腳電平,內(nèi)部連接上拉電阻,懸空時默認(rèn)高電平 |
下拉輸入 | 數(shù)字輸入 | 可讀取引腳電平,內(nèi)部連接下拉電阻,懸空時默認(rèn)低電平 |
模擬輸入 | 模擬輸入 | GPIO無效,引腳直接接入內(nèi)部ADC(ADC專屬配置) |
開漏輸出 | 數(shù)字輸出 | 可輸出引腳電平,高電平為高阻態(tài),低電平接VSS |
推挽輸出 | 數(shù)字輸出 | 可輸出引腳電平,高電平接VDD,低電平接VSS |
復(fù)用開漏輸出 | 數(shù)字輸出 | 由片上外設(shè)控制,高電平為高阻態(tài),低電平接VSS |
復(fù)用推挽輸出 | 數(shù)字輸出 | 由片上外設(shè)控制,高電平接VDD,低電平接VSS |
上表給出了GPIO的8種模式,通過配置GPIO的端口配置寄存器即可選擇相應(yīng)的模式。
- 每一個端口的模式由4位進(jìn)行控制,16個端口就需要64位,也就是兩個32位寄存器,即端口配置低寄存器、端口配置高寄存器。
- 輸入模式下,輸出無效;而輸出模式下,輸入有效。這是因?yàn)橐粋€IO口只能有一個輸出,但可以有多個輸入,所以直接將輸出信號輸入回去也沒問題。
3.2 硬件介紹-LED、蜂鳴器、面包板
首先,簡單介紹一下stm32芯片外圍的電路。
- LED:發(fā)光二極管,正向通電點(diǎn)亮,反向通電不亮。
- 有源蜂鳴器(本實(shí)驗(yàn)):內(nèi)部自帶振蕩源,將正負(fù)極接上直流電壓即可持續(xù)發(fā)聲,頻率固定。上圖所示的蜂鳴器模塊使用三極管作為開關(guān)。
- 無源蜂鳴器:內(nèi)部不帶振蕩源,需要控制器提供振蕩脈沖才可發(fā)聲,調(diào)整提供振蕩脈沖的頻率,可發(fā)出不同頻率的聲音。
- 下面是其實(shí)物圖:
![]()
注:LED長腳為正極、燈內(nèi)部小頭為正極。本實(shí)驗(yàn)的蜂鳴器低電平驅(qū)動。

上圖則是給出了LED和蜂鳴器的驅(qū)動電路圖。注意,三極管的發(fā)射極一定要直接接正電源/地,這是因?yàn)槿龢O管的開啟需要發(fā)射極和基極之間有一定的電壓,如果接在負(fù)載側(cè)有可能會導(dǎo)致三極管無法正常開啟。

上圖給出了面包板的示意圖??梢钥闯觯姘逯虚g的金屬爪是豎著排列的,用于插各種元器件;上下四排金屬爪是橫著排列的,一般用于供電。注意,在使用面包板之前,一定要觀察孔位的連接情況。
3.3 實(shí)驗(yàn):LED閃爍、LED流水燈、蜂鳴器提示
需求1: 面包板上的LED以1s為周期進(jìn)行閃爍。亮0.5s、滅0.5s……
- LED低電平驅(qū)動。
- 需要用到延時函數(shù)
Delay.h
、Delay.c
,在UP注提供的“程序源碼”中,為了方便管理,應(yīng)在工程內(nèi)創(chuàng)建System文件夾,專門存放這些可以復(fù)用的代碼。

注:實(shí)際上,應(yīng)該在LED和驅(qū)動電源之間接上保護(hù)電阻,但是由于本電路過于簡單,于是直接省略保護(hù)電阻。后面“LED流水燈”、“蜂鳴器提示”實(shí)驗(yàn)同樣省略保護(hù)電阻。

代碼展示:
- main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void){
// 開啟APB2-GPIOA的外設(shè)時鐘RCC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 初始化PA0端口:定義結(jié)構(gòu)體及參數(shù)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//下面是對GPIO端口賦值的常用的四種方式
// GPIO_ResetBits(GPIOA, GPIO_Pin_0);//復(fù)位PA0
// GPIO_SetBits(GPIOA, GPIO_Pin_0);//將PA0置1
// GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET);//將PA0清零
// GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);//此函數(shù)可以對16位端口同時操作
while(1){
//正常思路
GPIO_ResetBits(GPIOA, GPIO_Pin_0);//復(fù)位PA0
Delay_ms(500);
GPIO_SetBits(GPIOA, GPIO_Pin_0);//將PA0置1
Delay_ms(500);
//使用GPIO_WriteBit函數(shù),且強(qiáng)制類型轉(zhuǎn)換
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0);//把0類型轉(zhuǎn)換成BitAction枚舉類型
Delay_ms(500);
GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1);
Delay_ms(500);
};
}
- Delay.h
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
- Delay.c
#include "stm32f10x.h"
/**
* @brief 微秒級延時
* @param xus 延時時長,范圍:0~233015
* @retval 無
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //設(shè)置定時器重裝值
SysTick->VAL = 0x00; //清空當(dāng)前計數(shù)值
SysTick->CTRL = 0x00000005; //設(shè)置時鐘源為HCLK,啟動定時器
while(!(SysTick->CTRL & 0x00010000));//等待計數(shù)到0
SysTick->CTRL = 0x00000004; //關(guān)閉定時器
}
/**
* @brief 毫秒級延時
* @param xms 延時時長,范圍:0~4294967295
* @retval 無
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒級延時
* @param xs 延時時長,范圍:0~4294967295
* @retval 無
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
注:此后Delay.h
、Delay.c
將作為常用函數(shù)長期存放于System文件夾
中,后續(xù)如果使用到將直接調(diào)用不會再在筆記中展示源代碼。
編程感想:
- Keil編譯過后,整個工程會比較大,不利于分享給別人??梢允褂肬P主提供的批處理程序,刪掉工程中的中間文件后再分享給別人,其他人使用的時候只需要重新編譯一下就行。
- 本教程用到了RCC和GPIO兩個外設(shè),這些外設(shè)的庫函數(shù)在Library中,一般存放在相應(yīng)的 .h 文件的最后。
- 將LED的短腳接負(fù)極,長腳接PA0口,就是高電平驅(qū)動方式,但是現(xiàn)象和低電平相同。
- 將GPIO設(shè)置成開漏輸出模式,可以發(fā)現(xiàn)高電平(高阻態(tài))無驅(qū)動能力,低電平有驅(qū)動能力。
需求2: 面包板上的8個LED以0.5s切換一個的速度,實(shí)現(xiàn)流水燈。低電平驅(qū)動。

代碼調(diào)用關(guān)系與“LED閃爍”實(shí)驗(yàn)相同,下面是代碼展示:
- main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void){
// 開啟APB2-GPIOA的外設(shè)時鐘RCC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 初始化PA的8個端口:定義結(jié)構(gòu)體及參數(shù)
GPIO_InitTypeDef GPIO_InitStructure;
//同時定義某幾個端口
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 |
GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
while(1){
//使用GPIO_SetBits、GPIO_ResetBits進(jìn)行賦值,這里僅用于演示“或操作”同時賦值
GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 |
GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7);
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
// //對指定的端口同時賦值
// GPIO_Write(GPIOA, ~0x01);
Delay_ms(500);
GPIO_Write(GPIOA, ~0x02);
Delay_ms(500);
GPIO_Write(GPIOA, ~0x04);
Delay_ms(500);
GPIO_Write(GPIOA, ~0x08);
Delay_ms(500);
GPIO_Write(GPIOA, ~0x10);
Delay_ms(500);
GPIO_Write(GPIOA, ~0x20);
Delay_ms(500);
GPIO_Write(GPIOA, ~0x40);
Delay_ms(500);
GPIO_Write(GPIOA, ~0x80);
Delay_ms(500);
};
}
編程感想:
- 使用或操作
|
就可以實(shí)現(xiàn)只初始化定義某幾個GPIO,或者某幾個外設(shè)的時鐘。
需求3: 蜂鳴器不斷地發(fā)出滴滴、滴滴……的提示音。蜂鳴器低電平觸發(fā)。
注:蜂鳴器執(zhí)行四個動作為1個周期,分別是響0.1s、靜0.1s、響0.1s、靜0.7s。

代碼調(diào)用關(guān)系與“LED閃爍”實(shí)驗(yàn)相同,下面是代碼展示:
- main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
int main(void){
// 開啟APB2-GPIOB的外設(shè)時鐘RCC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// 初始化PB12端口:定義結(jié)構(gòu)體及參數(shù)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
while(1){
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
Delay_ms(100);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
Delay_ms(100);
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
Delay_ms(100);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
Delay_ms(100);
Delay_ms(600);
};
}
編程感想:
- 控制蜂鳴器的IO端口可以隨便選,但是不要選擇三個JTAG調(diào)試端口:PA15、PB3、PB4。本實(shí)驗(yàn)選擇PB12端口進(jìn)行輸出。
- 關(guān)于調(diào)用庫函數(shù),有以下幾種方法:
- 直接查看每一個外設(shè)的
.h
函數(shù),拖到最后就可以看到本外設(shè)的所有庫函數(shù),然后在對應(yīng)的.c文件中查看函數(shù)定義和調(diào)用方式即可。- 查看庫函數(shù)的用戶手冊——“STM32F103xx固件函數(shù)庫用戶手冊.pdf”,這個中文版比較老;新版本的用戶手冊可以在ST公司的幫助文檔中查看,但只有英文版。
- 百度一下別人的代碼。
3.4 硬件介紹-按鍵開關(guān)、光敏電阻

按鍵是最常見的輸入設(shè)備,按下導(dǎo)通,松手?jǐn)嚅_。由于按鍵內(nèi)部使用的是機(jī)械式彈簧片來進(jìn)行通斷的,所以在按下和松手的瞬間會伴隨有一連串的抖動。
雖然前面已經(jīng)說過,GPIO端口有專門的肖特基觸發(fā)器對輸入信號進(jìn)行整形,但按鍵開關(guān)的抖動幅度大、時間長,所以還是 需要“軟件消抖”?;舅悸肪褪茄舆t5~10ms,跳過抖動時間范圍即可。

上圖給出了按鍵開關(guān)的硬件電路設(shè)計圖。對于按鍵開關(guān)來說,常見以上四種設(shè)計方法,而行業(yè)規(guī)范中,單片機(jī)端口一般都有 上拉輸入模式(弱上拉),所有基本上就選擇 內(nèi)部上拉/外部上拉 的設(shè)計電路。
- 如果電路同時存在內(nèi)部上拉和外部上拉,那么其高電平的驅(qū)動能力更強(qiáng),但是低電平會更加耗電。兩個下拉電路則可以使低電平驅(qū)動能力更強(qiáng),而不會明顯增加損耗。
- 浮空輸入模式下,每部沒有上下拉,此時必須在外部有上下拉電路。
- 注意 內(nèi)部和外部的上下拉模式必須一致! 內(nèi)部有上下拉時,就可以不用配置外部上下拉。

上面給出了傳感器模塊的實(shí)物圖,從左到右依次是光敏電阻傳感器、熱敏電阻傳感器、對射式紅外傳感器、反射式紅外傳感器。傳感器元件的電阻會隨外界模擬量的變化而變化,通過與定值電阻分壓即可得到模擬電壓輸出,再通過電壓比較器進(jìn)行二值化即可得到數(shù)字電壓輸出。

上面所給出的原理圖則是一個比較通用的傳感器模塊格式:
- 左起第三個模塊:下面的可變電阻就是各種傳感器模塊所對應(yīng)的阻值,與上面的分壓電阻R1進(jìn)行分壓,進(jìn)而輸出模擬電壓值A(chǔ)O。電容C2是濾波電容。
- 左起第一個模塊:使用LM393模塊,通過運(yùn)算放大器實(shí)現(xiàn)“電壓比較器”的功能。IN- 是一個可以調(diào)節(jié)的閾值,IN+ 則直接連接傳感器的模擬分壓AO,當(dāng) AO > IN- 時,數(shù)字輸出DO拉高;當(dāng) AO < IN- 時,數(shù)字輸出DO拉低。
- 左起第二個模塊:通過一個滑動變阻器實(shí)現(xiàn)比較電壓 IN- 的調(diào)整。
- 左起第四個模塊:電源指示燈。
- 左起第五個模塊:傳感器模塊的端口。LED2用于指示數(shù)字輸出DO的值。注意R5上拉電阻保證DO的默認(rèn)值為高。
補(bǔ)充情況:
- 對于對射式紅外傳感器來說,N1就是紅外接收管,并且額外還有一個點(diǎn)亮紅外發(fā)射管的電路,模擬電壓表示接收紅外信號的強(qiáng)度。并且該模塊常用于檢測通斷,所以用兩個電阻將閾值固定為1/2的參考電壓,而不是采用滑動變阻器。
- 對于反射式紅外傳感器,向下發(fā)射和接收紅外光,可以做尋跡小車。
而對于傳感器模塊的電路設(shè)計來說,由于采用模塊的方案,所以直接給傳感器接上VCC和GND,然后將模擬信號AO和數(shù)字信號DO接在stm32的對應(yīng)端口上即可。
本次實(shí)驗(yàn)采用數(shù)字信號DO接入,關(guān)于模擬信號接入的使用方法在后面AD/DA的實(shí)驗(yàn)中繼續(xù)講解。
3.5 實(shí)驗(yàn):按鍵控制LED、光敏傳感器控制蜂鳴器
需求1: 一個按鍵開關(guān)控制一個LED,每次按下按鍵,LED就改變自己的亮滅狀態(tài);兩套系統(tǒng)互不影響。
- LED低電平驅(qū)動。
- 按鍵B11控制LEDA2,按鍵B1控制LEDA1。
- LED的狀態(tài)改變是“松開觸發(fā)”。


代碼展示:
- main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
int main(void){
LED_Init();
Key_Init();
while(1){
if(Key_GetNum()==1){LED1_TURN();}
else if(Key_GetNum()==2){LED2_TURN();}
};
}
- LED.h
#ifndef __LED_H
#define __LED_H
void LED_Init(void);
void LED1_TURN(void);
void LED2_TURN(void);
#endif
- LED.c
#include "stm32f10x.h" // Device header
/**
* @brief 初始化PA2、PA1作為兩個LED的輸出端口
* @param 無
* @retvl 無
*/
void LED_Init(void){
// 開啟APB2-GPIOA的外設(shè)時鐘RCC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 初始化PA的輸出端口:定義結(jié)構(gòu)體及參數(shù)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽輸出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//默認(rèn)輸出為低電平-LED初始不亮
GPIO_SetBits(GPIOA, GPIO_Pin_2 | GPIO_Pin_1);
};
/**
* @brief LED1狀態(tài)翻轉(zhuǎn)
* @param 無
* @retvl 無
*/
void LED1_TURN(void){
if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1)==1){
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}else{
GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
}
/**
* @brief LED2狀態(tài)翻轉(zhuǎn)
* @param 無
* @retvl 無
*/
void LED2_TURN(void){
if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2)==1){
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}else{
GPIO_SetBits(GPIOA, GPIO_Pin_2);
}
}
- Key.h
#ifndef __KEY_H
#define __KEY_H
void Key_Init(void);
uint8_t Key_GetNum(void);
#endif
- Key.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* @brief 初始化B11、B1作為按鍵2、按鍵1
* @param 無
* @retvl 無
*/
void Key_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉輸入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//只有輸出才有速度等級,但是這里也可以定義
GPIO_Init(GPIOB, &GPIO_InitStructure);
};
/**
* @brief 檢測哪個按鍵已經(jīng)按下-松開觸發(fā)
* @param 無
* @retvl 返回按下的按鍵編號
* @arg 0,1,2
*/
uint8_t Key_GetNum(void){
uint8_t keynum = 0;
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)==0){
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)==0);
Delay_ms(20);
keynum = 2;
}
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0){
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0);
Delay_ms(20);
keynum = 1;
}
return keynum;
};
編程感想:
- 代碼提示框如果不彈出,可以使用
ctrl + space
彈出代碼提示框。如果沒用的話,大概率是和輸入法中/英切換快捷鍵沖突,輸入法右鍵設(shè)置取消即可。- GPIO配置好之后默認(rèn)就是低電平,所以配置好后LED會默認(rèn)是亮的狀態(tài)。
- 本工程創(chuàng)建了全新的驅(qū)動函數(shù)文件夾Hardware,專門用于存放程序中使用到的外設(shè)(如LED、按鍵、光敏傳感器等)的驅(qū)動函數(shù)。做好驅(qū)動代碼的提取是非常重要的,可以極大地方便程序梳理。
- 其實(shí)寫完之后發(fā)現(xiàn),這個按鍵開關(guān)非常不靈敏,經(jīng)常出現(xiàn)按鍵松手后LED沒有反應(yīng)的情況。大概這就是設(shè)置“光敏傳感器控制蜂鳴器”實(shí)驗(yàn)的原因吧。
需求2: 光敏電阻被遮擋,蜂鳴器長鳴,光敏電阻不被遮擋,蜂鳴器不響。
- 蜂鳴器低電平驅(qū)動。
- 光敏傳感器,光強(qiáng)越強(qiáng)阻值越小,分壓越??;DO的LED指示燈低電平驅(qū)動。


代碼展示:
- main.c
#include "stm32f10x.h" // Device header
#include "Buzzer.h"
#include "LightSensor.h"
int main(void){
Buzzer_Init();
LightSensor_Init();
while(1){
if(LightSensor_Get()==1){Buzzer_ON();}
else {Buzzer_OFF();}
};
}
- Buzzer.h
#ifndef __BUZZER_H
#define __BUZZER_H
void Buzzer_Init(void);
void Buzzer_ON(void);
void Buzzer_OFF(void);
#endif
- Buzzer.c
#include "stm32f10x.h" // Device header
/**
* @brief 蜂鳴器初始化-PB12推挽輸出
*/
void Buzzer_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//推挽輸出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
/**
* @brief 蜂鳴器開啟-低電平驅(qū)動
*/
void Buzzer_ON(void){
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
/**
* @brief 蜂鳴器關(guān)閉-輸入高電平
*/
void Buzzer_OFF(void){
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
- LightSensor.h
#ifndef __LIGHTSENSOR_H
#define __LIGHTSENSOR_H
void LightSensor_Init(void);
uint8_t LightSensor_Get(void);
#endif
- LightSensor.c
#include "stm32f10x.h" // Device header
/**
* @brief 光敏傳感器初始化-PB13上拉輸入
*/
void LightSensor_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉輸入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/**
* @brief 讀取光敏傳感器的數(shù)字輸入信號-光強(qiáng)越強(qiáng)分壓越低
* @retvl 讀取到的數(shù)字輸入信號0/1
*/
uint8_t LightSensor_Get(void){
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);
}
編程感想:
- 模塊化編程真香??!每個模塊都寫一個專門的驅(qū)動函數(shù)存放在Hardware文件夾中,看似麻煩,但是會使得main函數(shù)極其簡單,幾乎就可以直接按照正常的功能邏輯書寫代碼。
3.6 C語言的語法
1. 數(shù)據(jù)類型
關(guān)鍵字 | 位數(shù) | 表示范圍 | stdint關(guān)鍵字 | ST關(guān)鍵字 |
---|---|---|---|---|
char | 8 | -128 ~ 127 | int8_t | s8 |
unsigned char | 8 | 0 ~ 255 | uint8_t | u8 |
short | 16 | -32768 ~ 32767 | int16_t | s16 |
unsigned short | 16 | 0 ~ 65535 | uint16_t | u16 |
int | 32 | -2147483648 ~ 2147483647 | int32_t | s32 |
unsigned int | 32 | 0 ~ 4294967295 | uint32_t | u32 |
long | 32 | -2147483648 ~ 2147483647 | ||
unsigned long | 32 | 0 ~ 4294967295 | ||
long long | 64 | -(2^64)/2 ~ (2^64)/2-1 | int64_t | |
unsigned long long | 64 | 0 ~ (2^64)-1 | uint64_t | |
float | 32 | -3.4e38 ~ 3.4e38 | ||
double | 64 | -1.7e308 ~ 1.7e308 |
- 51單片機(jī)中
int
型 為16位;stm32中int
型 為32位。- 倒數(shù)第二列是C語言給這些類型提供的別名;最后一列是老版本ST公司庫函數(shù)給這些類型提供的別名,新版的庫函數(shù)已經(jīng)全部替換成倒數(shù)第二列。以后寫程序時盡量使用上表中加粗的關(guān)鍵字。
2. 宏定義#define
關(guān)鍵字 #define
,主要用于:用一個字符串代替一個數(shù)字,便于理解,防止出錯;或者提取程序中經(jīng)常出現(xiàn)的參數(shù),便于快速修改。
//定義宏定義:
#define ABC 12345
//引用宏定義:
int a = ABC; //等效于int a = 12345;
3. 關(guān)鍵字typedef
關(guān)鍵字 typedef
,常用于將一個比較長的變量類型名換個名字,便于使用。
//定義typedef:
typedef unsigned char uint8_t;
//引用typedef:
uint8_t num1; //等效于unsigned char num1;
相比于#define
來說,typedef
在進(jìn)行改名時會進(jìn)行變量類型檢查,所以更加安全。
4. 結(jié)構(gòu)體
關(guān)鍵字 struct
,用途:數(shù)據(jù)打包,將不同類型變量組成一個集合。文章來源:http://www.zghlxwxcb.cn/news/detail-613906.html
//在main函數(shù)中定義結(jié)構(gòu)體變量:
struct{
char x;
int y;
float z;
} StructName;
//因?yàn)榻Y(jié)構(gòu)體變量類型較長,所以通常在main函數(shù)外用typedef更改變量類型名
typedef struct{
char x;
int y;
float z;
} StructName;
//引用結(jié)構(gòu)體成員:方法一
StructName struct1;
struct1.x = 'A';
struct1.y = 66;
struct1.z = 1.23;
//引用結(jié)構(gòu)體成員:方法二
pStructName->x = 'A'; //pStructName為結(jié)構(gòu)體的地址
pStructName->y = 66;
pStructName->z = 1.23;
5. 枚舉類型
關(guān)鍵字 enum
,用途:定義一個取值受限制的整型變量,用于限制變量取值范圍;枚舉也相當(dāng)于一個宏定義的集合,可以直接把里面的枚舉變量拿出來用。注意枚舉變量用逗號隔開,且最后一個枚舉變量不加逗號。文章來源地址http://www.zghlxwxcb.cn/news/detail-613906.html
//函數(shù)內(nèi)定義枚舉變量:
enum{
FALSE = 0,
TRUE = 1
} EnumName;
//因?yàn)槊杜e變量類型較長,所以通常在函數(shù)外用typedef更改變量類型名
typedef enum{
FALSE = 0,
TRUE = 1
} EnumName;
//引用枚舉成員:
EnumName emu1;
emu1 = FALSE;
emu1 = TRUE;
到了這里,關(guān)于stm32學(xué)習(xí)筆記-3GPIO通用輸入輸出口的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!