ディープラーニング事始め、GPU]カード比較

AIブームに乗り遅れるな

世の中 AIブームで猫も杓子もAIAIと騒ぐようになってきました。
私のやっている仕事も画像による検査やロボット制御に絡む話が多いのでAIブームを黙って見過ごしている訳にはいきません。
 暫くは既存のアルゴリズムで何とかなるでしょうが10年先はAIを活用できないとかなり苦しい立場に立たされると思います。
ということで遅まきながら細々とディープラーニングの勉強に取り組んでいます。

ディープラーニングのためのツール選択

 最初はChainerというディープラーニング向けのフレームワークを使おうとしていましたが、互換性のないアップデートが何度もありその度に開発環境のインストールだけで止まってしまうという繰り返しをしている間にKerasが主流になってきたのでそちらに乗り換えました。
 こういうツールは利用者目線の情報が豊富にあることが大切なのでChainerのようにある程度のレベルに達したユーザーでないとついていけないほど速いサイクルでアップデートを繰り返しているツールはいくら内容が良くてもメジャーになり得ません。
 その点Kerasは私のように横着なユーザーでも何とかついていける初心者向けの情報が着実に増えてきているようで、嬉しい限りです。

ディープラーニング用のハードウェア

ハードウェアの比較

ディープラーニングにはGPUの活用が欠かせません。
 実用的なアプリケーションでニューラルネットワークの学習をしようとすると数時間から数週間以上の時間がかかり、GPUの有無で学習時間に一桁以上の差が出ます。
 今まではWindows 10のPCにGeforce GTX 750のグラフィックボードを増設したものとGeforce GT 650Mを搭載したMacbook Proを使っていましたが、もう少し真面目に取り組むためにディープラーニング専用のPCを用意してUbuntuをインストールして使うことにしました。

現在ディープラーニングで実用的に使えるGPUはNVIDIAのグラフィックボード一択なので、新しいマシンを用意するにあたってどのクラスのグラフィックボードが費用対効果が高いか調べて見ましたが明確なベンチマークは見つからなかったのでとりあえず比較的高性能なGTX 1070が搭載されていて手頃な価格のDELL XPS 8930シリーズを導入しました。

まずはWindows 10で試していた簡単なLTSM(時系列データ向きのモデル)をそのままUbuntuで走らせてみました。

下図が出力結果で上がWindows 10(GTX 750)、下が Ubuntu( GTX 1070)の出力です。
較べてみるとUbuntuの方が3倍強のスピードで学習が終わっていますのでGTX 1070はディープラーニング用として費用対効果が高く、かなりお勧めのGPUであることが判りました。

学習速度の比較

GeForce GTX 1070      220sec
GeForce GTX 750        750sec
GeForce GT 650M      1090sec

GeForce GT650Mは下のリストにはありませんがMacBook Pro搭載のGPUです。
GTX 750はヒートパイプ付きクーラー搭載の見た目がごついビデオカードですがノートPCに入っているGeForceに較べて3割程度しか速くないのが意外でした。

ものづくりコンテスト:演習課題

ものづくりコンテストでは新しくプロジェクトを作ってMCCを使って初期化部分の作成をおこなう必要がある。
ここでは新規プロジェクトの作り方とプロジェクトのコピーの練習をおこなう

演習の内容

1.ゼロから新しいプロジェクトを作る

新規プロジェクト mykadai1 を作成してRGBLEDを点滅させる(点滅間隔は人が点滅を認識できれば良い)

2.プロジェクトをコピーして新しいプロジェクトを作る

mykadai1をコピーしてmykadai2を作り回答例のプロジェクトを参考にして以下の処理をおこなう

A.7セグメントLEDに2桁の数値を表示する。

B.SW2(トグルSW)がONの時SW1(タクトスイッチ)を押す毎にLEDの数値がカウントアップする(最大12)

