ESP8266用のmrubyc_arduino

mruby/cをESP32で使っているという記事はよく見掛けるが、ESP8266で使っているという情報はあまり見掛けない。 ESP8266には、プログラムを載せられるflash領域が1Mと、 システム用も合わせてメモリは96kあり、 mrubyc_arduinoを使うためのスペックを十分に備えている。 標準的な機能に加えて、wifiもmruby/cから使えるようになったので、 ESP8266をmrubyc_arduinoから使う方法をまとめておこうと思う。 mrubyc_arduinoのライブラリのインストールは2026/5/3のブログとほぼ同じだが、vm_config.hにはwifiも組み込む。 #include "mrbc_wifi.h" ArduinoでESP8266を使えるようにするには、 設定のURLに以下を追加して、ボードマネージャーからesp8266を検索してインストールする。 http://arduino.esp8266.com/stable/package_esp8266com_index.json 自分の使うボードを撰択して、メモリのオプションは 「16KB+48KB IRAM and 2nd Heap (shared)」とするのが、httpsにアクセスできるようにするコツである。 ESP8266は十分な大きさのflashがあるので、mrubyc_arduinoのカスタマイズは不要で、Floatも使える。 但し、バイトオーダで32binを有効にしなければならない。 #define MRBC_REQUIRE_32BIT_ALIGNMENT 私は試していないが、Mathなども組み込むことが出来るかも知れない。 Arduinoのスケッチは、以下のようにした。 #include <mrubyc.h> #define MEMORY_SIZE (1024*10) static uint8_t memory_pool[MEMORY_SIZE]; #define FLASH_CODE 0x40280000 //esp8266 int hal_write(int fd, const void *buf, int nbytes) { Serial.print((char*)buf); 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.
Read more...

mrubyc_arduinoでESP8266からhttps

一週間と少し前にArduinoを使ってESP8266からhttpsにアクセスできることは確認できたが、その機能をmrubyc_arduinoに組み込むのに苦労していた。 Arduinoで動いたコードをmruby/cから呼び出せるようにして、プログラムは間違っていないように見えて、httpにはアクセスできるけど、httpsだとうまく行かないという症状になり、原因がなかなか分からなかった。 プログラムをいじったりしてもほとんど症状が変らずに、諦めかけていたが、httpsの処理にはメモリを沢山使うということを思い出して、メモリの設定をいじったらうまく行くようになった。 wifiはmruby/cの標準的な機能では無いので、どのようなクラスを定義すべきかから考える必要があり、 mrubyc-esp32-wifiやmrubyc-esp32などを参考にした。 ヘッダファイルmrbc_wifi.hは、代わり映えがしないが、以下のようにした。 #ifndef _MRBC_WIFI_H #define _MRBC_WIFI_H #include <Arduino.h> #ifdef __cplusplus extern "C" { #endif #include "mrubyc.h" void mrbc_init_class_wifi(void); #ifdef __cplusplus #endif #endif メインのmrbc_wifi.cppでは基本的なmethodを定義した。 #include "mrbc_wifi.h" #include <ESP8266WiFi.h> extern "C" { void c_wifi_connect(mrb_vm *vm, mrb_value *v, int argc){ if(argc>1) WiFi.begin( mrbc_string_cstr(&v[1]), mrbc_string_cstr(&v[2]) ); } void c_wifi_disconnect(mrb_vm *vm, mrb_value *v, int argc){ WiFi.disconnect(true); } void c_wifi_connected(mrb_vm *vm, mrb_value *v, int argc){ SET_BOOL_RETURN( WiFi.status() == WL_CONNECTED ); } void c_wifi_ip(mrb_vm *vm, mrb_value *v, int argc){ IPAddress ip=WiFi.
Read more...

ch32v203用のmrubyc_arduino

