ch32funのmruby/cでCH32X035のUART

CH32X035には、I2CやSPIは一つずつしかないが、USARTは四つある。 WeActStudioの開発ボードの限られた端子で、この四つをUARTとして有効に活用できるかを考えてみた。 すると、flow制御を行わずRXとTXのみを使う場合には、remappingも使うと、USART1のTXを除いて利用が可能であることが分かった。 これを下の表にまとめる。 pin TX RX remapping USART1 [PB10] PB11 0,2 USART2 PA2 PA3 0 USART3 PC18(DIO) PC19(DCK) 1 USART4 PB0 PB1 0 角括弧は端子が存在しないことを意味するが、ピンの数の多いパッケージなら使えるだろう。 そこで、これらの四つのUARTを、ch32funのmruby/cで使えるようにしてみた。 TXやRXを扱うには、主に三つの方法がある。 最も単純なのは、読み書きを指示したら、それが完了するまで待って、次に移る方法である。 プログラムは短くなるが、その処理の間はCPUを占有することになる。 次の方法は、割り込みを利用する方法であり、割り込みがかかるまではCPUがフリーになるが、それぞれの割り込み処理を書く必要がある。 そして、DMAを使う方法である。 UARTとバッファのやりとりをDMAに任せることができるが、処理がいつ完了したかなどを意識する必要がある。 CH32X035のDMAには8つのchannelがあり、下の表のように、どのUSARTに割り当てられるかが決まっているが、四つのUSARTのすべてにDMAを割り当てることもできる。 DMA TX RX USART1 4 5 USART2 7 6 USART3 2 3 USART4 1 8 mruby/cで、メモリやflashをできるだけ節約する形で、UARTを実装するにはどうしたら良いのかを考えたが、TXは単純にデータを送り、RXはDMAを使うことにした。 TXでDMAを使うためには、送信が終了するまでデータをバッファーにためて置く必要があり、mruby/cで臨時に作ったバッファーが廃棄されてしまうと駄目になってしまうので、仮にDMAを使う場合でも、すべての転送が終わるまで、そのサブルーチンを終了できず、DMAを使うメリットはあまり無い。 一方、RXはいつ来るか分からないデータを待って、それらを監視または割り込みで処理するのは面倒なので、受信バッファを用意しないといけないが、DMAを使うことにして、必要なときに、受信状態を調べることにした。 多量のデータが来て、バッファが一杯になる可能性もあり、それをどう処理するかが問題であるが、今回はそれは見送ることにした。 4つのUARTを区別しないといけないので、mrbc_uart.hの中で、UART_HANDLEを定義した。 #ifndef _MRBC_UART_H #define _MRBC_UART_H #include "ch32fun.h" #include "mrubyc.h" #define UART_BUF_SIZE 128 typedef struct UART_HANDLE { uint8_t unit_num; uint8_t rx_index; char rx_buf[UART_BUF_SIZE]; } UART_HANDLE; void mrbc_init_class_uart(void); #endif 一つのUARTを定義すると、バッファの分だけメモりを消費するが、CH32X035はそれなりにメモリが多いので、大丈夫だろう。
Read more...

ch32funのmruby/cでCH32X035のSPI

