MSGEQ7 이퀄라이저 모듈 사진 MSGEQ7 칩과 회로 사진

음악에 맞춰 LED가 춤추는 스펙트럼 표시기를 만들려면 소리를 주파수 대역별로 나눠야 합니다. 마이크로컨트롤러에서 FFT를 돌리는 방법도 있지만, ATmega8 정도의 작은 칩에서는 부담이 큽니다. 이럴 때 많이 쓰는 게 MSGEQ7입니다. 오디오 신호를 넣으면 7개 대역의 세기를 DC 전압으로 하나씩 내 주는 칩이라, AVR 쪽에서는 ADC로 전압만 읽으면 됩니다.

광고

나누는 대역은 63Hz, 160Hz, 400Hz, 1kHz, 2.5kHz, 6.25kHz, 16kHz 일곱 개입니다. 데이터시트상 3.3V와 5V 모두에서 동작하므로 5V로 돌리는 ATmega8A에 바로 붙일 수 있습니다.

칩 주변에 필요한 부품

부품은 몇 개 안 됩니다. 8번 핀에 200KΩ 저항과 33pF 세라믹 콘덴서를 달아 칩 내부 클럭 주파수를 정합니다. 이 두 부품은 8번 핀에 최대한 가깝게 붙이라는 게 원래 회로도의 주의 사항이에요. 노이즈 대책으로 6번 핀과 1번 핀에서 각각 GND로 0.1uF를 달고, 입력인 5번 핀에는 0.01uF 콘덴서를 거쳐 들어가게 하되 스테레오라면 채널마다 22KΩ 저항을 하나씩 직렬로 넣어 합칩니다.

RESET과 STROBE로 대역을 하나씩 꺼내는 방식

MSGEQ7 출력 핀(DC OUT)은 하나뿐입니다. 7개 값을 한꺼번에 주는 게 아니라 내부 멀티플렉서가 한 대역씩 차례로 내보내고, 이걸 RESET과 STROBE 두 핀으로 조종합니다.

RESET 펄스를 주면 멀티플렉서가 처음으로 돌아갑니다. 그 뒤 STROBE 펄스를 한 번 줄 때마다 다음 대역의 전압이 DC OUT에 나옵니다. RESET 직후 첫 STROBE에서 63Hz, 두 번째에서 160Hz, 이런 식으로 진행하다가 7번째가 끝나면 다시 63Hz로 돌아옵니다. 그래서 코드는 "RESET 한 번 → STROBE 7번 반복하며 매번 ADC 읽기" 구조가 됩니다.

ATmega8 연결

STROBE는 PD4, RESET은 PD7에 연결하고, MSGEQ7의 DC OUT은 ADC 0번 채널로 받습니다. 결과는 PWM이 되는 핀 세 개(OC2, OC1B, OC1A)에 LED를 달아 밝기로 표시합니다.

코드

main.c 는 입출력·타이머·ADC를 초기화한 뒤 무한 루프에서 7개 값을 읽고, 그중 0번(63Hz), 3번(1kHz), 6번(16kHz) 세 값으로 LED 밝기를 정합니다. 저음·중음·고음을 하나씩 보여 주는 셈입니다.

#include <avr/io.h>

#include "msgeq7.h"

//Three LEDSs connected to PWM Outputs
#define SET_LED1_BRIGHTNESS(x) (OCR2=x)
#define SET_LED2_BRIGHTNESS(x) (OCR1BL=x)
#define SET_LED3_BRIGHTNESS(x) (OCR1AL=x)

void IO_init(void);
void TIMER_init(void);
void ADC_init(void);
unsigned char ReadAnalog(void);

int main(void)
{
    unsigned char result[7];
    IO_init();
    TIMER_init();
    ADC_init();
    /* Replace with your application code */
    msgeq7_reset();
    while (1)
    {
        msgeq7_getValues((char *)&result);
        SET_LED1_BRIGHTNESS(result[0]);
        SET_LED2_BRIGHTNESS(result[3]);
        SET_LED3_BRIGHTNESS(result[6]);
    }
}

void IO_init(void){
    //PORTB as output
    DDRB=0XFF;

    //PORTB as current source
    PORTB=0XFF;

    //PORTD pins D7 and D4 as output
    DDRD|=(1<<DDRD7)|(1<<DDRD4);
}

