前言
上一篇中我介紹了 USART 的發送,這次要來寫 USART 的接收了。由於透過輪詢的方式實現 USART 的接收實在是不是一個好的寫法,因此我們會直接使用中斷(Interrupt)的方式來達成。
這次的功能為讓 STM32 將 USART 接收到的資料原封不動丟回去,且收到資料時 LED 會閃一下。
正文
一樣先以 Nucleo-F446RE 做示範。
首先建立一個 PIO 的專案,選擇 Framework 為「libopencm3」,並在 src/
資料夾中新增並開啓 main.c
檔案。
完整程式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
|
#include <libopencm3/stm32/rcc.h> #include <libopencm3/stm32/gpio.h> #include <libopencm3/stm32/usart.h> #include <libopencm3/cm3/nvic.h>
#define USART_BAUDRATE (9600)
#define RCC_USART_TXRX_GPIO (RCC_GPIOA) #define GPIO_USART_TXRX_PORT (GPIOA) #define GPIO_USART_TX_PIN (GPIO2) #define GPIO_USART_RX_PIN (GPIO3) #define GPIO_USART_AF (GPIO_AF7)
#define RCC_LED_GPIO (RCC_GPIOA) #define GPIO_LED_PORT (GPIOA) #define GPIO_LED_PIN (GPIO5)
static void delay(uint32_t value) { for (uint32_t i = 0; i < value; i++) { __asm__("nop"); } }
static void rcc_setup(void) { rcc_periph_clock_enable(RCC_LED_GPIO); rcc_periph_clock_enable(RCC_USART_TXRX_GPIO); rcc_periph_clock_enable(RCC_USART2); }
static void usart_setup(void) { gpio_mode_setup(GPIO_USART_TXRX_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
gpio_set_af(GPIO_USART_TXRX_PORT, GPIO_USART_AF, GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
nvic_enable_irq(NVIC_USART2_IRQ); usart_enable_rx_interrupt(USART2);
usart_set_baudrate(USART2, USART_BAUDRATE); usart_set_databits(USART2, 8); usart_set_stopbits(USART2, USART_STOPBITS_1); usart_set_parity(USART2, USART_PARITY_NONE); usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE); usart_set_mode(USART2, USART_MODE_TX_RX);
usart_enable(USART2); }
static void led_setup(void) { gpio_mode_setup(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_LED_PIN); gpio_set_output_options(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_LED_PIN); }
int main(void) { rcc_setup(); led_setup(); usart_setup();
while (1) { } return 0; }
void usart2_isr(void) { gpio_set(GPIO_LED_PORT, GPIO_LED_PIN);
uint8_t indata = usart_recv(USART2); usart_send_blocking(USART2, indata);
delay(100000); gpio_clear(GPIO_LED_PORT, GPIO_LED_PIN);
USART_SR(USART2) &= ~USART_SR_RXNE; }
|
分段說明
Include
1 2 3 4
| #include <libopencm3/stm32/rcc.h> #include <libopencm3/stm32/gpio.h> #include <libopencm3/stm32/usart.h> #include <libopencm3/cm3/nvic.h>
|
因為會用到中斷的功能,所以記得要引入 nvic.h
。
GPIO 腳位
1 2 3 4 5 6 7 8 9
| #define RCC_USART_TXRX_GPIO (RCC_GPIOA) #define GPIO_USART_TXRX_PORT (GPIOA) #define GPIO_USART_TX_PIN (GPIO2) #define GPIO_USART_RX_PIN (GPIO3) #define GPIO_USART_AF (GPIO_AF7)
#define RCC_LED_GPIO (RCC_GPIOA) #define GPIO_LED_PORT (GPIOA) #define GPIO_LED_PIN (GPIO5)
|
這裡一樣使用 Nucleo 開發板規劃好的 USART2,其 Tx 與 Rx 腳分別為 PA2 與 PA3。LED 一樣是 PA5。
RCC
1 2 3 4 5 6
| static void rcc_setup(void) { rcc_periph_clock_enable(RCC_LED_GPIO); rcc_periph_clock_enable(RCC_USART_TXRX_GPIO); rcc_periph_clock_enable(RCC_USART2); }
|
除了要致能 USART Tx/Rx 與 LED 所在的 GPIO Port 外,也要記得致能 USART 本身。
由於此例中 USART Tx/Rx 與 LED 都位於 GPIO Port-A,其實可以只致能一次就好。
USART 設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| static void usart_setup(void) { gpio_mode_setup(GPIO_USART_TXRX_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
gpio_set_af(GPIO_USART_TXRX_PORT, GPIO_USART_AF, GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
nvic_enable_irq(NVIC_USART2_IRQ); usart_enable_rx_interrupt(USART2);
usart_set_baudrate(USART2, USART_BAUDRATE); usart_set_databits(USART2, 8); usart_set_stopbits(USART2, USART_STOPBITS_1); usart_set_parity(USART2, USART_PARITY_NONE); usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE); usart_set_mode(USART2, USART_MODE_TX_RX);
usart_enable(USART2); }
|
先設定 GPIO。我們要把 USART 的 Tx 與 Rx 都設定成 Alternate Function。
再來使用 nvic_enable_irq()
來致能 USART 的 IRQ,usart_enable_rx_interrupt()
致能 USART 的接收中斷。
最後就是設定 USART 的通訊設置(鮑率、資料位元、停止位元等),值得注意的是因為我們這次需要同時啓用接收(Rx)與發送(Tx),所以 usart_set_mode()
的引數是 USART_MODE_TX_RX
。
USART ISR
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
void usart2_isr(void) { gpio_set(GPIO_LED_PORT, GPIO_LED_PIN);
uint8_t indata = usart_recv(USART2); usart_send_blocking(USART2, indata);
delay(100000); gpio_clear(GPIO_LED_PORT, GPIO_LED_PIN);
USART_SR(USART2) &= ~USART_SR_RXNE; }
|
這是 USART2 的 ISR,其名稱 usart2_isr
是固定的,不能打錯。當 STM32 從 USART2 接收到資料時就會產生 IRQ 並執行此 ISR。
使用 usart_recv()
函式來讀取接收到的資料,再用 usart_send_blocking()
把資料直接傳回去。
USART_SR(USART2) &= ~USART_SR_RXNE
是用來清除「接收資料非空(RXNE)」旗標的。
多環境程式(F446RE + F103RB)
由於 STM32F1 的部分函式不同,所以 F103RB 沒辦法直接使用上面的 F446RE 的程式。
以下列出主要的差異部分,也就是 GPIO 的部分。完整的程式請看 GitHub repo。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| static void usart_setup(void) { #if defined(STM32F1) gpio_set_mode(GPIO_USART_TXRX_PORT, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_USART_TX_PIN);
gpio_set_mode(GPIO_USART_TXRX_PORT, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_USART_RX_PIN); #else gpio_mode_setup(GPIO_USART_TXRX_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
gpio_set_af(GPIO_USART_TXRX_PORT, GPIO_USART_AF, GPIO_USART_TX_PIN | GPIO_USART_RX_PIN); #endif
}
|
1 2 3 4 5 6 7 8 9 10
| static void led_setup(void) { #if defined(STM32F1) gpio_set_mode(GPIO_LED_PORT, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_LED_PIN); #else gpio_mode_setup(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_LED_PIN); gpio_set_output_options(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_LED_PIN); #endif }
|
小結
這次介紹了 USART 的接收寫法,並且是以中斷的方式實現的。STM32 的中斷用法大同小異,都是致能 IRQ,然後實作對應的 ISR,應該不會太難。
參考資料
本文的程式也有放在 GitHub 上。
本文同步發表於 iT 邦幫忙-2022 iThome 鐵人賽。