# 在 Raspberry Pi 4B 上設定 UART 連線
contributed by < `OscarShiang` >
## 硬體準備
- Raspberry Pi 4B
- TTL to USB (P2303HXDL)
- micro SD card
- USB type A to type C
## 檢查 UART 是否可以正常運作
為了方便後續使用 bare metal 設定時可以先排除 UART 連接錯誤的問題,我們可以先使用 Raspberry Pi 官方提供的 Raspbian 來進行測試
### 燒錄系統
最簡單的方法就是使用 Raspberry Pi 官方所提供的 [Raspberry Pi Imager](https://www.raspberrypi.org/software/) 來進行下載與燒錄。
或是自行從 [Operating system images](https://www.raspberrypi.org/software/operating-systems/) 下載映像檔並透過 [rufus](https://rufus.ie) 或是 [UNetbootin](https://unetbootin.github.io) 進行燒錄。
### 連接 UART
請參考 Raspberry Pi 的接腳對應圖([UART at Raspberry Pi GPIO Pinout](https://pinout.xyz/pinout/uart))
將 TTL 的 `GND`, `RX` 與 `TX` 分別連接到以下的接腳:
- 將 TTL 線的 `GND` 接到 `6` 號接腳
- 將 TTL 的 `RX` 接到 `8` 號 `TXD` 接腳
- 將 TTL 的 `TX` 接到 `10` 號 `RXD` 接腳
### 安裝 TTL 線的驅動程式
為了可以在電腦上接收來自 TTL 線的訊號,我們首先需要將對應的驅動程式安裝在系統中,安裝的教學可以參考 [[基礎] 從序列埠登入到 Raspberry Pi](https://www.raspberrypi.com.tw/tag/usb-to-ttl/) 這篇。
:::warning
需要修改放在 `boot/` 底下的 `config.txt`:加入 `enable_uart=1` 與 `core_freq_min=500` 兩項設定
:::
### 測試結果
完成先前的配置之後,我們就可以把燒錄好 raspbian 的 micro SD card 裝到 raspberry pi 上並接上電源開機。
如果連線成功的話,我們就會看到 kernel 開機的訊息:
```shell
<...>
[ 3.176335] Segment Routing with IPv6
[ 3.214711] systemd[1]: systemd 241 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid)
[ 3.237254] systemd[1]: Detected architecture arm64.
[ 3.267411] systemd[1]: Set hostname to <raspberrypi>.
[ 3.828087] random: systemd: uninitialized urandom read (16 bytes read)
[ 3.846401] random: systemd: uninitialized urandom read (16 bytes read)
[ 3.854305] systemd[1]: Listening on Journal Socket.
[ 3.860399] random: systemd: uninitialized urandom read (16 bytes read)
[ 3.872492] systemd[1]: Mounting Kernel Debug File System...
[ 3.883067] systemd[1]: Starting Create list of required static device nodes for the current kernel...
[ 3.894427] systemd[1]: Created slice User and Session Slice.
[ 3.901770] systemd[1]: Listening on fsck to fsckd communication Socket.
[ 3.909961] systemd[1]: Listening on Syslog Socket.
[ 3.920163] systemd[1]: Starting Set the console keyboard layout...
Raspbian GNU/Linux 10 raspberrypi ttyS0
raspberrypi login:
```
## 使用 bare metal 程式測試 UART 功能
這邊的實作主要是參考以下兩個 Repo
- [bztsrc / raspi3-tutorial](https://github.com/bztsrc/raspi3-tutorial)
- [isometimes / rpi4-osdev](https://github.com/isometimes/rpi4-osdev)
### 硬體相關設定
因為 Raspberry Pi 使用 memory-mapped IO,所以我們首先需要定義相關的 register 的位址,詳細的描述可以在 [BCM2711 ARM Peripherials](https://datasheets.raspberrypi.org/bcm2711/bcm2711-peripherals.pdf) 文件中找到
#### UART
我們在這次所使用到的是 raspberry pi 的 mini UART,需要使用的 register 如下所列:
| Offset | Name | Description |
|:------ | --------------- | ---------------------------- |
| 0x00 | AUX_IRQ | Auxiliary Interrupt status |
| 0x04 | AUX_ENABLES | Auxiliary enables |
| 0x40 | AUX_MU_IO_REG | Mini UART I/O Data |
| 0x44 | AUX_MU_IER_REG | Mini UART Interrupt Enable |
| 0x48 | AUX_MU_IIR_REG | Mini UART Interrupt Identify |
| 0x4c | AUX_MU_LCR_REG | Mini UART Line Control |
| 0x50 | AUX_MU_MCR_REG | Mini UART Modem Control |
| 0x54 | AUX_MU_LSR_REG | Mini UART Line Status |
| 0x58 | AUX_MU_MSR_REG | Mini UART Modem Status |
| 0x5c | AUX_MU_SCRATCH | Mini UART Scratch |
| 0x60 | AUX_MU_CNTL_REG | Mini UART Extra Control |
| 0x64 | AUX_MU_STAT_REG | Mini UART Extra Status |
| 0x68 | AUX_MU_BAUD_REG | Mini UART Baudrate |
:::warning
:question: 根據 [BCM2711B0 - Raspberry Pi Forums](https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=244479) 指出 Raspberry Pi 4B 的 Peripherial Base 應該設定為 `0xFE000000` 但是在 [BCM 2711 ARM Peripherials](https://datasheets.raspberrypi.org/bcm2711/bcm2711-peripherals.pdf) 中卻說要設定為 `0x7E000000` ?
:::
:::info
:bulb: 在 BCM2711 當中,預設使用 Legacy master addresses 來表示;但在 bare metal 的環境下,VPU 預設啟動 Low Peripheral Mode,所以實際使用的 Peripheral Base 應為 `0xFE000000`
:::
設定好相關數值之後我們就可以來實作對應的函式:
- `uart_init()`: 初始化 mini UART 的硬體設定 (baud rate 設定為 **115200**)
- `uart_send()`: 使用 serial 傳送一個字元
- `uart_getc()`: 接收一個字元
- `uart_puts()`: 輸出一段字串
#### baud rate 計算
我們可以從 [BCM 2711 ARM Peripherials](https://datasheets.raspberrypi.org/bcm2711/bcm2711-peripherals.pdf) 得知 baud rate 的計算公式:
$$
baud\ rate = \frac{system\_clock\_freq}{8 \times (baudrate\_reg + 1)}
$$
因為我們想要使用的是 115200 baud 所以可以算得
$$
\begin{align}
115200 &= \frac{system\_clock\_freq}{8 \times (baudrate\_reg + 1)} \\\\
baudrate\_reg &= \frac{500000000}{8 \times 115200} - 1 \\\\
&= 541.53
\end{align}
$$
根據計算的結果我們將 `AUX_MU_BAUD_REG` 設定為 `541`
:::warning
這邊的 $system\_clock\_freq = 500000000$ 是因為在上一階段我們在 `config.txt` 設定 `core_freq_min=500`
:::
### 開機測試
透過 cross compiler 我們可以將剛剛實作的 `uart.c` 與 `start.S`, `main.c`, `link.ld` 等等必要的檔案進行連結,並產生 `kernel8.img` 的映像檔。
因為我們產生的只是單純的 bare metal 程式,因此可以使用在前一個階段燒錄好的 SD card 來進行接下來的操作。我們只需要將對應的系統映像檔放入,bootloader 就會幫我們在開機的時候載入對應的程式。
所以我們需要進行以下的操作:
- 將 boot 磁碟區中的 kernel* 檔案刪除,並將剛剛產生的 `kernel8.img` 寫入
- 接上 TTL 線,並開啟 putty 連接對應的裝置
- 將 SD Card 放入 raspberry pi 中,並連接電源
如果順利驅動的話就可以看到 putty 的畫面印出以下訊息並可以任意輸入字元:
```shell
Hello World!
```
:::success
:pencil: 實作詳細的修改可以參考 [`961e7af`](https://github.com/OscarShiang/raspi3-tutorial/commit/961e7af19f2d9e8feefdc8962c3f42c5bdc82d92)
:::
## 參考資料
- [[基礎] 從序列埠登入到 Raspberry Pi](https://www.raspberrypi.com.tw/tag/usb-to-ttl/)
- [BCM 2711 ARM Peripherials](https://datasheets.raspberrypi.org/bcm2711/bcm2711-peripherals.pdf)
- [bztsrc / raspi3-tutorial](https://github.com/bztsrc/raspi3-tutorial)
- [isometimes / rpi4-osdev](https://github.com/isometimes/rpi4-osdev)
- [BCM2711B0 - Raspberry Pi Forums](https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=244479)
- [UART configuration - Raspberry Pi Documentation](https://www.raspberrypi.org/documentation/configuration/uart.md)
###### tags: `raspi`