/** Copyright 2009 Al Cutter. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Definition of interrupt names #include // ISR interrupt service routine #include // Define a bunch of friendly names for pins // The _M defines are pre-shifted mask values #define HOOK (7) #define HOOK_M (1 << 7) #define RING (8) #define RING_M (1 << 0) #define ENDSTOP (19) #define ENDSTOP_M (1 << 5) #define ROTOR (18) #define ROTOR_M (1 << 4) #define LIST_ORANGE 14 #define LIST_RED 15 #define LIST_L_GREEN 16 #define LIST_PURPLE 17 #define SEND_PINK 2 #define SEND_BLACK 3 #define SEND_BROWN 4 #define SEND_D_GREEN 5 #define SEND_B_PEN 6 #define LIST_ORANGE_M (1 << 0) #define LIST_RED_M (1 << 1) #define LIST_L_GREEN_M (1 << 2) #define LIST_PURPLE_M (1 << 3) #define SEND_PINK_M (1 << 2) #define SEND_BLACK_M (1 << 3) #define SEND_BROWN_M (1 << 4) #define SEND_D_GREEN_M (1 << 5) #define SEND_B_PEN_M (1 << 6) #define SEND_OFF_HOOK_M (1 << 3) #define SEND_ON_HOOK_M (1 << 4) #define BELL1 (9) #define BELL2 (10) #define LED (13) // number of characters to buffer when we're dialing // this needs to be a power of 2 #define DIAL_BUF_SIZE (8) // couple of phantom key chars // to represent going on and off hook. #define KEY_OFF_HOOK (128) #define KEY_ON_HOOK (129) // number of consecutive cycles the ring-state must be high // before we go into ringing mode. // this suppresses sporadic tinkling #define RING_CHECK_CYCLES 50 // uncomment this to print debug data over the serial port. //#define DEBUG 1 // µs time stamp of when the ring-signal went high unsigned long ringUpMicros = micros(); // holds the last ring-signal state boolean ringSigLastState = LOW; //true if we think the phone is ringing boolean isRinging = false; // number of cycles that the ring-signal has been high int ringCheckCount; // the number of µs that the ring-signal is high unsigned long ringLength = 0; // true if we think the handset is on the cradle boolean onHook; // some variables to handle the rotary dialer boolean endStop = true; boolean newNumber = false; // holds the number of pulses coming back from the dialer int dialCount = 0; unsigned long lastDialIncMillis = 0; boolean lastRotorState = LOW; // circular buffer for keypresses byte dialledNumbers[DIAL_BUF_SIZE]; byte dialledNumbersHead = 0; byte dialledNumbersTail = 0; // variables to do with spoofing keypresses on the // DECT handset: // the key-code we want to spoof volatile byte dectKeyPress; // true if there is a key stored in dectKeyPress to be sent volatile boolean dectSendKey = false; // timestamp of when we started sending the key. volatile unsigned long dectStartPress; // precalc masks for the lines involved in sending the key-press volatile byte scanListenLine; volatile byte scanSendLine; // counter for the ringer unsigned long int bellCounter = 0; // our current timestamp, based on ring-signal cycles, millis() is way too expensive. unsigned long _millis = 0; boolean lastHookState; unsigned long hookTimer; // timestamp of when the phone last went on-hook // used to squash a little tinkle just after hanging up. unsigned long lastHookDownTime; // make keymatrix return lines outputs // so we can spoof the matrix scanning signals inline void prepareKeySending() { pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(6, OUTPUT); } // put the keymatrix return lines into Hi-Z // when we don't want to send keystrokes // this allows the regular keypad to still work // otherwise we pull the lines to GND. inline void finishKeySending() { pinMode(2, INPUT); pinMode(3, INPUT); pinMode(4, INPUT); pinMode(5, INPUT); pinMode(6, INPUT); digitalWrite(2, LOW); digitalWrite(3, LOW); digitalWrite(4, LOW); digitalWrite(5, LOW); digitalWrite(6, LOW); } /* * adds a number to the buffer of numbers to be dialed. */ inline void addDialledNumber(byte n) { if ((dialledNumbersHead + 1) % DIAL_BUF_SIZE == dialledNumbersTail) { #ifdef DEBUG Serial.println("dial buffer overflow"); #endif } dialledNumbers[dialledNumbersHead++] = n; dialledNumbersHead &= DIAL_BUF_SIZE - 1; } /* * returns true if there's a keypress waiting in the buffer */ inline boolean haveDialledNumber() { return dialledNumbersHead != dialledNumbersTail; } /* * gets the next keypress from the buffer */ int getDialledNumber() { if (!haveDialledNumber()) { #ifdef DEBUG Serial.println("dial buffer underflow"); #endif } byte r = dialledNumbers[dialledNumbersTail]; dialledNumbers[dialledNumbersTail++] = -1; dialledNumbersTail &= DIAL_BUF_SIZE - 1; return r; } /* * sets up the scan masks for the ISR to spoof the next keypress */ void dectPress(byte key) { prepareKeySending(); while (dectSendKey) {} // wait for last keypress to get eaten switch (key) { case 1: scanListenLine = LIST_L_GREEN_M; scanSendLine = SEND_D_GREEN_M; break; case 2: scanListenLine = LIST_PURPLE_M; scanSendLine = SEND_D_GREEN_M; break; case 3: scanListenLine = LIST_RED_M; scanSendLine = SEND_D_GREEN_M; break; case 4: scanListenLine = LIST_L_GREEN_M; scanSendLine = SEND_PINK_M; break; case 5: scanListenLine = LIST_PURPLE_M; scanSendLine = SEND_PINK_M; break; case 6: scanListenLine = LIST_RED_M; scanSendLine = SEND_PINK_M; break; case 7: scanListenLine = LIST_L_GREEN_M; scanSendLine = SEND_BROWN_M; break; case 8: scanListenLine = LIST_PURPLE_M; scanSendLine = SEND_BROWN_M; break; case 9: scanListenLine = LIST_RED_M; scanSendLine = SEND_BROWN_M; break; case 10: scanListenLine = LIST_PURPLE_M; scanSendLine = SEND_BLACK_M; break; case KEY_ON_HOOK: scanListenLine = LIST_ORANGE_M; scanSendLine = SEND_B_PEN_M; break; case KEY_OFF_HOOK: scanListenLine = LIST_ORANGE_M; scanSendLine = SEND_D_GREEN_M; break; } dectKeyPress = key; dectSendKey = true; } /* * Int0 * handles the ring signal */ //PCINT0 - RingSig (D8) ISR(PCINT0_vect) { if (!PINB & RING_M) { unsigned long now = TCNT0; if (now >= ringUpMicros) { now = now - ringUpMicros; ringLength = now; if (now < 120) { ringCheckCount++; } else { ringCheckCount = 0; } isRinging = ringCheckCount >= RING_CHECK_CYCLES; } ringUpMicros = TCNT0; } _millis++; } unsigned long a; /* * Int1 * Handles keymatrix scanning from the DECT handset, and monitors the rotary dialer. * * this ISR needs to be tight ! * if this ISR takes too long to execute we can't properly * spoof the key matrix scanning on the DECT handset. */ ISR(PCINT1_vect) { // no timers or anything else now please noInterrupts(); // do we need to spoof a keypress ? if (dectSendKey) { // if it's the ON/OFF HOOK key then we're doing that differently // (we use two transistors here for reliability) if (dectKeyPress == KEY_OFF_HOOK || dectKeyPress == KEY_ON_HOOK) { if (dectKeyPress == KEY_OFF_HOOK) { PORTB |= SEND_ON_HOOK_M; } else { PORTB |= SEND_OFF_HOOK_M; } // tick dectStartPress++; // have we pressed the key long enough ? if (dectStartPress > 800) { // yes, clean up. dectStartPress = 0; dectSendKey = false; PORTB &= ~(SEND_ON_HOOK_M | SEND_OFF_HOOK_M); finishKeySending(); } } else { // it's a key other than ON/OFF HOOK. // mirror the scan send line to the right scan return line while (PINC & scanListenLine) { PORTD |= scanSendLine; } // the send line has gone LOW so mirror that too PORTD &= ~scanSendLine; //tick dectStartPress++; // have we pressed the key long enough ? if (dectStartPress > 300) { // yes, clean up. dectStartPress = 0; dectSendKey = false; PORTD &= ~scanSendLine; finishKeySending(); } } // endif have keypress // ok, interrupts back on now. interrupts(); } // now deal with the rotor endStop = PINC & ENDSTOP_M; boolean rotorState = PINC & ROTOR_M; // if we're not at the endstop and the rotor digit signal has // changed since last time if (!endStop && (lastRotorState != rotorState)) { unsigned long now = _millis; if (rotorState == LOW) { // de-bounce if (now - lastDialIncMillis > 2) { // increment the pulse train counter - this is the number that's being dialed dialCount++; newNumber = true; } } else { lastDialIncMillis = now; } lastRotorState = rotorState; } else if (endStop && newNumber) { // no-one's dialing, but is there a new number ? // yes, so add it to the buffer addDialledNumber(dialCount); // and clean up newNumber = false; dialCount = 0; } } /* * Entry point * Sets up a bunch of stuff */ void setup() { // setup default variable states dectSendKey = false; dectStartPress = 0; dialledNumbersHead = 0; dialledNumbersTail = 0; newNumber = false; endStop = false; ringCheckCount = 0; #ifdef DEBUG Serial.begin(9600); #endif // setup pin directions pinMode(8, INPUT); pinMode(7, INPUT); pinMode(14, INPUT); pinMode(15, INPUT); pinMode(16, INPUT); pinMode(17, INPUT); pinMode(18, INPUT); pinMode(19, INPUT); pinMode(9, OUTPUT); pinMode(10, OUTPUT); pinMode(11, OUTPUT); pinMode(12, OUTPUT); pinMode(13, OUTPUT); // put scanner lines into highZ finishKeySending(); // set pull-ups digitalWrite(7, 1); digitalWrite(18, 1); digitalWrite(19, 1); // setup interrupts // we want pin-change ints on a bunch of places: PCMSK0 = (1 << PCINT0); PCMSK1 = (1 << PCINT13) | ( 1 << PCINT12) | (1 << PCINT11) | (1 << PCINT10) | (1 << PCINT9) | (1 << PCINT8); PCMSK2 = (1 << PCINT23); PCICR = (1 << PCIE1) | (1 << PCIE0); #ifdef DEBUG Serial.println("go"); #endif onHook = digitalRead(HOOK); } /* * Checks the state of the on/off hook switch */ void doHookCheck() { boolean hookState = (PIND & HOOK_M); if (lastHookState != hookState) { // hook state has changed // grab the new state onHook = hookState; if (onHook) { // add the phantom on-hook keypress addDialledNumber(KEY_ON_HOOK); lastHookDownTime = _millis; } else { // add the phantom off-hook keypress addDialledNumber(KEY_OFF_HOOK); } } lastHookState = hookState; } #define BELL_LEN 3 /* * Handles the ringers. */ void doBellRinger() { // are we getting the ring-signal, and we're on-hook, and // we're > 300 cycles since the receiver was last put back on hook ? if (isRinging && onHook && (_millis - lastHookDownTime > 300)) { // cool, then we're ringing // handle the on/off ringer pattern: if (((bellCounter >=0) && (bellCounter< BELL_LEN)) || ((bellCounter >= BELL_LEN*1.5) && (bellCounter < BELL_LEN*2.9))) { digitalWrite(BELL1, 1); digitalWrite(BELL2, 1); digitalWrite(LED, 1); } else { digitalWrite(BELL1, 0); digitalWrite(BELL2, 0); digitalWrite(LED, 0); } bellCounter++; if (bellCounter > (5 * BELL_LEN)) { bellCounter = 0; } } else { // ringing ? // nope, shut the hell up: digitalWrite(BELL1, 0); digitalWrite(BELL2, 0); digitalWrite(LED, 0); } } /* * Main loop */ void loop() { doHookCheck(); doBellRinger(); #ifdef DEBUG Serial.print(ringLength); Serial.print(' '); Serial.print(isRinging ? "ring" : ""); Serial.print(' '); if (onHook) { Serial.print("onHook"); } #endif if (haveDialledNumber()) { // Serial.print('get number '); int key = getDialledNumber(); #ifdef DEBUG Serial.print(' '); Serial.print(key); #endif dectPress(key); } #ifdef DEBUG Serial.print(' '); Serial.print(dectSendKey ? 't' : 'f'); Serial.println(); #endif //TODO: checkout going to low-power sleep here //zzzz delay(200); }