---
# System prepended metadata

title: 2019-MPSL Lab8

---

# 2019-MPSL Lab8

> 2019 Fall Microprocessor System Lab
> Lab8 UART and ADC
> [name=0516009 吳宗達、0616015 劉姿利]

## What to Do

- 了解如何使用 UART 讓 STM32 與電腦傳輸溝通
- 了解如何使用 ADC 將類比訊號轉成數位訊號

## How to Do

### 8-1 Hello World!

> 在按下板子上藍色按鈕時（PC13），利用 UART 將 "Hello World!" 字串傳送到電腦。並且可以在電腦端的 serial monitor 顯示出來。

```cpp=

#include "stm32l476xx.h"
#include "core_cm4.h"

int freq = 500;
int counting = 0;

/* GPIO */

GPIO_TypeDef* GPIO[16] = {[0xA]=GPIOA, [0xB]=GPIOB, [0xC]=GPIOC};
const unsigned int X[4] = {0xA5, 0xA6, 0xA7, 0xB6};
const unsigned int Y[4] = {0xC7, 0xA9, 0xA8, 0xBA};

void read_button() {
	int cnt = 0;
	while(1) {
		int button = GPIOC->IDR & (1 << 13);
		if (button == 0) { // 0
			cnt ++;
		} else if (cnt > (1 << 13)) { // 1 trigger
			cnt = 0;
			break;
		} else { // 1
			cnt = 0;
		}
	}
}

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(0xA9, 2); // TX
	set_moder(0xAA, 2); // RX
	set_moder(0xCD, 0);
	GPIOA->AFR[1] |= (7 << 4) + (7 << 8);
}

/* uart */
int UART_Transmit(uint8_t *arr, uint32_t size) {
	char *str = arr;
	for (int i = 0; i < size; i ++) {
		while ((USART1->ISR & USART_ISR_TXE) == 0);
		USART1->TDR = str[i];
	}
	while ((USART1->ISR & USART_ISR_TXE) == 0);
	return size;
}

void init_UART() {
	RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

	// CR1
	USART1->CR1 &= ~(USART_CR1_M | USART_CR1_PS | USART_CR1_PCE | USART_CR1_TE | USART_CR1_RE | USART_CR1_OVER8);
	USART1->CR1 |= (USART_CR1_TE | USART_CR1_RE);

	// CR2
	USART1->CR2 &= ~(USART_CR2_STOP);

	// CR3
	USART1->CR3 &= ~(USART_CR3_RTSE | USART_CR3_CTSE | USART_CR3_ONEBIT);

	// BRR
	USART1->BRR &= ~(0xFF);
	USART1->BRR |= 4000000L / 9600L ;

	/* In asynchronous mode, the following bits must be kept cleared:
	- LINEN and CLKEN bits in the USART_CR2 register,
	- SCEN, HDSEL and IREN bits in the USART_CR3 register.*/
	USART1->CR2 &= ~(USART_CR2_LINEN | USART_CR2_CLKEN);
	USART1->CR3 &= ~(USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN);

	// Enable UART
	USART1->CR1 |= (USART_CR1_UE);
}

int main() {
	gpio_init();
	init_UART();

	while (1) {
		read_button();
		UART_Transmit((uint8_t*)"Hello World!\r\n", 14);
	}
}
```
#### 接線
- +5v 紅色 +5v
- RxD 白色 PA9
- TxD 綠色 PA10
- GND 黑色 GND

#### Set GPIO Alternate Function

```c
set_moder(0xA9, 2); // TX
set_moder(0xAA, 2); // RX
GPIOA->AFR[1] |= (7 << 4) + (7 << 8);
```
我們使用 `PA9` 與 `PA10` 分別作為 `RX` 與 `TX`，要將他們對應到 `AF7`，所以需要在 register `GPIOA_AFRH` 做設定。

#### Uart Init

```cpp
// CR1
USART1->CR1 &= ~(USART_CR1_M | USART_CR1_PS | USART_CR1_PCE | USART_CR1_TE | USART_CR1_RE | USART_CR1_OVER8);
USART1->CR1 |= (USART_CR1_TE | USART_CR1_RE);
```
將 `TE` 與 `RE` 打開、一次傳輸 8 個 bits （`M`）。

