								/* dnload.c
******************************************************************************
	dnload.c - download S-record hexfile to 6811 MCU in boot mode
	Christopher Giese <geezer[AT]execpc.com>

	Release date 7/17/98. Distribute freely. ABSOLUTELY NO WARRANTY.

Notes:
	Should support 68HC11 A-series but I have not tested this.

Bugs/to do:
	- Support binary, Intel-hex, other file formats?
	- Add command-line option to download minimal number of bytes
	  (instead of 256 bytes minimum needed by A-series).
*****************************************************************************/
#include	<stdlib.h>	/* exit() */
#include	<string.h>	/* strncpy() strupr() strstr() strcat() */
#include	<stdio.h>	/* printf() */
#include	<conio.h>	/* kbhit() getch() cprintf() */
#include	<ctype.h>	/* toupper() */
#include	<dos.h>		/* inportb() outportb() peek() delay() */

#if defined(__DJGPP__)
#define		strncmpi	strncasecmp
#define		peek		_farpeekw
/* make the executable a little bit smaller. It also helps immensely if
you run STRIP on the DJGPP executable. */
__crt0_load_environment_file()
{	}
#endif

#define		BUF_LEN		256
#define		CODE_LEN	1024

char Code[CODE_LEN];
/*****************************************************************************
	name:	portExists
	action:	interrogates BIOS data segment to see if a COM port exists
	returns:zero if the port (0-3) doesn't exist, I/O address if it does
*****************************************************************************/
unsigned portExists(unsigned Port)
{	return(peek(0x40, 2 * Port)); }
/*****************************************************************************
	name:	portRead
	action: tries to read from COM port. If Timeout is nonzero, times
		out after Timeout ms. Also returns if a key is pressed.
	returns:-1 if timeout, -2 if keypress, else byte that was read
*****************************************************************************/
int portRead(unsigned PortAdr, unsigned Timeout)
{	unsigned Temp;

/* make sure PortAdr + 0 points to tx/rx register; not BRG register */
	outportb(PortAdr + 3, inportb(PortAdr + 3) & 0x7F);
	for(Temp=Timeout ? Timeout : 1; Temp; )
	{	if(inportb(PortAdr + 5) & 1)
			return(inportb(PortAdr));
		if(kbhit())
		{	getch();
			return(-2); }
		delay(1);
		if(Timeout) Temp--; }
	return(-1); }
/*****************************************************************************
	name:	portWrite
	action: tries to write to the COM port. If Timeout is nonzero, times
		out after Timeout ms. Also returns if a key is pressed.
	returns:-1 if timeout, -2 if keypress, zero if sucess
*****************************************************************************/
int portWrite(unsigned PortAdr, char Byte, unsigned Timeout)
{	unsigned Temp;

/* make sure PortAdr + 0 points to tx/rx register; not BRG register */
	outportb(PortAdr + 3, inportb(PortAdr + 3) & 0x7F);
	for(Temp=Timeout ? Timeout : 1; Temp; )
	{	if(inportb(PortAdr + 5) & 0x20)//& 0x40)
		{	outportb(PortAdr, Byte);
			return(0); }
		if(kbhit())
		{	getch();
			return(-2); }
		delay(1);
		if(Timeout) Temp--; }
	return(-1); }
/*****************************************************************************
	name:	getHexNybble
	action:	reads character at String, converts it from hex ASCII
		to binary
	returns:converted binary nybble or -1 if bad hex digit
*****************************************************************************/
int getHexNybble(char *String)
{	int RetVal;

	RetVal=toupper(*String);
	if(RetVal < '0' || RetVal > 'F') return(-1);
	if(RetVal > '9')
	{	if(RetVal < 'A') return(-1);
		RetVal -= 7; }
	return(RetVal - '0'); }
/*****************************************************************************
	name:	getHexByte
	action:	reads two characters at String, converts them from hex
		ASCII to binary
	returns:converted binary byte or -1 if bad hex digit(s)
*****************************************************************************/
int getHexByte(char *String)
{	int Temp, RetVal=0;

	Temp=getHexNybble(String++);
	if(Temp < 0) return(Temp);
	RetVal=(RetVal << 4) | Temp;
	Temp=getHexNybble(String++);
	if(Temp < 0) return(Temp);
	RetVal=(RetVal << 4) | Temp;
	return(RetVal); }
/*****************************************************************************
	name:	getHexWord
	action:	reads four characters at String, converts them from hex
		ASCII to binary
	returns:converted binary word (16-bit) or -1 if bad hex digit(s)
*****************************************************************************/
long getHexWord(char *String)
{	long RetVal=0;
	int Temp;

	Temp=getHexNybble(String++);
	if(Temp < 0) return(Temp);
	RetVal=(RetVal << 4) | Temp;
	Temp=getHexNybble(String++);
	if(Temp < 0) return(Temp);
	RetVal=(RetVal << 4) | Temp;
	Temp=getHexNybble(String++);
	if(Temp < 0) return(Temp);
	RetVal=(RetVal << 4) | Temp;
	Temp=getHexNybble(String++);
	if(Temp < 0) return(Temp);
	RetVal=(RetVal << 4) | Temp;
	return(RetVal); }
