Автор Тема: DCC декодер за стрелки и други принадлежности по макетите  (Прочетена 5589 пъти)

IvanC

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
ПРЕДВАРИТЕЛНА ИНФОРМАЦИЯ

В една-две други теми споменах, че за това, което съм публикувал в този форум, идеите ("мухите") ми бяха пуснати от брат ми Владо (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

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
DCC декодер за стрелки - програма, част 1
« Отговор #1 -: 01 Февруари 2023, 00:51:00 »
ПРОГРАМА

Заради ограничението на форума от 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


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

Иван
« Последна редакция: 01 Февруари 2023, 00:54:41 от IvanC »

IvanC

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
DCC декодер за стрелки - програма, част 2
« Отговор #2 -: 01 Февруари 2023, 00:56:33 »
ПРОГРАМА, 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

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
DCC декодер за стрелки - програма, част 3
« Отговор #3 -: 01 Февруари 2023, 00:59:03 »
ПРОГРАМА, 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

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
DCC декодер за стрелки - програма, част 4 - последна
« Отговор #4 -: 01 Февруари 2023, 01:01:26 »
ПРОГРАМА, 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()


Иван

IvanC

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
ПРОГРАМА - БИБЛИОТЕКИ

Програмата използва следните библиотеки:

1. NmraDcc.h - библиотека за DCC декодер по изискванията на NMRA

ИЛИ

2а. EEPROM.h - библиотека за работа с EEPROM
2b. DCC_Decoder.h - библиотека за DCC декодер

Коя от двете библиотеки се използва се задава в частта от програмата с коментар Указател за използвана DCC библиотека:.

Първата от двете библиотеки използва CV-та, т.е. чете и запомня параметрите, въвеждани от потребителя в CV-та. Тези CV-та на практика се четат от и запазват в EEPROM-а на Ардуиното, но това става "прозрачно" за програмата.

От друга страна, втората библиотека (2b) не използва CV-та, което налага програмата да се грижи за четенето и запазването на параметрите в EEPROM-a, което пък налага използването на библиотеката за работа с EEPROM (2a).

Така, както е написана програмата по-горе, тя използва първата библиотека - NmraDcc.h. Как да инсталираме тази библиотека описах в това мнение:

http://www.railwaypassion.com/forums/index.php/topic,5553.msg123157.html#msg123157

Библиотеката EEPROM.h идва заедно с Ардуино средата и се инсталира автоматично при инсталирането на средата. Ако някой реши да използва библиотеката DCC_Decoder.h, да каже и ще пусна препратка към файловете й, както и указания как да се "инсталира".


ПРОГРАМА - НАСТРОЙКИ И РАЗЯСНЕНИЯ

Програмата започва с най-важните за крайния потребител настройки, последвани от разяснения (help) как да се използва Ардуиното и декодера.

1. Задаване функциите на изводите:
В тази група настройки се задават изводите на Ардуиното, към които са свързани входа за DCC сигнала и изходите за сервата и светодиодите. Изводите за светодиодите могат да се използват и за управление на релета за превключване на захранването на сърцата на стрелките.

2. Константи за формиране на импулсите:
Тук се задават основните параметри на ШИМ импулсите, подавани към сервата - период на ШИМ-а и минималната и максималната продължителност на импулсите. Крайните позиции на сервата се задават в интервала от 0 то 100 включително, като стойност 0 отговаря на продължителност на импулса, зададена от pwmLo, а стойност 100 отговаря на продължителност, зададена от pwmHi. В повечето случаи няма нужда от промяна на тези стойности. Ако обаче по някаква причина, ходът на някое серво не е достатъчен, за да превключи напълно езика на някоя стрелка, то стойностите на тези два параметъра могат да бъдат променени. Това трябва да се предприеме само като крайна мярка, ако не може механично да се настрои хода на езика - премествне на рогчето с един (или повече) зъба на оста на сервото, преместване на тягата от рогчето на сервото към езика на стрелката на отвор на рогчето по-отдалечен от оста на завъртане на сервоти и т.н.
Последният параметър задава времето, след което Ардуиното спира да подава импулси към всяко едно серво след като сервото е завършило превключване от едното крайно положение в другото. С това се намалява значително консумацията от сервата, когато са в покой.

3. Свързване на светодиод:
Тук съм показал как да бъдат свързани светодиодите към изводите на Ардуиното, както и номерацията на изводите на Ардуиното.

4. Команди за управление през серийния интерфейс:
Стрелките могат да се превключват през серийния монитор на Ардуино средата, както и да се променят някои от параметрите на сервата, като крайните положения на сервата, времето за превключване на сервата от едното крайно положение в другото. Също от серийния монитор на Ардуино средата може да се зададе основния адрес на декодера и да се възстановят "заводските" настройки на декодера.

5. CV-та
Описани са всички CV-та, използвани от програмата, тяхното значение и "заводските" им стойности. Описанието е на български и английски езици.

Следващите параметри са вече във втората част на програмата.

6. Указатели за дебъгване на програмата:
Тези указатели са необходими само ако някой реши да променя кода на програмата. При разрешаване на някои от указателите, Ардуиното изпраща информация за състоянието на съответни параметри по серийния интерфейс, които могат да бъдат прочетени в серийния монитор на Ардуино средата.

7. Указател за използвана DCC библиотека:
Тук се задава коя DCC библиотека да бъде използвана (виж по-горе в това мнение) и съответно се забраняват някои от указателите за дебъгване на програмата (ако са разрешени), които не работят с библиотеката DCC_Decoder.h.

Останалите части от програмата не трябва да се променят, освен ако някой реши да експериментира с промяна на самата програма. Програмата е напълно функционална в този си вид и няма нужда да бъде променяна. Има едно изключение (за един параметър), което ще опиша по-нататък, когато стигна до управлението на релета за превключване на сърцата на стрелките.

Иван

IvanC

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
РЕЛЕТА

Стигнах до добавянето на релета към декодера. Релетата могат да се използват за захранване на сърцата на стрелките или ако използваме декодера да включваме други принадлежности, това става с контактите на релетата. Разбира се ако консуматорите са в рамките на допустимите напрежение (5 волта) и ток (максимум 20 мА) на изход на Ардуиното, това може да стане и без релетата, като свържем товара между изход за светодиод и масата на декодера.

Схемата на декодера с релетата:



Така начертана, схемата е за управление на сърца на стрелки. Ако трябва да включваме или превключваме друг вид верига, нормално отвореният (NO) и нормално затвореният (NC) контакти на съответното реле не трябва да са свързани към DCC сигнала (или към релсите), а да са изведени самостоятелно на отделни изводи (клеми, конектор) на декодера.

На схемата съм използвал двойни MOSFET транзистори. Причината за това, е че са малки, нямат нужда от съпротивления в гейта и поради това се опроводяват по-лесно. Недостатъкът им е, че точно този вид двоен MOSFET (NX3020NAKS) е наистина много малък и може да се използва само със заводски произведена платка. Иска и много внимание и търпение при запояване на ръка с поялник. Разбира се, MOSFET-ът може да се замени с биполярен NPN транзистор - емитер към масата на декодера, колекторът към релето и анода на шунтиращия диод и базата през съпротивление 3,3 килоома към джъмпера.

С помощта на джъмпера за канала на всяко реле се избира в кое състояние на изхода на този канал да се включи релето (контактът превключва общия извод на релето от нормално затворения (НЗ) към нормално отворения (НО) извод на релето). Обикновено при "включено" състояние на канала, изходът на Ардуиното, свързан към извод 1 на джъмпера (вдясно на схемата) получава високо ниво и ако искаме да затворим веригата на това, което управляваме със свързване на НО-ия контакт с общия такъв на релето, трябва джъмперът да окъсява изводите 1 и 3, а 2 да остане свободен. Ако искаме обратното - свързваме 2 с 3, а 1 остава свободен. Разбира се ако използваме НЗ извода вместо НО, логиката на джъмперите се обръща.

При захранването на сърца на стрелки, ако на някоя стрелка сърцето е захранено погрешно, просто трябва да превключим джъмпера от позиция 1-3 в 2-3 или обратно.

В програмата има една променлива, наречена LED_flasher. Намира се в "раздела" под коментара Константи за нормалната работа на програмата - да не се променят!!! и по-точно на ред 264 (ако не променяте програмата с добавяне или изтриване на редове). Този ред изглежда така:

const int  LED_flasher      = 10;                 // LED flash length: 10 for moderate flashing, 1000 for no flashing.

Тази константа указва колко бързо да мига светодиодът за новото положение на сервото по време на превключване на състоянието на сервото. Идеята за мигането е, докато сервото се завърта плавно от едното до другото крайно положение (това времето се задава със CV за всеки канал), понеже стрелката все още не е напълно превключила, светодиодът за новото положение мига, т.е. указва, че стрелката е все още в преходен режим, но също показва какво би било новото положение на стрелката. Като завърши завъртането на сервото, т.е. превключването на стрелката, светодиодът светва постоянно.

Това поведение би водило до няколкократно превключване на релето за захранване на сърцето. За да го избегнем, трябва да променим стойността на LED_flasher от 10 на 1000.

На показаната схема релетата са с бобинки за 5 волта и са свързани към регулираното от IC1 напрежение. По принцип, за един вид релета, колкото по-ниско е номиналното напрежението на бобинката, толкова по-голям е консумираният от бобинката ток. Ако и четирите релета са задействани едновременно, сумарният им ток се осигурява от регулатора на напрежение и ако този регулатор е линеен същият този ток се консумира от централата или бустера. Ако се използва импулсен регулатор, токът консумиран от централата/бустера намалява почти пропорционално на съотношението на напреженията на централата/бустера и регулатора, така че това не е проблем.

Може да използваме релета за по-високо напрежение, които да са свързани към входящото за регулатора напрежение, т.е. след Греца. В този случай обаче има "малък" проблем... Напрежението от централата/бустера може да варира от 13 до 19-20 волта за различните марки и модели, а за някои може да достигне и до 24 волта. Това е и "малкият" проблем - какви релета да използваме, с бобинки за какво напрежение? Ако напрежението на централата/бустера е до около 15 волта, спокойно може да използваме релета с бобинки за 12 волта. Ако е повече? Не всички видове релета се предлагат във вариант за 18 волта. Дори и с релета за 18 волта, те може да не превключват надеждно при напрежение на централата от 16 волта (в Греца има 1-2 волта пад на напрежение в зависимост от използваните диоди). Затова съм се спрял на релета за 5 волта. Тези, които използвам са много малки и съответно за малък комутиран ток - 1 А. Максималният ток, който могат да прокарат без повреда на контактите е 2 А. Това е достатъчно за преминаване на локомотив през правилно превключена стрелка. Ако стрелката бъде "разрязана", колелото разрязващо езика ще дъде накъсо станцията/бустера. Ако станцията/бустерът са настроени за малксимален ток по-голям от 2 А, това може да доведе до стопяване на контакта на релето, така че го имайте предвид.

Друга особеност на релетата е, че някои имат две версии - нормална, с по-голям консумиран от бобинката ток и "чувствителни" с по-малък ток на консумация от бобинката. Препоръчвам да си вземете от втория тип, ако имате такава възможност. Консумираният ток от "нормално" реле за 5 волта може да достигне 90-100 мА, докато реле с "чувствителна" бобинка консумира наполовина, т.е. между 30 и 50 мА. Дейташийтите на релетата дават тези параметри.

Иван

IvanC

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
DCC декодер за "паркетбан" стрелки
« Отговор #7 -: 04 Февруари 2023, 04:21:10 »
На основата на декодера, описан по-горе, направих DCC декодер за стрелки тип "паркетбан". Стрелките, на които монтирах декодерите, са моя разработка, "произведени" от брат ми Владо:




Схемата на декодера е много подобна на тази, описана по-горе, но само с едно реле и изходи само за едно серво:




Иван

IvanC

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
DCC декодер за "паркетбан" стрелки
« Отговор #8 -: 04 Февруари 2023, 18:49:54 »
Проблемът при управлението на "паркетбан" стрелки със серва е, че сервата и управлението им не могат да се монтират "под плота". Декодерът за 4 серва може да се сложи някъде встрани от релсите, но тогава възниква проблемът за опроводяването - 3 проводника към серво и евентуално още 3 за захранване на сърцето на една стрелка. Част от проблема е прекарването на проводниците под релсите, за свързване на стрелки на вътрешни коловози и то така, че проводниците да не надигат релсите. Затова реших да разработя декодера така, че да се монтира за постоянно на една стрелка и следователно да не се налага свързване и разкачане на връзките към декодера при построяване и разглобяване на "паркетбана".

И така, всяка стрелка си има собствен декодер, който е монтиран и свързан към стрелката. Захранването на декодера се взема от двете релси. И тук DCC сигналът се изправя от Греца и се подава на входа на регулатора за 5 волта. За регулатор на напрежение използвам импулсен такъв с MP2307, изграден като модул. Модулът е с малки размери и не се нуждае от радиатор, което е важно при това разположение на декодера.

Преди да разработя платката потърсих подходящи релета с бобинки за 5 волта, ток на контактите 2 А и минимални размери. Намери тези, които са на снимките (в зелен корпус) и разработих платката за тях. Бобинката на релето черпи около 90 мА ток при 5 волта. Тъй като регулаторът на напрежение е импулсен, общият ток на декодера, релето и сервото в покой, който те черпят от станцията/бустера, е около 40 мА при включено реле и около 10 мА при изключено реле, при напрежение на станцията/бустера около 16 волта.










Иван

IvanC

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
DCC декодер за "паркетбан" стрелки
« Отговор #9 -: 05 Февруари 2023, 00:15:08 »
Програмата в Ардуиното е същата, като публикуваната по-горе. За да не превключва релето няколкократно, докато стрелката преминава от направо в отклонение, както вече писах по-горе, промених стойността на константата LED_flasher, обявена на ред 264 от 10 на 1000:

const int  LED_flasher      = 1000;               // LED flash length: 10 for moderate flashing, 1000 for no flashing

Програмата си е все още за управление на 4 стрелки. Сервото и релето са свързани към изходите на Ардуиното за първата стрелка, така че декодерът "изхабява" останалите 3 адреса. За "паркетбан", където броят на стрелките е сравнително малък, това не е проблем.

Зелените релета, показани на снимките са FEME ESH A 001 5, но свършиха там, откъдето ги взех. Търсенето на други такива не се увенча с успех. Поразрових се повече и намерих релета с още по-малки размери, за които се наложи да променя платката:





XML (eagle)

PDF


Няколко производителя правят релета, които могат да се монтират на платката:

Omron G5V-1-T90 DC5
Fujitsu SY-4.5-K или SY-5-K
Panasonic HY1-5V
NAIS HD1E-M-DC5V


Иван

dred77

  • Trade Count: (2)
  • Sr. Member
  • ****
  • Публикации: 386
  • Рейтинг: 328
 С риск да пооцапам /предв. се извинявам за което/, имам няколко въпроса.
Първо много хубава, нагледна и добре обяснена тема, много добре представяни и подкрепяни със снимки неща.
Второ какъв е смисълът за ползването на 328p, за 4....или даже 1 серво?
С риск да стана нагъл-тука ползват същият микроконтролер за управление на 17 сервота:

https://forum.mrhmag.com/post/sma12-17-channel-configurable-multifunction-5-dcc-decoder-for-servos-12198051.
 Микроконтролер от рода на ATTINY85:ТУК, поне според мен ще свърщи същата работа.
 А има и трикове от рода на четене на вход /бутон и др./, и едновременно на същият пин да е изход за Led, сегмент или др.
 В нета има свободни схеми и декодери за 4 стрелки, с прецизно управление на сервота под DCC протокол, при които с промяна на опеделена променлива можеш да промениш "размаха" и скоросттта на сервото, с контролери които  са светлинни години под 328.
 Малко ми изглежда като да светна  вагон с STM32.
 Като цяло dcc е супер, ако го ползваш с определени софтуери за определяне на маршрути и заетост, ако ще трябва да въртиш стрелките ръчно, по-добре да се ползва друго управление-поне според мен, или както в една друга тема на "онзи с осмиците"  :)  :hi: да имаш управление на макета с "diy".
  Даже аналоговото ще е по-добре, от "ръчното"dcc.
 

