/*******************************************************************************
 * ワンコインデコーダ６  - Ver 2.1 - for PIC12F1822
 * Copyright (C) 2007-2026  Nucky
 * 使用PICマイコン PIC12F1822　　内部発振16MHzモード
 *  
 * PIC12F1822 DCC モーター＆ファンクション 車載デコーダ
 * 
 * 2026.03.21 Ver2.0  ソースコードをC言語記述に変更（機能強化）
 * 2026.04.13 Ver2.1  バグ修正
 * 
 * 【デコーダの概要】
 * DCCコマンドステーションからのパケットを受信し、車両のモーターおよびライトを
 * 制御する多機能モバイルデコーダです。PAGEモード/DIRECTモード(Bit対応)での
 * 確実なCV読み書きに対応し、あらゆるコマンドステーションで安定動作します。
 *
 * 【主な搭載機能】
 * 1. 無音/高トルク駆動: ECCPハードウェアPWMを使用 (CV9で 245Hz〜31.4kHz可変)
 * 2. 3点折れ線速度カーブ: CV2(始動), CV5(最大), CV6(中間) による柔軟な速度調整
 * 3. モメンタム(慣性): CV3(加速), CV4(減速) による重量感のある滑らかな加減速
 * 4. キックスタート: CV64(時間), CV65(強度) による静止摩擦(スティクション)打破
 * 5. 両極性Fxモード: CV30により、モーター端子を市販車向けのライト出力化
 * 6. 瞬断対策: レールの汚れによる電源断時も、ライト状態と進行方向を記憶・復元
 * 
 * 【ピン配置 (PIC12F1822)】
 * 1 (VDD): 5V電源 (+)
 * 2 (RA5): モーター出力 2 (ECCP P1A / モータードライバへ)
 * 3 (RA4): モーター出力 1 (ECCP P1B / モータードライバへ)
 * 4 (RA3): MCLR (未使用・入力専用)
 * 5 (RA2): DCC信号入力
 * 6 (RA1): ライト出力 1 (汎用Fx / 初期値=前進ヘッドライト)
 * 7 (RA0): ライト出力 2 (汎用Fx / 初期値=後進テールライト)
 * 8 (VSS): GND (-)
 *******************************************************************************/
 
/*
 *【ソースコードのライセンスおよび利用条件について】
 * 本ソースコードの著作権はすべてNuckyに帰属します。
 * 本ソフトウェアは、以下の条件に基づいて提供されます。
 *
 * 1. [個人・非営利利用]
 * 個人の鉄道模型の範囲での利用、研究、改変は自由かつ無償で行えます。
 * 本コード、または改変したコードを公開・共有することも可能ですが、
 * その際も必ず非営利での公開とし、本ライセンス表記を残してください。
 *
 * 2. [商用利用（販売等）]
 * 本ソフトウェア（改変したものを含む）を書き込んだマイコンやデコーダ基板の販売、
 * 製品化、その他いかなる形であっても利益を得る目的（商用）で利用する場合は、
 * 必ず事前にご連絡ください。別途、有償の商用ライセンスをご契約いただきます。
 *
 * --- English Notice ---
 * Copyright (c) Nucky. All rights reserved.
 * 
 * 1. Personal/Non-Commercial Use: Free to use, modify, and share strictly 
 *    for personal, non-commercial hobby purposes.
 * 2. Commercial Use: ANY commercial use, including manufacturing, selling 
 *    products, or integrating this software into items for sale, is STRICTLY 
 *    PROHIBITED without prior permission. A paid commercial license is required. 
 *    Please contact the author via the website.
 *
 * --- 免責事項 / Disclaimer ---
 * 本プログラムの利用によって生じたデコーダ、車両、その他の機材の破損、不具合、
 * およびいかなる損害についても、作者は一切の責任を負いかねます。
 * This software is provided "AS IS", WITHOUT ANY WARRANTY. Use at your own risk.
*/

/*******************************************************************************
 * * * 【プログラムの概要と設計思想 (The "Why")】
 * 本プログラムは、PIC12F1822の限られたROM(2048ワード)で動作するDCCデコーダです。
 * 以下の「あえて泥臭く書いている」最適化ハックによって、高い安定性を実現しています。
 * * 1. 【割り込み完全排除・ポーリング方式】
 * DCC信号のアナログ的ノイズ(チャタリング)によるステートマシンの崩壊を防ぐため、
 * ハードウェア割り込み(INT/IOC)は一切使用せず、whileループでピンを監視し続けます。
 * * 2. 【Timer0 の二重参照による非同期バッファリング】
 * モメンタム時間を計るTimer0のフラグは、メインループだけでなく、パルス計測関数
 * (measure_pulse)の中など「複数箇所」で参照・ストックしています。
 * これは割り込みを使わない完全シングルスレッド環境において、数ミリ秒かかるDCC受信中
 * に経過したTimer0のカウント(0.5ms周期)を取りこぼさず、メインループに戻った時に
 * まとめて「時間を追いつかせる」ための、安全で意図的な設計です。
 * * 3. 【XORチェックの6バイト固定(ゼロパディング)】
 * パケット長の変動(3〜6バイト)に関わらず、XORチェックは6バイト固定で行っています。
 * これは受信バッファを毎回 {0} で初期化し、受信しなかった後半バイトを「0」で
 * 埋めているためです。「0をXORしても結果は変わらない」という数学的性質を利用し、
 * forループのオーバーヘッドを削減する意図的な高速化ハックです。
 * * 4. 【goto文の積極的利用】
 * プリアンブル検知において goto 文を使用しているのは、アセンブラからの移植性だけでなく
 * フラグ判定や多重ループ脱出にかかる「数マイクロ秒の遅れ」を極限まで削り、
 * コンパイラに「1命令の相対ジャンプ(BRA)」を強制的に吐き出させるためです。
 *******************************************************************************/

