目的
Module-LLMとM5Stack CoreS3SEとの間でUART通信を行う
LLM Moduleのmp4をuart経由でCore S3に表示してみた。QVGAで受信に200ms、描画に40ms。やはり遅いっすね… pic.twitter.com/UGv5dkebGN
— nnn (@nnn112358) November 23, 2024
LLM Serviceの停止
LLM Moduleは、Ubuntu OSが起動すると、M5StackのLLMのサービス(llm_sys,llm_asr,llm_audio,llm_kws,llm_llm,llm_tts)が自動的に起動するようになっています。
LLMのサービスは、M5StackとシリアルポートttyS1を介して通信を行っています。
LLMのサービスが起動している場合はシリアルポートttyS1を占有しているため、他のアプリケーションはシリアルポートttyS1にアクセスすることができません。
そのため、一旦、LLMのサービスを停止します。
注:) LLM ModuleのLLM 機能が停止します。LLM Moduleを再起動すれば、元に戻ります。
注:) 次のバージョンのStackFlowで、Base64 イメージと UVC入力をサポートする計画があります。
https://x.com/HanxiaoM/status/1860184980103790665
Module LLM上のコンソールから、psコマンドで起動中のLLMのサービスを確認することができます。
# ps aux | grep -i llm
root 1790 0.0 0.4 327008 4824 ? Ssl 15:36 0:00 /opt/m5stack/bin/llm_sys
root 1791 0.0 0.4 84340 4228 ? Ssl 15:36 0:00 /opt/m5stack/bin/llm_asr
root 1792 0.0 0.4 83024 3984 ? Ssl 15:36 0:00 /opt/m5stack/bin/llm_audio
root 1793 0.0 0.5 96176 5368 ? Ssl 15:36 0:00 /opt/m5stack/bin/llm_kws
root 1794 0.0 0.4 83552 4316 ? Ssl 15:36 0:00 /opt/m5stack/bin/llm_llm
root 1795 0.0 1.6 98172 15828 ? Ssl 15:36 0:00 /opt/m5stack/bin/llm_tts
root 11923 0.0 0.2 10056 2036 ttyS0 S+ 16:41 0:00 grep --color=auto -i llm
systemctlコマンドで、LLMのサービスを停止することができます。
#systemctl stop llm-sys
LLM Module(Ubuntu)からM5StackS3SEへ画像を送信する。
M5StackS3SEのArduinoIDEプログラム
このプログラムは M5StackS3SE の ArduinoIDEのプログラムで、
シリアル通信で受信した JPEG 画像データを表示するものです。
このプログラムはLLM ModuleのPythonプログラムと対になっており、送信された動画フレームを M5StackS3SE のディスプレイにリアルタイムで表示するための受信側のコードです
#include <M5Unified.h>
void setup() {
M5.begin();
M5.Display.setTextSize(1);
M5.Display.setTextScroll(true);
M5.Lcd.setTextFont(&fonts::efontJA_16);
Serial.begin(115200);
//Serial2.begin(115200, SERIAL_8N1, 16, 17); // Basic
// Serial2.begin(115200, SERIAL_8N1, 13, 14); // Core2
Serial2.begin(921600, SERIAL_8N1, 18, 17); // CoreS3
/* Reset ModuleLLM */
M5.Display.printf(">> ModuleLLM..\n");
Serial.print(">> ModuleLLM..\n");
}
void loop() {
static const uint8_t packet_begin[3] = { 0xFF, 0xD8, 0xEA };
if (Serial2.available() > 0) {
uint8_t rx_buffer[10];
int rx_size = Serial2.readBytes(rx_buffer, 10);
if (rx_size == 10) {
// パケット開始バイトのチェック
if ((rx_buffer[0] == packet_begin[0]) && (rx_buffer[1] == packet_begin[1]) && (rx_buffer[2] == packet_begin[2])) {
// 画像サイズの計算
uint32_t jpeg_length = (uint32_t)(rx_buffer[4] << 16) | (rx_buffer[5] << 8) | rx_buffer[6];
unsigned long start_time = millis();
// バッファにデータを読み込み
uint8_t* jpeg_buffer = new uint8_t[jpeg_length];
int data_size = Serial2.readBytes(jpeg_buffer, jpeg_length);
unsigned long tty_time = millis();
M5.Lcd.drawJpg(jpeg_buffer, jpeg_length);
unsigned long draw_time = millis();
Serial.print("Image size: ");
Serial.print(jpeg_length);
Serial.print(" size: ");
Serial.print(data_size);
Serial.print("time(msec):");
Serial.print(tty_time - start_time);
Serial.print(",");
Serial.print(draw_time - tty_time);
Serial.println();
}
}
}
Serial.print(".");
delay(10);
}
LLM Module(Ubuntu)のPythonプログラム
このプログラムは、MP4動画ファイルをシリアル通信で送信するためのPythonスクリプトです。
動画ファイルを1フレームずつ読み込み、各フレームを320x240にリサイズ・JPEGに圧縮、各フレームの前に識別用のヘッダーパケットを付加、シリアルポート経由でデータを送信までを行います。使用例では、"BadApple.mp4"という動画ファイルを921600 baudのシリアル通信で送信し、JPEG品質は50に設定しています。
import serial
import time
import cv2
from pathlib import Path
def send_video_over_serial(video_path, serial_port='/dev/ttyS1', baudrate=115200, quality=70):
"""
Send MP4 video frames over serial port with identification packet
Args:
video_path (str): Path to the MP4 video
serial_port (str): Serial port to use
baudrate (int): Baud rate for serial communication
quality (int): JPEG compression quality (1-100)
"""
try:
# Open the serial port
ser = serial.Serial(
port=serial_port,
baudrate=baudrate,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=1
)
# Check if serial port is open
if not ser.is_open:
ser.open()
# Open the video file
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise Exception("Error opening video file")
frame_count = 0
while True:
# Read frame from video
ret, frame = cap.read()
if not ret:
break
resized_frame = cv2.resize(frame, (320, 240), interpolation=cv2.INTER_AREA)
# Convert frame to JPEG
_, frame_data = cv2.imencode('.jpg', resized_frame, [cv2.IMWRITE_JPEG_QUALITY, quality])
image_data = frame_data.tobytes()
total_size = len(image_data)
# Extract size bytes
img_size1 = (total_size & 0xFF0000) >> 16
img_size2 = (total_size & 0x00FF00) >> 8
img_size3 = (total_size & 0x0000FF) >> 0
# Create packet with header and frame number
data_packet = bytearray([
0xFF, # Start marker 1
0xD8, # Start marker 2
0xEA, # Custom identifier 1
0x01, # Custom identifier 2
img_size1, # Size byte 1 (MSB)
img_size2, # Size byte 2
img_size3, # Size byte 3 (LSB)
(frame_count >> 16) & 0xFF, # Frame number byte 1
(frame_count >> 8) & 0xFF, # Frame number byte 2
frame_count & 0xFF # Frame number byte 3
])
# Send identification packet
ser.write(data_packet)
print(f"Sent frame {frame_count} header: {' '.join([f'{b:02X}' for b in data_packet])}")
# Small delay after sending header
time.sleep(0.01)
start_t = time.perf_counter()
# Send frame data
sent = 0
chunk_size = 4096
while sent < total_size:
chunk = image_data[sent:sent + chunk_size]
bytes_sent = ser.write(chunk)
sent += bytes_sent
# Print progress
progress = (sent / total_size) * 100
print(f"Frame {frame_count} Progress: {progress:.1f}% ({sent}/{total_size} bytes)", end='\r')
end_t = time.perf_counter()
print(f"\nFrame {frame_count} transmission completed!")
frame_count += 1
elapsed_time = end_t - start_t
print(f"time: {elapsed_time}sec")
print(f"\nVideo transmission completed! Total frames sent: {frame_count}")
except serial.SerialException as e:
print(f"Serial port error: {e}")
except FileNotFoundError:
print(f"Video file not found: {video_path}")
except Exception as e:
print(f"An error occurred: {e}")
finally:
if 'ser' in locals() and ser.is_open:
ser.close()
print("Serial port closed")
if 'cap' in locals():
cap.release()
print("Video capture released")
# Example usage
if __name__ == "__main__":
# Configuration
VIDEO_PATH = "BadApple.mp4"
SERIAL_PORT = "/dev/ttyS1"
BAUD_RATE = 921600
QUALITY = 50 # JPEG quality (1-100)
# Send the video
send_video_over_serial(
video_path=VIDEO_PATH,
serial_port=SERIAL_PORT,
baudrate=BAUD_RATE,
quality=QUALITY
)