RailwayPassion.com - Форум за железопътен моделизъм
Електроника и Електротехника | Electronics and Electrical Engineering => Цифрово / дигитално управление | Digital Command Control => Темата е започната от: IvanC в 31 Януари 2023, 06:46:14
-
ПРЕДВАРИТЕЛНА ИНФОРМАЦИЯ
В една-две други теми споменах, че за това, което съм публикувал в този форум, идеите ("мухите") ми бяха пуснати от брат ми Владо (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 (http://www.railwaypassion.com/forums/index.php/topic,4987.msg110905.html#msg110905)
Mинаха няколко години и брат ми ми пусна новата муха - да управлява сервата за стрелките през DCC. Поразрових се и намерих две библиотеки за Ардуино за DCC контролери. С помощта на едната библиотека успях да направя първия декодер за стрелки. След няколко промени, направих "универсален" декодер - който може да използва по избор едната от двете библиотеки.
ГЛАВНАТА ИДЕЯ
В DCC, декодерите за "принадлежности" (стрелки, семафори, светофори и др. подобни) са групирани по 4 броя (адреса) на декодер. Заради това групиране, както и сметките за устройството за управление на стрелки със серва, описано по-горе, направих декодера за 4 стрелки (серва) с помощта на Ардуино - Уно, Нано или Про-мини, т.е. с процесор ATMega 328. ШИМ-управлението на сервата е част от програмата и е направено с помощта на прекъсване (Interrupt), генерирано от един от таймерите, който непрекъснато брои предварително зададен интервал от време, равен на продължителността на необходимия импулс или паузата между импулсите. Дотук това е без разлика спрямо програмата за управление на сервата с бутони. Разликата между двете програми е в това, че при декодера, превключването на сервата става от DCC импулсите, предавани към декодера, вместо от бутони. Това предаване на DCC импулсите може да стане по няколко начина - от станцията или от бустер през релсите или пак от станцията или бустер през отделни проводници, захранващи само декодерите за принадлежности - по избор от потребителя със съответното опроводяване към декодера. Последното (опроводяването) не е съществено в случая.
СХЕМА НА ДЕКОДЕРА
(https://cankov.com/modelrailroad/dcc/accdecoder/dccacc.png)
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.
Иван
-
ПРОГРАМА
Заради ограничението на форума от 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
Продължението следва...
Иван
-
ПРОГРАМА, 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()
Продължението следва...
Иван
-
ПРОГРАМА, 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
Продължението следва...
Иван
-
ПРОГРАМА, 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()
Иван
-
ПРОГРАМА - БИБЛИОТЕКИ
Програмата използва следните библиотеки:
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 (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.
Останалите части от програмата не трябва да се променят, освен ако някой реши да експериментира с промяна на самата програма. Програмата е напълно функционална в този си вид и няма нужда да бъде променяна. Има едно изключение (за един параметър), което ще опиша по-нататък, когато стигна до управлението на релета за превключване на сърцата на стрелките.
Иван
-
РЕЛЕТА
Стигнах до добавянето на релета към декодера. Релетата могат да се използват за захранване на сърцата на стрелките или ако използваме декодера да включваме други принадлежности, това става с контактите на релетата. Разбира се ако консуматорите са в рамките на допустимите напрежение (5 волта) и ток (максимум 20 мА) на изход на Ардуиното, това може да стане и без релетата, като свържем товара между изход за светодиод и масата на декодера.
Схемата на декодера с релетата:
(https://cankov.com/modelrailroad/dcc/accdecoder/dccaccr.png)
Така начертана, схемата е за управление на сърца на стрелки. Ако трябва да включваме или превключваме друг вид верига, нормално отвореният (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 мА. Дейташийтите на релетата дават тези параметри.
Иван
-
На основата на декодера, описан по-горе, направих DCC декодер за стрелки тип "паркетбан". Стрелките, на които монтирах декодерите, са моя разработка, "произведени" от брат ми Владо:
(https://cankov.com/modelrailroad/switchesvc/swvcic01.jpg)
Схемата на декодера е много подобна на тази, описана по-горе, но само с едно реле и изходи само за едно серво:
(https://cankov.com/modelrailroad/dcc/accdecoder/swdccdec.png)
Иван
-
Проблемът при управлението на "паркетбан" стрелки със серва е, че сервата и управлението им не могат да се монтират "под плота". Декодерът за 4 серва може да се сложи някъде встрани от релсите, но тогава възниква проблемът за опроводяването - 3 проводника към серво и евентуално още 3 за захранване на сърцето на една стрелка. Част от проблема е прекарването на проводниците под релсите, за свързване на стрелки на вътрешни коловози и то така, че проводниците да не надигат релсите. Затова реших да разработя декодера така, че да се монтира за постоянно на една стрелка и следователно да не се налага свързване и разкачане на връзките към декодера при построяване и разглобяване на "паркетбана".
И така, всяка стрелка си има собствен декодер, който е монтиран и свързан към стрелката. Захранването на декодера се взема от двете релси. И тук DCC сигналът се изправя от Греца и се подава на входа на регулатора за 5 волта. За регулатор на напрежение използвам импулсен такъв с MP2307, изграден като модул. Модулът е с малки размери и не се нуждае от радиатор, което е важно при това разположение на декодера.
Преди да разработя платката потърсих подходящи релета с бобинки за 5 волта, ток на контактите 2 А и минимални размери. Намери тези, които са на снимките (в зелен корпус) и разработих платката за тях. Бобинката на релето черпи около 90 мА ток при 5 волта. Тъй като регулаторът на напрежение е импулсен, общият ток на декодера, релето и сервото в покой, който те черпят от станцията/бустера, е около 40 мА при включено реле и около 10 мА при изключено реле, при напрежение на станцията/бустера около 16 волта.
(https://cankov.com/modelrailroad/switchesvc/swvcic03.jpg)
(https://cankov.com/modelrailroad/switchesvc/swvcic04.jpg)
(https://cankov.com/modelrailroad/switchesvc/swvcic05.jpg)
(https://cankov.com/modelrailroad/switchesvc/swvcic07.jpg)
Иван
-
Програмата в Ардуиното е същата, като публикуваната по-горе. За да не превключва релето няколкократно, докато стрелката преминава от направо в отклонение, както вече писах по-горе, промених стойността на константата 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, но свършиха там, откъдето ги взех. Търсенето на други такива не се увенча с успех. Поразрових се повече и намерих релета с още по-малки размери, за които се наложи да променя платката:
(https://cankov.com/modelrailroad/dcc/accdecoder/swdccdect.png)
(https://cankov.com/modelrailroad/dcc/accdecoder/swdccdecb.png)
XML (eagle) (https://cankov.com/modelrailroad/dcc/accdecoder/swdccdec.brd)
PDF (https://cankov.com/modelrailroad/dcc/accdecoder/swdccdec.pdf)
Няколко производителя правят релета, които могат да се монтират на платката:
Omron G5V-1-T90 DC5
Fujitsu SY-4.5-K или SY-5-K
Panasonic HY1-5V
NAIS HD1E-M-DC5V
Иван
-
С риск да пооцапам /предв. се извинявам за което/, имам няколко въпроса.
Първо много хубава, нагледна и добре обяснена тема, много добре представяни и подкрепяни със снимки неща.
Второ какъв е смисълът за ползването на 328p, за 4....или даже 1 серво?
С риск да стана нагъл-тука ползват същият микроконтролер за управление на 17 сервота:
(https://d28lcup14p4e72.cloudfront.net/259338/7399815/derSmall.jpg)
https://forum.mrhmag.com/post/sma12-17-channel-configurable-multifunction-5-dcc-decoder-for-servos-12198051 (https://forum.mrhmag.com/post/sma12-17-channel-configurable-multifunction-5-dcc-decoder-for-servos-12198051).
Микроконтролер от рода на ATTINY85:ТУК (https://www.aliexpress.com/item/32584084654.html?spm=a2g0o.productlist.main.5.423f7a487DtiBD&algo_pvid=9fece85e-59c4-40ba-994f-c8e841995bf1&algo_exp_id=9fece85e-59c4-40ba-994f-c8e841995bf1-2&pdp_ext_f=%7B%22sku_id%22%3A%2267323374940%22%7D&pdp_npi=2%40dis%21USD%213.0%213.0%21%21%21%21%21%40211be54b16755504789683010d071d%2167323374940%21sea&curPageLogUid=l8sd3OOItzf8), поне според мен ще свърщи същата работа.
А има и трикове от рода на четене на вход /бутон и др./, и едновременно на същият пин да е изход за Led, сегмент или др.
В нета има свободни схеми и декодери за 4 стрелки, с прецизно управление на сервота под DCC протокол, при които с промяна на опеделена променлива можеш да промениш "размаха" и скоросттта на сервото, с контролери които са светлинни години под 328.
Малко ми изглежда като да светна вагон с STM32.
Като цяло dcc е супер, ако го ползваш с определени софтуери за определяне на маршрути и заетост, ако ще трябва да въртиш стрелките ръчно, по-добре да се ползва друго управление-поне според мен, или както в една друга тема на "онзи с осмиците" :) :hi: да имаш управление на макета с "diy".
Даже аналоговото ще е по-добре, от "ръчното"dcc.
-
С риск да пооцапам /предв. се извинявам за което/, имам няколко въпроса.
Я да видим, дали ще мога да отговоря на въпросите.
Първо много хубава, нагледна и добре обяснена тема, много добре представяни и подкрепяни със снимки неща.
Благодаря за коментара! И понеже не намирам въпрос, нямам и отговор.
Второ какъв е смисълът за ползването на 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.". Ако работи за някои - добре. За мен не е така. Причината - аз съм по-скоро колекционер на моделчета, а не моделист. Нямам намерение да правя макет - нямам мястото, нямам нервите и желанието да правя нещо такова. Обичам от време на време да си пускам моделчетата от колекцията за няколко часа и след това да прибера моделчетата и да разтуря релсите. Затова редя "паркетбан" - реди се и се разтуря за минути, особено с новите стрелки с декодерите. Иначе с часове мога да седя с поялника в ръка, да пиша програмки като тези, които съм публикувал в този форум, да проектирам печатни платки, да монтирам осветление във вагони и декодери в локомотиви, да правя звукови схеми за декодерите.
Иван
-
да имаш управление на макета с "diy".
Абе друго си е да управляваш влака с дръжка за газ и дръжка за спирачка, а не с врътка за скоростта... Несравнимо е! И да си си го направил самичък, ако ще и да е Ардуино и ROCO LAN протокол.
-
Абе друго си е да управляваш влака с дръжка за газ и дръжка за спирачка, а не с врътка за скоростта... Несравнимо е! И да си си го направил самичък, ако ще и да е Ардуино и ROCO LAN протокол.
Или Аз не се изразих правилно, или не ме разбра. Имах напредвид пулта с ключетата /не знам как се казва/, със схемата на гарата. Та когато ползваш подобно табло като твоето, е удобно. В твоят случай е най добре, понеже програмата следи за заетост и т.н след дигитализацията.
Дори когато същото това нещо беше аналогово-със сто реленца и тем подобни, е пак по-удобно от това да направя dcc стрлки и да ги местя ръчно с "тротъла" /мауса,или андроид приложение/.
Представете си маршрут с 5стрелки, естественно трябва да знаете адресите на всички и се почва едно по едно всички декодери да се настроят с маус, да се погледне дали правилно са превключили, или една идея по-лесно ако са вкарани в трасе на управл.програма, пак трябва за всяка една стрелка да проследиш цялото трасе преди да пуснеш влака.
Уважавам труда на колегата Иван, но при толкова много свободни крака, няма никаква обратна връзка дали наистина се е преместила стрелката- дефектно серво, мех.повреда на стрелката, и т.н. Само като идея нещо като компаратор между сърцето и двете релси или тук във форума някой беше направил стрелката с цк ключета/не помня темата точно/, което е доста обемно ест.
Доста понацапах, не съм искал да обиждам никой :hi: :hi:.
-
. . . при толкова много свободни крака, няма никаква обратна връзка дали наистина се е преместила стрелката- дефектно серво, мех.повреда на стрелката, и т.н. Само като идея нещо като компаратор между сърцето и двете релси или тук във форума някой беше направил стрелката с цк ключета/не помня темата точно/, което е доста обемно ест.
. . .
Давай, направѝ стрелки с обратна връзка и ги покажи във форума!
Измислил съм как да направя обратна връзка за положението на стрелката, но се отказах. За мен това добавя усложнение, което в много малко случаи може да се оправдае.
Всичко, което съм публикувал във форума съм направил така, че да ми е лесно за употреба на мен самия и да задоволява нуждите ми. Добавял съм някои и други функции "по поръчка", които аз не използвам. Много неща могат да се направят, стига да има кой да ги използва. За мен направата на нещо, което няма да се използва, е безсмислено.
Иван
-
За оратна връзка използвам микроключета, нещо като тези (https://vikiwat.com/product/21813/mini-mikrek-1a-250vac-s-lost.html):
(https://vikiwat.com/userfiles/productimages/20406/product_large_120556.webp)
само че на цена от 30 стотинки за брой. Даже преди години сложих няколко на машинки от Конрад, вместо техните контакти, които правят проблем.
Ключетата са две, едното в крайно ляво и другото - в крайно дясно положение. След тях е само телчето за задвижване на езика, то е 99.99999 % надеждно. Следва "траверсата", която мести езиците. Тя също не може да се повреди. Остава да се счупи спойката между тази траверса и някой от езиците. Тогава моторът спира, а езикът е останал на милиметър от мястото си ... Което се е случвало няколко пъти за тези десетина години, на две от стрелките не бях напарви както трябва тази връзка.
Тези ключета казват на Ардуиното да ми спре стъпковите мотори. Едновременно това включва релетата за сърцето. А пък на пулта лампичките мигат докато стрелката не е в положение, зададено от командата, т.е. когато стрелката не е заключена в правилно положение. Така моите стрелки имат четири положения, а не две: ляво, дясно, движи се, не е включена в маршрут.
Иван, за твоите серво моторчета това естествено въобще не е нужно, след като си настроиш един път ъглите на отклонение.
-
От името на кибиците - много мерси за интересната тема и дискусия! Амбицира ме и мен да пусна една тема как управлявам аналоговото си трасе с компютри, но това по-нататък.
А сега няколко коментара за някои от техническите решения, и как всичко наистина зависи перспективата и нуждите на автора.
- DCC за стрелките защото мрежата от жици повдига трасето. Аз се чудих за същото, но при мен това решение беше изключено, аз нямам и няма да имам декодери, така че сложих трасето на мека настилка, както се вижда на клипчетата ми. Оказа се много удобно, освен че скрива жиците, настилката крепи трасето с винтчета които се забиват много лесно а пък го стабилизират напълно. И цялото трасе се мести ако трябва.
- излишно големи ресурси за просто управление. Това би било валиден упрек за комерсиално приложение, но тук сме хобити. Важно е какво е удобно и приятно за програмиране и свързване и с какво автора има опит. Щом Иван се чувства at home с Ардуините които ползва, more power to him. Аз използвам едни други, Seeduino XIAO, ама то понеже ги избирах сега наскоро. Когато заради пандемията заработихме от къщи и след 30 години съхранение извадих влакчетата от шкафа и реших да ги управлявам с компютърчета. И моята перспектива като ги избирах беше да са достатъчно мощни да поддържат RTOS за да мога да си програмирам в мултитаскинг среда както съм свикнал, и да са лесни за препрограмиране. И ги използвам засега именно тях навсякъде, въпреки че има други, като например RaspberryPi Pico, хем по-мощно хем с повече I/O, хем евтино. Ако бях започнал по-рано, може би щях да избера същите Ардуини като Иван и да ги използвам по същия начин навсякъде.
- имплементирай само от каквото имаш нужда и използваш за момента. Това е златно правило. Ако има нужда от нещо повече по-нататък, ще се добави. Единственото което трябва да се внимава когато се прави това което ще работи и ще се използва, е да се предвиди възможността да се добавят лесно нови функции. Например обратна връзка за положението на стрелките и аз не съм сложил, макар ГДР Пико стрелките да са по-ненадеждни от новите. Но аз ги тествам на стендче преди да ги включа в трасето и това е достатъчно засега. Но! ако реша все пак да добавя такава обратна връзка, просто ще навържа още няколко модула с ардуинчета които да четат положението на стрелките. И ще пусна още жици под меката настилка, няма как. :dirol:
Айде стига за днес, лека. Over and out.
-
Два влака през стрелките Made in VladoC - първият се тегли от 06 001 на Brawa (0632):
https://youtu.be/TxA-1dpjs1s
Начело на втория влак е 02 006 пак на Brawa (40962):
https://youtu.be/QoZhxqWZ25Y
Иван
-
Много красиво! Особено втория лок, по ме кефи!
Кои серво ползваш - S90? И платките ми харесват - семпли!
:clapping: :good:
Пп. Радвам се, че все още слушаш касети на SONY -то!!! :hi:
И аз си ги пазя , също със SONY! :dance1:
-
Много красиво! Особено втория лок, по ме кефи!
Кои серво ползваш - S90? И платките ми харесват - семпли!
:clapping: :good:
Пп. Радвам се, че все още слушаш касети на SONY -то!!! :hi:
И аз си ги пазя , също със SONY! :dance1:
S90 са огромни в HO. Тези серва са някаква куча марка, които намерих на Амазон, но са най-малките, които успях да намеря на Нета. Засега се държат много прилично - не съм имал никакви проблеми с тях.
Сложих на Тубата още няколко клипа с парни локомотиви (https://www.youtube.com/@ican7475/videos), за които имаше съмнение, че може да не преминават през стрелките без да дерайлират. Единственият локомотив, чийто тендер понякога подскача на стрелките е 06 001 на Брава. При него колоосите на задната талига на локомотива и всичките на тендера са направени от колела с "полуоси", нанизани на тънкостенна тръбичка - подобни на тези:
(https://www.modellbahnshop-lippe.com/article_data/images/1/22580_e.jpg)
Тръбичките са се спукали (локомотивът е доста стар) и колелата много лесно си променят разстоянието между ребордите, като това става и в движение. Някой ден, като ми дойде музата, ще се хвана да им сменя пластмасовите тръбички.
Иван
-
Пълнител за химикалка - обикновенно пасват идеално,
особено когато колоосите са били Мерклински!