C.SW2がOFFの時SW1を押す毎に7セグメントの数値がカウントダウンする。―12以下にはならないようにする。

D.printf文を使って7セグメントの数値とスイッチの状態をPCのターミナルに表示する

3.おまけの課題

時間が余った人はスイッチを押す毎にステッピングモータが文字盤の1目盛りづつ動くプロジェクト mykadai3を作ってみる。

新規プロジェクト作成手順

新規プロジェクト mykadai1 を作る

メインメニューの
File -> Close All Projectsで全てのプロジェクトを閉じる
File -> New Projectを実行して下のウィンドウを開き Next をクリック

デバイスPIC18F45K22を選択する

Select DeviceのウィンドウでFamily (PIC18)の中からPIC18F45K22 を選択してNextをクリック
※たくさんのデバイスがあるので間違えないように

書き込みツールにPicKit3を指定する

Select ToolsウィンドウでPicKit3を選んでNextをクリック

コンパイラを選ぶ

Select CompilerウィンドウでコンパイラXC8を選んでNextをクリック

プロジェクト名とフォルダを指定

Select Project Name and Folder ウィンドウでプロジェクト名とフォルダを入力してFinishをクリックすると新しいプロジェクトが作られる
※ プロジェクト名は mykadai とする
※ フォルダはサンプルプロジェクトが入っている場所と同じでOK
※ Encoding にShift JISを選んでおくとWindowsのメモ帳で日本語が正常に表示される

続いてMCCを使って設定をおこなう(別資料参照)

ものづくりコンテスト:サンプルプログラム

PIC18F45K22CPUボード用ヘッダファイル(mono_con.h)

ものづくりコンテストではポート定義のヘッダファイルに限り持ち込みが認められていて、下記のようなヘッダファイルを用意しておくとプログラム作成の時間短縮に役立ちます。

/* 
 * File: mono_con.h
 * Author: jsd
 *
 * Created on 2018/04/17, 14:14
 */

#ifndef MONO_CON_H
 #define MONO_CON_H

#ifdef __cplusplus
 extern "C" {
 #endif

// LED G, B, R
 #define LEDG LATDbits.LATD1
 #define LEDB LATDbits.LATD5
 #define LEDR LATEbits.LATE2

// BUZZER
 #define BZ LATEbits.LATE0
 
 // Latch
 #define LT LATDbits.LATD2
 
 // 7seg 
 #define SEG7L LATBbits.LATB0
 #define SEG7R LATBbits.LATB1
 
 #define SEG7(d) (LATC=(d))
 #define SEG7_a LATCbits.LATC0
 #define SEG7_b LATCbits.LATC1
 #define SEG7_c LATCbits.LATC2
 #define SEG7_d LATCbits.LATC3
 #define SEG7_e LATCbits.LATC4
 #define SEG7_f LATCbits.LATC5
 #define SEG7_g LATCbits.LATC6
 #define SEG7_dp LATCbits.LATC7

// Stepping motor
 #define STM(d) (LATC=(d)&0x0f)

// DC motor
 #define DCM(d) (LATC=(((d)<<6)&0xc0)
 
 // SW status
 #define ENCA (PORTAbits.RA3)
 #define ENCB (PORTAbits.RA4)
 #define SW2 (PORTAbits.RA5==0)
 #define SW1 (PORTAbits.RA6==0)

#ifdef __cplusplus
 }
 #endif

#endif /* MONO_CON_H */

割り込みの使い方サンプルプログラム(プロジェクトkadaiのmain.c)

下のプログラムは割り込みを使うためのサンプルソースです。

プログラムの最初に ヘッダファイルmono_con.hを読み込むための#include文を追加します。

#include "mcc_generated_files/mcc.h"

#include "mono_con.h"

