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 "mrbc_get.h"

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

#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 != rx_tail) {
    rx_buffer[rx_head] = c;
    rx_head = next;
  }
}
bool rx_buffer_available(void) {
    return rx_head != rx_tail;
}
uint8_t rx_buffer_pop(void) {
  if (rx_head == rx_tail) return 0;
  uint8_t c = rx_buffer[rx_tail];
  rx_tail = (rx_tail + 1) & (RX_BUFFER_SIZE - 1);
  return c;
}
void handle_usbfs_input(int numbytes, uint8_t *data) {
  for( int i = 0; i < numbytes; i++ ) rx_buffer_push(data[i]);
}

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(!rx_buffer_available());
  return rx_buffer_pop();
}
void hal_abort(const char *s) {}

long atol(const char *str) {
    long res = 0;
    int sign = 1;
    while (*str == ' ' || (*str >= 9 && *str <= 13)) { str++; }
    if (*str == '-') { sign = -1; str++; }
    else if (*str == '+') { str++; }
    while (*str >= '0' && *str <= '9') {
        res = res * 10 + (*str - '0');
        str++;
    }
    return res * sign;
}

int main()
{
  SystemInit();
  funGpioInitAll();
  funAnalogInit();
  mrbc_init(memory_pool, MEMORY_SIZE);
  mrbc_init_class_digital();
  mrbc_init_class_adc();
  mrbc_init_class_get();
  USBFSSetup();
  mrbc_create_task( (uint8_t *) FLASH_CODE, 0 );
  mrbc_run();
  while(1){}
}

USBからマイコンに送られたデータは、 HandleDataOutで処理されて、一旦はusb_inputbufferに保存している。 メモリを節約するためには、これとrx_bufferを共通化することも出来そうだ。 しかし、 プログラムの変更を最低限にするために、 poll_inputの中から呼び出されるhandle_usbfs_inputで、 rx_bufferに移動させるようにした。 USB CDCを組み込むことによって、2.5kほどサイズが大きくなった。 ArduinoでCH32X035_USBSerialを使ったときには、3k弱だったので、さらに少しはコンパクトになったかな。

これで、ch32funを用いてCH32X035の最低限の機能をmruby/cから扱えるようになった。 mruby/cの標準的なAPIとしては、PWM,I2C,SPI,UARTが残っているが、個人的にはI2C以外はあまり使わない気がする。 気が向いたら、これらも実装してみようと思う。