Sailor 通信协议 C# 静态库与动态库#
备注
基于 vSeaskyPort.dll 可快速开发高性能 WPF 串口上位机。提供 C# 可调用的静态库/动态库:底层串口为 Windows 原生 API,数据处理与协议为 C++(Seasky),C# 侧重界面,计算与协议封装在 C++ 中,便于开发高性能串口上位机。
备注
USB 虚拟串口转 CAN 实测可达 2ms 周期,优于 C# 自带串口库;基于 WPF 可实现接近 Qt 的性能且开发周期短。
将动态库添加到工程#
最后在此编写了一个简单的示例程序,不包含完整程序,以供参考。
vSeaskyPort 动态库接口#
PROTOCOL_CLASS_REF vSeaskyPort
{
public:
vSeaskyPort(void);
~vSeaskyPort();
//协议依赖
protocol_struct* pTxProtocol = new protocol_struct();//指针内数据受保护,C#禁止访问
protocol_struct* pRxProtocol = new protocol_struct();//指针内数据受保护,C#禁止访问
bool GetStorageMethodIsSmall(void);
/// <summary>
/// 初始化协议所需内存
/// </summary>
/// <param name="uTxLen">pTxProtocol的uint32_t数据长度</param>
/// <param name="uRxLen">pRxProtocol的uint32_t数据长度</param>
void ProtocolInit(uint16_t uTxLen, uint16_t uRxLen);
/// <summary>
/// 通过动态 Data(uint32_t) 长度计算总数据Buffer(uint8_t) 的长度
/// </summary>
/// <param name="uLen"></param>
/// <returns></returns>
uint16_t ProtocolCalcLen(uint16_t uLen);
/// <summary>
/// 自动初始化Tx所需动态内存,uLen需小于128,内部分配内存,在外部托管无法访问
/// </summary>
/// <param name="uLen">pTxProtocol的uint32_t数据长度</param>
void ProtocolAutoInitTx(uint16_t uLen);
/// <summary>
/// 自动初始化Rx所需动态内存,uLen需小于128,内部分配内存,在外部托管无法访问
/// </summary>
/// <param name="uLen">pRxProtocol的uint32_t数据长度</param>
void ProtocolAutoInitRx(uint16_t uLen);
/// <summary>
/// 获取Tx动态内存地址和长度,返回长度为 pRxData长度,pRxBuffer长度为 uMaxLen*4+12
/// </summary>
/// <param name="pTxData"></param>
/// <param name="pTxBuffer"></param>
/// <param name="uMaxLen">pTxData的数据长度</param>
void ProtocolTxPointGet(uint32_t* pTxData, uint8_t* pTxBuffer, uint16_t& uMaxLen);
/// <summary>
/// 获取Rx动态内存地址和长度,返回长度为 pRxData长度,pRxBuffer长度为 uMaxLen*4+12
/// </summary>
/// <param name="pRxData"></param>
/// <param name="pRxBuffer"></param>
/// <param name="uMaxLen">pRxData的数据长度</param>
void ProtocolRxPointGet(uint32_t* pTxData, uint8_t* pTxBuffer, uint16_t& uMaxLen);
/// <summary>
/// 初始化协议Tx所需内存,外部分配,uLen需小于128
/// </summary>
/// <param name="pTxData">预先分配的内存地址</param>
/// <param name="pTxBuffer">预先分配的内存地址</param>
/// <param name="uLen">pTxData的数据长度</param>
void ProtocolInitTx(uint32_t* pTxData, uint8_t* pTxBuffer, uint16_t uLen);
/// <summary>
/// 初始化协议Rx所需内存,外部分配,uLen需小于128
/// </summary>
/// <param name="pRxData">预先分配的内存地址</param>
/// <param name="pRxBuffer">预先分配的内存地址</param>
/// <param name="uLen">pRxData的数据长度</param>
void ProtocolInitRx(uint32_t* pRxData, uint8_t* pRxBuffer, uint16_t uLen);
/// <summary>
/// 同步方式打开串口
/// </summary>
/// <param name="com_num">串口号</param>
/// <param name="baud_rate">波特率</param>
/// <param name="parity">奇偶校验位</param>
/// <param name="byte_size">数据位</param>
/// <param name="stop_bits">停止位</param>
/// <returns></returns>
bool vSerialOpen(uint32_t com_num, uint32_t baud_rate, uint32_t parity, uint32_t byte_size, uint32_t stop_bits);
/// <summary>
/// 关闭串口
/// </summary>
void vSerialClose();
/// <summary>
/// 判断串口是否打开
/// </summary>
/// <returns>正确返回为ture,错误返回为false</returns>
bool vSerialIsOpen();
/// <summary>
/// 清除缓冲区
/// </summary>
void vSerialClearBuffer(void);
/// <summary>
/// 得到最后一次失败的错误信息
/// </summary>
/// <returns></returns>
uint8_t vSerialGetLastError();
/// <summary>
/// 将该函数放在一个独立的线程中,以实现串口消息的接收处理
/// </summary>
void vSerialReceiveTask();
/// <summary>
/// 协议计算,并发送数据
/// </summary>
void ProtocolTransmit(uint16_t equipment_type, uint16_t equipment_id, uint16_t data_id, uint32_t* pData, uint16_t data_len);
/// <summary>
/// 设置接收数据处理完成回调函数
/// </summary>
/// <param name="pFun"></param>
void vSerialSetReceivCallbackFun(pReceivePointer^ pFun);
/// <summary>
/// 设置调试信息等级和调试信息回调显示函数
/// </summary>
/// <param name="debugLevel">调试信息等级</param>
/// <param name="pFun">调试信息回调显示函数</param>
void vSerialSetDebugCallbackFun(uint8_t debugLevel, pDebugPointer^ pFun);
private:
// 此部分为私有,C#无法访问
static bool vSerialIsOpenIt = false; //串口打开标志
static bool pSerialReceiveCppPointerIsEnable = false; //是否初始化数据处理完成函数回调
static bool pSerialDebugIsEnable = false; //是否初始化调试信息函数回调
static vSerialPort* vSerialPortClass = new vSerialPort(); //Win Api串口实例
static pReceivePointer^ pReceiveCallbackFun; //数据处理完成函数回调指针
static pDebugPointer^ pDebugCallbackFun; //调试信息函数回调指针
/// <summary>
/// 接收数据处理完成中断
/// </summary>
void vReceiveCallBack(void);
/// <summary>
/// 接收调试信息信息打印函数
/// </summary>
/// <param name="pStr"></param>
void vSerialDebugPrintf(char* pStr);
};
开始使用#
使用前需在项目中启用 unsafe(允许不安全代码),否则 C# 无法使用指针。
添加动态库后,在需使用的文件中引用命名空间 vSeaskyProtocol。
using vSeaskyProtocol;
备注
初始化 Seasky 协议内存,Tx/Rx 不定长数据段(uint32_t)长度均为 24,详见 Seasky 协议说明。需实现规定格式的回调 vSerialReceiveCallback(每帧解析完成后由 C++ 调用)和 vSerialDebugCallback(出错时打印),并传入初始化参数及调试等级以过滤无关打印。
/// <summary>
/// 按需求初始化协议内存和相关回调函数,
/// </summary>
public unsafe void vSeaskyProtocolInit()
{
vSeaskyPortInit(24,24,new pReceivePointer(vSerialReceiveCallback), new pDebugPointer(vSerialDebugCallback), COM_LOG_LEVEL.COM_LOG_LEVEL_DEBUG);
}
/// <summary>
/// CLR 接收数据处理完成回调函数
/// </summary>
/// <param name="equipment_type"></param>
/// <param name="equipment_id"></param>
/// <param name="data_id"></param>
/// <param name="pData"></param>
/// <param name="data_len"></param>
public unsafe void vSerialReceiveCallback(UInt16 equipment_type, UInt16 equipment_id, UInt16 data_id, UInt32* pData, UInt16 data_len)
{
//Console.WriteLine("vSerialReceiveCallback");
//在此根据数据内容编写相关回调函数
}
/// <summary>
/// CLR debug 回调函数
/// </summary>
/// <param name="pStr"></param>
public unsafe void vSerialDebugCallback(sbyte* pStr)
{
//Console.WriteLine("vSerialDebugCallback");
//在此实现调试信息输出窗口
}
备注
另需在单独线程中循环调用 vSerialReceiveTask 完成串口接收与解析。
{
// 关闭串口
vcSeaskyPort.vSerialClose();
// 创建一个接收线程
vReceivedThread = new Thread(new ThreadStart(ThreadReceivedFun));
vSerialState = false;
// 线程先挂起,现在串口未打开
vReceivedThread.Start();
vReceivedThread.IsBackground = true;
vReceivedThread.Suspend();
}
/// <summary>
/// 接收线程,在初始化之后就不用管了,提供给 vSeaskyPort 内部循环检测接收数据用。
/// </summary>
public void ThreadReceivedFun()
{
while (true)
{
vcSeaskyPort.vSerialReceiveTask();
}
}
备注
为节省资源,可在串口关闭时挂起该线程,打开串口后再恢复。
/// <summary>
/// 打开串口,打开串口前应当提前设置串口参数。
/// </summary>
/// <returns></returns>
public bool vSerialOpen()
{
if (vFirstInit == true)
{
unsafe
{
try
{
// 需要提前设置参数
vcSeaskyPort.vSerialOpen(vSerialPortNameNum, vSerialBaudRate, vSerialParity, vSerialDataBits, vSerialStopBits);
Thread.Sleep(3);
// 获取串口打开状态
vSerialState = vcSeaskyPort.vSerialIsOpen();
if (vSerialState == true)
{
vReceivedThread.Resume();
}
}
catch (Exception)
{
};
}
}
return vSerialState;
}
/// <summary>
/// 关闭串口。
/// </summary>
/// <returns></returns>
public bool vSerialClose()
{
vSerialState = false;
Thread.Sleep(10);
vReceivedThread.Suspend();
vcSeaskyPort.vSerialClose();
return (vcSeaskyPort.vSerialIsOpen() == false);
}