//------------------------------------------------------------------------ // // OpenDCC - OpenDecoder - SignalDecoder - Zwergsignaldekoder // //------------------------------------------------------------------------ #include #include #include #include // put var to program memory #include #include #include #include #define SIMULATION 0 // 0: real application // 1: test receive routine // 2: test timing engine // 3: test action of running light #define PRBS 0 #define BELGIAN_SIGNALS 0 // 0: standard mode // 1: only Belgian signals //--------------------------------------------------------------------- // Timing Definitions: // #ifndef F_CPU // prevent compiler error by supplying a default # warning "F_CPU not defined for ", set default to 10MHz # define F_CPU 8000000UL // if changed: check every place where it is used // (possible range underflow/overflow in preprocessor!) #endif #if (F_CPU == 10000000UL) unsigned char speed[] PROGMEM = {"..10MHz.."}; #endif #if (F_CPU == 8000000UL) unsigned char speed[] PROGMEM = {"..8MHz.."}; #endif /// This are DCC timing definitions from NMRA #define PERIOD_1 116L // 116us for DCC 1 pulse - do not change #define PERIOD_0 232L // 232us for DCC 0 pulse - do not change // Definitions for the DIMM-Engine // see below on information about the DIMM-Engine #define PWM_TICK_PERIOD 300L // 300us tick for PWM Engine #define PWM_STEPS 60L #define PWM_PERIOD (PWM_TICK_PERIOD * PWM_STEPS) // 18ms = 300 * 60L #define DIMM_UP_DELAY 400000L // Dimm up with 400ms delay #define DIMM_DOWN_SPEED 4L // Original 4L, höhere Zahl => schneller #define DIMM_UP_SPEED 4L // Original 5L #define DIMM_DOWN_SAME_BULB 1 // 1: wenn im neuen Begriff die lampe // wieder leuchtet, zuerst abdimmen, // dann wieder aufdimmen. // 0: leuchtet durchgehend //--------------------------------------------------------------------------- // PORT Definitions: // // PORTD: #define PROGTASTER 0 // input, internal pullup #define DCCIN 2 // must be located on INT0 #define JUMPER 4 // if fitted: save state to EEPROM #define LED 6 // out #define PROG_PRESSED (!(PIND & (1< this is 116*0,75=87us OCR1A = F_CPU * PERIOD_1 * 3 / 4 / 1000000L; // Init Timer0 as CTC // check PWM_TICK_PERIOD and F_CPU #define TIMER0_CLKDIV 64 // possible values: 1, 8, 64, 256, 1024 #if (F_CPU / 1000000L * PWM_TICK_PERIOD / TIMER0_CLKDIV) > 255L #warning: overflow in OCR0A - check TICK_PERIOD and F_CPU #warning: suggestion: use a larger clkdiv #endif #if (F_CPU / 1000000L * PWM_TICK_PERIOD / TIMER0_CLKDIV) < 30L #warning: resolution accuracy in OCR0A too low - check TICK_PERIOD and F_CPU #warning: suggestion: use a smaller clkdiv #endif OCR0A = F_CPU / 1000000L * PWM_TICK_PERIOD / TIMER0_CLKDIV ; TCCR0A = (0 << COM0A1) // compare match A | (0 << COM0A0) | (0 << COM0B1) // compare match B | (0 << COM0B0) | 0 // reserved | 0 // reserved | (1 << WGM01) | (0 << WGM00); // Timer0 Mode 2 = CTC - Int on compare A TCCR0B = (0 << FOC0A) | (0 << FOC0B) | (0 << WGM02) #if TIMER0_CLKDIV == 1 | (0 << CS02) | (0 << CS01) | (1 << CS00); // CS[2:0]=001 clkdiv 1 #elif TIMER0_CLKDIV == 8 | (0 << CS02) | (1 << CS01) | (0 << CS00); // CS[2:0]=010 clkdiv 8 #elif TIMER0_CLKDIV == 64 | (0 << CS02) | (1 << CS01) | (1 << CS00); // CS[2:0]=011 clkdiv 64 #elif TIMER0_CLKDIV == 256 | (1 << CS02) | (0 << CS01) | (0 << CS00); // CS[2:0]=100 clkdiv 256 #elif TIMER0_CLKDIV == 1024 | (1 << CS02) | (0 << CS01) | (1 << CS00); // CS[2:0]=101 clkdiv 1024 #else #warning: TIMER0_CLKDIV is void #endif TIMSK = (0<| // // DCC 1: _________XXXXXXXXX_________XXXXXXXXX_________ // ^-INT0 // |----87us--->| // ^-INT1: reads zero // // DCC 0: _________XXXXXXXXXXXXXXXXXX__________________ // ^-INT0 // |----------->| // ^-INT1: reads one // // Result: 1. The received message is stored in "message" and "message_size" // 2. The flag C_Received is set. // #define MAX_MESSAGE 6 // including XOR-Byte volatile unsigned char message[MAX_MESSAGE]; volatile unsigned char message_size; struct { unsigned char state; // current state unsigned char bitcount; // current bit unsigned char bytecount; // current byte unsigned char accubyte; // actual check } dccrec; // some states: #define RECSTAT_WF_PREAMBLE 0 #define RECSTAT_WF_LEAD0 1 #define RECSTAT_WF_BYTE 2 #define RECSTAT_WF_TRAILER 3 #define RECSTAT_DCC 7 // ISR(INT0) loads only a register and stores this register to IO. // this influences no status flags in SREG. // therefore we define a naked version of the ISR with // no compiler overhead. #define ISR_INT0_OPTIMIZED #ifdef ISR_INT0_OPTIMIZED #define ISR_MY_NAKED(vector) \ void vector (void) __attribute__ ((signal, naked)); \ void vector (void) ISR_MY_NAKED(INT0_vect) { __asm__ __volatile ( "push r16" "\n\t" "ldi r16, %1" "\n\t" "out %0, r16" "\n\t" "pop r16" "\n\t" : // no output section : "M" (_SFR_IO_ADDR (TCCR1B)), "M" ((0 << ICNC1) // start timer1 | (0 << ICES1) | (0 << WGM13) | (1 << WGM12) // Mode 4: CTC | (0 << CS12) // clk 1:1 | (0 << CS11) | (1 << CS10)) ); asm volatile ( "reti" ); } #else ISR(INT0_vect) { TCCR1B = (0 << ICNC1) // start timer1 | (0 << ICES1) | (0 << WGM13) | (1 << WGM12) // Mode 4: CTC | (0 << CS12) // clk 1:1 | (0 << CS11) | (1 << CS10); } #endif unsigned char copy[] PROGMEM = {"..SignalDecoder V0.11 .."}; ISR(TIMER1_COMPA_vect) { #define mydcc (Recstate & (1< mydcc=0 else Recstate |= 1<= 10) { Recstate = 1<= 10) { Recstate = 1< |<------------------------>| // | dimm_val | PWM_PERIOD | // ----|------------------------|--------------------------|---> Time // // // 1. Einstellen des aktuellen Helligkeitswertes (do_dimm) // // Es gibt 60 Helligkeitstufen. // Alle 300us erfolgt ein Interrupt, dieser schaltet den Dimmer um eine // Stufe weiter, nach 60 Stufen wird wieder von vorne begonnen. // Stufe MIN: alle Ports mit einem dimm_val > DIMM_MIN werden eingeschaltet. // Stufe x: Ein Port, dessen dimm_val kleiner x ist, wird abgeschaltet. // Stufe MAX: Restart und Meldung an den DIMMER (C_Dimmstep) // // Folge: Alle Ports mit einem dimm_val kleiner DIMM_RANGE_MIN sind // dauerhaft aus, alle Ports mit einem dimm_val größer DIMM_RANGE_MAX // sind dauerhaft ein. // // Da der PWM 60 Stufen hat und alle 300us ein Int erfolgt, wird dieser // Durchlauf alle 18ms durchgeführt. Dies entspricht einer Refreshrate von // 55Hz. // // // 2. Langsame Veränderung der Helligkeit // // Nach einem Zyklus des PWM wird vom Hauptprogramm der neue aktuelle // dimm_val ausgerechnet. Hierzu wird vom aktuellen Wert mit einem // Schritt "delta" nach oben oder unten gerechnet, bis der neue Zielwert // erreicht ist. // // Die Zykluszeit der PWM ist 18ms, somit wird je nach "delta" folgende // Dimmzeit erreicht: // // delta | Dimmzeit // ----------------------------- // 1 | 1080ms // 2 | 540ms // 3 | 360ms // 4 | 270ms // 5 | 216ms // 6 | 180ms // 100 | sofort // // Der neue Zielwert wird in light_val hinterlegt. // // Wenn man von einem Wert kleiner DIMM_RANGE_MIN startet, dann wird // der Port erst mit Verzögerung aufgedimmt, weil zuerst der Bereich // bis DIMM_RANGE_MIN "aufgedimmt" wird. // Dies wird dazu benutzt, zuerst das alte Signalbild wegzudimmen // und dann das neue Signalbild aufzudimmen. // // 3. Signalbilder // // Signalbilder werden als Bitfeld hinterlegt. Für jedes Kommando // gibt es ein Bitfeld, in dem das neue Signalbild abgelegt ist und eine // Gültigkeitsmaske, diese bestimmt, auf welche Dimmwerte das Signalbild // wirken soll. Mit diesen beiden Pattern wird "set_new_light_val" // aufgerufen. // // 4. Blinken // // Falls ontime bzw. offtime ungleich 0 sind, wird nach Ablauf der // jeweils andere Phasenwert geladen. // #define DIMM_RANGE_MIN 100 // aktiver Bereich 100-160 #define DIMM_RANGE_MAX (DIMM_RANGE_MIN+PWM_STEPS+1) //------------------------------------------- Array for DIMM-Engine // a) running values unsigned char cur_dimm; // = DIMM_RANGE_MIN; // b) target values volatile struct { unsigned char rest; // Zeit bis zum nächsten Wechsel in PWM_PERIOD (18ms) unsigned char ontime; // Einschaltzeit unsigned char offtime; // Ausschaltzeit unsigned char dimm_val; // aktueller Istwert unsigned char light_A_val; // aktueller Zielwert in Richtung A unsigned char delta_A; // aktuelles Increment in Richtung A unsigned char light_B_val; // aktueller Zielwert in Richtung B unsigned char delta_B; } out_pwm[8]; // // void do_dimm(void) ISR(TIMER0_COMPA_vect) // Timer0 Compare Int { // macht pwm unsigned char port; unsigned char mask; sei(); mask = 1; cur_dimm++; if (cur_dimm == DIMM_RANGE_MAX) { cur_dimm = DIMM_RANGE_MIN; for (port=0; port<8; port++) { if (out_pwm[port].dimm_val > DIMM_RANGE_MIN) PORTB |= mask; // Einschalten wenn !0 mask = mask << 1; } Communicate |= (1<= out_pwm[port].dimm_val) PORTB &= ~mask; mask = mask << 1; } } } // Diese Funktion setzt die neuen Ziel-Bits für DIMM // beim Aufdimmen gibt es immer 400ms Verzögerung, damit ein Abdimmen vorher fertig wird. void set_new_light_val(unsigned char pattern, unsigned char valid) { unsigned char port; unsigned char mask; unsigned char inv_pattern = ~pattern; mask = 1; for (port=0; port<8; port++) { if (pattern & mask & valid) { // turn on #if DIMM_DOWN_SAME_BULB == 1 out_pwm[port].rest = DIMM_UP_DELAY / PWM_PERIOD; CurrentTarget &= ~mask; #else if (CurrentTarget & mask) { // already on, nothing to do, keep burning } else { out_pwm[port].rest = DIMM_UP_DELAY / PWM_PERIOD; CurrentTarget &= ~mask; } #endif } if (inv_pattern & mask & valid) { // turn off out_pwm[port].rest = 0; CurrentTarget &= ~mask; } mask = mask << 1; } } //--------------------------------------------------------------------------------- // dimmer() // this routine is called from main(), if C_Dimmstep is activated // // Timing Engine // // Howto: // 1. Generelles Timing: // Diese Routine wird alle PWM_PERIOD aufgerufen. Es wird folgendes // geprüft: // a) Wenn out_pwm[port].rest gleich 0: dann bleibt dieser Port unverändert. // b) out_pwm[port].rest wird decrementiert, wenn es dabei 0 wird, dann // wird ein Dimmvorgang in die andere Richtung eingeleitet. // // 2. Dimm-Übergänge: // Je nach aktueller Richtung des Dimmvorgang (CurrentTarget) wird der aktuelle // Dimmwert erhöht oder erniedrigt (z.Z. linear). // Die Dimmrampe ist unabhängig von den Zeiten, die bei ontime bzw. offtime // vorgegeben werden. // Wenn ein Ausgang schalten soll, dann muß sein Delta sehr groß gewählt // werden! void dimmer(void) { unsigned char port; unsigned char mask; unsigned char my_rest; mask = 1; for (port=0; port<8; port++) { my_rest = out_pwm[port].rest; // use a local variable to force if (my_rest !=0) // compiler to tiny code { if (--my_rest == 0) { if (CurrentTarget & mask) { // bit was on my_rest = out_pwm[port].offtime; CurrentTarget &= ~mask; } else { my_rest = out_pwm[port].ontime; CurrentTarget |= mask; } } out_pwm[port].rest = my_rest; } my_rest = out_pwm[port].dimm_val; if (CurrentTarget & mask) { // we are in phase A -> incr if (my_rest < out_pwm[port].light_A_val) { // fehlt Sicherung gegen Überlauf -> bei Daten aufpassen // delta darf nicht zu groß sein my_rest += out_pwm[port].delta_A; } } else { // we are in phase B -> decr if (my_rest > out_pwm[port].light_B_val) { if (my_rest > out_pwm[port].delta_B) my_rest -= out_pwm[port].delta_B; else my_rest = 0; } } out_pwm[port].dimm_val = my_rest; mask = mask << 1; // do *not* calc mask from port } } #endif // DIMM_ENGINE //============================================================================== // //============================================================================== // // Section 4 // // Pattern für die Signale // // a) Zwei Zwergsignale // // Anschluß: // // 0b76543210 Anschluß: // ***---- Zwergsignal 1 // ***-------- Zwergsignal 2 unsigned char EE_Signal_2ZS_Pattern[8] EEMEM = { 0b00000011, // Halt 0b00000110, // Vorsicht 0b00000101, // Freie Fahrt 0b00000000, // Dunkel 0b00110000, // Halt 0b01100000, // Vorsicht 0b01010000, // Freie Fahrt 0b00000000, // Dunkel }; unsigned char EE_Signal_2ZS_Valid[8] EEMEM = { 0b00001111, // erstes Signal 0b00001111, 0b00001111, 0b00001111, 0b11110000, // zweites Signal 0b11110000, 0b11110000, 0b11110000, }; //============================================================================== // // Section 5 // // MAIN: analyze command, call the action, do programming // //------------------------------------------------------------------------------ // This Routine is called when myAdr is received void action(void) { unsigned char myCommand; myCommand = ReceivedCommand & 0b00001111; cli(); // block interrupts Communicate |= (1<= 0b10000000) // MSB in Command byte set { if (message[1] & (1<<3)) // Bit 3: accessory command + active coil? { ReceivedCommand = message[1] & 0b00000111; // take bits 5 4 3 2 1 0 from message[0] // take Bits 6 5 4 from message[1] and invert #define OPTCODE1 #ifdef OPTCODE1 unsigned char temp; myxor = ~message[1] & 0b01110000; myxor = myxor<<1; // shift as byte temp = message[0] & 0b00111111; ReceivedAdr = (myxor<<1) | temp; #else ReceivedAdr = (message[0] & 0b00111111) | ((~message[1] & 0b01110000) << 2); #endif MyAdr = (eeprom_read_byte(&EE_myAdrH) << 8) | (eeprom_read_byte(&EE_myAdrL)); if (ReceivedAdr == MyAdr) return(2); else if (ReceivedAdr == (MyAdr+1)) { ReceivedCommand += 8; return(2); } else return(1); } } } } return(0); } //------------------------------------------------------------------------ // This Routine is called when PROG is pressed // #define DEBOUNCE (50000L / PWM_PERIOD) #if (DEBOUNCE == 0) #define DEBOUNCE 1 #endif void DoProgramming(void) { unsigned char myCommand; cli(); MyDelay = DEBOUNCE; sei(); while(MyDelay) ; // wait until ISR has decremented MyDelay if (PROG_PRESSED) // still pressed? { LED_ON; Communicate &= ~(1<> 8)); myCommand = ReceivedCommand & 0x07; eeprom_write_byte(&EE_myOpMode, myCommand); MyOpMode = myCommand; eeprom_write_byte(&EE_LastState, 0); do {} while (!eeprom_is_ready()); // wait for write to complete LED_OFF; // we got reprogrammed -> // forget everthing running and restart decoder! // cli(); // laut diversen Internetseiten sollte folgender Code laufen - // tuts aber nicht, wenn man das Assemblerlistung ansieht. // void (*funcptr)( void ) = 0x0000; // Set up function pointer // funcptr(); // Jump to Reset vector 0x0000 __asm__ __volatile ( "ldi r30,0" "\n\t" "ldi r31,0" "\n\t" "icall" "\n\t" ); // return; } } } // while LED_OFF; cli(); MyDelay = DEBOUNCE; sei(); while(MyDelay) ; // wait until ISR has decremented MyDelay while(PROG_PRESSED) ; // wait for release } return; } #if (PRBS_CODE == 1) // linear feedback shift register (prbs) // // |---| |---| |---| |---| |---| |---| |---| |---| // ->| 0 |--->| 1 |-o->| 2 |-o->| 3 |-o->| 4 |--->| 5 |--->| 6 |--->| 7 |--o---> // | |---| |---| | |---| | |---| | |---| |---| |---| |---| | // | | | | | // <--------------- + <----- + <----- + <---------------------------------- // unsigned char prbs8(unsigned char seed) { unsigned char new_rnd; new_rnd = seed; // copy bit 1 new_rnd = new_rnd << 1; new_rnd = new_rnd ^ seed; // xor bit 2 new_rnd = new_rnd << 1; new_rnd = new_rnd ^ seed; // xor bit 3 new_rnd = new_rnd << 4; new_rnd = new_rnd ^ seed; // xor bit 7 // now put this bit to seed's lsb new_rnd = new_rnd >> 7; seed = seed << 1; new_rnd = new_rnd + seed; return(new_rnd); } #endif int main(void) { unsigned char port; init_main(); // Delta für Glühlampensimulation vorbelegen for (port=0; port<8; port++) { out_pwm[port].dimm_val = DIMM_RANGE_MIN; out_pwm[port].delta_A = DIMM_UP_SPEED; out_pwm[port].light_A_val = DIMM_RANGE_MAX; out_pwm[port].delta_B = DIMM_DOWN_SPEED; out_pwm[port].light_B_val = DIMM_RANGE_MIN; } MyOpMode = eeprom_read_byte(&EE_myOpMode); //Folgender Teil inaktiviert, weil hoffentlich nicht nötig :-)) /************************************************************** /* if ((MyOpMode == 4) || (MyOpMode == 7)) // Bahnübergang oder Ampel { out_pwm[3].delta_A = PWM_STEPS+2; // Stopmagnet soll durchschalten out_pwm[3].delta_B = PWM_STEPS+2; out_pwm[7].delta_A = PWM_STEPS+2; // Stopmagnet soll durchschalten out_pwm[7].delta_B = PWM_STEPS+2; } */ CurrentTarget = eeprom_read_byte(&EE_LastState); Communicate = 0; Recstate = 1< 0) { memcpy(doi.current_dcc, next_message.dcc, sizeof(doi.current_dcc)); doi.bytes_in_message = next_message.size; // no size checking - if (doi.cur_size > 5) doi.cur_size = 5; next_message_count--; doi.ibyte = 0; doi.xor_byte = 0; doi.bits_in_state = 14; doi.state = dos_send_preamble; } break; case dos_send_preamble: do_send(1); doi.bits_in_state--; if (doi.bits_in_state == 0) doi.state = dos_send_bstart; break; case dos_send_bstart: do_send(0); if (doi.bytes_in_message == 0) { // message done, goto xor doi.cur_byte = doi.xor_byte; doi.state = dos_send_xor; doi.bits_in_state = 8; } else { // get next addr or data doi.bytes_in_message--; doi.cur_byte = doi.current_dcc[doi.ibyte++]; doi.xor_byte ^= doi.cur_byte; doi.state = dos_send_byte; doi.bits_in_state = 8; } break; case dos_send_byte: if (doi.cur_byte & 0x80) do_send(1); else do_send(0); doi.cur_byte <<= 1; doi.bits_in_state--; if (doi.bits_in_state == 0) { doi.state = dos_send_bstart; } break; case dos_send_xor: if (doi.cur_byte & 0x80) do_send(1); else do_send(0); doi.cur_byte <<= 1; doi.bits_in_state--; if (doi.bits_in_state == 0) { doi.state = dos_idle; } break; } } void dcc_generate_init(void) { doi.state = dos_idle; } void simulat_receive(void) { dcc_generate_init(); dcc_bit_generator(); dcc_bit_generator(); memcpy(next_message.dcc, message_adr001_out0_g, sizeof(doi.current_dcc)); next_message.size = 2; next_message_count = 2; while(1) { dcc_bit_generator(); dcc_receive(); if (Communicate & (1<