; vga_threaded.asm - threaded generate VGA text, background for frame updates ; author Neil Franklin, last modification 2008.10.22 ; ------ intro ; --- general drawing sequence ; lines, are the time critical thing ; first visible output pixels ; then horizontal retrace blank right time ; then horizontal retrace pulse time ; then horizontal retrace blank left time, largest spare time ; frame, for consistency with lines ; first visible output lines ; then vertical retrace blank bottom lines ; then vertical retrace pulse lines ; then vertical retrace blank top lines, largest spare time ; --- hardware specifics ; we are using an ATmega32 .include "m32def.inc" ; needed fuses, 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 ; wired ports and port bits .equ VGADPORT = PORTC ; 3 2bit colour DACs (bit0 to 1k5ohm, bit1 to 680ohm) ; write fast, must be same port, use full 8bit out ; bits 7+6 must accept overwrite with 0b00 .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 .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 .equ MODPORT = PORTD ; 1 mode switch TTL in with Pull-Up .equ MODPIN = PIND .equ MODDDR = DDRD .equ MODBIT = PIND6 ; 0(jumper) = double scan, 1(default) = 2nd blank ; PIND6 is next to GND on 10pin port header ; --- prepare coding conventions ; we use an systematic naming of AVR registers .include "avr_registers.inc" ; ------ handle reset and interrupts (all identical as not used) ; --- set up vectors .cseg .org MINROM jmp RESET jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT jmp UNUINT ; --- handle unused interrupts, despite not used 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 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 ; ------ string handling ; --- define an string buffer, "string accumulator", no dynamic alloc yet .equ STRMAXLEN = 80 .dseg STRBUF: .byte 2 ; buffer length, self documenting ; must be set to STMAXLEN while init .byte 2 ; used length, actually used amount of buffer ; must be cleared to 0 while init .byte STRMAXLEN ; data comes into here .cseg ; --- initialise string subsystem STRINIT: ldi XL,low(STRBUF) ; at present only one string buffer ldi XH,low(STRBUF) ldi ZL,low(STRMAXLEN) ; mark string buffer with length, never changed ldi ZH,high(STRMAXLEN) st X+,ZL st X+,ZH ldi ZL,0 ; mark string buffer used as empty, changes with data ldi ZH,0 st X+,ZL st X+,ZH ret ; --- load an string constant into string buffer ; parameters: TOS (old PC) address of string constant (in Flash!) ; format of constant: .dw length, .db data ; these is assembled in, directly after the call STRLDI STRLDI: ; next address in calling program, begin of constant ; must use ZH:ZL because of lpm instr, are temp pop ZH ; call stores low first/top, big endian(!) pop ZL ; so read low as last/top lsl ZL ; correct address for word vs byte addressing rol ZH push AL ; no YH:YL for this as not used as address push AH lpm AL,Z+ ; length of string constant, from program lpm AH,Z+ ldi XL,low(STRBUF) ; data will be copied to the string buffer ldi XH,high(STRBUF) ld MH,X+ ; check if string will fit in buffer ld ML,X+ cp AH,MH ; high(constlen) - high(buflen) brlo STRFITS ; <, is OK breq STRMAYFIT ; =, may fit, test also the low byte movw AH:AL,MH:ML ; >, too long, shorten to constlen = buflen rjmp STRFITS STRMAYFIT: cp AL,ML ; low(constlen) - low(buflen) brlo STRFITS ; <, is OK breq STRFITS ; =, is also OK, just fits movw AH:AL,MH:ML ; >, too long, shorten to constlen = buflen STRFITS: st X+,AL ; record used length of new constant in buffer st X+,AH ; AH:AL set above ; for char = LEN..1 STRLOOP: lpm T0,Z+ ; copy string data, from program st X+,T0 sbiw AH:AL,1 ; next char brne STRLOOP pop AH pop AL bst ZL,0 ; amount of characters dividable by 2 ? brtc STRNOFIL ; yes, OK adiw ZH:ZL,1 ; no, jump over added 0x00 filler character STRNOFIL: lsr ZH ; undo word vs byte address correction ror ZL ; new address in calling program, code after constant ijmp ; ijmp is faster than push push back and then ret ; ------ frame buffer handling ; --- define frame buffer ; row .equ FBROWBEG = 5 ; 5 color bytes a begin of row .equ FBCOLS = 40 ; columns of character code bytes in row .equ FBROWEND = 1 ; 1 abort pseudo character at end of row ; use ASCII DEL, aa=0x7F, hh=0x3F, ll=0x80, cc=0xBF .equ FBROWLEN = FBROWBEG+FBCOLS+FBROWEND ; legth of row in frame buffer ; frame .equ FBROWS = 25 ; rows of characters in entire frame .equ FBFREND = 1 ; no-more-lines pseudo colour byte at end of frame ; use invalid (not 0x00..0x1F) code 0xFF .equ FBSIZE = FBROWS*FBROWLEN+FBFREND ; space used for frame buffer ; uses 25*(5+40+1)+1 = 1151bytes (of ATmega32 2048) .dseg FBUF: .byte FBSIZE .cseg ; --- write colours into frame buffer ; parameters: XH:XL: address where in frame buffer, gets incremented ; T0: colour code, gets truncated to black(0x00)..white(0x3F) FBWCOLOUR: andi T0,VGAWHITE ; prevent false sync signals from broken colour values st X+,T0 ; place this colour ret ; --- write character into frame buffer ; parameters: XH:XL: address where in frame buffer, gets incremented ; T0: ASCII character, left converted to display code FBWCHAR: cpi T0,IVON ; char - IVON (lowest defined, specials pseudo-char) brsh FBBOTTOMOK ; >=, is ok ldi T0,0x2E ; <, replace with ASCII "." to show change FBBOTTOMOK: cpi T0,DEL ; char - DEL (lowest not defined, abort pseudo-char) brlo FBTOPOK ; <, is ok ldi T0,0x2E ; >=, replace with ASCII "." to show change FBTOPOK: bst T0,0 ; convert ASCII code to char code, 8bit rotate right lsr T0 ; .654 3210 -> ..65 4321 (0) bld T0,7 ; -> 0.65 4321 ; XH:XL set by caller st X+,T0 ; place this char ret ; --- write string buffer into frame buffer ; parameters: XH:XL: address where in frame buffer, gets incremented FBWSTR: push AL ; no YH:YL for this, analog to STRLDI push AH ; XH:XL set by caller, contains address in frame buffer ldi ZL,low(STRBUF) ; from the string buffer, must be ZH:ZL as XH:HL gone ldi ZH,high(STRBUF) adiw ZH:ZL,2 ; skip buffer length ld AL,Z+ ; get used length, for char = LEN..1 ld AH,Z+ FBWLOOP: ld T0,X ; check for line wrap cpi T0,0xBF ; code - 0xBF (end of row abort pseudo-char) brne FBWCOPY ; <>, is OK adiw XH:XL,FBROWEND ; =, correct address, skip row end abort pseudo-char ld T0,X ; check for page wrap cpi T0,0xFF ; code - 0xFF (end of frame pseudo-colour) brne FBWBEGIN ; <>, is OK ldi XL,low(FBUF) ; =, correct address, wrap to begin of frame buffer ldi XH,high(FBUF) FBWBEGIN: adiw XH:XL,FBROWBEG ; correct address, skip row begin colour codes FBWCOPY: ld T0,Z+ ; write string data call FBWCHAR ; breaks T0, does X+ subi AL,1 ; next char sbci AH,0 brne FBWLOOP pop AH pop AL ret ; --- initialise frame buffer ; avra fails to make an .equ (or .def) to an register name defined by .def .def FBCOL = R16 ; C0 .def FBROW = R17 ; C1 FBINIT: ldi XL,low(FBUF) ; begin of frame buffer ldi XH,high(FBUF) ldi FBROW,FBROWS ; for ROW = ROWS..1 FBRLOOP: ; default background colour for this row ldi T0,0x03 ; blue call FBWCOLOUR ; breaks T0, does X+ ; default foreground colour for this row ;ldi T0,0x0F ; cyan ;ldi T0,0x1F ; light blue, slight reddish cyan ldi T0,0x1B ; light blue, sligh reddisch and lower green cyan, best ;ldi T0,0x2B ; light blue, low red+green white call FBWCOLOUR ; breaks T0, does X+ ; default alternate colours for this row, progressively "stronger" ldi T0,0x0F ; cyan call FBWCOLOUR ; breaks T0, does X+ ldi T0,0x3F ; white call FBWCOLOUR ; breaks T0, does X+ ldi T0,0x3C ; yellow call FBWCOLOUR ; breaks T0, does X+ ; blank out the frame buffer ldi FBCOL,FBCOLS ; for COL = COLS..1 FBCLOOP: ldi T0,0x2E ; fill unused space with ASCII "." call FBWCHAR ; breaks T0 (requiring re-load), does X+ dec FBCOL ; next COL brne FBCLOOP ldi T0,0xBF ; end of row abort pseudo-char st X+,T0 ; can not use FBWCHAR because framing invalid char dec FBROW ; next ROW brne FBRLOOP ldi T0,0xFF ; end of frame no-more-lines pseudo-colour st X+,T0 ; can not use FBWCOLOUR because framing invalid colour ret ; ------ X/Y coordinate based frame buffer ; registers for row/coloumn loop counting and parameter passing ; avra fails to make an .equ (or .def) to an register name defined by .def .def XYCOL = R16 ; C0 .def XYROW = R17 ; C1 ; --- X/Y write colours into frame buffer ; parameters: XYROW: where in frame buffer to place colour ; T0: colour code for FBWCOLOUR, gets truncated ; T1: colour to set, 0..4 = BACK/FORE/ALT1/ALT2/ALT3 XYWCOLOUR: cpi XYROW,FBROWS ; XYROW in 0..(FBROWS-1) ? brsh XYOUTRANGE ; >=, dont' colour non-existant row cpi T1,FBROWBEG brsh XYOUTRANGE ; >=, dont' set to non-existant colour ldi XL,low(FBUF) ; begin of frame buffer colours ldi XH,high(FBUF) ldi ZL,FBROWLEN ; this row of frame buffer colours mul XYROW,ZL ; T0/T1 in use for colour and index, T2/T3 no immediate add XL,ML adc XH,MH add XL,T1 ; add index for wanted colour byte ldi T1,0 ; T1 is now free adc XH,T1 jmp FBWCOLOUR XYOUTRANGE: ret ; --- X/Y write character into frame buffer ; parameters: XYROW and XYCOL: where in frame buffer to place character ; T0: ASCII character for FBWCHAR, left converted XYWCHAR: cpi XYROW,FBROWS ; XYROW in 0..(FBROWS-1) ? brsh XYOUTRANGE ; >=, dont' draw in non-existant row cpi XYCOL,FBCOLS ; XYCOL 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 ZL,FBROWLEN ; this row of frame buffer chars mul XYROW,ZL ; T0 in use for ASCII code, T1 not, but ZL as above add XL,ML adc XH,MH add XL,XYCOL ; this character in frame buffer ldi T1,0 ; T1 as above adc XH,T1 jmp FBWCHAR ; --- X/Y write string buffer into frame buffer ; parameters: XYROW and XYCOL: where in frame buffer to place string XYWSTR: cpi XYROW,FBROWS ; XYROW in 0..(FBROWS-1) ? brsh XYOUTRANGE ; >=, dont' draw in non-existant row cpi XYCOL,FBCOLS ; XYCOL 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 ZL,FBROWLEN ; this row of frame buffer chars mul XYROW,ZL ; T0/T1 are not used, but ZL as above add XL,ML adc XH,MH add XL,XYCOL ; this character in frame buffer ldi T1,0 ; T1 as above adc XH,T1 jmp FBWSTR ; ------ drawing, timing helper ; --- wait a while doing nothing, spinloop ; parameters: T0: loop count, 1..256 (actually 1..255,0 where 0=256) ; gets destroyed (reduced to 0), avoid push/pop time ; OK, as seldom reused, so use immediate temp/param register ; timing: (T0-1)*(1+2) + 1*(1+1) + 4 = T0*3+3 clocks ; together with ldi T0, and call WAIT 1+4+(T0*3+3) clocks ; min 1+4+(1*3+3)=11 clocks, for T0=1 ; max 1+4+(256*3+3)=776 clocks, for T0=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! ; T0 set up by caller ; {0} for count = T0..1 WAILOOP: ; nothing in loop, for highest loop resolution, only 3 clocks dec T0 ; {1} next count brne WAILOOP ; {2|1} ret ; {4} ; ------ drawing, vertical retrace ; --- set up port(s) for sync pulses SYNCINIT: ; get data registers ready cbi VGAHPORT,VGAHBIT ; Port = 0 (= Sync off) cbi VGAVPORT,VGAVBIT ; get DDR registers ready sbi VGAHDDR,VGAHBIT ; DDR = 1 (= out) sbi VGAVDDR,VGAVBIT ret ; --- draw vertical retrace top or bottom blank line (or any other blank line) ; timing 480 + 30 + 30 + 5 = 600-55spare BLLINE: ; {480} "draw"/wait blank pixels, V = 0, H = 0, color = blank ldi T0,157 ; {1} call WAIT ; {4+(157*3+3)=478} nop ; {1} ; {30} horizontal retrace left blank, V = 0, H = 0, color = blank ldi T0,7 ; {1} call WAIT ; {4+(7*3+3)=28} ; {30} horizontal retrace pulse, V = 0, H = 1, color = blank sbi VGAHPORT,VGAHBIT ; {1pre+1} cbi VGAVPORT,VGAVBIT ; {2} kill any still active vertical retrace ldi T0,6 ; {1} call WAIT ; {4+(6*3+3)=25} ; {5of60} horizontal retrace right blank, V = 0, H = 0, color = blank cbi VGAHPORT,VGAHBIT ; {1pre+1} ; no wait, give rest of time to caller, 55spare ret ; {4} ; --- draw vertical retrace pulse line ; timing 480 + 30 + 30 + 5 = 600-55spare VPLINE: ; {480} "draw"/wait blank pixels, V = 1, H = 0, color = blank ldi T0,157 ; {1} call WAIT ; {4+(157*3+3)=478} nop ; {1} ; {30} horizontal retrace left blank, V = 1, H = 0, color = blank ldi T0,7 ; {1} call WAIT ; {4+(7*3+3)=28} ; {30} horizontal retrace pulse, V = 1, H = 1, color = blank sbi VGAHPORT,VGAHBIT ; {1pre+1} sbi VGAVPORT,VGAVBIT ; {2} set any not yet active vertical retrace ldi T0,6 ; {1} call WAIT ; {4+(6*3+3)=25} ; {5of60} horizontal retrace right blank, V = 1, H = 0, color = blank cbi VGAHPORT,VGAHBIT ; {1pre+1} ; no wait, give rest of time to caller, 55spare ret ; {4} ; ------ drawing, visible output ; --- set up registers and ports for DACs and mode switch ; registers for colour patterns, are the LUT, used for out VGADPORT,value-reg ; use non-immediate fast/specials vars, no immediate load and permanent use ; overrun into non-immediate interrupt vars, as this will later be ISR ; avra fails to make an .equ (or .def) to an register name defined by .def .def BLANK = R8 ; G0 constant for colour blanking (black) after line .def BACK = R9 ; G1 2 for foreground and background .def FORE = R10 ; G2 .def TEMP = R11 ; G3 temporary for FORE-TEMP- rotate/swap/exchange .def ALT1 = R12 ; G4 3 alternate foreground colours per row .def ALT2 = R13 ; G5 .def ALT3 = R14 ; G6 CHINIT: ; do not set LUT colour registers, they will be set while drawing every row ; but set register for blanking after pixels drawn ldi T0,VGABLACK ; black is all DAC bits set to 0, no signal mov BLANK,T0 ; use this for retrace colour blanking ; VGA DACs - get data register ready out VGADPORT,BLANK ; VGA DACs - get DDR register ready in T0,VGADDDR ori T0,VGAWHITE ; DDR = 1 = out, white is all DAC bits set to 1 out VGADDDR,T0 ; mode switch (double scan / 2nd blank) - get data register ready sbi MODPORT,MODBIT ; Port/Pull-Up = 1 (= on) ; mode switch (double scan / 2nd blank) - get DDR register ready cbi MODDDR,MODBIT ; DDR = 0 (= input) ret ; --- draw character based visible output line ; parameters: XH:XL: address of first char in row ; T1: font line segment offset, 0x00, 0x10, .. 0x70 ; T0 is used for calls to WAIT; T1 need not be pushed ; timing 5*2+8=18pre, 480 + 30 + 30 + 5 = 600-55spare CHLINE: ; {*pre} "predraw" time, before timepoint where first pixel drawing begins ; precompensate this in callers timing ; {10pre} load LUT colour registers for this row ld BACK,X+ ; {2pre} first 5 bytes in row in frame buffer are colours ld FORE,X+ ; {2pre} ld ALT1,X+ ; {2pre} ld ALT2,X+ ; {2pre} ld ALT3,X+ ; {2pre} ; {8pre} get first character code and unthread it ; use standard font unthreading technique, see there for generic case ; but compressed, without 4 clocks of intermixed pixel OUT statements ; and adding variable segment offset, for entering proper segment group ; XH:XL address has already been set up by caller ld ZL,X+ ; {2pre} next (=first) character: get code mov ZH,ZL ; {1pre} next (=first) character: extract top part andi ZH,0x3F ; {1pre} for ATmega32: 0x3F limits address to 64(*2) chars andi ZL,0x80 ; {1pre} next (=first) character: extract bottom part ; T1 pattern has already been set up by caller, because line dependant or ZL,T1 ; {1pre} add in segment offset for this line in row ijmp ; {2pre} next (=first) character: go and draw it ; continuation at next char, repeating above ; {(40*)12} draw characters, 40 char segments, V = 0, H = 0, color = pixel ; for each X+ fetched code: index and exec one line segment in font space ; drawing done by out VGADPORT,colour, one for each pixel, every 3 clocks ; {so per segment time 4 pixels a 3 clocks = 12 clocks/line} ; per character 8 such segments, one for each line in row ; so per segment size 16 instructions * 2 bytes = 128instr/char ; on ATmega32 32k Flash this allows 32k/2/128=128chars, minus used space ; note that with max 6 drawable lines this could reduce to 6*10(+4==64instr ; on ATmega32 this would allow 32k/2/64/=256chars, minus used space ; in font, once per character: ; .org $hhll ; ASCII ".", aa=0xaa, hh=0xhh, ll=0xll, cc=0xcc ; because 8*16=128instr blocks use up lowest 7 address bits ; and we need fast 8bit indexing in unthreading code ; index as (aa/2)*2*128, gives char codes in font ; interleaved as 00 80 01 81 02 82 ... 3F BF ; correct this by 8bit rotate right ASCII to char code ; aa=.654 3210, hh=..65 4321, ll=0... ...., cc=0.65 4321 ; hh=0x00..3F for aa=0x00,02..7E and repeats aa=0x01,03..7F ; ll=0x00 for aa=0x00..3F and 0x80 for aa=0x8x..BF ; no labels after .org, as font code is only ever reached by computed ijmp ; {12} in font, 8 times per character, each line segment: ; 4 clocks write pixels with out VGADPORT,xxxx (xxxx = FORE or BACK) ; between these 4*2 clocks for unthreading next character jump ; out VGADPORT,xxxx ; {1} current character: draw pixel 1 ; ld ZL,X+ ; {2} next character: get character code ; out VGADPORT,xxxx ; {1} current character: draw pixel 2 ; mov ZH,ZL ; {1} next character: copy and extract top part of index ; andi ZH,0x3F ; {1} for ATmega32: 0x3F limits address to 64(*2) chars ; out VGADPORT,xxxx ; {1} current character: draw pixel 3 ; andi ZL,0x80 ; {1} next character: extract bottom part for (64*)2 chars ; ori ZL,0xo0 ; {1} add in line segment offset for this line in row ; o = 0x00 0x10 .. 0x70 or o = 0x80 0x90 .. 0xF0 ; out VGADPORT,xxxx ; {1} current character: draw pixel 4 ; ijmp ; {2} next character: go and execute it ; continuation at that chars pixel 1, as above ; nop ; {0} fill rest of unused space, 16-10=6 instruction words ; nop ; no execution time as never run! ; nop ; nop ; nop ; nop ; above continues until an abort pseudo-char code is hit in frame buffer ; that pseudo-char exits the unthreadinging instead of drawing ; must be done this way, because no time for 40 char loop counting ; {30} horizontal retrace left blank, V = 0, H = 0, color = blank ; pseudo-char runs time critical out VGADPORT,BLANK then jumps back ; .org $hhll ; ASCII ".", aa=0xaa, hh=0xhh, ll=0xll, cc=0xcc ; no labels after .org, as font code is only ever reached by computed ijmp ; this code is in abort pseudo-character ; out VGADPORT,BLANK ; {1} switch off whatever was last background colour ; jmp CHEND ; {3} go back to CHLINE to make horizontal retrace pulse ; nop ; {0} fill rest of unused space, 16-1-2=13 instruction words ; nop ; no execution time as never run! ; nop ; followd by annother 10 nop instructions! ; like for every other char this is repeated for 8 line pseudo-segments CHEND: ldi T0,5 ; {1} call WAIT ; {4+(5*3+3)=22} nop ; {2} nop ; {30} horizontal retrace pulse, V = 0, H = 1, color = blank sbi VGAHPORT,VGAHBIT ; {1pre+1} cbi VGAVPORT,VGAVBIT ; {2} kill any still active vertical retrace ; never the case, but stay consistent with BLLINE ldi T0,6 ; {1} call WAIT ; {4+(6*3+3)=25} ; {5of60} horizontal retrace right blank, V = 0, H = 0, color = blank cbi VGAHPORT,VGAHBIT ; {1pre+1} ; no wait, give rest of time to caller, 55spare ret ; {4} ; --- draw pair of character based visible output lines ; belonging to one font line, double scan or 2nd line blank ; parameters: XH:XL: address of first char in row ; T1: font line segment offset, 0x00, 0x10, .. 0x70 ; T0 is used for calls to WAIT; T1 need not be pushed ; timing 4+18=22pre, 600 + 480 + 30 + 30 + 5 = 1200-55spare ; no call+ret at end, as jmp does same and is faster {3 instead of 4+4} CHPAIR: ; first line of linepair call CHLINE ; {4pre+(18pre+600-55spare)} ldi T0,7 ; {1} call WAIT ; {4+(7*3+3)=28} nop ; {2} nop ; second line of linepair, same segment, same T1 sbiw XH:XL,FBROWLEN ; {2pre} return to frame buffer row begin, undo all X+ ; switch for double scan or 2nd blank sbis MODPIN,MODBIT ; {1|3pre} 1(default) = 2nd line blank ; 0(jumpered) = double scan jmp CHLINE ; {3pre+(18pre+600-55spare)} double scan: draw chars again ; last line, no wait, give rest of time to caller, 55spare ; {16pre} correction, as BLLINE has no 18pre, unlike CHLINE ; {16pre} + {3-1=2pre} from the sbis = {18pre} ldi T0,2 ; {1pre} call WAIT ; {4+(2*3+3)=13pre} nop ; {2pre} nop jmp BLLINE ; {3pre+(600-55spare)} 2nd blank: draw an blank ; last line, no wait, give rest of time to caller, 55spare ; ------ drawing, frame timing control loops ; registers for loop counting ; avra fails to make an .equ (or .def) to an register name defined by .def .def FRLINE = R16 ; C0 .def FRROW = R17 ; C1 ; --- draw all output lines of one row ; parameters: XH:XL: address of first char in row ; timing 1+1+1+4+22=29pre, (7*2+1)*600 + (600-55=)545 = 16*600-55spare ; no call+ret at end, as jmp does same and is faster {3 instead of 4+4} ROWDRAW: ldi T1,0x00 ; {1pre} start row with first line segment offset ; loop through 7(of8) linepairs, 8th linepair without wait after it ldi FRLINE,7 ; {1pre} for line-in-row = 7..1 ROWLOOP: movw XH:XL,YH:YL ; {1pre} XH:XL for this line, YH:YL survive for next call CHPAIR ; {4pre+(22pre+600-55spare)} ldi T0,5 ; {1} call WAIT ; {4+(5*3+3)=22} ; segment switching for next linepair subi T1,-0x10 ; {1pre} move line segment offset by 16instr/32bytes andi T1, 0x70 ; {1pre} keep it in range, also after exiting loop dec FRLINE ; {1pre} next line-in-row brne ROWLOOP ; {2|1pre} ; {1+4+22pre, movw + call CHLINE + predraw} nop ; {1pre, postcompensate brne 2|1, as no ldi} ; 8th linepair, last in row, no wait, give rest of time to caller, 55spare movw XH:XL,YH:YL ; {1pre} nop ; {1pre, precompensate jmp 3 instead of call 4} jmp CHPAIR ; {3pre+(22pre+600-55spare)} ; --- draw all output rows of one frame ; timing 2+2+4+29=37pre, 24*8*600 + 7*600 + 545 = 400*600-55spare ; no call+ret at end, as jmp does same and is faster {3 instead of 4+4} FRDRAW: ldi YL,low(FBUF) ; {1} get frame buffer first row begin ldi YH,high(FBUF) ; {1} ; loop through 24(of25) rows, 25th row without wait after it ldi FRROW,FBROWS ; {1} for row-in-frame = (FBROWS-1)..1 dec FRROW ; {1} FRDLOOP: call ROWDRAW ; {4+(29pre+16*600-55spare)} ldi T0,3 ; {1} call WAIT ; {4+(3*3+3)=16} adiw YH:YL,FBROWLEN ; {2pre} go to frame buffer next row begin dec FRROW ; {1pre} next row-in-frame brne FRDLOOP ; {2|1pre} ; {4+29pre, call ROWDRAW + predraw} nop ; {1pre, postcompensate brne 2|1, as no ldi} ; 25th row, last in frame, no wait, give rest of time to caller, 55spare nop ; {1pre, precompensate jmp 3 instead of call 4} jmp ROWDRAW ; {3pre+(29pre+16*600-55spare)} ; --- draw all vertical retrace bottom blank lines of one frame ; timing 1+4=5pre, 11*600 + (600-55=)545 = 12*600-55spare ; no call+ret at end, as jmp does same and is faster {3 instead of 4+4} FRBOTTOM: ; loop through 11(of12) lines, 12th line without wait after it ldi FRLINE,11 ; {1} for line-in-bottom = (12-1)..1 FRBLOOP: call BLLINE ; {4+(600-55spare)} ldi T0,13 ; {1} call WAIT ; {4+(13*3+3)=46} nop ; {1} dec FRLINE ; {1pre} next line-in-bottom brne FRBLOOP ; {2|1pre} ; {4pre, call BLLINE} nop ; {1pre, postcompensate brne 2|1, as no ldi} ; 12th line, last in bottom, no wait, give rest of time to caller, 55spare nop ; {1pre, precompensate jmp 3 instead of call 4} jmp BLLINE ; {3pre+(600-55spare)} ; --- draw all vertical retrace pulse lines of one frame ; timing 1+4=5pre, 12*600 + (600-55=)545 = 13*600-55spare ; no call+ret at end, as jmp does same and is faster {3 instead of 4+4} FRVPULSE: ; loop through 12(of13) lines, 13th line without wait after it ldi FRLINE,12 ; {1} for line-in-vpulse = (13-1)..1 FRVLOOP: call VPLINE ; {4+(600-55spare)} ldi T0,13 ; {1} call WAIT ; {4+(13*3+3)=46} nop ; {1} dec FRLINE ; {1pre} next line-in-vpulse brne FRVLOOP ; {2|1pre} ; {4pre, call VPLINE} nop ; {1pre, postcompensate brne 2|1, as no ldi} ; 13th line, last in vpulse, no wait, give rest of time to caller, 55spare nop ; {1pre, precompensate jmp 3 instead of call 4} jmp VPLINE ; {3pre+(600-55spare)} ; --- draw all vertical retrace top blank lines of one frame ; timing 1+4=5pre, 24*600 + (600-55=)545 = 25*600-55spare ; no call+ret at end, as jmp does same and is faster {3 instead of 4+4} FRTOP: ; loop through 24(of25) lines, 25th line without wait after it ldi FRLINE,24 ; {1} for line-in-top = (25-1)..1 FRTLOOP: call BLLINE ; {4+(600-55spare)} ldi T0,13 ; {1} call WAIT ; {4+(13*3+3)=46} nop ; {1} dec FRLINE ; {1pre} next line-in-bottom brne FRTLOOP ; {2|1pre} ; {4pre, call BLLINE} nop ; {1pre, postcompensate brne 2|1, as no ldi} ; 25th line, last in top, no wait, give rest of time to caller, 55spare nop ; {1pre, precompensate jmp 3 instead of call 4} jmp BLLINE ; {3pre+(600-55spare)} ; --- draw all sections of one frame ; timing 4+37=41pre, (25*8+12+13+24)*600 + 545 = 450*600-55spare ; no call+ret at end, as jmp does same and is faster {3 instead of 4+4} FRAME: call FRDRAW ; {4pre+(37pre+400*600-55spare)} ldi T0,12 ; {1} call WAIT ; {4+(12*3+3)=43} nop ; {2} nop call FRBOTTOM ; {4pre+(5pre+12*600-55spare)} ldi T0,12 ; {1} call WAIT ; {4+(12*3+3)=43} nop ; {2} nop call FRVPULSE ; {4pre+(5pre+13*600-55spare)} ldi T0,12 ; {1} call WAIT ; {4+(12*3+3)=43} nop ; {2} nop ; last section in frame, no wait, give rest of time to caller, 55spare nop ; {1pre, precompensate jmp 3 instead of call 4} jmp FRTOP ; {3pre+(5pre+25*600-55spare)} ; --- main display loop, infinite loop, endlessly draw frames DISPLAY: call FRAME ; {4pre+41pre+(450*600-55spare)} nop ; {8} nop nop nop nop nop nop nop rjmp DISPLAY ; {2} ; {4+41pre, call FRAME + predraw} ; ------ main program ; --- blank line, to separate sections DEBLANK: ldi T0,0x00 ; black ldi T1,0 ; set background call XYWCOLOUR call STRLDI ; empty string .dw 40 .db " " call XYWSTR ; print it inc XYROW ; next row ret ; --- black text on coloured background line, for rainbow ; parameters: T1: background colour DERAIN: ldi T1,0 ; set background call XYWCOLOUR ldi T0,0x00 ; black ldi T1,1 ; set foreground call XYWCOLOUR call XYWSTR ; print it inc XYROW ; next row ret ; --- separator DESEPAR: ldi T0,0x1F ; all 4 2x2pixel set, gives black call XYWCHAR ; print it inc XYROW ; next row ldi T0,0x1F call XYWCHAR inc XYROW ldi T0,0x1F call XYWCHAR inc XYROW ldi T0,0x1F call XYWCHAR inc XYROW ldi T0,0x1F call XYWCHAR inc XYROW ldi T0,0x1F call XYWCHAR inc XYROW ret ; --- logo put together from 2x2pixel subchar graphics ; print this text using 1/2+7+1/2 * 4x12pixels, of 2x2pixel subchar graphics ; = space used 1/2+7+1/2 * 2x6 chars = 16 chars width, 6 rows ; .... ........ ........ ........ .... .... ........ ........ ........ .... ; .... ()()()() ()()()() ()()()() ()() ()() ()()()() ()()()() ()()()() .... ; .... ........ ........ ........ .... .... ........ ........ ........ .... ; .... ..()().. ........ ....().. ..() .... ..()..() ....()() ....().. .... ; .... ()...... ........ ..().... ..() .... ..()..() ..().... ..()..() .... ; .... ..().... ..().... ()()().. ()() ().. ..()..() ..().... ..()..() .... ; .... ....().. ()..().. ..().... ..() .... ..()..() ..()..() ..()()() .... ; .... ....().. ()..().. ..().... ..() .... ....().. ..()..() ..()..() .... ; .... ()().... ..().... ..().... .... ().. ....().. ....()() ..()..() .... ; .... ........ ........ ........ .... .... ........ ........ ........ .... ; .... ()()()() ()()()() ()()()() ()() ()() ()()()() ()()()() ()()()() .... ; .... ........ ........ ........ .... .... ........ ........ ........ .... DELOGO: call STRLDI .dw 16 .db 0x10, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C .db 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x10 call XYWSTR ; print it inc XYROW ; next row call STRLDI .dw 16 .db 0x10, 0x18, 0x14, 0x10, 0x10, 0x10, 0x14, 0x18 .db 0x10, 0x18, 0x18, 0x10, 0x1C, 0x10, 0x14, 0x10 call XYWSTR inc XYROW call STRLDI .dw 16 .db 0x10, 0x19, 0x10, 0x18, 0x10, 0x1E, 0x14, 0x1E .db 0x14, 0x1A, 0x1A, 0x1A, 0x10, 0x1A, 0x1A, 0x10 call XYWSTR inc XYROW call STRLDI .dw 16 .db 0x10, 0x10, 0x15, 0x15, 0x15, 0x1A, 0x10, 0x1A .db 0x10, 0x12, 0x16, 0x1A, 0x1A, 0x1A, 0x1B, 0x10 call XYWSTR inc XYROW call STRLDI .dw 16 .db 0x10, 0x13, 0x10, 0x12, 0x10, 0x12, 0x10, 0x10 .db 0x11, 0x10, 0x11, 0x10, 0x13, 0x12, 0x12, 0x10 call XYWSTR inc XYROW call STRLDI .dw 16 .db 0x10, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13 .db 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x10 call XYWSTR inc XYROW ret ; --- multistyle and multicoloured RGB text, for feature demo ; parameters: T1: main foreground colour, rest of colours derived from this DERGB: mov T2,T0 ; save for multiple reuse ldi T0,0x00 ; black ldi T1,0 ; set background call XYWCOLOUR mov T0,T2 ; full colour ldi T1,1 ; set foreground call XYWCOLOUR mov T0,T2 ; 2/3 bright andi T0,0x2A ; kill bit0 of each DAC ldi T1,2 ; set alternate colour 1 call XYWCOLOUR mov T0,T2 ; 1/3 bright andi T0,0x15 ; kill bit2 of each DAC ldi T1,3 ; set alternate colour 2 call XYWCOLOUR mov T0,T2 ; pastel ori T0,0x2A ; force bit2 of all DACs ldi T1,4 ; set alternate colour 3 call XYWCOLOUR call XYWSTR ; print it inc XYROW ; next row ret ; --- show something in frame buffer DEMO: ldi XYROW,0 ; start at top/left of screen ldi XYCOL,0 ; show all 95 ASCII chars in 3 rows, uses 3+1 rows call STRLDI .dw 40 .db " !", 0x22, "#$%&", 0x27, "()*+,-./0123456789:;<=>? " call XYWSTR inc XYROW call STRLDI .dw 40 .db " @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ " call XYWSTR inc XYROW call STRLDI .dw 40 .db " `abcdefghijklmnopqrstuvwxyz{|}~ " call XYWSTR inc XYROW call DEBLANK ; show ASCII graphic, with rainbow coloured background, uses 6+1 rows ; first 23 charactes of width call STRLDI ; common text pattern .dw 23 .db "-'__`-.__.-'__`-.__.-'_ " ldi T0,0x30 ; red call DERAIN ldi T0,0x3C ; yellow call DERAIN ldi T0,0x0C ; green call DERAIN ldi T0,0x0F ; cyan call DERAIN ldi T0,0x03 ; blue call DERAIN ldi T0,0x33 ; magenta call DERAIN ; show separator, with same rainbow, uses same 6+1 rows ; 1 character of width mov T2,XYROW ; save position mov T3,XYCOL subi XYROW,6 ; back up the 6 rows shared with rainbow ldi XYCOL,23 ; indent by 23 characters call DESEPAR ; show SoftVGA logo, with same rainbow, uses same 6+1 rows ; remaining 16 characters of width mov XYROW,T2 ; restore position first time mov XYCOL,T3 subi XYROW,6 ; back up the 6 rows shared with rainbow ldi XYCOL,24 ; indent by 23 characters call DELOGO mov XYROW,T2 ; restore position second time mov XYCOL,T3 call DEBLANK ; show announce text, uses middle 3 rows, of the 11+3+11 call STRLDI .dw 40 .db "AVR ATmega32 uC + SoftVGA = 400line 70Hz" call XYWSTR inc XYROW call STRLDI .dw 40 .db "18.432MHz, 3clk/pixl, 4pixl/char, 40char" call XYWSTR inc XYROW call STRLDI .dw 40 .db "full ASCII, 4x8pixel font, 2 lines/pixel" call XYWSTR inc XYROW ; show RGB foregrounds with styling and alternative colours, uses 1+7 rows call DEBLANK call STRLDI ; common text pattern .dw 40 .db "nor", IVON, "inv", IVOFF .db "nor", ULON, "ulin" .db ULOFF, "nor", COL1, "2/3" .db COL1, "nor", COL2, "1/3" .db COL2, "nor", COL3, "pas" ldi T0,0x03 ; blue call DERGB ldi T0,0x0C ; green call DERGB ldi T0,0x0F ; cyan call DERGB ldi T0,0x30 ; red call DERGB ldi T0,0x33 ; magenta call DERGB ldi T0,0x3C ; yellow call DERGB ldi T0,0x3F ; white call DERGB ; show website info text, uses 1+2 rows call DEBLANK call STRLDI .dw 40 .db "design and code open source available at" call XYWSTR inc XYROW call STRLDI .dw 40 .db "http://neil.franklin.ch/Projects/SoftVGA" call XYWSTR inc XYROW ret ; --- handle reset, initialise system, enter main loop RESET: ; get stack ready, for calls and interrupts (later not used) ldi ZL,low(MAXRAM) ldi ZH,high(MAXRAM) out SPL,ZL out SPH,ZH ; get interrupts ready, despite not used sei ; set up string handling call STRINIT ; set up frame buffer call FBINIT ; set up port(s) for generating sync pulses call SYNCINIT ; set up registers and ports for DACs and mode switch call CHINIT ; fill frame buffer call DEMO ; enter endless loop, no return back to here rjmp DISPLAY ; ------ fixed calculated address stuff, for font, executed by ijmp, no labels ; overjump program code space, which results in a few unusable character codes ; expected to contain 2*(64-4)-1 chars, 0x04 0x48 0x05 0x59 .. 0x3F, no 0xBF ; enough for 95 printable ASCII chars aa=0x20..0x7E (no non-draw DEL/0x7F) ; and 16 more non-ASCII drawable chars aa=0x10..1F before them ; and 8 more non-ASCII pseudo chars aa=0x08..0F before them ; this allows for 4*2*128=1k instr of code, safely more than is needed ; --- specials pseudo-characters "drawing" routines ; with pre-ASCII specials pseudo-chars from 0x08-0x0F .org $0400 ; ASCII non-draw 08, aa=0x08, hh=0x04, ll=0x00, cc=0x04 ; include specials as separate file, so they may be generated automatically .include "vga_text_specials.inc" ; --- drawable characters drawing routines ; with pre-ASCII drawable chars 0x10 to 0x1F and ASCII chars 0x20 to 0x7E .org $0800 ; pre-ASCII, aa=0x10, hh=0x08, ll=0x00, cc=0x08 ; include font as separate file, so they may be generated automatically .include "vga_text_font.inc" ; --- abort pseudo-characters "drawing" routines .org $3F80 ; non-draw ASCII DEL, aa=0x7F, hh=0x3F, ll=0x80, cc=0xBF .equ DEL = 0x7F ; abort drawing ; 8 line segments repeated identically out VGADPORT,BLANK ; {1} switch off whatever was last background colour jmp CHEND ; {3} go back to CHLINE to make horizontal retrace pulse nop ; {0} fill rest of unused space, 16-1-2=13 instructions nop ; no execution time as never run! nop nop nop nop nop nop nop nop nop nop nop out VGADPORT,BLANK jmp CHEND nop nop nop nop nop nop nop nop nop nop nop nop nop out VGADPORT,BLANK jmp CHEND nop nop nop nop nop nop nop nop nop nop nop nop nop out VGADPORT,BLANK jmp CHEND nop nop nop nop nop nop nop nop nop nop nop nop nop out VGADPORT,BLANK jmp CHEND nop nop nop nop nop nop nop nop nop nop nop nop nop out VGADPORT,BLANK jmp CHEND nop nop nop nop nop nop nop nop nop nop nop nop nop out VGADPORT,BLANK jmp CHEND nop nop nop nop nop nop nop nop nop nop nop nop nop out VGADPORT,BLANK jmp CHEND nop nop nop nop nop nop nop nop nop nop nop nop nop