В предыдущей статье был рассмотрен процесс создания простой модели для протеуса. На сей раз усложним задачу и создадим не только демонстрационную модель, но и необходимую в протеусе.
За основу был взят проект отладчика шины 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.
Продолжение статьи - модель цифрового светодиода.