//*****************************************************************************
// Title : Pulse to tone (DTMF) converter
// Author : Boris Cherkasskiy
// Created : 2011-10-24
//
// This code is distributed under the GNU Public License
// which can be found at http://www.gnu.org/licenses/gpl.txt
//
// DTMF generator logic is loosely based on the AVR314 app note from Atmel
//
//*****************************************************************************
//*****************************************************************************
// PIC16F628A VERSION TRANSLATED BY GioRock 2018
//*****************************************************************************
//----- Include Files ---------------------------------------------------------
#include "RotaryPulseToTone.h"
// In EEPROM:
// 7 Numeri di composizione rapida (funzioni di chiamata speciali da 3 a 9)
// Variabili Globali
volatile unsigned char cSWa = 0x00; // larghezza del passo di alta frequenza
volatile unsigned char cSWb = 0x00; // larghezza del passo di bassa frequenza
volatile unsigned int iCurSinValA = 0; // posizione freq. A in LUT (formato esteso)
volatile unsigned int iCurSinValB = 0; // posizione freq. B in LUT (formato esteso)
volatile unsigned long ulDelayCounter = 0; // Contatore di ritardo per la funzione sleep
// Struttura dello stato dei quadranti
typedef struct struct_DialStatus
{
signed char iDialedDigit; // Composto/Cifra selezionata rilevata
// Chiamato tenendo premuto il rotore per alcuni secondi (segnale acustico per indicare che SF è attivato) prima di rilasciarlo
// SF definito: 1: *; 2: #; 3-9: composizione rapida; 0: programma il numero di composizione rapida
bool bSF_Selected; // Funzione speciale selezionata
signed char iSpeedDialDigitIndex; // Indice numerico di composizione rapida
signed char iSpeedDialIndex; // Indice numerico di composizione rapida (nell' array SD)
signed char arSpeedDial[SPEED_DIAL_SIZE]; // Blocco di composizione rapida selezionato
} type_DialStatus;
volatile type_DialStatus sDS; // Struttura globale dello stato delle composizioni
//----- INIZIO PRINCIPALE ------------------------------------------------------
void main(void)
{
//Variabili locali
bool bPrevDialState = true; // Stato del rotore precedente
bool bPrevPulseState = false; // Stato degli impulsi del rotore precedente
bool bCurDialState = true; // Stato del rotore attuale
bool bCurPulseState = false; // Stato degli impulsi del rotore attuale
bool bSF_DetectionActive = false; // SF rilevamento attivo
unsigned char ucPhoneType = 0; // Selezione del tipo di Telefono
// Inizializza gli I/O e le variabili globali
init();
// Spegne il PWM (OFF)
GenerateDigit(DIGIT_OFF, 0);
// Identifica il tipo di Telefono utilizzato
// Il programma riconosce 4 tipi di Telefono
// impostabili con i due Jumper appositi
// X. Telefono - JP1 JP2 - Quadrante (da sinistra a destra)
// A. Standard - OFF OFF - 0987654321
// B. Svedese - ON OFF - 9876543210
// C. Zelandese - OFF ON - 0123456789
// D. Antico - ON ON - 1234567890 (raro)
ucPhoneType = 3 - (PORTB & 0b00000011);
// Imposta le PORTA 0 e 1 come variabili per memorizzare lo stato attuale dei
// pin su PORTB 4 e 5 in modo da non resettare il flag sull'Interrupt
// Questo è il segreto per far funzionare il codice su un PIC16F628A,
// AVR usa un ulteriore registro per memorizzare lo stato degli ingressi
// e preservare i flag di Interrupt, nei PIC16 invece non esiste
PIN_DIAL_TEMP = ON;
PIN_PULSE_TEMP = OFF;
// Ciclo principale
while (1)
{
bCurDialState = PIN_DIAL_TEMP;
bCurPulseState = PIN_PULSE_TEMP;
if (bPrevDialState != bCurDialState)
{
if (!bCurDialState)
{
// Composizione appena avviata
// Attivazione del rilevamento delle funzioni speciali
bSF_DetectionActive = true;
sDS.bSF_Selected = false;
sDS.iDialedDigit = 0;
SleepMS(25); // Ritardo di 50ms (25 con PIC a 8MHz)
}
else
{
// Disabilitazione del rilevamento SF (dovrebbe essere già disabilitato???)
bSF_DetectionActive = false;
// Controlla che stiamo rilevando una cifra valida
if ((sDS.iDialedDigit <= 0) || (sDS.iDialedDigit > 10))
{
// Non dovrebbe mai accadere - nessun impulso rilevato oppure conteggio superiore a 10 impulsi
sDS.iDialedDigit = DIGIT_OFF;
// Non fare nulla
SleepMS(25); // Ritardo 50ms (25 con PIC a 8MHz)
}
else
{
// Ottenete una cifra valida - elaboratela
// Telefono Standard
/*if (sDS.iDialedDigit == 10)
{
// 10 impulsi => 0
sDS.iDialedDigit = 0;
}*/
switch (ucPhoneType) {
case 0:
// Telefono Standard (0987654321)
// 10 impulsi => 0
if (sDS.iDialedDigit == 10) sDS.iDialedDigit = 0;
break;
case 1:
// Telefono Svedese (9876543210)
sDS.iDialedDigit = sDS.iDialedDigit - 1;
break;
case 2:
// Telefono Zelandese (0123456789)
sDS.iDialedDigit = 10 - sDS.iDialedDigit;
break;
case 3:
// Telefono Antico (1234567890)
sDS.iDialedDigit = 11 - sDS.iDialedDigit;
// 10 impulsi => 0
if (sDS.iDialedDigit == 10) sDS.iDialedDigit = 0;
break;
default:
// Errore di configurazione del tipo di telefono
// Non dovrebbe mai capitare ma...
GenerateDigit (DIGIT_BEEP, 1000);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
asm sleep;
break;
}
ProcessDialedDigit();
}
sDS.bSF_Selected = false; // Resetta il flag SF
}
}
else
{
if (!bCurDialState)
{
// Composizione in corso
if ((bPrevPulseState != bCurPulseState) && bCurPulseState)
{
// Disattivazione del rilevamento SF
bSF_DetectionActive = false;
// Un impulso appena avviato
sDS.iDialedDigit++;
SleepMS(25); // Ritardo 50ms (25 con PIC a 8MHz)
}
}
else
{
// Quadrante rotante in posizione di riposo
// Resetta tutte le variabili
bSF_DetectionActive = false;
sDS.bSF_Selected = false;
sDS.iDialedDigit = DIGIT_OFF;
}
}
bPrevDialState = bCurDialState;
bPrevPulseState = bCurPulseState;
// Non spegnere il PIC se è attivo il rilevamento delle funzioni speciali
if (bSF_DetectionActive)
{
// Rilevamento SF in corso - abbiamo bisogno del timer per poter funzionare (modalità IDLE)
// Nella realtà il PIC16F628A non ha una modaliatà IDLE del processore, così usiamo il WDT
// per simularne una simile anche se non è proprio la stessa cosa ma funziona ;-)
asm clrwdt;
if (!T1CON.TMR1ON) {
ResetTMR1();
PIR1.TMR1IF = false;
T1CON.TMR1ON = true;
}
// Modalità funzioni speciali rilevata?
if (ulDelayCounter >= SF_DELAY_MS * T1_OVERFLOW_PER_MS)
{
T1CON.TMR1ON = false;
PIR1.TMR1IF = true;
// Modalità SF rilevata
sDS.bSF_Selected = true;
bSF_DetectionActive = false;
// Indica che siamo entrati in modalità SF con un breve segnale acustico.
GenerateDigit(DIGIT_BEEP, 200);
}
}
else
{
// Non è più necessario il timer: modalità sleep di spegnimento processore
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
asm sleep;
}
}
}
//----- FINE ROUTINE PRINCIPALE ------------------------------------------------
// Elaborazione delle cifre composte
void ProcessDialedDigit (void)
{
unsigned char i=0;
// Funzioni speciali 1 e 2 (* e #)
if (sDS.bSF_Selected && (sDS.iDialedDigit == 1))
{
// SF 1-*
sDS.iDialedDigit = DIGIT_STAR;
}
else if (sDS.bSF_Selected && (sDS.iDialedDigit == 2))
{
// SF 2-#
sDS.iDialedDigit = DIGIT_POUND;
}
// Funzionalità di selezione rapida: entrata e uscita dalla modalità SD
if (sDS.bSF_Selected && (sDS.iDialedDigit == 0))
{
// SF 0 - scrive il numero di composizione rapida
// Programmazione SP già in corso?
if (sDS.iSpeedDialDigitIndex < 0)
{
// Appena entrato nel modo SD
sDS.iSpeedDialDigitIndex = 0;
// A questo punto non conosciamo ancora l' indice SD
sDS.iSpeedDialIndex = -1;
// Cancella l'array SD selezionato
for (i = 0; i<SPEED_DIAL_SIZE; i++)
{
sDS.arSpeedDial[i] = DIGIT_OFF;
}
// Beep quando si entra nella modalità SD, l'utente è entrato nell'indice SD
GenerateDigit (DIGIT_TUNE_ASC, 700);
GenerateDigit (DIGIT_TUNE_DESC, 700);
}
else
{
// SD in corso, l'utente è inserito in SF 0 - salvo SD ed esco dalla modalità SD
// Salvo il numero di chiamata rapida
WriteCurrentSpeedDial(sDS.iSpeedDialIndex);
// Abbandono la modalità SD
sDS.iSpeedDialIndex = -1;
sDS.iSpeedDialDigitIndex = -1;
// Beep per indicare che è stato fatto
GenerateDigit (DIGIT_TUNE_DESC, 800);
}
}
// Programmazione numero SD
else if (sDS.iSpeedDialDigitIndex >= 0)
{
// Composizione della prima cifra dopo aver selezionato la modalità SD. Indice SD non ancora impostato
if (sDS.iSpeedDialIndex < 0)
{
// L'indice SD dovrebbe essere compreso tra 3 e 9
if ((sDS.iDialedDigit >= 3) && (sDS.iDialedDigit <= 9))
{
sDS.iSpeedDialIndex = sDS.iDialedDigit;
// Beep per indicare che siamo in modalità SD
GenerateDigit (DIGIT_TUNE_ASC, 800);
}
else
{
// Indice SD errato - beep e uscita dalla modalità SD
// Abbandono la modalità SD
sDS.iSpeedDialIndex = -1;
sDS.iSpeedDialDigitIndex = -1;
// Un lungo Beep per indicare un errore
GenerateDigit (DIGIT_BEEP, 1000);
}
}
else
{
// Programmazione SD già in corso
// Abbiamo troppe cifre inserite?
if (sDS.iSpeedDialDigitIndex >= SPEED_DIAL_SIZE)
{
// SI - Termino e salvo il numero di composizione rapida
// Salva numero di chiamata rapida
WriteCurrentSpeedDial(sDS.iSpeedDialIndex);
// Abbandono la modalità SD
sDS.iSpeedDialIndex = -1;
sDS.iSpeedDialDigitIndex = -1;
// Beep per indicare che è stato fatto
GenerateDigit (DIGIT_TUNE_DESC, 800);
}
else
{
// Tutto a posto - imposto una nuova cifra sull'array
sDS.arSpeedDial[sDS.iSpeedDialDigitIndex] = sDS.iDialedDigit;
// Beep generico - non produco il codice DTMF
GenerateDigit(DIGIT_BEEP_LOW, DTMF_DURATION_MS);
// Prossima cifra
sDS.iSpeedDialDigitIndex++;
}
}
}
// Chiamo il numero SD memorizzato
else if (sDS.bSF_Selected && (sDS.iDialedDigit >= 3) && (sDS.iDialedDigit <= 9))
{
// SF 3-9 -> Numero di chiamata rapida
Dial_SpeedDialNumber(sDS.iDialedDigit);
}
// Standard (funzionalità di chiamata non rapida)
else
{
// Modalità standard (nessuna selezione rapida, nessuna funzione speciale)
// Genero il codice DTMF
GenerateDigit(sDS.iDialedDigit, DTMF_DURATION_MS);
}
}
// Inizializzazione PIC
void init(void)
{
unsigned char i = 0;
CMCON = 0b00000111; // Disabilita i comparatori
TRISA = 0b00100000; // PORTA tutte uscite eccetto MCLR
TRISB = 0b00110011; // PORTB tutte uscite eccetto RB0, RB1, RB4 e RB5
PORTA = 0b00100000; // tutte le porte A a livello basso eccetto MCLR
PORTB = 0b00110011; // tutte le porte B a livello basso eccetto RB0, RB1, RB4 e RB5, RB3 uscita PWM
OPTION_REG = 0b01001000; // Pull-up su PORTB Abilitate + WDT prescaler 1:1 (Watch Dog Timer)
INTCON = 0b00001000; // Interrupt Globale disabilitato + Interrupt delle Priferiche disabilitato + RB<7:4> Interrupt abilitato
T1CON = 0b00000001; // Impostazione TIMER1 interno sincrono Prescaler 1:1 abilitato (Fck / 4)
T2CON = 0b00000100; // Prescaler TIMER2 1:1 per il modulo PWM
PR2 = 0b01111111; // Preload TIMER2 per una frequenza di 62500 Hz con XT a 8Mhz risoluzione 8bit (vedere il datasheet)
CCP1CON = 0b00001100; // Impostazione della modalità PWM - 0x0C
CCPR1L = 0b00000000; // Reset del Duty Cycle a 8bit MAX.
PIE1 = 0b00000001; // CCP1 Interrupt + TIMER2 su PR2 Interrupt su corrispondenza del periodo disabilitati + TIMER1 Overflow Interrupt abilitato
// Fermo il generatore PWM e spengo l'uscita
PWM_Stop();
PIN_PWM_OUT = OFF;
// Inizializza la struttura (globale) dello stato della chiamata (sDS)
sDS.iDialedDigit = DIGIT_OFF;
// SF composto tenendo premuto il rotore per alcuni secondi (segnale acustico per indicare che SF è attivato) prima di rilasciarlo
// SF definito: 1:*; 2:#; 3-9: composizione rapida; 0: programma il numero di composizione rapida
sDS.bSF_Selected = false; // Funzione Speciale selezionata
// Velocità del Quadrante
sDS.iSpeedDialDigitIndex = -1; // Indice numerico di composizione rapida
sDS.iSpeedDialIndex = -1; // Indice numerico di composizione rapida (nell'array SD)
for (i=0; i<SPEED_DIAL_SIZE; i++) // Cancella l'array SD selezionato
{
sDS.arSpeedDial[i] = DIGIT_OFF;
}
// Resetta tutti i settaggi e abilita gli Interrupt
ResetTMR1(); // Resetta il Timer1
INTCON.RBIF = false; // Resetta il flag RB<7:4>
PIR1.TMR1IF = false; // Resetta il flag TMR1
PIR1.TMR2IF = false; // Resetta il flag TMR2
INTCON.PEIE = true; // Abilita l'Interrupt sulle Periferiche
INTCON.GIE = true; // Abilita l'Interrupt Globale
}
// Questa funzione simula la modalità del TM0 dell'AVR8
// in modalità IDLE per risparmiare corrente
// Quì usiamo il TIMER1 per generare un Interrupt per
// rivegliare il PIC16F628A - PSEUDO IDLE MODE
void set_sleep_mode(unsigned char s_mode)
{
ResetTMR1();
asm clrwdt;
if(s_mode == SLEEP_MODE_IDLE) {
// Prescaler WDT 1:1
OPTION_REG.PS2 = 0;
OPTION_REG.PS1 = 0;
OPTION_REG.PS0 = 0;
PIR1.TMR1IF = false;
T1CON.TMR1ON = true;
} else if (s_mode == SLEEP_MODE_PWR_DOWN){
// Prescaler WDT 1:128
OPTION_REG.PS2 = 1;
OPTION_REG.PS1 = 1;
OPTION_REG.PS0 = 1;
PIR1.TMR1IF = true;
}
}
// Genera i toni DTMF, durata x ms
void GenerateDigit (signed char scDigit, unsigned int uiDuarationMS)
{
if (scDigit >= 0 && scDigit <= DIGIT_POUND)
{
// Cifre standard 0-9, *, #
cSWa = auc_frequency[scDigit][0];
cSWb = auc_frequency[scDigit][1];
EnablePWM();
// Attesa x ms
SleepMS(uiDuarationMS);
}
else if (scDigit==DIGIT_BEEP)
{
// Beep ~1000Hz (66)
cSWa = 122;
cSWb = 0;
EnablePWM();
// Attesa x ms
SleepMS(uiDuarationMS);
}
else if (scDigit==DIGIT_BEEP_LOW)
{
// Beep ~500Hz (33)
cSWa = 66;
cSWb = 0;
EnablePWM();
// Attesa x ms
SleepMS(uiDuarationMS);
}
else if (scDigit==DIGIT_TUNE_ASC)
{
cSWa = 68; // C=523.25Hz
cSWb = 0;
EnablePWM();
SleepMS(uiDuarationMS/3);
cSWa = 86; // E=659.26Hz
SleepMS(uiDuarationMS/3);
cSWa = 102; // G=784Hz
SleepMS(uiDuarationMS/3);
}
else if (scDigit==DIGIT_TUNE_DESC)
{
cSWa = 102; // G=784Hz
cSWb = 0;
EnablePWM();
SleepMS(uiDuarationMS/3);
cSWa = 86; // E=659.26Hz
SleepMS(uiDuarationMS/3);
cSWa = 68; // C=523.25Hz
SleepMS(uiDuarationMS/3);
}
// Fermo la trasmissione DTMF
// Disabilito il PWM e spengo l'uscita con la MACRO
// La modalità di corrispondenza del periodo è disabilitata
PWM_Stop();
PIN_PWM_OUT = OFF;
#ifdef USE_LED
PIN_LED = OFF;
#endif
// Resetto le variabili di generazione del segnale
cSWa = 0;
cSWb = 0;
}
// Abilito l'uscita PWM configurando la modalità di corrispondenza con comparazione - senza inversione del PWM
void EnablePWM(void)
{
// Opzionale posso accendere un LED
#ifdef USE_LED
PIN_LED = ON; // It does working??? OK!!!
#endif
PWM_Start();
}
// Attesa x ms
void SleepMS(unsigned int uiMsec)
{
ulDelayCounter = 0;
set_sleep_mode(SLEEP_MODE_IDLE);
asm sleep;
while(ulDelayCounter <= uiMsec * T1_OVERFLOW_PER_MS)
{
// Usando il WDT prima di ogni 18ms bisogna fare un reset
// se siamo in modalità sleep, il PIC non si resetta ma
// ritorna alla chiamata successiva dopo questa (trucco)
asm clrwdt;
// Riaccendo il TIMER dopo l'Interrupt
T1CON.TMR1ON = true;
}
// Spengo il TIMER che non serve più
T1CON.TMR1ON = false;
}
// Legge il numero nella EEPROM in un passaggio
void eeprom_read_byte_block(char * __dst, char __src, unsigned short __n)
{
unsigned char p;
unsigned char * dst;
p = __src;
dst = __dst;
while (__n--) {
* dst++ = EEPROM_Read(p++);
SleepMS(20);
}
}
// Scrive il numero nella EEPROM e solo se il valore attuale è differente
void eeprom_update_byte_block(char * __src, char __dst, unsigned short __n)
{
unsigned char p;
unsigned char * src;
p = __dst;
src = __src;
while (__n--) {
if (EEPROM_Read(p) != * src+p) {
SleepMS(20);
EEPROM_Write(p++, * src++);
SleepMS(20);
}
}
}
// Composizione del numero di selezione rapida - (elimina il numero SD corrente nella struttura globale)
void Dial_SpeedDialNumber(unsigned char iSpeedDialIndex)
{
unsigned char i=0;
if ((iSpeedDialIndex >= 3) && (iSpeedDialIndex <= 9))
{
// Se è chiamato l'indice 3 => utilizza l'indice 0 dell'array
eeprom_read_byte_block((char *)sDS.arSpeedDial, ((iSpeedDialIndex - 3) * SPEED_DIAL_SIZE), SPEED_DIAL_SIZE);
for (i=0; i<SPEED_DIAL_SIZE; i++)
{
// Chiamo il numero
// Salto le cifre non valide
if ( (sDS.arSpeedDial[i] >= 0) && (sDS.arSpeedDial[i] <= DIGIT_POUND) )
{
GenerateDigit(sDS.arSpeedDial[i], DTMF_DURATION_MS);
// Una pausa tra i toni DTMF
SleepMS(DTMF_DURATION_MS);
}
}
}
}
// Scrivo l'array di selezione rapida corrente (dalla versione globale) alla EEPROM
void WriteCurrentSpeedDial(unsigned char iSpeedDialIndex)
{
if ((iSpeedDialIndex >= 3) && (iSpeedDialIndex <= 9))
{
// Se è chiamato l'indice 3 => utilizza l'indice 0 dell'array
eeprom_update_byte_block((char *)sDS.arSpeedDial, ((iSpeedDialIndex - 3) * SPEED_DIAL_SIZE), SPEED_DIAL_SIZE);
}
}
//Routine di Interrupt
void Interrupt(void)
{
unsigned char ucSinA = 0;
unsigned char ucSinB = 0;
unsigned int i_TmpSinValA = 0;
unsigned int i_TmpSinValB = 0;
// ISR Timer2 overflow o corrispondenza con il periodo del PWM (Interrupt Service Routine)
if (PIR1.TMR2IF) {
// PIR1.TMR2IF = 0; // Spostare qui se XT è di 20MHz
// Viene sempre utilizzata una componente (alta frequenza)
// sposto il Puntatore a circa la larghezza del passo in avanti
iCurSinValA += cSWa;
// normalizzo il Puntatore temporaneo
i_TmpSinValA = (char)(((iCurSinValA + 4) >> 3) & (0x007F));
ucSinA = auc_SinParam[i_TmpSinValA];
// La componente B (bassa frequenza) è opzionale
if (cSWb > 0)
{
// sposto il Puntatore a circa la larghezza del passo in avanti
iCurSinValB += cSWb;
// normalizzo il Puntatore temporaneo
i_TmpSinValB = (char)(((iCurSinValB + 4) >> 3) & (0x007F));
ucSinB = auc_SinParam[i_TmpSinValB];
}
else
{
ucSinB = 0;
}
// calcolo il valore del PWM: valore dell'alta frequenza + 3/4 del valore della bassa frequenza
// Per recuperare la bassa risoluzione dei bit dovuto al prescaler del PIC che divide XT per 4
// moltiplico il duty risultante per 2 in maniera da compensare tale perdita anche se il massimo
// valore della risoluzione PWM scende da 512 a 256 MAX.
SetDutyCycle((ucSinA + (ucSinB - (ucSinB >> 2))) << 1);
PIR1.TMR2IF = false; // Resetto il flag del Timer2
// ISR Timer1 overflow (Interrupt Sservice Routine)
} else if (PIR1.TMR1IF) {
ResetTMR1();
// Incremento il contatore per calcolare il tempo trascorso
ulDelayCounter++;
PIR1.TMR1IF = false; // Resetto il flag del Timer1
} else if (INTCON.RBIF) {
// I pin su PORTB hanno generato un Interrupt
// Salvo lo stato corrente degli ingressi su PORTA
PIN_DIAL_TEMP = PIN_DIAL;
PIN_PULSE_TEMP = PIN_PULSE;
// Piccola pausa per assicurarci che non ci siano rimbalzi
Delay_us(100);
// Resetto il flag su RB<7:4>
INTCON.RBIF = false;
}
}
// Funzione per impostare il Duty Cycle a 8bit
void SetDutyCycle(unsigned int duty) {
CCPR1L = (duty >> 2) & 0xFF; // Traslo il PWM da 10bit a 8bit
CCP1X_bit= 0; // 8bit PWM
CCP1Y_bit= 0; // 8bit PWM
}
// Reimpostazione del TIMER1
void ResetTMR1(void)
{
T1CON.TMR1ON = false;
TMR1H = T1_PRELOAD_PER_MS;
TMR1L = 255;
}