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