---
# System prepended metadata

title: 2019-MPSL Lab7

---

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