Skip to main content
Associate
July 2, 2026
Question

STM32F051SPI CRC error cannot generate error interrupt

I found an issue while using STM32F051, and the background is that I want to generate an interrupt when the host receives a CRC error, in order to perform my error handling in the interrupt handler. But when a CRC error occurred, the CPU did not jump into the interrupt service function. At this point, I checked that the CRCERR flag was raised and the ERRIE interrupt enable was also enabled. I have tried other conditions that can trigger error flags to generate interrupts, such as OVR, FRE, MODF, etc. These flag bits can generate interrupts and enter the interrupt service function, which proves that my interrupt enable steps are correct. I carefully reviewed the manual again, and it clearly states that when ERRIE is set, CRCERR can trigger an interrupt. So I want to know if there are some hardware defects that did not connect the CRCERR signal to the NVIC.

3 replies

mƎALLEm
ST Technical Moderator
September 4, 2026

Hello ​@ldxmz and sorry for the late reply,

1- According to the Errata sheet ES0202 - Rev 6, there is no mention of a such behavior/limitation.

2- How did you inject the SPI CRC error? How did you reproduce it?

3- Could you please share your code?

To give better visibility on the answered topics, please click "Best answer" on the reply which solved your issue or answered your question.
ldxmzAuthor
Associate
September 4, 2026

1. Yes, I have reviewed this document and did not find this situation.
2. I am connected through SPI with an external MCU (STM32H745ZI), which acts as a slave for SPI and prepares an incorrect CRC value in the sending buffer. When the host (STM32F051C8) provides a clock, both parties receive data from each other. The last frame of the host does not write data to the DR but sets the CRCNEXT bit, then waits for RXNE to read the DR and ends this transmission. At this point, I found that the CRCERR flag was raised but did not interrupt (I set a flag for observation in the interrupt and broke it to confirm that the interrupt did not enter).
3. There are many code snippets, and it is not convenient to provide them all. The main code snippets are as follows:

LL_SPI_EnableIT_ERR(SPIx);

spi_irq_nvic_enable((SpiInst_t)inst);



if (needNss) { SPI_NssLow((SpiInst_t)inst); savedNssLow = 1; }



/* 收 count 帧: 前 count-1 帧普通收, 末帧 CRCNEXT 触发 CRC 比对

所有帧的 RX 字节存到 g_spiRxData (复用已有缓冲, 避免新 bss) */

{

uint8_t *rxBuf = (uint8_t *)g_spiRxData;

for (i = 0; i < count; i++)

{

uint16_t dummy = is16bit ? 0xFFFFu : 0x00FFu;

uint16_t got;

if (i == count - 1)

{

got = SPI_Master_TxRxByte_CrcNext(SPIx, dummy); /* 末帧: 写 DR → 紧接 CRCNEXT */

}

else

{

got = SPI_Master_FullDuplex_TxRxByte(SPIx, dummy);

}

if (i < 128U) { rxBuf[i] = (uint8_t)(got & 0xFFu); }

}

rxBuf[count] = LL_SPI_ReceiveData8(SPIx);

}

SPI_WaitTransferComplete(SPIx); /* 等 BSY=0, 确保末帧发完 + CRC 比对完成 + CRCERR 已置位 */



/* 等 ISR 捕获 CRCERR (CRCERR置位 → ERRIE → ISR → g_spiIrqFired) */

fired = spi_irq_wait_fired(timeout);



if (savedNssLow) { SPI_NssHigh((SpiInst_t)inst); savedNssLow = 0; }

spi_irq_nvic_disable((SpiInst_t)inst);

LL_SPI_DisableIT_ERR(SPIx);

if (g_spiIrqFired) { fired = 1; }



/* RX 详情: 4 数据帧 + 末字节 (共 count 字节) */

{

char buf[300];

int off = snprintf(buf, sizeof(buf), "RX: ");

for (i = 0; i < count && off < (int)sizeof(buf) - 4; i++)

{

off += snprintf(buf + off, sizeof(buf) - off, "%02X ", ((uint8_t *)g_spiRxData)[i]);

}

LOGI("%s", buf);

}

/* FLAG 仅诊断: ISR 已清 CRCERR, 读数可能为 0, 但 pass 由 fired 判定 */

LOGI("FLAG: crcerr=%d (ISR may have cleared; pass by fired)", LL_SPI_IsActiveFlag_CRCERR(SPIx) ? 1 : 0);

}

 

