Skip to content

Hardware SPI VIA the TP3465V

December 4, 2015

15-12-04 LCD Santa
I spent a couple of hours today hooking up a little cellphone LCD to the olduino/z to prove that the hardware SPI is actually working.

15-12-04 LCD Adapter

I’m using a little adapter I made a long time ago for the N5110 LCD. It’s basically just a 5v to 3.3v converter. The 5V power is dropped to 3.3 by passing through a red LED. Each of the signals is dropped by a voltage divider. The pins from left to right are
5v, Ground, Select, Reset, Data/Command, Serial Input, and Serial Clock. Select and Reset are active low, Select is connected to CS0 on the TP3465 and Reset is connected to pin 0 of the parallel port. Data/Command tells the LCD whether an input is Data(if 1) or a Command(if 0) and is conected to parallel pin 1, Serial Input is connected to MOSI on the TP3465V, and Serial Clock is connected to its SCK output.

I had to adapt the software from the original 1802 olduino. I’m going to try to keep the changes to the lowest level possible and use conditional assembly to distinguish the processors rather than creating new versions of the headers – we’ll see.  The lowest level routines, the processor dependant ones, are in hspi2.c.  My first attempt is below. The new material is mostly at the bottom and bounded by “ifdef #CPUTYPEZ80”

//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
#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,0x02);	//clockrate is 1mhz
}

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
	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

The next level op routines are in hspi2lcd.c. Again, the changes are at the bottom bounded by ifdef’s. I didn’t bother rewriting the LcdClear routine in assembly. It takes .2 sec but it’s not annoying yet and i cordially detest Z80 assembler.

//Routines for access to the nokia 5110 LCD over hardware spi 2 - outboard clock

void LcdWrite(unsigned char dc, unsigned char payload) //flag for data vs command, byte to send out
{
//	register unsigned char dummy;
	digitalWrite(lcdcmd,dc); //set data or command
	spiSend(payload);
}

void LcdWriteN(unsigned char *ptr, unsigned int N)
{
	digitalWrite(lcdcmd,LCD_D); //LcdWriteN only sends out data
	spiSendN(ptr,N);
}

void gotoXY(unsigned char x, unsigned char y)
{
  LcdWrite( 0, 0x80 | x);  // Column.
  LcdWrite( 0, 0x40 | y);  // Row.
}


void LcdInitialise(void)
{
  	digitalWrite(lcdreset,LOW); //reset the LCD
  	digitalWrite(lcdreset,HIGH); //release the reset
  	LcdWrite(LCD_C, 0x20); //send initialization
  	LcdWrite(LCD_C, 0x0C); //					bytes

}

void LcdSet(unsigned char what) //set the whole LCD to a bit pattern
{
  unsigned int index;
  for (index = 0; index <504; index++)
  {
    LcdWrite(LCD_D, what);
  }
}
#ifdef __CPUTYPE1802
void LcdClear(){		//clear the LCD to 0's, uses assembly routine below
	lcdWrite(LCD_D,0);	//clear the 1st position and make sure MOSR is clear
	lcdclearer();	//set the rest of the lcd to 0's
}
void hwspilcdasm(){ //asm routines for hardware spi lcd
	asm("	align 16\n"		//make sure lcdclearer jumps will fit on page
		"_lcdclearer:\n" 	//clears last 503 bytes of lcd
		"	ldad R15,503\n"	//number of bytes to clear
		"	sex 3\n"		//allow inline output
		"$$clrloop:\n"		//come back here for more
			"	out 6\n	db 00\n"	//send a 0
			"	dec R15\n"	//decrease counter
			"	glo R15\n	bnz $$clrloop\n" //back for more
			"	ghi R15\n	bnz $$clrloop\n" //til done
		"	sex 2\n"	//reset the X register to normal
		"	cretn\n");	//and we're done
}
#endif
#ifdef __CPUTYPEZ80
void LcdSelect(){
	out(0x8A,0xFE);	//cs0 active
}
void LcdDeselect(){
	out(0x8A,0xFF);	//cs0 inactive
}
void LcdClear(){		//clear the LCD to 0's
	unsigned int i;
	for (i=504;i!=0;i--){
		spiSend(0);
	}
}
#endif

The mainline routine is a new source file because it’s different enough (actually that’s a bit odd isn’t it). Really, with a bit more thought, this would work with either processor. Getting some processor independance is critical because i don’t want to have to rewrite, for example, the ethernet code. There’s one new issue to deal with there tough. The ethernet chips mostly deal their 16 bit data elements as big endian and the z80 is little endian.

*
	Display the COSMAC starship on a nokia LCD using hardware SPI shift register
	15-12-03 adapting for olduino/z
*/
#include <cpuz80spd4port40.h>
#include <olduino.h>
#include <zPortIo.h>
#include "hspi2.h"
#include "hspi2Lcd.h"
#include "santaw2.h"
void main()
{
	spiInit();
  	LcdSelect();
	LcdInitialise();
	digitalWrite(6,HIGH);
	LcdClear();
	digitalWrite(6,LOW);
	digitalWrite(6,HIGH);
	LcdWriteN(santabw2,504);
	digitalWrite(6,LOW);
	while(1);
  	LcdDeselect();
}
#include "hspi2.c"
#include "hspi2Lcd.c"
#include <olduino.c>
#include <zPortIo.c>
Advertisements

From → Olduino/Z

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: