Getting a sprite up on a Spectrum Next

The other day i showed you how to get Cspect working from Visual Studio code and how run and debug your code.

If you missed that tutorial it can be found here.

Well during my step into exploring the Spectrum next I came across quite a few sources that had good code, but they were a little difficult to read and it took some time to strip out some of the basics to do what I needed.

And to be fair the book that comes with the Next is awesome at teaching basic programming but for me the print is too small for the IN-OUT and the next registers section, but hey I am now 60 years old so maybe it’s my eyes. 😊

Anyhow here is some more code for you to play with which has been added to the last load of code just because its easier.

OCD

Oh before we get to the code, I have an admission to make, I am a bit OCD as far as my code layout is concerned therefore I like everything lined up and I use Tabs rather than spaces,

Therefore if you are like me and want cleaner looking code then maybe you should add these settings to the bottom of your Visual Studio code workspace file.

"settings": {
                "C_Cpp.formatting": "vcFormat",
          "editor.tabSize": 10,
          "editor.insertSpaces": false
        }

My complete workspace file

{
	folders": [
		{
			"name": ".vscode",
			"path": "Source//.vscode"
		},
		{
			"name": "Source",
			"path": "Source"
		},
		{
			"name": "Assets",
			"path": "Assets"
		}
        ],
        "settings": {
                "C_Cpp.formatting": "vcFormat",
          "editor.tabSize": 10,
          "editor.insertSpaces": false
        }
}

So what’s the point of this code?

This code does several things, it puts a sprite on the screen, uploads the palette to the sprites bank and is heavily commented so you should be able to read it.  As its written in long hand, rather than using faster code and macros its been written to teach rather than impress.

This ports mentioned in the code come from the documentation, which can be found here https://www.specnext.com/tbblue-io-port-system/


Enjoy.

main.asm

// no copyright patricia dot curtis at luckyredfish.com

//-------------------------------------------------------------------------------------------------------
//
// compiler directives so when compiled it saves the executable in borders.nex and runs from StartAddress
//
//-------------------------------------------------------------------------------------------------------

		SAVENEX	OPEN "..\\Output\\mario.nex", StartAddress
		SAVENEX	CORE 3, 0, 0		// Next core 3.0.0 required as minimum
		SAVENEX	CFG  0

		OPT	--zxnext			// tell the assembler we want the Spectrum Next instructions
		DEVICE	ZXSPECTRUMNEXT		// Compile for the Spectrum next device
		ORG	0x8000			// start address for this block of code
		
//define the space for the stack
StackEnd:		ds	127 
StackStart:	db	0	  

