Наверное многие слышали или даже использовали программу Proteus. Для тех кто не знает, это пакет программ для автоматизированного проектирования (САПР) электронных схем. Разработан компанией Labcenter Electronics (Великобритания). http://ru.wikipedia.org/wiki/Proteus_(САПР)
Он состоит из двух составных частей: ISIS — программы синтеза и моделирования электронных схем (симулятор) и ARES — программы разработки печатных плат. О первой (ISIS) далее пойдет речь.
Эта программа позволяет "нарисовав схему" проверить ее работу без сборки устройства. Каждый электронный компонент в симуляторе является моделью. Но не всегда можно найти требуемую модель или ее аналог. В основном это касается не очень распространенных электронных компонентов или компонентов нашего (времен СССР) производства. В этом случае, можно создать модель самому.
Как создать модель для Proteus и что она из себя представляет?
О создании моделей (компонентов) для протеуса в интернете можно найти несколько статей. Например. http://nedopc.org/forum/viewtopic.php?t=10110

Разработку модели условно можно разделить на два этапа - рисование ее графической части, отображаемой на экране и создание программного кода.

Но сначала определимся модель с каким функционалом будем создавать. Для примера, создадим цифровую модель (существуют и аналоговые), являющуюся гибридом инвертирующего элемента (логическое состояние выхода противоположно входному) и генератора с периодом - 1 секунда и скажностью 2.

Разработка графической части модели для протеуса.

Здесь будут кратко показаны этапы создания "внешнего вида" модели Proteus. Более подробно все рассмотрено по ссылке выше.
Создаем новый проект в протеусе (обычно автоматически создается при запуске). Потом на панели инструментов в левой части окна, нажимаем на кнопку с изображением зеленного квадратика. После чего переведя указатель мышки на рабочее поле и нажав на левую кнопку, рисуем квадрат или прямоугольник, который будет основой изображения нашей модели.

http://i031.radikal.ru/1404/3d/667cb8b4db27.png

Затем добавим "украшательства" (не влияют на поведение модели и могут отсутствовать).

Добавим линию (необязательно).

http://s020.radikal.ru/i718/1404/d1/279e7dbeea47.png

И букву G, обозначив что это генератор (необязательно).

http://s019.radikal.ru/i630/1404/99/ff25c74dbabb.png

Теперь добавим выводы к модели, с помощью которых она будет "общаться" с остальной схемой.

http://s019.radikal.ru/i625/1404/33/11f58b203b36.png

Вывод с обозначением инвертирования (маленький кружочек между у вывода) только для обозначения данного факта. Будет сигнал проинвертирован или нет, зависит от программного кода модели.

На скрине можно увидеть что выводы пронумерованы и им присвоены имена, но при их добавлении этого нет. Чтобы добавить эту информацию, нужно дважды кликнуть лавой кнопкой мышки по выводу и в окне задать данные (имя, номер и направление вывода, вход или выход). Учите что имя используется для идентификации вывода в коде модели и если задать имена отличающиеся от тех что на скриншоте, понадобится вносить изменения в код модели. Выводы слева необходимо настроить как входы, а справа, как выходы.

http://i053.radikal.ru/1404/a4/76b1eb65a541.png

Теперь добавим описание модели.

http://s57.radikal.ru/i158/1404/9e/bf487c529cf4.png
Create model for Proteus

Код скрипта.

Код:
{*DEVICE} 
NAME=TestModel 
{PREFIX=DD} 
{*PROPDEFS} 
{MODDLL="VSM Model DLL",HIDDEN STRING} 
{PRIMITIVE="PrimitiveType",HIDDEN STRING} 
{*COMPONENT} 
{MODDLL=TestModel.dll} 
{PRIMITIVE=DIGITAL,TestModel}

Описание каждой строки можно найти здесь. Но отмечу, что в параметре MODDLL должно быть имя DLL-библиотеки с программным кодом модели. О создании такой библиотеки будет рассказано далее.

Описание модели должно быть рядом с ней.

http://i017.radikal.ru/1404/b2/d511ee6ea96e.png
Create component for Proteus

