; vga_threaded.asm - threaded generate VGA text, background for frame updates ; author Neil Franklin, last modification 2009.01.22 ; ------ hardware usage ; --- controller model ; we are using an ATmega32 .include "m32def.inc" ; --- fuse settings ; fast external quarz osc, max stable startup ; FF: 7:BODLVL=1, 6:BOD=1 (disable), 54:SUT=11, 3210:CKSEL=1111 (ext resonat) ; D9: 7:OCDEN=1 (dis), 6:JTAGEN=1 (dis), 5:SPIEN=0 (en), 4:CKOPT=1 ; 3:EESAVE=1 (not preserve), 21:BOOTSZ=00, 0:BOOTRST=1 ; --- board wiring ; VGA "HD-15" connector pinout (correctly named DE15, D-Shell E-size 15pin) ; pins 1/2/3: R/G/B, 0.7V analog ; pin 4: ID2, TTL ; pin 5: GND (for IDs?) ; pins 6/7/8: GND RGB (for R/G/B coax shields) ; pin 9: n.c./key (can also be wired as +5V for DDC) ; pin 10: GND HV (for H-Sync + V-Sync coax shields) ; pins 11/12: ID0/ID1, TTL ; pin 13: H-Sync, TTL (can also be wired as C-Sync) ; pin 14: V-Sync, TTL ; pin 15: n.c. (can also be wired as ID3) ; (ID1/ID3 can also be wired as SDA/SCA for DDC I2C bus) ; VGA "R" DACs, 3 times 2bit/4value, gives 4*4*4=64 colours, RRGGBB ; each DAC from each bit TTL 5V, to 2bit 0.233+0.466V=0.7V @ 75ohm monitor ; needs per DAC resistors: ; bit0 75ohm/0.233V*5V-75ohm = 1534okm -> resistor 1k5ohm ; bit1 75ohm/0.466V*5V-75ohm = 730ohm -> resistor 680ohm (not 820ohm) ; VGA ports and port bits ; video pixel data .equ VGADPORT = PORTC ; 3 2bit colour DACs, write fast, must be same port ; use full 8bit out, bits 7+6 must accept overwrite .equ VGADDDR = DDRC .equ VGABLACK = 0x00 ; all 3 DACs set to 0b00, clear port .equ VGABLUE = 0x03 ; pins 1+0 blue 2bit DAC .equ VGAGREEN = 0x0C ; pins 3+2 green 2bit DAC .equ VGARED = 0x30 ; pins 5+4 red 2bit DAC .equ VGAWHITE = VGABLUE+VGAGREEN+VGARED ; all 3 DACs set to 0b11, also DDR ; sync control lines .equ VGAHPORT = PORTC ; 1 H-Sync TTL out, can accept draw overwrite with 0 .equ VGAHDDR = DDRC .equ VGAHBIT = PINC6 .equ VGAVPORT = PORTC ; 1 V-Sync TTL out, can accept draw overwrite with 0 .equ VGAVDDR = DDRC .equ VGAVBIT = PINC7 ; control ports and port bits ; size, 25 or 30 rows, VGA text/400@70Hz or graphics/480@60Hz timing .equ SIZPORT = PORTD ; 1 size switch TTL in with Pull-Up .equ SIZPIN = PIND .equ SIZDDR = DDRD .equ SIZBIT = PIND7 ; 0(jumper) = 30 rows, 1(default) = 25 rows ; line 2 mode, 2nd blank or double scan .equ LN2PORT = PORTD ; 1 mode switch TTL in with Pull-Up .equ LN2PIN = PIND .equ LN2DDR = DDRD .equ LN2BIT = PIND6 ; 0(jumper) = double scan, 1(default) = 2nd blank ; 2nd blank colour, blank or background .equ CO2PORT = PORTD ; 1 colour switch TTL in with Pull-Up .equ CO2PIN = PIND .equ CO2DDR = DDRD .equ CO2BIT = PIND5 ; 0(jumper) = background, 1(default) = blank ; ------ constants usage ; --- line timing constant ; reduce this due to lack of SRAM on 2k ATmega32 ;.equ TITOP = 74 ; drawing is 40char * 12clk/char = 480 clocks ; ; line is draw + 120 horiz retrace (1/4) = 600 clocks ; ; TITOP is 600/8-1, for 0..TITOP counting @ clock/8 .equ TITOP = 68 ; drawing is 36char * 12clk/char = 432 clocks ; line is draw + 120 horiz retrace (1/4) = 552 clocks ; TITOP is 552/8-1, for 0..68 counting @ clock/8 ; --- basic drawing engine buildup .equ ROWSEGMENTS = 8 ; we draw 8 segments per row .equ SEGMENTLINES = 2 ; and 2 lines per segment ; --- VGA timing constants ; VGA text mode frequencies .equ VRBBTLINES = 12 ; vertical blanking lines, bottom blank .equ VRPUTLINES = 13 ; vertical blanking lines, retrace pulse .equ VRTBTLINES = 25 ; vertical blanking lines, top blank ; reduce this due to lack of SRAM on 2k ATmega32 ;.equ CHDRTROWS = 25 ; draw rows*segments(8)*lines(2) = lines(25*8*2=400) .equ CHDRTROWS = 23 ; draw rows*segments(8)*lines(2) = lines(23*8*2=368) ; VGA graphics mode frequencies .equ VRBBGLINES = 11 ; vertical blanking lines, bottom blank .equ VRPUGLINES = 12 ; vertical blanking lines, retrace pulse .equ VRTBGLINES = 22 ; vertical blanking lines, top blank ; reduce this due to lack of SRAM on 2k ATmega32 ;.equ CHDRGROWS = 30 ; draw rows*segments(8)*lines(2) = lines(30*8*2=480) .equ CHDRGROWS = 23 ; draw rows*segments(8)*lines(2) = lines(23*8*2=368) ; --- vertical blanking state numbers ; VR*LINE loops run VRINSTATE = with these constants, all negative values .equ VRINBB = -1 ; all negative values .equ VRINPU = -2 .equ VRINTB = -3 ; CHDRROW loop runs CHROW = CHDRTROWS|CHDRGROWS..1, all positive values ; ------ register usage ; --- standard ones ; use an systematic naming of AVR registers .include "avr_registers.inc" ; --- project specific ones ; here use/(re-)name R15..R2 (fast variables) registers ; avra fails to make an .def (or .equ) to an register name defined by .def ; so here use an 2nd .def with the original R* name, and use -W NoRegDef ; for drawing timer ISR ; registers for storing drawing timer ISR continuation address .def BGCONTH = R15 .def BGCONTL = R14 ; registers for storing drawing timer ISR frame buffer address .def BGFBUFH = R13 .def BGFBUFL = R12 ; for drawing control ; registers for vertical blanking line/blanktype loop counting .def VRLINE = R11 ; identical with CHLINE, same use, same register .def VRINSTATE = R10 ; identical with CHROW, track where drawing is ; registers for line drawing line/segm/row loop counting .def CHLINE = R11 ; identical with VRLINE, same use, same register .def CHSEGM = R23 ; must bei R23=I1, for using immediate ops .def CHROW = R10 ; identical with VRINSTATE, track where drawing is ; for colours ; registers for colour patterns, are the LUT, use for out VGADPORT,value-reg ; use non-immediate fast/specials vars, no immediate load and permanent use .def BLANK = R9 ; constant for colour blanking (black) after line .def LUTBACK = R8 ; 2 colours for foreground and background .def LUTFORE = R7 .def LUTCOL3 = R6 ; 3 alternate foreground colours per row .def LUTCOL2 = R5 .def LUTCOL1 = R4 ; R2 and R3 are still free to use as F0 and F1 ; ------ SRAM usage .dseg .org MINRAM ; --- define stack STACK: .byte 0x0020 ; uses leftover 0x0020 hole from 0x0060 to 0x007F ; must be here and this size, for FONTRAM: position ENDOFSTACK: ; --- define runtime font ; one segment .equ FOCHARS = 0x80 ; 0x80|128 characters possible, must be an 2^n value .equ FOINDEXMASK = 0x7F ; 0..0x7F|127 indexes possible, must be 2^n-1 value ; all segments .equ FOSIZE = ROWSEGMENTS*FOCHARS ; space used for font FONTRAM: ; must be an address dividable by FOCHARS, use 0x0080 .byte FOSIZE ; uses 8*128 = 1024bytes (of ATmega32 2048) ; --- define frame buffer ; one row .equ FBROWBEG = 5 ; 5 color bytes a begin of row ; reduce this due to lack of SRAM on 2k ATmega32 ;.equ FBCOLS = 40 ; 40 columns of character index bytes in row (old) .equ FBCOLS = 36 ; 36 columns of character index bytes in row (ld) .equ FBROWEND = 1 ; 1 abort pseudo-char "return" index, at end of row .equ FBROWLEN = FBROWBEG+FBCOLS+FBROWEND ; legth of row in frame buffer ; entire frame .equ FBROWS = CHDRGROWS ; rows in entire frame buffer, largest mode, graph .equ FBSIZE = FBROWS*FBROWLEN ; space used for frame buffer FBUF: ; reduce this due to lack of SRAM on 2k ATmega32 ;.byte FBSIZE ; uses 30*(5+40+1) = 1390bytes (of ATmega32 2048) .byte FBSIZE ; uses 23*(5+36+1) = 966bytes (of ATmega32 2048) ; --- interruts save background register contents INTSREG: ; ordered in row of restoring/loading/"poping" .byte 1 ; SREG first, while still not yet restored regs free INTXL: ; register pairs low first then high .byte 1 ; and generally following R0..R31 direction INTXH: .byte 1 INTYL: .byte 1 INTYH: .byte 1 INTZL: .byte 1 INTZH: .byte 1 ; --- snail animation demo position DESNPOS: .byte 1 ; snail horizontal position, 0..FBCOLS-1 ; --- cursor animation demo position DECUPOS: .byte 1 ; cursor horizontal position, 0..FBCOLS-1 ; --- left over 2048 - 32 - 1024 - 966 - 7 - 1 - 1 = 17 ; ------ Flash usage .cseg .org MINROM ; ------ system initialisation ; --- handle reset and interrupts (all identical as all not used) ; set up reset and interrupt vectors VECRESET: jmp RESET VECINT0: jmp UNUINT VECINT1: jmp UNUINT VECINT2: jmp UNUINT VECT2CMP: jmp UNUINT VECT2OVF: jmp UNUINT VECT1CAP: jmp UNUINT VECT1CMPA: jmp UNUINT VECT1CMPB: jmp UNUINT VECT1OVF: jmp UNUINT VECT0CMP: jmp T0CMPINT VECT0OVF: jmp UNUINT VECSPI: jmp UNUINT VECURXC: jmp UNUINT VECUDRE: jmp UNUINT VECUTXC: jmp UNUINT VECADC: jmp UNUINT VECEERDY: jmp UNUINT VECACMP: jmp UNUINT VECTWI: jmp UNUINT VECSPM: jmp UNUINT ; --- handle unused interrupts, despite not used nor triggered UNUINT: ; just return, nothing else makes sense here reti ; ------ debugging helpers ; --- debuging LED, using TxD pin + RS232 adapter + RSR23 tester TxD LED ; reset state is DDR-TxD=0=in, TTL=float/5V, RS232=-12V, LED=red ; allways set port bit and DDR, so no setup is required DBGLEDGRN: cbi PORTD,PINTXD ; TxD=0, TTL=0V, RS232=12V, LED=green sbi DDRD,PINTXD ; DDR-TxD=1=out ret DBGLEDRED: sbi PORTD,PINTXD ; TxD=1, TTL=5V, RS232=-12V, LED=red sbi DDRD,PINTXD ; DDR-TxD=1=out ret DBGLEDINV: sbis PORTD,PINTXD rjmp DBGLEDRED ; TxD is 0, invert to TxD=1 rjmp DBGLEDGRN ; TxD is 1, invert to TxD=0 ; ------ font unthreading and drawing mechanism ; --- segment drawing routines ; each char in font is an set of ROWSEGMENTS segments ; these get drawn on ROWSEGMENTS consecutive line(pair)s ; each segment consists of 4 pixels, giving 16 different possbile segments ; drawing an segment consists of 4*3 clocks, split as: ; 4 clocks write pixels with out VGADPORT,xxxx (xxxx = LUTFORE or LUTBACK) ; between these 4*2 clocks for unthreading and indirect jumping next char ; the code for unthreading and indirect jumping works like this: ; ijmp requires address in ZH:ZL, no other possible ; ZH is constant, high addr of SEGM table, settable by ldi ZH,high(SEGM) ; ZL is fetched from font segment index tables, each char, by ld ZL, ; ld requires address in XH:XL, as ZH:ZL and YH:YL already used ; XH is per-segment constant, high addr of table, set once for segm draw ; XL is part (bit6..0) fetched from frame buffer, by ld XL,Y+ ; and part (bit7) per-segment constant, low addr of table ; addressed/executed by indirect computed ijmp, only 8bit indexing for speed ; the labels are only needed for jump table building .org 0x0100 ; must be 0x??00, begin of 8bit 256word|512byte page ; hardcoded 0x0100, as avra knows no .align ; will give error message if more than 0x0100 used DRSEGM: ; all 16 segments for character drawing, repeat for each pixelsets segment: ; DRSEGMxxxx: ; {4*(1+2)=12} and 9instr, 16segm=144instr, 9/16 space ; out VGADPORT,LUT* ; {1} current character: draw pixel 1 ; ld XL,Y+ ; {2} next character: get character code/index ; out VGADPORT,LUT* ; {1} current character: draw pixel 2 ; or XL,I0 ; {1} next character: low(half-page base font table) ; nop ; {1} XH:I1 set to segm font table before drawing ; out VGADPORT,LUT* ; {1} current character: draw pixel 3 ; ld ZL,X ; {2} next character: read index, is low(DRSEGM*) ; ; ZH is set to high(DRSEGM*) before drawing ; out VGADPORT,LUT* ; {1} current character: draw pixel 4 ; ijmp ; {2} next character: goto ZH:ZL and execute it ; ; continuation at next chars pixel 1, as above ; ; this results in an endless loop, until abort char DRSEGM0000: out VGADPORT,LUTBACK ld XL,Y+ out VGADPORT,LUTBACK or XL,I0 nop out VGADPORT,LUTBACK ld ZL,X out VGADPORT,LUTBACK ijmp DRSEGM0001: out VGADPORT,LUTBACK ld XL,Y+ out VGADPORT,LUTBACK or XL,I0 nop out VGADPORT,LUTBACK ld ZL,X out VGADPORT,LUTFORE ijmp DRSEGM0010: out VGADPORT,LUTBACK ld XL,Y+ out VGADPORT,LUTBACK or XL,I0 nop out VGADPORT,LUTFORE ld ZL,X out VGADPORT,LUTBACK ijmp DRSEGM0011: out VGADPORT,LUTBACK ld XL,Y+ out VGADPORT,LUTBACK or XL,I0 nop out VGADPORT,LUTFORE ld ZL,X out VGADPORT,LUTFORE ijmp DRSEGM0100: out VGADPORT,LUTBACK ld XL,Y+ out VGADPORT,LUTFORE or XL,I0 nop out VGADPORT,LUTBACK ld ZL,X out VGADPORT,LUTBACK ijmp DRSEGM0101: out VGADPORT,LUTBACK ld XL,Y+ out VGADPORT,LUTFORE or XL,I0 nop out VGADPORT,LUTBACK ld ZL,X out VGADPORT,LUTFORE ijmp DRSEGM0110: out VGADPORT,LUTBACK ld XL,Y+ out VGADPORT,LUTFORE or XL,I0 nop out VGADPORT,LUTFORE ld ZL,X out VGADPORT,LUTBACK ijmp DRSEGM0111: out VGADPORT,LUTBACK ld XL,Y+ out VGADPORT,LUTFORE or XL,I0 nop out VGADPORT,LUTFORE ld ZL,X out VGADPORT,LUTFORE ijmp DRSEGM1000: out VGADPORT,LUTFORE ld XL,Y+ out VGADPORT,LUTBACK or XL,I0 nop out VGADPORT,LUTBACK ld ZL,X out VGADPORT,LUTBACK ijmp DRSEGM1001: out VGADPORT,LUTFORE ld XL,Y+ out VGADPORT,LUTBACK or XL,I0 nop out VGADPORT,LUTBACK ld ZL,X out VGADPORT,LUTFORE ijmp DRSEGM1010: out VGADPORT,LUTFORE ld XL,Y+ out VGADPORT,LUTBACK or XL,I0 nop out VGADPORT,LUTFORE ld ZL,X out VGADPORT,LUTBACK ijmp DRSEGM1011: out VGADPORT,LUTFORE ld XL,Y+ out VGADPORT,LUTBACK or XL,I0 nop out VGADPORT,LUTFORE ld ZL,X out VGADPORT,LUTFORE ijmp DRSEGM1100: out VGADPORT,LUTFORE ld XL,Y+ out VGADPORT,LUTFORE or XL,I0 nop out VGADPORT,LUTBACK ld ZL,X out VGADPORT,LUTBACK ijmp DRSEGM1101: out VGADPORT,LUTFORE ld XL,Y+ out VGADPORT,LUTFORE or XL,I0 nop out VGADPORT,LUTBACK ld ZL,X out VGADPORT,LUTFORE ijmp DRSEGM1110: out VGADPORT,LUTFORE ld XL,Y+ out VGADPORT,LUTFORE or XL,I0 nop out VGADPORT,LUTFORE ld ZL,X out VGADPORT,LUTBACK ijmp DRSEGM1111: out VGADPORT,LUTFORE ld XL,Y+ out VGADPORT,LUTFORE or XL,I0 nop out VGADPORT,LUTFORE ld ZL,X out VGADPORT,LUTFORE ijmp ; the pseudo-segment for the row end abort pseudo-character ; no drawing after 1st pixel, no timing, no unthreading next character DRSEGMABRT: out VGADPORT,BLANK ; {1} switch off whatever was last background colour ; this is timing critical, so do it before jump jmp CHEND ; {3} go back to common CHDRLINE handler for rest ; no rjmp because font may become too large ; some more pseudo-segments for the "specials" pseudo-characters ; no drawing after 1st pixel, no nop, but after unthread next character ; {1} current character: draw only pixel 1, as spaces all LUTBACK ; {12-1-7=4} time for special stuff, the desired side effects ; {2+1+2+2=7} next character: all unthread stuff at once DRSEGM0FXB: ; for special pseudo-chars "invert" and "underline" out VGADPORT,LUTBACK ; for invert use 8 times, for underline 7space+1this eor LUTFORE,LUTBACK ; {1} FORE.eor.BACK "mixed" eor LUTBACK,LUTFORE ; {1} BACK.eor.(FORE.eor.BACK) = FORE "unmix" eor LUTFORE,LUTBACK ; {1} (FORE.eor.BACK).eor.FORE = BACK "unmix" out VGADPORT,LUTBACK ; switch to new bg colour halfway through pseudo-space ld XL,Y+ or XL,I0 ld ZL,X ijmp DRSEGM0FX1: ; for special pseudo-char "foregr alt colour 1" out VGADPORT,LUTBACK eor LUTFORE,LUTCOL1 ; {1} FORE.eor.COL1 "mixed" eor LUTCOL1,LUTFORE ; {1} COL1.eor.(FORE.eor.COL1) = FORE "unmix" eor LUTFORE,LUTCOL1 ; {1} (FORE.eor.COL1).eor.FORE = COL1 "unmix" nop ; {1} ld XL,Y+ or XL,I0 ld ZL,X ijmp DRSEGM0FX2: ; for special pseudo-char "foregr alt colour 2" out VGADPORT,LUTBACK eor LUTFORE,LUTCOL2 ; {1} FORE.eor.COL2 "mixed" eor LUTCOL2,LUTFORE ; {1} COL2.eor.(FORE.eor.COL2) = FORE "unmix" eor LUTFORE,LUTCOL2 ; {1} (FORE.eor.COL2).eor.FORE = COL2 "unmix" nop ; {1} ld XL,Y+ or XL,I0 ld ZL,X ijmp DRSEGM0BX3: ; for special pseudo-char "backgr alt colour 3" out VGADPORT,LUTBACK eor LUTBACK,LUTCOL3 ; {1} BACK.eor.COL3 "mixed" eor LUTCOL3,LUTBACK ; {1} COL3.eor.(BACK.eor.COL3) = BACK "unmix" eor LUTBACK,LUTCOL3 ; {1} (BACK.eor.COL3).eor.BACK = COL3 "unmix" out VGADPORT,LUTBACK ; switch to new bg colour halfway through pseudo-space ld XL,Y+ or XL,I0 ld ZL,X ijmp ; ensure that segments fit in 256word|512byte and can be low() 8bit addressed .org DRSEGM+0x0100 ; must use less than an +1 8bit 256word|512byte page ; will give error if over 0x0100 used for DRSEGM* ; --- indexing into segment drawing routines ; table to convert pixel pattern numbers to segment drawing routine addresses ; addressed by computed base+offset, with 16bit base and 8bit offset ; so only one SEGM: label is needed, for marking table base ; the SEGM* are only .equ, not labels, for user using them in .db lines ; the .db lines must have n*2 bytes each, else lpm indexing fails ; group as sets of 4 to save code lines and scrolling ; pattern-as-index value is used as offset to SEGM: label ; first the 16 4-pixel combinations 0000..1111 as indexes 0..15 ; then the pseudo segments for pseudo characters with indexes 16 and above .equ SEGMNO = 0x14 ; we use indexes 0x00..0x13, 0x14 in all SEGM: .equ SEGM0000 = 0x00 .equ SEGM0001 = 0x01 .equ SEGM0010 = 0x02 .equ SEGM0011 = 0x03 .db low(DRSEGM0000), low(DRSEGM0001), low(DRSEGM0010), low(DRSEGM0011) .equ SEGM0100 = 0x04 .equ SEGM0101 = 0x05 .equ SEGM0110 = 0x06 .equ SEGM0111 = 0x07 .db low(DRSEGM0100), low(DRSEGM0101), low(DRSEGM0110), low(DRSEGM0111) .equ SEGM1000 = 0x08 .equ SEGM1001 = 0x09 .equ SEGM1010 = 0x0A .equ SEGM1011 = 0x0B .db low(DRSEGM1000), low(DRSEGM1001), low(DRSEGM1010), low(DRSEGM1011) .equ SEGM1100 = 0x0C .equ SEGM1101 = 0x0D .equ SEGM1110 = 0x0E .equ SEGM1111 = 0x0F .db low(DRSEGM1100), low(DRSEGM1101), low(DRSEGM1110), low(DRSEGM1111) ; no index for DRSEGMABRT, never in font, prevent user screwing stuff up ; as result of this the special character must be built with explicit low() .equ SEGM0FXB = 0x10 .equ SEGM0FX1 = 0x11 .equ SEGM0FX2 = 0x12 .equ SEGM0BX3 = 0x13 .db low(DRSEGM0FXB), low(DRSEGM0FX1), low(DRSEGM0FX2), low(DRSEGM0BX3) ; --- font segment index tables ; each char in font is an set of indexes (1 per segment) into SEGM table ; addressed by computed base+offset, again with 16bit base and 8bit offset ; the FONTROM: label is only for marking font base ; the font consists entirely of .db statements ; the .db lines must have n*2 bytes each, else lpm indexing fails ; group as sets of 4 to save code lines and scrolling ; intermingled with these are .equ statements for character names ; and also labels for identifying and loading blocks of characters FONTROM: ; include font as separate file, so it may be generated automatically ; is done so from file vga_text_font.fon, by generator ./genfont .include "vga_text_font.inc" ; --- load single char of font from Flash into SRAM ; parameters: ZH:ZL: character definition in Flash, gets incremented ; T0: character position to overwrite in all SRAM segments FOCHAR: andi T0,FOINDEXMASK ; prevent outindexing of the SRAM font tables ; prevent destroying of abort framing by user errors cpi T0,ABORT ; char - ABORT (abort pseudo-char, do not overwrite) brne FOCHNOTABORT ; <>, is ok ret ; =, do nothing FOCHNOTABORT: ; each character in ROM/Flash font has ROWSEGMENTS segment indexes ; ZH:ZL already points to the first of these ; each segment in SRAM font has an table of FOCHARS segment indexes ldi XL,low(FONTRAM) ; begin of table of all first segments ldi XH,high(FONTRAM) add XL,T0 ; add the characters offset into this segment table ; SRAM font is alligned, can never produce an carry ldi T1,ROWSEGMENTS ; for segment = ROWSEGMENTS..1 FOCHLOOP: lpm T2,Z+ ; get character from Flash, as sequential bytes cpi T2,SEGMNO ; catch invalid indexes, and so invalid bit patterns brne FOCHINRANGE ldi T0,SEGM0000 FOCHINRANGE: push ZH ; lpm can unfortunately only use ZH:ZL, so reuse it push ZL ldi ZL,low(SEGM*2) ; base + index for T1=SEGM[T1] ldi ZH,high(SEGM*2) ; *2 because of lpm word->byte addresses ; so index table must be in first 64kbyte|32kword add ZL,T2 ldi T2,0 ; expand index to 16bit, allways 0, as no adci ZH,0 adc ZH,T2 lpm T2,Z ; get low(DRSEGM*) address pop ZL ; restore main ZH:ZL after reusing pop ZH st X,T2 ; write to segments table, no X+, not sequential subi XL,low(-FOCHARS) ; goto next segments table, same position, +FOCHARS sbci XH,high(-FOCHARS) ; this gives distributing of segments over tables dec T1 ; next segment brne FOCHLOOP ret ; --- load default font from Flash into SRAM FOINIT: ldi ZL,low(FONTROM*2) ; begin of font data "file" in Flash ldi ZH,high(FONTROM*2) ; *2 because of lpm word->byte addresses ; so font must be in first 64kbyte|32kword ; default font is first FOCHARS of defined characters ldi T0,0 ; for char = 0..FOCHARS-1 FOINLOOP: rcall FOCHAR ; copy char T0 from Flash to SRAM inc T0 ; next index cpi T0,FOCHARS brne FOINLOOP ldi XL,low(FONTRAM+ABORT) ; set segments for abort pseudo-character ldi XH,high(FONTRAM+ABORT) ; can not use FOCHAR because framing protect ldi T1,ROWSEGMENTS FOABLOOP: ldi T2,low(DRSEGMABRT) ; allways must be DRSEGMABRT segment, not from font ; may be after all missing or missplaced there st X,T2 subi XL,low(-FOCHARS) sbci XH,high(-FOCHARS) dec T1 brne FOABLOOP ret ; ------ frame buffer handling ; --- write colour into frame buffer ; parameters: XH:XL: address where in frame buffer, gets incremented ; only use when at begin of row ; T0: colour code, gets truncated to black(0x00)..white(0x3F) FBWCOLOUR: andi T0,VGAWHITE ; prevent false sync signals from user errors ; XH:XL set by caller, contains address in frame buffer st X+,T0 ; place this colour ret ; --- read colour from frame buffer ; parameters: XH:XL: address where in frame buffer, gets incremented ; only use when at begin of row ; returns: T0: colour code FBRCOLOUR: ; XH:XL set by caller, contains address in frame buffer ld T0,X+ ; extract this colour ret ; --- write character into frame buffer ; parameters: XH:XL: address where in frame buffer, gets incremented ; only use when inside an row ; T0: character code FBWCHAR: andi T0,FOINDEXMASK ; prevent outindexing of font tables ; prevent insertion of false abort from user errors cpi T0,ABORT ; char - ABORT (abort pseudo-char, not in text) brne FBWCNOTABORT ; <>, is ok ldi T0,0x2E ; =, replace with ASCII "." to show fixup FBWCNOTABORT: ; XH:XL set by caller, contains address in frame buffer st X+,T0 ; place this char ret ; --- read character from frame buffer ; parameters: XH:XL: address where in frame buffer, gets incremented ; only use when inside an row ; returns: T0: character code FBRCHAR: ; XH:XL set by caller, contains address in frame buffer ld T0,X+ ; extract this character ret ; --- write immediate string into frame buffer ; parameters: TOS (old PC): address of string (in Flash!) ; format of constant: .dw length, .db data ; data in .db must allways be pairs ; if not, add an 0 filler byte ; no not include in length count, else gets drawn ; these are assembled-in, after call|rcall FBWSTRI ; XH:XL: address where in frame buffer, gets incremented ; write only while inside current row, only to end of row FBWSTRI: ; next address in calling program, begin of constant ; must use ZH:ZL because of lpm instr pop ZH ; call|rcall stores low first/top, big endian(!) pop ZL ; so read low as last/top lsl ZL ; correct address for prog word vs lpm byte addressing rol ZH ; this will fail on chips larger 64kByte/32kWord push DH ; source length, not YH:YL, as not an address push DL lpm DL,Z+ ; length of string constant, from program lpm DH,Z+ ; DH:DL set above ; for char = constantlen..1 FBWSLOOP: ; XH:XL set by caller, contains address in frame buffer ld T0,X ; check for row end, look at frame buffer index value cpi T0,ABORT ; index - ABORT (abort pseudo-char, do not overwrite) breq FBDSREST ; =, is end of row, abort copying string, drop rest lpm T0,Z+ ; copy string data, from program ... rcall FBWCHAR ; ... to frame buffer, breaks T0, does X+ sbiw DH:DL,1 ; next char brne FBWSLOOP FBDSDONE: pop DL pop DH sbrc ZL,0 ; first unused byte is in 2nd half of an word ? adiw ZH:ZL,1 ; yes, jump over added 2nd halfword filler byte lsr ZH ; undo prog word vs lpm byte address correction ror ZL ; new address in calling program, instr after constant ijmp ; ijmp is faster than push ZL push ZH ret ; --- drop immediate string, instead of into frame buffer ; DH:DL still set ; for char = current..1 FBDSLOOP: lpm T0,Z+ ; step over string data, in program, no copy/store FBDSREST: sbiw DH:DL,1 ; next char brne FBDSLOOP rjmp FBDSDONE ; --- initialise frame buffer FBINIT: push S0 push S1 ldi XL,low(FBUF) ; begin of frame buffer ldi XH,high(FBUF) ldi S0,FBROWS ; for row = FBROWS..1 FBRLOOP: ; set default colours for each row ldi T0,0x03 ; background, blue rcall FBWCOLOUR ; breaks T0, does X+ ;ldi T0,0x0F ; foreground, cyan ;ldi T0,0x1F ; foreground, cyan + 1/3 red ldi T0,0x1B ; foreground, cyan + 1/3 red - 1/3 green ;ldi T0,0x2B ; foreground, white - 1/3 red - 1/3 green rcall FBWCOLOUR ldi T0,0x1F ; alt 1 emphasized foregr, cyan + 1/3 red rcall FBWCOLOUR ;ldi T0,0x3F ; alt 2 strong foregr, white ;ldi T0,0x3C ; alt 2 strong foregr, yellow ldi T0,0x2F ; alt 2 strong foregr, cyan + 2/3 red, white - 1/3 red rcall FBWCOLOUR ldi T0,0x17 ; alt 3 marked backgr, blue + 1/3 red and green rcall FBWCOLOUR ; blank out and delimit the frame buffer ldi S1,FBCOLS ; for column = FBCOLS..1 FBCLOOP: ldi T0,0x2E ; fill unused space with ASCII "." rcall FBWCHAR ; breaks T0 (requiring re-load), does X+ dec S1 ; next column brne FBCLOOP ldi T0,ABORT ; abort pseudo-char, not in lines, frame at row end st X+,T0 ; can not use FBWCHAR because framing invalid char dec S0 ; next row brne FBRLOOP pop S1 pop S0 ret ; ------ timing delay helper ; --- wait a while doing nothing, spinloop ; parameters: I0: loop count, 1..256 (actually 1..255,0 where 0=256) ; gets destroyed (reduced to 0), to avoid push/pop time ; OK, as seldom reused, often loaded, so use immediate register ; this is only ever called from drawing, is interrupt, so I0 ; timing range: (I0-1)*(1+2) + 1*(1+1) + 4 = I0*3+3 clocks ; together with needed ldi I0, and rcall WAIT 1+3+(I0*3+3) clocks ; gives min 1+3+(1*3+3)=10 clocks, for I0=1 ; gives max 1+3+(256*3+3)=774 clocks, for I0=0 (=256) WAIT: ; {n} in comments = clocks used, for time computation ; this program requires lots of exact clock counting ; single-clock miscounts produce visual artifacts! ; I0 set up by caller ; {0} for LOOP = I0..1 WAILOOP: ; nothing in loop, for highest loop resolution, only 3 clocks dec I0 ; {1} next LOOP brne WAILOOP ; {2|1} ret ; {4} ; ------ handle timer interrupt driven drawing ; backgrounding drawing while blank lines are displayed ; --- interrupt service routine, push background, restore drawing state ; as far as drawing goes, this is an "return" from background T0CMPINT: ; TCNT0 now = 0, in 8 clks 1, in 16 clks 2, ... ; in 600-8 ckls 74, in 600clks again 0 ; {0..3?} last background instr jitter, if not {1} ; {4} interrupt entry|call|cli processing ; {3} interrupt vector does jmp T0CMPINT sts INTZH,ZH ; {2} save registers that are not ISR dedicated sts INTZL,ZL ; {2} use sts, to reduce push and stack usage size sts INTYH,YH ; {2} sts INTYL,YL ; {2} sts INTXH,XH ; {2} sts INTXL,XL ; {2} in I0,SREG ; {1} sts INTSREG,I0 ; {2} ; compensate for timer interrupt latency jitter ; basic method is to nop until 0 or 1 clock before ; timer steps, if not stepped wait 1 clock longer ; repeat entire game 8 clocks (1 timer step) later ; 3 times so that up to 3clocks correction ; as minimal instruction 1clk, maximal 4clk ; now is 22 + 0..3 clocks since TCNT0=0 nop ; {1} ; now is 23 + 1..3 clocks since TCNT0=0 in I0,TCNT0 ; {1} if 23+0=23 still TCNT0=2 else already TCNT0=3 cpi I0,2 ; {1} if TCNT0 = 2 breq TSYNC1 ; {1|2} not delayed by background 1, delay +1 TSYNC1: ; now is 26 + 1..3 clocks since TCNT0=0 nop ; {4} nop nop nop ; now is 30 + 1..3 clocks since TCNT0=0 in I0,TCNT0 ; {1} if 30+1=31 still TCNT0=3 else already TCNT0=4 cpi I0,3 ; {1} if TCNT0 = 3 breq TSYNC12 ; {1|2} not delayed by background 1..2, delay +1 TSYNC12: ; now is 34 + 2..3 clocks since TCNT0=0 nop ; {4} nop nop nop ; now is 37 + 2..3 clocks since TCNT0=0 in I0,TCNT0 ; {1} if 37+2=39 still TCNT0=4 else already TCNT0=5 cpi I0,4 ; {1} if TCNT0 = 4 breq TSYNC123 ; {1|2} not delayed by background 1..3, delay +1 TSYNC123: ; now is 40 + 3 clocks since TCNT0=0 ; restore drawing state, as before BACKGROUND movw YH:YL,BGFBUFH:BGFBUFL ; {1] restore framebuf address for drawing movw ZH:ZL,BGCONTH:BGCONTL ; {1} restore continuation point for "return" ijmp ; {3} and "return" to the line type that called ; now is 52 clocks since TCNT0=0 ; gives minimal for backgrounding = 23 + 4 + 52 clocks ; so forget backgrounding while horiz retrace ; here is always ijmp to lines *CONT point ; that is left of screen blanking, then horiz pulse ; after that count lines and possibly state change ; next line (or next state) will again BACKGROUND ; --- end of interrupt, store drawing state, pop background ; as far as drawing goes, it "calls" background until timer "returns" ; parameters: ZH:ZL: address for continuation, "return" of the "call" ; YH:YL: current frame buffer address, to save and restore BACKGROUND: ; timing not critical here, as enter background ; only for computing minimal backgrounding time ; drawing state part which has no dedicated variables movw BGCONTH:BGCONTL,ZH:ZL ; {1] save continuation point for "return" movw BGFBUFH:BGFBUFL,YH:YL ; {1] save framebuffer address for drawing ldi I0,(1< 0xFF breq CHNEXSEG ; {1|2} CHLINE now 0x00, was 0xFF, do next segment sbiw YH:YL,FBROWLEN ; {2} undo reads, repeat same segment for 2nd line ldi I0,3 ; {1} wait rest of its time, 30-(1+3)-(1+1)-2-6=16 rcall WAIT ; {3+(3*3+3)=15} rjmp CHDRLINE ; {2+(3+1)=6pre} CHNEXSEG: inc CHSEGM ; {1} next CHSEGM cpi CHSEGM,ROWSEGMENTS ; {1} breq CHNEXROW ; {1|2} all segments done, do next row sbiw YH:YL,FBROWLEN ; {2} undo reads, repeat same row for other segments ldi I0,1 ; {1} wait rest time, 30-(1+3)-(1+2)-(1+1+1)-2-7=11 rcall WAIT ; {3+(1*3+3)=9} nop ; {1} rjmp CHDRSEGM ; {2+(1+3+1)=7pre} CHNEXROW: dec CHROW ; {1} next CHROW breq CHNEXSTATE ; {1|2} all rows done, do next state nop ; {9} wait rest, 30-(1+3)-(1+2)-(1+1+2)-(1+1)-8=9 nop nop nop nop nop nop nop nop rjmp CHDRROW ; {2+(1+1+3+1)=8pre} CHNEXSTATE: nop ; {3} wait rest, 30-(1+3)-(1+2)-(1+1+2)-(1+2)-13=3 nop nop rjmp VRBBSTATE ; {2+11=13pre} on to next state, vert retrace bottom ; ------ character index based font handling ; --- character index load single char into SRAM font ; parameters: T0: index position to overwrite in all SRAM segments ; T1: index of character in font for overwriting SRAM segments CICHAR: ldi ZL,low(FONTROM*2) ; begin of font data "file" in Flash ldi ZH,high(FONTROM*2) ; *2 because of word->byte addresses ; so font must be in first 64kbyte|32kword ldi T2,ROWSEGMENTS ; ROWSEGMENTS bytes per char mul T1,T2 add ZL,ML ; index it adc ZH,MH rjmp FOCHAR ; ------ X/Y coordinate based frame buffer handling ; --- X/Y write colour into frame buffer ; parameters: S0: row in frame buffer to place colour ; T0: colour code for FBWCOLOUR, gets truncated ; T1: LUT reg to set, 0..4 = back/fore/alt1/alt2/alt3 XYWCOLOUR: cpi S0,FBROWS ; row in 0..(FBROWS-1) ? brsh XYOUTRANGE ; >=, dont' colour non-existant row cpi T1,FBROWBEG brsh XYOUTRANGE ; >=, dont' set to non-existant LUT colour ldi XL,low(FBUF) ; begin of frame buffer colours ldi XH,high(FBUF) ldi T2,FBROWLEN ; skip rows before this one mul S0,T2 add XL,ML ; this row of frame buffer colours adc XH,MH add XL,T1 ; add index for wanted colour byte ldi T2,0 adc XH,T2 rjmp FBWCOLOUR XYOUTRANGE: ret ; --- X/Y read colour from frame buffer ; parameters: S0: row in frame buffer to extract colour ; T1: LUT reg to set, 0..4 = back/fore/alt1/alt2/alt3 ; returns: T0: colour code from FBRCOLOUR XYRCOLOUR: cpi S0,FBROWS ; row in 0..(FBROWS-1) ? brsh XYOUTRANGE ; >=, dont' colour non-existant row cpi T1,FBROWBEG brsh XYOUTRANGE ; >=, dont' set to non-existant LUT colour ldi XL,low(FBUF) ; begin of frame buffer colours ldi XH,high(FBUF) ldi T2,FBROWLEN ; skip rows before this one mul S0,T2 add XL,ML ; this row of frame buffer colours adc XH,MH add XL,T1 ; add index for wanted colour byte ldi T2,0 adc XH,T2 rjmp FBRCOLOUR ; --- X/Y write character into frame buffer ; parameters: S0: row in frame buffer to place character ; S1: column in frame buffer to place character ; T0: ASCII character for FBWCHAR, left converted XYWCHAR: cpi S0,FBROWS ; row in 0..(FBROWS-1) ? brsh XYOUTRANGE ; >=, dont' draw in non-existant row cpi S1,FBCOLS ; column in 0..(FBCOLS-1) ? brsh XYOUTRANGE ; >=, dont' draw in non-existant column ldi XL,low(FBUF+FBROWBEG) ; begin of frame buffer chars ldi XH,high(FBUF+FBROWBEG) ldi T2,FBROWLEN ; skip rows before this one mul S0,T2 add XL,ML ; this row of frame buffer chars adc XH,MH add XL,S1 ; this column in frame buffer ldi T2,0 adc XH,T2 rjmp FBWCHAR ; --- X/Y read character from frame buffer, not needed up to now ; --- X/Y write immediate string into frame buffer ; parameters: S0: row in frame buffer to place first character of string ; S1: column in frame buffer to place first character of string XYWSTRI: cpi S0,FBROWS ; row in 0..(FBROWS-1) ? brsh XYOUTRANGE ; >=, dont' draw in non-existant row cpi S1,FBCOLS ; column in 0..(FBCOLS-1) ? brsh XYOUTRANGE ; >=, dont' draw in non-existant column ldi XL,low(FBUF+FBROWBEG) ; begin of frame buffer chars ldi XH,high(FBUF+FBROWBEG) ldi T2,FBROWLEN ; skip rows before this one mul S0,T2 add XL,ML ; this row of frame buffer chars adc XH,MH add XL,S1 ; this column in frame buffer ldi T2,0 adc XH,T2 rjmp FBWSTRI ; ------ demo program ; --- draw an blank row ; parameters: S0: row in frame buffer to blank out DEBLANK_1X36: rcall XYWSTRI .dw 36 .db " " ; empty string inc S0 ret ; --- draw entire ASCII char set ; parameters: S0: row in frame buffer to begin at ; S1: column in frame buffer to begin at DEASC_3X32: ; show 95 ASCII chars 32..126 (0x20..0x7E) in 3 rows of 32, 127 as blank rcall XYWSTRI .dw 32 .db " !", 0x22, "#$%&", 0x27, "()*+,-./0123456789:;<=>?" inc S0 rcall XYWSTRI .dw 32 .db "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_" inc S0 rcall XYWSTRI .dw 32 .db "`abcdefghijklmnopqrstuvwxyz{|}~ " inc S0 ret DEASC_3X36: rcall DEBLANK_1X36 ; get rid of left over dots at both ends rcall DEBLANK_1X36 rcall DEBLANK_1X36 subi S0,3 ; back up the 3 rows, for drawing actual stuff subi S1,-2 ; indent (36-32)/2=2 unused characters rcall DEASC_3X32 subi S1,2 ; and outdent back ret ; --- draw black row to separate sections ; parameters: S0: row in frame buffer to black out DEBLACK_1X36: ldi T0,0x00 ; black ldi T1,0 ; set background to black, leave unused foreground rcall XYWCOLOUR rjmp DEBLANK_1X36 ; --- setup 6 black texts on rainbow coloured row backgrounds ; parameters: S0: row in frame buffer to begin at ; T0: background colour DERAIN_1: ldi T1,0 ; set background to colour T0 rcall XYWCOLOUR ldi T0,0x00 ; black ldi T1,1 ; set foreground to black rcall XYWCOLOUR inc S0 ret ; parameters: S0: row in frame buffer to begin at DERAIN_6: ldi T0,0x30 ; red rcall DERAIN_1 ldi T0,0x3C ; yellow rcall DERAIN_1 ldi T0,0x0C ; green rcall DERAIN_1 ldi T0,0x0F ; cyan rcall DERAIN_1 ldi T0,0x03 ; blue rcall DERAIN_1 ldi T0,0x33 ; magenta rcall DERAIN_1 subi S0,6 ; back up the 6 rows, for drawing stuff over them ret ; --- draw ASCII art wave(s) ; parameters: S0: row in frame buffer to begin at ; S1: column in frame buffer to begin at DEWAVE_1X19: rcall XYWSTRI .dw 19 .db "-", 0x27, "__`-.__.-", 0x27, "__`-.__", 0 inc S0 ret DEWAVE_6X19: rcall DEWAVE_1X19 ; make 6 rows with this (and all other graphics) rcall DEWAVE_1X19 rcall DEWAVE_1X19 rcall DEWAVE_1X19 rcall DEWAVE_1X19 rcall DEWAVE_1X19 ret ; --- draw separator(s) of ASCII wave(s) from blockgraphic logo ; parameters: S0: row in frame buffer to begin at ; S1: column in frame buffer to begin at DESEPAR_1X1: ldi T0,BG1111 ; block graphics all 4 2x2pixel set to foreground rcall XYWCHAR inc S0 ret DESEPAR_6X1: rcall DESEPAR_1X1 ; make 6 rows with this (and all other graphics) rcall DESEPAR_1X1 rcall DESEPAR_1X1 rcall DESEPAR_1X1 rcall DESEPAR_1X1 rcall DESEPAR_1X1 ret ; --- draw logo put together from 2x2pixel blockgraphics ; parameters: S0: row in frame buffer to begin at ; S1: column in frame buffer to begin at DELOGO_6x16: ; print this text using 1/2+7+1/2 * 4x12pixels, of 2x2pixel blockgraphics ; = space used 1/2+7+1/2 * 2x6 chars = 16 chars width, always 6 rows ; .... ........ ........ ........ .... .... ........ ........ ........ .... ; .... ()()()() ()()()() ()()()() ()() ()() ()()()() ()()()() ()()()() .... ; .... ........ ........ ........ .... .... ........ ........ ........ .... ; .... ..()().. ........ ....().. ..() .... ..()..() ....()() ....().. .... ; .... ()...... ........ ..().... ..() .... ..()..() ..().... ..()..() .... ; .... ..().... ..().... ()()().. ()() ().. ..()..() ..().... ..()..() .... ; .... ....().. ()..().. ..().... ..() .... ..()..() ..()..() ..()()() .... ; .... ....().. ()..().. ..().... ..() .... ....().. ..()..() ..()..() .... ; .... ()().... ..().... ..().... .... ().. ....().. ....()() ..()..() .... ; .... ........ ........ ........ .... .... ........ ........ ........ .... ; .... ()()()() ()()()() ()()()() ()() ()() ()()()() ()()()() ()()()() .... ; .... ........ ........ ........ .... .... ........ ........ ........ .... rcall XYWSTRI .dw 16 .db BG0000, BG0011, BG0011, BG0011, BG0011, BG0011, BG0011, BG0011 .db BG0011, BG0011, BG0011, BG0011, BG0011, BG0011, BG0011, BG0000 inc S0 rcall XYWSTRI .dw 16 .db BG0000, BG0001, BG0010, BG0000, BG0000, BG0000, BG0010, BG0001 .db BG0000, BG0001, BG0001, BG0000, BG0011, BG0000, BG0010, BG0000 inc S0 rcall XYWSTRI .dw 16 .db BG0000, BG1001, BG0000, BG0001, BG0000, BG0111, BG0010, BG0111 .db BG0010, BG0101, BG0101, BG0101, BG0000, BG0101, BG0101, BG0000 inc S0 rcall XYWSTRI .dw 16 .db BG0000, BG0000, BG1010, BG1010, BG1010, BG0101, BG0000, BG0101 .db BG0000, BG0100, BG0110, BG0101, BG0101, BG0101, BG1101, BG0000 inc S0 rcall XYWSTRI .dw 16 .db BG0000, BG1100, BG0000, BG0100, BG0000, BG0100, BG0000, BG0000 .db BG1000, BG0000, BG1000, BG0000, BG1100, BG0100, BG0100, BG0000 inc S0 rcall XYWSTRI .dw 16 .db BG0000, BG1100, BG1100, BG1100, BG1100, BG1100, BG1100, BG1100 .db BG1100, BG1100, BG1100, BG1100, BG1100, BG1100, BG1100, BG0000 inc S0 ret ; --- draw graphics, wave(s) + seprator(s) + logo ; parameters: S0: row in frame buffer to begin at ; S1: column in frame buffer to begin at DEGRAPH_6X36: rcall DEWAVE_6X19 subi S0,6 ; back up the 6 rows, for separator subi S1,-19 ; indent by 19 columns, jump over ASCII wave(s) rcall DESEPAR_6X1 subi S0,6 ; back up the 6 rows, for logo inc S1 ; indent by 1 column, jump over separator(s) rcall DELOGO_6X16 subi S1,20 ; and back, both indents, 19 and 1 ret ; --- draw announce message ; parameters: S0: row in frame buffer to begin at ; S1: column in frame buffer to begin at DEANN_3X36: rcall XYWSTRI .dw 36 .db "ATmega32 uC + SoftVGA = 400line 70Hz" inc S0 rcall XYWSTRI .dw 36 .db "18.432MHz, 3clk/px, 4px/char, 40char" inc S0 rcall XYWSTRI .dw 36 .db "//neil.franklin.ch/Projects/SoftVGA/" inc S0 ret ; --- setup 7 RBG+derivatives coloured texts on black row backgrounds ; parameters: S0: row in frame buffer to begin at ; T0: main foreground colour, rest of colours derived from this DERGB_1: push T0 ; save for multiple reuse ldi T0,0x00 ; black ldi T1,0 ; set background to black rcall XYWCOLOUR pop T0 push T0 ldi T1,1 ; set foreground to colour T0 rcall XYWCOLOUR pop T0 push T0 andi T0,0x2A ; 2/3 bright, kill bit0 of each DAC ldi T1,2 ; set alternate foreground colour 1 rcall XYWCOLOUR pop T0 push T0 ori T0,0x2A ; 2/3 pastel, force bit1 of all DACs ldi T1,3 ; set alternate foreground colour 2 rcall XYWCOLOUR pop T0 andi T0,0x15 ; 1/3 pastel, force bit0 of all DACs ldi T1,4 ; set alternate background colour 3 rcall XYWCOLOUR inc S0 ret ; parameters: S0: row in frame buffer to begin at DERGB_7: ldi T0,0x03 ; blue rcall DERGB_1 ldi T0,0x0C ; green rcall DERGB_1 ldi T0,0x0F ; cyan rcall DERGB_1 ldi T0,0x30 ; red rcall DERGB_1 ldi T0,0x33 ; magenta rcall DERGB_1 ldi T0,0x3C ; yellow rcall DERGB_1 ldi T0,0x3F ; white rcall DERGB_1 subi S0,7 ; back up the 7 rows, for drawing stuff over them ret ; --- draw multistyle/-coloured text ; parameters: S0: row in frame buffer to begin at ; S1: column in frame buffer to begin at DEMULT_1X36: rcall XYWSTRI .dw 36 .db "n", "o", "r", INV, "i", "n", "v", INV .db "n", "o", ULIN, "u", "l", "i", "n", ULIN .db "n", "o", FG1, "f", "g", "1", FG1, "n" .db "o", FG2, "f", "g", "2", FG2, "n", "o" .db BG3, "b", "g", "3" inc S0 ret DEMULT_8X36: rcall DEMULT_1X36 ; make 7 rows with this rcall DEMULT_1X36 rcall DEMULT_1X36 rcall DEMULT_1X36 rcall DEMULT_1X36 rcall DEMULT_1X36 rcall DEMULT_1X36 rcall DEMULT_1X36 ret ; --- rotate ASCII char set background colour ; parameters: S0: row in frame buffer to rotate colour DEROTBG_1: ldi T1,0 ; background rcall XYRCOLOUR ; read existing colour subi T0,-0x10 ; only modify red, 4 steps rcall XYWCOLOUR ; write new colour ret ; parameters: S2: frame count 0..255 DEROTBG_3: sbi PORTD,PIND4 cbi DDRD,PIND4 sbic PIND,PIND4 ; only if switch on PortD4 activated ret ; no switch, no rotate, abort mov T0,S2 ; rotate every 64th frame, ~1s andi T0,0x3F ; frame 0|64|128|192|0|...? brne DERONOT ; no, do nothing push S0 ; yes, rotate background colour ldi S0,0 ; first row of ASCII char set rcall DEROTBG_1 inc S0 rcall DEROTBG_1 inc S0 rcall DEROTBG_1 pop S0 DERONOT: ret ; --- move andalusian video snail across blank row ; parameters: S2: frame count 0..255 DESNAIL_1X36: push S0 push S1 ldi S0,14 ; black row just after announce message ldi S1,0 rcall DEBLANK_1X36 ; restore blank in case switch off, remove old snail subi S0,1 ; back up the row, for drawing new snail sbi PORTD,PIND3 cbi DDRD,PIND3 sbic PIND,PIND3 ; only if switch on PortD3 activated rjmp DESNNOT ; no switch, no rotate, abort lds S1,DESNPOS ; wherever snail was drawn last time mov T0,S2 ; move every 16th frame, ~1/4s andi T0,0x0F ; frame 0|16|32|48|..|240|0|...? brne DESNSTAY ; no, leave column unchanged inc S1 ; yes, move snail one column to the right cpi S1,FBCOLS ; after last column? brlo DESNCOLOK ; <, column OK ldi S1,0 ; >=, back to the left column DESNCOLOK: sts DESNPOS,S1 ; store any changes DESNSTAY: rcall XYWSTRI .dw 4 .db "_@_/" ; the traditional 1-liner ASCII art inc S0 DESNNOT: pop S1 pop S0 ret ; --- bounce ball in middle of graohic DEBOFONT: .equ DEBOFS1001 = UN0110 ; vertical line .equ DEBOFR1001 = DEBOFS1001 ldi T0,DEBOFS1001 ldi T1,FS1001 call CICHAR .equ DEBOFS0110 = UN0111 ; horizontal line .equ DEBOFR0110 = DEBOFS0110 ldi T0,DEBOFS0110 ldi T1,FS0110 call CICHAR .equ DEBOFS0011 = UN1000 ; top/left square corner ldi T0,DEBOFS0011 ldi T1,FS0011 call CICHAR .equ DEBOFS0101 = UN1001 ; top/right square corner ldi T0,DEBOFS0101 ldi T1,FS0101 call CICHAR .equ DEBOFS1010 = UN1010 ; bottom/left square corner ldi T0,DEBOFS1010 ldi T1,FS1010 call CICHAR .equ DEBOFS1100 = UN1011 ; bottom/right square corner ldi T0,DEBOFS1100 ldi T1,FS1100 call CICHAR .equ DEBOFR0011 = UN1100 ; top/left round corner ldi T0,DEBOFR0011 ldi T1,FR0011 call CICHAR .equ DEBOFR0101 = UN1101 ; top/right round corner ldi T0,DEBOFR0101 ldi T1,FR0101 call CICHAR .equ DEBOFR1010 = UN1110 ; bottom/left round corner ldi T0,DEBOFR1010 ldi T1,FR1010 call CICHAR .equ DEBOFR1100 = UN1111 ; bottom/right round corner ldi T0,DEBOFR1100 ldi T1,FR1100 call CICHAR ret ; parameters: S0: row in frame buffer to begin at ; S1: column in frame buffer to begin at DEBOFIELD_1X10: rcall XYWSTRI .dw 10 .db BG0000, DEBOFS1001, BG0000, BG0000, BG0000, BG0000 .db BG0000, BG0000, DEBOFS1001, BG0000 inc S0 ret DEBOFIELD_8X10: rcall XYWSTRI .dw 10 .db BG0000, DEBOFS0011, DEBOFS0110, DEBOFS0110, DEBOFS0110, DEBOFS0110 .db DEBOFS0110, DEBOFS0110, DEBOFS0101, BG0000 inc S0 rcall DEBOFIELD_1X10 rcall DEBOFIELD_1X10 rcall DEBOFIELD_1X10 rcall DEBOFIELD_1X10 rcall DEBOFIELD_1X10 rcall DEBOFIELD_1X10 rcall XYWSTRI .dw 10 .db BG0000, DEBOFS1010, DEBOFS0110, DEBOFS0110, DEBOFS0110, DEBOFS0110 .db DEBOFS0110, DEBOFS0110, DEBOFS1100, BG0000 inc S0 ret DEBOBALL_3X3: rcall XYWSTRI .dw 3 .db DEBOFR0011, DEBOFR0110, DEBOFR0101, 0 inc S0 rcall XYWSTRI .dw 3 .db DEBOFR1001, BG0000, DEBOFR1001, 0 inc S0 rcall XYWSTRI .dw 3 .db DEBOFR1010, DEBOFR0110, DEBOFR1100, 0 inc S0 ret ; parameters: S2: frame count 0..255 DEBOUNCE_8X36: push S0 push S1 ldi S0,3 ; blank row before graphics ldi S1,0 rcall DEBLACK_1X36 ; restore blank in case switch off, remove old rcall DEGRAPH_6X36 ; restore graphics in case switch off, remove old rcall DEBLACK_1X36 ; restore blank in case switch off, remove old subi S0,8 ; back up the 1+6+1=8 rows, for box and ball sbi PORTD,PIND2 cbi DDRD,PIND2 sbic PIND,PIND2 ; only if switch on PortD2 activated rjmp DEBONOT ; no switch, no new field and ball, abort call DEBOFONT ldi S1,11 ; cut back ASCII waves to 11 wide ... rcall DEBOFIELD_8X10 ; ... and empty bounce field subi S0,8 ; back up the 8 rows, for drawing ball inc S0 ; enter field vertically inc S1 ; and also horizontally inc S1 mov T0,S2 ; move every 8th frame, ~1/8s lsr T0 ; so divide by 8 lsr T0 lsr T0 mov T1,T0 ; save for column direction andi T0,0x07 ; extract 3 bits for 8 (2*4) ball positions ldi T2,7 sbrc T0,2 ; range 4..7 or 0..3 sub T2,T0 ; 4..7 -> 3..0 sbrc T0,2 ; range 4..7 or 0..3 mov T0,T2 add S0,T0 ; add to row direction lsr T1 ; every 16th frame, so divide by 16 inc T1 ; phase shift by 1/8th, 45 degrees andi T1,0x07 ; extract 3 bits for 8 (2*4) ball positions ldi T2,7 sbrc T1,2 ; range 4..7 or 0..3 sub T2,T1 ; 4..7 -> 3..0 sbrc T1,2 ; range 4..7 or 0..3 mov T1,T2 add S1,T1 ; add to column direction rcall DEBOBALL_3X3 DEBONOT: pop S1 pop S0 ret ; --- blink cursor in first announce row ; parameters: S2: frame count 0..255 DECURSOR_3X36: push S0 push S1 ldi S0,11 ; first row of announce message ldi S1,0 rcall DEANN_3X36 ; restore announce in case switch off, remove old subi S0,3 ; back up the 2 rows, for drawing separator sbi PORTD,PIND1 cbi DDRD,PIND1 sbic PIND,PIND1 ; only if switch on PortD1 activated rjmp DECUNOT ; no switch, no new cursor, abort sbrc S2,0 ; alternating 2 frames draw cursor or character rjmp DECUNOT ; 2nd, do nothing, draws character sbrc S2,4 ; alternating 2*16 frames blink on or off, ~1/2s rjmp DECUNOT ; 16..31, do nothing, cursor off lds S1,DECUPOS ; wherever cursor was drawn last time mov T0,S2 ; move every 2nd blink, every 64th frame, ~1s andi T0,0x3F ; frame 0|64|128|192|0|...? brne DECUSTAY ; no, leave column unchanged inc S1 ; yes, move cursor one column to the right cpi S1,FBCOLS ; after last column? brlo DECUCOLOK ; <, column OK ldi S1,0 ; >=, back to the left column DECUCOLOK: sts DECUPOS,S1 ; store any changes DECUSTAY: ;ldi T0,0x5F ; draw "_" character as cursor ldi T0,BG1111 ; draw full block character as cursor rcall XYWCHAR DECUNOT: pop S1 pop S0 ret ; --- switch font of all digits on screen ; parameters: S2: frame count 0..255 .equ ASCIIZERO = 0x30 .equ ASCIIAFTERNINE = 0x3A DESQUAREFONTROM: ; ASCII "0" 0x30 .db SEGM0000, SEGM1110, SEGM1010, SEGM1010 .db SEGM1010, SEGM1010, SEGM1110, SEGM0000 ; ASCII "1" 0x31 .db SEGM0000, SEGM1100, SEGM0100, SEGM0100 .db SEGM0100, SEGM0100, SEGM1110, SEGM0000 ; ASCII "2" 0x32 .db SEGM0000, SEGM1110, SEGM0010, SEGM1110 .db SEGM1000, SEGM1000, SEGM1110, SEGM0000 ; ASCII "3" 0x33 .db SEGM0000, SEGM1110, SEGM0010, SEGM1110 .db SEGM0010, SEGM0010, SEGM1110, SEGM0000 ; ASCII "4" 0x34 .db SEGM0000, SEGM1010, SEGM1010, SEGM1010 .db SEGM1110, SEGM0010, SEGM0010, SEGM0000 ; ASCII "5" 0x35 .db SEGM0000, SEGM1110, SEGM1000, SEGM1110 .db SEGM0010, SEGM0010, SEGM1110, SEGM0000 ; ASCII "6" 0x36 .db SEGM0000, SEGM1110, SEGM1000, SEGM1110 .db SEGM1010, SEGM1010, SEGM1110, SEGM0000 ; ASCII "7" 0x37 .db SEGM0000, SEGM1110, SEGM0010, SEGM0010 .db SEGM0010, SEGM0010, SEGM0010, SEGM0000 ; ASCII "8" 0x38 .db SEGM0000, SEGM1110, SEGM1010, SEGM1110 .db SEGM1010, SEGM1010, SEGM1110, SEGM0000 ; ASCII "9" 0x39 .db SEGM0000, SEGM1110, SEGM1010, SEGM1010 .db SEGM1110, SEGM0010, SEGM1110, SEGM0000 DEFONT_0_9: ; restore/load standard font, chars 0x30..0x39 ldi ZL,low(FONTROM*2+ASCIIZERO*ROWSEGMENTS) ; *2 because of lpm ldi ZH,high(FONTROM*2+ASCIIZERO*ROWSEGMENTS) ldi T0,ASCIIZERO ; for char = '0'..'9' DEFOSTDLOOP: rcall FOCHAR ; copy char T0 from Flash to SRAM inc T0 ; next index cpi T0,ASCIIAFTERNINE brne DEFOSTDLOOP sbi PORTD,PIND0 cbi DDRD,PIND0 sbic PIND,PIND0 ; only if switch on PortD0 activated rjmp DEFONOT ; no switch, no square font, abort mov T0,S2 ; show on/off for 32+32 frames, blink ~1s andi T0,0x20 ; frame 32..63, second half of blink? brne DEFONOT ; no, leave standard font unchanged ; set/load square font, chars 0x30..0x39 ldi ZL,low(DESQUAREFONTROM*2) ; *2 because of lpm ldi ZH,high(DESQUAREFONTROM*2) ldi T0,ASCIIZERO ; for char = '0'..'9' DEFOSQUARELOOP: rcall FOCHAR ; copy char T0 from Flash to SRAM inc T0 ; next index cpi T0,ASCIIAFTERNINE brne DEFOSQUARELOOP DEFONOT: ret ; --- show something in frame buffer and animate it DEMO: ; no push/pop S0..S2 here, as routine never exited, endless loop ldi S0,0 ; row = 0, start at top of screen ldi S1,0 ; column = 0, start at left of screen rcall DEASC_3X36 ; rows 0..2 rcall DEBLACK_1X36 ; row 3 rcall DERAIN_6 ; rows 4..9 rcall DEGRAPH_6X36 rcall DEBLACK_1X36 ; row 10 rcall DEANN_3X36 ; rows 11..13 rcall DEBLACK_1X36 ; row 14 rcall DERGB_7 ; rows 15..22 rcall DEMULT_8X36 rcall DEBLACK_1X36 ; row 23 ; leave remaining 6 rows 24..29 of dots unused ldi S2,0 ; frame counter/timer, no conflict with S0 or S1 sts DESNPOS,S2 sts DECUPOS,S2 ENDLESS: rcall VRWAIT ; wait for next frame vertical blank inc S2 ; update frame counter rcall DEROTBG_3 rcall DESNAIL_1X36 rcall DEBOUNCE_8X36 rcall DECURSOR_3X36 rcall DEFONT_0_9 rjmp ENDLESS ; ------ main program ; --- handle reset, initialise system, start display, main loop RESET: ; get stack ready, for calls and interrupts ldi XL,low(ENDOFSTACK-1) ldi XH,high(ENDOFSTACK-1) out SPL,XL out SPH,XH ; and then enable interrupts sei ; set up font rcall FOINIT ; set up frame buffer rcall FBINIT ; set up port(s) for generating sync pulses and control switch rcall SYNCINIT ; set up registers and ports for DACs and control switches rcall CHINIT ; start display timer and ISR rcall DISPLAY ; enter demo, with endless loop, use up background time ; fill frame buffer to show something rjmp DEMO