STM32 LibOpenCM3:SPI (Slave mode)

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

前言

上次已經介紹了 SPI 作爲 Master device 的程式,這次要接著介紹作爲 Slave device 的程式寫法,讓 Master 與 Slave 可以互相溝通。

正文

首先一樣以 Nucleo-F446RE 做示範。

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

完整程式

  1/**
  2 * @file   main.c
  3 * @brief  SPI slave mode example for STM32 Nucleo-F446RE.
  4 */
  5
  6#include "main.h"
  7
  8int main(void)
  9{
 10  rcc_setup();
 11  usart_setup();
 12  spi_setup();
 13  spi_rq_setup();
 14
 15  usart_send_blocking(USART2, 's');
 16  usart_send_blocking(USART2, 'l');
 17  usart_send_blocking(USART2, 'a');
 18  usart_send_blocking(USART2, 'v');
 19  usart_send_blocking(USART2, 'e');
 20  usart_send_blocking(USART2, '\r');
 21  usart_send_blocking(USART2, '\n');
 22
 23  while (1)
 24  { }
 25  return 0;
 26}
 27
 28static void rcc_setup(void)
 29{
 30  rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_168MHZ]);
 31
 32  rcc_periph_clock_enable(RCC_GPIOA);
 33  rcc_periph_clock_enable(RCC_GPIOC);
 34  rcc_periph_clock_enable(RCC_USART2);
 35  rcc_periph_clock_enable(RCC_SPI1);
 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  /* Setup USART config. */
 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 spi_setup(void)
 66{
 67  /* Set SPI pins to alternate function. */
 68  gpio_mode_setup(GPIO_SPI_PORT,
 69                  GPIO_MODE_AF,
 70                  GPIO_PUPD_NONE,
 71                  GPIO_SPI_SCK_PIN | GPIO_SPI_MISO_PIN | GPIO_SPI_MOSI_PIN | GPIO_SPI_CS_PIN);
 72
 73  gpio_set_output_options(GPIO_SPI_PORT,
 74                          GPIO_OTYPE_PP,
 75                          GPIO_OSPEED_50MHZ,
 76                          GPIO_SPI_MISO_PIN);
 77
 78  gpio_set_af(GPIO_SPI_PORT,
 79              GPIO_SPI_AF,
 80              GPIO_SPI_SCK_PIN | GPIO_SPI_MISO_PIN | GPIO_SPI_MOSI_PIN | GPIO_SPI_CS_PIN);
 81
 82  spi_disable(SPI1);
 83  spi_reset(SPI1);
 84
 85  /* SPI init. */
 86  spi_init_master(SPI1,
 87                  SPI_CR1_BAUDRATE_FPCLK_DIV_64,   /* Clock baudrate. */
 88                  SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, /* CPOL = 0. */
 89                  SPI_CR1_CPHA_CLK_TRANSITION_2,   /* CPHA = 1. */
 90                  SPI_CR1_DFF_8BIT,                /* Data frame format. */
 91                  SPI_CR1_MSBFIRST);               /* Data frame bit order. */
 92  spi_set_slave_mode(SPI1);                        /* Set to slave mode. */
 93  spi_set_full_duplex_mode(SPI1);
 94
 95  /*
 96   * Set to hardware NSS management and NSS output disable.
 97   * The NSS pin works as a standard “chip select” input in slave mode.
 98   */
 99  spi_disable_software_slave_management(SPI1); /* SSM = 0. */
100  spi_disable_ss_output(SPI1);                 /* SSOE = 0. */
101
102  /* Serup interrupt. */
103  spi_enable_rx_buffer_not_empty_interrupt(SPI1);
104  nvic_enable_irq(NVIC_SPI1_IRQ);
105
106  spi_enable(SPI1);
107}
108
109static void spi_rq_setup(void)
110{
111  /* Set RQ pin to output push-pull. */
112  gpio_mode_setup(GPIO_SPI_RQ_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_SPI_RQ_PIN);
113  gpio_set_output_options(GPIO_SPI_RQ_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_25MHZ, GPIO_SPI_RQ_PIN);
114
115  spi_rq_reset();
116}
117
118static void spi_rq_set(void)
119{
120  gpio_clear(GPIO_SPI_RQ_PORT, GPIO_SPI_RQ_PIN);
121}
122
123static void spi_rq_reset(void)
124{
125  gpio_set(GPIO_SPI_RQ_PORT, GPIO_SPI_RQ_PIN);
126}
127
128/**
129 * @brief USART2 Interrupt service routine.
130 */
131void usart2_isr(void)
132{
133  uint8_t indata = usart_recv(USART2); /* Read received data. */
134  spi_send(SPI1, indata);              /* Put data into buffer. */
135  spi_rq_set();                        /* Request master device to select this device. */
136
137  /* Clear 'Read data register not empty' flag. */
138  USART_SR(USART2) &= ~USART_SR_RXNE;
139}
140
141/**
142 * @brief SPI1 Interrupt service routine.
143 */
144void spi1_isr(void)
145{
146  /* Wait for 'Busy' flag to reset. */
147  while ((SPI_SR(SPI1) & SPI_SR_BSY))
148  {
149  }
150
151  uint8_t indata = spi_read(SPI1);
152  spi_rq_reset();
153  usart_send_blocking(USART2, indata);
154
155  /* Clear 'Read data register not empty' flag. */
156  SPI_SR(SPI1) &= ~SPI_SR_RXNE;
157}
 1/* @file main.h */
 2
 3#ifndef MAIN_H
 4#define MAIN_H
 5
 6#include <libopencm3/stm32/rcc.h>
 7#include <libopencm3/stm32/gpio.h>
 8#include <libopencm3/stm32/spi.h>
 9#include <libopencm3/stm32/usart.h>