Теперь графическую часть модели можно считать созданной и ее можно добавить в библиотеку протеуса.
Для этого следует мышкой выделить модель и ее скрипт описания и в меню "Library" кликнуть по пункту "Make Device".

http://s019.radikal.ru/i638/1404/d8/55da921a8f49.png

В случае отсутствия ошибок, будет открыт ряд окон, описание которых можно найти здесь.

Продублирую оригинальный текст и скриншоты на случай недоступности ресурса.

Далее в этом процессе создания устройства для ввода параметров модели на экране последовательно появятся пять вкладок.
В первой вкладке отражаются строки.

Код:
{*DEVICE} 
NAME=DCFF 
{PREFIX=DD}

http://i062.radikal.ru/1404/3d/227b96b4bc3e.gif

Остальные поля этой вкладки в нашем проекте не используются.

Следующая вкладка позволяет выбрать нашему устройству стандартный корпус, геометрические размеры которого будут использованы при разводке печатной платы.

http://s52.radikal.ru/i138/1404/05/4327d12475ec.gif

На следующей вкладке отражены следующие строки текстового скрипта:

Код:
{*PROPDEFS} 
{MODDLL="VSM Model DLL",HIDDEN STRING} 

{*COMPONENT} 
{MODDLL=TestModel.dll}

На скриншоте вместо TestModel.dll показано Flipflop.dll. В нашем случае, будет TestModel.dll.

http://s020.radikal.ru/i710/1404/b9/8315a309845a.gif

Код:
{PRIMITIVE="PrimitiveType",HIDDEN STRING} 
... 
{PRIMITIVE=DIGITAL,DCFF}

http://s020.radikal.ru/i712/1404/3f/9a6aa50e2f48.gif

Следующая вкладка позволяет сопоставить нашей модели help–файл…

http://s019.radikal.ru/i621/1404/39/4e20fa07aff7.gif

И на последней вкладке в окна Категория устройства (Device Category) и Изготовитель устройства (Device Manufacturer) установим через сопутствующие кнопки New соответственно Прочее (Miscellaneous) и ExUSSR (я обычно делаю так, поскольку чаще всего подходящей фирмы-изготовителя в списке не находится). Принципиального значения эти настройки не имеют, и делается это более для того, чтобы не потерять нашу модель позже в оригинальных библиотеках Proteus. Поэтому, если есть желание, можно заполнить и поля Описание устройства (Device Description – я написал здесь D-Flip-Flop), а также Заметки по устройству (Device Notes - Test model).

http://s020.radikal.ru/i713/1404/38/588e75b6db14.gif

Скорее всего, Proteus предложит разместить нашу модель в библиотеке USERDVC и другой альтернативы не предоставит.
Заканчиваем процесс создания устройства нажатием кнопки ОК.

Вновь созданная модель DCFF теперь существует в библиотеке USERDVC Proteus, она доступна для выбора любому проекту из вкладки Выбор Устройства (Pick Devices).

http://i016.radikal.ru/1404/91/8f9a00037d1b.gif

Замечу, что лучше не сохранять модель в библиотеке USERDVC, а предварительно создать под нее новую. Это упростит распространение модели (если заходите ею с кем-то поделится) т. к. данные модели будут в отдельных файлах, а не в файлах USERDVC.

Теперь приступим к созданию программного кода модели. Как упоминалось выше, при создании графической части модели, ее программный код должен быть скомпилирован в виде DLL-библиотеки.

Разработка программной части модели для протеуса.

