memorandums

日々の生活で問題解決したこと、知ってよかったことなどを自分が思い出すために記録しています。

オダビ2024のプロトタイプ制作

今年もやります。オダビとの教育プロジェクト。

詳細は後日報告するとして、デザインの大学院生と一緒にプロトタイプを作っています。デザインの学生さんってホントいいですねぇ。モノづくりに対する姿勢がいかにもモノづくりって感じがします。ちゃんと作らないと気が済まない。気質なのか教育なのか、教えていただきたいくらいです。

情報ってコードは基本コピペだし、なんというかこだわりがありません。個人差はもちろんあるのでしょうけど、コードの意味を細部の細部まで考えるのって企業で仕事してからかな、と思います。

さて。

今年は子どもたちが作った作品を展示するミニミニ美術館を自分でプロデュースするというテーマになりました。その辺は組み合わせてから語るとして。ちなみに外形は以下です。デザインの先生が作ってくださいました。

IT要素として簡単な構造ではありますが小学校1〜6年生のみなさんが作れる内容ということで、今年もArduinoでセンシングしてLEDピカピカさせたりします。今年はマイクモジュールを使って声に反応してLEDパターンを変えようとしています。そこに意味をもたせるために仕掛けを大学院生くんと考えていたところでした。ちなみに以下がハードです。例年と比べるとかなりシンプルです。昨年はWifiありカメラありでしたからね。。。

とりあえず作ったコードが以下でした。こんなもの見せるようなものではないのですが、自分の記憶として残したくて書いています。Arduinoが起動したときに周囲の音量をマイクでサンプリングして閾値とします。その閾値と現在値との差でLEDパターンを変えようとしました。とにかくやっつけなコードです。これはうまく動いたり動かなかったりしました。

int avg = 0;

int b[30];
int idx = 0;
int counter = 0;

bool blink = true;

void setup() {
  Serial.begin(115200);

  pinMode(A0, INPUT);
  pinMode(3, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);

  //キャリブレーション(無音時の音量測定)
  long s = 0;
  for (int i = 0; i < 100; i++) {
    s += analogRead(A0);
    delay(10);
  }
  avg = s / 100;
}

void loop() {
  b[idx] = max(0, min(200, analogRead(A0) - avg));
  idx = ++idx % 30;

  int b_avg = 0;
  for (int i = 0; i < 30; i++) {
    b_avg += b[i];
  }
  b_avg = max(0, min(200, b_avg / 30));

  Serial.println(b_avg);
  
  static int c = 0;
  static int c2 = 0;

  if (counter > 0) {
    if (b_avg > 10) {
      blink = false;
      c = 0;
      c2 = 0;
    }
    if (blink) {
      switch(c) {
        case 0:
          digitalWrite(3, HIGH);
          digitalWrite(5, LOW);
          digitalWrite(6, LOW);
          break;
        case 1:
          digitalWrite(3, LOW);
          digitalWrite(5, HIGH);
          digitalWrite(6, LOW);
          break;
        case 2:
          digitalWrite(3, LOW);
          digitalWrite(5, LOW);
          digitalWrite(6, HIGH);
          break;
      }
      if (c2++ >= 100) {
        c = ++c % 3;
        c2 = 0;
      }
    } else {
      digitalWrite(3, HIGH);
      digitalWrite(5, HIGH);
      digitalWrite(6, HIGH);
    }
    counter --;
  } else {
    if (b_avg > 1) {
      counter = 1000;
    } else {
      digitalWrite(3, LOW);
      digitalWrite(5, LOW);
      digitalWrite(6, LOW);
      blink = true;
    }
  }

  delay(10);
}

で、もう少し、ちゃんと作ったのが以下です。ポイントは2つあります。

1つは、音量変化の取り方です。ゼロ交差とか色々試してみたんですがどうも思うように動きませんでした。データを観察してちょっと前の音量との差分の積分値が使えそうな感覚がしてきました。getVolDiff関数です。これなかなかいい感じに動作が安定しています。これも理論があるんでしょうね。探すのが面倒なので観察してコードを試しながら作っていきました。こういうのが楽しいですねぇ。

もう1つはLEDの点灯の仕方です。単にdigitalWriteを使うとHIGHかLOWしかありませんのでかっこよくないです。「ふわっ」と変わって欲しいと思いました。消えるときもつくときも。これも色々と実装方法を試行錯誤しました。管理が面倒にならず、digitalWriteしているのと変わらないコードにしたいと思いました。回路にコンデンサーをいれるような気持ちです。それがdigitalWrite2という独自関数です。中身はanalogWriteなんですね。でも、コンデンサのように電気を蓄積するような動作を実現しています。コードは複雑にせずに済みました。個人的にはなかなかよい発想だと思いました。はい。

#define MODE_OFF 0
#define MODE_BLINK 1
#define MODE_ON 2

int mode = MODE_OFF;

void setup() {
  Serial.begin(115200);
  pinMode(A0, INPUT);
  pinMode(3, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
}

int getVolDiff() {
  static const int N = 30;
  static int a[N];
  static int a_idx = 0;

  a_idx = ++a_idx % N;
  a[a_idx] = analogRead(A0);
  
  int s = 0;
  for (int i = 0; i < N; i++) {
    int p_i = (i - 1 < 0) ? N - 1: i - 1;
    s += abs(a[i] - a[p_i]);
  }
  return s;
}

void LED() {
  switch(mode) {
    case MODE_OFF:
      digitalWrite2(3, LOW);
      digitalWrite2(5, LOW);
      digitalWrite2(6, LOW);
      break;
    case MODE_BLINK:
      static int t1 = 0;
      static int t2 = 100;
      if (++t2 > 100) {
        t2 = 0;
        t1 = ++t1 % 3;
      }
      digitalWrite2(3, t1 == 0);
      digitalWrite2(5, t1 == 1);
      digitalWrite2(6, t1 == 2);
      break;
    case MODE_ON:
      digitalWrite2(3, HIGH);
      digitalWrite2(5, HIGH);
      digitalWrite2(6, HIGH);
      break;
  }
}

void digitalWrite2(int pinno, int value) {
  static int b[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  if (value == LOW) {
    b[pinno] = max(0, b[pinno] - 1);
  } else {
    b[pinno] = min(230, b[pinno] + 1);
  }
  analogWrite(pinno, b[pinno]);
//  digitalWrite(pinno, LOW);
}

void updateMode(int vol) {
  static const int SOUND_LEVEL1 = 10;
  static const int SOUND_LEVEL2 = 30;
  static int counter = 0;

  if (vol > SOUND_LEVEL2) {
    if (mode != MODE_ON) {
      counter = 500;
      mode = MODE_ON;
    }
  } else if (vol > SOUND_LEVEL1) {
    if (mode == MODE_OFF) {
      counter = 500;
      mode = MODE_BLINK;
    }
  }
  if (--counter <= 0) {
    counter = 0;
    mode = MODE_OFF;
  }
}

void loop() {
  int vd = getVolDiff();
  updateMode(vd);
  LED();
  
  Serial.println(vd);
  delay(10);
}

ということで。ずいぶんと時間がかかっちゃいましたが楽しい時間でした。やっぱ何か人に使ってもらうことを前提としたソフトを作るのって単純に楽しいなぁ。。。

さて、風呂入ってねよ。