vSeaskyPort 静态库&动态库

[!note] 基于 vSeaskyPort.dll 可以快速的开发高性能的 WPF串口上位机,这对于产品上位机将是一个不错的选择。在此提供 C# 调用的静态库&动态库,底层串口机制基于WIN原生的API接口实现,数据处理同样是基于C++,协议基于 Seasky协议,C# 本身比较适合写界面,但是不适合进行比较复杂的计算,在此将所有数据处理通过C++实现,同时封装好了通用接口,配合此静态库&动态库则可以快速的开发高性能的串口上位机,可以帮助你快速的开发高性能的串口上位机程序具有较大的学习性。

[!note] 实测使用USB虚拟串口转CAN可以满足2ms周期的通信频率,吊打C#自身的串口库,那么基于Wpf也可以做到和Qt性能差不太多的上位机,然而Wpf的开发周期可以说极短。

1. 将动态库添加到你的工程

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

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

3. 开始使用

在使用前需要先开启 unsafe 允许不安全的代码,否则 C# 将不支持指针

在添加动态库之后,在需要使用的地方引入 vSeaskyProtocol 命名空间

using vSeaskyProtocol;

[!kmdfoc] 初始化Seasky协议内存,Tx和Rx的不定长数据段(uint32_t)长度为24,具体的协议内容请查阅 Seasky协议 协议相关内容。 编写数据处理完成的函数回调 vSerialReceiveCallback (函数必须按规定格式) ,并传入初始化参数,C++内部每处理完一帧数据就会通过函数指针调用 vSerialReceiveCallback 以向 C# 提供解析完成的数据。 编写调试函数回调 vSerialDebugCallback(函数必须按规定格式) ,并传入初始化参数,C++内部数据处理出现错误,会调用此函数指针打印异常信息,必要的还需要传入信息等级,以过滤一些不重要的打印。

/// <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");
    //在此实现调试信息输出窗口
}

[!kmdfoc] 在此还需要初始化串口接收扫描,你需要提供一个线程循环调用 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();
    }
}

[!kmdfoc] 必要的为了节省性能,你可以在串口关闭时挂起线程,在串口打开后,再恢复线程

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

results matching ""

    No results matching ""