이번 글에서는 ADC의 개념과 ATmega128에서의 ADC 특징(여러 모드들과 인터럽트), ADC를 사용하기 위한 레지스터에 대해서 공부한 내용들을 정리해볼 예정이다.
공부한 내용들을 바탕으로 가변 저항을 이용해서 아날로그 값을 디지털 값으로 바꾸어 범위 별로 LED를 On/Off하고, LCD에 값을 띄워보았다. 이 때, 인터럽트를 사용했다.
ADC에 대해 공부하기 전에 Analog
와 Digital
이 무엇인지에 대해 먼저 공부해보았다.
아날로그란, 어떤 양 또는 데이터를 연속적으로 변환하는 물리량을 표현하는 것이며, 곡선의 형태로 정보가 전달된다.
디지털이란, 어떤 양 또는 데이터를 2진수로 표현하는 것이며, 1과 0이라는 숫자를 통해 정보가 전달된다.
Analog to Digital Converter
ADC란, 연속적인 analog
신호를 0과 1로 구성 된 digital
신호로 변환하는 것이다.
ADC는 크게 3가지 과정을 거친다.
연속적인 아날로그 신호를 시간 축을 따라 이산 값으로 변환한다.
- 진폭 축(Y축)을 따라 이산 값으로 변환한다.
- 양자화 레벨로 간격을 나누어 뽑은 표본 값을 미리 정해진 값에서 가장 가까운 값으로 변환한다.
표본화와 양자화를 통해 나온 숫자들을 2진수로 바꿔준다.
- 8개의 단극성 입력이 있음 = 8채널이 존재
- 10비트의 분해능이 있음
분해능
: 아날로그 변화 값을 표시할 수 있는 단계의 수
10비트
= 2의 10승 = 1024 ➡ 1024의 입력값이 단계로 이루어짐, 분해능이 높을수록 정교- 샘플/홀드 회로가 있어 변환 도중에 전압이 고정됨
- 8개의 단극성 입력과 22종류의 차동(위상 반대) 입력을 할 수 있음
- V_ref의 값 = AVCC 또는 내부 2.56V 또는 AREF 중 선택
- PORTF는 아날로그 비교기 기능으로 사용 가능
- 변환 시간 : 65us~260us, 50kHz~200kHz
ATmega128 ADC 모드에는 Single Conversion Mode(단일 변환 모드)와 Free Running Mode(자유 동작 모드, 연속 변환 모드)가 있다.
- 한 번씩 변환하는 모드
- ADSCRA 레지스터의 ADSC = 1로 설정함으로써 시작되고, 변환이 끝나면 0이 된 후 인터럽트 발생
- AD 변환 중 입력 채널이 바뀌었다면 ADC는 현재의 변환을 마치고 새로 선택 된 채널로 변경
- 계속 변환하지 않고 한 번씩만 변환하므로 연속 변환모드에 비해 소비 전력이 적음
- 연속해서 변환하는 모드
- ADSCRA 레지스터의 ADFR = 1로 설정함으로써 ADC는 지속적으로 샘플링과 변환을 수행하여 ADC 데이터 레지스터를 갱신
- 계속 변환하므로 입력 값에 대해 신뢰성이 단일 변환 모드보다 높음
ADC 관련 레지스터는 3가지로 각 레지스터의 bit들에 대해서는 datasheet를 통해 확인할 수 있다. bit들의 역할 같은 경우 세미나 했을 때 정리했던 PPT 내용을 사진으로 가져와보았다.
A/D 변환을 위한 기준 전압과 입력 채널을 선택하기 위해 사용
AD 변환 상태를 나타내거나 과정을 제어하기 위해 사용
- ADC의 변환 결과가 ADCH, ADCL 레지스터에 나누어 저장됨
- 10비트 분해능을 가지기 때문에 변환 결과를 저장하기 위해 10비트 필요
- ADLAR = 0 으로 설정 시 오른쪽 정렬 / ADLAR = 1로 설정 시 왼쪽 정렬
- ADCL 레지스터를 읽은 후 ADCH를 읽어야 함
- ADCL 레지스터에서 데이터를 읽으면, ADC가 데이터 레지스터에 접근할 수 없도록 차단되며, ADCH 레지스터 값까지 모두 읽고 나야 다시 ADC가 데이터 레지스터에 접근할 수 있음
#define F_CPU 16000000UL //16MHz
#include <stdio.h> //sprintf 사용 위한 헤더파일
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "lcd.h"
void init_port(); //port 초기설정 함수
void init_reg(); //레지스터 초기설정 함수
volatile unsigned int adc_result; //ADC 결과 값(디지털 값)
volatile float v_in; //아날로그 값
ISR(ADC_vect) //ADC 인터럽트 서비스 루틴
{
adc_result = ADC; //ADC 결과 값 저장
v_in = (float)ADC * 5.0 / 1024.0; //아날로그 값 저장
}
void init_port(void)
{
DDRA = 0xff; //PORTA(LED) 출력으로 설정
DDRC = 0xff; //PORTC(LCD) 출력으로 설정
DDRE = 0xe0; //PE5~7(LCD) 출력으로 설정
DDRF = 0xfe; //조도센서(PF0(ADC0)) 입력
PORTA = 0xff; //led 다 끈 상태에서 시작
_delay_ms(1000); //1초 지연
}
void init_reg(void)
{
ADMUX = (1 << REFS0) | (0 << ADLAR); //기준전압 AVCC, 오른쪽 정렬 이용
ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
//ADC 사용 on, 분주비 128
}
50kHz~200kHz 사이에서 동작하므로 128 분주비를 사용해주었다.
int main(void)
{
char adcResult[16]; //ADC결과 값(디지털 값) 저장 위한 문자형 변수
char voltage[16]; //ADC이전 값(아날로그 값) 저장 위한 문자형 변수
char analog[16]; //lcd에 'analog:' 출력 위한 문자형 변수 (없어도 됨)
char digital[16]; //lcd에 'digital:' 출력 위한 문자형 변수 (없어도 됨)
int level; //PORTA 비트 이동을 위한 변수(디지털 변환했을 때 단계)
init_port(); //port 초기 설정 함수
init_reg(); //레지스터 초기 설정 함수
sei(); //SREG 7번비트 1로 세트
lcd_init(); //lcd화면 초기화
cursor_off(); //커서 안 보이게
while (1)
{
ADCSRA |= (1 << ADSC); //ADC 변환 시작
while(ADCSRA & (1 << ADIF) == 0); //ADIF 비트가 1로 세트되었는지 안되었는지 확인 가능
sprintf(voltage, "%.2fV", v_in); //아날로그 전압 값 voltage 변수에 할당
sprintf(adcResult, "%d", adc_result); //변환 된 디지털 값 adcResult 변수에 할당
sprintf(analog, "analog :"); //"analog :"를 analog 변수에 할당
sprintf(digital, "digital:"); //"digital:"을 digital 변수에 할당
//lcd 출력
lcd_gotoxy(0,1); lcd_puts(analog);
lcd_gotoxy(9,1); lcd_puts(voltage);
lcd_gotoxy(0,0); lcd_puts(digital);
lcd_gotoxy(9,0); lcd_puts(adcResult);
_delay_ms(500);
lcd_clear();
level = adc_result/128; //8단계로 나누어서 출력하므로 디지털 값/128
PORTA = 0xff << level; //나온 값만큼 비트 이동을 시켜주어 LED 출력
}
}