10mS 毎に呼び出される割り込み関数inttmr0()でスイッチの読み込みなどの処理をおこないます。 関数名inttmr0()をmain関数の最初で登録して割り込みを有効にすることで割り込み関数が使えるようになります。 割り込み関数を使う上での注意事項  関数内で宣言するローカル変数は割り込み関数を抜けると記憶されないので変数はグローバル(外部)変数か、関数内で宣言する場合はstatic宣言を使います。  割り込み関数の中で無限ループや長い時間待ちを使ってはいけません。出来るだけ早く終わるように処理を記述します。

/* 10ms毎に起動するタイマ割り込み関数
 *  mainの最初で登録することで周期的に呼ばれる
 * 入力処理と制御をここに記述
 * ※割り込み関数使用上の注意
 * 1.関数は出来るだけ早く抜けること(無限ループ厳禁)
 * 2.変数はグローバル変数(関数の外に書く)にするかstatic宣言をつけること
 */
int tmr1s,tmr10ms;  // これはグローバル変数
uint16_t tmr0_s,tmr0_e;    // デバッグ用の変数
void inttmr0(void)
{
    static int i;
    tmr0_s = TMR0_ReadTimer(); // デバッグ用、課題処理には不要
    
    // 課題1回答例 ここから
    if( SW2 && SW1 ){
        DCM(2);
        printf("dcm(2) PORTA=%02x,LATC=%02x\n",PORTA,LATC);
        LEDG = 1;
    }else if( !SW2 && SW1 ){
        DCM(1);
        printf("dcm(1) PORTA=%02x,LATC=%02x\n",PORTA,LATC);
        LEDB = 1;
    }else{
        DCM(0);
        LEDG = 0;
        LEDB = 0;
    }
    // ラッチの立ち上がりでモータドライバに信号が伝わる
    LT = 0;
    __delay_us(10); // パルスを観測し易くするために10us待つ 
    LT = 1;
    // 課題1回答例 ここまで
            
    // tmr10msをインクリメントし100になったらtmr1sをカウントアップ
    if( 100 <= ++tmr10ms ){ 
        // この部分は1秒ごとに実行
        tmr1s++;
        tmr10ms = 0;
    }
    
    tmr0_e = TMR0_ReadTimer(); // デバッグ用:課題処理には不要
}

tmr01msは100us毎に呼び出される割り込み関数です ブザーを大きな音で鳴らすためには1KHz以上の周波数でON/OFFを繰り返す 必要があるので10KHzの周期にしています。 tmr1_s, tmr1_eには割り込み開始時と終了時のタイマカウンタの値が 入り、printfで表示するとどのくらいの余裕があるかを確認できます。

/* 0.1ms毎に起動するタイマ割り込み関数
 * 出力処理をここに記述
 * ※関数使用上の注意はtmr0と同じ
 */
int tmr01ms;        // これはグローバル変数
uint16_t tmr1_s,tmr1_e;    // デバッグ用の変数

void inttmr1(void)
{
    static int t;   // スタティック変数
    
    tmr1_s = TMR1_ReadTimer(); // デバッグ用、課題処理には不要
    
    // 0.1ms * 9000 = 0.9秒毎に '*' を出力
    // mainのprintfを邪魔する様子を観察するために書いてある
    if( (tmr10ms==0)&&(45 <= ++tmr01ms) ){
        tmr01ms = 0;
        printf("*");
    }  
    tmr1_e = TMR1_ReadTimer(); // デバッグ用、課題処理には不要
}

main関数では割り込み関数を登録してタイマー割り込みを開始する処理だけを記述して 課題の処理は全て割り込み関数でおこないます。