#include <xc.h>

// 内部クロック周波数(16MHz)の定義。
// XC8コンパイラが __delay_ms() 等の時間計算を正確に行うために必須です。
#define _XTAL_FREQ 16000000 

// =========================================================
// 1. 定義: モータードライバのハードウェア仕様選択
// =========================================================
// 使用するモータードライバICの特性に合わせてコメントを切り替えます。

//#define BD6231      // BD6231F-E2 等 (Fast Decay / 一般的な制御用)
#define TB67H450    // TB67H450FNG 等 (Slow Decay / ブレーキをかけながら回す低速安定型)

#if defined(BD6231)
    // BD6231仕様: PWMを出力していない側のピンを常に LOW(0V) に固定。
    // PWMは正論理(Active-High: 1の時にON)で駆動します。
    #define MOTOR_FIXED_LVL 0
    #define PWM_MODE_VAL    0x0C 
#elif defined(TB67H450)
    // TB67H450仕様: PWMを出力していない側のピンを常に HIGH(5V) に固定。
    // PWMは負論理(Active-Low: 0の時にON)で駆動します。
    #define MOTOR_FIXED_LVL 1
    #define PWM_MODE_VAL    0x0F 
#else
    #define MOTOR_FIXED_LVL 0
    #define PWM_MODE_VAL    0x0C 
#endif

// =========================================================
// 2. コンフィグレーション設定 (マイコンの基本ハードウェア設定)
// =========================================================
// CONFIG1
#pragma config FOSC = INTOSC    // 内部発振クロックを使用 (外部水晶を使わずピンを節約)
#pragma config WDTE = OFF       // ウォッチドッグタイマー無効 (DCC信号待ちでリセットされるのを防ぐ)
#pragma config PWRTE = ON       // パワーアップタイマー有効 (電源投入直後の電圧安定を待つ)
#pragma config MCLRE = OFF      // MCLRピン無効 (RA3ピンをリセット入力ではなく汎用入力にする)
#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      // 4倍PLL無効 (OSCCONレジスタで直接16MHzを設定するため)
#pragma config STVREN = ON      // スタックオーバーフローリセット有効
#pragma config BORV = LO        // ブラウンアウトリセットの電圧閾値を低め(約1.9V)に設定
#pragma config LVP = OFF        // 低電圧プログラミング無効 (ONだとRA3ピンが使えなくなるため必須)

// =========================================================
// 3. EEPROM CV初期値設定 (工場出荷時の設定値)
// =========================================================
// 配列のインデックスが (CV番号 - 1) に対応します。

// 【アドレス 0x00〜0x07】 CV1〜8: 基本設定 (アドレス、速度カーブ、バージョン等)
// [0]CV1(ｱﾄﾞﾚｽ)=3, [1]CV2(始動)=0, [2]CV3(加速)=0, [3]CV4(減速)=0
// [4]CV5(最大)=255, [5]CV6(中間)=0, [6]CV7(Ver)=21, [7]CV8(ID/ﾘｾｯﾄ)=156
__EEPROM_DATA(0x03, 0x00, 0x00, 0x00, 0xFF, 0x00, 21, 0x9C); 

// 【アドレス 0x08〜0x0F】 CV9〜16: モーターPWM設定
// [8]CV9(周波数)=0 (初期値は超高周波・無音設定)
__EEPROM_DATA(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); 

// 【アドレス 0x10〜0x17】 CV17〜24: ロングアドレス設定
// [16]CV17(上位)=192, [17]CV18(下位)=128 -> デフォルト0000相当
__EEPROM_DATA(0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); 

// 【アドレス 0x18〜0x1F】 CV25〜32: デコーダ基本設定
// [28]CV29(動作)=2(28Step有効), [29]CV30(両極性Fx)=0(通常モーターモード)
__EEPROM_DATA(0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00); 

// 【アドレス 0x20〜0x27】 CV33〜40: ファンクションマッピング (出力ピンに対するボタン割り当て)
// NMRA準拠ビット値: 1=F1, 2=F2, 4=F3, 8=F4, 16=F0前進, 32=F0後進
// [32]CV33(RA1割当)=16(F0前進), [34]CV35(RA0割当)=32(F0後進)
__EEPROM_DATA(0x10, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00); 

// 【アドレス 0x28〜0x2F】 CV41〜48: 瞬断対策メモリ1 (自動で読み書きされるため初期値0)
// [47]CV48 = 進行方向とF0〜F4のレジューム(復元)用メモリ
__EEPROM_DATA(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); 

// 【アドレス 0x30〜0x37】 CV49〜56: 瞬断対策メモリ2 (自動で読み書きされるため初期値0)
// [48]CV49 = F5〜F12のレジューム(復元)用メモリ
__EEPROM_DATA(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); 