10#include <libopencm3/cm3/nvic.h>
11
12#define USART_BAUDRATE (9600)
13
14#define GPIO_SPI_PORT (GPIOA)
15#define GPIO_SPI_SCK_PIN (GPIO5)  /* D13. */
16#define GPIO_SPI_MISO_PIN (GPIO6) /* D12. */
17#define GPIO_SPI_MOSI_PIN (GPIO7) /* D11. */
18#define GPIO_SPI_CS_PIN (GPIO4)   /* A2. */
19#define GPIO_SPI_AF (GPIO_AF5)    /* Ref: Table-11 in DS10693. */
20
21#define GPIO_SPI_RQ_PORT (GPIOC)
22#define GPIO_SPI_RQ_PIN (GPIO7) /* D9. */
23
24#define GPIO_USART_TXRX_PORT (GPIOA)
25#define GPIO_USART_TX_PIN (GPIO2) /* ST-Link (D1). */
26#define GPIO_USART_RX_PIN (GPIO3) /* ST-Link (D0). */
27#define GPIO_USART_AF (GPIO_AF7)  /* Ref: Table-11 in DS10693. */
28
29static void rcc_setup(void);
30static void usart_setup(void);
31static void spi_setup(void);
32static void spi_rq_setup(void);
33
34static void spi_rq_set(void);
35static void spi_rq_reset(void);
36
37#endif /* MAIN_H. */

分段說明

設定 SPI

 1static void spi_setup(void)
 2{
 3  /* Set SPI pins to alternate function. */
 4  gpio_mode_setup(GPIO_SPI_PORT,
 5                  GPIO_MODE_AF,
 6                  GPIO_PUPD_NONE,
 7                  GPIO_SPI_SCK_PIN | GPIO_SPI_MISO_PIN | GPIO_SPI_MOSI_PIN | GPIO_SPI_CS_PIN);
 8
 9  gpio_set_output_options(GPIO_SPI_PORT,
10                          GPIO_OTYPE_PP,
11                          GPIO_OSPEED_50MHZ,
12                          GPIO_SPI_MISO_PIN);
13
14  gpio_set_af(GPIO_SPI_PORT,
15              GPIO_SPI_AF,
16              GPIO_SPI_SCK_PIN | GPIO_SPI_MISO_PIN | GPIO_SPI_MOSI_PIN | GPIO_SPI_CS_PIN);
17
18  spi_disable(SPI1);
19  spi_reset(SPI1);
20
21  /* SPI init. */
22  spi_init_master(SPI1,
23                  SPI_CR1_BAUDRATE_FPCLK_DIV_64,   /* Clock baudrate. */
24                  SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, /* CPOL = 0. */
25                  SPI_CR1_CPHA_CLK_TRANSITION_2,   /* CPHA = 1. */
26                  SPI_CR1_DFF_8BIT,                /* Data frame format. */
27                  SPI_CR1_MSBFIRST);               /* Data frame bit order. */
28  spi_set_slave_mode(SPI1);                        /* Set to slave mode. */
29  spi_set_full_duplex_mode(SPI1);
30
31  /*
32   * Set to hardware NSS management and NSS output disable.
33   * The NSS pin works as a standard “chip select” input in slave mode.
34   */
35  spi_disable_software_slave_management(SPI1); /* SSM = 0. */
36  spi_disable_ss_output(SPI1);                 /* SSOE = 0. */
37
38  /* Serup interrupt. */
39  spi_enable_rx_buffer_not_empty_interrupt(SPI1);
40  nvic_enable_irq(NVIC_SPI1_IRQ);
41
42  spi_enable(SPI1);
43}

