ボトル水槽用ライト製作

ボトル水槽用のライトを製作しました。ボトル水槽って、ボトリウムって造語があるんですね。

 

作ったもの

ボトル水槽用ライトです。指定時刻に点灯・消灯するタイマー機能と、ライトのアーム部(アルミパイプ)に触れると点灯・消灯するタッチ機能があり、アルミパイプで出来ているライトのアーム部分を触ることでオンオフ出来ます。

オンオフの様子

ばしばし叩いてオンオフしているのは、2歳の娘です^^;

裏の基板はこんな感じ。タッチセンサがアルミパイプにつながっています。

残念なことに深さが足りなくて、蓋に穴を開ける事態に・・・orz

電源供給はUSB-Cコネクタ。ピンが沢山あっても半田付け難しそうだなっと思って、電源専用の2ピンのものにしたのですが、PD規格だと供給されないんですね。CCピンとやらをプルダウンしないといないらしい。次は対応しよっと。

明るさについては、前回は物足りないなーっと思ったのですが、実際使い出すと、見た目としても綺麗だし、マツモとオオカナダモはぷくぷく酸素を出してるので、十分かも。

 

回路

前にタッチセンサを使った時にリセットボタンはあった方が良いっという知見を得ているのにも関わらず、生かしてません^^;。
案の定、たまーにタッチセンサが不安定になって、勝手に付いたり消えたりします。一度電源コードを抜いてリセットすると落ち着きます。

コンデンサやら抵抗やらで、感度調整すればもう少し安定するかもしれませんが。

そもそも使ってみると、タイマー機能があれば手動でオンオフする機会ってそんなないので、不安定なタッチセンサを使う事自体が、不適ですね^^;

RTCは安価なのですが、けっこうずれます

 

パーツ一覧

ハイパワーLED (1W) ¥60
https://electronicwork.shop/items/644b1e0822a0b8002a8b539a

タッチセンサーモジュール (TTP223) ¥40
https://electronicwork.shop/items/62a9ec2805362474eb6e9d49

リアルタイムクロックモジュール (DS1307) ¥175
https://electronicwork.shop/items/66968597a18e8d095b23ae7c

NchパワーMOSFET (2SK4017)(60V/5A) ¥45
https://electronicwork.shop/items/6469cfbfa89fa510927fbeca

基板用電源供給Type-Cコネクタ ¥39
https://electronicwork.shop/items/654b35a2afae4d0033dcda43

 

プログラム

PICで、タッチセンサの出力とRTCからの時刻をチェックして、LEDをオンオフします。
たまにタッチセンサが誤作動するので、完全消灯時間も設定できるようにしました。

まぁ、ほぼGeminiさんが書いてますが。

PIC12F1822のプログラム

#include 
#include 
#include 
#include 
//config1
#pragma config FOSC=INTOSC  // 内部オシレータ使用
#pragma config WDTE=OFF     //Watchdog Timer off
#pragma config PWRTE=ON     //Power up timer on(スイッチを入れた直後電源が安定するまで待つ)
#pragma config MCLRE=OFF     // MCLRピンをリセットとして使用しない→RA3を入力ピンにするために必須
#pragma config CP=OFF       //CODE PROTECT OFF プログラムの読み出しのプロテクトoff
#pragma config CPD=OFF      //Data Protect OFF データ領域の読み出しのプロテクトoff
#pragma config BOREN=ON     // Brownout on (もし電源が不安定のとき一時停止する)
#pragma config CLKOUTEN=OFF // CLOCK 信号の外部出力をOFF
#pragma config IESO=OFF     // 2段階クロックoff(立ち上がりで即安定する内部クロックを使いその後外部クロックに切り替える設定)
#pragma config FCMEN=OFF    //Fale-safe Clock Monitor off(外部クロックが壊れたとき内部クロックに切り替える設定)
//config2
#pragma config WRT=OFF      //Write Protection プログラム領域の書き込み禁止 off
#pragma config PLLEN=OFF    //クロック逓倍off (onにすると発生したクロックが4倍になる)
#pragma config STVREN=ON    //スタック領域をオーバーしたらリセットする(offは何もしない)
#pragma config BORV=LO      // Brounout電圧設定 (HI:電源電圧がちょっとさがるとリセット、LO:うんと下がるとリセット)
#pragma config LVP=OFF      //プログラム書き込み電圧(ON;低電圧書き込み有効 マイコンの電圧で書き込める off:低電圧書き込み無効 PICKIT3のときoffに)

