/*
*       RTTY.C was developed as a method of controlling a
*       HAL Communications ST-6 teletype Terminal Unit (TU) which
*       was modified for RS-232. It is a very simple terminal
*       program that controls DTR for transmit/receive and RTS
*       for morse code ID. It has a morse code generator built
*       in. It is written by AA6ED in Borland C/C++ version 4.0
*       and also sports a serial communitations driver that
*       control up to eight COM ports IRQ 1-15 all at the same
*       time. To make this work for your computer, you will have
*       to find the parameter for setting the COM port and
*       rebuild the application.
*
*       Permission granted for non-commercial copying and use, 
*       provided this notice is retained.  Copyright 1999 
*       William H. Bytheway AA6ED, All Rights Reserved.
*/


#include <conio.h>
#include <bios.h>
#include <dos.h>
#include <ctype.h>
#include <stdlib.h>
#include "rtty.h"

/* nasty globals */
#define COMPORT COM5      /* This is where you define the COM port */
#define MYCALL "de aa6ed" /* Put your callsign here */

#define TRUE 1
#define FALSE 0

int transmitting = FALSE; /* check if transmitting */
int repeatlock = FALSE;   /* used for character echo */


int num_interrupts = 0;

/* Define the communications port absolute numbers */
#define COM1 0
#define COM2 1
#define COM3 2
#define COM4 3
#define COM5 4
#define COM6 5
#define COM7 6
#define COM8 7

#define buff_size 0xFF



/* Define the interrupt mask structure for every IRQ interrupt */
struct
{  unsigned int  port_address;
			unsigned char int_id;
			unsigned char base_is_set;
			unsigned char datardy;
			unsigned char ringbuf[buff_size+1];
			unsigned int  readptr;
			unsigned int  writptr;
} COM[8] = {
		{ 0x3F8,  4, 0, 0, 0, 0, 0 },   /* COM 1 */
		{ 0x2F8,  3, 0, 0, 0, 0, 0 },   /* COM 2 */
		{ 0x3E8, 10, 0, 0, 0, 0, 0 },   /* COM 3 */
		{ 0x3E0, 11, 0, 0, 0, 0, 0 },   /* COM 4 */
		{ 0x2F0, 12, 0, 0, 0, 0, 0 },   /* COM 5 */
		{ 0x2E8, 15, 0, 0, 0, 0, 0 },   /* COM 6 */
		{ 0x2E0,  0, 0, 0, 0, 0, 0 },   /* COM 7 */
		{ 0x260,  0, 0, 0, 0, 0, 0 }};  /* COM 8 */

/* Define the IRQ interrupt addresses and masks */
struct
{
unsigned char int_id;
unsigned char PIC1_mask;
unsigned char PIC2_mask;
unsigned char old_PIC1_mask;
unsigned char old_PIC2_mask;
} IRQ[16] = {
		{ 0x08, 0xFE, 0xFF, 0, 0},   /* IRQ00 tick counter */
		{ 0x09, 0xFD, 0xFF, 0, 0},   /* IRQ01 Keyboard     */
		{ 0x0A, 0xFB, 0xFF, 0, 0},   /* IRQ02 PIC2         */
		{ 0x0B, 0xF7, 0xFF, 0, 0},   /* IRQ03 COM2         */
		{ 0x0C, 0xEF, 0xFF, 0, 0},   /* IRQ04 COM1         */
		{ 0x0D, 0xDF, 0xFF, 0, 0},   /* IRQ05 Mouse        */
		{ 0x0E, 0xBF, 0xFF, 0, 0},   /* IRQ06 Floppy Drive */
		{ 0x0F, 0x7F, 0xFF, 0, 0},   /* IRQ07 Printer      */
		{ 0x70, 0xFB, 0xFE, 0, 0},   /* IRQ08 Clock        */
		{ 0x71, 0xFB, 0xFD, 0, 0},   /* IRQ09 Redirect IRQ2*/
		{ 0x72, 0xFB, 0xFB, 0, 0},   /* IRQ10 COM3         */
		{ 0x73, 0xFB, 0xF7, 0, 0},   /* IRQ11 COM4         */
		{ 0x74, 0xFB, 0xEF, 0, 0},   /* IRQ12 COM5         */
		{ 0x75, 0xFB, 0xDF, 0, 0},   /* IRQ13 System Int   */
		{ 0x76, 0xFB, 0xBF, 0, 0},   /* IRQ14 Fixed Disk   */
		{ 0x77, 0xFB, 0x7F, 0, 0}};  /* IRQ15 COM6         */

