# 2019-MPSL Lab3 > 2019 Fall Microprocessor System Lab > Lab3 STM32 GPIO System > [name=0516009 吳宗達、0616015 劉姿利] ## What to Do - 了解 `GPIO` 的基本設定以及電路接法 - 了解 Active Low 電路的實現方法 ## How to Do ### LED Pattern Displayer ```cpp= .syntax unified .cpu cortex-m4 .thumb .data leds: .byte 0 .text .global main .equ RCC_AHB2ENR, 0x4002104C .equ GPIOA_MODER, 0x48000000 .equ GPIOB_MODER, 0x48000400 .equ GPIOC_MODER, 0x48000800 .equ GPIOA_ODR, 0x48000014 .equ GPIOB_ODR, 0x48000414 .equ GPIOC_IDR, 0x48000810 .equ GPIOC_PUPDR, 0x4800080C main: bl GPIO_init ldr R4, =leds movs R3, #1 Loop: bl DisplayLED bl Delay b Loop GPIO_init: ldr R0, =RCC_AHB2ENR mov R1, #0x6 str R1, [R0] ldr R0, =GPIOB_MODER movs R1, #0x1540 ldr R2, [R0] and R2, #0xFFFFC03F orrs R2, R2, R1 str R2, [R0] ldr R0, =GPIOC_MODER //movs R1, #0x4000000 ldr R2, [R0] and R2, #0xF3FFFFFF //orrs R2, R2, R1 str R2, [R0] ldr R0, =GPIOC_PUPDR movs R1, #0x4000000 ldr R2, [R0] and R2, #0xF3FFFFFF orrs R2, R2, R1 str R2, [R0] ldr R0, =GPIOB_ODR ldr R5, =GPIOC_IDR mov R7, #0 mov R8, #1 //ldr R0, =GPIOB_ODR //movs R1, #0x78 //strh R1, [R0] bx LR // R3: state // R1 ldrb ?? DisplayLED: mov R2, #0x3 ldrb R1, [R4] // R1 = leds lsl R2, R1 lsr R2, #1 lsl R2, #3 mvn R2, R2 strh R2, [R0] cmp R3, #1 ite eq addeq R1, #1 subne R1, #1 strb R1, [R4] cmp R1, #0 it eq moveq R3, #1 cmp R1, #4 it eq moveq R3, #2 bx LR // R5: idr_address // R6: input data // R7: count // R8: state // R9: -1 Delay: mov R2, #(1<<18) L: ldr R6, [R5] asr R6, #13 and R6, #1 cmp R6, #0 beq Zero One: cmp R7, #(1<<13) mov R7, #0 bgt Trigger blt NotChange Zero: add R7, #1 b NotChange Trigger: eor R8, #1 b NotChange NotChange: cmp R8, #0 it ne subne R2, #1 cmp R2, #0 bne L bx LR ``` #### GPIO Configuration - Enable `GPIOB/C` Through `AHB2` ![](https://i.imgur.com/Ltpm4YE.png) 想要 Enable `GPIOB/C` 需要將 Register `RCC_AHB2ENR` 上的 bit1 和 bit2 的位置設為 1 ```cpp ldr R0, =RCC_AHB2ENR mov R1, #0x6 str R1, [R0] ``` #### GPIO Configuration - `GPIOB` Mode 想要將 `PB3`、`PB4`、`PB5`、`PB6` 設定為 Output Mode 需要去改 Register `GPIOB_Moder`。 每個 Port 會被分到 2 個 bit - `00`: Input mode - `01`: General purpose output mode - `10`: Alternate function mode - `11`: Analog mode (reset state) 所以要將 Register `GPIOB_Moder` 設定成以下的樣子。 ```cpp // GPIOB_MODER Port 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Mode x x x x x x x x x x x x x x x x x x 0 1 0 1 0 1 0 1 x x x x x x ``` 實作上我們使用了兩個 Masks 還有 bitwise operations。 ```cpp ldr R0, =GPIOB_MODER movs R1, #0x1540 // Mask1 ldr R2, [R0] and R2, #0xFFFFC03F // Mask2 orrs R2, R2, R1 str R2, [R0] ``` #### GPIO Configuration - `GPIOC` Mode and Pull Up Setting 同 `GPIOB` Mode,不過這次是要將 `PC13` 設定為 Input Mode ```cpp // GPIOC_MODER Port 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Mode x x x x 0 0 x x x x x x x x x x x x x x x x x x x x x x x x x x ``` - Set Pull Up 因為 spec 中有提到要將 User Button 設為 Pull Up, 所以還要改動 Register `GPIOC_PUPDR` ```cpp ldr R0, =GPIOC_PUPDR movs R1, #0x4000000 ldr R2, [R0] and R2, #0xF3FFFFFF orrs R2, R2, R1 str R2, [R0] ``` #### `GPIOB` Output Data (Turn On/Off the LED) 輸出 Port 現在要輸出什麼電位是從 Register `GPIOB_ODR` 靠近低位的 half word 控制。 如果要讓 LED 全亮就要將`GPIOB_ODR` bit3 ~ bit6 都設為 1。 ```cpp // GPIOB_ODR (half word) Port 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 Data x x x x x x x x x 1 1 1 1 x x x ``` 在實作中我們將想要設定的 pattern 存在 `R2` 中,再 `strh` 到 `GPIOB_ODR` 裡面。 ```cpp ldr R0, =GPIOB_ODR // do something to change LED pattern strh R2, [R0] ``` #### `GPIOC` Input Data (User Button) User Button,也就是 `PC13` 讀到的東西會存進 Register `GPIOC_IDR` 的 bit13 中。 ```cpp ldr R5, =GPIOC_IDR ldr R6, [R5] asr R6, #13 and R6, #1 ``` - Solution to Button Bounce Problem - 我們用一個 Register `R7` 去紀錄目前連續看到幾個 0(User Button 被按下去的狀態) - 在看到 1 的時候去看 `R7` 的值是多少,如果超過 $2^{13}$ 的話才切換 LED 跑馬燈的狀態 #### Hardware Layout ![](https://i.imgur.com/6E9SwUA.png) #### Active Low 硬體接線 ![](https://i.imgur.com/gho99EX.jpg) - 紅:`PB3` - 橘:`PB4` - 黃:`PB5` - 綠:`PB6` - 棕:`3V3` 其中棕色的線不是接在 `GND` 而是 `3V3` 的高電位,代表 `PB`s 要輸出低電位才能產生電位差讓燈泡亮起來。 ### Combination Lock ```cpp= .syntax unified .cpu cortex-m4 .thumb .data .text .global main .equ RCC_AHB2ENR, 0x4002104C .equ GPIOB_MODER, 0x48000400 .equ GPIOC_MODER, 0x48000800 .equ GPIOA_ODR, 0x48000014 .equ GPIOB_ODR, 0x48000414 .equ GPIOB_IDR, 0x48000410 .equ GPIOC_IDR, 0x48000810 .equ GPIOC_PUPDR, 0x4800080C .equ password, 0xC main: bl GPIO_init Start: mov R7, #0 bl Read_Button bl Check_Password //ldr R0, =GPIOB_IDR //LL: // ldr R1, [R0] // b LL GPIO_init: ldr R0, =RCC_AHB2ENR mov R1, #0x6 str R1, [R0] ldr R0, =GPIOB_MODER movs R1, #0x400 ldr R2, [R0] // 0xFFFFF3C0 //and R2, #0xFFFFF3FF // 0xF3FFFC00 //and R2, #0xFFFFFFC0 mov R2, #0 orrs R2, R2, R1 str R2, [R0] ldr R0, =GPIOC_MODER ldr R2, [R0] and R2, #0xF3FFFFFF str R2, [R0] ldr R0, =GPIOC_PUPDR movs R1, #0x4000000 ldr R2, [R0] and R2, #0xF3FFFFFF orrs R2, R2, R1 str R2, [R0] mov R7, #0 ldr R5, =GPIOC_IDR bx LR Read_Button: ldr R6, [R5] asr R6, #13 and R6, #1 cmp R6, #0 beq Zero One: cmp R7, #(1<<13) mov R7, #0 bgt Trigger b Read_Button Zero: add R7, #1 b Read_Button Trigger: bx LR Check_Password: ldr R0, =GPIOB_IDR ldr R6, [R0] mov R7, #0 // 1, 2 mov R8, R6 and R8, #6 asr R8, #1 eor R7, R8 // 11, 12 mov R8, R6 and R8, #6144 asr R8, #9 eor R7, R8 eor R7, #password cmp R7, #15 bne Wrong bl light bl light Wrong: bl light b Start light: ldr R0, =GPIOB_ODR // turn on ldr R2, [R0] orr R2, #(1<<5) strh R2, [R0] mov R7, #(1<<18) LoopOn: sub R7, #1 cmp R7, #0 bne LoopOn // turn off ldr R2, [R0] and R2, #0xFFFFFFDF strh R2, [R0] mov R7, #(1<<18) LoopOff: sub R7, #1 cmp R7, #0 bne LoopOff bx LR L: b L ``` ### Step 1 GPIO_init 同時用到 GPIOB 與 GPIOC 所以先將 register RCC_AHB2ENR 上的 bit1 和 bit2 的位置設為 1 ```c++ ldr R0, =RCC_AHB2ENR mov R1, #0x6 str R1, [R0] ``` - GPIOB - 用 PB5 進行輸出 - PB1, PB2, PB11, PB22 用來當密碼輸入 ``` ldr R0, =GPIOB_MODER movs R1, #0x400 ldr R2, [R0] mov R2, #0 orrs R2, R2, R1 str R2, [R0] ``` - GPIOC - 只有用到 PC13 的按鈕當作 input ``` ldr R0, =GPIOC_MODER ldr R2, [R0] and R2, #0xF3FFFFFF str R2, [R0] ``` ### Step 2 Read_Button 與前一題相同,為了避免 Buttom Bounce Problem,我們會用一個 counter 紀錄目前連續讀到幾個 0 (按鈕按下),當發現讀入的變成 1 時 counter 必須要大於 $2^13$ 我們才會當作有按下,這樣就可以解決 Buttom Bounce Problem 當我們確定的按鈕被按了一下,進入 `Step 3` ### Step 3 Read & Check password 將 PB1, PB2, PB11, PB12 的 bit 轉成整數整數表示的 password ``` // 1, 2 mov R8, R6 and R8, #6 asr R8, #1 eor R7, R8 // 11, 12 mov R8, R6 and R8, #6144 asr R8, #9 eor R7, R8 ``` 正確就執行亮燈三次,否則略過兩次 ``` eor R7, #password cmp R7, #15 bne Wrong bl light bl light Wrong: bl light b Start ``` ### Active low 一開始我們是做成 Active high, 直接用電路有通或是沒通來當作 1/0。 為了改成 Active low 我們先實驗了 1. 將 5v 接到 PB1, 這時顯示的是 1 2. 並聯另一個線路走到 GND, 這時 PB1 會顯示 0 上面這個效果也就是我們要的 Active low 藉著就是接上 DIP 開關, 讓開關打開了時候可以產生一條並聯走到 GND 的線路, 沒打開的時候會直接走到 PBx, 也就是上面的實驗, 接著做四個一樣的東西分別接到 PB1,2,11,12 就完成了/ ### 線路本尊 ![](https://i.imgur.com/9TmMStR.jpg) ![](https://i.imgur.com/zNUHi6x.jpg) ![](https://i.imgur.com/z6mrLCt.jpg) ![](https://i.imgur.com/03NmXxd.jpg) ## Feedback 第一次寫 code 直接控制到實際會動的機器覺得很新鮮,也複習到個國中的基本電路的觀(? 只用了短短幾行的 code 就可以控制 GPIO,讓人體會到原來寫 code 距離實體的機器其實沒有離很遠 另外寫組語愈來愈有種在寫自動機的感覺, 寫多個條件變得好麻煩, 現在覺得很感激 compiler。 還有學到不要用 LED 充當杜邦線 在 debug 的過程中, 學到不要一次把全部都拼出來, 這樣很難找到錯誤, 這時就可以用到資工人 unit test 的精神, 一步一步確定每一個環節沒有問題, 看似浪費時間實際上減少了很多 debug 找問題的時間 ## Reference - [UM1724 STM32 NUCLEO User Manual](https://hackmd.io/@REIq7VP9RZq5oslp3_iPcA/B1TbUB5BV/https%3A%2F%2Fstorage.googleapis.com%2F2019_mpsl%2Freference%2FUM1724-STM32-NUCLEO-User-Manual.pdf) p35 Figure 24. NUCLEO-L476RG - 板子上的接口名稱 - How to turn on single LED (Lecture Slide 005-MCSL-GPIO.pdf p31) - 控制 PA5 的 Sample Code (PA5就是GPIOA的第5個bit在控制的) - [STM32L476](https://www.st.com/content/ccc/resource/technical/document/reference_manual/02/35/09/0c/4f/f7/40/03/DM00083560.pdf/files/DM00083560.pdf/jcr:content/translations/en.DM00083560.pdf) - AHB2 - ==p77== Table1 各種GPIO的address - ==p242== 6.4.11 AHB2 peripheral reset register (RCC_AHB2RSTR) - AHB2 設定 - GPIO - ==p311== 8.4.13 GPIO register map - ==p303== 8.4.1 GPIO port mode register (GPIOx_MODER) (x =A to I) - 設定某個 port 是 input 還是 output - ==p305== 8.4.6 GPIO port output data register (GPIOx_ODR) (x = A to I) - port 的開關 - [成大資工wiki](http://wiki.csie.ncku.edu.tw/embedded/GPIO)