ch32v203には、公式なflashが64kと、さらに非公式で160kのflashが搭載されている。 mrubyc_arduinoに標準のAPIを組み込むと64kほどになるので、ch32v203はmrubyc_arduinoを使ってみるのに最低限の機能を有したマイコンの一つと言えるだろう。 ch32v203でmrubyc_arduinoを動かすための情報については、 これまでにこのブログにいろいろなことを書いて来たが、 その使い方をここにまとめておこうと思う。 まず、Arduinoでmrubycが使えるようにライブラリを追加する。 mrubyc_arduino をzipとしてdownloadして、「スケッチ-ライブラリをインクルード-.ZIP形式のライブラリをインストール」からインストールする。 そのライブラリに標準のAPIを追加するために、 以前紹介した私が作ったmrbc_*.*やoutputbuffer.cを ~/Arduino/libraries/mrubyc_arduino-main/src にコピーする。 また、これらを取り込むように、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" ここで、ファイル名はmrbc_*に統一したので、過去のブログとはファイル名が若干異なっている場合があるので注意して欲しい。 ch32やESP8266では、バイトオーダのオプションを変更する必要があり、 vm_config.hの中の以下の行を、コメントアウトを外して有効にする。 #define MRBC_REQUIRE_32BIT_ALIGNMENT これで、mrubyc_arduinoの準備は完了である。 次に、Arduinoでch32を使えるようにパッケージを追加する。 設定のURLに以下を追加して、ボードマネージャーからch32を検索してWCHサポートをインストールする。 https://github.com/openwch/board_manager_files/raw/main/package_ch32v_index.json WCHサボートのリリースにはToneなどが無いので、 最新版からTone.*とHardwareTimer.*を持って来て、~/.arduino15/packages/WCH/hardware/ch32v/1.0.4/cores/arduino/にコピーする。 これらのファイルを取り込むように、そのフォルダにあるwiring.hのHardwareTimer.hとTone.hのincludeをuncommentする。 Arduinoでは、ボードを選択して、最適化のオプションはsmallest with LTOとしておく。 最後に、mrubyc_arudinoをch32v203用にカスタマイズする。 まず、そのままだと64kのflashに収まり切らないので、いくつかの機能を削って、工夫をする。 floatを消すために、vm_config.hのMRBC_USE_FLOATを0にして、2026/4/15のブログに書いたようにいくつかのファイルを修正する。 Taskも消すために、2026/4/20のブログに書いたようにrrt0.hとrrt0.cを修正して、_autogen_class_rrt0.hを削除する。 実行するタスクは一つだけになったので、vm_config.h中で指定されているMAX_VM_COUNTを1にする。 mrblibをメインのflashから消すために、mrblib.cを削除する。 これで、64kに収まるようになる。 Single Wire Debugを標準入出力にするために、2026/5/2のブログに書いたようにswd.hとswd.cを作って、Arduinoのスケッチと同じフォルダに置く。 Arduinoのスケッチは以下のようにした。 #include <mrubyc.h> #include "swd.h" #define MEMORY_SIZE (1024*10) #define MRBLIB_CODE 0x8010000 //ch32v203 #define FLASH_CODE 0x8012000 //ch32v203 extern const uint8_t mrblib_bytecode[] = { 0x52,0x49,0x54,0x45,0x30,0x33,0x30,0x30,0x00,0x00,0x00,0x41,0x4d,0x41,0x54,0x5a, 0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0x25,0x30,0x33,0x30,0x30, 0x00,0x00,0x00,0x19,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05, 0x11,0x01,0x38,0x01,0x69,0x00,0x00,0x00,0x00,0x45,0x4e,0x44,0x00,0x00,0x00,0x00, 0x08,}; static uint8_t memory_pool[MEMORY_SIZE]; static volatile char inbytes=0; static volatile uint8_t last=' '; extern "
Read more...

ch32v203のSWD機能

