Есть у меня несколько проектов-долгостроев, один из которых — создание компьютера на базе CDP1802. Основную плату моделировал на бумаге и в Proteus.
Довольно скоро встал ребром вопрос: как быть с элементами, которые отсутствуют в Proteus?
На многих ресурсах подробно описано, как создать свою модель на С++ в Visual Studio.
К сожалению, при сборке под линуксом этот вариант не очень удобен. Да и как быть, если не знаешь С++ или нужно редактировать модель на лету для отладки?
Да и просто хочется сосредоточиться на моделировании, максимально упростив все остальное.
Так появилась идея делать симуляторные модели с помощью скриптов — на Lua.
Заинтересовавшихся прошу под кат (гифки на 2Мб).

Если забыть про всякую экзотику, вроде написания модели процессора, я давно отвык что-либо делать в симуляторе — подключил датчики к отладкам разного вида, осциллограф в руки, мультиметр, JTAG/UART и отлаживай себе.
Но когда понадобилось проверить логику работы программы при отказе GPS/в движении и тому подобном, пришлось писать эмуляцию GPS на другом микроконтроллере.
Когда было необходимо сделать телеметрию для машину под протокол KWP2000, отлаживать «на живую» было неудобно и опасно. Да и если одному — ой как неудобно.
Возможность отлаживать/тестировать в дороге или где-то, куда таскать с собой весь джентльменский набор просто неудобно (речь в первую очередь про хобби проекты) — хорошее подспорье, так что место симулятору есть.
Весь софт я пишу под GCC и модель я хотел так же собирать под ним, используя наработанные библиотеки и код, которые собрать под MSVS было бы затруднительно. Проблема заключалась в том, что собранная под mingw32 DLL вешала Proteus. Были перепробованы разные способы включая манипуляции с __thiscall и сотоварищи, а варианты с ассемблерными хаками вызовов не устраивал.
Друг moonglow с огромным опытом в таких делах предложил и показал как переписать С++ интерфейс на С, используя виртуальные таблицы. Из удобств, кроме возможности сборки под линуксом «без отрыва от производства», возможность, в теории, писать модели хоть на фортране — было бы желание.
Идея с «эмуляцией» виртуальных классов на практике выглядит так:
Оригинальный С++ заголовок виртуального класса выглядит так
А вот версия на С; это наш псевдо-класс и его виртуальная таблица
Теперь создаем структуру с указателями на функции, которые внутри класса (их мы создадим и объявим отдельно)
Пишем нужные функции и создаем один экземпляр нашего «класса», который и будем использовать
И так далее, со всеми нужными нам классами. Так как вызывать такое из структур не очень удобно, были написаны функции-обертки, какие-то вещи были автоматизированы, были добавлены отсутствующие, часто используемые функции. Даже в процессе написания этой статьи я добавил много нового, посмотрев на работу с другой стороны.
В итоге код рос и все более нарастало ощущение, что нужно что-то менять: на создание модели уходило сил и времени не меньше, чем на написания такого же эмулятора для микроконтроллера. В процессе отладки моделей требовалось постоянно что-то менять, экспериментировать. Приходилось пересобирать модель на каждой мелочи, да и работа с текстовыми данными в С оставляет желать лучшего. Знакомые, которым такое тоже было бы интересно, пугались С (кто-то используетТурбоПаскаль, кто-то QBasic).
Вспомнил о Lua: прекрасно интегрируется в С, быстр, компактен, нагляден, динамическая типизация — все что надо. В итоге продублировал все С функции в Lua с теми же названиями, получив полностью самодостаточный способ создания моделей, не требующий пересборки вообще. Можно просто взять dll и описать любую модель только на Lua. Достаточно остановить симуляцию, подправить текстовый скрипт, и снова в бой.
Основное тестирование велось в Proteus 7, но созданные с нуля и импортированные в 8-ю версию модели вели себя превосходно.
Создадим несколько простейших моделей и на их примере посмотрим, что и как мы можем сделать.
Я не буду описывать, как создать собственно графическую модель, это отлично описано тут и тут, поэтому остановлюсь именно на написании кода.
Вот 3 устройства, которые мы будем рассматривать. Я хотел сначала начать с мигания светодиодом, но потом решил, что это слишком уныло, надеюсь, не прогадал.
Начнем с A_COUNTER:

Это простейший двоичный счетчик с внутренним генератором тактов, все его выводы — выходы.
У каждой модели есть DLL, которая описывает поведение модели и взаимодействие с внешним миром. В нашем случае, у всех моделей dll будет одна и та же, а вот скрипты — разные. Итак, создаем модель:
device_pins это обязательная глобальная переменная, содержащая описание выводов устройства. На данном этапе библиотека поддерживает только цифровые устройства. Поддержка аналоговых и смешанных типов в процессе.
is_digital — наш вывод работает только с логическими уровнями, пока возможен только true
name — имя вывода на графической модели. Он должен точно соответствоват — привязка вывода внутри Proteus идет по имени.
Два оставшихся поля говорят сами за себя — время переключения пина в пикосекундах.
На самом деле, нет строгой необходимости создавать что-то в скрипте. Можно вообще ничего не писать — будет модель пустышка, но для минимального функционала нужно создать функцию device_simulate. Эта функция будет вызываться, когда изменится состояние нод (проводников), например, изменится логический уровень. Есть функция device_init. она вызывается (если существует) однократно сразу после загрузки модели.
Для установки состояния вывода в один из уровней есть функция set_pin_state, первым аргументом она принимает имя вывода, вторым — желаемое состояние, например, SHI, SLO, FLT и так далее
Для начала сделаем так, чтобы на запуске все выводы находились в логическом 0, с помощью однострочника/
Мы можем обращаться к выводу как через глобальную переменную, к примеру, A0, Так и через её имя как строковую константу «А0» через глобальную таблицу окружения _G
Теперь нам нужно реализовать сам счетчик; Начнем с задающего генератора. Для этого есть функция timer_callback, принимающую два аргумента — время и номер события.
Добавим в device_init после выставления состояние вывода следующий вызов:
PC_EVENT это числовая переменная, содержащая код события (её мы должны объявить глобально)
NOW означает что вызвать обработчик события нужно через 0 пикосекунд от текущего времени (функция принимает как аргумент пикосекунды)
А вот и функция обработчик
По событию вызывается функция set_pin_bool, которая управляет выводом принимая как аргумент одно из двух состояний — 1/0.
Можно заметить, что после переключения вывода снова вызывается set_callback, ибо эта функция планирует непериодические события. Разница в задании времени из-за того, что set_callback будет вызвана в будущем, поэтому нам нужно добавить разницу во времени, а time как раз содержит текущее системное время
Все остальное — объявление, инициализация модели и так далее делается на стороне библиотеки. Хотя разумеется, все то же самое можно сделать на С, а Lua использовать для прототипирования, благо названия функций идентичны.
Запускаем симуляцию и наблюдаем работу нашей модели

Основной целью было облегчение написания моделей и их отладки, поэтому рассмотрим некоторые возможности вывода полезной информации
4 функции для вывода в лог сообщений, причем две последнии автоматически приведут к остановку симуляции

Благодаря возможностям Lua легко, удобно, быстро и наглядно можно выводить любую нужную информацию:
Теперь перейдем ко второй нашей модели — микросхемы ПЗУ, и посмотрим на
Смоделируем нашу ПЗУ и подебажим её во время работы.
Объявления выводов тут ничем не отличается, но нам нужно добавить свойств нашей микросхеме, в первую очередь — возможность загрузить дамп памяти из файла:

Делается это в текстовом скрипте при создании модели:
Теперь сделаем так, что при постановке на паузу симуляции можно было посмотреть важную информацию о модели, такую как содержимое её памяти, содержимое адресной шины, шины данных, время работы. Для вывода бинарных данных в удобной форме есть memory_popup.
Функция on_suspend вызывается (если объявлена пользователем) во время постановки на паузу. Если окно не создано — создадим его.
Память передается в библиотеку как указатель, ничего высвобождать потом не нужно — все сделает сборщик мусора Lua. И создадим окно debug типа, куда выведем нужны нам переменные и для масовки сдампим 32 байта со смещения 0x1000:

Наконец, реализуем сам алгоритм работу ПЗУ, оставив без внимания OE, VPP и прочие CE выводы

Сделаем что-нибудь для нашего «отладчика»:

Интересный вопрос, который меня волновал. Я взял модель двоичного счетчика 4040, идущего в поставке Proteus 7 и сделал свой аналог.
Используя генератор импульсов подал на вход обоим моделям меандр с частотой 100кГц
Proteus's 4040 = 15-16% CPU Load
Библиотека на С = 25-28% CPU Load
Библиотека и Lua 5.2 = 98-100% CPU Load
Библиотека и Lua 5.3a = 76-78% CPU Load
Не сравнивал исходники, но видимо очень сильно оптимизировали виртуальную машину в версии 5.3. Тем ни менее, вполне терпимо за удобство работы.
Да и вопросами оптимизации я даже не начинал заниматься.
Весь этот проект родился как спонтанная идея, и ещё много чего нужно сделать:
Разумеется, хотелось бы найти единомышленников, желающих помочь если и не участием в написании кода, то идеями и отзывами. Ведь сейчас многое захардкодено под цели и задачи, которые нужны были мне.
Репозиторий с кодом.
Готовая библиотека и отладочные символы для GDB лежат тут.
Довольно скоро встал ребром вопрос: как быть с элементами, которые отсутствуют в Proteus?
На многих ресурсах подробно описано, как создать свою модель на С++ в Visual Studio.
К сожалению, при сборке под линуксом этот вариант не очень удобен. Да и как быть, если не знаешь С++ или нужно редактировать модель на лету для отладки?
Да и просто хочется сосредоточиться на моделировании, максимально упростив все остальное.
Так появилась идея делать симуляторные модели с помощью скриптов — на Lua.
Заинтересовавшихся прошу под кат (гифки на 2Мб).