void main(void)
{
    // Initialize the device
    SYSTEM_Initialize();

/* 次のコメントを除いた4行は新しく追加する必要があります。 登録関数とタイマースタート関数の名前は生成されたソースで確認することが出来ます。*/

    // タイマー0割り込み関数を登録 (tmr0.c の関数を呼ぶ)
    TMR0_SetInterruptHandler(inttmr0);
    // タイマー1割り込み関数を登録 (tmr1.c の関数を呼ぶ)
    TMR1_SetInterruptHandler(inttmr1);
    // タイマー0をスタート (tmr0.c の関数を呼ぶ)
    TMR0_StartTimer();
    // タイマー1をスタート (tmr1.c の関数を呼ぶ)
    TMR1_StartTimer();

/* 割り込みを有効にするために次の2行のコメントを外して有効にします。*/

   
    // If using interrupts in PIC18 High/Low Priority Mode you need to enable the Global High and Low Interrupts
    // If using interrupts in PIC Mid-Range Compatibility Mode you need to enable the Global and Peripheral Interrupts
    // Use the following macros to:

    // Enable the Global Interrupts:この下の行をコメントアウトし有効にする
    INTERRUPT_GlobalInterruptEnable();

    // Disable the Global Interrupts
    //INTERRUPT_GlobalInterruptDisable();

    // Enable the Peripheral Interrupts:この下の行をコメントアウトし、有効にする
    INTERRUPT_PeripheralInterruptEnable();

    // Disable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptDisable();

/* ここにはデバッグ用のprintf文を書いていますが 課題の処理ではメイン関数の無限ループは空のままで構いません。*/

    // メインループはデバッグ用のprintfだけを書く
    // 課題の処理には必要無し
    while (1)
    {
        // 1秒毎にインクリメントされるtmr1sの値を表示
        // tmr0_s : tmr0割り込み開始時のタイマー値  
        // tmr0_e : tmr0割り込み終了時のタイマー値 
        //    ※0になると次の割り込みが始まる  
        // tmr1_s : tmr1割り込み開始時のタイマー値  
        // tmr2_e : tmr1割り込み開始時のタイマー値 
        //    ※0になると次の割り込み開始  
        if( tmr10ms == 0 ){
            printf("tmr1s=%d, tmr0_s=%u, tmr0_e=%u, tmr1_s=%u, tmr1_e=%u\n",
                    tmr1s, 0xffff-tmr0_s, 0xffff-tmr0_e, 0xffff-tmr1_s, 0xffff-tmr1_e);
        }  
    }
}

課題1回答例 (MCCが生成したコメントを除く)

ポイント 条件文で SW1 == 0 と書く代わりに !SW1 と記述している  教科書的には等号・不等号をきちんと書いた方が良いが、 プログラムを書いている時に等号==を代入の記号=と間違えて記述する場合があり、 気が付きにくいバグとなるので後者の記述にした方が有利である。

#include "mcc_generated_files/mcc.h"

#include "mono_con.h"

/* 10ms毎に起動するタイマ割り込み関数
 */

void inttmr0(void)
{
    if( !SW2 && SW1 ){ // SW2:OFF  SW1:ON
        DCM(2);
    }else if( SW2 && SW1 ){ // SW2:ON SW1:ON
        DCM(1);
    }else{
        DCM(0);
    }
    // ラッチの立ち上がりでモータドライバに信号が伝わる
    LT = 0;
    __delay_us(10); // パルスを観測し易くするために10us待つ 
    LT = 1;
}

/* 10ms毎に起動するタイマ割り込み関数
 * 出力処理をここに記述
 * 課題1では必要ないが、プロジェクトの初期化部分を共通化するために空の関数を記述
 */
void inttmr1(void)
{

}

void main(void)
{
    // Initialize the device
    SYSTEM_Initialize();

    // タイマー0割り込み関数を登録 (tmr0.c の関数を呼ぶ)
    TMR0_SetInterruptHandler(inttmr0);
    // タイマー1割り込み関数を登録 (tmr1.c の関数を呼ぶ)
    TMR1_SetInterruptHandler(inttmr1);
    // タイマー0をスタート (tmr0.c の関数を呼ぶ)
    TMR0_StartTimer();
    // タイマー1をスタート (tmr1.c の関数を呼ぶ)
    TMR1_StartTimer();

    // Enable the Global Interrupts:この下の行をコメントアウトし有効にする
    INTERRUPT_GlobalInterruptEnable();

    // Enable the Peripheral Interrupts:この下の行をコメントアウトし、有効にする
    INTERRUPT_PeripheralInterruptEnable();

    while (1)
    {
  
    }
}

