CH32V003J4M6 - Does anyone have working code to write to Flash e.g., 0x1FFFF800 - Page 1
Forum section: electronics / general

author: dzairo
author_rank: Contributor
timestamp: August 24, 2025, 09:38:58 am
content: I am trying to save data to the user Flash on a CH32V003J4M6, specifically in the last page at 0x1FFFF800. Nothing I try works.Does anyone have a simple, proven, and working code snippet for erasing and writing to the user Flash on this chip? I don't need the theory, just code that works.Thank you.

author: microvia
author_rank: Regular Contributor
timestamp: August 24, 2025, 10:12:21 am
content: https://github.com/invariablyafk/CH32V-FlashStorage

author: brucehoult
author_rank: Super Contributor
timestamp: August 24, 2025, 10:39:40 am
content: I would assume this works also, it's a pretty minimal example but the other one looks more polished:https://github.com/cnlohr/ch32fun/tree/master/examples/flashtest

author: dzairo
author_rank: Contributor
timestamp: August 24, 2025, 11:19:33 am
content: Hi. Then If want store any data need Erase last page (1024 bytes) or Is possible only last 64bytes ??

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: August 24, 2025, 12:45:22 pm
content: Quote from: dzairo on August 24, 2025, 09:38:58 amThere was an error in an early version of the library - a register that needed bit 16 set was incorrectly defined as 16 bits wide.I think user options flash need specific commands to write. Here is some code I used to check and of necessary rewrite the user options to allow PD7 to be used as an outputCode:u32* fptr = (u32*) 0x1FFFF800;i = fptr >> 16;// factory value 0xE817if ((i&0x18)!=0x18){ // set PD7 as outputFLASH_Unlock();FLASH_EraseOptionBytes();FLASH_UserOptionByteConfig(0x1F, 0, 0, 0);FLASH_Unlock();PFIC->CFGR=0xBEEF0080; //reset to re-read value}

author: dzairo
author_rank: Contributor
timestamp: August 24, 2025, 01:08:37 pm
content: I'm confused from this ..Flash menory 16k + bootloader + optin byte + user byte ?? toatal is total 18 432byt ? is it right ?I sue mouRiver studio , and I see real data in flash .. then in what positon are user bytes (64 bytes size ) .I don't want change option bytes , only user .. and donw' want change bootlader or flash memory .. oruser flash is part of flash memory??and this is not efective to erase 1k last page flash, is there option to erase last 64bytes only ??

author: voltsandjolts
author_rank: Unknown
timestamp: August 24, 2025, 03:56:33 pm
content: So many questions.Maybe try switching to a different microcontroller which has documentation that is more correct/easier to understand.NXP are good in this respect.

author: dzairo
author_rank: Contributor
timestamp: August 24, 2025, 05:04:49 pm
content: ..hmm.. I make procejct where I need cheap MCU ..

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: August 24, 2025, 05:32:30 pm
content: Might help if you said exactly what you are trying to do.if it's saving configurations etc., easiest to use the last page of program flash.This code will write one 32 bit word :Code:#define flashadr 0x08003fc0 // last page of flashu32* flashptr = (u32*) flashadr;void writeflash(u32 flashdata){#define FLASH_CTLR_PAGEER 0x20000 // defs in header are wrong - declared as u16#define FLASH_CTLR_PAGEPG 0x10000 // defs in header are wrong - declared as u16#define FLASH_CTLR_BUFLD 0x40000 // defs in header are wrong - declared as u16#define FLASH_CTLR_BUFRST 0x80000FLASH->KEYR = 0x45670123;FLASH->KEYR = 0xCDEF89AB;FLASH->MODEKEYR = 0x45670123;FLASH->MODEKEYR = 0xCDEF89AB;FLASH->ADDR = (u32) flashptr;FLASH->CTLR = FLASH_CTLR_PAGEER;FLASH->CTLR = FLASH_CTLR_STRT | FLASH_CTLR_PAGEER;while( FLASH->STATR & FLASH_STATR_BSY);FLASH->CTLR = FLASH_CTLR_PAGEPG;FLASH->CTLR = FLASH_CTLR_BUFRST | FLASH_CTLR_PAGEPG;while( FLASH->STATR & FLASH_STATR_BSY );FLASH->ADDR = (u32) flashptr;flashptr = flashdata;FLASH->CTLR |= FLASH_CTLR_BUFLD | FLASH_CTLR_PAGEPG;while ( FLASH->STATR & FLASH_STATR_BSY );FLASH->CTLR = FLASH_CTLR_STRT | FLASH_CTLR_PAGEPG;while( FLASH->STATR & FLASH_STATR_BSY );}To read the flash word :Code:i = flashptr;

