Електроника и Електротехника | Electronics and Electrical Engineering > Цифрово / дигитално управление | Digital Command Control
DCC декодер за стрелки и други принадлежности по макетите
IvanC:
ПРЕДВАРИТЕЛНА ИНФОРМАЦИЯ
В една-две други теми споменах, че за това, което съм публикувал в този форум, идеите ("мухите") ми бяха пуснати от брат ми Владо (ValdoC). Съвсем невинно, той би ми подхвърлил:
"Я виж тази препратка и кажи какво трябва да направя, за да получа този резултат."
или
"Взех това (устройство/макет/и т.н.) и искам до мога да го управлявам по този-и-този-начин. Как мога да го направя?"
С това в главата ми се задейства "машинката", която води до някои "неочаквани" (дори и за мен) идеи и резултати ;D. В случая, това започна с направата от брат ми на самоделни стрелки, които той искаше да управлява по някакъв начин с бутони и чрез серва. Бидейки корабо- и авиомоделисти, ние използвахме серва за управление на почти всичко по корабо- и авиомоделите - рул, газ и въздух (на двигателя с вътрешно горене), елерони, хоризонтални и вертикални подвижни повърхности, клапи и т.н. Та поради това имахме натрупен опит със серво-машинки, както и някакъв запас от такива и като естествено продължение в ж.п. моделизма, искахме да ги използваме за превключване на езиците на стрелките.
Към времето, когато брат ми ме попита за управление на серва за превключване на стрелките, аз вече бях разработил и направил няколко PPM декодера за предаватели за радиоуправляеми модели, които използвах за самолети. Тук също има малко предистория. Накратко тя се състои в това, че реших да използвам Ардуино за целта и по-специално първоначално се спрях на вече готовата библиотека за Ардуино за управление на серва. Тази библиотека не работеше много успешно, когато трябваше да бъде приложена за 8 серва, заради което реших да се пробвам да напиша цялата програма, с директно управление от един от таймерите на ATMega386. Няколко проби по-късно успях да подкарам PPM да работи успешно, както и да подкарам управлението на 8 серва.
Така стигам до момента, в който брат ми искаше да управлява 1-2 серва, с които да превключва стрелките. Той дори ми изпрати линк към нещо подобно, направено с Ардуино, кодето използваше стандартната библиотека, която споменах по-горе и не ми хареса как работеше. След няколко сметки за броя необходими входове (за бутони) и изходи (за ШИМ към сервата и светодиоди за индициране на състоянието на сервата), прецених че едно Ардуино Уно/Нано/Про-мини (всичките на базата на ATMega328) може спокойно да управлява до 4 серва и направих програма за управление на тези 4 серва от 4 бутона (по един на серво) и индикация с 8 светодиода (по два на серво, един за всяко състояние на сервото). Брат ми публикува тази програма (с помощта на Павел - pavel75) в темата "Arduino за стрелки":
http://www.railwaypassion.com/forums/index.php/topic,4987.msg110905.html#msg110905
Mинаха няколко години и брат ми ми пусна новата муха - да управлява сервата за стрелките през DCC. Поразрових се и намерих две библиотеки за Ардуино за DCC контролери. С помощта на едната библиотека успях да направя първия декодер за стрелки. След няколко промени, направих "универсален" декодер - който може да използва по избор едната от двете библиотеки.
ГЛАВНАТА ИДЕЯ
В DCC, декодерите за "принадлежности" (стрелки, семафори, светофори и др. подобни) са групирани по 4 броя (адреса) на декодер. Заради това групиране, както и сметките за устройството за управление на стрелки със серва, описано по-горе, направих декодера за 4 стрелки (серва) с помощта на Ардуино - Уно, Нано или Про-мини, т.е. с процесор ATMega 328. ШИМ-управлението на сервата е част от програмата и е направено с помощта на прекъсване (Interrupt), генерирано от един от таймерите, който непрекъснато брои предварително зададен интервал от време, равен на продължителността на необходимия импулс или паузата между импулсите. Дотук това е без разлика спрямо програмата за управление на сервата с бутони. Разликата между двете програми е в това, че при декодера, превключването на сервата става от DCC импулсите, предавани към декодера, вместо от бутони. Това предаване на DCC импулсите може да стане по няколко начина - от станцията или от бустер през релсите или пак от станцията или бустер през отделни проводници, захранващи само декодерите за принадлежности - по избор от потребителя със съответното опроводяване към декодера. Последното (опроводяването) не е съществено в случая.
СХЕМА НА ДЕКОДЕРА
DCC сигналът се подава на куплунга DCC. Сигналът се изправя от диодите D1 до D4, филтрира от кондензатора C1 и напрежението се регулира на 5 волта от регулатора IC1. На схемата съм посочил LM7805 линеен регулатор, но може да се използва и друг вид регулатор на 5 волта, включително и импулсен регулатор. Импулсните регулатори са със значително по-голяма ефективност (КПД) от линейните регулатори, което води до значително по-малко топлоотделяне и съответно многократно по-малки загуби от това топлоотделяне.
След регулатора, напрежението, стабилизирано на 5 волта, се филтрира от кондензаторите C2 и C3 и захранва Ардуиното и сервата S1 до S4.
От DCC входа, сигналът се подава през съпротивлението R1 към извод D2 (2) на Ардуиното. Тъй като входовете на Ардуиното трябва да са ограничени в диапазона от 0 волта до +5 волта, двойният диод D5 се грижи да ограничи входното напрежение на този извод в желаните граници.
Изходите D3 (3), D5 (5), D6 (6) и D9 (9) на Ардуиното подават сигналите (ШИМ) към сервата, а D10 (10) до A3 (17) управляват светодиодите D11 до D42, които индицират състоянието на сервата. Изходите D10 (10) до A3 (17) могат също така да управляват и релета (през буферни транзистори), които да превключват сърцата на стрелките към съответната релса. За това (управлението на релета за сърцата) - малко по-нататък.
Това, кои изводи на Ардуиното се използват за DCC входа и за изходи към сервата и светодиодите, се задава в програмата за Ардуиното и може да се променя в някои допустими граници. Например за вход от Ардуиното може да се използват само изводите D2 (2) и D3 (3), а за изходите за сервата и светодиодите могат да се използват изводите от D2 (2) до A5 (19), без да има дублиране с входа за DCC.
Иван
IvanC:
ПРОГРАМА
Заради ограничението на форума от 20000 символа на мнение, ще пусна програмката в няколко последователни мнения.
--- Код: ---
// ---------------------------------- Задаване функциите на изводите: ----------------------------------
const int dcc_pin = 2; // Вход за DCC сигнала; стойност 2 или 3;
// да не се използват други стойности!!!
// {серво 1} {серво 2} {серво 3} {серво 4}
const int servo_pin[] = { 3 , 5 , 6 , 9 }; // Изводи за управление на сервата.
const int LED_pin[][2] = {{10, 11}, {12, 13}, {14, 15}, {16, 17}}; // Изводи за светодиодите:
// първата позиция е за главно положение,
// втората позиция е за отклонено положение.
// -------------------------------- Константи за формиране на импулсите: -------------------------------
#define pwmFrame 20000 // Период на ШИМ изходите в микросекунди (µs).
#define pwmLo 900 // Продължителност на "късия" импулс.
#define pwmHi 2100 // Продължителност на "дългия" импулс.
#define pwmOffDel 2000 // Време преди изключване на ШИМ в милисекунди (ms).
// -------------------------------------- Свързване на светодиод: --------------------------------------
//
// D2 до D13, A0 до A5 ───┐
// (2 до 19) │
// ┌┴┐
// │ │ 220Ω ┌ според светодиода и
// │ │ до ┤ желаната интензивност
// │ │ 470Ω └ на светене
// └┬┘
// │
// │
// ─┴─
// \ / ─→
// V ─→
// ─┬─
// │
// │
// ─┴─
//
// Изводите в програмата са означени от 2 до 19, където:
// извод 2 отговаря на D2;
// извод 3 отговаря на D3;
// и т.н. до
// извод 13 отговаря на D13;
// извод 14 отговаря на A0;
// извод 15 отговаря на A1;
// и т.н. до
// извод 19 отговаря на A5;
//
// Всички изводи от 2 до 19 могат да се използват като цифрови входове и изходи, а от 14 до 19 (A0 до
// A5) и като аналогови входове. A6 и A7 могат да се използват САМО като аналогови входове.
// -------------------------- Команди за управление през серийния интерфейс: ---------------------------
//
// 1. Настройки на серийния монитор:
//
// 1.1. В полето за задаване на скоростта на интерфейса (долу в дясно) се избира "115200 baud" или
// стойността, зададена на константата baudRate по-долу, ако е различна.
//
// 1.2. В ляво от полето за задаване на скоростта на интерфейса се избира "Both NL & CR".
//
// 2. Командите се въвеждат в полето горе, отляво на бутона "Send". Всички команди се изпращат при
// натискането на "Enter" на клавиатурата или на бутона "Send" горе в дясно на екрана на серийния
// монитор. Декодерът отговаря по серийния интерфейс със съответния статус. Командите са:
//
// 2.1. Празна команда или команда "0": декодерът отговаря с текущите настройки и състояния на
// изходите.
//
// 2.2. Команда от "1" до "4": декодерът подава команда за промяна на състоянието на изхода на
// съответното серво, т.е. превключва сервото в другото състояние. Декодерът отговаря с
// информация за новото състояние на съответния изход.
//
// 2.3. Команда "X Y", с ЕДНА и САМО ЕДНА празна позиция между стойностите на "X" и "Y": указване на
// новата крайна позиция "Y" на серво на изход "Х" за текущото му състояние; вместо "X" се
// въвежда номер на желания изход на серво от "1" до "4", а вместо "Y" се въвежда желаната
// стойност за новото крайно положение от "0" до "100", например "3 27" означава серво 3,
// положение 27. При въведени валидни стойности, сервото веднага се завърта до новата позиция и
// декодерът отговаря на екрана на серийния монитор с потвърждение за новото крайно положение.
// Ако въведените стойности за "X" и "Y" са извън посочените граници, декодерът връща съобщение
// за неправилни данни ("INVALID!") на екрана на серийния монитор.
// Тази команда задава новото положение за текущото състояние на сервото. Командата може да се
// въвежда многократно с различни стойности на "Y" за прецизна настройка на крайното положение
// на сервото. За да се въведе ново положение за другото състояние на сервото, то трябва да
// бъде превключено или с DCC команда или с команда през серийния интерфейс, както това е
// описано в 2.2 по-горе.
//
// 2.4. Команда "XsY", с малка латинска буква "s" между стойностите на "X" и "Y": указване на новата
// скорост "Y" на серво на изход "X"; вместо "X" се въвежда номер на желания изход на серво от
// "1" до "4", а вместо "Y" се въвежда желаната стойност на времето за превключване на сервото
// от едната крайна позиция в другата в милисекунди (ms), в диапазона от 100 (0,1 секунди) до
// 5000 (5 секунди). Например "2s1100" означава серво 2, скорост 1100 (1,1 секунди време за
// превключване). При въведени валидни стойности, декодерът отговаря на екрана на серийния
// монитор с потвърждение за новата скорост.
// Ако въведените стойности за "X" и "Y" са извън посочените граници, декодерът връща съобщение
// за неправилни данни ("INVALID!") на екрана на серийния монитор.
//
// 2.5. Команда "aX", с малка латинска буква "a" последвана от стойност "X": задаване на нов адрес
// на декодера; вместо "X" се въвежда номер на желания адрес на декодера от "1" до "2044".
// Въведеният адрес задава група от 4 последователни адреса на декодера, както е регламентирано
// от DCC стандарта При въведена валидна стойност на адреса, декодерът отговаря на екрана на
// серийния интерфейс с потвърждение за новия базов адрес. Базовият адрес на декодера е първият
// от групата от 4 адреса - 1, 5, 9, 13 и т.н.
// Ако въведената стойност за "X" е извън посочените граници, декодерът връща съобщение за
// неправилни данни ("INVALID!") на екрана на серийния монитор.
//
// 2.6. Команда "d11", с малка латинска буква "d" последвана от числото 11: връщане на заводските
// настройки на декодера.
//
// 2.7. Ако се въведе команда, която не отговаря на командите описани по-горе, декодерът връща
// съобщение за неправилни данни ("INVALID!") на екрана на серийния монитор.
//
// -----------------------------------------------------------------------------------------------------
// CCCC VV VV
// CC CC VV VV ТТТТТТ АААА
// CC VV VV ----- ТТ АА
// CC VV VV ----- ТТ ААААА
// CC CC VVV ТТ АА АА
// CCCC V ТТ АААА А
// Работят САМО с библиотека NmraDCC!
// ВНИМАНИЕ!!! Програмиране на работния/главния участък НЕ Е ВЪЗМОЖНО. Програмирай само на служебния/програмиращ участък!
// ----------------------------- CV-та за идентификатори и връщане на заводските настройки -----------------------------
// cvMfr 8 // Идентификатор на производителя - само за четене! [13]
// cvVer 7 // Вид и версия на декодера - само за четене! [71]
// cvSub 10 // Версия на програмата на декодера - само за четене! [1]
// cvDef 11 // Връщане на заводските настройки при запис на стойност 11 в това CV [0]
// --------------------------------------- address and decoder configuration CVs ---------------------------------------
// cvAddressLo 1 // Адрес на декодера - младша част [101]
// cvAddressHi 9 // Адрес на декодера - старша част [0]
// --------------------------------------- CV-та за настройка на ШИМ на сервата ----------------------------------------
// cvPWMframe 21 // Период на ШИМ в 0.2 ms; стойности от 75 до 125 [100]
// cvPWMlo 22 // Продължителност на импулса за 0% отклонение на сервото в 0.01 ms; 48 до cvPWMhi-1 [90]
// cvPWMhi 23 // Продължителност на импулса за 100% отклонение в 0.01 ms; cvPWMlo+1 до 248 [210]
// cvPWMoffDelay 24 // Време преди изключване на ШИМ в 0.1 s; стойности от 0 до 255 [20]
// ------------------------------------------ CV-та за управление на сервата -------------------------------------------
// cvServoAngle 31 // Ъгли на завъртане на сервата в диапазона от 0 до 100; използват се 8 CV-та:
// CV31: Серво 1 в ПРАВА (MAIN) [40]
// CV32: Серво 1 в ОТКЛОНЕНИЕ (SIDING) [60]
// - - - - - - - - - - - - - - - ---
// CV37: Серво 4 в ПРАВА (MAIN) [40]
// CV38: Серво 4 в ОТКЛОНЕНИЕ (SIDING) [60]
// cvServoSpeed 51 // Скорост на сервата в 0.1 s; стойности от 1 до 50; използват се 4 CV-та:
// CV51: Скорост на серво 1 [18]
// - - - - - - - - - - ---
// CV54: Скорост на серво 4 [18]
// cvServoImmediate 61 // Превключване на серво при запис на стойност, различна от 0; използват се 4 CV-та:
// CV61: Превключване на серво 1 [0]
// - - - - - - - - - - - - - - - - ---
// CV64: Превключване на серво 4 [0]
// ------------------------------------- CV-та за запомняне състоянията на сервата -------------------------------------
// cvMem 204 // Памет за състоянията на сервата; използват се 4 CV-та; само за програмата!
// ---------------------------------------------------- Други CV-та ----------------------------------------------------
// cvDump 256 // Стойностите на всички CV-та се изписват на екрана на серийния монитор при запис на стойност, различна от 0
// CCCC VV VV
// CC CC VV VV SSS
// CC VV VV SS
// CC VV VV SSS
// CC CC VVV SS
// CCCC V SSS
// Available ONLY with the NmraDcc library!
// CAUTION!!! Programming on the main DOES NOT WORK. Program ONLY on the service (programming) track!
// -------------------------------------------- ID and reset to defaults CVs -------------------------------------------
#define cvMfr 8 // manufacturer ID [13]
#define cvVer 7 // decoder type and code [71]
#define cvSub 10 // decoder software version [1]
#define cvDef 11 // reset to defaults: write 11 to reset all CVs to defaults [0]
// --------------------------------------- address and decoder configuration CVs ---------------------------------------
#define cvAddressLo 1 // decoder address low byte [1]
#define cvAddressHi 9 // decoder address high byte [0]
// --------------------------------------------------- servo PWM CVs ---------------------------------------------------
#define cvPWMframe 21 // PWM frame in 0.2ms; *200 to get pwmFrame [100]
#define cvPWMlo 22 // pulse length for 0% servo rotation in 0.01ms; *10 to get pwmLo [90]
#define cvPWMhi 23 // pulse length for 100% servo rotation in 0.01ms; *10 to get pwmHi [210]
#define cvPWMoffDelay 24 // delay before turning off PWM in 0.1s; *100 to get pwmOffDelay [20]
// ------------------------------------------------- servo control CVs -------------------------------------------------
#define cvServoAngle 31 // servo angles between 0 and 100; uses 8 bytes:
// CV31: servo 1 MAIN angle [40]
// CV32: servo 1 SIDING angle [60]
// - - - - - - - - - - - - - - - ---
// CV37: servo 4 MAIN angle [40]
// CV38: servo 4 SIDING angle [60]
#define cvServoSpeed 51 // servo speed in 0.1s; uses 4 bytes:
// CV51: servo 1 speed [18]
// - - - - - - - - - - ---
// CV54: servo 4 speed [18]
#define cvServoImmediate 61 // servo immediate control; uses 4 bytes:
// CV61: servo 1 immediate control [0]
// - - - - - - - - - - - - - - - - ---
// CV64: servo 4 immediate control [0]
// ---------------------------------------------- servo output memory CVs ----------------------------------------------
#define cvMem 204 // servo output state storage; uses 4 bytes
// ----------------------------------------------------- other CVs -----------------------------------------------------
#define cvDump 256 // write any non-zero value to this CV to dump CV values to serial
--- Край на кода ---
Продължението следва...
Иван
IvanC:
ПРОГРАМА, II-ра част
--- Код: ---
// ------------------------------- Указатели за дебъгване на програмата: -------------------------------
//#define debug_acc_turnout // Генерира код за дебъгване ако се махнат двете наклонени черти в началото на реда;
//#define debug_cvchange // нужно е само на автора на програмата.
//#define debug_i_read
//#define debug_i_save
//#define debug_setup
//#define debug_servo_pwm
#define debug_defaults
#define dump_CVs_enabled // uncomment to enable CV change readback and CV dump to serial
#if defined(debug_acc_turnout) or defined(debug_cvchange) or defined(debug_i_read) or defined(debug_i_save) or defined(debug_setup) or defined(debug_servo_pwm) or defined(debug_defaults) or defined(dump_CVs_enabled)
#define debug_main
#endif
// ------------------------------ Указател за използвана DCC библиотека: -------------------------------
#define use_NmraDcc // Comment to use DCC_Decoder.
#ifdef use_NmraDcc // Необходими библиотеки:
#include <NmraDcc.h> // Библиотека за DCC декодер. Поддържа CV-та.
NmraDcc Dcc; // Обявяване на променлива за работа с библиотеката за DCC декодер.
#else
#include <EEPROM.h> // Библиотека за работа с EEPROM.
#include <DCC_Decoder.h> // Библиотека за DCC декодер. НЕ ПОДДЪРЖА CV-та!
#undef debug_cvchange
#undef dump_CVs_enabled
#endif
// ----------------------------- Идентификатори на декодера и програмата: ------------------------------
#define defaultAddress 1 // Заводски адрес на декодера.
#define mfr_id 13 // Идентификатор на декодера.
#define mfr_ver 71 // Идентификатор на програмата.
#define mfr_sub 1 // Версия на програмата.
// ---------------- Константи за нормалната работа на програмата - да не се променят!!! ----------------
#define baudRate 115200 // Скорост на серийния интерфейс.
#define serialTimeout 14e6 / baudRate // Таймаут на серийния интерфейс в микросекунди (µs); Да не се променя!!!.
#define aHiMask 0x07 // Максимална стойност (маска) на старшата част на адреса.
#define pwmFrameMin 15000 // Минимален период на ШИМ изходите в микросекунди (µs).
#define pwmFrameMax 25000 // Максимален период на ШИМ изходите в микросекунди (µs).
#define pwmFrameDiv 200 // Делител на периода на ШИМ.
#define pwmMin 480 // Минимална продължителност на ШИМ импулс в микросекунди (µs).
#define pwmMax 2480 // Минимална продължителност на ШИМ импулс в микросекунди (µs).
#define pwmDiv 10 // Делител на продължителността на ШИМ импулс.
#define pwmOffDelDiv 100 // Делител на времетраенето преди изключване на ШИМ.
#define angleMin 0 // Минимален ъгъл на отклонение на серво.
#define angleMax 100 // Максимален ъгъл на отклонение на серво.
#define angleLoDef 40 // Заводски ъгъл на отклонение на серво в позиция 1.
#define angleHiDef 60 // Заводски ъгъл на отклонение на серво в позиция 2.
#define speedMin 100 // Минимална скорост на серво в ms.
#define speedMax 5000 // Максимална скорост на серво в ms.
#define speedDiv 100 // Делител на скоростта на серво.
#define speedDef 1800 // Заводска скорост на серво от едната крайна позиция до
// другата в ms; стойности от 100 до 5000 (0,1 - 4,8 s)
#define cCR 13 // Символ CR.
#define cLF 10 // Символ LF.
#define cDelimiter ' ' // Разделител за въвеждане на данни.
const String cStr[] = {" MAIN", " SIDING"};
const int channels = sizeof(servo_pin) / sizeof(int);
const int LED_flasher = 10; // LED flash length: 10 for moderate flashing, 1000 for no flashing.
#define calcAddressLo(x) (x & 0xFF)
#define calcAddressHi(x) ((x >> 8) & aHiMask)
struct cvPair {int cv; byte value;};
cvPair factoryDefaults[] = { // default CV values table
{cvAddressLo, calcAddressLo(defaultAddress)}, {cvAddressHi, calcAddressHi(defaultAddress)},
{cvSub, mfr_sub}, {cvDef, 0},
{cvPWMframe, pwmFrame / pwmFrameDiv},
{cvPWMlo, pwmLo / pwmDiv}, {cvPWMhi, pwmHi / pwmDiv},
{cvPWMoffDelay, pwmOffDel / pwmOffDelDiv},
{cvServoAngle + 0, angleLoDef}, {cvServoAngle + 1, angleHiDef},
{cvServoAngle + 2, angleLoDef}, {cvServoAngle + 3, angleHiDef},
{cvServoAngle + 4, angleLoDef}, {cvServoAngle + 5, angleHiDef},
{cvServoAngle + 6, angleLoDef}, {cvServoAngle + 7, angleHiDef},
{cvServoSpeed + 0, speedDef / speedDiv}, {cvServoSpeed + 1, speedDef / speedDiv},
{cvServoSpeed + 2, speedDef / speedDiv}, {cvServoSpeed + 3, speedDef / speedDiv},
{cvServoImmediate + 0, 0}, {cvServoImmediate + 1, 0}, {cvServoImmediate + 2, 0}, {cvServoImmediate + 3, 0},
{cvMem + 0, 0}, {cvMem + 1, 0}, {cvMem + 2, 0}, {cvMem + 3, 0},
{cvDump, 0}
};
// ------------------------------------- Променливи на програмата: -------------------------------------
unsigned int decoder_address; // Decoder address.
long pwm_frame = pwmFrame; // PWM frame rate in µs.
long pwm_lo = pwmLo; // 0% length in µs.
long pwm_hi = pwmHi; // 100% pulse length in µs.
long pwm_off_delay = pwmOffDel; // Delay in ms before PWM turn-off.
int delay_counter = pwmOffDel / (pwmFrame / 1000); // Counter before turning PWM off.
int servo_angle[channels][2]; // Servo "angles" between 0 and 100; First value LOWER than second!
int servo_speed[channels]; // Servo speed between end states in ms between 100 and 5000 ms
long servo_pwm[channels][2]; // Servo PWMs.
long servo_step[channels]; // PWM step.
int pwm_out[channels]; // Pulse length to be generated for each channel
byte channel_state[channels]; // Current channel state - 0 = main, 1 = siding.
bool pwm_off[channels] = {0, 0, 0, 0}; // Flag to turn off PWM.
bool cycle_end[channels] = {0, 0, 0, 0}; // Flag for end of PWM change.
int cycle_end_counter[channels] = {delay_counter, delay_counter, delay_counter, delay_counter}; // Counter before turning PWM off.
bool LED_state[channels][2] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; // LED current state - false = off, true = on.
int LED_counter[channels] = {LED_flasher, LED_flasher, LED_flasher, LED_flasher}; // LED flasher counter.
int current_channel = 0; // Current channel pointer; set to first channel.
bool get_values = 0; // Flag to tell main loop to run its routine.
word pwm_pause = pwm_frame; // Length of pause after all PWM pulses have been processed.
bool first_time_no_update = 1; // Flag to not update EEPROM on startup.
byte defsCVCnt = 0;
#ifdef use_NmraDcc
int getCVInd = 0;
int setCVInd = 0;
byte setCVVal = 0;
unsigned long cvChangeMillis = 0;
#define cUpdate "Update "
#define cMem "CV"
byte iRead(word a) {return(Dcc.getCV(a));}
void iSave(word a, byte d) {Dcc.setCV(a, d);}
bool iReady() {return(Dcc.isSetCVReady());}
byte badcvPWMframe() {return((iRead(cvPWMframe) * pwmFrameDiv < pwmFrameMin) || (iRead(cvPWMframe) * pwmFrameDiv > pwmFrameMax));}
byte badcvPWM() {return((iRead(cvPWMlo) * pwmDiv < pwmMin) || (iRead(cvPWMlo) >= iRead(cvPWMhi)) || (iRead(cvPWMhi) * pwmDiv > pwmMax));}
#else
#define cUpdate "EEPROM update "
#define cMem "EEPROM "
byte iRead(word a) {return(EEPROM.read(a));}
void iSave(word a, byte d) {EEPROM.update(a, d);}
bool iReady() {return(1);}
#endif
long calcPWM(int angle) {return (pwm_lo + angle * (pwm_hi - pwm_lo) / 100);}
long calcStep(int c) {return ((servo_pwm[c][1] - servo_pwm[c][0]) * (pwm_frame / 500) / (servo_speed[c] * 2));}
byte checkValue(int val, int vMin, int vMax) {return ((val >= vMin) && (val <= vMax));}
void getDecoderAddress() {decoder_address = ((iRead(cvAddressHi) & aHiMask) << 8) + ((iRead(cvAddressLo) - 1) & 0xfc) + 1;}
byte badcvAddressHi() {return(iRead(cvAddressHi) & (0xff - aHiMask));}
byte badcvServoAngle(int cv) {return((iRead(cv) < angleMin) || (iRead(cv) > angleMax));}
byte badcvServoSpeed(int cv) {return((iRead(cv) * speedDiv < speedMin) || (iRead(cv) * speedDiv > speedMax));}
int ISR_delay = 3; // Timer ISR delay.
byte timer1b = bit(WGM12) | bit(CS11); // Timer1 CTC, scale to clock / 8.
bool pwm_status = 0; // PWM signal flag; start with a pause.
word next_timer_value; // Value timer will count next.
ISR(TIMER1_COMPA_vect) { // Timer1 ISR.
TCCR1B = 0; // Stop timer.
TCNT1 = 0; // Reset timer.
OCR1A = next_timer_value; // Load compare A register value.
TCCR1B = timer1b; // Start timer.
if (pwm_status) { // Generating pulse?
if (current_channel) digitalWrite(servo_pin[current_channel - 1], LOW); // Turn off previous channel unless it was a pause.
if (!pwm_off[current_channel]) digitalWrite(servo_pin[current_channel], HIGH); // Turn on the new channel if needed.
pwm_pause -= pwm_out[current_channel]; // Subtract newly generated pulse width from pause.
if (++current_channel > channels - 1) { // Get ready for next channel and check if this was the last channel.
current_channel = 0; // Last channel - reset channel number.
pwm_status = 0; // Last channel pulse has just started - next time generate pause.
next_timer_value = (pwm_pause << 1) - ISR_delay; // Load pause length.
} else next_timer_value = (pwm_out[current_channel] << 1) - ISR_delay; // Not last channel - load new channel pulse width.
} else { // Done with PWM pulse.
digitalWrite(servo_pin[channels - 1], LOW); // Done with last channel pulse - pause is to follow.
current_channel = 0; // Set pointer to first channel.
next_timer_value = (pwm_out[current_channel] << 1) - ISR_delay; // Load first channel PWM.
get_values = 1; // Set to check input, run main cycle.
pwm_status = 1; // Next time generate pulse.
pwm_pause = pwm_frame; // Reload pause.
} // if (pwm_status)
sei(); // Enable interrupts.
} // ISR(TIMER1_COMPA_vect)
void setup() { // Program setup.
Serial.begin(baudRate); // Set up UART.
for (int i = 0; i < 100; i++) Serial.println(); // Clear serial terminal.
#ifdef use_NmraDcc
#ifdef digitalPinToInterrupt // Set up DCC pin.
Dcc.pin(dcc_pin, 0);
#else
Dcc.pin(0, dcc_pin, 1);
#endif
Dcc.init(mfr_id, mfr_ver, CV29_ACCESSORY_DECODER | CV29_OUTPUT_ADDRESS_MODE, 0); // Initialize DCC library.
#else
pinMode(dcc_pin, INPUT_PULLUP); // Initialize DCC input.
DCC.SetBasicAccessoryDecoderPacketHandler(notifyDccAccTurnoutOutput, 1); // Attach accessory decoder handler
DCC.SetupDecoder(mfr_id, mfr_ver, digitalPinToInterrupt(dcc_pin)); // Configure decoder
#endif
getDecoderAddress(); // Get decoder address.
#ifdef debug_main // Send decoder info if debug mode is enabled.
debugMain();
#endif
initChannels(); // Initialize all channels.
#ifdef debug_setup // Send channel setup data if debug mode is enabled.
debugSetup();
#endif // End of debug transmission.
if ((badcvAddressHi()) || // Valid decoder address?
(iRead(cvMfr) != mfr_id) || // Matching manufacturer ID?
(iRead(cvVer) != mfr_ver) || // Matching decoder ID?
(iRead(cvSub) != mfr_sub)) // Matching decoder software ID?
notifyCVResetFactoryDefault(); // Make defsCVCnt non-zero and equal to number of CVs to be reset
// to flag loop() that function reset to factory defaults is needed.
next_timer_value = (pwm_out[current_channel] << 1) - ISR_delay; // Load first channel PWM.
TCCR1A = 0; // Set up timer1 for normal mode.
TCCR1B = 0; // Stop timer.
TIMSK1 = bit (OCIE1A); // Interrupt on Compare A Match
TCCR1B = timer1b; // Start timer1.
} // setup()
--- Край на кода ---
Продължението следва...
Иван
IvanC:
ПРОГРАМА, III-та част
--- Код: ---
void loop() { // Main cycle.
#ifdef use_NmraDcc
Dcc.process(); // Loop through DCC library.
#else
DCC.loop(); // Loop through DCC library.
#endif
if (get_values) { // Need to get new values?
for (int c = 0; c < channels; c++) { // Cycle through channels.
bool cv_update_flag = 0; // So far no EEPROM update is needed.
if (!cycle_end[c] && !pwm_off[c]) { // Need to change channel PWM?
if (--LED_counter[c] == 0) { // Decrement LED flasher counter and check if expired?
LED_state[c][channel_state[c]] = !LED_state[c][channel_state[c]]; // LED flasher counter expired, toggle LED state.
LED_counter[c] = LED_flasher; // Reload LED flasher counter.
} // if (LED_counter[c] == 0)
bool rev = servo_pwm[c][0] > servo_pwm[c][1]; // Calculate channel servo reversing.
if (channel_state[c]) pwm_out[c] += servo_step[c]; // Update PWM.
else pwm_out[c] -= servo_step[c]; // Update PWM.
if (( (rev ^ channel_state[c]) && pwm_out[c] > servo_pwm[c][channel_state[c]]) ||
(!(rev ^ channel_state[c]) && pwm_out[c] < servo_pwm[c][channel_state[c]])) { // End point reached?
pwm_out[c] = servo_pwm[c][channel_state[c]]; // End point reached - transition complete, adjust to end point.
LED_state[c][channel_state[c]] = 1; // Set LED state.
LED_counter[c] = LED_flasher; // Reload LED flasher counter.
cycle_end_counter[c] = delay_counter; // Reload channel transition counter.
cycle_end[c] = 1; // Set end of transition flag.
cv_update_flag = 1; // CV update needed.
} // if (end_point_reached)
} // if (!cycle_end[c] && !pwm_off[c])
if (cycle_end[c]) { // Channel transition completed?
if (--cycle_end_counter[c] < 2) { // Decrement counter and check if expired.
cycle_end[c] = 0; // Counter expired - end of cycle.
pwm_off[c] = 1; // Turn off channel.
} // if (--cycle_end_counter[c] < 2)
} // if (cycle_end[c])
digitalWrite(LED_pin[c][0], LED_state[c][0]); // Update main LED output.
digitalWrite(LED_pin[c][1], LED_state[c][1]); // Update siding LED output.
if (cv_update_flag && !first_time_no_update) { // EEPROM update needed?
#ifdef use_NmraDcc
setCVInd = c + cvMem;
setCVVal = channel_state[c];
cvChangeMillis = millis(); // Get current millis for CV change blocking
#else
iSave(c, channel_state[c]); // Save new state.
#endif
#ifdef debug_i_save // Send channel state if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("servo "); // │
Serial.print(c + 1); // ├ Debug transmission...
Serial.print(" state to"); // │
Serial.print(cStr[channel_state[c]]); // │
Serial.println("."); //-┘
#endif // End of debug transmission.
cv_update_flag = 0; // Reset flag to prevent unwanted EEPROM updates.
} // if (cv_update_flag && !first_time_no_update)
} // for (int c)
get_values = 0; // Done reading and calculating data.
first_time_no_update = 0; // Further channel state changes will be saved.
} // if (get_values)
if (defsCVCnt && iReady()) { // Check if factory reset of CVs was requested.
defsCVCnt--; // Decrement from initial size of array.
iSave(factoryDefaults[defsCVCnt].cv, factoryDefaults[defsCVCnt].value);
#ifdef debug_defaults
Serial.print("Load Factory Defaults: ");
Serial.print(cMem);
Serial.print(factoryDefaults[defsCVCnt].cv);
Serial.print(": ");
Serial.println(factoryDefaults[defsCVCnt].value);
#endif
if (!defsCVCnt) { // Done with factory defaults:
getDecoderAddress(); // get decoder address,
initChannels(); // reload everything.
}
} // if (defsCVCnt)
#ifdef use_NmraDcc
if (setCVInd && iReady() && (millis() - cvChangeMillis > 63)) { // CV needs to reset immediately
iSave(setCVInd, setCVVal); // after its value has been set.
if (setCVInd == cvDump) dumpCVs(); // Dump CVs if dump CV changed.
getCVInd = setCVInd; // Flag the loop() to send a CV readback to serial
setCVInd = 0; // Clear request.
} // if setCVInd
#ifdef dump_CVs_enabled
if (getCVInd && !setCVInd) { // Check for CV readback to serial request.
Serial.print(cMem);
sendCV(getCVInd); // CV readback to serial requested, send CV.
Serial.println();
getCVInd = 0; // Clear request.
} // if getCVInd
#endif
#endif
if (Serial.available()) getSerial(); // Read serial if data was received.
} // loop()
#ifdef use_NmraDcc
void notifyDccAccTurnoutOutput(unsigned int address, byte data, byte dummy) { // Called when accessory data packet is received.
#else
void notifyDccAccTurnoutOutput(int rawAddress, boolean dummy, byte rawData) { // Called when accessory data packet is received.
long address = (rawAddress - 1) * 4 + ((rawData & 0x06) >> 1) + 1; // Calculate actual address.
byte data = rawData & 0x01; // Calculate actual data.
#endif
#ifdef debug_acc_turnout // Send DCC data if decug mode enabled.
Serial.print("notifyDccAccTurnoutOutput: "); //-┐
#ifndef use_NmraDcc // │
Serial.print("Raw: "); // │
Serial.print(rawAddress); // │
Serial.print(", "); // │
Serial.print(dummy); // │
Serial.print(", "); // │
Serial.print(rawData); // │
Serial.print("; "); // │
#endif // │
Serial.print("Address = "); // ├ Debug transmission...
Serial.print(address); // │
#ifdef use_NmraDcc // │
Serial.print(", Direction = "); // │
Serial.print(data); // │
Serial.print(", Output Power = "); // │
Serial.println(dummy); // │
#else // │
Serial.print(", Data = "); // │
Serial.println(data); // │
#endif //-┘
#endif // End of debug transmission.
data &= 0x01; // Fix data if needed.
for (int c = 0; c < channels; c++) { // Cycle through channels.
if ((decoder_address + c == address ) && (channel_state[c] != data)) { // Matching servo address and changing channel state?
channel_state[c] = data; // Toggle channel state.
pwm_off[c] = 0; // Turn on channel PWM.
cycle_end[c] = 0; // No end of PWM change.
LED_state[c][0] = 0; // Clear main LED state.
LED_state[c][1] = 0; // Clear siding LED state.
} // if (decoder_address)
} // for (int c)
} // notifyDccAccTurnoutOutput
void notifyCVResetFactoryDefault() {defsCVCnt = sizeof(factoryDefaults) / sizeof(cvPair);}
#ifdef use_NmraDcc
void notifyDccCVChange(unsigned int cv, byte value) { // Called when DCC packet changes CV value.
#ifdef debug_cvchange
Serial.print("notifyDccCVChange: CV");
Serial.print(cv);
Serial.print(": ");
Serial.println(value);
#endif
switch (cv) {
case cvDef: // Factory defaults CV has changed.
if (value == cvDef) // Change to reset to factory defaults.
notifyCVResetFactoryDefault(); // Make defsCVCnt non-zero and equal to number of CVs to be reset
break; // to flag loop() that function reset to factory defaults is needed.
case cvSub: // Subversion CV changed.
setCVInd = cv; // Flag loop() to restore it.
setCVVal = mfr_sub;
break;
#ifdef dump_CVs_enabled
case cvDump: // CV dump is requested.
setCVInd = cvDump; // Flag loop() to reset the value
setCVVal = 0; // and dump the CVs to serial.
break;
#endif
case cvAddressLo: // Address LSB changed.
case cvAddressHi: // Address MSB changed.
if (badcvAddressHi()) { // Valid address LSB?
setCVInd = cvAddressHi; // Ivalid address: flag loop() to restore it.
setCVVal = calcAddressHi(decoder_address);
} else getDecoderAddress(); // Get decoder address if valid value.
break;
case cvPWMframe: // PWM frame rate CV changed.
if (badcvPWMframe()) { // Valid PWM frame rate?
setCVInd = cv; // Invalid rate: flag loop() to restore it.
setCVVal = pwm_frame / pwmFrameDiv;
} else initChannels(); // Initialize all channels.
break;
case cvPWMlo: // 0% pulse length CV changed.
case cvPWMhi: // 100% pulse length CV changed.
if (badcvPWM()) { // Valid PWM frame rate?
setCVInd = cv; // Invalid rate: flag loop() to restore it.
setCVVal = ((cv == cvPWMlo) ? pwm_lo : pwm_hi) / pwmDiv;
} else initChannels(); // Initialize all channels.
break;
case cvServoAngle + 0: // Servo 1 MAIN angle CV changed.
case cvServoAngle + 1: // Servo 1 SIDING angle CV changed.
case cvServoAngle + 2: // Servo 2 MAIN angle CV changed.
case cvServoAngle + 3: // Servo 2 SIDING angle CV changed.
case cvServoAngle + 4: // Servo 3 MAIN angle CV changed.
case cvServoAngle + 5: // Servo 3 SIDING angle CV changed.
case cvServoAngle + 6: // Servo 4 MAIN angle CV changed.
case cvServoAngle + 7: // Servo 5 SIDING angle CV changed.
if (badcvServoAngle(cv)) { // Valid servo angle?
setCVInd = cv; // Invalid angle: flag loop() to restore it.
int c = cv - cvServoAngle;
setCVVal = servo_angle[c >> 1][c & 1];
} else initChannels(); // Initialize all channels.
break;
case cvPWMoffDelay: // Delay before PWM turn-off CV changed.
initChannels(); // Initialize all channels.
break;
case cvServoSpeed + 0: // Servo 1 speed CV changed.
case cvServoSpeed + 1: // Servo 2 speed CV changed.
case cvServoSpeed + 2: // Servo 3 speed CV changed.
case cvServoSpeed + 3: // Servo 4 speed CV changed.
if (badcvServoSpeed(cv)) { // Valid servo speed?
setCVInd = cv; // Invalid speed: flag loop() to restore it.
setCVVal = servo_speed[cv - cvServoSpeed] / speedDiv;
} else initChannels(); // Initialize all channels.
break;
case cvServoImmediate + 0: // Servo 1 state toggle CV changed.
case cvServoImmediate + 1: // Servo 2 state toggle CV changed.
case cvServoImmediate + 2: // Servo 3 state toggle CV changed.
case cvServoImmediate + 3: // Servo 4 state toggle CV changed.
int c = cv - cvServoImmediate;
channel_state[c] = !channel_state[c]; // Toggle channel state.
pwm_off[c] = 0; // Turn on channel PWM.
cycle_end[c] = 0; // No end of PWM change.
LED_state[c][0] = 0; // Clear main LED state.
LED_state[c][1] = 0; // Clear siding LED state.
setCVInd = cv; // Flag loop() to reset the value
setCVVal = 0; // and dump the CVs to serial.
break;
default:
break;
} // switch cv
getCVInd = cv;
cvChangeMillis = millis(); // Get current millis for CV change blocking
} // notifyDccCVChange
#endif
--- Край на кода ---
Продължението следва...
Иван
IvanC:
ПРОГРАМА, IV-та част (последна)
--- Код: ---
void initChannels() { // Initialize all channels
first_time_no_update = 1; // Prevent loop() from updating EEPROM one time.
#ifdef use_NmraDcc
pwm_frame = iRead(cvPWMframe) * pwmFrameDiv; // Load PWM frame rate.
pwm_lo = iRead(cvPWMlo) * pwmDiv; // Load 0% pulse length.
pwm_hi = iRead(cvPWMhi) * pwmDiv; // Load 100% pulse length.
pwm_off_delay = iRead(cvPWMoffDelay) * pwmOffDelDiv; // Load delay before PWM turn-off.
if (badcvPWMframe()) { // Valid frame rate?
pwm_frame = pwmFrame; // Invalid frame rate: load default value
iSave(cvPWMframe, pwm_frame / pwmFrameDiv); // and save to CV.
#ifdef debug_cv_write // Send CV if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("PWM frame rate to default "); // │
Serial.print(pwm_frame); // │
Serial.print(" µs ("); // ├ Debug transmission...
Serial.print(cMem); // │
sendCV(cvPWMframe); // │
Serial.println(")."); //-┘
#endif // End of debug transmission.
} // if (badcvPWMframe())
if (badcvPWM()) { // Valid pulse length?
pwm_lo = pwmLo; // Invalid pulse lengths: load default values
iSave(cvPWMlo, pwm_lo / pwmDiv); // and save to CV.
pwm_hi = pwmHi;
iSave(cvPWMhi, pwm_hi / pwmDiv);
#ifdef debug_cv_write // Send CV if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("pulse lengths to defaults "); // │
Serial.print(pwm_lo); // │
Serial.print(", "); // │
Serial.print(pwm_lo); // │
Serial.print(" µs ("); // ├ Debug transmission...
Serial.print(cMem); // │
sendCV(cvPWMlo); // │
Serial.print(", "); // │
Serial.print(cMem); // │
sendCV(cvPWMhi); // │
Serial.println(")."); //-┘
#endif // End of debug transmission.
} // if (badcvPWM())
delay_counter = pwm_off_delay / (pwm_frame / 1000); // Calculate delay counter.
#endif
for (int c = 0; c < channels; c++) { // For all channels:
digitalWrite(servo_pin[c], LOW); // Make servo pin low to prevent jerk.
cycle_end_counter[c] = delay_counter; // Load cycle end counter.
channel_state[c] = iRead(c + cvMem); // Read last saved channel state.
channel_state[c] &= 1; // Mask the channel state to 0 or 1.
int cv = (c << 1) + cvServoAngle; // Get base angle CV address.
servo_angle[c][0] = iRead(cv); // Read channel angle for state 0.
servo_angle[c][1] = iRead(cv + 1); // Read channel angle for state 1.
if (badcvServoAngle(cv)) { // Check if "angles" are valid.
servo_angle[c][0] = angleLoDef; // Load default value for state 0 "angle".
servo_angle[c][1] = angleHiDef; // Load default value for state 1 "angle".
iSave(cv, servo_angle[c][0]); // Save state 0 "angle" to EEPROM
iSave(cv + 1, servo_angle[c][1]); // Save state 1 "angle" to EEPROM
#ifdef debug_i_save // Send channel state if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("servo "); // │
Serial.print(c + 1); // │
Serial.print(" angles to defaults "); // │
Serial.print(servo_angle[c][0]); // │
Serial.print(", "); // │
Serial.print(servo_angle[c][1]); // ├ Debug transmission...
Serial.print(" ("); // │
Serial.print(cMem); // │
sendCV(cv); // │
Serial.print(", "); // │
Serial.print(cMem); // │
sendCV(cv + 1); // │
Serial.println(")."); //-┘
#endif // End of debug transmission.
} // if (badcvServoAngle(cv))
servo_speed[c] = iRead(c + cvServoSpeed) * speedDiv; // Read channel servo speed.
if (badcvServoSpeed(c + cvServoSpeed)) { // Check if servo speed is valid.
servo_speed[c] = speedDef; // Load default value for servo speed.
iSave(c + cvServoSpeed, servo_speed[c] / speedDiv); // Save speed to CV.
#ifdef debug_cv_write // Send channel servo speed if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("servo "); // │
Serial.print(c + 1); // │
Serial.print(" speed to defaults "); // │
Serial.print(servo_speed[c]); // ├ Debug transmission...
Serial.print(" ("); // │
Serial.print(cMem); // │
sendCV(c + cvServoSpeed); // │
Serial.println(")."); //-┘
#endif // End of debug transmission.
} // if (badcvServoSpeed(c + cvServoSpeed))
servo_pwm[c][0] = calcPWM(servo_angle[c][0]); // Calculate PWM for channel state 0.
servo_pwm[c][1] = calcPWM(servo_angle[c][1]); // Calculate PWM for channel state 1.
servo_step[c] = calcStep(c); // Calculate servo step for channel.
if (!servo_step[c]) servo_step[c] = 1; // Set sevo step to a min of 1.
pwm_out[c] = servo_pwm[c][channel_state[c]]; // Set initial channel PWM.
pwm_off[c] = 0; // Turn on channel PWM.
cycle_end[c] = 0; // No end of PWM change.
LED_state[c][0] = 0; // Clear main LED state.
LED_state[c][1] = 0; // Clear siding LED state.
pinMode(servo_pin[c], OUTPUT); // Initialize channel output.
pinMode(LED_pin[c][0], OUTPUT); // Initialize main LED output.
pinMode(LED_pin[c][1], OUTPUT); // Initialize siding LED output.
#ifdef debug_i_read // Send channel setup data if debug mode is enabled.
Serial.print("Servo "); //-┐
Serial.print(c + 1); // │
Serial.print(" set to"); // │
Serial.print(cStr[channel_state[c]]); // │
Serial.print(" ("); // │
Serial.print(cMem); // │
sendCV(c + cvMem); // │
Serial.print(", "); // │
Serial.print(cMem); // ├ Debug transmission...
sendCV(c * 2 + cvServoAngle); // │
Serial.print(", "); // │
Serial.print(cMem); // │
sendCV(c * 2 + cvServoAngle + 1); // │
Serial.print(", "); // │
Serial.print(cMem); // │
sendCV(c + cvServoSpeed); // │
Serial.println(")"); //-┘
#endif // End of debug transmission.
} // for (int c = 0; c < channels; c++)
} // initChannels()
void debugMain() { // Send decoder info if debug mode is enabled.
#ifdef debug_main
Serial.println();
Serial.println("--- Accessory DCC Decoder --- Ivan Cankov ---");
Serial.print("Mfr ID: ");
Serial.print(iRead(cvMfr));
Serial.print("; Version: ");
Serial.print(iRead(cvVer));
Serial.print(".");
Serial.print(iRead(cvSub));
Serial.print("; Address: ");
Serial.println(decoder_address);
Serial.println();
#endif
} // debugMain()
void debugSetup() { // Send channel setup data if debug mode is enabled.
Serial.println();
Serial.print(" PWM Frame Rate = ");
Serial.print(pwm_frame);
Serial.println(" µs");
Serial.print(" 0% PWM Pulse Length = ");
Serial.print(pwm_lo);
Serial.println(" µs");
Serial.print(" 100% PWM Pulse Length = ");
Serial.print(pwm_hi);
Serial.println(" µs");
Serial.print("Delay before PWM Turn-off = ");
Serial.print(pwm_off_delay);
Serial.println(" ms");
Serial.println();
for (int c = 0; c < channels; c++) { // For all channels:
Serial.print("Servo "); // Send channel servo parameters to serial.
Serial.print(c + 1);
Serial.print(" address ");
Serial.print(decoder_address + c);
Serial.print(", ");
Serial.print("end points ");
Serial.print(servo_angle[c][0]);
Serial.print(", ");
Serial.print(servo_angle[c][1]);
#ifdef debug_servo_pwm // Send channel servo PWMs if debug mode is enabled.
Serial.print(" ("); //-┐
Serial.print(servo_pwm[c][0]); // │
Serial.print(", "); // │
Serial.print(servo_pwm[c][1]); // ├ Debug transmission...
Serial.print(" µs), "); // │
Serial.print("step "); // │
Serial.print(servo_step[c]); //-┘
#endif
Serial.print(", speed ");
Serial.print(servo_speed[c]);
Serial.print("ms, set to");
Serial.print(cStr[channel_state[c]]);
Serial.print(" angle ");
Serial.println(servo_angle[c][channel_state[c]]);
} // for (int c = 0; c < channels; c++)
Serial.println();
} // debugSetup()
void dumpCVs() { // Dump all CVs to serial.
#ifdef dump_CVs_enabled
Serial.println();
Serial.println("--- CV Dump Start ---");
Serial.println("CV,Value");
for (int i = 1; i <= 256; i++) { // Cycle thru all (256 supported) CVs.
int p = ((i == cvVer) || (i == cvMfr)) ? i : 0; // CV7 and CV8 are not in defaults array.
if (!p)
for (int c = 0; c < sizeof(factoryDefaults) / sizeof(cvPair); c++)
if (i == factoryDefaults[c].cv) p = i; // Get index if CV in defaults array.
if (p) { // CV found?
Serial.print("CV"); // Send CV to serial.
Serial.print(p);
Serial.print(",");
Serial.println(iRead(p));
} // if (p)
} // for (int i = 1; i <= 256; i++)
Serial.println("---- CV Dump End ----");
Serial.println();
#endif
} // dumpCVs()
void sendCV(int cv) { // Send CV to serial if debug mode is enabled.
#ifdef debug_main
Serial.print(cv);
Serial.print(": ");
Serial.print(iRead(cv));
#endif
} // sendCV(int cv)
void getSerial() { // Read searial if data is received.
int lF = 0, r = 0, cv = -1;
unsigned long val = 0, t = 0;
char com = 0;
do {
if (Serial.available()) { // Read serial.
t = micros(); // Read current timestamp for serial timeout.
char c = Serial.read();
if ((c == 'a') || (c == 'd')) {com = c; cv = -1; r = 0;}
else if ((c == cDelimiter) || (c == 's')) {com = c; cv = r; r = 0;}
else if (c == cLF) {val = r; r = 0; lF = 1;}
else if (c != cCR) r = r * 10 + String(c).toInt();
} // if (Serial.available())
} while (!lF && (micros() - t < serialTimeout));
if (!lF) return;
if (((com == cDelimiter) || com == 's') && (cv > 0) && (cv <= channels)) {
cv--;
byte cState = channel_state[cv];
if ((val >= (com == 's' ? speedMin : angleMin)) && (val <= (com == 's' ? speedMax : angleMax))) {
first_time_no_update = 1;
if (com == cDelimiter) {
servo_angle[cv][cState] = val;
servo_pwm[cv][cState] = calcPWM(servo_angle[cv][cState]); // Calculate PWM for channel current state.
iSave(cv * 2 + cvServoAngle + cState, servo_angle[cv][cState]);
} // if (com == cDelimiter)
else if (com = 's') {
servo_speed[cv] = val;
iSave(cv + cvServoSpeed, servo_speed[cv] / speedDiv);
} // if (com = 's')
servo_step[cv] = calcStep(cv); // Calculate servo step for channel.
if (!servo_step[cv]) servo_step[cv] = 1; // Set sevo step to a min of 1.
pwm_out[cv] = servo_pwm[cv][cState]; // Set immediate channel PWM.
#ifdef debug_cv_write // Send channel state if debug mode is enabled.
Serial.print(cUpdate); //-┐
Serial.print("servo "); // │
Serial.print(cv + 1); // │
Serial.print(cStr[channel_state[cv]]); // │
Serial.print(com == 's' ? " speed" : " angle"); // │
Serial.print(" to "); // │
Serial.print(com == 's' ? servo_speed[cv] : servo_angle[cv][cState]); // │
if (com == 's') Serial.print("ms"); // │
#ifdef debug_servo_pwm // ├ Debug transmission...
else { // │
Serial.print(" ("); // │
Serial.print(servo_pwm[cv][cState]); // │
Serial.print(" µs), "); // │
Serial.print("step "); // │
Serial.print(servo_step[cv]); // │
} // else (com == 's') // │
#endif // │
Serial.println("."); //-┘
#endif // End of debug transmission.
pwm_off[cv] = 0; // Turn on channel PWM.
cycle_end[cv] = 0; // No end of PWM change.
LED_state[cv][0] = 0; // Clear main LED state.
LED_state[cv][1] = 0; // Clear siding LED state.
Serial.print("Servo ");
Serial.print(cv + 1);
Serial.print(cStr[channel_state[cv]]);
Serial.print(com == 's' ? " speed" : " angle");
Serial.print(" set to ");
Serial.print(val);
Serial.println(com == 's' ? "ms" : "");
} else {
Serial.print("Servo ");
Serial.print(cv + 1);
Serial.print(cStr[channel_state[cv]]);
Serial.print(com == 's' ? " speed " : " angle ");
Serial.print(val);
Serial.println(" INVALID!");
} // if (checkValue(val, angleMin, angleMax))
} // if (((com == cDelimiter) || com == 's') && (cv > 0) && (cv <= channels))
else if (!com && (cv == -1) && (val > 0) && (val <= channels)) {
val--;
channel_state[val] = !channel_state[val]; // Toggle channel state.
pwm_off[val] = 0; // Turn on channel PWM.
cycle_end[val] = 0; // No end of PWM change.
LED_state[val][0] = 0; // Clear main LED state.
LED_state[val][1] = 0; // Clear siding LED state.
Serial.print("Servo ");
Serial.print(val + 1);
Serial.print(" switched to");
Serial.print(cStr[channel_state[val]]);
Serial.print(" angle ");
Serial.print(servo_angle[val][channel_state[val]]);
#ifdef debug_servo_pwm
Serial.print(" (");
Serial.print(servo_pwm[val][channel_state[val]]);
Serial.print(" µs)");
#endif
Serial.println();
} // if (!com && (cv == -1) && (val > 0) && (val <= channels))
else if (!com && (cv == -1) && !val) debugSetup();
else if ((com == 'a') && (val > 0) && (val <= 2044)) {
decoder_address = ((val - 1) & 0xfffc) + 1;
iSave(cvAddressLo, calcAddressLo(decoder_address)); // Save decoder address LSB.
#ifdef dump_CVs_enabled
Serial.print(cMem); // CV readback to serial requested, send CV.
sendCV(cvAddressLo);
Serial.println();
#endif
iSave(cvAddressHi, calcAddressHi(decoder_address)); // Save decoder address MSB.
Serial.print("Decoder (servo 1) address set to ");
Serial.println(decoder_address);
} // if ((com == 'a') && (val > 0) && (val <= 2044))
else if ((com == 'd') && (val == 11)) notifyCVResetFactoryDefault();
else {
Serial.print(!com ? "Servo " : com == 'a' ? "Address " : "Command");
Serial.print(!com ? String(cv + 1) : com == 'a' ? String(val) : "");
Serial.println(" INVALID!");
} // if ((com == 'd') && (val == 11))
} // getSerial()
--- Край на кода ---
Иван
Навигация
[0] Списък на темите
Премини на пълна версия