/* Define address adders for the various Async card registers  */
#define RBR 0x00   /* Transmitter Holding Register        */
#define IER 0x01   /* Interrupt Enable Register           */
#define IIR 0x02   /* Interrupt Identification Register   */
#define LCR 0x03   /* Line Control Register               */
#define MCR 0x04   /* Modem Control Register              */
#define LSR 0x05   /* Line Status Register                */
#define MSR 0x06   /* Modem Status Register               */
#define DLL 0x00   /* Divisor Latch Least Significant     */
#define DLM 0x01   /* Divisor Latch Most  Significant     */

/* Define port addresses for the 8259 interrupt controller   */
#define PICCMD1 0x20  /* Primary Controller   */
#define PICMSK1 0x21
#define PICCMD2 0xA0  /* Secondary Controller */
#define PICMSK2 0xA1
#define PICEOI  0x20  /* End of interrupt     */

/* Define UART byte settings   */
#define pSpace 0x38
#define pOdd   0x08
#define pMark  0x28
#define pEven  0x18
#define pNone  0x00

/* Define UART number of data bits   */
#define d5 0x00
#define d6 0x01
#define d7 0x02
#define d8 0x03

/* define UART number of stop bits   */
#define s1 0x00
#define s2 0x04

#define true  1
#define false 0


/* Store the pointers to the interrupt address  */
void interrupt (*Com1)();
void interrupt (*Com2)();
void interrupt (*Com3)();
void interrupt (*Com4)();
void interrupt (*Com5)();
void interrupt (*Com6)();
void interrupt (*Com7)();
void interrupt (*Com8)();

/* Async Interrupt routine   */
void Asynch_COM(int irq_id)
{
int baseid;

num_interrupts++;
	for (baseid = 0; baseid <= 7; baseid++)
	{
		if ((COM[baseid].int_id == irq_id) && COM[baseid].base_is_set)
		{
 		  int ComBase = COM[baseid].port_address;
		  if (inportb(ComBase+IIR) == 0x04)
		{
		COM[baseid].ringbuf[COM[baseid].writptr] = inportb(ComBase + RBR);
		/* repeat data to external hardware*/
		if (!repeatlock)
			outportb(ComBase+RBR, COM[baseid].ringbuf[COM[baseid].writptr]);

		COM[baseid].writptr = (COM[baseid].writptr + 1) & buff_size;
		COM[baseid].datardy = true;
	}
	if (irq_id > 7) outportb(PICCMD2, PICEOI);
		outportb(PICCMD1,PICEOI);
	}   /* test for matching interrupt and base is set     */
		}      /* loop through 8 COM ports and test for baseid    */
}        /* end of the interrupt at this point and return   */

/* Pass the correct interrupt to the Async_COM(interrupt id)   */
void interrupt IRQ0_Interrupt()  { Asynch_COM(0);   }
void interrupt IRQ1_Interrupt()  { Asynch_COM(1);   }
void interrupt IRQ2_Interrupt()  { Asynch_COM(2);   }
void interrupt IRQ3_Interrupt()  { Asynch_COM(3);   }
void interrupt IRQ4_Interrupt()  { Asynch_COM(4);   }
void interrupt IRQ5_Interrupt()  { Asynch_COM(5);   }
void interrupt IRQ6_Interrupt()  { Asynch_COM(6);   }
void interrupt IRQ7_Interrupt()  { Asynch_COM(7);   }
void interrupt IRQ8_Interrupt()  { Asynch_COM(8);   }
void interrupt IRQ9_Interrupt()  { Asynch_COM(9);   }
void interrupt IRQ10_Interrupt() { Asynch_COM(10);  }
void interrupt IRQ11_Interrupt() { Asynch_COM(11);  }
void interrupt IRQ12_Interrupt() { Asynch_COM(12);  }
void interrupt IRQ13_Interrupt() { Asynch_COM(13);  }
void interrupt IRQ14_Interrupt() { Asynch_COM(14);  }
void interrupt IRQ15_Interrupt() { Asynch_COM(15);  }

