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)は無駄に思えるが、マクロの呼ばれ方によって、意図しない動作をしないようにするおまじないである。
mrbc_gpio.cの主要な部分はmrubyc_arduinoと同じだが、マクロの2を使うようになっている。
#include "mrbc_gpio.h"
static void c_gpio_new(mrb_vm *vm, mrb_value *v, int argc){
v[0] = mrbc_instance_new(vm, v[0].cls, sizeof(GPIO_HANDLE));
GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
if(argc>0){
handle->pin_num = GET_INT_ARG(1);
if( handle->pin_num==66 || handle->pin_num==67){ //swd
AFIO->PCFR1 &= ~AFIO_PCFR1_SWJ_CFG;
AFIO->PCFR1 |= AFIO_PCFR1_SWJ_CFG_DISABLE;
}
}
if(argc>1) funPinMode2( handle->pin_num, GET_INT_ARG(2) );
}
static void c_gpio_setmode(mrb_vm *vm, mrb_value *v, int argc){
uint8_t pin=0xff;
uint8_t mode;
if( v[0].tt == MRBC_TT_OBJECT ){ //Instance method mode.
GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
if(argc>0){ pin=handle->pin_num; mode=GET_INT_ARG(1); }
}else{ // Class method mode.
if(argc>1){ pin=GET_INT_ARG(1); mode=GET_INT_ARG(2); }
}
if(pin!=0xff) funPinMode2( pin, mode );
}
static void c_gpio_write(mrb_vm *vm, mrb_value *v, int argc){
GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
if(argc>0) funDigitalWrite2( handle->pin_num, GET_INT_ARG(1) );
}
static void c_gpio_write_at(mrb_vm *vm, mrb_value *v, int argc){
if(argc>1) funDigitalWrite2( GET_INT_ARG(1), GET_INT_ARG(2) );
}
static void c_gpio_read(mrb_vm *vm, mrb_value *v, int argc){
GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
SET_INT_RETURN( funDigitalRead2(handle->pin_num) );
}
static void c_gpio_high(mrb_vm *vm, mrb_value *v, int argc){
GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
SET_BOOL_RETURN( funDigitalRead2(handle->pin_num) >0 );
}
static void c_gpio_low(mrb_vm *vm, mrb_value *v, int argc){
GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
SET_BOOL_RETURN( funDigitalRead2(handle->pin_num) ==0 );
}
static void c_gpio_read_at(mrb_vm *vm, mrb_value *v, int argc){
if(argc>0) SET_INT_RETURN( funDigitalRead2( GET_INT_ARG(1) ) );
}
static void c_gpio_high_at(mrb_vm *vm, mrb_value *v, int argc) {
if(argc>0) SET_BOOL_RETURN( funDigitalRead2( GET_INT_ARG(1) ) >0 );
}
static void c_gpio_low_at(mrb_vm *vm, mrb_value *v, int argc) {
if(argc>0) SET_BOOL_RETURN( funDigitalRead2( GET_INT_ARG(1) ) ==0 );
}
void mrbc_init_class_digital(void){
mrb_class *gpio = mrbc_define_class(0, "GPIO", mrbc_class_object);
mrbc_define_method(0, gpio, "new", c_gpio_new);
mrbc_define_method(0, gpio, "setmode", c_gpio_setmode);
mrbc_define_method(0, gpio, "write", c_gpio_write);
mrbc_define_method(0, gpio, "write_at", c_gpio_write_at);
mrbc_define_method(0, gpio, "read", c_gpio_read);
mrbc_define_method(0, gpio, "high?", c_gpio_high);
mrbc_define_method(0, gpio, "low?", c_gpio_low);
mrbc_define_method(0, gpio, "read_at", c_gpio_read_at);
mrbc_define_method(0, gpio, "high_at?", c_gpio_high_at);
mrbc_define_method(0, gpio, "low_at?", c_gpio_low_at);
mrbc_set_class_const(gpio, mrbc_str_to_symid("IN"), &mrbc_integer_value(GPIO_CFGLR_IN_FLOAT));
mrbc_set_class_const(gpio, mrbc_str_to_symid("OUT"), &mrbc_integer_value(GPIO_CFGLR_OUT_50Mhz_PP));
mrbc_set_class_const(gpio, mrbc_str_to_symid("IN_PU"), &mrbc_integer_value(GPIO_CFGLR_IN_PUPD));
mrbc_set_class_const(gpio, mrbc_str_to_symid("IN_PD"), &mrbc_integer_value(GPIO_CFGLR_IN_PUPD));
mrbc_set_class_const(gpio, mrbc_str_to_symid("OUT_OD"), &mrbc_integer_value(GPIO_CFGLR_OUT_50Mhz_OD));
}
また、pull-upとpull-downは同じ定数に対応しているが、それぞれwrite(1)またはwrite(0)とする必要がある。 ちなみに、64と65はUSBで使用しているので、これらのピンを使おうすると、USBと干渉する。 66と67はSWDに割り当てられているので、使おうとするときに、SWDを無効にするようにした。
今回の修正で、100バイトほど大きくなった。 私が使っているCH32X035の開発ボードでは、新たに使えるピンは二本だけで、それに100バイトを必要とするのは、少しコストが高いかも知れない。 しかし、使えるGPIOが15本から17本となり、16本を超えたのは大きい。 ついGPIBアダプタを作りたくなってしまう。 mruby/cを使ったGPIBアダプタとかを作っている人は居るのかな。