The jonki

呼ばれて飛び出てじょじょじょじょーんき

Maker Faire Tokyo 2013 でレゴへのプロジェクションマッピングネタで出展してきた【ハード編】

先日のMaker Faire Tokyo 2013にid:hecomiと二人でMont.Blanc.Pjというチーム名で参加し、凸Pというレゴのプロジェクションネタで出展してきました。



既にid:hecomiがソフトウェア部分のところをかなり細かく書いているので、Arduinoの通信周りやプロジェクションする側の筐体作成までの困難な道のりを書いていければと思います。あんまり派手な部分はないので面白くないかもしれません(笑)

反響

Twitterや大手メディア様から反応がありました。本当に嬉しい限りです。ニコニコ学会マッドネス部門のご推薦も頂きました。

レゴの検出解説

既にid:hecomiがレゴの検出方法について詳しい記事を書いています。

筐体

筐体には写真にあるように銀ラックを使っています。色々な筐体を考えていたんですが、意外とこれが良かったです。そしてこの銀ラックによくポスターなんかを貼る5mmの厚さのボードで前面を隠すような箱を作って被せています。

  • 組み立て/分解が簡単
  • 安い(2000円弱)
  • 構造がしっかりしていて、しかも台を追加して内部のガジェットの整理が付きやすい
工夫

外側ができたことで結構良い感じになったのですが、レゴの取り付けなんかを行う際に結構ずれるんですね。キャリブが命のシステムだったのでできるだけ台を安定化させる必要がありました。色々考えた結果以下の3つをやっています。展示当日こどもたちにかなり激しく使われましたが、ほとんど動くことはありませんでした。今思えば台の安定化はかなり大切なポイントでした。

  • 銀ラックの足に滑り止めのスポンジを置く
  • おもり(今回はダンベルw)を下の方に置く
  • 外の白いカバーを銀ラックにヒモできつく固定する。真ん中の台のところに100均で買ったケーブルをまとめるもの(両面テープで付く)を白いカバーにつけてヒモが引っかかるようになっています。

Arduinoからの制御

今回Arduino2台(UNOとDuemilanove)を使って、DCモーター(UNO)、サーボ(UNO)、LED(Duemilanove)をPCからUSBのシリアル通信で操作しています。イーサネットシールドも持っていたのですが、シリアル通信は下記の3つの理由で採択しています。

  • 今回は無線化したりスマートにする必要がなかったので、USBでPCをつないでもデモの見栄えに影響がない
  • イーサネットシールドが高価
  • ネットワーク環境を構築する必要がないので、手間が少なく当日のネットワークトラブルの要因を排除できる
サーボ

サーボにはタミヤのユニバーサルアーム(オレンジの)が付いており、そしてその先に非常に細い糸をつけています。これをドッスンのブロックにつけることでドッスンの上下運動を実現しています。かなりアナログな方法です。
サーボのコントロールはArduinoにはサーボコントロール用のライブラリがあるので簡単です。サーボは配線が3本あり、信号線と電源(プラスとマイナス)です。解説は下記サイトを参考にして下さい。ただし電源は安定化のため外部電源の5Vを使っています。

DCモーター

DCモーターにはレゴのテックシリーズのクレーンなどに使われているキャタピラを使っています。通常の6角の棒とレゴがもちろんそのままでは合わないので、接合部にグルーガンを流し込んで無理矢理固定しています。レゴは基本的にレゴとしか接合しないので、こういうとき大変です。あんまり力がかかると簡単に外れちゃいます。なんか良い方法ほかにないですかね?


ちなみにDCモーターのArduinoの制御はサーボより少し複雑です。今回はTA7291PというICを使って制御します。ただし下記のサイトの例では3ピンが使われていますが、今回は既にサーボで3ピンが使われているので、1、2、3ピンと同じくPWMが使われる9、10、11ピンを使いました。

サーボとDCは下記のようなスケッチで1台のArduinoで制御しています。そのためデータは"d123"や"s23"といったように、単語の始めにサーボかDCを判断する文字、次にそれぞれが扱う数字を送ることでどちらのデバイスを制御するか判断しています。SoftwareSerialはデバッグ用となどで便利です。うまく動かないときは地道にprintデバッグです。

#define IN1PIN 9
#define IN2PIN 10
#define OUTPIN 11

#define SERVOPIN 3
#define BUFFERSIZE 32
#include <Servo.h>
#include "SoftwareSerial.h"

