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