用STM32點(diǎn)亮跑馬燈(庫函數(shù))啰嗦版
用STM32的GPIO來點(diǎn)亮跑馬燈
步驟:
1.新建工程:復(fù)制模板中的一切文件夾,注意刪除USER中模板Template產(chǎn)生的三個(gè)文件以及OBJ中的文件(OBJ中產(chǎn)生的是編譯過程中產(chǎn)生的文件)。
2.打開keil,對工程中的各縣設(shè)置進(jìn)行修改(包括:右鍵Manage Project Items里面的各個(gè)組及頭文件;魔術(shù)棒中的Output選項(xiàng)卡中HEX文件和Folder的修改;C/C++選項(xiàng)卡中Define和頭文件路徑的修改)
3.build工程,查看設(shè)置是否正確,注意此時(shí)各個(gè)文件前面有可能沒有小加號,解決辦法是Translate一下,要是還不行就關(guān)了重開。
至此新建工程完成
新建工程需注意的事項(xiàng):
(1)在不完全手冊中有著詳細(xì)的新建步驟,要仔細(xì)的按照步驟來,
(2)Define:STM32F10X_HD,USE_STDPERIPH_DRIVER,
(3)FIWLIB添加文件的時(shí)候添加的是src中的文件,但在添加路徑的時(shí)候是inc文件夾,并且注意一定要相應(yīng)的添加,
(4)Translate和build的區(qū)別:Translate的時(shí)候只是檢查語法的錯(cuò)誤,并不產(chǎn)生HEX文件,
(5)出現(xiàn)無法gotodefinition的情況的時(shí)候是沒有build工程,build一下就可以了,
想要對外設(shè)進(jìn)行驅(qū)動(dòng)就要相應(yīng)的編寫函數(shù),我們在這里只將具體要做什么事(如亮多久滅多久,一起亮還是輪流亮)放于main文件中,每個(gè)外設(shè)的初始化函數(shù)我們都放在HARDWARE文件夾下面相應(yīng)的.c和.h文件中(初始化函數(shù)是要根據(jù)外設(shè)連接到了哪個(gè)端口來編寫的,內(nèi)容包括用了哪個(gè)端口,處于什么工作模式下等),在這里使用到了這兩個(gè)文件,不要忘記添加頭文件和相應(yīng)的路徑。
1.對.h文件的編寫
(1).h文件一般用于存放函數(shù)的聲明和宏定義等
(2)使用到的函數(shù):ifndef,define,endif
#ifndef __XXXX_H
#define __XXXX_H
A
#else
B
#endif
這是一個(gè)宏定義,防止重復(fù)定義:如果沒有定義(if not define,ifndef)過xxxx.h,那么定義(define)xxxx.h并執(zhí)行A,如果定義過了就執(zhí)行B,通常我們在用的時(shí)候不會(huì)用到else,那么也就直接跳過了這段代碼(endif),其中的這些下劃線和H是c語言的書寫規(guī)范,并且都是用大寫來寫,如led.h就會(huì)寫成_LED_H。
關(guān)于這個(gè)函數(shù)的用法和重復(fù)定義的問題可以參考:http://blog.sina.com.cn/s/blog_aef927a50102wg9u.html
(3)舉例:
#ifndef __LED_H//if not define led.h
#define __LED_H//define led.h,并執(zhí)行下面的程序
void LED_Init(void);
#endif
注意:LED_Init是空函數(shù),在末尾要加上分號,沒有分號會(huì)報(bào)錯(cuò)。
說明:這里看到一個(gè)解釋,說.h頭文件并不是直接對應(yīng)執(zhí)行的,而是給編譯器看的,在.h文件中的代碼在實(shí)際編譯的時(shí)候會(huì)直接替換掉.c中的#include "xxx.h"。如果沒有這段代碼,那么遇到main.c中包含了許多頭文件,而這些頭文件中又不止一次包含了這個(gè)xxx.h,在編譯的時(shí)候就會(huì)出現(xiàn)重復(fù)定義的報(bào)錯(cuò),也會(huì)浪費(fèi)時(shí)間,而有了這段代碼就可以保證了它只被定義一遍,因?yàn)椴环蟦ot define,所以直接跳過。
2.對.c文件的編寫
在本實(shí)驗(yàn)中,.c文件里面存放了led的初始化函數(shù)LED_Init,
LED_Init包括:
(1)include相應(yīng)的.h文件
(2)對GPIO時(shí)鐘的初始化:GPIO用的是時(shí)鐘2,函數(shù)為:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx,ENABLE);
(3)對GPIO端口的初始化:
GPIO_InitTypeDefGPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=
GPIO_InitStructure.GPIO_Pin=
GPIO_InitStructure.GPIO_Speed=
GPIO_Init(GPIOx,&GPIO_InitStructure);
GPIO_SetBits(GPIOx,GPIO_Pin_x);
注意這里一定是要先設(shè)置初始值再進(jìn)行函數(shù)的調(diào)用。
下面來解釋一下這些語句:
1.GPIO_Init(GPIOx,&GPIO_InitStructure);
這條語句為初始化語句,輸入GPIO_Init后,可以goto去查看它的definition,可以看到是這個(gè)樣子的:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct),也就是說我們在.c文件中調(diào)用的是這個(gè)函數(shù)。通過查看這個(gè)函數(shù)我們就可以知道這個(gè)函數(shù)的格式和需要設(shè)置的參數(shù)都有哪些,可以看出,想要調(diào)用這個(gè)函數(shù),需要傳遞過來兩個(gè)參數(shù),一個(gè)是具體哪個(gè)管腳,一個(gè)是要被初始化的變量們組成的結(jié)構(gòu)體。具體是哪個(gè)管腳,取決于硬件電路的連接。
這兩句話可以類比于
hanshu(a,&b);//調(diào)用函數(shù)hanshu
void hanshu(int *p,char *q);//定義一個(gè)名叫hanshu的函數(shù),有兩個(gè)指針型變量,分別指向int和char型變量
GPIO_TypeDef* GPIOx:這句話表示一個(gè)結(jié)構(gòu)體變量的地址,GPIO_TypeDef是類型,相當(dāng)于int,float之類的,*GPIOx的意思是定義一個(gè)指針名叫GPIOx,整體這句話的意思就是定義一個(gè)名叫GPIOx的指針,這個(gè)指針指向GPIO_TypeDef類型的東西
在這里,要講一下指針的問題:
指針是能夠存放一個(gè)地址的一組存儲單元(通常為2個(gè)或4個(gè)字節(jié)),這就像機(jī)器的一個(gè)字節(jié)可以存放一個(gè)char類型的數(shù)據(jù),四個(gè)字節(jié)就可以存放一個(gè)long型的數(shù)據(jù),當(dāng)然了,四個(gè)字節(jié)我們就可以叫它單元了,存放著地址的存儲單元,我們叫它指針,它也是一個(gè)變量,只是其他變量表示的是一個(gè)數(shù)或者字母或者其他什么東西,而它表示的是地址,表示的是它所指向的東西的所在地址。這樣就可以理解指針到底是個(gè)什么東西了。
如果在A處存放了一個(gè)char類型的數(shù)據(jù)c(當(dāng)然也可以是別的,但char只是一個(gè)字節(jié)的,用來舉例比較好理解),若p是指向c的指針,那么,p中存放的就是這個(gè)c所在的地址,也就是A,根據(jù)學(xué)過的微機(jī)接口課程的內(nèi)容我們可以知道,這個(gè)A實(shí)際上應(yīng)該是一個(gè)類似于0xFFH之類的十六進(jìn)制的東西,我們在這里也沒有必要知道具體是什么,因?yàn)檫@個(gè)地址A是CPU自己分配的,我們想要進(jìn)行操作,那么就直接操作指針p就可以了,因?yàn)閜中存放的就是A呀?,F(xiàn)在這個(gè)A是我這里用來理解而加入的字母,在程序中是不存在這個(gè)A的,我們想要將一個(gè)東西指向另一個(gè)東西,要怎么寫呢?需要啷個(gè)運(yùn)算符,一個(gè)是地址運(yùn)算符&,一個(gè)是間接尋址或間接引用運(yùn)算符*。
p=&c,這樣p就指向c了。&可以取出內(nèi)存中的對象的地址,即變量或數(shù)組元素的地址(也就是說在這樣用的時(shí)候這個(gè)c只能夠代表變量或者數(shù)組元素,如果這個(gè)c是一個(gè)常量或者是一個(gè)表達(dá)式就不行了),&c就表示c所在的地址,也就是我之前假設(shè)的A,這個(gè)A也就相當(dāng)于&c。
*運(yùn)算符用于指針的定義和尋找指針指向的東西,&運(yùn)算符用于對變量的取址。
舉一個(gè)例子用來理解*和&
int x=1,y=2,z[10];
int *p;//定義一個(gè)指向int類型的東西的指針,它的名字叫p
p=&x;//現(xiàn)在指針p指向x,p指向x也就是p表示x所在的位置,現(xiàn)在p中應(yīng)該是一個(gè)地址
y=*p;//現(xiàn)在,y=1。*p表示取p中的內(nèi)容所代表的地址中的內(nèi)容,也就是去找p指向的東西,也就是x。
*p=0;//現(xiàn)在x=0,可以看出,指針是能夠改變它本來的東西的值的,與值的傳遞有差別,這一點(diǎn)稍后詳細(xì)解釋
p=&z[0];//現(xiàn)在指針p又轉(zhuǎn)而指向z[0]
這里簡要說一下為什么要用這個(gè)指針,在這個(gè)例子中當(dāng)然看不出它的優(yōu)勢,但是一旦出現(xiàn)對一個(gè)數(shù)組的每一個(gè)元素依次進(jìn)行計(jì)算或者是x中存放的內(nèi)容將會(huì)進(jìn)行改變的情況,指針的方便就顯現(xiàn)出來了。還有需要依次存儲的時(shí)候,可以用數(shù)組下標(biāo)來完成,也可以每次給地址+1,也就是用指針(這兩者的區(qū)別是用指針的時(shí)候程序執(zhí)行的速度更快,具體原理就不深入探究了,應(yīng)該就是微機(jī)接口那一套東西,指令時(shí)間指令長度之類的)。還有一種情況必須要用指針就是你想要對一個(gè)數(shù)組進(jìn)行計(jì)算,但需要調(diào)用子函數(shù)計(jì)算,因此就需要將這個(gè)數(shù)組傳給調(diào)用函數(shù),在傳遞的時(shí)候,c語言語法規(guī)定的是數(shù)組的名字代表這個(gè)數(shù)組第一個(gè)元素的地址,因此在調(diào)用子函數(shù)的時(shí)候傳遞的是這個(gè)數(shù)組的第一個(gè)元素的地址(參見c語言程序設(shè)計(jì)84頁),這個(gè)時(shí)候你在編寫子函數(shù)的時(shí)候,局部變量的變量類型必須設(shè)置成指針,因?yàn)榇娣诺氖堑刂罚▍⒁?5-86頁的例子)。其實(shí)到底什么時(shí)候用指針這個(gè)問題本人體會(huì)并不是非常深刻,所以就先寫到這里,如果以后遇到再添加進(jìn)來。指針給我的感覺就像是打包裝箱一樣,這樣可以不用考慮長度啊,類型啊之類的問題,用起來還是比較方便的,當(dāng)然前提是能用明白。。。
看書的時(shí)候還有一種感覺,正常將一個(gè)變量的值賦給另一個(gè)變量,就像將本地磁盤E中的文件復(fù)制粘貼到桌面上,無論我怎么對桌面上這個(gè)新文件進(jìn)行操作,存在E盤中的那個(gè)原文件都不會(huì)被改變,但指針不一樣,指針就像是將E盤中的這個(gè)文件創(chuàng)建了桌面的快捷方式,你能夠看到它的路徑是E盤而不是c盤的桌面,在進(jìn)行修改的時(shí)候也會(huì)直接影響到E盤中的那個(gè)原文件。忽然明白為什么說用指針會(huì)降低程序的可讀性了,因?yàn)槟阋窍胍眠@個(gè)指針,你就要追根溯源的去找它到底指向的是個(gè)什么東西而不是像變量那樣可以直接根據(jù)名字或者所賦的值來知道這個(gè)變量是個(gè)什么東西,而且也容易找著找著就蒙了。個(gè)人認(rèn)為,當(dāng)我們看到一個(gè)變量在使用的時(shí)候前面帶*,那么它應(yīng)該就是一個(gè)指針,我們就要去找這個(gè)指針指向的東西,找的時(shí)候如果看見這個(gè)指針=&一個(gè)什么東西,那它應(yīng)該就是指向這個(gè)東西了。
GPIO_InitTypeDef* GPIO_InitStruct:定義一個(gè)名叫GPIO_InitStruc的指針,指向GPIO_InitTypeDef型的數(shù)據(jù)。
整體來看這句話,就是我要調(diào)用這個(gè)初始化函數(shù)GPIO_Init(GPIOx,&GPIO_InitStructure);它要求我傳過去兩個(gè)參數(shù),一個(gè)是到底是哪個(gè)端口,是GPIOA還是B還是C還是什么,另一個(gè)是我要初始化的東西,由于初始化的參量比較多,于是就放在一個(gè)結(jié)構(gòu)體里面了關(guān)于結(jié)構(gòu)體,繼續(xù)看下文吧。
2.GPIO_InitTypeDefGPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=
GPIO_InitStructure.GPIO_Pin=
GPIO_InitStructure.GPIO_Speed=
這幾句話的意思:定義一個(gè)GPIO_InitTypeDef類型的結(jié)構(gòu),名字叫做GPIO_InitStructure(當(dāng)然可以叫別的名字)這個(gè)結(jié)構(gòu)的成員(結(jié)構(gòu)中定義的變量叫做成員)包括三個(gè):GPIO_Mode,GPIO_Pin和GPIO_Speed并進(jìn)行了初始化。至于為什么是這三個(gè),稍后會(huì)進(jìn)行解釋。
在這里我們只是定義了一個(gè)結(jié)構(gòu),并沒有對里面的成員進(jìn)行定義(里面的都是默認(rèn)值,需要設(shè)置哪個(gè)再單獨(dú)去改),下面三行語句是引用了成員并對其進(jìn)行了修改。
這里對結(jié)構(gòu)進(jìn)行一下說明:
結(jié)構(gòu)是一個(gè)或者多個(gè)變量的集合,之前我們在用MSP430編程的時(shí)候,就體會(huì)過傳遞參數(shù)的時(shí)候一下子傳過去好多個(gè),要加好多注釋,在matlab編程的時(shí)候也出現(xiàn)過這種狀況,在這里,采用了結(jié)構(gòu)(當(dāng)然其他語言也采用了,之前沒用過結(jié)構(gòu)體也是因?yàn)槲也粫?huì)用,哦呵呵)。對于結(jié)構(gòu)成員的引用的格式有兩個(gè):
(1)結(jié)構(gòu)名.成員,也就是后三行的引用方式。
(2)p->結(jié)構(gòu)成員,這個(gè)要求左邊的這個(gè)結(jié)構(gòu)必須為指針。若p是一個(gè)指向結(jié)構(gòu)的指針,那么可以用這種方式來引用相應(yīng)的結(jié)構(gòu)成員,在庫函數(shù)頭文件中采用的就是這種方式(當(dāng)我們gotodefinition的時(shí)候就可以發(fā)現(xiàn)),但是在我們的.c文件中不可以這么寫,因?yàn)檫@里我們在進(jìn)行定義的時(shí)候定義的是一個(gè)結(jié)構(gòu),是一個(gè)實(shí)體。
詳細(xì)內(nèi)容可以參照:http://www.cnblogs.com/winifred-tang94/p/5843440.html
(3)關(guān)于.和->的用法,有這樣一個(gè)規(guī)律:點(diǎn).要求左邊的這個(gè)結(jié)構(gòu)必須為實(shí)體,箭頭->要求左邊的這個(gè)結(jié)構(gòu)必須為指針。
如果想要對一個(gè)結(jié)構(gòu)進(jìn)行操作,有三種方法:一是分別傳遞各個(gè)成員,二是傳遞整個(gè)結(jié)構(gòu),三是傳遞指向結(jié)構(gòu)的指針。在這里就要再次提起剛剛討論過的那個(gè)初始化函數(shù)了,它的第二個(gè)變量傳遞的就是結(jié)構(gòu)體,并且采用的是第三種傳遞指針的方式。在結(jié)構(gòu)中使用指針的使用方法和普通變量的指針類似。
這里引用正點(diǎn)原子不完全手冊中的一個(gè)例子來解釋一下結(jié)構(gòu)體的優(yōu)勢:
在我們單片機(jī)程序開發(fā)過程中,經(jīng)常會(huì)遇到要初始化一個(gè)外設(shè)比如串口,它的初始化狀態(tài)是由幾個(gè)屬性來決定的,比如串口號,波特率,極性,以及模式。對于這種情況,在我們沒有 學(xué)習(xí)結(jié)構(gòu)體的時(shí)候,我們一般的方法是:
void USART_Init(u8 usartx,u32 u32 BaudRate,u8 parity,u8 mode);
這種方式是有效的,同時(shí)在一定場合是可取的。但是試想,如果有一天,我們希望往這個(gè)函數(shù)里 面再傳入一個(gè)參數(shù),那么勢必我們需要修改這個(gè)函數(shù)的定義,重新加入字長這個(gè)入口參數(shù)。于是我們的定義被修改為:
void USART_Init (u8 usartx,u32 BaudRate, u8 parity,u8 mode,u8 wordlength );
但是如果我們這個(gè)函數(shù)的入口參數(shù)是隨著開發(fā)不斷的增多,那么是不是我們就要不斷的修改函 數(shù)的定義呢?這是不是給我們開發(fā)帶來很多的麻煩?那又怎樣解決這種情況呢? 這樣如果我們使用到結(jié)構(gòu)體就能解決這個(gè)問題了。我們可以在不改變?nèi)肟趨?shù)的情況下, 只需要改變結(jié)構(gòu)體的成員變量,就可以達(dá)到上面改變?nèi)肟趨?shù)的目的。
結(jié)構(gòu)體就是將多個(gè)變量組合為一個(gè)有機(jī)的整體。上面的函數(shù),BaudRate,wordlength, Parity,mode,wordlength 這些參數(shù),他們對于串口而言,是一個(gè)有機(jī)整體,都是來設(shè)置串口參 數(shù)的,所以我們可以將他們通過定義一個(gè)結(jié)構(gòu)體來組合在一個(gè)。MDK 中是這樣定義的:
typedef struct
{
uint32_t USART_BaudRate;
uint16_t USART_WordLength;
uint16_t USART_StopBits;
uint16_t USART_Parity;
uint16_t USART_Mode;
uint16_t USART_HardwareFlowControl;
} USART_InitTypeDef;
于是,我們在初始化串口的時(shí)候入口參數(shù)就可以是 USART_InitTypeDef 類型的變量或者指針變 量了,MDK 中是這樣做的:
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
這樣,任何時(shí)候,我們只需要修改結(jié)構(gòu)體成員變量,往結(jié)構(gòu)體中間加入新的成員變量,而不需 要修改函數(shù)定義就可以達(dá)到修改入口參數(shù)同樣的目的了。這樣的好處是不用修改任何函數(shù)定義 就可以達(dá)到增加變量的目的。
在這里要說一下typedef函數(shù),這是一個(gè)類型定義函數(shù),可以用來建立新的數(shù)據(jù)類型名。用法為:
typedef舊的類型名新的類型名;
實(shí)際上這個(gè)函數(shù)并沒有新出現(xiàn)一個(gè)什么新的東西,只是在原來的基礎(chǔ)上命了新名字,作用類似于#define,但比define的功能更加強(qiáng)大,可以定義更多的東西。
舉個(gè)例子,我們看這段程序的第一句話,也就是定義了一個(gè)GPIO_InitTypeDef類型的結(jié)構(gòu),我們這里并不知道這個(gè)GPIO_InitTypeDef類型是個(gè)什么類型,那么我們?nèi)otodefinition,這樣就可以看到,在stm32f10x_gpio.h文件中存在著typedef定義的一個(gè)結(jié)構(gòu),里面包含了三個(gè)成員,GPIO_Mode,GPIO_Pin和GPIO_Speed,這也就解釋了為什么我們要對這幾個(gè)進(jìn)行賦值,對于其中的一個(gè)成員GPIO_Pin,我們可以看到,是一個(gè)uint16_t類型的數(shù)據(jù),到這里我們?nèi)匀徊恢肋@到底是個(gè)什么類型的數(shù)據(jù),因?yàn)閏語言中就只有int,char之類的類型,那么我們就再繼續(xù)gotodefinition,就可以在stdint.h文件中看到這樣一行語句:typedef unsigned shortint uint16_t;這句話我們可以類比typedef unsigned char uchar來看,用uchar代替unsigned char,這種方式在我們編寫msp430的時(shí)候經(jīng)常會(huì)用,只不過那時(shí)候用的是define。這樣我們就明白了,GPIO_Pin是一個(gè)無符號短整型的數(shù)據(jù),這和我們對430編程的時(shí)候的int x=0x00;中的x是一樣的。
到這里我們已經(jīng)剖開這個(gè)函數(shù)的層層外殼看到了最原始的定義,這也就和我們當(dāng)時(shí)編寫c51,msp430的時(shí)候自己定義的那些東西差不多了,而對于stm32來說,它的復(fù)雜程度已經(jīng)遠(yuǎn)遠(yuǎn)超過了那兩個(gè)單片機(jī),所以stm公司自己在出廠的時(shí)候就把許多函數(shù)打包好了,經(jīng)過層層包裝來到我們面前的這個(gè)函數(shù)庫,忽然覺得好震撼,這是一個(gè)多么龐大的工程啊,我們簡直是站在巨人的肩膀上。(咳咳,跑題了。。。)
就舉這一個(gè)例子,我實(shí)際操作又去看了一些其他的函數(shù),發(fā)現(xiàn)后面帶著TypeDef的東西都可以在gotodefinition的時(shí)候發(fā)現(xiàn)下一層函數(shù),剛才舉例的那個(gè)GPIO_Pin是屬于包裝層數(shù)少的了。
現(xiàn)在我們再回頭來看這四行代碼,疑問就可以解答了,整理如下:
為什么要定義一個(gè)結(jié)構(gòu)呢?
GPIO_Init函數(shù)要求我們傳遞過去一個(gè)結(jié)構(gòu)的地址,因此我們要在外面定義一個(gè)結(jié)構(gòu)。
為什么是GPIO_Pin,GPIO_Speed和GPIO_Mode這三個(gè)變量呢?
因?yàn)樵谖覀內(nèi)サ紾PIO_Init函數(shù)里面的時(shí)候,看到它的第二個(gè)參量GPIO_InitTypeDef*GPIO_StructInit,這里定義了傳過來的這個(gè)結(jié)構(gòu)的類型,就是GPIO_InitTypeDef,我們再goto去看GPIO_InitTypeDef是什么類型的時(shí)候會(huì)發(fā)現(xiàn)它被定義成一個(gè)包含這三個(gè)成員的結(jié)構(gòu)體,因此,我們在賦值的時(shí)候就要賦給這三個(gè)變量。
如果不賦值會(huì)怎樣?
如果不賦值,那么這個(gè)結(jié)構(gòu)體就默認(rèn)為stm公司設(shè)置的初值,這個(gè)初值也可以看到,在GPIO_Init函數(shù)里面,查看它的第二個(gè)參量GPIO_InitTypeDef*GPIO_StructInit的GPIO_StructInit,可以看到它的初始值,pin_all,2MHz和IN_FLOTING。
那么究竟要給這三個(gè)成員賦怎樣的初值呢?
這個(gè)的確定,我們需要用到一個(gè)工具,那就是技術(shù)手冊或是什么別的手冊,上面在講到GPIO都會(huì)仔細(xì)的說明每種工作方式,工作速度的區(qū)別我們要用哪個(gè)模式和速度,是由我們具體要做什么東西來確定的,而具體使用哪個(gè)引腳則需要通過查找硬件電路圖來確定。
選好想要的模式之后,具體的程序要怎么寫呢,有什么格式的要求嗎?
這就是對結(jié)構(gòu)體成員的賦值問題了。這個(gè)我們可以參照剛剛看到的stm給的初值的形式,但要注意使用點(diǎn)(.)而不是箭頭(->)。舉個(gè)例子,如Speed,在我們回答第二個(gè)問題的時(shí)候,我們可以看到stm定義的結(jié)構(gòu)體中的GPIO_Speed的類型為GPIOSpeed_TypeDef,當(dāng)然我們不知道這是一個(gè)什么類型,我們gotodefinition去看一下,可以看到stm用typedef定義了一個(gè)enum(枚舉類型)的東西,這里面包括三個(gè):GPIO_Speed_10MHz, GPIO_Speed_2MHz,GPIO_Speed_50MHz,因此我們可以知道,這個(gè)speed只有這三個(gè)值可以賦,并且不能夠?qū)懗?0,50之類的任意格式,而必須是GPIO_Speed_10MHz,GPIO_Speed_2MHz,GPIO_Speed_50MHz其中的一個(gè),假設(shè)說我們選擇的是50MHz的速度,那么就要寫成‘結(jié)構(gòu)體名.GPIO_Speed_50MHz’的形式。對于工作模式的設(shè)置也是一樣,goto之后就可以看到它也是一個(gè)枚舉類型,里面包含八個(gè)成員。
解釋到這里,這四句的意思大概已經(jīng)清楚了。
還有一點(diǎn)要說的,c語言中規(guī)定所有的變量、結(jié)構(gòu)體的聲明(定義)都必須在代碼段之前,就像我們之前define都要寫在最上面一樣,因此第一句的結(jié)構(gòu)定義放在了最前面。
3.GPIO_SetBits(GPIOx,GPIO_Pin_x);
這句話比較好理解,設(shè)置LED默認(rèn)不點(diǎn)亮,置1是不點(diǎn)亮,至于為什么,看電路圖就明白了,LED的正連的高電平,負(fù)連的管腳。
對于程序的解析到此為止。
附加說明一個(gè)問題,我們是如何知道要用這些函數(shù)呢,比如GPIO_SetBits這個(gè)函數(shù)。其實(shí)和GPIO有關(guān)的函數(shù)有一個(gè)匯總,就在stm32f10x_gpio.h文件的最下面,有許多的函數(shù),我們大致看一眼就可以知道那個(gè)函數(shù)是做什么用的,因?yàn)橐呀?jīng)經(jīng)過層層包裝,名字已經(jīng)很好辨認(rèn)了,就像SetBits函數(shù),很明顯是置位用的。(這里總覺得不太對勁,應(yīng)該還有什么別的方式來查找函數(shù)的)在這里我們也可以基本看到這個(gè)函數(shù)的格式,需要哪些參數(shù),例如void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);可以看出來我們需要給它兩個(gè)參數(shù),一個(gè)是GPIOx,一個(gè)是pinx,如PA8就是GPIOA,GPIO_Pin_8。這里要說明的是,我們在使用原子家的mini板的時(shí)候,往往不會(huì)自己親自動(dòng)手從頭再去編寫初始化什么的東西,而是可以參考他給的例程,進(jìn)行加工和修改。
我們剛剛編寫.c和.h文件,性質(zhì)上和stm32的開發(fā)者一樣,都是在給程序打包裝箱,只不過stm32給出的函數(shù)庫更加的通用,而我們寫的這兩個(gè)文件比較有針對性。
3.main.c的編寫
(1)include各個(gè)需要的文件
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
(2)編寫main主函數(shù):
一般情況下,main中包含兩部分:
用到的各個(gè)部分的初始化,在本實(shí)驗(yàn)中包括led的初始化(也就是剛剛在led.c里面寫的那個(gè)初始化函數(shù)),delay(延時(shí))函數(shù)的初始化
delay_init();
led_Init();
while函數(shù),基本上我們需要的函數(shù)都會(huì)放在while(1){ }里面,因?yàn)槲覀円屗恢眻?zhí)行,而不是執(zhí)行一次就完事了,但是對于比較復(fù)雜的函數(shù),往往不是這個(gè)樣子的,會(huì)有許多的判斷條件。
while(1)
{
GPIO_SetBits(GPIOA,GPIO_Pin_8);//設(shè)置LED默認(rèn)不點(diǎn)亮
delay_ms(500);
GPIO_ResetBits(GPIOA,GPIO_Pin_8);//設(shè)置LED默認(rèn)不點(diǎn)亮
delay_ms(500);
}
說明:本實(shí)驗(yàn)中,main函數(shù)的返回值類型是int型的,而不是void型的,void是空,是不提供返回值的意思
這里要寫成int main (void){}
這里需要說明一下:c語言規(guī)定不接收任何參數(shù)也不返回任何信息的函數(shù)原型為“void foo(void);”??赡苷且?yàn)檫@個(gè),所以很多人都誤認(rèn)為如果不需要程序返回值時(shí)可以把main函數(shù)定義成void main(void) 。然而這是錯(cuò)誤的!c語言規(guī)定main 函數(shù)的返回值應(yīng)該定義為 int 類型。雖然在一些編譯器中,void main 可以通過編譯(如我們用IAR寫msp430的時(shí)候),但并非所有編譯器都支持 void main(如我們現(xiàn)在用的keil),因?yàn)闃?biāo)準(zhǔn)中從來沒有定義過 void main 。
main 函數(shù)的返回值類型必須是 int ,這樣返回值才能傳遞給程序的激活者(如操作系統(tǒng)),如果 main 函數(shù)的最后沒有寫 return 語句的話,C99 規(guī)定編譯器要自動(dòng)在生成的目標(biāo)文件中(如 exe 文件)加入return 0; 。main函數(shù)的返回值用于說明程序的退出狀態(tài)。如果返回0,則代表程序正常退出。返回其它數(shù)字的含義則由系統(tǒng)決定。通常,返回非零代表程序異常退出。
更加詳細(xì)的解釋可以參考:http://blog.csdn.net/piaojun_pj/article/details/5986516
PS:
1.go to Definition 的時(shí)候會(huì)出現(xiàn)以下這種情況,解決辦法:在name下面任意雙擊其中一個(gè)就可以跳過去,但具體我們要找的是哪一個(gè),需要自己去判斷。