LEDの在庫が沢山あったので何か作れないかと思っていました。YouTubeにLEDを利用したPONG(テニスゲーム)が
掲載されていましたので、早速、Arduino NANOとシフトレジスタ74HC595を使って作成してみました。
ボックスの左右に取り付けたロータリーエンコーダーを操作し、両サイドの赤と緑のLED3個(ラケット)を動かして
ボールを跳ね返しています。3個のLEDの真ん中にボールが当たった時はまっすぐに跳ね返るようになっています。
1ゲームが終了するとブザーが鳴り、次のゲームに移ります。その時ボールのスピードを少しだけですがランダムに
早くしています。
以下のように順次確認して作成しました。
Arduino NANOとシフトレジスタ74HC595を1個使って、8個のLEDを順次点滅させました。
1.実験動画です。
2.回路図は次のようにしました。

3.スケッチプログラムについて
スケッチプログラムの関数shiftOut()の使用方法は以下の通りです。
shiftOut(dataPin, clockPin, bitOrder, value) (Arduino 日本語リファレンスより)
1バイト分のデータを1ビットずつ「シフトアウト」します。最上位ビット(MSB)と最下位ビット(LSB)の
どちらからもスタートできます。各ビットはまずdataPinに出力され、その後clockPinが反転して、
そのビットが有効になったことが示されます。
dataPin : 各ビットを出力するピン
clockPin : クロックを出力するピン。dataPinに正しい値がセットされたら、このピンが1回反転します
bitOrder : MSBFIRSTまたはLSBFIRSTを指定します。
MSBFIRST : Most Significant Bit Firstは最上位ビットから送ることを示します。
LSBFIRST : Least Significant Bit Firstは最下位ビットから送ることを示します。
value : 送信したいデータ (byte)
int dataPin = 8; // 74HC595のDSピンへ
int latchPin = 9; // 74HC595のST_CPピンへ
int clockPin = 10; // 74HC595のSH_CPピンへ
void setup() {
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
}
void loop() {
for (int i = 0; i < 8; i++) {
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, 1 << i);
digitalWrite(latchPin, HIGH);
delay(300);
}
}
2個のシフトレジスタ74HC595を使ってデイジーチェーン接続しています。(1個目のシフトレジスタの
Q7端子を2個目のシフトレジスタのDS端子に接続しています。)
LEDの点灯は、LED1→LED2→・・・・・→LED15→LED16→LED1→LED2→・・・・の順番で点灯します。
LED16が点灯したら、最初のLED1に戻ります。
1.実験動画です。
2.回路図です。

