Commit MetaInfo

Revisionb4953c7062a7f3d6e8543375d658f523ae60e285 (tree)
Time2016-12-09 14:39:06
Authoringlorion <homemicro@ingl...>
Commiteringlorion

Log Message

initial import

Change Summary

Incremental Difference

diff -r 000000000000 -r b4953c7062a7 LICENSE
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE Thu Dec 08 21:39:06 2016 -0800
@@ -0,0 +1,20 @@
1+Copyright 2016 Robbert Haarman
2+
3+Permission is hereby granted, free of charge, to any person obtaining
4+a copy of this software and associated documentation files (the
5+"Software"), to deal in the Software without restriction, including
6+without limitation the rights to use, copy, modify, merge, publish,
7+distribute, sublicense, and/or sell copies of the Software, and to
8+permit persons to whom the Software is furnished to do so, subject to
9+the following conditions:
10+
11+The above copyright notice and this permission notice shall be
12+included in all copies or substantial portions of the Software.
13+
14+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff -r 000000000000 -r b4953c7062a7 README
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/README Thu Dec 08 21:39:06 2016 -0800
@@ -0,0 +1,4 @@
1+The Home Micro project - build your own microcomputer at home!
2+
3+The project is made available under the terms of the MIT license. The
4+text of this license can be found in the file LICENSE.
diff -r 000000000000 -r b4953c7062a7 video/Makefile
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/video/Makefile Thu Dec 08 21:39:06 2016 -0800
@@ -0,0 +1,27 @@
1+AVR_AS ?= avr-as
2+AVR_LD ?= avr-ld
3+AVR_OBJCOPY ?= avr-objcopy
4+
5+TARGETS = vga.hex vga-m328.hex vga-m328-ntsc.hex
6+OBJECTS = vga.elf vga.o vga-m328.elf vga-m328.o vga-m328-ntsc.elf vga-m328-ntsc.o
7+
8+all : $(TARGETS)
9+
10+clean :
11+ -rm $(OBJECTS)
12+
13+distclean :
14+ -rm $(OBJECTS) $(TARGETS)
15+
16+.o.elf :
17+ $(AVR_LD) $< -o $@
18+
19+.elf.hex :
20+ $(AVR_OBJCOPY) -O ihex $< $@
21+
22+.s.o :
23+ $(AVR_AS) -mmcu=atmega328 $< -o $@
24+
25+.SUFFIXES : .elf .hex .o .s
26+
27+.PHONY : all clean distclean
diff -r 000000000000 -r b4953c7062a7 video/vga-m328-ntsc.s
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/video/vga-m328-ntsc.s Thu Dec 08 21:39:06 2016 -0800
@@ -0,0 +1,329 @@
1+;;; Generates VGA synchronization signals from an ATmega 328.
2+
3+;;; This program is designed to work with a 14.31818 MHz clock
4+;;; supplied on XTAL1. This requires CKDIV8 to be set to 1
5+;;; and CKSEL[0:3] to 0000b. This can be accomplished by
6+;;; setting lfuse to 0xe0.
7+;;;
8+;;; The program generates vsync on PC5, hsync on PC4, an image active
9+;;; signal on PC3, and an address to load video data from
10+;;; on PB5:PB0:PD7:PD0 (most significant bit in PB5, least
11+;;; significant bit in PD0). PC2 is set to high after initialization
12+;;; has been completed.
13+;;;
14+;;; It provides the following timings (standard VGA timings
15+;;; included for reference).
16+;;;
17+;;; Horizontal:
18+;;; Phase Clocks Time Standard
19+;;; active 364 25.422us 25.422us
20+;;; front porch 9 0.629us 0.636us
21+;;; hsync low 56 3.911us 3.813us
22+;;; back porch 27 1.886us 1.907us
23+;;; line total 456 31.848us 31.778us
24+;;;
25+;;; Vertial:
26+;;; Phase Lines Time Standard
27+;;; active 480 15.287ms 15.253ms
28+;;; front porch 10 0.318ms 0.318ms
29+;;; vsync low 2 0.064ms 0.064ms
30+;;; back porch 33 1.051ms 1.048ms
31+;;; frame total 525 16.720ms 16.683ms
32+;;; refresh rate 59.809Hz 59.940Hz
33+;;;
34+;;; We only actually display graphics in a 320x200 area. We repeat every active
35+;;; line twice, and add 40 inactive lines before and after. Horizontally, we
36+;;; add 22 inactive pixels before and after the active part. In effect, this
37+;;; increases the front porch and back porch values.
38+;;;
39+;;; Loading pixel data is controlled by a shift register that cycles through
40+;;; 8 positions, each active for 1 clock cycle. When active is low, the
41+;;; register is reset to position 0, and the CPU has access to RAM. Once
42+;;; active goes high, the register starts cycling. On positon 2, we start
43+;;; reading a byte from screen RAM, and we write that byte to a shift
44+;;; register on position 3. This repeats every 8 cycles. After accounting
45+;;; for the increased front and back porch values, and the 3 cycle delay
46+;;; from active going high to pixel data actually being loaded, the values
47+;;; we program into the controller become:
48+;;;
49+;;; Horizontal:
50+;;; Phase Clocks Time
51+;;; active 320 22.349us
52+;;; front porch 34 2.375us
53+;;; hsync low 56 3.911us
54+;;; back porch 46 3.213us
55+;;; line total 456 31.848us
56+;;;
57+;;; Vertical:
58+;;; Phase Lines Time
59+;;; active 400 12.739ms
60+;;; front porch 50 1.592ms
61+;;; vsync low 2 0.064ms
62+;;; back porch 73 2.325ms
63+;;; frame total 525 16.720ms
64+;;; refresh rate 59.809Hz
65+
66+ ;; We use in and out to access the DDR* and PORT* registers,
67+ ;; so those offsets are reduced by 0x20 compared to the values
68+ ;; in the datasheet.
69+ .equ DDRB, 0x04
70+ .equ DDRC, 0x07
71+ .equ DDRD, 0x0a
72+ .equ MCUSR, 0x54
73+ .equ PORTB, 0x05
74+ .equ PORTC, 0x08
75+ .equ PORTD, 0x0b
76+ .equ WDTCSR, 0x60
77+
78+__vectors:
79+ ;; Interrupt vectors.
80+ ;; The ATmega 328 has 26 interrupt vectors, each of which occupies
81+ ;; 2 words. Since we don't use interrupts, we set them all to
82+ ;; jump to __start.
83+ jmp __start
84+ jmp __start
85+ jmp __start
86+ jmp __start
87+ jmp __start
88+ jmp __start
89+ jmp __start
90+ jmp __start
91+ jmp __start
92+ jmp __start
93+ jmp __start
94+ jmp __start
95+ jmp __start
96+ jmp __start
97+ jmp __start
98+ jmp __start
99+ jmp __start
100+ jmp __start
101+ jmp __start
102+ jmp __start
103+ jmp __start
104+ jmp __start
105+ jmp __start
106+ jmp __start
107+ jmp __start
108+ jmp __start
109+
110+.global __start
111+__start:
112+ ;; first things first, disable interupts and reset watchdog
113+ cli
114+ wdr
115+ ;; set our outputs low as soon as we can
116+ eor r16, r16
117+ out PORTB, r16
118+ out PORTC, r16
119+ out PORTD, r16
120+ ;; configure PORTB pins 0-5, PORTC pins 0-5, and PORTD pins 0-7 as
121+ ;; outputs.
122+ ldi r16, 0x3f
123+ out DDRB, r16
124+ out DDRC, r16
125+ ldi r16, 0xff
126+ out DDRD, r16
127+ ;; disable the watchdog so we won't suddenly reset
128+ ;; we can only clear WDE after we clear WDRF, so do that first
129+ lds r16, MCUSR
130+ andi r16, 0xf7
131+ sts MCUSR, r16
132+ ;; enable changing the WDTCR register
133+ lds r16, WDTCSR
134+ ori r16, 0x18
135+ sts WDTCSR, r16
136+ ;; finally, actually disable the watchdog
137+ ;; this sets WDTIE and WDE to 0, which disables the watchdog
138+ ;; it also sets WDTIF and WDCE to 0 (clearing the flag and disabling further changes),
139+ ;; and sets WDP to 7 (which shouldn't matter, since we disabled the watchdog anyway).
140+ ldi r16, 0x07
141+ sts WDTCSR, r16
142+ ;; watchdog disabled, enable interrupts again
143+ sei
144+
145+ ;; r16 will be used to track value of PC
146+ ;; bit 5: vsync (set mask: 0x20, clear mask 0xdf)
147+ ;; bit 4: hsync (set mask: 0x10, clear mask 0xef)
148+ ;; bit 3: video (set mask: 0x80, clear mask 0xf7)
149+ ;; we will use r17 to compute a new value for r16
150+ ;; r29:r28 will be used to track the address. This starts at 0.
151+ eor r28, r28
152+ eor r29, r29
153+ ;; r27:r26 will be set to the number of scan lines per frame (525)
154+ ldi r26, 13
155+ ldi r27, 2
156+ ;; r21:r20 will be set to the number of video lines (400)
157+ ldi r20, 144
158+ ldi r21, 1
159+ ;; vsync pulse starts at scan line 450, which is 256 + 194
160+ ldi r18, 194
161+ ;; vsync pulse ends at scan line 452, which is 256 + 196
162+ ldi r19, 196
163+ ;; r25:r24 will be used to keep track of the line number
164+ ;; we initialize them so that we start with vsync low
165+ mov r24, r18
166+ mov r25, r21
167+ ;; at hsync_low, hsync is expected to be low and video disabled.
168+ ;; we're also starting with vsync low. Set r17 so that at the start
169+ ;; of the first active video, we will set bits 2 and 4 high.
170+ ldi r17, 0x14
171+
172+hsync_low:
173+ ;; because we execute an rjmp to get here, we start our cycle count at 2
174+ ;; If r25:r24 < r21:r18, we are before vsync.
175+ cp r25, r21 ; cycle 2
176+ brne no_vsync_msb ; cycle 3
177+ cp r24, r18 ; cycle 4
178+ brlo before_vsync ; cycle 5
179+ ;; If r24 > r19, we are after vsync.
180+ cp r19, r24 ; cycle 6
181+ brlo after_vsync ; cycle 7
182+ ;; Vsync active.
183+ andi r17, 0xdf ; cycle 8
184+ rjmp vsync_computed ; cycle 9
185+no_vsync_msb:
186+ nop ; cycle 5
187+ nop ; cycle 6
188+before_vsync:
189+ nop ; cycle 7
190+ nop ; cycle 8
191+after_vsync:
192+ ori r17, 0x20 ; cycle 9
193+ nop ; cycle 10
194+vsync_computed:
195+ ;; If r25:r24 < r21:r20, we are on an active scan line.
196+ cp r25, r21 ; cycle 11
197+ brlo active_msb ; cycle 12
198+ brne after_active ; cycle 13
199+ cp r24, r20 ; cycle 14
200+ brlo active_lsb ; cycle 15
201+ rjmp not_active ; cycle 16
202+active_msb:
203+ nop ; cycle 14
204+ nop ; cycle 15
205+ nop ; cycle 16
206+active_lsb:
207+ ori r17, 0x08 ; cycle 17
208+ rjmp active_computed ; cycle 18
209+after_active:
210+ nop ; cycle 15
211+ nop ; cycle 16
212+ nop ; cycle 17
213+not_active:
214+ andi r17, 0xf7 ; cycle 18
215+ nop ; cycle 19
216+active_computed:
217+ ;; compute new scan line number
218+ adiw r24, 1 ; cycle 20
219+ ;; wrap around to 0 if we exceeded the number of scan lines
220+ cp r25, r27 ; cycle 22
221+ brlo line_msb_ok ; cycle 23
222+ cp r24, r26 ; cycle 24
223+ brlo line_lsb_ok ; cycle 25
224+ eor r24, r24 ; cycle 26
225+ eor r25, r25 ; cycle 27
226+ eor r28, r28 ; cycle 28
227+ eor r29, r29 ; cycle 29
228+ rjmp line_ok ; cycle 30
229+line_msb_ok:
230+ nop ; cycle 25
231+ nop ; cycle 26
232+line_lsb_ok:
233+ nop ; cycle 27
234+ nop ; cycle 28
235+ nop ; cycle 29
236+ nop ; cycle 30
237+ nop ; cycle 31
238+line_ok:
239+ ;; Compute new address.
240+ ;; If scanline is a multiple of 16, clear lowest 3 bits of address.
241+ ;; E.g. address 327 becomes 320.
242+ ;; Else, subtract 320 from scanline. E.g. 321 becomes 1.
243+ ;; Then, if scanline is even, add 1 to address.
244+ ;; E.g. 1 becomes 2.
245+ mov r22, r24 ; cycle 32
246+ andi r22, 15 ; cycle 33
247+ breq line_multiple16 ; cycle 34
248+ subi r28, 64 ; cycle 35
249+ sbci r29, 1 ; cycle 36
250+ andi r22, 1 ; cycle 37
251+ breq incr_addr ; cycle 38
252+ nop ; cycle 39
253+ rjmp address_adjusted ; cycle 40
254+
255+line_multiple16:
256+ andi r28, 0xf8 ; cycle 36
257+ nop ; cycle 37
258+ nop ; cycle 38
259+ nop ; cycle 39
260+ rjmp address_adjusted ; cycle 40
261+
262+incr_addr:
263+ adiw r28, 1 ; cycle 40
264+
265+address_adjusted:
266+ ;; wait until cycle 56, then set hsync high
267+ ;; that's 14 cycles from now. we need a cycle to set r16,
268+ ;; leaving 13 cycles. That's 4 iterations of a wait loop,
269+ ;; plus one nop.
270+ ldi r22, 4 ; cycle 42
271+keep_hsync_low:
272+ subi r22, 1
273+ brne keep_hsync_low
274+ nop ; cycle 54
275+ ori r16, 0x10 ; cycle 55
276+ out PORTC, r16 ; cycle 56
277+ ;; wait 46 cycles, then set new values for active and vsync
278+ ;; we need 2 cycles to set the output pins, so that leaves
279+ ;; 44 cycles. We use 13 iterations of the wait loop, leaving
280+ ;; 5 cycles after the loop to set output pins.
281+ ldi r22, 13 ; cycle 57
282+back_porch:
283+ subi r22, 1
284+ brne back_porch
285+ mov r16, r17 ; cycle 96
286+ nop ; cycle 97
287+ nop ; cycle 98
288+ nop ; cycle 99
289+ out PORTD, r28 ; cycle 100 (8n+4)
290+ out PORTB, r29 ; cycle 101 (8n+5)
291+ out PORTC, r16 ; cycle 102
292+ nop ; cycle 103
293+ nop ; cycle 104
294+ ;; count for 320 cycles, incrementing the address every
295+ ;; 8 cycles. after that, set active low. we need 2 cycles
296+ ;; to set active low, so that leaves 318 cycles. A wait
297+ ;; loop takes 3 cycles. In our case, we need 4 additional
298+ ;; cycles to increment the address and write it to the
299+ ;; output pins. With the insertion of one nop, that gives
300+ ;; us 8 cycles per iteration of the loop. That means we
301+ ;; need 39 iterations of the loop, plus the last one,
302+ ;; where we don't update the loop counter but do pull
303+ ;; active low.
304+ ldi r22, 39 ; cycle 105
305+active:
306+ adiw r28, 8
307+ nop
308+ out PORTD, r28 ; cycle 109, 117, 125, ... (8n+5)
309+ out PORTB, r29 ; cycle 110, 118, 126, ... (8n+6)
310+ subi r22, 1
311+ brne active
312+
313+ adiw r28, 8 ; cycle 106 + 39 * 8 = 418
314+ nop ; cycle 420
315+ andi r16, 0xf7 ; cycle 421
316+ out PORTC, r16 ; cycle 422
317+ ;; wait 34 cycles, then set hsync low
318+ ;; two cycles to set the pins, leaving 32 cycles to wait.
319+ ;; that's 10 iterations of a wait loop plus 2 nops.
320+ ldi r22, 10 ; cycle 423
321+front_porch:
322+ subi r22, 1
323+ brne front_porch
324+ nop ; cycle 453
325+ andi r16, 0xef ; cycle 454
326+ out PORTC, r16 ; cycle 455
327+ nop ; cycle 456
328+ ;; next scan line
329+ rjmp hsync_low ; cycle 457, also cycle 0
diff -r 000000000000 -r b4953c7062a7 video/vga-m328.s
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/video/vga-m328.s Thu Dec 08 21:39:06 2016 -0800
@@ -0,0 +1,278 @@
1+;;; Generates VGA synchronization signals from an ATmega 328.
2+ ;; vsync doesn't work - always low
3+
4+;;; This program is designed to work with a 12.5 MHz clock
5+;;; supplied on XTAL1. This requires CKDIV8 to be set to 1
6+;;; and CKSEL[0:3] to 0000b. This can be accomplished by
7+;;; setting lfuse to 0xe0.
8+;;;
9+;;; The program generates vsync on PC5, hsync on PC4, an image active
10+;;; signal on PC3, and an address to load video data from
11+;;; on PB5:PB0:PD7:PD0 (most significant bit in PB5, least
12+;;; significant bit in PD0).
13+;;;
14+;;; It provides the following timings (standard VGA timings
15+;;; included for reference).
16+;;;
17+;;; Horizontal:
18+;;; Phase Clocks Time Standard
19+;;; active 320 25.6us 25.422us
20+;;; front porch 8 0.64us 0.636us
21+;;; hsync low 47 3.76us 3.813us
22+;;; back porch 23 1.84us 1.907us
23+;;; line total 398 31.84us 31.778us
24+;;;
25+;;; Vertial:
26+;;; Phase Lines Time Standard
27+;;; active 480 15.283ms 15.253ms
28+;;; front porch 10 0.318ms 0.318ms
29+;;; vsync low 2 0.064ms 0.064ms
30+;;; back porch 33 1.051ms 1.048ms
31+;;; frame total 525 16.716ms 16.683ms
32+;;; refresh rate 59.822Hz 59.940Hz
33+
34+ ;; We use in and out to access the DDR* and PORT* registers,
35+ ;; so those offsets are reduced by 0x20 compared to the values
36+ ;; in the datasheet.
37+ .equ DDRB, 0x04
38+ .equ DDRC, 0x07
39+ .equ DDRD, 0x0a
40+ .equ MCUSR, 0x54
41+ .equ PORTB, 0x05
42+ .equ PORTC, 0x08
43+ .equ PORTD, 0x0b
44+ .equ WDTCSR, 0x60
45+
46+__vectors:
47+ ;; Interrupt vectors.
48+ ;; The ATmega 328 has 26 interrupt vectors, each of which occupies
49+ ;; 2 words. Since we don't use interrupts, we set them all to
50+ ;; jump to __start.
51+ jmp __start
52+ jmp __start
53+ jmp __start
54+ jmp __start
55+ jmp __start
56+ jmp __start
57+ jmp __start
58+ jmp __start
59+ jmp __start
60+ jmp __start
61+ jmp __start
62+ jmp __start
63+ jmp __start
64+ jmp __start
65+ jmp __start
66+ jmp __start
67+ jmp __start
68+ jmp __start
69+ jmp __start
70+ jmp __start
71+ jmp __start
72+ jmp __start
73+ jmp __start
74+ jmp __start
75+ jmp __start
76+ jmp __start
77+
78+.global __start
79+__start:
80+ ;; first things first, disable interupts and reset watchdog
81+ cli
82+ wdr
83+ ;; set our outputs low as soon as we can
84+ eor r16, r16
85+ out PORTB, r16
86+ out PORTC, r16
87+ out PORTD, r16
88+ ;; configure PORTB pins 0-5, PORTC pins 0-5, and PORTD pins 0-7 as
89+ ;; outputs.
90+ ldi r16, 0x3f
91+ out DDRB, r16
92+ out DDRC, r16
93+ ldi r16, 0xff
94+ out DDRD, r16
95+ ;; disable the watchdog so we won't suddenly reset
96+ ;; we can only clear WDE after we clear WDRF, so do that first
97+ lds r16, MCUSR
98+ andi r16, 0xf7
99+ sts MCUSR, r16
100+ ;; enable changing the WDTCR register
101+ lds r16, WDTCSR
102+ ori r16, 0x18
103+ sts WDTCSR, r16
104+ ;; finally, actually disable the watchdog
105+ ;; this sets WDTIE and WDE to 0, which disables the watchdog
106+ ;; it also sets WDTIF and WDCE to 0 (clearing the flag and disabling further changes),
107+ ;; and sets WDP to 7 (which shouldn't matter, since we disabled the watchdog anyway).
108+ ldi r16, 0x07
109+ sts WDTCSR, r16
110+ ;; watchdog disabled, enable interrupts again
111+ sei
112+
113+ ;; r16 will be used to track value of PC
114+ ;; bit 5: vsync (set mask: 0x20, clear mask 0xdf)
115+ ;; bit 4: hsync (set mask: 0x10, clear mask 0xef)
116+ ;; bit 3: video (set mask: 0x80, clear mask 0xf7)
117+ ;; we will use r17 to compute a new value for r16
118+ ;; r29:r28 will be used to track the address. This starts at 0.
119+ eor r28, r28
120+ eor r29, r29
121+ ;; r27:r26 will be set to the number of scan lines per frame (525)
122+ ldi r26, 13
123+ ldi r27, 2
124+ ;; r21:r20 will be set to the number of video lines (480)
125+ ldi r20, 224
126+ ldi r21, 1
127+ ;; vsync pulse starts at scan line 490, which is 256 + 234
128+ ldi r18, 234
129+ ;; vsync pulse ends at scan line 492, which is 256 + 236
130+ ldi r19, 236
131+ ;; r25:r24 will be used to keep track of the line number
132+ ;; we initialize them so that we start with vsync low
133+ mov r24, r18
134+ mov r25, r21
135+ ;; at hsync_low, hsync is expected to be low and video disabled.
136+ ;; we're also starting with vsync low. we've already set the
137+ ;; PORTC pins at the biginning of the program, so just set
138+ ;; r16 to the expected value.
139+ eor r16, r16
140+ ldi r17, 0x10
141+
142+hsync_low:
143+ ;; because we execute an rjmp to get here, we start our cycle count at 2
144+ ;; If r25:r24 < r21:r18, we are before vsync.
145+ cp r25, r21 ; cycle 2
146+ brne no_vsync_msb ; cycle 3
147+ cp r24, r18 ; cycle 4
148+ brlo before_vsync ; cycle 5
149+ ;; If r24 > r19, we are after vsync.
150+ cp r19, r24 ; cycle 6
151+ brlo after_vsync ; cycle 7
152+ ;; Vsync active.
153+ andi r17, 0xdf ; cycle 8
154+ rjmp vsync_computed ; cycle 9
155+no_vsync_msb:
156+ nop ; cycle 5
157+ nop ; cycle 6
158+before_vsync:
159+ nop ; cycle 7
160+ nop ; cycle 8
161+after_vsync:
162+ ori r17, 0x20 ; cycle 9
163+ nop ; cycle 10
164+vsync_computed:
165+ ;; If r25:r24 < r21:r20, we are on an active scan line.
166+ cp r25, r21 ; cycle 11
167+ brlo active_msb ; cycle 12
168+ brne after_active ; cycle 13
169+ cp r24, r20 ; cycle 14
170+ brlo active_lsb ; cycle 15
171+ rjmp not_active ; cycle 16
172+active_msb:
173+ nop ; cycle 14
174+ nop ; cycle 15
175+ nop ; cycle 16
176+active_lsb:
177+ ori r17, 0x08 ; cycle 17
178+ rjmp active_computed ; cycle 18
179+after_active:
180+ nop ; cycle 15
181+ nop ; cycle 16
182+ nop ; cycle 17
183+not_active:
184+ andi r17, 0xf7 ; cycle 18
185+ nop ; cycle 19
186+active_computed:
187+ ;; compute new scan line number
188+ adiw r24, 1 ; cycle 20
189+ ;; wrap around to 0 if we exceeded the number of scan lines
190+ cp r25, r27 ; cycle 22
191+ brlo line_msb_ok ; cycle 23
192+ cp r24, r26 ; cycle 24
193+ brlo line_lsb_ok ; cycle 25
194+ eor r24, r24 ; cycle 26
195+ eor r25, r25 ; cycle 27
196+ eor r28, r28 ; cycle 28
197+ eor r29, r29 ; cycle 29
198+ rjmp line_ok ; cycle 30
199+line_msb_ok:
200+ nop ; cycle 25
201+ nop ; cycle 26
202+line_lsb_ok:
203+ nop ; cycle 27
204+ nop ; cycle 28
205+ nop ; cycle 29
206+ nop ; cycle 30
207+ nop ; cycle 31
208+line_ok:
209+ ;; If line is odd, subtract 40 from address.
210+ sbrc r24, 0 ; cycle 32
211+ rjmp adjust_address ; cycle 33
212+ nop ; cycle 34
213+ rjmp address_adjusted ; cycle 35
214+adjust_address:
215+ sbiw r28, 40 ; cycle 35
216+address_adjusted:
217+ ;; wait until cycle 47, then set hsync high
218+ ;; that's 10 cycles from now. we need two cycles to set hsync,
219+ ;; leaving 8 cycles.
220+ nop ; cycle 37
221+ nop ; cycle 38
222+ nop ; cycle 39
223+ nop ; cycle 40
224+ nop ; cycle 41
225+ nop ; cycle 42
226+ nop ; cycle 43
227+ nop ; cycle 44
228+ ori r16, 0x10 ; cycle 45
229+ out PORTC, r16 ; cycle 46
230+ ;; wait 23 cycles, then set new values for active and vsync
231+ ;; we need 2 cycles to set the output pins, so that leaves
232+ ;; 21 cycles, which equals 7 iterations of a wait loop.
233+ ldi r22, 7 ; cycle 47
234+back_porch:
235+ subi r22, 1
236+ brne back_porch
237+ mov r16, r17 ; cycle 68
238+ out PORTC, r16 ; cycle 69
239+ ;; count for 320 cycles, incrementing the address every
240+ ;; 8 cycles. after that, set active low. we need 2 cycles
241+ ;; to set active low, so that leaves 318 cycles. A wait
242+ ;; loop takes 3 cycles. In our case, we need 4 additional
243+ ;; cycles to increment the address and write it to the
244+ ;; output pins. With the insertion of one nop, that gives
245+ ;; us 8 cycles per iteration of the loop. That means we
246+ ;; need 39 iterations of the loop, plus the last one,
247+ ;; where we don't update the loop counter but do pull
248+ ;; active low.
249+ ;; TODO: Initialize the loop counter before we set
250+ ;; active high.
251+ ;; TODO: Should we move the address increment earlier, too?
252+ ldi r22, 39 ; cycle 70
253+active:
254+ adiw r28, 1
255+ out PORTD, r28
256+ out PORTB, r29
257+ nop
258+ subi r22, 1
259+ brne active
260+
261+ adiw r28, 1 ; cycle 71 + 39 * 8 = 383
262+ out PORTD, r28 ; cycle 385
263+ out PORTB, r29 ; cycle 386
264+ nop ; cycle 387
265+ andi r16, 0xf7 ; cycle 388
266+ out PORTC, r16 ; cycle 389
267+ ;; wait 8 cycles, then set hsync low
268+ ;; two cycles to set the pins, leaving 6 cycles to wait.
269+ nop ; cycle 390
270+ nop ; cycle 391
271+ nop ; cycle 392
272+ nop ; cycle 393
273+ nop ; cycle 394
274+ nop ; cycle 395
275+ andi r16, 0xef ; cycle 396
276+ out PORTC, r16 ; cycle 397
277+ ;; next scan line
278+ rjmp hsync_low ; cycle 398, also cycle 0
diff -r 000000000000 -r b4953c7062a7 video/vga-pinout.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/video/vga-pinout.txt Thu Dec 08 21:39:06 2016 -0800
@@ -0,0 +1,20 @@
1+
2+ /-------------------------\
3+ \ 0 . B+ G+ R+ /
4+ \ S0 - B0 G0 R0 /
5+ \ . V H . . /
6+ \-------------------/
7+
8+Legend:
9+-: No pin, or unconnected
10+.: Unconnected pin
11+0: Ground
12+B+: Blue signal
13+B0: Blue ground
14+G+: Green signal
15+G0: Green ground
16+H: Hsync signal
17+R+: Red signal
18+R0: Red ground
19+S0: Sync ground
20+V: Vsync signal
diff -r 000000000000 -r b4953c7062a7 video/vga.s
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/video/vga.s Thu Dec 08 21:39:06 2016 -0800
@@ -0,0 +1,198 @@
1+;;; Generates VGA synchronization signals from an ATtiny.
2+
3+;;; This program is designed to work with a 12.5 MHz clock.
4+;;; It generates hsync on B0, vsync on B1, and an image
5+;;; active signal on B2.
6+;;;
7+;;; It provides the following timings (standard VGA timings
8+;;; included for reference).
9+;;;
10+;;; Horizontal:
11+;;; Phase Clocks Time Standard
12+;;; active 320 25.6us 25.422us
13+;;; front porch 8 0.64us 0.636us
14+;;; hsync low 47 3.76us 3.813us
15+;;; back porch 23 1.84us 1.907us
16+;;; line total 398 31.84us 31.778us
17+;;;
18+;;; Vertial:
19+;;; Phase Lines Time Standard
20+;;; active 480 15.283ms 15.253ms
21+;;; front porch 10 0.318ms 0.318ms
22+;;; vsync low 2 0.064ms 0.064ms
23+;;; back porch 33 1.051ms 1.048ms
24+;;; frame total 525 16.716ms 16.683ms
25+;;; refresh rate 59.822Hz 59.940Hz
26+.equ DDRB, 0x17
27+.equ MCUSR, 0x34
28+.equ PORTB, 0x18
29+.equ WDTCR, 0x21
30+
31+__vectors:
32+ ;; interrupt table
33+ rjmp __start
34+ rjmp __start
35+ rjmp __start
36+ rjmp __start
37+ rjmp __start
38+ rjmp __start
39+ rjmp __start
40+ rjmp __start
41+ rjmp __start
42+ rjmp __start
43+
44+.global __start
45+__start:
46+ ;; first things first, disable interupts and reset watchdog
47+ cli
48+ wdr
49+ ;; set our outputs low as soon as we can
50+ eor r16, r16
51+ out PORTB, r16
52+ ;; we will use PORTB pins 0, 1, and 2 as outputs.
53+ ldi r16, 0x07
54+ out DDRB, r16
55+ ;; disable the watchdog so we won't suddenly reset
56+ ;; we can only clear WDE after we clear WDRF, so do that first
57+ in r16, MCUSR
58+ andi r16, 0xf7
59+ out MCUSR, r16
60+ ;; enable changing the WDTCR register
61+ in r16, WDTCR
62+ ori r16, 0x18
63+ out WDTCR, r16
64+ ;; finally, actually disable the watchdog
65+ ;; this sets WDTIE and WDE to 0, which disables the watchdog
66+ ;; it also sets WDTIF and WDCE to 0 (clearing the flag and disabling further changes),
67+ ;; and sets WDP to 7 (which shouldn't matter, since we disabled the watchdog anyway).
68+ ldi r16, 0x07
69+ out WDTCR, r16
70+ ;; watchdog disabled, enable interrupts again
71+ sei
72+ ;; r16 will be used to track value of PB
73+ ;; bit 0: vsync
74+ ;; bit 1: hsync
75+ ;; bit 2: video
76+ ;; we will use r17 to compute a new value for r16
77+ ;; r27:r26 will be set to the number of scan lines per frame (525)
78+ ldi r26, 13
79+ ldi r27, 2
80+ ;; r21:r20 will be set to the number of video lines (480)
81+ ldi r20, 224
82+ ldi r21, 1
83+ ;; vsync pulse starts at scan line 490, which is 256 + 234
84+ ldi r18, 234
85+ ;; vsync pulse ends at scan line 492, which is 256 + 236
86+ ldi r19, 236
87+ ;; r25:r24 will be used to keep track of the line number
88+ ;; we initialize them so that we start with vsync low
89+ mov r24, r18
90+ mov r25, r21
91+ ;; at hsync_low, hsync is expected to be low and video disabled.
92+ ;; we're also starting with vsync low. we've already set the
93+ ;; PORTB pins at the biginning of the program, so just set
94+ ;; r16 to the expected value.
95+ eor r16, r16
96+ ldi r17, 1
97+hsync_low:
98+ ;; because we execute an rjmp to get here, we start our cycle count at 2
99+ cp r25, r21 ; cycle 2
100+ brne no_vsync_msb ; cycle 3
101+ cp r24, r18 ; cycle 4
102+ brlo before_vsync ; cycle 5
103+ cp r19, r24 ; cycle 6
104+ brlo after_vsync ; cycle 7
105+ andi r17, 253 ; cycle 8
106+ rjmp vsync_computed ; cycle 9
107+no_vsync_msb:
108+ nop ; cycle 5
109+ nop ; cycle 6
110+before_vsync:
111+ nop ; cycle 7
112+ nop ; cycle 8
113+after_vsync:
114+ ori r17, 2 ; cycle 9
115+ nop ; cycle 10
116+vsync_computed:
117+ cp r25, r21 ; cycle 11
118+ brlo active_msb ; cycle 12
119+ brne after_active ; cycle 13
120+ cp r24, r20 ; cycle 14
121+ brlo active_lsb ; cycle 15
122+ rjmp not_active ; cycle 16
123+active_msb:
124+ nop ; cycle 14
125+ nop ; cycle 15
126+ nop ; cycle 16
127+active_lsb:
128+ ori r17, 4 ; cycle 17
129+ rjmp active_computed ; cycle 18
130+after_active:
131+ nop ; cycle 15
132+ nop ; cycle 16
133+ nop ; cycle 17
134+not_active:
135+ andi r17, 251 ; cycle 18
136+ nop ; cycle 19
137+active_computed:
138+ ;; compute new scan line number
139+ adiw r24, 1 ; cycle 20
140+ ;; wrap around to 0 if we exceeded the number of scan lines
141+ cp r25, r27 ; cycle 22
142+ brlo line_msb_ok ; cycle 23
143+ cp r24, r26 ; cycle 24
144+ brlo line_lsb_ok ; cycle 25
145+ eor r24, r24 ; cycle 26
146+ eor r25, r25 ; cycle 27
147+ rjmp line_ok ; cycle 28
148+line_msb_ok:
149+ nop ; cycle 25
150+ nop ; cycle 26
151+line_lsb_ok:
152+ nop ; cycle 27
153+ nop ; cycle 28
154+ nop ; cycle 29
155+line_ok:
156+ ;; wait until cycle 47, then set hsync high
157+ ;; that's 17 cycles from now. we need two cycles to set hsync,
158+ ;; leaving 15 cycles. we will use a wait loop. the loop
159+ ;; uses 3 cycles per iteration, except the last iteration,
160+ ;; which takes only 2 cycles. initialization takes 1 cycle,
161+ ;; so, in effect, we can count it as 3 cycles per iteration.
162+ ;; since we have 15 cycles to wait, that's 5 iterations.
163+ ldi r28, 5 ; cycle 30
164+keep_hsync_low:
165+ subi r28, 1
166+ brne keep_hsync_low
167+ ori r16, 1 ; cycle 45
168+ out PORTB, r16 ; cycle 46
169+ ;; wait 23 cycles, then set new values for active and vsync
170+ ;; we need 2 cycles to set the output pins, so that leaves
171+ ;; 21 cycles, which equals 7 iterations of a wait loop.
172+ ldi r28, 7 ; cycle 47
173+back_porch:
174+ subi r28, 1
175+ brne back_porch
176+ mov r16, r17 ; cycle 68
177+ out PORTB, r16 ; cycle 69
178+ ;; wait 320 cycles, then set active low. we need 2 cycles
179+ ;; to set the output pins, so that leaves 318 cycles, or
180+ ;; 106 iterations of a wait loop.
181+ ldi r28, 106 ; cycle 70
182+active:
183+ subi r28, 1
184+ brne active
185+ andi r16, 251 ; cycle 388
186+ out PORTB, r16 ; cycle 389
187+ ;; wait 8 cycles, then set hsync low
188+ ;; two cycles to set the pins, leaving 6 cycles to wait.
189+ nop ; cycle 390
190+ nop ; cycle 391
191+ nop ; cycle 392
192+ nop ; cycle 393
193+ nop ; cycle 394
194+ nop ; cycle 395
195+ andi r16, 254 ; cycle 396
196+ out PORTB, r16 ; cycle 397
197+ ;; next scan line
198+ rjmp hsync_low ; cycle 398, also cycle 0
Show on old repository browser