Simple serial and timer ISR state machine

March 23, 2013 Coded in C for the Atmel AT89
/*
    k164_js.c
    
    Purpose:    New firmware for the k164 dtmf decoder board and
                the AT89C2051-24PC The source code was compiled with sdcc.

    URLs:
        http://www.digikey.com/product-detail/en/AT89C2051-24PU/AT89C2051-24PU-ND/1118880
        http://www.electronics123.com/kits-and-modules/Telephone-Call-Logger-Kit-16k.html
        http://www.kitsrus.com/pdf/k164.pdf

    Compile:    sdcc k164_js.c ; packihx k164_js.ihx > k164_js.hex
    Simulate:   s51 k164_js.hex

    Copyright (C) 2009 Nu Tech Software Solutions, Inc.

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.

    AUTHOR: Sean Mathews <coder at f34r.com> 1/27/2009

*/

#include <at89x051.h>

#define STATUS_LED  P3_5
#define HOOK_LED    P3_4
#define LOOP_STATUS P3_3
#define STD_STATUS  P3_7
#define MODE_SWITCH P1_5

/* UART parameters                                            */
#define CpuClk        20275200              // 20.2752 MHz clock chip on the k164 board
#define Baudrate      9600                  // UART - 9600,N,8,1 baud used by current firmware
#define Timer1ReloadValue (256-(2*CpuClk/32/12/Baudrate))

#define F34R_MODE   0

char szVERSION[] = "V1.0";

/*
To determine the value that must be placed in TH1 to generate a given baud rate, we may use the following equation (assuming PCON.7 is clear).
      TH1 = 256 - ((Crystal / 384) / Baud) 
If PCON.7 is set then the baud rate is effectively doubled, thus the equation becomes:
      TH1 = 256 - ((Crystal / 192) / Baud)      

  make this next macro work and we wont hvae to hard code the values ... 
 */
#define InterruptRate 10000    // how oftin to hit our interrupt per second
#define Timer0H           0xBE //(char)((0xFF00 & (65536 - (InterruptRate / 12 * 1000))) >> 8)
#define Timer0L           0x00 //(char)(0x00FF & (65536 - (InterruptRate / 12 * 1000)))

/* prototypes                                                 */
void hw_init();
char getchar( void );
void myputchar( char c );
void doevents();
void myputs(char *);
void itoa(int value, char* string, int radix);
void uitoa(unsigned int value, char* string, int radix);
void send_version(void);
void send_hello(void);
void send_help(void);

#define UNKNOWN  0x01
#define OFFHOOK  0x02
#define ONHOOK   0x03
#define VERSION  0x04
#define EGGS     0x05
#define RESET    0x06
#define SEND_HELP 0x07

char hook_state;
char input_state;

int notdone=1;

#define ON       0x02
#define OFF      0x03
char std_state;

static char state_machine_active=0;

/* plug all of the other interrupt vectors                    */
#ifdef SDCC
void mydummyISR (void) interrupt 12 _naked {
}
#endif

/* Serial interrupt to track incoming key strokes             */
void serial_isr(void) interrupt 4 {
        if (RI != 0)
        {
          RI = 0;
          
          if(SBUF == '?')
            hook_state = UNKNOWN;
          if(SBUF == 'V' || SBUF == 'v')
            input_state = VERSION;
          if(SBUF == 'R' || SBUF == 'r')
            input_state = RESET;
          if(SBUF == '!')
            input_state = EGGS;
          if(SBUF == 'H' || SBUF == 'h')
            input_state = SEND_HELP;
          
        }
        return;
}

/*-------------------------------------------------------------------------
 integer to string conversion

 Written by:   Bela Torok, 1999 in the public domain
               bela.torok@kssg.ch
 usage:

 uitoa(unsigned int value, char* string, int radix)
 itoa(int value, char* string, int radix)

 value  ->  Number to be converted
 string ->  Result
 radix  ->  Base of value (e.g.: 2 for binary, 10 for decimal, 16 for hex)
---------------------------------------------------------------------------*/