// 【アドレス 0x38〜0x3F】 CV57〜64: キックスタート設定 (発進時の摩擦打破)
// [63]CV64(時間)=0 (1につき約2ms), [64]CV65(強度)=0 (上乗せPWM値)
__EEPROM_DATA(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); 

// プログラム内で読み書きしやすくするためのEEPROMアドレス定数定義
#define EEPROM_CV1   0x00   
#define EEPROM_CV2   0x01   
#define EEPROM_CV3   0x02   
#define EEPROM_CV4   0x03   
#define EEPROM_CV5   0x04   
#define EEPROM_CV6   0x05   
#define EEPROM_CV8   0x07   
#define EEPROM_CV9   0x08   
#define EEPROM_CV17  0x10   
#define EEPROM_CV18  0x11   
#define EEPROM_CV29  0x1C   
#define EEPROM_CV30  0x1D   
#define EEPROM_CV33  0x20   // RA1へのF0〜F4割当
#define EEPROM_CV34  0x21   // RA1へのF5〜F12割当
#define EEPROM_CV35  0x22   // RA0へのF0〜F4割当
#define EEPROM_CV36  0x23   // RA0へのF5〜F12割当
#define EEPROM_CV48  0x2F   // レジューム記憶1
#define EEPROM_CV49  0x30   // レジューム記憶2
#define EEPROM_CV64  0x3F   // キック時間
#define EEPROM_CV65  0x40   // キック強度

// =========================================================
// 4. 状態管理変数 (デコーダ内部の動作状態を保持)
// =========================================================

// --- ボタン・方向状態 (メモリ節約のためのビットパッキング) ---
// f0_4_dir: [空] [空] [F4] [F3] [F2] [F1] [F0] [進行方向(0=前,1=後)]
unsigned char f0_4_dir = 0; 
// f5_12:    [F12] [F11] [F10] [F9] [F8] [F7] [F6] [F5]
unsigned char f5_12 = 0;    

// --- 速度・加減速(モメンタム)・キックスタート管理 ---
unsigned char target_speed = 0;      // コマンドステーションから指示された目標速度 (0〜255)
unsigned char current_speed = 0;     // モメンタム等により徐々に変化している現在の内部速度 (0〜255)
unsigned char current_motor_dir = 0; // 実際のモーターの進行方向 (CV29の反転設定を加味した値)
unsigned char speed_tick = 0;        // モメンタム用。内部速度を1段変えるまでの残り待機時間
unsigned char motor_out_update = 0;  // 1にすると、メインループ内でモーターのハードウェア出力が更新されるフラグ
unsigned int  kick_timer = 0;        // キックスタートの残り時間を計るカウンタ (1につき約0.5ms)

// --- 【意図的ハック】 Timer0の取りこぼしを防ぐストック変数 ---
// パケット受信中などの「メインループ外」にいる間に経過したTimer0の
// ティック(0.5ms単位)をここに蓄積し、後でまとめて消化することで正確な時間を保ちます。
unsigned char tmr0_ticks = 0;

// --- プログラミング・瞬断対策用 ---
unsigned char page_register = 0;     // PAGEモードプログラミングにおける、現在の指定ページ
unsigned char last_saved_cv48 = 0;   // EEPROM書き換え寿命保護のため、前回保存したf0_4_dir状態を保持
unsigned char last_saved_cv49 = 0;   // 前回保存したf5_12状態を保持

// --- CVキャッシュ変数 ---
// EEPROMの読み出しは遅いため、起動時やCV書き込み時に一度だけRAM変数にコピーし、
// 走行中のメインループからはこのキャッシュを高速に参照します。
unsigned char cv1_c, cv2_c, cv3_c, cv4_c, cv5_c, cv6_c, cv9_c;
unsigned char cv17_c, cv18_c, cv29_c, cv30_c;
unsigned char cv33_c, cv34_c, cv35_c, cv36_c;
unsigned char cv64_c, cv65_c; 

// =========================================================
// 5. DCC受信データ変数 (パケットバッファ)
// =========================================================
// 受信した生のDCCパケット(最大6バイト)を保持します。
unsigned char data1 = 0; // 第1バイト (ショートアドレス または ロングアドレス上位)
unsigned char data2 = 0; // 第2バイト (命令 または ロングアドレス下位)
unsigned char data3 = 0; // 第3バイト
unsigned char data4 = 0; // 第4バイト
unsigned char data5 = 0; // 第5バイト
unsigned char data6 = 0; // 第6バイト (エラー検出/XORチェックサム用)
unsigned char packet_ready = 0;  // 1=パケット受信完了。メインループでデータ処理を開始する合図
unsigned char packet_length = 0; // 受信したパケットの有効なバイト数(3〜6)

// =========================================================
// 関数プロトタイプ宣言
// =========================================================
void init_hardware(void);
void update_pwm_config(void);
void load_cv_cache(void);
void write_cv(unsigned char cv_addr, unsigned char cv_data);
void load_saved_state(void);
void save_state_if_changed(void);
void func_out(void);
unsigned char calculate_speed_curve(unsigned char speed);
void motor_out(void); 
void send_ack(void);
void process_service_mode(void);
void process_operation_mode(void);
void process_momentum(void);

