-
Atmega128 - 장애물 피하기 게임 만들기(2)공부 2022. 10. 3. 23:59
본 포스팅은 작성자가 2021년에 수강했던 마이크로프로세서 강의에 제출한 과제를 바탕으로 작성되었으며, 현재와 문법이 다를 수 있습니다. 또한 학부 수준의 지식으로 작성되었기에 틀린 부분이 존재할 가능성이 다분합니다.
.
.
.
- 회로도

위는 설계한 회로의 회로도이다.
PA0, PA1, PA2에는 각각 LCD의 RS, R/W, E를 연결하였고, PA8(VCC), PA9(GND)에는 각각 LCD의 Vdd, Vss를 연결하였다.PB0, PB1, PB2, PB3, PB4, PB5, PB6, PB7에는 각각 LCD의 DB0, DB1, DB2, DB3, DB4, DB5, DB6, DB7을 연결하였고 PB8(VCC), PB9(GND)에고 는 각각 LCD의 BLA, BLK를 연결하였다.
PC0, PC1, PC2, PC3, PC4, PC5, PC6, PC7에는 각각 FND의 A, B, C, D, E, F, G, DP를 연결하였다.
PD0에는 외부 인터럽트로 사용될 스위치와 연결하였고 PD8(VCC), PD9(GND)에는 각각 스위치 회로의 VCC, GND를 연결하였다. PE0, PE1에는 Buzzer를 연결하였다.
PF0, PF1, PF2, PF3에는 각각 FND의 C1, C2, C3, C4를 연결하였다.- 코드
/* * 0528.c * * Created: 2021-05-28 오후 1:05:26 * Author: 장재영 */ #define F_CPU 16000000 #include <stdlib.h> #include <mega128.h> #include <delay.h> #define CMD_WRITE 0xFC //명령어쓰기 E=1, RW=0, RS=0 #define DATA_WRITE 0xFD //데이터쓰기 E=1, RW=0, RS=1 #define LCD_EN 0x04 int i; int k = -1; int count = 0; int level = 9; int life = 2; unsigned char position[2] = {0xc0, 0x80}; unsigned char fnd_sel[4] = {0b00001000, 0b00000100, 0b00000010, 0b00000001}; unsigned char fnd_data[10] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xd8, 0x80, 0x90}; void LCD_cmd_write(char cmd){ PORTA = CMD_WRITE; PORTB = cmd; PORTA = PORTA^LCD_EN; delay_ms(2); } void LCD_data_write(char data){ PORTA = DATA_WRITE; PORTB = data; PORTA = PORTA^LCD_EN; delay_ms(2); } void LCD_wr_string(char d_line, char *lcd_str){ LCD_cmd_write(d_line); while(*lcd_str != '\0'){ LCD_data_write(*lcd_str); lcd_str++; } } void FND_score(int score){ int temp = score; unsigned char fnd[4]; for(i = 0; i < 4; i++){ fnd[i] = temp%10; temp = temp/10; } for(i = 0; i < 4; i++){ PORTC = fnd_data[fnd[i]]; PORTF = fnd_sel[i]; delay_ms(5); } } void FND_life(void){ PORTF = fnd_sel[1]; PORTC = fnd_data[life]; } char generate_hurdle(char *line1, char *line2){ if(rand()%level == 0 && *(line2+15) == ' ' && *(line2+14) == ' ' && *(line1+14) == ' ' && *(line1+13) == ' ') return '*'; else return ' '; } char generate_life(char hurdle){ if(rand()%200 == 0 && life < 8) return '+'; else return hurdle; } void LCD_display(char *line1, char *line2){ LCD_wr_string(position[1] ,line1); LCD_wr_string(position[0] ,line2); LCD_wr_string(position[k%2],"o"); LCD_wr_string(position[(k+1)%2]," "); } void get_life(char life1, char life2){ if((life1 == '+' && k%2 == 1) || (life2 == '+' && k%2 == 0)){ life++; FND_life(); for(i = 0; i < 5; i++){ PORTE = 0x01; delay_ms(2); PORTE = 0x02; delay_ms(2); } } } void bump(char *line1,char *line2, int position){ int score; if(*line1 == '*' && k%2 == position){ life--; FND_life(); for(i = 0; i < 250; i++){ PORTE = 0x01; delay_ms(2); PORTE = 0x02; delay_ms(2); } k = position; if (life == 0){ for(i = 0; i < 16; i++){ *(line1+i) = ' '; *(line2+i) = ' '; } LCD_cmd_write(0x01); LCD_wr_string(0x80," GAME OVER"); score = count; count = 0; level = 9; life = 2; k = -1; while(k == -1){FND_score(score);} } } } void move_line(char *line1, char *line2){ for(i = 0; i < 15; i++){ *(line1+i) = *(line1+i+1); *(line2+i) = *(line2+i+1); } } void level_up(){ if(count%100 == 0 && level != 2){ level--; } } interrupt [EXT_INT0] void ext_int0_isr(void){ #asm("cli"); //interrupt disable k++; #asm("sei"); //interrupt enable } void init_LCD(void){ delay_ms(15); //15msec 이상 시간지연 LCD_cmd_write(0x38); //기능셋(데이터버스 8비트, 라인수:2줄) LCD_cmd_write(0x01); //화면 지우기 LCD_cmd_write(0x06); //엔트리모드셋 LCD_cmd_write(0x0C); //표시 On } void exint0_setting(void){ EICRA = 0x02; EIMSK = 0x01; } void init_system(void){ DDRA = 0xff; DDRB = 0xff; DDRC = 0xff; DDRD = 0x00; DDRE = 0xff; DDRF = 0xff; PORTA = 0xff; PORTB = 0xff; PORTC = 0xff; PORTE = 0x00; PORTF = 0x00; } void main(void) { char line1[17] = " "; char line2[17] = " "; init_system(); init_LCD(); exint0_setting(); #asm("sei"); //interrupt enable while (1){ while(k == -1){ LCD_wr_string(0x80," HURDLE GAME"); } FND_life(); line1[15] = generate_hurdle(line1, line2); line2[15] = generate_hurdle(line2, line1); line1[15] = generate_life(line1[15]); line2[15] = generate_life(line2[15]); LCD_display(line1, line2); get_life(line1[0], line2[0]); bump(line1, line2, 1); bump(line2, line1, 0); move_line(line1, line2); count++; level_up(); delay_ms(50); } }
CMD_WRITE : LCD에 명령어를 쓰기 위해 PORTA에 할당할 값이다.DATA_WRITE : LCD에 데이터를 쓰기 위해 PORTA에 할당할 값이다.
LCD_EN : PORTA에 할당된 값들과 XOR 연산할 값이다. (E에 클럭을 일으키기 위함)
i, k, count, leve, life, position[2] : 프로그램에서 사용될 변수들이다.
여러 함수에서 사용하기 위해 전역변수로 선언하였다.
fnd_sel[4] : PORTF에 할당하여 FND에 숫자를 번갈아 가며 출력하기 위해 선언한 변수이다.
fnd_data[16] : PORTC에 할당하여 FND에 숫자를 출력하기 위해 선언한 변수이다.
LCD_cmd_write : LCD에 명령어를 쓰기 위한 함수로, cmd (실행할 명령어)를 파라미터로 넘겨받는다.
먼저 PORTA에 CMD_WRITE를 할당한 후 PORTB에 cmd (넘겨받은 명령어)를 할당한다. 그 후 PORTA(CMD_WRITE)와 LCD_EN XOR 연산한 값을PORTA에 할당한다. 이 때 PORTA는 0b11111100 -> 0b11111000 으로 바뀌며 LCD의 E단자에 클럭이 발생하여 PORTB에 할당된 명령어가 실행된다.
LCD_data_write : LCD에 데이터를 나타내기 위한 함수로, data(나타낼 데이터) 를 파라미터로 넘겨받는다.
먼저 PORTA에 DATA_WRITE를 할당한 후 PORTB에 data(넘겨받은 데이터)를 할당한다. 그 후 PORTA(DATA_WRITE)와 LCD_EN을 XOR 연산한 값을 PORTA에 할당한다. 이 때 PORTA는 0b11111101 -> 0b11111001 로 바뀌며 LCD의 E단자에 클럭이 발생하여 PORTB에 할당된 데이터가 LCD에 나타난다.
LCD_wr_string : LCD에 문자열을 나타내기 위한 함수로, 커서 위치를 설정하는 커맨드와 문자열의 첫글자의 주소를 파라미터로 넘겨 받는다.
먼저 앞서 선언한 LCD_cmd_write 함수로 넘겨받은 명령어를 실행하여 커서 위치를 설정한다. 그 후 while 문에서 넘겨받은 주솟값을 1씩 증가시키며 주소에 담긴 값을 LCD_data_write 함수를 사용하여 LCD에 나타낸다. 이 때 데이터는 설정된 커서 위치에서 오른쪽으로 옮겨가며 나타난다. 주소에 담긴 값이 \0이면 while문에서 빠져나온다.
FND_score : FND에 점수를 출력하기 위한 함수로, score(게임의 점수)를 파라미터로 넘겨받는다. 게임이 종료되었을 때 while문에서 반복해서 사용될 함수이다.
먼저 temp를 선언하여 넘겨받은 score를 할당한다. 또한 temp의 각 자릿값을 할당할 fnd를 배열로 선언하였다.
첫 번째 for문에서 i를 0부터 3까지 증가시키며 fnd[i]에 temp의 각 자리에 해당하는 값을 할당한다.
두 번째 for문에서 i를 0부터 3까지 증가시키며 PORTC에 fnd_data[fnd[i]]를 할당한 후 PORTF에 fnd_sel[i]를 할당한다. FND 의 각 자리에 fnd[i]가 번갈아 가면서 나타나 FND에는 score가 출력되는 것처럼 보인다.
FND_life : FND에 life(게임에서 남은 생명 수)를 출력하기 위한 함수이다. 게임이 진행되는 동안 사용될 함수이다.
먼저 PORTF에 fnd_sel[1]을 할당한 후 PORTC에 fnd_data[life]를 할당하여 FND의 세 번째 자리에 life를 출력한다.
generate_hurdle : 무작위로 장애물을 생성하는 함수이다. 두 문자열의 첫글자의 주소 line1, line2를 파라미터로 넘겨받는다. 이 때 두 문자열은 LCD의 첫 번째 줄과 두 번째 줄에 반복해서 출력되는 값이다.
기본적으로 랜덤값을 level (9와 2 사이의 정수)로 나눈 나머지가 0이면 장애물 '*'를, 0이 아니면 ' '를 리턴한다. 다만 나눈 나머지가 0이라고 해도 앞 두 번 이내의 시도에서 장애물이 생성되었거나 다른 줄의 동일한 시도에서 장애물이 생성되었거나 다른 줄의 이전의 시도에서 장애물이 생성되었다면 ' '를 리턴한다.
generate_life : 생명을 생성하는 함수로, 메인 함수에서 문자 line[15]를 파라미터로 넘겨받는다.
랜덤값을 200으로 나눈 나머지가 0이면서 life가 8보다 작으면 '+'(생명)를 리턴하고, 이외에는 넘겨받은 문자를 리턴한다. life는 생명 수인데 2로 시작하여 주인공이 장애물과 부딪힐 때마다 1씩 감소하고, 주인공이 '+'를 얻을 때마다 1씩 증가한다. '+'는 life 가 8 이상이면 생성되지 않는다.
LCD_display : LCD에 문자열을 출력하는 함수이다. 두 문자열의 첫 글자의 주소 line1, line2를 파라미터로 넘겨받는다.
position[0], position[1]은 앞서 전역변수로 선언한 변수로, LCD의 커서 위치를 설정하는 명령어이다. position[0]는 커서 위치를 두 번째 줄의 가장 왼쪽으로 설정하고, position[1]은 커서 위치를 첫 번째 줄의 가장 왼쪽으로 설정한다.
LCD_wr_string 함수를 사용하여 넘겨받은 주소 line1, line2에 해당하는 문자열을 각각 LCD의 첫 번째, 두 번째 줄에 출력한다. 또한 'o' 를 k값에 따라 출력한다.
'o'는 게임의 주인공으로 이를 LCD의 첫 번째, 두 번째 줄로 옮겨가며 장애물을 피한다. k는 외부 인터럽트가 발생할 때마다 증가하는 값으로 스위치를 누를 때마다 외부 인터럽트가 발생하여 k가 증가하고, 주인공의 위치가 바뀐다.
get_life : 주인공이 생명을 얻으면 life가 증가하고 버저를 울리는 함수이다. 두 문자 life1, life2를 파라미터로 넘겨받는다. life1, life2는 메인함수에서 문자열 line1의 첫 글자, 문자열 line2의 첫 글자이다.
만약 line1(LCD의 첫 번째 줄)의 첫 글자가 '+'일 때 k를 2로 나눈 나머지가 1이거나 line2(LCD의 두 번째 줄)의 첫 글자가 '+' 일 때 k를 2로 나눈 나머지가 0이면 (주인공과 생명이 같은 위치에 있으면) life를 증가시키고 FND_life 함수를 사용하여 FND 에 증가한 life 값을 출력한다.
또한 for 문으로 20ms 동안 PORTE에 16진수 01과 16진수 02를 2ms 간격으로 번갈아가면서 인가한다. 이때 버저에는 교류전류가 흘러 소리가 발생한다.
bump : 주인공이 장애물과 부딪히면 life 가 감소하고 버저를 울린 후 life 가 0이 되었다면 LCD에 "GAME OVER" 를 출력하는 함수로, 두 문자열의 첫 글자의 주소 line1, line2 와 정수 position을 파라미터로 넘겨받는다.
먼저 정수형 변수 score를 선언한다. 그 후 주소 line1 에 해당하는 문자가 '*'일 때 k를 2로 나눈 나머지가 positon 이면 (주인공과 장애물이 같은 위치에 있으면) life를 감소시킨 후 FND_life 함수를 사용하여 FND 에 감소된 life 값을 출력한다.
또한 for 문으로 1초동안 PORTE에 16진수 01과 16진수 02를 2ms 간격으로 번갈아가면서 인가한다. 이 때 버저에는 교류전류가 흘러 소리가 발생한다.
for문이 끝나면 k에 position을 할당하여 주인공의 위치를 장애물과 부딪혔을 때의 위치로 놓는다. 소리가 울리는 중에 인터럽트가 발생하여 주인공의 위치가 바뀌는 것을 방지하기 위함이다.
만약 life가 0이 되었다면 먼저 for문을 사용해 i를 0부터 16까지 증가시키며 line1[i] 와 line2[i]에 빈 칸을 할당한다. for문이 끝나면 LCD_cmd_write 함수로 LCD의 화면클리어 명령어를 실행하여 LCD의 화면을 클리어한다. 그 후에 LCD_wr_string 함수를 사용하여 LCD에 "GAME OVER" 를 출력한다. 또한 앞서 선언한 score에 count 를 할당하고 count, level, life, k를 각각 0, 9, 2, -1로 초기화한다.
그 후에 while문을 사용하여 k가 -1인 동안 FND_score 함수로 FND에 score를 출력한다. 스위치를 누르면 k가 증가하여 while문에서 빠져나와 게임이 다시 시작된다.
move_line : 두 문자열의 첫글자의 주소 line1, line2를 파라미터로 넘겨받아 문자열의 문자들을 왼쪽으로 한 칸씩 옮기는 함수이다.
for문에서 i를 0부터 15까지 증가시켜 주소 line1+i, line2+i에 해당하는 문자를 각각 주소 line1+i+1, line2+i+1에 해당하는 문자로 바꾼다.
level_up : count가 100의 배수가 될 때마다 level값을 감소시켜 장애물 발생 확률을 높이는 함수이다.
count를 100으로 나눈 나머지가 0이면서 level값이 2가 아니면 level을 1 감소시킨다.
interrupt [EXT_INT0] void ext_int0_isr : 인터럽트가 발생하면 k를 1만큼 증가시키는 함수이다.
init_LCD : LCD 설정을 초기화하는 함수이다.
먼저 8비트 인터페이스, 두 줄 표시, 문자 5x7 도트를 설정한다. 화면을 지운 후 커서를 오른쪽으로 이동하면서 문자를 입력하도록 설정한다. 마지막으로 디스플레이 on, 커서 off, 커서 깜빡임 off를 설정한다.
exint0_setting : 인터럽트 설정을 초기화하는 함수이다.
스위치 회로를 PD0에 연결하였고 스위치를 누르는 순간 PD0의 전압레벨은 하강에지이기 때문에 EICRA에 16진수 02를 할당하여 PD0의 전압레벨이 하강에지인 순간 인터럽트가 발생하게 하였다. 또한 EIMSK에 16진수 01 을 할당하여 INTO를 활성화시켰다.
init_system : 시스템을 초기화하는 함수이다.
포트 A, B, C, E, F를 출력으로 사용하였고 포트 D를 입력으로 사용하였다. 포트 A, B, C 에 16진수 FF를 할당하였고 포트 E, F 에 16진수 00 을 할당하였다.
main : 메인 함수이다. 먼저 문자형 배열 line1, line2를 선언하였고 모두 빈 칸을 할당하였다. init_system 함수로 시스템을 초기화하고 init_LCD 함수로 LCD 설정을 초기화하고 exint0_setting 함수로 인터럽트 설정을 초기화하였다. 그 후 인터럽트를 활성화하였다.
while문 안에서는 while문으로 k가 -1인동안 LCD에 "HURDLE GAME" 을 출력한다. 스위치를 눌러 인터럽트가 발생하면 k가 증가하여 while 문에서 빠져나오고, 게임이 시작된다.
게임이 시작되면 먼저 generate_hurdle 함수를 사용하여 line1[15] 와 line2[15]에무작위로 '*' 혹은 ' '를 할당한다. 또한 generate_life 함수를 사용하여 line1[15], line2[15] 에 무작위로 linel[15], line2[15] 혹은 '+'를 할당한다.
그 후 LCD_display 함수를 사용하여 LCD에 line1, line2 와 주인공 'o'을 출력한다.
get_life 함수를 사용하여 'o'와 '+'의 위치가 같은지 판단하고, 같으면 life를 증가시킨 후 버저를 울린다.bump 함수를 사용하여 'o'와 '*'의 위치가 같은지 판단하고, 같으면 life를 감소시킨 후 버저를 울린다. 만약 life 가 0이라면 값들을 초기화한 후 LCD에 "GAME OVER" 를 출력한다.
move_line 함수를 사용하여 문자열의 문자들을 왼쪽으로 한 칸씩 옮긴다. count를 증가시킨 후 level_up 함수를 사용하여 count가 100의 배수일 때마다 level 값을 1씩 감소시킨다.50ms의 딜레이 이후에 다시 while 문의 처음으로 돌아간다.
- 게임 플레이 영상
https://www.youtube.com/watch?v=75lE06dlfdM
영상으로 보면 장애물의 속도가 약간 느린것 같다. 하지만 실제로 플레이할때 느리다는 느낌은 없으며, 딜레이만 수정하면 빨라질 것이다.
'공부' 카테고리의 다른 글
VHDL 디지털시계 소스코드 (0) 2025.01.05 Atmega128 - 장애물 피하기 게임 만들기(1) (0) 2022.09.30 Scilab - 구형 펄스열의 진폭 스펙트럼, 위상 스펙트럼 그리기 (0) 2022.09.29