ldxmzAuthor
Associate
September 24, 2026

STM32F051C8 SPI CRCERR does not generate an error interrupt

I observed an issue on STM32F051C8 where the SPI CRCERR flag is correctly set after a CRC mismatch, but no SPI interrupt is generated even when SPI_CR2.ERRIE is enabled.

To reproduce the issue, SPI1 is configured as master mode, 8-bit data size, CRC enabled, CRC polynomial 0x07, using direct register access without HAL, LL, DMA, or an external SPI slave. PA6/MISO is configured with an internal pull-up, so the received data and CRC frame are both 0xFF.

In the CRCERR test, the SPI peripheral calculates RXCRCR = 0xF3, while the received CRC frame is 0xFF, so a CRC mismatch is intentionally generated. The final register values are:

SPI_SR = 0x0012, where CRCERR = 1

SPI_CR2 = 0x1720, where ERRIE = 1

RXCRCR = 0xF3

Received CRC frame = 0xFF

Therefore, the CRC mismatch is correctly detected by the SPI peripheral. However, before enabling the NVIC interrupt, NVIC_GetPendingIRQ(SPI1_IRQn) returns 0, and after enabling the interrupt, SPI1_IRQHandler is never entered.

The CRCERR test result is:

stimulus_valid = 1

pending_before_enable = 0

irq_count = 0

sr_before_enable = 0x0012

cr2_before_enable = 0x1720

crc_pass = 0

As a control test, I deliberately generate an SPI receive overrun (OVR) using the same SPI1 peripheral, the same ERRIE bit, the same SPI1_IRQn, and the same SPI1_IRQHandler.

The OVR test result is:

OVR = 1

ERRIE = 1

pending_before_enable = 1

irq_count = 1

ovr_pass = 1

This confirms that the SPI error interrupt path, ERRIE configuration, NVIC configuration, vector table, and interrupt handler are working correctly.

I also performed a third control test using NVIC_SetPendingIRQ(SPI1_IRQn). The NVIC pending bit is correctly set and SPI1_IRQHandler is entered:

pending_before_enable = 1

irq_count = 1

nvic_pass = 1

The final comparison is therefore:

CRCERR = 1, ERRIE = 1 -> NVIC pending = 0, ISR not entered

OVR = 1, ERRIE = 1 -> NVIC pending = 1, ISR entered

NVIC software pending -> ISR entered

The attached debugger screenshot shows the complete g_spi_test structure after all three tests. The key result is crc_pass = 0, while ovr_pass = 1 and nvic_pass = 1.

Based on these results, CRC error detection itself is working correctly, and the general SPI1/NVIC interrupt path is also working correctly. The observed issue appears to be specific to the CRCERR to SPI interrupt request path.

Could you please confirm whether this is a known STM32F051C8 silicon limitation or erratum, or whether there is any additional condition required for CRCERR to generate an interrupt when ERRIE is enabled?
 

#include "stm32f0xx.h"

/*
 * STM32F051C8
 *
 * SPI1:
 *   PA5 = SCK
 *   PA6 = MISO
 *   PA7 = MOSI
 *
 * PA6 使用内部 Pull-Up。
 *
 * Test A:
 *   制造 CRC mismatch
 *   验证 CRCERR -> ERRIE -> SPI1 IRQ -> NVIC
 *
 * Test B:
 *   制造 RX overrun
 *   验证 OVR -> ERRIE -> SPI1 IRQ -> NVIC
 *
 * Test C:
 *   NVIC_SetPendingIRQ()
 *   验证 NVIC / vector table / ISR 本身正常
 */


/* ============================================================
 * 8-bit access to SPI DR
 * ============================================================ */

#define SPI1_DR8 (*((volatile uint8_t *)&SPI1->DR))


/* ============================================================
 * Test case identifier
 * ============================================================ */

typedef enum
{
    TEST_NONE = 0,
    TEST_CRCERR,
    TEST_OVR,
    TEST_NVIC
} TestCase_t;


volatile TestCase_t g_active_test = TEST_NONE;


/* ============================================================
 * Common IRQ test result
 * ============================================================ */

