UC3A Interrupts and EVBA

Go To Last Post
15 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi there.

I am getting into C programing UC3A. I come from an assembler background and so like to know whats actually going on "behind the scenes" - part of me has a deep mistrust of "high level" programming langages - too much smoke and mirrors. Anyway, I am trying to set up a TC interrupt with interrupt service routines written in C but the vector table defined in assembler. I understand that one must set up the EVBA and then the offset for each interrupt group. But how do you assign the EVBA. Also, how do you avoid memory allocation overlap between the memory containg the vector addresses and any memory used by the C part of the program - do the org stuff first and then the Type deffinitions second?

On another note - I can absolutley see the worth of using C - but with fully assembler programs you absolutley know where you are and have no one to blame but yourself if thing go wrong. I think that all the Atmel "routines" which are available make programmers lazy and encorage "programming without understanding" which has to be a bad thing - hence my efforts to programe in a sudo assembler way and then move to full C code.

Anyway, that's enough ranting for now. Any answers to the problem would be appriciated.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The interrupt vectors need to be organised in a special way, described in the AVR 32-bit Architecture Manual and/or the AVR32UC Technical Reference Manual. If you create a project and add the INTC driver from the ASF, there’ll be a file exception.x that you can have a look at. With that assembler file the ISR code will end up somewhere in the application Flash memory area without overlapping other code. Before using any interrupts the EVBA register must be loaded with the start address of that interrupt hander structure, possibly like this: Set_system_register(AVR32_EVBA, (uint32_t)&_evba );
Assuming that _evba is a global label placed at the start of the interrupt vectors.

As for C vs. assembly:

I don’t really like most of the ASF functions because they tend to be rather slow with loads of safety checks and I find them rather cumbersome to use, with loads of weird not-really-well-documented option structures as arguments. They’re probably good if you don’t want to bother with the inner workings of the MCU, need highly portable code for different AVR32 models or want to regularly update the ASF to make use of its newer features.

Before starting with AVR32 I had a bit of an Intel x86 assembly background, but I decided to avoid programming in assembly because the AVR32 has so many features, settings and registers that writing all that in assembly would take way too long. You don’t really want to re-implement all that USB stuff in assembly... There’s also a bunch of conventions related to argument passing, function intros and outtros so that it’s easy to shoot your own foot.

So I chose the third option: I program everything in C but avoid the ASF most of the time. Instead, I access the MCU’s registers directly.

This is the ASF function to set a GPIO pin:

void gpio_set_gpio_pin(unsigned int pin)
{
  volatile avr32_gpio_port_t *gpio_port = &GPIO.port[pin >> 5];

  gpio_port->ovrs  = 1 << (pin & 0x1F); // Value to be driven on the I/O line: 1.
  gpio_port->oders = 1 << (pin & 0x1F); // The GPIO output driver is enabled for that pin.
  gpio_port->gpers = 1 << (pin & 0x1F); // The GPIO module controls that pin.
}
//ThisStupidLineKeepsTheForumFromBreakingTheLinesAboveJustIgnoreItBlankBlankBlankBlankBlank

Contains everything you need to drive the pin, but rather slow if the pin’s configured already and you quickly need it to change state. Instead, I call that function once (or the pin clear one) during the initialisation phase to configure it, but later I use a simple macro:

#define PIN_SET(pin) \
  AVR32_GPIO.port[(pin) >> 5].ovrs  = 1 << ((pin) & 0x1F);

I also didn’t use the weird interrupt controller driver from the ASF, which is easy to use and good if you often need to change the interrupt handlers for a certain interrupt, but it’s quite inefficient and easily confuses you about the way the MCU’s interrupt controller really works.

By accessing registers directly you should be able to gain a good understanding of the inner workings of the MCU, without too many oddities introduced by the ASF. At the same time you’ll be a lot faster in writing your code, which will probably also be easier to debug and less prone to errors.

Edit: Long comment line to avoid line breaks in the code finally works.

