Programming embedded without interrupts

786 views Asked by At

I never would have believed I could be in this position in 2017, but I have a target system (LPC2138) which absolutely refuses to handle interrupts, despite lots of trying on my part. For various reasons I do need to work with it, so it’s just a matter of getting on with it. The application is ‘interrupt friendly’, with several asynchronous I/O streams (SPI, UART) plus timer signals. The one thing in my favour is that the processor is very fast in comparison to my real time requirements, so I have plenty of spare grunt available.

The approach I’m stuck with is to do the whole thing in one big polled loop, that will include 3 FIFOs to handle I/O. On a quick glance it seems viable, does anyone have any comments based on experience?

The interrupt problem is not trivial, 100% platform-compatible hello world snippets straight off the web fail to work, when they run the system crashes in a scrambled state. If there does happen to be a clear, expert fix somewhere that someone knows about, a pointer would be greatly appreciated.

2

There are 2 answers

2
old_timer On

dont have a clue where you are stuck we need more info, but maybe a small skeleton will confirm you are at least doing these few things. have you done an arm7 before or is this the first time, or you are well versed in the arm7/arm world but just cant get the interrupts to work?

start.s

.globl _start
_start:

.globl _start
_start:
    ldr pc,reset_handler
    ldr pc,undefined_handler
    ldr pc,swi_handler
    ldr pc,prefetch_handler
    ldr pc,data_handler
    ldr pc,unused_handler
    ldr pc,irq_handler
    ldr pc,fiq_handler
reset_handler:      .word reset
undefined_handler:  .word hang
swi_handler:        .word hang
prefetch_handler:   .word hang
data_handler:       .word hang
unused_handler:     .word hang
irq_handler:        .word irq
fiq_handler:        .word hang

reset:
    ;@ (PSR_IRQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xD2
    msr cpsr_c,r0
    ldr sp,=0x40002000

    ;@ (PSR_FIQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xD1
    msr cpsr_c,r0
    ldr sp,=0x40003000

    ;@ (PSR_SVC_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
    mov r0,#0xD3
    msr cpsr_c,r0
    ldr sp,=0x40004000

    bl notmain
hang: b hang

.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

.globl dummy
dummy:
    bx lr

.globl enable_irq
enable_irq:
    mrs r0,cpsr
    bic r0,r0,#0x80
    msr cpsr_c,r0
    bx lr

irq:
    push {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
    bl c_irq_handler
    pop  {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
    subs pc,lr,#4

yes that is too many registers...only need the volatile ones the compiler covers the others.

notmain:

void c_irq_handler ( void )
{
}
void notmain ( void )
{
    unsigned int ra;
    for(ra=0;ra<100;ra++) dummy(ra);
}

flash.ld

MEMORY
{
    rom : ORIGIN = 0x00000000, LENGTH = 0x1000
    ram : ORIGIN = 0x40000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > rom
    .bss : { *(.bss*) } > ram
}

guess I didnt need .bss

build

arm-none-eabi-as start.s -o start.o
arm-none-eabi-gcc -O2 -c notmain.c -o notmain.o
arm-none-eabi-ld -T flash.ld start.o notmain.o -o so.elf
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy so.elf -O binary so.bin

always examine your so.list

they have an interesting way to determine if you have a flash there or if they should dump into their bootloader

Criterion for valid user code: The reserved ARM interrupt vector location (0x0000 0014) should contain the 2’s complement of the check-sum of the remaining interrupt vectors. This causes the checksum of all of the vectors together to be 0.

I didnt do that yet, can do that by hand or have a program come along after and do it programmatically.

change it to this

ldr pc,reset_handler
ldr pc,undefined_handler
ldr pc,swi_handler
ldr pc,prefetch_handler
ldr pc,data_handler
.word 0xb8a06f58  @ldr pc,unused_handler
ldr pc,irq_handler
ldr pc,fiq_handler

and that should work fine.

If this is completely useless to elementary then let me know will delete this answer no problem.

notmain.c

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );
void enable_irq ( void );

#define T0IR 0xE0004000
#define T0TCR 0xE0004004
#define T0PC  0xE0004010
#define T0MCR 0xE0004014
#define T0TC  0xE0004008
#define T0MCR0 0xE0004018

void c_irq_handler ( void )
{
    PUT32(T0IR,1);
}
void notmain ( void )
{
    PUT32(T0TCR,2);
    PUT32(T0TCR,0);
    PUT32(T0TC,0);
    PUT32(T0MCR0,0x100000);
    PUT32(T0MCR,0x1); //3);
    PUT32(T0TCR,1);
    while(1)
    {
        if(GET32(T0IR&1)) break;
    }
    PUT32(T0IR,1);

    PUT32(T0TCR,2);
    PUT32(T0TCR,0);
    PUT32(T0TC,0);
    PUT32(T0MCR0,0x100000);
    PUT32(T0MCR,0x1); //3);
    PUT32(T0IR,1);
    enable_irq();
    PUT32(T0TCR,1);
    while(1) continue;

}

just banged this out from the manual didnt check if there was a clock enable for the timer, etc. Personally I get the gpio up first, blink an led with a big counter loop

for(ra=0;ra<0x20000;ra++) dummy(ra);

then use a timer in a polling mode (just get it started free-running) to blink based on time from this can figure out the clock speed, that leads into the uart, get the uart, up, have a trivial routine for printing data

void hexstring ( unsigned int d )
{
    //unsigned int ra;
    unsigned int rb;
    unsigned int rc;

    rb=32;
    while(1)
    {
        rb-=4;
        rc=(d>>rb)&0xF;
        if(rc>9) rc+=0x37; else rc+=0x30;
        uart_send(rc);
        if(rb==0) break;
    }
    uart_send(0x0D);
    uart_send(0x0A);
}

octal is even easier but man is it hard to think in octal...

THEN finally the interrupt stuff, take code like the above and I would poll and print the various registers in the timer, would see if that interrupt register does fire in a polling mode first (second, third, fourth... without enabling interrupts to the processor (dont do that for a while).

Once I can see it at the peripheral level, which some dont work that way but assume this one does. Then in this case there is a VIC, and I assume this register

VICRawIntr 0xFFFFF008

should also be asserted if the timer interrupt is asserted and has fired. confirm it is (bit 4 it appears) confirm it goes away when the interrupt is cleared in the peripheral.

VICIntSelect resets to zero which is irq, that is what we want dont need to touch it for now.

I assume set bit 4 in VICIntEnable

then do the polling thing again printing out to see what is going on.

Now I would expect to see the interrupt show in VICIRQStatus (still completely polling do not enable the irq to the processor yet) and go away when the peripheral clears, or figure out how to clear it if the peripheral interrupt being cleared does not make it this far.

NOW it is time to enable the irq to the processor, and I personaly would shove a byte into the uart to see it pop out. or flick an led on or something.

in theory we just clear the periheral and return to safely return to the application.

I follow the same procedure no matter what processor it is mcu or full sized, etc. interrupts can be nightmares and the more code you write without testing the more likely to fail. sometimes one line of code per test is required. YMMV.

again if this is completely useless sorry, will delete. I think I have one/some 2148s but not a 2138, and am not going to order one just to write/test working code. been using arm since this ARMV7TDMI was out and becoming popular to the present armv8s which are far more painful. the pi-zero and such are quite fun as they are old school like this arm7...

I figure the timer would be the easier one to get a real interrupt from. another might be a gpio pin although I think that is more work but would tie a jumper between pins, make one an output the other an input with interrupt edge detection if this chip has it use the output gpio to change the state of the input in a clean manner, and go through the whole polling process of watching the interrupt hit each layer in turn and confirm you can remove the interrupt each time at each layer. then bang on the edge of the core, and then finally let it into the core.

0
Basya On

Without knowing your application and your target platform well, I can't give you a definitive answer!

But, you asked for comments based on experience. Here goes :-)

  1. You mention real-time requirements. Working without interrupts can actually help with that! When we did projects with hard real-time requirements, we did not use interrupts. Let's say we were handling the incoming data stream and had exactly 20 us to handle a word or we would miss the next one, and right in the middle of processing one word we were interrupted. Bang! Lost the next one. So we did a lot of polling. In a different application the design decisions could be different, using interrupts to handle time-critical work at the expense of non-real-time code, or some such. The philosophy in the shop I was working in then was very anti-interrupt.

  2. Polling may "waste" some resources (not really waste, since you have to do it :-) ). But you mention that your processor is fast enough. If you can meet your speed requirements, enjoy polling. It is easier to handle than interrupts, in a lot of ways. Your program has more control over what will happen when.

  3. FIFOs are good. They soften your real-time requirements.

  4. I don't know your hw design at all, but if you have a logic chip there and a flexible hw engineer (OK, that's an oxymoron :-) ) you can route some of your inputs and so on through the hw and use hw logic to handle some of your simple requirements, and present you an interface to make writing the program easier (could be some kind of FIFO optimized for your specific needs, for example, or a register to poll that gives you several pieces of information in one go, etc.)

So, go for it! You'll learn a whole new approach that even has some advantages.