This card and a sister 512K card, were produced around 1986 by:
Elektronik-Service
Peter Kleinschmidt
Linning 37
D-4044 Kaarst Germany
Phone: 02101/603208
The software in the card ROM was written by Heiner Martin (the guy who wrote the "TI-99/4A Intern" book).
Card structure
CRU map
GRAM access
RAM access
Bank switching
RAM vs GRAM correspondance
DIP switches settings
The card ROM
Power-up routine
Subprograms
EDITMEM
MODULE
GRAM
Programs and TI main menu
The Gram Karte is a GROM emulator: it allows to copy cartridges on floppies and to load them in the card where they will execute normally.
It is controlled via the CRU and its CRU address can be set to any value with DIP switches. Usually, it is >1700.
The card has an 8K ROM, mapping at >4000-5FFF, with the several DSR, subroutines and programs. But most importantly, it has 128K of RAM.
The first 64K of RAM are used to emulate GROMs at access port >9800 (the port is also selected with a DIP switch). Note that the emulation covers the whole range of GROM addresses, from >0000 to >FFFF, which means there are some differences with cartridges:
The second 64K can be used in two ways.
Note that the second 64K can be accessed both as RAM and as GRAM, if it
is necessary. The correspondence between RAM pages and GRAM is designed
in such a way that is it possible to have a small module loaded in GRAM
at >9820, while still having enough memory for RAM.
The following is a list of terms I'll be using throughout this document. In most cases, these are "official" definitions, but some (like the distinction between cartridges and modules, or pages and banks) are my own and are only meant to facilitate the discussion.
If you are not familiar with the concepts of GROM, GRAM and cartridges bank, have a look at the page that describes the console architecture.
The Gram Karte has four DIP switches that allows to install it at
any
CRU address in the range >1000-1F00. Just compose the second nibble
of the CRU address in binary format on the switches S1 (msb) to S4
(lsb):
ON=0 and OFF=1 (e.g. >1700 = ON OFF OFF OFF). Be carefull not
to conflict with another existing card...
Bit | R12 address | I/O | Usage |
---|---|---|---|
0 | >1x00 | O | 1: Turn on card ROM at >4000-5FFF (and light) |
1 | >1x02 | O | 0: Enable reading from RAM at >6000-7FFF |
2 | >1x04 | O | GRAM enable 2 |
3 | >1x06 | O | 0: Enable reading from GRAM |
4 | >1x08 | O | 1: Enable writing to RAM at >6000-7FFF |
5 | >1x0A | O | 1: Override console GROMs with card GRAMs |
6 | >1x0C | O | 0: Switched bank appears at >7000-7FFF 1: Default bank appears (DIP selected 1-4) |
7 | >1x0E | O | 0: Writing to >6000-7FFF performs switching |
If GRAM memory is enabled, it can be accessed the usual way, through the GRAM access ports:
>9800 >9820: Read a byte from GRAM/GROM, then increments the
memory
pointer.
>9802 >9822: Read the address pointer, as two bytes. It points at
the next byte to be read/written plus one. The same pointer
works
for both ports.
>9C00 >9C20: Write a byte to GRAM, then increments the memory
pointer.
>9C02 >9C22: Set the memory pointer, passed as two bytes (MSB
first).
The value of the GRAM bases can be modified with 8 DIP-switches, but the two ports are always >0020 apart. All first 16 bases can be selected. This allows to have multiple Gram Karte installed in the PE-box, without conflict (supposedly, the 512K card implements 4 pairs of ports, so two such cards would cover the whole range of addressable GRAMs).
Port 1 | Port 2 | Switch |
---|---|---|
>9800 | >9820 | 1 |
>9804 | >9824 | 5 |
>9808 | >9828 | 2 |
>980C | >982C | 6 |
>9810 | >9830 | 3 |
>9814 | >9834 | 7 |
>9818 | >9838 | 4 |
>981C | >983C | 8 |
NB: Only one switch at a time can be ON, all others must be OFF.
You should always have one card using base >9800, since it's the
only
one that will have RAM banks.
By default, GRAM memory is readable in the range >6000-FFFF (in both ports). Three CRU bits can modify this situation:
The RAM at >6000-7FFF can be accessed normally, provided it is enabled. Two CRU bits control its enabling:
This is a more complicated issue. First of all, the memory block >6000-6FFF is not switchable: page 14 always appears in it. The only switchable block is >7000-7FFF. It is 4K in lenght, therefore 64K of memory will provide 16 pages that can appear in this bank. Selecting a page is performed by writing to the >6000-7FFF area. It is advisable to write protect it before switching, so as not to modify its content (MOVB a byte to itself is not guarantied to work as even the MOVB operation rewrites the low byte, and this may switch banks before the high byte is written).
Writing to >6000 (and >6020, >6040,...>7FE0) selects
bank
0.
Writing to >6002 (>6022, etc) selects bank 1.
Writing to >6004 selects bank 2, etc.
Two CRU bits control switching operations:
DIP switches | Bank | |
---|---|---|
7 | 8 | |
OFF | OFF | 1 |
ON | OFF | 0 |
OFF | ON | 2 |
ON | ON | 3 |
In summary, this is how to switch banks:
* This programs selects a page for the bank >7000-7FFF SELPAG LI R12,>1700 CRU address of Gram Karte (DIP switch selected) |
Page | Switching address | Location in GRAM |
---|---|---|
0 $ | >6000 | >E000 |
1 $ | >6002 | >F000 |
2 $ | >6004 | >C000 |
3 $ | >6006 | >D000 |
4 | >6008 | >A000 |
5 | >600A | >B000 |
6 | >600C | >8000 |
7 | >600E | >9000 |
8 | >6010 | >6000 |
9 | >6012 | >7000 |
10 | >6014 | >4000 |
11 | >6016 | >5000 |
12 | >6018 | >2000 |
13 | >601A | >3000 |
14 * | >601C | >0000 |
15 | >601E | >1000 |
* This is the page that appears at >6000-6FFF (non-switchable
bank).
$ The default page appearing when CRU bit 6=1 is selected among those
four,
with two DIP-switches.
The 128K Gram Karte has 3 blocks of 8 DIP switches on board.
Back |
DIP 1: switches 1 to 4 select the CRU address (see above), switches 5 and 6 are not used, switches 7 and 8 select the default bank (see table above).
DIP 2 selects the GROM base (see table above)
DIP 3 selects the number of wait states that depends on the memory installed. It is preset upon shipping (switch 2 closed, others open) and should not be modified.
The card ROM appears at addresses >4000-5FFF when CRU bit 0 is set to 1. Version 1 contains a power-up routine and 6 subprograms. The card can also install standard headers in GRAMs with programs in them. You can view a disassembled listing of the whole ROM in my download page.
Note: if you are not familiar with the structure of a standard header, have a look here.
The power-up routine check for the presence of a module, either as a solid-state cartridge or installed in the Gram Karte. If none is found, it clears the whole card memory. The user also has the option of erasing the card by pressing Fctn-4, if no cartridge is plugged-in. Once the card is erased, the routine places a default header at Gram address >6000 in both access ports. If the card answers to port >9800, the power-up routine also enables RAM in the range >6000-7FFF, but only if no ROM is detected here.
Here is an outline of the power-up routine:
There are 6 subprograms in the card ROM, all are meant to be called
from Basic (or Extended Basic):
EDITMEM: provides a small editor for cpu, VDP and GRAM/GROM memory.
MODULE: transfers a TI-Basic program from VDP memory to GRAM, thus
making
it a module!
GRAM: calls the Loader program in the first Gram Karte (never returns
if
found).
GRAM2: calls the Loader in the second Gram Karte found in the PE-box
(if
any).
GRAM3: calls the Loader for the third Gram Karte.
GRAM4: calls the Loader for the fourth Gram Karte.
This is a small, fairly primitive memory editor, to be called from Basic or Extended Basic. It does not have many features (no search function, no copy function, no page-up/page-down...) but it has two main advantages: 1) it is always here and thus very usefull to debug crashed programs, 2) it runs in the card ROM space (with a few bytes in the scratch-pad for registers and data storage) which means the whole memory expansion is free for inspection.
* EDIT MEMORY * |
* EDIT MEMORY * |
Here is how to use this program:
This subprogram allows to transfer the currently loaded Basic program into GRAM, at addresses >6000-9FFF. The program can be given a name that will appear on the TI main menu, after "TI-Basic". Selecting the program enters Basic and begin its execution. Upon completion, the message "Press any key to reset" is displayed so that you have time to read error messages, if any. Pressing a key resets the TI-99/4A. The Basic program can be saved from GRAM into a file and thus becomes a "module" in itself.
One advantage of this system is that much more VDP memory is now available for variables. Thus you can write larger Basic programs, that would normally results in a "Memory full" error.
The only drawbacks are first that such a program cannot be edited. You should therefore keep the normal (VDP loaded) version for future modifications. In this event, you will have to again call MODULE and GRAM to save it as a module. A further problem is that the "RESTORE line_number" instruction does not work (because of a bug in the Basic interpreter: RESTORE only searches for line numbers in the VDP memory).
MODULE relies on the fact that a Basic program can be executed either from the VDP memory or from GROM. A flag is set in byte >8389 to indicate GROM as a source (>FF). A special routine is provided in the console GROMs (at address >001E) to begin execution of a Basic program in GROM without having to enter Basic, nor to type "RUN" (which expects the line numbers in the VDP memory).
The MODULE subprogram thus copies the current Basic program and its line number table, from the VDP memory to GRAM memory, ending at address >9FFF. In addition, MODULE installs a standard header at GRAM address >6000-6100 in the current port. This header contains only one program to appear on the main menu. The program name is TEST, but can be changed by passing a string as an argument in the CALL MODULE("NAME") statement. All it does it to enter the Basic interpeter via the GROM subroutine mentionned above.
Here is the outline of the MODULE program:
The GPL program in the custom header does the following, when selected from the main menu:
These are four entry points to the same program. All it does is to copy the GRAM/GROM management program into the low memory expansion and branch to it. This program must be run in the memory expansion because it performs file operations, which will require turning on the ROM in the drive controller card, therefore turning off the Gram Karte ROMs. It resides at >4E00-5FFF in the ROM and will be copied at >2700-28FF.
Each entry point corresponds to a different Gram Karte, in the order of their CRU addresses. Thus we can have upto 4 such cards installed in the PE-Box. The program can determine which card is currently called by checking the value of R1: it contains the number of time the called subprogram was found. Generally R1 = >0001, but if a subprogram returns with B *R11 (as opposed to INCT R11, B *R11) the scanning will continue and, if a subprogram with the same name is found elsewhere, it will be entered with R1 = >0002.
Therefore, each entry point verifies if the value of R1 matches what it expects: >0001 for GRAM, >0002 for GRAM2, >0003 for GRAM3 and >0004 for GRAM4. If not, it returns with B *R11 and lets the scanning go on. This may result in a "not found" error if there are not as many Gram Karte installed.
If the number matches, the program copies the card management program (improperly called "Loader") from ROM addresses >4E00 to >5FFF (using the current GROM base found at >83FA) to the low memory expansion, at addresses >2700-38FF. It then leaves Basic without any hope of return and enters the card management program (at >27FE).
The card management program changes the screen color and displays the current GROM base at the top of the screen (if called from the main menu. From basic it displays GRAM-CARD LOADER). It then displays a 7-item main menu, enclosed in a pretty frame:
* GRAM-CARD >9800 * |
The user can select an option by pressing the coresponding key, refresh the screen with Fctn-6 or exit and reset the TI-99/4A by presssing Fctn-9. Once an option is selected, it can be aborted at any time by pressing Fctn-6 or Fctn-9, which returns to the main menu.
Save GROM
Is used to save GROM or GRAM memory to a memory-image "program"
file. You will be prompted for the start address (included) and the end
address (not included) of the memry block to dump. So for instance
>6000
to >7800 will save >1800 bytes, from >6000 to >77FF. The
maximum
size for a file is >2000 bytes (this is because file operations go
through
VDP memory, and space is limited there). Then you must enter the GROM
base.
A default value is proposed, that corresponds to the base in use when
the
loader was entered. Finally, you are prompted for a file name.
Once you entered everything, the operation begins. If an error occurs, you'll get a brief message ("I/O error xyz" or Loader error xyz"), followed by "Command completed". If all goes well, the only message will be "Command completed". In any case, you'll return to the main menu by pressing <enter> (or Fctn-6 or Fctn-9).
Save ROM
Is used to save a ROM or RAM memory in a memory-image "program"
file. You must first provide the card's CRU address so that the program
can toggle the appropriate CRU bits. The proposed default is the CRU
address
of the card the loader was loaded from. You will be prompted for
switching
address of the page to save. If you just press <enter>, or type
>6000,
the whole area >6000-7FFF will be saved. To save a page, enter its
switching
address (>6002, >6004, etc): the bank >7000-7FFF will be
saved,
after due switching to the selected page. Finally, you'll be asked for
a filename.
Load (G)RAM with program
This is used to load GRAM or RAM memory with data from the files that
were
saved with options 5 or 6. You will be prompted for the card's CRU
address
and GROM base, but default values are provided and in most cases you
can
just accept them with <enter>. You will then be prompted for a
filename.
These memory-image files include a 3-word header that tells the loader
which type of memory it is meant for, the destination address, and the
number of bytes to load. If no such header is found, you'll get the
error
message "No (G)RAM fike".
Load GROM 0-2
This allows to override the console GROMs with the Gram Karte GRAM
memory
in the range >0000-5FFF. You will be asked for confirmation, as
there
is no guaranty that this will not damage the console GROMs (!) If you
answer
"Y" the console GROMs are copied intro the GRAM card, at adresses
>0000-5FFF in both GROM bases (which means you cannot use RAM at
>6000-6FFF
since this correspond to GRAM >0000-1FFF in the second base)..
Load RAM with asm-file
This load an assembly program in "tagged object code" into the
RAM bank at >6000-7FFF. You will be prompted for the card CRU and
the
switching address of the bank to load (enter none to load the
>6000-6FFF
memory area). Note that this option only works with the >9800 port.
Finally, you'll be asked for the name of the assembly file. This file
should
be produced by a 9900 assembler, such as the one in the TI
Editor/Assembler
module. It contains all necessary address and size informations.
Note that the Gram Karte loader is very primitive: it does not support relocatable code, and does not allow for REFs, DEFs, nor for CSEG, DSEG and other sophisticated features of the TI assembler. Although it is only meant for the cartridge ROM area, it will load code anywhere in memory if the corresponding AORG was placed in the source program.
Load GRAM with asm-file
This loads a GPL program (a low-level language
to
be interpreted from GROM memory) into GRAM memory. The file should be
produced
with Michael Weiand GPL-assembler or a compatible program (such as mine).
The loader is extremely primitive and only allows for loading at
absolute
addresses, with no REF/DEFs, etc. You'll be asked for the card's CRU
address
and for the GROM base you want to use, then for the filename.
Load-file
A load file is a display/variable 80 text file containg a list of files
to load. This comes handy since a module can theoretically consists in
5 GROM files and 16 ROM files! Each line in the load-file must contain
one and only one filename to be passed to option 3. Optionally, a GROM
base can follow the filename after a unique space (and no leading
>).
The last line must be left blank as a signal for the loader to stop.
Upon selecting this option, you will be prompted for the card's CRU address, for the GROM base to use and for the name of the DV80 load-file.The files listed in the load-file are then loaded automatically. If a GRAM file has an address is in the range >0000-5FFF, it is loaded in both bases and the override of the console GROMs is turned on. RAM files greater than >1000 bytes load from >6000 and leave the bank enabled for writing, but not switchable. Smaller RAM files load from >7000 and leave the bank write-protected and switchable. A wrong file format aborts the loading process with a "No (G)RAM file" message.
Protected cartridges
It is hard to believe, but this piece of hardware meant to by-pass the copy protection mechanism provided by solid-state cartridges, actually contains a copy protection scheme! The author of the software, Heiner Martin, was appearently involved in a company called Apesoft. He was mean enough to install a safety mechanism in option 5 (Save GROM), that prevents us to save an Apesoft module on disk.
The protection subroutine checks the two bytes at address >6020-6021 in GROM memory. If they contain either "AP" or "HM" the message "Module copy protected" is displayed and the save operation aborts. The same thing is true for the two bytes at address >E015-E016, except that is this case the file is saved, but immediately deleted (and we don't get any error message).
Overcoming the protection is simple enough: we just need to patch
the
loader code, to replace the tests with NOPs or JMPs. The only problem
is
that the code is in ROM, so we must wait until it is loaded in the
low-memory
expansion to patch it. But that's no problem: the loader itself will do
it for us, thanks to its "load asm-file" function (remember,
it can load code anywhere in memory). Just assemble the following
assembly
language snippet, then CALL GRAM with the protected cartridge plugged
in,
select option 3 and load our assembly file: voila, the loader's option
5 will now ignore all protection codes.
* Crack for the Gram Karte protection in "Save GROM" AORG >30C0 |
With a Gram Karte installed, you will find that the TI main menu (upon leaving the title screen) looks slightly different from the usual:
Texas Instrument Home Computer Press |
The last item does not come from the GRAM card, it is added by the power-up routine in the console GROMs that looks for programs and builds the title screen. This routine searches the cartridge ROM (at >6000) and all GROMs (at >0000, >2000, >4000, >6000, >8000, >A000, >C000, and >E000 for all ports from >9800 to >981C) for standard headers and lists all programs it found (see the standard headers page for details).
This happens if the menu-building routine detects a device that discriminates between GROM bases (unlike cartridges and console GROMs). It is detected by comparing the contents of GROM addresses >6000-601E in bases >9800 and >9804. If there is a difference, the main menu will display one page for each active GROM base. Each page ends with the "Review Module Library" item. By selecting it, the user will access the next page, that deals with the next GROM base.
If you install a module in the Gram Karte port >9800 and disable the second port, the main menu will only contain TI-Basic and the program(s) in that module. If however both ports are enabled, the TI routine detects different modules in each (provided the card uses base >9800 or >9804 and has non-zero data loaded in GRAM at >6000-601E). It then displays a first screen with the programs found at GROM base >9800: TI-Basic, the programs in the loaded module(s), then "Review module library". Selecting the last option, repeats the scanning for the next used port (>9820 if we have only one Gram Karte). This second menu will again display "TI-Basic" since console GROMs show in all bases, plus any program in the module(s) installed in that port, etc.
When reset, the Gram Karte installs a default program in the GRAM header at >6000. The name of this program is "GRAMCARD >9800", but it is modified upon installation to reflect the GROM base it is intalled in: "GRAMCARD >9820", etc.
Selecting this program enters the same card managment program as CALL GRAM, but first displays a shortcut menu:
GRAMCARD >9800 |
Options 2 to 9 process "load files" meant to load some of the most popular modules: Extended Basic, Editor/Assembler, Disk Manager, Multiplan, TI-Writer, Terminal Emulator, TI-Logo and Mini-Memory. The files are loaded using the CRU address and the GROM base that were in use when the shortcut menu was entered.
Pressing Fctn-7 toggles between that list and another, that contains less meaningfull file names:
GRAMCARD >9800 |
Pressing 1 enters the Gram Karte managment program with the default CRU and GROM base corresponding to those in the header selected from the TI main menu.
These "program" files contain a plain copy of the memory saved, after a 3-word header. This header is different for GRAM and RAM files (and it does not follow the convention established by TI for RAM memory-image files, nor by the Gram Kracker for GRAM memory-image files).
GRAM files
>A5A5 Flag to indicate a GRAM file
>xxxx GRAM address
>xxxx Number of bytes (max >2000)
RAM files
>5A5A Flag to indicate a RAM file
>600x Switching address
>xxxx Number of bytes. If greater than >1000, loading starts at
>6000,
else is starts at >7000
These are display/fixed 80 files. They consists in 1-character "tags" generally followed by one or more byte of data.
GPL programs
Tag | Data | Meaning |
---|---|---|
>01 | >xxxx "........" |
Indicates a compressed file. The data (a number and 8 chars) is ignored. |
0 | >xxxx "........" |
Same as above but not compressed (numbers are "spelled out" as 4 chars) |
1 or 2 | >xxxx | Ignored, the data is skipped. |
3 or 4 | >xxxx | Loader error >0A |
5 or 6 | >xxxx "......" |
Ignored. The data (a number and 6 chars) is ignored. |
7 | >xxxx | Checksum: negated sum of all bytes in the record. Loader error >0B if incorrect. |
8 | >xxxx | Ignore checksum. The data is ignored. |
9 | >xxxx | Absolute loading address in GRAM memory. |
A | >xxxx | Loader error >0A |
B | >xxxx | Data bytes, to be loaded in GRAM |
C to E | >xxxx | Loader error >0A |
F | - | End-of-record (no data) |
G to O | >xxxx | Loader error >0A |
: | "c)...... | End-of-file. The copyright string is ignored |
Assembly programs
Tag | Data | Meaning |
---|---|---|
>00 | >xxxx "........" |
Indicates a compressed file. The size >xxxx must be less
than >2000
or error 8 is issued. The string of 8 chars (program name) is ignored. |
0 | >xxxx "........" |
Same as above but not compressed (all numbers are "spelled out" as 4 chars) |
1 or 2 | >xxxx | Ignored, the data is skipped. |
3 or 4 | >xxxx | Loader error >0A |
5 or 6 | >xxxx "......" |
Ignored. The data (a number and 6 chars) is ignored. |
7 | >xxxx | Checksum: negated sum of all bytes in the record. Loader error >0B if incorrect. |
8 | >xxxx | Ignore checksum. The data is ignored. |
9 | >xxxx | Absolute loading address in RAM memory. |
A | >xxxx | Loader error >0A |
B | >xxxx | Data word, to be loaded in RAM |
C to E | >xxxx | Loader error >0A |
F | - | End-of-record (no data) |
G to O | >xxxx | Loader error >0A |
: | "c)...... | End-of-file. The copyright string is ignored |