Sailor 通信协议 C# 静态库与动态库#

备注

基于 vSeaskyPort.dll 可快速开发高性能 WPF 串口上位机。提供 C# 可调用的静态库/动态库:底层串口为 Windows 原生 API,数据处理与协议为 C++(Seasky),C# 侧重界面,计算与协议封装在 C++ 中,便于开发高性能串口上位机。

备注

USB 虚拟串口转 CAN 实测可达 2ms 周期,优于 C# 自带串口库;基于 WPF 可实现接近 Qt 的性能且开发周期短。

将动态库添加到工程#

vseaskyport01.png vseaskyport02.png vseaskyport03.png vseaskyport04.png vseaskyport05.png vseaskyport06.png vseaskyport07.png vseaskyport08.png vseaskyport09.png vseaskyport10.png vseaskyport11.png vseaskyport12.png vseaskyport13.png vseaskyport14.png

最后在此编写了一个简单的示例程序,不包含完整程序,以供参考。

vseaskyport15.png

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);
}