I2Cだと電源などを合わせても、4本の配線があれば通信できるのに対して、SPIだと6本必要になる。 また、I2Cは同じ配線で複数のICとの通信が可能であるが、SPIではIC毎にセレクト信号を使うなどの工夫をしなければいけない。 少ない配線で多くのICと通信できる方が楽なので、SPIよりはI2Cを使うことが多く、私はSPIはあまり使わない。 しかし、SPIを使わないといけない場合もあるだろう。 そこで、ch32funのmruby/cからch32x035のSPIを使えるようにしてみた。 CH32X035にはSPIは一つしかないので、 mrbc_spi.hは単純に以下のようにした。 #ifndef _MRBC_SPI_H #define _MRBC_SPI_H #include "ch32fun.h" #include "mrubyc.h" void mrbc_init_class_spi(void); #endif 通信する相手毎に設定が異なる場合には、その設定を記憶しておいた方が使い易くなるかも知れないが、メモリやflashを減らすために、単純な仕様にした。 標準のAPIのガイドラインに従って、SSはmruby/c側で制御することにして、 mrbc_spi.cで必要な関数などを定義した。 #include "mrbc_spi.h" extern uint8_t * make_output_buffer(mrb_vm *vm, mrb_value v[], int argc, int start_idx, int *ret_bufsiz); void spi_init(){ RCC->APB2PCENR |= RCC_APB2Periph_SPI1; //PA4 NSS funPinMode(5,GPIO_CFGLR_OUT_50Mhz_AF_PP); //PA5 SCK funPinMode(6,GPIO_CFGLR_IN_FLOAT); //PA6 MISO funPinMode(7,GPIO_CFGLR_OUT_50Mhz_AF_PP); //PA7 MOSI } #define SPI_TIMEOUT_MAX 100000 void spi_transfer(uint8_t *buf, uint32_t len) { uint32_t timeout; for (uint32_t i = 0; i < len; i++) { timeout = SPI_TIMEOUT_MAX; while (!
Read more...

ch32funのmruby/cでCH32X035のI2C

