中斷的概念: 中斷是當單片機的CPU在執行程式時,外部或內部發生了一個隨機事件,導致CPU暫時中斷正在執行的程式,轉去執行一段特殊的服務程式也就是中斷服務子程式或中斷處理程式,當處理完服務程式后,回傳到被中斷的程式繼續執行,這樣的一個程序就被稱為中斷,引發這個中斷的事件被稱為中斷源,中斷在stm32中還被分為相應的優先級,低優先級的中斷會被高優先級中斷所中斷,即為中斷的嵌套,在Crotex-M3內核中支持256個中斷,其中包含16個內核中斷以及240個外部中斷,STM32F10X芯片中只用了其中的84個中斷通道,包含16個內核中斷和68個可屏蔽中斷,
中斷的優先級: STM32的每個中斷通道都有一個中斷優先級控制位元組(8位二進制資料,可設定為0~255,數值越小,優先級越高,在STM32F103中只用其中高4位資料),其用于表達優先級的高4位又被分為搶占式優先級和回應式優先級,在搶占式優先級相同情況下,高回應優先級的中斷優先被回應,優先級相同時,按照中斷回應的順序執行服務程式,越靠前的先執行,中斷的相關功能需要通過NVIC(嵌套向量中斷器)來配置,
中斷的配置: 首先使能某個外設中斷;然后設定中斷優先級分組,初始化NVIC_InitTypeDef結構體,設定搶占優先級和回應優先級,使能中斷請求;最后撰寫中斷服務函式,
1、外部中斷編程
1.1 EXTI的結構
在STM32F10X芯片中外部中斷/事件控制器(EXTI)有20個用于產生中斷/事件請求的邊沿檢測器,其對應的每一根輸入線都可以單獨進行配置,選擇“中斷”或“事件”型別以及觸發事件(上升沿觸發、邊沿觸發和下降沿觸發),還可以對中斷開啟屏蔽,外部中斷/事件控制器(EXTI)的結構如下圖中所示:

