ステップタコ まだ続きます

タコメータ 電子工作
タコメータ

PICを使用したX27系ステッピングタコメータのその後である。
RZ350RRとその他の機種用(無印RZ250など)パルスがプラスかマイナスかという区分でのハード設計は約2年前に最良解を完成してる。
タコメータを動かすパルス取得に関しても、車体で発生した点火のためのエネルギーを100%燃焼に使用できるようにするため、タコメータ駆動用パルス信号はIGコイルの1次側から分捕らず2次側の点火時の漏れパルスを増幅し成形取得しています。これにより点火エネルギーを無駄にしない構造としています。

現在、何をしてるかというと、ソフトウェアの改造で気に食わないところがありずっと改良している。しかしながら、プログラミング手法に懐が広くないための2年間ほどは迷走と言って良いと思う。
このあたりで自分が納得しないと、結局CPUを高性能なものにしてバージョンアップしたところで何も進展しないと考えている。

現在進めているのは、低回転から高回転を通して針の揺らぎを押さえて視覚的に如何にスムーズな動きを見せるかである。
ステッピングモータを使ったタコメータは挙動が正確すぎるので、低回転でのフラフラした針の動きや回転上昇中の俊敏な動きに視覚的違和感を感じると言うもので、それは鈍重な純正タコメータに慣れすぎているのが原因だと思う。
今は、その中間的な動きを模倣できないかプログラミングでどう表現すれば良いか長いスパンで考えています。
なお、本家のソースを基準にして、1クランク1パルスで動くソースとなっています。RZRのように1クランク2パルスに関してはハードウェアで吸収済みです。

以下、1月分の改良途中のソース(バグ有ります)
考え方のコメントを入れてるので何をしたいか、なんとなく分かるのではないかと思います。
しかしながら、ここまで時間がかかるとなるとプログラミングセンスだけは今らどうしようもないですね。

*
 * STEP SiZE
//4UO 0-1000rpm 12deg  1000rpm- 21deg  922=12*3(36)+21*14*3 (15000rpm)12000rpm 242deg
//0-1000  12deg
//0-12000  242deg
//1000-12000 230deg

//29L 0-1000rpm 13deg  1000prm- 20deg  880=13*3(40)+20*14*3
//51L 0-1000rpm 10deg  1000rpm- 20deg  870=10*3+20*14*3
//5KE 0-7000rpm 10deg  7000rpm- 25deg  810=10*3*7+25*3*8

 ------------------------------------------------------------------
              
 *  PIC12F1822
              +--------+
          Vdd |1      8| GND
  INTF -> RA5 |2      7| RA0 -> B0
  A0  <- RA4  |3      6| RA1 -> B1
          RA3 |4      5| RA2 -> A1
              +--------+
-------------------------------------------------------------------*/

/* ------------------------------------------------------------------
 *  Tachometer control for PIC12F1822 + X27.168
 *  改良点:
 *  - 可変スムージング (filtered_rpm)
 *  - デッドバンドによるピクつき抑制
 *  - RPMに応じたステップ送り速度
 *  - RPMに応じた Timer2 周期(加減速モード)
 *  - 既存のデモ動作は高速側のパラメータをそのまま維持
 * ------------------------------------------------------------------ */

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

/* 関数TEST TYPE */
void __interrupt() isr(void);
void rotate(void);
void initialize_system(void);
uint16_t rpm_to_step(uint8_t rpm);
void initialize_motor(void);

/* -------------------------------------------------------------------
 * CONFIG BITS
 * ------------------------------------------------------------------- */
#pragma config FOSC=INTOSC      // 内蔵オシレータ
#pragma config PLLEN=OFF        // PLL 無効
#pragma config WDTE=OFF         // ウォッチドッグタイマ無効
#pragma config MCLRE=OFF        // MCLR ピン無効
#pragma config CLKOUTEN=OFF     // CLKOUT 無効
#pragma config IESO=OFF         // 内部/外部スイッチオーバ無効
#pragma config FCMEN=OFF        // フェイルセーフクロックモニタ無効
#pragma config CP=OFF           // コードプロテクト無効
#pragma config CPD=OFF          // データコードプロテクト無効
#pragma config BOREN=ON         // ブラウンアウトリセット有効
#pragma config WRT=OFF          // セルフライトプロテクト無効
#pragma config STVREN=ON        // スタックオーバーフロー/アンダーフローリセット有効
#pragma config BORV=LO          // BOR 電圧 Low
#pragma config LVP=OFF          // 低電圧書き込み無効

