/*******************************************************************************
 * 信号機デコーダ for PIC16F1827 (C言語版)
 *
 * Web Nucky http://web.nucky.jp/
 *
 * Copyright (C) 2007-2026 Nucky
 *
 * 2026.04.04 Ver1.0  ソースコードをC言語記述に変更（機能強化）
 *
 *【Ver 1.0 (XC8 C言語移行版) の主な特長】
 * 1. 完全ポーリング駆動:
 * DCC信号のパルス幅計測を妨げないよう、割り込み(Interrupt)を一切排除。
 * タイマーフラグの裏回し技術により、受信安定性を極限まで高めています。
 * 2. プログラミングモードの完全対応:
 * PAGE / DIRECT(Byte/Bit) モードに対応。読出エラー対策として、
 * ACK応答時にマトリクス配線の全LEDをダイナミックスキャン点灯させます。
 * 3. 電球(白熱球)シミュレーション (CV5):
 * 非同期ステートマシンによるフェードエンジンを搭載。実物の信号機のような
 * 熱慣性(サーマルラグ)に伴う「余韻」と「遅延」を美しく再現します。
 * 4. 起動時現示(レジューム)切替 (CV6・CV9連携):
 * CV6=0時はCV9の現示で起動(アセンブラ互換)、CV6=1時は電源OFF前の状態へ復帰。
 * EEPROMの書き込みは「変化があった時のみ」に制限し、寿命を保護しています。
 *******************************************************************************/


/*
 *【ソースコードのライセンスおよび利用条件について】
 * 本ソースコードの著作権は放棄しておらず、すべてNuckyに帰属します。
 * 本ソフトウェアは、以下のデュアルライセンス方式で提供されます。
 *
 * 1. [GPL利用] 個人の鉄道模型の範囲での利用、研究、改変は自由です。
 * 本コードを改変したソフトウェアを公開・頒布する場合には、GPLv3に基づき、
 * 必ずソースコードを公開する義務が生じます。
 * 2. [商用利用] ソースコードを公開しない形での製品化や販売、基板への組み込み
 * 等の商用利用をご希望の場合は、必ず事前にご連絡ください。GPLとは別の
 * 商用ライセンス（Commercial License）をご案内いたします。
 *
 * --- English Notice ---
 * This software is dual-licensed:
 * 1. GPLv3: For personal, non-commercial, and open-source use.
 * 2. Commercial License: REQUIRED for any commercial use or products 
 * without source code disclosure. Please contact the author via the website.
 *
 * --- 免責事項 / Disclaimer ---
 * 本プログラムの利用によって生じたデコーダ、車両、その他の機材の破損、不具合、
 * およびいかなる損害についても、作者は一切の責任を負いかねます。
 * This program is distributed WITHOUT ANY WARRANTY. Use at your own risk.
*/


#include <xc.h>
#include <stdint.h>

// =========================================================
// 1. 基本設定・コンフィギュレーション
// =========================================================
#define _XTAL_FREQ 16000000 // 内部クロック16MHz駆動 (__delay_ms()の計算等に使用)

// --- CONFIG1 ---
#pragma config FOSC = INTOSC    // 内部発振器を使用 (外部クリスタル不要でピンを節約)
#pragma config WDTE = OFF       // ウォッチドッグタイマ無効 (DCC信号待ちでの意図せぬリセットを防止)
#pragma config PWRTE = ON       // パワーアップタイマ有効 (電源投入直後の電圧安定まで起動を待つ)
#pragma config MCLRE = OFF      // MCLRピンをリセットではなくデジタル入力(RA5)として使用
#pragma config CP = OFF         // プログラムメモリ保護無効
#pragma config CPD = OFF        // データEEPROM保護無効
#pragma config BOREN = ON       // ブラウンアウトリセット有効 (電圧低下時にマイコンを安全に停止)
#pragma config CLKOUTEN = OFF   // クロック出力無効
#pragma config IESO = OFF       // 内部/外部スイッチオーバー無効
#pragma config FCMEN = OFF      // フェイルセーフクロックモニタ無効

// --- CONFIG2 ---
#pragma config WRT = OFF        // フラッシュメモリ自己書込み保護無効
#pragma config PLLEN = OFF      // PLL無効 (OSCCONレジスタで直接16MHzを設定するため)
#pragma config STVREN = ON      // スタックオーバーフローリセット有効
#pragma config BORV = LO        // ブラウンアウトリセットの閾値(低レベル)
#pragma config LVP = OFF        // 低電圧プログラミング無効 (ONだと一部のピンが制限されるため必須)

