Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

マイコンで遊ぶBlenderシリーズ⑤M5StickCからBlenderに値を送る

0
Posted at

はじめに

この記事はマイコンで遊ぶBlenderシリーズの5番目です。

  • 準備編
  • Blenderの基本操作
  • モデリング
  • M5StickCとの連携①BlenderからM5StickCに値を送る
  • M5StickCとの連携②M5StickCからBlenderに値を送る←イマココ

M5StickCとの連携2回目です。
今回はM5StickC内蔵の加速度・ジャイロ6軸センサの値をBlenderに送って3D空間上のオブジェクトを回していきたいと思います。

IMAGE ALT TEXT HERE
こんなことができたりします。

プログラムの設計

プログラムの流れは次の通りです。

flow.jpg
Blenderからデータの送信要求を送る

M5StickCがセンサの値を読む

M5StickCが配列の中身を順にシリアル通信でPCに送る

Blenderがデータを受け取って内容を確認

受け取った値を使ってオブジェクトのプロパティの値を置き換える

一番最初に戻る

という流れです。

Arduino IDE(M5StickC)側のソースコード

以上を踏まえてコードを書いていきます。

Arduinoのコードは以下の通りです。

/*
  M5StickC内蔵の加速度センサの値をBlenderにおくるよ
*/

#include <M5StickC.h>

int16_t accX, accY, accZ;
int16_t gyroX, gyroY, gyroZ;
float temp = 0;

byte fromBle;
byte outBuf[14];
void setup() {
  M5.begin();
  M5.MPU6886.Init();

  Serial.begin(115200);

  pinMode(GPIO_NUM_10, OUTPUT);
  digitalWrite(GPIO_NUM_10, HIGH);  //  初期化
}

void loop() {
  M5.MPU6886.getGyroAdc(&gyroX,&gyroY,&gyroZ);
  M5.MPU6886.getAccelAdc(&accX,&accY,&accZ);

  //Blenderからの読み取り
  if (Serial.available() == 1) {
    byte inBuf[1];
    Serial.readBytes(inBuf, 1);
      
    if (inBuf[0] == 'o'){
      	digitalWrite(GPIO_NUM_10, LOW);
      	// start
      	outBuf[0]  = 's';  // 1
      	//  get 14bytes
      	outBuf[1]  = lowByte(accX); 
      	outBuf[2]  = highByte(accX);
      	outBuf[3]  = lowByte(accY); 
      	outBuf[4]  = highByte(accY);
      	outBuf[5]  = lowByte(accZ); 
      	outBuf[6]  = highByte(accZ);
      	outBuf[7]  = lowByte(gyroX); 
      	outBuf[8]  = highByte(gyroX);
      	outBuf[9]  = lowByte(gyroY); 
      	outBuf[10]  = highByte(gyroY);
      	outBuf[11]  = lowByte(gyroZ); 
      	outBuf[12]  = highByte(gyroZ);
      	//end
      	outBuf[13]  = 't'; 
      	Serial.write(outBuf, 14); 
    }else if (inBuf[0] == 'c'){
      	digitalWrite(GPIO_NUM_10, HIGH);
    }else{
    	while (Serial.available() > 0)Serial.read();
    }
  }

  if (Serial.available() > 1) {
    while (Serial.available() > 0)Serial.read();
  }
}

センサの値はint型で帰ってくるので、通信するためには2バイトのデータを1バイトづつ分割する必要があります。
lowbyte() とhighbyte()はそれぞれ2バイト値を1バイトに分割して下位1バイト/上位1バイトにに変換してくれる便利な奴です。Serial.write()基本的に1バイトづつしか送れないのですが、配列とその中身の数を引数に入れると配列の中身を順に送ってくれます。

Blender側のソースコード

前回のソースコードを基に、if event.type == 'TIMER':return {'PASS_THROUGH'}の間を書き換えます。

import datetime
import serial
import bpy
import math
import mathutils
import struct


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


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

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

    # タイマのハンドラ
    __timer = None
    # テキストオブジェクトの名前
    __text_object_name = None

    #Serial port
    __ser = serial.Serial('COM11', 115200, timeout = 0)
    samplingtime = 0.1

    @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(
                    blwm5.samplingtime, 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 Arduino] のボタン [終了] を押したときに、モーダルモードを終了
        if not self.is_running():
            return {'FINISHED'}

		#メインのループ内処理はここから
        if event.type == 'TIMER':
        	#送信処理(定期的にセンサーの値を送るようにリクエスト)
            blwm5.__ser.write(b'o')

            #受信処理
            if blwm5.__ser.inWaiting() == 14:
            	inBuf = blwm5.__ser.read(14)
                if inBuf.startswith(b's') and inBuf.endswith(b't'):
                	inBuf = inBuf[1:13]
                    val = struct.unpack('<6h', inBuf)

                    active_obj.rotation_euler[0] += val[3] / 32768 * 3.14
                    active_obj.rotation_euler[1] += val[4] / 32768 * 3.14
                    active_obj.rotation_euler[2] += val[5] / 32768 * 3.14
                    print(val)
                else:
                    print('missmatch')
                	blwm5.__ser.flushInput();

             elif blwm5.__ser.inWaiting() > 14:
             	inBuf = blwm5.__ser.read( blwm5.__ser.inWaiting() )
                print( "overflow" )
                print( inBuf )
                blwm5.__ser.flushInput();
      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.flushInput();
                blwm5.__ser.write(b'c')
                blwm5.__ser.close()
                print("STOP")
                return {'FINISHED'}
        else:
            return {'CANCELLED'}


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

    bl_label = "Blender with Arduino"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Blender with Arduino"
    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()

start_button.jpg
実行出来たら3DビューポートでNキーを押して右側からメニューを出し、赤枠で囲んだタブから開始ボタンを出してオブジェクトが動くか試してみてください。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?