PICマイコン(PIC16F1827)でステッピングモーターを操作する

組み込みエンジニア
本記事はプロモーションが含まれています。

こんにちは、ENGかぴです。

PIC16F1827のDOを使用することでステッピングモーターを操作することができます。DOを4ポート分実装し、ステップごとに励磁パターンを生成するとステッピングモーターを時計/反時回りに回転させることができます。

Arduinoスターターキットに付属していたステッピングモーター(28BYJ-48)及びドライバー基板をPIC16F1827のDOに接続して動作確認を行います。

PIC16F1827で動作確認したことについてリンクをまとめています。

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

ステッピングモーターを操作する

ステッピングモーターは回転数を制御するドライバーとセットで使用します。産業用でベルトコンベヤーや水量を調整する弁の開閉に使用されており用途は様々です。

ステッピングモーターはマイコンによるDOで電流を増幅して励磁する電流型とドライバーに電圧値を与えてモーターの速度を調整する電圧型があります。

28BYJ-48はユニポーラ方式で巻線に対して一方向の電流を流して起動する電流型です。モーターの巻線の電流により励磁しますが、電流をパターンを切り替えて回転磁界を発生させることで軸を回転させる仕組みです。

広告

モーターの起動方法

ユニポーラ方式での起動方法は、1相励磁、2相励磁、1-2相励磁による励磁によって起動します。これらの励磁方式の特徴について以下にまとめています。

励磁方式特徴
1相励磁(Full step)1相ずつ順に励磁していくため消費電流は少なくなります。ただし各相に切り替わる際に振動が発生しやすくなります。
2相励磁(Full step)2相ずつ切り替えるため出力トルクが大きくなりますが消費電流が増えます。1相励磁よりも振動が抑えられるため滑らかな動きとなる。
1-2相励磁(Half step)1励磁と2相励磁を交互に繰り返す方式。モーターのステップ角の分解能が基本ステップの1/2になりますが、2相励磁よりもさらに滑らかな動きとなる。
ステッピングモーターの励磁方式

本記事ではデータシートに記載されている励磁方式である1-2相励磁の方法でステッピングモーターを駆動します。

ステッピングモーターの特性
ステッピングモーターの特性

ステッピングモーターの1ステップの操作は1パルスの入力で行います。そのため周波数の単位でHzまたはppsで記載されています。

ステッピングモーターを起動する場合は特性曲線に従って起動することが推奨されます。モーターが停止している状態から急速な入力周波数で起動するとスルーイング特性から外れて軸が回転しない同期外れの問題が発生することがあります。

モーターを起動は効率よくトルクを取りながら高速回転に遷移させることが推奨されるためスルー領域内(赤線と青線内の領域)に特性が収まるようにステップ周波数を制御します。

スターティング特性はステップ数が入力パルスと1対1に対応して起動でき、最大のトルクが発生できる関係を示しています。

スルーイング特性はスターティング特性範囲以内で起動して同期回転しているモーターの入力パルスの周波数を徐々に増加した場合、スターティング特性以上の入力周波数に1対1に対応できる最大トルクと入力パルスの関係を示しています。

スルー領域は自起動領域から入力パルスの周波数を増加させるもしくは負荷トルクを増加させるとき、モーターが入力パルスと同期を保って応答できる領域です。

自起動領域は無負荷状態で、入力パルスに同期して自起動が可能な領域です。この領域から入力パルスの周波数をあげて起動することでトルクを高く得ながら高速回転に移ることができます。

28BYJ-48のデータシートではMax starting frequencyが500Hz、Max response frequencyが1000ppsと記載されています。無負荷状態で500Hz(2ms)で起動して周波数を上げる場合1000Hz(1ms)程度までは安定に動作させることができます。負荷がある場合は高トルクで起動するために500Hz以下の入力周波数で起動して徐々に入力周波数をあげて制御します。

広告

励磁のパターンを生成する

引用:28BYJ48-W01のデータシート
引用:28BYJ48-W01のデータシート

28BYJ-48のデータシートに記載されているステップ数による制御は1-2励磁方式です。マイコンのDOを増幅した信号で励磁パターンを作ることで回転させることができます。データシートを引用して制御に必要な事項を中心に説明します。

5.4極の励磁で1相励磁と2相励磁を切り替えて励磁パターンを作ります。-記号はDOをLOW、記号がない場所についてはHIGHにします。1から8ステップのパターンをDOで制御することで回転磁界を生成しモーターを回転させます。