Для создания программного кода библиотеки и компиляции в DLL, настоятельно рекомендуют использовать MS VS (Microsoft Visual Studio) C++ 5.0 и выше. Судя по информации в интернете, даже другой популярный у нас компилятор Borland C++ Builder не подходит для этих целей, т. к. не совместим по ряду параметров, например, таких как структура объектов.
Но если использовать VS C++, то этот материал принципиально не будет отличаться от уже имеющегося в интернете. Моей задачей является не только разработка примера модели, но и поиск альтернативного инструмента (компилятора) совместимого с VS C++. Причем возьмем язык программирования и близко не похожий на C++  - бейсик. Возможно многие подумали про VisualBasic, который создан фирмой Microsoft и по идее должен быть совместим (фирма же одна и та же). Но на деле, все немного не так. VB6 и подобные ему, вообще не умеют создавать стандартные DLL, а только ActiveX DLL, что не одно и тоже. VB.NET так же не создает стандартные DLL. По этой причине был выбран бейсик фирмы Fantaisie Software - PureBasic. Да, в нем тоже есть некоторая несовместимость с классами VS C++, но ее не сложно преодолеть. Основная проблема в том, что при вызове методов класса, используется соглашение stdcall, а не thiscall как в VS C++, но это довольно просто преодолеть. Как? Воспользоваться возможностями ассемблера FASM (который использован в процессе компиляции исходного текста), применив "хитрый", макрос который во время компиляции (из ассемблера в объектный файл) изменяет одну ассемблерную инструкцию (PUSH EAX заменяет на MOV ECX, EAX) в вызове метода объекта и в итоге, все вызовы методов станут не stdcall, а thiscall. Причем подмена настолько изящная, что IDA Pro заявляет что вызов thiscall. Даже при визуальном анализе дизассемблированного кода, виден вызов thiscall, а от stdcall и следа не остается. Код макроса.

Код:
!macro CALL arg
! {
! clabel = $
! CALL arg
! plabel = clabel-3
! callsize = $-clabel
! load ops dword from plabel
! load opc byte from plabel+4
! If ops=$FF008B50 & (opc=$10 | opc=$50 | opc=$90)
!   If opc=$10
!     db $10
!   Else If (opc=$50 | opc=$90)
!     db $00
!     Repeat callsize
!       load op byte from clabel+callsize-%
!       store byte op at clabel+callsize-%+1
!     End Repeat
!   End If
!   store dword $008BC189 at plabel
!   store byte $FF at plabel+4
! End If
! }

Его нужно добавить в начало кода и дальше он выполнит свое дело по замене stdcall на thiscall во всех вызовах методов объектов. Этот макрос находится в файле vsm.pbi и добавлять в исходный текст библиотеки, нет необходимости. Достаточно подключить этот файл к исходнику DLL модели.

Еще одна проблема в том, что протеус использует объектно ориентированную модель работы, а PureBasic официально не поддерживает ООП, но его не сложно реализовать, т. к. поддерживаются абстрактные классы, называемые интерфейсами. http://purebasic.info/phpBB3ex/viewtopi … amp;t=2278 Изначально они предназначены для работы с COM (Component Object Model) но в данном случае тоже подходят.

Препятствием так же является то, что SDK протеуса только для C++, но размер его небольшой и в коде в основном константы, структуры и абстрактные классы, поэтому перевод кода на PureBasic не вызывает затруднений. Примерно треть SDK уже переведена и этого достаточно для данного примера модели. SDK протеуса полностью переведен на PureBasic - файл vsm.pbi.

Теперь рассмотрим подробнее что из себя представляет код DLL.
Из DLL-библиотеки должно экспортироваться две функции с именами createdsimmodel() и deletedsimmodel(). Из может быть больше, но в данной простой модели, этого достаточно. Соглашение вызова должно быть cdecl. Код функций.

Код:
ProcedureCDLL createdsimmodel(device.s, *ils.ILICENCESERVER)
  Protected *Result=0
    
  If *ils\Authorize($80808081)
    *Result=ModelNEW()
  EndIf
  
  ProcedureReturn *Result
EndProcedure

ProcedureCDLL deletedsimmodel(*model.IDSIMMODEL)
  If *model
    FreeMemory(*model) 
  EndIf
EndProcedure

Строка ProcedureCDLL означает что процедура экспортируется из DLL и используется соглашение вызова cdecl.
Первая процедура вызывается при создании модели, а вторая - при ее удалении.
В процедуру createdsimmodel() через аргументы передается название устройства которое нужно создать и указатель на объект сервера лицензирования. Далее вызывается один из методов этого объекта. Почему ему передается именно шестнадцатеричное число 80808081, я не знаю. Нашел этот код в интернете и он работает. Если лицензирование прошло успешно, то вызывается процедура ModelNEW(), создающая новый объект устройства, типа IDSIMMODEL, указатель на который передается протеусу.
Функция deletedsimmodel() вызывается при удалении объекта, типа IDSIMMODEL. Она должна очистить память и освободить ресурсы (если это требуется) занимаемые объектом, что собственно и делает (освобождает память) функция FreeMemory().
Теперь рассмотрим создание объекта, с учетом того, что PureBasic по сути объектно ориентированное программирование официально не поддерживает.
Необходимо создать объект типа IDSIMMODEL, который в формате PureBasic, в виде интерфейса (абстрактного класса) выглядит таким образом.