StartAddress
		ld	a,0			// select sprite 0
		ld 	bc, $303b			// get the port in the b,c register
		out	(c),a			// send the zero to port 0x303b to select the sprite		
		ld	bc, $57			// sprite atribute port for the selected sprite
		// Auto incrementing pointer so we dont have to keep setting $303b
		// Sprite Attribute 0
		// bits 7-0 = LSB of X coordinate
		ld	a,50			// x position 
		out	(c),a			// send the x position to attribute 0 via port $57
		// Sprite Attribute 1
		// bits 7-0 = LSB of Y coordinate
		ld	a,100			// y position 
		out	(c),a			// send the y position to attribute 1 via port $57 
		// Sprite Attribute 2
		// bits 7-4 = Palette offset added to top 4 bits of sprite colour index
		// bit 3 = X mirror
		// bit 2 = Y mirror
		// bit 1 = Rotate
		// bit 0 = MSB of X coordinate		
		ld	a,0			// no rotation and mirroring , no palette offset
		out	(c),a			// send the y position to attribute 2 via port $57 
		// Sprite Attribute 3	
		// bit 7 = Visible flag (1 = displayed)
		// bit 6 = Extended attribute (1 = Sprite Attribute 4 is active)
		// bits 5-0 = Pattern used by sprite (0-63)	
		ld	a,192			// sprite pattern zero and visible (128) and activate Attribute 4 (64)
		out	(c),a			// attribute 3 via port $57 
		// Sprite Attribute 4		
		// bit 7 = H (1 = sprite uses 4-bit patterns)
		// bit 6 = N6 (0 = use the first 128 bytes of the pattern else use the last 128 bytes)
		// bit 5 = 1 if relative sprites are composite, 0 if relative sprites are unified
		// Scaling
		// bits 4-3 = X scaling (00 = 1x, 01 = 2x, 10 = 4x, 11 = 8x)
		// bits 2-1 = Y scaling (00 = 1x, 01 = 2x, 10 = 4x, 11 = 8x)
		// bit 0 = MSB of Y coordinate
		ld	a,0			// sprite pattern zero and visible (0)
		out	(c),a			// send the y position to attribute 3 via port $57

		// turn on all sprites 	
		ld	bc, $243b 		// select register on TBBlue features	
		ld	a, $15			// Enables/disables Sprites and low-res Layer, and chooses priority of sprites and Layer 2.
		out	(c), a
		// using access the port 
		ld	bc, $253b 		// now access the register we set $15
		ld	a, 1			// all sprites visible
		out	(c), a			// and send it

		//tell the machine we will be sending sprite data
		ld 	bc, $303b			// get the port in the b,c register
		ld	a,0			// select sprite zero we are writing too
		out	(c),a			// send the zero to port 0x303b to select the sprite		
	
		// copy the sprite data
 		ld	hl,mario0			// get the sprite data
		ld	c,$5b			// copy sprite data to through register $5B 
		ld	b,0			// do a 256 bytes which is 0 this and the prevous like could be bc,$005b	
		otir				// send that
	
		// tell the machine we will copy the palette one element at a time

		ld	bc,$243b 			// port to select zx next register
		ld	a,$43			// register number, palette control
		out	(c),a
		// bit 7 = ‘1’ to disable palette write auto-increment.
		// bits 6-4 = Select palette for reading or writing:
		// 000 = ULA first palette
		// 100 = ULA second palette
		// 001 = Layer 2 first palette
		// 101 = Layer 2 second palette
		// 010 = Sprites first palette
		// 110 = Sprites second palette
		// 011 = Tilemap first palette
		// 111 = Tilemap second palette
		// bit 3 = Select Sprites palette (0 = first palette, 1 = second palette)
		// bit 2 = Select Layer 2 palette (0 = first palette, 1 = second palette)
		// bit 1 = Select ULA palette (0 = first palette, 1 = second palette)
		// bit 0 = Enabe ULANext mode if 1. (0 after a reset)
		ld	bc,$253b			// port to access pallet selection register
		//	   76543210	
		ld	a,%00100000		// Select sprites first palette
		out	(c),a

		ld	bc, $243b 		// tbblue port again
		ld	a, $40			// Chooses an palette element (index) to manipulate with
		out	(c), a	

		ld	bc, $253b 		// tell it we are going to be starting with 
		ld	a, 0			// we are starting with palette element 0
		out	(c), a			// yup send it 

		ld	bc, $243b 		// tbblue port yet again
		ld	a, $41			// next we will be sending the value of the colour
		out	(c), a	
		
		ld	de,marioPalette		// get the a pointer to the palette data
		ld	h,marioColours		// do the number of colours times
Docolours:	ld	bc,$253b			// write to the port 
		ld	a,(de) 			// get the colour from the palette array
		out	(c),a			// send the colour to the register
		inc	de			// next element in the array
		dec	h			// decrement the amount of colours left to do
		ld	a,-1			// last colour check
		cp	h			// have we done all the colours?
		jp	nz,Docolours		// nope next colour
		
		// now set the Transparency index for sprites

		ld	bc, $243b 		// tbblue port again
		ld	a, $4b			// choose the transparency index register for sprites
		out	(c), a	

		ld	bc, $253b 		// tell it the index
		ld	a, 0			// setting the transparency index to palette element 0
		out	(c), a			// yup send it 

		//-------------------------------------
		// end of sprite and palette test code
		//-------------------------------------

MainLoop:		ld	a,4			// black border
		out	($fe),a			// set next border colour port $fe with the value of the accumulator which is 0 

// Here DE counts down and D and E are OR-ed to check if the loop has completed.

		ld	de,300			// loop for 1000 times just wasting cycles
Loop1:		dec	de			// take 1 off the 1000
		ld	a,d			// move it to a register we can or with
		or	e			// or with e to set the flags
		jp	nz,Loop1 

		ld	a,2			// change this for different colours
		out	($fe),a			// set next border colour with the value of the accumulator which is 1



		ld	de,100			// do another loop wasting more cycles , this time larger band
Loop2:	dec	de
		ld	a,d
		or	e
		jp	nz,Loop2
	    


		jp	MainLoop			// do the whole thing black and blue again and again
						//end start 
		ORG	0x4000			// change agdress to $4000

		include	"includes\mario.asm"		// add my sprites to the new bank


		SAVENEX	AUTO			// Save every 16k bank with data in it 
		SAVENEX	CLOSE   			// close the compiled file so we can either run it or debug it

		

mario.asm below

You will notice that the palette is laid out in binary with comments showing the 24 bit palette colours, I did this so you can maybe understand how it works.

