3 Redefining Character Sets

Character Set Utilities

Fred Pinho


In addition to providing some useful utilities for working with redefined characters, this article discusses memory allocation and the various ways of storing machine language subroutines.

The Atari computer has the ability to redefine its character set at will. Making full use of this power, however, requires some programming. It isn't available at the flick of a switch or the touch of a key. To help in this effort, I'd like to present two simple machine language utilities for use in the text modes (GRAPHICS 0, 1, 2).

To set the stage, here's a brief overview of the Atari character set. A character set is really just a table of shapes which defines what a character will look like when printed to the screen or to a printer. The characters are used solely for communication with the human operator. The computer, as always, is manipulating numbers, not letters or graphics symbols. However, we think very clumsily, if at all, in pure numbers. The computer graciously converts its thoughts into symbols that we can understand and manipulate.

The set is stored in Read Only Memory (ROM) beginning at memory location 57344. Each character is stored within eight bytes of information. This provides an eight-by-eight grid for defining a character. Within each byte, a one means a dot is turned on by the video display. A zero leaves the dot off. There are 128 regular characters plus inverse. Since inverse characters can be generated from the normal, only the regular characters are stored. Thus, the Atari character set contains 1024 bytes (8*128).

Before it can do its thing, the computer needs to know the location of the character set. This is stored in two registers: the "shadow" register (CHBAS = 756 decimal) and the hardware register (CHBASE = 54281). The computer actually uses the hardware register to locate the character set. However, every 60th of a second, during vertical blank of the TV screen, it goes to the shadow register and transfers its contents to the hardware register.

From BASIC, if you wish to change the location of the character set, you must POKE the new location into the shadow register, not the hardware register. If you POKEd into the hardware register, you would see nothing, as it would be wiped out immediately by the copy from its shadow. The setup of the two registers makes it impossible to have multiple character sets on the screen at the same time when using BASIC. However, it can be done with machine language subroutines.

What do you store in the character registers? You store the page number of the beginning memory location of the set. What's a page number? The computer breaks down memory into 256-byte "pages" (recall that the range of numbers that can be stored in a single byte is 0 to 255 for a total of 256). The page number is just a fancy way of indicating a multiple of 256. To get the page number, divide your memory address by 256. If your answer doesn't come out to an exact number, you're in trouble. Run, don't waIk, to another memory location that is exactly divisible. This is important because your programs will not work if your page calculation is incorrect.

The full character set, plus inverse, can be displayed in graphics mode 0. Inverse characters cannot be displayed in graphics modes 1 and 2. In addition, these modes can display only half of the full set at any one time (64 characters). Thus, in these modes, you are limited to displaying either capital letters, numbers and punctuation, or lowercase letters and graphics symbols. From BASIC, you can't have both.

As you can see, the power of the redefinable character set is there, but it's not available without some programming effort. What's required to use redefined and multiple character sets? Three main steps must be considered.

1) Relocating the original character set from ROM into Random Access Memory (RAM).
2) Revising the relocated, RAM-based, character set.
3) Providing the computer with a program that can switch between character sets at predictable times during the TV display process.


Relocating The Character Set

Relocating the character set is simple in principle: PEEK each ROM location and POKE it into the desired RAM location. The first problem arises as to where to store the set. One common solution is to lower RAMTOP. This memory location (106) defines the upper limit of available memory. By POKEing a lower page number into this location, you can fool the computer into thinking it has less memory than actually installed. The character set can then be relocated to this area. Note, however, that this hidden area is not completely safe. Certain programming operations can cause unanticipated visits into this area by the computer. This would have a disastrous effect on your characters. Solutions to this problem would involve avoiding the guilty program commands and/or allocating extra, wasted memory above RAMTOP.

Another way is to relocate the set just below the display list. You have to be careful not to overrun the display list. Also, you have to plan your program so that it doesn't expand into your new character set. Even if your program is properly sized, you can still run into problems with an overly-obese run/time stack. This software stack is established by BASIC and resides just above your BASIC program area. It stores needed information for GOSUB and FOR/NEXT routines. If you exit from a FOR/NEXT loop before it finishes counting down, an entry is left on the stack. If this loop is used frequently, the stack will grow until it attacks your character set. The solution is careful use of the POP command to clean up the stack whenever you prematurely exit from a loop. Table 1 will allow you to relocate your set without interfering with the display list.

Another possibility would be to relocate your character set into a string. Just DIMension it for the proper size and then use the ADR function to locate the first memory location. This method would certainly provide a safe location. However, another problem arises.

Note that when you relocate a character set there are certain limitations. The full set (GRAPHICS 0) must start on a 1K boundary (i.e., the first memory location must be a multiple of 1024). The reduced set for GRAPHICS 1 and 2 must start on a 1/2K boundary (multiple of 512). This poses no problem for the first two storage methods. However, if you wish to use the string-storage method, you will have to expend some extra programming effort. You must insure that the string begins on the proper memory boundary.

Which storage method is best? I'll leave that up to you and to the demands of your program.


Relocation of the Character Set Beneath the Display List.

GRAPHICS
MODE
Relocate at the Indicated Offset (in Pages)
from RAMTOP

  Full Set (1024 bytes) Half Set (512 bytes)
0
1
2
3
4
5
6
7
8
8
8
8
8
8
12
16
24
36
Won't work
6
4
4
6
8
12
20
34

Note: Graphics modes are included since certain applications may require plotting of characters to the graphics screen.


Example:

Relocate the first half of the character set so it resides just below the GRAPHICS 1 display list. Label the first memory location of the set as BEGIN.

BEGIN = (PEEK(106)-6)*256

Once you've chosen the location, the next step is to move the character set. Using the PEEK-POKE method works OK, but it's too slow. If you've chosen to store your new character set in a string, then you could also relocate your set using the Atari's string handling routines, which are fast. This involves modifying the variable value table to fool the computer into thinking that one string is located at the ROM-based character set. This technique is described by Bill Wilkinson (COMPUTE!, January 1982, #20). Use of this technique would also solve your memory boundary problem. Note that there is a BASIC bug that does not allow the correct movement of string characters in multiples of 256. Thus you would have to transfer either 513 or 1025 bytes, instead of 512 or 1024 bytes. The most general, quickest and hassle-free method is to use a machine language subroutine. As usual, a decision must first be made. Where do you store your routine in order to protect it from BASIC's voracious appetite? Here are three good methods:

1. In page six (begins at memory location 1536). This page of memory was set aside by the Atari designers for use by the BASIC programmer. Generally, you can safely store machine language programs here. You then access them by the USR command (X = USR (1536)). Note, however, that the first 128 bytes of page six are not always safe. If you perform cassette input/output during the program, you could lose this first block of memory. To be absolutely safe, store your routine only in the last half of the page.

2. As a string. The method that I like is to store the program commands as graphics symbols within a string. Take the machine language number (in decimal), go to the ATASCII table (Appendix C in your Atari BASIC Reference Manual), find the equivalent graphics symbol, and type it into the string. You then access the machine language program by:

X = USR (ADR(MVCHR$))

Remember to DIMension this string first. With this method you are not limited in the size of your program as you would be with page six storage, and your program is safe.

3. Within an array. Atari BASIC allocates memory within the string/array area in the order in which you DIMension it. This location doesn't change thereafter. Note that this is not true of many other machines. Thus, if you DIMension a string followed by an array, you can locate the array relative to the string by use of the ADR function.

10 DIM AA$(1), MVCHR(32)
20 X = USR(ADR(AA$) + 1)

AA$ is meaningless except that you can determine its memory location. If you POKE your machine language program into the array MVCHR, you can always access it in the memory location following AA$. This method, however, can chew up large gobs of memory. Each cell in the array takes six bytes. Be careful to type these DIMension statements sequentially.

Which method should you use? Again, it depends. I like the string method since it is safe and memory-efficient. The efficiency arises from storing data with a single symbol, rather than storing each integer of the number plus a comma in DATA statements. You also avoid the overhead of the program lines required for READing the data statements.

To aid your programming efforts, I've included a machine language routine in Programs 1, 2, 3, 4, and 5. This routine will rapidly relocate your character set. Program 1 is listed in assembly language. The others are BASIC versions which demonstrate the various ways of storing a machine language routine in memory. This routine uses four zero-page memory locations (203-206). These locations have been set aside by Atari for programming use and are safe. To use them, POKE the address of your new character set into locations 203 and 204. As always, the least significant byte of the 16-bit address is POKEd first, followed by the high byte. Since the set must start on either a 512 or 1024-byte boundary, the least significant byte must always be zero. Then POKE the page number of the character set address into location 204. Memory locations 205 and 206 are set by the machine language routine to point to the character set in ROM. Note that location 205 is also set to zero. Finally, do a USR call to your routine.


Switching Between Character Sets

Remember that, because of the setup of the two character set registers, it's not possible to use more than one set at a time via BASIC. You can display as many sets as you wish, however, with display list interrupts. Well, almost. Actually you're limited to some extent by memory availability and the constraints of the display list. I won't go into detail here; this subject has been covered by numerous articles. It's enough to say that:

1) The interrupt will cause the 6502 processor to stop at a given scan line of the TV display.
2) It will then execute a machine language routine of your choice. The address of the routine must be specified in locations 512 and 513.
3) Once done, the 6502 will then merrily resume its TV display.

If the interrupt is done properly, all the action will occur while the TV beam is off the visible part of the screen. Thus the changes performed will appear instantaneous. Program 3 is a routine to allow the use of a redefined character set in the upper window of a GRAPHICS 1 or 2 display and the standard set in the text window. This routine simply loads the page number of the old ROM-based character set into the hardware character base register (CHBASE = 54281). What good is that, you ask?

This is done normally anyhow. Here's the strategy:

1) In BASIC, load the address of your new set into the shadow character base register. This is copied into the hardware register at the start of each TV screen display (every 60th of a second).

2) Set a display list interrupt at the last line of the GRAPHICS 1 or 2 screen. Then the interrupt will begin at the first line of the text window.

