干貨|有趣好玩的音樂可視化系列小項目:OLED頻譜燈
OLED(Organic Light-Emitting Diode)
即有機發光二極管,在手機OLED上屬于新型產品,被稱譽為“夢幻顯示器”。OLED顯示技術與傳統的LCD顯示方式不同,無需背光燈,采用非常薄的有機材料涂層和玻璃基板(或柔性有機基板),當有電流通過時,這些有機材料就會發光。而且OLED顯示屏幕可以做得更輕更薄,可視角度更大,并且能夠顯著的節省耗電量。
OLED技術特點
(1) OLED 器件的核心層厚度很薄,厚度可以小于 1mm,為液晶的 1/3。
(2) OLED 器件為全固態機構,無真空,液體物質,抗震性好,可以適應巨大的加速度,振動等惡劣環境。
(3) 主動發光的特性使 OLED 幾乎沒有視角限制,視角一般可達到 170 度,具有較寬的視角,從側面也不會失真。
(4) OLED 顯示屏的響應時間超過 TFT—LCD 液晶屏。TFT—LCD 的響應時間大約使幾十毫秒,現在做得最好的 TFT—LCD 響應時間也只有 12 毫秒。而 OLED 顯示屏的響應時間大約是幾微秒到幾十微秒。
(5) OLED 低溫特性好,在零下 40 攝氏度都能正常顯示,目前航天服上也使用OLED 作為顯示屏。而 TFT—LCD 的響應速度隨溫度發生變化,低溫下,其響應速度變慢,因此,液晶在低溫下顯示效果不好。
(6) OLED 采用有機發光原理,所需材料很少,制作上比采用液體發光的液晶工序少,液晶顯示屏少 3 道工序,成本大幅降低。
(7) OLED 采用的二極管會自行發光,因此不需要背面光源,發光轉化效率高,能耗比液晶低,OLED 能夠在不同材質的基板上制造,廠家甚至可以將電路印刷在彈性材料上——做成能彎曲的柔軟顯示器。
(8) 低電壓直流驅動,5V 以下,用電池就能點亮。高亮度,可達 300 明流以上。
主要的實驗材料
MAX9814麥克風放大器模塊
MAX9814是一款低成本高性能麥克風放大器,具有自動增益控制(AGC)和低噪聲麥克風偏置。器件具有低噪聲前端放大器、可變增益放大(VGA)、輸出放大器、麥克風偏置電壓發生器和AGC控制電路。
●自動增益控制(AGC)
●3種增益設置(40dB、50dB、60dB)
●可編程動作時間
●可編程動作和釋放時間比
●電源電壓范圍2.7V~5.5V
●低THD:0.04% (典型值)
●低功耗關斷模式
●內置2V低噪聲麥克風偏置
MAX9814麥克風放大器模塊電原理圖
0.91寸OLED液晶屏顯示模塊參數
驅動芯片:SSD1306
支持接口:I2C
顯示顏色:白色
高分辨率:128×32
可視角度:大于160°
工作電壓:3.3V / 5V
模塊大小:36 x 12.5(mm)
0.96寸OLED模塊主要參數
電壓:3V~5V DC
工作溫度:-30℃~70℃
駕駛義務:1/64職責
高分辨率:128 * 64
面板尺寸:26.70 * 19.26 * 1.85mm / 1.03 * 0.76 * 0.07英寸(約)
有效面積:21.74 * 11.2mm /0.86*0.44英寸(約)
驅動IC:SSD1306
128 * 64 LED顯示模塊,支持多種控制芯片。
完全兼容51系列,MSP430系列,STM32 / 2,CSR IC等
超低功耗:全屏點亮0.08W
超高亮度和對比度可調
帶嵌入式驅動/控制器
接口類型為IIC
音樂可視化系列小項目:OLED頻譜燈
項目之一:使用MAX9814聲音模塊測試環境音樂的動態波形
實驗開源代碼:
/*
【花雕動手做】音樂可視化系列小項目(02)---OLED頻譜燈
項目之一:使用MAX9814聲音模塊測試環境音樂的動態波形
實驗接線:
MAX9814 Arduino
VCC 5V
GND GND
OUT A0
*/
const int sampleWindow = 50; // 以mS為單位的采樣窗口寬度(50 mS = 20Hz)
unsigned int sample;
void setup() {
Serial.begin(9600);
pinMode(A0, INPUT);
}
void loop() {
unsigned long startMillis = millis(); // 樣本窗口的開始
unsigned int peakToPeak = 0; // 峰峰值
unsigned int signalMax = 0;
unsigned int signalMin = 1024;
// collect data for 50 mS
while (millis() - startMillis < sampleWindow)
{
sample = analogRead(A0);
if (sample < 1024) // 拋出錯誤的讀數
{
if (sample > signalMax)
{
signalMax = sample; // 只保存最大級別
}
else if (sample < signalMin)
{
signalMin = sample; // 僅保存最低級別
}
}
}
peakToPeak = signalMax - signalMin; // max-min =峰峰值幅度
double volts = (peakToPeak * 5.0) / 166;
Serial.println(volts);
}
實驗串口返回情況
打開IDE的串口繪圖器
實驗場景圖
實驗場景圖 動態圖
音樂可視化系列小項目:OLED頻譜燈
項目之二:0.91寸OLED液晶屏聲音可視化頻譜燈
實驗開源代碼:
/*
【花雕動手做】音樂可視化系列小項目(02)---OLED頻譜燈
項目之二:0.91寸OLED液晶屏聲音可視化頻譜燈
實驗接線: max9814接A0
oled模塊 Ardunio Uno
GND---------GND接地線
VCC---------5V 接電源
SDA---------A4
SCL ------- A5
*/
#include "arduinoFFT.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SAMPLES 64 // power of 2
#define SAMPLING_FREQ 8000 // 12 kHz Fmax = sampleF /2
#define AMPLITUDE 100 // 靈敏度
#define FREQUENCY_BANDS 14
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define BARWIDTH 11
#define BARS 11
#define ANALOG_PIN A0
#define OLED_RESET -1 // 重置引腳 #(如果共享 Arduino 重置引腳,則為 -1)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
double vImag[SAMPLES];
double vReal[SAMPLES];
unsigned long sampling_period_us;
arduinoFFT fft = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);
//調整參考以去除背景噪聲
float reference = log10(60.0);
double coutoffFrequencies[FREQUENCY_BANDS];
void setup() {
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
for (;;); // Don't proceed, loop forever
}
// Setup display
display.clearDisplay();
display.display();
display.setRotation(0);
display.invertDisplay(false);
sampling_period_us = (1.0 / SAMPLING_FREQ ) * pow(10.0, 6);
// 計算截止頻率,以對數標度為基數 POt
double basePot = pow(SAMPLING_FREQ / 2.0, 1.0 / FREQUENCY_BANDS);
coutoffFrequencies[0] = basePot;
for (int i = 1 ; i < FREQUENCY_BANDS; i++ ) {
coutoffFrequencies = basePot * coutoffFrequencies[i - 1];
}
// 繪制虛線以分離頻段
for (int i = 0; i < BARS - 1 ; i++) {
for (int j = 0; j < SCREEN_HEIGHT ; j += 4) {
display.writePixel((i + 1)*BARWIDTH + 2 , j, SSD1306_WHITE );
}
}
display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE);
}
int oldHeight[20];
int oldMax[20];
double maxInFreq;
void loop() {
// 采樣
for (int i = 0; i < SAMPLES; i++) {
unsigned long newTime = micros();
int value = analogRead(ANALOG_PIN);
vReal = value;
vImag = 0;
while (micros() < (newTime + sampling_period_us)) {
yield();
}
}
// 計算 FFT
fft.DCRemoval();
fft.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fft.Compute(FFT_FORWARD);
fft.ComplexToMagnitude();
double median[20];
double max[20];
int index = 0;
double hzPerSample = (1.0 * SAMPLING_FREQ) / SAMPLES; //
double hz = 0;
double maxinband = 0;
double sum = 0;
int count = 0;
for (int i = 2; i < (SAMPLES / 2) ; i++) {
count++;
sum += vReal;
if (vReal > max[index] ) {
max[index] = vReal;
}
if (hz > coutoffFrequencies[index]) {
median[index] = sum / count;
sum = 0.0;
count = 0;
index++;
max[index] = 0;
median[index] = 0;
}
hz += hzPerSample;
}
// 計算每個頻段的中值和最大值
if ( sum > 0.0) {
median[index] = sum / count;
if (median[index] > maxinband) {
maxinband = median[index];
}
}
int bar = 0;
for (int i = FREQUENCY_BANDS - 1; i >= 3; i--) {
int newHeight = 0;
int newMax = 0;
// 計算實際分貝
if (median > 0 && max > 0 ) {
newHeight = 20.0 * (log10(median ) - reference);
newMax = 20.0 * (log10(max ) - reference);
}
// 調整最小和最大級別
if (newHeight < 0 || newMax < 0) {
newHeight = 1;
newMax = 1;
}
if (newHeight >= SCREEN_HEIGHT - 2) {
newHeight = SCREEN_HEIGHT - 3;
}
if (newMax >= SCREEN_HEIGHT - 2) {
newMax = SCREEN_HEIGHT - 3;
}
int barX = bar * BARWIDTH + 5;
// 刪除舊水平中位數
if (oldHeight > newHeight) {
display.fillRect(barX, newHeight + 1, 7, oldHeight, SSD1306_BLACK);
}
// 刪除舊的最大級別
if ( oldMax > newHeight) {
for (int j = oldMax; j > newHeight; j -= 2) {
display.drawFastHLine(barX , j, 7, SSD1306_BLACK);
}
}
// 繪制新的最大級別
for (int j = newMax; j > newHeight; j -= 2) {
display.drawFastHLine(barX , j, 7, SSD1306_WHITE);
}
// 繪制新的級別中位數
display.fillRect(barX , 1, 7, newHeight, SSD1306_WHITE);
oldMax = newMax;
oldHeight = newHeight;
bar++;
}
display.drawFastHLine(0 , SCREEN_HEIGHT - 1, SCREEN_WIDTH, SSD1306_WHITE);
display.display();
}
實驗場景圖
實驗場景圖 動態圖
實驗場景圖
實驗場景圖 動態圖
音樂可視化系列小項目:OLED頻譜燈
項目之三:32段分頻0.91寸OLED液晶可視化細條頻譜燈
實驗開源代碼:
/*
【花雕動手做】音樂可視化系列小項目(02)---OLED頻譜燈
項目之三:32段分頻0.91寸OLED液晶可視化細條頻譜燈
實驗接線: max9814接A0
oled模塊 Ardunio Uno
GND---------GND接地線
VCC---------5V 接電源
SDA---------A4
SCL ------- A5
*/
#include <fix_fft.h>
#include <ssd1306.h>
#include <nano_engine.h>
// These are user-adjustable
#define LOG_OUTPUT // Uncomment to enable logarithmic output (exchanges absolute resoluton for more readable output; may require different below params)
#define SAMPLING_FREQUENCY 15000 // Sampling frequency (Actual max measured frequency captured is half)
#define TIME_FACTOR 2 // Smoothing factor (lower is more dynamic, higher is smoother) ranging from 1 to 10+
#define SCALE_FACTOR 15 // Direct scaling factor (raise for higher bars, lower for shorter bars)
#ifdef LOG_OUTPUT
const float log_scale = 64. / log(64. / SCALE_FACTOR + 1.); // Attempts to create an equivalent to SCALE_FACTOR for log function
#endif
const float coeff = 1. / TIME_FACTOR; // Time smoothing coefficients (used to factor in previous data)
const float anti_coeff = (TIME_FACTOR - 1.) / TIME_FACTOR;
const unsigned int sampling_period_us = round(1000000 * (2.0 / SAMPLING_FREQUENCY)); // Sampling period (doubled to account for overclock)
int8_t data[64], buff[32]; // used to store FFT input/output and past data
unsigned long microseconds; // used for timekeeping
int summ, avg; // used for DC bias elimination
NanoEngine<TILE_32x32_MONO> engine; // declares nanoengine
void setup()
{
OSCCAL = 240; // Overclocks the MCU to around 30 MHz, set lower if this causes instability, raise if you can/want
ADCSRA &= ~(bit (ADPS0) | bit (ADPS1) | bit (ADPS2)); // clear ADC prescaler bits
ADCSRA |= bit (ADPS2); // sets ADC clock in excess of 10kHz
ADCSRA |= bit (ADPS0);
ssd1306_128x64_i2c_init(); // initializes OLED
ssd1306_clearScreen(); // clears OLED
engine.begin(); // inititalizes nanoengine
};
void loop()
{
summ = 0;
for (int i = 0; i < 64; i++) {
microseconds = micros();
data[i] = ((analogRead(A0)) >> 2) - 128; // Fitting analogRead data (range:0 - 1023) to int8_t array (range:-128 - 127)
summ += data[i];
while (micros() < (microseconds + sampling_period_us)) { // Timing out uC ADC to fulfill sampling frequency requirement
}
}
// Eliminating remaining DC component (produces usable data in FFT bin #0, which is usually swamped by DC bias)
avg = summ / 64;
for (int i = 0; i < 64; i++) {
data[i] -= avg;
}
fix_fftr(data, 6, 0); // Performing real FFT
// Time smoothing by user-determined factor and user-determined scaling
for (int count = 0; count < 32; count++) {
if (data[count] < 0) data[count] = 0; // Eliminating negative output of fix_fftr
#ifdef LOG_OUTPUT
else data[count] = log_scale * log((float)(data[count] + 1)); // Logarithmic function equivalent to SCALING_FACTOR*log2(x+1)
#else
else data[count] *= SCALE_FACTOR; // Linear scaling up according to SCALE_FACTOR
#endif
data[count] = (float)buff[count] * anti_coeff + (float)data[count] * coeff; // Smoothing by factoring in past data
buff[count] = data[count]; // Storing current output as next frame's past data
if (data[count] > 63) data[count] = 63; // Capping output at screen height
}
// Output to SSD1306 using nanoengine canvas from library
engine.refresh(); // Mark entire screen to be refreshed
engine.canvas.clear(); // Clear canvas as previous data
for (int i = 0; i < 8; i++) {
engine.canvas.drawVLine(i * 4, 31 - (data[i] + 1), 31); // Draw to canvas data for lower-leftest sector (FFT bins 0 - 7, lower half)
}
engine.canvas.blt(0, 32); // Outputs canvas to OLED with an offset (x pixels, y pixels)
engine.canvas.clear();
for (int i = 0; i < 8; i++) {
if (data[i] > 31) engine.canvas.drawVLine(i * 4, 31 - (data[i] - 31), 31); // Draw to canvas data for upper-leftest sector (FFT bins 0 - 7, upper half)
}
engine.canvas.blt(0, 0);
engine.canvas.clear();
for (int i = 8; i < 16; i++) {
engine.canvas.drawVLine((i - 8) * 4, 31 - (data[i] + 1), 31); // FFT bins 8 - 15, lower half
}
engine.canvas.blt(32, 32);
engine.canvas.clear();
for (int i = 8; i < 16; i++) {
if (data[i] > 31) engine.canvas.drawVLine((i - 8) * 4, 31 - (data[i] - 31), 31); // FFT bins 9 - 15, upper half
}
engine.canvas.blt(32, 0);
engine.canvas.clear();
for (int i = 16; i < 24; i++) {
engine.canvas.drawVLine((i - 16) * 4, 31 - (data[i] + 1), 31); // FFT bins 16 - 23, lower half
}
engine.canvas.blt(64, 32);
engine.canvas.clear();
for (int i = 16; i < 24; i++) {
if (data[i] > 31) engine.canvas.drawVLine((i - 16) * 4, 31 - (data[i] - 31), 31); // FFT bins 16 - 23, upper half
}
engine.canvas.blt(64, 0);
engine.canvas.clear();
for (int i = 24; i < 32; i++) {
engine.canvas.drawVLine((i - 24) * 4, 31 - (data[i] + 1), 31); // FFT bins 24 - 31, lower half
}
engine.canvas.blt(96, 32);
engine.canvas.clear();
for (int i = 24; i < 32; i++) {
if (data[i] > 31) engine.canvas.drawVLine((i - 24) * 4, 31 - (data[i] - 31), 31); // FFT bins 24 - 31, upper half
}
engine.canvas.blt(96, 0);
}
實驗場景圖
實驗場景動態圖
實驗場景圖
實驗場景動態圖
工程師必備
- 項目客服
- 培訓客服
- 平臺客服
TOP




















