//======================================================//
**基于STM32的按鍵控制與外部中斷實例詳解
**為了減少重復的內容,所以將外部中斷的例程與按鍵控制實例
放在一起學習。
//======================================================//
/********************************************************/
因為涉及的內容較多,這里大體說一下整片文章的內容分布:
1)、按鍵控制實例
1、按鍵的相關信息描述
2、按鍵的初始化代碼實現
3、常用按鍵檢測及控制代碼(主要學習其實現思路)
2)、外部中斷實例
1、外部中斷的相關信息描述
2、外部中斷的初始化代碼實現
/********************************************************/
==================================華麗的分界線=================================
//=========================按鍵控制實例=============================//
==================================華麗的分界線=================================
首先,先來講述簡單的按鍵控制,下面來看看按鍵是什么東西,開發的時候按鍵又有哪些要注意的事情.
釋義:
按鍵開關是一種電子開關,屬于電子元器件類,使用時以滿足操作力的條件向開關操作方向施壓開關功能閉合接通,當撤銷壓力時開關即斷開,其內部結構是靠金屬彈片受力變化來實現通斷的。
一般在開發中涉及按鍵的一般是按鍵消抖。按鍵消抖通常的按鍵所用開關為機械彈性開關,當機械觸點斷開、閉合時,由于機械觸點的彈性作用,一個按鍵開關在閉合時不會馬上穩定地接通,在斷開時也不會一下子斷開。也就是在閉合及斷開的那一瞬間會有一小段的不穩定狀態,即伴隨著連續的抖動。按鍵在電路中一般也就0跟1兩中狀態,正常情況下,按鍵沒有按下的情況下,按鍵所連的引腳狀態一般都是為1。原因是一般會在引腳上加上一個上拉,然后再與按鍵相連,按鍵的另一端接地,所以在按鍵沒有按下的情況下,引腳返回的一般都是高電平,這是電路決定的。如下圖:
按鍵的抖動就是介于1跟0的不定狀態。所以為了取得穩定狀態,需要在延遲取狀態值,也就是既然知道有抖動存在了,那么就把取值的時間延遲一下。還有一個東西跟抖動有關,那就是觸發沿的設置,比如上升沿和下降沿的觸發。上升沿是按鍵松開后那一瞬間,電平從0到1的過程,下降沿就與之相反,也就是按鍵按下的一瞬間,電平從1降為0的過程。邊沿觸發一般會用在外部中斷的過程中。下面的內容也會講到。
按鍵相對來說比較簡單,按鍵的初始化跟LED和BEEP蜂鳴器幾乎一致,不同之處也就在于按鍵是輸入設備,所以GPIO的模式上一般選為輸入模式,緊接著就要設置上拉/下拉輸入模式,最后設置初始狀態,這個要根據電路來間接。
//=======================key.c===============================//
/************************************************
接口:
key0引腳接在 PE4 低電平有效
key1引腳接在 PE3 低電平有效
key_up引腳接在 PA0 高電平有效
************************************************/
#include "stm32f10x.h"
#include "key.h"
#include "delay.h"
void key_Init(void)
{
RCC ->APB2ENR |= 1 << 2;//使能 PORTA 時鐘
RCC ->APB2ENR |= 1 << 6;//使能 PORTE 時鐘
//key_up配置
GPIOA ->CRL &= ~(15 << 0);//先設置輸入模式
GPIOA ->CRL |= 1 << 3; //PA.0 下拉輸入模式
GPIOA ->ODR &= ~(1 << 0); //設置下拉,即設置初始狀態為低電平
//key1配置
GPIOE ->CRL &= ~(3 << 13);//先設置輸入模式
GPIOE ->CRL |= 1 << 15; //PE.3 上拉輸入模式
GPIOE ->ODR |= 1 << 3; //設置上拉,即設置初始狀態為高電平
//key0配置
GPIOE ->CRL &= ~(3 << 17);//先設置輸入模式
GPIOE ->CRL |= 1 << 19; //PE.4 上拉輸入模式
GPIOE ->ODR |= 1 << 4; //設置上拉,即設置初始狀態為高電平
}
/*************************************************************
**功 能:按鍵處理函數
**輸入參數:無
**輸出參數:
//mode:0,不支持連續按;1,支持連續按;
//0,沒有任何按鍵按下
//1,KEY0 按下
//2,KEY1 按下
//3,KEY_UP 按下 即 WK_UP
**注 釋:注意此函數有響應優先級,KEY0>KEY1>KEY_UP!!
***********************************************************/
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1; //按鍵按松開標志
if(mode)key_up=1; //支持連按
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
delay_ms(10); //去抖動
key_up=0;
if(KEY0==0)return 1;
else if(KEY1==0)return 2;
else if(WK_UP==1)return 3;
}
else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
return 0;// 無按鍵按下
}
//======================================================//
注釋:按鍵檢測函數,原理是檢測按鍵的電平,而mode的作用就是一直把key_up置1,那么就會一直進入按鍵按下的檢測,而不會進入按鍵松開的檢測。
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))這句是進行按鍵按下的檢測,
else if(KEY0==1&&KEY1==1&&WK_UP==0)這句是進行按鍵松開的檢測。
如果沒有按鍵按下的話,雖然key_up為1,但是沒有按鍵,那么就只會返回0。
==================================華麗的分界線=================================
//===========================key.h===========================//
#ifndef __KEY_H_
#define __KEY_H_
#include "stm32f10x.h"
#include "sys.h"
#define KEY0 PEin(4) //PE4
#define KEY1 PEin(3) //PE3
#define WK_UP PAin(0) //PA0 WK_UP 即 KEY_UP
#define KEY0_PRES 1 //KEY0 按下
#define KEY1_PRES 2 //KEY1 按下
#define WKUP_PRES 3 //KEY_UP 按下(即 WK_UP/KEY_UP)
extern void key_Init(void);
extern u8 KEY_Scan(u8 mode);
#endif
//======================================================//
==================================華麗的分界線=================================
//==========================main.c============================//
/*************************************************************
**功 能:按鍵處理函數
//1,KEY0 按下 控制LED0
//2,KEY1 按下 控制LED1
//3,KEY_UP 按下 即 WK_UP 控制蜂鳴器
***********************************************************/
#include "stm32f10x.h"
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"
int main(void)
{
u8 key=0;
Stm32_Clock_Init(9); //系統時鐘設置
delay_init(72); //延時初始化
led_Init();//LED初始化
beep_Init(); //beep初始化
key_Init(); //key初始化
while(1)
{
key=KEY_Scan(0); //得到鍵值
if(key)
{
switch(key)
{
case WKUP_PRES: //控制蜂鳴器
GPIOB ->ODR |= (1 << 8);//PB.8 輸出高,即蜂鳴器開
delay_ms(10000);//延時
GPIOB ->ODR &= ~(1 << 8);//PB.8 輸出低,即蜂鳴器關
break;
case KEY1_PRES: //控制 LED1
GPIOB ->ODR &= ~ (1 << 5);//PB.5輸出低電平,即點亮LED
delay_ms(10000); //延時
GPIOB ->ODR |= 1 << 5;//PB.5輸出高電平,即滅掉LED
break;
case KEY0_PRES: //控制 LED0
GPIOE ->ODR &= ~(1 << 5); //PB.5輸出低電平,即點亮LED
delay_ms(10000); //延時
GPIOE ->ODR |= 1 << 5;//PB.5輸出高電平,即滅掉LED
break;
}
}
else delay_ms(10);
}
}
//======================================================//
==================================華麗的分界線=================================
//=========================外部中斷實例=============================//
**注釋:特別鳴謝CSDN博客的博主->“zzwdkxx”的博文,在外部中斷的學習中直接的幫助!
博文地址:http://blog.csdn.net/zzwdkxx/article/details/9036679
==================================華麗的分界線=================================
接著下面將進行外部和中斷的講解,講之前先對中斷先做一定了解。
//============================百度搜索==========================//
簡介:
中斷是處理器處理外部突發事件的一個重要技術。它能使處理器在運行過程中對外部事件發出的中斷請求及時地進行處理,處理完成后又立即返回斷點,繼續進行處理器原來的工作。引起中斷的原因或者說發出中斷請求的來源叫做中斷源。根據中斷源的不同,可以把中斷分為硬件中斷和軟件中斷兩大類,而硬件中斷又可以分為外部中斷和內部中斷 兩類。
外部中斷一般是由計算機外設發出的中斷請指求,如:鍵盤中斷、打印機中斷、定時器中斷等。外部中斷是可以屏蔽的中斷,也就是說,利用中斷控制器可以屏蔽這些外部設備 的中斷請求。
內部中斷是指因硬件出錯(如突然掉電、奇偶校驗錯等)或運算出錯(除數為零、運算溢出、單步中斷等)所引起的中斷。內部中斷是不可屏蔽的中斷。
軟件中斷其實并不是真正的中斷,它們只是可被調用執行的一般程序。
優先級:
CPU為了處理并發的中斷請求,規定了中斷的優先權,中斷優先權由高到低的順序是: (1)除法錯、溢出中斷、軟件中斷 (2)不可屏蔽中斷 (3)可屏蔽中斷 (4)單步中斷。
//======================================================//
我們要明確一點,就是所有的端口都有外部中斷的能力,但是為了使用外部中斷線,端口必須配置為輸入模式。
外部中斷的配置過程步驟如下:
1)、設置GPIO口為輸入
2)、開啟GPIO口復用時鐘,設置GPIO口與中斷線的映射關系。
3)、開啟與該GPIO口相對的線上中斷/事件,設置觸發條件。
4)、配置中斷分組(NVIC),并使能中斷。
5)、編寫中斷服務函數。
其中我們的外部中斷初始化函數就是需要做前面的4步,然后再編寫中斷服務函數。
說到底其實一個中斷要做的事有:使能中斷、中斷分組、屏蔽、IO口映射。完成以上工作就是一個完整的中斷設置。
具體外部中斷的配置如下:
==================================華麗的分界線=================================
//下面的設置是以人體紅外熱釋電模塊為例的一個外部中斷,當然也可以選用按鍵的電平的邊沿觸發的來寫一個外部中斷。只要會用外部中斷的初始化設置 ,那么用其他的模塊來設置外部中斷都是沒問題的。
//==========================exti.c============================//
#include "EXTI.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "beep.h"
#define RENTIPAin(5)//PA5
u8 flag=0;
//PA5紅外人體感應的引腳
void EXTIX_Init(void)
{
//1)先設置IO為輸入模式
RCC ->APB2ENR |= 1 << 2;//使能 PORTA 時鐘
GPIOA->CRL&=0XFF0FFFFF; //PA5設置成輸入
GPIOA->ODR &=~(1<<5);//設置PA5下拉
//2)開啟IO口復用時鐘,設置IO口與中斷線的映射關系
RCC->APB2ENR|=0x01;//使能io復用時鐘
AFIO->EXTICR[1] &=0xff0f; //EXTI5映射到PA5
//3)開啟與該IO口相對的線上中斷/事件,設置觸發條件
EXTI->IMR|=1<<5;//開啟line BITx上的中斷(EXTI->IMR是中斷屏蔽寄存器)
// EXTI->IMR&=~(1<<5); //屏蔽exti5線上的中斷
//EXTI->EMR是事件屏蔽寄存器
//EXTI->EMR|=1<<BITx;//不屏蔽line BITx上的事件 (如果不屏蔽這句,在硬件上是可以的,但是在軟件仿真的時候無法進入中斷!)
EXTI->RTSR|=1<<5;//Exti5上事件上降沿觸發
//EXTI->FTSR|=1<<5;//Exti5上事件下降沿觸發
//4)配置中斷分組(NVIC),并使能中斷
MY_NVIC_Init(2,3,EXTI9_5_IRQn,2);//搶占2,子優先級3,組2
//5)編寫中斷服務函數
}
//中斷服務函數
void EXTI9_5_IRQHandler(void)
{
if(RENTI==1) //有人
{
flag=1;//設置標志位為1,在main函數里標志位被清零
}
EXTI->PR=1<<5; //清除 LINE4 上的中斷標志位
}
===================================================================
在寫外部中斷的時候有幾個問題困惑我的有:
1、 中斷線的映射問題,就是怎么把PA5映射收到EXTI5?
2、中斷服務函數的書寫,一開始還以為EXTI1的中斷服務函數是EXTI1_IRQHandler();那么EXRTI5肯定是EXTI5_IRQHandler();了可是這是錯的。
解答區:
1、中斷線的映射問題。最主要的語句是
AFIO->EXTICR[1] &=0xff0f; //EXTI5映射到PA5
這句AFIO->EXTICR[1] 是什么意思呢?右鍵調進去查看以后是
0.png (24.67 KB, 下載次數: 41)
下載附件
2017-3-21 01:21 上傳
它相對應的是4個外部中斷配置寄存器,每個寄存器有32個位,但是高16位是保留的,有效的就只有低16位,即15~0位。
且看外部中斷的配置寄存器,外部中斷的寄存器有4個,它分為AFIO_EXTICR1、AFIO_EXTICR2、AFIO_EXTICR3、AFIO_EXTICR4,也就是EXTICR[0]~EXTICR[3],只是在寄存器里從0開始。如下圖所示:
(就AFIO->EXTICR[1] &=0xff0f; 而言,它的意思呢,就是設置PA5的中斷線為EXTI5,因為是PA端口,那么依寄存器的0000為PA端,那么PA5怎么表示呢,其實就從值的賦值上選定的,0xff0f 依次為7 6 5 4的順序,再如 AFIO->EXTICR[0]|=0X0020; 就是將EXTI1映射到PC1)
值得非常注意的是:通過AFIO_CRX配置GPIO線上的外部中斷/事件,必須先使能AFIO時鐘。切記!切記!這很重要!
1.png (25.5 KB, 下載次數: 45)
下載附件
2017-3-21 01:21 上傳

