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

2.3 STM32單片機的I/O端口配置

while(1)邏輯塊中的代碼是例程Led_Blink.c的功能主體:

      while (1)
        {
          GPIO_SetBits(GPIOC,GPIO_Pin_13);  //PC13輸出高電平
          delay_nms(500);                   // 延時500ms
          GPIO_ResetBits(GPIOC,GPIO_Pin_13);// PC13輸出低電平
          delay_nms(500);                   // 延時500ms
        }

先給PC13腳輸出高電平,由賦值語句GPIO_SetBits(GPIOC,GPIO_Pin_13)完成,然后調用延時函數delay_nms(500)等待500ms,再給PC13腳輸出低電平,即GPIO_ResetBits(GPIOC, GPIO_Pin_13),然后再次調用延時500ms函數delay_nms(500)。這樣就完成了一次閃爍。

在程序中,你沒有看到PC13:GPIOC和GPIO_Pin_13的定義,它們已經在固件函數標準庫(stm32f10x_map.h和stm32f10x_gpio.h)中定義好了,由頭文件stm32f10x_heads.h包括進來。回想一下,用Keil開發51單片機程序時,也是一樣的。后續章節中將要用到的其他引腳名稱和定義都是如此。

GPIO_SetBits和GPIO_ResetBits這兩個函數在stm32f10x_gpio.c中實現,后面將作介紹。

時序圖簡介

時序圖反應的是高、低電壓信號與時間的關系圖。在圖2.6中,時間從左到右增長,高、低電壓信號隨著時間在低電平或高電平之間變化。這個時序圖顯示的是剛才實驗中的1000ms的高、低電壓信號片段。右邊的省略號表示的是這些信號是重復出現的。

圖2.6 程序Led_Blink.c的時序圖

微控制器的最大優點之一就是它們從來不會抱怨不停地重復做同樣的事情。為了讓單片機不斷閃爍,你需要把讓LED閃爍一次的幾個語句放在while(1){…}循環里。這里用到了C語言實現循環結構的一種形式:

      while(表達式)  循環體語句

當表達式為非0值時,執行while語句中的內嵌語句,其特點是先判斷表達式,后執行語句。例程中直接用1代替了表達式,因此總是非0值,所以循環永不結束,也就可以一直讓LED燈閃爍。

注意:循環體語句如果包含一個以上的語句,就必須用花括號(“{ }”)括起來,以復合語句的形式出現。如果不加花括號,則while語句的范圍只到while后面的第一個分號處。例如,本例中while語句中如果沒有花括號,則while語句范只到“GPIO_SetBits(GPIOC,GPIO_Pin_13);”。

也可以不要循環體語句,如第一章例程中就直接用while(1);程序將一直停在此處。

STM32系列單片機的I/O端口模式

STM32系列單片機的輸入/輸出引腳可配置成以下8種(4輸入+2輸出+2復用輸出):

① 浮空輸入:In_Floating。

② 帶上拉輸入:IPU(In Push-Up)。

③ 帶下拉輸入:IPD(In Push-Down)。

④ 模擬輸入:AIN(Analog In)。

⑤ 開漏輸出:OUT_OD。OD代表開漏:Open-Drain。(OC代表開集:Open-Collector)。

⑥ 推挽輸出:OUT_PP。PP代表推挽式:Push-Pull。

⑦ 復用功能的推挽輸出:AF_PP。AF代表復用功能:Alternate-Function。

⑧ 復用功能的開漏輸出:AF_OD。

開漏輸出與推挽輸出

開漏輸出:MOS管漏極開路。要得到高電平狀態需要上拉電阻才行。一般用于線或、線與,適合做電流型的驅動,其吸收電流的能力相對強(一般20mA以內)。開漏是對MOS管而言,開集是對雙極型管而言,在用法上沒區別,開漏輸出端相當于三極管的集電極。如果開漏引腳不連接外部的上拉電阻,則只能輸出低電平。因此,對于經典的51單片機的P0口,要想做輸入/輸出功能必須加外部上拉電阻,否則無法輸出高電平邏輯。一般來說,可以利用上拉電阻接不同的電壓,改變傳輸電平,以連接不同電平(3.3V或5V)的器件或系統,這樣你就可以進行任意電平的轉換了。

