首先,作為剛入行不久的新人,我在單片機開發這塊并沒有太多的經驗,所以可能在寫一些相關的檔案的時候存在一些錯誤,希望大家多多包含!也希望各位不吝賜教,指點迷津!
好記性不如爛筆頭,之所以選擇開通博客是因為我想把自己在作業和學習程序中碰到的一些問題以及疑惑記錄下來,同時積極地定位問題的源頭以及尋求解決方案,或許在碰到相同的問題時就能很快地解決,同時在博客上也可以學習到很多工程師長期積累的經驗,分享自己的一些心得,這對個人的自我提升有很好的幫助!
好的,現在進入主題,剛開始接觸單片機的時候,看到別人的程式中會使用很多的宏定義以及結構體等等,當時總認為這樣的做法很累贅,顯得花里胡哨的,后來進入作業之后,看了一些量產產品的代碼框架,我才知道了當時自己有多無知(當然剛開始接觸的那些代碼作者水平也一般,雖然使用了很多宏定義和結構體,但是并不規范,不能做到觀其名知其義,反而令人看了眼花繚亂,不知所云),好的代碼框架更注重細節問題,比如命名上很規范,讓人一目了然,并且排版上也很用功夫,閱讀性自然很強,同時也增強了代碼的可移植性,也是從那個時候開始,我開始注重代碼的閱讀性以及一些方法的應用,雖然現在做不到很好,但是相比起剛開始的水平,還是有了一定的進步的,
最近的作業和學習中接觸STM32CubeMX的次數很多,這是STM32現在主推的一個工具,它可以為用戶生成初始化代碼,大大方便了用戶,提升了開發效率,生成的代碼使用的是HAL庫,實際上跟標準庫很像,但是現在標準庫已經不更新了,HAL庫必成主流,
從簡單的開始,今天就從點燈和按鍵開始(涉及到定時器、外部中斷),下面是CubeMX 的配置:
1、RCC配置,這里使用了外部高速時鐘(HSE):

2、定時器,這里隨便選擇了TIM3,也可以選擇其他的定時器,比如TIM6和TIM7這兩個基本定時器:

因為TIM3是掛載在APB1上的,我用的這款單片機APB1 Timer clock最大頻率是84(MHz),而我要做的是定時1ms進入一次中斷,所以其他配置如圖所示:
中斷記得配置為使能:

3、KEY的配置(KEY1:PA0, KEY2:PE2, KEY3:PE3, KEY4:PE4):全部配置為外部中斷



因為KEY1硬體設計的是按下后輸入高電平,KEY2~KEY4則是按下后輸入低電平,所以PA0配置為上升沿觸發,PE2~4配置為下降沿觸發:


中斷記得使能:

4、LED燈的配置跟KEY配置也差不多,只是模式配置為輸出,因為硬體設計上我的這幾個燈都是低電平點亮,所以初始化的時候我都配置為高電平,當需要其點亮的時候再配置為低電平:


5、時鐘樹配置:
這樣基本上就都配置完成了,點擊生成代碼,打開工程進行代碼添加,
1、首先為了方便對LED燈的控制,我做了幾個LED的宏定義:
#define led1 0x01
#define led2 0x02
#define led3 0x03
#define led4 0x04
#define LED_ON(LEDx) { \
if(LEDx == led1) HAL_GPIO_WritePin(GPIOF, LED1_Pin, GPIO_PIN_RESET); \
else if(LEDx == led2) HAL_GPIO_WritePin(GPIOF, LED2_Pin, GPIO_PIN_RESET); \
else if(LEDx == led3) HAL_GPIO_WritePin(GPIOF, LED3_Pin, GPIO_PIN_RESET); \
else if(LEDx == led4) HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_RESET); \
}
#define LED_OFF(LEDx) { \
if(LEDx == led1) HAL_GPIO_WritePin(GPIOF, LED1_Pin, GPIO_PIN_SET); \
else if(LEDx == led2) HAL_GPIO_WritePin(GPIOF, LED2_Pin, GPIO_PIN_SET); \
else if(LEDx == led3) HAL_GPIO_WritePin(GPIOF, LED3_Pin, GPIO_PIN_SET); \
else if(LEDx == led4) HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET); \
}
#define LED_Toggle(LEDx) { \
if(LEDx == led1) HAL_GPIO_TogglePin(GPIOF, LED1_Pin); \
else if(LEDx == led2) HAL_GPIO_TogglePin(GPIOF, LED2_Pin); \
else if(LEDx == led3) HAL_GPIO_TogglePin(GPIOF, LED3_Pin); \
else if(LEDx == led4) HAL_GPIO_TogglePin(LED4_GPIO_Port,LED4_Pin); \
}
2、接下來是按鍵檢測,關于按鍵KEY我也做了相關的宏定義和一個結構體:
typedef struct KEY
{
uint8_t KeyStatus; /* 當按鍵按下時引發第一次外部中斷,就會引起這個狀態位置1 */
uint32_t HoldTime; /* KeyStatus置1后開始計時,即按下按鍵到松開按鍵之間持續的時間 */
uint8_t IsPress; /* 按鍵是否按下,1按下,0沒按下 */
uint8_t PressMode; /* 按下的模式,0沒按下,1短按,2長按 */
uint32_t LoosenTime; /* 松開按鍵后計時10ms才可以進行下一次判斷,避免松開按鍵后抖動引起誤判 */
uint8_t IsLoosen; /* 按下按鍵后是否已經松開按鍵,0是沒松開,1是松開 */
}Key;
extern Key key[5];
#define key1 0x01
#define key2 0x02
#define key3 0x03
#define key4 0x04
另外還寫了一個檢測按鍵引腳電平的函式:
/*********************************************
*函式功能:回傳按鍵引腳電平的狀態值
*函式形參:uint8_t KEYx----按鍵
*函式回傳值:1----按下,0----松開
*備注:無
**********************************************/
uint8_t Key_Status(uint8_t KEYx)
{
uint8_t ret;
if(KEYx == key1)
{
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_SET)
{
ret = 1;
}
else
{
ret = 0;
}
}
else if(KEYx == key2)
{
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{
ret = 1;
}
else
{
ret = 0;
}
}
else if(KEYx == key3)
{
if(HAL_GPIO_ReadPin(KEY3_GPIO_Port,KEY3_Pin) == GPIO_PIN_RESET)
{
ret = 1;
}
else
{
ret = 0;
}
}
else if(KEYx == key4)
{
if(HAL_GPIO_ReadPin(KEY4_GPIO_Port,KEY4_Pin) == GPIO_PIN_RESET)
{
ret = 1;
}
else
{
ret = 0;
}
}
return ret;
}
使用外部中斷判斷是否有電平變化:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if((GPIO_Pin == KEY1_Pin) && (!key[1].LoosenTime) && (!key[1].HoldTime))
{
key[1].KeyStatus = 1;
}
else if((GPIO_Pin == KEY2_Pin) && (!key[2].LoosenTime) && (!key[2].HoldTime))
{
key[2].KeyStatus = 1;
}
else if((GPIO_Pin == KEY3_Pin) && (!key[3].LoosenTime) && (!key[3].HoldTime))
{
key[3].KeyStatus = 1;
}
else if((GPIO_Pin == KEY4_Pin) && (!key[4].LoosenTime) && (!key[4].HoldTime))
{
key[4].KeyStatus = 1;
}
}
這里還加了key[x].LoosenTime和key[x].HoldTime這兩個變數作為判斷的條件,只有當這兩個變數都為0的時候才會對對應的key[x].KeyStatus 做判斷,因為按鍵按下時和松開時都會有大概10ms左右的抖動,如果沒有這兩個變數作為判斷條件,可能會重復判斷,但是經過測驗,其實這兩個變數加不加影響不大,這里只是為了使邏輯更加嚴謹一些,這個回呼函式會在按鍵按下時觸發,接下來就是定時器起作用了,
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
uint8_t index;
for(index = 1; index < 5; index++) //1ms進入一次中斷,對四個按鍵進行輪詢
{
if(key[index].KeyStatus == 1) //key[index].KeyStatus等于1,說明有按鍵引腳電平發生了突變
{
key[index].HoldTime++; //開始計時,key[index].HoldTime每1ms增加1
if(key[index].HoldTime >= 10) //如果key[index].HoldTime大于或等于10,這段時間是按鍵抖動的時間
{
if(Key_Status(index) == 1) //判斷按鍵引腳的電平是否處于按下狀態,如果是,key[index].IsPress 置1
{
key[index].IsPress = 1;
}
}
}
if(key[index].IsPress == 1) //key[index].IsPress為1說明可以確定按鍵是按下了,接下來就是判斷是長按還是短按了
{
if(Key_Status(index) == 0) //按鍵按下后又松開了,此時判斷從按下到松開的程序持續的時間多長,從而判斷是長按還是短按
{
if(key[index].HoldTime < 1500) //持續時間小于1500ms,短按,key[index].PressMode置為1,key[index].IsLoosen置為1,其余引數清零
{
key[index].PressMode = 1;
key[index].HoldTime = 0;
key[index].KeyStatus = 0;
key[index].IsPress = 0;
key[index].IsLoosen = 1;
key[index].LoosenTime = 0;
}
else
{
key[index].PressMode = 2; //持續時間大于或等于1500ms,長按,key[index].PressMode置為2,key[index].IsLoosen置為1,其余引數清零
key[index].HoldTime = 0;
key[index].KeyStatus = 0;
key[index].IsPress = 0;
key[index].IsLoosen = 1;
key[index].LoosenTime = 0;
}
}
}
if(key[index].IsLoosen == 1) //key[index].IsLoosen為1時,說明按鍵經歷了按下到松開的程序,松開后會有10ms的抖動,不檢測按鍵
{
key[index].LoosenTime++;
if(key[index].LoosenTime >= 10)
{
key[index].IsLoosen = 0;
key[index].LoosenTime = 0;
}
}
}
}
對了,定時器雖然在CubeMX配置的時候已經配置為中斷使能,但是還要再工程中加入一個陳述句才能開啟中斷:
HAL_TIM_Base_Start_IT(&htim3);
這也是我除錯代碼后發現的,原來我以為只要在CubeMX配置為中斷使能就可以開啟中斷了,
然后,我們只要去判斷key[x].PressMode的值,就可以知道某一時刻按鍵的狀態了,也就可以做出相應的LED控制(也可以是其他的操作,視需求而定),執行完操作之后記得把key[x].PressMode 清零,不然的話會一直判斷為上一次的按鍵狀態,
其實除了長按短按兩種模式之外,我還嘗試過增加雙擊的邏輯,測驗效果并不是特別好,容易判斷錯誤,可能是一些邏輯沒處理好,由于時間關系就沒有繼續研究下去,但是我認為單片機的按鍵實際上不需要做得很復雜,能實作長按短按一般都是能滿足大部分的需求了,太復雜反而顯得累贅,
可能這段代碼寫得也不好,但是我認為也有一些可取的地方,比如結構體陣列的應用,按鍵實作程序中會有一些屬性或者標志位,我們把它們寫到同一個結構體里就變得很清晰了,多個按鍵實際上它們都有相同的屬性,那么再把它們做成一個陣列,實作程序通過輪詢方式就可以大大的簡化代碼了
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/199.html
標籤:嵌入式
上一篇:NOR閃存開始汽車領域的發展
