겨우 프로그램 완성을 했다...
블로그 글을 처음부터 읽어도 좋지만, 프로젝트에 대한 전반적인 내용을 알고싶으면 내가 작성했던 레포트를 참고해 보는것도 좋을 것 같다.
55장 정도 긴 분량이긴 하나.. 들어간 모든 기술들이 집약되어 있다.
우선 먼저 동작소스에 대해 간략하게 소개해보자면 아래와 같이 동작한다.
1. 초기 상태 | 2. 좌석을 선택한 경우 |
3. 번호 입력후 맞는지 확인 | 3-1. 2번을 누를 시 번호 다시입력 |
4. Yes입력시 문이 열림(5초동안) | 5. 문이 닫힌 후 1번좌석 업데이트 |
6. 4+*로 out모드 진입 | 6-1. 빈 좌석을 선택했을 시 |
비어있는 좌석만 표시 |
빈좌석이라 표시 후 초기상태로 돌아감 |
7. 초기상태에서 사용중인 좌석 선택 시 | 6-2 1번 선택시 |
사용중이라 표시 후 초기상태로 돌아감 |
시작할 때의 번호를 다시 입력하라고 표시 |
8. 역시 비밀번호 확인이 가능함 | 8-1 비밀번호 불일치시 |
8-2 비밀번호 일치시 | 9.1 2번 좌석에 자리를 등록한 상태 |
goodbye 출력 후 퇴장처리 |
|
10. 10초이상 자리가 비어있을 때 | 10-2. 자리가 계속 차 있는 경우 |
> 자리가 비움처리가 되어있음 1번 자리만 사람이 있는 상태 |
자리가 비움처리되지 않음 2번자리에 사람이 있는 상태 특히 자리가 비웠다가 10초안에 돌아오면 그때부터 카운트가 다시 시작되게 설정해두어져 있음 |
USART 로 mingyu입력시 | |
그 외 입력어는 작동하지 않음 |
아래 화면과 같이 각 자리에 저장해두었던 비밀번호가 출력된다. 1번은 제 번호, 2,3번은 각각 22222222222,33333333333 로 설정해두었음 |
https://www.youtube.com/watch?v=BqMQQIhrHjc
위 동영상을 보면 조금더 자세한 동작을 알 수 있다.
저번 코드에서 비밀번호를 입력 후에 퇴장할 수 있는 기능까지 추가하였는데, 이제는 초음파 거리감지 센서를 활용하여 자리를 비워두고 떠난사람이 일정시간 지나면 강제로 비움처리되는 기능을 추가하였다. 추가로 불꽃감지 센서와 USART를 통한 터미널 통신으로 번호 조회가 가능한 기능도 추가적으로 넣어주었다!
/*사용 헤더*/
#include <mega128.h >
#include <delay.h >
#include <stdio.h >
#include "lcd.h"
#include "twi.h"
#include "srf02.h"
#include "Keypad.h"
#include "usart.h"
/*STATE 정의*/ // 총 10개의 상태를 사용함
#define NONE 0
#define START 1
#define INPUT_PHONE 2
#define INPUT_PHONE_INIT 3
#define CHECK_PNUM 4
#define CHECK_PNUM_INIT 5
#define EXIT_CHOOSE 6
#define CHECK_PNUM_OUT 7
#define CHECK_PNUM_OUT_INIT 8
#define INPUT_PHONE_OUT 9
#define INPUT_PHONE_OUT_INIT 10
/*usart 정의*/
#define ENTER '\r'
#define MAXLEN 17
unsigned char str[MAXLEN], str2[MAXLEN];
unsigned char master_password[] ="1mingyu"; // 비밀번호 일치시 좌석들 번호출력
unsigned char open_password[] ="1opendo"; //문 제어를 위한 명령어 …[1]
char num[3][11]; //유저 폰 번호 저장공간 …[2]
unsigned char ti_Cnt_1ms; //초음파 센서구동을위한 cnt
unsigned char LCD_DelCnt_1ms;
unsigned char Distance_cnt_1ms; // 시간지나면 자리비움처리를 위한 cnt
unsigned char ch =0;
interrupt [USART1_RXC] void usart1_receive(void)
{ //마스터 동작 기능 수행
unsigned char i;
str[ch] = UDR1; //인터럽트 발생 시 수신된 문자를 str[ch]에 저장
if(str[ch] == ENTER){ //enter가 눌릴 때 비밀번호 일치를 확인
char access_cnt =0;
for(i =0;i <MAXLEN;i ++) str2[i] ='a';
for(i =0;i <MAXLEN;i ++) str2[i] = str[i];
str[ch-1] =0x00;
ch =0; //문자열 초기화
for (i =1;i <7;i ++) { //비밀번호 일치 확인
if (master_password[i] == str2[i]) access_cnt ++;
}
if (access_cnt ==6) { //각 좌석의 전화번호 출력
puts_USART1("\n first seat : \n");
for(i =0;i <11;i ++) putch_USART1(num[0][i]);
puts_USART1("\n Second seat : \n");
for(i =0;i <11;i ++) putch_USART1(num[1][i]);
puts_USART1("\n Third seat : \n");
for(i =0;i <11;i ++) putch_USART1(num[2][i]);
}
access_cnt =0;
for (i =1;i <7;i ++) { //문 제어
if (master_password[i] == str2[i]) access_cnt ++;
}
if (access_cnt ==6) {
OCR1A =3000;
LCD_Clear();
LCD_Pos(0,0);
LCD_Str("Door Open") ;
delay_ms(5000);
OCR1A =4710;
STATE = START;
fnd[0]=0;
}
for(i =0;i <MAXLEN;i ++) str2[i] =0;
}
else ch ++;
delay_ms(10);
if ( ch >= MAXLEN) ch =0; //최대 글자 이상일때 에러모드
}
void Timer0_Init() //초음파 센서구동 타이머 인터럽트 …[3]
{
TCCR0 = (1 <<WGM01)|(1 <<CS00)|(1 <<CS01)|(1 <<CS02);
TCNT0 =0x00;
OCR0 =100;
TIMSK = (1 <<OCIE0);
}
interrupt[TIM0_COMP] void timer0_comp(void) //실제 카운트 증가
{
ti_Cnt_1ms++;
LCD_DelCnt_1ms++;
}
int SRF_Run(char Sonar_Addr){ //SRF02 주소로 값을 받아오는 함수
unsigned char res;
unsigned int Sonar_range;
res = getRange(Sonar_Addr, &Sonar_range);
if(res)
{
return 0;
}
else if(LCD_DelCnt_1ms >100)
{
LCD_DelCnt_1ms =0;
return Sonar_range;
}
}
void main(void)
{ /* 초기 설정 */
unsigned char res;
char Sonar_Addr =0xE0; //검사할 SRF02 주소를 저장
unsigned int Sonar_range[3]={0,}; //각 좌석의 초음파센서값 저장공간
char Message[40]; //초음파 탐지 test를 위해 사용
int t =0; //키패드로 받은 숫자
int count =0; //Keypad로 값을 받을 때 계속받지 않도록 사용
int finalnum =0; //FND에 출력으로 넣어줄 변수
int fnd[12]={0,}; //FND에 쌓아 올려지면서 저장되는 공간
signed int angle =0; // 서브모터 각도로 넣을 변수
char STATE = START; // LCD상태제어를 위한 변수
char user_state[3] = {'X','X','X'}; //현재 좌석의 상태 저장
int i =0; //for문 동작을 위한 i
char user_pnumber[3][11]; //유저 비밀번호 받을 때 임시 저장
char check_pnumber[11]; //비밀번호 일치확인을위한 공간
char user_name; //1~3번중 어느좌석 유저를 가르키는지 저장
char empty_cnt[3]={0,}; //각 좌석이 10초이상 비어있는지 확인하기위한 cnt
DDRD |=0x03;
LCD_Init(); //LCD초기설정
Timer0_Init(); //초음파 구동을 위한 타이머
FND_PORT_Init(); // 포트들 입출력 초기 설정 (Keypad.h 안에 있음.)
Init_TWI();
Init_Timer1(); //서브모터 구동을 위한 PWM타이머
delay_ms(1000);
SREG |=0x80; //인터럽트 개방
OCR1A =4710; //개방 : 3000 , 닫힘 : 4710
startRanging(Sonar_Addr);
ti_Cnt_1ms =0;
LCD_DelCnt_1ms =0;
Init_USART1_IntCon(0,RX_Int); //USART 개방
DDRD.7 =1; //불꽃감지 센서 입력 설정
while(1)
{ /* 초음파 센서 */
if(ti_Cnt_1ms >100) //센서를 탐지하여 비어있는지 감지 …[4]
{
if (Sonar_Addr ==0xE0){
Sonar_Addr =0xEC;
startRanging(Sonar_Addr);
Sonar_range[0] = SRF_Run(Sonar_Addr);
}
else if (Sonar_Addr ==0xEC) {
Sonar_Addr =0xE2;
startRanging(Sonar_Addr);
Sonar_range[1] = SRF_Run(Sonar_Addr);
}
else{
Sonar_Addr =0xE0;
startRanging(Sonar_Addr);
Sonar_range[2] = SRF_Run(Sonar_Addr);
}
/* 초음파 확인용 테스트 코드 *//*
LCD_Clear();
sprintf(Message, "%03dcm", Sonar_range[0]);
LCD_Pos(0,0);
LCD_Str(Message);
sprintf(Message, "%03dcm", Sonar_range[1]);
LCD_Pos(1,0);
LCD_Str(Message);
sprintf(Message, "%03dcm", Sonar_range[2]);
LCD_Pos(1,5);
LCD_Str(Message); */
/* 자리비움 처리 */
for (i =0;i <3;i ++){ //좌석이 사용중이고, 30cm이상일 때 비워져있다 판단
if ( (Sonar_range[i] >30)&&(user_state[i] =='O') ){
empty_cnt[i] ++;
/*sprintf(Message, "%03dcm", empty_cnt[i]);
LCD_Clear();
LCD_Pos(0,0);
LCD_Str(Message); *///cnt확인용
}
else empty_cnt[i] =0;
if (empty_cnt[i] >15 ) {
user_state[i] ='E';
if (STATE == NONE ) STATE = START;
}
}
LCD_DelCnt_1ms =0;
ti_Cnt_1ms =0;
}
/* /*불꽃감지 센서동작*/ //불꽃감지시 시스템 리셋이아닌 경우 경고만 출력되게 설정
if(PIND.7 == 1){
LCD_Clear();
LCD_Pos(0,0); // 문자열 위치 0행 1열 지정
LCD_Str("Warning....."); // 문자열 str을 LCD 첫번째 라인에 출력
delay_us(10);
LCD_Pos(1,0); // 문자열 위치 1행 1열 지정
LCD_Str("Fire!!! "); // 문자열 str을 LCD 두번째 라인에 출력
LCD_Pos(1,0);
delay_us(10);
while(1)
{
for(i=0;i<20;i++){ //사이렌 소리
Buzzer_play(Sol/2);
delay_ms(10);
}
delay_ms(10);
for(i=0;i<20;i++){
Buzzer_play(Re/2);
delay_ms(10);
}
}
}
else{
}*/
/* 키패드 동작 */
t= Changenum(KeyScan());
if(t <11 & t >0 ) //숫자가 눌리면 새로운 값을 저장하도록 count값 설정
{
count++;
delay_ms(50);
}
else if(t ==0 & zero_flag) //zero_flag가 실행된 경우에만 0으로 입력 …[5]
{
count++;
zero_flag =0; //계속 0으로 입력된 상태가 안되게 zero_flag를 다시 0으로
delay_ms(50);
}
else if(t ==13) // FND 출력숫자 리셋버튼 기능
{
fnd[0]=0,fnd[1]=0,fnd[2]=0,fnd[3]=0;
}
else if (t ==14) // LCD를 초기 화면으로
{
STATE = START;
}
if((count%2) ==0){ //count가 짝수일때 들어온 t값을 저장하고
//다시 count를 홀수로 만듬
for(i =11;i >0;i --) {
fnd[i] = fnd[i -1];
delay_us(10);
}
fnd[0] = t;
count++;
delay_ms(50);
}
finalnum =1000 *fnd[3] +100 *fnd[2] +10 *fnd[1] + fnd[0];
OUTFND(finalnum); //FND 출력 …[6]
buzzer_play_function(t); //숫자에 맞는 음 출력
/* LCD 제어 */
switch (STATE) { //LCD처리를 STATE로 사용
case NONE: //기본 상태
if (fnd[1] <=3 && fnd[1] >0 && fnd[0] ==10) { //1~3번 좌석선택
user_name = fnd[1]-1;
STATE=INPUT_PHONE_INIT ;
}
if (fnd[1]==4 && fnd[0] ==10) { //4번을 눌렀을 경우
LCD_Clear();
LCD_Pos(0,0);
LCD_Str("Choose Seat");
LCD_Pos(1,0);
if (user_state[0] =='O') LCD_Str("1 ");
if (user_state[1] =='O') LCD_Str("2 ");
if (user_state[2] =='O') LCD_Str("3 ");
STATE=EXIT_CHOOSE ;
}
break;
case START: //상태 업로드
LCD_Pos(0,0);
LCD_Str("StudyRoom 4:OUT");
sprintf(Message, "1:%c 2:%c 3:%c", user_state[0],user_state[1],user_state[2]);
LCD_Pos(1,0);
LCD_Str(Message);
STATE = NONE;
break;
case INPUT_PHONE: //입장 : 폰번호 입력
if (fnd[0] ==10){
sprintf(user_pnumber[user_name], "%d%d%d%d%d%d%d%d%d%d%d", fnd[11],fnd[10],fnd[9],fnd[8],fnd[7],fnd[6],fnd[5],fnd[4],fnd[3],fnd[2],fnd[1]);
user_state[user_name] ='X';
for(i =0;i <11;i ++){
num[user_name][i] = user_pnumber[user_name][i];
}
STATE = CHECK_PNUM_INIT;
}
delay_ms(10);
break;
case INPUT_PHONE_INIT: //폰번호 입력 전에 출력 …[7]
fnd[0]=0;
LCD_Clear();
LCD_Pos(0,0);
LCD_Str("Input PhoneNum") ;
STATE = INPUT_PHONE;
if (user_state[user_name] =='O'){ //누군가 사용중일 경우
LCD_Clear();
LCD_Pos(0,0);
LCD_Str("Someone Used");
delay_ms(1000);
STATE = START;
}
break;
case CHECK_PNUM: //사용중으로 변경 및 문 개방
if (fnd[1] ==1 && fnd[0] ==10){
user_state[user_name] ='O';
OCR1A =3000;
LCD_Clear();
LCD_Pos(0,0);
LCD_Str("Door Open") ;
delay_ms(5000);
OCR1A =4710;
STATE = START;
fnd[0]=0;
user_name =4;
}
else if (fnd[1] ==2 && fnd[0] ==10) STATE = INPUT_PHONE_INIT;
break;
case CHECK_PNUM_INIT: //사용중으로 변경 및 문 개방상태 진입전 설정
fnd[0]=0;
LCD_Clear();
LCD_Pos(0,0);
LCD_Str(user_pnumber[user_name]);
LCD_Pos(1,0);
LCD_Str("1:Yes 2:No");
fnd[1] =0;
STATE = CHECK_PNUM;
break;
case EXIT_CHOOSE: //퇴장 좌석 선택
user_name = fnd[1]-1;
if (fnd[1] <=3 && fnd[1] >0 && fnd[0] ==10){
STATE = INPUT_PHONE_OUT_INIT;
if (user_state[user_name] !='O'){ //이미 비어있는 좌석인 경우
LCD_Clear();
LCD_Pos(0,0);
LCD_Str("Empty Seat");
delay_ms(1000);
fnd[0]=0;
STATE = START;
}
}
break;
case CHECK_PNUM_OUT_INIT: //비밀번호 일치시 탈출 전 설정
fnd[0]=0;
fnd[1] =0;
LCD_Clear();
LCD_Pos(0,0);
LCD_Str(check_pnumber);
LCD_Pos(1,0);
LCD_Str("1:Yes 2:No");
STATE = CHECK_PNUM_OUT;
break;
case CHECK_PNUM_OUT: //비밀번호 일치시 탈출
if (fnd[1] ==1 && fnd[0] ==10){
int cnt =0;
for(i =0;i <11;i ++){
if(num[user_name][i] == check_pnumber[i] ) cnt ++; }
if (cnt ==11 ){ //비밀번호 일치
LCD_Clear();
LCD_Pos(0,0);
LCD_Str("User Check") ;
LCD_Pos(1,0);
LCD_Str("Good Bye") ;
OCR1A =3000;
delay_ms(5000);
OCR1A =4710;
user_state[user_name] ='X';
STATE = START;
fnd[0] =0;
}
else{ //비밀번호 불일치
LCD_Clear();
LCD_Pos(0,0);
LCD_Str("Wrong Password") ;
delay_ms(2000);
fnd[0] =0;
STATE = START;
}
}
else if (fnd[1] ==2 && fnd[0] ==10) STATE = INPUT_PHONE_OUT_INIT;
break;
case INPUT_PHONE_OUT: //퇴장 : 폰번호 입력
if (fnd[0] ==10){
sprintf(check_pnumber, "%d%d%d%d%d%d%d%d%d%d%d", fnd[11],fnd[10],fnd[9],fnd[8],fnd[7],fnd[6],fnd[5],fnd[4],fnd[3],fnd[2],fnd[1]);
STATE = CHECK_PNUM_OUT_INIT;
}
delay_ms(10);
break;
case INPUT_PHONE_OUT_INIT: //퇴장 : 폰번호 입력 전 설정
fnd[0]=0;
LCD_Clear();
LCD_Pos(0,0);
LCD_Str("Input PhoneNum") ;
STATE = INPUT_PHONE_OUT;
break;
}
}
}
코드를 단순히 설명하자면 아래 멘트로 설명할 수 있겠다.
동작 구현을 위해서 크게 세 파트로 나누어서 구현을 진행하였습니다. 초음파 센서를 일정 시간마다 쏘아서 검출하면서 자리가 사용 되어 있는 자리이면서 비어있는 시간이 일정 시간(데모모델 약 10초) 이후까지 비어있음을 감지하기 위한 부분, LCD상에서 사용자가 직접 번호를 누르고 입장하거나 번호를 누르고 일치하는 경우에만 퇴장하는 등의 기능을 구현한 부분, USART를 사용하여 관리자가 번호조회 및 문 제어가 가능한 부분으로 이루어져 있고 그 외에 부저,FND,키패드 등의 사용을 위한 부분이 들어 있습니다.
특히, LCD제어를 위해서 STATE변수를 사용하여 제어하였습니다. STATE를 사용하여 LCD를 제어할 경우 상태로 동작하기에 코드 이해가 수월하며 언제든 원하는 동작으로 문제없이 돌아올 수 있는 장점이 있어 위 방식을 선택하였습니다.
헤더파일로는 위에서 소개했던 5개의 파일이 들어가며 위 구동함수에서 설명하였던 함수들을 모아서 기능을을 구현하였습니다. 더 자세한 설명은 아래코드를 진행하며 주석으로 더 자세히 서술하였습니다.
기본적으로 키패드 숫자로 동작하며, O는 사용중, X는 비어있음, E는 짐이 있을 수 있으나 치우고 사용가능한 좌석을 의미한다. 특히 사용자의 입력버튼은 *버튼이라 가정하였고 m1버튼으로는 FND초기화, m2버튼으로는 사용자 초기화면으로 돌아갈 수 있다.
특히 코드중간중간 주석에 ... 처리한 부분이 있는데 그부분의 설명은 아래와 같다.
[ 1 ] : LCD에서 enter키도 눌리면 저장되어서 첫 번째 단어가 \n이 들어가는 문제가 발생하여 \n을 삭제해주는 방법 대신 비밀번호 앞에 의미없는 단어를 넣어서 동작하도록 수행하였습니다.
[ 2 ] : sprintf()를 사용하여 문자열을 바꿀 때, 그 전에 사용한 sprintf()를 이용하여 바꾼 문자열에 쓰레기값으로 바뀌는 현상이 일어나서 sprintf()를 사용함과 동시에 깊은복사를 통해 값을 저장해줌으로 문제를 해결하였습니다.
[ 3 ] : pwm신호를 위한 타이머 1번은 keypad.h 헤더안에 들어있습니다.
[ 4 ] : 초음파센서를 매 while문마다 탐지하는 것이 아닌, 일정 시간마다 주소를 바꿔가면서 1 > 2 > 3 > 1 > 2 > .. 순서로 바꿔가면서 탐지하는 방식으로 설정하였습니다.
[ 5 ] : zero_flag가 없다면 keypad가 항상 값을 0으로 받아오게 설정하였기 때문에 해당 flag를 이용하여 0이 눌렸을 경우에만 0으로 값을 인정하게 받아오도록 설정하였습니다. 이는 keypad.h의 함수에서 아무것도 안 눌릴 경우 0으로 반환하게 설정했기 때문입니다.
[ 6 ] : OUTFND()를 통하여 출력을 구현할 때 인수로 int형의 숫자를 출력하게끔 설정하였기 때문에 finalnum변수를 통하여 변환 과정이 필요했습니다.
[ 7 ] : STATE를 위한 LCD제어에서 어떠한 키 입력을 대기하는 상태와 LCD를 출력하는 상태를 분리하여 코드를 볼 때 덜 헷갈리게 하였고 또한 LCD가 계속해서 출력하는 상태를 방지하여 주었습니다.
'프로젝트 > 스마트독서실 시스템' 카테고리의 다른 글
06_구현 2단계_퇴장 및 비밀번호 일치 (0) | 2021.12.06 |
---|---|
06_프로그램 구현 1단계 (0) | 2021.12.02 |
05_구동함수_Keypad,FND,Buzzer (0) | 2021.11.19 |
04_구동함수_Servo motor (0) | 2021.11.19 |
03_구동함수_ONE025 (0) | 2021.11.19 |