Keyboard

Get technical support from the community & developers with specific X16 programs if you can't find the solution elsewhere
(for general non-support related chat about programs please comment directly under the program in the software library)
SlithyMatt
Posts: 913
Joined: Tue Apr 28, 2020 2:45 am

Keyboard

Post by SlithyMatt »


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.

CursorKeys
Posts: 82
Joined: Tue Jan 12, 2021 9:52 pm

Keyboard

Post by CursorKeys »


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

}



 











 

Elektron72
Posts: 137
Joined: Tue Jun 30, 2020 3:47 pm

Keyboard

Post by Elektron72 »



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.

CursorKeys
Posts: 82
Joined: Tue Jan 12, 2021 9:52 pm

Keyboard

Post by CursorKeys »



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. 

SlithyMatt
Posts: 913
Joined: Tue Apr 28, 2020 2:45 am

Keyboard

Post by SlithyMatt »


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.

CursorKeys
Posts: 82
Joined: Tue Jan 12, 2021 9:52 pm

Keyboard

Post by CursorKeys »



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.

SlithyMatt
Posts: 913
Joined: Tue Apr 28, 2020 2:45 am

Keyboard

Post by SlithyMatt »



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

ZeroByte
Posts: 714
Joined: Wed Feb 10, 2021 2:40 pm

Keyboard

Post by ZeroByte »


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?)

Greg King
Posts: 162
Joined: Wed Jul 08, 2020 1:14 pm

Keyboard

Post by Greg King »


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
();
}

Elektron72
Posts: 137
Joined: Tue Jun 30, 2020 3:47 pm

Keyboard

Post by Elektron72 »



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.

Post Reply