Язык uLua
uLua позволяет писать автономную логику устройства на знакомом синтаксисе Lua. Исходный текст компилируется на компьютере, а контроллер получает компактный байт-код. Поэтому в прошивке не нужны синтаксический анализатор, сборщик мусора и файловая система с модулями.
Цена такой компактности — предсказуемые, но жёсткие ограничения. uLua подходит для GPIO, датчиков, таймеров и коротких протоколов, однако не заменяет полную Lua при работе с таблицами, динамическими модулями или числами с плавающей точкой.
hw.gled(true)
return 1 + 2
Модель выполнения
- исходный файл компилируется до загрузки на устройство;
- встроенные и пользовательские функции определяются при компиляции;
- на устройстве нет
require,load,_G, интерактивной командной строки (REPL) и сборщика мусора; - программа получает доступ к оборудованию через встроенные модули
hw,cmd,mlibи другие модули выбранного устройства.
Что поддерживается
В uLua доступны:
- целые числа, логические значения,
nilи строки; - локальные переменные;
if,elseif,else;while,repeat ... until, числовойforиbreak;- именованные функции верхнего уровня;
- до трёх возвращаемых значений;
- строковая конкатенация;
- арифметические, битовые, логические операции и сравнения.
Не поддерживаются:
- таблицы и конструкторы таблиц;
pairs,ipairsиnext;- синтаксис методов
object:method(); - обобщённый цикл
for ... in; goto;- числа с плавающей точкой;
- анонимные и вложенные функции;
- замыкания и захват внешних локальных переменных;
- косвенный вызов функции через значение, вычисленное во время выполнения;
- переменное число аргументов
....
Для небольших битовых наборов и множеств целых используйте
mlib.
Жёсткие ограничения
Одна программа может содержать:
- до 16 функций, включая основную;
- до 144 констант;
- не более 8 одновременно активных функций, включая основную;
- до 128 слотов на функцию: параметры, локальные переменные и стек вычислений вместе;
- до 65 535 байт кода в одной функции и во всей программе;
- до 65 535 байт в строковом пуле и в одной строковой константе;
- не более трёх значений в одном
return.
Некоторые ограничения конкретного оборудования — например, число таймеров или размер приёмного буфера — перечислены на страницах соответствующих модулей. При вызовах все активные функции также делят общий пул из 128 слотов. Поэтому глубоко вложенная цепочка крупных функций может исчерпать его раньше предела в восемь вызовов.
Фактический размер программы на устройстве
Пределы 65 535 байт относятся к полям формата байт-кода, а не к доступной памяти STM32F042. Для программы действуют следующие ограничения:
- сохранённый файл байт-кода для автоматического запуска может занимать не более 2032 байт;
- временный запуск файла до 250 байт использует ОЗУ;
- временный запуск файла размером
251..2048байт использует ту же страницу флеш-памяти и поэтому недоступен, пока в ней хранится программа автозапуска.
В размер входит весь файл: заголовок, таблицы функций и констант, строки и код. Если редактор сообщает, что программа не помещается, сокращать нужно итоговый байт-код, а не только исходный текст.
Типы значений
| Тип | Пример | Особенность |
|---|---|---|
| Целое | 42, -7, 0xFF | Знаковое 28-битное число |
| Логическое | true, false | false — ложное значение |
nil | nil | Отсутствие значения |
| Строка | "hello", a .. b | Константа или временный результат конкатенации |
| Встроенный дескриптор | hw.p1, mlib.bitset_new(8) | Значение, которым управляет встроенный модуль |
Константы вроде hw.p1 имеют собственный встроенный тип. Обычное число 1
нельзя использовать вместо дескриптора вывода; для преобразования номера служит
hw.pin(1).
Числа и арифметика
Все вычисления целочисленные. Значение uLua лежит в диапазоне
-2²⁷..2²⁷−1. Виртуальная машина выполняет арифметику в 32 битах, а при
сохранении результата оставляет 28 бит.
local a = 10 + 3
local b = 10 - 3
local c = 10 * 3
local d = 10 / 3 -- 3
local e = 10 % 3 -- 1
local f = -10
Оператор / делит целые числа с усечением к нулю. Остаток % рассчитан по
тому же правилу: например, -10 % 3 равно -1. Это отличается от округления
вниз в полной Lua. Деление на ноль завершает программу ошибкой. Операторы <<
и >> используют только пять младших битов правого операнда, то есть
фактически сдвигают на n & 31.
Битовые операции
Поддерживаются:
&— И;|— ИЛИ;- бинарный
~— исключающее ИЛИ; - унарный
~— инверсия; <<и>>— сдвиги.
local flags = 0
flags = flags | 1
flags = flags | 4
if (flags & 4) ~= 0 then
hw.cdc_write("bit 2 set\n")
end
Условия и логические операции
В условиях только false и nil считаются ложью. Ноль, как и в Lua, истинен.
Доступны сравнения ==, ~=, <, <=, > и >=, а также not, and
и or. Равенство можно проверять для любых значений; строки сравниваются по
содержимому. Сравнения порядка <, <=, > и >= работают только с целыми
числами. Операторы and и or вычисляют правую часть только при
необходимости.
Строки
uLua поддерживает строковые литералы, передачу строк встроенным функциям и
конкатенацию через ... Объединять можно строки и целые числа. Логические
значения, nil и встроенные дескрипторы конкатенация не принимает; выводите их
отдельным вызовом hw.print(). Общего аналога tostring() нет.
local name = "sensor"
local value = 42
hw.cdc_write(name .. "=" .. value .. "\n")
local n = #"hello" -- 5
Оператор # работает и со строковым литералом, и с результатом конкатенации.
Он возвращает число байтов, а не символов Unicode.
Результат конкатенации помещается в один из двух общих буферов по 128 байт. Следующая конкатенация может перезаписать прежнюю временную строку. Поэтому не сохраняйте такой результат для позднего использования, например в аргументе обработчика таймера. Строковые литералы от этого ограничения не зависят.
В одной операции можно объединить от двух до восьми частей. Более длинную последовательность разбейте на несколько выражений, помня, что для временных строк доступны только два общих буфера.
Встроенной библиотеки string в uLua нет. Доступные операции со строками
зависят от модели и версии прошивки.
Переменные и циклы
Поддерживаются обычные локальные переменные:
local pin = hw.p1
local state = 0
state = 1 - state
Условия
local x = 5
if x < 3 then
hw.print("small\n")
elseif x < 8 then
hw.print("medium\n")
else
hw.print("large\n")
end
while и repeat
local i = 0
local sum = 0
while i < 5 do
sum = sum + i
i = i + 1
end
repeat
i = i - 1
until i == 0
Числовой for
local sum = 0
for i = 1, 10 do
sum = sum + i
end
for i = 10, 1, -2 do
hw.print(i .. "\n")
end
Функции
Пользовательские функции должны быть именованными и находиться на верхнем уровне программы.
function square(x)
return x * x
end
function sum_and_diff(a, b)
return a + b, a - b
end
local x = square(6)
local sum, diff = sum_and_diff(7, 2)
return x + sum + diff
Число результатов определяется местом вызова, как в Lua: лишние отбрасываются,
недостающие становятся nil, а отдельный вызов не сохраняет результат.
Внешний интерфейс запуска программы показывает только первое значение,
возвращённое основной функцией; несколько результатов нужны прежде всего при
вызовах пользовательских функций внутри программы.
function values()
return 1, 2
end
local first = values() -- 1
local a, b, c = values() -- 1, 2, nil
values() -- оба результата отброшены
Почему внешняя локальная переменная недоступна
Такой код требует замыкания и не скомпилируется:
local OUT_OPEN = 1
local OUT_CLOSED = 0
function pulse(pin, ms)
hw.gpio_set(pin, OUT_CLOSED)
hw.delay_ms(ms)
hw.gpio_set(pin, OUT_OPEN)
end
Передайте значения явно:
function pulse(pin, ms, closed_level, open_level)
hw.gpio_set(pin, closed_level)
hw.delay_ms(ms)
hw.gpio_set(pin, open_level)
end
pulse(hw.pa3, 200, 0, 1)
Или оставьте неизменяемые значения внутри функции:
function pulse_low(pin, ms)
hw.gpio_set(pin, 0)
hw.delay_ms(ms)
hw.gpio_set(pin, 1)
end
Полный пример
hw.gpio_init(hw.p1, hw.out)
function blink_once()
hw.gpio_set(hw.p1, 1)
hw.delay_ms(100)
hw.gpio_set(hw.p1, 0)
end
local i = 0
while i < 5 do
blink_once()
hw.print("blink " .. i .. "\n")
i = i + 1
hw.delay_ms(400)
end
return i
Дальше можно перейти к работе с оборудованием или к примерам загрузки программ на устройство.