はじめに
この記事は、私が所属する技術系サークルである「いちぴろ・エクスプローラ」内で開催する「Processing&Arduino勉強会」用の資料です。
ProcessingとArduinoを組み合わせて、簡易的なゲームを作成する3記事構成の資料の3作目です。
1作目:Processing を使ってみよう!
2作目:Arduino を使ってみよう!
3作目:Processing と Arduino を組み合わせてみよう!(この記事)
今までの知識を組み合わせて、センサー入力をリアルタイムに可視化したり、PC 側から Arduino を操作したりと、電子工作とプログラミングの境界を越えた面白い体験をしてみましょう。
本記事では、以下の3ステップで Processing × Arduino の世界を体験します。
- シリアル通信の基礎(双方向通信)
- 半固定抵抗(ポテンショメータ)を使ったアナログ入力の可視化
- 応用:Processing 側で動く簡易ゲームを Arduino で操作する
1, シリアル通信の基礎
Arduino と Processing の連携は、USB 経由の シリアル通信 を使います。
1.1 Arduino → Processing:センサー値を送る
Arduino 側でセンサー値を読み取り、1 バイトのデータとして送信します。
アナログ入力から読み取った半固定抵抗の値を、Serial.write() で送信しましょう。
const int analogPin = A0; // アナログ入力ピン
void setup() {
Serial.begin(9600); // シリアル通信を初期化
}
void loop() {
int sensorValue = analogRead(analogPin); // アナログ値を読み取る
// 一度に送れるデータ(1byte)に変換
sensorValue = map(sensorValue, 0, 1023, 0, 255);
// シリアルデータ送信
Serial.write(sensorValue);
delay(200); // 200ms待機
}
import processing.serial.*;
Serial port;
float x;
float y;
int in_data;
void setup() {
size(300, 300);
print("Serial Port list: ");
println(Serial.list());
// 複数ある場合は、Arduino IDE側が認識しているポートを選択してください
String portName = Serial.list()[0];
port = new Serial(this, portName, 9600);
background(0, 0, 0);
}
void draw() {
// 描画エリア設定
fill(255, 10);
noStroke();
rect(0, 0, width, height);
// シリアルポートからデータを受け取ったら
if (port.available() > 0 ) {
// シリアルデータ受信
in_data = port.read();
// 描画
x = width / 2 + random(-3, 3);
y = height / 2 + random(-3, 3);
noFill();
stroke(random(255), random(255), 255);
ellipse(x, y, in_data, in_data);
}
}
Serial.write()によってArduinoから1バイト分のデータを送り、port.read()によって、シリアルバッファに蓄えられた1バイト分のデータを得ます。
analogRead()の解像度は10ビットで、0~1023の1024段階で入力電圧の高さを表現します。analogRead()の値をそのままSerial.write()に渡すと1バイトの表現力(0~255)を超えてしまうため、map()を使用しました。
map()はある範囲の値を別の範囲に線形に変換するための組み込み関数です。今回は、0~1023の範囲にある値sensorValueを、0~255の範囲に縮小して収める。という使い方をしました。
実行結果のウィンドウはこのような感じになっていると思います。アナログ値が増加するに従って輪のサイズが変わっていることを確認しましょう。
もし、動かない場合はArduino側でシリアルモニタを立ち上げている場合があります。閉じてからProcessingのプログラムを再実行してください。

