//************************************************************************************* // // avr-based real-time clock // //************************************************************************************* // use a 4MHz crystal with clock div set to 8 #define F_CPU ( 500UL * 1000UL ) #include #include #include #include #include #define INLINE static inline typedef unsigned char byte; typedef byte reg; // !!! enum{ false, true }; static const reg KX_SEC_IND_BIT = 0x01; static const reg KX_nINC_MIN_BIT = 0x02; static const reg KX_nINC_HOUR_BIT = 0x04; static const reg KX_MSI_CLOCK_BIT = 0x08; static const reg KX_MSI_DATA_BIT = 0x10; static const reg KI_MSI_TIMEBASE = 120; // clock period = 10 x this (us) static const reg KI_XMIT_HDR_BITS = 10; static const reg KI_XMIT_HOUR_BITS = 5; static const reg KI_XMIT_MIN_BITS = 6; static const reg KI_XMIT_SEC_BITS = 6; static const reg KI_XMIT_HUN_BITS = 7; enum EReceiveState { E_RECV_HEADER, E_RECV_HOURS, E_RECV_MINUTES, E_RECV_SECONDS, E_RECV_HUNDREDTHS, }; // assuming pins 0-6 are mapped to segments a-g static const byte KA_DIGIT_TO_PINS[16] = { 0x3f, // 0 0x06, // 1 0x5b, // 2 0x4f, // 3 0x66, // 4 0x6d, // 5 0x7d, // 6 0x07, // 7 0x7f, // 8 0x6f, // 9 0x77, // a 0x7c, // b 0x39, // c 0x5e, // d 0x79, // e 0x71, // f }; // config data (from eeprom) static reg kiDisplaySlot = 0; static reg kiFlashSeconds = 1; static reg kbMaster = true; // ------------ globals ------------ typedef struct { reg miHours; reg miMinutes; reg miSeconds; reg miHundredths; } MolenTime; // current time static MolenTime gCurrentTime = { 10, 0, 0, 0 }; // the value of port b last time we read it static reg gxLastPortB = 0; // do we need to send our time to everyone else? volatile static reg gbNeedToSendTime = false; // the time value we're currently syncing static MolenTime gXMitTime; // For slaves, what our current receive state is static reg geRecvState = E_RECV_HEADER; // how many bits of the current word we've received static reg giRecvBits = 0; INLINE void WriteDigit( reg liDigit ) { // safe-anise it reg liBits = KA_DIGIT_TO_PINS[ liDigit & 0xf ]; // dump it straight to port D which conveniently has 7 pins PORTD = liBits; } void WriteTimeDigit() { // pick the digit we're showing based on our eeprom config reg liValue; switch( kiDisplaySlot ) { case 5: liValue = ( gCurrentTime.miHours / 10 ); break; case 4: liValue = ( gCurrentTime.miHours % 10 ); break; case 3: liValue = ( gCurrentTime.miMinutes / 10 ); break; case 2: liValue = ( gCurrentTime.miMinutes % 10 ); break; case 1: liValue = ( gCurrentTime.miSeconds / 10 ); break; default: liValue = ( gCurrentTime.miSeconds % 10 ); break; } // show that mofo! WriteDigit( liValue ); } void AddTimeDelta( reg liDHours, reg liDMinutes, reg liDSeconds, reg lbHandleCarry ) { gCurrentTime.miSeconds += liDSeconds; if( gCurrentTime.miSeconds >= 60 ) { gCurrentTime.miSeconds = 0; liDMinutes++; } gCurrentTime.miMinutes += liDMinutes; if( gCurrentTime.miMinutes >= 60 ) { gCurrentTime.miMinutes = 0; liDHours++; } gCurrentTime.miHours += liDHours; if( gCurrentTime.miHours >= 24 ) { gCurrentTime.miHours = 0; } } INLINE void OnIncrement( reg liHourInc, reg liMinInc ) { // increment the appropriate value (making sure we propagate the update) AddTimeDelta( liHourInc, liMinInc, 0, true ); WriteTimeDigit(); // all the other modules need to know the time too! gbNeedToSendTime = true; } INLINE void OnTick() { // increment the hundredths gCurrentTime.miHundredths += 10; // we're running @ 10Hz // Have we just passed a second? if( gCurrentTime.miHundredths >= 100 ) { gCurrentTime.miHundredths = 0; // update the time AddTimeDelta( 0, 0, 1, false ); } WriteTimeDigit(); if( kiFlashSeconds ) { if( gCurrentTime.miHundredths < 20 ) { PORTB |= KX_SEC_IND_BIT; } else { PORTB &= ~KX_SEC_IND_BIT; } } } // interrupt handler for timer hitting counter ISR( TIMER1_COMPA_vect ) { // On the master, handle input if( kbMaster ) { // Figure out what's changed reg lxNewPortB = PINB; reg lxChanged = gxLastPortB ^ lxNewPortB; if( lxChanged ) { gxLastPortB = lxNewPortB; reg lxJustLow = lxChanged & (~lxNewPortB); // has the increment been hit? if( lxJustLow & KX_nINC_MIN_BIT ) { OnIncrement( 0, 1 ); } if( lxJustLow & KX_nINC_HOUR_BIT ) { OnIncrement( 1, 0 ); } } } OnTick(); } // --------------------------------------------------------------------------------------------- // Molen Serial Interface // INLINE void InitMsiMaster() { // Make sure that PORTB is set up as we expect DDRB |= KX_MSI_DATA_BIT | KX_MSI_CLOCK_BIT; } INLINE void TermMsiMaster() { // Revert MSI pins to input with PU resistors DDRB &= ~( KX_MSI_DATA_BIT | KX_MSI_CLOCK_BIT ); PORTB |= ( KX_MSI_DATA_BIT | KX_MSI_CLOCK_BIT ); } INLINE void InitMsiSlave() { // Enable pin interrupt on clock pin PCMSK |= KX_MSI_CLOCK_BIT; GIMSK |= 1 << PCIE; } void BangBit( reg lbBit ) { // set the data if( lbBit ) { PORTB |= KX_MSI_DATA_BIT; } else { PORTB &= ~KX_MSI_DATA_BIT; } _delay_us( KI_MSI_TIMEBASE ); // send clock low PORTB &= ~KX_MSI_CLOCK_BIT; _delay_us( KI_MSI_TIMEBASE * 5 ); // send clock high PORTB |= KX_MSI_CLOCK_BIT; _delay_us( KI_MSI_TIMEBASE * 4 ); } INLINE void BangWord( reg liWord, reg liNumBits ) { for( reg x = (1 << (liNumBits - 1)); x; x >>= 1 ) { BangBit( liWord & x ); } } // --------------------------------------------------------------------------------------------- // Time Syncing // INLINE void SendTime() { InitMsiMaster(); // Send header for( reg i = 0; i < KI_XMIT_HDR_BITS; ++i ) { BangBit( 1 ); } BangBit( 0 ); // Send data BangWord( gXMitTime.miHours, KI_XMIT_HOUR_BITS ); BangWord( gXMitTime.miMinutes, KI_XMIT_MIN_BITS ); BangWord( gXMitTime.miSeconds, KI_XMIT_SEC_BITS ); BangWord( gXMitTime.miHundredths, KI_XMIT_HUN_BITS ); _delay_us( 200 ); TermMsiMaster(); } void ResetReceiveState() { geRecvState = E_RECV_HEADER; giRecvBits = 0; gXMitTime.miHours = 0; gXMitTime.miMinutes = 0; gXMitTime.miSeconds = 0; gXMitTime.miHundredths = 0; } // Return true if this word is finished reg HandleRecvBit( reg lbBit, reg* lpWord, reg liNumBits ) { *lpWord = ( *lpWord << 1 ) | lbBit; giRecvBits++; if( giRecvBits < liNumBits ) { return false; } giRecvBits = 0; geRecvState++; return true; } INLINE void HandleMsiBit( reg lbBit ) { switch( geRecvState ) { case E_RECV_HEADER: { if( lbBit ) { giRecvBits++; break; } // could be end of header! if( giRecvBits < KI_XMIT_HDR_BITS ) { // eep, some kind of error :( ResetReceiveState(); break; } giRecvBits = 0; geRecvState++; } break; case E_RECV_HOURS: { if( !HandleRecvBit( lbBit, &gXMitTime.miHours, KI_XMIT_HOUR_BITS ) ) { break; } } break; case E_RECV_MINUTES: { if( !HandleRecvBit( lbBit, &gXMitTime.miMinutes, KI_XMIT_MIN_BITS ) ) { break; } } break; case E_RECV_SECONDS: { if( !HandleRecvBit( lbBit, &gXMitTime.miSeconds, KI_XMIT_SEC_BITS ) ) { break; } } break; case E_RECV_HUNDREDTHS: { if( !HandleRecvBit( lbBit, &gXMitTime.miHundredths, KI_XMIT_HUN_BITS ) ) { break; } // That's the whole time received! gCurrentTime = gXMitTime; ResetReceiveState(); } break; } } ISR( PCINT_vect ) { // TODO: check that the pin that changed was the one we're after (currently it's the only one that fires an ISR) // and that it changed to high (MSI reads on rising edge) if( PINB & KX_MSI_CLOCK_BIT ) { HandleMsiBit( ( PINB & KX_MSI_DATA_BIT ) != 0 ); } } // --------------------------------------------------------------------------------------------- // Loop // INLINE void loop() { if( gbNeedToSendTime ) { // pause interrupts cli(); gXMitTime = gCurrentTime; gbNeedToSendTime = false; // re-enable intterupts sei(); SendTime(); } // sleepy time! sleep_mode(); } // --------------------------------------------------------------------------------------------- // Setup // INLINE void ReadConfig() { kiDisplaySlot = eeprom_read_byte( (byte*) 0 ); kiFlashSeconds = eeprom_read_byte( (byte*) 1 ); kbMaster = ( eeprom_read_byte( (byte*) 2 ) != 0 ); } INLINE void SetupPortB() { // PORTB needs to be set up a bit fancy-like reg liPortBDir = 0; // default to all inputs reg liPortBVal = ~0; // default to all pull-ups enabled // seconds indicator if( kiFlashSeconds ) { liPortBDir |= KX_SEC_IND_BIT; liPortBVal &= ~KX_SEC_IND_BIT; // default LED to output } // increments // - default of input with PU is OK // Write the settings out! DDRB = liPortBDir; PORTB = liPortBVal; // Now read in and store off the current value of the port so we can detect changes later gxLastPortB = PINB; } INLINE void SetupTimer() { // set up timer 1 to fire intterupt @ 10Hz (16 bit): // - prescaler: 8 (takes us down to 62500Hz) // - clear-on-timer-compare mode // - compare against OCR1A TCCR1A = 0; TCCR1B = ( 1 << WGM12 ) | ( 1 << CS11 ); // - stick 6249 in the 16 bit comparator register (6250-1 due to zero-base) OCR1A = 6249UL; // - enable interrupt on timer 1 hitting comparator A TIMSK = 1 << OCIE1A; } INLINE void setup() { // read our config from eeprom ReadConfig(); SetupPortB(); // we want port D to be all-output as it's connected to the display DDRD = 0x7f; // set up our sleep mode set_sleep_mode( SLEEP_MODE_IDLE ); // - also switch off bits we don't need to conserve power ACSR = ( 1 << ACD ); SetupTimer(); // If we aren't the master, we need to enable slave comms mode if( !kbMaster ) { InitMsiSlave(); } // enable interrupts (globally) sei(); // Write our initial digit (should always be 0...) WriteTimeDigit(); } // --------------------------------------------------------------------------------------------- // Main // int main() { // arduino-like main api setup(); for( ;; ) { loop(); } return 0; }