Posted on Leave a comment

CH32V003 driving WS2812B LEDs with SPI – Part 14

13 March 2025

Continuing my investigation into why the CH32V003 SPI port sometimes just locks up, I have looked at the source code for the two functions that are involved: The SPI_I2S_GetFlagStatus() function and the SPI_I2S_SendData() function. They both do exactly what you would hope that they would do.

What comes up as suspicious is my initialization of the port. Here are the values of the two control registers as well as the status register immediately after being initialized:

SPI_CTLR1 = 0xC154
SPI_CTLR2 = 0x0000
SPI_STATR = 0x0002

This varies from the final value of SPI_CTLR1 that I used in my assembly language version of the diagnostic: 0xC354.

So what’s the exact difference here? Using the SDK function to initialize the SPI port with what was just my best guess at what would be correct, we get the following bits set in CTLR1, versus what I told it to do:

Bit Field       SDK mine    Description
--- --------    --- ----    -----------
0   CHPA        0   0   clock phase (don't care)
1   CPOL        0   0   clock polarity (don't care)
2   MSTR        1   1   coordinator mode
5-3 BR          2   2   bit rate FCLK/8
6   SPE         1   1   SPI enable
7   LSBFIRST    0   0   not set = MSB first
8   SSI         1   1   select pin level
9   SSM         0   1   select 0=hardware, 1=software control
10  RXONLY      0   0   receive only mode (not used)
11  DFF         0   0   0 = 8 bit data
12  CRCNEXT     0   0   send CRC (not used)
13  CRCEN       0   0   enable hardware CRC (not used)
14  BIDIOE      1   1   enable output, transmit only
15  BIDIMODE    1   1   one line bidirectional mode

The only difference I see is that the SSM bit is cleared in the SDK initialization and set in mine. Since we’re not using the select line to select anything, it shouldn’t matter. It does matter that the NSS line is already set high before enabling the peripheral in coordinator mode. Per the RM, Section 14.2.2. Master Mode, p. 162:

"Configure the NSS pin, for example by setting the SSOE bit and letting the hardware set the NSS.  [I]t is also possible to set the SSM bit and set the SSI bit high.  To set the MSTR bit and the SPE bit, you need to make sure that the NSS is already high at this time."

And I know that it indeed does not work if the NSS is not set high before enabling the peripheral. The peripheral simply locks up with a “mode fault” error.

I added some code to print out the status register when a timeout occurs. I immediately see that it is always 0x0020, which means a “mode fault” has occurred. Here’s a list of things that can cause a mode fault on this peripheral:

When the SPI is operating in NSS pin hardware management mode, an external pull-down of the NSS pin occurs
in NSS pin software management mode, the SSI bit is cleared
the SPE bit is cleared, causing the SPI to be shut down
the MSTR bit is cleared and the SPI enters slave mode

Perhaps noise on the otherwise un-initialized NSS line is triggering an intermittent mode fault? Looking back, I see I have, in my ignorance, not specified which NSS handling strategy (hardware vs software) to use when configuring the peripheral.

Setting the SPI_NSS field to ‘SPI_NSS_Soft’ (1) when performing the SPI initialization, we get the following setup profile when the application starts:

SPI_CTLR1 = 0xC354
SPI_CTLR2 = 0x0000
SPI_STATR = 0x0002

So now it matches my bit-wise initialization of the control register. Now it’s time to let it run on ‘The Gauntlet’, as I have named my alternate test setup, overnight, and see what we shall see.

Leave a Reply