« Последна редакция: 05 Февруари 2023, 02:03:47 от dred77 »

IvanC

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
Цитат на: dred77 link=topic=5606.msg124271#msg124271 date=1675554751
С риск да пооцапам /предв. се извинявам за което/, имам няколко въпроса.

Я да видим, дали ще мога да отговоря на въпросите.


Цитат
Първо много хубава, нагледна и добре обяснена тема, много добре представяни и подкрепяни със снимки неща.

Благодаря за коментара! И понеже не намирам въпрос, нямам и отговор.


Цитат
Второ какъв е смисълът за ползването на 328p, за 4....или даже 1 серво?
С риск да стана нагъл-тука ползват същият микроконтролер за управление на 17 сервота:

. . .

Микроконтролер от рода на ATTINY85: . . ., поне според мен ще свърщи същата работа.

Разбира се, че може да стане и с други микрота, както и да се управляват повече серва. Аз използвам Pro-Mini (с 328p) в една камара устройства - DCC станция, бустер, мишка (аз му викам "тротъл"), функционални декодери, този декодер, IR приемник, кодер за RC предавател. За мен е много по-лесно да използвам един вид микроконтролер - така "темата не се разводнява" много. За мен това е лесно и удобно. Що се отнася до управлението на 4 серва, а не повече - гледам да спазвам DCC "стандарта" (на NMRA), за който документацията е достъпна.