私がこのところ使っているch32x035のボードは、WeActStudioの開発ボードである。 そこには、QFN20というパッケージのCH32X035F8U6が使われており、ピンの数が20個と少ない。 さらに、その中の2ピンはUSBに使われているので、他の用途には使えない。 このような制約の下に、ハードウェアI2Cを使おうとすると、PC18とPC19を使う以外の方法が無いことが分かる。 以前、mrubyc_arduinoからI2Cを使えるようにプログラムを作ったが、このCH32X035ボードではおそらくI2Cは使えないだろう。 今回、ch32funのmruby/cからch32x035のI2Cを使えるようにしてみた。 I2Cに使うPC18とPC19の二つのピンは、WCH-Linkでの書き込みに使うDIOとDCKに割り当てられている。 I2Cを有効にすると、WCH-Linkでの書き込みはできなくなるが、USBから書き込えるので問題無いだろう。 そして、DIOとDCKにGNDと3.3Vを合わせて書き込み用の4ピンのピンヘッダを取り付けられるようになっており、これをそのままI2Cに使えるというメリットがある。 mrbc_i2c.hはほとんど何も指定する必要は無いので、以下の内容である。 #ifndef _MRBC_I2C_H #define _MRBC_I2C_H #include "ch32fun.h" #include "mrubyc.h" void mrbc_init_class_i2c(void); #endif mrbc_i2c.cの #include "mrbc_i2c.h" #define I2C_TIMEOUT_MAX 100000 #define WAIT_FOR_FLAG(reg, flag) { \ uint32_t timeout = I2C_TIMEOUT_MAX; \ while (!((reg) & (flag))) { \ if (--timeout == 0) return -1; \ } \ } extern uint8_t * make_output_buffer(mrb_vm *vm, mrb_value v[], int argc, int start_idx, int *ret_bufsiz); void i2c_init(){ AFIO->PCFR1 &= ~AFIO_PCFR1_SWJ_CFG; AFIO->PCFR1 |= AFIO_PCFR1_SWJ_CFG_DISABLE; GPIOC->CFGXR &= ~(0xF << 8 | 0xF << 12); //PC18 DIO, PC19 DCK GPIOC->CFGXR |= GPIO_CFGLR_OUT_50Mhz_AF_PP << 8 | GPIO_CFGLR_OUT_50Mhz_AF_PP << 12; RCC->APB1PCENR |= RCC_APB1Periph_I2C1; AFIO->PCFR1 &= ~AFIO_PCFR1_I2C1_REMAP; AFIO->PCFR1 |= AFIO_PCFR1_I2C1_REMAP_1 | AFIO_PCFR1_I2C1_REMAP_0; //011 I2C1->CTLR1 &= ~I2C_CTLR1_PE; I2C1->CTLR2 = (FUNCONF_SYSTEM_CORE_CLOCK / 1000000); I2C1->CKCFGR = FUNCONF_SYSTEM_CORE_CLOCK / (100000 << 1); //100kHz I2C1->CTLR1 |= I2C_CTLR1_PE; I2C1->CTLR1 |= I2C_CTLR1_ACK; } int i2c_start(uint8_t adrs, uint8_t rw){ // rw=1 if read uint32_t timeout = I2C_TIMEOUT_MAX; while (I2C1->STAR2 & I2C_STAR2_BUSY) { if (--timeout == 0) return -1; } I2C1->CTLR1 |= I2C_CTLR1_START; WAIT_FOR_FLAG(I2C1->STAR1, I2C_STAR1_SB); I2C1->DATAR = (adrs << 1) | rw; WAIT_FOR_FLAG(I2C1->STAR1, I2C_STAR1_ADDR); (void)I2C1->STAR1; (void)I2C1->STAR2; return 0; } int i2c_write(const uint8_t *buf, uint16_t len){ for (uint16_t i = 0; i < len; i++) { WAIT_FOR_FLAG(I2C1->STAR1, I2C_STAR1_TXE); I2C1->DATAR = buf[i]; } WAIT_FOR_FLAG(I2C1->STAR1, I2C_STAR1_BTF); return 0; } int i2c_read(uint8_t *buf, uint16_t len){ I2C1->CTLR1 |= I2C_CTLR1_ACK; for (uint16_t i = 0; i < len; i++) { if(i==len-1) I2C1->CTLR1 &= ~I2C_CTLR1_ACK; WAIT_FOR_FLAG(I2C1->STAR1, I2C_STAR1_RXNE); buf[i] = I2C1->DATAR; } I2C1->CTLR1 |= I2C_CTLR1_STOP; return 0; } void c_i2c_new(mrb_vm *vm, mrb_value *v, int argc){ i2c_init(); } void c_i2c_write(mrb_vm *vm, mrb_value *v, int argc){ uint8_t *buf = 0; int bufsiz = 0; if( argc>0 ){ uint8_t adrs = GET_INT_ARG(1); i2c_start(adrs,0); if(argc>1){ buf = make_output_buffer( vm, v, argc, 2, &bufsiz ); i2c_write(buf, bufsiz); mrbc_free( vm, buf ); } I2C1->CTLR1 |= I2C_CTLR1_STOP; } SET_RETURN( mrbc_integer_value(bufsiz) ); } void c_i2c_read(mrb_vm *vm, mrb_value *v, int argc){ uint8_t *buf = 0; int bufsiz = 0; mrbc_value ret = mrbc_nil_value(); if( argc>1 ){ int adrs = GET_INT_ARG(1); int size = GET_INT_ARG(2); if( argc > 2 ) { i2c_start(adrs,0); buf = make_output_buffer( vm, v, argc, 3, &bufsiz ); i2c_write(buf, bufsiz); mrbc_free( vm, buf ); } i2c_start(adrs,1); ret = mrbc_string_new(vm, 0, size); uint8_t *p = (uint8_t *)mrbc_string_cstr(&ret); i2c_read(p,size); } SET_RETURN(ret); } void mrbc_init_class_i2c(void){ mrb_class *i2c = mrbc_define_class(0, "
Read more...

ch32funのmruby/cでCH32X035のPWM

