# 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

#### 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)