Grove サーボモータ
今回のプログラムはどのように動くか
書き込むと A ボタンで -90 度、B ボタンで 0 度、C ボタンで 90 度にサーボモータの角度が操れます。
Grove への Grove ケーブルのつなぎかた
今回はサーボモータに直接つながっているので Grove 変換ケーブルをつなぐ必要はありません。
サーボモータにパーツを装着
サーボモータと同行されているパーツのうち、こちらの白いプラスチック製の長いパーツを取り出します。
サーボ側の白い出っ張りのギザギザと、
パーツ側のギザギザの凹みを合わせてはめ込みます。
よく確認しましょう。
はめ込むことで角度の変化がより分かりやすくなります。
実際にこのパーツ含めたパーツ群は、何かを動かすときに力を伝えるための軸の役割しています。
PORT A という Grove ポートを確認
今回は PORT A という Grove ポートにつなぐので確認します。
GPIO としては 21 と 22 で操作できます。今回は 22 で指示を出します。
M5Stack への Grove ケーブルのつなぎかた
PORT A に挿しこみます。
このように接続できます。
ソースコードを反映&保存
Arduino IDE で新規ファイルを作成し、以下のコードをコピーアンドペーストします。こちらを dhw-pp2-study-04-04-Servo
というファイル名で保存します。
#include <M5Stack.h>
#define SERVO_MIN_WIDTH_MS 0.6
#define SERVO_MAX_WIDTH_MS 2.4
#define LEDC_CHANNEL_3 3 // LEDCのチャンネル指定
#define LEDC_TIMER_BIT 16 // LEDCのPWMタイマーの精度設定
#define LEDC_SERVO_FREQ 50 // サーボ信号の1サイクル 50Hz:20ms
#define SERVO_PIN 22 // Servo ピン
void setup() {
M5.begin(
true, // LCD ディスプレイ ON
false, // SDカードは動かさない設定
true, // シリアル ON
false // I2C 無効(PORT A を GPIO で使用するため)
);
// スタート
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(10, 100);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(3);
// Arduino のシリアルモニタ・M5Stack LCDディスプレイ両方にメッセージを出す
Serial.print("Servo"); // Arduino のシリアルモニタにメッセージを出す
M5.Lcd.print("Servo"); // M5Stack LCDディスプレイにメッセージを出す(英語のみ)
// servoモーター設定
servo_setup();
}
// サーボ角度 PWM 補助
int servo_pwm_count(int v)
{
float vv = (v + 90) / 180.0 ;
return (int)(65536 * (SERVO_MIN_WIDTH_MS + vv * (SERVO_MAX_WIDTH_MS -SERVO_MIN_WIDTH_MS)) / 20.0) ;
}
// サーボ設定
void servo_setup(){
// 16ビット精度で制御
ledcSetup(LEDC_CHANNEL_3, LEDC_SERVO_FREQ, LEDC_TIMER_BIT) ;
// CH3 を SERVO にする
ledcAttachPin(SERVO_PIN, LEDC_CHANNEL_3) ;
}
// サーボの角度を設定
void servo_degree(int degree){
ledcWrite(LEDC_CHANNEL_3, servo_pwm_count(degree)) ;
}
void loop() {
M5.update();
if (M5.BtnA.wasReleased()) {
// A ボタン
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(10, 100);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(3);
Serial.print("A"); //
M5.Lcd.print("A Degree:90"); //
// サーボの角度を設定
servo_degree(90) ;
} else if (M5.BtnB.wasReleased()) {
// B ボタン
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(10, 100);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(3);
Serial.print("B"); //
M5.Lcd.print("B Degree:0"); //
// サーボの角度を設定
servo_degree(0) ;
} else if (M5.BtnC.wasReleased()) {
// C ボタン
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(10, 100);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(3);
Serial.print("C"); //
M5.Lcd.print("C Degree:-90"); //
// サーボの角度を設定
servo_degree(-90) ;
}
}
M5Stack に書き込んでみる
M5Stack に書き込んでみましょう。
動かしてみる
動かしてみると、Servo という文字が現れます。
A ボタンで -90 度、B ボタンで 0 度、C ボタンで 90 度にサーボモータの角度が操れます。
LINE BOT と連携するソースコードを試す
つづいて、LINE BOT 連携です。
0~180 の整数をおくってサーボの角度を制御してみましょう。
たとえば、60 と送ると 60 度に制御できる仕組みです。
Arduino IDE で新規ファイルを作成し、以下のコードをコピーアンドペーストします。こちらを dhw-pp2-study-04-05-Servo-LINEBOT
というファイル名で保存します。
#include <M5Stack.h>
#define SERVO_MIN_WIDTH_MS 0.6
#define SERVO_MAX_WIDTH_MS 2.4
#define LEDC_CHANNEL_3 3 // LEDCのチャンネル指定
#define LEDC_TIMER_BIT 16 // LEDCのPWMタイマーの精度設定
#define LEDC_SERVO_FREQ 50 // サーボ信号の1サイクル 50Hz:20ms
#define SERVO_PIN 22 // Servo ピン
// Wi-Fi をつなぐためのライブラリ
// 今回は MQTT のため
#include <WiFiClient.h>
#include <WiFi.h>
// MQTT をつなぎためのライブラリ
// 今回追加インストールする
#include <PubSubClient.h> // インストールすれば色がつく
// JSON を扱いやすくするライブラリ
#include <ArduinoJson.h> // こちらは色がついてなくてOK
// Wi-FiのSSID
char *ssid = "Wi-FiのSSID";
// Wi-Fiのパスワード
char *password = "Wi-Fiのパスワード";
// 今回使いたい CloudMQTT のブローカーのアドレス
const char *mqttEndpoint = "今回使いたい CloudMQTT のブローカーのアドレス";
// 今回使いたい CloudMQTT のポート
const int mqttPort = 1883;
// 今回使いたい CloudMQTT のユーザー名
const char *mqttUsername = "今回使いたい CloudMQTT のユーザー名";
// 今回使いたい CloudMQTT のパスワード
const char *mqttPassword = "今回使いたい CloudMQTT のパスワード";
// デバイスID
// デバイスIDは機器ごとにユニークにします
// YOURNAME を自分の名前の英数字に変更します
// デバイスIDは同じMQTTブローカー内で重複すると大変なので、後の処理でさらにランダム値を付与してますが、名前を変えるのが確実なので、ちゃんと変更しましょう。
char *deviceID = "M5Stack-YOURNAME";
// MQTT メッセージを LINE BOT に知らせるトピック
// YOURNAME を自分の名前の英数字に変更します
char *pubTopic = "/dhw/pp2/mqtt/YOURNAME/publish";
// MQTT メッセージを LINE BOT から待つトピック
// YOURNAME を自分の名前の英数字に変更します
char *subTopic = "/dhw/pp2/mqtt/YOURNAME/subscribe";
// JSON 送信時に使う buffer
char pubJson[255];
// PubSubClient まわりの準備
WiFiClient httpClient;
PubSubClient mqttClient(httpClient);
void setup() {
// init lcd, serial, but don't init sd card
// LCD ディスプレイとシリアルは動かして、SDカードは動かさない設定
M5.begin(true, false, true);
// スタート
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(2);
// Arduino のシリアルモニタ・M5Stack LCDディスプレイ両方にメッセージを出す
Serial.print("START"); // Arduino のシリアルモニタにメッセージを出す
M5.Lcd.print("START"); // M5Stack LCDディスプレイにメッセージを出す(英語のみ)
// WiFi 接続開始
WiFi.begin(ssid, password);
// 勝手に Button A が押されることを回避
WiFi.setSleep(false);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
// Arduino のシリアルモニタ・M5Stack LCDディスプレイ両方にメッセージを出す
Serial.print(".");
M5.Lcd.print(".");
}
// WiFi Connected
// WiFi 接続完了
M5.Lcd.setCursor(10, 40);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(2);
// Arduino のシリアルモニタ・M5Stack LCDディスプレイ両方にメッセージを出す
// 前のメッセージが print で改行入っていないので println で一つ入れる
Serial.println(""); // Arduino のシリアルモニタにメッセージを出し改行が最後に入る
M5.Lcd.println(""); // M5Stack LCDディスプレイにメッセージを出す改行が最後に入る(英語のみ)
// Arduino のシリアルモニタ・M5Stack LCDディスプレイ両方にメッセージを出す
Serial.println("WiFi Connected."); // Arduino のシリアルモニタにメッセージを出す
M5.Lcd.println("WiFi Connected."); // M5Stack LCDディスプレイにメッセージを出す(英語のみ)
// ちゃんとつながったと分かるために 2 秒待ってから MQTT の処理に行く
delay(2000);
// MQTT の接続先設定
mqttClient.setServer(mqttEndpoint, mqttPort);
// MQTT のデータを受け取った時(購読時)の動作を設定
mqttClient.setCallback(mqttCallback);
// MQTT の接続
mqttConnect();
// servoモーター設定
servo_setup();
}
// サーボ角度 PWM 補助
int servo_pwm_count(int v)
{
float vv = (v + 90) / 180.0 ;
return (int)(65536 * (SERVO_MIN_WIDTH_MS + vv * (SERVO_MAX_WIDTH_MS -SERVO_MIN_WIDTH_MS)) / 20.0) ;
}
// サーボ設定
void servo_setup(){
// 16ビット精度で制御
ledcSetup(LEDC_CHANNEL_3, LEDC_SERVO_FREQ, LEDC_TIMER_BIT) ;
// CH3 を SERVO にする
ledcAttachPin(SERVO_PIN, LEDC_CHANNEL_3) ;
}
// サーボの角度を設定
void servo_degree(int degree){
ledcWrite(LEDC_CHANNEL_3, servo_pwm_count(degree)) ;
}
void mqttConnect() {
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(2);
// MQTT clientID のランダム化(名称重複対策)
char clientID[40] = "clientID";
String rndNum = String(random(0xffffff), HEX);
String deviceIDRandStr = String(deviceID);
deviceIDRandStr.concat("-");
deviceIDRandStr.concat(rndNum);
deviceIDRandStr.toCharArray(clientID, 40);
M5.Lcd.println("[MQTT]");
M5.Lcd.println("");
M5.Lcd.printf("- clientID ");
M5.Lcd.println("");
M5.Lcd.println(clientID);
// 接続されるまで待ちます
while (!mqttClient.connected()) {
if (mqttClient.connect(clientID,mqttUsername,mqttPassword)) {
Serial.println("Connected.");
M5.Lcd.println("");
M5.Lcd.println("- MQTT Connected.");
// subTopic 変数で指定されたトピックに向けてデータを送ります
int qos = 0;
mqttClient.subscribe(subTopic, qos);
Serial.println("Subscribe start.");
M5.Lcd.println("");
M5.Lcd.println("- MQTT Subscribe start.");
M5.Lcd.println(subTopic);
// 初回データ送信 publish ///////////
// データ送信のための JSON をつくる
DynamicJsonDocument doc(1024);
doc["message"] = "Connected";
// pubJson という変数に JSON 文字列化されたものが入る
serializeJson(doc, pubJson);
// pubTopic 変数で指定されたトピックに向けてデータを送ります
mqttClient.publish(pubTopic, pubJson);
} else {
// MQTT 接続エラーの場合はつながるまで 5 秒ごとに繰り返します
Serial.print("Failed. Error state=");
Serial.println(mqttClient.state());
// Wait 5 seconds before retrying
delay(5000);
}
}
}
// JSON を格納する StaticJsonDocument を準備
StaticJsonDocument<2048> jsonData;
// MQTT のデータを受け取った時(購読時)の動作を設定
void mqttCallback (char* topic, byte* payload, unsigned int length) {
// データ取得
String str = "";
Serial.print("Received. topic=");
Serial.println(topic);
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
str += (char)payload[i];
}
Serial.print("\n");
// 来た文字列を JSON 化して扱いやすくする
// 変換する対象は jsonData という変数
DeserializationError error = deserializeJson(jsonData, str);
// JSON パースのテスト
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
// 以下 jsonData 内が JSON として呼び出せる
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(2);
M5.Lcd.println("MQTT Subscribed data");
// データの取り出し
// https://arduinojson.org/v6/example/parser/
const int message = jsonData["message"];
// データの表示
M5.Lcd.setCursor(0, 100);
M5.Lcd.setTextSize(4);
M5.Lcd.println(message);
servo_degree(message);
}
// 常にチェックして切断されたら復帰できるようにする対応
void mqttLoop() {
if (!mqttClient.connected()) {
mqttConnect();
}
mqttClient.loop();
}
void loop() {
M5.update();
// 常にチェックして切断されたら復帰できるようにする対応
mqttLoop();
if (M5.BtnA.wasReleased()) {
// A ボタンを押したら JSON 形式のメッセージを飛ばす
// データ送信のための JSON をつくる
DynamicJsonDocument doc(1024);
doc["message"] = "Pushed A";
// pubJson という変数に JSON 文字列化されたものが入る
serializeJson(doc, pubJson);
// pubTopic 変数で指定されたトピックに向けてデータを送ります
mqttClient.publish(pubTopic, pubJson);
} else if (M5.BtnB.wasReleased()) {
// B ボタンを押したら JSON 形式のメッセージを飛ばす
// データ送信のための JSON をつくる
DynamicJsonDocument doc(1024);
doc["message"] = "Pushed B";
// pubJson という変数に JSON 文字列化されたものが入る
serializeJson(doc, pubJson);
// pubTopic 変数で指定されたトピックに向けてデータを送ります
mqttClient.publish(pubTopic, pubJson);
} else if (M5.BtnC.wasReleased()) {
// C ボタンを押したら JSON 形式のメッセージを飛ばす
// データ送信のための JSON をつくる
DynamicJsonDocument doc(1024);
doc["message"] = "Pushed C";
// pubJson という変数に JSON 文字列化されたものが入る
serializeJson(doc, pubJson);
// pubTopic 変数で指定されたトピックに向けてデータを送ります
mqttClient.publish(pubTopic, pubJson);
}
}
Wi-Fi 情報を反映
// Wi-FiのSSID
char *ssid = "Wi-FiのSSID";
// Wi-Fiのパスワード
char *password = "Wi-Fiのパスワード";
自分のつなぎたい Wi-Fi の SSID とパスワードを反映します。
MQTT の接続設定を反映
LINE BOT と同じように 今回は私(講師)の方が、CloudMQTT というサービスで、ひとつブローカーを立ち上げているので、そのまま使いましょう。
// 今回使いたい CloudMQTT のブローカーのアドレス
const char *mqttEndpoint = "今回使いたい CloudMQTT のブローカーのアドレス";
// 今回使いたい CloudMQTT のポート
const int mqttPort = 1883;
// 今回使いたい CloudMQTT のユーザー名
const char *mqttUsername = "今回使いたい CloudMQTT のユーザー名";
// 今回使いたい CloudMQTT のパスワード
const char *mqttPassword = "今回使いたい CloudMQTT のパスワード";
ここの設定を Slack でお知らせする設定で置き換えましょう。
LINE BOT でも設定した自分の名前を思い出しましょう
このあと、MQTT の送受信トピックとクライアントIDに自分の名前を反映します。
LINE BOT でも設定した自分の名前を思い出して、全く同じもの
を使いましょう。
MQTT の送受信トピックとクライアントIDに自分の名前を反映します
MQTT では「自分がどんな名前の機器か」「どこからデータを待ち」「どこへデータを知らせる」という情報を MQTT ブローカーに知らせてあげると、いろいろなデバイスでデータが飛び交っても、目的のところにちゃんと届くようにしてくれます。しかも双方向。
まるで、住所と表札のようなものです。
とにもかくにも、これが重複してしまうと、違うところにデータが送られてしまったり、自分の名前がほかの人と同じになって混乱してしまいます。
// デバイスID
// デバイスIDは機器ごとにユニークにします
// YOURNAME を自分の名前の英数字に変更します
// デバイスIDは同じMQTTブローカー内で重複すると大変なので、後の処理でさらにランダム値を付与してますが、名前を変えるのが確実なので、ちゃんと変更しましょう。
char *deviceID = "M5Stack-YOURNAME";
// MQTT メッセージを LINE BOT に知らせるトピック
// YOURNAME を自分の名前の英数字に変更します
char *pubTopic = "/dhw/pp2/mqtt/YOURNAME/publish";
// MQTT メッセージを LINE BOT から待つトピック
// YOURNAME を自分の名前の英数字に変更します
char *subTopic = "/dhw/pp2/mqtt/YOURNAME/subscribe";
たとえば、hogehoge さんなら YOURNAME を hogehoge に変更します。
LINE BOT 連携のプログラムを M5Stack に書き込んでみる
そして、もう一度保存します。(大事)
M5Stack に書き込んでみましょう。
LINE BOT の Gitpod がスリープしてたら起こす
ここまで作業で時間が経過していると、LINE BOT の Gitpod がスリープしているかもしれません。
そのときは、Open Workspace で起こしてあげましょう。
LINE BOT 連携のプログラムを M5Stack に動かしてみる
書き込みと同時に Connected というメッセージが MQTT ブローカーに送信されます。
実際に M5Stack から接続されているデバイスID(clientID)やデータを待ち受けるトピック subscribe が表示されているので YOURNAME になっていないかや、設定した名前が Gitpod 側で設定した名前と一致しているかを確認しましょう。
0~180 の整数をおくってサーボの角度を制御してみましょう。
たとえば、60 と送ると 60 度に制御できます。
質疑応答
ここまでで質問があればどうぞ!
次にすすみましょう
左のナビゲーションから次の教材にすすみましょう。