author: dzairo
author_rank: Contributor
timestamp: August 24, 2025, 05:55:58 pm
content: Hi all.Problem .If try my code to store and read data from Flash then I make from MCU brick.. not only one .. 5pcs .. and I don't know how to get it unbrick .This code :Code:uint8_t SaveFlash(const uint8_t EraseOnly){__disable_irq();FLASH->KEYR = FLASH_KEY1;FLASH->KEYR = FLASH_KEY2;FLASH->MODEKEYR = FLASH_KEY1;FLASH->MODEKEYR = FLASH_KEY2;FLASH->ACTLR = (FLASH->ACTLR & ~FLASH_ACTLR_LATENCY) | FLASH_ACTLR_LATENCY_1;if( FLASH->CTLR & 0x8080 ) // Testuje ci je zamknuta pamet alebo nieco ine, ma byt 0{return 1;}uint32_t * ptr = (uint32_t*)USER_MEMORY_BASE;//Erase PageFLASH->CTLR = CR_PAGE_ER;FLASH->ADDR = (intptr_t)ptr;FLASH->CTLR = CR_STRT_Set | CR_PAGE_ER;while( FLASH->STATR & FLASH_STATR_BSY ); // Takes about 3ms.if( ptr != 0xffffffff ){return 2;}// Clear buffer and prep for flashing.FLASH->CTLR = CR_PAGE_PG; // synonym of FTPG.FLASH->CTLR = CR_BUF_RST | CR_PAGE_PG;FLASH->ADDR = (intptr_t)ptr; // This can actually happen about anywhere toward the end here.// Note: It takes about 6 clock cycles for this to finish.int i;for( i = 0; i < 16; i++ ){if (EraseOnly) ptr = 0xFFFFFFFF;else ptr = 0xabcd1234 + i; //Write to the memoryFLASH->CTLR = CR_PAGE_PG | FLASH_CTLR_BUF_LOAD; // Load the buffer.while( FLASH->STATR & FLASH_STATR_BSY ); // Only needed if running from RAM.}// Actually write the flash out. (Takes about 3ms)FLASH->CTLR = CR_PAGE_PG|CR_STRT_Set;while( FLASH->STATR & FLASH_STATR_BSY );if( ptr != 0xabcd1234 ){return 3;}__enable_irq();return 0;}I'm creazy from this .. not only sotware problem .. upload code .. bum.. my MCU is brick .. help me unbrick MCU ..I sue easy chepa WCHlink from aliexpress .. what happend ..

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: August 24, 2025, 06:02:21 pm
content: It can get to weird states - the memory at 1FFFF800 includes write-protect bits, so if you have programmed there, that will cause problems.From Mounriver studio try flash->configuration and click the open padlock symbol to erase code protection.The WCH-link utility can also be useful for reprogramming messed up chips

author: dzairo
author_rank: Contributor
timestamp: August 24, 2025, 06:11:09 pm
content: not working..Code:20:07:20:555>> Connected RISC-V mode WCH-Link Cnt:120:07:20:603>> Connected RISC-V mode WCH-Link Cnt:120:07:27:109>> Begin to clear all code flash...20:07:27:125>> Succeed!Code:20:08:34:460>> Connected RISC-V mode WCH-Link Cnt:120:08:34:507>> Connected RISC-V mode WCH-Link Cnt:120:08:42:002>> Begin to set chip type...20:08:42:084>> Failed,the chip type is not matched or status of chip is wrong!What is wrong..On first .. MCU start reset in cycle .. and then total brick ..

author: voltsandjolts
author_rank: Unknown
timestamp: August 24, 2025, 06:43:19 pm
content: Quote from: dzairo on August 24, 2025, 05:04:49 pmCheap. Well, that depends how much you value your time. And ours.

author: dzairo
author_rank: Contributor
timestamp: August 24, 2025, 06:47:54 pm
content: ..in 10kpcs production is MCU price very important ..chip what just restart in cycles , using WCH Link Utlity I set any bits and now working good..But brics not working ..

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 25, 2025, 01:10:02 am
content: The datasheet mentions 64-byte pages (see Memory characteristics), the RM also mentions 64-bytes pages (see Flash Memory Organisation), but the SDK seems to only implement erasing by "1K pages".The docs are confusing. They seem to mention two modes, standard programming and fast programming. The fast programming mode would be the only one apparently able to erase only by 64-byte pages. But as far as I've seen, the SDK doesn't seem to implement it.I have noticed for other WCH MCUs that their SDK would often evolve and remove fast flash programming support, for a reason unknown, as there's unfortunately no errata sheet that I know of for any of them.Don't get me wrong, WCH MCUs are great overall, but I do not like the absence of errata sheets and the way they tend to be too secretive about errata overall, which seems to be relatively common among chinese vendors.

author: dzairo
author_rank: Contributor
timestamp: August 25, 2025, 05:44:35 am
content: ...yes, I alos think there is problem.. I try use last 64bytes of end of flash .. and if try erase first .. then erase not only 64bytes.. I try this today..I will see ..thanks.

author: brucehoult
author_rank: Super Contributor
timestamp: August 25, 2025, 06:11:11 am
content: It only takes one person to figure this out and document it for the community...

author: mark03
author_rank: Frequent Contributor
timestamp: August 25, 2025, 04:50:47 pm
content: Quote from: brucehoult on August 25, 2025, 06:11:11 amDoes anyone stop to ask WHY we are reduced to reverse-engineering the parts sold to us by companies which [presumably?] already have the answers? (and if they *don't* know the answers, why on earth would I trust their technical chops enough to choose their products in the first place?)I will start paying more attention to RISC-V when a vendor offers a standard of documentation up to, let's say, the level of ST Microelectronics. That is hardly a high bar.I know this is mainly a China thing and not a RISC-V thing, but it is frightening to consider that this could be the future of embedded development. Countless millions of hours of wasted and lost productivity. Sheer, shortsighted madness

author: dzairo
author_rank: Contributor
timestamp: August 25, 2025, 07:07:27 pm
content: Quote"Yes, it’s true, but I have a very good reason for doing it. I'm designing a very small PCB for a Chinese product with few SMD components, so a low price is a very important option. I've been using Atmel products for 30 years—their datasheets are perfect, with 300 or more pages and everything well-documented. But do Atmel (Microchip) MCUs cost $0.10 in quantities of a thousand? The price of the ATTiny85 was too high for my project. So, I tried to find the best, most affordable Chinese MCU, and the CH32V003J4M6 is my favorite. I used Deepseek and Gemini to help me with the programming, and step by step, everything is working now.Now, back to my problem: I want to use the internal Flash memory (an external EEPROM costs around $0.01 in quantities of 10,000) to store no more than 64 bytes, and possibly more in the future. I've been searching for a code to do this. All my tests of changing the Option Bytes have resulted in the MCU not working properly. Also, changing a special bit and using pin PD1 as SWIO sets only PD1, which bricks the chip—a very cheap brick, but a brick nonetheless.Then, with help fromQuoteandQuote, I found and analyzed some code, and now I have a working solution. But how do I test if a Flash erase operation erases only 64 bytes or a full 1024-byte page?I did the following:Created a file flash_test.h with 2048 bytes of test data.Modified the Link.ld file to store this test data at the end of the Flash section (Deepseek helped me with this).Compiled and flashed the MCU.Used the WCH Link Utility to read the complete Flash memory and save it as a binary file.Then, I used my code to erase a part of the Flash memory. I used the following addresses:Code:#define USER_MEMORY_BASE 0x08003FC0#define USER_MEMORY_SIZE 64I also tried another address (my testing data is 2048 bytes, so I used an address within this range). I then read the entire Flash again and compared the files. The result was that only 64 bytes were erased after the erase page command. I set the last 64 bytes at 0x08003FC0 and will continue to use this section, hoping the MCU doesn't get bricked.Here is my eeprom.h fileCode:// Adresa user memory podla dokumentивcie#define USER_MEMORY_BASE 0x08003FC0#define USER_MEMORY_SIZE 64#define FLASH_KEY1 ((uint32_t)0x45670123)#define FLASH_KEY2 ((uint32_t)0xCDEF89AB)#define CR_STRT_Set ((uint32_t)0x00000040)#define CR_LOCK_Set ((uint32_t)0x00000080)#define CR_PAGE_PG ((uint32_t)0x00010000)#define CR_PAGE_ER ((uint32_t)0x00020000)#define CR_BUF_LOAD ((uint32_t)0x00040000)#define CR_BUF_RST ((uint32_t)0x00080000)uint8_t read_bytes(uint8_t start_addr, uint8_t *buffer, uint8_t length);uint8_t SaveFlash(const uint8_t EraseOnly);void print_full_memory(void);and eeprom.cCode:#include <ch32v00x_it.h>#include "eeprom.h"#include <ch32v00x_flash.h>uint8_t read_bytes(uint8_t start_addr, uint8_t *buffer, uint8_t length){if (start_addr >= USER_MEMORY_SIZE || buffer == NULL) return 0;if (start_addr + length > USER_MEMORY_SIZE) length = USER_MEMORY_SIZE - start_addr;__disable_irq();for (uint8_t i = 0; i < length; i++) {buffer = *((volatile uint8_t*)(USER_MEMORY_BASE + start_addr + i));}__enable_irq();return length;}uint8_t SaveFlash(const uint8_t EraseOnly){__disable_irq();FLASH->KEYR = FLASH_KEY1;FLASH->KEYR = FLASH_KEY2;FLASH->MODEKEYR = FLASH_KEY1;FLASH->MODEKEYR = FLASH_KEY2;FLASH->ACTLR = (FLASH->ACTLR & ~FLASH_ACTLR_LATENCY) | FLASH_ACTLR_LATENCY_1;if( FLASH->CTLR & 0x8080 ) // Testuje ci je zamknuta pamet alebo nieco ine, ma byt 0{__enable_irq();return 1;}uint32_t * ptr = (uint32_t*)USER_MEMORY_BASE;//Erase PageFLASH->CTLR = CR_PAGE_ER;FLASH->ADDR = (intptr_t)ptr;FLASH->CTLR = CR_STRT_Set | CR_PAGE_ER;while( FLASH->STATR & FLASH_STATR_BSY ); // Takes about 3ms.if( ptr != 0xffffffff ){__enable_irq();return 2;}// Clear buffer and prep for flashing.FLASH->CTLR = CR_PAGE_PG; // synonym of FTPG.FLASH->CTLR = CR_BUF_RST | CR_PAGE_PG;FLASH->ADDR = (intptr_t)ptr; // This can actually happen about anywhere toward the end here.// Note: It takes about 6 clock cycles for this to finish.int i;for( i = 0; i < 16; i++ ){if (EraseOnly) ptr = 0xFFFFFFFF;else ptr = 0xabcd1234 + i; //Write to the memoryFLASH->CTLR = CR_PAGE_PG | FLASH_CTLR_BUF_LOAD; // Load the buffer.while( FLASH->STATR & FLASH_STATR_BSY ); // Only needed if running from RAM.}// Actually write the flash out. (Takes about 3ms)FLASH->CTLR = CR_PAGE_PG|CR_STRT_Set;while( FLASH->STATR & FLASH_STATR_BSY );if( ptr != 0xabcd1234 ){__enable_irq();return 3;}__enable_irq();return 0;}void print_full_memory(void){uint8_t buffer;// Naread_bytes(0, buffer, USER_MEMORY_SIZE);printf("\r\n=== User memory (64B) ===\r\n");printf("---------------------------------------------------------\r\n");for (uint8_t i = 0; i < 64; i += 16) {printf("%02X: ", i);for (uint8_t j = 0; j < 16; j++) {printf("%02X ", buffer[i + j]);}printf("\r\n");}printf("\r\n=========================================\r\n");}I use MounRiver Studio ..This is my teting code .. not finall code .. this is only if any interesting.. and yes , of course .. many bugs ..Any ideas ??Best reagrds.

author: brucehoult
author_rank: Super Contributor
timestamp: August 26, 2025, 03:21:27 am
content: Quote from: mark03 on August 25, 2025, 04:50:47 pmSure that would be nice, but it's not a show-stopper. It takes fuck-all time and money (on a 10c part!) to test it and figure it out yourself, and then you know for sure. If it was a $1000 part then sure I'd like to know before I bought one.No doubt they do know the answers themselves. But they're writing reference material not tutorials, and when things are always done in a particular way experts who know it might not realise that it needs to be spelled out for the less expert.Can't say I'm bothered. Much of my career is based on getting things to do stuff that the manufacturer hasn't considered and, if asked, will say can't be done or isn't supported. An early example was when the Apple Laserwriter came out with the amazing Postscript language a graphics library built in, and an RS-422 serial port for LocalTalk, I asked both Apple and Data General whether I could connect it to a DG MV/10000 supermini and both said no. I looked at the specs, figured that RS-232 and RS-422 were actually compatible with the right cable, and made one up. And proceeded to happily for the next two years write PL/I code that queried flat files and DG/SQL databases and output PostScript.QuoteThe same manufacturer makes a range of Arm-based chips, and very popular 8051 and USB chips that sell in the billions.

author: ali_asadzadeh
author_rank: Super Contributor
timestamp: August 26, 2025, 01:31:58 pm
content: QuoteThanks for sharing the code, I would definitely needed it in a project in future.

author: dzairo
author_rank: Contributor
timestamp: August 26, 2025, 08:16:40 pm
content: Hi all , I do smoe changes ...eeprom.hCode:// Adresa user memory pod?a dokumentивcie#define USER_MEMORY_SIZE 64#define ERASE 1#define FILL 0// Definície pre lepšiu čitateľnosť#define FLASH_ERROR_INVALID_POSITION 1#define FLASH_ERROR_LOCKED 2#define FLASH_ERROR_ERASE_FAILED 3#define FLASH_ERROR_WRITE_FAILED 4#define FLASH_ERROR_VERIFY_FAILED 5extern volatile uint8_t Base0Buffer;uint8_t read_bytes(uint8_t start_addr, uint8_t *buffer, uint8_t length);uint8_t SaveFlash(const uint8_t EraseOnly,const uint8_t FlashPosition,volatile uint8_t *InBuffer);void print_full_memory(void);eeprom.cCode:#include <ch32v00x_it.h>#include <ch32v00x_flash.h>// vlastne subory#include "eeprom.h"//vlastne konstanty#define USER_MEMORY_BASE_0 0x08003F00#define USER_MEMORY_BASE_1 0x08003F40#define USER_MEMORY_BASE_2 0x08003F80#define USER_MEMORY_BASE_3 0x08003FC0#define FLASH_KEY1 ((uint32_t)0x45670123)#define FLASH_KEY2 ((uint32_t)0xCDEF89AB)#define CR_STRT_Set ((uint32_t)0x00000040)#define CR_LOCK_Set ((uint32_t)0x00000080)#define CR_PAGE_PG ((uint32_t)0x00010000)#define CR_PAGE_ER ((uint32_t)0x00020000)#define CR_BUF_LOAD ((uint32_t)0x00040000)#define CR_BUF_RST ((uint32_t)0x00080000)// Premenne pre fungovanievolatile uint8_t Base0Buffer;uint8_t read_bytes(uint8_t start_addr, uint8_t *buffer, uint8_t length){if (start_addr >= USER_MEMORY_SIZE || buffer == NULL) return 0;if (start_addr + length > USER_MEMORY_SIZE) length = USER_MEMORY_SIZE - start_addr;__disable_irq();for (uint8_t i = 0; i < length; i++) {buffer = *((volatile uint8_t*)(USER_MEMORY_BASE_0 + start_addr + i));}__enable_irq();return length;}uint8_t SaveFlash(const uint8_t EraseOnly, const uint8_t FlashPosition,volatile uint8_t *InBuffer){if (FlashPosition > 3) return 1; // Neplatná pozícia__disable_irq();// Odomknutie flashFLASH->KEYR = FLASH_KEY1;FLASH->KEYR = FLASH_KEY2;FLASH->MODEKEYR = FLASH_KEY1;FLASH->MODEKEYR = FLASH_KEY2;// Nastavenie latencieFLASH->ACTLR = (FLASH->ACTLR & ~FLASH_ACTLR_LATENCY) | FLASH_ACTLR_LATENCY_1;// Kontrola stavu flashif (FLASH->CTLR & 0x8080) // Testuje či je zamknutá pamäť{FLASH->CTLR = FLASH_CTLR_LOCK; // Zamknúť flash__enable_irq();return 2; // Flash je zamknutá alebo chyba}uint32_t *ptr = (uint32_t*)(USER_MEMORY_BASE_0 + (USER_MEMORY_SIZE * FlashPosition));// Mazanie stránkyFLASH->CTLR = FLASH_CTLR_PER; // Page eraseFLASH->ADDR = (uint32_t)ptr;FLASH->CTLR = FLASH_CTLR_STRT | FLASH_CTLR_PER;while (FLASH->STATR & FLASH_STATR_BSY); // Čakanie na dokončenie// Kontrola vymazaniaif (ptr != 0xFFFFFFFF){FLASH->CTLR = FLASH_CTLR_LOCK; // Zamknúť flash__enable_irq();return 3; // Chyba mazania}if (EraseOnly){// Iba mazanie - už hotovéFLASH->CTLR = FLASH_CTLR_LOCK; // Zamknúť flash__enable_irq();return 0;}// Zápis dát do flashFLASH->CTLR = CR_PAGE_PG; // synonym of FTPG.FLASH->CTLR = CR_BUF_RST | CR_PAGE_PG;FLASH->ADDR = (intptr_t)ptr; // This can actually happen about anywhere toward the end here.for (int i = 0; i < 16; i++) // 16 slov = 64 bytov{uint32_t data = ((uint32_t)InBuffer[(i * 4) + 0] << 0) |((uint32_t)InBuffer[(i * 4) + 1] << 8) |((uint32_t)InBuffer[(i * 4) + 2] << 16) |((uint32_t)InBuffer[(i * 4) + 3] << 24);ptr = data; // Zápis slovaFLASH->CTLR = CR_PAGE_PG | FLASH_CTLR_BUF_LOAD; // Load the buffer.while( FLASH->STATR & FLASH_STATR_BSY ){}; // Only needed if running from RAM.}// Finalizácia zápisuFLASH->CTLR = CR_PAGE_PG|CR_STRT_Set;while( FLASH->STATR & FLASH_STATR_BSY ){};FLASH->CTLR &= ~FLASH_CTLR_PG; // Vypni bit pre zapis// Kontrola celého zápisufor (int i = 0; i < 16; i++){uint32_t expected = ((uint32_t)InBuffer[(i * 4) + 0] << 0) |((uint32_t)InBuffer[(i * 4) + 1] << 8) |((uint32_t)InBuffer[(i * 4) + 2] << 16) |((uint32_t)InBuffer[(i * 4) + 3] << 24);if (ptr != expected){FLASH->CTLR = FLASH_CTLR_LOCK; // Zamknúť flash__enable_irq();return 5; // Chyba overenia}}// Zamknutie flashFLASH->CTLR = FLASH_CTLR_LOCK;__enable_irq();return 0; // Úspech}// Pre debugovanie// Funkcia na v?pis celej pam?tevoid print_full_memory(void){uint8_t buffer;// Naread_bytes(0, buffer, USER_MEMORY_SIZE);// Vypик? hlavi?kuprintf("\r\n=== User memory (64B) ===\r\n");printf("---------------------------------------------------------\r\n");// Vypик? hexfor (uint8_t i = 0; i < 64; i += 16) {printf("%02X: ", i);for (uint8_t j = 0; j < 16; j++) {printf("%02X ", buffer[i + j]);}printf("\r\n");}printf("\r\n=========================================\r\n");}code to test in main.cCode:print_full_memory();for (int i = 0; i < 64; i++) {Base0Buffer = i + 0x21; // Dáta: 0x10, 0x11, 0x12, ...}SaveFlash(0,0,Base0Buffer);print_full_memory();SaveFlash(1,0,Base0Buffer);print_full_memory();}all other chagnes .. lock flash , or ... make from MCU brick .. ( two guys dead..) still don't know what exactly happend .. but this I test .. and working ..