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