// =========================================================
// 【関数】 DCCパルス幅計測 (ポーリング＋Timer1方式)
// レールから入力されるRA2ピンのHigh/Lowを監視し、1つの波形の長さを測ります。
// 戻り値: 1(短い=DCCの'1'), 0(長い=DCCの'0'), 2(タイムアウト=ノイズ/無信号)
// =========================================================
unsigned char measure_pulse(void) {
    // 【意図的ハック】 信号計測の隙間にもTimer0のオーバーフローを監視し、
    // 割り込みを使わない完全シングルスレッド環境において時間の経過を逃さずストックする。
    // ※この関数と process_momentum() は決して同時には走らないため、レースコンディションは起きない。
    if (INTCONbits.TMR0IF) {
        INTCONbits.TMR0IF = 0;
        tmr0_ticks++;
    }

    // 1. RA2がLowになるのを待つ (DCCパルスの立ち上がりエッジを正確に捉えるため)
    TMR1H = 0; TMR1L = 0;
    while(PORTAbits.RA2 == 1) { 
        // 16MHz設定時、Timer1(プリスケーラ1:4)は 1カウント=1us。
        // TMR1Hが1になる(256us経過する)までHighのままならタイムアウト(エラー2)で抜ける。
        if (TMR1H > 0) return 2; 
    }
    
    // 2. RA2がHighになる(パルスの開始)のを待つ
    TMR1H = 0; TMR1L = 0;
    while(PORTAbits.RA2 == 0) { 
        if (TMR1H > 0) return 2; 
    }
    
    // 3. パルスの長さ(Highの期間)を実測する
    TMR1H = 0; TMR1L = 0;
    while(PORTAbits.RA2 == 1) { 
        if (TMR1H > 0) return 2; 
    }
    
    // 4. パルス幅の判定 (75usを閾値とする)
    // '1'は片波約58us、'0'は約100us。75usより短ければ'1'、長ければ'0'と判定する。
    unsigned char pulse = TMR1L;
    if (pulse < 75) return 1;
    return 0;
}

// =========================================================
// 【関数】 DCCパケット受信処理 (状態遷移ループ)
// プリアンブル(開始合図)を探し、最大6バイトのデータをビット単位で組み立てます。
// =========================================================
void receive_dcc(void) {
    packet_ready = 0;
    unsigned char bit_val;
    
    // 【意図的ハック】 バッファを確実に 0 で初期化する。
    // 後にこのバッファをフルコピーすることで、受信しなかった後半バイト(data4〜6)
    // が確実に「0埋め(ゼロパディング)」され、XOR計算時のゴミを排除できる。
    unsigned char buf[6] = {0}; 
    unsigned char p_count = 0;
    
// 【意図的ハック】 goto文の使用
// フラグによるループ制御のオーバーヘッド(数マイクロ秒)を排除し、
// コンパイラに1命令のジャンプ(BRA)を強制させて高速化を図るため。
WAIT_PREAMBLE:
    p_count = 0;
    
PREAMBLE_LOOP:
    // プリアンブル('1'の連続)を探す
    bit_val = measure_pulse();
    if (bit_val == 2) return; // タイムアウト時はパケット受信を諦め、メインループへ帰る
    
    if (bit_val == 1) {
        p_count++; // '1'ならカウントアップ
        goto PREAMBLE_LOOP;
    } else {
        // '0'が来た時、直前に'1'が10回以上続いていなければノイズと判断し探し直す
        if (p_count < 10) goto WAIT_PREAMBLE; 
    }
    
    // --- データバイトの受信 (最大6バイト) ---
    for (packet_length = 0; packet_length < 6; packet_length++) {
        
        // 8ビット分のデータを読み込み、左シフトして1バイトに組み立てる
        for (unsigned char i = 0; i < 8; i++) {
            bit_val = measure_pulse();
            if (bit_val == 2) return;
            buf[packet_length] = (buf[packet_length] << 1) | bit_val;
        }
        
        // 8ビット読んだ直後に来る「セパレータビット」を確認
        bit_val = measure_pulse();
        if (bit_val == 2) return;
        
        if (bit_val == 1) {
            // セパレータが '1' (エンドビット) なら、このパケットはここで終了
            packet_length++; 
            if (packet_length >= 3) {
                // 最低3バイト揃っていれば有効。
                // パケット長に関わらず、0埋めされたバッファをフルコピーする
                data1 = buf[0]; data2 = buf[1]; data3 = buf[2];
                data4 = buf[3]; data5 = buf[4]; data6 = buf[5];
                packet_ready = 1; 
            }
            return;
        }
        // セパレータが '0' なら、まだ次のバイトが続くのでループを継続する
    }
}

