Longan Nanoでmruby/c


マイコンとしては、最近はch32を使うことが多くなってきた。 ch32はRISC-Vなので、安価であるという利点があるというのも、その大きな理由の一つである。 同じくRISC-Vのマイコンとしては、stm32とピン互換であるGigaDevice社のgd32vf103がある。 このマイコンは、flashが128k、RAMが32kと比較的容量が大きいが、マイコン単体よりもLongan Nanoというマイコンボードが非常に安価で販売されていることで有名である。 このボードには160*80ドットのフルカラーLCDも内蔵されている。 マイコンを使うときには、状態を表示したいこともあり、その場合はキャラクタLCDを使ったりしていたが、このボードならそれを増設する必要も無い。 遅れ馳せながら、Longan Nanoを試してみた。

gd32では、コンパイラはch32と同じものが使える。 今回は、メーカーのライブラリを Makefileに対応させたものを用いた。 これに含まれるLCDのライブラリは評価ボード用のものだったので、Longan Nano用のLCDを扱うために、マイコンボードの開発元である Sipeed社のプログラムを改良した Longan Nano ExamplesのLCDライブラリを用いた。 Debianで必要なパッケージをインストールして、Makefileを少し修正すれば良い。 コンパイルの準備が整ったので、試しに標準出力をLCDにしたmruby/cを作ってみた。

細かく説明するよりも、シェルスクリプトを示した方が分かり易いと思うので、コンパイル環境を整えて、mruby/cと組み合せるところまでの処理を示す。

