오랜 시간 중단되었던 온도조절 인두기 프로젝트를 다시 꺼낸건 3학년 2학기가 끝나고 겨울방학이 시작한 12월 말이었습니다.
다시 시작할때는 이제 코딩만 하면 된다는 생각에 큰 부담은 없었습니다.
- 단지, 방학이 시작함과 동시에 몰아닥친 귀차니즘이 가장 큰 적이 되었죠..
하드웨어를 제어하는 펌웨어를 만들 때는, 주변 장치를 제어하는 드라이버 역할을 하는 장치 제어 모듈들을 따로 만들어서 검증한 다음, main에서 이들을 통합하는 순서로 진행하면 디버깅 없이 깔끔하게 작성할 수 있습니다.
소프트웨어 제작과정에서 예상대로 딱히 큰 어려움은 없었습니다..
- 귀차니즘때문에 하루면 코딩할거 며칠씩 붙잡고 있었던 현상만 제외하면 말이죠.
AVR에 C++ 객체지향 프로그래밍을 적용한 첫 프로젝트였던 만큼, 모듈화와 재사용성을 고려하여 프로그램을 작성하였습니다. 각 모듈별로 추후 다방면으로 응용이 가능한 요소들을 추가하기도 했습니다.
▲ 제어 소프트웨어 구조
- Button : 온도조절 버튼 2개를 제어하는 모듈. 단순 클릭만 감지하는 것이 아니라, 계속 누르고 있을 경우 지속적으로 이벤트를 발생시키는 Key Repeat 감지 기능도 지원합니다.
- Buzzer : 부저를 켜고 끄는 모듈. 뒤에서 설명할 Note Player에서 내부적으로 이 모듈을 활용합니다.
- Config : EEPROM을 제어하여 환경설정을 저장하거나 읽어오는 모듈. 마지막 설정 온도를 기억하고, 부저로부터 발생하는 소리를 켜고 끄는 기능을 지원합니다.
- Display : 4자리 숫자판(FND)을 제어하는 모듈. C언어의 printf와 같은 인터페이스를 제공하고, 숫자 뿐만 아니라 알파벳의 일부와 특수문자도 표시할 수 있습니다.
- Heater : 히터에 공급되는 24V를 외부 제어로 단속하고, 히터의 현재 온도를 지속적으로 계산하여 지정된 Event Listener로 반환합니다.
- Light : 기판 하단에 부착된 두 개의 상태표시등(청, 적)을 제어합니다.
- NotePlayer : 악보를 입력받아서 Buzzer 모듈을 통해 재생하는 모듈. 지금 생각해도 사실 이 모듈이 구지 온도조절 인두기에 구현될 필요는 없습니다. 단지 재미와 추후 재사용성을 위해 구현해 보았습니다. Timer를 활용여 구현하였기 때문에 Main routine을 Blocking하지 않고 마치 음악 재생 쓰레드가 새로 생성된 것과 유사하게 동작합니다.
다음은 모든 모듈들을 통합하여 인두기를 구동하는 Main Routine의 원본 소스코드입니다.
#define F_CPU 8000000UL #define __DELAY_BACKWARD_COMPATIBLE__ #include <avr/io.h> #include <avr/interrupt.h> #include <stdio.h> #include <stdarg.h> #include <stdlib.h> #include <util/delay.h> #include "include/Display.cpp" #include "include/Light.cpp" #include "include/Buzzer.cpp" #include "include/NotePlayer.cpp" #include "include/Button.cpp" #include "include/Config.cpp" #include "include/Heater.cpp" #define TEMPERATURE_IDLE 455 static unsigned char melodyTemperatureIncrease[][3] = { {4, NP_SYB_C, NP_DUR_32}, {4, NP_SYB_E, NP_DUR_32} }; static unsigned char melodyTemperatureDecrease[][3] = { {4, NP_SYB_E, NP_DUR_32}, {4, NP_SYB_C, NP_DUR_32} }; static unsigned char melodyHeaterOff[][3] = { {5, NP_SYB_C, NP_DUR_32}, {4, NP_SYB_G, NP_DUR_32}, {4, NP_SYB_E, NP_DUR_32}, {4, NP_SYB_C, NP_DUR_32} }; static unsigned char melodyBoot[][3] = { {4, NP_SYB_E, NP_DUR_16}, {4, NP_SYB_Eb, NP_DUR_16}, {4, NP_SYB_E, NP_DUR_16}, {4, NP_SYB_Eb, NP_DUR_16}, {4, NP_SYB_E, NP_DUR_16}, {4, NP_SYB_B, NP_DUR_16}, {4, NP_SYB_D, NP_DUR_16}, {4, NP_SYB_C, NP_DUR_16}, {4, NP_SYB_A, NP_DUR_8} }; bool gFlagSoundEnable; unsigned int gTemperature; void * operator new(size_t size) { return malloc(size); } void * operator new[](size_t size) { return malloc(size); } void operator delete(void * ptr) { free(ptr); } void operator delete[](void * ptr) { free(ptr); } void ControlHeater(); void DisplayTemperature(); void StartIncreaseTemperature(); void IncreaseTemperature(); void StartDecreaseTemperature(); void DecreaseTemperature(); void SaveTemperature();
/************************** Caution ************************ Set compiler optimization option to 'Optimize Most(-O3)'. Otherwise, EEPROM write procedure will not operate properly. ************************************************************/ #include "AdjustableSolderingIron.hpp" void ControlHeater() { unsigned int currentTemperature = hHeater->GetTemperature(); if (gTemperature == TEMPERATURE_IDLE) { hHeater->Off(); hLight->Off(LIGHT_RED); if (currentTemperature > 64) hLight->On(LIGHT_BLUE); else hLight->Off(LIGHT_BLUE); } else { hLight->Off(LIGHT_BLUE); if (currentTemperature <= gTemperature - 5) { hHeater->On(); hLight->On(LIGHT_RED); } else if (currentTemperature >= gTemperature + 10) { hHeater->Off(); hLight->Off(LIGHT_RED); } } } void DisplayTemperature() { if (gTemperature == TEMPERATURE_IDLE) hDisplay->Printf(" 0FF"); else hDisplay->Printf("%3u.c", gTemperature); } void StartIncreaseTemperature() { IncreaseTemperature(); if (gFlagSoundEnable) { if (gTemperature != TEMPERATURE_IDLE) hNotePlayer->SetNote(melodyTemperatureIncrease, 2, 0.5); else hNotePlayer->SetNote(melodyHeaterOff, 4, 0.5); hNotePlayer->Play(); } } void IncreaseTemperature() { if (gTemperature == TEMPERATURE_IDLE) gTemperature = 200; else if (gTemperature + 25 <= 450) gTemperature += 25; else gTemperature = TEMPERATURE_IDLE; DisplayTemperature(); } void StartDecreaseTemperature() { DecreaseTemperature(); if (gFlagSoundEnable) { if (gTemperature != TEMPERATURE_IDLE) hNotePlayer->SetNote(melodyTemperatureDecrease, 2, 0.5); else hNotePlayer->SetNote(melodyHeaterOff, 4, 0.5); hNotePlayer->Play(); } } void DecreaseTemperature() { if (gTemperature == TEMPERATURE_IDLE) gTemperature = 450; else if (gTemperature - 25 >= 200) gTemperature -= 25; else gTemperature = TEMPERATURE_IDLE; DisplayTemperature(); } void SaveTemperature() { hConfig->SetLastTemperature(gTemperature); } int main(void) { // Wait for power to be stable _delay_ms(10); // Change sound flag if button is pushed when booting if (hButton->GetButtonState(BUTTON_0) == BUTTON_STATE_DOWN) { gFlagSoundEnable = true; hConfig->SetSoundFlag(true); } else if (hButton->GetButtonState(BUTTON_1) == BUTTON_STATE_DOWN) { gFlagSoundEnable = false; hConfig->SetSoundFlag(false); } else { gFlagSoundEnable = hConfig->GetSoundFlag(); } // Load last temperature gTemperature = hConfig->GetLastTemperature(); // Validate temperature correctness (200 ~ 450 Celsius, Step = 25, 455 = IDLE) if (gTemperature != TEMPERATURE_IDLE && (gTemperature < 200 || gTemperature > 450 || (gTemperature % 25 != 0))) { gTemperature = 350; hConfig->SetLastTemperature(gTemperature); } // Booting Completed sei(); if (gFlagSoundEnable) { hNotePlayer->SetNote(melodyBoot, 9, 0.3); hNotePlayer->Play(); } DisplayTemperature(); hHeater->SetEventListener(HEATER_EVENT_TEMPERATURE_CHANGED, ControlHeater); hButton->SetEventListener(BUTTON_0, BUTTON_EVENT_KEYDOWN, StartIncreaseTemperature); hButton->SetEventListener(BUTTON_0, BUTTON_EVENT_KEYPRESS, IncreaseTemperature); hButton->SetEventListener(BUTTON_0, BUTTON_EVENT_KEYUP, SaveTemperature); hButton->SetEventListener(BUTTON_1, BUTTON_EVENT_KEYDOWN, StartDecreaseTemperature); hButton->SetEventListener(BUTTON_1, BUTTON_EVENT_KEYPRESS, DecreaseTemperature); hButton->SetEventListener(BUTTON_1, BUTTON_EVENT_KEYUP, SaveTemperature); while (1); }
프로젝트 설정에서 컴파일러 옵션 중 최적화(Optimization)를 -O3 로 설정해야 정상적으로 빌드됩니다. 최적화 하지 않으면 프로그램이 8KByte를 초과하고, 최적화 옵션을 낮게 설정하면 EEPROM Write가 정상적으로 이루어지지 않습니다.
(컴파일 옵션과 EEPROM Write의 연관성과 문제점은 추후 다른 포스트에서 다루도록 하겠습니다.)
▲ ADC Result - 히터 온도 변환 테이블 데이터 측정
사진을 보면 알 수 있지만, 작업하는 장소가 바뀐것을 알 수 있습니다. 그만큼 그 사이 엄청난 시간간극이... 이곳은 익산 제 방 테이블이 아닌 학교 기숙사입니다.
그렇게 소프트웨어 작성도 모두 끝나고, 칩에 굽는데도 성공했습니다. 그러나, 이것이 끝이 아니었습니다...
근본적인 문제가 해결되지 않은 하드웨어상의 문제점이 다시 고개를 든 것입니다... Hㅏ..
- 다음 글에 계속 -