Last Edited: Mon. Aug 27, 2012 - 02:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

RDEngineer wrote:
But how do you assign the EVBA.
There's AVR32_EVBA system register that has to been set. In C you can set it in this way
__builtin_mtsr(AVR32_EVBA, &my_evba);

Where __builtin_mtsr is avr32-gcc built in function for accessing system level register. my_evba is your defined and declared EVBA, for example in assembly code (.S file).

RDEngineer wrote:
Also, how do you avoid memory allocation overlap between the memory containg the vector addresses and any memory used by the C part of the program
Autovectors have to be aligned at least with four bytes between them to provide enough space for a jump instruction to the handler routine. Some more critical vectors may have more space between them so that the whole handler can be implemented in that space. See datasheet p. 29 for Exceptions and Interrupts.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

catweax wrote:
I don’t really like most of the ASF functions because they tend to be rather slow with loads of safety checks and I find them rather cumbersome to use, with loads of weird not-really-well-documented option structures as arguments.
Amen. :wink:

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks catweax

I have been getting to grips with the UC3A for about 3 months now and have come to the same conclusions as you. I do port pin setting etc in the same way. I still like to think of the microcontroller in an "assembler" type way and as I said, Im programming in C but trying to fully understand what is going on in the microcontroller.

I agree, the ASF functions are way too complex when with a bit of thought, you can write much more elegant, simple and faster code.

It seems like Atmel are attempting to turn microcontrollers into PCs, which they are not. Maybe they are trying to make them accessable to a wider audience.

Who knows

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kblomqvist wrote:
catweax wrote:
I don’t really like most of the ASF functions because they tend to be rather slow with loads of safety checks and I find them rather cumbersome to use, with loads of weird not-really-well-documented option structures as arguments.
Amen. :wink:

+1 to this.

-Deven

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Very nice perspective, catweax!

Quote:
It seems like Atmel are attempting to turn microcontrollers into PCs, which they are not. Maybe they are trying to make them accessable to a wider audience.

Most of the kids coming out of school only know PC's, Windows, Linux, maybe Java. The ASF isn't a bad thing. Sometimes it needs a little tweaking. Sometimes, a lot of tweaking. The smoke and mirrors under the hood can then be frustrating.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

PeteAVR pm’ed me about my INTC driver optimisations, and without realising, my reply turned into quite a lengthy rant about AVR32 interrupt handling. This isn’t supposed to start a lengthy discussion about the ASF or flame against the ASF, and I also know that it might be too complicated or overkill. I just thought I’d share, just in case someone else is interested in this as well. Maybe it even offers some interrupt handling insights that the Atmel application notes don’t.

PeteAVR wrote:
catweax wrote:
I also didn’t use the weird interrupt controller driver from the ASF, which is easy to use and good if you often need to change the interrupt handlers for a certain interrupt, but it’s quite inefficient and easily confuses you about the way the MCU’s interrupt controller really works.
after I read this, I am curious what you wanted to optimize. I found that if you inline _get_interrupt_handler() into exception.S you can calc "AVR32_INTC_INT3 - int_level" in advance, but most of the other code looks pretty efficient.
A word of caution first: I’ve been using the ASF 1.7 with my old-ish project. I haven’t used newer ASF versions yet, so I don’t know how much of this still applies nowadays, but your mention of _get_interrupt_handler() leads me to believe that not much changed. Just keep that in mind.

Second note: All my examples are for the AT32UC3A3256, because that’s what I wrote my code for. The INTC should be similar or identical for all other AVR32s.

Now on to the main menu...

The thing is that the INTC has fully configurable interrupt groups. Every group has a priority and its own autovector (see registers IPR0...IPR63). The INTC decides in hardware which group currently is the one with the highest pending interrupt request and delivers the address of its handler (EVBA + autovector) to the CPU. So if the USB group has an interrupt pending, the CPU can jump directly to the USB interrupt handler.

