eLua vs Lua on RTOS

classic Classic list List threaded Threaded
15 messages Options
Lwazi Dub Lwazi Dub
Reply | Threaded
Open this post in threaded view
|

eLua vs Lua on RTOS

Hi everyone,

I am not a Lua programmer but I find this project interesting. Why
would one use eLua instead of running their Lua app on an RTOS?
Does/Will eLua have threads that run directly on cpu cores? I have a
thousand other questions and thoughts but I will leave that for
another thread maybe.

Thanks,

Lwazi
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
jbsnyder jbsnyder
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

On Tue, Jan 25, 2011 at 12:11 PM, Lwazi Dub <[hidden email]> wrote:
> Hi everyone,
>
> I am not a Lua programmer but I find this project interesting. Why
> would one use eLua instead of running their Lua app on an RTOS?
> Does/Will eLua have threads that run directly on cpu cores? I have a
> thousand other questions and thoughts but I will leave that for
> another thread maybe.

Most of the platforms we currently run on are single core
microcontrollers.  There are a few multicore or multithreaded
microcontrollers out there like XMOS or the Propeller, but at least
for the former, not enough RAM & Flash are provided to make it really
usable with eLua.  eLua is currently written to run directly on a
single core, kindof as its own operating system.  We've looked into
some concurrency models, but at the moment if you were to port it to a
multi-core platform or run it on multi-core x86, it won't use more
than one core.  It would be possible, but it would likely require
multiple Lua states running separately and concurrently on different
cores with some sort of message passing interface between the
instances.

For the microcontroller platforms we run on, an RTOS would generally
just add overhead.  In our upcoming release we will support basic
concurrency in more of an event-driven manner using interrupts which
are

>
> Thanks,
>
> Lwazi
> _______________________________________________
> eLua-dev mailing list
> [hidden email]
> https://lists.berlios.de/mailman/listinfo/elua-dev
>



--
James Snyder
Biomedical Engineering
Northwestern University
[hidden email]
PGP: http://fanplastic.org/key.txt
Phone: (847) 448-0386
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
jbsnyder jbsnyder
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

On Tue, Jan 25, 2011 at 1:25 PM, James Snyder <[hidden email]> wrote:

> Most of the platforms we currently run on are single core
> microcontrollers.  There are a few multicore or multithreaded
> microcontrollers out there like XMOS or the Propeller, but at least
> for the former, not enough RAM & Flash are provided to make it really
> usable with eLua.  eLua is currently written to run directly on a
> single core, kindof as its own operating system.  We've looked into
> some concurrency models, but at the moment if you were to port it to a
> multi-core platform or run it on multi-core x86, it won't use more
> than one core.  It would be possible, but it would likely require
> multiple Lua states running separately and concurrently on different
> cores with some sort of message passing interface between the
> instances.
>
> For the microcontroller platforms we run on, an RTOS would generally
> just add overhead.  In our upcoming release we will support basic
> concurrency in more of an event-driven manner using interrupts which
> are

Ooops.. hit the send button when I was going to save this and finish
editing later.

To finish:
The interrupts support both Lua and C handlers, C handlers execute
immediately and Lua handlers can interrupt currently running Lua code,
but they have to wait a few instructions before execution (so latency
isn't going to be constant or really low).

--
James Snyder
Biomedical Engineering
Northwestern University
[hidden email]
PGP: http://fanplastic.org/key.txt
Phone: (847) 448-0386
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Lwazi Dub Lwazi Dub
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

On Tue, Jan 25, 2011 at 2:27 PM, James Snyder <[hidden email]> wrote:

> On Tue, Jan 25, 2011 at 1:25 PM, James Snyder <[hidden email]> wrote:
>>
>> For the microcontroller platforms we run on, an RTOS would generally
>> just add overhead.  In our upcoming release we will support basic
>> concurrency in more of an event-driven manner using interrupts which
>> are
>
> Ooops.. hit the send button when I was going to save this and finish
> editing later.
>
> To finish:
> The interrupts support both Lua and C handlers, C handlers execute
> immediately and Lua handlers can interrupt currently running Lua code,
> but they have to wait a few instructions before execution (so latency
> isn't going to be constant or really low).
>
I like this. Is this documented anywhere? What happens to the rest of
the system during this 'wait'? I presume the ISR and other interrupts
do not get blocked while the Lua handler waits to run. And I have to
ask if there is a guarantee that the Lua handler will run?

Thanks,
Lwazi
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Dado Sutter Dado Sutter
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

Hello guys,

On Tue, Jan 25, 2011 at 19:10, Lwazi <[hidden email]> wrote:
On Tue, Jan 25, 2011 at 2:27 PM, James Snyder <[hidden email]> wrote:
> On Tue, Jan 25, 2011 at 1:25 PM, James Snyder <[hidden email]> wrote:
>>
>> For the microcontroller platforms we run on, an RTOS would generally
>> just add overhead.  In our upcoming release we will support basic
>> concurrency in more of an event-driven manner using interrupts which
>> are
>
> Ooops.. hit the send button when I was going to save this and finish
> editing later.
>
> To finish:
> The interrupts support both Lua and C handlers, C handlers execute
> immediately and Lua handlers can interrupt currently running Lua code,
> but they have to wait a few instructions before execution (so latency
> isn't going to be constant or really low).
>
I like this. Is this documented anywhere?

Yes, this is new eLua feature and the initial doc will be release with v0.8
LPC24xx implementatin can be seen on trunk (or branch pre0.8) at  src/platform/lpc24xx

What happens to the rest of
the system during this 'wait'?

You mean, the aparent-latency wait until we actually handle the INT in Lua ? The Lua VM is executing during this. I think the hook checks for any queued INT to handle every 2 or 3 VM bytecodes.
 
I presume the ISR and other interrupts
do not get blocked while the Lua handler waits to run. 
And I have to
ask if there is a guarantee that the Lua handler will run

   As a preview of the doc to come, I'll paste here some infos about the Interrupt mechanism v0.8 will bring officially to eLua.
   Comments and reviews are welcomed.
   (and yes, the doc edition/refinement by the community will be easier in the future :)

Best
Dado

eLua interrupt handlers

Starting with version 0.8, eLua supports interrupt handlers written in Lua. Once an interrupt handler is set in the Lua code, it will be called each time a supported interrupt is generated. A supported interrupt is any interrupt that is handled by the platform C code (see here for more details).

IMPORTANT: before learning how to use interrupt handlers in Lua, please keep in mind that Lua interrupt handlers don’t work the same way as regular (C) interrupt handlers. As Lua doesn’t have direct suport for interrupts, they have to be emulated. eLua emulates them using a queue that is populated with interrupt data by the C support code. As long as the queue is not empty, a Lua hook is set to run every 2 Lua bytecode instructions. This hook function is the Lua interrupt handler. After all the interrupts are handled and the queue is emptied, the hook is automatically disabled. Consequently:

  • When the interrupt queue is full (a situation that might appear when interrupts are added to the queue faster than the Lua code can handle them) subsequent interrupts are ignored (not added to the queue) and an error message is printed on the eLua console device. The interrupt queue size can be configured at build time, as explained here. Even if the interrupt queue is large, one most remember that Lua code is significantly slower than C code, thus not all C interrupts make suitable candidates for Lua interrupt handlers. For example, a serial interrupt that is generated each time a char is received at 115200 baud might be too fast for Lua (this is largely dependent on the platform). On the other hand, a GPIO interrupt-on-change on a GPIO line connected with a matrix keyboard is a very good candidate for a Lua handler. Experimenting with different interrupt types is the best way to find the interrupts that work well with Lua.

  • A more subtle point is that the Lua virtual machine must run for the interrupt handlers to work. A simple analogy is that a CPU must have a running clock in order to function properly (and in order to take care of the hardware interrupts). If the clock is stopped, the CPU doesn’t run and the interrupt handlers aren’t called anymore, although the occurence of the interrupt might be recorded inside the CPU. This is the exact same situation with Lua: if the virtual machine doesn’t run, the interrupts are still recorded in the interrupt queue, but the Lua handler won’t be called until the virtual machine runs again. In this case though, the "clock" of the Lua VM is a C function that is executed for every VM instruction. If this function blocks for some reason, the VM instructions are not executed anymore. It’s not hard to make this function block; for example, it blocks everytime the Lua code waits for some user input at the console, or when a TODO tmr.delay is executed, or when TODO uart.read is called with an infinite or very large timeout; in general, any function from a Lua library that doesn’t return immediately (or after a short ammount of time) will block the VM. Care must be taken to avoid such operations as much as possible, otherwise the interrupt support code won’t run properly.

  • There is a single interrupt handler per interrupt type in Lua (the same holds true for C interrupt support), as opposed to the many hardware interrupts handlers usually found on the eLua targets. It is however easy to differentiate between different interrupt sources, as will be explained in the next paragraph.

  • Lua interrupt handlers are never reentrant.

While this might seem restrictive, Lua interrupt handlers work quite well in practical situations. As an added bonus, since they are implemented by C support code, there’s nothing preventing eLua from implementing "custom interrupts" (software generated interrupts that don’t correspond to a hardware interrupt on the CPU), such as serial interrupt on char match (generate an interrupt when a certain char is received on the serial port, for example a newline), timer interrupts for virtual timers, TCP/UDP data packet received interrupt and many others.

Using interrupt handlers in Lua

To enable Lua interrupt handler, define BUILD_LUA_INT_HANDLERS and PLTATFORM_INT_QUEUE_LOG_SIZE in platform_conf.h (see here for details). Setting up interrupt handlers is a straightforward process, most of the required functionality is provided by the CPU module:

  • use cpu.set_int_handler( int_id, handler ) to set the interrupt handler function for the specified interrupt (call with nil to disable the interrupt handler for that interrupt). cpu.set_int_handler returns the previous interrupt handler for int_id (or nil is an interrupt handler was not previously set for the interrupt). In most cases, your interrupt handler should call the previous handler to ensure proper interrupt management.

  • use cpu.sei( int_id, resnum1, [resnum2], …, [resnumn]) and cpu.cli( int_id, resnum1, [resnum2], …, [resnumn]) to enable/disable specific CPU interrupts that will trigger the interrupt handler. You can also use cpu.sei() and cpu.cli (without parameters) to enable/disable global interrupts on the CPU, although this is not recommended.

The interrupt handler receives the resource ID that specifies the resource that fired the interrupt. It can be a timer ID for a timer overflow interrupt, a GPIO port/pin combination for a GPIO interrupt on pin change, a SPI interface ID for a SPI data available interrupt, and so on.

An example that uses the above concepts and knows how to handle two different interrupt types is presented below:

local vtmrid = tmr.VIRT0
local to = 1500000

local prev_tmr, new_prev_tmr, prev_gpio

-- This is the timer interrupt handler
local function tmr_handler( resnum )
print( string.format( "Timer interrupt for id %d", resnum ) )
if prev_tmr then prev_tmr( resnum ) end
end

-- This is the timer interrupt handler that gets set after tmr_handler
local function new_tmr_handler( resnum )
print( string.format( "NEW HANDLER: timer interrupt for id %d", resnum ) )
-- This will chain to the previous interrupt handler (tmr_handler above)
if new_prev_tmr then new_prev_tmr( resnum ) end
end

-- This is the GPIO interrupt on change (falling edge) interrupt
local function gpio_negedge_handler( id, resnum )
local port, pin = pio.decode( resnum )
print( string.format( "GPIO NEGEDGE interrupt on port %d, pin %d", port, pin ) )
if prev_gpio then prev_gpio( resnum ) end
end

-- Set timer interrupt handler
prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, tmr_handler )
-- Set GPIO interrupt on change (negative edge) interrupt handler
prev_gpio = cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, gpio_negedge_handler )
-- Setup periodic timer interrupt for virtual timer 0
tmr.set_match_int( vtmrid, to, tmr.INT_CYCLIC )
-- Enable GPIO interrupt on change (negative edge) for pin 0 of port 0
cpu.sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
-- Enable timer match interrupt on virtual timer 0
cpu.sei( cpu.INT_TMR_MATCH, vtmrid )

local tmrid, count = 0, 0
while true do
print "Outside interrupt"
for i = 1, 1000 do tmr.delay( tmrid, 1000 ) end
if uart.getchar( uartid, 0 ) ~= "" then break end
count = count + 1
if count == 5 then
print "Changing timer interrupt handler"
new_prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, new_tmr_handler )
end
end

-- Cleanup
-- Stop the timer from generating periodic interrupts
tmr.set_match_int( vtmrid, 0, tmr.INT_CYCLIC );
-- Disable the GPIO interrupt on change (negative edge) interrupt
cpu.cli( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
-- Disable the timer interrupt on match interrupt
cpu.cli( cpu.INT_TMR_MATCH, vtmrid )
-- Clear the timer interrupt handler
cpu.set_int_handler( cpu.INT_TMR_MATCH, nil );
-- Clear the GPIO interrupt handler
cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, nil );

This is the most common use case for Lua interrupts, but it’s not the only one. Another way to use interrupts from eLua uses polling instead of interrupt handlers: directly check the interrupt flags and execute a certain action when one of them becomes set. For this, use the cpu.get_int_flag( id, resnum, [clear] ) function from the CPU module, which returns the specified interrupt’s status for resource resnum. clear is an optional boolean parameter, specifying if the interrupt flag should be cleared if it is set. It defaults to true, and in most cases it shouldn’t be changed. Using this feature, it becomes easy to wait for one or more interrupt flag(s) to be set. To use interrupt polling:

  • Enable/disable interrupts to be polled with cpu.hw_sei/cpu.hw_cli instead of cpu.sei/cpu.cli. These functions enable/disable interrupts only in hardware, as opposed to cpu.sei/cpu.cli that also set/clear an internal flag which makes the interrupt able to trigger a Lua handler.

  • Use cpu.get_int_flag to get the interrupt flag.

The int_select function below is a possible implementation of a function that gets an array of interrupts and returns the first one that gets active:

function int_select( int_table )
while true do
for i = 1, #int_table do
local t = int_table[ i ]
if cpu.get_int_flag[ t[ 1 ], t[ 2 ] ) then
return t[ 1 ], t[ 2 ]
end
end
end
end

cpu.hw_sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
cpu.hw_sei( cpu.INT_TMR_MATCH, tmr.VIRT0 )
local ints = { { cpu.INT_GPIO_NEGEDGE, pio.P0_0 }, { cpu.INT_TMR_MATCH, tmr.VIRT0 } }
-- int_select will wait for either INT_GPIO_NEGEDGE or INT_TMR_MATCH to become active
print( int_select( ints ) )

Note that the two mechanisms (interrupt handlers and polling) can be used in parallel as long as an interrupt is not set with both cpu.hw_sei and cpu.sei, in which case the bevahiour is unpredictable. This is why it makes sense to write the int_select function above in Lua instead of C: it keeps the Lua VM running, so Lua interrupt handlers can be executed.

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{
int id = SOME_INT_ID;

platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );
prev_handler = elua_int_set_c_handler( id, int_handler );
}

static void int_handler( elua_int_resnum resnum )
{
// Note: prev_handler can also be called at the end of int_handler
if( prev_handler )
prev_handler( resnum );

// (Optional) Check resnum and return if the interrupt was fired by a different resource
if( resnum != some_resnum )
return;

// Actual interrupt handler code comes here
}
--------------------------

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{
int id = SOME_INT_ID;

platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );
prev_handler = elua_int_set_c_handler( id, int_handler );
}

static void int_handler( elua_int_resnum resnum )
{
// Note: prev_handler can also be called at the end of int_handler
if( prev_handler )
prev_handler( resnum );

// (Optional) Check resnum and return if the interrupt was fired by a different resource
if( resnum != some_resnum )
return;

// Actual interrupt handler code comes here

-------------------------------

eLua interrupt support implementation

To add interrupt support for an eLua platform follow the steps below:

  1. Define your interrupts

    Your interrupt sources should be defined in platform_conf.h with macros (don’t use C enumerations). The first one should have the value ELUA_INT_FIRST_ID (defined in inc/elua_int.h), the next one ELUA_INT_FIRST_ID + 1 and so on. Also, there should be a definition for a macro called INT_ELUA_LAST that must be equal to the largest interrupt source value. An example is given below:

    #define INT_GPIO_POSEDGE      ELUA_INT_FIRST_ID
    #define INT_GPIO_NEGEDGE ( ELUA_INT_FIRST_ID + 1 )
    #define INT_TMR_MATCH ( ELUA_INT_FIRST_ID + 2 )
    #define INT_ELUA_LAST INT_TMR_MATCH

    Note that the interrupt names aren’t random, they should follow a well defined pattern. Check here for details.

  2. Add them to the list of constants from the CPU module

    Check the documentation of the CPU module for details.

  3. Implement your support functions

    The actual implementation of the interrupt handlers is of course platform specific, so it can stay in the platform.c file. However, since interrupt handlers might require quite a bit of code, it is recommended to implement them in a separate file. The eLua convention is to use the platform_int.c file for this purpose. For each interrupt defined in step 1 above, 3 functions need to be implemented:

    • A function that enables or disables the interrupt and returns its previous state (enabled or disabled).

    • A function that checks if the interrupt is enabled or disabled.

    • A function that checks the interrupt pending flag and optionally clears it.

    These functions are defined in inc/elua_int.h, which also defines an "int descriptor" type:

    // Interrupt functions and descriptor
    typedef int ( *elua_int_p_set_status )( elua_int_resnum resnum, int state );
    typedef int ( *elua_int_p_get_status )( elua_int_resnum resnum );
    typedef int ( *elua_int_p_get_flag )( elua_int_resnum resnum, int clear );
    typedef struct
    {
    elua_int_p_set_status int_set_status;
    elua_int_p_get_status int_get_status;
    elua_int_p_get_flag int_get_flag;
    } elua_int_descriptor;

    platform_int.c must have an array of elua_int_descriptor types named elua_int_table (remember to make it const to save RAM). The elements of this array must be in the same order as the interrupt sources. The interrupt table for the example from step 1 above might look like this:

    const elua_int_descriptor elua_int_table[ INT_ELUA_LAST ] =
    {
    { int_gpio_posedge_set_status, int_gpio_posedge_get_status, int_gpio_posedge_get_flag },
    { int_gpio_negedge_set_status, int_gpio_negedge_get_status, int_gpio_negedge_get_flag },
    { int_tmr_match_set_status, int_tmr_match_get_status, int_tmr_match_get_flag }
    };
  4. Implement the init function

    platform_int.c should implement a function named platform_int_init (defined in inc/platform.h) that must initialize all the required hardware and the internal data structures of the interrupt subsystem. This function should be called from platform_init.

  5. Implement the interrupt handlers

    There are two simple requirements for the interrupt handlers: clear the hardware interrupt flag (if needed) and call cmn_int_handler (src/common.c) to connect the handler with the eLua interrupt code. An example is given below:

    // EINT3 (INT_GPIO) interrupt handler
    static void int_handler_eint3()
    {
    elua_int_id id = ELUA_INT_INVALID_INTERRUPT;
    pio_code resnum = 0;
    int pidx, pin;

    EXTINT |= 1 << EINT3_BIT; // clear interrupt
    // Look for interrupt source
    // In can only be GPIO0/GPIO2, as the EXT interrupts are not (yet) used
    pidx = ( IO_INT_STAT & 1 ) ? 0 : 1;
    if( *posedge_status[ pidx ] )
    {
    id = INT_GPIO_POSEDGE;
    pin = intlog2( *posedge_status[ pidx ] );
    }
    else
    {
    id = INT_GPIO_NEGEDGE;
    pin = intlog2( *negedge_status[ pidx ] );
    }
    resnum = PLATFORM_IO_ENCODE( pidx * 2, pin, PLATFORM_IO_ENC_PIN );
    *intclr_regs[ pidx ] = 1 << pin;

    // Run the interrupt through eLua
    cmn_int_handler( id, resnum );
    VICVectAddr = 0; // ACK interrupt
    }

That’s it. If you followed all these steps correctly, your platform should be fully able to support interrupt handlers (as described here). Check the lpc24xx platform implementation (src/platform/lpc24xx) for a full example.

Interrupt list and naming conventions

To ensure maximum portability and correct system behaviour, interrupt names (as defined in platform_conf.h) must follow a well-defined naming pattern. Please note that this isn’t merely a convention, many times the names must be properly chosen for the system to work properly. For example, the timer interrupt match will never happen on virtual timers if the timer interrupt match name isn’t INT_TMR_MATCH (see here for more details on how to use the timer match interrupt).

The naming rule is that the interrupt name must have the format INT_<peripheral>_<type>_, where:

  • peripheral is a symbolic name of the peripheral to which the interrupt applies.

  • type is a symbolic name of the interrupt type.

This restriction applies only to interrupt names. The value associated with the interrupt name (as defined in platform_conf.h) can vary from platform to platform, as long as it follows the rules outlined in step 1 above.

The table below lists all the valid interrupt names currently known to eLua. If you add a new interrupt don’t forget to update the table below.

Name Meaning

INT_GPIO_POSEDGE

Interrupt on a positive edge on a GPIO pin

INT_GPIO_NEGEDGE

Interrupt on a negative edge on a GPIO pin

INT_TMR_MATCH

Interrupt on timer match

INT_UART_RX

Interrupt on UART character received


}
 