Код:
Interface IDSIMMODEL
  Isdigital(pinname.s)
  Setup(*instance.IINSTANCE, *dsim.IDSIMCKT)
  Runctrl(mode.RUNMODES)
  Actuate(time.REALTIME, newstate.ACTIVESTATE)
  Indicate(time.REALTIME,  *newstate.ACTIVEDATA)
  Simulate(time.ABSTIME, mode.DSIMMODES)
  Callback(time.ABSTIME, eventid.EVENTID)
EndInterface

Создать этот объект можно используя обычные процедуры. Код будет примерно таким.

Код:
XIncludeFile "vsm.pbi"

Structure ClassParam
  *vt ;pointer to virtual table as first entry 
  
  ; Приватные переменные класса.
  *inst.IINSTANCE
  *ckt.IDSIMCKT
  *Pin1.IDSIMPIN
  *Pin2.IDSIMPIN
  *Pin3.IDSIMPIN
  *Pin4.IDSIMPIN
EndStructure 

EnableExplicit
EnableASM

Declare ModelNEW()

ProcedureCDLL createdsimmodel(device.s, *ils.ILICENCESERVER)
  Protected *Result=0
    
  If *ils\Authorize($80808081)
    *Result=ModelNEW()
  EndIf
  
  ProcedureReturn *Result
EndProcedure

ProcedureCDLL deletedsimmodel(*model.IDSIMMODEL)
  If *model
    FreeMemory(*model) 
  EndIf
EndProcedure

Procedure isdigital(*PinName)
  Protected *This.ClassParam
  mov *This, ecx
  
  ProcedureReturn 1 ; В компоненте все выводы цифровые.
EndProcedure

Procedure setup(*instance.IINSTANCE, *dsimckt.IDSIMCKT)
  Protected *This.ClassParam
  mov *This, ecx
  	
EndProcedure

Procedure runctrl(mode.RUNMODES)
  Protected *This.ClassParam
  mov *This, ecx
EndProcedure

Procedure actuate(time.REALTIME, newstate.ACTIVESTATE)
  Protected *This.ClassParam
  mov *This, ecx
EndProcedure

Procedure indicate(time.REALTIME,  *aData.ACTIVEDATA)
  Protected *This.ClassParam
  mov *This, ecx
  
  ProcedureReturn #True
EndProcedure

Procedure simulate(time.ABSTIME, mode.DSIMMODES)
  Protected *This.ClassParam
  mov *This, ecx
  
EndProcedure

Procedure callback(time.ABSTIME, eventid.EVENTID)
  Protected *This.ClassParam
  mov *This, ecx
  
EndProcedure

Procedure ModelNEW()   ; Создание объекта.
  Protected *this.ClassParam=0
  
  *this = AllocateMemory(SizeOf(ClassParam)) 
  If *this
    *this\vt = ?ClassFunct
  EndIf   
  
  ProcedureReturn *this 
EndProcedure

DataSection
  ClassFunct:
  Data.i @isdigital()
  Data.i @setup()
  Data.i @runctrl()
  Data.i @actuate()
  Data.i @indicate()
  Data.i @simulate()
  Data.i @callback()
EndDataSection

