精华内容
下载资源
问答
  • 上位机串口通讯通用测试程序,提供灵活的测试环境,对各种串通讯协议均适用。
  • 基于简化计算机与外部串行设备或其他计算机之间串口通信软件开发流程的目的,采用了依据串口通信原理使用LABVIEW作为上位机程序开发平台的方法,运用图形化程序语言搭建上位机串口通信监控界面。通过由虚拟串口通信...
  • 1.Grbl上位机串口控制程序:带坐标解析反馈、单轴运动控制调试、串口指令输入; 2.完整且经典的串口编程(非MSCOMM32.OCX ActiveX控件),学习方便; 3.VS2015 x64 系统win10编译通过;
  • 51单片机与上位机串口通信程序设计,串行通讯必读
  • 51单片机与上位机串口通讯,通过上位机发送ASCII给51单片机,51单片机数码管显示ASCCII值(包括课程设计文档,上位机和51单片机的源码,可直接使用)
  • 串口通讯程序,单片机与上位机的通讯,c语言源代码,发送字符或者字符串
  • c#上位机串口通讯控制stm32步进电机

    热门讨论 2015-05-24 20:38:13
    本历程用的是VS2012程序编写,用c#编程,本包为工程压缩包,非exe执行文件,历程实现串口实时控制stm32控制步进电机正转翻转,转动速度的控制,还有可固定角度转动,每一句的程序后均有解释语句,适合c#串口通信入门...
  • 实现单片机与上位机的通信程序实例,实例中包含了接收和发送的通信子程序,希望对你有所帮助!
  • 使用RS232向单片机发送指令,用于控制两个步进电机的运动,内含反解程序。本用于Omni Wrist并联平台。
  • NEC 78k0 串口上位机VB通讯程序包括通讯时各个标志位等的设置
  • 最近因为项目需要,需要做一个STM32和Windows的串口通讯协议来交换数据,本着最求极致的心态,来讨论一下简明的数据帧的设计方法。数据的传输方式对于很多单片机初学者而言,可能他们接触到串口 首先想到的就是通过...

    最近因为项目需要,需要做一个STM32和Windows的串口通讯协议来交换数据,本着追求极致的心态,来讨论一下简明的数据帧的设计方法。

    数据的传输方式

    对于很多单片机初学者而言,可能他们接触到串口 首先想到的就是通过串口打印字符串,然后就会很理所当然的想到了用“打印”的方式来传输数据。
    比如我们需要传输一个float型的数据value,可能小白们首先会想到的方式就是串口重定向printf然后像下面这样把数据传输过去

    printf("%f",&value);
    

    但是实际上,对于程序之间的交流,使用字符串打印这样的方式是非常浪费传输数据的,因为假设这个浮点数据为1234.567占了8个字符(小数点也占一位) 所以用字符串传输的方式就需要通过串口传输8个字节,但是实际上float类型在内存里面永远都是占用4个字节,所以使用字符串的传输效率是非常低的,并且这样传输 上位机收到的也是字符串,还得把字符串的数据读出来重新放到一个float类型的数据里面,才可以进行运算。

    因此,通过串口传输的数据基本上都是通过直接传输内存数据来实现的。
    它的原理大概是这样 同样以float类型作例子
    当你在程序里面声明了一个float类型的变量的时候,程序会为这个变量开辟一个4字节的内存空间,然后这4个字节的内存数据唯一决定了这个float类型的数据(具体实现方式可以百度float类型的存储方式)。因此,我们的串口只要把这4个字节的数据通过串口传输到上位机,然后上位机根据传输协议直接把这4个字节写入到一个float类型的内存空间中,然后这个float类型的数据也就随之完成了。
    为了方便这个过程的实现 我们可以定义一个联合体

    typedef union{
    	float value;
    	unsigned char sendbuf[4];
    }send_type;
    

    这样定义的好处就是:联合体内的所有成员共用一片内存空间
    然后我们需要传输数据float型数据的时候,我们只需要声明一个send_type类型,然后对send_type的value直接赋值,然后与此同时sendbuf的数据也会因此改变,我们就可以直接发送sendbuf[4]这四个字节了。建议在上位机的程序里面也使用同样的联合体,然后当上位机的snedbuf[4]放入接收的4个字节之后,value就可以直接读出所需要的数据了。然后对于其他各种数据类型,如double,long int之类的类型同样也可以使用这种方式来传输,只需要注意好各种数据类型需要占用的内存空间就可以了

    组合数据的传输方式的注意事项

    通过上面的方式,我们通过传输内存数据的方式来传输单个数据了,那么当我们需要传输多个数据呢?
    或者说,传输的并不是直接的数据,而是带有一定的开头,结尾的数据,假如协议规定数据起始要先发一个字节的标志0x0d,然后传输两个float型数据,再以结束标识符0x0d结尾
    (此处0x0d是人为规定,协议可以要求为其他值)
    这时候只考虑联合体的话并不能解决我们的问题
    这时候结构体就派上用场了

    typedef struct{
    unsigned char   head;
    float	 		send1;
    float	 		sned2;
    unsigned char	end;
    }sned_frame;
    

    像这样我们就可以按照顺序把协议整合成一个结构体的形式,编译器会根据结构体内成员顺序在内存里按顺序分配内存空间,上位机和单片机共用同一种结构体形式,然后只需要设定好帧内各成员内容之后,把结构体直接发过去,好像就可以了??

    其实问题并没有想的这么简单。结构体虽然会占用一片连续的内存空间,但是实际上结构体内的成员并不一定是连续分配内存空间的。可以实践,刚刚声明的结构体,它并不是只占用1+4+4+1=10个字节的内存空间。为什么会这样呢?因为编译器在分配内存时会按照内存对齐的方式来分配内存,因此前面的unsigned char变量为了和float对齐,编译器为其分配了4个字节的空间。这会导致我们协议的帧长度增加一些没有意义的空的数据,并且判断帧长度将变得复杂
    (当然,其实如果上位机和stm32的内存对齐的方式恰好是一样的话,只要保证把整个结构体传输过去,整个通信应该还是可以完成的)
    那么如何取消编译器的内存自动对齐呢?
    我们可以在结构体定义后紧跟一句
    attribute ((packed))

    这一句的意义是将该定义的内存分配强行按最小位对齐(也就是按字节对齐)。
    那么我们修改后的结构体定义是这样的:

    typedef struct{
    unsigned char   head;
    float	 		send1;
    float	 		sned2;
    unsigned char	end;
    }_attribute__ ((__packed__)) sned_frame;
    

    可以实践,对现在的结构体使用sizeof关键字,得到的的长度为10。这样就可以压缩帧的长度,避免数据浪费了。在需要改动协议的时候,也只需要简单的增加结构体的成员即可。
    /-----------------------------------------------------------------------------------------------------------
    2018年6月4日11:26:30后记:

    在STM32的开发中,如果使用了硬件浮点数解算的话,同时又存在串口发送浮点型数据的话,此时最好不要压缩数据帧,按照编译器的自动补充空字节来发送,上位机也保持一样的数据包.因为硬件浮点解算涉及到了内部的专用电路,而此时如果浮点数没有内存对齐的话是无法使用硬件解算从而出现错误的.这时候会带来不必要的麻烦. 当然 如果需要和性能受限制的设备进行通信,不舍得填充空数据的话,在涉及内存不对齐的浮点数据可以先开一个临时变量进行操作,在进行发送的前一刻使用memcpy函数将浮点数放进数据帧内也是可以的

    展开全文
  • 串口通讯上位机可以发送两个命令给单片机:“QRV+#换行符”查询电压,“QRC+#换行符”查询电流。 每秒钟各发送一次查询命令。 我想把查询到的电压值放在“文本框1”显示,电流值放到“文本框2”显示。 [img=...
  • 基于VB实现上位机与FPGA的串口通讯程序设计.pdf
  • 为什么我用mfc写的上位机串口通讯程序每次打开只能接收一次数据,想要接收下一次的数据只能重新打开串口,小白求问大神们,用的是microsoft communications control 6.0控件。
  • 上位机所编写的VB程序联调已经通过,可以直接使用。
  • 此为本人工作中的辅助程序, 其中主要是把串口接收的指令与数据拆分并执行,程序附带Excel电子表格数据的导入导出功能,编程工具:vs2010 winform
  • 51单片机与上位机串口通讯

    千次阅读 2020-08-08 11:18:32
    实现功能:通过USB转串口与电脑进行串口通讯,实现上位机控制LED的功能。 1.单片机内烧入程序 #include <reg52.h> #include <string.h> #define uc unsigned char #define uint

    先上图

    电路连接实拍
    如图所示:使用12c052AD,晶振使用11.0592M,晶振电容使用20pf,使用USB使串口与电脑连接,LED灯正极接P1,加限流电阻

    注意:使用12M的晶振会出现大概率的乱码。也不能使用单片机的内部晶振。

    实现功能:通过USB转串口与电脑进行串口通讯,实现上位机控制LED的功能。

    1.单片机内烧入程序

    #include <reg52.h>
    #include <string.h>  
    #define uc unsigned char   
    #define uint unsigned int
    
    sbit LED=P3^7;      
    uc sChar[50]={'\0'};
    unsigned char sChar_i=0;//写入数据sChar指针
    unsigned char  sendFlag = 0;	//未发送数据时
    unsigned char  receFlag =0;		//未接受到数据时  
     
    void sendChar(unsigned char sendValue);  //发送一字节数据
    void sendAll(unsigned char *pValue);       //发送一组数据
          
    void initSer()//初始化  
    {  
         TMOD=0x20;    //定时器工作方式,选择了定时器1,工作方式2 八位初值自动重装的8位定时器。         
         TH1=0xfd;     //定时器1初值  ,设置波特率为9600 配合为晶振11.0529MHZ,晶振的电容是20uf?  
         TL1=0xfd;  
         TR1=1;        //开启定时器1  
       
         SM0=0;  	   //属于SCON寄存器
         SM1=1;        //串口工作方式1,10位异步接收,(8位数据)波特率可变  
         REN=1;        //允许串行口接收位  
         EA=1;         //允许中断(总闸)  
         ES=1;         //允许串口中断 
    }  
    void main()  
    {  
         initSer();  
         while(1)  
    	{  
    		if(receFlag)  
    		{
    			LED=~LED;
    			sChar_i=0;//接受数组指针归0,以便以下次接受
    			sendAll(sChar); 
    			if(strlen(sChar)==4&&sChar[0]=='L'&&sChar[1]=='E'&&sChar[2]=='D')
    			{
    				 P1=sChar[3];
    			}      	
    			receFlag=0;  
         	}  
         	if(sendFlag)                          //发送完毕之后,在电脑端输出。  
         	{  
             	TI=1;                             //printf之前必须将T1置为1才行。  
             	while(!TI);
             	sendFlag=0;  
         	}     
    	}  
    }
    
     
    void sendChar(unsigned char Value)			//发送一个字节数据
    {
    	 SBUF = Value;     
    	 sendFlag = 1;							//设置发送标志位,发一字节就置位
    	 while(sendFlag);						//直到发完数据,将sendFlag清零后,才退出sendChar函数
    }
    
    void sendAll(unsigned char *pValue)			//发送一组数据
    {
    	while((*pValue) != '\0')   				//如果没有发送完毕就继续发
    	{
    		sendChar(*pValue);      			//发送1字节数据
    		pValue++;         		  			//指向下1个字节
    	}
    }   
    void serInt() interrupt 4  //中断函数
    {  
    	//TI——发送中断bai标志位,可寻址标志位。方式0时,发送完第8位数据后,由硬件置位,其它方式下,在发送或停止位之前由硬件置位,因此,TI=1表示帧发送结束,TI可由软件清“0”。
    	//RI——接收中断标志位.可寻址标志位。接收完第8位数据后,该位由硬件置位,在其他工作方式下,该位由硬件置位,RI=1表示帧接收完成。
    	//在串口中断处理时,TI,RI都需要软件清"0",硬件置位后不可能自动清0,此外,在进行缓冲区操作时,需要ES=0,以防止中断出现。
    	 if(RI)       							//接收数据,手动将RI清0  
         {         
    		RI=0;
    		if(SBUF=='\0'||SBUF=='#')
    		{
    			sChar[sChar_i++]='\0';
    			receFlag=1;							//修改接受标志,便于主函数进入while中发数据
    		}
    		else
    		{
    			sChar[sChar_i++]=SBUF;				//每次接受8位,存在SBUF里,转存到数组中
    		}
         }  
         if(TI)     							//发送数据  
         {
    		TI = 0;								//发送完一个数据
    		sendFlag = 0;						//清标志位 
         }      
    }  

    2.使用Visual Studio制作上位机程序:

    软件界面如下:

    双击标题栏进入程序编辑界面。代码如下:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    
    namespace 串口通讯
    {
    
        public partial class Form1 : Form
        {
            string PortNameCopy;//记录打开哪个端口
            byte[] UsartReadBuff=new byte[2048];//串口接受的数据
            int UsartReadCnt = 0;//串口接收到的数据个数
            public Form1()
            {
                InitializeComponent();
            }
            
    
            /// <字节数组转16进制字符串>
            /// <param name="bytes"></param>
            /// <returns> String 16进制显示形式</returns>
            public static string byteToHexStr(byte[] bytes)
            {
                string returnStr = "";
                try
                {
                    if (bytes != null)
                    {
                        for (int i = 0; i < bytes.Length; i++)
                        {
                            returnStr += bytes[i].ToString("X2");
                            returnStr += " ";//两个16进制用空格隔开,方便看数据
                        }
                    }
                    return returnStr;
                }
                catch (Exception)
                {
                    return returnStr;
                }
            }
            private void Form1_Load(object sender, EventArgs e)
            {
                string[] ports = System.IO.Ports.SerialPort.GetPortNames();//获取电脑上可用的串口号
                comboBox_ckxz.Items.AddRange(ports);//给combox_ckxz添加可用串口号
                comboBox_ckxz.SelectedIndex = comboBox_ckxz.Items.Count > 0 ? 0 : -1;//如果有可用数据,显示第一个
                PortNameCopy = comboBox_ckxz.SelectedValue==null?"": comboBox_ckxz.SelectedValue.ToString();
            }
    
            private void button_ljzt_Click(object sender, EventArgs e)
            {
                if (button_ljzt.Text == "连接")//如果按钮显示的是打开
                {
                    try//防止意外错误
                    {
                        serialPort_ckxz.PortName = comboBox_ckxz.Text;//得到comboBox_ckxz显示的串口内容
                        serialPort_ckxz.BaudRate = Convert.ToInt32(comboBox_btl.Text);//得到comboBox2显示的波特率内容
                        serialPort_ckxz.Open();//打开串口
                        serialPort_ckxz.DataReceived += serialPort_ckxz_DataReceived;//注册传回事件
                        button_ljzt.Text = "断开";//按钮显示断开
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("打开失败, 提示!"+ ex.Message);//对话框显示打开失败
                    }
                }
                else//要关闭串口
                {
                    try//预防串口有问题了,实际上已经关了
                    {
                        serialPort_ckxz.Close();//关闭串口
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("关闭串口时发生错误" + ex.Message);//对话框显示打开失败
                    }
                    button_ljzt.Text = "连接";//按钮显示连接
                }
            }
            /// <summary>
            /// 支持热插拔
            /// </summary>
            /// <param name="m"></param>
            protected override void WndProc(ref Message m)
            {
                if (m.Msg == 0x0219)//设备改变
                {
                    if (m.WParam.ToInt32() == 0x8004)//usb串口拔出
                    {
                        string[] ports = System.IO.Ports.SerialPort.GetPortNames();//重新获取串口
                        comboBox_ckxz.Items.Clear();
                        comboBox_ckxz.Items.AddRange(ports);
                        if (button_ljzt.Text == "断开")//咱打开过一个串口
                        {
                            if (!serialPort_ckxz.IsOpen)//咱打开的那个关闭了,说明拔插的是咱打开的
                            {
                                button_ljzt.Text = "连接";
                                serialPort_ckxz.Dispose();//释放掉原先的串口资源
                                comboBox_ckxz.SelectedIndex = comboBox_ckxz.Items.Count > 0 ? 0 : -1;//显示获取的第一个串口号
                            }
                            else//热插拔不是咱打开的那个
                            {
                                comboBox_ckxz.Text = PortNameCopy;//默认显示的是咱打开的那个串口号
                            }
                        }
                        else//没有打开过
                        {
                            comboBox_ckxz.SelectedIndex = comboBox_ckxz.Items.Count > 0 ? 0 : -1;//显示获取的第一个串口号
                        }
                    }
                    else if (m.WParam.ToInt32() == 0x8000)//usb串口连接上
                    {
                        string[] ports = System.IO.Ports.SerialPort.GetPortNames();//重新获取串口
                        comboBox_ckxz.Items.Clear();
                        comboBox_ckxz.Items.AddRange(ports);
                        if (button_ljzt.Text == "断开")//咱打开过一个串口
                        {
                            comboBox_ckxz.Text = PortNameCopy;//默认显示的是咱打开的那个串口号
                        }
                        else
                        {
                            comboBox_ckxz.SelectedIndex = comboBox_ckxz.Items.Count > 0 ? 0 : -1;//显示获取的第一个串口号
                        }
                    }
                }
                base.WndProc(ref m);
            }
            /// <summary>
            /// LED控制
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void button_dd_Click(object sender, EventArgs e)
            {
                try
                {
                    if(button_dd.Text=="亮灯")
                    {
                        byte[] sendbyte = new byte[3];
                        sendbyte[0] = 0xaa;
                        sendbyte[1] = 0x55;
                        sendbyte[2] = 0x01;
                        serialPort_ckxz.Write(sendbyte, 0, sendbyte.Length);
                    }
                    else if(button_dd.Text=="熄灯")
                    {
                        byte[] sendbyte = new byte[3];
                        sendbyte[0] = 0xaa;
                        sendbyte[1] = 0x55;
                        sendbyte[2] = 0x00;
                        serialPort_ckxz.Write(sendbyte, 0, sendbyte.Length);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("控制LED时发生错误" + ex.Message);//对话框显示打开失败
                }
            }
    
            /// <summary>
            /// 串口接收到数据就会进入,需要在激活串口时,注册回传事件 serialPort_ckxz.DataReceived += serialPort_ckxz_DataReceived;,serialPort的DtrEnable,RtsEnable设置为true
            /// 数据接收可能会中断,主要原因是没bai有接受到一个完整du数据周期,例如你下位机是一个单bai片机 单片机发送 01 02 03 04 05 06 07 08 09 是一个完整du数据周期,但是DataReceived事件可能在一次数据接收中只接受到了 01 02 03 04 05 06 因此造成数据不完整,正确的做法是在数据头部以及尾部设置校验,例如我完整的数据包可以封装为 AA 01 02 03 04 05 06 07 08 09 BB,当接收到AA我认为数据开始接收 只有当接收到BB 我才认为数据接收完毕
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void serialPort_ckxz_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
            {
                int len = serialPort_ckxz.BytesToRead;//获取可以读取的字节数
                if (len > 0)
                {
                    byte[] recvBytes = new byte[len];//创建接收的数组
                    serialPort_ckxz.Read(recvBytes, 0, len);//接收数据
    
                    Invoke((new Action(() =>//显示字符串
                    {
                        textBox_js.AppendText("字符串:" + Encoding.Default.GetString(recvBytes)); //显示在文本框里面
                    })));
    
                    Invoke((new Action(() =>//显示16进制
                    {
                        textBox_js.AppendText("\r\n16进制:" + byteToHexStr(recvBytes) + "\r\n"); //显示在文本框里面
                    })));
    
                    //for (int i = 0; i < len; i++)//拷贝数据到UsartReadBuff
                    //{
    
                    //    UsartReadBuff[i + UsartReadCnt] = recvBytes[i];//从上次的地方接着填入数据
                    //}
                    //UsartReadCnt = UsartReadCnt + len;//记录上次的数据个数
                    //if (UsartReadCnt >= 3)//接收到可以处理的数据个数
                    //{
                    //    UsartReadCnt = 0;
                    //    if (UsartReadBuff[0] == 0xaa && UsartReadBuff[1] == 0x55)//判断数据
                    //    {
                    //        if (UsartReadBuff[2] == 0x01)//
                    //        {
                    //            Invoke((new Action(() =>
                    //            {
                    //                button_dd.Text = "熄灭";
                    //                label_zt.Text = "点亮";
                    //            })));
                    //        }
                    //        else if (UsartReadBuff[2] == 0x00)
                    //        {
                    //            Invoke((new Action(() =>
                    //            {
                    //                button_dd.Text = "点亮";
                    //                label_zt.Text = "熄灭";
                    //            })));
                    //        }
                    //    }
                    //}
                }
            }
            /// <summary>
            /// 发送字符串到串口
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void button_fs_Click(object sender, EventArgs e)
            {
                try
                {
                    if(textBox_fs.Text.ToString().Length>0)
                    {
                        serialPort_ckxz.Write(textBox_fs.Text.ToString());
                    }
                }
                catch(Exception ex)
                {
                    MessageBox.Show("发送失败,请检查串口是否连接"+ex.Message);
                    button_ljzt.Text = "连接";
                }
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                try
                {
                    byte[] sendbyte = new byte[5];
                    sendbyte[0] = 0x4C;
                    sendbyte[1] = 0x45;
                    sendbyte[2] = 0x44;
                    sendbyte[3] = 0x01;
                    sendbyte[4] = 0x00;
                    for (int i = 0; i < 100; i++)
                    {
                        serialPort_ckxz.Write(sendbyte, 0, sendbyte.Length);
                        System.Threading.Thread.Sleep(100);//延时100毫秒
                        //sendbyte[3] = (byte)(sendbyte[3] >> (8 - (1 % 8)));
                        unchecked
                        {
                            sendbyte[3] = (byte)((sendbyte[3] << 1)| (sendbyte[3] >>7));
    
                        }
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("发送失败,请检查串口是否连接" + ex.Message);
                    button_ljzt.Text = "连接";
                }
    
            }
        }
    }
    

     

    展开全文
  • 串口通讯程序

    2012-11-12 17:07:31
    avr单片机与上位机串口通信程序,atmegal 16
  • 上位机串口通信

    千次阅读 2016-01-20 10:30:31
    上位机与下位机通过RS232协议通信,现场没有硬件环境,通过软件模拟通信过程。 软件工具 Virtual Serial Port Driver (VSPD) :模拟串口驱动工具 如下图:添加了COM3-COM4和COM5-COM6,添加时成对添加,该对串口间...

    背景

    上位机与下位机通过RS232协议通信,现场没有硬件环境,通过软件模拟通信过程。

    软件工具

    Virtual Serial Port Driver (VSPD) :模拟串口驱动工具
    如下图:添加了COM3-COM4和COM5-COM6,添加时成对添加,该对串口间进行通信,COM3与COM4连接通信,COM5与COM6通信,
    若想自定义连接线,在Custom pinout中修改,具体可参考帮助文件。



    Serial Port Utlity:串口调试助手
    如下图:设置波特率,数据位,校验位,停止位即可



    程序流程

    建立CSerialPort类对象,打开串口,发送/接收数据,关闭串口

    CSerialPort类

     

    #pragma once
    
    #include <windows.h>
    
    /*
    SerialPort.h
    封装了串口通讯的WindowsAPI,支持异步操作串口
    */
    class CSerialPort
    {
    public:
    	CSerialPort(void);
    	~CSerialPort(void);
    
    	bool Open(int nPort = 2, int nBaud = 115200);
    
    	bool Close(void);
    
    	// 从串口读数据
    	int ReadData(void *, int);
    
    	// 向串口写数据
    	bool WriteCommByte(unsigned char*, int); 
    
    	// 查询缓冲区是否有未读取的数据
    	int ReadDataWaiting(void);
    
    	inline bool IsOpen(void) { return m_bOpened; }
    protected:
    	// 内部实现,向串口写数据
    	bool WriteCommByte(unsigned char);
    	
    	HANDLE m_hIDComDev;
    	OVERLAPPED m_OverLappedRead;
    	OVERLAPPED m_OverLappedWrite;
    
    	bool m_bOpened;
    };


     

     

     
     
    #include "SerialPort.h"
    
    #define BUFFER_INPUT_RECOMMENT 10240
    #define BUFFER_OUTPUT_RECOMMENT 10240
    
    // 不使用读超时
    #define TIMEOUT_READ_INTERVAL         0xFFFFFFFF
    
    // 读超时
    #define TIMEOUT_READ_TOTAL_MULTIPLIER 0
    #define TIMEOUT_READ_TOTAL_CONSTANT   0
    
    // 写超时为秒
    #define TIMEOUT_WRITE_TOTAL_MULTIPLIER 0
    #define TIMEOUT_WRITE_TOTAL_CONSTANT   5000
    
    // 异步读取/写入操作时等待事件的超时时间
    #define TIMEOUT_READCOMM_EVENT 4000
    #define TIMEOUT_WRITECOMM_EVENT 2000
    
    // 一些通讯协议使用的宏
    #define FC_DTRDSR	0x01
    #define FC_RTSCTS	0x02
    #define FC_XONXOFF	0x04
    #define ASSII_BEL	0x07
    #define ASSII_BS	0x08
    #define ASSII_LF	0x0A
    #define ASSII_CR	0x0D
    #define ASSII_XON	0x11
    #define ASSII_XOFF	0x13
    
    CSerialPort::CSerialPort(void) : m_hIDComDev(NULL), m_bOpened(false)
    {
    	memset(&m_OverLappedRead, 0x00, sizeof(OVERLAPPED));
    	memset(&m_OverLappedWrite, 0x00, sizeof(OVERLAPPED));
    }
    
    
    CSerialPort::~CSerialPort(void)
    {
    	Close();
    }
    
    // 打开串口
    bool CSerialPort::Open(int nPort, int nBaud)
    {
    	if (m_bOpened)
    	{
    		return true;
    	}
    
    	char szPort[50] = { 0 };
    	char szComParams[50] = { 0 };
    	DCB dcb;
    	sprintf_s(szPort, 50, "COM%d", nPort);
    
    	// API:建立文件,Windows中将串口设备当做文件对待
    	m_hIDComDev = CreateFile(
    							szPort,
    							GENERIC_READ | GENERIC_WRITE,
    							0,
    							NULL,
    							OPEN_EXISTING,
    							FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, // 异步读写
    							NULL);
    
    	if (m_hIDComDev == NULL)
    	{
    		return false;
    	}
    
    	memset(&m_OverLappedRead, 0x00, sizeof(OVERLAPPED));
    	memset(&m_OverLappedWrite, 0x00, sizeof(OVERLAPPED));
    
    	// 设置超时
    	COMMTIMEOUTS CommTimeOuts;
    	CommTimeOuts.ReadIntervalTimeout = TIMEOUT_READ_INTERVAL;
    	CommTimeOuts.ReadTotalTimeoutMultiplier = TIMEOUT_READ_TOTAL_MULTIPLIER;
    	CommTimeOuts.ReadTotalTimeoutConstant = TIMEOUT_READ_TOTAL_CONSTANT;
    	CommTimeOuts.WriteTotalTimeoutMultiplier = TIMEOUT_WRITE_TOTAL_MULTIPLIER;
    	CommTimeOuts.WriteTotalTimeoutConstant = TIMEOUT_WRITE_TOTAL_CONSTANT;
    	
    	SetCommTimeouts(m_hIDComDev, &CommTimeOuts);
    
    	sprintf_s(szComParams, 50, "COM%d:%d, n, 8, 1", nPort, nBaud);
    
    	// 设置异步读取/写入监视事件
    	m_OverLappedRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    	
    	m_OverLappedWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    
    	// 读取/设置串口设备参数
    	dcb.DCBlength = sizeof(DCB);
    	GetCommState(m_hIDComDev, &dcb);
    	dcb.BaudRate = nBaud;
    	dcb.ByteSize = 8;
    
    	unsigned char ucSet;
    	ucSet = (unsigned char)((FC_RTSCTS & FC_DTRDSR) != 0);
    	ucSet = (unsigned char)((FC_RTSCTS & FC_RTSCTS) != 0);
    	ucSet = (unsigned char)((FC_RTSCTS & FC_XONXOFF) != 0);
    
    	if (!SetCommState(m_hIDComDev, &dcb) 
    		|| !SetupComm(m_hIDComDev, BUFFER_INPUT_RECOMMENT, BUFFER_OUTPUT_RECOMMENT) 
    		|| m_OverLappedRead.hEvent == NULL
    		|| m_OverLappedWrite.hEvent == NULL)
    	{
    		DWORD dwError = GetLastError();
    
    		if (m_OverLappedRead.hEvent != NULL)
    		{
    			CloseHandle(m_OverLappedRead.hEvent);
    		}
    		if(m_OverLappedWrite.hEvent != NULL)
    		{
    			CloseHandle(m_OverLappedWrite.hEvent);
    		}
    		CloseHandle(m_hIDComDev);
    		return false;
    	}
    
    	m_bOpened = true;
    	return m_bOpened;
    }
    
    // 关闭串口
    bool CSerialPort::Close(void)
    {
    	if (!m_bOpened || m_hIDComDev == NULL)
    	{
    		return true;
    	}
    
    	if (m_OverLappedRead.hEvent != NULL)
    	{
    		CloseHandle(m_OverLappedRead.hEvent);
    	}
    	if(m_OverLappedWrite.hEvent != NULL)
    	{
    		CloseHandle(m_OverLappedWrite.hEvent);
    	}
    	CloseHandle(m_hIDComDev);
    
    	m_bOpened = false;
    	m_hIDComDev = NULL;
    
    	return true;
    }
    
    // 向串口写数据,类内部使用
    bool CSerialPort::WriteCommByte(unsigned char ucData)
    {
    	BOOL bWriteStat;
    	DWORD dwBytesWritten;
    
    	bWriteStat = WriteFile(m_hIDComDev, (unsigned char*)&ucData, 1, &dwBytesWritten, &m_OverLappedWrite);
    
    	// 查询异步写入是否完成,未完成则挂起等待
    	if (!bWriteStat && (GetLastError() == ERROR_IO_PENDING))
    	{
    		if (WaitForSingleObject(m_OverLappedWrite.hEvent, TIMEOUT_WRITECOMM_EVENT))
    		{
    			dwBytesWritten = 0;
    		}
    		else
    		{
    			GetOverlappedResult(m_hIDComDev, &m_OverLappedWrite, &dwBytesWritten, false);
    			m_OverLappedWrite.Offset += dwBytesWritten;
    		}
    	}
    
    	return true;
    }
    
    // 向串口写数据
    bool CSerialPort::WriteCommByte(unsigned char * pData, int nLen)
    {
    	BOOL bWriteStat;
    	DWORD dwBytesWritten;
    
    	CLogFile::WriteLog(pData, nLen);
    
    	bWriteStat = WriteFile(m_hIDComDev, pData, nLen, &dwBytesWritten, &m_OverLappedWrite);
    
    	// 查询异步写入是否完成,未完成则挂起等待
    	if (!bWriteStat && (GetLastError() == ERROR_IO_PENDING))
    	{
    		if (WaitForSingleObject(m_OverLappedWrite.hEvent, TIMEOUT_WRITECOMM_EVENT))
    		{
    			dwBytesWritten = 0;
    		}
    		else
    		{
    			GetOverlappedResult(m_hIDComDev, &m_OverLappedWrite, &dwBytesWritten, false);
    			m_OverLappedWrite.Offset += dwBytesWritten;
    		}
    	}
    
    	return true;
    }
    
    // 查询接受缓冲区内是否有数据(只查询,不读取)
    int CSerialPort::ReadDataWaiting(void)
    {
    	if (!m_bOpened || m_hIDComDev == NULL)
    	{
    		return 0;
    	}
    
    	DWORD dwErrorFlags;
    	COMSTAT ComStat;
    	ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat);
    	return (int)ComStat.cbInQue;
    }
    
    // 读取来自串口的数据
    int CSerialPort::ReadData(void* buffer, int limit)
    {
    	if (!m_bOpened || m_hIDComDev == NULL)
    	{
    		return 0;
    	}
    	BOOL bReadStatus;
    	DWORD dwBytesRead, dwErrorFlags;
    	COMSTAT ComStat;
    
    	// 读取之前必须清楚错误信息
    	ClearCommError(m_hIDComDev, &dwErrorFlags, &ComStat);
    
    	if (!ComStat.cbInQue)
    	{
    		return 0;
    	}
    
    	dwBytesRead = (DWORD) ComStat.cbInQue;
    	
    	if (limit < (int)dwBytesRead)
    	{
    		dwBytesRead = (DWORD)limit;
    	}
    
    	bReadStatus = ReadFile(m_hIDComDev, buffer, dwBytesRead, &dwBytesRead, &m_OverLappedRead);
    
    	// 查询异步读取是否完成,未完成则挂起等待
    	if (!bReadStatus)
    	{
    		if (GetLastError() == ERROR_IO_PENDING)
    		{
    			WaitForSingleObject(m_OverLappedRead.hEvent, TIMEOUT_READCOMM_EVENT);
    			return (int)dwBytesRead;
    		}
    		return 0;
    	}
    
    	DealReceiveData((unsigned char*)buffer, nCount);
    
    	return (int)nCount;
    }
    
    
    void CSerialPort::DealReceiveData(unsigned char* szData, int nLen)
    {
    <span style="white-space:pre">	</span>// 处理数据
    }
    

    使用示例

    #include "SerialPort.h"
    #include <Windows.h>
    
    DWORD WINAPI ThreadProc(LPVOID lpParam)
    {
    	CSerialPort SerialPort;
    	SerialPort.Open(6);
    	unsigned char szBuffer[MAX_PATH] = { 0 };
    	while (1)
    	{
    		int nLen = SerialPort.ReadData(szBuffer, MAX_PATH);
    		if (nLen)
    		{
    			for (int i = 0; i < nLen; i++)
    			{
    				printf("0x%02x ", szBuffer[i]);
    			}
    			printf("\n", szBuffer);
    			unsigned char* p = szBuffer;
    			do 
    			{
    				unsigned short usCmdType = 0;
    				unsigned short usLen     = 0;
    				
    				memcpy(&usCmdType, p + 7, 2);
    				memcpy(&usLen, p + 4, 2);
    
    				printf("%04x\n", usCmdType);
    				switch(usCmdType)
    				{
    				case 0x000A:
    					{
    						unsigned char szData[] = { 0x00, 0x00, 0x0c, 0x00, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd3};
    						SerialPort.WriteCommByte(szData, 12)<span style="font-family:Arial, Helvetica, sans-serif;">;</span>
    					}
    					break;
    				case 0x0002:
    					{
    						unsigned char szData[] = { 0x00, 0x00, 0x0c, 0x00, 0x0d, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcb};
    						SerialPort.WriteCommByte(szData, 12);
    					}
    					break;
    
    				}
    				p += usLen + 4;
    				nLen -= usLen + 4;
    			} while (nLen);
    
    			memset(szBuffer, 0x00, MAX_PATH);
    		}
    	}
    	SerialPort.Close();
    	return 0;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	DWORD threadID;
    	HANDLE hThread = CreateThread(NULL,0,ThreadProc,NULL,0,&threadID); // 创建线程
    	WaitForSingleObject(hThread,INFINITE);
    	CloseHandle(hThread); // 关闭内核对象
    	return 0;
    }
    该示例是之前一个项目的Demo程序,但是已经包含串口发送和接收数据了。




    展开全文
  • Python开发串口通讯上位机程序三部曲 ** 序 2020很特殊!这个春节假期,除了吃饭、睡觉、追剧、忧国忧民以外,仍然有大把的闲暇时间想想工作的事情。记得之前在很多项目开发中,都需要通过上位机来控制ECU或者通过...

    **

    Python开发串口通讯上位机程序三部曲

    **

    2020很特殊!这个春节假期,除了吃饭、睡觉、追剧、忧国忧民以外,仍然有大把的闲暇时间想想工作的事情。记得之前在很多项目开发中,都需要通过上位机来控制ECU或者通过上位机来读取ECU中的数据。其中上位机和ECU的通讯接口有串口,LIN或者CAN总线。串口作为一种低成本而又简单的通讯方式,仍然有很多客户在使用。毕竟,一个USB转串口工具,淘宝网上只要几十块就可以买一个,而LIN总线或CAN总线转换接口,成本至少在几百元以上。
    在这里插入图片描述
    要实现上位机和ECU之间的串口通讯,除了一个USB转串口硬件工具以外,还需要一个上位机软件。上位机软件可以采用串口调试助手(可以从网上免费下载),但是很多情况下,串口调试助手无法满足控制和数据监视,以及分析功能,还需要根据项目需求,进行定制化开发。定制化开发串口上位机可以采用Visual Basic、Visual C#、labview等软件开发。这些软件特别适合于开发图形化界面。但是这几个软件需要占用很大的硬盘空间,而且对于文件操作,数据记录等并不是特别方便。最近听说python很火,一直好奇能否用python开发一个串口上位机软件,经过这几天的尝试,终于成功了。接下来给大家分享一下。
    在这里插入图片描述

    安装python开发环境

    1. 首先到python官网https://www.python.org/downloads/windows/下载python软件。如下图所示,最新版本为3.8. 本人的电脑是WIN 10 64位操作系统,需要下载的文件为:Windows x86-64 executable installer(64位操作系统一定要下载这个文件,如果下载32位的文件,后果自负)下载好的文件名为:python-3.8.1-amd64.exe.
    2. 双击这个文件,进行安装。安装时,选择Add python3.8 to PATH。这一点也非常重要!选中这个框,后续会省去很多麻烦的。单击Install Now, 可以用默认的安装路径安装。在这里插入图片描述
    3. 安装完成后,在运行窗口或者win10的搜索窗口中输入”cmd”,打开DOS窗口在这里插入图片描述
    4. 在DOS窗口中出现:C:\Windows\system32>,然后输入python, 回车,出现如上图所示的内容,以及“>>>”,说明python已经安装成功。然后在>>>后面,输入exit()可以退出。
    5. 在win10程序中,可以发现新安装的程序:IDLE (python 3.8 64-bit)。运行这个程序,出现IDLE开发环境界面在这里插入图片描述
    6. 在IDLE界面中的File菜单中可以创建一个新文件,我们的python上位机程序就可以存储在这个新文件中。
    7. 好的!我们已经顺利完成第一步,比起Visual studio和LabVIEW,是不是又快又简单啊?先喝杯酒庆祝一下吧!

    安装pyserial模块

    • Python的最大优点是共享和开源,有很多工程师已经开发了很多程序模块,并且无私奉献出来,我们可以直接利用这些现成的模块。是不是相当于踩在巨人的肩膀上一样啊?所以首先我们要感谢一下这些前辈们!他们已经把串行通讯的代码封装在了pyserial模块中(即便封装好,你也可以看到源代码的),接下来我们可以直接安装这个模块了。
    • 大家还记得把大象放进冰箱的三步法吗,那么我们安装pyserial是不是也要经过找到网址,下载软件,安装软件三步法呢?No! 让我们一起见证奇迹是如何发生的吧:运行cmd把DOS窗口打开,在C:\Windows\system32>后面直接输入”pip install pyserial”,然后系统会自动搜索下载链接,下载并且安装,一步搞定!
    • 在C:\Windows\system32>后输入”python”,回车,当出现“>>>”以后,输入import serial, 导入pyserial模块。然后在“>>>”之后,输入“serial”,可以看到pyserial被安装到的文件夹。
    • 好的,pyserial安装也搞定了!在这里插入图片描述

    编写串口上位机软件

    编写串口上位机软件:

    • 功能说明:
      • 上位机首先向ECU发出指令:控制命令(用字母c表示),读取数据指令(用字母r表示)
      • ECU收到指令后,进行处理,如果是c,则将LED蓝灯点亮;如果是r,则将LED红灯点亮,并向上位机发送0x30到0x39的十个数字。
      • 上位机发出r指令后,等待接收数据,接收到数据以后,将数据输出到屏幕。
        重复以上步骤,如果输入字母n,则退出上位机
        软件流程图如下:
        在这里插入图片描述
        打开IDLE软件,创建一个新文件,并且保存为SCI.py
        在这里插入图片描述编写上位机界面程序
    import serial  #导入串口模块
    import time    #导入时间模块,时间模块是Python自带模块,无需安装
    try:
        #打开串口,并且获得串口对象
        SCI1 = serial.Serial("com15",9600,timeout=50)
        #判断是否打开成功
        if (SCI1.isOpen()==True):
            print("串口已经打开!")
    except Exception as exc:
        print("串口打开异常:",exc)
    while True:
        commandFromECU = input("请输入控制命令,c-向ECU发送命令;r-从ECU中读取数据。")  #从键盘上输入一个命令
        SCI1.write(str(commandFromECU).encode("utf-8"))  #将键盘输入的控制命令发送给ECU上的单片机
        if commandFromECU == 'r':          #如果是读取数据,则从串口中接收数据。    
            time.sleep(1)                  #等待1秒,等待接收ECU上单片机返回的数据. ECU会依次发送0x30-0x39等10个数据
            bufferSize = SCI1.in_waiting   #接收ECU上单片机发过来的数据,并且返回数据的大小
            print("接收到",str(bufferSize),'个数据') #打印缓冲区中接收到数据的个数 
            data = SCI1.read_all().hex()           #将接收缓冲区中数据读取到data中
            print(data)                    #将接收到的数据按照16进制打印出来                
        time.sleep(1)                      #等待1秒
        if(input("如果退出,请输入n,如果继续发送命令,请按其他键 ") == 'n'):
            break
    SCI1.close()    #关闭端口
    

    在Run菜单中,选择Run Module或者直接按F5键,运行程序,界面如下:
    在这里插入图片描述好的,大功告成!我们已经完成了上位机和ECU之间的基本通讯流程。是不是软件非常简单呢?个人认为这个是python最大的优点:简约而不简单!

    总结

    我们再回顾一下python开发一个基于串口通讯的上位机程序的步骤:

    1. 到python.org下载python 3.8 64-bit并安装
    2. 使用pip install pyserial安装serial模块
    3. 编写上位机和ECU的通讯流程,画出软件流程图,编写代码,运行测试程序

    熟悉了这个程序以后,我们还可以在这个程序上增加更多的功能:

    • 将收到的数据,打印曲线,用图形显示出来。这个需要用到自带的turtle画图模块
    • 将收到的数据,保存到txt, csv文件中。
    • 将收到的数据,自动发送到邮件中。

    以上每个功能的实现,也只需要几行代码就可以实现,接下来的几年里,我会慢慢介绍给大家。好的,如果大家觉得这个串口好用的话,赶紧把电脑拿出来试一试吧。

    展开全文
  • VB与欧姆龙PLC通过RS-232串口通讯上位机程序自动配料系统,源码!!
  • 一个用于单片机和上位机通讯的简单程序程序中采用2400波特率11.0592M晶振8位无校验程序采用了简单的握手协议也就说当PC机通讯软件发小写字母j的时候单片机再回应字符串jLQGk给上位机你可采用串口调试助手配合完成你...
  • 程序通过第三方的验收,是采用CNCOMM类,使用VC6.0编写的,程序完成了串口设置,串口发送信息,串口接收信息。 串口发送信息可以设置发送数量,当串口接收到发送的信息后再次发送信息,直到发送的信息结束。 本程序...
  • MFC上位机串口通信

    2019-08-09 15:08:22
    对于玩各种传感器的极客来...今天尝试一下用MFC做一个简单的串口通信上位机。 电脑是win10的 已经没有自带的串口了,所以我用的是usb转ttl模块。 将TX和RX短接,让他自发自收(调试)。 因为课题组是用的MFC...
  • VC++串口上位机接受程序,这里只发上位机接受程序,下位机由单片机发给串口
  • VC2012/VS2012 MFC串口通讯上位机程序教程笔记

    万次阅读 多人点赞 2016-03-20 18:19:35
    使用MFC来编写串口程序,需要有一定的c++语言功底,要清楚MFC代码的组织方式。 鉴于绝大多数的教程还停留在vc6.0这个骨灰级的环境,特在此说明一下VC2012下的代码组织方式,和大家一起交流下~ 本文略去建立窗体的...
  • 本人因项目需求,需要开发这个串口通信上位机界面,用于实时绘制串口发送过来的数据。本人之前基本没使用过MATLAB,也就知道个zeros(100)这种在命令行窗口中输入的函数。至于MATLAB能开发图形界面程序,我也是在接到...
  • labview自制上位机无线通讯界面,串口通讯,连接数据库实时记录数据
  • 前面我们介绍了,如何用Python做一个串口通讯上位机。用这个上位机我们可以从ECU中读取数据,然后输出到屏幕上。 有些时候,我们需要保存这些数据。那么今天我给大家介绍的就是如何把数据保存到文件中。 我们还是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,318
精华内容 1,327
关键字:

上位机串口通讯程序