Multi-function Shieldを使っている場合、可変抵抗のつまみを回しても変化の幅が小さいので、map関数の処理を消してしまったほうが分かりやすいかもしれません。
1.2 Processing → Arduino:PC から Arduino を操作
Processing 側から Arduino に 1 バイトのデータを送ることで、LED の ON/OFF を制御できます。
import processing.serial.*;
Serial port;
void setup() {
size(300, 300);
print("Serial Port list: ");
println(Serial.list());
// 複数ある場合は、Arduino IDE側が認識しているポートを選択してください
String portName = Serial.list()[0];
port = new Serial(this, portName, 9600);
}
void draw() {
if (mousePressed && mouseButton == LEFT) {
port.write('1'); // LED ON
}
if (mousePressed && mouseButton == RIGHT) {
port.write('0'); // LED OFF
}
}
#define led LED_BUILTIN // 内蔵LED
void setup() {
Serial.begin(9600);
pinMode(led, OUTPUT); // LEDを出力用に設定
}
void loop() {
// シリアルポートからデータを受け取ったら
if (Serial.available() > 0) {
// 受信したデータを読み込む
char data = Serial.read();
// データが'1'ならLED点灯
if (data == '1') {
digitalWrite(led, HIGH);
}
// データが'0'ならLED消灯
if (data == '0') {
digitalWrite(led, LOW);
}
}
}
では、このサンプルを使って、矢印キーに応じて、シールド上のLED:D1,D2,D3,D4が光る用にプログラムを書き換えてみましょう!
想定回答
import processing.serial.*;
Serial port;
void setup() {
size(300, 300);
print("Serial Port list: ");
println(Serial.list());
// 複数ある場合は、Arduino IDE側が認識しているポートを選択してください
String portName = Serial.list()[0];
port = new Serial(this, portName, 9600);
}
void draw() {
if (keyPressed == false) return;
background(200);
if (keyCode == LEFT) {
port.write('L'); // D1 ON
println("L");
triangle(120, 100, 120, 200, 50, 150);
square(120, 120, 60);
}
if (keyCode == RIGHT) {
port.write('R'); // D2 ON
println("R");
triangle(180, 100, 180, 200, 250, 150);
square(120, 120, 60);
}
if (keyCode == UP) {
port.write('U'); // D3 ON
println("U");
triangle(100, 120, 200, 120, 150, 50);
square(120, 120, 60);
}
if (keyCode == DOWN) {
port.write('D'); // D4 ON
println("D");
triangle(100, 180, 200, 180, 150, 250);
square(120, 120, 60);
}
}
#define led1 13 // 内蔵LED
#define led2 12
#define led3 11
#define led4 10
void setup() {
Serial.begin(9600);
pinMode(led1, OUTPUT); // LEDを出力用に設定
pinMode(led2, OUTPUT); // LEDを出力用に設定
pinMode(led3, OUTPUT); // LEDを出力用に設定
pinMode(led4, OUTPUT); // LEDを出力用に設定
}
void loop() {
// シリアルポートからデータを受け取ったら
if (Serial.available() > 0) {
// 受信したデータを読み込む
char data = Serial.read();
// データが'L'ならD1点灯
if (data == 'L') {
digitalWrite(led1, LOW);
digitalWrite(led2, HIGH);
digitalWrite(led3, HIGH);
digitalWrite(led4, HIGH);
}
// データが'R'ならD2点灯
if (data == 'R') {
digitalWrite(led1, HIGH);
digitalWrite(led2, LOW);
digitalWrite(led3, HIGH);
digitalWrite(led4, HIGH);
}
// データが'U'ならD3点灯
if (data == 'U') {
digitalWrite(led1, HIGH);
digitalWrite(led2, HIGH);
digitalWrite(led3, LOW);
digitalWrite(led4, HIGH);
}
// データが'D'ならD4点灯
if (data == 'D') {
digitalWrite(led1, HIGH);
digitalWrite(led2, HIGH);
digitalWrite(led3, HIGH);
digitalWrite(led4, LOW);
}
}
}
シールド上のLEDは出力ポートをLOWにすると点灯し、HIGHにすると消灯することに気を付けましょう。
参考:ArduinoとProcessingを連携させてシリアルデータをやり取りする
1.3 複数の半固定抵抗を使ったアナログ入力
3つの半固定抵抗を使い、Processingの背景色をリアルタイムに変化させましょう。Multi-function Shield装着時には実行できないので、ブレッドボードを使って電子工作してみましょう。
const int redPin = A0;
const int greenPin = A1;
const int bluePin = A2;
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.print(analogRead(redPin));
Serial.print(",");
Serial.print(analogRead(greenPin));
Serial.print(",");
Serial.println(analogRead(bluePin));
}
import processing.serial.*;
Serial myPort;
float r, g, b;
void setup() {
size(300, 300);
myPort = new Serial(this, Serial.list()[0], 9600);
myPort.bufferUntil('\n');
}
void draw() {
background(r, g, b);
}
void serialEvent(Serial p) {
String inString = trim(p.readStringUntil('\n'));
float[] colors = float(split(inString, ","));
if (colors.length == 3) {
r = map(colors[0], 0, 1023, 0, 255);
g = map(colors[1], 0, 1023, 0, 255);
b = map(colors[2], 0, 1023, 0, 255);
}
}
半固定抵抗を回すと、Processingの背景色が滑らかに変化します。
参考:Processing 入門 Lesson 06 【Arduino連携編 その6】
2, Arduinoで操作するProcessingゲームを作ろう
最後に、Processing 側で動く簡単なゲームを作り、Arduinoで操作してみましょう。
ゲーム内容は、落ちてくるボールからバーを半固定抵抗の操作で左右に避けていくものです。
- Arduino側
- A0: 半固定抵抗で横操作
- Processing側
- 上からボールをランダムに落とす
- 当たるとゲームオーバー
- 生存時間で競う
void setup() {
Serial.begin(9600);
}
void loop() {
int val = analogRead(A0);
Serial.println(val);
delay(20);
}
import processing.serial.*;
Serial port;
float playerX;
float enemyX, enemyY;
int score = 0;
void setup() {
size(400, 600);
port = new Serial(this, Serial.list()[0], 9600);
port.bufferUntil('\n');
resetEnemy();
textSize(32);
}
void draw() {
background(30);
// プレイヤー
fill(0, 200, 255);
rect(playerX - 20, height - 40, 40, 20);
// 敵
fill(255, 80, 80);
ellipse(enemyX, enemyY, 30, 30);
enemyY += 4;
// 敵が下に到達 → リセット
if (enemyY > height) {
resetEnemy();
score++;
}
// 当たり判定
if (dist(playerX, height - 40, enemyX, enemyY) < 30) {
gameOver();
}
fill(255);
text("Score: " + score, 20, 40);
}
void serialEvent(Serial p) {
String s = trim(p.readStringUntil('\n'));
int v = int(s);
playerX = map(v, 0, 1023, 20, width - 20);
}
void resetEnemy() {
enemyX = random(20, width - 20);
enemyY = -20;
}
void gameOver() {
noLoop();
fill(255, 0, 0);
text("GAME OVER", 100, 300);
}
半固定抵抗を回すと、Processingの画面上でバーが左右に動きます。このようにセンサーの入力情報に基づいて画面描画を行い、ゲームを作成できました。
Multi-function Shieldを使っている場合、可変抵抗のつまみを回しても変化の幅が小さいので、全然バーが移動できないゲームになっているかと思います。Arduino側の処理を変えてもっと楽しいゲームにしてみましょう。
想定回答
つまみ一回りでどれくらい値が変化するかを出力させて確認しましょう。 もし、480~500の範囲で変化するとき、この値を本来の0~1023の範囲に拡大することで、扱いやすいゲームになります。void setup() {
Serial.begin(9600);
}
void loop() {
int val = analogRead(A0);
val = min(val, 500);
val = max(val, 480);
val = map(val, 480, 500, 0, 1023);
Serial.println(val);
delay(20);
}
まとめ
Processing&Arduino勉強会の3記事で、それぞれを連携させて「物理世界」と「デジタル世界」をつなぐインタラクティブな作品が簡単に作れることが分かったと思います。
この勉強会の内容を深めたら、ピンポンゲームやオシロスコープなど、さらに高度な作品を作ることができます。
今度は皆さんで思い思いの作品を作ってみてください。