Now while that sounds pretty fast, the INTC driver from the ASF actually discards most of the work the INTC hardware has already done. Instead, it registers interrupt handlers for all interrupts that are identical except for the priority parameter. With this priority parameter, _get_interrupt_handler() goes to query the ICR for the group that caused the interrupt of that priority and then also gets the individual interrupt line inside that group with some error checking.

Now you noticed that the ASF includes figuring out the interrupt line, not only the group. However, some interrupt groups, such as the USB one, only have one line and there’s nothing to figure out. Also, this whole thing is rather slow. While it also includes error checking, that’s only needed either during initial development or if you have careless or inexperienced users who might enable an interrupt source without registering a handler for it first.

So what the ASF does:

    * interrupt X triggers * ISR only knows the interrupt priority
    * call function (additional overhead)
    * use priority to get group
    * use group to get line
    * check if interrupt source suddenly vanished (rather pointless at this point)
    * use line to get C handler
    * call the C handler unless it’s NULL
    * handle interrupt in C function
For comparison: The ASF ISR’s have 4 assembly instructions and _get_interrupt_handler() compiled with -O3 has 15 instructions. Each periphery bus register access has at least 1 or 2 stall cycles. The function call takes at least 4 cycles. I’d estimate all that to take at least 25 cycles (best case configuration; cycle data from AVR32UC Technical Reference Manual), probably more. At 66 MHz that’s almost 0.4 μs. That’s a lot if you’re trying to catch a byte that flies past via USART in just 5 μs (2 Mbit) and you have to leave ~3 μs for uninterruptible code from other ISRs.

What can be done if there’s only one line in a group:

    * interrupt X triggers * ISR for X is called
    * handle interrupt in assembler or call C handler
That’s a bit shorter and sounds faster, doesn’t it?

My ISR for the TC0 looks like this:

isr_tc0:
bral    tc0_interrupt

A single assembly instruction and my C handler is ready to roll. If that isn’t a low latency, I don’t know what is.

Now there are groups that have more than one line, like the PDCA. I have an array in SRAM with function pointers to the individual handlers, all simply created with C code. Structure would be like this:

    * interrupt X triggers * ISR for X is called
    * get line from group X
    * get C handler for line
    * call the C handler
    * handle interrupt in C function
This is more like what the ASF does, but faster.

My ISR for the PDCA group looks like this:

isr_pdca:
mov     r8,0xFFFF090C   // address of PDCA group interrupt request register (IRR3)
ld.w    r11,r8[0x0]     // read interrupt line flags
brev    r11             // reverse register for easier counting
clz     r12,r11         // count leading zeros (count equivalent to number of first active interrupt line)
mov     r9,pdcaHandlers // load handler table
ld.w    pc,r9[r12<<0x2] // jump to handler, pass PDCA number as parameter in r12
/////////////////////////////////////////////////////////////////////////////////DearForumDontBreakMyLines

That’s just 6 assembly instructions for what the ASF does (sans error checking), and as a bonus, you get the PDCA number as a parameter to your C handler because it starts counting from the LSB of IRR3. I have two C handlers for several PDCAs, one for transmit and one for receive. They know what PDCA they’ve been called for by that parameter:

__attribute__((__interrupt__))
void pdca_rx_handler(uint32_t pdca_num) { /* code here */ }

__attribute__((__interrupt__))
void pdca_tx_handler(uint32_t pdca_num) { /* code here */ }

At about 0.1 μs, that’s 0.3 μs saved.

Now there are several interrupt groups that have more than one line, like PDCA, GPIO, EIC and more, so you’d have to write your own small handlers for all groups that you use.

However, if you do that, you can use the same technique for interrupt groups that have only one line but several causes. I use it for USARTs and the USBB. Here’s my USART ISR:

