4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

M5Stack Module LLM Advent Calendar 2024

Day 4

Module-LLMとM5Stack CoreS3SEとの間でUART通信を行う

Last updated at Posted at 2024-12-03

目的

Module-LLMとM5Stack CoreS3SEとの間でUART通信を行う

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 のディスプレイにリアルタイムで表示するための受信側のコードです

M5StackS3SE.ino
#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に設定しています。

module_llm_send.py
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
    )
4
2
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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?