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.
숫자로 감 잡기
말로만 하면 안 와닿으니 숫자를 넣어보자. 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이더라.)
'프로그래밍 > 마이컴' 카테고리의 다른 글
| 타이머/인터럽트 (0) | 2026.07.19 |
|---|---|
| PWM (펄스폭 변조) (0) | 2026.07.19 |
| UART 통신 (0) | 2026.07.19 |
| SPI 통신 (0) | 2026.07.19 |
| ATMEGA32L External Interrupt (0) | 2013.08.14 |