GPIOやADCは、ch32funでもArduinoと同じような感覚で、マイコンの種類を意識しないで使うことができる。 ch32funでmruby/cを使うときには、これを利用してmrubyc_arduinoのコードを少し変更するだけで、GPIOとADCを使うことができた。 また、USB CDCも予想していたよりは簡単に、マイコンの違いはあまり考慮する必要な無く、組み込むことに成功した。 しかし、ch32funでそれ以外の機能を使うときには、マイコンの細かな違いを考慮して、プログラムを作る必要がある。 これはそれなりに面倒なのだが、CH32X035用のmruby/cの機能を充実させるために、PWMを使えるようにしてみた。 CH32X035には、三つのタイマーがあり、これらを用いることによって、PWMを使うことができる。 すべてのピンでPWMが使えるわけでは無いが、 タイマーとセッティングとチャンネルの設定を駆使することによって、かなりのピンでPWMを使えるようにすることができる。 逆に、一つのピンでPWMを使う場合には、これらの選択の仕方が一通りでない場合もあるので、その時には他のピンとの相性などから、一つの設定を選ぶ。 CH32X035のPA0-15とPB0-15とPC0-15について、どのような設定でPWMを使うかを考えてみると、その約2/3でPWMが使えることが分かった。 ch32funでは、16番以降のピンを扱うのは面倒なので、今回はそれらは対象外とした。 timerとsettingとchを八進法で表すと8ビットに収まるので、配列を作ってピンと対応させた。 あとは、タイマー、セッティング、チャンネル毎に、適切なレジスタに必要な値を代入するようにすれば良い。 ESP8266用のPWMのプログラムを変更して作ったら、最初はfloatを使うようになっていて、そのせいでコンパイルしたときのサイズがかなり大きくなってしまった。 そこで、整数しか使わないようにしたら、ましにはなったけど、それでも2kはある。 素直にプログラムを書いたら、 タイマーで分岐して、セッティングを変更して、チャンネルでさらに分岐して、必要な操作をするようにしたので、 多くの条件分岐が必要で、レジスタへ定数を代入する箇所が多数あり、そのために大きくなってしまっているようである。 レジスタのビットの場所を調べて、レジスタ操作をまとめたら、1.7kぐらいにはなった。 さらに、 構造体のポインタを駆使して、レジスタの指定も条件分岐しないで指定できるようにしたところ、可読性はかなり悪くなったが、サイズは1.3kまで小さくなった。 まあ、このくらいが限界かな。 mrbc_pwm.hは、周波数とdutyを整数にしたので、以下のようにした。 #ifndef _MRBC_PWM_H #define _MRBC_PWM_H #include "ch32fun.h" #include "mrubyc.h" typedef struct PWM_HANDLE { uint8_t pin_num; uint16_t duty_num; // unit 1/10000 uint32_t freq_num; } PWM_HANDLE; void mrbc_init_class_pwm(void); #endif ただし、floatを使わず整数だけで扱うようにしたことによって、dutyが整数のパーセントだと桁数が少なくなってしまうので、dutyの100倍の値を整数として保存するようにして、桁数を確保することにした。 パーセントの655.35倍にしても良いのだけど。 mrbc_pwm.cは、少し長くなるが、以下のような感じになった。 これでも、複雑な条件分岐無くなったので、かなり短かくなっている。 #include "mrbc_pwm.h" static volatile uint8_t pin_timer[]={ // timer, map, ch 0201, 0202, 0203, 0204, 0332, 0, 0301, 0302, 0, 0, 0, 0, 0215, 0216, 0217, 0, 0116, 0117, 0, 0223, 0224, 0312, 0125, 0126, 0127, 0121, 0122, 0123, 0124, 0, 0, 0212, 0131, 0132, 0133, 0134, 0, 0135, 0136, 0137, 0, 0, 0, 0, 0, 0, 0262, 0263, }; static volatile uint32_t base[]={TIM1_BASE,TIM2_BASE,TIM3_BASE}; void general_pwm(uint8_t pin, uint16_t limit, uint16_t val, uint16_t prescaler) { if(pin>=48) return; uint8_t tmr=pin_timer[pin]; if(tmr == 0) return; uint8_t ch=tmr%8; uint8_t alt=(tmr/8)%8; // alternate setting tmr>>=6; TIM_TypeDef *tim = ((TIM_TypeDef *) base[tmr-1]); volatile uint32_t *enadrs=&(RCC->APB2PCENR); *(enadrs+tmr/2) |= 1<<((tmr+10)%12); //RCC_APB2Periph_TIMx 0x800,1,2 AFIO->PCFR1 &= ~( (7-tmr/3*4)<<(tmr*3+12) ); // AFIO_PCFR1_TIM1_REMAP 7,7,3 AFIO->PCFR1 |= alt<<(tmr*3+12); // AFIO_PCFR1_TIMx_REMAP_0 15,18,21 tim->PSC = prescaler; tim->CTLR1 |= TIM_ARPE; tim->ATRLR = limit; tim->CCER |= (TIM_CC1E | TIM_CC1P)<<(4*(ch-1)-ch/5*14); // 1,1N,2,2N,3,3N,4 volatile uint16_t *chadrs=&(tim->CHCTLR1); *(chadrs+((ch-1)&2)) |= (TIM_OC1M_2 | TIM_OC1M_1 | TIM_OC1PE)<<((ch-1)%2*8); volatile uint32_t *vladrs=&(tim->CH1CVR); *(vladrs+(ch-1)%4) = val; tim->BDTR |= TIM_MOE; tim->SWEVGR |= TIM_UG; tim->CTLR1 |= TIM_CEN; funPinMode(pin, GPIO_CFGLR_OUT_50Mhz_AF_PP); } void c_pwm_sub(mrb_vm *vm, mrb_value *v){ PWM_HANDLE *handle = (PWM_HANDLE *)v[0].
Read more...