Thanks,
Lwazi

Best
Dado









 
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
John Hind John Hind
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

I must say that having taken a close look at this mechanism and even developed it further to allow C interrupt handlers to force a coroutine yield between any two virtual machine opcodes, I became concerned about the safety of this approach. Incidently, if we can force coroutine yields safely in this way we already have all the mechanisms for an RTOS built-in to Lua in the threads provided for coroutines.

 

There are really three concerns I have (note that in the following, by "interrupt" I mean the eLua mechanism for "splicing" extra Lua VM opcodes at runtime, not asynchronous interrupts at the processor level):

 

1. Are we sure that Lua VM operations are always atomic at the opcode level? For example if we interrupt in the middle of processing some complex mathematical expression and the code in the interrupt operates on some of the same variables?

 

2. Do we allow the interrupt code itself to be further interrupted? If so things could become very complex, if not, we have potentially unbounded latencies.

 

3. I believe we will need a Lua-level "critical section" operator which allows the Lua programmer to disable interrupts. For example, you might write a Ring Buffer structure using a Lua table and some pointers. You might want to read this buffer in interrupt code and write it in the main code. You cannot allow the interrupt code to run while the main code is in the middle of writing to the buffer (which will involve a table write, some bounds checks and a pointer write).

 

I feel we need someone who really understands the design of the Lua interpreter and can code-review what we are doing, because it will be virtually impossible to devise an adequate test set to prove this is safe in all cases.

 

Incidentally, I remember being very impressed that the BASIC-STAMP microcontroller had preemptive multitasking integrated with the (interpreted) Basic language, this on an 8-bit chip with only a few hundred *bytes* of RAM. I was able to write a really good serial comms handler for GPS using this, which would have been virtually impossible without. A good RTOS can actually save RAM by avoiding the need for large RAM buffers for incoming data.

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter
Sent: 25 January 2011 21:53
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello guys,

On Tue, Jan 25, 2011 at 19:10, Lwazi <[hidden email]> wrote:

On Tue, Jan 25, 2011 at 2:27 PM, James Snyder <[hidden email]> wrote:


> On Tue, Jan 25, 2011 at 1:25 PM, James Snyder <[hidden email]> wrote:
>>

>> For the microcontroller platforms we run on, an RTOS would generally
>> just add overhead.  In our upcoming release we will support basic
>> concurrency in more of an event-driven manner using interrupts which
>> are
>
> Ooops.. hit the send button when I was going to save this and finish
> editing later.
>
> To finish:
> The interrupts support both Lua and C handlers, C handlers execute
> immediately and Lua handlers can interrupt currently running Lua code,
> but they have to wait a few instructions before execution (so latency
> isn't going to be constant or really low).
>

I like this. Is this documented anywhere?

Yes, this is new eLua feature and the initial doc will be release with v0.8
LPC24xx implementatin can be seen on trunk (or branch pre0.8) at  src/platform/lpc24xx

What happens to the rest of
the system during this 'wait'?


You mean, the aparent-latency wait until we actually handle the INT in Lua ? The Lua VM is executing during this. I think the hook checks for any queued INT to handle every 2 or 3 VM bytecodes.
 

I presume the ISR and other interrupts
do not get blocked while the Lua handler waits to run. 

And I have to
ask if there is a guarantee that the Lua handler will run


   As a preview of the doc to come, I'll paste here some infos about the Interrupt mechanism v0.8 will bring officially to eLua.
   Comments and reviews are welcomed.
   (and yes, the doc edition/refinement by the community will be easier in the future :)

Best
Dado

eLua interrupt handlers

Starting with version 0.8, eLua supports interrupt handlers written in Lua. Once an interrupt handler is set in the Lua code, it will be called each time a supported interrupt is generated. A supported interrupt is any interrupt that is handled by the platform C code (see here for more details).

IMPORTANT: before learning how to use interrupt handlers in Lua, please keep in mind that Lua interrupt handlers don’t work the same way as regular (C) interrupt handlers. As Lua doesn’t have direct suport for interrupts, they have to be emulated. eLua emulates them using a queue that is populated with interrupt data by the C support code. As long as the queue is not empty, a Lua hook is set to run every 2 Lua bytecode instructions. This hook function is the Lua interrupt handler. After all the interrupts are handled and the queue is emptied, the hook is automatically disabled. Consequently:

·      When the interrupt queue is full (a situation that might appear when interrupts are added to the queue faster than the Lua code can handle them) subsequent interrupts are ignored (not added to the queue) and an error message is printed on the eLua console device. The interrupt queue size can be configured at build time, as explained here. Even if the interrupt queue is large, one most remember that Lua code is significantly slower than C code, thus not all C interrupts make suitable candidates for Lua interrupt handlers. For example, a serial interrupt that is generated each time a char is received at 115200 baud might be too fast for Lua (this is largely dependent on the platform). On the other hand, a GPIO interrupt-on-change on a GPIO line connected with a matrix keyboard is a very good candidate for a Lua handler. Experimenting with different interrupt types is the best way to find the interrupts that work well with Lua.

·      A more subtle point is that the Lua virtual machine must run for the interrupt handlers to work. A simple analogy is that a CPU must have a running clock in order to function properly (and in order to take care of the hardware interrupts). If the clock is stopped, the CPU doesn’t run and the interrupt handlers aren’t called anymore, although the occurence of the interrupt might be recorded inside the CPU. This is the exact same situation with Lua: if the virtual machine doesn’t run, the interrupts are still recorded in the interrupt queue, but the Lua handler won’t be called until the virtual machine runs again. In this case though, the "clock" of the Lua VM is a C function that is executed for every VM instruction. If this function blocks for some reason, the VM instructions are not executed anymore. It’s not hard to make this function block; for example, it blocks everytime the Lua code waits for some user input at the console, or when a TODO tmr.delay is executed, or when TODO uart.read is called with an infinite or very large timeout; in general, any function from a Lua library that doesn’t return immediately (or after a short ammount of time) will block the VM. Care must be taken to avoid such operations as much as possible, otherwise the interrupt support code won’t run properly.

·      There is a single interrupt handler per interrupt type in Lua (the same holds true for C interrupt support), as opposed to the many hardware interrupts handlers usually found on the eLua targets. It is however easy to differentiate between different interrupt sources, as will be explained in the next paragraph.

·      Lua interrupt handlers are never reentrant.

While this might seem restrictive, Lua interrupt handlers work quite well in practical situations. As an added bonus, since they are implemented by C support code, there’s nothing preventing eLua from implementing "custom interrupts" (software generated interrupts that don’t correspond to a hardware interrupt on the CPU), such as serial interrupt on char match (generate an interrupt when a certain char is received on the serial port, for example a newline), timer interrupts for virtual timers, TCP/UDP data packet received interrupt and many others.

Using interrupt handlers in Lua

To enable Lua interrupt handler, define BUILD_LUA_INT_HANDLERS and PLTATFORM_INT_QUEUE_LOG_SIZE in platform_conf.h (see here for details). Setting up interrupt handlers is a straightforward process, most of the required functionality is provided by the CPU module:

·      use cpu.set_int_handler( int_id, handler ) to set the interrupt handler function for the specified interrupt (call with nil to disable the interrupt handler for that interrupt). cpu.set_int_handler returns the previous interrupt handler for int_id (or nil is an interrupt handler was not previously set for the interrupt). In most cases, your interrupt handler should call the previous handler to ensure proper interrupt management.

·      use cpu.sei( int_id, resnum1, [resnum2], …, [resnumn]) and cpu.cli( int_id, resnum1, [resnum2], …, [resnumn]) to enable/disable specific CPU interrupts that will trigger the interrupt handler. You can also use cpu.sei() and cpu.cli (without parameters) to enable/disable global interrupts on the CPU, although this is not recommended.

The interrupt handler receives the resource ID that specifies the resource that fired the interrupt. It can be a timer ID for a timer overflow interrupt, a GPIO port/pin combination for a GPIO interrupt on pin change, a SPI interface ID for a SPI data available interrupt, and so on.

An example that uses the above concepts and knows how to handle two different interrupt types is presented below:

local vtmrid = tmr.VIRT0
local to = 1500000

local prev_tmr, new_prev_tmr, prev_gpio

-- This is the timer interrupt handler
local function tmr_handler( resnum )

 
  print( string.format( "Timer interrupt for id %d", resnum ) )
  if prev_tmr then prev_tmr( resnum ) end
end

-- This is the timer interrupt handler that gets set after tmr_handler
local function new_tmr_handler( resnum )

 
  print( string.format( "NEW HANDLER: timer interrupt for id %d", resnum ) )
  -- This will chain to the previous interrupt handler (tmr_handler above)
  if new_prev_tmr then new_prev_tmr( resnum ) end

 
end

-- This is the GPIO interrupt on change (falling edge) interrupt
local function gpio_negedge_handler( id, resnum )
    local port, pin = pio.decode( resnum )

 
  print( string.format( "GPIO NEGEDGE interrupt on port %d, pin %d", port, pin ) )
  if prev_gpio then prev_gpio( resnum ) end
end

-- Set timer interrupt handler
prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, tmr_handler )

 
-- Set GPIO interrupt on change (negative edge) interrupt handler
prev_gpio = cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, gpio_negedge_handler )
-- Setup periodic timer interrupt for virtual timer 0

 
tmr.set_match_int( vtmrid, to, tmr.INT_CYCLIC )
-- Enable GPIO interrupt on change (negative edge) for pin 0 of port 0
cpu.sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )

 
-- Enable timer match interrupt on virtual timer 0
cpu.sei( cpu.INT_TMR_MATCH, vtmrid )

local tmrid, count = 0, 0
while true do
  print "Outside interrupt"

 
  for i = 1, 1000 do tmr.delay( tmrid, 1000 ) end
  if uart.getchar( uartid, 0 ) ~= "" then break end
  count = count + 1
  if count == 5 then
    print "Changing timer interrupt handler"

 
    new_prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, new_tmr_handler )
  end
end

-- Cleanup
-- Stop the timer from generating periodic interrupts
tmr.set_match_int( vtmrid, 0, tmr.INT_CYCLIC );

 
-- Disable the GPIO interrupt on change (negative edge) interrupt
cpu.cli( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
-- Disable the timer interrupt on match interrupt
cpu.cli( cpu.INT_TMR_MATCH, vtmrid )

 
-- Clear the timer interrupt handler
cpu.set_int_handler( cpu.INT_TMR_MATCH, nil );
-- Clear the GPIO interrupt handler
cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, nil );

This is the most common use case for Lua interrupts, but it’s not the only one. Another way to use interrupts from eLua uses polling instead of interrupt handlers: directly check the interrupt flags and execute a certain action when one of them becomes set. For this, use the cpu.get_int_flag( id, resnum, [clear] ) function from the CPU module, which returns the specified interrupt’s status for resource resnum. clear is an optional boolean parameter, specifying if the interrupt flag should be cleared if it is set. It defaults to true, and in most cases it shouldn’t be changed. Using this feature, it becomes easy to wait for one or more interrupt flag(s) to be set. To use interrupt polling:

·      Enable/disable interrupts to be polled with cpu.hw_sei/cpu.hw_cli instead of cpu.sei/cpu.cli. These functions enable/disable interrupts only in hardware, as opposed to cpu.sei/cpu.cli that also set/clear an internal flag which makes the interrupt able to trigger a Lua handler.

·      Use cpu.get_int_flag to get the interrupt flag.

The int_select function below is a possible implementation of a function that gets an array of interrupts and returns the first one that gets active:

function int_select( int_table )
  while true do
    for i = 1, #int_table do
      local t = int_table[ i ]
      if cpu.get_int_flag[ t[ 1 ], t[ 2 ] ) then

 
        return t[ 1 ], t[ 2 ]
      end
    end
end
end

cpu.hw_sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
cpu.hw_sei( cpu.INT_TMR_MATCH, tmr.VIRT0 )

 
local ints = { { cpu.INT_GPIO_NEGEDGE, pio.P0_0 }, { cpu.INT_TMR_MATCH, tmr.VIRT0 } }
-- int_select will wait for either INT_GPIO_NEGEDGE or INT_TMR_MATCH to become active
print( int_select( ints ) )

Note that the two mechanisms (interrupt handlers and polling) can be used in parallel as long as an interrupt is not set with both cpu.hw_sei and cpu.sei, in which case the bevahiour is unpredictable. This is why it makes sense to write the int_select function above in Lua instead of C: it keeps the Lua VM running, so Lua interrupt handlers can be executed.

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );

 
  prev_handler = elua_int_set_c_handler( id, int_handler );
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )

 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;

  // Actual interrupt handler code comes here

 
}

--------------------------


Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );

 
  prev_handler = elua_int_set_c_handler( id, int_handler );
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )

 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;

  // Actual interrupt handler code comes here

 

-------------------------------

eLua interrupt support implementation

To add interrupt support for an eLua platform follow the steps below:

1.   Define your interrupts

Your interrupt sources should be defined in platform_conf.h with macros (don’t use C enumerations). The first one should have the value ELUA_INT_FIRST_ID (defined in inc/elua_int.h), the next one ELUA_INT_FIRST_ID + 1 and so on. Also, there should be a definition for a macro called INT_ELUA_LAST that must be equal to the largest interrupt source value. An example is given below:

#define INT_GPIO_POSEDGE      ELUA_INT_FIRST_ID
#define INT_GPIO_NEGEDGE      ( ELUA_INT_FIRST_ID + 1 )
#define INT_TMR_MATCH         ( ELUA_INT_FIRST_ID + 2 )
#define INT_ELUA_LAST         INT_TMR_MATCH

Note that the interrupt names aren’t random, they should follow a well defined pattern. Check here for details.

2.   Add them to the list of constants from the CPU module

Check the documentation of the CPU module for details.

3.   Implement your support functions

The actual implementation of the interrupt handlers is of course platform specific, so it can stay in the platform.c file. However, since interrupt handlers might require quite a bit of code, it is recommended to implement them in a separate file. The eLua convention is to use the platform_int.c file for this purpose. For each interrupt defined in step 1 above, 3 functions need to be implemented:

o  A function that enables or disables the interrupt and returns its previous state (enabled or disabled).

o  A function that checks if the interrupt is enabled or disabled.

o  A function that checks the interrupt pending flag and optionally clears it.

These functions are defined in inc/elua_int.h, which also defines an "int descriptor" type:

// Interrupt functions and descriptor
typedef int ( *elua_int_p_set_status )( elua_int_resnum resnum, int state );
typedef int ( *elua_int_p_get_status )( elua_int_resnum resnum );
typedef int ( *elua_int_p_get_flag )( elua_int_resnum resnum, int clear );

 
typedef struct
{
  elua_int_p_set_status int_set_status;
  elua_int_p_get_status int_get_status;
  elua_int_p_get_flag int_get_flag;
} elua_int_descriptor;

platform_int.c must have an array of elua_int_descriptor types named elua_int_table (remember to make it const to save RAM). The elements of this array must be in the same order as the interrupt sources. The interrupt table for the example from step 1 above might look like this:

const elua_int_descriptor elua_int_table[ INT_ELUA_LAST ] =
{
  { int_gpio_posedge_set_status, int_gpio_posedge_get_status, int_gpio_posedge_get_flag },
  { int_gpio_negedge_set_status, int_gpio_negedge_get_status, int_gpio_negedge_get_flag },

 
  { int_tmr_match_set_status, int_tmr_match_get_status, int_tmr_match_get_flag }
};

4.   Implement the init function

platform_int.c should implement a function named platform_int_init (defined in inc/platform.h) that must initialize all the required hardware and the internal data structures of the interrupt subsystem. This function should be called from platform_init.

5.   Implement the interrupt handlers

There are two simple requirements for the interrupt handlers: clear the hardware interrupt flag (if needed) and call cmn_int_handler (src/common.c) to connect the handler with the eLua interrupt code. An example is given below:

// EINT3 (INT_GPIO) interrupt handler
static void int_handler_eint3()
{
  elua_int_id id = ELUA_INT_INVALID_INTERRUPT;
  pio_code resnum = 0;
  int pidx, pin;

  EXTINT |= 1 << EINT3_BIT; // clear interrupt

 
  // Look for interrupt source
  // In can only be GPIO0/GPIO2, as the EXT interrupts are not (yet) used
  pidx = ( IO_INT_STAT & 1 ) ? 0 : 1;
  if( *posedge_status[ pidx ] )
  {
    id = INT_GPIO_POSEDGE;

 
    pin = intlog2( *posedge_status[ pidx ] );
  }
  else
  {
    id = INT_GPIO_NEGEDGE;
    pin = intlog2( *negedge_status[ pidx ] );
  }
  resnum = PLATFORM_IO_ENCODE( pidx * 2, pin, PLATFORM_IO_ENC_PIN );

 
  *intclr_regs[ pidx ] = 1 << pin;

  // Run the interrupt through eLua
  cmn_int_handler( id, resnum );
  VICVectAddr = 0; // ACK interrupt

 
}

That’s it. If you followed all these steps correctly, your platform should be fully able to support interrupt handlers (as described here). Check the lpc24xx platform implementation (src/platform/lpc24xx) for a full example.

Interrupt list and naming conventions

To ensure maximum portability and correct system behaviour, interrupt names (as defined in platform_conf.h) must follow a well-defined naming pattern. Please note that this isn’t merely a convention, many times the names must be properly chosen for the system to work properly. For example, the timer interrupt match will never happen on virtual timers if the timer interrupt match name isn’t INT_TMR_MATCH (see here for more details on how to use the timer match interrupt).

The naming rule is that the interrupt name must have the format INT_<peripheral>_<type>_, where:

·      peripheral is a symbolic name of the peripheral to which the interrupt applies.

·      type is a symbolic name of the interrupt type.

This restriction applies only to interrupt names. The value associated with the interrupt name (as defined in platform_conf.h) can vary from platform to platform, as long as it follows the rules outlined in step 1 above.

The table below lists all the valid interrupt names currently known to eLua. If you add a new interrupt don’t forget to update the table below.

Name

Meaning

INT_GPIO_POSEDGE

Interrupt on a positive edge on a GPIO pin

INT_GPIO_NEGEDGE

Interrupt on a negative edge on a GPIO pin

INT_TMR_MATCH

Interrupt on timer match

INT_UART_RX

Interrupt on UART character received


}

 

Thanks,
Lwazi


Best
Dado









 

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Robert G. Jakabosky Robert G. Jakabosky
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

On Wednesday 26, John Hind wrote:

> I must say that having taken a close look at this mechanism and even
> developed it further to allow C interrupt handlers to force a coroutine
> yield between any two virtual machine opcodes, I became concerned about the
> safety of this approach. Incidently, if we can force coroutine yields
> safely in this way we already have all the mechanisms for an RTOS built-in
> to Lua in the threads provided for coroutines.
>
>
>
> There are really three concerns I have (note that in the following, by
> "interrupt" I mean the eLua mechanism for "splicing" extra Lua VM opcodes
> at runtime, not asynchronous interrupts at the processor level):
>
>
>
> 1. Are we sure that Lua VM operations are always atomic at the opcode
> level? For example if we interrupt in the middle of processing some complex
> mathematical expression and the code in the interrupt operates on some of
> the same variables?

Interrupts between opcodes should be safe.  You can even yield a coroutine
from a debug hook.

If the "complex mathematical expression" (i.e. multiple VM opcodes) needs to
run atomically, then it should use a critical section like you described
below.

> 2. Do we allow the interrupt code itself to be further interrupted? If so
> things could become very complex, if not, we have potentially unbounded
> latencies.

Maybe this should be configurable?  An interrupt handler could be marked as
un-interruptable when it is registered, or is could use critical sections.

> 3. I believe we will need a Lua-level "critical section" operator which
> allows the Lua programmer to disable interrupts. For example, you might
> write a Ring Buffer structure using a Lua table and some pointers. You
> might want to read this buffer in interrupt code and write it in the main
> code. You cannot allow the interrupt code to run while the main code is in
> the middle of writing to the buffer (which will involve a table write, some
> bounds checks and a pointer write).
>
>
>
> I feel we need someone who really understands the design of the Lua
> interpreter and can code-review what we are doing, because it will be
> virtually impossible to devise an adequate test set to prove this is safe
> in all cases.

I could help some with code-reviewing, when I have spare time.


--
Robert G. Jakabosky
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Dado Sutter Dado Sutter
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

In reply to this post by Dado Sutter
Hello list,

On Wed, Jan 26, 2011 at 08:14, John Hind <[hidden email]> wrote:

I must say that having taken a close look at this mechanism and even developed it further to allow C interrupt handlers to force a coroutine yield between any two virtual machine opcodes, I became concerned about the safety of this approach. Incidently, if we can force coroutine yields safely in this way we already have all the mechanisms for an RTOS built-in to Lua in the threads provided for coroutines.


Thank you for diving into this John.

 

There are really three concerns I have (note that in the following, by "interrupt" I mean the eLua mechanism for "splicing" extra Lua VM opcodes at runtime, not asynchronous interrupts at the processor level):

 

1. Are we sure that Lua VM operations are always atomic at the opcode level?