2.png (88.64 KB, 下載次數: 58)
下載附件
2017-3-21 01:21 上傳
3.png (43.24 KB, 下載次數: 42)
下載附件
2017-3-21 01:21 上傳
2、中斷處理函數的書寫問題。
psb.jpg (30.61 KB, 下載次數: 56)
下載附件
2017-3-21 01:01 上傳
如圖所示,所有中斷線的中斷服務函數依次為
void EXTI0_IRQHandler(void);
void EXTI1_IRQHandler(void);
void EXTI2_IRQHandler(void);
void EXTI3_IRQHandler(void);
void EXTI4_IRQHandler(void);
void EXTI9_5_IRQHandler(void);
void EXTI15_10_IRQHandler(void);
==================================華麗的分界線=================================
STM32的每個IO都可以作為外部中斷的中斷輸入口。STM32F103的中斷控制器支持19個外部中斷/事件請求。每個中斷設有狀態位,每個中斷/事件都有獨立的觸發和屏蔽設置。
STM32F103 的19 個外部中斷為:
線 0~15:對應外部 IO 口的輸入中斷。
線 16:連接到 PVD 輸出。
線 17:連接到 RTC 鬧鐘事件。
線 18:連接到 USB 喚醒事件。
線 19:連接到以太網喚醒時間。
EXIT.png (42.28 KB, 下載次數: 40)
下載附件
2017-3-21 01:06 上傳
注意:通過AFIO_CRX配置GPIO線上的外部中斷/事件,必須先使能AFIO時鐘。(其實在STM32很多開發中,時鐘就是生命。很多人就會問,為什么STM32的設計要那么麻煩呢,弄什么都要使能相關的時鐘。其實這個很簡單,很大一方面是為了處于功耗的考慮。如果沒有用到的相關引腳一直處于工作狀態,那么功耗這個就大了。)
==================================華麗的分界線=================================
================================== 截至 CSDN博客的博主->“zzwdkxx”的博文=================================
外部中斷函數不能進入的原因分析分析,可能為以下幾個方面: 1)GPIO或者AFIO的時鐘沒有開啟; 2)GPIO和配置的中斷線路不匹配; 3)中斷觸發方式和實際不相符合; 4)中斷處理函數用庫函數時,寫錯,經?赡艹霈F數字和字母之間沒有下劃線; 5)外部中斷是沿觸發,有可能檢測不到沿,比如中斷線是低電平(浮空輸入),觸發是下降沿觸發,可能會出現一直是低電平,高電平的時候是一樣的情況,電平持續 為高電平; 6)沒有用軟件中斷來觸發外部中斷,調用函數EXTI_GenerateSWInterrupt;,因為軟件中斷先于邊沿中斷處理。
/*************************************************************************************/ PS:如有錯誤,歡迎指錯!期待學習! /*************************************************************************************/
|