最近要用nRF51822進行項目的多任務調度,其中不可避免的要用到要用到nRF51822的相關中斷
首先,nRF51822是基於Cortex-M0架構的MCU,根據nRF51822的軟件開發包中的core-m0.h文件夾,可以找到51822的相關中斷編號定義(Interrupt Number Definition)
<span style="font-size:18px;">/* ------------------------- Interrupt Number Definition ------------------------ */
typedef enum {
/* ------------------- Cortex-M0 Processor Exceptions Numbers ------------------- */
Reset_IRQn = -15, /*!< 1 Reset Vector, invoked on Power up and warm reset */
NonMaskableInt_IRQn = -14, /*!< 2 Non maskable Interrupt, cannot be stopped or preempted */
HardFault_IRQn = -13, /*!< 3 Hard Fault, all classes of Fault */
SVCall_IRQn = -5, /*!< 11 System Service Call via SVC instruction */
DebugMonitor_IRQn = -4, /*!< 12 Debug Monitor */
PendSV_IRQn = -2, /*!< 14 Pendable request for system service */
SysTick_IRQn = -1, /*!< 15 System Tick Timer */
/* ---------------------- nRF51 Specific Interrupt Numbers ---------------------- */
POWER_CLOCK_IRQn = 0, /*!< 0 POWER_CLOCK */
RADIO_IRQn = 1, /*!< 1 RADIO */
UART0_IRQn = 2, /*!< 2 UART0 */
SPI0_TWI0_IRQn = 3, /*!< 3 SPI0_TWI0 */
SPI1_TWI1_IRQn = 4, /*!< 4 SPI1_TWI1 */
GPIOTE_IRQn = 6, /*!< 6 GPIOTE */
ADC_IRQn = 7, /*!< 7 ADC */
TIMER0_IRQn = 8, /*!< 8 TIMER0 */
TIMER1_IRQn = 9, /*!< 9 TIMER1 */
TIMER2_IRQn = 10, /*!< 10 TIMER2 */
RTC0_IRQn = 11, /*!< 11 RTC0 */
TEMP_IRQn = 12, /*!< 12 TEMP */
RNG_IRQn = 13, /*!< 13 RNG */
ECB_IRQn = 14, /*!< 14 ECB */
CCM_AAR_IRQn = 15, /*!< 15 CCM_AAR */
WDT_IRQn = 16, /*!< 16 WDT */
RTC1_IRQn = 17, /*!< 17 RTC1 */
QDEC_IRQn = 18, /*!< 18 QDEC */
LPCOMP_COMP_IRQn = 19, /*!< 19 LPCOMP_COMP */
SWI0_IRQn = 20, /*!< 20 SWI0 */
SWI1_IRQn = 21, /*!< 21 SWI1 */
SWI2_IRQn = 22, /*!< 22 SWI2 */
SWI3_IRQn = 23, /*!< 23 SWI3 */
SWI4_IRQn = 24, /*!< 24 SWI4 */
SWI5_IRQn = 25 /*!< 25 SWI5 */
} IRQn_Type;
</span>
在進行中斷處理時,一般都要按照這個步驟:1、對要用到的中斷初始化;2、對用到的中斷進行使能;3、對中斷進行優先級設置
1、中斷初始化
nRF51822的外部I/O(暫時理解的程度以及用過的中斷只有I/O中斷,其他中斷沒有實踐過)中斷是基於任務和事件模式的。
按照nRF51822用戶手冊,每個GPIOTE通道的以下輸入條件可以產生一個事件(在此可將事件理解爲一箇中斷):上升沿、下降沿或者任何變化。因此,在進行中斷初始化的時候,首先要將相應的中斷輸入I/O引腳設置爲輸入模式。然後通過CONFIG[n]設置GPIOTE通道的模式MODE(任務或者事件)、關聯任務OUT[n]或者事件IN[n]的引腳PSEL,觸發方式POLARITY。例如下面是將ROCKER_INTERRUPT引腳初始化爲事件下降沿觸發的事件IN[0]。
<pre name="code" class="objc">NRF_GPIOTE->CONFIG[0]=(GPIOTE_CONFIG_POLARITY_HiToLo<<GPIOTE_CONFIG_POLARITY_Pos)
|(ROCKER_INTERRUPT<<GPIOTE_CONFIG_PSEL_Pos)
|(GPIOTE_CONFIG_MODE_Event<<GPIOTE_CONFIG_MODE_Pos);
在對相關事件的觸發方式以及關聯引腳初始化完成之後,還需要通過INTENSET寄存器進行使能
NRF_GPIOTE->INTENSET=GPIOTE_INTENSET_IN0_Set<<GPIOTE_INTENSET_IN0_Pos; //ʹÄÜIN0ʼþ
2、中斷使能
在中斷初始化中僅僅是對事件n的使能,在此要對相應的中斷進行使能。
類比於Cortex-M3架構,在MDK內與NVIC相關的寄存器,MDK爲其定義瞭如下的結構體
NVIC(Nested Vectored Interrupt Controller)嵌套中斷向量控制器
<span style="font-size:14px;">/** \ingroup CMSIS_core_register
\defgroup CMSIS_NVIC Nested Vectored Interrupt Controller (NVIC)
\brief Type definitions for the NVIC Registers
@{
*/
/** \brief Structure type to access the Nested Vectored Interrupt Controller (NVIC).
*/
typedef struct
{
__IO uint32_t ISER[1]; /*!< Offset: 0x000 (R/W) Interrupt Set Enable Register */
uint32_t RESERVED0[31];
__IO uint32_t ICER[1]; /*!< Offset: 0x080 (R/W) Interrupt Clear Enable Register */
uint32_t RSERVED1[31];
__IO uint32_t ISPR[1]; /*!< Offset: 0x100 (R/W) Interrupt Set Pending Register */
uint32_t RESERVED2[31];
__IO uint32_t ICPR[1]; /*!< Offset: 0x180 (R/W) Interrupt Clear Pending Register */
uint32_t RESERVED3[31];
uint32_t RESERVED4[64];
__IO uint32_t IP[8]; /*!< Offset: 0x300 (R/W) Interrupt Priority Register */
} NVIC_Type;
/*@} end of group CMSIS_NVIC */</span>
ISER[1]:中斷使能寄存器。由前邊可以看到,nRF51的寄存器編號爲0-25,在這裏ISER[1]爲32位寄存器,總共可以表示32箇中斷,bit0-bit25分別對應中斷0-25(其實後來發現在NVIC_EnableIRQ()函數中是將1左移IRQn位之後又與0x1F,也就是空出了低五位,所以在這裏應該是對應的高地址,而把低位給空出來的)。要使能某個中斷,必須設置相應的ISER位爲1,使該中斷被使能(這裏僅僅是使能,還要配合中斷分組、屏蔽、I/O口映射等設置纔算是一個完整的中斷設置)。此處是按照STM32中斷設置理解的。
ICER[1]:中斷除能寄存器,和ISER的作用恰好相反,用來清除某個中斷的使能的。專門設置一個ICER來清除中斷位,而不是向ISER寫0來清除,這是因爲NVIC的這些寄存器都是寫1有效的,寫0無效的。
ISPR[1]:中斷掛起控制寄存器。通過置1可以將正在進行的中斷掛起,而執行同級或者更高級別的中斷。
ICPR[1]:中斷解掛控制寄存器。通過置1可以將掛起的中斷解掛。
IP[8]:中斷優先級控制寄存器組。這個寄存器組相當重要。中斷分組與這個寄存器密切相關。
在這裏插入一下中斷優先級的理解。因爲以前都是用的STM32,在這裏就按照Cortex-M3架構來理解。Cortex-M3中有兩個優先級的概念——搶佔優先級和響應優先級,有人把響應優先級稱作“亞優先級”和“副優先級”,每個中斷源都需要被指定這兩種優先級。
具有高搶佔優先級的中斷可以在具有低搶佔優先級的中斷處理過程中被響應,即中斷嵌套。當兩個中斷源的搶佔優先級相同時,這兩個中斷將沒有嵌套關係,當一箇中斷到來後,如果正在處理另一箇中斷,這個後到來的中斷就要等到前一箇中斷處理完之後才能被處理。如果這兩個中斷同時到達,則中斷控制器根據他們的響應優先級高低來決定先處理哪一個(在《原子教你玩STM32》中說在搶佔優先級相同的中斷,高優先級的響應優先級不可打斷低響應優先級的中斷,有待考證);如果他們的搶佔優先級和響應優先級相等,則根據他們在中斷表中的排位順序來決定先處理哪一個。
在STM32中需要用NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)先對中斷優先級進行分組,這個分組在一個程序中只能設定一次,是在中斷與復位控制寄存器SCB->AIRCR中設置的,也就是分組之後所有的中斷只能按照這個分組方式進行設定搶佔優先級和響應優先級。
在對中斷優先級進行分組設置之後,要對每個中斷的中斷源優先級進行設置,也就是設置中斷優先級控制寄存器IP,中斷源優先級具體設置了該中斷源的優先級別。
在此先說一下STM32中斷優先級設置的步驟:
系統運行開始的時候設置中斷分組,確定組號,也就是確定搶佔優先級和子優先級的分配位數,調用函數爲NVIC_PriorityGroupConfig();
設置用到的中斷的中斷優先級別,對每個中斷調用函數爲NVIC_Init(NVIC_InitTypeDef *NVIC_InitStruct);
而在nRF51822中,我現在還沒有碰到中斷優先級進行分組的情況,在此就先不寫,如果將來用到,再進行相關補充
首先是中斷使能,和STM32中在初始化NVIC_InitStruct結構體的同時完成中斷使能不同,nRF51822通過專門的函數NVIC_EnableIRQ(IRQn_Type IRQn)來實現響應中斷使能,形參變量IRQn爲要進行初始化的中斷編號,在本博文的開頭已經給出,在SDK的core-m0.h中定義,以下爲中斷使能函數。
/** \brief Enable External Interrupt
The function enables a device-specific interrupt in the NVIC interrupt controller.
\param [in] IRQn External interrupt number. Value cannot be negative.
*/
__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
{
NVIC->ISER[0] = (1 << ((uint32_t)(IRQn) & 0x1F));
}
/** \brief Disable External Interrupt
The function disables a device-specific interrupt in the NVIC interrupt controller.
\param [in] IRQn External interrupt number. Value cannot be negative.
*/
__STATIC_INLINE void NVIC_DisableIRQ(IRQn_Type IRQn)
{
NVIC->ICER[0] = (1 << ((uint32_t)(IRQn) & 0x1F));
}
例如,要使能GPIOTE中斷,則調用
NVIC_EnableIRQ(GPIOTE_IRQn);
3、中斷優先級設置
中斷優先級設置源函數如下:
/** \brief Set Interrupt Priority
The function sets the priority of an interrupt.
\note The priority cannot be set for every core interrupt.
\param [in] IRQn Interrupt number.
\param [in] priority Priority to set.
*/
__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if(IRQn < 0) {
SCB->SHP[_SHP_IDX(IRQn)] = (SCB->SHP[_SHP_IDX(IRQn)] & ~(0xFF << _BIT_SHIFT(IRQn))) |
(((priority << (8 - __NVIC_PRIO_BITS)) & 0xFF) << _BIT_SHIFT(IRQn)); }
else {
NVIC->IP[_IP_IDX(IRQn)] = (NVIC->IP[_IP_IDX(IRQn)] & ~(0xFF << _BIT_SHIFT(IRQn))) |
(((priority << (8 - __NVIC_PRIO_BITS)) & 0xFF) << _BIT_SHIFT(IRQn)); }
}
#define __NVIC_PRIO_BITS 2 /*!< Number of Bits used for Priority Levels */
/* Interrupt Priorities are WORD accessible only under ARMv6M */
/* The following MACROS handle generation of the register offset and byte masks */
#define _BIT_SHIFT(IRQn) ( (((uint32_t)(IRQn) ) & 0x03) * 8 )
#define _SHP_IDX(IRQn) ( ((((uint32_t)(IRQn) & 0x0F)-8) >> 2) )
#define _IP_IDX(IRQn) ( ((uint32_t)(IRQn) >> 2) )
先來看_IP_IDX(IRQn)的具體意思,在對應的宏定義中,將中斷編號IRQn強制轉換爲uint32_t類型,在這裏我的理解是因爲中斷優先級控制寄存器是32bit的,將來爲了寫寄存器以及移位的需要,故將其強制轉換爲32位。在宏定義中將其右移兩位,右移兩位是因爲中斷優先級控制寄存器組IP由8個32位的寄存器組成,每個可屏蔽中斷佔8位,這樣也就是4箇中斷佔用一箇中斷優先級控制寄存器,將中斷編號右移兩位之後所得的剛好是中斷所在的寄存器的編號。觀察下表可以對中斷編號右移2位之後產生的作用一目瞭然。
移動之後對應的寄存器編號 | 中斷編號 | 對應的二進制數 | 右移兩位所得二進制數 |
0 | 0 | 0000 | 00 |
0 | 1 | 0001 | 00 |
0 | 2 | 0010 | 00 |
0 | 3 | 0011 | 00 |
1 | 4 | 0100 | 01 |
1 | 5 | 0101 | 01 |
1 | 6 | 0110 | 01 |
1 | 7 | 0111 | 01 |
2 | 8 | 1000 | 10 |
2 | 9 | 1001 | 10 |
2 | 10 | 1010 | 10 |
2 | 11 | 1011 | 10 |
3 | 12 | 1100 | 11 |
3 | 13 | 1101 | 11 |
3 | 14 | 1110 | 11 |
3 | 15 | 1111 | 11 |
4 | 16 | 010000 | 0100 |
4 | 17 | 010001 | 0100 |
4 | 18 | 010010 | 0100 |
4 | 19 | 010011 | 0100 |
5 | 20 | 010100 | 0101 |
5 | 21 | 010101 | 0101 |
5 | 22 | 010110 | 0101 |
5 | 23 | 010111 | 0101 |
6 | 24 | 011000 | 0110 |
#define _BIT_SHIFT(IRQn) ( (((uint32_t)(IRQn) ) & 0x03) * 8 )</span>
由上邊表格可以看到,中斷編號IRQn右移兩位餘下的部分對應的是中斷優先級控制寄存器編號,而移出去的(也就是最低兩位)則是對應的中斷優先級控制寄存器對應的得位號。每個中斷佔用8位,也就是[31-24]、[23-16]、[15-8]、[7-0]對應該組中斷的3-0。由上表觀察可以知道,中斷編號IRQn的最低兩位代表在某個中斷優先控制寄存器(0-8)中對應的位號。因此,利用宏定義_BIT_SHIFT(IRQn)對應要設置的某中斷對應的某個中斷優先控制寄存器的起始位號。例如:要設置的是外部IO中斷,其中斷編號GPIOTE被宏定義爲6,6的二級製爲0110,與0x03與之後,爲10(二進制),也就是對應1號中斷優先控制寄存器的第23-16位,因此,要對其進行設置中斷優先級,要設置的也就是1號中斷優先級控制寄存器的第23-16位。在移位操作時,也就是移動2*8位,因此在宏定義中有*8的操作。在設置之前,首先要將其他位進行屏蔽,以免對其他中斷編號產生誤操作,這也就是NVIC_SetPriority()函數中或號之前的作用,其實也就是屏蔽作用。
在這裏將其乘以8即可得到所對應的中斷對應的寄存器中的最低位,在將來將設定的中斷優先級直接左移_BIT_SHIFT(IRQn)位即可將其寫入中斷控制寄存器。
<span style="font-size:14px;">#define __NVIC_PRIO_BITS 2 /*!< Number of Bits used for Priority Levels */</span>
由_NVIC_PRIO_BITS的宏定義可知,中斷等級只有兩位,也就是只可以設定四個中斷優先級。每個中斷的8bit沒有全部使用,按照nRF5188官方SDK給出的宏定義可以看出,在此只用了其中2位。8-_NVIC_PRIO_BITS也就確定了將所設定的中斷優先級priority左移6位,達到中斷所用8bit的高2位,然後與0xFF相與,再左移_BIT_SHIFT(IRQn)即可得出需要寫入對應的中斷優先級控制寄存器的值。爲了將中斷對應的位屏蔽,在此通過NVIC->IP[_IP_IDX(IRQn)]&~(0xFF<<_BIT_SHIFT(IRQ))將其他位置1。其中對0xFF<<_BIT_SHIFT(IRQ)取反剛好將其他位置1,與NVIC->IP[_IP_IDX(IRQn)]相與,即可將需要設定的中斷寄存器其他位屏蔽。然後二者相或,即可對對應的寄存器完成設定。
在《低功耗藍牙開發與實戰》中,僅僅說是通過下述語句來配置中斷優先級爲低優先級的,具體如何實現現在不是太清楚,而這個中斷優先級設置在青風所給出的教程中根本就沒有用到,下步需要查閱Cortex-M3架構的NVIC和中斷控制,搞明白NVIC_SetPriority()的作用與具體功能。
NVIC_SetPriority(GPIOTE_IRQn,3);
中斷處理函數
進行完這三步設置之後,就可以編寫相應的中斷處理函數了。
在此完全是自己的理解,有待進行驗證。自己感覺中斷處理函數命名是由上面配置的中斷決定的,例如上面配置的中斷是GPIOTE_IRQn,則中斷處理函數的名稱就是GPIOTE_IRQHandler;如果配置的中斷是TIMER1_IRQn,則中斷處理函數的名稱就是TIMER1_IRQHandler。
進入中斷處理函數之後,一般的操作步驟是:1、判斷中斷類型(是否是所需的中斷以及是否使能過);2、對中斷標誌進行清零,以便程序下次能夠正常進入中斷;3、進行中斷處理
下面爲51822的某I/O中斷處理函數
void GPIOTE_IRQHandler(void)
{
if((NRF_GPIOTE->EVENTS_IN[0]==1)&&(NRF_GPIOTE->INTENSET&GPIOTE_INTENSET_IN0_Msk))
{
NRF_GPIOTE->EVENTS_IN[0]=0;
rocker_ready_flag=1;
}
}