15-Nov-87 22:41:42-MST,9407;000000000000
Date: Thursday, 21 August 1986  01:45-MDT
From: Phil Karn <karn at ka9q.bellcore.COM>
To:   info-hams
Re:   Controlling ICOM radios from the IBM PC - Part 2 (source code)

Here's the source code I wrote for controlling the ICOM radios through my PC
clone's printer port. Note that all frequencies are represented in hertz,
using longs; this makes things more consistent since different radio functions
require different precision.

-----------
/* icom.c
 * Library functions for driving the ICOM computer interface
 * Copyright 1986 by Phil Karn, KA9Q
 * Permission granted for free noncommercial copying and use by
 * licensed radio amateurs.
 */
#include "icom.h"

char *modes[] = {
"LSB",	/* 0 */
"USB",	/* 1 */
"AM",	/* 2 */
"CW",	/* 3 */
"RTTY",	/* 4 */
"FM",	/* 5 */
"CW Narrow",	/* 6 */
"RTTY Narrow",	/* 7 */
"lsb",	/* 8 */
"usb",	/* 9 */
"am",	/* a */
"cw narrow",	/* b */
"rtty narrow",	/* c */
"fm",	/* d */
"0xe",	/* e */
"0xf"	/* f */
};

/* Read band */
int
read_band(freq,lower,upper)
long freq;		/* Used just to select the radio */
long *lower,*upper;	/* Band limits returned through here */
{
	register int i;

	start_cmd();
	if(send_byte(BAND | band(freq)) < 0){
		end_cmd();
		return -1;
	}
	*upper = 0;
	read_byte();		/* Toss opening delim */
	for(i=0;i<6;i++)
		*upper = *upper * 10 + (read_byte() & 0xf);
	read_byte();		/* Toss closing delim */
	*upper *= 10000;	/* Convert to hertz */

	*lower = 0;
	read_byte();		/* Toss second opening delim */
	for(i=0;i<6;i++)
		*lower = *lower * 10 + (read_byte() & 0xf);
	read_byte();		/* Toss second closing delim */
	*lower *= 10000;	/* Convert to hertz */	
	end_cmd();
	return 0;
}
/* Set frequency; the proper radio is automatically selected */
int
set_freq(freq)
long freq;	/* Frequency, hertz */
{
	register int i;
	char fstr[15];

	start_cmd();
	if(send_byte(FREQ | band(freq)) < 0){
		end_cmd();
		return -1;
	}
	send_byte(FREQ | 0xd);
	sprintf(fstr,"%09ld",freq/10);
	for(i=0;i<9;i++)
		send_byte(FREQ | (fstr[i] - '0'));
	send_byte(FREQ | 0xe);
	end_cmd();
	return 0;
}

/* Read frequency */
long
read_freq(freq)
long freq;	/* For band selection only */
{
	register int i;

	start_cmd();
	if(send_byte(FREQ | band(freq)) < 0){
		end_cmd();
		return -1;
	}
	if(read_byte() < 0)	 /* Discard opening delimiter */
		return -1;
	freq = 0;
	for(i=0;i<9;i++){
		freq = freq * 10 + (read_byte() & 0xf);
	}
	read_byte();		/* Discard closing delimiter */
	freq *= 10;
	end_cmd();
	return freq;
}
/* set mode */
int
set_mode(freq,mode)
long freq;		/* For radio selection */
int mode;		/* Desired operating mode */
{
	start_cmd();
	if(send_byte(MODE | band(freq)) < 0){
		end_cmd();
		return -1;
	}
	send_byte(MODE | 0xd);
	send_byte(MODE | mode);
	send_byte(MODE | 0xe);
	end_cmd();
	return 0;
}
/* Return current mode */
int
read_mode(freq)
long freq;		/* For radio selection */
{
	int c;

	start_cmd();
	if(send_byte(MODE | band(freq)) < 0){
		end_cmd();
		return -1;
	}
	read_byte();
	c = read_byte();
	read_byte();
	end_cmd();
	return c & 0xf;
}
/* Set offset */
int
set_offset(freq,offset)
long freq;	/* For radio selection */
long offset;	/* Offset, hertz */
{
	register int i;
	char fstr[15];

	start_cmd();
	if(send_byte(OFFSET | band(freq)) < 0){
		end_cmd();
		return -1;
	}
	send_byte(OFFSET | 0xd);
	sprintf(fstr,"%09ld",freq/1000);
	for(i=0;i<9;i++)
		send_byte(OFFSET | (fstr[i] - '0'));
	send_byte(OFFSET | 0xe);
	end_cmd();
	return 0;
}

/* Read offset */
long
read_offset(freq)
long freq;	/* For radio selection */
{
	register int i;
	long offset;
	
	start_cmd();
	if(send_byte(OFFSET | band(freq)) < 0){
		end_cmd();
		return -1;
	}
	read_byte();	 /* Discard opening delimiter */
	offset = 0;
	for(i=0;i<9;i++)
		offset = offset * 10 + (read_byte() & 0xf);
	read_byte();	/* Discard closing delimiter */
	offset *= 1000;
	end_cmd();
	return offset;
}
/* Select memory channel or vfo */
int
set_mem(freq,val)
long freq;		/* For radio selection */
int val;		/* Desired VFO/channel number */
{
	start_cmd();
	if(send_byte(MEMVFO | band(freq)) < 0){
		end_cmd();
		return -1;
	}
	send_byte(MEMVFO | 0xd);
	send_byte(MEMVFO | ((val >> 4) & 0xf));	/* tens digit */
	send_byte(MEMVFO | (val & 0xf));	/* units digit */
	send_byte(MEMVFO | 0xe);
	end_cmd();
	return 0;
}
/* Transfer between VFO and memory */
int
transfer(freq,dir)
long freq;		/* For radio selection */
int dir;		/* Desired direction of transfer */
{
	start_cmd();
	if(send_byte(MEMRW | band(freq)) < 0){
		end_cmd();
		return -1;
	}
	send_byte(MEMRW | 0xd);
	send_byte(MEMRW | dir);
	send_byte(MEMRW | 0xe);
	end_cmd();
	return 0;
}
/* Set band
 * Uses the hack by NG6Q in April 1986 Ham Radio
 * Warning: untested (I don't have a 751).
 */
int
set_band(freq,b)
long freq;		/* For radio selection */
int b;			/* Desired band */
{
	long funny;

	set_mem(freq,38);	/* Select channel 38 */
	funny = (freq/1000000) * 1000000;	/* Truncate to Mhz */
	funny += 100000 * b;		/* Desired band goes in 100khz digit*/
	set_freq(funny);
	transfer(freq,WRITE);		/* Store in memory */
	set_mem(freq,0);		/* Go back to VFO */
	transfer(freq,READ);		/* Get the funny value */
	set_freq(freq);			/* Put in the one we really want */
	return 0;
}

/* The following are internal subroutines that perform the low-level
 * parts of the host/radio protocol. These can be "static" if you wish.
 */

/* Send individual byte of a message */
int
send_byte(c)
char c;
{	
	register int i;

	outportb(I_DATA,c);

	/* Turn on WP and output mode in addition to SRQ */
	outportb(I_CTL,CTL_POL^(SRQ_CMD|WP_CMD|OUTPUT_MODE));

	/* Wait for DAV to go active low */
	for(i=TIMEOUT;i != 0;i--){
		if((inportb(I_DAV) & DAV_STAT) == 0)
			break;
	}
	if(i == 0){
		outportb(I_CTL,CTL_POL);
		printf("sendbyte fail\n");
		return -1;
	}
	/* Drop WP and output mode, keeping SRQ active */
	outportb(I_CTL,CTL_POL^SRQ_CMD);

	/* Wait for DAV to go inactive high */
	for(i=TIMEOUT;i != 0;i--){
		if((inportb(I_DAV) & DAV_STAT) != 0)
			break;
	}		
	if(i == 0){
		outportb(I_CTL,CTL_POL);
		printf("sendbyte fail 2\n");
		return -2;
	}
	return 0;
}

