LEDランタンを修理したい

Arduino

一切を風靡した(?)GENTOSのEX-777XPのOEM版であるRAYOVACのSportsman Extremeを筆者は2台持っている。上位版のEX-888TFを持っていたのと、888と同じ暖色ではなくこれは白色だということと、当時Amazon.com(US)から送料込みで買っても日本で買う777より安かったのが理由だ。

2010年ころに買ったが使用頻度はそれほど高くないにもかかわらず、内一台が壊れてしまって電池を入れるとフル点灯してしまい、スイッチが利かないので消せなくなった。電池を抜いておけばいいわけだが何ともよろしくない。修理してみよう、と思って分解すると、中央にコントロール基板が一枚入っているだけである。

回路図をなんとなく起こしてみる。

概ねこのようになっているっぽい。っぽい、っていうのはIC1がマスクされていて、細かいところがよくわからない(わからない部分は点線)。おそらくカスタムICであるがSW1を押せばここで便宜上GP3としたPinがLowになり、Q2(型番は不明だが回路からPch-MOS-FETと推測)がONして点灯するわけだ。まぁ細かいところはどうでもよくて「どこが壊れて点きっ放しになるのか」ということである。Q2が壊れたならばFETを適当なものに交換すればよいのだが・・・試しにQ2を強制ONすると点くし、Highに吊ると消えるのでQ2は生きているようだ。つまりIC1が壊れているのである。これはどうしようもない。

オリジナルの機能はフル点灯(High/72時間)→スイッチ長押しでEcoモード(Eco/144時間)の切替と、さらに長押しで点滅モード(ただ、パカパカと点滅する)というものである。Ecoモードで電池が倍持つということは50%で点灯させているのだからPWM制御だろう、おそらくはPICのようなICで制御しているのだろうと推測。つまり点灯回路は全部生きているので、左半分の回路を何かに置き換えてしまえばこのランタンは生き返るわけである。

というわけで、アイドリングストップ解除でも使ったDigispark(ATTiny85)を使うことにして、せっかくだから機能を追加する。普段はスリープ→スイッチ短押しで100%→スイッチ短押しで75%→スイッチ短押しで50%→スイッチ短押しで25%→スイッチ短押しでSOS点滅モードでそれぞれ点灯させ、各箇所にて長押しでOFF、というのを考えた。そして25%モードはおやすみモードであり、2時間タイマーでOFFさせる機能も追加しよう。

SOS点滅モードというのは、モールス信号で・・・---・・・ ・・・---・・・ ・・・---・・・と繰り返し点滅させるものである。ただ、パカパカと点滅するでは芸がないのでモールスでSOSを発するわけだ。見る人が見れば「緊急事態」と理解してもらえるかもしれない。キャンプでそのようなことがあっては危ないのだが、ただの点滅よりは「使える」機能にしておいた方が良いだろう。

Nch-MOS-FETに置き換えるとローサイドSWとなり制御は楽なのだが、部品はそのまま流用することを考えたので右半分の回路はそのままである。あとはプログラミングでどうにでもなろうというものだ。用意したのは基板とタクトスイッチと抵抗2本だけ、あとはDigisparkを接続して終了。消費電力的にはそんなに変わらんのではなかろうか。Digisparkは後々、ATTiny85の1チップのみにしてもいいと思う。

万能基板にて同じ場所にタクトスイッチが来るようにし、裏面にチップ抵抗とチップFETを搭載。中央のLEDは元から無くてもいいと思ったので省略。

Digisparkは電池の隙間に収められそうだったので適当に両面テープで固定しておく。

#include <avr/sleep.h>
#include <avr/interrupt.h>

const int light_pin  = 0; // 外付けLED(PWM対応ピン PB0、Pchハイサイド)
const int led_pin    = 1; // 内蔵LED(PWM対応ピン PB1、通常ロジック)
const int button_pin = 2; // タクトスイッチ PB2, GNDに落とす

const int max_value = 255;
const double C = 255.0;

volatile bool buttonInterrupt = false;
int mode = 0; // 初期はOFF

const unsigned long longPressTime = 2000; // 長押し判定 2秒
const int fadeDelay = 4;                   // フェード速度
const unsigned long autoOffTime = 7200000; // case4 自動OFF 2時間

unsigned long case4Start = 0;

//======================================================
// 外付けLED用:PWM duty を反転して出力
//======================================================
inline void writePWM(uint8_t duty) {
  duty = 255 - duty; // Pchハイサイド用に反転
  analogWrite(light_pin, duty);
}

//======================================================
// ボタン割り込みISR(処理は軽く:押下の可能性があればフラグを立てる)
//======================================================
ISR(PCINT0_vect) {
  buttonInterrupt = true;
}

void setup() {
  // PchハイサイドMOSFETは「HIGHでOFF」なので、pinMode前にOFFを出しておく
  digitalWrite(light_pin, HIGH);
  pinMode(light_pin, OUTPUT);

  pinMode(led_pin, OUTPUT);
  pinMode(button_pin, INPUT_PULLUP);

  writePWM(0);
  analogWrite(led_pin, 0);

  // ピン変化割り込みを有効化 (PB2 = PCINT2)
  GIMSK |= (1 << PCIE);
  PCMSK |= (1 << PCINT2);
  sei();

  enterSleep(); // 電源投入時はOFF
}

void loop() {
  // case4 自動OFF判定
  if (mode == 4 && millis() - case4Start >= autoOffTime) {
    mode = 0;
    writePWM(0);
    analogWrite(led_pin, 0);
    enterSleep();
  }

  // ボタンが押された場合
  if (buttonInterrupt) {
    buttonInterrupt = false;
    delay(30); // チャタリング防止

    if (digitalRead(button_pin) == LOW) {
      unsigned long pressStart = millis();

      while (digitalRead(button_pin) == LOW) {
        // 長押し判定
        if (millis() - pressStart >= longPressTime) {
          mode = 0;
          writePWM(0);
          analogWrite(led_pin, 0);
          enterSleep();
        }
      }

      unsigned long pressDuration = millis() - pressStart;
      if (pressDuration < longPressTime) {
        // 短押しでモード切替
        if (mode != 5) { // SOSモードは loop で処理
          mode = (mode + 1) % 6;

          switch (mode) {
            case 0:
              writePWM(0);
              analogWrite(led_pin, 0);
              enterSleep();
              break;
            case 1: fade(0, max_value); break;
            case 2: fade(max_value, (int)(max_value*0.75)); break;
            case 3: fade((int)(max_value*0.75), (int)(max_value*0.5)); break;
            case 4:
              fade((int)(max_value*0.5), (int)(max_value*0.25));
              case4Start = millis(); // 自動OFFタイマー開始
              break;
            case 5: break; // SOSモードは loop で処理
          }
        }
      }
    }
  }

  // SOSモードの処理
  if (mode == 5) {
    sos_mode();
    // SOS終了後 mode が0ならOFF処理
    if (mode == 0) {
      writePWM(0);
      analogWrite(led_pin, 0);
      enterSleep();
    }
  }
}

void enterSleep() {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_cpu();
  sleep_disable();
}

void fade(int start_value, int end_value) {
  int current_value = start_value;
  while (current_value != end_value) {
    if (current_value < end_value) current_value++;
    if (current_value > end_value) current_value--;

    int duty = get_duty(current_value);
    writePWM(duty);
    analogWrite(led_pin, duty);
    delay(fadeDelay);
  }
}

// ---------- SOS モード ----------

// SOSモード(dot=0.25s, dash=0.75s, セット間隔3sOFF)
void sos_mode() {
  while (mode == 5) {
    // S = dot dot dot
    for (int i=0; i<3; i++) if (checkButtonSOS()) return; else sos_blink(250, 250);
    // O = dash dash dash
    for (int i=0; i<3; i++) if (checkButtonSOS()) return; else sos_blink(750, 250);
    // S = dot dot dot
    for (int i=0; i<3; i++) if (checkButtonSOS()) return; else sos_blink(250, 250);

    // セット間隔 3秒OFF
    writePWM(0);
    analogWrite(led_pin, 0);
    if (wait_ms_with_abort(3000)) return;
  }
}

// こま切れで待ち、途中でボタンが押されたら true を返す
bool wait_ms_with_abort(unsigned long ms) {
  unsigned long start = millis();
  while (millis() - start < ms) {
    if (checkButtonSOS()) return true;
    delay(10); // 10ms刻みで待つ
  }
  return false;
}

// SOSの1パターン(ON→OFF)
void sos_blink(int on_ms, int off_ms) {
  writePWM(255);
  analogWrite(led_pin, 255);
  if (wait_ms_with_abort(on_ms)) return;

  writePWM(0);
  analogWrite(led_pin, 0);
  if (wait_ms_with_abort(off_ms)) return;
}

// SOS中のボタン判定(短押し/長押しどちらでもOFF)
bool checkButtonSOS() {
  if (digitalRead(button_pin) == LOW) {
    unsigned long pressStart = millis();
    while(digitalRead(button_pin) == LOW) {
      if (millis() - pressStart >= longPressTime) {
        mode = 0; // 長押しでOFF
        return true;
      }
    }
    // 短押しでもOFF
    mode = 0;
    return true;
  }
  return false;
}

// 人間の目に合わせたガンマ補正付きduty計算
int get_duty(double ratio) {
  return round(exp(log(255.0) - (1 - (ratio/255.0))*log(C)));
}

読んでの通りだが、これで機能的にはグレードUPである。内蔵LEDの部分はデバッグ時に使用したので実機搭載時はコメントアウトすればよい。フェード演出を無駄に追加したので(笑)、100%点灯は0~ポワーっと明るくなり、75%→50%→25%への偏移はそれぞれシューンと暗くなる。あまり意味はないけど。

1970年代且つ昭和40年代秋田市生まれ。情報処理技術者、甲種危険物取扱者、第一種衛生管理者、第二種電気工事士、消防設備士乙6/乙7、品質管理検定2級、漢字検定2級、VEリーダーなどを所持。1990年代に結婚して一子あり。

minakawaをフォローする
Arduino
スポンサーリンク
minakawaをフォローする
タイトルとURLをコピーしました