ПРОГРАМА, 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()
Продължението следва...
Иван