Skip to content

A TCP Socket I/O Driver Framework for Z88DK

16-02-11 huzzah - a driver

The telnet server i got working is on the 1802 but i think the Z80 will better be able to handle the multi-user version.  I’m trying to establish the sockets as Z88dk stream drivers so i can use things like fopen() fprintf() etc.  On the advice of alvin from the z88dk forums I’m updating the drivers and crt I built before.

With a lot of help I have

  • added a new folder  C:\apps\z88dk\libsrc\_DEVELOPMENT\target\zmc\driver\character\zmc_00_io_sock with two files zmc_00_io_sock_ichar_msg_getc.asm and putc.asm
  • Added files zmc_00_io_sock.asm and .lst to the folder above that (…\character)
  • added invocations of the drivers to the zmc_crt_99.m4 file in C:\apps\z88dk\libsrc\_DEVELOPMENT\target\zmc\startup and run M4 to create the .asm version
  • rerun ‘winmake zmc’ in the C:\apps\z88dk\libsrc\_DEVELOPMENT folder

now I can reference SOCK0…SOCK7 in my code as below

/*
   test the socket driver framework
*/
#include <stdio.h>
extern FILE *SOCK0;extern FILE *SOCK1;extern FILE *SOCK2;
FILE* socket[3]={SOCK0,SOCK1,SOCK2};
void main()
{
	unsigned char c='*'; unsigned int i;
	printf("Hello via printf\n");
	fprintf(SOCK0,"Hello via fprintf(SOCK0)\n");

	printf("SOCK2 says:%c\n",getc(SOCK2));
	printf("SOCK1 says:%c\n",getc(SOCK1));
	printf("SOCK0 says:%c\n",getc(SOCK0));

}

For the moment the output routine just sends the output to serial and the input for socket N just returns the ascii digit N.

INCLUDE "clib_cfg.asm"
SECTION code_driver
PUBLIC zmc_00_io_sock_ichar_msg_getc
EXTERN error_mc

zmc_00_io_sock_ichar_msg_getc:

   ;   enter:  ix = & FDSTRUCT.JP
   ;    exit:  a = char after character set translation
   ;           carry set on error with hl=0 (err) or -1 (eof)
   ; can use:  af, bc, de, hl

   ;;;;; getchar here
   ;;;;; (ix+14) = socket number 0..7
;stub socket input routine always returns socket number as an ascii digit e.g. 0x30 for socket 0  
   ld A,(ix+14)	; that's it!
   or A,#0x30
   ; A = ascii code
   
   cp CHAR_CTRL_D
   jp z, error_mc              ; generate EOF
   
   cp 13
   jr z, cr
   
   cp 10
   jr z, lf
   
   or a
   ret

lf:

   ld a,CHAR_LF
   ret

cr:

   ld a,CHAR_CR
   ret

A Bad Telnet Approach

16-02-06 onegame

So, if all I wanted to do was play “Doughnuts” over Telnet I could declare victory now.  I swallowed my pride and implemented a blocking tgetline() that throws away control characters and returns only when it sees an end of line.  I embedded that in a modified version of the web server code and it “works”.  It would only play a single game and has other functional limits but the worst problem is that it could host only a single user at a time and would refuse other connections.  To correct that I need to support multiple sockets and sort of turn the code on its head so the input is being read at the top level and passed down rather than inside the game itself. For now, though, I think I’ll go sit in the sun and read a book.

The first set of code has: tgetcraw which gets a single character from the wiznet chip and removes it from the buffer; tgetchar which filters out initialization sequences; and tgetline which assembles a line in a buffer passed and returns it when the \r\n sequence signals the end of the line.

unsigned char tgetcraw(){//get a raw character from telnet
	unsigned int rsize;
	while(wizGetCtl16(SnRX_RSR)==0);
	recv0(buf,1);flush(1);
	return buf[0];
}
unsigned char tgetchar(){//get a character from telnet
	unsigned char craw;
	craw=tgetcraw();
	while (craw==255){//negotiation sequence
		craw=tgetcraw();craw=tgetcraw();
		craw=tgetcraw();
	}
	return craw;
}
unsigned int tgetline(unsigned char * linebuf, unsigned int maxlen){
	unsigned char c,tgotlen=0;
	c=tgetchar();
	while(c!='\r' && c!='\n'){
		*linebuf=c;
		if (tgotlen<maxlen){
			linebuf++; tgotlen++;
		}
		c=tgetchar();
	}
	if (c='\r'){c=tgetchar();} //swallow the lf of a cr,lf pair
	*linebuf=0; //terminate linebuf
	return tgotlen;
}

/*****************************************************************************
//  File Name    : w5500tdonuts.c
//  Version      : 1
//  Description  : Simple Telnet Server With Wiznet w5500
//  Author       : Bill Rowe
//  Target       : Olduino
//  Compiler     : LCC1802
//  Created	 : Feb 6, 2016
*****************************************************************************/
#define nofloats
#include <nstdlib.h> //for printf etc.
#include <cpu1802spd4port7.h> //defines processor type, speed, and host port
#include <olduino.h> //for digitalRead, digitalWrite, delay
#include <hspi2.h> //hardware spi header
//end of olduino/1802 specific declares
#include "w5500data.h"  //wiznet definitions
#include "w5500code.h"  //wiznet code definitions
union IPaddr thisip={182}; //the ip that a form/request came from
#define MAX_BUF 512
unsigned char buf[MAX_BUF];			//memory buffer for incoming & outgoing data
	static unsigned char Inst1[]=
		"I AM THINKING OF A 3 DIGIT NUMBER.\n\rTRY TO GUESS " //50
		"MY NUMBER AND I WILL GIVE YOU CLUES AS FOLLOWS:\n\r";
	static unsigned char Inst2[]=
		"...PICO - ONE DIGIT IS IN THE WRONG PLACE\n\r"
		"...FERMI - ONE DIGIT IS IN THE CORRECT PLACE\n\r"
		"...DOUGHNUTS - NO DIGIT IS CORRECT\n\r";
unsigned char secret[4]="145";
unsigned char guess [4]={0};
unsigned int pages=0; games=0; sessions=0,turns=0;
char pnbuf[8];
unsigned int tbrnd(){//random numbers ala tiny basic R:=R*2345+6789
	static unsigned int R=1;
	R=R*58653+13849;//R=R*2345+6789;
	//printf("%d\n",R);
	return R;
}
void setsecret(){//set the global secret to a 3 digit random number
	int i=0,d1=0,d2,d3;
	while(d1==0){
		d1=tbrnd()%10;
	}
	d2=tbrnd()%10;
	while(d1==d2){
		d2=tbrnd()%10;
	}
	d3=tbrnd()%10;
	while(d3==d1||d3==d2){
		d3=tbrnd()%10;
	}
	secret[0]=d1+0x30;secret[1]=d2+0x30;secret[2]=d3+0x30;secret[3]=0;
	printf("secret=%s\n",secret);
}
void bagelsinit(){
	int sendrc;
	games++;
	pages++;
	setsecret();
	printf("IP: %d.%d.%d.%d,slot %d,secret %s\n",
		thisip.c[0],thisip.c[1],thisip.c[2],thisip.c[3],0,secret);
}
void phexblk(unsigned char * buf,unsigned int len){
	int i;
	for (i=0;i<len;i++){
	printf("%cx",buf[i]);
	}
}
unsigned char tgetcraw(){//get a raw character from telnet
	unsigned int rsize;
	while(wizGetCtl16(SnRX_RSR)==0);
	//rsize=wizGetCtl16(SnRX_RSR); //get the size of the received data
	//printf("rawsize=%d ",rsize);
	recv0(buf,1);flush(1);
	//printf(" rawchar=%cx\n",rsize,buf[0]);
	return buf[0];
}
unsigned char tgetchar(){//get a character from telnet
	unsigned char craw;
	craw=tgetcraw();
	while (craw==255){//negotiation sequence
		craw=tgetcraw();craw=tgetcraw();
		craw=tgetcraw();
	}
	return craw;
}
unsigned int tgetline(unsigned char * linebuf, unsigned int maxlen){
	unsigned char c,tgotlen=0;
	c=tgetchar();
	while(c!='\r' && c!='\n'){
		*linebuf=c;
		if (tgotlen<maxlen){
			linebuf++; tgotlen++;
		}
		c=tgetchar();
	}
	if (c='\r'){c=tgetchar();} //swallow the lf of a cr,lf pair
	*linebuf=0; //terminate linebuf
	return tgotlen;
}
void sendresp(int pico, int fermi){
	unsigned int i;
	pages++;
	sendlit("  I say ");
	if (fermi==3){
		sendlit("YOU GOT IT!\r\n");
	} else {//once we're here we're going to send the guess form
		if (pico<0){//flag for duplicate digits
			sendconst("Oh, maybe I didn't tell you.  No two digits are the same.\r\n");
		} else if ((pico+fermi)==0){
			sendlit("Doughnuts!");
		} else{
			for(i=0;i<pico;i++) sendlit("Pico ");
			for(i=0;i<fermi;i++) sendlit("Fermi ");
		}
		sendlit("\r\n");
		sendconst("Try Again\r\n:"); 	// ask for another guess
	}
}
void bagelsturn(unsigned char guess[]){
	int i, /*ipslot,*/ pico=0, fermi=0;
	turns++; //count the turn
	for (i=0;i<3;i++){
		if (guess[i]==secret[i]){
			fermi++;
		}
	}
	if (guess[0]==secret[1]||guess[0]==secret[2]){pico++;}
	if (guess[1]==secret[0]||guess[1]==secret[2]){pico++;}
	if (guess[2]==secret[0]||guess[2]==secret[1]){pico++;}
	if (guess[0]==guess[1]||guess[0]==guess[2]||guess[1]==guess[2]||strlen((char *)guess)!=3){pico=-1;}
	sendresp(pico, fermi);
}