I "think" they are, as Lua itself has a debug lib that (I think) uses the same hook mechanism.
 

For example if we interrupt in the middle of processing some complex mathematical expression and the code in the interrupt operates on some of the same variables?


On the minimalist Lua approach, I think this would be considered as a programmer's fault :) But in this case I must admit it would be a serious flaw.
Our initial INT mechanism allows one to disable (or stop queueing) interrupts during critical program sections but this may be not fair to transfer the responsability to users in all the cases.
 

2. Do we allow the interrupt code itself to be further interrupted? 

If so things could become very complex, if not, we have potentially unbounded latencies.

 

3. I believe we will need a Lua-level "critical section" operator which allows the Lua programmer to disable interrupts. For example, you might write a Ring Buffer structure using a Lua table and some pointers. You might want to read this buffer in interrupt code and write it in the main code. You cannot allow the interrupt code to run while the main code is in the middle of writing to the buffer (which will involve a table write, some bounds checks and a pointer write).


The initial INT support has instructions to enable/disable Lua handlers.
 

I feel we need someone who really understands the design of the Lua interpreter and can code-review what we are doing, because it will be virtually impossible to devise an adequate test set to prove this is safe in all cases.


Yep. It would be great to have more help for this and for the dev in general.
 

Incidentally, I remember being very impressed that the BASIC-STAMP microcontroller had preemptive multitasking integrated with the (interpreted) Basic language, this on an 8-bit chip with only a few hundred *bytes* of RAM. I was able to write a really good serial comms handler for GPS using this, which would have been virtually impossible without. A good RTOS can actually save RAM by avoiding the need for large RAM buffers for incoming data.


Cool. I hope we can learn and add such vodoo to eLua too.

Best
Dado




 

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter
Sent: 25 January 2011 21:53
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello guys,

On Tue, Jan 25, 2011 at 19:10, Lwazi <[hidden email]> wrote:

On Tue, Jan 25, 2011 at 2:27 PM, James Snyder <[hidden email]> wrote:
> On Tue, Jan 25, 2011 at 1:25 PM, James Snyder <[hidden email]> wrote:
>>

>> For the microcontroller platforms we run on, an RTOS would generally
>> just add overhead.  In our upcoming release we will support basic
>> concurrency in more of an event-driven manner using interrupts which
>> are
>
> Ooops.. hit the send button when I was going to save this and finish
> editing later.
>
> To finish:
> The interrupts support both Lua and C handlers, C handlers execute
> immediately and Lua handlers can interrupt currently running Lua code,
> but they have to wait a few instructions before execution (so latency
> isn't going to be constant or really low).
>

I like this. Is this documented anywhere?


Yes, this is new eLua feature and the initial doc will be release with v0.8
LPC24xx implementatin can be seen on trunk (or branch pre0.8) at  src/platform/lpc24xx

What happens to the rest of
the system during this 'wait'?


You mean, the aparent-latency wait until we actually handle the INT in Lua ? The Lua VM is executing during this. I think the hook checks for any queued INT to handle every 2 or 3 VM bytecodes.
 

I presume the ISR and other interrupts
do not get blocked while the Lua handler waits to run. 

And I have to
ask if there is a guarantee that the Lua handler will run


   As a preview of the doc to come, I'll paste here some infos about the Interrupt mechanism v0.8 will bring officially to eLua.
   Comments and reviews are welcomed.
   (and yes, the doc edition/refinement by the community will be easier in the future :)

Best
Dado

eLua interrupt handlers

Starting with version 0.8, eLua supports interrupt handlers written in Lua. Once an interrupt handler is set in the Lua code, it will be called each time a supported interrupt is generated. A supported interrupt is any interrupt that is handled by the platform C code (see here for more details).

IMPORTANT: before learning how to use interrupt handlers in Lua, please keep in mind that Lua interrupt handlers don’t work the same way as regular (C) interrupt handlers. As Lua doesn’t have direct suport for interrupts, they have to be emulated. eLua emulates them using a queue that is populated with interrupt data by the C support code. As long as the queue is not empty, a Lua hook is set to run every 2 Lua bytecode instructions. This hook function is the Lua interrupt handler. After all the interrupts are handled and the queue is emptied, the hook is automatically disabled. Consequently:

·      When the interrupt queue is full (a situation that might appear when interrupts are added to the queue faster than the Lua code can handle them) subsequent interrupts are ignored (not added to the queue) and an error message is printed on the eLua console device. The interrupt queue size can be configured at build time, as explained here. Even if the interrupt queue is large, one most remember that Lua code is significantly slower than C code, thus not all C interrupts make suitable candidates for Lua interrupt handlers. For example, a serial interrupt that is generated each time a char is received at 115200 baud might be too fast for Lua (this is largely dependent on the platform). On the other hand, a GPIO interrupt-on-change on a GPIO line connected with a matrix keyboard is a very good candidate for a Lua handler. Experimenting with different interrupt types is the best way to find the interrupts that work well with Lua.

·      A more subtle point is that the Lua virtual machine must run for the interrupt handlers to work. A simple analogy is that a CPU must have a running clock in order to function properly (and in order to take care of the hardware interrupts). If the clock is stopped, the CPU doesn’t run and the interrupt handlers aren’t called anymore, although the occurence of the interrupt might be recorded inside the CPU. This is the exact same situation with Lua: if the virtual machine doesn’t run, the interrupts are still recorded in the interrupt queue, but the Lua handler won’t be called until the virtual machine runs again. In this case though, the "clock" of the Lua VM is a C function that is executed for every VM instruction. If this function blocks for some reason, the VM instructions are not executed anymore. It’s not hard to make this function block; for example, it blocks everytime the Lua code waits for some user input at the console, or when a TODO tmr.delay is executed, or when TODO uart.read is called with an infinite or very large timeout; in general, any function from a Lua library that doesn’t return immediately (or after a short ammount of time) will block the VM. Care must be taken to avoid such operations as much as possible, otherwise the interrupt support code won’t run properly.

·      There is a single interrupt handler per interrupt type in Lua (the same holds true for C interrupt support), as opposed to the many hardware interrupts handlers usually found on the eLua targets. It is however easy to differentiate between different interrupt sources, as will be explained in the next paragraph.

·      Lua interrupt handlers are never reentrant.

While this might seem restrictive, Lua interrupt handlers work quite well in practical situations. As an added bonus, since they are implemented by C support code, there’s nothing preventing eLua from implementing "custom interrupts" (software generated interrupts that don’t correspond to a hardware interrupt on the CPU), such as serial interrupt on char match (generate an interrupt when a certain char is received on the serial port, for example a newline), timer interrupts for virtual timers, TCP/UDP data packet received interrupt and many others.

Using interrupt handlers in Lua

To enable Lua interrupt handler, define BUILD_LUA_INT_HANDLERS and PLTATFORM_INT_QUEUE_LOG_SIZE in platform_conf.h (see here for details). Setting up interrupt handlers is a straightforward process, most of the required functionality is provided by the CPU module:

·      use cpu.set_int_handler( int_id, handler ) to set the interrupt handler function for the specified interrupt (call with nil to disable the interrupt handler for that interrupt). cpu.set_int_handler returns the previous interrupt handler for int_id (or nil is an interrupt handler was not previously set for the interrupt). In most cases, your interrupt handler should call the previous handler to ensure proper interrupt management.

·      use cpu.sei( int_id, resnum1, [resnum2], …, [resnumn]) and cpu.cli( int_id, resnum1, [resnum2], …, [resnumn]) to enable/disable specific CPU interrupts that will trigger the interrupt handler. You can also use cpu.sei() and cpu.cli (without parameters) to enable/disable global interrupts on the CPU, although this is not recommended.

The interrupt handler receives the resource ID that specifies the resource that fired the interrupt. It can be a timer ID for a timer overflow interrupt, a GPIO port/pin combination for a GPIO interrupt on pin change, a SPI interface ID for a SPI data available interrupt, and so on.

An example that uses the above concepts and knows how to handle two different interrupt types is presented below:

local vtmrid = tmr.VIRT0
local to = 1500000

local prev_tmr, new_prev_tmr, prev_gpio

-- This is the timer interrupt handler
local function tmr_handler( resnum )

 
  print( string.format( "Timer interrupt for id %d", resnum ) )
  if prev_tmr then prev_tmr( resnum ) end
end

-- This is the timer interrupt handler that gets set after tmr_handler
local function new_tmr_handler( resnum )

 
  print( string.format( "NEW HANDLER: timer interrupt for id %d", resnum ) )
  -- This will chain to the previous interrupt handler (tmr_handler above)
  if new_prev_tmr then new_prev_tmr( resnum ) end

 
end

-- This is the GPIO interrupt on change (falling edge) interrupt
local function gpio_negedge_handler( id, resnum )
    local port, pin = pio.decode( resnum )

 
  print( string.format( "GPIO NEGEDGE interrupt on port %d, pin %d", port, pin ) )
  if prev_gpio then prev_gpio( resnum ) end
end

-- Set timer interrupt handler
prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, tmr_handler )

 
-- Set GPIO interrupt on change (negative edge) interrupt handler
prev_gpio = cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, gpio_negedge_handler )
-- Setup periodic timer interrupt for virtual timer 0

 
tmr.set_match_int( vtmrid, to, tmr.INT_CYCLIC )
-- Enable GPIO interrupt on change (negative edge) for pin 0 of port 0
cpu.sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )

 
-- Enable timer match interrupt on virtual timer 0
cpu.sei( cpu.INT_TMR_MATCH, vtmrid )

local tmrid, count = 0, 0
while true do
  print "Outside interrupt"

 

  for i = 1, 1000 do tmr.delay( tmrid, 1000 ) end
  if uart.getchar( uartid, 0 ) ~= "" then break end
  count = count + 1
  if count == 5 then
    print "Changing timer interrupt handler"

 
    new_prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, new_tmr_handler )
  end
end

-- Cleanup
-- Stop the timer from generating periodic interrupts
tmr.set_match_int( vtmrid, 0, tmr.INT_CYCLIC );

 
-- Disable the GPIO interrupt on change (negative edge) interrupt
cpu.cli( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
-- Disable the timer interrupt on match interrupt
cpu.cli( cpu.INT_TMR_MATCH, vtmrid )

 
-- Clear the timer interrupt handler
cpu.set_int_handler( cpu.INT_TMR_MATCH, nil );
-- Clear the GPIO interrupt handler
cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, nil );

This is the most common use case for Lua interrupts, but it’s not the only one. Another way to use interrupts from eLua uses polling instead of interrupt handlers: directly check the interrupt flags and execute a certain action when one of them becomes set. For this, use the cpu.get_int_flag( id, resnum, [clear] ) function from the CPU module, which returns the specified interrupt’s status for resource resnum. clear is an optional boolean parameter, specifying if the interrupt flag should be cleared if it is set. It defaults to true, and in most cases it shouldn’t be changed. Using this feature, it becomes easy to wait for one or more interrupt flag(s) to be set. To use interrupt polling:

·      Enable/disable interrupts to be polled with cpu.hw_sei/cpu.hw_cli instead of cpu.sei/cpu.cli. These functions enable/disable interrupts only in hardware, as opposed to cpu.sei/cpu.cli that also set/clear an internal flag which makes the interrupt able to trigger a Lua handler.

·      Use cpu.get_int_flag to get the interrupt flag.

The int_select function below is a possible implementation of a function that gets an array of interrupts and returns the first one that gets active:

function int_select( int_table )
  while true do
    for i = 1, #int_table do
      local t = int_table[ i ]
      if cpu.get_int_flag[ t[ 1 ], t[ 2 ] ) then

 
        return t[ 1 ], t[ 2 ]
      end
    end
end
end

cpu.hw_sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
cpu.hw_sei( cpu.INT_TMR_MATCH, tmr.VIRT0 )

 
local ints = { { cpu.INT_GPIO_NEGEDGE, pio.P0_0 }, { cpu.INT_TMR_MATCH, tmr.VIRT0 } }
-- int_select will wait for either INT_GPIO_NEGEDGE or INT_TMR_MATCH to become active
print( int_select( ints ) )

Note that the two mechanisms (interrupt handlers and polling) can be used in parallel as long as an interrupt is not set with both cpu.hw_sei and cpu.sei, in which case the bevahiour is unpredictable. This is why it makes sense to write the int_select function above in Lua instead of C: it keeps the Lua VM running, so Lua interrupt handlers can be executed.

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );

 
  prev_handler = elua_int_set_c_handler( id, int_handler );
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )

 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;

  // Actual interrupt handler code comes here

 
}

--------------------------


Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );

 
  prev_handler = elua_int_set_c_handler( id, int_handler );
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )

 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;

  // Actual interrupt handler code comes here

 

-------------------------------

eLua interrupt support implementation

To add interrupt support for an eLua platform follow the steps below:

1.   Define your interrupts

Your interrupt sources should be defined in platform_conf.h with macros (don’t use C enumerations). The first one should have the value ELUA_INT_FIRST_ID (defined in inc/elua_int.h), the next one ELUA_INT_FIRST_ID + 1 and so on. Also, there should be a definition for a macro called INT_ELUA_LAST that must be equal to the largest interrupt source value. An example is given below:

#define INT_GPIO_POSEDGE      ELUA_INT_FIRST_ID
#define INT_GPIO_NEGEDGE      ( ELUA_INT_FIRST_ID + 1 )
#define INT_TMR_MATCH         ( ELUA_INT_FIRST_ID + 2 )
#define INT_ELUA_LAST         INT_TMR_MATCH

Note that the interrupt names aren’t random, they should follow a well defined pattern. Check here for details.

2.   Add them to the list of constants from the CPU module

Check the documentation of the CPU module for details.

3.   Implement your support functions

The actual implementation of the interrupt handlers is of course platform specific, so it can stay in the platform.c file. However, since interrupt handlers might require quite a bit of code, it is recommended to implement them in a separate file. The eLua convention is to use the platform_int.c file for this purpose. For each interrupt defined in step 1 above, 3 functions need to be implemented:

o  A function that enables or disables the interrupt and returns its previous state (enabled or disabled).

o  A function that checks if the interrupt is enabled or disabled.

o  A function that checks the interrupt pending flag and optionally clears it.

These functions are defined in inc/elua_int.h, which also defines an "int descriptor" type:


// Interrupt functions and descriptor
typedef int ( *elua_int_p_set_status )( elua_int_resnum resnum, int state );
typedef int ( *elua_int_p_get_status )( elua_int_resnum resnum );
typedef int ( *elua_int_p_get_flag )( elua_int_resnum resnum, int clear );

 
typedef struct
{
  elua_int_p_set_status int_set_status;
  elua_int_p_get_status int_get_status;
  elua_int_p_get_flag int_get_flag;
} elua_int_descriptor;

platform_int.c must have an array of elua_int_descriptor types named elua_int_table (remember to make it const to save RAM). The elements of this array must be in the same order as the interrupt sources. The interrupt table for the example from step 1 above might look like this:

const elua_int_descriptor elua_int_table[ INT_ELUA_LAST ] =
{
  { int_gpio_posedge_set_status, int_gpio_posedge_get_status, int_gpio_posedge_get_flag },
  { int_gpio_negedge_set_status, int_gpio_negedge_get_status, int_gpio_negedge_get_flag },

 
  { int_tmr_match_set_status, int_tmr_match_get_status, int_tmr_match_get_flag }
};

4.   Implement the init function

platform_int.c should implement a function named platform_int_init (defined in inc/platform.h) that must initialize all the required hardware and the internal data structures of the interrupt subsystem. This function should be called from platform_init.

5.   Implement the interrupt handlers

There are two simple requirements for the interrupt handlers: clear the hardware interrupt flag (if needed) and call cmn_int_handler (src/common.c) to connect the handler with the eLua interrupt code. An example is given below:

// EINT3 (INT_GPIO) interrupt handler
static void int_handler_eint3()
{
  elua_int_id id = ELUA_INT_INVALID_INTERRUPT;
  pio_code resnum = 0;
  int pidx, pin;

  EXTINT |= 1 << EINT3_BIT; // clear interrupt

 
  // Look for interrupt source
  // In can only be GPIO0/GPIO2, as the EXT interrupts are not (yet) used
  pidx = ( IO_INT_STAT & 1 ) ? 0 : 1;
  if( *posedge_status[ pidx ] )
  {
    id = INT_GPIO_POSEDGE;

 
    pin = intlog2( *posedge_status[ pidx ] );
  }
  else
  {
    id = INT_GPIO_NEGEDGE;
    pin = intlog2( *negedge_status[ pidx ] );
  }
  resnum = PLATFORM_IO_ENCODE( pidx * 2, pin, PLATFORM_IO_ENC_PIN );

 
  *intclr_regs[ pidx ] = 1 << pin;

  // Run the interrupt through eLua
  cmn_int_handler( id, resnum );
  VICVectAddr = 0; // ACK interrupt

 
}

That’s it. If you followed all these steps correctly, your platform should be fully able to support interrupt handlers (as described here). Check the lpc24xx platform implementation (src/platform/lpc24xx) for a full example.

Interrupt list and naming conventions

To ensure maximum portability and correct system behaviour, interrupt names (as defined in platform_conf.h) must follow a well-defined naming pattern. Please note that this isn’t merely a convention, many times the names must be properly chosen for the system to work properly. For example, the timer interrupt match will never happen on virtual timers if the timer interrupt match name isn’t INT_TMR_MATCH (see here for more details on how to use the timer match interrupt).

The naming rule is that the interrupt name must have the format INT_<peripheral>_<type>_, where:

·      peripheral is a symbolic name of the peripheral to which the interrupt applies.

·      type is a symbolic name of the interrupt type.

This restriction applies only to interrupt names. The value associated with the interrupt name (as defined in platform_conf.h) can vary from platform to platform, as long as it follows the rules outlined in step 1 above.

The table below lists all the valid interrupt names currently known to eLua. If you add a new interrupt don’t forget to update the table below.

Name

Meaning

INT_GPIO_POSEDGE

Interrupt on a positive edge on a GPIO pin

INT_GPIO_NEGEDGE

Interrupt on a negative edge on a GPIO pin

INT_TMR_MATCH

Interrupt on timer match

INT_UART_RX

Interrupt on UART character received


}

 

Thanks,
Lwazi


Best
Dado









 

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev



_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
BogdanM BogdanM
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

In reply to this post by Dado Sutter


On Wed, Jan 26, 2011 at 12:14 PM, John Hind <[hidden email]> wrote:

I must say that having taken a close look at this mechanism and even developed it further to allow C interrupt handlers to force a coroutine yield between any two virtual machine opcodes, I became concerned about the safety of this approach. Incidently, if we can force coroutine yields safely in this way we already have all the mechanisms for an RTOS built-in to Lua in the threads provided for coroutines.

 

There are really three concerns I have (note that in the following, by "interrupt" I mean the eLua mechanism for "splicing" extra Lua VM opcodes at runtime, not asynchronous interrupts at the processor level):

 

1. Are we sure that Lua VM operations are always atomic at the opcode level? For example if we interrupt in the middle of processing some complex mathematical expression and the code in the interrupt operates on some of the same variables?


I am pretty sure they are atomic.
 

2. Do we allow the interrupt code itself to be further interrupted? If so things could become very complex, if not, we have potentially unbounded latencies.


We don't support re-entrant interrupt handlers (and I don't think we will). The interrupts are queued though so we might be able to add priorities to them and add them to the queue according to the priority. This raises a number of interesting problems though, the hardest one being that the hardware interrupts already have some priorities of their own and it might not be that easy to find a priority mechanism that's good enough for both hardware (as it has to be platform independent) and for Lua. 
I have to say that I almost didn't think about "interrupt latency" when I implemented this. This wasn't a slip though. In my head any code that needs to worry about latencies should stay on the C side (loadable binary modules support will make this much easier to manage). Interrupts in eLua are nice and useful, but I don't think of them in terms of realtime, rather in terms of a basic multitasking mechanism. Read on for an actual example.
 

 3. I believe we will need a Lua-level "critical section" operator which allows the Lua programmer to disable interrupts. For example, you might write a Ring Buffer structure using a Lua table and some pointers. You might want to read this buffer in interrupt code and write it in the main code. You cannot allow the interrupt code to run while the main code is in the middle of writing to the buffer (which will involve a table write, some bounds checks and a pointer write).


Lua doesn't really have the concept of "interrupts". In eLua, on the other hand, the cpu module already has functions to disable/enable interrupts at will (both at hardware level and at eLua interrupt layer level).
 

 I feel we need someone who really understands the design of the Lua interpreter and can code-review what we are doing, because it will be virtually impossible to devise an adequate test set to prove this is safe in all cases.


I think we only need to worry about this if opcodes are indeed non-atomic (which I really doubt). If they are, the design of the language itself (which allows any hook functions to be called between Lua VM instructions) is a guarantee that things can't go crazy on the synchronization front.
 

 Incidentally, I remember being very impressed that the BASIC-STAMP microcontroller had preemptive multitasking integrated with the (interpreted) Basic language, this on an 8-bit chip with only a few hundred *bytes* of RAM. I was able to write a really good serial comms handler for GPS using this, which would have been virtually impossible without. A good RTOS can actually save RAM by avoiding the need for large RAM buffers for incoming data.