mrubyc_arduinoでは、hal_writeに何らかの出力命令を定義しておくと、それが標準出力として取り扱われる。 標準出力には通常はSerialを使うことが多いが、SerialはUARTからも使えるので、標準出力は他の出力に割り当てることができる。 CH32マイコンでは、Single Wire Debug(SWD)機能があり、プログラムの書き込みもこの機能を使って行うことができるが、入出力としても使うことができる。 この機能をArduinoから扱うことができれば、これをmrubyc_arduinoの標準入出力に割り当てることが可能になる。 ch32v203について、SWD機能の使い方について調べてみた。 SWD機能については、ch32v203のリファレンスマニュアルにも使い方がほとんど書いてないので、その機能を使っているプログラムのソースから使い方を読み取るしか無い。 まず、Arduinoのpackageの中を調べてみたが、この機能についての記述は見付からなかった。 そこで、WCH社のプログラムを調べてみると、EVT/EXAM/SRC/Debug/に、この機能についての関数が定義されていることを発見した。 Arduinoでも同様のファイルは、packages/WCH/hardware/ch32v/1.0.4/system/CH32V20x/SRC/Debug/にあるのだが、こちらの方が古いファイルのようで、若干の違いがあり、その機能についての関数が無い。 新しい方のdebug.cには、アクセスするためのアドレスや、初期化と出力に対応する関数が、以下のように定義されている。 #define DEBUG_DATA0_ADDRESS ((volatile uint32_t*)0xE0000380) #define DEBUG_DATA1_ADDRESS ((volatile uint32_t*)0xE0000384) void SDI_Printf_Enable(void) int _write(int fd, char *buf, int size) これらをArduinoにうまく組み込めば、使えるようになりそうなことまでは分かった。 一方、このSWDのデータを表示するLinuxで使えるツールとしては、ch32funのminichlinkが便利である。 しかし、minichlinkと上記のdebug.cとは相性が悪いようだ。 そこで、ch32funで使われているSWDのコードを使うことにした。 そのためのヘッダとして、swd.hを作った。 #ifndef __CH32_SWD_H #define __CH32_SWD_H #include "stdio.h" #ifdef __cplusplus extern "C" { #endif #define FUNCONF_DEBUGPRINTF_TIMEOUT (1<<31) // Wait for a very very long time. #define DMDATA0 ((volatile uint32_t*)0xe0000380) #define DMDATA1 ((volatile uint32_t*)0xe0000384) void poll_input( void ); int _write(int fd, const char *buf, int size); void SetupDebugPrintf( void ); #ifdef __cplusplus } #endif #endif そして、このファイルをincludeして、 ch32fun.
Read more...

mrubyc_arduinoにUART

mruby/cの標準的なAPIで、mrubyc_arduinoにまだ組み込んでいないのは、UARTだけになっていた。 標準入力を使えるようにしたので、UARTの必要性が感じられなくて、なかなかやる気にならなかったが、ようやくUARTも作ってみた。 今回もC言語とC++とArduinoとrubyの仕様の違いに苦しめられたが、なんとか完成した。 まずmrbc_uart.hでは、getsのときの最大の文字数を定義した。 #ifndef _MRBC_UART_H #define _MRBC_UART_H #include <Arduino.h> #ifdef __cplusplus extern "C" { #endif #include "mrubyc.h" #define UART_GETS_MAX 256 void mrbc_init_class_uart(void); #ifdef __cplusplus } #endif #endif ArduinoではSerialも通常はC++で書かれているので、メインのコードのファイル名はmrbc_uart.cppとして、様々なメソッドを定義した。 unit番号に応じてSerialやSerial1などを何らかの変数に入れて、それを使ってコマンドを実行したかったが、C++のオブジェクトのポインタを使うと何が起こるか分からなかったので、まずはswitch-caseで分岐するようにして書いたので、冗長になってしまった。 その際、モードの設定のswitch-caseでbreakを忘れたために、パリティ付に設定されて二文字目以降が文字化けして、その原因を見付けるのに苦労した。 また、ArduinoではSerialのモードはSERIAL_8N1などで設定するのだが、この型がマイコンによって異なっていたので、それを格納する変数を定義するときに、decltypeを用いた。 getsはreadBytesUntilを使えば簡単に書けそうに思ったけど、はまってしまった。 ArduinoのreadBytesUntilではsizeを指定しないといけなくて、改行コードが取り除かれるという点が、rubyのgetsとは異なる。 一文字以上が入力されるのを待って、timeoutを指定して、readBytesUntilで読み取ることにしたらうまくいった。 しかし、やはり冗長なのが気に入らないので、なんとかクラスのポインタを使って、それをrubyのUARTオブジェクトに保存して、以下のように比較的短くすることができた。 #include "mrbc_uart.h" extern "C" { typedef struct UART_HANDLE { decltype(Serial) *unit_serial; } UART_HANDLE; void c_uart_setmode(mrbc_vm *vm, mrbc_value v[], int argc){ UART_HANDLE *handle = (UART_HANDLE *)v[0].instance->data; decltype(SERIAL_8N1) myUARTSettings=SERIAL_8N1; MRBC_KW_ARG( baudrate, baud, data_bits, stop_bits, parity, flow_control, txd_pin, rxd_pin, rts_pin, cts_pin ); int32_t baud_rate = 9600; int datb = 8; int stpb = 1; int prty = 0; if( MRBC_KW_ISVALID(baudrate) ) baud_rate = mrbc_integer(baudrate); if( MRBC_KW_ISVALID(baud) ) baud_rate = mrbc_integer(baud); if( MRBC_KW_ISVALID(data_bits) ) datb = mrbc_integer(data_bits); if( MRBC_KW_ISVALID(stop_bits) ) stpb = mrbc_integer(stop_bits); if( MRBC_KW_ISVALID(parity) ) prty = mrbc_integer(parity); switch(prty){ case 0: myUARTSettings=(stpb<2)?
Read more...

