在第一講中曾經提到,GPIO有輸入輸出兩種模式。在點亮LED時,我們已經使用了GPIO輸出模式,在按鍵識別中,我們將要使用GPIO輸入模式。首先來看看按鍵的電路原理圖(下圖在選手資源數據包——CT117E-M4產品手冊中):
其中,B1~B4為4個不同的按鍵,它們通過PB0、PB1、PB2、PA0四個端口以上拉電阻的方式連接到單片機中。當按鍵松開時,PB0等端口處于高電平狀態;當按鍵按下后,端口處于低電平狀態。因此,我們可以把這些端口設置為GPIO輸入+上拉電阻(pull-up)模式,通過讀取其電平的高低狀態來判斷按鍵是否被按下。(所謂上下拉電阻,其實決定的就是GPIO輸入端口斷路時的初始電平狀態,有關介紹可以自行搜索)
例如,需要判斷B1是否被按下時,我們只需要判斷PB0的電平狀態:
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == GPIO_PIN_RESET)//讀取PB0的電平狀態,判斷是否為低電平
{/* 執行任務 */
}
在主循環中,利用按鍵掃描,我們就可以通過不同的按鍵操作來執行不一樣的任務,例如:
while (1)
{//B1按下,點亮LD1和LD2if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET){LED_On(LD1|LD2);}//B2按下,點亮LD3和LD4if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET){LED_On(LD3|LD4);}//B3按下,點亮LD5和LD6if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET){LED_On(LD5|LD6);}//B4按下,點亮LD7和LD8if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET){LED_On(LD7|LD8);}
}
不僅是按鍵,GPIO輸入模式與輸出模式一樣,均廣泛應用于各種需要讀取外部電路電平的場景。有關GPIO輸入的函數如下:
/*** @brief Read the specified input port pin.* @param GPIOx where x can be (A..G) to select the GPIO peripheral for STM32G4xx family* @param GPIO_Pin specifies the port bit to read.* This parameter can be any combination of GPIO_PIN_x where x can be (0..15).* @retval The input port pin value.*/
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
然而,這種方法在實際中并不常用。可以想象,除了判斷按鍵是否按下以外,一個嵌入式系統一定還有其他的許多任務需要執行。倘若在主循環中一直判斷按鍵是否被按下,則會占用CPU,效率低下,同時也可能因為在執行其他任務,響應不及時。因此,我們更常用的是采用中斷的方式來判斷按鍵是否被按下。
先來普及一下什么是中斷。我們知道,CPU是按照順序依次執行主函數中的指令的。中斷是指打斷CPU當前正在執行的指令,跳到中斷程序中執行,隨后跳會主函數原來的位置繼續執行原指令。例如在生活中,我們的主程序是寫代碼,這時電話鈴響,我們不得不停下手中的工作,進入中斷——接電話,接完電話后又繼續回到寫代碼的工作中。正如打斷我們的有可能是電話鈴聲,有可能是門鈴聲,也有可能是短信鈴聲等等,打斷CPU的中斷方式也是多種多樣的,如GPIO外部中斷、定時器中斷、定時器捕獲中斷等等……
考慮到比賽中對按鍵的判斷涉及到長短按,我們在考慮程序執行效率而采用中斷的同時,要考慮判斷長短按的方法,這就涉及到按鍵按下的時間問題。在單片機中,與時間有關的問題,都是通過定時器來實現的。因此,下面我們來介紹定時器中斷。
先來介紹一下與定時器有關的概念。在單片機中,有一個晶振(石英晶體振蕩器),它通常決定了單片機的時鐘頻率。通過對時鐘的分頻,可以得到許許多多的時鐘源。不同的硬件通過采用不同的時鐘源,再對其進行分頻,就得到了獨屬于這個硬件自己的時鐘頻率,定時器亦是如此。
參照官方例程(LCD的例程),我們按如下步驟配置時鐘樹:
(1)開啟外部高速時鐘
(2)勾選HSE,將時鐘頻率設置為80MHz后按回車
(3)所得到的定時器頻率即可以在上圖右側圓圈處查看
這樣我們就得到了時鐘頻率為80MHz的定時器。
下面我們來開啟定時器中斷。我們設置TIM4如下:
其中,定時器頻率按照如下公式計算:(具體原理請自行搜索)
f0為時鐘頻率80MHz,Prescaler為預分頻系數,Counter Period為計數周期。這樣我們就把TIM4定時器的頻率設置為了100Hz,即周期為0.01s。最后,只需要打開中斷開關,就完成了定時器中斷的配置。
在Cube中設置好后,想要使用定時器中斷,還要在主函數初始化時開啟定時器中斷(在此處是開啟TIM4的定時器中斷)
HAL_TIM_Base_Start_IT(&htim4); //開啟TIM4的基本(Base)功能(定時)中斷(IT(InTerrupt))
然后編寫定時器中斷函數(注意:函數名和形參均是固定的,不能修改!!!可參照下圖尋找):
?
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //定時(Period)中斷(Elapsed)回調(Callback)函數,回調即從主程序中調到中斷程序中
{if (htim->Instance == TIM4) //如果是TIM4定時器觸發的中斷{//B1按下,點亮LD1和LD2if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET){LED_On(LD1|LD2);}//B2按下,點亮LD3和LD4if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET){LED_On(LD3|LD4);}//B3按下,點亮LD5和LD6if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2) == GPIO_PIN_RESET){LED_On(LD5|LD6);}//B4按下,點亮LD7和LD8if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET){LED_On(LD7|LD8);}}
}
這樣,每當時間過去了0.01s,CPU就會進入定時中斷回調函數中,運行我們預先寫好的中斷程序(在此處是讀取按鍵端口的電平,隨后執行相應任務),即定時按鍵掃描,而不是一直循環掃描按鍵是否按下,這樣就為CPU節省下了大量的時間,大大提高了程序的運行效率。
?