购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.2 项目实施

下面通过三个实例循序渐进地介绍串口通信在物联网应用中的开发技术。

2.2.1 实例 1:PC机间串口通信

在搭建VS开发环境中,我们简单介绍了C#的一些基本知识,并成功地运行了第一个VS显示字符串的程序。从本小节开始,请自己动手确定一个上位机串口通信助手。

1.构思功能

串口通信助手在单片机开发中经常被用来调试,最基本的功能就是接收和发送功能;其次,串口在打开前需要进行一些设置:串口列表选择、波特率、数据位、校验位、停止位,这样就有了一个基本雏形;然后在此功能上添加字符串(ASCII码)或 16进制(HEX码)显示、发送、发送新行功能、重复自动发送以及显示接收数据时间等这几项扩展功能。

2.设计布局

根据构思功能,将整个界面分为两块:设置界面(不可缩放)及接收区和发送区(可缩放),下面就来依次拖放控件实现。

(1)容器控件(Panel)。

Panel是容器控件,是一些小控件的容器池,用来给控件进行大致分组,要注意容器是一个虚拟的,只会在设计的时候出现,不会显示在设计完成的界面上,这里我们将整个界面分为 6个容器池,如图2.12所示。

图2.12 上位机串口通信界面设计

(2)文本标签控件(Lable)。

用于显示一些文本,但是不可被编辑,改变其显示内容有两种方法:一是直接在属性面板修改Text的值,二是通过代码修改其属性,见如下代码;另外,可以修改Font属性修改其显示字体及大小,这里选择微软雅黑,12号字体。

(3)下拉组合框控件(ComboBox)。

显示下拉列表通常有两种模式:一种是DropDown模式,既可以选择下拉项,也可以选择直接编辑;另一种是DropDownList模式,只能从下拉列表中选择。两种模式通过设置DropDownStyle属性选择,这里选择第二种模式。那么,如何加入下拉选项呢?对于比较少的下拉项,可以通过在属性面板中的Items属性中加入,比如停止位设置,如图2.13所示;如果想要出现默认值,改变Text属性就可以,但要注意必须和下拉项一致。

另外一种是直接在页面加载函数代码中加入,比如波特率的选择,代码如下:

图2.13 下拉组合框选项设置

(4)按钮控件(Button):人机交互最常见的控件。

(5)文本框控件(TextBox)。

TextBox控件与Label控件不同的是文本框控件的内容可以由用户修改,这也满足了发送文本框的需求。在默认情况下,TextBox控件是单行显示的,如果想要多行显示,需要设置其Multiline属性为true。TextBox运用的方法中最多的是AppendText方法,它的作用是将新的文本数据从末尾处追加至TextBox中。但是当TextBox一直追加文本后就会带来本身长度不够而无法显示全部文本的问题,此时需要使用TextBox的纵向滚动条来跟踪显示最新文本,将TextBox的属性ScrollBars的值设置为Vertical即可。

至此,显示控件就全部添加完毕,但是还有一个最重要的控件没有添加,这种控件叫作隐式控件,它是运行于后台的,用户看不见,更不能直接控制,所以也称为组件,接下来添加最主要的串口组件。

(6)串口组件(SerialPort)。

串口这种隐式控件,添加后位于设计器下面。串口常用的属性有两个:一个是端口名(PortName),一个是波特率(BaudRate)。其他的还有数据位、停止位、奇偶校验位等属性。串口打开与关闭都有接口可以直接调用,串口同时还有一个IsOpen属性,IsOpen为真(true)表示串口已经打开,IsOpen为假(flase)则表示串口已经关闭。

添加串口组件后,就可以通过它来获取电脑当前端口,并添加到可选列表中,代码如下:

若想打开串口,还需确保USB转串口设备已连接,同时六个控件panel的BorderStyle属性设置为Fixed3D,启动串口通信程序后可以看到串口通信的界面布局如图2.14所示。

图2.14 串口模块界面布局

3.搭建后台

串口界面布局完成后,就要用代码来搭建整个软件的后台,这部分才是重中之重。

