1
votes

Currently I'm trying to establish a communication between my PC and an ARM Cortex M0 over UART. The system is simple: The custom-made UART module has a one Byte buffer. If the buffer is full the interrupt flag is set. The interrupt handler gets called and calls the interrupt service routine (ISR) in return. The ISR reads the buffer (therefore the interrupt flag of the modules gets reset) and writes the obtained Byte into a global array.

Doing so I discovered the following problem: Once I send a character (=1 Byte), the system enters the ISR but never returns. I can interrupt the system again and again but it never returns to the main code. Now due to my setup with the ARM hardcore integrated on an Xilinx Spartan 6 FPGA which is build on a dev board debugging of the ARM code is difficult. I haven't yet figured out a way how to do. So I thought of trying to eliminate possible (theoretical) errors before starting the pain of setting up a new debugging toolchain. Therefore my post here.

I think that I misunderstood somehow the way how to treat interrupts on an ARM. Here is how I do it:

Interrupt Handler Code (in ASM):

; Jump Table
__Vectors   DCD __initial_sp
            DCD Reset_Handler
            DCD 0

            ...

            ; External Interrupts
            DCD UART_Handler

            ...

; Interrupt Handler
UART_Handler PROC
    EXPORT  UART_Handler
    IMPORT  UART_ISR

    PUSH    {R0,LR}     ; SAVE register state
    ; GPIO #1

    MOVS    R0, #1      ; MASK all interrupts
    MSR     PRIMASK, R0

    BL      UART_ISR    ; JUMP to ISR
    ; GPIO #4

    MOVS    R0, #0      ; ENABLE all interrupts
    MSR     PRIMASK, R0

    POP     {R0,PC}     ; RESTORE register state
    ENDP

Interrupt Service Routine Code (in C):

// Defined in the main routine
char buffer[129] __attribute__((aligned (4)));

// Global Variables
int uart_in_progress;
char* uart_ptr = buffer;
unsigned int uart_length;

void UART_ISR()
{   
    char inChar;

    // GPIO #2

    // Read Character out of Buffer (=reset interrupt flag)
    inChar= *(unsigned int*)APB_UART_BASE;

    if(uart_in_progress) {
        // end transmission for EOL or Max Length
        if(current_char == '\n' || uart_length == 129) {
            uart_in_progress = 0;
        }
        // add next character to Memory Buffer
        else {
            *(uart_ptr + uart_length) = current_char;
            uart_length++;
        }
    }

    // GPIO #3
    return;
}

After my understanding this code is sufficient to treat and respond the interrupt call. Using GPIOs I can easily see how the flag rises, the interrupt handler reacts (GPIO #1), the ISR starts (GPIO #2), the flag falls, the ISR ends (GPIO #3) and then never jumps back to the handler (missing GPIO #4). The Stack should be large enough. Using only the simulator of ARM Keil everything works as it should.

Having googled to find examples I sometimes came across an explicit definition for ISR as following:

__attribute__ ((__interrupt__)) void UART_ISR()

Is such a definition mandatory? Or are additional commands necessary to indicate to the ARM processor that the interrupt was treated? Looking at my complied asm code I just see jumps back and forth and do not understand if such a definition would add anything to these (correct) jumps.

Any advice is much appreciated. And again: I am well aware of the need of a correct debugger - would just be great if I can avoid the hassle.

2
Isn't the UART_Handler PROC lacking a return from interrupt instruction? Or is that taken care of with the final POP? Usually a RETI type instruction will restore the flags register too.Weather Vane
Could it be that your UART_ISR is corrupting the normal flow (corrupting the stack, for instance)? The __attribute__ ((__interrupt__)) should not be needed here, since the routine is a regular function just called from the actual ISREugene Sh.
@WeatherVane In the M-class architecture, exceptions place a magic value in the link register - loading that back into the PC triggers an exception return rather than a normal branch. BL is a returnable-from 'call' rather than a straight jump. ARM is downright weird if you're used to x86 ;)Notlikethat
BL is a correct instruction hereEugene Sh.
The simplest thing would be to remove all of the code except the GPIO and see if it is working. The other note is that you should declare the global vars used in ISR as volatileEugene Sh.

2 Answers

2
votes

I don't see you clearing the interrupt. Some UART peripherals require you to clear the interrupt or the interrupt will continue to cycle. Some will clear the interrupt automatically when the data register is read, though, so refer to the peripheral documentation. In my experience an uncleared interrupt is often the cause of a perpetually cycling interrupt.

Also, I'm unconvinced it's your problem but, you shouldn't need the

MOVS    R0, #1      ; MASK all interrupts
MSR     PRIMASK, R0

and

MOVS    R0, #0      ; ENABLE all interrupts
MSR     PRIMASK, R0

sections of code. You shouldn't have to mask any interrupts while in an interrupt. If you're executing from an interrupt context, having properly configured interrupt priorities will stop other interrupts from triggering.

Like others say, based on how you're using it, you shouldn't need the assembly code at all, just name your C function "UART_Handler".

0
votes

Try pushing R0-R3, LR into stack and popping all of that out before return. The function call to handle your interrupt will corrupt those registers.