#define NUMBER_OF_DIGITS 16   /* space for NUMBER_OF_DIGITS + '\0' */

void uitoa(unsigned int value, char* string, int radix)
{
unsigned char index, i;

  index = NUMBER_OF_DIGITS;
  i = 0;

  do {
    string[--index] = '0' + (value % radix);
    if ( string[index] > '9') string[index] += 'A' - ':';   /* continue with A, B,.. */
    value /= radix;
  } while (value != 0);

  do {
    string[i++] = string[index++];
  } while ( index < NUMBER_OF_DIGITS );

  string[i] = 0; /* string terminator */
}

void itoa(int value, char* string, int radix)
{
  if (value < 0 && radix == 10) {
    *string++ = '-';
    uitoa(-value, string, radix);
  }
  else {
    uitoa(value, string, radix);
  }
}

/* setup UART                                                 */
void hw_init() {

        LOOP_STATUS = 1; //set our loop status pin to an input
        STD_STATUS  = 1; //set our std status pin to an input
        MODE_SWITCH = 1; //set the "ECHO" switch input on the K164 board to input 

        EA = 0; // disable all interrupts
                  
        PCON |= 0x80;  // SMOD = 1 double speed clock for our baud rate interrupt

        TH1 = TL1 = Timer1ReloadValue;   // timer 1 mode 1 reload value 9600 baud as calculated in our macro

        TMOD &= 0x0f;    /* Set timer 1 */ 
        TMOD |= 0x20;    /* Set timer 1 as Gate=0 Timer, mode 2 */ 

        TR1 = 1;        // turn on serial timer Timer 1

        SCON = 0x40;    // init port as 8-bit UART with variable baudrate 
        SCON |= 0x10;   // Enabling serial reception
//      SCON |= 0x02;   // Setting TI bit 
        ES = 1;         // Enable Serial Interrupt */

        /* Timer 0 setup */
        TMOD &= 0xf0;    /* Set timer 0 */ 
        TMOD |= 0x01;    /* Set timer 0 16 bit timer  */ 

        /* configure generic timer 0 reset value */        
        TH0 = Timer0H;
        TL0 = Timer0L;  // reload with 35711 for 1Hz
    
        TR0 = 1;        // turn on timer 0
        ET0 = 1;        // Enable timer 0 interrupt 
        

        RI  = 0;
        TI  = 1;

        EA  = 1; // enable all interrupts

}

/* setup FIRMWARE                                              */
void fw_init() {

        /* initialize our state machine to ON HOOK */
        hook_state = UNKNOWN;
        input_state = UNKNOWN;
        std_state = UNKNOWN;

        /* Turn off our LED's we just started */
        HOOK_LED = 0;
        STATUS_LED = 0;

}

/* read a character from UART                                 */
char getchar( void ) { 
        while(!RI);
        RI = 0;
        return(SBUF);
}

/* send a character to UART port                              */
void myputchar( char c ) { 
        while(!TI);
        TI =0;
        SBUF = c;
}

void myputs(char *sz) {
   while(*sz) myputchar(*sz++);
} 

