1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

p5.js での描画を M5Stack Core2 にシリアル通信で送って表示【M5Stack】

1
Last updated at Posted at 2025-12-23

(この記事は M5Stack の Advent Calendar 2025 の記事です)

はじめに

この記事で書いている内容は、「ブラウザのキャンバスに描画した画像を、M5Stack のデバイスに送って画面上に表示させる」というものです。

p5.js でブラウザのキャンバスに画像を描画し、それを M5Stack Core2 にシリアル通信(USB による有線接続)で送る仕組みで実装しています(以下の動画のようなことを試しました)。

この後、さっそく実装した内容などを書いていきます。

試した内容

コードの実装

実装したコードと簡単な補足を書いていきます。

M5Stack側(画像受信側)

以下は、M5Stack Core2側の実装です。

#include <M5Unified.h>
#include <M5GFX.h>

#define MAX_JPEG_SIZE 40000
uint8_t jpegBuffer[MAX_JPEG_SIZE];

// 受信待ち画面表示
void drawWaitingScreen()
{
  M5.Display.fillScreen(TFT_BLACK);
  M5.Display.setTextColor(TFT_WHITE, TFT_BLACK);
  M5.Display.setTextSize(2);
  M5.Display.setCursor(10, 10);
  M5.Display.print("Waiting for Image...");
}

void setup()
{
  auto cfg = M5.config();
  M5.begin(cfg);

  M5.Display.begin();
  M5.Display.setRotation(1);

  // バッファ拡張
  Serial.setRxBufferSize(50000);
  Serial.begin(1500000);

  // 待機画面
  drawWaitingScreen();
}

void loop()
{
  M5.update();

  // Aボタン押下:画面クリアして待機表示
  if (M5.BtnA.wasPressed())
  {
    drawWaitingScreen();
    while (Serial.available())
      Serial.read();
  }

  // データ受信部分
  if (Serial.available() >= 2)
  {
    if (Serial.read() == 0xFF && Serial.read() == 0xAA)
    {
      // サイズ待ちの処理
      while (Serial.available() < 4)
        ;

      uint32_t len = 0;
      Serial.readBytes((char *)&len, 4);

      if (len > MAX_JPEG_SIZE)
        return;

      // 表示するデータ本体の受信
      uint32_t received = 0;
      while (received < len)
      {
        if (Serial.available() > 0)
        {
          int toRead = min((uint32_t)Serial.available(), len - received);
          Serial.readBytes(jpegBuffer + received, toRead);
          received += toRead;
        }
      }
      M5.Display.drawJpg(jpegBuffer, len);
    }
  }
}

以下のような前提/挙動の処理にしています。

  • 受信待ち状態関連
    • 最初は受信待ち状態
    • 画像を受信したら表示処理を行う
    • Aボタン押下で画面表示をクリアして、受信待ち状態に
  • 画像受信関連
    • 画像はシリアル通信で送られてくる JPEG画像
      • JPEG画像がヘッダ情報付きで送られてくる
    • 画像は 1枚が単独でくる前提(連続的な送信は行われない)

それと iniファイルは、以下としています。

[env:m5stack-core2]
platform = espressif32
board = m5stack-core2
framework = arduino

lib_deps =
   M5GFX
   M5Unified

p5.js側(画像送信側)

以下は、p5.js Web Editor用の p5.js の実装で、送信側の処理です。

let port;
let writer;

// M5Stack Core2 の解像度に合わせた設定
const SEND_W = 320;
const SEND_H = 240;
const JPEG_QUALITY = 0.3;

function setup() {
  createCanvas(SEND_W, SEND_H);
  pixelDensity(1);

  background(0);
  fill(255);
  textAlign(CENTER, CENTER);
  textSize(16);
  text("キャンバスをクリックして接続", width / 2, height / 2);

  noLoop();
}

function keyPressed() {
  // スペースキーか9: ノイズでカラフルな描画
  if (key === " " || key === "9") {
    generateNoiseArt();
    sendIfConnected();
  }
  // bキーか0: 水色で塗りつぶし
  else if (key === "b" || key === "0") {
    background(135, 206, 250);

    fill(255);
    noStroke();
    textAlign(LEFT, TOP);
    textSize(14);
    text("水色の描画を送信", 10, 10);

    sendIfConnected();
  }
}

function sendIfConnected() {
  if (writer) {
    sendJpegSerial();
  } else {
    console.log("Not connected yet.");
  }
}

function generateNoiseArt() {
  background(30);
  noStroke();

  for (let i = 0; i < 50; i++) {
    fill(random(255), random(255), random(255), 200);
    let size = random(20, 80);
    rect(random(width), random(height), size, size);

    fill(random(255), random(255), random(255), 150);
    ellipse(random(width), random(height), size, size);
  }

  fill(255);
  textAlign(LEFT, TOP);
  textSize(14);
  text("カラフルな描画の送信", 10, 10);
}

async function mousePressed() {
  if (!port) {
    try {
      port = await navigator.serial.requestPort();
      await port.open({ baudRate: 1500000 });
      writer = port.writable.getWriter();

      console.log("Connected!");

      background(50);
      fill(255);
      textAlign(CENTER, CENTER);
      textSize(16);
      text("Space: Noise / 'b': Blue", width / 2, height / 2);
    } catch (e) {
      console.error("Connection failed", e);
    }
  }
}

async function sendJpegSerial() {
  let dataUrl = document
    .getElementById("defaultCanvas0")
    .toDataURL("image/jpeg", JPEG_QUALITY);

  const base64Str = dataUrl.split(",")[1];
  const binaryStr = atob(base64Str);
  const len = binaryStr.length;

  const totalSize = 2 + 4 + len;
  let buffer = new Uint8Array(totalSize);
  let idx = 0;

  // ヘッダ
  buffer[idx++] = 0xff;
  buffer[idx++] = 0xaa;
  // サイズ
  buffer[idx++] = len & 0xff;
  buffer[idx++] = (len >> 8) & 0xff;
  buffer[idx++] = (len >> 16) & 0xff;
  buffer[idx++] = (len >> 24) & 0xff;

  // データ
  for (let i = 0; i < len; i++) {
    buffer[idx++] = binaryStr.charCodeAt(i);
  }

  try {
    await writer.write(buffer);
    console.log(`Sent: ${len} bytes`);
  } catch (e) {
    console.error("Write error:", e);
  }
}

送信側は以下のような前提/挙動の処理にしています。

  • 送信する画像の描画関連
    • キャンバスのサイズは M5Stack Core2 の画面サイズに合わせたもの
    • キー押下でキャンバス描画と画像送信を行う
      • 動作1: 画面上にカラフルな図形描画をしたもの
      • 動作2: 画面を水色で塗りつぶしたもの
  • 画像データ関連
    • JPEG は、サイズ削減のための圧縮用パラメータを適当に設定
    • ヘッダ情報を付与した JPEG画像データを送信

動作確認

上記の実装内容でシリアル通信による画像送信を試した結果、ブラウザ側に描画された内容が M5Stack側の画面上で表示されることが確認できました。

その他

【追記】 動画対応

その後、送信側と受信側の実装に手を加えて、画像を連続送信・受信する仕組みも試しました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?