2025年9月6日土曜日

シガソケUSB PD 65Wチャージャーを自作してみた。

 前回昇圧DCDCコンバータとUSB PD 65Wの降圧DCDCコンバータを組み合わせて12Vを無駄に22Vに昇圧してからUSB PDの出力を得ることに成功したので、流石にそのままだと使いにくいのでケースの3Dデータを作成してみた。
65Wまで対応なので、自動車の中でパソコンを充電したりタブレットも急速充電できたり。

こんな感じで2ピース構成でシンプルにしてみた。基板の3Dモデルを作ってそれに合わせてギリギリに作ってみた感じ。

そんでもって3Dプリンタを持っている方にプリントてもらった。USB PDの基板はパチンと止めるような爪を付けていたんだけど少し削る必要があった。昇圧DCコンバータのフィンの部分は寸法ギリギリで作っていたので両側0.8mmほど削って対応した。

上下のカバーの固定はM2ネジをそのままねじ込む形で固定。上部にはグラボ用のヒートシンクを貼り付けられるように穴を設けてU1Eの冷却を行う。そもそもPLAだと60度ぐらいで柔らかくなってしまうのでちょっと心配である。

とりあえずこのケースで熱のテストをしてみて上の蓋をアルミ版で作ったりとか色々試せればなと思う。ちなみに入力には7.5Aのヒューズを入れておいた。
早速ノートPCを充電してみたところ、電力が測定できるケーブルで測定すると30Wから70Wぐらいの電力が供給できていた。200円ぐらいのケーブルなので精度は怪しいんだけども負荷をかけると60~70Wぐらいまで行っていたのでちゃんと20Vで供給できてそう。
ヒートシンクはどちらも触れる程度に暖かかった。

今回はDCDCコンバータが余っていたので作ってみたけども、部品を揃えて作るよりも市販のシガソケ充電器を買ったほうが安上がりかもしれない。しかし最近マルチポートの充電器が多くて1つのUSB Type C端子からどれだけ電力を供給できるかはよく見ておかないとだめかも。トータル100W超えるような製品でも単体のポートは最大45Wまでとかあるみたいだし。

2025年8月31日日曜日

12VからUSB PDの65Wを出力してみた。

 少し前にDC電源入力のUSB PD Charger基板を購入してみたんだけど、降圧しかできない仕様だったので65Wを出すには入力電圧が20Vより大きくないといけなかったのでフル性能がなかなか使えなかった。

たまたま昔購入した昇圧DC-DCコンバータを発見したので車のシガーソケットでノートPCを充電できないかということでやってみた。

注文履歴を見ると2013年10月ってなっていてなにに使ったかよくわからないけど5ドルで2つ購入していたうちの一つを発見。端子のネジも一個欠損していた。
OSKJの150W対応のDC-DCコンバータっぽい。コントローラICにはUC3843ANが乗っていた。これで12Vから22Vぐらいまで昇圧できれば、CKCS U1E互換基板で65Wまで出力できるようになるかな。Amazonで同じものを見つけたんだけど、12Vバッテリで19V 3.42A(65W)のノートPCを駆動させると温度が45℃ぐらいになるということだったので用途的にはちょうど良さそう。

端子のネジが一つないし、そもそもこの端子で大電流流すのはアレなので端子を取り外してケーブルを直結してみた。DC5521バージョンを購入したのに電源端子を外してしまってこっちも直結。

XPM52Cのほうの発熱が気になるので裏側にヒートシンクを装着できるようにはんだ付けの際はフラットにしておいた。ヒートシンク無しで65W出力すると100℃超えるらしいし。

昇圧電圧を22Vになるようにセットして、とりあえず12V 7.5Aヒューズを入れて12Vバッテリに繋いでみたけどちゃんとノートPCをつなぐとType Cからは20V出てるっぽい(モジュールの出力コンデンサをテスターで測定)

とりあえずあり物で12Vから65W(20V 3.25A)が出せるようになったので良いけども、アリエクとかで65W出力可能なシガソケ充電器が12-24V対応とかで売っているのでそっちを購入したほうが安上がりかも。Essager 120W Car Charger(FSJ-009)とかだと1500円ぐらいで、昇圧対応でPDは100Wまで行けるみたいだし。何よりアルミ製で放熱も安心だし…

