Електроника и Електротехника | Electronics and Electrical Engineering > Цифрово / дигитално управление | Digital Command Control
Осветление на вагони
IvanC:
--- Код: ---
// --------------------------------------------------------------------------- doOutputs ---------------------------------------------------------------------------
void doOutputs() { // apply output effects if any
unsigned long m;
for (int i = 0; i < outs; i++) { // cycle through all function outputs
if ((stateFA[i] != oldStateFA[i]) || throttleReq) { // check if output or throttle status has changed
byte j = ((i < 8) ? cvF0fEffect : (cvFA7Effect - 8)) + i; // check all output effects
byte d = Dcc.getCV(j);
effectFA[i] = d & outEffMask; // get non-directional effect
d &= outEffDir; // get directional effect
byte td = intThrottle.Dir; // get direction from throttle state
if (((d == outEffFwd) && (td == reverse)) || // output on forward only, throttle is in reverse or
((d == outEffRev) && (td == forward))) // output on in reverse only, throttle is forward:
stateFA[i] = 0; // turn off output state
#ifdef debug_effects
Serial.print("Output Effects: Index: ");
Serial.print(i);
Serial.print("; CV");
Serial.print(j);
Serial.print(": ");
Serial.print(Dcc.getCV(j));
Serial.print(" - ");
switch (effectFA[i]) {
case outEffNone: Serial.println("None"); break;
case outEffMars: Serial.println("Mars"); break;
case outEffRandom: Serial.println("Flicker"); break;
case outEffFlash: Serial.println("Flasher"); break;
case outEffStrobe1: Serial.println("Single Strobe"); break;
case outEffStrobe2: Serial.println("Dual Strobe"); break;
case outEffBeacon: Serial.println("Beacon"); break;
case outEffGyra: Serial.println("Gyralite"); break;
case outEffDitch1R: Serial.println("RH Ditch 1"); break;
case outEffDitch1L: Serial.println("LH Ditch 1"); break;
case outEffDitch2R: Serial.println("RH Ditch 2"); break;
case outEffDitch2L: Serial.println("LH Ditch 2"); break;
case outEffCoupler: Serial.println("Coupler"); break;
case outEffSoft: Serial.println("Soft Start"); break;
case outEffBrake: Serial.println("Brake Light"); break;
case outEffStand: Serial.println("Standing Only"); break;
case outEff5min: Serial.println("Off in 5 min"); break;
case outEff10min: Serial.println("Off in 10 min"); break;
case outEffFade: Serial.println("Fade-in/out"); break;
case outEffFluo: Serial.println("Fluorescent"); break;
case outEffSparks: Serial.println("Brake Sparks"); break;
default: Serial.println("Undefined (None)"); break;
} // switch effectFA
#endif
if (cvChanged) {
if (i == getFAEffectIndex(cvChanged)) {
oldStateFA[i] = !stateFA[i];
cvChanged = 0;
} // if i
} // if cvChanged
switch (effectFA[i]) {
case outEffFlash: // flashing light effect
if (stateFA[i] == oldStateFA[i]) break; // do nothing if output state has not changed
outPwm[i] = (stateFA[i]) ? on : off; // apply state to output
if (!stateFA[i]) { // check if output state is off
effectMillis[i] = 0; // no effect on turn off
break;
} // if !stateFA
effectMillis[i] = millis() + flashOnMillis; // get next time to check output effect
cEff[i] = 0; // clear output counter
break;
case outEffStrobe1: // single pulse strobe
case outEffStrobe2: // double pulse strobe
if (stateFA[i] == oldStateFA[i]) break; // do nothing if output state has not changed
outPwm[i] = (stateFA[i]) ? on : off; // apply state to output
if (!stateFA[i]) { // check if output state is off
effectMillis[i] = 0; // no effect on turn off
break;
} // if !stateFA
effectMillis[i] = millis() + strobeOnMillis; // get next time to check output effect
cEff[i] = 0; // clear output counter
break;
case outEffStand: // output on standing, off moving
if (intThrottle.Value) stateFA[i] = 0; // turn off output state if moving
outPwm[i] = (stateFA[i]) ? on : off; // apply state to output
break;
case outEff5min: // turn off output after 5 minutes
case outEff10min: // turn off output after 10 minutes
if (stateFA[i] == oldStateFA[i]) break; // do nothing if output state has not changed
outPwm[i] = (stateFA[i]) ? on : off; // apply state to output
if (!stateFA[i]) { // check if output state is off
effectMillis[i] = 0; // no effect on turn off
break;
} // if !stateFA
effectMillis[i] = millis(); // get current time
effectMillis[i] += (effectFA[i] == outEff5min) ? 300000 : 600000; // add 5 or 10 minutes (300,000µs or 600,000µs)
break;
case outEffCoupler: // coupler control per cvCoupler
if (intThrottle.Value) stateFA[i] = 0; // turn off output state if moving
case outEffDitch1R: // ditch light 1, right
case outEffDitch1L: // ditch light 1, left
case outEffDitch2R: // ditch light 2, right
case outEffDitch2L: // ditch light 2, left
case outEffMars: // mars light effect
case outEffRandom: // random flicker effect
case outEffBeacon: // rotary beacon effect
case outEffGyra: // gyralite effect
case outEffSoft: // soft start per cvSoft [0.1s]
if (!stateFA[i]) { // check if output state is off
outPwm[i] = off; // turn off output
effectMillis[i] = 0; // no effect on turn off
cEff[i] = 0; // clear output counter
break;
} // if !stateFA
case outEffFade: // fade-in per cvFadeIn, fade-out per cvFadeOut [0.1s]
if (stateFA[i] == oldStateFA[i]) break; // do nothing if output state has not changed
effectMillis[i] = millis(); // get current time for input
break;
case outEffFluo: // fluorescent light startup
if (stateFA[i] == oldStateFA[i]) break; // do nothing if output state has not changed
outPwm[i] = (stateFA[i]) ? on : off; // apply state to output
if (!stateFA[i]) { // check if output state is off
effectMillis[i] = 0; // no effect on turn off
break;
} // if !stateFA
effectMillis[i] = millis() + random(50, 300); // get next time to check output effect
cEff[i] = random(1, 4) << 1; // get random number of flickers
break;
default: // no effect
outPwm[i] = (stateFA[i]) ? on : off; // apply state to output
break;
} // switch effectFA
oldStateFA[i] = stateFA[i]; // output status has changed, store status
} // if stateFA
} // for i
} // doOutputs
// --------------------------------------------------------------------- checkEffectInProgress ---------------------------------------------------------------------
void checkEffectInProgress() {
byte f;
unsigned long m = micros();
truePwmTime = m - pwmMicros;
if (truePwmTime > pwmTime) {
pwmMicros = m;
pwmCounter += pwmStep;
doMotorFlag = true;
if (accelCounter) accelCounter--;
if (decelCounter) decelCounter--;
} // if micros
m = millis();
ditchFlag = (m - oldDitchMillis > ditchMillis);
if (ditchFlag) {
oldDitchMillis = m;
ditchCounter += ditchStep;
} // if oldDitchMillis
for (int i = 0; i < outs; i++) {
if (effectMillis[i]) {
byte eff = effectFA[i] & outEffMask;
switch (eff) {
case outEffMars: // mars light effect
f = cEff[i] << 2;
if (cEff[i] & 64) f = ~f;
if (millis() - effectMillis[i] < expo(marsMillis, f, false)) break;
effectMillis[i] = millis();
cEff[i] += marsStep;
f = cEff[i] << 2;
if (cEff[i] & 64) f = ~f;
if (cEff[i] & 128) f = f / 3;
outPwm[i] = max(f, marsMin);
break;
case outEffRandom: // random flicker effect
if (millis() < effectMillis[i]) break; // do nothing if time has not elapsed
cEff[i]++;
if (!cEff[i]) break;
cEff[i] = random(randMin, on);
f = random(0, 120);
outPwm[i] = (f > 80) ? randMin : cEff[i];
effectMillis[i] = millis() + f;
break;
case outEffFlash: // flashing light effect
if (millis() < effectMillis[i]) break; // do nothing if time has not elapsed
cEff[i]++;
if (cEff[i] & 1) {
outPwm[i] = off;
effectMillis[i] = millis() + flashOffMillis; // get next time to check output effect
break;
} // if cEff
outPwm[i] = on;
effectMillis[i] = millis() + flashOnMillis; // get next time to check output effect
break;
case outEffStrobe1: // single pulse strobe effect
case outEffStrobe2: // double pulse strobe effect
if (millis() < effectMillis[i]) break; // do nothing if time has not elapsed
cEff[i]++;
if (cEff[i] & 1) {
outPwm[i] = off;
m = (((cEff[i] & 2) == 0) && ((effectFA[i] & outEffMask) == outEffStrobe2)) ? strobe2Millis : strobeOffMillis;
effectMillis[i] = millis() + m; // get next time to check output effect
break;
} // if cEff
outPwm[i] = on;
effectMillis[i] = millis() + strobeOnMillis; // get next time to check output effect
break;
case outEffBeacon: // rotary beacon effect
f = cEff[i] << 2;
if (cEff[i] & 64) f = ~f;
if (millis() - effectMillis[i] < expo(beaconMillis, f, false)) break;
effectMillis[i] = millis();
cEff[i] += beaconStep;
if (cEff[i] & 128) {
outPwm[i] = off;
break;
} // if cEff
f = cEff[i] << 2;
if (cEff[i] & 64) f = ~f;
outPwm[i] = f;
break;
case outEffGyra: // gyralite effect
if (millis() - effectMillis[i] < marsMillis) break;
effectMillis[i] = millis();
cEff[i] += marsStep;
if (cEff[i] & 128) {
outPwm[i] = marsMin;
break;
} // if cEff
f = cEff[i] << 2;
if (cEff[i] & 64) f = ~f;
outPwm[i] = max(f, marsMin);
break;
case outEffDitch1R: // ditch light 1, right
case outEffDitch1L: // ditch light 1, left
case outEffDitch2R: // ditch light 2, right
case outEffDitch2L: // ditch light 2, left
if (!ditchFlag) break;
f = ditchCounter << 1;
if (eff & 4) f = ~f;
if (ditchCounter & 128) f = ~f;
if (f < ditchMin + ditchPause) f = ditchMin;
outPwm[i] = f;
break;
case outEff5min: // turn off output after 5 minutes
case outEff10min: // turn off output after 10 minutes
if (millis() < effectMillis[i]) break; // do nothing if time has not elapsed
outPwm[i] = off; // time has elapsed, turn off output
effectMillis[i] = 0;
break;
case outEffCoupler: // coupler control per cvCoupler
outPwm[i] = (millis() - effectMillis[i] < couplerMillis) ? on : couplerPwm;
break;
case outEffSoft: // soft start per cvSoft [0.1s]
if (millis() - effectMillis[i] < expo(softStMillis, cEff[i], false)) break;
effectMillis[i] = millis();
cEff[i] += softStStep;
if (cEff[i] > 255) {
outPwm[i] = on;
cEff[i] = 255;
effectMillis[i] = 0;
break;
} // if cEff
outPwm[i] = cEff[i];
break;
case outEffFade: // fade-in per cvFadeIn [0.1s]
f = stateFA[i]; // fade-out per cvFadeOut [0.1s]
m = (f) ? fadeInMillis : expo(fadeOutMillis, cEff[i], false);
if (millis() - effectMillis[i] < m) break;
effectMillis[i] = millis();
cEff[i] += (f) ? fadeInStep : -fadeOutStep;
if ((cEff[i] < 0) || (cEff[i] > 255)) {
outPwm[i] = (f) ? on : off;
cEff[i] = (f) ? 255 : 0;
effectMillis[i] = 0;
break;
} // if cEff
outPwm[i] = cEff[i];
break;
case outEffFluo: // fluorescent ligh start up effect
m = millis(); // get current time
if (m < effectMillis[i]) break; // do nothing if not time yet
cEff[i]--; // decrement effect counter
outPwm[i] = (cEff[i] & 1) ? off : on; // turn on/off output based on even/odd counter
effectMillis[i] = (cEff[i]) ? (m + ((cEff[i] & 1) ? random(400, 1000) : random(50, 300))) : 0; // get time for next event
break; // or 0 if counter expired
default:
effectMillis[i] = 0;
break;
} // switch effectFA
} // if effectMillis
f = highByte((unsigned int)outPwm[i] * (unsigned int)pwmFA[i]); // apply max PWM
if (!foPol[i]) f = ~f; // invert output if set as inverted
if (i <= 5)
analogWrite(foPin[i], f); else // apply hardware PWM to outputs if supported
digitalWrite(foPin[i], f > pwmCounter); // apply software PWM to outputs with no hardware PWM
} // for i
} // checkEffectInProgress
--- Край на кода ---
Използва се библиотеката NmraDcc, която е публикувана тук:
https://github.com/mrrwa/NmraDcc
Библиотеката може да се инсталира директно от Ардуино-IDE средата. Ако по някаква причина Ардуино средата не намери библиотеката, може да си свалите файловете от горната препратка. Трябват файловете NmraDcc.cpp и NmraDcc.h, като силно препоръчвам и keywords.txt, за да се маркират ключовите думи от библиотеката със съответните цветове.
Иван
pach:
Тази школа, няма как да се развие при младите хобисти!!!
Благодарско Иване - за споделеното и то до край!!!
:hi: :clapping: :hi: :clapping: :hi: :clapping: &bgflag
IvanC:
Изводите на Про-минито са зададени в тези редове:
--- Код: ---
// ---------------------------------- Задаване функциите на изводите: ----------------------------------
#define dccPin 2 // Вход за DCC сигнала; стойност 2 или 3;
// да не се използват други стойности!!!
#define motorPwm 14 // Изход за ШИМ на мотора
#define motorDir 13 // Изход за посока на мотора
// FA0f FA0r FA1 FA2 FA3 FA4 FA5 FA6 FA7 FA8
const byte foPin[] = { 3, 5, 6, 9, 10, 11, 12, 8, 7, 4}; // Изводи за управление на функциите.
// Първите 6 извода да не се променят
// и да няма дублиране с dccPin!!!
--- Край на кода ---
Ако не сте много навътре в ATmega328, силно препоръчвам да не променяте така присвоените функции на изводите.
С други думи:
2 - вход за DCC сигнала
3 - изход F0f
5 - изход F0r
6 - изход FA1
9 - изход FA2
10 - изход FA3
11 - изход FA4
12 - изход FA5
8 - изход FA6
7 - изход FA7
4 - изход FA8
Както се вижда броят изходи са 10, а не 12, както бях писал в първото мнение. При силно желание, броят на изходите може да се увеличи на 12 и дори малко повече, но може да доведе до забавяне на работата на програмата с последващи нежелателни ефекти.
"Истинските" (хардуерните) ШИМ изходи са първите 6. Останалите 4 използват софтуерен ШИМ, поради което някои ефекти не работят много добре - получава се видима стъпкова промяна на яркостта на светене на лампата/светодиода, свързан към някой от тези изводи. Заради това, препоръчвам FA5 до FA8 да не използват ефекти с плавна промяна на яркостта.
Всеки изход може да бъде зададен с активно ниско или високо ниво. Това става с помощтта на CV-та, които ще опиша в следващо мнение. Един изход може без проблеми да комутира до 20 mA, но общата комутация за цялото про-мини (сумата от комутирания ток на всички изводи) не трябва да надхвърля 200 mA.
Ако изпозлвате стабилизатор на напрежението за 3.3 V, про-минито трябва да е с кварц за максимум 8 MHz. Ако стабилизаторът е за 5 V, квaрцът може да е за 16 MHz. И в двата случая трябва да изберете правилната честота на кварца от Ардуино средата при програмирането на про-минито. В противен случай времеконстантите няма да отговарят на зададеното, като може времетраенетата на нарастнат или намалят двойно.
Както се вижда по-горе има и изводи за управление на мотор, но тази функцията е много първобитна и съм я направил единствено за да мога да направя задаването на времето за ускорение и спиране, което оказва влияние на някои от ефектите. Електромоторът НЕ МОЖЕ да се свърже директно към изводите на про-минито, а трябват драйвери. Ако се опитате да свържете мотор, като минимум съответните изводи ще се повредят, а е възможно да се повреди и цялото про-мини.
Иван
IvanC:
--- Цитат на: pach link=topic=5553.msg123159#msg123159 date=1644094251 ---Тази школа, няма как да се развие при младите хобисти!!!
Благодарско Иване - за споделеното и то до край!!!
:hi: :clapping: :hi: :clapping: :hi: :clapping: &bgflag
--- Край на цитат ---
Това хоби има много широка техническа и технологическа база. Много от дейностите са коренно различни една от друга. Затова и смятам, че трябва да си помагаме взаимно - всеки да помага в сферата, в която има добри способности. Това е причината да публикувам тази тема - с всичко, което съм направил от началото до края.
Иван
IvanC:
Пропуснах да обясня, че трябваше да разделя програмата на 4 части поради ограничението на форума за максимум 20000 символа в едно мнение. Поради същата причина ще разделя и описанието на CV-тата в няколко мнения.
Виждам, че съм пропуснал и да дам препратка към про-минито, която е:
https://docs.arduino.cc/retired/boards/arduino-pro-mini
Описанието на CV-тата на български ще е на практика превод от английски, защото не съм напълно запознат с българската терминология. Когато започнах да се занимавам с DCC, декодерите, които започнах да използвам бяха с описание на английски и от там съм запознат с английската терминология. То май българска терминология за това няма, но може и да греша в това. Та затова предварително моля да бъда извинен за неточности в българската терминология. Приемам конструктивна критика и поправки в това отношение. И понеже използвам "CV" навсякъде, ще поясня какво означава и защо използвам именно това съкращение.
CV е съкратено от Configuration Variable или на български "конфигурационна променлива". "Конфигурационна", защото задава какво да прави декодера, т.е. определя как е конфигурирана определена част от декодера, а "променлива", защото стойността може да бъде променяна. Стойността може да се променя както от потребителя (нас), така и от програмата на декодера. Някои CV-та могат да се променят само от потребителя, други от потребителя и декодера, трети само от декодера, а някои са само за четене, т.е. те са програмирани от производителя на декодера и не могат да бъдат променяни, освен ако не се смени например програмата в декодера. Като цяло CV-тата казват на програмата на декодера какво точно да прави и как тоано да го прави. В моята програмка, например, CV-тата за ефектите указват кой точно ефект да се използва за всеки изход на декодера, а други CV-та указват параметрите на съответния ефект. Постарал съм се в програмата колкото се може повече от параметрите да се задават с CV-та за колкото се може по-голяма гъвкавост. Това до известна степен усложнява настройките на CV-тата, но няма как - колкото повече възможности, толкова повече се усложнява настройката. Но пък ако не използвате много или въобще ефекти, настройките са минимални. Ще дам и пример накрая с точните настройки, които съм направил за моя багажен вагон.
Написах, че някои от CV-тата са само за четене. И за да няма неяснотии, трябва да поясня, че при този функционален декодер реално четене през централата не може да стане - декодерът в този си вид (като хардуер) няма как да върне сигнал на станцията. Единствената възможност да се прочетат CV-тата е през серийния интерфейс на про-минито, който в програмата така, както съм я публикувал, е разрешен. Това става със запис в едно от CV-тата, което ще опиша по-долу.
Описанието на CV-тата е групирано в зависимост от функционалността им.
CV-та за идентифициране на декодера и зареждане на "заводските" стойности
CVЗаводска
стойност ОписаниеCV813Идентификация на декодера; твърдо зададена с константа в програмата на декодера, т.е. само за четенеCV711Вид на декодера; твърдо зададен с константа в програмата на декодера, т.е. само за четенеCV651Версия на програмата на декодера; твърдо зададена с константа в програмата на декодера, т.е. само за четенеCV110Зареждане на "заводските" стойности в CV-тата при запис на стойност 11 в CV11
CV-та за задаване на адреса и основната конфигурация на декодера
CVЗаводска
стойност ОписаниеCV13Къс адрес на декодера; стойности от 1 до 127; активира се от CV29CV17192Дълъг адрес на декодера, старша част, активира се от CV29CV18128Дълъг адрес на декодера, младша част, активира се от CV29
Дългият адрес се формира по формулата (CV17 - 192) * 256 + CV18
Формулата за изчисляване на CV17 и CV18 при желан дълъг адрес е
CV17 = адрес / 256 + 192
CV18 = остатъкът от разделянето на адреса на 256 = адрес по модул 256 = адрес - 256 * цялата часта от(адрес / 256)CV296Основна конфигурация на декодера
бит 0 - тегло 1 - посока на движение - 0 = напред; 1 = назад
бит 1 - тегло 2 - брой стъпки на тротъла - 0 = 14 стъпки; 2 = 128 стъпки
бит 2 - тегло 4 - аналогов режим - 0 = аналоговият режим е забранен; 4 = аналоговият режим е разрешен
битове 3 и 4 - тегла 8 и 16 - не се използват
бит 5 - тегло 32 - вид адрес - 0 = къс адрес, зададен от CV1; 32 = дълъг адрес, зададен с (CV17 - 192) * 256 + CV18
битове 6 и 7 - тегла 64 и 128 - не се използват
Стойността на CV29 се образува, като се съберат стойностите на всички битове, например:
бит 0: посока на движение напред = 0
бит 1: 128 стъпки на тротъла = 2
бит 2: разрешен аналогов режим = 4
бит 5: къс адрес според CV1 = 0
-----------------------------------------------------------
0 + 2 + 4 + 0 = 6 - това записваме в CV29 (този пример е за заводската стойност на CV-то)
Ако искаме посока назад (1), 128 стъпки (2), забранен аналогов режим (0) и дълъг адрес (32), в CV29 трябва да запишем 1 + 2 + 0 + 32 = 35
Адресът на декодера е от 1 до 9983 включително. Ако се зададе адрес по-голям от 9983, програмата преценява, че данните в CV-тата са повредени и зарежда заводските стойности.
CV-та за управление на мотора
Както писах по-горе, функциите за управление на мотора са съвсем елементарни и няма хардуер за мотора, но стойностите на тези CV-та имат влияние върху някои от ефектите на изходите.
CVЗаводска
стойност ОписаниеCV21Минимален ШИМ на мотора (при тротъл = 1); стойност от 1 до CV6 - 1CV51Максимален ШИМ на мотора (при тротъл = 100%); стойност от CV6 + 1 до 255; 1 = 255CV61ШИМ на мотора при тротъл = 50%; стойност от CV2 + 1 до CV5 - 1; 1 = 1/3 от обхвата, зададен от CV2 и CV5CV35Време за ускорение от покой до максимална скорост в 0,9 секунди; стойност от 0 до 255; 0 = 0 сек.; 255 = 230 сек.CV45Време за спиране от максимална скорост до покой в 0,9 секунди; стойност от 0 до 255; 0 = 0 сек.; 255 = 230 сек.CV1240Намаляване на времената, зададени от CV3 и CV4 при активиране на бутона, зададен от CV156:
255=няма промяна на времената, зададени от CV3 и CV4192=времената, зададени от CV3 и CV4 са намалени до 75%128=времената са намалени до 50%64=времената са намалени до 25%0=времената са = 0, т.е. няма плавно потегляне и спиранеВсички стойности от 0 до 255 са валидниCV1550Бутон за активиране на намалена до 50% скорост на движение (маневрен режим или режим "Депо"):
0=няма присвоен бутон, т.е. функцията не може да бъде активирана1 - 28=бутон F1 до F28 активира функцията, когато бутонът е задействан29=бутон F0 активира функцията, когато бутонът е задействан101 - 128=бутон F1 до F28 активира функцията, когато бутонът НЕ е задействан129=бутон F0 активира функцията, когато бутонът НЕ е задействанПрограмата на декодера поддържа бутони от F0 до F12 включително; командите от бутоните от F13 до F28 включително се пренебрегватCV1560Бутон за активиране на намаляването на времената, зададени от CV3 и CV4;
Намаляването на времената е според CV124, описано по-горе;
Задаването на бутона е идентично с описаното за CV155 по-горе
Продължението следва...
Иван
Навигация
[0] Списък на темите
Премини на пълна версия