typedef struct
{
    /*
     * 对 CRCERR / OVR:
     *
     * 1 = 对应的 SPI status flag 确实已经置位,
     *     并且 ERRIE = 1
     *
     * 如果这里不是 1,则测试刺激本身失败,
     * 不能用于判断 IRQ 路径。
     */
    volatile uint32_t stimulus_valid;

    /*
     * NVIC 尚未 Enable 时读取 Pending。
     *
     * 外设 IRQ request 正常时应该为 1。
     */
    volatile uint32_t pending_before_enable;

    /*
     * SPI1_IRQHandler 实际进入次数
     */
    volatile uint32_t irq_count;

    /*
     * 进入 ISR 第一时间保存的 SPI_SR
     */
    volatile uint32_t sr_in_irq;

    /*
     * Enable NVIC 之前保存 SPI_SR
     */
    volatile uint32_t sr_before_enable;

    /*
     * Enable NVIC 之前保存 SPI_CR2
     */
    volatile uint32_t cr2_before_enable;

    /*
     * 最终结论:
     *
     * 1 = IRQ path 正常
     * 0 = IRQ path 未按预期工作
     */
    volatile uint32_t irq_path_pass;

} IRQ_TestResult;


/* ============================================================
 * Complete result structure
 * ============================================================ */

typedef struct
{
    /*
     * --------------------------------------------------------
     * Test A : CRCERR
     * --------------------------------------------------------
     */
    IRQ_TestResult crc;

    volatile uint32_t crc_rxcrcr;
    volatile uint32_t crc_txcrcr;

    volatile uint8_t crc_rx_data;
    volatile uint8_t crc_rx_frame;


    /*
     * --------------------------------------------------------
     * Test B : OVR
     * --------------------------------------------------------
     */
    IRQ_TestResult ovr;


    /*
     * --------------------------------------------------------
     * Test C : Software NVIC pending
     * --------------------------------------------------------
     */
    IRQ_TestResult nvic;


    /*
     * 方便 debugger 一眼看结论
     *
     * 理想的疑似 silicon bug 结果:
     *
     * crc_pass  = 0
     * ovr_pass  = 1
     * nvic_pass = 1
     */
    volatile uint32_t crc_pass;
    volatile uint32_t ovr_pass;
    volatile uint32_t nvic_pass;

} SPI_TestResult;


volatile SPI_TestResult g_spi_test;


/* ============================================================
 * Small delay / IRQ wait
 * ============================================================ */

static void WaitForIRQ(volatile uint32_t *counter)
{
    volatile uint32_t timeout = 200000;

    while ((*counter == 0U) && (timeout != 0U))
    {
        timeout--;
        __NOP();
    }
}


/* ============================================================
 * SPI1 interrupt handler
 * ============================================================ */

void SPI1_IRQHandler(void)
{
    uint32_t sr;

    /*
     * 第一时间读取状态。
     */
    sr = SPI1->SR;


    switch (g_active_test)
    {
        /* ----------------------------------------------------
         * CRCERR IRQ
         * -------------------------------------------------- */
        case TEST_CRCERR:

            g_spi_test.crc.irq_count++;
            g_spi_test.crc.sr_in_irq = sr;

            /*
             * 先禁止 ERRIE。
             *
             * 不需要在这里清 CRCERR,
             * 下一 Case 会直接 reset SPI1。
             */
            SPI1->CR2 &= ~SPI_CR2_ERRIE;

            break;


        /* ----------------------------------------------------
         * OVR IRQ
         * -------------------------------------------------- */
        case TEST_OVR:

            g_spi_test.ovr.irq_count++;
            g_spi_test.ovr.sr_in_irq = sr;

            /*
             * 防止重复进入。
             */
            SPI1->CR2 &= ~SPI_CR2_ERRIE;

            /*
             * 清 OVR:
             *
             * read DR
             * read SR
             *
             * 因为 RX FIFO 中可能有多个 byte,
             * 先把 RX FIFO drain 掉。
             */
            while ((SPI1->SR & SPI_SR_RXNE) != 0U)
            {
                volatile uint8_t dummy;

                dummy = SPI1_DR8;
                (void)dummy;
            }

            (void)SPI1->SR;

            break;


        /* ----------------------------------------------------
         * Software generated NVIC IRQ
         * -------------------------------------------------- */
        case TEST_NVIC:

            g_spi_test.nvic.irq_count++;

            /*
             * Test C 不依赖 SPI peripheral。
             */
            g_spi_test.nvic.sr_in_irq = 0;

            break;


        default:

            /*
             * 非测试期间如果意外进入 IRQ,
             * 直接禁止 ERRIE。
             */
            SPI1->CR2 &= ~SPI_CR2_ERRIE;

            break;
    }
}


