Grove サーボモータ

image

今回のプログラムはどのように動くか

image

書き込むと A ボタンで -90 度、B ボタンで 0 度、C ボタンで 90 度にサーボモータの角度が操れます。

Grove への Grove ケーブルのつなぎかた

今回はサーボモータに直接つながっているので Grove 変換ケーブルをつなぐ必要はありません。

サーボモータにパーツを装着

image

サーボモータと同行されているパーツのうち、こちらの白いプラスチック製の長いパーツを取り出します。

image

サーボ側の白い出っ張りのギザギザと、

image

パーツ側のギザギザの凹みを合わせてはめ込みます。

image

よく確認しましょう。

image

はめ込むことで角度の変化がより分かりやすくなります。

実際にこのパーツ含めたパーツ群は、何かを動かすときに力を伝えるための軸の役割しています。

PORT A という Grove ポートを確認

image

今回は PORT A という Grove ポートにつなぐので確認します。

GPIO としては 21 と 22 で操作できます。今回は 22 で指示を出します。

M5Stack への Grove ケーブルのつなぎかた

image

PORT A に挿しこみます。

image

このように接続できます。

ソースコードを反映&保存

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 に書き込んでみる

image

M5Stack に書き込んでみましょう。

動かしてみる

image

動かしてみると、Servo という文字が現れます。

image

A ボタンで -90 度、B ボタンで 0 度、C ボタンで 90 度にサーボモータの角度が操れます。

LINE BOT と連携するソースコードを試す

つづいて、LINE BOT 連携です。

image

0~180 の整数をおくってサーボの角度を制御してみましょう。

image

たとえば、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 に書き込んでみる

そして、もう一度保存します。(大事)

image

M5Stack に書き込んでみましょう。

LINE BOT の Gitpod がスリープしてたら起こす

image

ここまで作業で時間が経過していると、LINE BOT の Gitpod がスリープしているかもしれません。

そのときは、Open Workspace で起こしてあげましょう。

LINE BOT 連携のプログラムを M5Stack に動かしてみる

image

書き込みと同時に Connected というメッセージが MQTT ブローカーに送信されます。

実際に M5Stack から接続されているデバイスID(clientID)やデータを待ち受けるトピック subscribe が表示されているので YOURNAME になっていないかや、設定した名前が Gitpod 側で設定した名前と一致しているかを確認しましょう。

image

0~180 の整数をおくってサーボの角度を制御してみましょう。

image

たとえば、60 と送ると 60 度に制御できます。

質疑応答

image

ここまでで質問があればどうぞ!

次にすすみましょう

左のナビゲーションから次の教材にすすみましょう。

results matching ""

    No results matching ""