This is true in general, but probably not so much when running a virtual machine. A simple example for the GPS driver you mentioned ealier: take a serial port configured at 115200 baud. You receive a number of bytes on this serial port from a GPS. If you were to do this with eLua you'd have to call your serial interrupt handler 11520 times per second. Might be just a bit too much for eLua; it might or it might not be quick enough to get all the bytes (this is of course largely dependent on the platform and I can't make any estimation about the actual resources you'd need to acomplish this). Now imagine that you have a serial driver written in C that will call your Lua interrupt handler when an event happens: for example when a configurable number of bytes were received (like a FIFO) or when a certain character was received on the input stream. In our GPS case scenario we can use the second option: the C driver will send an interrupt when a '\n' is received (which ends the GPS sentence). In turn eLua will acknowledge this interrupt and read all the sentence (which is already stored in the C driver's buffers), then parse it and do whatever it needs with it. Of course the C driver is free to read the following GPS sentence while eLua processes the current sentence. Personally I find this model much more suitable for a system that runs around a VM and needs to handle real time data communication.

Best,
Bogdan
 

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter
Sent: 25 January 2011 21:53
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello guys,

On Tue, Jan 25, 2011 at 19:10, Lwazi <[hidden email]> wrote:

On Tue, Jan 25, 2011 at 2:27 PM, James Snyder <[hidden email]> wrote:
> On Tue, Jan 25, 2011 at 1:25 PM, James Snyder <[hidden email]> wrote:
>>

>> For the microcontroller platforms we run on, an RTOS would generally
>> just add overhead.  In our upcoming release we will support basic
>> concurrency in more of an event-driven manner using interrupts which
>> are
>
> Ooops.. hit the send button when I was going to save this and finish
> editing later.
>
> To finish:
> The interrupts support both Lua and C handlers, C handlers execute
> immediately and Lua handlers can interrupt currently running Lua code,
> but they have to wait a few instructions before execution (so latency
> isn't going to be constant or really low).
>

I like this. Is this documented anywhere?


Yes, this is new eLua feature and the initial doc will be release with v0.8
LPC24xx implementatin can be seen on trunk (or branch pre0.8) at  src/platform/lpc24xx

What happens to the rest of
the system during this 'wait'?


You mean, the aparent-latency wait until we actually handle the INT in Lua ? The Lua VM is executing during this. I think the hook checks for any queued INT to handle every 2 or 3 VM bytecodes.
 

I presume the ISR and other interrupts
do not get blocked while the Lua handler waits to run. 

And I have to
ask if there is a guarantee that the Lua handler will run


   As a preview of the doc to come, I'll paste here some infos about the Interrupt mechanism v0.8 will bring officially to eLua.
   Comments and reviews are welcomed.
   (and yes, the doc edition/refinement by the community will be easier in the future :)

Best
Dado

eLua interrupt handlers

Starting with version 0.8, eLua supports interrupt handlers written in Lua. Once an interrupt handler is set in the Lua code, it will be called each time a supported interrupt is generated. A supported interrupt is any interrupt that is handled by the platform C code (see here for more details).

IMPORTANT: before learning how to use interrupt handlers in Lua, please keep in mind that Lua interrupt handlers don’t work the same way as regular (C) interrupt handlers. As Lua doesn’t have direct suport for interrupts, they have to be emulated. eLua emulates them using a queue that is populated with interrupt data by the C support code. As long as the queue is not empty, a Lua hook is set to run every 2 Lua bytecode instructions. This hook function is the Lua interrupt handler. After all the interrupts are handled and the queue is emptied, the hook is automatically disabled. Consequently:

·      When the interrupt queue is full (a situation that might appear when interrupts are added to the queue faster than the Lua code can handle them) subsequent interrupts are ignored (not added to the queue) and an error message is printed on the eLua console device. The interrupt queue size can be configured at build time, as explained here. Even if the interrupt queue is large, one most remember that Lua code is significantly slower than C code, thus not all C interrupts make suitable candidates for Lua interrupt handlers. For example, a serial interrupt that is generated each time a char is received at 115200 baud might be too fast for Lua (this is largely dependent on the platform). On the other hand, a GPIO interrupt-on-change on a GPIO line connected with a matrix keyboard is a very good candidate for a Lua handler. Experimenting with different interrupt types is the best way to find the interrupts that work well with Lua.

·      A more subtle point is that the Lua virtual machine must run for the interrupt handlers to work. A simple analogy is that a CPU must have a running clock in order to function properly (and in order to take care of the hardware interrupts). If the clock is stopped, the CPU doesn’t run and the interrupt handlers aren’t called anymore, although the occurence of the interrupt might be recorded inside the CPU. This is the exact same situation with Lua: if the virtual machine doesn’t run, the interrupts are still recorded in the interrupt queue, but the Lua handler won’t be called until the virtual machine runs again. In this case though, the "clock" of the Lua VM is a C function that is executed for every VM instruction. If this function blocks for some reason, the VM instructions are not executed anymore. It’s not hard to make this function block; for example, it blocks everytime the Lua code waits for some user input at the console, or when a TODO tmr.delay is executed, or when TODO uart.read is called with an infinite or very large timeout; in general, any function from a Lua library that doesn’t return immediately (or after a short ammount of time) will block the VM. Care must be taken to avoid such operations as much as possible, otherwise the interrupt support code won’t run properly.

·      There is a single interrupt handler per interrupt type in Lua (the same holds true for C interrupt support), as opposed to the many hardware interrupts handlers usually found on the eLua targets. It is however easy to differentiate between different interrupt sources, as will be explained in the next paragraph.

·      Lua interrupt handlers are never reentrant.

While this might seem restrictive, Lua interrupt handlers work quite well in practical situations. As an added bonus, since they are implemented by C support code, there’s nothing preventing eLua from implementing "custom interrupts" (software generated interrupts that don’t correspond to a hardware interrupt on the CPU), such as serial interrupt on char match (generate an interrupt when a certain char is received on the serial port, for example a newline), timer interrupts for virtual timers, TCP/UDP data packet received interrupt and many others.

Using interrupt handlers in Lua

To enable Lua interrupt handler, define BUILD_LUA_INT_HANDLERS and PLTATFORM_INT_QUEUE_LOG_SIZE in platform_conf.h (see here for details). Setting up interrupt handlers is a straightforward process, most of the required functionality is provided by the CPU module:

·      use cpu.set_int_handler( int_id, handler ) to set the interrupt handler function for the specified interrupt (call with nil to disable the interrupt handler for that interrupt). cpu.set_int_handler returns the previous interrupt handler for int_id (or nil is an interrupt handler was not previously set for the interrupt). In most cases, your interrupt handler should call the previous handler to ensure proper interrupt management.

·      use cpu.sei( int_id, resnum1, [resnum2], …, [resnumn]) and cpu.cli( int_id, resnum1, [resnum2], …, [resnumn]) to enable/disable specific CPU interrupts that will trigger the interrupt handler. You can also use cpu.sei() and cpu.cli (without parameters) to enable/disable global interrupts on the CPU, although this is not recommended.

The interrupt handler receives the resource ID that specifies the resource that fired the interrupt. It can be a timer ID for a timer overflow interrupt, a GPIO port/pin combination for a GPIO interrupt on pin change, a SPI interface ID for a SPI data available interrupt, and so on.

An example that uses the above concepts and knows how to handle two different interrupt types is presented below:

local vtmrid = tmr.VIRT0
local to = 1500000

local prev_tmr, new_prev_tmr, prev_gpio

-- This is the timer interrupt handler
local function tmr_handler( resnum )

 
  print( string.format( "Timer interrupt for id %d", resnum ) )
  if prev_tmr then prev_tmr( resnum ) end
end

-- This is the timer interrupt handler that gets set after tmr_handler
local function new_tmr_handler( resnum )

 
  print( string.format( "NEW HANDLER: timer interrupt for id %d", resnum ) )
  -- This will chain to the previous interrupt handler (tmr_handler above)
  if new_prev_tmr then new_prev_tmr( resnum ) end

 
end

-- This is the GPIO interrupt on change (falling edge) interrupt
local function gpio_negedge_handler( id, resnum )
    local port, pin = pio.decode( resnum )

 
  print( string.format( "GPIO NEGEDGE interrupt on port %d, pin %d", port, pin ) )
  if prev_gpio then prev_gpio( resnum ) end
end

-- Set timer interrupt handler
prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, tmr_handler )

 
-- Set GPIO interrupt on change (negative edge) interrupt handler
prev_gpio = cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, gpio_negedge_handler )
-- Setup periodic timer interrupt for virtual timer 0

 
tmr.set_match_int( vtmrid, to, tmr.INT_CYCLIC )
-- Enable GPIO interrupt on change (negative edge) for pin 0 of port 0
cpu.sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )

 
-- Enable timer match interrupt on virtual timer 0
cpu.sei( cpu.INT_TMR_MATCH, vtmrid )

local tmrid, count = 0, 0
while true do
  print "Outside interrupt"

 

  for i = 1, 1000 do tmr.delay( tmrid, 1000 ) end
  if uart.getchar( uartid, 0 ) ~= "" then break end
  count = count + 1
  if count == 5 then
    print "Changing timer interrupt handler"

 
    new_prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, new_tmr_handler )
  end
end

-- Cleanup
-- Stop the timer from generating periodic interrupts
tmr.set_match_int( vtmrid, 0, tmr.INT_CYCLIC );

 
-- Disable the GPIO interrupt on change (negative edge) interrupt
cpu.cli( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
-- Disable the timer interrupt on match interrupt
cpu.cli( cpu.INT_TMR_MATCH, vtmrid )

 
-- Clear the timer interrupt handler
cpu.set_int_handler( cpu.INT_TMR_MATCH, nil );
-- Clear the GPIO interrupt handler
cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, nil );

This is the most common use case for Lua interrupts, but it’s not the only one. Another way to use interrupts from eLua uses polling instead of interrupt handlers: directly check the interrupt flags and execute a certain action when one of them becomes set. For this, use the cpu.get_int_flag( id, resnum, [clear] ) function from the CPU module, which returns the specified interrupt’s status for resource resnum. clear is an optional boolean parameter, specifying if the interrupt flag should be cleared if it is set. It defaults to true, and in most cases it shouldn’t be changed. Using this feature, it becomes easy to wait for one or more interrupt flag(s) to be set. To use interrupt polling:

·      Enable/disable interrupts to be polled with cpu.hw_sei/cpu.hw_cli instead of cpu.sei/cpu.cli. These functions enable/disable interrupts only in hardware, as opposed to cpu.sei/cpu.cli that also set/clear an internal flag which makes the interrupt able to trigger a Lua handler.

·      Use cpu.get_int_flag to get the interrupt flag.

The int_select function below is a possible implementation of a function that gets an array of interrupts and returns the first one that gets active:

function int_select( int_table )
  while true do
    for i = 1, #int_table do
      local t = int_table[ i ]
      if cpu.get_int_flag[ t[ 1 ], t[ 2 ] ) then

 
        return t[ 1 ], t[ 2 ]
      end
    end
end
end

cpu.hw_sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
cpu.hw_sei( cpu.INT_TMR_MATCH, tmr.VIRT0 )

 
local ints = { { cpu.INT_GPIO_NEGEDGE, pio.P0_0 }, { cpu.INT_TMR_MATCH, tmr.VIRT0 } }
-- int_select will wait for either INT_GPIO_NEGEDGE or INT_TMR_MATCH to become active
print( int_select( ints ) )

Note that the two mechanisms (interrupt handlers and polling) can be used in parallel as long as an interrupt is not set with both cpu.hw_sei and cpu.sei, in which case the bevahiour is unpredictable. This is why it makes sense to write the int_select function above in Lua instead of C: it keeps the Lua VM running, so Lua interrupt handlers can be executed.

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );

 
  prev_handler = elua_int_set_c_handler( id, int_handler );
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )

 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;

  // Actual interrupt handler code comes here

 
}

--------------------------


Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );

 
  prev_handler = elua_int_set_c_handler( id, int_handler );
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )

 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;

  // Actual interrupt handler code comes here

 

-------------------------------

eLua interrupt support implementation

To add interrupt support for an eLua platform follow the steps below:

1.   Define your interrupts

Your interrupt sources should be defined in platform_conf.h with macros (don’t use C enumerations). The first one should have the value ELUA_INT_FIRST_ID (defined in inc/elua_int.h), the next one ELUA_INT_FIRST_ID + 1 and so on. Also, there should be a definition for a macro called INT_ELUA_LAST that must be equal to the largest interrupt source value. An example is given below:

#define INT_GPIO_POSEDGE      ELUA_INT_FIRST_ID
#define INT_GPIO_NEGEDGE      ( ELUA_INT_FIRST_ID + 1 )
#define INT_TMR_MATCH         ( ELUA_INT_FIRST_ID + 2 )
#define INT_ELUA_LAST         INT_TMR_MATCH

Note that the interrupt names aren’t random, they should follow a well defined pattern. Check here for details.

2.   Add them to the list of constants from the CPU module

Check the documentation of the CPU module for details.

3.   Implement your support functions

The actual implementation of the interrupt handlers is of course platform specific, so it can stay in the platform.c file. However, since interrupt handlers might require quite a bit of code, it is recommended to implement them in a separate file. The eLua convention is to use the platform_int.c file for this purpose. For each interrupt defined in step 1 above, 3 functions need to be implemented:

o  A function that enables or disables the interrupt and returns its previous state (enabled or disabled).

o  A function that checks if the interrupt is enabled or disabled.

o  A function that checks the interrupt pending flag and optionally clears it.

These functions are defined in inc/elua_int.h, which also defines an "int descriptor" type:


// Interrupt functions and descriptor
typedef int ( *elua_int_p_set_status )( elua_int_resnum resnum, int state );
typedef int ( *elua_int_p_get_status )( elua_int_resnum resnum );
typedef int ( *elua_int_p_get_flag )( elua_int_resnum resnum, int clear );

 
typedef struct
{
  elua_int_p_set_status int_set_status;
  elua_int_p_get_status int_get_status;
  elua_int_p_get_flag int_get_flag;
} elua_int_descriptor;

platform_int.c must have an array of elua_int_descriptor types named elua_int_table (remember to make it const to save RAM). The elements of this array must be in the same order as the interrupt sources. The interrupt table for the example from step 1 above might look like this:

const elua_int_descriptor elua_int_table[ INT_ELUA_LAST ] =
{
  { int_gpio_posedge_set_status, int_gpio_posedge_get_status, int_gpio_posedge_get_flag },
  { int_gpio_negedge_set_status, int_gpio_negedge_get_status, int_gpio_negedge_get_flag },

 
  { int_tmr_match_set_status, int_tmr_match_get_status, int_tmr_match_get_flag }
};

4.   Implement the init function

platform_int.c should implement a function named platform_int_init (defined in inc/platform.h) that must initialize all the required hardware and the internal data structures of the interrupt subsystem. This function should be called from platform_init.

5.   Implement the interrupt handlers

There are two simple requirements for the interrupt handlers: clear the hardware interrupt flag (if needed) and call cmn_int_handler (src/common.c) to connect the handler with the eLua interrupt code. An example is given below:

// EINT3 (INT_GPIO) interrupt handler
static void int_handler_eint3()
{
  elua_int_id id = ELUA_INT_INVALID_INTERRUPT;
  pio_code resnum = 0;
  int pidx, pin;

  EXTINT |= 1 << EINT3_BIT; // clear interrupt

 
  // Look for interrupt source
  // In can only be GPIO0/GPIO2, as the EXT interrupts are not (yet) used
  pidx = ( IO_INT_STAT & 1 ) ? 0 : 1;
  if( *posedge_status[ pidx ] )
  {
    id = INT_GPIO_POSEDGE;

 
    pin = intlog2( *posedge_status[ pidx ] );
  }
  else
  {
    id = INT_GPIO_NEGEDGE;
    pin = intlog2( *negedge_status[ pidx ] );
  }
  resnum = PLATFORM_IO_ENCODE( pidx * 2, pin, PLATFORM_IO_ENC_PIN );

 
  *intclr_regs[ pidx ] = 1 << pin;

  // Run the interrupt through eLua
  cmn_int_handler( id, resnum );
  VICVectAddr = 0; // ACK interrupt

 
}

That’s it. If you followed all these steps correctly, your platform should be fully able to support interrupt handlers (as described here). Check the lpc24xx platform implementation (src/platform/lpc24xx) for a full example.

Interrupt list and naming conventions

To ensure maximum portability and correct system behaviour, interrupt names (as defined in platform_conf.h) must follow a well-defined naming pattern. Please note that this isn’t merely a convention, many times the names must be properly chosen for the system to work properly. For example, the timer interrupt match will never happen on virtual timers if the timer interrupt match name isn’t INT_TMR_MATCH (see here for more details on how to use the timer match interrupt).

The naming rule is that the interrupt name must have the format INT_<peripheral>_<type>_, where:

·      peripheral is a symbolic name of the peripheral to which the interrupt applies.

·      type is a symbolic name of the interrupt type.

This restriction applies only to interrupt names. The value associated with the interrupt name (as defined in platform_conf.h) can vary from platform to platform, as long as it follows the rules outlined in step 1 above.

The table below lists all the valid interrupt names currently known to eLua. If you add a new interrupt don’t forget to update the table below.

Name

Meaning

INT_GPIO_POSEDGE

Interrupt on a positive edge on a GPIO pin

INT_GPIO_NEGEDGE

Interrupt on a negative edge on a GPIO pin

INT_TMR_MATCH

Interrupt on timer match

INT_UART_RX

Interrupt on UART character received


}

 

Thanks,
Lwazi


Best
Dado









 

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev



_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
John Hind John Hind
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

In reply to this post by Dado Sutter

Thanks, Dado! I would like to post a paper for comment with my ideas for coroutine based preemptive multitasking. This is not really a suitable forum for collaborative work in detail because different peoples comments do not get merged in the same place and things tend to disappear over the time horizon. WiKi would be a better place - are you OK with using the "user Labs" for this purpose, and if so, maybe we need a separate corner of it for development discussions rather than usage examples?

 

And you are right about the current system using the debug hooks in standard Lua. Also Robert has pointed out that you can even execute a coroutine yield from debug hook so I do not even actually need a patch to achieve what I'm trying to do. However the debug hooks are peppered with performance warnings in the Lua literature and this is extending them well beyond the original intention of the mechanism. I did not give a very good example of my concern number 1, making it look like the same concern as number 3. The syntax of Lua constrains where you could place a Yield instruction more than a debug hook would - i.e. you could not put a yield in the middle of a mathematical expression in the way that could happen with a debug hook. But in any case if the system requires the programmer to pepper their code with extra synchronisation instructions beyond what is explicitly necessary at the application level, it becomes an unattractive (and error prone!)  programming model.

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter
Sent: 26 January 2011 16:24
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello list,

On Wed, Jan 26, 2011 at 08:14, John Hind <[hidden email]> wrote:

I must say that having taken a close look at this mechanism and even developed it further to allow C interrupt handlers to force a coroutine yield between any two virtual machine opcodes, I became concerned about the safety of this approach. Incidently, if we can force coroutine yields safely in this way we already have all the mechanisms for an RTOS built-in to Lua in the threads provided for coroutines.


Thank you for diving into this John.

 

There are really three concerns I have (note that in the following, by "interrupt" I mean the eLua mechanism for "splicing" extra Lua VM opcodes at runtime, not asynchronous interrupts at the processor level):

 

1. Are we sure that Lua VM operations are always atomic at the opcode level?


I "think" they are, as Lua itself has a debug lib that (I think) uses the same hook mechanism.
 

For example if we interrupt in the middle of processing some complex mathematical expression and the code in the interrupt operates on some of the same variables?


On the minimalist Lua approach, I think this would be considered as a programmer's fault :) But in this case I must admit it would be a serious flaw.
Our initial INT mechanism allows one to disable (or stop queueing) interrupts during critical program sections but this may be not fair to transfer the responsability to users in all the cases.
 

2. Do we allow the interrupt code itself to be further interrupted? 

If so things could become very complex, if not, we have potentially unbounded latencies.

 

3. I believe we will need a Lua-level "critical section" operator which allows the Lua programmer to disable interrupts. For example, you might write a Ring Buffer structure using a Lua table and some pointers. You might want to read this buffer in interrupt code and write it in the main code. You cannot allow the interrupt code to run while the main code is in the middle of writing to the buffer (which will involve a table write, some bounds checks and a pointer write).


The initial INT support has instructions to enable/disable Lua handlers.
 

I feel we need someone who really understands the design of the Lua interpreter and can code-review what we are doing, because it will be virtually impossible to devise an adequate test set to prove this is safe in all cases.


Yep. It would be great to have more help for this and for the dev in general.
 

Incidentally, I remember being very impressed that the BASIC-STAMP microcontroller had preemptive multitasking integrated with the (interpreted) Basic language, this on an 8-bit chip with only a few hundred *bytes* of RAM. I was able to write a really good serial comms handler for GPS using this, which would have been virtually impossible without. A good RTOS can actually save RAM by avoiding the need for large RAM buffers for incoming data.


Cool. I hope we can learn and add such vodoo to eLua too.

