Turbo Pascal 2.0 course cover
← Back to Courses
module
26

Reaching The Machine

Introduction

Everything so far has been Pascal: the language keeps you away from the machine underneath, which is mostly a kindness. But Turbo also leaves two doors open, straight into the Einstein - its memory and its chips. Through them, this section reaches the video chip and changes the shape of a character, so that Warehouse's crates can look like crates instead of $.

Mem

The Z80 reaches 65,536 bytes of memory, numbered from 0 to 65535 - $FFFF in hexadecimal. Turbo gives you all of it as an array of bytes called Mem: Mem[5] is the byte at address 5, and

Mem[A] := 7;

puts 7 into the byte at address A. Be careful where: that byte might be part of your program, or of Turbo.

Addr(X) gives the address of the variable X - where Turbo keeps it. Put the two together and you can look inside a variable:

X := 1234;
A := Addr(X);
Writeln(Mem[A], ' ', Mem[A + 1]);

prints 210 4. An Integer takes two bytes, and the Einstein keeps the low byte first: 4 * 256 + 210 = 1234.

Addresses above 32767 are too big for an Integer, so they come out negative: the X above was at -5126. That is still the right address - Mem takes it happily - and in hexadecimal it can be written as it is: Mem[$FFFF] is the very last byte.

Port

The Z80 talks to the Einstein's other chips - the video chip, the sound chip (which also reads the keyboard) and the rest - through ports, numbered from 0 to 255. Turbo gives them as another array, Port:

Port[9] := $40;  { $40 to port 9 }
B := Port[8];    { a byte from port 8 }

The Video Chip

The Einstein's picture is made by its video chip, which has 16K of memory of its own. The Z80 cannot reach that memory directly; it asks the chip through two ports:

  • Port 9 takes an address in the chip's memory: the low byte first, then the high byte - with $40 added if you are going to write there.
  • Port 8 then reads or writes bytes, one at a time, starting at that address and moving on by one each time.

The shapes of the characters - the font - live in the chip's memory, eight bytes to a character, starting at $1800. Character C's shape is at $1800 + C * 8. Each byte is one row of the character, top to bottom, and each bit is one dot, from the left:

........    0         the letter A,
...#....   16         read from the
..#.#...   40         video chip
.#...#..   68
.#####..  124
.#...#..   68
.#...#..   68
........    0

Only the left-hand six dots of each row show: that is how forty characters fit across the screen. The two right-hand bits could be anything.

A Character Of Your Own

Write eight bytes of your own at a character's place, and from then on, every time that character is printed, your shape appears instead.

Which character? The Einstein has 256, and the ones from 128 to 255 are graphics shapes that ordinary text never uses - 128 starts out blank - so they are the ones to take. Redefine a letter instead and every one of those letters, everywhere, changes.

Two rules, both important:

  • Print nothing between setting the address and sending the bytes. Printing uses the video chip too, and it moves the address. Your bytes go somewhere else, and your character does not change.
  • The new shape is used only for what is printed afterwards. A character already on the screen keeps its old shape. And the new one stays until you restart the Einstein - even after your program ends.

The Code

program Crate;
type
  Pattern = array[0..7] of Byte;
const
  Box: Pattern =
    ($FC,$84,$CC,$B4,$B4,$CC,$84,$FC);
var
  I: Integer;

procedure Define(Code: Integer;
                 Shape: Pattern);
var
  A, I: Integer;
begin
  A := $1800 + Code * 8;
  Port[9] := A mod 256;
  Port[9] := A div 256 + $40;
  for I := 0 to 7 do
    Port[8] := Shape[I];
end;

begin
  Writeln('Before: ', Chr(128));
  Define(128, Box);
  Writeln('After:  ', Chr(128));
  for I := 1 to 10 do
    Write(Chr(128), ' ');
  Writeln;
end.

Box is the crate, row by row. In dots:

######..   $FC
#....#..   $84
##..##..   $CC
#.##.#..   $B4
#.##.#..   $B4
##..##..   $CC
#....#..   $84
######..   $FC

Define splits the address into its low byte (mod 256) and high byte (div 256), adds $40 to the high byte because it is going to write, and sends the eight rows.

Starting from

The Einstein freshly restarted, Turbo started, Y, a new work file called CRATE.

What you should see

A crate of your own

Nothing after Before:, because character 128 starts out blank; a crate after After:; and a row of ten crates. Run it a second time, and the Before: line has a crate too: the shape stayed.

Change One Thing

  • Change every $FC to $FF. Does the crate look any different? Why not?
  • Change Define(128, Box) to Define(65, Box), and every Chr(128) to 'A'. Run it, look at the After: line - and then at the menu. (Restart the Einstein to get your A back.)
  • Add Writeln('Defining ', Code); inside Define, just after the two Port[9] lines. What happens to the crate?

Exercises

26.1 Ask for a key, then read that character's eight bytes from the video chip and draw them as # and ., with each byte's value beside it. (Reading needs the address without $40 - and read all eight before you print anything.)

26.2 Design a brick for the walls and a warehouse worker, and draw a small walled room with the worker and a crate in it.

26.3 Put 1000 in an Integer and print its two bytes with Mem, then put them back together.

Worked solutions are in Appendix II.

When It Goes Wrong

SymptomCause
The character does not changeSomething was printed between setting the address and sending the bytes.
The shape is right but only some of the ones on screen changedOnly characters printed after Define use the new shape. Define first, then draw.
Letters everywhere have turned into your shapeYou redefined a letter, not a character from 128 up. Restart the Einstein.
The right-hand side of the shape is missingOnly the left six dots of each row show.
The Einstein does something strange after a Mem writeThe byte belonged to something else. Restart it, and write only to your own variables.

Summary

Mem[A] is the byte at address A, to read or write; Addr(X) is where X is; an Integer is two bytes, low byte first. Port[N] sends to or reads from port N. The video chip takes an address on port 9 (low byte, then high byte plus $40 to write) and data on port 8. A character's shape is eight bytes at $1800 + code * 8, six dots to a row; write your own over characters 128 and up, print nothing in between, and everything printed afterwards uses it until the Einstein is restarted.

Next

S27, The Warehouse On Screen - Part three begins: the game itself, one piece at a time.

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.