# 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

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

從 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

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,非常方便