This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

how to populate interrupt vector table w/o functions

Other Parts Discussed in Thread: MSP430F1101A

MSP430F1101A

I would like to set almost all of the 16 interrupt vectors to point to one function, but I dont want to do 16 of:

interrupt [n] void isr_n(void)

{

}

where "n" is the vector offset..because i dont have enough room for 16 functions to be defined in ROM..and I want to make as little of a foot print as possible because this is for debug..

id like just one function that gets pointed to by every vector in the table. (with the exception of maybe 1 or 2 vectors)

im guessing some kind of .iseg statement in assembler, but AQ430 tells me not do mess with that...

cant I command the compiler to just put bytes in the vector table somehow?

  • Hi Asa,

    Here's an easy way to do it:

    #pragma vector = 0,1,2    // Declaring same ISR for 3 vectors
    __interrupt void default_isr (void)
    {

    ..

    }

    Regards,

    Luis R

  • Thanks but I tried it in AQ430 and it doesnt work. Is that the proper way for IAR, just so I know? Because I may switch to IAR.

  • IAR and CCS support this syntax.

    IAR however uses even numbers, i.e::

    #pragma vector = 0, 2, 4
    __interrupt void default_isr (void)

  • Asa Cannell said:
    I would like to set almost all of the 16 interrupt vectors to point to one function, but I dont want to do 16 of: interrupt [n] void isr_n(void)

    The problem is that he C language doesn't know about interrupt vectors. (or interrupts at all).
    So the interrupt keyword is a kludge to tell teh compiler something that is outside the language specification. IAR and CCS doe it with a #pragma, AQ430 with  interrupt[] keyword, MSPGCC uses the function attributes for this (and a macro that read as "interrupt(VECTOR) fname (void)" )

    All of those have one thing in common: a reference to the function (in case of the #pragme the funciton that follows the #pragma) is placed in the interrupt vector table segment at the appropriate place.

    If you cannot or do not want to use this mechanism, the alternative way is to place the reference manually in this segment. How to define a segment is once again compiler or assembler specific, and so is the organization of the vector table segment.

    If your compiler produces an intermediated assembly code for your C file, you may look at it to see how the compiler forwards the vector declaration to the assembler. And you can do the very same for all unused vectors in an assembler file.
    Or you try to adapt this directly in C, by using your compilers syntax to place elements into a fixed segment or fixed position.

  • Thanks. AQ430 emailed me instructions on how to do it:

    Yes, it's possible, but not the way you indicate.
    I would define one of the interrupt vectors through :
     
    interrupt [1] void multi_isr(void)
    {
    ....
    }
     
    then include some assembly code in an .asm file (added to the project) to define the other vectors through data statements:
     
                                .extern _multi_isr

    .pseg INT_VEC,abs=4+0xFFE0

                            .data _multi_isr

                            .data _multi_isr

                            .data _multi_isr

                            .data _multi_isr (as many as it takes)

     

    I'm not sure how this works but it seems like what I would expect..

  • One of the "advantages" of using c is "portability".

    If you stay to the same version of the same c-compiler, it is very portable.

  • old_cow_yellow said:
    One of the "advantages" of using c is "portability".

    This is true only for the parts of a project that are overed by the C language.

    old_cow_yellow said:
    If you stay to the same version of the same c-compiler, it is very portable.

    Portability means: Algorithms. Manipulation of memory locations. Not fancy things like processor registers (C doesn't know about processor registers and doesn't even require something like a stack), internal hardware (try to port a C program that uses the USCI to an Atmel processor. Even if the program compiles, the memory locations you write to or read form surely won't behave like they were an USCI - not C's fault) or the very weird fact that code execution sometimes suddenly jumps from the current point of execution to somewhere completely else, and then back again.
    Things like that can only be done by implementing additional functionality that is not part of C but understood by the compiler. And this is not standardized and therefore not portable. Only by coincidence :)

    Asa Cannell said:
    I'm not sure how this works but it seems like what I would expect..

    the .extern tells the assembler that there somehwere exists a symbold named _multi_isr. (the leading underline is auto-added by C to avoid name space conflicts between C and assembly functions)

    The .pseg tells teh assembler that the following data goes into the program segment INT_VEC (which is known by the linker through a device specific linker script file, defining the position and size of the interrupt vector table), at the absolute position of 0xffe4. The following lines jsut define binary data which starts at 0xffe4 and contains a value which i slater to be resolved by the linker (the position of your multi_isr function)

    It's jsu tnot clear how you mix the 'real' ISR into this construct then, as their position is already occupied by a dummy entry.
    Likely, you'll have to end the list when you come to the vector position that later holds a real ISR address, and then start another pseg with an offset that points behind that vector position.

    e.g.

    .pseg INT_VEC, abs=0xffe0+4;
    .data _multi_isr ; PORT1_VECTOR
    .data _multi_isr ; PORT2_VECTOR
    .data _multi_isr ; unused
    .data _multi_isr ; unused
    .data _multi_isr ; unused
    .data _multi_isr ; unused
    .data _multi_ist ; TIMERA1_VECTOR

    now TIMER_A0 vector will come, but we have a real ISR for it. Go on after it...

    .pseg INT_VEC, abs=0xffe0+20
    .data _multi_ist ; WDT_VECTOR
    ...

  • Jens-Michael Gross said:

    Portability means: Algorithms. Manipulation of memory locations. Not fancy things like processor registers (C doesn't know about processor registers and doesn't even require something like a stack), internal hardware (try to port a C program that uses the USCI to an Atmel processor. Even if the program compiles, the memory locations you write to or read form surely won't behave like they were an USCI - not C's fault) or the very weird fact that code execution sometimes suddenly jumps from the current point of execution to somewhere completely else, and then back again.
    Things like that can only be done by implementing additional functionality that is not part of C but understood by the compiler. And this is not standardized and therefore not portable. Only by coincidence :)

    I agree to most extent, but I would respectfully beg to differ that it's impossible for certain low-level hardware features to be "portable". Sure, maybe not between different processor families or even different models in the same families, but C/C++ has many features that reduce the need for vendor-specific trickery.

    For example, with your USCI example, you can have a memory-mapped model of the device registers in C structs or C++ classes, eg

    class UART_device {
    public:
    void toBuffer(unsigned char);
    unsigned char fromBuffer(void);
    void setBaud(uint32_t baud);
    // etc etc
    private:
    volatile uint16_t UART_CTL;
    volatile uint8_t UART_TXBUF;
    volatile uint8_t UART_RXBUF;
    // and so on
    }

    and if you do a

    UART_device &uart1 = *reinterpret_cast<UART_device *>(__UART_ADDR__);

    the whole thing maps nicely onto the whole UART register space. This will compile on any decent embedded C++ compiler, and it does not require non-standard pragmas or linker directives etc; you only need to change the device register address, the order/number of registers, and fill out the member methods.

    Of course I've left out lots and lots of implementation details (packing and alignment? what's that?) but I hope you see my point. =)

    Tony

  • TonyKao said:
    C/C++ has many features that reduce the need for vendor-specific trickery.

    No. All those features, whether you use classes (whcih are rather ineffective) or libraries or whatever, require of course individual code for the different hardware.

    What you described will only work if there is just a different name/memory location for the register. But if the meaning of the registes, or the way how to operate the hardware, is completely different? See how USCI and USI work for I2C or SPI. And I2C on an USART is completely different again.
    Being portable does not mean 'using USCI 2 instead of USCI1'.

    USCI on 2x family chares the RX vector for USCIA and USCIB, same for the TX vector. On 5x family, USCIA has its own RXTX vector, as has USCIB. So when going from 2x to 4x family, you'll have to change the code. you simpl ycannot write it in away that the code written for the 2x family will work on 5x family by just recompiling it.

    Sure you can write a library with the low-leel stuff, and dependign on used family, one or other code is linked, while your program only accesses a high-level, virtualized UART.

    If using an OS that provides streams like STDOUT, then your code can be portable, as it does not use anything processor specific. But then the OS, or the drivers it uses, or whatever, is the code that is not fully portable then. And on a microcontroller, you useally don't have an OS and then load your program. If an OS is used at all, it is compiled and linked together with the application.

    On my own projects, I use things like printf, putch etc. So the high-level code is portable (and I use it on different MSPs and also an ATMega128). However, the uart.c that does the actual programming of the UART, USART, USCI, whatever, is different for each target. And individually written. And while my code would be directly work on any platform that provides a putchar function that does the low-level transfer, it won't if there are only a few hardware registers that require individual attention.

    You can go for any level of abstraction you like, but on the very bottom, individual, non-portable code is required for the low-level hardware access.
    And each level of abstraction increases the overhead and slows things down. Which is widely ignored on high-end systems, but is an issue that disqualifies this approach on microcontrollers.

    Microcontroller code should be bug-free (or the applicaiton is crap), small (at least to the point that it fits into the target), fast (so it does the job without need of a faster processor) and nice (object-oriented, structured, high abstraction, portable). In this order.
    Unfortunately, if people ever manage to get the first three goals, the fourth is considered unimportant. Those, who start with the fourth usually discover that they are unable to reach the other three (in time or at all).

  • the .extern tells the assembler that there somehwere exists a symbold named _multi_isr. (the leading underline is auto-added by C to avoid name space conflicts between C and assembly functions)

    The .pseg tells teh assembler that the following data goes into the program segment INT_VEC (which is known by the linker through a device specific linker script file, defining the position and size of the interrupt vector table), at the absolute position of 0xffe4. The following lines jsut define binary data which starts at 0xffe4 and contains a value which i slater to be resolved by the linker (the position of your multi_isr function)

    It's jsu tnot clear how you mix the 'real' ISR into this construct then, as their position is already occupied by a dummy entry.
    Likely, you'll have to end the list when you come to the vector position that later holds a real ISR address, and then start another pseg with an offset that points behind that vector position.

    e.g.

    .pseg INT_VEC, abs=0xffe0+4;
    .data _multi_isr ; PORT1_VECTOR
    .data _multi_isr ; PORT2_VECTOR
    .data _multi_isr ; unused
    .data _multi_isr ; unused
    .data _multi_isr ; unused
    .data _multi_isr ; unused
    .data _multi_ist ; TIMERA1_VECTOR

    now TIMER_A0 vector will come, but we have a real ISR for it. Go on after it...

    .pseg INT_VEC, abs=0xffe0+20
    .data _multi_ist ; WDT_VECTOR
    ...

     

    Thank you! This explanation is very helpful.

  • Hi Jens-Michael,

    I very much appreciate you taking the time to write such a thoughtful and detailed reply; I hope I'm not needlessly wasting your time!

    Again, I agree with your assessment of what constitutes good, lean embedded programming, as well as the nature and necessity of low-level microcontroller code. However, I do disagree with parts of your analysis on what I posted.

    On your comparison of USCI, USI, and USART, my point was not that I could compile the code and they would run perfectly on different platforms by magic. On the contrary, I specifically mentioned that they will likely not work on different processor families or even different models within the same family. However, since the original discussion was about linker directives, and your previous remark seemed to imply that low-level microcontroller programming can only work by relying on such "non-portable" (in the C sense) vendor features, I wanted to illustrate that such may not be the case.

    Of course, different peripherals require different low-level code to configure and manage the hardware details, but that applies equally to C, C++ or even assembly. My point was that low-level need not equal to "non-portable", in the sense that it need not only rely on vendor extensions; there are facilities in the languages that reduce the dependency on such extensions.

    As for your remark that "abstractions" necessitates overhead, respectfully I'm afraid that is just pure FUD against object oriented programming. Sure, if you code a C++ class with multiple inheritance and virtual functions, then allocate dynamically into a STL container, then yes the overhead will be huge to say the least. But can't the same be said of C code that unnecessarily use malloc() or the full stdlib?

    Take a partial, incomplete but compilable snippet from a working code:

    typedef uint16_t volatile device_register;
    class TimerA {
    public:
    void pwm1(uint16_t period, uint16_t dutycycle) {
    TACCTL[1] = OUTMOD_7;
    TACCR[0] = period;
    TACCR[1] = dutycycle;
    TACTL = TASSEL_1 + MC_1;
    }
    explicit TimerA() {
    TACTL = 0;
    TAR = 0;
    }
    private:
    device_register TACTL;
    device_register TACCTL[7]; // regs 3-7 fillers for Timer_A3
    device_register TAR;
    device_register TACCR[7]; // last 3-7 fillers for Timer_A3
    device_register TAEX0;
    };

    If I make this call in the main

    TimerA & timer_a0 = *new ((void*)_TA0_ADDR_) TimerA;
    timer_a0.pwm1(0xffff, 1024);

    The entire code translates to this in assembly (using IAR with -O3s)

     MOV.W #0x0, &0x160
    MOV.W #0x0, &0x170
    MOV.W #0xe0, &0x164
    MOV.W #0xffff, &0x172
    MOV.W #0x400, &0x174
    MOV.W #0x110, &0x160

    I would argue that's not too shabby for OOP. Generally in C++ you don't pay for what you don't use, and things like classes, polymorphism, inheritance, etc are as efficient as their C equivalent, sometimes even more so. Of course there are exceptions (har har), but nearly all embedded C++ tools offer you settings to avoid unnecessary code bloat.

    Tony

    PS: Asa, so very very sorry for derailing your thread...

  • TonyKao said:

    (lots of fascinating code discussion)

    Tony

    PS: Asa, so very very sorry for derailing your thread...

    Hey no problem! At least you're nearby...maybe you'll suddenly think of something that might help!

     

    Asa

  • TonyKao said:
    Of course, different peripherals require different low-level code to configure and manage the hardware details, but that applies equally to C, C++ or even assembly. My point was that low-level need not equal to "non-portable", in the sense that it need not only rely on vendor extensions; there are facilities in the languages that reduce the dependency on such extensions.

    Meybe we're talkign abotu the same but puttign it into a different drawer.
    Portable code is code that runs on different platforms without any change.
    An applicaiton may be portable if it uses library funcitons that are equally availabel on all platforms. However, the liraries themselves are non-portable (or hand-ported) code. necessarily.

    I can do a library or class that provides 'send baudrate', 'send data' and 'receive data'. And code using this class or libray will be portable. The class or library itself, however, won't. And if the code for the library functions or the class are part of the project, the whole project isn't portable anymore. And this is the typical case for Microcontroller projects, since there is no generic API to an OS (such as INT13 on DOS, which was still available in windows, OS/2 and many emulators, or like the CONIO library that provides the conneciton to the different PC OS types and is provided with the compiler for this SO). So Microcontroller projects are usually non-potable or only partially portable. While parts of the code well may be.
    However, one layer of abstraction between portable and non-porable parts, is a good thing (TM)

    TonyKao said:
    As for your remark that "abstractions" necessitates overhead, respectfully I'm afraid that is just pure FUD against object oriented programming.

    I did a good share of OOP myself. And it has its uses. But using it just to use it is a bad idea (TM).

    Once, my company paid some 'experts' for a base version of our energy meterign software. They did it heavily object-oriented:

    All integer values were value objects. Passing those value objects from one part of the application to another was done by creating a virtual XML file, and the recippient then was parsing the XML file into a value object, which then was used by the recipient as an integer value in some math. Great. With two or three metering devices attached, the high-end PC was almost at its end.
    The rewritten, heavily dis-objected version handles >500 devices and also the Flex client for visualization. On the same hardware.
    The Flex client is doen in OOP too, but to an extent that it again costs way more than it gives. Sometimes, I have to pass events through long chains of objects because I cannot directly attach to the object of interest, as it is hidden by a large nubmer of other objects in-between. I rewrote large parts of this client too, speeding it up by some 100%.

    My paradigm is: use things where you benefit from them, and don't use them if it costs more than it gives. But never use them just because someone says that this is how things have to be done.

    TonyKao said:
    But can't the same be said of C code that unnecessarily use malloc() or the full stdlib

    I don't disagree. Using malloc is some sort of poor mans object orientation. The returned pointer is an instance pointer to a memory object. Just that it is not encapulated into its own namespace, and its whole class content is a public byte array of a certain size.

**Attention** This is a public forum