3) The interrupt routine loads the address of the standard character set into the hardware register. Thus the text window regains all the standard characters.

4) Things remain standard until vertical blank. At this time the TV beam is brought back to the top of the screen in order to begin a fresh sweep of the display. During vertical blank the contents of the shadow are automatically copied into the hardware register. Thus we've restored the new character set.

Note that the interrupt routine stores a number (any number will do) into memory location WSYNC (54282). This causes the processor to wait until the end of the blank period at the start of the next horizontal line. Thus the character set is switched while the electron beam is off the visible portion of the display. The result is a neat, clean change. Program 6 is an assembly listing of the display list interrupt routine, with decimal equivalents in parentheses to use in your DATA statements. (See line 31000 of Program 7.)

Notes: To set the interrupt, do the following:


Pulling It Together

Program 7 demonstrates the techniques discussed. It prints an identical set of characters to the text window and to the GRAPHICS 1 screen. The expected characters are seen in the text window. However, the GRAPHICS 1 display shows a band of archers besieging a castle.

Line No.
1
10-40
50-60
100

29000

29010

29020

29030-29040
29050-29060


30000-30060
31000
Calls initializing subroutine and turns screen display back on.
Draws a border using a redefined character.
Prints characters.
Uses STOP instead of END, as this allows you to experiment and print to the GRAPHICS 1 screen in direct mode. END does not.
Turns off display to speed processing. Calculates location of new character set (BEGIN) at six pages below RAMTOP.
POKEs the low and high bytes of the new set location into memory locations 203 and 204.
DIMensions the string for relocation of the old set.
Defines the string and calls the machine language routine.
Reads the redefined character data and POKEs it into the relocated set.
Calculates the location of the display list. POKEs an interrupt into the last GRAPHICS 1 line. Reads the interrupt routine into page six of memory. POKEs the address of page six into locations 512 and 513. Finally, it enables the interrupt.
New character data.
Data for interrupt routine.


You can do a simple experiment with this routine. To prove the necessity of writing to WSYNC in the interrupt routine, eliminate the fourth through sixth numbers in line 31000 (i.e., 141, 10, 212). Also change line 29050 to FROM X = 0 TO 7. Run the program again. You'll see that the last scan line of the GRAPHICS 1 screen stops about halfway across the screen. Also, the point at which it stops tends to jiggle annoyingly. Other weird lines appear when you hit a key. Finally, delete POKE 756, BEGIN/256 from line 29000. RUN and you'll see only normal characters.

There you have it. These programs only scratch the surface. With some further programming, you can have even more character sets on the screen simultaneously. You can also make your characters blink or change color without interfering with your BASIC program.


Program 1. Character Set Relocater--Assembly listing.







LOAD
PLA
LDA #0
STA $CD
TAY
LDA *
STA $CF
LOAD LDA ($CD),Y
STA ($CB),Y
INY
BNE LOAD
INC $CC
INC $CE
LDA $CE
CMP *
BNE LOAD
RTS
;Pull unused byte off stack

;Low byte of ROM-based character set
;Set register Y to zero
;*See Notes
;High byte of ROM set
;Load from ROM
;Store into RAM
;Increment Y
;Loop 256 times
;Increment RAM page number
;Same for ROM

;Compare to final page number *See Notes
;If not done, loop again
;Return

Notes on Programs 1, 2, 3, 4, and 5:

Programs 2 through 5 are BASIC versions of Program 1. To customize, make the following changes (if needed). Remember to convert the DATA numbers below to character equivalents when using Program 3.

1. To relocate the entire character set, be sure the 8th DATA number is 224, and the 25th DATA number is 228.

2. To relocate only the first half of the character set (uppercase, numbers, punctuation), be sure the 8th DATA number is 224, and the 25th DATA number is 226.

3. To relocate only the second half of the character set (lowercase, graphics symbols), be sure the 8th DATA number is 226, and the 25th DATA number is 228.

Download P086L2.BAS (Saved BASIC)
Download / View P086L2.LST (Listed BASIC)
Download P086L3.BAS (Saved BASIC)
Download / View P086L3.LST (Listed BASIC)
Download P086L4.BAS (Saved BASIC)
Download / View P086L4.LST (Listed BASIC)
Download P086L5.BAS (Saved BASIC)
Download / View P086L5.LST (Listed BASIC)
Download P086L6.SRC (Assembly source)
Download P086L7.BAS (Saved BASIC)
Download / View P086L7.LST (Listed BASIC)

Notes on Program 7:

1. To set interrupts for the text window of each of the text modes, change line 29050 as follows:

Text Mode
1
0
2
Change to
POKE DLST + 24,134
POKE DLST + 24,130
POKE DLST + 14,135

2. To add a text window to GRAPHICS 0, POKE 4 into memory location 703. See "Add a Text Window to GRAPHICS 0," reprinted in this book.

3. If you change the mode as in 2 above, don't forget to adjust the character set storage (BEGIN) in line 29000. Also remember the GRAPHICS call in line 29000.


Return to Table of Contents | Previous Section | Next Section