Привет!

Иногда хочется быстро что-то попробовать на микроконтроллере, запрограммировать маленький работающий прототип какой-то идеи. Для этих целей, как известно, хорошо подходят скриптовые языки. В этой статье я хочу рассказать, как с помощью Embox запустить интерпретатор Lua (cтандартный, не eLua) на STM32. Для демонстрации помигаем светодиодом по сети с помощью библиотеки luasocket, а также немного поработаем с http.



Lua —?это скриптовый язык программирования, интерпретатор которого является достаточно легковесным, чтобы его можно было легко встраивать в другие проекты, плюс свободная лицензия MIT. Нам давно был интересен этот язык, поэтому под Embox имеется поддержка Lua на qemu i386. А так как интерпретатор легковесный и POSIX-совместимый, появилась идея запустить Lua так же, как мы уже проделали с другими библиотеками типа Pjsip, OpenCV, Qt.

Сразу отмечу, что так как запускать будем с поддержкой luasocket, то выберем не stm32f4-discovery (1 Мб флэш, 192 Кб ОЗУ), а чуть побольше — stm32f7-discovery (1 Мб флэш, 320 Кб ОЗУ, и имеется дополнительная память SDRAM). При этом нет сомнений, что базовая версия Lua без поддержки сети легко запустится и на stm32f4 (увидим дальше).

Для начала подготовим простой пример — вычисление n-го числа Фибоначчи:

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

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

Давайте сначала запустим его под Линуксом, и посмотрим сколько потребовалось памяти. А потом без изменений перенесем в Embox. Сначала скачиваем свежий lua. Я загрузил 5.3.5, и собрал как “make linux”. Но на самом деле для наших целей можно и из репозитория поставить. Далее, запускаем наш fib.lua:

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

Теперь можно посмотреть в fib.out и выяснить, что максимальный объем выделяемой памяти в куче около 30 Кб. Тут я конечно же не буду говорить, что это минимальный размер. Например, в статье в разделе “Требования к оперативной памяти” приводятся существенно меньшие требования, но только лишь на момент включения Lua-машины. Но в любом случае 30 Кб выглядят обнадеживающими — у нас то 320 Кб есть.

Теперь собираем темплейт arm/stm32f746g-discovery-lua для Embox:

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

Прошиваем build/base/bin/embox на плату как указано на нашем wiki и запускаем:

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

Отлично, все в точности как на Линуксе (не удивительно :)).

Давайте усложним задачу, все-таки сейчас сеть — это необходимость почти всюду. Отправим HTTP /GET запрос куда-нибудь в интернет. Для этого понадобится библиотека luasocket. Тут я уже не стал собирать из исходников, а воспользовался готовым пакетом («sudo apt-get install lua-socket»).

Для отправки HTTP запроса используем вот такой простой пример:

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)

Опять же, все можно проверить на Линуксе:

lua http_request.lua http://example.com

Вернет код 200 и содержимое страницы. Давайте посмотрим что по памяти. Если снова запустить valgrind, то видно, что пик потребления памяти в куче возрос до 242 Кб. Так что вот тут я окончательно и выяснил, что в stm32f4 будет не влезть без дополнительных оптимизаций. На stm32f7 влезть вроде бы можно — там 320 Кб, но учитывая что еще есть и ОС c файловой системой, сетью, то немного повозившись я решил остановиться на том, что использую внешнюю SDRAM под кучу.

Включаем (в конфиге это уже включено) обычную кучу в RAM, и дополнительную в SDRAM.

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

Проверяем на stm32f7-discovery, работает как на Линуксе.

Давайте, наконец, помигаем светодиодом “удаленно”.

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

Здесь мы просто получаем UDP данные с указанного порта и передаем их в os.execute(). В качестве команды, которая будет исполняться будем посылать из клиента команду “pin”. Это простая команда в Embox, которая управляет GPIO — например, для stm32f7-disco нужно выполнить “pin gpioi 1 toggle”, что означает изменить 1-ую линию на GPIOI.

В качестве клиента будет взят стандартный пример из luasocket:

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


Как обычно, сначала проверяем на Линуксе. Valgrind показывает, что пиковый расход памяти в куче составляет порядка 70 Кб, что более чем в 2 раза больше, чем случае с Фибоначчи (30 Кб), но существенно меньше, чем в примере с HTTP (242 Кб).

Сразу продемонстрирую пример запуска на Embox.

На 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

На Еmbox:

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

Заключение


Нам удалось запустить Lua + luasocket на stm32f7-discovery. И с их помощью помигать светодиодом удаленно по сети. Причем получилось это довольно просто, ведь как отмечалось в начале, скриптовые языки позволяют экономить время на прототипировании.

Также хочу отметить, что есть такой проект eLua, который запускается на голом железе без ОС. Но у него есть несколько нюансов. Во-первых, ограниченная поддержка сети: UDP там нет, и в документации говорится, что сеть пока “in progress”. Во-вторых, я не смог найти поддержку ни под одну stm’ку типа F3, F4 (только F1). Да и в целом, согласитесь, куда полезнее использовать широко поддерживаемые проекты, и в этом плане elua конечно уступает основному проекту.

Все примеры, описанные в статье, есть у нас в репозитории.

В Embox также имеются и другие скриптовые языки — python (tinipy), Ruby (mruby), Tcl. Но это уже за рамками данной статьи :)

Наши контакты:
Рассылка: embox-ru@googlegroups.com
Телеграмм чат: t.me/embox_chat