2014年2月4日 星期二

作業系統之前的程式 for stm32f4discovery (8.2) - user button by 外部中斷

看過 <作業系統之前的程式 for stm32f4discovery (8.1) - user button by polling> (名字取的太長了) 之後, 這次我們來看看如何使用外部中斷。

如果你查過應該會看到類似的程式碼:

exti.c
 1 void main()
 2 {
 3   /* Configure EXTI Line0 (connected to PA0 pin) in interrupt mode ********/
 4   /* Enable GPIOA clock */
 5    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
 6   /* Enable SYSCFG clock */
 7    SYSCFG_CompensationCellCmd(ENABLE);
 8     
 9    /* Configure PA0 pin as input floating */
10     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
11     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
12     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
13     GPIO_Init(GPIOA, &GPIO_InitStructure);
14      
15     /* Connect EXTI Line0 to PA0 pin via SYSCFG */
16     SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
17      
18     /* Configure EXTI Line0 */
19      EXTI_InitStructure.EXTI_Line = EXTI_Line0;
20      EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
21      EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; 
22      EXTI_InitStructure.EXTI_LineCmd = ENABLE;
23      EXTI_Init(&EXTI_InitStructure);
24       
25      /* Enable and set EXTI Line0 Interrupt to the lowest priority */
26      NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
27      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
28      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
29      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
30      NVIC_Init(&NVIC_InitStructure);
31 }
32 
33 void EXTI0_IRQHandler(void)
34 {
35    int x = 2;
36 }

stm32 提供了 libray, 用起來像是這樣, 簡單的不得了, 不過你一定想知道更深入吧! 尤其是那個 exti.c L33 EXTI0_IRQHandler 到底是怎麼來的? 我也是, 所以才有這篇文章。而事實上若是沒有 stm32 的 library, 要完成這樣的功能比想像中還要麻煩不少, 難度是 5 個數量級以上。

首先得先查詢 user button 接在哪裡, 答案是 PA0。這是 GPIO 的部份, 所以接下來要設定 GPIO; 再來是外部中斷, user button 被歸類在外部中斷, 來找出相關暫存器的設定資料吧! 有:
  1. System configuration controller (SYSCFG)
  2. Interrupts and events
  3. nvic register
根據 stm32f407 datasheet 備齊以上這些資訊之後, 才能正確設定這些暫存器。

搞了這麼久, 終於設定完畢, 可以寫 button isr 了, 疑! user button 的中斷編號是?
直接告訴你: 外部中斷第六號, 就是那個 EXTI0_IRQHandler, 還沒完, 那你知道 button isr 要放在哪個位址嗎?

事實上要能來到這裡, 你還得搞定如何寫出開機的程式, library 當然幫了不少忙, 這也不算簡單, 若有跟著這系列的朋友 (系列 1, 2 已經有提過了), 應該不是難題。

你可能會有個疑問, 那用 library 不就好了, 這當然不是壞事, 你可以這樣選擇, 我只是想把整個過程搞清楚。所以別人可能花一小時完成這功能 (google 完, 貼上 code, 搞定, 睡大覺去了), 我得要花上兩個寂寞的晚上。

來看程式碼:

這次不小心把程式分成幾個檔案: user_button.c exti.c syscfg.c

exti.c syscfg.c 對照 datasheet 看就可以了。

user_button.c L239 ~ L243 設定 nvic, 很簡單吧! 都有注解要參考的資料了, 不會很難懂。其他和 8.1 的設定都一樣 (因為是從 8.1 抄過來的阿)。

user_button.c
  1 /*
  2  * ref: http://eliaselectronics.com/stm32f4-tutorials/stm32f4-gpio-tutorial/
  3  *   use polling to check user button status (press/release)
  4  */
  5 #include "stm32.h"
  6 
  7 #include "stm32f4xx.h"
  8 #include "stm32f4xx_gpio.h"
  9 #include "stm32f4xx_exti.h"
 10 #include "stm32f4xx_syscfg.h"
 11 #include "stm32f4xx_rcc.h"
 12 
 13 #define USE_STDPERIPH_DRIVER
 14 
 15 /*!< Uncomment the following line if you need to relocate your vector Table in
 16      Internal SRAM. */
 17 /* #define VECT_TAB_SRAM */
 18 #define VECT_TAB_OFFSET  0x0 /*!< Vector Table base offset field.  This value must be a multiple of 0x200. */
 19 
