В предыдущей статье был рассмотрен процесс создания простой модели для протеуса. На сей раз усложним задачу и создадим не только демонстрационную модель, но и необходимую в протеусе.
За основу был взят проект отладчика шины 1-Wire, найденый на сайте kazus.ru.
Он был переписан на PureBasic и немного доработан.
По сравнению с оригинальной версией из kazus.ru, добавлено следующее:
1. Расшифровка обмена не только команды "Search ROM", но и "Alarm Search".
2. Отображение серийного номера, найденного командой "Search ROM", или "Alarm Search" и расшифровка типа устройства.
3. Расшифровываются так же данные команд "Read ROM" и "Read Scratchpad".
4. Проверяется соответствие контрольной суммы CRC в данных команд "Search ROM", "Alarm Search", "Read ROM", "Match ROM" и "Read Scratchpad". В случае несоответствия, выводится предупреждение об этом и отображается рассчитанное значение CRC.
5. Детектируются два типа температурных датчиков и для запроса "Read Scratchpad" отображаются подсказки по результату, такие как измеренная температура, данные в регистре конфигурации и т. д.
Как создавать графическую часть, написано здесь и повторятся не буду.
В простейшем случае, графическая часть модели 1-Wire анализатора должна состоять из прямоугольника и одного вывода, настроенного как вход, с именем 1Wire.
У меня она получилась такой.