isr_usart2:
mov     r12,BUS2_NUM    // USART2 is bus 2, parameter is 1
mov     r8,0xFFFF1C00   // USART2
ld.w    r11,r8[0x10]    // IMR
ld.w    r10,r8[0x14]    // CSR
andal   r9,r10,r11      // mask interrupt flags
clz     r9,r9           // count leading zeros
mov     r8,usartHandlers// load handler table
ld.w    pc,r8[r9<<0x2]  // jump to handler, pass bus parameter in r12

This reads the USART’s CSR (Channel Status Register) and directly jumps to the individual handlers for TXEMPTY, TIMEOUT and whatever. The number parameter in r12 is there so that the called handler can tell which one of the two USARTs that I use it was called for.

Why do I do it like that? Without that CSR counting and handler-table-jumping, I’d have to check all relevant USART status bits in one long if/else if block, which would be rather slow. O(1) instead of O(n) is the way to go. ;-)

The USBB ISR looks similar, but it’s shorter because I know that the UDINT register won’t have any interrupt bits set that I don’t need. I can also prioritise causes with higher numbers (like USB DMA transfers) over ones with lower numbers by counting from the MSB of the register:

isr_usb:	
mov     r8,0xFFFE0004   // address of USB device global interrupt register
ld.w    r8,r8[0x0]      // read interrupt flags
clz     r8,r8           // count leading zeros
mov     r9,usbHandlers  // load handler table
ld.w    pc,r9[r8<<0x2]  // jump to handler

I did all this to reduce interrupt handling latencies as much as possible and the gain is about one order of magnitude (~10x). The numbers above sound like it isn't that much, but in practice there can be additional slowdowns due to concurrent access of the various DMA controllers on buses and SRAM, especially during burst accesses. The longer the interrupt handling, the higher the chances to collide with something. I needed this speed-up for my application, but some people might argue that I just should’ve chosen a faster CPU. Brawns over brains and all...

PeteAVR wrote:
What have you changed?
Nothing. Or everything. I threw out all the ASF stuff and wrote my own interrupt handling, as you can see. :D

Pro/Con of my version:
+ faster latencies (up to 0.5 μs, maybe more depending on MCU configuration)
+ usable for individual status bit handlers
+ can pass parameters to interrupt handlers
- needs some AVR32 assembly knowledge
- more complicated to maintain and takes longer to write
- some handlers can’t be changed during runtime
- less error checking, so it can crash if you’re not careful

Pro/Con of the ASF version:
+ doesn’t need any assembly knowledge
+ better to maintain and faster to write
+ all handlers can be changed during runtime (Does anyone actually do that after the init phase?)
+ a bit safer (can still crash easily, though)
- slower latencies
- needs cumbersome and even slower handlers with lots of if/else
- can’t pass parameters to interrupt handlers

Take your pick. I hope this answers your questions. Sorry for the lengthy rant. ;-)

Edit:
- register access delays apply to registers on periphery buses
- added missing interrupt attributes to example handlers
- added note about counting IRR3 LSB first

Edit 2:
- added a forgotten “isn’t”
- added a few missing commas

Last Edited: Wed. Nov 27, 2013 - 12:14 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi catweax, Just wanted to say thanks for the information. Trying to find this sort of stuff out at times can be almost impossible. For me personally I find that just reading the datasheets doesn't always give you a clue as to how you should start to use the hardware at a low level (in order to optimize as you have done). Having someone explain it as you have done is brilliant. Thanks again.

Regards.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yep, this was indeed high quality stuff. Catweax, thank you for sharing.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't think it is really necessary to use assembler for these, and the interrupt vector table does not even need to be placed in RAM. I use plain C without ASF for this, of course with customizing the linker script.

To the linker script I add the following:

I chop off a 16Kb area for interrupt routines from the end of my uc's flash in two parts. First a short area for the fixed exception entry points (264 bytes to include the supervisor call entry point, however that might be put in a separate larger section if desired to save the jump), then the rest in an another section for the interrupt handler bodies.

In the C code, I utilize the sections this way:

I build up an exception vector table in the first section mentioned above. This roughly follows the method presented in the Interrupt handling without ASF topic: https://www.avrfreaks.net/index.p... in that I use the LDDPC relative instruction in the table to fetch the entry points of the exception handlers (as unsigned integer constants hard-coded in the table). However instead of RAM, I hard-coded the entry points too in the empty positions of this table (and made the LDDPC instructions appropriately pointing to the respective handler offsets).

The second (almost 16Kb) section is used for the autovectored interrupt handler bodies. When defining an interrupt to be used this way, the compiler is just needed to be told about that the interrupt should be placed in the appropriate section (for example with an __attribute__((interrupt, section(.itsect))) prefix for the function declaration, '.itsect' being the section mentioned above).

After this the only things to do is to set the EVBA on startup appropriately by __builtin_mtsr, and later setting up the handlers as needed with the interrupt controller's IPR register (remembering to mask the high bits of the function pointers).

I don't think any lower latency is possible with plain C code :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In my solution the interrupt vector table isn’t placed in RAM either, but with the rest of the code.

Your idea sounds quite interesting and doesn’t need the additional jump from the assembly block to the C function elsewhere, thus saving a bit more time. The hard-coded integer constants and entry points don’t sound very flexible, though. I have a lot of debugging code that I toggle with #ifdef, so the interrupt handlers often significantly change in size. I prefer to call labels and let the linker take care of all the addressing issues for me.

In the end it boils down to speed vs. flexibility, I’d say.

More flexible: ASF > my solution > your solution
Greater speed: your solution > my solution > [the West Siberian plain fits here] > ASF

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

catweax wrote:
Greater speed: your solution > my solution > [the West Siberian plain fits here] > ASF

:D

A little misunderstood here, yes, my solution is quite rigid for the fixed table, but maybe not that much rigid.

The exception vector part (in Flash) stores only pointers to the exception vectors which themselves can be located anywhere (In the C code you simply set up the table like for example '(unsigned int)(&my_illegal_opcode_exception)' where you have an '__attribute((interrupt)) void my_illegal_opcode_exception(void);' exception handler somewhere, and so on), so they don't need to be in the 16Kb autovector area. For the autovectors I also don't think that 16Kb is a very limiting constraint: larger interrupt handlers will call other functions anyway which don't need to be there.

Of course it can definitely considered to be rigid that the path to the exception handlers are fixed in ROM, but is it generally really a good design approach to modify exception handlers during runtime?

In our case I came up with this idea since we are developing safety-critical applications. The AVR32 UC itself is currently not used in such scenario, just in a monitoring equipment, but anyway, it is always desirable to avoid placing more stuff affecting program flow and execution in RAM than necessary. This method accomplishes this well, probably to the greatest extent possible on the AVR32 regarding interrupt handling.

No problem, just promoting my approach :D

By the way where you point EVBA to? I don't get it. For me it seems you just have to put the 16Kb autovector area in RAM to avoid the rigidness of the exception handlers & supervisor call (We use UC3C parts which don't have an MMU - maybe with one you could mess a little around here). Otherwise if you have all of the stuff in ROM, it is pretty much the same. The tradeoff is that my code is faster, but at the cost of limiting the primary code of all autovectored interrupt handlers to a 16Kb section (an other limitation of my approach is that the C code containing the interrupt vector table must see all the exception handlers which may be messy to work with if one wants to build a nice modular design and wants to distribute these exception handlers across several modules).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

All my ISRs, like the isr_pdca that I mentioned in my long post, are in one assembly file that starts with a label called “_evba”. Thus, all ISRs are in one rather short continuous block somewhere in the code, usually somewhere at the end. Then I set EVBA to the address of “_evba”. The offsets for the IPR registers are calculated by subtracting “_evba” from the ISR addresses.

I took that idea from the ASF’s exception.x (or exception.S).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK, I checked the code, and now understand :) (It just was not in my mind that you could use a .balign directive to force the section of assembly code within a single 16Kb page to make it work reliably)