ch32funのmruby/cでCH32X035用にインストール
CH32X035をmruby/cから使うためのch32funを利用したプログラムは、ほぼ完成に近付いたと思っている。 その情報は主に今月のブログに書かれているが、ファイルの数も増えて来たし、複雑になっているように感じる。 私でも、このブログの内容を元に、それを再現するのは時間がかかりそうだ。 そこで、この環境を簡単に構築するシェルスクリプトを書いてみた。
まず、事前にrubyをインストールしておく必要がある。 また、wgetとsedやawkも使っているので、これらも同様であるが、通常のlinuxではすでに使える様になっているはずである。 適当なフォルダを作って、以下のスクリプトを実行すると、必要なファイルを取って来て、適切に修正するようになっている。
# download wget https://github.com/mrubyc/mrubyc/archive/refs/tags/release3.4.1.zip wget https://github.com/cnlohr/ch32fun/archive/refs/heads/master.zip wget https://github.com/ch32-rs/wchisp/releases/download/nightly/wchisp-linux-x64.tar.gz # unzip and copy mkdir mrubyc_ch32fun unzip master.zip mv ch32fun-master/ch32fun/ mrubyc_ch32fun/ mv ch32fun-master/extralibs/ mrubyc_ch32fun/ mkdir mrubyc_ch32fun/mrubyc unzip release3.4.1.zip mv mrubyc-release3.4.1/src/ mrubyc_ch32fun/mrubyc/ mv mrubyc-release3.4.1/support/ mrubyc_ch32fun/mrubyc/ mv mrubyc-release3.4.1/mrblib/ mrubyc_ch32fun/mrubyc/ cat mrubyc-release3.4.1/hal/minimal.h | awk '/define MRBC_SRC/{$0=$0"\n#include \"ch32fun.h\""}1' |sed 's/delay/Delay_Ms/' > mrubyc_ch32fun/mrubyc/src/hal.h tar xzvf wchisp-linux-x64.tar.gz # usb awk '/void HandleDataOut/{a=1}a&&/^}/{$0="\tpoll_input();\n"$0;a=0}/USBPRINTF/&&!u{$0="0 //"$0;u=1}1' mrubyc_ch32fun/extralibs/fsusb.c >temp.tmp && mv temp.tmp mrubyc_ch32fun/extralibs/fsusb.c # modify files cd mrubyc_ch32fun/mrubyc/src make autogen # require ruby # remove task rm rrt0.
ch32funのmruby/cでCH32X035用のGPIOを拡張
このところ、CH32X035をmruby/cから使うために、ch32funを利用したプログラムを作ってきた。 ほぼ完成したと思っていたが、16番以降のGIPOが使えないことを思い出した。 これはch32funのarduino-likeなインターフェースの仕様のためなのだが、それを拡張する形で組み込むことが出来たので、それについて説明する。
以前にも説明したが、ch32funでは、ピンを数字で管理しており、 PAはピン番号そのまま、PBはピン番号に16を足した数字、PCは32を足した数字となっている。 これは、CH32Vでは各ポートには8ピンまたは16ピンが割り当てられているために、そのような割り当てが自然だし、上位bitを取り出すとポートの情報が得られるという利便性があるためであろう。 しかし、CH32X035では、一つのポートに最大で24ピンが割り振られている。 そして、書き込みやモードの設定に使うレジスタが、0-15と16以降で不規則に離れているために、単純な形で扱えるのが16ピンまでになっている。 そのため、0-15まではマクロを使って簡単に扱えるが、16以降はレジスタを直接操作して使うようになっている。 しかし、それではmruby/cから使えるようにするのが困難である。 そこで、ch32funのマクロを拡張して、すべてのピンの制御ができるようにして、それをmruby/cから呼び出して、GPIOを扱うことにした。
そのためには、16以降のpinに数字を割り振る必要がある。 これまでの0-47を保って、それ以降に16以降のピンを割り当てるのが他の部分に影響が無くて安全だとは思ったが、pin番号が明らさまに関連するのは、GPIO以外ではPWMぐらいなので、扱い易いように新たに数字をつけ直した。 数字に空きがあると、無駄が増えると思ったので、それぞれのポートの24本を順に並べて、0から順番に数字を割り当てることにした。 つまり、PAは番号そのまま、PBはピン番号に24を足して、PCは48を足すのである。 すると、ピンnに対して、ポートはn/24で、ピン番号はn%24で求められる。 bit演算では無いので、多少は効率は落ちるかも知れないけど。 そして、数値によって適切なアドレスのレジスタにアクセスするようにマクロを書き換えた。
定数などもch32funに合わせて変更して、出来上がったのが以下のプログラムである。 PWMなどでも使う可能性があるので、マクロの定義はmrbc_gpio.hに入れることにした。
#ifndef _MRBC_GPIO_H #define _MRBC_GPIO_H #include "ch32fun.h" #include "mrubyc.h" //PA 0-23 0-23 //PB 0-23 24-47 //PC 0-23 48-71 #define GpioOf2( pin ) ((GPIO_TypeDef *)(GPIOA_BASE + 0x400 * ((pin)/24))) #define funDigitalWrite2( pin, value ) do{ *((&GpioOf2(pin)->BSHR)+((pin%24)>>4<<2)) = 1<<(((value)?0:16)+((pin%24)&0xf));}while(0) #define funDigitalRead2( pin ) ((int)((GpioOf2(pin)->INDR >> ((pin)%24)) & 1)) #define funPinMode2( pin, mode) do{ volatile uint32_t *p = (&GpioOf2(pin)->CFGLR)+((pin%24)>>3)+(((pin%24)>>4)*5); *p&=~(0xf<<(((pin)&0x7)<<2)); *p|=((mode)<<(((pin)&0x7)<<2)); }while(0) typedef struct GPIO_HANDLE { uint8_t pin_num; } GPIO_HANDLE; void mrbc_init_class_digital(void); #endif マクロの定義のdo{}while(0)は無駄に思えるが、マクロの呼ばれ方によって、意図しない動作をしないようにするおまじないである。
ch32funのmruby/cでTaskの削除
最近、安価なCH32X035をmruby/cから使うための環境の開発を行っているが、 このマイコンにはflashが62kしか無いので、ある程度機能を制限する必要がある。 まず、Floatの削除は必須である。 mrbとして組み込んでいたmrblibも、組み込まないようにすることで、使えるmethodが減ってしまうが、容量を減らすことができる。 この状態で、flashは1k程余るのだが、ユーザーのプログラムが少し長くなると、足り無くなるだろう。
このような単純なマイコンでマルチタスクをすることは無いので、Taskも削ることができる。 mrubyc_arduinoについて2026/4/20に書いたようにして、 さらにTaskを削除すると、約3k小さくなり、合計で約4kのflashがあれば、通常の使用には十分であろう。 以前のTaskの削除の仕方は、rrt0.hやrrt0.cの中身を書き換えないといけなかったが、もう少し単純な方法を発見したので、それを紹介しよう。
まず、rrt0.hとrrt0.cと_autogen_class_rrt0.hは、完全に消してしまってよい。 ただし、mrubyc.hでrrt0.hをincludeしているので、その行をコメントアウトする。 そして、メインのコードで、rrt0.cの中でされていたメモリ確保やsleepなどの定義をすれば、Taskを削除できる。 メインのコードが少し長くなるが、この方が単純だろう。 その際のmain.cは、以下のようになる。
#include "ch32fun.h" #include <stdio.h> #include "fsusb.h" #include "mrubyc.h" #include "mrbc_gpio.h" #include "mrbc_adc.h" #include "mrbc_pwm.h" #include "mrbc_i2c.h" #include "mrbc_spi.h" #include "mrbc_uart.h" #include "mrbc_get.h" #define MEMORY_SIZE (1024*10) static uint8_t memory_pool[MEMORY_SIZE]; #define FLASH_CODE (0x8000000+59152) #define RX_BUFFER_SIZE 64 volatile uint8_t rx_buffer[RX_BUFFER_SIZE]; volatile uint8_t rx_head = 0; volatile uint8_t rx_tail = 0; void rx_buffer_push(uint8_t c) { uint8_t next = (rx_head + 1) & (RX_BUFFER_SIZE - 1); if (next !
ch32funのmruby/cのhalと標準入出力
mrubyc_arduinoでは、hal_writeなどは、メインのスケッチの中で定義されている。 ch32funを使ったmruby/cにおいても、なんとなくその流儀を踏襲していたが、ファイルの構造を眺めていたら、これらはhal.cの中で定義すべきものであることに気が付いた。 そこで、ch32fun用のhal.cを作ってみたが、こんな感じになった。
#include "hal.h" #include "ch32fun.h" int hal_write(int fd, const void *buf, int nbytes) { _write(0, (char*)buf, nbytes); return nbytes; } int hal_flush(int fd) { return 0; } void hal_abort(const char *s) {} するとこれらをメインのコードに書く必要が無くなる。
hal_writeはmruby/cの標準出力に対応するが、 2026/4/25のブログに書いたように、 私は標準入力も使いたいので、mrbc_get.cなどを作ってhal_readという関数を介してgetbyte,getc,getsを使えるようにしている。 以前はhal.hの中でhal_readの宣言を行っていたが、mruby/cのファイルをできるだけ変更するべきでは無いと考えて、mrbc_get.hに移動した。
#ifndef _MRBC_GET_H #define _MRBC_GET_H #include "mrubyc.h" unsigned char hal_read(int fd); void mrbc_init_class_get(void); #endif また、mrbc_get.cにおいては、getbyteはC言語で定義したが、getcとgetsはrubyで書いてmrbに変換して組み込んでいた。 しかし、コンパイルしたときのサイズを少しでも小さくするために、これらもC言語で書いてみた。 getsは動的なバッファを確保すると複雑になるので、一行は127文字以下とした。
#include "mrbc_get.h" static void c_object_getbyte(struct VM *vm, mrbc_value v[], int argc) { uint8_t ret=hal_read(0); SET_INT_RETURN( ret ); } static void c_object_getc(struct VM *vm, mrbc_value v[], int argc) { char buf[2] = {0,0}; buf[0]=hal_read(0); mrbc_value value = mrbc_string_new_cstr(vm, buf); SET_RETURN(value); } static void c_object_gets(struct VM *vm, mrbc_value v[], int argc){ int i; char buf[128]; for(i=0;i<127;){ buf[i]=hal_read(0); if(buf[i++]=='\n') break; } buf[i]=0; mrbc_value value = mrbc_string_new_cstr(vm, buf); SET_RETURN(value); } void mrbc_init_class_get(void){ mrbc_define_method(0, 0, "
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はそれなりにメモリが多いので、大丈夫だろう。
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 (!
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, "
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].
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 "
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 "