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
---------------------------------------------------------------------------------*/

コメント