Posted on Leave a comment

CH32V003 driving WS2812B LEDs with SPI – Part 6

3 March 2025

After building and testing what I consider to be a more robust prototype for our experiments, I was dismayed to find that it misbehaved in precisely the same way. So today we are back to the “gold standard” of the WCH CH32V003 development board. Let’s see how well it does on a longer-term run, say 100,000,000 loops.

Now this brings up an interesting question: Does the code need to change for different packages of the -003 chip? The WCH dev board has a -F4P6 TSSOP20 20 pin package, which is different from the -F4U6 QFN20 package on the dev board I designed. I also have a different dev board of my own design that uses the -F4P6 TSSOP20 package, as well as a -A4M6 SOP16 version. I suppose I should haul those out and see what happens. Unfortunately, the -J4M6 SOP8 of “10 cents!” fame does not bring out the PC6 pin, so it can’t help us in this particular investigation.

Or can it? I’m not actually connecting any WS2812B LEDs in these latest tests. The code fails all by itself. The PC6 pin and associated circuitry is still probably in there, somewhere. Unlike my previous experiment to uncover the hidden pins of GPIOA besides PA1 and PA2, there’s no reason to think the chips in different packages are in any way different themselves. The ‘identifying’ codes are programmed into the flash memory at the factory.

Well, what do you know? The -J4 parts fail just like the other ones: randomly and often enough to be problematic.

One thing I can check in the MRS boiler-plate project code is what, if anything, it does with the “Device” information in the project properties settings, General -> Device.

Which, necessarily, brings up the question of whether any -003 device can determine, through code only, what package it is in? Well, I found the answer to that question first, while looking for the answer to the previous question. Weird, but I’ll take it.

Almost ironically, it’s been giving me this information this whole time. What I misunderstood to be the “unique chip ID” code printed out at the beginning of the example application as “ChipID” was in reality the device revision identifier “003” and package identifier, per this list:

Package         ID
------------    ----------
CH32V003F4P6    0x003005x0
CH32V003F4U6    0x003105x0
CH32V003A4M6    0x003205x0
CH32V003J4M6    0x003305x0

This information is retrieved from memory location 0x1FFFF7C4, which falls in the “Vendor Bytes” area, per RM p. 3.

The code in /Peripheral/src/ch32v00x_dbgmcu.c contains three functions that return some or all of this data:

DBGMCU_GetCHIPID()  returns the entire 32 bits as the "chip identifier"
DBGMCU_GetREVID()   returns the upper 16 bits as the "revision identifier"
DBGMCU_GetDEVID()   returns the lower 16 bits as the "device identifier"

However, this does not match the mapping given in the “ChipID List” comment of the DBGMCU_GetCHIPID() function source code. If correct, it would mean each different package was a different chip revision, and that seems unlikely.

But how am I to pursue these interesting and important questions if I have the whole system running an extended diagnostic? That’s right! Set up an entirely new system! So that only took an unreasonable amount of time, involving putting away the not-playing-with-them-right-now toys and making a nice spot adjacent to my desk to run the extended diagnostic. And almost 2,000,000 loops into it, I’m seeing zero errors or glitches, despite my vigorous wiggling of the cables and other apparatus. Go, go, gadget WCH board! You’re the best!

And immediately upon setting it up, my -A4 dev board exhibits the “behavior”. Well, we’re not here to figure out that particular problem at the moment. I’m only wanting to explore the chip’s internal identifiers and see what I can do with that information.

The console prologue give me this: “ChipID:00320500”. Now that’s based on the DBGMCU_GetCHIPID() function provided by the manufacturer’s SDK. Remember, that returns the entirety of the “chip identifier” information burned into the chip’s flash memory at the factory.

Let’s see what the other two functions actually return. First, the DBGMCU_GetREVID() function, which returns 0x0032. Next, the DBGMCU_GetDEVID() function, which returns 0x0500.

So it looks like bits 16-19 of the identifier word specify the package. In this case, it’s the -A4, just like the code comments indicated it would be. Note that I haven’t got all the other data points from other packages yet, but it’s a good start.

Re-attaching the original problem board, my little breadboard-based exploratory vehicle, we get this: “ChipID:00310510”, which corresponds to the F4U6 package. This is correct. We also get a 0x0510, where the x in the source code comment is a ‘1’ in this instance.

Now when I’m setting up a -J4 in the SOP8 package for testing, I am reminded that I have to remap the USART1 TX and RX pins due to the very limited number of available pins. But do I want a special version of the software just for J4 packages?

Now that I can just ask the chip itself what sort of package it has, I don’t need to. I can just test for the one exception and do the pin swap then.

The WCH-supplied SDK already has provision for remapping the USART pins in the example application. I just added a test in the debug.c code to re-#define the DEBUG variable:

// swap USART1 TX & RX pins if it's a J4 SOP8 package

volatile uint16_t dev_id;
dev_id = DBGMCU_GetREVID();

if(dev_id == 0x0033) {
    #undef DEBUG
    #define DEBUG DEBUG_UART1_Remap2
}

I also commented-out the call to the USARTx_CFG() function, as it goes and overrides the USART settings, in this one case incorrectly.

All this confirm that the -J4 packaged devices return 0x0033 from the DBGMCU_GetREVID() function.

Now I’ve built up yet another -F4P6-based development board, and it reports 0x0030 and 0x0500, as it should.

Additionally, I have just now discovered that my ever-so-clever device-check to remap only -J4 devices does not work at all, or rather it works all the time and declares every chip a -J4. That’s because the compiler sees the “#define” and does it at compile time, not at run time.

Now I’ve been able to cause power glitches on this new board, but no timeout errors yet. Again, I’ll have to leave it running for a while, pretend not to be looking at it and get up and sit down… you know, all the standard and accepted ways of making it misbehave.

Other than in the main() function, where the ChipID is reported at the beginning of the program run, I can’t find any other reference to this function being called anywhere. So at this point it looks like the supplied code does not execute differently if a different package is being used.

This doesn’t help me explain why the CH32V004F4P6 packaged devices work perfectly, but every other variant fails consistently. Your thoughts?

Leave a Reply