#define _XTAL_FREQ 2000000  //内部clock2MHz for delay macro
#define RTC_ADDR   0x68    // DS1307の7bitアドレス
#define ATOM_ADDR  0x30    // ATOM Liteの7bitアドレス

#define LED_PIN  LATA0      // RA0の出力ラッチ
#define SENSOR_IN RA3       // タッチセンサのIOをRA3に入力

void I2C_Wait() {
    while ((SSP1STAT & 0x04) || (SSP1CON2 & 0x1F));
}

void I2C_Start() {
    I2C_Wait();
    SSP1CON2bits.SEN = 1;
}

void I2C_Stop() {
    I2C_Wait();
    SSP1CON2bits.PEN = 1;
}

void I2C_Write(unsigned char data) {
    I2C_Wait();
    SSP1BUF = data;
}

uint8_t I2C_Read(uint8_t ack) {
    uint8_t temp;
    I2C_Wait();
    SSP1CON2bits.RCEN = 1;
    I2C_Wait();
    temp = SSP1BUF;
    I2C_Wait();
    SSP1CON2bits.ACKDT = (ack ? 0 : 1);
    SSP1CON2bits.ACKEN = 1;
    return temp;
}

// UNIX時間を4バイトに分解して送信する関数
void I2C_Send_UnixTime(unsigned char slave_addr, unsigned long unix_time) {
    I2C_Start();
    I2C_Write(slave_addr << 1); // アドレス送信 (Writeモード)

    // 上位バイトから順に送信 (ビッグエンディアン形式)
    I2C_Write((unsigned char)((unix_time >> 24) & 0xFF));
    I2C_Write((unsigned char)((unix_time >> 16) & 0xFF));
    I2C_Write((unsigned char)((unix_time >> 8)  & 0xFF));
    I2C_Write((unsigned char)(unix_time & 0xFF));

    I2C_Stop();
}

// BCDから10進数への変換
uint8_t bcd_to_decimal(uint8_t bcd) {
    return ((bcd >> 4) * 10) + (bcd & 0x0F);
}

