Interrupts in CC65 ?

Started by Mark Smith, August 03, 2007, 08:38 AM

Previous topic - Next topic

0 Members and 2 Guests are viewing this topic.

Mark Smith

Can anyone give me some pointers (no pun intended) on how to setup and use interrupts in CC65 ?

Thanks!

Mark
------------------------------------------------------------------------------------------------------------------

Commodore 128, 512K 1750 REU, 1581, 1571, 1541-II, MMC64 + MP3@64, Retro-Replay + RR-Net and a 1541 Ultimate with 16MB REU, IDE64 v4.1 + 4GB CF :-)

hannenz

to implement interrupts completely in C is quite difficult and i must admit that i simply don't know how to do it. But i remember that i used custom irq handling routines with cc65 mixed with ML parts, so i think the IRQ under cc65 progs is just the simple Kernel's System IRQ, which you should easily change by altering the $314/15 vector. you'll have to do it in ML propably, try something like this:

/* c program */

main(){
   setup_irq();
  /* ... */
}



; assembler source

.export _setup_irq
_setup_irq:
  sei
  lda #<irq
  ldx #>irq
  sta $314
  sta $315
  cli
  rts

irq:
  ...
  rti


at least that's what i did on c64 - don't know exactly what else is to be considered on c128 concerning interrupts (e.g. memory managment etc.)

BillBuckels

Pardon Me For Interrupting

Setting-up interrupts is hardly a difficult task in any C compiler environment. Otherwise how could we possibly write a compiler library. There was a time when we couldn't write much at all without using interrupts and come to think about it that time didn't end until long after the C64 and C128 had vanished from most basements.

I am honestly not so much interested in CC65,  but getting past my old-fashioned approach to using a time tested product, assuming that this CC65 is a real compiler with what I expect a compiler to offer like inline assembly for example (which of course Aztec C does and has for the last 25 years or so) here's how an interrupt call in Aztec C is setup on the C64 if one doesn't want to bother with linking-in a second object module.

You should presumably be able to just paste such code into CC65 and it will work on the C128. If it won't you should probably ask the question "Why won't it?".

The following example is a library module from my B64NAT library which I offer for (free of course) download from my Aztec C website c/w source. It is pretty well commented and probably needs little further ado:


/* (C) Copyright 2008 Bill Buckels */

#define TRUE 1
#define FALSE 0

#include <io.h>
#include <poke.h>

/* a "limited" directory lister */
/* for files of a 3 character extension */
/* or files of any extension */
/* this will not work for files without extensions */

/*

This module uses the native C64 SYSIO library functions
in conjunction with a Commodore 64 ROM call to the
built-in LOAD routine to list the files on the
program disk.

This is similar to the BASIC 2 command sequence:

LOAD "$",8
LIST

In fact this module uses exactly the same routines
but compresses the directory listing retrieved by the
LOAD routine into a buffer starting at 6144.

It does so by walking-through the buffer and pulling the
filenames from between the quote delimiters in the
buffer.

Although it helps if one is somewhat familiar with
C64 BASIC and the C64 flavor of 6502 assembler as
well as the C64 Aztec C native library and in fact
the Commodore 64 memory map and how the Aztec C linker
is used to build this thing, this still provides a good
read even for those who don't or can't remember
if they do.

The following link will be generally helpful
when it comes to knowing a little more about
Commodore DOS :

http://en.wikipedia.org/wiki/Commodore_DOS


*/


