この記事は 自作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さんには楽しい遊び場を提供していただいたことに感謝しております。
<(_ _)>