このままでは実際に使いにくいのでケースに入れないと。

2025年8月25日月曜日

PCファンをUSB扇風機として使えるように整流板を作ってみた。

 以前余ったファンをUSBで使えるように、5Vから12Vに昇圧してファンガードを両面につけてUSB扇風機として使うようにしてみたのだけれども、やっぱり風量が足りない。ポータブルファンとかと比べると回転数に対しての風量が少ない気がする。

PC用のファンなので実は風量よりも風向きがあまり良くないのではないかということで調べてみると、AINEXから風の直進性を高めるアイテムが出ていたので試してみたかったのだけれども販売終了していたので同じようなものを作ってみた。

整流板としてはとりあえず形状は何でも良さそうではあるけどもオリジナルを忠実に再現してヘキサゴン形状にした。大きさもスペックから同じぐらいの大きさにしてみた。ファンと嵌合する部分の長さがわからなかったけど予想でこんな感じに。Onshapeフリープランなのでfan boosterで検索すれば見つけられるかも。

というわけで例によって3Dプリンタ持っている人にお願いしてプリントしてもらった。ファンと合わせるくぼみのせいで余計なサポートが付いてしまったようで申し訳ないけど。

これ実は反対面をサポートにしたほうが良かったのでは?と思ったけどもまぁとりあえずこれでも問題なさそう。

ファンに装着するとこんな感じ。オリジナルはきつかったみたいだけどそれを考慮してゆとりをもたせたおかげでいい感じに装着できた。
長さ方向はこんな感じ。オリジナルと同じぐらいの長さになってると思う。

効果の程は結構絶大で、ネジ止めせずにつけたり外したりして試してもかなり風の直進性が高まってることが感じられる。自分に風を向けていると今まではファンの回転数調整はいらないと思っていたが少し下げられるように回転数を調整したいぐらいに涼しい。このファンだと2mぐらいでも風が感じられるぐらいには直進性が増していた。

次は風量を調整できるようにしないと…

もう少し小さいファンで持ち手をつけてハンディーファンでも作れそう。

2025年8月3日日曜日

EEZ StudioとArduino IDEでGUIを作ってみた

 この前ESP32-S3を張り替えたJC4827W543Cだけど、ROMも8MBにアップグレードしたことだしEEZ StudioでGUIアプリを作ってみることに。逆にArduino IDEだけでGUI作るの大変そう。

まずはEEZ Studioのインストール。とりあえずWindows用のexeファイルをダウンロードしたらインストーラーに従ってインストールできた。
起動するとHomeが表示されるのでLVGLを選んで、Nameにプロジェクト名をいれて、LVGL VerisionはArduino IDEにインストールするLVGLバージョンに合わせて選択する。今回は最新版を使用した。プロジェクトの保存先は覚えておかないとuiフォルダの中身をArduinoのプロジェクトにコピーする時困るので気をつけないと。

プロジェクトを作成したらまずはSettingsでDisplay widthとheightをNV3041Aに合わせるために480と272に変更する。あとはBuildのところでLVGL includeをlvgl.hに変更しておく。Mainタブに戻っても最初に作られた画面の解像度は自動で修正されないのでこちらも同じ画面に修正した。とりあえず適当にボタンを追加してみた。

スパナのマークを押すとソースファイルがプロジェクトフォルダの中のuiフォルダに生成される。これをArduinoのプロジェクトフォルダにコピーしてやる感じ。

ということで次はArduino側のプロジェクトを作成する。lvglライブラリはEEZ Studioで指定したバージョンをArduino IDEからインストールしておく。他にArduino_GFXライブラリとタッチパネルのGT911用にTouchLibライブラリが必要だった。

inoファイルの中身は

#include <lvgl.h>
#include <Arduino.h>
#include <Arduino_GFX_Library.h>
#include "touch.h"
#include "ui.h"

#define SCREEN_WIDTH 480
#define SCREEN_HEIGHT 272

#define LCD_BL 1

