しましまの妙なBLOG

しましまのBlenderに関する備忘録を貯めておく場所です

シリアル通信でマイコンBlenderを連携させる(サーボモーター編)

はじめに

ここ半年ほど、Blender用の自作入力デバイスというものを制作しています。


Gestoucher3D 紹介動画

この入力デバイス作りで得られた知見を残しておこうと思い記事を書くことにしました。 今回はマイコンBlenderをシリアル通信を使って連携することで、BlenderからLEDやモーターを制御してみます。

これは Blender Advent Calendar 2020参加記事です。

セットアップ

必要なもの・環境

ソフトウェア

ハードウェア

Windows10とmacOS High Sierraで動作確認しています。

pyserialのインストール

Blenderはすでにインストールされていることとして話を進めます。 まず初めにPythonでシリアル通信を扱うためのモジュールであるpyserialをBlenderにインストールします。

pypi.org

f:id:shima2cgstudio:20201215192004p:plain

Download filesからpyserial-3.5.tar.gzをクリックし、tar.gzファイルをダウンロードしてください。

f:id:shima2cgstudio:20201215191949p:plain 7-Zip等の展開ソフトでtar.gzファイルを展開します。Macの場合はダブルクリックで展開できます。

f:id:shima2cgstudio:20201215191918p:plain

展開して生成されたdistフォルダを開きます。

f:id:shima2cgstudio:20201215191909p:plain

pyserial-3.5.tarを先ほどと同様に展開します。

f:id:shima2cgstudio:20201215191821p:plain

展開してできたpyserial-3.5フォルダを開いてserialフォルダがあるか確認してください。

Windowsの場合は

C:\Program Files\Blender Foundation\Blender 2.91\2.91\scripts\modules(数字の箇所はバージョンによって異なります。)

Macの場合はアプリケーションフォルダ内にあるBlender.appを右クリックして、パッケージの内容を表示を選択し同様にmodulesフォルダに移動します。

f:id:shima2cgstudio:20201215191816p:plain

modulesフォルダにserialフォルダをコピーすればインストール完了です。

Arduino IDEのインストール

マイコン用のプログラムを書く開発環境にはArduino IDEを使用します。

https://www.arduino.cc/en/software

Arduinoとはマイコンを簡単に扱えるようにした開発システムです。C++のようなプログラムを書くことでマイコンを制御することができます。Arduino IDEはそのプログラムを書くためのソフトウェアです。今回つかうM5StickCはArduinoではありませんが、Arduino IDEに対応しており同様に扱うことができます。

ただその代わりにインストールに特別な手順を踏む必要があります。

https://kuratsuki.net/2019/07/m5stickc-%E3%81%A7%E9%96%8B%E7%99%BA%E3%82%92%E8%A1%8C%E3%81%86%E3%81%9F%E3%82%81%E3%81%AE-arduino-ide-%E3%81%AE%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97/

https://docs.m5stack.com/#/ja/quick_start/m5stickc/m5stickc_quick_start_with_arduino_Windows

これらの記事を参考にセットアップを行ってみてください。

以上でセットアップは終了です。Blenderを起動しましょう。

BlenderからM5StickCに値を送る

ようやく本題に入ります。まずはM5StickCにつないだサーボモータを、Blenderから操作していこうと思います。


BlenderからM5StickCにつないだサーボモーターを動かす

うまくいけばこのようにデフォルトキューブを回すことでサーボモータが回るはずです。

シリアル通信とは

Blenderマイコンとの間ではシリアル通信というものを使ってデータの受け渡しを行います。

シリアル通信というのはすごく平たく説明すると、小さいデータを一個づつ高速で送る通信方式のことです。マイコンとPCだけでなく、マイコン同士のデータのやり取りでもよく使われる方式です。

プログラムの流れ

f:id:shima2cgstudio:20201215191558j:plain

Blenderから一定間隔でM5StickCに値を送り、M5StickCは受け取り次第値を使ってサーボモータを動かします。

サーボモータをM5StickCにつなぐ

f:id:shima2cgstudio:20201215192145j:plain

サーボモータの三本線のうち、赤色をM5StickCの3.3V出力に、茶色をGNDに、オレンジ色の制御線をG26につなぎます。

Arduino IDEソースコードを書く

サーボモーターを使うためのESP32Servoライブラリをあらかじめインストールしてください。

f:id:shima2cgstudio:20201215191537p:plain f:id:shima2cgstudio:20201215191521p:plain