sudo apt install build-essential gcc-riscv64-unknown-elf picolibc-riscv64-unknown-elf dfu-util
wget -O gd32vf103_firmware_library_gcc_makefile-main.zip https://github.com/cjacker/gd32vf103_firmware_library_gcc_makefile/archive/refs/heads/main.zip
wget -O Longan-RISC-V-examples-master.zip https://github.com/linusreM/Longan-RISC-V-examples/archive/refs/heads/master.zip
wget https://github.com/mrubyc/mrubyc/archive/refs/tags/release3.4.1.zip
unzip gd32vf103_firmware_library_gcc_makefile-main.zip 
mv gd32vf103_firmware_library_gcc_makefile-main mrubyc_gd32
unzip Longan-RISC-V-examples-master.zip 'Longan-RISC-V-examples-master/05-LCD/lib/lcd/*'
mkdir mrubyc_gd32/User/lcd
mv Longan-RISC-V-examples-master/05-LCD/lib/lcd/include/*.h mrubyc_gd32/User/lcd
mv Longan-RISC-V-examples-master/05-LCD/lib/lcd/src/*.c mrubyc_gd32/User/lcd
sed -i -e 's/riscv-none-elf-/riscv64-unknown-elf-/' -e 's/nano.specs/picolibc.specs/' -e '/main.c/a C_SOURCES += $(wildcard ./mrubyc/*.c)' -e '/main.c/a C_SOURCES += User/lcd/lcd.c' -e '/CFLAGS += -DGD32VF103C_START/a CFLAGS += --specs=picolibc.specs' -e '/CFLAGS += -DGD32VF103C_START/a CFLAGS += -I./mrubyc -DNDEBUG' mrubyc_gd32/Makefile
unzip release3.4.1.zip
cd mrubyc-release3.4.1/src/
make autogen
cd ../..
mv mrubyc-release3.4.1/src/ mrubyc_gd32/mrubyc
cp mrubyc-release3.4.1/hal/minimal.h mrubyc_gd32/mrubyc/hal.h
sed -i -e 's/delay(/delay_1ms(/' mrubyc_gd32/mrubyc/hal.h
sed -i -e '/#define MRBC_SRC_HAL_H_/a #include "systick.h"'  mrubyc_gd32/mrubyc/hal.h

ここでは内容は省略するが、mruby/cに機能を追加するためのgd32_gpio.hなどを用意して、mrubyc_gd32/mrubycの中に配置する。 これらは、ch32用のものを改造したら、思ったよりも簡単に作れた。

main.cはmrubyc_gd32/Userの中にあり、これを以下の内容に書き換えて、mrubyc_g32/でmakeすると、build/gd32vf103cbt6.binができる。

#include "gd32vf103.h"
#include "systick.h"
#include <stdio.h>
#include "lcd/lcd.h"

#include "mrubyc.h"
#include "gd32_gpio.h"
#include "gd32_adc.h"
#include "gd32_pwm.h"
#include "gd32_i2c.h"
#include "gd32_spi.h"
#include "gd32_uart.h"
//#include "mrbc_get.h"

#define MEMORY_SIZE (1024*10)
static uint8_t memory_pool[MEMORY_SIZE];
#define FLASH_CODE (0x08000000+102400) //gd32vf103cbt6

static uint16_t lcd_x = 0;
static uint16_t lcd_y = 0;

int write(int file, char *ptr, int len)
{
    for (int i = 0; i < len; i++) {
        char c = ptr[i];
        if (c == '\n') {
            lcd_x = 0;
            lcd_y += 16;
        } else {
	  if(lcd_y>=80) lcd_y=0;
	  if(lcd_x==0) for(int j=0;j<20;j++) LCD_ShowChar(j*8,lcd_y,' ',0,WHITE);
	  LCD_ShowChar(lcd_x, lcd_y, c, 0, WHITE); 
            lcd_x += 8;
	    if(lcd_x>=160){lcd_x=0; lcd_y += 16;}
        }
    }
    return len;
}

void load_continuous_tasks(const uint8_t *start_addr) {
  for (int i = 0; i < MAX_VM_COUNT; i++) {
    if (memcmp(start_addr, "RITE", 4) != 0) break;
    mrbc_create_task(start_addr, 0);
    start_addr += // big endian
      ((uint32_t)start_addr[8]  << 24) |
      ((uint32_t)start_addr[9]  << 16) |
      ((uint32_t)start_addr[10] <<  8) |
      (uint32_t)start_addr[11];
  }
}

int main(void)
{
  SystemCoreClockUpdate();
  eclic_init(ECLIC_NUM_INTERRUPTS);
  eclic_mode_enable();
  eclic_global_interrupt_enable();

  Lcd_Init();
  LCD_Clear( BLACK );
  BACK_COLOR=BLACK;
  
  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();

  load_continuous_tasks( (const uint8_t *) FLASH_CODE );
  mrbc_run();
  while(1){}
}

サイズは100k弱とそれなりに大きくなったが、Floatも組み込んでいるし、今回は小さくする工夫はあまりしていないし、文字のフォントも含んでいるし、こんなものだろう。 bootボタンを押しながら、resetボタンを押すと、書き込みモードになるので、以下のコマンドで書き込む。

dfu-util -a 0 -s 0x08000000:leave -D build/gd32vf103cbt6.bin

rubyのコードは例えばtemp.rbを作って、mrbcで処理してから、以下のコマンドで書き込む。

dfu-util -a 0 -s 0x08019000:leave -D temp.mrb

標準出力をLCDにしたので、そのまま出力が見れて、便利である。 折角カラーなので、エスケープシーケンスで文字色や背景色を変えられるようにするのも面白いかも知れない。 GPIOとPWMとADCは動くことを確認したが、それ以外は書くだけ書いて、面倒なので動作確認はしていない。 動作確認できたら、公開しようかな。 また、標準入力はできなくなってしまった。 USB-CDCなども作ってみるのも良いが、標準出力は使ってしまっているので、rubyからどのように使うようにするかが問題である。 12bitのDACもあるようなので、それも実装したいが、DACクラスやメソッドをどうすべきかも考えないといけない。 しかし、このマイコンボードは生産が終了してしまっているようなので、新たに使い始めるには適していないとも言える。 もっと早く知っていれば。