1
0
mirror of https://github.com/elua/elua.git synced 2025-01-08 20:56:17 +08:00
elua/doc/en/inthandlers.txt
2011-02-02 17:52:12 +00:00

254 lines
16 KiB
Plaintext

// $$HEADER$$
eLua interrupt handlers
-----------------------
Starting with link:downloads.html[vesion 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 link:arch_ints.html[here] for more details).
[red]*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 support 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
link:building.html[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 link:refman_gen_tmr.html#tmr.delay[tmr.delay] is executed,
or when link:refman_gen_uart.html#uart.read[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
section.
* Lua interrupt handlers are not 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 link:arch_platform_timers.html#virtual_timers[virtual timers], TCP/UDP data packet received interrupt and many others.
IMPORTANT: An up-to-date list of all interrupts supported by eLua can be found link:arch_ints.html#intlist[here].
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 link:building.html[here] for details). Setting up interrupt
handlers is a straightforward process, most of the required functionality is provided by the _mcpu 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:
[subs="quotes"]
-------------------------------
local vtmrid = tmr.VIRT0
local to = 1500000
local prev_tmr, new_prev_tmr, prev_gpio
-- This is the timer interrupt handler
[bblue]*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
[bblue]*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
[bblue]*local function gpio_negedge_handler( 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
[bblue]*prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, tmr_handler )*
-- Set GPIO interrupt on change (negative edge) interrupt handler
[bblue]*prev_gpio = cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, gpio_negedge_handler )*
-- Setup periodic timer interrupt for virtual timer 0
[bblue]*tmr.set_match_int( vtmrid, to, tmr.INT_CYCLIC )*
-- Enable GPIO interrupt on change (negative edge) for pin 0 of port 0
[bblue]*cpu.sei( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )*
-- Enable timer match interrupt on virtual timer 0
[bblue]*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"
[bblue]*new_prev_tmr = cpu.set_int_handler( cpu.INT_TMR_MATCH, new_tmr_handler )*
end
end
-- Cleanup
-- Stop the timer from generating periodic interrupts
[bblue]*tmr.set_match_int( vtmrid, 0, tmr.INT_CYCLIC );*
-- Disable the GPIO interrupt on change (negative edge) interrupt
[bblue]*cpu.cli( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )*
-- Disable the timer interrupt on match interrupt
[bblue]*cpu.cli( cpu.INT_TMR_MATCH, vtmrid )*
-- Clear the timer interrupt handler
[bblue]*cpu.set_int_handler( cpu.INT_TMR_MATCH, nil );*
-- Clear the GPIO interrupt handler
[bblue]*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 _mcpu 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:
* Disable the interrrupt(s) to be polled with _cpu.cli_.
* (optional) disable the interrupt handler for the interrupt with _cpu.set_interrupt_handler( id, nil )_
* 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:
[subs="quotes"]
------------------------------
function int_select( int_table )
while true do
for i = 1, #int_table do
local t = int_table[ i ]
if [bblue]*cpu.get_int_flag[ t[ 1 ], t[ 2 ] )* then
return t[ 1 ], t[ 2 ]
end
end
end
end
[bblue]*cpu.cli( cpu.INT_GPIO_NEGEDGE, pio.P0_0 )*
[bblue]*cpu.cli( 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 interrupt polling) can be used at the same time as long as they are applied to different interrupt id/resource number pairs.
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.
[[cints]]
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 link:arch_platform_cpu.html[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:
[subs="quotes"]
------------------------------
#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
}
------------------------------
Sharing an interrupt between Lua and C
--------------------------------------
As seen above the eLua interrupt subsystem consists of two parts: the C side interrupt support and the Lua side interrupt support. When an interrupt handler is
enabled on both sides (C and Lua) the interrupt subsystem knows to enqueue the interrupt for the Lua handler and to call the C interrupt handler directly. When the
same interrupt (with the same ID) is handled by both the C and Lua code it becomes a *shared* interrupt. Shared interrupts might be problematic in some scenarios.
To avoid potential issues one should should try to remember a few simple rules:
Always check your resource number.::
The Lua side might enable an interrupt for a resource number while the C side might enable the same interrupt for another
resource number. The effect of this is that both interrupt handlers (in Lua and C) will be called with both resource numbers (since there is a single interrupt
handler per interrupt ID). Thus each side should check for the resource number that it needs before acting on the interrupt.
Disable your handlers when you don't need them anymore.::
Starting from the same scenario as above (a shared interrupt with a resource number on the C side and another resource number on the Lua side) let's assume that
at some point you don't need to receive interrupts on the Lua side anymore. You might think that calling _cpu.cli_ with the Lua resource number is enough to acomplish this
but this is not the case. Since the C side still has a resource number active your Lua handler will still be called with the resource number from the C side. If you
followed the first rule (above) your code will behave as expected but it will run slower because of the interrupt support overhead. To completely inhibit the
interrupt handler on the Lua side call _cpu.set_int_handler_ with *nil* for the \'handler' argument. After this the Lua interrupt subsystem will not queue
interrupts for the given interrupt ID and thus the handler won't be called anymore. You can re-enable the handler by calling _cpu.set_int_handler_ with an
actual function for the \'handler' argument.
+
The same holds true for the C side. To disable the handler on the C side call _elua_int_set_c_handler_ with NULL for the \'handler' argument. To re-enable the
C handler call _elua_int_set_c_handler_ with an actual function for the \'handler' argument.
Use cpu.cli and cpu.sei carefully.::
The _cpu.cli_ and _cpu.sei_ functions in the link:refman_gen_cpu.html[CPU module] are wrappers for the _platform_cpu_set_interrupt_ function described above.
This means that eventually they manipulate hardware interrupts directly. If you disable an interrupt ID/resource number pair (with _cpu.cli_) that is
used by the C side the C interrupt handler won't be called anymore and eLua will most likely malfuction. Most of the time this isn't an issue since the Lua
code is not supposed to enable/disable interrupts used by the C code. It might become a real issue when Lua and C share the same interrupt ID/resource number pair.
In this case it is recommended that the Lua code doesn't call _cpu.cli_ at all, instead it should simply set an interrupt handler and service the interrupt.
By contrast, _cpu.sei_ can be called even if the interrupt is already enabled.
// $$FOOTER$$