STM32CubeMX Lesson 1 ─ GPIO實現LED及按鈕控制

  General Purpose Input/Output (GPIO)是所有微控制器當中很基礎的一環,不論是否為STM32的晶片,其PIN腳可以透過使用者定義為輸入(Input)或輸出(Output),同時也可以透過 GPIO 模擬出各樣的通訊協定,這部分就不細談了。微控制器可以透過改變0或1的輸出狀態達到周邊的電路控制,或讀取接腳的狀態,判斷電路的訊號做出對應的程式應對。

   最簡單的例子莫過於使用按鍵控制LED的亮暗了,因此今天透過這個實例來學習如何從STM32 CubeMX來學習GPIO的基礎控制。這篇的篇幅較長,前面主要介紹軟體的操作與使用,若已經會的朋友們可以直接往後拉到GPIO的教學哦!

  下文將以STM32 L053R8Tx為示範,並使用意法半導體官方所提供的Nucleo開發板完成該實驗,使用的IDE為Keil uVision5(MDK-ARM),而STM32 CubeMX的版本為6.2.1。

STM32CubeMX

首先開啟STM32CubeMX,可以在中間處找到New Project。

如果是使用自己的開發板,則是選擇第一個選項(ACCESS TO MCU SELECTOR)選擇所使用的MCU型號;若跟我一樣使用官方的開發板,則選擇第二個選項(ACCESS TO BOARD SELECTOR)選擇開發板。

接著可以透過左上角的欄位選擇或搜尋自己所使用的開發板,以NUCLEO-L053R8為例,搜尋後右側則會出現對應的開發板,也可以點擊星星加入我的最愛,下一次就可以直接從左上角星星中選取此開發板進行開發。

點選所選用的開發板兩下,會出現一個提示視窗,詢問是否要將Pin腳的設置都用開發板預設的設定,這邊點選Yes,可以省去不少的設定,以符合該開發板使用。若是使用自己的開發板選擇MCU,則不會跳出該選項哦!

點選Yes之後則會看到所使用的IC圖樣,可以直接點選PIN腳指定功能至該腳,同時也簡單透過下圖介紹幾個區域的功能。

該開發板已經預設好PC13為藍色的Push Button、PA5為綠色LED燈,因此可以直接以此作為使用,倘若有更動開發板背後的電阻或開斷路設定調整到PA5、PC13,則可將此兩隻腳拉出以麵包板或洞洞板進行實驗,此電路板的相關設定可以參考官方文件UM1724

PC13的藍色Push Button
PA5的綠色LED

因此在System Core中的 GPIO 設定,可以看到已經先預設好PA5是輸出、PC13是輸入,並可以查看PA5與PC13的細項設定。

PA5設定中第一項 GPIO Output Level指的是Reset後的預設輸出準位,第二項 GPIO Mode的部分有推挽式輸出PP、開汲極輸出OD,第三項則是提升電阻或拉地電阻的選擇,因為PC13依照電路圖已有接4.7k的提升電阻,因此設定部分沒有選擇Pull-up,而PA5的第四項設定中輸出速度是較多人好奇的部分,因此針對這部分ST的官方文件也有說明,可以參考下圖。

而PC13的設定原廠預設為 GPIO_EXTI,是輸入中斷的模式,雖然今天的內容不會提及中斷,但這樣的設定仍可以視為一般的Input Mode作為使用哦!

接著來到Clock Configuration的設定,可以看到時脈的相關設定與走向,此部分因開發板的X3石英震盪器預設是空接的,因此選用內部的HIS 16MHz RC震盪器,經過PLL倍頻至最高時脈32MHz運行。

完成了接腳與時脈的設定,接著來到專案的設定,輸入自己的Project Name及選定專案的位置後,最重要的是Toolchain / IDE需選擇自己所使用的IDE,此處使用Keil進行開發,因此選擇MDK-ARM。

都完成以上的設定後,即可按下視窗右上角的GENERATE CODE,將程式碼輸出並交由Keil進行編輯囉!

