Back to firmware
Find three helpful tools in this report:
  1. Assembler program for modifying the character matrix.
  2. RSX for the above and the next program
  3. BASIC program as a help tool for the first program.

Hello Kiddies, Cliff Lawson presents part 13 in bis awe inspiring series of articles on the socio-economic effect of pink jelly on a settlement of lesser spotted wood voles. In fact it's really something to do with redefining the Joyce character set (on screen, not printer, sorry !), but that doesn't sound half as interesting.

Before we go any further, I think I had better make it clear that the rest of this article is unlikely to take any prisoners. For this reason I have devised an entry exam which must be passed before you are qualified to read any further.

Look very closely at the following .... RRRRRRR .... OK, now quickly hold one arm out in front of you and look closely at the joint between your wrist and your shoulder. Did you spot the difference ? Yup, thats right, I think its fair to assume that you do know your R's from your elbow and have thus passed the article entry test. Read on at your lesiure.

Much to the protestations of the editor, I think it has to be said that any normal thinking human being will prefer Sans-serif text to that used for setting this magazine. (Sans-serif means that the ends of the lines of letters don't have ostentatious twiddly bits). The eagle eyed amongst you may have noticed that your Joyce also has serifs on some of it characters and, as I don't like them, I figured that something needed to be done. The fruit of my labours is presented here, a simple (???) way to redefine the entire Joyce character set (or just selected characters).

This isn't going to be too easy, so you'll have to bare with me, if you do encounter problems, we would prefer it if you didn't phone up, anyone found doing so will probably be ex-communicated for life (and we all know how painful that can be don't we kids).

When we seil you a Joyce, there probably aren't many people who realise that we also throw in several hundred pounds worth of Software for absolutely bugger (not a lot at) all. Locoscript itself must be worth several hundred and in addition to this you get BASIC (£100), GSX (~£200), Dr Logo (£3.37), and Digital Research Programming tools (SID, ASM, MAC, RMAC, LINK) (~£200). So it seems like a good idea to make the most of this. The one drawback of the multiplicity of assemblers that we supply is that they are all Intel 8080, rather than Zilog Z80 assemblers, but this isn't really a major problem if your head is screwed on the right way (your nose should point in the same direction as you walk).

Your task, should you choose to accept it is to type in the two assembler listings shown here. They must be entered into a couple of files on disc and by far the easiest way of doing this is to use RPED. If you have set your CP/M disc so that it has a PROFILE.SUB as described on page 15 of the CP/M section in Book 1, then just type the word RPED. Once the editor has loaded, hit f3 to create a new file. When asked for a name, type in either REDIFINE.ASM or DOIT.ASM. depending on whether you are typing in listing 1 or 2. The spacing in assembler programs is not exactly critical, but it does ease readability if there is a good sized space between each field of an instruction.

When you have typed in the file name, before hitting [RETURN] it would be a good idea to remove your system disc and insert a formatted blank that has a bit more space on it.

Having entered these two files, the fun really starts. They both have to be assembled then linked and finally combined. To do this, you will need to get your side 3/4 disc out of his box. Remove the disc onto which you have just saved DOIT and REDEFINE and insert side 3 to the left. Then type :

RMAC B:DOIT

After a short pause, you will be prompted to insert the disc for drive B:. Remove side 3 and insert the disc with the two files on it. Once this operation has been successfully completed, you should be met by the following :

CP/M RMAC ASSEM 1.1
0260
000H USE FACTOR
END OF ASSEMBLY

If anything else appears, then this almost certainly means that you have made a typing error, so go back to RPED, to correct the file (DOIT.ASM). Actually this is a lie - I have a feeling the 0260 might be a different value.

Once the file has been successfully assembled, remove the disc and insert side 3 again. This time, type :

LINK B:DOIT

You will be asked to insert the disc for drive A:, just hit a key. You will then be asked to insert the disc for drive B:. Insert the disc with the assembler files and then hit a key. This should produce the following :

ABSOLUTE        0000
CODE SIZE       0260 (0100-035F)
DATA SIZE       0000
COMHON SIZE     0000
USE FACTOR      00

