Alaのアイコン画像

Leafony AI03A MIC&VR&LED 動作確認

Ala 2020年07月05日に作成  (2020年07月05日に更新)

はじめに

本稿では、Leafony の Extention キットにある AI03A MIC&VR&LED の動作確認を簡単に行います。

というのも、Leafony のキット内リーフを公式のサンプルスケッチを使って試していましたが、残念なことにこのリーフのサンプルスケッチが削除されていました。(マイクとボリュームを使ってLEDを点灯)

7/5 20:00 追記
コメントいただきましたが、上記サンプルは名前が変わっていたようで以下にあるようです。
https://github.com/Leafony/Sample-Sketches/blob/master/Sound_Level_Meter/Sound_Level_Meter.ino

仕方がないので自分でこのリーフの動作サンプルをやっていくことにします。

SpecSheet より、MEMSマイク、ボリューム、LEDが搭載されています。この3つを使用してみます。

コードについては ino ではなく、基本的に Platform IO で C++ ファイルで記載します。

リーフの組み立てについては マイクとボリュームを使ってLEDを点灯 と同様の構成となります。

リーフ
AI03A MIC & VR & LED
AZ01A USB
AP01A AVR MCU
AV01A CR2032 (電池)

LED

ピン番号さえわかれば LED点滅 などを参考にすれば問題ありません。

SpecSheet をみて確認します。しかし記載に差異(?)がありました。
実際に試してみたところ D6~D11 が正となります。

2-1.ブロック図ではD6~D11がLED。

キャプションを入力できます

2-5.ピンアウトでは、D3~D8がLED駆動と書かれています。

キャプションを入力できます

LEDの動作確認コードは以下のようになります。

#include <Arduino.h>

// 定数定義
#define LED1 6
#define LED2 7
#define LED3 8
#define LED4 9
#define LED5 10
#define LED6 11 

void setup() {
  // ピンの入出力設定
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);
  pinMode(LED4, OUTPUT);
  pinMode(LED5, OUTPUT);
  pinMode(LED6, OUTPUT);

  // LED 初期設定: LOW 出力 (OFF)
  digitalWrite(LED1, LOW);
  digitalWrite(LED2, LOW);
  digitalWrite(LED3, LOW);
  digitalWrite(LED4, LOW);
  digitalWrite(LED5, LOW);
  digitalWrite(LED6, LOW);

  // LED 点灯テスト
  uint8_t LED_GROUP[6] = {LED1, LED2, LED3, LED4, LED5, LED6};
  
  // 1つずつ点滅確認
  for(uint8_t led : LED_GROUP) {
    digitalWrite(led, HIGH);
    delay(500);
    digitalWrite(led, LOW);
    delay(500);
  }

  // 全点灯
  for(uint8_t led : LED_GROUP) {
    digitalWrite(led, HIGH);
  }
  delay(1000);

  // 全消灯
  for(uint8_t led : LED_GROUP) {
    digitalWrite(led, LOW);
  }

}

void loop() {
}

マイク

次にマイクの確認です。

2-5.ピンアウトより、A2 がマイク出力信号(アナログ) となっています。

とりあえずアナログらしいので analogRead で値をとってみました。

ここで問題として、シリアルモニタで確認する場合、ディレイを大きめに取らないと出力が早すぎて変化をみることができませんが、あまりディレイを大きくとりすぎると変化をとり逃しすぎてイマイチよくわかりませんでした。

解決策としては、Arduinoの公式ツールにシリアルプロッタなるものがあることを知ったのでそれを利用しました。シリアルモニタの出力値をグラフとしてプロットしてくれるものになります。(Arduinoのツールにあります。)

これを利用することで簡単にどのように変化しているかみることができます。

キャプションを入力できます

// 変数
uint16_t micData;

void setup() {
  // シリアル通信の開始
  Serial.begin(115200);
  // 略
}
void loop() {
  micData = analogRead(A2);
  Serial.print("mic :");
  Serial.println(micData);
  delay(2);
}

以下のようにプロットされて確認できます。

delay2ms delay500ms
キャプションを入力できます キャプションを入力できます

マイクとLED

いまいちマイクの出力値の使い方がわからないのですが、サンプルコードの説明に書いてあるように音量に応じてLEDを点滅させてみるようにしてみました。

マイクにより周囲の音量に応じた数のLEDを点灯させることが出来ます。

※注意
以降、色々と値をこねくり回したりしていますが、実測から適当にそれっぽく動作するように試していっただけのものになります。このマイクの出力値どう処理すればいいのかわかりません(サンプルコード見たかった・・・)。音の波形とか全くわからないので、ここではあくまでリーフの動作確認とします。

7/5 20:00 追記
サンプルコードを確認しましたが、サンプルコードでは100msのディレイで100ms周期で値を1回取得してそのままその値を使用して処理をおこなっていました。
もっとうまいマイクデータの処理がわかるかなと思っていたんですが、さすがにサンプルでそこまでのことはやっていませんでした。
taltalpさん、ありがとうございました。

platform.ini にはタイマ割り込みを利用するため MSTimer2 を追加します。

lib_deps = 
  # MSTimer2
  https://github.com/PaulStoffregen/MsTimer2

以下がコードとなります。

#include <Arduino.h>
#include <MsTimer2.h>

// 定数定義
#define LED1 6
#define LED2 7
#define LED3 8
#define LED4 9
#define LED5 10
#define LED6 11 

#define MIC_DATASET_MAX 100           // マイクデータ保持最大値

// 変数
uint16_t micData;                     // マイクの入力値保持
uint16_t micDataset[MIC_DATASET_MAX]; // マイクデータの配列(キュー)
uint16_t index;                       // マイクデータの配列のインデックス
uint16_t micLevel;                 // 0~6に変換したマイクデータ
uint32_t micAverage;               // マイクの基準値(起動時に平均値をとる)

// プロトタイプ宣言
void setup();
void loop();
void Timer1sInterrupt();
void setupDigitalPin();
void testLED();

// 初期化処理
void setup() {
  setupDigitalPin(); // デジタルピン設定

  testLED(); // LED 点灯テスト

  // シリアル通信の開始
  Serial.begin(115200);

  // マイクの基準算出
  uint32_t sum = 0;
  for(int cnt = 0; cnt < 500; cnt++) {
    sum += analogRead(A2);
  }
  micAverage = sum / 500; 
  Serial.println(micAverage);

  // タイマー割り込み設定: 1s毎にボリュームでLED点灯
  MsTimer2::set(1000, Timer1sInterrupt);
  MsTimer2::start();

}

// メインループ
void loop() {
  micData = analogRead(A2);

  // MIC_DATASET_MAX 分のキューを作成
  micDataset[index %= MIC_DATASET_MAX] = micData;
  index++;

  delay(2);
}

// 1s毎にボリュームでLED点灯割り込み
void Timer1sInterrupt() {
  uint32_t sum = 0;
  uint32_t max = 0;
  // キューのデータから平均, 最大値をとる
  for(uint16_t value : micDataset) {
    // 基準からの乖離を算出
    if(micAverage > value) {
      value = micAverage - value;
    } else {
      value = value - micAverage;
    }

    // 最大値
    if(max < value) {
      max = value;
    }
  }
  
  // 直近の最大値を判定値とする。
  uint32_t currentValue = max;
  
  // 0 ~ 6 の値に
  currentValue = constrain(currentValue, 0, 200);
  micLevel = map(currentValue, 0, 200, 0, 6);

  // ボリュームレベルにあわせてLEDを点灯
  switch (micLevel)
  {
  case 0:
    digitalWrite(LED1, LOW);
    digitalWrite(LED2, LOW);
    digitalWrite(LED3, LOW);
    digitalWrite(LED4, LOW);
    digitalWrite(LED5, LOW);
    digitalWrite(LED6, LOW);
    break;
  case 1:
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, LOW);
    digitalWrite(LED3, LOW);
    digitalWrite(LED4, LOW);
    digitalWrite(LED5, LOW);
    digitalWrite(LED6, LOW);
    break;
  case 2:
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, HIGH);
    digitalWrite(LED3, LOW);
    digitalWrite(LED4, LOW);
    digitalWrite(LED5, LOW);
    digitalWrite(LED6, LOW);
    break;
  case 3:
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, HIGH);
    digitalWrite(LED3, HIGH);
    digitalWrite(LED4, LOW);
    digitalWrite(LED5, LOW);
    digitalWrite(LED6, LOW);
    break;
  case 4:
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, HIGH);
    digitalWrite(LED3, HIGH);
    digitalWrite(LED4, HIGH);
    digitalWrite(LED5, LOW);
    digitalWrite(LED6, LOW);
    break;
  case 5:
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, HIGH);
    digitalWrite(LED3, HIGH);
    digitalWrite(LED4, HIGH);
    digitalWrite(LED5, HIGH);
    digitalWrite(LED6, LOW);
    break;
  case 6:
    digitalWrite(LED1, HIGH);
    digitalWrite(LED2, HIGH);
    digitalWrite(LED3, HIGH);
    digitalWrite(LED4, HIGH);
    digitalWrite(LED5, HIGH);
    digitalWrite(LED6, HIGH);
    break;
  default:
    break;
  }
}

// デジタルピン設定
void setupDigitalPin() {
  // ピンの入出力設定
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);
  pinMode(LED4, OUTPUT);
  pinMode(LED5, OUTPUT);
  pinMode(LED6, OUTPUT);

  // LED 初期設定: LOW 出力 (OFF)
  digitalWrite(LED1, LOW);
  digitalWrite(LED2, LOW);
  digitalWrite(LED3, LOW);
  digitalWrite(LED4, LOW);
  digitalWrite(LED5, LOW);
  digitalWrite(LED6, LOW);
}