完成輸出後會詢問是否要開啟資料夾或是直接開啟專案,這邊我們選擇Open Project直接開啟專案,轉到Keil編輯。

Keil uVision5

打開Keil之後,第一排有個「d」的望眼鏡icon,可以用來線上Debug,第二排左側兩個icon分別是build、Rebuild,分別是編譯新增的內容、整個專案全部重新編譯,通常打開Keil後我會先按Rebuild,確認所產生的Code沒有Error。

接著有LOAD字樣的icon則是燒錄,編譯完成後透過此按鈕可以完成燒錄。

可以先按下Rebuild確認無誤後,按下Download燒錄,視窗左下角則會出現Memory的燒錄進度條,此版本的燒錄會遇到一個錯誤問題,如下圖出現Invalid ROM Table。

點選工具列的Options for Target,選取Debug,點選Setting,此處的Pack下方有個Enable,將他取消掉即可解決該問題。

解決後測試再次燒錄,應如下圖出現Flash Load finished。

接著可以打開main.c來到main function,發現有許多的綠色註解文字及部分的程式碼,是由STM32CubeMX所產生的。建議不要刪除這些註解,特別是可以將程式碼及相關引用輸入在USER CODE BEGIN及USER CODE END之間,若回到STM32CubeMX調整設定重新產生程式碼,在這之間所輸入的一切都會保留,在這之外的都會被覆蓋及刪除哦!

GPIO ─ Output

接著就是這篇文章的重頭戲,GPIO中的OUTPUT了!回到PA5的預設LED可以發現是HIGH動作,當GPIO輸出HIGH的時候,開發板上的LD2會亮起,反之會熄滅。

因此先了解一下輸出的方式大致可以分為兩種:

  • 透過HAL的函示庫進行控制,需依照HAL的函數定義及變數名稱輸入
  • 透過底層的GPIO暫存器直接進行存取,透過Bit Mask完成Bitwise操作


首先第一種方式所使用的是HAL函式庫,可以從stm32l0xx_hal_gpio.c中找到該函式的使用方式及變數描述。

三個傳入的參數,分別是GPIO的PORT、GPIO的PIN、要寫入的狀態(SET、RESET),因此要輸出HIGH則可以如下

HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,SET);

反之若需要輸出LOW,則將SET改為RESET即可。

HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,RESET);

若使用第二種方法透過暫存器的存取,有設置HIGH的暫存器、設置LOW的暫存器也有輸出狀態的暫存器,我習慣直接控制輸出暫存器,因此可以透過一些位元遮罩完成指定的位元運算。

輸出的暫存器可以從官方提供的Reference Manual DM00095744 找到。

因此要針對PA5輸出HIGH,保留原有的其他PA0~PA4、PA6~PA15,可以利用or運算完成。與”1”做or則可以使該位元為1,其餘的保留位元與”0”做or則可以不影響原本的狀態。因此可以如下兩種做法:

GPIOA->ODR=GPIOA->ODR | (1<<5);

基於C語言的運算語法可以簡化成:

GPIOA->ODR|=1<<5;

反之若需要輸出LOW,就使用AND運算,指定輸出為LOW的位元與”0”做AND運算,其餘的保留位元與”1”做AND運算,則可以達到指定位元輸出LOW的效果。

GPIOA->ODR&=~(1<<5);

因此由上述可以學習到如何控制PIN腳輸出HIGH或LOW,則可以達到閃爍燈的效果,不論使用暫存器的存取或是使用HAL的GPIO函式,皆可以配合HAL函式所提供的HAL_Delay(ms)函式,即可完成。

GPIOA->ODR|=1<<5;
HAL_Delay(100);
GPIOA->ODR&=~(1<<5);
HAL_Delay(100);

在HAL函式庫中也有提供GPIO反轉的函式,而使用XOR運算也可以在ODR暫存器中做到反轉的效果。

HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_5);
HAL_Delay(100);
GPIOA->ODR^=1<<5;
HAL_Delay(100);