3.スケッチプログラムです。
int dataPin = 8; // 74HC595のDSへ
int latchPin = 9; // 74HC595のST_CPへ
int clockPin = 10; // 74HC595のSH_CPへ
int shift_val = 1;
int first_shift_val = 0;
int second_shift_val = 0;
void setup() {
Serial.begin(9600);
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
}
void loop() {
for (int i = 0; i < 16; i++) {
first_shift_val = shift_val << i;
second_shift_val = first_shift_val >> 8;
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, second_shift_val);
shiftOut(dataPin, clockPin, MSBFIRST, first_shift_val);
digitalWrite(latchPin, HIGH);
delay(300);
}
first_shift_val = 0;
second_shift_val = 0;
}
回路図は、上記「確認2」と同じです。LED1→LED2→・・・・・→LED15→LED16→LED15→LED14→・・・・LED2→LED1の
順番で点灯します。LED16が点灯したら、最初に戻らないで、逆方向にシフトしていきます。
1.実験動画です。
2.スケッチプログラムです。
int DataPin = 8;
int LatchPin = 9;
int ClockPin = 10;
int leds[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int mult[8] = {128,64,32,16,8,4,2,1};
int first_shift_val = 0;
int second_shift_val = 0;
int ball_update = 400;
int ball_location = 0;
int ball_Hdir = 1;
int loops = 0;
void setup() {
Serial.begin(9600);
pinMode(DataPin, OUTPUT);
pinMode(ClockPin, OUTPUT);
pinMode(LatchPin, OUTPUT);
}
void loop() {
digitalWrite(LatchPin, LOW);
shiftOut(DataPin,ClockPin,MSBFIRST,0);
shiftOut(DataPin,ClockPin,MSBFIRST,0);
digitalWrite(LatchPin, HIGH);
for(int i = 0; i < 8; i++){
first_shift_val += leds[i]*mult[7-i];
second_shift_val += leds[15-i]*mult[i];
}
digitalWrite(LatchPin, LOW);
shiftOut(DataPin,ClockPin,MSBFIRST,second_shift_val);
shiftOut(DataPin,ClockPin,MSBFIRST,first_shift_val);
digitalWrite(LatchPin, HIGH);
first_shift_val = 0;
second_shift_val = 0;
if(loops % ball_update == 0){
if(loops == 0){
leds[ball_location] = 1;
} else {
leds[ball_location] = 0;
if(ball_location == 0){
ball_Hdir *= -1;
ball_location = 0;
}
if(ball_location == 15){
ball_Hdir *= -1;
ball_location = 15;
}
ball_location += ball_Hdir;
leds[ball_location] = 1;
}
}
loops++;
}
ボールの部分は、LED8×16個を行列にしてあります。
使用するLEDについてですが、透明ケースのLEDは、光が拡散しないので横から見るとLEDの点滅が
見えにくいと思います。その点、不透明ケース(色付きのケース)の方は光が拡散して良く見えます。
1.実験動画です。
2.回路図です。

3.LEDの設置板の製作。
(1)3Dプリンターで次の3種類を作りました。
(a) LEDを設置する表板(黒い部分)です。

(b) 実験用のLED設置板の裏板(白い部分)です。

(c) 完成版のLED設置板の裏板(白い部分)です。ラケット用のLEDを設置します。

(2)下図のようにLEDを一定方向にして設置表板(黒い部分)の穴に挿していきます。その際少量の
接着剤をつけて挿入します。
注意:この場合は、LEDのプラスリード線は全て左側です。プラスとマイナスの方向を間違わないこと

(3)LEDのプラスの端子を根元から2mmくらいの所で折って、下図のように8個分のプラスの端子を半田付けします。
マイナスの端子は、そのまま真っすぐにしておきます。

(4)プラスの端子の半田付けを16列行い、16本のリード線を各列に半田付けします。その後、設置板の裏板(白い部分)の
スリットから全てのマイナス端子を出して、下図のように横1列を半田付けし各行に8本のリード線を半田付けします。


4.スケッチプログラムです。
int DataPin = 8;
int LatchPin = 9;
int ClockPin = 10;
int leds[8][16] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
int mult[16] = {128,64,32,16,8,4,2,1};
int first_shift_val = 0;
int second_shift_val = 0;
int C_DataPin = 5;
int C_LatchPin = 6;
int C_ClockPin = 7;
int rowbase = 255;
int row = 0;
int ball_location[2] = {0,5};
int ball_Hdir = 1;
int ball_Vdir = 1;
int ball_update = 40;
int loops = 0;
void setup() {
Serial.begin(9600);
pinMode(DataPin, OUTPUT);
pinMode(ClockPin, OUTPUT);
pinMode(LatchPin, OUTPUT);
pinMode(C_DataPin, OUTPUT);
pinMode(C_ClockPin, OUTPUT);
pinMode(C_LatchPin, OUTPUT);
}
void loop() {
if(row > 7){
row=0;
loops++;
}
while (row < 8){
digitalWrite(LatchPin, LOW);
shiftOut(DataPin,ClockPin,MSBFIRST,0);
shiftOut(DataPin,ClockPin,MSBFIRST,0);
digitalWrite(LatchPin, HIGH);
digitalWrite(C_LatchPin, LOW);
shiftOut(C_DataPin,C_ClockPin,MSBFIRST,rowbase - mult[row]);
digitalWrite(C_LatchPin, HIGH);
for(int i = 0; i < 8; i++){
first_shift_val += leds[row][i]*mult[7-i];
second_shift_val += leds[row][15-i]*mult[i];
}
digitalWrite(LatchPin, LOW);
shiftOut(DataPin,ClockPin,MSBFIRST,second_shift_val);
shiftOut(DataPin,ClockPin,MSBFIRST,first_shift_val);
digitalWrite(LatchPin, HIGH);
first_shift_val = 0;
second_shift_val = 0;
row++;
}
if(loops % ball_update == 0){
if(loops == 0){
leds[ball_location[0]][ball_location[1]] = 1;
}else{
leds[ball_location[0]][ball_location[1]] = 0;
if( ball_location[0]+ ball_Vdir == 8 || ball_location[0]+ ball_Vdir == -1){
ball_Vdir *= -1;
}
if(ball_location[1]+ ball_Hdir == 16 || ball_location[1]+ ball_Hdir == -1){
ball_Hdir *= -1;
}
ball_location[0 += ball_Vdir;
ball_location[1] += ball_Hdir;
leds[ball_location[0]][ball_location[1]] = 1;
}
}
}
1.実験動画です。
2.ロータリーエンコーダーについて
単純な3端子のロータリーエンコーダーを使用します。そのうちの 1 つを接地して、他の2 つのピンは
状態が変化し、常にハイまたはローのいずれかであるため、組み合わせは合計 4 つあります。00、01、10、および 11です。
実際にできることは、値が変化するたびに、それがどの方向に回転移動したかを確認できることです。
また、開始してからどれだけ回転移動したかを追跡できます
エンコーダーには、HIGH または LOW の 2 つのデジタル ピンがあります。ピンをバイナリとして扱う場合、
00、01、10、または 11 として読み取ります。
時計回りに回転している間にエンコーダーが出力するシーケンスは、00、01、11、10 の繰り返しです。
したがって、読み取り値が 01 の場合、次の読み取り値は、ノブを回す方向に応じて 00 または 11 になります。
以前にエンコードされた値を現在のエンコードされた値の先頭に追加することにより、8 つの可能な数字 (0001、
0010、0100、0111、1000、1011、1110 、1101) のうちの 1 つを取得します。
1110、0111、0001、1000 はすべて反時計回りです。
3.回路図です。

なお、74HC595のdataPin、latchPin、clockPinは、Arduinoのアナログピンをデジタルピンとして利用しています。
4.スケッチプログラムです。
int DataPin = 14;
int LatchPin = 15;
int ClockPin = 16;
int leds[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int mult[8] = {128,64,32,16,8,4,2,1};
int R1_shift_val = 0;
int R2_shift_val = 0;
int racket1 = 0;
int r1encA = 2;
int r1encB = 3;
int r1encA_last;
int r1encA_now;
bool racket1change = false;
int racket2 = 0;
int r2encA = 11;
int r2encB = 12;
int r2encA_last;
int r2encA_now;
bool racket2change = false;
void setup() {
pinMode(DataPin, OUTPUT);
pinMode(ClockPin, OUTPUT);
pinMode(LatchPin, OUTPUT);
pinMode(r1encA,INPUT);
pinMode(r1encB,INPUT);
digitalWrite(r1encA,HIGH);
digitalWrite(r1encB,HIGH);
r1encA_last = digitalRead(r1encA);
pinMode(r2encA,INPUT);
pinMode(r2encB,INPUT);
digitalWrite(r2encA,HIGH);
digitalWrite(r2encB,HIGH);
r2encA_last = digitalRead(r2encA);
void loop() {
leds[racket1] = 1;
leds[racket1+1] = 1;
leds[racket1+2] = 1;
leds[racket2+8] = 1;
leds[racket2+9] = 1;
leds[racket2+10] = 1;
//******************************************
digitalWrite(LatchPin, LOW);
shiftOut(DataPin,ClockPin,MSBFIRST,0);
shiftOut(DataPin,ClockPin,MSBFIRST,0);
digitalWrite(LatchPin, HIGH);
for(int i = 0; i < 8; i++){
R1_shift_val += leds[i]*mult[7-i];
R2_shift_val += leds[15-i]*mult[i];
}
digitalWrite(LatchPin, LOW);
shiftOut(DataPin,ClockPin,MSBFIRST,R2_shift_val);
shiftOut(DataPin,ClockPin,MSBFIRST,R1_shift_val);
digitalWrite(LatchPin, HIGH);
R1_shift_val = 0;
R2_shift_val = 0;
//******************* racket control*********************************
r1encA_now = digitalRead(r1encA);
if((r1encA_last == HIGH) && (r1encA_now == LOW)){
if(digitalRead(r1encB) == LOW){
if(racket1 > 0){
racket1--;
}
}else{
if(racket1 < 5){
racket1++;
}
}
racket1change = true;
}
r1encA_last = r1encA_now;
if(racket1change == true){
leds[0] = 0;
leds[1] = 0;
leds[2] = 0;
leds[3] = 0;
leds[4] = 0;
leds[5] = 0;
leds[6] = 0;
leds[7] = 0;
racket1change = false;
}
r2encA_now = digitalRead(r2encA);
if((r2encA_last == HIGH) && (r2encA_now == LOW)){
if(digitalRead(r2encB) == LOW){
if(racket2 > 0){
racket2--;
}
}else{
if(racket2 < 5){
racket2++;
}
}
racket2change = true;
}
r2encA_last = r2encA_now;
if(racket2change == true){
leds[8] = 0;
leds[9] = 0;
leds[10] = 0;
leds[11] = 0;
leds[12] = 0;
leds[13] = 0;
leds[14] = 0;
leds[15] = 0;
racket2change = false;
}
}
なお、void setup()の
digitalWrite(r1encA,HIGH);
digitalWrite(r1encB,HIGH); は、プルアップ抵抗をオンにするためのものです。
LED設置板、ロータリーエンコーダー、回路基板、ブザー、電源プラグを木製の箱に設置し完成させました。
1.回路図です。

2.全スケッチプログラムです。
int DataPin = 8;
int LatchPin = 9;
int ClockPin = 10;
int leds[8][16] = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
int R_leds[16]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int mult[16] = {128,64,32,16,8,4,2,1};
int first_shift_val = 0;
int second_shift_val = 0;
int R1_shift_val = 0;
int R2_shift_val = 0;
int C_DataPin = 5;
int C_LatchPin = 6;
int C_ClockPin = 7;
int R_DataPin = 14;
int R_LatchPin = 15;
int R_ClockPin = 16;
int rowbase = 255;
int row = 0;
int ball_pos[2] = {0,5};
int ball_Hturn = 1;
int ball_Vturn = 1;
int ball_speed = 30;
int racket1 = 0;
int r1encA = 2;
int r1encB = 3;
int r1encA_last;
int r1encA_now;
bool racket1change = false;
int racket2 = 0;
int r2encA = 11;
int r2encB = 12;
int r2encA_last;
int r2encA_now;
bool racket2change = false;
int buzzer = 4;
int repeat = 0;
bool gameover = false;
int gameover_start_timer = 0;
int gameover_end_timer = 0;
void setup() {
Serial.begin(9600);
pinMode(DataPin, OUTPUT);
pinMode(LatchPin, OUTPUT);
pinMode(ClockPin, OUTPUT);
pinMode(C_DataPin, OUTPUT);
pinMode(C_LatchPin, OUTPUT);
pinMode(C_ClockPin, OUTPUT);
pinMode(R_DataPin, OUTPUT);
pinMode(R_LatchPin, OUTPUT);
pinMode(R_ClockPin, OUTPUT);
pinMode(r1encA,INPUT);
pinMode(r1encB,INPUT);
digitalWrite(r1encA,HIGH);
digitalWrite(r1encB,HIGH);
r1encA_last = digitalRead(r1encA);
pinMode(r2encA,INPUT);
pinMode(r2encB,INPUT);
digitalWrite(r2encA,HIGH);
digitalWrite(r2encB,HIGH);
r2encA_last = digitalRead(r2encA);
pinMode(buzzer,OUTPUT);
digitalWrite(buzzer,LOW);
}
void loop() {
R_leds[racket1] = 1;
R_leds[racket1 + 1] = 1;
R_leds[racket1 + 2] = 1;
R_leds[racket2 + 8] = 1;
R_leds[racket2 + 9] = 1;
R_leds[racket2 + 10] = 1;
//**********R_RACKET LED****************
digitalWrite(R_LatchPin, LOW);
shiftOut(R_DataPin,R_ClockPin,MSBFIRST,0);
shiftOut(R_DataPin,R_ClockPin,MSBFIRST,0);
digitalWrite(R_LatchPin, HIGH);
for(int i = 0; i < 8; i++){
R1_shift_val += R_leds[i]*mult[7-i];
R2_shift_val += R_leds[15-i]*mult[i];
}
digitalWrite(R_LatchPin, LOW);
shiftOut(R_DataPin,R_ClockPin,MSBFIRST,R2_shift_val);
shiftOut(R_DataPin,R_ClockPin,MSBFIRST,R1_shift_val);
digitalWrite(R_LatchPin, HIGH);
R1_shift_val = 0;
R2_shift_val = 0;
//**********Ball LED****************
if(row > 7){
row = 0;
repeat++;
}
while (row < 8){
digitalWrite(LatchPin, LOW);
shiftOut(DataPin,ClockPin,MSBFIRST,0);
shiftOut(DataPin,ClockPin,MSBFIRST,0);
digitalWrite(LatchPin, HIGH);
digitalWrite(C_LatchPin, LOW);
shiftOut(C_DataPin,C_ClockPin,MSBFIRST,rowbase - mult[row]);
digitalWrite(C_LatchPin, HIGH);
for(int i = 0; i < 8; i++){
first_shift_val += leds[row][i]*mult[7-i];
second_shift_val += leds[row][15-i]*mult[i];
}
digitalWrite(LatchPin, LOW);
shiftOut(DataPin,ClockPin,MSBFIRST,second_shift_val);
shiftOut(DataPin,ClockPin,MSBFIRST,first_shift_val);
digitalWrite(LatchPin, HIGH);
first_shift_val = 0;
second_shift_val = 0;
row++;
}
//**********RACKET Control****************************
if (gameover != true){
r1encA_now = digitalRead(r1encA);
if((r1encA_last == HIGH) && (r1encA_now == LOW)){
if(digitalRead(r1encB) == LOW){
if(racket1 > 0){
racket1--;
}
}else{
if(racket1 < 5){
racket1++;
}
}
racket1change = true;
}
r1encA_last = r1encA_now;
if(racket1change == true){
for(int i = 0; i < 8; i++){
R_leds[i] = 0;
}
racket1change = false;
}
}
if (gameover != true){
r2encA_now = digitalRead(r2encA);
if((r2encA_last == HIGH) && (r2encA_now == LOW)){
if(digitalRead(r2encB) == LOW){
if(racket2 > 0){
racket2--;
}
}else{
if(racket2 < 5){
racket2++;
}
}
racket2change = true;
}
r2encA_last = r2encA_now;
if(racket2change == true){
for(int i = 8; i < 16; i++){
R_leds[i] = 0;
}
racket2change = false;
}
}
//*************Ball movement****************
if(gameover != true){
if(repeat % ball_speed == 0){
if(repeat==0){
leds[ball_pos[0]][ball_pos[1]] = 1;
}else{
leds[ball_pos[0]][ball_pos[1]] = 0;
if( ball_pos[0] + ball_Vturn == 8 || ball_pos[0] + ball_Vturn == -1){
ball_Vturn *= -1;
}
if(ball_pos[1] == 0){
if(ball_pos[0] == racket1){
ball_Hturn *= -1;
ball_Vturn = 1;
}else if(ball_pos[0] == (racket1 + 1)){
ball_Hturn *= -1;
ball_Vturn = 0;
}else if(ball_pos[0] == (racket1 + 2)){
ball_Hturn *= -1;
ball_Vturn = -1;
}else{
gameover = true;
digitalWrite(buzzer,HIGH);
delay(500);
digitalWrite(buzzer,LOW);
ball_pos[0] = 0;
ball_pos[1] = 5;
ball_Hturn = 1;
ball_Vturn = 1;
ball_speed = random(15,31);
repeat = 0;
}
}
if(ball_pos[1] == 15){
if(ball_pos[0] == racket2){
ball_Hturn *= -1;
ball_Vturn = 1;
}else if(ball_pos[0] == (racket2 + 1)){
ball_Hturn *= -1;
ball_Vturn = 0;
}else if(ball_pos[0] == (racket2 + 2)){
ball_Hturn *= -1;
ball_Vturn = -1;
}else{
gameover = true;
digitalWrite(buzzer,HIGH);
delay(500);
digitalWrite(buzzer,LOW);
ball_pos[0] = 7;
ball_pos[1] = 11;
ball_Hturn *= -1;
ball_Vturn = -1;
ball_speed = random(15,31);
repeat = 0;
}
}
if(gameover != true){
ball_pos[0] += ball_Vturn;
ball_pos[1] += ball_Hturn;
}
leds[ball_pos[0]][ball_pos[1]] = 1;
}
}
}
if(gameover == true){
if (gameover_start_timer == 0 ){
gameover_start_timer = repeat;
gameover_end_timer = repeat + 1000;
}else if(repeat >= gameover_end_timer){
gameover = false;
gameover_start_timer = 0;
}
}
}
LED設置板3種類のSTLファイル「LED_PONG1」、「LED_PONG2」、「LED_PONG3」のダウンロードです。
なお、「LED_PONG3」は実験用のもので、完成版は「LED_PONG1」、「LED_PONG2」を使用します。
Download