Зачем это надо
Если забыть про всякую экзотику, вроде написания модели процессора, я давно отвык что-либо делать в симуляторе — подключил датчики к отладкам разного вида, осциллограф в руки, мультиметр, JTAG/UART и отлаживай себе.
Но когда понадобилось проверить логику работы программы при отказе GPS/в движении и тому подобном, пришлось писать эмуляцию GPS на другом микроконтроллере.
Когда было необходимо сделать телеметрию для машину под протокол KWP2000, отлаживать «на живую» было неудобно и опасно. Да и если одному — ой как неудобно.
Возможность отлаживать/тестировать в дороге или где-то, куда таскать с собой весь джентльменский набор просто неудобно (речь в первую очередь про хобби проекты) — хорошее подспорье, так что место симулятору есть.
Visual Studio C++ и GCC
Весь софт я пишу под GCC и модель я хотел так же собирать под ним, используя наработанные библиотеки и код, которые собрать под MSVS было бы затруднительно. Проблема заключалась в том, что собранная под mingw32 DLL вешала Proteus. Были перепробованы разные способы включая манипуляции с __thiscall и сотоварищи, а варианты с ассемблерными хаками вызовов не устраивал.
Друг moonglow с огромным опытом в таких делах предложил и показал как переписать С++ интерфейс на С, используя виртуальные таблицы. Из удобств, кроме возможности сборки под линуксом «без отрыва от производства», возможность, в теории, писать модели хоть на фортране — было бы желание.
Мимикрируем под С++
Идея с «эмуляцией» виртуальных классов на практике выглядит так:
Оригинальный С++ заголовок виртуального класса выглядит так
class IDSIMMODEL { public: virtual INT isdigital ( CHAR* pinname ) = 0; virtual VOID setup ( IINSTANCE* instance, IDSIMCKT* dsim ) = 0; virtual VOID runctrl ( RUNMODES mode ) = 0; virtual VOID actuate ( REALTIME time, ACTIVESTATE newstate ) = 0; virtual BOOL indicate ( REALTIME time, ACTIVEDATA* newstate ) = 0; virtual VOID simulate ( ABSTIME time, DSIMMODES mode ) = 0; virtual VOID callback ( ABSTIME time, EVENTID eventid ) = 0; };
А вот версия на С; это наш псевдо-класс и его виртуальная таблица
struct IDSIMMODEL { IDSIMMODEL_vtable* vtable; };
Теперь создаем структуру с указателями на функции, которые внутри класса (их мы создадим и объявим отдельно)
struct IDSIMMODEL_vtable { int32_t __attribute__ ( ( fastcall ) ) ( *isdigital ) ( IDSIMMODEL* this, EDX, CHAR* pinname ); void __attribute__ ( ( fastcall ) ) ( *setup ) ( IDSIMMODEL* this, EDX, IINSTANCE* inst, IDSIMCKT* dsim ); void __attribute__ ( ( fastcall ) ) ( *runctrl ) ( IDSIMMODEL* this, EDX, RUNMODES mode ); void __attribute__ ( ( fastcall ) ) ( *actuate ) ( IDSIMMODEL* this, EDX, REALTIME atime, ACTIVESTATE newstate ); bool __attribute__ ( ( fastcall ) ) ( *indicate ) ( IDSIMMODEL* this, EDX, REALTIME atime, ACTIVEDATA* data ); void __attribute__ ( ( fastcall ) ) ( *simulate ) ( IDSIMMODEL* this, EDX, ABSTIME atime, DSIMMODES mode ); void __attribute__ ( ( fastcall ) ) ( *callback ) ( IDSIMMODEL* this, EDX, ABSTIME atime, EVENTID eventid ); };
Пишем нужные функции и создаем один экземпляр нашего «класса», который и будем использовать
IDSIMMODEL_vtable VSM_DEVICE_vtable = { .isdigital = vsm_isdigital, .setup = vsm_setup, .runctrl = vsm_runctrl, .actuate = vsm_actuate, .indicate = vsm_indicate, .simulate = vsm_simulate, .callback = vsm_callback, }; IDSIMMODEL VSM_DEVICE = { .vtable = &VSM_DEVICE_vtable, };
И так далее, со всеми нужными нам классами. Так как вызывать такое из структур не очень удобно, были написаны функции-обертки, какие-то вещи были автоматизированы, были добавлены отсутствующие, часто используемые функции. Даже в процессе написания этой статьи я добавил много нового, посмотрев на работу с другой стороны.
«Сделай настолько просто, насколько это возможно, но не проще»
В итоге код рос и все более нарастало ощущение, что нужно что-то менять: на создание модели уходило сил и времени не меньше, чем на написания такого же эмулятора для микроконтроллера. В процессе отладки моделей требовалось постоянно что-то менять, экспериментировать. Приходилось пересобирать модель на каждой мелочи, да и работа с текстовыми данными в С оставляет желать лучшего. Знакомые, которым такое тоже было бы интересно, пугались С (кто-то использует
Вспомнил о Lua: прекрасно интегрируется в С, быстр, компактен, нагляден, динамическая типизация — все что надо. В итоге продублировал все С функции в Lua с теми же названиями, получив полностью самодостаточный способ создания моделей, не требующий пересборки вообще. Можно просто взять dll и описать любую модель только на Lua. Достаточно остановить симуляцию, подправить текстовый скрипт, и снова в бой.
Моделирование в Lua
Основное тестирование велось в Proteus 7, но созданные с нуля и импортированные в 8-ю версию модели вели себя превосходно.
Создадим несколько простейших моделей и на их примере посмотрим, что и как мы можем сделать.
Я не буду описывать, как создать собственно графическую модель, это отлично описано тут и тут, поэтому остановлюсь именно на написании кода.
Вот 3 устройства, которые мы будем рассматривать. Я хотел сначала начать с мигания светодиодом, но потом решил, что это слишком уныло, надеюсь, не прогадал.
Начнем с A_COUNTER:

Это простейший двоичный счетчик с внутренним генератором тактов, все его выводы — выходы.
У каждой модели есть DLL, которая описывает поведение модели и взаимодействие с внешним миром. В нашем случае, у всех моделей dll будет одна и та же, а вот скрипты — разные. Итак, создаем модель:
Описание модели
device_pins = { {is_digital=true, name = "A0", on_time=100000, off_time=100000}, {is_digital=true, name = "A1", on_time=100000, off_time=100000}, {is_digital=true, name = "A2", on_time=100000, off_time=100000}, {is_digital=true, name = "A3", on_time=100000, off_time=100000}, --тут пропущены однотипные определения для остальных выводов --чтобы не прятать под кат {is_digital=true, name = "A15", on_time=100000, off_time=100000}, }
device_pins это обязательная глобальная переменная, содержащая описание выводов устройства. На данном этапе библиотека поддерживает только цифровые устройства. Поддержка аналоговых и смешанных типов в процессе.
is_digital — наш вывод работает только с логическими уровнями, пока возможен только true
name — имя вывода на графической модели. Он должен точно соответствоват — привязка вывода внутри Proteus идет по имени.
Два оставшихся поля говорят сами за себя — время переключения пина в пикосекундах.
Необходимые функции, объявляемые пользователем
На самом деле, нет строгой необходимости создавать что-то в скрипте. Можно вообще ничего не писать — будет модель пустышка, но для минимального функционала нужно создать функцию device_simulate. Эта функция будет вызываться, когда изменится состояние нод (проводников), например, изменится логический уровень. Есть функция device_init. она вызывается (если существует) однократно сразу после загрузки модели.
Для установки состояния вывода в один из уровней есть функция set_pin_state, первым аргументом она принимает имя вывода, вторым — желаемое состояние, например, SHI, SLO, FLT и так далее
Для начала сделаем так, чтобы на запуске все выводы находились в логическом 0, с помощью однострочника/
Мы можем обращаться к выводу как через глобальную переменную, к примеру, A0, Так и через её имя как строковую константу «А0» через глобальную таблицу окружения _G
function device_init() for k, v in pairs(device_pins) do set_pin_state(_G[v.name], SLO) end end
Теперь нам нужно реализовать сам счетчик; Начнем с задающего генератора. Для этого есть функция timer_callback, принимающую два аргумента — время и номер события.
Добавим в device_init после выставления состояние вывода следующий вызов:
set_callback(NOW, PC_EVENT)
PC_EVENT это числовая переменная, содержащая код события (её мы должны объявить глобально)
NOW означает что вызвать обработчик события нужно через 0 пикосекунд от текущего времени (функция принимает как аргумент пикосекунды)
А вот и функция обработчик
function timer_callback(time, eventid) if eventid == PC_EVENT then for k, v in pairs(device_pins) do set_pin_bool(_G[v.name], get_bit(COUNTER, k) ) end COUNTER = COUNTER + 1 set_callback(time + 100 * MSEC, PC_EVENT) end end
По событию вызывается функция set_pin_bool, которая управляет выводом принимая как аргумент одно из двух состояний — 1/0.
Можно заметить, что после переключения вывода снова вызывается set_callback, ибо эта функция планирует непериодические события. Разница в задании времени из-за того, что set_callback будет вызвана в будущем, поэтому нам нужно добавить разницу во времени, а time как раз содержит текущее системное время
Итого, вот что вышло
device_pins = { {is_digital=true, name = "A0", on_time=100000, off_time=100000}, {is_digital=true, name = "A1", on_time=100000, off_time=100000}, {is_digital=true, name = "A2", on_time=100000, off_time=100000}, {is_digital=true, name = "A3", on_time=100000, off_time=100000}, {is_digital=true, name = "A4", on_time=100000, off_time=100000}, {is_digital=true, name = "A5", on_time=100000, off_time=100000}, {is_digital=true, name = "A6", on_time=100000, off_time=100000}, {is_digital=true, name = "A7", on_time=100000, off_time=100000}, {is_digital=true, name = "A8", on_time=100000, off_time=100000}, {is_digital=true, name = "A9", on_time=100000, off_time=100000}, {is_digital=true, name = "A10", on_time=100000, off_time=100000}, {is_digital=true, name = "A11", on_time=100000, off_time=100000}, {is_digital=true, name = "A12", on_time=100000, off_time=100000}, {is_digital=true, name = "A13", on_time=100000, off_time=100000}, {is_digital=true, name = "A14", on_time=100000, off_time=100000}, {is_digital=true, name = "A15", on_time=100000, off_time=100000}, } PC_EVENT = 0 COUNTER = 0 function device_init() for k, v in pairs(device_pins) do set_pin_state(_G[v.name], SLO) end set_callback(0, PC_EVENT) end function timer_callback(time, eventid) if eventid == PC_EVENT then for k, v in pairs(device_pins) do set_pin_bool(_G[v.name], get_bit(COUNTER, k) ) end COUNTER = COUNTER + 1 set_callback(time + 100 * MSEC, PC_EVENT) end end
Все остальное — объявление, инициализация модели и так далее делается на стороне библиотеки. Хотя разумеется, все то же самое можно сделать на С, а Lua использовать для прототипирования, благо названия функций идентичны.
Запускаем симуляцию и наблюдаем работу нашей модели
Возможности отладки
Основной целью было облегчение написания моделей и их отладки, поэтому рассмотрим некоторые возможности вывода полезной информации
Текстовые сообщения
4 функции для вывода в лог сообщений, причем две последнии автоматически приведут к остановку симуляции
out_log("This is just a message") out_warning("This is warning") out_error("This is error") out_fatal("This is fatal error")

Благодаря возможностям Lua легко, удобно, быстро и наглядно можно выводить любую нужную информацию:
out_log("We have "..#device_pins.." pins in our device")
Теперь перейдем ко второй нашей модели — микросхемы ПЗУ, и посмотрим на
Всплывающие окна
Смоделируем нашу ПЗУ и подебажим её во время работы.
Объявления выводов тут ничем не отличается, но нам нужно добавить свойств нашей микросхеме, в первую очередь — возможность загрузить дамп памяти из файла:

Делается это в текстовом скрипте при создании модели:
{FILE=«Image File»,FILENAME,FALSE,,Image/*.BIN}
Теперь сделаем так, что при постановке на паузу симуляции можно было посмотреть важную информацию о модели, такую как содержимое её памяти, содержимое адресной шины, шины данных, время работы. Для вывода бинарных данных в удобной форме есть memory_popup.
function device_init() local romfile = get_string_param("file") rom = read_file(romfile) mempop, memid = create_memory_popup("My ROM dump") set_memory_popup(mempop, rom, string.len(rom)) end function on_suspend() if nil == debugpop then debugpop, debugid = create_debug_popup("My ROM vars") print_to_debug_popup(debugpop, string.format("Address: %.4X\nData: %.4X\n", ADDRESS, string.byte(rom, ADDRESS))) dump_to_debug_popup(debugpop, rom, 32, 0x1000) elseif debugpop then print_to_debug_popup(debugpop, string.format("Address: %.4X\nData: %.4X\n", ADDRESS, string.byte(rom, ADDRESS))) dump_to_debug_popup(debugpop, rom, 32, 0x1000) end end
Функция on_suspend вызывается (если объявлена пользователем) во время постановки на паузу. Если окно не создано — создадим его.
Память передается в библиотеку как указатель, ничего высвобождать потом не нужно — все сделает сборщик мусора Lua. И создадим окно debug типа, куда выведем нужны нам переменные и для масовки сдампим 32 байта со смещения 0x1000:

Наконец, реализуем сам алгоритм работу ПЗУ, оставив без внимания OE, VPP и прочие CE выводы
function device_simulate() for i = 0, 14 do if 1 == get_pin_bool(_G["A"..i]) then ADDRESS = set_bit(ADDRESS, i) else ADDRESS = clear_bit(ADDRESS, i) end end for i = 0, 7 do set_pin_bool(_G["D"..i], get_bit(string.byte(rom, ADDRESS), i)) end end
Сделаем что-нибудь для нашего «отладчика»:
создадим программный UART, в который будем выводить содержимое шины данных
device_pins = { {is_digital=true, name = "D0", on_time=1000, off_time=1000}, {is_digital=true, name = "D1", on_time=1000, off_time=1000}, {is_digital=true, name = "D2", on_time=1000, off_time=1000}, {is_digital=true, name = "D3", on_time=1000, off_time=1000}, {is_digital=true, name = "D4", on_time=1000, off_time=1000}, {is_digital=true, name = "D5", on_time=1000, off_time=1000}, {is_digital=true, name = "D6", on_time=1000, off_time=1000}, {is_digital=true, name = "D7", on_time=1000, off_time=1000}, {is_digital=true, name = "TX", on_time=1000, off_time=1000}, } -- UART events UART_STOP = 0 UART_START = 1 UART_DATA=2 -- Constants BAUD=9600 BAUDCLK = SEC/BAUD BIT_COUNTER = 0 ----------------------------------------------------------------- DATA_BUS = 0 function device_init() end function device_simulate() for i = 0, 7 do if 1 == get_pin_bool(_G["D"..i]) then DATA_BUS = set_bit(DATA_BUS, i) else DATA_BUS = clear_bit(DATA_BUS, i) end end uart_send(string.format("[%d] Fetched opcode %.2X\r\n", systime(), DATA_BUS)) end function timer_callback(time, eventid) uart_callback(time, eventid) end function uart_send (string) uart_text = string char_count = 1 set_pin_state(TX, SHI) -- set TX to 1 in order to have edge transition set_callback(BAUDCLK, UART_START) --schedule start end function uart_callback (time, event) if event == UART_START then next_char = string.byte(uart_text, char_count) if next_char == nil then return end char_count = char_count +1 set_pin_state(TX, SLO) set_callback(time + BAUDCLK, UART_DATA) end if event == UART_STOP then set_pin_state(TX, SHI) set_callback(time + BAUDCLK, UART_START) end if event == UART_DATA then if get_bit(next_char, BIT_COUNTER) == 1 then set_pin_state(TX, SHI) else set_pin_state(TX, SLO) end if BIT_COUNTER == 7 then BIT_COUNTER = 0 set_callback(time + BAUDCLK, UART_STOP) return end BIT_COUNTER = BIT_COUNTER + 1 set_callback(time + BAUDCLK, UART_DATA) end end
Производительность
Интересный вопрос, который меня волновал. Я взял модель двоичного счетчика 4040, идущего в поставке Proteus 7 и сделал свой аналог.
Используя генератор импульсов подал на вход обоим моделям меандр с частотой 100кГц
Proteus's 4040 = 15-16% CPU Load
Библиотека на С = 25-28% CPU Load
Библиотека и Lua 5.2 = 98-100% CPU Load
Библиотека и Lua 5.3a = 76-78% CPU Load
Не сравнивал исходники, но видимо очень сильно оптимизировали виртуальную машину в версии 5.3. Тем ни менее, вполне терпимо за удобство работы.
Да и вопросами оптимизации я даже не начинал заниматься.
Весь этот проект родился как спонтанная идея, и ещё много чего нужно сделать:
Ближайшие планы
- Пофиксить явные баги в коде
- Максимально уменьшить возможность выстрелить себе в ногу
- Документировать код под Doxygen
- Возможно, перейти на luaJIT
- Реализовать аналоговые и смешанные типы устройств
- С плагин для IDA
Разумеется, хотелось бы найти единомышленников, желающих помочь если и не участием в написании кода, то идеями и отзывами. Ведь сейчас многое захардкодено под цели и задачи, которые нужны были мне.
Скачать без рекламы и смс
Репозиторий с кодом.
Готовая библиотека и отладочные символы для GDB лежат тут.