LED Dual Volume Unit Meter Tower Rev.1 @ 2015.11
본 글에서 소개하는 VU미터 제작기는 AVR또는 아두이노(Arduino)를 활용하였으며, 약간의 손재주가 필요할 수 있습니다.
우선 VU 미터는 아래 사진과 같이 소리의 크기를 일정한 레벨에 따라 시각화시켜주는 것입니다.

위 작은 VU미터를 크고 아름답게 만든것이 아래 사진.

준비물
VU미터가 스테레오이므로 총 2대 제작.
1. 회로도
D1은 문턱전압이 높은 일반 정류 다이오드 사용하면 안 되고 1N5819와 같은 쇼트키 다이오드를 사용합니다.
일반적인 LED와 달리 본 포스트에서 사용하는 고휘도 LED는 전류소모가 많으니 반드시 TR로 전류를 증폭하여 제어하도록 합니다.
LED쪽 회로도는 다음과 같습니다.

TR base에는 1K 저항을 거쳐 MCU의 디지탈 포트와 연결합니다.
아두이노로 치면 D0 ~ D13의 디지탈 I/O 포트에 연결하도록 합니다.
LED 저항계산기는 아래 사이트를 이용하시기 바랍니다.
2. 아크릴 판재
아크릴 사이즈가 너무 크면 LED 빛이 이상하게 퍼져서 한 곳에 편중되고, 너무 작으면 눈이 아플 수 있습니다.
따라서 빛이 한 곳에 편중되지 않도록 적절한 사이즈를 정할 필요가 있습니다.
필자가 사용한 아크릴 사이즈는 다음과 같습니다.
- 기판 고정시킬 아크릴 판재 110mm x 80mm x 5T * 4개
- 실제 LED 바가 될 아크릴 판재 90mm x 60mm x 10T * 26개 (여분 2개)
아크릴 제작은 디바이스마트의 커스텀 아크릴 서비스를 사용했습니다.
3. 마이컴
아두이노 사용자 배려를 위해 아두이노 우노에 사용된 칩과 동일한 ATmega328P-PU를 사용합니다.
4. LED
일반 LED는 휘도가 약하므로 반드시 고휘도 LED를 쓰도록 합니다.
필자가 사용한 고휘도 LED 좌표는 다음과 같습니다.
- 블루: http://www.ic114.com/AJAXWWW/SITE/sc/00V0.aspx?ID_P=P0082816
- 그린: http://www.ic114.com/AJAXWWW/SITE/sc/00V0.aspx?ID_P=P0083225
- 레드: http://www.ic114.com/AJAXWWW/SITE/sc/00V0.aspx?ID_P=P0046208
5. 지지대
사람으로 치면 척추가 되는 부분이므로, 너무 얇으면 아크릴 무게 때문에 홀라당 자빠질 수 있으니.. 7 ~ 10파이 정도가 적당합니다.
자세한 사항은 후술하겠습니다.
제작
도착한 기판 고정용 (110mm x 80mm x 5T) x 4개와 LED 바 전용 (90mm x 60mm x 10T) x 26개(여분 2개) 입니다.
LED 아크릴 바를 고정시켜줄 구멍과 LED를 두개씩 넣을 수 있도록 구멍을 뚫었습니다.
타공 작업 후 지지대와 맞춰보는 모습입니다.
아크릴 LED 바는 총 13개에 20mm 간격으로 배치되고, 기판 지지대까지 합쳐서 LED VU 미터 타워 총 높이가 450mm 정도 됩니다.
LED VU미터의 지지대는 440 ~ 450mm 정도로 맞췄습니다.
LED는 구멍에 잘 넣고 글루건이나 투명 실리콘으로 고정시킨 뒤 LED (+)는 공통으로 땜하고 (-)는 나중에 선을 연결할 수 있도록 적절한 길이로 자르도록 합니다.
조립 후 LED 점등이 잘 되는지 확인합니다.
기판은 위 사진 처럼 고정했습니다.
가능하면 조립 전에 커넥터 먼저 연결해놓고 하는 것이 정신건강에 이롭습니다.
기판에서 나온 선들을 각 LED에 연결해주고 케이블 타이로 정리해주면 깔끔합니다.
Firmware 작성
AVR Studio에서 작성된 AVR용 코드입니다.
#define F_CPU 16000000UL
#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#define SAMPLING_NUM 100
#define SENSITIVITY 10
int ledLevelNow = 0;
int ledLevelOld = 0;
uint32_t ledLevelCount = 0;
uint8_t barLed[14][2] = {
/* PORTB, PORTD */
/* Port No. 10, 9, 19, 18, 17, 16, 15, 14, 13, 12, 11, 6, 5, 4, 3, 2 */
{ 0b00000000, 0b00000000 }, // 0
{ 0b00000010, 0b00000000 }, // 1
{ 0b00000010, 0b01000000 }, // 2
{ 0b00000110, 0b01000000 }, // 3
{ 0b00000110, 0b01100000 }, // 4
{ 0b00001110, 0b01100000 }, // 5
{ 0b00101110, 0b01100000 }, // 6
{ 0b00111110, 0b01100000 }, // 7
{ 0b00111110, 0b01110000 }, // 8
{ 0b00111111, 0b01110000 }, // 9
{ 0b00111111, 0b01111000 }, // 10
{ 0b00111111, 0b11111000 }, // 11
{ 0b00111111, 0b11111100 }, // 12
{ 0b00111111, 0b11111110 } // 13
};
uint8_t dotLed[14][2] = {
/* PORTB, PORTD */
{ 0b00000000, 0b00000000 }, // 0
{ 0b00000010, 0b00000000 }, // 1
{ 0b00000000, 0b01000000 }, // 2
{ 0b00000100, 0b00000000 }, // 3
{ 0b00000000, 0b00100000 }, // 4
{ 0b00001000, 0b00000000 }, // 5
{ 0b00100000, 0b00000000 }, // 6
{ 0b00010000, 0b00000000 }, // 7
{ 0b00000000, 0b00010000 }, // 8
{ 0b00000001, 0b00000000 }, // 9
{ 0b00000000, 0b00001000 }, // 10
{ 0b00000000, 0b10000000 }, // 11
{ 0b00000000, 0b00000100 }, // 12
{ 0b00000000, 0b00000010 } // 13
};
void initGPIO()
{
PORTB = 0x00;
PORTD = 0x00;
DDRB = 0xFF;
DDRD = 0xFF;
}
void initAdc()
{
ADMUX |= (1<<REFS0);
ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0) | (1<<ADEN);
}
uint16_t readAdc(uint8_t adcChannel)
{
ADMUX = (ADMUX & 0xF0) | (adcChannel & 0x0F);
ADCSRA |= (1<<ADSC);
while (ADCSRA & (1<<ADSC));
return ADC;
}
int main()
{
initGPIO();
initAdc();
while (true)
{
for (int i = 0; i < SAMPLING_NUM; i++) ledLevelCount += readAdc(5);
ledLevelCount /= SAMPLING_NUM;
ledLevelNow = ledLevelCount / SENSITIVITY;
if (ledLevelNow > 13) ledLevelNow = 13;
if (ledLevelOld < ledLevelNow) _delay_ms(10);
PORTB = barLed[ledLevelNow][0];
PORTD = barLed[ledLevelNow][1];
ledLevelOld = ledLevelNow;
}
}
아두이노용 코드입니다.
#define F_CPU 16000000UL
#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#define SAMPLING_NUM 100
#define SENSITIVITY 10
int ledLevelNow = 0;
int ledLevelOld = 0;
uint32_t ledLevelCount = 0;
uint8_t barLed[14][2] = {
/* PORTB, PORTD */
/* Port No. 10, 9, 19, 18, 17, 16, 15, 14, 13, 12, 11, 6, 5, 4, 3, 2 */
{ 0b00000000, 0b00000000 }, // 0
{ 0b00000010, 0b00000000 }, // 1
{ 0b00000010, 0b01000000 }, // 2
{ 0b00000110, 0b01000000 }, // 3
{ 0b00000110, 0b01100000 }, // 4
{ 0b00001110, 0b01100000 }, // 5
{ 0b00101110, 0b01100000 }, // 6
{ 0b00111110, 0b01100000 }, // 7
{ 0b00111110, 0b01110000 }, // 8
{ 0b00111111, 0b01110000 }, // 9
{ 0b00111111, 0b01111000 }, // 10
{ 0b00111111, 0b11111000 }, // 11
{ 0b00111111, 0b11111100 }, // 12
{ 0b00111111, 0b11111110 } // 13
};
uint8_t dotLed[14][2] = {
/* PORTB, PORTD */
{ 0b00000000, 0b00000000 }, // 0
{ 0b00000010, 0b00000000 }, // 1
{ 0b00000000, 0b01000000 }, // 2
{ 0b00000100, 0b00000000 }, // 3
{ 0b00000000, 0b00100000 }, // 4
{ 0b00001000, 0b00000000 }, // 5
{ 0b00100000, 0b00000000 }, // 6
{ 0b00010000, 0b00000000 }, // 7
{ 0b00000000, 0b00010000 }, // 8
{ 0b00000001, 0b00000000 }, // 9
{ 0b00000000, 0b00001000 }, // 10
{ 0b00000000, 0b10000000 }, // 11
{ 0b00000000, 0b00000100 }, // 12
{ 0b00000000, 0b00000010 } // 13
};
void initGPIO()
{
PORTB = 0x00;
PORTD = 0x00;
DDRB = 0xFF;
DDRD = 0xFF;
}
void initAdc()
{
ADMUX |= (1<<REFS0);
ADCSRA |= (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0) | (1<<ADEN);
}
uint16_t readAdc(uint8_t adcChannel)
{
ADMUX = (ADMUX & 0xF0) | (adcChannel & 0x0F);
ADCSRA |= (1<<ADSC);
while (ADCSRA & (1<<ADSC));
return ADC;
}
void setup()
{
}
void loop()
{
initGPIO();
initAdc();
while (true)
{
for (int i = 0; i < SAMPLING_NUM; i++) ledLevelCount += readAdc(5);
ledLevelCount /= SAMPLING_NUM;
ledLevelNow = ledLevelCount / SENSITIVITY;
if (ledLevelNow > 13) ledLevelNow = 13;
if (ledLevelOld < ledLevelNow) _delay_ms(10);
PORTB = barLed[ledLevelNow][0];
PORTD = barLed[ledLevelNow][1];
ledLevelOld = ledLevelNow;
}
}
C언어가 가능하단 가정하게 간단히 코드를 설명드리면,
- ADC 샘플링 횟수만큼 평균치내어 노래파형을 안정화시키고,
- 평균치된 값을 연산에서 LED 몇개를 킬 것인지 계산합니다.
SENSITIVITY가 낮을수록 Level 반응이 커지는데, 그만큼 정확도가 높아질수도, 작아질수도 있으므로 적당히 조절하는것이 중요합니다.
마무리
질문은 댓글을 남겨주시기 바랍니다.


Leave a Reply