步骤 1,先控制打开/关闭串口。当按下打开串口后,首先将参数设置到串口控件属性中,然后打开串口按钮则显示关闭串口,再次按下时串口关闭则显示打开按钮。

在这个过程中,要注意一点,当单击打开按钮时,会发生一些程序无法自动处理的事件,比如硬件串口没有连接,串口打开的过程中硬件突然断开,这些被称为异常;针对这些异常,C#也有try_catch处理语句,在try块中放置可能产生异常的代码,比如打开串口,在catch中捕捉异常进行处理,详细代码如下:

步骤 2,构建发送的后台代码,串口发送是在串口成功打开的情况下进行的,所以首先要判断串口属性IsOpen是否为true。

1串口发送有两种方法:一种是字符串发送WriteLine,一种是Write。可以发送字符串或十六进制发送,其中字符串发送WriteLine默认已经在末尾添加换行符。

步骤 3,实现串口接收功能,在使用串口接收之前,要先为串口注册一个Receive事件,相当于单片机中的串口接收中断,然后在中断内部对缓冲区的数据进行读取,如图2.15所示,输入完成后回车,就会跳转到响应代码部分。

图2.15 串口注册Receive事件和对应方法

同理,串口接收也有两种方法:一种是十六进制方式读,一种是字符串方式读。在刚刚生成的serialPort1_DataReceived代码中编写,代码如下。

这里又有一个新知识点,串口接收处理函数属于一个单独的线程,不属于main的主线程,而接收区的TextBox是在主线程中创建的,所以当直接用serialPort1.ReadExisting方法读取字符串,然后用textBox_receive.AppendText方法追加到文本框时,串口通信程序没有反应,甚至报异常。所以,这时就需要用到invoke方式,这种方式专门用于解决从不是创建控件的线程访问UI资源,加入invoke方式后,串口助手就可以正常接收到数据,如图2.16所示。

图2.16 串口正常接收数据

至此,已完成一个串口通信助手的雏形,实现了基本发送和接收字符串功能,并将打开/关闭串口异常进行了处理,下面就来按照流程,逐步将功能完善。

1.构思功能

首先是接收部分,要添加一个“清空接收”的按钮来清空接收区。因为串口通信协议常用都是 8bit数据(低 7bit表示ASCII码,高 1bit表示奇偶校验),作为一个开发调试工具,它还需要将这个 8bit码用十六进制方式显示出来,从而方便调试。所以还需要添加两个单选框来选择字符串(ASCII码)显示还是十六进制(HEX)显示。

然后是发送部分,与之前对应,调试过程中还需要直接发送十六进制数据,所以也需要添加两个单选框来选择发送字符串码还是十六进制码;除了这个功能,还需要添加自动发送的功能,从而方便调试。

2.设计布局

(1)单选按钮控件(RadioButton)。

接收数据显示不能同时选中字符串显示或者十六进制显示,所以要用单选按钮控件,在同一组中(如之前所讲的容器)的单选按钮控件只能选中一个,刚好符合要求。

(2)复选框控件(CheckBox)。

该控件通常被用于选择一些可选功能,比如是否显示数据接收时间,是否在发送时自送发送新行,是否开启自动发送功能等。它与RadioButton都有一个很重要的Checked属性:若为false,则表示未被选中,若为true,则表示被选中。

(3)数值增减控件(NumericUpDown)。

显示用户通过单击控件上的上/下按钮可以增加或减少的单个数值,这里我们用来设置自动发送的间隔时长。

(4)定时器组件(Timer)。

这里之所以称为组件是因为它和之前的串口一样,都不能被用户直接操作,它是按用户定义的间隔引发事件的组件。Timer主要是间隔(Interval)属性,用来设置定时值,默认单位毫秒(ms);在设置定时器之后,可以调用Timer对象的开始(start)方法和停止(stop)方法来启动或者关闭定时器。在启动之后,Timer就会每隔Interval毫秒触发一次Tick事件,如果设置初始值为 100ms,用户只需要设置一个全局变量i,每次时间到后i++,当i=10时,就表示计数值为 1s,这里Timer的方法和单片机定时周期采集数据模块功能相同。整体设计出来的效果图如图2.17所示。

图2.17 串口通信整体设计界面

