串口通信与自定义消息

摘自:http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=1551606&bbs_page_no=1&bbs_id=1000


课程之前:首先请大家确认一下前面两章都已经熟悉,因为一些前面已经介绍过的基础操作在这里将不再详细说明,如果有什么问题,可以翻看一下前面的两章或者留言提问.本章是基于PC机与单片机的串口通信,用到了一个动态链接库和一个自定义消息.
学习目标:掌握VC下串口编程式的方法,掌握动态库的静态调用及自定义消息.
课程详解:
1.        参照第一章新建一个基于对话框的Vc工程,名称定义为Eg03.


图片01  (原文件名:001.jpg) 

 
2.        工程建立后,在对话框上加入一个组合框(ComboBox),ID号改为IDC_COMPORT 用于选择使用PC机上的哪一个串口.
在组合框后加入一个按钮,标题(Caption)改为”打开”,ID号改为IDC_BTN_PORTOPEN 用于打开串口,开始通信.
下面加入一个编程框(EDIT),ID号改为IDC_EDIT_RECMSG 用于显示接收到的数据.
在编程框下面再添加一个编程框(EDIT),.ID号改为IDC_EDIT_SEDMSG 用于添加要发送的数据. 然后在这个编程框后加入一个按钮.标题(Caption)为”发送”,ID号为IDC_BTN_SEND 最后调整位置及大小如下


图片02  (原文件名:002.jpg) 

 
3.        添加Lib文件.这里介绍的串口通信用的不是VC自带的MSCOMM控件.原因有两个,一是顺便介绍一下动态库和自定义消息的用法.二是MSCOMM控件使用时数据类型转换比较复杂,并且使用也不是很方便.当然,以后也会介绍多线程串口通信给大家,我们会在后面开设一章多线程编程方法,并在那里详细介绍基于多线程的串口通信.这里使用一个动态库,其实也是别人封装好了的多线程通信,名字是Pcomm.在工程下载中,给出了三个文件,分别是Pcomm.h, Pcomm.lib, Pcomm.dll,现在请大家把这三个文件拷到工程目录,也就是Eg03这个文件夹中.至于什么是动态库,这三个文件倒底是什么作用,我们做完这个例程后再解释,现在还是先按步就搬,营造一个感性认识.下面添加Lib文件到工程.首先点击[工程](Project),选择下拉式菜单中的[设置](ProjectSettings)


图片03  (原文件名:003.jpg) 

 
然后会弹出一个对话框,在标签卡中选择[连接]


图片04  (原文件名:004.jpg) 

 
然后在[对象/模块]中添加Pcomm.lib,完成后如上面所示,单击[确定]退出.这样,我们就为Pcomm.dll这个动态库添加了静态链接,同时,这也就是动态库的静态链接方法,当然,还有一步就是包含Pcomm.h这个头文件.在刚才的步骤中,我们将Pcomm.Lib添加到工程,这个文件主要用于指定Pcomm.dll中各个功能函数的入口及地址,Pcomm.Lib就像一个地图指出目的地的路标,而真正的函数是在Pcomm.Dll中的.当然,为了方便调用,我们还要得到Pcomm.Dll中的函数声明,这些函数声明就在Pcomm.h这个头文件中,所以,大家打开Pcomm.h这个文件,只有函数及变量定义,并没有函数过程.下面我们来添加这个文件.
4.        打开左边的[工作空间](WorkSpace)中选择标签[ClassView](这里大家只能看到[Class…] 这一步前两章已经详细介绍过了,大家可以参考.),然后双击[OnInitDialog]就可以打开代码窗口了,在原有头文件包含后面加入串口头文件引用.输入#include ”Pcomm.h”就可以了,完成后如下图


图片05  (原文件名:005.jpg) 

 
这一步我们加入了动态库的函数声明,后面就可以直接使用Pcomm.Dll中的函数了.下面我们来添加事件响应.
单击工作空间中间的标签[ResourceView](大家看到的是[Reso…]),再双击[IDD_EG03_DIALOG]就可以回到控件编辑状态.


