修改后的6502中断返回

3

我想在中断返回时切换正常程序流:

START
    SEI
    LDX #<IRQ
    LDY #>IRQ
    STX $FFFE
    STY $FFFF
    CLI

LOOP1
    INC $D020
    JMP LOOP1

LOOP2
    INC $D021
    JMP LOOP2

IRQ
    STA SAVEA+1
    STX SAVEX+1
    STY SAVEY+1

    // Some Routines

    LDA #$00
    PHA
    LDA #<LOOP2
    PHA
    LDA #>LOOP2
    PHA

SAVEA   
    LDA #$00
SAVEX   
    LDX #$00
SAVEY   
    LDY #$00
    RTI

我根据这个来源编写了以下代码: http://6502.org/tutorials/interrupts.html#1.3

enter image description here

但是PHA会导致崩溃,如何在中断中将正常流程从LOOP1切换到LOOP2?


2
@DavidHoelzer,您误解了代码,那部分只是设置中断,而这两个循环是他想要调度的两个进程。话虽如此,在处理程序中禁用更多中断听起来像个好主意。另外,已经保存在堆栈上的内容应该被删除,否则如果您继续push东西,就会导致堆栈溢出。顺序似乎也错了,您需要使用与图表相同的顺序。 - Jester
哦,是的,你说得对。我忽略了IRQ代码中的<和>符号。这已经有一段时间了。 :) - David Hoelzer
3
不清楚你是想操纵栈内容还是写自修改代码。你已经压入了一个新的返回地址,但之前的地址仍然存在。你很快就会用完栈空间。我建议使用PHA; TXA; PHA; TSX指令,并通过索引X修改储存在栈中的返回地址,在恢复寄存器并执行RTI指令前完成修改。 - Weather Vane
你应该意识到,这样做很可能会导致除了最简单的例程(如循环)之外的非预期结果,因为它没有使用寄存器、堆栈或状态标志,而只能被其他程序在任何时刻覆盖。你最好在中断处理程序内设置一些内存标志,并在循环内执行受控检查此值的操作,然后相应地进行分支。 - Lars Haugseth
3个回答

4

最简单的方法可能是为每个任务都有两个堆栈区域。例如,$100-$17f和$180-$1ff。 然后,您可以像这样编写中断任务切换代码:

  pha
  txa
  pha
  tya
  pha ;saving task's registers on its stack,
      ;where flags and PC are already saved
      ;by entering the interrupt

  tsx
  stx ... ;save task's stack position

  ... ;select new task to run/etc.

  ldx ...
  txs ;load other task's stack position

  pla
  tay
  pla
  tax
  pla ;restore other task's registers

  rti ;and finally continue other task 

1
这是一个不错的方法。也许值得指出的是硬件并未强制执行任何堆栈边界。因此,如果B最终在A的堆栈空间中,A可能会开始做一些非常奇怪的事情。任务切换器可以进行一些范围完整性检查。但有些环绕错误仍然无法检测到。 - Core
1
@lvd,TSX是我下面写的解决方案的关键,我测试过了,它可以工作!谢谢! - Digerkam
1
硬件通常不会强制执行堆栈边界;唯一的区别是限制为128字节而不是256字节,如果你悄悄地越界,你将踩到别人的堆栈顶部而不是你自己的。 - Tommy

2

简单的方法是:

TSX
LDA #$00
STA $0101,X   // Processor Status
LDA #<LOOP2
STA $0102,X   // Task Low Address
LDA #>LOOP2
STA $0103,X   // Task High Address

但是对于更复杂的任务管理,我们需要为每个任务保存A、X、Y寄存器:

START
    SEI
    LDX #<IRQ
    LDY #>IRQ
    STX $FFFE
    STY $FFFF
    CLI

LOOP1
    INC $D020
    JMP LOOP1

LOOP2
    INC $D021
    JMP LOOP2

IRQ
    STA $FF
    STX $FE
    STY $FD
    LDX TASK+1
    CPX TASK
    BEQ CONT
    LDY TASKI,X
    TSX
    LDA $0101,X
    STA TASKS+0,Y
    LDA $0102,X
    STA TASKS+1,Y
    LDA $0103,X
    STA TASKS+2,Y
    LDA $FF
    STA TASKS+3,Y
    LDA $FE
    STA TASKS+4,Y
    LDA $FD
    STA TASKS+5,Y
    LDA TASK
    STA TASK+1
CONT

    // Change Task
    LDA TASK
    CLC
    ADC #$01
    AND #$01
    STA TASK


    LDX TASK
    CPX TASK+1
    BEQ CONT2
    STX TASK+1
    LDY TASKI,X
    TSX
    LDA TASKS+0,Y
    STA $0101,X
    LDA TASKS+1,Y
    STA $0102,X
    LDA TASKS+2,Y
    STA $0103,X
    LDA TASKS+3,Y
    STA $FF
    LDA TASKS+4,Y
    STA $FE
    LDA TASKS+5,Y
    STA $FD
CONT2
    LDA $FF
    LDX $FE
    LDY $FD
    RTI

TASK
    .BYTE 0,0
TASKI
    .BYTE 0,6,12,18,24,30,36
TASKS
    .BYTE 0,<LOOP1,>LOOP1,0,0,0
    .BYTE 0,<LOOP2,>LOOP2,0,0,0

1
当堆栈从$100到$1ff刚好发生环绕时,tsx:lda $101,x:etc.代码会有问题吗?对于您的情况可能不会有问题,因为您已经周到地划分了堆栈空间。但是一般情况下,如果堆栈仅用于中断堆叠、过程调用以及最终的PHA/PLA操作,并且没有明显需要担心确切的堆栈位置,那么可能不会出现问题。 - lvd
@lvd 是的,如果您在堆栈下溢,则会出现问题。但是6502上的256字节堆栈空间已经足够了。我很少见到堆栈指针低于$C0 - puppydrum64

1

我不确定你想要做什么,但是我认为你想要快速更改Commodore 64的背景颜色。如果你想改变你的做法,实际上有一个更简单的方法:

START
    SEI
    LDX #<IRQ
    LDY #>IRQ
    STX $FFFE
    STY $FFFF
    CLI

LOOP1
    INC $D020   ;the interrupt will switch this to $D021 and back every time it happens.
    JMP LOOP1

IRQ
    PHA
       LDA #$01
       EOR LOOP1+1  ;the value at this address is the "20" in "INC $D020"
       STA LOOP1+1  ;toggle between "INC $D020" and "INC $D021" each IRQ
    PLA
    RTI

这种方法比设置标志并基于该标志进行分支的开销要小得多。假设您的目标是尽快更新边框颜色/背景颜色,这将大大减少检查条件所需的时间。

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