6.Frequency of light pull outはMax response frequencyに相当します。7.Max starting frequency(最大応答周波数)の間にスルー領域(モーターが入力信号に応答できる領域)があるためこの区間の範囲内でのパルス(周波数)を調整します。

void StepMove(uint8_t no, uint16_t wait ){

    switch(no){
        case 0: //ステップ1 0111
            IO_RB3_SetLow();
            IO_RB2_SetHigh();
            IO_RB1_SetHigh();
            IO_RB0_SetHigh();
            break;
        case 1: //ステップ2 0011
            IO_RB3_SetLow();
            IO_RB2_SetLow();
            IO_RB1_SetHigh();
            IO_RB0_SetHigh();
            break;    
        case 2: //ステップ3 1011
            IO_RB3_SetHigh();
            IO_RB2_SetLow();
            IO_RB1_SetHigh();
            IO_RB0_SetHigh();
            break;              
        case 3: //ステップ4 1001
            IO_RB3_SetHigh();
            IO_RB2_SetLow();
            IO_RB1_SetLow();
            IO_RB0_SetHigh();
            break;
        case 4: //ステップ5 1101
            IO_RB3_SetHigh();
            IO_RB2_SetHigh();
            IO_RB1_SetLow();
            IO_RB0_SetHigh();
            break;            
        case 5: //ステップ6 1100
            IO_RB3_SetHigh();
            IO_RB2_SetHigh();
            IO_RB1_SetLow();
            IO_RB0_SetLow();
            break;            
        case 6: //ステップ7 1110
            IO_RB3_SetHigh();
            IO_RB2_SetHigh();
            IO_RB1_SetHigh();
            IO_RB0_SetLow();
            break;
        case 7: //ステップ8 0110
            IO_RB3_SetLow();
            IO_RB2_SetHigh();
            IO_RB1_SetHigh();
            IO_RB0_SetLow();
            break;                 
    }
    DELAY_microseconds(wait); //ステップ間隔を調整する
}

ステップ数1~8をswitch分で分岐させて励磁パターンを切り替えてします。ステップによる励磁パターンを切り替えた後は__delay_us()(MCC生成の遅延関数はDELAY_microseconds())でus間隔でウェイトを置きます。

モーター駆動時は自起動領域でトルクを多く得るため低い周波数でウェイトを置くため引数でステップ間隔を調整します。本記事では駆動時の周波数を100ppsから最大1000ppsまで増やしながら回転させます。

ユーザーがステッピングモーターの軸側を見た時を基準にして回転方向を決めるとステップ1~8の順でステップを進めると反時計回りに軸が回転し、ステップ8~1に向かう方向にステップを進めると時計回りに軸が回転します。

ステップ間隔を調整するウェイトを低く設定(周波数が高い)すると脱調(同期外れ)を起こし軸が回転しなくなるため注意が必要です。

スポンサーリンク

MCCの設定

PIC16F1827の設定はMCCを使っています。MCCの使い方については下記記事にまとめています。

MPLAB Code Configurator(MCC)の追加と使い方

今回はMCCを使用して動作クロックとTMR0の設定を行っています。

System Moduleの設定

最初に共通事項であるSystem Moduleの設定を行います。MPLAB X IDEを起動しMCCのアイコンをクリックしてMCCを有効にします。

System Moduleの設定(MCC)
System Moduleの設定(MCC)

内部クロックを使用するためINTOSCを選択しています。クロック周波数は任意でもよいですが4MHz_HFを選択しています。クロックを高速にするほど消費電流が増えてしまいますが消費電流が気にならない場合はクロック周波数を高くしても問題ありません。他にも設定項目はありますがクロックの設定のみとしています。

広告

TMR0を設定する

TMR0はDevice Resources内のPeripherals欄のTimerを選択しTMR0をクリックして追加します。

TMR0の設定(MCC)
TMR0の設定(MCC)

Timer Clock内でクロックに関する設定を行います。Clock Sourceは内部クロックを使用しているためFOSC(FOSC/4)を指定します。PrescalerはClock Sourceに対してTMR0のカウントアップする分周比を指定します。

Timer PeriodはTMR0がオーバーフローするタイミングを設定します。1ms毎にオーバーフロー割り込みが発生しますがCallback Function Rateを0x0A(10)を指定しているため10ms毎にコールバック関数が呼び出されます。

void mainTimer(void); //タイマー0割り込みのコールバック関数
//コールバック関数の使用例
TMR0_SetInterruptHandler(mainTimer); //コールバック関数を指定

上記例ではmainTimer()が10ms毎にコールされます。

MCCが生成した関数の使用例