ESP8266でArduinoからhttpsにアクセス

ESP8266は十年ちょっと前に発売されたが、簡単にwifiにつなげるということで、私も十年前ぐらいから使い始めた。 その頃は、まだhttpが主流だったので、ArduinoIDEを使ってESP8266からhttpにアクセスするIoT機器などを作ったりしていた。 しばらくすると、ESP8266でmicropythonを使えるようになり、プログラムはpythonの方が楽なので、ESP8266はmicropythonから使うようになった。 その頃に、ESP32やmrubyの事を知って、ESP32は使ってみたが、mrubyは敷居が高い気がして使って来なかった。 昨年、久々にIoT機器を作る必要性が出て来たが、ほとんどのサイトはhttpsとなっていた。 始めはESP8266を使おうとしたけど、メモリ不足のためかmicropythonからはhttpsにアクセスすることは出来無かった。 結局、ESP32でmicropythonを使ってhttpsにアクセスして、IoT機器を作った。 ちなみにその後、アクセスしていたwebサーバが更新されたようで、通信が不安定になったので、原因を調べないといけないのだけれど、まだできていない。 しかし、ESP8266でhttpsにアクセスできないとなると、ESP8266を使う用途が非常に限定されてしまう。 最近、mruby/cをArduinoから扱うようになって、mruby/cやArduinoはmicropythonよりメモリの使用量が少ないはずだから、mrubyc_arduinoならESP8266でもhttpsにアクセスできるのでは無いかと思うようになった。 それを確かめる準備として、まずはArduinoからhttpsにアクセスする方法を試してみた。 少し調べてスケッチ例のESP8266WiFi-HTTPSRequestを見付けたが、これはあらかじめ証明書を準備しておく方法で、面倒な上に期限がある。 もう少し探すと、 6年前の記事 を見付けて、この方法なら証明書なども不要で、いつでもアクセスできそうだったので、これを元に試してみた。 Arduinoの宣言部分では、ライブラリを組み込んで、wifiやアクセスしたいサイトの情報を書いておく。 #include <ESP8266WiFi.h> #include <WiFiClientSecure.h> const char* ssid = "SSID"; const char* password = "PASSWORD"; const char* host = "host"; String url = "/index.html"; httpsを読み取ってファイルの内容を返す関数は以下のように定義した。 String https_get(String host, String url){ BearSSL::WiFiClientSecure client; client.setTimeout(500); client.setInsecure(); if (!client.connect(host.c_str(), 443)) { Serial.println("failed"); return "failed"; } client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "
Read more...

mruby/cでstdin