```c
// CR2
USART1->CR2 &= ~(USART_CR2_STOP);
```
設定 `stop bits` 的數量為 1。

```c
// BRR
USART1->BRR &= ~(0xFF);
USART1->BRR |= 4000000L / 9600L ;
```
調整傳輸速度，要跟欲傳輸的機器同步，我們因為電腦端設定 9600，所以這裡要設為 4000000 / 9600，其中 4000000 為 `sysclk` frequency。

```c
// Enable UART
USART1->CR1 |= (USART_CR1_UE);
```

#### Uart Transmit

```c
int UART_Transmit(uint8_t *arr, uint32_t size) {
	char *str = arr;
	for (int i = 0; i < size; i ++) {
		while ((USART1->ISR & USART_ISR_TXE) == 0);
		USART1->TDR = str[i];
	}
	while ((USART1->ISR & USART_ISR_TXE) == 0);
	return size;
}
```

Register `USART_TDR` 代表欲傳輸的資料，而 register `USART_ISR` 中的 `TXE` bit 為 1 代表上一個檔案以傳輸完畢現在 `USART_TDR` 為可寫狀態，所以只要把想要傳送的資料在 `TXE` 時放到 `USART_TDR` 上就會被傳送到 serial port 上了。

#### Putty

![](https://i.imgur.com/R5AInD1.png)


#### Driver Issue

因為 driver 在 2012 年之後的版本不支援，所以需要在網路上找舊版的，serial port 才總算連接成功。

### 8-2 Analog-to-Digital

> 利用板子上提供的 ADC (Analog-to-Digital Converter) 利用 Interrupt 將光敏電阻的值以 12-bit 的解析度讀出，並且每按一次按鈕 (PC13) 時利用 UART 輸出數值。

```c=
#include "stm32l476xx.h"
#include "core_cm4.h"

int freq = 500;
int counting = 0;

/* GPIO */

GPIO_TypeDef* GPIO[16] = {[0xA]=GPIOA, [0xB]=GPIOB, [0xC]=GPIOC};
const unsigned int X[4] = {0xA5, 0xA6, 0xA7, 0xB6};
const unsigned int Y[4] = {0xC7, 0xA9, 0xA8, 0xBA};


void read_button() {
	int cnt = 0;
	while(1) {
		int button = GPIOC->IDR & (1 << 13);
		if (button == 0) { // 0
			cnt ++;
			if (cnt > (1 << 13)) break;
		} else if (cnt > (1 << 13)) { // 1 trigger
			cnt = 0;
			return;
		} else { // 1
			cnt = 0;
		}
	}
}

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(0xA9, 2); // TX
	set_moder(0xAA, 2); // RX
	set_moder(0xCD, 0);
	set_moder(0xA0, 3);
	GPIOA->AFR[1] |= (7 << 4) + (7 << 8);
	GPIOA->ASCR |= (1<<0);
}

/* uart */
int UART_Transmit(uint8_t *arr, uint32_t size) {
	char *str = arr;
	int ret = 0;
	for (int i = 0; str[i] && i < size; i ++) {
		while ((USART1->ISR & USART_ISR_TXE) == 0);
		USART1->TDR = str[i];
		ret ++;
	}
	while ((USART1->ISR & USART_ISR_TXE) == 0);
	return ret;
}

void init_UART() {
	RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

	// CR1
	USART1->CR1 &= ~(USART_CR1_M | USART_CR1_PS | USART_CR1_PCE | USART_CR1_TE | USART_CR1_RE | USART_CR1_OVER8);
	USART1->CR1 |= (USART_CR1_TE | USART_CR1_RE);

	// CR2
	USART1->CR2 &= ~(USART_CR2_STOP);

	// CR3
	USART1->CR3 &= ~(USART_CR3_RTSE | USART_CR3_CTSE | USART_CR3_ONEBIT);

	// BRR
	USART1->BRR &= ~(0xFF);
	//!
	USART1->BRR |= 1000000L / 9600L ;

	/* In asynchronous mode, the following bits must be kept cleared:
	- LINEN and CLKEN bits in the USART_CR2 register,
	- SCEN, HDSEL and IREN bits in the USART_CR3 register.*/
	USART1->CR2 &= ~(USART_CR2_LINEN | USART_CR2_CLKEN);
	USART1->CR3 &= ~(USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN);

	// Enable UART
	USART1->CR1 |= (USART_CR1_UE);
}


void init_ADC(){
	RCC->AHB2ENR |= RCC_AHB2ENR_ADCEN;
    
	ADC1->CFGR &= ~(ADC_CFGR_CONT); // continue
	ADC1->CFGR |= 0 << 3;
	ADC1->CFGR &= ~ADC_CFGR_ALIGN;

	ADC123_COMMON->CCR |= 1 << 16; // ckmode
	ADC123_COMMON->CCR |= 4 << 8; // delay

	ADC1->SQR1 |= 0<<0;
	ADC1->SQR1 |= 5<<6;

	ADC1->SMPR1 |= 2<<15;


	ADC1->CR &= ~ADC_CR_DEEPPWD;
	ADC1->CR |= ADC_CR_ADVREGEN;

	for (int i=0; i<200; i++);


	ADC1->IER |= ADC_IER_EOCIE;
	NVIC_EnableIRQ(ADC1_2_IRQn);

	ADC1->CR |= ADC_CR_ADEN;
	while (!(ADC1->ISR & ADC_ISR_ADRDY));
	ADC1->CR |= ADC_CR_ADSTART;
}

int ADC_data = 1;

void ADC1_2_IRQHandler(){
	// UART_Transmit((uint8_t*)"Scan\r\n", 20);
	NVIC_ClearPendingIRQ(ADC1_2_IRQn);
	for (int i=0; i<(1<<8); i++);
	ADC_data = ADC1->DR;
	ADC1->ISR |= ADC_ISR_EOC;
	// UART_Transmit_Number(ADC_data);

	NVIC_ClearPendingIRQ(ADC1_2_IRQn);
}

void UART_Transmit_Number(int n) {
	int dig[12] = {0};
	for (int i = 0; i < 12; i ++) {
		dig[i] = n % 10;
		n /= 10;
	}
	for (int i = 11; i >= 0; i --) {
		char c = '0' + dig[i];
		UART_Transmit((uint8_t*)&c, 1);
	}
	UART_Transmit((uint8_t*)"\r\n", 2);
}

void set_clock() {
	// Set system clock as MSI
	RCC->CFGR &= ~3;

	// HPRE -> 1MHz
	RCC->CFGR &= ~(0xF << 4);
	RCC->CFGR |= 11 << 4;

	// enable HSION
	RCC->CR |= 1 << 8;

	// Set system clock as PLL
	RCC->CFGR |= 1;
}

void systick_config() {
	SysTick->CTRL |= 7;
	SysTick->LOAD = 5000000;
	 NVIC_SetPriority(SysTick_IRQn, 1);
	 NVIC_SetPriority(ADC1_2_IRQn, 0);
}

void SysTick_Handler() {
	ADC1->CR |= ADC_CR_ADSTART;
	// UART_Transmit((uint8_t*)"Systick\r\n", 10);
}

int main() {
	gpio_init();
	set_clock();
	systick_config();

	init_UART();

	// TIM3_Config();
	init_ADC();

	UART_Transmit((uint8_t*)"Start\r\n", 20);

	while (1) {
		read_button();

		// UART_Transmit((uint8_t*)"Button\r\n", 20);
		UART_Transmit_Number(ADC_data);
	}

}
```

#### GPIO set as Analog Signal

```c
set_moder(0xA0, 3);
GPIOA->ASCR |= (1<<0);
```

在 `GPIOx_Moder` 上對應的位置設為 3 (analog mode)，並在 `GPIOx_ASCR` 上設為 1，代表此訊號要先經過 ADC 處理。

#### ADC Init - Configuration
```c
RCC->AHB2ENR |= RCC_AHB2ENR_ADCEN;

ADC1->CFGR &= ~(ADC_CFGR_CONT); // continue
ADC1->CFGR |= 0 << 3;
ADC1->CFGR &= ~ADC_CFGR_ALIGN;

ADC123_COMMON->CCR |= 1 << 16; // ckmode
ADC123_COMMON->CCR |= 4 << 8; // delay

ADC1->SQR1 |= 0<<0; // seq len = 1
ADC1->SQR1 |= 5<<6; // using channel 5

ADC1->SMPR1 |= 2<<15;
```

這裡我們設定了一個 sequence lenth 為 1，不 continue，且使用 channel 5 的 ADC。

#### ADC Init - Enable Interrupt

```c
ADC1->IER |= ADC_IER_EOCIE;
NVIC_EnableIRQ(ADC1_2_IRQn);
```
`EOCIE` 為 1 的話會在每次 seqence 結束後執行 `ADC1_2_IRQHandler`

#### ADC Init - Get Ready

```c
ADC1->CR &= ~ADC_CR_DEEPPWD;
ADC1->CR |= ADC_CR_ADVREGEN;

ADC1->CR |= ADC_CR_ADEN;
while (!(ADC1->ISR & ADC_ISR_ADRDY));
ADC1->CR |= ADC_CR_ADSTART;
```
將 deep power down 關閉並把 ADC voltage regulator enable 之後就可以設定 `ADEN` 把 ADC 打開成 ready 的狀態，當 ADC ready 時會在 register `ADC_ISR` 上的 `ADRDY` bit 上顯示 1，所以要等到 `ADRDY` 變成 1 之後才能讓 ADC start。

#### SysTick Handler
```c
void SysTick_Handler() {
	ADC1->CR |= ADC_CR_ADSTART;
}
```
因為直接使用 continuous 的 ADC 會有太多的 interrupt，所以我們選擇使用 Systick 來 trigger。

#### ADC_1_2_IRQHandler
```c
void ADC1_2_IRQHandler(){
	NVIC_ClearPendingIRQ(ADC1_2_IRQn);
	for (int i=0; i<(1<<8); i++);
	ADC_data = ADC1->DR;
	ADC1->ISR |= ADC_ISR_EOC;
	NVIC_ClearPendingIRQ(ADC1_2_IRQn);
}
```
被 systick trigger、且所有 sequence 執行完後會執行上面這個 handler，他會從 register `ADC_DR` 上將轉換成 digital 訊號的數值取出。

### 8-3 Simple Shell

> 在板子上利用 UART 實作 Simple Shell，並且擁有三個指令
> - showid: 顯示學號
> - light: 以每 0.5 秒更新並顯示光敏電阻的值、按 'q' 回到原本的 shell 模式
> - led \{on|off}: led on 將會把 PA5 的 LED 打開、led off 則會關閉

```cpp=
#include "stm32l476xx.h"
#include "core_cm4.h"

int freq = 500;
int counting = 0;

/* GPIO */

GPIO_TypeDef* GPIO[16] = {[0xA]=GPIOA, [0xB]=GPIOB, [0xC]=GPIOC};
const unsigned int X[4] = {0xA5, 0xA6, 0xA7, 0xB6};
const unsigned int Y[4] = {0xC7, 0xA9, 0xA8, 0xBA};

void read_button() {
	int cnt = 0;
	while(1) {
		int button = GPIOC->IDR & (1 << 13);
		if (button == 0) { // 0
			cnt ++;
		} else if (cnt > (1 << 13)) { // 1 trigger
			cnt = 0;
			break;
		} else { // 1
			cnt = 0;
		}
	}
}



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(0xA9, 2); // TX
	set_moder(0xAA, 2); // RX
	set_moder(0xCD, 0);
	set_moder(0xA5, 1);
	set_moder(0xA0, 3);
	GPIOA->ODR &= ~(1 << 5);
	GPIOA->AFR[1] |= (7 << 4) + (7 << 8);
	GPIOA->ASCR |= (1<<0);
}

/* uart */
int UART_Transmit(uint8_t *arr, uint32_t size) {
	char *str = arr;
	int ret = 0;
	for (int i = 0; str[i] && i < size; i ++) {
		while ((USART1->ISR & USART_ISR_TXE) == 0);
		USART1->TDR = str[i];
		ret ++;
	}
	while ((USART1->ISR & USART_ISR_TXE) == 0);
	return ret;
}

char buf[300];

char receive_char() {
	while (!(USART1->ISR & USART_ISR_RXNE));
	// USART1->RQR |= USART_RQR_RXFRQ;
	USART1->ISR = USART1->ISR & ~USART_ISR_RXNE;
	return USART1->RDR & 0xFF;
}

void read_cmd() {
	int ptr = 0;
	char c;
	do {
		c = receive_char();
		buf[ptr++] = c;
		UART_Transmit((uint8_t*)&c, 1);
	} while (c != '\n' && c != '\r');
	buf[ptr++] = '\0';
	UART_Transmit((uint8_t*)"\r\n", 2);
}

void init_UART() {
	RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

	// CR1
	USART1->CR1 &= ~(USART_CR1_M | USART_CR1_PS | USART_CR1_PCE | USART_CR1_TE | USART_CR1_RE | USART_CR1_OVER8);
	USART1->CR1 |= (USART_CR1_TE | USART_CR1_RE);

	// CR2
	USART1->CR2 &= ~(USART_CR2_STOP);

	// CR3
	USART1->CR3 &= ~(USART_CR3_RTSE | USART_CR3_CTSE | USART_CR3_ONEBIT);

	// BRR
	USART1->BRR &= ~(0xFF);
	USART1->BRR |= (1000000L / 9600L) & 0xFFFF ;

	/* In asynchronous mode, the following bits must be kept cleared:
	- LINEN and CLKEN bits in the USART_CR2 register,
	- SCEN, HDSEL and IREN bits in the USART_CR3 register.*/
	USART1->CR2 &= ~(USART_CR2_LINEN | USART_CR2_CLKEN);
	USART1->CR3 &= ~(USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN);

	// Enable UART
	USART1->CR1 |= (USART_CR1_UE);
	// USART1->ISR |= USART_ISR_RXNE;

	// NVIC_EnableIRQ(USART1_IRQn);
}

void init_ADC(){
	RCC->AHB2ENR |= RCC_AHB2ENR_ADCEN;
	// ADC123_COMMON->CCR |= ADC_CCR_CKMODE_0;

	// ADC1->CFGR |= ADC_CFGR_CONT; // continue
	ADC1->CFGR &= ~(ADC_CFGR_CONT); // continue
	ADC1->CFGR |= 0 << 3;
	ADC1->CFGR &= ~ADC_CFGR_ALIGN;

	//ADC123_COMMON->CCR |= 11 << 18;
	ADC123_COMMON->CCR |= 1 << 16; // ckmode
	ADC123_COMMON->CCR |= 4 << 8; // delay

	ADC1->SQR1 |= 0<<0;
	ADC1->SQR1 |= 5<<6;

	ADC1->SMPR1 |= 2<<15;


	ADC1->CR &= ~ADC_CR_DEEPPWD;
	ADC1->CR |= ADC_CR_ADVREGEN;

	for (int i=0; i<200; i++);


	ADC1->IER |= ADC_IER_EOCIE;
	NVIC_EnableIRQ(ADC1_2_IRQn);

//	ADC1->CFGR |= 4<<6;
//	ADC1->CFGR |= ADC_CFGR_EXTEN_0;

	ADC1->CR |= ADC_CR_ADEN;
	while (!(ADC1->ISR & ADC_ISR_ADRDY));
	ADC1->CR |= ADC_CR_ADSTART;
}

int ADC_data = 1;

void ADC1_2_IRQHandler(){
	// UART_Transmit((uint8_t*)"Scan\r\n", 20);
	NVIC_ClearPendingIRQ(ADC1_2_IRQn);
	for (int i=0; i<(1<<8); i++);
	ADC_data = ADC1->DR;
	ADC1->ISR |= ADC_ISR_EOC;
	UART_Transmit_Number(ADC_data);

	NVIC_ClearPendingIRQ(ADC1_2_IRQn);
}

void UART_Transmit_Number(int n) {
	int dig[12] = {0};
	for (int i = 0; i < 12; i ++) {
		dig[i] = n % 10;
		n /= 10;
	}
	for (int i = 11; i >= 0; i --) {
		char c = '0' + dig[i];
		UART_Transmit((uint8_t*)&c, 1);
	}
	UART_Transmit((uint8_t*)"\r\n", 2);
}

void set_clock() {
	// Set system clock as MSI
	RCC->CFGR &= ~3;

	// HPRE -> 1MHz
	RCC->CFGR &= ~(0xF << 4);
	RCC->CFGR |= 11 << 4;

	// enable HSION
	RCC->CR |= 1 << 8;

	// Set system clock as PLL
	RCC->CFGR |= 1;
}

void systick_enable() {
	SysTick->CTRL |= 1;
}

void systick_disable() {
	SysTick->CTRL &= ~1;
}

void systick_config() {
	SysTick->CTRL |= 7;
	SysTick->LOAD = 500000;
	 NVIC_SetPriority(SysTick_IRQn, 1);
	 NVIC_SetPriority(ADC1_2_IRQn, 0);
}

void SysTick_Handler() {
	ADC1->CR |= ADC_CR_ADSTART;
	// UART_Transmit((uint8_t*)"Systick\r\n", 10);
}

int main() {
	gpio_init();
	set_clock();
	systick_config();
	init_UART();
	init_ADC();
	systick_disable();

	UART_Transmit((uint8_t*)"Start\r\n", 250);

	while (1) {
		UART_Transmit((uint8_t*)"> ", 2);
		read_cmd();

		// UART_Transmit((uint8_t*)buf, 50);
		if (strncmp(buf, "showid", 6) == 0) {
			UART_Transmit((uint8_t*)"0616015\r\n", 15);
		} else if (strncmp(buf, "light", 5) == 0) {
			systick_enable();
			char c;
			do {
				c = receive_char();
			} while (c != 'q');
			systick_disable();
		} else if (strncmp(buf, "led on", 6) == 0) {
			GPIOA->ODR |= (1 << 5);
		} else if (strncmp(buf, "led off", 7) == 0) {
			GPIOA->ODR &= ~(1 << 5);
		} else {
			UART_Transmit((uint8_t*)"Unknown Command\r\n", 20);
		}
	}
}
```
#### Receive a Single Character

```c
char receive_char() {
	while (!(USART1->ISR & USART_ISR_RXNE));
	// USART1->RQR |= USART_RQR_RXFRQ;
	USART1->ISR = USART1->ISR & ~USART_ISR_RXNE;
	return USART1->RDR & 0xFF;
}
```
`USART_ISR` 中的 `RXNE` 為 1 代表 `USART_RDR` 上有讀進東西，可以被 return 拿走。

#### Receive a Line

```c
void read_cmd() {
	int ptr = 0;
	char c;
	do {
		c = receive_char();
		buf[ptr++] = c;
		UART_Transmit((uint8_t*)&c, 1);
	} while (c != '\n' && c != '\r');
	buf[ptr++] = '\0';
	UART_Transmit((uint8_t*)"\r\n", 2);
}
```
這裡一次讀取一個字直到讀到 `\r` 或 `\n`，為了使用者體驗所以特別把讀入的值直接輸出。

## Feedback

覺得有了 UART 之後可以在很多應用上讓介面變得更人性，至少可以傳輸到電腦上提供文字說明，不像之前只有 code 本體以及器材們，感覺很使用者不友善。

另外 ADC 讓我們可以抓到類比訊號感覺也很實用，而且說不定對我們的期末專題要側錄紅外線訊號有幫助。

## Reference

- [Datasheet](https://www.st.com/resource/en/datasheet/stm32f303vc.pdf)