65c02 Assembly programming for
the Atari Lynx
The Atari Lynx uses the 65c02...
coupled with a 16 bit GPU, the Atari Lynx is was far more powerful
than the Gameboy and Sega Master system
Although it's large size, and high power draw made it a commercial
failure, it's an interesting system to develop for!
The official cartridges are encrypted, but we can make a binary that
the emulator Handy can run quite easily!
Unlike most other systems, the Lynx does not have a tile array, a
chunk of about 8k of internal memory is used for the screen, and we
can write bytes directly to it (like on the CPC)... the hardware
sprite function also writes to this buffer, so we have no effective
sprite limit!
|
 |
 |
 |
There were two models of the atari
Lynx, but there's no real difference between them.
Cpu |
4mhz 65C02 |
Ram |
64k |
Vram |
Uses internal memory |
Resolution |
160x102 |
Sprites |
Unlimited... blits sprites to buffer in system memory |
Tilemap |
None |
Colors |
16 onscreen from 4096 |
Sound chip |
4 channel stereo |
|
 |
ChibiAkumas Tutorials
Memory Map
From
|
To
|
Use |
$0000 |
$00FF |
Zero Page |
$0100 |
$01FF |
Stack |
$0200 |
$FBFF |
Program RAM |
$FC00 |
$FCFF |
Suzy |
$DF00 |
$FDFF |
Mikey |
$FE00 |
$FFF7 |
ROM |
$FFFA |
$FFFB |
NMI Address |
$FFFC |
$FFFD |
Reset Address |
$FFFE |
$FFFF |
IRQ Address |
Binary file Header
While real cartridges are encrypted (causing copyright problems
for the hobbyist), the Handy emulator can work with an unencrypted O
file... this also means we do not need an official rom to run the
emulator!
There is a 10 byte header...
FixedBytes:Many
of
the bytes are fixed, and should not be changed
StartPoint:
Bytes 2 and 3 are the startpoint in Big Endian format.. . in our
example the first program byte is $0300
Length:
Bytes 4 and 5 are the length of the file. |
|
Hardware Sprites
Unlike other systems, Lynx hardware sprites are not an extra layer! the
'Suzy' graphics chip draws the sprite into the Vram area of the 6502's
addressable range
This may leave you wondering why not just do our sprites in software with
the 6502... but the Suzy chip is VERY fast... it's a 16mhz 16 bit chip...
and can even do dynamic scaling of sprites!
Sprites for the Suzy chip have to be held in RAM, and need a 'Sprite control
block' to define the drawing of a sprite... this pointer is passed to the
Suzy chip to get it to draw a sprite
Sprites can be 'Literal' (plain bmp) or 'RLE compressed' (defined
by bit 7 of byte two of the SCB - SCBCTL1).... the colordepth is
defined in SPRCTL0 (See later)
Each line of a sprite starts with a byte - this an offset to the
next line... effectively the number of bytes in the line +1
.... effectively the pointer to the next line.
1 or 0 in this position have special meanings!... 0 means the end of
the sprite... 1 means the end of the 'quardrent'... note this is
optional! Akusprite does not use it!
Quadrent rendering is where the sprite is drawn in 4 sections from
the middle... with a 1 byte marking each 1/4 of the sprite...
(followed by another 'offset to next line' byte)
the first quadrent is DownRight (default)... the second quadrent is
UpRight
the third quadrent is UpLeft)... the fourth quadrent is DownLeft
Apparently there is a bug in the hardware - the last bit of each
line must be 0! - we should always have a 0 at the end of our
sprites to counter it - color 0 is transparent anyway!
You can see a Literal Sprite to the right...
the Literal bitmap data is in green, and the header bytes are
in cyan |
Literal
Sprite Example (BMP)
LynxSprite:
db $8,
$11, $11, $11, $11, $11, $10,0
db $8, $10,
$0, $0, $0, $0, $10,0
db $8,
$10, $04, $44, $44, $0, $10,0
db $8, $10,
$04, $3, $04, $0, $10,0
db $8, $10,
$04, $3, $04, $0, $10,0
db $8, $10,
$04, $44, $44, $0, $10,0
db $8,
$10, $0, $0, $0, $0, $10,0
db $8, $11,
$11, $11, $11, $11, $10,0
db 0 |
RLE Sprite
Data is a bit more tricky....
The first byte in a line is again an offset to the next line as
before
The next BIT will be a 'block definition'... defining what the
following data is...
1 marks that the next data will be LITERAL
0 marks that the next data will be RLE
The next 4 bits will be the number of pixels to
draw-1... so 0 means 1 pixel, and 15 means 16 pixels... we will call
this N
If the block is RLE
the next 1/2/3/4 bits (depending on bitdepth) will be used for the
color to fill the next N
pixels
If the block LITERAL
the next N
*(1/2/3/4) bits (depending on bitdepth) will be used for the color
of the next N pixels
the next bit will be the next 'block definition'... this pattern
repeats until the line is done. |
4bpp
RLE Sprite Example
db $8
(offset to next line)
db %01111000,%00000000
(RLE block...16 pixels...
Color 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
db %00001001,%10000000
(RLE block...2 pixles...
Color 3,3)
db %10010000,%10010001,%10000000
(Literal block...3
pixels... Color 1,2,3)
(next line starts here) |
The Sprite Control Block
The sprite control block defines the
sprite onscreen, The first two bytes define the sprite type, how
it's drawn, and the other data in the block... note if RR<2
we don't need the Scale or Tilt words... so these can be
removed.
SPRCTL0 bits 7,6 defines the Colordepth 1/2/3/4 bits per pixel for
2/4/8/16 colors
SPRCTL1 bit 7 defines the sprite type 1=Literal... 0=RLE
Note... if you have the BPP wrong on a RLE sprite it will be a total
mess... a Literal sprite would just be stretched and a bit weird!
(colors would be sort of 'dithered')
The Xpos and Ypos are relative to the defined screen boundaries...
Wid and Hei are scales $100=100% $200=200%
The 'Palette' maps each 'color' in the sprite to a palette
setting... this is most useful for 1/2bpp sprites - where we will
need to select the colors each combination of bits will use. the
example shown is a 4bpp 16 color sprite
|
SCB:
;BBHV-TTT -
SPRCTL0... B=bits per pixel H=hflip V=vflip T=type (7=normal)
db %11000101
;LSRRPSUl - SPRCTL1... L=Literal S=Sizing choice (0 only!)
RR=Reloadable depth (1=Use Size 3=Use Size,ScaleTilt)
db %10010000
;P=Palette
reload (0=yes) s=skipsprite u=draw up l=draw left
db 0 ;- SPRCOL - 0= OFF
dw 0
;Next SCB (0=none)
dw LynxSprite
;Sprite
pointer
dw 10
;Xpos
dw 10
;Yos
dw $200 ;Wid ($100 = 100%)
dw $200 ;Hei ($100 = 100%)
; dw 0
;Scale - not needed if
B4,B5 of SPRCTL<3
; dw 0
;Tilt - not needed if
B4,B5 of SPRCTL<2
;Palette - maps nibbles to colors (useful for
<4 bpp)
db
$01,$23,$45,$67,$89,$AB,$CD,$EF |
Sprite Drawing
Lets Draw a sprite... if our visible
screen is at &C000 - the following will work!
Note when setting 16-bit Suzy values, we must set the LSB before the
MSB...
A write to the LSB will ZERO the
MSB automatically... so if we set FC09 first in this example it
WILL NOT WORK!
|
lda #$0
;MUST SET LSB FIRST!
;WRITE TO LSB will ZERO MSB
sta $FC08
;For sprites
lda #$C0
;Set screen ram pointer to $C000
sta $FC09
;For sprites
lda #<(SCB)
sta $fc10
ldy #>(SCB)
sty
$fc11
lda #$5
; 1 SprStart + 4 Everon
sta $FC91
;SPRGO
sta $FD90 ;SDONEACK
WaitSuzy:
stz $FD91 ;CPUSLEEP
lda $fc92
;DISPCTL
lsr
bcs WaitSuzy
stz
$FD90 ;SDONEACK |
If we want our sprites to clip at the top left, we can set a
screen offset - for example to set an offset of 8:
|
LDA #8
STA $FC04
;SCR OFFSET
STA $FC06
;SCR OFFSET |
Hardware Ports Memory Map
The Lynx memory map is pretty simple, the Zeropage is at $00xx , the Stack
is at $01xx....
addresses from $0200-$FC00 are free for ram... we need to allocate $2000 for
a screen buffer, which can be anywhere in ram we want (defined in address
$FD94-$FD95), but the rest is ours to use!
Note: Cartridge Rom is not memory mapped, it acts more like a 'disk drive'
which we have to access like an external system... which is annoying!
We need to use the hardware registers to control, and read from the devices
attached to the system, they are listed below:
From |
To |
Name |
Description |
Bits |
Meaning |
FC00 |
FC01 |
TMPADRL |
Temporary
Address LH |
|
|
FC02 |
FC03 |
TILTACUM |
Accumulator
for tilt value LH |
|
|
FC04 |
FC05 |
HOFF |
Offert to H
edge of screen |
|
|
FC06 |
FC07 |
VOFF |
Offert to V
edge of screen |
|
|
FC08 |
FC09 |
VIDBAS |
Base address
of video build buffer |
|
|
FC0A |
FC0B |
COLLBAS |
Base address
of Coll build buffer |
|
|
FC0C |
FC0D |
VIDADRL |
Current Video
Build Addres |
|
|
FC0E |
FC0F |
COLLARL |
Current
Collision Build Address |
|
|
FC10 |
FC11 |
SCBNEXT |
Address of
next SCB |
|
|
FC12 |
FC13 |
SPRDLINE |
Start of
Sprite Data Line Address |
|
|
FC14 |
FC15 |
HPOSSTRT |
Starting Hpos |
|
|
FC16 |
FC17 |
VPOSSTRT |
Starting Vpos |
|
|
FC18 |
FC19 |
SPRHSIZ |
Hsize |
|
|
FC1A |
FC1B |
SPRVSIZ |
Vsize |
|
|
FC1C |
FC1D |
STRETCH |
H Size Adder |
|
|
FC1E |
FC1F |
TILT |
H Position
Adder |
|
|
FC20 |
FC21 |
SPRDOFF |
Offset to
next sprite data line |
|
|
FC22 |
FC23 |
SPRVPOS |
Current Vpos |
|
|
FC24 |
FC25 |
COLLOFF |
Offset to
collision depository |
|
|
FC26 |
FC27 |
VSIZACUM |
Vertical Size
Accumulator |
|
|
FC28 |
FC29 |
HSIZOFF |
Horizontal
size offset |
|
|
FC2A |
FC2B |
VSIZOFF |
Vertical Size
Offeet |
|
|
FC2C |
FC2D |
SCBADR |
Address of
current SCB |
|
|
FC2E |
FC2F |
PROCADR |
Current Spr
data Proc Address |
|
|
FC80 |
FC80 |
SPRCTRL0 |
|
BBHV-TTT |
B=bits per
pixel
H=hflip V=vflip
T=type (7=normal) |
FC81 |
FC81 |
SPRCTRL1 |
|
LSRRPSUl |
L=Literal
S=Sizing choice (0 only!)
RR=Reloadable depth
P=Palette reload (0=yes)
s=skipsprite u=draw up l=draw left |
FC82 |
FC82 |
SPRCOLL |
|
|
|
FC83 |
FC83 |
SPRINT |
|
|
|
FC88 |
FC88 |
SUZYHrev |
Suzy Hardware
Revision R |
|
|
FC89 |
FC89 |
SUZYHrev |
Suzy Hardware
Revision W |
|
|
FC90 |
FC90 |
SUZYBUSEN |
Suzy bus
enable FF |
|
|
FC91 |
FC91 |
SPRGO |
Sprite
Process start bit |
---E-S |
S=Sprites on
E=Everon detector(?) |
FC92 |
FC92 |
SPRSYS |
System
Cotrlol Bits (RW) |
|
|
FCB0 |
FCB0 |
JOYSTICK |
Read Joystick
and Switches |
UDLR12IO |
|
FCB1 |
FCB1 |
SWITCHES |
Read other
switches |
-----CCP |
|
FCB2 |
FCB3 |
RCART |
Rcart (RW) |
|
|
FCC0 |
FCC0 |
LEDS |
Leds (W) |
|
|
FCC2 |
FCC2 |
PPT |
Paralell port
Status RW |
|
|
FCC3 |
FCC3 |
PPT DATA |
Paralell port
Data RW |
|
|
FCC4 |
FCC4 |
Howie |
Howie (RW) |
|
|
FD00 |
|
TIM0BKUP |
HTIMBKUP
Timer 0 backup value (Hblank) |
|
Count +1 |
FD01 |
|
TIM0CTLA |
HTIMCTL0
Timer 0 static control (Hblank) |
ID-RCSSS |
I=enable
Interrupt, D=Reset Timer Done R=enable Reload C enable Count
S=Clock Select |
FD02 |
|
TIM0CNT |
Timer 0
current count (Hblank) |
|
Timer Current
Value |
FD03 |
|
TIM0CTLB |
Timer 0
dynamic control (Hblank) |
---DLIO |
D=timer Done
L=Last clock I=borrow In O=borrow Out |
FD04 |
|
TIM1BKUP |
MAGA Timer 1
backup vatue |
|
Count +1 |
FD05 |
|
TIM1CTLA |
Timer 1
static control |
IDMRCSSS |
I=enable
Interrupt, D=Reset Timer Done M=Magmode (1,3,5,7 only) R=enable
Reload C enable Count S=Clock Select |
FD06 |
|
TIM1CNT |
Timer 1
current count |
|
Timer Current
Value |
FD07 |
|
TlM1CTLB |
Timer 1
dynamic control |
---DLIO |
D=timer Done
L=Last clock I=borrow In O=borrow Out |
FD08 |
|
TlM2BKUP |
VTIMBKUP
Timer 2 backup value (Vblank) |
|
Count +1 |
FD09 |
|
TIM2CTLA |
Timer 2
static control (Vblank) |
ID-RCSSS |
I=enable
Interrupt, D=Reset Timer Done R=enable Reload C enable Count
S=Clock Select |
FD0A |
|
TIM2CNT |
Timer 2
current count (Vblank) |
|
Timer Current
Value |
FD0B |
|
TIM2CTLB |
Timer 2
dynamic control (Vblank) |
---DLIO |
D=timer Done
L=Last clock I=borrow In O=borrow Out |
FD0C |
|
TIM3BKUP |
MAGB Timer 3
backup value |
|
Count +1 |
FD0D |
|
TIM3CTLA |
Timer 3
static control |
IDMRCSSS |
I=enable
Interrupt, D=Reset Timer Done M=Magmode (1,3,5,7 only) R=enable
Reload C enable Count S=Clock Select |
FD0E |
|
TIM3CNT |
Timer 3
current count |
|
Timer Current
Value |
FD0F |
|
TIM3CTLB |
Timer 3
dynamic control |
---DLIO |
D=timer Done
L=Last clock I=borrow In O=borrow Out |
FD10 |
|
TIM4BKUP |
BAUDBKUP
Timer 4 backup value |
|
Count +1 |
FD11 |
|
TIM4CTLA |
Timer 4
static control |
ID-RCSSS |
I=enable
Interrupt, D=Reset Timer Done R=enable Reload C enable Count
S=Clock Select |
FD12 |
|
TIM4CNT |
Timer 4
current count |
|
Timer Current
Value |
FD13 |
|
TlM4CTLB |
Timer 4
dynamic control |
---DLIO |
D=timer Done
L=Last clock I=borrow In O=borrow Out |
FD14 |
|
TIM5BKUP |
MAGC Timer 5
backup value |
|
Count +1 |
FD15 |
|
TIMSCTLA |
Timer 5
static control |
IDMRCSSS |
I=enable
Interrupt, D=Reset Timer Done M=Magmode (1,3,5,7 only) R=enable
Reload C enable Count S=Clock Select |
FD16 |
|
TIM5CNT |
Timer 5
current count |
|
Timer Current
Value |
FD17 |
|
TIMSCTLB |
Timer 5
dynamic control |
---DLIO |
D=timer Done
L=Last clock I=borrow In O=borrow Out |
FD18 |
|
TIM6BKUP |
Timer 6
backup value |
|
Count +1 |
FD19 |
|
TIM6CTLA |
Timer 6
static control |
ID-RCSSS |
I=enable
Interrupt, D=Reset Timer Done R=enable Reload C enable Count
S=Clock Select |
FD1A |
|
TIM6CNT |
Timer 6
current count |
|
Timer Current
Value |
FD1B |
|
TIM6CTLB |
Timer 6
dynamic control |
---DLIO |
D=timer Done
L=Last clock I=borrow In O=borrow Out |
FD1C |
|
TIM7BKUP |
MAGD Timer 7
backup value |
|
Count +1 |
FD1D |
|
TIM7CTLA |
Timer 7
static control |
IDMRCSSS |
I=enable
Interrupt, D=Reset Timer Done M=Magmode (1,3,5,7 only) R=enable
Reload C enable Count S=Clock Select |
FD1E |
|
TIM7CNT |
Timer 7
current count |
|
Timer Current
Value |
FD1F |
|
TIM7CTLB |
Timer 7
dynamic control |
---DLIO |
D=timer Done
L=Last clock I=borrow In O=borrow Out |
FD20 |
FD20 |
|
Audio Channel
0 � 2�s compliment Volume control |
|
0-127
|
FD21 |
FD21 |
|
Audio Channel
0 � Shift register feedback enable |
|
eg %00010000
|
FD22 |
FD22 |
|
Audio Channel
0 � Audio Output Value (Raw Data) |
|
Eg $80
|
FD23 |
FD23 |
|
Audio Channel
0 �Lower 8 bits of shift register |
|
Eg 0
|
FD24 |
FD24 |
|
Audio Channel
0 � Audio Timer Backup Value |
|
eg 0-63
|
FD25 |
FD25 |
|
Audio Channel
0 � Audio Control Bits
|
FTIRCKKK |
eg %00011110
|
FD26 |
FD26 |
|
Audio Channel
0 � Audio Counter |
|
|
FD27 |
FD27 |
|
Audio Channel
0 �Other Audio Bits |
|
Eg 0 |
FD28 |
FD2F |
|
Audio Channel 1 � Same as Channel 0
|
|
|
FD30 |
FD37 |
|
Audio Channel
2 � Same as Channel 0 |
|
|
FD38 |
FD3F |
|
Audio Channel 3 � Same
as Channel 0 |
|
|
FD40 |
FD40 |
ATTENREG0 |
LLLLRRRR �
Audio Attenuation |
|
|
FD41 |
FD41 |
ATTENREG1 |
LLLLRRRR �
Audio Attenuation |
|
|
FD42 |
FD42 |
ATTENREG2 |
LLLLRRRR �
Audio Attenuation |
|
|
FD43 |
FD43 |
ATTENREG3 |
LLLLRRRR �
Audio Attenuation |
|
|
FD44 |
FD44 |
MPAN |
Stereo
attenuation selection |
|
|
FD50 |
FD50 |
MSTEREO |
Stereo
disable |
LLLLRRRR |
0=all on
255=all off |
FD80 |
FD80 |
INTRST |
Interrupt
poll 0 |
|
|
FD81 |
FD81 |
INTSET |
Interrupt
poll 1 |
|
|
FD84 |
FD84 |
MAGRDY0 |
Mag tape
Ready Channel 0 |
|
|
FD85 |
FD85 |
MAGRDY1 |
Mag tape
Ready Channel 1 |
|
|
FD86 |
FD86 |
AUDI |
Audio In |
|
|
FD87 |
FD87 |
SYSCTRL1 |
|
|
|
FD88 |
FD88 |
MIKEYHREV |
Mikey
Hardware Revision R |
|
|
FD89 |
FD89 |
MikeySREV |
Mikey
Software Revision W |
|
|
FD8A |
FD8A |
IODIR |
Mikey
Paralell IO Data direction |
|
|
FD8B |
FD8B |
IODAT |
Mikey
Paralell data |
|
|
FD8C |
FD8C |
SERCTL |
Serial
Control Register |
|
|
FD8D |
FD8D |
SERDAT |
Serial Data |
|
|
FD90 |
FD90 |
SDONEACK |
Suzy Done
Acknowledge |
|
|
FD91 |
FD91 |
CPUSLEEP |
Cpu Bus
Request Disable |
|
|
FD92 |
FD92 |
DISPCTL |
Video Bus
Request Enable |
|
|
FD93 |
FD93 |
PBKUP |
Magic P count |
|
|
FD94 |
FD95 |
DISPADR |
Display
Address LH |
LLLLLLLL
HHHHHHHH |
Address of
video screen |
FD9C |
FD9C |
MTEST0 |
|
|
|
FD9D |
FD9D |
MTEST1 |
|
|
|
FD9E |
FD9E |
MTEST2 |
|
|
|
FDA0 |
FDAF |
Green �
Colors (0-15) |
|
0000GGGG |
|
FDB0 |
FDBF |
Blue/Red �
Colors (0-15) |
|
BBBBRRRR |
|
FE00 |
FFF7 |
ROM |
|
|
|
FFF9 |
|
Memory Map
Control |
|
C---VRMS |
C=CPU
Cycles
Ram enable:
S=Suzy Ram $FC00-$FCFF / M=Mikey $FD00-$FDFF / R=Rom $FE00-$FFF7
V=Vectors ($FFFA-$FFFF) |
FFFA |
FFFB |
CPU NMI
Vector |
|
LLLLLLLL
HHHHHHHH |
|
FFFC |
FFFD |
CPU Reset
Vector |
|
LLLLLLLL
HHHHHHHH |
|
FFFE |
FFFF |
CPU Interrupt
Vector |
|
LLLLLLLL
HHHHHHHH |
|
| |
Buy my Assembly programming book on Amazon in Print or Kindle!



Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
Buy my Assembly programming book on Amazon in Print or Kindle!



Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
Buy my Assembly programming book on Amazon in Print or Kindle!



Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
|