様々なマイコンで、簡単に mruby/c を使えるように出来るのではないかと思って、 mrubyc_arduino を拡張して、 これまでに、GPIO,ADC,PWM,I2C,SPIは動くようになった。 また、必要なflashの容量を節約するために、FLOATを使わなくする機能のバグを修正したり、マルチタスクを無効にすることが出来るようになった。 次はUARTを組み込もうかなとも思ったが、関連する機能を別の方法で実現してみた。 mruby/cでは、puts,print,printfなどのstdoutに出力するコマンドを、hal_writeを介して使うことができる。 一方、gets,getcなどのstdinから入力するコマンドは、 mrubyにはあるようだが、mruby/cには無い。 mrubyc_arduinoでは、hal_writeはSerialから出力するように指定してあり、Serialからの入力もできるようになれば、UARTを組み込まなくてもSerialを使えるようになることになる。 そこで、stdinからの入力コマンドをどのようにすれば実現できるかを試してみた。 まずやったのが、putsなどと同様にしてgetbyteなどを定義できるのでは無いかと予想して、 c_object.cや_autogen_class_object.hやhal.hに必要だと思われる修正をしたが、メソッドが無いとエラーが出てしまう。 autogen_class*.hで定義された定数をどこかでmethodとして定義できるようにしているのだと思っていたが、kernelのコマンドとして動かす場合は、別のやり方が必要なようだ。 どうなっているのだろう。 仕方が無いので、sleepなどを定義しているのと同じ方法で、mrbc_define_methodを使ってやってみたら、動作させることに成功した。 Arduinoで一文字読み出すようにhal_readを定義する。 unsigned char hal_read(int fd) { while (Serial.available() == 0); return Serial.read(); } 標準入力を組み込むためのヘッダファイルmrbc_get.hを作る。 #ifndef _MRBC_GET_H #define _MRBC_GET_H #include <Arduino.h> #ifdef __cplusplus extern "C" { #endif #include "mrubyc.h" void mrbc_init_class_get(void); #ifdef __cplusplus } #endif #endif //extern getbyteはhal_readの値をそのまま返すように書けば良いが、getcやgetsはC言語でどう書くのか考えていたが、getbyteを使ってrubyで書いて、それをmrbcのコードとして組み込んじゃえば良いということになった。 そのためのrubyのスクリプトget.rbがこれである。 def getc getbyte.chr end def gets() s="" s<<c=getc until c=="\n" s end 面倒だったので、getsは引数を取れるようにはしていない。 これをmrbcで処理してbyteコードを取り出す。
Read more...

mrubyc_arduinoにSPI

I2Cに続いて、mrubyc_arduinoにSPIを組み込んでみた。 Arduinoでは、SPIは通常はSPI.hとSPI.cppに書かれており、 これをmruby/cから呼び出す。 ArduinoでのSPIの設定の仕方が、以前とは変っていたので、少し手間取ったが、おそらく実装できただろう。 ヘッダファイルは、spi.hにしようと思ったが、ArduinoのSPI.hなどと干渉する可能性があるので、mbc_spi.hとした。 その内容は以下の通り、初期化関数だけを宣言した。 #ifndef _MRBC_SPI_H #define _MRBC_SPI_H #include <Arduino.h> #ifdef __cplusplus extern "C" { #endif #include "mrubyc.h" void mrbc_init_class_spi(void); #ifdef __cplusplus } #endif #endif SPIでは、周波数、ビットの順番、モードなどを設定する必要がある。 また、通信時にreadとwriteが同時に行われるという特徴をもつ。 そのため、I2Cよりは少し複雑になったが、 SPIクラス実装編 を参考にして、試行錯誤の末に完成した mrbc_spi.cppは、以下のとおりである。 #include "mrbc_spi.h" #include <SPI.h> extern "C" { static SPISettings mySPISettings = SPISettings(1000000, MSBFIRST, SPI_MODE0); extern uint8_t * make_output_buffer(mrb_vm *vm, mrb_value v[], int argc, int start_idx, int *ret_bufsiz); void c_spi_setmode(mrbc_vm *vm, mrbc_value v[], int argc){ MRBC_KW_ARG( unit, frequency, mode, first_bit ); int32_t spi_freq = 1000000; int spi_mode = SPI_MODE0; int spi_first_bit = MSBFIRST; if( MRBC_KW_ISVALID(frequency) ) spi_freq = mrbc_integer(frequency); if( MRBC_KW_ISVALID(first_bit) ) spi_first_bit = mrbc_integer(first_bit); if( MRBC_KW_ISVALID(mode) ){ switch(mrbc_integer(mode)){ case 1: spi_mode = SPI_MODE1; break; case 2: spi_mode = SPI_MODE2; break; case 3: spi_mode = SPI_MODE3; break; } } mySPISettings = SPISettings(spi_freq, spi_first_bit, spi_mode); MRBC_KW_DELETE( unit, frequency, mode, first_bit ); } void c_spi_new(mrb_vm *vm, mrb_value *v, int argc){ v[0] = mrbc_instance_new(vm, v[0].
Read more...