void handlesession(){
	unsigned char line[32]; unsigned int linelen;
	bagelsinit();
	sendconst(Inst1); sendconst(Inst2);
	linelen=tgetline(line,32);
	while((linelen<4)||strcmp("quit",line)!=0){
		bagelsturn(line);
		linelen=tgetline(line,32);
	}
	sendconst("I Say Byebye\r\n ");
	wizCmd(CR_DISCON);// Disconnect the connection- we're done here
}

void main(void){
	unsigned char socket0status,connectstatus=0,listening=0,prevstat=0xff;
    unsigned char ip_addr[] = {169,254,180,2};
	delay(500);
    wiz_Init(ip_addr); //initialize the wiznet chip
	while(1){  // Loop forever
		socket0status=wizGetCtl8(SnSR); //socket 0 status
		if (socket0status!=prevstat){
			printf("s0 stat change to %X\n\r",socket0status);
			prevstat=socket0status;
		}
		switch (socket0status){
			case SOCK_CLOSED: //initial condition
				socket0_init();	//initialize socket 0
				break;
			case SOCK_LISTEN:
				listening=1;
				break;
			case SOCK_ESTABLISHED: //someone wants to talk to the server
				if(connectstatus==0){
					connectstatus=1;
					thisip.l=getip();
					printf("Hello to: %d.%d.%d.%d\n",
							thisip.c[0],thisip.c[1],thisip.c[2],thisip.c[3]);
					sendconst("Oh Hello!!\n\r");
				}
				handlesession();
				break;
			//following are cases where we have to reset and reopen the socket
			case SOCK_FIN_WAIT: case SOCK_CLOSING: case SOCK_TIME_WAIT:
			case SOCK_CLOSE_WAIT: case SOCK_LAST_ACK:
				wizCmd(CR_CLOSE);
				break;
		}
		delay(100);
	}
}
#include <olduino.c>
#include <nstdlib.c>
#include <hspi2.c>
#include "w5500code.c"

 

 

Telnet/Putty Preamble

16:22:52.174> s0 stat change to 0017
16:22:52.174>
16:22:52.253> Hello to: 169.254.168.228
16:22:53.469> rsize=21: FFFB1F FFFB20 FFFB18 FFFB27 FFFD01 FFFB03 FFFD03
16:22:54.680> rsize=15: FFFE1F FFFE20 FFFE18 FFFE27 FFFC01
16:22:55.862> rsize=3: FFFB24
16:22:57.113> rsize=3: FFFE24

When a session is established putty sends a negotiation sequence consisting of sequences of 3 bytes like FFFB1F which means the client WILL tell you the window size if you ask.  If I don’t respond it sends a related sequence like FFFE1F which means(I think) DONT ask for window size.  I don’t care about any of this stuff but it takes bandwidth and i have to recognize it to ignore it.  I tried just waiting a second and flushing the receive buffer but somehow it knows when i’ve read the first transmission and waits before it sends the next.  I’m having visions of adopting this horrible pattern that i see in the arduino ethernet code.  It makes the ethernet socket look like a character input device and just reads one byte at a time.

