# 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 所以能將不同功能獨立開寫,讓程式的架構簡化了不少。