Lynx Programming for Dummies

Note: All content on this page is from Bjoern Spruck's website here: http://www.uni-giessen.de/~gd1113/lynx/kurs/

Chapter 1

Chapter 2

Chapter 3

Chapter 4

Chapter 5

Chapter 6

 

Demo & game programming on the Atari Lynx


Contents

  1. Introduction and objective

  2. The development environment

  3. "Hello World" in C

  4. "Hello World" in assembler

  5. Hardware, Mikey, Susy and structure of memory

  6. Sprites

  7. The individual programs in the overview

  8. Problems with compiler or assembler?


Introduction and objective

This course is to make the basis for the programming of the Atari Lynx understandable. At the end one should be able (with the help of the examples) to write its own small games for the Lynx.

Programming knowledge is presupposed, knowledge of C is of advantage. Knowledge of 6502 assembler (C64, Apple II, Atari XL, super Nintendo...) is naturally even better.

Basis of the course is the BLL kit (Behind Lynx Lines) of Bastian Schick and Lars Baumstark, which is freely available (in contrast to the original DevKit of Atari/Epyx).


The development environment

My development environment runs on a PC with Windoof. The reasons are a higher compiler speed and the presence of an emulator, which simplifies tests and debugging substantially. The original programs ran all on the Atari ST. So it is nor problem to us a ST, a TT or a Falcon for developing. Only an emulator is missing here. With a Falcon or a TT one can transfer the games with 62kbaud through the serial interface to the Lynx, with PC and ST only 9.6kbaud are possible. Developing under Linux should also be possible. You only have to recompile some of the programs. Most sourcecodes are available.

Needed software:

I use the BLL kit in this course as well as the PC conversion of Matthias Domin. The files can be downloaded archives <here> and <here>. The packages contain assembler (plus macros and include files), compilers (plus libraries), optimizier, linker, the Lynxer and examples. The Epxy DevKit includes similar programms. Also most of the macros have similar names.

Not necessary however very useful: PC Lynx emulator Handy. The PC version is available for download <here>. Additionally needed is the Lynx boot ROM image ("lynxboot.img"). I remind of the fact that it is illegal to use a ROM without having a Lynx. Mails asking for the boot image are passed directly/dev/null! So do not even think of asking! Better try a internet search engine.

Needed hardware:

Atari Lynx type 1 or 2

BLL cartridge or SIMIS cartridge

Serial to ComLynx adapter

If you have the Handy emulator you do not need any Lynx hardware for developing.

Software installation:

I assume with all further examples that all files are located in the folder "c:\lynx\" and its subfolders.

Then unpack the BLL archives into the folder "c:\lynx\bll". In this folder are now the following subfolders: "vardefs", "atari_st ", "include", "lynx_asm", "macros"... The folders "include", "macros" and "vardefs" are needed for the assembler, independent of the used operating system. The other folders contains examples and (unfortunately few) documentation.

Importantly: One should copy oneself in addition the Lynx hardware documentation, which with Bastian send on the side is, locally on the non removable disk. Apart from some funny remarks on the Entwicklungsgeschichte is there (nearly) a complete list of the hardware addresses, documentation of sprites, spriteengine, audio system , and further. I will refer to it in the following.

Unpack the archives of Matthias Domin. You will find translated programs for the PC plus sample sources.

After depacking, put all executables into one folder, e.g. "c:\lynx\lynx_dev". This path should then be included into the PATH variable. The C libraries should be in that folder, too.

The last thing to to is unpack Handy. Overwrite the dummy "lynxboot.img" by a valid one. Copy the make_lnx program into the "lynx_dev" the folder.

Note:

Nearly all programs are command line programs. It ist therefore recommended, to use a DOS (or any other terminal) window. The scrollbuffer should at least consist of 50 lines, so you can read any (error) messages of the programs.


 

"Hello World!" in C

Preface: One should avoid to give the same basenames to different files e.g. hallowelt.cpp and hallowelt.bmp, since otherwise the object files get the same name in each case (hallowelt.obj). Furthermore some programs have problems with more than "8+3" characters per filename. The file names should not be longer thus.