// Created by NextGraphics.exe on Friday, 25 September 2020 @ 14:10:22
marioColours:	equ	149

//			 RRRGGGBB
marioPalette:	db	%11100011		// Colour 00 is $e3 = 255,0,255
		db	%10000001		// Colour 01 is $81 = 157,13,21
		db	%01100000		// Colour 02 is $60 = 140,0,0
		db	%01100000		// Colour 03 is $60 = 143,0,0
		db	%01100000		// Colour 04 is $60 = 135,0,0
		db	%10000001		// Colour 05 is $81 = 152,0,8
		db	%10000101		// Colour 06 is $85 = 150,37,36
		db	%10000000		// Colour 07 is $80 = 167,5,0
		db	%10000001		// Colour 08 is $81 = 177,7,6
		db	%10100001		// Colour 09 is $a1 = 182,7,4
		db	%10100001		// Colour 0a is $a1 = 182,8,4
		db	%10100001		// Colour 0b is $a1 = 186,10,3
		db	%10100001		// Colour 0c is $a1 = 187,21,17
		db	%10000001		// Colour 0d is $81 = 151,32,42
		db	%10000101		// Colour 0e is $85 = 152,39,50
		db	%01101001		// Colour 0f is $69 = 138,106,18
		db	%01101001		// Colour 10 is $69 = 125,103,3
		db	%10001101		// Colour 11 is $8d = 156,118,19
		db	%11001101		// Colour 12 is $cd = 218,123,4
		db	%11001101		// Colour 13 is $cd = 216,121,4
		db	%01101001		// Colour 14 is $69 = 136,101,4
		db	%10101000		// Colour 15 is $a8 = 203,107,0
		db	%10001101		// Colour 16 is $8d = 159,138,50
		db	%11010001		// Colour 17 is $d1 = 223,144,4
		db	%01101101		// Colour 18 is $6d = 138,128,19
		db	%10110001		// Colour 19 is $b1 = 209,149,8
		db	%11110000		// Colour 1a is $f0 = 254,166,0
		db	%11010001		// Colour 1b is $d1 = 242,155,2
		db	%01101101		// Colour 1c is $6d = 123,108,13
		db	%11010001		// Colour 1d is $d1 = 228,152,5
		db	%11110001		// Colour 1e is $f1 = 255,179,35
		db	%11110101		// Colour 1f is $f5 = 255,181,42
		db	%01001000		// Colour 20 is $48 = 102,83,0
		db	%10101000		// Colour 21 is $a8 = 183,98,0
		db	%01101001		// Colour 22 is $69 = 114,94,16
		db	%01101001		// Colour 23 is $69 = 116,96,15
		db	%11110000		// Colour 24 is $f0 = 253,154,0
		db	%11110000		// Colour 25 is $f0 = 255,159,0
		db	%10101101		// Colour 26 is $ad = 191,121,8
		db	%01101101		// Colour 27 is $6d = 133,116,20
		db	%10101100		// Colour 28 is $ac = 190,114,0
		db	%10101000		// Colour 29 is $a8 = 207,102,0
		db	%11001101		// Colour 2a is $cd = 217,139,31
		db	%01101101		// Colour 2b is $6d = 112,109,58
		db	%01001001		// Colour 2c is $49 = 107,90,25
		db	%10101101		// Colour 2d is $ad = 198,120,7
		db	%10101101		// Colour 2e is $ad = 204,129,5
		db	%11110000		// Colour 2f is $f0 = 255,154,0
		db	%11110000		// Colour 30 is $f0 = 255,153,0
		db	%01101001		// Colour 31 is $69 = 135,102,14
		db	%01001001		// Colour 32 is $49 = 88,90,18
		db	%01001000		// Colour 33 is $48 = 93,74,0
		db	%01101001		// Colour 34 is $69 = 110,94,25
		db	%11001101		// Colour 35 is $cd = 223,137,22
		db	%11001100		// Colour 36 is $cc = 224,119,0
		db	%10101101		// Colour 37 is $ad = 213,135,5
		db	%11001101		// Colour 38 is $cd = 216,133,5
		db	%10101101		// Colour 39 is $ad = 198,121,5
		db	%10101001		// Colour 3a is $a9 = 198,107,1
		db	%10101101		// Colour 3b is $ad = 205,139,33
		db	%01101101		// Colour 3c is $6d = 138,134,54
		db	%01101101		// Colour 3d is $6d = 132,122,32
		db	%10010001		// Colour 3e is $91 = 148,150,62
		db	%10001101		// Colour 3f is $8d = 149,115,25
		db	%10100001		// Colour 40 is $a1 = 205,23,3
		db	%10001001		// Colour 41 is $89 = 148,98,18
		db	%01101101		// Colour 42 is $6d = 120,137,25
		db	%01101101		// Colour 43 is $6d = 130,111,5
		db	%10000101		// Colour 44 is $85 = 151,39,9
		db	%11110001		// Colour 45 is $f1 = 255,167,23
		db	%11010001		// Colour 46 is $d1 = 246,155,20
		db	%01001000		// Colour 47 is $48 = 100,77,0
		db	%01001000		// Colour 48 is $48 = 76,72,0
		db	%01001001		// Colour 49 is $49 = 79,95,3
		db	%01101101		// Colour 4a is $6d = 116,115,21
		db	%10100001		// Colour 4b is $a1 = 191,1,5
		db	%10000001		// Colour 4c is $81 = 178,0,5
		db	%01001001		// Colour 4d is $49 = 100,84,18
		db	%01001001		// Colour 4e is $49 = 86,96,17
		db	%01000101		// Colour 4f is $45 = 102,63,16
		db	%01100001		// Colour 50 is $61 = 141,29,42
		db	%01101001		// Colour 51 is $69 = 134,101,23
		db	%10101101		// Colour 52 is $ad = 193,133,12
		db	%11001101		// Colour 53 is $cd = 228,137,21
		db	%11010001		// Colour 54 is $d1 = 250,150,8
		db	%11001100		// Colour 55 is $cc = 249,133,0
		db	%10101000		// Colour 56 is $a8 = 213,105,0
		db	%10101101		// Colour 57 is $ad = 213,139,38
		db	%01001101		// Colour 58 is $4d = 104,111,52
		db	%01001001		// Colour 59 is $49 = 91,74,2
		db	%10100001		// Colour 5a is $a1 = 180,12,5
		db	%10100001		// Colour 5b is $a1 = 200,2,2
		db	%10100001		// Colour 5c is $a1 = 180,8,4
		db	%10100001		// Colour 5d is $a1 = 201,9,3
		db	%10100001		// Colour 5e is $a1 = 202,12,1
		db	%01001001		// Colour 5f is $49 = 92,88,18
		db	%01101001		// Colour 60 is $69 = 112,104,18
		db	%10101100		// Colour 61 is $ac = 201,113,0
		db	%11001100		// Colour 62 is $cc = 226,125,0
		db	%10101101		// Colour 63 is $ad = 215,123,7
		db	%10101101		// Colour 64 is $ad = 214,132,22
		db	%10100001		// Colour 65 is $a1 = 185,33,29
		db	%10000000		// Colour 66 is $80 = 169,2,0
		db	%10100001		// Colour 67 is $a1 = 190,17,4
		db	%10101001		// Colour 68 is $a9 = 208,104,4
		db	%10100001		// Colour 69 is $a1 = 200,17,3
		db	%10100001		// Colour 6a is $a1 = 200,0,2
		db	%10100000		// Colour 6b is $a0 = 200,0,0
		db	%10101001		// Colour 6c is $a9 = 207,107,10
		db	%10001101		// Colour 6d is $8d = 147,133,45
		db	%10100101		// Colour 6e is $a5 = 195,44,45
		db	%10100000		// Colour 6f is $a0 = 192,0,0
		db	%10100001		// Colour 70 is $a1 = 195,0,3
		db	%10000000		// Colour 71 is $80 = 175,0,0
		db	%10000000		// Colour 72 is $80 = 146,4,0
		db	%10000000		// Colour 73 is $80 = 172,0,0
		db	%10100000		// Colour 74 is $a0 = 180,0,0
		db	%10000001		// Colour 75 is $81 = 178,2,2
		db	%10000001		// Colour 76 is $81 = 164,21,2
		db	%10100101		// Colour 77 is $a5 = 202,46,48
		db	%10001101		// Colour 78 is $8d = 159,131,52
		db	%01101101		// Colour 79 is $6d = 124,134,33
		db	%10000101		// Colour 7a is $85 = 158,42,38
		db	%01100000		// Colour 7b is $60 = 138,0,0
		db	%10000000		// Colour 7c is $80 = 148,0,0
		db	%01100001		// Colour 7d is $61 = 143,4,5
		db	%01100001		// Colour 7e is $61 = 139,0,6
		db	%10000001		// Colour 7f is $81 = 147,4,6
		db	%10000001		// Colour 80 is $81 = 150,6,6
		db	%01100000		// Colour 81 is $60 = 137,0,0
		db	%10000000		// Colour 82 is $80 = 165,0,0
		db	%01101000		// Colour 83 is $68 = 110,74,0
		db	%01101101		// Colour 84 is $6d = 114,120,10
		db	%01101101		// Colour 85 is $6d = 137,133,41
		db	%01101000		// Colour 86 is $68 = 111,72,0
		db	%01100001		// Colour 87 is $61 = 141,11,2
		db	%10000001		// Colour 88 is $81 = 151,14,18
		db	%10000001		// Colour 89 is $81 = 146,14,15
		db	%10000001		// Colour 8a is $81 = 149,26,26
		db	%01100001		// Colour 8b is $61 = 138,4,4
		db	%10000001		// Colour 8c is $81 = 148,7,6
		db	%01001001		// Colour 8d is $49 = 98,76,6
		db	%01001001		// Colour 8e is $49 = 100,103,25
		db	%01001001		// Colour 8f is $49 = 97,90,13
		db	%01001000		// Colour 90 is $48 = 79,78,0
		db	%01101101		// Colour 91 is $6d = 133,111,29
		db	%01001001		// Colour 92 is $49 = 99,91,28
		db	%01001001		// Colour 93 is $49 = 78,80,6
		db	%01101101		// Colour 94 is $6d = 127,132,70
		