/* Read individual byte within a message */
int
read_byte()
{
	register int i;
	register int c;

	/* Configure for input */
	outportb(I_CTL,CTL_POL^(RP_CMD|SRQ_CMD));

	/* Wait for DAV to go active low */
	for(i=TIMEOUT;i != 0;i--){
		if((inportb(I_DAV) & DAV_STAT) == 0)
			break;
	}
	if(i == 0){
		outportb(I_CTL,CTL_POL);
		printf("read fail\n");
		return -1;
	}
	/* Read data byte from bus */
	c = inportb(I_DATA);

	/* Drop RP, keeping SRQ active */
	outportb(I_CTL,CTL_POL^SRQ_CMD);

	/* Wait for DAV to go inactive high */
	for(i=TIMEOUT;i != 0;i--){
		if((inportb(I_DAV) & DAV_STAT) != 0)
			break;
	}		
	if(i == 0){
		outportb(I_CTL,CTL_POL);
		printf("read fail 2\n");
		return -2;
	}
	return c & 0xff;
}
/* Derive band number from frequency */
int
band(freq)
register long freq;
{
	if(freq >= 1200000000){
		return MHZ_1200;
	} else if(freq >= 420000000){
		return MHZ_430;
	} else if(freq >= 220000000){
		return MHZ_220;
	} else if(freq >= 140000000){
		return MHZ_144;
	} else if(freq >= 50000000){
		return MHZ_50;
	} else
		return HF;
}
/* Begin a message */
start_cmd()
{
	/* Assert SRQ */
	outportb(I_CTL,CTL_POL^SRQ_CMD);
}
/* End a message */
end_cmd()
{
	register int i;

	/* Wait a little bit */
	for(i=WAIT;i != 0;i--)
		;
	/* Deactivate SRQ */
	outportb(I_CTL,CTL_POL);
}
-----------------
/* icom.h
 * Definitions for the ICOM library functions
 * 21 Aug 1986
 * Phil Karn, KA9Q
 */

/* System-dependent constants; edit to taste */

/* Port addresses */
#define	I_DATA		0x3bc	/* Data I/O port */
#define	I_CTL		0x3be	/* Control port (output) */
#define	I_DAV		0x3bd	/* Data available port (input) */

/* Bits within I_DAV */
#define	DAV_STAT	0x40
#define	DAV_POL		0x40	/* DAV is negative polarity */

/* Bits within I_CTL */
#define	OUTPUT_MODE	0x20
#define	RP_CMD		0x8
#define	WP_CMD		0x2
#define	SRQ_CMD		0x1
/* Specify any bits in I_CTL which are negative logic.
 * Output mode, RP and WP are negative logic; SRQ is positive
 */
#define	CTL_POL	(OUTPUT_MODE|RP_CMD|WP_CMD)

/* These two values were found experimentally to work on an 8-MHz 8088
 * Increase WAIT if you get frequent timeouts or protocol lockups
 */
#define	TIMEOUT	65535	/* Timeout on a read/write operation */
#define	WAIT	1100	/* Delay at end of sequence */

/* The following definitions are fixed by the ICOM design; they should not
 * have to be changed.
 */

/* Commands */
#define	BAND		0x10
#define	FREQ		0x20
#define	MODE		0x30
#define	OFFSET		0x40
#define	MEMVFO		0x50
#define	MEMRW		0x60

/* Addresses */
#define	HF		0x1	/* IC-71 or IC-751 */
#define	MHZ_50		0x2
#define	MHZ_144		0x3	/* IC-271 */
#define	MHZ_220		0x4
#define	MHZ_430		0x5	/* IC-471 */
#define	MHZ_1200	0x6	/* IC-1271 */

/* Modes */
#define	LSB	0
#define	USB	1
#define	AM	2
#define	CW	3
#define	RTTY	4
#define	FM	5
#define	CWN	6
#define	RTTYN	7

#define	WRITE	1	/* VFO to memory */
#define	READ	2	/* Memory to VFO */

long read_freq();
--------------
End of code