Best
Dado




 

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter
Sent: 25 January 2011 21:53
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello guys,

On Tue, Jan 25, 2011 at 19:10, Lwazi <[hidden email]> wrote:

On Tue, Jan 25, 2011 at 2:27 PM, James Snyder <[hidden email]> wrote:


> On Tue, Jan 25, 2011 at 1:25 PM, James Snyder <[hidden email]> wrote:
>>

>> For the microcontroller platforms we run on, an RTOS would generally
>> just add overhead.  In our upcoming release we will support basic
>> concurrency in more of an event-driven manner using interrupts which
>> are
>
> Ooops.. hit the send button when I was going to save this and finish
> editing later.
>
> To finish:
> The interrupts support both Lua and C handlers, C handlers execute
> immediately and Lua handlers can interrupt currently running Lua code,
> but they have to wait a few instructions before execution (so latency
> isn't going to be constant or really low).
>

I like this. Is this documented anywhere?

Yes, this is new eLua feature and the initial doc will be release with v0.8
LPC24xx implementatin can be seen on trunk (or branch pre0.8) at  src/platform/lpc24xx

What happens to the rest of
the system during this 'wait'?


You mean, the aparent-latency wait until we actually handle the INT in Lua ? The Lua VM is executing during this. I think the hook checks for any queued INT to handle every 2 or 3 VM bytecodes.
 

I presume the ISR and other interrupts
do not get blocked while the Lua handler waits to run. 

And I have to
ask if there is a guarantee that the Lua handler will run


   As a preview of the doc to come, I'll paste here some infos about the Interrupt mechanism v0.8 will bring officially to eLua.
   Comments and reviews are welcomed.
   (and yes, the doc edition/refinement by the community will be easier in the future :)

Best
Dado

eLua interrupt handlers

Starting with version 0.8, eLua supports interrupt handlers written in Lua. Once an interrupt handler is set in the Lua code, it will be called each time a supported interrupt is generated. A supported interrupt is any interrupt that is handled by the platform C code (see here for more details).

IMPORTANT: before learning how to use interrupt handlers in Lua, please keep in mind that Lua interrupt handlers don’t work the same way as regular (C) interrupt handlers. As Lua doesn’t have direct suport for interrupts, they have to be emulated. eLua emulates them using a queue that is populated with interrupt data by the C support code. As long as the queue is not empty, a Lua hook is set to run every 2 Lua bytecode instructions. This hook function is the Lua interrupt handler. After all the interrupts are handled and the queue is emptied, the hook is automatically disabled. Consequently:

·      When the interrupt queue is full (a situation that might appear when interrupts are added to the queue faster than the Lua code can handle them) subsequent interrupts are ignored (not added to the queue) and an error message is printed on the eLua console device. The interrupt queue size can be configured at build time, as explained here. Even if the interrupt queue is large, one most remember that Lua code is significantly slower than C code, thus not all C interrupts make suitable candidates for Lua interrupt handlers. For example, a serial interrupt that is generated each time a char is received at 115200 baud might be too fast for Lua (this is largely dependent on the platform). On the other hand, a GPIO interrupt-on-change on a GPIO line connected with a matrix keyboard is a very good candidate for a Lua handler. Experimenting with different interrupt types is the best way to find the interrupts that work well with Lua.

·      A more subtle point is that the Lua virtual machine must run for the interrupt handlers to work. A simple analogy is that a CPU must have a running clock in order to function properly (and in order to take care of the hardware interrupts). If the clock is stopped, the CPU doesn’t run and the interrupt handlers aren’t called anymore, although the occurence of the interrupt might be recorded inside the CPU. This is the exact same situation with Lua: if the virtual machine doesn’t run, the interrupts are still recorded in the interrupt queue, but the Lua handler won’t be called until the virtual machine runs again. In this case though, the "clock" of the Lua VM is a C function that is executed for every VM instruction. If this function blocks for some reason, the VM instructions are not executed anymore. It’s not hard to make this function block; for example, it blocks everytime the Lua code waits for some user input at the console, or when a TODO tmr.delay is executed, or when TODO uart.read is called with an infinite or very large timeout; in general, any function from a Lua library that doesn’t return immediately (or after a short ammount of time) will block the VM. Care must be taken to avoid such operations as much as possible, otherwise the interrupt support code won’t run properly.

·      There is a single interrupt handler per interrupt type in Lua (the same holds true for C interrupt support), as opposed to the many hardware interrupts handlers usually found on the eLua targets. It is however easy to differentiate between different interrupt sources, as will be explained in the next paragraph.

·      Lua interrupt handlers are never reentrant.

While this might seem restrictive, Lua interrupt handlers work quite well in practical situations. As an added bonus, since they are implemented by C support code, there’s nothing preventing eLua from implementing "custom interrupts" (software generated interrupts that don’t correspond to a hardware interrupt on the CPU), such as serial interrupt on char match (generate an interrupt when a certain char is received on the serial port, for example a newline), timer interrupts for virtual timers, TCP/UDP data packet received interrupt and many others.

Using interrupt handlers in Lua

To enable Lua interrupt handler, define BUILD_LUA_INT_HANDLERS and PLTATFORM_INT_QUEUE_LOG_SIZE in platform_conf.h (see here for details). Setting up interrupt handlers is a straightforward process, most of the required functionality is provided by the CPU module:

·      use cpu.set_int_handler( int_id, handler ) to set the interrupt handler function for the specified interrupt (call with nil to disable the interrupt handler for that interrupt). cpu.set_int_handler returns the previous interrupt handler for int_id (or nil is an interrupt handler was not previously set for the interrupt). In most cases, your interrupt handler should call the previous handler to ensure proper interrupt management.

·      use cpu.sei( int_id, resnum1, [resnum2], …, [resnumn]) and cpu.cli( int_id, resnum1, [resnum2], …, [resnumn]) to enable/disable specific CPU interrupts that will trigger the interrupt handler. You can also use cpu.sei() and cpu.cli (without parameters) to enable/disable global interrupts on the CPU, although this is not recommended.

The interrupt handler receives the resource ID that specifies the resource that fired the interrupt. It can be a timer ID for a timer overflow interrupt, a GPIO port/pin combination for a GPIO interrupt on pin change, a SPI interface ID for a SPI data available interrupt, and so on.

An example that uses the above concepts and knows how to handle two different interrupt types is presented below:

local vtmrid = tmr.VIRT0
local to = 1500000



local prev_tmr, new_prev_tmr, prev_gpio

-- This is the timer interrupt handler
local function tmr_handler( resnum )
 
  print( string.format( "Timer interrupt for id %d", resnum ) )

 
  if prev_tmr then prev_tmr( resnum ) end
end

-- This is the timer interrupt handler that gets set after tmr_handler
local function new_tmr_handler( resnum )

 
 
 
  print( string.format( "NEW HANDLER: timer interrupt for id %d", resnum ) )
  -- This will chain to the previous interrupt handler (tmr_handler above)

 
  if new_prev_tmr then new_prev_tmr( resnum ) end
 
end

-- This is the GPIO interrupt on change (falling edge) interrupt
local function gpio_negedge_handler( id, resnum )

 
    local port, pin = pio.decode( resnum )
 
  print( string.format( "GPIO NEGEDGE interrupt on port %d, pin %d", port, pin ) )
  if prev_gpio then prev_gpio( resnum ) end

 
end

-- Set timer interrupt handler
prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, tmr_handler )
 
-- Set GPIO interrupt on change (negative edge) interrupt handler

 
prev_gpio = cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, gpio_negedge_handler )
-- Setup periodic timer interrupt for virtual timer 0
 
tmr.set_match_int( vtmrid, to, tmr.INT_CYCLIC )

 
-- Enable GPIO interrupt on change (negative edge) for pin 0 of port 0
cpu.sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
 
-- Enable timer match interrupt on virtual timer 0

 
cpu.sei( cpu.INT_TMR_MATCH, vtmrid )

local tmrid, count = 0, 0
while true do
  print "Outside interrupt"
 
 
 
  for i = 1, 1000 do tmr.delay( tmrid, 1000 ) end
  if uart.getchar( uartid, 0 ) ~= "" then break end
  count = count + 1
  if count == 5 then
    print "Changing timer interrupt handler"

 
 
 
    new_prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, new_tmr_handler )
  end
end

-- Cleanup
-- Stop the timer from generating periodic interrupts

 
tmr.set_match_int( vtmrid, 0, tmr.INT_CYCLIC );
 
-- Disable the GPIO interrupt on change (negative edge) interrupt
cpu.cli( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )

 
-- Disable the timer interrupt on match interrupt
cpu.cli( cpu.INT_TMR_MATCH, vtmrid )
 
-- Clear the timer interrupt handler
cpu.set_int_handler( cpu.INT_TMR_MATCH, nil );

 
-- Clear the GPIO interrupt handler
cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, nil );

This is the most common use case for Lua interrupts, but it’s not the only one. Another way to use interrupts from eLua uses polling instead of interrupt handlers: directly check the interrupt flags and execute a certain action when one of them becomes set. For this, use the cpu.get_int_flag( id, resnum, [clear] ) function from the CPU module, which returns the specified interrupt’s status for resource resnum. clear is an optional boolean parameter, specifying if the interrupt flag should be cleared if it is set. It defaults to true, and in most cases it shouldn’t be changed. Using this feature, it becomes easy to wait for one or more interrupt flag(s) to be set. To use interrupt polling:

·      Enable/disable interrupts to be polled with cpu.hw_sei/cpu.hw_cli instead of cpu.sei/cpu.cli. These functions enable/disable interrupts only in hardware, as opposed to cpu.sei/cpu.cli that also set/clear an internal flag which makes the interrupt able to trigger a Lua handler.

·      Use cpu.get_int_flag to get the interrupt flag.

The int_select function below is a possible implementation of a function that gets an array of interrupts and returns the first one that gets active:

function int_select( int_table )

 
  while true do
    for i = 1, #int_table do
      local t = int_table[ i ]
      if cpu.get_int_flag[ t[ 1 ], t[ 2 ] ) then
 
        return t[ 1 ], t[ 2 ]
      end
    end
end
end

cpu.hw_sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
cpu.hw_sei( cpu.INT_TMR_MATCH, tmr.VIRT0 )

 
 
 
local ints = { { cpu.INT_GPIO_NEGEDGE, pio.P0_0 }, { cpu.INT_TMR_MATCH, tmr.VIRT0 } }
-- int_select will wait for either INT_GPIO_NEGEDGE or INT_TMR_MATCH to become active

 
print( int_select( ints ) )

Note that the two mechanisms (interrupt handlers and polling) can be used in parallel as long as an interrupt is not set with both cpu.hw_sei and cpu.sei, in which case the bevahiour is unpredictable. This is why it makes sense to write the int_select function above in Lua instead of C: it keeps the Lua VM running, so Lua interrupt handlers can be executed.

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{

 
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );
 
  prev_handler = elua_int_set_c_handler( id, int_handler );

 
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )
 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;

 

  // Actual interrupt handler code comes here
 
}

--------------------------

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

 

void module_init()
{
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );
 
  prev_handler = elua_int_set_c_handler( id, int_handler );

 
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )
 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;

 

  // Actual interrupt handler code comes here
 

-------------------------------

eLua interrupt support implementation

To add interrupt support for an eLua platform follow the steps below:

1.   Define your interrupts

Your interrupt sources should be defined in platform_conf.h with macros (don’t use C enumerations). The first one should have the value ELUA_INT_FIRST_ID (defined in inc/elua_int.h), the next one ELUA_INT_FIRST_ID + 1 and so on. Also, there should be a definition for a macro called INT_ELUA_LAST that must be equal to the largest interrupt source value. An example is given below:

#define INT_GPIO_POSEDGE      ELUA_INT_FIRST_ID
#define INT_GPIO_NEGEDGE      ( ELUA_INT_FIRST_ID + 1 )
#define INT_TMR_MATCH         ( ELUA_INT_FIRST_ID + 2 )

 
#define INT_ELUA_LAST         INT_TMR_MATCH

Note that the interrupt names aren’t random, they should follow a well defined pattern. Check here for details.

2.   Add them to the list of constants from the CPU module

Check the documentation of the CPU module for details.

3.   Implement your support functions

The actual implementation of the interrupt handlers is of course platform specific, so it can stay in the platform.c file. However, since interrupt handlers might require quite a bit of code, it is recommended to implement them in a separate file. The eLua convention is to use the platform_int.c file for this purpose. For each interrupt defined in step 1 above, 3 functions need to be implemented:

o  A function that enables or disables the interrupt and returns its previous state (enabled or disabled).

o  A function that checks if the interrupt is enabled or disabled.

o  A function that checks the interrupt pending flag and optionally clears it.

These functions are defined in inc/elua_int.h, which also defines an "int descriptor" type:

 
 
// Interrupt functions and descriptor
typedef int ( *elua_int_p_set_status )( elua_int_resnum resnum, int state );
typedef int ( *elua_int_p_get_status )( elua_int_resnum resnum );
typedef int ( *elua_int_p_get_flag )( elua_int_resnum resnum, int clear );

 
 
 
typedef struct
{
  elua_int_p_set_status int_set_status;
  elua_int_p_get_status int_get_status;

 
  elua_int_p_get_flag int_get_flag;
} elua_int_descriptor;

platform_int.c must have an array of elua_int_descriptor types named elua_int_table (remember to make it const to save RAM). The elements of this array must be in the same order as the interrupt sources. The interrupt table for the example from step 1 above might look like this:

const elua_int_descriptor elua_int_table[ INT_ELUA_LAST ] =
{
  { int_gpio_posedge_set_status, int_gpio_posedge_get_status, int_gpio_posedge_get_flag },

 
  { int_gpio_negedge_set_status, int_gpio_negedge_get_status, int_gpio_negedge_get_flag },
 
  { int_tmr_match_set_status, int_tmr_match_get_status, int_tmr_match_get_flag }

 
};

4.   Implement the init function

platform_int.c should implement a function named platform_int_init (defined in inc/platform.h) that must initialize all the required hardware and the internal data structures of the interrupt subsystem. This function should be called from platform_init.

5.   Implement the interrupt handlers

There are two simple requirements for the interrupt handlers: clear the hardware interrupt flag (if needed) and call cmn_int_handler (src/common.c) to connect the handler with the eLua interrupt code. An example is given below:

// EINT3 (INT_GPIO) interrupt handler
static void int_handler_eint3()
{
  elua_int_id id = ELUA_INT_INVALID_INTERRUPT;
  pio_code resnum = 0;

 
  int pidx, pin;

  EXTINT |= 1 << EINT3_BIT; // clear interrupt
 
  // Look for interrupt source

 
  // In can only be GPIO0/GPIO2, as the EXT interrupts are not (yet) used
  pidx = ( IO_INT_STAT & 1 ) ? 0 : 1;
  if( *posedge_status[ pidx ] )
  {
    id = INT_GPIO_POSEDGE;

 
 
 
    pin = intlog2( *posedge_status[ pidx ] );
  }
  else
  {
    id = INT_GPIO_NEGEDGE;

 
    pin = intlog2( *negedge_status[ pidx ] );
  }
  resnum = PLATFORM_IO_ENCODE( pidx * 2, pin, PLATFORM_IO_ENC_PIN );
 
  *intclr_regs[ pidx ] = 1 << pin;

  // Run the interrupt through eLua
  cmn_int_handler( id, resnum );

 
  VICVectAddr = 0; // ACK interrupt
 
}

That’s it. If you followed all these steps correctly, your platform should be fully able to support interrupt handlers (as described here). Check the lpc24xx platform implementation (src/platform/lpc24xx) for a full example.

Interrupt list and naming conventions

To ensure maximum portability and correct system behaviour, interrupt names (as defined in platform_conf.h) must follow a well-defined naming pattern. Please note that this isn’t merely a convention, many times the names must be properly chosen for the system to work properly. For example, the timer interrupt match will never happen on virtual timers if the timer interrupt match name isn’t INT_TMR_MATCH (see here for more details on how to use the timer match interrupt).

The naming rule is that the interrupt name must have the format INT_<peripheral>_<type>_, where:

·      peripheral is a symbolic name of the peripheral to which the interrupt applies.

·      type is a symbolic name of the interrupt type.

This restriction applies only to interrupt names. The value associated with the interrupt name (as defined in platform_conf.h) can vary from platform to platform, as long as it follows the rules outlined in step 1 above.

The table below lists all the valid interrupt names currently known to eLua. If you add a new interrupt don’t forget to update the table below.

Name

Meaning

INT_GPIO_POSEDGE

Interrupt on a positive edge on a GPIO pin

INT_GPIO_NEGEDGE

Interrupt on a negative edge on a GPIO pin

INT_TMR_MATCH

Interrupt on timer match

INT_UART_RX

Interrupt on UART character received


}

 

Thanks,
Lwazi


Best
Dado









 

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
John Hind John Hind
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

In reply to this post by BogdanM

 

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Bogdan Marinescu
Sent: 26 January 2011 17:02
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

 Incidentally, I remember being very impressed that the BASIC-STAMP microcontroller had preemptive multitasking integrated with the (interpreted) Basic language, this on an 8-bit chip with only a few hundred *bytes* of RAM. I was able to write a really good serial comms handler for GPS using this, which would have been virtually impossible without. A good RTOS can actually save RAM by avoiding the need for large RAM buffers for incoming data.

 

This is true in general, but probably not so much when running a virtual machine. A simple example for the GPS driver you mentioned ealier: take a serial port configured at 115200 baud. You receive a number of bytes on this serial port from a GPS. If you were to do this with eLua you'd have to call your serial interrupt handler 11520 times per second. Might be just a bit too much for eLua; it might or it might not be quick enough to get all the bytes (this is of course largely dependent on the platform and I can't make any estimation about the actual resources you'd need to acomplish this). Now imagine that you have a serial driver written in C that will call your Lua interrupt handler when an event happens: for example when a configurable number of bytes were received (like a FIFO) or when a certain character was received on the input stream. In our GPS case scenario we can use the second option: the C driver will send an interrupt when a '\n' is received (which ends the GPS sentence). In turn eLua will acknowledge this interrupt and read all the sentence (which is already stored in the C driver's buffers), then parse it and do whatever it needs with it. Of course the C driver is free to read the following GPS sentence while eLua processes the current sentence. Personally I find this model much more suitable for a system that runs around a VM and needs to handle real time data communication.

 

[John Hind] Yes, a well designed com port driver can provide for the most common and simple packet designs such as fixed length or variable length with a terminating control code. But even with a GPS sentence, you need to provide a RAM buffer for a complete sentence while a byte-by-byte approach only requires buffering for the data you need in the application. In my case, I simply slowed the baud rate down until I could handle it byte-by-byte - no point in transferring one update per second in a microsecond! Also it is hard to cope with more complex cases of the sort than need a state-machine parser. If you have to write specialised device drivers and compile an application specific image for most applications, it severely reduces the appeal of the virtual machine approach.

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
BogdanM BogdanM
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

In reply to this post by BogdanM
> If you have to write specialised device
> drivers and compile an application specific image for most applications, it
> severely reduces the appeal of the virtual machine approach.

This is very true and eLua tries to cope with this in two major ways:

- making the API and services as generic as possible so one can
implement any kind of application on top of them
- introducing loadable binary modules support in the future :) (by
which I don't mean just Lua modules but generic components that can be
loaded and used at runtime as needed).

Best,
Bogdan
_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
John Hind John Hind
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

In reply to this post by John Hind

I did some more digging around the web site and I guess I should post this on the WiKi at tracker.eluaproject.net. I have registered for this, but I cannot work out how to edit the WiKi - do I need to join the project to get this functionality? If so, how do I do that? Do I need one of the admins to authorise me?

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of John Hind
Sent: 28 January 2011 10:17
To: 'eLua Users and Development List (www.eluaproject.net)'
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Thanks, Dado! I would like to post a paper for comment with my ideas for coroutine based preemptive multitasking. This is not really a suitable forum for collaborative work in detail because different peoples comments do not get merged in the same place and things tend to disappear over the time horizon. WiKi would be a better place - are you OK with using the "user Labs" for this purpose, and if so, maybe we need a separate corner of it for development discussions rather than usage examples?

 

And you are right about the current system using the debug hooks in standard Lua. Also Robert has pointed out that you can even execute a coroutine yield from debug hook so I do not even actually need a patch to achieve what I'm trying to do. However the debug hooks are peppered with performance warnings in the Lua literature and this is extending them well beyond the original intention of the mechanism. I did not give a very good example of my concern number 1, making it look like the same concern as number 3. The syntax of Lua constrains where you could place a Yield instruction more than a debug hook would - i.e. you could not put a yield in the middle of a mathematical expression in the way that could happen with a debug hook. But in any case if the system requires the programmer to pepper their code with extra synchronisation instructions beyond what is explicitly necessary at the application level, it becomes an unattractive (and error prone!)  programming model.

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter
Sent: 26 January 2011 16:24
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello list,

On Wed, Jan 26, 2011 at 08:14, John Hind <[hidden email]> wrote:

I must say that having taken a close look at this mechanism and even developed it further to allow C interrupt handlers to force a coroutine yield between any two virtual machine opcodes, I became concerned about the safety of this approach. Incidently, if we can force coroutine yields safely in this way we already have all the mechanisms for an RTOS built-in to Lua in the threads provided for coroutines.