3.搭建后台

按照之前的思路,界面布局完成后,就要开始一个软件最重要的搭建后台部分。

步骤 1:状态栏显示串口状态。

这里直接在Form1_Load方法中添加如下代码即可。

步骤 2:接收部分。

之前直接在串口接收事件中调用serialPort1.ReadExisting方法读取整个接收缓存区,然后追加到接收显示文本框中,但在这里需要在底部状态栏显示接收字节数和发送字节数,所以就不能这样整体读取,要逐字节读取/发送并且计数。具体过程如下。

(1)类的属性。

首先定义一个用于计数接收字节的全局变量,这个变量的作用相当于C语言中的全局变量,在C#中称之为类的属性,这个属性可以被这个类中的方法所访问,或者通过这个对象来访问,同理也可以定义一个计数发送字节的全局变量,代码如下:

(2)按字节读取缓冲区。

首先通过访问串口的BytesToRead属性获取到接收缓冲区中数据的字节数,然后调用串口的Read(byte[]buffer,int offset,int count)方法从输入缓冲区读取一些字节并将那些字节写入字节数组中指定的偏移量处。

上一步将串口接收缓冲区中的数据按字节读取到了字节(byte)型数组received_buf中,但是要注意,这里的数据全部是byte型数据,如何显示到接收文本框中呢?要知道接收文本框显示的内容都是以字符串形式呈现的,也就是说追加到文本框中的内容必须是字符串类型,即使是十六进制显示,也是将数据转化为十六进制字符串类型显示的,接下来讲述如何将字节型数据转化为字符串类型数据。

(3)字符串构造类型(StringBuilder)。

需要将整个received_buf数组进行遍历,将每一个byte型数据转化为字符型,然后将其追加到总的字符串(要发送到接收文本框去显示的那个完整字符串)后面;但是String类型不允许对内容进行任何改动,更何况需要遍历追加字符;所以这时就需要用到字符串构造类型StringBuilder,它不仅允许任意改动内容,还提供Append、Remove、Replace、Length、ToString等有用的方法,这时再来构造字符串就显得很简单,代码如下:

接下来运行此串口通信程序,结果如图2.18所示。

图2.18 串口通信整体设计界面

图2.18中显示,当发送字符“1”时,状态栏显示接收到 1byte数据,表明计数正常,但是接收到的却是字符形式的“49”,这是因为接收到的byte类型的数据存放的就是ASCII码值,而调用byte对象的ToString方法,这个C#方法刚好又将这个ASCII值 49转化成为字符串“49”,而不是对应的ASCII字符“1”。

(4)C#类库编码类(Encoding Class)。

接着上一个问题,我们需要将byte转化为对应的ASCII码,这就属于解码(将一系列编码字节转换为一组字符的过程),同样将一组字符转换为一系列字节的过程称为编码。

转换ASCII码有两种方法:第一种采用Encoding类的ASCII属性实现,第二种采用Encoding Class的派生类ASCIIEncoing Class实现。这里采用第一种方法,然后调用GetString(Byte[])方法将整个数组解码为ASCII数组,代码如下:

再次运行一下,可以看到结果显示正常,即发送字符“1”时接收到的也是字符“1”。

(5)byte类型值转化为十六进制字符显示。

在前面已分析byte.ToString方法,它可以将byte类型直接转化为字符显示,比如接收字符“1”的ASCII码值是 49,就将 49直接转化为“1”显示;在这里需要将“1”用十六进制显示,也就是显示“31”(0x31),这种转化并没有什么实质上的改变,只是进行数制转化而已,所以采用格式控制的ToString方法。其中,Int.ToString(format)格式字符串采用以下形式:Axx,其中A为格式说明符,指定格式化类型,xx为精度说明符,控制格式化输出的有效位数或小数位数,具体见下表2.2。

表2.2 Int.ToString(format)格式控制方法

这里需要将其转化为 2位十六进制文本显示,另外,由于ASCII和HEX只能同时显示一种,所以还要对单选按钮是否选中进行判断,代码如下:

先发送“Mcu 51”加回车,然后发送“1”加回车,运行结果如图2.19所示。

