CH32V203 の隠しフラッシュの調査
CH32V203 にはドキュメントに書かれていないフラッシュ領域が存在するようなので調査
はじめに
CH32V203 は、安くて速いので使い勝手のいい製品ですが、4月の末にこんな噂が流れました「CH32V203 には、どの製品にも 224KB のフラッシュが積まれている」。 ということで確認してみましょう。
仕様を再確認する
確かにデータシート上は以下のように記述されています。

製品セレクタの注意書きは以下のようになっています。

これを読む限り、フラッシュの容量が 224KB あるのは仕様であり、 ZeroWait でアクセスできる容量を公式なフラッシュ容量としていることがわかります。
CH32V203 はフラッシュとコアが別のダイになっていて、 フラッシュダイは 256KB の容量があります。256KB 中 28KB はブートローダで使用されますから、224KB がユーザ用フラッシュとして使用可能なはずです。
ちなみに公式には 256KB フラッシュの CH32V30x シリーズは、同様に 480KB のフラッシュを積んでいます。
読み出す
単純にプログラムで読んでみます。
#include "debug.h"
int main(void) {
    int sum;
    uint8_t *ptr;
    volatile int count,lastcount;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    SystemCoreClockUpdate();
    SysTick->CNT = 0;
    SysTick->CTLR |= (1 << 0);
    Delay_Init();
    USART_Printf_Init(115200);
    printf("SystemClk:%d\r\n", SystemCoreClock);
    printf("ChipID:%08x\r\n", DBGMCU_GetCHIPID());
    lastcount=SysTick->CNT;
    for (int i = 0; i < 0x40000; i += 0x100) {
        sum = 0;
        for (int loop = 0; loop < 1000; loop++) {
            for (int j = 0; j < 0x100; j++) {
                ptr = i + j;
                sum += *ptr;
            }
        }
        count=SysTick->CNT;
        printf("%08x:%08x:%d\n\r", i, sum, count-lastcount);
        lastcount=count;
    }
    while(1)
    {
    }
}
実行結果です。
0000f800:022ab000:268282
0000f900:022ab000:268281
0000fa00:022ab000:268282
0000fb00:022ab000:268280
0000fc00:022ab000:268282
0000fd00:022ab000:268281
0000fe00:022ab000:268282
0000ff00:022ab000:268280
00010000:022ab000:2466532
00010100:022ab000:2468274
00010200:022ab000:2468282
00010300:022ab000:2468280
00010400:022ab000:2468282
00010500:022ab000:2468282
00010600:022ab000:2468280
00010700:022ab000:2468282
0x10000 未満の 64KB の領域が公式にサポートされているフラシュの範囲です。
0x10000 を境に消費クロックが一気に大きくなっていることがわかります。
この数値は SytemCoreClock に依存しないので、単純に隠しフラッシュを読み出す際には 10分の一くらいの速度になると思っていればいいようです。
なお、メモリの存在しないエリアを読み出すとトラップが発生します。
どうやって使う
160KB 存在する隠しフラッシュですが、このままでは、コンパイラ(というかリンカ)で認識できません。 単純にはフラッシュサイズを変えればよさそうですが、 コードが隠しフラシュ領域に書かれると思いっきり遅くなるので、明示的に区別した方がよさそうです。
gcc では __attribute__ を使って変数をどこに置くか明示的に指定できます。
リンカーの設定ファイル Link.ld に隠しフラッシュの領域を定義します。
MEMORY に FLASH2の行を追記します。(この例だとデータのみ置けます)
/* CH32V20x_D6 - CH32V203K8-CH32V203C8-CH32V203G8-CH32V203F8 */
    FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 64K
    RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
    FLASH2 (r) : ORIGIN = 0x00010000, LENGTH = 160K
SECTIONS に .data2 領域を追記します。
	.data2 :
	{
		. = ALIGN(8);
	} AT>FLASH2
gcc のコード上では以下のように定義すると、隠しフラッシュ上に確保されます。
const uint8_t testdata[] __attribute__((section(".data2"))) = {
        0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
        0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f
};
128KB のダミーデータを用意しました。
11:06:50 **** Incremental Build of configuration obj for project memtest203 ****
make -j8 all 
   text	   data	    bss	    dec	    hex	filename
 137984	    152	   2072	 140208	  223b0	memtest203.elf
11:06:51 Build Finished. 0 errors, 1 warnings. (took 1s.175ms)
MounRiver Studio から普通に書き込めます。
このほかの使い方としては SDK の FLASH 系の関数を使って直接操作しても良いかと思います。
考察
CH32V に限らず、高速マイコンではフラッシュの読み込みが遅いために、起動時に SRAM にデータをコピーしているものが多くあります。 たとえば、データシート上の各種マイコンの起動時間(Power on RESET)を表にしてみると以下のようになっています。
| MCU | 起動時間 | 
|---|---|
| STM32F401 | 1.5ms | 
| GD32VF103 | 2ms | 
| CH32V003 | 17ms | 
| CH32V203 | 28ms | 
| AT32F403A | 13ms | 
このなかでは、CH32V203 と AT32F403A が起動時にコピーをやっているようです。(CH32V003 もやってるのかな…) なお、STM32F401 や AT32F403A の NonZero Wait フラッシュは、2-3 wait くらい(4分の1くらいの速度?)で読み出せるようです。