STM32 LibOpenCM3:USART 接收

系列:簡單入門 LibOpenCM3 STM32 嵌入式系統開發 Posted on 2022-09-23

前言

上一篇中我介紹了 USART 的發送,這次要來寫 USART 的接收了。由於透過輪詢的方式實現 USART 的接收實在是不是一個好的寫法,因此我們會直接使用中斷(Interrupt)的方式來達成。

這次的功能爲讓 STM32 將 USART 接收到的資料原封不動丟回去,且收到資料時 LED 會閃一下。

正文

一樣先以 Nucleo-F446RE 做示範。

首先建立一個 PIO 的專案,選擇 Framework 爲「libopencm3」,並在 src/ 資料夾中新增並開啓 main.c 檔案。

完整程式

 1/**
 2 * @file   main.c
 3 * @brief  USART with receive interrupt for STM32 Nucleo-F446RE.
 4 */
 5
 6#include <libopencm3/stm32/rcc.h>
 7#include <libopencm3/stm32/gpio.h>
 8#include <libopencm3/stm32/usart.h>
 9#include <libopencm3/cm3/nvic.h>
10
11#define USART_BAUDRATE (9600)
12
13#define RCC_USART_TXRX_GPIO (RCC_GPIOA)
14#define GPIO_USART_TXRX_PORT (GPIOA)
15#define GPIO_USART_TX_PIN (GPIO2) /* D1. */
16#define GPIO_USART_RX_PIN (GPIO3) /* D0. */
17#define GPIO_USART_AF (GPIO_AF7)  /* Ref: Table-11 in DS10693. */
18
19#define RCC_LED_GPIO (RCC_GPIOA)
20#define GPIO_LED_PORT (GPIOA)
21#define GPIO_LED_PIN (GPIO5) /* D13. */
22
23static void delay(uint32_t value)
24{
25  for (uint32_t i = 0; i < value; i++)
26  {
27    __asm__("nop"); /* Do nothing. */
28  }
29}
30
31static void rcc_setup(void)
32{
33  rcc_periph_clock_enable(RCC_LED_GPIO);
34  rcc_periph_clock_enable(RCC_USART_TXRX_GPIO);
35  rcc_periph_clock_enable(RCC_USART2);
36}
37
38static void usart_setup(void)
39{
40  /* Set USART-Tx & Rx pin to alternate function. */
41  gpio_mode_setup(GPIO_USART_TXRX_PORT,
42                  GPIO_MODE_AF,
43                  GPIO_PUPD_NONE,
44                  GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
45
46  gpio_set_af(GPIO_USART_TXRX_PORT,
47              GPIO_USART_AF,
48              GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
49
50  /* Setup interrupt. */
51  nvic_enable_irq(NVIC_USART2_IRQ);
52  usart_enable_rx_interrupt(USART2); /* Enable receive interrupt. */
53
54  /* Config USART params. */
55  usart_set_baudrate(USART2, USART_BAUDRATE);
56  usart_set_databits(USART2, 8);
57  usart_set_stopbits(USART2, USART_STOPBITS_1);
58  usart_set_parity(USART2, USART_PARITY_NONE);
59  usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
60  usart_set_mode(USART2, USART_MODE_TX_RX);
61
62  usart_enable(USART2);
63}
64
65static void led_setup(void)
66{
67  /* Set LED pin to output push-pull. */
68  gpio_mode_setup(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_LED_PIN);
69  gpio_set_output_options(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_LED_PIN);
70}
71
72int main(void)
73{
74  rcc_setup();
75  led_setup();
76  usart_setup();
77
78  while (1)
79  { /* Halt. */ }
80  return 0;
81}
82
83/**
84 * @brief USART2 Interrupt service routine.
85 */
86void usart2_isr(void)
87{
88  gpio_set(GPIO_LED_PORT, GPIO_LED_PIN); /* LED on. */
89
90  uint8_t indata = usart_recv(USART2); /* Read. */
91  usart_send_blocking(USART2, indata); /* Send. */
92
93  delay(100000);
94  gpio_clear(GPIO_LED_PORT, GPIO_LED_PIN); /* LED off. */
95
96  USART_SR(USART2) &= ~USART_SR_RXNE; /* Clear 'Read data register not empty' flag. */
97}

分段說明

Include

1#include <libopencm3/stm32/rcc.h>
2#include <libopencm3/stm32/gpio.h>
3#include <libopencm3/stm32/usart.h>
4#include <libopencm3/cm3/nvic.h>

因爲會用到中斷的功能,所以記得要引入 nvic.h

GPIO 腳位

1#define RCC_USART_TXRX_GPIO (RCC_GPIOA)
2#define GPIO_USART_TXRX_PORT (GPIOA)
3#define GPIO_USART_TX_PIN (GPIO2) /* D1. */
4#define GPIO_USART_RX_PIN (GPIO3) /* D0. */
5#define GPIO_USART_AF (GPIO_AF7)  /* Ref: Table-11 in DS10693. */
6
7#define RCC_LED_GPIO (RCC_GPIOA)
8#define GPIO_LED_PORT (GPIOA)
9#define GPIO_LED_PIN (GPIO5) /* D13. */

這裡一樣使用 Nucleo 開發板規劃好的 USART2,其 Tx 與 Rx 腳分別爲 PA2 與 PA3。LED 一樣是 PA5。

RCC

1static void rcc_setup(void)
2{
3  rcc_periph_clock_enable(RCC_LED_GPIO);
4  rcc_periph_clock_enable(RCC_USART_TXRX_GPIO);
5  rcc_periph_clock_enable(RCC_USART2);
6}

除了要致能 USART Tx/Rx 與 LED 所在的 GPIO Port 外,也要記得致能 USART 本身。

由於此例中 USART Tx/Rx 與 LED 都位於 GPIO Port-A,其實可以只致能一次就好。

USART 設定

 1static void usart_setup(void)
 2{
 3  /* Set USART-Tx & Rx pin to alternate function. */
 4  gpio_mode_setup(GPIO_USART_TXRX_PORT,
 5                  GPIO_MODE_AF,
 6                  GPIO_PUPD_NONE,
 7                  GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
 8
 9  gpio_set_af(GPIO_USART_TXRX_PORT,
10              GPIO_USART_AF,
11              GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
12
13  /* Setup interrupt. */
14  nvic_enable_irq(NVIC_USART2_IRQ);
15  usart_enable_rx_interrupt(USART2); /* Enable receive interrupt. */
16
17  /* Config USART params. */
18  usart_set_baudrate(USART2, USART_BAUDRATE);
19  usart_set_databits(USART2, 8);
20  usart_set_stopbits(USART2, USART_STOPBITS_1);
21  usart_set_parity(USART2, USART_PARITY_NONE);
22  usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
23  usart_set_mode(USART2, USART_MODE_TX_RX);
24
25  usart_enable(USART2);
26}

先設定 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 * @brief USART2 Interrupt service routine.
 3 */
 4void usart2_isr(void)
 5{
 6  gpio_set(GPIO_LED_PORT, GPIO_LED_PIN); /* LED on. */
 7
 8  uint8_t indata = usart_recv(USART2); /* Read. */
 9  usart_send_blocking(USART2, indata); /* Send. */
10
11  delay(100000);
12  gpio_clear(GPIO_LED_PORT, GPIO_LED_PIN); /* LED off. */
13
14  USART_SR(USART2) &= ~USART_SR_RXNE; /* Clear 'Read data register not empty' flag. */
15}

這是 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

 1static void usart_setup(void)
 2{
 3  /* Set USART-Tx & Rx pin to alternate function. */
 4#if defined(STM32F1)
 5  gpio_set_mode(GPIO_USART_TXRX_PORT,
 6                GPIO_MODE_OUTPUT_50_MHZ,
 7                GPIO_CNF_OUTPUT_ALTFN_PUSHPULL,
 8                GPIO_USART_TX_PIN);
 9
10  gpio_set_mode(GPIO_USART_TXRX_PORT,
11                GPIO_MODE_INPUT,
12                GPIO_CNF_INPUT_FLOAT,
13                GPIO_USART_RX_PIN);
14#else
15  gpio_mode_setup(GPIO_USART_TXRX_PORT,
16                  GPIO_MODE_AF,
17                  GPIO_PUPD_NONE,
18                  GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
19
20  gpio_set_af(GPIO_USART_TXRX_PORT,
21              GPIO_USART_AF,
22              GPIO_USART_TX_PIN | GPIO_USART_RX_PIN);
23#endif
24
25  /* 省略部分程式 */
26}
 1static void led_setup(void)
 2{
 3  /* Set LED pin to output push-pull. */
 4#if defined(STM32F1)
 5  gpio_set_mode(GPIO_LED_PORT, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_LED_PIN);
 6#else
 7  gpio_mode_setup(GPIO_LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_LED_PIN);
 8  gpio_set_output_options(GPIO_LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_LED_PIN);
 9#endif
10}

小結

這次介紹了 USART 的接收寫法,並且是以中斷的方式實現的。STM32 的中斷用法大同小異,都是致能 IRQ,然後實作對應的 ISR,應該不會太難。

參考資料

本文的程式也有放在 GitHub 上。
本文同步發表於 iT 邦幫忙-2022 iThome 鐵人賽



留言可能不會立即顯示。若過了幾天仍未出現,請 Email 聯繫:)

comments powered by Disqus