M5Stack과 M5StickC 간의 arduino BLE 통신

1. 개요



M5Stack과 M5StickC를 구입했습니다.

Ultrasonic Ranger라고 하는 초음파로 거리를 측정하는 GROVE 모듈이 수중에 있었으므로, 이것을 우선 연결해 보자고 하는 곳으로부터 처음으로, BLE로 날려 보려고 생각했습니다.
  • M5Stack 화면에 Ultrasonic Ranger로 얻은 거리 값 표시
  • M5Stack의 A 버튼을 누르면 "XX cm 정도입니다"라는 MP3 파일을 내장 스피커로 재생합니다
  • M5Stack과 M5StickC에는 모두 통신 기능 (ESP32)이 있으므로 BLE에서 M5StickC에 거리 값을 보냅니다

  • 라는 느낌으로 증축해간 것이 됩니다.
    이미지도는 이쪽.


    2. 환경


  • M5Stack Gray (조금 전 메모리 적은 버전)
  • M5StickC
  • Windows10
  • Arduino IDE

  • 3. 사전 준비



    참고 사이트 등에 있는, 음성 합성 데이터를 MP3로 다운로드할 수 있는 서비스를 이용해 적당한 음성 MP3 파일을 다운로드해, ​​microSD 카드에 기입해 M5Stack에 세트 해 둡니다.

    4. 프로그램



    기본적으로 arduino 샘플 프로그램이나 참고하신 코드에서 잘라 붙여 desu.

    4.1 BLE 주변 측(M5Stack)



    ※사전에 arduino IDE로 Partition Scheme을 No OTA로 하고 나서 빌드합니다. 현재 판매되고 있는 것이라면 메모리가 증량되고 있으므로 불필요할지도 모릅니다
    //Make sure to install
    #include <Arduino.h>
    #include <M5Stack.h>
    #include "Ultrasonic.h"
    #include <WiFi.h>
    #include "AudioFileSourceSD.h"
    #include "AudioFileSourceID3.h"
    #include "AudioGeneratorMP3.h"
    #include "AudioOutputI2S.h"
    #include <BLEDevice.h>
    #include <BLEServer.h>
    #include <BLEUtils.h>
    #include <BLE2902.h>
    
    uint16_t Dvalue = 0;
    
    BLEServer* pServer = NULL;
    BLECharacteristic* pCharacteristic = NULL;
    bool deviceConnected = false;
    bool oldDeviceConnected = false;
    uint32_t value = 0;
    
    // See the following for generating UUIDs:
    // https://www.uuidgenerator.net/
    
    //#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
    //#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
    #define SERVICE_UUID        "7780" //適当なID(上のような長いUUIDだとうまく認識されなかったので適当に)
    #define CHARACTERISTIC_UUID "7781" //適当なID
    
    
    class MyServerCallbacks: public BLEServerCallbacks {
        void onConnect(BLEServer* pServer) {
          deviceConnected = true;
        };
    
        void onDisconnect(BLEServer* pServer) {
          deviceConnected = false;
        }
    };
    
    AudioGeneratorMP3 *mp3;
    AudioFileSourceSD *file;
    AudioOutputI2S *out;
    AudioFileSourceID3 *id3;
    
    Ultrasonic ultrasonic(22);
    void setup()
    {
      M5.begin();
      WiFi.mode(WIFI_OFF); 
      Serial.print("");
    
      // Create the BLE Device
      BLEDevice::init("M5STACK1G"); //適当な名前
    
      // Create the BLE Server
      pServer = BLEDevice::createServer();
      pServer->setCallbacks(new MyServerCallbacks());
    
      // Create the BLE Service
      BLEService *pService = pServer->createService(SERVICE_UUID);
    
      // Create a BLE Characteristic
      pCharacteristic = pService->createCharacteristic(
                          CHARACTERISTIC_UUID,
                          BLECharacteristic::PROPERTY_READ   |
                          BLECharacteristic::PROPERTY_WRITE  |
                          BLECharacteristic::PROPERTY_NOTIFY |
                          BLECharacteristic::PROPERTY_INDICATE
                        );
    
      // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
      // Create a BLE Descriptor
      pCharacteristic->addDescriptor(new BLE2902());
    
      // Start the service
      pService->start();
    
      // Start advertising
      BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
      pAdvertising->addServiceUUID(SERVICE_UUID);
      pAdvertising->setScanResponse(false);
      pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
      BLEDevice::startAdvertising();
    //  Serial.println("Waiting a client connection to notify...");
    }
    
    void PlaySound(int fid){ //予めmicroSDにmp3ファイルを格納しておきます
        switch(fid){
          case 0: //under 5cm
            file = new AudioFileSourceSD("/under5cm-jp-f.mp3");
            break;
          case 1: //about 10cm
            file = new AudioFileSourceSD("/10cm-jp-f.mp3");
            break;
          case 2: //about 20cm
            file = new AudioFileSourceSD("/20cm-jp-f.mp3");
            break;
          case 3: //about 30cm
            file = new AudioFileSourceSD("/30cm-jp-f.mp3");
            break;
          case 4: //about 50cm
            file = new AudioFileSourceSD("/50cm-jp-f.mp3");
            break;
          case 5: //about 1m
            file = new AudioFileSourceSD("/1m-jp-f.mp3");
            break;
          case 6: //about 1.5m
            file = new AudioFileSourceSD("/1_5m-jp-f.mp3");
            break;
          case 7: //about 2m
            file = new AudioFileSourceSD("/2m-jp-f.mp3");
            break;
          case 8: //about 3m
            file = new AudioFileSourceSD("/3m-jp-f.mp3");
            break;
          case 9: //over 3.5m
            file = new AudioFileSourceSD("/over3m-jp-f.mp3");
            break;
        }
    
        id3 = new AudioFileSourceID3(file);
        out = new AudioOutputI2S(0, 1); // Output to builtInDAC
        out->SetOutputModeMono(true);
        mp3 = new AudioGeneratorMP3();
        mp3->begin(id3, out);
    
        while(mp3->isRunning()) {
          if (!mp3->loop()) mp3->stop();
        }
    }
    
    void pressAdisp(int fid,int rng){
      M5.Lcd.clear(WHITE);
      M5.Lcd.setTextSize(4);
      M5.Lcd.setTextColor(BLACK);
      M5.Lcd.println("VOICE ON:");
      M5.Lcd.println(rng);
    }
    
    void loop()
    {
      M5.update();
      long RangeInInches;
      long RangeInCentimeters;
      int voicenum;
      uint8_t buf[2]; //要素数2
    
      RangeInCentimeters = ultrasonic.MeasureInCentimeters(); // two measurements should keep an interval
      voicenum=99;
    
      if (M5.BtnA.wasReleased()) { //Aボタン押下時に距離範囲毎にMp3ファイルを再生
        if (RangeInCentimeters<5  ) { //距離範囲は適当
          voicenum=0;
          pressAdisp(voicenum,RangeInCentimeters);
        }else if (RangeInCentimeters>=5 && RangeInCentimeters<15) {
          voicenum=1;
          pressAdisp(voicenum,RangeInCentimeters);
        }else if (RangeInCentimeters>=15 && RangeInCentimeters<25) {
          voicenum=2;
          pressAdisp(voicenum,RangeInCentimeters);
        }else if (RangeInCentimeters>=25 && RangeInCentimeters<40) {
          voicenum=3;
          pressAdisp(voicenum,RangeInCentimeters);
        }else if (RangeInCentimeters>=40 && RangeInCentimeters<75) {
          voicenum=4;
          pressAdisp(voicenum,RangeInCentimeters);
        }else if (RangeInCentimeters>=75 && RangeInCentimeters<125) {
          voicenum=5;
          pressAdisp(voicenum,RangeInCentimeters);
        }else if (RangeInCentimeters>=125 && RangeInCentimeters<175) {
          voicenum=6;
          pressAdisp(voicenum,RangeInCentimeters);
        }else if (RangeInCentimeters>=175 && RangeInCentimeters<250) {
          voicenum=7;
          pressAdisp(voicenum,RangeInCentimeters);
        }else if (RangeInCentimeters>=251 && RangeInCentimeters<350) {
          voicenum=8;
          pressAdisp(voicenum,RangeInCentimeters);
        }else if (RangeInCentimeters>=350) {
          voicenum=9;
          pressAdisp(voicenum,RangeInCentimeters);
        }else{
          voicenum=99;
        }
        PlaySound(voicenum);
      } 
      M5.Lcd.clear(BLACK);
      M5.Lcd.setCursor(10, 0);
      M5.Lcd.setTextSize(5);
      M5.Lcd.setTextColor(YELLOW);
      M5.Lcd.println("DISTANCE:");
      M5.Lcd.setTextColor(RED);
      M5.Lcd.println(String(RangeInCentimeters)+ " cm");
      Serial.println(RangeInCentimeters);
      delay(300);
      Dvalue=RangeInCentimeters; //distance value
    
    // BT code
        // notify changed value
        if (deviceConnected) {
            memset(buf,0,sizeof buf);  
            buf[0]=(uint8_t)(Dvalue & 0xff);
            buf[1]=(uint8_t)((Dvalue >>8) & 0xff);
            // 数値は0~500少しまであるので、2バイト使います
    
            pCharacteristic->setValue(buf,sizeof buf);
            pCharacteristic->notify();
            value++;
            delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
        }
        // disconnecting
        if (!deviceConnected && oldDeviceConnected) {
            delay(500); // give the bluetooth stack the chance to get things ready
            pServer->startAdvertising(); // restart advertising
    //        Serial.println("start advertising");
            oldDeviceConnected = deviceConnected;
        }
        // connecting
        if (deviceConnected && !oldDeviceConnected) {
            // do stuff here on connecting
            oldDeviceConnected = deviceConnected;
        }
    }
    
  • BLE 주변 장치로부터 통신이 날고 있는지는 스마트 폰의 BLE 앱을 사용하면 알 수 있습니다. (iOS에서는 LightBlue, BLE Scanner 등)
  • UUID는 htps //w w. 우우 d 게 네라와 r. 네 t/과 같이 생성 할 수 있지만이 긴 문자열을 사용하면 어디가 좋지 않은지 센트럴과 연결할 수 없었기 때문에 적당한 짧은 숫자로하고 있습니다
  • Characteristic에 태우는 데이터는 2바이트가 됩니다. (Ultrasonic Ranger로부터의 거리 수치가 0~500 조금까지 있으므로 1바이트에서는 부족하다)
  • 여러 파일을 캡처하고 있으므로 빌드하는 데 시간이 걸립니다. 내 Core i3-7130U에서 5 분 정도 걸립니다

  • 4.2 BLE 중앙측(M5StickC)


    /**
     * A BLE client example that is rich in capabilities.
     * There is a lot new capabilities implemented.
     * author unknown
     * updated by chegewara
     */
    
    #include <M5StickC.h>
    #include "BLEDevice.h"
    //#include "BLEScan.h"
    
    // The remote service we wish to connect to.
    //static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
    static BLEUUID serviceUUID("7780");
    // The characteristic of the remote service we are interested in.
    //static BLEUUID    charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");
    static BLEUUID    charUUID("7781");
    
    static boolean doConnect = false;
    static boolean connected = false;
    static boolean doScan = false;
    static BLERemoteCharacteristic* pRemoteCharacteristic;
    static BLEAdvertisedDevice* myDevice;
    
    uint16_t val; //安直にglobal...
    
    static void notifyCallback(
      BLERemoteCharacteristic* pBLERemoteCharacteristic,  uint8_t* pData,  size_t length,  bool isNotify) {
        val=0;
        val=(uint16_t)(pData[1]<<8 | pData[0]); //ペリフェラルから受信した値(2バイト)を数値に変換
        Serial.println(val);
    }
    
    class MyClientCallback : public BLEClientCallbacks {
      void onConnect(BLEClient* pclient) {
      }
    
      void onDisconnect(BLEClient* pclient) {
        connected = false;
        Serial.println("onDisconnect");
      }
    };
    
    bool connectToServer() {
        Serial.print("Forming a connection to ");
        Serial.println(myDevice->getAddress().toString().c_str());
    
        BLEClient*  pClient  = BLEDevice::createClient();
        Serial.println(" - Created client");
    
        pClient->setClientCallbacks(new MyClientCallback());
    
        // Connect to the remove BLE Server.
        pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
        Serial.println(" - Connected to server");
    
        // Obtain a reference to the service we are after in the remote BLE server.
        BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
        if (pRemoteService == nullptr) {
          Serial.print("Failed to find our service UUID: ");
          Serial.println(serviceUUID.toString().c_str());
          pClient->disconnect();
          return false;
        }
        Serial.println(" - Found our service");
    
    
        // Obtain a reference to the characteristic in the service of the remote BLE server.
        pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
        if (pRemoteCharacteristic == nullptr) {
          Serial.print("Failed to find our characteristic UUID: ");
          Serial.println(charUUID.toString().c_str());
          pClient->disconnect();
          return false;
        }
        Serial.println(" - Found our characteristic");
    
        // Read the value of the characteristic.
        if(pRemoteCharacteristic->canRead()) {
          //サンプルだと下の行を実行するタイミングでリブートかかるのでコメント化。
          //notifyCallbackのほうでCharacteristicから取得するように変更
    
          //std::string value = pRemoteCharacteristic->readValue();
          //Serial.print("The characteristic value was: ");
          //Serial.println(value.c_str());
        }
    
        if(pRemoteCharacteristic->canNotify())
          pRemoteCharacteristic->registerForNotify(notifyCallback);
    
        connected = true;
    }
    /**
     * Scan for BLE servers and find the first one that advertises the service we are looking for.
     */
    class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
     /**
       * Called for each advertising BLE server.
       */
      void onResult(BLEAdvertisedDevice advertisedDevice) {
        Serial.print("BLE Advertised Device found: ");
        Serial.println(advertisedDevice.toString().c_str());
    
        // We have found a device, let us now see if it contains the service we are looking for.
    
        if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
    //    if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) {
    
          BLEDevice::getScan()->stop();
          myDevice = new BLEAdvertisedDevice(advertisedDevice);
          doConnect = true;
          doScan = true;
    
        } // Found our server
      } // onResult
    }; // MyAdvertisedDeviceCallbacks
    
    
    void setup() {
      M5.begin();
      Serial.begin(1500000);
      Serial.println("Starting Arduino BLE Client application...");
      BLEDevice::init("");
    
      // Retrieve a Scanner and set the callback we want to use to be informed when we
      // have detected a new device.  Specify that we want active scanning and start the
      // scan to run for 5 seconds.
      BLEScan* pBLEScan = BLEDevice::getScan();
      pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
    //  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
      pBLEScan->setInterval(1349);
      pBLEScan->setWindow(449);
      pBLEScan->setActiveScan(true);
      pBLEScan->start(5, false);
    } // End of setup.
    
    
    // This is the Arduino main loop function.
    void loop() {
      M5.Lcd.fillScreen(BLACK); //この辺の表示は接続確認後、にしたほうがいいかも
      M5.Lcd.setCursor(0, 10);
      M5.Lcd.setTextSize(2);
      M5.Lcd.setTextColor(YELLOW);
      M5.Lcd.println("DSTNC:"); //distanceの略w 画面小さいので
      M5.Lcd.setTextSize(4);
      M5.Lcd.setTextColor(RED);
      M5.Lcd.println(val);
      M5.Lcd.setTextColor(GREEN);
      M5.Lcd.println("cm");
    
      // If the flag "doConnect" is true then we have scanned for and found the desired
      // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
      // connected we set the connected flag to be true.
      if (doConnect == true) {
        if (connectToServer()) {
          Serial.println("We are now connected to the BLE Server.");
        } else {
          Serial.println("We have failed to connect to the server; there is nothin more we will do.");
        }
        doConnect = false;
      }
    
      // If we are connected to a peer BLE Server, update the characteristic each time we are reached
      // with the current time since boot.
      if (connected) {
    /*
        String newValue = "Time since boot: " + String(millis()/1000);
        Serial.println("Setting new characteristic value to \"" + newValue + "\"");
    
        // Set the characteristic's value to be the array of bytes that is actually a string.
        pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
    */
      }else if(doScan){
        BLEDevice::getScan()->start(0);  // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino
      }
    
      delay(1000); // Delay a second between loops.
    } // End of loop
    
  • 주변 장치 (M5Stack)에 연결하여 보낸 숫자를 표시하기 만하면됩니다
  • arduino의 BLE Client 샘플이라면, connect 후에 Characteristic에서 내용 취득하는 곳에서 M5StickC가 재부팅되기 때문에 (어째서), 거기는 코멘트화해, 콜백 함수 notifyCallback 쪽으로 Characteristic로부터 취득하도록 변경했습니다 (리굴 잘 모르지만 리부트는 걸리지 않게 되었다)
  • 여기는 Partition Scheme을 No OTA로하지 않아도 OK였습니다

  • 5. 기타


  • 어색한 곳이 곳곳에 있습니다만, 여러가지 참고로 하고 어떻게든 BLE로 통신할 수 있었던 것이 상당히 기쁜 w
  • Arduino IDE에서는 시리얼 모니터 화면이 하나밖에 나오지 않기 때문에, 동작 테스트에서는 1개씩 실시하는지, PC2대 사용하는 편이 편할지도 모릅니다

  • 6. 참고로 한 페이지



    M5Stack에서 BLE 사용
    htps : // m / nsawa / ms / 2 a fd4 꺾어 9c10 a f87d0133

    BLE 환경 센서·게이트웨이(ESP32편)
    htps : // 아메다였다. 이오/사 mpぇs/m5s타운 ck/bぇ_gw/

    M5Stack으로 BLE 환경 센서 단말기 만들기
    htps : // 아무도였다. 이오/사 mp㎇s/m5s ck/m5s ck_bぇ_전자 r/

    M5Stack으로 SD 카드에서 mp3를 재생해보세요
    htps : // 코 m / k 탄사이 / ms / f0096495 에 2 또는 7 38 에 b7

    WEB 편리 노트(음성 MP3 생성)
    htps : //에서. c 만. jp/오테ぇr/ゔぉ이세/

    이상입니다. 고마워요.

    좋은 웹페이지 즐겨찾기