Цитат
А има и трикове от рода на четене на вход /бутон и др./, и едновременно на същият пин да е изход за Led, сегмент или др.

Трикове има много и аз използвам някои. В случая (програмата на декодера за принадлежности) не ми се наложи да използвам тези, които описваш.


Цитат
В нета има свободни схеми и декодери за 4 стрелки, с прецизно управление на сервота под DCC протокол, при които с промяна на опеделена променлива можеш да промениш "размаха" и скоросттта на сервото, с контролери които  са светлинни години под 328.
 Малко ми изглежда като да светна  вагон с STM32.

В нета има какво ли не. Много програми са написани нескопосано и не работят много кадърно... поне за мен. Освен това предпочитам сам да си пиша програмите.

Не разбрах как 328 може да е светлинни години напред спрямо други контролери. 328 е вече доста стара технология. Ако сравняваме с микроконтролерите на базата на 8051 (моят любим микроконтролер), това е така, но аз приключих с 8051 и неговите производни преди малко повече от 20 години. Трябва да има прогрес. STM32 пък е светлинни години пред 328, така че не схванах връзката. А вагоните си ги осветявам предимно с регулатор за 3,3 волта и супер-кондензатор (някои го наричат "златен" кондензатор).


Цитат
Като цяло dcc е супер, ако го ползваш с определени софтуери за определяне на маршрути и заетост, ако ще трябва да въртиш стрелките ръчно, по-добре да се ползва друго управление-поне според мен, или както в една друга тема на "онзи с осмиците"  :)  :hi: да имаш управление на макета с "diy".
Даже аналоговото ще е по-добре, от "ръчното"dcc.

