---
# System prepended metadata

title: 2019-MPSL Lab6

---

# 2019-MPSL Lab6

## What to Do

## How to Do

### 6-1 Modify System Initial Clock

> 讓 LED 以 4MHz 的頻率閃爍
> 按下 User Button 改變 System Clock 頻率
> 1MHz -> 6MHz -> 10MHz -> 16MHz -> 40MHz -> 1MHz -> ...

```cpp=
#include "stm32l476xx.h"

GPIO_TypeDef* GPIO[16] = {[0xA]=GPIOA, [0xB]=GPIOB, [0xC]=GPIOC};
int PLL_NMR[5][3] = {
	{ 8, 3, 3}, //  1
	{ 9, 0, 2}, //  6
	{10, 0, 1}, // 10
	{ 8, 0, 0}, // 16
	{20, 0, 0}  // 40
};
int freq_idx = 0; // which frequency

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(int N, int M, int R) {
	// Set system clock as MSI
	RCC->CFGR &= ~3;

	// disable PLLON
	RCC->CR &= ~(1 << 24);
	// wait until PLLRDY is cleared
	while (RCC->CR & (1 << 25));

	// Change parameters

	// PLLsrc
	RCC->PLLCFGR &= ~3;
	RCC->PLLCFGR |= 1;

	// PLLN
	RCC->PLLCFGR &= ~(0x7F << 8);
	RCC->PLLCFGR |= N << 8;
	// PLLM
	RCC->PLLCFGR &= ~(0x7 << 4);
	RCC->PLLCFGR |= M << 4;
	// PLLR
	RCC->PLLCFGR &= ~(0x3 << 25);
	RCC->PLLCFGR |= R << 25;

	// Enable PLLON
	RCC->CR |= 1 << 24;
	while (RCC->CR & (1 << 25) == 0);

	// Enable PLLREN (PLLCFGR)
	RCC->PLLCFGR |= 1 << 24;

	// Set system clock as PLL
	RCC->CFGR |= 3;
}

void delay4Mclocks() {
	int n = 95000;
	int cnt = 0;
	while(n--) {
		int button = GPIOC->IDR & (1 << 13);
		if (button == 0) {
			cnt ++;
		} else if (cnt > (1 << 13)) {
			cnt = 0;
			int N = PLL_NMR[freq_idx][0];
			int M = PLL_NMR[freq_idx][1];
			int R = PLL_NMR[freq_idx][2];
			set_clock(N, M, R);
			freq_idx = (freq_idx + 1) % 5;
		} else {
			cnt = 0;
		}
	}
}

void gpio_init() {
	set_moder(0xA5, 1);
	set_moder(0xCD, 0);
}

int main() {
	gpio_init();
	int N = PLL_NMR[freq_idx][0];
	int M = PLL_NMR[freq_idx][1];
	int R = PLL_NMR[freq_idx][2];
	set_clock(N, M, R);
	freq_idx = (freq_idx + 1) % 5;
	while (1) {
		GPIOA->ODR ^= (1 << 5); // switch A5 LED
		delay4Mclocks();
	}
}
```