課題2回答例

main関数は課題1と同じなので省略しています ポイント  シーケンシャルな処理をするためにステートマシンを使っています。  ステートマシンとはswitch文と状態変数を使って割り込みが呼ばれるたびに  状態変数で処理内容を選択する書き方です。  慣れると複雑なシーケンスでも簡単に記述できます。  スイッチがONした瞬間をとらえるためにSWの状態を記憶しておいて  次のタイミングでスイッチがONでと記憶した状態がOFFの時にスイッチON時の処理をしています。

#include "mcc_generated_files/mcc.h"
#include "mono_con.h"

/* 10ms毎に起動するタイマ割り込み関数
 */
void inttmr0(void)
{
    static int sw1; // SW1の状態を記憶する変数 ※static宣言が必要
    static int state=0; // ステートマシン用変数
    
    switch(state){
        case 0: // 消灯
            LEDR = LEDG = LEDB = 0;  // 全部消灯
            if( SW2 )state = 1; // トグルSWがONになったら 1 へ
            break;
        case 1:  // 黄色
            LEDR = LEDG = 1;        // 緑と赤を点灯(黄色)
            if( !SW2 ) state = 0;   // トグルSWがOFFになったら 0 へ
            if( SW1 && !sw1 ){      // タクトSWの立ち上がりで2へ
                LEDR = LEDG = 0;
                state = 2;
            }
            break;
        case 2: // 緑点灯
            LEDG = 1;
            if( !SW2 ) state = 0;
            if( SW1 && !sw1 ){
                LEDG = 0;
                state = 3;
            }
            break;
        case 3: // 青点灯
            LEDB = 1;
            if( !SW2 ) state = 0;
            if( SW1 && !sw1 ){
                LEDB = 0;
                state = 4;
            }
            break;
        case 4: // 赤点灯
            LEDR = 1;
            if( !SW2 ) state = 0;
            if( SW1 && !sw1 ){
                LEDR = 0;
                state = 2;
            }
            break;
        default:
            state = 0;
            break;
    }
    sw1 = SW1;
    // printf文は無くても良いし、あっても課題の動作には影響が無い
    printf("state=%d,SW1=%d,sw1=%d\n",state,SW1,sw1);

}

/* 10ms毎に起動するタイマ割り込み関数:関数名は任意
 *  mainの最初で登録することで周期的に呼ばれる
 * 出力処理をここに記述
 * ※関数使用上の注意はtmr0と同じ
 */
void inttmr1(void)
{

}

課題3回答例

ポイント  エンコーダ信号もスイッチ信号と同じく変化したタイミングでだけ処理をおこなうのがポイントです。  使っているエンコーダはA相の信号がHighからLowになった時でないと方向判別が出来ない仕様です。  割り込みを使った7SEGの時分割表示はパターンを覚えればとても簡単です。

#include "mcc_generated_files/mcc.h"
#include "mono_con.h"

/* 10ms毎に起動するタイマ割り込み関数
 */
int seg7R = 16, seg7L = 16;
int ct = 0;  // `エンコーダカウント

void inttmr0(void)
{
    static int encA; // SW1の状態を記憶する変数 ※static宣言が必要
    
    // ロータリーエンコーダの信号でct をカウントアップ・ダウン
    if( !ENCA && encA ){ // ENCAがHighからLowになった時
        if( ENCB ){
            ct++;
            if( 20 < ct ) ct = 20;
        }else{
            ct--;
            if( ct < -5 ) ct = -5;
        }
    }
    encA = ENCA; // 次のために記憶
    
    if( !SW2 ) ct = 0;
    
    // 7SEGに表示
    if( 0 <= ct ){
        seg7L = (ct/10) % 10; // seg7L に ctの上位桁を入れる
        seg7R = ct % 10;      // seg7R にctの下位桁を入れる
    }else{
        seg7L = 17;
        seg7R = -ct % 10;      // seg7R にctの下位桁を入れる        
    }
        
}