/* -------------------------------------------------------------------
 * SETUP
 * ------------------------------------------------------------------- */
/* クランク1回転あたりのパルス数設定(最終的にシングルに固定)
 * TYPE_ONE_PULSE:  SDR/DT200/TZR250(3XV)/etc
 * TYPE_TWO_PULSE:  RZ250R/TZR250(1KT-3MA)/etc
 */
#define TYPE_ONE_PULSE      // <<setting>> Select ONE or TWO
// #define TYPE_TWO_PULSE   // <<setting>> Select ONE or TWO

/* 1000rpm の位置(ステップ数)
 * 1度 = 3ステップ
 * 例: 0~1000rpm が 15度 → 3x15 = 45ステップ
 */
#define POS_RPM1000 (30)        // <<setting>>

/* 1000rpm あたりのステップ数
 * 1度 = 3ステップ
 */
#define STEP_OF_1000RPM (63)    // <<setting>>

/* -------------------------------------------------------------------
 * 定数・マクロ
 * ------------------------------------------------------------------- */
#define ACC_MODE_LO         (6)
#define ACC_MODE_HI         (2)     // 針が重い場合は増やす
#define _XTAL_FREQ  32000000        // __delay_ms/__delay_us 用

#define RO_TURN_R (1)
#define RO_TURN_L (-1)
#define RO_STOP   (0)

#define MT_BUFFER_COUNT     (5)
#define MT_BUFFER_COUNT_END (MT_BUFFER_COUNT-1)
#define MAX_X27_COUNT_STEP  (940)   // 315度 x 3ステップ ≒ 945

// グローバル変数
uint8_t  g_rpm = 0;               // 生のRPM値(0~255, 10=1000rpm)
uint16_t g_motor_pos_step = 0;    // 現在のモータ位置(ステップ)
uint16_t g_motor_target_step;     // 目標モータ位置(ステップ)
uint16_t g_ccpr1 = 0;             // キャプチャ値(回転周期)
volatile uint8_t g_demo_mode = 0; // 0=通常動作, 1=デモ中
volatile uint8_t g_reached_target = 0; // 目標到達フラグ

// スムージング後のRPM(可変フィルタ用)
uint16_t filtered_rpm = 0;

// モータ駆動マクロ
#define MTHLLH()    {LATA=0;LATA=0b00010001;} // pin1/4
#define MTHLLL()    {LATA=0;LATA=0b00000001;} // pin1
#define MTLLHL()    {LATA=0;LATA=0b00000100;} // pin3
#define MTLHHL()    {LATA=0;LATA=0b00000110;} // pin2/3
#define MTLHLL()    {LATA=0;LATA=0b00000010;} // pin2
#define MTLLLH()    {LATA=0;LATA=0b00010000;} // pin4

#ifdef TYPE_TWO_PULSE
#define RPM_CONVERT (18750) // 2パルス/回転
#else
#define RPM_CONVERT (37500) // 1パルス/回転
#endif

/* ------------------------------------------------------------------
 * main
 *  - 回転数計測
 *  - スムージング+デッドバンド
 *  - RPM→ステップ変換
 * ------------------------------------------------------------------ */
