3

I've been playing with Game Boy Advance coding in C. For interrupts to work, the address of your interrupt handler routine must be manually copied to RAM location 0x03007FFC. I know how to do this in ARM Assembly but not in C. I was trying something like this:

#define REG_INTERRUPT *(vu32*)0x03007FFC
void irqhandler()
{
    while(1){}
}

int main()
{
    REG_INTERRUPT = &irqhandler();

    while(1){}
    return 0;

}

But it doesn't work. From what I understand about C functions, it seems like C is thinking I'm trying to get the address of the return value of irqhandler (which there is none since it's a void) and that's where the error comes from. How do I tell C that I want the memory location of the function itself?

Lundin
  • 195,001
  • 40
  • 254
  • 396
puppydrum64
  • 1,598
  • 2
  • 15
  • 1
    Try `&irqhandler`, without the `()`. – sj95126 Nov 03 '21 at 00:43
  • 2
    Get rid of the `()` after the function. You're calling the function. – Barmar Nov 03 '21 at 00:43
  • Ok, that did it. It says I'm making an integer from a pointer without a cast, but it still goes through (checking the memory viewer confirms that an actual code location was written there.) – puppydrum64 Nov 03 '21 at 01:16
  • Drop the ampersand and add the cast: `REG_INTERRUPT = (vu32)irqhandler;` – user3386109 Nov 03 '21 at 01:29
  • alternately, use a `void *` in the macro: `#define REG_INTERRUPT *(void **)0x03007FFC` – Chris Dodd Nov 03 '21 at 01:43
  • @ChrisDodd: That should still give a warning, since pointers to functions are not automatically convertible to `void *`. Since the intent is to store a pointer to `void (void)`, simply define `REG_INTERRUPT` as `(* (void (**)(void)) 0x3007FFC)`. – Eric Postpischil Nov 03 '21 at 01:52
  • 1
    I recommend avoiding macros and using a real variable, then use prototypes to get them to match. – Neil Nov 03 '21 at 02:21
  • 1
    @Neil You cannot allocate a variable at a specific address in pure standard C without using casts like in this macro. So your comment doesn't make any sense. – Lundin Nov 03 '21 at 11:41
  • @user3386109 Ok, so that's what a cast is. The tutorial I read told me that the unary & prefix is the "address-of" operator, so I figured that if I wanted the address of ```irqhandler``` that's what I needed to do. – puppydrum64 Nov 03 '21 at 11:49
  • @ChrisDodd Hmm... I'm not sure I understand. I see all those parentheses and get confused. I know that a ```*``` makes a variable a pointer to the specified data type but I don't understand all the nested parentheses – puppydrum64 Nov 03 '21 at 11:51
  • 1
    If you wish to do anything useful, I suggest the attribute `void irq_handler () __attribute__ ((interrupt ("IRQ")));`. See: [ARM function attributes](https://gcc.gnu.org/onlinedocs/gcc/ARM-Function-Attributes.html), the function will not return correctly and you will execute in irq mode instead of the prior mode you were in. This is as useful as the accepted answer? – artless noise Nov 03 '21 at 18:05
  • @artlessnoise You must be a mind reader because I had that exact problem - the IRQ would happen correctly yet would never return! – puppydrum64 Nov 03 '21 at 18:20
  • @Lundin true, you can't get it without a cast, but I believe you could still do something like, `typedef (*(volatile uint32_t *reg_interrupt)(void)); static const reg_interrupt irq = (reg_interrupt)0x03007FFC; *irq = &irqhandler` and forget the macro, (something like that? Maybe I'm wrong.) – Neil Nov 03 '21 at 18:26
  • 1
    @Neil Assuming you meant `typedef volatile uint32_t (*reg_interrupt)(void);` then no that won't work, since what's stored at this address is a function, not a function pointer. De-referencing a function pointer gives a function, which is not a "lvalue" and cannot be placed on the left side of assignment. You can only assign a function pointer to another function pointer. – Lundin Nov 05 '21 at 11:11

1 Answers1

6
  • irqhandler() is wrong, you are calling the function instead of taking its address. You should write irqhandler or the 100% equivalent &irqhandler.
  • Don't use strange home-brewed types like vu32, nobody knows what that means. Use standardized types from stdint.h.
  • You can't assign a function pointer to an integer, they are not compatible types. You need an explicit cast to integer.

Corrected code:

#include <stdint.h>
#define REG_INTERRUPT (*(volatile uint32_t*)0x03007FFCu)
...
REG_INTERRUPT = (uint32_t)irqhandler;

This of course assuming that the underlying hardware supports this and that something valid exists at address 0x3007FFC that expects a 32 bit integer access.

For details check out How to access a hardware register from firmware?

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    ```vu16``` was just shorthand for ```volatile unsigned short``` so I didn't have to type it all out. What does the ```_t``` mean in ```uint32_t```? A quick Google search says it's a numeric type that guarantees 32 bits, but I thought all ints were 32 bits? – puppydrum64 Nov 03 '21 at 11:54
  • @puppydrum64 Copy/paste is a thing - and in general, if typing a lot of text is a problem then you picked the wrong profession :) The `_t` stands for "type", it's a common way to traditionally name custom types in C. `int` can have different size depending on system, it's not a portable type and it's also signed, which is not normally something you ever want in embedded systems. `uint32_t` is fully portable and the type you should be using most of the time when coding for 32 bit ARM. – Lundin Nov 03 '21 at 11:56
  • Your editor should also provide auto-complete features that save you the typing. Remember that code is written once, but read many times, so it's worth the extra effort to type out something that is understandable to future maintainers. @puppydrum64 – Cody Gray - on strike Nov 03 '21 at 12:00
  • @puppydrum64 Btw `int main (void)` is not the correct form to use for "bare metal" freestanding embedded systems. You'll normally use the compiler-specific form `void main (void)` since returning from main() is nonsense in such systems. With gcc-like compilers that means you must also compile with `-ffreestanding` which means "this code here is for an embedded system". – Lundin Nov 03 '21 at 12:01
  • 1
    My compiler threw a fit when I tried to use ```void main (void)``` which is why I went with ```int main()``` even though it made no sense since there's nowhere to return to. Forgive me, I'm very new to C, having mostly done assembly. (ARM Assembly isn't very user-friendly which is why I went to C) – puppydrum64 Nov 03 '21 at 12:08
  • @puppydrum64 Which compiler is it? – Lundin Nov 03 '21 at 12:12
  • 1
    I believe it's gcc, I was using devkitadv – puppydrum64 Nov 03 '21 at 12:13
  • 2
    @puppydrum64 Then as mentioned, compile with `-ffreestanding` or it thinks you are coding for a PC. – Lundin Nov 03 '21 at 12:14
  • @CodyGray I'm using Notepad++, it does have autocomplete but since I mostly do assembly programming the auto-complete wasn't very useful and was getting annoying so I turned it off. I don't think there's a way to enable autocomplete on a per-language basis. – puppydrum64 Nov 05 '21 at 10:59