Jump to content
  • 0

Assigning IRQ vectors in cc65


ZeroByte
 Share

Question

I found a thread about void function(void) pointers elsewhere here, and I could basically follow what I was reading, but I can't quite figure out the syntax for doing what I'm trying to do.

I have these ingredients:

// RAM location of the IRQ handler vector
#define IRQVECTORLOC 0x314

// dummy variable so I can write to 0x314
#define IRQvector    (*(uint16_t*)IRQVECTORLOC)

static void irq(void) {
  //stuff
  SystemIRQ();
}

Okay - so what I want to do is store IRQvector into a variable "SystemIRQ" so that my irq() function can call SystemIRQ();
e.g.:
SystemIRQ = IRQvector;
asm("SEI");
IRQvector = (uint16_t)&irq;
asm("CLI");

I just can't seem to find the secret sauce of declarations that will make the compiler happy.
Should I just put asm("JMP #%b",SystemIRQ); and have done?

Link to comment
Share on other sites

19 answers to this question

Recommended Posts

  • 0
31 minutes ago, ZeroByte said:

So if you defined void(*)(void) as a type "VoidFnPtr" then the #define just looks like:

// 0x314 as a variable
#define IRQvector (*(VoidFnPtr)IRQVECTORLOC)

After making some changes to the code, I was able to get the following version to compile:

Quote

// RAM location of the IRQ handler vector
#define IRQVECTORLOC 0x314

typedef void (*VoidFnPtr)(void); // create a named typedef
#define IRQvector (VoidFnPtr *)(IRQVECTORLOC)

void (*SystemIRQ)(void);

static void irq(void) {
  //stuff
  SystemIRQ();
}

int main() {
    SystemIRQ = *IRQvector;
    asm("SEI");
    *IRQvector = irq;
    asm("CLI");
    return 0;
}

 

  • Thanks 1
Link to comment
Share on other sites

  • 0

It looks to me like you're missing a `void (*SystemIRQ)();` declaration somewhere...

Edit: That having been said, I haven't done any interrupts in cc65, and I doubt it's safe to just clobber the system IRQ with a cc65 function (`rts` vs `rti`, and all that). Are there any IRQ-related functions in the x16 library for cc65?

Edited by StephenHorn
Link to comment
Share on other sites

  • 0

Not sure whether this will crash when it runs, but I was able to get this test program to compile:

Quote

// RAM location of the IRQ handler vector
#define IRQVECTORLOC 0x314

// dummy variable so I can write to 0x314
void (*IRQvector)(void) = (void (*)(void))(IRQVECTORLOC);

void (*SystemIRQ)(void);

static void irq(void) {
  //stuff
  SystemIRQ();
}

int main() {
    SystemIRQ = IRQvector;
    asm("SEI");
    IRQvector = &irq;
    asm("CLI");
    return 0;
}

 

Edited by Elektron72
Link to comment
Share on other sites

  • 0

The "dummy variable" isn't a dummy -- it's the KERNAL interrupt vector.  It must be described this way:

// 0x314 as a variable
#define IRQvector (*(void (*)(void))IRQVECTORLOC)

However, read this: https://github.com/cc65/wiki/wiki/Interrupt-handlers-in-C

The set_irq() method is safe, but it's slow.  Your C function won't start as soon after the interrupt as you want it to start -- especially for VERA code!  Writing your handler in Assembly is a better plan.

Another consideration is the 16-bit API.  If you use the pseudo-registers r0 to r15 in your C code and in your handler, then you must preserve their values during your handler's run.

P.S., You don't need the "&" address operator -- the name of the function is its address.

  • Like 3
Link to comment
Share on other sites

  • 0
1 hour ago, Elektron72 said:

void (*IRQvector)(void) = (void (*)(void))(IRQVECTORLOC);

LOL - no wonder I couldn't find the right combo. So it looks like IRQvector is an actual variable and not just a #define anymore - maybe that was what I needed.

I ended up just using this and it works great:
asm("JMP (_SystemIRQ)"); - thankfully, indirect addressing mode for JMP works right on 65C02.

P.S.: what does putting the * in parentheses do? void (*)(void)

Edited by ZeroByte
Link to comment
Share on other sites

  • 0
3 minutes ago, Greg King said:

Writing your handler in Assembly is a better plan.

I agree - I just didn't want to have to shift gears and start my first mixed project if I didn't have to - learning how to access the C-space variables, etc, code segment names, etc. Plus I kind of want Flappy Bird to be as much pure C as possible just to see if a fully playable game runs well built entirely in C. If I weren't spending so much time refactoring my code over and over to keep changing how I'm doing things under the hood, I'd be done already - lol.