Имаше един преиод от време, когато и аз недооценявах DCC за моето "ръчно" каране. Тогава за да мога да карам повече от един влак, пусках една камара проводници от пултове за управление на участъци и стрелки. Реденето на релсите ("паркетбан") ми отнемаше ден-два, развалянето 2-3 часа (понякога и повече) и за да има смисъл от цялата галимация, трябваше да държа "паркетбана" нареден около седмица, което не се връзва с другите ми интереси. С DCC се родих! Мога да сложа на релсите толкова влака, колкото се събират на коловозите в гарата и да карам по два (понякога и по три) влака едновременно. Все още превключвах стрелките (купешки) на ръка. Но понеже купешките стрелки не позволяват някои специфични геометрии, разработих мои стрелки, които поради липса на друг механизъм за превключването им, се наложи да управлявам със серва. И за да упростя максимално свързването на сервата, както и за да захраня сърцата, реших да ги направя с индивидуални декодери. И понеже вече бях написал програмата (по поръчка на брат ми), беше логично да я използвам заедно с Про-минито за всяка една стрелка. Така единственото "свързване" става автоматично с нареждане на релсите и стрелките - не си играя да пускам каквито и да е проводници към стрелките. Ако трябваше да ги управлявам без DCC, трябва да пускам една камара проводници от съответното устройство към стрелките.