Arduino_DataBus *bus = new Arduino_ESP32QSPI(45, 47, 21, 48, 40, 39);
Arduino_GFX *gfx = new Arduino_NV3041A(bus, GFX_NOT_DEFINED, 0, true);
static lv_color_t buf1[SCREEN_WIDTH * 40];

void my_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1);

    gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)px_map, w, h);

    lv_display_flush_ready(disp);
}

void my_touch_read(lv_indev_t *indev, lv_indev_data_t *data)
{
    if (touch_has_signal() && touch_touched())
    {
        data->state = LV_INDEV_STATE_PRESSED;
        data->point.x = touch_last_x;
        data->point.y = touch_last_y;
    }
    else
    {
        data->state = LV_INDEV_STATE_RELEASED;
    }
}

void backlight_init()
{
    pinMode(LCD_BL, OUTPUT);
    digitalWrite(LCD_BL, HIGH);
}

void lvgl_init_all()
{
    if (!gfx->begin()) {
        Serial.println("gfx begin failed!");
    }
    gfx->fillScreen(0x0000);
    backlight_init();

    touch_init(gfx->width(), gfx->height(), gfx->getRotation());

    lv_init();

    lv_display_t *disp = lv_display_create(SCREEN_WIDTH, SCREEN_HEIGHT);
    lv_display_set_buffers(disp, buf1, NULL, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL);
    lv_display_set_flush_cb(disp, my_flush_cb);
    lv_display_set_default(disp);
    lv_indev_t *indev = lv_indev_create();
    lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
    lv_indev_set_read_cb(indev, my_touch_read);
    lv_indev_set_display(indev, disp);
}

void setup()
{
    Serial.begin(115200);
    Serial.println("START");

    lvgl_init_all();
    ui_init();
    Serial.println("EEZ started");
}

void loop()
{
    lv_timer_handler();
    ui_tick();
    lv_tick_inc(5);
    delay(5);
}

こんな感じにしてみた。バックライトはとりあえず輝度固定。あとはタッチパネル用にtouch.hをDemoから頂戴してきてプロジェクトフォルダに放り込む。そんでもってEEZ Studioで生成したuiフォルダのhファイルとcファイルをすべて同じところに放り込む。

あとはビルドして書き込めば完成。最初なかなか映らなくて困っていたんだけど、loopのなかにlv_tick_inc(5);を入れ忘れいていただけだったorz。

とりあえずテスト画面が表示されたので次は各コンポーネントを試してみた。

まずはtextから。これは文字通りtextを表示させるものなんだけど、コンポーネントの設定で名前(GeneralのNameってところ)を入れておくとArduinoで関数を読み込むと表示を変えることもできる。

lv_label_set_text(objects.test, "Hello");

こんな感じでArduinoのどこかで上のように実行すればNameにtextって入れてあるtextの内容をHelloに書き換えれる。センサーの値を取ってきてそれをtextに表示するみたいなことも可能。

