얼마전에 저가 rc카를 하나 구매를 했는데
같이 딸려온 rc충전팩이 영 시원찮습니다.
오래방치해서 수명이 다됐거나, 아니면 구라 용량인지 몇시간충전해도 단 몇분이면 rc카가 멈추는 희안한 사태가 벌어져서
버릴까 하다가 그냥 남는 부품도 있고 해서 배터리 충방전기를 만들어 보기로 했습니다.
같이 딸려온 충전아답터는 보니까 과충전 방지 기능이나 전류 제어 같은 기능은 전혀 없는듯하고 그냥
계속 일정 전압과 전류만 지속적으로 공급해주는 단순한 타입인듯한데,
요즘들어 배터리를 많이 가지고 놀다보니 배터리에 대해서 여러가지 정보를 많이 접한지라
왠지 무서워서
충전 시간이라도 제어되는 장치를 만들어 보기로 했습니다.
일단 전체 장치 구성입니다. 왠지 허접해 보이지만
아두이노 나노 호환 모듈을 통해서 제어부를 구성했습니다.
2개의 릴레이 모듈을 사용했는데 각기 전류 흐름을 제어하는 스위치입니다.
1개의 릴레이는 방전부로 가는 통로를 제어하고 있고
나머지 한개의 릴레이는 전압측정부로 가는 흐름을 제어합니다.
마지막 릴레이는 충전전류를 제어하는 역할입니다.
오른쪽 기판에는 3개의 시멘트 저항과 2개의 일반 저항 및 스위치 2개가 있습니다.
2개의 시멘트 저항은 직렬구성되어 있고 릴레이 스위치 및 배터리와 연결되어 배터리 방전회로를 구성하고 있습니다.
한개의 시멘트저항과 2개의 일반저항은 전압측정을 위한 회로를 구성합니다.
배터리 전압을 대략 1/2로 분압해서 아두이노의 아날로그 입력을 통해서 전압을 측정합니다.
배터리 방전시 일정이하로 떨어질경우 방전을 중지하기 위한 구성입니다.
저항의 오차에 의해서 정밀한 전압측정은 되지 않지만 딱히 다른 방법이 없으니 ㅎㅎ
각 스위치는 충전시간을 선택하는 충전모드 선택과 충방전 루틴을 시작/종료 시키는 역할을 합니다.
아래 사진에 보이는 가변저항은 원래 방전회로를 구성하기 위해서 사용했던 것인데, 실사용결과 완충된 배터리 방전시 하얀 연기를 내뿜으며 불이 날뻔해서
오른쪽의 시멘트 저항으로 교체를 했습니다. 대략 210ma로 방전을 하게 되어 있고 배터리 팩의 전압이 5v이하가 되는 시점에 방전을 종료하도록했습니다(배터리 셀당 대략 1v정도). 전압측정부의 시멘트 저항은 일반저항의 전력초과로 타버리는 것을 방지하기 위한 차원으로 직렬연결해뒀습니다.
1/2분압회로에서 안그래도 저항의 오차가 있는데 시멘트저항이 오차를 증가 시키겠지만 일반저항의 저항값이 매우 크므로(10k Ohm) 크게 신경쓰지 않아도 될정도이지 않나 생각됩니다.
현재 충방전기의 상태를 표시하기 위해서 16 * 2 lcd 디스플레이를 사용했습니다. 오른쪽 2채널릴레이는 위에서 설명했지만
전압측정부와 충전아답터로부터 오는 전류를 제어하는 역할입니다.
아래는 실제 작동영상입니다.
충방전기 소스는 다음과 같습니다.
def.h//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef DEF_H_
#define DEF_H_
#define BATT_PIN A0 //전압측정핀
#define LELAY_PIN 12 //방전릴레이 제어핀
#define LIMIT_VOLTAGE 5.0f //방전종지전압
#define BATT_CHK_PIN 3 //전압측정릴레이 제어핀
#define CHARGE_CPIN 4 //충전전류제어 릴레이 핀
#define CHARGE_TIME1 (60 * 60 * 3.64f) //700mAh배터리팩 충전시간
#define CHARGE_TIME2 (60 * 60 * 5.2f) //1300mAh배터리팩 충전시간
#define MODE_1 0 //700mAh충전모드
#define MODE_2 1 //1300mmAh충전모드
//충반전기 상태정의
#define STATUS_WAIT 0 //아무런 처리를 하고 있지않음
#define STATUS_DIS_CHARGE 1 //방전중
#define STATUS_CHARGE 2 //충전중
#define STATUS_IDLE 3 //충반전완료됨
#define MODE_PIN 6 //충전시간 선택 버튼 핀
#define STARTSTOP_PIN 7 //충방전 시작/종료 버튼 핀
#endif
CMyTime.h//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef MYTIME_H_
#define MYTIME_H_
//시간측정기
class CMyTime
{
long m_lTime;
public:
CMyTime()
{
m_lTime = millis();
}
//현재 시간을 캡쳐함
void Capture()
{
m_lTime = millis();
}
//이전에 capture함수를 호출한 시점부터 이함수를 호출한시점까지의 경과 시간을 초단위로 구함
float GetPassedTime()
{
long lpassedTime = millis() - m_lTime;
return (float)lpassedTime * (1.0f / 1000.0f);
}
};
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <inttypes.h>
#include <MemoryFree.h>
#include "def.h"
#include "CMyTime.h"
LiquidCrystal_I2C g_LCD(0x27,16,2);
//전압측정
class CVoltMng
{
public:
void Init()
{
pinMode(BATT_CHK_PIN, OUTPUT);
Enable(false);
}
void Enable(bool bEnable)
{
//digitalWrite(BATT_CHK_PIN, bEnable ? HIGH : LOW);
digitalWrite(BATT_CHK_PIN, bEnable ? LOW : HIGH);
}
float GetVoltage()
{
return ((float)analogRead(BATT_PIN) / 1024.0f) * 5.0f * 2.0f;
}
};
CVoltMng g_VoltMng;
//충전 제어
class CCharger
{
bool m_bEnabled;
bool m_bCompleted;
CMyTime m_CheckTime;
CMyTime m_ScrUpdateTime;
int m_iFlag;
int m_iMode;
float m_fChargeTime;
public:
CCharger()
{
m_fChargeTime = CHARGE_TIME1;
m_iMode = MODE_1;
Clear();
}
void Clear()
{
m_bEnabled = false;
m_bCompleted = false;
}
int GetMode() {return m_iMode;}
void SelectMode1()
{
m_iMode = MODE_1;
m_fChargeTime = CHARGE_TIME1;
}
void SelectMode2()
{
m_iMode = MODE_2;
m_fChargeTime = CHARGE_TIME2;
}
void Init()
{
m_bEnabled = false;
m_bCompleted = false;
pinMode(CHARGE_CPIN, OUTPUT);
EnableRelay(false);
}
bool IsCompleted() {return m_bCompleted;}
void Start()
{
EnableRelay(true);
m_CheckTime.Capture();
m_ScrUpdateTime.Capture();
m_bEnabled = true;
m_iFlag = 0;
}
void Update()
{
if (!m_bEnabled)
return;
if (m_bCompleted)
return;
if (m_ScrUpdateTime.GetPassedTime() >= 1.0f)
{
m_ScrUpdateTime.Capture();
g_LCD.clear();
g_LCD.home();
float fPassedTime = m_CheckTime.GetPassedTime();
long min = (long)(fPassedTime / 60.0f);
long sec = (long)(fPassedTime - (min * 60));
switch(m_iFlag)
{
case 0:
g_LCD.print(min);
g_LCD.print("m ");
g_LCD.print(sec);
g_LCD.print("s");
if (m_iMode == MODE_1)
g_LCD.print(" 700mAh");
else
g_LCD.print(" 1300mAh");
g_LCD.setCursor(0, 1);
g_LCD.print("charging.");
++m_iFlag;
break;
case 1:
g_LCD.print(min);
g_LCD.print("m ");
g_LCD.print(sec);
g_LCD.print("s");
if (m_iMode == MODE_1)
g_LCD.print(" 700mAh");
else
g_LCD.print(" 1300mAh");
g_LCD.setCursor(0, 1);
g_LCD.print("charging..");
++m_iFlag;
break;
case 2:
g_LCD.print(min);
g_LCD.print("m ");
g_LCD.print(sec);
g_LCD.print("s");
if (m_iMode == MODE_1)
g_LCD.print(" 700mAh");
else
g_LCD.print(" 1300mAh");
g_LCD.setCursor(0, 1);
g_LCD.print("charging...");
m_iFlag = 0;
break;
}
}
if (m_CheckTime.GetPassedTime() >= m_fChargeTime)
{
m_bCompleted = true;
EnableRelay(false);
g_LCD.clear();
g_LCD.home();
g_LCD.print("charging completed.");
}
}
void EnableRelay(bool bEnable)
{
digitalWrite(CHARGE_CPIN, bEnable ? LOW : HIGH);
}
};
CCharger g_Charger;
//방전제어
class CDisCharger
{
bool m_bEnabled;
CMyTime m_CheckTime;
CMyTime m_TotalTime;
CMyTime m_EndStepTime;
bool m_bDisChargeCompleted;
int m_iEndStep;
float m_fResultTime;
public:
CDisCharger()
{
Clear();
}
void Clear()
{
m_bEnabled = true;
m_bDisChargeCompleted = false;
m_iEndStep = 0;
m_fResultTime = 0.0f;
}
void Init()
{
m_bEnabled = true;
m_bDisChargeCompleted = false;
m_iEndStep = 0;
m_fResultTime = 0.0f;
m_CheckTime.Capture();
m_TotalTime.Capture();
pinMode(LELAY_PIN, OUTPUT);
EnableDischarger(false);
}
/*
배터리가 지속적으로 방전시 방전이 거의 완료되는 시점에 전압이 급격히 떨어지는데
그렇다고 해서 완전히 방전된것이 아니다.
방전을 중지시키고 약간 대기하게 되면 다시 전압과 전류가 올라가는데
그 시점에 대한 처리
5초간 방전, 2초간 휴식을 계속반복하면서 배터리 전압을 측정해서 이때에 전압이 허용범위이하까지 떨어지면
완전방전되었다고 가정함.
*/
void ProcessEndStep()
{
if (m_iEndStep == 1)
{
if (m_EndStepTime.GetPassedTime() < 5.0f)
return;
}
else
{
if (m_EndStepTime.GetPassedTime() < 2.0f)
return;
}
m_EndStepTime.Capture();
if (m_iEndStep == 1)
{
EnableDischarger(false);
delay(150);
g_VoltMng.Enable(true);
delay(150);
float fCurVolt = g_VoltMng.GetVoltage();
g_VoltMng.Enable(false);
delay(150);
g_LCD.clear();
g_LCD.home();
g_LCD.print(fCurVolt, 3);
if (m_bDisChargeCompleted)
g_LCD.print("V, end");
else
g_LCD.print("V");
g_LCD.setCursor(0, 1);
g_LCD.print("Time = ");
if (!m_bDisChargeCompleted)
g_LCD.print(m_TotalTime.GetPassedTime() / 60.0f, 2);
else
g_LCD.print(m_fResultTime / 60.0f, 2);
g_LCD.print("min");
if (fCurVolt <= LIMIT_VOLTAGE)
{
m_iEndStep = 3;
return;
}
EnableDischarger(true);
m_iEndStep = 2;
}
else if (m_iEndStep == 2)
{
m_iEndStep = 1;
EnableDischarger(false);
}
}
void Update()
{
if (!m_bEnabled)
return;
if (m_CheckTime.GetPassedTime() < 10.0f)
return;
m_CheckTime.Capture();
if (m_iEndStep == 1 || m_iEndStep == 2)
{
ProcessEndStep();
}
else
{
EnableDischarger(false);
delay(150);
g_VoltMng.Enable(true);
delay(150);
float fCurVolt = g_VoltMng.GetVoltage();
g_VoltMng.Enable(false);
delay(150);
EnableDischarger(true);
if (!m_bDisChargeCompleted && fCurVolt <= LIMIT_VOLTAGE)
{
EnableDischarger(false);
if (m_iEndStep == 3)
{
m_bDisChargeCompleted = true;
m_fResultTime = m_TotalTime.GetPassedTime();
}
else
{
m_iEndStep = 1;
m_EndStepTime.Capture();
}
}
g_LCD.clear();
g_LCD.home();
g_LCD.print(fCurVolt, 3);
if (m_bDisChargeCompleted)
g_LCD.print("V, end");
else
g_LCD.print("V");
switch (g_Charger.GetMode())
{
case MODE_1:
g_LCD.print(", 700mAh");
break;
case MODE_2:
g_LCD.print(", 1300mAh");
break;
}
}
g_LCD.setCursor(0, 1);
g_LCD.print("Time = ");
if (!m_bDisChargeCompleted)
g_LCD.print(m_TotalTime.GetPassedTime() / 60.0f, 2);
else
g_LCD.print(m_fResultTime / 60.0f, 2);
g_LCD.print("min");
}
void EnableDischarger(bool bEnable)
{
digitalWrite(LELAY_PIN, bEnable? HIGH : LOW);
}
bool IsCompleted() {return m_bDisChargeCompleted;}
};
CDisCharger g_DisCharger;
//버튼 스위치 입력처리
class CInputMng
{
bool m_bNowDelay;
CMyTime m_Time;
int m_iPin;
public:
CInputMng(int pin)
{
m_bNowDelay = false;
m_iPin = pin;
m_Time.Capture();
}
void Init()
{
pinMode(m_iPin, INPUT_PULLUP);
}
bool Check()
{
if (m_bNowDelay)
{
if (m_Time.GetPassedTime() > 1.0f)
{
m_bNowDelay = false;
}
return false;
}
if (digitalRead(m_iPin) == LOW)
{
m_bNowDelay = true;
m_Time.Capture();
return true;
}
return false;
}
};
CInputMng g_InputMode(MODE_PIN);
CInputMng g_StartStop(STARTSTOP_PIN);
int g_iCurMode = STATUS_WAIT;
//충반전 루틴 정지
void StopProcessing()
{
g_DisCharger.Init();
g_Charger.Init();
g_iCurMode = STATUS_WAIT;
g_LCD.clear();
g_LCD.home();
g_LCD.print("Stop Processing..");
g_LCD.setCursor(0, 1);
if (g_Charger.GetMode() == MODE_1)
g_LCD.print("700mAh..");
else
g_LCD.print("1300mAh..");
}
//충방전 루틴 시작
void StartProcessing()
{
g_DisCharger.Init();
g_Charger.Init();
g_InputMode.Init();
g_StartStop.Init();
g_DisCharger.EnableDischarger(true);
g_LCD.clear();
g_LCD.home();
g_LCD.print("Discharging..");
g_iCurMode = STATUS_DIS_CHARGE;
}
void setup()
{
/* add setup code here */
g_LCD.init();
g_LCD.backlight();
g_VoltMng.Init();
g_InputMode.Init();
g_StartStop.Init();
StopProcessing();
}
void loop()
{
if (g_StartStop.Check())
{
if (g_iCurMode != STATUS_WAIT)
{
StopProcessing();
return;
}
else
{
StartProcessing();
return;
}
}
if (g_InputMode.Check())
{
switch(g_Charger.GetMode())
{
case MODE_1:
g_Charger.SelectMode2();
break;
case MODE_2:
default:
g_Charger.SelectMode1();
break;
}
StopProcessing();
return;
}
/* add main program code here */
if (g_iCurMode == STATUS_DIS_CHARGE)
{
g_DisCharger.Update();
if (g_DisCharger.IsCompleted())
{
g_VoltMng.Enable(false);
g_DisCharger.EnableDischarger(false);
delay(1000);
g_iCurMode = STATUS_CHARGE;
g_Charger.Start();
}
}
else if (g_iCurMode == STATUS_CHARGE)
{
g_Charger.Update();
if (g_Charger.IsCompleted())
{
g_iCurMode = STATUS_IDLE;
}
}
else if (g_iCurMode == STATUS_IDLE)
{
delay(1000);
}
}
'아두이노작품' 카테고리의 다른 글
아두이노 4륜구동 (0) | 2015.05.23 |
---|---|
밸런싱 로봇 만들어 보기 (105) | 2015.05.09 |
라인트레이서 만들어보기 (1) | 2015.03.25 |
아두이노 스파이더 로봇만들어보기 (0) | 2015.02.22 |
아두이노 탱크 만들어보기 (0) | 2015.02.22 |