/**
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);
}