/* 10ms毎に起動するタイマ割り込み関数:関数名は任意
 *  mainの最初で登録することで周期的に呼ばれる
 * 出力処理をここに記述
 * ※関数使用上の注意はtmr0と同じ
 */
/*****************************************************************
  7SEG LED の表示データ
 ****************************************************************/
const unsigned char SEG7D[] = {      // 7セグメントLEDの表示データ
    0b00111111,                         //  0 = 0
    0b00000110,                         //  1 = 1
    0b01011011,                         //  2 = 2
    0b01001111,                         //  3 = 3
    0b01100110,                         //  4 = 4
    0b01101101,                         //  5 = 5
    0b01111101,                         //  6 = 6
    0b00100111,                         //  7 = 7
    0b01111111,                         //  8 = 8
    0b01101111,                         //  9 = 9
    0b01110111,                         // 10 = A
    0b01111100,                         // 11 = b
    0b00111001,                         // 12 = C
    0b01011110,                         // 13 = d
    0b01111001,                         // 14 = E
    0b01110001,                         // 15 = F
    0b00000000,                         // 16 = 消灯
    0b01000000,                         // 17 = -(マイナス)
    0b01010100,                         // 18 = OFF(冖)
    0b00001000,                         // 19 = ON (_)
};

void inttmr1(void)
{
     static int selseg, t;
    
    // 7SEG 表示処理
    if( selseg ){ // 右の7SEG点灯
        SEG7R = 1;
        SEG7L = 0;
        SEG7(SEG7D[seg7R]);       
    }else{        // 左の7SEG点灯
        SEG7R = 0;
        SEG7L = 1;
        SEG7(SEG7D[seg7L]);
    }
    selseg = !selseg; // フラグを反転する
    
    // ブザーを鳴らす
    if( ct == -5  ){
        if( 2 <= ++t ){
            t = 0;
            BZ = !BZ;
        }
    }    
}

課題4回答例

ポイント エンコーダのカウントと7SEG表示は課題3と同じでカウンタの範囲を±3に制限するだけです。  割り込みを使うとステッピングモータ駆動も簡単に追加することが出来ます。  ステッピングモータ駆動信号と7SEG表示信号が重なっているため、 割り込み関数の中で最初に駆動信号をラッチしてから表示信号をセットするように注意しなければなりません。  順序が逆の場合は7SEGの表示がでたらめになります。  その理由を考えてみるのはハードを理解するうえで良い勉強になるでしょう。

/* 10ms毎に起動するタイマ割り込み関数
 */
int seg7R = 16, seg7L = 16;
int ct = 0;
int stmct = 0; // ステッピングモータ回転指令カウンタ

void inttmr0(void)
{
    static int sw1,encA; // SW1の状態を記憶する変数 ※static宣言が必要
    static int stmpos = 0, tgtpos = 0;
    static int state = 0;
    
    // ロータリーエンコーダの信号でct をカウントアップ・ダウン
    if( !ENCA && encA ){ // ENCAがHighからLowになった時
        if( ENCB ){
            ct++;
            if( 3 < ct ) ct = 3;
        }else{
            ct--;
            if( ct < -3 ) ct = -3;
        }
    }
    encA = ENCA; // 次のために記憶
    if( !SW2 ) ct = 0;

    // ステッピングモーター制御
    if( SW1 && !sw1 ) tgtpos = ct*120; // 目標位置
    sw1 = SW1;
    
    switch(state){
        case 0: // 目標位置との差分に指令値をセット
            if( tgtpos != stmpos ){
                stmct = tgtpos - stmpos;
                stmpos = tgtpos;
                state = 1;
            }
            break;
        case 1: // ステッピングモーターが止まるまで待つ
            if( stmct == 0 ) state = 0;
            break;
        default:
            state = 0;
            break;                
    }
    if( tgtpos != stmpos ){
        
    }
        
    // 7SEGに表示
    if( 0 <= ct ){
        seg7L = (ct/10) % 10; // seg7L に ctの上位桁を入れる
        seg7R = ct % 10;      // seg7R にctの下位桁を入れる
    }else{
        seg7L = 17;
        seg7R = -ct % 10;      // seg7R にctの下位桁を入れる        
    }
        
    //printf("stmct=%d\n",stmct);
}

