Активная модель, в отличие от рассмотренных в предыдущих статьях (Разработка компонента для протеуса и Создаем модель 1-Wire анализатора для Proteus), может работать с экраном, мышкой и клавиатурой. Например, активными моделями являются различные индикаторы (светодиоды, самисегментые, ЖК и т. д.).

Для примера, создадим цифровую модель RGB светодиода. Для этого в новом проекте поместим прямоугольник в рабочую область. Нарисуем кружок в центре этого прямоугольника и добавим выводы с именами R, G, B и GND. Скрипт модели, должен быть таким:

Код:
{*DEVICE}
NAME=led3color
{PREFIX=LED}
{ACTIVE=led3color,1,DLL}
{*PROPDEFS}
{MODDLL="VSM Model DLL",READONLY STRING}
{PRIMITIVE="Primitive Type",HIDDEN STRING}
{*COMPONENT}
{MODDLL=Led_3Color.dll}
{PRIMITIVE=DIGITAL,Led_3Color}

После этого нужно скомпилировать модель (в меню "Library" кликнуть по пункту "Make Device") и она станет доступной в библиотеке протеуса.
Подробнее о создании графической части модели можно прочитать в Разработка компонента для протеуса.
Получилась такая модель:

http://i056.radikal.ru/1404/e0/6f55b684a7d8.png

Теперь приступим к программной части модели. Код активной модели немного отличается от ранее рассмотренных, тем что интерфейс состоит из двух интерфейсов - IACTIVEMODEL и IDSIMMODEL. Первый отвечает за активную часть модели (графика и ввод), а второй, за цифровую часть (обмен данными через выводы модели). В этом случае так же, экспортируемые из DLL процедуры createdsimmodel() и deletedsimmodel() заменены на createactivemodel() и deleteactivemodel().
Код DLL.

Код:
XIncludeFile "vsm.pbi"

Macro mClassPrivate
  
  ; Приватные перемнные класса IACTIVEMODEL.
  *component.ICOMPONENT
  *instance.IINSTANCE
  
  ; Приватные переменные класса IDSIMMODEL.
  *inst.IINSTANCE
  *ckt.IDSIMCKT
  
  *PinR.IDSIMPIN
  *PinG.IDSIMPIN
  *PinB.IDSIMPIN
  *PinGND.IDSIMPIN
  
  ; Остальные приватные переменные.
  Color.l
  new_flag.b 
  
EndMacro

Structure Class_IDSIM
  *vt[1] ;pointer to virtual table as first entry.
  
  mClassPrivate ; Приватные перемнные.
EndStructure 

Structure Class_IACTIVE
  *vt[2] ;pointer to virtual table as first entry.
  
  mClassPrivate ; Приватные перемнные.
EndStructure 

EnableExplicit
EnableASM

Declare ModelNEW()
Declare animate(element.i, *newstate.ACTIVEDATA)

ProcedureCDLL.i createactivemodel(device.s, *ils.ILICENCESERVER)
  Protected *Result=0
  
  If *ils\Authorize($80808081)
    *Result=ModelNEW() ; Создание объекта.
  EndIf
  
  ProcedureReturn *Result
EndProcedure

ProcedureCDLL deleteactivemodel(*model.Class_IACTIVE)
  If *model
    ;ClearStructure(*model, Class_IACTIVE)
    FreeMemory(*model) ; Освобождение памяти, занимаемой объектом.
  EndIf
EndProcedure

Procedure ModelNEW()   ; Создание объекта.
  Protected *Class.Class_IACTIVE=0
  
  *Class = AllocateMemory(SizeOf(Class_IACTIVE)) 
  If *Class
    ;InitializeStructure(*Class, Class_IACTIVE)
    *Class\vt[0] = ?IACTIVE_Funct ; Функции класса IACTIVEMODEL.
    *Class\vt[1] = ?IDS_Funct     ; Функции класса IDSIMMODEL.
  EndIf   
  
  ProcedureReturn *Class 
EndProcedure

