/* 簡易パルスジェネレーター(timer1割り込みで動作) * 設定範囲 パルス幅:40μs〜9.999s、周期:80μs〜9.999s * 2016/03/13 ラジオペンチ http://radiopench.blog96.fc2.com/ */ #include #include #include #define ECA 9 // エンコーダーA相 = D9 #define ECB 10 //       B相 = D10 LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // 標準LCDシールド long t1 = 5000; // パルスON時間 long t2 = 10000; //    周期 long t3; // off時間 int IncStep; // エンコーダ1クリックの最小ステップ long X; // ロータリーエンコーダーの値 int mode; // モード boolean PosiPulse; // 出力の極性 true でポジ出力 long duty1000; // Dutyの千倍の値(0.1%単位) long freq100; // 周波数の100倍の値 void setup() { Serial.begin(9600); pinMode(13, OUTPUT); pinMode(ECA, INPUT_PULLUP); // エンコーダーA相 pinMode(ECB, INPUT_PULLUP); // エンコーダーB相 pinMode(8, INPUT_PULLUP); // 早送りボタン = D8 pinMode(7, INPUT_PULLUP); // セレクトボタン = D7 pinMode(6, INPUT_PULLUP); // 出力極性ボタン = D6 lcd.begin(16, 2); defGaiji(); // 液晶の外字を定義 EepLoad(); // EEPROMの値を読んでt1,t2,極性フラグを設定 lcd.clear(); if (PosiPulse == true) { // 出力の極性を表示 lcd.print("P"); } else { lcd.print("N"); } fix4Disp(1, 0, t1); // パルス幅表示 fix4Disp(9, 0, t2); // パルス周期表示 Timer1.initialize(10000); // 10ms後に割込みループ開始 Timer1.attachInterrupt(T1Isr); // Timer1割込み開始 } void loop() { duty50(); // ボタンが押されていたらパルス幅を50%デューティにする EepSave(); // ボタンが押されていたらEEPROMへ設定内容を保存する setPorarity(); // パルス極性設定 mode = ModeSelect(); // ロータリーエンコーダー設定する対象の選択(パルス幅or周期) X = ReadEnc(); // ロータリーエンコーダーを読む switch (mode) { case 0: // パルス幅設定 IncStep = DecideStep(t1); t1 += X * IncStep; t1 = ResoADJ(t1); // 有効数字を補正 if (t1 < 40) { // t1(パルス幅)が40μs以下なら t1 = 40; // t1の下限は40μs } if ((t2 - t1) < 40) { // t2とt1の差が40μs以下なら t1 = t2 - 40; // t1の上限はt2から40μs下 } fix4Disp(1, 0, t1); // パルス幅表示 break; case 1: // パルス周期設定 IncStep = DecideStep(t2); t2 += X * IncStep; t2 = ResoADJ(t2); // 有効数字を補正 if ((t2 - t1) < 40) { // t2とt1の差が40μs以下なら t2 = t1 + 40; // t2の下限はt1より40μs上まで } if (t2 > 10000000UL) { t2 = 9999000UL; // t2の上限は9.999s } fix4Disp(9, 0, t2); // パルス周期表示 break; } t3 = t2 - t1; // OFF時間計算 // if (X != 0) { // Serial.print(t1); Serial.print(", "); Serial.println(t2); // } if (X == 0) { // エンコーダーが変化していなかった場合だけ duty1000 = (t1 * 100) / (t2 / 10); // t1*1000/t2ではオーバーフローするので、 DispDuty(duty1000); // デューティ値を表示 freq100 = 100000000 / t2; DispFreq(freq100); // 周波数を表示 } // ここを毎回動かすとエンコーダーのレスポンスが悪化 } void duty50() { boolean isPushed = false; // ボタン押されたかフラグ while ((digitalRead(8) == LOW) && (digitalRead(6) == LOW)) { isPushed = true; lcd.setCursor(0, 1); lcd.print("set duty=50.0% "); } if (isPushed == true) { // ボタンが押されていたら delay(20); // チャッタ消し t1 = t2 / 2; // 周期の半分の値をパルス幅とする t1 = ResoADJ(t1); // 但し、設定可能範囲以下の端数は四捨五入で丸める while ((digitalRead(8) == LOW) || (digitalRead(6) == LOW)) { // どっちかがまだ押されていたら待つ } // Serial.print(t1); Serial.print(", "); Serial.println(t2); fix4Disp(1, 0, t1); // パルス幅表示(周期設定モードの場合、表示が更新されないため) } } long ResoADJ(long dt) { // 有効数字を四捨五入で丸める if (dt < 100000UL) { dt = ((dt + 10) / 20) * 20; // 0.02msステップ } else if (dt < 1000000UL) { dt = ((dt + 50) / 100) * 100; // 0.1msステップ } else if (dt < 10000000UL) { dt = ((dt + 500) / 1000) * 1000; // 0.001sステップ } return dt; } void setPorarity() { // パルス出力極性設定 boolean isPushed = false; // ボタン押されたかフラグ while (digitalRead(6) == LOW) { isPushed = true; } if (isPushed == true) { // ボタンが押されていたら delay(20); // チャッタ消し PosiPulse = !PosiPulse; // 極性反転 lcd.setCursor(0, 0); if (PosiPulse == true) { lcd.print("P"); } else { lcd.print("N"); } } } void EepSave() { // 設定をEEPROMに保存 Pin8+Pin7の同時押しで起動 boolean SaveFlag = false; while ((digitalRead(8) == LOW) && (digitalRead(7) == LOW)) { lcd.setCursor(0, 1); lcd.print("Seve to EEPROM "); // 開始メッセージ SaveFlag = true; } if (SaveFlag == true) { EEPROM.write(0, t1 >> 24); // t1 EEPROM.write(1, (t1 & 0x00FF0000) >> 16); EEPROM.write(2, (t1 & 0x0000FF00) >> 8); EEPROM.write(3, t1 & 0x000000FF); EEPROM.write(4, t2 >> 24); // t2 EEPROM.write(5, (t2 & 0x00FF0000) >> 16); EEPROM.write(6, (t2 & 0x0000FF00) >> 8); EEPROM.write(7, t2 & 0x000000FF); EEPROM.write(8, PosiPulse); // 極性 while ((digitalRead(8) == LOW) || (digitalRead(7) == LOW)) { // どっちか押されていたら待つ } } } void EepLoad() { // EEPROMから設定値を読み出し unsigned long buf; buf = EEPROM.read(0); buf = EEPROM.read(1) | buf << 8; buf = EEPROM.read(2) | buf << 8; buf = EEPROM.read(3) | buf << 8; t1 = buf; buf = EEPROM.read(4); buf = EEPROM.read(5) | buf << 8; buf = EEPROM.read(6) | buf << 8; buf = EEPROM.read(7) | buf << 8; t2 = buf; PosiPulse = EEPROM.read(8); } int ReadEnc() { // ロータリーエンコーダーの読取り static byte bp = 0; int R = 0; static unsigned long LastChangeTime; unsigned long TimeDelta; bp = bp << 1; if (digitalRead(ECA) == HIGH) { // A相の状態を bp |= 0x01; // bpの末尾に記録 } bp = bp << 1; if (digitalRead(ECB) == HIGH) { // B相の状態を bp |= 0x01; // bpの末尾に記録 } bp = bp & 0x0F; // 下位4ビット残して上位を消す if (bp == 0b0111) { // このビットパターンと一致していたら R = 1; } if (bp == 0b1011) { // このビットパターンと一致していたら、 R = -1; } if (R != 0 ) { TimeDelta = millis() - LastChangeTime; if (TimeDelta < 50) { // すごく短い間隔で操作されていたら R *= 20; // 20倍速 } else if (TimeDelta < 70) { // 短い間隔だったら R *= 5; // 5倍速 } LastChangeTime = millis(); } if (digitalRead(8) == LOW) { // 早送りボタンが押されていたら R *= 50; // 更に50倍速 } return R; } void fix4Disp(int x, int y, long dt) { // 小数点付き4桁で表示 lcd.setCursor(x, y); if (dt < 100000) { // 100ms以下なら XX.XXms形式で表示 if (dt < 10000) { // 先頭がゼロなら lcd.print(" "); // スペース } lcd.print(dt / 1000); // 小数点より上を表示 lcd.print("."); if ( (dt / 10) % 100 < 10) { // 10以下だったら lcd.print("0"); // 下1桁に0を補充 } lcd.print( (dt / 10) % 100); lcd.print("ms/"); } else if (dt < 1000000UL) { // 1000ms以下なら XXX.Xms形式で表示 lcd.print( dt / 1000); // 値は必ず100以上あるので空白入れる必要は無い lcd.print("."); lcd.print((dt / 100) % 10); lcd.print("ms/"); } else if ( dt < 10000000UL) { // 1秒以上なら、X.XXXs形式で表示 lcd.print(dt / 1000000UL); // 整数部 lcd.print("."); if ((dt % 1000000UL) < 100000UL) { // 必要なだけゼロを補充 lcd.print("0"); } else { lcd.print((dt / 100000) % 10); } if (( dt % 100000UL) < 10000) { lcd.print("0"); } else { lcd.print(( dt / 10000) % 10); } lcd.print((dt / 1000) % 10); lcd.print("s /"); } } void DispDuty(long d) { // デューティ比を xx.x% 形式で表示 lcd.setCursor(0, 1); if (d < 1000) { lcd.print(" "); // 100%も表示出来るようにする。 } if (d < 100) { lcd.print(" "); } lcd.print(d / 10); lcd.print("."); lcd.print(d % 10); lcd.print("%"); } void DispFreq(long d) { // 周波数を xxxxx.xxHz 形式で表示 lcd.setCursor(6, 1); if (d < 1000000) { // 上に空白があればその数だけスペースを埋める lcd.print(" "); } if (d < 100000) { lcd.print(" "); } if (d < 10000) { lcd.print(" "); } if (d < 1000) { lcd.print(" "); } lcd.print(d / 100); // 整数部表示 lcd.print("."); if ( (d % 100) < 10) { // 小数点以下1桁目がゼロなら lcd.print("0"); // ゼロを表示 } lcd.print(d % 100); // 小数点以下を表示 lcd.print("Hz"); } int DecideStep(long x) { // ロータリーエンコーダーのステップ決定 int y; if (x < 100000) { // 99.99msレンジなら y = 20; // ステップは20 } else if (x < 1000000UL) { y = 100; } else if ( x < 10000000UL) { y = 1000; } return y; } int ModeSelect() { // エンコーダーで変化させる対象(パルス幅/周期)の選定 static int m = 1; // 初期値はエンコーダで周期を設定 while (digitalRead(7) == LOW) { // セレクトボタンが押されていたら m = m + ReadEnc(); // ロータリーエンコーダーを読んで if ((m % 2) == 0) { // モードの値を切り替える lcd.setCursor(0, 1); lcd.print(" "); lcd.write(1); // 外字で↑表示 lcd.print(" Width set"); lcd.setCursor(5, 0); // パルス幅は lcd.cursor(); // 対象位置にカーソル表示 delay(10); // カーソルを見せるためにちょっと待つ m = 0; // モード0 } else { lcd.setCursor(0, 1); lcd.print(" Period set "); lcd.write(1); // 外字で↑表示 lcd.print(" "); lcd.setCursor(13, 0); // パルス周期は lcd.cursor(); delay(10); m = 1; // モード1 } } return m; // モードの値を返す } // 液晶の外字設定 void defGaiji() { byte bar1[8] = { // 上矢印"↑"定義 B00100, B01110, B10101, B00100, B00100, B00100, B00100, B00000, }; lcd.createChar(1, bar1); } // タイマー割り込みでパルス出力 void T1Isr() { if (PosiPulse == true) { PORTB |= B00100000; // sbi PORTBのビット5(D13ピン) } else { PORTB &= B11011111; // cbi (digitalWriteよりこの方が高速) } Timer1.setPeriod(t1); // パルス幅セット Timer1.attachInterrupt( T2Isr ); // タイムアップでT2Isr } void T2Isr() { if (PosiPulse == true) { PORTB &= B11011111; // cbi } else { PORTB |= B00100000; // sbi } Timer1.setPeriod(t3); // 次のパルス Timer1.attachInterrupt( T1Isr ); // タイムアップでT1Isrに戻る }