Maybe this sounds silly, but a "Hello World!" program is not that easy to write on the Lynx. Thit is because of the fact that there is no simple text funcion like printf or writeln. Therefore we simply show a picture/sprite with "Hello World!" on it. This should be enough.

First, use a simple graphic program to create a 16 color picture about the size of 100*30 pixel and write "Hello World!" in it. Store the picture as a 16 color BMP file. Next step is to convert the picture in a Lynx Sprite. Use the program SPRPCK for that. The command for that is like: "sprpck -t6 v -s4 -p0 hallobmp.bmp hallobmp.spr". The parameters will be explained, if SPRPCK is called without parameters. The parameter -p0 ensures that a C-Objektfile is produced.

Next we convert the C-Objektfile into a library file. This happens with LIBR65 . The command line looks about in such a way: "libr65 A sprites.olb hallobmp.obj"

Now follows the main program, just call it "hallo_c.cpp".

/**************  * hallo_c.cpp *  **************/  /* first (as ever) the Includefiles */  #include <lynx.h>    extern char clsSCB[]; /* for clearing the screen */  extern char SpriteHallo[]; /* Hello World! Sprite */    #asm  ; sprite used to clear the screen           dc.w 0                          ; collision depository  _clsSCB: dc.b $c0,$90,$00                           dc.w 0,cls_data           dc.w 0,0                        ; X,Y           dc.w 160*$100,102*$100          ; scaled to 160*102 pixels           dc.b $00    ; data: 1*1 pixel  cls_data        dc.b 2,$10,0    ; Import -> external Libraryfile  xref _hallobmp             dc.w 0             ; collisionflags (not used)  _SpriteHallo:           dc.b $c1, $10, $00 ; spriteparameter           dc.w 0             ; pointer to next sprite (=0 not used)           dc.w _hallobmp     ; pointer to spritedata           dc.w 40, 30        ; x,y position           dc.w $100,$100     ; x,y scaling           dc.b $01,$23,$45,$67,$89,$ab,$cd,$ef ; sprite index -> coloratable index    #endasm    /* clear screen with a black sprite */  void CLS(void)  {      DrawSprite(clsSCB);  }    /* "Standard" palette */  char pal[]={      0x00,0x00,0x08,0x08,0x00,0x00,0x08,0x0C,0x08,0x00,0x0F,0x0F,0x00,0x00,0x0F,0x0F,      0x00,0x08,0x00,0x08,0x80,0x88,0x80,0xCC,0x88,0x0F,0x00,0x0F,0xF0,0xFF,0xF0,0xFF};    void main(void)  {      InitIRQ();        /* set up buffer : display buffer=render buffer, no collision buffer */      /* renderbuffer=displaybuffer => no doublebuffering */      SetBuffers(0xc000, 0xc000, 0x0);        /* set the palette */      _SetRGB(pal);        CLI; /* allow interrupts */        CLS(0); /* clear screen */        DrawSprite(SpriteHallo);        while(1){};/* Infinite */  }

The program is translated using CC65. The resulting sourcecode is optimized using XOPT. Next the assembler RA65 follows. (Attention, this is not another assembler than the LYXASS mentioned above. It produces another object format!)

Afterwards the components are linked with LINK65 .

To simplify this, you can use scripts and/or batch files. For this example a batch file would look in such a way (the parameters for include file path, run time environment, stack size etc. are omitted for simplification):

rem ---- Compiler ----  cc65.exe hallo_c.cpp  rem ---- Optimizer ----  xopt.exe hallo_c.m65  rem ---- Assembler ----  ra65.exe hallo_c.m65  rem ---- Linker ----  link65.exe -o hallo_c.o hallo_c.obj sprites.olb lynx.olb c.olb

You can start the resulting "*.o" file with HANDY or send it directly to the Lynx. It should look like:

screenshot1

 

"Hello World!" in assembler

Showing text with the assembler is simpler than in C, since a finished text function already exists here. Nevertheless the first example here is to show a small sprite with the text "Hello World!". This is for better comparison.

Create s picture, save it as 16 color BMP. Convert it using SPRPCK. This time the parameters are slightly different: "sprpck -t6 -v -s4 hallobmp.bmp hallobmp.spr" (no -p0 here!). The created "*.spr" can then included with ibytes into the assembler source code. For the following program it is meaningful to set the point of action within the Sprites approximately into the center. This happens with the parameter "-a" (to set a point on x-offset 40 and y-offset 9 the parameter is "-a040009").

Now the main source, call it "hello_a.s". The main strength of the assembler programs is the large selection of prefabricated macros. In the first step the necessary macros and variable definitions are loaded. Afterwards the declaration of the global variables followed by the Zeropage variables and the memory variables. The program starts at the run macro, LOMEM meant that the lowest possible address is used. It is possible to specify a start address. Afterwards follow some macros, which initialize the hardware (in C these functions are invisibly integrated in the startupcode). The next lines should be also self-describing. The two red marked lines set the offset of the visible screen in the virtual spriteuniverse. Later more about that. The two loops increase the x-position of the Sprites and increase/degrade the y-enlargement.

***************  * hallo_a.asm *  ***************    * include macros      path "c:\lynx\alles"      include <hardware.asm>      include "help.mac"      include "if_while.mac"      include "font.mac"      include "window.mac"      include "mikey.mac"      include "suzy.mac"      include "irq.mac"      include "debug.mac"      path        MACRO SHOW      LDAY \0      jsr DrawSprite      ENDM    * include variables      path "c:\lynx\alles"      include "help.var"      include "font.var"      include "window.var"      include "mikey.var"      include "suzy.var"      include "irq.var"      path    BEGIN_ZP  ; Zeropagevariables  END_ZP    BEGIN_MEM          ALIGN 4 ; screen must beginn on a double-even adress  screen0         ds SCREEN.LEN  irq_vektoren    ds 16  END_MEM        run LOMEM   ; code directly after variables  Start::      START_UP    ; start-Label needed for reStart      CLEAR_MEM   ; clear memory varaibles      CLEAR_ZP +STACK ; clear zeropage ($000) and stack ($100) too        INITMIKEY   ; initialize Mikey und Suzy      INITSUZY      FRAMERATE 60 ; Screenrefreshrate      SETRGB pal   ; Standardpalette      INITIRQ irq_vektoren         cli ; allow interrupts      SCRBASE screen0,screen0 ; set screenadresses      CLS #0 ; clear screen      lda #40 ; screen offset inside the virual spriteworld      sta $fc04 ; set    .infinite1 ; Loop - increase der y-scaling      VSYNC      SHOW hallo_SCB      dec hallo_x      clc      lda #$4      adc hallo_fy      beq .infinite2      sta hallo_fy      bra .infinite1    .infinite2 ; Loop - decrease y-scaling      VSYNC      SHOW hallo_SCB      dec hallo_x      sec      lda hallo_fy      sbc #$4      beq .infinite1      sta hallo_fy      bra .infinite2      * INCLUDES        path "c:\lynx\alles"      include "hexdez.inc"      include "font.inc"      include "window2.inc"      include "irq.inc"      include "font2.hlp"      path      pal       STANDARD_PAL    * Spritedaten              dc.w 0  hallo_SCB            dc.b $C9,$10,$00            dc.w 0            dc.w hallo_data ; pointer to spritedata  hallo_x   dc.w 0          ; X position            dc.w 20         ; Y   "            dc.w $100       ; X scale by faktor 1,0  hallo_fy  dc.w $100       ; Y 1,? - fraction is changed            dc.b $01,$23,$45,$67,$89,$AB,$CD,$EF                 path "c:\lynx\kurs\hallowelt"  hallo_data   ibytes "hallobm2.spr" 

The program is translated with the LYXASS (attention, this is different assembler than teh one used by the C compiler!). The command line is: "lyxass hallo_a.asm". You can start the resulting object file ("hallo_a.o") with HANDY or send it directly to the Lynx. You should see a pulsating text, moving from right to left over the screen:

screenshot2

 

 

Hardware, Mikey, Suzy and structure of memory

The Lynx consists of two main processors, Mikey and Suzy.

Mikey is the main processor. The core consists of an extended 65C02. The bit manipulation instructions rmbx, smbx, bbsx and bbrx are missing in the Lynx 1.