void main(void) {
    initialize_system();
    initialize_motor();

    // キャプチャ割り込み許可
    CCP1IE = 1;

    while (1) {
        uint16_t ccpr = g_ccpr1;

        // CCP1キャプチャ値からRPMを算出
        if (ccpr > 0) {
            // g_ccpr1 >> 4 で分解能を少し落としてオーバーフローを避ける
            g_rpm = (uint8_t)(RPM_CONVERT / (g_ccpr1 >> 4));
        } else {
            g_rpm = 0;
        }

        // ----------------------------------------------------------
        // デッドバンド:
        // 低回転では小さな変動で針がピクつくのを防ぐため、
        // 現在値と filtered_rpm の差が小さい場合は変化を無視する。
        // ----------------------------------------------------------
        if ((g_rpm > filtered_rpm ? g_rpm - filtered_rpm : filtered_rpm - g_rpm) < 30) {
            g_rpm = (uint8_t)filtered_rpm;
        }

        // ----------------------------------------------------------
        // 可変スムージング:
        // 低回転ほど強くスムージングして重厚に、
        // 高回転ほどスムージングを弱くして追従性を優先する。
        // ----------------------------------------------------------
        uint8_t filter;

        if (g_rpm < 20) {
            // ~2000rpm: 重厚(強スムージング)
            filter = 8;
        } else if (g_rpm < 60) {
            // 2000~6000rpm: 自然
            filter = 5;
        } else {
            // 6000rpm~: 鋭い(弱スムージング)
            filter = 2;
        }

        filtered_rpm = (filtered_rpm * (filter - 1) + g_rpm) / filter;

        // RPM → 目標ステップ位置へ変換
        g_motor_target_step = rpm_to_step((uint8_t)filtered_rpm);
    }
}

/* ------------------------------------------------------------------
 * 割り込みハンドラ
 *  - CCP1: ピックアップ信号キャプチャ(回転周期計測)
 *  - TMR1: タイムアウトでエンジン停止判定
 *  - TMR2: ステッピングモータ駆動タイミング
 * ------------------------------------------------------------------ */
void __interrupt() isr(void) {
    if (CCP1IF) {
        // ピックアップ信号入力 → Timer1値をキャプチャ
        TMR1H = 0;
        TMR1L = 0;

        g_ccpr1 = ((uint16_t)CCPR1H << 8) | CCPR1L;

        CCP1IF = 0;
    }

    if (TMR1IF) {
        // Timer1タイムアウト → エンジン停止とみなす
        g_ccpr1 = 0;
        TMR1IF = 0;
    }

    if (TMR2IF) {
        // ステッピングモータ駆動
        rotate();
        TMR2IF = 0;
    }
}

/* ------------------------------------------------------------------
 * rpm_to_step
 *  - RPM値(10=1000rpm)をステップ位置に変換
 *  - 0~1000rpm は非線形(under10_posテーブル)
 *  - 1000rpm以上は線形マッピング
 * ------------------------------------------------------------------ */
uint16_t rpm_to_step(uint8_t rpm) {
    // 0~1000rpm の目盛りは不等間隔なことが多いためテーブル化
    static uint16_t under10_pos[] = {
        0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30
    };

    if (rpm < 10) {
        g_motor_target_step = under10_pos[rpm];
    } else {
        g_motor_target_step =
            (uint16_t)((rpm - 10) * STEP_OF_1000RPM) / 10 + POS_RPM1000;
    }

    if (g_motor_target_step >= MAX_X27_COUNT_STEP) {
        g_motor_target_step = MAX_X27_COUNT_STEP;
    }

    return g_motor_target_step;
}

/* ------------------------------------------------------------------
 * rotate
 *  - Timer2 割り込みごとに呼ばれ、ステッピングモータを1回分進める。
 *  - 改良版:
 *      * RPMに応じて Timer2 周期を変更(加減速モード)
 *      * RPMに応じてステップ送り速度を変更
 *      * 方向バッファで先読みし、加減速モードを滑らかに変化
 * ------------------------------------------------------------------ */