137 
138 int init_stm32f4(void)
139 {
140   GPIO_InitTypeDef GPIO_InitStructure;
155 
156   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
157   RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
158 
159   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;            // we want to configure PA0
160   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;         // we want it to be an input
161 #ifdef POLL_USER_BUTTON
162   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;   // this sets the pin type to push / pull (as opposed to open drain)
163   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//this sets the GPIO modules clock speed
164   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;   // this enables the pulldown resistor --> we want to detect a high level
165 
166 #else
167   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
168   //GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;   // this enables the pulldown resistor --> we want to detect a high level
169 #endif
170   GPIO_Init(GPIOA, &GPIO_InitStructure);   
171 }
209 
210 
211 int main(void)
212 {
213   init_stm32f4();
214   int p;
215 
216   /* Connect EXTI Line0 to PA0 pin */
217   // if no line, it can work.
218   SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
219 
220   EXTI_InitTypeDef   EXTI_InitStructure;// ext interupt structure
221   /* Configure EXTI Line0 */
222   EXTI_InitStructure.EXTI_Line = EXTI_Line0;
223   EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
224   EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
225   EXTI_InitStructure.EXTI_LineCmd = ENABLE;
226   EXTI_Init(&EXTI_InitStructure);
227 
238 
239 #if 1
240   // ref:  arm cortex-m3: 嵌入式系統設計入門 p8-3
241   *(volatile unsigned long*) 0xE000E100 = 0;
242   *(volatile unsigned long*) 0xE000E100 |= (1 << 6);
243 #endif
244 
245   while(1)
246   {
247 #ifdef POLL_USER_BUTTON
248     int i=0;
249     if(GPIOA->IDR & 0x0001)
250     {
251       i=1;
252     }
253     else
254     {
255       i=2;
256     }
257     p = i;
258 #else
259     p = 5;
260 #endif
261   }
262 }
263 
264 void exti0_isr(void)
265 {
266   int a=6;
267   
268   // if no line, it will envoke exti0_isr().
269   EXTI_ClearITPendingBit(EXTI_Line0);
270 
271 }   
272 

再來是 L264 是怎麼來的? 這要看 stm32.h L166, 剛好是外部中斷第六號, wwdg_isr 是第零號, 依序數下去。

stm32.h
  1 #ifndef STM32_H
  2 #define STM32_H
  3 
  4 #include "lib_mygpio_led.h"
  5 
  6 #define STACK_SIZE 64
  7 extern unsigned long _etext;
  8 extern unsigned long _data;
  9 extern unsigned long _edata;
 10 extern unsigned long _bss;
 11 extern unsigned long _ebss;
 12 
 13 void ResetISR(void)
 14 {
 15   unsigned long *pulSrc, *pulDest;
 16 
 17   pulSrc = &_etext;
 18   for (pulDest = &_data; pulDest < &_edata;)
 19     *pulDest++ = *pulSrc++;
 20   for (pulDest = &_bss; pulDest < &_ebss;)
 21     *pulDest++ = 0;
 22 
 23   main();
 24 }
 25 
 59 
 60 void wwdg_isr(void)
 61 {
 62   int b=33;
 63 }
 64 void pvd_isr(void)
 65 {
 66 }
 67 void tamp_stamp_isr(void)
 68 {
 69 }
 70 
 71 void rtc_wkup_isr(void)
 72 {
 73 }
 74 void flash_isr(void)
 75 {
 76 }
 77 void rcc_isr(void)
 78 {
 79 }
 80 
 81 void exti0_isr(void);
 82 
 83 
 84 typedef void (*pfnISR)(void);
 85 __attribute__((section(".stackares")))
 86 static unsigned long pulStack[STACK_SIZE];
 87 
 88 
 89 __attribute__((section(".isr_vector")))
 90 pfnISR VectorTable[]=
 91 {
 92   (pfnISR)((unsigned long)pulStack+sizeof(pulStack)),
 93   ResetISR, // 1
 94   int_isr,
 95   int_isr,
 96   int_isr,
 97   int_isr,
 98   int_isr,
 99   int_isr,
100   int_isr,
101   int_isr,
102   int_isr,
103   svc_isr,    // 11
104   int_isr,
105   int_isr,
106   pendsv_isr, // 14
107   systick_isr, // 15
108 
109   // External Interrupts
110   wwdg_isr,                   // Window WatchDog
111   pvd_isr,                   // PVD through EXTI Line detection                      
112   tamp_stamp_isr,            // Tamper and TimeStamps through the EXTI line
113   rtc_wkup_isr,              // RTC Wakeup through the EXTI line                     
114   flash_isr,                 // FLASH                                           
115   rcc_isr,                   // RCC                                             
116   exti0_isr                  // EXTI Line0 
117 };
118 
119 #endif

把所有的努力集合起來就是: 設定好相關暫存器, 確定外部中斷號碼, 建立相關 isr, 搞定。

在 user button 按下的時候, 就會跳到 exti0_isr 執行。

我在完成這些事情之後, 測試之下, 按下 user button, 竟然會一直跳到 exti0_isr, 就差一點了, 程式就可以正常執行, 實在令人氣惱。

這篇文章幫了我大忙, 卡在 button isr 一直會被觸發, 需要設定 Pending register (EXTI_PR) 才不會一值發動 isr。所以才有 user_button.c L269 這行。

我並非所有的資訊都是來自文件, 有些是直接看程式碼的, 要是每份文件去掃完, 可能現在還在看文件, 這是查詢範例程式碼的優點; 文件/程式碼各有其優缺點, 交互參照可以提升效率。

source code: https://github.com/descent/stm32f4_prog
commit 1f10eecb06dfecc5a1fc0731c30c01ecd10ff58b

ref:

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。