Можно заметить соответствие имен процедур с методами интерфейса и оно не случайно поскольку эти процедуры являются составной частью класса типа IDSIMMODEL. Но имена процедур не обязательно должно быть именно такими. Из можно выбрать другими если в этом есть необходимость. В так называемой ДатаСекции (DataSection) сохранены адреса процедур причем их последовательность должна соответствовать той что в интерфейсе (классе) IDSIMMODEL, иначе правильно работать не будет. Адреса сохраняются во время компиляции и хранятся в исполняемом файле в виде 4-ех байтных констант (для x86). Последовательность расположения процедур в коде может быть произвольной.
Обратите внимание что в начале процедур от isdigital() и до callback() присутствует строка mov *This, ecx Для чего она нужна? Дело в том, что эти процедуры являются составной частью класса и вызываются из протеуса используя соглашение вызова thiscall, а функции рассчитаны на stdcall. Разница в том, что в отличие от stdcall, при thiscall, указатель на экземпляр класса передается в регистре ecx процессора, а не в стеке. Указанная строка копирует содержимое регистра ecx в локальную переменную *This, являющеюся указателем на структуру типа ClassParam. В данном коде, с этим экземпляром структуры работа не производится, но в далее рассматриваемом коде, этот указатель необходим. При использовании такого метода реализации thiscall, необходимо отказаться от аргументов передаваемых по значению с динамическим размером содержимого. Например текстовых строк. Иначе вызывается функция копирования динамического содержимого перед сохранением регистра ecx, что искажает данные в регистре. В место строки, можно использовать указатель на нее, либо передавать строку по ссылке.

Описание функций класса (взято из интернета).

Первой для каждого вывода вызывается функция isdigital. Она должна вернуть «1», если вывод с данным именем является цифровым, и «0», если нет. У нас нет других выводов, кроме аналоговых, поэтому мы всегда возвращаем «1».
Далее, вызывается функция setup. Ей передаются интерфейсы объектов IINSTANCE – интерфейс экземпляра, и IDSIMCKT – интерфейс схемы. В этой функции мы должны проинициализировать внутренние переменные, а также сохранить на будущее ссылки на IINSTANCE и IDSIMCKT. Также, в этой функции чаще всего сохраняются адреса интерфейсов выводов (через вызов функции inst->getdsimpin).
Функция runctl вызывается при смене режима симуляции – например, когда мы приостанавливаем процесс симуляции. Это удобно для того, чтобы остановить внутренние таймеры или проделать другие необходимые действия.
Функция actuate вызывается, если у нас приводятся в действие актюаторы – стандартные элементы управления компонентом. Они бывают двух видов – «больше/меньше» и «нажато/отпущено».
Функция indicate вызывается каждый раз при перерисовке экрана. Если мы возвращаем значение FALSE, то эта функция больше не будет вызываться. Позже я покажу, как можно изменять внешний вид компонента во время работы.
Функция simulate вызывается при каждом изменении состояния выводов. Небольшое замечание – она вызывается также когда мы изменяем состояние собственного вывода, так что необходимо точно устанавливать, произошло ли какое-либо входное событие и какое именно.
Функция callback вызывается при наступлении определенного времени, которое задается вызовом IDSIMCKT::setcallback. Её удобно использовать для тактовых генераторов.

Объект создает процедура ModelNEW() и делает она это довольно простым способом (учитывая что PureBasic не поддерживает ООП). Выделяется объем памяти под структуру типа ClassParam равный размеру структуры и если при этом не произошло ошибок, то далее в переменную vt указателя *this на структуру ClassParam записывается указатель на метку ClassFunct находящуюся в ДатаСекции (DataSection). После этого, получаем экземпляр объекта IDSIMCKT, совместимый с аналогичным в VS C++. Указатель на этот экземпляр возвращается процедурой.

В принципе это полностью рабочий код и если его скомпилировать сохранив под именем TestModel.dll и поместить в папку MODELS протеуса, то он будет работать, правда, его работа не будет заметна, поскольку он ничего не выполняет (пустой минимально необходимый код). Его можно использовать как заготовку для моделей.

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

Код:
XIncludeFile "vsm.pbi"

Structure ClassParam
  *vt ;pointer to virtual table as first entry 
  
  ; Приватные переменные класса.
  *inst.IINSTANCE
  *ckt.IDSIMCKT
  *Pin1.IDSIMPIN
  *Pin2.IDSIMPIN
  *Pin3.IDSIMPIN
  *Pin4.IDSIMPIN
EndStructure 

EnableExplicit
EnableASM

Declare ModelNEW()