图2.19 类型byte值转化为十六进制字符显示

(6)日期时间结构(DateTime Struct)。

当勾选上显示接收数据时间时,要在接收数据前加上时间,这个时间通过DateTime Struct来获取,为了避免在接收处理函数中反复调用,依然声明为一个全局变量private DateTime current_time=new DateTime()。

这时current_time是一个DateTime类型,通过调用ToString(String)日期格式方法将其转化为文本显示具体选用哪种,常见的有如下几种:

比如:current_time=2022/2/27 01:02:03,

①ToString("yyyy/MM/dd");//2022/02/27。

②ToString("yyyy-MM-dd");//2022-02-27。

③ToString("dd.MM.yyyy");//27.02.2022。

④ToString("yyyy年MM月dd日");//2022年 02月 27日。

⑤ToString("yyyy/MM/dd HH:mm:ss.fff"));//2022/02/27 01:02:03.001,fff越多精度越高。

⑥ToString("yyyy-MM-dd HH:mm:ss:fff"));//2022-02-27 01:02:03:234。

★y代表年份(注意是小写的y,大写的Y并不代表年份);M表示月份;d表示日期,(大写的D并不代表什么);h或H表示小时,h用的是 12小时制,H用的是 24小时制;m表示分钟;s表示秒(大写的S并不代表什么);f代表毫秒。

在显示时,依然要对用户是否选中进行判断,代码如下:

(7)清空接收按钮。

相关控件的Text赋值为空或者对因变量清零,代码如下:

步骤 3:发送部分。

首先为了避免发送出错,启动时将发送按钮失能,只有成功打开后才能使用,关闭后失能,这部分代码简单,读者可以自行编写。具体过程如下。

(1)字节计数+发送新行。

有了上面基础,实现这两个功能就比较简单,要注意Write和WriteLine的区别。

(2)正则表达式的简单应用。

正则表达式很重要!比如希望发送的数据是 0x31,功能应该被设计为在十六进制发送模式下,用户输入“31”就应该发送 0x31,这个不难,只需要将字符串每 2个字符提取一下,然后按 16进制转化为一个byte类型的值,最后调用write(byte[]buffer,int offset,int count)方法将这个字节发送即可,那么当用户同时输入多个十六进制字符该如何发送呢?这个时候就需要用到正则表达式,用户可以将输入的十六进制数据用任意多个空格隔开,然后利用正则表达式匹配空格,并替换为“Space”,相当于删除掉空格,这样对整个字符串进行遍历,用刚才的方法逐个发送即可!完整的发送代码如下:

(3)定时器组件(Timer)。

在串口通信助手项目中,添加定时器组件和行为的方法结果如图2.20所示。

图2.20 添加定时器组件和行为

自动发送功能是串口通信助手搭建的最后一个功能,这个定时器和单片机中的定时器用法基本一样,所以,大致思路如下:当勾选自动发送多选框的时候,将右边数值增减控件的值赋予定时器作为定时值,同时将右边数值选择控件失能;然后当定时器时间到后,重置定时器并调用发送按钮的回调函数,当勾选自动发送时,停止定时器,同时使能右边数值选择控件,代码如下:

至此,PC间的串口通信程序设计完成并部署在测试的PC机上,用一根 9针的串口线连接 2台PC机的串口或同一台主机上的 2个不同串口,测试结果如图2.21所示。

图2.21 PC间串口通信程序

★若PC机主板无RS232串口只有USB口,可以用USB转串口线进行辅助连接。

2.2.2 实例 2:C51单片机串口通信

随着单片机系统的广泛应用和计算机网络技术的普及,单片机的通信功能愈来愈显得重要。单片机通信是指单片机与计算机或单片机与单片机之间的信息交换,前者用得较多。在单片机系统以及现代单片机测控系统中,信息的交换多采用串行通信方式。

下面以HC6800-ES V2.0开发板为例,讲解C51单片机与计算机的串口通信方法。

步骤 1:运行Keil uVision4开发环境,新建HC6800单片机串口UARTpro.uvproj工程,其主程序Main.c文件完整代码如下。