/* ============================================================
 * GPIO Init
 * ============================================================ */

static void GPIO_SPI1_Init(void)
{
    /*
     * GPIOA Clock Enable
     */
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;


    /*
     * PA5 / PA6 / PA7
     *
     * Alternate Function mode
     */
    GPIOA->MODER &=
        ~((3U << (5U * 2U)) |
          (3U << (6U * 2U)) |
          (3U << (7U * 2U)));

    GPIOA->MODER |=
         (2U << (5U * 2U)) |
         (2U << (6U * 2U)) |
         (2U << (7U * 2U));


    /*
     * STM32F051:
     *
     * PA5 = SPI1_SCK  AF0
     * PA6 = SPI1_MISO AF0
     * PA7 = SPI1_MOSI AF0
     */

    GPIOA->AFR[0] &=
        ~((0xFU << (5U * 4U)) |
          (0xFU << (6U * 4U)) |
          (0xFU << (7U * 4U)));


    /*
     * PA6 = Pull-Up
     *
     * 无外部 slave 时:
     *
     * MISO = 1
     *
     * 收到:
     *
     * 0xFF
     */
    GPIOA->PUPDR &= ~(3U << (6U * 2U));

    GPIOA->PUPDR |=
         (1U << (6U * 2U));


    /*
     * SCK / MOSI high speed
     */
    GPIOA->OSPEEDR |=
         (3U << (5U * 2U)) |
         (3U << (7U * 2U));
}


/* ============================================================
 * Reset SPI1
 * ============================================================ */

static void SPI1_Reset(void)
{
    /*
     * 先禁止 NVIC,避免 Case 之间互相影响。
     */
    NVIC_DisableIRQ(SPI1_IRQn);
    NVIC_ClearPendingIRQ(SPI1_IRQn);


    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;


    RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;
    RCC->APB2RSTR &= ~RCC_APB2RSTR_SPI1RST;
}


/* ============================================================
 * Common SPI Init
 * ============================================================ */

static void SPI1_Common_Init(uint32_t enable_crc)
{
    SPI1_Reset();


    SPI1->CR1 = 0;
    SPI1->CR2 = 0;


    /*
     * CRCPR = 0x07
     */
    SPI1->CRCPR = 0x07;


    /*
     * CR1:
     *
     * Master
     * Software NSS
     * Baudrate = PCLK / 256
     * Mode 0
     * MSB first
     */

    SPI1->CR1 =
          SPI_CR1_MSTR
        | SPI_CR1_BR_0
        | SPI_CR1_BR_1
        | SPI_CR1_BR_2
        | SPI_CR1_SSM
        | SPI_CR1_SSI;


    if (enable_crc != 0U)
    {
        SPI1->CR1 |= SPI_CR1_CRCEN;
    }


    /*
     * CR2:
     *
     * DS = 7
     * 8-bit frame
     *
     * FRXTH = 1
     *
     * ERRIE = 1
     */
    SPI1->CR2 =
          (7U << 8)
        | SPI_CR2_FRXTH
        | SPI_CR2_ERRIE;
}


/* ============================================================
 *
 * TEST A
 *
 * CRCERR -> ERRIE -> NVIC
 *
 * ============================================================ */

