Arduono単体でステッピングモーターを制御する [モータードライバー不要]

###はじめに
ステッピングモーターをマイコンで制御しようと記事を検索すると、ほぼ間違いなくモータードライバーがセットになっています。まあ正しい選択ですよね。
ただ、Arduinoでチョロっとステッピングモーターを動かして遊んでみたい、と思った時にはちょっと敷居が高いですよね。
そこで今回は、モータードライバーを使用せず、Arduinoだけでステッピングモーターを制御してみたいと思います。

###Arduino単体で制御できるステッピングモーター
Arduino単体で制御できるステッピングモーターには２つほど条件があります。

・励磁方式が2相ユニポーラであること
　一般的なステッピングモーターには大きく 2相ユニポーラ/2相バイポーラ が存在します。
　バイポーラはコイルに流す電流の正逆を制御する必要がありますが、
　Arduino単体では正の電圧しか印加することができません。

・小さいこと
　Arduino単体では大きなモーターを動かすことができません。
　小型のステッピングモーターは2相ユニポーラであることが多いようです。

今回は秋月電子通商で購入できる[SPG27-1101](https://akizukidenshi.com/catalog/g/gP-11839/)を使用しました。
![SPG-1101](https://akizukidenshi.com/img/goods/4/P-11839.jpg)

###ステッピングモーターのしくみ
ユニポーラタイプのステッピングモーターの多くは６本の線が出ています。
このうち２本はGND（センター）で、残りの４本が制御用の線になります。
この制御用の線に対して順番に位相をずらしたパルスを入力することで回転を制御できます。
![dotstudio, inc.](https://dotstud.io/img/blog/189/6.png)

###ソースコード
実験用に作成したソースコードはこちらになります。

φ1,φ2,~~φ1~~,~~φ2~~ をそれぞれピン10,11,12,13に、φ1C,φ2CをGNDに割り当てています
![スクリーンショット 2021-01-18 20.53.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/a5dda221-c8cb-6f85-eb6a-e1230a727f5f.png)


```steppermotortest.cpp
#include <Arduino.h>
//#include "tmDeltaTime.h"
//#include "tmStepperMotor.h"

class TmDeltaTime{
    public:
    TmDeltaTime(){
        Setup();
    };
    void Setup(){
        currentMillis = millis();
        deltaMillis = 0;
    }
    uint32_t Update(){
        uint32_t nowMillis = millis();
        deltaMillis = nowMillis - currentMillis;
        currentMillis = nowMillis;
        return deltaMillis;
    };
    uint32_t GetDeltaMillis(){
        return deltaMillis;
    };
    private:
    uint32_t currentMillis;
    uint32_t deltaMillis;
};

class TmStepperMotor{
    public:
    TmStepperMotor(uint8_t _phA=9, uint8_t _phB=10, uint8_t _phAi=11, uint8_t _phBi=12, uint32_t _delay = 50, bool _dir = false, uint32_t _pulseWidth=500){        
        mPh[0] = _phA; mPh[1] = _phB; mPh[2] = _phAi; mPh[3] = _phBi;
        mIsNegative = _dir;
        mDelay = _delay;
        mPhase = 0;
        mSumMillis = 0;
        mPulseWidth = mPulseTimer = _pulseWidth;
    };
    void Setup(){
        for(int i=0; i< 4; ++i){
            pinMode(mPh[i],OUTPUT);
            digitalWrite(mPh[i],LOW);
        }
    }

    uint32_t Update(uint32_t _deltaMillis){
        if((mPulseTimer>0)&&(mDelay > (mPulseWidth+10))){
            mPulseTimer -= _deltaMillis;
            if(mPulseTimer<=0){
                phaseOff();
            }
        }
        mSumMillis += _deltaMillis;
        if(mSumMillis >= mDelay){
            mSumMillis -= mDelay;
            mPhase = updatePhase(mPhase,mIsNegative);
            mPulseTimer = mPulseWidth;
        }
        return mSumMillis;
    }
    void SetDelay(uint32_t _delay){
        mDelay = _delay;
        //mSumMillis = 0;
    }
    uint32_t GetDelay(void){
        return mDelay;
    }
    void SetDirection(bool _isNegative){
        mIsNegative = _isNegative;
        //mSumMillis = 0;
    }
    bool GetDirection(void){
        return mIsNegative;
    }
    void SetPulseWidth(uint32_t _width=100){
        mPulseWidth = mPulseTimer = _width;
    }
    uint32_t GetPulseWidth(void){
        return mPulseWidth;
    }

    private:
    uint8_t mPh[4];
    uint8_t mPhase;
    bool mIsNegative;
    uint32_t mDelay;    
    uint32_t mSumMillis;
    uint32_t mPulseWidth;
    uint32_t mPulseTimer;

    uint8_t updatePhase(uint8_t _phase, bool _isNegativeRotation = false){
        static uint8_t phaseArr[] = {HIGH,LOW,LOW,HIGH};
        for(int i=0; i< 4; ++i){
            digitalWrite(mPh[i],phaseArr[(_phase+i) & 3]);
        }
        return _isNegativeRotation ? ((_phase+3) & 3):(_phase+1) & 3;
    }
    void phaseOff(){
        for(int i=0; i< 4; ++i){
            digitalWrite(mPh[i],LOW);
        }
    }
};

TmDeltaTime* pDeltaTime = new TmDeltaTime();
TmStepperMotor* pStepMotor = new TmStepperMotor(10,11,12,13,30,false);
uint32_t invTimer=0;
uint32_t spdTimer=0;

void setup() {
  pDeltaTime->Setup();
  pStepMotor->Setup();
  pStepMotor->SetPulseWidth(20);
}

void loop() {
    uint32_t delta = pDeltaTime->Update();

    invTimer+= delta;
    if(invTimer >= 10000){
      invTimer-= 10000;
      pStepMotor->SetDirection(!pStepMotor->GetDirection());
    }
    spdTimer+= delta;
    if(spdTimer >= 3000){
      spdTimer-= 3000;
      pStepMotor->SetDelay(pStepMotor->GetDelay()==30 ? 300 : 30);
    }

    pStepMotor->Update(delta);
    delay(1);
}
```

実際に動作させてみた[動画](https://youtu.be/hznRWLU2LFw)
それほど大きなトルクは期待できませんが、指で出力軸をつまみ上げるとモーターが回転する程度にはトルクが出ています。

###測定
実際に測定してみるとこんな感じでした。
![IMG_9646.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/59911/c6c4a82b-7ef4-05e9-4210-70ab7eefeee8.jpeg)
コイル端子間の抵抗が約30[Ω]でしたので、70[mA]程度流れている感じでしょうか。
定格では[１ピンあたり40[mA]まで](http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=1643#:~:text=Arduino%20Uno%E3%81%AE%E3%83%94%E3%83%B3%E3%81%AF,%E3%82%88%E3%81%86%E6%B3%A8%E6%84%8F%E3%81%97%E3%81%A6%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84%E3%80%82)という事なので、あまり長く遊ばない方が良さそうです。
一応ライブラリにもパルス出力持続時間の上限をつけてみました(default0.5[sec])

###まとめ
Arduino単体でステッピングモーターを制御することができました。
本格的に動かすにはやはりモータードライバーでドライブした方が良さそうですが、
ちょっと試しに動かしてみるには十分かと思います。