Задача
Під час проектування чергового пристрою, нам потрібно було побудувати інтеграцію між двома різними системами. І саме через цю необхідність було вирішено перевірити, з якою швидкістю програмний обробник переривань може обробляти зміни вхідного дискретного сигналу на GPIO ESP8266 з рівня Lua.
Для виконання такої перевірки ми використали:
- модуль мікроконтролера ESP8266-12;
- для тестування обрали GPIO D3;
- регульований генератор тестового вхідного сигналу квадратної форми (меандр) з скважністю 50%;
- тестовий скрипт, написаний на Lua.
Тестовий код
Для того, щоб витягнути з Lua максимальну швидкодію, тестовий код був спеціальним чином оптимізований, а частота ЦП мікроконтролера підвищена до максимуму у 160 МГц.
Оптимізація коду покладається на настанови автора мови (ознайомитися можливо тут: https://www.lua.org/gems/sample.pdf) і полягає у двох заходах:
- переведення всіх глобальних викликів і змінних у локальні;
- запобігання повторюваному пере-гешуванню таблиці.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
------ Тестовий код Lua ------ -- Всі глобальні ідентифікатори переносимо у локальний простір адресації, щоб суттєво пришвидшити виконання коду Lua local gpio = gpio local mode = gpio.mode local trig = gpio.trig local INT = gpio.INT local FLOAT = gpio.FLOAT local node = node local setcpufreq = node.setcpufreq --local CPU80MHZ = node.CPU80MHZ local CPU160MHZ = node.CPU160MHZ local count = 0 local print = print -- створюємо масив потрібної довжини, щоб уникнути ре-гешу на рівні Lua local arr = {0,0,0,0,0,0,0,0,0,0,0} local avg = 0 -- встановлюємо максимальну частоту, 160 МГц: -- замінюємо node.setcpufreq(node.CPU160MHZ) setcpufreq(CPU160MHZ) -- замінюємо gpio.mode(3, gpio.INT, gpio.FLOAT) mode(3,INT,FLOAT) -- підключаємо обробник подій за зростаючим фронтом (Rising Edge) -- замінюємо gpio.trig(3, 'both', function(lvl, ts, cnt) print(lvl, ts, cnt) end) trig(3, 'up', function(lvl, ts, cnt) print(count, 'int:', lvl, ts, cnt) if arr == nil then arr = {} end count = count + 1 arr[count] = ts if count == 11 then trig(3) local avg = 0 for i = 2, #arr do print ('rising edge delta uSec:', arr[i] - arr[i-1]) avg = avg + (arr[i] - arr[i-1]) end avg = avg / (#arr - 1) print('average delta uSec:', avg) print('average frequency, Hz:', 1000000 / avg) end end ) ---- end of file ----- |
Результати тестування
Ми вирішили не “обкидувати” цю публікацію нудними скріншотами з консолі, а натомість розмістили тексти з тієї ж консолі – так краще видно.
Перевірка з максимальною частотою тактування центрального процесора (160 МГц)
Вхідний дискретний сигнал частотою 100 Гц
Виконання тестового коду при частоті генератора на GPIO D3 100 Гц.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
dofile("gpio_int_speed_test.lua") > 0 int: 1 289331235 1 1 int: 1 289341176 1 2 int: 1 289351112 1 3 int: 1 289361053 1 4 int: 1 289370996 1 5 int: 1 289380934 1 6 int: 1 289390874 1 7 int: 1 289400817 1 8 int: 1 289410754 1 9 int: 1 289420692 1 10 int: 1 289430627 1 rising edge delta uSec: 9941 rising edge delta uSec: 9936 rising edge delta uSec: 9941 rising edge delta uSec: 9943 rising edge delta uSec: 9938 rising edge delta uSec: 9940 rising edge delta uSec: 9943 rising edge delta uSec: 9937 rising edge delta uSec: 9938 rising edge delta uSec: 9935 average delta uSec: 9939.2 average frequency, Hz: 100.61171925306 |
Як видно з видачі, події з 0 по 10 обробилися без брязкоту (стовпчик cnt
праворуч з одиниць) – це значить, що контролер все встиг обробити і взаємних накладок фронтів не відбулося.
На цій частоті все добре. Спробуємо перейти в наступний діапазон: 200-300 Гц.
Вхідний дискретний сигнал частотою 200 – 300 Гц
Виконано запуски тестового коду при частоті генератора на GPIO D3 починаючи з 300 Гц з поступовим зниженням частоти доти, доки контролер не почав стабільно якісно обробляти сигнал.
Тест почав виконуватися стабільно на частоті 230 Гц: 10 / 10 запусків дали якісний результат – всі події оброблені без накладок, всі таймаути витримано в припустимих межах.
Приклад одного з запусків:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
dofile("gpio_int_speed_test.lua") > 0 int: 1 183450051 1 1 int: 1 183454361 1 2 int: 1 183458672 1 3 int: 1 183462983 1 4 int: 1 183467296 1 5 int: 1 183471607 1 6 int: 1 183475919 1 7 int: 1 183480230 1 8 int: 1 183484543 1 9 int: 1 183488854 1 10 int: 1 183493162 1 rising edge delta uSec: 4310 rising edge delta uSec: 4311 rising edge delta uSec: 4311 rising edge delta uSec: 4313 rising edge delta uSec: 4311 rising edge delta uSec: 4312 rising edge delta uSec: 4311 rising edge delta uSec: 4313 rising edge delta uSec: 4311 rising edge delta uSec: 4308 average delta uSec: 4311.1 average frequency, Hz: 231.95936072 |
Це пікова можлива частота обробки подій на рівні прикладного скрипта Lua. Якщо частоту вхідного тестового сигналу збільшити, то події починають проскакувати повз Lua-обробник подій, або сприймаються ним як брязкіт.
Перевірка з частотою тактування центрального процесора за замовчуванням (80 МГц)
В коді встановлюємо частоту ЦП явно 80 МГц, або просто прибираємо інструкції з встановлення частоти ЦП, адже модуль за замовчуванням працює на потрібній нам частоті.
1 2 3 |
local CPU80MHZ = node.CPU80MHZ --local CPU160MHZ = node.CPU160MHZ setcpufreq(CPU80MHZ) |
Вхідний дискретний сигнал частотою 230 Гц
І зразу перевіряємо, як контролер відпрацює наш тестовий код з вхідним сигналом частотою 230 Гц, але вже при частоті ЦП 80 МГц.
Приклад одного з запусків:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
dofile("gpio_int_speed_test.lua") > 0 int: 1 1028960092 1 1 int: 1 1028964403 1 2 int: 1 1028968711 1 3 int: 1 1028973022 1 4 int: 1 1028977332 1 5 int: 1 1028981642 1 6 int: 1 1028985951 2 7 int: 1 1028994573 1 8 int: 1 1028998886 1 9 int: 1 1029003199 1 10 int: 1 1029007512 1 rising edge delta uSec: 4311 rising edge delta uSec: 4308 rising edge delta uSec: 4311 rising edge delta uSec: 4310 rising edge delta uSec: 4310 rising edge delta uSec: 4309 rising edge delta uSec: 8622 rising edge delta uSec: 4313 rising edge delta uSec: 4313 rising edge delta uSec: 4313 average delta uSec: 4742 average frequency, Hz: 210.88148460565 |
Як можна бачити (рядки: 6 int: 1 1028985951 2 та rising edge delta uSec: 8622) , мали отримати 230 Гц, але контролер нарахував лише 210 Гц. Так відбулося через те, що контролер просто не встиг обробити всі вхідні зростання фронтів періодичного сигналу.
10 з 10 разів тест виконався з помилками. Обробник не встигає реєструвати події і вони “ковтаються”. Переходи у “Up” та “Down” – або плутаються між собою, або складаються обробником до купи – як одна триваліша подія, чи брязкіт.
Спробуємо поступово знизити частоту вхідного сигналу так, щоб контролер почав успішно відпрацьовувати тест:
- 225 Гц: 2 з 10 успішно;
- 220 Гц: 6 з 10 успішно;
- 215 Гц: 8 з 10 успішно;
- 210 Гц: 9 з 10 успішно;
- 205 Гц: 10 з 10 успішно.
Таким чином ми визначили, що 205 Гц є максимальною частотою вхідного дискретного сигналу на GPIO ESP8266, який NodeMCU з Lua може стабільно обробляти засобами обробника переривань з стандартними налаштуваннями частоти (80 МГц) ЦП ESP8266-12.
Висновки
За декілька сот запусків у різних режимах і з різними частотами, ми на практиці визначили, що з прошивкою NodeMCU / Lua можливо стабільно обробити вхідні дискретні сигнали макимальною частотою до 205 Гц при 80 МГц ЦП; та до 230 Гц при 160 Гц ЦП:
Частота обробки подій на GPIO при Duty 50% | Частота ЦП ESP8266, МГц |
205 Гц | 80 МГц |
230 Гц | 160 МГц |
Чи означає це, що NodeMCU дуже повільна система? – з нашого досвіду – ні. Адже NodeMCU відмінно працює з пристроями по шині I2C, де мінімальна частота тактування 100 000 Гц, також NodeMCU працює з зовнішніми пристроями через інтерфейс SPI, де стандартна швидкодія 80 МГц / 8 = 10 000 000 Гц.
Також на контролері ESP8266 / NodeMCU одночасно з вже згаданими процесами, постійно працюють повноцінний WiFi та UART.
Але так, слід брати до уваги, що отримані нами результати свідчать: систему реального часу на базі даної реалізації NON OS SDK + NodeMCU Lua, будувати не варто.
NodeMCU це мініатюрний супергерой, який просто не призначений для польотів у Космос, комп’ютерного бачення чи автономного керування автомобілем.
Втім, цей мікроконтролер цілком дозволяє користувачеві натискати на кнопки 200 разів на секунду – тепер ми це теж знаємо!. 😉
Зверніть також увагу, що ці тестування виконувалися лише на зростаючих фронтах, а також з вхідним сигналом, що має Duty Cycle 50%. За інших умов, результати можуть бути іншими.