Breakout, the game the course builds in HiSoft C, running on the Tatung Einstein: coloured brick rows, the bat and the ball on a black court
← Back to Courses
module
23

Reading A Real Program: EIN.LIB

Introduction

So far you have written every line you have compiled. Now read some lines you did not write. EIN.LIB on the course disc is the Einstein library - the functions that clear the screen, put the cursor where you want it, set the colours, draw, and read the keyboard without waiting - and it is short enough to read in one sitting: about two hundred lines, most of them three lines long. Reading it answers a question the course has been putting off since S8: what is a library, and how does a C function get at the machine? The answer is that it is C, mostly, and where it is not, it is eleven bytes of Z80.

Opening It

ED EIN.LIB opens it in the editor as any file. Do not save it. (Or DISP EIN.LIB scrolls it past, too fast to read; or HC #list+ any program that includes it.) The first screen is a header and a block of #defines:

#define mcal 0xcf
#define zkscan 0xb5
#define zplot 0xc4
#define zdrwto 0xc8

Hold those; they are the machine's telephone numbers, and their meaning comes in a moment. Then the functions begin, and the first is the simplest in the file:

void   cls40()
         {
           rawout(14);
         }

That is all cls40 is. rawout sends one character to the screen with nothing in the way, and character 14 is not a character at all: it is a control code that the Einstein's operating system in ROM (MOS) understands as "clear the screen and set 40 columns". cls() is rawout(12), cls32() is 15, curon() and curoff() are 17 and 20, and beep() - the whole of it - is rawout(7). Six of the library's functions are one control code each, and S24 uses them.

Reaching Into Memory

void   curat(h,v)
         {
           poke(0xfb4a,h);
           poke(0xfb4b,v);
         }

curat moves the cursor by writing the column and row into two bytes at FB4Ah and FB4Bh, and nothing else. Those two bytes are MOS's own record of where its cursor is, near the top of memory in the workspace MOS keeps for itself, and the next character printed goes wherever they say. tcol and gcol are the same trick - one poke each, to FB38h and FB39h, where MOS keeps the text and graphics colours - and line is four pokes in a row, the dash pattern the drawing routines read. poke itself, if you look in STDIO.LIB, is two lines: a cast of the address to a char * and an assignment through it, which is S16 with a library name on it.

Notice what this means. Nothing in the language knows about cursors or colours. The library knows where MOS keeps its notes, and writes on them. A library on this machine is largely a list of addresses.

Reaching The Chips

void   bcol(c)
         {
           c &=0x0F;
           out(c,9);
           out(0x87,9);
         }

The border colour is not in memory; it is in the video chip, and bcol talks to the chip directly through port 9 with out, which is STDIO.LIB's function for sending a byte to a Z80 port (data first, port second). Two bytes go out: the colour, then 0x87, which the chip reads as "that was for register 7". S25 explains registers and ports; here, see that the library needs nothing but out and the right two numbers.

shapedef is the same conversation, longer: it sends the video chip an address inside its own memory - 0x1800 plus eight times the character number, where the chip keeps the shapes of the characters - through port 9, and then the eight bytes of the new shape through port 8. That is how the Einstein lets you redefine a character, and Breakout's ball and bricks will be characters redefined this way. vpoke, further down, is the general form: one byte to any address in the video chip's memory.

Reaching MOS

int    kbd()
          {
            inline(mcal,zkscan,LD_A_into,&reg_a);
            return reg_a;
          }

Here the C runs out. kbd needs to ask MOS whether a key is down, and MOS is asked things in Z80 machine code: the instruction RST 08h followed by a byte saying which of its routines you want. HiSoft C has inline for exactly this. Each argument is a number to be placed in the program as machine code, right there, and the compiler does not look at what they mean. So mcal is 0xcf, the byte for RST 08h; zkscan is 0xb5, MOS routine 181, the keyboard scan that returns without waiting; and LD_A_into, &reg_a is the instruction that stores the A register at the address of a variable called reg_a, which STDIO.H declares for the purpose. Four numbers; then return reg_a and it is C again.

plot and draw are the same shape with more setting-up: the coordinates go into reg_bc and reg_de, an inline loads them into the IX and IY registers where MOS's drawing routines expect them, and mcal, zdrwto - routine 200 - draws the line. PUSH_IX and POP_IX around it are the library being careful: the compiler uses IX itself, and a routine that changed it would break the function that called it.

You will not need to write inline in this course. But when you read a library and meet a function that is nothing but numbers, this is what it is: a Z80 routine written in hexadecimal, and the names in the #defines are the only documentation.

Why The Library Goes First

draw calls testline, which calls peek and poke; bcol calls out. Those are in STDIO.LIB. S8 said that ?STDIO.LIB? compiles only the functions that have been called above it - and a call inside EIN.LIB counts only once EIN.LIB has been read. So the order at the end of a program is #include ?EIN.LIB? and then #include ?STDIO.LIB?, and the other way round ends with undefined symbol poke. The Einstein library uses the standard one; the standard one uses nothing.

The Bug

Read spritedef and the two functions after it:

void   spritedef(s,n)
          {
            n<<=2;
            n+= 0x3b02;
            vpoke(s,n);
          }

void   spritecol(s,c)
          {
            s<<=2;
            s+=0x3b03;
            vpoke(s,c);
          }

vpoke(addr, c) writes the byte c at addr in the video chip's memory. spritecol works out the address from the sprite number s and writes the colour c there. spritedef works out an address in the same way - but from n, the shape, not from s, the sprite - and then calls vpoke(s, n): sprite number as the address, computed address as the byte. It cannot have been tested. The line that was meant is s <<= 2; s += 0x3b02; vpoke(s, n);, matching its neighbour.

That is worth finding for its own sake, and for the lesson: a library is a program, and programs have bugs. When a library function does nothing, or something strange, the source is on the disc and it is short. S25 puts a sprite on the screen the corrected way.

The Code

There is no program to type in this section. Open EIN.LIB in ED80 and read it from the top with the help file, EINLIB.HLP, beside it - DISP EINLIB.HLP prints it, in pieces small enough to read if you are ready with SPACE. Then leave with GRPH-K Q so that nothing is saved.

Starting from

The course disc at 0:; ED EIN.LIB.

What you should see

The header, the #defines, and cls() as the first function; curat about a screen further down; kbd() and testline() near the end.

Change One Thing

  • Find every function in EIN.LIB that is a single rawout, and list the control codes. How many are there?
  • Find every address the library pokes, and put them in order. What is the lowest and the highest, and what does that say about where MOS keeps its workspace?
  • Read mag and say, from bcol as a model, which video chip register it writes and with what.

Exercises

23.1 Copy cls40 and curat into a program of your own, renamed clear and at, and use them - without including EIN.LIB. Does it compile? What did you have to include instead?

23.2 Write home() that puts the cursor at the top left, using poke and the addresses from curat, and test it.

23.3 Read doke and say what it does, then write dpeek(addr), its opposite, using peek twice.

Worked solutions are in Appendix II.

When It Goes Wrong

SymptomCause
ED80 asks Filename: when you leave and you press ENTERIt saved EIN.LIB back. It is unchanged unless you typed; GRPH-K Q is the safe way out.
undefined symbol poke (or out, peek) at the end of a compile that uses EIN.LIB?EIN.LIB? is after ?STDIO.LIB?. Swap them.
spritedef shows nothingIt is the library's bug, above. Use spritecol's shape of code.

Summary

EIN.LIB is two hundred lines of three-line functions. Six are one control code through rawout; most of the rest poke MOS's workspace or out to the video chip's ports; four reach MOS's own routines with inline, which places machine code in the program as numbers. It depends on STDIO.LIB, so it goes first. And spritedef has its arguments the wrong way round.

Next

S24, Where Text Goes - the screen as a grid: clearing it, putting text at a position, colours, and the control codes, measured for speed.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.