この記事は 自作OS Advent Calendar 2021 の 8日目の記事として書かれました。

 

OS自作初心者です。「ゼロからのOS自作入門」を3月20日に購入して以来、少しずつ読んではgit checkout osbook_dayXXして動作確認をしてきました。動作確認は最初は実機でやっていたのですが搭載しているUSBが2.0だったので途中からQemuでの確認に変更しました。現在22章まで読みました。

ただし22章まで読んだと言っても3回読んだら理解できないところがあっても先に進んでいます。OS自作の勉強は初めてなのでしっかり理解できてから先に進むというやり方だと挫折しそうなので、最低3周は読むつもりでまずは少しでも早く1周目を済ませようという方針です。なので現時点では読んだ箇所の理解もかなり浅いです。


ところで、MikanOSが外部とつながるといいな、マイコンのセンサデータをMikanOSに送れると面白そうだなとは思っていましたがどうすればよいのか全く分からなかったので放置していました。OS自作における真っ当なアプローチとしてはMikanOSにシリアル通信やLANのサポートを追加することなのでしょうが、そういうのは自分はどこから手を付ければよいのかも分かりません。いつかはチャレンジしたいのですが。

現在自分が所有しているマイコンはM5Stackなのですが、ある日偶然にM5StackをBLEキーボードとして動作させるライブラリ(https://github.com/T-vK/ESP32-BLE-Keyboard)があることを知りました。MikanOSにはTerminal上で動くアプリがいくつかあります。例えば逆ポーランド記法で計算するrpnはTerminalに対して"rpn 5 4 + Enterキー"とキーボードから入力すると5+4が計算され9が得られます。ということはM5Stackのボタンを押せば"rpn 5 4 + Enterキー"とBLEキーボードから入力したように動作させるM5Stackアプリを書けばM5StackからMikanOSのrpnアプリを動かせるかも知れないと気付きました。これができればM5Stackに搭載されている3軸加速度センサデータを送信し、それを受け取ったMikanOSアプリで目玉を動かしたり3次元立方体を回転させたりもできるはずです。

ただ、MikanOS自体はBLEのサポートをしていないのでBLEキーボードからの入力を受け取れるか分かりません。そこでまず本物のBLEキーボードを購入しました。元々1台あってもいいかなと思っていたのでこの際買いました(言い訳)。それをWindows10に認識させると当然のことですがメモ帳等Windows上のアプリにはキーボード入力できました。次にWindows10上のWSL2上のQemu上のMikanOSのTerminalについても実験したところ、嬉しいことに入力できました。
\(^o^)/
BIOSかWindows10かWSL2かQemuの誰かがBLEキーボードをUSBキーボードに見せかけてくれているようです。

ここまで確認できれば後はM5StackのBLEキーボードアプリを作るだけです。

M5StackアプリM5BLEkeyboard
-------------------------------------------------
#include <M5Stack.h>
#include <BleKeyboard.h>
 
BleKeyboard bleKeyboard("M5BLEkeyboard");
bool blestate = false;
 
void showstate(char *txt) {
    M5.Lcd.setCursor(0, 120);
    M5.Lcd.fillRect(0, 120, 320, 20, BLACK);
    M5.Lcd.printf(txt);
}
 
void setup() {
    M5.begin();
    M5.Power.begin();
 
    bleKeyboard.begin();
 
    M5.Lcd.clear(BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.println("Application");
    M5.Lcd.println("A: rpn 5 4 +");
    M5.Lcd.println("B: stars 7");
    M5.Lcd.println("C: hanoi 3"); // 自作のハノイの塔アプリ
    showstate("Disconnected");
}
 
void loop() {
    M5.update();
 
    if (bleKeyboard.isConnected()) {
        if (!blestate) {
            blestate = true;
            showstate("Connected");
        }
 
        if (M5.BtnA.wasPressed()) {
            bleKeyboard.print("rpn 5 4 +");
            bleKeyboard.write(KEY_RETURN);
        } else if (M5.BtnB.wasPressed()) {
            bleKeyboard.print("stars 7");
            bleKeyboard.write(KEY_RETURN);
        } else if (M5.BtnC.wasPressed()) {
            bleKeyboard.print("hanoi 3");
            bleKeyboard.write(KEY_RETURN);
        }
    } else {
        if (blestate) {
            blestate = false;
            showstate("Disconnected");
        }
    }
}
-------------------------------------------------

期待通りうまくいくことを確認しました。

次にM5Stackの3軸加速度の送信をしようかと思ったのですが、その前にファイル転送ができるのではと思いつきました。アイデアは次の通りです。

・MikanOSアプリとしてテキストにエンコードされたデータを標準入力から受け取りデコードして標準出力に出すrcvfを作る。
・M5Stackアプリとしてボタンを押すと"rcvf > 出力ファイル Enterキー"に引き続き送信したいファイルの中身をテキストエンコードしてキー入力として送信するものを作る。

ただしM5Stackアプリの方はSDカードのファイルを読み込んでテキストデータへエンコードする部分は未だできておらず、事前にファイルをテキストエンコードしたデータをハードコーディングしてあります。またテキストエンコードは以前Base64の関数は書いたことがあったので使いたかったのですが見つからず、とりあえず1バイトのバイナリデータを16進数表記(つまり2バイト)にエンコードしています。

M5StackアプリM5BLEkeyboard2
-------------------------------------------------
#include <M5Stack.h>
#include <BleKeyboard.h>

// Hello, MikanOS!(LF)
// I am M5bleKeyboard.(LF)
char hlm[][65] = {
"48656c6c6f2c204d696b616e4f53210a4920616d204d35626c654b6579626f61",
"72642e0a0a",
"xx"
};

// mikan.jpg
char mikan[][65] = {
"ffd8ffe000104a46494600010101009000900000ffe100224578696600004d4d",
"002a00000008000101120003000000010001000000000000ffdb004300020101",
"0201010202020202020202030503030303030604040305070607070706070708",
"090b0908080a0807070a0d0a0a0b0c0c0c0c07090e0f0d0c0e0b0c0c0cffdb00",
"4301020202030303060303060c0807080c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
"0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
"0c0cffc00011080010001003012200021101031101ffc4001f00000105010101",
"01010100000000000000000102030405060708090a0bffc400b5100002010303",
"020403050504040000017d010203000411051221314106135161072271143281",
"91a1082342b1c11552d1f02433627282090a161718191a25262728292a343536",
"3738393a434445464748494a535455565758595a636465666768696a73747576",
"7778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3",
"b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7",
"e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f010003010101010101010101000000",
"0000000102030405060708090a0bffc400b51100020102040403040705040400",
"010277000102031104052131061241510761711322328108144291a1b1c10923",
"3352f0156272d10a162434e125f11718191a262728292a35363738393a434445",
"464748494a535455565758595a636465666768696a737475767778797a828384",
"85868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9",
"bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5",
"f6f7f8f9faffda000c03010002110311003f00fd1cfda1ff006cbd63c4daceab",
"a3e9faa58e8be1bba9e4d1205c47f69d419b745cbc99f99c87088ab923aeee83",
"13f631fda1af7e0f78a7fb0eff00c4faf78b7c305ed342582ff518ae5bc31222",
"20400aa073949a02eb231608c8c0f66a3fb4d7ec8dae787fc473abe8f79ac68f",
"0ea7fdaba2ea76f662edf4eb8dcef1b84c398e68f7b286230464e7e6da25fd9c",
"bf652d7bc53e20b45fec9b9d36cee3524d4f5cd5eeacbec2f7b32795c84daa64",
"90c70c51020615117e63b307f89e19c71f4789b91caabadcf6e4e5fdd72f36f7",
"b5b9396fadef7b1fb94b2ce189652aab50b72fc57f7ef6d6eaf76efd1a3fffd9",
"xx"  
};
 
BleKeyboard bleKeyboard("M5BLEkeyboard");
bool blestate = false;
 
void showstate(char *txt) {
    M5.Lcd.setCursor(0, 120);
    M5.Lcd.fillRect(0, 120, 320, 20, BLACK);
    M5.Lcd.printf(txt);
}
 
void setup() {
    M5.begin();
    M5.Power.begin();
 
    bleKeyboard.begin();
 
    M5.Lcd.clear(BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.println("Application");
    M5.Lcd.println("A: rpn 5 4 +");
    M5.Lcd.println("B: send hlm.txt");
    M5.Lcd.println("C: send mikan.jpg");
    showstate("Disconnected");
}
 
void loop() {
    M5.update();
 
    if (bleKeyboard.isConnected()) {
        if (!blestate) {
            blestate = true;
            showstate("Connected");
        }
 
        if (M5.BtnA.wasPressed()) {
            bleKeyboard.print("rpn 5 4 +");
            bleKeyboard.write(KEY_RETURN);
        } else if (M5.BtnB.wasPressed()) {
            bleKeyboard.print("rcvf > hlm.txt");
            bleKeyboard.write(KEY_RETURN);
            for (int i = 0; i < sizeof hlm / sizeof hlm[0]; i++) {
                bleKeyboard.print(hlm[i]);
                bleKeyboard.write(KEY_RETURN);
            }
        } else if (M5.BtnC.wasPressed()) {
            bleKeyboard.print("rcvf > mikan.jpg");
            bleKeyboard.write(KEY_RETURN);
            for (int i = 0; i < sizeof mikan / sizeof mikan[0]; i++) {
                bleKeyboard.print(mikan[i]);
                bleKeyboard.write(KEY_RETURN);
            }
        }
    } else {
        if (blestate) {
            blestate = false;
            showstate("Disconnected");
        }
    }
}
-------------------------------------------------

MikanOSアプリrcvf.cpp
-------------------------------------------------
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include "../syscall.h"
#define BUFLEN 1000


int h2int(char c)
{
        if ('0' <= c && c <= '9')
                return c - '0';
        else if ('A' <= c && c <= 'F')
                return c - 'A' + 10;
        else if ('a' <= c && c <= 'f')
                return c - 'a' + 10;
        else
                return 0;
}

int hh2int(char h, char l)
{
        return (h2int(h) << 4) | h2int(l);
}

extern "C" void main(int argc, char** argv)
{
        char buf[BUFLEN];
        int  len;

        while (1) {
                fgets(buf, BUFLEN, stdin);
                len = strlen(buf) - 1; // 改行コードの分を引く

                for (int i = 0; i < len - 1; i += 2) {
                        if (buf[i] == 'x' || buf[i] == 'X') // "xx" は行頭が前提
                                exit(0);
                        putchar(hh2int(buf[i], buf[i + 1]));
                }
        }

        exit(0);
}
-------------------------------------------------

MikanOSがリダイレクトや標準入出力をサポートするのは最後の方なので、まだ22章までしか読めていないですが git checkout osbook_day30f した後に実験したところテキストファイルとjpegファイルの送信に成功しました。テキストファイルがMikanTerm内でcatされ、jpegファイルが左上にちょこんと表示されているのが分かるでしょうか。


MikanOSのおかげで遊びのアイデアが沢山浮かんできて楽しいです。uchan_nosさんには楽しい遊び場を提供していただいたことに感謝しております。
<(_ _)>
 

すごく間が空いてしまった。

なぜかブログのことを忘れてました。

世間がわさわさしていて知らぬ間に自分も浮足立っていたのかな。

 

で、さらっと復活。

 

勉強会で、Tinkercad Circuitsというブラウザ上で使える電子回路のCADを教えてもらいました。抵抗、コンデンサだけでなく、ブレッドボード、オシロスコープ、74シリーズのIC、Arduino UNOレベルの部品を並べて配線してシミュレーションできます。良い時代になった!

 

そこでふと思い出したのが、「CPUの創りかた」という本に載っていた4bit CPUのTD4です。実際に手作りするとなると配線量がものすごくて、自分のようにたまにマイコンキットを作る程度のスキルでは大量にいもハンダが発生しいつまでたっても収束しないことが予想されたので、本を読むだけにとどめていました。

 

それがTinkercad Circuitsで作成すればいもハンダはありえないのでできるのではないかと思いつき回路を作成し始めましたが、途中で問題発生。

 

1つは次第にTinkercad Circuitsの動作が遅くなり、回路を開くだけでもエラーメッセージみたいなのが出るなど、あまり大きな回路を入れるのは無理な気配があること。現状ROMまで入れたのですが完成させるにはこれの10倍位の回路を入れなくてはなりません。まともに使い続けられるのか不安があります。

 

もう1つは同じ部分回路を何個も並べる箇所がありますが、Tinkercad Circuitsにはコピペの機能がないっぽいことです。「CPUの創りかた」では1個のICですませているがTinkercad Circuitsの電子部品一覧にそれが含まれていないのでもっと低機能のICを並べて作成しなくてはいけない箇所も複数あり、作業量が益々膨らんでしまいます。

 

ということでTinkercad CircuitsでTD4を作成するのは一旦ペンディングとし、最近出版された「作ろう! CPU」という本にしたがってSystem VerilogでTD4を作ってみる方針に切り替えました。これなら8bitへの拡張もできそうです。

 

途中でペンディングにはなりましたが、Tinkercad Circuitsである程度まで回路を組んでは出力ポートの電圧を測定して動作確認したことでCPUに対してかなりリアリティのある感覚が得られ十分有意義でした。System Verilogでの作業も楽しみです。

 

先日「低レイヤを知りたい人のためのCコンパイラ作成入門」読書会6回目を開催しました。5回目に続きZoomでの開催です。

ステップ14:関数の呼び出しに対応する、までやりました。自分はステップ13:ブロック、までは「低レイヤを知りたい人のためのCコンパイラ作成入門」の解説を参考にCコンパイラを自作しましたが非常に良い勉強になっています。

for文は初めてC言語を勉強した時に全く違和感のない自然な構文だと思った記憶がありますが、コンパイラを作るという目線で見ると結構癖の強い構文だと思いました。"for(式1;式2;式3)文"をコンパイルすると式1と式3は式であるがスタックトップに式の評価値を残さないし、式2が空だった時に式2の評価値を1にする必要がありますので。