Interrupt latency

01 Mar 2011

Where can I find exactly how fast (in nanoseconds) the mbed (or LPC in fact) reacts to an interrupt ? Is it possible to optimize this in assembly. Can any port pin be used as NMI interrupt ?

02 Mar 2011

See Cortex-M3 Technical Reference Manual.

http://infocenter.arm.com/help/topic/com.arm.doc.ddi0337g/graphics/exception_entry_timing.svg

So in the general case the exception handler starts executing 12 cycles after the exception. If my math is right, at 100MHz that's around 120 nanoseconds.

Maybe if you tell us what you're trying to achieve we can help better.

02 Mar 2011

Hi Igor,

Well.. I had put the question up earlier in another form, just to test an idea I have:

What I would like to do is getting the mbed to emulate an (EP)ROM for an ancient 8048 console system. My idea was to generate an interrupt on the mbed from the RD, WR or PSEN signals of the 8048, latch the address bus and then lookup a byte value from a ram table in the mbed and put that into a bidirectional latch (HC646) so the 8048 reads that value. The 8048 runs at only 3.57 MHz which is divided internally by three to generate its master clock, a complete external memory access is done in 5 cycles, each cycle taking 838 ns if my math is correct :-)

Thing is of course: the 8048 will not wait until data is available, it just reads it in as the 5th cycle elapses. But when I can get the mbed to react to the interrupt in this 120 ns than it might be doable. Not in 'C' I guess but a small routine in assembler might do the trick.

Thanks for the pointer to the tech reference manual!

Gert

03 Mar 2011

C should be fine, especially if you don't use mbed library wrappers. Since the CPU automatically pushes scratch registers onto the stack, you don't need any assembly wrappers and your C code will be the fist thing executed when the control is transferred to handler. The ARM compiler used by the online IDE is very good, I honestly doubt you will gain anything by writing in assembler, as long as you don't use C++ objects or other heavy stuff. Fetching a byte from an array in memory can be done only in so many ways.

12 Apr 2011

Well, this is what I am trying to do, mind though I have not tested this as I can imagine the busin/busout routines be too slow for this. I hoped that I had 2.5 usec to go but I only can respond to the RD/WR or PSEN signals which are 1 usec wide..

#include "mbed.h"
// hypothetical ROM/RAM simulator by mbed for 8048 CPU
// designed by Gert van der Knokke (c)2011


// 8 bit bus in/out to HC573 latches
// pins are selected to form a contiguous 8 bit bus on port
BusInOut mybus (p30,p29,p8,p7,p6,p5,p28,p27);

// latch select bits (decoded externally by 2-4 decoder)
// p23 and p24 select latch 0-3 and p25 disables all outputs when '1'
BusOut selectbus (p23,p24,p25);

// interrupt pin is driven by falling edge of either RD/WR or PSEN (logic and-ed externally)
InterruptIn myINT(p21);

#define ROMSIZE 8192
#define RAMSIZE 1024

char rom[ROMSIZE]; // to be filled with 8048 rom code
char ram[RAMSIZE]; // simulated ram space for 8048

#define PSEN    0x06    // bit 0 low
#define RD      0x05    // bit 1 low
#define WR      0x03    // bit 2 low

// the interrupt routine which has to respond to each and every RD/WR or PSEN signal
// total time available is about 1 usec
void interrupt_routine()
{
        mybus.input();  // switch to input mode
        selectbus=2;    // select latch 2 (control bit latch)
        control=mybus;  // read 8048 control bits (RD/WR/PSEN)
        selectbus=1;    // select latch 1 (adresbus high byte)
        adresh=mybus;   // read value
        selectbus=0;    // select latch 0 (adresbus low byte)
        adresl=mybus(); // read value
        
        switch (control)
        {
            case PSEN:
                        selectbus=4;    // disable all outputs of the latches
                        myLE=0;         // set LE of data output latch low
                        mybus.output(); // switch mybus to output mode
                        mybus=rom[adresh<<8+adresl]; // write rom byte in output latch
                        myLE=1;         // toggle output latch enable
                        break;
            case RD:
                        selectbus=4;    // disable all outputs of the latches
                        myLE=0;         // set LE of output latch low
                        mybus.output(); // switch mybus to output mode
                        mybus=ram[adresh<<8+adresl]; // write ram byte in output latch
                        myLE=1;         // toggle latch enable
                        break;
            case WR:
                        selectbus=3;    // select data input latch
                        ram[adresh<<8+adresl]=mybus; // read byte and store in ram
                        break;
        }            
}

int main() {
    myINT.fall(interrupt_routine);

    while(1) {
        // other stuff 
    }
}
13 Apr 2011

Here's a version with PortInOut which should be pretty fast. I moved some pins around to make use of ports.

// 8 bit bus in/out to HC573 latches
// pins are selected to form a contiguous 8 bit bus on port
// p30,p29,p8,p7,p6,p5,p28,p27 -> P0.4 .. P0.11
#define PORT0MASK 0x00000FF0
PortInOut mybus(Port1, PORT0MASK);

inline uint8_t read_bus()
{
  return mybus.read() >> 4;
}

inline void write_bus(uint8_t data)
{
  return mybus.write(data << 4);
}

// latch select bits (decoded externally by 2-4 decoder)
// p26, p25, p24 -> P2.0 .. P2.2
#define PORT2MASK 0x00000007
// p26 and p25 select latch 0-3 and p24 disables all outputs when '1'
PortOut selectbus(Port2, PORT2MASK);

