Compare commits

..

1 Commits

Author SHA1 Message Date
b47267400e these changes were sitting on my disk from years ago 2022-11-12 22:06:14 -07:00
56 changed files with 259 additions and 344 deletions

View File

@ -1,211 +1,249 @@
#include "avr/sleep.h" #include "avr/sleep.h"
#include "Adafruit_FONA.h" #include "Adafruit_FONA.h"
#include "Keypad.h" #include "Keypad.h"
//////////////////////////////// ////////////////////////////////
////////// PARAMETERS ////////// ////////// PARAMETERS //////////
//////////////////////////////// ////////////////////////////////
#define LED_BAT_LOW 3 // Arduino will wake from sleep when WAKE_PIN is pulled low. Connect any waking buttons to WAKE_PIN
#define LED_NO_SERVICE 2 #define WAKE_PIN 2
#define BUT_ANS A3 #define BUT_ANS A3
#define BUT_END A4 #define BUT_END A4
#define GSM_RST A2 // RX/TX are in reference to the Arduino, not SIM800
#define GSM_RING A5 #define GSM_RX 3
#define GSM_BAUDRATE 115200 #define GSM_TX 11
#define GSM_RST A2
// Low battery light threshold #define GSM_RING A5
#define CHG_VLO 3800
// TIMEOUT_SLEEP is the time to stay awake from last activity until sleep (milliseconds)
// Time in miliseconds to stop listening for keypad input #define TIMEOUT_SLEEP 6000
#define SLEEP_TIMEOUT 120000
// Charging voltage thresholds. Will turn on charging at CHG_VLO and turn off charging at CHG_VHI (millivolts)
// RSSI value below which No Service LED will light #define CHG_VLO 3900
#define RSSI_THRESHOLD 1 #define CHG_VHI 4100
#define CHG_PIN A0
// Keypad pinout
byte rowPins[4] = {5, 10, 9, 7}; // Comment the following line to use HW serial for Fona and disable debugging information
byte colPins[3] = {6, 4, 8}; #define USB_DEBUG
//////////////////////////////////// // Comment the following line to disable sleeping the Arduino
////////// END PARAMETERS ////////// //#define SLEEP
////////////////////////////////////
// Keypad pinout
char phoneNumber[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; byte rowPins[4] = {9, 4, 5, 7};
int phoneNumberLength = 0; byte colPins[3] = {8, 10, 6};
char keys[4][3] = { ////////////////////////////////////
{'1', '2', '3'}, ////////// END PARAMETERS //////////
{'4', '5', '6'}, ////////////////////////////////////
{'7', '8', '9'},
{'*', '0', '#'} char phoneNumber[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
}; int phoneNumberLength = 0;
unsigned long lastActiveTime; char keys[4][3] = {
bool dialtoneActive = false; {'1', '2', '3'},
bool startDialtone = false; {'4', '5', '6'},
bool awake = true; {'7', '8', '9'},
{'*', '0', '#'}
HardwareSerial *fonaSerial = &Serial; };
Adafruit_FONA fona = Adafruit_FONA(GSM_RST);
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, 4, 3 ); unsigned long lastActiveTime;
bool dialtoneActive = false;
/////////////////////////////// bool startDialtone = false;
////////// FUNCTIONS //////////
/////////////////////////////// #ifdef USB_DEBUG
#include "SoftwareSerial.h"
void inCall() { SoftwareSerial fonaSS = SoftwareSerial(GSM_RX, GSM_TX);
while (1) { SoftwareSerial *fonaSerial = &fonaSS;
if ( !digitalRead(BUT_END) || fona.getCallStatus() < 3 ) { // End button pressed #else
fona.hangUp(); HardwareSerial *fonaSerial = &Serial;
break; #endif
}
Adafruit_FONA fona = Adafruit_FONA(GSM_RST);
// numpad stuff Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, 4, 3 );
}
clearPhoneNumber();
startDialtone = true; ///////////////////////////////
lastActiveTime = millis(); ////////// FUNCTIONS //////////
delay(100); ///////////////////////////////
}
void wakeFromSleep() {
void beginCall() { sleep_disable();
fona.sendCheckReply( F("AT+STTONE=0"), F("OK") ); // End dialtone detachInterrupt(WAKE_PIN);
dialtoneActive = false; #ifdef USB_DEBUG
if ( fona.callPhone( phoneNumber ) ) { Serial.println("Woke up");
inCall(); #endif
} startDialtone = true;
} }
void resumeDialtone() { void goToSleep() {
fona.sendCheckReply( ("AT+STTONE=1,20," + String(SLEEP_TIMEOUT)).c_str(), "OK" ); // Start dialtone #ifdef USB_DEBUG
dialtoneActive = true; Serial.println("Going to sleep");
} #endif
sleep_enable();
void clearPhoneNumber() { attachInterrupt(WAKE_PIN, wakeFromSleep, LOW);
for ( int j = 0; j < phoneNumberLength; j++) { set_sleep_mode(SLEEP_MODE_PWR_DOWN);
phoneNumber[j] = 0; cli();
} sleep_bod_disable();
phoneNumberLength = 0; sei();
} sleep_cpu();
}
void goToSleep() {
digitalWrite( LED_NO_SERVICE, LOW ); void inCall() {
digitalWrite( LED_BAT_LOW, LOW ); while (1) {
awake = false; if ( !digitalRead(BUT_END) || fona.getCallStatus() < 3 ) { // End button pressed
} fona.hangUp();
#ifdef USB_DEBUG
////////////////////////////////// Serial.println("Hanging up");
///// ARDUINO CORE FUNCTIONS ///// #endif
////////////////////////////////// break;
}
void setup() {
pinMode( BUT_ANS, INPUT_PULLUP ); // numpad stuff
pinMode( BUT_END, INPUT_PULLUP ); }
pinMode( GSM_RST, OUTPUT ); for ( int j = 0; j < phoneNumberLength; j++) {
pinMode( GSM_RING, INPUT_PULLUP ); phoneNumber[j] = 0;
pinMode( LED_BAT_LOW, OUTPUT); }
pinMode( LED_NO_SERVICE, OUTPUT); phoneNumberLength = 0;
startDialtone = true;
digitalWrite( LED_BAT_LOW, HIGH ); }
digitalWrite( LED_NO_SERVICE, HIGH );
void beginCall() {
digitalWrite( GSM_RST, HIGH ); #ifdef USB_DEBUG
fonaSerial->begin(GSM_BAUDRATE); Serial.println("Starting Call");
if (! fona.begin(*fonaSerial)) { #endif
while (1); //fona didn't start fona.sendCheckReply( F("AT+STTONE=0"), F("OK") ); // End dialtone
digitalWrite( LED_BAT_LOW, HIGH ); dialtoneActive = false;
} if ( fona.callPhone( phoneNumber ) ) {
#ifdef USB_DEBUG
startDialtone = true; Serial.println("Call Started");
lastActiveTime = millis(); #endif
inCall();
fona.sendCheckReply( F("AT+CLVL=100"), F("OK") ); // set volume }
#ifdef USB_DEBUG
digitalWrite( LED_BAT_LOW, LOW ); Serial.println("Call ended");
digitalWrite( LED_NO_SERVICE, LOW ); #endif
} }
void loop() { void resumeDialtone() {
if ( awake ) { fona.sendCheckReply( F("AT+STTONE=1,20,30000" ), F("OK") ); // Start dialtone
dialtoneActive = true;
// Handle Incoming Call }
if ( !digitalRead(GSM_RING) ) {
while ( digitalRead(BUT_ANS) & digitalRead(BUT_END) & !digitalRead(GSM_RING) ) delay(10); // Wait for answer button or end ring/call //////////////////////////////////
if ( !digitalRead(BUT_ANS) ) { ///// ARDUINO CORE FUNCTIONS /////
fona.pickUp(); //////////////////////////////////
delay(100);
inCall(); void setup() {
} else if( !digitalRead(BUT_END) ) { #ifdef USB_DEBUG
fona.hangUp(); Serial.begin(9600);
clearPhoneNumber(); Serial.println("Booted. Setting up");
startDialtone = true; #endif
delay(100); pinMode( WAKE_PIN, INPUT_PULLUP );
resumeDialtone(); pinMode( BUT_ANS, INPUT_PULLUP );
lastActiveTime = millis(); pinMode( BUT_END, INPUT_PULLUP );
} pinMode( GSM_RST, OUTPUT );
} pinMode( GSM_RING, INPUT_PULLUP );
pinMode( CHG_PIN, OUTPUT );
// Begin dialtone if necessary
if ( startDialtone ) { digitalWrite( GSM_RST, HIGH );
resumeDialtone(); digitalWrite( CHG_PIN, HIGH );
startDialtone = false;
} fonaSerial->begin(4800);
if (! fona.begin(*fonaSerial)) {
// Read keypad input #ifdef USB_DEBUG
char key = keypad.getKey(); Serial.println("Fona failed to respond");
if ( key != NO_KEY ) { #endif
phoneNumber[ phoneNumberLength ] = key; while (1); //fona didn't start
phoneNumberLength = phoneNumberLength + 1; }
while ( !fona.setAudio(FONA_EXTAUDIO) ) {}
if ( dialtoneActive ) {
fona.sendCheckReply( F("AT+STTONE=0"), F("OK") ); // End dialtone #ifdef USB_DEBUG
dialtoneActive = false; Serial.println("Setup Complete");
delay(100); #endif
} startDialtone = true;
fona.playDTMF( key ); // Play DTMF tone lastActiveTime = millis();
lastActiveTime = millis(); fona.sendCheckReply( F("AT+CLVL=20"), F("OK") ); // set dialtone volume
// Check for complete phone number (including +1 country code) }
if ( ( phoneNumberLength == 10 & phoneNumber[0] != '1' ) || phoneNumberLength > 10 ) {
beginCall(); void loop() {
} if ( !digitalRead(GSM_RING) ) {
} while ( digitalRead(BUT_ANS) & !digitalRead(GSM_RING) ) delay(10); // Wait for answer button or end ring/call
if ( !digitalRead(BUT_ANS) ) {
// Clear stored phone number on press of end button fona.pickUp();
if ( !digitalRead(BUT_END) ) { delay(100);
clearPhoneNumber(); inCall();
resumeDialtone(); }
} }
// Indicator LEDs if ( startDialtone ) {
uint16_t vbat; resumeDialtone();
fona.getBattVoltage(&vbat); startDialtone = false;
if ( vbat < CHG_VLO ) { }
digitalWrite( LED_BAT_LOW, HIGH );
} else { // Read from keypad
digitalWrite( LED_BAT_LOW, LOW ); char key = keypad.getKey();
} if ( key != NO_KEY ) {
phoneNumber[ phoneNumberLength ] = key;
uint8_t rssi; phoneNumberLength = phoneNumberLength + 1;
rssi = fona.getRSSI();
if ( rssi < RSSI_THRESHOLD || rssi == 99 ) { if ( dialtoneActive ) {
digitalWrite( LED_NO_SERVICE, HIGH ); fona.sendCheckReply( F("AT+STTONE=0"), F("OK") ); // End dialtone
} else { dialtoneActive = false;
digitalWrite( LED_NO_SERVICE, LOW ); delay(50);
} }
fona.playDTMF( key ); // Play DTMF tone
// If inactive, sleep
if ( (long)(millis() - lastActiveTime) > SLEEP_TIMEOUT ) { lastActiveTime = millis();
goToSleep(); #ifdef USB_DEBUG
} int i;
} else { for (i = 0; i < 15; i = i + 1) {
// sleeping Serial.print(phoneNumber[i]);
if ( !digitalRead( BUT_ANS ) || !digitalRead( BUT_END ) || !digitalRead( GSM_RING ) ) { Serial.print(", ");
lastActiveTime = millis(); }
awake = true; Serial.println(phoneNumberLength);
} #endif
} // Check for complete phone number (including +1 country code)
} if ( ( phoneNumberLength == 10 & phoneNumber[0] != '1' ) || phoneNumberLength > 10 ) {
beginCall();
}
}
// Clear stored phone number on press of end button
if ( !digitalRead(BUT_END) ) {
for ( int j = 0; j < phoneNumberLength; j++) {
phoneNumber[j] = 0;
}
phoneNumberLength = 0;
#ifdef USB_DEBUG
Serial.println( phoneNumberLength );
#endif
resumeDialtone();
}
uint16_t vbat;
fona.getBattVoltage(&vbat);
if ( vbat < CHG_VLO ) {
digitalWrite( CHG_PIN, HIGH );
} else if ( vbat > CHG_VHI ) {
digitalWrite( CHG_PIN, LOW );
}
#ifdef SLEEP
// Autoshutdown if inactive for extended period
// Typecast to long avoids "rollover" issues
if ( (long)(millis() - lastActiveTime) > TIMEOUT_SLEEP ) {
goToSleep();
}
#endif
}

View File

@ -1,10 +1,10 @@
# Hohm-Phone # Hohm-Phone
## Hardware Connections ## Hardware Connections
-->|-- Indicates diode -->|-- Indicates diode
WAKE_PIN -->|-- GSM_RING --- RI(Fona) WAKE_PIN -->|-- GSM_RING --- RI(Fona)
WAKE_PIN -->|-- BUT_ANS ---- Button --- GND WAKE_PIN -->|-- BUT_ANS ---- Button --- GND
WAKE_PIN -->|-- BUT_END ---- Button --- GND WAKE_PIN -->|-- BUT_END ---- Button --- GND
BUT_ANS and BUT_END have pull up resistors BUT_ANS and BUT_END have pull up resistors

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

View File

@ -1,54 +0,0 @@
@misc{ wiki:DTMF,
author = "Wikipedia",
title = "Dual-tone multi-frequency signaling",
year = "2016",
url = "https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling",
note = "[Online; accessed 27-August-2016]"
}
@misc{ wiki:Goertzel,
author = "Wikipedia",
title = "Goertzel algorithm",
year = "2016",
url = "https://en.wikipedia.org/wiki/Goertzel_algorithm",
note = "[Online; accessed 27-August-2016]"
}
@misc{ wiki:FIR,
author = "Wikipedia",
title = "Finite impulse response",
year = "2016",
url = "https://en.wikipedia.org/wiki/Finite_impulse_response",
note = "[Online; accessed 27-August-2016]"
}
@misc{ wiki:IIR,
author = "Wikipedia",
title = "Infinite impulse response",
year = "2016",
url = "https://en.wikipedia.org/wiki/Infinite_impulse_response",
note = "[Online; accessed 27-August-2016]"
}
@misc{ wiki:Nyquist-Shannon,
author = "Wikipedia",
title = "NyquistShannon sampling theorem",
year = "2016",
url = "https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem",
note = "[Online; accessed 27-August-2016]"
}
@misc{ wiki:Z-transform,
author = "Wikipedia",
title = "Z-transform",
year = "2016",
url = "https://en.wikipedia.org/wiki/Z-transform",
note = "[Online; accessed 27-August-2016]"
}
@article{ DTMF_genave,
author = "Genave.com",
title = "DTMF Explained",
url = "http://www.genave.com/dtmf.htm",
note = "[Online; accessed 27-August-2016]"
}

Binary file not shown.

Binary file not shown.

View File

@ -1,66 +0,0 @@
\documentclass[12pt]{article}
\usepackage[margin=1in]{geometry}
\usepackage{parskip}
\usepackage{cite}
\usepackage{url}
\usepackage{arydshln}
\usepackage{amsmath}
\begin{document}
\title{Dual-Tone Multi-Frequency Signaling}
\author{Brendan Haines}
\maketitle
\section{Encoding}
Dual-Tone Multi Frequency Signaling uses audible tones as opposed to digital signals due to the nature of the mediums they are broadcast over. Since DTMF is often used with radio phones as well as traditional "landline" telephones, and these technologies are designed to carry voice well, staying within the same frequency range reduces complexity and expense of specialized equipment at both the transmitting and receiving stations. Often, the speaker output of a radio can simply be plugged into a decoder with no additional specialized hardware, resulting in lower equipment costs\cite{DTMF_genave}.
\subsection{Frequencies\cite{wiki:DTMF}}
\begin{tabular}{|c c c : c | l |}
\hline
1 & 2 & 3 & A & 687 Hz \\
4 & 5 & 6 & B & 770 Hz\\
7 & 8 & 9 & C & 852 Hz\\
$\ast$ & 0 & \# & D & 941 Hz\\
\hline
1209 Hz & 1336 Hz & 1477 Hz & 1633 Hz \\
\cline{1-4}
\end{tabular}
The A, B, C, and D keys are omitted on most handsets however are often used for automation purposes for triggering of remote functions such as controling an amateur radio repeater during an active phone call\cite{wiki:DTMF}.
\subsection{Timing}
DTF uses a "mark" (amplitude $\neq$ 0) followed by a "space" (amplitude = 0). Timing can vary widely depending on the system being used. For example, when using a manual encoder such as an ordinary telephone each button press will create a mark and the time between presses will be spaces. Higher speeds can be achieved using automatic or "store and forward" DTMF encoders. Motorola uses a standard of 250ms mark and 250ms space, and other systems include 40ms/20ms or 20ms/20ms mark and space respectively\cite{DTMF_genave}.
\section{Decoding}
Originally decoded using tuned filter banks, however DSP now dominates decoding and the Goertzel algorthim is often used\cite{wiki:DTMF}.
\subsection{Goertzel Algorithm}
The Goertzel algorithm or Goertzel filter uses the Discrete Fourier Transform (DFT) to evaluate frequency content of a signal at specific frequencies very efficiently. Although the Fast Fourier Transform (FFT) is more efficient for analyzing a complete spectrum of frequency, in the case where only a few frequencies are relevant (such as the 8 DTMF frequencies), the Goertzel algorithm is more numerically efficient. It works well even on small processors in embedded applications\cite{wiki:Goertzel}
Where $x[n]$ is the $n^{th}$ sample and $\omega _{0}$ is frequency in radians per sample, an intermediate sequence $s[n]$:
$$s[-2]=s[-1]=0$$
\begin{equation} \label{eqn:stage_1_goertzel}
s[n] = x[n] + 2 cos(\omega _{0})s[n-1] - s[n-2]
\end{equation}
The second stage of the Goertzel filter applies to $s[n]$, producing output sequence $y[n]$:
\begin{equation} \label{eqn:stage_2_goertzel}
y[n] = s[n] - e^{-j\omega _{0}}s[n-1]
\end{equation}
A Z-transform converts a discrete-time signal to a complex frequency domain\cite{wiki:Z-transform}. It can be applied to equations \ref{eqn:stage_1_goertzel} and \ref{eqn:stage_2_goertzel} respectively:
\begin{align} \label{eqn:z_goertzel}
\frac{S(z)}{X(z)} &= \frac{1}{1-2cos(\omega _{0})z^{-1} + z^{-2}}\nonumber\\
&= \frac{1}{(1-e^{+j\omega _{0}} z^{-1})(1 - e^{-j\omega _{0}}z^{-1})}\\
\frac{Y(z)}{S(z)} &= 1 - e^{-j\omega _{0}} z^{-1}
\end{align}
The combined transfer function of the cascade of the two filters:
\begin{align} \label{eqn:z_combined_goertzel}
\frac{S(z)}{X(z)}\frac{Y(z)}{S(z)} = \frac{Y(z)}{X(z)} &= \frac{(1-e^{-j\omega _{0}}z^{-1})}{(1-e^{+j\omega _{0}} z^{-1})(1 - e^{-j\omega _{0}}z^{-1})}\nonumber\\
&= \frac{1}{(1-e^{+j\omega _{0}} z^{-1})}
\end{align}
\bibliography{DTMF_Research}{}
\bibliographystyle{IEEEtran}
\end{document}

View File

@ -1,3 +0,0 @@
@article{ wikipedia_DTMF
title="DTMF"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.