/* another global */
int ComBase;

/* Open the COM port     */
void OpenCom(int ComPort,int baud,int parity,int databits,int stopbits)
{
int rate;
int LCRreg;
int interrupt_id;
int interrupt_no;


	COM[ComPort].base_is_set = true;
	interrupt_no = COM[ComPort].int_id;
	interrupt_id = IRQ[interrupt_no].int_id;
	switch (ComPort)
	{
		case 0: Com1 = getvect(interrupt_id); break;
		case 1: Com2 = getvect(interrupt_id); break;
		case 2: Com3 = getvect(interrupt_id); break;
		case 3: Com4 = getvect(interrupt_id); break;
		case 4: Com5 = getvect(interrupt_id); break;
		case 5: Com6 = getvect(interrupt_id); break;
		case 6: Com7 = getvect(interrupt_id); break;
		case 7: Com8 = getvect(interrupt_id); break;
	}
	disable();
	switch (interrupt_no)
	{
		case  0: setvect(interrupt_id,IRQ0_Interrupt);  break;
		case  1: setvect(interrupt_id,IRQ1_Interrupt);  break;
		case  2: setvect(interrupt_id,IRQ2_Interrupt);  break;
		case  3: setvect(interrupt_id,IRQ3_Interrupt);  break;
		case  4: setvect(interrupt_id,IRQ4_Interrupt);  break;
		case  5: setvect(interrupt_id,IRQ5_Interrupt);  break;
		case  6: setvect(interrupt_id,IRQ6_Interrupt);  break;
		case  7: setvect(interrupt_id,IRQ7_Interrupt);  break;
		case  8: setvect(interrupt_id,IRQ8_Interrupt);  break;
		case  9: setvect(interrupt_id,IRQ9_Interrupt);  break;
		case 10: setvect(interrupt_id,IRQ10_Interrupt); break;
		case 11: setvect(interrupt_id,IRQ11_Interrupt); break;
		case 12: setvect(interrupt_id,IRQ12_Interrupt); break;
		case 13: setvect(interrupt_id,IRQ13_Interrupt); break;
		case 14: setvect(interrupt_id,IRQ14_Interrupt); break;
		case 15: setvect(interrupt_id,IRQ15_Interrupt); break;
	}
	IRQ[interrupt_no].old_PIC2_mask = inportb(PICMSK2);
	IRQ[interrupt_no].old_PIC1_mask = inportb(PICMSK1);
	outportb(PICMSK2, (IRQ[interrupt_no].old_PIC2_mask & IRQ[interrupt_no].PIC2_mask));
	outportb(PICMSK1, (IRQ[interrupt_no].old_PIC1_mask & IRQ[interrupt_no].PIC1_mask));
	enable();
	outportb(PICCMD2,PICEOI);
	outportb(PICCMD1,PICEOI);

	ComBase = COM[ComPort].port_address;
	outportb(IER+ComBase, 0x01);
	outportb(MCR+ComBase, 0x08);
	LCRreg = 0x80;
	LCRreg = LCRreg | parity;
	LCRreg = LCRreg | databits;
	LCRreg = LCRreg | stopbits;
	outportb(LCR+ComBase, LCRreg);
	rate = 115200L / baud;

	/* Load the two bytes of the register */
	outportb(DLM+ComBase, (char)(rate>>8  & 0xFF));      /* Hi byte */
	outportb(DLL+ComBase, (char)(rate     & 0xFF));      /* Low byte */

	outportb(LCR+ComBase, LCRreg & 0x7F);

	/* final flush to clear both interrupt controllers */
	Asynch_COM(COM[COMPORT].int_id);
}