mruby/cでI2C

mrubyc_arduinoにI2Cを組み込んだので、ESP8266を使ってテストしてみた。 少し困ったのが、ピン番号が分かり難いことだった。 Arduinoのファイルの中を見て、 I2Cでどのピンが使われているのかを調べたら、以下のように定義されていた。 #define PIN_WIRE_SDA (4) #define PIN_WIRE_SCL (5) しかし、今度はその番号がどのピンか分からないので、これも調べたら、以下のようになっていた。 static const uint8_t D1 = 5; static const uint8_t D2 = 4; つまり、D1がSCLでSDAがSDAのようだ。 不思議なナンバリングな気がする。 I2Cで通信する相手としては、AHT10という温湿度計を用いた。 mruby/c用のプログラムは以下のようにした。 g=GPIO.new 2,GPIO::OUT i=I2C.new loop{ print "reading\n" g.write 1 i.write 0x38,0xba sleep_ms 20 i.write 0x38,0xe1,0x08,0x00 sleep_ms(10) while ( i.read(0x38,1).ord & 0x80 > 0) i.write 0x38,0xac,0x33,0x00 sleep_ms(10) while ( i.read(0x38,1).ord & 0x80 > 0) d=i.read(0x38,6).bytes h=(d[1]<<12)+(d[2]<<4)+(d[3]>>4) printf( "humidity %.2f\n",h*100.0/0x100000 ) t=((d[3] & 0x0f)<<16)+(d[4]<<8)+d[5] printf( "temperature %.
Read more...

mrubyc_arduinoにI2C

mrubyc_arduinoには、 GPIO, ADC, PWMはすでに組み込んだので、 mruby, mruby/cのI/O APIガイドラインに規定されているもので、 残っているのはI2C, SPI, UARTの三つである。 その内から、I2Cを組み込んでみた。 Arduinoでは、I2Cは通常はWire.hとWire.cppに書かれており、 C++を使っているので、C言語で書かれているmruby/cと組み合せるのに少し苦労したが、 なんとか動くようになった。 まず、i2c.hだが、これは以下のようにした。 #ifndef _I2C_H #define _I2C_H #include <Arduino.h> #ifdef __cplusplus extern "C" { #endif #include "mrubyc.h" void mrbc_init_class_i2c(void); #ifdef __cplusplus } #endif #endif mruby/cの他のマイコンへの実装では、 writeのときに数値や配列などの様々な形で指定されたものを出力するために、 make_output_bufferという関数を使っている。 この関数はSPIなどでも使うので、outputbuffer.cという別のファイルを作って、cppからも呼び出せるように以下のおまじないで囲んでおいた。 #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif mrbc_allocは(void *)なのだが、エラーが出ることがあったので、(uint8_t *)に型変換をして用いるという一箇所だけ変更を加えた。 i2c.cppは、ArduinoのWireの使い方を使って、以下のように書いた。 #include "i2c.h" #include <Wire.h> extern "C" { extern uint8_t * make_output_buffer(mrb_vm *vm, mrb_value v[], int argc, int start_idx, int *ret_bufsiz); void c_i2c_new(mrb_vm *vm, mrb_value *v, int argc){ Wire.
Read more...