ATtiny85 - Spectrum Analyzer on RGB Led Matrix 16x20
by tuenhidiy in Circuits > Arduino
9904 Views, 83 Favorites, 0 Comments
ATtiny85 - Spectrum Analyzer on RGB Led Matrix 16x20
Continuing with ATtiny85, today I'd like to share how to build a music spectrum analyzer on 16x20 RGB led matrix. The music signal FFT transformation and LED Bit Angle Modulation are all carried out by one DigiSpark ATtiny85.
Please watch my video below:
Things We Need
Main components are as follows:
- 1 x DigiSpark ATtiny85.
- 320 x 5mm Common Anode RGB LEDs.
- 16 x A1013 Transistors.
- 2 x Shift Register 74HC595N.
- 9 x Power Logic 8-Bit Shift Register TPIC6B595N.
- 12 x 0.1uF Decoupling Capacitors.
- 100 x 100Ω Resistors.
- 16 x 1kΩ Resistors.
- 4 x 10kΩ Resistors.
- 1 x Single-Side Copper Prototype PCB Size A4.
- 2 x Clear Acrylic Plate Size A4.
- 2 x Male & Female 40pin 2.54mm Header.
- 1 x Power Supply Adapter 5V/2A.
- 1 x DC Power Supply Female Socket.
- 1 x DC Power Supply Screw Type.
- 8 x Copper Standoff Spacers 20mm.
- 1 x 3.5mm Audio Jack.
- 2 meter x Rainbow Color Flat Ribbon Cable.
Schematic
You can download the project schematic in PDF format HERE.
The components for controlling a RGB led matrix 16x20:
- Columns (cathodes) scanning: including 3 groups of TPIC6B595N to control 20 columns, each group includes 3 x TPIC6B595N for 3 colors (Red, Green & Blue), for example with blue color:
- Rows (anodes) scanning: including 2 x 74HC595N and 16 x A1013 transistor to control 16 rows.
Soldering and Arrangement
Firstly, I soldered 320 RGB leds on the PCB, from outer edge of the PCB I left about 15~16 holes to reserve space for control components.
As picture above, I soldered all cathode pins in same column (total 20 columns - cathode with R, G, B pin) together after aligning leds on the top side.
After soldering 20 led columns, I soldered anode led pins in same row ( total 16 rows - anode) together by bending anode led pins so that there is a gap between the rows and columns, avoiding them touching each other.
The 16x20 RGB led matrix has been done!
Then around this led matrix, I soldered all row (2 x 74HC595N + 16 x A1013) and column (9 x TPIC6B595N) scanning components, female header for ATtiny85, 3.5mm audio jack and power supply socket.
I didn't use wires in this project but instead I used the led pins which were left from many led related projects.
I want all components and connections to be seen, so I covered the top and botom of PCB by clear arcylic plates.
Done!!! And I can put it on my tea table for decoration.
If you have a PCB project, please visit the NEXTPCB website to get exciting discounts and coupons.
- Only $0 for 1-4 layer PCB Prototype: https://www.nextpcb.com/pcb-quote?act=2&code=tunen...
- New customer get $100 coupons, register at: https://www.nextpcb.com/register?code=tunendd
Programing and How It Works
The project code is as below:
/* Tested with Arduino 1.8.13, the ATTinyCore and libraries: https://github.com/SpenceKonde/ATTinyCore https://github.com/JChristensen/tinySPI https://github.com/kosme/fix_fft Connections: - DigiSpark ATtiny85 P0 (PB0) - BLANK PIN OF 74HC595 & TPIC6B595. - DigiSpark ATtiny85 P1 (PB1) - DATA PIN OF TPIC6B595 - DigiSpark ATtiny85 P2 (PB2) - CLOCK PIN OF 74HC595 & TPIC6B595. - DigiSpark ATtiny85 P3 (PB3) - LATCH PIN OF TPIC6B595 & 74HC595 - DigiSpark ATtiny85 P4 (PB4) - AUDIO PIN. */ #include <tinySPI.h> // https://github.com/JChristensen/tinySPI #include "fix_fft.h" // https://github.com/kosme/fix_fft #define HARDWARE_SPI 1 // Set to 1 to use hardware SPI, set to 0 to use software SPI #define BAM_RESOLUTION 2 // Define Bit Angle Modulation BAM resolution, // Digispark Attiny85 pin definitions const int DATA_PIN(1), // Serial data in (Data pin) CLOCK_PIN(2), // Shift register clock (Clock pin) LATCH_PIN(3), // Storage register clock (Latch pin) BLANK_PIN(0); // Output enable pin (Blank pin) //AUDIO_PIN(4); // Input audio pin (Audio pin) //**************************************************BAM Variables**********************************************************// byte red[BAM_RESOLUTION][48]; byte green[BAM_RESOLUTION][48]; byte blue[BAM_RESOLUTION][48]; // Anode low and high byte for shifting out. byte anode[16][2]= {{B11111110, B11111111}, {B11111101, B11111111}, {B11111011, B11111111}, {B11110111, B11111111}, {B11101111, B11111111}, {B11011111, B11111111}, {B10111111, B11111111}, {B01111111, B11111111}, {B11111111, B11111110}, {B11111111, B11111101}, {B11111111, B11111011}, {B11111111, B11110111}, {B11111111, B11101111}, {B11111111, B11011111}, {B11111111, B10111111}, {B11111111, B01111111}}; int row; int level; int BAM_Bit, BAM_Counter=0; // Colorwheel array for spectrum analyzer byte colorwheels[20][3]={{0, 0, 3}, {0, 0, 3}, {1, 0, 3}, {2, 0, 3}, {3, 0, 3}, {3, 0, 2}, {3, 0, 1}, {3, 0, 0}, {3, 1, 0}, {3, 2, 0}, {3, 3, 0}, {2, 3, 0}, {1, 3, 0}, {0, 3, 0}, {0, 3, 1}, {0, 3, 2}, {0, 3, 3}, {0, 2, 3}, {0, 1, 3}, {0, 0, 3}}; //****************************************************Fix_FFT Variables********************************************************// int8_t data[32]; unsigned long useconds; int sum_data; //************************************************************************************************************// void setup() { row = 0; level = 0; #if HARDWARE_SPI == 1 SPI.begin(); // Start hardware SPI. #else pinMode(CLOCK_PIN, OUTPUT); // Set up the pins for software SPI pinMode(DATA_PIN, OUTPUT); #endif // Set up the pins for software SPI pinMode(LATCH_PIN, OUTPUT); digitalWrite(LATCH_PIN, HIGH); noInterrupts(); // Clear registers TCNT1 = 0; TCCR1 = 0; // Reset to $00 in the CPU clock cycle after a compare match with OCR1C register value // 50 x 3.636 = 181.8us // If software SPI is used, this value should be increased. OCR1C = 50; // A compare match does only occur if Timer/Counter1 counts to the OCR1A value OCR1A = OCR1C; // Clear Timer/Counter on Compare Match A TCCR1 |= (1 << CTC1); // Prescaler 64 - 16.5MHz/64 = 275Kz or 3,636us TCCR1 |= (1 << CS12) | (1 << CS11) | (1 << CS10); // Output Compare Match A Interrupt Enable TIMSK |= (1 << OCIE1A); interrupts(); clearfast(); // ADC Control and Status Register ADCSRA &= ~((1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2)); ADCSRA |= ((1 << ADPS2) |(1 << ADPS0)); } void loop() { sum_data = 0; for (int i = 0; i < 32; i++) { useconds = micros(); data[i] = ((analogRead(A2)) >> 2) - 128; // DigiSpark ATtiny85 analog pin A2 at PB4 sum_data += data[i]; while (micros() < (useconds + 100)) { } } for (int i = 0; i < 32; i++) { data[i] -= sum_data/32; } fix_fftr(data, 5, 0); for(int j = 0; j < 16; j++) { data[j] = 4*((float)data[2*j]+ (float)data[2*j+1]); // Everage & scale 2 bars together to get 16 bars } for (byte xx=0; xx<20; xx++) { for (byte yy=0; yy < 16; yy++) { if (xx > data[yy]) { LED(19-xx, yy, 0, 0, 0); } else { LED(19-xx, yy, colorwheels[xx][0], colorwheels[xx][1], colorwheels[xx][2]); // Colorwheel spectrum bars } } } } void LED(int X, int Y, int R, int G, int B) { X = constrain(X, 0, 19); Y = constrain(Y, 0, 15); R = constrain(R, 0, 3); G = constrain(G, 0, 3); B = constrain(B, 0, 3); int WhichByte = int(Y*3+ X/8); int WhichBit = (X%8); for (byte BAM = 0; BAM < BAM_RESOLUTION; BAM++) { bitWrite(green[BAM][WhichByte], WhichBit, bitRead(G, BAM)); bitWrite(red[BAM][WhichByte], WhichBit, bitRead(R, BAM)); bitWrite(blue[BAM][WhichByte], WhichBit, bitRead(B, BAM)); } } void clearfast () { memset(green, 0, sizeof(green[0][0]) * BAM_RESOLUTION * 48); memset(red, 0, sizeof(red[0][0]) * BAM_RESOLUTION * 48); memset(blue, 0, sizeof(blue[0][0]) * BAM_RESOLUTION * 48); } ISR(TIMER1_COMPA_vect){ PORTB |= ((1<<BLANK_PIN)); // Set BLANK PIN low - 74HC595 & TPIC6B595 if(BAM_Counter==8) BAM_Bit++; BAM_Counter++; // Anode scanning DIY_SPI(anode[row][1]); // Send out the anode level high byte DIY_SPI(anode[row][0]); // Send out the anode level low byte switch (BAM_Bit) { case 0: DIY_SPI(green [0][level + 2]); DIY_SPI(green [0][level + 1]); DIY_SPI(green [0][level + 0]); DIY_SPI(red [0][level + 2]); DIY_SPI(red [0][level + 1]); DIY_SPI(red [0][level + 0]); DIY_SPI(blue [0][level + 2]); DIY_SPI(blue [0][level + 1]); DIY_SPI(blue [0][level + 0]); break; case 1: DIY_SPI(green [1][level + 2]); DIY_SPI(green [1][level + 1]); DIY_SPI(green [1][level + 0]); DIY_SPI(red [1][level + 2]); DIY_SPI(red [1][level + 1]); DIY_SPI(red [1][level + 0]); DIY_SPI(blue [1][level + 2]); DIY_SPI(blue [1][level + 1]); DIY_SPI(blue [1][level + 0]); if(BAM_Counter==24) { BAM_Counter=0; BAM_Bit=0; } break; } PORTB &= ~(1<<LATCH_PIN); // Set LATCH PIN low - 74HC595 & TPIC6B595 PORTB |= 1<<LATCH_PIN; // Set LATCH PIN high - 74HC595 & TPIC6B595 PORTB &= ~(1<<BLANK_PIN); // Set BLANK PIN low - 74HC595 & TPIC6B595 row++; level = row * 3; if (row == 16) row = 0; if (level == 48) level = 0; pinMode(BLANK_PIN, OUTPUT); } void DIY_SPI(uint8_t DATA) { uint8_t i; #if HARDWARE_SPI == 1 SPI.transfer(DATA); #else for (i = 0; i<8; i++) { digitalWrite(DATA_PIN, !!(DATA & (1 << i))); PORTB |= 1<<CLOCK_PIN; PORTB &= ~(1<<CLOCK_PIN); } #endif }
The following core and libraries need to be installed in this project:
- ATTinyCore at: https://github.com/SpenceKonde/ATTinyCore
- TinySPI library at: https://github.com/JChristensen/tinySPI
- Fix_FFT library at: https://github.com/kosme/fix_fft
DigiSpark ATtiny85 pin usage:
- DigiSpark ATtiny85 P0 (PB0) - BLANK PIN OF 74HC595 & TPIC6B595.
- DigiSpark ATtiny85 P1 (PB1) - DATA PIN OF TPIC6B595
- DigiSpark ATtiny85 P2 (PB2) - CLOCK PIN OF 74HC595 & TPIC6B595.
- DigiSpark ATtiny85 P3 (PB3) - LATCH PIN OF TPIC6B595 & 74HC595
- DigiSpark ATtiny85 P4 (PB4) - AUDIO PIN.
Due to ATtiny85's memory, to perform both B.A.M and FFT processes, I had to reduce B.A.M resolution to 2. That's enough for me to customize 16 x spectrum bar colors.
Base on the hardware connection, the "shiftout" function is carried out in following order:
DIY_SPI(anode[row][1]); // Send out the anode level high byte DIY_SPI(anode[row][0]); // Send out the anode level low byte DIY_SPI(green[BAM_Bit][row * 3 + 2]); // Send the green third byte DIY_SPI(green[BAM_Bit][row * 3 + 1]); // Send the green second byte DIY_SPI(green[BAM_Bit][row * 3 + 0]); // Send the green first byte DIY_SPI(red[BAM_Bit][row * 3 + 2]); // Send the red third byte DIY_SPI(red[BAM_Bit][row * 3 + 1]); // Send the red second byte DIY_SPI(red[BAM_Bit][row * 3 + 0]); // Send the red first byte DIY_SPI(blue[BAM_Bit][row * 3 + 2]); // Send the blue third byte DIY_SPI(blue[BAM_Bit][row * 3 + 1]); // Send the blue second byte DIY_SPI(blue[BAM_Bit][row * 3 + 0]); // Send the blue first byte
Each spectrum bar amplitude was shown following an 2-dimensions color-wheel array. It looks quite pretty in this way.
for (byte xx=0; xx<20; xx++) { for (byte yy=0; yy < 16; yy++) { if (xx > data[yy]) { LED(19-xx, yy, 0, 0, 0); } else { LED(19-xx, yy, colorwheels[xx][0], colorwheels[xx][1], colorwheels[xx][2]); } } }
And colorwheel array was declared as follow:
byte colorwheels[20][3] = {{0, 0, 3}, {0, 0, 3}, {1, 0, 3}, {2, 0, 3}, {3, 0, 3}, {3, 0, 2}, {3, 0, 1}, {3, 0, 0}, {3, 1, 0}, {3, 2, 0}, {3, 3, 0}, {2, 3, 0}, {1, 3, 0}, {0, 3, 0}, {0, 3, 1}, {0, 3, 2}, {0, 3, 3}, {0, 2, 3}, {0, 1, 3}, {0, 0, 3}};