So far, so good, now you'll have to repeat the whole process for REDEFINE.ASM. Insert side 3 and type :

RMAC B:REDEFINE

(helpful hint: RMAC is on the disc for drive A: (side 3) and REDEFINE is on the disc for drive B: (disc with assembler files)). Then type :

LINK B:REDEFINE [OP]

This will produce a file called REDEFINE.PRL, which is a page relocatable file containing a CP/M RSX. This must be renamed :

REN REDEFINE.RSX=REDEFINE.PRL

This must be added on to DOIT.COM using :

GENCOM B:DOIT B:REDEFINE

(GENCOM is on side 3).

If you understood all of that, you should now have a disc containing a file called DOIT.COM. With this disc in the drive, type :

DOIT

All being well, the character set will now have been changed (for the better ?)

If you cast your eye over the file called DOIT.ASM, you will see that the majority of lines in it take the form :

db	'?',nn,nn,nn,nn,nn,nn,nn,nn

Where the '?' is the character to be defined (this can also be given as a numeric character number 0..255). This is then followed by 8 bytes of bit significant data that define the 8 successive lines of dots that make up a character. Anyone who has ever met user defined symbols of any sort (such as on other Amstrads, Sinclairs and Acorns) will no doubt see the exact method of use for this. Basically, if you draw the character to be defined on an 8 by 8 grid, then convert each line in turn into a binary number, these can then be used in such a definition. To help you along your way, Listing 3 is a Mallard BASIC program to allow you to work out these values easily. The Cursor keys will move the blob about and the '+' and '-' keys will set or reset a point. [EXIT] finishes the definition and prints out a line that can be added to the DOIT.ASM file using RPED.

