Lua on STM32

Hello!

Sometimes you want to quickly try something on the microcontroller, program a small working prototype of an idea. For these purposes, as you know, scripting languages ​​are well suited. In this article I want to tell how to use Embox to run the Lua interpreter (standard, not eLua) on STM32. To demonstrate, we blink the LED over the network using the luasocket library, and also work a bit with http.



Lua is a scripting programming language whose interpreter is lightweight enough to be easily integrated into other projects, plus a free MIT license. We have been interested in this language for a long time, therefore, under Embox, there is Lua support on qemu i386. And since the interpreter is lightweight and POSIX-compatible, the idea came up to launch Lua in the same way as we did with other libraries like Pjsip , OpenCV , Qt .

I’ll note right away that since we’ll start with luasocket support, we ’ll choose not stm32f4-discovery (1 MB flash, 192 KB RAM), but a bit more - stm32f7-discovery(1 MB flash, 320 KB RAM, and additional SDRAM). At the same time, there is no doubt that the basic version of Lua without network support can easily start on stm32f4 (see below).

To get started, let's prepare a simple example - calculating the nth Fibonacci number:

function fib(n)
    if n < 3 then
        return 1
    else
        return fib(n - 1) + fib(n - 2)
    end
end

print("fib(7) = " .. fib(7))

Let's start it under Linux first and see how much memory it took. And then we will transfer it to Embox without changes. First, download the fresh lua. I downloaded 5.3.5, and compiled it as “make linux”. But in fact, for our purposes, you can put it from the repository. Next, run our fib.lua:

$ valgrind --tool=massif --massif-out-file=fib.massif lua fib.lua
$ ms_print fib.massif > fib.out

Now you can look in fib.out and find out that the maximum amount of allocated memory in the heap is about 30 Kb. Here, of course, I will not say that this is the minimum size. For example, in the article in the section “Requirements for RAM”, substantially less requirements are given, but only at the time of switching on the Lua-machine. But in any case, 30 Kb looks encouraging - we have 320 Kb.

Now build the arm / stm32f746g-discovery-lua template for Embox:

$ make confload-arm/stm32f746g-discovery-lua
$ make -j $(nproc)

Firmware build / base / bin / embox on the board as indicated on our wiki and run:

embox> lua fib.lua 
fib(7) = 13

Great, everything is exactly like on Linux (not surprising :)).

Let's complicate the task, yet now the network is a necessity almost everywhere. Send an HTTP / GET request somewhere on the Internet. To do this, you need the luasocket library . Then I did not begin to collect from source codes, but used the ready-made package ("sudo apt-get install lua-socket").

To send an HTTP request, use this simple example:

local http = require("socket.http")

if not arg or not arg[1] then
    print("lua http_request.lua <url>")
    os.exit(0)
end

local body, code = http.request(arg[1])
print(code)
print(body)

os.exit(code == 200)

Again, everything can be checked on Linux:

lua http_request.lua http://example.com

Will return code 200 and page content. Let's see that from memory. If you run valgrind again, you can see that the peak memory consumption on the heap has increased to 242 Kb. So here I finally found out that stm32f4 will not fit without additional optimizations. It seems to be possible to get on stm32f7 - there is 320 Kb, but taking into account that there is still an OS with a file system and a network, I decided a bit to tinker with using a bunch of external SDRAM.

We turn on (in the config this is already included) the usual heap in RAM, and the additional one in SDRAM.

    include embox.mem.static_heap(heap_size=0x10000)
    include embox.mem.fixed_heap(heap_size=0x40000, heap_start=0x60000000)

We check on stm32f7-discovery, it works like on Linux.

Let's finally blink the LED “remotely”.

local socket = require("socket")

port = arg[1] or 1027
udp = assert(socket.udp())
assert(udp:setsockname('*', port))
print("Lua UDP server started on port " .. port .. "...")
while 1 do
    cmd, ip, port = assert(udp:receivefrom())
    print("Execute '" .. cmd .. "' from " .. ip .. ":" .. port)
    os.execute(cmd)
end

Here we just get the UDP data from the specified port and pass it to os.execute (). As a command that will be executed, we will send a pin command from the client. This is a simple command in Embox that controls GPIO - for example, for stm32f7-disco you need to execute “pin gpioi 1 toggle”, which means changing the 1st line to GPIOI.

A standard example from luasocket will be taken as a client:

echoclnt.lua
-----------------------------------------------------------------------------
-- UDP sample: echo protocol client
-- LuaSocket sample files
-- Author: Diego Nehab
-----------------------------------------------------------------------------
local socket = require("socket")

host = "localhost"
port = 1027
if arg then
    host = arg[1] or host
    port = arg[2] or port
end

udp = assert(socket.udp())
assert(udp:setpeername(host, port))
print("Using remote host '" ..host.. "' and port " .. port .. "...")
while 1 do
    line = io.read()
    if not line or line == "" then os.exit() end
    assert(udp:send(line))
end


As usual, first check on Linux. Valgrind shows that the peak memory consumption on the heap is about 70 Kb, which is more than 2 times more than the case with Fibonacci (30 Kb), but significantly less than in the example with HTTP (242 Kb).

Immediately demonstrate an example of launching on Embox.

On Linux:

$ lua echoclnt.lua 192.168.0.128 1027
Using remote host '192.168.0.128' and port 1027...
pin gpioi 1 toggle
pin gpioi 1 toggle

On Embox:

embox>                                                   
embox>lua udp_server.lua                                                        
Lua UDP server started on port 1027...
Execute 'pin gpioi 1 toggle' from 192.168.0.102:34300
Execute 'pin gpioi 1 toggle' from 192.168.0.102:34300

Conclusion


We managed to run Lua + luasocket on stm32f7-discovery. And with their help, blink the LED remotely over the network. And it turned out to be quite simple, because as noted at the beginning, scripting languages ​​can save time on prototyping.

I also want to note that there is such an eLua project that runs on bare metal without an OS. But he has several nuances. Firstly, limited network support: UDP is not there, and the documentation says that the network is “in progress” for now. Secondly, I could not find support for any stm'ku type F3, F4 (only F1). And in general, you must agree, it is much more useful to use widely supported projects, and in this regard, elua is certainly inferior to the main project.

All the examples described in the article are in our repository.

Embox also has other scripting languages ​​- python (tinipy), Ruby (mruby), Tcl. But this is beyond the scope of this article :)

Our contacts:
Newsletter: embox-ru@googlegroups.com
Telegram chat: t.me/embox_chat

All Articles