//mario0 is from frame 0 at position x=0  y=0
mario0:		.db	$00,$00,$00,$00,$00,$01,$02,$03,$04,$05,$00,$00,$00,$00,$00,$00
		.db	$00,$00,$00,$00,$06,$07,$08,$09,$0a,$0b,$0c,$0d,$0e,$00,$00,$00
		.db	$00,$00,$00,$00,$0f,$10,$11,$12,$13,$14,$15,$00,$00,$00,$00,$00
		.db	$00,$00,$00,$16,$17,$18,$19,$1a,$1b,$1c,$1d,$1e,$1f,$00,$00,$00
		.db	$00,$00,$00,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$00,$00
		.db	$00,$00,$00,$2b,$2c,$2d,$2e,$2f,$30,$31,$32,$33,$34,$00,$00,$00
		.db	$00,$00,$00,$00,$00,$35,$36,$37,$38,$39,$3a,$3b,$00,$00,$00,$00
		.db	$00,$00,$3c,$3d,$3e,$3f,$40,$41,$42,$43,$44,$00,$00,$00,$00,$00
		.db	$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f,$50,$51,$52,$53,$54
		.db	$55,$56,$57,$00,$58,$59,$5a,$5b,$5c,$5d,$5e,$03,$5f,$60,$61,$62
		.db	$63,$64,$00,$00,$65,$66,$67,$68,$69,$6a,$6b,$6c,$00,$00,$6d,$00
		.db	$00,$00,$00,$6e,$6f,$70,$71,$72,$73,$74,$75,$76,$77,$78,$79,$00
		.db	$00,$00,$7a,$7b,$73,$7c,$7d,$7e,$7f,$80,$81,$04,$82,$83,$84,$00
		.db	$00,$85,$86,$87,$88,$89,$00,$00,$00,$00,$8a,$8b,$8c,$8d,$8e,$00
		.db	$00,$8f,$90,$91,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
		.db	$00,$00,$92,$93,$94,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00

