Réaliser un analyseur de spectre stéréo avec un Raspberry Pi Pico et quelques composants. C’est sur un blog japonais que j’ai trouvé cette réalisation que je mets à votre disposition.
Au sommaire :
Réaliser un analyseur de spectre stéréo avec un Raspberry Pi Pico
Cet article est la traduction d’un article en japonais accessible ici.
Description
L’auteur Radio Pliers anime un blog sur la construction électronique, il parle parfois de son quartier (Inagi City, Tokyo) et d’autres sujets. Il a plus de 60ans et sentant sa vue et sa dextérité se détériorer, mais continue d’expérimenter chaque jour ! (note de framboise314 : un Maker !)
L’utilisation du contenu de cet article se fait aux risques et périls du lecteur. C’est un analyseur de spectre audio qui affiche sur un écran OLED 0,96″ (interface SSD1306) à l’aide d’un Raspberry Pi Pico.
Au départ le projet de FFT devait utiliser un Arduino UNO et un écran LCD. Il a été abandonné en raison de la limitation sévère de la mémoire. L’auteur a donc poursuivi son idée sur un Raspberry Pi Pico.
Schéma
L’entrée est couplée en courant alternatif et polarisée par une résistance de façon à ce que le potentiel soit de 1/2 Vcc à l’entrée de l’ADC. Normalement, une valeur supérieure devrait être utilisée pour R1-R4, mais la résistance d’entrée de l’ADC du RP2040 est spécifiée à 80 kΩ min. et une valeur de résistance inférieure a donc été utilisée. L’écran OLED de 0,96 pouce est connectée via l’interface I2C.
Le montage
L’ensemble du montage ne pose pas de problème et tient sur une petite carte de prototypage (breadboard).
Programme
La bibliothèque pour l’affichage de l’OLED 0.96″ est u8g2.h. Pour la bibliothèque FFT l’auteur a utilisé ArduinoFFT.h.
Le programme est un peu long, mais il est publié sous une forme où l’ensemble du programme est visible.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
>/* Affichage du spectre des signaux audio sur OLED 128x64 20220603_PiPicoFftAnalyzer.ino La carte utilise le RP pico. La librairie FFT est ArduinoFFT.h 2022/06/03 Radiopliers http://radiopench.blog96.fc2.com/ */ #include #include #include #include "arduinoFFT.h" #define R_IN 26 // R-入力ピン #define L_IN 27 // L-入力ピン #define PX1 62 // 左画面(L)原点 #define PX2 65 // 右画面(R)原点 #define PY1 16 // 波形画面の下端 #define PY2 55 // スペクトル画面の下端(-50db) arduinoFFT FFT = arduinoFFT(); // Create FFT object U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); const uint16_t samples = 128; // サンプル数 double vReal_R[samples]; // FFTの計算領域(実際にはたぶん32ビット浮動小数点?) double vImag_R[samples]; double vReal_L[samples]; double vImag_L[samples]; int16_t wave_R[samples]; // 波形の生データー int16_t wave_L[samples]; void setup() { pinMode(16, OUTPUT); // 実行時間測定用 pinMode(25, OUTPUT); // pico内蔵LED // Wire.setClock(400000); // これは効果無し Serial.begin(115200); analogReadResolution(12); // ADCのフルスケールを12ビットに設定 u8g2.begin(); u8g2.setFont(u8g2_font_6x10_tf); u8g2.setDrawColor(1); u8g2.setFontPosTop(); // 左上を文字位置とする u8g2.clearBuffer(); u8g2.drawStr( 0, 0, "Start FFT v0.4"); u8g2.sendBuffer(); delay(1000); } void loop() { // 波形の読み取り(5ms) // digitalWrite(16, HIGH); digitalWrite(25, HIGH); // サンプリング中は内蔵LED点灯 for (int i = 0; i < samples; i++) { wave_R[i] = analogRead(R_IN); // 波形データー取得(fs:4096) wave_L[i] = analogRead(L_IN); delayMicroseconds(21); // サンプリング周期調整(1サイクル39us) } // (5ms) digitalWrite(25, LOW); // digitalWrite(16, LOW); // FFT計算データ準備(1.7ms) for (int i = 0; i < samples; i++) { vReal_R[i] = (wave_R[i] - 2048) * 3.3 / 4096.0; // 電圧に換算 vReal_L[i] = (wave_L[i] - 2048) * 3.3 / 4096.0; vImag_R[i] = 0; vImag_L[i] = 0; } // FFTの計算 (33ms) FFT.Windowing(vReal_R, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD); // 窓関数(ハミング)適用 FFT.Windowing(vReal_L, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD); FFT.Compute(vReal_R, vImag_R, samples, FFT_FORWARD); // FFT FFT.Compute(vReal_L, vImag_L, samples, FFT_FORWARD); FFT.ComplexToMagnitude(vReal_R, vImag_R, samples); // 絶対値の算出 FFT.ComplexToMagnitude(vReal_L, vImag_L, samples); u8g2.clearBuffer(); // 画面バッファクリア(22us) showWaveform(); // 波形表示(6ms) showSpectrum(); // スペクトラム表示(9.4ms) showOthers(); // 目盛線他の表示(1.6ms) u8g2.sendBuffer(); // (30ms) delay(1); // 書き込み不良対策のおまじない } //(ループ実行時間:82ms) void showWaveform() { // 入力波形表示 for (int i = 0; i < 52; i++) { u8g2.drawLine(PX2 + i, PY1 - (wave_R[i * 2]) / 256, PX2 + i + 1, 16 - (wave_R[i * 2 + 1] / 256)); // R-ch波形プロット u8g2.drawLine(PX1 - i, PY1 - (wave_L[i * 2]) / 256, PX1 - i - 1, 16 - (wave_L[i * 2 + 1] / 256)); // L-ch波形プロット } } void showSpectrum() { // スペクトラム表示 int d; static int peak_R[64]; // 過去ピーク値 static int peak_L[64]; for (int xi = 1; xi < 60; xi++) { // スペクトラム表示(0は飛ばす) d = barLength(vReal_R[xi]); u8g2.drawVLine(xi + PX2, PY2 - d, d); // 右側(R-ch)スペクトラム u8g2.drawVLine(xi + PX2, PY2 - peak_R[xi], 1); // 右側ピーク if (peak_R[xi] < d) { // 最新値がピーク値以上だったら peak_R[xi] = d; // ピーク値の更新 } if (peak_R[xi] > 0) { peak_R[xi] --; // ピーク値をディケイ } d = barLength(vReal_L[xi]); // L側スペクトラム表示 u8g2.drawVLine(PX1 - xi, PY2 - d, d); // 左側(L-ch)スペクトラム u8g2.drawVLine(PX1 - xi, PY2 - peak_L[xi], 1); // 右側ピーク if (peak_L[xi] < d) { // 最新値がピーク値以上だったら peak_L[xi] = d; // ピーク値更新 } if (peak_L[xi] > 0) { peak_L[xi] --; // ピーク値をディケイ } } } int barLength(double d) { // スペクトルグラフの長さを計算 float fy; int y; fy = 14.0 * (log10(d) + 1.5); // 10倍(20dB)で14画素 y = fy; y = constrain(y, 0, 56); return y; } void showOthers() { // グラフの修飾(目盛他の作画) // 領域区分線 u8g2.drawVLine(PX1, 0, 64); // L画面原点線 u8g2.drawVLine(PX2, 0, 64); // R画面原点線 u8g2.drawHLine(0, PY2, 128); // スペクトル下端線 // 周波数目盛(横軸) for (int xp = PX1; xp > 0; xp -= 5) { // L側 1kHz間隔目盛 u8g2.drawVLine(xp, PY2 + 1, 1); } u8g2.drawVLine(PX1 - 25, PY2 + 1, 2); // L 5k目盛 u8g2.drawVLine(PX1 - 50, PY2 + 1, 2); // L 10k目盛 for (int xp = PX2; xp < 127; xp += 5) { // R側 1kHz間隔目盛 u8g2.drawVLine(xp, PY2 + 1, 1); } u8g2.drawVLine(PX2 + 25, PY2 + 1, 2); // R 5k目盛 u8g2.drawVLine(PX2 + 50, PY2 + 1, 2); // R 10k目盛 u8g2.setFont(u8g2_font_micro_tr); // 小さなフォント(3x5)で、 u8g2.setCursor( 7, 58); u8g2.print("10k"); // L側周波数表示 u8g2.setCursor( 34, 58); u8g2.print("5k"); u8g2.setCursor( 58, 58); u8g2.print("0"); u8g2.setCursor( 67, 58); u8g2.print("0"); // R側周波数表示 u8g2.setCursor( 87, 58); u8g2.print("5k"); u8g2.setCursor(110, 58); u8g2.print("10k"); // スペクトルレベル目盛(縦軸) for (int y = PY2 - 7; y > 16; y -= 14) { // dB目盛線(横の点線) for (int x = 13; x < 128; x += 5) { u8g2.drawHLine(x, y, 2); } } u8g2.setCursor(0, 17); u8g2.print("0dB"); // スペクトル感度 u8g2.setCursor(0, 30); u8g2.print("-20"); // u8g2.setCursor(0, 45); u8g2.print("-40"); // // LR表示 // u8g2.setFont(u8g2_font_6x10_tf); // 少し大きなフォントで // u8g2.setCursor(0, 4); u8g2.print("L"); // チャンネル表示 // u8g2.setCursor(121, 4); u8g2.print("R"); // u8g2.setFont(u8g2_font_crox1cb_tf); // 少し豪華なフォントで u8g2.setCursor(0, 3); u8g2.print("L"); // チャンネル表示 u8g2.setCursor(119, 3); u8g2.print("R"); // } |
Je vous ai laissé les commentaires d’origine. Si certaines lignes vous posent question, n’hésitez pas à solliciter Google Trad ou DeepL.
Les conditions d’entrée de la FFT sont 128 échantillons et une entrée stéréo avec une période d’échantillonnage de 39 µs, ce qui permet une limite de fréquence supérieure de 12,8 kHz et une largeur de bande de 200 Hz/64 canaux.
Un cycle, de l’échantillonnage à l’affichage, dure 82 ms. (Les temps de traitement individuels sont indiqués dans les commentaires).
Ecran
Les formes d’onde et le spectre des canaux stéréo G/D sont affichés à gauche et à droite de l’écran.
Cet écran montre une onde sinusoïdale de 5 kHz provenant du générateur de fonctions. Vous pouvez voir que le spectre est affiché à l’endroit exact.
Ce qui est un peu inquiétant, c’est qu’il y a une sorte de bruit parasite autour du pic principal de 5 kHz. Cela est peut-être dû à un mauvais INL (errata E11) dans l’ADC du RP2040.
De plus, lorsqu’un signal à fréquence unique est entré de cette manière, le signal au-dessus de 12,8 kHz est replié en raison d’un phénomène de repliement. Selon l’application, il peut être préférable d’ajouter un simple filtre passe-bas.
Video
Conclusion
C’est un montage relativement simple, mais il est intéressant de le réaliser. Si vous essayez de le programmer à partir de la bibliothèque comme ici, vous approfondirez votre compréhension de la FFT.
Cette fois, l’auteur a réalisé un analyseur stéréo, mais il est également possible de faire un analyseur sur un seul canal.
L’auteur n’a pas beaucoup d’expérience avec le Raspberry Pi Pico, et ne sait pas où trouver une référence de langage pour l’utiliser avec Arduino, donc il a écrit le code en copiant le code de ses prédécesseurs. Il se peut donc que le programme contienne du code étrange.
Sources
La page qui a inspiré cet article
https://www.tomshardware.com/news/raspberry-pi-pico-audio-spectrum-visualizer
Page d’origine
http://radiopench.blog96.fc2.com/blog-entry-1184.html
Ping : Un analyseur de spectre avec un Raspberry Pi PICO