// LED 点灯テスト
void testLED() {
  uint8_t LED_GROUP[6] = {LED1, LED2, LED3, LED4, LED5, LED6};

  // 1つずつ点滅確認
  for(uint8_t led : LED_GROUP) {
    digitalWrite(led, HIGH);
    delay(500);
    digitalWrite(led, LOW);
    delay(500);
  }

  // 全点灯
  for(uint8_t led : LED_GROUP) {
    digitalWrite(led, HIGH);
  }
  delay(1000);

  // 全消灯
  for(uint8_t led : LED_GROUP) {
    digitalWrite(led, LOW);
  }
}

初期化処理の setup 内でマイクの基準を算出してますが、とりあえず 500回マイクの値を読みだして平均を保持するようにしています。初期化時は静かに音を出さないようにすることを想定しています。

// マイクの基準算出
  uint32_t sum = 0;
  for(int cnt = 0; cnt < 500; cnt++) {
    sum += analogRead(A2);
  }
  micAverage = sum / 500; 
  Serial.println(micAverage);

その後、1s毎のタイマー割り込みを設定しています。

// タイマー割り込み設定: 1s毎にボリュームでLED点灯
  MsTimer2::set(1000, Timer1sInterrupt);
  MsTimer2::start();

メインループ内では毎周期マイクデータを読みだして100件分のデータを配列に保持しています。

micData = analogRead(A2);

// MIC_DATASET_MAX 分のキューを作成
micDataset[index %= MIC_DATASET_MAX] = micData;
index++;

タイマ割り込み関数内では保持している直近100件分のデータから最大値を取り出しています。
このときそのままのデータを使用せず 初期化時に保持していた基準値 からの差を使用しています。理由としては少し前のプロットの図をみていただくと分かると思いますが、音量が上がったとき読みだした値が大きくなるわけではなく振れ幅が大きくなります。
よって初期化時にとっていた平均データから「どれだけ離れているか」という値を利用しています。

直近数件のデータから最大値を取り出している理由については割り込み時点で1回だけの読み出しにすると音を出してもうまく音を出したタイミングのデータを取り切れなかった為です。

ちなみに最初は直近データからの平均を使用しようとしてみたのですが、平均値をとってしまうと大きな音を出し続けなければ変化が丸められてしまった為、最大値に変更しています。

// キューのデータから平均, 最大値をとる
  for(uint16_t value : micDataset) {
    // 基準からの乖離を算出
    if(micAverage > value) {
      value = micAverage - value;
    } else {
      value = value - micAverage;
    }

    // 最大値
    if(max < value) {
      max = value;
    }
  }
  
  // 直近の最大値を判定値とする。
  uint32_t currentValue = max;

ボリューム

次はボリューム確認です。

2-5.ピンアウトより、A3 がボリューム出力信号(アナログ) となっています。

SpecSheetをみてもボリュームがどのような値がとれるのかなどがわからなかったので、実際に値をシリアルモニターに出力して試してみます。
結果は、0~929 の値が取れました。

uint16_t volumeData;                  // ボリュームの入力

void loop() {
  volumeData = analogRead(A3);
  Serial.println(volumeData);
  delay(2);
}

おわりに

以上で、AI03A MIC&VR&LEDの動作確認としてはおわります。

特にマイクの部分はLEDが音量にあわせて点灯がかわるようにこねくりまわしただけで参考にならないかもしれません。とりあえずマイクを使ってみようくらいの気持ちだったので、読みだしたマイクデータの処理方法がわかりませんでした・・・。

この他のサンプルも色々試してみてそのあたりでひとつ書こうかななどとも思っていたのですが、サンプルコード試していくうちにまさかのサンプルが消えてしまっているリーフがあった為、せっかくなので自分が試した内容をまとめてみました。

1
4
Alaのアイコン画像
電子工作初心者です
Ala さんが 2020/07/05 に 編集 をしました。 (メッセージ: 初版)
Opening
taltalpのアイコン画像 taltalp 2020/07/05

Alaさん
シリアルプロッタというツールがあるんですね!とても参考になりました。

各リーフのサンプルデザインはこちらのサイトのほうがまとまっています。ぜひご覧になって下さい。
https://docs.leafony.com/docs/leaf/

また、リンク切れのサンプルはファイル名が変更されてgithubのリポジトリに置いてありますので、こちらもご確認下さい。
https://github.com/Leafony/Sample-Sketches/blob/master/Sound_Level_Meter/Sound_Level_Meter.ino

Alaのアイコン画像 Ala 2020/07/05

taltalpさん、ありがとうございます。
サンプル、探しきれなかったんですが名前が変わっていたんですね。

サンプルの中身は、ただ100msディレイで1発値をとって使用しているだけなんですね。
一定周期の瞬間の値だけとってもLEDちらつくし、ちょっと音出してもなかなか取り切れずイメージしていたものと違ったので色々悩んだんだので一体どう処理してるんだろうと思ってたんですがサンプルもその実装だったとは・・・。

1 件の返信が折りたたまれています
Ala さんが 2020/07/05 に 編集 をしました。 (メッセージ: サンプルコード実装確認・追記)
ログインしてコメントを投稿する