Learn Multi platform Z80 Assembly Programming... With Vampires!
Platform Specific Lessons
<- Back to the Main Contents & Basic Z80 Assembly Lessons

Platform Specific Series - Lets learn how the hardware of the systems work, so we can get it to do what we want... Covers Amsrad CPC,MSX,ZX Spectrum, TI-83,Enterprise 128/64 and Sam Coupe!
    Lesson P1 - Basic Firmware Text functions

Lesson P2 - More Text Functions, Improvements... and the Sam Coupe!

Lesson P3 - Bitmap graphics on the Amstrad CPC and Enterprise 128

Lesson P4 - Bitmap graphics on the ZX Spectrum and Sam Coupe

Lesson P5 - Bitmap graphics on the TI-83 and MSX

Lesson P6 - Keyreading on the Amstrad CPC, ZX Spectrum and Sam Coupe

Lesson P7 - Keyreading on the MSX, Enterprise and TI-83

Lesson P8 - Tilemap graphics on the Sega Master System & Game Gear

Lesson P9 - Tilemap graphics on the Gameboy and Gameboy Color

Lesson P10 - Tilemap graphics on the MSX1

Lesson P11 - Tilemap graphics on the MSX2

Lesson P12 - Joypad reading on Master System,GameGear, Gameboy and Gameboy Color

Lesson P13 - Palette definitions on the Amstrad CPC and CPC+

Lesson P14 - Palette definitions on the Enterprise and Sam Coupe

Lesson P15 - Palette definitions on the MSX2 and V9990

Lesson P16 - Palette definitions on the Sega Master System and Game Gear

Lesson P17 - Palette definitions on the Gameboy and Gameboy Color

Lesson P18 - Making Sound with the AY-3-8910 on the Amstrad CPC, MSX,ZX Spectrum.... and NeoGeo + Atari ST!!

Lesson P19 - Sound on the Elan Enterprise

Lesson P20 - Sound on the Sam Coupe

Lesson P21 - Sound on the Gameboy and GBC

Lesson P22 - Sound with the SN76489 on the Master System, GameGear, Megadrive (Genesis) and BBC Micro!

Lesson P23 - Sound with the 'Beeper' on the ZX Spectrum and Apple II

Lesson P24 - Bankswitching and hardware detection on the Amstrad CPC

Lesson P25 - Bankswitching and hardware detection on the MSX

Lesson P26 - Bankswitching and hardware detection on the ZX Spectrum

Lesson P27 - Bankswitching and hardware detection on the Enterprise

Lesson P28 - Bankswitching and hardware detection on the Sam Coupe

Lesson P29 - Hardware detection and Bank Switching on the Gameboy/GBC and Sega Mastersystem/GameGear

Lesson P30 - Hardware Sprites on the gameboy

Lesson P31 - Hardware Sprites on the Master System / Game Gear and MSX1!

Lesson P32 - Hardware Sprites on the CPC+

Lesson P33 - Bitmap Graphics on the Camputers Lynx

Lesson P34 - Sound and Keyboard on the Camputers Lynx

Lesson P35 - Playing Digital Sound with WAV on the AY-3-8910!

Lesson P36 - Playing Digital Sound with WAV on the CPC+ via DMA!

Lesson P37 - Playing Digital Sound with WAV on the Sam Coupe, Camputers Lynx and ZX Spectrum

Lesson P38 - Playing Digital Sound with WAV on the Sega MasterSystem/GameGear, Elan Enterprise and GameBoy/GBC

Lesson P39 - Setting the CPC screen with CRTC registers

Lesson P40 - Syncronized mode switches for 320x200 @ 16 color EGX graphics on the Amstrad CPC

Lesson P41 - CRTC Rupture for Interrupt based splitscreen on the CPC

Lesson P42 - Advanced CRTC Rupture






Lesson P41 - CRTC Rupture for Interrupt based splitscreen
We're going to take things to another level, and learn how to exploit the CRTC to do clever tricks...

First we'll take a look at rupture, which allows for CPC Hardware Splitscreen.
 

CPC_CRTC_Rupture_Interrupt.asm

The CRTC in detail... Know your Enemy!
We've looked at the CRTC before, but we've not looked at it in enough depth for what we're going to do now... we need to look at things in a bit more detail!

Secrets of the CPC screen
We all know the 'Bitmap Screen' area of the CPC screen where our text and gameplay occur... and we all know too well the 'Border' area which doesn't really do much... but there's some more areas we need to know about...

As the beam of the CRT draws, the 'Offscreen area' is areas that are not visible on the screen, above, below or to the sides of the screen.

the Hblank area is to the right of the visible screen, the Vblank area is at the bottom...

The screen is divided into 8 pixel tall character blocks... so the total screen (including invisible areas) is 39 characters tall

Interrupts ALWAYS occur every 52 pixel lines, so they don't divide equally to character blocks... 


While an interrupt happens every 52 lines, we CAN trick the interrupt into happening earlier using a trick called RVI (Rupture: Vertical, invisible)

This combines multiple lines into one, and makes the interrupt happen faster, but it's VERY complex to get working, and we can't look at it yet.

CRTC Registers
The CRTC has 18 registers we can read and write to, but it also has some INTERNAL registers that we' can't change, but we can see them within WinApe, and we'll need to know what many of them do when it comes to using complex CRTC tricks

It's also important to note that some CRTC chips have limitations that others do not... we need to try to make sure our code works the same on ALL CRTC chips

Reg Num Abbrev Name Range Bits Default Value 256x192 384x272(26k) Details CRTC Limitations
&00 HTOT Horizontal Total 0-255 DDDDDDDD 63 (&3F) 63 63 Physical width of screen -1 Leave alone!
&01 HDISP Horizontal Displayed 0-255 DDDDDDDD 40 (&28) 32 48 Logical width in Chars (8 pixels in mode 1)
&02 HSYNC Horizontal Sync Position 0-255 DDDDDDDD 46 (&2E) 42 51 Logical Xpos 2: HsncPos+HsyncWid must be < Htot
&03 V/HWID Horiz. and Vert. Sync Widths 0-15,0-15 VVVVHHHH 142 (&8E) 134 (8,6) 142 (8,14) Hsync / Vsync area size Leave alone! 1+2: Vhei fixed to 16
&04 VTOT Vertical Total 0-127 -DDDDDDD 38 (&26) 38 38 Physical height of screen -1 Leave alone!
&05 VADJ Vertical Total Adjust 0-31 ---DDDDD 0 0 0 Scanline Offset
&06 VDISP Vertical Displayed 0-127 -DDDDDDD 25 24 34 Logical Height in Chars (8 Pixels)
&07 VSYNC Vertical Sync position 0-127 -DDDDDDD 30 31 35 Logical Ypos of screen
&08
Interlace and Skew 0-3 ------DD 0 0 0 0/2=off 1/3=on Leave alone!
&09 MR Maximum Raster Address 0-31 ---DDDDD 7 7 7 Scanlines per Char row Leave alone!
&0A
Cursor Start Raster 0-127 -DDDDDDD 0 0 0 unused
&0B
Cursor End Raster 0-31 ---DDDDD 0 0 0 unused
&0C DISPH Display Start Address 0-63 xxPPSSOO 00 / 16 /
32 / 48
00 / 16 /
32 / 48
12+1 / 28 /
44+1 / 60
PP=Screen Page (11=C000)
S=Size(11=32k else 16k) O=Offset

&0D DISPL Display Start Address 0-255 OOOOOOOO 0 0 0 O=Offset
&0E CURH Cursor Address 0-63 --DDDDDD 0 0 0 unused
&0F CURL Cursor Address 0-255 DDDDDDDD 0 0 0 unused
&10 LPH Light Pen Address 0-63 --DDDDDD 0 0 0 Read Only
&11 LPL Light Pen Address 0-255 DDDDDDDD 0 0 0 Read Only
internal VCC Vertical Character Count




Ypos of drawn line
internal R52 Raster 52 interrupt timer




Time since last interrupt
internal HDC Horizontal Display Counter




Current Hpos
internal HCC Horizontal Character Count




Xpos of drawn line
internal VMA Video Memory Address




Address of current screenpos
internal VLC Vertical Line Count




Line of character block being drawn
internal VSC Vertical Sync Counter




Winape emulator function Time synce vsync
internal VTAC Vertical Total Adjust Counter




Time since end of frame
internal HSC Horizontal Sync Counter




Cycles during Hsync
internal VDUR VDU Raster




Winape Emulator function offset within display

We can see the internal registers within Winape

Select Registers from the Debug menu


We can also turn on ROW HIGHLIGHT - this will show a line on the screen when the emulator is paused, which shows what the beam is currently drawing

Remember, the Memory address and screen size are only read in at the start of a new screen, and some tricks (like mode changes and RVI) need to be synchronized to the end of the line
We'll need to keep an eye on HCC and VCC to do this, and ROW HIGHLIGHT will help make things more clear.


Registers and how they affect the screen.
The position of the screen, the borders, and the Vblank locations are all controlled by the registers,

The current location of the beam is stored in the internal registers...


The physical size of the screen is fixed, and there are limits to what the CRTC can do within the screen... but there are some tricks we can do to exploit the CRTC and get it to do things it usually wouldn't!

If we mess with Hblank or Vblank areas, or VSYNC there's a very good chance we're going to end up with an unstable screen!
This can also happen if our screen width (HTOTAL) is too big,

This is just part of the difficulty of these CRTC tricks, we're going to have to do some of the work of the CRTC ourselves if we want to do this kind of thing.

Rupture... two screens are better than one!

We can change colors at any point during the screen, to get more than 4 colors on a Mode 1 screen (Like in ChibiAkumas)
We saw last week that EGX switches the screen mode every line to simulate a new screen mode...

But what if we want to have two areas of the screen that hardware scroll independently? we'd probably want to change registers R12 and R13 to set the display base... but unfortunately the CRTC only reads in this address when it starts drawing the screen...

But we can Cheat!... we define a short screen... then as soon as it finishes, define a new screen.... this will trick the CRTC into starting again... re reading in all the registers, and starting a whole new screen... this is known as Rupture (also known as Horizontal Rupture)

Unfortunately it's not so easy, we're going to have to trick the CRTC, and do a lot of the work of laying out the screen for it... so that we get the screen we want!

Lets take a look!

Horizontal Rupture... the theory.
There's a few steps we need to do to get Rupture working!

1. Synchronize with the screen redraw... We need to be very precise in our timing and change the screen layout during the redraw procedure
2. Set the VSYNC position (R7) to 255... this is impossible but we need to stop VSYNC occurring while we're messing... we're going to fire the VSYNC manually later once we're done

3. Set the height  of the screen in VTOT (R6)
4. Set the Memory address of the screen (R12+R13)
5. Wait for the beam to get to the bottom of the screen
6. Repeat from stage 3 for the next screen

7. Once all the screens are done, Set the VSYNC position (R7) to 0...
this forces the VSYNC to occur.
8. Wait for the screen redraw to start again... repeat from step 1

We're going to have to repeat this procedure every single frame the CPC draws - this is why Rupture is such a pain!

The Rules!
1.The screen must be a total of 312 lines otherwise the screen won't synchronize

2.Our timing must be precise or we're going to make a mess!

3.A new screen cannot start before an old one ends.

4.Our screen height will be R6 * (R9+1).... R9 is the character height (usually 8)... we can change this, but setting it to 0 will cause problems on some screens - it's best left alone at this stage.
We can only set the base address of the screen (in R12,R13) to an address from &C000-&C7FF... the other areas will be used for following lines within a character block.

A 'Simple' Example!
We're going to create a simple example... it will split the screen into 5 subscreens! (one per interrupt)

Each screen can have base memory address... and a different screen mode!... though please note defining screen modes will slow down your game (just rem them out if you don't need them)

Because the interrupts are handling the stability of the Rupture effect, you won't need to worry about timing, just don't disable interrupts!

The example code will show some changing text, and also effect horizontal and vertical scrolling!

The Code
We're going to need to set things up for the Rupture,

We going to use self modifying code to change the interrupt handler every time an interrupt occurs...

We load a Jump, and the first interrupt address... Interrupt1

We now turn on interrupts, and start our code!
Interrupt 1...

At the start of each interrupt, we self-modify the jump, so the next interrupt will call Interrupt2 instead

We now have some code which will fake the top and bottom borders... as we are defining the screen ourselves, the borders only exists because of this code..

We now need to wait until the screen reaches the VSYNC state,  we do this by checking Bit 0 of port &F5xx.
We now wait a little while for the VSYNC area to finish, and the beam to start scanning the actual screen... The first part will be the Offscreen area

First we want to set the height of the 'screen' (The first rupture section), we use Reg 4 to do this... we're going to set this to 4... this results in a screen 5 character blocks tall.... giving a screen with a total of 5x8= 40 pixel tall screen

We're now going to set the High and Low Memory addresses using Reg 12 and Reg 13... the format is a little odd... effectively, we need to bit shift the top byte by 2 bits, so if we want our screen to start at &C000 we need to set Reg 12 to &30 (&C0 shifted right twice)...

Note that we're using the settings of R2_Addr here... we're always defining the settings for the NEXT strip during the CURRENT interrupt



Finally we're going to set the Vsync position in Reg 7 to 255... this effectively means VSYNC cannot occur, we'll re-enable it after our last rupture
Our newly defined screen will only start being drawn after the current one completes...

The Interrupt happens slightly before this, which is great for our splitscreen, but if we want to change Screenmode for the block, then it won't line up with the rupture...

Unfortunately the only way we can fix this is by making screen mode changes, then waiting in NOPs... this wastes our CPU power, but it DOES allow us to align the rupture and screen mode change.

Once again, if we're simulating screen borders, we now want to set a proper screen size using Reg 6

Now We've done the first interrupt, we've covered all the complex stuff... the other 5 interrupts are just using the same kind of commands to repeat the procedure for another 4/5 'screens'...
Having so many screens is probably excessive, you'd probably only want 2 in reality... one for your status, and one for the game - or one for player 1, and one for player 2!

Each interrupt sets the screen settings for the FOLLOWING interrupt block, so Interrupt 1 sets 2's mode and address... so if you want to reprogram things you'll need to bear this in mind...

Interrupt 2-5...
Interrupts 2 to 5 are all the same, and just a simpler version of what we just saw.

Like before, We switch in our new interrupt handler,

Next we define the new screen height with Reg 4

Now ew  set the new Screen memory address with Reg 12+13

Finally we wait, then change our screen mode if needed....


Note... there is NO messing with borders, or vsync position this time... those were just for Interrupt handler 1!
Interrupt 6...
Time for the last interrupt!... it's a little different.

We need to define the next interrupt handler, this time we're using Interrupt 1

If we want borders, then we want a border to appear , so we define it with Reg 6

As before we define the height of the block with Reg 4.... the Screen memory address with Reg 12+13... we've seen this in Interrupt 2-5

The next bit is different... Now we need that VSYNC to occur... we want to make the Vsync occur now, so we set Reg 7 to Zero...

The VSYNC will occur, then the settings we just set will be used for the top of the new screen

Different CRTC versions have different limitations, and some don't like things other's are OK with... This example should work ok on all of them...




Lesson P42 - Advanced CRTC Rupture 
We used Rupture last time to do splitscreen, but it's possible to use Rupture along with other registers to build a visible screen that is constructed from

Last time all our blocks were the typical 8 pixels tall, This time we're going to work with 4 pixel strips instead by altering Reg 9
 

CPC_CRTC_Rupture_Advanced.asm



Rupture of less than 8 pixels
Many parameters such as Screen address can only change every screen...but a screen can be any height... even one single character block, and as we can even change the height of character blocks we can have whatever we want,

In this example we'll use 4 character blocks just 1 pixel tall!

We'll use 3sets of 4 lines to build up the entire screen - a full overscan screen is being made from just a few hundred bytes of ram!
3 sets of 4 lines used to build screen
While we can set the screen base for the start of each screen, we can only set the base aligned to one of the original 8 pixel character blocks... this limits us to addresses like  &C000, &C800, &D000, &D800, &E000, &E800, &F000, &F800

Of course similar screen bases exist for ranges &0000-3FFF, &4000-7FFF and &8000-BFFF
Resulting screen
We can change the base by 1 character block (effectively a 2 byte horizontal scroll) but due to the limitations of R12 and R13, it's impossible to start a screen from one line down (for example &C050 cannot be the start of a screen)
CRTC Registers:

7 6 5 4 3 2 1 0
Reg 12 DispH - - P P S S O O
Reg 13 DispL O O O O O O O O
P=Page  S=Screen-size (16k/32k) O=Offset

Resulting Screen Address:

 F 
 E 
 D 
 C 
 B 
 A 
 9  
 8 
 
 7 
 6 
 5 
 4 
 3 
 2 
 1 
 0 
Screen Address P P L L L O O O
O O O O O O O -
P=Page L=Line in Charblock (Automatic - Cannot be set) O=Offset

Because we can only set the start of the screen to a limited number of memory locations, it can be difficult to get the screen to do exactly what we want with R12 and R13...
There is a solution... RVI (Rupture, Vertical - Invisible)... this creates a mini hidden screen at the start of a line a few pixels wide... the visible screen then starts on the next line - effectively manipluating the 3 bits that connot be set with R12+R13

Initialization
We're going to set the Hsync width as small as we can - this reduces the 'black areas' of the offscreen parts - and will help stop 'black areas' when we have an excessive offset

Normal Hwid
Reduced Hwid
We need to ensure interrupts are controlled, when an interrupt occurs we're just going to immediately return,

This program is very time sensitive, so our interrupt handler just returns as quickly as possible
We need to sync exactly with the screen, we're going to ensure we're consistently aligned to the interrupts by checking port &F5 and waiting for a new screen to start.

The Main Loop
When our loop starts, we need to wait again for the first Vsync of a new screen...

When the new screen starts, we set the Vsync point to 255 - this is impossible, effectively ensuring that a Vsync cannot occur during our drawing of the screen
We need to wait a little while for the end of the Vsync,
We will then start defining our physical screen
We are going to define some rather odd screens!

Each 'character block' is just one line tall (defined by setting Reg 09 to &00)...

Each 'Screen' is just 4 character blocks tall (defined by setting Reg 04 to &03)...

This has the effect of making each screen just 4 pixels tall, and allows us to change screen address every 4 lines!... because we disabled Vsync, we can start a new screen after each one of these tiny screens!
We're going to use a 'Command list' for the main loop,this list contains entries, one for every 4 pixel tall screen (so 280 lines in total)

The command list contains 4 bytes...

The first two are the address of the 4 pixel tall screen (loaded into Reg 12 and 13)... the next is the Hsync position (Reg 2) - effectively this moves the horizontal position of the 4 pixel strip onscreen

The final byte is a screen mode - we can change screen mode every 4 pixels if we wish!




During the main loop, we load each of the 3 screen parameters, and OUT them to the video registers...

If we 're changing the screen mode, we then Out this to the gate array...

Once this is done, we perform a delay until the next 4 pixel screen starts... we define a large number of NOP commands (Byte &00) with a DS command.

We repeat this whole procedure for every 4 pixel screen.
Once the screen has finished drawing, we need to reset the screen settings so we can cause a Vsync.

Once we've done this, a vsync will occur, and when it does we can start the procedure again!

This may seem pretty logical, but the trouble is the timing of the 4 pixel tall screen creation has to be EXACT, and it takes all the CPU time.

If we were making a game, we'd effectively have to fit the entire game-play code into the NOP sections
 

View Options
Default Dark
Simple (Hide this menu)
Print Mode (white background)

Top Menu
Youtube channel
ASM Programming Forums
GitHub
Dec/Bin/Hex/Oct/Ascii Table

Z80 Content
Learn Z80 Assembly
Hello World
Advanced Series
Multiplatform Series
Platform Specific Series
ChibiAkumas Series
Grime Z80
Z80 Downloads
Z80 Cheatsheet
Sources.7z
DevTools kit
Z80 Platforms
Amstrad CPC
Elan Enterprise
Gameboy & Gameboy Color
Master System & GameGear
MSX & MSX2
Sam Coupe
TI-83
ZX Spectrum
Camputers Lynx

6502 Content
Learn 6502 Assembly
Advanced Series
Platform Specific Series
Grime 6502
6502 Downloads
6502 Cheatsheet
Sources.7z
DevTools kit
6502 Platforms
Apple IIe
Atari 800 and 5200
Atari Lynx
BBC Micro
Commodore 64
Super Nintendo (SNES)
Nintendo NES / Famicom
PC Engine (Turbografx-16)
Vic 20

68000 Content
Learn 68000 Assembly
Platform Specific Series
Grime 68000
68000 Downloads
68000 Cheatsheet
Sources.7z
DevTools kit
68000 Platforms
Amiga 500
Atari ST
Neo Geo
Sega Genesis / Mega Drive
Sinclair QL (Quantum Leap)
X68000 (Sharp x68k)

My Game projects
Chibi Aliens
Chibi Akumas

Work in Progress
Learn 6809 Assembly
Learn 65816 Assembly
Learn 8086 Assembly (x86)
Wonderswan
MsDos
Learn ARM Assembly
Gameboy Advance
Risc Os

Misc bits
Ruby programming




Chibi Akumas V1.666 has taken over 350 hours of development, if you want to support my work, and learn all the secrets of the game's development, please back me on patreon!





Thanks to Homebrew Legends for help promoting my game!
Buy Chibi Akuma(s) from PolyPlay
Buy ChibiAkuma(s) games now!