Thank you for diving into this John.

 

There are really three concerns I have (note that in the following, by "interrupt" I mean the eLua mechanism for "splicing" extra Lua VM opcodes at runtime, not asynchronous interrupts at the processor level):

 

1. Are we sure that Lua VM operations are always atomic at the opcode level?


I "think" they are, as Lua itself has a debug lib that (I think) uses the same hook mechanism.
 

For example if we interrupt in the middle of processing some complex mathematical expression and the code in the interrupt operates on some of the same variables?


On the minimalist Lua approach, I think this would be considered as a programmer's fault :) But in this case I must admit it would be a serious flaw.
Our initial INT mechanism allows one to disable (or stop queueing) interrupts during critical program sections but this may be not fair to transfer the responsability to users in all the cases.
 

2. Do we allow the interrupt code itself to be further interrupted? 

If so things could become very complex, if not, we have potentially unbounded latencies.

 

3. I believe we will need a Lua-level "critical section" operator which allows the Lua programmer to disable interrupts. For example, you might write a Ring Buffer structure using a Lua table and some pointers. You might want to read this buffer in interrupt code and write it in the main code. You cannot allow the interrupt code to run while the main code is in the middle of writing to the buffer (which will involve a table write, some bounds checks and a pointer write).


The initial INT support has instructions to enable/disable Lua handlers.
 

I feel we need someone who really understands the design of the Lua interpreter and can code-review what we are doing, because it will be virtually impossible to devise an adequate test set to prove this is safe in all cases.


Yep. It would be great to have more help for this and for the dev in general.
 

Incidentally, I remember being very impressed that the BASIC-STAMP microcontroller had preemptive multitasking integrated with the (interpreted) Basic language, this on an 8-bit chip with only a few hundred *bytes* of RAM. I was able to write a really good serial comms handler for GPS using this, which would have been virtually impossible without. A good RTOS can actually save RAM by avoiding the need for large RAM buffers for incoming data.


Cool. I hope we can learn and add such vodoo to eLua too.

Best
Dado




 

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter
Sent: 25 January 2011 21:53
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello guys,

On Tue, Jan 25, 2011 at 19:10, Lwazi <[hidden email]> wrote:

On Tue, Jan 25, 2011 at 2:27 PM, James Snyder <[hidden email]> wrote:


> On Tue, Jan 25, 2011 at 1:25 PM, James Snyder <[hidden email]> wrote:
>>

>> For the microcontroller platforms we run on, an RTOS would generally
>> just add overhead.  In our upcoming release we will support basic
>> concurrency in more of an event-driven manner using interrupts which
>> are
>
> Ooops.. hit the send button when I was going to save this and finish
> editing later.
>
> To finish:
> The interrupts support both Lua and C handlers, C handlers execute
> immediately and Lua handlers can interrupt currently running Lua code,
> but they have to wait a few instructions before execution (so latency
> isn't going to be constant or really low).
>

I like this. Is this documented anywhere?

Yes, this is new eLua feature and the initial doc will be release with v0.8
LPC24xx implementatin can be seen on trunk (or branch pre0.8) at  src/platform/lpc24xx

What happens to the rest of
the system during this 'wait'?


You mean, the aparent-latency wait until we actually handle the INT in Lua ? The Lua VM is executing during this. I think the hook checks for any queued INT to handle every 2 or 3 VM bytecodes.
 

I presume the ISR and other interrupts
do not get blocked while the Lua handler waits to run. 

And I have to
ask if there is a guarantee that the Lua handler will run


   As a preview of the doc to come, I'll paste here some infos about the Interrupt mechanism v0.8 will bring officially to eLua.
   Comments and reviews are welcomed.
   (and yes, the doc edition/refinement by the community will be easier in the future :)

Best
Dado

eLua interrupt handlers

Starting with version 0.8, eLua supports interrupt handlers written in Lua. Once an interrupt handler is set in the Lua code, it will be called each time a supported interrupt is generated. A supported interrupt is any interrupt that is handled by the platform C code (see here for more details).

IMPORTANT: before learning how to use interrupt handlers in Lua, please keep in mind that Lua interrupt handlers don’t work the same way as regular (C) interrupt handlers. As Lua doesn’t have direct suport for interrupts, they have to be emulated. eLua emulates them using a queue that is populated with interrupt data by the C support code. As long as the queue is not empty, a Lua hook is set to run every 2 Lua bytecode instructions. This hook function is the Lua interrupt handler. After all the interrupts are handled and the queue is emptied, the hook is automatically disabled. Consequently:

·      When the interrupt queue is full (a situation that might appear when interrupts are added to the queue faster than the Lua code can handle them) subsequent interrupts are ignored (not added to the queue) and an error message is printed on the eLua console device. The interrupt queue size can be configured at build time, as explained here. Even if the interrupt queue is large, one most remember that Lua code is significantly slower than C code, thus not all C interrupts make suitable candidates for Lua interrupt handlers. For example, a serial interrupt that is generated each time a char is received at 115200 baud might be too fast for Lua (this is largely dependent on the platform). On the other hand, a GPIO interrupt-on-change on a GPIO line connected with a matrix keyboard is a very good candidate for a Lua handler. Experimenting with different interrupt types is the best way to find the interrupts that work well with Lua.

·      A more subtle point is that the Lua virtual machine must run for the interrupt handlers to work. A simple analogy is that a CPU must have a running clock in order to function properly (and in order to take care of the hardware interrupts). If the clock is stopped, the CPU doesn’t run and the interrupt handlers aren’t called anymore, although the occurence of the interrupt might be recorded inside the CPU. This is the exact same situation with Lua: if the virtual machine doesn’t run, the interrupts are still recorded in the interrupt queue, but the Lua handler won’t be called until the virtual machine runs again. In this case though, the "clock" of the Lua VM is a C function that is executed for every VM instruction. If this function blocks for some reason, the VM instructions are not executed anymore. It’s not hard to make this function block; for example, it blocks everytime the Lua code waits for some user input at the console, or when a TODO tmr.delay is executed, or when TODO uart.read is called with an infinite or very large timeout; in general, any function from a Lua library that doesn’t return immediately (or after a short ammount of time) will block the VM. Care must be taken to avoid such operations as much as possible, otherwise the interrupt support code won’t run properly.

·      There is a single interrupt handler per interrupt type in Lua (the same holds true for C interrupt support), as opposed to the many hardware interrupts handlers usually found on the eLua targets. It is however easy to differentiate between different interrupt sources, as will be explained in the next paragraph.

·      Lua interrupt handlers are never reentrant.

While this might seem restrictive, Lua interrupt handlers work quite well in practical situations. As an added bonus, since they are implemented by C support code, there’s nothing preventing eLua from implementing "custom interrupts" (software generated interrupts that don’t correspond to a hardware interrupt on the CPU), such as serial interrupt on char match (generate an interrupt when a certain char is received on the serial port, for example a newline), timer interrupts for virtual timers, TCP/UDP data packet received interrupt and many others.

Using interrupt handlers in Lua

To enable Lua interrupt handler, define BUILD_LUA_INT_HANDLERS and PLTATFORM_INT_QUEUE_LOG_SIZE in platform_conf.h (see here for details). Setting up interrupt handlers is a straightforward process, most of the required functionality is provided by the CPU module:

·      use cpu.set_int_handler( int_id, handler ) to set the interrupt handler function for the specified interrupt (call with nil to disable the interrupt handler for that interrupt). cpu.set_int_handler returns the previous interrupt handler for int_id (or nil is an interrupt handler was not previously set for the interrupt). In most cases, your interrupt handler should call the previous handler to ensure proper interrupt management.

·      use cpu.sei( int_id, resnum1, [resnum2], …, [resnumn]) and cpu.cli( int_id, resnum1, [resnum2], …, [resnumn]) to enable/disable specific CPU interrupts that will trigger the interrupt handler. You can also use cpu.sei() and cpu.cli (without parameters) to enable/disable global interrupts on the CPU, although this is not recommended.

The interrupt handler receives the resource ID that specifies the resource that fired the interrupt. It can be a timer ID for a timer overflow interrupt, a GPIO port/pin combination for a GPIO interrupt on pin change, a SPI interface ID for a SPI data available interrupt, and so on.

An example that uses the above concepts and knows how to handle two different interrupt types is presented below:

local vtmrid = tmr.VIRT0
local to = 1500000
 
local prev_tmr, new_prev_tmr, prev_gpio

-- This is the timer interrupt handler
local function tmr_handler( resnum )
 
  print( string.format( "Timer interrupt for id %d", resnum ) )
 
  if prev_tmr then prev_tmr( resnum ) end
end

-- This is the timer interrupt handler that gets set after tmr_handler
local function new_tmr_handler( resnum )
 
 
 
  print( string.format( "NEW HANDLER: timer interrupt for id %d", resnum ) )
  -- This will chain to the previous interrupt handler (tmr_handler above)
 
  if new_prev_tmr then new_prev_tmr( resnum ) end
 
end

-- This is the GPIO interrupt on change (falling edge) interrupt
local function gpio_negedge_handler( id, resnum )
 
    local port, pin = pio.decode( resnum )
 
  print( string.format( "GPIO NEGEDGE interrupt on port %d, pin %d", port, pin ) )
  if prev_gpio then prev_gpio( resnum ) end
 
end

-- Set timer interrupt handler
prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, tmr_handler )
 
-- Set GPIO interrupt on change (negative edge) interrupt handler
 
prev_gpio = cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, gpio_negedge_handler )
-- Setup periodic timer interrupt for virtual timer 0
 
tmr.set_match_int( vtmrid, to, tmr.INT_CYCLIC )
 
-- Enable GPIO interrupt on change (negative edge) for pin 0 of port 0
cpu.sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
 
-- Enable timer match interrupt on virtual timer 0
 
cpu.sei( cpu.INT_TMR_MATCH, vtmrid )

local tmrid, count = 0, 0
while true do
  print "Outside interrupt"
 
 
 
  for i = 1, 1000 do tmr.delay( tmrid, 1000 ) end
  if uart.getchar( uartid, 0 ) ~= "" then break end
  count = count + 1
  if count == 5 then
    print "Changing timer interrupt handler"
 
 
 
    new_prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, new_tmr_handler )
  end
end

-- Cleanup
-- Stop the timer from generating periodic interrupts
 
tmr.set_match_int( vtmrid, 0, tmr.INT_CYCLIC );
 
-- Disable the GPIO interrupt on change (negative edge) interrupt
cpu.cli( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
 
-- Disable the timer interrupt on match interrupt
cpu.cli( cpu.INT_TMR_MATCH, vtmrid )
 
-- Clear the timer interrupt handler
cpu.set_int_handler( cpu.INT_TMR_MATCH, nil );
 
-- Clear the GPIO interrupt handler
cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, nil );

This is the most common use case for Lua interrupts, but it’s not the only one. Another way to use interrupts from eLua uses polling instead of interrupt handlers: directly check the interrupt flags and execute a certain action when one of them becomes set. For this, use the cpu.get_int_flag( id, resnum, [clear] ) function from the CPU module, which returns the specified interrupt’s status for resource resnum. clear is an optional boolean parameter, specifying if the interrupt flag should be cleared if it is set. It defaults to true, and in most cases it shouldn’t be changed. Using this feature, it becomes easy to wait for one or more interrupt flag(s) to be set. To use interrupt polling:

·      Enable/disable interrupts to be polled with cpu.hw_sei/cpu.hw_cli instead of cpu.sei/cpu.cli. These functions enable/disable interrupts only in hardware, as opposed to cpu.sei/cpu.cli that also set/clear an internal flag which makes the interrupt able to trigger a Lua handler.

·      Use cpu.get_int_flag to get the interrupt flag.

The int_select function below is a possible implementation of a function that gets an array of interrupts and returns the first one that gets active:

function int_select( int_table )
 
  while true do
    for i = 1, #int_table do
      local t = int_table[ i ]
      if cpu.get_int_flag[ t[ 1 ], t[ 2 ] ) then
 
        return t[ 1 ], t[ 2 ]
      end
    end
end
end

cpu.hw_sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
cpu.hw_sei( cpu.INT_TMR_MATCH, tmr.VIRT0 )
 
 
 
local ints = { { cpu.INT_GPIO_NEGEDGE, pio.P0_0 }, { cpu.INT_TMR_MATCH, tmr.VIRT0 } }
-- int_select will wait for either INT_GPIO_NEGEDGE or INT_TMR_MATCH to become active
 
print( int_select( ints ) )

Note that the two mechanisms (interrupt handlers and polling) can be used in parallel as long as an interrupt is not set with both cpu.hw_sei and cpu.sei, in which case the bevahiour is unpredictable. This is why it makes sense to write the int_select function above in Lua instead of C: it keeps the Lua VM running, so Lua interrupt handlers can be executed.

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{
 
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );
 
  prev_handler = elua_int_set_c_handler( id, int_handler );
 
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )
 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;
 

  // Actual interrupt handler code comes here
 
}

--------------------------

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );
 

void module_init()
{
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );
 
  prev_handler = elua_int_set_c_handler( id, int_handler );
 
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )
 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;
 

  // Actual interrupt handler code comes here
 

-------------------------------

eLua interrupt support implementation

To add interrupt support for an eLua platform follow the steps below:

1.   Define your interrupts

Your interrupt sources should be defined in platform_conf.h with macros (don’t use C enumerations). The first one should have the value ELUA_INT_FIRST_ID (defined in inc/elua_int.h), the next one ELUA_INT_FIRST_ID + 1 and so on. Also, there should be a definition for a macro called INT_ELUA_LAST that must be equal to the largest interrupt source value. An example is given below:

#define INT_GPIO_POSEDGE      ELUA_INT_FIRST_ID
#define INT_GPIO_NEGEDGE      ( ELUA_INT_FIRST_ID + 1 )
#define INT_TMR_MATCH         ( ELUA_INT_FIRST_ID + 2 )
 
#define INT_ELUA_LAST         INT_TMR_MATCH

Note that the interrupt names aren’t random, they should follow a well defined pattern. Check here for details.

2.   Add them to the list of constants from the CPU module

Check the documentation of the CPU module for details.

3.   Implement your support functions

The actual implementation of the interrupt handlers is of course platform specific, so it can stay in the platform.c file. However, since interrupt handlers might require quite a bit of code, it is recommended to implement them in a separate file. The eLua convention is to use the platform_int.c file for this purpose. For each interrupt defined in step 1 above, 3 functions need to be implemented:

o  A function that enables or disables the interrupt and returns its previous state (enabled or disabled).

o  A function that checks if the interrupt is enabled or disabled.

o  A function that checks the interrupt pending flag and optionally clears it.

These functions are defined in inc/elua_int.h, which also defines an "int descriptor" type:

 
 
// Interrupt functions and descriptor
typedef int ( *elua_int_p_set_status )( elua_int_resnum resnum, int state );
typedef int ( *elua_int_p_get_status )( elua_int_resnum resnum );
typedef int ( *elua_int_p_get_flag )( elua_int_resnum resnum, int clear );
 
 
 
typedef struct
{
  elua_int_p_set_status int_set_status;
  elua_int_p_get_status int_get_status;
 
  elua_int_p_get_flag int_get_flag;
} elua_int_descriptor;

platform_int.c must have an array of elua_int_descriptor types named elua_int_table (remember to make it const to save RAM). The elements of this array must be in the same order as the interrupt sources. The interrupt table for the example from step 1 above might look like this:

const elua_int_descriptor elua_int_table[ INT_ELUA_LAST ] =
{
  { int_gpio_posedge_set_status, int_gpio_posedge_get_status, int_gpio_posedge_get_flag },
 
  { int_gpio_negedge_set_status, int_gpio_negedge_get_status, int_gpio_negedge_get_flag },
 
  { int_tmr_match_set_status, int_tmr_match_get_status, int_tmr_match_get_flag }
 
};

4.   Implement the init function

platform_int.c should implement a function named platform_int_init (defined in inc/platform.h) that must initialize all the required hardware and the internal data structures of the interrupt subsystem. This function should be called from platform_init.

5.   Implement the interrupt handlers

There are two simple requirements for the interrupt handlers: clear the hardware interrupt flag (if needed) and call cmn_int_handler (src/common.c) to connect the handler with the eLua interrupt code. An example is given below:

// EINT3 (INT_GPIO) interrupt handler
static void int_handler_eint3()
{
  elua_int_id id = ELUA_INT_INVALID_INTERRUPT;
  pio_code resnum = 0;
 
  int pidx, pin;

  EXTINT |= 1 << EINT3_BIT; // clear interrupt
 
  // Look for interrupt source
 
  // In can only be GPIO0/GPIO2, as the EXT interrupts are not (yet) used
  pidx = ( IO_INT_STAT & 1 ) ? 0 : 1;
  if( *posedge_status[ pidx ] )
  {
    id = INT_GPIO_POSEDGE;
 
 
 
    pin = intlog2( *posedge_status[ pidx ] );
  }
  else
  {
    id = INT_GPIO_NEGEDGE;
 
    pin = intlog2( *negedge_status[ pidx ] );
  }
  resnum = PLATFORM_IO_ENCODE( pidx * 2, pin, PLATFORM_IO_ENC_PIN );
 
  *intclr_regs[ pidx ] = 1 << pin;

  // Run the interrupt through eLua
  cmn_int_handler( id, resnum );
 
  VICVectAddr = 0; // ACK interrupt
 
}

That’s it. If you followed all these steps correctly, your platform should be fully able to support interrupt handlers (as described here). Check the lpc24xx platform implementation (src/platform/lpc24xx) for a full example.

Interrupt list and naming conventions

To ensure maximum portability and correct system behaviour, interrupt names (as defined in platform_conf.h) must follow a well-defined naming pattern. Please note that this isn’t merely a convention, many times the names must be properly chosen for the system to work properly. For example, the timer interrupt match will never happen on virtual timers if the timer interrupt match name isn’t INT_TMR_MATCH (see here for more details on how to use the timer match interrupt).

The naming rule is that the interrupt name must have the format INT_<peripheral>_<type>_, where:

·      peripheral is a symbolic name of the peripheral to which the interrupt applies.

·      type is a symbolic name of the interrupt type.

This restriction applies only to interrupt names. The value associated with the interrupt name (as defined in platform_conf.h) can vary from platform to platform, as long as it follows the rules outlined in step 1 above.

The table below lists all the valid interrupt names currently known to eLua. If you add a new interrupt don’t forget to update the table below.

Name

Meaning

INT_GPIO_POSEDGE

Interrupt on a positive edge on a GPIO pin

INT_GPIO_NEGEDGE

Interrupt on a negative edge on a GPIO pin

INT_TMR_MATCH

Interrupt on timer match

INT_UART_RX

Interrupt on UART character received


}

 

Thanks,
Lwazi


Best
Dado









 

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Dado Sutter Dado Sutter
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

In reply to this post by Dado Sutter


On Fri, Jan 28, 2011 at 08:17, John Hind <[hidden email]> wrote:

Thanks, Dado! I would like to post a paper for comment with my ideas for coroutine based preemptive multitasking. This is not really a suitable forum for collaborative work in detail because different peoples comments do not get merged in the same place and things tend to disappear over the time horizon. WiKi would be a better place - are you OK with using the "user Labs" for this purpose,


Sure, absolutely !
 

and if so, maybe we need a separate corner of it for development discussions rather than usage examples?


You are free to re-organize anything there as you feel. We can create a new section for this kind of brainstorming, I don't know what is better now but the nice think on wikis is that we can simply start it and it will show us the best place to stay soon.

 And you are right about the current system using the debug hooks in standard Lua. Also Robert has pointed out that you can even execute a coroutine yield from debug hook so I do not even actually need a patch to achieve what I'm trying to do.


Cool
 

However the debug hooks are peppered with performance warnings in the Lua literature and this is extending them well beyond the original intention of the mechanism.


I see. We can check with Roberto if this is the right way to go (but he'll be away for a few more days)

I did not give a very good example of my concern number 1, making it look like the same concern as number 3. The syntax of Lua constrains where you could place a Yield instruction more than a debug hook would - i.e. you could not put a yield in the middle of a mathematical expression in the way that could happen with a debug hook. But in any case if the system requires the programmer to pepper their code with extra synchronisation instructions beyond what is explicitly necessary at the application level, it becomes an unattractive (and error prone!)  programming model.


Yes, it looks hard (to me) to figure out all the critical cases but we have good brains here to help :)
Thank you all for this great thread

Best
Dado

 

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter
Sent: 26 January 2011 16:24


To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello list,

On Wed, Jan 26, 2011 at 08:14, John Hind <[hidden email]> wrote:

I must say that having taken a close look at this mechanism and even developed it further to allow C interrupt handlers to force a coroutine yield between any two virtual machine opcodes, I became concerned about the safety of this approach. Incidently, if we can force coroutine yields safely in this way we already have all the mechanisms for an RTOS built-in to Lua in the threads provided for coroutines.


Thank you for diving into this John.

 

There are really three concerns I have (note that in the following, by "interrupt" I mean the eLua mechanism for "splicing" extra Lua VM opcodes at runtime, not asynchronous interrupts at the processor level):

 