#### System Clock Configuration
![](https://i.imgur.com/w5GNy9B.jpg)

stm32 的 clock 主要有4種(HSE, MSI, HSI, PLL)，其中系統預設值是 MSI，且預設頻率為 4MHz。當我們想要切換到 PLL 並改變頻率時
1. 首先設定 RCC_CFGR register 將 SYSCLK 先改回 MSI, 這樣我們才能對 PLL 進行修改
2. 設定 RCC_CR register 將 PLLON 設為關閉
3. 設定 PLL 的我 N, M, R 來改變輸出頻率
4. 設定 RCC_CR register 將 PLLON 設為開啟
5. 設定 RCC_CFGR register 將 SYSCLK 改成使用 PLL

#### PLL Frequency Computation
> 6.4.4 PLL configuration register (RCC_PLLCFGR)

在 Clock Tree 的圖上可以看到，PLL_R 是我們最後要輸出的位置，根據 reference 所附的公式
$$
f(\text{PLL_R}) = \frac{ f(\text{PLL clock input}) \times \text{PLLN} }{ \text{PLLM} \times \text{PLLR} }
$$
其中，我們用的 input 是預設的 MSI，所以頻率是 4MHz

另外 PLLN, PLLM, PLLR 可以設定的數值分別有
- PLLN = 8~86
- PLLM = 1~8
- PLLR = {2, 4, 6, 8}

可以利用 python 枚舉出所有可能，找出我們需要的輸出頻率

### 6-2 Timer Counter

> 設定 TIME_SEC
> 以 7-SEG 顯示從 0 upcounting TIME_SEC 的時間
> 顯示到小數點以下第二位，結束時要停留在 TIME_SEC 的數字

```c=
#include "stm32l476xx.h"

extern void max7219_init();
extern void max7219_send(unsigned char, unsigned char);

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 gpio_init() {
	set_moder(0xA5, 1);
	set_moder(0xB3, 1);
	set_moder(0xB4, 1);
	set_moder(0xB5, 1);
	max7219_init();
}

int display(int data) {
	int num_digs = 1;
	const int base = 10;
	for (int i = 0; i < 8; i ++) {
		int d = data % base;
		if (num_digs == 3) d = d | (1 << 7);
		max7219_send(num_digs, d);
		if (data) num_digs ++;
		data /= base;
	}
	int limit = num_digs - 2;
	if (limit < 2) limit = 2;
	max7219_send(0xb, limit);
	return 0;
}

void timer_init() {
	RCC->APB1ENR1 |= (1 << 3);//timer1 clock enable
	TIM5->PSC = (uint32_t)40000;//Prescalser
	TIM5->ARR = (uint32_t)500;//Reload value
	TIM5->EGR = 1;//Reinitialize the counter. CNT takes the auto-reload value.
	TIM5->CR1 |= (1 << 3);
	TIM5->CR1 |= 1;//start timer
}

int main() {
	gpio_init();
	timer_init();

	while(1){
		int timerValue = TIM5->CNT;
		display(timerValue);
	}
}

```

#### Timer Configuration
1. 在 RCC_APB1ENR1 register 將 TIM5 enable
2. 設定每 40000 個 system clock cycle (0.01 秒) 更新一次
3. TIM5_ARR 設定計時上限
4. 在 TIM5_CR1 中的設定 OPN (one-pulse mode) 這樣時間數到上限就會自動停下來

### 6-3/4 Music Keypad

> 6-3
> 利用 timer 產生並輸出 duty cycle 為 50% 的 PWM 訊號
> 按下對應的 keypad 要產生對應的音高

> 6-4
> 新增兩個按鈕調整 duty cycle
> 將蜂鳴器換成 LED 看亮度

```cpp=
#include "stm32l476xx.h"


GPIO_TypeDef* GPIO[16] = {[0xA]=GPIOA, [0xB]=GPIOB, [0xC]=GPIOC};
const unsigned int X[4] = {0xC7, 0xA9, 0xA8, 0xBA};
const unsigned int Y[4] = {0xA5, 0xA6, 0xA7, 0xB6};

const char mapping[16] = {
	1, 2, 3, 10,
	4, 5, 6, 11,
	7, 8, 9, 12,
	15, 0, 14, 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(const char *mapping, const unsigned int *X, const unsigned int *Y) {

	int result = 0;
	char nil = 1;

	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;
			}
		}
		GPIO[x]->ODR ^= (1 << k);
	}
	if (nil) return -1;
	return  result;
}

void gpio_init() {
	set_moder(0xA5, 1);
	set_moder(0xB3, 2);
	GPIOB->ODR &= ~(1 << 3);
}

void timer_init() {
	RCC->APB1ENR1 |= 1;//timer1 clock enable
	TIM2->PSC = (uint32_t)(4000000 / 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 change_freq(int freq) {
	TIM2->CR1 &= ~1;//stop timer
	TIM2->PSC = (uint32_t)(4000000 / freq) / 100;//Prescalser
	TIM2->CR1 |= 1;//start timer
}

void silent() {
	set_moder(0xB3, 1);
}

int main() {
	gpio_init();
	timer_init();
	keypad_init(X, Y);

	double keys[8] = {261.6, 293.7, 329.6, 349.2, 392.0, 440.0, 493.9, 523.3};

	// change_freq(keys[0]);

	//while (1);

	while (1) {
		int key = keypad_scan(mapping, X, Y);

		int t = 50000;
		while (t --);

		if (key == 10 && TIM2->CCR2 < 90)
			TIM2->CCR2 += 5;

		if (key == 11 && TIM2->CCR2 > 10)
			TIM2->CCR2 -= 5;

		if (key < 1 || key > 8) {
			silent();
			continue;
		}

		key --;

		change_freq(keys[key]);
		set_moder(0xB3, 2);
	}
}

```

### PB3 as Alternate Function Mode

為了將 timer 從 `PB3` 直接輸出，我們需要將 `PB3` 設為 alternate function mode

![](https://i.imgur.com/RAyTxfx.png)

從 datasheets 中的表格可以知道 `PB3` 的 `AF2` 是 `TIM2_CH2`，也就是 `TIM2` 的 output channel 2。

```cpp
set_moder(0xB3, 2);
```

將 `PB3` 設為 alternate function mode

```cpp
GPIOB->AFR[0] &= ~(0xF << 12);
GPIOB->AFR[0] |= 1 << 12;
```

在 `GPIOB_AFRL` 中將 `PB3` 的 alternate function 設為 `AF2` 的 `TIM2_CH2`。

#### Output Compare Mode

![](https://i.imgur.com/53H8m3W.png)


Timer 的 output compare mode 可以透過 `TIMx_CNT` 和一些其他設定，將想要 output 的值導到 port 上。

以下是一些需要設定的 Registers
- `TIMx_ARR`: 設定 auto reload value
- `TIMx_CCRx`: `TIMx_CNT` 比較的對象
- `TIMx_CCMRx`: output compare 模式
    - `OCxM`: output compare mode
        - 這裡我們設定為 `0110`: PWM mode 1 - In upcounting, channel 1 is active as long as `TIMx_CNT`<`TIMx_CCR1` else inactive.
    - `OCxPE`: output compare preload enable
- `TIMx_CCER`: output compare enable

```cpp
// timer as output compare
// 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);
```

#### PWN Mode

在 `TIM2_CCMR1` 中我們將 `OC2M` 設為 `0110`: PWM mode 1 - In upcounting, channel is active as long as `TIMx_CNT`<`TIMx_CCR1` else inactive.

並將輸出導到 `PB3` ，代表當 `TIM2_CNT` 小於 `TIM2_CCR` 時 `PB3` 會輸出 `1`，大於的時後則會輸出 `0`。

為了達到 50% duty cycle，我們要將 `TIM2_CCR2` 設為 `TIM2_ARR` 的一半。

```cpp
TIM2->ARR = 100;
TIM2->CCR2 = 50;
```

至於調整 duty cycle percentage 成其他值就直接動 `TIM2_CRR2` 就可以了。

```cpp
if (key == 10 && TIM2->CCR2 < 90)
	TIM2->CCR2 += 5;

if (key == 11 && TIM2->CCR2 > 10)
	TIM2->CCR2 -= 5;
```

## Feedback

覺得有 timer 就可以有更多實際的功能，相當於有好多個 thread 的感覺
比如 delay 就不用寫個 for 迴圈可以等待 cnt，非常方便