// =========================================================
// 【関数】 ハードウェア初期化
// マイコン起動時に1度だけ呼ばれ、ピンの役割やタイマーを設定します。
// =========================================================
void init_hardware(void) {
    OSCCON = 0x78;      // 内部クロックを16MHzに設定
    ANSELA = 0x00;      // 全てのピンをデジタルI/Oに切り替え
    TRISA  = 0x0C;      // RA2(DCC入力), RA3(MCLR)を入力ピンに、他を出力ピンに設定
    LATA   = 0x00;      // 出力ピンの初期状態をすべてLow(0V)にする

    APFCON = 0x03;      // PWM出力ピンの割り当てを変更 (P1AをRA5へ、P1BをRA4へ)

    // ECCP1 (ハードウェアPWMモジュール) の設定
    CCP1CON = PWM_MODE_VAL; // 冒頭で定義したドライバ用論理(正/負論理)をセット
    CCPR1L = 0;             // 初期Duty(速度)は0%
    PSTR1CON = 0x00;        // ステアリング(PWMの出力先)を一旦全て無効化し安全を確保

    // Timer0 の設定 (モメンタムやキックスタートの時間をカウントするため)
    OPTION_REG = 0x82;      // プリスケーラ1:8。16MHz駆動時、約0.512ms周期でフラグが立つ
    INTCONbits.TMR0IF = 0;  // 割り込みフラグをクリア

    // Timer1 の設定 (DCCパルス幅の計測用)
    T1CONbits.TMR1CS = 0;   // クロック源を内部クロック(Fosc/4)に設定
    T1CONbits.T1CKPS = 2;   // プリスケーラ1:4。これにより「1カウント＝1us」になる
    T1CONbits.TMR1ON = 1;   // Timer1を起動

    // 割り込みはノイズによる暴走を防ぐため一切使用しない(ポーリング方式)
    INTCONbits.PEIE = 0;  
    INTCONbits.GIE  = 0;  
}

// =========================================================
// 【関数】 PWM周波数の更新
// CV9の設定値に応じてモーターから鳴る発振音(周波数)を調整します。
// =========================================================
void update_pwm_config(void) {
    unsigned char cv9_val = cv9_c & 0x0F; 
    
    // PR2はPWMの1周期の長さを決めるレジスタ
    // bit3が0なら高周波寄り(126ステップで100%)、1なら低周波寄り(254ステップで100%)
    if ((cv9_val & 0x08) == 0) PR2 = 126;
    else                       PR2 = 254;
    
    // Timer2のプリスケーラ(分周比)を変更し、最終的なPWM周波数を決定
    unsigned char ps = cv9_val & 0x03; 
    if (ps == 0)      T2CON = 0x04; // 1:1  (無音域: 約31.4kHz)
    else if (ps == 1) T2CON = 0x05; // 1:4  (高音域: 約7.8kHz)
    else if (ps == 2) T2CON = 0x06; // 1:16 (中音域: 約2.0kHz)
    else              T2CON = 0x07; // 1:64 (低音域: 約490Hz) 極低速重視
}

// =========================================================
// 【関数】 EEPROMキャッシュの読み込み
// 起動時やCV書き込み後に呼ばれ、EEPROMの値をRAMに展開します。
// =========================================================
void load_cv_cache(void) {
    cv1_c  = eeprom_read(EEPROM_CV1);  cv2_c  = eeprom_read(EEPROM_CV2); 
    cv3_c  = eeprom_read(EEPROM_CV3);  cv4_c  = eeprom_read(EEPROM_CV4); 
    cv5_c  = eeprom_read(EEPROM_CV5);  cv6_c  = eeprom_read(EEPROM_CV6); 
    cv9_c  = eeprom_read(EEPROM_CV9);  cv17_c = eeprom_read(EEPROM_CV17);
    cv18_c = eeprom_read(EEPROM_CV18); cv29_c = eeprom_read(EEPROM_CV29);
    cv30_c = eeprom_read(EEPROM_CV30); cv33_c = eeprom_read(EEPROM_CV33);
    cv34_c = eeprom_read(EEPROM_CV34); cv35_c = eeprom_read(EEPROM_CV35);
    cv36_c = eeprom_read(EEPROM_CV36); cv64_c = eeprom_read(EEPROM_CV64); 
    cv65_c = eeprom_read(EEPROM_CV65); 
    
    // 【意図的ハック】 新品のマイコンはEEPROMが 0xFF で初期化されているため、
    // キックスタート設定が 0xFF の場合は安全のため 0(無効) として扱う。
    if (cv64_c == 0xFF) cv64_c = 0;
    if (cv65_c == 0xFF) cv65_c = 0;
    
    update_pwm_config(); // 直ちにハードウェアへ反映
}

// =========================================================
// 【関数】 CVの書き込み処理
// =========================================================
void write_cv(unsigned char cv_addr, unsigned char cv_data) {
    if (cv_addr == EEPROM_CV8) {
        // NMRA規格: メーカーID(CV8)に 8 を書き込まれたら「ファクトリーリセット」
        if (cv_data == 8) {
            eeprom_write(EEPROM_CV1,  3);     
            eeprom_write(EEPROM_CV17, 0xC0);  
            eeprom_write(EEPROM_CV18, 0x80);  
            eeprom_write(EEPROM_CV29, 2);     
        }
    } else {
        eeprom_write(cv_addr, cv_data);
    }
    load_cv_cache(); // 書き換え後、すぐにキャッシュへ反映させる
}

// =========================================================
// 【関数】 レジューム機能: 状態の読み込み
// 電源瞬断から復帰した際、直前のライト状態や進行方向を復元します。
// =========================================================
void load_saved_state(void) {
    f0_4_dir = eeprom_read(EEPROM_CV48);
    f5_12    = eeprom_read(EEPROM_CV49);
    
    if (f0_4_dir == 0xFF) { f0_4_dir = 0; eeprom_write(EEPROM_CV48, 0); }
    if (f5_12 == 0xFF)    { f5_12 = 0;    eeprom_write(EEPROM_CV49, 0); }
    
    last_saved_cv48 = f0_4_dir;
    last_saved_cv49 = f5_12;
    
    // 復元した進行方向と、CV29の「反転フラグ」をXORしてモーターの極性を決定
    current_motor_dir = (f0_4_dir & 0x01) ^ (cv29_c & 0x01);
}