ch32funのmruby/cでUSB CDC

先日、ch32funを使うことによって、CH32X035にmruby/cを組み込んで、GPIOとADCを使えるようにしてみた。 mrubyc_arduinoに比べると、予想通りflashを圧迫しないという利点がある。 しかし、ADCを使えても、その値を表示できないという致命的な欠点があった。 この欠点を解消する最も単純な方法は、USB Serialを使えるようにすることである。 ch32funのexampleを活用すると、mruby/cに比較的簡単にUSB Serialを組み込むことができたので、それを紹介する。 USB CDCを使えるようにするためには、 2026/6/1のブログのように環境を整えてから、 一部のファイルの内容を変更する必要がある。 まず、 ch32funでUSB Serialの入力を受け付けるためには、 poll_input()を呼ぶ必要があるが、 mruby/cではそのコード中で実行するのが難しいので、 extralibs/fsusb.cの中で、HandleDataOutの最後にpoll_input()を呼び出すように書き加えた。 そして、 標準入力を組み込むために、2026/4/25のブログを参考にしてmrbc_get.*をmrubyc/srcに入れる。 そして、mrubyc/src/hal.hにhal_readの宣言を加えた。 USB Serialを使うCH32X035用の新しいフォルダを作って、必要なファイルを配置する。 Makefileは先のブログと同じ内容でよい。 USB CDCを使いたいので、 ch32funのexamples_usb/USBFS/usbfs_cdc_ttyからusb_config.hをコピーして、フォルダに入れる。 funconfig.hは、USBを動かすのに必要な設定をするために、以下のようにした。 #ifndef _FUNCONFIG_H #define _FUNCONFIG_H #define FUNCONF_USE_DEBUGPRINTF 0 #define FUNCONF_USE_USBPRINTF 1 #define FUNCONF_USE_HSI 1 #define FUNCONF_DEBUG_HARDFAULT 0 #define FUNCONF_USE_CLK_SEC 0 #define FUNCONF_5V_OPERATION 1 #endif そして、main.cは、USBの入力を取り込むring bufferを作って、以下のようにした。 #include "ch32fun.h" #include <stdio.h> #include <string.h> #include <stdint.h> #include <stdbool.h> #include "fsusb.h" #include "mrubyc.h" #include "mrbc_gpio.h" #include "mrbc_adc.h" #include "
Read more...

ch32funでmruby/c