void rotate(void) {
    static int8_t  l_motor_phase = 0;
    static uint8_t l_acceleration_mode = ACC_MODE_LO;
    int8_t  new_dir;
    uint8_t ii;

    static int8_t dir_buffer[MT_BUFFER_COUNT] = {RO_STOP};
    uint8_t unmatch_flag = 0;

    // ---------------------------------------------------------
    // RPMに応じて Timer2 周期を可変
    // デモ中 (g_demo_mode=1) は元の動きを優先したい場合は
    // ここをスキップする運用も可能だが、
    // デモ中は「高速動作を維持する」。
    // ---------------------------------------------------------
    if (!g_demo_mode) {
        if (filtered_rpm < 20) {
            // 低回転: 最も遅い → 重厚
            l_acceleration_mode = 5;
        } else if (filtered_rpm < 60) {
            // 中回転: 中間
            l_acceleration_mode = 3;
        } else {
            // 高回転: 最速 → 追従性重視
            l_acceleration_mode = 0;
        }
}    
       // ★mode 3で固定(テスト)
    if (g_demo_mode) {
        l_acceleration_mode = 3;   
    } 
   // 加減速モードに応じた Timer2 設定
    switch (l_acceleration_mode) {
     // ★ Modification: ensure Timer2 is turned ON after setting T2CON/PR2
        case 0: {T2CON = 0b00100101; PR2 = 222; TMR2ON = 1;} break;  //  556us
        case 1: {T2CON = 0b00100101; PR2 = 240; TMR2ON = 1;} break;  //  600us
        case 2: {T2CON = 0b00101101; PR2 = 225; TMR2ON = 1;} break;  //  676us
        case 3: {T2CON = 0b00110101; PR2 = 239; TMR2ON = 1;} break;  //  838us
        case 4: {T2CON = 0b01010101; PR2 = 234; TMR2ON = 1;} break;  // 1292us
        case 5: {T2CON = 0b01010101; PR2 = 251; TMR2ON = 1;} break;  // 2008us
        default:{T2CON = 0b01111110; PR2 =  99; TMR2ON = 1;} break;  // 3168us
    }

    // ---------------------------------------------------------
    // RPMに応じたステップ送り速度
    // 低回転: 1ステップずつ → 重厚
    // 中回転: 2~3ステップ
    // 高回転: 4~5ステップ → 追従性重視
    // ---------------------------------------------------------
    uint8_t step_speed;

    if (filtered_rpm < 20) {
        step_speed = 1;
    } else if (filtered_rpm < 60) {
        step_speed = 2;
    } else if (filtered_rpm < 100) {
        step_speed = 3;
    } else if (filtered_rpm < 150) {
        step_speed = 4;
    } else {
        step_speed = 5;
    }

    // ---------------------------------------------------------
    // 目標ステップに向けて位置を更新
    // ---------------------------------------------------------
    if (g_motor_pos_step > g_motor_target_step) {
        new_dir = RO_TURN_L;
        g_motor_pos_step -= step_speed;
        if (g_motor_pos_step < g_motor_target_step) {
            g_motor_pos_step = g_motor_target_step;
        }
    } else if (g_motor_pos_step < g_motor_target_step) {
        new_dir = RO_TURN_R;
        g_motor_pos_step += step_speed;
        if (g_motor_pos_step > g_motor_target_step) {
            g_motor_pos_step = g_motor_target_step;
        }
    } else {
        new_dir = RO_STOP;
    }

    // 目標到達判定
    g_reached_target = (g_motor_pos_step == g_motor_target_step);

    // ---------------------------------------------------------
    // モータ位相更新
    // ---------------------------------------------------------
    if (RO_TURN_L == new_dir) {
        l_motor_phase = (l_motor_phase + 1) % 6;
    } else if (RO_TURN_R == new_dir) {
        l_motor_phase = (l_motor_phase == 0) ? 5 : (l_motor_phase - 1);
    } else {
        // RO_STOP の場合は位相維持
    }

    switch (l_motor_phase) {
        case 0: MTHLLH(); break;
        case 1: MTHLLL(); break;
        case 2: MTLLHL(); break;
        case 3: MTLHHL(); break;
        case 4: MTLHLL(); break;
        case 5: MTLLLH(); break;
        default: break;
    }

    // ---------------------------------------------------------
    // 方向バッファ:
    // 先読みして方向変化が近い場合は unmatch_flag を立て、
    // 針の速度(加減速モード)を事前に調整する。
    // ---------------------------------------------------------
    for (ii = 0; ii < MT_BUFFER_COUNT_END; ii++) {
        dir_buffer[ii] = dir_buffer[ii + 1];
        if (new_dir != dir_buffer[ii]) {
            unmatch_flag = 1;
        }
    }
    dir_buffer[ii] = new_dir;

    // ---------------------------------------------------------
    // 次の加減速モード決定
    // ---------------------------------------------------------
    if (unmatch_flag) {
        // 方向変化が近い → 速度を落とす方向へ
        if (l_acceleration_mode < ACC_MODE_LO) {
            l_acceleration_mode++;
        }
    } else {
        // 方向変化がしばらくない → 速度を上げる方向へ
        if (l_acceleration_mode > ACC_MODE_HI) {
            l_acceleration_mode--;
        }
    }
}

