Wio Terminalであそぶ 〜基本の入出力機能+Groveモジュールを使う〜

M5StickC関連の記事を見てくださったSeeed社の方から、「良かったらWio Terminalを触ってレビューしませんか?」というお声がけを頂きました。丁度気になっていたアイテムでしたので快諾させて頂き、取り急ぎちょっと触ってみて、諸々のサンプルコード作成も兼ねて簡単な測距デバイスを作ってみました。

以下、パッケージから簡単にご紹介です。

同梱物は以下です。5方向スイッチのトップのスペアがついてきましたが、特に取れやすいということはないです。

ちなみにユーザマニュアルは英語/日本語/ドイツ語対応しています。

本体正面。スッキリまとまっている印象です。5方向スイッチがデフォルトでついているのが面白いですね。昔からゲーム機のコントローラに慣れ親しんだ身としては、左側ではなく右側に方向キーがあるのがちょっと不思議な感じです。

本体裏面。Raspberry Piとの接続を意識したGPIO、赤外線送信と明るさセンサを利用するための透明窓と、なかなか特徴的な見た目になっています。赤外線送信と明るさセンサのための透明窓は、Wio Terminalを普通に置いたりネジ穴でマウントしてしまうと完全に塞がれてしまうので、搭載はされているものの、実際利用しようと思うとちょっと工夫が要りそうです。

側部の電源スイッチ&SDカードスロット。電源スイッチは上の写真だとOFFの状態になっていて、この状態でPCに繋いでもシリアルポートは見えませんので、Arduinoとかに慣れている人はちょっと戸惑うかもしれません。一段下げるとON状態になり、PCからのプログラム書き込みも可能になります。さらに一段下げるとリセットがかかりますが、こちらはバネでスイッチがONに戻るようになっています。PCからシリアルポートが見えなくなったときは、リセット操作を素早く2回実行すると、大抵見えるようになります(←ブートローダーを起動させるためのオフィシャルな手順)。

 

さて、ここから開発に入っていきます。開発手段については定番のArduino言語の他、MicroPythonやArduPyなどでの開発も可能とのことなのですが、取り急ぎは使い慣れたArduino IDEでの開発を進めていきたいと思います。環境構築は以下の公式ドキュメントの手順に従っていけば特に問題はないはずです。私の場合は先にSeeeduino XIAOを使っていたためか、特に追加の手順は不要でした。

Wio Terminalについては、現時点(2020/8/15)においてはそこまで豊富な情報があるわけではないので、上記の公式ドキュメントを参照する機会は多いと思います。

ちなみに、公式ドキュメントの日本語版のページもあるのですが、

一部翻訳が追いついていないところもあるようなので、まずは日本語版の方を見てみて、情報が足りないと思ったら、英語の公式ドキュメントを参照するのが良いと思います。

 

とりあえず何を作るかですが、今後の開発を容易にするため、まずは以下の基本的な入出力機能を扱うためのサンプルコードを書いてみることにします。

  • 3つのユーザ定義ボタン
  • 5方向スイッチ
  • ディスプレイ
  • ブザー

それぞれの機能の個別の使い方は上記の公式ドキュメントで詳しく説明されていますので、そちらのコードをベースに組み合わせていきます。

また、同社のGroveシステムが活用できることもWio Terminalの一つの売りですので、Groveモジュール活用のサンプルとして、超音波距離センサも使ってみることにします。

距離センサの取り付けはこんな感じです。

背面の透明窓をちょっと塞いでしまっているので、赤外線送信は利用不可になっています(明るさセンサは利用可)。

背面左右に本体のマウント用に2mmのネジ穴があるので、それを使えばこんな風にWio Terminal本体にGroveモジュールを直接マウントできてしまいます。

 

で、出来上がったのがこちらです。

 

Aボタン(上部ユーザボタン右)で距離をcm単位で計測します。Cボタン(上部ユーザボタン左)で距離表示を0cmにリセットします。

また、機能上では特に意味はないのですが、5方向スイッチとディスプレイ、ブザー機能を使用するための練習として、5方向スイッチの上下でディスプレイの明るさ調整、左右で背景色を変更できるようにしています(※スイッチの押込みで背景色をデフォルトの黒に戻す)。ディスプレイの明るさ調整は、上限/下限に到達するとブザーでお知らせするようにしています。

ソースコードはこんな感じです。

#define ON  LOW
#define OFF HIGH

uint8_t key_A = OFF;
//uint8_t key_B = OFF;
uint8_t key_C = OFF;
uint8_t prev_key_A = OFF;
//uint8_t prev_key_B = OFF;
uint8_t prev_key_C = OFF;

uint8_t up    = OFF;
uint8_t down  = OFF;
uint8_t left  = OFF;
uint8_t right = OFF;
uint8_t press = OFF;
uint8_t prev_up    = OFF;
uint8_t prev_down  = OFF;
uint8_t prev_left  = OFF;
uint8_t prev_right = OFF;
uint8_t prev_press = OFF;

////////////////////////////////////////////////

#include "Ultrasonic.h"
#define   ULTRASONIC_PIN D0
Ultrasonic ultrasonic(ULTRASONIC_PIN);
long range_in_centimeters = 0;

////////////////////////////////////////////////

#define BUZZER_DURATION_MS 100

//Play tone
void playTone(int tone, int duration) {
  for (long i = 0; i < duration * 1000L; i += tone * 2) {
    digitalWrite(WIO_BUZZER, HIGH);
    delayMicroseconds(tone);
    digitalWrite(WIO_BUZZER, LOW);
    delayMicroseconds(tone);
  }
}

void playNote(char note, int duration) {
  char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
  int  tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 };

  // play the tone corresponding to the note name
  for (int i = 0; i < 8; i++) {
    if (names[i] == note) {
      playTone(tones[i], duration);
    }
  }
}

////////////////////////////////////////////////

#include"TFT_eSPI.h"
#include "lcd_backlight.hpp"
#include <cstdint>
// https://github.com/Seeed-Studio/Seeed_Arduino_LCD/blob/master/TFT_eSPI.h

TFT_eSPI tft;
static  LCDBackLight backLight;
#define LCD_BACKLIGHT (72Ul) // Control Pin of LCD
#define LCD_WIDTH  360
#define LCD_HEIGHT 240
#define LCD_BRIGHTNESS_CONTROL_UNIT 5
#define DRAW_BASE_COLOR TFT_WHITE

#define NUM_BACKGROUND_COLORS 18

uint16_t background_colors[] = {
  TFT_BLACK,
  TFT_NAVY,
  TFT_DARKGREEN,
  TFT_DARKCYAN,
  TFT_MAROON,
  TFT_PURPLE,
  TFT_OLIVE,
  TFT_LIGHTGREY,
  TFT_DARKGREY,
  TFT_BLUE,
  TFT_GREEN,
  TFT_CYAN,
  TFT_RED,
  TFT_MAGENTA,
  TFT_YELLOW,
  TFT_ORANGE,
  TFT_GREENYELLOW,
  TFT_PINK
};

int8_t color_index = 0;

int lcd_max_brightness = 0;
int lcd_brightness = 0;

void control_lcd_brightness(char sign){
  if(sign == '+'){
    lcd_brightness += LCD_BRIGHTNESS_CONTROL_UNIT;
    if(lcd_brightness > lcd_max_brightness){
      lcd_brightness = lcd_max_brightness;
      playNote('C',BUZZER_DURATION_MS);
    }
  }else if(sign == '-'){
    lcd_brightness -= LCD_BRIGHTNESS_CONTROL_UNIT;
    if(lcd_brightness < 0){
      lcd_brightness = 0;
      playNote('c',BUZZER_DURATION_MS);
    }
  }
  backLight.setBrightness(lcd_brightness);
}

void update_distance(){
  tft.setTextSize(10);
  tft.fillRect(0, 50, 200, 150, background_colors[color_index]);
  tft.drawString(String(range_in_centimeters), 50, 50);
}

void draw_ui(){
  // 押しボタン描画
  tft.setTextSize(2);
  tft.fillTriangle( 7,  0,  0, 13, 14, 13, DRAW_BASE_COLOR);
  tft.drawString("RESET", 16, 0);
  tft.fillTriangle(172,  0,  165, 13, 179, 13, DRAW_BASE_COLOR);
  tft.drawString("MEASURE", 181, 0);
  // 計測距離描画
  update_distance();
  tft.setTextSize(4);
  tft.drawString("cm", 200, 72);
  // 液晶操作描画
  tft.drawFastHLine(0, 200, LCD_WIDTH, DRAW_BASE_COLOR);
  tft.setTextSize(2);
  tft.fillTriangle( 7,205,  0, 218, 14, 218, DRAW_BASE_COLOR);
  tft.fillTriangle(14,205, 28, 205, 21, 218, DRAW_BASE_COLOR);
  tft.drawString("Change BG brightnesss", 32, 205);
  tft.fillTriangle( 0,228, 12,221,  12,235, DRAW_BASE_COLOR);
  tft.fillTriangle(16,221, 28,228,  16,235, DRAW_BASE_COLOR);
  tft.drawString("Change BG color", 32, 221);
}

void set_background_color(uint32_t color){
  tft.fillScreen(color);
  // 塗りつぶしてしまうので、再び描画する
  draw_ui();
}

////////////////////////////////////////////////

void setup() {
  Serial.begin(115200);
  pinMode(WIO_KEY_A, INPUT_PULLUP);
  //pinMode(WIO_KEY_B, INPUT_PULLUP);
  pinMode(WIO_KEY_C, INPUT_PULLUP);

  pinMode(WIO_5S_UP,    INPUT_PULLUP);
  pinMode(WIO_5S_DOWN,  INPUT_PULLUP);
  pinMode(WIO_5S_LEFT,  INPUT_PULLUP);
  pinMode(WIO_5S_RIGHT, INPUT_PULLUP);
  pinMode(WIO_5S_PRESS, INPUT_PULLUP);

  pinMode(WIO_BUZZER, OUTPUT);

  // ディスプレイ初期設定
  tft.begin();
  tft.setRotation(3);
  backLight.initialize();
  digitalWrite(LCD_BACKLIGHT, HIGH);
  set_background_color(TFT_BLACK);
  lcd_max_brightness = backLight.getMaxBrightness();
  lcd_brightness = lcd_max_brightness;
  tft.setTextColor(DRAW_BASE_COLOR);

  draw_ui();
}

