올인원 태양광 파워 허브: 충전 컨트롤러, 인버터, 파워뱅크 & LED 램프 통합
부품 및 소모품
마이크로칩 ATmega8
1개
벅 컨버터 (Buck Converter)
1개
3W 알루미늄 PCB LED (웜 화이트, 5팩)
1개
노키아 5110 디스플레이
1개
히트 싱크
1개
N-채널 MOSFET
4개
P-채널 MOSFET
1개
태양광 패널 40W 이하 (12V 시스템용)
1개
리튬 이온 배터리 18650 셀
1개
DC-DC 0.9V-5V to USB 부스트 모듈
1개
범용 NPN 트랜지스터
1개
12-0-12/220 센터 탭 트랜스포머
1개
1N4007 고전압 다이오드
1개
수컷 헤더 36핀 (0.1")
1개
슬라이드 스위치
1개
푸시버튼 스위치 12mm
1개
아두이노 프로토 실드
1개
스트립/베로/프로토 보드 8x11cm
1개
아두이노 우노 (Arduino UNO)
1개
아두이노 나노 R3
1개
아두이노 메가 2560
1개
저순방향 전압 쇼트키 다이오드 5822
1개
커패시터 10µF
1개
커패시터 100nF
1개
필요 도구 및 장비
앱 및 온라인 서비스
프로젝트 개요
이 프로젝트에 대하여
몇 개의 태양광 패널, 12V 배터리, 트랜스포머 등이 오랫동안 방치되어 있어 이를 활용한 장치를 만들기로 했습니다. 이렇게 탄생한 것이 이 장치입니다. 단일 보드 위의 완전한 소형 PV 솔루션입니다. 각 기능이 무엇인지 살펴보죠.
충전 컨트롤러: 태양광 패널에서 DC 배터리 충전과 부하에 의한 방전을 제어해 배터리 수명 연장
인버터: DC 전기를 AC로 변환해 AC 부하 구동
파워뱅크: USB 포트로 5V DC 공급해 모바일 기기 휴대/오프그리드 충전
비상 램프: 정전, 캠핑, 야간 야외 활동용 초고휘도 LED 조명
이 장치는 위 모든 기능을 갖추고 있으며, 마지막 두 기능(파워뱅크, 비상 램프)은 온보드 리튬 이온 배터리로 독립 구동됩니다. 태양광 패널(최대 100W), 12V 납축 배터리, 트랜스포머만 있으면 모든 기능을 활용할 수 있습니다.
우노 보드 프로토타입 버전
첫 버전은 우노 보드에서 최소 하드웨어와 최소 기능으로 개발했습니다. 노키아 디스플레이를 직접 장착하고 MOSFET 실드로 배터리 충방전 제어. 메뉴 제어, 인버터, PWM 충전, 고급 기능 없음. 단순 온/오프 충전과 배터리/패널 전압 표시만. 기능은 하지만 매력이 없었습니다.
프로토보드 완전판 버전
다음과 같은 기능으로 새로 개발했습니다:
최대 100W 패널 지원 PWM 태양광 충전 컨트롤러
정현파 미니 60W 인버터
최대 3개 12V DC 부하 제어
주 DC 부하 자동 온/오프 기능 지원
독립형 USB 파워뱅크
벅 레귤레이터 모듈
밝기/깜빡임 조절 가능한 온보드 LED 램프
2개 버튼/스위치로 스크롤 및 선택하는 메뉴 시스템, 노키아 5110 디스플레이로 옵션 및 상태 표시. 후면은 핫글루로 절연해 금속 물체와의 단락 방지!
노키아 5110 디스플레이 메뉴 접근 예시:
기술적 원리 학습
충전 컨트롤러는 온/오프, PWM, MPPT 타입이 있습니다. 온/오프가 가장 단순(내 1번 버전)하며 배터리 전압이 완충 전압에 접근해도 충전 전류를 조절하지 않습니다.
반면 PWM은 배터리가 차오를수록 충전 전류를 점진적으로 감소시킵니다. PWM 컨트롤러의 이점:
손실된 배터리 용량 회복 및 탈황산화
배터리 충전 수용 능력 향상
평균 배터리 용량 95% 유지
편차 있는 배터리 셀 균등화로 내부 셀 동일 전위 달성
배터리 발열 및 가스 발생 감소로 전해액 손실 방지
노화 지연 및 시스템 수명 연장
하지만 PWM은 패널을 배터리 전압 근처에서 동작하게 해 태양광 패널의 최대 전력을 뽑아내지 못합니다. MPPT가 이 문제의 해결책으로, 기본적으로 조절 가능한 벅-부스트 DC-DC 컨버터로 PWM보다 더 많은 태양광 전력을 변환합니다.
인버터는 구형파, 수정 구형파, 순수 정현파 타입이 있습니다. 구형파 인버터는 설계가 매우 간단하며 소형 DC 부하(AC 램프, CFL, 탁상 선풍기, 납땜 인두)에는 충분하지만, 유도 모터나 정밀 장비/전원장치에는 고조파 왜곡으로 권장하지 않습니다.
수정 구형파는 구형파로 만든 일종의 변형 정현파로 일반 구형파보다 낫습니다. 정현파 인버터가 모든 부하에 최고지만 하드웨어 설계 복잡, 소프트웨어 알고리즘 난이도 높음, 제작 비용 비쌈.
벅 레귤레이터는 강압 DC-DC 컨버터로, 여기서는 패널 전력으로 컨트롤러(아두이노+디스플레이), 독립 USB 파워뱅크, 온보드 LED 램프를 구동하는 4.2V 리튬 이온 배터리 충전에 벅 모듈을 사용했습니다.
USB 파워뱅크는 기본적으로 DC-DC 부스트로, 리튬 이온/리포 배터리의 3.3~4.2V 범위 전압을 USB 버스 전압인 5V로 승압해 모바일 기기 충전용으로 씁니다. 배터리 충전용 벅 컨버터도 내장. 내 프로젝트에서는 벅 모듈이 패널에서 일부 전력을 가져와 온보드 배터리를 충전합니다.
하드웨어 동작 원리
장치 주변 모든 연결을 보여주는 이미지부터 확인하세요:
시스템은 다양한 목적의 하드웨어로 구성되며, 두뇌는 AVR ATmega8A 마이크로컨트롤러(ATmega328P로 마이너 변경 후 사용 가능, 아래 논의)이고 아두이노로 프로그래밍됩니다.
새 ATmega8A에 아두이노 옵티부트8 부트로더를 굽습니다. 번거로우면 부트로더 내장 아두이노 미니/ATmega328 구매 권장.
컨트롤러, 디스플레이, LED 램프, 파워뱅크는 온보드 리튬 이온 배터리로 구동. 두 개의 순간 터치 버튼으로 디스플레이 메뉴 접근해 장치의 다양한 기능 조작. 스위치는 병렬 커패시터로 하드웨어 디바운싱. 슬라이드 스위치로 장치 전원 온/오프.
태양광 충전 기능은 2N2222 트랜지스터 구동 회로로 구동되는 P-MOSFET로 수행되며, 마이크로컨트롤러의 PWM으로 제어. PWM은 외부 배터리 전압 레벨 기반으로 제어. 태양광 패널 전류가 P-MOSFET를 통해 납축 배터리로 흐름. 배터리 완충 시 마이크로컨트롤러가 MOSFET 오프. 충전 중단 후 배터리 전압이 서서히 떨어지면 13.6V 도달 시 낮은 듀티 사이클로 플로트 충전 재개.
12V DC 부하는 N-MOSFET로 게이트 핀을 마이크로컨트롤러에서 제어.
온보드 LED 램프도 N-MOSFET로 구동, 게이트를 PWM 제어해 밝기 조절.
인버터 블록은 2개의 N-MOSFET를 번갈아 온/오프해 AC 시뮬레이션. 외부 센터 탭 트랜스포머 추가로 구형파 AC 출력 구현.
인버터 동작 설명:
센터 탭 승압 트랜스포머의 저압 측 코일 센터 탭을 통해 MOSFET로 전류를 반대 방향으로 번갈아 흐르게 하면 2차 측에 AC 전압 발생. 위쪽 MOSFET 온/아래쪽 오프 → 전류 상향 흐름. 위쪽 오프/아래쪽 온 → 전류 하향 흐름.
두 MOSFET 게이트 신호는 AC 주파수의 2배로 토글해야 함. 다음 그림 참고:
50Hz AC 생성하려면 12-0-12V/220V 센터 탭 트랜스포머 저압 측에 교번 구형파 인가. 50Hz = 파동당 20ms. 따라서 20ms/2 = 10ms, 즉 100회/초로 인버터 구동 MOSFET 게이트 신호 토글.
경고!!!: AC 전압은 인체에 치명적, 사망/부상 유발 가능! 트랜스포머 고압 측을 맨손으로 만지지 마세요!
인버터 기능 미사용 시 2XDC Load 옵션으로 인버터 단자에 12V DC 부하 2개 추가 연결 가능.
소프트웨어의 마법
코드 두 세트 제공: 단일 아두이노 탭에 모든 코드 통합 버전, 기능별 탭 분리 버전.
작업 그룹을 단일 함수로 일반화했습니다. 예:
Get_ADCVal(): 패널, 내부 배터리, 외부 배터리 전압 측정, 20회 샘플링 평균 내 전압 변수 업데이트
Context_Control(): 사용자 상호작용, 액션 제어, 디스플레이 정보 업데이트
Charging_Control(), Discharging_Control(), Load_Control(): 배터리 전압 모니터링, 자동 부하 기능, 과충전/과방전 보호 등 클로즈드 루프 백그라운드 함수
사용자 입력은 인터럽트 구동 순간 스위스로 수집. 스위치 누르면 INT0/INT1 관련 ISR 실행. 휘발성 변수 dp, ds 변경. 세 번째 변수 level 이 dp(디스플레이 포인터)와 ds(디스플레이 콘텐츠 선택자)와 함께 메뉴/서브메뉴 탐색 및 액션 수행 제어.
AVR 16비트 Timer1을 10ms마다 타이머 오버플로 인터럽트 발생하도록 설정, 인버터 기능 켜져 있을 때 PIN_INVP와 PIN_INVN 플립.
모든 Lcd_...() 함수는 디스플레이 제어 관련.
ATmega8A vs ATmega328P (우노)
시스템을 ATmega328P/우노로 쉽게 업그레이드 가능. 코드에서 다음 찾아 교체:
TIMSK → TIMSK1
#define ADC_VREF 2.69 → #define ADC_VREF 1.11
#define ADCVDIV_R1 22 → #define ADCVDIV_R1 8.2
하드웨어에서는 패널/배터리 전압을 ADC 측정 범위로 스케일링하기 위해 22kΩ 대신 8.2kΩ 저항 사용 .
동작 영상
저도 프로젝트 글을 읽기보다 영상부터 봅니다. 저와 같다면 영상 감상하세요!
개선 여지
ATmega8A 플래시 공간 부족으로 중요한 기능 추가 못 함:
소프트웨어 제어 과부하/단락 보호
그래픽 아이콘 및 로고
에너지 측정 및 로깅
백업 시간 추정 및 알람
노트북 충전 옵션
6V 시스템 지원
블루투스 부하 제어
타이머 기반 부하 제어
RTC 시계로 더 많은 기능
IoT 연결 솔루션
이런 프로젝트 계획하신다면 위 기능들 꼭 추가하세요! ATmega328P(우노)나 아두이노 메가가 더 나은 선택일 겁니다. 그래도 제 역할은 다 하고, 저는 만족합니다.
추가 자료
ATmega8 보드 지원 포함 아두이노 1.0.6 다운로드
아두이노 실행 → 도구 > 보드 > Optiboot8 선택
이 방법으로 부트로더 굽기
컴파일 및 업로드
코드
단일 탭 코드 (Arduino)
#define PIN_SCE 12
#define PIN_RESET 13
#define PIN_DC 8
#define PIN_SDIN 7
#define PIN_SCLK 6
#define PIN_INVP 4
#define PIN_INVN 5
#define PIN_LOAD 9 // 12V DC 부하
#define PIN_LAMP 10 // 백색 LED 라이트
#define PIN_BATTPWM 11 // 외부 배터리 충전용 PMOS 구동
#define PIN_BATTint_Sense A2
#define PIN_SOLAR_Sense A0
#define PIN_BATText_Sense A3
#define ADC_VREF 2.695 // 내부 레퍼런스 전압, 아두이노 우노(ATmega328P)는 약 1.11V, 내 ATmega8A는 이 값!
#define ADCVDIV_R1 22 // 22K 전압 분배기 하측 저항
#define ADCVDIV_R2 175 // 175K 전압 분배기 상측 저항
//const uint8_t skulljoke[] PROGMEM ={2,} ; // 핀 2,3은 인터럽트 스위치용
// 핀 0,1은 프로그래밍용
// 핀 리셋은
#define LCD_C LOW
#define LCD_D HIGH
#define LCD_X 84
#define LCD_Y 48
uint8_t x=0;
uint8_t level=0;
uint8_t blinker=0;
boolean Load_Auto_Enable=0;
float maxADC_Voltage=0.0;
float BattInt_Voltage=0.0;
float BattExt_Voltage=0.0;
float PV_Voltage=0.0;
volatile int y=0;
volatile uint8_t dp =0;
volatile uint8_t ds =0;
volatile boolean cycle=0;
volatile uint8_t cdc_level =0;
void setup(void) {
LcdInitialise();
LcdClear();
LcdString("*ARDUBOY PV*");
LcdString(" CONTROLLER,");
LcdString("AC INVERTER,");
LcdString("POWER BANK, ");
LcdString("LAMP THING!!");
delay(3000);
analogReference(INTERNAL);
maxADC_Voltage=(ADC_VREF/ADCVDIV_R1)*(ADCVDIV_R1+ADCVDIV_R2);
pinMode(PIN_LOAD,OUTPUT);
digitalWrite(PIN_LOAD,LOW);
pinMode(2, INPUT_PULLUP);
attachInterrupt(0, SW1, FALLING); // 스위치 1 인터럽트
pinMode(3, INPUT_PULLUP);
attachInterrupt(1, SW2, FALLING); // 스위치 2 인터럽트
}
void loop(void) {
Get_ADCVal();
Context_Control();
Charging_Control();
Discharging_Control();
Load_Control();
LcdClear();
}
///////////// ADC ///////////////
void Get_ADCVal(void) {
int I=0, J=0, K=0;
for(x=0;x<20;x++) {
I+=analogRead(PIN_BATTint_Sense);
J+=analogRead(PIN_BATText_Sense);
K+=analogRead(PIN_SOLAR_Sense);
}
BattInt_Voltage=I/20.0;
BattExt_Voltage=J/20.0;
PV_Voltage=K/20.0;
BattInt_Voltage=maxADC_Voltage*BattInt_Voltage/1023.0;
BattExt_Voltage=maxADC_Voltage*BattExt_Voltage/1023.0;
PV_Voltage=maxADC_Voltage*PV_Voltage/1023.0;
}
//////////// 디스플레이 및 제어 //////////////
void Context_Control(void) {
if(ds==0) {show_menu();}
if(ds==1 && dp == 0) {show_info(); delay(100);}
// 1번 서브메뉴 진입: DC 부하 제어
if(ds==1 && dp == 1) {
level=1; dp=0;
while(level==1) {
int temp=ds;
LcdClear(); show_load_ctrl(); delay(250);
if (dp==0){ if(ds!=temp){Load_Auto_Enable=0; digitalWrite(PIN_LOAD,LOW);}}
if (dp==1){ if(ds!=temp){Load_Auto_Enable=0; digitalWrite(PIN_LOAD,HIGH);}}
if (dp==2){ if(ds!=temp){Load_Auto_Enable=1;}}
if (dp==3){show_load_ctrl(); delay(250); level=0; dp=0; ds=0;}
}
}
// 2번 서브메뉴 진입: 인버터 제어
if(ds==1 && dp == 2) {
level=2; dp=0;
while(level==2) {
int temp=ds;
show_inv_ctrl(); delay(250); LcdClear();
if (dp==0){ if(ds!=temp){Timer1_Init();}}
if (dp==1){ if(ds!=temp){Timer1_DeInit();}}
if (dp==2){ if(ds!=temp){Timer1_DeInit(); digitalWrite(PIN_INVP,1); digitalWrite(PIN_INVN,1);}}
if (dp==3){show_inv_ctrl(); delay(250); level=0; dp=0; ds=0;}
}
}
// 3번 서브메뉴 진입: LED 램프 제어
if(ds==1 && dp == 3) {
level=3; dp=0;
while(level==3) {
int temp=ds;
LcdClear(); show_led_ctrl(); delay(250);
if (dp==0){blinker=0; if(ds!=temp) {if(y<=255){y=y+15; analogWrite(PIN_LAMP,y);}}}
if (dp==1){blinker=0; if(ds!=temp) {if(y>=0){y=y-15; analogWrite(PIN_LAMP,y);}}}
if (dp==2){ if(ds!=temp) {blinker^=1; analogWrite(PIN_LAMP,127); delay(250); analogWrite(PIN_LAMP,0);}}
if (dp==3){show_led_ctrl(); delay(250); level=0; dp=0; ds=0;}
}
}
if(blinker==1) {analogWrite(PIN_LAMP,0);}
delay(250);
if(blinker==1) {analogWrite(PIN_LAMP,127);}
}
/////////////////// 노키아 5110 메뉴 텍스트 ///////////////////
void show_menu(void) {
LcdXY(0,dp); LcdString("->");
LcdXY(15,0); LcdString("Sys Info");
LcdXY(15,1); LcdString("DC Load+");
LcdXY(15,2); LcdString("AC Load~");
LcdXY(15,3); LcdString("LED Lamp");
}
void show_info(void) {
LcdXY(0,0); LcdString("Bat_I="); LcdNumtoString(BattInt_Voltage); LcdString("v");
LcdXY(0,1); LcdString("Bat_E="); LcdNumtoString(BattExt_Voltage); LcdString("v");
LcdXY(0,2); LcdString("Sol_P="); LcdNumtoString(PV_Voltage); LcdString("v");
LcdXY(0,3);
if(BattExt_Voltage > 8.0) {LcdString("Batt Conn OK");} else {LcdString("Connect Batt");}
if (PV_Voltage > 10.5 && cdc_level!=3 && cdc_level!=0) {LcdString("Charging:ON ");} else {LcdString("Charging:OFF");}
if (TCNT1 >= 45535) {LcdString("Inverter:ON");} else {LcdString("Inverter:OFF");}
}
void show_load_ctrl(void) {
LcdXY(0,dp); LcdString("->");
LcdXY(15,0); LcdString("Load Off");
LcdXY(15,1); LcdString("Load On");
LcdXY(15,2); LcdString("Load Auto");
LcdXY(15,3); LcdString("Return");
LcdXY(0,4); LcdString("Must Connect");
LcdXY(0,5); LcdString("12V DC Load");
}
void show_inv_ctrl(void) {
LcdXY(0,dp); LcdString("->");
LcdXY(15,0); LcdString("AC Inv On");
LcdXY(15,1); LcdString("AC Inv Off");
LcdXY(15,2); LcdString("2XDC Load");
LcdXY(15,3); LcdString("Return");
LcdXY(0,4); LcdString("2XDC Load,NO");
LcdXY(0,5); LcdString("TRANSFORMER!");
}
void show_led_ctrl(void) {
LcdXY(0,dp); LcdString("->");
LcdXY(15,0); LcdString("LED ++");
LcdXY(15,1); LcdString("LED --");
LcdXY(15,2); LcdString("LED Blk");
LcdXY(15,3); LcdString("Return");
LcdXY(0,4); LcdString("LED DISABLEs");
LcdXY(0,5); LcdString("When INVR On");
}
////////////// 인터럽트 ISR //////////////
void SW1() { dp++; if(dp>3){dp=0;} }
void SW2() { ds^=1; }
ISR(TIMER1_OVF_vect) {
noInterrupts();
TCNT1 = 45535;
cycle^=1;
if(cycle==0) {
digitalWrite(PIN_INVP,HIGH);
delayMicroseconds(1); // 데드 밴드
digitalWrite(PIN_INVN,LOW);
delayMicroseconds(1);
}
if(cycle==1) {
digitalWrite(PIN_INVP,LOW);
delayMicroseconds(1); // 데드 밴드
digitalWrite(PIN_INVN,HIGH);
delayMicroseconds(1);
}
interrupts();
}
///////////// 노키아 5110 함수 ////////////
static const byte ASCII[][5] = {
{0x00, 0x00, 0x00, 0x00, 0x00}, // 20 space
{0x00, 0x00, 0x5f, 0x00, 0x00}, // 21 !
{0x00, 0x07, 0x00, 0x07, 0x00}, // 22 "
{0x14, 0x7f, 0x14, 0x7f, 0x14}, // 23 #
{0x24, 0x2a, 0x7f, 0x2a, 0x12}, // 24 $
{0x23, 0x13, 0x08, 0x64, 0x62}, // 25 %
{0x36, 0x49, 0x55, 0x22, 0x50}, // 26 &
{0x00, 0x05, 0x03, 0x00, 0x00}, // 27 '
{0x00, 0x1c, 0x22, 0x41, 0x00}, // 28 (
{0x00, 0x41, 0x22, 0x1c, 0x00}, // 29 )
{0x14, 0x08, 0x3e, 0x08, 0x14}, // 2a *
{0x08, 0x08, 0x3e, 0x08, 0x08}, // 2b +
{0x00, 0x50, 0x30, 0x00, 0x00}, // 2c ,
{0x08, 0x08, 0x08, 0x08, 0x08}, // 2d -
{0x00, 0x60, 0x60, 0x00, 0x00}, // 2e .
{0x20, 0x10, 0x08, 0x04, 0x02}, // 2f /
{0x3e, 0x51, 0x49, 0x45, 0x3e}, // 30 0
{0x00, 0x42, 0x7f, 0x40, 0x00}, // 31 1
{0x42, 0x61, 0x51, 0x49, 0x46}, // 32 2
{0x21, 0x41, 0x45, 0x4b, 0x31}, // 33 3
{0x18, 0x14, 0x12, 0x7f, 0x10}, // 34 4
{0x27, 0x45, 0x45, 0x45, 0x39}, // 35 5
{0x3c, 0x4a, 0x49, 0x49, 0x30}, // 36 6
{0x01, 0x71, 0x09, 0x05, 0x03}, // 37 7
{0x36, 0x49, 0x49, 0x49, 0x36}, // 38 8
{0x06, 0x49, 0x49, 0x29, 0x1e}, // 39 9
{0x00, 0x36, 0x36, 0x00, 0x00}, // 3a :
{0x00, 0x56, 0x36, 0x00, 0x00}, // 3b ;
{0x08, 0x14, 0x22, 0x41, 0x00}, // 3c <
{0x14, 0x14, 0x14, 0x14, 0x14}, // 3d =
{0x00, 0x41, 0x22, 0x14, 0x08}, // 3e >
{0x02, 0x01, 0x51, 0x09, 0x06}, // 3f ?
{0x32, 0x49, 0x79, 0x41, 0x3e}, // 40 @
{0x7e, 0x11, 0x11, 0x11, 0x7e}, // 41 A
{0x7f, 0x49, 0x49, 0x49, 0x36}, // 42 B
{0x3e, 0x41, 0x41, 0x41, 0x22}, // 43 C
{0x7f, 0x41, 0x41, 0x22, 0x1c}, // 44 D
{0x7f, 0x49, 0x49, 0x49, 0x41}, // 45 E
{0x7f, 0x09, 0x09, 0x09, 0x01}, // 46 F
{0x3e, 0x41, 0x49, 0x49, 0x7a}, // 47 G
{0x7f, 0x08, 0x08, 0x08, 0x7f}, // 48 H
{0x00, 0x41, 0x7f, 0x41, 0x00}, // 49 I
{0x20, 0x40, 0x41, 0x3f, 0x01}, // 4a J
{0x7f, 0x08, 0x14, 0x22, 0x41}, // 4b K
{0x7f, 0x40, 0x40, 0x40, 0x40}, // 4c L
{0x7f, 0x02, 0x0c, 0x02, 0x7f}, // 4d M
{0x7f, 0x04, 0x08, 0x10, 0x7f}, // 4e N
{0x3e, 0x41, 0x41, 0x41, 0x3e}, // 4f O
{0x7f, 0x09, 0x09, 0x09, 0x06}, // 50 P
{0x3e, 0x41, 0x51, 0x21, 0x5e}, // 51 Q
{0x7f, 0x09, 0x19, 0x29, 0x46}, // 52 R
{0x46, 0x49, 0x49, 0x49, 0x31}, // 53 S
{0x01, 0x01, 0x7f, 0x01, 0x01}, // 54 T
{0x3f, 0x40, 0x40, 0x40, 0x3f}, // 55 U
{0x1f, 0x20, 0x40, 0x20, 0x1f}, // 56 V
{0x3f, 0x40, 0x38, 0x40, 0x3f}, // 57 W
{0x63, 0x14, 0x08, 0x14, 0x63}, // 58 X
{0x07, 0x08, 0x70, 0x08, 0x07}, // 59 Y
{0x61, 0x51, 0x49, 0x45, 0x43}, // 5a Z
{0x00, 0x7f, 0x41, 0x41, 0x00}, // 5b [
{0x02, 0x04, 0x08, 0x10, 0x20}, // 5c \
{0x00, 0x41, 0x41, 0x7f, 0x00}, // 5d ]
{0x04, 0x02, 0x01, 0x02, 0x04}, // 5e ^
{0x40, 0x40, 0x40, 0x40, 0x40}, // 5f _
{0x00, 0x01, 0x02, 0x04, 0x00}, // 60 `
{0x20, 0x54, 0x54, 0x54, 0x78}, // 61 a
{0x7f, 0x48, 0x44, 0x44, 0x38}, // 62 b
{0x38, 0x44, 0x44, 0x44, 0x20}, // 63 c
{0x38, 0x44, 0x44, 0x48, 0x7f}, // 64 d
{0x38, 0x54, 0x54, 0x54, 0x18}, // 65 e
{0x08, 0x7e, 0x09, 0x01, 0x02}, // 66 f
{0x0c, 0x52, 0x52, 0x52, 0x3e}, // 67 g
{0x7f, 0x08, 0x04, 0x04, 0x78}, // 68 h
{0x00, 0x44, 0x7d, 0x40, 0x00}, // 69 i
{0x20, 0x40, 0x44, 0x3d, 0x00}, // 6a j
{0x7f, 0x10, 0x28, 0x44, 0x00}, // 6b k
{0x00, 0x41, 0x7f, 0x40, 0x00}, // 6c l
{0x7c, 0x04, 0x18, 0x04, 0x78}, // 6d m
{0x7c, 0x08, 0x04, 0x04, 0x78}, // 6e n
{0x38, 0x44, 0x44, 0x44, 0x38}, // 6f o
{0x7c, 0x14, 0x14, 0x14, 0x08}, // 70 p
{0x08, 0x14, 0x14, 0x18, 0x7c}, // 71 q
{0x7c, 0x08, 0x04, 0x04, 0x08}, // 72 r
{0x48, 0x54, 0x54, 0x54, 0x20}, // 73 s
{0x04, 0x3f, 0x44, 0x40, 0x20}, // 74 t
{0x3c, 0x40, 0x40, 0x20, 0x7c}, // 75 u
{0x1c, 0x20, 0x40, 0x20, 0x1c}, // 76 v
{0x3c, 0x40, 0x30, 0x40, 0x3c}, // 77 w
{0x44, 0x28, 0x10, 0x28, 0x44}, // 78 x
{0x0c, 0x50, 0x50, 0x50, 0x3c}, // 79 y
{0x44, 0x64, 0x54, 0x4c, 0x44}, // 7a z
{0x00, 0x08, 0x36, 0x41, 0x00}, // 7b {
{0x00, 0x00, 0x7f, 0x00, 0x00}, // 7c |
{0x00, 0x41, 0x36, 0x08, 0x00}, // 7d }
{0x10, 0x08, 0x08, 0x10, 0x08}, // 7e ~
{0x78, 0x46, 0x41, 0x46, 0x78} // 7f
};
void LcdCharacter(char character) {
LcdWrite(LCD_D, 0x00);
for (int index = 0; index < 5; index++) {
LcdWrite(LCD_D, ASCII[character - 0x20][index]);
}
LcdWrite(LCD_D, 0x00);
}
void LcdClear(void) {
for (int index = 0; index < LCD_X * LCD_Y / 8; index++) {
LcdWrite(LCD_D, 0x00);
}
}
void LcdInitialise(void) {
pinMode(PIN_SCE, OUTPUT);
pinMode(PIN_RESET, OUTPUT);
pinMode(PIN_DC, OUTPUT);
pinMode(PIN_SDIN, OUTPUT);
pinMode(PIN_SCLK, OUTPUT);
digitalWrite(PIN_RESET, LOW);
digitalWrite(PIN_RESET, HIGH);
LcdWrite(LCD_C, 0x21); // LCD 확장 명령
LcdWrite(LCD_C, 0xA0); // LCD Vop(대비) 설정
LcdWrite(LCD_C, 0x04); // 온도 계수 설정
LcdWrite(LCD_C, 0x14); // LCD 바이어스 모드 1:48
LcdWrite(LCD_C, 0x20); // LCD 기본 명령
LcdWrite(LCD_C, 0x0C); // LCD 정상 모드
}
void LcdString(char *characters) {
while (*characters) {
LcdCharacter(*characters++);
}
}
void LcdWrite(byte dc, byte data) {
digitalWrite(PIN_DC, dc);
digitalWrite(PIN_SCE, LOW);
shiftOut(PIN_SDIN, PIN_SCLK, MSBFIRST, data);
digitalWrite(PIN_SCE, HIGH);
}
void LcdXY(int x, int y) {
LcdWrite(LCD_C, 0x80 | x);
LcdWrite(LCD_C, 0x40 | y);
}
void LcdNumtoString(float number) {
char string[8];
LcdString(dtostrf(number, 5, 2, string));
}
////////////// 백그라운드 루프 제어 함수 ////////////
void Load_Control(void) {
if (Load_Auto_Enable == 1 && PV_Voltage < 4.5 && cdc_level > 0) {digitalWrite(PIN_LOAD,HIGH);}
if (Load_Auto_Enable == 1 && (PV_Voltage > 10.5 || cdc_level==0)) {digitalWrite(PIN_LOAD,LOW);}
}
void Charging_Control(void) {
// 외부 배터리 전압: 14.3V 충전 끄기, 13.5V 이하 충전 켜기, 10.8V 부하 끄기, 12.5V 부하 켜기
// 야간(패널 0V) 충전 시도 방지
if (PV_Voltage > 10.5 && BattExt_Voltage > 8.0) {
if (BattExt_Voltage <= 12.5) { analogWrite(PIN_BATTPWM,255); cdc_level = 0; }
else if (BattExt_Voltage <= 12.9) { analogWrite(PIN_BATTPWM,200); cdc_level = 1; }
else if (BattExt_Voltage <= 13.3) { analogWrite(PIN_BATTPWM,160); cdc_level = 1; }
else if (BattExt_Voltage <= 13.6) { analogWrite(PIN_BATTPWM,120); cdc_level = 2; }
else if (BattExt_Voltage <= 13.9 && cdc_level == 2) { analogWrite(PIN_BATTPWM,80); cdc_level = 2; }
else if (BattExt_Voltage <= 14.3 && cdc_level == 2) { analogWrite(PIN_BATTPWM,40); cdc_level = 2; }
// 과전압 락아웃, cdc_level 3이면 충전 안 함. 13.5V 이하로 떨어지면 cdc_level 2로 재개
if (BattExt_Voltage > 14.3) { analogWrite(PIN_BATTPWM,0); cdc_level = 3; }
} else { analogWrite(PIN_BATTPWM,0); cdc_level = 3; }
}
void Discharging_Control(void) {
// 저전압 락아웃
if (BattExt_Voltage <= 10.8) {
cdc_level = 0;
digitalWrite(PIN_LOAD,LOW);
Timer1_DeInit();
}
}
/////////////// 타이머 1 함수 //////////////
// AVR 스타일로 작성
void Timer1_Init(void) {
noInterrupts();
pinMode(PIN_INVP,OUTPUT);
pinMode(PIN_INVN,OUTPUT);
TCCR1A = 0; TCCR1B = 0;
/* ========================================
50Hz AC = 20ms 파동 = 2개 10ms 펄스(PIN_INVP, PIN_INVN)
두 핀은 100Hz로 토글해야 함.
100Hz = 0.01초, 아두이노 시스템 클럭 16MHz = 16,000,000 사이클/0.01초 = 160,000 사이클
프리스케일러 8(CS11) 사용 → 타이머 카운트 160,000/8 = 20,000 틱
16비트 업카운터, 오버플로 65535에서 발생
65535-20000 = 45535부터 카운트 시작
TCNT1 = 45535로 시작 → 틱틱... 65536 → 오버플로 인터럽트 → ISR에서 인버터 구동 핀 토글 → 다시 45535부터!
(백그라운드에서 자동 반복)
======================================== */
TCNT1 = 45535;
TCCR1B |= (1 << CS11); // 8 프리스케일러
TIMSK |= (1 << TOIE1); // 타이머 비교 인터럽트 활성화
interrupts();
}
void Timer1_DeInit(void) {
noInterrupts();
TCCR1A = 0; TCCR1B = 0; TCNT1 = 0;
digitalWrite(PIN_INVP,LOW);
digitalWrite(PIN_INVN,LOW);
interrupts();
}
전체 코드 (C/C++)
코드 구성 및 가독성 향상 버전 - 다운로드만 가능
부트로더 (C/C++)
다운로드만 가능
회로도