/*

B-15. Function Name: LOAD
Purpose: Load RAM from device
Call address: $FFD5 (hex) 65493 (decimal)
Communication registers:A, X, Y
Preparatory routines: SETLFS, SETNAM
Error returns: 0,4,5,8,9, READST
Stack requirements: None
Registers affected: A, X, Y

Description: This routine LOADs data bytes from any input device di-
rectly into the memory of the Commodore 64. It can also be used for a
verify operation, comparing data from a device with the data already
in memory, while leaving the data stored in RAM unchanged.

The accumulator (.A) must be set to 0 for a LOAD operation, or 1 for a
verify, If the input device is OPENed with a secondary address (SA) of
0 the header information from the device is ignored. In this case, the
X and Y registers must contain the starting address for the load. If
the device is addressed with a secondary address of 1, then the data
is loaded into memory starting at the location specified by the
header. This routine returns the address of the highest RAM location
loaded.

Before this routine can be called, the KERNAL SETLFS, and SETNAM
routines must be called. NOTE: You can NOT LOAD from the keyboard (0),
RS-232 (2), or the screen (3).


How to Use:

Call the SETLFS, and SETNAM routines. If a relocated load is desired,
use the SETLFS routine to send a secondary address of 0.

Set the A register to 0 for load, 1 for verify.

If a relocated load is desired, the X and Y registers must be set to
the start address for the load.

Call the routine using the JSR instruction.

EXAMPLE:

         ;LOAD   A FILE FROM TAPE

          LDA #DEVICE1        ;SET DEVICE NUMBER
          LDX #FILENO         ;SET LOGICAL FILE NUMBER
          LDY CMD1            ;SET SECONDARY ADDRESS
          JSR SETLFS
          LDA #NAME1-NAME     ;LOAD A WITH NUMBER OF
                              ;CHARACTERS IN FILE NAME
          LDX #<NAME          ;LOAD X AND Y WITH ADDRESS OF
          LDY #>NAME          ;FILE NAME
          JSR SETNAM
          LDA #0              ;SET FLAG FOR A LOAD
          LDX #$FF            ;ALTERNATE START
          LDY #$FF
          JSR LOAD
          STX VARTAB          ;END OF LOAD
          STY VARTA B+1
          JMP START
  NAME    .BYT 'FILE NAME'
  NAME1   ;

*/


/*

B-28. Function Name: SETLFS
Purpose: Set up a logical file
Call address: $FFBA (hex) 65466 (decimal)
Communication registers: A, X, Y
Preparatory routines: None
Error returns: None
Stack requirements: 2
Registers affected: None

Description: This routine sets the logical file number, device
address, and secondary address (command number) for other KERNAL
routines.

The logical file number is used by the system as a key to the file
table created by the OPEN file routine. Device addresses can range
from 0 to 31. The following codes are used by the Commodore 64 to
stand for the CBM devices listed below:

ADDRESS  DEVICE
0  Keyboard
1  DatassetteTM
2  RS-232C device
3  CRT display
4  Serial bus printer
8  CBM serial bus disk drive


Device numbers 4 or greater automatically refer to devices on the
serial bus.

A command to the device is sent as a secondary address on the serial
bus after the device number is sent during the serial attention
handshaking sequence. If no secondary address is to be sent, the Y
index register should be set to 255.

How to Use:


Load the accumulator with the logical file number.
Load the X index register with the device number.
Load the Y index register with the command.
EXAMPLE:

   FOR LOGICAL FILE 32, DEVICE #4, AND NO COMMAND:
   LDA #32
   LDX #4
   LDY #255
   JSR SETLFS


*/


There is no replacement for Well Commented Code Examples.

All too often I see code that is offered as being supported with few if any comments because the programmer thinks all this is obvious. Well the saying goes something like "I've cut more code from an empty screen than you have pasted in from a textbook." and I still can't say often enough that there is no replacement for properly commented code.

Hopefully my C64 library that I added to the Aztec C stuff will probably never need a lick of support. Keep in mind that there are working examples for pretty much every library function with the following level of comments and this complier has been in production for a quarter century for a platform that hasn't changed in almost as long.


dlist(prg,ext)
char *prg, *ext;
{

    /* this function moves the names of any valid files
       to the beginng of our memory hole at 6144 and leaves the
       rest of our memory hole for other uses */

    /* best called at the start of a program.
       the file list can then be moved as needed to some other
       area of memory. I am overlaying the same area
       that I load the graphics font into so for a graphics program
       the font should be relocated before making this call
       or reloaded afterwards, after something is done with the listing. */
    int idx, found, c,d,e,f,p,r,g, ctr, first, base = 0;

    char E='*', X='*', T='*';

    /* args are either a wild card for all or a 3 character extension */
    if (ext[0] != '*') {
E = ext[0];
X = ext[1];
T = ext[2];
}


    dirtop = 6144; /* set the top of the listing in memory
                      to the base of the directory array.
                      this will be advanced for each file found */


_dset(prg); /* seed the device listing with this
                 program's name. assume that they
                 have not pulled the disk out of the
                 drive */

first = FALSE; /* no disk name yet */
found = FALSE; /* no names of any kind yet */
ctr = 0;
for (idx = 0; idx < 8192;idx++) { /* 8192 is an unlikely size for a directory */
  c=dirptr[idx];
  /* if a quote character is found it may be the beginning or end of
     a file name so test further */
  if (c == '\"') {
  if (found==FALSE){
  /* test for recognized file types and file names */
  if (idx == 5) {
                  found = TRUE;
  }
  else {
  p = dirptr[idx+19]; /* type of file */
  r = dirptr[idx+20];
  g = dirptr[idx+21];

  if (p == 'P' && r == 'R' && g == 'G')found = TRUE; /* program */
  if (p == 'S' && r == 'E' && g == 'Q')found = TRUE; /* sequential */
  if (p == 'R' && r == 'E' && g == 'L')found = TRUE; /* relative */
  if (p == 'U' && r == 'S' && g == 'R')found = TRUE; /* user specified */
  if (p == 'D' && r == 'E' && g == 'L')found = TRUE; /* internal use */

  if (found == TRUE) {
/* we are using the SIB file extension as a directory
    filter string. there is no telling what kind of trouble
    we might have otherwise */
p = FALSE;
r = idx+1;
/* check for a valid file and pop it to the next position
    in our directory list if we find it. */
for (g = 0; g < 13; g++) {
    c = dirptr[r];
                        if (c== '\"')break;
                        if (c=='.') {
   d = dirptr[r+1];
   e = dirptr[r+2];
   f = dirptr[r+3];
   if (E == '*') {
      p = TRUE;
      break;
   }
   if (d == E  && e == X && f == T) {
  p = TRUE;
  break;
   }
}
    r++;
}
if (p == TRUE) {
p = base;
r = idx+1;
for (g = 0; g < 17; g++) {
/* static length of 17 is stuffed with 0's first */
dirptr[base] = 0; base++; dirtop++;
}
for (g = 0; g < 16; g++) {
c = dirptr[r];
if (c== '\"')break;
dirptr[p] = c;
p++;
r++;
      }
ctr++; /* we are only counting the valid files */
}
first = TRUE;
  }
  }
  }
  else {
  found = FALSE;  /* turn-off file name */
  first = TRUE;
  }
  }
  else {
  if(found != TRUE) {
/* the end of the listing is terminated with "BLOCKS FREE." */
if (c=='B') {
/* quick check on first 3 characters */
     p = dirptr[idx+1];
     r = dirptr[idx+2];
     g = dirptr[idx+3];
     if (p == 'L' && r == 'O' && g == 'C') {
       if (strncmp("BLOCKS FREE.",(char *)&dirptr[idx],12)==0) {
break;
   }

   }
}
  }

      }
}

return ctr;

}


Use Whatever built-in SYS functions you have available.

One nice thing about Aztec C is that all C is translated to assembly on the first pass. You can examine the output and also tweak it if you like and at the very least determine exactly what syntax to use to do whatever it is that you wish to do without even needing to memorize an op code.

However, before you even need to think about that approach since you have all the library source and all the low-level code for review, you can determine whatever other low-level calls can be made and the best way to do so and follow those examples as well.


/* use Aztec C's Commodore 64 SYSIO functions
   to set-up the directory lister then
   call a helper function to complete
   the command */
_dset(prg)
char *prg;
{
struct fil_tab fp; /* static file table for lowlevel call */

errno = 0;
/* get the device that we have been loaded from */
if (_get_dev(prg, &fp) < 0)
return(-1);
    /* set logical file number, device address, and indicate
       that we will be using a secondary load address.
       in this case we will be using our 2K buffer below
       the graphics screen */
_setlfs(2,fp.dev,0);
/* the next line is reminiscent of BASIC */
_setnam("$"); /* set name to directory */
    /* now call the rom routine to load the directory into
       our buffer */
    return _dlode();
}


Yes virginia, there is inline assembly in Aztec C.

The nice thing about wrapping an open call in inline assembly is that the C compiler has already saved the stack prior to calling the function and you would have to really muck-up royally before the function didn't return properly. At least that's the popular theory...


/* yes virginia, there is inline assembly in Aztec C */
_dlode()
{
#asm

* rather than muck with passing stack args
* i kept it simple and hard-coded the relocatable
* address for the dir buffer which is $1800

LOAD equ $ffd5


* SET FLAG FOR A LOAD
lda #0
* ALTERNATE START
    ldx #0
    ldy #$18
    jsr LOAD
    rts

#endasm
}


I sincrely hope that this little code snippet provides some useful information to you and for more of the same (much more actually) visit the following link and fill your hand...

http://www.clipshop.ca/Aztec/index.htm#commodore

BillBuckels



A Directory Lister using A Library Call

To complete the listing for the interrupt example, as for any code, one needs a test harness. Lots of little examples are exactly that... and one leads to another and pretty soon a library is built and tested with samples and examples and all that as part of the deliverable. This is the way of the Wonderfully Ancient World of Aztec-C and hopefully this time honoured tradition passes forward. Otherwise how does one test and demonstrate what they have tested to themselves and others?