お次はButton。ButtonはEventsのところでイベントハンドラーを追加する。Clickedを選択して、Actionは新しいActionを作成してAction自体に名前をつける。あとはMainとかがあるタブの左側に<>User Actionsっていうタブがあるのでそこで先程作ったAction(今回はmovetotestを選択すると左の方にTipsがあって、それのコードをArduinoのコードに追加すると実行可能。

#include "actions.h"

void action_movetotest(lv_event_t *e) {
    //lv_scr_load(objects.test); 
    //Serial.println("Button");
    lv_label_set_text(objects.test, "Hello");
}

Actions.hをIncludeしてこんな感じでActionに対しての関数を追加する感じ。コメントアウトされてるけどlv_scr_loadを使えばtestっていうページを追加して表示したり、ボタンを押したらtextの内容を書き換えたり。

Buildをした後にArduinoのプロジェクトの中にコピーするのが面倒になったのでSettingsのDestination folderを直接指定してみた。これで忘れることがないはず…

EEZ Studioを使うと簡単にUIが作れるので便利かも。

2025年7月20日日曜日

ESP32でGPSラップタイマーを自作してみる

 知り合いがGPSラップタイマーが欲しいけど高いよーとか言っているのでこの前購入したGPSモジュールで試しに作ってみることに。ATGM336Hのスペック上10Hzまで行けるらしいし。

とりあえずESP32でGPSのMNEAセンテンスを読んで、指定している座標Aと座標Bの間を通ったらカウントする感じ。これならタイム測定ポイントを正確に指定できるかなと。最終的にはWeb設定画面を表示して設定変更したり、内部メモリで選択できるようにしたいな。

まずはちゃんと座標間を通ったときにカウントできるかのテスト。最初に通ったらカウントを開始して、2回目通ったらLAPタイムをSerial.printするだけのテスト。M5Atom Liteで動作確認してみた。

#define GPS_RX 32
#define GPS_TX 26

char nmeaLine[128];
int idx = 0;
double LAT0 = 38.14;
double LON0 = 140.77;

HardwareSerial GPS(2);

struct Vec2 {
  double x;
  double y;
  uint64_t time_ms;  // 経過ms
};

struct Line {
  double x1, y1, x2, y2;
};

Vec2 prevPos;
bool firstFix = true;
bool firstLineCross = true;
uint64_t lastLapTime = 0;

Vec2 latlonToXY(double lat, double lon, uint64_t t) {
  Vec2 p;
  p.x = (lon - LON0) * cos(LAT0 * DEG_TO_RAD) * 111320.0;
  p.y = (lat - LAT0) * 110540.0;
  p.time_ms = t;
  return p;
}

double cross(double ax, double ay, double bx, double by) {
  return ax * by - ay * bx;
}

double signedDistance(const Vec2& p, const Line& L) {
  double lx = L.x2 - L.x1;
  double ly = L.y2 - L.y1;
  double px = p.x - L.x1;
  double py = p.y - L.y1;
  return cross(lx, ly, px, py);
}

bool checkLineCross(const Vec2& prev, const Vec2& now, const Line& line, uint64_t& crossTime) {
  double d1 = signedDistance(prev, line);
  double d2 = signedDistance(now, line);
  if (d1 * d2 >= 0) return false;
  // 交点の補間
  double ratio = fabs(d1) / (fabs(d1) + fabs(d2));
  double cx = prev.x + (now.x - prev.x) * ratio;
  double cy = prev.y + (now.y - prev.y) * ratio;
  // 線分範囲内判定
  double minX = fmin(line.x1, line.x2);
  double maxX = fmax(line.x1, line.x2);
  double minY = fmin(line.y1, line.y2);
  double maxY = fmax(line.y1, line.y2);
  if (cx < minX || cx > maxX || cy < minY || cy > maxY)
    return false;  // 線分の延長上なら無視
  crossTime = prev.time_ms + (uint64_t)((now.time_ms - prev.time_ms) * ratio);
  return true;
}
Line startLine;
void setupStartLine() {
  Vec2 A = latlonToXY(38.142650747977115, 140.77813371163867, 0);
  Vec2 B = latlonToXY(38.14193773632026, 140.77804079991844, 0);
  startLine = { A.x, A.y, B.x, B.y };
}

double nmeaToDeg(const char* val, const char* dir) {
  double raw = atof(val);
  int deg = (int)(raw / 100);
  double min = raw - deg * 100;
  double result = deg + min / 60.0;

  if (dir[0] == 'S' || dir[0] == 'W')
    result *= -1;
  return result;
}

void parseRMC(char* line) {
  char* token;
  int field = 0;
  char utc[16] = { 0 };
  char status[2] = { 0 };
  char lat[16] = { 0 }, lon[16] = { 0 };
  char ns[2] = { 0 }, ew[2] = { 0 };
  char date[8] = { 0 };
  token = strtok(line, ",");
  while (token) {
    field++;
    if (field == 2) strcpy(utc, token);
    if (field == 3) strcpy(status, token);
    if (field == 4) strcpy(lat, token);
    if (field == 5) strcpy(ns, token);
    if (field == 6) strcpy(lon, token);
    if (field == 7) strcpy(ew, token);
    if (field == 10) strcpy(date, token);
    token = strtok(NULL, ",");
  }
  if (status[0] != 'A') return;
  double latitude = nmeaToDeg(lat, ns);
  double longitude = nmeaToDeg(lon, ew);
  uint64_t gpsTime = esp_timer_get_time() / 1000;  // μs → ms

  if (firstFix) {
    LAT0 = latitude;
    LON0 = longitude;
    setupStartLine();
    prevPos = latlonToXY(latitude, longitude, gpsTime);
    firstFix = false;
    Serial.println("Fix complete, waiting for START line...");
    return;
  }
  Vec2 pos = latlonToXY(latitude, longitude, gpsTime);
  // ラップ判定
  uint64_t crossTime;
  if (checkLineCross(prevPos, pos, startLine, crossTime)) {
    if (firstLineCross) {
      lastLapTime = crossTime;
      firstLineCross = false;
      Serial.println("START!");
    } else if (crossTime - lastLapTime > 8000) {
      uint64_t lapMs = crossTime - lastLapTime;
      uint32_t minutes = lapMs / 60000;
      uint32_t seconds = (lapMs % 60000) / 1000;
      uint32_t millisec = lapMs % 1000;
      lastLapTime = crossTime;
      Serial.printf("LAP! %02u:%02u.%03u\n", minutes, seconds, millisec);
    }
  }
  prevPos = pos;
}

void readGPS() {
  while (GPS.available()) {
    char c = GPS.read();
    if (c == '\n') {
      nmeaLine[idx] = 0;
      if (strstr(nmeaLine, "GNRMC") || strstr(nmeaLine, "GPRMC")) {
        parseRMC(nmeaLine);
      }
      idx = 0;
    } else if (idx < 127)
      nmeaLine[idx++] = c;
  }
}
void setup() {
  Serial.begin(115200);
  GPS.begin(115200, SERIAL_8N1, GPS_RX, GPS_TX);
  setupStartLine();  // 初期値
  Serial.println("GPS LapTimer");
}
void loop() {
  readGPS();
}

実際にサーキットを走ったりして確認するのは大変なので、まずは先日作ったシリアルロガーで、近所を歩いて一周してきたログをPythonでシリアルポートに流して動作確認をしてみたが、とりあえずは動いてそう。10Hzで取ったログを10倍速とかで流してもちゃんと反応してくれる。(Baudrate的に本当に10倍速になってるかはわからないけど…)
ちなみにMNEAセンテンスは絞ってる

あとはディスプレイに表示できるようにしないと。屋外でも視認性が良さそうなディスプレイを探さないとな。あとはセクタータイム測定したり、GPSのデータをそのままロギングできたら便利そう。

2025年7月19日土曜日

ESP32でシリアルデータをロギングしてみた。

 先日ATGM336Hの動作確認ができたので、実際にGPSから出ているデータをロギングしてみることに。

今回はESP32とWebSocketを使って、ブラウザ上にデータを表示してそれをログデータとして保存してみようと思う。外でノートパソコンを持ってウロウロするのもアレだし…

#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>

const char* ssid = "ESP32_LOG";
const char* password = "12345678";

WebServer server(80);
WebSocketsServer webSocket = WebSocketsServer(8080);

#define RXD2 32
#define TXD2 26 

const char MAIN_page[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ESP32 Serial2 Logger</title>
<style>
body { font-family: monospace; margin:20px; }
textarea {
  width:100%;
  height:400px;
}
button {
  font-size:18px;
  padding:8px 20px;
}
</style>
</head>
<body>
<h2>ESP32 UART2 Monitor</h2>
<textarea id="log"></textarea><br><br>
<button onclick="saveLog()">保存してダウンロード</button>
<script>
let ws;
function connectWS(){
  ws = new WebSocket("ws://" + location.hostname + ":8080/");
  ws.onmessage = function(event) {
    const ta = document.getElementById("log");
    ta.value += event.data;
    ta.scrollTop = ta.scrollHeight;
  };
  ws.onclose = function(){
    // 自動再接続
    setTimeout(connectWS, 2000);
  };
}
connectWS();
function saveLog() {
  const text = document.getElementById("log").value;
  const blob = new Blob([text], {type: "text/plain"});
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = "serial2_log.txt";
  a.click();
  URL.revokeObjectURL(url);
}
</script>
</body>
</html>
)rawliteral";

void setup()
{
  Serial.begin(115200);
  Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2);
  WiFi.softAP(ssid, password);
  Serial.println("AP Started");
  Serial.println(WiFi.softAPIP());
  server.on("/", []() {
    server.send_P(200, "text/html", MAIN_page);
  });
  server.begin();
  webSocket.begin();
}