IO_RB0_SetHigh();//DOをHIGH
IO_RB0_SetLow();//DOをLOW

IO_RB0_SetHigh()関数はRB0のDO出力をHighにします。IO_RB0_SetLow()関数はRB0のDO出力をLowにします。他のポートのDOの場合はRB0の部分が対象のポート名になります。Header Files内のpin_manager.h内で#defineで定義されています。

PR:テックジム:プログラミングの「書けるが先で、理解が後」を体験しよう!

動作確認

ステッピングモーターの動作確認回路
ステッピングモーターの動作確認回路

マイコンでステッピングモーターを起動する場合、増幅基板を使用してDOの出力電流を増幅してステッピングモーターを励磁します。増幅基板はステッピングモーターとセットになっていることが多く、トランジスタアレイ(ULN2003APGなど)が実装されておりマイコンのDOをトランジスタアレイの入力に接続します。

マイコンのDOをステッピングモーターに直接接続すると出力電流による負荷が大きくなりすぎて発熱しマイコン故障の原因になるので注意が必要です。

PIC16F1827の電源はDC5Vですが、ステッピングモーターの起動に必要な消費電流が供給できる場合は赤線のように電源を共通にして使用できます。電流が不足する場合はPIC16F1827の電源とステッピングモーターの電源を分けて使用しますが、GND(ー)を共通にして青線部分に別途電源を接続します。

当記事では電源アダプター(AD-D50P100:秋月電子)を使用しているため赤線の配線で動作確認をしています。

電源をONし、SW1を長押しするとステッピングモーターは反時計回り回転を始め、LED1が点灯/消灯します。SW2を長押しすると時計回りに回転を始め、LED2が点灯/消灯します。ステッピングモーターの起動時は緩やかに回転し、長押しするにつれて回転速度が速くなり一定速度になることが確認できました。PIC16F1827のピン配置は以下の通りです。

PIC16F1827のピン設定
PIC16F1827のピン設定

PB4、PB5はINPUTでウィークプルアップ(WPU)設定しています。WPUを有効にするためにはRegistersのnWPUENをenabledにする必要があります。

Easy SetupでWPUにチェックするのみでは有効にならないので注意が必要です。Notificationsにワーニングで通知されます。

PR: ゼロからはじめるPython入門講座の申込 テックジム

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

#include "mcc_generated_files/mcc.h"

#define TIME_OFF -1         //タイマーを使用しない場合
#define TIME_UP 0           //タイムアップ
#define LED_WAIT 20
#define DI_MAX 2
#define DI_FILT_MAX 4            //DIフィルタのサンプリング数
#define TIME_FILTER_MAX 1    //ベースタイマカウント値
#define	CNT_INIT_MAX 10     //10ms×10 = 100ms
#define STEP_OFF 100
#define STEP_START_FREQ 10000
#define STEP_CUT 1000

typedef struct{
    uint8_t wp;
    uint8_t buf[DI_MAX][DI_FILT_MAX];
    uint8_t di[DI_MAX];
}DIFILT_TYP;

int16_t timStep;
int16_t timled1 = TIME_OFF;
int16_t timled2 = TIME_OFF;
int16_t timstepoff = TIME_OFF;
DIFILT_TYP difilt;
int16_t timdifilt;   //DIフィルタ起動
uint8_t CntInit;	//初期化時のみ使用
uint16_t stepfreq_n;
uint16_t stepfreq_p;

void mainTimer(void);
void mainApp(void);
void StepMove(uint8_t no, uint16_t wait );
void DiFilter(void);

/* Main application */
void main(void)
{
    // initialize the device
    SYSTEM_Initialize();
    TMR0_SetInterruptHandler(mainTimer);
    INTERRUPT_GlobalInterruptEnable();
    INTERRUPT_PeripheralInterruptEnable();
    
	CntInit = CNT_INIT_MAX;
	while(CntInit > 0){  //0になるまでフィルタを実施
        DiFilter();         //DIフィルタ処理
        __delay_ms(10);     //10ms遅延させてDIフィルタ処理
        CntInit--;
        timdifilt = TIME_UP;
	}      

    while (1)
    {
        mainApp();
        DiFilter();
    }
}

