「M5Atomで作る歩行ロボット」という本の犬型ロボットの製作記です。

本の内容からそれてしまっていますが、Ubuntuが必要なROSを使わずに、Windowsで完結するシステムにしたいと考え、ヌンチャクとPCの通信をUDPで行おうと思っています。

ここでは、Wiiのヌンチャクの信号をUDPでPCのUnityに送り、それに応じた描画をするという実験をしました。

WiiヌンチャクのM5Atom LITE 側

Wiiヌンチャクの信号をI2C通信で取得し、WiFiを使ってUDPでPCに送るプログラムです。ヌンチャクの信号は、zボタンとcボタンのオン/オフ(zbtn, cbtn)、十字キーのx値(joyx)、y値(joyy)、ヌンチャクの傾き3方向(accx, accy, accy) の7種類あります。

// ヌンチャクの情報をUDPで送信する

#include "WiFi.h"
#include "WiFiUdp.h"
#include "M5Atom.h"
#include <Wire.h>

static uint8_t nunchuck_buf[6];   // ヌンチャクの状態保存用バッファ

// WiFiの設定
const char *ssid = "your_ssid"; // 適切なものを記載
const char *password = "passward"; //適切なものを記載
const char *destination_ip = "xxx.xxx.xxx.xxx"; // PCのIPアドレスを記載
unsigned int destination_port = 5000;

// UDPの設定
WiFiUDP udp;

void setup() {
  // M5Atomの初期化
  M5.begin(true, false, true);

  // I2Cの初期化
  Serial.begin(19200);
  Wire.begin(19, 22);
  Wire.beginTransmission(0x52);
  Wire.write((uint8_t)0x40);
  Wire.write((uint8_t)0x00);
  Wire.endTransmission();

  // WiFiの接続開始, LEDを緑に点灯
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    M5.dis.drawpix(0, 0x00FF00);
    delay(500);
  }
  M5.dis.drawpix(0, 0x000000);
}

// ヌンチャクからデータを受信する
static int nunchuck_get_data()
{
  int cnt=0;
  Wire.requestFrom (0x52, 6);
  while (Wire.available ()) {
      nunchuck_buf[cnt] = nunchuk_decode_byte( Wire.read() );
      cnt++;
  }
  nunchuck_send_request();
  if (cnt >= 5) {
      return 0;  // success
  }
  return 1; // failure
}

// データのデコーディング
static char nunchuk_decode_byte (char x)
{
    // x = (x ^ 0x17) + 0x17; // 純正品のヌンチャクの場合はコメントを外す
    return x;
}


// ヌンチャクにデータをリクエストする
static void nunchuck_send_request()
{
    Wire.beginTransmission(0x52);
    Wire.write((uint8_t)0x00);
    Wire.endTransmission();
}

static int nunchuck_zbutton()
{
    return ((nunchuck_buf[5] >> 0) & 1) ? 0 : 1;
}

static int nunchuck_cbutton()
{
  return ((nunchuck_buf[5] >> 1) & 1) ? 0 : 1;
}

static int nunchuck_joyx()
{
  return nunchuck_buf[0]; 
}

static int nunchuck_joyy()
{
  return nunchuck_buf[1];
}

static int nunchuck_accelx()
{
  return nunchuck_buf[2];
}

static int nunchuck_accely()
{
  return nunchuck_buf[3];
}

static int nunchuck_accelz()
{
  return nunchuck_buf[4]; 
}

