[5호]Advanced Virtual Risk 정복하기!
제1부
개요
AVR은 Atmel사에서 1997년에 개발한 8비트 제어용 마이크로프로세서로서, 프로그램 메모리와 데이터 메모리를 액세스하기 위한 버스를 독립적으로 사용하는 하버드 아키텍처와 파이프라인 처리 방식을 기반으로 하는 Risk기술을 적용하여 높은 성능을 발휘하는 것만 아니라 플래시 메모리를 생산하던 Atmel사의 장점을 살려 칩내에 플래시 메모리를 내장하고 여기에 사용자가 프로그램을 쉽게 다운로드 할 수 있는 ISP방식을 적용하였다.
우리는 AVR에서도 가장 규모가 크고 성능이 높은 응용분야에 사용되는 ATmega 패밀리 중에 ATmega 128을 사용하여 개발한 LK EMBEDDED사의 LK ATmega 128 교육용 KIT의 하드웨어를 기반으로 공부하게 될 것이다.
입출력 포트(I/O PORT)
마이크로프로세서(CPU)의 기능 중에서 가장 기본이 되는 기능은 주변장치와 서로 신호를 주고(Output) 받을(Input)수 있는 입출력(I/O)이다. 마이크로프로세서에서 입출력 (I/O)을 제어하기란 매우 쉽다.
먼저 신호를 출력(Output)할 것인지 입력(Input)할 것인지 제어할 신호의 방향부터 결정한 후 방향에 따라 신호를 출력하거나 입력 받으면 된다. 여러분의 이해를 돕기 위해 그림을 이용하여 이해해보자.
그림-1에서 빨간색으로 되어 있는 부분을 Tri-State Buffer라고 부른다. 이 버퍼에 보이는 1, 2, 3신호 선들로 구성되어 있고 동작은 버퍼 안에 어떤 신호가 입력되느냐에 따라 달라지게 된다.
그림-2을 보면 버퍼의 1 신호 선에 High = 1 이 흐르게 되면 버퍼가 연결되어 2 신호 값이 I/O핀을 통해 외부로 빠져나가게 되며 출력 장치 등을 제어할 수 있게 된다.
그림-3을 보면 반대로 3 신호 선에 Low = 0이 흐르게 되었을 경우 버퍼가 닫히게 되어 외부의 입력 장치로부터 신호의 입력을 받을 수 있게 되는 것이다.
그림-4의 박스 안에 들어 있는 이름들은 실제로 AVR에서 사용되는 레지스터들의 이름이다. 위의 설명에도 나와 있듯이 DDR 레지스터의 방향에 의해서 출력과 입력이 결정되는 것을 알 수 있었다. 이해를 돕기 위해 DDR 레지스터의 특징과 실제 예를 들어 설명해 보겠다.
※ 참고사항 ※
여기서 주의해야 할점은 Tri State Buffer 신호의 방향성이다. 만약 버퍼의 흐르는 신호에 방향성이 없다면 거꾸로 입출력 핀을 통해 역으로 신호가 들어갈 수도 있기 때문이다. 이를 막기 위해 버퍼에 흐르는 신호는 일정한 방향성이 있다. 쉽게 설명하면 도로에 그어진 일방통행 표시처럼 삼각형 꼭지점 방향으로 한 방향으로 신호가 흐르게 된다.
DDR 레지스터의 특징
1) 데이터의 입, 출력을 결정해주는 레지스터
2) 각 포트의 각각의 비트마다 제어 가능
3) 입력/출력 포트로 설정된 핀은 해당 포트로만 사용될 수 있음
4) 1 : 출력(Output), 0 : 입력(Input)
예) PORTB의 PB7~PB4는 출력으로 PB3~PB0는 입력으로 설정
DDRB=0XF0; // PB7 BIT ~ PB4 BIT (Output) PB3 BIT ~ PB0 BIT (Input)
우리는 위와 같이 입력과 출력을 설정 할 수 있는 I/O PORT에 대해 이해 할 수 있었다. 자 그럼 이제 실제 코드에 적용해 보자.
CodeVision AvR C컴파일러
LK ATmega 128 예제소스1 : DDR 레지스터를 활용한 LED 동작하기
//예제 소스 시작
#include <mega128.h>
#include <stdio.h>
#include <delay.h>
void main(void)
{
DDRE=0XFF; //LED 출력 레지스터 설정
PORTE=0X00; //LED PORT 초기화
DDRG=0XFF; //BUZZER 출력 레지스터 설정
PORTG=0X01; //BUZZER PORT 초기화
while (1)
{
PORTE=0XFF; //LED ON
delay_ms(1000); //딜레이 1초
PORTE=0X00; //LED OFF
delay_ms(1000); //딜레이 1초
PORTG=0X00; //BUZZER ON
delay_ms(1000); //딜레이 1초
PORTG=0X01; //BUZZER OFF
delay_ms(1000); //딜레이 1초
};
}
//예제 소스 끝
1) MAIN 함수에서 DDR 레지스터와 PORT의 초기값을 결정해준다.
2) LED 점등
3) 1초 후에 LED 소등
4) 1초 후에 BUZZER 동작
5) 1초 후에 BUZZER 동작중지
6) 2~5번의 동작을 계속 반복한다.
※ 참고사항 ※
위의 그림은 LK ATmega 128 교육용 Kit의 회로도 이다.
LED에는 풀다운 저항이 설계되어 있고 BUZZER에는 반대로 풀업 저항이 설계 되어 있다. 우리 MCU에서 출력을 어떻게 해야 할까?
LED에는 MCU에서 HIGH를 출력으로 내보내야만 전위차에 의해서 LED가 작동되는 것을 알 수 있고 BUZZER에는 MCU에서 LOW를 출력으로 내보내야만 전위차가 발생되어 BUZZER가 동작 될 수 있는 것을 알 수 있다. 위의 소스를 이해하는데 참고하길 바란다.
1) Delay_ms(1000) 이 값의 의미는 ms 단위 시간에 1000이라는 데이터가 대입되는 것이다. 즉 1초라는 말이다. 순차적으로 시간을 제어 할 수 있다는 것은 프로그램 작성 하는데 있어서 없어서는 안될 중요한 요소다. 하지만 delay 함수를 사용할 때 주의할 점은 MCU가 정지되는 것에 있다. 가급적이면 사용하지 않는 것이 올바른 프로그램 작성이라고 할 수 있겠다. 그러면 우린 시간을 제어 할 수 없다는 말인가? 그렇지 않다. ATmega 128에서는 Timer interrupt라는 고성능에 타이머 제어 인터럽트 서비스가 존재 하기 때문이다.
Timer interrupt는 우리가 I/O 제어를 원활하게 하게 되었을 때 학습하게 될 것이다.
CodeVision AvR C컴파일러
LK ATmega 128 예제소스2 : BUTTON을 이용한 LED동작하기
#include <mega128.h>
#include <stdio.h>
#include <delay.h>
void main(void)
{
DDRE=0XFF; //LED 출력 레지스터 설정
PORTE=0X00; //LED PORT 초기화
DDRD=0X00; //BUTTON 입력 레지스터 설정
while (1)
{
if(!PIND.3){ //SW1 ON
delay_ms(10);
if(PIND.3)
PORTE=0X0c; //LED 1,2ON
}
if(!PIND.4){ //SW2 ON
delay_ms(10);
if(PIND.4)
PORTE=0X30; //LED 3,4ON
}
if(!PIND.5){ //SW3 ON
delay_ms(10);
if(PIND.5)
PORTE=0Xc0; //LED 5,6ON
}
if(!PIND.6){ //SW4 ON
PORTE=0X00; //LED OFF
}
};
}
//예제소스 끝
1) DDRE을 출력으로 DDRD을 입력으로 레지스터 설정
2) 스위치 1을 눌렀을 경우 LED 1번과 2번이 점등
3) 스위치 2을 눌렀을 경우 LED 3번과 4번이 점등
4) 스위치 3을 눌렀을 경우 LED 5번과 6번이 점등
5) 스위치 4을 눌렀을 경우 LED 소등
위의 그림은 LK ATmega 128 교육용 Kit의 회로도 이다.
스위치 입력의 신호를 확실하게 규명 짓기 위하여 풀업 저항을 이용하여 평소에는 항상 HIGH의 신호를 입력하고 있으며 스위치 동작시에맊LOW의 신호를 입력 받을 수 있게 설계 되어 있다. 여기서 중요한 것은 위의 하드웨어 특성이다. 풀업저항, 풀다운저항을 사용하지 않고 스위치를 MCU에 연결했을 경우를 우리는 플로팅 상태라고 한다. 플로팅 상태란 간단하게 설명하면 MCU 입장에서 1도 아닌 0도 아닌 어중간한 상태를 뜻한다. 즉 스위치를 누르지도 않았는데 동작 할 수도 있다는 이야기이다. 그러므로 항상 플로팅 상태를 피하기 위해서 풀업저항 또는 풀다운저항이 설계되어야 한다.
제2부
개요
ATmega 128은 4개의 범용 타이머/카운터가 있다. 종류로는 Timer/Counter0 (8비트), Timer/Counter1(16비트), Timer/Counter2(8비트), Timer/Counter3(16비트)가 있다.
타이머/카운터의 제어에 필요한 레지스터는 타이머/카운터 제어 레지스터(TCCRn), 타이머/카운터 레지스터(TCNTn), 출력 비교 레지스터(OCRn)이 있으며 인터럽트 관련하여 인터럽트 플래그 레지스터(TIFR), 인터럽트 마스크 레지스터(TIMSK)가 있다.
사실 타이머/카운터 레지스터에는 인터럽트 및 PWM 출력 기능을 가지고 있다. 인터럽트는 카운터의 값이 오버플로우 되는 순간에 발생하는 오버플로우 인터럽트, 카운터 값이 출력비교 레지스터의 값과 같게 되는 순간에 발생하는 출력비교 인터럽트, 입력캡쳐 인터럽트등이 있는데 우리는 그 중에 오버플로우 인터럽트를 이용하여 타이머 기능을 구현 할 것이다. 그리고 이 강좌에서는 Timer/Counter0 (8비트)를 사용하여 것이며 CodeVision AvR 컴파일러로 관련 레지스터 설정하는 방법을 통해 강의를 진행할 것이다.
OVERFLOW INTERRUPT
AVR은 두 가지 모드의 타이머/카운터 인터럽트를 지원하는데 출력비교 인터럽트와 오버플로우 인터럽트가 그것이다.
그럼 이 인터럽트가 어떻게 동작되는지 해당 레지스터를 참조해보자.
BIT1의 OCF0는 TNCT와 OCR 레지스터의 값을 비교하여 이것이 같으면 이 비트가 1로 SET 되면서 출력비교 인터럽트가 요청된다.
이 인터럽트는 처리가 시작되면 자동으로 0으로 CLEAR 된다.
BIT0의 TOV0는 오버플로우가 발생되면 BIT1로 SET 되면서 오버플로우 인터럽트가 요청된다. 이 인터럽트도 인터럽트 처리가 시작되면 자동으로 0으로 CLEAR된다.
PRESCALER
프리스케일러란 타이머에 공급되는 입력 클럭의 속도를 제어하는 분주기이다. 분주기는 주기를 나눈다라는 의미가 있습니다.
즉 주기를 나눈다는 말은 클럭의 속도를 느리게 한다는 의미와 같다. 예를 들면 16Mhz를 4분주 한다면 4Mhz가 되는 것이다.
TIMER/COUNTER 설정
타이머를 사용하기 위해서는 타이머에서 사용하는 클럭에 대해서 설정을 해야 하는데 이는 프리스케일러(Prescaler) 값으로 조절할 수 있다. 프리스케일러 값은 각 타이머의 컨트롤 레지스터(TCCRn)에서 설정할 수가 있다.
타이머 인터럽트는 각 타이머 관련 컨트롤 레지스터에서 적절한 프리스케일러 값을 설정한 후, 각 타이머 레지스터(TCNTn)에 설정하고 싶은 시간에 대해 인터럽트를 발생시킬 것인지에 대해 관련된 값을 설정해 주면 된다. 그리고 인터럽트를 허용해야 하므로 관련 레지스터인 TIMSK 레지스터를 설정해 주어야 한다.
레지스터를 어떻게 설정해야 원하는 시간을 얻을 수 있을까? 관련 레지스터 설정에 대하여 수식과 왼쪽그림을 통하여 확인해 보자.
[그림-1], [그림-2], [그림-3]을 참조해 보면 CCR0 레지스터의 0~2번 BIT을 사용하여 Prescaler 설정 할 수 있으며 TCNT0 레지스터를 사용하여 시간을 설정할 수 있다. 그리고 TIMSK 레지스터의 TOIE0 BIT을 활성화 해야지만 TIMER0 레지스터의 인터럽트를 허용하겠다는 설정이므로 꼭 기억해야 한다.
위의 레지스터 값을 수식에 대입하여 이해해보자.
좀 더 자세히 수식을 표현해 보겠다.
위의 수식을 보면 TIME(원하는 시간)은 XTAL과 Prescaler 그리고 N값을 통해 구해진다. XTAL은 발진 소자의 발진 데이터를 말하며 프리스케일러 데이터는 위에서도 언급했지만 TCCR 레지스터의 의해서 구해진다. 그리고 N은 TNCT 레지스터의 의해서 설정되는 값이다. 이해가 안 되는 분들을 위해서 예를 들어 설명해 보겠다.
위의 수식처럼 1ms을 구하기 위하여 XTAL은 16Mhz 발진 소자를 사용하였고 TCCR= 0X04, TCNT=0X06로 레지스터를 설정한 결과이다. 우리는 위와 같이 타이머/카운터 레지스터에 대해 이해 할 수 있었다. 자 그럼 이제 실제 코드에 적용해 보자.
CodeVision AvR C컴파일러
LK ATmega 128 예제소스3 : TIMER/COUNTER 레지스터를 활용한 LED 동작하기
//예제소스 시작
#include <mega128.h>
#include <stdio.h>
int cnt; //카운터 변수 선얶
interrupt [TIM0_OVF] void timer0_ovf_isr(void) //TIMER 0 인터럽트 서비스 루틴
{
TCCR0=0×04;
TCNT0=0×06;
cnt++; //1ms 마다 카운터 변수 증가
if(cnt > 7000){ //7초 후에 LED OFF
PORTE=0X00; //카운터 변수 초기화= 반복 동작
cnt=0;
}
else if(cnt > 6000) //6초 후에 LED6 ON
PORTE=0X80;
else if(cnt > 5000) //5초 후에 LED5 ON
PORTE=0X40;
else if(cnt > 4000) //4초 후에 LED4 ON
PORTE=0X20;
else if(cnt > 3000) //3초 후에 LED3 ON
PORTE=0X10;
else if(cnt > 2000) //2초 후에 LED2 ON
PORTE=0X08;
else if(cnt > 1000) //1초 후에 LED1 ON
PORTE=0X04;
}
void main(void)
{
DDRE=0XFF; //LED 출력 레지스터 설정
PORTE=0X00; //LED PORT 초기화
TCCR0=0×04; //Prescaler 64 설정
TCNT0=0×06; //N값 설정
TIMSK=0×01; //TIMER 0 오버 인터럽트 허용
#asm(“sei”)
while (1)
{
};
}
※ 프로그램 해석 ※
1) DDRE 레지스터를 이용하여 LED 출력 PORT로 변경
2) TCCR0과 TCNT0을 이용하여 시간을 설정
3) TIMSK 레지스터를 이용하여 타이머 0 오버 인터럽트를 허용
4) 카운터 변수 int cnt를 선언
5) 타이머 서비스루틴은 TCCR0과 TCNT0 값의 의해 1ms로 설정 되어 인터럽트가 발생 될 때 마다 호출된다.
6) cnt(카운터변수)를 증가 처리 했으므로 1ms마다 카운터는 1씩 증가
7) if문 조건에 따라 1초마다 LED가 순차적으로 점등 소등 된다.
8) 7초 후에 마지막 LED가 소등되며 카운터 변수는 리셋된다.
9) 4~8번 순서대로 동작을 반복한다.
★FLOWCHART
※참고사항 ※
1. 카운터 변수의 자료 형은 int형인데 그 이유는 무엇일까?
카운터 변수의 자료 형을 int형을 선택한 이유는 물론 정수형의 숫자를 사용하는 이유도 있지만 char형이라고 정수형의 데이터를 처리 못하는 것은 아니다. 그러나 char형의 크기는 1byte이고 int형은 2byte 이므로 사용할 수 있는 수의 범위가 틀리다.
1byte= 8bit이다. 즉, 10진수로 256의 데이터 수를 갖는다.
그럼 2byte는 16bit 이므로 256 X 256 즉, 65536의 데이터 수를 갖는 것이다.
Cnt 변수의 증가 범위를 보면 0~7000이다. char형을 사용했을 경우 조건이 성립 될 수가 없다.
2. 카운터 변수를 초기화 하지 않을 때 동작은?
int형은 최대 65536이란 데이터의 수를 갖고 있다고 말했다.
카운터 변수는 1ms 마다 증가하고 그 값을 초기화 하지 않는다고 가정했을 때 7초까지는 프로그램 작성자가 원하는 대로 동작을 한다. 그러나 그 이후에는 카운터 변수의 데이터는 0~65535까지 증가하고 난 후에야 처음으로 복귀하는 것이다.
항상 변수나 함수를 선택하고 정의하는 부분은 프로그램 작성자가 생각해야 할 부분이므로 의미 있게 체크해두자.
제3부
EXTERNAL INTERRUPT
REQUEST(외부 인터럽트)강좌
개요
인터럽트를 해석하면 “끼어든다”는 의미를 가지고 있다. 프로그램상으로 해석한다면 MCU가 동작을 중지하고 인터럽트를 수행한다고 생각하면 된다.
인터럽트는 크게 내부 인터럽트와 외부 인터럽트로 구분하며 다음과 같은 특징을 가지고 있다.
1) 내부 인터럽트의 예는 전 강의 주제인 타이머 인터럽트가 대표적이다. 전 강의에서 우리는 오버플로우을 이용하여 주기적으로 인터럽트를 발생 시켰고 그로 인해 시간을 제어 할 수 있었다.
2) 외부 인터럽트는 사용자의 의해서 들어오는 신호들을 이용하여 인터럽트를 발생 시키는 방식이다. 예를 들면 외부에서 스위치 및 센서 등을 이용하여 MCU로 신호를 발생 시켰을 때 인터럽트가 걸리는 방식이라고 생각하면 되겠다.
3) ATmega 128은 8개의 외부 핀 INT0~7을 통해 입력되는 신호에 의해서 인터럽트를 발생 시킬 수 있다.
4) 외부 인터럽트는 L의 논리레벨(low level), 하강 에지(falling edge), 상승 에지(rising edge)에 의해 트리거(trigger) 될 수 있는데 이러한 인터럽트 트리거 방법은 외부 인터럽트 제어 레지스터의 설정에 의하여 결정된다.
외부 인터럽트 레지스터 설정
외부 인터럽트를 사용하기 위해서는 ECIRA, ECIRB, EIMSK, EIFR 레지스터 설정에 의해서 사용 여부가 결정 된다.
그 내용들을 공부해 보자.
ECIRB, ECIRA 레지스터
ECIR 레지스터는 2개로 구분되는데 EICRA는 INT0~INT3핀을 제어하고 EICRB는 INT4~INT7을 제어한다. 위 2개의 레지스터의 의해서 트리거(trigger)방식을 결정하게 되는데 이해를 돕기 위해 그림을 통하여 학습해보자.
1) L 논리레벨(low level)트리거 방식 : 해당 핀의 L상태가 입력되는 동안 인터럽트가 발생되는 방식이다.
2) 하강 에지(falling edge)트리거 방식 : 클럭에 관계없이 비동기 적으로 신호가 검출 되며 말 그대로 논리 레벨이 HIGH에서 LOW로 하강하는 순서에 인터럽트가 발생하는 방식이다.
3) 상승 에지(rising edge)트리거 방식 : 클럭에 관계없이 비동기 적으로 신호가 검출되며 논리 레벨이 LOW에서 HIGH로 상승하는 순서에 인터럽트가 발생하는 방식이다.
4) 해당 핀을 설정하는 방법는 ISCn1, ISCn0 BIT를 [그림-3]의 방식으로 설정하면 되며 여기서 n은 해당 핀의 따라 달라지게 된다.
EIMSK 레지스터
EIMSK 레지스터는 INT0~7핀의 허용을 결정하는데 사용된다.
설정 방법으로는 해당 BIT를 1로 설정하면 인터럽트 허용, 0으로 설정하면 인터럽트 사용금지가 된다.
EIFR 레지스터
EIFR 레지스터는 INT0~INT7 핀의 입력이 발생되면 해당 인터럽트에 플래그가 발생 되었음을 표시하는데 사용되며 이 비트들은 인터럽트 발생시 1로 SET되고 인터럽트 서비스 루틴으로 진행하게 되면 0로 CLEAR된다.
우리는 위와 같이 외부 인터럽트 레지스터에 대해 이해 할 수 있었다. 자 그럼 이제 실제 코드에 적용해 보자.
CodeVision AvR C컴파일러
LK ATmega 128 예제소스4 : 외부 인터럽트 레지스터를 활용한 LED 동작하기
#include <mega128.h>
#include <stdio.h>
#include <delay.h>
char cnt; //카운터 변수 선언
interrupt [EXT_INT2] void ext_int2_isr(void) //외부 인터럽트0 서비스 루틴
{
cnt++; //외부 인터럽트 발생시 카운터 증가
}
void main(void)
{
DDRE=0XFF; //LED 포트 출력 레지스터 설정
PORTE=0X00; //LED 초기 OFF
EICRA=0×20; //외부 인터럽트 2 FALLING EDGE 설정
EIMSK=0×04; //외부 인터럽트 2 허용
EIFR=0×04;
#asm(“sei”)
while (1)
{
if(cnt==1) //카운터 1일 때 LED1 ON
PORTE=0X04;
else if(cnt==2) //카운터 2일 때 LED2 ON
PORTE=0X08;
else if(cnt==3) //카운터 3일 때 LED3 ON
PORTE=0X10;
else if(cnt==4) //카운터 4일 때 LED4 ON
PORTE=0X20;
else if(cnt==5) //카운터 5일 때 LED5 ON
PORTE=0X40;
else if(cnt==6) //카운터 6일 때 LED6 ON
PORTE=0X80;
else if(cnt==7){ //카운터 7일 때 LED OFF
PORTE=0X00;
cnt=0; //카운터 초기화
}
};
}
//예제소스 끝
1) 외부 인터럽트 2번 사용설정 및 하강에지 인터럽트 레지스터 설정
2) 외부에서 인터럽트가 발생할 때 카운터는 1씩 증가
3) 카운터가 증가 될 때 마다 LED 순차적으로 점등
4) 카운터 데이터 7이 되면 카운터 변수 RESET 및 LED 소등 된다.
5) 2~4을 반복된다.
★ FLOWCHART
※ 참고사항 ※
ATmega 128에서는 RESET을 포함하여 총 35종류의 인터럽트를 가지고 있으며 인터럽트는 MCU의 의지에 따라 순차적으로 제어 되는 것이 아니라 비동기적으로 발생 되는 것이 일반적이므로 우연히 2개 이상의 인터럽트 요청이 중복 되는 경우가 있을 수 있다.
MCU는 인터럽트를 한꺼번에 처리할 수가 없으므로 한번에 1개의 인터럽트를 선택하여 처리하게 되는데 이를 인터럽트 우선 순위 제어라고 한다.
ATmega 128은 인터럽트의 중복을 막기 위하여 인터럽트의 어드레스 번지의 0×0000 ~ 0×0044까지 순차적으로 인터럽트를 처리하게 되어있다. 그리고 35종류의 인터럽트는 각각의 마스크 레지스터를 이용하여 개별적으로 허용여부를 결정할 수 있고 상태 레지스터인 SREG 글로벌 인터럽트를 이용하여 전체적인 허용 여부를 설정할 수 있다.
제4부
UART(Universal Asynchronous Receiver and Transmitter)강좌
개요
ATmega 128은 2개의 UART 직렬통신 포트를 가지고 있는데 비 동기 전송 모드에서 전이중 통신이 가능하고 멀티프로세서 통신 모드동작도 가능하다.
UART기능을 사용하기 위해서는 UDRn, UCSRnA, UCSRnB, UCSRnC 레지스터를 이용하여 설정하여야 한다. 하지만 UART 레지스터의 기능들을 초보자가 전부 소화하는 것은 쉽지 않으므로 필요한 부분만을 참조하여 설명하겠다.
1) UART 직렬 통신 포트에서는 송신완료, 송신데이터 준비완료, 수신완료 3가지의 인터럽트를 사용할 수 있으며 내부 구성으로는 클럭 발생부, 송신부, 수신부의 구성으로 나누어 있다.
2) 높은 정밀도의 보레이트 발생기를 내장하고 있다.
3) 비동기식 전송 모드에서는 항상 내부의 클럭에 의하여 보레이트가 결정된다.
※ 참고사항 ※
LK 개발보드에는 CP2012 IC가 내장되어 있어 TTL0레벨의 통신신호를 USB레벨의 통신신호로 변환 해주어 PC와 통신이 가능하게 해주고 있다. 이해를 돕기 위해 회로도를 첨부 하겠다.
★ 회로도
UDRn 레지스터
UDRn레지스터는 UART 포트의 송, 수신 데이터 버퍼의 기능을 가지고 있다.
[그림-1]을 참조하면 UDRn레지스터는 동일한 메모리 번지에 위치하지만 내부적으로는 서로 다른 레지스터로서, 송신데이터를 UDRn에 라이트하면 이는 송신 데이터 버퍼 TXBn에 저장되고, 수신된 데이터를 UDRn으로 읽으면 수신 데이터 버퍼 RXBn에 수신되어 값을 읽을 수 있게 된다.그리고 송신 버퍼는 UCSRnA 레지스터의 UDREn 플래그 비트가 1로 설정 되어야만 데이터를 라이트 할 수 있으며 UDREn 플래그 비트가 0으로 되어 있는 경우에는 UDRn에 데이터를 라이트 하더라도 무시 처리가 된다.
UCSRnA 레지스터
UCSRnA 레지스터는 UART 포트의 송수신 동작을 제어하거나 송수신 상태를 저장하는 기능을 가지고 있다.
1) RXCn : MCU가 수신 버퍼에 읽혀지지 않은 수신 문자가 들어 있으면 1로 SET되고 MCU가 수신 버퍼의 데이터를 다 읽은 상태이면 0으로 CLEAR되는 플래그 비트
2) TXCn : 송신 시프트 레지스터에 있는 송신 데이터가 모두 송신되고 UDRn의 송신 버퍼에 새로운 데이터가 라이트 되지 않은 상태이면 1로 SET되는 상태 플래그 비트
3) UDREn : UDRn의 송신 버퍼가 비어 있어 새로운 송신 데이터를 받을 준비가 되면 1로 SET되는 상태 플래그 비트
UCSRnB 레지스터
UCSRnB 레지스터는 UART 포트의 송수신 동작을 제어하거나 전송 데이터를 9비트로 설정한 경우에 9번째 비트 데이터를 저장하는 기능을 가지고 있다.
1) RXCIEn : 수신완료 인터럽트를 개별적으로 허용하는 비트.
2) TXCIEn : 송신완료 인터럽트를 개별적으로 허용하는 비트.
3) UDRIEn : 송신 데이터 준비완료 인터럽트를 개별적으로 허용하는 비트.
4) RXENn : USART 포트의 수신부가 동작하도록 허용하는 비트
이 비트는 RXDn 핀이 I/O포트가 아니라 직렬 통신 데이터 수신 단자로 동작 하도록 설정하며 에러 플래그 비트의 동작을 유효하게 한다.
5) TXENn : UART 포트의 송신부가 동작하도록 허용하는 비트.
6) UCSZn2 : 전송 문자의 데이터 비트 수를 설정하는데 사용.
7) RXB8n : 전송 문자가 9비트로 설정된 경우에 수신된 문자의 9번째 비트의 데이터를 저장한다.
8) TXB8n : 전송 문자가 9비트로 설정된 경우에 송신할 문자의 9번째 비트의 데이터를 저장.
UCSRnC 레지스터
UCSRnC 레지스터는 UART 포트의 송수신 동작을 제어하는 기능을 가지고 있다.
1) USBSn비트를 0으로 설정하면 USART 포트에서 데이터 포맷을 구성하는 스톱 비트를 1개 설정하고 이 비트가 1이면 스톱 비트를 2개를 설정한다.
2) UCSZn0~1비트는 [그림-6]과 같이 UCSRnB 레지스터의 UCSZn2비트와 함께 전송 문자의 데이터 비트 수를 설정하는데 사용된다.
UBRRnH/L 레지스터
UBRRnH/L레지스터는 UART 포트의 송수신 속도를 설정하는 기능을 가지고 있다.
보레이트를 설정하기 위해서는 UBRRnH/L 레지스터를 설정하여야 하는데 계산 수식은 아래와 같다.
우리는 위와 같이 UART 레지스터에 대해 이해 할 수 있었다.
자 그럼 이제 실제 코드에 적용해 보자.
CodeVision AvR C컴파일러
LK ATmega 128 예제소스6: UART레지스터을 이용한 DATA출력하기
//예제소스 시작
#include <mega128.h>
#include <stdio.h>
#include <delay.h>
#define RXB8 1
#define TXB8 0
#define UPE 2
#define OVR 3
#define FE 4
#define UDRE 5
#define RXC 7
#define FRAMING_ERROR (1<<FE)
#define PARITY_ERROR (1<<UPE)
#define DATA_OVERRUN (1<<OVR)
#define DATA_REGISTER_EMPTY (1<<UDRE)
#define RX_COMPLETE (1<<RXC)
#define RX_BUFFER_SIZE0 1 //수 버퍼 사이즈 1로 설정
char rx_buffer0[RX_BUFFER_SIZE0];
#if RX_BUFFER_SIZE0<256
unsigned char rx_wr_index0,rx_rd_index0,rx_counter0;
#else
unsigned int rx_wr_index0,rx_rd_index0,rx_counter0;
#endif
bit rx_flag; //수싞플래그 비트 선언
void tx_out(unsigned char buff) //송싞데이터 출력 함수
{
while ( !( UCSR0A & (1<<UDRE)) ); //폴링 방식으로 송 준비 체크
UDR0 = buff; // 데이터 출력
}
interrupt [USART0_RXC] void usart0_rx_isr(void) //USART0 서비스 루틴
{
char status,data; //변수 선언
status=UCSR0A; //상태 체크 변수
data=UDR0; //데이터 변수
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0) //통싞 에러 및 상태 이상 무
{
rx_buffer0[rx_wr_index0]=data; //수싞 버퍼에 데이터 저장
if (++rx_wr_index0 == RX_BUFFER_SIZE0) {
rx_wr_index0=0; rx_flag=1; //rx_flag 비트 활성화 및 초기화
}
};
}
void main(void)
{
DDRE=0XFF; //LED 출력 레지스터 설정
PORTE=0X00; //LED PORT 초기화
DDRG=0X01; //BUZZER 출력 레지스터 설정
PORTG=0X01; //BUZZER 초기화
UCSR0A=0×00;
UCSR0B=0×98; //송,수 허용
UCSR0C=0×06; //비 동기 통 모드, 데이터 사이즈 8bit
UBRR0H=0×00;
UBRR0L=0×67; //boud rate : 9600 pbs
#asm(“sei”)
while (1)
{
If(rx_flag){
rx_flag=0; //수 플래그 비트 초기화
if(rx_buffer0[0]==1){ //수싞버퍼 데이터가 1일 때
PORTE=0XFF; //LED ON
PORTG=0X01; //BUZZER OFF
tx_out(0×01); //송 데이터 0×01
}
else if(rx_buffer0[0]==2){ //수싞 버퍼 데이터가 2일 때
PORTG=0X00; //BUZZER ON
PORTE=0X00; //LED OFF
tx_out(0×02); //송 데이터 0×02
}
else {PORTE=0X00;PORTG=0X01; //수싞 버퍼 데이터 값이 1 또는 2가 아닐 경우
tx_out(0xff) //송싞데이터 0xff
} //LED OFF, BUZZER OFF
}
};
}
//예제소스 끝
※ 프로그램 해석 ※
1) USART 레지스터 설정
2) 수신 인터럽트 발생시 인터럽트 서비스 루틴 진입
3) 상태 레지스터 및 각종 에러 플래그 비트 체크
4) 수신된 데이터와 설정된 데이터 버퍼수가 같을 때 수신 플래그 비트 활성화
5) 수신 플래그 비트 활성화 시 if문으로 조건 체크
6) If문 조건에 따라서 LED ON 및 BUZZER ON 혹은 LED OFF 및 BUZZER OFF
7) If문 조건에 따라서 송신 데이터 출력
8) 2~7을 반복한다.
★ FLOWCHART
※ 참고사항 ※
통신분야에 있어서 통신속도에 관련되는 단위로 흔히 사용되는 것으로 Bps와 Baud가 있다. Bps는 비트 전송 속도를 나타내는 단위이며, 1초간에 전송되는 비트수를 나타낸다.
Baud은 변조 속도를 나타내는 단위이며, 초 단위로 나타낸 최단 변조 주기를 T라고 할 때 Baud=(1/T) [sec]로 정의 된다.
제5부
ADC CONVERSION (Analog to Digital Converter)강좌
개요
ATmega 128은 10비트의 분해능을 가진 8채널 A/D 컨버터를 가지고 있으며 이 8채널의 아날로그 입력 신호는 PORTF와 동일한 핀을 사용하고 있으며 ADMUX 레지스터에 의하여 사용 여부가 결정된다. ATmega 128의 ADC 기능들의 특징은 아래와 같다.
1) A/D컨버터에는 샘플/홀드 회로가 있어서 A/D 변환이 수행되고 있는 동안에는 아날로그 전압이 일정하게 유지되도록 하고 있다.
2) ADMUX레지스터는 8개의 단 극성 입력으로 사용될 수도 있고 1개의 지정된 핀을 기준으로 7개의 차동 입력으로 사용될 수도 있으며 2가지의 차동입력에 대해서는 MCU 내부에서 10배 또는 200배 증폭하여 A/D 변환을 할 수도 있다.
3) A/D변환시간을 사용자가 주파수 50Khz~200Khz까지 시간60~260us 범위에서 설정할 수 있다.
4) A/D는 2가지의 변환모드가 있는데 단일변환모드와 프리런닝모드가 있으며 사용자가 원하는 출력상태를 선택하여 결정하면 된다.
ADMUX레지스터
ADMUX레지스터는 AD컨버터의 기준전압을 선택하거나 변환 결과 레지스터의 데이터 저장형식을 지정하는 기능을 가지고 있으며 아날로그 입력채널을 선택하는 기능을 가지고 있다. 이해를 돕기 위해 아래의 그림을 참조해 설명하겠다.
1) REFS1~0 : 기준 전압을 결정할 때 사용되는 비트로서 외부의 VREF 전압을 사용할 것인지, 외부의 AVCC 전압을 사용할 것인지 내부 기준전압(2.56v)를 사용할 것인지를 결정하는 기능을 수행한다.
2) ADLAR : 변환 결과를 저장하는 형식을 결정하며 이 비트를 1로 지정하면 변환 결과가 상위 바이트를 시작으로 저장이 되며 0으로 설정하면 하위 바이트를 시작으로 데이터가 저장된다.
3) MUX4~0 : 아날로그 입력 채널을 결정할 때 사용되는데 아날로그 입력은 크게 단 극성 입력과 차동입력으로 나눈다.
단 극성은 8가지의 단 극성 입력을 선택 할 수 있고 차동입력은 단자사이의 차동입력 그리고 이득값을 20 또는 200으로 증폭해서 취득할 수도 있다.
ADCSRA 레지스터
ADCSRA 레지스터는 AD 컨버터의 동작을 설정하거나 동작상태를 표시하는 기능을 수행한다.
1) ADEN : AD컨버터를 ON 또는 OFF 하는 기능을 수행한다.
2) ASSC : 이 비트를 1로 설정하면 AD 컨버터의 변환이 시작되며 프리런닝모드에서는 처음 이후에는 자동으로 변환이 진행된다.
3) ADFR : AD 컨버터를 프리런닝 모드로 설정할 때 사용된다.
4) ADIF : AD 컨버터의 변환이 완료 되었을 때 1로 SET 되며 인터럽트가 발생하게 된다.
이 인터럽트가 처리되기 시작하면 다시 자동적으로 0으로 CLEAR 된다.
5) ADIE : 변환 완료 인터럽트를 개별적으로 허용할 때 사용된다.
6) ADPS2~0 : 위와 같이 AD 컨버터에 인가되는 클럭의 분주비를 선택하는 기능을 수행한다.
ADCH, ADCL 레지스터
ADCH 및 ADCL 레지스터는 AD 컨버터의 변환 결과를 저장하는 레지스터로서 단 극성 입력을 사용할 경우에는 0~1023의 데이터 범위를 가지지만 차동입력을 사용할 경우에는 -512~+511 의 데이터 범위를 가지게 된다. 그리고 ATmega 128은 10비트의 분해 능을 가지고 있으므로 1 byre로는 처리가 불가능하다.
그래서 ADCH(상위 바이트), ADCL(하위 바이트)로 데이터를 나눠서 저장하게 되며 ADMUX 레지스터의 ADLAR 비트의 설정에 따라 데이터가 우측 또는 좌측으로 정렬되어 저장하게 되는데 그 내용은 아래의 [그림 -6]과 같다.
A/D 변환 결과
AD 컨버터가 변환을 완료하면 ADCH와 ADCL에 데이터가 저장된다. 그러면 우리는 아날로그 입력이 디지털 값으로 변환이 되었을 때의 데이터를 알아볼 필요가 있다.
42페이지에 [그림-7]과 [그림-8]을 참고하여 계산하여 올바른 데이터 변환이 일어나고 있는지 확인 하면 된다.
1. 단 극성 입력 채널을 사용
2. 차동입력 채널 사용
우리는 위와 같이 ADC 레지스터에 대해 이해 할 수 있었다.
자 그럼 이제 실제 코드에 적용해 보자.
CodeVision AvR C컴파일러
LK ATmega 128 예제소스6: ADC레지스터와 UART 통신을 이용한 DATA출력하기
#include <mega128.h>
#include <stdio.h>
#include <delay.h>
#define ADC_VREF_TYPE 0×40
int data; //AD 컨버팅 데이터 변수
unsigned int read_adc(unsigned char adc_input) //AD 컨버팅 함수
{
ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);
delay_us(10);
ADCSRA|=0×40; //ADC 단일 컨버팅 모드 설정
while ((ADCSRA & 0×10)==0); //ADC 폴링상태로 대기
ADCSRA|=0×10; //인터럽트 발생 플래그 비트 초기화
return ADCW; //ADCL, ADCH 각각 8비트를 합산하여 16비트로 사용
}
void main(void)
{
ADMUX=ADC_VREF_TYPE & 0xff; //ADMUX = 0X40 (define선언 참조), AVCC 핀을 VREF로 설정
ADCSRA=0×84; //AD 컨버팅 허용, 16분주 비 사용
UCSR0A=0×00;
UCSR0B=0×98; //송싞, 수싞 허용
UCSR0C=0×06; //비 동기 통싞 모드, 데이터 사이즈 8bit
UBRR0H=0×00;
UBRR0L=0×67; //boud rate : 9600 BPS
#asm(“sei”)
while (1)
{
data=read_adc(0); //데이터 변수에 read_adc(0)채널의 데이터 삽입
printf(“%d\r\n”,data); //printf문을 이용하여 데이터 출력
};
}
//예제소스 끝
※ 프로그램 해석 ※
1) ADMUX 레지스터 기준전압 설정(데이터 시트 참조) 및 AD컨버팅 허용, 분주 비 설정, ADC 채널 설정
2) 데이터 값을 확인하기 위하여 USART 통신 레지스터 설정
3) read_adc 함수에서 ADC기준 전압을 통하여 ADC0 채널에 디지털 데이터 입력
4) printf 문을 사용하여 ADC0데이터 확인
5) 3~4번 동작을 반복한다.
FLOWCHART
※참고사항※
ATmega 128은 프리런닝 모드와 단일 변환 모드를 지원하고 있는데 사용자가 원하는 시점에서 변환을 하려면 단일 변환 모드를 사용해야 한다.
하지만 프로그램의 구성의 따라 이러한 단점을 극복 할 수가 있는데 그 방법으로는 타이머 인터럽트를 이용하여 변환 시간을 사용자가 선택할 수가 있기 때문이다.
예를 들면 타이머 인터럽트를 1ms로 설정하고 read_adc함수를 호출한다면 1ms마다 ADC 데이터를 정의 하게 되기 때문이다.
강의를 통해 얻는 예제 소스를 활용하여 AVR의 기능을 더 극대화 해보길 바란다.