mrubyc_arduinoを使って,rubyからマイコンを使える環境を整えて来た。 Wifiを使うなら、EPS8266に各種の機能を実装できたので、それを使うと良いだろう。 Wifiが必要ないのなら、価格や性能などを考えると、CH32V203が最適であると感じている。 しかし、CH32V203はマイコンボードの入手性があまり良くない。 BluePill+が日本でも600円程度で入手可能だが、ボードが少し大きめである。 同じようにUSBが使えるマイコンにはCH32X035があり、400円程度でボードが容易に手に入る。 しかし、かなり機能を削らないとflashに収まらない。 これまでは、環境の構築の容易さや、汎用性を重視して、mrubyc_arduinoを使っていたが、 CH32については、ch32funの方が、コンパイルサイズが小さいので、ch32funとmruby/cを組み合せて使ってみた。 ArduinoはC++なので、C言語と組み合せるときに工夫が必要だが、 ch32funはC言語なので、mruby/cと相性が良いというメリットもある。 しかし、機能を実装するのが面倒という大きなデメリットがあり、今回はArduinoっぽく使えるようになっているGPIOとADCを使えるようにしてみた。 まず、必要なファイルを揃えないといけないので、 例えばmrubyc_ch32funというフォルダを作って、その中に以下のようにファイルやフォルダを配置する。 ch32funの最新のファイルをzipで取って来て、その中のch32funとextralibsのフォルダを入れる。 mruby/cのリリースは3.4.1が最新なので、それを持って来て、mrubycというフォルダを作って、その下にsrcとsupportフォルダを入れる。 さらに、hal/minimal.hをmrubyc/src/hal.hにコピーする。 hal.hの中では、“ch32fun.h"をincludeするようにして、delayをDelay_Msに変更する。 必要なファイルを生成するために、mrubyc/srcフォルダの中で、make autogenを実行する。 mrubyc/src/vm_config.hでは、MRBC_USE_FLOATを0にして、MRBC_REQUIRE_32BIT_ALIGNMENTのコメントアウトを外してこれを有効にする。 FLOATを0にすると、そのままではコンパイルできないので、2026/04/15のブログを参考にして、いくつかのファイルを変更する。 mrblibを組み込まないclass.cの括弧を除いた最後の二行をコメントアウトする。 ch32fun用のコンパイラは、 Debianの場合は2025/11/25のブログを参考にしてインストールできる。 これで、準備は完了である。 ch32x035用のプログラムを作るために、先のフォルダの中にCH32X035というフォルダを作って、必要なファイルをつくる。 コンパイルに必要なMakefileは、以下の内容にする。 all : flash TARGET:=main TARGET_MCU?=CH32X035 MRUBYC_SRC := $(wildcard ../mrubyc/src/*.c) ADDITIONAL_C_FILES += $(MRUBYC_SRC) CFLAGS += -I../mrubyc/src -DNDEBUG CFLAGS += -Os -flto include ../ch32fun/ch32fun.mk flash : cv_flash clean : cv_clean ch32funで組み込まれるfunconfig.hは、特に何の指定も必要無いので、以下のようにする。 #ifndef _FUNCONFIG_H #define _FUNCONFIG_H #endif 中心となるmain.cは、以下のようにした。 #include "ch32fun.h" #include <stdio.h> #include "mrubyc.h" #include "
Read more...

ESP8266のArduinoでmruby/cのPWM

