Nixie Clock

Aus Dario's Home
Wechseln zu: Navigation, Suche

Projektdatum: 2004

Nixie Uhr ohne Gehäuse
Schaltplan der Nixie Uhr
Gehäusestudie

Schon lange wusste ich, dass die C Programmiersprache der Wahl bei Mikrocontrollern ist, bisher habe ich mich erfolgreich gegen C wehren können, aber nun fand ich es an der Zeit auch einmal C zu lernen.

Im IEEE-Spectrum habe ich einen Artikel über die alten Nixieröhren gelesen und über Freaks, die aus diesen alten Röhren eine Uhr gebaut haben. "Das ist doch auch was für mich", dachte ich und daher habe ich beschlossen eine Nixie Uhr mit DCF-Empfänger und einem Atmel Controller zu bauen und in C zu programmieren.

Die Hochspannung für die Nixies wollte ich nicht mit einem zweiten Transformator (zu gross) und erst recht nicht ohne galvanische Trennung (zu gefährlich) erzeugen. Da der MC 34063 A von Reichelt nicht geliefert werden konnte, habe ich Ihn aus einem Adapter ausgebaut, der eigentlich gedacht war um meinen Palm im Auto zu laden.

Die Schaltung ist unterteilt in die Anzeigeplatine mit 6 Nixie Röhren ZM-1210 einem DC/DC-Wandler, dem Decoder für Nixies (74141), der Spannungsregler für 5V, sowie der Anodentreiber zum Multiplexen der einzelnen Röhren. Diese Platine ist 3cm breit und 28cm lang (zusammengesetzt auf zwei 14cm Stücken Lochrasterplatine. Die Leitungen sind größtenteils gefädelt und als Röhrensockel dienen 14-polige IC-Sockel.

Leider paßte der Atmel nicht mehr mit auf die Platine, sodass ich eine zweite Platine machen musste, wo dann der Atmel drauf ist.

Die Frage des Gehäuses ist auch noch nicht geklärt. Da eine solche Uhr Technologien eines Zeitraumes von einem halben Jahrhundert vereinigt, suche ich nach einem möglichst edlen Gehäuse, das die Uhr dann auch ins rechte Licht setzt. Die Abmaße habe ich schon in Form eines Papiermodells nachgebaut, aus welchem Material das Gehäuse dann mal werden soll, weiss ich aber noch nicht. Ich schwanke zwischen Holz und Stein, habe aber bei letzterem keine Ahnung wer mir sowas preiswert anfertigen könnte. Hat jemand eine Idee?

Programmiert ist das ganze in AVR-GCC. Leider habe ich jetzt festgestellt, dass es extrem viele Bitfehler beim Empfangs gibt, insbesondere wenn die Uhr in der Nähe von Monitoren steht. Es kommt so oft vor, dass die Bits so kippen, dass die Parity-Bits wieder stimmen und eine völlig Falsche Uhrzeit dekodiert wird.

Also müsste die Decodier-Routine noch verfeinert werden indem mehrere aufeinanderfolgende Zeiten verglichen werden und nur bei drei aufeinanderfolgenden Zeiten im Abstand von je einer Minute der Wert in die interne Uhr übernommen wird. Kann das jemand für mich einbauen? Ferner schwingt der Quarz so ungenau, dass ich ohne DCF-Empfang eine Abweichung von mehreren Minuten pro Tag habe, eventuell hat auch hier jamand einen Vorschlag.

Hier also der Quelltext meines ersten C-Programmes eventuell baut ja jemand noch die oben genannten Korrekturen ein:

/********************************************************************
 *  Title:    Nixie DCF-Clock
 *  Author:   Dario Carluccio
 *  Date:     03/2004
 *  Purpose:  Receives and decodes the DCF-Signal.
 *            Build in internal Interrupt controlled Software Clock
 *            Shows Time multiplexed on 6 Nixie Tubes
 *  Software: AVR-GCC to compile
 *  Hardware: ATS90S2313
 *            DCF-Receiver (Not inverted) at INT0
 *            6 Nixies connected to:
 *              PB 0-5: Multiplxed Anodes driven by MPS 92/42
 *               PD 3-6: Kathodes driven by BCD-Decoder 74141
 *            High Voltage for Nixies generated with
 *              DC/DC Converter MC-34063
 *            You might find more AVR related stuff at my homepage:
 *                http://www.carluccio.de
 ********************************************************************/

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <avr/pgmspace.h>


/********************************************************************
 * Prototypes
 ********************************************************************/
int main (void);
void DCFBitReceived(uint8_t);
void InitChip (void);
void InternalClock(void);
uint8_t GetDCFBitValue(uint8_t);


/********************************************************************
 * SET  OC1, OCR, DDROC according to Target
 ********************************************************************/
#if defined(__AVR_AT90S2313__)
#  define OC1 PB3
#  define OCR OCR1
#  define DDROC DDRB
#elif defined(__AVR_AT90S2333__) || defined(__AVR_AT90S4433__)
#  define OC1 PB1
#  define DDROC DDRB
#  define OCR OCR1
#elif defined(__AVR_AT90S4414__) || defined(__AVR_AT90S8515__) || \
      defined(__AVR_AT90S4434__) || defined(__AVR_AT90S8535__) || \
      defined(__AVR_ATmega163__)
#  define OC1 PD5
#  define DDROC DDRD
#  define OCR OCR1A
#else
#  error "Don't know what kind of MCU you are compiling for"
#endif


/********************************************************************
 * SET  XCOM11 according to Target
 ********************************************************************/
#if defined(COM11)
#  define XCOM11 COM11
#elif defined(COM1A1)
#  define XCOM11 COM1A1
#else
#  error "need either COM1A1 or COM11"
#endif


/********************************************************************
 * Defines
 ********************************************************************/
#define CRYSTAL_FREQ 9216000        // Crystal Frequency e.g. 9,216 Mhz
#define UART_BAUD_RATE 9600         // Baud Rate e.g. 9600

#define DCFLowMin   7               // min. duration Low:      80ms
#define DCFLowMax   11              // max. duration Low:     100ms
#define DCFHighMin  17              // min. duration High:    180ms
#define DCFHighMax  21              // max. duration High:    200ms
#define DCF59Min    170             // sec.59 min. dur. LOW: 1700ms
#define DCF59Max    200             // sec.59 max. dur. LOW: 2000ms

#define DCFYear     aDCFTime[5]     // DCF clock year
#define DCFMonth    aDCFTime[4]     // DCF clock month
#define DCFDay      aDCFTime[3]     // DCF clock day
#define DCFHour     aDCFTime[2]     // DCF clock hours
#define DCFMin      aDCFTime[1]     // DCF clock minutes
#define DCFSec      aDCFTime[0]     // DCF clock seconds
#define ITCYear     aITCTime[5]     // internal clock year
#define ITCMonth    aITCTime[4]     // internal clock month
#define ITCDay      aITCTime[3]     // internal clock day
#define ITCHour     aITCTime[2]     // internal clock hours
#define ITCMin      aITCTime[1]     // internal clock minutes
#define ITCSec      aITCTime[0]     // internal clock seconds

#define DCFPause    0               // valDcfSig: Pause received
#define DCFPulse    1               // valDcfSig: Pulse received

#define CtrlSecPuls 0               // CtrlReg:  Flag to do things once per second

#define ErrParity   0               // ErrorReg: Parity Error            = 1
#define ErrDcfBit   1               // ErrorReg: Bitduration Error       = 2
#define ErrDCFStart 2               // ErrorReg:

#define BitSet(Byte, BitNo)     Byte |= (1<<BitNo)
#define BitClear(Byte, BitNo)   Byte &= ~(1<<BitNo)

const uint8_t aDCFValues[] PROGMEM ={                                        \
  0x1,0x2,0x4,0x8,0xA,0x14,0x28,  0x0,0x1,0x2,0x4,0x8,0xA,0x14,              \
  0x0,0x1,0x2,0x4,0x8,0xA,0x14,   0x1,0x2,0x4,                               \
  0x1,0x2,0x4,0x8,0xA,            0x1,0x2,0x4,0x8,0xA,0x14,0x28,0x50,0x0};

volatile uint8_t cntDcf;                // 10ms counter for DCF pulse measure
volatile uint8_t cntIntClk;             // 10ms counter for internal clock
volatile uint8_t DcfSigVal;             // DCFPulse or DCFPause
volatile uint8_t DcfValue;              // Value for received DCF-Bit
volatile uint8_t DcfParity;             // Parity for DCF-Signal
volatile uint8_t LastSigVal;            // Last received DCF Signal
volatile uint8_t DcfSigLen;             // duration of signal in 10ms
volatile uint8_t CtrlReg;               // Control flag register
volatile uint8_t ErrorReg;              // Error flag register
volatile uint8_t aDCFTime[6];           // Time of DCF-Clock
volatile uint8_t aITCTime[6];           // Time of Internal-Clock
volatile uint8_t NixieH;                // Value for Nixie Valve 1+2
volatile uint8_t NixieM;                // Value for Nixie Valve 3+4
volatile uint8_t NixieS;                // Value for Nixie Valve 5+6
uint8_t iCnt;                           // Interner Counter
uint8_t Nixie;                          // Which Nixie is shown
uint8_t NixZiffer;                      // What is shown on actual Nixie
 

/********************************************************************
 *   Timer / Counter Interrupt every 2ms
 *   - increment DCF-pluse Counter every 10ms
 *   - increment internal Clock Counter every 10ms
 *   - multiplex Time to 6 Nixie Tubes
 ********************************************************************/
SIGNAL (SIG_OUTPUT_COMPARE1A){
  if (++iCnt==5){           // increment Counters every 5 x 2ms = 10ms
      iCnt=0;
    cntDcf++;         // counter DCF-Pulse measure
    cntIntClk++;      // counter internal Clock
    }
  // Multiplexing of 6 nixie Tubes
  PORTB &= 192;             // Turn all Nixies off (Reset PB 0-5)
  if (++Nixie==6){          // Next Nixie
    Nixie=0;
    }
    // get byte to bee show on actual nixie
  if ((Nixie==0) | (Nixie==1)){
    NixZiffer=NixieH;
    } else {
    if ((Nixie==2) | (Nixie==3)){
      NixZiffer=NixieM;
    } else {
      if ((Nixie==4) | (Nixie==5)){
      NixZiffer=NixieS;
      }
    }
  }
    // upper or lower nibble?
  if ((Nixie==0)|(Nixie==2)|(Nixie==4)){
    NixZiffer >>= 4;
  } else {
    NixZiffer &= 15;
  }
  PORTD &= 3;               // Reset BCD-Values
  PORTD |= (NixZiffer<<3);  // BCD-Value to Nixie at PD 3-6
  PORTB |= (1<<Nixie);      // Turn on actual Nixie
}
 

/********************************************************************
 *   DCF Interrupt on raising/falling edge at INT1
 *   - detect raising / falling edge,
 *   - change int to other edge
 *   - set signal tag
 *   - set signal time
 *   - set LED on / off
 ********************************************************************/
SIGNAL(SIG_INTERRUPT0){
  DcfSigLen=cntDcf;
  if (!(MCUCR & (1<<ISC00))) {
    // This is done every falling edge (End of Pulse)
    BitSet(MCUCR, ISC00);       // next int on rising edge
    DcfSigVal = DCFPulse;       // HIGH received
    } else {
    // This is done every rising edge (End of Pause)
    BitClear(MCUCR, ISC00);     // next int on falling edge
    DcfSigVal = DCFPause;       // LOW  received
  }
  cntDcf=0;                           // reset DCF-counter
}


/********************************************************************
 *
 * main
 *
 ********************************************************************/
int main (void)
{
    // unsigned int chUart;
  uint8_t lNib;
  uint8_t hNib;

  // Init Ports, Timers, UART and Interrupts
  InitChip();

  // Enable Interrupts
  sei();

  // Main Loop
  for (;;) {
    // Internal Clock
    if (cntIntClk>=100){                    // every 100 * 10ms
      cntIntClk -= 100;               // subtract 100 * 10ms from counter
      InternalClock();                // add 1 second
      BitSet (CtrlReg, CtrlSecPuls);  // set Flag second
    }

    // Set Values to be displayed on Nixies
    if (CtrlReg & (1<<CtrlSecPuls)) {
      // Nixie 1 + 2
      hNib = ITCHour / 10;
      lNib = ITCHour - hNib * 10;
      NixieH = (16 * hNib + lNib);
      // Nixie 3 + 4
      hNib = ITCMin / 10;
      lNib = ITCMin - hNib * 10;
      NixieM = (16 * hNib + lNib);
      // Nixie 5 + 6
      hNib = ITCSec / 10;
      lNib = ITCSec - hNib * 10;
      NixieS = (16 * hNib + lNib);
    }

    // DCF-Signal: Status changed
    if (LastSigVal != DcfSigVal) {
      LastSigVal = DcfSigVal;
      if (DcfSigVal==DCFPulse) {
        // Pulse received
        if ((DcfSigLen>DCFLowMin) & (DcfSigLen<DCFLowMax)){
          DCFBitReceived(0);
        } else {
          if ((DcfSigLen>DCFHighMin) & (DcfSigLen<DCFHighMax)) {
            DCFBitReceived(1);
          } else {
            BitSet (ErrorReg, ErrDcfBit);
          }
        }
      } else if (DcfSigVal==DCFPause) {
        // Pause received
        DCFSec++;                                       // increment DCF second
        // Second 59 ?
        if ((DcfSigLen >= DCF59Min) & (DcfSigLen <= DCF59Max)) {
          DCFSec=0;                               // set DCF second = 0
          // Set Internal Clock
          if (ErrorReg == 0) {
            cntIntClk=0;
            ITCSec=0;
            ITCMin=DCFMin;
            ITCHour=DCFHour;
            ITCDay=DCFDay;
            ITCMonth=DCFMonth;
            ITCYear=DCFYear;
          }
          ErrorReg = 0;        // clear all errors
        }
      }
    }

    // delete second tag
    BitClear (CtrlReg, CtrlSecPuls);                                // Reset bit SecPuls
    }
    return (0);
}


/********************************************************************
 * Init Ports, Timers, UART and Interrupts
 *   - Port B: Output
 *   - Port D: Input
 *   - Int1: Interrupt
 *     - ISC11 (on falling edge )
 *     - INT1  (enable external int1)
 *   - Timer 1: Interrupt every 10 ms
 *     - CK/64 (CS11 + CS 10)
 *     - CTC1  (Clear Timer on Compare match)
 *     - OCR   (Output Compare Register: [ms]*CRYSTAL_FREQ/(1000*64)
 *     - OCIE1 (Output Compare Interrupt Enable)
 *   - Serial Port 1
 *     - Enable RxD
 *     - Enable TxD
 *     - Set Baud Rate: UBRR= (F_CPU / (16*BAUD) )-1
 ********************************************************************/
void InitChip (void) {
  // Port B: Output
  DDRB = 255;               // PortB: Output
  PORTB = 0;                // clear all (activ High)

  // Port D: Input
  DDRD = 120;               // PortD: 0,1,2 In - 3,4,5,6 Out
  PORTD = (1<<2);           // activate Pull-Up on PD3

  // Int0: Interrupt
  //  - ISC01 (on falling edge )   //  - INT0  (enable external int0)
  MCUCR = (1<<ISC01);              // raise int0 on falling edge
  GIMSK = (1<<INT0);               // enable external int0

  // Timer 1: Interrupt every 10 ms
  //  - CK/64 (CS11 + CS 10)
  //  - CTC1  (Clear Timer on Compare match)
  //  - OCR   (Output Compare Register: [ms]*CRYSTAL_FREQ/(1000*64)
  //  - OCIE1 (Output Compare Interrupt Enable)
  TCCR1A = 0;                               // No Output, No PWM
  TCCR1B = (1<<CS11)|(1<<CS10)|(1<<CTC1);    // CK/64 , CTC1
  OCR = 2 * CRYSTAL_FREQ / 64000L;          // 2ms: 5A0h = 1440d
  TIMSK = (1<<OCIE1A);                      // Interrupt Enable

  // Serial Port 1
  //  - Enable RxD
  //  - Enable TxD
  //  - Set Baud Rate: UBRR= (F_CPU / (16*BAUD) )-1
  UCR = (1 << RXEN) | (1 << TXEN);
  UBRR = (CRYSTAL_FREQ / (UART_BAUD_RATE * 16L)) - 1;
}


/********************************************************************
 * Inital Clock
 *   - raise time by one second every call
 ********************************************************************/
void InternalClock(void){
  if (++ITCSec==60){                                                 // seconds
    ITCSec=0;
    if (++ITCMin==60){                                         // minutes
      ITCMin=0;
      if (++ITCHour==24){                                // hours
        ITCHour=0;
        if (++ITCDay==32){                         // days
          ITCMonth++;                        // month with 31 days
          ITCDay=1;
        } else if (ITCDay==31){                    // month with  30 days
          if ((ITCMonth==4)||(ITCMonth==6)||(ITCMonth==9)||(ITCMonth==11)){
            ITCMonth++;                // apr, may, sep, nov
            ITCDay=1;
          }
        } else if (ITCDay==30){                    // feb 29 days
          if(ITCMonth==2){
            ITCMonth++;
            ITCDay=1;
          }
        } else if (ITCDay==29){                    // feb 28 days
          if((ITCMonth==2) && (ITCYear%4)){
            ITCMonth++;
            ITCDay=1;
          }
        }
        if (ITCMonth==13){                         // year
          ITCMonth=1;
          ITCYear++;
        }
      }
    }
  }
}


/********************************************************************
 * Get value for each DCF Bit (second)
 ********************************************************************/
uint8_t GetDCFBitValue(uint8_t nBitNo)
{
    return(PRG_RDB(&aDCFValues[nBitNo]));
}


/********************************************************************
 * DCF Bit recived routine
 ********************************************************************/
void DCFBitReceived(uint8_t nBit){
  // from second 21 get value for DCF clock
  if (DCFSec>=21) {
    if (nBit & DCFPulse)
      DcfValue += GetDCFBitValue(DCFSec-21);  // add bit to DcfValue
    DcfParity ^= nBit;                          // DcfParity XOR received bit
  }

  switch (DCFSec) {
    case 21:
    case 29:
    case 36:
      DcfParity=(nBit & 1);                   // Reset Parity h, m, date
    break;
    case 27:
      DCFMin=DcfValue;                        // set DCF-Minute
      DcfValue=0;
    break;
    case 34:
      DCFHour = DcfValue;                     // set DCF-Hour
      DcfValue=0;
    break;
    case 41:
      DCFDay=DcfValue;                        // set DCF-Day
      DcfValue=0;
    break;
    case 44:
      DcfValue=0;                             // Reset DcfValue
    break;
    case 49:
      DCFMonth=DcfValue;                      // set DCF-Month
      DcfValue=0;
    break;
    case 28:
    case 35:
    case 58:
      if (DCFSec == 58){
        DCFYear=DcfValue;               // DCF-Year
        DcfValue=0;
      }
      if (DcfParity){                         // Check Parity 3 Times
        BitSet (ErrorReg, ErrParity);
        DcfParity=0;
      }
    break;
  }
}