Suzy is the graphics processor. It draws among other things the sprites, zooms them, unpacked them, check for sprite collisions and more. In addition it includes a hardware multiplication and division.

Structure of memory:

The system memory size is 65536 bytes ($10000). The first 256 bytes ($00-$ff) are the "Zeropage", this range can be accessed by the processor one clock cycle faster. Often used variables should one therefore here placings. The next 256 bytes ($100-$1ff) are the processor stack. The first value, which is put on the stack, is written in $1FF, the stack grows then downward. (remark: C compiler and/or the compiled programs uses a second stack, too!). At the upper end of the memory (starting with $FC00) are the hardware addess ranges for Suzy and Mikey access. The last eight bytes are the processor status registers. In order to save memory, you can prevent the hardware accesses. With exception of two bytes ($FFF8 and $FFF9) the whole hardware address range can then be used as memory. The BLL debugger for example uses thas address range. The register to switch these addresses on and off is called MAPCNTL and lies at $$FFF9. See the BLL Doku for more information.

Adress

Description

0000 thru 00FF

Zeropage

0100 thru 01FF

Stack

0200 thou FBFF

RAM

FC00 thru FCFF

SUZY Hardware Access

FD00 thru FDFF

MIKEY Hardware Access

FE00 thru FFF7

(Boot-) ROM

FFF8

Reserved

FFF9

Memory Map Control

FFFA, FFFB

CPU NMI Vector

FFFC, FFFD

CPU Reset Vector

FFFE, FFFF

CPU Interrupt Vector



These address ranges affect only CPU accesses from Mikey. The video and refresh generators in Mikey and the sprite engine in Suzy see the entire 64K byte range as RAM. This means, you can put the screen memory "under" the hardware addresses to save memory.

The game ROM can be up to 16 megabit (= 2 MB) large. Most games however use only 256kB or 512kB.


Display and graphics engine

The display has 160*102 pixel. For a pixel a half byte at memory is needed internally. The screen memory is thus 8160 bytes (= $1F40 byte) largely. The memory is not flat-oriented (in contrast to the ST ) . That is, in the first byte of the memory the value of the first pixel lies on the screen, in the lower Nibble the value for the second pixel in the upper Nibble. These values are the indices of the farbpalette. This gives then the actually represented color on (as with the ST). Each color chart entry has for in each case red, green and blue 16 possible gradations ($0 to $$F). That results in altogether 4096 possible colors. Changes of the color chart become immediately effective. One can use thus (as also with the ST) each line another color chart. Thus one comes on (theoretically) nevertheless already more than at the same time 1600 representable colors. The image repetition frequency can relatively freely between 10(!) and about 78 cycles per second to be adjusted. Under 50Hz one should not go however, it flickers otherwise. In addition one can deactivate the display also completely.

Although it would be easy because of the documented and simple structure of the screen memory, directly the memory to manipulieren, it is to be let make usually more recommendable the Spriteengine that. Usually is faster! In addition one has then no problems to ship the memory under ROM/hardware addresses.


Besonderheiten des Displays

Das Display hat 102 Zeilen und 160*3=480 Spalten. Die Spalten sind immer abwechselnd Rot, Grün, Blau. Je drei Spalten zusammen ergeben also ein Pixel. Das heisst, wenn man zwei dünne, vertikale Linien (z.B. Rot und Blau) hat, welche aneinandertreffen, so scheinen sie doch horizontal leicht nebeneinander zu liegen. Also würde die linke Linie auf dem Display eher wie die Linie(n) rechts aussehen.

Das Display "verzieht" die Farben. Das heisst, wenn man helle Farben auf schwarzem (oder dunklem, einfarbigen Untergrund) verwendet, sieht ein solches Bild

auf einem echten Lynx Display etwa so aus:

Leider sieht man solche "Designfehler" erst wenn man ein Spiel auf einem realen Lynx testet. Handy verschmiert die Farben ja nicht.


Sprites

Suzy has a virtual Spriteuniversum of 512*512 pixels. The Spritepositionen can be indicated as 16 bits, the hardware supported however only the lowest nine bits wants one the full 16 bits to thus really use must one on the basis the upper eight bits themselves for visibility examine. In the reality thus no large restriction, who wants "to indicate" computing time thereby verplempern around Sprites far outside of the screen to lie. In addition offers the processor a flag, which indicates whether the Sprite lies outside of the screen.