これまでに、mrubyc_arduino用にいくつかの機能を組み込むためのプログラムを書いて来た。 しかし、PWMについては、不十分な点があった。 その時には、Arduinoの機能のみを使うことにしたので、APIのガイドラインのすべての機能を実装できていないかったのである。 dutyとfrequencyは、それぞれAnalogWriteとToneを使って変化させることが出来るのだが、これらを同時に変えることが出来無いので、それを実現するのは諦めていた。 マイコン毎に別々にプログラムを書くのは非効率なので、mrubyc_arduinoを用いることによって、共通のプログラムで様々なマイコンが使えるようにしてきた。 しかしESP8266では、WiFiを扱うために、専用のプログラムを作ったが、そこではArduinoから使える関数を使った。 ESP8266専用のプログラムが一つあるのだから、PWMも専用のプログラムを書いても良いのでは無いかと思うようになった。 それでもハードに近い部分までいじるのは避けたい。 少し調べると、ESP8266用のArduinoでは、 AnalogWriteやToneを実現するためには、startWaveformという関数が使われており、この関数を使えば、dutyとfrequencyを同時に変えることができることが分かった。 mrbc_pwm.hは、pin番号以外に、dutyとfrequencyを保存するために少しだけ書き換えた。 #ifndef _MRBC_PWM_H #define _MRBC_PWM_H #include <Arduino.h> #ifdef __cplusplus extern "C" { #endif #include "mrubyc.h" typedef struct PWM_HANDLE { uint8_t pin_num; float duty_num; float freq_num; } PWM_HANDLE; void mrbc_init_class_pwm(void); #ifdef __cplusplus } #endif #endif mrbc_pwm.cppは、dutyとfrequencyの値から、highの時間とlowの時間を計算して、startWaveformに渡すようなサブルーチンを作って、それを他の関数から呼び出すようにした。 少し長くなったが、ガイドラインに沿った機能が使えるようになった。 #include "mrbc_pwm.h" #include <core_esp8266_waveform.h> extern "C" { void c_pwm_sub(mrb_vm *vm, mrb_value *v){ PWM_HANDLE *handle = (PWM_HANDLE *)v[0].instance->data; if(handle->freq_num==0){ stopWaveform(handle->pin_num); }else{ uint32_t timeLow=(uint32_t)(1e6/(handle->freq_num)); uint32_t timeHigh=(uint32_t)(timeLow*(handle->duty_num)/100); timeLow = timeHigh>timeLow ?
Read more...

USBSerialを使ったCH32V203用のmrubyc_arduino

single wire debugを標準入出力としたCH32V203用のmruby/c環境は、2026/5/3や5/17のブログで紹介した。 CH32V203のUSB機能を活用して、USB serialを標準入出力にしたmruby/c環境を作りたいと考えていたが、いくつもの問題があって、なかなか実現出来無かったが、なんとかそれなりのものが出来たので、その方法について説明したい。 mrubyc_arduinoのライブラリやCH32用のpackageのインストールは2026/5/3のブログと同じである。 USB serialは21km43さんのライブラリを使うことにしたので、2026/5/16のブロクを参考にしてインストールする。 通常のflashに配置するデータを減らすために、2026/5/17のブログに書いたように、Link.ldを変更する。 USB CDCを使うためには、最適化でLTOが指定できないので、サイズが大きすぎるので、さらに工夫をする必要がある。 Floatを消すために、vm_config.hのMRBC_USE_FLOATを0にして、2026/4/15のブログに書いたようにいくつかのファイルを修正し、Taskも消すために、2026/4/20のブログに書いたようにrrt0.hとrrt0.cを修正して、_autogen_class_rrt0.hを削除する。 そして、以下のスケッチをsmallestでコンパイルすると、ぎりぎり64kに入った。 #include <usb_serial.h> #include <mrubyc.h> #define MEMORY_SIZE (1024*10) #define FLASH_CODE 0x8020000 //ch32v203 static uint8_t memory_pool[MEMORY_SIZE]; int hal_write(int fd, const void *buf, int nbytes) { Serial.write((uint8_t*)buf,(size_t)nbytes); return (nbytes); } int hal_flush(int fd) { return 0; } unsigned char hal_read(int fd) { while (Serial.available() == 0); return Serial.read(); } void setup() { Serial.begin(115200); 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_get(); mrbc_run_mrblib( FLASH_CODE ); } void loop() {} 実はここでも少し工夫をしていて、Serial.
Read more...

拡張flashを活用するCH32V203用のmrubyc_arduino

