ESP32S3 A4988 ステッパモータ駆動
短時間でモータ指令値を変化させる用途のパルス生成クラス
PWM発生機構利用
2027.7.20 Coskx Lab
1 はじめに
Xiao ESP32S3で小型ステッパモータ(ステッピングモータ)を制御します。
短時間でモータ指令値を変化させる用途のパルス生成クラスで制御するようにしました。
PWM信号の発生はマイコンの持っているPWM発生機構を用いています。

ステッパモータドライバIC A4988を使用して,小型ステッパモータを駆動します。
A4988にのpulse端子にパルスを与えるだけでステッパモータは決められたステップ角度で回転します。
原理的な説明は次のWebページを参照してください。
≫ モータドライバA4988でステッピングモータ制御
1/16マイクロステップ駆動を採用します。そのため,通常のモータ1ステップ動作は1.8度の回転ですが,ここでは0.1125度(=1.8度の1/16)回転になります。
参考 モータドライバIC A4988基板実装(秋月電子販売など)
参考 使用しているモータ
名称: 3DプリンターTitan Stepper Motor (AMAZON)
モーターサイズ(L * W * H):42 * 42 * 23 mm
フェーズ:2フェーズ
電圧:4.1V
電流:1A /相
抵抗値:4.1±10%Ω/相
インダクタンス:4.1±20%mH /相
保持トルク:13N.cm以上(18.5oz.in)
ディテントトッグ:2.0N.cm(2.85oz.in)
絶縁材クラス:B
ステップ角:1.8°±5%
重量:132g
2 使用環境
- Windows 11 64-bit
- Arduino IDE 2.3.5
- Xiao ESP32S3
- モータドライバIC A4988
3 実験用配線
次のように配線しました。
DIR端子,STEP端子,ENABLE端子はそれぞれマイコンのD0,D1,D6に接続しました。
1/16マイクロステップ設定のためMS1,MS2,MS3は,3つともH(VDD)に接続します。
RESET端子,SLEEP端子は,それぞれH(VDD),H(VDD)に接続します。