ライブラリを管理から、esp32servoを検索してインストールします。

プログラムは以下の通りです。

#include <ESP32Servo.h>     //esp32servoライブラリの読み込み
#include <M5StickC.h>     //M5StickCを使うためのライブラリの読み込み
Servo myservo;              //サーボモーターを扱うクラスの宣言

void setup(){       //マイコン起動時に一回だけ実行されます。初期設定をここで行います。
  M5.begin();       //M5StickCの各種機能の初期化を行います。
  
  myservo.attach(G26, 650, 2500);       
  //サーボモーターの設定を行います。引数は順にサーボの制御線が繋がっているピン番号、
  //回転角度が0の時のパルス幅、回転角度が180度の時のパルス幅です
  
  Serial.begin(115200);     //シリアル通信を初期化します。引数はシリアル通信の速度です。
}


void loop(){        //setup()終了後、電源が入っている間はずっと繰り返し実行されます。
  if (Serial.available() > 0) {      //シリアル通信で受信したデータの有無を確認します。
    byte readvalue = (byte)Serial.read();
    //byte型変数"readvalue"にSerial.read()で受信したデータを格納します。
    //型変換(キャスト)するために(byte)をつけます。
    
    uint8_t angle = (uint8_t)readvalue;
    //uint8_t型変数"data"にreadvalueを格納します。
    //型変換(キャスト)するために(uint8_t)をつけます。
    
    myservo.write( angle );     //何度の位置までサーボモーターを回すか指定します。
    delay(20);      //20ミリ秒待ちます。
  }
}

コードが書けたらマイコンに書き込みましょう。

f:id:shima2cgstudio:20201215191501p:plain

赤線部のボードがM5StickC、シリアルポートがM5StickCが繋がっているポートになっていることを確認し、左上の矢印ボタンを押してください。

BlenderPythonを実行する

BlenderにはPythonというプログラミング環境が内蔵されていて、Pythonでプログラムを書くことでアドオン(追加機能)や処理の自動化プログラムを制作することができます。 以下の手順でBlender上でPythonプログラムを実行することができます。

f:id:shima2cgstudio:20201215191336p:plain

上部のタブの[Scripting]をクリックします。

f:id:shima2cgstudio:20201215191327p:plain

このようなレイアウトに変わります。

f:id:shima2cgstudio:20201215191324p:plain

テキストから新規を選ぶとソースコードを入力できるようになります。

f:id:shima2cgstudio:20201215191246p:plain

ソースコードを実行するときは上部ファイル名横の三角形ボタンを押してください。

Blenderアドオンのソースコードを書く

M5StickCとシリアル通信を行うプログラムのソースコードは次の通りです。

import serial
import bpy
import math
import mathutils
import struct


bl_info = {
    "name": "Blender with M5StickC",
    "author": "しましま",
    "version": (3, 0),
    "blender": (2, 80, 0),
    "location": "3Dビューポート > Sidebar > Blender with M5StickC",
    "description": "BlenderとM5StickCを連携させるアドオン",
    "warning": "",
    "support": "TESTING",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Object"
}

# モーダルモードでオブジェクトを動かすオペレータ
class BLENDER_WITH_M5(bpy.types.Operator):

    bl_idname = "object.blender_with_m5"
    bl_label = "M5StickCと連携"
    bl_description = "Blender with M5StickC"

    # タイマのハンドラ
    __timer = None

    #シリアルポートの設定
    #最初の引数'COM5'にはM5StickCのCOMポート名を入れてください。
    __ser = serial.Serial('COM5', 115200, timeout = 0)

    @classmethod
    def is_running(cls):
        # モーダルモード中はTrue
        return True if cls.__timer else False

    def __handle_add(self, context):
        blwm5 = BLENDER_WITH_M5
        if not self.is_running():
            # タイマを登録
            blwm5.__timer = \
                context.window_manager.event_timer_add(
                    0.1, window=context.window
                )
            # モーダルモードへの移行
            context.window_manager.modal_handler_add(self)

    def __handle_remove(self, context):
        if self.is_running():
            # タイマの登録を解除
            context.window_manager.event_timer_remove(
                BLENDER_WITH_M5.__timer)
            BLENDER_WITH_M5.__timer = None

    def modal(self, context, event):
        blwm5 = BLENDER_WITH_M5
        active_obj = context.active_object

        # エリアを再描画
        if context.area:
            context.area.tag_redraw()

        # パネル [Blender with M5StickC] のボタン [終了] を押したときに、モーダルモードを終了
        if not self.is_running():
            return {'FINISHED'}

        #メインのループ内処理はここから
        if event.type == 'TIMER':
            value = int(active_obj.rotation_euler[2] * 180/math.pi)

            #送信処理(オブジェクトの角度が0度から180度の時だけ値を送る)
            if value >= 0 and value <= 180:
                blwm5.__ser.write( bytes([value]) )
        return {'PASS_THROUGH'}
        #ここまで

    def invoke(self, context, event):
        blwm5 = BLENDER_WITH_M5
        active_obj = context.active_object

        if context.area.type == 'VIEW_3D':
            # [開始] ボタンが押された時の処理
            if not blwm5.is_running():
                # モーダルモードを開始
                self.__handle_add(context)
                if not blwm5.__ser.is_open:
                    blwm5.__ser.open()
                print("START")
                return {'RUNNING_MODAL'}
            # [終了] ボタンが押された時の処理
            else:
                # モーダルモードを終了
                self.__handle_remove(context)
                blwm5.__ser.close()
                print("STOP")
                return {'FINISHED'}
        else:
            return {'CANCELLED'}