;- Процедуры класса 
;{

;- IACTIVEMODEL.

; Инициализация модели.
; В качестве параметра передается указатель на интерфейс IComponent.
Procedure initialize(*cpt.ICOMPONENT)
  Protected *This.Class_IACTIVE, s.s, c.l, i
  MOV *This, ecx
  
  *This\component = *cpt
  
  *This\new_flag = #True
  *This\Color = 0
 
EndProcedure

; ISIS вызывает эту функцию, когда создается аналоговая модель.
; Имя модели передается в качестве параметра.
; Например, если мы присвоили в сценарии параметра "PRIMITIVE=.ANALOG.ammeter",
; будет передано в функцию слово "AMMETER".
Procedure.i getspicemodel(*sPrimitive)
  Protected *This.Class_IACTIVE
  MOV *This, ecx
  
  ProcedureReturn 0 ; Это не аналоговая модель.
EndProcedure
  
; Функция будет вызвана, когда ISIS создает цифровую модель,
; которая соответствует активной модели.
; Напрмиер, если в сценарии будет "PRIMITIVE=.DIGITAL.display",
; эта функция будет вызвана с параметром "DISPLAY".
Procedure.i getdsimmodel(*sPrimitive)
  Protected *This.Class_IACTIVE
  MOV *This, ecx
  
  ProcedureReturn *This+SizeOf(Integer) ; Ссылка на объект IDSIMMODEL.
EndProcedure

; Эта функция вызывается, при копировании или масштабировании компонента (модели).
; Это отличается от функции animate() с точки зрения того факта,
; что необходимо, полностью перерисовать компонент.
; Кроме того, в качестве параметра текущее состояние передается функции.
; Минимальная реализация функции такова: *This\component\drawstate(state).
Procedure Plot_IACTIVE(state.ACTIVESTATE)
  Protected *This.Class_IACTIVE
  MOV *This, ecx
  
  *This\component\drawstate(state)

  MOV ecx, *This ; Thiscall вызов.
  animate(0, 0)  ; Вызов функции перерисовки содержимого модели.
EndProcedure

; Функция вызывается для анализа события, генерируется внутри электрического аналога,
; (интерфейс ISPICEMODEL или IDSIMMODEL) для соответствующей активной модели.
; Активные события создаются ядром PROSPICE в результате
; извлечения значения функций IDSIMMODEL\indicate(),
; которая вызывается при каждом копировании экрана.
; Это обеспечивает общий механизм, с помощью которого
; электрический аналог может взаимодействовать с графикой частью модели.
; Зачастую используется этот механизм, если вы планируется использовать
; примитивы RTVPROBE, RTIPROBE или RTDPROBE для того, чтобы принять простое
; измерения, которые вы затем использовать в графической модели.
; Параметер "element" - элемент графической модели, для которой генерируется событие.
; Эта дает возможность использовать несколько примитивов RTxPROBE,
; которые расположены в модели для того, чтобы проводить измерения одной моделью.
; Параметр *newstate содержит данные о событии.
Procedure animate(element.i, *newstate.ACTIVEDATA)
  Protected *This.Class_IACTIVE
  MOV *This, ecx
  
  If *This\new_flag = #True ; Перерисока лишь если это необходимо.
    *This\new_flag = #False
    *This\component\setbrushcolour(*This\Color)  ; Цвет заливки.
    *This\component\drawcircle(500, -500, 300)   ; Рисуем круг.
  EndIf
  
EndProcedure

; Эта функция вызывается при нажатии кнопок клавиатуры,
; а так же при перемещении или нажатии кнопок мышки.
; Функция вызывается только когда указатель мыши он находится над компонентом (моделью).
; Если функция возвращает значение #True, это означает, что она "захватывает" фокус
; и получает поток событий клавиатуры и мыши, пока не вернет #False.
; Параметры функци: key - код виртуальной клавиши Windows,
; х и у - координаты указателя мыши относительно левого верхнего угла модели,
; flags - флаги нажатых кнопок мыши: 1 - левая, 2 - правая.
Procedure.b actuate_IACTIVE(key.w, x.i, y.i, flags.l)
  Protected *This.Class_IACTIVE
  MOV *This, ecx
  
  ProcedureReturn #False
EndProcedure


;- IDSIMMODEL.

; Функция isdigital.
; Она должна вернуть «1», если вывод с данным именем является цифровым, и «0», если нет.
; У нас нет других выводов, кроме аналоговых, поэтому мы всегда возвращаем «1».
Procedure isdigital(*PinName)
  Protected *This.Class_IDSIM
  MOV *This, ecx
  
  ProcedureReturn 1 ; В компоненте все выводы цифровые.
EndProcedure

; Функция setup.
; Ей передаются интерфейсы объектов IINSTANCE – интерфейс экземпляра,
; и IDSIMCKT – интерфейс схемы. 
; В этой функции мы должны проинициализировать внутренние переменные, 
; а также сохранить на будущее ссылки на IINSTANCE и IDSIMCKT.
; Также, в этой функции чаще всего сохраняются адреса интерфейсов выводов
; (через вызов функции *This\inst\getdsimpin).
Procedure setup(*instance.IINSTANCE, *dsimckt.IDSIMCKT)
  Protected *This.Class_IDSIM
  MOV *This, ecx
  
  *This\inst = *instance ; Сохраняем указатели на объекты.
  *This\ckt  = *dsimckt
  
  ; Сопоставляем выводы модели с интерфейсами.
  *This\PinR = *This\inst\getdsimpin("R", #True)
  *This\PinG = *This\inst\getdsimpin("G", #True)
  *This\PinB = *This\inst\getdsimpin("B", #True)
  *This\PinGND = *This\inst\getdsimpin("GND", #True)
  
EndProcedure

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

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

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

; Функция simulate вызывается при каждом изменении состояния выводов.
; Небольшое замечание – она вызывается также когда мы изменяем состояние собственного вывода,
; так что необходимо точно устанавливать, произошло ли какое-либо входное событие и какое именно.
Procedure simulate(time.ABSTIME, mode.DSIMMODES)
  Protected *This.Class_IDSIM
  MOV *This, ecx
  
  ; Формируем цвет в зависимости от логических уровней на входах модели.
  *This\Color = RGB(ishigh(*This\PinR\istate())*255,
                    ishigh(*This\PinG\istate())*255, 
                    ishigh(*This\PinB\istate())*255)
  *This\new_flag = #True
  
EndProcedure

; Функция callback вызывается при наступлении определенного времени,
; которое задается вызовом IDSIMCKT::setcallback. Её удобно использовать для тактовых генераторов.
Procedure callback(time.ABSTIME, eventid.EVENTID)
  Protected *This.Class_IDSIM
  MOV *This, ecx
  
EndProcedure


DataSection
  
  IACTIVE_Funct:
  Data.i @initialize()
  Data.i @getspicemodel()
  Data.i @getdsimmodel()
  Data.i @Plot_IACTIVE()
  Data.i @animate()
  Data.i @actuate_IACTIVE()
  IDS_Funct:
  Data.i @isdigital()
  Data.i @setup()
  Data.i @runctrl()
  Data.i @actuate_IDS()
  Data.i @indicate()
  Data.i @simulate()
  Data.i @callback()
  
EndDataSection

;}

Если внимательно посмотреть, то видны уже знакомые по предыдущим статьям процедуры ,такие как isdigital(), setup() и т. д. Они образуют класс IDSIMMODEL.
В код так же добавлены новые, такие как initialize(), getspicemodel() и т. д. Они образуют класс IACTIVEMODEL.
В функции initialize() производится инициализация модели. В ней нужно сохранить указатель на объект ICOMPONENT, который в дальнейшем понадобится.
В функции getspicemodel() возвращается 0 в место указателя на объект ISPICEMODEL, поскольку аналоговая часть в этой модели не реализована. Модель цифровая, поэтому в следующей на ней процедуре getdsimmodel() возвращается указатель на объект IDSIMMODEL.
Функция Plot_IACTIVE() (на самом деле ее имя Plot, но поскольку в PureBasic есть функция с аналогичным именем, эту пришлось переименовать) вызывается протеусом при перерисовке экрана, например, при изменении масштаба и в ней должна производится перерисовка модели.
Функция animate() вызывается протеусом при необходимости перерисовки содержимого модели. В ней и рисуется активная часть светодиода. Для этого указывается цвет свечения и рисуется круг в центре модели с этим цветом. Код цвета задается в функции simulate(), вызываемой при изменении состояния выводов модели. В ней анализируется логичекий уровень каждого вывода и на основе этого создается соответствующий код цвета.

Скомпилируйте код в DLL и сохраните с именем Led_3Color.dll. После чего скопируйте в папку MODELS протеуса и если все сделано правильно, то модель будет доступна в библиотеке и ее можно будет использовать в проектах.

Модель во время симуляции.

http://s019.radikal.ru/i607/1404/ac/9ea3d8fd5bbe.png

На входах R и B установлены высокие логические уровни, а при смешивании красного и синего цвета получается розовый, что видим на скриншоте.

Но эта модель может отобразить только 8 цветов, в зависимости от логических уровней на входах R, G и B.
Чтобы расширить поддерживаемый цветовой диапазон, модель была доработана, таким образом, чтобы цвет зависел не только от наличия или отсутствия логических уровней на входах, но и от скважности импульсов (подразумевается что на входы модели светодиода поступает импульсное напряжение). Тем более что многие микроконтроллеры аппаратно могут изменять скважность импульсов, используя технологию ШИМ - широтно-импульсная модуляция. По английски это звучит как pulse-width modulation (PWM).

Доработка кода модели, для поддержки ШИМ (PWM)

Прежде всего определимся что такое ШИМ (PWM) и как оно работает. Принцип работы по сути прост. При неизменной частоте, изменяется скважность - отношение длительности положительного полупериода импульса к отрицательному.
Значит для того чтобы модель определила скважность, необходимо измерить длительность положительного полупериода и отрицательного, после чего сравнить их и вычислить длительность которого больше и насколько. На основании этих данных, отобразить соответствующий цвет. На словах вроде все просто, но как реализовать, особенно точный подсчет времени? Протеус предоставляет все необходимое для этого. Во первых, информирует о любых изменениях состояния выводов модели, а во вторых, предоставляет счетчик времени с разрешающей способностью до единиц пикосекунд! :O Но вряд ли современные компьютеры (о суперкомпьютерах речь не идет) могут дать подобную точность при симуляции в режиме реального времени. В лучшем случае с точность будет до единиц наносекунд. Но в нашем случае, достаточна точность на порядок хуже. :)

Рассмотрим доработанный код (файл dLed_PWM.pb в папке Src\DLL).

Код:
XIncludeFile "vsm.pbi"

Structure PinParam
  *Pin.IDSIMPIN
  OldState.b
  OldTime.ABSTIME[2]
  Color.a
EndStructure

Macro mClassPrivate
  ; Приватные перемнные класса IACTIVEMODEL.
  *component.ICOMPONENT
  *instance.IINSTANCE
  
  ; Приватные переменные класса IDSIMMODEL.
  *inst.IINSTANCE
  *ckt.IDSIMCKT
  
  PinR.PinParam
  PinG.PinParam
  PinB.PinParam
  PinGND.PinParam
  
  ; Остальные приватные переменные.
  Color.l
  new_flag.b 
  
EndMacro

Structure Class_IDSIM
  *vt_IDSIM ;pointer to virtual table as first entry.
  
  mClassPrivate ; Приватные переменные.
EndStructure 

Structure Class_IACTIVE
  *vt_IACTIVE ;pointer to virtual table as first entry.
  *vt_IDSIM
  
  mClassPrivate ; Приватные переменные.
EndStructure 

EnableExplicit
EnableASM

Declare ModelNEW()
Declare animate(element.i, *newstate.ACTIVEDATA)

ProcedureCDLL.i createactivemodel(device.s, *ils.ILICENCESERVER)
  Protected *Result=0
  
  If *ils\Authorize($80808081)
    *Result=ModelNEW() ; Создание объекта.
  EndIf
  
  ProcedureReturn *Result
EndProcedure

ProcedureCDLL deleteactivemodel(*model.Class_IACTIVE)
  If *model
    ;ClearStructure(*model, Class_IACTIVE)
    FreeMemory(*model) ; Совобождение памяти, занимаемой объектом.
  EndIf
EndProcedure

Procedure ModelNEW()   ; Создание объекта.
  Protected *Class.Class_IACTIVE=0
  
  *Class = AllocateMemory(SizeOf(Class_IACTIVE)) 
  If *Class
    ;InitializeStructure(*Class, Class_IACTIVE)
    *Class\vt_IACTIVE = ?IACTIVE_Funct ; Функции класса IACTIVEMODEL.
    *Class\vt_IDSIM   = ?IDS_Funct     ; Функции класса IDSIMMODEL.
  EndIf   
  
  ProcedureReturn *Class 
EndProcedure

Procedure PinColor(*This.Class_IDSIM, *Pin.PinParam, time.ABSTIME)
  Protected Temp.f
  Protected st.b = ishigh(*Pin\Pin\istate())
  
  ; Если сюда попали в первый раз, то запоминаем.
  ; состояние пина и текущее время, затем выходим.
  If *Pin\OldTime[0] = 0
    *Pin\OldState = st
    *Pin\OldTime[0] = time
    *Pin\OldTime[1] = time
    ProcedureReturn
  EndIf
    
  ; Состояние пина не менялось больше 100 миллисекунд - сброс по таймауту.
  If (time - *Pin\OldTime[0]) / 100000000000 > 0
    If st
      *Pin\Color=255
    Else
      *Pin\Color=0
    EndIf
    *Pin\OldTime[0]=time
    *Pin\OldTime[1]=time
    *Pin\OldState = st
    ProcedureReturn
  EndIf
  
  ; Если текущее состояние пина равно предыдущему, то выходим.
  If *Pin\OldState = st
    ProcedureReturn
  EndIf
    
  If st=0 ; Спад импульса.
    If *Pin\OldTime[0] = *Pin\OldTime[1] ; Это начало измрения.
      *Pin\OldTime[0]=time
    Else ; Длительность полуериодов измерена.
      
      ; Вычисление скажности и перевод в диапазон 0...255.
      Temp = 255/(((Time-*Pin\OldTime[0]) / (Time-*Pin\OldTime[1])))
      If Temp>255
        *Pin\Color = 255
      ElseIf Temp<0
        *Pin\Color = 0
      Else
        *Pin\Color = Int(Temp)
      EndIf
      
      *Pin\OldTime[0]=time ; Запоминает текущее время.
      *Pin\OldTime[1]=time
    EndIf
    
  Else ; Фронт импульса
    *Pin\OldTime[1]=time ; Запоминаем вемя отрицательного полупериода.
  EndIf
  
  *Pin\OldState = st
EndProcedure

;- Процедуры класса 
;{

;- IACTIVEMODEL.

; Инициализация модели.
; В качестве параметра передается указатель на интерфейс IComponent.
Procedure initialize(*cpt.ICOMPONENT)
  Protected *This.Class_IACTIVE, s.s, c.l, i
  MOV *This, ecx
  
  *This\component = *cpt
  *This\new_flag = #True
  *This\Color = $7F7F7F
  
EndProcedure

; ISIS вызывает эту функцию, когда создается аналоговая модель.
; Имя модели передается в качестве параметра.
; Например, если мы присвоили в сценарии параметра "PRIMITIVE=.ANALOG.ammeter",
; будет передано в функцию слово "AMMETER".
Procedure.i getspicemodel(*sPrimitive)
  Protected *This.Class_IACTIVE
  MOV *This, ecx
  
  ProcedureReturn 0 ; Это не аналоговая модель.
EndProcedure

; Функция будет вызвана, когда ISIS создает цифровую модель,
; которая соответствует активной модели.
; Напрмиер, если в сценарии будет "PRIMITIVE=.DIGITAL.display",
; эта функция будет вызвана с параметром "DISPLAY".
Procedure.i getdsimmodel(*sPrimitive)
  Protected *This.Class_IACTIVE
  MOV *This, ecx
  
  ProcedureReturn *This+OffsetOf(Class_IACTIVE\vt_IDSIM);SizeOf(Integer) ; Ссылка на объект IDSIMMODEL.
EndProcedure

; Эта функция вызывается, при копировании или масштабировании компонента (модели).
; Это отличается от функции animate() с точки зрения того факта,
; что необходимо, полностью перерисовать компонент.
; Кроме того, в качестве параметра текущее состояние передается функции.
; Минимальная реализация функции такова: *This\component\drawstate(state).
Procedure Plot_IACTIVE(state.ACTIVESTATE)
  Protected *This.Class_IACTIVE
  MOV *This, ecx
  
  *This\component\drawstate(state)
  *This\new_flag = #True
  
  MOV ecx, *This ; Thiscall вызов.
  animate(0, 0)  ; Вызов функции перерисовки содержимого модели.
  
EndProcedure

; Функция вызывается для анализа события, генерируется внутри электрического аналога,
; (интерфейс ISPICEMODEL или IDSIMMODEL) для соответствующей активной модели.
; Активные события создаются ядром PROSPICE в результате
; извлечения значения функций IDSIMMODEL\indicate(),
; которая вызывается при каждом копировании экрана.
; Это обеспечивает общий механизм, с помощью которого
; электрический аналог может взаимодействовать с графикой частью модели.
; Зачастую используется этот механизм, если вы планируется использовать
; примитивы RTVPROBE, RTIPROBE или RTDPROBE для того, чтобы принять простое
; измерения, которые вы затем использовать в графической модели.
; Параметер "element" - элемент графической модели, для которой генерируется событие.
; Эта дает возможность использовать несколько примитивов RTxPROBE,
; которые расположены в модели для того, чтобы проводить измерения одной моделью.
; Параметр *newstate содержит данные о событии.
Procedure animate(element.i, *newstate.ACTIVEDATA)
  Protected *This.Class_IACTIVE
  MOV *This, ecx
  
  If *This\new_flag = #True ; Перерисока лишь если это необходимо.
    *This\new_flag = #False
    *This\component\setbrushcolour(*This\Color)  ; Цвет заливки.
    *This\component\drawcircle(500, -500, 300)   ; Рисуем круг.
  EndIf
  
EndProcedure

; Эта функция вызывается при нажатии кнопок клавиатуры,
; а так же при перемещении или нажатии кнопок мышки.
; Функция вызывается только когда указатель мыши он находится над компонентом (моделью).
; Если функция возвращает значение #True, это означает, что она "захватывает" фокус
; и получает поток событий клавиатуры и мыши, пока не вернет #False.
; Параметры функци: key - код виртуальной клавиши Windows,
; х и у - координаты указателя мыши относительно левого верхнего угла модели,
; flags - флаги нажатых кнопок мыши: 1 - левая, 2 - правая.
Procedure.b actuate_IACTIVE(key.w, x.i, y.i, flags.l)
  Protected *This.Class_IACTIVE
  MOV *This, ecx
  
  ProcedureReturn #False
EndProcedure


;- IDSIMMODEL.

; Функция isdigital.
; Она должна вернуть «1», если вывод с данным именем является цифровым, и «0», если нет.
; У нас нет других выводов, кроме аналоговых, поэтому мы всегда возвращаем «1».
Procedure isdigital(*PinName)
  Protected *This.Class_IDSIM
  MOV *This, ecx
  
  ProcedureReturn 1 ; В компоненте все выводы цифровые.
EndProcedure

; Функция setup.
; Ей передаются интерфейсы объектов IINSTANCE – интерфейс экземпляра,
; и IDSIMCKT – интерфейс схемы. 
; В этой функции мы должны проинициализировать внутренние переменные, 
; а также сохранить на будущее ссылки на IINSTANCE и IDSIMCKT.
; Также, в этой функции чаще всего сохраняются адреса интерфейсов выводов
; (через вызов функции *This\inst\getdsimpin).
Procedure setup(*instance.IINSTANCE, *dsimckt.IDSIMCKT)
  Protected *This.Class_IDSIM
  MOV *This, ecx
  
  *This\inst = *instance ; Сохраняем указатели на объекты.
  *This\ckt  = *dsimckt
  
  ; Сопоставляем выводы модели с интерфейсами.
  *This\PinR\Pin = *This\inst\getdsimpin("R", #True)
  *This\PinG\Pin = *This\inst\getdsimpin("G", #True)
  *This\PinB\Pin = *This\inst\getdsimpin("B", #True)
  *This\PinGND\Pin = *This\inst\getdsimpin("GND", #True)
  
  *This\ckt\setcallback(20000000000, *This, $20)
EndProcedure

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

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

; Функция indicate вызывается каждый раз при перерисовке экрана.
; Если мы возвращаем значение FALSE, то эта функция больше не будет вызываться.
Procedure indicate(time.REALTIME,  *aData.ACTIVEDATA)
  Protected *This.Class_IDSIM
  MOV *This, ecx
  
  If *This\new_flag = #True
    *aData\Type = #ADT_DSIMDATA
    *aData\dsimdata\numpins=4
  EndIf
  
  ProcedureReturn #True
EndProcedure

; Функция simulate вызывается при каждом изменении состояния выводов.
; Небольшое замечание – она вызывается также когда мы изменяем состояние собственного вывода,
; так что необходимо точно устанавливать, произошло ли какое-либо входное событие и какое именно.
Procedure simulate(time.ABSTIME, mode.DSIMMODES)
  Protected *This.Class_IDSIM
  MOV *This, ecx
  
  PinColor(*This, *This\PinR, time)
  PinColor(*This, *This\PinG, time)
  PinColor(*This, *This\PinB, time)
  
  *This\Color = RGB(*This\PinR\Color,
                    *This\PinG\Color, 
                    *This\PinB\Color)
  
  *This\new_flag = #True
EndProcedure

; Функция callback вызывается при наступлении определенного времени,
; которое задается вызовом *This\ckt\setcallback(). Её удобно использовать для тактовых генераторов.
Procedure callback(time.ABSTIME, eventid.EVENTID)
  Protected *This.Class_IDSIM, *x
  MOV *This, ecx
  
  If eventid=$20
    PinColor(*This, *This\PinR, time)
    PinColor(*This, *This\PinG, time)
    PinColor(*This, *This\PinB, time)
    
    *This\Color = RGB(*This\PinR\Color,
                      *This\PinG\Color, 
                      *This\PinB\Color)
    *This\new_flag = #True
    
    ; Запускаем таймер. Через 0.2 секунды будет вызнана процедура setcallback() с кодом $20.
    *This\ckt\setcallback(time+200000000000, *This, $20)
  EndIf
EndProcedure

DataSection
  IACTIVE_Funct:
  Data.i @initialize()
  Data.i @getspicemodel()
  Data.i @getdsimmodel()
  Data.i @Plot_IACTIVE()
  Data.i @animate()
  Data.i @actuate_IACTIVE()
  IDS_Funct:
  Data.i @isdigital()
  Data.i @setup()
  Data.i @runctrl()
  Data.i @actuate_IDS()
  Data.i @indicate()
  Data.i @simulate()
  Data.i @callback()
EndDataSection

;}

Изменения в основном коснулись кода процедур simulate() и callback(). В них, перед расчетом отображаемого цвета, вызывается три раза процедура PinColor() для каждого входа в отдельности. Именно в ней измеряется скважность импульсов и вычисляется цвет. В начале процедуры PinColor(), используя макрос ishigh() из файла vsm.pbi, определяется установлен ли высокий логический уровень на входе (какой именно это вход, зависит от того, указатель на какую структуру был передан процедуре во втором аргументе *Pin). Затем выясняется первый ли это вызов процедуры для данной структуры и если первый, то запоминается текущее состояние входа и текущее время. Если же это не первый вызов, то проверяется не превышен ли таймаут 100 миллисекунд по которому определяется что это не ШИМ, а обычные логические уровни. Необходимо для того чтобы изменять цвет можно было не только методом ШИМ, но и как в предыдущем коде, путем изменения логических уровней. Т. е. сделано для универсальности. Но это так же накладывает ограничения на минимальную частоту ШИМ, которая должна быть не ниже 10 Гц.
Если таймаут не превышен, то сравнивается текущий логический уровень на входе, с предыдущим и если они равны, то производится выход из процедуры. А вот если не равны, то определяется фронт это (на входе логическая единица) или спад (на входе логический ноль). Логика измерения времени такова: По спаду (переход от логической единицы к логическому нулю) сохраняется текущее время от начала симуляции (если не забыли, разрешающая способность до единиц пикосекунд). По фронту (переход от логического нуля к единице), так же сохраняется время. При следующем спаде импульса, вычисляется длительность положительного полупериода (когда была логическая единица на входе) и общая длительность импульса. На основании этих данных, вычисляется соотношение длительности положительного и отрицательного полупериода импульса, т. е. скважность. Далее скважность переводится в диапазон значений от 0 до 255 и записывается в поле Color структуры *Pin.

Есть еще одна особенность о которой стоит упомянуть, поскольку о ней почти нигде не написано. По умолчанию, графика модели обновляется несколько раз в секунду. И при динамическом изменении ШИМ видим не плавные переходы цвета, а слайд-шоу.
Чтобы протеус прорисовывал модель чаще, нужно ему об этом сообщить. Для этого в процедуре indicate() записываются данные в структуру с именем *aData типа ACTIVEDATA. В данном случае, это произвольные данные, только для того чтобы протеус перерисовал содержимое модели. Но при необходимости, через структуру можно передать необходимые данные, которые будет переданы в процедуру animate() которая в одном из аргументов принимает аналогичную структуру.

Поучилось следующее.

http://i023.radikal.ru/1404/67/786b450dfcddt.jpg

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

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

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

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