/* 10ms毎に起動するタイマ割り込み関数:関数名は任意
 *  mainの最初で登録することで周期的に呼ばれる
 * 出力処理をここに記述
 * ※関数使用上の注意はtmr0と同じ
 */
/*****************************************************************
  7SEG LED の表示データ
 ****************************************************************/
const unsigned char SEG7D[] = {      // 7セグメントLEDの表示データ
    0b00111111,                         //  0 = 0
    0b00000110,                         //  1 = 1
    0b01011011,                         //  2 = 2
    0b01001111,                         //  3 = 3
    0b01100110,                         //  4 = 4
    0b01101101,                         //  5 = 5
    0b01111101,                         //  6 = 6
    0b00100111,                         //  7 = 7
    0b01111111,                         //  8 = 8
    0b01101111,                         //  9 = 9
    0b01110111,                         // 10 = A
    0b01111100,                         // 11 = b
    0b00111001,                         // 12 = C
    0b01011110,                         // 13 = d
    0b01111001,                         // 14 = E
    0b01110001,                         // 15 = F
    0b00000000,                         // 16 = 消灯
    0b01000000,                         // 17 = -(マイナス)
    0b01010100,                         // 18 = OFF(冖)
    0b00001000,                         // 19 = ON (_)
};

/* ステッピングモーター相励磁データ
 *  STMD[stmidx]の形でstmidxを0,1,2,3,0,1,2,3,...とすると
 *  ここに設定したパターンが順番に呼び出される
 * それをモータードライバーに接続されたポートに出力してモーターを回す
*/
const unsigned char STMD[] = {
    // oooo にモーターが回転する相励磁パターンを入れる
	0b0001, // 0 A 励磁 (A相だけ1にする)
	0b0010, // 1 B 励磁 (B相だけ1)
	0b0100, // 2 /A 励磁(/A相だけ1)
	0b1000, // 3 /B 励磁(/B相だけ1)
};

void inttmr1(void)
{
   static int selseg, t, stmidx;
    
    //  ステッピングモーター駆動
    //  7SEGの信号と共通(時分割)のため先にステッピングモーター信号をセット
    SEG7R = SEG7L = 0;  // ちらつき防止のため7SEGGをOFFにする
    if( 50 <= ++t ){ // ステッピングモータの速度調整
        t = 0;
        if( 0 < stmct ){
            if( --stmidx < 0 ) stmidx = 3; // 時計回り
            stmct--;
        }else if( stmct < 0 ){
            if( 3 < ++stmidx ) stmidx = 0; // 反時計回り
            stmct++;
        }
        STM(STMD[stmidx]);  // 相励磁信号セット
        LT = 0;        // 相励磁信号をラッチ
        LT = 1;
    }
#if 1
    // 7SEG 表示処理
    if( selseg ){ // 右の7SEG点灯
        SEG7R = 1;
        SEG7L = 0;
        SEG7(SEG7D[seg7R]);       
    }else{        // 左の7SEG点灯
        SEG7R = 0;
        SEG7L = 1;
        SEG7(SEG7D[seg7L]);
    }
    selseg = !selseg; // フラグを反転する
#endif
}