STM32學習筆記(1):GPIO口的使用
2011年3月19日 順序點亮LED燈
摸索了很久之后終於把ARM開發板上的LED燈點亮了,雖然是很簡單的一個IO口操作,但是由於以前從來都沒有什么經驗,所以浪費了很多時間,也查找了很多資料。現在可以操作IO口了,證明邁出了學習ARM的第一步。
實驗平台清單如下:
開發板: 奮斗STRIVE V3
核心芯片: STM32F103VET6
開發環境: RealView MDK-ARM Version:3.50
PC操作系統: Windows 7 家庭普通版
仿真器: SEGGER J-Link
其中,STM32F103VET6芯片是基於ARM Cortex-M3內核的,具體技術參數請參考ST公司給出的芯片資料(http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/CD00191185.pdf)。關於該芯片的其他資料,可以在http://www.st.com/cn/mcu/product/164491.jsp上找到。如果對RealView MDK不熟悉的話,可以參考ARM RealView系列叢書《ARM開發工具RealView MDK使用入門》,李寧編著,北京航空航天大學出版社出版。如果對於STM32不熟悉的話,可以參考ARM RealView MDK系列叢書《基於MDK的STM32處理器開發應用》,李寧編著,北京航空航天大學出版社出版。
當軟硬件平台都准備好之后,就可以開始新的工程了。對於一個初學者來說,新建一個可以運行的工程其實是有難度的,因為根本不知道從何下手。因此,我將每一步細節都描述出來,以便於即使是初學者也能很好的理解ARM的初級操作。
打開MDK開發平台,在菜單欄中單擊“Project - New μVision Project”創建一個新的工程。然后在彈出的“Select Device for Target 1”對話框中選擇合適的芯片。由於我采用的是STM32F103VET6,所以選擇ST – STM32F103VE。
選擇好芯片之后,會彈出一個消息框,“Copy STM32 Startup Code to Project Folder and Add File to Project?”問你是否需要加載啟動代碼。選擇“是”后進入工程。
所謂啟動代碼,就是處理器在啟動的時候執行的一段代碼,主要任務是初始化處理器模式,設置堆棧,初始化變量等等。由於以上的操作均與處理器體系結構和系統配置密切相關,所以一般由匯編來編寫。而對於初學者而言,自己設計啟動代碼有一定的難度,MDK開發平台內置了一些常用芯片的啟動代碼,因此在新建工程的時候,最好是采用默認的啟動代碼。當然,芯片制造廠商也會自己編寫一些啟動代碼,放在官網上供開發者下載。
進入工程之后,我們就可以開始寫代碼了。首先得新建一個文件,然后將其保存成為*.c的格式,這樣開發環境就可以識別出編寫的代碼里面一些常用的關鍵字和其他信息了。我就直接保存成為main.c。然后在屏幕左邊的Project Workspace中的Source Group 1單擊右鍵,選擇Add Files to Group “ Source Group 1”,將我們剛剛保存起來的main.c添加到Source Group 1中,或者直接雙擊Source Group 1,也可以添加文件。
接下來就可以開始寫代碼了,對於初學者而言,最基礎的操作應該是對芯片IO口的操作了。因此我在學習ARM的時候,第一個工程就選擇了讓開發板上的3個LED燈順序點亮。STM32F103VET6中一共有A-G共7組通用輸入輸出接口(General-Purpose Inputs/Outputs),每個GPIO引腳都可以由軟件配置成輸出(推挽或開漏)、輸入(帶或不帶上拉或下拉)或復用的外設功能端口。多數GPIO引腳都與數字或模擬的復用外設共用。具體的細節請參考Datasheet。在《基於MDK的STM32處理器開發應用》一書中,“7.1 通用IO端口”詳細描述了各個端口的功能、寄存器格式以及其他相關信息,因此就不在這里贅述了。
回到MDK開發平台,現在要在main.c中加入相關代碼了。代碼清單如下:
#include "stm32f10x_lib.h"
int main()
{
int i;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOB, ENABLE); //開啟外設時鍾
GPIOD->CRL = 0x33333333; //設置端口配置寄存器
GPIOB->CRL = 0x33333333;
while(1)
{
GPIOD->ODR = 0xffffffbf; //設置端口輸出寄存器
for(i=0;i<1000000;i++); //延時
GPIOD->ODR = 0xffffffff7;
for(i=0;i<1000000;i++);
GPIOD->ODR = 0x00000000;
GPIOB->ODR = 0xffffffff;
for(i=0;i<1000000;i++);
GPIOB->ODR = 0x00000000;
}
}
上述代碼中,#include "stm32f10x_lib.h"包含了開發stm32f10x系列芯片所需的基本頭文件,在進行程序編寫的時候,務必要包含此頭文件。
RCC_APB2PeriphClockCmd()函數是設置外設時鍾。ARM與C51單片機不同的是,不用外設的時候,如IO口、ADC、定時器等等,都是禁止時鍾的,以達到節能的目的,只有要用到的外設,才開啟它的時鍾。因此在需要用到GPIOB和GPIOD的時候,我們需要先開啟它的時鍾,具體用到的是函數庫里面的函數:
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
其中,第一個參數需要指示要開啟什么端口的時鍾,RCC_APB2Periph_GPIOx就是開啟GPIOx的時鍾,第二個參數需要指示是開啟還是關閉,ENABLE/DISABLE。
開啟外設時鍾之后,然后就開始對GPIO的配置寄存器進行設置了,具體設置方式參考《基於MDK的STM32處理器開發應用》一書中,“7.1 通用IO端口”。While循環里面就是給GPIO的端口輸出寄存器賦值,由於我手上這款奮斗開發板的三個LED燈分別接的是D3、D6和B5,所以只要將D端口和B端口相應的位上置1就可以了。
編譯之后我們發現編譯器報錯,Undefined symbol RCC_APB2PeriphClockCmd,是因為我們使用了的RCC_APB2PeriphClockCmd()函數在頭文件中聲明了,卻沒有在C文件中定義,這個函數在.. Keil\ARM\RV31\LIB\ST\STM32F10x\stm32f10x_rcc.c中,將這個文件復制到工程的根目錄下,然后在屏幕左邊的Workspace中添加進來,就可以了。
至於如何下載到ARM開發板中,不同的開發板有不同的方法,而開發板生產廠商一般都會將有關的文檔連同開發板一起附送,在此就不詳細贅述了。
其實,在MDK的庫中,還定義了很多宏,可以避免讓我們自己去查找相關資料來設置寄存器的各個位。比如,在本次實驗中,對於LED等的亮滅也可以通過以下代碼來實現。
#include "stm32f10x_lib.h"
int main()
{
int i;
GPIO_InitTypeDef GPIO_InitStructure; //定義GPIO宏操作結構體
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOB,ENABLE); //外設時鍾配置,開啟GPIOB和GPIOD的時鍾
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //將B5口配置為通用推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //口線翻轉速度為50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure); //配置GPIOB口
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_3; //將D3和D6口配置為推挽輸出
GPIO_Init(GPIOD, &GPIO_InitStructure); //配置GPIOD口
while(1)
{
GPIO_SetBits(GPIOB, GPIO_Pin_5); //B5口輸出高電平
GPIO_ResetBits(GPIOD, GPIO_Pin_6); //D6口輸出低電平
GPIO_ResetBits(GPIOD, GPIO_Pin_3); //D3口輸出低電平
for(i=0;i<1000000;i++);
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
GPIO_ResetBits(GPIOD, GPIO_Pin_6);
GPIO_SetBits(GPIOD, GPIO_Pin_3);
for(i=0;i<1000000;i++);
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
GPIO_ResetBits(GPIOD, GPIO_Pin_3);
GPIO_SetBits(GPIOD, GPIO_Pin_6);
for(i=0;i<1000000;i++);
}
}
由於我們使用了GPIO_InitTypeDef類型,所以我們需要找到它的定義,這個定義包含在“…\Keil\ARM\RV31\LIB\ST\STM32F10x\stm32f10x_gpio.c”中,將文件復制到工程根目錄下,然后再添加進入工程中,編譯才不會報錯。
在絕大多數C編譯器中,要求所有的變量聲明都在執行語句塊之前,也就是說如果需要定義的變量需要先在進入main函數一開始就全部定義好,如果執行了某一條語句之后再定義變量的話,就會報錯.