static void Test_CRCERR(void)
{
    /*
     * --------------------------------------------------------
     * Prepare
     * --------------------------------------------------------
     */

    g_active_test = TEST_CRCERR;

    SPI1_Common_Init(1);


    /*
     * NVIC 保持 disabled。
     *
     * 我们要先观察 pending。
     */


    /*
     * Enable SPI
     */
    SPI1->CR1 |= SPI_CR1_SPE;


    /*
     * Wait TX ready
     */
    while ((SPI1->SR & SPI_SR_TXE) == 0U)
    {
    }


    /*
     * Send one data byte
     */
    SPI1_DR8 = 0xA5;


    /*
     * 下一 frame 进入 CRC phase。
     */
    SPI1->CR1 |= SPI_CR1_CRCNEXT;


    /*
     * Receive data byte.
     *
     * MISO pull-up:
     *
     * expected = FF
     */
    while ((SPI1->SR & SPI_SR_RXNE) == 0U)
    {
    }

    g_spi_test.crc_rx_data = SPI1_DR8;


    /*
     * Receive CRC frame.
     *
     * MISO 仍为 high:
     *
     * received CRC = FF
     */
    while ((SPI1->SR & SPI_SR_RXNE) == 0U)
    {
    }

    g_spi_test.crc_rx_frame = SPI1_DR8;


    /*
     * Wait transfer complete
     */
    while ((SPI1->SR & SPI_SR_BSY) != 0U)
    {
    }


    /*
     * --------------------------------------------------------
     * Capture evidence
     * --------------------------------------------------------
     */

    g_spi_test.crc.sr_before_enable = SPI1->SR;

    g_spi_test.crc.cr2_before_enable = SPI1->CR2;

    g_spi_test.crc_rxcrcr = SPI1->RXCRCR;
    g_spi_test.crc_txcrcr = SPI1->TXCRCR;


    /*
     * 确认:
     *
     * CRCERR = 1
     * ERRIE  = 1
     */

    if (((SPI1->SR & SPI_SR_CRCERR) != 0U) &&
        ((SPI1->CR2 & SPI_CR2_ERRIE) != 0U))
    {
        g_spi_test.crc.stimulus_valid = 1;
    }
    else
    {
        g_spi_test.crc.stimulus_valid = 0;
    }


    /*
     * --------------------------------------------------------
     * Critical observation #1
     *
     * NVIC Disabled 状态下:
     *
     * peripheral IRQ request 应该已经使 NVIC Pending = 1
     * --------------------------------------------------------
     */

    g_spi_test.crc.pending_before_enable =
        NVIC_GetPendingIRQ(SPI1_IRQn);


    /*
     * --------------------------------------------------------
     * Critical observation #2
     *
     * Enable NVIC。
     *
     * 如果 pending / peripheral IRQ request 正常,
     * 应进入 ISR。
     * --------------------------------------------------------
     */

    NVIC_EnableIRQ(SPI1_IRQn);

    WaitForIRQ(&g_spi_test.crc.irq_count);


    /*
     * 判断 IRQ path。
     *
     * 必须同时满足:
     *
     * stimulus valid
     * pending = 1
     * ISR entered
     */

    if ((g_spi_test.crc.stimulus_valid == 1U) &&
        (g_spi_test.crc.pending_before_enable == 1U) &&
        (g_spi_test.crc.irq_count > 0U))
    {
        g_spi_test.crc.irq_path_pass = 1;
    }
    else
    {
        g_spi_test.crc.irq_path_pass = 0;
    }


    g_spi_test.crc_pass =
        g_spi_test.crc.irq_path_pass;


    /*
     * Cleanup
     */
    NVIC_DisableIRQ(SPI1_IRQn);
    NVIC_ClearPendingIRQ(SPI1_IRQn);

    g_active_test = TEST_NONE;
}


/* ============================================================
 *
 * TEST B
 *
 * OVR -> ERRIE -> NVIC
 *
 * ============================================================ */

static void Test_OVR(void)
{
    uint32_t i;


    g_active_test = TEST_OVR;


    /*
     * CRC not needed.
     */
    SPI1_Common_Init(0);


    /*
     * Enable SPI
     */
    SPI1->CR1 |= SPI_CR1_SPE;


    /*
     * --------------------------------------------------------
     * 故意不读 RX DR。
     *
     * 连续发送多个 frame。
     *
     * RX FIFO 填满后继续接收,
     * 应产生 OVR。
     * --------------------------------------------------------
     */

    for (i = 0; i < 16U; i++)
    {
        while ((SPI1->SR & SPI_SR_TXE) == 0U)
        {
        }

        SPI1_DR8 = (uint8_t)(0x10U + i);
    }


    /*
     * Wait all frames finish.
     */
    while ((SPI1->SR & SPI_SR_BSY) != 0U)
    {
    }


    /*
     * --------------------------------------------------------
     * Capture evidence BEFORE reading DR.
     *
     * 很重要:
     *
     * 不要提前读取 DR,
     * 否则可能影响 OVR 状态。
     * --------------------------------------------------------
     */

    g_spi_test.ovr.sr_before_enable =
        SPI1->SR;

    g_spi_test.ovr.cr2_before_enable =
        SPI1->CR2;


    /*
     * Confirm:
     *
     * OVR   = 1
     * ERRIE = 1
     */

    if (((SPI1->SR & SPI_SR_OVR) != 0U) &&
        ((SPI1->CR2 & SPI_CR2_ERRIE) != 0U))
    {
        g_spi_test.ovr.stimulus_valid = 1;
    }
    else
    {
        g_spi_test.ovr.stimulus_valid = 0;
    }


    /*
     * --------------------------------------------------------
     * NVIC currently disabled.
     *
     * OVR IRQ request should set Pending.
     * --------------------------------------------------------
     */

    g_spi_test.ovr.pending_before_enable =
        NVIC_GetPendingIRQ(SPI1_IRQn);


    /*
     * Enable NVIC.
     */
    NVIC_EnableIRQ(SPI1_IRQn);


    WaitForIRQ(&g_spi_test.ovr.irq_count);


    /*
     * PASS condition
     */
    if ((g_spi_test.ovr.stimulus_valid == 1U) &&
        (g_spi_test.ovr.pending_before_enable == 1U) &&
        (g_spi_test.ovr.irq_count > 0U))
    {
        g_spi_test.ovr.irq_path_pass = 1;
    }
    else
    {
        g_spi_test.ovr.irq_path_pass = 0;
    }


    g_spi_test.ovr_pass =
        g_spi_test.ovr.irq_path_pass;


    /*
     * Cleanup
     */
    NVIC_DisableIRQ(SPI1_IRQn);
    NVIC_ClearPendingIRQ(SPI1_IRQn);

    g_active_test = TEST_NONE;
}