图片06  (原文件名:006.jpg) 

 
首先为[打开]按钮添加代码.双击[打开]按钮,然后在按钮事件中添加.完成后如下
void CEg03Dlg::OnBtnPortopen() 
{
        // TODO: Add your control notification handler code here
        Port=GetDlgItemInt(IDC_COMPORT);
        if(SIO_OK!=sio_open(Port))
        {
                MessageBox("串口打开错误");
        }
        else
        {
                sio_ioctl(Port,BaudRate,DataBits | StopBits | Parity);
                sio_cnt_irq(Port,CntIrq,1);
        }        
}
其中, sio开头的变量及函数都是Pcomm中的,我们来解释一下. sio_open是打开某个串口,传入的参数是串口号,如果我们要打开COM1,可以用sio_open(1),返回的参数在Pcomm里面定义了,如果返回SIO_OK就表示串口打开没有问题,否则,就是打开串口失败. sio_ioctl用于设置通信的相关信息,Port中串口号, BaudRate是波特率, DataBits是数据位数, StopBits是停止位数, Parity是校验. sio_cnt_irq用于设定中断回调函数.中断回调函数其实前面的Timer定时器里也提到过,在这里,我们设定一个中断回调函数,每当串口接收到指定字节数据时,系统就会自动调用这个中断回调函数,就像单片机中的串口中断函数一样.这里的回调函数名是CntIrq,我们将在后面定义.细心的朋友一定会发现, BaudRate,DataBits | StopBits | Parity这些都没定义过啊?所以要定义一下这些变量.参照前面的加入文件的方法,在头文件引用下一行加入以下宏定义
#define BaudRate B57600 //波特率
#define DataBits BIT_8 //数据位
#define Parity P_NONE //效验位
#define StopBits STOP_1 //停止位
完成后如图


图片07  (原文件名:007.jpg) 

 
下面我们要定义sio_cnt_irq 一般来说,中断回调函数并不写在类里面,我们添加后如下


图片08  (原文件名:008.jpg) 

 
///串口中断回调函数//
VOID CALLBACK CntIrq(int port) 
{
        if(::AfxGetMainWnd()) 
{
if(::AfxGetMainWnd()->m_hWnd)
{
::PostMessage(::AfxGetMainWnd()->m_hWnd,WM_PCOMM,0,0); 
}
}
}
学习过前面两章我们知道,这个中断回调函数只做了一件事情,就是发送一个WM_PCOMM消息到窗口. AfxGetMainWnd()这个函数用于获得主窗口,返回类型是CWnd的指针,主窗体句柄我们是不知道的,用AfxGetMainWnd()->m_hWnd来获得.这样,消息就可到发到主窗体了.有了这个函数,每当串口接收了数据,就会发一个消息到窗体.WM_PCOMM这个消息不是系统的,也不是Pcomm本身的,它是我们自定义的一个消息,怎么定义呢?我们在前面说的宏定义后面再加入一个定义
#define WM_PCOMM WM_USER+500 //自定义消息


图片09  (原文件名:009.jpg) 

 
WM_USER是一个消息地址,这个是系统定义好的,从这个地址开始可以自定义消息, 我们把WM_PCOMM定义为WM_USER+500也就是说,我们定义的这个消息位于WM_USER后面的偏移500,当然,这只是个地址,与执行先后无关.这个偏移大家可以自己随便设,不与别的自定义消息冲突就行了.消息定义好了还要为消息添加关联.首先要定义一个消息响应函数,名字随便,我们这里取名为OnPcomm(),双击[工作空间]中的Ceg03Dlg就可以打开窗体的文头件,这里定义了Ceg03Dlg 这个类,我们在类定义里面添加一个成员函数.
afx_msg void OnPcomm(); //这里是我们自定义的消息响应函数
完成后如图
此外,这里还顺便定义了一个变量,就是前面我们用到的Port 用于记录打开的串口号.
public:
        int Port;