/* タイマ管理関数 */
void mainTimer(void){
 
    if( timled1 > TIME_UP ){
        --timled1;
    }
    
    if( timled2 > TIME_UP ){
        --timled2;
    }    

    if( timdifilt > TIME_UP ){
        --timdifilt;
    }
    
    if( timstepoff > TIME_UP ){
        --timstepoff;
    }
   
}
/* メイン処理 */
void mainApp(void){
    uint8_t i,j;
    
    if( timled1 == TIME_UP ){
        timled1 = TIME_OFF;
        IO_RB7_Toggle();
    }
    
    if( timled2 == TIME_UP ){
        timled2 = TIME_OFF;
        IO_RB6_Toggle();
    }
    
    if( timstepoff == TIME_UP){
        timstepoff = TIME_OFF;
        IO_RB3_SetLow();
        IO_RB2_SetLow();
        IO_RB1_SetLow();
        IO_RB0_SetLow(); 
    }
    
    if(difilt.di[0] == LOW ){
        if(timled1 == TIME_OFF){
            timled1 = LED_WAIT;
        }
        timstepoff = STEP_OFF;
                
        for(i=0; i < 8; i++){
            StepMove(i, stepfreq_n );
        }

        if(stepfreq_n <= STEP_CUT){
            stepfreq_n = STEP_CUT;
        }
        else{
            stepfreq_n -= STEP_CUT;
        }
    }
    else{
        stepfreq_n = STEP_START_FREQ;
        IO_RB7_SetLow();
    }

    if(difilt.di[1] == LOW ){
        if( timled2 == TIME_OFF){
            timled2 = LED_WAIT;
        }
        timstepoff = STEP_OFF;
        
        for(i=0; i < 8; i++){
            StepMove(7-i, stepfreq_p);
        }
        
        if(stepfreq_p <= STEP_CUT){
            stepfreq_p = STEP_CUT;
        }
        else{
            stepfreq_p -= STEP_CUT;
        }
        
    }
    else{
        stepfreq_p = STEP_START_FREQ;
        IO_RB6_SetLow();
    }
}


void StepMove(uint8_t no, uint16_t wait ){

    switch(no){
        case 0: //ステップ1 0111
            IO_RB3_SetLow();
            IO_RB2_SetHigh();
            IO_RB1_SetHigh();
            IO_RB0_SetHigh();
            break;
        case 1: //ステップ2 0011
            IO_RB3_SetLow();
            IO_RB2_SetLow();
            IO_RB1_SetHigh();
            IO_RB0_SetHigh();
            break;    
        case 2: //ステップ3 1011
            IO_RB3_SetHigh();
            IO_RB2_SetLow();
            IO_RB1_SetHigh();
            IO_RB0_SetHigh();
            break;              
        case 3: //ステップ4 1001
            IO_RB3_SetHigh();
            IO_RB2_SetLow();
            IO_RB1_SetLow();
            IO_RB0_SetHigh();
            break;
        case 4: //ステップ5 1101
            IO_RB3_SetHigh();
            IO_RB2_SetHigh();
            IO_RB1_SetLow();
            IO_RB0_SetHigh();
            break;            
        case 5: //ステップ6 1100
            IO_RB3_SetHigh();
            IO_RB2_SetHigh();
            IO_RB1_SetLow();
            IO_RB0_SetLow();
            break;            
        case 6: //ステップ7 1110
            IO_RB3_SetHigh();
            IO_RB2_SetHigh();
            IO_RB1_SetHigh();
            IO_RB0_SetLow();
            break;
        case 7: //ステップ8 0110
            IO_RB3_SetLow();
            IO_RB2_SetHigh();
            IO_RB1_SetHigh();
            IO_RB0_SetLow();
            break;                 
    }
    DELAY_microseconds(wait); //回転速度を調整する
}
/* DiFilter function add */
void DiFilter(void){
    uint8_t i;

    if( timdifilt == TIME_UP ){
        timdifilt = TIME_FILTER_MAX;
        difilt.buf[0][difilt.wp] = IO_RB4_GetValue();
        difilt.buf[1][difilt.wp] = IO_RB5_GetValue();

        for(i=0; i < DI_MAX; i++){
            if( difilt.buf[i][0] == difilt.buf[i][1] &&
            difilt.buf[i][1] == difilt.buf[i][2] &&
            difilt.buf[i][2] == difilt.buf[i][3] ){ //4回一致を確認
            difilt.di[i] = difilt.buf[i][0];
            }
        }

        if( ++difilt.wp >= DI_FILT_MAX ){
          difilt.wp = 0;
        }
    }
}
/* End of File */

本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていることが前提となります。MCCをインストールする方法は下記記事を参考にしてください。

MPLAB Code Configurator(MCC)の追加と使い方

SW1とSW2を同時に押すと時計回りと反時計回りが繰り返し動作となるため、後押し分を受け付けないようにするインターロックを実装してもよさそうです。

関連リンク

PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

広告

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました