STM32 LibOpenCM3:CRC

前言

CRC(Cyclic redundancy check)即循環冗餘校驗是一種雜湊函式,通常用於通訊,用以讓接收方確認資料是否正確。

多數的 STM32 家族都有內建 CRC 計算單元,本篇要來介紹如何使用。

正文

首先一樣以 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
/**
* @file main.c
* @brief CRC example for STM32 Nucleo-F446RE.
*/

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/crc.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) /* ST-Link (Arduino-D1). */
#define GPIO_USART_RX_PIN (GPIO3) /* ST-Link (Arduino-D0). */
#define GPIO_USART_AF (GPIO_AF7) /* Table-11 in DS10693. */

static void rcc_setup(void)
{
rcc_periph_clock_enable(RCC_USART_TXRX_GPIO);
rcc_periph_clock_enable(RCC_USART2);
rcc_periph_clock_enable(RCC_CRC);
}

static void usart_setup(void)
{
/* Set USART-Tx and Rx pin to alternate function. */
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);

/* Config USART params. */
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);

/* Setup interrupt. */
usart_enable_rx_interrupt(USART2); /* Enable receive interrupt. */
nvic_enable_irq(NVIC_USART2_IRQ);

usart_enable(USART2);
}

int main(void)
{
rcc_setup();
usart_setup();

usart_send_blocking(USART2, 'C');
usart_send_blocking(USART2, 'R');
usart_send_blocking(USART2, 'C');
usart_send_blocking(USART2, '\r');
usart_send_blocking(USART2, '\n');

while (1)
{ }
return 0;
}

/**
* @brief USART2 Interrupt service routine.
*/
void usart2_isr(void)
{
usart_disable_rx_interrupt(USART2);
crc_reset(); /* Resets the CRC calculation unit and sets the data register to 0xFFFF FFFF. */

uint8_t data[4];
for (int i = 0; i < 4; i++)
{
data[i] = usart_recv_blocking(USART2);
}

uint32_t comb = data[3] + (data[2] << 8) + (data[1] << 16) + (data[0] << 24);
uint32_t result = crc_calculate(comb);

usart_send_blocking(USART2, (result >> 24) & 0xFF);
usart_send_blocking(USART2, (result >> 16) & 0xFF);
usart_send_blocking(USART2, (result >> 8) & 0xFF);
usart_send_blocking(USART2, result & 0xFF);

USART_SR(USART2) &= ~USART_SR_RXNE; /* Clear 'Read data register not empty' flag. */
usart_enable_rx_interrupt(USART2);
}

分段說明

CRC 計算

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
/**
* @brief USART2 Interrupt service routine.
*/
void usart2_isr(void)
{
usart_disable_rx_interrupt(USART2);
crc_reset(); /* Resets the CRC calculation unit and sets the data register to 0xFFFF FFFF. */

uint8_t data[4];
for (int i = 0; i < 4; i++)
{
data[i] = usart_recv_blocking(USART2);
}

uint32_t comb = data[3] + (data[2] << 8) + (data[1] << 16) + (data[0] << 24);
uint32_t result = crc_calculate(comb);

usart_send_blocking(USART2, (result >> 24) & 0xFF);
usart_send_blocking(USART2, (result >> 16) & 0xFF);
usart_send_blocking(USART2, (result >> 8) & 0xFF);
usart_send_blocking(USART2, result & 0xFF);

USART_SR(USART2) &= ~USART_SR_RXNE; /* Clear 'Read data register not empty' flag. */
usart_enable_rx_interrupt(USART2);
}

CRC 計算單元的使用方式很單純,因此我直接寫在 USART2 的 ISR 中。

但 ISR 執行後,先禁能 USART2 的中斷,以方便之後連續讀取 4 Byte 的資料。

crc_reset() 重設 CRC 單元,並將資料暫存器重置為 0xFFFF FFFF(即 CRC Init = 0xFFFF FFFF)。

for 迴圈連續接收 4 Byte 的資料,並使用 crc_calculate() 將要計算的資料寫入 CRC 的資料暫存器,該函式會自行 Blocking 直到 CRC 計算完就會回傳結果。

最後再將結果用 USART2 傳出,再重新致能其中斷以等待下次接收。

多環境程式(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
static void usart_setup(void)
{
/* Set USART-Tx and Rx pin to alternate function. */
#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
/* 省略部分程式. */
}

成果

從 RM0390 或 AN4187 中可以得知,STM32 使用的多項式是 0x4C1 1DB7(部分系列可修改),初始值為 0xFFFF FFFF

我依序輸入 32 位元的資料並各別得到其結果:

  • 輸入 0x9D 12 3A D4 得到 0xC9 68 5F 5E
  • 輸入 0x00 00 00 00 得到 0xC7 04 DD 7B
  • 輸入 ABCD (ASCII) 得到 0xAB CF 9A 63

可以到一些線上的 CRC 計算機(如這個)驗證其結果是正確的(算法選擇「CRC-32/MPEG-2」)。

▲ STM32 各系列的 CRC 單元功能比較。取自 AN4187 Rev1 P.13。

小結

CRC 的使用還是滿單純的,就只要致能 RCC 後呼叫計算函式,將要計算的資料傳入後就可以得到結果了。

參考資料

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


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