工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Manufacturing Technology >> 製造プロセス

FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築

必要な部品と材料

FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 Arduino Nano R3 × 1
WS2812B 8x32 RGB LEDマトリクス × 1
FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 抵抗 4.75kΩ × 3
FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 抵抗 100kΩ × 2
FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 抵抗 10kΩ × 2
FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 スルーホール抵抗 390Ω × 1
FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 1N4007 ダイオード (1N4004でも可) × 1
FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 コンデンサ 100nF × 2
FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 コンデンサ 1000µF × 1
FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 プッシュボタンスイッチ 12mm (ボックス組み込み時はパネルボタン2個でも可) × 2
4x6cm 汎用プロトタイプ基板 × 1
PCBステレオRCAメスプラグ × 1

必要な工具と機器

FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 はんだごて
3.5mmオーディオジャック - ステレオRCA変換ケーブル

プロジェクトについて

Arduino FHTライブラリ

FHT(Fast Hartley Transform)は、より有名なFFT(Fast Fourier Transform)と同じ役割を果たしますが、FFTが複素数データで動作するのに対し、FHTは実数データのみを使用するため、処理能力とメモリ使用量が半分で済みます。

このライブラリは、FFTライブラリが実部・虚部用に2つの配列を使うのに対し、データサンプリング用に単一配列を使用します。また、複素数浮動小数点対数計算のループを避け、数値テーブルを使用することで複雑な数学計算を簡素化しています。

デメリットは、サンプリング配列サイズの半分の値しか返さないため、オーディオ帯域の両端で一定の分解能と精度の損失が生じます。しかし、計測器ではなくシンプルなガジェットを開発する場合、極限の精度は不要です。音楽に合わせてLEDを点灯させるだけなら十分です。

もちろんここでFHTライブラリの動作原理を詳細に語る場所ではありません。詳しく知りたい方はOpen Music Labのウェブサイトを参照してください。関数の詳細説明やArduino用の汎用例が揃っています。

FFTライブラリは非常に高速で、チラツキを抑えるためにコードに遅延を入れる必要がありました。最終結果はすぐにわかります - FFT版では音楽の後を追うように遅れがちでしたが、FHT版はずっと高速で反応が良いです。システムリソースの使用量が少ないため、追加機能のコードも追加できました。

新バージョンの特徴

前バージョンと基本は同じなので、既に作成済みの方はボタン1つと10kΩ抵抗を追加し、コードを更新するだけで対応できます。

FHTライブラリ採用に加え、以下の機能を追加しました:

コード

FHTコードバージョン Arduino
/* Copyright (c) 2020 Janux
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Based on an original project for the MAX72xx LED matrix and FFT lib made from Shajeeb.
Configuration settings section based on work of Ragnar Ranøyen Homb from Norvegian Creation. */

#define LIN_OUT 1          // FHTリニア出力振幅
#define FHT_N 128          // FHTサンプル数、2の累乗であること
#include <FHT.h>

#define xres 32            // 表示列数、FHT_N/2以下
#define yres 8             // 表示行数
#define ledPIN 6           // LED制御出力ピン
#define NUM_LEDS (xres * yres) // マトリクス総LED数

#include <Adafruit_NeoPixel.h>

#define colorPIN 5         // 色変更ボタンピン
#define brightnessPIN 10   // 輝度変更ボタンピン

byte displaycolor = 0;     // デフォルト色
byte brightness = 1;       // デフォルト輝度

#include <EEPROM.h>
#define CONFIG_START 32    // メモリ開始位置
#define CONFIG_VERSION "VER01" // 設定バージョン

typedef struct {
  char version[6];
  byte displaycolor;
  byte brightness;
} configuration_type;

configuration_type CONFIGURATION = { CONFIG_VERSION, displaycolor, brightness };

byte yvalue;
int peaks[xres];
byte state = HIGH;
byte previousState = LOW;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 100;
byte data_avgs[xres];

// NeoPixel設定
// Parameter 1 = LED数
// Parameter 2 = ピン番号
// Parameter 3 = ピクセルタイプフラグ
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(NUM_LEDS, ledPIN, NEO_GRB + NEO_KHZ800);

// イコライザーフィルター
byte eq[32] = {
  60, 65, 70, 75, 80, 85, 90, 95, 100, 100, 100, 100, 100, 100, 100, 100,
  100, 100, 100, 100, 100, 100, 100, 100, 115, 125, 140, 160, 185, 200, 200, 200
};
bool EQ_ON = true; // falseでEQ無効

// 5種類のカラーパレット (0はカスタムカラー用)
byte colors[][8] = {
  {170, 160, 150, 140, 130, 120, 1, 1},
  {1, 5, 10, 15, 20, 25, 90, 90},
  {90, 85, 80, 75, 70, 65, 1, 1},
  {90, 90, 90, 30, 30, 30, 1, 1},
  {170, 160, 150, 140, 130, 120, 110, 0}
};