Така че въобще не съм съгласен с твърдението "Даже аналоговото ще е по-добре, от "ръчното"dcc.". Ако работи за някои - добре. За мен не е така. Причината - аз съм по-скоро колекционер на моделчета, а не моделист. Нямам намерение да правя макет - нямам мястото, нямам нервите и желанието да правя нещо такова. Обичам от време на време да си пускам моделчетата от колекцията за няколко часа и след това да прибера моделчетата и да разтуря релсите. Затова редя "паркетбан" - реди се и се разтуря за минути, особено с новите стрелки с декодерите. Иначе с часове мога да седя с поялника в ръка, да пиша програмки като тези, които съм публикувал в този форум, да проектирам печатни платки, да монтирам осветление във вагони и декодери в локомотиви, да правя звукови схеми за декодерите.

Иван

mitko0888

  • Trade Count: (35)
  • Hero Member
  • *****
  • Публикации: 2385
  • Рейтинг: 970
  • H0, еп. I, II, III, IV, София
    • Снимките ми ...
Цитат на: dred77 link=topic=5606.msg124271#msg124271 date=1675554751
да имаш управление на макета с "diy".
Абе друго си е да управляваш влака с дръжка за газ и дръжка за спирачка, а не с врътка за скоростта... Несравнимо е! И да си си го направил самичък, ако ще и да е Ардуино и ROCO LAN протокол.

