13. Keypad와 7-Segment를 활용한 심화 작품 만들기
안녕하세요!!! 오늘은 저번 강의 시간에 배운 Keypad와 7-Segment를 활용한 작품을 만들어 볼까 합니다.
Easy Processor Kit을 준비 하시고 AVR Studio를 켜 봅시다~!
- 실험 1
우선 첫 번째 실험으로는 4*4로 배치 되어 있는 키패드에 0~15까지 번호를 매겨서 Segment에 출력 되도록 해 볼까요??
대충 감이 오시나요?? 한번 코딩해보도록 하세요!! 제가 한 것과 비교해 가며 더욱더 효율적인게 무엇인지 판단하시면서 코딩 실력을 향상 시켜 볼 수 있답니다~~ 물론.. 저도 잘하지는 않지만 같이 공부하는 입장이자나요 ㅎㅎ 서두가 길었네요... 얼른 코딩합시다!
#include <avr/io.h>
#define KEY_DATA (*(volatile unsigned char *)0x5400) // KEY_DATA 주소 지정
#define KEY_SCAN (*(volatile unsigned char *)0x5800) // KEY_SCAN 주소 지정
#define DIG_SELECT (*(volatile unsigned char *)0x7000) // FND선택을 위한 주소 지정
#define FND_DATA (*(volatile unsigned char *)0x6C00) // FND data 값 저장 위한 주소 지정
void Platform_Init(void) // 레지스터 설정 함수
{
MCUCR |= 0x80 ; // 외부 메모리 사용
}
void SEG(uint8_t data) // 0부터 차례대로 Segment에 표시 함수
{
switch(data) // 0부터 차례대로 Segment에 표시
{
case 0 :
FND_DATA = ~0x3F; // 0 표시
break;
case 1 :
FND_DATA = ~0x06; // 1 표시
break;
case 2 :
FND_DATA = ~0x5B; // 2 표시
break;
case 3 :
FND_DATA = ~0x4F; // 3 표시
break;
case 4 :
FND_DATA = ~0x66; // 4 표시
break;
case 5 :
FND_DATA = ~0x6D; // 5 표시
break;
case 6 :
FND_DATA = ~0x7C; // 6 표시
break;
case 7 :
FND_DATA = ~0x07; // 7 표시
break;
case 8 :
FND_DATA = ~0x7F; // 8 표시
break;
case 9 :
FND_DATA = ~0x6F; // 9 표시
break;
}
}
void Write_KeyPadData(uint8_t data) // 확인하고 싶은 행에 해당하는 값 입력
{
KEY_DATA = data;
}
uint8_t Read_KeyPadData(void) // 열에 해당하는 값 리턴
{
return KEY_SCAN;
}
void delay_us(unsigned char time_us) // us단위 delay를 위한 함수
{
register unsigned char i;
for(i=0;i<time_us;i++)
{
asm volatile("PUSH R0"); // 스택 푸쉬
asm volatile("POP R0"); // 스택 팝
}
}
void delay_ms(unsigned int time_ms) // ms단위 dealy를 위한 함수
{
register unsigned int i;
for(i=0;i<time_ms;i++)
{
delay_us(250); // 250us 딜레이(2^8=256까지 표현가능)
delay_us(250);
delay_us(250);
delay_us(250); // 총 1ms 딜레이 표현
}
}
void KeyPadScan() // 몇 번 버튼인지 스캔하는 함수
{
register unsigned int Key_Data;
uint8_t data;
Write_KeyPadData(0x0E); // 1행 확인
data = Read_KeyPadData(); // 열에 해당하는 값 읽음
if(!(data&0x01)) return 3; // 3버튼
if(!(data&0x02)) return 2; // 2버튼
if(!(data&0x04)) return 1; // 1버튼
if(!(data&0x08)) return 0; // 0버튼
Write_KeyPadData(0x0D); // 2행 확인
data = Read_KeyPadData(); // 열에 해당하는 값 읽음
if(!(data&0x01)) return 7; // 7버튼
if(!(data&0x02)) return 6; // 6버튼
if(!(data&0x04)) return 5; // 5버튼
if(!(data&0x08)) return 4; // 4버튼
Write_KeyPadData(0x0B); // 3행 확인
data = Read_KeyPadData(); // 열에 해당하는 값 읽음
if(!(data&0x01)) return 11; // 11버튼
if(!(data&0x02)) return 10; // 10버튼
if(!(data&0x04)) return 9; // 9버튼
if(!(data&0x08)) return 8; // 8버튼
Write_KeyPadData(0x07); // 4행 확인
data = Read_KeyPadData(); // 열에 해당하는 값 읽음
if(!(data&0x01)) return 15; // 15버튼
if(!(data&0x02)) return 14; // 14버튼
if(!(data&0x04)) return 13; // 13버튼
if(!(data&0x08)) return 12; // 12버튼
return 0xFF; // 아무것도 누르지 않을 경우 0xFF 리턴
}
int main(void)
{
uint8_t keyvalue = 0; // 현재 키패드 값 저장하는 변수
uint8_t prevalue = 0xFF; // 이전 키패드 값 0xFF로 초기화 현재 키패드 값과 비교 위함
uint8_t keybutton = 0;
Platform_Init(); // 초기 설정 함수
while(1)
{
keyvalue = KeyPadScan(); // 몇 번 버튼인지 스캔하는 함수
if(keyvalue != 0xFF && keyvalue != pre_val) // 이전의 값이랑 다르고, 키를 누른 상태일 때
keybutton = keyvalue; // 현재 키패드 값을 세그먼트에 출력할 키버튼으로 지정
if(keybutton < 10) // 키버튼의 값이 10보다 작은 경우
{
DIG_SELECT = 0x01;
SEG(0);
delay_ms(3); // 세그먼트 첫번째 자리에 0표시 및 3ms 딜레이
DIG_SELECT = 0x02;
SEG(keybutton);
delay_ms(3); // 세그먼트 두번째 자리에 키버튼 표시 및 3ms 딜레이
}
else // 키버튼의 값이 10보다 큰 경우
{
DIG_SELECT = 0x01;
SEG(keybutton/10);
delay_ms(3); // 세그먼트 첫번째 자리에 10의자리 표시 및 3ms 딜레이
DIG_SELECT = 0x02;
SEG(keybutton%10);
delay_ms(3); // 세그먼트 두번째 자리에 1의자리 표시 및 3ms 딜레이
}
pre_val = keyvalue; // 이전값에 현재 값 옮김
}
}
저는 이렇게 코딩 하였답니다~!! 자신의 것과 비교해 보시면서 코딩 실력을 저랑 같이 키워 나가자구요 ㅎㅎ
모두들 아래와 같은 결과를 얻으셨나요??
그리고 아마 실험을 하면서 어? 이렇게는 왜 안되지?? 하시는 분들이 많을 거에요. AVR 코딩의 Tip을 드리자면 while 문에 유의 하셔야 합니다.
while 문에 유의하면서 코딩하더라도 키패드의 경우는 좀 특별한데요. 바로 채터링(chattering) 현상 때문입니다!
- 채터링(chattering) 현상
이 현상에 대해 아시는 분들도 있을거지만, 설명하자면 스위치나 릴레이 등의 접점이 개폐될 때 기계에서 발생하는 진동입니다. 스위치를 ON 또는 OFF로 했을 때, 접점 부분의 진동으로 말미암아 단속 상태가 반복되는 일, 키보드에서 이 상태가 발생하면 같은 문자가 여러 개 찍히게 되는 것이죠. 이해 되셨나요???
즉, 키패드를 한 번 누르게 되면 한 개의 값이 나와야 하지만, 결과값이 여러 번 반복 되어서 나온다는 이야기입니다. 그렇다면 이것을 어떻게 해결 할까요??
- 채터링(chattering)을 피하기 위한 방법
1. 소프트웨어적으로 피하는 방법
첫 번째로 소프트웨어적으로 피하는 방법입니다. 이 방법으로 아주 다양한 알고리즘이 존재합니다. 제일 간단하게로는 약간의 딜레이를 이용해서 스위치 값을 읽는 것이죠. 그럼 진동시의 값이 아닌 일정한 값이 들어오게 되겠죠?? 하지만 이 방법에는 한계가 있답니다. 그래서 나온 방법이 제가 소스코드에서 쓴 방법이죠. 키패드가 눌러지고, 현재 키패드의 값과 이전 키패드의 값을 비교하여 값이 다를 때만 받아들여 한번 누른 것과 같은 결과를 보이도록 나타낸 것입니다. 물론, 이 방법 말고도 여러분이 생각하여 적용하시면 됩니다!! ^^
2. 하드웨어적으로 피하는 방법
두 번째로는 하드웨어적으로 피하는 방법입니다. 여기에도 아주 다양한 하드웨어가 존재하는데 여기서는 널리 사용되는 방법인 R-S 플립플롭을 이용한 방법으로 예를 들어 보이겠습니다.
그림과 같이 회로를 구성하면 되는데, 분석해보면 풀업저항으로 인해 U1B에는 5V가 들어가게 되고, 스위치가 연결된 U1A에는 0V가 들어가게 되는데요. 그럼 U1A NAND 게이트의 값은 1로 나올 것이고, U1B의 NAND 값은 0으로 나오겠네요! 최종 결과로는 당연히 1으로 나오겠죠?
아하 그렇다면 스위치가 채터링으로 인해 떨어지게 되는 경우를 보게 된다면, U1A, U1B에는 풀업저항으로 인해 1의 값이 들어가게 되고 U1A의 NAND 게이트의 값은 1이었으니깐 여전히 U1B의 NAND 값은 당연히 0이고, 최종 결과로는 1이 유지가 되는 것을 알 수 있네요!!
이렇게 채터링을 피하기 위한 방법들을 알아보았는데요. 원하시는 방법을 채택하여 적용하시면 된답니다~!!!
- 실험 2
다음 실험은 실험의 단골주제 계산기를 해보도록 하겠습니다!!
음~ 일단 키패드에서 어떻게 구현할 것인지 각 버튼의 기능을 설계 해보자면, 아래 표와 같습니다!
- 계산기에서 키패드 구성
이제 각 버튼에 대해서 구현할 수 있겠죠? 아까 했던 실험에서 조금만 추가하고, 변경하면 된답니다!! 그리고 여러 알고리즘들이 있을 수 있으니 제꺼는 참고해서 보세요! 연산기호를 Segment로 표시 할 때는 마음대로 하셔도 되요.
#include <avr/io.h>
#define KEY_DATA (*(volatile unsigned char *)0x5400) // KEY_DATA 주소 지정
#define KEY_SCAN (*(volatile unsigned char *)0x5800) // KEY_SCAN 주소 지정
#define DIG_SELECT (*(volatile unsigned char *)0x7000) // FND선택을 위한 주소 지정
#define FND_DATA (*(volatile unsigned char *)0x6C00) // FND data 값 저장 위한 주소 지정
void Platform_Init(void) // 레지스터 설정 함수
{
MCUCR |= 0x80 ; // 외부 메모리 사용
}
void SEG(uint8_t data)
{
switch(data) // 0부터 차례대로 Segment에 표시
{
case 0 :
FND_DATA = ~0x3F; // 0 표시
break;
case 1 :
FND_DATA = ~0x06; // 1 표시
break;
case 2 :
FND_DATA = ~0x5B; // 2 표시
break;
case 3 :
FND_DATA = ~0x4F; // 3 표시
break;
case 4 :
FND_DATA = ~0x66; // 4 표시
break;
case 5 :
FND_DATA = ~0x6D; // 5 표시
break;
case 6 :
FND_DATA = ~0x7C; // 6 표시
break;
case 7 :
FND_DATA = ~0x07; // 7 표시
break;
case 8 :
FND_DATA = ~0x7F; // 8 표시
break;
case 9 :
FND_DATA = ~0x6F; // 9 표시
break;
case 11 :
FND_DATA = ~0x46; // 덧셈
break;
case 12 :
FND_DATA = ~0x40; // 뺄셈
break;
case 13 :
FND_DATA = ~0x70; // 곱셈
break;
case 14 :
FND_DATA = ~0x49; // 나눗셈
break;
}
}
void Write_KeyPadData(uint8_t data) // 확인하고 싶은 행에 해당하는 값 입력
{
KEY_DATA = data;
}
uint8_t Read_KeyPadData(void) // 열에 해당하는 값 리턴
{
return KEY_SCAN;
}
void delay_us(unsigned char time_us) // us단위 delay를 위한 함수
{
register unsigned char i;
for(i=0;i<time_us;i++)
{
asm volatile("PUSH R0"); // 스택 푸쉬
asm volatile("POP R0"); // 스택 팝
}
}
void delay_ms(unsigned int time_ms) // ms단위 dealy를 위한 함수
{
register unsigned int i;
for(i=0;i<time_ms;i++)
{
delay_us(250); // 250us 딜레이(2^8=256까지 표현가능)
delay_us(250);
delay_us(250);
delay_us(250); // 총 1ms 딜레이 표현
}
}
uint8_t KeyPadScan()
{
register unsigned int Key_Data;
uint8_t data;
Write_KeyPadData(0x0E); // 1행 확인
data = Read_KeyPadData(); // 열에 해당하는 값 읽음
if(!(data&0x01))
return 11; // 덧셈표시
if(!(data&0x02))
return 3; // 3표시
if(!(data&0x04))
return 2; // 2표시
if(!(data&0x08))
return 1; // 1표시
Write_KeyPadData(0x0D); // 2행 확인
data = Read_KeyPadData(); // 열에 해당하는 값 읽음
if(!(data&0x01))
return 12; // 뺄셈표시
if(!(data&0x02))
return 6; // 6표시
if(!(data&0x04))
return 5; // 5표시
if(!(data&0x08))
return 4; // 4표시
Write_KeyPadData(0x0B); // 3행 확인
data = Read_KeyPadData(); // 열에 해당하는 값 읽음
if(!(data&0x01))
return 13; // 곱셈표시
if(!(data&0x02))
return 9; // 9표시
if(!(data&0x04))
return 8; // 8표시
if(!(data&0x08))
return 7; // 7표시
Write_KeyPadData(0x07); // 4행 확인
data = Read_KeyPadData(); // 열에 해당하는 값 읽음
if(!(data&0x01))
return 14; // 나눗셈표시
if(!(data&0x02))
return 50; // = 표시
if(!(data&0x04))
return 0; // 0표시
if(!(data&0x08))
return 100; // 취소표시
return 0xFF; // 안누르면 0xFF 반환
}
int main(void)
{
uint8_t first_operand=0; // 첫 번째 인수를 담을 변수
uint8_t second_operand=0; // 두 번째 인수를 담을 변수
uint8_t cal_signal=11; // 연산기호 담을 변수 더하기로 초기화
uint8_t result_val=0; // 결과값을 담을 변수
uint8_t keyvalue=0; // 키패드 스캔값 담을 변수
uint8_t pre_val=0xFF; // 이전의 스캔값 0xFF로 초기화
uint8_t flag=0; // 플래그 0
uint8_t first_sig=0; // 마이너스를 표현하기 위한 결과값의 왼쪽에 해당하는 값
uint8_t equal=0; // 결과에 해당하는 스캔값을 저장할 변수
Platform_Init();
while(1)
{
keyvalue = KeyPadScan(); // 키스캔
if(keyvalue != 0xFF && keyvalue != pre_val) // 이전의 값이랑 다르고, 키를 누른 상태일 때
{
if(flag == 0x00) // 첫번째 인수
{
first_operand = keyvalue; // 첫 번째 인수를 변수에 담기
if(first_operand > 10) // 만약 그 인수가 숫자가 아니면 초기화
{
first_operand = 0;
cal_signal = 11;
second_operand = 0;
result_val = 0;
first_sig = 0;
flag=0x00;
}
else
flag = 0x01; // 숫자면 플래그 1
}
else if(flag == 0x01) // 두번째 인수
{
cal_signal = keyvalue;
if(cal_signal < 10 || cal_signal >= 50) // 연산기호가 아니면 초기화
{
first_operand = 0;
cal_signal = 11;
second_operand = 0;
result_val = 0;
first_sig = 0;
flag=0x00;
}
else // 연산기호이면 플래그 2
flag = 0x02;
}
else if(flag == 0x02) // 세번째 인수
{
second_operand = keyvalue;
if(second_operand > 10) // 숫자가 아니면 초기화
{
first_operand = 0;
cal_signal = 11;
second_operand = 0;
result_val = 0;
first_sig = 0;
flag=0x00;
}
else
flag = 0x03; // 숫자면 플래그 3
}
else // 결과
{
equal = keyvalue;
if(equal==50) // 스캔값이 =이면
{
switch(cal_signal)
{
case 11 : // 덧셈
result_val = first_operand + second_operand;
flag=0x00; // 초기화
break;
case 12 : // 뺄셈
if(first_operand < second_operand) // 결과가 -이면
{
result_val = second_operand - first_operand;
first_sig = 12; // 결과값에 - 붙임
}
else // 결과가 +이면
result_val = first_operand - second_operand;
break;
case 13 : // 곱셈
result_val = first_operand * second_operand;
break;
case 14 : // 나눗셈
if(second_operand == 0) // 나누는 값이 0이면 초기화
{
first_operand = 0;
cal_signal = 11;
second_operand = 0;
result_val = 0;
first_sig = 0;
flag=0x00;
}
else // 나누는 값이 0이 아니면
result_val = first_operand/second_operand;
break;
}
}
else // = 아니면 초기화
{
first_operand = 0;
cal_signal = 11;
second_operand = 0;
equal = 0;
result_val = 0;
first_sig = 0;
flag=0x00;
}
}
}
/*-----------------출력-----------------*/
DIG_SELECT = 0x01; // 1번 자리
SEG(first_operand);
delay_ms(3);
DIG_SELECT = 0x04; // 2번 자리
SEG(cal_signal);
delay_ms(3);
DIG_SELECT = 0x10; // 3번 자리
SEG(second_operand);
delay_ms(3);
if(result_val < 10) // 결과 자리 결과 값이 10보다 작을 때
{
DIG_SELECT = 0x40;
SEG(first_sig); // 0이거나 -값
delay_ms(3);
DIG_SELECT = 0x80;
SEG(result_val); // 1의 자리
delay_ms(3);
}
else // 결과 값이 10보다 크거나 같을 경우
{
DIG_SELECT = 0x40;
SEG(result_val/10); // 10의 자리
delay_ms(3);
DIG_SELECT = 0x80;
SEG(result_val%10); // 1의 자리
delay_ms(3);
}
pre_val = keyvalue; // 이전값에 현재 값 옮김
}
}
실험결과 입니다!! 왼쪽 위부터 덧셈, 뺄셈, 곱셈, 나눗셈 순이에요~! 연산 기호를 저는 그림과 같이 나타내 보았어요~~!!
8 + 5 = 13 (왼쪽), 1 - 6 = -5 (오른쪽)
4 * 8 = 32 (왼쪽), 8 / 4 = 2 (오른쪽)
소스코드를 대략적으로 설명하자면, 저는 키패드 리턴값을 0~9는 해당 숫자들을 그대로 표현하였고, 11~14는 연산기호를 표현 하였습니다. 그리고 취소 버튼의 경우 100으로 키패드 리턴값을 받구요. 어짜다보니, 취소버튼 뿐만 아니라 해당 플래그에서 다른 버튼이 들어오면 초기화 해버렸지만... 의미는 같으니까요~~^^ 또한, 플래그를 두어 각 단계마다 해당 값들을 받아서 변수에 저장 시켰답니다. 주석을 잘 보시고 이해하세요.
수고하셨어요!!! 이제 4개의 소자에 대해서 배웠으니 4개를 활용해서 재미난 작품들을 만들 수 있답니다.
다음 시간에는 Timer와 Counter에 대해서 배워보도록 할게요!! 안녕~!!
'Study > AVR ATmega128 Easy Processor Kit' 카테고리의 다른 글
12. Easy Processor Kit 7-segment 제어하기 (0) | 2016.01.21 |
---|---|
11. Easy Processor Kit 외부 keypad 제어 하기 (0) | 2016.01.14 |
10. 외부 LED와 Dip Switch를 활용한 심화 작품 만들기 (1) | 2016.01.12 |
9. Dip Switch 및 외부 LED 제어 (0) | 2016.01.11 |
8. AVR의 GPIO 특성 이해 (0) | 2016.01.10 |