Servo servo;
// SoftwareSerial softSerial(5, 6);
char buffer[BUFFERSIZE];
void setup() 
{ 
    Serial.begin(9600);
    //softSerial.begin(9600);

	pinMode(IN1PIN, OUTPUT);
	pinMode(IN2PIN, OUTPUT);

    servo.attach(SERVOPIN);
} 

int ReadData(char data[]) 
{
    int val = 0;
    int i = 0;
    buffer[0] = '\0';
    while(1) {
        if(Serial.available()) {
            buffer[i] = Serial.read();
            //softSerial.println("read data " + String(i) + " = " + String(buffer[i]));
            if('\0' == buffer[i]) {
                //softSerial.println("end of data");
                val = atoi(buffer);
                break;
            }
            i++;
            if(i >= BUFFERSIZE) break;
        }
    }

    //softSerial.println("ReadData: " + String(val));
    return val;
}

void loop() 
{
    if(Serial.available()) {
        char head = Serial.read();
        if('d' == head) {
        	//softSerial.println("Motor Mode");
        	int val = ReadData(buffer);
        	//softSerial.println("Motor val: " + String(motorVal));
        	MotorDrive(val);
        } else if('s' == head) {
        	//softSerial.println("Servo Mode");
        	int val = ReadData(buffer);
        	//softSerial.println("Servo val: " + String(servoVal));
        	ServoDrive(val);
        }
    }
} 

void MotorDrive(int val) 
{
	if(val > 10) {
		digitalWrite(IN1PIN, HIGH);
		digitalWrite(IN2PIN, LOW);
        analogWrite(OUTPIN, val); 

	} else if(val < -10) {
        	digitalWrite(IN1PIN, LOW);
        	digitalWrite(IN2PIN, HIGH);
        	analogWrite(OUTPIN, -1.0f * val); 
	} else { 
		digitalWrite(IN1PIN, LOW);
		digitalWrite(IN2PIN, LOW);
	}
}

void ServoDrive(int val)
{
    //softSerial.println("ServoDrive: " + String(val));
    servo.write(val);   
}
LED

LEDはゲーム用やシステムの動作確認用などに多く使うかなぁと準備していましたが、結局時間がなくてLEDは1つしか使いませんでした(コインブロック)。TLC5940というICで16個まで1つのArduinoで簡単に制御できますが、今回に限っては完全にスペックオーバーでした。

#include "Tlc5940.h"
#include "SoftwareSerial.h"

#define RET_OK 0
#define RET_ERROR -1

#define MAX_BRIGHTNESS 4095 // 0-4095 
#define POWAN_SPEED 100     // microsecond 
#define BUFFERSIZE 32

#define LED_NUM 16

TLC_CHANNEL_TYPE ch; 
int targetChannel;  // current channel 
int targetBrightness;   // current brightness 
char buffer[BUFFERSIZE];
int LED[LED_NUM];


SoftwareSerial softSerial(5, 6);

void setup() { 
    Serial.begin(9600);
    softSerial.begin(9600);

    Tlc.init(); 
    Tlc.clear(); 
    for(int i = 0; i < LED_NUM; i++) { 
        LED[i] = 0;
        Tlc.set(i, LED[i]); 
    } 
    Tlc.update(); 
} 

void loop() 
{
    if(Serial.available()) {
        int ret = ReadData(buffer, targetChannel, targetBrightness);
        if(ret == RET_OK) {
            if(targetChannel >= 0 && targetChannel < LED_NUM) {
                LED[targetChannel] = targetBrightness;
            }
        }
    }

    for(int i = 0; i < LED_NUM; i++) { 
        Tlc.set(i, LED[i]);
        // Tlc.set(i, (sin(micros() / 1000000.0f) + 1.0f) * 2000);
        Tlc.update();
        delayMicroseconds(POWAN_SPEED); 
    } 
} 

int ReadData(char *data, int &channel, int &brightness) 
{
    int ret = RET_ERROR;

    int i = 0;
    buffer[0] = '\0';
    while(1) {
        if(Serial.available()) {
            buffer[i] = Serial.read();
            softSerial.println("read data " + String(i) + " = " + String(buffer[i]));
            if('\0' == buffer[i]) {
                sscanf(buffer, "%d,%d", &channel, &brightness);
                softSerial.println("ReadData: " + String(channel) + "," + String(brightness));
                if(channel < 0 || channel >= LED_NUM) ret = RET_ERROR;
                else if(brightness < 0 || brightness > MAX_BRIGHTNESS) ret = RET_ERROR;
                else ret = RET_OK;
                break;
            }
            i++;
            if(i >= BUFFERSIZE) {
                ret = RET_ERROR;
                break;
            }
        }
    }

    return ret;
}
シリアル通信のホスト側