// =========================================================
// 2. ピン割り当て・マクロ定義
// =========================================================
#define DCC_IN PORTAbits.RA2    // DCCパルス信号の入力ピン (Pin 1)
#define RELAY_OUT LATBbits.LATB7// 停止信号リレー出力 (赤現示時、またはACKの電流稼ぎ時にON)

// =========================================================
// 3. EEPROM CV初期値設定 (工場出荷時)
// =========================================================
// 括弧内はEEPROM内の物理アドレス(= CV番号 - 1)。8バイトずつ定義しています。

// [0]CV1: 信号機種別(31=3現示, 43=4現示YGFなど)
// [1]CV2: G輝度(PWM値), [2]CV3: Y輝度, [3]CV4: R輝度
// [4]CV5: 電球フェード速度 (0=LEDモード[即時], 1以上=電球モード[ウェイト値]) ★初期値10
// [5]CV6: 起動時現示モード (0=CV9固定で起動, 1=前回状態からレジューム) ★初期値0
// [6]CV7: デコーダVer (1.0 = 10), [7]CV8: メーカーID (156=Nucky)
__EEPROM_DATA(31, 255, 255, 255,  10,   0,  10, 156);

// [8]CV9: 初期現示コード (CV6=0の時の起動時現示として使用。アセンブラ版互換機能)
// [9]CV10: 抑速(YGF)点滅周期 (56)
// [10]CV11/CV12: 方向てこ条件アドレス (上位/下位)
// [12]CV13/CV14: 条件1アドレス (上位/下位)
// [14]CV15/CV16: 条件2アドレス (上位/下位)
__EEPROM_DATA( 0,  56, 129, 248, 129, 250, 129, 252);

// [16]CV17/CV18: 条件3アドレス (上位/下位)
// [18]CV19/CV20: 条件4アドレス (上位/下位)
// [20]CV21/CV22: 条件5アドレス (上位/下位)
__EEPROM_DATA(129, 254, 130, 248, 130, 250, 255, 255);

// =========================================================
// 4. グローバル変数定義
// =========================================================

// --- CV値のRAMキャッシュ ---
// EEPROMの読み出しは時間がかかるため、メインループ内で毎回読むとDCC受信の妨げになります。
// 起動時やCV書き込み時に一度だけRAM(変数)へコピーし、以降は高速なRAMへアクセスします。
uint8_t cv_signal_set;         
uint8_t cv_kido_g, cv_kido_y, cv_kido_r; 
uint8_t cv_fade_speed;         // 電球フェード速度 (CV5)
uint8_t cv_poweron_mode;       // 起動時現示モード (CV6)
uint8_t cv_ygf_rate;           // 抑速点滅周期 (CV10)

// アドレス設定のキャッシュ (上位/下位)
uint8_t dir_adr1, dir_adr2;    // 方向条件
uint8_t a_adr1,   a_adr2;      // 条件1
uint8_t b_adr1,   b_adr2;      // 条件2
uint8_t c_adr1,   c_adr2;      // 条件3
uint8_t d_adr1,   d_adr2;      // 条件4
uint8_t e_adr1,   e_adr2;      // 条件5

// --- 動作状態フラグ ---
uint8_t genji_code = 0;        // 5つの条件(A〜E)のON/OFF状態を各ビットに保持 (CV9互換構造)
uint8_t dir_code = 0;          // 方向条件(bit0=1で逆方向と判定し赤現示を強制)

// --- サービスモード(プログラミング)管理 ---
uint8_t in_service_mode = 0;   // 1=プログラミングモード滞在中(ベース電流を下げるため全消灯を維持)
uint16_t service_timeout = 0;  // プログラミング終了を判定するための無通信タイムアウトカウンタ
uint8_t page_register = 0;     // PAGEモードでのレジスタ保存用

// --- バックグラウンド処理・タイマー変数 ---
// 割り込みを使わないため、メインループや受信待機の隙間でTimer0のフラグを監視し、
// 時間経過を tmr0_ticks に「ストック」しておきます。
uint8_t  tmr0_ticks = 0;       // 未消化のタイマー経過回数 (1tick ≒ 0.512ms)
uint16_t ygf_timer = 0;        // 抑速点滅の残り時間カウンタ
uint8_t  ygf_toggle = 0;       // 抑速点滅の状態 (1=点灯フェーズ, 0=消灯フェーズ)
uint8_t  is_ygf_active = 0;    // 現在YGF(抑速)現示が選ばれているかどうかのフラグ