// =========================================================
// 【関数】 レジューム機能: 状態の保存
// 状態が変化した時「だけ」EEPROMに書き込みます。(EEPROM書き換え寿命保護)
// =========================================================
void save_state_if_changed(void) {
    if (f0_4_dir != last_saved_cv48) { eeprom_write(EEPROM_CV48, f0_4_dir); last_saved_cv48 = f0_4_dir; }
    if (f5_12 != last_saved_cv49)    { eeprom_write(EEPROM_CV49, f5_12);    last_saved_cv49 = f5_12; }
}

// =========================================================
// 【関数】 ACKパルス送信 (プログラミングトラックでの応答)
// コマンドステーションに「読書成功」を知らせるため、モーターとライトを
// 瞬間的にONにして、NMRA規格で定められた電流(60mA以上を6ms間)を消費します。
// =========================================================
void send_ack(void) {
    unsigned char backup_lata = LATA; 
    unsigned char backup_duty = CCPR1L;
    unsigned char backup_pstr = PSTR1CON;
    
    LATA4 = MOTOR_FIXED_LVL; 
    LATA |= 0x03;            // ライト出力(RA0,RA1)を強制ON
    PSTR1CON = 0x01;         // モーター出力を有効化
    CCPR1L = PR2;            // PWM 100% (フルスロットル)
    
    __delay_ms(6);           // 6ミリ秒間電流を流し続ける
    
    CCPR1L = backup_duty;
    PSTR1CON = backup_pstr;
    LATA = backup_lata;
}

// =========================================================
// 【関数】 ファンクション(ライト)出力処理
// ユニバーサルマッピング。CV33〜36の設定に基づいて、押されたボタンを
// 任意のピン(RA0/RA1)へ出力します。
// =========================================================
void func_out(void){
    // F1〜F4の状態を抽出し、比較しやすいようビット位置を調整
    unsigned char state_A = (f0_4_dir >> 2) & 0x0F; 
    unsigned char actual_dir = (f0_4_dir & 0x01) ^ (cv29_c & 0x01); 
    
    // F0(ヘッド/テール)ボタンがONの時、進行方向に応じてビットを合成
    if (f0_4_dir & 0x02) {
        if (actual_dir == 0) state_A |= 0x10; // 前進ならビット4 (値:16)
        else                 state_A |= 0x20; // 後進ならビット5 (値:32)
    }
    
    // 【意図的ハック】 ビット演算による超軽量マッピング
    // 多数の if文を排除し、論理和(OR)と論理積(AND)だけで全ボタンの割り当てを判定する。
    LATA1 = ((cv33_c & state_A) || (cv34_c & f5_12)) ? 1 : 0;
    LATA0 = ((cv35_c & state_A) || (cv36_c & f5_12)) ? 1 : 0;
}

// =========================================================
// 【関数】 3点折れ線速度カーブ計算
// コントローラのスロットル値(0〜255)を、CV2(始動), CV5(最大), CV6(中間)
// に基づいて実際のモーター出力電圧値に変換します。
// =========================================================
unsigned char calculate_speed_curve(unsigned char speed) {
    if (speed == 0) return 0;
    unsigned int v_start = cv2_c;
    unsigned int v_high  = cv5_c;
    unsigned int v_mid   = cv6_c;
    
    // CV6(中間)が未設定(0)の場合は、始点と終点の真ん中を自動計算
    if (v_mid == 0) v_mid = (v_start + v_high) >> 1;
    
    unsigned int result = 0;
    
    if (speed <= 128) {
        // 前半: 始動電圧〜中間電圧 までを直線補間
        if (v_mid >= v_start) result = v_start + (((unsigned int)speed * (v_mid - v_start)) >> 7);
        else result = v_start; 
    } else {
        // 後半: 中間電圧〜最大電圧 までを直線補間
        if (v_high >= v_mid) result = v_mid + (((unsigned int)(speed - 128) * (v_high - v_mid)) >> 7);
        else result = v_mid; 
    }
    
    if (result > 255) result = 255;
    return (unsigned char)result;
}

