CH32X035用のmrubyc_arduino


flashやRAMが豊富なマイコンの場合には、 大抵はmicropythonを動かすことが出来て、 そのマイコンをC言語やC++以外で使いたいときには、micropythonを使えば良い。 しかし、 micropythonは動かないけど、mruby/cなら動くような場合には、mruby/cを使おういうことになるだろう。 そこで、より少ないflashやRAMしか持たないマイコンでmruby/cを動かそうとしているが、 マイコン毎にハードに依存する部分をいちいち書くのは面倒なので、 ハード依存部はArduinoに任せられるようにmrubyc_arduinoを利用している。

これまでに、 WiFiを組み込んだESP8266や、SWDを組み込んだCH32V203について、 mrubyc_arduinoでmruby/cを使える環境を構築してきた。 これらはそれぞれ2026/5/5と5/3のブログにまとめてある。 これらの環境の構築の際には、より実用的な環境になるように、次のような条件を付けている。 まず、標準的なAPIであるGPIO,ADC,PWM,I2C,SPI,UARTを組み込む。 そして、標準入力を使えるようにする。 また、rubyスクリプトを書き換えたときには、mrbcで処理したコードを、Arduinoでの再コンパイルしないで、書き込むことができるようにする。 ESP8266はflashやRAMがそれなりに大きいので、それほど苦労せずにこれらの条件を満たすことができたが、WiFiでhttpsにアクセスするのが大変だった。 CH32V203は公式なflashは64kとそれほど大きく無いが、隠しflashがあり、これをうまく活用すると、FloatとTaskの二つのclassを無効にしなければならなかったが、flashになんとか収まった。

次にmruby/cを使いたいと思っていたマイコンの一つがCH32X035である。 このマイコンはflashが62kしか無いが、マイコン自体がUSB機能を持っているので、他に外付のIC無しでPCと通信が出来て、安価なマイコンボードが入手できる。 このマイコンでは、標準入出力をUSBSerialにして、mruby/cを使えるようにするのが目標である。 しかし、USBSerialやmrbも62kのflashに収めないといけないので、様々な工夫が必要である。 まだ改善の余地はあるかも知れないが、なんとか使えそうな環境が構築できたので、それについて説明したい。

mrubyc_arduinoのライブラリのインストールは2026/5/3のブログと同じである。 ch32用のパッケージは、USBSerialを使えるようにする関係で、以下のURLを設定に追加してボードマネージャーからインストールする。

https://raw.githubusercontent.com/jobitjoseph/CH32_Arduino_Core/main/board_manager/package_ch32_index.json

このパッケージでは、ToneやI2Cなどのファイルはすべて揃っているので、それ以外の操作は不要である。 Arduinoでは、CH32 EVT boards supportからCH32X035を選択して、最適化のオプションはsmallest with LTOとしておく。 USBSerial用のライブラリは2026/5/11のブログに書いたように、「ライブラリを管理」から検索してインストールする。

mrubyc_arudinoがCH32X035のflashに入るように、これまでにやってきた工夫を総動員する。 まず、floatを消すために、vm_config.hのMRBC_USE_FLOATを0にして、2026/4/15のブログに書いたようにいくつかのファイルを修正する。 Taskも消すために、2026/4/20のブログに書いたようにrrt0.hとrrt0.cを修正して、_autogen_class_rrt0.hを削除する。 mrblibも消すために、mrblib.cを削除して、class.cの最後の二つの命令をコメントアウトする。 StringとArrayとHashについて、autogen_class.hのsymbolsとfunctionsについて必要最低限のmethod以外を/ */でコメントアウトする。 そして、2026/5/12のブログに載せたrubyスクリプトを使って、_autogen_builtin_symbol.hを作って、上書きする。 これで、USBSerialも含めてなんとか61k弱になり、残りはユーザーのmrb用にできる。

Arduino用のスケッチは以下の通りである。

#include <mrubyc.h>
#include <CH32X035_USBSerial.h>
using namespace wch::usbcdc;

#define MEMORY_SIZE (1024*10)
#define FLASH_CODE (0x8000000+62072)
static uint8_t memory_pool[MEMORY_SIZE];

int hal_write(int fd, const void *buf, int nbytes) {
  USBSerial.print((char*)buf);
  return (nbytes);
}
int hal_flush(int fd) { return 0; }
unsigned char hal_read(int fd) {
  while (USBSerial.available() == 0);
  return USBSerial.read();
}

void setup() {
  USBSerial.begin();
  USBSerial.waitForPC(); // Wait for host connection
  mrbc_init(memory_pool, MEMORY_SIZE);
  mrbc_init_class_digital();
  mrbc_init_class_adc();
  mrbc_init_class_pwm();
  mrbc_init_class_i2c();
  mrbc_init_class_spi();
  mrbc_init_class_uart();
  mrbc_init_class_get();
  mrbc_run_mrblib( FLASH_CODE );
}
void loop() {}

これをコンパイルしてバイナリを出力させて、そのファイルサイズをFLASH_CODEの行に書き込んで、もう一度バイナリを出力して、サイズが変っていないことを確認する。 methodをさらに削ると、もう少し小さくできるだろう。

マイコンで動かすスクリプトtest.rbを書いたら、以下のコマンドで書き込み用のファイルを作る。

mrbc --remove-lv test.rb
cat ch32x035.ino.CH32X035G8U.bin test.mrb >temp.bin

書き込みモードにするためにBOOTボタンを押しながらUSBに接続して、すぐに以下のコマンドを実行して書き込みをする。

sudo ./wchisp flash temp.bin 

USBSerialを使っている関係で、PCに繋いでシリアルモニタを動作させないと動かない点に注意が必要である。 これは、使っているUSBSerialの仕様だろう。 マイコン単独でも動くようにしたいところではあるが。

rubyスクリプトの例はこんな感じである。

a=ADC.new(0)
g=GPIO.new(18,GPIO::OUT)
while true
 g.write(0)
 print "off\n"
 printf "%d\n",a.read_raw()
 sleep 1
 g.write(1)
 print "on\n"
 sleep 1
end

mrblibを組み込んでいないために、loopが使えないので、whileを使うことに注意する必要がある。 これでmrbが248バイトなので、この数倍のプログラムしか組み込めないので、flashはもう少し空けたいところではある。

様々な機能を削りはしたが、CH32X035の標準のAPIとUSBSerialをrubyから使うことが出来るようになった。 もう少し工夫すれば、flashに余裕が出るかも知れないが、そのためにはmruby/cのソース本体をそれなりに変更しないといけないだろう。 また、アイディアが浮かんだら、サイズの削減に挑戦してみよう。

2026/5/18追記 以下のようにprintの代わりにwriteを使うと、ほんの少し小さくなるようだ。

USBSerial.write((uint8_t*)buf,(size_t)nbytes);