zoukankan      html  css  js  c++  java
  • 关于mfc作为上位机接收硬件端USB或串口数据显示成图片 解决串口接收数据丢字节丢包问题

    原文作者:aircraft

    原文地址:https://www.cnblogs.com/DOMLX/p/9490616.html

    本文用的是VS2013MFC写串口数据接收:

    第一步:首先建立一个MFC工程,成功后会跳出一个对话框,直接在对话框上点击右键-》点击插入ACTIVAE控件-》选择MicrosoftCommunications Control, version 6.0

    成功后会显示一个电话的图标在对话框上,运行起来不会显示的 不用担心这个美观问题。如果没有这个插件的话,可能是版本太低  可以自己下载一个补上

    第二步:大概的窗体搞好:   那个显示图片的大框是PICTURE控件变量

    然后就要项目->类向导中定义变量了  如果你们是英文版就找英文字符对应的就行了。(英文不会比我还差吧 哈哈哈哈哈哈哈)

    定义的变量大概如上图所示 ,那个小电话就是串口通信最重要的    变量ID是   IDC_MSCOMM1    变量名如上图:

    这时候简单的绑定变量后 要开始写第一个小函数了  ,直接双击那个对话框上 BUTTON1这个按钮

    这时候就会生成一个关联函数:代码如下:

    void CMFCApplication2Dlg::OnBnClickedButton1()
    {
        // TODO:  在此添加控件通知处理程序代码
        if (m_ctrlComm.get_PortOpen())
            m_ctrlComm.put_PortOpen(FALSE);
    
        m_ctrlComm.put_CommPort(4); //注意了  这里因为我硬件端接我电脑的口是com4  所以我打开一样的    你们不知道自己是哪个的话 就打开硬件经常用的串口调试助手先找找  不过 测试的时候一定要关闭其他串口  不然会打开失败的
        if (!m_ctrlComm.get_PortOpen())
        {
            m_ctrlComm.put_PortOpen(TRUE);//打开串口
            AfxMessageBox(L" open serial port successfully");
        }
        else
            AfxMessageBox(L"cannot open serial port");
    
        m_ctrlComm.put_Settings(L"9600,n,8,1"); //波特率9600,无校验,8个数据位,1个停止位
        m_ctrlComm.put_InputMode(1); //1:表示以二进制方式检取数据
        m_ctrlComm.put_RThreshold(1);
        //参数1表示每当串口接收缓冲区中有多于或等于1个字符时将引发一个接收数据的OnComm事件
        m_ctrlComm.put_InputLen(0); //设置当前接收区数据长度为0
        m_ctrlComm.get_Input();//先预读缓冲区以清除残留数据
    }

    好这是打开串口的函数 ,既然打开的串口那么硬件就要给我们发数据了 ,而mFC也要有接收的能力 所以这时候我们要添加一个 串口数据的响应函数:

    可以双击那个消息函数改名字在点  添加处理程序应用:

     然后插入代码:

    void CMFCApplication2Dlg::onComm()
    {
        // TODO:  在此处添加消息处理程序代码
        VARIANT variant_inp;
        COleSafeArray safearray_inp;
        LONG len, k;
        //BYTE rxdata[1000480];//设置BYTE数组 An 8-bit integerthat is not signed.
        unsigned char * data = m_COMBits + m_COMIndex;
        CString strtemp;
        //m_COMIndex = 0;
        if (m_ctrlComm.get_CommEvent() == 2) //事件值为2表示接收缓冲区内有字符
        {             ////////以下你可以根据自己的通信协议加入处理代码
            variant_inp = m_ctrlComm.get_Input(); //读缓冲区  
            safearray_inp = variant_inp; //VARIANT型变量转换为ColeSafeArray型变量  
            len = safearray_inp.GetOneDimSize(); //得到有效数据长度  注意了这里数据是一段一段接收的
            for (k = 0; k < len; ++k, ++m_COMIndex)
            {
                if (m_COMIndex > 240 * 320 - 1)
                    break;
                safearray_inp.GetElement(&k, data + k);//转换为BYTE型数组
            }
            if (m_COMIndex > 240 * 320 - 1)
            {
                m_COMIndex = 0;
                m_hasCOMImage = true;
                LoadImageData(m_COMImage, m_COMBits);
                OnPaint();
            }
        }
        //UpdateData(FALSE); //更新编辑框内容
    }

     这里是我的图片大小 240*320的:

    你们的自己看    至于为什么要大于后马上跳出循环呢   因为 接收数据是一段一段接收的从缓冲区  所以我们一次性接收够了我们就跳出来  要是一直接收肯定会炸的  不信可以自己试试哈哈哈哈哈哈

    还有这里有时候会出现一个问题,就是  串口传输数据的时候回丢包     有时候单步调试的时候却不会丢包 丢字节   STM32   单片机51都有可能出现这种情况  (串口调试助手收发大量数据时是怎样处理的新手求教,写了一个串口调试助手,接收数据会丢帧串口通讯,丢包严重是什么问题为什么串口单步调试正常,全速会丢包)这是因为因为CPU处理速度太快导致FIFO中数据早就被读完了,RBR为空,而后续的数据不能及时到达被MCU抛弃掉了。我加了一个延时就OK了   这里加延时 可以硬件端发送加  也可以MFC 中加  都可以反正  串口发送数据会丢包说白就是电脑跟不上  电脑垃圾    这时候我们就辅助一个延时函数 然程序停一下  慢点接  让缓冲区有点东西在接收

    下面是绘制图片调用的函数:

    第一个是 位图的数据操作辅助用的    第二是将图片数据LOAD  

    bool CMFCApplication2Dlg::InitalImage(CImage &image, int width, int height)
    {
        if (image.IsNull())
            image.Create(width, height, 8);
        else
        {
            if (width <= 0 || height <= 0)
                return false;
            else if (image.GetHeight() == width && image.GetWidth() == height)
                return true;
            else
            {
                image.Destroy();
                image.Create(width, height, 8);
            }
        }
        //写入调色板
        RGBQUAD ColorTable[256];
        image.GetColorTable(0, 256, ColorTable);
        for (int i = 0; i < 256; i++)
        {
            ColorTable[i].rgbBlue = (BYTE)i;
            ColorTable[i].rgbGreen = (BYTE)i;
            ColorTable[i].rgbRed = (BYTE)i;
        }
        image.SetColorTable(0, 256, ColorTable);
        return true;
    }
    void CMFCApplication2Dlg::LoadImageData(CImage &image, unsigned char * data)
    {
        if (data == nullptr)
            return;
        byte *pS;
        byte *pImg = (byte *)image.GetBits();
        int step = image.GetPitch();
        int height = image.GetHeight();
        int width = image.GetWidth();
        for (int i = 0; i < image.GetHeight(); ++i)
        {
            pS = data + i * width;
            for (int j = 0; j < image.GetWidth(); ++j)
            {
                *(pImg + i*step + j) = pS[j];
            }
        }
    }

     同样的 在Dlg头文件中药加上这两个函数的声明和用到变量的定义:

    //位图函数
        void CMFCApplication2Dlg::LoadImageData(CImage &image, unsigned char * data);
        bool CMFCApplication2Dlg::InitalImage(CImage &image, int width, int height);
        //位图数据
        unsigned char m_COMBits[240 * 320];
        int m_COMIndex;
        bool m_hasCOMImage;
        CImage m_COMImage;

    然后在看Dlg.cpp文件  我们还需要几个函数:

    第一  在Dlg::OnInitDialog()初始中 我们要先给图片变量分配内存 不然程序会中断:

    InitalImage(m_COMImage, 240, 320);
        m_COMIndex = 0;

    第二 要将图片绘制 就要绘制函数中操作Dlg::OnPaint():

    首先定义下面要用到的变量

        int cx, cy;
        CRect    rect;
        CWnd *pWnd = NULL;

    然后加入调用的代码:

    else
        {
            if (m_hasCOMImage && !m_COMImage.IsNull())
            {
                //获取图片的宽 高度
                cx = m_COMImage.GetWidth();
                cy = m_COMImage.GetHeight();
                //ResizeWindow(cx, cy);
                //获取IDC_PIC1的窗口指针
                pWnd = GetDlgItem(IDC_PICCOM);
                //获取Picture Control控件的大小
                pWnd->GetWindowRect(&rect);
                //将客户区选中到控件表示的矩形区域内
                ScreenToClient(&rect);
                //窗口移动到控件表示的区域
                pWnd->MoveWindow(rect.left, rect.top, cx, cy, TRUE);
                pWnd->GetClientRect(&rect);//获取句柄指向控件区域的大小
                CDC *pDc = NULL;
                pDc = pWnd->GetDC();//获取picture的DC
                m_COMImage.Draw(pDc->m_hDC, rect);//将图片绘制到picture表示的区域内
                ReleaseDC(pDc);
            }

    是在那个 onpaint():函数中的 else部分里面添加噢    给你们看看总体的  不要抄下面这个   用上面的就行了  复制下面这个也没用 因为对话框有不同:

    void CMFCApplication2Dlg::OnPaint()
    {
        int cx, cy;
        CRect    rect;
        CWnd *pWnd = NULL;
        if (IsIconic())
        {
            CPaintDC dc(this); // 用于绘制的设备上下文
    
            SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
    
            // 使图标在工作区矩形中居中
            int cxIcon = GetSystemMetrics(SM_CXICON);
            int cyIcon = GetSystemMetrics(SM_CYICON);
            CRect rect;
            GetClientRect(&rect);
            int x = (rect.Width() - cxIcon + 1) / 2;
            int y = (rect.Height() - cyIcon + 1) / 2;
    
            // 绘制图标
            dc.DrawIcon(x, y, m_hIcon);
        }
        else
        {
            if (m_hasCOMImage && !m_COMImage.IsNull())
            {
                //获取图片的宽 高度
                cx = m_COMImage.GetWidth();
                cy = m_COMImage.GetHeight();
                //ResizeWindow(cx, cy);
                //获取IDC_PIC1的窗口指针
                pWnd = GetDlgItem(IDC_PICCOM);
                //获取Picture Control控件的大小
                pWnd->GetWindowRect(&rect);
                //将客户区选中到控件表示的矩形区域内
                ScreenToClient(&rect);
                //窗口移动到控件表示的区域
                pWnd->MoveWindow(rect.left, rect.top, cx, cy, TRUE);
                pWnd->GetClientRect(&rect);//获取句柄指向控件区域的大小
                CDC *pDc = NULL;
                pDc = pWnd->GetDC();//获取picture的DC
                m_COMImage.Draw(pDc->m_hDC, rect);//将图片绘制到picture表示的区域内
                ReleaseDC(pDc);
            }
            //确认对话框数据中是否有一张完整的图像
            /*
            if (m_hasFileImage && !m_FileImage.IsNull())
            {
                //获取图片的宽 高度
                cx = m_FileImage.GetWidth();
                cy = m_FileImage.GetHeight();
                //ResizeWindow(cx, cy);
                //获取IDC_PIC1的窗口指针
                pWnd = GetDlgItem(IDC_PICFILE);
                //获取Picture Control控件的大小
                pWnd->GetWindowRect(&rect);
                //将客户区选中到控件表示的矩形区域内
                ScreenToClient(&rect);
                //窗口移动到控件表示的区域
                pWnd->MoveWindow(rect.left, rect.top, cx, cy, TRUE);
                pWnd->GetClientRect(&rect);//获取句柄指向控件区域的大小
                CDC *pDc = NULL;
                pDc = pWnd->GetDC();//获取picture的DC
                m_FileImage.Draw(pDc->m_hDC, rect);//将图片绘制到picture表示的区域内
                ReleaseDC(pDc);
            }
            */
            CDialogEx::OnPaint();
        }
    }

    要   清空缓冲 的数据的话    就这样双击那个按钮 加这个:

    void CMFCApplication2Dlg::OnBnClickedButton3()
    {
        // TODO:  在此添加控件通知处理程序代码
        m_COMIndex = 0;
    }

    好了这就是所有的代码了 ,语文不好可能需要一点MFC基础才能听得懂哈哈哈哈哈:

    不过给你们准备了福利嘿嘿嘿,我测试的项目代码:

    MFC做上位机与USB串口连接传输数据显示图像:链接:https://pan.baidu.com/s/1iQyeu50-2joZgp4xedGzpg 密码:bed9

    若有兴趣交流分享技术,可关注本人公众号,里面会不定期的分享各种编程教程,和共享源码,诸如研究分享关于c/c++,python,前端,后端,opencv,halcon,opengl,机器学习深度学习之类有关于基础编程,图像处理和机器视觉开发的知识

  • 相关阅读:
    SHELL
    Docker
    RHCE内容记要
    mysql基本知识的总结
    Linux启动提示Kernel panic
    配置tomcat、nginx实现反向代理(需操作)
    linux下nginx的安装和配置
    linux下安装mysql5.7(centos6.0)
    linux打包解压包(.tar .gz .tar.gz .zip)
    多重继承下的类型转换
  • 原文地址:https://www.cnblogs.com/DOMLX/p/9490616.html
Copyright © 2011-2022 走看看