- 基于ARM Cortex-M3的STM32系列嵌入式微控制器應(yīng)用實(shí)踐
- 彭剛 秦志強(qiáng)編著
- 667字
- 2018-12-27 16:01:46
2.3 STM32單片機(jī)的I/O端口配置
while(1)邏輯塊中的代碼是例程Led_Blink.c的功能主體:
while (1) { GPIO_SetBits(GPIOC,GPIO_Pin_13); //PC13輸出高電平 delay_nms(500); // 延時(shí)500ms GPIO_ResetBits(GPIOC,GPIO_Pin_13);// PC13輸出低電平 delay_nms(500); // 延時(shí)500ms }
先給PC13腳輸出高電平,由賦值語(yǔ)句GPIO_SetBits(GPIOC,GPIO_Pin_13)完成,然后調(diào)用延時(shí)函數(shù)delay_nms(500)等待500ms,再給PC13腳輸出低電平,即GPIO_ResetBits(GPIOC, GPIO_Pin_13),然后再次調(diào)用延時(shí)500ms函數(shù)delay_nms(500)。這樣就完成了一次閃爍。
在程序中,你沒(méi)有看到PC13:GPIOC和GPIO_Pin_13的定義,它們已經(jīng)在固件函數(shù)標(biāo)準(zhǔn)庫(kù)(stm32f10x_map.h和stm32f10x_gpio.h)中定義好了,由頭文件stm32f10x_heads.h包括進(jìn)來(lái)。回想一下,用Keil開(kāi)發(fā)51單片機(jī)程序時(shí),也是一樣的。后續(xù)章節(jié)中將要用到的其他引腳名稱和定義都是如此。
GPIO_SetBits和GPIO_ResetBits這兩個(gè)函數(shù)在stm32f10x_gpio.c中實(shí)現(xiàn),后面將作介紹。
時(shí)序圖簡(jiǎn)介
時(shí)序圖反應(yīng)的是高、低電壓信號(hào)與時(shí)間的關(guān)系圖。在圖2.6中,時(shí)間從左到右增長(zhǎng),高、低電壓信號(hào)隨著時(shí)間在低電平或高電平之間變化。這個(gè)時(shí)序圖顯示的是剛才實(shí)驗(yàn)中的1000ms的高、低電壓信號(hào)片段。右邊的省略號(hào)表示的是這些信號(hào)是重復(fù)出現(xiàn)的。

圖2.6 程序Led_Blink.c的時(shí)序圖
微控制器的最大優(yōu)點(diǎn)之一就是它們從來(lái)不會(huì)抱怨不停地重復(fù)做同樣的事情。為了讓單片機(jī)不斷閃爍,你需要把讓LED閃爍一次的幾個(gè)語(yǔ)句放在while(1){…}循環(huán)里。這里用到了C語(yǔ)言實(shí)現(xiàn)循環(huán)結(jié)構(gòu)的一種形式:
while(表達(dá)式) 循環(huán)體語(yǔ)句
當(dāng)表達(dá)式為非0值時(shí),執(zhí)行while語(yǔ)句中的內(nèi)嵌語(yǔ)句,其特點(diǎn)是先判斷表達(dá)式,后執(zhí)行語(yǔ)句。例程中直接用1代替了表達(dá)式,因此總是非0值,所以循環(huán)永不結(jié)束,也就可以一直讓LED燈閃爍。
注意:循環(huán)體語(yǔ)句如果包含一個(gè)以上的語(yǔ)句,就必須用花括號(hào)(“{ }”)括起來(lái),以復(fù)合語(yǔ)句的形式出現(xiàn)。如果不加花括號(hào),則while語(yǔ)句的范圍只到while后面的第一個(gè)分號(hào)處。例如,本例中while語(yǔ)句中如果沒(méi)有花括號(hào),則while語(yǔ)句范只到“GPIO_SetBits(GPIOC,GPIO_Pin_13);”。
也可以不要循環(huán)體語(yǔ)句,如第一章例程中就直接用while(1);程序?qū)⒁恢蓖T诖颂帯?/p>
STM32系列單片機(jī)的I/O端口模式
STM32系列單片機(jī)的輸入/輸出引腳可配置成以下8種(4輸入+2輸出+2復(fù)用輸出):
① 浮空輸入:In_Floating。
② 帶上拉輸入:IPU(In Push-Up)。
③ 帶下拉輸入:IPD(In Push-Down)。
④ 模擬輸入:AIN(Analog In)。
⑤ 開(kāi)漏輸出:OUT_OD。OD代表開(kāi)漏:Open-Drain。(OC代表開(kāi)集:Open-Collector)。
⑥ 推挽輸出:OUT_PP。PP代表推挽式:Push-Pull。
⑦ 復(fù)用功能的推挽輸出:AF_PP。AF代表復(fù)用功能:Alternate-Function。
⑧ 復(fù)用功能的開(kāi)漏輸出:AF_OD。
開(kāi)漏輸出與推挽輸出
開(kāi)漏輸出:MOS管漏極開(kāi)路。要得到高電平狀態(tài)需要上拉電阻才行。一般用于線或、線與,適合做電流型的驅(qū)動(dòng),其吸收電流的能力相對(duì)強(qiáng)(一般20mA以內(nèi))。開(kāi)漏是對(duì)MOS管而言,開(kāi)集是對(duì)雙極型管而言,在用法上沒(méi)區(qū)別,開(kāi)漏輸出端相當(dāng)于三極管的集電極。如果開(kāi)漏引腳不連接外部的上拉電阻,則只能輸出低電平。因此,對(duì)于經(jīng)典的51單片機(jī)的P0口,要想做輸入/輸出功能必須加外部上拉電阻,否則無(wú)法輸出高電平邏輯。一般來(lái)說(shuō),可以利用上拉電阻接不同的電壓,改變傳輸電平,以連接不同電平(3.3V或5V)的器件或系統(tǒng),這樣你就可以進(jìn)行任意電平的轉(zhuǎn)換了。
推挽輸出:如果輸出級(jí)的兩個(gè)參數(shù)相同MOS管(或三極管)受兩互補(bǔ)信號(hào)的控制,始終處于一個(gè)導(dǎo)通、一個(gè)截止的狀態(tài),就是推挽相連,這種結(jié)構(gòu)稱為推拉式電路。推挽輸出電路輸出高電平或低電平時(shí),兩個(gè)MOS管交替工作,可以減低功耗,并提高每個(gè)管的承受能力。又由于不論走哪一路,管子導(dǎo)通電阻都很小,使RC常數(shù)很小,邏輯電平轉(zhuǎn)變速度很快,因此,推拉式輸出既可以提高電路的負(fù)載能力,又能提高開(kāi)關(guān)速度,且導(dǎo)通損耗小效率高。輸出既可以向負(fù)載灌電流(作為輸出),也可以從負(fù)載抽取電流(作為輸入)。
下面我們來(lái)看看通用GPIO(General Purpose Input Output)端口的初始化配置函數(shù):GPIO_Configuration。在文件“stm32f10x_gpio.h”中定義了:
typedef enum
{
GPIO_Speed_10MHz = 1,
GPIO_Speed_2MHz,
GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;
…
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;
…
typedef struct
{
u16 GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;
在文件“HelloRobot.h”中定義了:
GPIO_InitTypeDef GPIO_InitStructure;
…
void GPIO_Configuration()
{
/* Configure USART1 Tx (PA.09) as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART1 Rx (PA.10) as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure LEDs IO */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12| GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Configure Motors I/O */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/* Configure infrared I/O */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* Configure ADC I/O */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* Configure KEY I/O PC8 to PC11 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Configure INT I/O PE4 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* Configure INT I/O PE5 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* Configure LCD1602 I/O */
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_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5| GPIO_Pin_6 | GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
}
其中,函數(shù)GPIO_Init的具體實(shí)現(xiàn)在庫(kù)文件“stm32f10x_gpio.c”中(\library\src目錄下)。其作用是定義各個(gè)通用I/O端口的模式。
從上面的程序代碼可以看出,對(duì)應(yīng)到外設(shè)的輸入/輸出功能有以下三種情況:
(1)外設(shè)對(duì)應(yīng)的引腳為輸入:則根據(jù)外圍電路的配置可以選擇浮空輸入、帶上拉輸入或帶下拉輸入。
(2)ADC對(duì)應(yīng)的引腳:配置引腳為模擬輸入。
(3)外設(shè)對(duì)應(yīng)的引腳為輸出:需要根據(jù)外圍電路的配置選擇對(duì)應(yīng)的引腳為復(fù)用功能的推挽輸出或復(fù)用功能的開(kāi)漏輸出。如果把端口配置成復(fù)用輸出功能,則引腳和輸出寄存器斷開(kāi),并和片上外設(shè)的輸出信號(hào)連接。將引腳配置成復(fù)用輸出功能后,如果外設(shè)沒(méi)有被激活,那么它的輸出將不確定。
當(dāng)GPIO口設(shè)為輸入模式時(shí),輸出驅(qū)動(dòng)電路與端口是斷開(kāi),此時(shí)輸出速度配置無(wú)意義,不用配置。在復(fù)位期間和剛復(fù)位后,復(fù)用功能未開(kāi)啟,I/O端口被配置成浮空輸入模式。所有端口都有外部中斷能力。為了使用外部中斷線,端口必須配置成輸入模式。
當(dāng)GPIO口設(shè)為輸出模式時(shí),有3種輸出速度可選(2MHz、10MHz和50MHz),這個(gè)速度是指I/O口驅(qū)動(dòng)電路的響應(yīng)速度而不是輸出信號(hào)的速度,輸出信號(hào)的速度與程序有關(guān)(芯片內(nèi)部在I/O口的輸出部分安排了多個(gè)響應(yīng)速度不同的輸出驅(qū)動(dòng)電路,可以根據(jù)需要選擇合適的驅(qū)動(dòng)電路)。通過(guò)選擇速度來(lái)選擇不同的輸出驅(qū)動(dòng)模塊,達(dá)到最佳的噪聲控制和降低功耗的目的。高頻的驅(qū)動(dòng)電路,噪聲也高,當(dāng)不需要高的輸出頻率時(shí),請(qǐng)選用低頻驅(qū)動(dòng)電路,這樣非常有利于提高系統(tǒng)的電磁干擾(EMI)性能。當(dāng)然如果要輸出較高頻率的信號(hào),但卻選用了較低頻率的驅(qū)動(dòng)模塊,很可能會(huì)得到失真的輸出信號(hào)。關(guān)鍵是GPIO的引腳速度跟應(yīng)用匹配(推薦10倍以上)。
對(duì)于串口,假如最大波特率只需115.2K,那么用2M的GPIO的引腳速度就夠了,既省電也噪聲小;對(duì)于I2C接口,假如使用400K傳輸速率,若想把余量留大些,那么用2M的GPIO的引腳速度或許不夠,這時(shí)可以選用10M的GPIO引腳速度;對(duì)于SPI接口,假如使用18M或9M傳輸速率,用10M的GPIO的引腳速度顯然不夠了,需要選用50M的GPIO的引腳速度。
電磁干擾
EMI:電磁干擾(Electromagnetic Interference)是指電磁波與電子元件作用后產(chǎn)生的干擾現(xiàn)象,分為傳導(dǎo)干擾和輻射干擾。傳導(dǎo)干擾是指通過(guò)導(dǎo)電介質(zhì)把一個(gè)電網(wǎng)絡(luò)上的信號(hào)耦合(干擾)到另一個(gè)電網(wǎng)絡(luò)。輻射干擾是指干擾源通過(guò)空間把其信號(hào)耦合(干擾)到另一個(gè)電網(wǎng)絡(luò)。在高速系統(tǒng)中,高頻信號(hào)線、集成電路的引腳、各類接插件等都可能成為具有天線特性的輻射干擾源,能發(fā)射電磁波并影響其他系統(tǒng)或本系統(tǒng)內(nèi)其他子系統(tǒng)的正常工作。
由此可見(jiàn),STM32系列單片機(jī)的GPIO功能很強(qiáng)大,具有以下功能:
(1)最基本的功能是可以驅(qū)動(dòng)LED、產(chǎn)生PWM、驅(qū)動(dòng)蜂鳴器等;
(2)具有單獨(dú)的位設(shè)置或位清除,編程簡(jiǎn)單。端口配置好以后只需GPIO_SetBits(GPIOx, GPIO_Pin_x)就可以實(shí)現(xiàn)對(duì)GPIOx的pinx位為高電平,GPIO_ResetBits(GPIOx, GPIO_Pin_x)就可以實(shí)現(xiàn)對(duì)GPIOx的pinx位為低電平;
(3)具有外部中斷/喚醒能力,端口配置成輸入模式時(shí),具有外部中斷能力;
(4)具有復(fù)用功能,復(fù)用功能的端口兼有I/O功能等;
(5)軟件重新映射I/O復(fù)用功能:為了使不同器件封裝的外設(shè)I/O功能的數(shù)量達(dá)到最優(yōu),可以把一些復(fù)用功能重新映射到其他一些腳上。這可以通過(guò)軟件配置相應(yīng)的寄存器來(lái)完成。這時(shí),復(fù)用功能就不再映射到它們的原始引腳上了;
(6)GPIO口的配置具有鎖定機(jī)制,當(dāng)配置好GPIO口后,在一個(gè)端口位上執(zhí)行了鎖定(LOCK),可以通過(guò)程序鎖住配置組合,在下一次復(fù)位之前,將不能再更改端口位的配置。
STM32系列單片機(jī)的每個(gè)GPIO端口有兩個(gè)32位配置寄存器(GPIOx_CRL,GPIOx_CRH),兩個(gè)32位數(shù)據(jù)寄存器(GPIOx_IDR,GPIOx_ODR),一個(gè)32位置位/復(fù)位寄存器(GPIOx_BSRR),一個(gè)16位復(fù)位寄存器(GPIOx_BRR)和一個(gè)32位鎖定寄存器(GPIOx_LCKR)。GPIO端口的每個(gè)位可以由軟件分別配置成多種模式。每個(gè)I/O端口位可以自由編程。
注意:I/O端口寄存器必須按32位字被訪(不允許半字或字節(jié)訪問(wèn))。GPIOx_BSRR和GPIOx_BRR寄存器允許對(duì)任何GPIO寄存器的讀/寫(xiě)的獨(dú)立訪問(wèn)。定義這些GPIO寄存器組的結(jié)構(gòu)體是GPIO_TypeDef,在庫(kù)文件“stm32f10x_map.h”中:
#define PERIPH_BASE ((u32)0x40000000)
...
#define APB2PERIPH_BASE (PERIPH_BASE+0x10000)
...
typedef struct
{
vu32 CRL; // 配置寄存器低32位:configuration register low(GPIOx_CRL)(x=A..E)
vu32 CRH; // 配置寄存器高32位:configuration register high(GPIOx_CRH)(x=A..E)
vu32 IDR; // 輸入數(shù)據(jù)寄存器:input data register(GPIOx_IDR)(x=A..E)
vu32 ODR; // 輸出數(shù)據(jù)寄存器:output data register(GPIOx_ODR)(x=A..E)
vu32 BSRR; // 位置位/復(fù)位寄存器:bit set/reset register(GPIOx_BSRR)(x=A..E)
vu32 BRR; // 位復(fù)位寄存器:bit reset register(GPIOx_BRR)(x=A..E)
vu32 LCKR; // 位鎖定寄存器:lock register(GPIOx_LCKR)(x=A..E)
} GPIO_TypeDef;
...
#define AFIO_BASE (APB2PERIPH_BASE+0x0000)
#define EXTI_BASE (APB2PERIPH_BASE+0x0400)
#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)
...
#ifdef _GPIOA
#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)
#endif
#ifdef _GPIOB
#define GPIOB ((GPIO_TypeDef*)GPIOB_BASE)
#endif
#ifdef _GPIOC
#define GPIOC ((GPIO_TypeDef*)GPIOC_BASE)
#endif
#ifdef _GPIOD
#define GPIOD ((GPIO_TypeDef*)GPIOD_BASE)
#endif
#ifdef _GPIOE
#define GPIOE ((GPIO_TypeDef*)GPIOE_BASE)
#endif
函數(shù)GPIO_Init的第一個(gè)參數(shù)是這些GPIOx(x=A,B,C,D,E)寄存器的存儲(chǔ)映射首地址,第二個(gè)參數(shù)是用戶對(duì)GPIO端口設(shè)置的參數(shù)所在首地址,這些數(shù)據(jù)存放在結(jié)構(gòu)體GPIO_InitTypeDef中,包括所要設(shè)置GPIO的端口號(hào),類型和速度。STM32單片機(jī)使用固件庫(kù)函數(shù)完成外設(shè)(如GPIO、TIM、USART、ADC、DMA、RTC等)初始化都采用這種規(guī)范,如圖2.7所示,這種固件庫(kù)結(jié)構(gòu)大大提高了程序的開(kāi)發(fā)效率。

圖2.7 使用固件庫(kù)函數(shù)完成外設(shè)初始化示意圖
從上面的宏定義可以看出,GPIOx(x=A,B,C,D,E)寄存器的存儲(chǔ)映射首地址分別是0x40010800,0x40010C00,0x40011000,0x40011400,0x40011800;AFIO寄存器的存儲(chǔ)映射首地址是0x40010000。GPIO和AFIO寄存器映像和復(fù)位值如表2.2和2.3所示。
表2.2 GPIO寄存器映像和復(fù)位值

表2.3 AFIO寄存器映像和復(fù)位值

下面我們來(lái)看看“stm32f10x_gpio.c”文件中的GPIO_SetBits和GPIO_ResetBits等對(duì)STM32單片機(jī)I/O口操作的幾個(gè)函數(shù):
void GPIO_SetBits(GPIO_TypeDef* GPIOx, u16 GPIO_Pin) { /*Check the parameters:斷言檢查是否定義了GPIO_Pin*/ assert(IS_GPIO_PIN(GPIO_Pin)); GPIOx->BSRR=GPIO_Pin; } void GPIO_ResetBits(GPIO_TypeDef* GPIOx, u16 GPIO_Pin) { /*Check the parameters*/ assert(IS_GPIO_PIN(GPIO_Pin)); GPIOx->BRR=GPIO_Pin; } ///////////////// void GPIO_Write(GPIO_TypeDef* GPIOx, u16 PortVal) { GPIOx->ODR=PortVal; } void GPIO_WriteBit(GPIO_TypeDef* GPIOx, u16 GPIO_Pin, BitAction BitVal) { /*Check the parameters*/ assert(IS_GET_GPIO_PIN(GPIO_Pin)); assert(IS_GPIO_BIT_ACTION(BitVal)); if (BitVal != Bit_RESET) { GPIOx->BSRR = GPIO_Pin; } else { GPIOx->BRR = GPIO_Pin; } } ///////////////// u16 GPIO_ReadOutputData(GPIO_TypeDef* GPIOx) { return ((u16)GPIOx->ODR); } u8 GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, u16 GPIO_Pin) { u8 bitstatus = 0x00; assert(IS_GET_GPIO_PIN(GPIO_Pin)); /*Check the parameters*/ if ((GPIOx->ODR & GPIO_Pin) != (u32)Bit_RESET) { bitstatus = (u8)Bit_SET; } else { bitstatus = (u8)Bit_RESET; } return bitstatus; }
從上面的程序,我們可以看出GPIO_SetBits和GPIO_ResetBits函數(shù)實(shí)際上是直接訪問(wèn)了GPIO的相關(guān)寄存器,對(duì)I/O端口的對(duì)應(yīng)位置“1”或清“0”,使引腳輸出高電平或低電平。我們可以利用這些STM32固件庫(kù)的函數(shù)來(lái)開(kāi)發(fā)自己的應(yīng)用程序,當(dāng)然也可以不使用固件庫(kù),而直接對(duì)寄存器訪問(wèn)來(lái)編寫(xiě)應(yīng)用程序,這需要你對(duì)STM32單片機(jī)寄存器的各個(gè)位的含義和使用十分熟悉。不使用固件庫(kù)的發(fā)光二極管閃爍程序見(jiàn)本書(shū)配套例程。你可以對(duì)比一下代碼尺寸的變化和程序運(yùn)行的結(jié)果。
對(duì)于開(kāi)發(fā)一個(gè)系統(tǒng)級(jí)的產(chǎn)品而言,建議使用STM32固件庫(kù)。它具有以下特性:
● 兼容性好:使用宏定義能夠靈活地兼容各個(gè)型號(hào)和不同功能;
● 命名規(guī)范:不用注釋就能看懂變量或函數(shù),可讀性好,而且不會(huì)重名;
● 通用性強(qiáng):多數(shù)庫(kù)文件都是只讀類型,不用修改便可實(shí)現(xiàn)不同功能間的調(diào)用。
使用固件庫(kù)也有缺點(diǎn),如運(yùn)行性能有所損失,速度會(huì)變慢些。對(duì)于越來(lái)越復(fù)雜的嵌入式應(yīng)用而言,隨著處理器存儲(chǔ)容量和頻率的提高,筆者建議應(yīng)該更關(guān)注項(xiàng)目的整體開(kāi)發(fā)效率,而不是具體的代碼尺寸。對(duì)于時(shí)序要求嚴(yán)格的地方,完全可以直接訪問(wèn)寄存器減小代碼尺寸,根據(jù)實(shí)際開(kāi)發(fā)設(shè)計(jì)的要求來(lái)確定。同時(shí),使用固件庫(kù)進(jìn)行程序開(kāi)發(fā),借鑒ST固件庫(kù)函數(shù)的命名規(guī)范,有助于養(yǎng)成良好的編碼風(fēng)格,學(xué)以致用,提高編程水平,正如本書(shū)前言所述。關(guān)于STM32單片機(jī)固件庫(kù)的介紹參見(jiàn)附錄。
什么是assert(斷言)
編寫(xiě)代碼時(shí),我們總是會(huì)做出一些假設(shè),斷言就是用于在代碼中檢測(cè)這些假設(shè)是否成立,比如,向一個(gè)端口寫(xiě)數(shù)據(jù),如果這個(gè)端口不存在,則不能向一個(gè)端口寫(xiě)數(shù)據(jù)。例如,向GPIO_Pin_0端口寫(xiě)是合法的,而向GPIO_Pin_20端口寫(xiě)就是非法的,因?yàn)镾TM32單片機(jī)根本不存在這個(gè)端口。
可以將斷言看做是程序異常處理的一種高級(jí)形式。斷言表示為一些布爾表達(dá)式,如果在程序的某個(gè)特定點(diǎn)要判斷某個(gè)表達(dá)式值為真,則可以進(jìn)行斷言驗(yàn)證。可以在任何時(shí)候啟用和禁用斷言驗(yàn)證。一般我們會(huì)讓斷言語(yǔ)句在編譯Debug版本的程序時(shí)生效,而在編譯Release版本的程序時(shí)禁止。同樣,最終用戶在運(yùn)行程序遇到問(wèn)題時(shí)可以重新啟用斷言。使用斷言可以創(chuàng)建更穩(wěn)定,優(yōu)秀且不易于出錯(cuò)的代碼。當(dāng)需要在一個(gè)值為FALSE時(shí)中斷當(dāng)前操作的話,可以使用斷言。單元測(cè)試必須使用斷言(Junit/JunitX)。除了類型檢查和單元測(cè)試外,斷言還提供了一種確定各種特性是否在程序中得到維護(hù)的極好的方法。
assert宏的原型定義在<assert.h>中,其作用是如果它的條件返回錯(cuò)誤,則終止程序執(zhí)行,原型定義:void assert( int expression );
assert的作用是現(xiàn)計(jì)算表達(dá)式expression,如果其值為假(即為0),那么它先向stderr打印一條出錯(cuò)信息,然后通過(guò)調(diào)用abort來(lái)終止程序運(yùn)行。
使用assert的缺點(diǎn)是:頻繁的調(diào)用會(huì)極大的影響程序的性能,增加額外的開(kāi)銷。在調(diào)試結(jié)束后,可以通過(guò)如下代碼來(lái)禁用assert調(diào)用:
#include <stdio.h> #define NDEBUG #include <assert.h>
一般地,STM32系列單片機(jī)中配置片內(nèi)外設(shè)使用的通用I/O端口需經(jīng)過(guò)一下幾步設(shè)置:
(1)配置輸入的時(shí)鐘。
使能APB2總線外設(shè)時(shí)鐘:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE)。釋放GPIO復(fù)位:RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC, DISABLE)。
(2)初始化后即被激活(開(kāi)啟)。
(3)如果使用該外設(shè)的輸入/輸出引腳,則需要配置相應(yīng)的GPIO端口(否則該外設(shè)對(duì)應(yīng)的輸入/輸出引腳可以做普通GPIO引腳使用)。
(4)配置各個(gè)PIN端口的模式和速度。
(5)GPIO初始化。
本書(shū)所用STM32單片機(jī)教學(xué)開(kāi)發(fā)板的各個(gè)I/O口配置如下:
(1)PA9和PA10是串口1的發(fā)送和接收引腳(電路板設(shè)計(jì))。
(2)PB8、PB9、PC12、PC13是輸出控制發(fā)光二極管(電路板設(shè)計(jì))。
(3)PD7、PD8、PD9、PD10是輸出控制電機(jī)(電路板設(shè)計(jì))。
(4)PA0、PB0是AD輸入引腳,PA4是AD輸入引腳或DA輸出引腳(電路板設(shè)計(jì))。
(5)PC8、PC9、PC10、PC11是按鍵輸入引腳(電路板設(shè)計(jì))。
(6)PC0~PC7、PD4、PD5、PD6是輸出控制1602液晶(電路板設(shè)計(jì))。
(7)PE0、PE1定義成輸出引腳。
(8)PE2、PE3定義成輸入引腳。
(9)PE4、PE5定義成是輸入引腳。
其中,PE口的16個(gè)I/O端口并未設(shè)計(jì)具體電路,開(kāi)放出來(lái)了,你可在面包板上自行搭建電路或制作一個(gè)擴(kuò)展板。
注意:PE4、PE5在函數(shù)NVIC_Configuration中,定義成了外部中斷輸入。那些還沒(méi)有設(shè)置的引腳,你可以參照上述方法設(shè)置。
串口初始化函數(shù)USART_Configuration,在頭文件HelloRobot.h中實(shí)現(xiàn),具體內(nèi)容將在后面章節(jié)講解。調(diào)用printf是為了在程序執(zhí)行前給調(diào)試終端發(fā)送一條提示信息,告訴你現(xiàn)在程序開(kāi)始執(zhí)行了,并告訴你隨后程序?qū)㈤_(kāi)始干什么。這在你以后的編程開(kāi)發(fā)過(guò)程中是一個(gè)良好的習(xí)慣,將非常有助于你提高程序的調(diào)試效率。
任務(wù)三
該你了——讓另一個(gè)LED閃爍
LED電路元件
(1)1個(gè)發(fā)光二極管(紅色、綠色、黃色皆可);
(2)1個(gè)470Ω電阻(色環(huán):黃—紫—黑—黑)。
LED電路搭建
參照?qǐng)D2.2所示電路在智能機(jī)器人教學(xué)開(kāi)發(fā)板的面包板上搭建起實(shí)際電路。實(shí)際搭建好的電路參考圖2.8所示照片。實(shí)際搭建電路時(shí)注意:

圖2.8 發(fā)光二極管在PE0端口低電平時(shí)亮
● 確認(rèn)電路板電源斷開(kāi),等搭建好電路后,再開(kāi)電源開(kāi)關(guān);
● 確認(rèn)發(fā)光二極管的短針腳(陰極)通過(guò)470Ω電阻與PE0相連;
● 確認(rèn)發(fā)光二極管的長(zhǎng)針腳(陽(yáng)極)通過(guò)導(dǎo)線與“5V”或“3.3V” 電源相連。注意養(yǎng)成良好習(xí)慣:當(dāng)連接導(dǎo)線與“電源”相連時(shí)用“紅色”導(dǎo)線,與“地”相連時(shí)用“黑色”導(dǎo)線,與“信號(hào)”相連時(shí)用其他顏色導(dǎo)線,如白色導(dǎo)線。
你也可以這樣搭建電路,如圖2.9所示。

圖2.9 發(fā)光二極管在PE0端口高電平時(shí)亮
● 確認(rèn)發(fā)光二極管的短針腳(陰極)與“GND” 相連;
● 確認(rèn)發(fā)光二極管的長(zhǎng)針腳(陽(yáng)極)通過(guò)470Ω電阻與PE0相連。
嵌入式系統(tǒng)中,通過(guò)I/O端口控制LED時(shí),盡量考慮使用灌電流的方式,即低電平時(shí),LED亮。
讓另一個(gè)連接到PE0引腳的LED閃爍是一件很容易的事情,把PC13改為PE0,重新運(yùn)行程序即可。參考下面的代碼段修改程序:
while (1) { GPIO_SetBits(GPIOE,GPIO_Pin_0); //PE0輸出高電平 delay_nms(500); //延時(shí)500ms GPIO_ResetBits(GPIOE,GPIO_Pin_0);//PE0輸出低電平 delay_nms(500); //延時(shí)500ms }
運(yùn)行修改后的程序,確定能讓LED閃爍。你也可以讓兩個(gè)LED同時(shí)閃爍。參考下面代碼段修改程序:
while (1) { GPIO_SetBits(GPIOC,GPIO_Pin_13); //PC13輸出高電平 GPIO_SetBits(GPIOE,GPIO_Pin_0); //PE0輸出高電平 delay_nms(500); //延時(shí)500ms GPIO_ResetBits(GPIOC,GPIO_Pin_13); //PC13輸出低電平 GPIO_ResetBits(GPIOE,GPIO_Pin_0); //PE0輸出低電平 delay_nms(500); //延時(shí)500ms }
運(yùn)行修改后的程序,確定能讓兩個(gè)LED幾乎同時(shí)閃爍。
當(dāng)然,你可以再次修改程序,讓兩個(gè)發(fā)光二極管交替亮或滅,你也可以通過(guò)改變延時(shí)函數(shù)的參數(shù)n的值,來(lái)改變LED的閃爍頻率。嘗試一下!
任務(wù)四 流水燈
例程:流水燈Led_Shift.c
#include "stm32f10x_heads.h" #include "Led_Blink.h" int main(void) { BSP_Init(); USART_Configuration(); printf("Program Running!\n"); while (1) { GPIO_SetBits(GPIOB, GPIO_Pin_8); delay_nms(500); GPIO_ResetBits(GPIOB,GPIO_Pin_8); delay_nms(500); GPIO_SetBits(GPIOB, GPIO_Pin_9); delay_nms(500); GPIO_ResetBits(GPIOB,GPIO_Pin_9); delay_nms(500); GPIO_SetBits(GPIOC, GPIO_Pin_12); delay_nms(500); GPIO_ResetBits(GPIOC,GPIO_Pin_12); delay_nms(500); GPIO_SetBits(GPIOC, GPIO_Pin_13); delay_nms(500); GPIO_ResetBits(GPIOC,GPIO_Pin_13); delay_nms(500); } }
按照上述方法建立新的項(xiàng)目,輸入程序Led_Shift.c,運(yùn)行查看結(jié)果。你也可以調(diào)整延時(shí)時(shí)間為100ms或10ms,試試效果。
- ANSYS Workbench基礎(chǔ)教程與工程分析詳解
- 用Proteus可視化設(shè)計(jì)玩轉(zhuǎn)Arduino
- MC9S12XS單片機(jī)原理及嵌入式系統(tǒng)開(kāi)發(fā)
- 嵌入式Qt實(shí)戰(zhàn)教程
- 基于HCS12的嵌入式系統(tǒng)設(shè)計(jì)
- 51單片機(jī)逆向?qū)W習(xí)實(shí)戰(zhàn)教程(電子設(shè)計(jì)與嵌入式開(kāi)發(fā)實(shí)踐叢書(shū))
- STM32單片機(jī)全案例開(kāi)發(fā)實(shí)戰(zhàn)
- ANSYS Workbench 17.0有限元分析從入門到精通
- 基于STM32的嵌入式系統(tǒng)設(shè)計(jì)與實(shí)踐
- 單片機(jī)原理與應(yīng)用技術(shù)
- 案例解說(shuō)組態(tài)軟件典型控制應(yīng)用
- 嵌入式系統(tǒng):基于項(xiàng)目的分析和設(shè)計(jì)
- 單片機(jī)原理與工程應(yīng)用
- 單片機(jī)原理與應(yīng)用技術(shù)
- 51單片機(jī)應(yīng)用開(kāi)發(fā)案例手冊(cè)