推挽輸出:如果輸出級的兩個參數相同MOS管(或三極管)受兩互補信號的控制,始終處于一個導通、一個截止的狀態,就是推挽相連,這種結構稱為推拉式電路。推挽輸出電路輸出高電平或低電平時,兩個MOS管交替工作,可以減低功耗,并提高每個管的承受能力。又由于不論走哪一路,管子導通電阻都很小,使RC常數很小,邏輯電平轉變速度很快,因此,推拉式輸出既可以提高電路的負載能力,又能提高開關速度,且導通損耗小效率高。輸出既可以向負載灌電流(作為輸出),也可以從負載抽取電流(作為輸入)。

下面我們來看看通用GPIO(General Purpose Input Output)端口的初始化配置函數: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);
      }

其中,函數GPIO_Init的具體實現在庫文件“stm32f10x_gpio.c”中(\library\src目錄下)。其作用是定義各個通用I/O端口的模式。

從上面的程序代碼可以看出,對應到外設的輸入/輸出功能有以下三種情況:

(1)外設對應的引腳為輸入:則根據外圍電路的配置可以選擇浮空輸入、帶上拉輸入或帶下拉輸入。

(2)ADC對應的引腳:配置引腳為模擬輸入。

(3)外設對應的引腳為輸出:需要根據外圍電路的配置選擇對應的引腳為復用功能的推挽輸出或復用功能的開漏輸出。如果把端口配置成復用輸出功能,則引腳和輸出寄存器斷開,并和片上外設的輸出信號連接。將引腳配置成復用輸出功能后,如果外設沒有被激活,那么它的輸出將不確定。

當GPIO口設為輸入模式時,輸出驅動電路與端口是斷開,此時輸出速度配置無意義,不用配置。在復位期間和剛復位后,復用功能未開啟,I/O端口被配置成浮空輸入模式。所有端口都有外部中斷能力。為了使用外部中斷線,端口必須配置成輸入模式。

當GPIO口設為輸出模式時,有3種輸出速度可選(2MHz、10MHz和50MHz),這個速度是指I/O口驅動電路的響應速度而不是輸出信號的速度,輸出信號的速度與程序有關(芯片內部在I/O口的輸出部分安排了多個響應速度不同的輸出驅動電路,可以根據需要選擇合適的驅動電路)。通過選擇速度來選擇不同的輸出驅動模塊,達到最佳的噪聲控制和降低功耗的目的。高頻的驅動電路,噪聲也高,當不需要高的輸出頻率時,請選用低頻驅動電路,這樣非常有利于提高系統的電磁干擾(EMI)性能。當然如果要輸出較高頻率的信號,但卻選用了較低頻率的驅動模塊,很可能會得到失真的輸出信號。關鍵是GPIO的引腳速度跟應用匹配(推薦10倍以上)。

對于串口,假如最大波特率只需115.2K,那么用2M的GPIO的引腳速度就夠了,既省電也噪聲小;對于I2C接口,假如使用400K傳輸速率,若想把余量留大些,那么用2M的GPIO的引腳速度或許不夠,這時可以選用10M的GPIO引腳速度;對于SPI接口,假如使用18M或9M傳輸速率,用10M的GPIO的引腳速度顯然不夠了,需要選用50M的GPIO的引腳速度。

電磁干擾

EMI:電磁干擾(Electromagnetic Interference)是指電磁波與電子元件作用后產生的干擾現象,分為傳導干擾和輻射干擾。傳導干擾是指通過導電介質把一個電網絡上的信號耦合(干擾)到另一個電網絡。輻射干擾是指干擾源通過空間把其信號耦合(干擾)到另一個電網絡。在高速系統中,高頻信號線、集成電路的引腳、各類接插件等都可能成為具有天線特性的輻射干擾源,能發射電磁波并影響其他系統或本系統內其他子系統的正常工作。

由此可見,STM32系列單片機的GPIO功能很強大,具有以下功能:

(1)最基本的功能是可以驅動LED、產生PWM、驅動蜂鳴器等;

(2)具有單獨的位設置或位清除,編程簡單。端口配置好以后只需GPIO_SetBits(GPIOx, GPIO_Pin_x)就可以實現對GPIOx的pinx位為高電平,GPIO_ResetBits(GPIOx, GPIO_Pin_x)就可以實現對GPIOx的pinx位為低電平;

(3)具有外部中斷/喚醒能力,端口配置成輸入模式時,具有外部中斷能力;

(4)具有復用功能,復用功能的端口兼有I/O功能等;

(5)軟件重新映射I/O復用功能:為了使不同器件封裝的外設I/O功能的數量達到最優,可以把一些復用功能重新映射到其他一些腳上。這可以通過軟件配置相應的寄存器來完成。這時,復用功能就不再映射到它們的原始引腳上了;

(6)GPIO口的配置具有鎖定機制,當配置好GPIO口后,在一個端口位上執行了鎖定(LOCK),可以通過程序鎖住配置組合,在下一次復位之前,將不能再更改端口位的配置。

STM32系列單片機的每個GPIO端口有兩個32位配置寄存器(GPIOx_CRL,GPIOx_CRH),兩個32位數據寄存器(GPIOx_IDR,GPIOx_ODR),一個32位置位/復位寄存器(GPIOx_BSRR),一個16位復位寄存器(GPIOx_BRR)和一個32位鎖定寄存器(GPIOx_LCKR)。GPIO端口的每個位可以由軟件分別配置成多種模式。每個I/O端口位可以自由編程。

注意:I/O端口寄存器必須按32位字被訪(不允許半字或字節訪問)。GPIOx_BSRR和GPIOx_BRR寄存器允許對任何GPIO寄存器的讀/寫的獨立訪問。定義這些GPIO寄存器組的結構體是GPIO_TypeDef,在庫文件“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;  // 輸入數據寄存器:input data register(GPIOx_IDR)(x=A..E)
        vu32 ODR;  // 輸出數據寄存器:output data register(GPIOx_ODR)(x=A..E)
        vu32 BSRR; // 位置位/復位寄存器:bit set/reset register(GPIOx_BSRR)(x=A..E)
        vu32 BRR;  // 位復位寄存器: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

函數GPIO_Init的第一個參數是這些GPIOx(x=A,B,C,D,E)寄存器的存儲映射首地址,第二個參數是用戶對GPIO端口設置的參數所在首地址,這些數據存放在結構體GPIO_InitTypeDef中,包括所要設置GPIO的端口號,類型和速度。STM32單片機使用固件庫函數完成外設(如GPIO、TIM、USART、ADC、DMA、RTC等)初始化都采用這種規范,如圖2.7所示,這種固件庫結構大大提高了程序的開發效率。

圖2.7 使用固件庫函數完成外設初始化示意圖

從上面的宏定義可以看出,GPIOx(x=A,B,C,D,E)寄存器的存儲映射首地址分別是0x40010800,0x40010C00,0x40011000,0x40011400,0x40011800;AFIO寄存器的存儲映射首地址是0x40010000。GPIO和AFIO寄存器映像和復位值如表2.2和2.3所示。

表2.2 GPIO寄存器映像和復位值

表2.3 AFIO寄存器映像和復位值

下面我們來看看“stm32f10x_gpio.c”文件中的GPIO_SetBits和GPIO_ResetBits等對STM32單片機I/O口操作的幾個函數:

      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函數實際上是直接訪問了GPIO的相關寄存器,對I/O端口的對應位置“1”或清“0”,使引腳輸出高電平或低電平。我們可以利用這些STM32固件庫的函數來開發自己的應用程序,當然也可以不使用固件庫,而直接對寄存器訪問來編寫應用程序,這需要你對STM32單片機寄存器的各個位的含義和使用十分熟悉。不使用固件庫的發光二極管閃爍程序見本書配套例程。你可以對比一下代碼尺寸的變化和程序運行的結果。

對于開發一個系統級的產品而言,建議使用STM32固件庫。它具有以下特性:

● 兼容性好:使用宏定義能夠靈活地兼容各個型號和不同功能;

