/* LEDストロボスコープ ・アナログボリュームでストロボの周期を変更。調整範囲は300〜60000rpm ・回転数は液晶に表示 ・デジタルモードにするとボタン操作でrpmを微調整。 ・スイッチでライトモードに入る。ライトモードでは明るさ調整と電池電圧表示。 ・Up/Downボタンでモードに応じた動作を行う。 ・UPボタン押しながら電源投入でDuty変更モード。DutyはEEPROMに保存 ・DNボタン押しながら電源投入で外部トリガモード by ラジオペンチ 2012/11/17 http://radiopench.blog96.fc2.com/ */ #include #include LiquidCrystal lcd(12, 11, 5, 4, 3, 2); int led = 13; int adValue = 0; // ADCの値 int UP = 9; // UPボタンはdigital 9ピン int DN = 10; // DNボタンはdigital 10ピン int sss=0; // ストロボモードの表示カウンタ int VdispInt=0; // LEDライト時の電圧表示インターバルカウンタ int sum4Buf[4] = { // ADCの値4回分の保存バッファ 0,0,0,0}; int sum4Count = 0; int sum4Value = 0; unsigned int rpm = 1000; // 回転数(デジタルモード起動時のデフォルト値) char disp[9] = { // 表示用の文字配列 "D 50000"}; int nnn=0; unsigned long interval; // 単位はμs unsigned long dispWait = 0; unsigned long incWait = 0; unsigned long beginTime; unsigned long lcdDispInterval = 100000UL; // 液晶表示インターバル(unit=us) boolean lightSW; unsigned int duty; // 点灯デューティ unsigned int timeTable[17] = { 290,419,584,815,1137,1587,2215,3091,4313,6018,8398,11718,16352,22818,31840,44430,61999 }; // べき乗カーブのテーブル unsigned long onTime; // unsigned long offTime; // boolean storoboMode; // trueならストロボ、falseならライトモード boolean modeChanged=false; void setup() { pinMode(led, OUTPUT); pinMode(6, OUTPUT); // ストロボ用LEDピン pinMode(7, INPUT); // Analog Digital切り替え LOWでデジタル digitalWrite(7,HIGH); pinMode(8, INPUT); // ストロボ/ライト モード切替スイッチ digitalWrite(8, HIGH); pinMode(UP, INPUT); // rpm UP ボタン digitalWrite(UP, HIGH); pinMode(DN, INPUT); // rpm DN ボタン digitalWrite(DN, HIGH); lcd.begin(16, 2); // Serial.begin(9800); byte gaijiUp[8]={ B00100, B01110, B10101, B00100, B00100, B00100, B00000, }; lcd.createChar(1, gaijiUp); // ↑を外字1に定義 byte gaijiDn[8]={ B00000, B00100, B00100, B00100, B10101, B01110, B00100, }; lcd.createChar(2, gaijiDn); // ↓を外字2に定義 duty=EEPROM.read(0); // EERROMからdutyの値を読み出し if((duty > 250) | (duty <20)){ // 不当な値なら (未書込みのEEPROMの値は0xFF) duty=100; // デフォルトにする } if(digitalRead(DN)==LOW){ // DNが押されていたら extTrigMode(); // 外部トリガモードを実行(このモードから復帰は無し) } if(digitalRead(UP)==LOW){ // 開始時にUPボタンが押されていたら dutySet(); // dutyの設定モードを実行(これは戻ってくる) } storoboMode = digitalRead(8); // 開始時のモードスイッチの状態取得 if(storoboMode == true){ dispStorobo(); // 画面作成 } else{ dispLight(); // 画面作成 } beginTime=micros(); } void loop() { lightSW = digitalRead(8); // モードスイッチを読んで if(lightSW != storoboMode){ // 前と変わっていたら modeChanged = true; // フラグを立て storoboMode = lightSW; } if(modeChanged == true){ // モード変更があった時だけ if(storoboMode == false){ // 画面書き換え dispLight(); } else{ dispStorobo(); } } modeChanged= false; // フラグを消す if(storoboMode == true){ storoboLight(); // ストロボのルーチン実行 } else{ flashLight(); // LEDライトのルーチン実行 } } // ストロボの処理ルーチン void storoboLight(){ digitalWrite(led, HIGH); // busy LED ON if(digitalRead(7) != LOW){ // アナログモードだったら disp[2]=' '; adValue = sum4(analogRead(5)); rpm = timeTable[(adValue >> 8)] + (((long)(adValue % 256) * (timeTable[(adValue >> 8) + 1] - timeTable[(adValue >> 8)])) >> 8); disp[0] = 'A'; // モード表示文字 if((digitalRead(UP)==LOW) && ( rpm < 32767U)) { // rpm = rpm << 1; // rpmを2倍 disp[2] = 1; // 外字の 1(↑) } if(digitalRead(DN)==LOW){ rpm = rpm >> 1; // rpmを1/2倍 disp[2]=2; // 外字の 2 (↓) } } else{ disp[0] ='D'; // モード表示文字 incWait = incWait + interval; if(incWait > 300000UL){ // ボタン押し間隔0.3秒以上確保 if(digitalRead(UP)==LOW){ rpmInc(); incWait =0; } if(digitalRead(DN)==LOW){ rpmDec(); incWait =0; } } } interval=60000000UL/rpm; // rpmの値からインターバル計算(us) dispWait = dispWait + interval; if(dispWait > lcdDispInterval){ // 動作モード変更と液晶表示 switch(nnn){ // 高速化のためswitchi文で処理 case 0: { int2chr(rpm); // rpm表示用文字列作成 lcd.setCursor(5, 1); // カーソル位置設定 break; } case 1: { lcd.print(disp[0]); // 2文字のデータ送る lcd.print(disp[1]); break; } case 2: { lcd.print(disp[2]); // 2文字のデータ送る lcd.print(disp[3]); break; } case 3: { lcd.print(disp[4]); // 2文字のデータ送る lcd.print(disp[5]); break; } case 4: { lcd.print(disp[6]); // 2文字のデータ送る lcd.print(disp[7]); break; } } // swichi文の終わり nnn++; // 書込みポインタ更新 if(nnn >=5){ nnn=0; dispWait = 0; } } offTime = beginTime + interval; onTime = offTime -interval/duty; beginTime = offTime; digitalWrite(led, LOW); // busy LED off、ここまでを1ms以内で処理する waitMicros( onTime); digitalWrite(6, HIGH); // ストロボ発光 waitMicros( offTime); digitalWrite(6, LOW); // ストロボOFF } // 過去4回分の値の合計値を求める int sum4(int data){ sum4Value = sum4Value - sum4Buf[sum4Count] + data; // 合計値の計算 sum4Buf[sum4Count] = data; // ポインタの示す配列の値を更新 sum4Count++; if( sum4Count >= 4){ // ポインタを0-3で循環させる sum4Count = 0; } return sum4Value; } // 数値を表示用の文字列に変換 void int2chr( unsigned int value){ byte xx; if(value < 10000){ disp[3]=' '; } else { xx =(value /10000); disp[3]= 0x30 | xx; } if(value < 1000) { disp[4]=' '; } else{ xx = (value / 1000) % 10; disp[4]= 0x30 | xx; } xx = (value / 100) % 10; disp[5]= 0x30 | xx; xx = (value / 10) % 10; disp[6]= 0x30 | xx; disp[7] =0x30 | (value % 10); } // 設定時刻になるまで待つ void waitMicros( unsigned long t){ while( micros() < t ){ // micros()は4μs単位の値しか返さないのを注意! } } // LEDライトモード void flashLight(){ unsigned long x, v; int batteryVoltage, t, n; boolean blinkFlag; digitalWrite(led, HIGH); // busy LED ON v = analogRead(5); v = v * v; // 変化を二乗特性にして微分コンデンサの特性を殺す x = v >> 12; // 8ビットに変換 if( x == 0){ // 消し忘れ用に最小値は1にする x =1; } lcd.setCursor(3, 1); // 出力を%で表示 v = v / 10486; // 1024*1024/100 if(v == 0){ v=1; } sss++; if(sss >= 4){ // %の更新は4回に一回 4*50=200ms sss = 0; if(v <10){ lcd.print(" "); // } lcd.print(v); lcd.print('%'); VdispInt++; if(VdispInt >=5 ){ // ちらつき防止のため電圧の更新は更に5回に一度 batteryVoltage = (long)(500UL * analogRead(4)) / 1024; // 電池電圧取得 lcd.setCursor(11,1); lcd.print(batteryVoltage/100); // 電圧表示 lcd.print("."); lcd.print((batteryVoltage /10) % 10); lcd.print(batteryVoltage % 10); VdispInt=0; } } digitalWrite(led, LOW); // busy LED off t=0; blinkFlag=false; if(digitalRead(UP)==LOW){ // Upボタンの動作を設定 t=50; blinkFlag=true; } if(digitalRead(DN)==LOW){ // Dnボタンの動作を設定 t=600; blinkFlag=true; } if(blinkFlag == true){ analogWrite(6, 0); // LED off delay(t); analogWrite(6, x); // LEDをPWMで点灯 delay(t); } else{ // Up/Dnボタンが押されていなかったら analogWrite(6, x); // LEDをPWMで点灯 delay(50); } } // LEDライトの末尾 // ストロボの表示画面作成 void dispStorobo() { lcd.clear(); lcd.print("Storobo D=1/"); disp3digits(duty); lcd.setCursor(0,1); lcd.print("Mode=A rpm"); } // ライトの表示画面作成 void dispLight(){ lcd.clear(); lcd.print("LED Light "); lcd.setCursor(0,1); lcd.print("Out**% BAT=2.41V"); } // rpmの値を増やして適当に丸める void rpmInc(){ if(rpm>=49981U){ rpm=(rpm+50)/50*50; } else if(rpm >= 19991){ rpm=(rpm+20)/20*20; } else if(rpm >= 9996){ rpm=(rpm+10)/10*10; } else if(rpm >= 4999){ rpm=(rpm+5)/5*5; } else if(rpm >= 2000){ rpm=(rpm+2)/2*2; } else { rpm++; } } // rpmの値を減らして適当に丸める void rpmDec(){ if(rpm>=50050U){ rpm=(rpm-50)/50*50; } else if(rpm >= 20020){ rpm=(rpm-20)/20*20; } else if(rpm >= 10010){ rpm=(rpm-10)/10*10; } else if(rpm >= 5005){ rpm=(rpm-5)/5*5; } else if(rpm >= 2002){ rpm=(rpm-2)/2*2; } else{ rpm--; } } // 可変抵抗の値を読んでDutyの値を決めEEPROMに保存 void dutySet(){ int adc; int x,y; lcd.clear(); lcd.print("Duty set mode"); // 開始メッセージ lcd.setCursor(0,1); lcd.print("starting..."); while(digitalRead(UP)==LOW){ // ボタンが離されるまで待つ } delay(30); lcd.clear(); lcd.print("EEP=*** set=xxx"); // 初期画面作成 lcd.setCursor(0,1); lcd.print("UP=Save, DN=Quit"); x=EEPROM.read(0); // EEPROMの値を読んで if( (x > 250) | ( x< 20) ){ // Xが不当な値なら x=100; // 100にする } lcd.setCursor(4,0); // 元の値を表示 disp3digits(x); beginTime=micros(); while((digitalRead(UP)==HIGH) && (digitalRead(DN)==HIGH)){ // UPとDNのどちらも押されてなければ digitalWrite(led, HIGH); // busy LED ON adc=analogRead(5); // ADCの値からdutyを求める y=(adc/20); if(y >50){ // 上限 y=50; } if(y <= 3){ // 下限 y=4; } y=y*5; // y (duty)の値の範囲は20〜250で刻みは5 (20以下は60000rpmの時に間に合わない) lcd.setCursor(13,0); // 更新する値を表示 disp3digits(y); offTime = beginTime + 60000U; onTime =offTime - 60000U / y; // 表示デモ用の値の準備 60000us=1000rpm beginTime = offTime; digitalWrite(led, LOW); // bysy LED OFF waitMicros(onTime); digitalWrite(6, HIGH); // 効果確認用にLED ON waitMicros(offTime); digitalWrite(6,LOW); } if(digitalRead(UP) == LOW){ // UPボタンが押されてループを抜けたのなら duty = y; // dutyに新しい値をセット EEPROM.write(0, y); // EEPROMに値を保存 lcd.clear(); lcd.print("New Duty saved"); delay(1000); while(digitalRead(UP) == LOW){ // UPボタンが離されるまで待つ } while(digitalRead(DN) == LOW){ // DNボタンが離されるまで待つ } } } // 3桁の数値をゼロサプレスで液晶に表示 void disp3digits(int x){ // 3桁の十進数をゼロサプレスで液晶に表示 if(x >= 100){ lcd.print((x / 100) % 10); // 100以上なら3桁目を表示 } else{ lcd.print(' '); // 100以下なら3桁目は空白 } if(x <10){ lcd.print(' '); // 10以下なら2桁目は空白 } else{ lcd.print((x /10) % 10); // 10以下でなければ2桁目を表示 } lcd.print( x % 10); // 1桁目を表示 } // 外部トリガモード 無限ループに入って戻ることは無い。抜けるにはリセット void extTrigMode(){ int batteryVoltage; lcd.clear(); lcd.print("Ext Trig Mode"); // 開始メッセージ lcd.setCursor(0,1); lcd.print("starting..."); while(digitalRead(DN)==LOW){ // ボタンが離されるまで待つ } delay(30); lcd.clear(); lcd.print("External Trigger"); // 初期画面作成 lcd.setCursor(0,1); lcd.print(" Battery= V"); pinMode(6, INPUT); // ストロボドライブピンを入力にアサイン digitalWrite(6, LOW); // 念のためにプルアップを解除 for(;;){ batteryVoltage = (long)(500UL * analogRead(4)) / 1024; // 電池電圧取得 lcd.setCursor(11,1); lcd.print(batteryVoltage/100); // 電圧表示 lcd.print("."); lcd.print((batteryVoltage /10) % 10); lcd.print(batteryVoltage % 10); delay(1000); // 将来的には割り込み使ってスリープに入れる } }