├── bin ├── helpers_Compiled.prg ├── VSP_Basics_Compiled.prg ├── VSP_Coarse_Horizontal_Scrolling_Compiled.prg ├── VSP_23Rows_Coarse_Horizontal_Scrolling_Compiled.prg ├── VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling_Compiled.prg ├── VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling_Compiled.prg ├── VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling_Compiled.prg ├── helpers.vs ├── helpers.sym ├── helpers_BuildLog.txt ├── VSP_Basics_BuildLog.txt ├── VSP_Basics.sym ├── VSP_Coarse_Horizontal_Scrolling_BuildLog.txt ├── VSP_23Rows_Coarse_Horizontal_Scrolling_BuildLog.txt ├── VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling_BuildLog.txt ├── VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling_BuildLog.txt ├── VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling_BuildLog.txt ├── VSP_Basics.vs ├── VSP_Coarse_Horizontal_Scrolling.sym ├── VSP_23Rows_Coarse_Horizontal_Scrolling.sym ├── VSP_Coarse_Horizontal_Scrolling.vs ├── VSP_23Rows_Coarse_Horizontal_Scrolling.vs ├── VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling.sym ├── VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling.vs ├── VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling.sym ├── VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling.vs ├── VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling.vs └── VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling.sym ├── VSP_0.png ├── VSP_1.png ├── VSP_2.png ├── VSP_20.png ├── VSP_24.png ├── VSP_25.png ├── VSP_39.png ├── VSP_40.png ├── screen_ram_hex_1000.png ├── README.md ├── LICENSE ├── wait_functions.asm ├── helpers.asm ├── VSP_Basics.asm ├── VSP_Correct_But_Still_Coarse_Horizontal_Scrolling.asm ├── VSP_23Rows_Coarse_Horizontal_Scrolling.asm ├── VSP_Coarse_Horizontal_Scrolling.asm ├── VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling.asm ├── VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling.asm └── VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling.asm /bin/helpers_Compiled.prg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VSP_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/VSP_0.png -------------------------------------------------------------------------------- /VSP_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/VSP_1.png -------------------------------------------------------------------------------- /VSP_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/VSP_2.png -------------------------------------------------------------------------------- /VSP_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/VSP_20.png -------------------------------------------------------------------------------- /VSP_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/VSP_24.png -------------------------------------------------------------------------------- /VSP_25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/VSP_25.png -------------------------------------------------------------------------------- /VSP_39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/VSP_39.png -------------------------------------------------------------------------------- /VSP_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/VSP_40.png -------------------------------------------------------------------------------- /screen_ram_hex_1000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/screen_ram_hex_1000.png -------------------------------------------------------------------------------- /bin/VSP_Basics_Compiled.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/bin/VSP_Basics_Compiled.prg -------------------------------------------------------------------------------- /bin/VSP_Coarse_Horizontal_Scrolling_Compiled.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/bin/VSP_Coarse_Horizontal_Scrolling_Compiled.prg -------------------------------------------------------------------------------- /bin/VSP_23Rows_Coarse_Horizontal_Scrolling_Compiled.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/bin/VSP_23Rows_Coarse_Horizontal_Scrolling_Compiled.prg -------------------------------------------------------------------------------- /bin/VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling_Compiled.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/bin/VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling_Compiled.prg -------------------------------------------------------------------------------- /bin/VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling_Compiled.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/bin/VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling_Compiled.prg -------------------------------------------------------------------------------- /bin/VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling_Compiled.prg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chris73it/C64_Scrolling_PAL/HEAD/bin/VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling_Compiled.prg -------------------------------------------------------------------------------- /bin/helpers.vs: -------------------------------------------------------------------------------- 1 | al C:d019 .vic2_interrupt_status_register 2 | al C:d011 .vic2_screen_control_register1 3 | al C:d016 .vic2_screen_control_register2 4 | al C:d012 .vic2_rasterline_register 5 | al C:d01a .vic2_interrupt_control_register 6 | -------------------------------------------------------------------------------- /bin/helpers.sym: -------------------------------------------------------------------------------- 1 | .label vic2_interrupt_status_register=$d019 2 | .label vic2_screen_control_register1=$d011 3 | .label vic2_screen_control_register2=$d016 4 | .label vic2_rasterline_register=$d012 5 | .label vic2_interrupt_control_register=$d01a 6 | -------------------------------------------------------------------------------- /bin/helpers_BuildLog.txt: -------------------------------------------------------------------------------- 1 | parsing 2 | flex pass 1 3 | Output pass 4 | Writing prg file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\helpers_Compiled.prg 5 | 6 | Memory Map 7 | ---------- 8 | 9 | Writing Vice symbol file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\helpers.vs 10 | Writing Symbol file: bin\helpers.sym 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C64_Scrolling_PAL 2 | VSP is used to scroll the screen from left to right. 3 | 4 | NOTICE that I was planning to add bidrectional horizonthal scrolling, but for lack of time it ended up being from left to right only. 5 | NOTICE that this repo does not contain any Line Crunching code: I was planning to add it at a later time but got busy (it may still arrive in the future though.) 6 | -------------------------------------------------------------------------------- /bin/VSP_Basics_BuildLog.txt: -------------------------------------------------------------------------------- 1 | parsing 2 | flex pass 1 3 | flex pass 2 4 | flex pass 3 5 | Output pass 6 | Writing prg file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_Basics_Compiled.prg 7 | 8 | Memory Map 9 | ---------- 10 | Default-segment: 11 | $0801-$080c Basic 12 | $080e-$0a7d Basic End 13 | 14 | Writing Vice symbol file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_Basics.vs 15 | Writing Symbol file: bin\VSP_Basics.sym 16 | -------------------------------------------------------------------------------- /bin/VSP_Basics.sym: -------------------------------------------------------------------------------- 1 | .label vic2_interrupt_status_register=$d019 2 | .label border=$d020 3 | .label cia1_interrupt_control_register=$dc0d 4 | .label exiting_irq1=$a63 5 | .label vic2_screen_control_register1=$d011 6 | .label vic2_screen_control_register2=$d016 7 | .label main=$80e 8 | .label vic2_rasterline_register=$d012 9 | .label cia2_interrupt_control_register=$dd0d 10 | .label background=$d021 11 | .label loop=$a06 12 | .label irq1=$a09 { 13 | } 14 | .label vic2_interrupt_control_register=$d01a 15 | -------------------------------------------------------------------------------- /bin/VSP_Coarse_Horizontal_Scrolling_BuildLog.txt: -------------------------------------------------------------------------------- 1 | parsing 2 | flex pass 1 3 | flex pass 2 4 | flex pass 3 5 | Output pass 6 | Writing prg file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_Coarse_Horizontal_Scrolling_Compiled.prg 7 | 8 | Memory Map 9 | ---------- 10 | Default-segment: 11 | $0801-$080c Basic 12 | $080e-$0b29 Basic End 13 | 14 | Writing Vice symbol file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_Coarse_Horizontal_Scrolling.vs 15 | Writing Symbol file: bin\VSP_Coarse_Horizontal_Scrolling.sym 16 | -------------------------------------------------------------------------------- /bin/VSP_23Rows_Coarse_Horizontal_Scrolling_BuildLog.txt: -------------------------------------------------------------------------------- 1 | parsing 2 | flex pass 1 3 | flex pass 2 4 | flex pass 3 5 | Output pass 6 | Writing prg file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_23Rows_Coarse_Horizontal_Scrolling_Compiled.prg 7 | 8 | Memory Map 9 | ---------- 10 | Default-segment: 11 | $0801-$080c Basic 12 | $080e-$0a29 Basic End 13 | 14 | Writing Vice symbol file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_23Rows_Coarse_Horizontal_Scrolling.vs 15 | Writing Symbol file: bin\VSP_23Rows_Coarse_Horizontal_Scrolling.sym 16 | -------------------------------------------------------------------------------- /bin/VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling_BuildLog.txt: -------------------------------------------------------------------------------- 1 | parsing 2 | flex pass 1 3 | flex pass 2 4 | flex pass 3 5 | Output pass 6 | Writing prg file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling_Compiled.prg 7 | 8 | Memory Map 9 | ---------- 10 | Default-segment: 11 | $0801-$080c Basic 12 | $080e-$0b29 Basic End 13 | 14 | Writing Vice symbol file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling.vs 15 | Writing Symbol file: bin\VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling.sym 16 | -------------------------------------------------------------------------------- /bin/VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling_BuildLog.txt: -------------------------------------------------------------------------------- 1 | parsing 2 | flex pass 1 3 | flex pass 2 4 | flex pass 3 5 | Output pass 6 | Writing prg file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling_Compiled.prg 7 | 8 | Memory Map 9 | ---------- 10 | Default-segment: 11 | $0801-$080c Basic 12 | $080e-$0e29 Basic End 13 | 14 | Writing Vice symbol file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling.vs 15 | Writing Symbol file: bin\VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling.sym 16 | -------------------------------------------------------------------------------- /bin/VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling_BuildLog.txt: -------------------------------------------------------------------------------- 1 | parsing 2 | flex pass 1 3 | flex pass 2 4 | flex pass 3 5 | Output pass 6 | Writing prg file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling_Compiled.prg 7 | 8 | Memory Map 9 | ---------- 10 | Default-segment: 11 | $0801-$080c Basic 12 | $080e-$0e29 Basic End 13 | 14 | Writing Vice symbol file: C:\C64\Tools\KickAssembler\Mini_Projects\C64_Scrolling_PAL\bin\VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling.vs 15 | Writing Symbol file: bin\VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling.sym 16 | -------------------------------------------------------------------------------- /bin/VSP_Basics.vs: -------------------------------------------------------------------------------- 1 | al C:d019 .vic2_interrupt_status_register 2 | al C:d020 .border 3 | al C:dc0d .cia1_interrupt_control_register 4 | al C:a3b .test 5 | al C:95e .next_color 6 | al C:835 .next_char 7 | al C:a63 .exiting_irq1 8 | al C:d011 .vic2_screen_control_register1 9 | al C:a09 .start 10 | al C:d016 .vic2_screen_control_register2 11 | al C:80b .upstartEnd 12 | al C:80e .main 13 | al C:d012 .vic2_rasterline_register 14 | al C:dd0d .cia2_interrupt_control_register 15 | al C:d021 .background 16 | al C:9b0 .continue 17 | al C:a06 .loop 18 | al C:a09 .irq1 19 | al C:822 .clear_next_char 20 | al C:a24 .irq2 21 | al C:a43 .next_instruction 22 | al C:83b .ok 23 | al C:d01a .vic2_interrupt_control_register 24 | -------------------------------------------------------------------------------- /bin/VSP_Coarse_Horizontal_Scrolling.sym: -------------------------------------------------------------------------------- 1 | .label vic2_interrupt_status_register=$d019 2 | .label cia1_interrupt_control_register=$dc0d 3 | .label exiting_irq1=$a96 4 | .label vic2_screen_control_register1=$d011 5 | .label vic2_screen_control_register2=$d016 6 | .label left2rigth=$824 7 | .label main=$80e 8 | .label NDelay=$b00 9 | .label loop=$a10 10 | .label border=$d020 11 | .label prep_screen=$828 { 12 | } 13 | .label scroll_left_to_right=$a8a 14 | .label vic2_rasterline_register=$d012 15 | .label ndelay=$a6a 16 | .label cia2_interrupt_control_register=$dd0d 17 | .label background=$d021 18 | .label irq1=$a13 { 19 | } 20 | .label finalize_scroll_dir=$826 21 | .label vic2_interrupt_control_register=$d01a 22 | -------------------------------------------------------------------------------- /bin/VSP_23Rows_Coarse_Horizontal_Scrolling.sym: -------------------------------------------------------------------------------- 1 | .label vic2_interrupt_status_register=$d019 2 | .label cia1_interrupt_control_register=$dc0d 3 | .label exiting_irq1=$9b7 4 | .label vic2_screen_control_register1=$d011 5 | .label vic2_screen_control_register2=$d016 6 | .label left2rigth=$824 7 | .label main=$80e 8 | .label NDelay=$a00 9 | .label loop=$931 10 | .label border=$d020 11 | .label prep_screen=$828 { 12 | } 13 | .label scroll_left_to_right=$9ab 14 | .label vic2_rasterline_register=$d012 15 | .label ndelay=$98b 16 | .label cia2_interrupt_control_register=$dd0d 17 | .label background=$d021 18 | .label irq1=$934 { 19 | } 20 | .label finalize_scroll_dir=$826 21 | .label vic2_interrupt_control_register=$d01a 22 | -------------------------------------------------------------------------------- /bin/VSP_Coarse_Horizontal_Scrolling.vs: -------------------------------------------------------------------------------- 1 | al C:d019 .vic2_interrupt_status_register 2 | al C:dc0d .cia1_interrupt_control_register 3 | al C:a96 .exiting_irq1 4 | al C:d011 .vic2_screen_control_register1 5 | al C:d016 .vic2_screen_control_register2 6 | al C:824 .left2rigth 7 | al C:80b .upstartEnd 8 | al C:80e .main 9 | al C:b00 .NDelay 10 | al C:9ba .continue 11 | al C:a10 .loop 12 | al C:845 .ok 13 | al C:d020 .border 14 | al C:a45 .test 15 | al C:828 .prep_screen 16 | al C:968 .next_color 17 | al C:83f .next_char 18 | al C:a13 .start 19 | al C:a8a .scroll_left_to_right 20 | al C:d012 .vic2_rasterline_register 21 | al C:a6a .ndelay 22 | al C:dd0d .cia2_interrupt_control_register 23 | al C:d021 .background 24 | al C:a13 .irq1 25 | al C:82c .clear_next_char 26 | al C:a2e .irq2 27 | al C:a4d .next_instruction 28 | al C:826 .finalize_scroll_dir 29 | al C:d01a .vic2_interrupt_control_register 30 | -------------------------------------------------------------------------------- /bin/VSP_23Rows_Coarse_Horizontal_Scrolling.vs: -------------------------------------------------------------------------------- 1 | al C:d019 .vic2_interrupt_status_register 2 | al C:dc0d .cia1_interrupt_control_register 3 | al C:9b7 .exiting_irq1 4 | al C:d011 .vic2_screen_control_register1 5 | al C:d016 .vic2_screen_control_register2 6 | al C:824 .left2rigth 7 | al C:80b .upstartEnd 8 | al C:80e .main 9 | al C:a00 .NDelay 10 | al C:8f7 .continue 11 | al C:931 .loop 12 | al C:840 .more_sprite_pointers 13 | al C:854 .ok 14 | al C:d020 .border 15 | al C:966 .test 16 | al C:828 .prep_screen 17 | al C:8a5 .next_color 18 | al C:84e .next_char 19 | al C:934 .start 20 | al C:9ab .scroll_left_to_right 21 | al C:d012 .vic2_rasterline_register 22 | al C:98b .ndelay 23 | al C:dd0d .cia2_interrupt_control_register 24 | al C:d021 .background 25 | al C:934 .irq1 26 | al C:82c .clear_next_char 27 | al C:94f .irq2 28 | al C:96e .next_instruction 29 | al C:826 .finalize_scroll_dir 30 | al C:d01a .vic2_interrupt_control_register 31 | -------------------------------------------------------------------------------- /bin/VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling.sym: -------------------------------------------------------------------------------- 1 | .label vic2_interrupt_status_register=$d019 2 | .label cia1_interrupt_control_register=$dc0d 3 | .label exiting_irq2=$9ac 4 | .label exiting_irq1=$982 5 | .label vic2_screen_control_register1=$d011 6 | .label vic2_screen_control_register2=$d016 7 | .label left2rigth=$824 8 | .label main=$80e 9 | .label shift_column_24_39_down=$a23 10 | .label NDelay=$b00 11 | .label loop=$91b 12 | .label shift_column_0_23_down=$9ca 13 | .label border=$d020 14 | .label exiting_shift_column_24_39_down=$a89 15 | .label make_screen_scroll=$a8d 16 | .label scroll_left_to_right=$aa0 17 | .label from_0_to_23=$9a3 18 | .label exiting_shift_column_0_23_down=$a1f 19 | .label vic2_rasterline_register=$d012 20 | .label ndelay=$975 21 | .label screen_LSB_addr=$aad 22 | .label cia2_interrupt_control_register=$dd0d 23 | .label background=$d021 24 | .label from_24_to_39=$9a9 25 | .label irq1=$91e { 26 | } 27 | .label irq2=$99d 28 | .label exiting_make_screen_scroll=$aac 29 | .label screen_MSB_addr=$ac7 30 | .label finalize_scroll_dir=$826 31 | .label vic2_interrupt_control_register=$d01a 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Christian Sasso 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling.vs: -------------------------------------------------------------------------------- 1 | al C:d019 .vic2_interrupt_status_register 2 | al C:dc0d .cia1_interrupt_control_register 3 | al C:9ac .exiting_irq2 4 | al C:982 .exiting_irq1 5 | al C:d011 .vic2_screen_control_register1 6 | al C:d016 .vic2_screen_control_register2 7 | al C:824 .left2rigth 8 | al C:80b .upstartEnd 9 | al C:80e .main 10 | al C:a23 .shift_column_24_39_down 11 | al C:b00 .NDelay 12 | al C:8e1 .continue 13 | al C:91b .loop 14 | al C:9ca .shift_column_0_23_down 15 | al C:832 .ok 16 | al C:d020 .border 17 | al C:950 .test 18 | al C:88c .next_color 19 | al C:82c .next_char 20 | al C:a89 .exiting_shift_column_24_39_down 21 | al C:91e .start 22 | al C:a8d .make_screen_scroll 23 | al C:aa0 .scroll_left_to_right 24 | al C:9a3 .from_0_to_23 25 | al C:a1f .exiting_shift_column_0_23_down 26 | al C:d012 .vic2_rasterline_register 27 | al C:975 .ndelay 28 | al C:aad .screen_LSB_addr 29 | al C:dd0d .cia2_interrupt_control_register 30 | al C:d021 .background 31 | al C:9a9 .from_24_to_39 32 | al C:91e .irq1 33 | al C:939 .irq2__0 34 | al C:99d .irq2__1 35 | al C:958 .next_instruction 36 | al C:aac .exiting_make_screen_scroll 37 | al C:ac7 .screen_MSB_addr 38 | al C:826 .finalize_scroll_dir 39 | al C:d01a .vic2_interrupt_control_register 40 | -------------------------------------------------------------------------------- /bin/VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling.sym: -------------------------------------------------------------------------------- 1 | .label fsip=$82e 2 | .label exiting_second_screen_is_primary_shift_column_24_39_down=$d03 3 | .label left2rigth=$824 4 | .label main=$80e 5 | .label exiting_first_screen_is_primary_shift_column_0_23_down=$b07 6 | .label second_screen_is_primary=$a16 7 | .label first_screen_is_primary=$9fb 8 | .label second_from_24_to_39=$a22 9 | .label loop=$975 10 | .label second_from_0_to_23=$a1c 11 | .label second_screen_is_primary_shift_column_24_39_down=$c55 12 | .label activate_second_buffer=$a4c 13 | .label border=$d020 14 | .label first_from_24_to_39=$a07 15 | .label first_from_0_to_23=$a01 16 | .label make_screen_scroll=$d04 17 | .label scroll_left_to_right=$d17 18 | .label exiting_second_screen_is_primary_shift_column_0_23_down=$c54 19 | .label vic2_rasterline_register=$d012 20 | .label init_screen=$834 { 21 | } 22 | .label ndelay=$9cf 23 | .label ssip=$831 24 | .label cia2_interrupt_control_register=$dd0d 25 | .label background=$d021 26 | .label exiting_make_screen_scroll=$d23 27 | .label finalize_scroll_dir=$826 28 | .label vic2_interrupt_status_register=$d019 29 | .label cia1_interrupt_control_register=$dc0d 30 | .label first_screen_is_primary_shift_column_0_23_down=$a6a 31 | .label exiting_irq2=$a2e 32 | .label exiting_irq1=$9dc 33 | .label vic2_screen_control_register1=$d011 34 | .label vic2_screen_control_register2=$d016 35 | .label first_screen_is_primary_shift_column_24_39_down=$b08 36 | .label NDelay=$e00 37 | .label second_screen_is_primary_shift_column_0_23_down=$bb7 38 | .label exiting_first_screen_is_primary_shift_column_24_39_down=$bb6 39 | .label scroll_right_to_left=$d08 40 | .label activate_first_buffer=$a5b 41 | .label irq1=$978 { 42 | } 43 | .label irq2=$9f7 44 | .label vic2_interrupt_control_register=$d01a 45 | -------------------------------------------------------------------------------- /bin/VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling.vs: -------------------------------------------------------------------------------- 1 | al C:d019 .vic2_interrupt_status_register 2 | al C:dc0d .cia1_interrupt_control_register 3 | al C:82e .fsip 4 | al C:a6a .first_screen_is_primary_shift_column_0_23_down 5 | al C:d03 .exiting_second_screen_is_primary_shift_column_24_39_down 6 | al C:a2e .exiting_irq2 7 | al C:9dc .exiting_irq1 8 | al C:d011 .vic2_screen_control_register1 9 | al C:d016 .vic2_screen_control_register2 10 | al C:824 .left2rigth 11 | al C:80b .upstartEnd 12 | al C:80e .main 13 | al C:b07 .exiting_first_screen_is_primary_shift_column_0_23_down 14 | al C:b08 .first_screen_is_primary_shift_column_24_39_down 15 | al C:a16 .second_screen_is_primary 16 | al C:9fb .first_screen_is_primary 17 | al C:e00 .NDelay 18 | al C:a22 .second_from_24_to_39 19 | al C:bb7 .second_screen_is_primary_shift_column_0_23_down 20 | al C:975 .loop 21 | al C:a1c .second_from_0_to_23 22 | al C:bb6 .exiting_first_screen_is_primary_shift_column_24_39_down 23 | al C:c55 .second_screen_is_primary_shift_column_24_39_down 24 | al C:d08 .scroll_right_to_left 25 | al C:a4c .activate_second_buffer 26 | al C:d020 .border 27 | al C:9aa .test 28 | al C:8e6 .next_color 29 | al C:a07 .first_from_24_to_39 30 | al C:978 .start 31 | al C:a01 .first_from_0_to_23 32 | al C:d04 .make_screen_scroll 33 | al C:d17 .scroll_left_to_right 34 | al C:c54 .exiting_second_screen_is_primary_shift_column_0_23_down 35 | al C:d012 .vic2_rasterline_register 36 | al C:834 .init_screen 37 | al C:9cf .ndelay 38 | al C:831 .ssip 39 | al C:dd0d .cia2_interrupt_control_register 40 | al C:d021 .background 41 | al C:a5b .activate_first_buffer 42 | al C:978 .irq1 43 | al C:993 .irq2__0 44 | al C:9f7 .irq2__1 45 | al C:9b2 .next_instruction 46 | al C:d23 .exiting_make_screen_scroll 47 | al C:826 .finalize_scroll_dir 48 | al C:d01a .vic2_interrupt_control_register 49 | -------------------------------------------------------------------------------- /bin/VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling.vs: -------------------------------------------------------------------------------- 1 | al C:831 .fsip 2 | al C:d18 .exiting_second_screen_is_primary_shift_column_24_39_down 3 | al C:827 .left2rigth 4 | al C:80e .main 5 | al C:b1c .exiting_first_screen_is_primary_shift_column_0_23_down 6 | al C:a2b .second_screen_is_primary 7 | al C:a10 .first_screen_is_primary 8 | al C:a37 .second_from_24_to_39 9 | al C:978 .loop 10 | al C:a31 .second_from_0_to_23 11 | al C:c6a .second_screen_is_primary_shift_column_24_39_down 12 | al C:9df .exiting_irq1_to_irq2 13 | al C:a61 .activate_second_buffer 14 | al C:d020 .border 15 | al C:9ad .test 16 | al C:8e9 .next_color 17 | al C:a1c .first_from_24_to_39 18 | al C:a16 .first_from_0_to_23 19 | al C:d19 .make_screen_scroll 20 | al C:d2c .scroll_left_to_right 21 | al C:c69 .exiting_second_screen_is_primary_shift_column_0_23_down 22 | al C:d012 .vic2_rasterline_register 23 | al C:837 .init_screen 24 | al C:9d2 .ndelay 25 | al C:834 .ssip 26 | al C:dd0d .cia2_interrupt_control_register 27 | al C:d021 .background 28 | al C:9b5 .next_instruction 29 | al C:d38 .exiting_make_screen_scroll 30 | al C:829 .finalize_scroll_dir 31 | al C:d019 .vic2_interrupt_status_register 32 | al C:a46 .exiting_irq2_to_irq1 33 | al C:dc0d .cia1_interrupt_control_register 34 | al C:a7f .first_screen_is_primary_shift_column_0_23_down 35 | al C:d011 .vic2_screen_control_register1 36 | al C:d016 .vic2_screen_control_register2 37 | al C:80b .upstartEnd 38 | al C:b1d .first_screen_is_primary_shift_column_24_39_down 39 | al C:e00 .NDelay 40 | al C:bcc .second_screen_is_primary_shift_column_0_23_down 41 | al C:bcb .exiting_first_screen_is_primary_shift_column_24_39_down 42 | al C:d1d .scroll_right_to_left 43 | al C:a0c .primary_screen 44 | al C:97b .start 45 | al C:a43 .scroll_screen 46 | al C:a07 .reset_xscroll_back_to_7 47 | al C:a70 .activate_first_buffer 48 | al C:97b .irq1 49 | al C:996 .irq2__0 50 | al C:9fa .irq2__1 51 | al C:d01a .vic2_interrupt_control_register 52 | -------------------------------------------------------------------------------- /bin/VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling.sym: -------------------------------------------------------------------------------- 1 | .label fsip=$831 2 | .label exiting_second_screen_is_primary_shift_column_24_39_down=$d18 3 | .label left2rigth=$827 4 | .label main=$80e 5 | .label exiting_first_screen_is_primary_shift_column_0_23_down=$b1c 6 | .label second_screen_is_primary=$a2b 7 | .label first_screen_is_primary=$a10 8 | .label second_from_24_to_39=$a37 9 | .label loop=$978 10 | .label second_from_0_to_23=$a31 11 | .label second_screen_is_primary_shift_column_24_39_down=$c6a 12 | .label exiting_irq1_to_irq2=$9df 13 | .label activate_second_buffer=$a61 14 | .label border=$d020 15 | .label first_from_24_to_39=$a1c 16 | .label first_from_0_to_23=$a16 17 | .label make_screen_scroll=$d19 18 | .label scroll_left_to_right=$d2c 19 | .label exiting_second_screen_is_primary_shift_column_0_23_down=$c69 20 | .label vic2_rasterline_register=$d012 21 | .label init_screen=$837 { 22 | } 23 | .label ndelay=$9d2 24 | .label ssip=$834 25 | .label cia2_interrupt_control_register=$dd0d 26 | .label background=$d021 27 | .label exiting_make_screen_scroll=$d38 28 | .label finalize_scroll_dir=$829 29 | .label vic2_interrupt_status_register=$d019 30 | .label exiting_irq2_to_irq1=$a46 31 | .label cia1_interrupt_control_register=$dc0d 32 | .label first_screen_is_primary_shift_column_0_23_down=$a7f 33 | .label vic2_screen_control_register1=$d011 34 | .label vic2_screen_control_register2=$d016 35 | .label first_screen_is_primary_shift_column_24_39_down=$b1d 36 | .label NDelay=$e00 37 | .label second_screen_is_primary_shift_column_0_23_down=$bcc 38 | .label exiting_first_screen_is_primary_shift_column_24_39_down=$bcb 39 | .label scroll_right_to_left=$d1d 40 | .label primary_screen=$a0c 41 | .label scroll_screen=$a43 42 | .label reset_xscroll_back_to_7=$a07 43 | .label activate_first_buffer=$a70 44 | .label irq1=$97b { 45 | } 46 | .label irq2=$9fa 47 | .label vic2_interrupt_control_register=$d01a 48 | -------------------------------------------------------------------------------- /wait_functions.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | #import "helpers.asm" 4 | 5 | /* 6 | * Wait functions. 7 | */ 8 | 9 | // Waits 23 cycles minus 12 cycles for the caller's jsr and this function's rts. 10 | wait_one_bad_line: //+6 11 | :cycles(-6+23-6) // 23-12 12 | rts //+6 13 | wait_one_bad_line_minus_3: //+6 14 | :cycles(-6+23-3-6) //20-12 15 | rts //+6 16 | 17 | // Waits 63 cycles minus 12 cycles for the caller's jsr and this function's rts. 18 | wait_one_good_line: //+6 19 | :cycles(-6+63-6) // 63-12 20 | rts //+6 21 | 22 | // Waits 63 cycles minus 12 cycles for the caller's jsr and this function's rts, and 23 | // further minus 12 cycles for the caller's caller's jsr and corresponding rts. 24 | // Basically this wait function is meant to be called from another wait function. 25 | wait_one_good_line_minus_jsr_and_rts: //+6 26 | :cycles(-6-6+63-6-6) // 63-24 27 | rts //+6 28 | 29 | // Waits 63 cycles minus 12 cycles for the caller's jsr and this function's rts, and 30 | // further minus 12 cycles for the caller's caller's jsr and corresponding rts. 31 | // Basically this wait function is meant to be called from another wait function. 32 | wait_6_good_lines: //+6 33 | jsr wait_one_good_line // 1: 63-12+6+6 = 63 34 | jsr wait_one_good_line // 2: 63-12+6+6 = 63 35 | jsr wait_one_good_line // 3: 63-12+6+6 = 63 36 | jsr wait_one_good_line // 4: 63-12+6+6 = 63 37 | jsr wait_one_good_line // 5: 63-12+6+6 = 63 38 | // 6: Wait_one_good_line minus 24 cycles for 2 jsrs and 2 rtses. 39 | jsr wait_one_good_line_minus_jsr_and_rts // 6: 63-12 40 | rts //+6 41 | 42 | // Wait one entire row worth of cycles minus the 12 cycles to call this function. 43 | wait_1_row_with_20_cycles_bad_line: //+6 44 | jsr wait_one_bad_line_minus_3 // 23-3 = 20 45 | jsr wait_6_good_lines // 63*5 + 63-12+6+6 = 63*6 46 | // Wait_one_good_line minus 24 cycles for 2 jsrs and 2 rtses. 47 | jsr wait_one_good_line_minus_jsr_and_rts // 63-12 48 | rts //+6 49 | 50 | // Wait two full rows worth of cycles minus the 12 cycles to call this function. 51 | wait_2_rows_with_20_cycles_bad_lines: //+6 52 | jsr wait_1_row_with_20_cycles_bad_line 53 | jsr wait_one_bad_line_minus_3 // 23-3 = 20 54 | jsr wait_6_good_lines // 63*5 + 63-12+6+6 = 63*6 55 | // Wait_one_good_line minus 24 cycles for 2 jsrs and 2 rtses. 56 | jsr wait_one_good_line_minus_jsr_and_rts // 63-12 57 | rts //+6 58 | 59 | wait_4_rows_with_20_cycles_bad_lines: //+6 60 | jsr wait_2_rows_with_20_cycles_bad_lines 61 | jsr wait_1_row_with_20_cycles_bad_line 62 | jsr wait_one_bad_line_minus_3 // 23-3 = 20 63 | jsr wait_6_good_lines // 63*5 + 63-12+6+6 = 63*6 64 | // Wait_one_good_line minus 24 cycles for 2 jsrs and 2 rtses. 65 | jsr wait_one_good_line_minus_jsr_and_rts // 63-12 66 | rts //+6 67 | 68 | wait_8_rows_with_20_cycles_bad_lines: //+6 69 | jsr wait_4_rows_with_20_cycles_bad_lines 70 | jsr wait_2_rows_with_20_cycles_bad_lines 71 | jsr wait_1_row_with_20_cycles_bad_line 72 | jsr wait_one_bad_line_minus_3 // 23-3 = 20 73 | jsr wait_6_good_lines // 63*5 + 63-12+6+6 = 63*6 74 | // Wait_one_good_line minus 24 cycles for 2 jsrs and 2 rtses. 75 | jsr wait_one_good_line_minus_jsr_and_rts // 63-12 76 | rts //+6 77 | -------------------------------------------------------------------------------- /helpers.asm: -------------------------------------------------------------------------------- 1 | #importonce 2 | 3 | .const colorRam = $d800 4 | .const screen = $0400 5 | .const screen_0 = $0400 6 | .const screen_0_40 = $0428 7 | .const screen_1 = $0500 8 | .const screen_2 = $0600 9 | .const screen_3 = $0700 10 | 11 | .const NAS = 1 12 | 13 | .label vic2_screen_control_register1 = $d011 14 | .label vic2_screen_control_register2 = $d016 15 | .label vic2_rasterline_register = $d012 16 | .label vic2_interrupt_control_register = $d01a 17 | .label vic2_interrupt_status_register = $d019 18 | 19 | .macro stabilize_irq() { 20 | start: 21 | :mov16 #irq2 : $fffe 22 | inc vic2_rasterline_register 23 | asl vic2_interrupt_status_register 24 | tsx 25 | cli 26 | 27 | :cycles(18) 28 | 29 | irq2: 30 | txs 31 | :cycles(44) 32 | test: 33 | lda vic2_rasterline_register 34 | cmp vic2_rasterline_register 35 | beq next_instruction 36 | next_instruction: 37 | } 38 | 39 | .macro set_raster(line_number) { 40 | // Notice that only the 8 least significant bits are stored in the accumulator. 41 | lda #line_number 42 | sta vic2_rasterline_register 43 | 44 | lda vic2_screen_control_register1 45 | .if (line_number > 255) { 46 | ora #%10000000 47 | } else { 48 | and #%01111111 49 | } 50 | sta vic2_screen_control_register1 51 | } 52 | 53 | .pseudocommand mov16 source : destination { 54 | :_mov bits_to_bytes(16) : source : destination 55 | } 56 | .pseudocommand mov source : destination { 57 | :_mov bits_to_bytes(8) : source : destination 58 | } 59 | .pseudocommand _mov bytes_count : source : destination { 60 | .for (var i = 0; i < bytes_count.getValue(); i++) { 61 | lda extract_byte_argument(source, i) 62 | sta extract_byte_argument(destination, i) 63 | } 64 | } 65 | .pseudocommand _add bytes_count : left : right : result { 66 | clc 67 | .for (var i = 0; i < bytes_count.getValue(); i++) { 68 | lda extract_byte_argument(left, i) 69 | adc extract_byte_argument(right, i) 70 | sta extract_byte_argument(result, i) 71 | } 72 | } 73 | 74 | .function extract_byte_argument(arg, byte_id) { 75 | .if (arg.getType()==AT_IMMEDIATE) { 76 | .return CmdArgument(arg.getType(), extract_byte(arg.getValue(), byte_id)) 77 | } else { 78 | .return CmdArgument(arg.getType(), arg.getValue() + byte_id) 79 | } 80 | } 81 | .function extract_byte(value, byte_id) { 82 | .var bits = _bytes_to_bits(byte_id) 83 | .eval value = value >> bits 84 | .return value & $ff 85 | } 86 | .function _bytes_to_bits(bytes) { 87 | .return bytes * 8 88 | } 89 | .function bits_to_bytes(bits) { 90 | .return bits / 8 91 | } 92 | 93 | //Clears the screen using the blank (' ') character. 94 | //Notice that we also clean up the extra 24 bytes after the first 1000, 95 | //in fact, we even clean up 16 extra chatacters: this is done for 96 | //simplicity, but on production code you should probably refrain from 97 | //cleaning up those 16 bytes as well. 98 | .macro clear_screen(filler) { 99 | lda #filler //PETSCII for blank (' ') character 100 | ldx #0 //column index 101 | repeat: 102 | .for (var row = 0; row < 25+1; row++) { 103 | sta screen + row * 40, x 104 | } 105 | inx 106 | cpx #40 107 | bne repeat 108 | } 109 | 110 | .macro randomize_screen() { 111 | //randomize character codes 112 | ldx #0 //column index 113 | !repeat: //The exclamation point is necessary because 'repeat' is a duplicated label 114 | txa 115 | adc #33 //33 is the PETSCII char code for exclamation point '!' character 116 | .for (var row = 0; row < 25+1; row++) { 117 | sta screen + row * 40, x 118 | } 119 | inx 120 | cpx #40 121 | bne !repeat- //The minus means "go to the previous 'repeat' label" 122 | 123 | //randomize colors 124 | ldx #0 //column index 125 | !repeat: //The exclamation point is necessary because 'repeat' is a duplicated label 126 | txa 127 | adc #33 //33 is the PETSCII char code for exclamation point '!' character 128 | .for (var row = 0; row < 25+1; row++) { 129 | sta colorRam + row * 40, x 130 | } 131 | inx 132 | cpx #40 133 | bne !repeat- //The minus means "go to the previous 'repeat' label" 134 | } 135 | 136 | //Place blank chars on the first ($0400) and the second ($2000) screen. 137 | .macro init_screen_ram() { 138 | lda #32 //in PETSCII 32 is the character code for blank/whitespace 139 | ldx #0 //column index 140 | !ok: 141 | //We want to also initialize the 24 hidden bytes after the first 142 | // normally visible 1000 bytes: we actually overflow by 16 bytes: this 143 | // is ok since this demo is a proof of concept of how to implement VSP, 144 | // but on a real game you should probably fix that.) 145 | .for (var row = 0; row < 25+1; row++) { 146 | sta $0400 + row * 40,x 147 | } 148 | inx 149 | cpx #40 150 | bne !ok- 151 | 152 | ldx #0 //column index 153 | !ok: 154 | //We want to also initialize the 24 hidden bytes after the first 155 | // normally visible 1000 bytes: we actually overflow by 16 bytes: this 156 | // is ok since this demo is a proof of concept of how to implement VSP, 157 | // but on a real game you should probably fix that.) 158 | .for (var row = 0; row < 25+1; row++) { 159 | sta $2000 + row * 40,x 160 | } 161 | inx 162 | cpx #40 163 | bne !ok- 164 | 165 | // Place certain colors in color memory. 166 | ldx #0 //column index 167 | ldy #BLACK+1 //color index 168 | lda #BLACK+1 //char color (numerically same as y) 169 | next_color: 170 | .for (var row = 0; row < 25+1; row++) { 171 | sta colorRam + row * 40,x 172 | } 173 | iny 174 | cpy #8+1 //We only use 8 colors, because 8 divides both 16 and 24 175 | // and we do not need to bend head over hills just to make 176 | // this demo look right (the gosl here is to show how VSP 177 | // works, and not implement a full blown scroller: said that 178 | // it should be easy to add a tiler system for the new 179 | // graphics that enters from the right side of the screen.) 180 | bne !ok+ 181 | ldy #BLACK+1 //roll back to WHITE 182 | !ok: 183 | tya 184 | inx 185 | cpx #40 186 | bne next_color 187 | } 188 | 189 | .macro nops(count) { 190 | .for (var i = 0; i < count; i++) { 191 | nop 192 | } 193 | } 194 | 195 | .macro cycles(count) { 196 | .if (count < 0) { 197 | .error "The cycle count cannot be less than 0 (" + count + " given)." 198 | } 199 | .if (count == 1) { 200 | .error "Can't wait only one cycle." 201 | } 202 | .if (mod(count, 2) != 0) { 203 | bit $ea 204 | .eval count -= 3 205 | } 206 | :nops(count/2) 207 | } 208 | -------------------------------------------------------------------------------- /VSP_Basics.asm: -------------------------------------------------------------------------------- 1 | #import "helpers.asm" 2 | 3 | .label border = $d020 4 | .label background = $d021 5 | 6 | .label cia1_interrupt_control_register = $dc0d 7 | .label cia2_interrupt_control_register = $dd0d 8 | 9 | // Note: the '-2' is required because stabilize_irq() takes 2 raster 10 | // lines to synchronize the raster. More precisely, it _always_ ends 11 | // after completing the 3rd cycle of raster line number RASTER_LINE. 12 | .const RASTER_LINE = 48-2 // We want to "land" at RL:48:03 13 | 14 | // The number of cycles we want to skip: imagine to be at the top left 15 | // corner of the character page (by default at $0400) and that you can 16 | // choose to "rebase" what is the character that appears in that corner. 17 | // 18 | // Notice that when below I say "equivalent" I do not mean "identical": 19 | // it goes without saying that we will still need to adjust things 20 | // (typically the 1st or 25th row, and the 1st or 40th column.) 21 | // 22 | // If you choose: 23 | // (A) 0: Equivalent to no effect on scrolling. 24 | // (B) 1: Equivalent to right to left scrolling, where the second column 25 | // becomes the 1st, and the 1st becomes the 40th (shifted up 26 | // by one character because the 1001th byte of the char memory, 27 | // that normally is not visible, becomes the character in the 28 | // bottom right corner.) 29 | // (C)39: Equivalent to left to right scrolling, where the 40th column 30 | // becomes the 1st, and the 1st becomes the 2nd, etc. 31 | // Notice that the 25th row, from the 2nd to the 25th column, is 32 | // made of 24 characters from the 1001st to the 1024th char memory 33 | // that are normally not visible (remarkably, the last 8 of these 34 | // 24 hidden chars, are seen by the VIC-II as sprite pointers.) 35 | // Notice that the 25th row, from the 26th to the 40th column, is 36 | // made of the 15 characters corresponding to the bytes from the 37 | // 1st to the 15th char memory. 38 | // (D)40: Equivalent to upward vertical scrolling, where the 2nd row 39 | // becomes the 1st, the 25th row becomes the 24th. 40 | // Notice how the first 24 characters of the 25th row are the 41 | // normally hidden charcters from the 1001st to the 1024th char 42 | // memory. 43 | // Notice how the last 16 characters of the 25th row correspond 44 | // to the symbols associated to the bytes that go from the 1st 45 | // to the 16th char memory locations. 46 | 47 | //By default, we shift 2 columns to the left 48 | .const NUM_DMA_DELAY_CYCLES = 2 49 | 50 | :BasicUpstart2(main) 51 | main: 52 | sei 53 | lda #WHITE 54 | sta border 55 | lda #BLACK 56 | sta background 57 | 58 | // We are simply emphasizing that $D011 is set at its default value, 59 | // and this means YSCROLL is 3, and the first bad line is 51. 60 | lda #$1B 61 | sta $D011 62 | 63 | clear_screen(32) //Clean all chars, including chars in the 1000-1023 range 64 | randomize_screen() //Fill up screen in the range 0-999 with letters 65 | //Place few '<' and '>' symbols close to the 4 corners of the screen, 66 | //so we can better assess the effect of scrolling using DMA Delay/VSP. 67 | lda #60 //'<' symbol.. 68 | sta 1024+1*40+0 //..on the 2nd row, 1st column 69 | sta 1024+2*40+1 //..on the 3rd row, 2st column 70 | sta 1024+22*40+1 //..on the 23rd row, 2st column 71 | sta 1024+23*40+0 //..on the 24th row, 1st column 72 | lda #62 //'>' symbol.. 73 | sta 1024+1*40+39 //..on the 2nd row, 40th column 74 | sta 1024+2*40+38 //..on the 3rd row, 39st column 75 | sta 1024+22*40+38 //..on the 23th row, 39th column 76 | sta 1024+23*40+39 //..on the 24th row, 40th column 77 | 78 | lda $01 79 | and #%11111101 80 | sta $01 81 | 82 | lda #%01111111 83 | sta cia1_interrupt_control_register 84 | sta cia2_interrupt_control_register 85 | lda cia1_interrupt_control_register 86 | lda cia2_interrupt_control_register 87 | 88 | lda #%00000001 89 | sta vic2_interrupt_control_register 90 | sta vic2_interrupt_status_register 91 | :set_raster(RASTER_LINE) 92 | :mov16 #irq1 : $fffe 93 | cli 94 | 95 | loop: 96 | jmp loop 97 | 98 | //From Christian Bauer "VIC-II article" 99 | //http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt 100 | // 101 | //3.14.6. DMA delay 102 | //----------------- 103 | //The most sophisticated Bad Line manipulation is to create a Bad Line 104 | //Condition within cycles 15-53 of a raster line in the display window in 105 | //which the graphics data sequencer is in idle state, e.g. by modifying 106 | //register $d011 so that YSCROLL is equal to the lower three bits of RASTER. 107 | 108 | irq1: 109 | //jmp exiting_irq1 //Uncomment this to verify initialization works as intended 110 | :stabilize_irq() //RasterLine 48 after cycle 3, in short RL:48:03 111 | 112 | // Notice that up to this point, raster line 51 is the one that is 113 | // expected to become the first bad line since YSCROLL has value 3. 114 | 115 | // About cycles: 116 | // 117 | // - The inital -3 is to compensate for the fixed 3 cycle delay after 118 | // stabilize_irq() has completed running. 119 | // 120 | // - The final -6 is because lda #$18 + sta $D011 are 6 cycles combined. 121 | // 122 | // - The middle +14 is the earliest VSP can be triggered 123 | // (see 3.14.6. DMA delay in the "VIC-II article" by Christian Bauer.) 124 | // 125 | :cycles(-3 +14+(40-NUM_DMA_DELAY_CYCLES) -6) //RL:48:(8+40-NUM_DMA_DELAY_CYCLES) 126 | // Make *this* raster line (i.e. raster line 48) be 127 | // a Bad Line => This triggers the VSP/scroll 128 | // of (40-NUM_DMA_DELAY_CYCLES) columns. 129 | lda #$18 //(2,RL:48:(8+NUM_DMA_DELAY_CYCLES+2) %0001:1000 130 | sta $D011 //(4,RL:48:(8+NUM_DMA_DELAY_CYCLES+6) 131 | 132 | //...because raster line 48 is forced into a bad line, a VSP scroll 133 | // is triggered, so the CPU is stun until cycle 54... 134 | 135 | // ...so we can reset $D011 to use $1B as soon as we can: this way 136 | // we make sure that by the end of raster line 48, YSCROLL is 137 | // back to its default value 3, and we are ready for the next frame. 138 | lda #$1B //(2, RL48:56) 139 | sta $D011 //(4, RL48:60) 140 | 141 | exiting_irq1: 142 | asl vic2_interrupt_status_register 143 | :set_raster(RASTER_LINE) 144 | :mov16 #irq1 : $fffe 145 | rti -------------------------------------------------------------------------------- /VSP_Correct_But_Still_Coarse_Horizontal_Scrolling.asm: -------------------------------------------------------------------------------- 1 | #import "helpers.asm" 2 | 3 | .label border = $d020 4 | .label background = $d021 5 | 6 | .label cia1_interrupt_control_register = $dc0d 7 | .label cia2_interrupt_control_register = $dd0d 8 | 9 | // Note: the '-2' is required because stabilize_irq() takes 2 raster 10 | // lines to synchronize the raster. More precisely, it _always_ ends 11 | // after completing the 3rd cycle of raster line number RASTER_LINE. 12 | .const RASTER_LINE = (48-1)-2 // We want to "land" at RL:47:03 13 | 14 | // The number of cycles we want to skip: imagine to be at the top left 15 | // corner of the character page (by default at $0400) and that you can 16 | // choose to "rebase" what is the character that appears in that corner. 17 | // 18 | // Notice that when below I say "equivalent" I do not mean "identical": 19 | // it goes without saying that we will still need to adjust things 20 | // (typically the 1st or 25th row, and the 1st or 40th column.) 21 | // 22 | // If you choose: 23 | // (A) 0: Equivalent to no effect on scrolling. 24 | // (B) 1: Equivalent to right to left scrolling, where the second column 25 | // becomes the 1st, and the 1st becomes the 40th (shifted up 26 | // by one character because the 1001th byte of the char memory, 27 | // that normally is not visible, becomes the character in the 28 | // bottom right corner.) 29 | // (C)39: Equivalent to left to right scrolling, where the 40th column 30 | // becomes the 1st, and the 1st becomes the 2nd, etc. 31 | // Notice that the 25th row, from the 2nd to the 25th column, is 32 | // made of 24 characters from the 1001st to the 1024th char memory 33 | // that are normally not visible (remarkably, the last 8 of these 34 | // 24 hidden chars, are seen by the VIC-II as sprite pointers.) 35 | // Notice that the 25th row, from the 26th to the 40th column, is 36 | // made of the 15 characters corresponding to the bytes from the 37 | // 1st to the 15th char memory. 38 | // (D)40: Equivalent to upward vertical scrolling, where the 2nd row 39 | // becomes the 1st, the 25th row becomes the 24th. 40 | // Notice how the first 24 characters of the 25th row are the 41 | // normally hidden charcters from the 1001st to the 1024th char 42 | // memory. 43 | // Notice how the last 16 characters of the 25th row correspond 44 | // to the symbols associated to the bytes that go from the 1st 45 | // to the 16th char memory locations. 46 | 47 | :BasicUpstart2(main) 48 | main: 49 | sei 50 | lda #LIGHT_GRAY 51 | sta border 52 | lda #BLACK 53 | sta background 54 | 55 | //We are simply emphasizing that $D011 is set at its default value, 56 | //and this means YSCROLL is 3, and the first bad line is 51: this 57 | //way we can DMA delay raster line 48 since it is a good line, 58 | //then immediately after we will restore raster line 51 to be 59 | //the next bad line. 60 | lda #$1B 61 | sta $D011 62 | 63 | //By default, we start with the screen in neutral position, 64 | //i.e., no column is scrolled. The valid values are: 65 | //[0:39] is 40 different positions for horizontal scrolling. 66 | // 40 is the "scrolled up" position (not used in this demo.) 67 | 68 | //Notice that the initial value for $fe can only be either 0 or 39. 69 | lda #0 //By default we scroll right to left (so $ff will be 0) 70 | sta $fe 71 | // cmp #0 //right to left? 72 | beq finalize_scroll_dir 73 | left2rigth: 74 | lda #$ff //Scroll left to right 75 | finalize_scroll_dir: 76 | sta $ff 77 | 78 | prep_screen: 79 | clear_screen(32) //Clean all chars, including chars in the 1000-1023 range 80 | randomize_screen() //Fill up screen in the range 0-999 with letters 81 | 82 | lda $01 83 | and #%11111101 84 | sta $01 85 | 86 | lda #%01111111 87 | sta cia1_interrupt_control_register 88 | sta cia2_interrupt_control_register 89 | lda cia1_interrupt_control_register 90 | lda cia2_interrupt_control_register 91 | 92 | lda #%00000001 93 | sta vic2_interrupt_control_register 94 | sta vic2_interrupt_status_register 95 | :set_raster(RASTER_LINE) 96 | :mov16 #irq1 : $fffe 97 | cli 98 | 99 | loop: 100 | jmp loop 101 | 102 | //From Christian Bauer "VIC-II article" 103 | //http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt 104 | // 105 | //3.14.6. DMA delay 106 | //----------------- 107 | //The most sophisticated Bad Line manipulation is to create a Bad Line 108 | //Condition within cycles 15-53 of a raster line in the display window in 109 | //which the graphics data sequencer is in idle state, e.g. by modifying 110 | //register $d011 so that YSCROLL is equal to the lower three bits of RASTER. 111 | 112 | irq1: 113 | //jmp exiting_irq1 //Uncomment to verify initialization works as intended 114 | :stabilize_irq() //RasterLine 47 after cycle 3, in short RL:47:03 115 | 116 | // Notice that up to this point, raster line 51 is the one that is 117 | // expected to become the first bad line since YSCROLL has value 3. 118 | 119 | ldy $fe //(3, RL:47:06) 120 | sty ndelay+1 //(4, RL:47:10) Set $fe cycles into LSB NDelay's address 121 | :cycles(-3-3-4 +63 -6) //RL:47:57 122 | ndelay: 123 | //Notice that sty above is writing into the LSB of the NDelay address 124 | jsr NDelay //RL:47:57+6+42-$fe+6 == RL:47:69+42-$fe == RL:48:8+40-$fe 125 | 126 | // Make *this* raster line (i.e. raster line 48) be a Bad Line 127 | // => This triggers the VSP/DMA delay of $fe cycles. 128 | lda #$18 //(2,RL:48:(8+2+40-$fe)) %0001:1000 129 | sta $D011 //(4,RL:48:(10+4+40-$fe) = RL:48:54-$fe) 130 | 131 | //...because raster line 48 is forced into a bad line, a VSP scroll 132 | // is triggered, so the CPU is stun until cycle 54... 133 | 134 | // ...so we can reset $D011 to use $1B as soon as we can: this way 135 | // we make sure that by the end of raster line 48, YSCROLL is 136 | // back to its default value 3, and we are ready for the next frame. 137 | lda #$1B //(2, RL:48:(54-$fe+2)) 138 | sta $D011 //(4, RL:48:(56-$fe+4) = RL:48:60-$fe) 139 | 140 | //jmp exiting_irq1 //Uncomment to skip scrolling 141 | 142 | //Make the screen scroll 143 | //(r2l if $ff is 1/*0*/, or l2r if $ff is $ff.) 144 | lda $ff 145 | // cmp #0 //right to left? 146 | bne scroll_left_to_right 147 | inc $fe 148 | lda $fe 149 | cmp #39+1 150 | bne exiting_irq1 151 | lda #0 152 | sta $fe 153 | jmp exiting_irq1 154 | scroll_left_to_right: 155 | dec $fe 156 | lda $fe 157 | cmp #$ff //Equivalent to -1, i.e. 0 decremented by 1 158 | bne exiting_irq1 159 | lda #39 160 | sta $fe 161 | 162 | exiting_irq1: 163 | asl vic2_interrupt_status_register 164 | :set_raster(RASTER_LINE) 165 | :mov16 #irq1 : $fffe 166 | rti 167 | 168 | .align $100 //Align to the nearest page boundary 169 | //Actual delay in cycles is 40+2-$fe = 42-$fe 170 | //See https://bumbershootsoft.wordpress.com/2014/05/04/cycle-exact-delays-on-the-6502/ 171 | NDelay: //(0->42 ; 1->41 ; ... ; 39->3 ; 40->2) Notice that input+output is always 42 172 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 173 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 174 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 175 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 176 | .byte $c5 //(3) 177 | nop //(2) 178 | rts //(6) -------------------------------------------------------------------------------- /VSP_23Rows_Coarse_Horizontal_Scrolling.asm: -------------------------------------------------------------------------------- 1 | #import "helpers.asm" 2 | 3 | .label border = $d020 4 | .label background = $d021 5 | 6 | .label cia1_interrupt_control_register = $dc0d 7 | .label cia2_interrupt_control_register = $dd0d 8 | 9 | // Note: the '-2' is required because stabilize_irq() takes 2 raster 10 | // lines to synchronize the raster. More precisely, it _always_ ends 11 | // after completing the 3rd cycle of raster line number RASTER_LINE. 12 | .const RASTER_LINE = (48-1)-2 // We want to "land" at RL:47:03 13 | 14 | // The number of cycles we want to skip: imagine to be at the top left 15 | // corner of the character page (by default at $0400) and that you can 16 | // choose to "rebase" what is the character that appears in that corner. 17 | // 18 | // Notice that when below I say "equivalent" I do not mean "identical": 19 | // it goes without saying that we will still need to adjust things 20 | // (typically the 1st or 25th row, and the 1st or 40th column.) 21 | // 22 | // If you choose: 23 | // (A) 0: Equivalent to no effect on scrolling. 24 | // (B) 1: Equivalent to right to left scrolling, where the second column 25 | // becomes the 1st, and the 1st becomes the 40th (shifted up 26 | // by one character because the 1001th byte of the char memory, 27 | // that normally is not visible, becomes the character in the 28 | // bottom right corner.) 29 | // (C)39: Equivalent to left to right scrolling, where the 40th column 30 | // becomes the 1st, and the 1st becomes the 2nd, etc. 31 | // Notice that the 25th row, from the 2nd to the 25th column, is 32 | // made of 24 characters from the 1001st to the 1024th char memory 33 | // that are normally not visible (remarkably, the last 8 of these 34 | // 24 hidden chars, are seen by the VIC-II as sprite pointers.) 35 | // Notice that the 25th row, from the 26th to the 40th column, is 36 | // made of the 15 characters corresponding to the bytes from the 37 | // 1st to the 15th char memory. 38 | // (D)40: Equivalent to upward vertical scrolling, where the 2nd row 39 | // becomes the 1st, the 25th row becomes the 24th. 40 | // Notice how the first 24 characters of the 25th row are the 41 | // normally hidden charcters from the 1001st to the 1024th char 42 | // memory. 43 | // Notice how the last 16 characters of the 25th row correspond 44 | // to the symbols associated to the bytes that go from the 1st 45 | // to the 16th char memory locations. 46 | 47 | :BasicUpstart2(main) 48 | main: 49 | sei 50 | lda #LIGHT_GRAY 51 | sta border 52 | lda #BLACK 53 | sta background 54 | 55 | //We are simply emphasizing that $D011 is set at its default value, 56 | //and this means YSCROLL is 3, and the first bad line is 51: this 57 | //way we can DMA delay raster line 48 since it is a good line, 58 | //then immediately after we will restore raster line 51 to be 59 | //the next bad line. 60 | lda #$1B 61 | sta $D011 62 | 63 | //By default, we start with the screen in neutral position, 64 | //i.e., no column is scrolled. The valid values are: 65 | //[0:39] is 40 different positions for horizontal scrolling. 66 | // 40 is the "scrolled up" position (not used in this demo.) 67 | 68 | //Notice that the initial value for $fe can only be either 0 or 39, 69 | // that is because we use 0 in $ff to mean "right to left". 70 | lda #0 //By default we scroll right to left (so $ff will be 0) 71 | sta $fe 72 | // cmp #0 //right to left? 73 | beq finalize_scroll_dir 74 | left2rigth: 75 | lda #$ff //Scroll left to right 76 | finalize_scroll_dir: 77 | sta $ff 78 | 79 | prep_screen: 80 | clear_screen(32) //Clean all chars, including chars in the 1000-1023 range 81 | randomize_screen() //Fill up screen in the range 0-999 with letters 82 | 83 | lda $01 84 | and #%11111101 85 | sta $01 86 | 87 | lda #%01111111 88 | sta cia1_interrupt_control_register 89 | sta cia2_interrupt_control_register 90 | lda cia1_interrupt_control_register 91 | lda cia2_interrupt_control_register 92 | 93 | lda #%00000001 94 | sta vic2_interrupt_control_register 95 | sta vic2_interrupt_status_register 96 | :set_raster(RASTER_LINE) 97 | :mov16 #irq1 : $fffe 98 | cli 99 | 100 | loop: 101 | jmp loop 102 | 103 | //From Christian Bauer "VIC-II article" 104 | //http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt 105 | // 106 | //3.14.6. DMA delay 107 | //----------------- 108 | //The most sophisticated Bad Line manipulation is to create a Bad Line 109 | //Condition within cycles 15-53 of a raster line in the display window in 110 | //which the graphics data sequencer is in idle state, e.g. by modifying 111 | //register $d011 so that YSCROLL is equal to the lower three bits of RASTER. 112 | 113 | irq1: 114 | //jmp exiting_irq1 //Uncomment to verify initialization works as intended 115 | :stabilize_irq() //RasterLine 47 after cycle 3, in short RL:47:03 116 | 117 | // Notice that up to this point, raster line 51 is the one that is 118 | // expected to become the first bad line since YSCROLL has value 3. 119 | 120 | ldy $fe //(3, RL:47:06) 121 | sty ndelay+1 //(4, RL:47:10) Set $fe cycles into LSB NDelay's address 122 | :cycles(-3-3-4 +63 -6) //RL:47:57 123 | ndelay: 124 | //Notice that sty above is writing into the LSB of the NDelay address 125 | jsr NDelay //RL:47:57+6+42-$fe+6 == RL:47:69+42-$fe == RL:48:8+40-$fe 126 | 127 | // Make *this* raster line (i.e. raster line 48) be a Bad Line 128 | // => This triggers the VSP/DMA delay of $fe cycles. 129 | lda #$18 //(2,RL:48:(8+2+40-$fe)) %0001:1000 130 | sta $D011 //(4,RL:48:(10+4+40-$fe) = RL:48:54-$fe) 131 | 132 | //...because raster line 48 is forced into a bad line, a VSP scroll 133 | // is triggered, so the CPU is stun until cycle 54... 134 | 135 | // ...so we can reset $D011 to use $1B as soon as we can: this way 136 | // we make sure that by the end of raster line 48, YSCROLL is 137 | // back to its default value 3, and we are ready for the next frame. 138 | lda #$1B //(2, RL:48:(54-$fe+2)) 139 | sta $D011 //(4, RL:48:(56-$fe+4) = RL:48:60-$fe) 140 | 141 | //jmp exiting_irq1 //Uncomment to skip scrolling 142 | 143 | //Make the screen scroll 144 | //(r2l if $ff is 1/*0*/, or l2r if $ff is $ff.) 145 | lda $ff 146 | // cmp #0 //right to left? 147 | bne scroll_left_to_right 148 | inc $fe 149 | lda $fe 150 | cmp #39+1 151 | bne exiting_irq1 152 | lda #0 153 | sta $fe 154 | jmp exiting_irq1 155 | scroll_left_to_right: 156 | dec $fe 157 | lda $fe 158 | cmp #$ff //Equivalent to -1, i.e. 0 decremented by 1 159 | bne exiting_irq1 160 | lda #39 161 | sta $fe 162 | 163 | exiting_irq1: 164 | asl vic2_interrupt_status_register 165 | :set_raster(RASTER_LINE) 166 | :mov16 #irq1 : $fffe 167 | rti 168 | 169 | .align $100 //Align to the nearest page boundary 170 | //Actual delay in cycles is 40+2-$fe = 42-$fe 171 | //See https://bumbershootsoft.wordpress.com/2014/05/04/cycle-exact-delays-on-the-6502/ 172 | NDelay: //(0->42 ; 1->41 ; ... ; 39->3 ; 40->2) Notice that input+output is always 42 173 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 174 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 175 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 176 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 177 | .byte $c5 //(3) 178 | nop //(2) 179 | rts //(6) -------------------------------------------------------------------------------- /VSP_Coarse_Horizontal_Scrolling.asm: -------------------------------------------------------------------------------- 1 | #import "helpers.asm" 2 | 3 | .label border = $d020 4 | .label background = $d021 5 | 6 | .label cia1_interrupt_control_register = $dc0d 7 | .label cia2_interrupt_control_register = $dd0d 8 | 9 | // Note: the '-2' is required because stabilize_irq() takes 2 raster 10 | // lines to synchronize the raster. More precisely, it _always_ ends 11 | // after completing the 3rd cycle of raster line number RASTER_LINE. 12 | .const RASTER_LINE = (48-1)-2 // We want to "land" at RL:47:03 13 | 14 | // The number of cycles we want to skip: imagine to be at the top left 15 | // corner of the character page (by default at $0400) and that you can 16 | // choose to "rebase" what is the character that appears in that corner. 17 | // 18 | // Notice that when below I say "equivalent" I do not mean "identical": 19 | // it goes without saying that we will still need to adjust things 20 | // (typically the 1st or 25th row, and the 1st or 40th column.) 21 | // 22 | // If you choose: 23 | // (A) 0: Equivalent to no effect on scrolling. 24 | // (B) 1: Equivalent to right to left scrolling, where the second column 25 | // becomes the 1st, and the 1st becomes the 40th (shifted up 26 | // by one character because the 1001th byte of the char memory, 27 | // that normally is not visible, becomes the character in the 28 | // bottom right corner.) 29 | // (C)39: Equivalent to left to right scrolling, where the 40th column 30 | // becomes the 1st, and the 1st becomes the 2nd, etc. 31 | // Notice that the 25th row, from the 2nd to the 25th column, is 32 | // made of 24 characters from the 1001st to the 1024th char memory 33 | // that are normally not visible (remarkably, the last 8 of these 34 | // 24 hidden chars, are seen by the VIC-II as sprite pointers.) 35 | // Notice that the 25th row, from the 26th to the 40th column, is 36 | // made of the 15 characters corresponding to the bytes from the 37 | // 1st to the 15th char memory. 38 | // (D)40: Equivalent to upward vertical scrolling, where the 2nd row 39 | // becomes the 1st, the 25th row becomes the 24th. 40 | // Notice how the first 24 characters of the 25th row are the 41 | // normally hidden charcters from the 1001st to the 1024th char 42 | // memory. 43 | // Notice how the last 16 characters of the 25th row correspond 44 | // to the symbols associated to the bytes that go from the 1st 45 | // to the 16th char memory locations. 46 | 47 | :BasicUpstart2(main) 48 | main: 49 | sei 50 | lda #LIGHT_GRAY 51 | sta border 52 | lda #BLACK 53 | sta background 54 | 55 | //We are simply emphasizing that $D011 is set at its default value, 56 | //and this means YSCROLL is 3, and the first bad line is 51: this 57 | //way we can DMA delay raster line 48 since it is a good line, 58 | //then immediately after we will restore raster line 51 to be 59 | //the next bad line. 60 | lda #$1B 61 | sta $D011 62 | 63 | //By default, we start with the screen in neutral position, 64 | //i.e., no column is scrolled. The valid values are: 65 | //[0:39] is 40 different positions for horizontal scrolling. 66 | // 40 is the "scrolled up" position (not used in this demo.) 67 | 68 | //Notice that the initial value for $fe can only be either 0 or 39. 69 | lda #0 //By default we scroll right to left (so $ff will be 0) 70 | sta $fe 71 | // cmp #0 72 | beq finalize_scroll_dir 73 | left2rigth: 74 | lda #$ff //Scroll left to right 75 | finalize_scroll_dir: 76 | sta $ff 77 | 78 | prep_screen: 79 | clear_screen(32) //Clean all chars, including chars in the 1000-1023 range 80 | randomize_screen() //Fill up screen in the range 0-999 with letters 81 | //Place few '<' and '>' symbols close to the 4 corners of the screen, 82 | //so we can better assess the effect of scrolling using DMA Delay/VSP. 83 | lda #60 //'<' symbol.. 84 | sta 1024+1*40+0 //..on the 2nd row, 1st column 85 | sta 1024+2*40+1 //..on the 3rd row, 2st column 86 | sta 1024+22*40+1 //..on the 23rd row, 2st column 87 | sta 1024+23*40+0 //..on the 24th row, 1st column 88 | lda #62 //'>' symbol.. 89 | sta 1024+1*40+39 //..on the 2nd row, 40th column 90 | sta 1024+2*40+38 //..on the 3rd row, 39st column 91 | sta 1024+22*40+38 //..on the 23th row, 39th column 92 | sta 1024+23*40+39 //..on the 24th row, 40th column 93 | 94 | lda $01 95 | and #%11111101 96 | sta $01 97 | 98 | lda #%01111111 99 | sta cia1_interrupt_control_register 100 | sta cia2_interrupt_control_register 101 | lda cia1_interrupt_control_register 102 | lda cia2_interrupt_control_register 103 | 104 | lda #%00000001 105 | sta vic2_interrupt_control_register 106 | sta vic2_interrupt_status_register 107 | :set_raster(RASTER_LINE) 108 | :mov16 #irq1 : $fffe 109 | cli 110 | 111 | loop: 112 | jmp loop 113 | 114 | //From Christian Bauer "VIC-II article" 115 | //http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt 116 | // 117 | //3.14.6. DMA delay 118 | //----------------- 119 | //The most sophisticated Bad Line manipulation is to create a Bad Line 120 | //Condition within cycles 15-53 of a raster line in the display window in 121 | //which the graphics data sequencer is in idle state, e.g. by modifying 122 | //register $d011 so that YSCROLL is equal to the lower three bits of RASTER. 123 | 124 | irq1: 125 | //jmp exiting_irq1 //Uncomment to verify initialization works as intended 126 | :stabilize_irq() //RasterLine 47 after cycle 3, in short RL:47:03 127 | 128 | // Notice that up to this point, raster line 51 is the one that is 129 | // expected to become the first bad line since YSCROLL has value 3. 130 | 131 | ldy $fe //(3, RL:47:06) 132 | sty ndelay+1 //(4, RL:47:10) Set $fe cycles into LSB NDelay's address 133 | :cycles(-3-3-4 +63 -6) //RL:47:57 134 | ndelay: 135 | //Notice that sty above is writing into the LSB of the NDelay address 136 | jsr NDelay //RL:47:57+6+42-$fe+6 == RL:47:69+42-$fe == RL:48:8+40-$fe 137 | 138 | // Make *this* raster line (i.e. raster line 48) be a Bad Line 139 | // => This triggers the VSP/DMA delay of $fe cycles. 140 | lda #$18 //(2,RL:48:(8+2+40-$fe)) %0001:1000 141 | sta $D011 //(4,RL:48:(10+4+40-$fe) = RL:48:54-$fe) 142 | 143 | //...because raster line 48 is forced into a bad line, a VSP scroll 144 | // is triggered, so the CPU is stun until cycle 54... 145 | 146 | // ...so we can reset $D011 to use $1B as soon as we can: this way 147 | // we make sure that by the end of raster line 48, YSCROLL is 148 | // back to its default value 3, and we are ready for the next frame. 149 | lda #$1B //(2, RL:48:(54-$fe+2)) 150 | sta $D011 //(4, RL:48:(56-$fe+4) = RL:48:60-$fe) 151 | 152 | //Make the screen scroll 153 | //(r2l if $ff is 0, or l2r if $ff is $ff.) 154 | lda $ff 155 | // cmp #0 156 | bne scroll_left_to_right 157 | inc $fe 158 | lda $fe 159 | cmp #40 160 | bne exiting_irq1 161 | lda #0 162 | sta $fe 163 | jmp exiting_irq1 164 | scroll_left_to_right: 165 | dec $fe 166 | lda $fe 167 | cmp #$ff 168 | bne exiting_irq1 169 | lda #39 170 | sta $fe 171 | 172 | exiting_irq1: 173 | asl vic2_interrupt_status_register 174 | :set_raster(RASTER_LINE) 175 | :mov16 #irq1 : $fffe 176 | rti 177 | 178 | .align $100 //Align to the nearest page boundary 179 | //Actual delay in cycles is 40+2-$fe = 42-$fe 180 | //See https://bumbershootsoft.wordpress.com/2014/05/04/cycle-exact-delays-on-the-6502/ 181 | NDelay: //(0->42 ; 1->41 ; ... ; 39->3 ; 40->2) Notice that input+output is always 42 182 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 183 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 184 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 185 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 186 | .byte $c5 //(3) 187 | nop //(2) 188 | rts //(6) -------------------------------------------------------------------------------- /VSP_Fixed_25Rows_Coarse_Horizontal_Scrolling.asm: -------------------------------------------------------------------------------- 1 | #import "helpers.asm" 2 | 3 | .label border = $d020 4 | .label background = $d021 5 | 6 | .label cia1_interrupt_control_register = $dc0d 7 | .label cia2_interrupt_control_register = $dd0d 8 | 9 | // Note: the '-2' is required because stabilize_irq() takes 2 raster 10 | // lines to synchronize the raster. More precisely, it _always_ ends 11 | // after completing the 3rd cycle of raster line number RASTER_LINE. 12 | .const RASTER_LINE = (48-1)-2 // We want to "land" at RL:47:03 13 | 14 | // The number of cycles we want to skip: imagine to be at the top left 15 | // corner of the character page (by default at $0400) and that you can 16 | // choose to "rebase" what is the character that appears in that corner. 17 | // 18 | // Notice that when below I say "equivalent" I do not mean "identical": 19 | // it goes without saying that we will still need to adjust things 20 | // (typically the 1st or 25th row, and the 1st or 40th column.) 21 | // 22 | // If you choose: 23 | // (A) 0: Equivalent to no effect on scrolling. 24 | // (B) 1: Equivalent to right to left scrolling, where the second column 25 | // becomes the 1st, and the 1st becomes the 40th (shifted up 26 | // by one character because the 1001th byte of the char memory, 27 | // that normally is not visible, becomes the character in the 28 | // bottom right corner.) 29 | // (C)39: Equivalent to left to right scrolling, where the 40th column 30 | // becomes the 1st, and the 1st becomes the 2nd, etc. 31 | // Notice that the 25th row, from the 2nd to the 25th column, is 32 | // made of 24 characters from the 1001st to the 1024th char memory 33 | // that are normally not visible (remarkably, the last 8 of these 34 | // 24 hidden chars, are seen by the VIC-II as sprite pointers.) 35 | // Notice that the 25th row, from the 26th to the 40th column, is 36 | // made of the 15 characters corresponding to the bytes from the 37 | // 1st to the 15th char memory. 38 | // (D)40: Equivalent to upward vertical scrolling, where the 2nd row 39 | // becomes the 1st, the 25th row becomes the 24th. 40 | // Notice how the first 24 characters of the 25th row are the 41 | // normally hidden charcters from the 1001st to the 1024th char 42 | // memory. 43 | // Notice how the last 16 characters of the 25th row correspond 44 | // to the symbols associated to the bytes that go from the 1st 45 | // to the 16th char memory locations. 46 | 47 | :BasicUpstart2(main) 48 | main: 49 | sei 50 | lda #LIGHT_GRAY 51 | sta border 52 | lda #BLACK 53 | sta background 54 | 55 | //We are simply emphasizing that $D011 is set at its default value, 56 | //and this means YSCROLL is 3, and the first bad line is 51: this 57 | //way we can DMA delay raster line 48 since it is a good line, 58 | //then immediately after we will restore raster line 51 to be 59 | //the next bad line. 60 | lda #$1B 61 | sta $D011 62 | 63 | //By default, we start with the screen in neutral position, 64 | //i.e., no column is scrolled. The valid values for $FE are: 65 | //[0:39] is 40 different positions for horizontal scrolling. 66 | // 40 is the "scrolled up" position (not used in this demo.) 67 | 68 | //Notice that the initial value for $FE can only be either 0 or 39, 69 | // that is because we use 0 in $ff to mean "right to left". 70 | lda #0 //By default we scroll right to left (so $ff will be 0) 71 | sta $FE 72 | // cmp #0 //Right to left? 73 | beq finalize_scroll_dir 74 | left2rigth: 75 | lda #$FF //Scroll left to right 76 | finalize_scroll_dir: 77 | sta $FF 78 | 79 | //Prepare screen: 80 | randomize_screen() //Fill up screen in the range 0-999 with letters 81 | 82 | lda $01 83 | and #%11111101 84 | sta $01 85 | 86 | lda #%01111111 87 | sta cia1_interrupt_control_register 88 | sta cia2_interrupt_control_register 89 | lda cia1_interrupt_control_register 90 | lda cia2_interrupt_control_register 91 | 92 | lda #%00000001 93 | sta vic2_interrupt_control_register 94 | sta vic2_interrupt_status_register 95 | :set_raster(RASTER_LINE) 96 | :mov16 #irq1 : $fffe 97 | cli 98 | 99 | loop: 100 | jmp loop 101 | 102 | //From Christian Bauer "VIC-II article" 103 | //http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt 104 | // 105 | //3.14.6. DMA delay 106 | //----------------- 107 | //The most sophisticated Bad Line manipulation is to create a Bad Line 108 | //Condition within cycles 15-53 of a raster line in the display window in 109 | //which the graphics data sequencer is in idle state, e.g. by modifying 110 | //register $d011 so that YSCROLL is equal to the lower three bits of RASTER. 111 | 112 | irq1: 113 | //jmp exiting_irq1 //Uncomment to verify initialization works as intended 114 | :stabilize_irq() //RasterLine 47 after cycle 3, in short RL:47:03 115 | 116 | // Notice that up to this point, raster line 51 is the one that is 117 | // expected to become the first bad line since YSCROLL has value 3. 118 | 119 | ldy $FE //(3, RL:47:06) 120 | sty ndelay+1 //(4, RL:47:10) Set $FE cycles into LSB NDelay's address 121 | :cycles(-3-3-4 +63 -6) //RL:47:57 122 | ndelay: 123 | //Notice that sty above is writing into the LSB of the NDelay address 124 | jsr NDelay //RL:47:57+6+42-$FE+6 == RL:47:69+42-$FE == RL:48:8+40-$FE 125 | 126 | // Make *this* raster line (i.e. raster line 48) be a Bad Line 127 | // => This triggers the VSP/DMA delay of $FE cycles. 128 | lda #$18 //(2,RL:48:(8+2+40-$FE)) %0001:1000 129 | sta $D011 //(4,RL:48:(10+4+40-$FE) = RL:48:54-$FE) 130 | 131 | //...because raster line 48 is forced into a bad line, a VSP scroll 132 | // is triggered, so the CPU is stun until cycle 54... 133 | 134 | // ...so we can reset $D011 to use $1B as soon as we can: this way 135 | // we make sure that by the end of raster line 48, YSCROLL is 136 | // back to its default value 3, and we are ready for the next frame. 137 | lda #$1B //(2, RL:48:(54-$FE+2)) 138 | sta $D011 //(4, RL:48:(56-$FE+4) = RL:48:60-$FE) 139 | exiting_irq1: 140 | asl vic2_interrupt_status_register 141 | :set_raster(RASTER_LINE+210/*-4*/) //RASTER_LINE is (48-1)-2 = 45 142 | :mov16 #irq2 : $fffe 143 | rti 144 | 145 | irq2: 146 | //jmp exiting_irq2 147 | lda $FE 148 | cmp #24 149 | bcs from_24_to_39 //Accumulator >= 24? 150 | from_0_to_23: 151 | jsr shift_column_0_23_down 152 | jmp exiting_irq2 153 | from_24_to_39: 154 | jsr shift_column_24_39_down 155 | exiting_irq2: 156 | jsr make_screen_scroll 157 | asl vic2_interrupt_status_register 158 | :set_raster(RASTER_LINE) 159 | :mov16 #irq1 : $fffe 160 | rti 161 | 162 | shift_column_0_23_down: 163 | ldx $FE 164 | clc 165 | lda $FE 166 | adc #48 //48 is PETSCII for '0' 167 | sta $0428,x //2nd row 168 | sta $0450,x 169 | sta $0478,x 170 | sta $04A0,x 171 | sta $04C8,x 172 | sta $04F0,x 173 | sta $0518,x 174 | sta $0540,x 175 | sta $0568,x 176 | sta $0590,x 177 | sta $05B8,x 178 | sta $05E0,x 179 | sta $0608,x 180 | sta $0630,x 181 | sta $0658,x 182 | sta $0680,x 183 | sta $06A8,x 184 | sta $06D0,x 185 | sta $06F8,x 186 | sta $0720,x 187 | sta $0748,x 188 | sta $0770,x 189 | sta $0798,x 190 | sta $07C0,x //25th row 191 | sta $07E8,x //26th row 192 | exiting_shift_column_0_23_down: 193 | rts 194 | 195 | shift_column_24_39_down: 196 | ldx $FE 197 | clc 198 | lda $FE 199 | adc #48 //48 is PETSCII for '0' 200 | sta $0428,x //2nd row 201 | sta $0450,x 202 | sta $0478,x 203 | sta $04A0,x 204 | sta $04C8,x 205 | sta $04F0,x 206 | sta $0518,x 207 | sta $0540,x 208 | sta $0568,x 209 | sta $0590,x 210 | sta $05B8,x 211 | sta $05E0,x 212 | sta $0608,x 213 | sta $0630,x 214 | sta $0658,x 215 | sta $0680,x 216 | sta $06A8,x 217 | sta $06D0,x 218 | sta $06F8,x 219 | sta $0720,x 220 | sta $0748,x 221 | sta $0770,x 222 | sta $0798,x 223 | sta $07C0,x //25th row 224 | //The x index is offset by -48-24 compared to its current value so far 225 | sec 226 | sbc #48+24 //48 are for the '0', and 24 are for the column's offset 227 | tax //Now x is 24 less than $FE 228 | //Fix first row 229 | clc 230 | lda $FE 231 | adc #48 //48 is PETSCII for '0' 232 | sta $0400,x //1st row 233 | //TRICK for this demo: fix color using a color on the same x column 234 | ldy $FE 235 | lda $D800,y 236 | sta $D800,x 237 | exiting_shift_column_24_39_down: 238 | rts 239 | 240 | make_screen_scroll: 241 | lda $FF 242 | // cmp #0 //right to left? 243 | bne scroll_left_to_right 244 | inc $FE 245 | lda $FE 246 | cmp #39+1 247 | bne exiting_make_screen_scroll 248 | lda #0 249 | sta $FE 250 | jmp exiting_make_screen_scroll 251 | scroll_left_to_right: 252 | dec $FE 253 | lda $FE 254 | cmp #$FF //Equivalent to -1, i.e. 0 decremented by 1 255 | bne exiting_make_screen_scroll 256 | lda #39 257 | sta $FE 258 | exiting_make_screen_scroll: 259 | rts 260 | 261 | screen_LSB_addr: 262 | .byte $00, $28, $50, $78, $A0, $C8, $F0, $18, $40, $68 263 | .byte $90, $B8, $E0, $08, $30, $58, $80, $A8, $D0, $F8 264 | .byte $20, $48, $70, $98, $C0, $E8 265 | screen_MSB_addr: 266 | .byte $04, $04, $04, $04, $04, $04, $04, $05, $05, $05 267 | .byte $05, $05, $05, $06, $06, $06, $06, $06, $06, $06 268 | .byte $07, $07, $07, $07, $07, $07 269 | 270 | .align $100 //Align to the nearest page boundary 271 | //Actual delay in cycles is 40+2-$FE = 42-$FE 272 | //See https://bumbershootsoft.wordpress.com/2014/05/04/cycle-exact-delays-on-the-6502/ 273 | NDelay: //(0->42 ; 1->41 ; ... ; 39->3 ; 40->2) Notice that input+output is always 42 274 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 275 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 276 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 277 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 278 | .byte $c5 //(3) 279 | nop //(2) 280 | rts //(6) -------------------------------------------------------------------------------- /VSP_Fixed_DoubleBuffered_25Rows_Coarse_Horizontal_Scrolling.asm: -------------------------------------------------------------------------------- 1 | #import "helpers.asm" 2 | 3 | .label border = $d020 4 | .label background = $d021 5 | 6 | .label cia1_interrupt_control_register = $dc0d 7 | .label cia2_interrupt_control_register = $dd0d 8 | 9 | // Note: the '-2' is required because stabilize_irq() takes 2 raster 10 | // lines to synchronize the raster. More precisely, it _always_ ends 11 | // after completing the 3rd cycle of raster line number RASTER_LINE. 12 | .const RASTER_LINE = (48-1)-2 // We want to "land" at RL:47:03 13 | 14 | // The number of cycles we want to skip: imagine to be at the top left 15 | // corner of the character page (by default at $0400) and that you can 16 | // choose to "rebase" what is the character that appears in that corner. 17 | // 18 | // Notice that when below I say "equivalent" I do not mean "identical": 19 | // it goes without saying that we will still need to adjust things 20 | // (typically the 1st or 25th row, and the 1st or 40th column.) 21 | // 22 | // If you choose: 23 | // (A) 0: Equivalent to no effect on scrolling. 24 | // (B) 1: Equivalent to right to left scrolling, where the second column 25 | // becomes the 1st, and the 1st becomes the 40th (shifted up 26 | // by one character because the 1001th byte of the char memory, 27 | // that normally is not visible, becomes the character in the 28 | // bottom right corner.) 29 | // (D)40: Equivalent to upward vertical scrolling, where the 2nd row 30 | // becomes the 1st, the 25th row becomes the 24th. 31 | // Notice how the first 24 characters of the 25th row are the 32 | // normally hidden charcters from the 1001st to the 1024th char 33 | // memory. 34 | // Notice how the last 16 characters of the 25th row correspond 35 | // to the symbols associated to the bytes that go from the 1st 36 | // to the 16th char memory locations. 37 | 38 | :BasicUpstart2(main) 39 | main: 40 | sei 41 | lda #LIGHT_GRAY 42 | sta border 43 | lda #BLACK 44 | sta background 45 | 46 | //Here we are simply emphasizing that $D011 is set at its default 47 | // value, and this means YSCROLL is 3, and that the first bad line is 48 | // 51. Since it is a good line, raster line 48 can be made into a bad 49 | // line by tinkering with $D011. 50 | // If it were a bad line to start with, it wouldn't be possible 51 | // to _change_ it into one: this change is what we call DMA delay. 52 | // Then immediately after, we restore raster line 51 to be the next 53 | // bad line. 54 | lda #$1B 55 | sta $D011 56 | 57 | //By default, we start with the screen in neutral position, 58 | //i.e., no column is scrolled. The valid values for $FE are: 59 | //[0:39] is 40 different positions for horizontal scrolling. 60 | // 40 is the "scrolled up" position (not used in this demo.) 61 | 62 | //Notice that the initial value for $FE can only be either 0 or 39, 63 | // that is because we use 0 in $FF to mean "right to left". 64 | lda #0 //By default we scroll right to left (so $ff will be 0) 65 | sta $FE 66 | // cmp #0 //Right to left? 67 | beq finalize_scroll_dir 68 | left2rigth: 69 | lda #$FF //Scroll left to right (TODO: not implemented yet!) 70 | finalize_scroll_dir: 71 | sta $FF 72 | 73 | //$FD keeps track of which screen is the primary and which one 74 | // is the one used as double buffer. 75 | lda #0 //First screen is primary (1: second screen is primary) 76 | sta $FD 77 | //cmp #0 //First screen is primary 78 | bne ssip //Second screen is primary 79 | fsip: //First screen is primary (default $0400: no need to activate) 80 | jmp init_screen 81 | ssip: 82 | jsr activate_second_buffer 83 | 84 | init_screen: 85 | init_screen_ram() 86 | 87 | lda $01 88 | and #%11111101 89 | sta $01 90 | 91 | lda #%01111111 92 | sta cia1_interrupt_control_register 93 | sta cia2_interrupt_control_register 94 | lda cia1_interrupt_control_register 95 | lda cia2_interrupt_control_register 96 | 97 | lda #%00000001 98 | sta vic2_interrupt_control_register 99 | sta vic2_interrupt_status_register 100 | :set_raster(RASTER_LINE) 101 | :mov16 #irq1 : $fffe 102 | cli 103 | 104 | loop: 105 | jmp loop 106 | 107 | //From Christian Bauer "VIC-II article" 108 | //http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt 109 | // 110 | //3.14.6. DMA delay 111 | //----------------- 112 | //The most sophisticated Bad Line manipulation is to create a Bad Line 113 | //Condition within cycles 15-53 of a raster line in the display window in 114 | //which the graphics data sequencer is in idle state, e.g. by modifying 115 | //register $d011 so that YSCROLL is equal to the lower three bits of RASTER. 116 | 117 | irq1: 118 | //jmp exiting_irq1 //Uncomment to verify initialization works as intended 119 | :stabilize_irq() //RasterLine 47 after cycle 3, in short RL:47:03 120 | 121 | // Notice that up to this point, raster line 51 is the one that is 122 | // expected to become the first bad line since YSCROLL has value 3. 123 | 124 | ldy $FE //(3, RL:47:06) 125 | sty ndelay+1 //(4, RL:47:10) Set $FE cycles into LSB NDelay's address 126 | :cycles(-3-3-4 +63 -6) //RL:47:57 127 | ndelay: 128 | //Notice that sty above is writing into the LSB of the NDelay address 129 | jsr NDelay //RL:47:57+6+42-$FE+6 == RL:47:69+42-$FE == RL:48:8+40-$FE 130 | 131 | // Make *this* raster line (i.e. raster line 48) be a Bad Line 132 | // => This triggers the VSP/DMA delay of $FE cycles. 133 | lda #$18 //(2,RL:48:(8+2+40-$FE)) %0001:1000 134 | sta $D011 //(4,RL:48:(10+4+40-$FE) = RL:48:54-$FE) 135 | 136 | //...because raster line 48 is forced into a bad line, a VSP scroll 137 | // is triggered, so the CPU is stun until cycle 54... 138 | 139 | // ...so we can reset $D011 to use $1B as soon as we can: this way 140 | // we make sure that by the end of raster line 48, YSCROLL is 141 | // back to its default value 3, and we are ready for the next frame. 142 | lda #$1B //(2, RL:48:(54-$FE+2)) 143 | sta $D011 //(4, RL:48:(56-$FE+4) = RL:48:60-$FE) 144 | 145 | exiting_irq1: 146 | asl vic2_interrupt_status_register 147 | :set_raster(RASTER_LINE+210/*-4*/) //RASTER_LINE is (48-1)-2 = 45 148 | :mov16 #irq2 : $fffe 149 | rti 150 | 151 | 152 | irq2: 153 | //jmp exiting_irq2 154 | lda $FD 155 | //cmp #0 //First screen is primary 156 | bne second_screen_is_primary 157 | 158 | first_screen_is_primary: 159 | lda $FE 160 | cmp #24 161 | bcs first_from_24_to_39 //Accumulator >= 24? 162 | first_from_0_to_23: 163 | jsr first_screen_is_primary_shift_column_0_23_down 164 | jmp exiting_irq2 165 | first_from_24_to_39: 166 | jsr first_screen_is_primary_shift_column_24_39_down 167 | lda $FE 168 | cmp #39 //Time to switch to the second buffer? 169 | bne exiting_irq2 170 | jsr activate_second_buffer 171 | jmp exiting_irq2 172 | 173 | second_screen_is_primary: 174 | lda $FE 175 | cmp #24 176 | bcs second_from_24_to_39 //Accumulator >= 24? 177 | second_from_0_to_23: 178 | jsr second_screen_is_primary_shift_column_0_23_down 179 | jmp exiting_irq2 180 | second_from_24_to_39: 181 | jsr second_screen_is_primary_shift_column_24_39_down 182 | lda $FE 183 | cmp #39 //Time to switch to the second buffer? 184 | bne exiting_irq2 185 | jsr activate_first_buffer 186 | 187 | exiting_irq2: 188 | jsr make_screen_scroll 189 | asl vic2_interrupt_status_register 190 | :set_raster(RASTER_LINE) 191 | :mov16 #irq1 : $fffe 192 | rti 193 | 194 | activate_second_buffer: 195 | lda #1 196 | sta $FD //Make second screen primary 197 | lda $D018 198 | and #$0F 199 | ora #$80 //$2000 200 | sta $D018 201 | rts 202 | 203 | activate_first_buffer: 204 | lda #0 205 | sta $FD //Make first screen primary 206 | lda $D018 207 | and #$0F 208 | ora #$10 //$0400 209 | sta $D018 210 | rts 211 | 212 | first_screen_is_primary_shift_column_0_23_down: 213 | ldx $FE 214 | clc 215 | lda $FE 216 | adc #33 //in PETSCII 33 is the character code for '!' 217 | //Second screen (now it is being used for double buffering) 218 | sta $2000,x //1st row 219 | sta $2028,x //2nd row 220 | sta $2050,x 221 | sta $2078,x 222 | sta $20A0,x 223 | sta $20C8,x 224 | sta $20F0,x 225 | sta $2118,x 226 | sta $2140,x 227 | sta $2168,x 228 | sta $2190,x 229 | sta $21B8,x 230 | sta $21E0,x 231 | sta $2208,x 232 | sta $2230,x 233 | sta $2258,x 234 | sta $2280,x 235 | sta $22A8,x 236 | sta $22D0,x 237 | sta $22F8,x 238 | sta $2320,x 239 | sta $2348,x 240 | sta $2370,x 241 | sta $2398,x 242 | sta $23C0,x //25th row 243 | //First (current) screen 244 | sta $0428,x //2nd row 245 | sta $0450,x 246 | sta $0478,x 247 | sta $04A0,x 248 | sta $04C8,x 249 | sta $04F0,x 250 | sta $0518,x 251 | sta $0540,x 252 | sta $0568,x 253 | sta $0590,x 254 | sta $05B8,x 255 | sta $05E0,x 256 | sta $0608,x 257 | sta $0630,x 258 | sta $0658,x 259 | sta $0680,x 260 | sta $06A8,x 261 | sta $06D0,x 262 | sta $06F8,x 263 | sta $0720,x 264 | sta $0748,x 265 | sta $0770,x 266 | sta $0798,x 267 | sta $07C0,x //25th row 268 | sta $07E8,x //26th row 269 | exiting_first_screen_is_primary_shift_column_0_23_down: 270 | rts 271 | 272 | first_screen_is_primary_shift_column_24_39_down: 273 | ldx $FE 274 | clc 275 | lda $FE 276 | adc #33 //33 is PETSCII for '!' 277 | //Second screen (now it is being used for double buffering) 278 | sta $2000,x //1st row 279 | sta $2028,x //2nd row 280 | sta $2050,x 281 | sta $2078,x 282 | sta $20A0,x 283 | sta $20C8,x 284 | sta $20F0,x 285 | sta $2118,x 286 | sta $2140,x 287 | sta $2168,x 288 | sta $2190,x 289 | sta $21B8,x 290 | sta $21E0,x 291 | sta $2208,x 292 | sta $2230,x 293 | sta $2258,x 294 | sta $2280,x 295 | sta $22A8,x 296 | sta $22D0,x 297 | sta $22F8,x 298 | sta $2320,x 299 | sta $2348,x 300 | sta $2370,x 301 | sta $2398,x 302 | sta $23C0,x //25th row 303 | //First (current) screen 304 | sta $0428,x //2nd row 305 | sta $0450,x 306 | sta $0478,x 307 | sta $04A0,x 308 | sta $04C8,x 309 | sta $04F0,x 310 | sta $0518,x 311 | sta $0540,x 312 | sta $0568,x 313 | sta $0590,x 314 | sta $05B8,x 315 | sta $05E0,x 316 | sta $0608,x 317 | sta $0630,x 318 | sta $0658,x 319 | sta $0680,x 320 | sta $06A8,x 321 | sta $06D0,x 322 | sta $06F8,x 323 | sta $0720,x 324 | sta $0748,x 325 | sta $0770,x 326 | sta $0798,x 327 | sta $07C0,x //25th row 328 | //The x index is offset by -33-24 compared to its current value so far 329 | sec 330 | sbc #33+24 //33 are for the '!', and 24 are for the column's offset 331 | tax //Now x is 24 less than $FE 332 | //Fix first row 333 | clc 334 | lda $FE 335 | adc #33 //33 is PETSCII for '!' 336 | sta $0400,x //1st row 337 | //TRICK for this demo: fix color using a color on the same x column 338 | ldy $FE 339 | lda $D800,y 340 | sta $D800,x 341 | exiting_first_screen_is_primary_shift_column_24_39_down: 342 | rts 343 | 344 | second_screen_is_primary_shift_column_0_23_down: 345 | ldx $FE 346 | clc 347 | lda $FE 348 | adc #33 //33 is PETSCII for '!' 349 | //First screen (here is being used for double buffering) 350 | sta $0400,x //1st row 351 | sta $0428,x //2nd row 352 | sta $0450,x 353 | sta $0478,x 354 | sta $04A0,x 355 | sta $04C8,x 356 | sta $04F0,x 357 | sta $0518,x 358 | sta $0540,x 359 | sta $0568,x 360 | sta $0590,x 361 | sta $05B8,x 362 | sta $05E0,x 363 | sta $0608,x 364 | sta $0630,x 365 | sta $0658,x 366 | sta $0680,x 367 | sta $06A8,x 368 | sta $06D0,x 369 | sta $06F8,x 370 | sta $0720,x 371 | sta $0748,x 372 | sta $0770,x 373 | sta $0798,x 374 | sta $07C0,x //25th row 375 | //Second (current) screen 376 | sta $2028,x //2nd row 377 | sta $2050,x 378 | sta $2078,x 379 | sta $20A0,x 380 | sta $20C8,x 381 | sta $20F0,x 382 | sta $2118,x 383 | sta $2140,x 384 | sta $2168,x 385 | sta $2190,x 386 | sta $21B8,x 387 | sta $21E0,x 388 | sta $2208,x 389 | sta $2230,x 390 | sta $2258,x 391 | sta $2280,x 392 | sta $22A8,x 393 | sta $22D0,x 394 | sta $22F8,x 395 | sta $2320,x 396 | sta $2348,x 397 | sta $2370,x 398 | sta $2398,x 399 | sta $23C0,x //25th row 400 | sta $23E8,x //26th row 401 | exiting_second_screen_is_primary_shift_column_0_23_down: 402 | rts 403 | 404 | second_screen_is_primary_shift_column_24_39_down: 405 | ldx $FE 406 | clc 407 | lda $FE 408 | adc #33 //33 is PETSCII for '!' 409 | //First screen (here it is being used for double buffering) 410 | sta $0400,x //1st row 411 | sta $0428,x //2nd row 412 | sta $0450,x 413 | sta $0478,x 414 | sta $04A0,x 415 | sta $04C8,x 416 | sta $04F0,x 417 | sta $0518,x 418 | sta $0540,x 419 | sta $0568,x 420 | sta $0590,x 421 | sta $05B8,x 422 | sta $05E0,x 423 | sta $0608,x 424 | sta $0630,x 425 | sta $0658,x 426 | sta $0680,x 427 | sta $06A8,x 428 | sta $06D0,x 429 | sta $06F8,x 430 | sta $0720,x 431 | sta $0748,x 432 | sta $0770,x 433 | sta $0798,x 434 | sta $07C0,x //25th row 435 | //Second (current) screen 436 | sta $2028,x //2nd row 437 | sta $2050,x 438 | sta $2078,x 439 | sta $20A0,x 440 | sta $20C8,x 441 | sta $20F0,x 442 | sta $2118,x 443 | sta $2140,x 444 | sta $2168,x 445 | sta $2190,x 446 | sta $21B8,x 447 | sta $21E0,x 448 | sta $2208,x 449 | sta $2230,x 450 | sta $2258,x 451 | sta $2280,x 452 | sta $22A8,x 453 | sta $22D0,x 454 | sta $22F8,x 455 | sta $2320,x 456 | sta $2348,x 457 | sta $2370,x 458 | sta $2398,x 459 | sta $23C0,x //25th row 460 | //The x index is offset by -33-24 compared to its current value so far 461 | sec 462 | sbc #33+24 //33 are for the '!', and 24 are for the column's offset 463 | tax //Now x is 24 less than $FE 464 | //Fix first row 465 | clc 466 | lda $FE 467 | adc #33 //33 is PETSCII for '!' 468 | sta $2000,x //1st row 469 | //TRICK for this demo: fix color using a color on the same x column 470 | ldy $FE 471 | lda $D800,y 472 | sta $D800,x 473 | exiting_second_screen_is_primary_shift_column_24_39_down: 474 | rts 475 | 476 | make_screen_scroll: 477 | lda $FF 478 | // cmp #0 //right to left? 479 | bne scroll_left_to_right 480 | scroll_right_to_left: 481 | inc $FE 482 | lda $FE 483 | cmp #39+1 484 | bne exiting_make_screen_scroll 485 | lda #0 486 | sta $FE 487 | jmp exiting_make_screen_scroll 488 | scroll_left_to_right: 489 | dec $FE 490 | lda $FE 491 | cmp #$FF //Equivalent to -1, i.e. 0 decremented by 1 492 | bne exiting_make_screen_scroll 493 | lda #39 494 | sta $FE 495 | exiting_make_screen_scroll: 496 | rts 497 | 498 | .align $100 //Align to the nearest page boundary 499 | //Actual delay in cycles is 40+2-$FE = 42-$FE 500 | //See https://bumbershootsoft.wordpress.com/2014/05/04/cycle-exact-delays-on-the-6502/ 501 | NDelay: //(0->42 ; 1->41 ; ... ; 39->3 ; 40->2) Notice that input+output is always 42 502 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 503 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 504 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 505 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 506 | .byte $c5 //(3) 507 | nop //(2) 508 | rts //(6) -------------------------------------------------------------------------------- /VSP_Fixed_DoubleBuffered_25Rows_Smooth_Horizontal_Scrolling.asm: -------------------------------------------------------------------------------- 1 | #import "helpers.asm" 2 | 3 | .label border = $d020 4 | .label background = $d021 5 | 6 | .label cia1_interrupt_control_register = $dc0d 7 | .label cia2_interrupt_control_register = $dd0d 8 | 9 | // Note: the '-2' is required because stabilize_irq() takes 2 raster 10 | // lines to synchronize the raster. More precisely, it _always_ ends 11 | // after completing the 3rd cycle of raster line number RASTER_LINE. 12 | .const RASTER_LINE_47 = (48-1)-2 //We want to "land" at RL:47:03. 13 | .const RASTER_LINE_251 = 251 //No need to be cycle exact, so RL:251. 14 | 15 | // The number of cycles we want to skip: imagine to be at the top left 16 | // corner of the character page (by default at $0400) and that you can 17 | // choose to "rebase" what is the character that appears in that corner. 18 | // 19 | // Notice that when below I say "equivalent" I do not mean "identical": 20 | // it goes without saying that we will still need to adjust things 21 | // (typically the 1st or 25th row, and the 1st or 40th column.) 22 | // 23 | // If you choose: 24 | // (A) 0: Equivalent to no effect on scrolling. 25 | // (B) 1: Equivalent to right to left scrolling, where the second column 26 | // becomes the 1st, and the 1st becomes the 40th (shifted up 27 | // by one character because the 1001th byte of the char memory, 28 | // that normally is not visible, becomes the character in the 29 | // bottom right corner.) 30 | // (D)40: Equivalent to upward vertical scrolling, where the 2nd row 31 | // becomes the 1st, the 25th row becomes the 24th. 32 | // Notice how the first 24 characters of the 25th row are the 33 | // normally hidden charcters from the 1001st to the 1024th char 34 | // memory. 35 | // Notice how the last 16 characters of the 25th row correspond 36 | // to the symbols associated to the bytes that go from the 1st 37 | // to the 16th char memory locations. 38 | 39 | :BasicUpstart2(main) 40 | main: 41 | sei 42 | lda #BLACK 43 | sta border 44 | sta background 45 | 46 | //Place the screen as right as possible by setting XSCROLL = 7. 47 | //Notice that we are also setting screen width to 38 columns. 48 | lda #$C7 49 | sta $D016 50 | 51 | //Here we are simply emphasizing that $D011 is set at its default 52 | // value, and this means YSCROLL is 3, and that the first bad line is 53 | // 51. Since it is a good line, raster line 48 can be made into a bad 54 | // line by tinkering with $D011. 55 | // If it were a bad line to start with, it wouldn't be possible 56 | // to _change_ it into one: this change is what we call DMA delay. 57 | // Then immediately after, we restore raster line 51 to be the next 58 | // bad line. 59 | lda #$1B 60 | sta $D011 61 | 62 | //By default, we start with the screen in neutral position, 63 | //i.e., no column is scrolled. The valid values for $FE are: 64 | //[0:39] is 40 different positions for horizontal scrolling. 65 | // 40 is the "scrolled up" position (not used in this demo.) 66 | 67 | //Notice that the initial value for $FE can only be either 0 or 39, 68 | // that is because we use 0 in $FF to mean "right to left". 69 | lda #0 //By default we scroll right to left (so $ff will be 0) 70 | sta $FE 71 | // cmp #0 //Right to left? 72 | beq finalize_scroll_dir 73 | left2rigth: 74 | lda #$FF //Scroll left to right (TODO: not implemented yet!) 75 | finalize_scroll_dir: 76 | sta $FF 77 | 78 | //$FD keeps track of which screen is the primary and which one 79 | // is the one used as double buffer. 80 | lda #0 //First screen is primary (1: second screen is primary) 81 | sta $FD 82 | //cmp #0 //First screen is primary 83 | bne ssip //Second screen is primary 84 | fsip: //First screen is primary (default $0400: no need to activate) 85 | jmp init_screen 86 | ssip: 87 | jsr activate_second_buffer 88 | 89 | init_screen: 90 | init_screen_ram() 91 | 92 | lda $01 93 | and #%11111101 94 | sta $01 95 | 96 | lda #%01111111 97 | sta cia1_interrupt_control_register 98 | sta cia2_interrupt_control_register 99 | lda cia1_interrupt_control_register 100 | lda cia2_interrupt_control_register 101 | 102 | lda #%00000001 103 | sta vic2_interrupt_control_register 104 | sta vic2_interrupt_status_register 105 | :set_raster(RASTER_LINE_47) 106 | :mov16 #irq1 : $fffe 107 | cli 108 | 109 | loop: 110 | jmp loop 111 | 112 | //From Christian Bauer "VIC-II article" 113 | //http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt 114 | // 115 | //3.14.6. DMA delay 116 | //----------------- 117 | //The most sophisticated Bad Line manipulation is to create a Bad Line 118 | //Condition within cycles 15-53 of a raster line in the display window in 119 | //which the graphics data sequencer is in idle state, e.g. by modifying 120 | //register $d011 so that YSCROLL is equal to the lower three bits of RASTER. 121 | 122 | irq1: 123 | //jmp exiting_irq1 //Uncomment to verify initialization works as intended 124 | :stabilize_irq() //RasterLine 47 after cycle 3, in short RL:47:03 125 | 126 | // Notice that up to this point, raster line 51 is the one that is 127 | // expected to become the first bad line since YSCROLL has value 3. 128 | 129 | ldy $FE //(3, RL:47:06) 130 | sty ndelay+1 //(4, RL:47:10) Set $FE cycles into LSB NDelay's address 131 | :cycles(-3-3-4 +63 -6) //RL:47:57 132 | ndelay: 133 | //Notice that sty above is writing into the LSB of the NDelay address 134 | jsr NDelay //RL:47:57+6+42-$FE+6 == RL:47:69+42-$FE == RL:48:8+40-$FE 135 | 136 | // Make *this* raster line (i.e. raster line 48) be a Bad Line 137 | // => This triggers the VSP/DMA delay of $FE cycles. 138 | lda #$18 //(2,RL:48:(8+2+40-$FE)) %0001:1000 139 | sta $D011 //(4,RL:48:(10+4+40-$FE) = RL:48:54-$FE) 140 | 141 | //...because raster line 48 is forced into a bad line, a VSP scroll 142 | // is triggered, so the CPU is stun until cycle 54... 143 | 144 | // ...so we can reset $D011 to use $1B as soon as we can: this way 145 | // we make sure that by the end of raster line 48, YSCROLL is 146 | // back to its default value 3, and we are ready for the next frame. 147 | lda #$1B //(2, RL:48:(54-$FE+2)) 148 | sta $D011 //(4, RL:48:(56-$FE+4) = RL:48:60-$FE) 149 | 150 | exiting_irq1_to_irq2: 151 | asl vic2_interrupt_status_register 152 | :set_raster(RASTER_LINE_251) 153 | :mov16 #irq2 : $fffe 154 | rti 155 | 156 | irq2: 157 | //jmp exiting_irq2 158 | //Check XSCROLL and decrease it 159 | lda $D016 160 | and #$07 161 | beq reset_xscroll_back_to_7 162 | dec $D016 163 | jmp exiting_irq2_to_irq1 164 | reset_xscroll_back_to_7: 165 | //Set XSCROLL back to 7. 166 | lda #$C7 167 | sta $D016 168 | 169 | primary_screen: 170 | //Check what screen is primary 171 | lda $FD 172 | //cmp #0 //First screen is primary 173 | bne second_screen_is_primary 174 | 175 | first_screen_is_primary: 176 | lda $FE 177 | cmp #24 178 | bcs first_from_24_to_39 //Accumulator >= 24? 179 | first_from_0_to_23: 180 | jsr first_screen_is_primary_shift_column_0_23_down 181 | jmp scroll_screen 182 | first_from_24_to_39: 183 | jsr first_screen_is_primary_shift_column_24_39_down 184 | lda $FE 185 | cmp #39 //Time to switch to the second buffer? 186 | bne scroll_screen 187 | jsr activate_second_buffer 188 | jmp scroll_screen 189 | 190 | second_screen_is_primary: 191 | lda $FE 192 | cmp #24 193 | bcs second_from_24_to_39 //Accumulator >= 24? 194 | second_from_0_to_23: 195 | jsr second_screen_is_primary_shift_column_0_23_down 196 | jmp scroll_screen 197 | second_from_24_to_39: 198 | jsr second_screen_is_primary_shift_column_24_39_down 199 | lda $FE 200 | cmp #39 //Time to switch to the second buffer? 201 | bne scroll_screen 202 | jsr activate_first_buffer 203 | 204 | scroll_screen: 205 | jsr make_screen_scroll 206 | 207 | exiting_irq2_to_irq1: 208 | asl vic2_interrupt_status_register 209 | :set_raster(RASTER_LINE_47) 210 | :mov16 #irq1 : $fffe 211 | rti 212 | 213 | activate_second_buffer: 214 | lda #1 215 | sta $FD //Make second screen primary 216 | lda $D018 217 | and #$0F 218 | ora #$80 //$2000 219 | sta $D018 220 | rts 221 | 222 | activate_first_buffer: 223 | lda #0 224 | sta $FD //Make first screen primary 225 | lda $D018 226 | and #$0F 227 | ora #$10 //$0400 228 | sta $D018 229 | rts 230 | 231 | first_screen_is_primary_shift_column_0_23_down: 232 | ldx $FE 233 | clc 234 | lda $FE 235 | adc #33 //in PETSCII 33 is the character code for '!' 236 | //Second screen (now it is being used for double buffering) 237 | sta $2000,x //1st row 238 | sta $2028,x //2nd row 239 | sta $2050,x 240 | sta $2078,x 241 | sta $20A0,x 242 | sta $20C8,x 243 | sta $20F0,x 244 | sta $2118,x 245 | sta $2140,x 246 | sta $2168,x 247 | sta $2190,x 248 | sta $21B8,x 249 | sta $21E0,x 250 | sta $2208,x 251 | sta $2230,x 252 | sta $2258,x 253 | sta $2280,x 254 | sta $22A8,x 255 | sta $22D0,x 256 | sta $22F8,x 257 | sta $2320,x 258 | sta $2348,x 259 | sta $2370,x 260 | sta $2398,x 261 | sta $23C0,x //25th row 262 | //First (current) screen 263 | sta $0428,x //2nd row 264 | sta $0450,x 265 | sta $0478,x 266 | sta $04A0,x 267 | sta $04C8,x 268 | sta $04F0,x 269 | sta $0518,x 270 | sta $0540,x 271 | sta $0568,x 272 | sta $0590,x 273 | sta $05B8,x 274 | sta $05E0,x 275 | sta $0608,x 276 | sta $0630,x 277 | sta $0658,x 278 | sta $0680,x 279 | sta $06A8,x 280 | sta $06D0,x 281 | sta $06F8,x 282 | sta $0720,x 283 | sta $0748,x 284 | sta $0770,x 285 | sta $0798,x 286 | sta $07C0,x //25th row 287 | sta $07E8,x //26th row 288 | exiting_first_screen_is_primary_shift_column_0_23_down: 289 | rts 290 | 291 | first_screen_is_primary_shift_column_24_39_down: 292 | ldx $FE 293 | clc 294 | lda $FE 295 | adc #33 //33 is PETSCII for '!' 296 | //Second screen (now it is being used for double buffering) 297 | sta $2000,x //1st row 298 | sta $2028,x //2nd row 299 | sta $2050,x 300 | sta $2078,x 301 | sta $20A0,x 302 | sta $20C8,x 303 | sta $20F0,x 304 | sta $2118,x 305 | sta $2140,x 306 | sta $2168,x 307 | sta $2190,x 308 | sta $21B8,x 309 | sta $21E0,x 310 | sta $2208,x 311 | sta $2230,x 312 | sta $2258,x 313 | sta $2280,x 314 | sta $22A8,x 315 | sta $22D0,x 316 | sta $22F8,x 317 | sta $2320,x 318 | sta $2348,x 319 | sta $2370,x 320 | sta $2398,x 321 | sta $23C0,x //25th row 322 | //First (current) screen 323 | sta $0428,x //2nd row 324 | sta $0450,x 325 | sta $0478,x 326 | sta $04A0,x 327 | sta $04C8,x 328 | sta $04F0,x 329 | sta $0518,x 330 | sta $0540,x 331 | sta $0568,x 332 | sta $0590,x 333 | sta $05B8,x 334 | sta $05E0,x 335 | sta $0608,x 336 | sta $0630,x 337 | sta $0658,x 338 | sta $0680,x 339 | sta $06A8,x 340 | sta $06D0,x 341 | sta $06F8,x 342 | sta $0720,x 343 | sta $0748,x 344 | sta $0770,x 345 | sta $0798,x 346 | sta $07C0,x //25th row 347 | //The x index is offset by -33-24 compared to its current value so far 348 | sec 349 | sbc #33+24 //33 are for the '!', and 24 are for the column's offset 350 | tax //Now x is 24 less than $FE 351 | //Fix first row 352 | clc 353 | lda $FE 354 | adc #33 //33 is PETSCII for '!' 355 | sta $0400,x //1st row 356 | //TRICK for this demo: fix color using a color on the same x column 357 | ldy $FE 358 | lda $D800,y 359 | sta $D800,x 360 | exiting_first_screen_is_primary_shift_column_24_39_down: 361 | rts 362 | 363 | second_screen_is_primary_shift_column_0_23_down: 364 | ldx $FE 365 | clc 366 | lda $FE 367 | adc #33 //33 is PETSCII for '!' 368 | //First screen (here is being used for double buffering) 369 | sta $0400,x //1st row 370 | sta $0428,x //2nd row 371 | sta $0450,x 372 | sta $0478,x 373 | sta $04A0,x 374 | sta $04C8,x 375 | sta $04F0,x 376 | sta $0518,x 377 | sta $0540,x 378 | sta $0568,x 379 | sta $0590,x 380 | sta $05B8,x 381 | sta $05E0,x 382 | sta $0608,x 383 | sta $0630,x 384 | sta $0658,x 385 | sta $0680,x 386 | sta $06A8,x 387 | sta $06D0,x 388 | sta $06F8,x 389 | sta $0720,x 390 | sta $0748,x 391 | sta $0770,x 392 | sta $0798,x 393 | sta $07C0,x //25th row 394 | //Second (current) screen 395 | sta $2028,x //2nd row 396 | sta $2050,x 397 | sta $2078,x 398 | sta $20A0,x 399 | sta $20C8,x 400 | sta $20F0,x 401 | sta $2118,x 402 | sta $2140,x 403 | sta $2168,x 404 | sta $2190,x 405 | sta $21B8,x 406 | sta $21E0,x 407 | sta $2208,x 408 | sta $2230,x 409 | sta $2258,x 410 | sta $2280,x 411 | sta $22A8,x 412 | sta $22D0,x 413 | sta $22F8,x 414 | sta $2320,x 415 | sta $2348,x 416 | sta $2370,x 417 | sta $2398,x 418 | sta $23C0,x //25th row 419 | sta $23E8,x //26th row 420 | exiting_second_screen_is_primary_shift_column_0_23_down: 421 | rts 422 | 423 | second_screen_is_primary_shift_column_24_39_down: 424 | ldx $FE 425 | clc 426 | lda $FE 427 | adc #33 //33 is PETSCII for '!' 428 | //First screen (here it is being used for double buffering) 429 | sta $0400,x //1st row 430 | sta $0428,x //2nd row 431 | sta $0450,x 432 | sta $0478,x 433 | sta $04A0,x 434 | sta $04C8,x 435 | sta $04F0,x 436 | sta $0518,x 437 | sta $0540,x 438 | sta $0568,x 439 | sta $0590,x 440 | sta $05B8,x 441 | sta $05E0,x 442 | sta $0608,x 443 | sta $0630,x 444 | sta $0658,x 445 | sta $0680,x 446 | sta $06A8,x 447 | sta $06D0,x 448 | sta $06F8,x 449 | sta $0720,x 450 | sta $0748,x 451 | sta $0770,x 452 | sta $0798,x 453 | sta $07C0,x //25th row 454 | //Second (current) screen 455 | sta $2028,x //2nd row 456 | sta $2050,x 457 | sta $2078,x 458 | sta $20A0,x 459 | sta $20C8,x 460 | sta $20F0,x 461 | sta $2118,x 462 | sta $2140,x 463 | sta $2168,x 464 | sta $2190,x 465 | sta $21B8,x 466 | sta $21E0,x 467 | sta $2208,x 468 | sta $2230,x 469 | sta $2258,x 470 | sta $2280,x 471 | sta $22A8,x 472 | sta $22D0,x 473 | sta $22F8,x 474 | sta $2320,x 475 | sta $2348,x 476 | sta $2370,x 477 | sta $2398,x 478 | sta $23C0,x //25th row 479 | //The x index is offset by -33-24 compared to its current value so far 480 | sec 481 | sbc #33+24 //33 are for the '!', and 24 are for the column's offset 482 | tax //Now x is 24 less than $FE 483 | //Fix first row 484 | clc 485 | lda $FE 486 | adc #33 //33 is PETSCII for '!' 487 | sta $2000,x //1st row 488 | //TRICK for this demo: fix color using a color on the same x column 489 | ldy $FE 490 | lda $D800,y 491 | sta $D800,x 492 | exiting_second_screen_is_primary_shift_column_24_39_down: 493 | rts 494 | 495 | make_screen_scroll: 496 | lda $FF 497 | // cmp #0 //right to left? 498 | bne scroll_left_to_right 499 | scroll_right_to_left: 500 | inc $FE 501 | lda $FE 502 | cmp #39+1 503 | bne exiting_make_screen_scroll 504 | lda #0 505 | sta $FE 506 | jmp exiting_make_screen_scroll 507 | scroll_left_to_right: 508 | dec $FE 509 | lda $FE 510 | cmp #$FF //Equivalent to -1, i.e. 0 decremented by 1 511 | bne exiting_make_screen_scroll 512 | lda #39 513 | sta $FE 514 | exiting_make_screen_scroll: 515 | rts 516 | 517 | .align $100 //Align to the nearest page boundary 518 | //Actual delay in cycles is 40+2-$FE = 42-$FE 519 | //See https://bumbershootsoft.wordpress.com/2014/05/04/cycle-exact-delays-on-the-6502/ 520 | NDelay: //(0->42 ; 1->41 ; ... ; 39->3 ; 40->2) Notice that input+output is always 42 521 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 522 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 523 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 524 | .byte $c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9,$c9 525 | .byte $c5 //(3) 526 | nop //(2) 527 | rts //(6) --------------------------------------------------------------------------------