1. Are we sure that Lua VM operations are always atomic at the opcode level?


I "think" they are, as Lua itself has a debug lib that (I think) uses the same hook mechanism.
 

For example if we interrupt in the middle of processing some complex mathematical expression and the code in the interrupt operates on some of the same variables?


On the minimalist Lua approach, I think this would be considered as a programmer's fault :) But in this case I must admit it would be a serious flaw.
Our initial INT mechanism allows one to disable (or stop queueing) interrupts during critical program sections but this may be not fair to transfer the responsability to users in all the cases.
 

2. Do we allow the interrupt code itself to be further interrupted? 

If so things could become very complex, if not, we have potentially unbounded latencies.

 

3. I believe we will need a Lua-level "critical section" operator which allows the Lua programmer to disable interrupts. For example, you might write a Ring Buffer structure using a Lua table and some pointers. You might want to read this buffer in interrupt code and write it in the main code. You cannot allow the interrupt code to run while the main code is in the middle of writing to the buffer (which will involve a table write, some bounds checks and a pointer write).


The initial INT support has instructions to enable/disable Lua handlers.
 

I feel we need someone who really understands the design of the Lua interpreter and can code-review what we are doing, because it will be virtually impossible to devise an adequate test set to prove this is safe in all cases.


Yep. It would be great to have more help for this and for the dev in general.
 

Incidentally, I remember being very impressed that the BASIC-STAMP microcontroller had preemptive multitasking integrated with the (interpreted) Basic language, this on an 8-bit chip with only a few hundred *bytes* of RAM. I was able to write a really good serial comms handler for GPS using this, which would have been virtually impossible without. A good RTOS can actually save RAM by avoiding the need for large RAM buffers for incoming data.


Cool. I hope we can learn and add such vodoo to eLua too.

Best
Dado




 

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter
Sent: 25 January 2011 21:53
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello guys,

On Tue, Jan 25, 2011 at 19:10, Lwazi <[hidden email]> wrote:

On Tue, Jan 25, 2011 at 2:27 PM, James Snyder <[hidden email]> wrote:
> On Tue, Jan 25, 2011 at 1:25 PM, James Snyder <[hidden email]> wrote:
>>

>> For the microcontroller platforms we run on, an RTOS would generally
>> just add overhead.  In our upcoming release we will support basic
>> concurrency in more of an event-driven manner using interrupts which
>> are
>
> Ooops.. hit the send button when I was going to save this and finish
> editing later.
>
> To finish:
> The interrupts support both Lua and C handlers, C handlers execute
> immediately and Lua handlers can interrupt currently running Lua code,
> but they have to wait a few instructions before execution (so latency
> isn't going to be constant or really low).
>

I like this. Is this documented anywhere?


Yes, this is new eLua feature and the initial doc will be release with v0.8
LPC24xx implementatin can be seen on trunk (or branch pre0.8) at  src/platform/lpc24xx

What happens to the rest of
the system during this 'wait'?


You mean, the aparent-latency wait until we actually handle the INT in Lua ? The Lua VM is executing during this. I think the hook checks for any queued INT to handle every 2 or 3 VM bytecodes.
 

I presume the ISR and other interrupts
do not get blocked while the Lua handler waits to run. 

And I have to
ask if there is a guarantee that the Lua handler will run


   As a preview of the doc to come, I'll paste here some infos about the Interrupt mechanism v0.8 will bring officially to eLua.
   Comments and reviews are welcomed.
   (and yes, the doc edition/refinement by the community will be easier in the future :)

Best
Dado

eLua interrupt handlers

Starting with version 0.8, eLua supports interrupt handlers written in Lua. Once an interrupt handler is set in the Lua code, it will be called each time a supported interrupt is generated. A supported interrupt is any interrupt that is handled by the platform C code (see here for more details).

IMPORTANT: before learning how to use interrupt handlers in Lua, please keep in mind that Lua interrupt handlers don’t work the same way as regular (C) interrupt handlers. As Lua doesn’t have direct suport for interrupts, they have to be emulated. eLua emulates them using a queue that is populated with interrupt data by the C support code. As long as the queue is not empty, a Lua hook is set to run every 2 Lua bytecode instructions. This hook function is the Lua interrupt handler. After all the interrupts are handled and the queue is emptied, the hook is automatically disabled. Consequently:

·      When the interrupt queue is full (a situation that might appear when interrupts are added to the queue faster than the Lua code can handle them) subsequent interrupts are ignored (not added to the queue) and an error message is printed on the eLua console device. The interrupt queue size can be configured at build time, as explained here. Even if the interrupt queue is large, one most remember that Lua code is significantly slower than C code, thus not all C interrupts make suitable candidates for Lua interrupt handlers. For example, a serial interrupt that is generated each time a char is received at 115200 baud might be too fast for Lua (this is largely dependent on the platform). On the other hand, a GPIO interrupt-on-change on a GPIO line connected with a matrix keyboard is a very good candidate for a Lua handler. Experimenting with different interrupt types is the best way to find the interrupts that work well with Lua.

·      A more subtle point is that the Lua virtual machine must run for the interrupt handlers to work. A simple analogy is that a CPU must have a running clock in order to function properly (and in order to take care of the hardware interrupts). If the clock is stopped, the CPU doesn’t run and the interrupt handlers aren’t called anymore, although the occurence of the interrupt might be recorded inside the CPU. This is the exact same situation with Lua: if the virtual machine doesn’t run, the interrupts are still recorded in the interrupt queue, but the Lua handler won’t be called until the virtual machine runs again. In this case though, the "clock" of the Lua VM is a C function that is executed for every VM instruction. If this function blocks for some reason, the VM instructions are not executed anymore. It’s not hard to make this function block; for example, it blocks everytime the Lua code waits for some user input at the console, or when a TODO tmr.delay is executed, or when TODO uart.read is called with an infinite or very large timeout; in general, any function from a Lua library that doesn’t return immediately (or after a short ammount of time) will block the VM. Care must be taken to avoid such operations as much as possible, otherwise the interrupt support code won’t run properly.

·      There is a single interrupt handler per interrupt type in Lua (the same holds true for C interrupt support), as opposed to the many hardware interrupts handlers usually found on the eLua targets. It is however easy to differentiate between different interrupt sources, as will be explained in the next paragraph.

·      Lua interrupt handlers are never reentrant.

While this might seem restrictive, Lua interrupt handlers work quite well in practical situations. As an added bonus, since they are implemented by C support code, there’s nothing preventing eLua from implementing "custom interrupts" (software generated interrupts that don’t correspond to a hardware interrupt on the CPU), such as serial interrupt on char match (generate an interrupt when a certain char is received on the serial port, for example a newline), timer interrupts for virtual timers, TCP/UDP data packet received interrupt and many others.

Using interrupt handlers in Lua

To enable Lua interrupt handler, define BUILD_LUA_INT_HANDLERS and PLTATFORM_INT_QUEUE_LOG_SIZE in platform_conf.h (see here for details). Setting up interrupt handlers is a straightforward process, most of the required functionality is provided by the CPU module:

·      use cpu.set_int_handler( int_id, handler ) to set the interrupt handler function for the specified interrupt (call with nil to disable the interrupt handler for that interrupt). cpu.set_int_handler returns the previous interrupt handler for int_id (or nil is an interrupt handler was not previously set for the interrupt). In most cases, your interrupt handler should call the previous handler to ensure proper interrupt management.

·      use cpu.sei( int_id, resnum1, [resnum2], …, [resnumn]) and cpu.cli( int_id, resnum1, [resnum2], …, [resnumn]) to enable/disable specific CPU interrupts that will trigger the interrupt handler. You can also use cpu.sei() and cpu.cli (without parameters) to enable/disable global interrupts on the CPU, although this is not recommended.

The interrupt handler receives the resource ID that specifies the resource that fired the interrupt. It can be a timer ID for a timer overflow interrupt, a GPIO port/pin combination for a GPIO interrupt on pin change, a SPI interface ID for a SPI data available interrupt, and so on.

An example that uses the above concepts and knows how to handle two different interrupt types is presented below:

local vtmrid = tmr.VIRT0
local to = 1500000



local prev_tmr, new_prev_tmr, prev_gpio

-- This is the timer interrupt handler
local function tmr_handler( resnum )
 
  print( string.format( "Timer interrupt for id %d", resnum ) )

 
  if prev_tmr then prev_tmr( resnum ) end
end

-- This is the timer interrupt handler that gets set after tmr_handler
local function new_tmr_handler( resnum )

 
 
 
  print( string.format( "NEW HANDLER: timer interrupt for id %d", resnum ) )
  -- This will chain to the previous interrupt handler (tmr_handler above)

 
  if new_prev_tmr then new_prev_tmr( resnum ) end
 
end

-- This is the GPIO interrupt on change (falling edge) interrupt
local function gpio_negedge_handler( id, resnum )

 
    local port, pin = pio.decode( resnum )
 
  print( string.format( "GPIO NEGEDGE interrupt on port %d, pin %d", port, pin ) )
  if prev_gpio then prev_gpio( resnum ) end

 
end

-- Set timer interrupt handler
prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, tmr_handler )
 
-- Set GPIO interrupt on change (negative edge) interrupt handler

 
prev_gpio = cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, gpio_negedge_handler )
-- Setup periodic timer interrupt for virtual timer 0
 
tmr.set_match_int( vtmrid, to, tmr.INT_CYCLIC )

 
-- Enable GPIO interrupt on change (negative edge) for pin 0 of port 0
cpu.sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
 
-- Enable timer match interrupt on virtual timer 0

 
cpu.sei( cpu.INT_TMR_MATCH, vtmrid )

local tmrid, count = 0, 0
while true do
  print "Outside interrupt"
 
 
 
  for i = 1, 1000 do tmr.delay( tmrid, 1000 ) end
  if uart.getchar( uartid, 0 ) ~= "" then break end
  count = count + 1
  if count == 5 then
    print "Changing timer interrupt handler"


 
 
 
    new_prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, new_tmr_handler )
  end
end

-- Cleanup
-- Stop the timer from generating periodic interrupts

 
tmr.set_match_int( vtmrid, 0, tmr.INT_CYCLIC );
 
-- Disable the GPIO interrupt on change (negative edge) interrupt
cpu.cli( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )

 
-- Disable the timer interrupt on match interrupt
cpu.cli( cpu.INT_TMR_MATCH, vtmrid )
 
-- Clear the timer interrupt handler
cpu.set_int_handler( cpu.INT_TMR_MATCH, nil );

 
-- Clear the GPIO interrupt handler
cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, nil );

This is the most common use case for Lua interrupts, but it’s not the only one. Another way to use interrupts from eLua uses polling instead of interrupt handlers: directly check the interrupt flags and execute a certain action when one of them becomes set. For this, use the cpu.get_int_flag( id, resnum, [clear] ) function from the CPU module, which returns the specified interrupt’s status for resource resnum. clear is an optional boolean parameter, specifying if the interrupt flag should be cleared if it is set. It defaults to true, and in most cases it shouldn’t be changed. Using this feature, it becomes easy to wait for one or more interrupt flag(s) to be set. To use interrupt polling:

·      Enable/disable interrupts to be polled with cpu.hw_sei/cpu.hw_cli instead of cpu.sei/cpu.cli. These functions enable/disable interrupts only in hardware, as opposed to cpu.sei/cpu.cli that also set/clear an internal flag which makes the interrupt able to trigger a Lua handler.

·      Use cpu.get_int_flag to get the interrupt flag.

The int_select function below is a possible implementation of a function that gets an array of interrupts and returns the first one that gets active:

function int_select( int_table )

 
  while true do
    for i = 1, #int_table do
      local t = int_table[ i ]
      if cpu.get_int_flag[ t[ 1 ], t[ 2 ] ) then
 
        return t[ 1 ], t[ 2 ]
      end
    end
end
end

cpu.hw_sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
cpu.hw_sei( cpu.INT_TMR_MATCH, tmr.VIRT0 )

 
 
 
local ints = { { cpu.INT_GPIO_NEGEDGE, pio.P0_0 }, { cpu.INT_TMR_MATCH, tmr.VIRT0 } }
-- int_select will wait for either INT_GPIO_NEGEDGE or INT_TMR_MATCH to become active

 
print( int_select( ints ) )

Note that the two mechanisms (interrupt handlers and polling) can be used in parallel as long as an interrupt is not set with both cpu.hw_sei and cpu.sei, in which case the bevahiour is unpredictable. This is why it makes sense to write the int_select function above in Lua instead of C: it keeps the Lua VM running, so Lua interrupt handlers can be executed.

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{

 
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );
 
  prev_handler = elua_int_set_c_handler( id, int_handler );

 
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )
 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;

 

  // Actual interrupt handler code comes here
 
}

--------------------------

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );


 

void module_init()
{
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );
 
  prev_handler = elua_int_set_c_handler( id, int_handler );

 
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )
 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;

 

  // Actual interrupt handler code comes here
 


-------------------------------

eLua interrupt support implementation

To add interrupt support for an eLua platform follow the steps below:

1.   Define your interrupts

Your interrupt sources should be defined in platform_conf.h with macros (don’t use C enumerations). The first one should have the value ELUA_INT_FIRST_ID (defined in inc/elua_int.h), the next one ELUA_INT_FIRST_ID + 1 and so on. Also, there should be a definition for a macro called INT_ELUA_LAST that must be equal to the largest interrupt source value. An example is given below:

#define INT_GPIO_POSEDGE      ELUA_INT_FIRST_ID
#define INT_GPIO_NEGEDGE      ( ELUA_INT_FIRST_ID + 1 )
#define INT_TMR_MATCH         ( ELUA_INT_FIRST_ID + 2 )

 
#define INT_ELUA_LAST         INT_TMR_MATCH

Note that the interrupt names aren’t random, they should follow a well defined pattern. Check here for details.

2.   Add them to the list of constants from the CPU module

Check the documentation of the CPU module for details.

3.   Implement your support functions

The actual implementation of the interrupt handlers is of course platform specific, so it can stay in the platform.c file. However, since interrupt handlers might require quite a bit of code, it is recommended to implement them in a separate file. The eLua convention is to use the platform_int.c file for this purpose. For each interrupt defined in step 1 above, 3 functions need to be implemented:

o  A function that enables or disables the interrupt and returns its previous state (enabled or disabled).

o  A function that checks if the interrupt is enabled or disabled.

o  A function that checks the interrupt pending flag and optionally clears it.

These functions are defined in inc/elua_int.h, which also defines an "int descriptor" type:

 

 
// Interrupt functions and descriptor
typedef int ( *elua_int_p_set_status )( elua_int_resnum resnum, int state );
typedef int ( *elua_int_p_get_status )( elua_int_resnum resnum );
typedef int ( *elua_int_p_get_flag )( elua_int_resnum resnum, int clear );

 
 
 
typedef struct
{
  elua_int_p_set_status int_set_status;
  elua_int_p_get_status int_get_status;

 
  elua_int_p_get_flag int_get_flag;
} elua_int_descriptor;

platform_int.c must have an array of elua_int_descriptor types named elua_int_table (remember to make it const to save RAM). The elements of this array must be in the same order as the interrupt sources. The interrupt table for the example from step 1 above might look like this:

const elua_int_descriptor elua_int_table[ INT_ELUA_LAST ] =
{
  { int_gpio_posedge_set_status, int_gpio_posedge_get_status, int_gpio_posedge_get_flag },

 
  { int_gpio_negedge_set_status, int_gpio_negedge_get_status, int_gpio_negedge_get_flag },
 
  { int_tmr_match_set_status, int_tmr_match_get_status, int_tmr_match_get_flag }

 
};

4.   Implement the init function

platform_int.c should implement a function named platform_int_init (defined in inc/platform.h) that must initialize all the required hardware and the internal data structures of the interrupt subsystem. This function should be called from platform_init.

5.   Implement the interrupt handlers

There are two simple requirements for the interrupt handlers: clear the hardware interrupt flag (if needed) and call cmn_int_handler (src/common.c) to connect the handler with the eLua interrupt code. An example is given below:

// EINT3 (INT_GPIO) interrupt handler
static void int_handler_eint3()
{
  elua_int_id id = ELUA_INT_INVALID_INTERRUPT;
  pio_code resnum = 0;

 
  int pidx, pin;

  EXTINT |= 1 << EINT3_BIT; // clear interrupt
 
  // Look for interrupt source

 
  // In can only be GPIO0/GPIO2, as the EXT interrupts are not (yet) used
  pidx = ( IO_INT_STAT & 1 ) ? 0 : 1;
  if( *posedge_status[ pidx ] )
  {
    id = INT_GPIO_POSEDGE;

 

 
 
    pin = intlog2( *posedge_status[ pidx ] );
  }
  else
  {
    id = INT_GPIO_NEGEDGE;

 
    pin = intlog2( *negedge_status[ pidx ] );
  }
  resnum = PLATFORM_IO_ENCODE( pidx * 2, pin, PLATFORM_IO_ENC_PIN );
 
  *intclr_regs[ pidx ] = 1 << pin;

  // Run the interrupt through eLua
  cmn_int_handler( id, resnum );

 
  VICVectAddr = 0; // ACK interrupt
 

}

That’s it. If you followed all these steps correctly, your platform should be fully able to support interrupt handlers (as described here). Check the lpc24xx platform implementation (src/platform/lpc24xx) for a full example.

Interrupt list and naming conventions

To ensure maximum portability and correct system behaviour, interrupt names (as defined in platform_conf.h) must follow a well-defined naming pattern. Please note that this isn’t merely a convention, many times the names must be properly chosen for the system to work properly. For example, the timer interrupt match will never happen on virtual timers if the timer interrupt match name isn’t INT_TMR_MATCH (see here for more details on how to use the timer match interrupt).

The naming rule is that the interrupt name must have the format INT_<peripheral>_<type>_, where:

·      peripheral is a symbolic name of the peripheral to which the interrupt applies.

·      type is a symbolic name of the interrupt type.

This restriction applies only to interrupt names. The value associated with the interrupt name (as defined in platform_conf.h) can vary from platform to platform, as long as it follows the rules outlined in step 1 above.

The table below lists all the valid interrupt names currently known to eLua. If you add a new interrupt don’t forget to update the table below.

Name

Meaning

INT_GPIO_POSEDGE

Interrupt on a positive edge on a GPIO pin

INT_GPIO_NEGEDGE

Interrupt on a negative edge on a GPIO pin

INT_TMR_MATCH

Interrupt on timer match

INT_UART_RX

Interrupt on UART character received


}

 

Thanks,
Lwazi


Best
Dado









 

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev



_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev
Dado Sutter Dado Sutter
Reply | Threaded
Open this post in threaded view
|

Re: eLua vs Lua on RTOS

In reply to this post by Dado Sutter


On Fri, Jan 28, 2011 at 09:13, John Hind <[hidden email]> wrote:

I did some more digging around the web site and I guess I should post this on the WiKi at tracker.eluaproject.net.


You can do that too. We have a brainstorm on SimpleMachines/Mizar32/eLuaBook ongoing there already.
It can also be on the Main Wiki (eLua User Labs). It is really up to the community.

I have registered for this, but I cannot work out how to edit the WiKi 

- do I need to join the project to get this functionality? If so, how do I do that? Do I need one of the admins to authorise me?


Sure, I'll check it now, tkssss

Best
Dado
 

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of John Hind
Sent: 28 January 2011 10:17


To: 'eLua Users and Development List (www.eluaproject.net)'
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Thanks, Dado! I would like to post a paper for comment with my ideas for coroutine based preemptive multitasking. This is not really a suitable forum for collaborative work in detail because different peoples comments do not get merged in the same place and things tend to disappear over the time horizon. WiKi would be a better place - are you OK with using the "user Labs" for this purpose, and if so, maybe we need a separate corner of it for development discussions rather than usage examples?

 

And you are right about the current system using the debug hooks in standard Lua. Also Robert has pointed out that you can even execute a coroutine yield from debug hook so I do not even actually need a patch to achieve what I'm trying to do. However the debug hooks are peppered with performance warnings in the Lua literature and this is extending them well beyond the original intention of the mechanism. I did not give a very good example of my concern number 1, making it look like the same concern as number 3. The syntax of Lua constrains where you could place a Yield instruction more than a debug hook would - i.e. you could not put a yield in the middle of a mathematical expression in the way that could happen with a debug hook. But in any case if the system requires the programmer to pepper their code with extra synchronisation instructions beyond what is explicitly necessary at the application level, it becomes an unattractive (and error prone!)  programming model.

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter


Sent: 26 January 2011 16:24
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello list,

On Wed, Jan 26, 2011 at 08:14, John Hind <[hidden email]> wrote:

I must say that having taken a close look at this mechanism and even developed it further to allow C interrupt handlers to force a coroutine yield between any two virtual machine opcodes, I became concerned about the safety of this approach. Incidently, if we can force coroutine yields safely in this way we already have all the mechanisms for an RTOS built-in to Lua in the threads provided for coroutines.


Thank you for diving into this John.

 

There are really three concerns I have (note that in the following, by "interrupt" I mean the eLua mechanism for "splicing" extra Lua VM opcodes at runtime, not asynchronous interrupts at the processor level):

 