I can see making something like a tgetchar() that would return a character or block if none were available and would just swallow the initialization sequences.  I could have a tgetavailable() to tell me if there were characters available MAYBE but that would be trickier.

Telnet?

 

One of the challenges of adapting the Bagels Game for the olduino is that it was meant to be played on a tty type terminal where you could see your earlier input and responses rather than a browser type session where each response overlays everything that preceded it.  I had thought about developing some JavaScript hoohah that would let me emulate a line mode terminal but then i started thinking about telnet.  Telnet is basically a line mode terminal running over TCP so there’s no reason the ethernet cards wouldn’t support it and, in fact, I’ve probably used telnet to debug the bagels server so i could see exactly what it sent without the browser interpreting the html.

I fired up the 1802 Olduino running the Bagels game to make sure it worked, then started a putty telnet session to the same ip address and port.  It worked like a champ except that the telnet session shut down right after the server sent a page.  I had foolishly assumed that telnet was transaction-oriented like the browser but it’s not – it expects the socket to stay connected for the duration of the session.  Still, the principle is sound and it’s just a small matter of programming now.

I would need a new framework for the game though.  The current game keeps a table of active IPs and when a request comes in it looks up the ip in the table to get the current state.  Using Telnet with a single socket I wouldn’t have to do that – there would only be one state to keep but I could only have one player at a time.  If I activate a bunch of sockets I’d have to scan them sequentially for activity and maintain the states in an indexed table.  Maybe I’ll start with one socket and a single user.

There are some simple examples for arduino – like this one.

UPDATE:
Continuing to play with telnet I’m still struggling to find a mental model that I can extend to multiple users without driving myself bonkers or making something that’s even MORE complicated than the web server.  I feel like it should be simple but it isn’t.  I could maybe come up with a blocking single user client – maybe that’s something to start with.  One immediate issue is spinning off the negotiation sequences that putty sends at the beginning of a session.  I think I can safely ignore them so I will:

void handlesession(){
	unsigned int rsize;
	static unsigned char Inst1[]=
		"I AM THINKING OF A 3 DIGIT NUMBER.\n\rTRY TO GUESS " //50
		"MY NUMBER AND I WILL GIVE YOU CLUES AS FOLLOWS:\n\r";
	static unsigned char Inst2[]=
		"...PICO - ONE DIGIT IS IN THE WRONG PLACE\n\r"
		"...FERMI - ONE DIGIT IS IN THE CORRECT PLACE\n\r"
		"...DOUGHNUTS - NO DIGIT IS CORRECT\n\r";
	sendconst(Inst1); sendconst(Inst2);
	while(wizGetCtl16(SnRX_RSR)==0)	printf(".");
	delay(5);
	rsize=wizGetCtl16(SnRX_RSR); //get the size of the received data
	printf("\nrsize=%d\n",rsize);
	if (recv0(buf,min(16,rsize))>0){
		send0(buf,min(16,rsize));
	}
	sendconst("OK!!\n\r>"); //send a response
	phexblk(buf,rsize); printf("\n\r");
	flush(rsize);	//get rid of the received data
}

void main(void){
	unsigned char socket0status,connectstatus=0,listening=0,prevstat=0xff;
    unsigned char ip_addr[] = {169,254,180,2};
	delay(500);
    wiz_Init(ip_addr); //initialize the wiznet chip
	while(1){  // Loop forever
		socket0status=wizGetCtl8(SnSR); //socket 0 status
		if (socket0status!=prevstat){
			printf("s0 stat change to %X\n\r",socket0status);
			prevstat=socket0status;
		}
		switch (socket0status){
			case SOCK_CLOSED: //initial condition
				socket0_init();	//initialize socket 0
				break;
			case SOCK_LISTEN:
				listening=1;
				break;
			case SOCK_ESTABLISHED: //someone wants to talk to the server
				if(connectstatus==0){
					connectstatus=1;
					thisip.l=getip();
					printf("Hello to: %d.%d.%d.%d\n",
							thisip.c[0],thisip.c[1],thisip.c[2],thisip.c[3]);
					sendconst("Oh Hello!\n\r");
				}
				handlesession();
				break;
			//following are cases where we have to reset and reopen the socket
			case SOCK_FIN_WAIT: case SOCK_CLOSING: case SOCK_TIME_WAIT:
			case SOCK_CLOSE_WAIT: case SOCK_LAST_ACK:
				wizCmd(CR_CLOSE);
				break;
		}
		delay(100);
	}
}

run1802.5F(2000)
Done Wiznet W5500 Initialization on IP address 169.254.180.2
Gateway Address 169.254.180.1
s0 stat change to 0000
s0 stat change to 0014
s0 stat change to 0017
Hello to: 169.254.168.228
rsize=21
FFFB1FFFFB20FFFB18FFFB27FFFD001FF000000000000000
rsize=15
FFFE1FFFFE20FFFE18FFFE27FFFC001
rsize=3
FFFB24
rsize=3
FFFE24

In-Circuit EEPROM Programming the AT28C64

The AT28C64 has a substantially similar pinout to the 27C256 I’ve replaced with it. To use it in the Z80 Membership Card I made a socket adapter that re-routes pin 27 to pin 1. Pin 27 is A14 for the 27C256 but it’s write-enable(/WE) for the 28C64. Pin 1 is tied to +V in Lee’s ZMC CPU board and it’s N/C for the 28C64 so that covered the ground perfectly. I can program the 28C64 in a separate circuit and put it into the ZMC CPU card knowing that /WE will be held high which is inactive.

I’ve had the idea that I could reprogram the 28C64 without taking it out of the circuit if I hooked up the /WE and wrote some careful code. Hooking up the /WE is easy enough: the A14 trace going to pin 27 is under the chip but easy to identify and it doesn’t go anywhere else.  /WE is available at pin 27 of the RAM chip right next to the eeprom.

The code part is tricky. You can write to the 28C64 like any memory chip BUT after each write, the chip goes into a write cycle and won’t respond to reads for about 10ms. My proof of concept write routine looks like the following:

volatile unsigned char __at(0x1F00) romloc;	//unused rom location