void loop()
{
  server.handleClient();
  webSocket.loop();

  static String line = "";

  while (Serial2.available())
  {
    char c = Serial2.read();
    //Serial.write(c);
    line += c;
    if (c == '\n')
    {
      webSocket.broadcastTXT(line);
      line = "";
    }
  }
}

こんな感じでとりあえずブラウザのtextareaに淡々と表示し続けて最後にsaveボタンを押す簡単なやつにしてみた。

10Hz設定だと流石に流れるのが早すぎるのか表示が追いついていない気がする。WiFi切ってもしばらく流れてるし。とりあえずiPhoneで保存したやつをPCに送って拡張子を.nmeaに変更。GSXSeeで開くとちゃんと軌跡が表示できた。

2025年7月13日日曜日

ATGM336HなGPSモジュールを買ってみた

 アリエクで安いGPSモジュールがあったので購入してみた。ATGM336HっていうシリーズのGPS。580円だった。AT6558が使われてるモジュールらしい。

とりあえずアンテナは小さいやつがついているのにしてみたけども、感度はどうなんだろう?

裏面にはホットスタート用のバッテリとEEPROM(24C32A)がついている。以前UBX-G7020のGPSモジュールを購入したときはEEPROMがついてなくて設定が保存できなかったので設定が保存できるのは便利そう。あと3.3VレギュレータのRT9193-33GBが付いているのでこれならVCCに5V入れても大丈夫そう。ただしTXとRXは3.3Vレベルなので注意。
アンテナのコネクタとバッテリが近いのでアンテナのコネクタがバッテリーに触らないように注意かも。テープでも貼っておくか…

とりあえず動作確認をしてみた。適当なシリアル変換アダプタに接続して、GnssToolKit3で動作確認ができる。室内だと窓際で1個補足できたぐらいでFIXすることはできなかった。

$GPTXT,01,01,02,MA=CASIC*27
$GPTXT,01,01,02,IC=AT6558F-5N-32-1C580900*06
$GPTXT,01,01,02,SW=URANUS5,V5.3.0.0*1D
$GPTXT,01,01,02,TB=2020-03-26,13:25:12*4B
$GPTXT,01,01,02,MO=GB*77

起動時にはこんな感じでバージョン情報とかが出る模様。

デフォルトのBaudrateは9600なんだけど、10Hzとかで動かしたら足りなくなるかもしれないということでBaudrateを変更してみた。

$PCAS01,5*19

をArduino IDEとかのシリアルコンソールからCR+CF付きで送信すると即座にBaudrateが115200に変更できた。もとに戻すには

$PCAS01,1*1D

を送ると9600に戻る。電源を切っても保持されるっぽいので設定時に一回送ればOK。

次にアップデートレートの変更だけども、10Hzに変更するには

$PCAS02,100*1E

のようにコマンドを送れば10Hz(100ms周期)でデータが送られてくる。1Hzに戻すには

$PCAS02,1000*2E

とすればOK。他にも色々変更できるっぽいけど、設定は先程のGnssToolKit3を使ったほうが便利かもしれない。GUIで色々いじれる。View→Configurationで設定画面が開ける。

バイク用にバンク角もログれるGPSロガーがほしいという知り合いがいたのでとりあえずNMEAのGPSなのであとは独自のセンテンスを追加してやれば一緒にログれそうな気がする。

MNEAセンテンスをロギング用にGGA + RMC + ZDAだけに絞るには

$PCAS03,1,0,0,0,1,0,1,0,0,0,,,0,0*03

このコマンドでおk。もとに戻す場合は

$PCAS03,1,1,1,1,1,1,1,1,0,1,,,1,0*02

こんな感じでもとに戻った気がする。そういえばSaveコマンドとか送らなくても全部保存されているようで、再起動してもそのままだった。

あとはツール側でそれを表示できるかどうか?とりあえずはGPXSeeっていうフリーソフトで試してみようかな。
まずは適当にログれるようなやつをM5 Atomあたりで作ってみようかな。