// --- 電球モード フェードエンジン(ステートマシン)用変数 ---
// 「フェードアウト -> 極性切り替え -> フェードイン」という時間のかかる処理を、
// 処理を止めずに(非同期で)実行するための状態遷移変数群です。
#define FADE_STABLE 0          // 安定点灯状態 (フェード完了)
#define FADE_OUT    1          // 旧現示のフェードアウト処理中
#define FADE_SWITCH 2          // PWMゼロの安全な状態で極性・リレーを切り替えるタイミング
#define FADE_IN     3          // 新現示のフェードイン処理中

uint8_t fade_state = FADE_STABLE;
uint8_t fade_tick_timer = 0;   // 1ステップの変化にかけるウェイト(CV5依存)

// 現在ハードウェアに出力中のPWM値 (エンジンが徐々に増減させる)
uint8_t current_duty1 = 0;
uint8_t current_duty2 = 0;

// 新しく切り替わる目標(Target)のハードウェア状態
uint8_t target_relay = 0;
uint8_t target_pstr1 = 0;
uint8_t target_duty1 = 0;
uint8_t target_pstr2 = 0;
uint8_t target_duty2 = 0;

// --- DCCパケット受信バッファ ---
uint8_t data1=0, data2=0, data3=0, data4=0, data5=0, data6=0;
uint8_t packet_ready = 0;      // 1=有効なパケットの受信が完了し、処理待ち
uint8_t packet_length = 0;     // 受信したパケットのバイト数(3〜6)

// =========================================================
// 関数プロトタイプ宣言
// =========================================================
void system_init(void);
void load_cv_cache(void);
void factory_reset(void);
uint8_t eeprom_read_byte(uint8_t address);
void eeprom_write_byte(uint8_t address, uint8_t data);
void write_cv(uint8_t cv_addr, uint8_t cv_data);
void save_current_state(void);
void send_ack(void);
uint8_t measure_pulse(void);
void receive_dcc(void);
void request_aspect(uint8_t relay, uint8_t pstr1, uint8_t duty1, uint8_t pstr2, uint8_t duty2);
void update_signal_aspect(void);
void process_background_tasks(void);
void process_packet(void);
void process_service_mode(void);

// =========================================================
// ハードウェア初期化
// =========================================================
void system_init(void) {
    OSCCON = 0x7A;  // 16MHz内部オシレータ
    ANSELA = 0x00; ANSELB = 0x00; // アナログ機能をOFFにし、すべてデジタルI/Oにする
    TRISA = 0b00000100; TRISB = 0b00000000; // RA2(DCC_IN)のみ入力、他はすべて出力
    PORTA = 0; PORTB = 0;

    // --- Timer0設定 (バックグラウンドのストックタイマー) ---
    // 16MHz駆動時、Fosc/4 = 4MHz。プリスケーラ1:8で 256 * 8 / 4MHz = 512us(約0.512ms)周期
    OPTION_REG = 0b00000010; 
    INTCONbits.TMR0IF = 0;

    // --- Timer1設定 (DCCパルス幅の計測用) ---
    // プリスケーラ1:4。これによりTMR1の1カウントがピッタリ「1us」となり計算が直感的になります
    T1CONbits.TMR1CS = 0;
    T1CONbits.T1CKPS = 2; 
    T1CONbits.TMR1ON = 1;

    // --- PWM(ECCP)設定 ---
    CCP1CON = 0b00001100; CCP2CON = 0b00001100; // PWMモード
    CCPTMRS = 0b00000100; // CCP1にTMR2を、CCP2にTMR4を紐付け
    
    // ピンルーティング変更(Alternate Pin Function)
    // ハードウェアPWMの出力を、マトリクス配線に合わせた任意のピンにルーティングします
    APFCON0bits.CCP1SEL = 0; APFCON0bits.P1CSEL = 1;  
    APFCON0bits.P1DSEL = 1;  APFCON0bits.CCP2SEL = 1; 
    APFCON0bits.P2BSEL = 1;  

    T2CON = 0b00000100; T4CON = 0b00000100; // PWM用タイマーON
    PR2 = 0xFF; PR4 = 0xFF; // PWM分解能(100%出力の時の値)
    
    // 【重要】完全ポーリング動作のため、割り込みは全体禁止を維持します
    INTCONbits.PEIE = 0;  
    INTCONbits.GIE  = 0;
}

// =========================================================
// EEPROM操作・CV管理
// =========================================================
uint8_t eeprom_read_byte(uint8_t address) {
    EEADRL = address;
    EECON1bits.CFGS = 0; EECON1bits.EEPGD = 0; EECON1bits.RD = 1;
    return EEDATL;
}

