STM32筆記(18):按鍵控制-使用外部中斷(下)


[撰寫外部中斷步驟]

瞭解設定優先順序 NVIC 及外部中斷 EXTI 兩個重要的功能後,接下來以一個範例來說明,如何撰寫外部中斷程式,有以下幾個步驟:

(1) 啟用 AFIO 複用時鐘功能
使用 RCC_APB2PeriphClockCmd() 函式,將 RCC_APB2Periph_AFIO 啟用。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

(2) 設定觸發中斷的 GPIO Port
要將 GPIO Port 作為觸發中斷的來源,需將 GPIO 設定為輸入模式,有以下 4 種:
  • GPIO_Mode_AIN 模擬輸入(ADC模擬輸入,或者低功耗下省電)
  • GPIO_Mode_IN_FLOATING 浮空輸入
  • GPIO_Mode_IPD 下拉輸入
  • GPIO_Mode_IPU 上拉輸入
以下是設定 GPIOA Pin2 的範例:
GPIO_InitTypeDef GPIO_InitStructure;			//定義結構體
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	//設定成上拉輸入
GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_14;		//設定 PB14 當作I/O 
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOE,&GPIO_InitStructure);		//使用結構體進行初始化
(3) 將 GPIO Port 與中斷來源連接起來
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
(4) 定義初始化中斷設定,並建立 EXTI_InitStructure
EXTI_InitTypeDef EXTI_InitStructure;		//定義初始化結構體

EXTI_InitStructure.EXTI_Line = EXTI_Line14;		//設定 Line14 為中斷線的標號
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;	 //中斷模式:中斷 EXTI_Mode_Interrupt 或 事件 EXTI_Mode_Event。
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		//觸發方式:上升/下降或上下沿觸發
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);		//根據結構體進行初始化
(5) 設定中斷優先順序分組
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
(6) 中斷優先順序配置:
NVIC_InitTypeDef NVIC_InitStructure;		//定義結構體

NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;	//Enable 外部中斷的通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//設定主要優先順序
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//設定次要優先順序
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;		//Enable外部中斷通道 
NVIC_Init(&NVIC_InitStructure);		//根據結構體進行初始化
(7) 編寫外部中斷服務函式的
外部中斷函式分別為以下七種之一,中斷線 0-4 每個中斷線對應一箇中斷函式,中斷線 5-9 共用中斷函式 EXTI9_5_IRQHandler,中斷線 10-15 共用中斷函式 EXTI15_10_IRQHandler。
  • EXPORT EXTI0_IRQHandler
  • EXPORT EXTI1_IRQHandler
  • EXPORT EXTI2_IRQHandler
  • EXPORT EXTI3_IRQHandler
  • EXPORT EXTI4_IRQHandler
  • EXPORT EXTI9_5_IRQHandler
  • EXPORT EXTI15_10_IRQHandler
處理中斷服務的範例程式如下:
void EXTI15_10_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line14) == SET)	//判斷 Line14 的中斷是否發生 
	{
		// 中斷執行的程式...
		Button_Count ++;
		delay_ms(300);
		EXTI_ClearITPendingBit(EXTI_Line14);	//清除 LINE 上的中斷標誌位
	}
}

以下就用一個按鍵的範例來說明如何觸發外部中斷,然後將計數器加 1 ,顯示在 OLED 上。

[材料]

  • STM32F103C8T6 主板 x 1
  • OLED SSD1306 顯示器 x 1
  • 麵包板 x 1
  • 按鍵 x 1 個
  • STLINK V2 模擬下載器 x 1
  • 連接線 x N 條

[接線與電路圖]

按鍵的一個接腳接在 GND,另一腳接在 STM32F103x PB14,與 SSD1306 連接的方式如下:
STM32F103xSSD1306 OLED按鍵
3.3vVDD-
GNDGND其中一個 Pin
B8SCK/SCL-
B9SDA-
B14-另一個 Pin


[程式]

主程式一開始先對 OLED 進行初始化,再設定 PB14 為按鍵的其中一個引腳,進入迴圈循環後,就等中斷產生,主程式 main.c 如下:
/**********************************************************
 * @file main.c
 * @date 2022/3/27  
 * @brief traffic light with OLED
 *        	VDD 	- 3.3V
 *       	GND 	- GND
 * 	  	SCL/SCK - PB8    // IIC時鐘信號
 *	  	SDA 	- PB9    // IIC資料信號
 **********************************************************/

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "bmp.h"
#include "button.h"

// GPIO_InitTypeDef GPIO_InitStruct;    // 定義一個GPIO_InitTypeDef類型的結構體

int main (void)
{
	delay_init();	    	 //延時函數初始化	  
	NVIC_Configuration(); 	 //設置 NVIC 中斷分組2:2位搶佔優先順序,2位回應優先順序 	
	OLED_Init();		 //初始化OLED  
	Button_Init();
	OLED_Clear(0);           //清除螢幕
	OLED_ShowString(2, 2, "Count:",16);	
	while (1)
	{
		OLED_ShowNum(60, 2, Button_Get(),3, 16); 
	}
}


以下 Button.c 內有一段產生中斷服務程式:EXTI15_10_IRQHandler(void) ,這段程式使用 delay_ms(300); 原本沒有加上這一行時,按一下按鍵會隨機跳好幾個數字,原因是按下按鍵的瞬間產生好幾個中斷,讓數字連續跳好幾個號碼。加上 delay 後,這個問題就解決了。
#include "stm32f10x.h"  
#include "delay.h"

uint16_t Button_Count;

void Button_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);	//設定 RCC 時鐘
	
	GPIO_InitTypeDef GPIO_InitStructure;		//設定 GPIOB Pin14
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);	//設定外部中斷 EXTI_Line14
	
	EXTI_InitTypeDef EXTI_InitStructure;	
	
	EXTI_InitStructure.EXTI_Line = EXTI_Line14;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);		//設定 NVIC 優先順序
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;	//使用 EXTI15_10_IRQn 通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	
	NVIC_Init(&NVIC_InitStructure);
}

uint16_t Button_Get(void)
{
	return Button_Count;	// 傳回累計按鍵數
}

void EXTI15_10_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line14) == SET)
	{
		Button_Count ++;	// 將計數 +1
		delay_ms(300);		// 暫停 300ms
		EXTI_ClearITPendingBit(EXTI_Line14);
	}
}


Button.h
#ifndef __BUTTON_H
#define __BUTTON_H

void Button_Init(void);
uint16_t Button_Get(void);

#endif

完整的程式請參考 Github:4. Push button control led using Interrupt

[結果]


[參考資料]



Post a Comment

較新的 較舊