/* Timer 0 interrupt the state machines main interrupt */
void timer_isr(void) interrupt 1 {
        static int suppressfirst=1;
        static int x=0;
        static int counter=0;
        char buffer[17];
        /* configure generic timer 0 reset value */
        TH0 = Timer0H;
        TL0 = Timer0L;
        
        /* every 1 second do our event routine */        
        if(x++>50) {
                x=0;
                doevents();
        }

        /* we need to control this or we will be trying to send out serial data from two threads */
        if(state_machine_active) {

                if( input_state == VERSION ) {
                        send_version();
                        input_state = UNKNOWN;
                }
                if( input_state == SEND_HELP ) {
                        send_help();
                        input_state = UNKNOWN;
                }
                if( input_state == EGGS ) {
                        myputs("! Jack Edin 1961-2012 rip - Logic Unlimited !\r\n");
                        myputs("! Sean Mathews - NuTech.com   !\r\n");
                        input_state = UNKNOWN;
                }
                if( input_state == RESET ) {
                        notdone=0;
                        input_state = UNKNOWN;
                }

                /* check state of the hook line it seems to be inverted */
                if(!LOOP_STATUS) {
                        HOOK_LED = 1; /* ON  NPN Transistor base*/
                          if( hook_state != OFFHOOK ) {
                                counter++;
                                if(counter>10) { // 100ms
                                        hook_state = OFFHOOK;
                                        if(!suppressfirst) {
                                           myputs("OFFHOOK\r\n");
                                        } else {
                                           suppressfirst=0;
                                        }
                                }
                          }

                } else {
                        HOOK_LED = 0; /* OFF NPN Transistor base*/
                        counter=0;
                        if( hook_state != ONHOOK ) {
                                hook_state = ONHOOK;
                                if(!suppressfirst) {
                                   myputs("ONHOOK\r\n");                                   
                                } else {
                                   suppressfirst=0;
                                }
                        }
                }

                /* check state of the STD pin on the MT8870CE chip      */
                if(STD_STATUS) {
                        if( std_state != ON ) {
                                std_state = ON;
                                if(MODE_SWITCH==F34R_MODE) {
                                        myputs("TONE ");
                                }

                                switch(P1 & 0x0f) {
                                        case  10:
                                                buffer[0]='0';
                                                buffer[1]=0;
                                                break;
                                        case  11:
                                                buffer[0]='*';
                                                buffer[1]=0;
                                                break;
                                        case  12:
                                                buffer[0]='#';
                                                buffer[1]=0;
                                                break;
                                        default:
                                                itoa(P1 & 0x0f,buffer,10);
                                                break;
                                }
                                myputs(buffer);
                                if(MODE_SWITCH==F34R_MODE) {
                                        myputs("\r\n");
                                }
                        }
                } else {
                        if( std_state != OFF ) {
                                std_state = OFF;
                        }
                }
        }
} 

/* Event routine for periodic processing                      */
void doevents() {
  static char flipflop=0;
  /* one second event handler. Future use...*/

 
 
 /* flash the status led every 1 second */
 if(MODE_SWITCH!=F34R_MODE) {   
 
   STATUS_LED = !STATUS_LED;   
   
 } else {
 
        flipflop = !flipflop;
 
        if(flipflop) 
           STATUS_LED = !STATUS_LED;   
 }
                 
}

/* MAIN                                                       */
void main(void) {
        notdone=1;
        /* first setup our states and any other startup code so 
          when our hardware calls our routines they are ready */
        fw_init();

        /* ok now setup our hardware and start the interrupts */
        hw_init();

        /* tell the world we are up and running */
        send_hello();

        /* let the state machine go */
        state_machine_active=1;

        /* ... */
        while (notdone) { }

        // disable all interrupts
        EA = 0; 
        // jump to 0
        ((void (code *)(void)) 0) ();
}

void send_hello() {
        myputs("\r\n! K164mh Telephone DTMF Decoder ");
        myputs(szVERSION);
        myputs(" written for my good friend Jack Edin 1961-2012 rip!\r\n");
}

void send_version() {
        myputs(szVERSION);
        myputs("\r\n");
}

void send_help() {
 myputs("\r\n! Every line that starts with a ! is considered informational\r\n!and is not part of any call logging.\r\n");
 myputs("! The state messages are ONHOOK [%1], OFFHOOK, TONE %1\r\n");
 myputs("! The tones can also be on the ONHOOK line if the device is in inbound calls mode\r\n");
 myputs("! K164mh commands: \r\n!    ? = Information\r\n!    V = Version\r\n!    R = Reset\r\n!    H = This info\r\n");        
}