void eeprom_write_byte(uint8_t address, uint8_t data) {
    // 【重要】書き込み寿命節約保護: 
    // EEPROMは書き込み回数に寿命があるため、現在の値と同じなら書き込み動作をスキップします
    if (eeprom_read_byte(address) == data) return;
    
    EEADRL = address; EEDATL = data;
    EECON1bits.CFGS = 0; EECON1bits.EEPGD = 0; EECON1bits.WREN = 1;
    EECON2 = 0x55; EECON2 = 0xAA; EECON1bits.WR = 1; // アンロックシーケンス
    while(EECON1bits.WR); // 書き込み完了待ち
    EECON1bits.WREN = 0;
}

void load_cv_cache(void) {
    cv_signal_set   = eeprom_read_byte(0); 
    cv_kido_g       = eeprom_read_byte(1); 
    cv_kido_y       = eeprom_read_byte(2); 
    cv_kido_r       = eeprom_read_byte(3); 
    cv_fade_speed   = eeprom_read_byte(4); // CV5
    cv_poweron_mode = eeprom_read_byte(5); // CV6
    cv_ygf_rate     = eeprom_read_byte(9); // CV10
    
    dir_adr1 = eeprom_read_byte(10); dir_adr2 = eeprom_read_byte(11);
    a_adr1   = eeprom_read_byte(12); a_adr2   = eeprom_read_byte(13);
    b_adr1   = eeprom_read_byte(14); b_adr2   = eeprom_read_byte(15);
    c_adr1   = eeprom_read_byte(16); c_adr2   = eeprom_read_byte(17);
    d_adr1   = eeprom_read_byte(18); d_adr2   = eeprom_read_byte(19);
    e_adr1   = eeprom_read_byte(20); e_adr2   = eeprom_read_byte(21);
}

// CV8にリセット命令(8)が書き込まれた際に呼ばれる、工場出荷状態へのリセット処理
void factory_reset(void) {
    eeprom_write_byte(0, 31); eeprom_write_byte(1, 255);
    eeprom_write_byte(2, 255); eeprom_write_byte(3, 255);
    eeprom_write_byte(4, 10); eeprom_write_byte(5, 0); // CV5=10, CV6=0
    // ※CV7(Ver)やCV8(MfgID)は基本システム情報のためリセット対象外とします
    eeprom_write_byte(8, 0);  eeprom_write_byte(9, 56); 
    
    eeprom_write_byte(10, 129); eeprom_write_byte(11, 248);
    eeprom_write_byte(12, 129); eeprom_write_byte(13, 250);
    eeprom_write_byte(14, 129); eeprom_write_byte(15, 252);
    eeprom_write_byte(16, 129); eeprom_write_byte(17, 254);
    eeprom_write_byte(18, 130); eeprom_write_byte(19, 248);
    eeprom_write_byte(20, 130); eeprom_write_byte(21, 250);
    
    // レジューム記憶用の隠し領域もリセット
    eeprom_write_byte(254, 0); 
    eeprom_write_byte(255, 0); 

    load_cv_cache();
}

void write_cv(uint8_t cv_addr, uint8_t cv_data) {
    if (cv_addr == 7 && cv_data == 8) factory_reset(); // NMRA規格のファクトリーリセット
    else { eeprom_write_byte(cv_addr, cv_data); load_cv_cache(); }
}

// 現在の現示状態をEEPROMの隠し領域(254, 255番地)に保存する (レジューム用)
void save_current_state(void) {
    // 内部の eeprom_write_byte 関数で「同一値ならスキップ」が行われるため、
    // ここを毎回呼び出してもEEPROMの寿命を無駄に消費することはありません。
    eeprom_write_byte(254, dir_code);
    eeprom_write_byte(255, genji_code);
}