今回メインのゲームがUnityであるため、UnityとArduinoシリアル通信を行っています。PC側にはUSBケーブルが2本、それぞれUNOとDuemilanoveが繋がっていて、同時に様々なメッセージが送れます。
今回はライブラリをid:hecomiに提供するということで、シングルトン風にそれぞれのインスタンスを取得し、簡単なAPIを叩いてもらうようにしました。特にSerialの通信周りは使う側からは隠れています。(と、いいつつポートはあらかじめ指定する必要があるんですが)
長くなってしまうのでシリアル通信部をラップするSerialHandler.cs、DCモーターのライブラリ(DCMotorController.cs)、そしてその使い方のソースコードを載せます。LEDとサーボもDCモーターのライブラリと基本的に同じです。

  • SerialHandler.cs
using UnityEngine;
using System.Collections;
using System.IO.Ports;
using System.Collections.Generic;
using System.Text;

public enum ArduinoType {
	DUEMILANOVE,
	UNO,
}

public class SerialHandler {
	const string DuemilanovePort = "/dev/tty.usbserial-A800ey7d";
	const string UnoPort 		 = "/dev/tty.usbmodemfa1311";
	const int BaudRate 			 = 9600;
	
	static private Dictionary<ArduinoType, SerialHandler> m_handlerDict = new Dictionary<ArduinoType, SerialHandler>();
	
	private SerialPort 		m_serial;
	
	private SerialHandler(ArduinoType type)
	{
		switch(type)
		{
		case ArduinoType.DUEMILANOVE:
			Debug.Log("Try to open DUEMILANOVE Port " + DuemilanovePort);
			m_serial = new SerialPort(DuemilanovePort, BaudRate, Parity.None, 8, StopBits.One);
			break;
		case ArduinoType.UNO:
			Debug.Log("Try to open UNO Port " + UnoPort);
			m_serial = new SerialPort(UnoPort, BaudRate, Parity.None, 8, StopBits.One);
			break;
		}
		
		Start();
	}
	
	static public SerialHandler GetSerialHandler(ArduinoType type)
	{
		if(type != ArduinoType.DUEMILANOVE && type != ArduinoType.UNO) return null;
		
		SerialHandler handler = null;
		if(!m_handlerDict.ContainsKey(type)) {
			handler = new SerialHandler(type);
			m_handlerDict.Add(type, handler);
		} else {
			handler = m_handlerDict[type];	
		}
	
		return handler;
	}
	
	private void OnSerialDataReceived(object sender, SerialDataReceivedEventArgs e)
	{
		Debug.Log("OnSerialDataReceived");
		SerialPort port = (SerialPort)sender;
		byte[] buf = new byte[1024];
		int len = port.Read(buf, 0, 1024);
		string s = Encoding.GetEncoding("Shift_JIS").GetString(buf, 0, len);
		Debug.Log(s);
	}

	private void OnSerialErrorReceived(object sender, SerialErrorReceivedEventArgs e)
	{
		Debug.Log("Serial port error: " + e.EventType.ToString ("G"));
	}
	
	private void IOErrorHandler()
	{
		Debug.LogError("IOException!!!!");
		Stop();
	}
	
	private void Start() {
		if(m_serial != null) {
			if(m_serial.IsOpen) {
				Debug.LogError("Failed to open Serial Port, already open!");
				m_serial.Close();
			} else {
				try
				{
					m_serial.DataReceived += OnSerialDataReceived;
					m_serial.ErrorReceived += OnSerialErrorReceived;
					m_serial.Open();
					m_serial.DtrEnable = true;
					m_serial.RtsEnable = true;
					m_serial.ReadTimeout = 50;
					Debug.Log("Open Serial port");
				}
				catch(System.IO.IOException)
				{
					IOErrorHandler();
				}
			}
		}
	}
	
	public void Stop() 
    {
		if(m_serial != null) {
			Debug.Log("CLose Serial Port");
			m_serial.Close();
		}
    }
		
	public string CreateSendData<T>(string header, T data)
	{
		return header + data.ToString() + "\0";
	}
	
	public void SendData(string data)
	{
		try
		{
			m_serial.Write(data);
		}
		catch(System.IO.IOException)
		{
			IOErrorHandler();
		}
	}
	
}
  • DCMotorController.cs
using UnityEngine;
using System.Collections;

public class DCMotorController {
	private const string DCMotorHeader = "d";  // Arduinoの制御用ヘッダ
	
	static private DCMotorController s_instance = null;
	
	private SerialHandler m_handler = null;
	
	private DCMotorController()
	{
		m_handler = SerialHandler.GetSerialHandler(ArduinoType.UNO);
	}
	
	static public DCMotorController Instance
	{
		get 
		{
			if(s_instance == null)
			{
				s_instance = new DCMotorController();	
			}
			return s_instance;
		}
	}
	
	/// <summary>
	/// Sets the DC motor speed.
	/// </summary>
	/// <param name='speed'>
	/// Speed. Reverse Rot: -255 to -11, Stop: -10 to 10, Forward Rot: 11 to 255
	/// </param>
	public void SetSpeed(int speed)
	{
		if(m_handler != null) {
			var data =  m_handler.CreateSendData<float>(DCMotorHeader, speed);
			m_handler.SendData(data);
		}
	}
	
	public void Quit()
	{
		if(m_handler != null) {
			m_handler.Stop();	
			m_handler = null;
		}
	}
}
  • 使い方
using UnityEngine;
using System.Collections.Generic;

public class SerialTestClient : MonoBehaviour {
    private DCMotorController m_dcController;
    private ServoController   m_servoController;
    private LEDController     m_ledController;
    
    public int m_servoValue = 0;
    public int m_motorValue = 0;
    public List<int> m_ledValueList = new List<int>();
    
    // Use this for initialization
    void Start () {
        m_dcController = DCMotorController.Instance;
        m_servoController = ServoController.Instance;
        m_ledController = LEDController.Instance;
        
        for(int i = 0; i < 16; i++) {
            m_ledValueList.Add(0);
        }
    }
    
    // Update is called once per frame
    void Update () {
    }
                
    void OnGUI() {
        // Servo Update
        var val = 0;
        val = (int)GUI.HorizontalSlider(new Rect(10, 10, 200, 40), m_servoValue, 0, 180);
        if(val != m_servoValue) {
            m_servoValue = val;
            if(m_servoController != null) {
                m_servoController.SetDegree(m_servoValue);  
            }
        }
        GUI.Label(new Rect(220, 10, 100, 40), "Servo: " + m_servoValue.ToString());
        
        // DCMotor Update
        val = (int)GUI.HorizontalSlider(new Rect(10, 50, 200, 40), m_motorValue, -255, 255);
        if(val != m_motorValue) {
            m_motorValue = val;
            if(m_dcController != null) {
                m_dcController.SetSpeed(m_motorValue);  
            }
        }
        GUI.Label(new Rect(220, 50, 100, 40), "DCMotor: " + m_motorValue.ToString());
        
        // LED Update
        for(int i = 0; i < m_ledValueList.Count; i++) {
            var rect = new Rect(10 + i * 35, 90, 40, 150);
            val = (int)GUI.VerticalSlider(rect, m_ledValueList[i], 0, 100);
            if(val != m_ledValueList[i]) {
                m_ledValueList[i] = val;
                if(m_ledController != null) {
                    m_ledController.SetLedWithBrightness(i, m_ledValueList[i]); 
                }
            }
            GUI.Label(rect, "LED" + i.ToString() + ": " + m_ledValueList[i].ToString());
        }
    }
    
    void OnApplicationQuit() 
    {
        if(m_dcController != null) {
            m_dcController.Quit();
        }
        if(m_servoController != null) {
            m_servoController.Quit();
        }
        if(m_ledController != null) {
            m_ledController.Quit();
        }
    }
}

最後に

自分もid:hecomiもソフトウェア屋なのでハード面、デザイン面、もう少しがんばりたかったなぁというのが本音です。が、反響も大きくいろんなところから結構声がかかっていたりして結構嬉しいです。今回は2ヶ月ぐらいしか時間がなかったので来年のMakerや他の展示にも向けてどんどん頑張っていきたいところです。

【Arduino/Android】SoftwareSerialでAndroidとArduionoの間でシリアル通信する

前回こんな記事を書きましたが、USBのシリアル通信でなくSoftwareSerialを使いました。

SoftwareSerialを使うことでUSBをPC側に繋いだまま開発ができます。またシリアル線ができたので他のマイコンなどにも使えて便利そうです。またVCCがあるのでPCとのUSBを外してもAndroid側から電力を供給できます。

用意するもの

Arduino

SoftwareSerialシリアル通信をソフト側でやろうというものです。USBケーブル内でやってる配線を簡単にArduino側の配線に繋げるイメージでしょうか。FTDIの変換アダプターはTX・RX・VCC・GNDArduino側とつなげます。TXとRXは送信と受信ですが、Arduino側のRX(10ピン)はアダプタ側のTX、TX(11ピン)はアダプタ側のRXと逆になるので注意してください。送る側に付けるのは受ける側、受ける側に繋がっているのは送る側ですからね。
Serial受信時はわかりやすいように13ピンのLEDを点灯させるようにしました。またSoftwareSerialのボーレートはこれより低いとメッセージの取りこぼしがありました。

#include "SoftwareSerial.h"

#define StringMaxLength 32

int ledNum = 13;
SoftwareSerial mySerial(10, 11); // RX, TX
char data[StringMaxLength];
int counter = 0;

void setup() {                
    Serial.begin(14400);
    pinMode(ledNum, OUTPUT);
    
    // set the data rate for the SoftwareSerial port
    mySerial.begin(9600);
    mySerial.println("Hello from Arduino SoftwareSerial!");
}

void loop() {
    if(mySerial.available()) {
        Serial.println("available");
        digitalWrite(ledNum, HIGH);
        data[counter] = mySerial.read();
        counter++;
        if(data[counter] == '\0' || counter == StringMaxLength) {
            data[counter] == '\0';
            Serial.println(data);
            mySerial.print("Hi from Arduino "); 
            mySerial.println(data);
            counter = 0; 
            digitalWrite(ledNum, LOW);
        }
    }
}

Android

ベースはFTDriverのサンプルとほぼ同じです。レイアウト変えたのと、TextViewをクリアするボタンをつけた感じです。Android側から固定のStringを送っていますが、テキストエリアから自在にメッセージを送るように作っても良いと思います。

アクティビティ
package net.jonki.serialapplication;

import jp.ksksue.driver.serial.FTDriver;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
    FTDriver mSerial;
    
    // [FTDriver] Permission String
    private static final String ACTION_USB_PERMISSION =
            "jp.ksksue.tutorial.USB_PERMISSION";
    
    Button btnBegin,btnRead,btnWrite,btnEnd;
    TextView tvMonitor;
    StringBuilder mText = new StringBuilder();;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnBegin = (Button) findViewById(R.id.btnBegin);
        btnRead = (Button) findViewById(R.id.btnRead);
        btnWrite = (Button) findViewById(R.id.btnWrite);
        btnEnd = (Button) findViewById(R.id.btnEnd);
        
        btnRead.setEnabled(false);
        btnWrite.setEnabled(false);
        btnEnd.setEnabled(false);
        
        tvMonitor = (TextView) findViewById(R.id.tvMonitor);
        
        // [FTDriver] Create Instance
        mSerial = new FTDriver((UsbManager)getSystemService(Context.USB_SERVICE));

        // [FTDriver] setPermissionIntent() before begin()
        PendingIntent permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(
                ACTION_USB_PERMISSION), 0);
        mSerial.setPermissionIntent(permissionIntent);
        
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();

        // [FTDriver] Close USB Serial
        mSerial.end();
    }
    
    public void onBeginClick(View view) {
        // [FTDriver] Open USB Serial
        if(mSerial.begin(FTDriver.BAUD9600)) {
            btnBegin.setEnabled(false);
            btnRead.setEnabled(true);
            btnWrite.setEnabled(true);
            btnEnd.setEnabled(true);
            
            Toast.makeText(this, "connected", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "cannot connect", Toast.LENGTH_SHORT).show();
        }
    }
    
    public void onReadClick(View view) {
        int i,len;

        // [FTDriver] Create Read Buffer
        byte[] rbuf = new byte[4096]; // 1byte <--slow-- [Transfer Speed] --fast--> 4096 byte

        // [FTDriver] Read from USB Serial
        len = mSerial.read(rbuf);
        mText.append("\n-------\n");
        for(i=0; i<len; i++) {
            mText.append((char) rbuf[i]);
        }
        tvMonitor.setText(mText);
    }
    
    public void onWriteClick(View view) {
        String wbuf = "Hello! I'm jonki.";
        mSerial.write(wbuf.getBytes());
    }
    
    public void onEndClick(View view) {
        // [FTDriver] Close USB Serial
        mSerial.end();
        
        btnBegin.setEnabled(true);
        btnRead.setEnabled(false);
        btnWrite.setEnabled(false);
        btnEnd.setEnabled(false);
        
        Toast.makeText(this, "disconnect", Toast.LENGTH_SHORT).show();
    }
    
    public void onClearClick(View view) {
    	mText.setLength(0);
    	tvMonitor.setText("");
    }
}
レイアウトファイル
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

	    <Button
	      android:id="@+id/btnBegin" 
	      android:layout_width="wrap_content"
	      android:layout_height="wrap_content"
		  android:textSize="12sp"
		  android:onClick="onBeginClick"
	      android:text="Begin"/>
	    <Button
	      android:id="@+id/btnRead" 
	      android:layout_width="wrap_content"
	      android:layout_height="wrap_content"
		  android:textSize="12sp"
		  android:onClick="onReadClick"
	      android:text="Read"/>
	    <Button
	      android:id="@+id/btnWrite" 
	      android:layout_width="wrap_content"
	      android:layout_height="wrap_content"
		  android:textSize="12sp"
		  android:onClick="onWriteClick"
	      android:text="Write"/>
	    <Button
	      android:id="@+id/btnEnd" 
	      android:layout_width="wrap_content"
	      android:layout_height="wrap_content"
		  android:textSize="12sp"
		  android:onClick="onEndClick"
	      android:text="End"/>
	    <Button
	      android:id="@+id/btnClear" 
	      android:layout_width="wrap_content"
	      android:layout_height="wrap_content"
		  android:textSize="12sp"
		  android:onClick="onClearClick"
	      android:text="Clear"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >


    <TextView
        android:id="@+id/tvMonitor"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="" />

    </LinearLayout>
</LinearLayout>

まとめ

次は小さいATTinyでこれをやろうと思います。

【Arduino/Android】SoftwareSerialでAndroidとATtiny 85でLEDを2つ光らせる

さて、前回SoftwareSerialでAndroidArduinoがお話できるようになったので、Arduinoブートローダを焼いたATtiny 85を使って、かなり小さいLEDのBlinkモジュールを作りました。こんな感じにAndroid側から光らせるLEDを指定しています。

参考

ATtiny 85のArduino開発環境については下記の記事を参考に構築してみてください。

ATtiny 85のおさらい

ATtiny 85のピン配列はこんな感じでした。SoftwareSerialをPIN0とPIN1で利用しますが、PIN3とPIN4が余るのでこれを自由に使えます。今回はとりあえず2つのLEDをそれぞれ繋いでAndroid側からLEDを光らせてみます。


追記

よく考えればPIN2も開いてますね…

Arduino

糞コードですみません。とりあえずAndroid側からピン番号の文字をArduino側に送ってそれをArduino側でPIN3かPIN4を判断して、LEDを光らせています。

#include "SoftwareSerial.h"

#define StringMaxLength 32

SoftwareSerial mySerial(0, 1); // RX, TX
char data[StringMaxLength];
int counter = 0;

// the setup routine runs once when you press reset:
void setup() {                
    // initialize the digital pin as an output.
    pinMode(3, OUTPUT);
    pinMode(4, OUTPUT); 
    mySerial.begin(9600);
    mySerial.println("Hello from Arduino SoftwareSerial!");
}

// the loop routine runs over and over again forever:
void loop() {
    if(mySerial.available()) {
        data[counter] = mySerial.read();
        counter++;
        if(data[counter] == '\0' || counter == StringMaxLength) {
            data[counter] == '\0';
            mySerial.print("Hi from Arduino "); 
            mySerial.println(data);
            counter = 0;
            if(strcmp(data, "3") == 0) {
                mySerial.print("CMN3"); 
                BlinkLed(3);
            } else if(strcmp(data, "4") == 0) {
                mySerial.print("CMN4"); 
                BlinkLed(4);
            }
        }
    }
}

void BlinkLed(int led) {
    digitalWrite(led, HIGH);
    delay(1000);
    digitalWrite(led, LOW);
}