學會了如何透過暫存器或HAL函式控制LED,立刻就在Keil上編譯與燒錄看看吧!LD2會不斷閃爍,並依照100ms的頻率閃爍哦。
相關的位元運算若有不清楚或是想更仔細的了解,可以參考Lesson 1.1

GPIO ─ Input

前面介紹了如何輸出HIGH與LOW的訊號,接著要學習輸入狀態的判讀,通常會用來判斷按鈕的狀態或是數位電路的訊號,在NUCLEO L053R8的開發板中也提供了藍色的Push Button在PC13,因此可以直接利用此按鈕來進行該實驗。

輸入的判讀仍可以透過HAL的函式及暫存器的存取,因此在stm32l0xx_hal_gpio.c中也可以找到HAL_GPIO_ReadPin的相關描述,在此就不附上該函式的原始程式碼,直接進行函式的使用。如上電路圖所示,按下為LOW,因此需存取狀態的變數為HIGH。

所要傳入的參數僅有兩個,GPIO的Port與Pin,因此使用方式如下:

int pin_status=1;
pin_status = HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13);

若要使用暫存器則需透過兩次的位移得到正確的位元狀態,第一次的左移是為了與第13個bit的資料AND,取出PC13的當前狀態,但第0~12個bit則會有許多的0,因此需將多餘的0移除,所以進行右移13個位元,使得原本的PC13在第一個位元:

int pin_status=1;
pin_status = (GPIOC->IDR&(1<<13))>>13;

透過上述兩種方法可以取得按鈕或開關的當前狀態,那就可以和LED進行結合,當按下時亮起,放開則熄滅。

int pin_status=1;
pin_status = (GPIOC->IDR&(1<<13)>>13;
if(pin_status ==0)GPIOA->ODR|=(1<<5);
else GPIOA->ODR&=~(1<<5);

完成之後可以挑戰看看更困難的,按一下亮燈,按一下關燈,這部分會遇到按鈕或開關機械彈跳(Bouncing)的問題,可以透過迴圈、延遲、變數解決,如果是一般的輪詢式(Polling)程式,或是很有順序性的程式,則可以使用迴圈解決,若沒有類似七段顯示器等掃描或類似電路需在main當中執行也可以考慮使用延遲20ms解決,或是設置一個counter作用的變數來避開彈跳現象時讀取到接腳的錯誤狀態。

取自Stack Overflow

在目前的程式當中可以使用迴圈來解決是最簡單的方式,如下

if((GPIOC->IDR&(1<<13))>>13 ==0){
	while((GPIOC->IDR&(1<<13))>>13 ==0);	 //按著時進入無窮迴圈
	GPIOA->ODR^=(1<<5); 				 //放開則反轉訊號
}

若透過延遲解決則如下

if((GPIOC->IDR&(1<<13))>>13 ==0){
	if((GPIOC->IDR&(1<<13))>>13 ==0)HAL_Delay(20);
	if((GPIOC->IDR&(1<<13))>>13 ==1)GPIOA->ODR^=(1<<5);
}

若是變數計數的方式如下

int count=20,flag=0;
  while (1)
  {
		int status=(GPIOC->IDR&(1<<13))>>13;
		if(status ==0)flag=1;
		
		if(flag==1&&status==1)count++;
		if(count>=20){
			GPIOA->ODR^=1<<5;
			count=0;
			flag=0;
		}
		HAL_Delay(1);
}

同時也提供另一個方法,讓大家可以自己思考看看為什麼這樣會可以讀取到正確的按鈕訊號,並完成除彈跳。變數push是用在while迴圈讀取該副程式的回傳值,若為”1”則代表傳入的GPIO_Port中的GPIO_Pin有被按下,反之沒有按下則回傳”0”,該副程式以Low Active去做撰寫。

以上就是這次的教學,也可以多嘗試看看更多GPIO的變化,例如按幾下有不同的變化,或是長按短按的判斷,都是很好的練習題目,熟悉位元運算及時序變化後,在後續的學習是很重要的基礎之一!

在Lesson 1.1有更詳細的介紹位元的運算及基本的GPIO操作:https://www.shimine.com/stm32cubemx-lesson1-1/

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *