This covers a random issue I ran into. This ch32v307 microcontroller has a feature that I haven't seen before - a configuration option to exchange ram for flash size. My guess is they're using shadow ram to speed up access to the flash, and any ram left over is available for use.

This is part of a series on the ch32v307 dev board

The Problem

I have two of these dev boards, one with a WCH-LinkE debugger, and one without. I'm using the wchisp program with the microcontroller's USB bootloader to program the board without the debugger. I'm using minichlink to program the board with the debugger. I noticed the board without the debugger is set to use 64K ram, and the board with the debugger is set to use 32K ram. This is a problem for using a linker script set to use 64K, because it'll try to put the stack at the end. This ends in a crash on the 32K board. So I changed my linker script to 32K ram, which leads me to the next issue.

I've been modifying a lwip port to the ch32v307 to work in my build environment. That lwip is currently configured to use 35K of ram, which doesn't work with a linker script set to 32K:

ld: build/example.elf section `.bss' will not fit in region `RAM'
ld: section .stack VMA [0000000020007c00,0000000020007fff] overlaps section .bss VMA [00000000200000c8,00000000200089d7]

The Documentation

So I went looking at the documentation. The ram/flash split setting is copied to a register on startup. The current state is in the OBR register in the flash controller, which is read-only:

Option Byte Register SRAM_CODE_MODE in bits 9:8, the top values are for the ch32v307

These values are copied from the user option bytes starting at 0x1FFFF800:

User option bytes, RAM_CODE_MOD in bits 7:6

The trial and error

To change this setting to use 192K flash + 128K ram (setting 00), I used this code (with my library/framework):

  if (FLASH_GetUserOptionByte() & 0xc0) {
    uint8_t set = (FLASH_GetUserOptionByte() & 0b00111111) | 0b00000000;
    printf("changing program option byte data, set %x\r\n", set);
    FLASH_ProgramOptionByteData(0x1FFFF802, set);
    // applied on reset: NVIC_SystemReset();

And then to verify the setting:

  switch (FLASH->OBR & 0x300) {
    case 0x300:
    case 0x200:
    case 0x100:
    case 0x000:

To test this, I would run the program to flash the new setting, and then flash a new program to see if it could handle 128K of ram. This wasn't working at all, and it took me a long time to even guess what was happening. I started by trying to figure out if the FLASH_ProgramOptionByteData function wasn't working. It would show the user option bytes set the way I expected it to while the program was still running, but when I flashed a new program, it would reset back to the 32K setting. The programming area is at 0x08000000 (and writing about 9KB), so it shouldn't be affecting the option bytes at 0x1FFFF800.

The only reference I could find on the web even talking about this feature was a Chinese forum. And that code didn't do anything differently.

I finally figured out that it was how minichlink was talking with the debugger. If I use wlink, the option bytes don't get overwritten. But wlink leaves my debugger hardware in some unknown state and I have to power cycle it to get the debugger working again. I've opened a github issue, and I want to try reverse engineering the WCH-LinkE protocol to see if I can figure out how to fix this.