// --- UNIX時間計算関数 ---
// 簡易版: 2000年〜2099年対応
uint32_t get_unix_time(uint8_t y, uint8_t m, uint8_t d, uint8_t hh, uint8_t mm, uint8_t ss) {
    uint32_t t;
    uint16_t y_full = 2000 + y;
    uint8_t months[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    
    // 1970年からの経過年数による秒数 (2000年1月1日まで = 946684800秒)
    t = 946684800;

    // 2000年から指定年前年までの経過日数を加算
    for (uint16_t i = 2000; i < y_full; i++) {
        t += 31536000; // 365日
        if ((i % 4 == 0)) t += 86400; // うるう年
    }

    // 今年の経過月による日数を加算
    if (y_full % 4 == 0) months[1] = 29;
    for (uint8_t i = 0; i < m - 1; i++) {
        t += (uint32_t)months[i] * 86400;
    }

    // 日・時・分・秒を加算
    t += (uint32_t)(d - 1) * 86400;
    t += (uint32_t)hh * 3600;
    t += (uint32_t)mm * 60;
    t += ss;

    return t;
}

void main() {
    // OSCCON設定 (2MHz)
    // IRCF = 1100 (2MHz内部クロック)
    OSCCON = 0b01100000; 

    // --- I/Oピン設定 ---
    ANSELA = 0x00;      // 全てのピンをデジタルI/Oに設定
    TRISA = 0b00001110; 
    // ビット構造の解説:
    // Bit 5 (RA5): 0 (Output) - 空き
    // Bit 4 (RA4): 0 (Output) - 空き
    // Bit 3 (RA3): 1 (Input Only)  - タッチセンサー
    // Bit 2 (RA2): 1 (Input)  - I2C SDA
    // Bit 1 (RA1): 1 (Input)  - I2C SCL
    // Bit 0 (RA0): 0 (Output) - LED出力ピン
    LATA5 = 0;  // RA5をLow固定(未使用ピンによるノイズ防止)
    LATA4 = 0;  // RA4をLow固定(未使用ピンによるノイズ防止)
    
    // I2C設定 (100kHz用)
    SSP1ADD = 4; // 2MHz時の計算値
    SSP1STAT = 0x80;    // 標準速度 (100kHz)
    SSP1CON1 = 0x28;    // I2C Master mode, SSP Enable  
    LED_PIN = 0;          // 最初は消灯
    
    uint8_t sec, min, hour, day, month, year;
    uint32_t current_unix_time;
    
    uint8_t led_state = 0;        // 現在のLED状態(0:消灯, 1:点灯)
    uint8_t last_sensor_state = 0; // 前回のセンサー状態(エッジ検出用)

    // --- 時刻設定の変数 ---
    uint8_t start_h = 8;  // 開始時
    uint8_t start_m = 0;  // 開始分
    uint8_t end_h   = 18; // 終了時
    uint8_t end_m   = 0;  // 終了分
    uint8_t sleep_start_h = 22; // 強制消灯&センサー無効
    uint8_t sleep_end_h   = 6;  // センサー有効化
    
    // 電源投入時の初期状態決定 ---
    // 一回だけRTCを読み出す
    I2C_Start();
    I2C_Write(RTC_ADDR << 1);
    I2C_Write(0x00);
    I2C_Start();
    I2C_Write(RTC_ADDR << 1 | 1);
    sec   = bcd_to_decimal(I2C_Read(1));
    min   = bcd_to_decimal(I2C_Read(1));
    hour  = bcd_to_decimal(I2C_Read(0)); // ここでは時まででOK
    I2C_Stop();

    // 現在時刻と設定時刻を「分単位」に換算して比較
    uint16_t current_total_min = (uint16_t)hour * 60 + min;
    uint16_t start_total_min   = (uint16_t)start_h * 60 + start_m;
    uint16_t end_total_min     = (uint16_t)end_h * 60 + end_m;

    if (current_total_min >= start_total_min && current_total_min < end_total_min) {
        led_state = 1; // 範囲内なら点灯
    } else {
        led_state = 0; // 範囲外なら消灯
    }
    LED_PIN = led_state;
    
    while(1) {
        
        // RTCから時刻を読み出す
        I2C_Start();
        I2C_Write(RTC_ADDR << 1);  // 書き込みモードでRTC指定
        I2C_Write(0x00);         // レジスタアドレス0(秒)を指定
        
        I2C_Start();             // リスタート
        I2C_Write(RTC_ADDR << 1 | 1); // 読み出しモード
        
        sec  = bcd_to_decimal(I2C_Read(1)); // 秒 (ACK)
        min  = bcd_to_decimal(I2C_Read(1)); // 分 (ACK)
        hour = bcd_to_decimal(I2C_Read(1)); // 時 (NACK)
        I2C_Read(1); // 曜日 (不使用なので読み飛ばし)
        day   = bcd_to_decimal(I2C_Read(1));
        month = bcd_to_decimal(I2C_Read(1));
        year  = bcd_to_decimal(I2C_Read(0)); // 最後はNACK
        I2C_Stop();
        
        //タッチセンサー無効期間の判定 ---
        uint8_t is_sleep_time = 0;
        if (hour >= sleep_start_h || hour < sleep_end_h) {
            is_sleep_time = 1;
        }
        
        // タッチセンサーの処理(トグル動作)
        uint8_t current_sensor_state = SENSOR_IN;
        if (current_sensor_state == 1 && last_sensor_state == 0) {
            // 無効期間(is_sleep_time == 0)の時だけトグル動作を許可
            if (is_sleep_time == 0) {
                led_state = !led_state;
            }
            __delay_ms(50); // デバウンス
        }
        last_sensor_state = current_sensor_state;
        
        // 時刻による自動点灯・消灯(イベント動作)
        // 開始時刻になった瞬間
        if (hour == start_h && min == start_m && sec == 0) {
            led_state = 1;
        }
        // 終了時刻になった瞬間
        if (hour == end_h && min == end_m && sec == 0) {
            led_state = 0;
        }
        // 就寝時間の強制消灯
        if (hour == sleep_start_h && min == 0 && sec == 0) {
            led_state = 0;
        }
        
        // --- LED制御ロジック ---
        LED_PIN = led_state;
       
        // ATOM Lite (アドレス0x30) へUNIX時間を送信(デバック用)
        current_unix_time = get_unix_time(year, month, day, hour, min, sec);
        I2C_Send_UnixTime(ATOM_ADDR, current_unix_time);

        __delay_ms(100); // ループ
    }
}
Updated: 2026年2月23日 — 14:51

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です