[stella] 20 columns of almost-readable text

Subject: [stella] 20 columns of almost-readable text
From: "B. Watson" <atari@xxxxxxxxxxxxxx>
Date: Thu, 13 Sep 2001 19:25:47 -0400 (EDT)
This is what I spent last night doing, to get my mind off the terrorist
bombings (there's nothing I can do to change the situation right now, so
obsessing over it isn't helping me sleep...)

This may have been done before, I hope not though (my ego wants to think
I invented something new, even if my id doubts it :)

Anyway, what I did, was take the old 6digit.asm from the archives and modify
it so that it only draws 5 `digits', starting around color clock 40. Each of
the `digits' is actually 2 characters wide, in a 4-bit wide font. Once I got
that working, I wrote another routine that does the same thing, starting at
about color clock 80, then called the routines on alternate frames. This gives
me a 20 character wide display area, from clocks 40 to 120, with flickering.

I did try displaying all 6 digits each frame, for 12 digits (*2 4x5 characters
per digit, would have been 24 characters), but couldn't quite get the timing
right for both frames... I may try adding the extra digit (2 chars) to one
frame or the other, for 22 characters.

The display is something like this:

Even-numbered frames:

 +----------------------------------------+
 |          0011001100                    |

Odd-numbered frames:

 +----------------------------------------+
 |                    0011001100          |

each dash represents 4 color clocks. the 0's and 1's are which player is being
displayed (NUSIZ0 is 3, NUSIZ1 is 1).

Next I created a 4x5 font with ASCII characters 41-90 (right paren thru Z),
and a blank character for a space. I wrote a not-very-optimized subroutine
that converts an ASCII string in ROM (or, I suppose, in RAM...) to 25 bytes
of display data, 5 bytes per every 2 ASCII characters. I called it once per
frame, so I could display any arbitrary 20-character string in 2 frames...

Then I tried incrementing the message pointer every 4 frames. Now I've got a
20-character wide marquee that scrolls a 128-byte message (the last 20 bytes
of which need to be spaces).

Amazingly, it worked on a real Atari the first time I tried it. So I decided
to get cute, and try to display 2 text messages, by calling the character
generation routine again during the kernel. It turns out that my non-optimised
code takes approximately 30 scanlines to execute. The display routines display
each of the 5 bytes twice, plus one empty scanline (11 scanlines total), so
I could have 5 lines of widely-spaced text for a total of 175 scanlines.

The flicker looks worse in an emulator than it does on a TV set, but it's still
pretty ugly-looking. 20x5 characters isnt much of a text display, and the 4x5
font makes it even harder to read. However... I did get the 2600 to do
something I didn't think it could do at all, and the scrolling marquee would be
kind of cool for a Star Raiders type of game (scrolling messages reading
`SHIELDS DAMAGED...PHOTONS DESTROYED...STARBASE UNDER ATTACK...' etc. etc.)

Hrmmm... with some modifications, and a lot more RAM usage, it might be
possible to do 20x4 lines of closely-spaced text, vertically. This would be
44 scanlines tall, but would eat up 100 bytes of RAM... Of course those 100
bytes are re-usable, you only need them from the time you calculate the
graphics data to the time you're done displaying it.. This could be something
like the `text window' in the graphics modes on the Atari 400/800 machines.

Another idea I had was a one-line message window, that scrolls *vertically*
instead of horizontal marquee-style. This would be tougher to do, but still
should only require 25 bytes of video RAM, plus 2 pointers (to the current
message, and to the old one that's being scrolled away).

Basically I'm just throwing out ideas, and the code... if anybody finds a use
for this, go for it. The code is not so great, could stand to be cleaned up
and made smarter (it *should* be possible to use the same kernel to display
either the left or right frame, but every time I tried I couldn't make the
timing come out right)

Enjoy,

Brian

---

If a trainstation is the place where trains stop, what is a workstation?

---begin marquee.asm---

; marquee.asm

; by B. Watson <atari@xxxxxxxxxxxxxx>

; dasm marquee.asm -omarquee.bin -f3 -v3

; Draws a 20-character scrolling marquee, with too much flicker :(

; Tested on a real Atari 2600
; Tested on z26 v1.46 on a windows machine
; Tested on xstella 1.1 on a linux machine

 processor 6502
 include "vcs.h"

 seg.u data

 org $80

framectr ds 1
digit0 ds 5
digit1 ds 5
digit2 ds 5
digit3 ds 5
digit4 ds 5
tmp0 ; is same var as tmp1
tmp1 ds 1
tmp2 ds 1
tmp3 ds 1
count1 ds 1
msgptr ds 2
fontptr ds 2
scrollctr ds 1

 echo *-$80,"bytes of zero page used."

 seg code

 org $F000

 sei
 cld
 ldx #$ff
 txs
 lda #0
iloop
 sta 0,x
 bne iloop


 lda #10
 sta COLUP0
 sta COLUP1

 lda #$B0
 sta COLUPF

 lda #3
 sta NUSIZ0
 lda #1
 sta NUSIZ1
 sta CTRLPF

; the main_loop is nasty: I just unrolled it so each time thru the loop
; draws 2 frames, each with its own separate, nearly-identical kernel.

; In a real game I'd want to figure out a way to be more clever.

main_loop
 lda #2
 sta VSYNC ; start blanking
 sta WSYNC
 sta WSYNC
 lda #44
 sta TIM64T ; go ahead & set timer
 lda #0
 sta WSYNC
 sta VSYNC ; 3 WSYNC's, then turn off VSYNC

 inc framectr
 lda framectr
 ;sta COLUPF ; flashing colors make it even harder to read!
 and #$03
 bne skip_scroll
 inc scrollctr
 lda scrollctr
 and #$7F
 sta scrollctr
skip_scroll

 lda #<message
 adc scrollctr
 sta msgptr
 lda #>message
 sta msgptr+1
 jsr decode_message ; yowza!

l_wait_timer
 lda INTIM
 bne l_wait_timer ; busy-wait for timer to expire
 sta WSYNC

l_kernel

 sta WSYNC

 repeat 14
 nop
 repend

 sta $2000+RESP0 ; 14*2+4=32


 sta WSYNC
 repeat 16
 nop
 repend
         sta RESP1 ; 16*2+3 = 35
         sta HMCLR
         lda #%10100000
         sta HMP1
         lda #%10010000;
         sta HMP0
         sta WSYNC
         sta HMOVE


 sta WSYNC
 lda #$FF
 sta PF2
 lda #$07
 sta PF1
 sta WSYNC
 sta WSYNC
 lda #$04
 sta PF1
 lda #$0
 sta PF2

 jsr draw_lhs

 sta WSYNC
 lda #$07
 sta PF1
 lda #$FF
 sta PF2
 sta WSYNC
 sta WSYNC

 lda #$00
 sta PF1
 sta PF2

 lda #<email
 sta msgptr
 lda #>email
 sta msgptr+1
 jsr decode_message
 jsr draw_lhs

 ldy #139
l_blank_pf
 sta WSYNC
 dey
 bne l_blank_pf

end_kernel

 lda #37
 sta TIM64T
l_overscan
l_finish_overscan
 lda INTIM
 bne l_finish_overscan

; begin right frame

 lda #2
 sta VSYNC ; start blanking
 sta WSYNC
 sta WSYNC
 lda #44
 sta TIM64T ; go ahead & set timer
 lda #0
 sta WSYNC
 sta VSYNC ; 3 WSYNC's, then turn off VSYNC


 lda #<[message+10]
 adc scrollctr
 sta msgptr
 lda #>[message+10]
 sta msgptr+1
 jsr decode_message ; yowza!

r_wait_timer
 lda INTIM
 bne r_wait_timer ; busy-wait for timer to expire
 sta WSYNC

r_kernel

 sta WSYNC

 repeat 21
 nop
 repend

 sta $2000+RESP0 ; 13*2+4=30


 sta WSYNC
 repeat 23
 nop
 repend
         sta RESP1 ; 14*2+3 = 31
         sta HMCLR
         lda #%11000000
         sta HMP1
         lda #%10110000;
         sta HMP0
         sta WSYNC
         sta HMOVE

 sta WSYNC
 lda #$FF
 sta PF2
 lda #$07
 sta PF1
 sta WSYNC
 sta WSYNC
 lda #$04
 sta PF1
 lda #$0
 sta PF2

 jsr draw_rhs


 lda #0
 sta GRP0
 sta GRP1

 sta WSYNC
 lda #$07
 sta PF1
 lda #$FF
 sta PF2
 sta WSYNC
 sta WSYNC

 lda #$00
 sta PF1
 sta PF2

 lda #<[email+10]
 sta msgptr
 lda #>[email+10]
 sta msgptr+1
 jsr decode_message
 jsr draw_rhs

 ldy #139
blank_pf
 sta WSYNC
 dey
 bne blank_pf

r_end_kernel

 lda #37
 sta TIM64T
r_overscan
r_finish_overscan
 lda INTIM
 bne r_finish_overscan

 jmp main_loop

; subroutines

decode1byte ; A holds ASCII char
            ; on exit, (fontptr) hold pointer to appropriate character in ROM font table
 sec
 sbc #40 ; de-ASCIIfy (our ASCII subset starts at character 41)
 bcs valid_ascii
 lda #0 ; if we don't have the character, just print a space
valid_ascii
 sta tmp1
 asl ; a=a*2
 asl ; a=a*2 again
 clc
 adc tmp1 ; now a=(original a)*5
 sta fontptr ; lo byte only, charset is <256 bytes!
 rts

decode2chars ; (msgptr),y is ASCII character 1, (msgptr),y+1 is 2nd
             ; X is index into character RAM
 lda #>font
 sta fontptr+1 ; set up hi-byte
 sty tmp2 ; hang on to it
 stx tmp3 ; tmp3 is index into character RAM
do_1st_char
 ;ldy tmp2
 lda (msgptr),y
 jsr decode1byte
 ldy #4
store_hi_nybble
 lda (fontptr),y
 sta 0,x
 inx
 dey
 bpl store_hi_nybble
do_2nd_char
 ldy tmp2
 iny
; sty tmp2
 ldx tmp3
 lda (msgptr),y
 jsr decode1byte
 ldy #4
store_lo_nybble
 lda (fontptr),y
 lsr
 lsr
 lsr
 lsr
 ora 0,x
 sta 0,x
 inx  
 dey
 bpl store_lo_nybble
 stx tmp3 ; maybe not need
 ldy tmp2 ; restore Y to what it was before we were called
 rts

decode_message ; (msgptr) should point to message
 ldx #digit0 ; X will be index into character RAM
 ldy #0 ; Y will be index into ASCII string
do_string
 jsr decode2chars ; each call eats up 2 characters of string,
 iny ; so we have 2 INY's here to keep our place.
 iny
 cpy #10
 bne do_string
 rts

draw_lhs
 ldx #4
 stx count1
; the drawloops actually do all the work. Again, I just unrolled them, so
; each time thru the loop draws the same thing on 2 consecutive scanlines.
l_drawloop
 sta WSYNC
 ldx count1 ; 3
 lda digit0,x ; +4 = 7
 sta GRP0 ; +3 = 10
 lda digit1,x ; + 4 = 14
 sta GRP1 ; +3 = 17
 lda digit2,x ; +4 = 21
 sta tmp0 ; +3 = 24
 lda digit3,x ; +4 = 28
 tay ; + 2 = 30
 lda digit4,x ; + 4 = 34
 ldx tmp0 ; + 3 = 37
 stx GRP0 ; +3 = 40
 sty GRP1 ; +3 = 43
 sta GRP0 ; +3 = 46
 sta WSYNC
 ldx count1 ; 3
 lda digit0,x ; +4 = 7
 sta GRP0 ; +3 = 10
 lda digit1,x ; + 4 = 14
 sta GRP1 ; +3 = 17
 lda digit2,x ; +4 = 21
 sta tmp0 ; +3 = 24
 lda digit3,x ; +4 = 28
 tay ; + 2 = 30
 lda digit4,x ; + 4 = 34
 ldx tmp0 ; + 3 = 37
 stx GRP0 ; +3 = 40
 sty GRP1 ; +3 = 43
 sta GRP0 ; +3 = 46
 dec count1 ; +5 = 51
 bpl l_drawloop ; +2 = 53

 sta WSYNC
 lda #0
 sta GRP0
 sta GRP1
 rts

draw_rhs
 ldx #4
 stx count1

r_drawloop
 sta WSYNC
 repeat 6
 nop
 repend
 ldx count1 ; 3
 lda digit0,x ; +4 = 7
 sta GRP0 ; +3 = 10
 lda digit1,x ; + 4 = 14
 sta GRP1 ; +3 = 17
 lda digit2,x ; +4 = 21
 sta tmp0 ; +3 = 24
 lda digit3,x ; +4 = 28
 tay ; + 2 = 30
 lda digit4,x ; + 4 = 34
 ldx tmp0 ; + 3 = 37
 stx GRP0 ; +3 = 40
 sty GRP1 ; +3 = 43
 sta GRP0 ; +3 = 46
 sta WSYNC
 repeat 6
 nop
 repend
 ldx count1 ; 3
 lda digit0,x ; +4 = 7
 sta GRP0 ; +3 = 10
 lda digit1,x ; + 4 = 14
 sta GRP1 ; +3 = 17
 lda digit2,x ; +4 = 21
 sta tmp0 ; +3 = 24
 lda digit3,x ; +4 = 28
 tay ; + 2 = 30
 lda digit4,x ; + 4 = 34
 ldx tmp0 ; + 3 = 37
 stx GRP0 ; +3 = 40
 sty GRP1 ; +3 = 43
 sta GRP0 ; +3 = 46
 dec count1 ; +5 = 51
 bpl r_drawloop ; +2 = 53

 sta WSYNC
 lda #0
 sta GRP0
 sta GRP1
 rts

 echo *-$F000,"bytes of code"

 org $F800

font
; include "4x5font.h"

font_space
 byte %00000000
 byte %00000000
 byte %00000000
 byte %00000000
 byte %00000000
font_rparen
 byte %01000000
 byte %00100000
 byte %00100000
 byte %00100000
 byte %01000000
font_star
 byte %00000000
 byte %10100000
 byte %01000000
 byte %10100000
 byte %00000000
font_plus
 byte %00000000
 byte %01000000
 byte %11100000
 byte %01000000
 byte %00000000
font_comma
 byte %00000000
 byte %00000000
 byte %00000000
 byte %01000000
 byte %10000000
font_dash
 byte %00000000
 byte %00000000
 byte %11100000
 byte %00000000
 byte %00000000
font_dot
 byte %00000000
 byte %00000000
 byte %00000000
 byte %00000000
 byte %01000000
font_slash
 byte %00100000
 byte %00100000
 byte %01000000
 byte %10000000
 byte %10000000
font_0
 byte %01000000
 byte %10100000
 byte %11100000
 byte %10100000
 byte %01000000
font_1
 byte %01000000
 byte %11000000
 byte %01000000
 byte %01000000
 byte %11100000
font_2
 byte %11000000
 byte %00100000
 byte %01000000
 byte %10000000
 byte %11100000
font_3
 byte %11000000
 byte %00100000
 byte %01000000
 byte %00100000
 byte %11000000
font_4
 byte %00100000
 byte %10100000
 byte %11100000
 byte %00100000
 byte %00100000
font_5
 byte %11100000
 byte %10000000
 byte %01000000
 byte %00100000
 byte %11000000
font_6
 byte %01100000
 byte %10000000
 byte %11100000
 byte %10100000
 byte %11000000
font_7
 byte %11100000
 byte %00100000
 byte %01000000
 byte %10000000
 byte %10000000
font_8
 byte %11100000
 byte %10100000
 byte %01000000
 byte %10100000
 byte %11100000
font_9
 byte %01100000
 byte %10100000
 byte %11100000
 byte %00100000
 byte %11000000
font_colon
 byte %00000000
 byte %01000000
 byte %00000000
 byte %01000000
 byte %00000000
font_semi
 byte %00000000
 byte %01000000
 byte %00000000
 byte %01000000
 byte %10000000
font_less
 byte %00100000
 byte %01000000
 byte %10000000
 byte %01000000
 byte %00100000
font_equal
 byte %00000000
 byte %11100000
 byte %00000000
 byte %11100000
 byte %00000000
font_greater
 byte %10000000
 byte %01000000
 byte %00100000
 byte %01000000
 byte %10000000
font_ques
 byte %01100000
 byte %10010000
 byte %00100000
 byte %00000000
 byte %00100000
font_at
 byte %01000000
 byte %10100000
 byte %10100000
 byte %10000000
 byte %01100000
font_A
 byte %01000000
 byte %10100000
 byte %11100000
 byte %10100000
 byte %10100000
font_B
 byte %11000000
 byte %10100000
 byte %11000000
 byte %10100000
 byte %11000000
font_C
 byte %01100000
 byte %10000000
 byte %10000000
 byte %10000000
 byte %01100000
font_D
 byte %11000000
 byte %10100000
 byte %10100000
 byte %10100000
 byte %11000000
font_E
 byte %11100000
 byte %10000000
 byte %11000000
 byte %10000000
 byte %11100000
font_F
 byte %11100000
 byte %10000000
 byte %11000000
 byte %10000000
 byte %10000000
font_G
 byte %01100000
 byte %10000000
 byte %10100000
 byte %10100000
 byte %01100000
font_H
 byte %10100000
 byte %10100000
 byte %11100000
 byte %10100000
 byte %10100000
font_I
 byte %11100000
 byte %01000000
 byte %01000000
 byte %01000000
 byte %11100000
font_J
 byte %01100000
 byte %00100000
 byte %00100000
 byte %10100000
 byte %01100000
font_K
 byte %10100000
 byte %10100000
 byte %11000000
 byte %10100000
 byte %10010000
font_L
 byte %10000000
 byte %10000000
 byte %10000000
 byte %10000000
 byte %11100000
font_M
 byte %10100000
 byte %11100000
 byte %11100000
 byte %10100000
 byte %10100000
font_N
 byte %11000000
 byte %10100000
 byte %10100000
 byte %10100000
 byte %10100000
font_O
 byte %01000000
 byte %10100000
 byte %10100000
 byte %10100000
 byte %01000000
font_P
 byte %11000000
 byte %10100000
 byte %11000000
 byte %10000000
 byte %10000000
font_Q
 byte %01000000
 byte %10100000
 byte %10100000
 byte %01000000
 byte %00100000
font_R
 byte %11000000
 byte %10100000
 byte %11000000
 byte %10100000
 byte %10100000
font_S
 byte %01100000
 byte %10000000
 byte %01000000
 byte %00100000
 byte %11000000
font_T
 byte %11100000
 byte %01000000
 byte %01000000
 byte %01000000
 byte %01000000
font_U
 byte %10100000
 byte %10100000
 byte %10100000
 byte %10100000
 byte %11100000
font_V
 byte %10100000
 byte %10100000
 byte %10100000
 byte %11000000
 byte %10000000
font_W
 byte %10100000
 byte %10100000
 byte %11100000
 byte %11100000
 byte %10100000
font_X
 byte %10100000
 byte %10100000
 byte %01000000
 byte %10100000
 byte %10100000
font_Y
 byte %10100000
 byte %10100000
 byte %01000000
 byte %01000000
 byte %01000000
font_Z
 byte %11100000
 byte %00100000
 byte %01000000
 byte %10000000
 byte %11100000

 org $F900
message ; should begin with 20 spaces (FIXME: get rid of this requirement)
 byte "                    "
 byte "HOW DO YOU LIKE "
 byte "THIS UGLY FLICKE"
 byte "RING SCROLLING M"
 byte "ARQUEE? I HOPE I"
 byte "T DOESNT GIVE YO"
 byte "U A HEADACHE LIK"
 byte "E IT GAVE ME"
 repeat 128
 byte " "
 repend

email byte "ATARI@xxxxxxxxxxxxxx"

 echo *-$F800,"bytes of data"

 org $FFFC
 word $F000
 word $F000


Attachment: marquee.bin
Description: Binary data

Current Thread