/*------------ Close any initialized COM -----------------------*/
void CloseCom(int ComPort)
{
int interrupt_no, interrupt_id, ComBase;

	ComBase = COM[ComPort].port_address;
	COM[ComPort].base_is_set = false;
	interrupt_no = COM[ComPort].int_id;
	interrupt_id = IRQ[interrupt_no].int_id;
	outport(PICMSK2, (IRQ[interrupt_no].old_PIC2_mask));
	outport(PICMSK1, (IRQ[interrupt_no].old_PIC1_mask));
	switch (ComPort)
	{
		case 0: setvect(interrupt_id,Com1);  break;
		case 1: setvect(interrupt_id,Com2);  break;
		case 2: setvect(interrupt_id,Com3);  break;
		case 3: setvect(interrupt_id,Com4);  break;
		case 4: setvect(interrupt_id,Com5);  break;
		case 5: setvect(interrupt_id,Com6);  break;
		case 6: setvect(interrupt_id,Com7);  break;
		case 7: setvect(interrupt_id,Com8);  break;
	}
	outportb(IER+ComBase, 0x00);
	outportb(MCR+ComBase, 0x00);
}

/* Checks the datardy status for a given receive port and gets the char   */
/* It returns a -1 if there isn't anything, or the character if there is */
int  recvchar(int ComPort)
{
int ch = -1;

	if (COM[ComPort].datardy)
	{
		ch = COM[ComPort].ringbuf[COM[ComPort].readptr];
		COM[ComPort].readptr = (COM[ComPort].readptr + 1) & buff_size;
		if (COM[ComPort].readptr == COM[ComPort].writptr)  COM[ComPort].datardy = false;
	}
	return(ch);
}

/* Transmits a character to the appropriate communications port   */
int xmitchar(int ComPort, int ch)
{
int  LSRreg,i=-1;

	do {
		LSRreg = inportb(LSR+COM[ComPort].port_address) & 0x20;
		i++;
	}  while (LSRreg == 0);
	outportb(RBR+COM[ComPort].port_address,ch);
	return(i);
}

/* Returns a -1 if there is no DCD connect, a +1 if there is  */
int DCDconnect(int ComPort)
{
	int ComBase;
	int i = -1;
	ComBase = COM[ComPort].port_address;        /* Set ComBase Address   */
	if ((inportb(ComBase + MSR) & 0x80) == 0x80) i = 1;
	return (i);
}


/* Resets the input buffer on any given comport    */
void purgeline(int ComPort)
{

	COM[ComPort].readptr = 0;
	COM[ComPort].writptr = 0;
	COM[ComPort].datardy = false;
}


/* finis rs-232 utility */

/* ----------------------------- MAIN PROGRAM ------------------ */


void send_baudot(int ch1)
{
int indx = 0;
char c;

	indx = (int) ch1;
	if (indx > 96) indx -= 32;			/* lower case becomes upper case  */
	state= table3[indx].state;   /* get BAUDOT case                */
	c= table3[indx].baudot;			       /* get Baudot character           */
	if (c != UU)    /* untranslatable!    */
	{
	switch (c) {
			case BCR:  xmitchar(COMPORT,BCR);/* baudot return */
				   c = BLF;              /* let following do line feed */
				   state = LTRS;
				   cprintf("\n");
			 	   break;
			case FIGS: state= FIGS; break;   /* FIGS c*/
			case LTRS:                  		   /* letters */
			case BLF:  c = LTRS;             /* baudot line feed replace LTRS */
			case BSP:  state = LTRS; break;  /* baudot space*/
			default:   break;
	}

	if (state != last_state)
	{
		if (state==LTRS)
		{
			xmitchar(COMPORT,LTRS);
			last_state = LTRS;
		}
		else if (state==FIGS)
		{
			xmitchar(COMPORT,FIGS);
			last_state = FIGS;
		}
	}
	xmitchar(COMPORT,c);
	cprintf("%c",ch1);
	}
	else
		cprintf("%c",'~');
	return;
}

