- Support serial communication (RX and TX) @ 115,200 baud (bluetooth module)
- Generate audible tones (440, 2300 and 2700 Hz)
- Detect and debounce the hook switch and the rotary dial contacts
- Count the dialed pulses and store the digits
- Provide real time timers with fine resolution (typically msecs)
- Able to handle multiple tasks
- State-Event driven (using a state machine)
Structure
The SW is based on a multitask, state-event-driven structure. Each task has its own timer assigned and its value can range from 1 to 65534 msecs. A master interrupt driven by PIC Timer0 (RTCC) checks every 1 msec all task timers and decrements their values if the timers are active (value 1 to 65534). If a timer is disabled (value 0xFFFF) or has expired (value 0) the master interrupt won't decrement its value.#define TICK 213 //interrupt reload value 1 mSec
#INT_RTCC
void clock_isr()
{
int i; //local index
set_rtcc(TICK); //reload timer to TICK every 1 mS
for (i = 0; i < T_END; ++i) //for each active task timer, decrement it
{
if (task_delay[i] != T_DISABLE) //if task timer is enabled..
if (task_delay[i] != 0) //..and running..
task_delay[i]--; //..then decrement it
}
}
The main loop checks if any task timer has expired, which in such case will run the task. Multiple tasks can have their timer expired, in which case the main loop will first execute the task with the highest priority. In case no task requires to run, the main loop also check if any serial received message is ready for processing and if any serial transmission is pending. Regular 'get strings' and 'print strings' (such as printf() functions) are not allowed, because they will hog the processor and will affect time critical tasks. The main loop is:
while(true)
{
task_index = 0; //start scanning from first task
flag.run_task = false; //test flag
while (!flag.run_task && (task_index < T_END)) //check if a task needs to run
{
if (task_delay[task_index] == 0) //if task timer expired, run task
flag.run_task = true;
else //if not, search for next task until done
task_index++;
}
if (flag.run_task) //if task needs to run, then run it
{
task_delay[task_index] = T_DISABLE; //disable further calls of same task
switch (task_index) //run highest task that is ready
{
case T_ANALIZE_CONTACTS: ANALIZE_CONTACTS(); break;
case T_END_OF_ROTARY: END_OF_ROTARY(); break;
case T_END_OF_DIAL: END_OF_DIAL(); break;
case T_HOOK_FLASH: HOOK_FLASH(); break;
case T_BUSY_TONE: BUSY_TONE(); break;
case T_RING: RING(); break;
default: break;
}
}
else //no task pending, work on serial messages
{
TX_MESSAGE(); //check if a TX message ready and send it
RX_MESSAGE(); //check if a RXd message ready and process it
}
}//end while loop
{
task_index = 0; //start scanning from first task
flag.run_task = false; //test flag
while (!flag.run_task && (task_index < T_END)) //check if a task needs to run
{
if (task_delay[task_index] == 0) //if task timer expired, run task
flag.run_task = true;
else //if not, search for next task until done
task_index++;
}
if (flag.run_task) //if task needs to run, then run it
{
task_delay[task_index] = T_DISABLE; //disable further calls of same task
switch (task_index) //run highest task that is ready
{
case T_ANALIZE_CONTACTS: ANALIZE_CONTACTS(); break;
case T_END_OF_ROTARY: END_OF_ROTARY(); break;
case T_END_OF_DIAL: END_OF_DIAL(); break;
case T_HOOK_FLASH: HOOK_FLASH(); break;
case T_BUSY_TONE: BUSY_TONE(); break;
case T_RING: RING(); break;
default: break;
}
}
else //no task pending, work on serial messages
{
TX_MESSAGE(); //check if a TX message ready and send it
RX_MESSAGE(); //check if a RXd message ready and process it
}
}//end while loop
Serial Communication
PIC interrupt RDA is triggered every time the USART receives a character. The interrupt routine stores the received character in a receive buffer. Only the first sizeof(buffer)-1 characters are stored and the rest are discarded. A new line (\n) character determines the end of reception and the interrupt routine inserts a null (\0) character for further string analysis. The interrupt sets a flag to indicate that a message is ready to be analyzed by RX_MESSAGE().#INT_RDA
void rs232_isr(void)
{
int character;
character = getc(); // Get new character
if (character=='\n') // If \n then line is completed
{
disable_interrupts(INT_RDA); //freeze serial input while analyzes data
buffer.rx[index.rx] = '\0'; //replace \n for \0 to the last position
flag.rx_msg_ready = true; //message ready to process
}
else
{
if (index.rx < sizeof(buffer.rx)-1) //store char if buffer not full yet, otherwise ignore it
{
buffer.rx[index.rx] = character; //save character in buffer
index.rx++; //increment index
}
}
}
To transmit a string another buffer is used. Different functions can store characters (commands) in the transmit buffer. Once the message is ready a flag is set and TX_MESSAGE() takes care of it. The function checks if the USART transmitter buffer is empty and then send the next character from the buffer. The operation is pretty simple using pointers and doesn't take so many memory and time resources as would a printf() command.
void TX_MESSAGE()
{
int character;
if (flag.tx_msg_ready && TRMT) //check if needs to send message AND USART is ready
{
character = *(buffer.tx + index.tx); //read the next character to send
TXREG = character; //send it to UART output (triggers TX)
if (character == '\n') //check if char was end of line
flag.tx_msg_ready = false; //transmission ends
else
index.tx++; //increment pointer to read next char
}
}
To achieve a precise and stable communication speed of 115,200 bauds I used a 11.0592 MHz crystal. Unfortunately, the internal oscillator doesn't provide the precise rate and its accuracy is not that high.
Tone Generation
To generate tones I used the PIC Timer1. It is a 2-byte counter clocked by the master oscillator. Every time the counter rolls over it generates an interrupt. To generate the tone, I load the counter with a value that is half of the period of the tone. Every time the timer rolls over, the routine reloads the value and toggles the output port where I am producing the tone. To turn the tone on or off I enable or disable the interrupt, respectively. This allows me to generate virtually any tone with a very accurate frequency and without any noticeable jitter.#INT_TIMER1
void tone_isr()
{
set_timer1(tone); //reload timer with tone value
if (flag.dial0_ring1) //if false, toggle dial port; if true, toggle ring port
RING_TONE_O = ~RING_TONE_O; //toggle ring tone port
else
DIAL_TONE_O = ~DIAL_TONE_O; //toggle dial tone port
}
Debouncing
Debouncing and status of the contacts is accomplished by ANALYZE_CONTACTS() task. This task calls itself every 5 msecs and checks for three consecutive same-value readings from the external contacts before it declares a valid (steady-state) input. If the new steady-state value differs from a previous valid-state value, a function checks if the particular event requires any action based on the current state. Here the code how I debounce the rotary dial contact (similar for the hook contact):flag.aux = ROTARY_IN; //read input port
if (flag.aux == rotary.sample_1 && flag.aux == rotary.sample_2 && flag.aux == rotary.sample_3)
{
if (flag.aux != rotary.value) //if all 3 samples are the same and is a change...
{
rotary.value = flag.aux; //update status
RTRY_CHANGE(); //analyze change based on phone state
}
}
rotary.sample_3 = rotary.sample_2; //update all samples (shift)
rotary.sample_2 = rotary.sample_1;
rotary.sample_1 = flag.aux;
task_delay[T_ANALIZE_CONTACTS] = 5; //run this task every 5 msecs forever
States
The code is driven by a state machine. Depending on what state the phone is, the code takes different actions. Using state machines greatly simplify the code and makes it very robust. Here the states used in the code:
enum {
IDLE, //phone is idle (on-hook)
DIAL_TONE, //dial tone (before dialing 1st digit)
DIALING, //first digit dialed. dialing process
TALKING, //talking
RINGING, //ringing (incoming call)
BLOCKED //obtaining busy tone. only escape from here is to hang up
} state;
Code
I have combined multiple programming techniques and developed and fine-tuned processes over the years to make this multi-task, state-event driven concept very powerful and robust, nonetheless extremely simple. You can use the same concept with any micro-controller, including with Arduino platforms.If you consider that you can get useful ideas and concepts from this code and project, that you can apply them in your current and future projects, or that you can help somebody else by teaching these concepts, then I'll appreciate a voluntary contribution of your convenience for keep improving the work and propagate it to others. Thank you!
Next blog: Putting all things together
The link to thee code is broken. Is there another way to get btericofonXX.zip?
ReplyDeleteAre you planning on a dtmf decoder/encoder circuit or a way to make it possible for touch tone phones to use this as well? I have been thinking about this same idea for a long time as a way to repurpose old home phones for the elderly so they can have a cell phone yet use the familiar and RFradiation-free desk phones they all have packed away. I'll be the first one to buy your kit. Good Luck.
ReplyDeleteDeveloping a wireless Audio or Data accessory can become a tedious, expensive and risky project. By offering a fully integrated solution with certifiedBluetooth Module the associated development kits and Smart Phone Apps
ReplyDelete