步骤 2:编译工程,将程序下载到开发板,复位或重新上电开发板,结果如图2.22所示,即计算机发送数据到开发板,数据通过串口再次从开发板返回到计算机。

图2.22 C51单片机串口通信实例

★ C51单片机开发板首次连接PC时,设备管理器中若提示“请求USB设备描述符失败”,请先安装单片机开发板板载USB的驱动程序CH341SER.EXE。

2.2.3 实例 3:STM32的ARM处理器串口通信

下面介绍如何使用STM32的ARM处理器无线节点板上的串口进行数据发送和接收,其所需硬件环境如图2.23所示,其开发环境参见 2.1.3小节的第 4部分。

图2.23 STM32 ARM处理器串口通信开发环境

1.基本原理

STM32无线节点板提供两个串口供用户开发使用,分别是usart1和usart2,在本例中采用usart1来进行实验。为了让用户更简单地调用串口通信方法,将C库中的fputc()、fgetc()方法进行重写,将串口接收数据方法作为fgetc()的执行体,若用户在执行scanf函数时,则可以实现从串口接收字符数据。同理,将串口发送数据方法作为fputc()的执行体,在执行printf函数时,则可以实现向串口发送数据。

在配套光盘common文件夹存放的是例程所需要的公共文件,包括驱动程序以及官方提供的库文件。此文件夹里面有hal和lib两个子文件夹,hal里面存放的是用户自定义的驱动程序的头文件和源文件,lib里面存放的是官方提供的STM32 3.5库文件。

2.新建ARM串口通信工程

步骤 1:在预定义ARMUartTest目录下先创建一个命名为common的文件夹,在common中创建hal和lib文件夹。然后在hal中创建include和src文件夹,前者存放用户自定义头文件,后者存放自定义源文件,最后将官方common\lib目录下所有文件拷贝到lib中。

步骤 2:打开IAR for ARM应用程序。在菜单Project中选择Create New Project…,在弹出页面中Tool chain选项中选择ARM,在工程模板中选择Empty Project,单击OK,将文件存放在ARMUartTest下并编写工程名。创建一个名为uart的工程,创建完成后,在菜单File中单击Save Workspace,并以uart命名保存。

步骤 3:构建IAR-EWARM工程目录,添加好的目录如图2.24所示。其中,添加目录的方法是:在工程文件上右击选择Add菜单下Add Files…或Add Group…来添加文件或者组(文件夹),lib目录中需要添加的所有文件都放在工程目录common\lib目录里。

★ lib\CMMIS\startup里面的startup_stm32f10x_md.s文件为STM32芯片启动文件,该文件在common\lib\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\iar目录下。

★ lib\CMMIS里的core_cm3.c在common\lib\CMSIS\CM3\CoreSupport目录中,system_stm32f10x.c在common\lib\CMSIS\CM3\DeviceSupport\ST\STM32F10x目录下。

★ STM32F10x_StdPeriph_Driver目录中所有文件为官方提供的库函数,这些文件都在common\lib\STM32F10x_StdPeriph_Driver\src目录里,用户可根据需要进行选择性添加。本次例程是一个串口通信实验例程,用户需要添加的主要库函数文件为stm32f10x_usart.c。

★ Output输出文件夹,是IDE自动生成的文件夹。

图2.24 构建IAR-EWARM工程目录

步骤4:由配套光盘中无线节点板芯片原理图可得知usart1的TX端连接到开发板上的PA9引脚,RX端连接到开发板上的PA10引脚,因此接下来的工作就是配置这两个IO口,并配置串口的一些参数,下面是usart1的配置源码。

上述代码中实现usart1的初始化,接下来就是编写串口接收和发送的函数,按照本例设计思路,将串口数据接收和发送方法分别写进fgetc()和fputc()中,可通过scanf函数和printf函数实现在串口收发数据,下面是串口数据接收和发送的实现源码。

实现了串口的数据接收、发送方法之后只需要在main函数里面调用scanf、printf方法即可实现串口的数据接收、发送功能。

步骤 5:编写main.c文件,并存放在工程的根目录,同时将该文件添加到工程的目录列表中,文件main.c源码如下。

3.工程设置

在工程设置里面需要设置CPU的类型以及编译选项等参数,具体设置过程如下。

