Електроника и Електротехника | Electronics and Electrical Engineering > Цифрово / дигитално управление | Digital Command Control

DCC декодер за стрелки и други принадлежности по макетите

(1/5) > >>

IvanC:
ПРЕДВАРИТЕЛНА ИНФОРМАЦИЯ

В една-две други теми споменах, че за това, което съм публикувал в този форум, идеите ("мухите") ми бяха пуснати от брат ми Владо (ValdoC). Съвсем невинно, той би ми подхвърлил:

"Я виж тази препратка и кажи какво трябва да направя, за да получа този резултат."

или

"Взех това (устройство/макет/и т.н.) и искам до мога да го управлявам по този-и-този-начин. Как мога да го направя?"

С това в главата ми се задейства "машинката", която води до някои "неочаквани" (дори и за мен) идеи и резултати ;D. В случая, това започна с направата от брат ми на самоделни стрелки, които той искаше да управлява по някакъв начин с бутони и чрез серва. Бидейки корабо- и авиомоделисти, ние използвахме серва за управление на почти всичко по корабо- и авиомоделите - рул, газ и въздух (на двигателя с вътрешно горене), елерони, хоризонтални и вертикални подвижни повърхности, клапи и т.н. Та поради това имахме натрупен опит със серво-машинки, както и някакъв запас от такива и като естествено продължение в ж.п. моделизма, искахме да ги използваме за превключване на езиците на стрелките.

Към времето, когато брат ми ме попита за управление на серва за превключване на стрелките, аз вече бях разработил и направил няколко PPM декодера за предаватели за радиоуправляеми модели, които използвах за самолети. Тук също има малко предистория. Накратко тя се състои в това, че реших да използвам Ардуино за целта и по-специално първоначално се спрях на вече готовата библиотека за Ардуино за управление на серва. Тази библиотека не работеше много успешно, когато трябваше да бъде приложена за 8 серва, заради което реших да се пробвам да напиша цялата програма, с директно управление от един от таймерите на ATMega386. Няколко проби по-късно успях да подкарам PPM да работи успешно, както и да подкарам управлението на 8 серва.

Така стигам до момента, в който брат ми искаше да управлява 1-2 серва, с които да превключва стрелките. Той дори ми изпрати линк към нещо подобно, направено с Ардуино, кодето използваше стандартната библиотека, която споменах по-горе и не ми хареса как работеше. След няколко сметки за броя необходими входове (за бутони) и изходи (за ШИМ към сервата и светодиоди за индициране на състоянието на сервата), прецених че едно Ардуино Уно/Нано/Про-мини (всичките на базата на ATMega328) може спокойно да управлява до 4 серва и направих програма за управление на тези 4 серва от 4 бутона (по един на серво) и индикация с 8 светодиода (по два на серво, един за всяко състояние на сервото). Брат ми публикува тази програма (с помощта на Павел - pavel75) в темата "Arduino за стрелки":

http://www.railwaypassion.com/forums/index.php/topic,4987.msg110905.html#msg110905

Mинаха няколко години и брат ми ми пусна новата муха - да управлява сервата за стрелките през DCC. Поразрових се и намерих две библиотеки за Ардуино за DCC контролери. С помощта на едната библиотека успях да направя първия декодер за стрелки. След няколко промени, направих "универсален" декодер - който може да използва по избор едната от двете библиотеки.


ГЛАВНАТА ИДЕЯ

В DCC, декодерите за "принадлежности" (стрелки, семафори, светофори и др. подобни) са групирани по 4 броя (адреса) на декодер. Заради това групиране, както и сметките за устройството за управление на стрелки със серва, описано по-горе, направих декодера за 4 стрелки (серва) с помощта на Ардуино - Уно, Нано или Про-мини, т.е. с процесор ATMega 328. ШИМ-управлението на сервата е част от програмата и е направено с помощта на прекъсване (Interrupt), генерирано от един от таймерите, който непрекъснато брои предварително зададен интервал от време, равен на продължителността на необходимия импулс или паузата между импулсите. Дотук това е без разлика спрямо програмата за управление на сервата с бутони. Разликата между двете програми е в това, че при декодера, превключването на сервата става от DCC импулсите, предавани към декодера, вместо от бутони. Това предаване на DCC импулсите може да стане по няколко начина - от станцията или от бустер през релсите или пак от станцията или бустер през отделни проводници, захранващи само декодерите за принадлежности - по избор от потребителя със съответното опроводяване към декодера. Последното (опроводяването) не е съществено в случая.


СХЕМА НА ДЕКОДЕРА



DCC сигналът се подава на куплунга DCC. Сигналът се изправя от диодите D1 до D4, филтрира от кондензатора C1 и напрежението се регулира на 5 волта от регулатора IC1. На схемата съм посочил LM7805 линеен регулатор, но може да се използва и друг вид регулатор на 5 волта, включително и импулсен регулатор. Импулсните регулатори са със значително по-голяма ефективност (КПД) от линейните регулатори, което води до значително по-малко топлоотделяне и съответно многократно по-малки загуби от това топлоотделяне.

След регулатора, напрежението, стабилизирано на 5 волта, се филтрира от кондензаторите C2 и C3 и захранва Ардуиното и сервата S1 до S4.

От DCC входа, сигналът се подава през съпротивлението R1 към извод D2 (2) на Ардуиното. Тъй като входовете на Ардуиното трябва да са ограничени в диапазона от 0 волта до +5 волта, двойният диод D5 се грижи да ограничи входното напрежение на този извод в желаните граници.

Изходите D3 (3), D5 (5), D6 (6) и D9 (9) на Ардуиното подават сигналите (ШИМ) към сервата, а D10 (10) до A3 (17) управляват светодиодите D11 до D42, които индицират състоянието на сервата. Изходите D10 (10) до A3 (17) могат също така да управляват и релета (през буферни транзистори), които да превключват сърцата на стрелките към съответната релса. За това (управлението на релета за сърцата) - малко по-нататък.

Това, кои изводи на Ардуиното се използват за DCC входа и за изходи към сервата и светодиодите, се задава в програмата за Ардуиното и може да се променя в някои допустими граници. Например за вход от Ардуиното може да се използват само изводите D2 (2) и D3 (3), а за изходите за сервата и светодиодите могат да се използват изводите от D2 (2) до A5 (19), без да има дублиране с входа за DCC.


Иван

IvanC:
ПРОГРАМА

Заради ограничението на форума от 20000 символа на мнение, ще пусна програмката в няколко последователни мнения.


--- Код: ---
// ---------------------------------- Задаване функциите на изводите: ----------------------------------
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


--- Край на кода ---

Продължението следва...

Иван

IvanC:
ПРОГРАМА, II-ра част


--- Код: ---
// ------------------------------- Указатели за дебъгване на програмата: -------------------------------
//#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()


--- Край на кода ---

Продължението следва...

Иван

IvanC:
ПРОГРАМА, III-та част


--- Код: ---
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


--- Край на кода ---

Продължението следва...

Иван

IvanC:
ПРОГРАМА, IV-та част (последна)


--- Код: ---
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()


--- Край на кода ---

Иван

Навигация

[0] Списък на темите

[#] Следваща страница

Премини на пълна версия