프로그래밍/마이컴

ADC (아날로그 읽기)

얼땅불땅 2026. 7. 19. 05:18

UART, I2C, SPI 통신 글은 이미 다 써버렸다. 근데 이번엔 통신 얘기가 아니다. 지금까지 다룬 건 죄다 디지털 신호(0/1)였는데, 세상엔 0과 1로 딱 안 떨어지는 값이 훨씬 많더라. 온도, 밝기, 가변저항 돌린 정도… 이런 건 전부 연속적인 아날로그 전압이다. 오늘은 이걸 마이컴이 어떻게 읽는지, ADC를 정리해본다.

일단 문제부터

마이컴은 기본적으로 0 아니면 1밖에 모른다. 핀에 전압이 걸렸냐(HIGH) 안 걸렸냐(LOW), 딱 두 가지다. 그런데 예를 들어 가변저항을 천천히 돌리면 핀에 걸리는 전압이 0V에서 5V까지 스르륵 바뀐다. 2.3V, 2.31V, 2.317V… 무한히 잘게 쪼갤 수 있는 값이다.

이걸 마이컴한테 "지금 몇 볼트야?" 하고 물어보면, 디지털만 아는 녀석이 답을 못 한다. 그래서 아날로그 전압을 숫자로 바꿔주는 통역기가 필요하다. 그게 ADC다.

용어적 설명부터

ADC는 Analog to Digital Converter, 말 그대로 "아날로그를 디지털로 바꾸는 변환기"다. "에이-디-씨"라고 읽으면 된다. 반대로 디지털을 아날로그로 뱉는 DAC라는 것도 있는데 그건 나중에 얘기하고, 오늘은 읽는 쪽(ADC)만 본다.

ADC를 이해하려면 딱 3가지 용어만 잡으면 된다.

  • 해상도(resolution) : 전압을 몇 단계로 쪼개서 표현하냐. AVR은 보통 10비트라서 0 ~ 1023, 즉 1024단계로 나눈다.
  • 기준전압(Vref) : 측정의 "만점" 기준. 예를 들어 5V로 잡으면, "5V가 곧 1023(만점)"이라는 뜻이다.
  • 변환식 : 실제로 얼마가 나오냐. 디지털값 = (Vin / Vref) × 1023. 거꾸로 전압을 알고 싶으면 Vin = 디지털값 × Vref / 1023.

0 ~ Vref 사이의 전압을 0 ~ 1023 숫자에 나눠 대응시킨다. 전압이 높을수록 값도 커진다.

 

숫자로 감 잡기

말로만 하면 안 와닿으니 숫자를 넣어보자. Vref = 5V, 10비트(0~1023)라고 하자.

  • 한 칸이 몇 볼트냐? → 5V를 1024단계로 나누니까 5V / 1024 ≈ 약 4.9mV. 즉 디지털값이 1 올라갈 때마다 약 4.9mV씩 차이 난다.
  • 0V를 넣으면 → 0
  • 2.5V(절반)를 넣으면 → (2.5 / 5) × 1023 ≈ 512
  • 5V(만점)를 넣으면 → 1023

딱 절반 넣으면 딱 절반 값이 나온다. 처음엔 이게 왜 1024가 아니라 1023을 곱하나 싶어서 좀 헷갈렸는데, 단계는 1024개지만 표현할 수 있는 최댓값(번호)은 0부터 세서 1023이라 그렇다. (0번 칸이 있으니까.)

어떻게 숫자로 바뀌나 — 표본화와 양자화

ADC가 전압을 숫자로 바꾸는 과정은 크게 두 단계다. 이름은 거창한데 뜻은 별거 없다.

  • 표본화(sampling) : 연속으로 흐르는 전압을 일정 간격으로 콕콕 찍는다. 계속 보는 게 아니라 순간순간 사진 찍듯 값을 잡는 거다.
  • 양자화(quantization) : 찍은 값을 정해진 계단값 중 가장 가까운 걸로 반올림한다. 5V/1024 칸 중에 제일 가까운 칸으로 몰아준다.

그래서 아무리 매끈한 아날로그 곡선이라도, ADC를 거치고 나면 계단 모양으로 근사된 값이 나온다. 해상도가 낮으면(계단이 크면) 실제 곡선이랑 좀 어긋나고, 높으면(계단이 촘촘하면) 원본에 가까워진다.

연속 전압을 일정 간격으로 찍어(표본화) 가까운 계단값으로 반올림(양자화)한다. 그래서 계단 근사가 된다.

 

핀은 여러 개, 채널로 고른다

아날로그 입력핀은 보통 하나가 아니다. AVR을 보면 ADC0, ADC1, ADC2… 여러 개가 있다. 그런데 실제 변환기(ADC 회로)는 칩 안에 딱 하나다. 그럼 어떻게 여러 핀을 읽나?

MUX(멀티플렉서)라는 스위치로 "이번엔 ADC0 읽어줘", "다음엔 ADC3 읽어줘" 하고 한 번에 하나씩 골라서 변환기에 연결한다. 그래서 가변저항은 ADC0, 조도센서는 ADC1… 이렇게 꽂아두고 채널만 바꿔가며 돌려 읽으면 된다. 이 채널 선택은 코드에서 레지스터로 지정한다.