// =========================================================
// 【関数】 モーター PWM出力 / 両極性Fx 出力
// 計算された速度と進行方向に基づいて、ハードウェアPWMを操作します。
// =========================================================
void motor_out(void) {
    if (cv30_c == 2) {
        // --- 両極性Fxモード ---
        // モーター端子をライト出力として使う特殊モード(市販の無改造ライト基板用)。
        // 速度に関係なく、F0ボタンに連動してPWM100%出力を行います。
        unsigned char actual_dir = (f0_4_dir & 0x01) ^ (cv29_c & 0x01); 
        unsigned char f0_on = (f0_4_dir & 0x02); 
        if (f0_on) {
            CCPR1L = PR2; 
            if (actual_dir == 0) { LATA4 = MOTOR_FIXED_LVL; PSTR1CON = 0x01; } 
            else                 { LATA5 = MOTOR_FIXED_LVL; PSTR1CON = 0x02; } 
        } else {
            PSTR1CON = 0x00; LATA &= ~(0x30); CCPR1L = 0; // 消灯
        }
    } 
    else {
        // --- 通常のモーター制御モード ---
        if (current_speed == 0 && kick_timer == 0) {
            PSTR1CON = 0x00; LATA &= ~(0x30); CCPR1L = 0; // 完全停止
        } else {
            unsigned int calc_spd = calculate_speed_curve(current_speed);
            
            // 発進時のみ、キックスタート電圧(CV65)を上乗せして摩擦を打ち破る
            if (kick_timer > 0) calc_spd += cv65_c; 
            
            // PWM周波数に合わせてDuty比の最大値をスケーリング
            unsigned char duty;
            if ((cv9_c & 0x08) == 0) {
                duty = calc_spd >> 1; 
                if (duty > 126) duty = 126; 
            } else {
                if (calc_spd > 254) duty = 254; 
                else duty = (unsigned char)calc_spd;
            }
            
            CCPR1L = duty; // PWMレジスタへ書き込み

            // PSTR1CON(ステアリングレジスタ)でHブリッジの極性を切り替える
            if (current_motor_dir == 0) { LATA4 = MOTOR_FIXED_LVL; PSTR1CON = 0x01; } 
            else                        { LATA5 = MOTOR_FIXED_LVL; PSTR1CON = 0x02; } 
        }
    }
}

// =========================================================
// 【関数】 プログラミングモード処理 (Service Mode)
// PAGEモード、DIRECTモード(Byte / Bit)の両方に対応しています。
// =========================================================
void process_service_mode(void) {
    if ((data1 & 0xF0) != 0x70) return; 
    
    if (packet_length == 3) {
        // --- PAGEモード (3バイト構成) ---
        unsigned char is_write = (data1 & 0x08) ? 1 : 0; 
        unsigned char reg_num = data1 & 0x07; 
        unsigned char cv_addr = 0;
        unsigned char 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 = EEPROM_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 cv_addr = reg_num; 
        
        if (is_write) {
            send_ack(); // タイムアウトを防ぐため、EEPROMへ書き込む前にすぐACKを返す
            write_cv(cv_addr, target_data); 
        } else {
            // 読み出し(Verify)時は、持っている値と一致した時のみACKを返す
            if (eeprom_read(cv_addr) == target_data) send_ack(); 
        }
    } 
    else if (packet_length == 4 && (data1 & 0xF4) == 0x74) {
        // --- DIRECTモード Byte (4バイト構成) ---
        unsigned char is_write = (data1 & 0x08) ? 1 : 0; 
        unsigned char cv_addr = data2; 
        unsigned char target_data = data3;
        if (is_write) {
            send_ack();      
            write_cv(cv_addr, target_data); 
        } else {
            if (eeprom_read(cv_addr) == target_data) send_ack(); 
        }
    }
    else if (packet_length == 4 && (data1 & 0xFC) == 0x78) {
        // --- DIRECTモード Bit (スマホ系コントローラ等で必須) ---
        unsigned char cv_addr = data2; 
        unsigned char k_bit = (data3 & 0x10) ? 1 : 0; // 1=書き込み, 0=読み出し
        unsigned char d_bit = (data3 & 0x08) ? 1 : 0; // 対象ビット値
        unsigned char bit_pos = data3 & 0x07;         // ビット位置(0〜7)
        
        unsigned char current_cv_val = eeprom_read(cv_addr);
        unsigned char bit_mask = 1 << bit_pos;
        unsigned char 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);
        }
    }
}

// =========================================================
// 【関数】 通常運転パケット処理 (Operation Mode)
// メイン線路上での速度・方向・ファンクション指示を処理します。
// =========================================================
void process_operation_mode(void) {
    unsigned char inst = 0, next_d = 0, cv_addr = 0, cv_data = 0;
    
    // アドレスの一致確認 (CV29の設定でショートかロングかを判断)
    if ((cv29_c & 0x20) == 0) { 
        if (data1 != cv1_c) return; 
        inst = data2; next_d = data3; cv_addr = data3; cv_data = data4;
    } else {
        if (data1 != cv17_c || data2 != cv18_c) return;
        inst = data3; next_d = data4; cv_addr = data4; cv_data = data5;
    }
    
    if ((inst & 0xFC) == 0xEC) {
        // OPSモード(POM: 走行中プログラミング)。ACK送信なしでサイレントに書き込む
        write_cv(cv_addr, cv_data); 
    } 
    else if ((inst & 0xC0) == 0x40 || inst == 0x3F) {
        // --- 速度と進行方向の命令 ---
        unsigned char old_target = target_speed;
        
        if (inst == 0x3F) {
            // 128ステップモード
            if(next_d & 0x80) f0_4_dir &= ~0x01; else f0_4_dir |= 0x01; 
            unsigned char s = next_d & 0x7F;
            if (s == 1) { target_speed = 0; current_speed = 0; motor_out_update = 1; } // E-Stop
            else if (s == 0) target_speed = 0; 
            else target_speed = (s - 1) * 2;   // 1〜126のステップ値を 0〜255に拡張
        } else {
            // 14/28ステップモード
            if(inst & 0x20) f0_4_dir &= ~0x01; else f0_4_dir |= 0x01; 
            unsigned char s = inst & 0x0F;
            unsigned char c = (inst & 0x10) >> 4;
            unsigned char step28 = (s << 1) | c;
            if (step28 == 1) { target_speed = 0; current_speed = 0; motor_out_update = 1; } 
            else if (step28 == 0) target_speed = 0; 
            else target_speed = (step28 - 1) * 9; 
        }
        
        // 停止状態から発進した瞬間を検知し、キックスタート用タイマー(CV64)をセット
        if (old_target == 0 && target_speed > 0) {
            kick_timer = (unsigned int)cv64_c << 2; // 約2ms単位のカウント値に変換
            if (kick_timer > 0) motor_out_update = 1;
        }
    } 
    else if ((inst & 0xE0) == 0x80) {
        // Group1 ファンクション命令 (F0〜F4)
        unsigned char tmp = (inst & 0x0F) << 2;
        if(inst & 0x10) tmp |= 0x02; 
        f0_4_dir = (f0_4_dir & 0x01) | tmp; 
    } 
    else if ((inst & 0xF0) == 0xB0) {
        // Group2 ファンクション命令 (F5〜F8)
        f5_12 = (f5_12 & 0xF0) | (inst & 0x0F); 
    } 
    else if ((inst & 0xF0) == 0xA0) {
        // Group2 ファンクション命令 (F9〜F12)
        f5_12 = (f5_12 & 0x0F) | ((inst & 0x0F) << 4); 
    }
}

