Friday, December 25, 2009

Software

The software (SW) was entirely written in C. I used the CCS compiler PCM, which is in my opinion very affordable and amazingly good, together with the MPLAB IDE development platform (free). For this project, the SW should be able to handle following requirements:
  • 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)
I won't get into much detail, since the code is well documented and self explanatory. I will only explain the highlights of its operation.

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

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

    3 comments:

    1. The link to thee code is broken. Is there another way to get btericofonXX.zip?

      ReplyDelete
    2. Are 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.

      ReplyDelete
    3. Developing 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