ProcedureCDLL createdsimmodel(device.s, *ils.ILICENCESERVER)
  Protected *Result=0
  
  If *ils\Authorize($80808081)
    *Result=ModelNEW()
  EndIf
   
  ProcedureReturn *Result
EndProcedure


ProcedureCDLL deletedsimmodel(*model.IDSIMMODEL)
  If *model
    ;ClearStructure(*model, ClassParam)
    FreeMemory(*model) 
  EndIf
EndProcedure


Procedure isdigital(*PinName)
  Protected *This.ClassParam
  mov *This, ecx
  
  ProcedureReturn 1 ; В компоненте все выводы цифровые.
EndProcedure

; Далее, вызывается функция setup.
; Ей передаются интерфейсы объектов IINSTANCE – интерфейс экземпляра,
; и IDSIMCKT – интерфейс схемы. 
; В этой функции мы должны проинициализировать внутренние переменные, 
; а также сохранить на будущее ссылки на IINSTANCE и IDSIMCKT.
; Также, в этой функции чаще всего сохраняются адреса интерфейсов выводов
; (через вызов функции inst->getdsimpin).
Procedure setup(*instance.IINSTANCE, *dsimckt.IDSIMCKT)
  Protected *This.ClassParam
  mov *This, ecx
  
  *This\inst = *instance ; Сохраняем указатели на объекты.
  *This\ckt  = *dsimckt
  
  ; Сохраняем указатели на объекты выводов модели.
  *This\Pin1 = *instance\getdsimpin("In", #True)
  *This\Pin2 = *instance\getdsimpin("Out", #True)
  *This\Pin3 = *instance\getdsimpin("pinx", #True)
  *This\Pin4 = *instance\getdsimpin("G", #True)
  
  ; Запускаем таймер. Через полсекунды будет вызнана процедура setcallback() с кодом $20.
  *This\ckt\setcallback(500000000000, *This, $20)
  
EndProcedure

; Функция runctl вызывается при смене режима симуляции – например,
; когда мы приостанавливаем процесс симуляции.
; Это удобно для того, чтобы остановить внутренние таймеры
; или проделать другие необходимые действия.
Procedure runctrl(mode.RUNMODES)
  Protected *This.ClassParam
  mov *This, ecx
EndProcedure

; Функция actuate вызывается, если у нас приводятся в действие актюаторы – стандартные
; элементы управления компонентом. Они бывают двух видов – «больше/меньше» и «нажато/отпущено». 
Procedure actuate(time.REALTIME, newstate.ACTIVESTATE)
  Protected *This.ClassParam
  mov *This, ecx
EndProcedure

; Функция indicate вызывается каждый раз при перерисовке экрана.
; Если мы возвращаем значение FALSE, то эта функция больше не будет вызываться.
Procedure indicate(time.REALTIME,  *aData.ACTIVEDATA)
  Protected *This.ClassParam
  mov *This, ecx
  
  ProcedureReturn #True
EndProcedure

; Функция simulate вызывается при каждом изменении состояния выводов.
; Небольшое замечание – она вызывается также когда мы изменяем состояние собственного вывода,
; так что необходимо точно устанавливать, произошло ли какое-либо входное событие и какое именно.
Procedure simulate(time.ABSTIME, mode.DSIMMODES)
  Protected *This.ClassParam
  mov *This, ecx
  
  ; Считываем логическое состояние входа "In" и устанавливаем противоположное на выходе "Out".
  If ishigh(*This\Pin1\istate())
    *This\Pin2\setstate2(time, 1, #SLO)
  Else
    *This\Pin2\setstate2(time, 1, #SHI)
  EndIf
  
EndProcedure

; Функция callback вызывается при наступлении определенного времени,
; которое задается вызовом IDSIMCKT::setcallback. Её удобно использовать для тактовых генераторов.
Procedure callback(time.ABSTIME, eventid.EVENTID)
  Protected *This.ClassParam
  mov *This, ecx
  
  If eventid = $20
    
    ; Считываем логическое состояние выхода "G" и устанавливаем противоположное на этом выходе,
    ; т. е. инвертируем логическое состояние выхода "G".
    If ishigh(*This\Pin4\istate()) 
      *This\Pin4\setstate2(time, 1, #SLO)
    Else
      *This\Pin4\setstate2(time, 1, #SHI)
    EndIf
    
    ; Запускаем таймер. Через полсекунды будет вызнана процедура setcallback() с кодом $20.
    *This\ckt\setcallback(time + 500000000000, *This, $20)
    
  EndIf
EndProcedure

; Создание объекта.
Procedure ModelNEW()
  Protected *this.ClassParam=0
  
  *this = AllocateMemory(SizeOf(ClassParam)) 
  If *this
    ;InitializeStructure(*this, ClassParam)
    *this\vt = ?ClassFunct
  EndIf   
  
  ProcedureReturn *this 
EndProcedure

DataSection
  ClassFunct:
  Data.i @isdigital()
  Data.i @setup()
  Data.i @runctrl()
  Data.i @actuate()
  Data.i @indicate()
  Data.i @simulate()
  Data.i @callback()
EndDataSection

Обратите внимание что в процедуре deletedsimmodel() есть закомментированная строка ClearStructure(*model, ClassParam), а в процедуре ModelNEW() строка InitializeStructure(*this, ClassParam). В данном коде они не нужны (хотя если раскомментировать, то это мало что изменит в данном случае, разве что немного увеличится размер исполняемого файла). Их следует раскомментировать в том случае, если в структуре ClassParam будет находится динамическое содержимое, такое как строки, динамические и ассоциативные массивы, связные списки и т. д.

Теперь рассмотрим как работает программа.
Когда протеус вызывает процедуру setup(), (если не забыли она является составляющей класса типа IDSIMMODEL, указатель на экземпляр которого был передан протеусу при создании модели, т. е. при вызове экспортируемой функции createdsimmodel()) то в ней в первую очередь сохраняются в структуре (в приватных переменных класса IDSIMMODEL) указатели на экземпляры классов (интерфейсов) *instance и *dsimckt. Они понадобятся в дальнейшем.
Затем происходит сопоставление выводов модели. Для этого производится обращение в выводу по его имени (помните как указывали имена выводов при создании графической части модели?) и сохраняется указатель на экземпляр класса (интерфейса) в переменных структуры.
После чего запускается таймер, который сработает через полсекунды. Учтите, время задается в пикосекундах. Когда пройдут эти полсекунды, то протеус вызовет процедуру callback(), в которой анализируется текущее состояние выхода Pin4 (его имя "G") и оно инвертируется. После чего устанавливается таймер опять на полсекунды. В итоге получается генератор.
Функция simulate() вызывается протеусом когда изменилось состояние выводов модели. В ней анализируется состояние входа Pin1 (имя "In") и устанавливается противоположный логический уровень на выходе Pin2 (имя "Out"). Так получается инвертирующий элемент.

Как создать DLL из кода, написано в этой статье. http://pure-basic.narod.ru/docs/dll.html
Для компиляции нужна полная версия (не демо) PureBasic не ниже 5.10 (использовал 5.11) для Windows x86.
При компиляции получаем довольно компактный DLL-файл, с размером немного больше 4 КБ.
После того как DLL создана, ее необходимо поместить в папку MODELS протеуса.

Если все сделано правильно, то модель уже находится в библиотеке протеуса.

http://i016.radikal.ru/1404/37/50b692e391b4t.jpg

Ее можно добавить в проект и вместе с ней пару светодиодов и соединить выход генератора модели с ее входом инвертирующего элемента
В итоге получится такой проект (показан во время симуляции).

http://s43.radikal.ru/i101/1404/ed/f96726d99fde.png

На скриншоте не видно, но во время симуляции, модели светодиодов попеременно зажигаются и гаснут.

Скачать файлы.

В папке Proteus находится пример использования и библиотека, расположенная в папках LIBRARY и MODELS. Для установки модели, эти папки (LIBRARY и MODELS) нужно скопировать в папку с протеусом.
В папке Src находятся исходники DLL-библиотеки и графической части модели.

Модель создавалась и тестировалась в Proteus 7.8 SP2. С другими версиями, работоспособность не проверялась.


Продолжение статьи - модель 1-Wire анализатора для Proteus.
Модель цифрового светодиода.