dred77

  • Trade Count: (2)
  • Sr. Member
  • ****
  • Публикации: 386
  • Рейтинг: 328
Цитат на: mitko0888 link=topic=5606.msg124273#msg124273 date=1675581615
Абе друго си е да управляваш влака с дръжка за газ и дръжка за спирачка, а не с врътка за скоростта... Несравнимо е! И да си си го направил самичък, ако ще и да е Ардуино и ROCO LAN протокол.
Или Аз не се изразих правилно, или не ме разбра. Имах напредвид пулта с ключетата /не знам как се казва/, със схемата на гарата. Та когато ползваш подобно табло като твоето, е удобно. В твоят случай е най добре, понеже програмата следи за заетост и т.н след дигитализацията.
  Дори когато същото това нещо беше аналогово-със сто реленца и тем подобни, е пак по-удобно от това да направя dcc стрлки и да ги местя ръчно с "тротъла" /мауса,или андроид приложение/.
 Представете си маршрут с 5стрелки, естественно трябва да знаете адресите на всички и се почва едно по едно всички декодери да се настроят с маус, да се погледне дали правилно са превключили, или една идея по-лесно ако са вкарани в трасе на управл.програма, пак трябва за всяка една стрелка да проследиш цялото трасе преди да пуснеш влака.
 Уважавам труда на колегата Иван, но при толкова много свободни крака, няма никаква обратна връзка дали наистина се е преместила стрелката- дефектно серво, мех.повреда на стрелката, и т.н. Само като идея нещо като компаратор между сърцето и двете релси или тук във форума някой беше направил стрелката с цк ключета/не помня темата точно/, което е доста обемно ест.
 Доста понацапах, не съм искал да обиждам никой  :hi: :hi:.

IvanC

  • Trade Count: (0)
  • Sr. Member
  • ****
  • Публикации: 274
  • Рейтинг: 198
Цитат на: dred77 link=topic=5606.msg124274#msg124274 date=1675587707
. . . при толкова много свободни крака, няма никаква обратна връзка дали наистина се е преместила стрелката- дефектно серво, мех.повреда на стрелката, и т.н. Само като идея нещо като компаратор между сърцето и двете релси или тук във форума някой беше направил стрелката с цк ключета/не помня темата точно/, което е доста обемно ест.
. . .

Давай, направѝ стрелки с обратна връзка и ги покажи във форума!

Измислил съм как да направя обратна връзка за положението на стрелката, но се отказах. За мен това добавя усложнение, което в много малко случаи може да се оправдае.

Всичко, което съм публикувал във форума съм направил така, че да ми е лесно за употреба на мен самия и да задоволява нуждите ми. Добавял съм някои и други функции "по поръчка", които аз не използвам. Много неща могат да се направят, стига да има кой да ги използва. За мен направата на нещо, което няма да се използва, е безсмислено.

Иван