Научно-Производственное Объединение «Каскад-ГРУП»
428000, Россия, Чувашская Республика, г. Чебоксары, пр. Машиностроителей, д. 1 КГ
Телефон: (8352) 22-34-32,
Факс: (8352) 63-48-38
E-mail: abc@kaskad-asu.com
Реализация протокола в SoftLogic-системе «KLogic»
Разработка драйвера начинается с добавления поддержки протокола и устройств в инструментальную среду разработки «KLogic IDE». Для этого нужно сделать следующее:
- Добавить описание протокола в файл KLData\prot_mod_uni.xml в следующем виде (XML-формат):
<Object Type="Protocol">
<Settings>
<sName>[UNI] Водосчетчики АС-001</sName>
<sFullName>Протокол опроса водосчетчиков АС-001</sFullName>
<sProtGroupName>Водосчетчики</sProtGroupName>
<Controller>DECONT</Controller>
<Controller>I-7000</Controller>
<Controller>I-8000</Controller>
<Controller>WKLOGIC</Controller>
<Controller>None-target</Controller>
<Controller>Теконик P06</Controller>
<Controller>MOXA IA240</Controller>
<Controller>MOXA</Controller>
<TSName>UniTabSheet</TSName>
<isExternalProt>True</isExternalProt>
<isCounterProt>False</isCounterProt>
<isVKTProt>False</isVKTProt>
<ProtCode>34</ProtCode>
<Properties>
<Prop Id="1" Name="COM порт" Descr="Номер COM-порта контроллера, к которому подключены водосчетчики" Type="BYTE" Init="1"/>
<Prop Id="2" Name="Период опроса, мсек" Descr="Периодичность опроса (в милисекундах) контроллером водосчетчиков" Type="DWORD" Init="1000"/>
<Prop Id="3" Name="Количество повторных запросов" Descr="Количество повторных запросов при сбое по линии связи" Type="BYTE" Init="3"/>
<Prop Id="4" Name="Тайм-аут по обмену" Descr="Тайм-аут по обмену (в мсек)" Type="DWORD" Init="2000"/>
<Prop Id="5" Name="Тайм-аут между байтами" Descr="Тайм-аут между байтами (в мсек)" Type="DWORD" Init="20"/>
</Properties>
</Settings>
</Object>
Здесь нужно изменить содержимое следующих секций:
- sName – название протокола.
- sFullName – полное наименование протокола (описание).
- sProtGroupName – группа, к которой относится протокол.
- Controller – наименование контроллера (см. файл KLData\cntrlrs.xml), к которому данный протокол применим. Должна быть по крайней мере одна секция с контроллером None-target для возможности разработки и отладки протокола под Windows.
- ProtCode – уникальный код протокола, по которому он идентифицируется в бинарной конфигурации.
- Properties – данная секция описывает свойства конкретного протокола, их состав целиком определятся программистом. В программном коде обращение к конкретному свойству будет осуществляться по его номеру.
2. Описать модули, работающие по данному протоколу, в *.io-файле (XML-формат):
<?xml version="1.0" encoding="windows-1251"?>
<!-- АС-001 -->
<KLogicModules>
<Module Id="76">
<Name>АС-001</Name>
<CfgName>AS_001_Cfg</CfgName>
<Descr>Водосчетчик ультразвуковой АС-001</Descr>
<Protocol>[UNI] Водосчетчики АС-001</Protocol>
<Properties>
<Prop Id="1" Name="Адрес модуля" Descr="Сетевой номер водосчетчика АС-001" Type="BYTE" Init="0"/>
</Properties>
<TagProperties>
<Prop IdStr="Id" Name="Идентификатор" Type="WORD" Access="H"/>
</TagProperties>
<TagTree>
<Group Name="Измерения">
<Tag Name="Расход м3/час" Descr="Текущий расход жидкости через прибор" Measure="м3/час" Type="AIF" Id="1"/>
<Tag Name="Объем м3" Descr="Накопленный объем жидкости" Measure="м3" Type="AIF" Id="2"/>
<Tag Name="Время наработки" Descr="Время работы прибора в часах" Measure="час" Type="AIF" Id="3"/>
<Tag Name="Состояние прибора" Descr="True - работа, False - ошибка" Type="DI" Id="4"/>
</Group>
<Group Name="Служебные параметры">
<Tag Name="Условный диаметр" Descr="Условный диаметр водосчетчика" Type="AOF" Id="5"/>
<Tag Name="Отсутствие связи" Descr="True - связь отсутствует, False - присутствует" Type="DI" Id="6"/>
<Tag Name="Опрос отключен" Descr="True - опрос отключен, False - включен" Type="DO" Id="7"/>
<Tag Name="Время опроса" Descr="Время одного цикла опроса" Measure="мсек" Type="AII" Id="8"/>
</Group>
</TagTree>
</Module>
</KLogicModules>
См. файл KLData\_example.io_ для получения справки по заполнению.
Свободные коды протоколов и идентификаторы модулей могут быть определены с помощью экспорта списка поддержанных протоколов и модулей.
На этом этапе уже можно попробовать добавить в KLogic IDE новый протокол и модули, посмотреть, все ли верно описано.
Вторым шагом необходимо создать заготовку нового протокола в коде исполнительной системы. Для этого нужно:
1. Добавить код протокола в файл main.h следующим образом:
#define SERIAL_PROT_AS001UNI 34 // протокол опроса водосчетчиков АС001 (универсальный формат)
2. Добавить поддержку протокола в файл serial.c:
#ifdef USE_AS001UNI_SERIAL_TASK
#include "io/serial/as001.h"
#endif
...
PROT ArrProt[] = {
...
#ifdef USE_AS001UNI_SERIAL_TASK
{ SERIAL_PROT_AS001UNI, 0, AS001UNI_InitOperations },
#endif
...
};
Здесь указывается заголовочный файл добавляемого протокола, а также функция его инициализации.
Кроме того, как можно заметить, используется директива условной компиляции, которая указывает, включать поддержку протокола в исполнительную систему, или нет. Необходимо включить эту директиву в config-файле вашего рабочего проекта, например, platform\win32\config-win-console.h:
#define USE_AS001UNI_SERIAL_TASK
3. Создать заголовочный файл протокола (as001.h):
#ifndef AS001_H
#define AS001_H
#include "fb/task.h"
#include "io/serial.h"
// Структура модуля
typedef struct _as001uni_module {
WORD fst; // Номер первого параметра модуля в массиве pParams
WORD quan; // Количество параметров в модуле
char* name; // Имя модуля (10 байт)
} ALIGN_DWORD as001uni_module;
extern int AS001UNI_InitOperations(SERIAL_TASK_CB* pSerialTasksCB);
#endif // #ifndef AS001_H
4. Создать файл исходного кода протокола (as001uni.c):
#include "ser_api.h"
#include "io/serial/as001.h"
#ifdef USE_AS001UNI_SERIAL_TASK
// Прототипы функций задачи
int AS001UNI_GetSizeCfgModule(void);
int AS001UNI_InitSerialTask(SERIAL_TASK_CB*);
int AS001UNI_SetCfgModules(SERIAL_TASK_CB*, BYTE R_HUGE_PTR);
int AS001UNI_SerialTaskOneStep(SERIAL_TASK_CB*);
// Имя протокола
static char* AS001UNI_protocol_name = "AS001UNI";
int AS001UNI_InitOperations(SERIAL_TASK_CB* pSerialTasksCB) {
// Указатель на функцию определения размера модуля
pSerialTasksCB->operations.get_size_cfg_module = (void*)AS001UNI_GetSizeCfgModule;
// Указатель на функцию конфигурирования модулей
pSerialTasksCB->operations.set_cfg_modules = (void*)AS001UNI_SetCfgModules;
// Указатель на функцию конфигурирования протокола
pSerialTasksCB->operations.init_serial_task = (void*)AS001UNI_InitSerialTask;
// Указатель на функцию выполнения одного цикла задачи
pSerialTasksCB->operations.one_step = (void*)AS001UNI_SerialTaskOneStep;
// Указатель на название протокола
pSerialTasksCB->operations.protocol_name = AS001UNI_protocol_name;
return 0;
}
int AS001UNI_GetSizeCfgModule(void) {
return sizeof(as001uni_module);
}
int AS001UNI_SetCfgModules(SERIAL_TASK_CB* pSerialTasksCB, BYTE R_HUGE_PTR pModulesCfg) {
return 0;
}
int AS001UNI_InitSerialTask(SERIAL_TASK_CB* pSerialTaskCB) {
return 0;
}
int AS001UNI_SerialTaskOneStep(SERIAL_TASK_CB* pSTaskCB) {
return 0;
}
#endif // #ifdef USE_AS001UNI_SERIAL_TASK
Теперь исполнительная система может быть скомпилирована без ошибок, шаблон протокола создан.
При запуске исполнительной системы KLogic производится однократная инициализация модулей и протокола, после чего с заданным периодом вызывается основная функция протокола, осуществляющая обмен с его модулями (чтение и запись значений). Рассмотрим эти функции подробнее.
int AS001UNI_SetCfgModules(SERIAL_TASK_CB* pSerialTasksCB, BYTE R_HUGE_PTR pModulesCfg) {
as001uni_module *mod;
int m, i;
BYTE *cfg;
mod = pSerialTasksCB->pModules;
// Цикл по модулям
for (m = 0; m<pSerialTasksCB->QuanModules; m++, mod++) {
// Считываем настройки текущего модуля
cfg = GetModuleCfg(pSerialTasksCB, m);
// Получаем память
memset(mod, 0, sizeof(as001uni_module));
mod->fst = READ_WORD(cfg); // Номер первого параметра модуля в массиве pParams
mod->quan = READ_WORD(cfg + 2); // Количество параметров в модуле
mod->name = cfg + 4; // Код протокола (имя)
// Сетевой номер водосчетчика
mod->Addr = GetModuleWordProp(cfg, 1, 0x0);
// Номера служебных каналов модуля
mod->pDiametrIdx = -1;
mod->pConnectIdx = -1;
mod->pDontSpeakIdx = -1;
// Заполняем теги модуля
mod->tags = RMemGet(sizeof(WORD)*mod->quan);
if (!mod->tags) {
PRINT("Error RMemGet for pTagsId!"); CR;
return -1;
}
// Получаем конфигурацию тегов
cfg = GetModuleTagsCfg(pSerialTasksCB, m);
for (i = 0; i < mod->quan; i++) {
// Запоминаем id переданных каналов
mod->tags[i] = READ_WORD(cfg);
cfg += 2;
if (mod->tags[i] == tiDiametr) // Условный диаметр
mod->pDiametrIdx = i;
if (mod->tags[i] == tiConnect) // Отсутствие связи
mod->pConnectIdx = i;
if (mod->tags[i] == tiDontSpeak) // Опрос отключен
mod->pDontSpeakIdx = i;
}
}
return 0;
}
Чтение свойств модулей происходит по номерам, определенным в io-файле. Используются функции API:
PROP_TYPE GetModuleProp(BYTE* pModuleCfg, BYTE PropID, void** ppProperty);
WORD GetModuleWordProp(BYTE* pModuleCfg, BYTE PropID, WORD DefVal);
DWORD GetModuleDwordProp(BYTE* pModuleCfg, BYTE PropID, DWORD DefVal);
Первая функция универсальна, возвращает тип свойства и позволяет получить указатель на данные. Остальные две позволяют прочитать свойство модуля с приведением типа к WORD и DWORD.
Конфигурация тегов модуля сквозная:
Первое свойство первого тега модуля
...
Последнее свойство первого тега модуля
...
Первое свойство последнего тега модуля
...
Последнее свойство последнего тега модуля
Для получения значений нужно пользоваться макросами READ_WORD, READ_DWORD, READ_FLOAT, READ_LONG, сдвигая после указатель на нужное количество байт.
int AS001UNI_InitSerialTask(SERIAL_TASK_CB* pSerialTaskCB) {
as001uni_prot *prt;
// Получаем "чистую" память
prt = GetCleanMem(sizeof(as001uni_prot));
if (!prt) return -1;
// Инициализируем конфигурацию протокола
pSerialTaskCB->pProtocolSettings = (void*)prt;
// Номер СОМ-порта контроллера, к которому подключены водосчетчики (default: СОМ1)
prt->Port = GetDriverWordProp(pSerialTaskCB, 1, 1);
// Периодичность опроса (в мсек) контроллером водосчетчиков (default: 1 сек)
prt->Period = GetDriverWordProp(pSerialTaskCB, 2, 1000);
pSerialTaskCB->pHeader->Period = prt->Period;
// Количество повторных запросов при сбое по линии связи (default: 0)
prt->Retries = GetDriverWordProp(pSerialTaskCB, 3, 0);
// Таймаут по обмену (в мсек) (default: 2 сек)
prt->Taut = GetDriverWordProp(pSerialTaskCB, 4, 2000);
pSerialTaskCB->Taut = prt->Taut;
// Таймаут между байтами (в мсек) (default: 20 мсек)
prt->TautBit = GetDriverWordProp(pSerialTaskCB, 5, 20);
pSerialTaskCB->TautBit = prt->TautBit;
// Устанавливаем размеры буферов
pSerialTaskCB->SizeSendBuf = 32;
pSerialTaskCB->SizeRecvBuf = 32;
// Иициализируем буфер отправки
pSerialTaskCB->SendBuf = RMemGet(pSerialTaskCB->SizeSendBuf + pSerialTaskCB->SizeRecvBuf);
if (pSerialTaskCB->SendBuf == NULL) return (-1);
pSerialTaskCB->RecvBuf = pSerialTaskCB->SendBuf + pSerialTaskCB->SizeSendBuf;
return 0;
}
Чтение свойств протокола происходит по номерам, определенным в файле prot_mod_uni.xml. Аналогично чтению свойств модулей, используются функции API:
PROP_TYPE GetDriverProp(SERIAL_TASK_CB *pSerialTaskCB, BYTE PropID, void** ppProperty);
WORD GetDriverWordProp(SERIAL_TASK_CB *pSerialTaskCB, BYTE PropID, WORD DefVal);
char* GetDriverStringProp(SERIAL_TASK_CB *pSerialTaskCB, BYTE PropID, char *DefVal);
DWORD GetDriverDwordProp(SERIAL_TASK_CB *pSerialTaskCB, BYTE PropID, DWORD DefVal);
В основной функции описывается работа одного цикла протокола, обмен данными со всеми модулями, установка значений качества выходов и т.п. Вызывается периодически.
int AS001UNI_SerialTaskOneStep(SERIAL_TASK_CB* pSTaskCB) {
int i, j, Err, Adr;
as001uni_module *mod;
RPARAM *Signl, *PrSv, *StatOpr;
int GlIndDyIdx;
ZGLKADR *Otv;
ulong t;
#ifdef USE_TIMESTAMP
RTIME Timestamp;
#endif
// Инициализация каналов
// Указатель на структуру модулей
mod = (as001uni_module*)pSTaskCB->pModules;
// Цикл по модулям
for (i = 0; i < pSTaskCB->QuanModules; i++, mod++) {
// Инициализация канала "Отсутствие связи"
if (mod->pConnectIdx != -1) {
PrSv = pSTaskCB->pParams + mod->fst + mod->pConnectIdx;
PrSv->Quality = OPC_QUALITY_GOOD;
PrSv->Value.Boolean = RTRUE; // По-умолчанию пишем True, связи нет
}
// Цикл по каналам модуля
for (j = 0; j < mod->quan; j++) {
if (j != mod->pDiametrIdx && j != mod->pConnectIdx && j != mod->pDontSpeakIdx) {
Signl = pSTaskCB->pParams + mod->fst + j;
Signl->Quality = 003; // Ошибка открытия ресурса (СОМ-порт, сокет и т.п.)
}
}
}
// Инициализируем COM-порт
if (AS001UNI_InitComPort(pSTaskCB)) return -1;
// Запоминаем текущее значение времени
t = GetTickCountMs();
// Указатель на структуру модулей
mod = (as001uni_module*)pSTaskCB->pModules;
// Кадр ответа
Otv = (ZGLKADR*)pSTaskCB->RecvBuf;
// Цикл по модулям
for (i = 0; i < pSTaskCB->QuanModules; i++, mod++) {
// Запрос состояния прибора
mod->RamDev[0] = 0;
Err = AS001UNI_Obmen(pSTaskCB, mod, 0);
if (!Err) {
mod->RamDev[0] = Otv->Byte5;
// Канал "Отсутствие связи с устройством"
if (mod->pConnectIdx != -1) {
PrSv->Quality = OPC_QUALITY_GOOD;
PrSv->Value.Boolean = RFALSE;
}
}
StatOpr = pSTaskCB->pParams + mod->fst + mod->pDontSpeakIdx;
// Проверка канала "Опрос отключен"
if (mod->pDontSpeakIdx != -1 && StatOpr->Quality == OPC_QUALITY_GOOD && StatOpr->Value.Boolean) {
for (j = 0; j < mod->quan; j++) {
if (j != mod->pDiametrIdx && j != mod->pConnectIdx && j != mod->pDontSpeakIdx) {
Signl = pSTaskCB->pParams + mod->fst + j;
Signl->Quality = 001; // Модуль(устройство) блокирован(о) для опроса
}
}
// Переходим к следующему модулю
continue;
}
// Инициализация канала "Условный диаметр"
if (mod->pDiametrIdx != -1 && Signl->Quality != OPC_QUALITY_GOOD)
mod->IndDy = 0;
else {
Signl = pSTaskCB->pParams + mod->fst + mod->pDiametrIdx;
mod->IndDy = AS001UNI_ProvDy(Signl);
}
// Запрос условного диаметра (в случае необходимости)
if (!mod->IndDy) {
Err = AS001UNI_Obmen(pSTaskCB, mod, 11);
if (!Err) {
if (Otv->Byte5 < 1 || Otv->Byte5 > 7) continue;
mod->IndDy = Otv->Byte5;
if (mod->pDiametrIdx != -1) {
Signl->Quality = OPC_QUALITY_GOOD;
GlIndDyIdx = mod->fst + mod->pDiametrIdx;
#ifdef USE_TIMESTAMP
if (!pSTaskCB->operations.use_timestamp)
RGetDateTime(&Timestamp);
#endif
LOCK_GLOBAL_ARRAY();
// Пишем условный диаметр напрямую в глобальный массив
iWriteFloat(&GlParam(pSTaskCB->pListParams[GlIndDyIdx].NumParamInGlobalArray), (float)Dy[mod->IndDy]);
// Определяемся с временной меткой
#ifdef USE_TIMESTAMP
if (pSTaskCB->operations.use_timestamp)
pGlobArrayTime[pSTaskCB->pListParams[GlIndDyIdx].NumParamInGlobalArray] = pSTaskCB->pTimestamps[GlIndDyIdx];
else
pGlobArrayTime[pSTaskCB->pListParams[GlIndDyIdx].NumParamInGlobalArray] = Timestamp;
#endif
// Передаем в задачу МЭК для анализа изменений параметров
#ifdef USE_IEC_TASK
#ifndef IPC_ANY
IecTaskProcessOneParam(pSTaskCB->pListParams[GlIndDyIdx].NumParamInGlobalArray);
#endif
#endif
UNLOCK_GLOBAL_ARRAY();
}
}
// Если и тут облом, переходим к следующему модулю
else continue;
}
mod->KoefQ = KoefQ[mod->IndDy];
mod->KoefF = KoefF[mod->IndDy];
// Цикл по каналам модуля
for (j = 0; j < mod->quan; j++) {
Signl = pSTaskCB->pParams + mod->fst + j;
switch(mod->tags[j]) {
case tiConsumption: // Текущий расход
Signl->Quality = 047; // Ошибка считывания параметра из устройства
// Читаем данные
Err = AS001UNI_Obmen(pSTaskCB, mod, 1); // 1, 2 байты
if (Err) break;
mod->RamDev[1] = Otv->Byte5;
mod->RamDev[2] = Otv->Byte4;
// Выводим значение
Signl->Quality = OPC_QUALITY_GOOD;
Signl->Value.Float = (float)(READ_WORD(mod->RamDev + 1)) / mod->KoefQ;
Signl->Value.Float *= 3.6; // л/сек --> м3/час
break;
case tiV: // Накопленный объем
Signl->Quality = 047; // Ошибка считывания параметра из устройства
// Читаем данные
Err = AS001UNI_Obmen(pSTaskCB, mod, 5); // 6 байт
if (Err) break;
mod->RamDev[6] = Otv->Byte4;
Err = AS001UNI_Obmen(pSTaskCB, mod, 7); // 7, 8 байты
if (Err) break;
mod->RamDev[7] = Otv->Byte5;
mod->RamDev[8] = Otv->Byte4;
Err = AS001UNI_Obmen(pSTaskCB, mod, 9); // 9, 10 байты
if (Err) break;
mod->RamDev[9] = Otv->Byte5;
mod->RamDev[10] = Otv->Byte4;
Signl->Quality = OPC_QUALITY_GOOD;
Signl->Value.Float = AS001UNI_PreobrStrFloat(mod->RamDev + 6, 5) * mod->KoefF;
Signl->Value.Float *= 0.001; // л --> м3
break;
case tiT: // Время наработки
Signl->Quality = 047; // Ошибка считывания параметра из устройства
// Читаем данные
Err = AS001UNI_Obmen(pSTaskCB, mod, 3); // 3, 4 байты
if (Err) break;
mod->RamDev[3] = Otv->Byte5;
mod->RamDev[4] = Otv->Byte4;
Err = AS001UNI_Obmen(pSTaskCB, mod, 5); // 5 байт
if (Err) break;
mod->RamDev[5] = Otv->Byte5;
// Выводим значение
Signl->Quality = OPC_QUALITY_GOOD;
Signl->Value.Float = AS001UNI_PreobrStrFloat(mod->RamDev + 3, 3) * 0.1;
break;
case tiState: // Статус прибора
Signl->Quality = OPC_QUALITY_GOOD;
Signl->Value.Boolean = mod->RamDev[0] ? 1 : 0;
break;
case tiWorkTime: // Время опроса
Signl->Quality = OPC_QUALITY_GOOD;
Signl->Value.Integer = GetTickCountMs() - t;
break;
}
}
}
AS001UNI_DeInitComPort(pSTaskCB);
return 0;
}
Значения в глобальном массиве параметров обновляются после выхода из функции OneStep. Получить указатель на конкретный тег модуля в локальном массиве задачи можно следующим образом:
Signl = pSTaskCB->pParams + mod->fst + j;
Здесь mod->fst – номер первого тега модуля в массиве pParams, j – порядковый номер тега в модуле. Характер тега определяется по ассоциативному массиву mod->tags, в котором хранятся все свойства тегов, вычитанные при инициализации модуля.
Сам тег представлен структурой вида:
typedef struct _RPARAM
{
#ifdef BIG_ENDIAN_VERSION
unsigned short Type:3;
unsigned short Flags:5;
unsigned short Quality:8;
#else
unsigned short Quality:8;
unsigned short Flags:5;
unsigned short Type:3;
#endif
ALIGN_DWORD union Value {
BYTE Byte[4];
WORD Word[2];
RFLOAT Float;
RBOOLEAN Boolean;
RINTEGER Integer;
RDATETIME DateTime;
DWORD IpAddr;
#ifdef USE_STRING
RSTRING String;
#endif
} Value;
}ALIGN_DWORD RPARAM;
В данной структуре обязательно заполнение полей качества и значения. Хорошее качество равно 192 (OPC_QUALITY_GOOD), остальные значения отведены под коды ошибок (см. файл CodeErr.h).
При реализации функций обмена по COM-порту или через TCP сокет можно как использовать низкоуровневые функции API из файла ser_api.c, так и универсальные, описанные в файле io\serial.c (в этом случае поля структуры SERIAL_TASK_CB, относящиеся к связи, должны быть заполнены корректно):
/*
* Функция захватывает и инициализирует последовательный порт или создаёт сокет и устанавливает соединение
*/
int Init_Port(SERIAL_TASK_CB* pSerialTaskCB, DWORD IPAddr, WORD TCPPort, DWORD TautMutex);
/*
* Функция посылает пакет в COM-порт или в сокет
*/
int Send_Port(SERIAL_TASK_CB* pSerialTaskCB, BYTE* SendBuf, int ChSend);
/*
* Функция принимает пакет из COM-порта или из сокета
*/
int Recv_Port(SERIAL_TASK_CB* pSerialTaskCB, BYTE* RecvBuf, int* ChRecv, DWORD TimeOut_ms);
/*
* Функция посылает запрос и принимает ответ из COM-порта или из сокета
*/
int Obmen_Port(SERIAL_TASK_CB* pSerialTaskCB, BYTE* SendBuf, int ChSend, BYTE* RecvBuf, int* ChRecv, ulong TautObmen);
/*
* Функция очищает COM-порт (для сокета никаких действий не производится)
*/
void Clear_Port(SERIAL_TASK_CB* pSerialTaskCB);
/*
* Функция освобождает последовательный порт или закрывает сокет
*/
void Deinit_Port(SERIAL_TASK_CB* pSerialTaskCB);