8 thoughts on “Getting a sprite up on a Spectrum Next”

  1. Hello, great content. I have kickstarted Next Issue 2 and I am learning to code for the next with emulator. Now I have a question, you are using some ports in your code, like sprite port $303B, is there any refeence manual out there that lists all the existing ports and their addresses and what they do? Or how do you know that sprite 0 uses port address $303B?

    BR
    Jörgen Jönsson

  2. Hi Patricia. Thanks for putting this together this bare-bones sprite sample – it is just what I was looking for. Great code, but I have spotted a few ways that might make it even simpler:

    1. In quite a few places, the NEXTREG opcode coudl be used to set the Next Feature Control Registers without going through ports TBBlue Register Select ($243B / 9275) and then the TBBlue Register Access ($253B / 9531). See https://wiki.specnext.dev/Extended_Z80_instruction_set

    2. The second write to Sprite Select port $303b is redundant. The first call sets both the attribute and pattern upload pointers, and they increment independently, so after uploading the attribute data the pattern data pointer is untouched and ready to use. https://wiki.specnext.dev/Sprite_Status/Slot_Select

    3. The palette colour loop can be simplified with the djnz instruction using the B register as the implicit loop index.

    1. Thanks for the feedback, Yes I know NEXTREG could be used and the code is not optimal but as I stated in the tutorial “Its written in long hand, rather than using faster code and macros its been written to teach rather than impress.”

Leave a Reply

Your email address will not be published. Required fields are marked *