Скрипт свойств модели.
{*DEVICE}
NAME=1-Wire Debudder
{*PROPDEFS}
{PRIMITIVE="Primitive Type",HIDDEN STRING}
{MODDLL="VSM Model DLL",HIDDEN STRING}
{*INDEX}
{CAT=Miscellaneous}
{SUBCAT=}
{MFR=}
{DESC=1-Wire Protocol Debugger}
{*COMPONENT}
{PRIMITIVE=DIGITAL}
{MODDLL=1WireDebug.dll}После этого можно выделить модель и ее скрипт и скомпилировать, тем самым добавить в библиотеку. Как это сделать, написано в предыдущей статье.
Программный код модели.
XIncludeFile "vsm.pbi"
Structure ClassParam
*vt ;pointer to virtual table as first entry
; Приватные переменные класса.
*inst.IINSTANCE
*ckt.IDSIMCKT
*Pin_1Wire.IDSIMPIN ; Вход 1-Wire.
*MyPopup.IDEBUGPOPUP
Prev_Time.q ; Предыдущее время.
Prev_St.b ; Предыдущее состояние входа 1-Wire.
imode.a ; Режим:
; 0 - ожидание RESET.
; 1 - RESET получили, ожидание PRESENCE.
; 2 - ожидание команды.
; 3 - передача бит.
; 4 - прием бит.
; 5 - поиск 1-Wire.
byte.a ; Байт, считанный с линии.
num_bit.a ; Номер бита.
kolvo.a ; Сколько байт будем передавать/принимать.
cmnd.a ; Последняя команда.
CountBit.a ; Подсчет бит при выполнении команды поиска.
DevType.a ; Тип 1-Wire устройства.
Buff.a[258] ; Буфер под данные прпинятые по 1-Wire.
EndStructure
Structure Buff
Byte.a[0]
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.a CRC8(*Buff.Buff, Count) ; Вычисление CRC.
Protected CRC.a, Byte.a, i, x
If *Buff And Count>0
Count-1
For i=0 To Count
Byte=*Buff\Byte[i]
For x=1 To 8
If (Byte ! CRC) & 1
CRC = ((CRC!$18)>>1)|$80
Else
CRC >> 1
EndIf
Byte>>1
Next x
Next i
EndIf
ProcedureReturn CRC
EndProcedure
Procedure.s SN_Text(*Buff.Buff)
Protected String.s, i
String = RSet(Hex(*Buff\Byte[0]), 2, "0")+"-"
For i=6 To 1 Step -1
String + RSet(Hex(*Buff\Byte[i]), 2, "0")
Next i
String + "-"+RSet(Hex(*Buff\Byte[7]), 2, "0")
ProcedureReturn String
EndProcedure
Procedure.s DetectDevice(Dev.a)
Protected Result.s
Select Dev
Case $10 : Result="DS1820 / DS18S20"
Case $28 : Result="DS18B20"
EndSelect
If Result<>""
Result=" "+Result
EndIf
ProcedureReturn Result
EndProcedure
; Декодирование 1-Wire команд.
Procedure OneWire_DecodeCommand(*This.ClassParam, *szString.String, delta_mks.i)
Protected crc.a
; Если ждем команду, то...
If *This\imode = 2
; Если ведущий передает 0, то увеличиваем номер бита
If delta_mks > 15 And delta_mks < 120
*This\num_bit + 1
; Если же ведущий передает 1, то устанавливаем направление передачи
; и изменяем значение байта, а также увеличиваем номер бита
ElseIf delta_mks > 1 And delta_mks <= 15
*This\byte + (1 << *This\num_bit)
*This\num_bit + 1
Else
*szString\s="Transmission error!"
*This\imode = 0;
EndIf
; Если считали байт полностью, то выводим байт в HEX-формате,
; обнуляем байт и номер бита
If *This\num_bit = 8
; Определяем команду
If *This\byte = $F0
*szString\s="> Search ROM" + #CRLF$
*This\imode = 5 ; Далее будет обмен битами.
*This\num_bit = 0
*This\CountBit = 0
*This\kolvo = 64 ; Прием 64 бит.
FillMemory(@*This\Buff[0], SizeOf(*This\Buff), 0)
ElseIf *This\byte = $EC
*szString\s="> Alarm Search"
*This\imode = 5 ; Далее будет обмен битами.
*This\num_bit = 0
*This\CountBit = 0
*This\kolvo = 64 ; Прием 64 бит.
FillMemory(@*This\Buff[0], SizeOf(*This\Buff), 0)
ElseIf *This\byte = $33
*szString\s="> Read ROM"
*This\imode = 4 ; Далее будет прием адреса.
*This\kolvo = 8 ; Прием 8 байт.
FillMemory(@*This\Buff[0], SizeOf(*This\Buff), 0)
ElseIf *This\byte = $55
*szString\s="> Match ROM"
*This\imode = 3 ; Далее будет передача адреса.
*This\kolvo = 8 ; Передача 8 байт.
FillMemory(@*This\Buff[0], SizeOf(*This\Buff), 0)
ElseIf *This\byte = $CC
*szString\s="> Skip ROM"
*This\imode = 2 ; Далее будет команда.
*This\DevType = 0
ElseIf *This\byte = $44
*szString\s="> Convert T" ; Для DS1820.
*This\imode = 4 ; Далее будет прием.
*This\kolvo = 1 ; Прием 1-го байта.
ElseIf *This\byte = $BE
*szString\s="> Read Scratchpad"+" "+DetectDevice(*This\DevType) ; Для DS1820.
*This\imode = 4 ; Далее будет прием данных.
*This\kolvo = 255 ; Прием байт.
FillMemory(@*This\Buff[0], SizeOf(*This\Buff), 0)
ElseIf *This\byte = $B4
*szString\s="> Read Power Supply" ; Для DS1820.
*This\imode = 4 ; Далее будет прием данных.
*This\kolvo = 1 ; Прием 1-го байта.
ElseIf *This\byte = $4E
*szString\s="> Write Scratchpad" ; Для DS1820.
*This\imode = 3 ; Далее будет передача данных.
*This\kolvo = 255 ; Передача байт.
ElseIf *This\byte = $48
*szString\s="> Copy Scratchpad" ; Для DS1820.
*This\imode = 0 ; Далее ждем RESET.
ElseIf *This\byte = $B8
*szString\s="> Recall E2" ; Для DS1820.
*This\imode = 0 ; Далее ждем RESET.
Else ; Иначе - неизвестная команда.
; Если получается один HEX-символ, то добавляем впереди 0.
*szString\s = RSet(Hex(*This\byte, #PB_Byte), 2, "0")
*This\imode = 0 ; Далее будет ожидание RESET.
EndIf
*This\num_bit = 0 ; Обнуляем номер бита.
*This\cmnd = *This\byte ; Запоминаем команду.
*This\byte = 0 ; Обнуляем байт.
EndIf
; Если передача, то...
ElseIf *This\imode = 3
; Если ведущий передает 0, то увеличиваем номер бита.
If delta_mks > 15 And delta_mks < 120
*This\num_bit + 1
; Если же ведущий передает 1, то изменяем значение байта.
; и увеличиваем номер бита.
ElseIf delta_mks > 1 And delta_mks <= 15
*This\byte | (1 << *This\num_bit)
*This\num_bit + 1
Else ; Иначе - ошибка!
*szString\s="Transmission Error!"
*This\imode = 0
EndIf
; Если считали байт полностью, то выводим байт в HEX-формате,
; обнуляем байт и номер бита
If *This\num_bit = 8
; Если получается один HEX-символ, то добавляем впереди 0.
*szString\s = RSet(Hex(*This\byte, #PB_Byte), 2, "0")
; Если была команда Match ROM, то...
If *This\cmnd = $55
If *This\kolvo>0 And *This\kolvo<=8
*This\Buff[8-*This\kolvo] = *This\byte
EndIf
; Если считываем первый байт, то этот байт является FC.
If *This\kolvo = 8
*szString\s + " : FC"+" "+DetectDevice(*This\byte)
*This\DevType = *This\byte
; Если считываем последний байт, то этот байт является CRC.
ElseIf *This\kolvo = 1
*szString\s + " : CRC"
crc=CRC8(@*This\Buff[0], 7)
If crc<>*This\Buff[7]
*szString\s + " <-- ERROR должно быть "+RSet(Hex(crc, #PB_Byte), 2, "0")
EndIf
Else ; Иначе - SN
*szString\s + " : SN"
EndIf
EndIf
*This\num_bit = 0
*This\byte = 0
; Уменьшаем кол-во считываемых байт.
*This\kolvo - 1
; Если считали все байты, то...
If *This\kolvo = 0
; Если была команда Match ROM, то после передачи 8 байт адреса.
; будет передаваться команда устройству.
If *This\cmnd = $55
*This\imode = 2
Else ; Иначе - ждем RESET.
*This\imode = 0
EndIf
EndIf
EndIf
; Если прием, то...
ElseIf *This\imode = 4
; Если ведущий принимает 0, то увеличиваем номер бита.
If delta_mks > 15 And delta_mks < 120
*This\num_bit + 1
; Если же ведущий принимает 1, то изменяем значение байта.
; и увеличиваем номер бита.
ElseIf delta_mks > 1 And delta_mks <= 15
*This\byte | (1 << *This\num_bit)
*This\num_bit + 1
Else ; Иначе - ошибка!
*szString\s="Reception error!"
*This\imode = 0
EndIf
; Если считали байт полностью, то выводим байт в HEX-формате,
; обнуляем байт и номер бита.
If *This\num_bit = 8
; Если получается один HEX-символ, то добавляем впереди 0.
*szString\s = RSet(Hex(*This\byte, #PB_Byte), 2, "0")
; Если была команда Read ROM, то...
If *This\cmnd = $33
If *This\kolvo>0 And *This\kolvo<=8
*This\Buff[8-*This\kolvo] = *This\byte
EndIf
; Если считываем первый байт, то этот байт является FC.
If *This\kolvo = 8
*szString\s + " : FC"+" "+DetectDevice(*This\byte)
; Если считываем последний байт, то этот байт является CRC.
ElseIf *This\kolvo = 1
*szString\s + " : CRC"
crc=CRC8(@*This\Buff[0], 7)
If crc<>*This\Buff[7]
*szString\s + " <-- ERROR должно быть "+RSet(Hex(crc, #PB_Byte), 2, "0")
EndIf
Else ; Иначе - SN
*szString\s + " : SN"
EndIf
ElseIf *This\cmnd = $BE
If *This\kolvo>0 And *This\kolvo<=255
*This\Buff[255-*This\kolvo] = *This\byte
EndIf
If *This\DevType = $10 ; DS1820 / DS18S20
Select 255 - *This\kolvo
Case 0 : *szString\s + " : TBL"
Case 1 : *szString\s + " : TBH"+" "+StrF(PeekW(@*This\Buff[0])/2, 1)+" °C"
Case 2 : *szString\s + " : TH"
Case 3 : *szString\s + " : TL"
Case 4 : *szString\s + " : Reserved"
Case 5 : *szString\s + " : Reserved"
Case 6 : *szString\s + " : COUNT_REMAIN"
Case 7 : *szString\s + " : COUNT_PER_C"
Case 8 : *szString\s + " : CRC"
crc=CRC8(@*This\Buff[0], 255 - *This\kolvo)
If crc<>*This\Buff[(255 - *This\kolvo)]
*szString\s + " <-- ERROR должно быть "+RSet(Hex(crc, #PB_Byte), 2, "0")
EndIf
EndSelect
ElseIf *This\DevType = $28 ; DS18B20
Select 255 - *This\kolvo
Case 0 : *szString\s + " : TBL"
Case 1 : *szString\s + " : TBH"+" "+StrF(PeekW(@*This\Buff[0])/16, 1)+" °C"
Case 2 : *szString\s + " : TH"
Case 3 : *szString\s + " : TL"
Case 4 : *szString\s + " : Config "+Str(((*This\byte>>5)&%11)+9)+" bit"
Case 5 : *szString\s + " : Reserved"
Case 6 : *szString\s + " : Reserved"
Case 7 : *szString\s + " : Reserved"
Case 8 : *szString\s + " : CRC"
crc=CRC8(@*This\Buff[0], 255 - *This\kolvo)
If crc<>*This\Buff[(255 - *This\kolvo)]
*szString\s + " <-- ERROR должно быть "+RSet(Hex(crc, #PB_Byte), 2, "0")
EndIf
EndSelect
EndIf
EndIf
*This\num_bit = 0
*This\byte = 0
*This\kolvo - 1 ; Уменьшаем кол-во считываемых байт.
If *This\kolvo = 0 ; Если считали все байты, то ждем RESET.
*This\imode = 0
EndIf
EndIf
; Если была команда $F0 или $EC, то...
ElseIf *This\imode = 5
; Далее два бита - прием, третий - передача. И так 64 раза
If *This\num_bit = 0
*szString\s+" "
EndIf
; Если num_bit равно 0 или 1, то это прием
If *This\num_bit < 2
; Если ведущий принимает 0, то...
If delta_mks > 15 And delta_mks < 120
*szString\s+"0, "
; Если же ведущий принимает 1, то...
ElseIf delta_mks > 1 And delta_mks <= 15
*szString\s+"1, "
Else ; Иначе - ошибка!
*szString\s="Reception error!"
*This\imode = 0
EndIf
*This\num_bit + 1
; Иначе - передача
Else
; Если ведущий передает 0, то...
If delta_mks > 15 And delta_mks < 120
*szString\s+" 0"
; Если же ведущий передает 1, то...
ElseIf delta_mks > 1 And delta_mks <= 15
*szString\s+" 1"
*This\byte | (1 << *This\CountBit)
Else ; Иначе - ошибка!
*szString\s="Transmission error!"
*This\imode = 0
EndIf
*This\CountBit + 1
If *This\CountBit=8
If *This\kolvo>0
*This\Buff[7-(*This\kolvo / 8)] = *This\byte
EndIf
*This\CountBit=0
*This\byte = 0
EndIf
*szString\s + #CRLF$
*This\num_bit = 0
*This\kolvo - 1
EndIf
; Если считали все 192 бита, то ждем команду
If *This\kolvo = 0
*This\imode = 2
*szString\s + "Found device "+SN_Text(@*This\Buff[0])+" "+DetectDevice(*This\Buff[0])
crc=CRC8(@*This\Buff[0], 7)
If crc<>*This\Buff[7]
*szString\s + " <-- ERROR CRC должно быть "+RSet(Hex(crc, #PB_Byte), 2, "0")
EndIf
EndIf
EndIf
EndProcedure
Procedure OneWirePin(*This.ClassParam, time.ABSTIME, mode.DSIMMODES)
Protected delta_mks.i, szString.String
Protected st.b = ishigh(*This\Pin_1Wire\istate()) ;Определяем уровень сигнала на выводе 1Wire.
; Если сюда попали в первый раз, то запоминаем.
; состояние пина и текущее время, затем выходим.
If *This\prev_time = 0
*This\prev_st = st
*This\prev_time = time
ProcedureReturn
EndIf
; Если текущее состояние пина равно предыдущему, то сразу выходим.
If st = *This\prev_st
ProcedureReturn
EndIf
; Если текущее состояние пина низкое, то запоминаем состояние пина и время,
; затем выходим
If st = 0
*This\prev_st = st
*This\prev_time = time
ProcedureReturn
EndIf
; Определяем разницу в пикосекундах, конвертируем в микросекунды
delta_mks = (time - *This\prev_time) / 1000000
; Определяем RESET - отрицательный импульс длиной более 480 мкс,
; независимо от режима
If delta_mks >= 480
szString\s = "> RESET"
*This\imode = 1
*This\DevType = 0
; Определяем PRESENCE - отрицательный импульс длиной 60...240 мкс,
; идущий после RESET
ElseIf *This\imode = 1 And delta_mks >= 60 And delta_mks <= 240
szString\s = "< PRESENCE"
*This\imode = 2 ; Далее будем ждать команду.
*This\byte = 0 ; Обнуляем байт.
*This\num_bit = 0 ; Обнуляем номер бита.
ElseIf *This\imode>=2 And *This\imode<=5 ; Декодирование команд.
OneWire_DecodeCommand(*This, @szString, delta_mks)
Else ; Если не RESET и не PRESENCE, то выводим длительность импульса
; Конвертируем число в строку
szString\s = Str(delta_mks) + " low"
EndIf
If szString\s<>""
If *This\imode<>5
szString\s+#CRLF$
EndIf
; Cdecl вызов функции.
CallCFunctionFast(@*This\MyPopup\Print(), *This\MyPopup, @szString\s, 0)
EndIf
*This\prev_st = st ; Запоминаем новое состояние пина.
EndProcedure
;- Функции составляющие клас типа IDSIMMODEL
;{
Procedure isdigital(*PinName)
Protected *This.ClassParam
MOV *This, ecx
ProcedureReturn 1 ; В компоненте все выводы цифровые.
EndProcedure
Procedure setup(*instance.IINSTANCE, *dsimckt.IDSIMCKT)
Protected *This.ClassParam
MOV *This, ecx
Protected cps.CREATEPOPUPSTRUCT
*This\inst = *instance ; Сохраняем указатели на объекты.
*This\ckt = *dsimckt
; Сохраняем указатели на объекты выводов модели.
*This\Pin_1Wire = *instance\getdsimpin("1Wire", #True)
cps\caption = "1-Wire Debugger Log" ; Заголовок окна
cps\flags = #PWF_VISIBLE| ; Видимое окно
#PWF_SIZEABLE ; Размеры окна можно изменять мышкой.
cps\type = #PWT_DEBUG ; Тип окна - DEBUG
cps\height = 500 ; Высота окна
cps\width = 300 ; Ширина окна
cps\id = 200 ; Идентификатор окна
*This\MyPopup = *instance\createpopup(@cps)
*This\Prev_Time = 0
*This\imode = 0 ; Ожидание RESET.
*This\byte = 0 ; Обнуляем байт
*This\num_bit = 0 ; Обнуляем номер бита
*This\kolvo = 0
*This\cmnd = 0
*This\DevType = 0
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 #False
EndProcedure
Procedure simulate(time.ABSTIME, mode.DSIMMODES)
Protected *This.ClassParam
MOV *This, ecx
OneWirePin(*This, time, mode)
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: ; Таблица методов класса типа IDSIMMODEL.
Data.i @isdigital()
Data.i @setup()
Data.i @runctrl()
Data.i @actuate()
Data.i @indicate()
Data.i @simulate()
Data.i @callback()
EndDataSectionОбратите внимание как вызывается метод Print() в 520 строке. Он вызывается несколько необычным способом через функцию CallCFunctionFast(). Дело вот в чем. Данный метод (функция, находящая в классе) использует соглашение вызова Cdecl, а не Thiscall и чтобы соглашение вызова соответствовало нужному, использована эта функция. Она вызывает метод в указателю и используя соглашение вызова Cdecl передает методу ссылку на объект и на строковую переменную. Таких методов (использующих Cdecl в место Thiscall) не много и в файле vsm.pbi, в комментарии написано что в них Cdecl вызов функции.
Скомпилируйте код в DLL и сохраните с именем 1WireDebug.dll. После чего скопируйте в папку MODELS протеуса и если все сделано правильно, то модель будет доступна в библиотеке и ее можно будет использовать в проектах.
В качестве примера был создан проект, точнее переделан один из имеющихся, в который добавлена модель 1-Wire отладчика и кнопка, по которой происходит считывание температуры с 1-Wire датчика.
При запуске симуляции проекта, видно что происходит поиск 1-Wire устройств и найден датчик температуры типа DS18B20.
Если нажать на кнопку, то произойдет чтение температуры из датчика и в логе анализатора видно какие команды проходят через шину 1-Wire и в какой последовательности. Так же расшифровываются некоторые данные, например, температура считанная из датчика.
Если не отображается окно "1-Wire Debugger Log", нужно во время симуляции кликнуть правой кнопкой мышки по модели 1-Wire анализатора и найти в меню что-то похожее на "1-Wire Debugger Log" и кликнуть по этому пункту.
В папке Proteus находится пример использования и библиотека, расположенная в папках LIBRARY и MODELS. Для установки модели, эти папки (LIBRARY и MODELS) нужно скопировать в папку с протеусом.
В папке Src находятся исходники DLL-библиотеки и графической части модели, а так же исходник программы для микроконтроллера. Там также есть промежуточные варианты исходника DLL, рассмотренные на kazus.ru.
Модель создавалась и тестировалась в Proteus 7.8 SP2. С другими версиями, работоспособность не проверялась.
DLL компилировалась в PureBasic 5.11 Windows x86.
Продолжение статьи - модель цифрового светодиода.