If DOIT.ASM is changed, then it will have to be assembled and linked again (you don't need to do REDEFINE.ASM again). Then the RSX has to be added using GENCOM just as before. Once this is done, the command DOIT will once again redefine the selected characters.

The more enterprising amongst you may be wondering just how this rabbit was pulled out of the hat. (If your not one of these then retire satisfied at this point because the going is about to get really tough).

These programs demonstrate several useful lessons for the would be assembler programmer. A lot of people raust be wondering how to poke the screen memory on the Joyce and, while this program doesn't exactly do that, it gives you all the clues that are needed. It also makes use of a CP/M RSX and therefore demonstrates how they are used.

In CP/M+, any modifications to the operation of the system can very easily be made using RSXs. I'll assume you already know what a BDOS call is. An RSX allows one to patch location 5 (the BDOS entry point) so that your external code can have a first look at the call being made before control is passed onto the original BDOS entry point. The beauty of this system is that these operating system modifications are contained within a relocatable module that can easily be attached to a .COM file so that it is able to make use of new BDOS calls or modified versions of the existing ones that can be used within a number of different driving programs. RSXs attached to a .COM file are loaded on page boundaries from the top of the TPA downwards. This gives a secondary benefit if a piece of code must reside in the common memory of PLUS (from #C000 to #FFFF), it need not be moved from an address at the lower end of the TPA to a fixed address at tbe top. Instead, CP/M + will move it for you to an address that best suits it and will da any relocating needed into the bargain.

You may wonder why a piece of code would want to be in the top of memory in preference to being lower down. Well, the essence of operation of CP/M + is that one bank (64K chunk) of memory contains the TPA while the rest of the CP/M and screen memory etc. are kept hidden within some other 64K chunk. To communicate between TPA and screen memory (for instance when a character is to be printed) there must be a smallish piece of the memory shared between all banks so that values can be passed back and forth. The 64K banks are really made up of 4 16K blocks one of which is common to all banks. There are various possible combinations used for the operation of CP/M. The BDOS bank is made up of blocks 7,3,1,0, the TPA is 7,6,5,4, there is an extra combination of 7,?,8,? and the screen environment is blocks 7,2,1,0. Block 7 (the top 16K of TPA) is common to all these environments. The blocks contain the following :

0BIOS extended jumpblock
1screen memory
2matrix RAM, roller RAM and some screen memory
3BIOS and BDOS
4bottom 16K of TPA
5next 16K chunk of TPA
6third 16K chunk of TPA
7common - top of TPA plus switching code
8CCP.hash tables and data buffers

From within the TPA, it is possible to call one of the extended BIOS routines in block 0 by calling the USERF entry in the BIOS. This is achieved by getting the address at location 1 (WBOOT) and adding an offset of 87. This gives the address of the USERF routine. This value can then be stored following a 0C3h opcode (JMP instruction) to produce a simple routine that can be called (just like ENTERFIRMWARE in Amstrad CP/M 2.2). The call is followed by an inline address that is the address of the routine to call. One particularly interesting routine is at address 000E9h, this is known as SCRRUNROUTINE and will allow the screen environment bank to be switched in, thus allowing access to the screen memory, the roller RAM and the character matrices. The matrices are held at 0B800h..0BFFFh and the effect of the program is to update a particular group of 8 bytes that constitute one particular character. The actual position in this area is given by multiplying the characters value by 8 and adding this to the base address (0B800h). The area of memory from 0B600h to 0B7FFh holds what is known as the 'roller RAM', this contains 256 words each of which is the address of a pixel line on the screen. This can be used for rolling the screen etc.

If you didn't understand all that, then you'll be pleased to know that neither did I. A good look at the example program shown here will probably teil you a whole heap more than a multitude of fatuous waffle.

REDEFINE.ASM contains the code to implement my RSX, this adds a new BDOS call (73, well why not ?). The header Information contained within the first 27 bytes at the top of this file will always be pretty similar whenever you want to implement a new RSX. The first 6 bytes are always 0 and are filled when the RSX is loaded. The next 3 contain a jump instruction to the start of your interception routine. The following couple will be followed by the address of the previous module. This is the address to call if a BDOS function is required within the RSX. This is followed by a single byte that should be 0FFh if the RSX should be removed from memory next time the RSX is loaded and 000h if it should remain. An 8 byte name will follow this. The loader flag indicates whether or not this is the last RSX in the chain. Finally there are a couple of bytes that are reserved.

The first thing the RSX code should do is check the value in register C, if this contains the value of the BDOS call to be intercepted then the routine is entered, otherwise, control is passed to the 'next:' routine. In this example, the RSX intercepts BDOS call 73. If this is called, then the 9 bytes pointed to by HL are moved into a buffer in common memory. The address of USERF is calculated, stored then used to access the SCRRUNROUTINE. This calls a small section of program entitled 'code:'. This picks up the character number which is multiplied by 8 and added to the base address of the matrices. Finally, the new definition is moved into place with an 'LDIR' Operation (accessed by a db in 8080 code).

Weil, my good God, that was interesting wasn't it. I am sure we can all sleep a little sounder in our beds tonight happy in this new found knowledge. Seriously though (if thats possible), the above has shown how values can be raoved from CP/Ms TPA into the bank containing the screen memory etc. so there is no longer any excuse for people not to produce some amazing graphic effects an the machine (Yeah, I know you were all just about to do it in GSX anyway).

The following tool defines a new matrix for some characters. It requires the RSX REDEFINE.ASM, which is performing the real job. To generate the definition lines, use the BASIC program DESIGN.BAS before.
These programs are working as follows: The BASIC program designs a new matrix, which will activated if the design is finished. At the same time the BASIC program generates definition lines such as:

        db      char, byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8

Based upon the ideas in the tools above I implemented another solution:
  1. MATEDIT.BAS: A BASIC program for designing character sets. This tool allows the individual creation of appropriate character sets.
  2. SETMAT.ASM: Assembler program for reading and writing new character sets
  3. CHARSET.ASM: Assembler program in common memory (RSX) to modify a character set

As text file - 8080 or Z80
[DOIT.ASM]

	cseg

	lxi	h,table
	mvi	b,94
fred:
	mvi	c,73
	push	h
	push	b
	call	5
	pop	b
	pop	h
	lxi	d,9
	dad	d
	dcr	b
	jnz	fred
	rst	0

table:
	db	'!',48,48,48,48,48,0,48,0
	db	'"',108,108,0,0,0,0,0,0
	db	'£',108,108,254,108,254,108,108,0
	db	'$',24,62,88,60,26,124,24,0
	db	'%',0,198,204,24,48,102,198,0
	db	'&',56,108,56,118,220,204,118,0
	db	39,24,24,48,0,0,0,0,0
	db	'(',12,24,48,48,48,24,12,0
	db	')',48,24,12,12,12,24,48,0
	db	'?',60,102,6,12,24,0,24,0
	db	':',0,0,24,24,0,24,24,0
	db	';',0,0,24,24,0,24,24,48
	db	'<',12,24,48,96,48,24,12,0
	db	'>',96,48,24,12,24,48,96,0
	db	'=',0,0,126,0,0,126,0,0
	db	'@',124,198,222,222,222,192,124,0
	db	'*',0,102,03ch,0ffh,03ch,102,0,0
	db	'+',0,24,24,126,24,24,0,0
	db	',',0,0,0,0,0,24,24,48
	db	'-',0,0,0,126,0,0,0,0
	db	'.',0,0,0,0,0,24,24,0
	db	'/',6,12,24,48,96,192,128,0
	db	'[',60,48,48,48,48,48,60,0
	db	'\',192,96,48,24,12,6,2,0
	db	']',60,12,12,12,12,12,60,0
	db	'^',24,03ch,07eh,24,24,24,24,0
	db	'`',0,48,24,12,0,0,0,0
	db	'{',14,24,24,112,21,24,14,0
	db	'}',112,24,24,14,24,24,112,0
	db	'|',24,24,24,24,24,24,24,0
	db	'~',118,216,0,0,0,0,0,0
	db	'0',038h,06Ch,0C6h,0C6h,0C6h,06Ch,038h,00
	db	'1',018h,038h,018h,018h,018h,018h,018h,00
	db	'2',03Ch,066h,06h,03Ch,060h,060h,07Eh,00
	db	'3',03Ch,066h,06h,01Ch,06h,066h,03Ch,00
	db	'4',01Ch,03Ch,06Ch,0CCh,0FEh,0Ch,0Ch,00
	db	'5',07Eh,060h,060h,07Ch,06h,066h,03Ch,00
	db	'6',03Ch,066h,060h,07Ch,066h,066h,03Ch,00
	db	'7',07Eh,06h,06h,0Ch,018h,018h,018h,00
	db	'8',03Ch,066h,066h,03Ch,066h,066h,03Ch,00
	db	'9',03Ch,066h,066h,03Eh,06h,066h,03Ch,00
	db	'A',03Ch,066h,066h,07Eh,066h,066h,066h,00
	db	'B',07Ch,066h,066h,07Ch,066h,066h,07Ch,00
	db	'C',03Ch,066h,0C0h,0C0h,0C0h,066h,03Ch,00
	db	'D',078h,06Ch,066h,066h,066h,06Ch,078h,00
	db	'E',07Eh,060h,060h,078h,060h,060h,07Eh,00
	db	'F',07Eh,060h,060h,078h,060h,060h,060h,00
	db	'G',03Ch,066h,0C0h,0C0h,0CEh,066h,03Ch,00
	db	'H',066h,066h,066h,07Eh,066h,066h,066h,00
	db	'I',07Eh,018h,018h,018h,018h,018h,07Eh,00
	db	'J',0Ch,0Ch,0Ch,0Ch,0CCh,0CCh,078h,00
	db	'K',066h,066h,06Ch,078h,06Ch,066h,066h,00
	db	'L',060h,060h,060h,060h,060h,060h,07Eh,00
	db	'M',06Ch,0FEh,0FEh,0D6h,0D6h,0C6h,0C6h,00
	db	'N',0C6h,0E6h,0F6h,0DEh,0CEh,0C6h,0C6h,00
	db	'O',07Ch,0C6h,0C6h,0C6h,0C6h,0C6h,07Ch,00
	db	'P',07Ch,066h,066h,07Ch,060h,060h,060h,00
	db	'Q',07Ch,0C6h,0C6h,0C6h,0DAh,0CCh,076h,00
	db	'R',07Ch,066h,066h,07Ch,06Ch,066h,066h,00
	db	'S',03Ch,066h,060h,03Ch,06h,066h,03Ch,00
	db	'T',07Eh,018h,018h,018h,018h,018h,018h,00
	db	'U',066h,066h,066h,066h,066h,066h,03Ch,00
	db	'V',066h,066h,066h,066h,066h,03Ch,018h,00
	db	'W',0C6h,0C6h,0C6h,0D6h,0FEh,0FEh,06Ch,00
	db	'X',0C6h,06Ch,038h,038h,06Ch,0C6h,0C6h,00
	db	'Y',066h,066h,066h,03Ch,018h,018h,018h,00
	db	'Z',0FEh,06h,0Ch,018h,030h,060h,0FEh,00
	db	'a',00h,00h,078h,0CCh,0CCh,0CCh,076h,00
	db	'b',060h,060h,07Ch,066h,066h,066h,07Ch,00
	db	'c',00h,00h,03Ch,066h,060h,066h,03Ch,00
	db	'd',0Ch,0Ch,07Ch,0CCh,0CCh,0CCh,074h,00
	db	'e',00h,00h,03Ch,066h,07Eh,060h,03Ch,00
	db	'f',03Ch,066h,060h,078h,060h,060h,060h,00
	db	'g',00h,00h,03Ch,066h,066h,03Eh,06h,03Ch
	db	'h',060h,060h,07ch,066h,066h,066h,066h,00
	db	'i',018h,00h,018h,018h,018h,018h,018h,00
	db	'j',06h,00h,06h,06h,06h,066h,066h,03Ch
	db	'k',060h,060h,066h,06Ch,078h,06Ch,066h,00
	db	'l',030h,030h,030h,030h,030h,036h,01Ch,00
	db	'm',00h,00h,06Ch,0FEh,0D6h,0D6h,0C6h,00
	db	'n',00h,00h,05Ch,066h,066h,066h,066h,00
	db	'o',00h,00h,03Ch,066h,066h,066h,03Ch,00
	db	'p',00h,00h,07Ch,066h,066h,07Ch,060h,060h
	db	'q',00h,00h,07Ch,0CCh,0CCh,07Ch,0Ch,0Eh
	db	'r',00h,00h,07Ch,066h,060h,060h,060h,00
	db	's',00h,00h,03Ch,060h,03Ch,06h,07Ch,00
	db	't',030h,030h,03Ch,030h,030h,036h,01Ch,00
	db	'u',00h,00h,066h,066h,066h,066h,03Ch,00
	db	'v',00h,00h,066h,066h,066h,03Ch,018h,00
	db	'w',00h,00h,0C6h,0D6h,0D6h,0FEh,06Ch,00
	db	'x',00h,00h,0C6h,06Ch,038h,06Ch,0C6h,00
	db	'y',00h,00h,066h,066h,066h,03Eh,06h,03Ch
	db	'z',00h,00h,07Eh,0Ch,018h,030h,07Eh,00

efw:	db	0c3h
hidihi:	dw	0

This RSX is required for the assembler program DOIT.ASM as well as for the BASIC programm DESIGN.BAS.

As text file - 8080 or Z80
[REDEFINE.ASM]

wboot		equ	1
charmat		equ	0b800h
scrrunroutine	equ	000e9h

	cseg

	db	0,0,0,0,0,0
	jmp	start
next:	db	0c3h
	dw	0
prev:	dw	0
remov:	db	0ffh
nbank:	db	0
	db	'NCHARSET'
loader:	db	0
	db	0,0

start:		
	mov	a,c
	cpi	73
	jz	begin
	jmp	next

begin:		
	lxi	d,buffer
	lxi	b,9
	db	0edh,0b0h	;move parms to high mem before switch

	lhld	wboot
	lxi	d,87
	dad	d
	shld	cjfirm

	lxi	h,buffer
	lxi	b,code
	call	entfw
	dw	scrrunroutine

	ret

entfw:	db	0c3h
cjfirm:	dw	0

code:
	mov	a,m		;get char nunber
	inx	h
	push	h
	mov	l,a
	mvi	h,0
	dad	h
	dad	h
	dad	h
	lxi	d,charmat
	dad	d
	push	h
	pop	d
	pop	h
	lxi	b,8
	db	0edh,0b0h	;ldir
	ret

buffer:
	ds	10
	end

This BASIC program requires the RSX REDEFINE.ASM. It asks for the decimal value of the character to be changed. (Example: Character A, hex value 41, decimal value 65.) Thereafter an empty matrix will be displayed. Moving through the matrix will be performed by pressing any of the four arrow keys. A bit will be set with the key [+] and reset with the key [-]. The input sequence will be finished by pressing the ESC the key. Then the program displays the definition such as:

        db      char, byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8

Subsequently this definition will be activated (calling line 490).

As BASIC text file
[DESIGN.BAS]

10 esc$=CHR$(27)
20 loc$=esc$+"Y"
30 cls$=esc$+"H"+esc$+"E"
40 hole$=CHR$(187)
50 blob$=CHR$(188)
60 spc$=CHR$(32)
70 DIM c(8,8)
80 x=1:y=1
90 PRINT cls$
100 PRINT loc$;CHR$(41);CHR$(32);"12345678"
110 FOR i=1 TO 8
120 PRINT loc$;CHR$(i+32);CHR$(39);i
130 NEXT
140 PRINT loc$;CHR$(42);CHR$(32);
150 INPUT"char : ",c
160 WHILE a$<>CHR$(27)
170 a$=UPPER$(INKEY$)
180 ox=x:oy=y
190 PRINT loc$;CHR$(y+32);CHR$(x+31);hole$;
200 IF a$=CHR$(31) THEN y=y-1:y=MAX(y,1)
210 IF a$=CHR$(30) THEN y=y+1:y=MIN(y,8)
220 IF a$=CHR$(6) THEN x=x+1:x=MIN(x,8)
230 IF a$=CHR$(1) THEN x=x-1:x=MAX(x,1)
240 PRINT loc$;CHR$(oy+32);CHR$(ox+31);
245 IF c(ox,oy)=1 THEN PRINT blob$;ELSE PRINT spc$
250 PRINT loc$;CHR$(y+32);CHR$(x+31);
260 IF a$=CHR$(22) THEN PRINT blob$;:c(x,y)=1
270 IF a$=CHR$(28) THEN PRINT CHR$(32);:c(x,y)=0
280 WEND
290 PRINT loc$;CHR$(44);CHR$(32);
300 PRINT"The line for this is :"
310 PRINT
320 PRINT"      db    ";
330 PRINT c;
340 FOR j=1 TO 8
350 byte(j)=0
360 FOR i=1 TO 8
370 byte(j)=byte(j)+c(i,j)*2^(8-i)
380 NEXT i
390 PRINT",";byte(j);
400 NEXT j
410 GOSUB 490
420 PRINT:PRINT
430 FOR i=1 TO 30
440 PRINT CHR$(c);
442 NEXT i
443 PRINT:PRINT
450 PRINT:PRINT:PRINT "Another ? (Y/N) ";
460 a$=INPUT$(1)
470 IF UPPER$(a$)="Y" THEN RUN
480 END
490 a$="                           "
500 FOR i=1 TO 9
510 READ n
520 x$=CHR$(n)
530 MID$(a$,i)=x$
540 NEXT
550 DATA 33,0,0,14,73,205,5,0,201
560 FOR i=10 TO 18
570 x$=CHR$(byte(i-10))
580 MID$(a$,i)=x$
590 NEXT i
600 x$=CHR$(c)
610 MID$(a$,10)=x$
620 ad=VARPTR(a$)+1
630 addr=PEEK(ad)+256*PEEK(ad+1)
640 high=INT((addr+9)/256)
650 low=addr+9-(high*256)
660 POKE addr+1,low
670 POKE addr+2,high
680 CALL addr
690 RETURN
The above will only work with a modified copy of BASIC

You will need to type 'GENCOM BASIC REDEFINE' so that the RSX is added to BASIC The subroutine in line 490 onwards POKEs machine code into memory which calls BDOS function 73 this is the BDOS call that is added by REDEFINE so the upshot is that characters can be redefined while a BASIC program is running - this subroutine could well be of use in other BASIC applications but the copy of BASIC used must have REDFEINE GENCOMd onto it.