图片10  (原文件名:010.jpg) 

 
位置就放在DECLARE_MESSAGE_MAP() 的前面.函数声明就可以了.现在来添加函数体.双击[OnInitDialog( )],然后在该文件的最后添加一个函数.写成如下形式.
void CEg03Dlg::OnPcomm()
{
        char buf[200];
        int end=sio_read(Port,buf,100);
        if(end)
        {
                CString a,b="";
                GetDlgItemText(IDC_EDIT_RECMSG,b);
                buf[end]=0;
                for(int i=0;i<end;i++)
                {
                        a.Format("%X ",(unsigned char)buf[i]);
                        b+=a;
                }
                SetDlgItemText(IDC_EDIT_RECMSG,b);
        }
}
这一段其实不难理解,因为前面两章已经介绍过多次了. sio_read是Pcomm的函数,从串口读取数据用. GetDlgItemText(IDC_EDIT_RECMSG,b);用于读出以前的历史记录,这样每次发上来的数据都放在后面连接起来.end是返回的收到的数据个数.用十六进制形式显示出来.


图片11  (原文件名:011.jpg) 

 
做完了上面一些,我们差一步就可以收到数据了.因为数据发上来后,底层响应,并调用了回调函数,在回调函数里面,发出一个消息WM_PCOMM 虽然我们在后面定义了一个Pcomm()函数专门用于响应这个消息,但这个自定义消息并不是自动连接到Pcomm()的,需要添加一个消息影射才能使WM_PCOMM消息影射到Pcomm()函数.双击左边[工作空间](WorkSpace)中的
DoDataExchange(CDataExchange* pDX) 
DoDataExchange这个函数的下面一般都这是用于定义消息影射的,将下面一段程序增加一行,完成后如下.
BEGIN_MESSAGE_MAP(CEg03Dlg, CDialog)
        //{{AFX_MSG_MAP(CEg03Dlg)
        ON_WM_SYSCOMMAND()
        ON_WM_PAINT()
        ON_WM_QUERYDRAGICON()
        ON_BN_CLICKED(IDC_BTN_PORTOPEN, OnBtnPortopen)
        //}}AFX_MSG_MAP
        ON_MESSAGE(WM_PCOMM,OnPcomm) //这里是消息影射
END_MESSAGE_MAP()


图片12  (原文件名:012.jpg) 

 
完成以后就可以按F7编译一下,如果无误就可以接收到数据了.运行后,选择正确的串口号,按一下[打开]按钮就可以了.
现在我们再来看看怎么发送数据
回到控件编辑状态,双击[发送]按钮,为该按钮添加代码.
void CEg03Dlg::OnBtnSend() 
{
        // TODO: Add your control notification handler code here
        CString a;
        unsigned char b=0;
        GetDlgItemText(IDC_EDIT_SEDMSG,a); //取得编辑框内所有文本
        a.MakeUpper();//全部转换为大写
        for(unsigned char i=0;i<a.GetLength();i+=3)
        {
                if(a.GetAt(i)>='A' && a.GetAt(i)<='Z') b=(a.GetAt(i)-55)*16; //判断填入的是字母还是数字,并把字符转换成十六进制数
                else if(a.GetAt(i)>='0' && a.GetAt(i)<='9') b=(a.GetAt(i)-0x30)*16;

                if(a.GetAt(i+1)>='A' && a.GetAt(i+1)<='Z') b+=(a.GetAt(i+1)-55);
                else if(a.GetAt(i+1)>='0' && a.GetAt(i+1)<='9') b+=(a.GetAt(i+1)-0x30);

                sio_putch(Port,b); //发送
        }
}
这一段主要是把获得的编辑框内的字串转换成十六进制的数字,转换一个发送一个.Cstring类型以前已经提起来,应该际上是一个类, MakeUpper是一个成员函数,用于将字符串全部转成大写.GetAt也是一个成员函数,可以取出字符串中任意下标的字符. sio_putch用于发送一个字符.


图片13  (原文件名:013.jpg) 

 
填写待发送数据的时候要注意,每两位中间空格一下.填入的是十六进制数据.
下面再来总结一下静态方式调用动态库的方法.
1.        拷贝Lib,H头文件到工程路径
2.        在工程->设置中加入Lib模块.
3.        加入.h头文件,用于函数声明
4.        将Dll文件拷入到工程目标路径中
总结一下自定义消息方法:
1.        用#define WM_NAME WM_USER+1 定义一个自定义消息,名称随便.一般用WM开头.WM_USER+1中的1那个数字是自己定的,一个消息就无所谓了,喜欢多少都行,如果要定义很多个消息,不要冲突就行了
2.        在类定义里面声明一个消息响应函数,写成 afx_msg void FunctionName();格式.
3.        添加一个消息影射ON_MESSAGE(WM_NAME, FunctionName)注意这句后面是没有分号的.
4.        写好FunctionName的函数.
很简单的四步就行了.
这里是整个工程及教程的下载
工程文件ourdev_433389.rar(文件大小:1.72M) (原文件名:VC3.rar) 
教程第二章链接http://ouravr.com/bbs/bbs_content.jsp?bbs_sn=1481238&bbs_page_no=2&bbs_id=1000
教程第一章链接http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=1455135&bbs_page_no=1&bbs_id=1000 