DigitalOut myLE(p23); // latch enable

// interrupt pin is driven by falling edge of either RD/WR or PSEN (logic and-ed externally)
InterruptIn myINT(p21);

#define ROMSIZE 8192
#define RAMSIZE 1024

char rom[ROMSIZE]; // to be filled with 8048 rom code
char ram[RAMSIZE]; // simulated ram space for 8048

#define PSEN    0x06    // bit 0 low
#define RD      0x05    // bit 1 low
#define WR      0x03    // bit 2 low

uint32_t control;
uint32_t adresh;
uint32_t adresl;

// the interrupt routine which has to respond to each and every RD/WR or PSEN signal
// total time available is about 1 usec
void interrupt_routine()
{
        mybus.input();      // switch to input mode
        selectbus=2;        // select latch 2 (control bit latch)
        control=read_bus(); // read 8048 control bits (RD/WR/PSEN)
        selectbus=1;        // select latch 1 (adresbus high byte)
        adresh=read_bus();  // read value
        selectbus=0;        // select latch 0 (adresbus low byte)
        adresl=read_bus();  // read value
        
        switch (control)
        {
            case PSEN:
                        selectbus=4;    // disable all outputs of the latches
                        myLE=0;         // set LE of data output latch low
                        mybus.output(); // switch mybus to output mode
                        write_bus(rom[(adresh<<8) | adresl]); // write rom byte in output latch
                        myLE=1;         // toggle output latch enable
                        break;
            case RD:
                        selectbus=4;    // disable all outputs of the latches
                        myLE=0;         // set LE of output latch low
                        mybus.output(); // switch mybus to output mode
                        write_bus(ram[(adresh<<8) | adresl]); // write ram byte in output latch
                        myLE=1;         // toggle latch enable
                        break;
            case WR:
                        selectbus=3;    // select data input latch
                        ram[adresh<<8+adresl] = read_bus(); // read byte and store in ram
                        break;
        }            
}

Btw, I found some graphs Sylvain did: http://sylvain.azarian.org/doku.php?id=mbed

14 Apr 2011

Hi Igor,

Thanks for modifying the code. For now I am waiting for my second Hitex protoboard to arrive to test the program in 'real life'. Furthermore I can sacrifice more port pins by for example hooking up the RD/WR/PSEN lines directly to the mbed and have them interrupt individually and use three interrupt routines.

Gert

08 Dec 2011

Well, some time ago I mentioned the above. Last weekend I tried to implement the mbed as rom emulator. The good news: It works The bad news: it does not work from an interrupt...

I connected the mbed almost directly to the 8048 bus, using about every IO pin available. D0-D7 (the Databus) is connected to 8 consecutive bits on port P0.4-P0.11

The lower 6 Addressbus bits A0-A5 are on P2.0-P2.5 the next 4 bits A6-A9 are on P0.15-P0.18 and finally A10 and A11 are on P1.30 and P1.31

nPSEN should be triggering the interrupt on the falling edge, I then have 1 usec to switch the bus to output and generate a databyte onto the databus, as soon as nPSEN rises again the databus is released.

This is (so far) my most optimized coding attempt by using direct addressing to set the port pins.

volatile uint32_t *myport0= (volatile uint32_t *) 0x2009c000;
volatile uint32_t *myport1= (volatile uint32_t *) 0x2009c020;
volatile uint32_t *myport2= (volatile uint32_t *) 0x2009c040;

// endless loop
    while (1) {
        // wait for PSEN to drop
        while (*(myport0+5) & 2);

        // set databus to output
        *(myport0) |= DataBusMask;
        
        // connect A0-A5 (P2.0-P2.5) A6-A9 (P0.15-P0.18) and A10/A11 (P1.30/P1.31) together
        // and use that as address pointer (offset)
        ad=(*(myport2+5) & 0x0000003FL) | ((*(myport0+5) & 0x00078000L)>>9) | ((*(myport1+5) & 0xC0000000L)>>20);

        // now emit the appropriate data byte from testrom
        *(myport0+7)=0x00000FF0L;          // clear all 8 data bits of port0
        *(myport0+6)=(*(testrom+ad))<<4;    // set the 1 bits of byte on port0

   
        // now wait for PSEN to rise again
        while (!(*(myport0+5) & 2));

        // set databus to input again
        *(myport0) &= ~DataBusMask;
    }

If I implement the code above as interrupt routine, starting on the falling edge of PSEN, it is too slow. It seems it takes too long for the interrupt routine to start. The code above is fast enough (according to my oscilloscope). The 8048 runs fine on the generated bytes and does not miss a beat.

I had hoped the remaining time after PSEN rises (1.5 usec) could be used for other routines on the mbed or maybe even use a second interrupt on the rising edge to release the bus freeing even more cycles.

Any ideas how to speed up the interrupt response ?

08 Dec 2011

Here is an image of the response in the closed loop version of the code:

/media/uploads/gertk/_scaled_mbed_psen_and_data_line.jpg

The top line is PSEN, the bottom line is a data line. On the left you can see how a zero bit is emitted after PSEN drops, on the right a one bit is emitted. The 8048 reads the databus on the rising edge of PSEN. The slow rise of the data line is when the databus is released by the mbed.