● 命名規范:不用注釋就能看懂變量或函數,可讀性好,而且不會重名;

● 通用性強:多數庫文件都是只讀類型,不用修改便可實現不同功能間的調用。

使用固件庫也有缺點,如運行性能有所損失,速度會變慢些。對于越來越復雜的嵌入式應用而言,隨著處理器存儲容量和頻率的提高,筆者建議應該更關注項目的整體開發效率,而不是具體的代碼尺寸。對于時序要求嚴格的地方,完全可以直接訪問寄存器減小代碼尺寸,根據實際開發設計的要求來確定。同時,使用固件庫進行程序開發,借鑒ST固件庫函數的命名規范,有助于養成良好的編碼風格,學以致用,提高編程水平,正如本書前言所述。關于STM32單片機固件庫的介紹參見附錄。

什么是assert(斷言)

編寫代碼時,我們總是會做出一些假設,斷言就是用于在代碼中檢測這些假設是否成立,比如,向一個端口寫數據,如果這個端口不存在,則不能向一個端口寫數據。例如,向GPIO_Pin_0端口寫是合法的,而向GPIO_Pin_20端口寫就是非法的,因為STM32單片機根本不存在這個端口。

可以將斷言看做是程序異常處理的一種高級形式。斷言表示為一些布爾表達式,如果在程序的某個特定點要判斷某個表達式值為真,則可以進行斷言驗證。可以在任何時候啟用和禁用斷言驗證。一般我們會讓斷言語句在編譯Debug版本的程序時生效,而在編譯Release版本的程序時禁止。同樣,最終用戶在運行程序遇到問題時可以重新啟用斷言。使用斷言可以創建更穩定,優秀且不易于出錯的代碼。當需要在一個值為FALSE時中斷當前操作的話,可以使用斷言。單元測試必須使用斷言(Junit/JunitX)。除了類型檢查和單元測試外,斷言還提供了一種確定各種特性是否在程序中得到維護的極好的方法。

assert宏的原型定義在<assert.h>中,其作用是如果它的條件返回錯誤,則終止程序執行,原型定義:void assert( int expression );

assert的作用是現計算表達式expression,如果其值為假(即為0),那么它先向stderr打印一條出錯信息,然后通過調用abort來終止程序運行。

使用assert的缺點是:頻繁的調用會極大的影響程序的性能,增加額外的開銷。在調試結束后,可以通過如下代碼來禁用assert調用:

      #include <stdio.h>
      #define NDEBUG
      #include <assert.h>

一般地,STM32系列單片機中配置片內外設使用的通用I/O端口需經過一下幾步設置:

(1)配置輸入的時鐘。

使能APB2總線外設時鐘:RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE)。釋放GPIO復位:RCC_APB2PeriphResetCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC, DISABLE)。

(2)初始化后即被激活(開啟)。

(3)如果使用該外設的輸入/輸出引腳,則需要配置相應的GPIO端口(否則該外設對應的輸入/輸出引腳可以做普通GPIO引腳使用)。

(4)配置各個PIN端口的模式和速度。

(5)GPIO初始化。

本書所用STM32單片機教學開發板的各個I/O口配置如下:

(1)PA9和PA10是串口1的發送和接收引腳(電路板設計)。

(2)PB8、PB9、PC12、PC13是輸出控制發光二極管(電路板設計)。

(3)PD7、PD8、PD9、PD10是輸出控制電機(電路板設計)。

(4)PA0、PB0是AD輸入引腳,PA4是AD輸入引腳或DA輸出引腳(電路板設計)。

(5)PC8、PC9、PC10、PC11是按鍵輸入引腳(電路板設計)。

(6)PC0~PC7、PD4、PD5、PD6是輸出控制1602液晶(電路板設計)。

(7)PE0、PE1定義成輸出引腳。

(8)PE2、PE3定義成輸入引腳。

(9)PE4、PE5定義成是輸入引腳。

其中,PE口的16個I/O端口并未設計具體電路,開放出來了,你可在面包板上自行搭建電路或制作一個擴展板。

