稳定的C64屏幕光栅

13

我正在使用Commodore 64上的6510汇编语言,尝试创建一个稳定的光栅效果。我使用双重IRQ原理在屏幕上绘制了一些光栅线。我添加NOP以匹配每个正常扫描线的63个周期,并匹配每个坏行的23个周期。我意识到我需要设置特定的起始行,以便将我的第8次迭代与一个坏行匹配,但无论我将第一行放在哪条线上或使用什么组合的NOP,我都不能正确地控制时序。我想要完整的没有"断裂"的行。有人能看出我做错了什么吗?代码是Kick Assembler格式。这是一个截图:

截图

.pc = $0801 "Basic upstart"
:BasicUpstart($8000)

.pc = $8000 "Program"

  jsr $ff81

  sei
  lda #$35
  sta $01

  jsr setupInterrupts
  cli

  jmp *

setupInterrupts:
  lda #<int1
  ldy #>int1
  sta $fffe
  sty $ffff

  lda #$01
  sta $d01a
  lda #$7f
  sta $dc0d
  sta $dd0d
  lda $dc0d  
  lda $dd0d
  lda #$1b
  sta $d011
  lda #$01
  sta $d019

  lda start
  sta $d012

  rts

start:
  .byte 56

int1:
  pha txa pha tya pha

  :STABILIZE()

.for (var i=0; i<7; i++) {
  inc $d020   // 6 cycles
  inc $d021   // 6 cycles
  nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop // 24*2=48 cycles
  bit $ea     // 3 cycles
              // = 63 cycles
}
  inc $d020   // 6 cycles
  inc $d021   // 6 cycles
  nop nop nop nop // 4*2=8 cycles
  bit $ea     // 3 cycles
              // = 23 cycles (badline)

  lda #$00
  sta $d020
  sta $d021

  lda start
  sta $d012

  lda #<int1 
  ldy #>int1 
  sta $fffe
  sty $ffff

  lda #$01
  sta $d019

  pla tay pla tax pla

  rti


.macro STABILIZE() {

  lda #<nextRasterLineIRQ
  sta $fffe
  lda #>nextRasterLineIRQ
  sta $ffff   

  inc $d012

  lda #$01
  sta $d019

  tsx

  cli

  nop nop nop nop nop nop nop nop

nextRasterLineIRQ:
  txs

  ldx #$08
  dex
  bne *-1
  bit $00

  lda $d012
  cmp $d012

  beq *+2      
}
4个回答

14
据我理解,您的问题不是您的光栅条在闪烁(即您的光栅中断稳定),而是您正在屏幕上绘制的光栅条的第二行未完全变成红色。
您的问题是坏行。(参见[1])
当您使用您发布的代码稳定了您的光栅中断后,“实际代码”将从光栅线$3A的第4个周期开始运行。
您要使背景颜色和边框颜色都变成红色的光栅条的第二行是一个坏行。(它是光栅线$3B。由于$D011 = $1B,所以这是一个坏行,因为$D011和$D012的低3位相同)
在这个坏行上,第一个INC(INC $D020)成功运行,因此边框颜色变成了红色。然后第二个INC(INC $D021)开始运行,但VIC在完成之前接管了控制权,因此您的INC $D021直到VIC归还总线后才完成。(这是43个周期之后 - 即将背景颜色设置为红色延迟了43个周期)。
您差点就做到了,但是错误行与您的代码预期不同,您需要“推几个周期”,以便在VIC中断之前执行两个INC指令。如果您想要这两个指令在VIC接管之前都被执行,那么在错误行的第4个周期开始执行它们已经为时过晚了。请注意保留HTML标签。 更新的示例: 尝试替换您代码中的以下部分:
.for (var i=0; i<7; i++) {
  inc $d020   // 6 cycles
  inc $d021   // 6 cycles
  nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop // 24*2=48 cycles
  bit $ea     // 3 cycles
              // = 63 cycles
}

  inc $d020   // 6 cycles
  inc $d021   // 6 cycles
  nop nop nop nop // 4*2=8 cycles
  bit $ea     // 3 cycles
              // = 23 cycles (badline)

用这个:
// a delay to get to some cycle at the end of the raster-line, so we have time to execute both inc's on 
// each successive raster-line - in particular on the badlines before the VIC takes over the bus.
.for (var i=0; i<28; i++) nop

