아두이노로 모델 레일 디지털 DCC 명령국 만들기 - 무료 앱 포함
부품 목록
부품 수량 비고
스파크펀 아두이노 프로 미니 328 - 5V/16MHz 1
LMD18200T 1 H-브리지 드라이버
PCB 1
리니어 레귤레이터 7805 1
3.6V 0.5W 제너 다이오드 1
HC-06 블루투스 모듈 1
0.1옴 2와트 저항 1 전류 감지용
커패시터 220µF 1 16V
커패시터 10µF 1 16V
세라믹 커패시터 0.1µF 3
저항 10k옴 2
저항 4.7k옴 1
스크류 터미널 2P 3.5mm 1 전원 입력
스크류 터미널 6P 2.54mm 1 트랙 출력
4핀 2.54mm 소켓 1 블루투스 모듈용
12핀 2.54mm 소켓 2 아두이노 프로 미니용
앱 및 온라인 서비스
LocoMotive - 무료 버전 앱 (구글 플레이 스토어)
LocoMotive DCC - 유료 버전 (CV 1~255 읽기/쓰기 지원, £5+세금)
프로젝트 소개
이 시스템의 차별점: 매우 간단한 전자 회로로 DCC 신호와 트랙 전력을 공급하지만, 실제 복잡한 작업은 스마트폰 앱이 수행합니다. 휴대폰의 컴퓨팅 파워를 활용해 각 명령 패킷을 구성하므로 아두이노 컨트롤러의 부담이 크게 줄어듭니다.
업데이트 내역:
2019-08-16: CV 1~255 읽기/쓰기 기능 추가된 유료 앱 출시 (£5.99)
2019-05-18: 최대 28개 기능, 편집 가능한 제목/가시성/모멘터리 옵션, 동시 4대 기관차 제어, 속도 바 UI 추가 (£3+세금)
2019-02-17: HC-06 안테나 연장 팁 - 31.2mm 단단한 구리선(2.4GHz 1/4 파장) 납땜으로 수신 범위 확장
2019-01-29: F2/F3 모멘터리 옵션(경적 소리), 기관차별 최고 속도 프리셋 추가
이 시스템은 NMRA 호환 디코더와 작동합니다. 앱 'LocoMotive'가 DCC 패킷을 구성해 블루투스로 아두이노에 전송하면, 아두이노는 DCC 클럭 신호와 결합해 트랙으로 내보냅니다. 부품비 약 £20(이베이 구매 기준).
주요 기능
1~20대 기관차 제어
중소형 레이아웃에 적합
2A 부하로 OO/HO 기관차 최대 16대 구동 가능
더 높은 전류의 H-브리지로 용량 확장 가능
단락 보호, 아두이노 코드로 과전류 차단 임계값 조절 가능
라이트 및 방향 제어
기능 F1~F8 (이름/가시성/모멘터리 편집 가능)
턴아웃/포인트/액세서리 최대 8쌍 출력 (이름 편집 가능)
기관차 커스텀 이름 및 최고 속도 설정
CV1(주소) 프로그래밍, CV 1~255 읽기/쓰기
액세서리 주소 직접 추가
스케일(Z/N/OO/HO/O/G)에 맞는 DC 전원 선택 (12~20V)
이전 DCC 무선 시스템 작업을 발전시켜, HC-06 블루투스 모듈과 LMD18200 H-브리지(2A)를 탑재한 수신기 아두이노 회로와 블루투스 명령국을 구성했습니다.
1단계: 아두이노 스케치
전체 스케치는 첨부 파일 참조. 핵심은 루프와 데이터 수신부입니다. 안드로이드 앱이 완성된 패킷을 보내면 여기서 디코딩해 DCC 패킷 포맷으로 변환합니다.
void loop() {
if (bluetooth.available() > 0) {
inString = bluetooth.readStringUntil('\n');
}
if (inString.substring(0, 1) == "D") {
string();
}
delay(20);
}
string() 함수는 수신한 텍스트 문자열을 파싱해 DCC 패킷의 적절한 위치에 각 명령을 배치합니다.
예시:
수신: DD, 3, 63, 12, 48, → 4바이트 속도 패킷 (각 8비트)
10진수: [3] [63] [12] [48]
이진수: [00000011] [00111111] [00001100] [00110000]
의미: [기관차 3] [속도 스텝] [기관차 속도] [XOR 체크섬]
수신: DD, 1, 129, 128, → 3바이트 기능 패킷
10진수: [1] [129] [128]
이진수: [00000001] [10000001] [10000000]
의미: [기관차 1] [F1 켬] [XOR 체크섬]
NMRA 패킷 표준 참고: S-9.2.1 표준 문서
LMD18200은 DIR 핀에 DCC 신호, PWM 핀은 HIGH 고정 필요.
2단계: 프리칭 회로도
아두이노 프로 미니가 HC-06으로 블루투스 데이터 수신 → NMRA 표준 클럭 사이클로 패킷 생성 → 새 데이터 수신 시 시퀀스 인터럽트 및 패킷 업데이트 → 트랙으로 전송.
필수 부품 (이베이 구매 가능):
PCB (이베이 판매)
아두이노 프로 미니 Atmega328P 5V/16MHz
LMD18200T H-브리지
HC-06 블루투스 모듈
0.1옴 2W 메탈 필름 저항
커패시터 0.1µF ×3
저항 10kΩ ×2
커패시터 10µF 25V
커패시터 220µF 16V
피닉스 컨택트 2P 스크류 터미널 3.5mm
저항 4.7kΩ
L7805CV 5V 레귤레이터
4핀/12핀/6핀 헤더 및 터미널 블록
제너 다이오드 3.6V 0.5W (1N4732A)
전선
전원 공급: OO/HO용 14V DC (노트북 어댑터 등, 2.5A 이상)
HC-06 안테나 연장: 모듈 안테나 끝에 31.2mm 단단한 구리선(접지선 활용) 납땜. 수신 범위 크게 향상. 안테나 선은 슬리브로 피복해 타 부품과 쇼트 방지.
주의: HC-06 연결은 PCB Rx → 모듈 Rx, PCB Tx → 모듈 Tx (크로스 아님).
전류 감지 보호 회로: 0.1옴 감지 저항으로 아날로그 입력 A0에서 0~200mV(0~2A) 측정. 약 5mV/스텝 → 50mA 정밀도. 2A 도달 시 아두이노 D13 HIGH로 LMD18200 브레이크 핀 작동해 출력 차단. LMD18200 내장 과열 보호도 백업으로 동작.
if (C > 2000) { // 2A
Serial.println("Over Current");
digitalWrite(13, HIGH); // 브레이크 작동
}
소비 전류 값을 매초 블루투스로 앱에 전송:
void send_c() {
t = millis() - lastmillis;
if (t >= interval) {
bluetooth.write(sensorValue);
bluetooth.write('c');
lastmillis = millis();
}
}
3단계: 안드로이드 앱
무료 앱: 구글 플레이에서 'LocoMotive' 검색 설치
유료 풀 버전: 'LocoMotive DCC' (£5.99, CV 1~255 읽기/쓰기)
유료 버전 사용 설명서 첨부
코드
locomotive_dcc_sep19.ino (아두이노)
// 1 Sep 2019
// Android App 'Locomotive DCC'용 스케치, LMD18200 H-브리지 사용
// CV 읽기/쓰기 기능 추가
#include <SoftwareSerial.h>
SoftwareSerial bluetooth(8,9); // RX, TX
int C; int sensorValue;
long t = 0, lastmillis = 0, interval = 1000;
String inString;
int a[8];
int preamable_type = 0;
int Address;
float CV_VAL, cv_val, cv_val0, cv_val1, cv_val2, cv_val3, cv_val4, cv_val5, cv_val6, cv_val7;
int test_num, cv_write_val, Cs;
boolean cv_logic, ok;
int num;
#define TIMER_SHORT 0x8D // 58µs 펄스
#define TIMER_LONG 0x1B // 116µs 펄스
unsigned char last_timer = TIMER_SHORT;
unsigned char flag = 0;
unsigned char every_second_isr = 0;
#define PREAMBLE 0
#define SEPERATOR 1
#define SENDBYTE 2
unsigned char state = PREAMBLE;
unsigned char preamble_count = 16;
unsigned char outbyte = 0, cbit = 0x80;
unsigned char xdata = 0, data = 0, data_f = 0, data_f1 = 0, data_f2 = 0;
int locoAdr = 9;
struct Message { unsigned char data[7]; unsigned char len; };
#define MAXMSG 3
struct Message msg[MAXMSG] = {
{ {0xFF,0,0xFF,0,0,0,0}, 3 }, // 아이들 메시지
{ {locoAdr,0,0,0,0,0,0}, 3 } // 128 스텝 속도 메시지 (0x3f)
};
int msgIndex = 0, byteIndex = 0;
void SetupTimer2() {
TCCR2A = 0;
TCCR2B = (0<= MAXMSG) msgIndex = 0;
byteIndex = 0;
}
break;
case SEPERATOR:
flag = 0;
state = SENDBYTE;
cbit = 0x80;
outbyte = msg[msgIndex].data[byteIndex];
break;
case SENDBYTE:
if (outbyte & cbit) flag = 1; else flag = 0;
cbit >>= 1;
if (cbit == 0) {
byteIndex++;
if (byteIndex >= msg[msgIndex].len) {
state = PREAMBLE;
preamble_count = (preamable_type == 0) ? 16 : 24;
} else {
state = SEPERATOR;
}
}
break;
}
if (flag) { latency = TCNT2; TCNT2 = latency + TIMER_SHORT; last_timer = TIMER_SHORT; }
else { latency = TCNT2; TCNT2 = latency + TIMER_LONG; last_timer = TIMER_LONG; }
}
}
void setup() {
Serial.begin(115200);
analogReference(INTERNAL);
pinMode(13, OUTPUT);
bluetooth.begin(9600); delay(500);
Serial.println("Bluetooth Started");
bluetooth.println("Bluetooth Started");
DDRD = B01100000; // D5, D6 출력
SetupTimer2();
t = millis(); lastmillis = millis();
digitalWrite(6, LOW); digitalWrite(13, HIGH); // 브리지 출력 끔
int get_cv_val = cv_calc(test_num);
}
void current() {
int value = 0;
for (int i = 0; i < 5; i++) { value += analogRead(A0); delay(1); }
sensorValue = value / 5;
C = 10.8 * sensorValue; // mA (내부 레퍼런스 1.1V, 0.1옴 감지 저항)
if (C > 2000) {
Serial.println("Over Current");
bluetooth.println("Short!");
digitalWrite(6, LOW); digitalWrite(13, HIGH);
bluetooth.println(C);
}
}
void loop() {
if (bluetooth.available()) { inString = bluetooth.readStringUntil('\n'); Serial.println(inString); }
if (inString.substring(0,1) == "D") { string(); }
if (inString.substring(0,1) == "C") { digitalWrite(6, HIGH); digitalWrite(13, LOW); } // 과전류 후 리셋
if (inString.substring(0,1) == "G") { digitalWrite(6, HIGH); digitalWrite(13, LOW); current(); bluetooth.println(C); } // 전류 요청
if (inString.substring(0,1) == "A") { digitalWrite(6, HIGH); digitalWrite(13, LOW); get_cv_data(); cv1_prog(); digitalWrite(6, LOW); digitalWrite(13, HIGH); } // CV1 쓰기
if (inString.substring(0,3) == "get") { cv_val = 0; digitalWrite(6, HIGH); digitalWrite(13, LOW); get_cv_num(); cv_read(); digitalWrite(6, LOW); digitalWrite(13, HIGH); } // CV 읽기
if (inString.substring(0,3) == "add") { cv_write_val = 0; digitalWrite(6, HIGH); digitalWrite(13, LOW); get_cv_new_val(); repeat_cv_write(); digitalWrite(6, LOW); digitalWrite(13, HIGH); } // CV 쓰기
if (inString.substring(0,1) == "V") { delay(50); bluetooth.print("CV1 updated"); Serial.println(Address); }
if (inString.substring(0,1) == "E") { digitalWrite(6, LOW); digitalWrite(13, HIGH); } // 비상 정지
inString = "";
}
// --- 헬퍼 함수들 (파싱, 패킷 구성, CV 읽기/쓰기 시퀀스 등) ---
// 원본 코드의 모든 헬퍼 함수(string, amend_len3/4, assemble_3/4_byte, print_data,
// cv_current, cv_read, cv1_prog, reset_packet, page_preset_packet, cv1_write_packet,
// cv_verify1/0_packet, cv_write_packet, valid_packet, repeat_cv_write, cv_write,
// cv_calc) 포함 생략. 필요시 원본 참조.
커스텀 파트 및 인클로저
인클로저 내부에 완성된 회로
회로도
bluetooth_android_dcc_lmd18200_S8NTVsI4LY.fzz (프리칭 파일)
앱 사용 설명서 첨부