# UI
class BLENDER_WITH_M5_UI(bpy.types.Panel):

    bl_label = "Blender with M5StickC"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Blender with M5StickC"
    bl_context = "objectmode"

    def draw(self, context):
        blwm5 = BLENDER_WITH_M5

        layout = self.layout
        # [開始] / [停止] ボタンを追加
        if not blwm5.is_running():
            layout.operator(blwm5.bl_idname, text="開始", icon="PLAY")
        else:
            layout.operator(blwm5.bl_idname, text="終了", icon="PAUSE")

classes = [
    BLENDER_WITH_M5,
    BLENDER_WITH_M5_UI,
]

def register():
    for c in classes:
        bpy.utils.register_class(c)
    print("Registed")

def unregister():
    for c in classes:
        bpy.utils.unregister_class(c)
    print("Unregisted")

if __name__ == "__main__":
    register()

ぬっち氏著の「はじめてのBlenderアドオン開発 (Blender 2.8版)」を参考にBlenderアドオンとして書いています。

https://colorful-pico.net/introduction-to-addon-development-in-blender/2.8/index.html

特にModalメゾッドを使用するため、以下の記事が参考になりました。

https://colorful-pico.net/introduction-to-addon-development-in-blender/2.8/html/chapter_03/01_Handle_Mouse_Event.html

必ず確認していただきたいのが、34行目のシリアルポートを設定する一行です。

__ser = serial.Serial('COM5', 115200, timeout = 0)

引数の一番目'COM5'のところにはM5StickCが繋がっているシリアルポート名をいれてください。 ArduinoIDEで設定したものと同じものです。

if event.type == 'TIMER':からreturn {'PASS_THROUGH'}までの間がメインのループ処理、Arduinoでいうところのloop()にあたると考えてください。

#メインのループ内処理はここから
    if event.type == 'TIMER':
        value = int(active_obj.rotation_euler[2] * 180/math.pi)

        #送信処理(オブジェクトの角度が0度から180度の時だけ値を送る)
        if value >= 0 and value <= 180:
            blwm5.__ser.write( bytes([value]) )
    return {'PASS_THROUGH'}
#ここまで

この箇所を書き換えることで、様々なデータをシリアル通信で送ることができます。 今回は選択したオブジェクトのZ軸角度が0から180の時だけ、シリアル通信で角度を送信するようになっています。

ここまで出来たらマイコンをつないだ状態で実行してみてください。 f:id:shima2cgstudio:20201215191129j:plain

3DビューポートでNキーを押して出てくる右側のメニューから[Blender with M5StickC]を選んで開始ボタンを押してください。オブジェクトを回転させるとサーボモーターも回るはずです。

終わりに

サーボモーターは動きましたか?3D空間内の動きが現実世界に反映されるっていうのは結構面白いんじゃないかと思います。

次はM5StickC内蔵の6軸センサを使ってデフォルトキューブを動かす記事を書く予定です...

参考記事

Arduino Blender & Maya Serial通信テスト(Facebook内容転載) ~ 3DCGと映像とINTERACTIVE http://dekapoppo.blogspot.com/2017/11/arduino-serial-blender-facebook.html

MikeBeradino/Python-Blender-serial_comm: Using blender to control arduino board via serial com. https://github.com/MikeBeradino/Python-Blender-serial_comm