步骤 1:单击工程名选择Options…弹出工程设置界面,选择设备型号,在General Options中的Target页面中的Device选项中选择ST STM32F103xB。

步骤 2:添加所需头文件的路径以及库文件中需要用到的宏定义语句:单击左边页面中的C/C++Compiler进入编译选项设置界面,选择preprocessor,然后在Additional include directories一栏输入头文件的路径,然后在Defined symbols一栏输入工程所需的宏定义。

头文件路径如下:

★添加过程中,必须坚持一行添加一条头文件的路径,其中“..\”为上一级目录。

添加宏定义:

添加完成后如图2.29所示。

图2.25 添加头文件路径宏定义语句

步骤 3:设置文件输出格式,单击图2.25左边的Output Converter进入输出格式设置界面,勾选generate additional output选项,然后在Output format中选择Intel extended,最后勾选Override default,输入输出格式为uart.hex。

步骤 4:设置调试选项,单击图2.25左边的Debugger进入调试设置界面,在Driver界面中选择J-Link/J-Trace,最后单击OK按钮完成工程设置。

4.IAR在线系统编程与运行

工程设置好后就可以进行编译程序、调试程序和下载程序到开发板中了。

步骤 1:编译工程,在菜单栏Project中单击Rebuild All,或者直接单击工具栏中的make按钮,编译成功后会在该工程的Debug\Exe目录下生成uart.hex文件。

步骤 2:下载hex文件,确定节点板跳线为模式二,通过调试板正确连接J-Link仿真器到PC机和STM32开发板,将开发板接上电源,无线节点板电源开关必须拨到ON,单击菜单栏Project Download and Debug或者单击工具栏的download图标按钮将程序下载到无线节点板。程序下载成功后IAR自动进入调试界面。

步骤 3:进入到调试界面就可以对程序进行调试,IAR的调试按钮包括如下几个选项:重置按钮Reset、终止按钮Break、跳过Step Over、跳入函数按钮Step Into、跳出函数按钮Step Out、下一条语句Next Statement、运行到光标的位置Run to Cursor、全速运行Go和停止调试按钮Stop Debugging。由于这些调试按钮的使用比较简单,读者可自行尝试。

步骤 4:对于物联网嵌入式程序来说,最重要的调试功能应该就是查看寄存器的值,IAR在调试的过程中也支持寄存器值的查看。打开寄存器窗口的方法是:在程序调试过程中,在菜单栏View中单击Register即可打开,默认情况下寄存器窗口显示基础寄存器的值,单击寄存器下拉框选项可以看到不同设备的寄存器。

步骤 5:调试结束之后,单击go按钮,或将无线节点板重新上电或者按下复位按钮,就可以观察到PC机串口助手上接收到的信息。

步骤 6:程序成功运行后,在PC机上打开串口助手,设置接收波特率为 115200,数据位为 8,无奇偶校验,停止位为 1,无数据流控制,将会在接收区看到如下信息。

此时通过串口调试助手发送一个字符,比如a,它就会回显如下信息:

5.JLINK下载hex文件

在上述步骤中利用IAR环境烧写程序,但有时会出现烧写失败的问题,此时可利用J-Flash下载编译生成的hex文件到无线节点板中。

步骤 1:在下载之前,需要在PC机安装JLINK仿真器驱动程序并配置正确的参数。

步骤 2:运行J-Flash ARM仿真软件,在菜单栏Target中单击Connect,连接成功后,在LOG窗口下会显示Connected successfully。注意:在每次下载程序之前,需要将JLINK仿真器与PC机和无线节点板进行软件连接,若没有连接可能会导致烧写程序失败。

步骤 3:在菜单栏File中单击Open data file,打开例程目录下Debug/Exe/uart.hex,然后在Target菜单栏中单击Erase chip,最后单击Program&Verify,显示成功后,即表示该程序已烧写到STM32的无线节点板中。 hAbRKlekBMHCgsHEg7HZ2gu2G0e7dqluQd6FtUqGwEyfaQkjjOem8X3ErI2R1Zbt

点击中间区域
呼出菜单
上一章
目录
下一章
×