/* * -------------------------------------------------- * 아두이도 정전류 모스펫 배터리 방전기 * Arduino CC Power Mosfet Battery Discharger * -------------------------------------------------- * * 저자 : HooneyPaPa(MindEater) * 버전 : 2.0 * 배포일 : 2020-07-12 * 배포사이트 : https://mindeater.tistory.com/2428 * 자작기 : https://mindeater.tistory.com/2427 * * [HISTORY] * * Version 3.2 : * -. 50V 이상의 팩을 테스트하기 위해 전압분배비 R1값 Define 처리 * -. 배터리 연결시 내부저항 계산을 옵션으로 처리함 (환경설정) * -. 방전시작시 ADJ 단계를 Skip 할 수 있도록 함 -> 설정 전류까지 천천히 상승함 * -. 방전시 디버그 모드에서 Dac 전압대신 Watt 표시 * -. 내부저항 체크시 방전전류를 1.5A로 계산 * -. Time Tune 설정항목에서 아두이노 타임 적용을 선택할 수 있도록 함 * -. Test Version 2, Wat 값 소수점 버림 * * Version 3.1 : * -. 방전시 홀딩후 재시작 오류 수정 * -. 전압분배비에 따라 유연하게 적용할 수 있도록 BattReg 값 범위값 늘림 * -. LCD Backlight Auto 모드 버그 수정 * * Version 3.0 : 초기화 필요!!!! * -. mAs 조정 단위 수정 및 방전시간에 반영 * -. 방전전류가 같으면 Adjust를 최초 한 번만 수행하도록 함 * -. mills() 를 loop()에서 실행한 값으로 통일함 (Fine Tune) * -. Mod A_Rate값을 션트 저항 값에 직접 반영 -> 조정 범위 필요(초기화 필요) * -. 방전 종료시 시리일 로깅 추가 * -. 기타 불필요 코드 수정 * * Version 2.9 : * -. Cut Off 전압 설정시 4.2V 이상일 경우 0.2V 단위에서 0.1V 단위씩 조정하도록 함 * * Version 2.8 : * -. 설정화면 진입시 방전전류를 가장 먼저 설정할 수 있도록 수정 * -. Adjust flag 오류로 두 번째 방전 실패 현상 수정 * * Version 2.7 : * -. LCD2004/LCD1602로 각자 분리된 파일을 하나로 통합 * LCD2004로 사용할 경우 * #define DISPLAY_LCD2004 1 * //#define DISPLAY_LCD1602 1 * * LCD1602로 사용할 경우 * //#define DISPLAY_LCD2004 1 * #define DISPLAY_LCD1602 1 * * -. LCD1602 모드에서 전류 조정시 Mod A-Rate값이 10.000이상일 경우 Display 깨지는 현상 수정 * * Version 2.6 : * -. 방전 전류값 100mA 단위로 수정(이홈메이드클럽 넘버원님 의견) * -. 방전 플래그 D를 없애고 초를 00:00의 ':'을 깜빡이도록 함 * -. 90에 Auto Hold 이후 온도가 70도까지 떨어지면 Auto Restart * -. Serial Logging Enable..(sram 사용률 74%) * * Version 2.5 : * -. KILL 모드일 경우 Cut-Off mAH 동작하지 않도록 함 * -. 외부전원으로 부팅시 Battery OFF 그림 깨지는 문제점 수정 * * Version 2.4 (2020-08-17) : * -. Adjust 단계를 좀 더 세분화하고 실제 방전시는 출렁임 방지를 위해 0.5V 이상일 경우 조정하도록 함 * -. Tune mA/Sec 기능 추가, 초단위 측정치에 튜닝값을 가감할 수 있도록 함( Default 0.0, -0.1~1.0 ) * 레퍼런스 방전기가 있을 경우 수치를 맞출 수 있도록 하는 기능 * -. Adjust와 방전중 전류값 수정시 조정단계 및 수치 조정 * -. Serial Logging Disable ( Enable할 경우 sram 사용율이 66% -> 76% ) * -. 방전시 loop문에서 scanVoltages 방지 (중복) * * Version 2.3 (2020-08-16) : * -. 기본 션트 저항 0.22로 조정, 가장 먼저 수정하세요. * -. 온도 경고 Holing 값을 90도 조정( 모스펫 소손을 방지하기 위해) * -. 초기화시 백라이트 항상 켜짐으로 수정 * * Version 2.2 (2020-08-16) : * -. 0.1 이외의 다른 션트 적용 * * Version 2.1 (2020-08-2) : * -. FAN2 Control * * Version 2.0 (2020-07-23) : Major Update!! * -. KILL 모드 보완 * -. 다이얼을 돌려 환경설정 진입 동작 후 END모드 설정값이 증가하는 증상 보완 * -. 모스펫 온도 경보 온도 100도로 조정하고 해당 온도시 방전 Holding, IRFP250N은 -55~175도까지 동작됨 * -. 코드 정리 * * Version 1.9 (2020-07-22) * -. 내부저항 계산 수정 (회로 GND 수정후 패치본 제거) * -. CV 모드로 방전시 너무 느린 시간을 보완 * -. LCD 백라이트 켜져 있는 시간을 5분에서 10분으로 조정 * -. 방전 종료시 Sound Disable 설정이면 엔코더 클릭시 바로 Normal 모드로 전환 * -. 방전 전류값을 Define 처리(DCG_MAX_CURRENT) 해서 한 번에 바꿀 수 있도록 함 * -. 사용한 Shunt 의 저항값 설정에 따라 수치값 자동 조정 (테스트 X) * * Version 1.8 (2020-07-21) * -. 내부저항 계산 알고리즘 개선 - 0.3A로 방전하고 Drop률을 배제함!! (TODO : 합산예정) * -. 0.1옴 이외의 션트저항 적용을 위한 선작업 * -. 방전시 Drop 전압 표시 오류 (V Drop Rate이 반영안되는 문제) * * Version 1.7 (2020-07-19) * -. 수동(Passive) 부저컨트롤 * tone()함수는 인터럽트 서비스 루틴에서 동작하지 않는 문제를 우회하여 해결 * -. 1.6의 사이드 이펙트로 전압칼리브레이션 때 Gate를 닫지 않도록 함 * * Version 1.6 (2020-07-13) * -. BEEP ON/OFF 설정 ( 회사에서 방전테스트시 소음을 위해... ^^;; ) * * Version 1.5 (2020-07-13) * -. 내부저항 체크 로직에서 BEEP음이 간헐적으로 울리는 현상 수정 * -. BEEP음 로직 수정 * -. 방전모드에서 일반모드로 전환될때 배터리 스캔하지 않도록 함(매뉴얼로 할 수 있음) * -. 엔코더 무효클릭 보강 * * Version 1.4 (2020-07-12) * -. 내부저항 버그 수정 * -. 경고 온도 수정 : 250 --> 150 * -. 방전 중 방전 전류값 조정할 수 있도록 수정 * -. 시리얼 로깅 항목 간소화 --> Time, Voltage, Current, mAh, Wh * 로깅 제목정보를 부팅시에서 방전 시작전으로 옮기고 콘솔로그 출력 타임 조절 - 20초 마다 1번씩 * -. 백라이트가 꺼진 후 다이얼을 돌렸을 때 백라이트만 켜도록 함 * -. 문자열대신 심볼 적용 (배터리 ON/OFF) * -. 방전종료후 엔코더 클릭시 비프음이 꺼지고, * 이 상태에서 한번 클릭시 노멀모드로 전환, 길게 누르면 다시 방전시작합니다. * -. 방전화면에서 Mod를 End로 변경 * * Version 1.3 (2020-07-07) * 1. LCD 백라이트 5분 후 처음 한 번 꺼졌다가 다시 켜지는 증상 수정 * 2. 방전시 LCD 깨지는 증상 수정 * * Version 1.2 (2020-07-06) * 1. SRAM 사이즈 튜닝 * 2. 방전 종료 조건에 mAh 추가 (이홈메이드클럽의 무선조종님 의견) * 3. 방전전류(Current) 0.X 대 저장이 안되는 문제 수정 * 4. 대기 모드에서 엔코더를 한 번 누르면 배터리 스캔을 다시 시작함 * 5. Auto Stop 기능을 Auto Hold 기능으로 변경 * * Version 1.1 * 1. 화면 구성과 조작 방법 개선 * * Version 1.1 (2020-07-03) * 1. 최초 작성 후 테스트 OK * * [조작과 화면 설명] * 상단의 배포사이트에 자세한 설명이 있습니다. * * * [필독] * 1. 코드 수정 및 재배포 가능합니다. 다만 코드와 관련글에 "원출처"를 꼭 밝혀주세요!! * 2. 개인의 사용은 자유롭지만 업체나 개인이 판매 목적으로 사용할 경우 무단사용을 불허합니다. * 3. 문의 사항은 아래 블로그에 남겨주시면 답변 드리겠습니다. * * * [TODO] * 1. 경고 온도 설정 및 경고발생시 Hold or Not * * * [Base Code] * - Special Thanks to SlotGodori!! * - 아두이노 DAC 출력을 이용한 N-mosfet사용 방전기 v 6.0 by slotgodori ( http://slotgodori.blog.me ) */ //#pragma GCC push_options //#pragma GCC optimize ("O0") #include // 방전전류보정용 값 저장을위한 라이브러리 #include #include // 전압측정용 ads1115용 라이브러리 #include // i2c clcd 를 사용하기위한 라이브러리 #include // 정전류제어를위한 GATE 제어용 DAC 라이브러리 #include #include typedef unsigned long ulong_t; typedef unsigned char uchar_t; ///////////////////////////////////////////////////////////////////////////////////////////////// // LCD, SHUNT, BUZZER 값 필수 확인!! // 제작하는 부품 스펙에 따라 수정필요!!!!! // ★★★★ 주석은 필히 확인 ///////////////////////////////////////////////////////////////////////////////////////////////// #define DISPLAY_LCD2004 1 /* ★★★★,V2.7 */ //#define DISPLAY_LCD1602 1 /* ★★★★, 1602를 사용할 경우 위 라인의 DISPLAY_LCD2004를 주석처리, 이라인을 활성화 */ #define SHUNT_R 0.1 /* ★★★★, V2.2 션트 값에 맞게 수정 */ #define BUZZER_TYPE_ANALOG 0 /* 능동부저 */ #define BUZZER_TYPE_PASSIVE 1 /* 수동부저 */ #define BUZZER_TYPE BUZZER_TYPE_PASSIVE /* ★★★★, 능동부저인 경우 BUZZER_TYPE_ANALOG */ #define V_D_R1 100 /* ★★★★, K-ohm, 200K 저항 권장, 전압분배 R1 저항 값 */ #define V_D_R2 10 /* ★★★★, K-ohm, 전압분배 R2 저항 값, 10K 고정 */ #define DEFAULT_BATT_REG (V_D_R1+V_D_R2)/V_D_R2 ///////////////////////////////////////////////////////////////////////////////////////////////// // Debugging.... ///////////////////////////////////////////////////////////////////////////////////////////////// //#define SERIAL_LOGGING 1 /* 방전 데이타를 시리얼모니터로 전송 */ //#define DEBUG_INR 1 /* Default OFF */ //#define DEBUG_DCG 1 /* Default OFF */ //#define DEBUG_ADS1115 1 /* Default OFF */ //#define DEBUG_ONDO 1 /* Default OFF */ ///////////////////////////////////////////////////////////////////////////////////////////////// // PIN MAP // 2,3번을 제외한 나머지 핀은 수정 가능 ///////////////////////////////////////////////////////////////////////////////////////////////// #define PIN_ROTARY_RT 2 /* do not change, fixed by INTR_NO_0 */ #define PIN_ROTARY_LT 3 /* do not change, fixed by INTR_NO_1 */ #define PIN_ENCODER_SWITCH 4 #define PIN_FAN_CONTROL 5 // 방전시 팬가동을위한 모스펫 게이트오픈용 핀. #define PIN_FAN_2_CONTROL 6 // 방전시 팬가동을위한 모스펫 게이트오픈용 핀. #define PIN_BUZZER 7 #define PIN_LED_BATT_ON 8 // 배터리가 연결 체크용 LED용 #define PIN_LED_BATT_DCG_ON 9 // 방전 인식용 LED용 #define PIN_TEMPERATURE A0 /* NTC 센서 */ #define PIN_ADC_V_DAC 0 /* ADS1115, A0 -> 0 */ #define PIN_ADC_V_SHUNT 1 /* ADS1115, A1 -> 1 */ #define PIN_ADC_V_BATT 2 /* ADS1115, A2 -> 2 */ ///////////////////////////////////////////////////////////////////////////////////////////////// // EEPROM Address, MAX 512B ///////////////////////////////////////////////////////////////////////////////////////////////// #define ADDR_AUTO_STOP_TIME 0 /* 1byte, max 255 hour */ #define ADDR_DCG_A ADDR_AUTO_STOP_TIME+1 /* 4, 4byte */ #define ADDR_DCG_END_MODE ADDR_DCG_A+4 /* 5, 1byte */ #define ADDR_FAN_THRESHOLD ADDR_DCG_END_MODE+1 /* 6, 1byte */ #define ADDR_V_CUT_OFF ADDR_FAN_THRESHOLD+1 /* 10, 4-bytes */ #define ADDR_A_DIS_RATE ADDR_V_CUT_OFF+4 /* 14, 4-bytes */ #define ADDR_V_DROP_RATE ADDR_A_DIS_RATE+4 /* 18, 4-bytes */ #define ADDR_BATT_REG ADDR_V_DROP_RATE+4 /* 22, 4-bytes */ #define ADDR_BACKLIGHT_CTRL ADDR_BATT_REG+4 /* 23, 1byte */ #define ADDR_MAH_CUT_OFF ADDR_BACKLIGHT_CTRL+1 /* 27, 4byte */ #define ADDR_NO_BEEP_FLAG ADDR_MAH_CUT_OFF+4 /* 28, 1byte */ #define ADDR_MAS_TUNE ADDR_NO_BEEP_FLAG+1 /* 32, 4byte, V2.4 */ #define ADDR_AUTO_INR_FLAG ADDR_MAS_TUNE+4 /* 33, 1byte, V3.2 */ #define ADDR_SKIP_ADJ_FLAG ADDR_AUTO_INR_FLAG+1 /* 34, 1byte, V3.2 */ #define ADDR_TUNE_TIME ADDR_SKIP_ADJ_FLAG+1 /* 35, 1byte, V3.2 */ #define ADDR_DCG_THEORY ADDR_TUNE_TIME+1 /* 36, 1byte, V3.2 */ ///////////////////////////////////////////////////////////////////////////////////////////////// /// Time Schedulling, 수정시 검증 필요 - 오차가 발생 할 수 있음 ///////////////////////////////////////////////////////////////////////////////////////////////// #define INTERVAL_BATT_CHECK 1000 /* 1s */ #define INTERVAL_DCG_CHECK 1000 /* 1s */ #define INTERVAL_POLL_TEMP 500 /* 2s --> 500ms */ #define INTERVAL_LCD_UPDATE 200 /* 200ms, do not modify.. */ #define LCD_BACKLIGHT_ON_TIME 40000 //600000 /* 10min */ ///////////////////////////////////////////////////////////////////////////////////////////////// /// Etc ///////////////////////////////////////////////////////////////////////////////////////////////// #define MAX_BATT_INR_CHK_CNT 15 /* 내부저항 체크 카운트 */ #define WARN_ONDO 90 /* 모스펫 20A 기준 온도 경보 온도 */ #define DAC_PWR_1A 76*(SHUNT_R/0.1) /* V2.3 */ #define DCG_MAX_CURRENT 20.0 /* V1.9 */ #define DCG_MIN_CURRENT 0.2 /* V2.6 */ ///////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////// #define ADS1115_Scale_Factor 0.1875057F // (6.144/32767) #define DELAY(ms) {uint16_t kk=0; while(kk++ 8.4 && CutOff <= 16.8 ) ? 0.2 :\ ( CutOff > 16.8 && CutOff <= 25.2 ) ? 0.4 :\ ( CutOff > 25.2 && CutOff ) ? 0.6 : 0.1; uint16_t dac_power = 0; /* 방전 전류 컨트롤 용 OAMP Mapping 값 */ uint16_t prv_dac_power = 0; /* V3.0 */ float watt; /* 체크 1주기당 측정된 전력 */ float mAh ; /* 누적 용량 */ float Wh = 0.0; /* 누적 전력 */ float diffC = 0.0; float mAsTune = 0.00; /* V2.4 */ ulong_t dcgLogTime = 0; /* 방전 체크 시간 */ ulong_t dcgTime = 0; /* 전체 방전 시간 */ uint8_t battInRCheckCount=0 ; ulong_t lcdBacklightOnTime; float gOndo = 0.0; ulong_t curTime; /* MEM_TUNE */ uint32_t uCount=0; ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// void initDcgData() { //V1.9, V_Batt = 0; V_Shunt = 0; V_Dac = 0; inCurrent = 0.0; watt = 0.0; Wh = 0; mAh = 0; diffC = 0.0; //prvDiffC = 0.0; dcgTime = 0; dcgPack.dcgDebug = 0; //inResistance = 0.0; /* TBD : V1.5 */ //battInRCheckCount = 0; /* TBD : V1.5 */ //dcgPack.isNoBattery = 1; /* TBD : V1.5 */ //dcgPack.isNoBattery = 1; //dcgPack.startFlag = 0; dcgPack.holdFlag = 0; dcgPack.finishFlag = 0; //dcgPack.BackLightOnFlag = 1; dcgPack.tempWarning = 0; dcgPack.isCallibrate = 0; dcgPack.fConfirmed = 0; //dcgPack.adjusted = 0; } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// void EEPROM_writeFloat(int address, float value) { byte *pValue = (byte*)&value; byte byteValue; for ( int ii = 0; ii < 4; ii++) { byteValue = *(pValue + ii); EEPROM.write(address + ii, byteValue); } } float EEPROM_readFloat(int address) { float value; byte *bytePointer = (byte*)(&value), byteValue; for (int ii = 0; ii < 4; ii++) { byteValue = EEPROM.read(address + ii); *(bytePointer + ii) = byteValue; } return value; } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// void BEEP(int8_t times) { if ( dcgPack.isBeepDisable ) return; /* V1.6 */ do { if ( BUZZER_TYPE == BUZZER_TYPE_PASSIVE ) { tone(PIN_BUZZER, 6000); DELAY(50); noTone(PIN_BUZZER); } else { digitalWrite(PIN_BUZZER, HIGH); DELAY(30); analogWrite(PIN_BUZZER, LOW); } times--; DELAY(50); } while( times >= 0); } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// void setDefaultBattChecker() { EEPROM.write(ADDR_AUTO_STOP_TIME, 12); dcgPack.autoStopTime = EEPROM.read(ADDR_AUTO_STOP_TIME); EEPROM.write(ADDR_BACKLIGHT_CTRL, 0); /* V1.7, Default */ dcgPack.BackLightCtrl = EEPROM.read(ADDR_BACKLIGHT_CTRL); EEPROM_writeFloat(ADDR_DCG_A, 1); Dcg_A = EEPROM_readFloat(ADDR_DCG_A); EEPROM.write(ADDR_DCG_END_MODE, DCG_MODE_END_CC); dcgPack.disChargeMode = EEPROM.read(ADDR_DCG_END_MODE); EEPROM.write(ADDR_FAN_THRESHOLD, 40); /* 40℃ */ dcgPack.fanThresTemp = EEPROM.read(ADDR_FAN_THRESHOLD); EEPROM_writeFloat(ADDR_V_CUT_OFF, 2.5); CutOff = EEPROM_readFloat(ADDR_V_CUT_OFF); UPDATE_CUT_RATE; EEPROM_writeFloat(ADDR_MAH_CUT_OFF, 0); CutOffmAh = EEPROM_readFloat(ADDR_MAH_CUT_OFF); /* V2.2 for various shunt */ //EEPROM_writeFloat(ADDR_A_DIS_RATE, (1/SHUNT_R)); EEPROM_writeFloat(ADDR_A_DIS_RATE, 0.000); A_Rate = EEPROM_readFloat(ADDR_A_DIS_RATE); EEPROM_writeFloat(ADDR_V_DROP_RATE, 0.050); Drop_V_Rate = EEPROM_readFloat(ADDR_V_DROP_RATE); EEPROM_writeFloat(ADDR_BATT_REG, DEFAULT_BATT_REG); /* V3.2 */ BattREG = EEPROM_readFloat(ADDR_BATT_REG); dcgPack.operMode = OPER_MODE_NORMAL; //dcgPack.curConfigPos = CPOS_DISC_END_MODE; /* V1.6 */ EEPROM.write(ADDR_NO_BEEP_FLAG, 0); dcgPack.isBeepDisable = EEPROM.read(ADDR_NO_BEEP_FLAG); /* V2.4 */ EEPROM_writeFloat(ADDR_MAS_TUNE, 0.000); mAsTune = EEPROM_readFloat(ADDR_MAS_TUNE); /* V3.2, Default Auto */ EEPROM.write(ADDR_AUTO_INR_FLAG, 1); dcgPack.autoInrFlag = EEPROM.read(ADDR_AUTO_INR_FLAG); /* V3.2, Default Auto */ EEPROM.write(ADDR_SKIP_ADJ_FLAG, 0); dcgPack.skipAdjFlag = EEPROM.read(ADDR_SKIP_ADJ_FLAG); /* V3.2, Default Auto */ EEPROM.write(ADDR_TUNE_TIME, 0); dcgPack.tuneTime = EEPROM.read(ADDR_TUNE_TIME); initDcgData(); BEEP(4); } void saveBattChecker() { EEPROM.write(ADDR_AUTO_STOP_TIME, dcgPack.autoStopTime); EEPROM.write(ADDR_BACKLIGHT_CTRL, dcgPack.BackLightCtrl); EEPROM_writeFloat(ADDR_DCG_A, Dcg_A); EEPROM.write(ADDR_DCG_END_MODE, dcgPack.disChargeMode); EEPROM.write(ADDR_FAN_THRESHOLD, dcgPack.fanThresTemp); EEPROM_writeFloat(ADDR_V_CUT_OFF, CutOff); EEPROM_writeFloat(ADDR_MAH_CUT_OFF, CutOffmAh); EEPROM_writeFloat(ADDR_A_DIS_RATE, A_Rate); EEPROM_writeFloat(ADDR_V_DROP_RATE, Drop_V_Rate); EEPROM_writeFloat(ADDR_BATT_REG, BattREG); EEPROM.write(ADDR_NO_BEEP_FLAG, dcgPack.isBeepDisable); /* V1.6 */ EEPROM_writeFloat(ADDR_MAS_TUNE, mAsTune); /* V2.4 */ EEPROM.write(ADDR_AUTO_INR_FLAG, dcgPack.autoInrFlag); /* V3.2 */ EEPROM.write(ADDR_SKIP_ADJ_FLAG, dcgPack.skipAdjFlag); /* V3.2 */ EEPROM.write(ADDR_TUNE_TIME, dcgPack.tuneTime); /* V3.2 */ } void initBattChecker() { uint8_t tmpData=0; dcgPack.autoStopTime = EEPROM.read(ADDR_AUTO_STOP_TIME); if ( dcgPack.autoStopTime > 48 ) { EEPROM.write(ADDR_AUTO_STOP_TIME, 12); dcgPack.autoStopTime = EEPROM.read(ADDR_AUTO_STOP_TIME); } /* 0 or 1 */ tmpData = EEPROM.read(ADDR_BACKLIGHT_CTRL); if ( tmpData > 1 ) { EEPROM.write(ADDR_BACKLIGHT_CTRL, 0); dcgPack.BackLightCtrl = EEPROM.read(ADDR_BACKLIGHT_CTRL); } else dcgPack.BackLightCtrl = tmpData; Dcg_A = EEPROM_readFloat(ADDR_DCG_A); if ( Dcg_A < 0.1 || Dcg_A > 20 ) { EEPROM_writeFloat(ADDR_DCG_A, 1); Dcg_A = EEPROM_readFloat(ADDR_DCG_A); } dcgPack.disChargeMode = EEPROM.read(ADDR_DCG_END_MODE); if ( !dcgPack.disChargeMode || dcgPack.disChargeMode > 50 ) { EEPROM.write(ADDR_DCG_END_MODE, DCG_MODE_END_CC); dcgPack.disChargeMode = EEPROM.read(ADDR_DCG_END_MODE); } dcgPack.fanThresTemp = EEPROM.read(ADDR_FAN_THRESHOLD); if ( dcgPack.fanThresTemp > 100 ) { EEPROM.write(ADDR_FAN_THRESHOLD, 40); dcgPack.fanThresTemp = EEPROM.read(ADDR_FAN_THRESHOLD); } CutOff = EEPROM_readFloat(ADDR_V_CUT_OFF); if ( isnan(CutOff) || CutOff > 50 ) { EEPROM_writeFloat(ADDR_V_CUT_OFF, 2.75); CutOff = EEPROM_readFloat(ADDR_V_CUT_OFF); } UPDATE_CUT_RATE; CutOffmAh = EEPROM_readFloat(ADDR_MAH_CUT_OFF); if ( CutOffmAh > 100000 ) { EEPROM_writeFloat(ADDR_MAH_CUT_OFF, 0); CutOffmAh = EEPROM_readFloat(ADDR_MAH_CUT_OFF); } BattREG = EEPROM_readFloat(ADDR_BATT_REG); if ( isnan(BattREG) || BattREG > 50 ) { EEPROM_writeFloat(ADDR_BATT_REG, DEFAULT_BATT_REG); BattREG = EEPROM_readFloat(ADDR_BATT_REG); } A_Rate = EEPROM_readFloat(ADDR_A_DIS_RATE); //if ( isnan(A_Rate) || A_Rate > 25 ) { /* 1/SHUNT(0.05) = 20 */ if ( isnan(A_Rate) || A_Rate > 1.000 ) { /* 1/SHUNT(0.05) = 20 */ //EEPROM_writeFloat(ADDR_A_DIS_RATE, (1/SHUNT_R)); /* V2.4 */ EEPROM_writeFloat(ADDR_A_DIS_RATE, 0.000); /* V3.0 */ A_Rate = EEPROM_readFloat(ADDR_A_DIS_RATE); } Drop_V_Rate = EEPROM_readFloat(ADDR_V_DROP_RATE); if ( isnan(Drop_V_Rate) || Drop_V_Rate > 50 ) { EEPROM_writeFloat(ADDR_V_DROP_RATE, 0.02); Drop_V_Rate = EEPROM_readFloat(ADDR_V_DROP_RATE); } tmpData = EEPROM.read(ADDR_NO_BEEP_FLAG); // V1.6 if ( tmpData > 1) { EEPROM.write(ADDR_NO_BEEP_FLAG, 0); dcgPack.isBeepDisable = EEPROM.read(ADDR_NO_BEEP_FLAG); } else dcgPack.isBeepDisable = tmpData; /* V2.4 */ mAsTune = EEPROM_readFloat(ADDR_MAS_TUNE); if ( isnan(mAsTune) || mAsTune < -1.00 || mAsTune > 1.00 ) { EEPROM_writeFloat(ADDR_MAS_TUNE, 0.00); mAsTune = EEPROM_readFloat(ADDR_MAS_TUNE); } /* V3.2*/ dcgPack.autoInrFlag = EEPROM.read(ADDR_AUTO_INR_FLAG); if ( dcgPack.autoInrFlag > 1 ) { EEPROM.write(ADDR_AUTO_INR_FLAG, 1); /* Default is AUTO */ dcgPack.autoInrFlag = EEPROM.read(ADDR_AUTO_INR_FLAG); } battInRCheckCount = dcgPack.autoInrFlag ? 0 : MAX_BATT_INR_CHK_CNT; /* V3.2 */ dcgPack.skipAdjFlag = EEPROM.read(ADDR_SKIP_ADJ_FLAG); if ( dcgPack.skipAdjFlag > 1 ) { EEPROM.write(ADDR_SKIP_ADJ_FLAG, 0); dcgPack.skipAdjFlag = EEPROM.read(ADDR_SKIP_ADJ_FLAG); } /* V3.2 */ dcgPack.tuneTime = EEPROM.read(ADDR_TUNE_TIME); if ( dcgPack.tuneTime > 1 ) { EEPROM.write(ADDR_TUNE_TIME, 0); dcgPack.tuneTime = EEPROM.read(ADDR_TUNE_TIME); } dcgPack.operMode = OPER_MODE_NORMAL; initDcgData(); } #define DCG_GATE_OPEN(num) dcgGageOpen(num) #define DCG_GATE_SHUTDOWN dcgGageOpen(0) void dcgGageOpen(uint16_t value) { if ( value > 0 ) { if (digitalRead(PIN_LED_BATT_DCG_ON) == LOW) digitalWrite(PIN_LED_BATT_DCG_ON, HIGH); } else { if (digitalRead(PIN_LED_BATT_DCG_ON) == HIGH) digitalWrite(PIN_LED_BATT_DCG_ON, LOW); } if ( prv_dac_power != value ) { /* V3.0, fine tune */ MCP4725.setVoltage(value, false); prv_dac_power = value; } } void scanBattery() { static ulong_t prevTime=0; static float Voc_Batt = 0.0; float inVoltage; if((curTime - prevTime) < INTERVAL_BATT_CHECK) return; prevTime = curTime; scanVoltages() ; if (V_Batt > 0 ) { dcgPack.isNoBattery = 0; if ( digitalRead(PIN_LED_BATT_ON) == LOW ) digitalWrite(PIN_LED_BATT_ON, HIGH); if ( battInRCheckCount == 0 ) { Voc_Batt = V_Batt; if ( Voc_Batt < 1.0 ) /* V2.0, for KILL Mode */ return; DCG_GATE_OPEN(DAC_PWR_1A*1.5); /* V3.2 */ //DCG_GATE_OPEN(Dcg_A * DAC_PWR_1A); /* V3.0 */ inResistance = 0.0; } while (battInRCheckCount <= MAX_BATT_INR_CHK_CNT) { battInRCheckCount++; delayMicroseconds(20); scanVoltages(); /* 처음 6개의 값 이후 비교적 안정적인 값이 출력 */ if ( battInRCheckCount < 6 ) continue; if(V_Batt > 0 && V_Shunt > 0) { V_Batt = V_Batt + (inCurrent * Drop_V_Rate); inVoltage = Voc_Batt - V_Batt; #if defined(DEBUG_INR) Serial.print(F(", InR="));Serial.print(inResistance, 3); Serial.print(F(", [inVoltage("));Serial.print(inVoltage); Serial.print(F(")/inCurrent("));Serial.print(inCurrent); Serial.print(F(")], Voc_Batt="));Serial.print(Voc_Batt, 3); Serial.print(F(", V_Batt="));Serial.print(V_Batt, 3); Serial.print(F(", Drop_V_Rate="));Serial.print(Drop_V_Rate); Serial.print(F(", Vraw_Batt="));Serial.println(V_Batt-(Dcg_A*Drop_V_Rate), 3); #endif if ( inVoltage > 0 ) { inResistance= (inVoltage/inCurrent)*1000; if (isnan(inResistance)) inResistance=0.0; } } if (!(battInRCheckCount%2)) lcdUpdate(1); if ( battInRCheckCount >= MAX_BATT_INR_CHK_CNT ) { BEEP(1); DCG_GATE_SHUTDOWN; Voc_Batt = 0.0; } } } else { DCG_GATE_SHUTDOWN; if ( digitalRead(PIN_LED_BATT_ON) == HIGH) digitalWrite(PIN_LED_BATT_ON, LOW); dcgPack.isNoBattery = 1; initDcgData(); /* V3.2 */ //battInRCheckCount = 0; battInRCheckCount = dcgPack.autoInrFlag ? 0 : MAX_BATT_INR_CHK_CNT; inResistance=0.0; } //Do Not Here!! For Callibration, DCG_GATE_SHUTDOWN; } void scanVoltages () { int16_t rawData; /* DAC Voltage */ rawData = ads.readADC_SingleEnded(PIN_ADC_V_DAC); V_Dac = (rawData * ADS1115_Scale_Factor) / 1000; if ( isnan(V_Dac) || V_Dac < 0.004 ) { V_Dac = 0.0 ; } #if defined(DEBUG_ADS1115) Serial.print(F("ADS1115, V_Dac="));Serial.print(V_Dac);Serial.print(F("("));Serial.print(rawData); #endif /* Shunt Voltage */ rawData = ads.readADC_SingleEnded(PIN_ADC_V_SHUNT); V_Shunt = (rawData * ADS1115_Scale_Factor) / 1000; if ( isnan(V_Shunt) || V_Shunt < 0.004 ) { V_Shunt = 0.0 ; inCurrent = 0.0; } else { /* V3.0, A_Rate값을 디버깅이 쉽게 SHUNT_R값과 분리함 */ inCurrent = V_Shunt / (SHUNT_R + A_Rate); } #if defined(DEBUG_ADS1115) Serial.print(F("), V_Shunt="));Serial.print(V_Shunt);Serial.print(F("("));Serial.print(rawData); Serial.print(F("), inCurrent="));Serial.print(inCurrent); #endif /* Battery Voltage */ rawData = ads.readADC_SingleEnded(PIN_ADC_V_BATT); V_Batt = ((rawData*ADS1115_Scale_Factor)/1000) * BattREG; if ( isnan(V_Batt) || V_Batt <= 0.1 ) { V_Batt = 0.0 ; } #if defined(DEBUG_ADS1115) Serial.print(F("), V_Batt="));Serial.print(V_Batt);Serial.print(F("("));Serial.print(rawData, 3); Serial.print(F("), BattREG="));Serial.println(BattREG); #endif } /* * adjustDacPower * * 측정값을 방전전류값으로 맞추기위한 dac_power 조정!! * slotgodori님 로직을 기반으로 수정 */ void adjustDacPower() { #if defined(DEBUG_DCG) Serial.print(F("Starting DAC Power Adjusting..., dac_power="));Serial.println(dac_power); #endif float absDiffC; dcgPack.operMode = OPER_MODE_ADJUST; BEEP(1); lcdUpdate(1); /* V2.0 */ if ( dcgPack.disChargeMode == DCG_MODE_END_KILL ) { if ( V_Batt < 1.0 ) { dac_power = DAC_PWR_1A/2; goto EXIT; } } else { if ( !dcgPack.adjusted || !dac_power ) { /* V3.0 */ dac_power = Dcg_A * DAC_PWR_1A; } } for ( uint8_t ii=0; ii<60; ii++ ) { DCG_GATE_OPEN(dac_power); delayMicroseconds(10); scanVoltages(); V_Batt = V_Batt + ( Dcg_A * Drop_V_Rate ) ; if ( inCurrent <= 0 ) /* abnormal state exit */ continue; diffC = Dcg_A - inCurrent; absDiffC = abs(diffC); if ( absDiffC <= 0.002 ) { lcdUpdate(1); break; } #if defined(DEBUG_DCG) Serial.print(F("Adjusting..., absDiffC="));Serial.print(absDiffC, 3); Serial.print(F(", dac_power="));Serial.println(dac_power); #endif if ( absDiffC > 0.003 && absDiffC <= 0.050 ) dac_power += (diffC<0) ? (-1) : 1 ; else if ( absDiffC > 0.050 && absDiffC <= 0.350 ) dac_power += (diffC<0) ? (-2) : 2 ; else if ( absDiffC > 0.350 && absDiffC <= 0.500 ) dac_power += (diffC<0) ? (-4) : 4; else if ( absDiffC > 0.500 ) dac_power += (diffC<0) ? (-8) : 8; if (!(ii%2)) lcdUpdate(1); } DCG_GATE_SHUTDOWN; EXIT: DELAY(50); /* V1.5(1s -> 0.5s). V1.8(500ms -> 50ms) */ dcgPack.adjusted = 1; //EEPROM.write(ADDR_ADJUST_FLAG, dcgPack.adjusted); /* V3.0 */ #if defined(DEBUG_DCG) Serial.print(F("Finish DAC Power Adjusting, dac_power="));Serial.println(dac_power); #endif } //__attribute__((optimize("O0"))) /* * TODO : INTERVAL_DCG_CHECK 값을 환경 설정에서 변결할 수 있도록... * Check Time을 줄일수록 오차를 줄일 수 있을 거라 판단.. */ void dcgBattery() { ulong_t elapsedTime=0; elapsedTime = curTime - dcgLogTime; if( elapsedTime <= INTERVAL_DCG_CHECK ) return; dcgLogTime = curTime; /* V3.0 */ /* Exit or Hold Point */ if ( dcgPack.holdFlag || dcgPack.finishFlag ) { DCG_GATE_SHUTDOWN; scanVoltages(); if (dcgPack.finishFlag && !dcgPack.fConfirmed) BEEP(1); return; } DCG_GATE_OPEN(dac_power); scanVoltages(); /* V2.0, Skipped the First Time */ if ( dcgPack.startFlag ) { dcgPack.startFlag = 0; return; } if ( V_Shunt < 0.001 ) goto FINISH; ////////////////////////////////////////////////////////////////////// if ( dcgPack.tuneTime ) /* V3.2 */ elapsedTime -= elapsedTime*0.024; /* V3.0, arduino millis() interrupt per 1.024ms */ dcgTime += elapsedTime; dcgPack.dcgLogTimeHH = (dcgTime/1000/3600); dcgPack.dcgLogTimeMM = ((dcgTime/1000/60)%60); dcgPack.dcgLogTimeSS = ((dcgTime/1000)%60); if ( inCurrent > 0 ) { V_Batt = V_Batt + ( inCurrent * Drop_V_Rate ); if ( V_Batt <= 0 ) goto FINISH; watt = V_Batt * inCurrent; Wh += watt*elapsedTime*1.024 / 3600000.0; mAh += (inCurrent*elapsedTime)/3600.0; mAh += mAsTune; /* V2.4 */ dcgTime += mAsTune / inCurrent; /* V3.0 --> adjust mAsTune to dcgTime */ } ////////////////////////////////////////////////////////////////////// if ( dcgPack.autoStopTime && dcgPack.dcgLogTimeHH >= dcgPack.autoStopTime ) { #if defined(SERIAL_LOGGING) Serial.println(F("Discharged Holded by Auto Hold Time!!")); #endif dcgPack.holdFlag = 1; return; } if (dcgPack.disChargeMode != DCG_MODE_END_KILL && CutOffmAh && mAh >= CutOffmAh) { /* V2.5 */ #if defined(SERIAL_LOGGING) Serial.println(F("Discharged Stoped by Cut-Off mAH Config!!")); #endif goto FINISH; } /* V1.9 */ if ( dcgPack.disChargeMode == DCG_MODE_END_KILL ) { if ( V_Batt <= 0.001 ) { DCG_GATE_SHUTDOWN; delayMicroseconds(1000000); DCG_GATE_OPEN(dac_power); scanVoltages (); if ( V_Batt <= 0.001 ) goto FINISH; } } else { if (V_Batt <= CutOff) goto FINISH; } ////////////////////////////////////////////////////////////////////////////////// diffC = Dcg_A - inCurrent; if ( dcgPack.disChargeMode == DCG_MODE_END_CC && diffC != 0.00) { /* V2.4 */ if ( abs(diffC) > 0.005 && abs(diffC) <= 0.020) { dac_power += (diffC>0) ? 1 : -1 ; } else if ( abs(diffC) > 0.020 && abs(diffC) <= 0.050 ) { dac_power += (diffC>0) ? 2 : -2 ; } else if ( abs(diffC) > 0.050 && abs(diffC) <= 0.100) { dac_power += (diffC>0) ? 4 : -4 ; } else if ( abs(diffC) > 0.100 && abs(diffC) <= 0.500) { dac_power += (diffC>0) ? 8 : -8 ; } else if ( abs(diffC) > 0.500 && abs(diffC) <= 1.000) { dac_power += (diffC>0) ? 8 : -8 ; } else if ( abs(diffC) > 1.000 ) { /* V3.2 */ dac_power += (diffC>0) ? DAC_PWR_1A : -DAC_PWR_1A ; } } else if ( dcgPack.disChargeMode == DCG_MODE_END_CV ) { if ( (V_Batt - CutOff) < CutRate ) { if ( dac_power > DAC_PWR_1A ) { dac_power /= 2 ; } else if ( dac_power > 30 ) { dac_power -= 2 ; } else if ( dac_power >= 19 && dac_power <= 30 ) { dac_power -= 1 ; } } else { /* V1.9 - 너무 느린시간을 보완!! */ if ( diffC > 0.006 ) /* V2.0 */ dac_power += 1; } } else if ( dcgPack.disChargeMode == DCG_MODE_END_KILL ) { if ( V_Batt < 1.0 && dac_power > 26) dac_power -= 1 ; } ////////////////////////////////////////////////////////////////////////////////// #if defined(SERIAL_LOGGING) /* CSV Format for EXCEL */ /* Serial.println(F("Time, Voltage, Current, mAh, Wh")); */ if ( !(dcgPack.dcgLogTimeSS%20) ) /* per 20 sec */ { Serial.print(dcgPack.dcgLogTimeHH);Serial.print(F(":")); Serial.print(dcgPack.dcgLogTimeMM);Serial.print(F(":")); Serial.print(dcgPack.dcgLogTimeSS);Serial.print(F(",")); Serial.print(V_Batt,3);Serial.print(F(",")); Serial.print(inCurrent);Serial.print(F(",")); Serial.print(mAh,1); Serial.print(F(",")); Serial.println(Wh,1); } #endif /* return here when Discharging */ return; FINISH: DCG_GATE_SHUTDOWN; //dac_power = 0 ; dcgPack.finishFlag = 1; watt = 0.0; BEEP(2); #if defined(SERIAL_LOGGING) Serial.print(dcgPack.dcgLogTimeHH);Serial.print(F(":")); Serial.print(dcgPack.dcgLogTimeMM);Serial.print(F(":")); Serial.print(dcgPack.dcgLogTimeSS);Serial.print(F(",")); Serial.print(V_Batt,3);Serial.print(F(",")); Serial.print(inCurrent);Serial.print(F(",")); Serial.print(mAh,1); Serial.print(F(",")); Serial.println(Wh,1); Serial.println(F("Discharging Finished!!")); #endif } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// void pollTemperature() { static ulong_t prevTime=0; if((curTime - prevTime) < INTERVAL_POLL_TEMP ) return; prevTime = curTime; int16_t RawADC = analogRead(PIN_TEMPERATURE); float temp = log(10000.0 * ((1024.0 / RawADC - 1))); temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * temp * temp )) * temp ); temp = temp - 273.15; // Convert Kelvin to Celcius gOndo = (isnan(temp)||temp<-99.00) ? -99.00 : temp; memset(sOndoStr, 0, sizeof(sOndoStr)); #if defined(DISPLAY_LCD2004) sprintf(sOndoStr, "%5s%cC", DATA2STR(gOndo, 1), 0xDF); #elif defined(DISPLAY_LCD1602) sprintf(sOndoStr, "%3s", DATA2STR(gOndo, 0)); #endif if ( gOndo >= WARN_ONDO ) { dcgPack.holdFlag = 1; /* V2.0, 모스펫 과열로 소손을 막기 위해 Hold */ dcgPack.holdTemp = dcgPack.tempWarning = 1; BEEP(1); } else { if ( dcgPack.holdTemp && gOndo <= 70.0 ) { /* V2.6, Auto Restart */ dcgPack.holdFlag = 0; dcgPack.holdTemp = 0; BEEP(2); } dcgPack.tempWarning = 0; } /* Fan Control */ if ( gOndo >= dcgPack.fanThresTemp ) { if ( digitalRead(PIN_FAN_CONTROL) == LOW ) digitalWrite(PIN_FAN_CONTROL, HIGH); } else { if ( digitalRead(PIN_FAN_CONTROL) == HIGH ) digitalWrite(PIN_FAN_CONTROL, LOW); } /* V2.1 FAN2 Control */ if ( gOndo >= 50 ) { if ( digitalRead(PIN_FAN_2_CONTROL) == LOW ) digitalWrite(PIN_FAN_2_CONTROL, HIGH); } else { if ( digitalRead(PIN_FAN_2_CONTROL) == HIGH ) digitalWrite(PIN_FAN_2_CONTROL, LOW); } } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// #define FCK(pos) (dcgPack.curConfigPos==pos&&dcgPack.flicker) #if defined(DISPLAY_LCD2004) void lcdUpdate(uchar_t force) { static ulong_t prevTime=0; //curTime = millis(); if ( !force && curTime - prevTime < INTERVAL_LCD_UPDATE ) return; if ( !(uCount%3) ) { if ( dcgPack.BackLightCtrl ) { if (dcgPack.BackLightOnFlag) { lcdBackLightOn(); dcgPack.BackLightOnFlag = 0; } else { if ( (curTime - lcdBacklightOnTime) > LCD_BACKLIGHT_ON_TIME ) lcdBackLightOff(); } } else lcdBackLightOn(); } dcgPack.flicker = ((uCount%4)<2)?1:0; memset(LCD_LINE_BUF, 0, sizeof(LCD_LINE_BUF)); if ( dcgPack.tempWarning && dcgPack.flicker) { memset(sOndoStr, 0, sizeof(sOndoStr)); strcpy(sOndoStr, "-WARN-"); } ////////////////////////////////////////////////////////////////////////////////////////////////// if ( dcgPack.operMode == OPER_MODE_NORMAL ) { if ( dcgPack.isNoBattery ) { sprintf(LCD_LINE_BUF[0], "%-12s%7s", " ", sOndoStr); sprintf(LCD_LINE_BUF[1], " InR: --"); sprintf(LCD_LINE_BUF[2], "V: -.--- Dac: -.---"); sprintf(LCD_LINE_BUF[3], "A: -.--- Snt: -.---"); } else { sprintf(LCD_LINE_BUF[0], "%-12s%7s", " ", sOndoStr); //sprintf(LCD_LINE_BUF[1], "%6s InR:%4sm%c", "", DATA2STR(inResistance, 0), 0xF4); sprintf(LCD_LINE_BUF[1], "%6s InR:%4smR", "", DATA2STR(inResistance, 0)); sprintf(LCD_LINE_BUF[2], "V:%6s Dac:%6s", DATA2STR(V_Batt, 3), DATA2STR(V_Dac, 3)); sprintf(LCD_LINE_BUF[3], "A:%6s Snt:%6s", DATA2STR(inCurrent, 3), DATA2STR(V_Shunt, 3)); } } ////////////////////////////////////////////////////////////////////////////////////////////////// else if ( dcgPack.operMode == OPER_MODE_ADJUST ) { sprintf(LCD_LINE_BUF[0], "%-13s%7s", ((uCount%5)<2) ? "" : "Adjusting ...", sOndoStr); sprintf(LCD_LINE_BUF[1], " Set:%5sA", DATA2STR(Dcg_A, 1)); sprintf(LCD_LINE_BUF[2], "V:%6s Dac:%6s", DATA2STR(V_Batt, 3), DATA2STR(V_Dac, 3)); sprintf(LCD_LINE_BUF[3], "A:%6s Snt:%6s", DATA2STR(inCurrent, 3), DATA2STR(V_Shunt, 3)); } ////////////////////////////////////////////////////////////////////////////////////////////////// else if ( dcgPack.operMode == OPER_MODE_DCG ) { /* 온도 경고 Hold 기능에 의해 100도 까지 표시할 필요가 없음 */ if ( dcgPack.finishFlag ) sprintf(LCD_LINE_BUF[0], "%4sA %02d:%02d%1s%7s", DATA2STR(Dcg_A,1), dcgPack.dcgLogTimeHH, dcgPack.dcgLogTimeMM, (dcgPack.flicker && !dcgPack.fConfirmed) ? "":"F", sOndoStr); /* V2.0, Confirm일때 No Flicking */ else if ( dcgPack.holdFlag ) sprintf(LCD_LINE_BUF[0], "%4sA %02d:%02d%1s%7s", DATA2STR(Dcg_A,1), dcgPack.dcgLogTimeHH, dcgPack.dcgLogTimeMM, "H", sOndoStr); else { if (dcgPack.isDcgAConfig && dcgPack.flicker) { sprintf(LCD_LINE_BUF[0], " A %02d%1s%02d %7s", dcgPack.dcgLogTimeHH, (dcgPack.dcgLogTimeSS%2)? "":":", dcgPack.dcgLogTimeMM, sOndoStr); } else { sprintf(LCD_LINE_BUF[0], "%4sA %02d%1s%02d %7s", DATA2STR(Dcg_A,1), dcgPack.dcgLogTimeHH, (dcgPack.dcgLogTimeSS%2)? "":":", dcgPack.dcgLogTimeMM, sOndoStr); } } if ( dcgPack.dcgDebug ) { sprintf(LCD_LINE_BUF[1], "%6sWh %7smAh", DATA2STR(Wh,1), DATA2STR(mAh,0)); sprintf(LCD_LINE_BUF[2], "V:%6s Wat:%6s", DATA2STR(V_Batt, 3), DATA2STR(watt, 0)); /* V3.3, 소수점 버림 */ sprintf(LCD_LINE_BUF[3], "A:%6s Snt:%6s", DATA2STR(inCurrent, 3), DATA2STR(V_Shunt, 3)); } else { sprintf(LCD_LINE_BUF[1], "%6sWh %7smAh", DATA2STR(Wh,1), DATA2STR(mAh,0)); if ( dcgPack.disChargeMode == DCG_MODE_END_KILL ) sprintf(LCD_LINE_BUF[2], "V:%6s Cut: 0.00V", DATA2STR(V_Batt, 3)); else sprintf(LCD_LINE_BUF[2], "V:%6s Cut:%5sV", DATA2STR(V_Batt, 3), DATA2STR(CutOff, 2)); sprintf(LCD_LINE_BUF[3], "A:%6s End:%6s", DATA2STR(inCurrent, 3), DCG_MODE_STR(dcgPack.disChargeMode)); } } ////////////////////////////////////////////////////////////////////////////////////////////////// else if ( dcgPack.operMode == OPER_MODE_CONFIG ) { switch (dcgPack.curConfigPos) { case CPOS_NONE: case CPOS_DISC_END_MODE: case CPOS_CUTTOFF: case CPOS_DCG_A: case CPOS_CUTTOFF_MAH: sprintf(LCD_LINE_BUF[0], " Current: %6sA", FCK(CPOS_DCG_A) ? "" : DATA2STR(Dcg_A,1)); sprintf(LCD_LINE_BUF[1], "Cut-Off Vol: %6sV", FCK(CPOS_CUTTOFF) ? "" : DATA2STR(CutOff,2)); sprintf(LCD_LINE_BUF[2], "Cut-Off mAh: %7s", FCK(CPOS_CUTTOFF_MAH) ? "" : (CutOffmAh > 0) ? DATA2STR(CutOffmAh,0) : "OFF"); sprintf(LCD_LINE_BUF[3], " End-Mode: %7s", FCK(CPOS_DISC_END_MODE) ? "" : DCG_MODE_STR(dcgPack.disChargeMode)); break; case CPOS_MAS_TUNE: /* V2.4 */ case CPOS_AUTO_INR: case CPOS_SKIP_ADJ: case CPOS_TIME_TUNE: /* V3.2 */ sprintf(LCD_LINE_BUF[0], "mA/Sec Tune: %7s", FCK(CPOS_MAS_TUNE) ? "" : DATA2STR(mAsTune,3)); sprintf(LCD_LINE_BUF[1], " Time Tune: %7s", FCK(CPOS_TIME_TUNE) ? "" : dcgPack.tuneTime ? "YES" : " NO"); sprintf(LCD_LINE_BUF[2], " Auto InR: %7s", FCK(CPOS_AUTO_INR) ? "" : dcgPack.autoInrFlag ? "YES" : "NO"); sprintf(LCD_LINE_BUF[3], "Skip Adjust: %7s", FCK(CPOS_SKIP_ADJ) ? "" : dcgPack.skipAdjFlag ? "YES" : "NO"); break; case CPOS_AUTO_STOP_TIME: case CPOS_FAN_THRESHOLD: case CPOS_BACKLIGHT_CTRL: case CPOS_BEEP_OFF : sprintf(LCD_LINE_BUF[0], "Fan-On-Temp: %4s %cC", FCK(CPOS_FAN_THRESHOLD) ? "" : String(dcgPack.fanThresTemp).c_str(), 0xDF); if ( dcgPack.autoStopTime > 0 ) sprintf(LCD_LINE_BUF[1], " Auto-Hold: %2s Hour", FCK(CPOS_AUTO_STOP_TIME) ? "" : String(dcgPack.autoStopTime).c_str()); else sprintf(LCD_LINE_BUF[1], " Auto-Hold: %7s", FCK(CPOS_AUTO_STOP_TIME) ? "" : "NO-STOP"); sprintf(LCD_LINE_BUF[2], " BackLight: %7s", FCK(CPOS_BACKLIGHT_CTRL) ? "" : dcgPack.BackLightCtrl?"TimeOff":"NO-OFF"); sprintf(LCD_LINE_BUF[3], " Sound: %7s", FCK(CPOS_BEEP_OFF) ? "" : dcgPack.isBeepDisable?"OFF":"ON"); break; case CPOS_BATT_REG: sprintf(LCD_LINE_BUF[0], "V Distribution-Ratio"); sprintf(LCD_LINE_BUF[1], "(R1+R2)/R1 => "); sprintf(LCD_LINE_BUF[2], "Batt-REG : %9s", dcgPack.flicker ? "" : DATA2STR(BattREG,3)); sprintf(LCD_LINE_BUF[3], "%19sV", DATA2STR(V_Batt, 4)); break; case CPOS_V_DROP_ADJ: case CPOS_A_RATE_ADJ: sprintf(LCD_LINE_BUF[0], "Callibrate Drop Rate"); sprintf(LCD_LINE_BUF[1], "Drop V-Rate: %7s", FCK(CPOS_V_DROP_ADJ) ? "" : DATA2STR(Drop_V_Rate, 3)); sprintf(LCD_LINE_BUF[2], " Mod A-Rate: %7s", FCK(CPOS_A_RATE_ADJ) ? "" : DATA2STR(SHUNT_R+A_Rate, 4)); sprintf(LCD_LINE_BUF[3], "%8sV %9sA", DATA2STR(V_Batt+(Dcg_A*Drop_V_Rate), 3), DATA2STR(inCurrent, 3)); break; } ////////////////////////////////////////////////////////////////////////////////////////////////// } #if 0//defined(DEBUG_LCD) Serial.println(LCD_LINE_BUF[0]); Serial.println(LCD_LINE_BUF[1]); Serial.println(LCD_LINE_BUF[2]); Serial.println(LCD_LINE_BUF[3]); #endif /* update display */ if ( dcgPack.operMode == OPER_MODE_NORMAL ) { /* Version 1.4, battery symbol */ lcd.setCursor(0, 0); if (dcgPack.isNoBattery) lcd.write(CHAR_BATT_OFF); else { if ( battInRCheckCount < MAX_BATT_INR_CHK_CNT && (uCount%5)<2) { lcd.print(" "); } else lcd.write(CHAR_BATT_ON); } lcd.setCursor(1, 0); lcd.print(LCD_LINE_BUF[0]); } else { lcd.setCursor(0, 0); lcd.print(LCD_LINE_BUF[0]); } lcd.setCursor(0, 1); lcd.print(LCD_LINE_BUF[1]); lcd.setCursor(0, 2); lcd.print(LCD_LINE_BUF[2]); lcd.setCursor(0, 3); lcd.print(LCD_LINE_BUF[3]); prevTime = curTime; uCount++; } #elif defined(DISPLAY_LCD1602) void lcdUpdate(uchar_t force) { static ulong_t prevTime=0; if ( !force && curTime - prevTime < INTERVAL_LCD_UPDATE ) return; if ( !(uCount%3) ) { if ( dcgPack.BackLightCtrl ) { if (dcgPack.BackLightOnFlag) { lcdBackLightOn(); dcgPack.BackLightOnFlag = 0; } else { if ( lcdBacklightOnTime && (curTime-lcdBacklightOnTime) > LCD_BACKLIGHT_ON_TIME ) lcdBackLightOff(); } } else lcdBackLightOn(); } dcgPack.flicker = ((uCount%4)<2)?1:0; memset(LCD_LINE_BUF, 0, sizeof(LCD_LINE_BUF)); if ( dcgPack.tempWarning && dcgPack.flicker) { memset(sOndoStr, 0, sizeof(sOndoStr)); strcpy(sOndoStr, "WAN"); } ////////////////////////////////////////////////////////////////////////////////////////////////// if ( dcgPack.operMode == OPER_MODE_NORMAL ) { if ( dcgPack.isNoBattery ) { sprintf(LCD_LINE_BUF[0], " %10s%3s ", "", sOndoStr); sprintf(LCD_LINE_BUF[1], " --.---V ---mR"); } else { sprintf(LCD_LINE_BUF[0], " %10s%3s ", "", sOndoStr); sprintf(LCD_LINE_BUF[1], " %6sV %5smR", DATA2STR(V_Batt, 3), inResistance==0 ? "---" : DATA2STR(inResistance, 0)); } } ////////////////////////////////////////////////////////////////////////////////////////////////// else if ( dcgPack.operMode == OPER_MODE_ADJUST ) { sprintf(LCD_LINE_BUF[0], "%4sA %5s %3s ", DATA2STR(Dcg_A, 1), ((uCount%5)<2) ? "" : "ADJ..", sOndoStr); sprintf(LCD_LINE_BUF[1], "%5sA %4s %4s", DATA2STR(inCurrent, 2), DATA2STR(V_Dac, 2), DATA2STR(V_Shunt, 2)); } ////////////////////////////////////////////////////////////////////////////////////////////////// else if ( dcgPack.operMode == OPER_MODE_DCG ) { /* 온도 경고 Hold 기능에 의해 100도 까지 표시할 필요가 없음 */ if ( dcgPack.finishFlag ) sprintf(LCD_LINE_BUF[0], "%4sA %02d:%02d%1s%3s ", (dcgPack.isDcgAConfig && dcgPack.flicker) ? "" : DATA2STR(Dcg_A, 1), dcgPack.dcgLogTimeHH, dcgPack.dcgLogTimeMM, (dcgPack.flicker && !dcgPack.fConfirmed) ? "":"F", sOndoStr); /* V2.0, Confirm일때 No Flicking */ else if ( dcgPack.holdFlag ) sprintf(LCD_LINE_BUF[0], "%4sA %02d:%02dH%3s ", (dcgPack.isDcgAConfig && dcgPack.flicker) ? "" : DATA2STR(Dcg_A, 1), dcgPack.dcgLogTimeHH, dcgPack.dcgLogTimeMM, sOndoStr); else sprintf(LCD_LINE_BUF[0], "%4sA %02d%1s%02d %3s ", (dcgPack.isDcgAConfig && dcgPack.flicker) ? "" : DATA2STR(Dcg_A, 1), dcgPack.dcgLogTimeHH, (dcgPack.dcgLogTimeSS%2)? "":":", dcgPack.dcgLogTimeMM, sOndoStr); if ( dcgPack.dcgDebug == 1 ) { sprintf(LCD_LINE_BUF[1], "%6sA %6sW", DATA2STR(inCurrent, 3), DATA2STR(watt, 0)); /* V3.3, watt 소수점 버림 */ } else if ( dcgPack.dcgDebug == 2 ) { if ( dcgPack.disChargeMode == DCG_MODE_END_KILL ) { sprintf(LCD_LINE_BUF[1], "KILL 0.00V Cut"); } else { if ( CutOffmAh ) sprintf(LCD_LINE_BUF[1], "%2s %6smAh Cut", DCG_MODE_STR(dcgPack.disChargeMode), DATA2STR(CutOffmAh,0)); else sprintf(LCD_LINE_BUF[1], "%2s %8sV Cut", DCG_MODE_STR(dcgPack.disChargeMode), DATA2STR(CutOff,2)); } } else { sprintf(LCD_LINE_BUF[1], "%6sV %5smAh", DATA2STR(V_Batt, 3), DATA2STR(mAh,0)); } } ////////////////////////////////////////////////////////////////////////////////////////////////// else if ( dcgPack.operMode == OPER_MODE_CONFIG ) { switch (dcgPack.curConfigPos) { case CPOS_NONE: case CPOS_CUTTOFF: case CPOS_CUTTOFF_MAH: sprintf(LCD_LINE_BUF[0], " Cut-V: %6sV", FCK(CPOS_CUTTOFF) ? "" : DATA2STR(CutOff,2)); sprintf(LCD_LINE_BUF[1], "Cut-mAh: %7s", FCK(CPOS_CUTTOFF_MAH) ? "" : (CutOffmAh > 0) ? DATA2STR(CutOffmAh,0) : "OFF"); break; case CPOS_DCG_A: case CPOS_DISC_END_MODE: sprintf(LCD_LINE_BUF[0], " Current: %5sA", FCK(CPOS_DCG_A) ? "" : DATA2STR(Dcg_A,1)); sprintf(LCD_LINE_BUF[1], "End-Mode: %6s", FCK(CPOS_DISC_END_MODE) ? "" : DCG_MODE_STR(dcgPack.disChargeMode)); break; case CPOS_MAS_TUNE: /* V2.4 */ case CPOS_TIME_TUNE: /* V3.2 */ sprintf(LCD_LINE_BUF[0], "Time Tune:%6s", FCK(CPOS_TIME_TUNE) ? "" : dcgPack.tuneTime ? "YES" : " NO"); sprintf(LCD_LINE_BUF[1], " mAs Tune:%6s", FCK(CPOS_MAS_TUNE) ? "" : DATA2STR(mAsTune, 3)); break; case CPOS_AUTO_INR: /* V3.2 */ case CPOS_SKIP_ADJ: /* V3.2 */ sprintf(LCD_LINE_BUF[0], "Auto InR: %5s", FCK(CPOS_AUTO_INR) ? "" : dcgPack.autoInrFlag ? "YES" : " NO"); sprintf(LCD_LINE_BUF[1], "Skip Adj: %5s", FCK(CPOS_SKIP_ADJ) ? "" : dcgPack.skipAdjFlag ? "YES" : " NO"); break; case CPOS_AUTO_STOP_TIME: case CPOS_FAN_THRESHOLD: if ( dcgPack.autoStopTime > 0 ) sprintf(LCD_LINE_BUF[0], "Auto-Hold: %3sH", FCK(CPOS_AUTO_STOP_TIME) ? "" : String(dcgPack.autoStopTime).c_str()); else sprintf(LCD_LINE_BUF[0], "Auto-Hold: %4s", FCK(CPOS_AUTO_STOP_TIME) ? "" : "NO"); sprintf(LCD_LINE_BUF[1], " Fan Ctrl:%4s%cC", FCK(CPOS_FAN_THRESHOLD) ? "" : String(dcgPack.fanThresTemp).c_str(), 0xDF); break; case CPOS_BACKLIGHT_CTRL: case CPOS_BEEP_OFF : sprintf(LCD_LINE_BUF[0], "LcdLight: %6s", FCK(CPOS_BACKLIGHT_CTRL) ? "" : dcgPack.BackLightCtrl?"AUTO":"NO-OFF"); sprintf(LCD_LINE_BUF[1], " Beep: %6s", FCK(CPOS_BEEP_OFF) ? "" : dcgPack.isBeepDisable?"OFF":"ON"); break; case CPOS_BATT_REG: sprintf(LCD_LINE_BUF[0], "V Dist-Ratio: "); sprintf(LCD_LINE_BUF[1], "%07s %6sV", FCK(CPOS_BATT_REG) ? "" : DATA2STR(BattREG, 3), DATA2STR(V_Batt, 3)); break; case CPOS_V_DROP_ADJ: sprintf(LCD_LINE_BUF[0], "Drop V Rate: "); sprintf(LCD_LINE_BUF[1], "%07s %6sV", FCK(CPOS_V_DROP_ADJ) ? "" : DATA2STR(Drop_V_Rate, 3), DATA2STR(V_Batt+(Dcg_A*Drop_V_Rate), 3)); break; case CPOS_A_RATE_ADJ: sprintf(LCD_LINE_BUF[0], "Mod A Rate: "); sprintf(LCD_LINE_BUF[1], "%07s %6sA", FCK(CPOS_A_RATE_ADJ) ? "" : DATA2STR(SHUNT_R+A_Rate, 4), DATA2STR(inCurrent, 3)); break; } ////////////////////////////////////////////////////////////////////////////////////////////////// } /* update display */ if ( dcgPack.operMode == OPER_MODE_NORMAL ) { /* Version 1.4, battery symbol */ lcd.setCursor(0, 0); if (dcgPack.isNoBattery) { lcd.write(CHAR_BATT_OFF); } else { if ( battInRCheckCount < MAX_BATT_INR_CHK_CNT && (uCount%5)<2) { lcd.print(" "); } else lcd.write(CHAR_BATT_ON); } lcd.setCursor(1, 0); lcd.print(LCD_LINE_BUF[0]); } else { lcd.setCursor(0, 0); lcd.print(LCD_LINE_BUF[0]); } /* V2.6 */ if ( dcgPack.operMode != OPER_MODE_CONFIG ) { lcd.setCursor(15, 0); lcd.write(CHAR_BATT_ONDO); } lcd.setCursor(0, 1); lcd.print(LCD_LINE_BUF[1]); lcd.setCursor(0, 2); prevTime = curTime; uCount++; } #endif void lcdDescUpdate(char *line1, char *line2) { /* V2.7, LCD_MERGE, 2020-08-27 */ #if defined(DISPLAY_LCD2004) if (strlen(line1) > 20 || strlen(line2) > 20 ) return; lcd.clear(); lcd.setCursor(0, 1); lcd.print(line1); lcd.setCursor(0, 2); lcd.print(line2); #elif defined(DISPLAY_LCD1602) if (strlen(line1) > 16 || strlen(line2) > 16 ) return; lcd.clear(); lcd.setCursor(0, 0); lcd.print(line1); lcd.setCursor(0, 1); lcd.print(line2); #endif DELAY(1000); } void lcdBackLightOn() { //if ( lcdBacklightOnTime == 0) { lcd.backlight(); dcgPack.isBackLightOn = 1; lcdBacklightOnTime = millis(); //} } void lcdBackLightOff() { lcd.noBacklight(); dcgPack.isBackLightOn = 0; lcdBacklightOnTime = 0; dcgPack.BackLightOnFlag = 0; } void lcdPrintLogo() { lcd.setCursor(0, 0); #if defined(DISPLAY_LCD2004) lcd.print(F(" Battery Discharger ")); lcd.setCursor(0, 1); lcd.print(VERSION_STR); lcd.setCursor(0, 3); lcd.print(AUTHOR); #elif defined(DISPLAY_LCD1602) lcd.print(VERSION_STR); lcd.setCursor(0, 1); lcd.print(AUTHOR); #endif BEEP(2); DELAY(2000); } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// /* Encoder Contorl Object */ #define PRESSED LOW #define NOT_PRESSED HIGH Rotary rotary = Rotary(PIN_ROTARY_RT, PIN_ROTARY_LT); typedef struct Encoder { #define PRESS_DEBOUNCE 2 #define PRESS_LEVEL_1_TIME 2 #define PRESS_LEVEL_2_TIME 600 #define PRESS_LEVEL_3_TIME 2000 #define IS_PRESS_LEVEL1(t) (t >= PRESS_LEVEL_1_TIME && t < PRESS_LEVEL_2_TIME ) #define IS_PRESS_LEVEL2(t) (t >= PRESS_LEVEL_2_TIME && t < PRESS_LEVEL_3_TIME ) #define IS_PRESS_LEVEL3(t) (t >= PRESS_LEVEL_3_TIME ) ulong_t pressedTime=0; bool prevState:1;// = NOT_PRESSED; bool curState:1; #define CW_ACTION_COUNT 16 #define CCW_ACTION_COUNT 16 uchar_t cw_count; uchar_t ccw_count; ulong_t cw_Time=0; ulong_t ccw_Time=0; } RotaryEncoder; RotaryEncoder encoder; void checkEncoder() { ulong_t presstime = 0; /* Check Turn Count */ if ( curTime - encoder.cw_Time > 800 ) encoder.cw_count = 0; if ( curTime - encoder.ccw_Time > 800 ) encoder.ccw_count = 0; /* Check Encoder Button */ encoder.prevState = encoder.curState; encoder.curState = digitalRead(PIN_ENCODER_SWITCH); if (encoder.curState == encoder.prevState)// || encoder.curState != PRESSED ) return; DELAY(PRESS_DEBOUNCE); /* more correct but slow, consider before enabled */ encoder.curState = digitalRead(PIN_ENCODER_SWITCH); if (encoder.curState == PRESSED) { encoder.pressedTime = millis(); return; } else if ( encoder.pressedTime ) { presstime = millis() - encoder.pressedTime; encoder.pressedTime = 0; /* clear */ } if ( !dcgPack.isBackLightOn ) { dcgPack.BackLightOnFlag=1; return; } /////////////////////////////////////////////////////////////////////////////////////////// /* Processing Click Level */ if (IS_PRESS_LEVEL1(presstime)) { if ( dcgPack.operMode == OPER_MODE_DCG ) { if ( dcgPack.isDcgAConfig ) { BEEP(2); dcgPack.isDcgAConfig = 0; } else if ( dcgPack.finishFlag ) { if ( dcgPack.fConfirmed || dcgPack.isBeepDisable ) { /* V1.9, isBeepDisable */ dcgPack.operMode = OPER_MODE_NORMAL; DCG_GATE_SHUTDOWN; //V2.0, 강제 종료 후 버튼을 눌러 노멀모드로 빠져 나올경우 } else dcgPack.fConfirmed = 1; } else { dcgPack.holdFlag = !dcgPack.holdFlag; BEEP(1); } } else if ( dcgPack.operMode == OPER_MODE_CONFIG ) { //BEEP(1); SHIFT_CPOS(dcgPack.curConfigPos); if (dcgPack.curConfigPos == CPOS_A_RATE_ADJ || dcgPack.curConfigPos == CPOS_V_DROP_ADJ) { DCG_GATE_OPEN(Dcg_A * DAC_PWR_1A); /* V3.0 */ dcgPack.isCallibrate = 1; } else { DCG_GATE_SHUTDOWN; dcgPack.isCallibrate = 0; } } else { /* retry Battery Check */ initDcgData(); battInRCheckCount = 0; } } /////////////////////////////////////////////////////////////////////////////////////////// else if (IS_PRESS_LEVEL2(presstime)) { if ( dcgPack.operMode == OPER_MODE_NORMAL ) { if ( dcgPack.isNoBattery ){ BEEP(3); lcdDescUpdate( #if defined(DISPLAY_LCD2004) " Error :", " No Connection" #elif defined(DISPLAY_LCD1602) "Error :", " No Connection " #endif ); return; } /* V3.2 */ if ( dcgPack.skipAdjFlag ) { dac_power = DAC_PWR_1A; if ( V_Batt < 1.0 && dcgPack.disChargeMode == DCG_MODE_END_KILL) { dac_power = DAC_PWR_1A/2; } } else { adjustDacPower(); } #if defined(SERIAL_LOGGING) /* CSV Format for EXCEL */ Serial.println(F("Time, Voltage, Current, mAh, Wh")); #endif initDcgData(); dcgPack.startFlag = 1; dcgPack.fConfirmed = 0; dcgPack.operMode = OPER_MODE_DCG; BEEP(2); } else if ( dcgPack.operMode == OPER_MODE_DCG ) { BEEP(2); if ( dcgPack.finishFlag ) { /* Forced Restart */ dcgPack.finishFlag= 0; dcgPack.fConfirmed = 0; } else { dcgPack.finishFlag = 1; /* Forced Stop */ dcgPack.fConfirmed = 1; /* V2.4 */ } } else if ( dcgPack.operMode == OPER_MODE_CONFIG ) { BEEP(2); saveBattChecker(); initBattChecker(); dcgPack.operMode = OPER_MODE_NORMAL; battInRCheckCount = MAX_BATT_INR_CHK_CNT; DCG_GATE_SHUTDOWN; /* V1.8 */ lcdDescUpdate( #if defined(DISPLAY_LCD2004) " Configuration ", " Saved!! " #elif defined(DISPLAY_LCD1602) " Configuration ", " Saved!! " #endif ); } } /////////////////////////////////////////////////////////////////////////////////////////// else if (IS_PRESS_LEVEL3(presstime)) { if ( dcgPack.operMode == OPER_MODE_CONFIG ) { setDefaultBattChecker(); lcdDescUpdate( #if defined(DISPLAY_LCD2004) " Initialize...", " OK!!" #elif defined(DISPLAY_LCD1602) " Initialize... ", " OK!!" #endif ); } } } void checkEncoderRotaryCount(int dir) { if ( dir != DIR_CW && dir != DIR_CCW ) return; if ( dir == DIR_CW ) { encoder.cw_count++; encoder.cw_Time = millis(); if ( encoder.cw_count > CW_ACTION_COUNT ) { if ( dcgPack.operMode == OPER_MODE_NORMAL ) { dcgPack.beepCount=2; dcgPack.operMode = OPER_MODE_CONFIG; dcgPack.curConfigPos = CPOS_NONE; // V2.0 환경설정 진입 동작 후 END모드 설정값이 증가하는 걸 보완 encoder.cw_count = 0; } } } else if ( dir == DIR_CCW ) { encoder.ccw_count++; encoder.ccw_Time = millis(); if ( encoder.ccw_count > CCW_ACTION_COUNT ) { encoder.ccw_count = 0; if ( dcgPack.operMode == OPER_MODE_DCG ) { if ( !dcgPack.finishFlag ) { /* V2.6 */ dcgPack.beepCount=2; dcgPack.isDcgAConfig = 1; } } } } } void isrEncoderRotary() { int dir = rotary.process(); if ( dir == DIR_CW || dir == DIR_CCW ) dcgPack.BackLightOnFlag=1; if ( dcgPack.operMode == OPER_MODE_NORMAL ) { checkEncoderRotaryCount(dir); return; } /* 방전중 전류값 조정 기능 */ else if ( dcgPack.operMode == OPER_MODE_DCG ) { switch (dir) { case DIR_CW: if ( dcgPack.isDcgAConfig ) { if ( Dcg_A < DCG_MAX_CURRENT ) { /* V2.6 */ Dcg_A += 0.1; dac_power += DAC_PWR_1A/10; } } else { #if defined(DISPLAY_LCD2004) dcgPack.dcgDebug = !dcgPack.dcgDebug; #elif defined(DISPLAY_LCD1602) dcgPack.dcgDebug = dcgPack.dcgDebug == 0 ? 1 : dcgPack.dcgDebug == 1 ? 2 : 0; #endif } //V3.2_Disable, dcgPack.beepCount=1; break; case DIR_CCW: if ( dcgPack.isDcgAConfig ) { if ( Dcg_A > DCG_MIN_CURRENT ) { /* V2.6 */ Dcg_A -= 0.1; dac_power -= DAC_PWR_1A/10; } //V3.2_Disable, dcgPack.beepCount=1; } else checkEncoderRotaryCount(DIR_CCW); break; } return; } switch (dir) { case DIR_CW: { //V3.2_Disable, if ( dcgPack.curConfigPos != CPOS_NONE ) dcgPack.beepCount=1; switch (dcgPack.curConfigPos) { case CPOS_BATT_REG: /* 사용된 저항치에 따라 유연하게 적용 ==> +1 */ if (BattREG >= 7.000 && BattREG <= 39.999 ) BattREG += 0.001; break; case CPOS_V_DROP_ADJ: if ( Drop_V_Rate < 0.500 ) /* 0.3 --> 0.5 */ Drop_V_Rate += 0.001; break; case CPOS_A_RATE_ADJ: //if ( A_Rate < 49.999 ) /* V1.9 */ if ( A_Rate < 0.100 ) /* V3.0 */ A_Rate += 0.0001; break; case CPOS_DISC_END_MODE : ROTATE_DCG_MODE(dcgPack.disChargeMode); break; case CPOS_CUTTOFF: if (CutOff < 4.16 ) { CutOff += 0.05; } else { if (CutOff < 50.0) CutOff += 0.1; /* V2.9, 0.1V 단위 조정 */ } UPDATE_CUT_RATE; break; case CPOS_CUTTOFF_MAH: if ( CutOffmAh >= 50000 && CutOffmAh < 100000 ) /* MAX 100A */ CutOffmAh += 2000; /* 2A */ else if ( CutOffmAh >= 10000 && CutOffmAh < 50000 ) CutOffmAh += 1000; /* 1A */ else if ( CutOffmAh >= 1000 && CutOffmAh < 10000 ) CutOffmAh += 500; /* 500mAh*/ else if ( CutOffmAh >=0 && CutOffmAh < 1000 ) CutOffmAh += 100; /* 100mAh*/ break; case CPOS_DCG_A: if ( Dcg_A < DCG_MAX_CURRENT ) { /* V2.6 */ Dcg_A += 0.1; dcgPack.adjusted = 0; /* V2.7 */ } break; case CPOS_AUTO_STOP_TIME: if (dcgPack.autoStopTime < 48 ) dcgPack.autoStopTime += 1; break; case CPOS_FAN_THRESHOLD: if ( dcgPack.fanThresTemp <= 95) dcgPack.fanThresTemp += 5; break; case CPOS_BACKLIGHT_CTRL: dcgPack.BackLightCtrl = !dcgPack.BackLightCtrl; break; case CPOS_BEEP_OFF : dcgPack.isBeepDisable = !dcgPack.isBeepDisable; break; case CPOS_MAS_TUNE: /* V2.4 */ if ( mAsTune < 1.000 ) mAsTune += 0.001; break; case CPOS_AUTO_INR: /* V3.2 */ dcgPack.autoInrFlag = !dcgPack.autoInrFlag; break; case CPOS_SKIP_ADJ: /* V3.2 */ dcgPack.skipAdjFlag = !dcgPack.skipAdjFlag; break; case CPOS_TIME_TUNE: /* V3.2 */ dcgPack.tuneTime = !dcgPack.tuneTime; break; } break; } case DIR_CCW: { //V3.2_Disable, if ( dcgPack.curConfigPos != CPOS_NONE ) dcgPack.beepCount=1; switch (dcgPack.curConfigPos) { case CPOS_BATT_REG: if (BattREG >= 7.001 && BattREG < 40.001 ) BattREG -= 0.001; break; case CPOS_V_DROP_ADJ: if ( Drop_V_Rate > 0.001 ) Drop_V_Rate -= 0.001; break; case CPOS_A_RATE_ADJ: //if ( A_Rate > 1.001 ) /* V1.9 */ if ( A_Rate > -0.1000 ) /* V1.9 */ A_Rate -= 0.0001; break; case CPOS_DISC_END_MODE : ROTATE_DCG_MODE(dcgPack.disChargeMode); break; case CPOS_DCG_A: if ( Dcg_A > DCG_MIN_CURRENT ) { /* V2.6 */ Dcg_A -= 0.1; dcgPack.adjusted = 0; /* V2.7 */ } break; case CPOS_CUTTOFF: if (CutOff > 2.05 && CutOff < 4.16 ) { CutOff -= 0.05; } else if (CutOff >= 4.16 && CutOff < 50.0) { CutOff -= 0.1; /* V2.9, 0.1V 단위 조정 */ } UPDATE_CUT_RATE; break; case CPOS_CUTTOFF_MAH: if ( CutOffmAh > 50000 && CutOffmAh <= 100000 ) /* MAX 100A */ CutOffmAh -= 2000; /* 2A */ else if ( CutOffmAh > 10000 && CutOffmAh <= 50000 ) CutOffmAh -= 1000; /* 1A */ else if ( CutOffmAh > 1000 && CutOffmAh <= 10000 ) CutOffmAh -= 500; /* 500mAh*/ else if ( CutOffmAh > 100 && CutOffmAh <= 1000 ) CutOffmAh -= 100; /* 100mAh*/ else CutOffmAh = 0; break; case CPOS_AUTO_STOP_TIME: if (dcgPack.autoStopTime > 0) dcgPack.autoStopTime -= 1; break; case CPOS_FAN_THRESHOLD: if ( dcgPack.fanThresTemp >= 5 ) dcgPack.fanThresTemp -= 5; break; case CPOS_BACKLIGHT_CTRL: dcgPack.BackLightCtrl = !dcgPack.BackLightCtrl; break; case CPOS_BEEP_OFF : dcgPack.isBeepDisable = !dcgPack.isBeepDisable; break; case CPOS_MAS_TUNE: /* V2.4 */ if ( mAsTune > -1.000 ) mAsTune -= 0.001; break; case CPOS_AUTO_INR: /* V3.2 */ dcgPack.autoInrFlag = !dcgPack.autoInrFlag; break; case CPOS_SKIP_ADJ: /* V3.2 */ dcgPack.skipAdjFlag = !dcgPack.skipAdjFlag; break; case CPOS_TIME_TUNE: /* V3.2 */ dcgPack.tuneTime = !dcgPack.tuneTime; break; } break; } break; } } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// void setup() { /* LCD Setup */ lcd.init(); #if defined(DISPLAY_LCD2004) lcd.begin(20, 4); /* V2.5, charBattOff 깨지는 문제점 수정 */ lcd.createChar(CHAR_BATT_ON, charBattOn); lcd.createChar(CHAR_BATT_OFF, charBattOff); #elif defined(DISPLAY_LCD1602) lcd.begin(16, 2); /* V2.5, charBattOff 깨지는 문제점 수정 */ lcd.createChar(CHAR_BATT_ON, charBattOn); lcd.createChar(CHAR_BATT_OFF, charBattOff); lcd.createChar(CHAR_BATT_ONDO, charOndo); #endif /* PIN MODE */ pinMode(PIN_ROTARY_RT, INPUT_PULLUP); pinMode(PIN_ROTARY_LT, INPUT_PULLUP); pinMode(PIN_ENCODER_SWITCH, INPUT_PULLUP); pinMode(PIN_BUZZER, OUTPUT); pinMode(PIN_FAN_CONTROL, OUTPUT); pinMode(PIN_FAN_2_CONTROL, OUTPUT); pinMode(PIN_LED_BATT_ON, OUTPUT); pinMode(PIN_LED_BATT_DCG_ON, OUTPUT); /* Default LED Status, 초기 구동확인 */ digitalWrite(PIN_LED_BATT_ON, HIGH); digitalWrite(PIN_LED_BATT_DCG_ON, HIGH); digitalWrite(PIN_FAN_CONTROL, HIGH); digitalWrite(PIN_FAN_2_CONTROL, HIGH); lcdBackLightOn(); initBattChecker(); lcdPrintLogo(); MCP4725.begin(0x60); // The I2C Address of my DAC module START MCP4725.setVoltage(0, false); ads.setGain(GAIN_TWOTHIRDS); // ADC 측정범위 설정. +/- 6.144V (1bit = 0.1875mV) ads.begin(); /* interrupt */ attachInterrupt(0, isrEncoderRotary, CHANGE); attachInterrupt(1, isrEncoderRotary, CHANGE); #if defined(SERIAL_LOGGING) || defined(DEBUG_DCG) || defined(DEBUG_ONDO) || defined(DEBUG_ADS1115) /* start serial if DEBUG is ON */ Serial.begin(9600); #endif } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// void loop() { /* note : check time only here */ curTime = millis(); pollTemperature(); /* V3.0 */ if ( dcgPack.operMode == OPER_MODE_DCG ) { dcgBattery(); } else { scanBattery(); } checkEncoder(); if ( dcgPack.beepCount ) { BEEP(dcgPack.beepCount); dcgPack.beepCount = 0; } lcdUpdate(0); } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// //#pragma GCC pop_options