/* morse code utility for sending ID */
void morse(char * input)
{
int i;
int j;
int k;
char in;
int l;
int timex;

	for (i=0; i<strlen(input); i++)
		input[i] = toupper(input[i]);

	textcolor(YELLOW);
	cprintf(" [I");

	delay(500);  /* wait a little before sending */
	for (i=0; i<strlen(input); i++)
	{
		l = (int) (input[i] - 32);
		timex = 20;
		if (morsecode[l][0] == '3')
			delay(2000/timex); /* inter-letter delay */
		else
		{
			for (j=0; j<5; j++)
			{
				in = morsecode[l][j];
				if (in != ' ')
			{
			k = atoi(&in);
			outportb(MCR+ComBase, 0x0B);
			sound(2125);
			delay(k*800/timex);
			outportb(MCR+ComBase, 0x09);
			nosound();
			delay(300/timex);
			}
		}
		delay(2000/timex);
		}
	}
	delay(500);
	cprintf("D] ");
	textcolor(RED);
	return;
}

/* more globals */
char  nn[4];
int   nndex = 0;
int   ii;


/* adds a delay, clears the receive buffer and continues */
/* if transmitting, transitions back to receive as well  */
void nnnn(char n)
{
	nn[nndex++] = toupper(n);      /* store in array */
	if (nndex > 3) nndex = 0;       /* circular storage */
	if ((nn[0] == 'N') && (nn[1] == 'N') &&
		(nn[2] == 'N') && (nn[3] == 'N'))
	{
		nn[0] = '\n';
		nn[1] = '\n';
		nn[2] = '\n';
		nn[3] = '\n';
		if (transmitting)
		{
			morse(MYCALL);
			outportb(MCR+ComBase, 0x08);
			cprintf("\n\r");
			textcolor(YELLOW);
			textbackground(GREEN);
			cprintf("<RX>");
			textcolor(BLUE);
			textbackground(LIGHTGRAY );
			cprintf("\n\r");
			transmitting = FALSE;
			repeatlock = FALSE;
		}
		delay(3000);
		purgeline(COMPORT);
	}
	return;
}



/* keeps the Model 28 cariage from slamming into the stops */
void autoreturn(char ch)
{
	if (((wherex() > 66) &&  (ch == ' ')) ||
		(wherex() > 75))
	{
		if (transmitting)
			send_baudot('\r');
		else
			cprintf("\n\r");
	}
	return;
}