CH32V203でmrubyc_arduinoを使うために、2026/5/3のブログでは、FloatとTaskを無効にしたり、mrblibを拡張flashに移動させたりする方法を紹介した。 拡張flashをさらに有効に活用することによって、Floatを無効にするだけで、mruby/cを動かすことに成功したので、その手法を紹介する。 環境の構築は、先のブログの通りである。 Arduinoのスケッチは非常に単純で、以下の通りである。 #include <mrubyc.h> #include "swd.h" #define MEMORY_SIZE (1024*10) #define FLASH_CODE 0x8020000 //ch32v203 static uint8_t memory_pool[MEMORY_SIZE]; static volatile char inbytes=0; static volatile uint8_t last=' '; extern "C" { void handle_debug_input( int numbytes, uint8_t * data ) { inbytes+=numbytes; last = data[0]; } } int hal_write(int fd, const void *buf, int nbytes) { _write(0, (char*)buf, nbytes); return (nbytes); } int hal_flush(int fd) { return 0; } unsigned char hal_read(int fd) { while(inbytes==0) _write(0, "
Read more...

CH32V203でArduinoからUSBSerial

Arduino上で、CH32V203のUSBSerialを使おうとしたが、思ったよりも苦労した。 このマイコンのUSBは以前にArduinoから使ったことがあったので、その時と同じようにすれば良いと予想していたが、使い方が変わったりしていて、素直には動かなかったが、動かし方が分かったので、そのコツを書いておく。

CH32V203でArduinoからUSBSerialを使う場合、いくつかの方法がある。その中で最も一般的なのがTinyUSBを使う方法である。 TinyUSBのライブラリは、「ライブラリを管理」から「Adafruit TinyUSB Library」を選ぶことでインストールできる。 最新versionは3.7.7で、それなりに頻繁に更新されているようだ。 すぐに動くと思ったのに、なかなかうまく動かなくて苦労したが、最終的には次のようにすると動くことが分かった。 スケッチ例のCDCのserial_echoを見ると参考になるが、ライブラリのヘッダを取り込んで、TinyUSBDevice.begin(0);を実行してから、SerialTinyUSBをSerialと同様に使えばよい。 以前使ったときには,Adafruit_USBD_CDCオブジェクトをUSBSerialなどの名前で作って使っていたが、使い方が変わったようだ。 私が試したところ、最適化でSmallest with LTOでは、USBがうまく認識せず、Smallestだとうまく動いた。 しかし、その場合には、簡単なプログラムでもコンパイルサイズが24k程と、かなり大きくなった。 また、 Suzuduinojobitjosephさんのものなどの、 WCH公式以外のarduino coreで、USB supportを選択できる場合でも、「なし」としないとエラーが出てしまった。 これはおそらくversionの相性の問題だろうと考えている。

サイズがもう少し小さいものとしては、 21km43さんのライブラリを使う方法がある。 これを使うには少しだけ準備が必要である。 ファイルをdownload zipとして取って来て、解凍してからsrcフォルダをCH32V203_USBCDCなどと名前を変えてから、Arduinoのlibrariesフォルダに入れるのである。 使い方は単純で、以下のようにヘッダを取り込んだら、Serialという名前で使える。

#include <usb_serial.h>

こちらも、Smallest with LTOだと駄目で、Newlib Nano + USBDにしても、LTOだと動かない。 やはりSmallestにする必要があり、このときのサイズは15kとなった。 TinyUSBよりは小さいが、それでもまだ大きいと感じる。 また、個人的にはUARTと混同しないように、Serialとは別の名前で動いてくれた方が良いので、usb_serial.hとusb_serial.cppを変更しようかと考えている。

LTOを指定した場合には、割り込みに必要な関数などが使われていないと判断されて、消去されてしまってUSBが動かないのだろう。 これらの関数に、敵切に__attribute__((used))などの指定をつければ、LTOも使えるのでは無いかと思うが、それがどこに定義されているのかを調べるのが大変だろう。 一方、LTOを指定しない時には、使われていない多くの関数が残って、コンパイルサイズが大きくなっていると思われる。 もう少しサイズが小さく出来無いものかな。

Read more...