Login
Back to forumReply to this topicGo to last reply

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 happy

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 happy 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 happy
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! wink

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 happy
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!! happy

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.. grin 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!



Back to topReply to this topic


Copyright © Plus/4 World Team, 2001-2024