ESP32の実験ついでにステッパーで遠隔サーボを作ってみました。ここではSP32 Dev Board (38ピン)を2つ使い、片側(Slave)で位置の信号を作り、もう一方(Master)へ送り、そこでステッパーをサーボとして動かしています。上の動画では両方をすぐ隣においてあります^^ が、部屋の中ではかなり遠くでも問題ありません。どこまで届くかは実験してませんが。
これを前にはWiFiで苦労して作りましたが、こういう用途ではBlueToothがずいぶん楽だと気付きましたので、Bluetooth SPP(Serial Port Profile)にしました。送受の両方のESP32プログラムはこの記事の最後につけておきます。
Arduino IDEでプログラミングしましたが、ESP32をArduino IDEで開発する際のソフトウェア導入方法については、次の記事の中に簡単に書いておきました。
ESP32のアクセスポイント (つまりスタンドアローンWiFi-LAN) - 勝手な電子工作・・
なお、強力なESP32Devlopment Boardは海外ネットではずいぶん安価になり、この例で使ったのは1つあたり3.6ドルでした。
まず、マスター側の結線は次のとおりです。毎度汚い手書きですみませんが。
これをブレッドボードに次のように試作しました。
ごらんのとおりへんなブレッドボードですが、真ん中にある普通のブレッドボードの外側上下に、もう1枚のブレッドボードを2つに割ったものをはめ込んだだけ。ブレッドボードの受け金具のはいってないところで自由に切ればよいわけ。金鋸でもバンドソーでも何でも簡単に切ることができますから^^ うまく作るコツは、組み合わせの凹凸部がはまるように考えるだけです。あるいは切らずに組み合わせるだけでOKですね(ただし余分な面積が増える)。
次にスレーブですが、これはもう簡単そのもの、結線図というほどのものではありません。この場合、もしVRにセンターノッチがはいっているとサーボコントロールに使いやすいですが、特に必要はありません。
これはブレッドボード(1列6ピン)1枚に試作。
電源はUSB供給でもいいですが、持ち歩きのリモコンにする場合は単三電池3本(4.5V)をV5端子に入れるとよいです。
ステッパーをサーボとして使うときに一番難しいのは、回転の絶対位置を何で決めるかということ。リミッターとかセンサーとかなんらかの「とりつくしま」を付ければごく簡単なことなのですが、そこをつけずにやりたいもの。実は、A4988コントローラーなどのリセット機能を用いてHome positionを決め、後は決まったステップ数単位で動かせば再現性をもってできます。前に次のレーザーカッターの記事に書いたとおりです。
オリジナル・レーザープロッター その2 Arduinoでの刻印プログラム - 勝手な電子工作・・
今回は荒っぽい実験でとくにそうはしてませんがほとんど狂いません。
我ながらこれはなかなか面白い実験でした^^
なお、サーボの位置を細かく決めるには、スレーブ側のVRをCoarse(右の10Kオーム)+Fine(左の500オーム)と組み合わせると微調整が効きます。VRのケースは秋月で売っている一番小さなプラケースです。また、A4988で分周比の設定ができますので、角度はさらに細かい単位にすることができます。
このVRは2.4GHz電波の近傍にさらされるわけなので、どちらの方法でもVRへのワイヤはシールドワイヤが望ましいかも。そうしないと電波を拾ってわずかながら不安定になる感じで、その場合のセンター位置(511)の受信例が次。
あるいはVRの抵抗値を下げて例えば10Kオームを2Kオームにすればノイズは拾いにくくなるし、また、高周波をカットするようにC(キャパシター)をADC端子間に入れる手もよいでしょうね。
最後にそれぞれのプログラムを添付しますが、Arduino IDEからESP32 Dev Boardに書き込みます。
まずスレーブ側で、とても小さなプログラムです。
/* ************************************************************************
ESP32 Serial communication with Bluetooth-SPP as a Slave
Example; ADC data sending to master's remote-Servo
Initial version Sept.26, 2020 (c)Akira Tominaga
*************************************************************************/
#include "BluetoothSerial.h"
BluetoothSerial btSPP;
int ADpin = 36; // ADC pin
uint16_t adVal = 0; // ADC value
byte mH; // High byte for ADC measured value
byte mL; // Low byte for ADC measured value
void setup() { // ***** ESP32 setup *****
Serial.begin(9600);
btSPP.begin("HOGEHOGE-Slave-xx"); //Bluetooth name of this slave
Serial.println(""); // NewLine
uint8_t mA[6]; // for blue-tooth MAC adr
esp_read_mac(mA, ESP_MAC_BT); // get my blue-tooh MAC adr
Serial.printf("%02X:%02X:%02X:%02X:%02X:%02X\r\n", mA[0], mA[1], mA[2], mA[3], mA[4], mA[5]);
// upper result to be used by master
Serial.println("btSPP started, pair with master.");
// for AD conversion
pinMode(ADpin, INPUT);
adcAttachPin(ADpin);
analogReadResolution(10); // ADC with 10 bits (from 0 to 1023)
delay(5);
}
void loop() { // ***** ESP32 Loop *****
while (btSPP.available() < 3) {} // wait for master message
String s = btSPP.readString(); // read whole message
Serial.println(s);
adVal = analogRead(ADpin);
Serial.println(adVal);
mH = adVal >> 8;
mL = adVal - mH * 256;
Serial.print("mH="); Serial.print(mH, HEX);
Serial.print(" mL="); Serial.println(mL, HEX);
btSPP.write(mH); // send measured-high byte
btSPP.write(mL); // send measured-low byte
}
次にマスター側です。これもあまり大きくありません。
/* ************************************************************************
Serial communication with Bluetooth - Serial Port Profile (Master)
Example: Stepper as a remote-Servo
Initial version Sept.26, 2020 (c)Akira Tominaga
*************************************************************************/
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;
uint8_t Adr[6] = {0x7C, 0x9E, 0xBD, 0xF4, 0xDB, 0x56}; // slave adr
String name = "HOGEHOGExxx";
byte mH; // high byte of measured ADC at slave
byte mL; // low byte of measured ADC at slave
uint16_t adVal; // ADC value reproduced from mH&mL
#define adVmax 1023 // 10bits-ADC max value
#define adVmid 512 // 10 bits-ADC center value
uint16_t adVsv = adVmid; // ADC value saved last time
// for stepper with A4988
int16_t Move; // moving-steps for stepper
const char Roger[] = "Rgr"; // ready message to slave
uint16_t vRot; // value for rotation steps (oneRot=200)
gpio_num_t Dir = GPIO_NUM_33; // Direction for A4988
gpio_num_t Step = GPIO_NUM_25; // Step for A4988
gpio_num_t Nsleep = GPIO_NUM_26; // !sleep for A4988
gpio_num_t Nreset = GPIO_NUM_27; // !reset for A4988
#define tmStp 2500 // stepper phase timing in μS
#define oneRot 200 // steps req'd for one rotation (360°)
#define sMax 150.0 // max steps as a servo (270°)
void setup() { // ***** Arduino (ESP32) Setup *****
pinMode(Nsleep, OUTPUT);
sSleep(); // sleep A4988 to begin with
pinMode(Dir, OUTPUT);
pinMode(Step, OUTPUT);
digitalWrite(Step, LOW);
Serial.begin(9600);
pinMode(Nreset, OUTPUT);
sReset(); // set stepper at home position
// Bluetooth slave shoud be on, first
SerialBT.begin(name, true);
Serial.println("master started");
if (SerialBT.connect(Adr)) {
; // connect with slave adr
Serial.println("Connected!");
} else {
while (!SerialBT.connected(10000)) {
Serial.println("Failed to connect!");
}
}
delay(5);
for (int n = 0; n < 3; n++) {
SerialBT.write(Roger[n]); // send ready to slave
}
}
void loop() { // ***** Arduino (ESP32) Loop *****
//Serial.println("Loop");
while (SerialBT.available() < 2) {} // wait for 2 bytes from slave
mH = SerialBT.read();
mL = SerialBT.read();
adVal = mH * 256 + mL;
//Serial.print(" adVal=");
Serial.println(adVal);
Move = adVal - adVsv;
adVsv = adVal;
if (Move > 0) {
vRot = (float)(Move * sMax / adVmax);
//Serial.print("Positive ");
//Serial.println(vRot);
sP(vRot);
}
if (Move < 0) {
Move = -Move;
vRot = (float)(Move * sMax / adVmax);
//Serial.print("Negative ");
//Serial.println(vRot);
sN(vRot);
}
if (Move = 0) {
// do nothing
}
for (int n = 0; n < 3; n++) {
SerialBT.write(Roger[n]); // send ready to slave
}
}
/**********************************************
User defined functions
**********************************************/
// *** sP(n-rotations) *** move stepper positive
void sP(int nRot) {
digitalWrite(Dir, LOW);
mvS(nRot, tmStp);
}
// *** sN(n-rotations) *** move steppe negative
void sN (int nRot) {
digitalWrite(Dir, HIGH);
mvS(nRot, tmStp);
}
// *** mvS(nRot, ndelay) *** move stepper
void mvS(int nRot, long nDelay) {
sWake(); // wake-up A4988
for (int l = 0; l < nRot; l++) {
digitalWrite(Step, HIGH);
delayMicroseconds(nDelay);
digitalWrite(Step, LOW);
delayMicroseconds(nDelay);
}
sSleep(); // Sleep A4988 to avoid heat
}
// *** sReset() *** Reset at current position & sleep
void sReset() {
sWake();
digitalWrite(Nreset, LOW);
delay(10);
digitalWrite(Nreset, HIGH);
//delay(1);
sSleep();
}
// *** sSleep() *** Sleep stepper to avoid heat
void sSleep(void) {
delay(2); // wait 2mS until end of inertia
digitalWrite(Nsleep, LOW);
}
// *** sWake() *** Wake-up stepper
void sWake(void) {
digitalWrite(Nsleep, HIGH);
delay(2); // wait for charge-pump full
}
// end of program
ステッパー停止時には、いちいちA4988をSleep させていますが、こうするとA4988もステッパーも熱をもたせずにすみます(逆に停止時にSleepさせず放置すると直流が流れるのですぐ過熱しますからご注意)。このあたりは、前に次の記事などに詳しく書きましたのでご興味のある方は次をご覧ください。
以上、自分のまとめとして書いた記事ですが、これはなかなか楽しい実験でした。この応用が皆様の何かのお役に立てば幸いです。
©2020 Akira Tominaga, All rights reserved.