はじめに
- M5UnitVのカメラからQVGA(320 * 240)サイズの画像を撮影し、M5Stackの液晶画面に表示します。
- UnitVは本来AIカメラですが、今回は単に外付けのカメラとして使用します。
- M5UnitVとM5Stack(Gray)はGroveケーブルで接続し、JPEG形式で圧縮してシリアル転送します。
参考にしたページ
- HomeMadeGarbage さんのページがとても参考になります。構成が多少異なるので、適宜読み替えます。
- https://homemadegarbage.com/ai13
- グラフィックス系はLovyanGFXライブラリを使わせてもらっています。フレームレートが向上します。
- https://github.com/lovyan03/LovyanGFX
本体を接続
- 接続には、M5UnitVに付いてきたGroveケーブルを使います。
- UnitVにはバッテリが搭載されていませんが、M5Stackから電源供給できるのでこんな感じでシンプルな構成になります。
- 自作でここまで最適化された構成を作るのはかなり難しいので、こう言う使い方が出来るところがM5Stackの良いところですね。
M5UnitV側のコード
- カメラの撮影画像サイズはQVGA(320 * 240)、JPEG圧縮率はとりあえず50%。
- Groveポートに割り当てられているGPIO34, 35を使ってシリアル通信します。
- ポートレートは115200
- JPEG画像データを送る前に、
uart.write(data_packet)
でPacketヘッダ10byteを先に送ります。 - 先頭ビットは固定で
0xxFF, 0xD8, 0xEA, 0x01
、その後に画像サイズが続きます。 - JPEG圧縮後の画像データは
uart.write(img_buf)
で送ります。 - UnitVは液晶画面が無く、ちゃんと動いてるか見た目で分からないためRGB LEDでステータスを表示します。
-
class_ws2812 = ws2812(8,100)
でGPIO8
と、(多分)明度の刻み100
を指定します。 - ブートしたら、3回青色に点滅後、点灯させます。
- これでちゃんと動いてるかが分かるようになります。
- このプログラムは、SDカード直下に
boot.py
と言うファイル名で配置します。 - こうしておくと、UnitVの電源がONになったタイミングでこのファイルを読み込んで実行されます。
- SDカードの抜かずにMaixPyから直接配置できる機能があるので、こちらのページを参考にしてください。
boot.py
import sensor
import time
from machine import UART
from fpioa_manager import fm
from Maix import GPIO
from modules import ws2812
# RGB LED設定
class_ws2812 = ws2812(8,100)
BRIGHTNESS = 0x10
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_vflip(1)
sensor.set_hmirror(1)
sensor.run(1)
sensor.skip_frames()
# UART
fm.register(35, fm.fpioa.UART1_TX, force=True)
fm.register(34, fm.fpioa.UART1_RX, force=True)
# RGB LEDを光らせる
def RGB_LED(r,g,b):
a = class_ws2812.set_led(0,(r,g,b))
a = class_ws2812.display()
time.sleep(0.3)
a = class_ws2812.set_led(0,(0,0,0))
a = class_ws2812.display()
time.sleep(0.3)
a = class_ws2812.set_led(0,(r,g,b))
a = class_ws2812.display()
# 起動インジケータとしてblueで点滅、最後に点灯
RGB_LED(0,0,BRIGHTNESS)
RGB_LED(0,0,BRIGHTNESS)
RGB_LED(0,0,BRIGHTNESS)
uart = UART(UART.UART1, 115200,8,0,0, timeout=1000, read_buf_len=4096)
while(True):
img = sensor.snapshot()
# Jpeg圧縮率を上げると表示フレームレートは上がるが、綺麗さとトレードオフ
img_buf = img.compress(quality=50)
# Packetヘッダ
img_size1 = (img.size()& 0xFF0000)>>16
img_size2 = (img.size()& 0x00FF00)>>8
img_size3 = (img.size()& 0x0000FF)>>0
data_packet = bytearray([0xFF,0xD8,0xEA,0x01,img_size1,img_size2,img_size3,0x00,0x00,0x00])
uart.write(data_packet)
# imgデータ転送
uart.write(img_buf)
M5Stack側のコード
- M5Stack GrayのGroveポートに割り当てられているGPIOは21, 22なので、これを使ってシリアル通信します。
-
Serial2.begin(115200, SERIAL_8N1, 21, 22);
でポートレートは送信側と同じ115200に合わせます。 -
loop()
でデータ受信と画面表示を行います。 - Packetデータ10byteを読み込みます。
- 先頭バイト列がフォーマットに合ってた場合、データサイズを読み込みます。
-
Serial2.readBytes(jpeg_data.buf, jpeg_data.length);
でそのサイズ分のJPEGデータを読み込みます。 - 最後にdrawJpgでM5Stackの液晶画面に表示します。
- drawJpgについて
- 最初、
M5.Lcd.drawJpg
を使って描画したところ、30フレーム目くらいで更新がストップしてしまいました。 - 高速表示できる事で有名なLovyanGFXに差し替える事で、ストップせずに表示し続けられるようになりました。
- 素晴らしいライブラリを提供していただき、ありがとうございます。
#include <M5Stack.h>
#define LGFX_M5STACK
#include <LovyanGFX.hpp>
static LGFX lcd;
typedef struct {
uint32_t length; //Jpegデータサイズ
uint8_t *buf; //Jpegデータバッファ
} jpeg_data_t;
jpeg_data_t jpeg_data;
static const int RX_BUF_SIZE = 100000;
// Packetヘッダ判別用固定バイト列
static const uint8_t packet_begin[3] = { 0xFF, 0xD8, 0xEA };
void setup() {
M5.begin();
Serial2.begin(115200, SERIAL_8N1, 21, 22);
jpeg_data.buf = (uint8_t *) malloc(sizeof(uint8_t) * RX_BUF_SIZE);
lcd.init();
lcd.setRotation(1);
lcd.setBrightness(255);
lcd.setColorDepth(16);
lcd.clear();
}
void loop() {
if (Serial2.available()) {
uint8_t rx_buffer[10];
// 10byteずつ読み込む
int rx_size = Serial2.readBytes(rx_buffer, 10);
if (rx_size == 10) {
// Packetヘッダのbyte列を特定
if ((rx_buffer[0] == packet_begin[0]) && (rx_buffer[1] == packet_begin[1]) && (rx_buffer[2] == packet_begin[2])) {
// データサイズを取得
jpeg_data.length = (uint32_t)(rx_buffer[4] << 16) | (rx_buffer[5] << 8) | rx_buffer[6];
Serial.println(jpeg_data.length);
// データサイズ分バッファに読み込み
int rx_size = Serial2.readBytes(jpeg_data.buf, jpeg_data.length);
}
// Jpegデータを画面に描画
//M5.Lcd.drawJpg(jpeg_data.buf, jpeg_data.length,0, 0, 320, 240, 0, 0, JPEG_DIV_NONE);
// LovyanGFXの方が高速
lcd.drawJpg(jpeg_data.buf, jpeg_data.length,0, 0, 320, 240, 0, 0, ::JPEG_DIV_NONE);
}
}
}
実行してみる
- ポートレート115200でQVGAサイズのJPEG50%圧縮した画像で、フレームレート5fpsくらいで結構綺麗に画像が表示されます。
- これで液晶付きのM5StickVを買わなくても、M5UnitVをM5Stackと組み合わせる事でイケてる表示環境が手に入りますね。
- フレームレートを上げたい場合は、JPEG圧縮率を上げると良いです。(画質はかなり落ちますが・・・)
- このガジェット感、良いな。
@H0meMadeGarbage さんのM5UnitVの記事を参考に、M5Stack画面にUnitVからのカメラ画像を表示できた。
— Nabeshin (@desmoquattro996) December 30, 2020
JPEG表示し続けると30フレーム目位で画面更新が止まってしまったけど@lovyan03 さんのLovyanGFX使ったら止まらなくなりました。感謝! pic.twitter.com/MaGyJ2Nk8f