AVR 코드로 보자

이제 실제 레지스터를 만져보자. 통신 글들에서 핀을 직접 두드렸던 것처럼, ADC도 결국 레지스터 몇 개 설정 → 변환 시작 → 끝날 때까지 대기 → 결과 읽기 순서다. 관련 레지스터부터 정리한다.

레지스터 하는 일
ADMUX 기준전압 선택(REFS 비트) + 읽을 채널 선택(MUX 비트)
ADCSRA ADC 켜기(ADEN), 변환 시작(ADSC), 클럭 프리스케일러(ADPS)
ADC (ADCH:ADCL) 변환 결과가 담기는 16비트 값. 여기서 최종 0~1023을 읽는다

먼저 초기화다. 기준전압은 AVCC(보통 5V)를 쓰고, 변환 클럭이 너무 빠르면 결과가 부정확해서 프리스케일러를 128로 팍 줄여준다.

void ADC_init(void)
{
    // 기준전압 = AVCC (REFS0 = 1)
    ADMUX = (1 << REFS0);

    // ADC 켜고(ADEN), 프리스케일러 128 (ADPS2:0 = 111)
    ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
}

다음은 실제로 한 채널을 읽는 함수다. 채널 선택 → 변환 시작 → 끝날 때까지 대기 → 결과 리턴, 이 흐름 그대로다.

unsigned int ADC_read(unsigned char ch)
{
    // 하위 4비트만 채널 번호로 갈아끼운다 (상위 비트는 그대로 유지)
    ADMUX = (ADMUX & 0xF0) | (ch & 0x0F);

    // 변환 시작
    ADCSRA |= (1 << ADSC);

    // ADSC가 자동으로 0이 되면 변환 끝 → 그때까지 대기
    while (ADCSRA & (1 << ADSC));

    return ADC;   // 0 ~ 1023
}

포인트는 while (ADCSRA & (1 << ADSC)); 이 한 줄이다. 변환을 시작하면 ADSC 비트가 1이 되고, 변환이 끝나면 하드웨어가 알아서 0으로 내려준다. 그래서 0이 될 때까지 기다리기만 하면 된다. I2C/SPI에서 클럭 굴리며 비트 세던 것보다 오히려 편하다.

마지막에 그냥 ADC라고 쓰면 결과 16비트를 통째로 읽어준다. 컴파일러가 ADCL, ADCH를 알아서 합쳐주는 거다.

주의 : 만약 ADCL, ADCH직접 따로 읽는다면 반드시 ADCL(하위)을 먼저, ADCH(상위)를 나중에 읽어야 한다. 순서를 바꾸면 값이 갱신되며 꼬인다. ADC로 한 번에 읽으면 이 걱정은 없다.

실제로 써먹으면

읽은 0~1023 값 하나로 할 수 있는 게 은근 많다.

  • 가변저항 : 돌린 만큼 0~1023이 바뀌니 볼륨 노브, 밝기 조절 같은 데 쓴다.
  • 조도센서(CDS) : 밝으면 저항이 변해서 전압이 바뀐다 → 밝기 측정.
  • 온도센서 : 온도에 비례하는 전압을 뱉는 센서를 읽어서 온도로 환산.

이걸 응용하면 "어두워지면 LED 켜기", "가변저항으로 모터 속도 조절" 같은 게 다 ADC 값 하나에서 출발한다. 값을 그대로 쓰거나, 아까 그 변환식으로 실제 전압/물리량으로 바꿔 쓰면 된다.

void main(void)
{
    unsigned int val;

    ADC_init();

    while (1)
    {
        val = ADC_read(0);      // ADC0 채널(예: 가변저항) 읽기

        if (val > 512) LED_ON;  // 절반(약 2.5V) 넘으면 LED 켜기
        else           LED_OFF;
    }
}

정리하면

  • ADC = 아날로그 전압을 디지털 숫자로 바꾸는 변환기. 센서값을 읽으려면 필수다.
  • 핵심 3가지 : 해상도(AVR 10비트=0~1023), 기준전압(Vref), 변환식(디지털값 = Vin/Vref × 1023).
  • 과정은 표본화(일정 간격으로 찍기) + 양자화(가까운 계단값으로 반올림). 그래서 계단 근사가 된다.
  • 여러 핀은 MUX로 채널을 골라 하나씩 읽는다.
  • 코드는 ADMUX로 기준전압/채널, ADCSRA로 켜고 시작, ADSC가 0되면 끝, 결과는 ADC에서 읽기.

통신 3종(UART/I2C/SPI) 정리하고 나서 아날로그까지 오니까, 이제 센서 웬만한 건 다 읽을 수 있게 됐다. 읽는 건 이걸로 됐으니, 다음엔 반대로 아날로그처럼 출력하는 PWM을 정리해봐야겠다. (LED 밝기 조절이나 모터 속도, 이게 다 PWM이더라.)

반응형