Although, switching from waitvsync() to using an IRQ was pretty much a must at this point. Unfortunately, my new way of tracking used/unused sprites has borked the display so I wasn't able to post a working version to the site just yet. I should be able to get that sorted soon, hopefully.

Link to comment
Share on other sites

  • 0
10 minutes ago, ZeroByte said:

LOL - no wonder I couldn't find the right combo. So it looks like IRQvector is an actual variable and not just a #define anymore - maybe that was what I needed.

I ended up just using this and it works great:
asm("JMP (_SystemIRQ)"); - thankfully, indirect addressing mode for JMP works right on 65C02.

P.S.: what does putting the * in parentheses do? void (*)(void)

Normally a C function signature / prototype is: RETTYPE NAME(ARGLIST). If you want a pointer to a function, then you need the pointer declaration syntax:

RETTYPE (*PTRNAME)(ARGLIST)

When you are casting something from one type to another, you don't need the name any more:

RETTYPE (*)(ARGLIST)

The reason the asterisk is in parentheses has to do with C precedence rules. If you omit the parentheses, it is going to bind the asterisk to the return type, saying you want to return a pointer to some type. For example:

RETTYPE *NAME(ARGLIST); // NAME is a function taking ARGLIST and returning a pointer to RETTYPE (don't let the space fool you)

RETTYPE (*NAME)(ARGLIST); // NAME is a pointer to a function taking ARGLIST and returning RETTYPE

  • Like 2
Link to comment
Share on other sites

  • 0
14 minutes ago, Scott Robison said:

When you are casting something from one type to another, you don't need the name any more:

Holy crap, that's confusing. I mean, I follow your explanation completely. It totally makes sense. I'm still not quite seasoned enough to have produced that particular concoction myself - lol.
It seems that making a typedef would cause that monstrosity to be a lot more readable....
So if you defined void(*)(void) as a type "VoidFnPtr" then the #define just looks like:

// 0x314 as a variable
#define IRQvector (*(VoidFnPtr)IRQVECTORLOC)

Link to comment
Share on other sites

  • 0
1 minute ago, ZeroByte said:

Holy crap, that's confusing. I mean, I follow your explanation completely. It totally makes sense. I'm still not quite seasoned enough to have produced that particular concoction myself - lol.
It seems that making a typedef would cause that monstrosity to be a lot more readable....
So if you defined void(*)(void) as a type "VoidFnPtr" then the #define just looks like:

// 0x314 as a variable
#define IRQvector (*(VoidFnPtr)IRQVECTORLOC)

A typedef is definitely the way to go when writing your own code. Reading the decl is important when reading code from someone else.

typedef void (*VoidFnPtr)(void); // create a named typedef

Link to comment
Share on other sites

  • 0
58 minutes ago, ZeroByte said:

Although, switching from waitvsync() to using an IRQ was pretty much a must at this point.

set_irq() is slower than Assembly, but it should be faster than waitvsync().  waitvsync() doesn't run until KERNAL has finished running its interrupt handler.  That handler reads the mouse, the keyboard, and the joystick controllers.  And, it "bumps" the jiffy timer, and blinks the text cursor.

Link to comment
Share on other sites

  • 0
51 minutes ago, Greg King said:

set_irq() is slower than Assembly, but it should be faster than waitvsync().  waitvsync() doesn't run until KERNAL has finished running its interrupt handler. 

I'd noticed that about waitvsync() - just hitting keys on the kbd was making it take long enough that there was tearing at the top of the screen, so I knew I needed to use an IRQ. Since it didn't affect the game from a developer's perspective (just looked like crap), I decided to table that until I got other things working. Last night was the time I got to it, mostly because I had to keep manually calling my update_screen() routine in various branches of my code, where an IRQ would "just happen" so at this point, IRQ became the thing to make my life easier so I did it. 🙂

Link to comment
Share on other sites

  • 0
2 hours ago, SlithyMatt said:

Next time someone asks me why I think it's easier to program in assembly rather than C for the X16, I'll point them to this thread.

Yeah - it's got its pros and its cons. I got to my initial level of "doneness" in just a week back when I first started working on this. It was so much simpler to just say x * y and let the compiler handle that. It's whenever you get close to the metal that this kind of issue arises - and I spent several days' worth of hair-pulling because I hadn't gotten the knack for dealing in pointers ( x, *x, &x, x, &x, *x, etc etc ). I've got a decent grasp on it now where I can get it into the compiler relatively error-free now, but when I came across this "how do I tell frikkin' C that I wanna JSR ($0314) ???" - that one beat me. 🤣

The thing about assembler that gets me is that sometimes it feels like "I wanna put on my shoes. First, I must raise a cow, slaughter and skin it, then cure the hide so I can have some leather....."

