2023. 3. 2. 18:59ㆍAVR ATmega128
1. State Machine 설계
상태 머신은 Fan 상태를 기준으로 동작하고, Fan이 OFF 상태일때는 Timer는 동작하지 않는다.
Fan이 ON되면 기본적으로 타이머가 꺼진 상태로 동작하고, Timer 버튼을 누르면 타이머의 시간을 설정할 수 있다.
현재는 디버그용으로 5초, 10초로 동작하게 설정되어있다. 시간이 경과한후 Timer상태는 Timer_OFF상태, Fan상태는 FAN 꺼짐 상태로 동작하게 된다.
2. S/W Stack 설계
두번째로는 소프트웨어 스택을 설계하였다. 소프트웨어 스택 방식으로 코딩하면 디버그와 모듈 추가가 그렇지 않을 때보다 훨씬 쉬워지는 장점이 있다.
main()함수에서 바로 하드웨어 핀을 제어하는게 아닌, 메인함수는 Application 단을 호출하고, Application단은 다시 필요한 H/W 드라이버를 호출하고, 드라이버는 GPIO를 호출해 하드웨어를 제어하게 되는 방식이다.
/*
* Button.c
*
* Created: 2023-02-21 오전 11:12:30
* Author: rhoblack
*/
#include "Button.h"
void Button_init(button_t *btn, volatile uint8_t *DDR, volatile uint8_t *PIN, uint8_t pinNum)
{
btn->DDR = DDR;
btn->PIN = PIN;
btn->pinNum = pinNum;
btn->prevState = RELEASED;
Gpio_initPin(btn->DDR, btn->pinNum, INPUT);
}
uint8_t Button_getState(button_t *btn)
{
uint8_t curState = Gpio_readPin(btn->PIN, btn->pinNum);
if ( (curState == PUSHED) && (btn->prevState == RELEASED) ) {
_delay_ms(10); // debounce code
btn->prevState = PUSHED;
return ACT_PUSHED;
}
else if ( (curState != PUSHED) && (btn->prevState == PUSHED) ){
_delay_ms(10); // debounce code
btn->prevState = RELEASED;
return ACT_RELEASED;
}
return ACT_NONE;
}
위의 코드는 버튼 드라이버인데, init 부분을 보면 PORT를 직접 제어하는게 아닌 GPIO내의 함수를 사용하는 것을 볼 수 있다.
3. Class Diagram
Application은 Listener, Model, Service, Presenter가 있다.
Listener은 입력란과 상태 머신을 담당하고, Model은 Application 내에서 각각의 객체들이 서로 정보를 주고받기위한 데이터베이스, Service는 상태 머신에서 지정된 상태에 대한 동작을 실행하는 객체이고, 마지막으로 Presenter는 출력장치를 제어하기 위한 객체이다.
C언어는 객체지향언어가 아니기 때문에 실제 객체처럼 동작하진 않지만, 객체지향언어와 유사하게 동작시킨다.
3-1 Listener
/*
* Listener.c
*
* Created: 2023-02-27 오전 10:40:09
* Author: rhoblack
*/
#include "Listener.h"
button_t btnMode, btnTimer, btnTimerPlus;
void Listener_init()
{
Button_init(&btnTimer, &DDRA, &PINA, 1);
Button_init(&btnMode, &DDRA, &PINA, 0);
Button_init(&btnTimerPlus, &DDRA, &PINA, 2);
}
void Listener_checkEvent() //선풍기의 상태 머신
{
uint8_t fanModeState = Model_getFanStateData();
uint8_t buzzerState = Model_getBuzzerOnStateData();
switch(fanModeState)
{
case FAN_STOP :
if(Button_getState(&btnMode) == ACT_RELEASED)
{
buzzerState = BUZZER_POWERONSOUND;
fanModeState = FAN_POWER_ONE;
Model_setBuzzerOnStateData(buzzerState);
Model_setFanStateData(fanModeState);
}
break;
case FAN_POWER_ONE:
Listener_timerEvent();
if(Button_getState(&btnMode) == ACT_RELEASED)
{
buzzerState = BUZZER_BUTTONSOUND;
fanModeState = FAN_POWER_TWO;
Model_setFanStateData(fanModeState);
Model_setBuzzerOnStateData(buzzerState);
}
break;
case FAN_POWER_TWO :
Listener_timerEvent();
if(Button_getState(&btnMode) == ACT_RELEASED)
{
buzzerState = BUZZER_POWEROFFSOUND;
fanModeState = FAN_STOP;
Model_setFanStateData(fanModeState);
Model_setBuzzerOnStateData(buzzerState);
}
break;
}
}
void Listener_timerEvent()
{
uint8_t timerModeState = Model_getTimerStateData(); //오류의원인
uint8_t timerInitState = Model_getTimerInitData();
uint8_t buzzerState = Model_getBuzzerOnStateData();
switch(timerModeState)
{
case TIMER_OFF :
if(Button_getState(&btnTimer) == ACT_RELEASED)
{
buzzerState = BUZZER_BUTTONSOUND;
Model_setBuzzerOnStateData(buzzerState);
timerInitState = 1;
Model_setTimerInitData(timerInitState);
timerModeState = TIMER_30;
Model_setTimerStateData(timerModeState);
}
break;
case TIMER_30 :
if(Button_getState(&btnTimer) == ACT_RELEASED)
{
buzzerState = BUZZER_BUTTONSOUND;
Model_setBuzzerOnStateData(buzzerState);
timerInitState = 1;
Model_setTimerInitData(timerInitState);
timerModeState = TIMER_60;
Model_setTimerStateData(timerModeState);
}
break;
case TIMER_60 :
if(Button_getState(&btnTimer) == ACT_RELEASED)
{
buzzerState = BUZZER_BUTTONSOUND;
Model_setBuzzerOnStateData(buzzerState);
timerInitState = 0;
Model_setTimerInitData(timerInitState);
timerModeState = TIMER_OFF;
Model_setTimerStateData(timerModeState);
}
break;
}
}
Listener애서는 버튼 입력을 체크해 상태를 분기하는 역할을 맡고 있다. 선풍기가 꺼져있을 때에는 타이머가 동작하지 않게 하려면 FAN 상태머신을 메인 상태머신으로 두고, 팬이 켜진 상태에서 TimerEvent가 동작할 수 있게 설계하였다.
timerInit변수는 타이머 상태가 변할 때 딱 한번만 초기화할 수 있게 만들어주는 변수이다. 해당 변수를 사용하지 않으면
버튼입력과 무관하게 main()의 무한루프에서 계속 타이머를 초기화하게 된다.
3-2 Model
/*
* Model_BuzzerOnState.c
*
* Created: 2023-03-02 오전 9:25:36
* Author: yrt12
*/
#include "Model_BuzzerOnstate.h"
uint8_t buzzerStateData;
uint8_t Model_getBuzzerOnStateData()
{
return buzzerStateData;
}
void Model_setBuzzerOnStateData(uint8_t state)
{
buzzerStateData = state;
}
모델은 다음과 같이 현재 상태를 저장한다. 애플의 아이클라우드나 구글의 구글 드라이브같은 클라우드 서비스처럼 생각하면 이해가 쉽다. Listener에서 사용자의 입력에 따라 상태를 변경해 Model에 업데이트 하면, 상태에 따라 동작해야하는 Service에서 Model에 저장된 상태 변수를 불러와 변한 상태에 따라 동작을 실행한다. 마치 핸드폰에서 사진을 찍고 클라우드에 동기화시키면, 태블릿 PC나 컴퓨터에서 해당 사진을 불러와 편집 등의 동작을 실행하는 것처럼 동작한다.
3-1 Service
/*
* Serivce_Buzzer.c
*
* Created: 2023-03-02 오전 9:43:23
* Author: yrt12
*/
#include "Serivce_Buzzer.h"
void Buzzer_run()
{
uint8_t buzzerState = Model_getBuzzerOnStateData();
switch(buzzerState)
{
case BUZZER_OFF :
Presenter_buzzerSound(BUZZER_OFF);
break;
case BUZZER_POWERONSOUND :
Presenter_buzzerSound(BUZZER_POWERONSOUND);
Model_setBuzzerOnStateData(BUZZER_OFF);
break;
case BUZZER_POWEROFFSOUND :
Presenter_buzzerSound(BUZZER_POWEROFFSOUND);
Model_setBuzzerOnStateData(BUZZER_OFF);
break;
case BUZZER_BUTTONSOUND:
Presenter_buzzerSound(BUZZER_BUTTONSOUND);
Model_setBuzzerOnStateData(BUZZER_OFF);
break;
}
}
다음은 Service이다. Service는 Listener에서 사용자의 입력을 받아 상태를 변경하면, 변경된 상태에 따른 동작을 수행한다. 위의 코드는 ON-OFF-팬 상태 변경에 따라 각각 다른 소리를 출력하는 부저의 Service이다.
모델에서 값을 전달받아 해당하는 동작을 실행시키는 모습을 볼 수 있다. 여기서는 상태가 변경되었을때 한번만 수행할 수 있게 부저상태변수를 사용하였다.
3-4 Presenter
/*
* Presenter.c
*
* Created: 2023-02-27 오전 11:28:28
* Author: rhoblack
*/
#include "Presenter.h"
void Prsenter_init()
{
Buzzer_init();
FND_init();
LCD_init();
}
void Presenter_dispTimeClock(uint8_t hour, uint8_t min, uint8_t sec, uint16_t milisec){
static uint8_t prevMilisec = 0xff;
if ((milisec/10) == prevMilisec) return;
prevMilisec = milisec/10;
char buff[30];
sprintf(buff, "CLOCK:%02d:%02d:%02d", hour, min, sec);
LCD_writeStringXY(0, 0, buff);
}
void Presenter_dispTimeLevel(uint8_t fanStateData)
{
FND_setFndData(fanStateData);
}
void Presenter_dispTimer (uint8_t sec)
{
char buff2[30];
sprintf(buff2, "timer = %2d ",sec);
LCD_writeStringXY(1, 0, buff2);
}
void Presenter_buzzerSound(uint8_t buzzerState)
{
if (buzzerState == BUZZER_OFF) Buzzer_soundOff();
if (buzzerState == BUZZER_POWERONSOUND) Buzzer_powerOnSound();
if (buzzerState == BUZZER_POWEROFFSOUND) Buzzer_powerOffSound();
if (buzzerState == BUZZER_BUTTONSOUND) Buzzer_buttonSound();
}
Presenter는 서비스에서 수행한 동작을 사용자가 알아볼 수 있게 출력장치를 제어하는 역할을 한다. 각각의 코드를 보면
위에서부터 LCD, FND, BUZZER를 제어하는 모습을 볼 수 있고, 해당 함수에서 포트를 직접 제어하는게 아닌 드라이버와 GPIO를 거쳐서 하드웨어를 제어하는 모습을 볼 수 있다.
4. 동작 영상
'AVR ATmega128' 카테고리의 다른 글
UART 활용 (0) | 2023.03.06 |
---|---|
UART (0) | 2023.03.06 |
I2C(TWI) LCD (1) | 2023.03.03 |
초음파센서 드라이브 만들기 (0) | 2023.03.03 |
AVR초음파 센서 (0) | 2023.03.03 |