Lua на STM32
Привет!
Иногда хочется быстро что-то попробовать на микроконтроллере, запрограммировать маленький работающий прототип какой-то идеи. Для этих целей, как известно, хорошо подходят скриптовые языки. В этой статье я хочу рассказать, как с помощью 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 ")
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:
-----------------------------------------------------------------------------
-- 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