// ---------------------------------- Задаване функциите на изводите: ----------------------------------
const int dcc_pin = 2; // Вход за DCC сигнала; стойност 2 или 3;
// да не се използват други стойности!!!
// {серво 1} {серво 2} {серво 3} {серво 4}
const int servo_pin[] = { 3 , 5 , 6 , 9 }; // Изводи за управление на сервата.
const int LED_pin[][2] = {{10, 11}, {12, 13}, {14, 15}, {16, 17}}; // Изводи за светодиодите:
// първата позиция е за главно положение,
// втората позиция е за отклонено положение.
// -------------------------------- Константи за формиране на импулсите: -------------------------------
#define pwmFrame 20000 // Период на ШИМ изходите в микросекунди (µs).
#define pwmLo 900 // Продължителност на "късия" импулс.
#define pwmHi 2100 // Продължителност на "дългия" импулс.
#define pwmOffDel 2000 // Време преди изключване на ШИМ в милисекунди (ms).
// -------------------------------------- Свързване на светодиод: --------------------------------------
//
// D2 до D13, A0 до A5 ───┐
// (2 до 19) │
// ┌┴┐
// │ │ 220Ω ┌ според светодиода и
// │ │ до ┤ желаната интензивност
// │ │ 470Ω └ на светене
// └┬┘
// │
// │
// ─┴─
// \ / ─→
// V ─→
// ─┬─
// │
// │
// ─┴─
//
// Изводите в програмата са означени от 2 до 19, където:
// извод 2 отговаря на D2;
// извод 3 отговаря на D3;
// и т.н. до
// извод 13 отговаря на D13;
// извод 14 отговаря на A0;
// извод 15 отговаря на A1;
// и т.н. до
// извод 19 отговаря на A5;
//
// Всички изводи от 2 до 19 могат да се използват като цифрови входове и изходи, а от 14 до 19 (A0 до
// A5) и като аналогови входове. A6 и A7 могат да се използват САМО като аналогови входове.
// -------------------------- Команди за управление през серийния интерфейс: ---------------------------
//
// 1. Настройки на серийния монитор:
//
// 1.1. В полето за задаване на скоростта на интерфейса (долу в дясно) се избира "115200 baud" или
// стойността, зададена на константата baudRate по-долу, ако е различна.
//
// 1.2. В ляво от полето за задаване на скоростта на интерфейса се избира "Both NL & CR".
//
// 2. Командите се въвеждат в полето горе, отляво на бутона "Send". Всички команди се изпращат при
// натискането на "Enter" на клавиатурата или на бутона "Send" горе в дясно на екрана на серийния
// монитор. Декодерът отговаря по серийния интерфейс със съответния статус. Командите са:
//
// 2.1. Празна команда или команда "0": декодерът отговаря с текущите настройки и състояния на
// изходите.
//
// 2.2. Команда от "1" до "4": декодерът подава команда за промяна на състоянието на изхода на
// съответното серво, т.е. превключва сервото в другото състояние. Декодерът отговаря с
// информация за новото състояние на съответния изход.
//
// 2.3. Команда "X Y", с ЕДНА и САМО ЕДНА празна позиция между стойностите на "X" и "Y": указване на
// новата крайна позиция "Y" на серво на изход "Х" за текущото му състояние; вместо "X" се
// въвежда номер на желания изход на серво от "1" до "4", а вместо "Y" се въвежда желаната
// стойност за новото крайно положение от "0" до "100", например "3 27" означава серво 3,
// положение 27. При въведени валидни стойности, сервото веднага се завърта до новата позиция и
// декодерът отговаря на екрана на серийния монитор с потвърждение за новото крайно положение.
// Ако въведените стойности за "X" и "Y" са извън посочените граници, декодерът връща съобщение
// за неправилни данни ("INVALID!") на екрана на серийния монитор.
// Тази команда задава новото положение за текущото състояние на сервото. Командата може да се
// въвежда многократно с различни стойности на "Y" за прецизна настройка на крайното положение
// на сервото. За да се въведе ново положение за другото състояние на сервото, то трябва да
// бъде превключено или с DCC команда или с команда през серийния интерфейс, както това е
// описано в 2.2 по-горе.
//
// 2.4. Команда "XsY", с малка латинска буква "s" между стойностите на "X" и "Y": указване на новата
// скорост "Y" на серво на изход "X"; вместо "X" се въвежда номер на желания изход на серво от
// "1" до "4", а вместо "Y" се въвежда желаната стойност на времето за превключване на сервото
// от едната крайна позиция в другата в милисекунди (ms), в диапазона от 100 (0,1 секунди) до
// 5000 (5 секунди). Например "2s1100" означава серво 2, скорост 1100 (1,1 секунди време за
// превключване). При въведени валидни стойности, декодерът отговаря на екрана на серийния
// монитор с потвърждение за новата скорост.
// Ако въведените стойности за "X" и "Y" са извън посочените граници, декодерът връща съобщение
// за неправилни данни ("INVALID!") на екрана на серийния монитор.
//
// 2.5. Команда "aX", с малка латинска буква "a" последвана от стойност "X": задаване на нов адрес
// на декодера; вместо "X" се въвежда номер на желания адрес на декодера от "1" до "2044".
// Въведеният адрес задава група от 4 последователни адреса на декодера, както е регламентирано
// от DCC стандарта При въведена валидна стойност на адреса, декодерът отговаря на екрана на
// серийния интерфейс с потвърждение за новия базов адрес. Базовият адрес на декодера е първият
// от групата от 4 адреса - 1, 5, 9, 13 и т.н.
// Ако въведената стойност за "X" е извън посочените граници, декодерът връща съобщение за
// неправилни данни ("INVALID!") на екрана на серийния монитор.
//
// 2.6. Команда "d11", с малка латинска буква "d" последвана от числото 11: връщане на заводските
// настройки на декодера.
//
// 2.7. Ако се въведе команда, която не отговаря на командите описани по-горе, декодерът връща
// съобщение за неправилни данни ("INVALID!") на екрана на серийния монитор.
//
// -----------------------------------------------------------------------------------------------------
// CCCC VV VV
// CC CC VV VV ТТТТТТ АААА
// CC VV VV ----- ТТ АА
// CC VV VV ----- ТТ ААААА
// CC CC VVV ТТ АА АА
// CCCC V ТТ АААА А
// Работят САМО с библиотека NmraDCC!
// ВНИМАНИЕ!!! Програмиране на работния/главния участък НЕ Е ВЪЗМОЖНО. Програмирай само на служебния/програмиращ участък!
// ----------------------------- CV-та за идентификатори и връщане на заводските настройки -----------------------------
// cvMfr 8 // Идентификатор на производителя - само за четене! [13]
// cvVer 7 // Вид и версия на декодера - само за четене! [71]
// cvSub 10 // Версия на програмата на декодера - само за четене! [1]
// cvDef 11 // Връщане на заводските настройки при запис на стойност 11 в това CV [0]
// --------------------------------------- address and decoder configuration CVs ---------------------------------------
// cvAddressLo 1 // Адрес на декодера - младша част [101]
// cvAddressHi 9 // Адрес на декодера - старша част [0]
// --------------------------------------- CV-та за настройка на ШИМ на сервата ----------------------------------------
// cvPWMframe 21 // Период на ШИМ в 0.2 ms; стойности от 75 до 125 [100]
// cvPWMlo 22 // Продължителност на импулса за 0% отклонение на сервото в 0.01 ms; 48 до cvPWMhi-1 [90]
// cvPWMhi 23 // Продължителност на импулса за 100% отклонение в 0.01 ms; cvPWMlo+1 до 248 [210]
// cvPWMoffDelay 24 // Време преди изключване на ШИМ в 0.1 s; стойности от 0 до 255 [20]
// ------------------------------------------ CV-та за управление на сервата -------------------------------------------
// cvServoAngle 31 // Ъгли на завъртане на сервата в диапазона от 0 до 100; използват се 8 CV-та:
// CV31: Серво 1 в ПРАВА (MAIN) [40]
// CV32: Серво 1 в ОТКЛОНЕНИЕ (SIDING) [60]
// - - - - - - - - - - - - - - - ---
// CV37: Серво 4 в ПРАВА (MAIN) [40]
// CV38: Серво 4 в ОТКЛОНЕНИЕ (SIDING) [60]
// cvServoSpeed 51 // Скорост на сервата в 0.1 s; стойности от 1 до 50; използват се 4 CV-та:
// CV51: Скорост на серво 1 [18]
// - - - - - - - - - - ---
// CV54: Скорост на серво 4 [18]
// cvServoImmediate 61 // Превключване на серво при запис на стойност, различна от 0; използват се 4 CV-та:
// CV61: Превключване на серво 1 [0]
// - - - - - - - - - - - - - - - - ---
// CV64: Превключване на серво 4 [0]
// ------------------------------------- CV-та за запомняне състоянията на сервата -------------------------------------
// cvMem 204 // Памет за състоянията на сервата; използват се 4 CV-та; само за програмата!
// ---------------------------------------------------- Други CV-та ----------------------------------------------------
// cvDump 256 // Стойностите на всички CV-та се изписват на екрана на серийния монитор при запис на стойност, различна от 0
// CCCC VV VV
// CC CC VV VV SSS
// CC VV VV SS
// CC VV VV SSS
// CC CC VVV SS
// CCCC V SSS
// Available ONLY with the NmraDcc library!
// CAUTION!!! Programming on the main DOES NOT WORK. Program ONLY on the service (programming) track!
// -------------------------------------------- ID and reset to defaults CVs -------------------------------------------
#define cvMfr 8 // manufacturer ID [13]
#define cvVer 7 // decoder type and code [71]
#define cvSub 10 // decoder software version [1]
#define cvDef 11 // reset to defaults: write 11 to reset all CVs to defaults [0]
// --------------------------------------- address and decoder configuration CVs ---------------------------------------
#define cvAddressLo 1 // decoder address low byte [1]
#define cvAddressHi 9 // decoder address high byte [0]
// --------------------------------------------------- servo PWM CVs ---------------------------------------------------
#define cvPWMframe 21 // PWM frame in 0.2ms; *200 to get pwmFrame [100]
#define cvPWMlo 22 // pulse length for 0% servo rotation in 0.01ms; *10 to get pwmLo [90]
#define cvPWMhi 23 // pulse length for 100% servo rotation in 0.01ms; *10 to get pwmHi [210]
#define cvPWMoffDelay 24 // delay before turning off PWM in 0.1s; *100 to get pwmOffDelay [20]
// ------------------------------------------------- servo control CVs -------------------------------------------------
#define cvServoAngle 31 // servo angles between 0 and 100; uses 8 bytes:
// CV31: servo 1 MAIN angle [40]
// CV32: servo 1 SIDING angle [60]
// - - - - - - - - - - - - - - - ---
// CV37: servo 4 MAIN angle [40]
// CV38: servo 4 SIDING angle [60]
#define cvServoSpeed 51 // servo speed in 0.1s; uses 4 bytes:
// CV51: servo 1 speed [18]
// - - - - - - - - - - ---
// CV54: servo 4 speed [18]
#define cvServoImmediate 61 // servo immediate control; uses 4 bytes:
// CV61: servo 1 immediate control [0]
// - - - - - - - - - - - - - - - - ---
// CV64: servo 4 immediate control [0]
// ---------------------------------------------- servo output memory CVs ----------------------------------------------
#define cvMem 204 // servo output state storage; uses 4 bytes
// ----------------------------------------------------- other CVs -----------------------------------------------------
#define cvDump 256 // write any non-zero value to this CV to dump CV values to serial
// ------------------------------- Указатели за дебъгване на програмата: -------------------------------
//#define debug_acc_turnout // Генерира код за дебъгване ако се махнат двете наклонени черти в началото на реда;
//#define debug_cvchange // нужно е само на автора на програмата.
//#define debug_i_read
//#define debug_i_save
//#define debug_setup
//#define debug_servo_pwm
#define debug_defaults
#define dump_CVs_enabled // uncomment to enable CV change readback and CV dump to serial
#if defined(debug_acc_turnout) or defined(debug_cvchange) or defined(debug_i_read) or defined(debug_i_save) or defined(debug_setup) or defined(debug_servo_pwm) or defined(debug_defaults) or defined(dump_CVs_enabled)
#define debug_main
#endif
// ------------------------------ Указател за използвана DCC библиотека: -------------------------------
#define use_NmraDcc // Comment to use DCC_Decoder.
#ifdef use_NmraDcc // Необходими библиотеки:
#include <NmraDcc.h> // Библиотека за DCC декодер. Поддържа CV-та.
NmraDcc Dcc; // Обявяване на променлива за работа с библиотеката за DCC декодер.
#else
#include <EEPROM.h> // Библиотека за работа с EEPROM.
#include <DCC_Decoder.h> // Библиотека за DCC декодер. НЕ ПОДДЪРЖА CV-та!
#undef debug_cvchange
#undef dump_CVs_enabled
#endif
// ----------------------------- Идентификатори на декодера и програмата: ------------------------------
#define defaultAddress 1 // Заводски адрес на декодера.
#define mfr_id 13 // Идентификатор на декодера.
#define mfr_ver 71 // Идентификатор на програмата.
#define mfr_sub 1 // Версия на програмата.
// ---------------- Константи за нормалната работа на програмата - да не се променят!!! ----------------
#define baudRate 115200 // Скорост на серийния интерфейс.
#define serialTimeout 14e6 / baudRate // Таймаут на серийния интерфейс в микросекунди (µs); Да не се променя!!!.
#define aHiMask 0x07 // Максимална стойност (маска) на старшата част на адреса.
#define pwmFrameMin 15000 // Минимален период на ШИМ изходите в микросекунди (µs).
#define pwmFrameMax 25000 // Максимален период на ШИМ изходите в микросекунди (µs).
#define pwmFrameDiv 200 // Делител на периода на ШИМ.
#define pwmMin 480 // Минимална продължителност на ШИМ импулс в микросекунди (µs).
#define pwmMax 2480 // Минимална продължителност на ШИМ импулс в микросекунди (µs).
#define pwmDiv 10 // Делител на продължителността на ШИМ импулс.
#define pwmOffDelDiv 100 // Делител на времетраенето преди изключване на ШИМ.
#define angleMin 0 // Минимален ъгъл на отклонение на серво.
#define angleMax 100 // Максимален ъгъл на отклонение на серво.
#define angleLoDef 40 // Заводски ъгъл на отклонение на серво в позиция 1.
#define angleHiDef 60 // Заводски ъгъл на отклонение на серво в позиция 2.
#define speedMin 100 // Минимална скорост на серво в ms.
#define speedMax 5000 // Максимална скорост на серво в ms.
#define speedDiv 100 // Делител на скоростта на серво.
#define speedDef 1800 // Заводска скорост на серво от едната крайна позиция до
// другата в ms; стойности от 100 до 5000 (0,1 - 4,8 s)
#define cCR 13 // Символ CR.
#define cLF 10 // Символ LF.
#define cDelimiter ' ' // Разделител за въвеждане на данни.
const String cStr[] = {" MAIN", " SIDING"};
const int channels = sizeof(servo_pin) / sizeof(int);
const int LED_flasher = 10; // LED flash length: 10 for moderate flashing, 1000 for no flashing.
#define calcAddressLo(x) (x & 0xFF)
#define calcAddressHi(x) ((x >> 8) & aHiMask)
struct cvPair {int cv; byte value;};
cvPair factoryDefaults[] = { // default CV values table
{cvAddressLo, calcAddressLo(defaultAddress)}, {cvAddressHi, calcAddressHi(defaultAddress)},
{cvSub, mfr_sub}, {cvDef, 0},
{cvPWMframe, pwmFrame / pwmFrameDiv},
{cvPWMlo, pwmLo / pwmDiv}, {cvPWMhi, pwmHi / pwmDiv},
{cvPWMoffDelay, pwmOffDel / pwmOffDelDiv},
{cvServoAngle + 0, angleLoDef}, {cvServoAngle + 1, angleHiDef},
{cvServoAngle + 2, angleLoDef}, {cvServoAngle + 3, angleHiDef},
{cvServoAngle + 4, angleLoDef}, {cvServoAngle + 5, angleHiDef},
{cvServoAngle + 6, angleLoDef}, {cvServoAngle + 7, angleHiDef},
{cvServoSpeed + 0, speedDef / speedDiv}, {cvServoSpeed + 1, speedDef / speedDiv},
{cvServoSpeed + 2, speedDef / speedDiv}, {cvServoSpeed + 3, speedDef / speedDiv},
{cvServoImmediate + 0, 0}, {cvServoImmediate + 1, 0}, {cvServoImmediate + 2, 0}, {cvServoImmediate + 3, 0},
{cvMem + 0, 0}, {cvMem + 1, 0}, {cvMem + 2, 0}, {cvMem + 3, 0},
{cvDump, 0}
};
// ------------------------------------- Променливи на програмата: -------------------------------------
unsigned int decoder_address; // Decoder address.
long pwm_frame = pwmFrame; // PWM frame rate in µs.
long pwm_lo = pwmLo; // 0% length in µs.
long pwm_hi = pwmHi; // 100% pulse length in µs.
long pwm_off_delay = pwmOffDel; // Delay in ms before PWM turn-off.
int delay_counter = pwmOffDel / (pwmFrame / 1000); // Counter before turning PWM off.
int servo_angle[channels][2]; // Servo "angles" between 0 and 100; First value LOWER than second!
int servo_speed[channels]; // Servo speed between end states in ms between 100 and 5000 ms
long servo_pwm[channels][2]; // Servo PWMs.
long servo_step[channels]; // PWM step.
int pwm_out[channels]; // Pulse length to be generated for each channel
byte channel_state[channels]; // Current channel state - 0 = main, 1 = siding.
bool pwm_off[channels] = {0, 0, 0, 0}; // Flag to turn off PWM.
bool cycle_end[channels] = {0, 0, 0, 0}; // Flag for end of PWM change.
int cycle_end_counter[channels] = {delay_counter, delay_counter, delay_counter, delay_counter}; // Counter before turning PWM off.
bool LED_state[channels][2] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; // LED current state - false = off, true = on.
int LED_counter[channels] = {LED_flasher, LED_flasher, LED_flasher, LED_flasher}; // LED flasher counter.
int current_channel = 0; // Current channel pointer; set to first channel.
bool get_values = 0; // Flag to tell main loop to run its routine.
word pwm_pause = pwm_frame; // Length of pause after all PWM pulses have been processed.
bool first_time_no_update = 1; // Flag to not update EEPROM on startup.
byte defsCVCnt = 0;
#ifdef use_NmraDcc
int getCVInd = 0;
int setCVInd = 0;
byte setCVVal = 0;
unsigned long cvChangeMillis = 0;
#define cUpdate "Update "
#define cMem "CV"
byte iRead(word a) {return(Dcc.getCV(a));}
void iSave(word a, byte d) {Dcc.setCV(a, d);}
bool iReady() {return(Dcc.isSetCVReady());}
byte badcvPWMframe() {return((iRead(cvPWMframe) * pwmFrameDiv < pwmFrameMin) || (iRead(cvPWMframe) * pwmFrameDiv > pwmFrameMax));}
byte badcvPWM() {return((iRead(cvPWMlo) * pwmDiv < pwmMin) || (iRead(cvPWMlo) >= iRead(cvPWMhi)) || (iRead(cvPWMhi) * pwmDiv > pwmMax));}
#else
#define cUpdate "EEPROM update "
#define cMem "EEPROM "
byte iRead(word a) {return(EEPROM.read(a));}
void iSave(word a, byte d) {EEPROM.update(a, d);}
bool iReady() {return(1);}
#endif
long calcPWM(int angle) {return (pwm_lo + angle * (pwm_hi - pwm_lo) / 100);}
long calcStep(int c) {return ((servo_pwm[c][1] - servo_pwm[c][0]) * (pwm_frame / 500) / (servo_speed[c] * 2));}
byte checkValue(int val, int vMin, int vMax) {return ((val >= vMin) && (val <= vMax));}
void getDecoderAddress() {decoder_address = ((iRead(cvAddressHi) & aHiMask) << 8) + ((iRead(cvAddressLo) - 1) & 0xfc) + 1;}
byte badcvAddressHi() {return(iRead(cvAddressHi) & (0xff - aHiMask));}
byte badcvServoAngle(int cv) {return((iRead(cv) < angleMin) || (iRead(cv) > angleMax));}
byte badcvServoSpeed(int cv) {return((iRead(cv) * speedDiv < speedMin) || (iRead(cv) * speedDiv > speedMax));}
int ISR_delay = 3; // Timer ISR delay.
byte timer1b = bit(WGM12) | bit(CS11); // Timer1 CTC, scale to clock / 8.
bool pwm_status = 0; // PWM signal flag; start with a pause.
word next_timer_value; // Value timer will count next.
ISR(TIMER1_COMPA_vect) { // Timer1 ISR.
TCCR1B = 0; // Stop timer.
TCNT1 = 0; // Reset timer.
OCR1A = next_timer_value; // Load compare A register value.
TCCR1B = timer1b; // Start timer.
if (pwm_status) { // Generating pulse?
if (current_channel) digitalWrite(servo_pin[current_channel - 1], LOW); // Turn off previous channel unless it was a pause.
if (!pwm_off[current_channel]) digitalWrite(servo_pin[current_channel], HIGH); // Turn on the new channel if needed.
pwm_pause -= pwm_out[current_channel]; // Subtract newly generated pulse width from pause.
if (++current_channel > channels - 1) { // Get ready for next channel and check if this was the last channel.
current_channel = 0; // Last channel - reset channel number.
pwm_status = 0; // Last channel pulse has just started - next time generate pause.
next_timer_value = (pwm_pause << 1) - ISR_delay; // Load pause length.
} else next_timer_value = (pwm_out[current_channel] << 1) - ISR_delay; // Not last channel - load new channel pulse width.
} else { // Done with PWM pulse.
digitalWrite(servo_pin[channels - 1], LOW); // Done with last channel pulse - pause is to follow.
current_channel = 0; // Set pointer to first channel.
next_timer_value = (pwm_out[current_channel] << 1) - ISR_delay; // Load first channel PWM.
get_values = 1; // Set to check input, run main cycle.
pwm_status = 1; // Next time generate pulse.
pwm_pause = pwm_frame; // Reload pause.
} // if (pwm_status)
sei(); // Enable interrupts.
} // ISR(TIMER1_COMPA_vect)
void setup() { // Program setup.
Serial.begin(baudRate); // Set up UART.
for (int i = 0; i < 100; i++) Serial.println(); // Clear serial terminal.
#ifdef use_NmraDcc
#ifdef digitalPinToInterrupt // Set up DCC pin.
Dcc.pin(dcc_pin, 0);
#else
Dcc.pin(0, dcc_pin, 1);
#endif
Dcc.init(mfr_id, mfr_ver, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0); // Initialize DCC library.
#else
pinMode(dcc_pin, INPUT_PULLUP); // Initialize DCC input.
DCC.SetBasicAccessoryDecoderPacketHandler(notifyDccAccTurnoutOutput, 1); // Attach accessory decoder handler
DCC.SetupDecoder(mfr_id, mfr_ver, digitalPinToInterrupt(dcc_pin)); // Configure decoder
#endif
getDecoderAddress(); // Get decoder address.
#ifdef debug_main // Send decoder info if debug mode is enabled.
debugMain();
#endif
initChannels(); // Initialize all channels.
#ifdef debug_setup // Send channel setup data if debug mode is enabled.
debugSetup();
#endif // End of debug transmission.
if ((badcvAddressHi()) || // Valid decoder address?
(iRead(cvMfr) != mfr_id) || // Matching manufacturer ID?
(iRead(cvVer) != mfr_ver) || // Matching decoder ID?
(iRead(cvSub) != mfr_sub)) // Matching decoder software ID?
notifyCVResetFactoryDefault(); // Make defsCVCnt non-zero and equal to number of CVs to be reset
// to flag loop() that function reset to factory defaults is needed.
next_timer_value = (pwm_out[current_channel] << 1) - ISR_delay; // Load first channel PWM.
TCCR1A = 0; // Set up timer1 for normal mode.
TCCR1B = 0; // Stop timer.
TIMSK1 = bit (OCIE1A); // Interrupt on Compare A Match
TCCR1B = timer1b; // Start timer1.
} // setup()
void loop() { // Main cycle.
#ifdef use_NmraDcc
Dcc.process(); // Loop through DCC library.
#else
DCC.loop(); // Loop through DCC library.
#endif
if (get_values) { // Need to get new values?
for (int c = 0; c < channels; c++) { // Cycle through channels.
bool cv_update_flag = 0; // So far no EEPROM update is needed.
if (!cycle_end[c] && !pwm_off[c]) { // Need to change channel PWM?
if (--LED_counter[c] == 0) { // Decrement LED flasher counter and check if expired?
LED_state[c][channel_state[c]] = !LED_state[c][channel_state[c]]; // LED flasher counter expired, toggle LED state.
LED_counter[c] = LED_flasher; // Reload LED flasher counter.
} // if (LED_counter[c] == 0)
bool rev = servo_pwm[c][0] > servo_pwm[c][1]; // Calculate channel servo reversing.
if (channel_state[c]) pwm_out[c] += servo_step[c]; // Update PWM.
else pwm_out[c] -= servo_step[c]; // Update PWM.
if (( (rev ^ channel_state[c]) && pwm_out[c] > servo_pwm[c][channel_state[c]]) ||
(!(rev ^ channel_state[c]) && pwm_out[c] < servo_pwm[c][channel_state[c]])) { // End point reached?
pwm_out[c] = servo_pwm[c][channel_state[c]]; // End point reached - transition complete, adjust to end point.
LED_state[c][channel_state[c]] = 1; // Set LED state.
LED_counter[c] = LED_flasher; // Reload LED flasher counter.
cycle_end_counter[c] = delay_counter; // Reload channel transition counter.
cycle_end[c] = 1; // Set end of transition flag.
cv_update_flag = 1; // CV update needed.
} // if (end_point_reached)
} // if (!cycle_end[c] && !pwm_off[c])
if (cycle_end[c]) { // Channel transition completed?
if (--cycle_end_counter[c] < 2) { // Decrement counter and check if expired.
cycle_end[c] = 0; // Counter expired - end of cycle.
pwm_off[c] = 1; // Turn off channel.
} // if (--cycle_end_counter[c] < 2)
} // if (cycle_end[c])
digitalWrite(LED_pin[c][0], LED_state[c][0]); // Update main LED output.
digitalWrite(LED_pin[c][1], LED_state[c][1]); // Update siding LED output.
if (cv_update_flag && !first_time_no_update) { // EEPROM update needed?
#ifdef use_NmraDcc
setCVInd = c + cvMem;
setCVVal = channel_state[c];
cvChangeMillis = millis(); // Get current millis for CV change blocking
#else
iSave(c, channel_state[c]); // Save new state.
#endif
#ifdef debug_i_save // Send channel state if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("servo "); // │
Serial.print(c + 1); // ├ Debug transmission...
Serial.print(" state to"); // │
Serial.print(cStr[channel_state[c]]); // │
Serial.println("."); //-┘
#endif // End of debug transmission.
cv_update_flag = 0; // Reset flag to prevent unwanted EEPROM updates.
} // if (cv_update_flag && !first_time_no_update)
} // for (int c)
get_values = 0; // Done reading and calculating data.
first_time_no_update = 0; // Further channel state changes will be saved.
} // if (get_values)
if (defsCVCnt && iReady()) { // Check if factory reset of CVs was requested.
defsCVCnt--; // Decrement from initial size of array.
iSave(factoryDefaults[defsCVCnt].cv, factoryDefaults[defsCVCnt].value);
#ifdef debug_defaults
Serial.print("Load Factory Defaults: ");
Serial.print(cMem);
Serial.print(factoryDefaults[defsCVCnt].cv);
Serial.print(": ");
Serial.println(factoryDefaults[defsCVCnt].value);
#endif
if (!defsCVCnt) { // Done with factory defaults:
getDecoderAddress(); // get decoder address,
initChannels(); // reload everything.
}
} // if (defsCVCnt)
#ifdef use_NmraDcc
if (setCVInd && iReady() && (millis() - cvChangeMillis > 63)) { // CV needs to reset immediately
iSave(setCVInd, setCVVal); // after its value has been set.
if (setCVInd == cvDump) dumpCVs(); // Dump CVs if dump CV changed.
getCVInd = setCVInd; // Flag the loop() to send a CV readback to serial
setCVInd = 0; // Clear request.
} // if setCVInd
#ifdef dump_CVs_enabled
if (getCVInd && !setCVInd) { // Check for CV readback to serial request.
Serial.print(cMem);
sendCV(getCVInd); // CV readback to serial requested, send CV.
Serial.println();
getCVInd = 0; // Clear request.
} // if getCVInd
#endif
#endif
if (Serial.available()) getSerial(); // Read serial if data was received.
} // loop()
#ifdef use_NmraDcc
void notifyDccAccTurnoutOutput(unsigned int address, byte data, byte dummy) { // Called when accessory data packet is received.
#else
void notifyDccAccTurnoutOutput(int rawAddress, boolean dummy, byte rawData) { // Called when accessory data packet is received.
long address = (rawAddress - 1) * 4 + ((rawData & 0x06) >> 1) + 1; // Calculate actual address.
byte data = rawData & 0x01; // Calculate actual data.
#endif
#ifdef debug_acc_turnout // Send DCC data if decug mode enabled.
Serial.print("notifyDccAccTurnoutOutput: "); //-┐
#ifndef use_NmraDcc // │
Serial.print("Raw: "); // │
Serial.print(rawAddress); // │
Serial.print(", "); // │
Serial.print(dummy); // │
Serial.print(", "); // │
Serial.print(rawData); // │
Serial.print("; "); // │
#endif // │
Serial.print("Address = "); // ├ Debug transmission...
Serial.print(address); // │
#ifdef use_NmraDcc // │
Serial.print(", Direction = "); // │
Serial.print(data); // │
Serial.print(", Output Power = "); // │
Serial.println(dummy); // │
#else // │
Serial.print(", Data = "); // │
Serial.println(data); // │
#endif //-┘
#endif // End of debug transmission.
data &= 0x01; // Fix data if needed.
for (int c = 0; c < channels; c++) { // Cycle through channels.
if ((decoder_address + c == address ) && (channel_state[c] != data)) { // Matching servo address and changing channel state?
channel_state[c] = data; // Toggle channel state.
pwm_off[c] = 0; // Turn on channel PWM.
cycle_end[c] = 0; // No end of PWM change.
LED_state[c][0] = 0; // Clear main LED state.
LED_state[c][1] = 0; // Clear siding LED state.
} // if (decoder_address)
} // for (int c)
} // notifyDccAccTurnoutOutput
void notifyCVResetFactoryDefault() {defsCVCnt = sizeof(factoryDefaults) / sizeof(cvPair);}
#ifdef use_NmraDcc
void notifyDccCVChange(unsigned int cv, byte value) { // Called when DCC packet changes CV value.
#ifdef debug_cvchange
Serial.print("notifyDccCVChange: CV");
Serial.print(cv);
Serial.print(": ");
Serial.println(value);
#endif
switch (cv) {
case cvDef: // Factory defaults CV has changed.
if (value == cvDef) // Change to reset to factory defaults.
notifyCVResetFactoryDefault(); // Make defsCVCnt non-zero and equal to number of CVs to be reset
break; // to flag loop() that function reset to factory defaults is needed.
case cvSub: // Subversion CV changed.
setCVInd = cv; // Flag loop() to restore it.
setCVVal = mfr_sub;
break;
#ifdef dump_CVs_enabled
case cvDump: // CV dump is requested.
setCVInd = cvDump; // Flag loop() to reset the value
setCVVal = 0; // and dump the CVs to serial.
break;
#endif
case cvAddressLo: // Address LSB changed.
case cvAddressHi: // Address MSB changed.
if (badcvAddressHi()) { // Valid address LSB?
setCVInd = cvAddressHi; // Ivalid address: flag loop() to restore it.
setCVVal = calcAddressHi(decoder_address);
} else getDecoderAddress(); // Get decoder address if valid value.
break;
case cvPWMframe: // PWM frame rate CV changed.
if (badcvPWMframe()) { // Valid PWM frame rate?
setCVInd = cv; // Invalid rate: flag loop() to restore it.
setCVVal = pwm_frame / pwmFrameDiv;
} else initChannels(); // Initialize all channels.
break;
case cvPWMlo: // 0% pulse length CV changed.
case cvPWMhi: // 100% pulse length CV changed.
if (badcvPWM()) { // Valid PWM frame rate?
setCVInd = cv; // Invalid rate: flag loop() to restore it.
setCVVal = ((cv == cvPWMlo) ? pwm_lo : pwm_hi) / pwmDiv;
} else initChannels(); // Initialize all channels.
break;
case cvServoAngle + 0: // Servo 1 MAIN angle CV changed.
case cvServoAngle + 1: // Servo 1 SIDING angle CV changed.
case cvServoAngle + 2: // Servo 2 MAIN angle CV changed.
case cvServoAngle + 3: // Servo 2 SIDING angle CV changed.
case cvServoAngle + 4: // Servo 3 MAIN angle CV changed.
case cvServoAngle + 5: // Servo 3 SIDING angle CV changed.
case cvServoAngle + 6: // Servo 4 MAIN angle CV changed.
case cvServoAngle + 7: // Servo 5 SIDING angle CV changed.
if (badcvServoAngle(cv)) { // Valid servo angle?
setCVInd = cv; // Invalid angle: flag loop() to restore it.
int c = cv - cvServoAngle;
setCVVal = servo_angle[c >> 1][c & 1];
} else initChannels(); // Initialize all channels.
break;
case cvPWMoffDelay: // Delay before PWM turn-off CV changed.
initChannels(); // Initialize all channels.
break;
case cvServoSpeed + 0: // Servo 1 speed CV changed.
case cvServoSpeed + 1: // Servo 2 speed CV changed.
case cvServoSpeed + 2: // Servo 3 speed CV changed.
case cvServoSpeed + 3: // Servo 4 speed CV changed.
if (badcvServoSpeed(cv)) { // Valid servo speed?
setCVInd = cv; // Invalid speed: flag loop() to restore it.
setCVVal = servo_speed[cv - cvServoSpeed] / speedDiv;
} else initChannels(); // Initialize all channels.
break;
case cvServoImmediate + 0: // Servo 1 state toggle CV changed.
case cvServoImmediate + 1: // Servo 2 state toggle CV changed.
case cvServoImmediate + 2: // Servo 3 state toggle CV changed.
case cvServoImmediate + 3: // Servo 4 state toggle CV changed.
int c = cv - cvServoImmediate;
channel_state[c] = !channel_state[c]; // Toggle channel state.
pwm_off[c] = 0; // Turn on channel PWM.
cycle_end[c] = 0; // No end of PWM change.
LED_state[c][0] = 0; // Clear main LED state.
LED_state[c][1] = 0; // Clear siding LED state.
setCVInd = cv; // Flag loop() to reset the value
setCVVal = 0; // and dump the CVs to serial.
break;
default:
break;
} // switch cv
getCVInd = cv;
cvChangeMillis = millis(); // Get current millis for CV change blocking
} // notifyDccCVChange
#endif
void initChannels() { // Initialize all channels
first_time_no_update = 1; // Prevent loop() from updating EEPROM one time.
#ifdef use_NmraDcc
pwm_frame = iRead(cvPWMframe) * pwmFrameDiv; // Load PWM frame rate.
pwm_lo = iRead(cvPWMlo) * pwmDiv; // Load 0% pulse length.
pwm_hi = iRead(cvPWMhi) * pwmDiv; // Load 100% pulse length.
pwm_off_delay = iRead(cvPWMoffDelay) * pwmOffDelDiv; // Load delay before PWM turn-off.
if (badcvPWMframe()) { // Valid frame rate?
pwm_frame = pwmFrame; // Invalid frame rate: load default value
iSave(cvPWMframe, pwm_frame / pwmFrameDiv); // and save to CV.
#ifdef debug_cv_write // Send CV if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("PWM frame rate to default "); // │
Serial.print(pwm_frame); // │
Serial.print(" µs ("); // ├ Debug transmission...
Serial.print(cMem); // │
sendCV(cvPWMframe); // │
Serial.println(")."); //-┘
#endif // End of debug transmission.
} // if (badcvPWMframe())
if (badcvPWM()) { // Valid pulse length?
pwm_lo = pwmLo; // Invalid pulse lengths: load default values
iSave(cvPWMlo, pwm_lo / pwmDiv); // and save to CV.
pwm_hi = pwmHi;
iSave(cvPWMhi, pwm_hi / pwmDiv);
#ifdef debug_cv_write // Send CV if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("pulse lengths to defaults "); // │
Serial.print(pwm_lo); // │
Serial.print(", "); // │
Serial.print(pwm_lo); // │
Serial.print(" µs ("); // ├ Debug transmission...
Serial.print(cMem); // │
sendCV(cvPWMlo); // │
Serial.print(", "); // │
Serial.print(cMem); // │
sendCV(cvPWMhi); // │
Serial.println(")."); //-┘
#endif // End of debug transmission.
} // if (badcvPWM())
delay_counter = pwm_off_delay / (pwm_frame / 1000); // Calculate delay counter.
#endif
for (int c = 0; c < channels; c++) { // For all channels:
digitalWrite(servo_pin[c], LOW); // Make servo pin low to prevent jerk.
cycle_end_counter[c] = delay_counter; // Load cycle end counter.
channel_state[c] = iRead(c + cvMem); // Read last saved channel state.
channel_state[c] &= 1; // Mask the channel state to 0 or 1.
int cv = (c << 1) + cvServoAngle; // Get base angle CV address.
servo_angle[c][0] = iRead(cv); // Read channel angle for state 0.
servo_angle[c][1] = iRead(cv + 1); // Read channel angle for state 1.
if (badcvServoAngle(cv)) { // Check if "angles" are valid.
servo_angle[c][0] = angleLoDef; // Load default value for state 0 "angle".
servo_angle[c][1] = angleHiDef; // Load default value for state 1 "angle".
iSave(cv, servo_angle[c][0]); // Save state 0 "angle" to EEPROM
iSave(cv + 1, servo_angle[c][1]); // Save state 1 "angle" to EEPROM
#ifdef debug_i_save // Send channel state if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("servo "); // │
Serial.print(c + 1); // │
Serial.print(" angles to defaults "); // │
Serial.print(servo_angle[c][0]); // │
Serial.print(", "); // │
Serial.print(servo_angle[c][1]); // ├ Debug transmission...
Serial.print(" ("); // │
Serial.print(cMem); // │
sendCV(cv); // │
Serial.print(", "); // │
Serial.print(cMem); // │
sendCV(cv + 1); // │
Serial.println(")."); //-┘
#endif // End of debug transmission.
} // if (badcvServoAngle(cv))
servo_speed[c] = iRead(c + cvServoSpeed) * speedDiv; // Read channel servo speed.
if (badcvServoSpeed(c + cvServoSpeed)) { // Check if servo speed is valid.
servo_speed[c] = speedDef; // Load default value for servo speed.
iSave(c + cvServoSpeed, servo_speed[c] / speedDiv); // Save speed to CV.
#ifdef debug_cv_write // Send channel servo speed if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("servo "); // │
Serial.print(c + 1); // │
Serial.print(" speed to defaults "); // │
Serial.print(servo_speed[c]); // ├ Debug transmission...
Serial.print(" ("); // │
Serial.print(cMem); // │
sendCV(c + cvServoSpeed); // │
Serial.println(")."); //-┘
#endif // End of debug transmission.
} // if (badcvServoSpeed(c + cvServoSpeed))
servo_pwm[c][0] = calcPWM(servo_angle[c][0]); // Calculate PWM for channel state 0.
servo_pwm[c][1] = calcPWM(servo_angle[c][1]); // Calculate PWM for channel state 1.
servo_step[c] = calcStep(c); // Calculate servo step for channel.
if (!servo_step[c]) servo_step[c] = 1; // Set sevo step to a min of 1.
pwm_out[c] = servo_pwm[c][channel_state[c]]; // Set initial channel PWM.
pwm_off[c] = 0; // Turn on channel PWM.
cycle_end[c] = 0; // No end of PWM change.
LED_state[c][0] = 0; // Clear main LED state.
LED_state[c][1] = 0; // Clear siding LED state.
pinMode(servo_pin[c], OUTPUT); // Initialize channel output.
pinMode(LED_pin[c][0], OUTPUT); // Initialize main LED output.
pinMode(LED_pin[c][1], OUTPUT); // Initialize siding LED output.
#ifdef debug_i_read // Send channel setup data if debug mode is enabled.
Serial.print("Servo "); //-┐
Serial.print(c + 1); // │
Serial.print(" set to"); // │
Serial.print(cStr[channel_state[c]]); // │
Serial.print(" ("); // │
Serial.print(cMem); // │
sendCV(c + cvMem); // │
Serial.print(", "); // │
Serial.print(cMem); // ├ Debug transmission...
sendCV(c * 2 + cvServoAngle); // │
Serial.print(", "); // │
Serial.print(cMem); // │
sendCV(c * 2 + cvServoAngle + 1); // │
Serial.print(", "); // │
Serial.print(cMem); // │
sendCV(c + cvServoSpeed); // │
Serial.println(")"); //-┘
#endif // End of debug transmission.
} // for (int c = 0; c < channels; c++)
} // initChannels()
void debugMain() { // Send decoder info if debug mode is enabled.
#ifdef debug_main
Serial.println();
Serial.println("--- Accessory DCC Decoder --- Ivan Cankov ---");
Serial.print("Mfr ID: ");
Serial.print(iRead(cvMfr));
Serial.print("; Version: ");
Serial.print(iRead(cvVer));
Serial.print(".");
Serial.print(iRead(cvSub));
Serial.print("; Address: ");
Serial.println(decoder_address);
Serial.println();
#endif
} // debugMain()
void debugSetup() { // Send channel setup data if debug mode is enabled.
Serial.println();
Serial.print(" PWM Frame Rate = ");
Serial.print(pwm_frame);
Serial.println(" µs");
Serial.print(" 0% PWM Pulse Length = ");
Serial.print(pwm_lo);
Serial.println(" µs");
Serial.print(" 100% PWM Pulse Length = ");
Serial.print(pwm_hi);
Serial.println(" µs");
Serial.print("Delay before PWM Turn-off = ");
Serial.print(pwm_off_delay);
Serial.println(" ms");
Serial.println();
for (int c = 0; c < channels; c++) { // For all channels:
Serial.print("Servo "); // Send channel servo parameters to serial.
Serial.print(c + 1);
Serial.print(" address ");
Serial.print(decoder_address + c);
Serial.print(", ");
Serial.print("end points ");
Serial.print(servo_angle[c][0]);
Serial.print(", ");
Serial.print(servo_angle[c][1]);
#ifdef debug_servo_pwm // Send channel servo PWMs if debug mode is enabled.
Serial.print(" ("); //-┐
Serial.print(servo_pwm[c][0]); // │
Serial.print(", "); // │
Serial.print(servo_pwm[c][1]); // ├ Debug transmission...
Serial.print(" µs), "); // │
Serial.print("step "); // │
Serial.print(servo_step[c]); //-┘
#endif
Serial.print(", speed ");
Serial.print(servo_speed[c]);
Serial.print("ms, set to");
Serial.print(cStr[channel_state[c]]);
Serial.print(" angle ");
Serial.println(servo_angle[c][channel_state[c]]);
} // for (int c = 0; c < channels; c++)
Serial.println();
} // debugSetup()
void dumpCVs() { // Dump all CVs to serial.
#ifdef dump_CVs_enabled
Serial.println();
Serial.println("--- CV Dump Start ---");
Serial.println("CV,Value");
for (int i = 1; i <= 256; i++) { // Cycle thru all (256 supported) CVs.
int p = ((i == cvVer) || (i == cvMfr)) ? i : 0; // CV7 and CV8 are not in defaults array.
if (!p)
for (int c = 0; c < sizeof(factoryDefaults) / sizeof(cvPair); c++)
if (i == factoryDefaults[c].cv) p = i; // Get index if CV in defaults array.
if (p) { // CV found?
Serial.print("CV"); // Send CV to serial.
Serial.print(p);
Serial.print(",");
Serial.println(iRead(p));
} // if (p)
} // for (int i = 1; i <= 256; i++)
Serial.println("---- CV Dump End ----");
Serial.println();
#endif
} // dumpCVs()
void sendCV(int cv) { // Send CV to serial if debug mode is enabled.
#ifdef debug_main
Serial.print(cv);
Serial.print(": ");
Serial.print(iRead(cv));
#endif
} // sendCV(int cv)
void getSerial() { // Read searial if data is received.
int lF = 0, r = 0, cv = -1;
unsigned long val = 0, t = 0;
char com = 0;
do {
if (Serial.available()) { // Read serial.
t = micros(); // Read current timestamp for serial timeout.
char c = Serial.read();
if ((c == 'a') || (c == 'd')) {com = c; cv = -1; r = 0;}
else if ((c == cDelimiter) || (c == 's')) {com = c; cv = r; r = 0;}
else if (c == cLF) {val = r; r = 0; lF = 1;}
else if (c != cCR) r = r * 10 + String(c).toInt();
} // if (Serial.available())
} while (!lF && (micros() - t < serialTimeout));
if (!lF) return;
if (((com == cDelimiter) || com == 's') && (cv > 0) && (cv <= channels)) {
cv--;
byte cState = channel_state[cv];
if ((val >= (com == 's' ? speedMin : angleMin)) && (val <= (com == 's' ? speedMax : angleMax))) {
first_time_no_update = 1;
if (com == cDelimiter) {
servo_angle[cv][cState] = val;
servo_pwm[cv][cState] = calcPWM(servo_angle[cv][cState]); // Calculate PWM for channel current state.
iSave(cv * 2 + cvServoAngle + cState, servo_angle[cv][cState]);
} // if (com == cDelimiter)
else if (com = 's') {
servo_speed[cv] = val;
iSave(cv + cvServoSpeed, servo_speed[cv] / speedDiv);
} // if (com = 's')
servo_step[cv] = calcStep(cv); // Calculate servo step for channel.
if (!servo_step[cv]) servo_step[cv] = 1; // Set sevo step to a min of 1.
pwm_out[cv] = servo_pwm[cv][cState]; // Set immediate channel PWM.
#ifdef debug_cv_write // Send channel state if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("servo "); // │
Serial.print(cv + 1); // │
Serial.print(cStr[channel_state[cv]]); // │
Serial.print(com == 's' ? " speed" : " angle"); // │
Serial.print(" to "); // │
Serial.print(com == 's' ? servo_speed[cv] : servo_angle[cv][cState]); // │
if (com == 's') Serial.print("ms"); // │
#ifdef debug_servo_pwm // ├ Debug transmission...
else { // │
Serial.print(" ("); // │
Serial.print(servo_pwm[cv][cState]); // │
Serial.print(" µs), "); // │
Serial.print("step "); // │
Serial.print(servo_step[cv]); // │
} // else (com == 's') // │
#endif // │
Serial.println("."); //-┘
#endif // End of debug transmission.
pwm_off[cv] = 0; // Turn on channel PWM.
cycle_end[cv] = 0; // No end of PWM change.
LED_state[cv][0] = 0; // Clear main LED state.
LED_state[cv][1] = 0; // Clear siding LED state.
Serial.print("Servo ");
Serial.print(cv + 1);
Serial.print(cStr[channel_state[cv]]);
Serial.print(com == 's' ? " speed" : " angle");
Serial.print(" set to ");
Serial.print(val);
Serial.println(com == 's' ? "ms" : "");
} else {
Serial.print("Servo ");
Serial.print(cv + 1);
Serial.print(cStr[channel_state[cv]]);
Serial.print(com == 's' ? " speed " : " angle ");
Serial.print(val);
Serial.println(" INVALID!");
} // if (checkValue(val, angleMin, angleMax))
} // if (((com == cDelimiter) || com == 's') && (cv > 0) && (cv <= channels))
else if (!com && (cv == -1) && (val > 0) && (val <= channels)) {
val--;
channel_state[val] = !channel_state[val]; // Toggle channel state.
pwm_off[val] = 0; // Turn on channel PWM.
cycle_end[val] = 0; // No end of PWM change.
LED_state[val][0] = 0; // Clear main LED state.
LED_state[val][1] = 0; // Clear siding LED state.
Serial.print("Servo ");
Serial.print(val + 1);
Serial.print(" switched to");
Serial.print(cStr[channel_state[val]]);
Serial.print(" angle ");
Serial.print(servo_angle[val][channel_state[val]]);
#ifdef debug_servo_pwm
Serial.print(" (");
Serial.print(servo_pwm[val][channel_state[val]]);
Serial.print(" µs)");
#endif
Serial.println();
} // if (!com && (cv == -1) && (val > 0) && (val <= channels))
else if (!com && (cv == -1) && !val) debugSetup();
else if ((com == 'a') && (val > 0) && (val <= 2044)) {
decoder_address = ((val - 1) & 0xfffc) + 1;
iSave(cvAddressLo, calcAddressLo(decoder_address)); // Save decoder address LSB.
#ifdef dump_CVs_enabled
Serial.print(cMem); // CV readback to serial requested, send CV.
sendCV(cvAddressLo);
Serial.println();
#endif
iSave(cvAddressHi, calcAddressHi(decoder_address)); // Save decoder address MSB.
Serial.print("Decoder (servo 1) address set to ");
Serial.println(decoder_address);
} // if ((com == 'a') && (val > 0) && (val <= 2044))
else if ((com == 'd') && (val == 11)) notifyCVResetFactoryDefault();
else {
Serial.print(!com ? "Servo " : com == 'a' ? "Address " : "Command");
Serial.print(!com ? String(cv + 1) : com == 'a' ? String(val) : "");
Serial.println(" INVALID!");
} // if ((com == 'd') && (val == 11))
} // getSerial()
С риск да пооцапам /предв. се извинявам за което/, имам няколко въпроса.
Първо много хубава, нагледна и добре обяснена тема, много добре представяни и подкрепяни със снимки неща.
Второ какъв е смисълът за ползването на 328p, за 4....или даже 1 серво?
С риск да стана нагъл-тука ползват същият микроконтролер за управление на 17 сервота:
. . .
Микроконтролер от рода на ATTINY85: . . ., поне според мен ще свърщи същата работа.
А има и трикове от рода на четене на вход /бутон и др./, и едновременно на същият пин да е изход за Led, сегмент или др.
В нета има свободни схеми и декодери за 4 стрелки, с прецизно управление на сервота под DCC протокол, при които с промяна на опеделена променлива можеш да промениш "размаха" и скоросттта на сервото, с контролери които са светлинни години под 328.
Малко ми изглежда като да светна вагон с STM32.
Като цяло dcc е супер, ако го ползваш с определени софтуери за определяне на маршрути и заетост, ако ще трябва да въртиш стрелките ръчно, по-добре да се ползва друго управление-поне според мен, или както в една друга тема на "онзи с осмиците" :) :hi: да имаш управление на макета с "diy".
Даже аналоговото ще е по-добре, от "ръчното"dcc.
да имаш управление на макета с "diy".Абе друго си е да управляваш влака с дръжка за газ и дръжка за спирачка, а не с врътка за скоростта... Несравнимо е! И да си си го направил самичък, ако ще и да е Ардуино и ROCO LAN протокол.
Абе друго си е да управляваш влака с дръжка за газ и дръжка за спирачка, а не с врътка за скоростта... Несравнимо е! И да си си го направил самичък, ако ще и да е Ардуино и ROCO LAN протокол.Или Аз не се изразих правилно, или не ме разбра. Имах напредвид пулта с ключетата /не знам как се казва/, със схемата на гарата. Та когато ползваш подобно табло като твоето, е удобно. В твоят случай е най добре, понеже програмата следи за заетост и т.н след дигитализацията.
. . . при толкова много свободни крака, няма никаква обратна връзка дали наистина се е преместила стрелката- дефектно серво, мех.повреда на стрелката, и т.н. Само като идея нещо като компаратор между сърцето и двете релси или тук във форума някой беше направил стрелката с цк ключета/не помня темата точно/, което е доста обемно ест.
. . .
Много красиво! Особено втория лок, по ме кефи!
Кои серво ползваш - S90? И платките ми харесват - семпли!
:clapping: :good:
Пп. Радвам се, че все още слушаш касети на SONY -то!!! :hi:
И аз си ги пазя , също със SONY! :dance1:
:100000002100F030990040282000111C0C280C1011
:100010001110B8168C1009000C193A2803140B198A
:100020000310A50DA40DA30DA20DA10DA00DC030B0
:100030002104FF3A031D2528F8302205F03A031D5C
:100040003D28251C3D28A1013028A017200F3D2860
:10005000F0302105E03A031D3D2838162108A6009E
:100060002508AA002408A9002308A8002208A70040
:1000700038143D28B33095000B1127009301090077
:100080008230950011140C308C0023008C01270065
:1000900011151215200031309800203084008001A5
:1000A000840A80300406031D4F28C8308B00963028
:1000B000BF00C0007E2238186020B81A8B215B2850
:1000C00038108B11381A8828A70CA80CA90CAA0C78
:1000D000031C8528A70CA80CA90C03188528A70CBD
:1000E000A80C03188528280829062A06031D852838
:1000F00028080319E828F039703A0319EE2828086F
:10010000C039803A03192F29280FB8138B1508001E
:100110003812A60CA70CA80CA90CAA0C031C852845
:10012000A60CA70CA80CA90C03188528A60CA70CD4
:10013000A80C03188528A60CA70C031885282708E7
:10014000280629062A06031D85282708F039703A53
:10015000031D8528B81F2A29381F2D29381329087F
:10016000EF00271886282808D222B8188628A71951
:10017000BB28271D8628271DC528A71D2329381918
:10018000672AB8196B2911236F2A0923EF006F30F2
:10019000840007302905DF20291ED628A9198004EC
:1001A000FF3AA91D80052808D222B72800050319A7
:1001B000DC28A91927298628A91D272986280B00AC
:1001C0000134023404340834103420344034803490
:1001D0002908031D85283813B8178628B81F2A292F
:1001E000381F2D2938132908EF002808F739753AE8
:1001F00003190E29013A03190C29023A03191C2983
:10020000013A03192029280803392B07D222B818EC
:100210008628A81D2329BF281C300929A81D1629B6
:10022000A903A90D290DFC39AB008628A903A90D46
:10023000290DFC392B0625290630A81D23298628DF
:100240000730A8196A2A09232906031D8628732264
:1002500038138628B8133813862838178628B81319
:10026000A91D85288C1D4F292D0880382906F039B5
:10027000031D85282803ED00ED0DED0D2C036D0603
:10028000FC39031D852829082E060439031D8528FD
:10029000290803390B0077297A2980298329A91F86
:1002A00085282803ED00ED0D6D0DFC39013EAC00F5
:1002B000EF000030112329087039AD002D0E073AE8
:1002C000EF00083011232E1129192E152E08EF00EA
:1002D000133011234329ED003C30ED0201306D183D
:1002E00002303A050310031903146D0D49293A1819
:1002F00039147C293A1C391434084A22B60088295A
:10030000BA18B9148529BA1CB91435084A22B7009D
:10031000B8138B150800B8128B199429A501A401F4
:10032000A301A201A1018B153C080B009A29A72962
:10033000B029BC01C1012F08003CC2003F08B82110
:1003400039188B1139180C141814BC0A080040080D
:10035000B821B9188B11B9188C141814BC0AD429F7
:10036000181041089600420897001814BC010F2A83
:100370001810970196000310960D970D960D970D86
:10038000960D970D96070318970A96070318970A74
:100390001608C1070318C20A1708C2079609970969
:1003A000960A0319970A0800391C08003D180A2A02
:1003B000B60B080034084A22B600340854223A1C0E
:1003C000F2293B1C5E22BF07891FEA293F08ED0086
:1003D0003008ED293008ED003F08ED020318080051
:1003E0003008022A3B185E22BF07891FFB293F08FD
:1003F000ED003108FE293108ED003F08ED02031839
:1004000008003108BF003E08B6003D140130BA06AE
:10041000C522612AB60B08003D1039100800B91C2E
:100420000800BD18452AB70B080035084A22B70056
:1004300035085422BA1C2D2ABB1C5E22C007891F16
:10044000252A4008ED003208282A3208ED0040082D
:10045000ED020318080032083D2ABB185E22C007CF
:10046000891F362A4008ED003308392A3308ED0089
:100470004008ED02031808003308C0003E08B7002A
:10048000BD140230BA06C522612AB70B0800BD10A0
:10049000B910080007390B00073405340334013460
:1004A000013401340134013407390B0001340134C3
:1004B0000134013402340334043410348909013E18
:1004C0000800AE1F08003A08EF001230112B073A5F
:1004D000031D862808302906031D862867237322FA
:1004E000381338107E2AED018030EC0030308C0655
:1004F000ED0F782AEC0B782A8C06080000300923CF
:10050000AC0008300923AD00AD0E2D097039AD00E7
:100510003B101E300923B0001F300923B100300208
:1005200003183B14BB1020300923B20021300923EB
:10053000B30032020318BB1432300923B400333045
:100540000923B50014300923AF0017300923BE007A
:1005500013300923AE003B122E183B16BB12AE1807
:10056000BB168B15AE1FC52A12300923BA003008FE
:100570003A183108BF003208BA183308C0003E08E4
:10058000B600B7000330BD00B90000303A180916B4
:10059000BA1889163B063039ED000C0803396D0492
:1005A0008C000800B8103811B811003A0319003453
:1005B000083A03190834143A03191C34023A03198F
:1005C0001E34013A03191F343F3A03192034013A0B
:1005D00003192134353A03191434033A0319173433
:1005E000043A03191334213A03193234013A031936
:1005F0003334B8150F3A03193C34013A03193D342A
:1006000038153B3A03190634013A03190734B81474
:100610001234803E8600873087000318870A403FE7
:100620000800EE0009236F06031908006E08803EDB
:10063000F0398600873087000318870A70308400FD
:1006400016001A0006080F39031D202B6E08803E85
:100650000F39703E84006F08803F6E08803EF0398D
:1006600023009100073092000318920A8B13151390
:10067000151545237030840012004E23910A091885
:100680003C2B15118B1720000800151655309600CD
:10069000AA309600951400000000080095169300FB
:1006A000940111080F3A0F3903195E2B553096004B
:1006B000AA309600951400000000FF3495125530C2
:1006C0009600AA3096009514000000000034F00156
:1006D000230080309100073092008B1315131515FD
:1006E0007008803E0F3903194523700882234E237A
:1006F000910AF00A40307006031D702B15118B17FC
:10070000200008000B000134FF34FF34FF34FF34B5
:10071000FF3401340D340034FF34FF34FF34FF3430
:10072000FF34FF34FF34FF34FF34003480344B3463
:10073000FF34FF342834FF34FF34FF34FF34803477
:10074000FF348C34A0348C34A034FF34FF34FF34B5
:10075000FF34FF34FF34FF34FF34FF34FF34FF3401
:10076000FF34FF34FF34FF34FF3403340334FF34E9
:10077000FF34FF34FF34FF34FF34FF34FF34FF34E1
:06078000FF34FF34FF34DA
:100F00000100FF00FF00FF00FF00FF0001000D00D7
:100F10000000FF00FF00FF00FF00FF00FF00FF00D8
:100F2000FF00FF00000080004B00FF00FF002800D2
:100F3000FF00FF00FF00FF008000FF008C00A0000A
:100F40008C00A000FF00FF00FF00FF00FF00FF007B
:100F5000FF00FF00FF00FF00FF00FF00FF00FF0099
:100F6000FF00FF0003000300FF00FF00FF00FF0081
:100F8000200032002D0053006500720076006F00D3
:100F9000200049002E002000430061006E00200068
:100FA000300031002F00300031002F0032003600B9
:020000040001F9
:04000E00843FFF1E0E
:00000001FF
| CV | Обхват | Заводска стойност | Описание |
| ------ | --------- | --------------- | ---------------------------------------------------------------------------- |
| CV1 | 1 - 255 | 255 | Адрес на декодера, младша част |
| CV7 | - | 1 | Версия на програмата на декодера, само за четене |
| CV8 | - | 13 | Идентификация на декодера, само за четене |
| CV9 | 0 - 7 | 0 | Адрес на декодера, старша част |
| ------ | --------- | --------------- | ---------------------------------------------------------------------------- |
| CV19 | - | 0 | Памет за състоянието на изходите; програмата сама пише в това CV |
| ------ | --------- | --------------- | ---------------------------------------------------------------------------- |
| CV20 | 0 - 135 | 128 | Кофигурация на декодера: |
Бит: | ----------------- | ----------------------------------- | |
0 | 0 | Реверсиране на реле 1: не (0); да (1) | |
1 | 0 | Реверсиране на реле 2: не (0); да (2) | |
2 | 0 | Избор на устройство: 0 и 1 (0); 2 и 3 (4) | |
3 - 6 | 0 | Не се използват (0) | |
7 | 128 | Запазване състоянието на изходите: не (0); да (128) | |
| ------ | --------- | --------------- | ---------------------------------------------------------------------------- |
| CV21 | 59 - 117 | 75 | Продължителност на ШИМ импулсите в 0,256 милисекунди |
| CV24 | 1 - 255 | 40 | Пауза преди спиране на импулсите към сервотата в 20 милисекунди |
| ------ | --------- | --------------- | ---------------------------------------------------------------------------- |
| CV | Обхват | Заводска стойност | Описание |
| ------ | --------- | --------------- | ---------------------------------------------------------------------------- |
| CV31 | 75 - 225 | 140 | Импулс към серво 1 за "права" посока на стрелката в 0,01 секунди |
| CV32 | 75 - 225 | 160 | Импулс към серво 1 за посока "отклонение" на стрелката в 0,01 секунди |
| CV33 | 75 - 225 | 140 | Импулс към серво 2 за "права" посока на стрелката в 0,01 секунди |
| CV34 | 75 - 225 | 160 | Импулс към серво 2 за посока "отклонение" на стрелката в 0,01 секунди |
| ------ | --------- | --------------- | ---------------------------------------------------------------------------- |
| CV51 | 0 - 7 | 3 | Скорост за превключване на серво 1, 0 = минимална (бавно), 7 = максимална (бързо) |
| CV52 | 0 - 7 | 3 | Скорост за превключване на серво 2, 0 = минимална (бавно), 7 = максимална (бързо) |
| ------ | --------- | --------------- | ---------------------------------------------------------------------------- |
| CV61 | 0 - 255 | 0 | Превключване на серво 1, няма значение каква стойност е въведена |
| CV62 | 0 - 255 | 0 | Превключване на серво 2, няма значение каква стойност е въведена |
| ------ | --------- | --------------- | ---------------------------------------------------------------------------- |
- | CV-тата, които не са показани по-горе не се използват или не са достъпни за използване | ||
| ------ | --------- | --------------- | ---------------------------------------------------------------------------- |