EXTI主要有兩個功能,一個用來產生中斷,另一個被用來產生事件,
(1)產生中斷的線路由輸入線開始,經邊沿檢測電路,到一個或門電路再到一個與門電路,最后至NVIC中斷控制器結束,輸入線可以通過暫存器設定為任何一個GPIO引腳,也可以是外設的時間,在這部分主要是傳遞一個電平變化的信號;邊沿檢測電路可以對觸發方式進行選擇,有上升沿觸發、下降沿觸發以及邊沿觸發三種方式;或門電路使得中斷不僅可以由外部電路進行觸發也可以通過軟體來啟動中斷/事件;與門電路用來決定是否產生中斷,只有中斷屏蔽暫存器端和或門電路端的有效信號均為1時,中斷才會被產生;最后將掛起暫存器內容輸入到NVIC中,實作系統中斷事件的控制,
(2)產生事件的前三步與(1)相同,后經過一個與門電路,再經過一個脈沖發生器電路后結束,其中與門電路是用來決定是否產生事件,只有事件屏蔽暫存器端和或門電路端的有效信號均為1時,事件才會被產生;脈沖發生器電路只有在與門電路為有效信號1時才會輸出一個脈沖信號;脈沖信號可被用于其他外設電路的使用,如定時器、ADC等等,
從EXTI框圖中可以看出,中斷的輸出是NVIC控制器,從而會導致系統運行中斷服務函式,處于軟體層面;時間的輸出是脈沖信號流向其他外設電路,處于硬體層面,
1.2 外部中斷/事件線 映射
STM32F10X的EXTI對用連接的外設如下表中所示:
| EXTI線路 | 說明 |
|---|---|
| EXTI 線 0~15 | 對應外部IO口的輸入中斷 |
| EXTI 線 16 | 連接到PVD輸出 |
| EXTI 線 17 | 連接到RTC鬧鐘事件 |
| EXTI 線 18 | 連接到USB OTG FS喚醒時間 |
| EXTI 線 19 | 連接到以太網喚醒事件 |
從表中可以看出EXTI可供外部IO使用的中斷線有16根,但STM32F103芯片的IO口數量卻有很多,每個GPIO埠均有16個管腳對應了16根中斷線,中斷線每次只能連接在一個IO口上,因此就需要用到外部中斷的映射功能,通過AFIO(復用IO)的外部中斷配置暫存器1的EXTIx[3:0]位來決定對應的中斷線映射到哪個GPIO埠上,對于中斷映射到GPIO埠的配置函式在stm32f10x_gpio.c和stm32f10x_gpio.h中,所以在使用標準庫進行開發時需要將這兩個檔案添加到工程中,
1.3 外部中斷/事件線 映射
EXTI相關的庫函式在stm32f10x_exti.c和stm32f10x_exti.h檔案中,使用庫函式對外部中斷進行配置的步驟如下:
- 使能IO口時鐘,配置IO模式為輸入;
- 開啟AFIO時鐘,設定IO口與中斷線的映射關系;
首先需要使能AFIO時鐘,其是掛載在APB2總線上的;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
然后將GPIO映射到對應的中斷線上;
GPIO_EXTILineConfig(GPIO_PortSourceGPIOx, GPIO_PinSourceY]);//Y是0~15號中斷線
- 配置中斷分組(NVIC),使能中斷;
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //EXTI0 中斷通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //搶占優先級
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子優先級
NVIC_Init(&NVIC_InitStructure);
- 初始化EXTI,選擇觸發方式;
配置好NVIC后就需要對中斷線上的中斷進行初始化,需要呼叫void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)函式,其中結構體EXTI_InitTypeDef的成員變數有:
typedef struct
{
uint32_t EXTI_Line; //中斷/事件線
EXTIMode_TypeDef EXTI_Mode; //EXTI模式
EXTITrigger_TypeDef EXTI_Trigger; //EXTI 觸發方式
FunctionalState EXTI_LineCmd; //中斷線使能或失能
}
EXTI_Line: 可配置引數為EXTI0~EXTI20;
EXTI_Mode: 可配置為EXTI_Mode_Interrupt(中斷模式)和EXTI_Mode_Event(事件模式);
EXTI_Trigger: 可配置為EXTI_Trigger_Rising(上升沿觸發)、EXTI_Trigger_Falling(下降沿觸發)以及EXTI_Trigger_Rising_Falling(邊沿觸發);
EXTI_LineCmd: 配置ENABLE為使能,DISABLE為失能,
5. 撰寫EXTI中斷服務函式
中斷服務函式就是當外部中斷發生后,需要執行的一段用戶程式,可根據需求進行撰寫,
1.4 應用示例
在本小節中使用一個按鍵來觸發外部中斷,在服務函式中執行點亮LED的程式,這個功能是很簡單的,但也能夠解釋清楚外部中斷的配置程序,詳細的代碼如下所示(其中LED檔案的代碼在上一章節中已經給出):
exit.h
#ifndef _exti_H
#define _exti_H
#include "system.h"
void MY_EXTI_Init(void);
#endif
exit.c
#include "exti.h"
#include "SysTick.h"
#include "key.h"
#include "led.h"
void MY_EXTI_Init()
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel=EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel=EXTI3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel=EXTI4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
EXTI_InitStructure.EXTI_Line=EXTI_Line0;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line=EXTI_Line2|EXTI_Line3|EXTI_Line4;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
}
void EXTI0_IRQHandler()
{
if(EXTI_GetFlagStatus(EXTI_Line0)==1)
{
delay_ms(10);
if(K_UP==1)
{
led2=0;
}
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
void EXTI2_IRQHandler()
{
if(EXTI_GetFlagStatus(EXTI_Line2)==1)
{
delay_ms(10);
if(K_LEFT==0)
{
led3=1;
}
}
EXTI_ClearITPendingBit(EXTI_Line2);
}
void EXTI3_IRQHandler()
{
if(EXTI_GetFlagStatus(EXTI_Line3)==1)
{
delay_ms(10);
if(K_DOWN==0)
{
led2=1;
}
}
EXTI_ClearITPendingBit(EXTI_Line3);
}
void EXTI4_IRQHandler()
{
if(EXTI_GetFlagStatus(EXTI_Line4)==1)
{
delay_ms(10);
if(K_RIGHT==0)
{
led3=0;
}
}
EXTI_ClearITPendingBit(EXTI_Line4);
}
key.h
#ifndef _key_H
#define _key_H
#include "system.h"
#define KEY_UP_Pin GPIO_Pin_0
#define KEY_LEFT_Pin GPIO_Pin_2
#define KEY_DOWN_Pin GPIO_Pin_3
#define KEY_RIGHT_Pin GPIO_Pin_4
#define KEY_UP_PORT GPIOA
#define KEY_PORT GPIOE
#define K_UP PAin(0)
#define K_DOWN PEin(3)
#define K_LEFT PEin(2)
#define K_RIGHT PEin(4)
#define KEY_UP 1
#define KEY_LEFT 3
#define KEY_DOWN 2
#define KEY_RIGHT 4
void KEY_Init(void);
u8 KEY_Scan(u8 mode);
#endif
key.c
#include "key.h"
#include "SysTick.h"
void KEY_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);
GPIO_InitStructure.GPIO_Pin=KEY_UP_Pin;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(KEY_UP_PORT,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=KEY_LEFT_Pin|KEY_DOWN_Pin|KEY_RIGHT_Pin;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(KEY_PORT,&GPIO_InitStructure);
}
u8 KEY_Scan(u8 mode)
{
static u8 key=1;
if(key==1&&(K_UP==1||K_DOWN==0||K_LEFT==0||K_RIGHT==0))
{
delay_ms(10);
key=0;
if(K_UP==1)
{
return KEY_UP;
}
else if(K_DOWN==0)
{
return KEY_DOWN;
}
else if(K_LEFT==0)
{
return KEY_LEFT;
}
else if(K_RIGHT==0)
{
return KEY_RIGHT;
}
}
else if(K_UP==0&&K_DOWN==1&&K_LEFT==1&&K_RIGHT==1)
{
key=1;
}
if(mode==1)
{
key=1;
}
return 0;
}
main.c
#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "exti.h"
#include "key.h"
u8 i=0;
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
MY_EXTI_Init();
LED_Init();
KEY_Init();
while(1)
{
i++;
if(i%20==0)
{
led1=!led1;
}
delay_ms(10);
}
}
2、 定時器中斷編程
STM32F1的定時器系統由2個基本定時器(TIM6、TIM7)、4個通用定時器(TIM2~TIM5)和2個高級定時器(TIM1、TIM8)所組成,基本定時器同51單片機內的定時器類似,功能較為簡單;通用定時器在其基礎上增加了輸入捕獲與輸出比較功能;高級定時器又在通用定時器基礎上增加了可編程死去互補輸出、重復計數、帶剎車(斷路)的功能主要針對于工業電機的控制,
2.1 通用定時器
STM32F1的通用定時器內有一個16位自動多載計數器(CNT)由可編程預分頻器(PSC)驅動,其可用于測量輸入信號的脈沖寬度(輸入捕獲)或者生產輸出波形(輸出比較和PWM),使用定時器預分頻器和RCC時鐘控制器預分頻器,脈沖長度和波形周期可以在幾微秒到幾毫秒之間調整,此外每一個定時器都是獨立的,之間不互相共享任何資源,通用定時器TIM2~TIM5具備如下功能:
- 16位向上、向下、向上/向下自動裝載計數器(TIMx_CNT);
- 16位可編程預分頻器,計數器時鐘頻率的分頻系數為1~65535;
- 4個獨立通道(TIMx_CH1~4),通道可以被用作輸入捕獲、輸出比較、PWM生成和單脈沖模式輸出功能;
- 可以使用外部信號(TIMx_ETR)控制定時器,可實作多個定時器互連的同步電路;
- 發生更新(計數器溢位、初始化)、觸發時間(計數器啟動、停止、初始化或者由內部/外部觸發計數)、輸入捕獲以及輸出比較事件時會產生中斷/DMA請求;
- 支持針對定位的增量(正交)編碼器和霍爾傳感器電路
- 觸發輸入作為外部時鐘或者按周期的電流管理,
2.2 定時器配置步驟
定時器相關的庫函式在stm32f10x_tim,c和stm32f10x_tim.h檔案中,使用庫函式對定時器進行配置的詳細步驟如下:
- 使能定時器時鐘;
定時器是掛載在APB1總線上的設備,因此使能定時器可以呼叫函式:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIMx, ENABLE); - 初始化定時器引數,包含了自動重裝值,分頻系數,計數方式等;
呼叫函式:void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
TIM_TimeBaseInitTypeDef為一個結構體型別,包含了定時器初始化的成員變數:
typedef struct
{
uint16_t TIM_Prescaler; //定時器預分頻器
unit16_t TIM_CouterMode; //計數模式
unit16_t TIM_Period; //定時器周期
unit16_t TIM_ClockDivision; //時鐘分頻
unit8_t TIM_RepetitionCounter; //重復計數器
}
TIM_Prescaler: 時鐘源經過該預分頻器后輸出的是定時器時鐘,設定范圍為0~65535;
TIM_CouterMode: 可設定為TIM_CounterMode_Up(向上)、TIM_CounterMode_Down(向下)以及中心對齊計數模式;
TIM_Period:設定定時器自動多載計數值,范圍為0~65536;
TIM_ClockDivision: 時鐘分頻因子,設定定時器時鐘CK_INT頻率與數字濾波器采樣時鐘頻率分頻比;
TIM_RepetitionCounter: 重復計數器,簡單的控制PWM輸出個數,
-
設定定時器中斷型別,使能定時器;
呼叫函式:void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
TIM_IT 用來設定定時器中斷型別,包含TIM_IT_Update(更新中斷)、TIM_IT_Trigger(觸發中斷)以及輸入捕獲中斷等等;
FunctionalState 用來使能或使能定時器中斷,ENABLE和DISABLE, -
設定定時器中斷優先級,使能定時器中斷通道;
對NVIC初始化,如前一節中所示, -
開啟定時器;
呼叫函式:void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState); -
撰寫定時器中斷服務函式
由于定時器中斷型別有很多,因此在中斷服務函式中需要通過狀態暫存器的值來判斷此次中斷屬于哪一種型別,然后再執行相應的用戶程式,
2.3 應用示例
本次實驗通過TIM4定時器的更新中斷控制LED燈實作不斷閃爍的功能,詳細的代碼如下所示;
time.h
#ifndef _time_H
#define _time_H
#include "system.h"
void TIM4_Init(u16 pre,u16 psc);
#endif
time.c
#include "time.h"
#include "led.h"
void TIM4_Init(u16 pre,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
TIM_TimeBaseInitStructure.TIM_Period=pre;//?ü?ú
TIM_TimeBaseInitStructure.TIM_Prescaler=psc;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
TIM_ClearITPendingBit(TIM4,TIM_CounterMode_Up);
NVIC_InitStructure.NVIC_IRQChannel=TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM4,ENABLE);
}
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4,TIM_IT_Update)==1)
{
led2=!led2;
}
TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
}
main.c
#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "time.h"
u8 i=0;
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init();
TIM4_Init(1000,36000-1);//500ms
while(1)
{
i++;
if(i%20==0)
{
led1=!led1;
}
delay_ms(10);
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/278158.html
標籤:其他
上一篇:關于 CROSS 延期至4月21日登陸幣安智能鏈 BSC 的公告
下一篇:MCU學習筆記_C語言基礎