// =========================================================
// 【関数】 加減速(モメンタム) と キックスタートの裏回し処理
// =========================================================
void process_momentum(void) {
    // メインループ滞在中に発生したタイマーフラグも漏らさず回収する
    if (INTCONbits.TMR0IF) {
        INTCONbits.TMR0IF = 0;
        tmr0_ticks++;
    }
    
    // 【意図的ハック】 ストックされたタイマーティック(0.5ms単位)を全て消化。
    // DCC受信中などメインループ外で取りこぼした時間をここで正しく進める。
    while (tmr0_ticks > 0) {
        tmr0_ticks--;
        
        if (kick_timer > 0) {
            kick_timer--;
            // キック終了時に通常電圧へ下げるため出力を更新
            if (kick_timer == 0) motor_out_update = 1; 
        }
        
        if (speed_tick > 0) speed_tick--; 
        
        // カウントが0になったら、内部速度を1段変える (CV値1=約0.13秒スケール)
        if (speed_tick == 0) {
            unsigned char actual_target_dir = (f0_4_dir & 0x01) ^ (cv29_c & 0x01);
            
            if (current_motor_dir != actual_target_dir) {
                // 安全保護: 走行中に逆転命令が来た場合は、停止するまでCV4レートで強制減速
                kick_timer = 0; 
                if (current_speed > 0) {
                    if (cv4_c == 0) current_speed = 0; 
                    else { current_speed--; speed_tick = cv4_c; } 
                } else {
                    current_motor_dir = actual_target_dir; 
                }
                motor_out_update = 1;
            } 
            else if (current_speed < target_speed) {
                // 加速: 目標速度までCV3レートで徐々に上げる
                if (cv3_c == 0) current_speed = target_speed;
                else { current_speed++; speed_tick = cv3_c; } 
                motor_out_update = 1;
            } 
            else if (current_speed > target_speed) {
                // 減速: 目標速度までCV4レートで徐々に下げる
                if (cv4_c == 0) current_speed = target_speed;
                else { current_speed--; speed_tick = cv4_c; } 
                motor_out_update = 1;
            }
        }
    }
}

// =========================================================
// 【メイン関数】 プログラムの心臓部
// =========================================================
void main(void) {
    init_hardware();    
    load_cv_cache();    
    load_saved_state(); 
    
    // 起動直後の出力反映 (瞬断からのレジューム)
    func_out();       
    motor_out();        

    while(1) {
        // DCCパケットの待機と受信 (信号が途絶えたらすぐに戻ってくる)
        receive_dcc(); 
        
        if (packet_ready) {
            packet_ready = 0; 
            
            // 【意図的ハック】 6バイト固定XOR (ゼロパディング前提)
            // packet_length によるforループ等を用いないことで、計算を高速化し
            // バグの入り込む余地をなくしている。使われない後半バイトは必ず0である。
            if ((data1 ^ data2 ^ data3 ^ data4 ^ data5 ^ data6) == 0) {
                
                // リセットパケット (00 00 00) の処理
                if (data1 == 0x00 && data2 == 0x00) {
                    target_speed = 0;
                    current_speed = 0;
                    motor_out_update = 1;
                }
                else if ((data1 & 0xF0) == 0x70) {
                    process_service_mode();   
                }
                else {
                    process_operation_mode(); 
                }
            }
            
            func_out(); 
            save_state_if_changed(); 
            
            // 両極性Fxモード時は即時反映
            if (cv30_c == 2) motor_out_update = 1;
        }

        // 加減速とキックスタートのバックグラウンド計算
        process_momentum();

        // 目標速度やモメンタムによって出力すべき値に変更があった場合のみPWMを更新
        if (motor_out_update) {
            motor_out();
            motor_out_update = 0;
        }
    }
}