Moxa是一家做了几十年工业串口卡、串口服务器之类设备的公司,PComm Lite是一套易用性、可靠性久经考验串口编程开发包。相比用API或mscomm控件开发简单太多了。 版本:目前最新版是Version 1.6 Released May 14, 2012 支持XP/win7, 32/64bit的库都有,开发环境支持VC/VB/Delphi。注意可以会搜到另外一个Version 2.6. Released Jul 8, 2008,那是在WIN9x/NT4用的,不要看版本号高下错了。 使用方法:运行安装后有类库、例程、帮助和几个小工具。关键的有四个文件:pcomm.h/pcomm.lib两个文件复制到项目目录并引用,pcomm.dll丢到windows\system32下或跟应用程序放在一起,pcomm.chm帮助备查。 函数介绍:整个库包含50多个函数,最常用的也就10来个: 打开、关闭、设波特率的:sio_open ()、sio_close()、sio_baut() 发送数据的:sio_putch(),sio_write() 接收数据的:sio_getch(),sio_read() 查询输入输出缓冲区状态的:sio_iqueue(), sio_oqueue() 有时可能要设读写超时:sio_SetReadTimeouts(), sio_SetWriteTimeouts() 这些函数见名知义,用法查一下PComm.chm就行了。 编程方法: 接收数据一般免不了要开线程的,在接收线程里sio_iqueue()看一下有没有数据,有就处理,没就Sleep()一会。接收数据时它至少会帮你缓冲几十k,一般也不会丢数据。也可以用sio_term_irq()指定接收一定长数据数据就调用一个CALLBACK函数。 这些基本就齐活了。需要控制DTS/RTS、自动流控制都有,甚至还有Xmoderm/Ymoderm/Zmoderm发送文件。如果要Modbus之类的协议就要自己写了。 与其它串口开发方式比较:简单地说API是基础零件,自己做起来麻烦。MSComm控件、CSerialPort类是实验室产品,Pcomm lite是工业成熟产品。
stm32串口通信自定义协议是指在stm32微控制器中使用串口进行通信时,为了满足特定需求所设计的一种协议。相比于常见的通信协议,如UART、SPI或I2C等,自定义协议可以根据实际需求进行灵活的定制,以实现更高效、稳定的数据传输。 首先,自定义协议需要设计合适的数据格式。可以选择使用不同的帧结构,如起始字符、帧长度、命令字、数据域和校验等。起始字符用于标识消息的开始,帧长度用于表示数据域的长度,命令字用于确定接收方的操作,数据域用于传输实际数据,校验用于验证数据的完整性。 其次,自定义协议需要确定合适的数据传输方式。可以选择使用同步传输或异步传输方式,同步传输通常使用时钟来同步发送和接收数据,而异步传输则在数据中添加起始位和停止位来标识数据边界。 接着,自定义协议还需要考虑数据的传输速率和可靠性。可以根据实际需求选择合适的波特率(Baud rate),以确保数据能够在一定的时间内传输完毕。同时,可以采用CRC校验或其他纠错技术,来减少数据传输过程中发生错误的可能性。 最后,为了实现自定义协议,需要在stm32的程序中编写相应的发送和接收函数,并根据协议规定进行数据的拆包和组包。发送函数负责将数据按照协议格式封装成帧,并通过串口发送出去。接收函数则负责解析接收到的数据帧,并根据协议提取出有用的信息。 总之,stm32串口通信自定义协议可以根据实际需求,灵活定制数据格式、传输方式和校验机制等,以实现更高效、灵活和可靠的数据传输。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值