/* ------------------------------------------------------------------
 * initialize_motor
 *  - モータ初期化とデモ動作
 *  - ゼロ当て → 段階的スイープ → MAX → 0 
 *  - 元のデモ動作を維持し、雰囲気を重視
 *  - デモ中の while ループを CPU 非占有型に変更
 * ------------------------------------------------------------------ */
void initialize_motor(void) {
    uint8_t ii;
    rotate();

    // デモ開始フラグ
    g_demo_mode = 1;
    

    // -----------------------------
    // ? 左端へゼロ当て準備(オーバーラン)
    // -----------------------------
    g_motor_target_step = MAX_X27_COUNT_STEP + 10;
    g_reached_target = 0;
    // ★ Modification: prevent CPU blocking during demo
    while (!g_reached_target) { __delay_ms(1); }   // ★ Added: prevent CPU blocking
    __delay_ms(2);

    // -----------------------------
    // ? 右端へフルスイープ
    // -----------------------------
    g_motor_target_step = 0;
    g_reached_target = 0;
    // ★ Modification: prevent CPU blocking during demo
    while (!g_reached_target) { __delay_ms(1); }   // ★ Added: prevent CPU blocking
    __delay_ms(2);

    // -----------------------------
    //  1000rpmごとに上昇 → MAX → 0
    //     ※デモ中は鋭いスムージングに固定
    //  注意 動作がおかしいのでロジックを再度確認!!
    // -----------------------------
    for (ii = 0; ; ii += 10) {   // 10 = 1000rpm
        // デモ中は鋭いスムージングに固定
        filtered_rpm = ii;      // 直接反映(スムージング弱)
        g_motor_target_step = rpm_to_step(ii);

        g_reached_target = 0;
               // ★ Modification: prevent CPU blocking during demo
        while (!g_reached_target) { __delay_ms(1); }   // ★ Added: prevent CPU blocking
        __delay_ms(150);

        if (g_motor_target_step >= MAX_X27_COUNT_STEP)
            break;
    }

    // MAX → 0 に戻す
    g_motor_target_step = 0;
    g_reached_target = 0;
    // ★ Modification: prevent CPU blocking during demo
    while (!g_reached_target) { __delay_ms(1); }   // ★ Added: prevent CPU blocking
    __delay_ms(150);

    // -----------------------------
    // デモ終了 → 通常動作へ戻す
    // -----------------------------
    g_demo_mode = 0;
 }
    
/* ------------------------------------------------------------------
 * initialize_system
 *  - PIC本体の初期化
 *  - クロック、ポート、タイマ、CCP1 の設定
 * ------------------------------------------------------------------ */
void initialize_system(void) {
    OPTION_REG = 0b00000000;
    WPUA      = 0b00000000;

    // 内蔵オシレータ 32MHz
    OSCCON = 0b11110000;

    // PORTA 設定
    APFCON = 0b00000001; // CCP1 on RA5
    TRISA  = 0b00101000; // RA5,RA3 入力
    PORTA  = 0b00000000;
    ANSELA = 0b00000000; // 全ピンデジタル

    // Timer1: CCP1キャプチャ用
    T1CONbits.TMR1CS  = 0;      // FOSC/4
    T1CONbits.T1CKPS  = 0b11;   // 1:8 プリスケーラ
    T1CONbits.T1OSCEN = 0;      // 専用オシレータ無効
    T1CONbits.nT1SYNC = 1;
    T1CONbits.TMR1ON  = 1;

    // Timer2: ステッピングモータ駆動用
    T2CON   = 0;
    PR2     = 0;
    TMR2IF  = 0;
    T2CONbits.T2CKPS = 0b01;    // x4 プリスケーラ

    // CCP1: キャプチャモード(立下りエッジ)
    CCP1CON = 0b00000100;

    // 割り込み設定
    PEIE  = 1;
    GIE   = 1;
    CCP1IE = 0;
    CCP1IF = 0;

    TMR1IE = 1;
    TMR1IF = 0;
    TMR1ON = 1;

    TMR2IE = 1;
    TMR2IF = 0;
    TMR2ON = 0; // Timer2 は rotate() 内の設定で動作
}

/*---------------------------------------------------------------------------------
 2026/01 version
---------------------------------------------------------------------------------*/

コメント