// =========================================================
// ACKパルス出力 (全LEDダイナミックスキャン)
// 6ms間ですべての現示極性を高速スキャンし、マトリクス配線の
// すべてのLEDを同時点灯させ、コマンドステーション側の検知電流を稼ぎます。
// =========================================================
void send_ack(void) {
    RELAY_OUT = 1; // 停止リレーのコイル電流もダミーロードとして活用
    LATBbits.LATB3 = 0; // P1Aに乗っ取られるため、手動でHIGHにはしない
    CCPR1L = 0xFF; CCPR2L = 0xFF; // 全PWM出力を100%に
    
    // チャーリープレクシングの全経路に電流を流すため、5つの現示パターンを高速で回す
    PSTR1CON = 0b00000111; PSTR2CON = 0x00; __delay_us(1200); // RED極性
    PSTR1CON = 0b00001001; PSTR2CON = 0x00; __delay_us(1200); // YY極性
    PSTR1CON = 0b00001011; PSTR2CON = 0x00; __delay_us(1200); // YELLOW極性
    PSTR1CON = 0b00000110; PSTR2CON = 0b00000010; __delay_us(1200); // YG極性
    PSTR1CON = 0b00000110; PSTR2CON = 0x00; __delay_us(1200); // GREEN極性
    
    // ACK出力後は即座に全消灯し、ベース電流を極限まで下げる
    PSTR1CON = 0x00; PSTR2CON = 0x00; CCPR1L = 0; CCPR2L = 0; RELAY_OUT = 0;
}

// =========================================================
// DCCパルス計測・受信処理
// =========================================================
uint8_t measure_pulse(void) {
    // 計測の待機中であってもTimer0フラグを回収し、裏のタイマーを回し続ける
    if (INTCONbits.TMR0IF) { INTCONbits.TMR0IF = 0; tmr0_ticks++; }

    // High -> Low -> High の1周期の時間を計測
    TMR1H = 0; TMR1L = 0;
    while(DCC_IN == 1) { if (TMR1H > 0) return 2; } // Timer1オーバーフロー(256us)でタイムアウト
    TMR1H = 0; TMR1L = 0;
    while(DCC_IN == 0) { if (TMR1H > 0) return 2; }
    TMR1H = 0; TMR1L = 0;
    while(DCC_IN == 1) { if (TMR1H > 0) return 2; }
    
    // 75us未満なら '1' パルスと判定
    if (TMR1L < 75) return 1;
    return 0; // '0' パルスと判定
}

void receive_dcc(void) {
    packet_ready = 0;
    uint8_t bit_val, buf[6] = {0}, p_count = 0;
    
WAIT_PREAMBLE:
    p_count = 0;
PREAMBLE_LOOP:
    bit_val = measure_pulse();
    if (bit_val == 2) return; 
    if (bit_val == 1) { p_count++; goto PREAMBLE_LOOP; } // プリアンブル('1'の連続)をカウント
    else { if (p_count < 10) goto WAIT_PREAMBLE; } // 10回未満の'1'はノイズとして破棄
    
    // パケットデータの読み込み
    for (packet_length = 0; packet_length < 6; packet_length++) {
        for (uint8_t i = 0; i < 8; i++) {
            bit_val = measure_pulse();
            if (bit_val == 2) return;
            buf[packet_length] = (buf[packet_length] << 1) | bit_val; // ビットシフトして保存
        }
        bit_val = measure_pulse();
        if (bit_val == 2) return;
        
        // セパレータ '1' でパケット完了
        if (bit_val == 1) {
            packet_length++; 
            if (packet_length >= 3) {
                data1 = buf[0]; data2 = buf[1]; data3 = buf[2];
                data4 = buf[3]; data5 = buf[4]; data6 = buf[5];
                packet_ready = 1; 
            }
            return;
        }
    }
}

// =========================================================
// 現示リクエスト (ステートマシンへの目標値セット)
// =========================================================
void request_aspect(uint8_t relay, uint8_t pstr1, uint8_t duty1, uint8_t pstr2, uint8_t duty2) {
    // 同じ現示への変更指示なら何もしない(フェードの再スタートを防ぐ)
    if (target_relay == relay && target_pstr1 == pstr1 && target_duty1 == duty1 &&
        target_pstr2 == pstr2 && target_duty2 == duty2) {
        return; 
    }

    // フェードエンジンの目標値(Target)をセット
    target_relay = relay;
    target_pstr1 = pstr1;
    target_duty1 = duty1;
    target_pstr2 = pstr2;
    target_duty2 = duty2;

    // CV5(フェード速度)が0のLEDモード、またはプログラミング滞在中は瞬時に出力を切り替える
    if (cv_fade_speed == 0 || in_service_mode) {
        RELAY_OUT = relay;
        PSTR1CON = pstr1; CCPR1L = duty1;
        PSTR2CON = pstr2; CCPR2L = duty2;
        current_duty1 = duty1; current_duty2 = duty2;
        fade_state = FADE_STABLE;
    } else {
        // CV5>=1の電球モードの場合、フェードエンジンを起動 (まずは旧現示のフェードアウトから)
        fade_state = FADE_OUT;
        fade_tick_timer = 0; 
    }
}

