#include #define begin { #define end } void initialize(void); //all the usual mcu stuff void fillVRAM(void); // a test pattern so we have something to display on startup #pragma regalloc- //register unsigned char isOne @21; // a constant of the number "1" //register unsigned char inBlank @19; // loop counter for Horizontal Blanking //register unsigned char didVsync @20; // flag to only run VSync code once //register unsigned char syncIn @18; // Input from PIND // keep these locked so that we can use assembly instructions // PORTA is SRAM address high byte // PORTB is SRAM address low byte //register unsigned char vramAddrH @16; //register unsigned char vramAddrL @17; #pragma regalloc+ #pragma warn- #pragma savereg- //********************************************************** // external interrupt 0 ISR // if main() is empty, we don't have to save SREG, state, etc. // so automatically save 8 instrs interrupt [EXT_INT0] void ext_interrupt0(void) begin /* wait for 75 cycles at the start of HBLANK (this is ~4.7us) check to see if we're in VSYNC after back-porch, should be either white or black NOTE: this takes 3 cycles, so we lose 1 pixel of screen! v1 == v2 == 1 means white, no vsync v1 == 1 && v2 == 0 means black, in vsync v1 == D.2, v2 == D.3, PIND == IO 0x10 to draw a pixel: (assumes direct link from vram to ntsc encoder) must be less than 3.28 cycles also assumes vramAddr occupy 2 registers, instead of memory (L = R17, H = R16) also assumes register with value 0x01 = r21 since we have 256 pixels, we only need to increment vramAddrL while drawing, can increment vramAddrH during HSYNC */ #asm .macro pushpixel out 0x18,r17 ; low byte to PORTB ;out 0x15,r17 ; low byte to PORTC - for debugging purposes add r17,r21 ; go to next pixel nop ; we need ~3.3 cycles/pixel. This gets us to 3 cycles, close enough ;out 0x15,r20 ; output 0x00 to mem, such that AD724 gets AC-coupled - for debugging .endm .macro push4pixels pushpixel pushpixel pushpixel pushpixel .endm .macro push16pixels push4pixels push4pixels push4pixels push4pixels .endm .macro push64pixels push16pixels push16pixels push16pixels push16pixels .endm .macro push255pixels push64pixels push64pixels push64pixels push16pixels push16pixels push16pixels push4pixels push4pixels push4pixels pushpixel pushpixel pushpixel .endm .macro jumpEqDone in r23,63 sbrc r23,1 jmp ISRdone .endm ; wait for 75 cycles at the start of HBLANK (~4.7us) - 3 cycles/iteration = 25 iterations ldi r19,25 hwait: dec r19 brne hwait ; check to see if its in VSYNC, cuts into video timing: 3 cycles = 1 lost pixel ; if PIND == 0x0C, means white = draw pixels!, no vsync in r18,0x10 ; read from PIND andi r18,0x0C ; isolate the ELM sync pins inputs cpi r18,0x0C ; if its white, draw! breq drawline cpi r20,1 ; check to see if Vsync code has already been run jumpEqDone ldi r20,1 ; set the has-run-vsync flag to 1 ldi r16,0 ; resets vramAddrH ; vsync code here ; can use within 20*63.5us == 20320 cycles, we use way more for serial comm. ; interrupts are disabled within an ISR, so no worries about interrupts ; check if the serial line has new data, and if so, tell the comp to download the picture sbis 0x0B,7 ; check if data on rcx (bit 7) of UCSRA. if no data is waiting, keep displaying rjmp vsync_end in r23,0x0C ; read in the waiting data so we can start receiving pixels ; setup sram for writing again ldi r23,0xFF out 0x14,r23 ; DDRC = 0xFF ldi r23,0x00 out 0x12,r23 ; PORTD = 0x00 for sram write enable-ing xmit_ready: ; these are from stdio:getchar sbis 0x0B,5 ; udre (bit 5) of UCSRA rjmp xmit_ready ldi r23,1 out 0x0C,r23 ; send a 1 to UDR -> computer ; rake in those bytes! ldi r23,0 ; keith addition (cancel xmit) ldi r16,0 ldi r17,0 outerloop: out 0x1B,r16 innerloop: out 0x18,r17 cpi r23, 0x01 ; keith addition (cancel xmit) breq writepixel ; keith addition (cancel xmit) ; these are just for a single byte recv: ; these are from stdio: putchar sbis 0x0B,7 ; rcx (bit 7) of UCSRA rjmp recv in r23,0x0C ; read a byte from UDR/computer writepixel: ; keith addition (cancel xmit) out 0x15,r23 ; send byte to SRAM ; loop maintenance inc r17 ; go to next memory address cpi r17,0x00 ; fill 256 pixels / line brne innerloop inc r16 ; go to next line cpi r16,0x00 ; fill 256 lines / image brne outerloop ; return sram for reading ldi r23,0x00 out 0x14,r23 ; DDRC = 0x00 ldi r23,0x10 out 0x12,r23 ; PORTD = 0x10 for sram write disable-ing ldi r23,0x90 out 0x12,r23 ; PORTD = 0x90 for sram write disable-ing ; flush the serial port read buffer clear: sbis 0x0B,7 ; wait for any data rjmp vsync_end in r23, 0x0C ; read it, clear it rjmp clear vsync_end: ; end of vsync code jmp ISRdone ; draws pixels: data goes right from vram to ntsc encoder ; 3 cycles per pixel, <3.3 needed. assumes r21==0x01 drawline: ; note, screen starts at line 10, 224 lines are visible cpi r16,234 jumpEqDone ldi r20,0 ldi r17,0x00 drawpixel: ;out 0x15,r17 ; low byte to PORTC - old method ;sub r17,r21 ; go to next pixel - old method ;brne drawpixel ; old method - we unrolled this loop to go 4 cycles/pixel -> 3 cycles/pixel push255pixels adc r16,r21 ; increment to next line out 0x1B,r16 ; high byte to PORTA out 0x15,r20 ; reset output ; done until next time to draw ISRdone: sei ; we let the next interrupt interrupt this one sleep ; sleep or infinite loop, doesnt matter #endasm while(1){} // the "front porch" lets the ISR finish before it is called for the next line end #pragma savereg+ #pragma warn+ //********************************************************** //Entry point and task scheduler loop // 3 instructions? void main(void) begin initialize(); //main task scheduler loop (infinite, and empty) // branch predictors will LOVE this // too bad atmels dont have branch predictors while(1) begin end end //********************************************************** void initialize(void) begin long baudrate = 57600; //set up the ports // PORTD is SYNC input // D.2 == V1 sync, D.3 == V2 sync // we only need V1 & V2, do distinguish sync vs blank vs white // we also use D.4 for SRAM write enable (which is active LOW!) // use PORTD to set the rest to 0's DDRD = 0b11110011; PORTD = 0x80; // PORTA is SRAM address high byte // PORTB is SRAM address low byte // PORTC is SRAM data i/o byte // DDRC will need to be negated for reads DDRA = 0xFF; DDRB = 0xFF; DDRC = 0xFF; PORTA = 0; PORTB = 0; PORTC = 0; // setup mcu for serial communication UCSRB = 0x18; UBRRL = (char)(16000000 / (baudrate * 16)-1); // setup INT0 to interrupt on the rising edge // this should be when hsync turns to blanking MCUCR = 0b00000011; // enable the INT0 interrupt GICR = 0b01000000; fillVRAM(); // set PORTC to inputs, from SRAM DDRC = 0x00; PORTD = 0b10010000; #asm ldi r20,0 ; has not run during-vsync code ldi r21,0x01 ; loop increment init ldi r17,0 ; vram address low = 0 ldi r16,0 ; vram address high = 0 #endasm //crank up the ISRs #asm sei #endasm end // fills the VRAM with pixel data // assumes that initialize() has already been run // assumptions: DDRC == 0xFF, vramAddr == 0 // for now, writes junk based on address // since writing a pixel to PORTC will be bRRRGGGBB // thats how we'll format the pixels in VRAM void fillVRAM(void) { // 11 instructions #asm ldi r20,0 ldi r16,0 ldi r17,0 highloop: out 0x1B,r16 ; portA lowloop: out 0x18,r17 ; portB cpi r17,0xFF brne noborder1 out 0x15,r20 ; black pixel rjmp nextpixel1 noborder1: out 0x15,r17 ; portC nextpixel1: inc r17 cpi r17,0x00 brne lowloop inc r16 cpi r16,0x00 brne highloop #endasm /* for(vramAddrH = 0; vramAddrH <= 255; vramAddrH++) { PORTA = vramAddrH; for(vramAddrL = 0; vramAddrL <= 255; vramAddrL++) { PORTB = vramAddrL; PORTC = vramAddrL; } }*/ }