Each Sprite consists of two parts, the SCB (SpriteControllBlock) and the actual Spritedaten. The Spritedaten e.g. of SPRPCK generates the SCB describes the characteristics of the Sprites. It is as follows developed:

SpriteControlBlock:  BYTE - SPRCTL0 -> see $FC80   BYTE - SPRCTL1 -> see $FC81   BYTE - SPRCOLL -> see $FC82  WORD - SCBNEXT - pointer to next SCB or zero for last   WORD - pointer to Spritedata  WORD - X position Sprite  WORD - Y position Sprite  * WORD - Horizontal scaling $100 = 1,0 (8 bits fixed point)   * WORD - Vertikal scaling   * WORD - Stretch   * WORD - Tilt   * BYTE - color table allocation, depending upon depth of shade 1, 2, 4 or 8 bytes  (* = only if activated)

Like exactly the whole functioned interested here rather, we do not use the prefabricated routines for first anyway. The values load the routine DrawSprite (and/or the macro SHOW) from the handed over SCB into the appropriate registers. Then Suzy draws Sprites until it discovers a SCBNEXT von Null. The data marked by one * are optional. If the appropriate values not when "RELOAD" are characterized in SPRCTL1, (= must) one can omit them. Also the pallet is recyclebar, i.e. if one draws successively two Sprites with the same pallet allocation, one can set second "STAGGERED ARRAY with PALLET", then the appropriate entries are not loaded. The whole goes then naturally also faster! In addition, staggered array means that the values must be before reasonably initialized. For Tilt and Stretch that is the case (= 0), but the H&V scaling is initialized not with $100. If one does not indicate a scaling thus in the first Sprite, the result looks very large.

Screen position change:

One can specify (seen as above in the example already) the position of the screen (off set) in the Spriteuniversum. In addition serve the four registers $$FC04 to $$FC07.

Collision inquiry:

In order to activate the collision inquiry, one must indicate first the memory for the Kollisionsbuffer. Then one sets the flags into the SCBs accordingly. Additionally one has still the possibility of experiencing the result of the collision inquiry. In addition one must communicate Suzy however, where will hang-write the result is. One indicates for it the OFFSET on the start of the SCB (thus e.g. -1=$ffff for the byte in the memory directly before it) in the registers $$FC24/$FC25.

Stretch and Tilt

If in SPRCTL2 activates, one can the Sprites horizontal additionally by means of Stretch "widen" and/or the starting position by means of Tilt change. Try out best simply. If the Actionpoint is in the center of the Sprites, that results in interesting effects!

Color chart allocation

Example (Sprite with 4 indices):

dc.b $51, $2F

Meant that the color $5 from the color chart is assigned to the index value $0 of the Sprites. Similar to $1->$1, $2->$2 and $3->$F

There are further explanations in addition in the Lynx Doku.

The color chart

The actual color chart consists of 2*16 byte. In the first 16 bytes the green values of the color are put down in the Lo-Nibble, the upper Nibble are unused in each case. In the next 16 bytes red and blue are defined. In the rear-Nibble blue and in the Lo-Nibble red. The farbwerte can be enough thus from 0 to 15 ($$F =). That gives a maximally possible Fabraum of 4096 colors.


Sound and Samples

The Lynx has four eight bits (mono) digital-analog converter. These can be distributed (with the Lynx 2) freely on left and right ear. For each transducer a pseudo random Noise generator exists. Everyone is supplied with its own timer (and timer has the Lynx definitely enough!). The Noisegeneratoren makes computing time-saving expenditure for noise possible (e.g. engine and explosion noise). In addition, by the since transducers digital sound is possible. The data must be written however by hand into the registers (no DMA (direct memory access) as with the STE). Since this happens in the timer interrupt, computing time consumption is relatively high. In C do not exist into "lynx.olb" the functions SmpInit, SmpStart, SmpStop and SmpActive (dependent on it which "lynx.olb" one used) < the routines are functional! >. These routines load the Samples from the RAM. In the BLL kits are also assembler SOURCE codes, with which Samples are loaded and played directly from the ROM. The SAM polarizing structure and/or SAM polarizing header is further down avowed. The program WAV2LSF converts Samples suitably.


