북동부에 살면서 오래된 자동차나 보트, 잔디 깎는 기계 등 배터리로 시동 거는 차량을 악천후(일부는 겨울이라 부름)에 사용하지 않거나 운전하지 않으면 매 시즌 배터리를 어떻게 할지 결정해야 합니다. 한두 개라면 트리클 충전기로 꽤 쉽습니다. 15달러 미만의 저렴한 단순 충전기는 단순히 13볼트 이상을 배터리에 공급할 뿐 배터리 피드백이나 모니터링이 없습니다. 더 나은 선택은 스마트 충전기입니다. 보통 일반 고속 충전과 배터리 전압과 상태에 따라 출력을 조절하는 눈/트리클 충전 모드가 있습니다. 매우 잘 작동합니다. 하버 프레이트(Harbor Freight) 제품이 39달러, 세일가 29달러인데 거의 항상 세일 중입니다.
저는 1975년형 MGB(3.5리터 올즈모빌 V8), 1981년형 토요타 픽업, 보트, 발전기 등 12볼트 배터리를 사용하는 장비가 4대 있습니다. 겨울에 여행을 가면 차 한 대를 두고 가니 배터리가 하나 더 있습니다. 하버 프레이트 세일가로 5개 사면 150달러입니다.
이 생각을 하던 중, 각 배터리에 하나씩 연결하면 배터리를 일정 수준으로 충전하고 나머지 시간 동안 모니터링하며 트리클 충전만 한다는 생각이 들었습니다. 그렇다면 하루에 몇 시간씩 각 배터리에 연결해도 되지 않을까요? 자동으로 배터리 사이를 전환할 수 있다면?
잘 됩니다. 아두이노 하나로 바로 그 일을 하는 프로그램을 작성했고, 몇 가지 추가 기능까지 넣게 되었습니다. 여기에 최종 결과물의 세부 사항을 소개합니다. 이 버전은 최대 6개 배터리까지 자동 감지 기능으로 설정되어 있습니다.
1~6개 배터리까지 작동합니다. 첫 번째 배터리는 반드시 1번 위치에 연결해야 합니다. 이 연결은 아두이노와 기타 전자 부품에 전원을 공급하는 전압 레귤레이터와 연결되어 있습니다. 2번부터는 어떤 순서로든 연결할 수 있습니다. 배터리를 연결하면 Read_Show_Volts 함수에서 전압을 감지하여 해당 배터리를 충전 루프에 추가합니다.
작동 원리
6개 배터리를 수용하고 싶었습니다. 아두이노 우노의 A4, A5 아날로그 입력은 2선식 LCD 제어에 필요하므로 아날로그 입력이 4개만 남는데, 이는 부족합니다. 그래서 MCP3008 8채널 아날로그 입력을 연결했습니다. 각 MCP3008 입력에 2개의 1kΩ 저항을 전압 분배기로 사용합니다.
프로그램에서 기본적으로 4개의 함수가 호출됩니다.
read_show_volts 함수: 각 연결부에서 8볼트 이상이 있는지 테스트하여 배터리가 연결되어 있는지 확인합니다. 8V 이상이면 해당 연결을 충전 루프에 추가합니다.
update_display 함수: 디스플레이를 업데이트합니다. 먼저 1, 2번 연결 전압을 1번째 줄에, 3, 4번 연결 전압을 2번째 줄에 표시합니다. 타이머가 알리면 5, 6번 연결 전압을 1번째 줄에 표시하고, 남는 2개 아날로그 입력으로 시스템 전압(5V 희망)과 레퍼런스 전압(3.3V)을 표시합니다. 이 읽기 값은 필요 없지만 빈 줄이 있었고 뭘 표시할지 몰라 넣었습니다. 아이디어 환영합니다.
read_distance_update 함수: 가장 재미있게 가지고 놀았던 기능입니다. HC-SR04 초음파 거리 측정 장치를 연결했습니다. LCD로 전압을 모니터링하고 싶지만 항상 앞에 서서 볼 수는 없습니다. 스위치를 달 수도 있지만(쉬운 방법), HC-SR04를 연결하여 일정 시간 후 LCD를 끄고, 손을 HC-SR04 가까이 지나가면 LCD를 다시 x초간 켜게 했습니다.
대부분의 설정을 변수로 두어 필요에 맞게 조정할 수 있습니다. 배터리당 충전 시간을 1시간으로 설정했습니다. 배터리 2개가 연결되면 각각 1시간씩 하루 12번 충전합니다. 4개면 하루 6번, 이런 식입니다.
HC-SR04를 연결했으니 타이머 루프를 더 짧은 시간(배터리당 몇 초)으로 변경하는 루틴도 추가했습니다. 배터리 간 전환이 잘 되는지 확인하려고 1시간을 기다릴 필요 없게 하려는 의도였습니다. HC-SR04 위에 손을 몇 초간 대면 LCD 백라이트가 깜빡여 전환됨을 알리고, 릴레이가 몇 초마다 전환되는 소리가 들립니다. 다시 손을 대면 원래대로 돌아옵니다.
코드 압축 파일: BatteryMultiplexer
소스 코드
배터리 멀티플렉서 아두이노 코드
아두이노 우노를 사용했습니다.
#include
#include // 버전 1.2.1 사용
#include
#include
auto timer = timer_create_default(); // 기본 설정으로 타이머 생성
// LCD 생성자 - 주소 0x27은 사용자 것에 따라 다를 수 있음
// YWRobot LCM1602 IIC V1 기반
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
int zcnt = 0;
int acnt = 0; // 현재 범용
int chargeTime = (10 * 1000); // 각 배터리에 소비할 시간
int charge_time_counter = 59; // 몇 번의 충전 시간이 지났나?
int charge_time_max = 60; // 최대 충전 시간 카운트
int relay1 = 3; // 각 릴레이별 핀
int relay2 = 4;
int relay3 = 5;
int relay4 = 6;
int current_relay = 0; // 현재 켜진 릴레이
int max_relays = 3; // 릴레이 수 - 0 기반. 처음엔 3개 모두 예상
float volts1; // 각 배터리 전압
float volts2;
float volts3;
int min_volts = 1; // 충전할 배터리 최소 전압
int volts_update_count = 5; // 전압 읽기 업데이트 주기(초)
int volts_update_counter = 0; // 경과 초 카운트
int volts_update = (500);
int read_distance_update = (500); // 거리 리더 업데이트 주기(밀리초)
int time_on_counter = 0; // 디스플레이 온/오프 카운터
int time_on_count = 100; // 켜져 있을 시간(초)
int heart_beat = 0; // 0↔1 토글로 하트비트 표시
UltraSonicDistanceSensor distanceSensor(11, 12); // 디지털 핀 13, 12 사용하는 센서 초기화
int distance;
int relays[] = { relay1, relay2, relay3 };
int is_live[] = { 0, 0, 0}; // 1이면 활성, 0이면 비활성. 3개만 있음
int debug = 5; // 시리얼 프린터로 보낼 디버그 메시지 레벨
void all_change_to(int which) {
// 모든 릴레이를 HIGH 또는 LOW로 변경
digitalWrite(relay1, which);
digitalWrite(relay2, which);
digitalWrite(relay3, which);
}
void toggle_relays(int whichOne) {
lcd.setCursor(0, 0); // 현재 켜진 릴레이 표시
lcd.print("Relay on:");
lcd.print(current_relay + 1); // current_relay는 0 기반
all_change_to(HIGH); // 먼저 모두 끄기
digitalWrite(relays[current_relay], LOW); // 켜야 할 릴레이 켜기
acnt++; // 작동 확인용 카운터
lcd.print(" ");
lcd.print(acnt);
}
void checkRelays() {
// current_relay를 다음 릴레이로 이동
// 최대에 도달하면 0으로
// 여기서 toggle_relays 호출하여 빈/방전된 배터리 충전하지 않게 함
charge_time_counter++;
if (charge_time_counter >= charge_time_max) {
current_relay++;
if (current_relay >= max_relays) {
current_relay = 0;
}
toggle_relays(current_relay);
charge_time_counter = 0;
}
}
void read_distance() {
// HC-SR04에서 거리 읽기
// 80cm 내에 무언가 있으면 디스플레이 켜고 디스플레이 온 카운터 리셋
// 카운터 도달 시 80cm 내에 아무것도 없음 -> 디스플레이 끄기
distance = distanceSensor.measureDistanceCm();
// 실제 측정된 거리 보려면 주석 해제
// Serial.print("Distance in CM: ");
// Serial.println(distance);
if (distance < 80) {
// 제 시스템에서는 200 미만 아무거나 가능. 위 주석 해제하고 본인 것 확인
lcd.backlight(); // 디스플레이 켜기
time_on_counter = 0;
} else {
// 꺼질 수도 있음?
time_on_counter++;
if (time_on_counter > time_on_count) {
time_on_counter = 0;
lcd.noBacklight();
}
}
}
void read_show_volts() {
// 각 배터리 입력 전압 읽기
// min_volts 미만이면 충전할 가치 없는 것, 해당 연결 건너뛰기
// 이 함수가 반복 호출되므로 전선이 빠지거나 하면 충전 루프에서 제외됨
volts1 = analogRead(0);
volts1 = (volts1 * 0.016);
lcd.setCursor(0, 1);
lcd.print(" "); // 줄 지우기
lcd.setCursor(11, 0);
lcd.print(volts1);
volts2 = analogRead(1);
volts2 = (volts2 * 0.0164);
lcd.setCursor(4, 1);
lcd.print(volts2);
volts3 = analogRead(2);
volts3 = (volts3 * 0.0166);
lcd.setCursor(11, 1);
lcd.print(volts3);
// 이제 전압 테스트. 10 미만이면 방전/불량 또는 배터리 없음으로 간주 -> 로테이션에서 제외
// 먼저 모든 릴레이 배열 초기화
int temp_cnt = 0;
// 모든 배열을 0으로 - 즉 끄기
relays[0] = 0;
relays[1] = 0;
relays[2] = 0;
if (volts1 > min_volts) {
relays[temp_cnt] = relay1; // 릴레이 1 양호
temp_cnt++;
}
if (volts2 > min_volts) {
relays[temp_cnt] = relay2; // 릴레이 2 양호
temp_cnt++;
}
if (volts3 > min_volts) {
relays[temp_cnt] = relay3; // 릴레이 3 양호
temp_cnt++;
}
max_relays = temp_cnt;
// LCD 하트비트 - 실행 중임을 보여줌
lcd.setCursor(0, 1);
if (heart_beat == 1) {
lcd.print("<>");
heart_beat = 0;
} else {
lcd.print("><");
heart_beat = 1;
}
lcd.print(charge_time_counter);
read_distance();
}
void setup() {
Serial.begin(19200);
Serial.println("시작");
lcd.begin(16, 2); // 16자 × 2줄
lcd.backlight();
pinMode(relay1, OUTPUT);
pinMode(relay2, OUTPUT);
pinMode(relay3, OUTPUT);
all_change_to(HIGH);
// 타이머 설정. 3개 타이머 - 각 배터리 충전 시간, 전압 업데이트 주기, 디스플레이 켜기 위한 거리 체크 주기
read_show_volts(); // 처음 한 번 실행하여 타이머 대기 안 함
checkRelays();
timer.every(chargeTime, checkRelays);
timer.every(volts_update, read_show_volts);
}
void loop() {
timer.tick(); // 타이머 틱
}