Keyboard
-
- Posts: 913
- Joined: Tue Apr 28, 2020 2:45 am
Keyboard
No idea without seeing the difference in assembly output. It seems like cc65 does some weird things while it tries to work around the limitations of the 6502. Again, it's why I would not use C for low-level stuff like interrupt handling. Keep it confined to algorithms that can be called from assembly when the state is appropriate. If you have to twist yourself up in knots to make C generate the assembly you're looking for, then you should just write the assembly and be done with it.
-
- Posts: 82
- Joined: Tue Jan 12, 2021 9:52 pm
Keyboard
I hear your argument for 100% assembly ? and maybe I will eventually be convinced.
But for me now it is either "use c and make cool stuff on the x16", or "do assembly and need plenty of time, and end up not coding for the x16"
Anyway, we'll see where it ends... To start out it is KickC. I did not really get a liking for cc65 too much. Maybe a question of taste, but if a simple thing like "Don't start the program in lowercase", had to be dealt with as a workaround, I felt that was an bad indicator for me.
For the complete assembly code, I'll be thank full to know what you think, if you want to comment, so I publish it below. Maybe I learn something ?
Anyway, I added many comments, but most code is generated by kickc. My after kickc did it's "thing" comments start with "/////".
Quote
////////////// AUTO CODE
// Example program for the Comander X16
// Displays raster bars in the border
.cpu _65c02
// Commodore 64 PRG executable file
.file [name="vlinterupt.prg", type="prg", segments="Program"]
.segmentdef Program [segments="Basic, Code, Data"]
.segmentdef Basic [start=$0801]
.segmentdef Code [start=$80d]
.segmentdef Data [startAfter="Code"]
.segment Basic
:BasicUpstart(__start)
.const VERA_ADDRSEL = 1
.const VERA_LINE = 2
.const VERA_VSYNC = 1
// $9F20 VRAM Address (7:0)
.label VERA_ADDRX_L = $9f20
// $9F21 VRAM Address (15:8)
.label VERA_ADDRX_M = $9f21
// $9F22 VRAM Address (7:0)
// Bit 4-7: Address Increment The following is the amount incremented per value value:increment
// 0:0, 1:1, 2:2, 3:4, 4:8, 5:16, 6:32, 7:64, 8:128, 9:256, 10:512, 11:40, 12:80, 13:160, 14:320, 15:640
// Bit 3: DECR Setting the DECR bit, will decrement instead of increment by the value set by the 'Address Increment' field.
// Bit 0: VRAM Address (16)
.label VERA_ADDRX_H = $9f22
// $9F23 DATA0 VRAM Data port 0
.label VERA_DATA0 = $9f23
// $9F25 CTRL Control
// Bit 7: Reset
// Bit 1: DCSEL
// Bit 2: ADDRSEL
.label VERA_CTRL = $9f25
// $9F26 IEN Interrupt Enable
// Bit 7: IRQ line (8)
// Bit 3: AFLOW
// Bit 2: SPRCOL
// Bit 1: LINE
// Bit 0: VSYNC
.label VERA_IEN = $9f26
// $9F27 ISR Interrupt Status
// Interrupts will be generated for the interrupt sources set in the lower 4 bits of IEN. ISR will indicate the interrupts that have occurred.
// Writing a 1 to one of the lower 3 bits in ISR will clear that interrupt status. AFLOW can only be cleared by filling the audio FIFO for at least 1/4.
// Bit 4-7: Sprite Collisions. This field indicates which groups of sprites have collided.
// Bit 3: AFLOW
// Bit 2: SPRCOL
// Bit 1: LINE
// Bit 0: VSYNC
.label VERA_ISR = $9f27
// $9F28 IRQLINE_L IRQ line (7:0)
// IRQ_LINE specifies at which line the LINE interrupt will be generated.
// Note that bit 8 of this value is present in the IEN register.
// For interlaced modes the interrupt will be generated each field and the bit 0 of IRQ_LINE is ignored.
.label VERA_IRQLINE_L = $9f28
// $0314 (RAM) IRQ vector - The vector used when the KERNAL serves IRQ interrupts
.label KERNEL_IRQ = $314
.label vbcounter = 5
.label default_irq_vector = 6
.segment Code
__start: {
lda #0
sta.z vbcounter
sta.z default_irq_vector
sta.z default_irq_vector+1
jsr main
rts
}
////////////// MY INTERRUPT
myInterupt: {
.label intbits = 8
lda VERA_ISR
sta.z intbits
lda #VERA_LINE
and.z intbits
cmp #0
beq __b1
inc.z vbcounter
lda #VERA_LINE|VERA_VSYNC
sta VERA_ISR
sta VERA_IEN
lda #$64
sta VERA_IRQLINE_L
ply
plx
pla
rti
__b1:
lda #VERA_VSYNC
and.z intbits
cmp #0
beq __b2
lda #VERA_LINE|VERA_VSYNC
sta VERA_ISR
sta VERA_IEN
jmp (default_irq_vector)
__b2:
lda #VERA_LINE|VERA_VSYNC
sta VERA_ISR
sta VERA_IEN
lda #$64
sta VERA_IRQLINE_L
jmp (default_irq_vector)
}
////////////// MAIN
main: {
jsr initMain
rts
}
////////////// REAL MAIN
initMain: {
////////////// MAIN INIT
///// MAIN LOOP COUNTER
.label ll = 2
///// NOTES: "ll" is in memory, keyboard counter seem to be optimized and not stored in memory (we have ll = mainloop, keycounter, and vbcounter)
///// forcing keycounter in memory solves it, but make me unsure of what other issues would arise, for rest a pretty big c program
///// which I am using as my base program, so forcing things to memory is not wanted, and also it kills optimisation.
///// SET INTERRUPTS
sei
lda KERNEL_IRQ
sta.z default_irq_vector
lda KERNEL_IRQ+1
sta.z default_irq_vector+1
lda #<myInterupt
sta KERNEL_IRQ
lda #>myInterupt
sta KERNEL_IRQ+1
lda #VERA_LINE|VERA_VSYNC
sta VERA_IEN
lda #4
sta VERA_IRQLINE_L
cli
///// POKE INITIAL VALUES ON SCREEN
ldx #0
txa
sta.z vpoke.vaddr
sta.z vpoke.vaddr+1
jsr vpoke
//set an "@" left top screen
ldx #$12
lda #<1
sta.z vpoke.vaddr
lda #>1
sta.z vpoke.vaddr+1
jsr vpoke
lda #0
sta.z ll
tay
////////////// MAIN LOOP
__b2:
///// CHECK KB
jsr kbhit
///// IS SPACEBAR??
cmp #$20
bne __b3
iny
tya
tax
lda #<0
sta.z vpoke.vaddr
sta.z vpoke.vaddr+1
jsr vpoke
ldx #$12
lda #<1
sta.z vpoke.vaddr
lda #>1
sta.z vpoke.vaddr+1
jsr vpoke
/////NO SPACE KEY PRESSED
__b3:
ldx.z vbcounter
lda #<2
sta.z vpoke.vaddr
lda #>2
sta.z vpoke.vaddr+1
jsr vpoke
//set an "@" left top screen
ldx #$12
lda #<3
sta.z vpoke.vaddr
lda #>3
sta.z vpoke.vaddr+1
jsr vpoke
ldx.z ll
//set an "@" left top screen
lda #<4
sta.z vpoke.vaddr
lda #>4
sta.z vpoke.vaddr+1
jsr vpoke
inc.z ll
//set an "@" left top screen
ldx #$12
lda #<5
sta.z vpoke.vaddr
lda #>5
sta.z vpoke.vaddr+1
jsr vpoke
jmp __b2
}
////////////// FROM KICK C LIBRARY FOR VERA
// Put a single byte into VRAM.
// Uses VERA DATA0
// - bank: Which 64K VRAM bank to put data into (0/1)
// - addr: The address in VRAM
// - data: The data to put into VRAM
// vpoke(byte* zp(3) vaddr, byte register(X) data)
vpoke: {
.label vaddr = 3
// Select DATA0
lda #VERA_ADDRSEL^$ff
and VERA_CTRL
sta VERA_CTRL
lda.z vaddr
// Set address
sta VERA_ADDRX_L
lda.z vaddr+1
sta VERA_ADDRX_M
lda #0
sta VERA_ADDRX_H
// Set data
stx VERA_DATA0
rts
}
////////////// FROM KICK C LIBRARY FOR CONSOLE
// Return true if there's a key waiting, return false if not
kbhit: {
.label chptr = ch
.label IN_DEV = $28a
// Current input device number
.label GETIN = $ffe4
.label ch = 9
lda #0
sta.z ch
// CBM GETIN API
jsr _kbhit
bne L3
jmp continue1
.var via1 = $9f60 //VIA#1
.var d1pra = via1+1
_kbhit:
ldy d1pra // The count of keys pressed is stored in RAM bank 0.
stz d1pra // Set d1pra to zero to access RAM bank 0.
lda $A00A // Get number of characters from this address in the ROM of the CX16 (ROM 38).
sty d1pra // Set d1pra to previous value.
rts
L3:
ldy IN_DEV // Save current input device
stz IN_DEV // Keyboard
phy
jsr GETIN // Read char, and return in .A
ply
sta chptr // Store the character read in ch
sty IN_DEV // Restore input device
ldx #>$0000
rts
continue1:
nop
rts
}
-
- Posts: 137
- Joined: Tue Jun 30, 2020 3:47 pm
Keyboard
21 minutes ago, CursorKeys said:
I hear your argument for 100% assembly ? and maybe I will eventually be convinced.
I believe SlithyMatt is suggesting that you write the interrupt handler in assembly, and the rest of the program in C. On a system like the X16, a language like C should be used when it makes the code less complicated; if C begins to make the code more complicated, use assembly for that part of the program.
-
- Posts: 82
- Joined: Tue Jan 12, 2021 9:52 pm
Keyboard
4 minutes ago, Elektron72 said:
I believe SlithyMatt is suggesting that you write the interrupt handler in assembly, and the rest of the program in C. On a system like the X16, a language like C should be used when it makes the code less complicated; if C begins to make the code more complicated, use assembly for that part of the program.
Ok, that is cool, that was my aim from the start.
The weird thing here is that, the is change in the non-interupt part. The main code behaviour seems modified by the interrupt. Or the change is the main code breaks the interrupt. So it's the combination of the two that does not go too well. Anyways..... more investigations seem to be needed... kickc seems to have build in debugger, that can talk to the emulator, or such I understood. Maybe I give that a spin, but I'm not sure how that would look, with interupts twirling about.
-
- Posts: 913
- Joined: Tue Apr 28, 2020 2:45 am
Keyboard
Thanks, @Elektron72, that was my point. 100% assembly is not for everybody, but until cc65 for the X16 is more mature, you need to be careful what you use C for. You can still make a program that's mostly in C, but still with some assembly to hold it together. What would be nice if somebody made a C application framework for cc65 that did things like set up interrupts and let you register callback functions. Then you could focus your C code on the particular processing of your app and not have to deal with boilerplate stuff, and then only inject assembly where it suits your app's particular needs, if at all.
-
- Posts: 82
- Joined: Tue Jan 12, 2021 9:52 pm
Keyboard
17 minutes ago, SlithyMatt said:
What would be nice if somebody made a C application framework for cc65 that did things like set up interrupts and let you register callback functions.
Hi!
More or less this is why I went to KickC, since (maturity aside), it has build in functions for enabling interrupts. SEI() and CLI(). It's like a minimal super super minimal "framework" to get it done.
Anyway when I am more or less happy with my code working in both world (ASM and C) without the inexplicable behaviour, a framework sounds like a cool project.
But yeah, first it needs to work properly ?
Sorry for the spam, thanks for the feedback so far guys, I do appreciate it.
ps. Now I think of it, I don't see a "libraries and frameworks" section under downloads, so not sure where to post it, if I'd ever get around to doing something like that.
-
- Posts: 913
- Joined: Tue Apr 28, 2020 2:45 am
Keyboard
6 minutes ago, CursorKeys said:
Now I think of it, I don't see a "libraries and frameworks" section under downloads, so not sure where to post it, if I'd ever get around to doing something like that.
There is a "Dev Tools" section
Keyboard
I ended up using one instance of "asm("jmp _DefaultIRQ");" in my 99.999% C program. (that and asm("SEI"); c code; asm("CLI"); during my IRQ install routine)
Sometimes being close to the metal in C, but having the metal itself under glass can be pretty frustrating. But at the end of the day, I got it all done. It's not just the results - it's the friends we made along the way. (right?)
Keyboard
Your program does too much! Instead, it should follow these rules:
Ignore interrupts that aren't important to the program. Your test program cares about only the raster line. Let Kernal handle the other interrupt.
Don't constantly re-enable the interrupt. Set it once, at the beginning (it will stay enabled).
After you handle the interrupt, clear only that interrupt!
Always exit through the saved vector. Let Kernal do its things.
#pragma target(cx16)
#include <cx16.h>
#include <6502.h>
#include <string.h>
#include <stdlib.h>
#include <conio.h>
#include <veralib.h>
volatile char vbcounter=0;
volatile void *default_irq_vector;
__interrupt void myInterrupt(void)
{
if (*VERA_ISR & VERA_LINE) {
vbcounter++;
*VERA_IRQLINE_L = 100;
*VERA_ISR = VERA_LINE;
}
asm {
jmp (default_irq_vector)
}
}
void initMain(void)
{
volatile unsigned char ll = 0, mm=0;
default_irq_vector = *KERNEL_IRQ;
SEI();
*KERNEL_IRQ = &myInterrupt;
CLI();
*VERA_IRQLINE_L = 4;
*VERA_IEN |= VERA_LINE;
vpoke( 0,0, 0 ); // set "@" left top screen
vpoke( 0,1, 0x12 ); // set "R" next to it
while (1) {
unsigned char c = kbhit();
if (c == 32) {
vpoke( 0,0, ++mm ); // increment left top screen
vpoke( 0,1, 0x12 ); // set "R" next to it
}
vpoke( 0,2, vbcounter );
vpoke( 0,3, 0x12 ); // set "R" next to it
vpoke( 0,4, ll++ );
vpoke( 0,5, 0x12 ); // set "R" next to it
}
}
void main(void)
{
initMain();
}
-
- Posts: 137
- Joined: Tue Jun 30, 2020 3:47 pm
Keyboard
1 hour ago, Greg King said:
Always exit through the saved vector. Let Kernal do its things.
It doesn't make sense to jump through the original vector when a line interrupt is handled, as that would cause the kernal interrupt handler to run multiple times in one frame. I may be wrong, but it looks like the program above jumps to the kernal IRQ handler every time an interrupt occurs.