# apple1-videocard-lib

Library and demos for the "Apple-1 Graphic Card" by P-LAB, 
featuring the TMS9918 Video Display Processor by Texas Instruments.

## Repo structure

```
demos/
   demo/     demo program that makes use of the library
   picshow/  demo program that shows a picture in bitmap mode
   tetris/   a game
   montyr/   "Monty on the Run" SID tune by R. Hubbard
   tapemon/  tape monitor utility
docs/        TMS9918 and Apple-1 manuals
kickc/       target configuration files for KickC
lib/         the library files to include in your project
tools/       
   wavconv/  prg <-> WAV file converter   
```

## Introduction

The library is a set of C routines that make very easy
to use the TMS9918 on the Apple-1. It is intended to work
with the "Apple-1 Graphic Card" board by [P-LAB]() or any 
other video card that maps the TMS9918 in the `$CC00`-`$CC01`
memory range of the Apple-1.

The library is written in C with [KickC](https://gitlab.com/camelot/kickc/-/releases), a very efficient 6502 C compiler.

## Choice of the screen mode

The library supports screen modes 1 and 2 only (screen 0 and screen 3 not 
being very useful). Both are 256x192 pixels but there are some differences 
you should consider when evaluating which mode to use:

- Screen 1: there are 256 tiles that can be written very quickly on the 
screen, but the color choice is limited to 8 consecutive tiles for each 
color in the palette. It's commonly used for text applications with 
limited colors.

- Screen 2: you can address every pixel on the screen with a limitation 
of 2 colors per line within an 8x8 tile grid. It's commonly used for bitmapped
graphic or slow-but-colorful text.

Both screen modes support 32 sprites.

### Working with screen 1

Some example code:
```c
// *** first we set the SCREEN 1 mode

tms_init_regs(SCREEN1_TABLE);   // initializes the registers with SCREEN 1 precalculated values
screen1_prepare();              // prepares the screen to be used, loads a useful 8x8 ASCII font
tms_set_color(COLOR_VIOLET);    // sets border color to violet (see tms9918.h for the list of all colors)

// *** then we can use it

screen1_cls();                  // clears the screen 
screen1_putc('A');              // writes the character "A"
screen1_putc(CHR_BACKSPACE);    // goes back 1 character
screen1_putc('B');              // overwrites a "B" over it

// writes "A" in the bottom-right corner, causing the screen to scroll
screen1_locate(31,23);
screen1_putc("A");

// some printing
screen1_puts(CLS "Hello world");                  // CLS, REVERSE_ON, REVERSE_OFF
screen1_puts(REVERSE_ON "reverse" REVERSE_OFF);   // are macros defined in tms_screen1.h 
screen1_puts("Line1\nLine2");                     // '\n' is also supported

// simple string input from the keyboard (editing with CTRL-H is also supported)
char buffer[32];
screen1_strinput(buffer, 32);
screen1_prints("you wrote:");
screen1_prints(buffer);
```

### Working with screen 2

Some example code:
```c
// initializes the registers with SCREEN 2 precalculated values
tms_init_regs(SCREEN2_TABLE);   

// sometimes two colors need to be packed into a single byte
// you can easily do that with the FG_BG() macro:
byte mycolor = FG_BG(COLOR_BLACK,COLOR_WHITE);

// prepares the screeen to be used as a bitmap with default colors black on white
screen2_init_bitmap(mycolor);

// plots a pixel in the middle of the screen
screen2_plot(128,96);

// and erases it:   
screen2_plot_mode = PLOT_MODE_RESET;  // PLOT_MODE_INVERT is also supported
screen2_plot(128,96);
screen2_plot_mode = PLOT_MODE_SET;

// draws a diagonal line
screen2_line(0,0,255,191);

// writes a character from the embedded FONT
byte col = FB_BG(COLOR_DARK_RED,COLOR_LIGH_YELLOW);
screen2_putc('A', 31, 23, col);

// writes a string
screen2_puts("HELLO", 16, 12, col);

// note: screen2_putc() and screen2_puts() are fast but they
// can only print characters aligned within the 8x8 grid
```

### Working with VRAM directly

Some example code:
```c
// writes the value 42 at VRAM location 8000
tms_set_vram_write_addr(8000);
TMS_WRITE_DATA_PORT(42);

// and re-reads it
tms_set_vram_read_addr(8000);
byte val = TMS_READ_DATA_PORT;
```

When using the default values that came with `SCREEN1_TABLE[]` and `SCREEN2_TABLE[]`,
VRAM is organized according the following memory map:
```c
// ZONE                RANGE          NAME YOU CAN USE IN C
// =========================================================== 
// pattern table       $0000-$17FF    TMS_PATTERN_TABLE       
// sprite patterns     $1800-$19FF    TMS_SPRITE_PATTERNS  
// color table         $2000-$27FF    TMS_COLOR_TABLE      
// name table          $3800-$3AFF    TMS_NAME_TABLE      
// sprite attributes   $3B00-$3BFF    TMS_SPRITE_ATTRS        

// example: writes the bitmap value 10101010 on row 3 of pattern 4
tms_set_vram_write_addr(TMS_PATTERN_TABLE+4*8+3);
TMS_WRITE_DATA_PORT(0b10101010);
```

### Working on a more low-level

#### Setting the TMS9918 registers

```c
// you can set a TMS9918 register directly with:
tms_write_reg(7, 0x1F);

// which also saves the written value to a buffer
// because the TMS does not allow to read from 
// its registers (they are write-only)
byte oldvalue = tms_regs_latch[7];
```

### Working with sprites

```c
// set 8x8 or 16x16 sprites
tms_set_sprite_double_size(0);

// set single pixel or double pixel sprites
tms_set_sprite_magnification(1);

// define the sprite pattern 0
tms_copy_to_vram(ghost, 8, TMS_SPRITE_PATTERNS);

// define a sprite using the "sprite" struct
tms_sprite spr;
spr.x = 100;
spr.y = 50;
spr.name = 0;  // pattern 0
spr.color = COLOR_BLACK;
tms_set_sprite(0, &spr);
```

#### Working directly with the I/O chip interface

If you want to program the VDP directly you can use the following utility functions:

```c
TMS_WRITE_CTRL_PORT(value);       // writes a byte to the control port ($CC01)
TMS_WRITE_DATA_PORT(value);       // writes a byte to the data port ($CC00)
byte value = TMS_READ_CTRL_PORT;  // reads the status register ($CC01)
byte value = TMS_READ_DATA_PORT;  // reads a byte from the data port ($CC00)
```

### Miscellaneous functions

```
tms_wait_end_of_frame();   // waits the end of video frame, for timimng or sync video updates
tms_set_blank(1);          // turns on video blanking (0 restores normal view)
tms_set_external_video(1); // turns on/off external video input

tms_set_interrupt_bit(1);  // enable end of frame interrupts generation

(TODO: interrupt functions)
```

### Apple-1 utility functions

There are also utility functions to interact with the Apple-1
screen and keyboard:

```c
// prints hex "F3" on the Apple-1 screen
woz_print_hex(0xF3)

// prints "A" on the Apple-1 screen
woz_putc('A');

// prints "HELLO" on the Apple-1 screen
woz_puts("HELLO");

// gets a key from the keyboard (waits for it)
byte k = apple1_getkey();

// non blocking keyboard read: do something until RETURN is hit
while(1) {
   if(apple1_iskeypressed()) {
      if(apple1_readkey()==0x0d) break;
   }
   else do_something_else();
}                
```

### Building the source code

To link the library, simply `#include` the `tms9918.h` file
in your C sources. The recommended way is to add the KickC
command line switch `-includedir=thisrepo/lib` to your
build script and then include the file with `#include <tms9918.h>`.

The the `tools/` directory contains a simple `build.bat` script
example (for Windows) that you can customize to your needs.

### Setting a machine target

There are three configurations you can target with
the switches `-t target -targetdir thisrepo/kickc`
of the KickC compiler:

- `apple1`
- `apple1_jukebox`

#### Target "apple1"

With this target, the compiled program will start at `$0280` in 
the free RAM of the Apple-1 (please make sure you have enough RAM).

(TODO: add reference to `hexdump.js`)

#### Target "apple1_jukebox"

This target is for expansion cards that provide a ROM storage in 
the range `$4000`-`$7FFF`, as:
- the "CodeTank" EEPROM daughterboard of the "Apple-1 Graphic Card"
- "Juke-Box Card" FLASH

In this target configuration, the program is split into two segments:
- the `Code` that resides in ROM at `$4000`
- the `Data` that resides in RAM at `$0280`

The split is required because the program needs to write on the `Data` segment (e.g. when changing the value of a variable).

The only issue is that the "Data" segment needs to be initialized 
with the correct startup values (for example, the value that 
a global `int` variable takes before it's used).

The "Data" initialization needs to be done manually in the C program 
by explicitly calling `apple1_eprom_init()` in `main()`. The function 
will copy the ROM portion $7582-$7FFF into the "Data" segment at $0280-$0FFF.

The initialization values in the ROM range $7582-$7FFF are generated with 
the build script `mkeprom.js` which creates a fixed-length 16K binary file 
to be put on the EEPROM/FLASH.

Below is a recap of the memory map for this target:

$0000-$00FF zero page: holds some C program variables 
$0280-$0FFF RAM: C program "Data" segment 
$4000-$7581 ROM: C program "Code" segment 
$7582-$7FFF ROM: C program "Data" segment (startup values)