// --- 各現示のマクロ的呼び出し関数 ---
void set_signal_RED(void)    { is_ygf_active = 0; request_aspect(1, 0b00000111, cv_kido_r, 0x00, 0); }
void set_signal_YY(void)     { is_ygf_active = 0; request_aspect(0, 0b00001001, cv_kido_y, 0x00, 0); }
void set_signal_YELLOW(void) { is_ygf_active = 0; request_aspect(0, 0b00001011, cv_kido_y, 0x00, 0); }
void set_signal_YG(void)     { is_ygf_active = 0; request_aspect(0, 0b00000110, cv_kido_g, 0b00000010, cv_kido_y); }
void set_signal_GREEN(void)  { is_ygf_active = 0; request_aspect(0, 0b00000110, cv_kido_g, 0x00, 0); }
void set_signal_YGF(void)    { is_ygf_active = 1; ygf_toggle = 1; ygf_timer = 0; } // 点滅はバックグラウンドへ委譲

// 条件コードから実際の現示を決定
void update_signal_aspect(void) {
    if (in_service_mode) return; // プログラミング中は消灯を維持

    if (dir_code & 0x01) { set_signal_RED(); return; } // 逆方向なら赤強制

    switch(cv_signal_set) {
        case 21: if (!(genji_code & 0x01)) set_signal_RED(); else set_signal_GREEN(); break;
        case 31: if (!(genji_code & 0x01)) set_signal_RED(); else if (!(genji_code & 0x02)) set_signal_YELLOW(); else set_signal_GREEN(); break;
        case 41: if (!(genji_code & 0x01)) set_signal_RED(); else if (!(genji_code & 0x02)) set_signal_YY(); else if (!(genji_code & 0x04)) set_signal_YELLOW(); else set_signal_GREEN(); break;
        case 42: if (!(genji_code & 0x01)) set_signal_RED(); else if (!(genji_code & 0x02)) set_signal_YELLOW(); else if (!(genji_code & 0x04)) set_signal_YG(); else set_signal_GREEN(); break;
        case 43: if (!(genji_code & 0x01)) set_signal_RED(); else if (!(genji_code & 0x02)) set_signal_YELLOW(); else if (!(genji_code & 0x04)) set_signal_YG(); else if (!(genji_code & 0x08)) set_signal_YGF(); else set_signal_GREEN(); break;
        case 51: if (!(genji_code & 0x01)) set_signal_RED(); else if (!(genji_code & 0x02)) set_signal_YY(); else if (!(genji_code & 0x04)) set_signal_YELLOW(); else if (!(genji_code & 0x08)) set_signal_YG(); else set_signal_GREEN(); break;
        case 52: if (!(genji_code & 0x01)) set_signal_RED(); else if (!(genji_code & 0x02)) set_signal_YY(); else if (!(genji_code & 0x04)) set_signal_YELLOW(); else if (!(genji_code & 0x08)) set_signal_YG(); else if (!(genji_code & 0x10)) set_signal_YGF(); else set_signal_GREEN(); break;
        default: set_signal_RED(); break;
    }
}