這部分與設定 Master 時的類似。不過要注意的是,Master device 的 CS(NSS)腳不受 AF 控制,但 Slave device 的會,所以 CS 腳也要設爲 AF。

SPI 本身的設定如 CPOL 與 CPHA 要與 Master 一致才可以正常通訊,這裡設爲 CPOL=0 CPHA=1

然後先使用 spi_init_master() 初始化 SPI 的相關設定,再以 spi_set_slave_mode() 設定成 Slave mode。

一樣以 spi_set_full_duplex_mode() 設爲全雙工模式。

再來,爲了要使用硬體 CS,所以要將 SSM 和 SSOE 都設爲 0。這裡呼叫 spi_disable_software_slave_management()spi_disable_ss_output() 來完成設定。

NSS output disable (SSM=0, SSOE = 0): In slave mode, the NSS pin works as a standard “chip select” input and the slave is selected while NSS line is at low level.
節錄自 RM0390 Rev6 P.854。

之後再啓用 SPI 的中斷功能。

SPI ISR

 1/**
 2 * @brief SPI1 Interrupt service routine.
 3 */
 4void spi1_isr(void)
 5{
 6  /* Wait for 'Busy' flag to reset. */
 7  while ((SPI_SR(SPI1) & SPI_SR_BSY))
 8  {
 9  }
10
11  uint8_t indata = spi_read(SPI1);
12  spi_rq_reset();
13  usart_send_blocking(USART2, indata);
14
15  /* Clear 'Read data register not empty' flag. */
16  SPI_SR(SPI1) &= ~SPI_SR_RXNE;
17}

我們設定啓用 SPI 的「接收資料非空」中斷事件,因此 ISR 就負責讀取 Master 傳送的資料,若先前有 Slave 要傳送的資料也會在 CS 腳被下拉且 Master 發起 SCK 時脈訊號後傳送。

USART ISR

 1/**
 2 * @brief USART2 Interrupt service routine.
 3 */
 4void usart2_isr(void)
 5{
 6  uint8_t indata = usart_recv(USART2); /* Read received data. */
 7  spi_send(SPI1, indata);              /* Put data into buffer. */
 8  spi_rq_set();                        /* Request master device to select this device. */
 9
10  /* Clear 'Read data register not empty' flag. */
11  USART_SR(USART2) &= ~USART_SR_RXNE;
12}

當 USART 收到資料時,會將資料先用 spi_send() 寫入到傳送暫存器中,然後以 spi_rq_set() 將 RQ 腳拉低以請求 Master 進行通訊。

多環境程式(F446RE + F103RB)

由於 STM32F1 的部分函式不同,所以 F103RB 沒辦法直接使用上面的 F446RE 的程式。

由於這次程式較長,所以完整的程式請看 GitHub repo

成果

我使用兩塊 STM32 Nucleo 板分別當作 Master 與 Slave。將線都接好後就可以讓兩者互相溝通了,記得要共地。

小結

這次接續上次的 SPI Master,寫了 Slave 的操作介紹。其實用法基本上是差不多的,相信不會太難。

會使用 SPI 通常是要連接其它的模組,所以 STM32 通常是當作 Master 的角色,但如果想要自己用 STM32 做一個「模組」的話,就可以用到 SPI Slave 模式了。

參考資料

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



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

comments powered by Disqus