/* ---------------------------------- MAIN -----------------------------*/
int main()
{
char c   = 0;
char ch  = 0;
int ch1 = 0;
char ch2 = 0;
int c_hi;
int c_lo;
int counter = 0;
char countmask = 0;
int statex;

	textmode(C4350);
	window(1,1,80,2);
	clrscr();
	textcolor(YELLOW);
	textbackground(GREEN);
	cprintf(" PF1 <RX> ");
	textcolor(YELLOW);
	textbackground(RED);
	cprintf(" PF2 <TX> ");
	textcolor(YELLOW);
	textbackground(BROWN);
	cprintf(" PF3 [CWID] ");
	textbackground(BLACK);
	cprintf("          ");
	textbackground(BLUE);
	textcolor(YELLOW);
	cprintf(" PF5 [MARK] " );
	textbackground(GREEN);
	cprintf(" PF6 [SPACE] ");
	textbackground(BROWN);
	cprintf(" PF7 [NoSound] ");
	textbackground(BLACK);

	/* SETUP THE MAIN RECEIVE WINDOW */
	window(1,2,80,50);
		textcolor(BLUE);
	textbackground(LIGHTGRAY);
	clrscr();


	setcbrk(0);

OpenCom(COMPORT,45,pNone,d5,s2);
do
{
	/*  Receive BAUDOT character      */
	if (COM[COMPORT].datardy)
	{
		ch = recvchar(COMPORT) & 0x7F;
		switch (ch) {
			case FIGS: statex= 32; 		/* subsequent characters from FIGS case */
				state = FIGS;
				break;
			case LTRS:                  		/* subsequent characters from letters */
			case BCR :            	      		/* case shift down          */
			case BLF :
			case BSP : statex= 0;         /* case shift down          */
			state= LTRS;
			break;
			default:   break;
			}
			ch= table1[ch + statex];/* lookup Baudot character, */
			switch (ch) {
				case   0 : break;
				case  UU : break;
				case  10 : break;
				case   7 : cprintf("~"); break;
				case  13 : cprintf("\n\r"); break;   /* force cr/lf  */
				default  : cprintf("%c",ch);
					autoreturn(ch);   /* fource linefeed */
					break;
			}
			nnnn(ch);   /* lock receive if nnnn received */
		}

		if (bioskey(1) !=0)   /*   Keyboard   */
			{
			ch1 = bioskey(0);
			c_lo = ch1 & 0x7F;
			c_hi = (ch1>>8) & 0x7F;
			switch (c_lo) {
				case 0:   /* function key */
					switch (c_hi) {
				/*F1*/		case 59:  outportb(MCR+ComBase, 0x08);  /* receive */
							  if (transmitting)
							{
							cprintf("\n\r");
							textcolor(YELLOW);
							textbackground(GREEN);
							cprintf("<RX>");
							repeatlock = FALSE;
							delay(3000);
							purgeline(COMPORT);
							repeatlock = FALSE;
							textcolor(BLUE);
							textbackground(LIGHTGRAY );
							cprintf(" \n\r");
							}
							transmitting = FALSE;
							break;
				/*F2*/		case 60:		outportb(MCR+ComBase, 0x09);   /* transmit */
							if (!transmitting)
							{
							cprintf("\n\r");
							textcolor(YELLOW);
							textbackground(RED);
							cprintf("<TX>");
							textcolor(RED);
							textbackground(LIGHTGRAY );
							cprintf("\n\r");
							repeatlock = TRUE;
							}
							transmitting = true;
							break;
				/*F3*/		case 61:		if (transmitting)
																															morse(MYCALL);
							 break;
				/*F4*/		case 62:
				/*F5*/ 		case 63:nosound();
						        sound(2125);
							cprintf("[MARK=2215 Hz]");
							break;
				/*F6*/		case 64: nosound();
							sound(2295);
							cprintf("[SPACE=2295 Hz]");
							break;
				/*F7*/			case 65:  nosound();
								cprintf("[NoSound]");
								break;
				/*F8*/		case 66:  break;
				/*F9*/		case 67:  repeatlock = TRUE;
							cprintf("<LOCK>");
							break;
				/*F10*/		case 68:  repeatlock = FALSE;
							cprintf("<UNLOCK>");
							break;
							default: break;/* ignore rest */
							}
							break;
						case  18:  outportb(MCR+ComBase, 0x08);
							transmitting = FALSE;
							textcolor(BLUE);
							break;  /*c-R receive*/
						case  20:  outportb(MCR+ComBase, 0x09);
							transmitting = TRUE;
							textcolor(RED);
							break; /*c-T transmit*/
						case   9:  if (transmitting)
							morse(MYCALL);
							break; /*c-I CW-ID*/
						case  27:  CloseCom(COMPORT);
							exit(0);      /* quit the program */
							break;
						default: 	if (transmitting)
								{
								send_baudot(c_lo);
								counter = 0;
								autoreturn((char) c_lo);
								nnnn(c_lo);
								}
								else
								{
								sound(2295);
								delay(20);
								sound(2125);
								delay(20);
								nosound();
								}
						}   /* end outer switch */
			}
			else if (transmitting && (counter < 30) &&
				(countmask  == 1))
				{
				counter++;
			 	if (state == FIGS)
					xmitchar(COMPORT,FIGS);
				else
					xmitchar(COMPORT,LTRS);
			/* end check for keyboard, cut and paste, and fast typeing*/
		}
		countmask++;
} while (ch1 !=27);

CloseCom(COMPORT);
return(0);
}








