/*********************************/ /* 弾性衝突をLEDテープで */ /*********************************/ // フリーソフトとしてご自由にどうぞ。 // 最大8個の衝突体が走ります。 // LED制御用出力 = D12 // D10を入力にして点灯/消灯 Hなら点灯  Lなら消灯 // D11で明暗制御 H:明るく L:夜間用にちょい暗 // LEDだけでなくシリアルにも出力しているので雰囲気がわかるかと // 115.2kBPSで 1行144文字出すので横幅を広げて // デバッグ用パルス出力 // D8 25ms(40Hz)ごと衝突体を進めるタイミング // D9 シリアル出力データ と LED表示データ作成時間チェック // シリアル出力中もHに(64文字のバッファリングの様子が見える) // LED制御ライブラリ #include #define LED_PIN 12 // LED信号出力端子のピン番号 #define LED_SU 144 // LED個数 144個 Adafruit_NeoPixel LED = Adafruit_NeoPixel(LED_SU, LED_PIN, NEO_GRB + NEO_KHZ800); // ポートH/L (タイミングチェック) #define D8_H (PORTB |= (1 << PB0)) // D8 (PB0) H/L #define D8_L (PORTB &= ~(1 << PB0)) #define D9_H (PORTB |= (1 << PB1)) // D9 (PB1) H/L #define D9_L (PORTB &= ~(1 << PB1)) // 配列のデータ数を返すマクロ #define DIMSIZ(a) (sizeof(a)/sizeof(*a)) /***** 衝突体のデータ *****/ #define OBJ_SU 8 // 衝突体の数 // もっと多くてもかまわないが,見栄えが煩雑になる感じ typedef struct{ float v; // 速度 マイナスは逆方向 毎秒あたりのdot数 byte m; // 質量 (1~6) 0は衝突体なしを示す float x; // 先頭X位置(vで累積) short x1; // 先頭表示位置 0が出発点 short x2; // 後端位置 x1 - (m - 1) byte c; // 色データ(0~255) }ST_OBJ; ST_OBJ obj[OBJ_SU]; // 衝突体データ byte obj_color; // 衝突体の色 0~255 色相環で // 次色を保持 byte obj_mass; // 直前の衝突体質量 // 同じ値のを続けたくないので比較用 const float obj_m[] = { // 衝突体の実質量 1.0, // 1 m値 衝突計算で使用する 3.0, // 2 m値がLEDの数 6.0, // 3 衝突時の質量をこのテーブルで変換 12.0, // 4 24.0, // 5 48.0, // 6 衝突体の最大LED数 }; /***** タイマーデータ *****/ volatile byte f_push; // 衝突体押し出しタイミング volatile byte tm_newobj; // 新衝突体作成チェック用タイマー 0.1秒 volatile byte tm_stopck; // 衝突体の止まりかけチェック用  0.1秒 volatile byte tm_disp; // 表示実行タイマー (1秒) // P10がLなら表示停止 /***** 衝突体の数を数える *****/ byte cntobj(void) { byte i; byte cnt = 0; for(i = 0; i < OBJ_SU; i++){ // loop if(obj[i].m) cnt++; // 質量ゼロ以外 else break; } return cnt; } /***** 衝突後の速度変化計算 *****/ // n1,n2 : 衝突体番号 void collis(byte n1, byte n2) { float v1, m1; float v2, m2; if((obj[n1].m != 0) && // 両方質量がゼロでない (obj[n2].m != 0)){ v1 = obj[n1].v; // 衝突体速度 v2 = obj[n2].v; m1 = obj_m[obj[n1].m - 1]; // 衝突体質量 m2 = obj_m[obj[n2].m - 1]; obj[n1].v = (((m1-m2)*v1) + (2*m2*v2)) / (m1+m2); // 新速度を計算 obj[n2].v = (((m2-m1)*v2) + (2*m1*v1)) / (m1+m2); } } /***** 新しい衝突体を作成 *****/ // 質量の大小で速度設定を変える // 重いのを続けたくないので前回値とチェック // n : 衝突体の番号 0~7(max) void newobj(byte n) { byte m, v0, v1; float v; // 質量 m m = 1 + random(DIMSIZ(obj_m)); // 質量1~6で6種類 if(obj_mass > (DIMSIZ(obj_m) / 2)){ // 前回が重い時 m = (m + 1) / 2; // 新質量1~3に制限 } obj_mass = m; // 今回の質量値を保存 // 速度 v if(n == 0){ // 先頭(0) v0 = 5; // ゆっくりで v1 = 10; } else if(n < (OBJ_SU / 2)){ // 4まで(1,2,3)の時 v0 = 5; // ちょっと早く v1 = 15; if(obj[n-1].x2 >= (LED_SU / 2)){ // 最後尾が1/2越え v0 = 30; // もっと早く v1 = 50; } } else{ // 数が4以上(4,5,6,7)の時 if(m > (DIMSIZ(obj_m) / 2)){ // 重い時(4,5,6) v0 = 5; v1 = 15; } else{ // 軽い時(1,2,3) v0 = 20; // 早く v1 = 40; } if(obj[n-1].x2 >= (LED_SU / 3)){ // 最後尾が1/3越え v0 = 25; // 質量に関係なくもっと早く v1 = 50; } } v = random(v0, v1 + 1); // 速度を乱数で // 新衝突体データ設定 obj[n].m = m; // 質量設定 obj[n].v = v; // 速度設定 obj[n].x = 0.0; // 先頭位置(floatで) obj[n].x1 = (short)obj[n].x; // 先頭座標 obj[n].x2 = obj[n].x1 - obj[n].m + 1; // 後端座標 obj[n].c = obj_color; // 色指定0~255 obj_color = (byte)(obj_color + random(30, 90 + 1)); // 色相環上で次色 } /***** 衝突体を消す *****/ // 位置がマイナス、あるいはLED_SUを越えたら // 後ろのデータを前に詰める void delobj(byte n) { byte i; for(i = n; i < (OBJ_SU - 1); i++){ obj[i] = obj[i + 1]; // 前詰め } obj[OBJ_SU - 1].m = 0; // 最後は質量=0にして無しに } /***** 衝突体を進める *****/ // f_pushフラグ 25msタイムアップで処理 // 速度に従ってx位置 x1,x2を設定 void pushobj(void) { byte i, n; float x0, x1; n = cntobj(); // 衝突体の数 // 速度で位置計算 for(i = 0; i < n; i++){ // loop x1 = obj[i].x + (obj[i].v * 0.025); // 25ms(40Hz)単位で速度から位置を計算 if(i != 0){ // 先頭じゃない時 x0 = obj[i - 1].x - (float)obj[i - 1].m; // 前の後端 if(x1 >= x0){ // 衝突! x1 = x0; // 前の後端を先端に collis(i - 1, i); // 衝突処理 } } obj[i].x = x1; // 現在位置 obj[i].x1 = (short)x1; // 先端 obj[i].x2 = obj[i].x1 - obj[i].m + 1; // 後端 } // 後端がLED_SUを越えた、前端が0未満になったら消去 for(i = 0; i < OBJ_SU; i++){ // X1とX2が範囲内? if(obj[i].m){ // 質量ゼロは処理しない if(obj[i].x2 >= LED_SU){ // 後端がLED数を越えた delobj(i); // 範囲を越えたら消す } else if (obj[i].x1 < 0){ // バックして先端が0未満になった delobj(i); // 範囲外は消す tm_newobj = random(3, 7 + 1); // 次までの時間 0.3~0.7秒 } } } } /***** 32bit色データ変換 *****/ // RGBデータをまとめて32bitに uint32_t ledColor(uint8_t r, uint8_t g, uint8_t b) { return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; } /***** 色相環データ取得 *****/ // WheelPos:0~255 uint32_t ledWheel(byte WheelPos) { WheelPos = 255 - WheelPos; if(WheelPos < 85) { return ledColor(255 - WheelPos * 3, 0, WheelPos * 3); } if(WheelPos < 170) { WheelPos -= 85; return ledColor(0, WheelPos * 3, 255 - WheelPos * 3); } WheelPos -= 170; return ledColor(WheelPos * 3, 255 - WheelPos * 3, 0); } /****************************/ /* 40Hzタイマー割り込み */ /****************************/ // LED.Show()中は割り込みが禁止されている /***** 25msタイマー割り込み実行 *****/ // タイマー1のコンペアA割り込み ISR(TIMER1_COMPA_vect) { static byte cnt4 = 0; // 100ms計時 : 25ms x 4回 static byte cnt40 = 0; // 表示実行タイマー計時用(1秒) 25ms x 40回 // 0.1秒タイマー cnt4++; // 100ms計時 if(cnt4 >= 4){ // 25ms x 4回 cnt4 = 0; if(tm_newobj) tm_newobj--; // 新衝突体作成タイミング if(tm_stopck) tm_stopck--; // 止まりかけチェック } // 1秒タイマー cnt40++; // 1秒計時用 if(cnt40 >= 40){ // 25ms x 40回 cnt40 = 0; if(tm_disp) tm_disp--; // 表示実行用タイマー } // 押し出しタイミング 40Hz=25ms f_push = 1; // 押し出しフラグをon } /**************************/ /* シリアル入出力 */ /**************************/ /***** 書式付シリアル出力 *****/ void txprintf(const char *s, ...) { va_list vp; char bff[64]; // バッファを確保 va_start(vp, s); vsnprintf(bff, sizeof(bff), s, vp); Serial.print(bff); va_end(vp); } /***** メッセージ出力 *****/ void txputs_P(PGM_P s) { char c; while(1){ c = pgm_read_byte(s); if(c == '\x0') break; Serial.write(c); s++; } } /***** xxx.xx速度値出力 *****/ void tx4r2(float d) { char bff[10]; dtostrf(d, 7, 2, bff); Serial.print(bff); } /***** 衝突体データ出力 *****/ // データ番号、質量、速度、X位置、先端、後端位置 void txobj(byte n) { txprintf("#%d %d ", n ,obj[n].m); tx4r2(obj[n].v); tx4r2(obj[n].x); txprintf(" %3d %3d\r\n", obj[n].x1, obj[n].x2); } /************************/ /* SETUP */ /************************/ // ※メモ // Mstimer2はタイマー2のオーバーフロー割り込みを用いて1ms周期の // 割り込みサイクルを得ている。 // 設定を10msにした場合でも1msごとに割り込み処理が行われ // ていて、10msなら10回目に設定した割り込みルーチンがコールされる。 // 今回のLED表示ルーチンのように割り込み禁止時間が長い場合、 // バックグランドの1ms割り込み処理が抜けるので計時が遅れる。 /***** セットアップ *****/ void setup() { pinMode(8, OUTPUT); // D8 test pulse pinMode(9, OUTPUT); // D9 test pulse pinMode(10, INPUT_PULLUP); // D10 消灯/点灯(H) pinMode(11, INPUT_PULLUP); // D11 暗 / 明 (H) // D12がLED出力 // シリアル Serial.begin(115200); // 115.2kBPSで // タイマー1 40Hzで割り込み TCCR1A = 0b00000000; // |||| ++---- WGM10,11 CTCモード // ||++-------- COM1B // ++---------- COM1A TCCR1B = 0b00001011; // || ||+++---- Clk Ssel 16MHz / 64 250kHz // || ++------- WGM13,12 CTCモード // |+---------- ICES1 // +----------- ICNC1 OCR1A = 6250 - 1; // 250kHz / 6250 = 40Hz TIMSK1 = 0b00000010; // +----- OCIE1A コンペアマッチA割り込み有効 // タイマー0割り込み禁止 TIMSK0 = 0b00000000; // ||+---- TOIE0 オーバーフロー割り込みなしに // |+----- OCIE0A // +------ OCIE0B // 開始メッセージ txputs_P(PSTR("led_collision1a / 2021-04-19\r\n")); // LED制御 LED.begin(); LED.show(); // オフ状態で表示 } /************************/ /* LOOP */ /************************/ /***** ループ *****/ void loop() { short i, j; byte n; // 衝突体数 byte f; // 新衝突体作成指令フラグ byte c; // 色相環データ char bff[LED_SU + 2]; // シリアル出力用 while(1){ if(f_push){ // 衝突体押す出しタイミング(50Hz) f_push = 0; // 50Hz=20msで D8_H; // (!!!) pushobj(); // 衝突体を進める D8_L; // (!!!) // 新衝突体の作成チェック n = cntobj(); // 衝突体の数 f = 0; // 新衝突体作成フラグ if(n == 0){ // 衝突体の数がゼロ f = 1; // 無条件に新作成 } else{ // すでに衝突体がある if(obj[n - 1].x2 >= 5){ // 最後尾が5離れた f = 1; // 新衝突体作成 } else{ // 最後尾が0~4にある if(fabs(obj[n - 1].v) < 2.0){ // 止まりかけている if(tm_stopck == 0){ // タイムアップした obj[n - 1].v = -10.0; // 高速でバックさせて消す } } } } if(f){ // 新衝突体作成指令あり tm_stopck = 20; // 止まりかけチェックタイマー2.0秒 if((n < OBJ_SU) && // 最大数まで余裕あり? (tm_newobj == 0)){ // タイムアップした? newobj(n); // 新衝突体作成 // txprintf("%d:%3d\r\n", n, obj_color); // 色を出力 if(n < (OBJ_SU / 2)){ // 全体数の1/2までなら tm_newobj = random(3, 10 + 1); // 次までの時間 0.3~1.0秒 } else{ // 1/2以上なら tm_newobj = random(5, 20 + 1); // 次までの時間 0.3~1.2秒 } n++; // 衝突体数+1 } } // シリアル出力データ+LED表示データ作成 D9_H; // (!!!) if(digitalRead(11) == HIGH) // D11で明暗区分 LED.setBrightness(80); // LED明るさ昼間用に明るく else LED.setBrightness(40); // 夜用に暗く LED.clear(); // LEDバッファクリア memset(bff, ' ', LED_SU); // bffをスペースで埋める bff[LED_SU] = '|'; // endを示す bff[LED_SU+1] = '\x0'; // 最後はnull // D10入力で表示の有無 if(digitalRead(10) == HIGH) tm_disp = 30; // Hで点灯処理 30秒 if(tm_disp != 0){ // P10入力がLを継続していたら消灯 for(i = 0; i < n; i++){ // 衝突体情報 // txobj(i); // それぞれの質量,速度などシリアル出力 for(j = obj[i].x2; j <= obj[i].x1; j++){ if((j >= 0) && (j < LED_SU)){ c = obj[i].c; // 色指定 0~255 LED.setPixelColor(j, ledWheel(c)); // 色相環で bff[LED_SU - 1 - j] = '!' + (c * 100 / 273); // 衝突体色をで示す } } } } D9_L; // (!!!) // 出力 LED.show(); // LED出力 D12 pinが制御ポート(4.3ms) D9_H; // (!!!) Serial.println(bff); // 1行シリアル出力(12.5ms) D9_L; // (!!!) } } } /*==== end of "led_collision1a.ino" ====*/