注意PE4、PE5在函數NVIC_Configuration中,定義成了外部中斷輸入。那些還沒有設置的引腳,你可以參照上述方法設置。

串口初始化函數USART_Configuration,在頭文件HelloRobot.h中實現,具體內容將在后面章節講解。調用printf是為了在程序執行前給調試終端發送一條提示信息,告訴你現在程序開始執行了,并告訴你隨后程序將開始干什么。這在你以后的編程開發過程中是一個良好的習慣,將非常有助于你提高程序的調試效率。

任務三 該你了——讓另一個LED閃爍

LED電路元件

(1)1個發光二極管(紅色、綠色、黃色皆可);

(2)1個470Ω電阻(色環:黃—紫—黑—黑)。

LED電路搭建

參照圖2.2所示電路在智能機器人教學開發板的面包板上搭建起實際電路。實際搭建好的電路參考圖2.8所示照片。實際搭建電路時注意:

圖2.8 發光二極管在PE0端口低電平時亮

● 確認電路板電源斷開,等搭建好電路后,再開電源開關;

● 確認發光二極管的短針腳(陰極)通過470Ω電阻與PE0相連;

● 確認發光二極管的長針腳(陽極)通過導線與“5V”或“3.3V” 電源相連。注意養成良好習慣:當連接導線與“電源”相連時用“紅色”導線,與“地”相連時用“黑色”導線,與“信號”相連時用其他顏色導線,如白色導線。

你也可以這樣搭建電路,如圖2.9所示。

圖2.9 發光二極管在PE0端口高電平時亮

● 確認發光二極管的短針腳(陰極)與“GND” 相連;

● 確認發光二極管的長針腳(陽極)通過470Ω電阻與PE0相連。

嵌入式系統中,通過I/O端口控制LED時,盡量考慮使用灌電流的方式,即低電平時,LED亮。

讓另一個連接到PE0引腳的LED閃爍是一件很容易的事情,把PC13改為PE0,重新運行程序即可。參考下面的代碼段修改程序:

      while (1)
      {   GPIO_SetBits(GPIOE,GPIO_Pin_0);    //PE0輸出高電平
            delay_nms(500);                  //延時500ms
            GPIO_ResetBits(GPIOE,GPIO_Pin_0);//PE0輸出低電平
            delay_nms(500);                  //延時500ms
      }

運行修改后的程序,確定能讓LED閃爍。你也可以讓兩個LED同時閃爍。參考下面代碼段修改程序:

      while (1)
      {   GPIO_SetBits(GPIOC,GPIO_Pin_13);     //PC13輸出高電平
            GPIO_SetBits(GPIOE,GPIO_Pin_0);    //PE0輸出高電平
            delay_nms(500);                    //延時500ms
            GPIO_ResetBits(GPIOC,GPIO_Pin_13); //PC13輸出低電平
      GPIO_ResetBits(GPIOE,GPIO_Pin_0);        //PE0輸出低電平
            delay_nms(500);                    //延時500ms
      }

運行修改后的程序,確定能讓兩個LED幾乎同時閃爍。

當然,你可以再次修改程序,讓兩個發光二極管交替亮或滅,你也可以通過改變延時函數的參數n的值,來改變LED的閃爍頻率。嘗試一下!

任務四 流水燈

例程:流水燈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);
        }
      }

按照上述方法建立新的項目,輸入程序Led_Shift.c,運行查看結果。你也可以調整延時時間為100ms或10ms,試試效果。

主站蜘蛛池模板: 锡林郭勒盟| 乐至县| 米林县| 拉萨市| 嘉善县| 丁青县| 海宁市| 那坡县| 洞口县| 西宁市| 合作市| 堆龙德庆县| 富平县| 永福县| 涡阳县| 阳泉市| 博乐市| 纳雍县| 崇左市| 吴江市| 海淀区| 湄潭县| 屏南县| 南城县| 延吉市| 咸阳市| 上虞市| 尖扎县| 金溪县| 长沙市| 商南县| 太湖县| 中江县| 太谷县| 萍乡市| 蒙山县| 临泽县| 游戏| 普洱| 宜黄县| 沅陵县|