ENABLE端子は通常Lに設定します。
ステッパモータは停まっているときも動いているときも常時電流が流れています。(停まっているときに保持トルクを発生しています。)
ステッパモータを操作していないときで,保持トルク不要の場合は,電流を流さないようにしたいです。そのときは,ENABLE端子にHを与えます。
マイコンの出力端子を一つ使って,プログラムでA4988のENABLE端子にL,H信号を与えるようしています。
なお,モータ用電源は12Vを使いました。
4 電流制限の設定
説明は次のWebページを参照してください。
≫ モータドライバA4988でステッピングモータ制御
5 モータ駆動デモプログラム
モータ駆動の細かなところはパルス生成クラスに任せているので,メイン側の記述は簡素になります。
行っているのは100msec間隔でモータ指令値(-1から1まで,正の値の場合は正転,負の場合は逆転します。)をパルス生成クラス側に与えています。ただし,実際に指令値が変化するのは1秒ごとです。パルス生成クラス側では最小パルス間隔を200μsecの設定で,指令値に合わせたパルス間隔を算出します。
実際の駆動では,モータ指令値を-0.1から0.1で変化させています。
動作周期と最小パルス間隔の設定によりますが,指令値が非常に小さいとモータが回らないこともあります。
ステッパモータの動作テスト用プログラム本体(メイン)
//steppermotorA4988withclass_singleDEMO.ino
// A4988ステッピングモータードライバーモジュール
#include "StepperMotorESP32.h"
StepperMotorESP32 stepper;
uint32_t MinPulsePeriod = 200; //[microsec]
uint32_t ControlFrequency = 100; //[Hz]
const uint8_t PinDIR = D0;
const uint8_t PinSTEP = D1;
const uint8_t PinEnable = D6; // モータドライブEnable(府論理)に使用する PIN番号
int MaxControlValue;
void setup() {
Serial.begin(115200);
delay(100);
Serial.println("Hello");
stepper.setPins(PinDIR, PinSTEP, PinEnable);
stepper.setDirModifier(1);
MaxControlValue = stepper.getMaxControlValue(ControlFrequency, MinPulsePeriod);
Serial.print("MaxControlValue = ");
Serial.println(MaxControlValue);
stepper.enableDriver();
}
void loop() {
static int control = 0;
static int dcontrol = 1;
static int n=0;
int res;
double fcontrol = (double)control/50.;
res = stepper.drive(fcontrol);
n++;
if (n == 100) {
if (control == 5 || control == -5) {
dcontrol *= -1;
}
control += dcontrol;
n=0;
Serial.print("control, res = ");
Serial.print(fcontrol);
Serial.print(", ");
Serial.println(res);
}
delay(1000/ControlFrequency); //[msec]
}
パルス生成クラスを記述したヘッダファイル
(StepperMotorESP32.hの名前でメインと同じフォルダに入れてください)
//StepperMotorESP32.h
//The class StepperMotorESP32 drives a stepper motor using the A4988 driver module.
#ifndef StepperMotorESP32_h
#define StepperMotorESP32_h
class StepperMotorESP32 {
public:
void setPins(uint8_t DirPin, uint8_t StepPin, uint8_t EnablePin);
int getMaxControlValue(uint32_t ControlFrequency, uint32_t MinPulsePeriod);//Hz, microsec
void setDirModifier(int Modifier); // 1 or -1
int drive(double ControlValue); // -1.0 <= ControlValue <= 1.0: return with number of palses
void enableDriver();
void disableDriver();
private:
uint8_t dirpin, steppin, enablepin;
uint32_t controlfrequency;
int MaxControlValue;
int modifier = 1;;
double limitfrequency;
int duty;
const uint32_t orgfrequency = 500;
const int nBits_forPWM = 12;
};
#endif
パルス生成クラスを記述したC++ファイル
(StepperMotorESP32.cppの名前でメインと同じフォルダに入れてください)
//StepperMotorESP32.cpp
#include <Arduino.h>
#include "StepperMotorESP32.h"
void StepperMotorESP32::setPins(uint8_t DirPin, uint8_t StepPin, uint8_t EnablePin) {
dirpin = DirPin;
steppin = StepPin;
enablepin = EnablePin;
pinMode(dirpin, OUTPUT);
pinMode(steppin, OUTPUT);
pinMode(enablepin, OUTPUT);
digitalWrite(enablepin, HIGH); //HIGH:disable; LOW:enable
ledcAttach(steppin, orgfrequency, nBits_forPWM);
digitalWrite(dirpin, LOW);
digitalWrite(steppin, LOW);
int nShifts = nBits_forPWM - 4;
if (nShifts < 0) nShifts = 1;
duty = 1 << (nShifts); //duty cycle
/*
Serial.print("duty = ");
Serial.println(duty);
*/
}
/*If the motor shaft does not rotate forward when a positive value is entered using the method drive,
then giving -1 to this method will cause the motor shaft to rotate forward
when a positive value is entered using the method drive.
*/
void StepperMotorESP32::setDirModifier(int Modifier) { // 1 or -1
modifier = Modifier;
}
/*
Returns the maximum value that can be given to the motor within this class.
This maximum value is given by dividing the startup cycle of the method drive by the minimum motor drive pulse length.
This value is only used for checking and is not used in the program.
The argument “ControlFrequency” gives the frequency [Hz] at which the method drive is invoked.
The argument “MinPulsePeriod” gives the minimum pulse length (pulse period) [microsecond] that may be given to the stepping motor.
*/
int StepperMotorESP32::getMaxControlValue(uint32_t ControlFrequency, uint32_t MinPulsePeriod) {//Hz, microsec
MaxControlValue = 1000000 / ControlFrequency / MinPulsePeriod;
controlfrequency = ControlFrequency;
limitfrequency = 1000000 / MinPulsePeriod;
return MaxControlValue;
}
/*
Drives the motor.
The argument “ControlValue” gives the command value to the motor. (-1 <= ControlValue <= 1)
A positive value means forward rotation and a negative value means reverse rotation.
Returns with number of palses.
*/
int StepperMotorESP32::drive(double ControlValue) {// -1 <= ControlValue <= 1: returns with number of palses
uint32_t outfrequency;
int nSteps;
int sign = 1;
uint32_t res = 99999;
if (ControlValue < 0.) sign = -1;
ControlValue *= modifier;
if (0 < ControlValue) {
digitalWrite(dirpin, HIGH);
} if (ControlValue < 0.) {
digitalWrite(dirpin, LOW);
ControlValue = -ControlValue;
}
outfrequency = (uint32_t)(controlfrequency * ControlValue * MaxControlValue);
if (limitfrequency < outfrequency) outfrequency = limitfrequency;
nSteps = sign * (int)(outfrequency / controlfrequency);
if (outfrequency == 0) {
ledcWrite(steppin, 0);
nSteps = 0;
} else {
res = ledcChangeFrequency(steppin, outfrequency, nBits_forPWM);
ledcWrite(steppin, duty);
}
/*
Serial.print("res, outfrequency= ");
Serial.print(res);
Serial.print(", ");
Serial.println(outfrequency);
*/
return nSteps;
}
void StepperMotorESP32::enableDriver()
{
digitalWrite(enablepin, LOW);
}
void StepperMotorESP32::disableDriver()
{
digitalWrite(enablepin, HIGH);
}
6 実行の様子
小さな指令値を使っているためゆっくりとモータは回転します。
次のようなログが得られました。指令値0.01のとき,指令値が小さすぎてモータは回っていません。(ログでは1パルス動かしたことになっている)
control, res = 0.00, 0
control, res = 0.02, 1
control, res = 0.04, 2
control, res = 0.06, 3
control, res = 0.08, 4
control, res = 0.10, 5
control, res = 0.08, 4
control, res = 0.06, 3
control, res = 0.04, 2
control, res = 0.02, 1
control, res = 0.00, 0
control, res = -0.02, -1
control, res = -0.04, -2
control, res = -0.06, -3
control, res = -0.08, -4
control, res = -0.10, -5
control, res = -0.08, -4
control, res = -0.06, -3
control, res = -0.04, -2
control, res = -0.02, -1
7 補足
PWM発生機構がハードウエアでパルスを発生するため,,ソフトウエアの負担はありません。。
しかし,コントロール周期(上記プログラムのloop()関数の実行時間)を時間ユニットとし,その中に綺麗にパルスを並べるため生成するパルス間隔は離散的になります。

8 まとめ
Xiao ESP32S3およびモータドライバA4988でステッパモータを駆動しました。
駆動では短い時間ごとにモータ指令値を変更することができるパルス生成クラスを用いて,プログラミングの負担を減らしています。
ここで使用しているパルス生成クラスでのPWM信号発生は,マイコンの持っているPWM発生機構を用いています。
そのため,低いPWM信号の周波数が離散的になってしまい,長いパルス間隔のパルス発生が苦手です。