// 設定表示用カスタム文字ビットマップ
byte charBitmap[] = {
  0x1C, 0x10, 0x10, 0x10, 0x10, 0x1C,  // 0
  0x08, 0x18, 0x08, 0x08, 0x08, 0x1C,  // 1
  0x0C, 0x12, 0x04, 0x08, 0x10, 0x1E,  // 2
  0x0C, 0x12, 0x02, 0x06, 0x12, 0x0C,  // 3
  0x10, 0x10, 0x10, 0x14, 0x1E, 0x04,  // 4
  0x1E, 0x10, 0x1E, 0x02, 0x12, 0x0C,  // 5
  0x1E, 0x10, 0x10, 0x1E, 0x12, 0x1E,  // 6
  0x1E, 0x02, 0x04, 0x08, 0x08, 0x0C,  // 7
  0x12, 0x0C, 0x12, 0x12, 0x12, 0x0C,  // 8
  0x1C, 0x12, 0x1C, 0x12, 0x12, 0x1C   // 9
};

void setup() {
  pixel.begin();
  
  // ADCフリーランニングモード、プリスケーラ32
  ADCSRA = 0b11100101; // 0xe5
  ADMUX = 0b00000000;  // A0ピン、外部電圧リファレンス
  
  // EEPROMから設定読み込み
  if (loadConfig()) {
    displaycolor = CONFIGURATION.displaycolor;
    brightness = CONFIGURATION.brightness;
  }
  
  pixel.setBrightness(brightness * 24 + 8);
  showSettings(3, true); // 起動時設定表示 (falseで無効化)
}

void loop() {
  while (1) {
    Sampling();
    RearrangeFHT();
    SendToDisplay();
    colorChange();
    brightnessChange();
    delay(10); // チラツキ低減 (FHTが高速すぎるため)
  }
}

void Sampling() {
  for (int i = 0; i < FHT_N; i++) {
    while (!(ADCSRA & 0x10)); // ADC変換完了待ち (ADIFビット)
    ADCSRA = 0b11110101;      // ADIFクリア (0xf5)
    // ADLAR=0なのでADCデータは右寄せ10ビット
    byte m = ADCL;
    byte j = ADCH;
    int value = (j << 8) | m;
    value -= 0x0200;          // 符号付きintに変換
    value <<= 6;              // 16ビット符号付きintに拡張
    fht_input[i] = value / 8; // 圧縮してFHT入力配列へ
  }
  
  // FHTデータ処理開始
  fht_window();  // ウィンドウ関数で周波数応答改善
  fht_reorder(); // FHT前のデータ並び替え
  fht_run();     // FHT実行
  fht_mag_lin(); // リニア出力振幅取得
}

void RearrangeFHT() {
  // FHTは単一配列で実数値のみ返す
  // fht_mag_lin()後、値はfht_lin_out[]の最初のFHT_N/2要素に格納
  int step = (FHT_N / 2) / xres;
  int c = 0;
  for (int i = 0; i < (FHT_N / 2); i += step) {
    data_avgs[c] = 0;
    for (int k = 0; k < step; k++) {
      data_avgs[c] += fht_lin_out[i + k];
    }
    data_avgs[c] /= step; // 平均値保存
    c++;
  }
}

void SendToDisplay() {
  for (int i = 0; i < xres; i++) {
    if (EQ_ON) data_avgs[i] = data_avgs[i] * (float)(eq[i]) / 100; // EQ適用
    data_avgs[i] = constrain(data_avgs[i], 0, 80);                 // 0-80にクリップ
    data_avgs[i] = map(data_avgs[i], 0, 80, 0, yres);              // 0-yresにマッピング
    yvalue = data_avgs[i];
    peaks[i]--;                   // ピーク減衰
    if (yvalue > peaks[i]) peaks[i] = yvalue; // 新ピーク記録
    yvalue = peaks[i];            // ピーク値を表示用に採用
    setColumn(i, yvalue);
  }
  pixel.show();
}

// X列のY高さまでLED点灯
void setColumn(byte x, byte y) {
  int led, i;
  for (i = 0; i < yres; i++) {
    led = GetLedFromMatrix(x, i);
    if (peaks[x] > i) {
      switch (displaycolor) {
        case 4: // カスタムカラー (配列値0=白)
          if (colors[displaycolor][i] == 0) {
            pixel.setPixelColor(led, 255, 255, 255);
          } else {
            pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
          }
          break;
        case 5: // 列ごとに色変更
          pixel.setPixelColor(led, Wheel(x * 16));
          break;
        case 6: // 行ごとに色変更
          pixel.setPixelColor(led, Wheel(i * y * 3));
          break;
        case 7: // 国旗パターン等
          // イタリア国旗
          // if (x < 11) pixel.setPixelColor(led, 0, 255, 0);
          // if (x > 10 && x < 21) pixel.setPixelColor(led, 255, 255, 255);
          // if (x > 20) pixel.setPixelColor(led, 255, 0, 0);
          // 星条旗
          if (i < yres - 2) {
            if (x & 0x01) pixel.setPixelColor(led, 0, 0, 255);
            else pixel.setPixelColor(led, 255, 0, 0);
          } else {
            pixel.setPixelColor(led, 255, 255, 255);
          }
          break;
        default: // 定義済みカラーパレット
          pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
      }
    } else {
      pixel.setPixelColor(led, pixel.Color(0, 0, 0)); // 消灯
    }
  }
}

