Created
October 2, 2022 03:28
-
-
Save mohitbhoite/5c3732cfeb7346f6b2cd1318612114aa to your computer and use it in GitHub Desktop.
ATtiny85 based VU meter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* ATtiny85 based VU meter | |
* Author: Mohit Bhoite | |
* Date: 01 August 2022 | |
* | |
* Based on original code by Adafruit Industries. Distributed under the BSD license. | |
* and David Johnson-Davies - www.technoblogy.com | |
* CC BY 4.0 Licensed under a Creative Commons Attribution 4.0 International license: | |
* http://creativecommons.org/licenses/by/4.0/ | |
* | |
* For the board, select ATTinyCore in Arduino - ATtiny25/45/85 (No bootloader) | |
* ATtiny85 @ 8 MHz (internal oscillator; BOD disabled) | |
* | |
*/ | |
#include <Adafruit_NeoPixel.h> | |
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket | |
#define N_PIXELS 10 // Number of pixels in strand | |
#define MIC_PIN A1 // Microphone is attached to this analog pin | |
#define LED_PIN 1 // NeoPixel LED strand is connected to this pin | |
#define DC_OFFSET 0 // DC offset in mic signal - if unusure, leave 0 | |
#define NOISE 120 // Noise/hum/interference in mic signal | |
#define SAMPLES 40 // Length of buffer for dynamic level adjustment | |
#define TOP (N_PIXELS + 2) // Allow dot to go slightly off scale | |
#define PEAK_FALL 500 // Rate of peak falling dot | |
#define VUBRIGHTNESS 90 //can go upto 255 | |
byte | |
peak = 0, // Used for falling dot | |
dotCount = 0, // Frame counter for delaying dot-falling speed | |
volCount = 0; // Frame counter for storing past volume data | |
unsigned int | |
vol[SAMPLES], // Collection of prior volume samples | |
lvl = 20, // Current "dampened" audio level | |
minLvlAvg = 0, // For dynamic adjustment of graph low & high | |
maxLvlAvg = 512; | |
Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_PIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); | |
void setup() { | |
clock_prescale_set(clock_div_1); | |
// Set up ADC in differential mode with 20x gain | |
ADMUX = 2<<REFS0 | 0<<REFS2 | 11<<MUX0; // Internal 1.1V ref, ADC0 vs ADC1 x20 | |
ADCSRA = 1<<ADEN | 3<<ADPS0; // Enable, 125kHz ADC clock | |
memset(vol, 0, sizeof(vol)); | |
strip.begin(); | |
strip.setBrightness(80); | |
} | |
void loop() { | |
uint8_t i; | |
uint16_t minLvl, maxLvl; | |
unsigned int n, height; | |
n = LogBar(ReadADC()/7); //dividing by 7 to make it less sensitive. You can play with different values here. | |
//n = ReadADC(); | |
//n = analogRead(MIC_PIN); // Raw reading from mic | |
//n = abs(n - 512 - DC_OFFSET); // Center on zero | |
//n = (n <= NOISE) ? 0 : (n - NOISE); // Remove noise/hum | |
lvl = ((lvl * 7) + n) >> 3; // "Dampened" reading (else looks twitchy) | |
// Calculate bar height based on dynamic min/max levels (fixed point): | |
height = TOP * (lvl - minLvlAvg) / (long)(maxLvlAvg - minLvlAvg); | |
if(height < 0L) height = 0; // Clip output | |
else if(height > TOP) height = TOP; | |
if(height > peak) peak = height; // Keep 'peak' dot at top | |
//Set pixel colors here | |
for(i=0; i<10; i++) { | |
if(i >= height) | |
{ | |
strip.setPixelColor(i,0,0,0); | |
//strip.setPixelColor(i+8,0,0,0); | |
//strip.setPixelColor(i+16,0,0,0); | |
} | |
else | |
{ | |
//strip.setPixelColor(i,Wheel(map(i,0,strip.numPixels()-1,30,150))); | |
//strip.setPixelColor(i+8,Wheel(map(i,0,strip.numPixels()-1,30,150))); | |
//strip.setPixelColor(i+16,Wheel(map(i,0,strip.numPixels()-1,30,150))); | |
strip.setPixelColor(i,VUcolorPicker(i)); | |
} | |
} | |
// Draw peak dot | |
if(peak > 0 && peak <= 10-1) | |
{ | |
strip.setPixelColor(peak,Wheel(map(peak,0,9,30,150))); | |
//strip.setPixelColor(peak+8,Wheel(map(peak,0,strip.numPixels()-1,30,150))); | |
//strip.setPixelColor(peak+16,Wheel(map(peak,0,strip.numPixels()-1,30,150))); | |
} | |
strip.show(); // Update strip | |
// Every few frames, make the peak pixel drop by 1: | |
if(++dotCount >= PEAK_FALL) { //fall rate | |
if(peak > 0) peak--; | |
dotCount = 0; | |
} | |
vol[volCount] = n; // Save sample for dynamic leveling | |
if(++volCount >= SAMPLES) volCount = 0; // Advance/rollover sample counter | |
// Get volume range of prior frames | |
minLvl = maxLvl = vol[0]; | |
for(i=1; i<SAMPLES; i++) { | |
if(vol[i] < minLvl) minLvl = vol[i]; | |
else if(vol[i] > maxLvl) maxLvl = vol[i]; | |
} | |
// minLvl and maxLvl indicate the volume range over prior frames, used | |
// for vertically scaling the output graph (so it looks interesting | |
// regardless of volume level). If they're too close together though | |
// (e.g. at very low volume levels) the graph becomes super coarse | |
// and 'jumpy'...so keep some minimum distance between them (this | |
// also lets the graph go to zero when no sound is playing): | |
if((maxLvl - minLvl) < TOP) maxLvl = minLvl + TOP; | |
minLvlAvg = (minLvlAvg * 63 + minLvl) >> 6; // Dampen min/max levels | |
maxLvlAvg = (maxLvlAvg * 63 + maxLvl) >> 6; // (fake rolling average) | |
} | |
// The original code used these colors for the LEDs. I'm not using this effect. | |
// Input a value 0 to 255 to get a color value. | |
// The colors are a transition r - g - b - back to r. | |
uint32_t Wheel(byte WheelPos) { | |
if(WheelPos < 85) { | |
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); | |
} else if(WheelPos < 170) { | |
WheelPos -= 85; | |
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); | |
} else { | |
WheelPos -= 170; | |
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); | |
} | |
} | |
int LogBar (unsigned int x) { | |
x = x | x>>1; | |
x = x | x>>2; | |
x = x | x>>4; | |
x = x | x>>8; | |
return x; | |
} | |
// Analogue to Digital ********************************** | |
unsigned int ReadADC() { | |
static int Read; | |
unsigned char low,high; | |
ADCSRA = ADCSRA | 1<<ADSC; // Start | |
do ; while (ADCSRA & 1<<ADSC); // Wait while conversion in progress | |
low = ADCL; | |
high = ADCH; | |
Read = max(Read>>1, high<<8 | low); // Add delay | |
return Read; | |
} | |
//Pick RGB color for each pixel. I have tried going from first pixel of Blue>Cyan>Green>Orange>Red last pixel | |
uint32_t VUcolorPicker(uint8_t i) | |
{ | |
uint32_t vucolor = 0; | |
switch(i) | |
{ | |
case 0: | |
vucolor = strip.Color(0,0,VUBRIGHTNESS); | |
break; | |
case 1: | |
vucolor = strip.Color(0,VUBRIGHTNESS/4,VUBRIGHTNESS); | |
break; | |
case 2: | |
vucolor = strip.Color(0,VUBRIGHTNESS,0); | |
break; | |
case 3: | |
vucolor = strip.Color(VUBRIGHTNESS/4,VUBRIGHTNESS,0); | |
break; | |
case 4: | |
vucolor = strip.Color(VUBRIGHTNESS,VUBRIGHTNESS,0); | |
break; | |
case 5: | |
vucolor = strip.Color(VUBRIGHTNESS,VUBRIGHTNESS,0); | |
break; | |
case 6: | |
vucolor = strip.Color(VUBRIGHTNESS,VUBRIGHTNESS/2,0); | |
break; | |
case 7: | |
vucolor = strip.Color(VUBRIGHTNESS,VUBRIGHTNESS/2,0); | |
break; | |
case 8: | |
vucolor = strip.Color(VUBRIGHTNESS,0,0); | |
break; | |
case 9: | |
vucolor = strip.Color(VUBRIGHTNESS,0,0); | |
break; | |
} | |
return vucolor; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment