| Posted By
BigFatRed on 2019-03-15 18:48:12
| C16 Hardware Scrolling
Hi,
Firstly - How do you sign up for this forum?
Secondly - I want to start TRYING to write 6502 code for the C16 and/or Plus/4. I used to have a C16 many, many moons ago and I used to know some of the programmers at Elite Systems. I tried getting in touch with one of them, Richard Ikin, but, apparently, he's got a lot of health problems so I don't want to bother him. I did find out he's re-writing Commando C16 though, which is interesting. Anyway, I want to learn how to do hardware scrolling but I'm having a lot of difficulty finding any information on how to do it. Would anyone be able to help me out and get started on this? I do know 6502 pretty well which will help.
Much appreciated if you could
|
|
Posted By
Luca on 2019-03-15 20:49:56
| Re: C16 Hardware Scrolling
Welcome BigFatRed.
You don't need to register in order to fully use this forum. Nonetheless, you can directly ask for a registration, in order to get access to a series of additional features: site's style customizing, log and forum avatars, database editing, on-site instant messages and so on. Nice, but unneded We only need to be contacted via email, me or Csabo, just to start with a valid email from you, some basic data about you and, if you want to, an 80x80px avatar for the forum and a 120x160px picture of you; afterward, you'll be able to edit your own data page.
Yes, we're following the project of Richard, we're aware of his personal situation and sometimes he pops out on Facebook writing sparse comments; he was trying to buy a Plus/4 just tonite There's a solid compendium about the memory map of C16 and Plus/4, and long descriptions pf the most important points, thanks to my mate SVS (thanks!), and you can download it from the Tools link above in the main menu, where you will find any other useful utility to help your coding desire: it's the Ultimate Map!
Regarding your specific objective: well, the usual h/v scrolling works exactly like on C64, so you can simply "translate" the most common C64 code slices to the right locations: bits 0/1/2 of $FF06 for the vertical one, and $FF07 of the horizontal one.
Good luck!
|
|
Posted By
Doug on 2019-03-16 09:14:15
| Re: C16 Hardware Scrolling
Hi BigFatRed,
Let me add my welcome too! Luca's comments are spot on with regard to scrolling, however - and its a big however - things are very different if you are trying to achieve a split screen! A whole screen scroll can be done just as on C64 (easier and more effective in fact because we have a faster CPU and the ability to double buffer colour RAM on TED), but a horizontal split is nasty to do. And very nasty to do seamlessly. I've recently fought and won this battle so rather than you going though the same voyage of discovery and pain that I did, I am happy to share code if you want it. Just let me know. I check the forum regularly, or sign up and instant message me.
Cheers, Doug
|
|
Posted By
BigFatRed on 2019-03-16 15:58:17
| Re: C16 Hardware Scrolling
Hi guys, Thanks for the replies I must try and get in touch with Rich, hopefully he'll remember me. Doug, many many thanks. I would appreciate any help available. Most kind of you to offer. Cheers
|
|
Posted By
Doug on 2019-03-16 18:14:25
| Re: C16 Hardware Scrolling
Hi BigFatRed,
Sorry for the long post!! Hope it helps....
Things you need to be aware of with split screen scrolling on TED:
TED hits you with 2 bad lines vs the VIC-II's one when it needs to fetch new char and colour data (usually every 8 scanlines). Bad line == CPU stalled for almost entire scan line. Be aware - you won't get much processing done on those lines!
Horizontal panning holds no surprises and is identical to VIC-II horizontal panning, using equivalent registers on TED to those found on VIC-II.
Vertical panning with a horizontal screen split is tricky, as you need to update both the vertical scroll register, and TED's vertical sub-address register in a way that produces the desired results (this register is not present on VIC-II, its a TED only register). You also need to be aware of the scroll position of the pane above the raster split vs the pane below it, because it affects whether your changes are going to hit you with bad lines or not - important to avoid garbage or jaggy raster splits.
TED supports raster interrupts just like VIC-II - set the desired line in a couple of registers, and if the interrupt is enabled you'll get an interrupt on that line. Usual caveats apply: the currently executing instruction must complete before the interrupt will be taken, so without special precautions you'll get the familar raster jaggies that you see on VIC-II. They can be overcome.
TED tells you the current raster line just like VIC-II (though the numbering is different just to confuse you). Line 0 is the top of the visible screen, not top of border).
TED also tells you the current horizonal raster position - yes it changes rapidly if you read it, going up in steps of 4 usually.
TED lets you write to the current vertical and horizontal raster registers - you can't do that on VIC-II. This seems odd, but it lets you create bad lines on demand (something the FLI guys love to do I think).
With no sprites to lay over raster interrupt screen splits, you need to be very trick get that slick jaggy free raster split between screen panes.
Just like VIC-II if you enable extended colour mode (ECM) and multicolour mode (MCM) simultaneously TED draws black pixels. This little known (?) trick is extremely useful when it comes to smoothing out raster jaggies.
Do not use VICE as an emulator for C16/Plus4. Use Yape or Plus4Emu: you absolutely need a reliable and accurate emulator for TED, which VICE is not. Or real hardware of course!
Finally, other people on here know a lot more about the internals of TED than me, especially those who write the crazy demos!
TED Register Nuts and Bolts
Key registers you'll be messing with in TED (you should read the excellent SVS Ultimate Map on this site for better details).
$ff06 - vertical scroll is in bits 0-2. Clear bit 3 to shrink screen to 24 lines. $ff07 - horizontal scroll is in bits 0-2. Clear bit 3 to shrink screen to 39 columns. $ff09 - IRQ status. Bit 1 is the raster IRQ. Write $2 to clear it. $ff0a - IRQ mask. Bit 1 is the raster IRQ mask, bit 0 is bit 8 of the raster match. (You'll probably not use it). $ff0b - Raster match bits 0-7: set this to the line you want to be interrupted on. $ff1c - Bit 0 is bit 8 of the current raster line $ff1d - Bits 0-7 of the current raster line $ff1e - Bits 2-8 of the current horizontal raster position
Techniques
This is just what I have found works - there are probably other ways of doing it.
Suppose we're going to setup a screen with a static top portion (status, score, etc), a split, and a lower portion that pans around (the game screen). We'll need a raster interrupt at the bottom of the screen to set up for it - set scroll registers etc. Its easier to use the bottom of the screen, since the top border has the raster MSB set, and it a faff to have to handle that. This bottom IRQ will set the horizontal and vertical scroll values to 7. I also need a 'buffer zone' of one character row between the upper and lower screen areas - this is where the split will occur. There is a raster interrupt at the top of this row. When its hit, we create a few black lines - all TED colours set to black, ECM+MCM enabled and the scroll values for the lower screen portion are set into TED (you can't see this, because TED is outputting black pixels).
Next we program the split screen interrupt seven lines down. We actually set the raster master two lines before this, to allow us to get sync'ed up and have a jaggy free split. This is done with the traditional 'dual interrupt' technique. Basically take a raster interrupt just before where you really want it, and when it fires setup the the real interrupt, and then execute a bunch of 2 cycle instructions until the interrupt fires. This ensures that the 'real' interrupt is dispatched in a timely manner, and avoids the random delays of upto 6 cycles that you get if it were executing ordinary code (the interrupt may fire during an instruction that takes that long to complete). So we have an inaccurate interrupt at line-2, this sets an interrupt for line-1 and executes nops until the line-1 interrupt fires.
When the 'real' interrupt fires, and then busy waits for line+1 before it updates the vertical scroll and subaddress registers. Remember the TED is still drawing black pixels, so none of this is visible. Next the code waits for either line+2 or line+3 (depends on the precise scroll position, this is due to bad lines) before it wastes precisely 38 cycles, to get to the very end of that line, and it disables ECM mode to start the TED drawing again. This results in a perfect split with no jaggies.
Its hard to explain, so here's the code snippets (in KickAss):
/* * TED register offsets and bits */ .namespace ted { .label timer1ReloadLo = $ff00 .label timer1ReloadHi = $ff01 .label timer2ReloadLo = $ff02 .label timer2ReloadHi = $ff03 .label timer3ReloadLo = $ff04 .label timer3ReloadHi = $ff05 .label vScroll = $ff06 .label vScrollMask = $07 .label rowSelect = $08 .label screenOn = $10 .label hiResMode = $20 .label extColourMode = $40 .label testMode = $80 .label hScroll = $ff07 .label hScrollMask = $07 .label columnSelect = $08 .label multiColourMode= $10 .label haltBit = $20 .label ntscMode = $40 .label normalMode = $80 .label keyboardLatch = $ff08 .label readKB = $ff .label readJ1 = $fb .label readJ2 = $fd .label irqStatus = $ff09 .label rasterIRQ = $02 .label timer1IRQ = $08 .label timer2IRQ = $10 .label timer3IRQ = $40 .label summaryIRQ = $80 .label irqMask = $ff0a .label rasterMatchMSB = $01 .label rasterMatch = $ff0b .label cursorPosHi = $ff0c .label cursorPosLo = $ff0d .label voice1FrqLo = $ff0e .label voice2FrqLo = $ff0f .label voice2FrqHi = $ff10 .label audioControl = $ff11 .label volMax = $08 .label voice1On = $10 .label voice2On = $20 .label voice2Noise = $40 .label digiMode = $80 .label videoControl = $ff12 .label voice1FrqHi = $ff12 .label charMapBase = $ff13 .label charMask = $f800 .label screenBase = $ff14 .label bgColour = $ff15 .label screenColour1 = $ff16 .label screenColour2 = $ff17 .label screenColour3 = $ff18 .label borderColour = $ff19 .label textPtrHi = $ff1a .label textPtrLo = $ff1b .label curRasterHi = $ff1c .label curRasterLo = $ff1d .label horizPosition = $ff1e .label verticalSub = $ff1f .label romSelect = $ff3e .label ramSelect = $ff3f
.label black = 0 .label white = 1 .label red = 2 .label cyan = 3 .label purple = 4 .label green = 5 .label blue = 6 .label yellow = 7 .label orange = 8 .label brown = 9 .label yellowGreen = 10 .label pink = 11 .label blueGreen = 12 .label lightBlue = 13 .label darkBlue = 14 .label lightGreen = 15
.label intensity0 = $00 .label intensity1 = $10 .label intensity2 = $20 .label intensity3 = $30 .label intensity4 = $40 .label intensity5 = $50 .label intensity6 = $60 .label intensity7 = $70
.label multiColourAttr = 8 }
.namespace cpu { .label irqVectorLo = $fffe .label irqVectorHi = $ffff }
.const STATUS_LINES = 7 .const RASTER0 = (STATUS_LINES-1)*8 .const RASTER1 = STATUS_LINES*8 .const RASTER2 = (7+(STATUS_LINES*8))
/* *************************************** * Raster interrupt just above split * In the middle of the 'safe' black line * above the real split. Bang out the fine * x position for the play area of the * screen and set the screen colours *************************************** */ raster1IRQ: pha
lda finex: #0 /* And fine X position */ sta ted.hScroll
lda #7 | ted.extColourMode /* Enable ecm -> black pixels */ sta ted.vScroll /* mcm is already enabled */
lda #ted.black sta ted.screenColour1 sta ted.screenColour2
lda #>(gameChars & ted.charMask) /* Set char base */ sta ted.charMapBase
lda #ted.rasterIRQ sta ted.irqStatus /* Clear interrupt latch */
lda #RASTER2-2 /* Setup for next raster */ sta ted.rasterMatch
set16Imm(cpu.irqVectorLo, raster2PreIRQ)
pla rti /* *************************************** * Raster interrupt pre-split screen * Allows us to enter the real split with * cycle accuracy (+/- 1 cycle) *************************************** */ raster2PreIRQ: pushAXY() tsx /* Remember stack ptr */ lda #RASTER2-1 /* Setup for real line */ sta ted.rasterMatch set16Imm(cpu.irqVectorLo, raster2IRQ) lda #ted.rasterIRQ sta ted.irqStatus /* Clear interrupt latch */ cli /* Ints back on */ ldy #10 /* Initial cycle waste */ !loop: dey bne !loop- .for (var i=0; i<10; i++) /* Bunch of 2 cycle nops */ { /* that'll get hit with the interrupt */ nop }
/* *************************************** * Raster interrupt at split screen *************************************** */ raster2IRQ: txs /* Get back stack */
ldx liveScreen /* Set TED colours */ lda screenColour1,x sta ted.screenColour1 lda screenColour2,x sta ted.screenColour2
lda finey: #0 /* Set regs for smooth scroll */ ldx vSubVal: #0 ldy #RASTER2+1
!wait: cpy ted.curRasterLo bne !wait-
sta ted.vScroll /* Note setting registers close together */ stx ted.verticalSub
ldy targetY: #0 /* Wait for designated line */ !wait: cpy ted.curRasterLo bne !wait- inc ted.screenColour3 /* Waste 38 cycles */ dec ted.screenColour3 inc ted.screenColour3 dec ted.screenColour3 inc ted.screenColour3 dec ted.screenColour3 nop
lda finey2: #0 /* Disable ECM - TED draws */ sta ted.vScroll
...
popAXY() rti
The code that sets the embedded immediate values in the ISRs looks like this:
lda screenx,x /* Screen X fine position */ and #7 /* Isolate scroll portion */ tay lda scrollTab,y ora #(ted.multiColourMode | ted.normalMode) sta finex /* Embedded immediate in code above */
lda screeny,x /* Screen Y fine position */ and #7 /* Isolate scroll portion */ tay lda scrollTabY,y /* Required scroll position */ ora screenOn /* TED screen on bit or 0 for blank */ sta finey2 /* Final Y scroll immediate above */ ora #ted.extColourMode /* Enable ecm + mcm -> black pixels */ sta finey /* Set at start of split above */ and #7 /* Work out accompanying vertical sub value */ tax lda scrollTab,x sta vSubVal lda targetYVals,x /* Work out final line to re-enable TED */ sta targetY
...
scrollTab: .byte 7,6,5,4,3,2,1,0 scrollTabY: .byte 1,0,7,6,5,4,3,2 targetYVals: .byte RASTER2+3 .byte RASTER2+2 .byte RASTER2+3 .byte RASTER2+3 .byte RASTER2+3 .byte RASTER2+3 .byte RASTER2+3 .byte RASTER2+3
|
|
Posted By
Mad on 2019-03-16 21:43:21
| Re: C16 Hardware Scrolling
Doug:
Just like VIC-II if you enable extended colour mode (ECM) and multicolour mode (MCM) simultaneously TED draws black pixels. This little known (?) trick is extremely useful when it comes to smoothing out raster jaggies.
that info by you can be extremely useful here.. I remember asking people about a possibility to do this and just got "use a blank bitmap" and similar as an answer.. Great one.. Thanx! Makes the TED even more adorable!!
|
|
Posted By
gerliczer on 2019-03-17 06:24:59
| Re: C16 Hardware Scrolling
You sure about that, Mad? Back in 2009 or 2010, when KiCHY was working on Adventures In Time, this exact trick was suggested him, IIRC. I did a moderately quick search and found the topic IRQ and FF06 where the guys were discussing this.
|
|
Posted By
Mad on 2019-03-17 10:57:40
| Re: C16 Hardware Scrolling
gerliczer I just asked personally some people I did know many years ago.. I think I needed that for a fadeout of pictures and I didn't ask that here in the forum. + it was many years ago, too.. Yesterday I found this also mentioned in the ultimate map.. Sadly in a almost non readable color.. Thanx for that hint! I think this "trick" is really useful for me in later projects..
|
|
Posted By
BigFatRed on 2019-03-22 18:28:03
| Re: C16 Hardware Scrolling
Doug, In your code you shared, what does the line LDA FINEX: #0 do?
|
|
Posted By
Doug on 2019-03-23 03:37:28
| Re: C16 Hardware Scrolling
BigFatRed its setting the horizontal scroll register. My game does a full panning window in 2 dimensions, so its needed. If you were doing a straight up down scroller it wouldn't be needed. Elsewhere in the code I'm calculating what the value at finex should be, and inserting it into that instruction.
If its the syntax you're not familar with, its one of KickAss's great features. We're all used to self modifying code, and in less awesome assemblers you'd have to concoct something like this:
foo: lda #0 bar: sta $1234
Then elsewhere in the code:
lda someVar sta foo+1 lda someOtherVar sta bar+1 lda someOtherVar+1 sta bar+2
Yuck. No need to in KickAss, because it lets you put a label on either an instruction, or associated operand, whether it an immediate value (which is the case you highlighted), or an address. It makes life so much easier and less error prone.
|
|
Posted By
BigFatRed on 2019-03-24 16:57:40
| Re: C16 Hardware Scrolling
Thanks Doug, that's awesome! I like that feature of KickAss, seems like that will be dead useful!
|
|
| |
Copyright © Plus/4 World Team, 2001-2024. Support Plus/4 World on Patreon |