Although, I do think it would be cool if you were to do some more mixed C/ASM projects in your tutorial series. I've done assembly before, but only in more straightforward tools than ca65 with all of this code segment / map file stuff, and haven't wanted to roll up my sleeves and learn that side of assembly just yet.

Edited by ZeroByte
Link to comment
Share on other sites

  • 0
2 minutes ago, ZeroByte said:

I wanna put on my shoes. First, I must raise a cow, slaughter and skin it, then cure the hide so I can have some leather....."

To turn that metaphor on its head, the farm gives us cows. Our friendly farm hand C says he can get you shoes. You say you want milk. Instead of just milking the cow, C has to reconstruct the cow from a bunch of shoes and cut a hole in one of the toes to pour milk through it....

or something. 

Link to comment
Share on other sites

  • 0

C is like that scene out of Idiocracy where he's in the line of people being booked into prison and he tells the guard "I think there's some mistake. I'm supposed to be getting let out of prison today." and the guard smacks him over the head, and says "you're in the wrong line, dumbass!"

It goes to these great lengths to make sure you do everything right with types, but then it lets you point a pointer right on top of whatever and start writing away willy-nilly, or write past the end of a string or an array. I mean, you expect that in assembly.

Link to comment
Share on other sites

  • 0
  • Super Administrators
1 hour ago, ZeroByte said:

C is like that scene out of Idiocracy where he's in the line of people being booked into prison and he tells the guard "I think there's some mistake. I'm supposed to be getting let out of prison today." and the guard smacks him over the head, and says "you're in the wrong line, dumbass!"

It goes to these great lengths to make sure you do everything right with types, but then it lets you point a pointer right on top of whatever and start writing away willy-nilly, or write past the end of a string or an array. I mean, you expect that in assembly.

That's because C and assembly are meant for real programmers, not children. If you're not skilled enough to know the size of an array you allocated, then you should be working in a career where the hardest question is "would you like fries with that?" 

That being said, I use C# more than C or C++, because I already know my customers want fries- along with an extra side of ranch, please.

 

Edited by TomXP411
Link to comment
Share on other sites

  • 0
1 hour ago, TomXP411 said:

That's because C and assembly are meant for real programmers, not children. If you're not skilled enough to know the size of an array you allocated, then you should be working in a career where the hardest question is "would you like fries with that?" 

 

Assembly is what it is because every CPU has it's own vendor defined syntax, and then different people create assemblers that create variations on that syntax for different reasons. Assembly (and by extension machine language) is as non-standard as things get.

C is often used these days as portable assembly language, because it is in theory "standardized". The hard part of C is that it has a difficult syntax with some unintuitive operator precedence to people coming from other languages.

int * a, b; // looks like you are creating two integer pointers, but really you are creating one pointer (*a) and one plain integer (b)

I prefer to write these declarations as "int* a" instead of "int *a". The latter is what is really happening, but the former reads better to me. Until you try to declare two in one line. I'm just used to reading declarations like these from right to left: "a is a pointer to an integer". Or to get even more complicated: "const char* const p" is "p is a constant immutable pointer to a character that is itself constant / immutable".

Most people who use C use postfix increment and decrement operators (i++ or i--) because that's what they learned. I prefer ++i and --i. The argument can be made that the prefix form is potentially more efficient. But the bigger win in my mind is that it reads more naturally: "i++" reads "i increment" whereas "++i" reads "increment i". The latter makes a lot more sense to my native English speaking mind, though I'm sure there are other languages where the reverse might be true. There are times where you need the postfix, and that's fine, but when either will suffice, I choose it to read most naturally to me.

There is no reason why one cannot have a close to the metal language that can be portable like C that has a more intuitive syntax than C. I like C++ and use it primarily in my day job, but it has the same issue dialed up to 11 in some ways.I've been thinking lately about defining a "portable assembly language" or "pseudo assembly language" that trades all the TLA in 6502 assembly for operators.

Link to comment
Share on other sites

  • 0
3 minutes ago, ZeroByte said:

I wonder how hard FORTRAN would be to port to X16.....

I took one class in it, and looked at some FORTRAN code much more recently after knowing assembly and was struck by how close they seemed to me...

I was a TA for a FORTRAN class circa 1988, back when FORTRAN 77 was still the standard. I think the hardest thing about FORTRAN would be the type system. It is build around the idea of numerical computing and has a lot of operators and types to support various precisions of integer, real, and complex numbers.

FORTRAN 77 didn't support recursion, so that would be a plus for the 6502, but I think that's about the only real win that it would have. But I'm 30+ years out of experience with old FORTRAN and no experience with FORTRAN 90 or newer.

BASIC was inspired by FORTRAN, so it isn't that it couldn't be done, I just don't see anyone wanting to write the types of programs that you would want to use FORTRAN for on a 6502. But someone might!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

Please review our Terms of Use