void TIMER_init(void){
    //timer2 in fast PWM mode and inverting output on OCR2
    TCCR2|=(1<<COM21)|(1<<COM20)|(1<<WGM21)|(1<<WGM20)|(1<<CS20);

    //tiemr1 in fast PWM mode and inverting output on OCR1A and OCR1B
    TCCR1A|=((1<<COM1A0)|(1<<COM1A1)|(1<<WGM10)|(1<<COM1B0)|(1<<COM1B1));
    TCCR1B|=(1<<WGM12)|(1<<CS10);
}

void ADC_init(){
    //Select internal referance and Channel 0
    ADMUX|=(1<<REFS0)|(1<<REFS1);

    //Enable ADC and ADC frequency=clock frequency/4
    ADCSRA|=(1<<ADEN)|(1<<ADPS1);
}

unsigned char ReadAnalog(void){
    //interger to store 10-bit ADC value
    unsigned int result=0;

    //start conversion
    ADCSRA|=(1<<ADSC);

    //wait till converion is done
    while(ADCSRA&(1<<ADSC));

    //read result
    result=ADCW;

    //return 10-bit value mapped to 8-bit value i.e 1024/4=256
    return (unsigned char)(result/4);
}

타이머는 두 개 모두 8비트 Fast PWM에 반전 출력(inverting)으로 설정돼 있습니다. 반전 모드라서 OCR 값이 클수록 핀이 LOW인 시간이 길어지는데, PORTB=0XFF 주석에 "current source"라고 적힌 걸 보면 LED를 어떤 방향으로 달았느냐에 맞춘 설정입니다. 직접 만들 때 LED가 반대로(소리가 클수록 어둡게) 동작하면 COM 비트를 비반전으로 바꾸거나 LED 방향을 확인해 보세요.

ADC는 REFS0, REFS1 을 모두 켜서 내부 기준전압(ATmega8은 2.56V)을 쓰고 채널 0을 읽습니다. 10비트 결과를 4로 나눠 0~255로 만들어서 PWM 레지스터에 그대로 넣을 수 있게 했습니다.

msgeq7.h 에는 핀 정의와 함수 선언만 있습니다. 배선을 바꾸면 여기만 고치면 됩니다.

#ifndef INCFILE1_H_
#define INCFILE1_H_

#define STROBE_PORT PORTD
#define STROBE_PIN PORTD4

#define RESET_PORT PORTD
#define RESET_PIN PORTD7

void msgeq7_reset(void);
void msgeq7_getValues(char * const);

#endif /* INCFILE1_H_ */

실제 칩 제어는 msgeq7.c 에 있습니다.

#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>
#include "msgeq7.h"
unsigned char ReadAnalog(void);

void msgeq7_reset(){
    STROBE_PORT|=(1 << STROBE_PIN); //make sure that STORBE pin is high before RESET
    _delay_us(100);

    RESET_PORT|=(1 << RESET_PIN);
    _delay_us(100);

    RESET_PORT &= ~(1 << RESET_PIN);
    _delay_us(100);

}
void msgeq7_getValues(char * const result){
    unsigned char i=0;
    for(i=0;i<7;i++){
        STROBE_PORT&= ~(1 << STROBE_PIN);
        _delay_us(100);
        result[i]=ReadAnalog();

        if(result[i]<60)
            result[i]=0;
        STROBE_PORT|=(1 << STROBE_PIN);
        _delay_us(100);
    }
}

msgeq7_reset 은 STROBE를 HIGH로 올려 둔 상태에서 RESET 펄스를 한 번 줍니다. msgeq7_getValues 는 STROBE를 LOW로 내리고 잠깐 기다린 뒤(출력이 안정될 시간) ADC를 읽고, 다시 HIGH로 올리는 걸 7번 반복합니다. STROBE가 LOW로 떨어지는 순간이 "다음 대역으로 넘어가라"는 신호입니다.

중간의 if(result[i]<60) result[i]=0; 은 잡음 제거용입니다. 소리가 없어도 MSGEQ7 출력에는 바닥 전압이 조금 남아 있어서, 이걸 그대로 쓰면 LED가 늘 희미하게 켜져 있습니다. 60이라는 기준값은 회로와 입력 레벨에 따라 달라지니 직접 조용한 상태에서 값을 찍어 보고 맞추는 게 좋습니다.

F_CPU 가 16MHz로 정의돼 있으니 실제 크리스털도 16MHz여야 _delay_us 시간이 맞습니다. 내부 RC 발진기로 돌린다면 이 값을 실제 클럭에 맞게 바꿔야 합니다. 그리고 이 예제는 7개 중 3개만 LED로 쓰지만, result 배열에는 7개 값이 다 들어 있으니 LED를 늘리거나 시리얼로 PC에 보내 막대 그래프를 그리는 식으로 쉽게 넓힐 수 있습니다.