As far as the code below, I would expect that some compilers would provide a findfirst, findnext structure to wrap this in but I confess that I did stop short of all that before this go-around. That will be a project for another day.


/* dirlist.c (C) Copyright 2008 Bill Buckels */
/* Read a file list from the current disk directory
   in an Aztec C Commodore 64 program */

#define CMAIN 1

#include <poke.h>
#include <colors.h>


/* The linker provides a 2K memory hole directly below
   the graphics screen area which provides a static
   buffer without the need for memory allocation.

   This is where we load the disk directory (at decimal 6144).

   This is also where I preload my fonts and title screen
   vram in an image loader program.

   The linker line from the MAKEFILE is as follows:

   ln65 $(PRG).rel C64NAT.LIB -b 810 -d 4400 -c 5000

   You should also note that the base address of 810
   provides the usual jmp address for the launcher code
   that is appended to the beginning of the PRG
   after linking. (SYS 2064 CALL)

   The data is well beyond the screen
   and allows 3K before loading the program code.
   The deal here is to make everything fit
   without clobbering BASIC and the rest of the C64.

   When that occurs we just go to overlays and
   you can review my overlay samples to see how we
   do that. 'Nuff said.

*/

#define NOFF 17

main()
{
int idx, jdx, found;

    scr_clear(); /* home cursor and clear screen */
    scr_txtcolor(C64_BWHITE); /* change text to bwhite */

    printf("LISTING OF CURRENT DISK:\n");

    /* read the directory of the disk that we are on */
found = dlist("DIRLIST.PRG","*");

printf("%d FILE(S) FOUND:\n",found);

for (idx = 0; idx < found; idx++) {
   jdx = (idx * NOFF); /* each listing is 17 bytes long */
   puts(&dirptr[jdx]);
    }

    found = dlist("DIRLIST.PRG","PRG");
    printf("%d PRG(S) FOUND:\n",found);
    for (idx = 0; idx < found; idx++) {
   jdx = (idx * NOFF); /* each listing is 17 bytes long */
   puts(&dirptr[jdx]);
    }

    printf("PRESS A KEY TO END ");
    getchar();
    scr_txtcolor(C64_LBLUE); /*  change text to lblue */
    scr_clear(); /* clear screen and outa here */
exit(0);
}


The MAKEFILE is your friend.

I am a programmer not a typist. My preference has always been to type as little as possible. To this end, the one thing that I know that I can remember even on a bad day is to type "MAKE" and press [Return].


# -----------------------------------------
# Aztec C64 makefile by bill buckels 2008
# -----------------------------------------

PRG=dirlist

$(PRG).B64: $(PRG).asm
   as65 $(PRG).asm
   del $(PRG).asm
   copy $(CLIB65)B64NAT.LIB .
   copy $(CLIB65)C64NAT.LIB .
   ln65 $(PRG).rel B64NAT.LIB C64NAT.LIB -b 810 -d 4000 -c 4200
   del $(PRG).rel
   del C64NAT.LIB
   del B64NAT.LIB
   MKBASIC $(PRG) $(PRG).prg
   del $(PRG)
   
   
$(PRG).asm: $(PRG).c MAKEFILE
    copy $(INCL65)poke.h .
    copy $(INCL65)colors.h .
    c65 $(PRG).c
    del poke.h
    del colors.h


The Emulator is your Friend.



If one is lucky enough to compile the goal then then the code must still be tested of course, and the VICE emulator and warpmode makes this a very easy process. This is why when preparing the Aztec-C distribution and the samples that each got its own MAKEFILE and MakeDisk.BAT.


@echo off
SET PRG=dirlist
rem batchfile to create c64 diskimage
rem by Bill Buckels 2007
rem this batch cannot be called from the makefile
rem c1541.exe that comes with winvice must be on-path
rem this disk must be used on a c64 and not on a c128
rem the program is a true c64 binary with BASIC startup code

copy makefile makefile.txt
copy makedisk.bat makedisk.txt
copy %PRG%.c %PRG%.txt
if exist %PRG%.d64 del %PRG%.d64
c1541 -format clipshop,99 d64 %PRG%.d64 -write %PRG%.prg -write %PRG%.txt -write makefile.txt -write makedisk.txt -quit
del makefile.txt
del makedisk.txt
del %PRG%.txt

SET PRG=



http://www.clipshop.ca/Aztec/index.htm#commodore