Multiplication and division

Since multiplication and division on that would have to be accomplished 6502 "by hand", Susy built multiplication and division directly in the chip. The SOURCE registers are in each case 16 bits, the result 32 bits Vozeichenbehaftetes and unsigned counting are possible. How that exactly functioned and the unit has which errors, one rereads best in the Lynx Doku.

 

Overview of the utilities:

[edit: most of these utilities can be downloaded from Retro Isle's Lynx Software page here]

  1. LYXASS

    LyxAss is the macro assembler of the BLL kit. It translates assembler sourcecode files into executable programs ("*.o" files). They can be run by Handy or sended to a Lynx equipped with a BLL cartridge).

  2. CC65 and/or newCC65

    The 6502 C compiler. It translates C sourcen into RA65 ("*.m65") assembler source code. Inline assembler code is possible.

  3. XOPT

    This program optimizes the assembler source codes produced by C compiler. The files are saved with the same name.

  4. RA65

    The assembler belonging to the C compiler. It makes object files ("* obj") from assembler source ("*.m65").

  5. LINK65

  6. LIB65

  7. LYNXER

    The LYNXER assembles out "*.o" files and other files for ROM images ("*.lyx"). If one a only one "*.o" wants to convert file into a ROM image, is simply "more lynxer the syntax test.o". However the LYNXER loads then still another file "insert.o", these must in the same listing lie.

  8. MAKE_LNX

    Thus one these Rome images (*.lyx) in HANDY to start can convert, must one her with small utility ( which HANDY is attached) into "*.lnx" files. One can indicate additional information (e.g. portrait format, landscape format, author, copyright...), which is also stored into the file. 16 files (start Logo, program, +14 further) are maximally possible.

  9. SEND_PC and/or. SEND_OBJ

    With this program "* o" are sent over serial ComLynx Seriell-ComLynx-Adapeter to the Lynx.

  10. SPRPCK

    With this program pictures ("*.pcx", "*.bmp"...) are converted into Spritedaten. When desired either as Binary or as "*.obj" file. Also pallet information is spent. The program offers even the option to convert whole picture sequences. In the PC version all indicated formats cannot be read because of Lsb msb nose (e.g. NEO does not go). In addition only the furthest common BMP type is supported.

  11. HANDY

    The Lynx emulator for PC, Linux and Mac. There exists a Debug version with the possibility to view CPU register, Code and RAM/ROM. Handy needs a valid "lynxboot.img".


Problems with C compiler?

C compiler needs additional blank line after the last line still another, otherwise there are strange error messages! Caution with Inline assembler. The function calls are very much complicated in C. Bug: cc65 does always signed division.


Problems with LyxAss?

Before the variable and label names no blanks may stand! That meets also also those through BEGIN_ZP/BEGIN_MEM and END_ZP/END_MEM limits ranges too.


Problems with "path" the indication and LyxAss?

At least in wind OS the stringer of the second indication of path is attached to first. This leads to not dissolvable paths (file one does not find). To go around one can that by an empty indication of path. This deletes the past indication of path completely.

Example:
  path "C:\LYNX\DEVELOP\INCLUDES"     include "file.inc"     path ;  This is important here!!!      path "C:\LYNX\DEVELOP\MACROS"     include "file.mac" 

Too little memory?

  • In C programs one can use additionally the memory between $200 and $3FF (with $400 the program normally begins).

  • C stack on the most necessary make smaller (when linking).

  • The Bildschirmbuffer (Renderbuffer, Kollisionsbuffer)"under" the ROM put (if one does not want to write directly into the screen memory...).

  • One can use (if still which is free) the Zeropage.

  • The Sprites and Samples pack.

  • Debuggers and BLL Loader deactivate.

  • From C to assembler transfer; -)

  • Completely bad nature use still (hopefully did not use) the remainder of the processor stack with $100 upward.

  • The code, Sprites, Samples... in the ROM page out and if necessary load.

 

 

  •   .