void loop() {
   key_A = digitalRead(WIO_KEY_A);
   //key_B = digitalRead(WIO_KEY_B);
   key_C = digitalRead(WIO_KEY_C);

   up    = digitalRead(WIO_5S_UP);
   down  = digitalRead(WIO_5S_DOWN);
   left  = digitalRead(WIO_5S_LEFT);
   right = digitalRead(WIO_5S_RIGHT);
   press = digitalRead(WIO_5S_PRESS);

   if(prev_key_A == OFF && key_A == ON){
     playNote('C',BUZZER_DURATION_MS);
     range_in_centimeters = ultrasonic.MeasureInCentimeters();
     update_distance();
   }

   if(prev_key_C == OFF && key_C == ON){
     playNote('c',BUZZER_DURATION_MS);
     range_in_centimeters = 0;
     update_distance();
   }

   /////////////////////////////////////////

   if(prev_up == OFF && up == ON){
     control_lcd_brightness('+');
   }

   if(prev_down == OFF && down == ON){
     control_lcd_brightness('-');
   }

   if(prev_left == OFF && left == ON){
     color_index--;
     if(color_index < 0){
       color_index = NUM_BACKGROUND_COLORS - 1;
     }
     set_background_color(background_colors[color_index]);
   }

   if(prev_right == OFF && right == ON){
     color_index++;
     if(color_index >= NUM_BACKGROUND_COLORS){
       color_index = 0;
     }
     set_background_color(background_colors[color_index]);
   }

   if(prev_press == OFF && press == ON){
     playNote('c',BUZZER_DURATION_MS);
     color_index = 0;
     set_background_color(background_colors[color_index]);
   }

   prev_key_A = key_A;
   //prev_key_B = key_B;
   prev_key_C = key_C;

   prev_up    = up;
   prev_down  = down;
   prev_left  = left;
   prev_right = right;
   prev_press = press;

   delay(10);
}

なお、ディスプレイの明るさ調整は、こちらで公開してくださっている”lcd_backlight.hpp”を、Arduinoの”xxxxxx.ino”ファイルと同じフォルダに置いておく必要があります。また、超音波距離センサを使うためのライブラリ&使い方はこちらをご参照ください。

 

というわけで、手元にある具材でエイヤで作った割には、比較的実用性のあるものにはなった気がします。ただ、できれば有線ではなくバッテリー駆動で持ち歩けるようにしたいところです。日本では(少なくとも2020/8/15時点では)まだほとんど流通していないようですが、Wio Terminal用の公式バッテリーが存在するようなので、そちらと組み合わせられれば、使い勝手はもっと良くなると思います。

 

ちなみに、だいぶ昔にArduino Unoと超音波距離センサ、LCDとブザーを組み合わせてフラフープカウンターなるものを作ったことがあるのですが、Wio Terminalベースでこれを作り直したら、もっと良い感じのものができそうな気がします。まだ使用していない組み込み機能として、

  • 赤外線送信
  • 明るさセンサ
  • 加速度センサ
  • マイク

があるので、これらも駆使すれば、フラフープカウンターからさらに発展して、腕立てや腹筋、屈伸の数を数えたりと、Nintendo Switchの『リングフィット アドベンチャー』のようなトータルフィットネス機器を自作することも可能かもしれません。WiFi機能もありますから、日々のトレーニングの記録をクラウドにアップロードする、という機能も実装できるのではないかと思います。

 

以上、Wio Terminalの簡単なレビューでした。自分の趣味のメイン活動は仮面ライダー玩具の改造なので、それに活かすことができれば一番良いのですが、残念ながら今のところはノーアイデアです。ただ、手段の一つとしては頭にインプットしたので、いつかのタイミングで出番が来るかもしれません。

また、上でちょっと触れたフィットネス機器自作のアイデアは結構面白い気がするのですが、自分の少ない趣味の時間からはライダー玩具の改造時間を捻出するのが精一杯のため、余程改造アイデアが浮かばない or 本気で健康管理しないとやばいぐらいになったら、取り組んでみたいと思います。

なお、Wio Terminalについて現時点(2020/8/15)で一つ気になっているのは、WiFiの利用についての記事はちらほら見かけるのですが、Bluetoothの利用についての記事をほとんど見かけない、というところです。BLEが使えるようになるといろんなセンサとの連携がしやすくなるので、このあたりの情報が早く充実されることを期待したいところです。また、Raspberry Piとの連携や機械学習技術の活用など、このあたりも手軽に扱えるようになってくると、かなりいろんな使い道が出てくるような気がしています。

 

なお、Seeed社の直販サイトはこちらになります。国内で手に入りづらくなっているときは、こちらも当たってみると良いかと思います。