# 2019-MPSL Lab7 > 2019 Fall Microprocessor System Lab > Lab3 STM32 GPIO System > [name=0516009 吳宗達、0616015 劉姿利] ## What to Do - 了解如何使用 Systick 以及其 interrupt - 了解外部訊號如何啟動 interrupt ## How to Do ### 7-1 SysTick timer interrupt setting > 設定 clock source 為 HSI 的 SysTick timer。並利用 SysTick timer 中斷機制,控制 LED 使它暗 3 秒,亮 3 秒 (SysTick 3 秒中斷一次)。 ```c= #include "stm32l476xx.h" #include "core_cm4.h" void SysTick_Handler(void) { GPIOA->ODR ^= 1 << 5; } GPIO_TypeDef* GPIO[16] = {[0xA]=GPIOA, [0xB]=GPIOB, [0xC]=GPIOC}; void set_moder(int addr, int mode) { // mode: 0 input, 1 output int x = addr >> 4, k = addr & 0xF; RCC->AHB2ENR |= 1<<(x-10); GPIO[x]->MODER &= ~(3 << (2*k)); GPIO[x]->MODER |= (mode << (2*k)); if (mode == 0) { GPIO[x]->PUPDR &= ~(3 << (2*k)); GPIO[x]->PUPDR |= (2 << (2*k)); } } void set_clock() { // Set system clock as MSI RCC->CFGR &= ~3; // HPRE -> 1MHz RCC->CFGR &= ~(0xF << 4); RCC->CFGR |= 11 << 4; // enable HSION RCC->CR |= 1 << 8; // Set system clock as HSI16 RCC->CFGR |= 1; } void systick_config() { SysTick->CTRL |= 7; SysTick->LOAD = 3000000; } void gpio_init() { set_moder(0xA5, 1); } // freq = freq * N / M / R // 2MHz = 16MHZ * 8 / 8 / 8 int main() { gpio_init(); set_clock(); systick_config(); while (1); } ``` #### SysTick Configuration - 將 clock source 設定成 Processor clock (System Clock 是從 HSI 來, 並且轉成 1MHz) - 設定 SysTick 倒數到 0 時會產生 assert interrupt - 將 counter enable - 設定 reload value = 3M, 也就是 3秒會觸發一次 #### SysTick_Handler - SysTick_Handler 是在 vector table 中的關鍵字, 當 SysTick trigger 時會呼叫這個函式 - 每當 SysTick_Handler 被呼叫時,就改變燈泡的狀態 ### 7-2 Multiple External Interrupt setting > 設定 keypad 的 input 腳為外部中斷的輸入源,當沒按任何按鍵時,一顆 LED 會保持在亮的狀態,當按下按鍵時會觸發中斷,使 LED 會亮、暗各保持 0.5秒為一次閃爍。閃爍次數為 Keypad 對應的次數,閃爍結束後 LED 會回到一開始的亮燈狀態。 ```c= #include "stm32l476xx.h" #include "core_cm4.h" GPIO_TypeDef* GPIO[16] = {[0xA]=GPIOA, [0xB]=GPIOB, [0xC]=GPIOC}; const unsigned int X[4] = {0xA5, 0xA6, 0xA7, 0xB6}; const unsigned int Y[4] = {0xC7, 0xA9, 0xA8, 0xBA}; const char mapping[16] = { 1, 4, 7, 15, 2, 5, 8, 0, 3, 6, 9, 14, 10, 11, 12, 13 }; void set_moder(int addr, int mode) { // mode: 0 input, 1 output, 2 alternate int x = addr >> 4, k = addr & 0xF; RCC->AHB2ENR |= 1<<(x-10); GPIO[x]->MODER &= ~(3 << (2*k)); GPIO[x]->MODER |= (mode << (2*k)); if (mode == 0) { GPIO[x]->PUPDR &= ~(3 << (2*k)); GPIO[x]->PUPDR |= (2 << (2*k)); } } void keypad_init(int * X, int * Y) { for (int i = 0; i < 4; i++) { set_moder(X[i], 1); } for (int i = 0; i < 4; i++) { set_moder(Y[i], 0); } } int keypad_scan() { int result = -1; char nil = 1; for (int i = 0; i < 4; i ++) { int x = X[i] >> 4, k = X[i] & 0xF; GPIO[x]->ODR &= ~(1 << k); } for (int i=0; i<4; i++){ int x = X[i] >> 4, k = X[i] & 0xF; GPIO[x]->ODR &= ~(1 << k); GPIO[x]->ODR |= (1 << k); for (int j=0; j<4; j++){ int y = Y[j] >> 4, l = Y[j] & 0xF; if (GPIO[y]->IDR & (1<<l)){ result = mapping[j*4 + i]; nil = 0; } if (result != -1) break; } GPIO[x]->ODR ^= (1 << k); if (result != -1) break; } if (nil) return -1; return result; } void gpio_init() { set_moder(0xA5, 1); GPIOA->ODR |= (1 << 5); } void EXTI_config(){ RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // set GPIO PC7 PA8 PA9 PB10 as external input SYSCFG->EXTICR[1] |= SYSCFG_EXTICR2_EXTI7_PC; SYSCFG->EXTICR[2] |= SYSCFG_EXTICR3_EXTI8_PA; SYSCFG->EXTICR[2] |= SYSCFG_EXTICR3_EXTI9_PA; SYSCFG->EXTICR[2] |= SYSCFG_EXTICR3_EXTI10_PB; // enable GPIO 7~10 interrupt EXTI->IMR1 |= EXTI_IMR1_IM7; EXTI->IMR1 |= EXTI_IMR1_IM8; EXTI->IMR1 |= EXTI_IMR1_IM9; EXTI->IMR1 |= EXTI_IMR1_IM10; // set GPIO 7~10 as rising trigger EXTI->RTSR1 |= EXTI_RTSR1_RT7; EXTI->RTSR1 |= EXTI_RTSR1_RT8; EXTI->RTSR1 |= EXTI_RTSR1_RT9; EXTI->RTSR1 |= EXTI_RTSR1_RT10; } void NVIC_config(){ NVIC_EnableIRQ(EXTI9_5_IRQn); NVIC_EnableIRQ(EXTI15_10_IRQn); NVIC_SetPriority(EXTI9_5_IRQn, 0); NVIC_SetPriority(EXTI15_10_IRQn, 0); } void delay(){ for (int i=0; i<(1<<17); i++); } void EXTI9_5_IRQHandler(){ if (EXTI->PR1==0) return; int value = keypad_scan(); for (int i = 0; i < 4; i ++) { int x = X[i] >> 4, k = X[i] & 0xF; GPIO[x]->ODR |= (1 << k); } while (value--){ GPIOA->ODR ^= (1<<5); delay(); GPIOA->ODR ^= (1<<5); delay(); } GPIOA->ODR |= (1 << 5); EXTI->PR1 &= ~EXTI_PR1_PIF7; EXTI->PR1 &= ~EXTI_PR1_PIF8; EXTI->PR1 &= ~EXTI_PR1_PIF9; EXTI->PR1 &= ~EXTI_PR1_PIF10; NVIC_ClearPendingIRQ(EXTI9_5_IRQn); } void EXTI15_10_IRQHandler(){ if (EXTI->PR1==0) return; int value = keypad_scan(); for (int i = 0; i < 4; i ++) { int x = X[i] >> 4, k = X[i] & 0xF; GPIO[x]->ODR |= (1 << k); } while (value--){ GPIOA->ODR ^= (1<<5); delay(); GPIOA->ODR ^= (1<<5); delay(); } GPIOA->ODR |= (1 << 5); EXTI->PR1 &= ~EXTI_PR1_PIF7; EXTI->PR1 &= ~EXTI_PR1_PIF8; EXTI->PR1 &= ~EXTI_PR1_PIF9; EXTI->PR1 &= ~EXTI_PR1_PIF10; NVIC_ClearPendingIRQ(EXTI15_10_IRQn); } int main() { gpio_init(); keypad_init(X, Y); NVIC_config(); EXTI_config(); while(1); } ``` #### 修改本來的keypad_scan - 我們本來用的 output pin 是 PA5, PA6, PA7, PB6, input pin 是 PC7, PA9, PA8, PB10, 但是因為 input pin 有數字重複, 而 output pin 沒有, 於是我們就把 input 的 4 個 pin 與 output 的 4 個 pin 對調。 - 另外為了接收 interrupt 訊號所以我們本來把 Output pin 全部都打開, 但是在做 keypad_scan 的時候, 為了正確的讀到按鈕, 根本來不一樣的是我們要再重新將 Output pin 都關掉後才能開始跑 keypad_scan 內的迴圈 #### EXTI_config - enable 需要用到的 pin, EXTI 並設定 rising trigger #### NVIC_config - 將我們需要用到的 EXTI9_5 以及 EXTI15_10 enable - 將 priority 都設為 0 #### EXTIx_IRQHandler - 每次收到 interrupt 後都做一次 keypad_scan, 讓 PA5 閃輸入的數次次 - 在做 keypad_scan 的時候會把 output pin 關掉, 所以 keypad scan 結束後要在這裡把它們打開, 這樣之後才收得到 interrupt ### 7-3 簡單鬧鐘 > 利用 SysTick timer、User button 和蜂鳴器設計一個簡單的鬧鐘,keypad 上每一個按鈕代表設定倒數計時幾秒,當輸入為 0 時則倒數 0 秒即馬上倒數結束。時間輸入完畢後,Systick timer 會開始計時,而當時間到後,使蜂鳴器響起,直到使用者按下 User button 後才會停止發出聲音並回到等待使用者輸入,注意 timer 開始計時到使用者關閉蜂鳴器的期間,keypad 不會有任何作用。 ```c= #include "stm32l476xx.h" #include "core_cm4.h" /* GPIO */ GPIO_TypeDef* GPIO[16] = {[0xA]=GPIOA, [0xB]=GPIOB, [0xC]=GPIOC}; const unsigned int X[4] = {0xA5, 0xA6, 0xA7, 0xB6}; const unsigned int Y[4] = {0xC7, 0xA9, 0xA8, 0xBA}; void read_button() { int cnt = 0; while(1) { int button = GPIOC->IDR & (1 << 13); if (button == 0) { // 0 cnt ++; } else if (cnt > (1 << 13)) { // 1 trigger cnt = 0; // TODO // end alert silent(); // keypad output 1 for (int i = 0; i < 4; i ++) { int x = X[i] >> 4, k = X[i] & 0xF; GPIO[x]->ODR |= (1 << k); } break; } else { // 1 cnt = 0; } } } void set_moder(int addr, int mode) { // mode: 0 input, 1 output int x = addr >> 4, k = addr & 0xF; RCC->AHB2ENR |= 1<<(x-10); GPIO[x]->MODER &= ~(3 << (2*k)); GPIO[x]->MODER |= (mode << (2*k)); if (mode == 0) { GPIO[x]->PUPDR &= ~(3 << (2*k)); GPIO[x]->PUPDR |= (2 << (2*k)); } } void gpio_init() { set_moder(0xA5, 1); set_moder(0xCD, 0); } /* SysTick */ void SysTick_Handler(void) { // TODO systick_disable(); // alert ring(); // read_button read_button(); } void set_clock() { // Set system clock as MSI RCC->CFGR &= ~3; // HPRE -> 1MHz RCC->CFGR &= ~(0xF << 4); RCC->CFGR |= 11 << 4; // enable HSION RCC->CR |= 1 << 8; // Set system clock as PLL RCC->CFGR |= 1; } void systick_config(int value) { // load first SysTick->LOAD = value * 1000000; SysTick->CTRL |= 7; } void systick_disable() { SysTick->CTRL &= ~1; } /* keypad */ const char mapping[16] = { 1, 4, 7, 15, 2, 5, 8, 0, 3, 6, 9, 14, 10, 11, 12, 13 }; void keypad_init(int * X, int * Y) { for (int i = 0; i < 4; i++) { set_moder(X[i], 1); } for (int i = 0; i < 4; i++) { set_moder(Y[i], 0); } } int keypad_scan() { int result = -1; char nil = 1; for (int i = 0; i < 4; i ++) { int x = X[i] >> 4, k = X[i] & 0xF; GPIO[x]->ODR &= ~(1 << k); } for (int i=0; i<4; i++){ int x = X[i] >> 4, k = X[i] & 0xF; GPIO[x]->ODR &= ~(1 << k); GPIO[x]->ODR |= (1 << k); for (int j=0; j<4; j++){ int y = Y[j] >> 4, l = Y[j] & 0xF; if (GPIO[y]->IDR & (1<<l)){ result = mapping[j*4 + i]; nil = 0; } if (result != -1) break; } GPIO[x]->ODR ^= (1 << k); if (result != -1) break; } if (nil) return -1; return result; } /* ring */ void timer_init() { RCC->APB1ENR1 |= 1;//timer1 clock enable TIM2->PSC = (uint32_t)(1000000 / 500) / 100;//Prescalser TIM2->ARR = (uint32_t)100;//Reload value TIM2->EGR = 1;//Reinitialize the counter. CNT takes the auto-reload value. // timer as output compare // cr1 - appe TIM2->CR1 |= (1 << 7); // ccr TIM2->CCR2 = 50; // ccmr - pwn mode TIM2->CCMR1 &= ~(7 << 12); TIM2->CCMR1 |= 6 << 12; // pwm mode 1 TIM2->CCMR1 |= 1 << 11; // oc2pe // ccer - enable output compare TIM2->CCER |= (1 << 4); // dier - cc2ie TIM2->DIER |= (1 << 2) + (1 << 10); TIM2->CR1 |= 1;//start timer // set alternative function for PB3 GPIOB->AFR[0] &= ~(0xF << 12); GPIOB->AFR[0] |= 1 << 12; } void silent() { GPIOB->ODR &= ~(1 << 3); set_moder(0xB3, 1); } void ring() { set_moder(0xB3, 2); } /* interrupt */ void EXTI_config(){ RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // set GPIO PC7 PA8 PA9 PB10 as external input SYSCFG->EXTICR[1] |= SYSCFG_EXTICR2_EXTI7_PC; SYSCFG->EXTICR[2] |= SYSCFG_EXTICR3_EXTI8_PA; SYSCFG->EXTICR[2] |= SYSCFG_EXTICR3_EXTI9_PA; SYSCFG->EXTICR[2] |= SYSCFG_EXTICR3_EXTI10_PB; // enable GPIO 7~10 interrupt EXTI->IMR1 |= EXTI_IMR1_IM7; EXTI->IMR1 |= EXTI_IMR1_IM8; EXTI->IMR1 |= EXTI_IMR1_IM9; EXTI->IMR1 |= EXTI_IMR1_IM10; // set GPIO 7~10 as rising trigger EXTI->RTSR1 |= EXTI_RTSR1_RT7; EXTI->RTSR1 |= EXTI_RTSR1_RT8; EXTI->RTSR1 |= EXTI_RTSR1_RT9; EXTI->RTSR1 |= EXTI_RTSR1_RT10; } void NVIC_config(){ NVIC_EnableIRQ(EXTI9_5_IRQn); NVIC_EnableIRQ(EXTI15_10_IRQn); NVIC_SetPriority(EXTI9_5_IRQn, 0); NVIC_SetPriority(EXTI15_10_IRQn, 0); } void EXTI9_5_IRQHandler(){ if (EXTI->PR1==0) return; // TODO // read int value = keypad_scan(); // clear keypad output for (int i = 0; i < 4; i ++) { int x = X[i] >> 4, k = X[i] & 0xF; GPIO[x]->ODR &= ~(1 << k); } // enable systick systick_config(value); EXTI->PR1 &= ~EXTI_PR1_PIF7; EXTI->PR1 &= ~EXTI_PR1_PIF8; EXTI->PR1 &= ~EXTI_PR1_PIF9; EXTI->PR1 &= ~EXTI_PR1_PIF10; NVIC_ClearPendingIRQ(EXTI9_5_IRQn); } void EXTI15_10_IRQHandler(){ if (EXTI->PR1==0) return; // TODO // read int value = keypad_scan(); // clear keypad output for (int i = 0; i < 4; i ++) { int x = X[i] >> 4, k = X[i] & 0xF; GPIO[x]->ODR &= ~(1 << k); } // enable systick systick_config(value); EXTI->PR1 &= ~EXTI_PR1_PIF7; EXTI->PR1 &= ~EXTI_PR1_PIF8; EXTI->PR1 &= ~EXTI_PR1_PIF9; EXTI->PR1 &= ~EXTI_PR1_PIF10; NVIC_ClearPendingIRQ(EXTI15_10_IRQn); } int main() { gpio_init(); keypad_init(X, Y); NVIC_config(); EXTI_config(); set_clock(); timer_init(); silent(); for (int i = 0; i < 4; i ++) { int x = X[i] >> 4, k = X[i] & 0xF; GPIO[x]->ODR |= (1 << k); } while (1); } ``` - 基本上每個部分都是用之前寫的功能組合出來的 ## Feedback 利用 interrup 我們可以在有需要的事件發生時再去做事情,可以大量減少沒有用的無窮迴圈,覺得對於撰寫第耗電的程式十分有用,而且因為類似有多個 thread 所以能將不同功能獨立開寫,讓程式的架構簡化了不少。