// マトリクス座標(x,y)からLED番号を計算
// WS2812B蛇行配置、水平配置、DINコネクタ右側=LED0前提
// x: 0〜xres-1, y: 0〜yres-1
// 戻り値: 0〜NUM_LEDS-1
int GetLedFromMatrix(byte x, byte y) {
  int led;
  x = xres - x - 1; // 右から左へ反転
  if (x & 0x01) {   // 奇数列: 逆順
    led = ((x + 1) * yres - y - 1);
  } else {           // 偶数列: 正順
    led = ((x + 1) * yres - yres + y);
  }
  return constrain(led, 0, NUM_LEDS);
}

void colorChange() {
  int reading = digitalRead(colorPIN);
  if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
    displaycolor++;
    if (displaycolor > 7) displaycolor = 0;
    showSettings(1, true);
    saveConfig();
    lastDebounceTime = millis();
  }
  previousState = reading;
}

void brightnessChange() {
  int reading = digitalRead(brightnessPIN);
  if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
    brightness++;
    if (brightness > 7) brightness = 0;
    pixel.setBrightness(brightness * 24 + 8);
    showSettings(2, true);
    saveConfig();
    lastDebounceTime = millis();
  }
  previousState = reading;
}

// Adafruit NeoPixelデモスケッチからのユーティリティ
// 0-255入力でカラー値取得 (R→G→B→Rの遷移)
unsigned long Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return pixel.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return pixel.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return pixel.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

// EEPROMから設定読み込み
int loadConfig() {
  if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
      EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
      EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2] &&
      EEPROM.read(CONFIG_START + 3) == CONFIG_VERSION[3] &&
      EEPROM.read(CONFIG_START + 4) == CONFIG_VERSION[4]) {
    for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++) {
      *((char*)&CONFIGURATION + i) = EEPROM.read(CONFIG_START + i);
    }
    return 1;
  }
  return 0;
}

// 設定をEEPROMに保存
void saveConfig() {
  CONFIGURATION.displaycolor = displaycolor;
  CONFIGURATION.brightness = brightness;
  for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++)
    EEPROM.write(CONFIG_START + i, *((char*)&CONFIGURATION + i));
}

// 設定表示: 1=色, 2=輝度, 3=両方
void showSettings(byte num, bool show) {
  if (show) {
    pixel.clear();
    if (num == 1 || num == 3) {
      drawChar(0, 0);
      drawChar(displaycolor + 1, 5);
    }
    if (num == 2 || num == 3) {
      drawChar(9, xres - 9);
      drawChar(brightness + 1, xres - 4);
    }
    delay(1000);
    pixel.clear();
  }
}

// カスタム文字描画 (5x6ピクセル)
void drawChar(byte val, byte pos) {
  for (int x = 4; x >= 0; x--) {
    for (int y = 5; y >= 0; y--) {
      if ((charBitmap[val * 6 + 5 - y] >> x) & 0x01) {
        pixel.setPixelColor(GetLedFromMatrix(4 - x + pos, y + 1), Wheel((pos > 10) * 170));
        pixel.show();
      }
    }
  }
}

// by Janux®, Last version on 28/06/2020.

回路図

FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築 FHTオーディオスペクトラムビジュアライザー - 驚くほど美しいLEDオーディオディスプレイを構築

製造プロセス

  1. 32バンドArduinoオーディオスペクトラムアナライザー:リニア&疑似ログ表示対応
  2. スマート渇きアラート植物アラーム – 最適な水やりタイミングを知らせるDIYセンサー
  3. イタリア語ワードクロック自作:Arduino Nano、NeoPixel、LCD、RTCで時刻を文字で表示
  4. Arduino MKR Fox 1200で作る信頼性の高いSigfox kWhメーター
  5. Bluetooth対応温度モニター:Arduino NanoとHC-06でスマホ監視
  6. 非接触ジェスチャー錠: APDS-9960とArduinoでセキュアアクセス
  7. Arduino Nanoコンパニオンキット – 電子工作に必須の部品とツールを搭載した多機能IC型デバイス
  8. Arduino Micro/Leonardo用 USB-MIDIアダプタキット – 完全パーツリストと実装ガイド
  9. Arduino用絶縁アナログ入力回路の設計:LM331 V-F変換+フォトカプラでノイズ耐性を確保
  10. RGB 32バンド オーディオスペクトラムビジュアライザー - Arduino LEDマトリックスプロジェクト
  11. Arduino UNOで作る精密反応速度テスター