/*****************************************************************************
	name:	main
*****************************************************************************/
void main(int ArgCnt, char *ArgVal[])
{	int PortAdr, RecLen, Offset, HiAdr, Adr, Temp;
	char Buffer[BUF_LEN];
	FILE *Handle;

	if(ArgCnt < 2)
USE:	{	printf("Downloads S-record hexfile to 6811 MCU via serial "
			"port. Usage:\nDNLOAD HEXFILE[.S19] [COMn]\n");
		exit(1); }
/* process filename */
	strncpy(Buffer, ArgVal[1], BUF_LEN - 5);
	strupr(Buffer);
	if(strstr(Buffer, ".") == NULL) strcat(Buffer, ".S19");
/* process optional port name */
	if(ArgCnt >= 3)
	{	if(!strncmpi(ArgVal[2], "COM", 3))
		{	Temp=ArgVal[2][3];
			if(Temp < '1' || Temp > '4') goto COM;
			Temp -= '1'; }
		else
COM:		{	printf("Second argument must specify a valid COM "
				"port (e.g. 'COM1', 'com2', 'CoM4').\n");
			exit(1); }}
/* only two args? Set Temp=0 (i.e. use COM1) */
	else Temp=0;
/* detect COM port */
	PortAdr=portExists(Temp);
	if(PortAdr == 0)
	{	printf("Sorry, COM%1u does not exist on this PC.\n",
			Temp + 1);
		exit(2); }
/* set port for 1200 N 8 1 */
	outportb(PortAdr + 3, 0x80);
	outportb(PortAdr, 0x60);	/* 1200 */
	outportb(PortAdr + 1, 0);
	outportb(PortAdr + 3, 0x03);	/* 8 N 1 */
/* open file */
	Handle=fopen(Buffer, "r");
	if(Handle == NULL)
	{	printf("Can't open file '%s'.\n", Buffer);
		exit(3); }
/* fill Code with 6811 NOPs */
	memset(Code, 0x01, CODE_LEN);
/* read file */
	HiAdr=0;
	while(fgets(Buffer, BUF_LEN, Handle) != NULL)
/* S9 marks end of file */
	{	if(!strncmpi(Buffer, "S9", 2)) break;
/* S1 marks a record */
		if(strncmpi(Buffer, "S1", 2)) continue;
/* total bytes in this line, after the S1 */
		if((RecLen=getHexByte(Buffer + 2)) < 0)
BAD:		{	printf("Bad S-record:\n%s\n", Buffer);
			exit(4); }
/* load address for this line */
		if((Adr=getHexWord(Buffer + 4)) < 0) goto BAD;
		if(Adr >= CODE_LEN)
		{	printf("Load address of S-record > %u:\n%s\n",
				CODE_LEN, Buffer);
			exit(4); }
/* data bytes */
		for(Offset=0; Offset < RecLen - 3; Offset++)
		{	if((Temp=getHexByte(Buffer + 8 + 2 * Offset)) < 0)
				goto BAD;
			Code[Adr + Offset]=Temp; }
/* ignore checksum */
		if(Adr + Offset > HiAdr) HiAdr=Adr + Offset; }
/* await break (zero byte) */
	printf("Awaiting BREAK from 6811 system...\n");
	Temp=portRead(PortAdr, 0);
ERR:	if(Temp == -1)
	{	printf("Timeout while reading/writing serial port.\n");
		exit(5); }
	else if(Temp == -2)
	{	printf("Aborted...\n");
		exit(6); }
/* send 0xFF. This tells 6811 to switch from 7812 baud to 1200 baud */
	printf("Sending 0xFF byte...\n");
	Temp=portWrite(PortAdr, 0xFF, 1000);
	if(Temp < 0) goto ERR;
/* send bytes, awaiting echo of each byte */
	printf("Downloading code...\n");
/* the 6811 F-series (68-pin PLCC) doesn't need this; it will automatically
stop downloading and start running after a timeout. The 6811 A-series
(52-pin PLCC; 48-pin DIP) DOES need this: */
	if(HiAdr < 255) HiAdr=255;

	for(Offset=0; Offset < HiAdr; Offset++)
	{	Temp=portWrite(PortAdr, Code[Offset], 1000);
		if(Temp < 0) goto ERR;
		Temp=portRead(PortAdr, 1000);
		if(Temp < 0) goto ERR;
/* DJGPP uses buffered output for printf() (i.e. you'd see nothing
until the next \n), so use cprintf() */
		cprintf("<%02X>", Temp); }
/* exit with errorlevel 0 */
	printf("\n...done.\n");
	exit(0); }