// just for illustrative purposes - not cool code :)
.for (var i=0; i<8*6; i++) {
  inc $d020   // 6 cycles
  inc $d021   // 6 cycles
  .if ([i & %111] == 0) {
      // badline
      nop nop nop nop // 4*2=8 cycles
  } else {
      // non-badline
      nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop // 24*2=48 cycles
      bit $ea     // 3 cycles
                  // = 63 cycles
  }
}

(警告:这段代码在内存方面相当浪费——同样的效果可以很容易地使用普通循环实现)(如果您不打算显示字符图形,您可以通过修改$D011将坏行推开,而不是改变延迟时间)
尝试在HOXS64模拟器中查看机器码监视器。它非常适合调试与时间相关的问题。它会向您显示您在任何给定时间所处的哪个光栅线的哪个周期(+它可以在中断发生时中断)。
希望这有所帮助 :)
注意,我还没有彻底检查你的稳定光栅例程是否存在问题,但看起来没问题 - 方法正确,没有闪烁。如果出现闪烁的光栅条,你知道该如何修复。;)

如果有人不知道什么是"badlines":

参考资料:

[1]: 在vic-article(或者称为"vic-bible")中了解更多关于“bad lines”的信息:http://csdb.dk/release/?id=44685 (PDF) 或者 http://vice-emu.sourceforge.net/plain/VIC-Article.txt (TXT)。另外还可以看看补充说明:http://vice-emu.sourceforge.net/plain/VIC-Addendum.txt

基本上,当VIC芯片开始绘制文本行的第一个光栅线时,它会从CPU中窃取40-43个时钟周期(下面会解释为什么不总是43个)。这些光栅线被称为“bad lines”。 因此,在"bad line"上只有20-23个时钟周期可用,而不是63个。

更准确地说,当$D011的最低3位等于$D012的最低3位时(且我们不在边框内且屏幕没有被$D011的第4位“关闭”),就会出现坏行。

VIC芯片使用这43个周期中的最后40个周期来读取文本行上要显示的40个字符。在这40个周期期间,CPU不能执行任何指令。

然而,在这43个周期的前3个周期中,CPU实际上可以执行其指令的“写入周期” - 但只有写入周期,没有读取周期。(见[2])因此,如果您正确计时您的操作码,您可以在这3个周期中执行一些指令周期。 (注意:唯一具有3个写入周期的指令是“brk”,通常是无用的,因此在实践中,您最多只能使用其中的2个周期进行某些有用的操作)。

请注意,除了在坏行上窃取周期之外,VIC还会从具有精灵的光栅线上窃取CPU周期。

[2]:请参考“64doc”了解C64的不同指令周期中哪些是写周期:http://vice-emu.sourceforge.net/plain/64doc.txt
(在表格中,写周期标记为“W”,读周期标记为“R”)

[X]:...还有很多好的文章可以在http://codebase64.org找到。


1
为了使光栅线无闪烁,需要一些额外的技巧来稳定时间。原因是,您永远无法确定光栅例程是否在线的开头执行,但是取决于CPU“离开”主程序代码的位置,未知数量的周期将被浪费以执行最后一个操作。有不同的方法来达到这个目标,您应该查看Codebase64上的this页面,了解更多关于此主题的信息并获取一些示例代码。然而,一旦您建立了稳定的光栅计时,我认为您的方法很好。

0
你可能想要检查一下这个: https://github.com/Zibri/C64_Stable_Raster HSYNC例程的核心是这样的:
DEC $DC03         ; trigger the light pen
INC $DC03         ; restore port B to input
LDA $D013         ; read the raster X position
STA $2
LSR A
LDA $2
ADC #$00          ; if carry is set this is a 8565 i f it's clear it's a 6569
CMP #$0B          ; this is just sheer magic :D
ADC #$11          ; this is just sheer magic :D
LSR A
LSR A
STA SS+1          ; A will be: 0-6
SS:
BVC *
; the following 1 cycle clock slide does not affect any registers nor the cpu status.

.BYTE $80
.BYTE $80, $80
.BYTE $80, $80
.BYTE $44,$5A
RTS  ; Here we we always be at cycle 52 of a scanline.

-3

如果我没记错的话(20多年前的事了)...

由于微小的中断抖动,很难获得完全稳定的光栅效果定时;因此,无论你在软件中做什么,扫描线的起始处都会有一个“闪烁”的点。

为了解决这个问题,可以使用精灵来隐藏闪烁的点。


5
不,完全稳定的时间是可能实现的。这就是你使用“双重IRQ原则”的原因(http://codebase64.org/doku.php?id=base:stable_raster_routine)。 - Kristoffer Jälén
1
稳定的光栅是打开C64侧边框的唯一方法。 - BerggreenDK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接