// =========================================================
// バックグラウンド処理 (タイムアウト、点滅、電球フェード)
// メインループの隙間時間で実行され、時間軸を伴う処理を進行します。
// =========================================================
void process_background_tasks(void) {
    // 割り込みを使わないための工夫。メインループ滞在中に発生したタイマーフラグも回収
    if (INTCONbits.TMR0IF) { INTCONbits.TMR0IF = 0; tmr0_ticks++; }
    
    while (tmr0_ticks > 0) {
        tmr0_ticks--; 
        
        // --- 1. プログラミングモード無通信タイムアウト処理 ---
        if (in_service_mode) {
            if (service_timeout > 0) service_timeout--;
            else {
                // 一定時間(約1秒)パケットが来なかったら通常動作に自動復帰。
                // CV9へのリセットは行わず、プログラミングに入る直前の現示状態のまま
                // シームレスにフワッと復帰します。
                in_service_mode = 0; 
                update_signal_aspect();
            }
        }
        
        // --- 2. YGF(抑速) 点滅処理 ---
        if (is_ygf_active && !in_service_mode) {
            if (ygf_timer > 0) ygf_timer--;
            else {
                ygf_timer = (uint16_t)cv_ygf_rate * 12; // アセンブラ版との互換レート計算
                ygf_toggle ^= 1; 
                // 目標値をセットするだけ。電球モードON時(CV5>0)は、この指示が
                // 自動的に「じわーっ」と点滅する実物風フラッシャーに変換されます。
                if (ygf_toggle) request_aspect(0, 0b00000110, cv_kido_g, 0b00000010, cv_kido_y);
                else request_aspect(0, 0b00000110, 0, 0b00000010, 0); 
            }
        }
        
        // --- 3. 電球モード フェードエンジン (順次フェード方式) ---
        if (cv_fade_speed > 0 && fade_state != FADE_STABLE && !in_service_mode) {
            if (fade_tick_timer > 0) {
                fade_tick_timer--;
            } else {
                fade_tick_timer = cv_fade_speed; // CV5で設定したスピードウェイト
                
                // 1回の処理で変化させるPWM量 (キビキビ感と余韻のバランス調整済)
                uint8_t fade_step = 6; 

                // ステート1: 旧現示を完全に消灯させるまでPWMを減衰
                if (fade_state == FADE_OUT) {
                    uint8_t done = 1;
                    if (current_duty1 > fade_step) { current_duty1 -= fade_step; done = 0; }
                    else if (current_duty1 > 0) { current_duty1 = 0; done = 0; }
                    
                    if (current_duty2 > fade_step) { current_duty2 -= fade_step; done = 0; }
                    else if (current_duty2 > 0) { current_duty2 = 0; done = 0; }

                    CCPR1L = current_duty1; CCPR2L = current_duty2;

                    if (done) fade_state = FADE_SWITCH; // 完全に消灯したら次へ
                }
                // ステート2: 消灯状態で安全に極性（PSTR）を切り替え
                else if (fade_state == FADE_SWITCH) {
                    PSTR1CON = target_pstr1;
                    PSTR2CON = target_pstr2;
                    RELAY_OUT = target_relay;
                    fade_state = FADE_IN;
                }
                // ステート3: 新現示の目標輝度までPWMを上昇させる
                else if (fade_state == FADE_IN) {
                    uint8_t done = 1;
                    if (current_duty1 + fade_step < target_duty1) { current_duty1 += fade_step; done = 0; }
                    else if (current_duty1 < target_duty1) { current_duty1 = target_duty1; done = 0; }
                    
                    if (current_duty2 + fade_step < target_duty2) { current_duty2 += fade_step; done = 0; }
                    else if (current_duty2 < target_duty2) { current_duty2 = target_duty2; done = 0; }
                    
                    // 目標値が途中で下がった時の安全策(オーバーシュート防止)
                    if (current_duty1 > target_duty1) { current_duty1 = target_duty1; done = 0; }
                    if (current_duty2 > target_duty2) { current_duty2 = target_duty2; done = 0; }

                    CCPR1L = current_duty1; CCPR2L = current_duty2;

                    if (done) fade_state = FADE_STABLE; // 到達完了
                }
            }
        }
    }
}

// =========================================================
// プログラミング・パケット処理
// =========================================================
void process_service_mode(void) {
    if (packet_length == 3) { // PAGEモード
        uint8_t is_write = (data1 & 0x08) ? 1 : 0; 
        uint8_t reg_num = data1 & 0x07; 
        uint8_t cv_addr = 0, target_data = data2;
        
        if (reg_num <= 3) {         
            if (page_register == 0) { packet_ready = 0; return; }
            cv_addr = (page_register - 1) * 4 + reg_num;
        } 
        else if (reg_num == 4) cv_addr = 28; // CV29相当
        else if (reg_num == 5) { // ページレジスタ書き込み 
            if (is_write) page_register = data2;
            if (!is_write && page_register != data2) { packet_ready = 0; return; }
            send_ack(); packet_ready = 0; return;     
        } else return; 
        
        if (is_write) { send_ack(); write_cv(cv_addr, target_data); } 
        else { if (eeprom_read_byte(cv_addr) == target_data) send_ack(); }
    } 
    else if (packet_length == 4 && (data1 & 0xF4) == 0x74) { // DIRECTモード Byte
        uint8_t is_write = (data1 & 0x08) ? 1 : 0; 
        uint8_t cv_addr = data2, target_data = data3;
        if (is_write) { send_ack(); write_cv(cv_addr, target_data); } 
        else { if (eeprom_read_byte(cv_addr) == target_data) send_ack(); }
    }
    else if (packet_length == 4 && (data1 & 0xFC) == 0x78) { // DIRECTモード Bit
        uint8_t cv_addr = data2; 
        uint8_t k_bit = (data3 & 0x10) ? 1 : 0; // 1=書き込み, 0=読み出し
        uint8_t d_bit = (data3 & 0x08) ? 1 : 0; // 対象ビット値
        uint8_t bit_pos = data3 & 0x07;         // ビット位置(0〜7)
        
        uint8_t current_cv_val = eeprom_read_byte(cv_addr);
        uint8_t bit_mask = 1 << bit_pos;
        uint8_t actual_bit = (current_cv_val & bit_mask) ? 1 : 0;
        
        if (k_bit == 0) { if (actual_bit == d_bit) send_ack(); } 
        else {
            send_ack(); 
            if (d_bit) current_cv_val |= bit_mask; else current_cv_val &= ~bit_mask; 
            write_cv(cv_addr, current_cv_val);
        }
    }
}

