//#include <EEPROM.h>
#include <Keypad.h>
#include <LiquidCrystal.h>
//#include "ArduinoLowPower.h" //Any SAMD21 Based Arduino Boards (MKR Family)
const byte ROWS = 4;
const byte COLS = 4;
char hexaKeys[ROWS][COLS] = {
{'1', '2', '3', 'A'}, //697Hz
{'4', '5', '6', 'B'}, //770Hz
{'7', '8', '9', 'C'}, //852Hz
{'*', '0', '#', 'D'} //941Hz
//1209Hz, 1336Hz, 1477Hz, 1633Hz
};
byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 10, d4 = A0, d5 = A1, d6 = A2, d7 = A3;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
//*************************************************************************
//** EEPROM stuff: Store short dial numbers in the setup we check if storage was initialized
//** as we have enough space we reserve 20 Byte per number and store it as String
//*****************************************************************************
//static const char EEPROM_init[] = "DTMF Dial";
//***************************************************************************
//** DTMF Generator based on ATMEL AVR314 Application note
//***************************************************************************
#define prescaler 1 // timer2 prescaler
#define N_sample 128 // Number of samples in lookup table
#define Fck F_CPU/prescaler // Timer2 working frequency
#define DTMFout 11 //DTNF output pin 3 = OC2B, timer2
#define MUTE 4 //Microphone Mute
#define PO 5 //Pulse Out, useful for handsfree application
#define HKS 6 //Hook State, no use for basic phone
#define LED 13
//************************************************************************
//** HKS pin is logic high when On Hook, active low when Off Hook
//** It is controlled by PO when handsfree.
//*************************************************************************
//************************** SIN TABLE *************************************
// Samples table : one period sampled on 128 samples and quantized on 7 bit
//**************************************************************************
const unsigned char Sin128[N_sample] = {
64,67,70,73,76,79,82,85,88,91,94,96,99,102,104,106,109,111,113,115,117,118,120,
121,123,124,125,126,126,127,127,127,127,127,127,127,126,126,125,124,123,121,120,
118,117,115,113,111,109,106,104,102,99,96,94,91,88,85,82,79,76,73,70,67,64,60,57,
54,51,48,45,42,39,36,33,31,28,25,23,21,18,16,14,12,10,9,7,6,4,3,2,1,1,0,0,0,0,0,
0,0,1,1,2,3,4,6,7,9,10,12,14,16,18,21,23,25,28,31,33,36,39,42,45,48,51,54,57,60};
//*************************** x_SW ***************************************
//Table of x_SW (excess 8): x_SW = ROUND(8*N_samples*f*510/Fck)
//ToDo calculate values in the preprocessor ..but be careful rounding errors
// might violate 1.5% frequency tolerance of DTMF
//**************************************************************************
#define SWC(x) ((x)*N_sample*8*510/Fck) //Step_Width Calculation
//const unsigned char auc_freqH [4] = {SWC(1209), SWC(1336), SWC(1477), SWC(1633)};
#if Fck == 8000000 // 8 MHz
const unsigned char auc_freqH [4] = {79,87,96,107}; //8MHz
const unsigned char auc_freqL [4] = {46,50,56,62}; //8MHz
#else // 16MHz onboard
const unsigned char auc_freqH [4] = {40, 44, 48,53}; //@16MHz
const unsigned char auc_freqL [4] = {23, 25, 28, 31};
#endif
const String DialPad = "123A456B789C*0#D"; // position for lookup into Freq. Table
//************************** global variables ****************************
volatile unsigned char x_SWh = 0x00; // step width of high frequency
volatile unsigned char x_SWl = 0x00; // step width of low frequency
unsigned int i_CurSinH = 0; // position freq. A in LUT (extended format)
unsigned int i_CurSinL = 0; // position freq. B in LUT (extended format)
unsigned int i_TmpSinH; // position freq. A in LUT (actual position)
unsigned int i_TmpSinL; // position freq. B in LUT (actual position)
//**************************************************************************
// Timer overflow interrupt service routine for DTMF sine wave
//**************************************************************************
ISR (TIMER2_OVF_vect)
{
if (x_SWl) { // dual tone
// move Pointer about step width ahead
i_CurSinH += x_SWh;
i_CurSinL += x_SWl;
// normalize Temp-Pointer
i_TmpSinH = (char)(((i_CurSinH+4) >> 3)&(0x007F));
i_TmpSinL = (char)(((i_CurSinL+4) >> 3)&(0x007F));
// calculate PWM value: high frequency value + 3/4 low frequency value
OCR2A = (Sin128[i_TmpSinH] + (Sin128[i_TmpSinL]-(Sin128[i_TmpSinL]>>2)));
} else { // single tone
i_CurSinH += x_SWh;
i_TmpSinH = (char)(((i_CurSinH+4) >> 3)&(0x007F));
OCR2B = Sin128[i_TmpSinH];
}
}
// counts the HW impulse from HKS on the FALLING edge
/*void ISR_countImpulse (void)
{ //noInterrupts(); ToDo not sure if this is needed
if ((millis() - last_impulse_at) > 25) {
// we are in the middle of a rotating disk
// we check for bouncing, normal impulse take 100 ms (60/40)
// if diff is too short we ignore it
iwvcounter++;
last_impulse_at = millis();
};
//interrupts();
}*/
void dialNumber(String nrstr) {
char digitchar;
for (byte i=0; i< nrstr.length(); i++) {
digitchar = nrstr.charAt(i); //get char by char from dial number string
dialDigit(digitchar);
//Serial.print(digitchar);
}
}
void dialDigit(char digitchar) {
unsigned short digitpos = DialPad.indexOf(digitchar); //get position
x_SWh = auc_freqH[(digitpos & 0x03)]; // column of 4x4 DTMF Table
x_SWl = auc_freqL[(digitpos /4)]; //row of DTMF Table
pinMode(DTMFout, OUTPUT); //output pin ready
bitSet(TIMSK2,TOIE2); // timer interrupt on
delay (120); //tone duration
//tone off
pinMode(DTMFout, INPUT); //make it high impedance
bitClear(TIMSK2,TOIE2);
delay (100); // pause
}
//Single frequency audio generator
// a bit ugly hack to reuse the DTMF tone generation ISR
void sendTone (char auc_tone, unsigned int duration) {
x_SWh = auc_tone;
x_SWl = 0; //single tone
pinMode(DTMFout, OUTPUT); //output pin ready
bitSet(TIMSK2,TOIE2); // timer interrupt on
delay (duration); //tone duration
pinMode(DTMFout, INPUT); //make it high impedance when turn off
bitClear(TIMSK2,TOIE2); //switch off ISR
}
//#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
//#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
//******************************************************************
// timer2 setup
// set prscaler to 1, PWM mode to phase correct PWM, 16000000/510 = 31372.55 Hz clock
/*void Setup_timer2() {
// Timer2 Clock Prescaler to : 1
sbi (TCCR2B, CS20);
cbi (TCCR2B, CS21);
cbi (TCCR2B, CS22);
// Timer2 PWM Mode set to Phase Correct PWM
cbi (TCCR2A, COM2A0); // clear Compare Match
sbi (TCCR2A, COM2A1);
sbi (TCCR2A, WGM20); // Mode 1 / Phase Correct PWM
cbi (TCCR2A, WGM21);
cbi (TCCR2B, WGM22);
}*/
//**************************************************************************
// Initialization
//**************************************************************************
void setup ()
{
Serial.begin(9600); //for debug purpose
//Serial.println("DTMF Test");
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
// Print a message to the LCD.
lcd.print("DTMF Test!");
lcd.setCursor(0, 1);
pinMode(LED, OUTPUT);
pinMode(DTMFout, OUTPUT);
pinMode(MUTE, OUTPUT);
pinMode(PO,OUTPUT);
pinMode(HKS, INPUT_PULLUP);
//setup HKS switch counter ISR
//attachPCINT(digitalPinToPCINT(HKS), ISR_countImpulse, RISING);
cli(); // disable all interrupts
//Setup_timer2();
//ToDo is that correct?
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
//TCCR2A |= (1<<WGM21);
//bitSet(TCCR2A,WGM20);
//bitSet(TCCR2B, WGM22);
TCCR2A |= _BV(COM2A1) | _BV(COM2B1) | /*_BV(WGM21) |*/ _BV(WGM20);
//TCCR2A = (1<<COM2B1)+(1<<WGM20); // non inverting / 8Bit PWM
//TCCR2B = (1 << CS20); // CLK/1
TCCR2B=(TCCR2B & 0b11111000) | 0x01;
bitClear(TIMSK2,TOIE2);
sei(); // enable all interrupts
// timer interrupt is enabled during dialing: bitSet(TIMSK2,TOIE2)
// EEPROM init check
/* unsigned short iee;
for (iee =0; iee < sizeof(EEPROM_init); iee++) {
if (EEPROM.read(iee) != EEPROM_init[iee]) break;
}
//if we did not reach the end of the compare string, we (re)initial the EEPROM storage structure
if (iee < sizeof(EEPROM_init)) {
Serial.println("init EEPROM");
//EEPROM.write(0,EEPROM_init);
for (iee=1; iee < 6; iee++) { // 1..5
EEPROM.write(iee*sizeof(phoneNumber),0); //set the phone
}
}*/
//if (!HKS) {
digitalWrite(PO, HIGH);
digitalWrite(LED, HIGH);
delay(200);
digitalWrite(MUTE, LOW);
//Serial.println("Call ...");
//sendTone(SWC(400), 800);
//sei();
//dialNumber("02");
//delay(5000);
//}
}
/*unsigned long diff_iwv = 0;
unsigned long nsa_diff = 0;
unsigned short newdigit = 0;
String numberstr = String("");
String numberstr2 = String("");*/
//unsigned long lastdigittime = 0; //when has the last digit been dialed? reset the state 2
bool offhook = false;
String Call = String("");
String lastCall = String("");
void loop ()
{
char customKey = customKeypad.getKey();
if (customKey) {
//Serial.println(customKey);
switch(customKey)
{
case '0' ... '9' : // This keeps collecting the first value until a operator is pressed "+-*/"
Call.concat(customKey);
lcd.print(customKey);
break;
case '*':
lcd.print('*');
dialDigit('*');
break;
case '#':
lcd.print('#');
dialDigit('#');
break;
case 'A': //On/OFF
offhook = !offhook;
if (offhook) {
lcd.clear();
lcd.println("Try again?");
lcd.setCursor(0, 1);
tone(DTMFout, 440); //Dial tone when ON
delay(1000); //Timeout
}
else {
noTone(DTMFout);
lcd.clear();
lcd.println("Disconnected!");
}
//LowPower.sleep();
break;
case 'B': //Backspace to delete last one
if(Call.length() > 0) {
int lastIndex = Call.length() - 1;
Call.remove(lastIndex);
}
lcd.command(0x10); //Shift cursor command, no backspace
lcd.command(0xF); //Just blink, sorry
break;
case 'C':
lcd.setCursor(0, 1);
Call.trim(); // remove any \r \n whitespace at the end of the String
if ((Call.length()) > 0) {
//Serial.println("Call...");
//lcd.setCursor(0, 1);
lcd.print("Call:"+Call);
dialNumber(Call);
lastCall = Call;
Call = String("");
} else{
lcd.print("No number to call");
}
break;
case 'D': //Redial
lcd.setCursor(0,1);
lcd.print("Redial:"+lastCall);
dialNumber(lastCall);
break;
default:
//cli();
break;
}
//Call = String(""); //Empty the number
}
}
/*if (customKey){
Serial.println(customKey);
}
Serial.println("Enter Number:");
while (Serial.available() == 0) {} //wait for data available
String Call = Serial.readString(); //read until timeout
Call.trim(); // remove any \r \n whitespace at the end of the String
Serial.println("Call...");
dialNumber(Call);
Serial.println("");*/
//sendTone(SWC(400), 800);
//dialNumber("3515768");
//Hook Switch check and timing check here; do not forget ivw counter
//finish state 2
/*if ((nsa_state == 2) && ((millis() - lastdigittime) > STATE2_TIME_OUT)) {
nsa_state = 0; // reset the state
//get the storage place (first digit)
//store the remaining string at position
//Serial.println(numberstr2);
boolean store_ok = false;
if ((numberstr2.length() > 1) && (numberstr2.length() < 20)){
unsigned short pos = numberstr2.charAt(0) - '0';
numberstr2.remove(0,1);
if ((pos > 0) && (pos <6)) {
numberstr2.toCharArray(phoneNumber,sizeof(phoneNumber));
//EEPROM.put(pos*sizeof(phoneNumber), phoneNumber);
store_ok = true;
}
}
//send notification tone
if (store_ok) {
sendTone(auc600Hz, 300); delay(50);
sendTone(auc1KHz, 300);
}
else {
sendTone(auc600Hz, 100); delay(50);
sendTone(auc600Hz, 100); delay(50);
sendTone(auc600Hz, 100); delay(50);
sendTone(auc600Hz, 100); delay(50);
}
//clear numberstring
numberstr2.remove(0); //deletes the String but keeps the object
} // reset state 2
if (iwvcounter == 0) { //to make sure IWV has not started = disk not released
if ((nsa_switch.read() == Button::PRESSED) && (nsa_state < 2)) {
if (nsa_switch.pressed()) nsa_start = millis(); //get the one shot event
nsa_diff = millis() - nsa_start;
if (nsa_diff > NSA_long ) { // we enter 2nd level
nsa_state = 2;
numberstr.remove(0); //delete the numberstring
lastdigittime = millis (); // we start state 2
sendTone(auc1KHz, 300); //notify the user that he can release the disk
} else if (( nsa_diff > NSA_short) && (nsa_state < 1)){
// we enter 1st level
nsa_state = 1;
sendTone(auc600Hz, 100); //notify the user that he can release the disk
}
// it is to short we have to wait
} //nsa_switch pressed
} else { // there has been IWV impulse iwvcounter != 0
//we had a race condition, so make it atomic
// ToDo could we double check the race??
noInterrupts();
diff_iwv = millis () - last_impulse_at;
interrupts();
if ( (diff_iwv > 300)) {
// next impulse would belong to a new digit
newdigit = iwvcounter%10; // 10 -> 0
iwvcounter = 0; //reset the counter for a new digit
lastdigittime = millis();
switch (nsa_state) { //in which dial mode we are?
case 0 : dialDigit(newdigit +'0');
break;
case 1 : numberstr = shift_func (newdigit);
dialNumber(numberstr);
//Serial.println(numberstr);
nsa_state = 0; // one shot function
break;
case 2 : //add digit to number string
numberstr2.concat(String(newdigit, DEC));
break; // we handle here the storage
}
//Serial.print(numberstr);
}
}*/
//}