官术网_书友最值得收藏!

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,試試效果。

主站蜘蛛池模板: 安庆市| 龙里县| 靖边县| 西和县| 巴彦淖尔市| 富平县| 基隆市| 关岭| 综艺| 云龙县| 长寿区| 日土县| 日土县| 宿松县| 黄陵县| 黄石市| 嘉祥县| 翼城县| 南投市| 前郭尔| 类乌齐县| 蒙山县| 英山县| 浦东新区| 永济市| 吉水县| 交城县| 巧家县| 凌海市| 南溪县| 保定市| 永修县| 简阳市| 长泰县| 仙桃市| 衡阳市| 凤翔县| 罗甸县| 永登县| 沁水县| 申扎县|