ゲーム機についての表側についてはこちら
https://qiita.com/pluswing/items/021b8ddc0e88ad6f36ac
どーもこんにちは。
今回は、前回の続きで、そんなゲーム機のファームウェア作るにはどーしたらいいの?
ということろを解説していこうと思います。
思ったより簡単(ESP32の機能がスゴイ)なので、これを気に電子工作の世界に足を踏み入れる人が増えると良いなーと思ったりしています。
かくいう私もかれこれ電子工作は暇があればやっていますが、いまだに電子回路についてはよくわからないことが多く、ほぼ行き当たりばったりでやっています。
意外となんとかなるので(電子部品をぶち壊すことはよくある。たまに白煙が出て超焦るw)まぁとりあえずやってみるのが一番かなと思います。
さて、今回作成したゲーム機。「ESP Console」ですが、
Arduino で開発しているので、
組み込みだから、開発環境用意するの面倒なんでしょ?
WIndowsじゃ無いと開発環境ないんでしょ?
といったことは一切ありません。
Arduino IDEをインストールしたら、環境構築はほぼ完了です。
私はMacで開発しています。
技術的トピックとしては以下になるかなと思います。(恣意的抜粋ですが。。)
- WiFi接続ができる
- Web API 経由でスクリプトをアップロードできる
- 組み込みなのにファイルシステムがある
- Luaを組み込んだ
WiFi接続
ESP32にはWiFiモジュールが内臓されています。
ついでに言うと今回は使っていませんが、Bluetoothモジュールも入っています。
WiFiに接続するには、C言語で何百行と書かないといけないんでしょ?
いいえ、4行です。
WiFi.begin("<SSID>", "<PASSWORD>");
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
}
はい。これだけ。ぶっちゃけ重要なコードは最初の1行で、あとの3行は接続されるのを待ってるだけです。
ね。簡単でしょ?
multicast DNSにも対応していて、(IPの代わりに、hoge.local とかでその端末にアクセスできるようにする機能)
それを有効にするのも超簡単です。
if (!MDNS.begin("<HOST NAME>")) {
Serial.println("Error setup MDNS.");
}
MDNS.addService("http", "tcp", 80);
<HOST NAME>
に espconsole
を指定してあげると、
http://espconsole.local
でこの端末にアクセスができるようになります。
Web API 経由でスクリプトをアップロードできる
Webサーバになるのは流石に面倒でしょう!?
AsyncWebServer webServer(80);
webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "running");
});
webServer.begin();
これはC++なのか・・・と疑いたくなるくらい簡単です。
ちなみに、これは外部ライブラリを使用しているのですが、
セットアップは規定のフォルダgit clone
するだけです。
https://github.com/me-no-dev/ESPAsyncWebServer
組み込みなのにファイルシステムがある
はい。ファイルシステム。
組み込みの世界では、ファイルシステムなんて贅沢なものは無いのが当然なのですが、
(普通はFlashにアドレス指定で読み書きする)
なんと、ESP32には SPIFFS
というFlashの領域をファイルストレージとして利用できる機能があります。
SPIFFS.begin();
File f = SPIFFS.open("/.setting.json", "r");
char content[SETTING_BUFFER_SIZE];
f.readBytes(content, SETTING_BUFFER_SIZE);
f.close();
ちょっとCらしさが垣間見えましたが、普通にファイル名でアクセスができちゃいます。
これは便利すぎます。
ちなみにですが、SDカードのファイルも同じような感じで読み書きができてしまいます。
Luaを組み込んだ
はい。本命です。
Luaを組み込むってどういうこと?
という人も多いような気がするので、そこから説明していきます。
ちょっとコードを見た方が理解ができると思うので、こちらをご覧ください。
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dostring(L, "<Lua script code>");
lua_close(L);
1行目で、VMを作ります。
2行目で、Luaの基本ライブラリを読み込みます。
3行目で、文字列をLuaスクリプトとして評価・実行します。
4行目で、後始末します。
基本はこれだけです。
これを実現するには、
https://www.lua.org/download.html
ここから、一式ダウンロードして、
.inoファイルのあるフォルダに丸っと解凍して
extern "C"
{
# include "lua.h"
# include "lualib.h"
# include "lauxlib.h"
}
これだけです。
・・・とは行かないのが組み込みの世界。
ESP32の話になりますが、
C言語のsystem()
関数がないので、loslib.c
の該当箇所でエラーが出ます。
-static int os_execute (lua_State *L) {
- lua_pushinteger(L, system(luaL_optstring(L, 1, NULL)));
- return 1;
-}
・・・
static const luaL_Reg syslib[] = {
{"clock", os_clock},
{"date", os_date},
{"difftime", os_difftime},
- {"execute", os_execute},
{"exit", os_exit},
{"getenv", os_getenv},
{"remove", os_remove},
{"rename", os_rename},
{"setlocale", os_setlocale},
{"time", os_time},
{"tmpname", os_tmpname},
{NULL, NULL}
};
こんな感じで、該当処理をまるっと消してしまえば良いです。
他の環境では、別のエラーが出ることがありますが、その環境に合わせて同等の処理に書き直すか、必要ないなら消してしまえば良いです。
LuaからCを呼び出す
ゲーム機なので、描画命令などC側の機能をLuaから呼び出す必要があります。
普通は、Cの関数を呼び出すために、↑で消したsystem()
を呼び出すようにグルーコードを書いてあげないといけないんですが、
七面倒くさい。
ESP Consoleでは、tolua++
というモノを使って、このグルーコードを自動生成しています。
C++のクラスをLuaで使う手順をざっくり。
まず、ヘッダファイルを用意して、公開したい宣言を
// TOLUA_BEGIN
~//TOLUA_END
で囲みます。
今回の例だと、コンストラクタ、デストラクタは公開したくないので、それ以外を囲っています。
// TOLUA_BEGIN
class GameApi
{
// TOLUA_END
public:
GameApi();
virtual ~GameApi();
// TOLUA_BEGIN
int width();
int height();
};
// TOLUA_END
次に、pkgファイルというものをを用意します。
これは$hfile
に続いてヘッダファイル名を書けばOKです。
$hfile "game_api.h"
ここまで準備ができれば、あとは tolua++
コマンドを実行するだけです。
bin/tolua++ -o tolua_game_api.cpp game_api.pkg
sed -i -e s/tolua++.h/toluapp.h/ tolua_game_api.cpp
これで、tolua_game_api.cppというファイルが出来上がるので、
(sedしてるのは、Arduinoの制限でファイル名に+
が使用できないのでその対応のため。)
そしたら
lua_State *L = luaL_newstate();
luaL_openlibs(L);
tolua_game_api_open(L); // 追加!
luaL_dostring(L, "<Lua script code>");
lua_close(L);
とVMに追加でロードしてあげるだけです。
これで、クラスが使用可能になります。
今回の例だと、コンストラクタを非公開にしているので、このままではGameApiクラスをインスタンス化することができません。
ESP ConsoleではGetAPI()
という関数も公開していて、シングルトンなGameApiのインスタンスを取得できるようにしています。↓
https://github.com/pluswing/espconsole/blob/master/game_api.h#L119
CからLuaのfunctionを呼び出す
単純な実行なら、luaL_dostring()
を使えば良いのですが、
ESP Consoleは Lua側で定義されている update()
, draw()
functionを呼び出す必要があります。
この呼び出しは簡単で2行で実現できます。
lua_getglobal(L, "update");
lua_pcall(L, 0, 0, 0);
引数があるfunctionを呼び出す場合、若干コードが増えるのですが、引数なしの場合はこれだけで呼び出せてしまいます。
・・・ということで、ゲーム機のファームウェアをつくるに当たっての秘術的な話はこの辺で。
知ってしまえば、なんだそう言うことか。俺でもできそうだ!と思ったんじゃないでしょうか?
ぶっちゃけ、SSID選択画面つくったり、WiFiパスワード入力画面つくったりする方がよほど面倒なのですが。。
まだ夏休み真っ只中!電子工作に勤しんでみてはいかがでしょうか?