void process_packet(void) {
    // XORチェックサムによるデータ整合性確認
    if ((data1 ^ data2 ^ data3 ^ data4 ^ data5 ^ data6) != 0) return;
    
    // サービスモード(プログラミングトラック)判定
    if ((data1 & 0xF0) == 0x70) {
        if (in_service_mode == 0) {
            in_service_mode = 1; is_ygf_active = 0;
            // サービスモード突入時は即座に全消灯しベース電流を下げる
            RELAY_OUT = 0; PSTR1CON = 0; PSTR2CON = 0; 
            CCPR1L = 0; CCPR2L = 0; fade_state = FADE_STABLE;
        }
        service_timeout = 2000; // 無通信タイムアウトを延長
        process_service_mode();
        return;
    }

    // アクセサリデコーダ命令(オペレーションモード)判定
    if ((data1 & 0xC0) == 0x80) {
        // 通常の命令が来たら即座にサービスモードを抜け、保存された状態を復元
        if (in_service_mode) {
            in_service_mode = 0;
            update_signal_aspect();
        }

        uint8_t d20 = data2 & 0xFE; // 下位1ビットを切り捨てたアドレスデータ
        uint8_t c_bit = data2 & 0x01; // C(ON) or T(OFF) の制御ビット
        uint8_t state_changed = 0;    // 現示の再評価が必要かのフラグ
        
        // 受信アドレスと各条件(方向/A〜E)の一致判定
        if (data1 == dir_adr1 && d20 == dir_adr2) {
            if (c_bit) dir_code |= 0x01; else dir_code &= ~0x01; state_changed = 1;
        }
        if (data1 == a_adr1 && d20 == a_adr2) {
            if (c_bit) genji_code |= 0x01; else genji_code &= ~0x01; state_changed = 1;
        }
        if (data1 == b_adr1 && d20 == b_adr2) {
            if (c_bit) genji_code |= 0x02; else genji_code &= ~0x02; state_changed = 1;
        }
        if (data1 == c_adr1 && d20 == c_adr2) {
            if (c_bit) genji_code |= 0x04; else genji_code &= ~0x04; state_changed = 1;
        }
        if (data1 == d_adr1 && d20 == d_adr2) {
            if (c_bit) genji_code |= 0x08; else genji_code &= ~0x08; state_changed = 1;
        }
        if (data1 == e_adr1 && d20 == e_adr2) {
            if (c_bit) genji_code |= 0x10; else genji_code &= ~0x10; state_changed = 1;
        }
        
        // 状態が変化していれば現示を更新
        if (state_changed) {
            update_signal_aspect();
            
            // 起動時モードが「レジューム(CV6=1)」の場合のみEEPROMに状態を保存
            if (cv_poweron_mode == 1) {
                save_current_state();
            }
        }
    }
}

// =========================================================
// メイン関数
// =========================================================
void main(void) {
    system_init();
    load_cv_cache();
    
    // --- 起動時の初期現示ロジック (CV6とCV9の連携) ---
    if (cv_poweron_mode == 1) {
        // CV6=1: レジュームモード (前回電源OFF時の状態をEEPROMから読み出す)
        dir_code = eeprom_read_byte(254);
        if (dir_code == 0xFF) dir_code = 0; // まっさらなPICへの保護
        
        genji_code = eeprom_read_byte(255);
        if (genji_code == 0xFF) genji_code = eeprom_read_byte(8); // 初回起動時はCV9を使用
    } else {
        // CV6=0: 固定モード (常にCV9の設定値で起動するアセンブラ互換モード)
        dir_code = 0;
        genji_code = eeprom_read_byte(8); // CV9 (初期現示コード)
    }
    
    in_service_mode = 0;
    update_signal_aspect();

    // メインループ: 決して止まらず、処理を回し続ける
    while(1) {
        receive_dcc(); // ポーリングによるDCC受信
        
        if (packet_ready) {
            packet_ready = 0;
            process_packet(); // 受信成功時の解析
        }
        
        process_background_tasks(); // フェードや点滅の進行
    }
}