void main(){
	unsigned int i;
	unsigned char romsave=romloc;
	printf(&quot;timein %d, romloc contains %x\n&quot;,millis(),romsave);
	__asm di __endasm;
	romloc=0xAA;
	for (i=1;i&lt;10000;i++){
	}
	__asm ei __endasm;
	printf(&quot;timeout %d,romloc contains %x\n&quot;,millis(),romloc);
	while(1);

The routine disables interrupts, changes an unused rom location, and spins 10,000 times through a loop before reenabling interrupts and checking the location.

The first time i tried it i forgot the disable/enable part so the whole thing went byebye when the millisecond interrupt occurred and the Z80 tried to read from the ROM. That, in a nutshell, is the fly in the ointment: if some faulty code tries to write to an address in the ROM, it will not only succeed but it will take the eeprom offline for 10ms – no good will come of that! The eeprom has a software data protection feature where you write a specific sequence of data/addresses to lock it. That would prevent the overwrite but the ROM would still go offline for 10ms after a write attempt. I guess I could live with that but it would be better to have a jumper or something to disable /WE before the eeprom saw it.

I’ve modified the CPU board to connect /WE but I’ll probably leave the eeprom in its adapter socket so i don’t have to worry about overwriting. I had a quick shot at putting the software data protection into the ghetto programmer but you have to do the writes within 150us and my current code is too slow loading shift registers etc. I had it in the older sanguino-based version where I had more direct i/o pins.

I’ve learned a couple of things doing this: I could speed up programming substantially by using the AT28C64’s page mode – even my ghetto programmer could write 32 bytes at a time.  Also, it’s crazy to be rewriting the whole eeprom each time – i could work up some kind of patch control.

 

Just For The Record – Overrunning the TP3465V

16-01-24 too fast

I wanted to verify the timing with the Z80 and the SPI bus both clocked at 4mhz.  I set up a sequences of Z80 OUT instructions with and without intervening noops.  the pair on the left clearly interfered with extra clocks and garbled output.  The pair on the right, with one intervening no-op are clearly ok.  Mat Millman pointed out a trick for running the TP3465V in 16 bit mode to tighten up the timing but hopefully it won’t come to that.  The speed of the Z80 getting data ready is more likely to be an issue in a real program: the current spisendN() routine takes about 11 Z80 instructions or 13uS/byte=75K/second. I’ll see whether that seems like an issue with ethernet and  the 16 bit mode might help with that too.

16-01-24 not too fast

void spiSendN(unsigned char* loc, unsigned int N){ //this is for output only
//following code pretty much copied from compiler output for above with out(... replaced by inline assembly;C:\Users\Bill\Desktop\olduinoZ\hwspilcd\/hspi2.c:80: while(N!=0){
//i'm quite sure it could be done better
__asm
	ld	hl, #4
	add	hl, sp
	ld	e, (hl)
	inc	hl
	ld	d, (hl)
	ld	hl, #2
	add	hl, sp
	ld	c, (hl)
	inc	hl
	ld	b, (hl)
;here the count is in de and the sending location is in bc
1$:
	ld	a,d
	or	a,e
	ret	Z
;out(0x81,*loc);
	ld	a,(bc)
	out (#0x81),a
;N--;
	dec	de
;loc++;
	inc	bc
	jr	1$
__endasm;
	N;loc;	//these don't do anything but they keep the compiler happy

}
void spiReceiveN(unsigned char* loc, unsigned int N){ //this is for input only
//following code pretty much copied from compiler output for above with out(... replaced by inline assembly;C:\Users\Bill\Desktop\olduinoZ\hwspilcd\/hspi2.c:80: while(N!=0){
//i'm quite sure it could be done better
__asm
	ld	hl, #4
	add	hl, sp
	ld	e, (hl)
	inc	hl
	ld	d, (hl)
	ld	hl, #2
	add	hl, sp
	ld	c, (hl)
	inc	hl
	ld	b, (hl)
;here the count is in de and the receiving location is in bc
1$:
	ld	a,d
	or	a,e
	ret	Z
;*loc=in(0x81);
	out	(#0x81),a	;trigger a transfer
	nop				;added 16-01-23 to allow tp3465 to respond
	in	a,(#0x81)	;retrieve the result
	ld	(bc),a		;store it
;N--;
	dec	de
;loc++;
	inc	bc
	jr	1$
__endasm;
	N;loc;	//these don't do anything but they keep the compiler happy
}

 

Finally! Bidirectional SPI

I re-soldered a connection with no change.  I started to think about timing and the SPI protocol and could I be reading the data back before the TP3465V was ready.  Yes I could!  There’s a status register that goes from 00 to 0x80 when the chip is done the transmission.  I changed the receive code to return that instead of the data.  Sure enough, I was getting 0’s back. I put in a no-op to give the chip a microsecond and bingo – 80’s.  I put the code back to the original and i see my ip address come back in nice as pie. I’m a bit surprised i wasn’t overrunning it streaming data out to it but there you go. I’ll have to see if this turns into a speed problem.

16-01-23 puttygo

//hspi2.c - routines for hardware spi on olduino with shield adapter outboard clock
//spixfer sends and receives, spisend output only
//spisendN spireceiveN, multibyte send and receive
//dec 18 cleanup
//Feb 16 fix spelling of spiSend
//15-12-04 begin adapting for zmc
//16-01-23 putting some nop's in spirxvn
#ifdef __CPUTYPE1802
unsigned char spixfer(unsigned char value){
	//this code depends on the argument being in Reg 12 and returned value in reg 15
	asm("	glo 12\n" //get the char to be sent
		"	dec 2\n"  //make a work area
		"	str 2\n"  //save the character
		"	out 6\n"  //this loads the shift register and starts the outboard clock
		"	dec 2\n"
		"	sex 2\n"  //delay to allow outbound shift to complete
		"	inp 6\n"  //read the shift register
		"	plo 15\n" //leave it in 15
		"	inc 2\n"  //restore the stack
		"	cretn\n");
	//The return below is NOT executed. It just prevents a compiler warning
	//the cretn inside the asm block above returns the value from the spi transfer
	//sorry.
	return 0;
}
void spiSend(unsigned char value){ //this is for output only
	//this code depends on the argument being in Reg 12
	asm("	glo 12\n" //get the char to send
		"	dec 2\n"  //make a work area
		"	str 2\n"  //place the outbound char
		"	out 6\n" //this loads the MOSR and starts the outboard clock
		"	cretn\n"
	);
	//there needs to be a 1 instruction gap before the next spi accesee - return is 12 or so
}
void spiNroutines(){//wrapper for assembly routines
	//spiSendN(unsigned char *, unsigned int n){//send n bytes over spi
	//spiReceiveN(unsigned char *, unsigned int n){//Receive n bytes over spi
		//the assembler loops count on having the buffer address in R12 and a non-zero count in R13
		asm("	align 16\n" //make sure jumps stay on page
			"_spiSendN:\n"
			"	sex 12\n" //point X to the buffer
			"$$spiSendLoop:\n" //we will do this N times
			"	out 6\n"  //this sends out a byte
			"	dec 13\n" //decrement the byte count
			"	glo 13\n" //check bottom byte of counter
			"	bnz $$spiSendLoop\n" //back for more if needed - 4 inst. per byte
			"	ghi 13\n" //check high byte of counter if necessary
			"	bnz $$spiSendLoop\n"
			"	sex 2\n"  //reset X register
			"	cretn\n" //return to caller
		);
		asm("	align 16\n" //make sure jumps stay on page
			"_spiReceiveN:\n"
			"	sex 12\n" //point X to the buffer
			"	dec 12\n" //back off so the first OUT will leave us in the 1st position
			"$$spiRxvLoop:\n" //we will do this N times
			"	out 6\n"  //this sends out garbage and clocks in the 1st character
			"	dec 13\n" //decrement the byte count(and allow shift to complete)
			"	inp 6\n"  //this reads the nth byte into the nth buffer location
			"	glo 13\n" //check bottom byte of counter
			"	bnz $$spiRxvLoop\n" //back for more if needed - 6 inst. per byte
			"	ghi 13\n" //check high byte of counter if necessary
			"	bnz $$spiRxvLoop\n"
			"	sex 2\n"  //reset X register
			"	cretn\n"
		);
} //that's it
#endif
#ifdef __CPUTYPEZ80
void spiInit(){//spi hardware init - Z80 only
	out(0x8f,0xfe);	//port direction CS0 is output
	out(0x8A,0xFF);	//cs0 inactive
	out(0x8D,0x00);	//clockrate is 4mhz
}

void spiSend(unsigned char value){ //this is for output only
	out(0x81,value);
}
void spiSendNc(unsigned char* loc, unsigned int N){ //this is for output only
	while(N!=0){
		out(0x81,*loc);
		N--;
		loc++;
	}
}
void spiSendN(unsigned char* loc, unsigned int N){ //this is for output only
//following code pretty much copied from compiler output for above with out(... replaced by inline assembly;C:\Users\Bill\Desktop\olduinoZ\hwspilcd\/hspi2.c:80: while(N!=0){
//i'm quite sure it could be done better
__asm
	ld	hl, #4
	add	hl, sp
	ld	e, (hl)
	inc	hl
	ld	d, (hl)
	ld	hl, #2
	add	hl, sp
	ld	c, (hl)
	inc	hl
	ld	b, (hl)
;here the count is in de and the sending location is in bc
1$:
	ld	a,d
	or	a,e
	ret	Z
;out(0x81,*loc);
	ld	a,(bc)
	out (#0x81),a
;N--;
	dec	de
;loc++;
	inc	bc
	jr	1$
__endasm;
	N;loc;	//these don't do anything but they keep the compiler happy

}
void spiReceiveN(unsigned char* loc, unsigned int N){ //this is for input only
//following code pretty much copied from compiler output for above with out(... replaced by inline assembly;C:\Users\Bill\Desktop\olduinoZ\hwspilcd\/hspi2.c:80: while(N!=0){
//i'm quite sure it could be done better
__asm
	ld	hl, #4
	add	hl, sp
	ld	e, (hl)
	inc	hl
	ld	d, (hl)
	ld	hl, #2
	add	hl, sp
	ld	c, (hl)
	inc	hl
	ld	b, (hl)
;here the count is in de and the receiving location is in bc
1$:
	ld	a,d
	or	a,e
	ret	Z
;*loc=in(0x81);
	out	(#0x81),a	;trigger a transfer
	nop				;added 16-01-23 to allow tp3465 to respond
	in	a,(#0x81)	;retrieve the result
	ld	(bc),a		;store it
;N--;
	dec	de
;loc++;
	inc	bc
	jr	1$
__endasm;
	N;loc;	//these don't do anything but they keep the compiler happy
}
#endif
Follow

Get every new post delivered to your Inbox.