1. Are we sure that Lua VM operations are always atomic at the opcode level?


I "think" they are, as Lua itself has a debug lib that (I think) uses the same hook mechanism.
 

For example if we interrupt in the middle of processing some complex mathematical expression and the code in the interrupt operates on some of the same variables?


On the minimalist Lua approach, I think this would be considered as a programmer's fault :) But in this case I must admit it would be a serious flaw.
Our initial INT mechanism allows one to disable (or stop queueing) interrupts during critical program sections but this may be not fair to transfer the responsability to users in all the cases.
 

2. Do we allow the interrupt code itself to be further interrupted? 

If so things could become very complex, if not, we have potentially unbounded latencies.

 

3. I believe we will need a Lua-level "critical section" operator which allows the Lua programmer to disable interrupts. For example, you might write a Ring Buffer structure using a Lua table and some pointers. You might want to read this buffer in interrupt code and write it in the main code. You cannot allow the interrupt code to run while the main code is in the middle of writing to the buffer (which will involve a table write, some bounds checks and a pointer write).


The initial INT support has instructions to enable/disable Lua handlers.
 

I feel we need someone who really understands the design of the Lua interpreter and can code-review what we are doing, because it will be virtually impossible to devise an adequate test set to prove this is safe in all cases.


Yep. It would be great to have more help for this and for the dev in general.
 

Incidentally, I remember being very impressed that the BASIC-STAMP microcontroller had preemptive multitasking integrated with the (interpreted) Basic language, this on an 8-bit chip with only a few hundred *bytes* of RAM. I was able to write a really good serial comms handler for GPS using this, which would have been virtually impossible without. A good RTOS can actually save RAM by avoiding the need for large RAM buffers for incoming data.


Cool. I hope we can learn and add such vodoo to eLua too.

Best
Dado




 

 

From: [hidden email] [mailto:[hidden email]] On Behalf Of Dado Sutter
Sent: 25 January 2011 21:53
To: eLua Users and Development List (www.eluaproject.net)
Subject: Re: [eLua-dev] eLua vs Lua on RTOS

 

Hello guys,

On Tue, Jan 25, 2011 at 19:10, Lwazi <[hidden email]> wrote:

On Tue, Jan 25, 2011 at 2:27 PM, James Snyder <[hidden email]> wrote:
> On Tue, Jan 25, 2011 at 1:25 PM, James Snyder <[hidden email]> wrote:
>>

>> For the microcontroller platforms we run on, an RTOS would generally
>> just add overhead.  In our upcoming release we will support basic
>> concurrency in more of an event-driven manner using interrupts which
>> are
>
> Ooops.. hit the send button when I was going to save this and finish
> editing later.
>
> To finish:
> The interrupts support both Lua and C handlers, C handlers execute
> immediately and Lua handlers can interrupt currently running Lua code,
> but they have to wait a few instructions before execution (so latency
> isn't going to be constant or really low).
>

I like this. Is this documented anywhere?


Yes, this is new eLua feature and the initial doc will be release with v0.8
LPC24xx implementatin can be seen on trunk (or branch pre0.8) at  src/platform/lpc24xx

What happens to the rest of
the system during this 'wait'?


You mean, the aparent-latency wait until we actually handle the INT in Lua ? The Lua VM is executing during this. I think the hook checks for any queued INT to handle every 2 or 3 VM bytecodes.
 

I presume the ISR and other interrupts
do not get blocked while the Lua handler waits to run. 

And I have to
ask if there is a guarantee that the Lua handler will run


   As a preview of the doc to come, I'll paste here some infos about the Interrupt mechanism v0.8 will bring officially to eLua.
   Comments and reviews are welcomed.
   (and yes, the doc edition/refinement by the community will be easier in the future :)

Best
Dado

eLua interrupt handlers

Starting with version 0.8, eLua supports interrupt handlers written in Lua. Once an interrupt handler is set in the Lua code, it will be called each time a supported interrupt is generated. A supported interrupt is any interrupt that is handled by the platform C code (see here for more details).

IMPORTANT: before learning how to use interrupt handlers in Lua, please keep in mind that Lua interrupt handlers don’t work the same way as regular (C) interrupt handlers. As Lua doesn’t have direct suport for interrupts, they have to be emulated. eLua emulates them using a queue that is populated with interrupt data by the C support code. As long as the queue is not empty, a Lua hook is set to run every 2 Lua bytecode instructions. This hook function is the Lua interrupt handler. After all the interrupts are handled and the queue is emptied, the hook is automatically disabled. Consequently:

·      When the interrupt queue is full (a situation that might appear when interrupts are added to the queue faster than the Lua code can handle them) subsequent interrupts are ignored (not added to the queue) and an error message is printed on the eLua console device. The interrupt queue size can be configured at build time, as explained here. Even if the interrupt queue is large, one most remember that Lua code is significantly slower than C code, thus not all C interrupts make suitable candidates for Lua interrupt handlers. For example, a serial interrupt that is generated each time a char is received at 115200 baud might be too fast for Lua (this is largely dependent on the platform). On the other hand, a GPIO interrupt-on-change on a GPIO line connected with a matrix keyboard is a very good candidate for a Lua handler. Experimenting with different interrupt types is the best way to find the interrupts that work well with Lua.

·      A more subtle point is that the Lua virtual machine must run for the interrupt handlers to work. A simple analogy is that a CPU must have a running clock in order to function properly (and in order to take care of the hardware interrupts). If the clock is stopped, the CPU doesn’t run and the interrupt handlers aren’t called anymore, although the occurence of the interrupt might be recorded inside the CPU. This is the exact same situation with Lua: if the virtual machine doesn’t run, the interrupts are still recorded in the interrupt queue, but the Lua handler won’t be called until the virtual machine runs again. In this case though, the "clock" of the Lua VM is a C function that is executed for every VM instruction. If this function blocks for some reason, the VM instructions are not executed anymore. It’s not hard to make this function block; for example, it blocks everytime the Lua code waits for some user input at the console, or when a TODO tmr.delay is executed, or when TODO uart.read is called with an infinite or very large timeout; in general, any function from a Lua library that doesn’t return immediately (or after a short ammount of time) will block the VM. Care must be taken to avoid such operations as much as possible, otherwise the interrupt support code won’t run properly.

·      There is a single interrupt handler per interrupt type in Lua (the same holds true for C interrupt support), as opposed to the many hardware interrupts handlers usually found on the eLua targets. It is however easy to differentiate between different interrupt sources, as will be explained in the next paragraph.

·      Lua interrupt handlers are never reentrant.

While this might seem restrictive, Lua interrupt handlers work quite well in practical situations. As an added bonus, since they are implemented by C support code, there’s nothing preventing eLua from implementing "custom interrupts" (software generated interrupts that don’t correspond to a hardware interrupt on the CPU), such as serial interrupt on char match (generate an interrupt when a certain char is received on the serial port, for example a newline), timer interrupts for virtual timers, TCP/UDP data packet received interrupt and many others.

Using interrupt handlers in Lua

To enable Lua interrupt handler, define BUILD_LUA_INT_HANDLERS and PLTATFORM_INT_QUEUE_LOG_SIZE in platform_conf.h (see here for details). Setting up interrupt handlers is a straightforward process, most of the required functionality is provided by the CPU module:

·      use cpu.set_int_handler( int_id, handler ) to set the interrupt handler function for the specified interrupt (call with nil to disable the interrupt handler for that interrupt). cpu.set_int_handler returns the previous interrupt handler for int_id (or nil is an interrupt handler was not previously set for the interrupt). In most cases, your interrupt handler should call the previous handler to ensure proper interrupt management.

·      use cpu.sei( int_id, resnum1, [resnum2], …, [resnumn]) and cpu.cli( int_id, resnum1, [resnum2], …, [resnumn]) to enable/disable specific CPU interrupts that will trigger the interrupt handler. You can also use cpu.sei() and cpu.cli (without parameters) to enable/disable global interrupts on the CPU, although this is not recommended.

The interrupt handler receives the resource ID that specifies the resource that fired the interrupt. It can be a timer ID for a timer overflow interrupt, a GPIO port/pin combination for a GPIO interrupt on pin change, a SPI interface ID for a SPI data available interrupt, and so on.

An example that uses the above concepts and knows how to handle two different interrupt types is presented below:

local vtmrid = tmr.VIRT0
local to = 1500000
 
local prev_tmr, new_prev_tmr, prev_gpio

-- This is the timer interrupt handler
local function tmr_handler( resnum )
 
  print( string.format( "Timer interrupt for id %d", resnum ) )
 
  if prev_tmr then prev_tmr( resnum ) end
end

-- This is the timer interrupt handler that gets set after tmr_handler
local function new_tmr_handler( resnum )
 
 
 

  print( string.format( "NEW HANDLER: timer interrupt for id %d", resnum ) )
  -- This will chain to the previous interrupt handler (tmr_handler above)
 

  if new_prev_tmr then new_prev_tmr( resnum ) end
 
end

-- This is the GPIO interrupt on change (falling edge) interrupt
local function gpio_negedge_handler( id, resnum )
 
    local port, pin = pio.decode( resnum )
 
  print( string.format( "GPIO NEGEDGE interrupt on port %d, pin %d", port, pin ) )
  if prev_gpio then prev_gpio( resnum ) end
 
end

-- Set timer interrupt handler
prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, tmr_handler )
 
-- Set GPIO interrupt on change (negative edge) interrupt handler
 
prev_gpio = cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, gpio_negedge_handler )
-- Setup periodic timer interrupt for virtual timer 0
 
tmr.set_match_int( vtmrid, to, tmr.INT_CYCLIC )
 

-- Enable GPIO interrupt on change (negative edge) for pin 0 of port 0
cpu.sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
 
-- Enable timer match interrupt on virtual timer 0
 
cpu.sei( cpu.INT_TMR_MATCH, vtmrid )

local tmrid, count = 0, 0
while true do
  print "Outside interrupt"
 
 
 
  for i = 1, 1000 do tmr.delay( tmrid, 1000 ) end
  if uart.getchar( uartid, 0 ) ~= "" then break end
  count = count + 1
  if count == 5 then
    print "Changing timer interrupt handler"
 
 
 
    new_prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, new_tmr_handler )
  end
end

-- Cleanup
-- Stop the timer from generating periodic interrupts
 
tmr.set_match_int( vtmrid, 0, tmr.INT_CYCLIC );
 
-- Disable the GPIO interrupt on change (negative edge) interrupt
cpu.cli( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
 

-- Disable the timer interrupt on match interrupt
cpu.cli( cpu.INT_TMR_MATCH, vtmrid )
 
-- Clear the timer interrupt handler
cpu.set_int_handler( cpu.INT_TMR_MATCH, nil );
 
-- Clear the GPIO interrupt handler
cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, nil );

This is the most common use case for Lua interrupts, but it’s not the only one. Another way to use interrupts from eLua uses polling instead of interrupt handlers: directly check the interrupt flags and execute a certain action when one of them becomes set. For this, use the cpu.get_int_flag( id, resnum, [clear] ) function from the CPU module, which returns the specified interrupt’s status for resource resnum. clear is an optional boolean parameter, specifying if the interrupt flag should be cleared if it is set. It defaults to true, and in most cases it shouldn’t be changed. Using this feature, it becomes easy to wait for one or more interrupt flag(s) to be set. To use interrupt polling:

·      Enable/disable interrupts to be polled with cpu.hw_sei/cpu.hw_cli instead of cpu.sei/cpu.cli. These functions enable/disable interrupts only in hardware, as opposed to cpu.sei/cpu.cli that also set/clear an internal flag which makes the interrupt able to trigger a Lua handler.

·      Use cpu.get_int_flag to get the interrupt flag.

The int_select function below is a possible implementation of a function that gets an array of interrupts and returns the first one that gets active:

function int_select( int_table )
 
  while true do
    for i = 1, #int_table do
      local t = int_table[ i ]
      if cpu.get_int_flag[ t[ 1 ], t[ 2 ] ) then
 
        return t[ 1 ], t[ 2 ]
      end
    end
end
end

cpu.hw_sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )
cpu.hw_sei( cpu.INT_TMR_MATCH, tmr.VIRT0 )
 
 
 
local ints = { { cpu.INT_GPIO_NEGEDGE, pio.P0_0 }, { cpu.INT_TMR_MATCH, tmr.VIRT0 } }
-- int_select will wait for either INT_GPIO_NEGEDGE or INT_TMR_MATCH to become active
 
print( int_select( ints ) )

Note that the two mechanisms (interrupt handlers and polling) can be used in parallel as long as an interrupt is not set with both cpu.hw_sei and cpu.sei, in which case the bevahiour is unpredictable. This is why it makes sense to write the int_select function above in Lua instead of C: it keeps the Lua VM running, so Lua interrupt handlers can be executed.

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );

void module_init()
{
 
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );
 

  prev_handler = elua_int_set_c_handler( id, int_handler );
 
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )
 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;
 

  // Actual interrupt handler code comes here
 
}

--------------------------

Interrupt handlers in C

The interrupt subsystem has also a basic C API that can be used to implement portable eLua components and modules. It is enabled by defining BUILD_C_INT_HANDLERS in platform_conf.h. It is defined in inc/elua_int.h and has 2 functions:

elua_int_c_handler elua_int_set_c_handler( elua_int_id inttype, elua_int_c_handler phandler )

Sets the interrupt handler for interrupt inttype to phandler and returns the previous interrupt handler for interrupt inttype.

elua_int_c_handler elua_int_get_c_handler( elua_int_id inttype )

Returns the interrupt handler for interrupt inttype

elua_int_c_handler is a function that doesn’t return anything and receives a single parameter of type elua_int_resnum to differentiate between the sources (GPIO pin, UART id, timer id and so on) that can trigger the interrupt inttype. This is similar in functionality with the Lua handlers.

To work with interrupts from C code use these functions defined by the CPU platform interface:

int platform_cpu_set_interrupt( elua_int_id id, elua_int_resnum resnum, int status )

Enable (status = PLATFORM_CPU_ENABLE) or disable (status = PLATFORM_CPU_DISABLE) interrupt id for resource resnum.

int platform_cpu_get_interrupt( elua_int_id id, elua_int_resnum resnum )

Returns 1 if interrupt id is enabled for resource resnum, 0 otherwise.

int platform_cpu_get_interrupt_flag( elua_int_id id, elua_int_resnum resnum, int clear )

Get interrupt flag for interrupt id and resource resnum, clear interrupt flag if it is set and clear is 1, leave it untouched otherwise.

Since elua_int_set_c_handler returns the previous handler, it is easy to chain the interrupt handlers from different system components. To ensure correct operation, every C module that needs access to interrupt handlers should use this sequence:

#include "elua_int.h"

static elua_int_c_handler prev_handler;
static void int_handler( elua_int_resnum resnum );
 

void module_init()
{
  int id = SOME_INT_ID;

  platform_cpu_set_interrupt( id, some_resnum, PLATFORM_CPU_ENABLE );

 
  prev_handler = elua_int_set_c_handler( id, int_handler );
 
}

static void int_handler( elua_int_resnum resnum )
{
  // Note: prev_handler can also be called at the end of int_handler
  if( prev_handler )
 
    prev_handler( resnum );

  // (Optional) Check resnum and return if the interrupt was fired by a different resource
  if( resnum != some_resnum )
    return;
 


  // Actual interrupt handler code comes here
 

-------------------------------

eLua interrupt support implementation

To add interrupt support for an eLua platform follow the steps below:

1.   Define your interrupts

Your interrupt sources should be defined in platform_conf.h with macros (don’t use C enumerations). The first one should have the value ELUA_INT_FIRST_ID (defined in inc/elua_int.h), the next one ELUA_INT_FIRST_ID + 1 and so on. Also, there should be a definition for a macro called INT_ELUA_LAST that must be equal to the largest interrupt source value. An example is given below:

#define INT_GPIO_POSEDGE      ELUA_INT_FIRST_ID
#define INT_GPIO_NEGEDGE      ( ELUA_INT_FIRST_ID + 1 )
#define INT_TMR_MATCH         ( ELUA_INT_FIRST_ID + 2 )
 
#define INT_ELUA_LAST         INT_TMR_MATCH

Note that the interrupt names aren’t random, they should follow a well defined pattern. Check here for details.

2.   Add them to the list of constants from the CPU module

Check the documentation of the CPU module for details.

3.   Implement your support functions

The actual implementation of the interrupt handlers is of course platform specific, so it can stay in the platform.c file. However, since interrupt handlers might require quite a bit of code, it is recommended to implement them in a separate file. The eLua convention is to use the platform_int.c file for this purpose. For each interrupt defined in step 1 above, 3 functions need to be implemented:

o  A function that enables or disables the interrupt and returns its previous state (enabled or disabled).

o  A function that checks if the interrupt is enabled or disabled.

o  A function that checks the interrupt pending flag and optionally clears it.

These functions are defined in inc/elua_int.h, which also defines an "int descriptor" type:

 

 
// Interrupt functions and descriptor
typedef int ( *elua_int_p_set_status )( elua_int_resnum resnum, int state );
typedef int ( *elua_int_p_get_status )( elua_int_resnum resnum );
typedef int ( *elua_int_p_get_flag )( elua_int_resnum resnum, int clear );
 
 
 

typedef struct
{
  elua_int_p_set_status int_set_status;
  elua_int_p_get_status int_get_status;
 
  elua_int_p_get_flag int_get_flag;
} elua_int_descriptor;

platform_int.c must have an array of elua_int_descriptor types named elua_int_table (remember to make it const to save RAM). The elements of this array must be in the same order as the interrupt sources. The interrupt table for the example from step 1 above might look like this:

const elua_int_descriptor elua_int_table[ INT_ELUA_LAST ] =
{
  { int_gpio_posedge_set_status, int_gpio_posedge_get_status, int_gpio_posedge_get_flag },
 
  { int_gpio_negedge_set_status, int_gpio_negedge_get_status, int_gpio_negedge_get_flag },

 
  { int_tmr_match_set_status, int_tmr_match_get_status, int_tmr_match_get_flag }
 
};

4.   Implement the init function

platform_int.c should implement a function named platform_int_init (defined in inc/platform.h) that must initialize all the required hardware and the internal data structures of the interrupt subsystem. This function should be called from platform_init.

5.   Implement the interrupt handlers

There are two simple requirements for the interrupt handlers: clear the hardware interrupt flag (if needed) and call cmn_int_handler (src/common.c) to connect the handler with the eLua interrupt code. An example is given below:

// EINT3 (INT_GPIO) interrupt handler
static void int_handler_eint3()
{
  elua_int_id id = ELUA_INT_INVALID_INTERRUPT;
  pio_code resnum = 0;
 
  int pidx, pin;

  EXTINT |= 1 << EINT3_BIT; // clear interrupt
 
  // Look for interrupt source
 

  // In can only be GPIO0/GPIO2, as the EXT interrupts are not (yet) used
  pidx = ( IO_INT_STAT & 1 ) ? 0 : 1;
  if( *posedge_status[ pidx ] )
  {
    id = INT_GPIO_POSEDGE;
 
 
 
    pin = intlog2( *posedge_status[ pidx ] );
  }
  else
  {
    id = INT_GPIO_NEGEDGE;
 
    pin = intlog2( *negedge_status[ pidx ] );
  }
  resnum = PLATFORM_IO_ENCODE( pidx * 2, pin, PLATFORM_IO_ENC_PIN );
 
  *intclr_regs[ pidx ] = 1 << pin;

  // Run the interrupt through eLua
  cmn_int_handler( id, resnum );
 

  VICVectAddr = 0; // ACK interrupt
 
}

That’s it. If you followed all these steps correctly, your platform should be fully able to support interrupt handlers (as described here). Check the lpc24xx platform implementation (src/platform/lpc24xx) for a full example.

Interrupt list and naming conventions

To ensure maximum portability and correct system behaviour, interrupt names (as defined in platform_conf.h) must follow a well-defined naming pattern. Please note that this isn’t merely a convention, many times the names must be properly chosen for the system to work properly. For example, the timer interrupt match will never happen on virtual timers if the timer interrupt match name isn’t INT_TMR_MATCH (see here for more details on how to use the timer match interrupt).

The naming rule is that the interrupt name must have the format INT_<peripheral>_<type>_, where:

·      peripheral is a symbolic name of the peripheral to which the interrupt applies.

·      type is a symbolic name of the interrupt type.

This restriction applies only to interrupt names. The value associated with the interrupt name (as defined in platform_conf.h) can vary from platform to platform, as long as it follows the rules outlined in step 1 above.

The table below lists all the valid interrupt names currently known to eLua. If you add a new interrupt don’t forget to update the table below.

Name

Meaning

INT_GPIO_POSEDGE

Interrupt on a positive edge on a GPIO pin

INT_GPIO_NEGEDGE

Interrupt on a negative edge on a GPIO pin

INT_TMR_MATCH

Interrupt on timer match

INT_UART_RX

Interrupt on UART character received


}

 

Thanks,
Lwazi


Best
Dado









 

_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev

 


_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev



_______________________________________________
eLua-dev mailing list
[hidden email]
https://lists.berlios.de/mailman/listinfo/elua-dev