void loop() {
  // ヌンチャクの情報を取得
  if (nunchuck_get_data()) {
    M5.dis.drawpix(0, 0xFF0000);  // 取得失敗時はLEDを赤色に点灯
    delay(500);
    M5.dis.drawpix(0, 0x000000);
    delay(500);
    return;
  }

  int zbut = nunchuck_zbutton();
  int cbut = nunchuck_cbutton(); 
  int joyx = map(nunchuck_joyx(),3,252,-15,15);  // -15, 15の範囲に変換
  int joyy = map(nunchuck_joyy(),2,253,-15,15);
  int accx = map(nunchuck_accelx(),0,255,-15,15);
  int accy = map(nunchuck_accely(),0,255,-15,15);
  int accz = map(nunchuck_accelz(),0,255,-15,15);
  
  // シリアルモニタに表示
  Serial.print("zbut:"); Serial.print(zbut * 10); Serial.print(",");
  Serial.print("cbut:"); Serial.print(cbut * -10); Serial.print(",");
  Serial.print("joyx:"); Serial.print(joyx); Serial.print(",");
  Serial.print("joyy:"); Serial.print(joyy); Serial.print(",");
  Serial.print("accx:"); Serial.print(accx); Serial.print(",");
  Serial.print("accy:"); Serial.print(accy); Serial.print(",");
  // Serial.print("accz:"); Serial.print(accz); Serial.print(",");
  Serial.print("min:"); Serial.print(-20); Serial.print(","); // グラフ表示用
  Serial.print("max:"); Serial.print(20); //グラフ表示用
  Serial.print("\n");

  // データを送信
  String message = 
    + "," + String(zbut)
    + "," + String(cbut)
    + "," + String(joyx)
    + "," + String(joyy)
    + "," + String(accx)
    + "," + String(accy)
    + "," + String(accz);
  udp.beginPacket(destination_ip, destination_port);
  udp.write((const uint8_t*)message.c_str(), message.length());
  udp.endPacket();

  delay(20);
}Code language: C++ (cpp)

Unity

UnityでUDPをひろい、描画に反映する部分のプログラムはこんな感じです。初めなかなかうまくいかなかったのですが、ファイヤーウォールの設定が必要でした。こちらのブログを参考にさせていただきました。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;
using TMPro;

public class UDP_receiver : MonoBehaviour
{
    private UdpClient udpClient;
    private int port = 5000;

    public GameObject cube;
    public GameObject zbut_obj;
    public GameObject cbut_obj;
    public TMPro.TextMeshProUGUI text_board;

    private int zbut = 0;
    private int cbut = 0;
    private int joyx = 0;
    private int joyy = 0;
    private int accx = 0;
    private int accy = 0;
    private int accz = 0;

    void Start()
    {
        try
        {
            udpClient = new UdpClient(port);
            udpClient.BeginReceive(ReceiveCallback, null);
            Debug.Log("UDP Receiver started on port " + port);
        }
        catch (Exception e)
        {
            Debug.LogError("Failed to start UDP Receiver: " + e.Message);
        }
    }

    void Update()
    {
        string msg = string.Format("zbut: {0}\tcbut: {1}\tjoyx: {2}\tjoyy: {3}\naccx: {4}\taccy: {5}\taccz: {6}", zbut, cbut, joyx, joyy, accx, accy, accz);

        Debug.Log(msg);
        text_board.text = msg;

        // Existing code
        float scale = 0.2f;
        cube.transform.position = new Vector3(joyx * scale, 1.5f, joyy * scale);
        float rot_scale = 8.0f;
        cube.transform.rotation = Quaternion.Euler(accy * rot_scale, 0.0f, -accx * rot_scale);
        
        if (cbut == 1)
        {
            cbut_obj.GetComponent<Renderer>().material.color = Color.green;
        }
        else
        {
            cbut_obj.GetComponent<Renderer>().material.color = Color.gray;
        }


        if (zbut == 1)
        {
            zbut_obj.GetComponent<Renderer>().material.color = Color.green;
        }
        else
        {
            zbut_obj.GetComponent<Renderer>().material.color = Color.gray;
        }

    }

    private void OnDisable()
    {
        if (udpClient != null)
        {
            udpClient.Close();
        }
    }    

    private void ReceiveCallback(IAsyncResult ar)
    {
        try
        {
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, port);
            byte[] receivedBytes = udpClient.EndReceive(ar, ref endPoint);
            string receivedMessage = Encoding.ASCII.GetString(receivedBytes);
            udpClient.BeginReceive(ReceiveCallback, null);
            Controllcube(receivedMessage);

        }
        catch (Exception e)
        {
            Debug.LogError("Failed to receive UDP message: " + e.Message);
        }

    }

    private void Controllcube(string message)
    {
        try
        {
            string[] parts = message.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
            zbut = int.Parse(parts[0]);
            cbut = int.Parse(parts[1]);
            joyx = int.Parse(parts[2]);
            joyy = int.Parse(parts[3]);
            accx = int.Parse(parts[4]);
            accy = int.Parse(parts[5]);
            accz = int.Parse(parts[6]);
        }
        catch (Exception e)
        {
            Debug.LogError("Failed to parse message: " + e.Message);
        }

    }
}
Code language: C# (cs)

完成図

こんな感じです。けっこう反応は良いです。