# STM32 ###### STM32 ## 環境建製 * 本環境建制參考 [ARM 開發筆記](https://hackmd.io/@YW0330/r1KNL4NEj/%2FMfVAPNatR8OW0J9B9Pj3yA) ### STM32CubeIDE 首先找到以下網站並選擇要使用的版本,這邊以 Linux 做舉例 * [Download STM32CubeIDE](https://www.st.com/en/development-tools/stm32cubeide.html) ![](https://hackmd.io/_uploads/BJppj7P16.png) 下載完後解壓縮檔案到只有下面的 `.sh` 檔 ```shell $ ls -l 總用量 2317796 ... -rwxrwxr-x 1 fewletter fewletter 1109998056 七 28 11:40 st-stm32cubeide_1.13.1_17479_20230728_0839_amd64.sh ``` 然後將其改變權限成可執行並且執行它 ```shell $ sudo chmod +x st-stm32cubeide_1.13.1_17479_20230728_0839_amd64.sh $ sudo ./st-stm32cubeide_1.13.1_17479_20230728_0839_amd64.sh ``` :::success 在安裝時只須一直按空白鍵並且按下 y 即可將 IDE 安裝在預設路徑 /opt/... ::: 安裝成功後在全部搜尋中打 `st` 即可找到 STM32CubeIDE。 :::info ![](https://hackmd.io/_uploads/S1yHC7wJ6.png) ::: ### 解決 `arm-none-eabi-gdb` 命令無法找到 如果是下載 STM32CubeIDE 的話那理論上 `arm-none-eabi-gdb` 也會被下載到 `/usr/bin/` 中,若出現 `arm-none-eabi-gdb` 無法被找到可參考以下步驟手動將 `arm-none-eabi-gdb` 加入至 `usr/bin` 中 * 最一開始如果遇到此 error 請先嘗試安裝相依套件 ```shell arm-none-eabi-gdb: /usr/lib/libncurses.so.5: version `NCURSES_5.3.20021019’ not found (required by arm-none-eabi-gdb) arm-none-eabi-gdb: /usr/lib/libncurses.so.5: version `NCURSES_5.1.20000708’ not found (required by arm-none-eabi-gdb) arm-none-eabi-gdb: /usr/lib/libncurses.so.5: version `NCURSES_5.6.20061217’ not found (required by arm-none-eabi-gdb) arm-none-eabi-gdb: /usr/lib/libncurses.so.5: version `NCURSES_5.0.19991023’ not found (required by arm-none-eabi-gdb) ``` 安裝相依套件 ``` $ sudo apt install libncurses-dev ``` 若成功以下接不需要嘗試,還是出現 error 請繼續以下步驟 * 先移除安裝在此電腦下的 `gcc-arm-none-eabi` ```shell $ sudo apt remove gcc-arm-none-eabi ``` * 在[官網 Arm Developer](https://developer.arm.com/downloads/-/gnu-rm) 找到合適版本下載並解壓縮到 `/usr/share/` ```shell $ sudo tar xjf gcc-arm-none-eabi-YOUR-VERSION.bz2 -C /usr/share/ ``` * 將 `/usr/bin/` 當中的檔案連結到 `/usr/share/` 中 ```shell $ sudo ln -s /usr/share/gcc-arm-none-eabi-YOUR-VERSION/bin/arm-none-eabi-gcc /usr/bin/arm-none-eabi-gcc $ sudo ln -s /usr/share/gcc-arm-none-eabi-YOUR-VERSION/bin/arm-none-eabi-g++ /usr/bin/arm-none-eabi-g++ $ sudo ln -s /usr/share/gcc-arm-none-eabi-YOUR-VERSION/bin/arm-none-eabi-gdb /usr/bin/arm-none-eabi-gdb $ sudo ln -s /usr/share/gcc-arm-none-eabi-YOUR-VERSION/bin/arm-none-eabi-size /usr/bin/arm-none-eabi-size $ sudo ln -s /usr/share/gcc-arm-none-eabi-YOUR-VERSION/bin/arm-none-eabi-objcopy /usr/bin/arm-none-eabi-objcopy ``` :::success 結果會像這樣 ![](https://hackmd.io/_uploads/ryZOhmiJT.png) ::: * 最後下載相依套件 ```shell $ sudo apt install libncurses-dev ``` * 執行以下指令確認是否正常運作 ```shell $ arm-none-eabi-gcc --version $ arm-none-eabi-g++ --version $ arm-none-eabi-gdb --version $ arm-none-eabi-size --version ``` > [How can I install gdb-arm-none-eabi on ubuntu 18.04](https://askubuntu.com/questions/1031103/how-can-i-install-gdb-arm-none-eabi-on-ubuntu-18-04-bionic-beaver) > [How To Install arm-none-eabi-gdb on Ubuntu 20.04 LTS](https://devicetests.com/install-arm-none-eabi-gdb-ubuntu) > [Problem with debugging programs in STM32CubeIDE](https://forum.manjaro.org/t/problem-with-debugging-programs-in-stm32cubeide/107088) ## STM32 讀取 mpu6050 ### 了解 I2C * [I2C 通訊協議:瞭解I2C Primer、 PMBus和SMBus](https://www.analog.com/cn/design-center/landing-pages/002/tech-articles-taiwan/i2c-communication-protocol-understanding-i2c-primer-pmbus-and-smbus.html) I²C僅使用兩個雙向開漏線,串列資料線(SDA)和串列時鐘線(SCL),以及上拉電阻。使用的典型電壓是+5 V或+3.3 V(雖然其他電壓系統也是允許的)。 在I²C參考設計中,使用7或10位(取決於所使用的裝置)位址空間。普通I²C匯流排速度為100 kbit / s的標準模式和10 kbit / s的低速模式,但任意低時脈頻率也是允許的。 I²C的最新修訂可以承載更多的節點,並以更快的速度執行[b]。這些速度被更廣泛地使用在嵌入式系統中而不是PC上。I²C也有其他的特性,例如16位元定址。 ### 閱讀 STM32 和 MPU6050 datasheet * [STM32 datasheet](https://www.st.com/resource/en/user_manual/um1472-discovery-kit-with-stm32f407vg-mcu-stmicroelectronics.pdf) * [MPU6050 datasheet](https://www.alldatasheet.com/datasheet-pdf/pdf/1132807/TDK/MPU-6050.html) 從 STM32 datasheet 中可以看到 PB6,PB8 和 PB7,PB9 為 I2C1_SCL 和 I2C2_SDA 兩個皆為 I2C 通訊協議的通道,而 MPU6050 通過 I2C 的通訊協議來傳遞資料,從這邊可以理解到,要擷取 MPU6050 的資料,必須要通過以上的 pin 腳。 ![](https://hackmd.io/_uploads/SJGtEsyMp.png) ![](https://hackmd.io/_uploads/rkKFEjyG6.png) ## STM32 操控電動滑板車速度 > [電動滑板車](https://store.segway.com/segway-ninebot-kickscooter-es4) ## STM32 連接 Inertialense(IMU) > [InertialSense](https://inertialsense.com/) ### Inertialense(IMU) 基本介紹 IMX-5™ 是一個 10-DOF 感測器模組,由慣性測量單元 (IMU)、磁力計和氣壓計組成。輸出包括角速率、線性加速度、磁向量以及氣壓和高度。 IMU 校準包括偏壓、比例因子、交叉軸對準和溫度補償。 IMX-5 包括姿態航向參考系統 (AHRS) 感測器融合,用於估計橫滾(roll)、俯仰(yaw)和航向(pitch)。將 GNSS 輸入新增至 IMX-5 可實現機載慣性導航系統 (INS) 感測器融合,用於橫滾、俯仰、航向、速度和位置。 以下為其應用: * 無人機導航 * 無人駕駛車輛酬載 * 需穩定性的平台 * 天線和攝影機指向 * 急救人員和人員追蹤 * 行人和汽車室外/室內導航 * 健康、健身和運動監測器 * 手持裝置 * 機器人和地面車輛 ### 如何使用? > [Evatool-Windows](https://docs.inertialsense.com/user-manual/software/evaltool/) [CLtool-Linux](https://docs.inertialsense.com/user-manual/software/cltool/) [SDK](https://docs.inertialsense.com/user-manual/software/SDK/) 筆者所使用的開發板為 EVB-2 ,在上方的三個連結則是可以設定此開發板,並根據**設定**獲得數據,但是從前面兩個軟體中不能獲得即時數據並且傳輸出來,所以就須進行式下一步,從開發板上直接獲取數據。 :::success ***設定*** 簡單來說就是設定資料輸出形式和 UART * 資料輸出形式 * NEMA $\to$ 人較為看得懂,但傳輸麻煩 * Binary $\to$ 人較為看不懂,但傳輸較為簡單並可直接使用 SDK 中的 api * UART * 基本的鮑率一定要設定 * 從哪個 Serial port 中輸出也要設定 ::: [DID_PIMU](https://docs.inertialsense.com/user-manual/com-protocol/nmea/#pimu) * 傾斜角 : R * 轉向角 : P * 直線速度 : Z :::warning 此韌體使用方式大致上分為三種,經測試 CLtool 和 SDK 在 Linux 中皆會遇到無法找到 libusb 的問題,Evatool 在 Windows 中可以正常使用 ::: > [EVB-2](https://docs.inertialsense.com/user-manual/hardware/EVB2/) > [EVB-2 IMX Connection](https://docs.inertialsense.com/user-manual/hardware/EVB2/#h7-imx-connections) ### STM32 建立 USART ## STM32 連接 ESP32 > [ESP-idf](https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32/index.html) ESP32 為 ESPRESSIF 所製作出來專門連接藍芽和 WIFI 的 SoC ,然而 STM32 上面有 ethernet 界面,為什麼不直接使用 ethernet 界面來連接網路 ? 原因是因為 STM32 上面所需要負責的控制硬體已經夠多了,對於控制速度的追求下只能使用 ESP32 作為 STM32 的外接晶片來傳輸或是儲存資料 ### 設定 ESP32 根據 [esp-idf programming guide](https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32/index.html#esp-idf-programming-guide) 之指示,須先設定 `$IDF_PATH` 於環境變數中,並且透過 `export.sh` 檢查 python 環境以及 ESP32 toolchain 的路徑。 ```shell $ . $IDF_PATH/export.sh Detecting the Python interpreter Checking "python3" ... Python 3.8.10 "python3" has been detected ... Done! You can now compile ESP-IDF projects. Go to the project directory and run: idf.py build ``` 接著選取一個 ESP32 專案進行建置 ```shell $ idf.py build Executing action: all (aliases: build) Running cmake in directory /home/fewletter/Research/ESP32/esp-idf/ESP32_HTTPSERVER_STM32/EHSS/sdspi/build ... python -m esptool --chip esp32 -b 460800 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size 2MB --flash_freq 40m 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/sd_card.bin or from the "/home/fewletter/Research/ESP32/esp-idf/ESP32_HTTPSERVER_STM32/EHSS/sdspi/build" directory python -m esptool --chip esp32 -b 460800 --before default_reset --after hard_reset write_flash "@flash_args" ``` 已建置的專案會有其執行檔 `.elf` ,ESP32 利用 OpenOCD 建立其內部 flash 和 ELF 檔案之間的連結,在建立連結之前先設定連結通道,以 Linux 來說,通常會是 `/dev/ttyUSB1` 之類的 COM ,接著執行下面命令 ```shell $ idf.py -p /dev/ttyUSB0 flash monitor ``` `flash` 為將 ELF 檔案寫入 ESP32 的 flash 中,`monitor` 為建立 ESP32 的終端,其由 uart 作為通訊協議,ESP32 中的 `ESP_LOGI` 會經由系統呼叫顯示於終端中。 #### 設定 ESP32 flash partition table 若完成 `. $IDF_PATH/export.sh` 命令即可使用 `idf.py menuconfig` 來設定所有有關專案的一切,而這裡則是要來設定有關 flash 的分區表 partition table,一開始的分區表的設定如下: ``` # ESP-IDF Partition Table # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 1M, ``` 而此分區表並不一定會適合所有專案,所以有時會遇以下錯誤: ```shell $ idf.py build app partition is too small for binary camera_test_4.bin size 0x15a5b0: - Part 'factory' 0/0 @ 0x10000 size 0x100000 (overflow 0x5a5b0) ``` 此時就需要去針對你所遇到的況去調整,在此專案中則是將分區表以 CSV 檔的形式,並且須儲存在專案目錄底下。 ``` # ESP-IDF Partition Table # Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x6000, phy_init, data, phy, 0x1000, factory, app, factory, 1M, ota_0, app, ota_0, 1984K, ota_1, app, ota_1, 1984K, ``` #### 設定 ESP32 CMakeLists 若依照 Espressif IDF extension 的 Github 開啟一個範例,可以看到以下的目錄和檔案 ``` . │ ├── main │ ├── CMakeLists.txt │ └── main.c ├── sdkconfig ├── gitignore └── README.md ``` `CMakeLists.txt` 為設定如何建置此專案,打開可以看到 ``` idf_component_register(SRCS "main.c" INCLUDE_DIRS "." EMBED_TXTFILES "certs/servercert.pem" "certs/prvtkey.pem" ) ``` 代表此專案只有 `main.c` 會被編譯,若要增加其他 `.c` 檔,直接增加檔名即可。 ### ESP32 網路伺服器 ## STM32 連接上藍芽無線手把 ## STM32 連接直流馬達 > [馬達型號](https://www.orientalmotor.com.tw/products/sc/list/detail/?brand_tbl_code=BL&product_name=BLHM5100KCM-30%2BBLHD100K%2BCC02BLH2R) # Cortex Mx 處理器 ## 處理器的執行模式 * Thread mode 所有程式都執行於 thread mode,在有必要的情況下才會使用 handler mode * Handler mode Handler mode 使用於當程式用到中斷 (irq) > 在電腦科學中,中斷是指處理器接收到來自硬體或軟體的訊號,提示發生了某個事件,應該被注意,這種情況就稱為中斷。 通常,在接收到來自外圍硬體的非同步訊號,或來自軟體的同步訊號之後,處理器將會進行相應的硬體/軟體處理。發出這樣的訊號稱為進行中斷請求 (IRQ) 。 -- wikipedia ## 處理器的存取權限 * Privileged Access Level (PAL) * Non-privileged Access Level (NPAL) ## 核心暫存器 Core Register 從下圖中可以看到,Cortex-M4 使用了 R0~R12 作為可以使用的寄存器,R13 則是作為一個 main 函式的起點 Stack Pointer,並在整個程式執行完畢後返回此位址,R14 則是作為 Link Register,在呼叫函式並且返回時的地址 ![](https://hackmd.io/_uploads/r1_qoGpb6.png) ### 狀態暫存器 Program Status Register Program Status Register 包含以下三種不同的狀態暫存器: * ASPR (Application Program Status Register) * IPSR (Interrupt Program Status Register) * EPSR (Execution Program Status Register) ![](https://hackmd.io/_uploads/rJeIDzrGp.png) :::info **特別討論: T 位元於 EPSR 中的作用** 在 ARM 處理器中有兩種 ISA 指令集分別為 ARM 指令集 (32 位元) 和 THUMB 指令集 (16 位元),這代表著在 ARM 處理器中可以切換這兩種指令集的狀態,在 EPSR 中的 T 位元就是此作用 * 在 T 位元等於 1 時,處理器會將之後的程式碼以 THUMB 指令集 (16 位元) 執行 * 在 T 位元等於 0 時,處理器會將之後的程式碼以 ARM 指令集 (32 位元) 執行。 > [嵌入式開發板 ARM 和 THUMB 狀態詳解](https://zhuanlan.zhihu.com/p/636310569) ::: ### Exception for system level services ARM cotex Mx 處理器支援兩種系統等級服務(system level services) * SVC (SuperVisor Call) * SVC 通常使用在有 os 環境中,舉例來說,當一個 user task 要啟動 SVC 例外處理去解決系統等級的問題,像是從 kernel 調用外部設備等 * SVC 是一個 ==THUMB 指令集的指令==,其目的在於調用 SVC 例外處理 * 在 RTOS 中,user task 可以執行帶有引數 SVC 指令去使用 kernel 中的特權資源 * PendSV (Pendable SerVice) * PendSV 則是在沒有其他任務的情況下進行兩個或多個任務的 context switch ## Cortex 中斷設計和 NVIC 理解 STM32 的中斷設計就必須先理解 GPIO 是如何中斷處理的,而中斷處理都與 NVIC(Nested vectored interrupt controller) 息息相關,而根據下圖可以得知,如果有些界面 ex. APB 要觸發或是啟動 NVIC 須通過 EXTI(external interrupt/event controller) 的界面才能夠啟動。 ![image](https://hackmd.io/_uploads/r1gPxoZx0.png) 從上圖來說,NVIC 和牽扯到所有的元件,首先是 EXTI Controller ,其用來判斷是否有中斷發生,而對於 Processor Core 來說,其處於 polling 著狀態,其根據 SysTick 不斷的監視著是否有需要處理的中斷 :::info **EXTI Controller** ![image](https://hackmd.io/_uploads/BJ_mGoWl0.png) ::: ![2023-11-21 14-04-01 的螢幕擷圖](https://hackmd.io/_uploads/H1vPuptNT.png) EXTI 則是通過外圍設備 Peripheral Bus 所控制,如下圖的 memory map ![2023-11-21 14-11-14 的螢幕擷圖](https://hackmd.io/_uploads/SJ3z9pYVa.png) 而下方是如何透過按按鈕事件來觸發處理的中斷的流程圖 ```mermaid graph TD; Button-->GPIO_pin GPIO_pin-->設定輸入模式 設定輸入模式-->EXTI控制器 EXTI控制器-->OR EXTI_Pending_register-->OR OR-->NVIC NVIC-->觸發處理器中斷 觸發處理器中斷-->實作handler來啟用中斷 ``` ## Volatile `volatile` 關鍵字在於避免編譯器的優化改變原本的數值,而為什麼編譯器的優化會改變原本的數值? 首先先了解什麼叫做編譯器的優化,編譯器在編譯一個程式檔時會或多或少對其進行優化,比如說為了將程式執行速度加快,會使用經典的 `Loop unrolling` 來增加編譯出 assembly code 的大小,而此方法雖然增加的 assembly code 的大小但是卻可以節省多個不必要的指令 instruction 因而加快速度,有或者是編譯器將原本的程式碼優化到不須一直從內存 SRAM 讀取數值,但是你更新的值卻又儲存在內存,導致更新的未被取出。 所以從以上的方法可看出編譯器的優化有時確實會不小心改變到原本的值,而 `volatile` 就是為了防止此狀況發生的關鍵字。 > [volatile 到底是什麼](https://hackmd.io/UhcO3yP7SB-6b1fVFBwr0g?view#volatile-%E5%88%B0%E5%BA%95%E6%98%AF%E4%BB%80%E9%BA%BC) > [編譯器優化實例](https://hackmd.io/@fewletter/riscvtoolchain#Analysis) > [你所不知道的 C 語言:編譯器原理和案例分析](https://hackmd.io/@sysprog/c-linker-loader) > [你所不知道的 C 語言:連結器和執行檔資訊](https://hackmd.io/@sysprog/c-compiler-construction) ## Driver 驅動程式 在了解 driver 實作前,必須要瞭解到整個系統的架構,首先如果要執行一個程式,要先經過分析,確認要用何種 driver,例如 mpu6050 要使用 `I2C`,而有時遇到不同訊號時,不同種 driver 也會需要混合使用,比如說 PWM 就會使用到 `RCC`,`GPIO`,端看使用情況。 ```mermaid graph TD; Sample_Appliction-->Driver_Layer; Driver_Layer-->GPIO_Driver.c/h; Driver_Layer-->I2C_Driver.c/h; Driver_Layer-->SPI_Driver.c/h; Driver_Layer-->UART_Driver.c/h; Driver_Layer-->OTHER_Driver.c/h; Driver_Layer-->Device_header; OTHER_Driver.c/h-->STM32f407_MCU; GPIO_Driver.c/h-->STM32f407_MCU; I2C_Driver.c/h-->STM32f407_MCU; SPI_Driver.c/h-->STM32f407_MCU; UART_Driver.c/h-->STM32f407_MCU; ``` 確認過 driver 後,需要依照上面架構圖先建立 `Device_header` 的 .h 檔,此檔代表著此次任務所會使用到的 driver。 ### Device header > [Reference manual](https://www.st.com/resource/en/reference_manual/dm00031020-stm32f405-415-stm32f407-417-stm32f427-437-and-stm32f429-439-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf) 在嵌入式系統中,必須要仔細閱讀產品的 datasheet,由於嵌入式系統的效能有限所以每個區間的記憶體空間都十分重要,而 STM32 特別是 STM32F407XX 整個嵌入式系統架構如下 * 8 個 masters: * Cortex-M4 和 FPU 核心的 I-bus, D-bus, S-bus * DMA1 memory bus * DMA2 memory bus * DMA2 peripheral bus * Ethernet DMA bus * USB OTG HS DMA bus * 7 個 slaves: * Internal Flash memory ICode bus * Internal Flash memory DCode bus * Main internal SRAM1 (112 KB) * Auxiliary internal SRAM2 (16 KB) * AHB1 peripherals including AHB to APB bridges and APB peripherals * AHB2 peripherals * FSMC 所以根據 datasheet ,可以將 flash memory,SRAM1,SRAM2 的位址寫在 Device driver 的檔案中 ```c #define FLASH_BASEADDR 0x08000000U /*Flash memory address 0x08000000 ~ 0x080fffff*/ #define SRAM1_BASEADDR 0x20000000U /*SRAM memory address 112 kb*/ #define SRAM2_BASEADDR 0x20001c00U /*SRAM2 memory address 16 kb*/ #define ROM_BASEADDR 0x1fff0000U /*System memory address*/ #define SRAM SRAM1_BASEADDR ``` 接著便是撰寫外部設備的地址,外部設備主要是由微控制板上的 pin 腳所組成,那些 pin 腳都可由地址去讀取 ```c #define PERIPH_BASEADDR 0x40000000U #define APB1PERIPH_BASEADDR PERIPH_BASEADDR #define APB2PERIPH_BASEADDR 0x40010000U #define AHB1PERIPH_BASEADDR 0x40020000U #define AHB2PERIPH_BASEADDR 0x50000000U ``` ### GPIO GPIO 是 general-purpose input output 的縮寫,在不同為控制板中會有不同數量的 GPIO ,在 STM32 板中, GPIO 有 16 bit 的 pin 腳,其名字為 PA0~15。 GPIO 的每個腳位都由許多的暫存器所傳輸資料。 ![2023-11-04 21-36-18 的螢幕擷圖.png](https://hackmd.io/_uploads/BJD1tpQ7a.png) 那在 STM32 中 GPIO 是如何決定其腳位的輸入呢? 從 [STM32 : RM0090 Reference manual](https://www.st.com/resource/en/reference_manual/dm00031020-stm32f405-415-stm32f407-417-stm32f427-437-and-stm32f429-439-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf) 中我們可以知道,GPIO 有自己一套規則,也就是這些 pin 腳如前面提到每個都有 16 bit ,所以每個 pin 腳就可以透過 offset 來計算並表示。 ![2023-11-04 23-37-06 的螢幕擷圖.png](https://hackmd.io/_uploads/S12nSkVQa.png) ### GPIO 使用方法 在使用 GPIO 之前都必須決定其模式,不管是要使用其作為輸入,輸出或是其他用法,而 GPIO 有以下幾種用法 ![2023-11-04 23-50-56 的螢幕擷圖.png](https://hackmd.io/_uploads/H1Od_1VXT.png) ![2023-11-04 23-51-14 的螢幕擷圖.png](https://hackmd.io/_uploads/r1CKOyEXa.png) ![2023-11-04 23-51-40 的螢幕擷圖.png](https://hackmd.io/_uploads/BkQoO1VQ6.png) ![2023-11-04 23-51-57 的螢幕擷圖.png](https://hackmd.io/_uploads/S1kndJV76.png) ![2023-11-04 23-52-10 的螢幕擷圖.png](https://hackmd.io/_uploads/r1kpu1Vm6.png) ![2023-11-04 23-52-51 的螢幕擷圖.png](https://hackmd.io/_uploads/H14yFJV76.png) ![2023-11-04 23-53-05 的螢幕擷圖.png](https://hackmd.io/_uploads/SkIgtk4Qa.png) ![2023-11-04 23-53-32 的螢幕擷圖.png](https://hackmd.io/_uploads/r1gGKk4Xp.png) ![2023-11-04 23-53-48 的螢幕擷圖.png](https://hackmd.io/_uploads/S1hMKJN76.png) ![2023-11-04 23-53-48 的螢幕擷圖.png](https://hackmd.io/_uploads/rkdmYk4Q6.png) ### GPIO driver ![2023-11-05 00-27-49 的螢幕擷圖.png](https://hackmd.io/_uploads/HJlXZgVmp.png) ### SPI driver