/* ============================================================
 *
 * TEST C
 *
 * Software Pending -> NVIC -> ISR
 *
 * 验证:
 *
 * - SPI1_IRQn 正确
 * - Vector table 正确
 * - SPI1_IRQHandler 名称正确
 * - NVIC enable 正常
 * - CPU interrupt response 正常
 *
 * ============================================================ */

static void Test_NVIC(void)
{
    g_active_test = TEST_NVIC;


    /*
     * 让 SPI peripheral 保持 reset,
     * Test C 完全独立于 SPI flag。
     */

    NVIC_DisableIRQ(SPI1_IRQn);
    NVIC_ClearPendingIRQ(SPI1_IRQn);


    /*
     * Software force pending.
     */
    NVIC_SetPendingIRQ(SPI1_IRQn);


    /*
     * 验证 pending register 本身。
     */
    g_spi_test.nvic.pending_before_enable =
        NVIC_GetPendingIRQ(SPI1_IRQn);


    /*
     * 这里把 stimulus_valid 定义为:
     *
     * software pending 是否成功置位。
     */
    if (g_spi_test.nvic.pending_before_enable == 1U)
    {
        g_spi_test.nvic.stimulus_valid = 1;
    }
    else
    {
        g_spi_test.nvic.stimulus_valid = 0;
    }


    /*
     * Enable IRQ.
     *
     * 应立即进入 SPI1_IRQHandler。
     */
    NVIC_EnableIRQ(SPI1_IRQn);


    WaitForIRQ(&g_spi_test.nvic.irq_count);


    /*
     * PASS condition
     */
    if ((g_spi_test.nvic.pending_before_enable == 1U) &&
        (g_spi_test.nvic.irq_count > 0U))
    {
        g_spi_test.nvic.irq_path_pass = 1;
    }
    else
    {
        g_spi_test.nvic.irq_path_pass = 0;
    }


    g_spi_test.nvic_pass =
        g_spi_test.nvic.irq_path_pass;


    /*
     * Cleanup
     */
    NVIC_DisableIRQ(SPI1_IRQn);
    NVIC_ClearPendingIRQ(SPI1_IRQn);

    g_active_test = TEST_NONE;
}


/* ============================================================
 * main
 * ============================================================ */

int main(void)
{
    /*
     * 明确确保 PRIMASK 没有禁止 IRQ。
     */
    __enable_irq();


    /*
     * GPIO only needs initialization once.
     */
    GPIO_SPI1_Init();


    /*
     * --------------------------------------------------------
     * A : CRCERR IRQ path
     * --------------------------------------------------------
     */
    Test_CRCERR();


    /*
     * --------------------------------------------------------
     * B : OVR IRQ path
     * --------------------------------------------------------
     */
    Test_OVR();


    /*
     * --------------------------------------------------------
     * C : NVIC / ISR path
     * --------------------------------------------------------
     */
    Test_NVIC();


    /*
     * ========================================================
     *
     * 在这里打断点。
     *
     * Debugger 中展开:
     *
     * g_spi_test
     *
     * ========================================================
     */

    while (1)
    {
        __NOP();
    }
}