Visual Basic与分布式监控系统


Visual Basic 与分布式监控系统 目录表 第一章 串行通讯概念………………………………………………1 第二章 Visual Basic 及常用组件简介……………………………24 第三章 分布式监控模块简介………………………………………60 第四章 分布式模块的指令字符串及格式…………………………77 第五章 数字输入输出模块-7060D…………………………………95 第六章 模拟输入模块-7012D………………………………………262 第七章 模拟输出模块-7021………………………………………300 第八章 频率计数模块-7080D………………………………………355 第九章 模块进阶设定及控制………………………………………353 第十章 整合实验……………………………………………………400 第十一章 监控的延伸………………………………………………439 第十二章 分布式模块的 OCX 与 DLL…………………………………528 附录: 如何使用本书的光盘片……………………………………595 第一章 串行通讯概念 什么是通讯?简单地说 通讯就是二个人之间的沟通 也可以说 是二个设备之间的资料交换 人类之间的通讯使用了诸如电话 书 信等等的工具进行 而设备之间的通讯则是使用了电气讯号的变 化 最常见的讯号传递就是使用电压的改变来达到状况表明的目 的 以计算机来说 较高电平的电压(高电位)代表了一种状况 而 较低的电压电平(低电位)又代表了另一种状况 在组合了很多的电 平情形后就形成了二种设备之间的资料交换 如果提到最简单的方式的话 应该就是使用一条讯号线路来作 电压的改变而达到传送讯息的目的 只要准备沟通的双方先行定义 好什么样的状况就代表什么样的意义 那么透过这一条线就可以让 双方作资料交换 在计算机的内部 所有的资料均是使用位(Bit)予以储存 每一 位均是电位上的一个状态(计算机上以 0 1 表示) 计算机内部使用 了 8 个位代表了一般所使用的字符 数字及一些符号之中的一个 例如 01000001 就表示了其中的一个字符 对于人类来说 最起码必 须传这些字符 数字或符号才能算是资料的交换 如果以上面所讨 论的使用一条线路来传送讯息的话 8 个位就必须在线路上连续化 8 个状态才算是作完一个字符的传递 这种一次只传送一个位的作 法就是 串行通讯 换个角度来说 如果我们可以使用多一些的线路 来达到资料传送的目的 这些位就可以被快一些传送完毕 因此还 有所谓的并列传输 并列传输使用了 8 条讯号线一次将一个字符资 料全部传送完毕 基本上其速度是串行通讯的 8 倍 例如打印机端 口就是其中的一个典型例子 计算机的发展也有一段不算短的时间了 其中的串行通讯存在 的时间相当久远 应用也相当地广泛 市场上相当多的产品使用了 串行通讯技术作为产品的对外沟通桥梁 为何串行通讯受到这么多 的重视?实际上则是因为这个技术的简单易实现的特性 本章首先 介绍串行通讯的相关基础 第 1 页,共 606 页1-1 RS-232 与 RS-485 串行通讯以 RS-232 及 RS-485 作为代表 二者各有其应用领域 使用的范畴也不见得一样 不过二者却同样是串行通讯中的一种方 法 以下说明此二种方式 1-1-1 RS-232 通讯 串行通讯端口(Serial Communication Port) 在系统控制的范畴中 一直占有极重要的角色 不仅没有因为时代的进步而被淘汰 反而 是在规格上愈来愈向其极限挑战 现在 计算机上的串行通讯端口 (RS-232)是标准配备 用途上则以连接调制解调器(Modem)作通讯传 输 (尤其是网际网络成为潮流趋势后 接上 WWW 取得资料是相当重 要的一个收集管道)最为常见 当然了 它的重要性还不只如此呢! 以下还会对通讯端口作一个深入的介绍 RS-232 的通讯端口是每部计算机上的必要配备 通常含有 COM1 与 COM2 两个信道 以前的计算机将 COM1 以 9Pin 的接头接 出 而以 25Pin 的接头将 COM2 接出 新一代的计算机均以 9Pin 的 接头接出所有的 RS-232 通讯端口 在计算机上的 RS-232 均是公头 即使是 25Pin 也是公头 千万不要与其它的设备弄胡涂了(打印机连 接端口也是 25Pin 不过它是母头 请仔细分别) RS-232 在一般个 人计算机上的外观如图 1- 1- 1 所示 图 1- 1- 1 RS- 232 的外观 而工业应用的场合中 工业计算机的使用比一般计算机来得普 遍 在工业计算机上的接头位置图片就可能如图 1- 1- 2 第 2 页,共 606 页 图 1- 1- 2 工业计算机中的 COM 端口 其中 9 脚的位置有二个地方 分别位于上一排的右边算来第二 个及下排的左边 不管工业级或是一般的桌上型计算机 只要是 RS- 232 就是公头 9 针 这一点是共通的 现在计算机上的 RS- 232 共有 9 支脚位(以前还有 25 支脚位的 不过现在已经很少见了) 每一支脚位有其特定的名称与用途 其 在计算机及连接线上的脚位位置和定义如图 1- 1- 3 图 1- 1- 3 RS- 232 脚位定义 最早的串行端口是用来和调制解调器作连接的 因此它的各脚 位的定义也和调制解调器有关 当 RS-232 串行端口和调制解调器 (Modem)连接时 每一脚位上的讯号流动方向如表 1-1-1 所示 表 1-1-1 脚位讯号流向及说明 脚位 方向 说明 CD 计算机<调制解调器 调制解调器通知计算机有载波被侦测到 RXD 计算机<调制解调器 接收资料 第 3 页,共 606 页TXD 计算机>调制解调器 传送资料 DTR 计算机>调制解调器 计算机告诉调制解调器:可以进行传输 GND 计算机=调制解调器 地线 DSR 计算机<调制解调器 调制解调器告诉计算机:一切准备就绪 RTS 计算机>调制解调器 计算机要求调制解调器将资料送出 CTS 计算机<调制解调器 调制解调器通知计算机可送出资料过来 RI 计算机<调制解调器 调制解调器通知计算机有电话进来 虽然 RS- 232 最早是为了和调制解调器作联机 但是发展到现 在 很多的设备也是透过 RS-232 和计算机作联机 其讯号流向也 是遵照上表的定义 因此妥善利用各脚位 计算机端就可以方便地 和各个设备作数据传输或联机控制了 通常与计算机连接的设备 最简单的沟通接口就是 RS-232 不 仅实作简单 而且价格上也便宜很多 在市面可见的数字相机 调 制解调器 很多都是使用 RS-232 作为与计算机沟通的接口 1-1-2 RS-485 通讯 由于串行通讯的简单易用 使得工业上也使用了串行通讯来作 为资料的流通之用 可是工业环境通常会有噪声干扰传输线路 在 以 RS-232 作为传输时就经常会受到外界的电气干扰而使得讯号发 生错误 另外 RS-232 的传输距离在不另加缓冲器的情形下 最长 也只有 15 公尺 为了要解决这些问题 RS-485 的通讯方式就应运 而生了 首先 RS-232 的讯号传输模式是如图 1-1-4 所示 第 4 页,共 606 页訊號電壓 傳送端 接收端 接地端1 接地端2 图 1-1-4 RS- 232 讯号传输模式 由图中可知 RS-232 的讯号电平乃是参考到地线而来的 传送 端参考到接地端 1 而据以传送资料 接收端则参考接地端 2 而还原 出传送端的讯号电平 在二个接地端同电位的前提下 传送与接收 端的讯号电平会呈现一样的结果 如果有噪声进到传输线路中的 话 可能会如图 1-1-5 所示 訊號電壓 傳送端 接收端 接地端1 接地端2 雜訊 原始訊號 干擾訊號 图 1-1-5 RS- 232 讯号与噪声 干扰的噪声讯号在地线及讯号上均会产生影响 原始讯号在加上噪 声后依然是传送到接收端 而地线部份的讯号则被地电平给平均掉 了 因此 讯号便发生了扭曲 当然讯号是整个都不对了 而 RS-485 的讯号传输方式如图 1-1-6 所示 第 5 页,共 606 页串音 地電壓 訊號電壓 傳送端 接收端 接地端1 接地端2 图 1-1-6 RS- 485 讯号传输方式 RS-485 的讯号将被传送出去时会先分成正负的二条线路 当到达接 收端后 再将讯号相减还原成原来的讯号 如果将原始的讯号标示 成 (DT) 而被分成后的讯号分别标示成(D+)及 (D-) 则原始的讯号 与离散的讯号在由传送端传送出去时的运算关系就如下 (DT)=(D+)- (D-) 同样地 接收端在接收到讯号后 也依上式的关系将讯号还原成原 来的样子 而 如果此线路受到干扰 其情形可能如图 1-1-7 串音 地電壓 訊號電壓 傳送端 接收端 接地端1 接地端2 雜訊 图 1-1-7 RS- 485 之讯号与噪声 第 6 页,共 606 页这时候在二条传输线上的讯号会分别成为(D+)+Noise 及 (D-)+Noise 如果接收端接收此讯号 它必须依照一定的方式将其合成 因此合 成的方程式如下 (DT)=[(D+)+Noise]- [(D- )+Noise] =(D+)- (D-) 此式与前一式的结果是一样的 所以使用 RS-485 网络可以有效地防止噪声的干扰 也因为此种 特性 工业上的应用比较适合此种串行传输的方式 第 7 页,共 606 页1-2 串行通讯讯号定义与字码 计算机并无法分辨所谓的数字或文字 在其内部的记录上 所 有的资料都是 0 与 1 任何的资料都是这两个数字的组合 想想看 现在的科技如此发达 竟然都是 0 与 1 的组合而已!? 1-2-1 讯号定义 唯有讯号定义正确才能对所传递的资料有共同的认知 所有电 子仪器的基础均来自于 开 关 两个状态的改变 我们可以 将它们表示为 0 1 或是 高电位 低电位 不管表示方法 如何 目的在于造成 状态的改变 将很多的 0 与 1 组合成一大 串的数列后 就可以定义它们所代表的意义为何了 既然是高低电位可以形成 0 1 因此由 0 1 的无数组合可以 有无穷的意义 但是何谓高电位?何谓低电位呢?任何的电子零件或 芯片 其高低电位的定义均不相同 可是相同性质的电子零件会有 一定的规定 大家遵守此规定 所作出来的芯片高低电位表现就会 一致了 当然 RS-232 也会有相关的电气电平定义 是大家共同遵 循的 ITU(国际电气通信协会)咨询机构 CCITT(国际通信电话咨询委 员会)公布了规格 RS-232C 之后由 EIA(美国电子工业协会)制定 很多国家都依此为模板 而日本亦依此制定了 JIS X5101 资料回 线终端装置和资料终端装置的接口 在此标准里 其 0 1 的规 定如图 1-2-1 所示 第 8 页,共 606 页+5V -5V +15V -15V 0 1 模糊區 +3V -3V +15V -15V 0 1 模糊區 雜訊範圍 雜訊範圍 信號準位 图 1-2-1 讯号电平 原定义是+5V~+15V 之间为 0 -5V~-15V 之间为 1 考虑到噪声 的干扰后 可允许的范围成为+3V~+15V 之间为 0 -3V~-15V 之间 为 1 如果电压电平落在 模糊区 此部份的电压将使得计算机 无法判断 可能会得到 0 但也有可能得到 1 如此得到的结果是 不可信的 因此所有信号必须明确地落于规定的范围内才行 在 RS-232 的规范中 电压在+3V~+15V(一般使用+6V)之间称为 “0”或 “Space“ 一般用途是作为 ”On” 电压在-3V~-15V(一般使用-6V) 之间称为“1”或 “Mark“ 一般用途是作为”Off” 有时候以“High” “Low“ 更能表现出其实际状况 1-2-2 字符对应 计算机内部的设计也是一样的 都是利用 高电位 与 低电 位 的状态改变而组合成一串的资料 因此 若以 RS-232 来说 讯号要被传送 一定要定义所谓的 0 或 1 的状态 个人计算机上的 讯号撷取卡等装置 其低电位是 0.7 伏以下 而高电位则是 2.1 伏 以上 根据实验的结果 计算机上的 RS-232 高电位 约 9 伏 而 低电位 则约-9 伏 如 此正负 9 伏的改变 被记录成 0 与 1 的状 态 因此 RS-232 串行通讯以正负 9 伏代表 0 1 的状态 这个范 围是符合规定的 第 9 页,共 606 页由于两部计算机之间的讯息传递是在一连串的高低电位电平 之间进行 每一个电压电平可以当成一个状态 这个状态可能是 0 或 1 计算机里面将每一个 0 或 1 称为一个位(Bit) 而 8 个位则形 成一个字节 将 8 个字节合起来后 共会有 2^8=256 种数值 其数 值从 0 到 255 而在计算机中便有一个 ASCII 码对照表 将此 256 种组合情况分别代表 256 种字符或句柄 而通讯的进行便是这 256 个字符或句柄的交换 大部份在盘键上的按键均是可见的 这些字符集中在 ASCII 码 的前 128 个字符内 而超过 ASCII 码 128 以上的字符是不可见字符 在我们的个人计算机屏幕上是看不到的 此部份的字符又称为二进 制 (Binary)资料 一般个人计算机或欧美仪器 均会使用到 256 个字符 其形式 几乎是二进制的资料 工业上常用的最普遍自动控制仪器是可程序 逻辑控制器(Programming Logical Controller PLC) 在其上所传输的 数据几乎是可见字符 也就是说 所传输的数据或句柄均会落在 ASCII 码 128 以内 128 以上的 ASCII 码几乎是没有使用的 针对 128 以上的 ASCII 码 日本便重新加以定义 将这些不可见字符的 位置留给假名使用 日本将这组被改编过的码与原来位于 128 之内 的 ASCII 码合在一起制定了 JIS 码 利用事先定义好的 ASCII 码 通讯双方所传输的高低电位组合 成一个字节后 便可以在 ASCII 码对照表中找到相对应的字符 比 方所传输的字节是如下的电位讯号”01001100” 是以二进制作为表 示的方法 它会是 16 进位的 4C 或是 10 进位的 76 依此数值去 ASCII 码对照表寻找的话 我们可以发现所代表的字符是 L 因此 表现在屏幕上的便是英文字母的 L 这个字 其它的字符也是如此换 算过来的 同样的 若 RS-232 的接收端接收到”01000001”的讯号 那么就 表示”A”字符被接收到了(可参考 ASCII 码得知) 同理也可以推到其 它的字符 另外要注意的是 ASCII 表中有部份字符是不可见的 即使接收到此字符也无法在屏幕上看到;在系统控制领域的使用上 这其中的一些字符通常被当成特殊的句柄来用(尤其在 PLC 的控制 通讯中更是如此) 这些句柄的使用有一般性原则 一般的产品设 计也和依照此原则 附录三中列出 ASCII 码前 128 个字符 当读者 第 10 页,共 606 页在所使用的产品应用手册中看到相关的术语 可在附录二中找到相 关的字符号码 第 11 页,共 606 页1-3 工作模式 通讯的形成是两方的资料互相流动 流动当然是经由一定的线 路来达成其目的 我们在马路上开车 有的时候会碰到 单行道 这时候只有一个方向可以通行 另一个方是不准有车子经过的;有的 时候 我们也会在上班的尖峰时段碰到所谓的 调拨车道 也就 是一个时间内只会有一个方向的车流在移动 而另一个时间则是由 另一个方向的车流移动;再另一种情形则是 双向车道 不管任何 时候 双方都可以有车流的程动 当计算机在作资料的传送与接收时 传输线上的资料流动情形 (也就是线上两端的沟通)也可以分成三种;当其上的资料流动只有 一个方向时 称为”单工” 也就是 单行道 ; 当资料的流动是双 向 但同一时间只能一个方向行进 称为”半双工”(Half Duplex) 也就是 调拨车道 ;当同时具有两个方向的传输能力时 我们称为” 全双工”(Full Duplex) 也就是 双向车道 以串行通讯来说 一 般会使用到的通讯型态中 RS-232 使用的是全双工的模式在进 行 ;RS-422及 RS-485则分别使用全双工 半双工 传输方式如图 1-3-1 所示 設備 1 單工 設備 2 時間 1 時間 2 半雙工 時間 1 時間 1 全雙工 图 1-3-1 工作模式 通常在市面上看到的 RS-485 属于半双工 而 RS-422 则属于全 双工;以 RS-232 来说 其为全双工 达到全双工的功能是因为其脚 第 12 页,共 606 页位在设计上就是接收与传送是分属二个不同的脚位与线路 不同的 工作模式可以应用在不同的地方 也各有其优点 不见得一个工作 模式就必须用在所有的串行通讯的场合 因此 同时可以利用的传输线路就决定了工作模式 RS-232 上 拥有二条个别的线路 其讯号电平乃是参考到地线而得 分别作为 资料的传送及接收 因此是全双工的模式 这种参考到地线而得到 讯号电平的传导方式称为单接点式(Single-Ended)输入 RS-485 上的 资料线路虽然也有二条 不过这二条线路却是一个讯号电平的正负 端 真正的讯号必须是二条线路的相减而得 因此在一个时间点内 只可以有一个方向的资料在传送 也就形成了的半双工的工作模 式 这种不参考地线而由二条讯号电平相减 以得到讯号电平的传 导方式称为差动式(Differential)输入 第 13 页,共 606 页1-4 传输速度 RS-232 通常用来作异步传输 既然是异步传输 双方并没有一 个参考的同步时脉可以当作基准 但若没有一个参考时脉的话 双 方所传送的高低电位到底代表几个位就不得而知了 要使得双方的 资料读取正常 就要考虑到传输速度 — 鲍率(BaudRate) 图 1-4-1 是 当传输的速度不一样时所产生的错误的情形 0 1 1 1111 00 0000 0 0 較快速度 較慢速度 原始訊號 原始訊號遭到扭曲 图 1-4-1 取样频率所产生的误差 原始讯号经过不一样的鲍率取样后 所得的结果完全不一样 如上图 取样速度只有原来的一半时 讯号被跳着取样 资料因此 错误 因此通讯双方取得一样的通讯速度是首先要作的事情 串行通讯的传输受到通讯双方配备性能及通信线路之特性所 左右 我们通常将传输速度称为 BPS 指的是每一秒所传送的位数 (Bit per Second) 我们经常可以看到仪器或 Modem 的规格书上都写 着 19200bps 38400bps 所指的也就是传输速度 异步之串行传输 IC 称为 UART(Universal Asynchronous Receiver-Transmitter) 如果该 IC 亦可时作同步传输 则称为 USART(Universal Synchronous/Asynchronous Receiver-Transmitter) IC 内各有一颗数据发送器与接收器 此 UART 芯片的型式相当多 而 随着科技的进步 IC 所能传送/接收的速度也不断地增加 亦即在 硬件线路上的资料流量不断地增加 在 386 计算机的时代 计算机 上所配备的串行通讯 IC 大部份为 16C450 而现在的计算机 其串 第 14 页,共 606 页行通讯 IC 的基本配备是高速传输芯片 16C550 此芯片上配备有 16 位的输出及输入缓冲区 更适合作高速的传输工作 第 15 页,共 606 页1-5 串行通讯端口的比较 以下再将 RS-232 RS-422 RS-485 三种串行通讯方式的相关参 数比较如表 1-5-1 表 1-5-1 传输模式比较 标准类别 RS-232 RS-422 RS-485 讯号模式 单接点式 差动式 差动式 接线方式 9 线 /3 线 4 线 /2 线 2 线 最高驱动端数目 1 1 32 最高接收端数目 1 10 32 传输长度 15m 1200m 1200m 传输速率 19200bps 10Mbps 10Mbps 传输型式 全双工 全双工 半双工 在上述的表格中 最高驱动端数目及最高接收端数目视制造厂 而定 所谓的最高驱动侧指的是可以发送字符串或是命令的模块数 目 而其有数目的限制的原因乃是因驱动 IC 能力的限制 当驱动 IC 的性能改善后 此数目也会随着升高 其它的比较再说明如下 1、 传输长度 基于驱动 IC 的限制 RS-232 最 长的传输距离在 15 公尺左右已是其极限 若再加长的话 会使得传输线上的电压 产生压降现象 一个原来是高电位的讯号电平可能就因此而成 为低电位了 而使得接收的一方接收到的讯号产生错误 RS-485 传输的距离就比较长一些 因其所供的讯号能量的不同 最长的传输距离可以达到 1200 公尺 另一方面 485 采用了差 动式的传输方法 也使得噪声的干扰降低了不少 2、 传输速率 传送与接收的双方使用相同的速率才能使资料正确 地交换 而此传输速率与串行通讯 IC 设备上的时脉产生装置所 能产生的时脉有关 而且也与传输长度息息相关 当传输的长 度 增加时 传输的速率必须降低才能使资料的接收正常 此乃 因长度增加使得传输的品质降低 较快的速率会使得接收的一 方会接收到噪声数据 根据实验的结果 当使用分布式模块进 行离距传输测试时 原来在几公尺内的 115BPS 的传输速率在长 度变成 1200 公尺时会剩下 2400BPS 第 16 页,共 606 页3、 传输型式 传统的 RS-232 使用全双工式的传输方式作资料的交 换 因此其硬件的线路配置上是接收与传送均有一脚位搭配 RS-485 则是采用二线式的差动传输方式作资料的传送 二条线 的讯号是一正一负 这二条线的传送/接收过程中 只有一个方 向会成立—不是正在接收就是正在传送 第 17 页,共 606 页1-6 通讯端口的初始化 既然是通讯 双方当然得说明白到底是如何传送资料或命令 的 否则双方没有一套共同的译码方式 恐怕无法了解对方所传过 来资料的意义 因此 双方为了可以沟通起见 必须遵守一定的通 讯规则 这个共同的规则就是通讯端口的初始化 通讯端口的初始化有以下数项须设定: (1) 资料的传送单位:串行通讯端口所传送的资料是字符型态 工业界使用到的有 ASCII 字符码及 JIS 字符码 ASCII 码使 用了 8 个位形成一个字符 而 JIS 码则以 7 个位形成一个字 符 我们可以发现 欧美的设备多使用 8 个位的资料组 而 日本的设备则多使用 7 个位为一个资料组 以实际的 RS-232 传输上看来 由于工业界常使用的 PLC 大多只是传送文字 码 因此只要 7 个位就可以将 ASCII 码的 0~127 号字码表达 出来(2^7=128 共有 128 种组合方式) 所有的可见字符也落 在此范围内 所以只要 7 个资料位数就够了 不同的情形下 (端看所使用的协议) 会使用到不同的传送单位 使用了多 少个位合成一个字节必须先行确定 (2) 起始位及停止位:当双方准备要开始传送资料时 发送端会 在所送出的字符前后分别加上低电位的起始位及高电位的 停止位 接收端会依起始位及停止位的设定确实地接收到字 符 ;当加入了起始位及停止位也才比较容易达到多字符的接 收能力 起始位固定为 1 个位 而停止位则有 1 1.5 2 个 位等多种选择 只要通讯双方协议通过即可 (3)同位位的检查:同位位是用来检查所传送资料的正确性的一 种核对码 这之中又分成奇同位(Odd Parity)及偶同位 (Even Parity)两种 分别是检查字符码中 1 的数目是奇数或偶数 以偶同位为例 A 的 ASCII 码是 41H(16 进位) 将它以 2 进 位表示时 是 01000001 其中 1 的数目是 2 因此同位位便是 0 使 1 的数目保持偶数 同样的 同位位是奇同位时 A 的同位位便是 1 使 1 的数目保持在奇数 我们再以图 1-6-1 予以表示 第 18 页,共 606 页10011100 狀態1的數目有4個 若設偶同位 原狀態 100111000 100111001若設奇同位 應傳送 應傳送 图 1-6-1 同位位的意义 将传送字符依上述的说明组合起来之后 就形成了传输的资料 格式 在串行通讯上的资料格式如下表示 起始位+传送字符+同位位+停止位 因此 假设在传输时用了 1 个起始位 传送字符为 8 个位 1 个停止位 不使用同位位检查 这时 每次所传输的资料位格式为 1 个起始位+8 个资料位+0 个同位位+1 停止位 总共有 10 个位 所以此时最小的传输单元是以 10 位为单位 如果采用不同的资料位数 同位位检查 停止位 则每次传输的字 节中的位数都不相同 另外 我们也可以从传输速度算出实际上的传输字符数;假设资 料格式为以下的格式 1 个起始位+8 个资料位+0 个同位位+1 停止位 总共有 10 个位 若我们采用 19200bps 的传输速度 每一秒便 可以传输 19200/10=1920(Byte) 的资料 一般容易弄混淆的地方就 是 一个字节是 8 个位 所以 19200bps 的传输速率每秒可以传送 2400 个字节的资料(19200/8=2400) 可是我们从上面的讨论中已经 很清楚地了解到实际的传输速率是每秒钟 1920 个字节 这个数字 就 是一个不小的差异了 那么这样子的差异有任何值得我们重视的 第 19 页,共 606 页地方吗?试想 如果我们今天使用 RS-232 的传输技术建立一个控制 系统 我们如何告诉别人系统的效能在那里?因为任何的传输都需 要时间 一个命令下达到机器或控制器后 在其执行结果响应到主 控站前的传输时就是我们所谓的系统效能 时间愈短 愈能显示出 系统的卓越性能 所以这种时间的计算在我们评估系统的效能时相 当地重要 当我们下达命令要求设备传送资料回计算机时 在命令到达设 备后 设备不可能随即将资料传送完成 最起码设备的资料传送需 依据速度及传输量予以计算; 以上面的计算为例 如果我们以 19200bps 的速度传送 1920 字节的资料 我们就需要等待 1 秒钟的时 间 在时间未达 1 秒钟 资料是不可能由设备至计算机间传送完成 的 因此等待时间未达 1 秒前 所读取的资料是不完整的 不可拿 来作处理 第 20 页,共 606 页1-7 接线方法 RS-232 的接线方式如图 1-7-1 所示 電腦上為公頭 連接線上為母頭 1 2 3 4 5 6 7 8 9 12345 6789 2 (RXD) 3 (TXD) 5 (GND) 4 (DTR) 1 (DCD) 6 (DSR) 7 (RTS) 8 (CTS) 9 (RI) 9 Pin 9 Pin 3 (TXD) 2 (RXD) 5 (GND) 6 (DSR) 1 (DCD) 4 (DTR) 8 (CTS) 7 (RTS) 9 (RI) NULL Modem 腳位配線圖 图 1-7-1 传输接线图 计算机设备上的 RS-232 脚位必定是公头 当然连接线就是母头 了 部份的计算机上面也有 25 脚的 RS-232 与打印机连接端口的 25 脚是一样的脚位 不过 只要是 RS-232 其于计算机上的脚位 必定是公头 不 管是 9 脚或是 25 脚 此规则是不变的 打印机的 接头必是母头 因此 即使是 25 脚的接头 我们也可以轻易地辨 别出何者是 RS-232 何者是打印机连接端口了 当使用 RS-232 与调制解调器作联机时 我们只需将调制解调器 上所附的 RS-232 连接线把计算机上的 RS-232 与调制解调器上的 D 型接头连接起来就可以了 也不需要考虑太多 可是当我们希望以 RS-232 与一般的设备或是其它的个人计算机作通讯实验时 就必须 先作跳线的工作 一般所谓的 NULL Modem 其实就是为了欺骗计 算机 让计算机以为它所连接的是一部调制解调器 而能够顺利地 进行数据传输 ; 因此 NULL Modem 就是将 RS-232 另一端与 Modem 相连的部份作一些线路变更 而能让另一部计算机连接 成为一部 虚拟的 Modem 第 21 页,共 606 页RS-485 的接线方式就有一些不一样了 若不考虑电源的供给的 话 实际上的 RS-485 系统只需要 2 条线就可以连接所有的设备 若设备所需的电源一并算入的话 也只要四条也就够了 二者均是 串行通讯的一种 一般在使用 RS-485 时除了可以使用 RS-232 与 RS-485 转换器之外也可以使用 RS-485 专用适配卡 而由于 RS-232 与 RS-485 的电平不同 不管是转换器或适配卡都有相关的芯片会 作这二个接口的电平转换 图 1-7-2 就是 RS-232 与 RS-485 转换的 模块之一 图 1-7-2 RS- 232/RS- 485 讯号电平转换模块 第 22 页,共 606 页本章习题 1、 使用 RS-232 最少需要几条讯号线? 2、 请说明使用 DTR 硬件交握的程序? 3、 异步传输的资料格式包含了几项? 4、 如果使用 9600bps 偶同位位检查 8 个位的资料 2 个位的停 止位来传输资料 那么传输一个含有 800 个字节的字符串需要 多久的时间? 第 23 页,共 606 页第二章 Visual Basic 及常用组件简介 自从微软推出了 Visual Basic 后 使得窗口程序的设计难度大 大降低 使用 Visual Basic 进行系统开发的人数也是笔直地增加 它提供了相当简单的途径给想设计窗口程序的设计师 本书使用 Visual Basic 来作分布式数据撷取控制模块的控制 但并不专门讨论 Visual Basic 的详细操作细节 仅重点式地列出部 份内容 另外 本书会针对书中所提到的控制模块 监控流程 其 与程序有关的部份作详细的讨论与研究 但对基本的 Visual Basic 的操作则着墨较少 若读者尚不了解 Visual Basic 的使用 请参考 本书附录所列的部份参考书藉 这些书应该对读者会有相当的帮 助 2-1 窗口程序概念简述 自从窗口操作系统兴起后 计算机的使用就改观了 一切的作 业都是在图形化的操作系统中进行 不仅由于窗口环境与以前的 DOS 作业环境非常不一样 就连概念上都大相径庭 2-1-1 对象的概念 在真实的世界中 我们时时刻刻都在接触 对象 所谓的对 象就是一个真实存有的物品 例如 我们想组装一台计算机 我们 会到计算机大卖场去买一些必须的零件 如屏幕 主机板 内存 硬盘 软盘 键 盘 等等 全部带回家后 拿起起子就准备组装了 在这样的例子里 所买的零件全部都是 对象 而被我们组装完 成的整部计算机也是 对象 所以一个大对象是可以由其它的小 对象组装而成 软件开发也愈来愈趋近真实世界 现在的窗口操作系统的构成 也是依照真实世界的模式建构 我们看到的操作系统可以被看成是 一个大对象 里面就蕴藏了无数的小对象 我们看到的 Mouse 窗 体 按钮 打印机 图形 日期显示 都是对象 而且这些是在 Windows 里经常看到的 只要是在 Windows 系统中可以被叫得出来 第 24 页,共 606 页的 就可以被称为对象 在构想系统时 我们会想到一个系统应该 有一个表现的地方 那是一个窗体 也会想到需要放几个命令按钮 再安排一个放项目的地方(那会是一个 ListBox 或 Combo Box) 也 许再来一个文字书写及显示的方框(就是文字框) 以上所提到的 都是经常在各个应用程序中所看到的各种外观 这些都是 对象 虽然都是程序所创造出来的 可是都被看成是对象一般地控制着 接下来的就是利用程序来操作这些所谓的 对象 了 让这些对象 随着使用者的意思而进行既定的工作 想要在窗口操作系统中写程序 也和以往在 DOS 下完全地不 同 现在的程序设计必须考虑对象 而对象的考虑上也必须参考量 属性 事件与 方法等三个与对象息息相关的名词 不管使用那一种程序语言 建立系统的程序是大同小异的 它们 是 分析问题 设计解决步骤 撰写程序 验证程序等四个步骤 就 Visual Basic 来说亦是如此 不过 由于它是一个窗口程序的开发 工具 因此虽然程序是一样 但与以前的 DOS 下的程序设计比较 在实作上就必须考虑以下的问题了 1、 应给使用者看什么样的画面? 2、 使用者如何操作画面? 3、 如何让使用者和程序产生关联? 4、 如何作程序设计? 在使用 Visual Basic 设计窗口程序时 其设计的方向大约分成 二个部份画面设计和 程序撰写 其中的画面设计是与之前的设计程 序最大不同的地方 程序撰写则和以前没什么差异 这二个部份的 先后顺序是先作画面设计 再作程序撰写 2-1-2 接口成员 车子是一个对象 当描述这部车子的相关特性时 我们也许会 使用诸如 这是四门房车 它拥有 ABS 具安全气囊 等等的叙述 这些叙述都是用来描述该部车子的 我们称这些是该 部车子的特性 以 VB 的语言来说 它们就叫作 属性 车子被 创造出来后 当然是被拿来给人们使用的 而人们在使用的过程中 就和车子有了互动 例如我们将车门打开 踩下油门 转动方向盘 等等 这些动作都是由操作车子的人员给予车的一种输入 这种施 加到车子的动作 对于车子来说就是一种 事件 针对这些驾驶 第 25 页,共 606 页施加到车子的事件 车子在被设计之初也都设想到了 诸如当油门 被踩下后 连杆动作发生 拉索被拉动 接着一连串的动作后引擎 的转速就增加了 而方向盘转动时 也是一连串的动作后 轮胎就 顺着一定的方向作转动 这些种种的呈现 都是因为驾驶对车子 作了一些输入 对于车子产生事件 而车子针对这些事件所作的最 后响应 这些响应用程序的语言来说 就是子程序 或是函式 也 称之为 方法 以上所提到的 属性 事件 方法 就 是在 Visual Basic 里面的接口成员 Visual Basic 的线上说明中也经常会出现这些相关的讯息 例 如 MSDN 中的画面里 如果我们查看某一对象的说明 就会看到如 图 2- 1- 1 的画面 图 2-1-1 组件相关说明 了解接口成员的意义 再去看线上说明时 就比较能够知道程 序设计的大概 而透过这些接口成员的运作 程序设计就像是在操 作实际的对象一般 由最实际的方面去想象 最后当然还是要由程 序来完成我们脑中的想象 不过 如此的对象化思考过程其实对于 系统设计人员来说是相当地重要而直接 2-1-3 以 Visual Basic 发展系统 执行 VB 光盘片上的 Setup.exe 执行档后 依照 VB 安装提示的 指引 一步步作完安装项目的选择 便可以成功地装 VB 安装至计 算机里面 欲执行 VB 必须在计算机左下角的”开始”\”程序集” 第 26 页,共 606 页\”Microsoft Visual Basic 6.0”中选择 Microsoft Visual Basic 6.0 的程序 如图 2-1-2 所示 图 2-1-2 激活 Visual Basic 6.0 Visual Basic 是一个所见即所得的快速开发软件工具 在实际 的发展流程上 它分成二个部份 画面设计及程序撰写 画面设计 的结果就会是未来程序执行时使用者可以看到的样子 在现在的设 计中 引用了大量的可视化组件 这些可视化组件可以有效地简化 我们的设计工作 完成了可视化的画面设计后 接下来只要将相关 的动作流程以循序的程序代码予以完成即可 选择 Microsoft Visual Basic 6.0 后 随即出现如图 2- 1- 3 的选择画面 图 2-1-3 项目建立选项 在上图中有许多的选项 在不同的目的下必须选择不同的发展 项目 通常我们选择 标准执行档 作为开发的项目 此类项目被 开发出来后可以编译成窗口环境下的执行文件 并且可以作成安装 程序 散发到其它的计算机上执行 选择了标准执行档后 接着出现设计环境的画面如图 2-1-4 第 27 页,共 606 页 图 2-1-4 项目发展环境 图中的最上方是菜单及工具列 用来执行相关的功能 另外图 中标出 5 个区域 以下分别说明此 5 个区域 工具箱 设计系统时所需要的可视化对象及功能性对象的集中 摆放处 当我们设计画面或是功能时 一定会需要各式各样由微软 或是其它的协力厂商(3rd-party)所制作的对象 我们称这些被用来 设计系统的对象为控件 透过这些控件的运作 我们就可以轻松地 设计所需的画面或功能 新增多个控件后 工具箱中的控件就会多 起来 看起来就可能像图 2-1-5 图 2-1-5 工具箱及控件的新增 项目一开始并不会出现所有 Visual Basic 所提供的控件 只有出现 部份基本的控件 其它的控件在需要引用时 必须透过 设定使用 组件 的方式来加入到工具箱里面 第 28 页,共 606 页项目管理区 现在的系统设计不像以前那样使用一 二支程序 就可以搞定 通常一个系统程序需要的内容是相当多的 这些内容 被 项目总管所管理 其内容包含有专门处理画面的窗体(Form)集 合 专门处理函式的模块(Module)集合 专门处理类别(Class)的集 合 也包含有处理资源 自制的使用者控件等等 每个不同的部份 都被分开 以便于程序设计者可以一目了然 当欲处理某一部份时 只要在项目总管里双击该项目即可 系统发展一大起来 项目总管 就可能会像图 2- 1- 6 的样子 图 2- 1- 6 项目总管内的各区 实时运算区 写程序总是会遇到困难或是瓶颈 一旦有问题时 必须能够在必要的地方中断程序的执行 然后找到问题的所在点 予以解决 要找到问题点 通常需要进行一些程序代码的测试 或 是表达式的结果查验 这时就需要实时运算窗口提供的环境来作程 序代码的试算 在断点处之前存在(指的是还在内存中而未被消灭) 的任何变量都可以拿来作运算 结果也会马上出来 这提供了程序 开发者一个良好的测试工具 是开发系统程序所不可或缺的项目 运算过程中的画面就如图 2-1-7 第 29 页,共 606 页 图 2-1-7 实时运算窗口 画面设计区 真正的画面就在这里被系统设计者所摆放上去的 这上面会摆放着设计者由工具箱中所选择的各种控件 各种的控件 有各式各样不一样的功能及画面 设计时必须了解到自己的需求及 控件的内容 才会设计时比较符合需求的画面来 每一个画面设计 区都称一个 窗体 (Form) 也相当于在 Windows 环境中所看到的 一个画面 如果设计的系统所需的画面为 10 个 那么在设计时就 需要设计 10 个画面来符合系统的需求 需要增加时 只要在项目 总管中按鼠标右键 选择新增窗体即可 通常每个窗体中的控件都 是完全独立 互不干扰 如图 2- 1- 8 的画面 图 2-1-8 二个不同功能的窗体 程序设计区 此部份是真正写入程序代码的地方 不管画面设 计得如何 最终总是要在这个地将系统要执行的步骤写入 程序代 码的写作并不是类似一张流程图般从头写而尾 一个开始 最后一 个结束 而是针对某一个对象的某一个事件发生时 该对象应该要 第 30 页,共 606 页有什么样的反应或作为来写程序代码 程序代码的写作方式完全采 用事件驱动的方式 一旦该事件引发后便执行此段我们预先写入的 程序代码 当此段程序代码执行完毕后 系统就处于闲置状态 例 如一个按钮被按下了 我们也许在这个按下的动作的事件里头写了 一个激活声霸卡播放一段音乐的程序 当这段程序被执行完毕 除 了原本就安排的固定动作外(如定时器的固定转询动作) 系统便不 再执行其它的程序了 设计的样子如图 2-1-9 每一个程序区段以 Sub…End Sub 分隔开 图 2-1-9 程序编辑窗口 2-1-4 VB 的设计模式 Visual Basic 分成几种模式 设计模式 执行模式及中断模式 设计画面 撰写程序代码时所处的环境称之为设计模式 这很类似 于我们在以前 DOS 发展程序的整合发展环境 设计者想法均是在 此环境中先形成 当程序发展到一个阶段后 就可以进入执行模式 测试一下执行的结果 此模式下的结果一般会与编译成为执行档后 执行的结果一样(部份 API 例外) 而中断模式则是当设计者在程序 中设下断点 而执行的过程中遇到此行时 程序即在此中止 等待 设计者的下一步指令 设计者可以继续执行 单步执行 跨函式执 行或进行断点前的变量运算 如图 2- 1- 10 所示 第 31 页,共 606 页 图 2-1-10 三种模式 整个系统的发展过程中 就是在这三种模式间不断交错进行 着 2-1-5 发展项目步骤 面对 Visual Basic 看似复杂的环境 也许会有令人望之却步的 感觉 其实了解程序后 很容易上手的 Visual Basic 的开发环境 分成二个部份 一个是设计模式 一个是执行模式 程序在设计模 式下被开发至一定程度后 就可以在执行模式下观看程序执行的结 果 以作为修正的参考 面对开启的项目 以及上述的几个区域 以下是一个发展的顺 序 供读者作个参考 1、 拉动窗体的画面至适当的大小 窗体是我们主要的门面 也会 是我们最早接触到的 首先是将此窗体拉至我们希望执行起来 所想看到的样子 需要的外观控件可由工具箱中取出来 当在 工具箱中找到希望的控件时 首先点选 接着在画面上以拖拉 (Drag- Drop)的方式拉出希望的外观 2、 对象被放到画面上 并确定大小位置后 接着按下 F4 叫出属性 窗口改变重要属性 首先是 Name 属性的变更 程序撰写过程 中控件的操作就要依此名称而决定 其它的属性依需要而定 例如 外观颜色希望不一样时 此时需要改变 Color 的相关属 第 32 页,共 606 页性 显示在控件的文字需改变时 此时要改变 Caption 属性 等等 通常只需要改变部份的属性即可 很多的属性都只要保 持默认值就可以了 3、 不断地将需要的控件以拖拉的方式放到窗体上 并且设定各个 对象的重要属性 外观 什么是所需要的控件呢?读者必须在心 里先有个底 有个大概的样子后 再由工具箱中取出 4、 考虑画面上的各个控件之间的排列关系 排出读者认为最好看 的样子 5、 每一个画面上的对象的设计都是为了可以和系统的使用者产生 互动 构思每一个对象的动作就是接下来的步骤了 6、 把动作写入程序代码编辑窗口中 在程序代码的编写过程中 均是以对象和外界的互动为第一参考量的重点 再以此发展下 去 例如 某一个按钮被按下后 画面上绘图一个曲线 当使 用者在曲线上点一下鼠标的左键时 出现一个十字光标 而点 下鼠标的右键时 出现一个弹出式的选单 诸如此类的参考量 在上述的例子中 我们就必须在按钮的 Click 事件中写入绘图 的函式 而在图形的 MouseDown 事件中写入绘线及弹出式菜 单的程序代码 依照这样的思绪 一步步地把程序代码完成 7、 完成后的项目 可以按下 F5 或菜单上的执行选项将项目执行起 来 就可以看到项目执行后的画面结果 试着操作各控件及其 事件的反应 也可以了解设计的事件程序是否恰当 8、 测 试的过程通常需要不少的时间 在修改与测试的过程中 中 断是一项经常被使用到的功能 将光标停留在需要中断的某一 行 按下 F9 即可订定断点 一旦程序被执行至此 即会产生 中断而停止执行 这时可在实时运算窗口中进行各项参数的测 试 也可按下 F8 使程序单步执行下去 或直接按下 F5 立刻让 程序执行下去 9、 需要修改程序时 可从执行模式回到设计模式 待修改完毕后 再按 F5 进入执行模式 通常我们需要在二种模式之间不断切 换 直到系统的执行结果符合我们的要求 第 33 页,共 606 页10、 完成项目的设计工作后 再利用 Visual Basic 提供的 封装暨 部署精灵 制作项目的安装程序 至此才算完成项目的制作 2-2 Visual Basic 的组件引用 开启 Visual Basic 后出现的工具箱通常只有一些预设的控件 如果建立的是简单的项目 也许这些就够了 如果要建立的项目比 较复杂 或希望项目的成果可以像一般的窗口程序一样光彩耀眼 功能较多时 那就有必要引入较多的其它控件 这就是组件的引用 2-2-1 引用步骤 刚开启的工具箱中的 工具 不够用时 必须在 项目 \ 设 定使用组件 选择适当的控件档案 程序如图 2- 2- 1 图 2-2-1 引用控件步骤 透过上述的 3 个 步骤 工具箱中就会出现我们选择的控件的图 标 点选此图标并在窗体上拖拉 即可在系统中设计出所需的画面 或功能 第 34 页,共 606 页2-2-2 如何了解控件 Visual Basic 中可以使用的控件非常地多 针对不同的功能或 是画面 都会有相对的控件可以运用 VB 的版本中 除了普及版 可使用的控件较少外 专业版及企业版都相当多 其实任何的参考 书都不容易将所有的控件介绍详尽 不过常见及使用率高的控件都 会在一般的参考书上找到解释 部份的参考书针对书本上的特殊功 能也会介绍一些特殊的控件 最好的参考资料 笔者认为还是微软 的线上使用手册 其中对于内建控件及扩充控件的基本概念介绍得 相当仔细 而对于属性 事件 方法也都有详尽的列举及介绍 当 然也会举部份的例子作为使用范例 想要彻底了解各个控件的话 还是看看 MSDN 吧 !只要把握住基本概念 再加上牢记 对象 属性 事件 方法 的思考顺序和努力测试 控件的使 用应该是不成问题的 2-2-3 组件的分类 VB 内的使用组件大概可以分成执行时期可见组件及执行时期 不可见组件二大类 所谓的执行时期可见组件指的是在设计模式及执行模式下均 可看见其样子的控件 这类的控件通常被用来设计画面 诸如卷标 文字框 图 形框 按钮 工具列 等等 均属于此类组件 执行时 期不可见组件指的是在设计模式可以被设计者看见(否则设计者如 何知道已经引入控件) 而在执行模式下看不见其样子的控件 这 类的控件通常为系统提供特定的功能 此功能不需画面的特别支 持 即可完成其份内的工作 诸如定时器 通讯控件 网络传输控 件 等等 均属于此类组件 如图 2- 2- 2 第 35 页,共 606 页 图 2-2-2 可见与不可见组件 图 2-2-2 中的通讯控件(长得像电话盒的样子)和定时器控件(长 得像马表的样子)在设计阶段时均可看见 但在执行模式下就看不 见了 除了这二个控件外 此画面中的其它控件在设计时期与执行 时期均为可见 2-3 常用组件介绍 在本书的实验范例中 经常使用到的控件 将会在本节中予以 介绍 而在各个实验中可能就不会花太多的篇幅在这些基本的控件 上 希望能读者有帮助 2-3-1 卷标控件 VB 工具箱中的卷标控件(Label) 专用于提供显示文数字之用 在工具箱中的位置及外观如图 2- 3- 1 所示 第 36 页,共 606 页 图 2-3-1 Label 的位置及外观 使用卷标控件设计在画面上的步骤如图 2- 3- 2 图 2-3-2 Label 的使用过程 表 2- 3- 1 Label 常用属性 常用属性 说明 Name 对象名称 物放摆放完成后给予 是 第一个必须要设定的属性 Caption 卷标的标题 Font 显示字型的设定 卷标控件通常配合其它的对象一起使用 而提供其它对象的简 要说明标示之用 由于通常被用来标示之用 事件的使用就比较不 频繁 如图 2-3-3 即是用以标示一个文字框的用途 第 37 页,共 606 页 图 2-3-3 Label 和文字框的搭配 2-3-2 按钮控件 此控件是使用率最高的控件 用于提供一个给系统使用者操作 用的按钮 在工具箱中的位置及外观如图 2- 3- 4 图 2-3-4 按钮控件的位置及外观 其拖拉设计的步骤与上一小即的控件相同 而重要的属性则如 表 2- 3- 2 表 2- 3- 2 按钮控件常用属性 常用属性 说明 Name 对象名称 物放摆放完成后给予 是第一个必须要设定 的属性 Caption 出现在按钮上的文字标题 Picture 希望出现在按钮上的图片 一般使用 ICON(32*32 像素) 较多 设定图文件后 还需设定 Style 为图形外观 Style 决定是否出现图形外观 或只是文字外观 调整好外观及相关属性后 由于按钮的目的是让使用者按下 并进而执行某些功能 因此还需在按钮控件的 Click 事 件中写入程 序代码 执行一些系统的动作 第 38 页,共 606 页图 2- 3-5 是一个设计的例子 画面将一个按钮安排妥当后 双 击按钮即可在其事件程序中选择一个事件区段 将欲执行的程序代 码写入 图 2- 3- 5 按钮控件的设计及事件 2-3-3 定时器控件 定时器的功能用于达到程序的自动化 其最大的效果在于固定 的时间内会执行所设定好的程序代码一次 比较特别的是 它只提 供定时的功能 但没有相关的画面 因此属于执行时期不可见组件 它在工具箱中的位置及外观如图 2- 3- 6 所示 图 2-3-5 定时器控件的外观及位置 第 39 页,共 606 页常用属性说明如表 2-3-3 表 2-3-3 定时器常用的属性 常用属性 说明 Name 对象名称 物放摆放完成后给予 是第一个必须要设定 的属性 Interval 定时器的间隔时间 每隔此一时间 定时器内的程序代 码就会被执行一次 Enabled 决定是否激活定时器 一旦设定其 Enabled 属性为 True 后 (一开始就设定或程序过程 中设定均可) 定时器内的程序代码就会被定时执行 而程序代码 必须是写在定时器的 Timer 事件程序中的 如图 2- 3- 6 图 2-3-6 定时器及其唯一事件 2-3-4 图片框控件 当系统必须有部份的结果需要使用图形的方式表现时 图片框 是一个很好的选择 它允许我们以程序的方式在其内部绘图 此控 件在工具箱中的位置及外观如图 2- 3- 7 第 40 页,共 606 页 图 2-3-7 图片框控件的位置及外观 常用属性说明如下表 2-3-4 表 2-3-4 图片框控件常用属性 常用属性 说明 Name 对象名称 物放摆放完成后给予 是第一个必须要设定 的属性 BackColor 图形框的背景颜色 以能明显表现差异为原则 ForeColor 图形框的前景颜色 以能明显表现差异为原则 大部份的绘图工作是在程序中完成的 只要在程序中指明绘图 的对象为此图片框 则绘图的结果均将显示在此图中 如图 2-3- 8 所示 图 2- 3- 8 图片框之设计 程序代码及绘图结果 第 41 页,共 606 页2-3-5 选项(Option)控件 有时候我们也需要在程序设计时 让使用者可以针对项目作必 要的选择 如果众多的选择中只能选其中的一个 这时必须使用 Option 控件 让众多的选择中只择其一 此控件在工具箱中的位置 及外观如图 2- 3- 9 所示 图 2-3-9 Option 控件及位置及外观 常用属性说明如表 2-3-5 表 2-3-5 Option 控件常用的属性 常用属性 说明 Name 对象名称 物放摆放完成后给予 是第一个必须要设定 的属性 Caption 显示在此对象的标题文字 用以提醒使用者 Value 决定是否选中此项 当为 True 时 圆圈中出现实心黑点 表示选择此项 若其它项被选中 则为空白圆 通常很多项目一起作选择时 习惯把 Name 属性设成一样 使 这些控件变成数组 程序上也会比较简洁 设计上如图 2-3-10 图 2-3-10 相关的选项放在一起 图 2-3-10 就是用以选择 COM1 或 COM2 的一个设计例 第 42 页,共 606 页2-3-6 框架(Frame)控件 系统设计时所使用的控件一般说来都不少 控件一多就使得画 面看起来比较杂乱 使用框架控件可以将部份的控件集合起来 感 觉比较整齐 此控件在工具箱中的位置及外观如图 2-3-11 所示 图 2-3-11 Frame 控件的位置及外观 此控件又被称为 Container 意为包含 也就是其它的控件可以 被含在这个控件里面 常用属性如表 2- 3- 6 表 2- 3- 6 Frame 常用属性 常用属性 说明 Name 对象名称 物放摆放完成后给予 是第一个必须要设定 的属性 Caption 出现在 Frame 上方的标题文字 Font 决定 Frame 标题文字的字型 ForeColor 决定标题文字的颜色 上一小节中的通讯端口选择图例中 将二个通讯端口选择项框 住的就是 Frame 控件 2-3-7 几何图形(Shape)控件 程序设计中若需使用到几何图形 就需引用 Shape 控件 此控 件在工具箱中的位置及外观如图 2- 3- 12 第 43 页,共 606 页 图 2-3-12 Shape 控件的位置及外观 常用属性如表 2-3-7 表 2-3-7 Shape 控制常用的属性 常用属性 说明 Name 对象名称 物放摆放完成后给予 是第一个必须要设定 的属性 FillColor 该控件的填满颜色 FillStyle 填满的型式选择 Shape 决定此控件的几何形状为何 以本书而言 最常使用此控件作为灯号的显示 以改变 FillColor 的方式就可以动态地改换显示的颜色 相当方便 设计及使用如图 2- 3- 13 图 2-3-13 Shape 控件的使用 第 44 页,共 606 页2-3-8 文字框控件 让使用者最方便的输入方式 就属文字框了 其主要的工作就 是输入文字或显示文字的结果 此控件在工具箱中的位置及外观如 图 2- 3- 14 图 2-3-14 文字框控件的位置及外观 常用属性如表 2-3-8 所示 表 2-3-8 文字框常用属性 常用属性 说明 Name 对象名称 物放摆放完成后给予 是第一个必须要设定 的属性 Font 决定字型 ForeColor 决定字型颜色 Text 决定文字框内的内容 使用 Text 属性 就可以设定文字框的内容或读取其内容 2-4 串行通讯组件介绍 串行通讯是本书的一个重点 当然少不了要介绍这个相当重要 的控件 VB 的版本分为普及版 专业版及企业版 每一个版本都 为了适应不同的使用族群 普及版特别适合刚刚学习可视化设计系 统的人 而专业版及企业版则适合专业的系统设计人员 串行通讯 的组件是包含在专业版及企业版中 如果使用的是普及版的话 是 第 45 页,共 606 页无法使用串行通讯控件来作系统设计的工作 即使是将此组件由其 它的计算机中拷贝过来也是不行的 此点是请读者注意的地方 2-4-1 通讯控件的引用 与其它的控件相同的引用程序 在项目开始设计之前 到 项 目 \ 设定使用组件 里面指定 MSComm32.OCX 并开启旧档 如此就完成了组件的引用程序 引用成功的通讯组件 在工具箱中 的图标及位置如图 2- 4- 1 图 2-4-1 通讯控件的位置及外观 若如果无法在 设定使用组件 的程序中引用成功 也可以使 用该程序中的 浏览 功能 到 Windows\System 目录下去找 MSComm32.ocx 并开启它 2-4-2 通讯控件的属性 通讯控件的属性众多 笔者将其重要属性说明如下: CommPort:设定或传回通讯连接端口代号 程序必须指定所要使用 的串行端口的号码 Windows 系统会使用所设定的通讯端口与外界 作通讯 程序亦可经由此属性读回所使用的连接端口号码 在此所 设定的通讯端口号由 1 开始往上递增 MSComm控件的最大值是 16 当使用的通讯端口号码超过 16 时 此控件会通知错误讯息 其错 误显示如图 2-4-2 所示 第 46 页,共 606 页 图 2-4-2 通讯端口指定超过时的情形 虽然 Windows 操作系统可以容许最多 256 个串行通讯端口 不过 VB 的 MSComm 控件则是有 16 个的限制 虽是如此 一次要用到 超过 16 个 RS- 232 通讯端口也不容易了 虽然可以使用的通讯端口号码有 16 个 但并不一定表示我们 使用上一定会正常 我们必须确定通讯端口存在才能设定相关的号 码 也才保证可以让通讯组件拿来运作 通讯端口是否存在且正常 可以参考 控制台 \ 系统 \ 装置管理员 中的连接端口项目 如图 2- 4- 3 所示 图 2-4-3 检视通讯端口是否存在 若不正常 系统会以一个黄色的惊叹号予以表示 以图 2-4-3 为例 打印机连接端口(LPT1)就不正常 而其它的串行埠则是正常 工作 且可用的串行端口号码(由 COM1 到 COM10)亦表示在其中 第 47 页,共 606 页例 :MsComm1.CommPort=1 ‘ 指定使用 COM1 作通讯传输 在此打印机端口不正常的原因是由于笔者使用了 ISA 接口 的串行端口扩充卡 此卡必须指定系统中断号码 而笔者的计算机 中断已大多被用 只好指定一个比较少用的中断 想想之后 就决 定使用打印机的中断(第 7 号 ) 故打印机出现了错误 Settings: 设定初始化参数(请参考第一章) 以字符串的型式设定或 传回联机速度 同位检查 资料位 停止位等四个参数 其格式为 "BBBB,P,D,S" 其中 BBBB 为联机速度 P 为同位检查方式 D 为 资料位数 S 为停止位数 默认值是"9600,N,8,1" 其意为”所使用 的通讯端口是以每秒 9600Bit 的速度作传输 不作同位位的检查 每个元是 8 个 Bit 而停止位是 1 个 Bit” 而此四项必须是依照顺 序 不可前后对调 其中的字母 N 可以是大写或小写 联机速度可为 110 300 600 1200 2400 9600 14400 19200 28800 38400 保留 56000 保留 128000 保留 256000 保留 ”保留”的字眼系 VB 所加上 意为保留给高速传输装置 用 若计算机使用的传输芯片是 16C450 则其最高联机速度只有 19200 BPS;而若是使用 16C550 芯片 因含有缓冲区 其最高速度支 持可达 115200 BPS 合法的同位检查值如表 2-4-1 所示 表 2-4-1 同位检查的种类 设定 说明 E 偶数 (Even) M 记号 (Mark) N 默认值 (Default) None 无同位检查 O 奇数 (Odd) S 空白 (Space) 正确的资料位值则有:4 5 6 7 8 默认值 日本系列的 产品多使用 7 位的资料位 而欧美的产品则多采用 8 位的资料位 正确的停止位值有:1(默认值 1.5 2 第 48 页,共 606 页Settings 设定完成之后 所送出及收回的字符串便以此设定为 主 使用 RS-232 通讯的两方 Settings 必须完全一样 彼此才能顺 利地作沟通 否则双方无法收到彼此所传送的信号 例 :MSComm1.Settings=”9600,n,8,1” PortOpen: 设定或传回通讯连接端口的状态 使用串行埠之前必须 先将要使用的串行埠先行开启;而在使用完毕之后 也必须执行关闭 的动作 串行通讯端口的各项功能的完成都是在 PortOpen 的 True 与 False 之间 Input: 从输入暂存区传回并移除字符 程序靠着这个指令将从对方 所传至输入暂存区的字符读进程序中 并清除于暂存区中已被读取 的字符 暂存区的特性是先进先出(First In First Out, FIFO) 先进 入的字符会被先读出来 而后进去的字符则往前递补 例 :Buffer=MSComm1.Input 将输入暂存区的字符读入 Buffer 字 符串变量中 Output: 将一个字符串写入输出暂存区 当程序需要传输字符串至 对方时 可将字符串使用此一指令写入输出暂存区中 例 :MSComm1.Output=”ABCDE” 此即将 ABCDE 5 个字符透过 RS-232 传送出去 InputLen:指定由串行端口读入的字符串长度 VB 所写的程序可以 使用 Input 的指令将存放在输入暂存区的字符读入 但欲指定所读 入的字符长度则是使用本属性的设定 默认值是 0 此值会使得控 件的 Input 指令一次读取所有输入缓冲区中的数据 例 :MSComm1.InputLen=10 当程序执行 Input 指令时 只会读取 10 个字符 假设在输入缓冲区中有 55 个字节 而设定的 InputLen 属性性是 10 由于每一次的 Input 指令会依 InputLen 的属性而读取 10 个字节进来 因此要全部读完所有的资料需要执行 6 次的 Input 指令 什么时候会使用到指定的 InputLen 指令呢?如果我们需要对 第 49 页,共 606 页固定的字符串长度作特别的运算或判断时 就必须使用到这一个技 巧 HandShaking:指定通讯两方的交握协议 交握协议只能在没有暂存 区超速的情况下 才能保证资料不会遗失 而暂存区超速是指资料 到达连接端口的速度对通讯装置来说太快 以致于来不及将资料移 到接收暂存区 既然交握协议所要作的是数据传输速度的控制 因此也被称为” 流量控制(Flow Control)”;简单地说 当双方传送资料时 如果一方 送出资料的速度快过另一方所能处理的速度时 接收的一方便会要 求传送的一方暂停送出资料 待接收的一方处理完资料之后 再通 知传送方继续传送未传完的资料 VB 中的交握协议之设定有如表 2-4-2 中的几种 表 2-4-2 交握协议的设定 设定 值 说明 comNone 0 默认值 没有交握协议 comXOnXOff 1 (XON/XOFF) 交握协议 comRTS 2 RTS/CTS (Request To Send/Clear To Send) 交握协议 comRTSXOnXOff 3 Request To Send 和 XON/XOFF 交握协议 表 2-4-2 中的设定字段是 VB 所提供的常数定义字符串 其意义 与中间的值 是一样的 在 VB 里面可以使用这些常数定义当作是数 值的输入 我们在阅读程序时 也以这种常数定义的字符串较易了 解 RTS/CTS 交握协议是所谓的硬件交握协议 它使用 RS-232 上 的 RTS脚位及 CTS脚位的功能来控制资料的传送 调制解调器(Modem) 大多使用硬件的交握协议作为与计算机之间的流量控制 如果控制 调制解调器时未指定交握协议 虽然也可以进行控制或拨号的动 作 可是一连线上后 在传送资料时会出现问题 这点需注意 XON/XOFF 交握协议也是所谓的软件交握协议 它使用 XON 表 示暂停资料的传送;而使用 XOFF 表示恢复传送 其中 由于 XON 乃是使用 chr(19)作为控制信号 当系统采用 XON/XOFF 交握协议 时 若所传送的资料中含有 chr(19)字符 将使得传输暂停 而发生 错误 这 是使用上应该要注意的地方 第 50 页,共 606 页例 :MSComm1.HandShaking=comNone RThreshold:设定或传回引发接收事件的字符数 即属性页上的 最 小接收字符数 当接收暂存区达到所设定的字符数时 将会引发 OnComm 事件中的接收事件(请看后面章节的说明) 所以此属性也 就是引起接收事件的”门槛” 此属性的默认值是 0 其意义是无论 暂存区中有多少字符均不引发接收事件 有关事件的应用 我们会 在第八章的实验范例中详加说明 例 :MSComm1.RThreshold=20 当接收暂存区达到 20 个字符时 引发接收事件 CommEvent: 只要有通讯错误或事件发生时都会产生 OnComm 事件 (这也是此控件唯一的事件) CommEvent 属性存有该错误或事件的 数值码 DTREnable:判断在通讯时是否启用 Data Terminal Ready (DTR) 线 路 通常 Data Terminal Ready 是由计算机传送到调制解调器的信号 指示计算机在等待接受传输 即属性页上的 启用 Data Terminal Ready 线路 当 DTREnable 设定为 True Data Terminal Ready 线 会在连接埠开启时被设定为高电位 在连接埠关闭时被设定为低电 位 当 DTREnable 设定为 False Data Terminal Ready 始终保持为低电位 在大部分的情况下(与 Modem 连接使用时) 使用者可以把 Data Terminal Ready 线设定为低电位用来挂断电话 此脚位也可以被拿 来作为数字输出的脚位 例 :MSComm1.DTREnable=True 将 DTR 脚位升为高电位 RTSEnable: 决定是否使 Request To Send (RTS) 线有效 一般情况 下 由计算机传送 Request To Send 信号到调制解调器 以请示准许 传送资料 即属性页上的 启用 Request To Send 线路 若 RTSEnable 设定为 True 则连接埠开启时 Request To Send 线就会被 设定为高电位 连接埠关闭时就会被设定为低电位 Request To Send 线被用在 RTS/CTS 硬件交握协议上 RTSEnable 属性允许使用者以 手动方式轮询侦测 Request To Send 线以判断其状态 此脚位也可以 被拿来作为数字输出的脚位 第 51 页,共 606 页例 :MSComm1.RTSEnable=True 将 RTS 脚 位升为高电位 InBufferCount:传回在接收暂存区中的字符数 该属性在设计阶段 无法使用 InBufferCount 是指已接收 并在接收暂存区等待读取的 字符数 使用者可以把 InBufferCount 属性设定为 0 以用来清除接 收暂存区 例 :Count%=MSComm1.InBufferCount 传回已接收到的字符数 InputMode:设定或传回 Input 属性取回的资料的型态 可设定的数 值如表 2-4-3 表 2-4-3 InputMode 的设定 常数 值 说明 ComInputModeText 0 默认值 资料透过 Input 属性以文 字形式取回 ComInputModeBinary 1 资料透过 Input 属性以二进制形式 取回 InputMode 属性决定 Input 属性如何取回资料 资料可能会以字 符串的形式接收或是当作字节数组中的二进制数据来接收 完全视 此属性的设定 对于资料中只用 ANSI 字符集 则使用 comInputModeText;对于 其它字符数据 如数据中有嵌入控制字符 Nulls 等等 则使用 comInputModeBinary 另外如果所使用的场合中需将超过 ASCII 128 以上的字符传送时 也必须使用 comInputModeBinary 的方式 此点 请参考文献 2 第八章第二节的相关说明 例 :MSComm1.InputMode=1 以二进制方式取得资料 DSRHolding:传回通讯端口的 DSR 脚位状态 若为 True 表示此脚 位为高电位 若为 False 表示为低电位 此脚位也可当成数字输入 的端子使用 例 :State=MSComm1.DSRHolding 传回此脚位的电位状态 第 52 页,共 606 页CTSHolding:传回通讯端口的 CTS 脚位状态 若为 True 表示此脚位 为高电位 若为 False 表 示为低电位 此脚位也可当成数字输入的 端子使用 例 :State=MSComm1.CTSHolding 传回此脚位的电位状态 CDHolding:传回通讯端口的 DCD 脚位状态 若为 True 表示此脚位 为高电位 若为 False 表示为低电位 此脚位也可当成数字输入的 端子使用 例 :State=MSComm1.CDHolding 传回此脚位的电位状态 以上就是针对重要的属性作介绍 而其实际的用法在接下来的 范例中都会有详细的说明 针对属性的特别用法 也会在个别专题 中作深入的讨论 2-4-3 通讯控件的事件 OnComm 事件是通讯控件唯一的事件 此事件可用来处理所有 与通讯相关的事件 不管是何种事件发生 通讯控件只用一个 CommEvent 的属性予以代表 使用事件程序的好处是不需要一直让程序处于侦测的状态下 只要事先将程序代码写好 一有事件发生 就会直接执行相对应的 程序代码 CommEvent 属性会传回下列所述的值之一 来表示不同的通讯错误 或事件 通讯错误设定值如表 2-4-4 所示 表 2-4-4 通讯错误值及说明 常数 值 说明 ComEventBreak 1001 接收到一个中断信号 此讯号由传送端藉由属 性的设定即可作到 ComEventCTSTO 1002 Clear To Send 线逾时 试着传送一个字符 但 在一段系统指定的时间内 Clear To Send 线仍 处于低电位 亦即流量控制机制失败 ComEventDSRTO 1003 Data Set Ready 线逾时 试着传送一个字符 但 在一段系统指定的时间内 Data Set Ready 线仍 第 53 页,共 606 页处于低电位 亦即流量控制机制失败 ComEventFrame 1004 讯框错误(Framing Error) 硬件检测到一个讯框 错误 当送收双方的设定格式不同或鲍率不同 时 便引发此错误 ComEventOverrun 1006 连接埠超速 一个字符没有在下一个字符到达 之前被硬件读取 该字符就会遗失 当接收端 处理接收资料速度比资料传送端所传送的速度 慢时 引发此错误 ComEventCDTO 1007 Carrier Detect 线逾时 试着传送一个字符 但 Carrier Detect 线在一段系统规定的时间内仍处 于低电位 Carrier Detect 也称为 Receive Line Signal Detect (RLSD) 若计算机原与调制解调 器联机 此情况的发生时 极可能电话线路已 断线 ComEventRxOver 1008 接收暂存区溢位 接收暂存区没有空间 设计 者必须于程序中增加接收暂存区的空间才能解 决此问题 ComEventRxParity 1009 同位检查错误 硬件检测到一个同位检查错误 可能为传输环境差 噪声干扰大 ComEventTxFull 1010 传输暂存区溢位 试着将字符排入传输暂存区 时 发现该暂存区已满 设计者必须于程序中 增加接收暂存区的空间才能解决此问题 ComEventDCB 1011 从连接埠接收 Device Control Block (DCB) 时 发生非预期的错误 通讯事件常数设定及其值则如表 2-4-5 所示 表 2-4-5 通讯事件常数 常数 值 说明 comEvSend 1 传输暂存区中的字符数比 Sthreshold 还少 ComEvReceive 2 收到 Rthreshold 个字符 该事件将持续产生直 到用 Input 属性从接收暂存区中移除数据 ComEvCTS 3 Clear To Send 线的状态发生变化 comEvDSR 4 Data Set Ready 线的状态发生变化 该事件只在 DST 从 1 变到 0 时才发生 comEvCD 5 Carrier Detect 线的状态发生变化 comEvRing 6 检测到振铃信号 一些 UART universal asynchronous receiver-transmitters 可能不支持 此事件 ComEvEOF 7 收到档案结尾 ASCII 字符为 26 字符 在程序的写法上则如下 Private Sub MSComm1_OnComm() Select Case MSComm1.CommEvent 第 54 页,共 606 页 ' 事件 Case comEvCD ' CD 线的状态发生变化. If MSComm1.CDHolding Then '侦测 DCD 脚位电位 spDCD.FillColor = RGB(255, 0, 0) '改变灯号为红 灯 spDTR.FillColor = RGB(255, 0, 0) '改变 DTR 显示灯号 MSComm1.DTREnable = True '激活 DTR 线路 Else spDCD.FillColor = RGB(255, 255, 255) '灯号灭掉 spDTR.FillColor = RGB(255, 255, 255) '改变 DTR 显示灯号 If Not fDSR Then MSComm1.DTREnable = False '关闭 DTR 线路 End If Case comEvCTS ' CTS 线的状态发生变化. If MSComm1.CTSHolding Then '侦测 CTS 脚位电位 spCTS.FillColor = RGB(255, 0, 0) '改变灯号为红灯 spRTS.FillColor = RGB(255, 0, 0) '改变 RTS 显示灯号 MSComm1.RTSEnable = True '激活 RTS 线路 Else spCTS.FillColor = RGB(255, 255, 255) '灯号灭掉 spRTS.FillColor = RGB(255, 255, 255) '改变 RTS 显示灯号 If Not fRI Then MSComm1.RTSEnable = False '关闭 RTS 线 路 End If Case comEvDSR ' DSR 线的状态发生变化. If MSComm1.DSRHolding Then '侦测 DSR 脚位电位 spDSR.FillColor = RGB(255, 0, 0) '改变灯号为红灯 fDSR = True Else spDSR.FillColor = RGB(255, 255, 255) '灯号灭掉 fDSR = False End If Case comEvRing ' Ring Indicator 变化. If spRI.FillColor = RGB(255, 255, 255) Then spRI.FillColor = RGB(255, 0, 0) '改变灯号为红灯 fRI = True ElseIf spRI.FillColor = RGB(255, 0, 0) Then spRI.FillColor = RGB(255, 255, 255) '灯号灭掉 fRI = False End If Case comEvReceive ' 收到最小接收字符数个字符 Case comEvSend ' 传输暂存区有最小传输字符数个字符 Case comEvEOF ' 输入数据流中发现 EOF 字符 End Select End Sub 如上述的作法即是在通讯控件的事件程序中写入希望的动作 步骤 当某一事件发生时 即会引发相对应的程序代码 上列的程 序代码是最常使用的事件程序 包含了硬件线路的程序及接收 传 送的程序 在这些最常使用在控制领域的事件 我们会在第八章的 实验中详细介绍 第 55 页,共 606 页2-5 串行通讯组件的使用 使用通讯组件有其一定的步骤 这些步骤都必须正确 串行通 讯端口才能被我们所正常使用 以下就介绍使用此组件的相关步 骤 2-5-1 通讯的开始及结束 通讯控件的 PortOpen 属性决定了通讯的开始及结束的时机点 完整的通讯过程是包含在 PortOpen 属性的 True 和 False 之间 如 下所示 Dim Buf$ MSComm1.PortOpen = True ‘开启通讯端口 MSComm1.Output = "这是一个测试!" MSComm1.Output = UCase("%%Data") & vbCr TimeDelay 1000 Buf = MSComm1.Input Text1.Text = Buf MSComm1.PortOpen = False ‘关闭通讯端口 在开启及关闭通讯端口之间的动作才能顺利地进行 开启之前及关 闭之后所进行的通讯指令均会产生如图 2- 5- 1 的错误 图 2-5-1 未开启即使用通讯端口时会产生的错误 一般说来 将 PortOpen=True 放在窗体启始的时候(Form_Load 事 件 ) 而 PortOpen=False 放在系统结束之前 是比较恰当的 如此 也可以保证系统使用通讯端口工作的过程中不管在何处写入通讯 程序代码也不会碰到上图的状况 第 56 页,共 606 页2-5-2 通讯参数的确定 使用通讯传输的仪器设备 最重要的就是参数的设定 这个参 数设定除了在通讯组件上的 Settings 属性外 有的时候也要注意 HandShaking 属性 尤其是 Settings 属性 不同的设备都可能采用 非常不一样的通讯参数设定 在传输系统的程序设计前 务必要弄 清楚所要控制和传输的设备参数为何 由一般的情形看来 日本的 设备较常用的设定是”9600,e,7,2” 而欧美的仪器则较常使 用 ”9600,n,8,1” 台湾则不一定 二种都有可能 交握(HandShaking)的方式依每种设备的设计而有所不同 最主 要的目的在于预防资料的漏失 当资料量不大 或是双方的资料缓 冲区够大时 HandShaking 也可以设成 None 不过还是以设备的要 求为主 2-5-3 软硬件之间 从最前面提到的硬件到现在的软件架构 如果建立系统的话 也许已经有了大概的轮廓了 由程序的执行到硬件动作的完成 以 VB 来说可以有二种方式 其程序如图 2- 5- 2 图 2- 5- 2 控制串行埠的二个步骤 第 57 页,共 606 页使用通讯组件的方式看来要比使用直接呼叫 API 要来得复杂 但实际上 使用通讯组件可以省却不少的低阶程序的撰写 并使用 组件概念实现系统的设计 因此要比 API 简单得多了 在操作系统方面 Windows 使用通讯驱动程序 Comm.drv 以 让应用程序能够使用标准的 Windows API 函数传送和接收资料 串行外围设备制造商则提供硬件驱动程序 以便让其硬件能与 Windows 连结 使用 MSComm 控件时 实际上就是使用了 API 函 数 API 函数将被 Comm.drv 解译并传输给外围设备驱动程序 使用 Visual Basic 的设计者只需要关心如何使用通讯控件所提 供的属性或事件 以驱动 API 函数的接口完成工作 第 58 页,共 606 页本章习题 1、 试说明对象 属性 事件 方法用于系统设计的程序 2、 说明引用延伸控件的程序 3、 设计一简单的画面 上面有按钮 卷标 文字框 执行 时的现象是在文字框中输入文字 而按下按钮后 文字 框中的文字出现在卷标上 4、 说明 RS- 232 组件使用的步骤 第 59 页,共 606 页第三章 分布式监控模块简介 3-1 何谓分布式监控 由于人类的努力 使用机器来达到自动化是世界的趋势 这些 各式各样的机器 配有各式各样的先进设计 目的在于使用此机器 可以大量且有效地降低人类的负荷 增加产能 因为采用了机器或 设备来作事情 使用者必须随时知道实际的状况 以便知道如何因 应 这就是监视 在了解机器或设备的相关讯息后 使用者还必须 针对不同的情形 给予不同的输出指令 以达到系统的要求 这就 是 控制 将现场结果传回系统控制者 系统控制者再依一定的判断 法则作出相对的控制输出 就形成了一个监控系统 世界不断地在前进 工厂的规模也愈来愈大 通常一个具有规 模的厂区所涵盖的面积也不小 在自动化的领域中 我们希望能以 机器来代替人类工作 虽然机器可以为我们完成很多的事情 不过 还是要有设备来监视这些机器的动作是否正常 并据以作出一些控 制的动作 这也就是监控 以现在的环境来看 计算机及其它工业用的设备均不是很昂 贵 因此一部工作的机器可能会配上一部监视的设备或是计算机作 为工作辅助之用 就如上述 一个厂区若是含有不少的机器设备或 是厂区不小的话 如此的情形会是监控用的设备到处都是 而且每 个点都必须长时间每人在一旁看着 要不然就是配人到处查看监视 的情形 最会发生此种情形的就是石化厂之类的大厂 此种类型的厂区 通常很大 而且厂内的设备极多 生产设备又必须注重一些控制参 数 例如温度 压力 流量 等等 此种大厂在机器设备上经常会 配有监控设备(常见的是盘面显示) 每隔一段时间会有专人带着纸 笔来厂区内的所有设备巡视一番 并将所有的数值记录下来 带回 办公室作数据分析及处理 若是比较重要的设备的话 索性就派一 位工程师驻在现场 不断地看着显示表呈现的值 并在必要时作出 相对的改善动作 第 60 页,共 606 页如果只有一 二部机器的话 也许还好 问题是 通常厂区须 监控机器设备都不少 当然会耗去不少的人事成本及控制效能 于 是有另外的一种看法产生 是不是可以将这些机器设备的监测讯号 全部都传送到主控制室 而由主控制室来统一管理?而且可以全盘 地监控所有的情形? 上述的想法要成立的话 讯号必须所有的传感器讯号拉至控制 室 全部集合在一起 不过 讯号不可能跑这么远的距离 一般的 传感器讯号是电压型式 如果传输距离过远的话 会造成讯号的衰 减效应及杂散电容效应 如此一来 将得不到正确的结果 因此 传感器的讯号处理工作必须在监测位置就予以完成 处理完后的结 果再回传(或由主控制计算机轮询)即可 这种分散各地的监控工作 而监控结果可以集中管理的架构就是分布式的概念 如图 3-1-1 所 示 图 3-1-1 分布式监控 图 3-1-1 在不同的地方装上了分布式模块或是其它的设备 这 些模块及设备可能含有数字输出 数字输入 模拟输入 等等的功 能 每个模块负责自己的一块区域 实际的物理现象会输入到各个 模块中 而每一个模块再以 RS-485 网络连接起来 一起送到主监 控站 而达到了分布式监控的目的 为何使用的是 RS-485 的方式 而不是 RS- 232 就好了呢?如果 我们从之前二种串行方式的比较上就可以发现 在面对环境噪声较 多 传输距离较长的情形下 使用 RS- 485 是要比 RS- 232 要来得好 再者 由于串行通讯的实现比其它的传输接口来的容易 因此分布 第 61 页,共 606 页式的资料撷取控制系统就采用 RS- 485 作传输的接口了 至于发展 上是否一定就是 RS-485接口呢?笔者认为只要是接口是很多人使用 的 应该就会成为厂商支持的对象 例如 Internet 此种接口由于 是现在及未来个人计算机上的必备组件 厂商就会支持此种接口的 分布式系统解决方案 现在已可发现厂商对于 Internet 的支持也是 不遗余力 3-1-1 另一种分布式系统 现在我们经常会看到所谓的分布式系统架构 此种分布式的架 构通常出现在所谓的主从式架构(Client- Server 也称为二层式架构) 中 其主要的作法是将资料存放在服务器(Server)中 而各个客户 端 (Client)欲取得所要的资料时 都到服务器中搜寻 其架构简图如 图 3-1-2 所示 客戶端-1 客戶端-2 客戶端-3 產品資料庫 行銷資料庫 配送資料庫 图 3-1-2 分布式架构 一般的客户端所需的资料可能分布在不同的计算机或是数据库里 面 透过网络的连接 客户端可以向各个不同的资料要求所需的资 料 这种资料分散在各处的系统架构也称为分布式系统 现在的数 据库分布式架构的发展朝向多层架构发展 有人称之为三层式 (3- Tier)架构 不过更有人称之为多层架构 这个部份不在本书的讨 论范围内 读者若有兴趣 请自行参阅参考文献中的资料 第 62 页,共 606 页3-1-2 分布式与集中式 相较上述的分布式系统 也有一系统可称之集中式的系统 如 果我们所处理的讯号均集中在一起 而讯号一起进入处理器(也就 是计算机)被处理 即使拥有前端讯号处理器的话 此前端处理器 和计算机也是近在呎尺 我们可称之前集中式的系统 如图 3-1-3 所示 图 3- 1- 3 集中式系统 集中式系统是以讯号适配卡为主角 工业上用的适配卡规格众 多 功能也各异 通常须视情形选择适用的卡片 集中式系统由于 均集中于一部工业级计算机内 透过计算机对各卡片下达资料撷取 或控制的动作 一样可以达到监控的目的 只不过这种架构比较适 合在距离较近的范围内 若距离较长的话 讯号必须经过长距离的 传输才能进到计算机内的讯号处理卡 讯号就有可能衰减 或是在 传输过程中受到其它噪声的干扰 当然资料的可信度就会下降 第 63 页,共 606 页3-2 模块分类 一般的依讯号种类的不同 使用不同的信道进入控制器时 约 可分为数字输入 数字输出 模拟输入 模拟输出 计数/频率输入 等等模块 以下的各节将扼要介绍这些模块 待后面的章节中再以 Visual Basic 程序的角度及实验详细说明 本书所使用的模块为力 激科技之 I- 7000 系列 程序及实验均以 I- 7000 系列作为对象 同 样的原理也可使用在其它厂商所开发的分布式模块 3-2-1 讯号电平转换模块 不管是 RS-232 或是 RS-485 它们都是串行通讯的一种 对于 我们来说 如果可以使用和 RS-232 相同的方式在 RS-485 上作通讯 是比较方便的 而由于 RS-232 与 RS-485 所采用的 IC 不一样 它们 之间的讯号电平也不相同 如果希望能使用 RS-232 去进行 RS-485 的控制 就必须将 RS-232 的电平作转换 I-7000 系列中的 7520 模 块就是用来作电平的转换之用 其外观如图 3-2-1 所示 图 3-2-1 7520 外观及脚位定义 另外 当我们将 RS-232 接到 7000 系列上 而转换为 RS-485 网 络时 其配线仅使用 3 条线路 这 3 条线是 RS-232 上的第 2 脚 第 3 脚及第 5 脚 (与最简单的传输线路是一样的) 图 3-2-1 中的 7520 虽然使用 DB- 9 的接头 这只是为了方便和计算机作连接 它内部 的接脚也只有使用 3 条线 图 3-2-2 是 I-7520 的内部电路示意图 第 64 页,共 606 页Self tuner network controller RS-232RS-485D+ D- DC DC V+ V- 5V 0V DC DC +V -V T R GND I-7520 Isolation=3000Vdc Isolation in RS-232 site 图 3-2-2 7520 模块内部电路示意图 本模块的规格如表 3-2-1 表 3-2-1 7520 模块的规格 i-7520 : RS-232 to RS-485 Converter l Protocol : two-wire RS-485, (D+,D-), protocol l Connector : plug-in screw terminal block l Speed : “Self Tuner” inside, auto switching baud rate, from 300 to 115200 BPS l 256 modules max in one RS-485 network without repeater l 2048 modules max in one RS-485 network with repeater l Isolation voltage : 3000V l Isolation site : RS-232 l Repeater request : 4,000 feet or over 256 modules l Power requirements:+10V to +30VDC l Power consumption : 2.2W(Max) 在第一章曾讨论到 利用串行端口作资料的交换 可以有许多 的传输速度 当然在 RS- 485 网络时也一样有不同的传输速度可以 选择 利用 7520 模块转换 RS-232 和 RS-485 之间的讯号电平时 传输速度该如何调配呢?若由 232 端使用的速度由 9600bps 改变到 115200bps 除了计算机端的串行组件而要变更外 分布式模块本 身理论上也必须予以变更才行 实际上 在 7000 系列模块中的确 必须将模块的传输速度改变 但是比较不同的是 负责讯号电平转 换的 7520 却完全不需作任何的改变 进到 7520 模块中的资料若使 用的速度是 A 也将会被 7520 模块以 A 速度传送出去 同样的 计算机或其它的模块若改用 B 速度作传输 7520 模块一样把被送 进来的资料以 B 速度送出 这种技术芯片在 7520 模块上被称为 Self- Tunner Chip(自调适芯片) 也是在表 3- 2-1 中所提及的规格之 一 这种设计的好处就在于同一个 RS-485 网络上可存在不同的速 度模块 而只使用同一部计算机的串行端口作处理 由于 7520 实 际上的动作只是将计算机端的讯号电平作一个转换的动作而已 本 身并不作其它的动作 我们也可以将其看成是一个缓冲区 第 65 页,共 606 页表 3- 2- 1 中提及了在不需要缓冲模块(模块编号 7510)的帮忙下 最多可以连接 256 个模块 这是因为讯号的传输无法负荷的关系 如果加上 7510 模块的话 就可以将同一个 RS- 485 网络上的模块数 目提升到 2048 个模块 另一个需要加上 7510 的时机是当传输的距 离超过 1200 公尺时 这是基于讯号的传输能力限制 使用在实际的厂区环境时 噪声问题经常是困扰的来源 将噪 声隔离掉是一件相当重要的事情 而由上图也可以看出在 RS-232 端与 RS-485 端有着一个隔离保护 其可耐的电压高达 3000 伏的直 流 因此可以将在传输网络线上的噪声隔离掉 即使网络上的设备 因 高电压的侵袭而损坏时 也不致影响到监控计算机端 而达到保 护的目的 相关的隔离课题 请看下一节的讨论 有些 RS-485 的转换动作进行时 必须由计算机端来决定讯号的 流向 如果某一个脚位电位升高的话 讯号流向是由计算机到 RS-485 网络 若该条线路降为低电位的话 则讯号的流向是由 RS-485 网络到计算机端 在这种情形下的操作方式时 计算机端就 必须是俱备 9 线式(其实也用不着这么多)的 RS-232 作连接 只有 3 条路的话是不够的 如果其它的转换器需要用到超过 3 条的线路 为什么 7520 只要 3 条就够了呢?这是因为此模块本身就具备有判断 资料流向的能力 3-2-2 噪声的隔离 7520 用来将 RS-232 的讯号作转换 使其资料可以在 485 的网 络上传输 而由之前的讨论也可以知道 网络都是串连在一起 如 果有一个高压的噪声传送到传输线上 将可能造成设备的损坏 不 管是前端的资料撷取装置或后端的计算机控制设备等等 均可能因 高压噪声的因素而使得系统停摆 因此设备的首要之务应是如何阻 绝任何可能的高压噪声 一般说来 噪声的种类有很多 以发生原因概分以下各类 1、 自然噪声 内含宇宙噪声 太阳系噪声(太阳风暴噪声) 大 气噪声 半导体噪声 第 66 页,共 606 页2、 人工噪声 内含持续振动(如高频率设备 广播及无线通讯 设备 携带用无线机器) 火花放电(如引擎 马达碳刷 继 电器) 电晕放电(如输送电线 送电用碍子) 辉光放电(如 萤光灯 霓虹灯) 机器脉冲产生(如数字电路 开关回路 电子继电器 SCR 回路) 3、 人为噪声 内含开关切换(如电源开关及继电器 电源定时 器 PWM 切换) 点火装置(如瓦斯点火装置 锅炉点火装 置 ) 静电放电及带电(如人体带电 机器带电 空间带电) 电源变动(如停电 电源电压降低 瞬间停电 闪烁) 而噪声的传播则有以下的几种 1、 空间传导(高频噪声为主) 2、 导 体传导 3、 空间至导体传导 4、 导体至空间传导 5、 电磁波-信号线-电磁波 6、 电源线传导 7、 接地线传导 3-2-3 模块编号及功能 分布式模块种类相当多 依不同的功能分类如表 3-2-2 表 3-2-2 分布式模块概分 分组 分类 模块编号 说明 嵌入式 控制 嵌入式控 制模块 I-7188/I-7188D I-7188XA/I-7188XAD I-7188XB/I-7188XBD I-7188XC/I-7188XCD 当成主控计算机使用 无法 使用一般计算机作监控时 此模块可取而代之 模块中 需写入控制程序用的程序 代码 一般以 C 语言为之 第 67 页,共 606 页网络嵌入式 控制模块 I-7188EA/I-7188EAD I-7188EB/I-7188EBD I- 7188EC/I- 7188ECD 与 7188 类似 但提供网络 连接功能 转换模块 SA-7520R PCI-7520AR I-7520 I-7520R I-7520A I- 7520AR RS-232/RS-485 转换模 块 不同的型号可用于不同 的架构上 缓冲模块 I-7510 I- 7510A 模块距离过长时 使用此模 块可增加模块的传输距离 嵌入式转换 模块 I-7521/I-7521D I-7522/I-7522D I- 7523/I- 7523D 当网络上有 RS-232 的设 备需连上 RS -485 网络时 可使用这些模块作中间的 接口 模拟输入模 块 I-7011/I-7011D I-7011P/I-7011PD I-7012/I-7012D I-7012F/I-7012FD I-7013/I-7013D I-7033/I-7033D I-7014D I-7016/I-7016D I-7016P/I-7016PD I-7017/I-7017F I- 7018/ I- 7018P 外界的模拟讯号可利用这 些模块作撷取 依模拟讯号 的不同选择不同的模块 模 块中可直接量测电压 也有 模块直接可量测温度感器 的数值 而得到温度值 输出入 模拟输出模 块 I-7021/I-7021P I-7022 I- 7024 可输出电压或电流 不过属 于讯号等级 并不能推动设 备 还需要放大器才能推动 设备 第 68 页,共 606 页数字输 出入模块 I-7050/I-7050D I-7052/I-7052D I-7053/I-7053D I-7041/I-7041D I-7042/I-7042D I-7043/I-7043D I-7044/I-7044D I-7060/I-7060D I-7063/I-7063D I-7063A/I-7063AD I-7063B/I-7063BD I-7065/I-7065D I-7065A/I-7065AD I-7065B/I-7065BD I-7066/I-7066D I- 7067/I- 7067D 提供各式不同通道和型式 的数字输出入 计数模 块 I- 7080/I- 7080D 可量测计数值及频率 值 无线传 输模块 SST-900EXT SST-900A SST-2400EXT SST-2400A-3 SST- 2400A- 13 无法使用接线的方式时 可 使用这些模块作讯号的传 输 工业上会使用到的量测情形 应该都可使用这些模块作讯号的 撷取 该选用那些个模块 视现场情形而定 大底上就是表 3-2-2 中的模块的组合 第 69 页,共 606 页3-2-4 模块中的隔离设计 7000 系列模块中 用来转换 RS-232 与 RS-485 电平的模块有 I-7520 I-7520R I-7520A ISA-7520R 几种 前三种使用的是模块 式的方式连接网络 而 ISA-7520R 则与 I-7520R 具有相同的功能 而其是直接安装在 ISA 接口上 这些转换模块均具有电源噪声隔离 的效果及方向 首先检视图 3-2-2 中的 7520 模块示意图 在该图的 右下角有段字写着”Isolation in RS -232 Site ” 表示此一模块具有隔 离的功能 而且将隔离的部份作在 RS-232 端 图 3-2-2 的左方标示 着资料线(D+ D- )及电源线(V+ V- ) 为何此种设计是将 RS- 232 端隔离开?原因在于噪声的来源大多来自于电源端 若强大的噪声 打进模块中 一般会循着电源线路径进入 在 图 3-2-2 中可以看出 RS-485 端和电源端处于同一个区域 RS- 485 端使用的电源来自接 入电源 但是 RS-232 端所需的电源则是模块内部经转换后的电源 转换前后的电源中间有着 3000V 的隔离设计 如此一来 当 RS- 485 端的电源被噪声干扰时 只要此干扰在保护范围内 则可保证 RS- 232 端的设备不会损坏 达到保护主控计算机的目的 另外 我们再看看 7520R 的示意图 如图 3-2-3 此种隔离设计 目的与图 3-2-2 不同 图 3-2-3 不同端的隔离设计 7520R 与 7520 是相当类似的 不过我们仔细地观察一下可以发现 模块所需要的电源是由 RS-232 端所供应的 这是与 7520 模块比较 不一样的地方 RS- 485 讯号传输所需的电源则是由模块本身作电压 转换而得 透过讯号线 D+和 D- RS-485 模块均连接在一起 当噪 声由 V+及 V-二条线路过来时 由于隔离的作用 将使得 RS- 485 端 上的模块获得保护而不致损坏 但 RS-232 端则可能遭殃 第 70 页,共 606 页由上面的二种模块情形可以知道 藉由选用不同的隔离型模块 可 以确保所要保护的端点 如果我们要保护的是监控计算机端 则必 须选择 I-7520(也就是 RS-232 端隔离) 而若要保护分布式模块的话 就必须选择 I-7520R(也就是 RS-485 端隔离) 到底选用那一种模块 就完全看我们的系统设计了 监控计算机端(RS-232 端 )与分布式模块端(RS-485 端 )所使 用的电源必须是分开的电源来源 才能因隔离的作用而保护到模块 或监控计算机 若来源相同 就算有隔离也无法得到保护 3-2-5 隔离模块选用 既然 I-7520 I-7520R 模块在隔离作用上是不同的 则使用于系 统的连接时 自然得视情形来作选择 图 3-2-4 是其中一种情形 图 3-2-4 利用计算机接上 RS- 485 网络 原厂所使用的变压器是产生 24V 的直流电源 它 和 交流输入电 源是隔离开的 因此本身就具备有隔离的效果 由于 I-7520 所使用 的电源和 RS- 485 资料线同一端 透过另一个变压器的使用 会使 得 RS-485 端和计算机端隔离开来 RS- 485 端所产生的噪声也不会 流进计算机端 而达到保护主控计算机的目的 另一方面来说 主 控计算机端的噪声也无法进入 RS-485 端 再则 如果将 I- 7520 改 用 I- 7520R 也同样使用变压器作为电源来源 7520R 所使用的电 源和 RS- 232 端相同 一样具有和 RS-485 端隔离的作用 所以在图 3-2- 4 中 不管使用的是 7520 或 7520R 都可以达到 RS-232 端和 RS- 485 端隔离的作用 现场的使用上 可能考虑而设备和传输模块间的噪声干扰问 题 通常也须作些隔离的步骤 如图 2- 3-5 是二种可行的连接方法 第 71 页,共 606 页 图 2- 3- 5 连接 PLC 时的二种作法 同样都是 232 型式的 PLC 但是图 2-3- 5 的左方使用 PLC 上经 常使用的+24V 电源 将此电源供给 7520R 模块使用 由于 7520R 模块上的电源和 RS- 232 端是在一起的 而和 RS- 485 端是隔开的 因此如图左的接法将使得 PLC 端的噪声无法进而分布式模块而达 到保护模块的目的 另一种情形则是使用 7520 由于 7520 上的电 源和 RS-485 端的资料线是相同端的 因此在电源接线时 使用原 厂提供的 24V 变压器 此变压器本身就具有隔离的作用 自然就可 以将 7520 二端的 RS-232 和 RS- 485 隔离开 达到保护双方的目的 由于现场的环境不见得相当地好 因此噪声干扰问题也时有所 见 使用分布式模块时 除了要考虑到模块不可以干扰到其它的设 备以外 也必须考虑到其它的设备电源是否可能干扰到模块 双方 面都作好考量 实际上线工作时才不会造成损坏 第 72 页,共 606 页3-3 模块运作方式 每个分布式模块的连接是以 RS-485 线路为之 每个模块的内部 均可传送/接收资料 由于功能不尽相同 每个模块平常各自独立工 作 与其它的模块之间并没有直接的资料交换 整个 RS-485 网络 上的模块控制通常是透过一部主控计算机或是一个控制模块 3-3-1 使用计算机作控制 最常见的系统架构方式就是如图 3-2-4 一般 利用一部个人计 算机 一个 RS- 232/RS-485 转换模块 接着就可以控制整个 RS- 485 网络上其它的 I/O 模块 此种方式最少将使用到个人计算机上的一 个 RS-232 串行通讯端口 由于 RS-232 是个人计算机上的基本配备 因此使用此一方式对于计算机而言是一件非常容易的事情 另外一种的方式是不用 7520/7520R 等讯号转换模块 而直接使 用计算机上的 RS-485 通讯端口 一般的个人计算机是没有 RS- 485 的配备 不过在某些工业用的计算机上 RS-485 也可以看得到 因 此就可以直接将 7000 模块接到计算机上 而不需要经过转换模块 的转换 如图 3- 3- 1 所示 图 3- 3- 1 直接使用工业级计算机上的 RS- 485 3-3-2 使用 CPU 模块作控制 如果监控的场合不太适合计算机的存在 此时可以使用 I-7188CPU 模块取代计算机 控制程序的部份就必须写入 CPU 模块 第 73 页,共 606 页内 而显示的部份则可以使用人机接口处理 如图 3- 3-2 就是一个 例子 图 3- 3- 2 利用 CPU 模块监控 RS- 485 网络 图 3-3-2 的控制方式可以视为以 CPU 模块取代了一部计算机 由于 不若计算机般的资源丰富 此种方式所进行通常是任务相当单纯的 时机 需使用大量系统资源的时候使用上一小节的方式 对工程师 是比较轻松的 3-3-3 个人计算机和 CPU 模块混合控制 笔者个人比较倾向于使用计算机和分布式模块作联机控制 现 在的个人计算机功能强大 资源丰富 再加上网络的发达 种种的 好处使得我们开发系统都可以得到相当大的便利 因此很多的系统 也就使用计算机 再加上窗口操作系统作发展的基础 另一方面来 说 笔者使用工业级计算机多年 从一开始使用到现在 还没有办 法保证计算机不会当机 不管当机的原因是出在软件或是硬件 反 正就是无法百分之一百地作到计算机系统不当机 基于这个原因 硬件厂商通常会在产品上设计所谓的 Watch- Dog(看门狗)电路 当 看门狗电路侦测到不正常的情形发生时 就会引发硬件上的初始化 动作 使得硬件电路重新再执行一次 让它恢复到正常状态 使用计算机作分布式模块的监控时也要防范此种不正常当机 的情形发生 正常情形下 计算机可以很正常地和各个分布式模块 作讯息的交换 但是当机情形发生时 应该能马上反应出来 不可 第 74 页,共 606 页让监控系统停摆 而是激活另一个备用的系统马上取代计算机的监 控作业而让系统可以继续运作下去 如图 3- 3- 3 所示 图 3- 3- 3 计算机和 CPU 模块的结合 除了使用计算机和 RS- 485 上的分布式模块作资料的交换外 网络 上还有一个 7188 的 CPU 模块 一般时候此 CPU 模块不会有任何的 动作 一旦计算机当机 此模块就会马上激活接替计算机 以 7188 内部预先准备好的监控程序执行 RS- 485 网络上的监控作业 欲达 到此一目的 我们需要设计所谓的看门狗程序 计算机端和 7188 均依一定的程序执行 即可达到预防系统当机 7000 系统的模块本身也拥有看门狗的设计 一旦模块因故不正 常时 也会重新激活模块 解决模块当机的问题 另外 使用模块 作系统设计时 也可以激活软件的看门狗功能 此软件看门狗功能 可以让控制端和被控端处于正常运作的情形 若看门狗功能丧失 表示网络上的主控计算机或模块已失效 此时可采取必要措施 第 75 页,共 606 页 第 76 页,共 606 页第四章 分布式模块的指令字符串及格式 透过传送字符串指令 计算机可以控制分布式模块 或由模块 取得资料 和所有可控制的设备一样 模块本身的指令也不少 并 且有它自己的格式及用法 本章将大略地说明指令字符串及使用的 格式 由于一般的计算机上所拥有的串行通讯端口是 RS- 232 而非 RS-485 因此在文中一律以计算机连接 I-7520 将 RS- 232 的电平 转换到 RS- 485 命令字符串才因而传送到 485 网络上 4-1 模块指令 分布式模块可以透过计算机本身上的 RS-232 串行通讯端口转换 到 RS-485 上 因此其命令格式也会和 RS-232 息息相关 RS-485 中 的指令均是文字格式 所用的码均是位于 ASCII 码 128 以内 因此 在处理上就不用去考虑到超过 128 的部份 4-1-1 指令过程 就分布式模块的指令传送方式而言 主控计算机与模块间的指令 流通过程如图 4-1-1 主控電腦 232指令送出 232指令經 7520轉換為485 模組接 收指令 後運作 模組經485傳 回執行結果 485指令經 7520轉換為232 1 2 3 45 图 4-1-1 指令流程 整个的流程如图 4-1-1 中的顺序号码所示 由主控计算机送出的 指令是透过 RS- 232 串行通讯端口传送出去 此讯号经过 232- 485 的转换模块(模块编号 I- 7520)将讯号电平及型态转换后 在 485 网 第 77 页,共 606 页络上传播开来 模块收到属于自身模块的指令后 会进行分析控制 的动作 最后将结果再送至 485 网络上 此讯号再经由 I- 7520 的转 换后 可由计算机的 232 串行端口收进来 因此分布式模块的命令格式可以被分成送出与 响应二个部份予 以讨论 送出的部份乃是由计算机下达命令给分布式模块 此部份 包含了以下的几个片断的组合 ( 前导字符)(地址)(命令)(CHK)(CR) 以上的几个就构成了送到分布式模块的指令 各项分别解释如下 ( 前导字符) 一个字节 用以标明此命令的群组 模块的控制指令 通常被分成几个不同的群组 而此前导字符均使用一些键盘上的特 殊字符 如 $ # ~ 等等 ( 地址) 二个字节 用以表示此命令将要送至的模块 同一个网络 上 相同传输参数设定的分布式模块必须使用不同的地址来区分 而此模块的地址可由 00~FF 共有 256 个地址 ( 命令) 一个至数个字节 用来指定模块所要执行的指令 这些指 令通常使用不同的数字来表示不同的功能 (CHK) 总合冗余检查码(CheckSum) 所使用的程序是将所有传输 的字符在 ASCII 对照码中的地址数值全部加起来 保留最后的一个 字节 拆成前后二个字符而成为此检查值 后面会再对此部份详加 说明 (CR) 就是键盘上的 Enter 按键 在 ASCII 码上是第 13 号 如果使 用 Visual Basic 的语法写出就是 vbCr 在程序代码中将会大量地使 用此字符 在分布式模块中 主控计算机会依实际的需求向模块发出指 令 要求其执行相对应的动作 而与送出指令的配对的是分布式模 块在接收到主控计算机的指令后所送回的执行结果 这个被传送回 来的字符串由以下的几个部份组成 ( 前导字符)(地址)(数据数据)(CHK)(CR) 回传的字符串部份只有(数据资料)是和命令字符串格式不同 其余的部份均如上述 至于数据资料的格式在不同的模块上也有不 同的解释 当使用到该模块时 我们会再详加地说明 不管是主控计算机送出的字符串指令或是分布式模块回传的 执行结果 每个部份都包含有 1 个至数个不等的字符 除了(CHK) 及 (CR)二个部份 其它的部份均是可见字符 直接就可以看到其内 第 78 页,共 606 页容 构成指令中若使用到英文字母 这些英文字母均必须大写 4-1-2 前导字符 分布式模块所使用的前导字符分成几种 相同类型的指令会使用 相同的前导字符 这些前导字符有% # $ ~ @等几种 每一个 指令中的第一个字符都必须是这几个前导字符中的一个 前导字符 通常是比较少见的字符码 在键盘上都可以找到这些字符 4-1-3 模块地址 一部计算机里里外外所连结的设备非常地多 CPU 为了和这些 连结的设备互相沟通 这些设备就必须被设定一个编号 CPU 透过 每个设备上独一无二的编号来和不同的设备作沟通 这个编号就是 地址 每一个设备透过不同的地址指定让计算机知道其所在的位 置 如果 CPU 需要传送讯息或要求某一个设备传回讯息也是透过 地址来交换讯息 分布式模块透过一条 RS-485 网络将每一个模块组合起来 每一 个模块都经由这组网络而接收资料 传送资料 至于那一个模块应 该对线上主控计算机的命令作出响应就必须透过每一个模块上的 地址指定方式来区分 地址字符串必须紧接在前导字符之后 RS-485 网络所传送者为 字符串型式的资料 因此地址也必须是字符串 在送出指令中的模 块地址占有二个字符(不可多 也不可少) 以 16 进制表示 每一个 位可由 0~F 地址组合即可能由 00~FF 共 256 个地址 这二个地 址字符虽是文字(例如地址 0A 表示 ASCII 码上的 10 进位第 48 号 及第 64 号字符) 却都是代表了数目 这点是必须特别注意的地方 原来我们在处理地址时 应该给的都是数字(因为计算机中的地址 本来就是一组数字的组合) 但在串行通讯的场合中 却是以字符 串最为常用 因此传送时 通常也会将数字转为字符串传递 特别 要注意的是 所有 ASCII 表上看到的字符 其实都是使用一个字节 的内容被记录在计算机中 通讯双方凭着传送此字节的内容 再解 开后得以了解对方的传送内容 第 79 页,共 606 页如上所述 串行通讯中经常使用字符传递数字 因此也必须了 解这二者之间的关系 假设我们将传送地址 01 以数字来说 它们 一个是 0 一个是 1 不过以串行通讯来说 它们却是必须直接被 视为”0”这个字符和”1”这个字符 所以必须传送”01”这个字符串 再查看 ASCII 对照表 ”0”字符的号码是&H30(以十六进制表示 数 值为 30) ”1”字符的号码是&H31(以十六进制表示 数值为 31) 因此地址 01 必须传送&H30 &H31 二个字符出去 这种数字转字 符的方法是在 PLC 的通讯控制中最为常见的 如表 4- 1-1 即是三菱 FX 系列 PLC 中的通讯格式(计算机端送出的命令格式) 表 4- 1- 1 PLC 通讯指令格式对照 163 162 161 160 161 160 161 160 STX &H2 CMD “0” &H30 “0” &H30 “0” &H30 “A” &H41 “0” &H30 “0” &H30 “2” &H32 ETX &H3 “6” &H36 “6” &H30 由表 4-1- 1 可以看出 计算机送给 PLC 的指令 看起来均是数 值 而实际上均是转换成 ASCII 上的相对应字符号码后 才传送出 去 我们的分布式模块也是如此的作法 4-1-4 命令字符 命令字符接在地址之后 用以表示主控计算机希望模块执行的 指令 命令字符可能是数字(0~9) 也可能是文字(M L F M ) 依指令的不同 所使用的命令字符可由 1 至数个不等 虽说命令字 符 (有时是字符串)可能有数字或文字 不过 如 4-1-2 节所说明的 只要是透过串行通讯端口传输出去时 一律需将其转换为 ASCII 上 的对应字码 转换为对应的字码是否很麻烦呢?其实仔细观表4- 1-1 可以发现 只要将传送的指令全部当成字符串处理就可以了 4-1-5 总合检查码(CheckSum) 不管如何地保护周全 在传输的过程中一样会有数据传输错误 的可能 为了保证传输过程的资料正确性 总合检查码就是一种方 法 通常我们较常以 CheckSum 称呼此法 不同的设备采用的 CheckSum 计算方法可能会不相同 不过其理念是一样的 分布式模块的 CheckSum 计算乃是将送出指令中的( 前导字 第 80 页,共 606 页符 )(地址)(命令) 三个部份的字符或回是应字符串中的( 前导字符)(地 址 )(数据资料) 三个部份在 ASCII 中的号码作相加 相加后的结果保 留 16 进制中的最后二位数 再将此二位数的变成一般的字符 此 二个字符即是 CheckSum 例如传送的指字符串令是$012[Enter] 分 别将前导字符($) 地址(01) 命令(2)三者取出其 ASCII 所表示的数 值相加 结果如下 CheckSum = 0x24+0x30+0x31+0x32=0xB7 最后的二个字符结果是”B” ”7” 直接将这二个当成是一般的字符 并在原始传送指令的后面 [Enter]的前面 于是应传送的字符串就 成为$012B7[Enter] 如此就完成了一个带有 CheckSum 的指令字符 串了 当主控计算机使用了 CheckSum 的方式欲将指令送出时 本身 就必须先行计算此 CheckSum 的数值 接收到指令的模块也会将所 接收到的字符串再进行一次 CheckSum 的计算 当二者的结果一致 时 则此次的传送便被认为是正确的 而二者的结果若不一致时 模块也会送回错误的代表符号 同样的 主控计算机在 CheckSum 激活的情形下接收到模块传 回的字符串时 一样必须就 CheckSum 的值再作一次计算及检查 以便确认传输过程的正确性 分布式模块出厂预设是不作 CheckSum 检查 当欲激活此 功能时 需对模块作组态设定 并且需在模块上的 INIT*脚位和 GND 脚位短路时作变更的动作 4-1-6 结尾字符 此字符用以表示一个字符串指令已被传送完毕 在分布式模块 中使用的是 ASCII 码第 13 号字符 一般说来 相当多的设备在传 送字符串的辨认上使用的结尾字符也是这个字符 使用率相当地 高 模块唯有接收到此一字符才会认定主控计算机已将指令传送完 毕 也才会进行指令的分析和执行相对应的控制动作 否则模块是 不 的动的 结尾字符在键盘上是 Enter 这个按键 模块和本书中会采用的结尾字符符号有[Enter] (CR) (Cr)等 第 81 页,共 606 页几个 请读者看到这几个代表符号时 记得是结尾字符 4-1-7 数据资料 主控计算机传送指令到模块 无非是要求模块执行某些功能或 传回模块资料 当要求资料时 所需的资料就夹在传回的字符串里 面 其位置也就是上文所提到的( 数据资料) 不同的情形下 数据 资料也会不同 当然其格式也就各有差异了 这些差异除了和要求 的指令有关之外 也和每一个模块的功能有关 详细的说明将会在 使用该模块时特别再予以说明 这 些数据资料也是以字符串的格式 被传回 不过 程序解读时必须将其视为数字 例如传回的数据字 符串是”12.55” 我们应该视为 12.55 这个数值 第 82 页,共 606 页4-2 Visual Basic 中的字符串处理 字符串的运用是分布式模块中的主轴 所有的数据交换均是以字 符串的型式进行 而在 Visual Basic 中对于字符串的处理是相当容 易的一个课题 Visual Basic 本身即提供了不少专门处理字符串的 函式 使用上很方便 4-2-1 字符串的结合 使用分布式模块大概就可以视为几乎全都在处理字符串的问 题 首先要处理的问题就是结合欲传送的字符串 分析由计算机传 送的指令中 除了数据资料是会利用数值转字符串的处理函式外 其余的均是原本就属于字符串 在 Visual Basic 中可使用二个运算 符合作字符串的合并 它们是+及 & 例如前导字符是$ 地址是 01 命令是 2 不使用 CheckSum 结尾字符是(Cr) 在 Visual Basic 里面结合这些字符的程序就要写 成 Buf=”$” & “01” & “2” & vbCr (4-2-1) Buf=”$” + “01” + “2” + vbCr (4-2-2) Buf=”$” & “01” & “2” & Chr(13) (4-2-3) Buf 是自行定义的字符串变量 而写成的(4- 2-1) (4-2- 2) (4- 2-3) 三个式子都对 VbCr 是 Visual Basic 中的常数定义 意义是 ASCII 码第 13 号 Chr(13)也是代表 ASCII 码第 13 号 4-2-2 字符串与数值的转换 在 Visual Basic 中的数值可以表示成 1.234 0.45 10 若将 其写成字符串 则是必须在其前后加上双引号(“) 成 为 ”1.234” ”0.45” ”10” 程序经常会使用变量代表某一个数值 例如 V1=1.234 (4-2-4) 程序即可用 V1 作其它的运算 当欲将此变量中的数值转换为字符 串时 使用的函式是 CStr 例如 第 83 页,共 606 页Buf=CStr(V1) (4-2-5) 若原来 V1 中的数值如(4-2- 4)式 则 (4-2- 5)式转换完成的字符串将 成为”1.234” 分布式模块中的指令均有一定的字符串格式 当数值 转换成字符串时必须符合既定的格式 欲转换成固定格式在 Visual Basic 中使用的是 Format 函式 例如我们希望(4-2- 4)式的数值变成 小数点前 2 位 小数点后 3 位 不足的地全部补 0 的字符串 则转 换程序应写成 Buf=Format(V1,”00.000”) (4-2-6) 转换后的字符串就成为”01.234” 此结果和(4- 2-5)式的结果稍有不 同 如果数值是 2.6 则利用(4- 2-6)式转换后 转换完成的字符串 会成为”02.600” 我们可看出在结果字符串 凡不足位数处 均会 以 0 补上 换个角度考虑 当我们向模块要求资料传送时 模块也是以字 符串的方式传回来 程序也必须处理此传回字符串成为数值 以便 在程序作后续的其它计算 Visual Basic 中对于字符串转数值的函 式使用的是 Val 函式 例如有一字符串变量 Buf 之内容为”12.34” 欲转换为数值 则程序可写成 Buf=”12.34” V2=Val(Buf) (4-2-7) 经 (4-2- 7)式的转换 V2 中的结果将是 12.34 此 Val 函式转换字符 串时 也会只取字符串中含有数值的部份 例如原字符串 为 ”34.6>>A” 此字符串除了数值外 还有其它的字符在其中 使 用 Val 函式转换后 得到的结果将会是 34.6 其它的字符一律不见 利用 CStr Format Val 三个函式 我们就可以轻松地处理分 布式模块中的字符串及数值间的转换问题 4-2-3 十六进制和十进制 控制领域使用十六进制的数值表示方法相当普遍 但是使用十 进制的数值表示方法却是对程序设计师来说非常直觉 二者在转换 上有一些必须注意的地方 通常说一个数值是十进制 指的通常是整数或是长整数 十进 制的数值 一般在程序以 123 或 6000 等数值表示 欲将此十进制的 第 84 页,共 606 页数值表示成十六进制时 在 Visual Basic 中使用 Hex 函式 例如 Hex(123)将传回以十六进制表示的 123 结果将是”7B” 并且此结果 是以字符串的格式传回 若是十六进制的数值 在 Visual Basic 程序的写法上 通常会 以 &H7B 表示 &H 表示其后所接的是十六进制的数值 而此&H7B 也就相当于十进制的数值 123 十六进制欲转换成十进制时 程序 中其实不需要特别加上函式 因为 Visual Basic 中原本就是以十进 制数值作运算 就算使用&H7B 在运算时编译器也会让它是 123 然后再去作运算 当原本是以字符串的型式储存十六进制的数值 而在程序中需作转换时 就得稍微注意一下了 例如原字符串 是 ”7B” 程序中欲转换时的程序代码就可能如下 Buf=”7B” Buf=”&H” + Buf V3=Val(Buf) 其中原来的 Buf 变 量是字符串 而其中存放着”7B” 欲转换前需先 将此字符串加上”&H” 让字符串成为”&H7B” 接着使用 Val 函式 此函式将会把字符串转换为十进制数值 而当此函式看到字符串中 有 ”&H”二个字符时 会把此字符串当成是十六进制的字符串去转 换 而不是当成一般的字符串 以 Visual Basic 的实时运算窗口可 以看出此差异 如图 4- 2- 1 图 4- 2- 1 以实时运算窗口验算结果 一般在二种进制间交替切换 大概使用 Hex 和 Val 函式已经足 够 不过还是特别注意到整数和长整数之间的不同 整数使用 2 个 字节记录数值 而长整数则是使用 4 个字节记录数值 C 语言中的 整数定义可以全部均是正数 或是正负数各一半 但是 Visual Basic 中的整数是正负数各一半予以代表 这样一来 整数所能表示的数 值最大就只能到 32767 因此当由十进制的数值转换到十六进制时 第 85 页,共 606 页若数值大于 32767 却又小于 65536 时 必须特别注意转换时的正 确性 以图 4- 2- 2 作个说明 图 4- 2- 2 二个数值的交叉转换结果 在图 4-2-2 将 32767 转换为十六进制的数值时 结果是”7FFF” 而将 32780 转换为十六进制的数值时 其结果是”800C” 数学转换 的结果必须是可逆的 由图中的反向验证结果 &H7FFF 的十进制 结果是 32767 和原先的数值相符 但是&H800C 反算的结果却是 - 32765 这和原先的值是不同的 那里错了呢? Visual Basic 中整数的最大值是 32767 所以大于此值的整数会 被储存成长整数 也就是以 4 个字节储存 因此 32780 实际上是被 存成长整数 但是转换为十六进制时 此数值只要使用二个字节的 空间即可存放 而当转换&H800C 时 Visual Basic 会认为此数值只 有二个字节 所以将其转换成整数 故形成负数 而不是我们原先 的值 解决此种转换上的问题的方法 就是必须确定所要转换的十六 进制数值是整数或是长整数 若是整数 当然使用 Val 函式即可转 换成功 若是长整数的话 也只要在十六进制字符串末加上一个”&” 的符号即可 如图 4- 2- 3 所示 第 86 页,共 606 页图 4- 2- 3 正确的转换方法 由图 4- 2- 3 中可以明白地看出 只要在十六进制字符串尾端加上一 个代表长整数型态的”&”符号 即可解决转换上的问题 十六进制的表示法中 每个字符可由 0~F 共有十六种 数值 所以可以表示四个位的数值 一个字节有八个位 故需使用 二个十六进制字符表示 因此整数的二个字节转换后 最多就会有 四个字符 若不足四个字符 表示数值不大 只要使用一部份的字 符即可表示数值 4-2-4 字符串的解析 在分布式模块中就是一直使用字符串在传送或是接收 由主控 计算机传送给模块的字符串当然必须经过处理 如数值转字符串 组合固定的格式顺序等等 另外模块将资料传回后 主控计算机也 必须将所收到的结果字符串作分析 找出所需要的资料 因此需要 Visual Basic 中的字符串处理函式 Visual Basic 中的字符串处理函 式在分布式模块场合中经常使用的有 Left 取得字符串的左边固定字符数的字符串 使用此一函式将传 回一字符串由左算起特定数量的字符所构成的字符串 例如 Left(“ABCD123”,3)传回的结果是”ABC” Right 取得字符串的右边固定字符数的字符串 使用此一函式将 传回一字符串由右算起特定数量的字符所构成的字符串 例 如 Right(“ABCD123”,3)传回的结果是”123” Mid 取得字符串中特定数量的字符 可指定开始撷取的位置及长 度 例如 Mid(“ABCD123”,3,2) 意为由字符串中的左边第 3 个字符开始取 2 个字符 因此传回的结果是”CD” InStr 传回在某字符串中一字符串的最先出现位置 例如 InStr(1,“ABCD123”,”12”) 意为由字符串中的左边第 1 个字 符开始寻找 原始字符串是”ABCD123” 在其中找”12”这组 字符串的位置 此时传回的结果是 5 也就是从原始字符串中 算起的第 5 个字符就可以发现字符串”12”的位置 Space 传回在固定长度的空白字符串 此函式通常用在需要产生 格式输出或清除固定长度字符串时 例如 Buf=”$016” & 第 87 页,共 606 页Space(4) & “0012” & vbCr 意为将字符串”$016”后面加上 4 个空格符(ACII 码为第 32 号 ) 再组合”0012”字符串 最后形 成 Buf 字符串变量 由于分布式模块指令一定是固定格式的 字符串 因此对分布式模块作数值组合时 若是遇到十六进 制数值转字符串的场合 就必须使用此函式作空格的先行转 换 再利用 Replace 函式转为所需的字符 Replace 当一个字符串有某一个字符需转换为另一个字符时 即 使用此一函式 例如 Buf0=Space(4) & “3456” Buf1=Replace(Buf0, Chr(32),"0") 上式即是将原来 Buf0 字符串中的字符以”0”取代掉 因此 Buf1 的结果就会是”00003456” 结合上述的几个函式 就可以处理模块所传回的结果 并从其 中分离出所要的部份 第 88 页,共 606 页4-3 指令及传回格式 I-7520 帮我们将 RS-232 的字符串讯号转换成 RS-485 的讯号后 此讯号就在网络上传布了 如果传送的内容属于该模块的话 该模 块会作反应 至于希望模块作出反应的基本条件 就是字符串的资 料格式必须正确 接收模块传回的字符串也必须作正确的解析 才 能自其中找出必要的信息 这二样重要的步骤均必须由了解指令的 格式下手 再加上正确的串行通讯传输 4-3-1 指令概观 分布式架构可以在同一个 485 网络中连接最多 256 的模块(在没 有缓冲模块的情形下) 如同之前的讨论 每一个模块必须要有一 个足以辨认的站号(也就是地址) 以便于主控计算机将相对应的命 令送达 而也保证这些模块中的相对应模块才会作响应 不致于多 个模块同时作响应而造成网络上的资料产生碰撞 其余在命令格式 后的字符串就看该模块是如何去定义的了 表 4-3-1 是一些命令字 符串的范例 表 4-3-1 部份分布式指令 命令格式 响应值 叙述 %AANNTTCCFF !AA 设定模块组态 #** No Response 同步取样 #AA >(data) 读取模拟输入值 $AA0 !AA 作频宽校正 $AA1 !AA 作零点校正 $AA2 !AATTCCFF 读取模块组态 $AA3 >S(data) 读取 CJC 值 $AA3 !AA(LO)(HI) 读取原点线性对应值 $AA4 !AA(data) 读取同步数值 $AA5 !AA(LO)(HI) 读取目标线性对应值 上表中的英文字母都所其特别的意义 这些必须看使用手册上 的规定 不过”AA”这二个字母通常代表的是地址(Address) 也就是 我们所提到的站号 后面其它的数值或是字母就要视各个模块而 定 不见得有固定的意义 除了组态指令是比较长的以外 其它的 指令其实都很短 第 89 页,共 606 页由于命令字符串的长度是不固定的 在 7000 系列模块中必须在 所有的命令后面加上结束字符 ASCII 码第 13 号 以便让所有的模 块知道一个命令的结束位置 例如 想要设定模块的组态时 我们要下达” %AANN40CCFF” 的指令(此指令用于数字输出入模块) 可是这样子的字符串却不是 直接下达到模块去 而是必须先找出对应的意义来 对应此命令 它们的意义如下 % 前导字符 AA 二个字符表示的模块地址,由 00 到 FF NN 新 的 AA 值 也就是新的地址 40 表示使用的是 DIO 模块 模块会以不同的号码来代表该模块的 使用范围 而 40 特别用以代表 DIO CC 以二个字符数字表示的传输速度 使用手册上列出的数值参 照表如表 4-3-2 表 4-3-2 传输速度代表值 CC Baud Rate 03 1200 BPS 04 2400 BPS 05 4800 BPS 06 9600 BPS 07 19200 BPS 08 38400 BPS 09 57600 BPS 0A 115200 BPS FF 以二个字符数字表示的状态码 可被拆成 8 个位 其在使用手 册上的定义依模块功能的不同而有差异 表 4-3-3 是其中一种 表 4-3-3 状态码位对应表 7 6 5 4 3 2 1 0 0 Checksum 0=disable 1=enable 7050 000 7060 001 7052 010 7053 011 第 90 页,共 606 页而在此命令的最后面还有可能加上 CheckSum 检查码的值及结束字 符 Chr(13) 当此命令下达时 相对的模块便会将该模块的情形回传 由表 4-3-1 中的回传值可以看出 如果回传的字符串开头是”! ” 表示命 令正确 手册上也说明了 若是”? ” 表示所下的命令是错误的 错 误的产生原因可能是模块地址号码错误或是下达了不正确的参数 所致 利用 详细的命令响应字符串必须参考各模块手册上的说明 不过各 命令是大同小异的 4-3-2 MSComm 控件与指令的传输 接收 在第二章时已经提到 MSComm 控件的基本使用规则 上一小 节也说明了组态指令的组成 本小节结合二者 作一个基础的说明 在指令中经常会提到数值的部份 我们必须很清楚地了解到 这些 数值都必须转换为字符串后才传送到 485 网络给分布式模块 MSComm 控件中使用 Output 属性将所欲传送的字符串送至规 定的串行端口(一般的计算机就是 COM1 或 COM2) 以上一小节的 组态指令来说 %AANN4040CCFF 指令字符串可以写成如下的字符 串 (Buf 在程序中须被宣告为字符串) Buf=”%0101400600” & vbCr 结合 MSComm 控件的 Output 属性送出此指令 于此变成 MSComm1.Output=Buf 如此就将组态指令经由串行端口送出了 而站号是 01 的模块将会 作出响应 到此完成了图 4- 1-1 中 5 个步骤中的 3 个 接下来就是 接收由模块所传回的执行结果 其中我们假设使用的 MSComm 控 件的名称是 MSComm1 正常情形下(指的是模块存在 站号正确 指令正确) 模块接 收到组态指令后会传回执行结果 以此指令来说 就是将模块本身 的组态以字符串的格式传回 MSComm 控件要接收传自模块的字符 串 使用的是 Input 属性 接收的程序就须写成(Buf 在程序中须被 宣告为字符串) Buf=MSComm1.Intput 第 91 页,共 606 页Buf 字符串变量中就存有自模块传回的执行结果 至于后续的字符 串处理和其中必要的数据分析 就要使用到 4-2- 3 节的字符串处理 技巧 4-3-3 程序步骤 在图 4-4-1 说明的指令流通过程是每一个指令都必须遵守的 在 Visual Basic 程序建构的过程中 这一连串的步骤可以归纳如下 1、 引用 MSComm 控件 并给予一适当的对象名称 可在设计时给 予串行端口号码 或在程序中给定 2、 在适当的事件程序中令 MSComm 对象之 PortOpen 属性为 True 藉以开启串行通讯端口 3、 准备送出指令时 将指令字符串作好必要的组合 接着使用 Output 指令送出(图 4- 1- 1 的步骤 1 2) 4、 等待一些时间 给模块有时间执行我们送给它的指令(图 4-1- 1 的步骤 3 4) 5、 使用 MSComm 对象的 Input 属性将执行结果收回(图 4-1- 1 的步 骤 5) 6、 其它的程序处理 这几个步骤都相当重要 尤其是第 4 个 模块一定需要时执行指令 及传送结果回主控计算机 4-3-4 MSComm 的事件 MSComm 控件在第二章的说明中提及了 OnComm 事件 透过事 件程序的侦测也可以得知被控设备所传回的信息 不过 这却不适 合用在 RS-485 系统中 试想 一个 485 网络上这么多的模块同时 在侦测相关的外界讯号 主控计算机和模块间的沟通仅仅透过二条 讯号进行 如果模块发生事件就马上将信息传送到 485 网络而期望 主控计算机知道 当一次有多个模块同时传送讯号时 这时的网络 线一定发生错乱 因此每个模块的资料讯号实际上都会在线路上形 成高低变化的电位 当很多的模块同时传上来时 就会使得线路上 第 92 页,共 606 页的讯号发生交错 讯号一定会被重组 重组后的讯号当然就不一样 了 由此在发展 485 网络程序时 鲜少用到 MSComm 的事件程序 在程序中以一定的步骤取代之 当使用在 RS- 232 型式的设备时 因此一般只有接一个设备在线路上 所以事件程序还是经常被使用 在程序中 第 93 页,共 606 页本章习题 1、 在 4-2-2 节中的数值转字符串函式中 使用 Format 指令可 以指定转换格式 若 (4- 2- 4)式中的数值是 0.3456 一样要 求 小数点前 2 位 小数点后 3 位 则转换结果为何? 2、 使用 Output 属性可以将指令送出 参考 4-3-1 节 模块地 址由 1 改成 3 时 指令应如何使用 Output 下达? 3、 欲找出一个字符串中是否含有特定的字符串应如何撰写程 序 ?若该字符串可能出现很多次 又如何找出所有的字符串 组数?以程序说明之 第 94 页,共 606 页第五章 数字输出入模块-7060D 5-1 模块介绍 实现监控最基础的便是应用数字输出入来达到检测外界状态及 控制开关 而 7000 系列模块中 最一般化的数字输出模块便是编 号 7060 的数字输出入模块 本模块用来作数字输出入之用 模块提供了二个四个信道的数 字输入及四个通道的数字输出 原厂规格标示如表 5-1-1 所示 表 5- 1- 1 I- 7060 规格表列 数字输出 数字输入 规格 数值 规格 数值 Output Channels 4 Input Channels 4 Relay Type "RL1, RL2 : Form A RL3, RL4 : Form C" Isolation Isolation with Common Source Contact Rating 0.6A @125VAC 2A @30VDC Isolation Voltage 3750Vrms Surge Strength 500V Digital Level 0 +1V max Operate Time 3mS Digital Level 1 +4 to +30 V Release Time 2mS Input Impedance 3K ohms Min. Life 5*105 ops. Power Input +10 to +30 VDC 其中标明了此模块是隔离型( Isolated) 亦即模块具有扺抗外界 一定干扰之能力 当使用模块在工业场合时 难免会有噪声(通常 是过大的电压)进到模块中 而当此噪声太大时 可能会将模块打 坏 因此工业上所使用的模块最好都选择具隔离设计 规格上的输 入隔离电压是 3750Vrms 其意义是当外界的干扰小于此值时 系 统乃具保护作用 不会导致损坏 而所谓的隔离和非隔离型设计 我们在 3- 2- 4 及 3- 2- 5 节中已作过详细说明 读者可参考该说明 5-1-1 谈谈规格 使用一个模块之前首先必须了解该模块可以使用的场合 以及 这个模块所具备的能力在那里 作为设计应用系统的参考 唯有了 解所选择模块的相关规格 才能符合系统所需 首先是数字输入的部份 由规格上可以看出此模块主要用来作 为数字输出入之用 Four isolated channels with Common power 指明 了提供四个数字输入的通道 这四个输入的通道均作隔离设计 并 第 95 页,共 606 页使用相同的输入电源(也就是说电路必须包含电源 这个设计将这 四个数字输入的电源全部接在一起) 如此的设计可以在相同的输 入通道数下 有效地减少接脚数目 数字输出的部份 此部份的输出采用的是继电器(Relay)输出 提供了四个通道 分成二种型式(Form A 及 Form B 在稍后会作详 细的说明) 继电器透过接点的接触与跳脱而达到开关动作的目的 在模块内的继电器在不同的电源型式下会有不同的电流容忍度 如 规格所述 当继电器使用在交流电(AC)场合时 125V 可以耐到 0.6A 的电流 但使用到 250V 时 则只能耐到 0.3A 同样的道理 当继 电器使用在直流电(DC)场合时 30V 可以耐到 2A 的电流 但使用 到 110V 时 则只能耐到 0.6A 由此推论 当我们要控制的电流大 过此值时 不能直接使用模块上的信道 而是必须使用串接的方式 利用模块控制前级 Relay 再用此 Relay 去控制更大电流的 Relay 继电器的开关动作时间如规格中的说明 在接触与跳脱的时间 分别需要 3 毫秒及 1 毫秒 当我们实际控制设备时 这些设备的动 作时间必须列入控制程序的考量 免得程序跑了一大段 而设备根 本就无法符合程序的要求时 我们还误以为设备出了问题呢! 模块所使用的电压是直流+10V~+30V 使用的理由是模块使用 的场合电压通常会有一些变化 不见得很稳定 有了宽广的电源允 许范围也可以保护模块运作正常(笔者的实作经验中 还是以+24V 的直流电源较佳) 5-1-2 7060 的外观及脚位定义 7060 的外观如图 5- 1- 1 所示 图 5- 1- 1 I- 7060 外观接脚配置图 第 96 页,共 606 页以上的各脚位 分成几个部份说明如下 1、 电源 标明了(R)+Vs 及 (B)GND 10 的二支脚在上图的左下 方处 分别是正电源及接地线 正电源必须接上+10V~+30V 之间的电压 笔者的建议是使用由原厂所提供的变压器 或是独立的稳压变压器 计算机内部虽然有+12V 的电源可 以使用 不过 笔者试过之后 感觉不是很稳定 所以还 是建议读者使用独立的电源较佳 2、 讯号传输 上图标明(Y)DATA+及 (G)DATA-的二支脚位紧 接在电源接脚的上方 RS- 485 网络所使用的网络线就是接 在这儿 需特别注意的是 所有 485 网络上的 DATA+必须 接在一起 而 DATA-也必须接在一起(否则讯号会错乱) 二个以上的分布式模块之电源及讯号接线示意图如图 5- 1- 2 所示 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 图 5- 1- 2 电源及讯号的接图 3、 模块初始 上图标明 INIT*的脚位紧接在讯号线接脚的旁 边 此脚位用来对模块作初始化时之用 当模块在必须初 始化时 就可以将此脚和 GND 脚位接在一起 模块内的设 定就可以回到默认值 另外 部份对于模块的特别设定也 会使用到这支脚位 4、 数字输入 在图片左上方有四个脚位标明了 IN1~IN4 这 四个点分别用来当成数字输入的接入点(所以共有四个数 字输入的脚位) 在 INIT*的旁边有一脚位标明了 IN.COM 此脚位用来当作数字输入回路中的共接点(COMMON) 只 要在 IN 和 IN.COM 之间可以形成一个完整电路 当此回路 中的电压大过模块数字输入的门槛值时 一个数字输入的 第 97 页,共 606 页高电位状态就会被模块所侦测 同样地 状态回复至模块 所定义的低电位时 模块也会得知 5、 数字输出 在 7060 图片的右方有 10 个接点 这些接点均 是用来当成数字输出用 共有四组数字输出端子 (RL1 NO,RL1 COM) (RL2 NO,RL2 COM) (RL3 NO, RL3 NC,RL3 COM) (RL4 NO, RL4 NC,RL4 COM) 接线时必 须分组接 此模块的内部电路方块如图 5- 1- 3 所示 图 5- 1- 3 I- 7060 内部电路方块图 5-1-3 和 7060D 取得联机与沟通 实验是最好的学习方式 现在我们就以 7060D 来作一个简单的 专题 先取得模块和计算机之间的联机 首先是硬件的接线部份 使用个人计算机 I- 7520 及 I- 7060 连接如图 5- 1- 4 所示 第 98 页,共 606 页 图 5- 1- 4 I- 7060 I- 7520 测试接线图 其中个人计算机和 7520 之间的接线方法是使用 RS- 232 线 (9 公 -9 母 不需跳线)作为连接 一端接到 7520 上 另一端接到计算机上 的 COM1 或 COM2(如果计算机有扩充的 COM 的话 也可以接到其 它的 COM) 至于 7060D 和 7520 间的接线 再放大显示如图 5- 1- 5 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 7060D 7520 图 5- 1- 5 讯号及电源接线方式 I-7060D 与 I-7060 在功能上是完全相同的 不同的地方是 I-7060D 多了 8 颗小型 LED 用来显示数字输入和数字输出的状态 以下我们先就计算机与 7060D之 间的讯息沟通作一项目予以探 讨 本项目使用的 7060D 中的设定均为出厂默认值(速度 9600bps 地址 1) 若读者使用的模块非默认值 可以使用模块公用程序(如 附录 2 之说明)予以变更 或于以下发展之程序直接使用您手边的 模块中的设定值亦可 第 99 页,共 606 页画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 按下 F4 叫出属性窗口 变更 CommPort 属性值为 2 若该通讯端口已 被其它设备使用 则将通讯端口号码改为 1 3、 安排一个文字框 按下 F4 叫出其属性窗口 变更 Name 属性 为 ”txtSend” 作为控制指令的输入区 4、 安排一个文字框 按下 F4 叫出其属性窗口 变更 Name 属性 为 ”txtReceive” 作为模块结果的显示区 另外也将其 MultiLine 属性设为 True 并将 ScrollBars 属性设为 2-垂直滚动条 若 传回的资料较多时 亦可方便地显示在文字框中 5、 安排二个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为”命令区”与 ”传回区” 作为上述的指令文字框及接收区 的标示之用 6、 安排三个按钮控件 其 Caption 属性分别输入 送出指令 读取资料 结束 作为执行相对应的动作之用 7、 设计后之画面如图 5-1-6 图 5-1-6 测试画面设计 动作流程解析 使用者可以在画面上的命令区输入指令(指令需 参考模块使用手册) 再按下 送出指令 按钮后 程序随即将指令 送到 RS-485 网络上 当指令正确 而且模块执行完指令后 使用者 可以按下 读取资料 将结果由串行埠的输入缓冲区中读入 并显示 到传回区的文字框中 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 第 100 页,共 606 页MSComm1.PortOpen = True 此举乃是让窗体加载后就激活通讯端口 可能是 COM1 或 COM2 端视设计时的指定数值而定 2、 双击 送出指令 的按钮 在其 Click 事件中输入以下程序代码 MSComm1.Output = Trim(txtSend.Text) & Chr(13) 将使用者输入在输入区中的字符串送出 由于分布式模块必须 透过 Chr(13)判断使用者计算机是否已将资料字符串传送完毕 因此必须在所传送的字符串最尾端加上此一字符 3、 双击 读取资料 的按钮 在其 Click 事件中输入以下程序代码 txtReceive.Text = MSComm1.Input 读取在输入缓冲区中的资料 并将其显示在文字框中 4、 双击窗体 在其 Load 事件中输入以下程序代码 Comm1.PortOpen = True 开启设定的通讯端口 在此是 COM2 5、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在命令区中输入”$012” 接着按下 送 出指令 的按钮 再按下 读取资料 的按钮后 结果可能如图 5-1-7 图 5-1-7 测试结果 再将程序及程序解释如下 第 101 页,共 606 页沟通四步骤 如上图所示 使用计算机和分布式模块取得沟通的步 骤基本上有图中所示的四个 将指令填入后 接着就是利用 Output 属性将指令送出 当模块接到指令并依指令执行工作后 会传回结 果 而此传回的结果会先暂存在计算机端的输入缓冲区中 利用 Input 属性可以将留在缓冲区中的数据取出来 当然取出来后应该 要接下去作其它的处理或是显示给操作者 在本例中则是直接取得 后显示在文字框中 $012 依照前几章的说明 此分布式模块 7060 本指令所代表的意 义 - 前导字符为 $ 模块地址为 01 指令为 2 整合起 来的意义就是 将前导字符$中的第 2 号指令送到地址编号 01 的模 块 而此指令的作用在于要求模块送回该模块的组态 (Configuration) Output/Input 由属性名称应该可以很容易明白用法 需要特别注 意的是 Output 属性是唯写(只能在程序等号的左边) 而 Input 属 性是只读(只能在程序等号的右边) 错误使用时将导致程序错误 此通讯控件的属性请直接参考第二章有关通讯控件 MSComm 的属 性说明 !01400601 相对于$012 所传回的执行结果 此结果显示出 7060D 模块的组态情形 当模块收到指令后会将对应的结果传回 在上面 的例子中所传回的这个结果依序分成几个部份说明 !01 中的! 是表示模块已接收到的指令是一个正确的指令 可以被该模块执 行 而 01 则表示模块编号为 01 40 指的是模块的使用 IO 型态 一般的可见 IO 型态有数字输出入(Digital IO) 数比输入(Analog Input) 模拟输出(Analog Output) 计时计数(Timer/Counter)等等 而在 7000 系列模块中以 40 代表本模块的 IO 型态是数字输出入 06 指的是使用的串行通讯传送速度是 9600bps 其它的代表字 符串及速度如表 5-1- 2 最后的 01 则是指该模块的编号为 7060D 表 5- 1- 2 字符串与传输速度之对应 Code 03 04 05 06 07 08 09 0A Baudrate 1200 2400 4800 9600 19200 38400 57600 115200 对于不同的指令都会有不同的传回结果表示 在各模块所附的 使用手册上对于每个指令均详细说明其对应的结果字符串及该字 符串中的各字符所代表的意义 读者应实际参阅 本书将会尽量将 常用的各指令及结果详细地在建立的各项目中予以说明 第 102 页,共 606 页 以上使用了组态传回指令将 7060D 的结果传回 图 5- 1- 8 中的 各个子图则是使用了其它不同的指令所传回的结果 各位读者可以 看出每个指令均传回不一样的字符串结果 图 5- 1- 8 不同的指令所传回的结果 读者是否注意到了 简单的程序就可以正确地控制模块了呢!在 接下来的其它章节里也是在一样的基础下建构起来的 读者也将发 现 —RS- 485 网络的分布式监控不难实现 除了以上的指令外 和 7060 模块本身(不含 IO 通道)有关的字 符串指令列如表 5- 1- 3 表 5- 1- 3 与 7060 模块本身相关的指令简述 指令字符串格式 正确时的响应格式 简述 %AANN40CCFF !AA 设定模块组态 $AA2 !AA40CCSS 读取模块组态 $AA5 !AAS 读回重置状态 $AAF !AA(number) 读取韧体版本号码 $AAM !AA(name) 读取模块名称 $AAC !AA 清除被闩锁的输入 ~** No Response 主控计算机正常 ~AA0 !AASS 读取模块状态 ~AA1 !AA 重置模块状态 ~AA2 !AASTT 读取主控计算机看门狗状态 ~AA3ETT !AA 激活主控计算机看门狗 ~AA4P/~AA4S !AAVV00 读取开机/安全输出设定值 ~AA5P/~AA5S !AA 设定开机/安全输出设定值 ~AAO(name) !AA 设定模块名称 第 103 页,共 606 页这些指令格式均是字符串 响应值也是字符串 在每一个指 令字符串的后面都必须要加上 Chr(13)字符 模块执行后的传回结 果字符串也会在最后面加上一个 Chr(13)字符 以上的程序代码 您可以参考光盘中 范例 \ 第五章 \ 指令 的送出及读取 档案夹中的项目 双击 Ex1.vbp 项目档 即可进行 以上的测试 完整的程序代码列表如下 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 读取资料 按钮后激活此事件 ' 将模块传回的资料利用 Input 指令接收 并显示在传回区文字框中 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdRead_Click() txtReceive.Text = MSComm1.Input End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 送出指令 按钮后激活此事件 ' 将写在命令区中的字符串利用 Output 属性将其送出 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdSend_Click() MSComm1.Output = Trim(txtSend.Text) & Chr(13) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口在窗体加载时就开启 让其它的程序可以开始使用通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() MSComm1.PortOpen = True End Sub 5-2 数字输入 所谓的数字输出入分为数字输出及数字输入二种 而数字的意 义指的是这种类型的输出入状态只有简单的二种状态 不是高电位 第 104 页,共 606 页(High) 就是低电位(Low) 从另一个角度来看 也可以说只有开(On) 或关(Off)二种状态 5-2-1 7060 的数字输入 数字输出的方法有很多种 端看使用的场合决定应该使用型式 及其接线方法 而 7060D 模块使用的数字输出可由模块之数字输出 定义看出 Input Channels 4 Isolation Isolation with Common Source Isolation Voltage 3750Vrms Digital Level 0 +1V max Digital Level 1 +4 to +30 V Input Impedance 3K ohms Power Input +10 to +30 VDC 逻辑 0(Logical 0)的最高电压是 1V 所谓的逻辑 0 就是我们经 常在说的低电平(Low Level)状态 或说是低电位 由于不可能给定 一个完全的 0V 因此通常都会定义一个最高的限制 只要不超过 这个值 模块都会认为是低电位状态 或称现在这个状态是 0 逻辑 1(Logical 1)的电压范围则是 4V~30V 所谓的逻辑 1 就是 我们说的高电平(High Level)状态 或说是高电位 同样的 我们也 不太可能定义一个真正的高电位电压值 因此高电位通常也会定义 一个最低的门槛(在此是 4V) 只要超过这个值 模块都会认为是高 电位状态 或称现在这个状态是 1 在模块的高电位定义中 我们 也看到了最高的电压值是 30V 此最高电压的给定考虑到模块所能 承受的电压值 太高的话将可能使得模块受到伤害 因此高电位不 可比限定的最高电压还高 并没有一个真正的绝对电压值称为某某电压哦! 电压值是比 较来的 例如 A 点是 5V B 点是 3V 那么由 B 点来看 A 点 就称 A 点是+2V 相反的 由 A 点来看 B 点就称 B 点的电压是-2V 也 由于均是找一个参考点作为电压值的读值来源 因此就在设备上可 看到一个名为 GND(接地)的接点 所有的接线电压值都以这个点作 为参考 想得到电压值的话 那就把参考点接在这个 GND 点就可 以了 第 105 页,共 606 页5-2-2 数字输入原理 如何才能造成一个数字输入的状态让模块侦测到呢?首先 我们 要了解到一点 如果要造成一个电流的流动 必须是一个完整的电 路才行 什么是一个完整的电路呢?那就要电流由高电位流向低电 位才行 考虑图 5- 2- 1 的电路示意图 電阻 電源 R V + - V 壓降 A B 電阻 電源 R V + - V=0 壓降 A B 图 5- 2- 1 电路示意图(左边为通路 右边为断路) 图 5-2-1 的左图是一个完整的电路图 当电源给定后 电流就循着 由高电位往低电位 的方向(如虚线箭头)流动 当电流通过电阻 R 时 由 V=IR 的公式 我们可以知道在电阻的二端就有了电压降(简 称压降 A 点电压高于 B 点电压) 若使用三用电表也可以量到此 电阻的二端的电压差是多少 若将此电阻二端的高电位接点 A 接到 数字输入点 再将低电位接点接到模块的 GND 点 模块就可以感 受到此电阻所产生的压降了 如果此压降的数值是模块所规定的范 围的话 此状态就可以被模块所判断 右图中的电路被断开了 这时候电流无法顺利地形成一个流动的循 环 因而成为停滞的状态 也由此在电阻的二端就因为 I=0 而使得 V 也等于 0 这种状态如果依照上述的方式接到模块 就会被模块 判定是逻辑 0 有了以上的电路概念后 我们实际看看在 7060 的模块中 针对 数字输入的电路部份 其中的部份电路图如图 5-2-2 所示 第 106 页,共 606 页A B IN.COM IN.COM IN1 +5V S1 C +5V S2 IN1 图 5-2-2 数字输入电路图及解析图 我们将图 5- 2-2 上图分成 A B C 三个部份来作一个讨论 首先是 A 部份 这是一个光藕合器 在其内部构造就如图中的 A 所示 右 边是发光二极管 左边的是光敏晶体管 其主要的目的藉由 S2 边 发光二极管的光线剌激 S1 边的光敏晶体管 当光敏晶体管受到发 光二极管的剌激后 会使得其电路导通 这个导通的动作将会使得 S1 左边的电路如同一个闸开关般地切下去 也因此使得 C 部份的 电流因为这个导通动作而由+5V 开始流经电阻 再到 GND 端 而 形成一个完整的电流流通回路 7060 模块就是藉由此回路是否形成 而得知 B 部份的电路状态 再则 我们考虑在什么情形下发光二极管会发光?当然是电流流 过去时 电压只要大于 2V 电流大于 10 毫安就可以了 如此的推 论就必须让图 5-2-2 的 S2 边也形成一个完整的电路 电流才能通过 而且方向也必须是如 S2 边的箭头般地流动才行(发光二极管有其方 向性 方向不对的话 它是不会发亮的) 既然如此 看 看图 5-2- 2 中 B 部份的电路图 由前述最简单的接线方法推论 IN.COM 脚位 就必须接正电源 而当然 IN1 就是接地了 自然就形成了一个完整 的电路 经由以上的推论 一个数字输出的电路大概也有一个初步的了 解 如果拿来接简单的测试电路也应不成问题 我们再将图 5-2- 2 的电路由右至左解析一次 B 区中的 IN.COM 接正电源 而 IN1 接 地 电流会流经电阻及 S2 边的发光二极管 此时的发光二极管因 电流的流动而发光 发出来的光线剌激到光敏晶体管而使得 S1 边 的电路导通 这个导通的动作使得 S1 左边的电路由原来的断路而 形成完整回路 7060 模块因而得知 IN.COM 及 IN1 间已形成一个电 位差 第 107 页,共 606 页晶体管(如图 5-2-3)的最简单运用就在于利用其导通特性 当其 基极 (Base)没有电流时 集极 (Collector) 与 射极 (Emitter)间的电路是断路的 而只要在 基极 和 射极 间加上 偏压达 0.7V 时 集极 与 射极 间的电路就会导通 基极的 电流可以非常小 但是由集极和射极所取出的电流可以很大 这就 具有了放大的效果 就由于此特性 利用小电流控制较大电流的开 关动作就经常利用晶体管来完成 Base Emitter Collector NPN 图 5- 2- 3 晶体管各脚位名称 5-2-3 以 7060D 侦测数字输入状态 了解模块的数字输入原理后 可以据以设计一个最简单的数字 输入接线实验 目的就是要让上一小节中图 5- 2-2 的 B 部份电路要 成立 使电流可以顺利地流动 那么理所当然地 IN COM 接脚必 须接上电源或是高电位(可以是其它完整电路中的某一个高电位 点 ) 而 IN1~IN4 则是接到 GND(或是其它完整电路中的低电位点) 利用模块中的+Vs 和 GND 二支脚位 7060D 的数字输入脚位接如 图 5- 2- 4 所示 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 INIT* IN COM IN 1 IN 2 IN 3 IN 4 图 5- 2- 4 数字输入实验接脚图 图 5- 2-4 中的 IN1~IN4 与 IN COM 之间都可以藉由+Vs 和 GND 二支脚位让电流在一个完整的电路中流动 由于需要一个切换的动 作 所以每个输入线路中都设计了一个开关(可以是指拨开关 或 第 108 页,共 606 页是其它相等的设计 只要方便就好) 这样就完成了一个数字输入 的基本接线 接下来就是程序的设计部份!要得知 7060D 的数字输入状态也 是透过串行端口将指令传送给模块 因此必须先了解与数字输入相 关的字符串指令才行 在本模块中可以使用的数字输出相关指令列 如表 5- 2- 1 表 5- 2- 1 数字输入相关指令及简述 指令字符串格式 正确时的响应格式 简述 $AA6 !(data) 读取数字输入数值 $AALS !(data) 读取被闩锁住的数字输入数值 $AAC !AA 清除被闩锁住的数字输入数值 有了表 5- 2-1 的表列 我们就先使用 5-1- 3 节的 和 7060D 取 得联机与沟通 的范例程序 测试一下到底这些指令传回的结果是 什么!下表是实验时的硬件接线方式和指令”$016”传送后所得到的 结果如表 5- 2- 2 表 5- 2- 2 数字输入状态及传回的字符串 IN1~IN4 的接线开关状态 (On 表连结,Off 表开路) 使用$016 命令所传回 的字符串 IN1-Off/IN2-Off/IN3-Off/IN4-Off !000F00 IN1-On/IN2-Off/IN3-Off/IN4-Off !000E00 IN1-Off/IN2-On/IN3-Off/IN4-Off !000D00 IN1-Off/IN2-Off/IN3-On/IN4-Off !000B00 IN1-Off/IN2-Off/IN3-Off/IN4-On !000700 IN1-On/IN2-Off/IN3-On/IN4-Off !000A00 IN1-Off/IN2-On/IN3-Off/IN4-On !000500 IN1-On/IN2-On/IN3-On/IN4-On !000000 以下再将实验的流程说明一次 假设 7060D 的地址是 1 传输 速率 9600bps 利用 5-1- 3 的范例程序 执行该程序后 依表 5-2- 2 的开关状态顺序 每完成一个线路的切换 便在指令区中输 入 ”$016” 接着按下 送出指令 的按钮 随即按下 读取数据 的按钮 正确时 模块的传回字符串就会出现在 传回区 的文字 框中 在实验的过程中 随着切换的动作 7060D 上的 DI0~DI3 四个 灯号会变化 ON 起来的那一个脚位在 7060D 上的对应灯号会灭掉 其它则是亮起 先来看看 7060D 模块手册上针对$AA6(由于我们将模块设定为 1 号 所以命令为$016)命令的说明 第 109 页,共 606 页Command : $AA6[CHK](cr) $ delimiter character AA address of reading module (00 to FF) 6 command for read digital input/output status Response : Valid Command : !(Data)[CHK](cr) Invalid Command : ?AA[CHK](cr) 而粗体字中的(Data)即是命令字符串正确时的传回字符串 此字符 串在手册中的解释为 Read Digital Input/Output Data Format Data of $AA6,$AA4,$AALS : (First Data)(Second Data)00 First Data Second Data I-7041/41D DI(8-13) 00 to 3F DI(0-7) 00 to FF I-7053/53D DI(8-15) 00 to FF DI(0-7) 00 to FF I-7060/60D DO(1 -4) 00 to 0F DI(1-4) 00 to 0F I-7063s *1 DO(1-3) 00 to 07 DI(1-8) 00 to FF 第一組 第二組 解釋了字元的意義及範圍 所以传回字符串除了前导字符”!”外 Data 分为三组 每组二个 字符 第一组是 First Data 第二组是 Second Data 第三组则固定 是 00 数值则是使用 16 进位表示 检视传回的结果字符串 对应上述的解释 我们着重在代表数 字输入情形的第二组 由结果 变化的位置在由字符串左边算起的 第 5 个字符 若所得的字符是 0(以二进制表示则是 0000) 此时所 有的数字输入状态是 ON 的 而当所得的字符是 F(以二进制表示则 是 1111)时 则所有的数字输入状态是 OFF 的 由这个结果我们知 道 当线路 ON 时 该对应的位是 0 当线路是 OFF 时 该对应的 位是 1 四个输入的状态结合起来 以四个位排列 就可得到如表 5-2-2 的结果 将这些通道及其数值整理就如表 5-2- 3 所示的结果 由此表可以查出输入状态及其应有的传回值 例如 IN1 是 ON 其它均是 OFF 由表 5-2- 3 IN1 的数值是 0 其它三个通道的数值 就分别是 2 4 8 相加后就是 0+2+4+8=14 将此十进制数值转成 十六进制的话就可以得到 E 读者试对照表 5-2- 2 的实验结果 是 否一样呢? 表 5- 2- 3 各通道及其代表之数值 数字输入通道 ON 时的代表数值 OFF 时的代表数值 第 110 页,共 606 页 二进制 十六进制 二进制 十六进制 IN1 0000 0 0001 1 IN2 0000 0 0010 2 IN3 0000 0 0100 4 IN4 0000 0 1000 8 所谓的闩锁(Latch) 指的是某一个讯号的状态被保持住 即使原来的讯号源发生了改变 被闩锁住的讯号并不因此而改变 除非解除闩锁 为了保证讯号状态一定会被处理器所处理 就会利 用闩锁来保持讯号状态 而一旦讯号已被读取 再由处理器下达解 除闩锁的指令给闩锁 IC 而解除闩锁 允许下一个讯号状态进入 5-2-4 设计数字输入项目 由 5- 2- 3 的实验 我们已经了解到 7060D 模块中的数字输入状 态的取得指令 传回字符串的解析也作了说明 本节将发展一个自 动读取数字输入状态的项目程序 此项目所需的实验线路可以如前一节所述的接线方式 图 5- 2-5 是另外设计的一个简单线路 提供读者参考 +24V GND IN COM IN1 IN2 IN3 IN4 D1 LED R1 2K S1 SW DIP-4 1 2 3 4 8 7 6 5 D2 LED R2 2K D3 LED R3 2K D4 LED R4 2K 图 5- 2- 5 数字输入实验电路图 图 5-2- 5 上标明了数字输入所使用的 5 支脚位 使用的电源是 直接由模块上接过来(出厂建议的的变压器即是+24V) 地线(GND) 当然也是由模块接过来的 如此的线路即和模块使用相同的电源及 第 111 页,共 606 页接地 透过指拨开关(DIP Switch)可以控制数字输入的状态 而发 光二极管(LED)及电阻(2K )则是为了指示线路是否导通及限流 透过此电路 我们改变指拨开关 只要状态改变了 发光二极管也 会发亮 不必查看 7060D 上的灯号 也知道现在的状况 以下就本节希望达到的功能 描述在软件上应有的考量 1、 自动读取 既然要自动 以 Visual Basic 来说免不了就要使用 定时器 或是使用串行通讯硬件上的侦测功能 在此先以定时器 为讨论的主题 待下节再讨论硬件自动侦测的作法 将程序写入 定时器中是达到自动读取资料目的的简便方法 时间间隔的控制 就利用定时器中的 Interval 属性 2、 字符串解析 由上一节 所传回的字符串形式会一致 我们必 须将数字输入所代表的二个字符(由于 7060D 只有四个数字输 入 其实也只要解析其中一个字符即可)取出 如表 5- 2-3 般地分 析出所代表的状态 3、 状态显示 有了相关的状态后 当然也要显示到画面上 前一 节使用的是文字的方式展现 本节希望使用图形予以表现 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其它 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属性为”cmbCOM”及 ”cmbNO” 作为通讯端口信道及侦测模块站 号的选择区 4、 安排三个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgON” ”imgOFF” ”imgDisable” 而在其各别的 Picture 属性中选择项目目录下的 RedLamp.jpg GreenLamp.jpg GrayLamp.jpg, 作为显示输入的状态之用 另外也要将其 Visible 属性设为 False 令其执行时不显示 5、 安排四个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgIN” 并设成数组(Index 由 0~3) 调整适当大小并排列 之 此四个 Image 控件的 Visible 属性要设成 True 6、 安排 7 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为” 通讯端口” ” 站号” ” 数字输入状 态 ” ”IN1” ”IN2” ”IN3” ”IN4” 作为各控件的标示之用 第 112 页,共 606 页7、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性 窗口 其 Name 属性改为”lblMsg” BorderStyle 选择”1- 单线固 定 ” BackColor 选择淡黄色 作为显示系统讯息之用 8、 安排三个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始侦测 结束 作为执行相对应的动作之用 9、 安排一个定时器控件 其 Interval 属性设定 1000 而 Enabled 属性设定 False 10、 设计后之画面如图 5-2-6 所示 图 5-2-6 数字输入侦测的设计画面 有了画面后 接下来考虑程序流程的相关事项 1、 通讯端口可能因使用状况的不同而改变 所以要让使用者可 自由选择 接着在 开启通讯端口 的按钮被按下后才去执 行开启通讯端口的动作 2、 站号也可能会改变 因此也让使用者可以变更站号 3、 开始侦测 的按钮按下后激活定时器作侦测的工作 并将 结果借着改变 imgIN(0)~imgIN(3)四个控件的 Picture 属性而 达到显示的目的 而 Picture 属性所需的图形 已经事先存放 在 imgON imgOFF imgDiable 等三个控件里面了 4、 当执行的过程中使用者改变通讯端口的选择时 停止侦测的 工作 关闭原来的通讯端口 等待使用再次按下 开启通讯 端口 及 开始侦测 的按钮 第 113 页,共 606 页以流程图表示如图 5- 2- 7 所示 選擇COM 選擇模組站號 啟動計時器 送出指令 延遲一段時間 讀取傳回字串 解析數位輸入 結果 變更狀態圖形 變更COM? 停止偵測? 關閉計時器No 按下「開啟通 訊埠」按鈕? 按下「開始偵 測」按鈕? Yes程式休息 Yes No No No Yes Yes 1 1 图 5- 2- 7 程序流程图 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 0 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 并令 ListIndex 为 0 让选项留在第一个项目上 由 于还未开启通讯端口 因此 开始侦测 的按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 第 114 页,共 606 页 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当然 得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布式模块所 使用的 Settings 参数 再开启通讯端口 一旦开启通讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 3、 双击 开始侦测 的按钮 在其 Click 事件中输入以下程序代码 Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If 此按钮在被按下后 程序将定时器的状态转态 原来激活的话 就将其暂停 而原来暂停的话 就将其激活 透过定时器状态的 检查 也动态地改变此按钮上显示的文字 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序循环就不需执行 当 然直接跳出去即可 这个部份的程序可以在使用者变更通讯端口 号码时 将通讯端口关闭 并将原先 Disable 的开启通讯端口按 钮重新 Enable 当然啦!如果直接在此部份中关闭原来的通讯端 口 开启新的通讯端口也是可以的 5、 双击定时器控件 在其 Timer 事件中输入以下程序代码 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If 第 115 页,共 606 页 MSComm1.Output = "$" & Buf & "6" & Chr(13) '组合完整的命令字符 串 lblMsg.Caption = "侦测" & Buf & "中 " TimeDelay 100 Buf = MSComm1.Input If Len(Buf) < 6 Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " imgIN(0).Picture = imgDisable.Picture imgIN(1).Picture = imgDisable.Picture imgIN(2).Picture = imgDisable.Picture imgIN(3).Picture = imgDisable.Picture Exit Sub End If Buf = Mid(Buf, 5, 1) '取出代表数字输入状态的字符 CheckDIStatus Buf '送入状态侦测子程序 这段程序是真正传送命令及接收传回的循环 程序中使用 Output 属性将命令送出 模块指令规定地址需使用二个字符表示 因此 程序也作了判断 由前一节的讨论可知 所传回的字符串会含有 6 个以上的字符 所以当字符数不足 6 个时 后面的检查动作就 不需要 并将灯号全设定为灰色 正常情形下就将输入结果字符以 Mid 函式取出 送到 CheckDIStatus 函式中判断并显示灯号 状态的判断使用最直接 的对照方法 以 Select Case 分别对照传回字符串(0~F 中的任一 个 字符) 程序代码请参照后列 指令一经送出后 程序作了一个 TimeDelay 的动作 此动作的目 的是让模块有时间处理指令并传回结果回来 这个动作不能没有 喔 !很多时候会忘记 特别注意一下 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 定时 器随即被激活 定时器的侦测结果可能如图 5-2-8 所示 第 116 页,共 606 页 图 5-2-8 数字输入侦测的执行结果 程序执行后 尝试着改变数字输入的状态 由画面上可以得到 其结果 灯号情形也会一直改变 不过我们注意到的是 如果改变 输入状态快一点的话 画面上的更新似乎就没有这么快了 这个问 题是反应时间不够 但可细分为二个方面讨论 一个是定时器的 Interval 属性 另一个则是 TimeDelay 的时间指定 如果是定时器 的话 我们尝试着将 Interval 的值改小 例如改为 500 毫秒或是 100 毫秒 再执行程序一次 我们发现效果还不错 如果还是不满意 定时器的 Interval 属性还可以再设小一些 不过 它总是有极限的 因为 TimeDelay 100 的叙述一直存在 总是不能达到最佳状态 所 以最终还是要解决 TimeDelay 的问题 下一节将讨论到此一问题 当我们选择了不同的通讯端口或站号进行侦测时 如果该通讯 端口或站号不存在模块 就会得到如图 5- 2- 9 的结果 图 5- 2- 9 当站号不正确时的侦测情形 第 117 页,共 606 页以上的程序代码 您可以参考光盘中 范例 \ 第五章 \ 自动 侦测数字输入-定时器 档案夹中的项目 双击 Ex.vbp 项目档 即 可进行以上的测试 完整的程序代码列表如下 Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub 第 118 页,共 606 页 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 并预设二者的选项是第一个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 0 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件引发后 就不断地执行其中的程序 ' 将地址结合命令(6)送出 再取得传回字符串并判断 ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$ Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "$" & Buf & "6" & Chr(13) '组合完整的命令字符 串 lblMsg.Caption = "侦测" & Buf & "中 " TimeDelay 100 Buf = MSComm1.Input If Len(Buf) < 6 Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " imgIN(0).Picture = imgDisable.Picture imgIN(1).Picture = imgDisable.Picture 第 119 页,共 606 页 imgIN(2).Picture = imgDisable.Picture imgIN(3).Picture = imgDisable.Picture Exit Sub End If Buf = Mid(Buf, 5, 1) '取出代表数字输入状态的字符 CheckDIStatus Buf '送入状态侦测子程序 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 状态侦测子程序 所得的字符送到这里作位对应判断 ' 这里将字符和 16 种可能的情形作比较 再显示到灯号上去 ' 当 ON 时则显示红色灯号 而 OFF 时显示绿色灯号 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub CheckDIStatus(DIBuf As String) If Len(DIBuf) > 1 Then DIBuf = Right(DIBuf, 1) '可能的字符由"0"~"F" 一一作比对 特别注意 A~F 的部份一定是大写 '手册上的规定是大写而不是小写 Select Case DIBuf Case "0" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "1" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "2" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "3" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "4" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "5" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture img IN(3).Picture = imgON.Picture Case "6" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "7" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "8" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture 第 120 页,共 606 页 imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "9" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "A" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "B" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "C" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "D" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "E" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "F" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture End Select End Sub 延迟程序 TimeDelay 写在 Module 里面 列表如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim tt& tt = GetTickCount() Do DoEvents Loop Until GetTickCount() - tt >= t End Sub 5-2-5 侦测方式的修正 由 5- 2-4 的范例程序 我们已经了解到使用定时器不断地自 7060D 模块传回数字输入状态的程序设计方法了 读者是否记得在 第 121 页,共 606 页每一个指令传送后 总是要等待一个短暂时间 才能读取传回的字 符串 当改变数字输入状态稍快时 也似乎可以感觉到画面上的灯 号改变不是很实时 要解决这个问题 除了降低定时器的 Interval 设定值外 也要从 TimeDelay 的时间着手 以 5- 2-4 的范例程序作为蓝本 以下进行适度的修正 使程序 的反应速度更快 与前一小节的程序比较 在画面上除了定时器不 同外 其它的部份是一样的 画面修改后如图 5-2-10 所示 5-2-10 使用 Do…Loop 方法的画面 仔细检视 7060D 模块传回的格式 由传回格式中发现 每一个 传回的字符串在其字符串结尾处会加上一个 CR 字符(也就是 ASCII 13) 透过检测这个字符 我们可以知道一次的传输已经结束 也 就不用再等待了 而子程序的实现方法如以下程序 ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 上述的程序 如果等到了预定的 RS 字符串(Return String) 子 程序会将收到的字符串全部传回来 如果等不到 RS 字符串的话 会在等待所设定的时间后传一个空字符串回来 所以呼叫此函式不 第 122 页,共 606 页仅可以在最佳的等待时间后得到模块传回的字符串 也可以在系统 无法传回字符串时于一定的时间后回来 而不会锁住系统的执行 (也就是当掉啦!) 已经对等待的过程时间作最佳化后 接下来也可以使用 Do…Loop 的程序语法来达到快速动作的要求 原本在 5-2-4 节中是 以定时器的不断执行达到连续侦测的目的 每一次的定时器间隔时 间毕竟存在 而 使用 Do…Loop 的话就没有时间的等待 其循环会 不断地执行下去 删除掉原来的定时器控件后 修改原来 5-2- 4 程 序的 开始侦测 按钮内的 Click 事件程序代码如下 If cmdStart.Caption = "开始侦测" Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" End If Do GetDIFrom7060D '取数据的子程序 DoEvents Loop Until cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" 其中加了一个 Do…Loop 循环 里头是一个 GetDIFrom7060D 的子 程序 这个循环会在使用者改变执行状态时侦测其是否应该停止 否则会一直执行循环 GetDIFrom7060D 子程序与原来使用定时器 时几乎一样 差异的是这个程序使用了聪明的结尾字符等待循环 以免浪费时间在无谓的等待上 此子程序如下 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "$" & Buf & "6" & Chr(13) '组合完整的命令字符 串 lblMsg.Caption = "侦测" & Buf & "中 " '等待资料传回 Buf = WaitRS(MSComm1, Chr(13), 3000) If Len(Buf) < 6 Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " imgIN(0).Picture = imgDisable.Picture imgIN(1).Picture = imgDisable.Picture imgIN(2).Picture = imgDisable.Picture imgIN(3).Picture = imgDisable.Picture Exit Sub End If Buf = Mid(Buf, 5, 1) '取出代表数字输入状态的字符 CheckDIStatus Buf '送入状态侦测子程序 读者可以发现 粗体字的部份是改变的地方 其它是一样的 由于 使用了 Do…Loop 此函式并无时间的延迟 所以在这个地方没有 第 123 页,共 606 页时间的浪费 唯一的时间等待又被聪明的 WaitRS 函式所取代 时 间的掌握可说是非常地准确 一点都不会浪费 以上修正的程序代码 您可以参考光盘中 范例 \ 第五章 \ 自动侦测数字输入-DoLoop 档案夹中的项目 双击 Ex.vbp 项目 档 即可进行以上的测试 再次将程序执行起来 我们发现程序画面上对于数字输入状态 改变的灵敏度真的是变快了 完整的程序代码列表如下 Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直 接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable 第 124 页,共 606 页 cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() If cmdStart.Caption = "开始侦测" Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" End If Do GetDIFrom7060D '取数据的子程序 DoEvents Loop Until cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 并预设二者的选项是第一个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 0 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 状态侦测子程序 所得的字符送到这里作位对应判断 ' 这里将字符和 16 种可能的情形作比较 再显示到灯号上去 ' 当 ON 时则显示红色灯号 而 OFF 时显示绿色灯号 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub CheckDIStatus(DIBuf As String) If Len(DIBuf) > 1 Then DIBuf = Right(DIBuf, 1) '可能的字符由"0"~"F" 一一作比对 特别注意 A~F 的部份一定是大写 '手册上的规定是大写而不是小写 Select Case DIBuf 第 125 页,共 606 页 Case "0" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "1" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "2" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "3" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "4" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "5" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "6" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "7" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "8" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "9" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "A" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "B" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "C" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = img ON.Picture 第 126 页,共 606 页 imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "D" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "E" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "F" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture End Select End Sub Sub GetDIFrom7060D() Dim Buf$ Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "$" & Buf & "6" & Chr(13) '组合完整的命令字符 串 lblMsg.Caption = "侦测" & Buf & "中 " '等待资料传回 Buf = WaitRS(MSComm1, Chr(13), 3000) If Len(Buf) < 6 Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " imgIN(0).Picture = imgDisable.Picture imgIN(1).Picture = imgDisable.Picture imgIN(2).Picture = imgDisable.Picture imgIN(3).Picture = imgDisable.Picture Exit Sub End If Buf = Mid(Buf, 5, 1) '取出代表数字输入状态的字符 CheckDIStatus Buf '送入状态侦测子程序 End Sub 延迟程序 TimeDelay 及 WaitRs 函式则是写在 Module 里面 列 表如下 Option Explicit Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t 第 127 页,共 606 页End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 5-3 数字输出 在 5-2 节中讨论了数字输入的相关原理及作法后 本节要讨论 的是 7060D 模块中的数字输出功能 相对于数字输入是侦测外界传 达给模块的开关状态 数字输出则是由模块将开关状态提供给外 界 当然这种开关状态只有二种情形 不是开(ON) 就是关(OFF) 只不过就方向上来说 输入与输出是分属二个截然不同的方向 5-3-1 7060 的数字输出 可以定义出开关状态的方式当然有很多种 以下是 7060D 模块 中对于数字输出的规格定义 Output Channels 4 Relay Type "RL1, RL2 : Form A RL3, RL4 : Form C" Contact Rating 0.6A@125VAC 2A@30VDC Surge Strength 500V Operate Time 3mS Release Time 2mS Min. Life 5*10^5 ops. 本模块所提供的数字输出通道(Output Channel)共有 4 个 (Form A 二个 Form C 二个) 而提供的方式是继电器(Relay)输出 可提 第 128 页,共 606 页供控制的条件在直流电和交流电的情形下分别可以是最大 125V 的 交流电压下耐 0.6 安培 而在最大直流电压 30V 下耐 2 安培 开关时间(Operate Time Release Time)指的是命令继电器导通 和断开的所需的时间 导通所需的时间是 3 毫秒 而断开所需的时 间则是 2 毫秒 继电器在工业场合中经常使用 常见到的有机械式与电 子式的 继电器提供的是利用线圈控制另一端线路的接通或断开 在继电器允许的工作条件下(最高限制电流和电压) 控制的一方的 电流不会和被控端的电流有任何的关联 也经常看到利用小继电器 控制大继电器 再辗转控制更大的继电器的情形 而这种情形的发 生通常是因为要控制的电流相当大 只好利用层层的继电器将可控 制的电流数愈提愈高 5-3-2 数字输出原理 不同的模块提供了不同方式的数字输出 7060D 模块所提供的 数字输出方式可见于图 5- 1- 3 的右下方 而图 5-3-1 将此下方图中 的一个继电器予以放大 A B C D 图 5- 3- 1 一般(Form A)继电器电路示意图 由图 5-3- 1 继电器分成二个部份讨论 在 AB 边是控制端 而 在 CD 边是被控制端 当 AB 边的线圈导电后 将会对 E 点处的簧 片具有一个拉引的力量 此力量将使得 E 点处的小圆球向左边靠 近 当其向左边而碰到由 C 延伸的线路时 CD 这条线路于是导通 由此图也可以看出二边的线路除了以磁力交换状态外 没有任何相 接的地方 所以 AB 端接点导通后 CD 端只有接合的动作 AB 端 并没有电流在 CD 线路中流动 因此 CD 端所处的电路中必须自行 提供电流 第 129 页,共 606 页继电器的型式在这个模块中有二种 分别是 Form A 及 Form C Form A型式的继电器就如图5- 3- 1 所示 一般时候在 E 点是断开的 当命令其 ON 时 接点会接在一起 Form C 型式的继电器则如图 5-3-2 所示 其中的被控制端有二种流通的线路 一种是在一般情 形下一直都导通的状态(Normal Close NC) 而在 ON 的指令下达 时其接点为脱离状态 另一种是平常为脱离状态(Normal Open NO) 而在 ON 的指令下达时其接点为接合状态 一般也可由继电 器上的标示得知该接点是 NC 或是 NO 应用上则要考虑到使用时 的状况选用 NC 或是 NO 接点 A B C D E C ' 图 5- 3- 2 Form C 继电器示意图 什么时候该用 Form A?什么时候又该采用 Form C 呢 ?如果我们 的线路只是单纯的接上 断开状态 这个时候选择 Form A是相当 适合的 如果线路上的要求是平常处于某一个线路的导通状态 使 其线上的电流流通 而在必要时 必须将此线路断开 但同时让另 一个线路上的电流流通 这个时候就必须选择 Form C型式 不过 必须了解的是 以 Form C作为应用的场合中 继电器上只有三个 接点 也就是有一点是 共通点 因此被控制的二条线是以共通 点作为切换点而切换到二条通路去 5-3-3 以 7060D 控制数字输出 了解模块的数字输出原理后 可以据以设计一个最简单的数字 输出接线实验 目的就是要控制上一小节中所提到的四个继电器的 开关状态 首先检视图 5-1- 3 右下方的数字输出电路 7060D 所提 供的四组数字输出中 有二组是 Form A(有 COM NO 二个接点) 有二组是 Form C(有 COM NO NC 三个接点) 接下来讨论数字 输出的指令 本模块中所提供用以控制模块作数字输出的命令 为 ”#AABBDD[CHK](cr)” #是前导码 AA 指的是模块地址 以 16 进位表示 而且必须是二个字符 另外的指令字符再说明此如下 BB 此二个字符用来指定所要设定的通道别 如果一次设定所有的 通道 则将 BB 设成”00” 部份分布式模块的数字输出的通道 第 130 页,共 606 页超过 8 个 在此情形下 其输出通道会被分成二组 前 8 个通 道为 A 组 而第 9 个以后的通道为 B 组 若使用输出控制的指 令一次为 A 组作设定 则 BB 需写成”0A” 反之 若是为设定 第 9 个以后的通道 则 BB 需写成”0B” DD 用来指定数字输出的数值 共二个字符 每一个字符的数值 可以由 0~F 可以代表四个位的数值 因此可以控制四个数值 输出的通道 故二个字符共有 8 个通道可控制 在 7060D 模块 中只有四个数字输出的通道 会有十六种变化 故只需使用一 个字符即可 所 DD 的数值可由”00”~”0F” 先使用 5- 1- 3 节的范例程序测试和模块间的数字输出指令的沟 通 请开启 范例 \ 第五章 \ 指令的送出及读取 档案夹中 的项目 双击 Ex1.vbp 项目档 待进入 VB 设计环境后 按下 F5 开 始进行测试 表 5-3-1 是在命令区中输入控制字符串 按 下 送出 指令 再按下 读取资料 后 其指令及结果的列表 表 5-3-1 数字输出指令的测试及其结果 接点状态 指令 传回值 RL1 RL2 RL3 RL4 #010001 > ON OFF OFF OFF #010003 > ON ON OFF OFF #010009 > ON OFF OFF ON #01000F > ON ON ON ON 相对于之前指令的讨论 由于 7060D 控制数字输出的通道只有四 个 当然只需一次设定 所以使用的指令型态上就是”#01000X”即 可 变化了最后一个字符的型态(X 可由 0~F) 就可以成功地使得 模块上的 LED 指示灯发生变化 由推论及显示出来的结果 程序 及指令是吻合的 5-3-4 设计数字输出项目 由 5- 3- 3 的实验 我们已经了解到 7060D 模块中的数字输出的 控制指令 也看到在模块上的 LED 灯号的变化 本节将发展一个 数字输出控制的项目程序 此项目所需的实验线路笔者设计如图 5-3- 3 提供读者参考 第 131 页,共 606 页+24V GND RL1 COM RL2 COM RL3 COM RL4 COM RL4 NC RL4 NO RL3 NC RL3 NO RL2 NO RL1 NO D5 LED R5 2K JP1 R6 2K D6 LED JP2 S2R7 2K D7 R8 2K D8 S3D9 R9 2K D10 R10 2K 图 5- 3- 3 数字输出实验电路图 图 5- 3- 3 上标明了数字输出所使用的脚位 使用的电源是直接由模 块上接过来(出厂建议的的变压器即是+24V) 地线(GND)当然也是 由模块接过来的 如此的线路使得我们不需另外加上其它的电源和 接地线 线路再予以说明如下 1、 JP1 JP2 S2 S3 四组接点处的右方均连接至地线 这四个 接点直接接到 7060D 模块数字输出中的 COM 接点(RL1 COM RL3 COM )即可 2、 JP1 JP2 的另一个接点接到 7060D 模块之数字输出接点中的 RL1 NO 及 RL2 NO 二个接点 3、 S2 S3 的另一边分别有二个接点 S2 的二个接点要接到 RL3 NC 及 RL3 NO S3的二个接点则是要接到 RL4 NC 及 RL4 NO 为何接线需如此呢?我们接下来先了解继电器和线路的关系 由电 路学的知识 一个简单的电路可以用图 5- 3- 4(A)表示 +24V (A) (B) BT1 BATTERY D5 LED R5 2K R5 2K D5 LED 图 5- 3- 4 基础电路 电源可由电池组产生 如图 5- 3-4 中的(A)线路走向 电流由高 电位经由 LED 塞经电阻而回到电池的负端 这样可以形成一个完 第 132 页,共 606 页整的电路 如果将电池的正端以模块的正电源予以代替 而电池的 负端就可以用模块中的 GND 予以取代 所完成的线路则如图 5-3- 4(B) 只要线路连接正确 接上电源后 发光二极管就会发光 而指示出电路的完成 由于继电器的作用是接点的接合与断开 如果对应到图 5- 3-4 可以视为线路上的某一点的接合与断开 亦即可在图 5- 3-4(B)的电 路中任意一点将其断开 断开的二端则接到 7060D 模块中的 RL1 COM 和 RL1 NO 如此就完成了第一个继电器的接线了 依照此原 理 第二个继电器也可依样画葫芦地完成实验线路 属于 Form C的第三组和第四组继电器的实验电路和第一 二 组相当类似 其中不同的地方就在于 Form C 中的继电器之 COM 接 点和 NC 接点在一般情形下是接通的(和第一 二组不同) 而 COM 接点和 NO 接点在一般情形下是不接通的(和第一 二组相同) 这 种的实验电路只要多一组线路 同样也是在电路中取一个点断开 并接到模块上的接点就可以了 综合以上的讨论 第一 二组的实 验电路需要一个电阻配上一个 LED 而第三 四组则需要二个电阻 配上二个 LED 来完成实验电路 了解这些原理后 图 5-3-3 的实验 电路图于是完成 接下来就是项目的设计 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其也 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”cmbCOM”及”cmbNO” 作为通讯端口信道及侦测模块站号 的选择区 4、 安排二个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgON” ”imgOFF” 而在其各别的 Picture 属性中选择项 目目录下的 ON.jpg OFF.jpg, 作为显示输出控制之用 另外也 要将其 Visible 属性设为 False 令其执行时不显示 5、 安排四个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgOut” 并设成数组(Index 由 0~3) 调整适当大小并排列 之 此四个 Image 控件的 Visible 属性要设成 True 6、 安排 7 个 Label 控 件 按下 F4 叫出属性窗口 其 Caption 属性分 别改为” 通讯端口” ” 站号” ” 数字输出控 制” ”RL1” ”RL2” ”RL3” ”RL4” 作为各控件的标示之用 7、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性 第 133 页,共 606 页窗口 其 Name 属性改为”lblMsg” BorderStyle 选择”1-单线固定” BackColor 选择淡黄色 作为显示系统讯息之用 8、 安排二个按钮控件 其 Caption 属性分别输入 开启通讯端口 结束 作为执行相对应的动作之用 9、 设计后之画面如图 5-3-5 所示 图 5-3-5 数字输出控制的设计画面 动作流程解析 有了画面后 接下来考虑程序流程的相关事项 1、 通讯端口可能因使用状况的不同而改变 所以要让使用者可自 由选择 接着在 开启通讯端口 的按钮被按下后才去执行开 启通讯端口的动作 2、 站号也可能会改变 因此也让使用者可以变更站号 3、 当使用者按下 imgOut(0)~imgOut(3)四个控件时 程序就去变更 此控件的 Picture 属性而达到切换 ON/OFF 的目的 而 Picture 属性所需的图形 已经事先存放在 imgON imgOFF 控件里面 而且在图形切换过来后 也接着去作实际的数字输出的程控 4、 当执行的过程中使用者改变通讯端口的选择时 停止侦测的工 作 关闭原来的通讯端口 等待使用再次按下 开启通讯端口 及 开始侦测 的按钮 有了以上的规划后 接着就是程序的安排 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 第 134 页,共 606 页 For i = 0 To 3 imgOut(i).Picture = imgOFF.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 0 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 并令 ListIndex 为 0 让选项留在第一个项目上 由 于还未开启通讯端口 因此 开始侦测 的按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 If cmdOpenCOM.Caption = "关闭通讯端口" Then MSComm1.PortOpen = False cmdOpenCOM.Caption = "开启通讯端口" Exit Sub End If '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Caption = "关闭通讯端口" lblMsg.Caption = "可按下 ON- OFF 开关 执行 Relay 的控制工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当然 得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布式模 块所使用的 Settings 参数 再开启通讯端口 一旦开启通讯端口 后 就关闭此按钮 以避免使用者按二次 造成错误 3、 双击 imgOut 的控件(任何一个均可) 本项目的重点就在此部份 在其 Click 事件中输入以下程序代码 RelayStatus(Index + 1) = Not RelayStatus(Index + 1) 第 135 页,共 606 页 If RelayStatus(Index + 1) Then imgOut(Index).Picture = imgON.Picture Else imgOut(Index).Picture = imgOFF.Picture End If '计算输出值 RLOut = 0 For i = 1 To 4 If RelayStatus(i) Then RLOut = RLOut + 2 ^ (i - 1) Next i '若通讯端口尚未开启 不可送出数据 If cmdOpenCOM.Caption = "开启通讯端口" Then Exit Sub Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & "000" & Hex(RLOut) & Chr(13) '组 合完整的命令字符串 程序使用 RelayStatus 数组变量记录现在的输出状态 而每一次 使用者按下控件时 就将状态作改变(原来是 ON 的 就将它变 成 OFF 原来是 OFF 的 就将它变成 ON) 并且将其中的 Picture 属性作变更 以符合现在的 ON/OFF 状态 在作为转态及图形的 变更后 就是使用 Output 属性将数字输出的指令(#AABBDD)送 出 AA 的部份使用 Buf 变量 BB 的部份固定是 00 而由于只 需变更输出指令字符串中的一个字符 因此 DD 也就可以使用 0 及 Hex(RLOut)作表示 合在一起就形成了 MSComm1.Output = "#" & Buf & "000" & Hex(RLOut) & Chr(13) 的完整输出指令 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序循环就不需执行 当 然直接跳出去即可 这个部份的程序可以在使用者变更通讯端口 号码时 将通讯端口关闭 并将原先 Disable 的开启通讯端口按 钮重新 Enable 当然啦!如果直接在此部份中关闭原来的通讯端 口 也开启新的通讯端口也是可以的 5、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 定时 器随即被激活 定时器的侦测结果可能如图 5-3-6 所示 第 136 页,共 606 页 图 5-3-6 数字输出控制的执行结果 程序执行后 尝试着改变数字输入的状态 由画面上可以得到其结 果 而由图 5-3-6 的结果 在面板上的 RL1 及 RL3 的灯号亮起 而 实验电路上的相对应 LED 也亮起 读者可以尝试不同的数字输出按 钮 看看实际的输出情形 以上的程序代码 您可以参考光盘中 范例 \ 第五章 \ 数 字输出控制 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上 的测试 完整的程序代码列表如下 Option Explicit ' 宣告记录四个数字输出状态的数组变量 Dim RelayStatus(1 To 4) As Boolean ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub 第 137 页,共 606 页''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() '检查是否开关通讯端口 If cmdOpenCOM.Caption = "关闭通讯端口" Then MSComm1.PortOpen = False cmdOpenCOM.Caption = "开启通讯端口" Exit Sub End If '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Caption = "关闭通讯端口" lblMsg.Caption = "可按下 ON- OFF 开关 执行 Relay 的 控制工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 并预设二者的选项是第一个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% For i = 0 To 3 imgOut(i).Picture = imgOFF.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 0 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当代表数字输出的 Image 控件被选中时触发此事件 ' 将被选中的 Relay 作转态 ' 转态后将数字输出的决定送至模块执行 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub imgOut_Click(Index As Integer) 第 138 页,共 606 页 Dim Buf$, RLOut%, i% '将记录转态 并改变 ON- OFF 按钮的显示 RelayStatus(Index + 1) = Not RelayStatus(Index + 1) If RelayStatus(Index + 1) Then imgOut(Index).Picture = imgON.Picture Else imgOut(Index).Picture = imgOFF.Picture End If '计算输出值 RLOut = 0 For i = 1 To 4 If RelayStatus(i) Then RLOut = RLOut + 2 ^ (i - 1) Next i '若通讯端口尚未开启 不可送出数据 If cmdOpenCOM.Caption = "开启通讯端口" Then Exit Sub Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & "000" & Hex(RLOut) & Chr(13) '组 合完整的命令字符串 End Sub 5-3-5 数字输出指令的传回值 由 5- 3-4 的项目程序 我们可以透过画面上的开关而控制 7060D 的开关动作 由模块上的灯号或是实验板上的灯号都可以证实程序 设计是可用的 不过 这其中还是有一些小缺失 检视分布式模块的所有指令说明可以发现 指令被送出后 模 块一定会将执行的结果传回(除非所下达指令的模块编号不存在) 一般设计时通常只关心需要的部份 而对于不需要的部份常会忽略 不计 以上一小节的情形来说 项目程序的目的只是将继电器的控 制指令输出 其它的就不管了 所以一个 Output 指令被执行后也就 没有其它的程序进行 程 序执行后似乎也没有发生什么错误 但仔 细想想 这却有危险 因为此种不接收传回值的习惯可能就此植下 那么前一次的输出指令的传回值一定还留在串行通讯端口的输入 缓冲区中 如果在程序的其它程序中执行了指令 而接收缓冲区中 的传回结果 这时将会收到前次未读取的遗留字符串和本次传回字 符串的结合字符串 此字符串可能引起程序在判断上出现误差 为 了避免这种现象的发生 最好的方式就是每次只要有命令送出 一 定得收取传回字符串 使得串行通讯端口的输入缓冲一直保持清空 的状态 方便其它指令的传回值被读取时可以有正确的格式 利用之前发展的等待子程序 以下是数字输出时在后段程序中 的修正 其中以粗体字展现者即是新增的部份 第 139 页,共 606 页略 MSComm1.Output = "#" & Buf & "000" & Hex(RLOut) & Chr(13) '组合 完整的命令字符串 '等到 cr 的字符传回 Buf = WaitRS(MSComm1, vbCr, 2000) End Sub 新增中的第一行程序使得程序必须等待 chr(13)字符的传回(因为传 回字符串的最尾端字符是此字符) 若在等待了 2000 秒后仍未传回 我们的程序依然执行一次 Input 的属性将缓冲区清空 以避免在输 入缓冲区中夹有其它的字符 而影响了下一次的数据传输纯度 以上的程序代码 您可以参考光盘中 范例 \ 第五章 \ 数字 输出控制-修正 档案夹中的项目 双击 Ex.vbp 项目档 即可进行 以上的测试 第 140 页,共 606 页5-4 数字输入及输出的结合 前面的几节针对 7060D 模块的数字输入和数字输出作了介绍 本节将结合输入及输出作出交互控制的项目 将这二个原本不相关 的二个功能结合在一起 混合中搭配着相互的功能 利用之前的电 路图 我们依样作出的实验板如图 5-4- 1 而之前的实验及接下来 的整合实验也都可以使用此实验板 图 5- 4- 1 7060D 所使用的测试板 图上均有相对应到 7060D 上的接脚编号 可以看出在接线时应 接的位置 RL1 RL2 各使用了一个发光二极管显示 Form A 式继电 器的控制情形(NO) RL3 RL4 则是各使用二个发光二极管显示 Form C式继电器的控制情形(NO NC) IN1~IN4 及 IN COM 可接 到数字输入的各相关接点 配上一个四组的指拨开关及四颗发光二 极管 可透过指拨开关的切换控制 LED 的亮灭 并进而将状态导 入数字输入的接脚中 5-4-1 数字输入结果转数字输出 原本的实验项目 我们将数字输入的结果直接显示到画面上 而在本小节中 我们将项目程序稍作修改 希望能将数字输入的结 果直接就转输出到相对应的数字输出通道 欲达到这样的结果 首 先当然要透过串行端口送出指令取得数字输入的状态 接着将此结 果同样透过串行端口将输出指令送出 画面设计如下 第 141 页,共 606 页1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其也 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”cmbCOM”及”cmbNO” 作为通讯端口信道及侦测模块站号 的选择区 4、 安排三个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属性 为”imgON” ”imgOFF” ”imgDisable” 而在其各别的 Picture 属 性中选择项目目录下的 RedLamp.jpg GreenLamp.jpg GrayLamp.jpg, 作为显示输入的状态之用 另外也要将其 Visible 属性设为 False 令其执行时不显示 5、 安排一个 Frame 控件 其 Caption 属性改为”数字输入状态” 再 安排四个 Image 控件 放入此 Frame 控件中 按下 F4 叫出其属 性窗口 变更 Name 属性为”imgIN” 并设成数组(Index 由 0~3) 调整适当大小并排列之 此四个 Image 控件的 Visible 属性要设成 True 此将作为数字输入状态显示之用 6、 安排一个 Frame 控件 其 Caption 属性改为”数字输出状态” 再 安排四个 Image 控件 放入此 Frame 控件中 按下 F4 叫出其属 性窗口 变更 Name 属性为”imgOut” 并设成数组(Index 由 0~3) 调整适当大小并排列之 此四个 Image 控件的 Visible 属性要设成 True 此将作为数字输出状态显示之用 7、 安排 10 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为”通讯端口” ”站号” ”OUT1” ”OUT2” ”OUT3” ” OUT4” ”IN1” ” IN 2” ” IN 3” ” IN 4” 作为各控件的标示 之用 8、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性窗 口 其 Name 属性改为”lblMsg” BorderStyle 选择”1-单线固定” BackColor 选择淡黄色 作为显示系统讯息之用 9、 安排二个按钮控件 其 Caption 属性分别输入 开启通讯端口 结束 作为执行相对应的动作之用 10、 设计后之画面如图 5-4-2 所示 第 142 页,共 606 页 图 5-4-2 数字输出控制的设计画面 动作流程解析 本小节程序以 5- 2-5 的程序作为参考的基准 再考 虑以下的事项 1、 一旦数字输入被侦测到后 随即将此输入通道号码转向为同一 通道的数字输出 2、 数字输入和数字输出的灯号显示要同步且状态相同 以下接着就是程序的安排 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 0 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 并令 ListIndex 为 0 让选项留在第一个项目上 第 143 页,共 606 页由于还未开启通讯端口 因此 开始侦测 的按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox " 指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当然 得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布式模 块所使用的 Settings 参数 再开启通讯端口 一旦开启通讯端口 后 就关闭此按钮 以避免使用者按二次 造成错误 3、 双击 开始侦测 控件(任何一个均可) 在其 Click 事件中输入以 下程序代码 If cmdStart.Caption = "开始侦测" Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" End If Do GetDIFrom7060D '取数据的子程序 DoEvents Loop Until cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" 在此内容中还是使用 Do…Loop 作循环的控制 其它的份旨在 更换按钮的显示文字 一方面也可拿来作为使用者的按下判 断 GetDIFrom7060D 函式则是真正的程序执行所在 而此一函 式的内容则如下 ChBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(ChBuf) = 1 Then ChBuf = "0" & ChBuf 第 144 页,共 606 页 End If MSComm1.Output = "$" & ChBuf & "6" & Chr(13) '组合完整的命令字 符串 lblMsg.Caption = "侦测" & ChBuf & "中 " '等待资料传回 Buf = WaitRS(MSComm1, Chr(13), 1000) If Len(Buf) < 6 Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture imgOut(i).Picture = imgDisable.Picture Next i Exit Sub End If Buf = Mid(Buf, 5, 1) '取出代表数字输入状态的字符 CheckDIStatus Buf '送入状态侦测子程序 SendRelayOut ChBuf, Buf '将侦测的结果转控制至继电器输出 与之前 5-2-5 的程序相当类似 也是采用了 WaitRS 的函式作为串 行埠传回及等待之用 询问数字输入状态的指令为”$AA6” 由于 必须将输入的结果再转输出到继电器 因此多了 SendRelayOut 的 函式 此函式会将模块的地址(ChBuf)和取得的输入值(Buf)当作参 数传送 而实际作数字输出 此函式如下 MSComm1.Output = "#" & ChBuf & "000" & RLOut & Chr(13) Buf = WaitRS(MSComm1, vbCr, 1000) For i = 0 To 3 imgOut(i).Picture = IIf(Val("&H" & RLOut) And 2 ^ i, imgOFF.Picture, imgON.Picture) Next I 所使用的数字输出指令模式是”#AABBDD” 依照之前讨论的方法 即可作出正确的程序 作了 Output 指令后 我们一样使用了 WaitRS 等待模块传回结果 就数字输出而言 此结果不是很重要 可是 就系统整体来说 却是不可缺的一个指令 我们在之前一直强调 的接收缓冲区在每次资料的传送后必须清空 若我们不清空的话 下一个指令的传回结果将会排在之前的指令传回值之后 而程序 接收时的结果是 除了本次的传回字符串 还夹了上一次的指令 未取走的传回字符串 这可能造成程序上的判断错误 另外是显示图形的判断 之前所使用的方法是直接将传回的字符 串作比对 而将图形作改变 如此的程序显得较为直觉 不过程 序就稍微长了一些 在这里则是使用了位比对的方式 数值是由 二进制排列的第 0 个 Bit 到第 3 个 Bit 所组成(共四个 Bit) 那么只 要将数值和每一个 Bit 的 True(程序上使用 2 的次方数即为如此 而次方数为每一个位的所在位数)作 And 运算 若结果是 1 那必 定此位的数值是 1 反之 若结果是 0 那必定此位的数值是 0 透过此判断 再将图形作改变 子程序的名称为 SendRelayOut 笔者另外也提供了一个和数字输入项目相同的比对子程序 第 145 页,共 606 页SendRelayOut_1 给读者作一个比较之用 二个子程序均可被呼 叫 结果也一样 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序循环就不需执行 当 然直接跳出去即可 这个部份的程序可以在使用者变更通讯端口 号码时 将通讯端口关闭 并将原先 Disable 的开启通讯端口按 钮重新 Enable 当然啦!如果直接在此部份中关闭原来的通讯端 口 也开启新的通讯端口也是可以的 5、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 程序 的侦测结果可能如图 5-4-3 所示 图 5-4-3 数字输出控制的执行结果 程序执行后 尝试着改变数字输入的状态 由画面上可以得到 其结果 而由图 5-4-3 的结果 在面板上的 RL1 及 RL3 的灯号亮起 而实验板上的相对应 LED 也亮起 读者可以尝试不同的数字输出按 钮 看看实际的输出情形 在图 5-4-3 的情形下 实验板的变化如 图 5-4-4 第 146 页,共 606 页 图 5-4-4 实验板所对应出的结果 实验的结果我们可以发现 模块 实验板及程序上的反应都很 快 为了证明收取传回字符串是重要的一个工作 读者可以尝试将 SendRelayOut 或 SendRelayOut_1 子程序中的 WaitRS 取消 并将程序 执行 结果是 当改变数字输入状态时 模块上数字输入灯号反应 一样保持原来的快速 但实验板及程序上的反应变慢了 其实这不 是变慢 而是数字输出指令的传回值没有被程序取回 下一次的数 字输入指令将此结果一并由串行埠的输入缓冲区传回 而造成判断 上的错误 程序因此丧失一次的读值时间 连带地影响程序所控制 的实验板 当然会看到这二者的改变速度变慢 以上的程序代码 您可以参考光盘中 范例 \ 第五章 \ 数 字输入及数字输出 档案夹中的项目 双击 Ex.vbp 项目档 即可进 行以上的测试 完整的程序代码列表如下 Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 147 页,共 606 页' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() If cmdStart.Caption = "开始侦测" Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" End If Do GetDIFrom7060D '取数据的子程序 DoEvents Loop Until cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 并预设二者的选项是第一个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 148 页,共 606 页Private Sub Form_Load() Dim i% For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 0 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 状态侦测子程序 所得的字符送到这里作位对应判断 ' 这里将字符和 16 种可能的情形作比较 再显示到灯号上去 ' 当 ON 时则显示红色灯号 而 OFF 时显示绿色灯号 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub CheckDIStatus(DIBuf As String) If Len(DIBuf) > 1 Then DIBuf = Right(DIBuf, 1) '可能的字符由"0"~"F" 一一作比对 特别注意 A~F 的部份一定是大写 '手册上的规定是大写而不是小写 Select Case DIBuf Case "0" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "1" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "2" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "3" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "4" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "5" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "6" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture 第 149 页,共 606 页 imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "7" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "8" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "9" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "A" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "B" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "C" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = img ON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "D" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "E" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "F" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture End Select End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 由 7060D 取得数字输入状态子程序 要求命令由此子程序下达 ' 取得字符串后的比对由另一个子程序负责 ' 并将输入结果转控制至继电器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub GetDIFrom7060D() Dim ChBuf$, Buf$, i% ChBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(ChBuf) = 1 Then ChBuf = "0" & ChBuf End If 第 150 页,共 606 页 MSComm1.Output = "$" & ChBuf & "6" & Chr(13) '组合完整的命令字 符串 lblMsg.Caption = "侦测" & ChBuf & "中 " '等待资料传回 Buf = WaitRS(MSComm1, Chr(13), 1000) If Len(Buf) < 6 Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture imgOut(i).Picture = imgDisable.Picture Next i Exit Sub End If Buf = Mid(Buf, 5, 1) '取出代表数字输入状态的字符 CheckDIStatus Buf '送入状态侦测子程序 SendRelayOut ChBuf, Buf '将侦测的结果转控制至继电器输出 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当数字输入的结果得到后 转控制继电器输出的子程序在此 ' 此子程序作位运算 程序较短 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SendRelayOut(ChBuf As String, RLOut As String) Dim Buf$, i% '数字输出指令 MSComm1.Output = "#" & ChBuf & "000" & RLOut & Chr(13) Buf = WaitRS(MSComm1, vbCr, 1000) '等待传回值 '每一个位均运算后 显示对应的图形 '其中使用 IIF 作二种情形的判断 For i = 0 To 3 imgOut(i).Picture = IIf(Val("&H" & RLOut) And 2 ^ i, imgOFF.Picture, imgON.Picture) Next i End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当数字输入的结果得到后 转控制继电器输出的子程序在此 ' 此子程序将数字结果以比对的方式 一对一地转送至继电器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SendRelayOut_1(ChBuf As String, RLOut As String) Dim Buf$, i% MSComm1.Output = "#" & ChBuf & "000" & RLOut & Chr(13) Buf = WaitRS(MSComm1, vbCr, 1000) Select Case RLOut Case "0" imgOut(0).Picture = imgON.Picture imgOut(1).Picture = imgON.Picture imgOut(2).Picture = imgON.Picture imgOut(3).Picture = imgON.Picture Case "1" imgOut(0).Picture = imgOFF.Picture imgOut(1).Picture = imgON.Picture imgOut(2).Picture = imgON.Picture imgOut(3).Picture = imgON.Picture Case "2" imgOut(0).Picture = imgON.Picture imgOut(1).Picture = img OFF.Picture imgOut(2).Picture = imgON.Picture 第 151 页,共 606 页 imgOut(3).Picture = imgON.Picture Case "3" imgOut(0).Picture = imgOFF.Picture imgOut(1).Picture = imgOFF.Picture imgOut(2).Picture = imgON.Picture imgOut(3).Picture = imgON.Picture Case "4" imgOut(0).Picture = imgON.Picture imgOut(1).Picture = imgON.Picture imgOut(2).Picture = imgOFF.Picture imgOut(3).Picture = imgON.Picture Case "5" imgOut(0).Picture = imgOFF.Picture imgOut(1).Picture = imgON.Picture img Out(2).Picture = imgOFF.Picture imgOut(3).Picture = imgON.Picture Case "6" imgOut(0).Picture = imgON.Picture imgOut(1).Picture = imgOFF.Picture imgOut(2).Picture = imgOFF.Picture imgOut(3).Picture = imgON.Picture Case "7" imgOut(0).Picture = imgOFF.Picture imgOut(1).Picture = imgOFF.Picture imgOut(2).Picture = imgOFF.Picture imgOut(3).Picture = imgON.Picture Case "8" imgOut(0).Picture = imgON.Picture imgOut(1).Picture = imgON.Picture imgOut(2).Picture = imgON.Picture imgOut(3).Picture = imgOFF.Picture Case "9" imgOut(0).Picture = imgOFF.Picture imgOut(1).Picture = imgON.Picture imgOut(2).Picture = imgON.Picture imgOut(3).Picture = imgOFF.Picture Case "A" imgOut(0).Picture = img ON.Picture imgOut(1).Picture = imgOFF.Picture imgOut(2).Picture = imgON.Picture imgOut(3).Picture = imgOFF.Picture Case "B" imgOut(0).Picture = imgOFF.Picture imgOut(1).Picture = imgOFF.Picture imgOut(2).Picture = imgON.Picture imgOut(3).Picture = imgOFF.Picture Case "C" imgOut(0).Picture = imgON.Picture imgOut(1).Picture = imgON.Picture imgOut(2).Picture = imgOFF.Picture imgOut(3).Picture = imgOFF.Picture Case "D" imgOut(0).Picture = imgOFF.Picture imgOut(1).Picture = imgON.Picture imgOut(2).Picture = imgOFF.Picture imgOut(3).Picture = imgOFF.Picture Case "E" imgOut(0).Picture = imgON.Picture imgOut(1).Picture = imgOFF.Picture imgOut(2).Picture = imgOFF.Picture imgOut(3).Picture = imgOFF.Picture Case "F" imgOut(0).Picture = imgOFF.Picture 第 152 页,共 606 页 imgOut(1).Picture = imgOFF.Picture imgOut(2).Picture = imgOFF.Picture imgOut(3).Picture = imgOFF.Picture End Select End Sub 5-4-2 另外的输出入指令 模块所提供的输出入指令除了以上的几节所讨论的以外 尚有 二个可用 在之前的小节中 我们对于数字输出的显示部份直接使 用程序中的状态作判断 当一个数字输出的指令下后 就将画面上 的输出灯号作改变 在正常情形下这种作法当然不会有问题 但是 当有问题发生时(如模块不正常 线路不正常 ) 虽然输出指令已 送出 画面上的灯号也作了改变 可是实际的模块却没有相对应的 输出动作 程序上将画面灯号作改变就不对了 为了解决这样的问 题 最佳的方式就是由模块上读回现在状态值 再将此状态值显示 到画面上 前面几节使用的询问数字输入状态的指令是$AA6 其实所侦测 的数字输入状态外 所传回的结果里面也包含了数字输出的状态 值 只要稍微修改一下就可以取得数字输出的状态值 而数字输出 的指令则是#AABBDD 本小节打算使用另外的二个指令 @AA 读取数字输入及数字输出的状态 传回值有四个字符 左边 的二个字符是数字输出状态 右边的二个字符则是数字输入 状态 型态与$AA6 的前四个字符相同 输出入的变化均是由 00~0F @AA(Data) 控制数字输出 与 #AABBDD 不同的是 此指令的(Data) 格式并不固定是四个字符 而是依不同模块其数字输出的通 道而有不同的控制资料格式 小于 4 个通道的模块 只要一 个数据字符 小于 8 个通道的模块则使用二个数据字符 当 通道数小于 16 时 则是使用四个数据字符 画面设计和 5-4- 1 完全相同 主要是在程序内容稍微修改了一些 主要是在 开始侦测 按钮内的程序 修正的内容如下 Do GetDIOFrom7060D '取数据的子程序 DoEvents Loop Until cmdStart.Caption = "开始侦测" 第 153 页,共 606 页笔者将 GetDIFrom7060D 名称改为 GetDIOFrom7060D 表示我们所 要进行的除了数字输入外 还包含了数字输出 此函式的内容列如 下 修 改的部份笔者以粗体予以表示 ChBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(ChBuf) = 1 Then ChBuf = "0" & ChBuf End If MSComm1.Output = "@" & ChBuf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "侦测" & ChBuf & "中 " '等待资料传回 Buf = WaitRS(MSComm1, Chr(13), 1000) If Len(Buf) < 4 Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture imgOut(i).Picture = imgDisable.Picture Next i Exit Sub End If Buf1 = Mid(Buf, 3, 1) '取出代表数字输出状态的字符 CheckDOStatus Buf1 '送入 DO 状态侦测子程序 Buf2 = Mid(Buf, 5, 1) '取出代表数字输入状态的字符 CheckDIStatus Buf2 '送入状态侦测子程序 SendRelayOut ChBuf, Buf2 '将侦测的结果转控制至继电器输出 由程序可以发现 原来的询问字符串已经改为@AA 的格式 而状 态的判断上则增加了 CheckDOStatus 其参数来自于询问字符串传 回值的第 3 个字符 CheckDOStatus 中的内容则是将原来 SendRelayOut 中的显示判断移过来 在此我们以运算的方法撰写如 下 For i = 0 To 3 imgOut(i).Picture = IIf(Val("&H" & Buf) And 2 ^ i, imgOFF.Picture, imgON.Picture) Next i 另外在输出控制的指令也作了改变 SendRelayOut 程序如下 修改 的部份以粗体予以表示 我们也发现到 改用的函式似乎更简洁 MSComm1.Output = "@" & ChBuf & RLOut & Chr(13) Buf = WaitRS(MSComm1, vbCr, 1000) '等待传回值 因此只需将询问的字符串和输出的指令稍微改变一下 就可以形成 一个全新的项目 而检查的机制也更健全 程序的修改部份并不是非常地多 在此就不予列出全部内容 以上的程序代码 您可以参考光盘中 范例 \ 第五章 \ 数字 输入及数字输出-修正 档案夹中的项目 双击 Ex.vbp 项目档 即 可进行测试 第 154 页,共 606 页5-4-3 输入状态闩锁及改变次数的记录 由以上的各个项目 我们大约可以了解 7060D 模块的用法 本 小节将再一次地将项目作修改 目的在于侦测数字输入到底被改变 了多少次 此时的情况就得提到所谓的闩锁(Latch) 考虑一种情形 监控程序运作的过程中 使用者不停地操作模 块 对于 7060D 而言 也许就一直在对数字输入的开关作切换的动 作 若程序在其输入状态切换过程序正在处理别的事情 也许就错 过了一次(甚至更多次)的输入状态 有鉴于此模块具有可将数字输 入状态保留的功能 这就是模块手册上所提到的闩锁 数字输入分为高电位(High)及低电位(Low)二种状态 模块可被 设定成记录 High 或 Low 的状态 当状态被改变时 此状态会被记 主 而模块内的计数器也会加 1 除非使用清除指令将其清除 否 则状态值会保留 且数值会一直随着输入状态的电压切换而往上增 加 相关指令说明如下 $AALS 读取被闩锁住的状态 L=1 时闩锁 High 状态 L=0 时闩 锁 Low 状态 只要设定的状态出现(未出现时的相对应位 值为 0) 此状态就会被保留在模块中(相对应的位值被设定 为 1) 此一指令即将此一状态读进程序 $AAC 清除因$AALS 指令所造成的状态 此一指令可以让模块重 新锁住新的状态 #AAN 从第 N 个通道读取数字输入的计数值 一旦数字输入状态 改变 就会在模块内将此通道的输入数字加 1 程序即可透 过此指令取回此记录值 7060D 之 N 值由 0~3 $AACN 此指令用于清除模块内部的记录值 使其数值归零 7060D 之 N 值由 0~3 以下就以 5- 4- 2 节的程序作为基础 发展本小节的项目 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其也 的属性均维持默认值 第 155 页,共 606 页3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”cmbCOM”及”cmbNO” 作为通讯端口信道及侦测模块站号 的选择区 4、 安排三个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgON” ”imgOFF” ”imgDisable” 而在其各别的 Picture 属性中选择项目目录下的 RedLamp.jpg GreenLamp.jpg GrayLamp.jpg, 作为显示输入的状态之用 另外也要将其 Visible 属性设为 False 令其执行时不显示 5、 安排一个 Frame 控件 其 Caption 属性改为”数字输入状态” 再 安排四个 Image 控件 放入此 Frame 控件中 按下 F4 叫出其属 性窗口 变更 Name 属性为”imgIN” 并设成数组(Index 由 0~3) 调整适当大小并排列之 此四个 Image 控件的 Visible 属性要设 成 True 此将作为数字输入状态显示之用 6、 安排一个 Frame 控件 其 Caption 属性改为”数字输出状态” 再 安排四个 Image 控件 放入此 Frame 控件中 按下 F4 叫出其属 性窗口 变更 Name 属性为”imgOut” 并设成数组(Index 由 0~3) 调整适当大小并排列之 此四个 Image 控件的 Visible 属性要设 成 True 此将作为数字输出状态显示之用 7、 安排一个 Frame 控件 其 Caption 属性改为”数字输入次数记录” 再安排四个 Label 控件 放入此 Frame 控件中 按下 F4 叫出其 属性窗口 变更 Name 属性为”lblNum” 并设成数组(Index 由 0~3) 调整适当大小并排列之 将其 Caption 属性均设成”????” 此将作为数字输入记录值显示之用 在此 Frame 中亦相对应安排 四个按钮控件 其 Name 属性改为”cmdClear” 亦设成数组格式 (Index 由 0~3) Caption 属性设为”清除” 这些按钮用于清除模 块内各通道的记录值 8、 安排一个 Frame 控件 其 Caption 属性改为”输入闩锁状态” 再 安排四个 Image 控件 放入此 Frame 控件中 按下 F4 叫出其属 性窗口 变更 Name 属性为”imgLatch” 并设成数组(Index 由 0~3) 调整适当大小并排列之 此将作为输入闩锁状态显示之用 9、 安排二个 Option 控件 设成数组 其 Name 属性设为”optLatch” Index 由 0 开始 Caption 属性分别设成”High” ”Low” 目的在 于让使用者选择闩锁住何种状态 在其旁安排一个按钮控件 其 Name 属性设成 cmdClsLatch 而 Caption 属性则设成”清除闩锁” 使用者可按下此键清除闩锁的值 重新再侦测 10、 安排 18 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 依各个不同的功能及 Frame 区分 分别改为”通讯端口” ”站 号” ”OUT1” ” OUT2” ” OUT3” ” OUT4” 三组的”IN1” ” IN 2” ” IN 3” ” IN 4” 作为各控件的标示之用 11、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性 第 156 页,共 606 页窗口 其 Name 属性改为”lblMsg” BorderStyle 选择”1-单线固 定” BackColor 选择淡黄色 作为显示系统讯息之用 12、 安排三个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始侦测 结束 作为执行相对应的动作之用 13、 设计后之画面如图 5-4-5 所示 图 5-4-5 包含闩锁及次数记录的设计画面 动作流程解析 使用者选好通讯端口和站号后 可按下 开启通 讯端口 及 开始侦测 的按钮 使程序和模块开始沟通 数字输入 状态会被侦测 其中还包括了次数及闩锁状态 当程序也可然执行数 字输出的控制 应记录的闩锁情形依 High/Low 选项而定 以下接着就是程序的安排 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 0 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False 第 157 页,共 606 页通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因 此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 并令 ListIndex 为 0 让选项留在第一个项目上 由于还未开 启通讯端口 因此 开始侦测 的按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 If cmbCOM.ListIndex >= 0 And cmb COM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当然 得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布式模块所 使用的 Settings 参数 再开启通讯端口 一旦开启通讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 3、 双击 开始侦测 控件(任何一个均可) 在其 Click 事件中输入以 下程序代码 If cmdStart.Caption = "开始侦测" Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" End If Do GetDIOFrom7060D '取数据的子程序 DoEvents Loop Until cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" 在此内容中还是使用 Do…Loop 作循环的控制 其它的份旨在更换 按钮的显示文字 一方面也可拿来作为使用者的按下判断 GetDIOFrom7060D 函式则是真正的程序执行所在 而此一函式的内 容则如下 ChBuf = cmbNO.List(cmbNO.ListIndex) 第 158 页,共 606 页 '若只有一位数 则在此位数的前端加上一个 0 If Len(ChBuf) = 1 Then ChBuf = "0" & ChBuf End If MSComm1.Output = "@" & ChBuf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "侦测" & ChBuf & "中 " '等待资料传回 Buf = WaitRS(MSComm1, Chr(13), 1000) If Len(Buf) < 4 Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture imgOut(i).Picture = imgDisable.Picture Next i Exit Sub End If Buf1 = Mid(Buf, 3, 1) '取出代表数字输出状态的字符 CheckDOStatus Buf1 '送入 DO 状态侦测子程序 Buf2 = Mid(Buf, 5, 1) '取出代表数字输入状态的字符 CheckDIStatus Buf2 '送入状态侦测子程序 SendRelayOut ChBuf, Buf2 '将侦测的结果转控制至继电器输出 '以下加入闩锁功能的侦测 MSComm1.Output = "$" & ChBuf & "L" & IIf(OptLatch(0).Value, "1", "0") & vbCr Buf = WaitRS(MSComm1, vbCr, 1000) Buf = Mid(Buf, 5, 1) '传回字符串的第 5 个即为输入值 '将得到的值作图形显示 For i = 0 To 3 imgLatch(i).Picture = IIf(Val("&H" & Buf) And 2 ^ i, imgOFF.Picture, imgON.Picture) Next i '以下加入计数功能的侦测 数值的部份是传回字符串的第 4 个字符开始 5 个字符 For i = 0 To 3 MSComm1.Output = "#" & ChBuf & CStr(i) & vbCr Buf = WaitRS(MSComm1, vbCr, 1000) lblNum(i).Caption = CStr(Val(Mid(Buf, 4, 5))) '数值显示 Next i 与之前 5-4-2 的程序相当类似 增加的部份如其中的粗体字 分别 是闩锁(指令为$AALS)及读取计数值(指令为#AAN)二项 这二项所 读回的状态均使用位运算的方法显示相对应的图形及数值 闩锁读值时会先检查状态设定(由 optLatch 控件的 Value 属性)是 High 或 Low 再决定使用$AAL1 或$AAL0 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 第 159 页,共 606 页使用者未改变通讯端口的号码时 此子程序循环就不需执行 当 然直接跳出去即可 这个部份的程序可以在使用者变更通讯端口 号码时 将通讯端口关闭 并将原先 Disable 的开启通讯端口按 钮重新 Enable 5、 双击四个 清除 控件其中的一个 在其 Click 事件中输入以下 程序代码 ChBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(ChBuf) = 1 Then ChBuf = "0" & ChBuf End If MSComm1.Output = "$" & ChBuf & "C" & CStr(Index) & vbCr Buf = WaitRS(MSComm1, vbCr, 1000) 模块中所被记录的数字输入次数会一直增加 此按钮内的程序用 来将记录值清除(指令为$AACN) 6、 双击 清除闩锁 按钮 在其 Click 事件中输入以下程序代码 ChBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(ChBuf) = 1 Then ChBuf = "0" & ChBuf End If MSComm1.Output = "$" & ChBuf & "C" & vbCr Buf = WaitRS(MSComm1, vbCr, 1000) 模块中的闩锁记录后 一旦状态达到便不会改变 故使用清除使 之归零 允许再一次执行闩锁动作(指令为$AAC) 7、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 程序 的侦测结果可能如图 5-4-6 所示 图 5-3-6 数字输出控制的执行结果 第 160 页,共 606 页由以上的执行结果 数字输入的记录值在每个通道都不一样 我们可以透过 清除 按钮将此记录值清掉 使其重新计数 至于输入闩锁状态的部份 由于预设锁住状态是 High 最好先 将实验板上的指拨开关全部扳到 ON 的位置 使模块上的 DI 灯号 全部灭掉(此时的模块中的状态就全部是 Low) 按下 清除闩锁 的按钮(闩锁状态灯号会变成绿色) 接着读者可以改变实验板上的 指拨开关 将其由 ON 的位置扳到相反的位置 就会发现被扳动的 通道由绿色灯号变成红色灯号 如果再将指拨开关扳回原来的 ON 位置后 灯号会不会改变呢?不会!在我们按下 清除闩锁 按钮前 状态会被记住 以上的程序代码 您可以参考光盘中 范例 \ 第五章 \ 数 字输入闩锁 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上 的测试 完整的程序代码列表如下 Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 清除 按钮后激活此事件 ' 将记录在模块内的计数值清除 重新开始计数 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdClear_Click(Index As Integer) Dim ChBuf$, Buf$ ChBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(ChBuf) = 1 Then ChBuf = "0" & ChBuf End If MSComm1.Output = "$" & ChBuf & "C" & CStr(Index) & vbCr Buf = WaitRS(MSComm1, vbCr, 1000) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 清除闩锁 按钮后激活此事件 第 161 页,共 606 页' 将原来闩锁的状态解除 让下一个状态可以被侦测 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdClsLatch_Click() Dim ChBuf$, Buf$ ChBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(ChBuf) = 1 Then ChBuf = "0" & ChBuf End If MSComm1.Output = "$" & ChBuf & "C" & vbCr Buf = WaitRS(MSComm1, vbCr, 1000) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.Lis tIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() If cmdStart.Caption = "开始侦测" Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" 第 162 页,共 606 页 End If Do GetDIOFrom7060D '取数据的子程序 DoEvents Loop Until cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 并预设二者的选项是第一个 ' ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 0 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 状态侦测子程序 所得的字符送到这里作位对应判断 ' 这里将字符和 16 种可能的情形作比较 再显示到灯号上去 ' 当 ON 时则显示红色灯号 而 OFF 时显示绿色灯号 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub CheckDIStatus(DIBuf As String) If Len(DIBuf) > 1 Then DIBuf = Right(DIBuf, 1) '可能的字符由"0"~"F" 一一作比对 特别注意 A~F 的部份一定是大写 '手册上的规定是大写而不是小写 Select Case DIBuf Case "0" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "1" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "2" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "3" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture 第 163 页,共 606 页 imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgON.Picture Case "4" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "5" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "6" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "7" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgON.Picture Case "8" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "9" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "A" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "B" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgON.Picture imgIN(3).Picture = imgOFF.Picture Case "C" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = img ON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "D" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgON.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "E" imgIN(0).Picture = imgON.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture Case "F" imgIN(0).Picture = imgOFF.Picture imgIN(1).Picture = imgOFF.Picture imgIN(2).Picture = imgOFF.Picture imgIN(3).Picture = imgOFF.Picture End Select 第 164 页,共 606 页End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 由 7060D 取得数字输入状态子程序 要求命令由此子程序下达 ' 取得字符串后的比对由另一个子程序负责 ' 并将输入结果转控制至继电器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub GetDIOFrom7060D() Dim ChBuf$, Buf$, Buf1$, Buf2$, i% ChBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(ChBuf) = 1 Then ChBuf = "0" & ChBuf End If MSComm1.Output = "@" & ChBuf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "侦测" & ChBuf & "中 " '等待资料传回 Buf = WaitRS(MSComm1, Chr(13), 1000) If Len(Buf) < 4 Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " For i = 0 To 3 imgIN(i).Picture = imgDisable.Picture imgOut(i).Picture = imgDisable.Picture Next i Exit Sub End If Buf1 = Mid(Buf, 3, 1) '取出代表数字输出状态的字符 CheckDOStatus Buf1 '送入 DO 状态侦测子程序 Buf2 = Mid(Buf, 5, 1) '取出代表数字输入状态的字符 CheckDIStatus Buf2 '送入状态侦测子程序 SendRelayOut ChBuf, Buf2 '将侦测的结果转控制至继电器输出 '以下加入闩锁功能的侦测 MSComm1.Output = "$" & ChBuf & "L" & IIf(OptLatch(0).Value, "1", "0") & vbCr Buf = WaitRS(MSComm1, vbCr, 1000) Buf = Mid(Buf, 5, 1) '传回字符串的第 5 个即为输入值 '将得到的值作图形显示 For i = 0 To 3 imgLatch(i).Picture = IIf(Val("&H" & Buf) And 2 ^ i, imgOFF.Picture, imgON.Picture) Next i '以下加入计数功能的侦测 数值的部份是传回字符串的第 4 个字符开始 5 个字符 For i = 0 To 3 MSComm1.Output = "#" & ChBuf & CStr(i) & vbCr Buf = WaitRS(MSComm1, vbCr, 1000) lblNum(i).Caption = CStr(Val(Mid(Buf, 4, 5))) '数值显示 Next i End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当数字输入的结果得到后 转控制继电器输出的子程序在此 ' 此子程序作位运算 程序较短 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SendRelayOut(ChBuf As String, RLOut As String) Dim Buf$, i% '数字输出指令 MSComm1.Output = "@" & ChBuf & RLOut & Chr(13) 第 165 页,共 606 页 Buf = WaitRS(MSComm1, vbCr, 1000) '等待传回值 End Sub Sub CheckDOStatus(Buf As String) '每一个位均运算后 显示对应的图形 '其中使用 IIF 作二种情形的判断 Dim i% For i = 0 To 3 imgOut(i).Picture = IIf(Val("&H" & Buf) And 2 ^ i, imgOFF.Picture, imgON.Picture) Next i End Sub 第 166 页,共 606 页本章习题 1、 试作一程序 让数字输出依序激活及关闭 2、 若使用”$AA6”指令取得数字输出状态值 应判断传回值中 的第几个字符?其对应程序如何撰写? 3、 试作一程序 当数字输入的个数为奇数时 第 3 个数字输 出作开关的切换动作 当数字输入的个数为偶数时 第 4 个数字输出作开关的切换动作 4、 试作一程序 当模块内的记录输入值达 10 次以上 让相对 的数字输出通道作开关切换动作 第 167 页,共 606 页第六章 模拟输入模块-7012D 6-1 模块介绍 真实世界里的物理现象 诸如温度 压力 振动 速度 位移 等等均是模拟的讯号 其型式是连续而不中断的 任一个时间点上 均存在一个数值可被读取 我们可以藉由传感器的输出而得知其值 的变化 例如温度的高低可以藉由温度计而得知其值 其它的物理 量的读值亦是如此情形 欲读取此模拟读值就需要一个模拟输入的 模块 在 7000 系列模块中 最一般化的模拟输入模块便是编号 7012D 的模拟输入模块 本模块用来作量测模拟输入之用 模块主要提供了一个信道的 模拟输入 另有二个通道的数字输出 原厂规格标示如表 6-1-1 所 示 表 6-1-1 7012D 模块重要规格 7012: Analog Input Module Analog Input · Channels: 1 · Type: mV, V , mA · Sampling rate: 10 samples/sec · Bandwidth: 5.24 Hz · Accuracy: ±0.05% · Zero drift: 20uV/°C · Span drift: 25PPm/°C · CMR 86 dB · Input Impedance : 20M Ohms · Isolation : 3000VDC Digital Input · Channel: 1 · Logic 0: 0 to 1V, Logic 1: 3.5V to 30V Event Counter · Input frequency: 50Hz max. · Input pulse width: 1ms min. Digital Output · Channels: 2 · Open collector to 30V · Output Load:Sink 30mA max. Power Dissipation 300mW 此模块仅提供了单一通道的模拟输入 其功能概分为模拟输 入 数字输入及数字输出几个部份 6-1-1 谈谈规格 以下就针对此模块的相关重要规格描述如下 了解了相关的规 格才方便继续作其它的应用 首先是模拟输入的部份 由规格上可以看出此模块的模拟输入 通道只有一个 而量测的型态(Type)则包含有 mV V mA 三种 第 168 页,共 606 页也就是此模块所量测的单位在电压上可以是毫伏特 伏特 而在电 流上则是毫安 在量测电压时 模块允许的电压范围有±150mV ±500mV ±1V ±5V ±10V 等 5 个 使用者可以依照实际情况的不同 选择 不一样的量测范围 以取得最佳的量测精度 例如 实验所接的传 感器输出范围在±0.7V 之间 设定模块时就指定其量测范围为±1V 可得到最佳的分辨率 在量测电流时 模块允许的电流范围是±20mA 这是一般最常 见的 TTL 电平中的规格 仪器中的电流输出也经常使用这个范围 取样频率(Sampling Rate)则是每秒 10 个读值 而频宽(Bandwidth) 则是 4Hz 这二个明白指出了模块取样的最高限制 因此不能拿这 个模块作快速取样 同样的 由于频宽只有 4Hz 变化较快速的电 气讯号也将无法被正确取样 这是使用前必须充份了解的事项 数字输入的部份 此部份的通道也是只有一个 逻辑 0(也就是 我们常说的 Low)电压范围为 0V~1V 逻辑 1(也就是我们常说的 High) 电压范围为 3.5V~30V 数字输出的部份 共有二个数字输出的通道可以使用 采用的 是开集极式的输出 最大的使用负载是 30V 及 30mA 模块所使用的电压是直流+10V~+30V 使用的理由是模块使用 的场合电压通常会有一些变化 不见得很稳定 有了宽广的电源允 许范围也可以保护模块运作正常 6-1-2 7012D 的外观及脚位定义 7012D 的外观如图 6-1-1 所示 第 169 页,共 606 页 图 6-1-1 7012 模块外观图 以上的各脚位 分成几个部份说明如下 1、 电源 标明了(R)+Vs 及 (B)GND 10的二支脚在上图的左下方处 分别是正电源及接地线 正电源必须接上+10V~+30V 之间的电 压 笔者的建议是使用由原厂所提供的变压器 或是独立的稳压 变压器 计算机内部虽然有+12V 的电源可以使用 不过 笔者 试过之后 感觉不是很稳定 所以还是建议读者使用独立的电源 较佳 2、 讯号传输 上图标明(Y)DATA+及 (G)DATA-的二支脚位紧接在 电源接脚的上方 RS-485 网络所使用的网络线就是接在这儿 需特别注意的是 所有 485 网络上的 DATA+必须接在一起 而 DATA- 也必须接在一起(否则讯号会错乱) 二个以上的分布式模 块之电源及讯号接线示意图如下 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 3、 模块初始 上图标明 INIT*的脚位紧接在讯号线接脚的旁边 此脚位用来对模块作初始化时之用 当模块在必须初始化时 就 可以将此脚和 GND 脚位接在一起 模块内的设定就可以回到默 认值 另外 部份对于模块的特别设定也会使用到这支脚位 第 170 页,共 606 页4、 模拟输入 在图片左上方有二个脚位标明了+IN 及 -IN 这二个 点分别用来当成模拟输入的正接入点及负接入点 不管所欲执行 的是电压的量测或是电流的量测 均是接入此二个接点(电压和 电流差一个电阻 后文将会再说明此点) 5、 数字输出 在图片的左方有二个接点 标明了 DO1/HI 及 DO0/LO 此二接点有二个不同的用途 一般当作用数字输出之 用 可由主控计算机的程控其输出与否 另一个用途则是将输出 控制权交由 7012D 模块 当输入的电压值在超过一定的电压值 或小于特定的电压值时 模块即控制这二个数字输出 这二个输 出脚位使用的是开极集式的输出方式 在后文会有详细的说明 6、 数字输入 在图左的数字输出的二个接点的中间处 有一个标 明为 DI0/EV 的脚位 此为数字输入接脚 此脚位亦有二个用途 一是当作纯数字输入之用 主控计算机可由程序得知此脚位状 态 另一用途则是当作事件(Event)之用 当此输入的状态改变 时 其改变的次数会被记录在模块内部 亦可由主控计算机的程 序得知此储存在模块内部的计数值 此计数功能被模块称为 Event 此模块的内部电路方块图如图 6-1-2 所示 图 6-1-2 7012 内部电路方块图 6-1-3 和 7012D 取得联机与沟通 和第五章所提到的 7060D 一样 欲和 7012D 模块通讯 首先使 用个人计算机 I- 7520 及 I- 7012 连接如图 6- 1- 3 第 171 页,共 606 页 图 6- 1- 3 主控计算机和 7012D 间的联机 其中个人计算机和 7520 之间的接线方法是使用 RS- 232 线 (9 公 -9 母 不需跳线)作为连接 一端接到 7520 上 另一端接到计算机上 的 COM1 或 COM2(如果计算机有扩充的 COM 的话 也可以接到其 它的 COM) 至于 7012D 和 7520 间的接线 再放大显示图 6- 1- 4 图 6- 1- 4 7520 与 7012D 电源及讯号之连接图 I-7012D 与 I-7012 在功能上是完全相同的 不同的地方是 I-7012D 多了 4 位半的 LED 用来显示输入的电压值 在第五章针对 7060D 模块讨论时 曾使用一个简单的程序作为 计算机和模块之间通讯的范例 现在一样可以使用该项目由计算机 传送指令给 7012D 模块 第 172 页,共 606 页由于分布式模块的基础在于每一个网络上的模块都有一个地 址 透过不同的地址可以控制不同的模块 在第五章中的 7060D 模 块所使用的模块地址是”1” 为了区别及未来整合实验方便 在此 将 7012D 模块的地址设定为”2” 至于设定模块地址的方式 读者 可以使用 5-1-3 节项目的程序 对出厂预设的 7012D 下达修改地址 的指令 也可以透过原厂所提供的公用程序来对模块地址编号作修 改 而笔者是比较建议使用公用程序(第一次还是使用原厂的东西 比较安心) 使用 5-1-3 的项目程序 将项目开启并按下 F5 后开始执行 在 命令区文字框中输入指令”$022” 按下 送出指令 再接着按下 读取资料 的按钮 传回的讯息如图 6- 1- 5 图 6-1-5 7012D 的组态指令及传回结果 由于分布式模块所传回的组态讯息格式是相类似的 在第五章中也 针对 7060D 模块作过讨论 读者可以参考 5- 1-3 节的解释 以下仅 就 7012D 模块的特有部份作说明 !02080600 相对于$022 所传回的执行结果 此结果显示出 7012D 模块的组态情形 当模块收到指令后会将对应的结果传回 分析此 字符串 !02 中的!是表示模块已接收到的指令是一个正确的指 令 可以被该模块执行 而 02 则表示模块编号为第 2 号 08 指的是模块使用的模拟输入范围及型态是电压输入 10V 模块 的数种输入型态及范围分别以不同的字符表示 如表 6-1-2 所示 表 6- 1- 2 型态码与量测范围 Type Code 08 09 0A 0B 0C 0D Min. Input - 10V - 5V - 1V - 500mV - 150mV - 20mA Max. Input +10V +5V +1V +500mV +150mV +20mA 第 173 页,共 606 页由表 6-1- 2 可以了解到 7012D 模块所提供的范围 使用者可依不同 的情形选用不同的范围 06 指的是使用的串行通讯传送速度是 9600bps 最后的 00 字符串可以对应到 8 个位 每个字符可由 0 变化到 F 左边的字符所对应的是由位 7~位 4 而右边的位则可以 对应到位 3~位 0 对应的关系如图 6- 1- 6 图 6- 1- 6 字符与位位置的对应 图 6- 1- 6 中的各位之代表意义如下 Bit7 0 时表示模块将排除 60Hz 的讯号 1 时表示模块将排除 50Hz 的讯号 例如在台湾 我们使用的是交流电源的频率是 60Hz 如果讯号中含有此类讯号 通常就是干扰 如果当成讯号作 量测将造成错误 因此模块中才将此讯号排除 而在其它地 方若使用的电源频率是 50Hz(例如中国大陆)的话 就要设定 为排除 50Hz 讯号才行 Bit6 此位用于表示模块是否激活 CheckSum 若为 0 表示不激活 1 表示激活 Bit5 表示模块的取样是平常取样或高速取样 0 表示正常取样 1 表示高速取样 由于 7012D 模块本身并不提供高速取样的功 能 故此位为 0 Bit4 Bit3 Bit2 固定为 0 Bit1 Bit0 00 时表示使用工程单位(Engineering Unit)的格式表现 量测的数值 01 时表示使用百分比格式 而 10 则表示使用十 六进制格式 使用何种格式并无一定 端看使用者的习惯 模块的组态除了可以取得之外 也可以透过命令字符串而将其 更改 例如我们希望将模块量测的电压设定在正负 5V 使用百分 比格式传输量测结果 使用的指令格式是”%AANNTTCCFF” 由模 块手册可以知道 AA 指的是地址 依我们现在的情形就是 02 NN 指是是新的模块编号 由于不打算改变其编号 故此值仍设为 02 第 174 页,共 606 页TT 指的是量测范围的设定 参考表 6- 1-2 TT 字符串应设为 09 CC 是通讯速度 保持 06 即可 FF 就是资料格式设定(参考图 6- 1-6) 为了使用百分比格式 除了图 6-1- 6 中的 Bit0 是 1 之外 其它的位 均是 0 排列出来的二进制表就是 00000001 若表示成十六进制的 话就是 01 故 FF 必须写成 01 到此已经将这次要作的设定所需的 字符串找出 接下来依然使用 5- 1-3 的项目程序 在命令区文字框 中输入”%0202090601” 接着按下 送出指令 按钮 再按下 读 取数据 的按钮 若操作顺利 应出现如图 6-1- 7 中的上图结果(!02 表示指令正确被执行) 而再一次使用”$022”的指令检查模块的组 态 经由传回的字符串”!02090601” 也可以知道 模块组态的确是 被改变了 图 6- 1- 7 改变组态的指令及组态检查结果 上述的组态改变测试完成后 我们可以再使用”%0202080600” 将组态改回原来的样子 若传回值是”!02” 表示改回来的动作指令 也被成功地执行 6-2 模拟输入 在我们所处的环境有可以感受到的温度 压力 流量 等物理 量 人类也相当习惯使用数字度量单位表示自己所感受到的这些物 理量 可是真实的物理量到了计算机之后 一切都不一样了 计算 机不知道数字 也不知道如何去读取温度计上的显示值 在计算机 上只有 0 与 1 两个数字(硬件上的构造则是高电位或低电位) 所有 要输入计算机中的资料必须先行作转换 因此 所有要给计算机的 讯息以及计算机中所处理的讯息资料必须先行转换为一系列的 0 1 组合 而计算机再据以解释此组合 并显示解释之后的结果在屏幕 第 175 页,共 606 页上让使用者看到 这样由真实世界中的连续讯息转换为计算机系统 中的 0 与 1 的转换过程 就是所谓的 数字化 6-2-1 数字与模拟 数字化的过程可以将物理量转换为计算机理解的讯息 由于计 算机不会主动帮我们作一些计算机基本功能以外的事情 这些额外 的工作必须藉由其它的设备予以完成 由外界的讯息变成计算机可 以解读的资料格式 其过程如图 6-2-1 图 6-2-1 模拟和数字化的过程 在图中 由传感器所量测的温度会在所经的一段时间内变化 这些变化是连续的 也就是找不到某一个时间点是没有温度值的 当设备取得这些信息时 如果要传送到计算机 就必须先将连续的 温度先转换成计算机可以判读和储存的格式 计算机没有办法记录 这些连续的讯号 只能将讯号以类似连续的离散状态予以储存和处 理 因此撷取设备或计算机是以间断的方式取得温度读值 每一次 的取值 就称之为”取样” 取样的速度愈快 所取得的讯号就会显 得和原始讯号愈接近 取样愈慢 讯号就愈失真 取样速度被称之 为 ”取样频率” 速度愈快当然愈好 不过硬件上是有其极限的 不 同的价格 其极限也不相同 不同的取样频率可能会对系统的结果造成影响 例如图 6-2-2 模拟的原始讯号一经取样后 被计算机所得到的讯号已经完全走样 了 这种情形发生在取样频率小于讯号频率 导致取毕的数字讯号 无法近似原来的模拟讯号 第 176 页,共 606 页 图 6-2-2 失真情形的产生 在高速资料撷取场合中 取样频率的选择相当的重要 有时也 必须配合滤波器的使用才能达到系统要求(最少讯号必须是可信任 的 ) 电压是进入计算机中最常见的一个参数 虽然外界的物理量众 多 但进入计算机之前大多先行转换为电压 而计算机再经一个转 换电路来读取电压值 而如果需要将电压值换算成实际的物理量呢? 其实传感器的规格书中都会附有一张对照表告诉使用者 多少的电 原对应到多少的物理量 只要照着此转换数值作转换 就可以得到 真正的物理量 以下几个是常见到的名词 模拟数字转换器 用以将模拟的电压(或电流)转换为计算机可以辨 别的数字资料 一般被称为 ADC(Analog/Digital Converter 模拟数 字转换器) 电压值必须透过此转换器的转换才能被取进计算机中 ADC 也有准确度之分 通常使用位数(Bit)表示 准确愈高的的 ADC 价格愈高(不一定成正比) 量测范围 每一个 ADC 均会定义量测电压的范围 根据不同的情况 我们可以改变 ADC 的量测范围 一般以 V 或 mV 作为范围的定义 单位 在该量测范围内 所量测的值与实际的物理量会呈线性 也 就是输出与所量测的物理量为一定的比例关系 而且可用线性之内 差法或外差法得到预测的输出 如图 6-2-3 所 示 图中 A 区 所显示 的情形即是线性范围区 此区会有一致的可预测结果 而在 B 区内 的结果则变得不可预测 第 177 页,共 606 页 图 6- 2- 3 线性输出的意义 分辨率 模拟的电压值被转换为数字资料时 是以一串的 0 与 1 的 组合来表示实际的物理量 假设转换器以 10 个位表示转换的电压 值 以二进制来说 10 个位共会有 2 的 10 次方个数目 也就是可 以代表 2 的 10 次方种不同的电压值 真实世界中的电压值是不断 地变化的 而可以被侦测到的最小电压变化量就是分辨率 以上述 的 2 的 10 次方为例 如果量测的电压在 10V 间 由于会有 2 的 10 次方种情况可以代表 因此最小的可变化电压为 10(V)/2^10= 0.009765625(V)=9.8(mV) 也就是说 只要电压的变化超过 9.8 毫伏 转换电路即可察觉 以上的说明再以图 6- 2- 4 予以表示 图 6- 2- 4 位与被转换的物理量 图 6- 2- 4 假设传感器所量测的范围是由零度 C 变化到 100 度 C 当 第 178 页,共 606 页温度在最小量测值时 储存温度信息的 IC 内的所有位均被记录为 0 若将此二进制内容以十进制予以表示 则为 0 同样地 当温度 在容许的最大量测值时 储存温度信息的 IC 内的所有位均被记录 为 1 若将此二进制内容以十进制予以表示 则为 1024(恰为 2 的 10 次方) 而当温度是位于中间状态时 就是十进制值的 512 由以上的算法亦可看出 如果想提高系统的量测分辨率 有两种方 法 :一是缩小 IC 的可量测电压范围 另一则是提高转换电路的次方 数 6-2-2 模拟输入量测接线 了解模拟输入的相关基本知识后 本节说明模拟输入量测时的 相关硬件接线方法 模拟的讯号基本上分为电压及电流 端看使用 上需求而去设定所要量测的物理量 此二种讯号在使用 7012D 作量 测时 其线号的接法如图 6- 2- 5 图的左边为量测电压时的线接方 法 而图的右边则为量测电流时的接线法 图 6-2-5 7012D 于 量测电压及电流时的接线方法 量测电压时 从一般的电路中的接法则如图 6-2- 6 左边所示 量测电流时 从一般的电路中的接法则如图 6- 2- 6 右边所示 +IN -IN +IN +IN R1 R BT1 BATTERY BT2 BATTERY R3 R R2 125 A + - M1 Load A + - M1 Load 图 6- 2- 6 电压与电流的量测接线法 在整个电路中 电压会维持定值 若电源提供的电压是 24V 那么该电路由开始(如图中的电池正端) 绕了一圈回来后(电池的负 端 ) 总共的电压就是 24V 电路中的每个负载(图 6- 2-6 中的 Load 和电阻 R 均是负载的一种)都会消耗一定的电压 所有负载所消耗 第 179 页,共 606 页的各别电压相加后的总电压就是 24V 电压量测主要是一个负载二 端的到底通过了多少的电压 也就是负载的二端到底消耗了多少的 电压 由图 6-2-6 左边的接线图 电源的走向是由负载的正端进入 而由负端出去 正端的电压会高于负端 故量测的接法就是由负载 的正端接到模块的+IN 而由负载的负端接到模块的-IN 如此即可 量测负载二端的电压降 由 V=IR 的公式 电路中的电流在电压和电阻(也就是负载)固定 的情形下 其值保持一定 当电压和电阻发生变化时 电流也会发 生改变 不过 不管电压和电阻如何改变 完整电路中的电流从起 点到终点一定保持一致 因此 量测电流时 我们可以在电路中的 任一处将线路断开 在断开处使用一个电流计即可量测此电路中的 电流 7012D 模块所量测的是电压 若要量测电流 则必须加上一 个 125 欧姆的电阻 让电流流过此电阻 再将此电阻的二端依上述 的电压接线方法接到模块中的+IN 和 - IN 即可量测此电路中的电 流 我们可以发现 以 7012D 所作的量测 其实均是量测电压 虽然使用一个外加的电阻就可以达到量测电路中的电流的 目的 不过须注意的是 这个外加的电阻的电阻值必须远小于被量 测电路中的阻抗 否则将会影响到原电路中的电流值 6-2-3 取得 7012D 的模拟输入值 了解模拟输入的相关原理后 本节将进行模拟值的量测 不过 欲进行模拟值的量测 首先就得先让模拟的电压值进到 7012D 模 块 图 6- 2- 7 是一个简单的模拟输入设计图 24V +IN -IN +IN -IN GND R4 VR 3K R5 1K D1 LED BT3 BATTERY R4 VR 3K D1 LED R5 1K 图 6- 2- 7 模拟输入的实验电路图 为了方便 我们一样使用模块上的电源(24V)及地线(GND) 将左边 的电路图稍作变化一下 就成为右边的的图形 其中 +IN 和 - IN 第 180 页,共 606 页就接往 7012D 模块的+IN 及 - IN 接脚 如此就形成了模拟输入的实 验线路 同样地 我们先使用简单的方式看看 7012D 模块所传回的模拟 输入数值的情形 使用 5-1-3 的项目程序 将项目开启并按下 F5 后开始执行 在命令区文字框中输入指令”#02” 按下 送出指 令 的按钮 接着按下 读取资料 的按钮 传回的讯息如图 6- 2-8 图 6-2-8 7012D 的模拟输入读值指令及传回结果 我们多测试几次此指令 在测试的过程也不断地改变可变电阻 的旋钮 图 6- 2- 9 是数次测试的结果 图 6- 2- 9 数次的模拟读值结果 检视图 6-2-8 和图 6-2-9 中传回的结果字符串 我们发现这些字符串 有一些共通的特性 这些数值字符串均是以”>”符号开始 后面接 一个”+” 再接数值 而数值的格式是小数点前二位 小数后三位 数值部份加起是 6 个字符 第 181 页,共 606 页 分布式模块的传回格式都有一定的格式 表 6-2- 1 是 7012D 模 块的模拟值格式 表 6- 2- 1 7012D 使用的资料格式 型态码 量测范围 格式单位 正方向全 刻度值 零值 负方向全 刻度值 工程单位 +10.000 +00.000 - 10.000 百分比 +100.00 +000.00 - 100.00 08 - 10 to +10 V 十六进制 7FFF 0000 8000 工程单位 +5.0000 +0.0000 - 5.0000 百分比 +100.00 +000.00 - 100.00 09 - 5 to +5 V 十六进制 7FFF 0000 8000 工程单位 +1.0000 +0.0000 - 1.0000 百分比 +100.00 +000.00 - 100.00 0A - 1 to +1 V 十六进制 7FFF 0000 8000 工程单位 +500.00 +000.00 - 500.00 百分比 +100.00 +000.00 - 100.00 0B - 500 to +500 mV 十六进制 7FFF 0000 8000 工程单位 +150.00 +000.00 - 150.00 百分比 +100.00 +000.00 - 100.00 0C - 150 to +150 mV 十六进制 7FFF 0000 8000 工程单位 +20.000 +00.000 - 20.000 百分比 +100.00 +000.00 - 100.00 0D - 20 to +20 mA 十六进制 7FFF 0000 8000 本模块使用之初被我们型态码将设定为 08 且使用工程单位 依此对应到表 6-2-1 中的上列部份 被量测的值会成为小数点前二 位 小数点后三位的资料格式 而且在数值前会有正负符号 — 这符 合我们的实验结果 表 6-2- 1 中还显示了另二种不同的量测格式 — 百分比及十六进 制 所谓的百分比指的是将量测的范围分成正方向 100 等分 负方 向 100 等分 一个模拟值进到 7012D 后 模块上的显示及程序所得 到的结果就是输入值占正方向的百分比或负方向的百分比 而 十六 进制就是直接将所使用模拟数字转换位以十六进制数值表示出来 最为低阶 不管使用的格式是那一种 传回的数值均会依照表 6-2- 1 的格式 因此 当程序以自动化的方法作数值转换时 就可以使用 固定的转换程序 而不必担心会出错 实务的使用上 笔者还是比 较 习惯工程单位 因为最易了解 第 182 页,共 606 页6-2-4 连续取值 在上一节中已经取得了模拟输入的数值 不过每一次都得按一 下的话 似乎就显得太麻烦了 本节将藉由定时器及部份的字符串 处理函式作模拟值的读取及处理 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其它 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属性为”cmbCOM”及 ”cmbNO” 作为通讯端口信道及侦测模块 站号的选择区 4、 安排一个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 改为”????” 按出 Font 属性 并将字号设为 18 颜色设为蓝色 读者亦可使用其它的显示设定 5、 安排 3 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为”通讯端口” ”站号” ”电压值” 作为各控件的标示 之用 6、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性 窗口 其 Name 属性改为”lblMsg” BorderStyle 选择”1- 单线固 定 ” BackColor 选择淡黄色 作为显示系统讯息之用 7、 安排三个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始侦测 结束 作为执行相对应的动作之用 8、 安排一个定时器控件 其 Interval 属性设定 100 而 Enabled 属 性设定 False 9、 设计后之画面如图 6-2-10 所示 第 183 页,共 606 页 图 6-2-10 模拟值读取画面设计 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 当一切选择妥当后 按下开启通讯端口的按钮 接着激活定时 器 而定时器就依 Interval 属性值一直执行其内部的程序代码 其程 序代码即是不断地对 7012D 模块送出询问模拟读值的指令 接着处 理传回的指令结果 显示模拟输入值在画面上 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 并令 ListIndex 为 0 让选项留在第一个项目上 由于还未开启通讯端口 因此 开始侦测 的按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then 第 184 页,共 606 页 MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当然 得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布式模 块所使用的 Settings 参数 再开启通讯端口 一旦开启通讯端口 后 就关闭此按钮 以避免使用者按二次 造成错误 3、 双击 开始侦测 的按钮 在其 Click 事件中输入以下程序代码 Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If 此按钮在被按下后 程序将定时器的状态转态 原来激活的话 就将其暂停 而原来暂停的话 就将其激活 透过定时器状态 的检查 也动态地改变此按钮上显示的文字 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序循环就不需执行 当然直接跳出去即可 这个部份的程序可以在使用者变更通讯 端口号码时 将通讯端口关闭 并将原先 Disable 的开启通讯端 口按钮重新 Enable 5、 双击定时器控件 在其 Timer 事件中输入以下程序代码 第 185 页,共 606 页 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " Buf = WaitRS(MSComm1, vbCr, 1000) If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If ValueStr = Val(Mid(Buf, Pos1 + 1, 7)) '分离出正号以后的数值 lblValue.Caption = Format(ValueStr, "0.000") & "V" '显示在画面上 这段程序是真正传送命令及接收传回的循环 程序中使用 Output 属性将命令送出 模块指令规定地址需使用二个字符表示 因此 程序也作了判断 指令一经送出后 程序用了一个之前讨论的 WaitRS 函式接收传回值 由于传回的电压值是以”>”符号作为前导 程序接下来就寻找”>” 符号所在位置 而接在此符号后面的就是正负号及真正的数值 了 数值若包含正负号的话为 7 个字符 故程序中撷取 7 个字符 出来 并将其转换为单精度数值 使用 Format 指令可以要求显 示的数值依一定的格式 在此我们要求的格式是小数点前一位 小数点后三位 而格式字符串中的”0” 则表示当数值在此位置 无值时 以 0 显示 大部份的数值会在 0 到 10 之间 使用这个 格式会让使用者看起来比较不会累(数值的的位数不会变化之 故 ) 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 定时 器随即被激活 定时器的侦测结果可能如图 6-2-11 所示 第 186 页,共 606 页 图 6-2-11 读值程序的执行结果 程序执行后 转动可变电阻 画面上的电压值就会一直改变 同时 在模块本身的 LED 显示器上也会出现相同的变化 我们可 以比较一下二者是否一致 实验时需特别注意到模块的编号是 02 很多时候会忘掉 而一 直用 01 作为指令的传输开头字符串 同时 通讯端口也得看看自 己计算机上所用的通讯端口来决定 以上的程序代码 您可以参考光盘中 范例 \ 第六章 \ 模拟 值的自动读取 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以 上的测试 完整的程序代码列表如下 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开 启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End 第 187 页,共 606 页End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口号码及站号填入 Combo 控件 ' 预设通讯端口的选项是第一个 ' 预设站号的选项是第二个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" 第 188 页,共 606 页 cmbCOM.ListIndex = 0 cmdStart.Enabled = False End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件引发后 就不断地执行其中的程序 ' 将模拟读值命令送出 再取得传回字符串并判断 ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, ValueStr As Single, Pos1% Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " Buf = WaitRS(MSComm1, vbCr, 1000) If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If ValueStr = Val(Mid(Buf, Pos1 + 1, 7)) '分离出正号以后的数值 lblValue.Caption = Format(ValueStr, "0.000") & "V" '显示在画面上 End Sub Module 中的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不 正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then 第 189 页,共 606 页 WaitRS = Buf Else WaitRS = "" End If End Function 6-2-5 连续读值及绘图显示 通常将外界的讯号读进来后 显示为数值当然是常见的 但是 人类的视觉却是对于图形最为有感觉 因此将资料以线条的方式表 现在画面上也是最常见一种图形展现 Visual Basic 当中 对于图形的表现最为方便的控件就是使用图片 框(PictureBox)控件 图片框不仅可以将图片读进其中显示 并可使 用程序语法绘制图形在其上 而实务上经常使用图片框作为结果的显 示区 而绘图时应考虑的项目有 订立坐标 决定图形的种类 决定 绘图笔的宽度 决定绘图的形态 等等 以下就这些部份作一简要的 说明 1、 订立坐标 Visual Basic 提供的坐标系统设定共有 8 种 如表 6-2-2 所示 表 6-2-2 Visual Basic 中可设定的坐标种类 ScaleMode 设定 描述 0 使用者自订 若直接设定了 ScaleWidth ScaleHeight ScaleTop 或 ScaleLeft 则 ScaleMode 属性会自动地设定 为 0 1 Twips 这是预设刻度 1,440 twips 等于一英吋 2 点 72 点等于一英吋 3 像素 像素是显示器或打印机分辨率的最小单位 每英吋里像 素的数目是由外围设备的分辨率来决定 4 字符 打印时 一个字符是 1/6 英吋高 1/12 英吋宽 5 英吋 6 公厘 7 公分 以前在设定坐标型态不方便时 几乎都是使用像素作为绘图的依据 而且必须自行计算相关的绘图位置 笔者比较习惯使用自订坐标系 统 依实际的数据点的多寡及数值的大小来订立适当的绘图范围 如 此一来 程序的写作上也比较方便且直觉 2、 图形种类 欲在图片框上绘图时 我们可能想到使用线条或是其 其它的几何图形来作 这些大部份使用 Line 指令及 Circle 指令予以 完成 Line 指令的格式如下 第 190 页,共 606 页object.Line [Step] (x1, y1) [Step] - (x2, y2), [color], [B ][F ] 其中参数的意义如表 6-2-3 表 6-2-3 Line 指令的参数意义 参数 说明 Object 欲绘图的对象 如窗体 图片框等等 Step 指定相对于由 CurrentX 及 CurrentY 属性指定的目前图形位置的起始 点坐标 (x1, y1) 绘制的启始位置 若省略则表示以上一次的绘制终点为此次绘制的启点 Step 指定相对于线的起始点的终止点坐标 (x2, y2) 绘制的终止点坐标值 color 指定画线的 RGB 色彩的值(Long) 若忽略这个值 则使用对象的 ForeColor 的属性值 B 如果包含的话 表示将绘一方块 且以指定方块对角的坐标画出 F 如果使用了 B 选项 则 F 选项指定此方块系以用来画方块的色彩来加 以填满 不能在没有 B 的情况下使用 F 如果用了 B 而没有 F 则此 方块系以目前的 FillColor 及 FillStyle FillStyle 的默认值是透明的 最常使用的绘线程序的写法如下(假设绘至名为 Pic1 的图片框) Pic1.Line (10,10)- (20,40),RGB(255,0,0) Circle 指令用来绘制和圆形相关的图形 其格式如下 object.Circle [Step] ( x, y), radius, [color, start, end, aspect] 其中参数的意义如表 6-2-4 表 6-2-4 Circle 指令的参数意义 参数 说明 object 欲绘图的对象 如窗体 图片框等等 Step 指定相对于由 object 的 CurrentX 及 CurrentY 属性所指定的目前坐 标的圆 椭圆或弧形之中心 (x, y) 指示圆 椭圆或弧形之中心点的坐标值 radius 指示圆 椭圆或弧形半径的 Single 值 Color 指示圆外缘的 RGB 色彩值(Long) 如果忽略这个值 则使用 ForeColor 的属性值 Start, end 当画弧形或部份圆或椭圆时 start 及 end 指定(径度)弧形的起点与终点 两者的范围从 -2 pi 径度到 2 pi 径度 start 的默认值为 0 径度 end 的默认值为 2 * pi 径度 Aspect 指示圆的纵横比的单精准度值 默认值为 1.0 其在任何屏幕上产生一正 (非椭圆的) 圆 第 191 页,共 606 页以下是使用 Circle 的一例(假设绘至名为 Pic1 的图片框) Pic1.Circle (10, 10), 5 3、 线条宽度 宽度所使用的属性为 DrawWidth 此设定值会决定绘 出的线条的宽度 而且此值是以像素为单位 4、 绘图形态 绘出的线条可以使用不同的方式表现出来 所使用的 属性为 DrawStyle Visual Basic 中可以设定的型态有表 6-2-5 中所列 的几种 表 6-2-5 DrawStyle 中可使用的常数及其数值 常数 值 描述 VbSolid 0 (预设) 实线 VbDash 1 破折线 VbDot 2 点线 VbDashDot 3 破折线-点线 VbDashDotDot 4 破折线-点线-点线 VbInvisible 5 透明 VbInsideSolid 6 内实线 使用上述的 Line 及 Circle 指令 再变化一下各个相关的属性值 设定 就可以在可绘图的对象上绘出希望的图形 接下来是将 7012D 的模拟读值绘制到图片框中 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其它 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属性为”cmbCOM”及 ”cmbNO” 作为通讯端口信道及侦测模块 站号的选择区 4、 安排一个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 改为”????” 按出 Font 属性 并将字号设为 18 颜色设为蓝色 读者亦可使用其它的显示设定 5、 安排一个定时器控件 按下 F4 叫出其属性窗口 变更 Interval 属性为 100 当定时器被激活后以 100 毫秒的时间间隔执行程 序 6、 安排 5 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为”10V” ”0V” ”通讯端口” ”站号”与 ”现在读值” 作 为标示各控件之用 7、 安排一个图片框控件 拉至适当的大小 按下 F4 叫出属性窗口 将其 Name 属性设成 picVoltage 而 BackColor 属性值设成淡绿 色 (或其它浅色系列) 其它属性维持默认值即可 第 192 页,共 606 页8、 安排一个 Label 控件 按下 F4 叫出属性窗口 作为讯息显示处 9、 安排三个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始侦测 结束 作为执行相对应的动作之用 10、 设计后之画面如图 6-2-12 图 6-2-12 读值绘图项目的设计画面 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 当一切选择妥当后 按下开启通讯端口的按钮 接着激活定时 器 而定时器就依 Interval 属性值一直执行其内部的程序代码 其程 序代码即会不断地对 7012D 模块送出询问模拟读值的指令 接着处 理传回的指令执行结果 显示模拟输入值在画面上 也将取得的模拟 值以图形的方式显示在画面上 处理绘图时 上升段与下降段使用不 同的颜色线段予以表示 让使用者容易了解整个的趋势 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 MaxPlotNo = 100 '设定绘图的资料点数 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False '以下设定绘图范围 (Xmin,YMax)- (XMax,YMin) picVoltage.Scale (0, 10)- (MaxPlotNo, 0) picVoltage.DrawWidth = 2 '使用画笔为二个像素宽度 第 193 页,共 606 页通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 令模块选项的 ListIndex 为 1 让选项留在第二个 项目上(因此我们预设 7012D 的模块编号是 02) 通讯端口的预 设 ListIndex 则设为 0(使用 COM1)即可 由于还未开启通讯端 口 因此 开始侦测 的按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = Fals e '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当 然得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持 到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布 式模块所使用的 Settings 参数 再开启通讯端口 一旦开启通 讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 3、 双击 开始侦测 的按钮 在其 Click 事件中输入以下程序代码 Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If 此按钮在被按下后 程序将定时器的状态转态 原来激活的话 就将其暂停 而原来暂停的话 就将其激活 透过定时器状态 的检查 也动态地改变此按钮上显示的文字 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 第 194 页,共 606 页 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序循环就不需执行 当然直接跳出去即可 这个部份的程序可以在使用者变更通讯 端口号码时 将通讯端口关闭 并将原先 Disable 的 开启通讯 端口 按钮重新 Enable 5、 双击定时器控件 在其 Timer 事件中输入以下程序代码 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " Buf = WaitRS(MSComm1, vbCr, 1000) If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If ValueStr = Val(Mid(Buf, Pos1 + 1, 7)) '分离出正号以后的数值 lblValue.Caption = Format(ValueStr, "0.000") & "V" '显示在画面上 If NowX = 0 Then picVoltage.Cls '清除图形 picVoltage.PSet (0, ValueStr) '设定起点 Else '以下判断现在的读值是否大于前一次的读值 若是 则以红色绘线 '若否 则以蓝色绘线 If ValueStr > PreValue + 0.01 Then picVoltage.Line - (NowX, ValueStr), RGB(255, 0, 0) '由上一次的位 置画至此点 Else picVoltage.Line - (NowX, ValueStr), RGB(0, 0, 255) '由上一次的位 置画至此点 End If End If PreValue = ValueStr NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 '超过范围则数值归零 传值指令格式及传回字符串的解析请参阅上一小节的说明 和上 一小节一样 传回的数值会显示在画面上 于传回字符串中取出 数值后 若是第一次传回数值 就将图片框清除 并设定起点 而接下来的传回数值就直接以上一次的终点作为起绘图即可 程 第 195 页,共 606 页序中还保留了上一次的数值作为比较之用 当本次的数值大于上 一次的数值加 0.01(由于小数点下第三位的变化较大 因此取变 化较小的第二位作为比较的标准)的话 就以红色线绘图 否则 就以蓝色线绘图 因此在变化的过程中 我们可以很清楚地以颜 色区分出量测期间电压值的起伏变化 最后 当绘图的范围超过设定的点数后 就将绘图位置归零 再 重新开始绘图 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 转动 可变电阻 电压随即改变 定时器亦会被激活 定时器的侦测结果 可能如图 6-2-13 所示 图 6-2-13 模拟读值绘图执行结果 以上的程序代码 您可以参考光盘中 范例 \ 第六章 \ 模拟 值的绘图 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的 测试 完整的程序代码列表如下 Option Explicit Dim NowX As Integer '现在的 X 轴位置 Dim MaxPlotNo As Long '最长的 X 轴范围 Dim PreValue As Single '前一个量测值 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 196 页,共 606 页Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" 第 197 页,共 606 页 End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 并预设二者的选项是第一个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% MaxPlotNo = 100 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False '以下设定绘图范围 (Xmin,YMax)- (XMax,YMin) picVoltage.Scale (0, 10)- (MaxPlotNo, 0) picVoltage.DrawWidth = 2 '使用画笔为二个像素宽度 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件引发后 就不断地执行其中的程序 ' 将模拟读值命令送出 再取得传回字符串并判断 ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, ValueStr As Single, Pos1% Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " Buf = WaitRS(MSComm1, vbCr, 1000) If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If ValueStr = Val(Mid(Buf, Pos1 + 1, 7)) '分离出正号以后的数值 lblValue.Caption = Format(ValueStr, "0.000") & "V" '显示在画面上 If NowX = 0 Then picVoltage.Cls '清除图形 picVoltage.PSet (0, ValueStr) '设定起点 Else '以下判断现在的读值是否大于前一次的读值 若是 则以红色绘线 '若否 则以蓝色绘线 第 198 页,共 606 页 If ValueStr > PreValue + 0.01 Then picVoltage.Line - (NowX, ValueStr), RGB(255, 0, 0) '由上一次的位 置画至此点 Else picVoltage.Line - (NowX, ValueStr), RGB(0, 0, 255) '由上一次的位 置画至此点 End If End If PreValue = ValueStr NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 '超过范围则数值归零 End Sub Module 中的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不 正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 6-2-6 连续扫描绘图 上一小节的范例中之绘图是以图片框作为绘图的标的 而当绘 图的点数达到时 就将原来的图形清掉 再重新开始绘图 在本小 节中将要把此一项目程序作一些小变动 使得图形的显示可以类似 第 199 页,共 606 页示波器一般不断地由右向左移动 旧资料向左边移动 而新资料则 一直出现在最右边 既然要能够将资料以类似心跳图一般地左移变化 就得先让程 序记住所有要重绘的资料 所以需要一个数组变量储存绘线的数 据 如果绘图的范围固定的话 每一笔新资料进入后 必须将最旧 的数据自数组中除去 而补以最新的资料 完成此动作后 再将所 有的资料重绘在图片框中 绘图的部份程序流程图如图 6-2-14 取得類比值 類比值存入陣 列 陣列索引值+1 索引值=1 索引值超過設 定值? 清除圖片框 取值次數已超 過設定值? 由索引值開始 重新繪圖 必須檢查索引值的 位置,以決定繪圖 的順序。 直接繪圖在圖 片框中 否 是 是 是 图 6- 2- 14 绘图部份流程图 当资料增加到所设定的显示数目时 图片框中的图形势必要清 除而作重绘的动作 而且此重绘动作是针对整个图片中的资料作操 作 在图片框中作此清除 重绘及显示的动作将会使得画面在视觉 上显得非常地闪烁而不平滑 造成的原因就在于大量资料而且显示 动作的频繁 所以采用这种作法时就得考虑是否可以忍受画面的闪 烁 显示资料量如果比较少(例如 50 个资料点)的话 采用上述的方 法是相当直觉而方便的 不过当资料量大(例如 200 个资料点)时 这种作法必须适当地作修正 才能使得图形的显示变换平顺些 为 了改正这一个缺点 显示的资料就不能直接在画面上绘制 因此观 看者不会看到画面上的绘图动作 另一个问题是 若不在画面中的 图片框中绘制 那如何显示最新的资料图形呢?流程如图 6-2- 15 所 示 此流程图和图 6- 2-4 大同小异 最大的不同在于采用了二个图 片框 第 200 页,共 606 页取得類比值 類比值存入陣 列 陣列索引值+1 索引值=1 索引值超過設 定值? 清除隱藏圖片 框 取值次數已超 過設定值? 由索引值開始 在隱藏圖片框 中重新繪圖 必須檢查索引值的 位置,以決定繪圖 的順序。 直接繪圖在顯 示圖片框中 否 是 是 是 隱藏圖片框的 圖形轉移到顯 示圖片框 轉移的速度要夠 快,才不會閃爍。 图 6- 2- 15 平滑图形显示程序流程 在图 6-2-15 中被使用的二个图片框 一个是显示的图片框 另一 个则是隐藏的图片框 当一开始取值后 若次数还未超过设定显示的 点数时 直接就绘在显示的图片框就可以了 当取值的次数已超过设 定的显示点数时 新的资料点必须出现在图形的最右边 而最旧的资 料点由图形的左边被挤掉 这时就将全部的资料在隐藏的图片框中先 绘过一次 再利用程序将隐藏图片框中的图形一次拷贝到显示的图片 框中 完成资料的显示 由于是在背景绘图 计算机的动作相当快就 完成了 我们只要着重在隐藏图片框的图形快速转移到显示的图片框 中就好了 转移的速度愈快 图形的表现就愈平滑 如何将隐藏的图形 Copy 到显示的图片框中呢?笔者在此使用的 是 API 函式群中的 BitBlt 此函式可以将二个图形直接作 Copy(不作 其它的处理) 虽然只能直接 Copy 而无其它的变化 但因使用的资源 很少 速度上也最快 — 这个合乎我们的要求 BitBlt 的函式宣告如下 Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long 表 6-2-6 BitBlt 函式中可的参数意义 参数 说明 hDestDC 目标的 DC Handle 例如此例中的显示图片框就是目标 而其 Handle 指的是属性中的 hDC x 目标图片框的左上角 X 轴坐标值 Y 目标图片框的左上角 Y 轴坐标值 第 201 页,共 606 页NWidth 目标图片的宽度 NHeight 目标图片的高度 HSrcDC 来源的 DC Handle 例如此例中的隐藏图片框就是目标 而其 Handle 指的是属性中的 hDC XSrc 来源图片框的左上角 X 轴坐标值 YSrc 来源图片框的左上角 X 轴坐标值 DwRop Copy 的方法 常数定义 API(Application Programming Interface)是由微软提供的 函式 这些函式可以用来处理 Windows 系统中各项工作 功能相 当强大 不过都很低阶 一般的使用者若使用上不小心 通常造成 意想不到的错误 MSDN 中的 API 宣告是提供 C 语言式的宣告 Visual Basic 使用者可以使用 API检视员直接叫出 Visual Basic 式 的宣告而 Copy 至模块中供 Visual Basic 程序使用 将二个图片框的相关资料给到 BitBlt 函式后 自然可以作图形 的快速转移 了解到使用的函式内容后 接下来是将 7012D 的模拟 读值绘制到图片框中的扫描程序设计 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其它 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属性为”cmbCOM”及 ”cmbNO” 作为通讯端口信道及侦测模块 站号的选择区 4、 安排一个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 改为”????” 按出 Font 属性 并将字号设为 18 颜色设为蓝色 读者亦可使用其它的显示设定 5、 安排一个定时器控件 按下 F4 叫出其属性窗口 变更 Interval 属性为 100 当定时器被激活后以 100 毫秒的时间间隔执行程 序 6、 安排 5 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为”10V” ”0V” ”通讯端口” ”站号”与 ”现在读值” 作 为标示各控件之用 7、 安排二个图片框控件 拉至适当的大小 按下 F4 叫出属性窗口 将其 BackColor 属性值设成淡绿色( 或其它浅色系列) 一个 Name 属性改为”PicShow” AutoReDraw 属性设为 False Visible 属性设成 True 作为显示的图片框 另一个 Name 属性设 为 ”PicHide” AutoReDraw 属性设为 True Visible 属性设成 第 202 页,共 606 页False 作为隐藏的图片框 二个图片框的 ScaleMode 属性均设 为 3- 像素 8、 安排一个 Label 控件 按下 F4 叫 出属性窗口 作为讯息显示处 9、 安排三个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始侦测 结束 作为执行相对应的动作之用 10、 设计后之画面如图 6-2-16 其余未标明的控件均与上一小节 相同 图 6-2-16 读值扫描绘图项目的设计画面 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 当一切选择妥当后 按下开启通讯端口的按钮 接着激活定时 器 而定时器就依 Interval 属性值一直执行其内部的程序代码 其程 序代码即会不断地对 7012D 模块送出询问模拟读值的指令 接着处 理传回的指令结果 显示模拟输入值在画面上 也将取得的模拟值以 图形的方式显示在画面上 处理绘图时 上升段与下降段使用不同的 颜色线段予以表示 让使用者容易了解整个的趋势 当询问的次数超 过画面的显示范围后 使用了另一个图形框先绘制结果 接着再将此 结果图形以 API 函式 BitBlt 转移到显示图片框中 可以造出扫描的效 果 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 ReDim PlotValue(0 To MaxPlotNo) '设定数组储存模拟读值 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) 第 203 页,共 606 页 Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False '以下设定绘图范围 (Xmin,YMax)- (XMax,YMin) PicHide.Scale (0, 10)- (MaxPlotNo, 0) PicHide.DrawWidth = 2 '使用画笔为二个像素宽度 PicShow.Scale (0, 10)- (MaxPlotNo, 0) PicShow.DrawWidth = 2 '使用画笔为二个像素宽度 笔者绘图的习惯是使用自订的坐标系统 但使用 BitBlt 函式时 由于此函式是针对位图作处理 因此在绘图的坐标上也就必须采 用像素作为度量的单位 既然如此 我们只好在程序一开始就以 像素单位先记住图片框的宽度和高度 准备在 BitBlt 函式中使 用 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 令模块选项的 ListIndex 为 1 让选项留在第二个项 目上(因此我们预设 7012D 的模块编号是 02) 通讯端口的预设 ListIndex 则设为 0(使用 COM1)即可 由于还未开启通讯端口 因此 开始侦测 的按钮先行 Disable 掉 将二个图片框的绘图范围设成一样 方便绘图度量的统一 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当然 得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布式模 第 204 页,共 606 页块所使用的 Settings 参数 再开启通讯端口 一旦开启通讯端口 后 就关闭此按钮 以避免使用者按二次 造成错误 3、 双击 开始侦测 的按钮 在其 Click 事件中输入以下程序代码 Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If 此按钮在被按下后 程序将定时器的状态转态 原来激活的话 就将其暂停 而原来暂停的话 就将其激活 透过定时器状态的 检查 也动态地改变此按钮上显示的文字 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序循环就不需执行 当 然直接跳出去即可 这个部份的程序可以在使用者变更通讯端口 号码时 将通讯端口关闭 并将原先 Disable 的开启通讯端口按 钮重新 Enable 5、 双击定时器控件 在其 Timer 事件中输入以下程序代码 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " Buf = WaitRS(MSComm1, vbCr, 1000) If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If ValueStr = Val(Mid(Buf, Pos1 + 1, 7)) '分离出正号以后的数值 lblValue.Caption = Format(ValueStr, "0.000") & "V" '显示在画面上 PlotValue(NowX) = ValueStr '将值记录到数组中 If Not fIsOver Then '绘图取样数是否超过 第 205 页,共 606 页 If NowX = 0 Then PicShow.Cls '清除图形 PicShow.PSet (0, ValueStr), RGB(255, 0, 0) '设定起点 Else '以下判断现在的读值是否大于前一次的读值 若是 则以红色绘线 '若否 则以蓝色绘线 If ValueStr > PreValue + 0.01 Then PicShow.Line - (NowX, ValueStr), RGB(255, 0, 0) '由上一次的 位置画至此点 Else PicShow.Line - (NowX, ValueStr), RGB(0, 0, 255) '由上一次的 位置画至此点 End If End If Else Plot NowX + 1 '将图绘在另一个隐藏的图片框 '绘好的图形转移至显示的图片框 '使用最简洁的 API 指令 可以清除图形变化时的闪烁现象 '而达到图形平滑移动的效果 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.hDC, 0, 0, &HCC0020) End If PreValue = ValueStr NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then '绘图取样数是否超过 NowX = 0 fIsOver = True '准备激活隐藏图片框的绘制 End If 传值指令格式及传回字符串的解析请参阅上一小节的说明 未超 过显示点数部份的程序代码也和上一小节相同 也请参考上一小 节 在超过显示点数时 程序会将绘图的动作在 Plot 子程序中 完成 完成后再使用 BitBlt 作图形的转移 Plot 子程序部份的程 序代码如下 PicHide.Cls '清除图形 j = StartX '第一个值 If j > MaxPlotNo Then j = 0 PicHide.PSet (0, PlotValue(j)), RGB(255, 0, 0) '设定起点 For i = 1 To MaxPlotNo If j = 0 Then If PlotValue(j) > PlotValue(MaxPlotNo) + 0.01 Then PicHide.Line - (i, PlotValue(j)), RGB(255, 0, 0) '由上一次的 位置画至此点 Else PicHide.Line - (i, PlotValue(j)), RGB(0, 0, 255) '由上一次 的位置画至此点 End If Else If PlotValue(j) > PlotValue(j - 1) + 0.01 Then PicHide.Line - (i, PlotValue(j)), RGB(255, 0, 0) '由上一次 的位置画至此点 Else PicHide.Line - (i, PlotValue(j)), RGB(0, 0, 255) '由上一次 的位置画至此点 End If 第 206 页,共 606 页 End If j = j + 1 If j > MaxPlotNo Then j = 0 '已到底部了 回到最前面 Next i Plot 子程序中就是一个完整的绘图程序 绘图资料数组的使用也 有一些技巧 由于使用同一个数组作存放 新的数据点就会把旧 的资料点盖掉 所以绘图时也必须知道那一个点是最新的 那一 个是最旧的(透过由传入参数 StartX 得知) 如果是从中间开始绘 图 当绘到数组的最后时 其实还有数据在数组的最前端还没有 绘到 因此才会利用 If j>MaxPlotNo Then j=0 的片断判断是否 已到底部 若已到底部 自然要反转到数组的最前面再绘图 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 转动 可变电阻 电压随即改变 定时器亦会被激活 定时器的侦测结果 可能如图 6-2-17 所示 图 6-2-?? 模拟读值绘图执行结果 读者可以发现 使用 BitBlt 作图形的转移时 图形的平滑度是相当 不错的 试着改变在 Fom_Load 事 件中的 MaxPlotNo 设定吧!看看效 果如何 嗯 !笔者想 您应该已作出了一个示波器的雏型了 以上的程序代码 您可以参考光盘中 范例 \ 第六章 \ 模拟 值的绘图-Sweep 档案夹中的项目 双击 Ex.vbp 项目档 即可进行 以上的测试 完整的程序代码列表如下 第 207 页,共 606 页 Option Explicit Dim NowX As Long '现在的 X 轴位置 Dim MaxPlotNo As Long '最长的 X 轴范围 Dim PreValue As Single '前一个量测值 Dim PlotValue() As Single '读值数组 Dim fIsOver As Boolean '是否已超过绘图的极限位置 Dim P1Width&, P1Height& ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() ' 判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub 第 208 页,共 606 页 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输 入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 并预设二者的选项是第一个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 30 ReDim PlotValue(0 To MaxPlotNo) '设定数组储存模拟读值 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False '以下设定绘图范围 (Xmin,YMax)- (XMax,YMin) PicHide.Scale (0, 10)- (MaxPlotNo, 0) PicHide.DrawWidth = 2 '使用画笔为二个像素宽度 PicShow.Scale (0, 10)- (MaxPlotNo, 0) PicShow.DrawWidth = 2 '使用画笔为二个像素宽度 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件引发后 就不断地执行其中的程序 ' 将模拟读值命令送出 再取得传回字符串并判断 ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, ValueStr As Single, Pos1% Dim i&, j& Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf 第 209 页,共 606 页 End If MSComm1.Output = "#" & Buf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " Buf = WaitRS(MSComm1, vbCr, 1000) If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If ValueStr = Val(Mid(Buf, Pos1 + 1, 7)) '分离出正号以后的数值 lblValue.Caption = Format(ValueStr, "0.000") & "V" '显示在画面上 PlotValue(NowX) = ValueStr '将值记录到数组中 If Not fIsOver Then '绘图取样数是否超过 If NowX = 0 Then PicShow.Cls '清除图形 PicShow.PSet (0, ValueStr), RGB(255, 0, 0) '设定起点 Else '以下判断现在的读值是否大于前一次的读值 若是 则以红色绘线 '若否 则以蓝色绘线 If ValueStr > PreValue + 0.01 Then PicShow.Line -( NowX, ValueStr), RGB(255, 0, 0) '由上一次的 位置画至此点 Else PicShow.Line - (NowX, ValueStr), RGB(0, 0, 255) '由上一次的 位置画至此点 End If End If Else Plot NowX + 1 '将图绘在另一个隐藏的图片框 '绘好的图形转移至显示的图片框 '使用最简洁的 API 指令 可以清除图形变化时的闪烁现象 '而达到图形平滑移动的效果 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.hDC, 0, 0, &HCC0020) End If PreValue = ValueStr NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then '绘图取样数是否超过 NowX = 0 fIsOver = True '准备激活隐藏图片框的绘制 End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 隐藏图片框的绘图子程序 ' 若超过设定的资料点数时 先把图绘在隐藏的图片框中 ' 再将此图片框的图 Copy 到显示的图片框中 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Plot(StartX&) Dim i&, j& PicHide.Cls '清除图形 j = StartX '第一个值 If j > MaxPlotNo Then j = 0 PicHide.PSet (0, PlotValue(j)), RGB(255, 0, 0) '设定起点 第 210 页,共 606 页 For i = 1 To MaxPlotNo If j = 0 Then If PlotValue(j) > PlotValue(MaxPlotNo) + 0.01 Then PicHide.Line - (i, PlotValue(j)), RGB(255, 0, 0) '由上一次的 位置画至此点 Else PicHide.Line - (i, PlotValue(j)), RGB(0, 0, 255) '由上一次 的位置画至此点 End If Else If PlotValue(j) > PlotValue(j - 1) + 0.01 Then PicHide.Line - (i, PlotValue(j)), RGB(255, 0, 0) '由上一次 的位置画至此点 Else PicHide.Line - (i, PlotValue(j)), RGB(0, 0, 255) '由上一次 的位置画至此点 End If End If j = j + 1 If j > MaxPlotNo Then j = 0 Next i End Sub Module 中的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long,ByVal ySrc As Long, ByVal dwRop As Long) As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If 第 211 页,共 606 页End Function 6-3 数字输出及数字输出 7012D 中含有二个通道的数字输出及一个通道的数字输出 这 二个数字输出具备有二种不同的功能用途 本节将就此部份予以说 明 6-3-1 数字输出原理 7012D 模块中有二个通道的数字输出 在模块标示了 DO0/Lo 及 DO1/HI 我们可以利用程控这二个输出通道的 High/Low 状态 首先 在模块使用手册中载明数字输出的型式是开极式(Open Collector)的输出 原理示意图如图 6- 3- 1 所示 DO 1 2 3 4 +24V 5 R5 2K D5 LED DO Low High R5 2K +24V 電流走向 (A) (B) (C) 图 6- 3- 1 数字输出原理示意图 首先由图 6- 3-1 的 (A)部份开始说明 手册中的电路示意图就如 (A)部份所示 它是一个晶体管 笔者将其编号为 1 2 3 当编号 1 的基极有电流输入(只要 1 毫安即可) 编号 1 的基极和编号 3 的 射极间的顺向偏压也达到 0.7V 时 编号 2 的集极和编号 3 的射极 就会导通 集极和基极的电流将会流向射极 而于射极接地 故电 流在导通状态下将会流向 GND 再来是(B)部份 一个完整电路中 电流会从高电位一端流向低 电位的一端 使用上最方便的话 就是将高电位接模块的+24V 而 第 212 页,共 606 页低电位就接 GND 而电路形成后 使用一个 2K 欧姆的电阻当成限 流电阻 发光二极管将会因线路上的电流而发亮 将 (A)部份和(B)部份作一个整合 就形成了(C)部份 将 (B)部 份中的电阻和编号 5 的接地间安排(A)部份 就完成了数字输出的 实验电路 由 (C)部份的电路中 当晶体管的基极讯号输入时(也就 是当模块控制数字输出为 ON 时 ) 线路上的电流就会如图中所示地 流动 自然使得发光二极管作动 我们也因此知道数字输出的状态 是否成立 了解此原理后 我们可据以作出实验电路 若应用到其 它的场合 也可以知道应如何接线才能符合现场及模块二边的需 求 (晶体管在第五章 7060D 模块的数字输入中说明过 读者可参 考该章!) 6-3-2 数字输入原理 和数字输出对应的就是数字输入 在 7012D 模块中所提供的数 字输入脚位接点只有一个 其原理如图 6- 3- 2 所示 DI 1 (A) (B) +V R +V 2 3 R5D5 LED (C) DI +V R Switch 图 6- 3- 2 数字输入原理示意图 一样分别三个部份作详细的说明 在 (A)部份是模块内部数字输 入的电路示意图 由 图中可以看出在其内部是接往电源 并串有一 个电阻 然以直接接出一个脚位 即名为 DI 同样的 一个完整回路可以如(B)部份所示 电流由高电位流向 低电位 其间可置放电阻及发光二极管作为标示之用 将 (A) (B) 二部份合在一起 就形成了(C)部份 笔者将其设计为使用一个摇 头开关作切换 让电流从+V 流向 GND 或是断开 藉以控制数字输 第 213 页,共 606 页入的状态是 ON 或是 OFF 当此状态改变时 会连带地改变模块中 的状态 当使用程序向模块询问时 模块即会传回此状态 由实作发现 当使用一个 LED 在实验电路中时 此 LED 产生的阻抗会影响整个电路 导致模块的数字输入状态无法改变 故删除 LED 的使用 6-3-3 数字输出与输入的控制 有了前面二小节的观念后 本小节将进行数字输出入的实验项 目设计 到此为止 介绍了模拟输入的实验电路和数字输出入的实 验电路 读者可以试著作一个整合的实验板 线路则如各节所述 笔者亦以实验板作了一个简单的实验电路如图 6- 3- 3 所示 图 6- 3- 3 7012D 模块的实验线路图 此实验板就是利用之前的原理推论后的电路图所制作出来的 主要是方便我们作实验 免除在面包板上插来插去的麻烦 数字输出指令 7012D 模块的数字输出指令格式为@AADO(Data) 其中的 AA 指的是模块地址 可以由 00~FF DO 固定 而 (Data)则 有如表 6- 3-1 所示的几种情形 给定不同的字符串 就会产生不同 的数字输出结果 表 6- 3- 1 (Data)所代表的几种数字输出组合方式 输出情形 (Data) 字符 串 DO0 DO1 00 OFF OFF 第 214 页,共 606 页01 ON OFF 02 OFF ON 03 ON ON 数字输入指令 7012D 模块的数字输入指令格式为@AADI 其中的 AA 指的是模块地址 此指令正确执行时的传回字符串格式 为 !AASOOII S 表示警戒状态(详见下一小节的说明) 0 表警戒不 激活 1 表瞬间警戒激活 2 表闩锁警戒激活 OO 表示数字输出状 态 如表 6-3- 1 所示 II 表示数字输入状态 00 表输入低电平 01 表输入高电平 因此就数字输入而言 我们只需要解析传回字符串 的第 7 8 二字位即可(或是只解析第 8 个字符) 接下来就是项目的设计 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其也 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”cmbCOM”及”cmbNO” 作为通讯端口信道及侦测模块站号 的选择区 4、 安排四个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgRed” ”imgDisable” ”imgON” ”imgOFF” 而在其各 别的 Picture 属性中选择项目目录下的 Red.jpg Disable.jpg ON.jpg OFF.jpg, 作为显示输出及输入状态之用 另外也要将其 Visible 属性设为 False 令其执行时不显示 5、 安排三个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgIN” 另外二个设为”imgOut” 并设成数组(Index 由 0~1) 调整适当大小并排列之 此三个 Image 控件的 Visible 属 性要设成 True 6、 安排 6 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性分 别改为”通讯端口” ” 站号” ” 数字输入状态” ”数字输出控 制” ”DO0” ”DO1” 作为各控件的标示之用 7、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性 窗口 其 Name 属性改为”lblMsg” BorderStyle 选择”1-单线固定” BackColor 选择淡黄色 作为显示系统讯息之用 8、 安排一个定时器 按下 F4 叫出属性窗口 修改其 Interval 属性 为 100 定时器将以 100 毫秒的周期执行内部的程序代码 9、 安排二个按钮控件 其 Caption 属性分别输入 开启通讯端口 结束 作为执行相对应的动作之用 10、 设计后之画面如图 6-3-4 所示 第 215 页,共 606 页 图 6-3-4 数字输出控制的设计画面 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 当一切选择妥当后 按下 开启通讯端口 的按钮 接着激活 定时器 而定时器就依 Interval 属性值一直执行其内部的程序代码 其程序代码即是不断地对 7012D 模块送出询问数字输出入状态的指 令 接着处理传回的指令结果 显示输出入状态在画面上 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 For i = 0 To 1 imgOut(i).Picture = imgOFF.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 imgIN.Picture = imgDisable.Picture 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 此模块编号通常会被我们设成 02 因此在程序中 直接让它出现模块的号码为 2 由于还未开启通讯端口 因此 开 始侦测 的按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 第 216 页,共 606 页 '检查是否开关通讯端口 If cmdOpenCOM.Caption = "关闭通讯端口" Then MSComm1.PortOpen = False cmdOpenCOM.Caption = "开启通讯端口" Exit Sub End If '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Caption = "关闭通讯端口" lblMsg.Caption = "可按下 ON- OFF 开关 执行 DO 的控制工作 " '以下主要是关闭警戒功能 避免干扰数字通道功能 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "@" & Buf & "DA" & vbCr '关闭警戒 RetBuf = WaitRS(MSComm1, vbCr, 1000) '传回值 Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当然 得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布式模 块所使用的 Settings 参数 再开启通讯端口 一旦开启通讯端口 后 就关闭此按钮 以避免使用者按二次 造成错误 由于警戒功能的开启将会影响到数字输出的操作 因此在此程序 的后段部份是关闭警戒功能(指令码是@AADA 详见下一小节的 说明) 避免干扰的出现 3、 双击 imgOut 的控件(任何一个均可) 本项目的重点就在此部份 在其 Click 事件中输入以下程序代码 '将记录转态 并改变 ON- OFF 按钮的显示 DOStatus(Index + 1) = Not DOStatus(Index + 1) If DOStatus(Index + 1) Then imgOut(Index).Picture = imgON.Picture Else imgOut(Index).Picture = imgOFF.Picture End If 第 217 页,共 606 页程序使用 DOStatus 数组变量记录现在的输出状态 而每一次使 用者按下控件时 就将状态作改变(原来是 ON 的 就将它变成 OFF 原来是 OFF 的 就将它变成 ON) 并且将其中的 Picture 属性作变更 以符合现在的 ON/OFF 状态 在此并末作直接的输 出指令句柄 笔者将此部份的程序和数字输入的侦测放在一起 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序就不需执行 当然直 接跳出去即可 这个部份的程序可以在使用者变更通讯端口号码 时 将通讯端口关闭 并将原先 Disable 的开启通讯端口按钮重 新 Enable 5、 双击定时器控件 在其 Timer 事件中输入以下的程序代码 '若通讯端口尚未开启 不可送出数据 If cmdOpenCOM.Caption = "开启通讯端口" Then Exit Sub COMBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(COMBuf) = 1 Then COMBuf = "0" & COMBuf End If MSComm1.Output = "@" & COMBuf & "DI" & Chr(13) '组合完整的命 令字符串 lblMsg.Caption = "侦测" & COMBuf & "数字输入/ 事件中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " Else Buf = Mid(RetBuf, 8, 1) '取出代表数字输入状态的字符 If Buf = "0" Then imgIN.Picture = imgRed.Picture Else imgIN.Picture = imgDisable.Picture End If End If '以下是数字输出的部份 '计算输出值 DOut = 0 For i = 1 To 2 If DOStatus(i) Then DOut = DOut + 2 ^ (i - 1) Next i MSComm1.Output = "@" & COMBuf & "DO" & Format(DOut, "00") & Chr(13) '组合完整的命令字符串 Buf = WaitRS(MSComm1, vbCr, 1000) '此行在此项目中可省略 If Buf = "" Then lblMsg.Caption = "指令的执行结果错误 " Else 第 218 页,共 606 页 lblMsg.Caption = "指令成功 " End If 数字输入的功能如果要不断地被执行的话 最方便的方法就是采 用定时器作周期性的程序执行 程序可以看出分成二个部份进行 首先是数字输入的部份 利用 数字输入指令将传回模块状态传回后 解析第 8 个字符 便可得 而模块的数字输入状态 再来是数字输出的部份 使用者可以从 画面上改变 ON-OFF 的开关状态 而状态会被记录到数组中 在 定时器中的第二部份就是将此数组状态作输出 定时器就如此地周而复始地作数字输入的侦测及数字输出的控 制 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 的按钮 定时器随即被激活 定时器的侦测结果可能如图 6-3-5 所示 图 6-3-5 数字输出控制的执行结果 图 6-3-5 的执行即是命令第 0 个数字输出作动 另外也侦测到数 字输入呈现 ON 的状态 以上的程序代码 您可以参考光盘中 范例 \ 第六章 \ 数 字输出控制与数字输入 档案夹中的项目 双击 Ex.vbp 项目档 即 可进行以上的测试 完整的程序代码列表如下 Option Explicit 第 219 页,共 606 页' 宣告记录四个数字输出状态的数组变量 Dim DOStatus(1 To 4) As Boolean ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub If Not MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯 端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() Dim Buf$, RetBuf$ '检查是否开关通讯端口 If cmdOpenCOM.Caption = "关闭通讯端口" Then MSComm1.PortOpen = False cmdOpenCOM.Caption = "开启通讯端口" Exit Sub End If '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Caption = "关闭通讯端口" lblMsg.Caption = "可按下 ON- OFF 开关 执行 DO 的控制工作 " '以下主要是关闭警戒功能 避免干扰数字通道功能 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "@" & Buf & "DA" & vbCr '关闭警戒 RetBuf = WaitRS(MSComm1, vbCr, 1000) '传回值 第 220 页,共 606 页 Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 并预设二者的选项是第一个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% For i = 0 To 1 imgOut(i).Picture = imgOFF.Picture Next i cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 imgIN.Picture = imgDisable.Picture End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当代表数字输出的 Image 控件被选中时触发此事件 ' 将被选中的 DO 作转态 ' 转态后将数字输出的决定送至模块执行 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub imgOut_Click(Index As Integer) '将记录转态 并改变 ON- OFF 按钮的显示 DOStatus(Index + 1) = Not DOStatus(Index + 1) If DOStatus(Index + 1) Then imgOut(Index).Picture = imgON.Picture Else imgOut(Index).Picture = imgOFF.Picture End If End Sub Private Sub Timer1_Timer() Dim COMBuf$, Buf$, DOut%, i%, RetBuf$ '若通讯端口尚未开启 不可送出数据 If cmdOpenCOM.Caption = "开启通讯端口" Then Exit Sub COMBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(COMBuf) = 1 Then COMBuf = "0" & COMBuf End If MSComm1.Output = "@" & COMBuf & "DI" & Chr(13) '组合完整的命 令字符串 lblMsg.Caption = "侦测" & COMBuf & "数字输入/ 事件中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " 第 221 页,共 606 页 Else Buf = Mid(RetBuf, 8, 1) '取出代表数字输入状态的字符 If Buf = "0" Then imgIN.Picture = imgRed.Picture Else imgIN.Picture = imgDisable.Picture End If End If '以下是数字输出的部份 '计算输出值 DOut = 0 For i = 1 To 2 If DOStatus(i) Then DOut = DOut + 2 ^ (i - 1) Next i MSComm1.Output = "@" & COMBuf & "DO" & Format(DOut, "00") & Chr(13) '组合完整的命令字符串 Buf = WaitRS(MSComm1, vbCr, 1000) '此行在此项目中可省略 If Buf = "" Then lblMsg.Caption = "指令的执行结果错误 " Else lblMsg.Caption = "指令成功 " End If End Sub 模块内的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 6-3-4 警戒输出 第 222 页,共 606 页如上一小节项目所述 数字输出的功能可以由程序予以控制 但 7012D 模块的数字输出还有一个相当重要的功能 就是提供警戒 输出 而警戒输出就是使用数字输出的二个通道 警戒主要用于让 模块本身具备判断状态 并随时反应状态及控制的能力 7012D 模块用于量测模拟的输入值 所量测的电压值可能随着 时间而变化 分布式的架构中必须使用主控计算机对所有的分散模 块作命令的输出和状态的读入 当主控计算机正在处理其它的事务 或当机时 若此时的模拟值发生了变化 而此变化必须马上被反应 出来 主控计算机就无法作出立即的反应 也有可能因此错失一个 时间点 因此本模块的数字输出通道就在此时派上用场 我们可以 利 用此二个通道设定电压的变化量必须落在一定的范围内 若超出 所设定的范围则作输出 可藉此输出而达到通知 转控制其它设备 的目的 本模块针对模拟值的比对和警戒输出有以下的二个情形 高值 警戒 低值警戒 所谓的高值警戒指的是设定一个电压值 当模拟 输入的电压值超过此值时 第 1 个数字输出就会作动(称为 HI Alarm) 低值警戒指的是设定一个电压值 当模拟输入的电压值低 过此值时 第 0 个数字输出就会作动(称为 LO Alarm) 与警戒相关 的指令则如表 6- 3- 2 所示 表 6- 3- 2 警戒相关指令及传回值 功能 命令格式 命令范例 传回字符串 传回范列 激活警戒 @AAEAT @02EAM !AA !02 取消警戒 @AADA @02DA !AA !02 设定高值 警戒值 @AAHI(Data) @02HI+07.000 !AA !02 设定低值 警戒值 @AALO(Data) @02LO+03.000 !AA !02 清除警戒 闩锁 @AACA @02CA !AA !02 读取高值 警戒值 @AARH @02RH !AA(Data) !02+07.000 读取低值 警戒值 @AARL @02RL !AA(Data) !02+03.000 读取警戒 状态 @AADI @02DI !AASOOII !0210001 表 6- 3-2 中的 AA 字符串均表示模块的地址 激活警戒范 例 ”@02EAM”字符串中的 M 表示警戒发生时是采用 Momentary 型 式 另外一种相对的是 Latch 型式 所谓的 Momentary 指的是当电 压值超过设定值时 DO1 会作激活输出 而若此后的电压值又低过 高值警戒设定值 则 DO1 的输出动作将被取消 也就是电压超过 第 223 页,共 606 页时激活警戒 安全时警戒失效 另一种 Latch 型式则不同 只要有 一次的输入电压值超过警戒值 DO1 就会激活 而且尽管之后的电 压值恢复至正常范围 此警戒输出也不会取消 而是一直保持在激 活状态 唯一的取消方法就是主控计算机利用程序传送取消指令要 求模块取消此一激活状态 取消指令格式是@AACA 这二种不同 的警戒方式适合在高值警戒及低值警戒 若欲采用 Latch 的方式作 警戒 则原来的”@02EAM”修改为” @02EAL”即可 设定警戒值及读取警戒值中的(Data)的格式为正负号一个字符 小 数点前二位 小数点后三位 连正负号一共 7 位 详如表 6- 2- 1 所 列 请读者参考该节之说明 读取警戒状态的传回字符串中 第 5 和第 6 个 字符表示输出入状态 同时也表示了警戒输出的状态 如表 6- 3- 1 所示 除了特别说明的代表用字符外 一般的指令字符均必须使用 大写字母表示 绝不能使用小写字符 否则模块将不执行此指令 例如@02CA 就不可写成@02ca 接下来就是项目的设计 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其也 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”cmbCOM”及”cmbNO” 作为通讯端口信道及侦测模块站号 的选择区 4、 安排三个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgRed” ”imgDisable” ”imgGreen” 而在其各别的 Picture 属性中选择项目目录下的 Red.jpg Gray.jpg Green.jpg 作为 显示输出及输入状态之用 另外也要将其 Visible 属性设为 False 令其执行时不显示 5、 安排二个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgLO” 另外一个设为”imgHI” 此二个 Image 控件的 Visible 属性要设成 True 作为高低警戒显示用灯号 6、 安排二个文字框 按下 F4 叫出属性窗口 予其 Name 属性中分 别输入”txtHighAlarm” ”txtLowAlarm” 作为高值警戒及低值警 戒的设定输入区 第 224 页,共 606 页7、 安排二个 ScrollBar(滚动条)控件 按下 F4 叫出属性窗口 予其 Name 属性中分别输入”hsHigh” ”hsLow” Max 属性设为 1000 Min 属性设为 0 LargeChange 属性设成 50 SmallChange 属性设 成 10 此二个控件分别对应到高低值警戒值设定文字框 可利 用此控件值的改变而达到改变文字框中数值的目的 8、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性 窗口 其 Name 属性改为”lblMsg” BorderStyle 选择”1-单线固定” BackColor 选择淡黄色 作为显示系统讯息之用 9、 安排一个定时器 按下 F4 叫出属性窗口 修改其 Interval 属性 为 100 Enabled 属性设为 False 定时器被激活后 将以 100 毫 秒的周期执行内部的程序代码 10、 安排二个图片框控件 拉至适当的大小 按下 F4 叫出属性窗 口 将其 BackColor 属性值设成淡绿色(或其它浅色系列) 一个 Name 属性改为”PicShow” AutoReDraw 属性设为 False Visible 属性设成 True 作为显示的图片框 另一个 Name 属性设 为”PicHide” AutoReDraw 属性设为 True Visible 属性设成 False 作为隐藏的图片框 二个图片框的ScaleMode属性均设为3-像素 11、 安排一个 Check Box 控件 按下 F4 叫出属性窗口 将其 Name 属性改为 chkAlarm 并将 Caption 属性设为”激活警戒” 作为是 否激活警戒的选项 12、 安排 8 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属 性分别改为”通讯端口” ”站号” ”高值警戒设定” ”低值警戒设 定” ”0V” ”10V” ”0” ”20” 作为各控件的标示及图片框的 X Y 轴范围指示之用 13、 安排三个按钮控件 其 Caption 属性分别输入 开启通讯端 口 开始侦测 结束 作为执行相对应的动作之用 14、 设计后之画面如图 6-3-6 所示 第 225 页,共 606 页图 6-3-6 数字输出控制的设计画面 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 另外也让使用者可以设定高值警戒及低值警戒 并以一个 CheckBox 决定是否激活警戒 当一切选择及设定妥当后 按下开启 通讯端口的按钮及开始侦测的按钮 激活定时器后 其程序代码会不 断地对 7012D 模块送出询问模拟输入值的指令 接着处理传回的指 令结果 显示输入的模拟值到画面上 此项目的目的是设定警戒值 到模块上 令模块读取模拟输入值 将此电压值以数值及绘图的方 式显示到画面上 使模块侦测高低电压值的状态 并依状况作数字 输出 以下接着就是程序的安排 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 100 lblEnd.Caption = CStr(MaxPlotNo) ReDim PlotValue(0 To MaxPlotNo) cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False '以下设定绘图范围 (Xmin,YMax)- (XMax,YMin) PicHide.Scale (0, 10)- (MaxPlotNo, 0) PicHide.DrawWidth = 2 '使用画笔为二个像素宽度 PicShow.Scale (0, 10)- (MaxPlotNo, 0) PicShow.DrawWidth = 2 '使用画笔为二个像素宽度 一开始就以像素单位先记住图片框的宽度和高度 准备在 BitBlt 函式中使用 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因 此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 此模块编号通常会被我们设成 02 因此在程序中直接让它 出现模块的号码为 2 由于还未开启通讯端口 因此 开始侦测 的按钮先行 Disable 掉 程序中也设定了二个图片框所使用的坐标范围 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then 第 226 页,共 606 页 MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Dis able cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当然 得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布式模块所 使用的 Settings 参数 再开启通讯端口 一旦开启通讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 3、 双击 hsHigh 滚动条控件 在其 Change 事件中输入以下程序代码 txtHighAlarm.Text = hsHigh.Value / 100 将此设定值和文字框产生关联 此值除以 100 后作为文字框的值 同样的 也在 hsLow 轴卷控件的 Change 事件中输入以下的程序代 码 txtLowAlarm.Text = hsLow.Value / 100 同样将此设定值和文字框产生关联 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序就不需执行 当然直 接跳出去即可 这个部份的程序可以在使用者变更通讯端口号码 时 将通讯端口关闭 并将原先 Disable 的开启通讯端口按钮重新 Enable 5、 双击定时器控件 在其 Timer 事件中输入以下的程序代码 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then 第 227 页,共 606 页 Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, RetBuf, ">") If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If ValueStr = Val(Mid(RetBuf, Pos1 + 1, 7)) '分离出正号以后的数值 lblValue.Caption = Format(ValueStr, "0.000") & "V" '显示在画面上 PlotValue(NowX) = ValueStr '将值记录到数组中 If Not fIsOver Then If NowX = 0 Then PicShow.Cls '清除图形 PicShow.DrawWidth = 1 '使用画笔为一个像素宽度画格子线 PicShow.DrawStyle = vbDot '绘 X 轴的格子线, 均分 10 等分 For i = 0 To 9 x = i * MaxPlotNo / 10 PicShow.Line (x, 0)- (x, 10) Next i '绘 Y 轴的格子线, 均分 5 等分 For i = 0 To 4 y = i * 10 / 5 PicShow.Line (0, y)- (MaxPlotNo, y) Next i PicShow.DrawWidth = 2 '绘线宽度改为 2 PicShow.DrawStyle = vbSolid '以实线绘图 PicShow.PSet (0, ValueStr), RGB(255, 0, 0) '设定起点 Else '以下判断现在的读值是否大于前一次的读值 若是 则以红色绘线 '若否 则以蓝色绘线 If ValueStr > PreValue + 0.01 Then PicShow.Line - (NowX, ValueStr), RGB(255, 0, 0) '由上一次的 位置画至此点 Else PicShow.Line - (NowX, ValueStr), RGB(0, 0, 255) '由上一次的 位置画至此点 End If End If Else lblStart.Caption = CStr(Val(lblStart.Caption) + 1) lblEnd.Caption = CStr(Val(lblEnd.Caption) + 1) Plot NowX + 1 '将图绘在另一个隐藏的图片框 '绘好的图形转移至显示的图片框 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.hDC, 0, 0, &HCC0020) End If PreValue = Va lueStr NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 第 228 页,共 606 页 fIsOver = True End If '检查警戒状态 MSComm1.Output = "@" & Buf & "DI" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 1000) If Mid(RetBuf, 4, 1) = "1" Then '若激活警戒才作检查 i = Val(Mid(RetBuf, 5, 2)) If (i And 1) = 1 Then '低值警戒是否激活 imgLO.Picture = imgGreen.Picture Else imgLO.Picture = imgDisable.Picture End If If (i And 2) = 2 Then '高值警戒是否激活 imgHigh.Picture = imgRed.Picture Else imgHigh.Picture = imgDisable.Picture End If End If 大部份的程序和 6-2-4 节一样 在本程序中多了绘出格子线的步骤 这样子的格子线可以方便使用者看到图形上的电压值分布 另外就是程序末端的警戒状态的读取 所采用的指令是@AADI 此 指令可传回数字输出入状态 也可传回警戒状态 由表 6-3-1 可知 二种警戒状态分别可用 AND 运算对第 0 个和第 1 个位作运算而得 到状态 得到状态后再显示出灯号 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 定时 器随即被激活 定时器的所取得的模拟输入结果及警戒状态值可能 如图 6-3-7 所示 图 6-3-7 数字输出控制的执行结果 由图 6-3-7 呈现出以下的几种情形 第 229 页,共 606 页1、 高值警戒设定为 7V 而低值警戒设定为 3V 因此 当输入电 压超过高值警戒值时 现在读值下方的红号灯号将会亮起 同样 的 当输电压值低过低值警戒时 绿色灯号将会亮起 2、 在绘图区改变的过程中 X 轴的最高及最低范围也会发生变化 如此比较能了解到已进行了多少的数值读取 3、 当模拟数值低过 3V 时 右方的绿色灯会亮起 而高过 7V 时 红色灯会亮起 以上的程序代码 您可以参考光盘中 范例 \ 第六章 \ 模 拟值与警戒输出 档案夹中的项目 双击 Ex.vbp 项目档 即可进行 以上的测试 完整的程序代码列表如下 Option Explicit Dim NowX As Long '现在的 X 轴位置 Dim MaxPlotNo As Long '最长的 X 轴范围 Dim PreValue As Single '前一个量测值 Dim PlotValue() As Single '读值数组 Dim fIsOver As Boolean '是否已超过绘图的极限位置 Dim P1Width&, P1Height& ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 此程序中会先关闭警戒的部份 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() Dim Buf$, RetBuf$ Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If If MSComm1.PortOpen Then MSComm1.Output = "@" & Buf & "DA" & vbCr 第 230 页,共 606 页 RetBuf = WaitRS(MSComm1, vbCr, 1000) End If End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Dim Buf$, RetBuf$ Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If If chkAlarm.Value = 1 Then MSComm1.Output = "@" & Buf & "HI+" & Format(txtHighAlarm.Text, "00.000") & vbCr RetBuf = WaitRS(MSComm1, vbCr, 1000) MSComm1.Output = "@" & Buf & "LO+" & Format(txtLowAlarm.Text, "00.000") & vbCr RetBuf = WaitRS(MSComm1, vbCr, 1000) MSComm1.Output = "@" & Buf & "EAM" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 1000) Else MSComm1.Output = "@" & Buf & "DA" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 1000) End If Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" 第 231 页,共 606 页 Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 并预设二者的选项是第一个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 100 lblEnd.Caption = CStr(MaxPlotNo) ReDim PlotValue(0 To MaxPlotNo) cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False '以下设定绘图范围 (Xmin,YMax)- (XMax,YMin) PicHide.Scale (0, 10)- (MaxPlotNo, 0) PicHide.DrawWidth = 2 '使用画笔为二个像素宽度 PicShow.Scale (0, 10)- (MaxPlotNo, 0) PicShow.DrawWidth = 2 '使用画笔为二个像素宽度 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当使用者按下上下钮时 触发此事件 ' 直接将 hsHigh 的 Value 属性值指定给高值警戒文字框 ' 由于 Scroll 控件的 Value 值是整数 为了精确度 ' 我们以 100 作为除数 如此即可将 0~1000 的 Value 值变成 '7012D 中量测的 0~10V 的数值 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub hsHigh_Change() txtHighAlarm.Text = hsHigh.Value / 100 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当使用者按下上下钮时 触发此事件 ' 直接将 hsLow 的 Value 属性值指定给低值警戒文字框 ' 由于 Scroll 控件的 Value 值是整数 为了精确度 ' 我们以 100 作为除数 如此即可将 0~1000 的 Value 值变成 '7012D 中量测的 0~10V 的数值 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub hsLow_Change() txtLowAlarm.Text = hsLow.Value / 100 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 232 页,共 606 页' 定时器的 Timer 事件引发后 就不断地执行其中的程序 ' 将模拟读值命令送出 再取得传回字符串并判断 ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, ValueStr As Single, Pos1% Dim i&, j&, x!, y!, RetBuf$ Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Chr(13) '组 合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, RetBuf, ">") If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If ValueStr = Val(Mid(RetBuf, Pos1 + 1, 7)) '分离出正号以后的数值 lblValue.Caption = Format(ValueStr, "0.000") & "V" '显示在画面上 PlotValue(NowX) = ValueStr '将值记录到数组中 If Not fIsOver Then If NowX = 0 Then PicShow.Cls '清除图形 PicShow.DrawWidth = 1 '使用画笔为一个像素宽度画格子线 PicShow.DrawStyle = vbDot '绘 X 轴的格子线, 均分 10 等分 For i = 0 To 9 x = i * MaxPlotNo / 10 PicShow.Line (x, 0)- (x, 10) Next i '绘 Y 轴的格子线, 均分 5 等分 For i = 0 To 4 y = i * 10 / 5 PicShow.Line (0, y)- (MaxPlotNo, y) Next i PicShow.DrawWidth = 2 '绘线宽度改为 2 PicShow.DrawStyle = vbSolid '以实线绘图 PicShow.PSet (0, ValueStr), RGB(255, 0, 0) '设定起点 Else '以下判断现在的读值是否大于前一次的读值 若是 则以红色绘线 '若否 则以蓝色绘线 If ValueStr > PreValue + 0.01 Then PicShow.Line - (NowX, ValueStr), RGB(255, 0, 0) '由上一次的 位置画至此点 Else PicShow.Line - (NowX, ValueStr), RGB(0, 0, 255) '由上一次的 位置画至此点 End If End If Else 第 233 页,共 606 页 lblStart.Caption = CStr(Val(lblStart.Caption) + 1) lblEnd.Caption = CStr(Val(lblEnd.Caption) + 1) Plot NowX + 1 '将图绘在另一个隐藏的图片框 '绘好的图形转移至显示的图片框 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.hDC, 0, 0, &HCC0020) End If PreValue = ValueStr NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 fIsOver = True End If '检查警戒状态 MSComm1.Output = "@" & Buf & "DI" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 1000) If Mid(RetBuf, 4, 1) = "1" Then '若激活警戒才作检查 i = Val(Mid(RetBuf, 5, 2)) If (i And 1) = 1 Then '低值警戒是否激活 imgLO.Picture = imgGreen.Picture Else imgLO.Picture = imgDisable.Picture End If If (i And 2) = 2 Then '高值警戒是否激活 imgHigh.Picture = imgRed.Picture Else imgHigh.Picture = imgDisable.Picture End If End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 绘图的子程序 ' 模拟读值的数组取出后绘图 ' 绘图时需考虑绘图的起点和终点 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Plot(StartX&) Dim i&, j&, x!, y! PicHide.Cls '清除图形 PicHide.DrawWidth = 1 '使用画笔为一个像素宽度画格子线 PicHide.DrawStyle = vbDot '以实线绘图 '绘 X 轴的格子线, 均分 10 等分 For i = 0 To 9 x = i * MaxPlotNo / 10 PicHide.Line (x, 0)- (x, 10) Next i '绘 Y 轴的格子线, 均分 5 等分 For i = 0 To 4 y = i * 10 / 5 PicHide.Line (0, y)- (MaxPlotNo, y) Next i '以下开始曲线绘图 PicHide.DrawWidth = 2 '使用画笔为二个像素宽度画读值线 PicHide.DrawStyle = vbSolid '以实线绘图 j = StartX '第一个值 If j > MaxPlotNo Then j = 0 PicHide.PSet (0, PlotValue(j)), RGB(255, 0, 0) '设定起点 For i = 1 To MaxPlotNo If j = 0 Then If PlotValue(j) > PlotValue(MaxPlotNo) + 0.01 Then 第 234 页,共 606 页 PicHide.Line - (i, PlotValue(j)), RGB(255, 0, 0) '由上一次的 位置画至此点 Else PicHide.Line - (i, PlotValue(j)), RGB(0, 0, 255) '由上一次 的位置画至此点 End If Else If PlotValue(j) > PlotValue(j - 1) + 0.01 Then PicHide.Line - (i, PlotValue(j)), RGB(255, 0, 0) '由上一次 的位置画至此点 Else PicHide.Line - (i, PlotValue(j)), RGB(0, 0, 255) '由上一次 的位置画至此点 End If End If j = j + 1 If j > MaxPlotNo Then j = 0 Next i End Sub Private Sub txtHighAlarm_Change() hsHigh.Value = Val(txtHighAlarm.Text) * 100 End Sub Private Sub txtLowAlarm_Change() hsLow.Value = Val(txtLowAlarm.Text) * 100 End Sub 模 块内的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 第 235 页,共 606 页 6-3-5 事件次数读取 数字输入的信道除了可以得到现在外界的数字状态之外 此种 数字输入通道还可以具有计数的功能 当数字输入通道作切换的动 作时 模块内部就会将计数值加 1 最高的计数值可达 65535 和事件操作的相关指令如表 6-3-3 所示 由传回范列可知 传 回字符串中的第四个字符开始 连续 5 个字符代表模块内部的计数 值 表 6- 3- 3 事件相关指令及传回值 功能 命令格式 命令范例 传回字符串 传回范列 读取事件值 @AARE @02RE !AA(Data) !0200098 清除事件值 @AACE @02CE !AA !02 接下来我们设计一个读取计数值和清除计数值的项目吧! 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其也 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”cmbCOM”及”cmbNO” 作为通讯端口信道及侦测模块站号 的选择区 4、 安排一个 Label 控件 按下 F4 叫出属性窗口 其 Name 属性改 为”lblCount” BackColor 选择淡黄色 作为事件值显示之用 5、 再安排三个 Label 控件 按下 F4 叫出属性窗口 Caption 属性分 别输入”通讯端口” ”站号” ”事件值” 作为各个控件的标示之 用 6、 安排一个定时器 按下 F4 叫出属性窗口 修改其 Interval 属性 为 100 定时器将以 100 毫秒的周期执行内部的程序代码 7、 安排四个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始读值 清除读值 结束 作为执行相对应的动作之 用 8、 设计后之画面如图 6-3-8 所示 第 236 页,共 606 页 图 6-3-8 事件读值的设计画面 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 当一切选择妥当后 按下开启通讯端口及开始读值的按钮 接 着激活定时器 而定时器就依 Interval 属性值一直执行其内部的程序 代码 其程序代码即是不断地对 7012D 模块询问现在事件的读值 接着处理传回的指令结果 显示读值数目在画面上 我们的目的是 读取模块内的计数值 而在按下 清除读值 按钮后 将 模块内的 读值清掉 以下接着就是程序的安排 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因 此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 此模块编号通常会被我们设成 02 因此在程序中直接让它 出现模块的号码为 2 由于还未开启通讯端口 因此 开始侦测 的按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '检查是否开关通讯端口 If cmdOpenCOM.Caption = "关闭通讯端口" Then 第 237 页,共 606 页 MSComm1.PortOpen = False cmdOpenCOM.Caption = "开启通讯端口" Exit Sub End If '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Caption = "关闭通讯端口" cmdStart.Enabled = True Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当然 得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布式模块所 使用的 Settings 参数 再开启通讯端口 一旦开启通讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 3、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端口 End If cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序就不需执行 当然直 接跳出去即可 这个部份的程序可以在使用者变更通讯端口号码 时 将通讯端口关闭 并将原先 Disable 的开启通讯端口按钮重新 Enable 4、 双击定时器控件 在其 Timer 事件中输入以下的程序代码 ComBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(ComBuf) = 1 Then ComBuf = "0" & ComBuf End If MSComm1.Output = "@" & ComBuf & "RE" & Chr(13) '组合完整的命 令字符串 Buf = WaitRS(MSComm1, vbCr, 1000) 第 238 页,共 606 页 If Buf <> "" And Len(Buf) > 8 Then lblCount.Caption = Mid(Buf, 4, 5) End If If fClearEvent Then '清除旗标 MSComm1.Output = "@" & ComBuf & "CE" & vbCr '清除的指令 Buf = WaitRS(MSComm1, vbCr, 1000) fClearEvent = False '已清除 故再设成 False End If 程序分成二个部份进行 首先是计数值读取的部份 利用@AARE 指令将执行字符串传回后 解析第 4 个字符开始的连续 5 个字符 并将其显示在 lblCount 再来是数字清除的部份 使用@AACE 将 模块内的计数值清掉 定时器就如此地周而复始地作计数值的读取和清除的工作 5、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 定时 器随即被激活 定时器的侦测结果可能如图 6-3-9 所示 图 6-3-9 事件读取/清除的执行结果 图 6-3-9 的执行即是模块内部的计数值是 63 次 而当我们按下 清 除读值 的按钮后 此事件值将会为 0 读者亦可使用实验板上的 摇头开关的切换而测试此一结果 以上的程序代码 您可以参考光盘中 范例 \ 第六章 \ 事 件值的读取 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上 的测试 完整的程序代码列表如下 Option Explicit Dim fClearEvent As Boolean '清除事件值的旗标 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 第 239 页,共 606 页' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端口 End If cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() Dim Buf$, RetBuf$ '检查是否开关通讯端口 If cmdOpenCOM.Caption = "关闭通讯端口" Then MSComm1.PortOpen = False cmdOpenCOM.Caption = "开启通讯端口" Exit Sub End If '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯 息 " Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Caption = "关闭通讯端口" cmdStart.Enabled = True Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 读取资料 按钮后激活此事件 ' 将模块传回的资料利用 Input 指令接收 并显示在传回区文字框中 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdClear_Click() fClearEvent = True End Sub 第 240 页,共 606 页 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口号码及站号填入 Combo 控件 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 1 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False End Sub Private Sub Timer1_Timer() Dim Buf$, ComBuf$ ComBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(ComBuf) = 1 Then ComBuf = "0" & ComBuf End If MSComm1.Output = "@" & ComBuf & "RE" & Chr(13) '组合完整的命 令字符串 Buf = WaitRS(MSComm1, vbCr, 1000) If Buf <> "" And Len(Buf) > 8 Then lblCount.Caption = Mid(Buf, 4, 5) End If If fClearEvent Then '清除旗标 MSComm1.Output = "@" & ComBuf & "CE" & vbCr '清除的指令 Buf = WaitRS(MSComm1, vbCr, 1000) fClearEvent = False '已清除 故再设成 False End If End Sub 模块内的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() 第 241 页,共 606 页 Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 第 242 页,共 606 页本章习题 1、 试作一程序 参考 6-2-4 节的程序 将定时器的使用改以 Do…Loop 的方法予以完成 2、 参考 6-3-4 的程序 将程序中加入高低值的二条线图 使得 观看者可由图上看出警戒值的设定范围 3、 试作一程序 设定高值警戒和低值警为 Latch 型式 4、 同上一题 当 Latch 的警戒发生时 利用数字输入的通道让 警戒清除 第 243 页,共 606 页第七章 模拟输出模块-7021 7-1 模块介绍 部份的设备允许使用者透过电压或电流控制相关的参数 如果 使用电压或电流控制设备时 通常必须再经过放大器(Amplifier)将 电压或电流作放大 所需的放大器依不同的设备而不同 大部份的 设备都有专门设计的放大器 所以在控制端只要传送电压或电流 的 ”讯号”就可以了 对控制端而言 这就是模拟输出 在 7000 系列 模块中 最一般化的模拟输出模块便是编号 7021 的模拟输出模块 本模块用来作模拟输出之用 模块主要提供了一个信道的模拟 输出 原厂规格标示如表 7-1-1 所示 表 7-1-1 7021 模块重要规格 Analog Output · Output Channel : 1 · Output Type : mA, V · Accuracy : ±.0.1% of FSR · Resolution : ±0.02% of FSR · Readback Accuracy : ±1% of FSR · Zero Drift : · Voltage output : ±30uV/°C · Current output : ±0.2uA/°C · Span Temperature Coefficient : · ±25ppm/°C · Programmable Output Slope : 0.125 to 1024 mA/Second 0.0625 to 512 V/Second · Voltage Output : 10mA max. · Current Load Resistance : · Internal power : 500 ohms · External 24V : 1050 ohms · Isolation : 3000VDC Power Supply · Input : +10 to +30VDC · Consumption : 1.8W 此模块仅提供了单一通道的模拟输出 其功能概分为电压模拟 输出及电流模拟输出 不过 既然有电流和电压输出 通道数怎么 只有一个呢?接着看下去 作作实验就知道了 7-1-1 谈谈规格 以下就针对此模块的相关重要规格描述如下 了解相关的规格 才方便继续作其它的应用 由规格上可以看出此模块的模拟输出通道只有一个 而输出型 态 (Type)则包含有 V mA 二种 也就是此模块所量测的单位在电压 上是伏特 而在电流上则是毫安 表 7-1-2 是输出型态和范围 表 7-1-2 7021 的输出型态和范围 输出型态 最大输出值 最小输出值 第 244 页,共 606 页电流 0 mA 20mA 电流 4mA 20mA 电压 0V 10V 在输出电流讯号时 模块允许的电流范围有二种 一种是 0mA~20mA 另一种是 4mA~20mA 二种在设备控制上都有可能用 到 这是一般最常见的 TTL 电平中的规格 仪器中的电流输出也经 常使用这个范围 在输出电压讯号时 则落于 0V~10V 之间 模块所使用的电压是直流+10V~+30V 使用的理由是模块使用 的场合电压通常会有一些变化 不见得很固定 有了宽广的电源允 许范围也可以保护模块运作正常 7-1-2 7021 的外观及脚位定义 7021D 的外观如图 7-1-1 所示 图 7-1-1 7021 模块外观图 以上的各脚位 分成几个部份说明如下 1、 电源 标明了(R)+Vs 及 (B)GND 10的二支脚在上图的左下方处 分别是正电源及接地线 正电源必须接上+10V~+30V 之间的电 压 笔者的建议是使用由原厂所提供的变压器 或是独立的稳压 变压器 计算机内部虽然有+12V 的电源可以使用 不过 笔者 试过之后 感觉不是很稳定 所以还是建议读者使用独立的电源 较佳(+24V 较佳) 2、 讯号传输 上图标明(Y)DATA+及 (G)DATA-的二支脚位紧接在 电源接脚的上方 RS-485 网络所使用的网络线就是接在这儿 需特别注意的是 所有 485 网络上的 DATA+必须接在一起 而 第 245 页,共 606 页DATA- 也必须接在一起(否则讯号会错乱) 二个以上的分布式模 块之电源及讯号接线示意图如下 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 3、 模块初始 上图标明 INIT*的脚位紧接在讯号线接脚的旁边 此脚位用来对模块作初始化时之用 当模块在必须初始化时 就 可以将此脚和 GND 脚位接在一起 模块内的设定就可以回到默 认值 另外 部份对于模块的特别设定也会使用到这支脚位 4、 电流输出 在图片左上方有二个脚位标明了+IOUT 及 -IOUT 这二个点分别用来当成电流输出的正接点及负接点 5、 电压输出 在图片左方 电流输出之下有二个脚位标明了 +VOUT 及 -VOUT 这二个点分别用来当成电压输出的正接点及 负接点 6、 外接电源 在电压输出的下方有一通道标有 Ext.PWR 当电流 输出时 若不使用内部的电源 则必须跳 Jumper 而使用此通 道提供的电源 此模块的内部电路方块图如图 7-1-2 所示 第 246 页,共 606 页 图 7-1-2 7021 内部电路方块图 7-1-3 和 7021 取得联机与沟通 和第五章所提到的 7060D 第六章所提到的 7012D 一样 欲和 7021 模块通讯 首先使用个人计算机 I- 7520 及 I- 7021 连接如图 7- 1- 3 图 7- 1- 3 主控计算机和 7021 间的联机 其中个人计算机和 7520 之间的接线方法是使用 RS- 232 线 (9 公 -9 母 不需跳线)作为连接 一端接到 7520 上 另一端接到计算机上 的 COM1 或 COM2(如果计算机有扩充的 COM 的话 也可以接到其 它的 COM) 至于 7021 和 7520 间的接线 再放大显示图 7- 1- 4 第 247 页,共 606 页 图 7- 1- 4 7520 与 7021 电源及讯号之连接图 I-7021P 与 I-7021 在功能上是完全相同的 不同的地方是 I-7021P 的分辨率达 16Bit 误差可控制在极低的范围内 在第五章针对 7060D 模块讨论时 曾使用一个简单的程序作为 计算机和模块之间通讯的范例 现在一样可以使用该项目由计算机 传送指令给 7021 模块 由于分布式模块的基础在于每一个网络上的模块都有一个地 址 透过不同的地址可以控制不同的模块 在第五章中的 7060D 模 块所使用的模块地址是”1” 而在讨论 7012D 则是设定该模块地 址为 2 为了区别及未来整合实验方便 在此将 7021 模块的地址设 定为”3” 至于设定模块地址的方式 读者可以使用 5-1-3 节项目的 程序 对出厂预设的 7012D 下 达修改地址的指令 也可以透过原厂 所提供的公用程序来对模块地址编号作修改 而笔者是比较建议使 用公用程序(第一次还是使用原厂的东西比较安心) 使用 5-1-3 的项目程序 将项目开启并按下 F5 后开始执行 在 命令区文字框中输入指令”$032” 接着按下 读取资料 的按钮 传回的讯息如图 7- 1- 5 第 248 页,共 606 页 图 7-1-5 7021 的组态指令及传回结果 由于分布式模块所传回的组态讯息格式是相类似的 在第五章和第 六章中也针对 7060D 模块和 7012D 模块作过讨论 读者可以参考 5- 1- 3 节的解释 以下仅就 7021 模块的特有部份作说明 !03320600 相对于$032 所传回的执行结果 此结果显示出 7021 模 块的组态情形 当模块收到指令后会将对应的结果传回 分析此字 符串 !03 中的!是表示模块已接收到的指令是一个正确的指令 可以被该模块执行 而 03 则表示模块地址编号为第 3 号 32 指的是模块使用的模拟输出范围及型态是电压输出 0~10V 模块 的数种输出型态及范围分别以不同的字符表示 如表 7-1-3 所示 表 7- 1- 3 型态码与量测范围 Type Code 30 31 32 Min. Output 0mA 4mA 0V Max. Output 20mA 20mA 10V 由表 7-1- 3 可以了解到 7021 模块所提供的范围 使用者可依不同的 情形选用不同的范围 06 指的是使用的串行通讯传送速度是 9600bps 最后的 00 字符串可以对应到 8 个位 每个字符可由 0 变化到 F 左边的字符所对应的是由位 7~位 4 而右边的位则可以 对应到位 3~位 0 对应的关系如图 7- 1- 6 图 7- 1- 6 字符与位位置的对应 第 249 页,共 606 页图 7- 1- 6 中的各位之代表意义如下 Bit7 固定为 0 Bit6 此位用于表示模块是否激活 CheckSum 若为 0 表 示不激活 1 表示激活 Bit5 Bit4 Bit3 Bit2 输出变化率控制(详见后述) Bit1 Bit0 00 时表示使用工程单位(Engineering Unit)的格式表现 量测的数值 01 时表示使用百分比格式 而 10 则表示使用十 六进制格式 使用何种格式并无一定 端看使用者的习惯 模块的组态除了可以取得之外 也可以透过命令字符串而将其 更改 而 使用的指令格式是”%AANNTTCCFF” 由模块手册可以知 道 AA 指的是地址 依我们现在的情形就是 03 NN 指是是新的 模块编号 若不打算改变其编号 则此值仍设为 03 TT 指的是输 出型态的设定 参考表 7-1- 3 可知 共有 30 31 32 可供设定 CC 是通讯速度 有 03~0A 的范围可设定 分别表示了 1200~115200bps 的传输速度 FF 就是资料格式设定(参考图 7-1- 6) 设定的步骤一 样可以利用使用 5-1-3 的项目程序 这个部份就留给读者测试吧! 7-2 模拟输出 由 7012D 的讨论中 我们知道外界的物理量(如电压 电流 流 量 温度 )要进到计算机系统中 必须经过模拟/数字的转换 将 这些连续的模拟讯号经过转换器后 成为计算机可以了解的数字讯 息 同样的 当外界的设备需要被控制时 它也是需要这些物理量 的输入才行 可是计算机只知道 01001100 的数字化资料 要成为 1V 10mA 这样子的物理量也是必须经过转换 这种转换就是数字 转模拟(Digital To Analog) 和 7012D 比起来 它们二个的过程是 相反的 7-2-1 数字转模拟 数字化的世界中 所有的信息使用了 0 与 1 表示 和 7012D 使 用了一定的位数表示外界的电压一样 7021 若欲将一定的模拟值输 出 也是使用一定的位数予以表示 7021 所使用的位数是 12 位 第 250 页,共 606 页也就是模块使用 12 位的长度代表所要输出的模拟值(电压或电流) 以电压输出为例 当全部的位均为 0 时 代表的电压就是 0V 当 全部的位均为 1 时 则表示的电压为 10V 各中间值则依所占的位 权值计算 如图 7-2-1 所示 图 7- 2- 1 位及电压的对照方式 由图 7-2- 1 每一位从低位到高位均有一个对应的数值 位只会 有二种状态 — 0 和 1 将每一个位中的 0 和 1 直接乘上对应的权值 后相加 即可的实际的十进制数值 全刻度时为 10V 此时全部位 值相加后的十进制数值是 4095 因此每一个刻度即为 10/4095 刻 度可能从 0 到 4095 电压值也就可从 0V 变化到 10V 7021 的 输出 为 12 位 理论上可以量测到的电压分辨率为 0.00244 但受限于硬 件精度及传输格式 实际可以指定的位数达小数点下 3 位 在之前的讨论中 笔者也强调了一点 任何电压的形成必须参 考一个共同的接地电平 此点在我们进行的实验中是必须了解的 Slew Rate 由一个电压电平跳到另一个电压电平时 控制上的 跳跃可能只是一行指令而已 可是硬件不见得可以跟得上 就算可 以跟得上 有时候也不允许变化的速度过快 因此在部份情形下就 必须控制此模拟输出值的变化率 此变化率称为 Slew Rate 若受 控的设备对于模拟值的变化并无特别的规格 当然就让模块直接由 初始电压变化到目的电压即可 若限制了电压变化率 那就必须在 模块组态中作适当的变更才行 7-2-2 模拟输出量测接线 模拟讯号的输出基本上分为电压及电流 端看使用上需求(例如 受控制的设备)而去决定所要使用的输出型态 在使用 7021 作 此二 种讯号的输出时 其线号的接法如图 7- 2-2 此图分为(A) (B) (C) 第 251 页,共 606 页三个部份 (A)部份是当输出电压时的接线方法 使用电流输出时 又分为二种 一种是使用模块内部的电源 则接线方法如(B)部份 所示 当使用的是外部的电源时 则接线方法必须如(C)部份所示 图 7-2-2 7021 于输出电压及电流时的接线方法 在整个电路中 电流的走向是由负载的正端进入 而由负端出 去 因此模块讯号接点的正端均是进入负载的正端 而负载的负端 再连接到模块讯号接点的负端 电压的输出控制情形是较为单纯 接线法只有一种 也较为疑 义 而电流的输出就比较特别 它可以使用模块的内部电源方式 也可用外部电源的方式 若受控的设备仅需模块提供电流讯号供其 作遵输入讯号比较之用 此时就使用内部电源 而模块线路负载阻 抗设计是基于 500 欧姆 考虑到输出电流时 最大的输出是 20 毫 安 透过 V=IR 的计算 我们可以得到 V=0.02*500=10(V) 所以最 大的电压是 10V 当设备本身提供了电源 而必须由模块适当地控 制在此整个电路中的电流大小时 就必须使用外部的电源 此外部 电源实际上也就是受控设备中原本就存在的 模块手册上载明使用 外部 24V 电源时 整个负戴的阻抗设计是基于 1050 欧姆 同样的 利用 V=IR 的计算 我们可以得到 V=0.02*1050=21(V) 所以最大 只到达 21V 还小于 24V 的电源 当使用 7021 模块作电流的输出时 会不会所使用的电阻设计 和所要控制的设备不合的情形呢?基本上 需 要电流输入的设备 其输入阻抗设计通常很高(百万欧姆以上 M ) 模块的电流输出 阻抗和其并联后 整个电阻也会和模块的输出阻抗相当 所以输出 的电流不会有不合的情形 第 252 页,共 606 页7-2-3 控制 7021 的电压输出值 了解模拟输出的相关信息后 本节将进行模拟值的输出 不过 欲进行模拟值的输出 得先考虑如何得知我们的输出是否正确 因 此必须要有量测的设备才行 在第六章提过了 7012D 模块 此模块 是用来作模拟输入的量测 在此小节就以 7012D 模块作为检查的仪 器 (若读者有其它的量测设备更好 可以作个比较 ) 图 7- 2-3 是 7021 和 7012D 的测试连接图 图 7- 2- 3 7021 和 7012D 实验接线图 将 7021 模拟输出模块的+Vout 和 - Vout 脚位分别接往 7012D 模块的 +IN 及 - IN 接脚 就形成了模拟输入的实验线路 当然 还必须将 相关的接线也接往 7520 模块 而 7052 模块也必须和主控计算机接 好才行 模拟输出指令 7021 模块的模拟输出指令为#AA(Data) #为前导码 AA 为地址 在本章假设模块的编号是 3 号 所以 AA 就是 03 (Data) 是模拟输出的资料 此资料格式如表 7- 2- 1 所示 表 7- 2- 1 模拟输出的资料类别及格式 型态码 输出范围 格式单位 最大值 最小值 工程单位 20.000 00.000 百分比 +100.00 +000.00 30 0 TO 20mA 十六进制 FFF 000 工程单位 20.000 04.000 百分比 +100.00 +000.00 31 4 TO 20mA 十六进制 FFF 000 32 0 TO 10V 工程单位 10.000 00.000 第 253 页,共 606 页百分比 +100.00 +000.00 十六进制 FFF 000 由表 x-2- 1 三种不同的输出型态上 各有三种不一样的资料格式 以第三种的输出电压来看 最小的输出电压为 0 格式上必须写成 00.000 而最大的输出电压为 10V 格式上必须写成 10.000 分析 这二个格式发现 输出时的资料格式必须写成小数点前二位 小数 点后三位的数字 例如我们希望输出 5.012V 的电压 则输出的指 令必须写成”#0305.012” 由于小数点前的数值格式必须是二位 因 此在 5 的前方加上一个 0 以符合模块的格式要求 先使用简单的方式看看 7021 模块所传作的模拟输出的情形 使 用 5-1-3 的项目程序 将项目开启并按下 F5 后开始执行 在命令区 文字框中输入指令”#0305.000” 接着按下 送出指令 再按下 读 取资料 的按钮 传回的讯息如图 7- 2- 4 图 7-2-4 7021 的模拟输入读值指令及传回结果 传回的结果是只有一个”>”的符号 而此符号表示指令是正确 的 并且已被模块成功地执行 查看 7012D 上的显示情形 显示出 的结果是+5.008 最后的一位数是误差 读者可以使用不同的输出 数值测试所输出的结果 在 7012D 上的显示中均是最后一位产生些 许的误差 除了可以使用工程单位的方式输出电压值外 也可以使用如表 7-2-1 中所列出的百分比方式和十六进制的方式作数值的指定 此 二种数值的解释如 7012D 一般 其中的十六进制就是直接将所使用 数字转模拟的转换位以十六进制数值表示出来 最为低阶 由于 7021 模块的模拟输出是 12 位 因此只需要使用到三个十六进制的 位置就可以达到指定的目的 指定的数值就类 第 254 页,共 606 页似 ”FFF” ”014” ”123” ”ABC” 等等的格式 不过使用这种方式 还是得先将模块的数据传输格式指定为十六进制格式才行 这点别 忘了 不管使用的格式是那一种 传回的数值均会依照表 7-2- 1 的 格式 因此 当程序以自动化的方法作数值转换时 就可以使用固 定的转换程序 而不必担心会出错 实务的使用上 笔者还是比较 习惯工程单位 因为最易了解 7-2-4 实验用的模拟表头 在上一节中已经利用 7012D 的显示功能验证了模拟输出的格式 和输出结果 为了实验上的方便 笔者还是为 7021 模块准备了二 个实验器材 — 电压表头和电流表头 这二种表头非常容易在电子材 料上买到 便宜的表头价格约在 NT$150 元 ~200 元之间 很适合作 实验用 它们的实验外观如图 7- 2- 5 所示 图 7- 2- 5 直流电压 电流表头实验盒图 电压表头及电流表头分成直流和交流二种 我们的模块是直流 的输出 所以必须选择直流的 由于 7021 模块可以输出电压和电 流 因此选用电压和电流表头各一个 当模块和表头作连接时 电压部份只要将模块上的+Vout 接往 电压表头的正接点 而 - Vout 接往电压表头的负接点 另外 电流 部份要将模块上的+Iout 接往电流表头的正接点 而 -Iout 接往电流 表头的负接点 如此就完成了 7021 模块的测试实验板了 完成实验线路后 接下来实作一个控制电压输出的项目 第 255 页,共 606 页画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其它 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属性为”cmbCOM”及 ”cmbNO” 作为通讯端口信道及侦测模块 站号的选择区 4、 安排二个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 均改为”????” 按出 Font 属性 并将字号设为 18 颜色设为蓝 色 这二个 Label 将用于显示所送出的电压值及由模块所读回 的输出电压值 读者亦可使用其它的显示设定 5、 安排一个 Slider 控件 拉出适当的外观 按下 F4 叫出属性窗口 将其 LargeChange 改为 10 Max 改为 100 其它维持默认值 此控件将作为电压改变的来源 由于其 Value 值规定为整数 为求小数点以下的数值 故将其 Max 设为 100 而以 10 除数 以求得小数点下一位的数值 6、 安排 4 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为”通讯端口” ”站号” ”电压值” ”读回值” 作为各控 件的标示之用 7、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性 窗口 其 Name 属性改为”lblMsg” BorderStyle 选择”1- 单线固 定 ” BackColor 选择淡黄色 作为显示系统讯息之用 8、 安排二个按钮控件 其 Caption 属性分别输入 开启通讯端口 结束 作为执行相对应的动作之用 9、 设计后之画面如图 7-2-6 所示 图 7-2-6 模拟输出的画面设计 第 256 页,共 606 页Slider 控件并非预设控件 所以必须由组件库中设定到工具 箱 设定的方法是由 Visual Basic 设计窗口中的 项目 \ 设定 使用组件 开启组件选项后 勾选 Microsoft Windows Common Controls 6.0 按下确定后即可在工具箱中看到多了一些控件 Slider 即为其中之一 如图 7-2-7 所 示 图 7-2-7 多出的控件及 Slider 控件 动作流程解析 程序执行后 我们希望使用者选择通讯端口及模 块站号 选定后按下开启通讯端口 接着使用者可以拉动 Slider 控件 程序接着就把模拟输出的指令送到模块上去执行 并将模块上的模拟 输出值也一并传回而显示在画面上 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 2 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 并令 ListIndex 为 0 让选项留在第一个项目上 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then 第 257 页,共 606 页 MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系 统讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable lblMsg.Caption = "可选择 Slide 控件的指针 执行输出的工作 " If cmdOpenCOM.Enabled Then lblMsg.Caption = "尚未开启通讯端口!" Exit Sub End If '先将模块输出状态作设定 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '设定为电压输出 MSComm1.Output = "%" & Buf & Buf & "300600" & Chr(13) RetBuf = WaitRS(MSComm1, vbCr, 1000) '先恢复到 0V 的位置 MSComm1.Output = "#" & Buf & Format(0, "00.000") & Chr(13) '组 合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前 当然得先检查通讯端口号码是否落在 1~16(MSComm 控件仅 支持到 16 个通讯端口 一般情形已足够)之间 接着就是设 定分布式模块所使用的 Settings 参数 再开启通讯端口 一 旦开启通讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 由于模块之前可能作过实验而改变了其它的设定 所以先将 模式设定为电压输出 并且把电压恢复到 0V 的位置 3、 双击 Silder 控件 在其 Scroll 事件(不是 Click 事件!)中输入以下 程序代码 If cmdOpenCOM.Enabled Then lblMsg.Caption = "尚未开启通讯端口!" Exit Sub End If Buf = cmbNO.List(cmbNO.ListIndex) ' 取得模块编号 '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If 第 258 页,共 606 页 lblValue.Caption = Slider1.Value / 10 & "V" '显示在画面上 '送出模拟指令 MSComm1.Output = "#" & Buf & Format(Slider1.Value / 10, "00.000") & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then lblMsg.Caption = "输出电压失败 " Exit Sub End If '取回现在读值 MSComm1.Output = "$" & Buf & "8" & Chr(13) '组合完整的命令字 符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Or Len(RetBuf) < 9 Then lblMsg.Caption = "读回失败 " Exit Sub End If lblReadBack.Caption = Val(Mid(RetBuf, 4, 6)) & "V" '显示在画面上 Scroll 事件会在使用者以鼠标的左键按下而拉动滑杆时产生 而 且在拉动的过程中就会引发生此事件 若使用 Click 事件的话 只有在拉动结束时会发生 拉动过程不发生 这是二者不同的 地方 此事件发生后 程序使用模拟输出指令#AA(Data)的格式将 Slider 上的值送出 并且使用$AA8 的读回指令读回模块现在的输出值 送出与读回的数值均使用 Label 控件将其显示在画面上 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序循环就不需执行 当然直接跳出去即可 这个部份的程序可以在使用者变更通讯 端口号码时 将通讯端口关闭 并将原先 Disable 的开启通讯端 口按钮重新 Enable 5、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 的按钮 我们可接着在 Slider 控件上以鼠标拉动指针 而藉以改变数值 此数值会马上引发 Slider 的 Scroll 事件 程序代码因而被执行 程序的执行结果可能如图 7-2-8 所示 第 259 页,共 606 页 图 7-2-8 读值程序的执行结果 程序执行后 使用鼠标拉动滑杆 画面上的电压值就会改变 同时此模拟值会被送到模块上 透过指令读回后 此值显示在读回 值区 由以上的结果 我们发现输出数值与实际的读回数值有一些 差异 此差异在小数点下第三位 精度上相当不错 实验时将 7021 模块上的±Vout 及 ±Iout 均连接至表头上 利用 本节的项目 可以得到如图 7- 2- 9 的各个结果 图 7- 2- 9 几次实验的表头结果 此实验让我们了解到 7021 模块上的输出特性 虽然模块分别 提供了电压和电流的输出通道 可是当使用程序的方法控制模块作 电压输出时 电流的部份也跟著作输出 比例则是以 0V 对应到 0mA 而 10V 对应到 20mA 总而言之 分别提供的二个通道 其 实是经由同一个数字/模拟转换器 由 图 7-1-2 也可看到 它们均是 连接到同一颗 DAC 第 260 页,共 606 页以上的程序代码 您可以参考光盘中 范例 \ 第七章 \ 电 压 的输出 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的 测试 如果改控制电流输出的话 在硬件配线上是必须使用到+Iout 和 - Iout 二支接脚上 此二支脚位分别接到受控制设备上所标示出的 +I 和 -I 的二个接点 程序部份的改变相当小 以本小节的范例程序 为模板 需修改的地方是将 Slider 的 Max 属性设为 200(以 0mA~20mA 为输出范围) 其它的部份则完全相同 读者可以试着 改看看 而修改后的程序 您可以参考光盘中 范例 \ 第七章 \ 电 流 的输出 档案夹中的项目档 Ex.vbp 电压的输出 档案夹中的项目 其完整的程序代码列表如下 Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯 端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按 钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() Dim Buf$, RetBuf$ '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else 第 261 页,共 606 页 MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系 统讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable lblMsg.Caption = "可选择 Slide 控件的指针 执行输出的工作 " If cmdOpenCOM.Enabled Then lblMsg.Caption = "尚未开启通讯端口!" Exit Sub End If '先将模块输出状态作设定 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '设定为电压输出 MSComm1.Output = "%" & Buf & Buf & "300600" & Chr(13) RetBuf = WaitRS(MSComm1, vbCr, 1000) '先恢复到 0V 的位置 MSComm1.Output = "#" & Buf & Format(0, "00.000") & Chr(13) '组 合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口号码及站号填入 Combo 控件 ' 预设通讯端口的选项是第一个 ' 预设站号的选项是第二个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 2 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'Slider 的 Scroll 事件 ' 使用鼠标在 Slider 上移动时发生 ' 我们将 Slider 的值处理后送出 ' 并取回模块上的现在输出值 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 262 页,共 606 页Private Sub Slider1_Scroll() Dim Buf$, ValueStr As Single, Pos1% Dim RetBuf$ If cmdOpenCOM.Enabled Then lblMsg.Caption = "尚未开启通讯端口!" Exit Sub End If Buf = cmbNO.List(cmbNO.ListIndex) '取得模块编号 '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If lblValue.Caption = Slider1.Value / 10 & "V" '显示在画面上 '送出模拟指令 MSComm1.Output = "#" & Buf & Format(Slider1.Value / 10, "00.000") & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then lblMsg.Caption = "输出电压失败 " Exit Sub End If '取回现在读值 MSComm1.Output = "$" & Buf & "8" & Chr(13) '组合完整的命令字 符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Or Len(RetBuf) < 9 Then lblMsg.Caption = "读回失败 " Exit Sub End If lblReadBack.Caption = Val(Mid(RetBuf, 4, 6)) & "V" '显示在画面上 End Sub Module 中的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT 第 263 页,共 606 页 If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 7-2-5 电压改变率的控制 使用 7021 模块输出电压时 通常会依控制的需求而改变输入 的电压电平给受控设备 如此一来势必存在电压的转变 一个电压 变换到另一个电压时 就会有一个转换的过程 这个过程可以快 也可以慢 端看受控设备的要求而定 最常见的情形就是一改变电 压后就马上反应出来 不过 并不是每一个受控设备都可以容忍电 压的快速改变 所以模块也就提供了不同的电压/电流改变率 让使 用 者可以透过组态的设定而达到改变电压转换速率的目的 藉以满 足受控设备的要求 7021 模块的电压改变率设定是透过组态设定指令 其指令格式 是”%AANNTTCCFF” 其中的 AA 指模组编号 NN 指设定的编号 TT 为输出模块 CC 是通讯速率 FF 则是资料格式设定 此资料格 式的设定就包括了电压改变率的设定 请读者参阅图 7- 1-6 及其以下 之说明 电压改变率的设定使用了 4 个位的位置 改变率设定及对 应的速率如表 7- 2- 2 所示 表 7- 2- 2 电压改变率及位对应 位值 电压改 变率 (V/S) 电流改变 率 (mA/S) 位值 电 压改 变率 (V/S) 电流改变 率 (mA/S) 0000 Immediate 1000 8.0 16.0 0001 0.0625 0.125 1001 16.0 32.0 0010 0.125 0.25 1010 32.0 64.0 0011 0.25 0.5 1011 64.0 128.0 0100 0.5 1.0 1100 128.0 256.0 0101 1.0 2.0 1101 256.0 512.0 0110 2.0 4.0 1110 512.0 1024.0 0111 4.0 8.0 对照图 7-1-6 替换掉 Bit5~Bit2 中的相对位值 再对 7021 模块 下达组态变更的指令即可改变模块的输出改变率 例如 希望电压的 转换是以每秒 2V 的速率进行 替换掉图 7-1-6 中的四个位后 其位 排列会是 00011000 左边的第二个位代表 CheckSum 右边的二个位 代表资料单位设定 我们均维持默认值 中间的四个位就是由表 7-2-2 找出替代掉的位 而二位数值 00011000 以十六进制表示会变成 18 第 264 页,共 606 页因此组态设定时给定的 FF 就是 18 即可改变电压改变率为每秒钟 2V 有了这样的了解后 接下来就是将 7021 的模拟输出和改变率作 一个项目 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其它 的属性均维持默认值 3、 安排三个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属性为”cmbCOM” ”cmbNO”及 ”cmbRate” 作为通讯端口信道 侦测模块站号及电压改变率的选择区 4、 安排二个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 均改为”????” 按出 Font 属性 并将字号 1818 颜色设为蓝色 这二个 Label将用于显示所送出的电压值及由模块所读回的输出 电压值 读者亦可使用其它的显示设定 5、 安排一个 Slider 控件 拉出适当的外观 按下 F4 叫出属性窗口 将其 LargeChange 改为 10 Max 改为 100 其它维持默认值 此 控件将作为电压改变的来源 由于其 Value 值规定为整数 为求 小数点以下的数值 故将其 Max 设为 100 而以 10 除数 以求 得小数点下一位的数值 6、 安排一个定时器控件 按下 F4 叫出属性窗口 改变其 Enabled 属性为 False Interval 属性为 100 上一小节中是在 Slider 控件 中执行输出的指定 本小节改用定时器达到此功能 7、 安排 5 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为”通讯端口” ”站号” ”电压改变率” ”电压值” ”读回 值 ” 作为各控件的标示之用 8、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性 窗口 其 Name 属性改为”lblMsg” BorderStyle 选择”1- 单线固 定 ” BackColor 选择淡黄色 作为显示系统讯息之用 9、 安排二个按钮控件 其 Caption 属性分别输入 开启通讯端口 结束 作为执行相对应的动作之用 10、 设计后之画面如图 7-2-10 所示 第 265 页,共 606 页 图 7-2-10 模拟输出和改变率项目的设计画面 动作流程解析 此项目的画面设计完成后 使用者可以在画面上 选择通讯端口号码 模块的编号 电压改变率 当一切选择妥当后 按下开启通讯端口的按钮 接着激活定时器 而定时器就依 Interval 属性值一直执行其内部的程序代码 其程序代码即是不断地对 7021 模块送出 Slider 中的 Value 属性值 我们需要关切的是 当电压被改 变后 模块实际上的输出过程 另外也观察由模块读回的输出电压值 的改变情形是如何 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 2 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmbRate.Clear cmbRate.AddItem "Immediate" cmbRate.AddItem "0.0625V/s" cmbRate.AddItem "0.125V/s" cmbRate.AddItem "0.25V/s" cmbRate.AddItem "0.5V/s" cmbRate.AddItem "1.0V/s" cmbRate.AddItem "2.0V/s" cmbRate.AddItem "4.0V/s" cmbRate.AddItem "8.0V/s" cmbRate.AddItem "16.0V/s" cmbRate.AddItem "32.0V/s" cmbRate.AddItem "64.0V/s" cmbRate.AddItem "128.0V/s" 第 266 页,共 606 页 cmbRate.AddItem "256.0V/s" cmbRate.AddItem "512.0V/s" cmbRate.AddItem "1024.0V/s" cmbRate.ListIndex = 0 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 并令 ListIndex 为 0 让选项留在第一个项目上 电压改变率的选项也在其中被放入 让使用者可以选择不同的 改变率 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '改变按钮的文字 作为判别之用 If cmdOpenCOM.Caption = "关闭通讯端口" Then cmdOpenCOM.Caption = "开启通讯端口" Else cmdOpenCOM.Caption = "关闭通讯端口" End If '若选择关闭通讯端口 则将定时器及通讯端口均关闭 If cmdOpenCOM.Caption = "开启通讯端口" Then Timer1.Enabled = False MSComm1.PortOpen = False lblMsg.Caption = "通讯端口已关闭!" Exit Sub End If '若为开启通讯端口 则进行以下的工作 '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系 统讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 '先将模块输出状态恢复到 0V 的位置 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Format(0, "00.000") & Chr(13) '组 合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) '改变 Slew Rate Rate = cmbRate.ListIndex RateBuf = Hex((Rate And 8) / 8 * 2 + (Rate And 4) / 4 * 1) & Hex((Rate And 2) / 2 * 8 + (Rate And 1) * 4) MSComm1.Output = "%" & Buf & Buf & "3206" & RateBuf & Chr(13) ' 组合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) lblMsg.Caption = "侦测中 " 第 267 页,共 606 页 Timer1.Enabled = True Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前 当然得先检查通讯端口号码是否落在 1~16(MSComm 控件仅 支持到 16 个通讯端口 一般情形已足够)之间 接着就是设 定分布式模块所使用的 Settings 参数 再开启通讯端口 一 旦开启通讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 由于模块之前可能作过实验而改变了其它的设定 所以先将 模式设定为电压输出 并且把电压恢复到 0V 的位置 程序中也对组态作设定 设为电压输出 9600Bps 的串行传输 速度 以及使用者所选择的电压改变率 由于四个代表改变 率的四个位在格式上是 Bit5~Bit2 程序必须判断到底使用者 选择的改变率是多少 由表 7-2-2 可知这些电压的改变率使用 的位是逐步增加的 每次均递增 1 转换为十六进制后 分为 二个字符 Bit5~Bit2 所占的位比重可分为左边字符的第 0 和 第 1 位及右边字符的第 3 和第 2 位 计算时使用的计算式 Hex((Rate And 8) / 8 * 2 + (Rate And 4) / 4 * 1) & Hex((Rate And 2) / 2 * 8 + (Rate And 1) * 4) 就是分成左右二个字符计算 数值的作法 3、 双击定时器控件 在其 Timer 事件中输入以下程序代码 If cmdOpenCOM.Caption = "开启通讯端口" Then lblMsg.Caption = "尚未开启通讯端口!" Exit Sub End If Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If lblValue.Caption = Slider1.Value / 10 & "V" '判断数值发生改变 若改变才令其输出 避免多余的动作 If PreVoltage <> Val(lblValue.Caption) Then MSComm1.Output = "#" & Buf & Format(Slider1.Value / 10, "00.000") & Chr(13) '组合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) PreVoltage = Val(lblValue.Caption) If RetBuf = "" Then lblMsg.Caption = "输出电压失败 " Exit Sub End If End If lblMsg.Caption = "联机" & Buf & "中 " 第 268 页,共 606 页 MSComm1.Output = "$" & Buf & "8" & Chr(13) '读回现在的模拟输 出值 RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Or Len(RetBuf) < 9 Then lblMsg.Caption = "读回失败 " Exit Sub End If lblReadBack.Caption = Val(Mid(RetBuf, 4, 6)) & "V" 在上一小节中是在 Slider 控件的 Scroll 事件中处理输出和读回 值显示 在本小节中则是将模拟输出和读回的动作写在这个定 时器中 指令的使用同样是#AA(Data)和 $AA8 二个指令 送出与读回的数值均使用 Label 控件将其显示在画面上 这部份 和上一小节是一样的 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" 使用者未改变通讯端口的号码时 此子程序循环就不需执行 当然直接跳出去即可 这个部份的程序可以在使用者变更通讯 端口号码时 将通讯端口关闭 5、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 的按钮 拉动 Slider 希望的 输出电压随即会被定时器送到模块上去执行 而定时器的读回也将 显示在画面上 实验结果可能如图 7-2-11 所示 第 269 页,共 606 页 图 7-2-11 模拟读值绘图执行结果 图 7-2-11 中 上方的图形设定直接变更电压 因此当 Slider 的 数值改变时 读回值也会马上跟着改变 而下方的二个图形就是设 定改变率是每秒 0.5V 的情形 当 Slider 的数值瞬间改变时 模块 的输出电压并不是马上改变 而是顺着所设定的改变率而改变 所 以我们在实验时可以看到读回值的部份会逐渐地逼近所设定的电 压输出值 但不会马上改变成最终值 依照受控设备的不同而设定不同的电压改变率是在模拟输出 时要注意的事项 电流改变率和电压改变率是相同的 当输出型态 由电压改变到电流时 改变率也自然由电压改换到电流 以上的程序代码 您可以参考光盘中 范例 \ 第七章 \ 不同 的电压改变率 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以 上的测试 完整的程序代码列表如下 Option Explicit Dim PreVoltage As Single '上一次的模拟输出值 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯 端口 End If 第 270 页,共 606 页 lblMsg.Caption = "已停止侦测并关闭通讯端口" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() Dim Buf$, RetBuf$, Rate%, RateBuf$ '改变按钮的文字 作为判别之用 If cmdOpenCOM.Caption = "关闭通讯端口" Then cmdOpenCOM.Caption = "开启通讯端口" Else cmdOpenCOM.Caption = "关闭通讯端口" End If '若选择关闭通讯端口 则将定时器及通讯端口均关闭 If cmdOpenCOM.Caption = "开启通讯端口" Then Timer1.Enabled = False MSComm1.PortOpen = False lblMsg.Caption = "通讯端口已关闭!" Exit Sub End If '若为开启通讯端口 则进行以下的工作 '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系 统讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 '先将模块输出状态恢复到 0V 的位置 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Format(0, "00.000") & Chr(13) '组 合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) '改变 Slew Rate Rate = cmbRate.ListIndex RateBuf = Hex((Rate And 8) / 8 * 2 + (Rate And 4) / 4 * 1) & Hex((Rate And 2) / 2 * 8 + (Rate And 1) * 4) 第 271 页,共 606 页 MSComm1.Output = "%" & Buf & Buf & "3206" & RateBuf & Chr(13) ' 组合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) lblMsg.Caption = "侦测中 " Timer1.Enabled = True Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口号码及站号填入 Combo 控件 ' 预设通讯端口的选项是第一个 ' 预设站号的选项是第三个 ' 电压改变率亦填入其中 ' 预设的改变率设为"Immediate" 亦即马上就改变电压 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 2 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 '将改变率输入 Combo 控件内 cmbRate.Clear cmbRate.AddItem "Immediate" cmbRate.AddItem "0.0625V/s" cmbRate.AddItem "0.125V/s" cmbRate.AddItem "0.25V/s" cmbRate.AddItem "0.5V/s" cmbRate.AddItem "1.0V/s" cmbRate.AddItem "2.0V/s" cmbRate.AddItem "4.0V/s" cmbRate.AddItem "8.0V/s" cmbRate.AddItem "16.0V/s" cmbRate.AddItem "32.0V/s" cmbRate.AddItem "64.0V/s" cmbRate.AddItem "128.0V/s" cmbRate.AddItem "256.0V/s" cmbRate.AddItem "512.0V/s" cmbRate.AddItem "1024.0V/s" cmbRate.ListIndex = 0 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件 ' 定时器被激活后 会不断地执行此部份的程序代码 ' 判断输出值被变更后 就将输出电压的指令送到模块 ' 并由模块读回现在的模块输出值 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, ValueStr As Single, Pos1% 第 272 页,共 606 页 Dim RetBuf$ If cmdOpenCOM.Caption = "开 启通讯端口" Then lblMsg.Caption = "尚未开启通讯端口!" Exit Sub End If Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If lblValue.Caption = Slider1.Value / 10 & "V" '判断数值发生改变 若改变才令其输出 避免多余的动作 If PreVoltage <> Val(lblValue.Caption) Then MSComm1.Output = "#" & Buf & Format(Slider1.Value / 10, "00.000") & Chr(13) '组合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) PreVoltage = Val(lblValue.Caption) If RetBuf = "" Then lblMsg.Caption = "输出电压失败 " Exit Sub End If End If lblMsg.Caption = "联机" & Buf & "中 " MSComm1.Output = "$" & Buf & "8" & Chr(13) '读回现在的模拟输 出值 RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Or Len(RetBuf) < 9 Then lblMsg.Caption = "读回失败 " Exit Sub End If lblReadBack.Caption = Val(Mid(RetBuf, 4, 6)) & "V" End Sub Module 中的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input 第 273 页,共 606 页 Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 7-3 模拟输入和模拟输出 7012D 主司模拟输入现况的读取 而 7021 则是作电压或电流的 输出 这二个模块是相反的二个动作 本节将就这二个模块作项目 讨论部份程序的写作 7-3-1 模拟输入转模拟输出 7012D 模块用于作模拟值的读取 在 7012 该章也对模拟输入作 了详细的讨论 在前几小节中则是对 7021 模块作输出控制的讨论 本小节将作一个项目 针对 7012D 的输入结果转 7021 作模拟输出 而二者均以图形绘出 使我们明白模拟输入和模拟输出之间的关 系 在上一小节中讨论到电压改变率 设定不同的电压改变率可以 使得输出的电压改变依着必要的状况 本小节也将测试不同的电压 改变率对于模拟输入的反应如何 接下来就是项目的设计 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其也 的属性均维持默认值 3、 安排四个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”cmbCOM” ”cmb7012” ”cmb7021”及 ”cmbRate” 作为通 讯端口信道 7012 模块编号 7021 模块编号及电压改变率的选 择区 4、 安排一个图片框控件 拉至适当的大小 按下 F4 叫出属性窗口 将其 Name 属性设成 picVoltage 而 BackColor 属性值设成淡绿 色(或其它浅色系列) 其它属性维持默认值即可 所取得的模拟 输入值和取得的模拟输出值将显示在此图片框中 第 274 页,共 606 页5、 安排 8 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性分 别改为”通讯端口” ”7012D 站号” ”7021 站号” ”7021 电压改 变率” ”7012 读值” ”7021 输出值” ”0V” ”10V” 作为各控 件的标示之用 6、 安排二个 Label 控件 按下 F4 将其 Name 属性改为 lblLabel lbl7021 ForeColor 属性改为蓝色 作为 7012D 模拟读取值及 7021 模拟输出值的实时显示之用 7、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性 窗口 其 Name 属性改为”lblMsg” BorderStyle 选择”1-单线固定” BackColor 选择淡黄色 作为显示系统讯息之用 8、 安排一个定时器 按下 F4 叫出属性窗口 修改其 Interval 属性 为 100 定时器将以 100 毫秒的周期执行内部的程序代码 9、 安排三个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始侦测 结束 作为执行相对应的动作之用 10、 设计后之画面如图 7-3-1 所示 图 7-3-1 模拟输入转模拟输出画面设计 动作流程解析 程序的执行 我们希望使用者选择 COM 端口 7012 模块的编号 7021 模块的编号及 7021 模块的电压改变率 选定 后按下开启通讯端口 接着按下 开始侦测 的按钮 此时即开始侦 测 7012 的模拟输入值 一旦取得此值后 即刻将相等的模拟值利用 7021 输出 定时器中并将 7021 的实时输出电压状态取回 7012 的读 取和 7021 的现况取得之数值都绘至图片框中 利用不同的颜色及线 条 可以明显地看出 7012 和 7021 二者之间的关系 以下接着就是程序的安排 程序代码部份如下 第 275 页,共 606 页1、 双击窗体 在其 Load 事件中输入以下程序代码 MaxPlotNo = 100 '设定绘图的资料点数 cmb7012.Clear For i = 1 To 255 cmb7012.AddItem CStr(Hex(i)) cmb7021.AddItem CStr(Hex(i)) Next i cmb7012.ListIndex = 1 cmb7021.ListIndex = 2 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 '将电压改变率的选项先填入 Combo 控件 cmbRate.Clear cmbRate.AddItem "Immediate" cmbRate.AddItem "0.0625V/s" cmbRate.AddItem "0.125V/s" cmbRate.AddItem "0.25V/s" cmbRate.AddItem "0.5V/s" cmbRate.AddItem "1.0V/s" cmbRate.AddItem "2.0V/ s" cmbRate.AddItem "4.0V/s" cmbRate.AddItem "8.0V/s" cmbRate.AddItem "16.0V/s" cmbRate.AddItem "32.0V/s" cmbRate.AddItem "64.0V/s" cmbRate.AddItem "128.0V/s" cmbRate.AddItem "256.0V/s" cmbRate.AddItem "512.0V/s" cmbRate.AddItem "1024.0V/s" cmbRate.ListIndex = 0 cmdStart.Enabled = False '以下设定绘图范围 (Xmin,YMax)- (XMax,YMin) picVoltage.Scale (0, 10)- (MaxPlotNo, 0) picVoltage.DrawWidth = 1 '使用画笔为一个像素宽度 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 7012D 模块编号被我们预设成 02 因此在程序中 直接让它出现模块的号码为 2 7021 模块编号被我们预设成 03 因此在程序中直接让它出现模块的号码为 3 电压改变率预设 是 ”即刻改变” 由于还未开启通讯端口 因此 开始侦测 的 按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系 统讯息" Exit Sub 第 276 页,共 606 页 End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " '7021 的设定 '先将模块输出状态恢复到 0V 的位置 Buf = cmb7021.List(cmb7021.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Format(0, "00.000") & Chr(13) '组 合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) '改变 Slew Rate Rate = cmbRate.ListIndex RateBuf = Hex((Rate And 8) / 8 * 2 + (Rate And 4) / 4 * 1) & Hex((Rate And 2) / 2 * 8 + (Rate And 1) * 4) MSComm1.Output = "%" & Buf & Buf & "3206" & RateBuf & Chr(13) ' 组合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) picVoltage.Cls '清除图片框 NowX = 0 Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前 当然得先检查通讯端口号码是否落在 1~16(MSComm 控件仅 支持到 16 个通讯端口 一般情形已足够 )之间 接着就是设 定分布式模块所使用的 Settings 参数 再开启通讯端口 一 旦开启通讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 程序中也将模拟输出先设成 0V(使用的指令是#AA(Data) 而 改变率也设成 Combo 控件中的设定值( 使用的指令是 %AANNTTCCFF) 3、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按 钮 第 277 页,共 606 页使用者未改变通讯端口的号码时 此子程序就不需执行 当然 直接跳出去即可 这个部份的程序可以在使用者变更通讯端口 号码时 将通讯端口关闭 并将原先 Disable 的开启通讯端口 按钮重新 Enable 4、 双击定时器控件 在其 Timer 事件中输入以下的程序代码 Buf = cmb7012.List(cmb7012.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " Buf = WaitRS(MSComm1, vbCr, 1000) If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If ValueStr = Val(Mid(Buf, Pos1 + 1, 7)) '分离出正号以后的数值 lblValue.Caption = Format(ValueStr, "0.000") & "V" '显示在画面上 '以下是 7021 输出读回的动作 Buf = cmb7021.List(cmb7021.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Format(ValueStr, "00.000") & Chr(13) ' 组合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) MSComm1.Output = "$" & Buf & "8" & Chr(13) '读回现在的模拟输 出值 RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Or Len(RetBuf) < 9 Then lblMsg.Caption = "读回失败 " DAValue = 0 Else lbl7021.Caption = Val(Mid(RetBuf, 4, 6)) & "V" DAValue = Val(lbl7021.Caption) End If If NowX = 0 Then picVoltage.Cls '清除图形 picVoltage.PSet (0, ValueStr) '设定起点 picVoltage.PSet (0, DAValue) '设定起点 Else '以下判断现在的读值是否大于前一次的读值 若是 则以红色绘线 '若否 则以蓝色绘线 picVoltage.DrawStyle = vbDot picVoltage.Line (NowX - 1, Pre7021Value)- (NowX, DAValue), RGB(0, 0, 0) '由上一次的位置画至此点 picVoltage.DrawStyle = vbSolid 第 278 页,共 606 页 If ValueStr > PreVa lue + 0.01 Then picVoltage.Line (NowX - 1, PreValue)- (NowX, ValueStr), RGB(255, 0, 0) '由上一次的位置画至此点 Else picVoltage.Line (NowX - 1, PreValue)- (NowX, ValueStr), RGB(0, 0, 255) '由上一次的位置画至此点 End If End If PreValue = ValueStr '记住先前的值 Pre7021Value = DAValue '记住先前的值 NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 '超过范围则数值归零 数字输入的功能如果要不断地被执行的话 最方便的方法就是采 用定时器作周期性的程序执行 程序可以看出分成几个部份进行 首先是模拟输入的部份 利用 模拟输入指令(#AA)将 7012D 的模拟输入值传回后 解析其中的 模拟值 接下来使用模拟输出指令(#AA(Data))将取得的模拟输入 值由 7021 模块作输出的动作 输出完成后 随即读取此时 7021 中的实时电压输出值 取得的实时数值均被显示到画面上的 Label 控件 除了显示 Label 之外 也将数值绘到图片框 因此可由图片框中看到动作趋势 和之前的绘图程序较不同的是 此部份的绘图有二条线在其中 一条是模拟输入值 一条是 7021 的实时输出值 并且使用不同 的颜色表示 定时器就如此地周而复始地作 7012 的模拟值读取和 7021 的模拟 值输出 5、 双击 开始侦测 的按钮 在其 Click 事件中输入以下程序代码 Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" cmdOpenCOM.Enabled = True MSComm1.PortOpen = False cmdStart.Enabled = False lblMsg.Caption = "已停止侦测" End If 此按钮在被按下后 程序将定时器的状态转态 当欲停止侦测 的动作时 程 序会将开启通讯端口的控钮激活 通讯端口关闭 并将此按钮 Disable 掉 欲再一次侦测 必须再一次开启通讯端 口 及按下开始侦测的按钮才行 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 第 279 页,共 606 页 以上项目执行后 我们可以在画面中的通讯端口 站号 电压改 变率选择必要的设定 接着按下 开启通讯端口 开始侦测 的按钮 定时器随即被激活 实验的结果可能如图 7-3-2 所示 图 7-3-2 模拟输入转输出的执行结果 分析图 7-3-2 的执行结果 当选择”Immediate”时 利用可变电 阻 改变 7012D 的模拟输入值 程序会随即将此值送给 7021 作输出 由上图可以发现 虚线为 7021 的模拟输出情形 它的线形相当接 近 7012 的变化 也就是当 7012 的电压值发生改变时 7021 会以最 快的速度作输出反应 当选择的电压改变率是 0.5V/S 时 意味着 7021 以秒 0.5V 的速 度作电压输出 当 7012 的输入电压改变较快时(斜率增大) 7021 要追上就需要花上较多的时间 这也是图 7- 3-2 的下图所呈现出来 的情形 再次选择不同的电压改变率时 亦可看出其中的些微不同点 如图 7- 3- 3 所示 图 7- 3- 3 另二种电压改变率 第 280 页,共 606 页以上的程序代码 您可以参考光盘中 范例 \ 第七章 \ 7012 输入电压转 7021 输出电压 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的测试 完整的程序代码列表如下 Option Explicit Dim NowX As Integer '现在的 X 轴位置 Dim MaxPlotNo As Long '最长的 X 轴范围 Dim PreValue As Single '前一个量测值 Dim Pre7021Value As Single '前一个量测值 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯 端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按 钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 由于第一次开启 我们将输出的值设成 0V ' 改变率也依设定而变更 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() Dim Buf$, RetBuf$, Rate%, RateBuf$ '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系 统讯息" Exit Sub End If '激活错误侦测机制 第 281 页,共 606 页 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " '7021 的设定 '先将模块输出状态恢复到 0V 的位置 Buf = cmb7021.List(cmb7021.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Format(0, "00.000") & Chr(13) '组 合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) '改变 Slew Rate Rate = cmbRate.ListIndex RateBuf = Hex((Rate And 8) / 8 * 2 + (Rate And 4) / 4 * 1) & Hex((Rate And 2) / 2 * 8 + (Rate And 1) * 4) MSComm1.Output = "%" & Buf & Buf & "3206" & RateBuf & Chr(13) ' 组合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) picVoltage.Cls '清除图片框 NowX = 0 Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" cmdOpenCOM.Enabled = True MSComm1.PortOpen = False cmdStart.Enabled = False lblMsg.Caption = "已停止侦测" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 先将各个 Combo 控件中所需的选项填入 ' 由于预设的 7012 编号为第 2 号 7021 编号为第 3 号 故其 ListIndex 属 性 ' 均以此为默认值( 也可以线上被改变) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% MaxPlotNo = 100 '设定绘图的资料点数 第 282 页,共 606 页 cmb7012.Clear For i = 1 To 255 cmb7012.AddItem CStr(Hex(i)) cmb7021.AddItem CStr(Hex(i)) Next i cmb7012.ListIndex = 1 cmb7021.ListIndex = 2 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 '将电压改变率的选项先填入 Combo 控件 cmbRate.Clear cmbRate.AddItem "Immediate" cmbRate.AddItem "0.0625V/s" cmbRate.AddItem "0.125V/s" cmbRate.AddItem "0.25V/s" cmbRate.AddItem "0.5V/s" cmbRate.AddItem "1.0V/s" cmbRate.AddItem "2.0V/s" cmbRate.AddItem "4.0V/s" cmbRate.AddItem "8.0V/s" cmbRate.AddItem "16.0V/s" cmbRate.AddItem "32.0V/s" cmbRate.AddItem "64.0V/s" cmbRate.AddItem "128.0V/s" cmbRate.AddItem "256.0V/s" cmbRate.AddItem "512.0V/s" cmbRate.AddItem "1024.0V/s" cmbRate.ListIndex = 0 cmdStart.Enabled = False '以下设定绘图范围 (Xmin,YMax)- (XMax,YMin) picVoltage.Scale (0, 10)- (MaxPlotNo, 0) picVoltage.DrawWidth = 1 '使用画笔为一个像素宽度 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件引发后 就不断地执行其中的程序 ' 将模拟读值命令送出 再取得传回字符串并判断 ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, ValueStr As Single, Pos1% Dim DAValue As Single, RetBuf$ Buf = cmb7012.List(cmb7012.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Chr(13) '组合完整的命令字符串 lblMsg.Caption = "联机" & Buf & "中 " Buf = WaitRS(MSComm1, vbCr, 1000) If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") If Pos1 = 0 Then 第 283 页,共 606 页 lblMsg.Caption = "传回值不正确 " Exit Sub End If ValueStr = Val(Mid(Buf, Pos1 + 1, 7)) '分离出正号以后的数值 lblValue.Caption = Format(ValueStr, "0.000") & "V" '显示在画面上 '以下是 7021 输出读回的动作 Buf = cmb7021.List(cmb7021.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & Format(ValueStr, "00.000") & Chr(13) ' 组合完整的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) MSComm1.Output = "$" & Buf & "8" & Chr(13) '读回现在的模拟输 出值 RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Or Len(RetBuf) < 9 Then lblMsg.Caption = "读回失败 " DAValue = 0 Else lbl7021.Caption = Val(Mid(RetBuf, 4, 6)) & "V" DAValue = Val(lbl7021.Caption) End If If NowX = 0 Then picVoltage.Cls '清除图形 picVoltage.PSet (0, ValueStr) '设定起点 picVoltage.PSet (0, DAValue) '设定起点 Else '以下判断现在的读值是否大于前一次的读值 若是 则以红色绘线 '若否 则以蓝色绘线 picVoltage.DrawStyle = vbDot picVoltage.Line (NowX - 1, Pre7021Value)- (NowX, DAValue), RGB(0, 0, 0) '由上一次的位置画至此点 picVoltage.DrawStyle = vbSolid If ValueStr > PreValue + 0.01 Then picVoltage.Line (NowX - 1, PreValue)- (NowX, ValueStr), RGB(255, 0, 0) '由上一次的位置画至此点 Else picVoltage.Line (NowX - 1, PreValue)- (NowX, ValueStr), RGB(0, 0, 255) '由上一次的位置画至此点 End If End If PreValue = ValueStr '记住先前的值 Pre7021Value = DAValue '记住先前的值 NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 '超过范围则数值归零 End Sub Module 中的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do 第 284 页,共 606 页 DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 第 285 页,共 606 页本章习题 1、 试作一程序 使电压的输出由 0V 至 10V 再由 10V 回到 0V 间隔电压及时间自定 2、 同上题 使电流的输出由 4mA 至 20mA 再由 20mA 回到 4mA 间隔电压及时间自定 3、 参考 7-3-1 的项目程序 算出每次模拟输入的变化斜率 依 不同的斜率设定不同的电压改变率 4、 参考 7-3-1 的项目程序 算出输入与输出的误差值 并将此 误差值以不同的颜色绘在图中 第 286 页,共 606 页第八章 频率计数模块-7080D 8-1 模块介绍 在某些场合中 我们需要量测频率值或是计数 在 7000 系列模 块中 频率/计数量测模块便是编号 7080/7080D 的频率/计数模块 本模块用来量测频率或是计数之用 不管是频率或计数 二者 均是脉波型式的讯号 而 7080D 模块主要提供了二个信道的脉波输 入 另有二个通道的数字输出 原厂规格标示如表 8-1-1 所示 表 8-1-1 7080D 模块重要规格 i-7080: Counter/Frequency Module Counter Input l Channels: Two independents 32 bit counters, counter 0&1 l Input signal: Isolated or non-isolated programmable l Isolation input levels: Logic level 0: +1V max. Logic level 1: +3.5V to +30V l Isolation voltage: 3750 Vrms l non-isolation input threshold level: programmable Logic level 0: 0 to +5V (default = 0.8V) Logic level 1: 0 to +5V (default = 2.4V) l Maximum count: 32 bit (4,294,967,295) l Programmable digital noise filter: 2 us to 65 ms l Alarming: alarm on counter 0 or counter 0&1, programmable l Counter preset value: programmable Display l LED Indicator: 5-digit readout, channel 0 or channel 1 Frequency Measurement l Input frequency: 1Hz to 100K Hz max. l Programmable built-in gate time: 1.0/0.1sec Digital Output l 2 channels, open-collector to 30V, 30mA max. load l Power dissipation: 300mW Power l Power requirements: +10V to 30V(non-regulated) l Power consumption : 2W for I-7080, 2.2W for I-7080D 此模块提供了二个通道的频率/计数输入 其功能概分为频率输 入 计数输入 数字输出几个部份 8-1-1 谈谈规格 以下就针对此模块的相关重要规格描述如下 了解了相关的规 格才方便继续作其它的应用 首先是计数输入的部份 由规格上可以看出此模块的独立计数 输入通道有二个(分为隔离式与非隔离式 二者选其一) 分别命名 第 287 页,共 606 页为 Counter0 及 Counter1 这二个计数器都是 32 位 (4 个位组长度) 若采用隔离式的输入 则辑逻 0 的最高电压是 1V 而辑逻 1 的电 压则落于 3.5V~30V 若采用非隔离式的输入 则辑逻 0 与辑逻 1 的电压范围可以自行设定 模块在此情形下的预设辑逻 0 是 0.8V 以下 而辑逻 1 则是 2.4V 以上 Visual Basic 中的长整数也使用了 4 字节的长度作资料的储存 所能存放的最大数值是 2147483647 这与 7080 模块所提供的最大 计数值 4294967295 相差了 2 倍 此乃因模块使用了无号数的储存方 式 而 Visual Basic 中的数值是有号数的储存方式 为了避免噪声进入造成计数错误 模块提供 2 微秒~65 毫秒的 讯号滤波功能 可以依设定的频率滤除不必要的讯号 避开噪声 模块的自动数字输出警戒功能只适用在计数模式下 可利用程 序设定警戒的方式 频率输入的部份 此模式位于此模式下所能量测的最大频率是 100KHz 最小为 1Hz 此部份则不似计数功能般地拥有其它的滤波 电压设定 警戒等功能 数字输出的部份 若不当成计数模式下的警戒功能 则该二通 道可当成一般的数字输出通道使用 共有二个数字输出的通道可以 使用 采用的是开集极式的输出 最大的使用负载是 30V 及 30mA 模块所使用的电压是直流+10V~+30V 使用的理由是模块使用 的场合电压通常会有一些变化 不见得很稳定 有了宽广的电源允 许范围也可以保护模块运作正常 8-1-2 7080D 的外观及脚位定义 7080D 的外观如图 8-1-1 所示 图中的右方讯号接点为隔离端的 输入 而左方的讯号接点则是非隔离式的输入 第 288 页,共 606 页I - 7 0 8 0 D 20 DO1/HIIN0 DO0/LOGate0 IN0+D.GND IN1+(Y)DATA+ (G)DATA- IN0- IN1 IN1- Gate0+Gate1 Gate1+(R)+Vs Gate0-INIT* Ga te1 -(B )GND 1 图 8-1-1 7080D 模块外观图 以上的各脚位 分成几个部份说明如下 1、 电源 标明了(R)+Vs 及 (B)GND 10 的二支脚在上图的左下方处 分别是正电源及接地线 正电源必须接上+10V~+30V 之间的电 压 笔者的建议是使用由原厂所提供的变压器 或是独立的稳压 变压器 计算机内部虽然有+12V 的电源可以使用 不过 笔者 试过之后 感觉不是很稳定 所以还是建议读者使用独立的电源 较佳 2、 讯号传输 上图标明(Y)DATA+及 (G)DATA-的二支脚位紧接在 电源接脚的上方 RS-485 网络所使用的网络线就是接在这儿 需特别注意的是 所有 485 网络上的 DATA+必须接在一起 而 DATA- 也必须接在一起(否则讯号会错乱) 二个以上的分布式模 块之电源及讯号接线示意图如下 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 ( Y)DATA+ ( G)DATA - ( R) +Vs ( B)GND 10 3、 模块初始 上图标明 INIT*的脚位紧接在讯号线接脚的旁边 此脚位用来对模块作初始化时之用 当模块在必须初始化时 就 可以将此脚和 GND 脚位接在一起 模块内的设定就可以回到默 认值 另外 部份对于模块的特别设定也会使用到这支脚位 第 289 页,共 606 页4、 数字输出 在图 8- 1-1 右上方有二个接点 标明了 DO1/HI 及 DO0/LO 此二接点有二个不同的用途 一般当作用数字输出之 用 可由主控计算机的程控其输出与否 另一个用途则是将输出 控制权交由 7080D 模块 当输入的计数值在超过一定的设定值 时 模块即控制这二个数字输出 作为警戒输出 这二个输出脚 位使用的是开极集式的输出方式 在后文会有详细的说明 5、 IN0+ IN0- 在图 8- 1- 1 右边的这二个接点 用于接上当成输 入的正负讯号 此接点具有隔离作用 我们需要自行接通电路使 用讯号进入 6、 GATE0+ GATE0- 这个接点用来控制模块是否激活计数 程 序可以控制模块依据此部份的电位高低来决定由 IN0+ IN0-通 道进入的讯号是否继续计数的工作 7、 IN1+ IN1- 和 IN0+ IN0-相同的意义 乃是另一个输入的通 道 8、 GATE1+ GATE1- 和 GATE0+ GATE0-相同的意义 乃 IN1+ 及 IN1- 讯号通道的控制所在 9、 IN0 GATE0 位于图 8- 1-1 的左方 属于非隔离式的输入端 这二个接点分别是讯号的输入及控制端点 意义和隔离式输入类 似 只不过此讯号和模块是共地的 10、 D.GND 输入讯号的地接点 此接点将会和模块的地线相接 11、 IN1 GATE1 和 IN0 GATE0 意义相同 乃是另一个非隔离 型讯号输入点及控制点 此模块的内部电路方块图如图 8-1-2 所示 第 290 页,共 606 页 图 8-1-2 7080D 内部电路方块图 8-1-3 和 7080D 取得联机与沟通 和第五章所提到的 7060D 一样 欲和 7080D 模块通讯 首先使 用个人计算机 I- 7520 及 I- 7080D 连接如图 8- 1- 3 图 8- 1- 3 主控计算机和 7080D 间的联机 其中个人计算机和 7520 之间的接线方法是使用 RS- 232 线 (9 公 -9 母 不需跳线 )作为连接 一端接到 7520 上 另一端接到计算机上 的 COM1 或 COM2(如果计算机有扩充的 COM 的话 也可以接到其 它的 COM) 至于 7080D 和 7520 间的接线 再放大显示图 8- 1- 4 第 291 页,共 606 页 图 8- 1- 4 7520 与 7080D 电源及讯号之连接图 I-7080D 与 I-7080 在功能上是完全相同的 不同的地方是 I-7080D 多了 5 位 LED 用来显示输入的电压值 在第五章针对 7060D 模块讨论时 曾使用一个简单的程序作为 计算机和模块之间通讯的范例 现在一样可以使用该项目由计算机 传送指令给 7080D 模块 由于分布式模块的基础在于每一个网络上的模块都有一个地 址 透过不同的地址可以控制不同的模块 在第五章中的 7060D 模 块所使用的模块地址是”1” 7012D 是 ”2” 7021 是 ”3” 为了区别 及未来整合实验方便 在此将 7080D 模块的地址设定为”4” 至于 设定模块地址的方式 读者可以使用 5-1-3 节项目的程序 对出厂 预设的 7080D 下达修改地址的指令 也可以透过原厂所提供的公用 程序来对模块地址编号作修改 而笔者是比较建议使用公用程序 (第一次还是使用原厂的东西比较安心) 使用 5-1-3 的项目程序 将项目开启并按下 F5 后开始执行 在 命令区文字框中输入指令”$042” 接着按下 读取资料 的按钮 传回的讯息如图 8- 1- 5 第 292 页,共 606 页 图 8-1-5 7080D 的组态指令及传回结果 由于分布式模块所传回的组态讯息格式是相类似的 在第五章中也 针对 7060D 模块作过讨论 读者可以参考 5- 1-3 节的解释 以下仅 就 7080D 模块的特有部份作说明 !04510600 相对于$042 所传回的执行结果 此结果显示出 7080D 模块的组态情形 当模块收到指令后会将对应的结果传回 分析此 字符串 !04 中的!是表示模块已接收到的指令是一个正确的指 令 可以被该模块执行 而 04 则表示模块编号为第 4 号 51 指的是模块使用的输入型态是频率输入 模块的输入型态以不同的 字符表示 如表 8- 1- 2 所示 表 8- 1- 2 型态码与量测类别 TT Input Range 50 Counter 51 Frequency 由表 8-1- 2 可以了解到 7080D 模块所提供的输入型态 使用者可依 不同的情形选用不同的型态 06 指的是使用的串行通讯传送速 度是 9600bps 最后的 00 字符串可以对应到 8 个位 每个字符 可由 0 变化到 F 左边的字符所对应的是由位 7~位 4 而右边的位 则可以对应到位 3~位 0 对应的关系如图 8- 1- 6 图 8- 1- 6 字符与位位置的对应 第 293 页,共 606 页图 8- 1- 6 中的各位之代表意义如下 Bit7 固定为 0 Bit6 此位用于表示模块是否激活 CheckSum 若为 0 表示不激活 1 表示激活 Bit5 Bit4 Bit3 固定为 0 Bit2 若设为 0 表示使用 0.1 秒的闸控时间(Gate Time) 若设为 1 表示使用 1 秒的闸控时间 此功能用于频率量测时 指定计 算频率所用的时间宽度 Bit1 Bit0 固定为 0 模块的组态除了可以取得之外 也可以透过命令字符串而将其 更改 例如我们希望将模块量测的型态设定为计数 使用的指令格 式是”%AANNTTCCFF” 由模块手册可以知道 AA 指的是地址 依我们现在的情形就是 04 NN 指是是新的模块编号 由于不打算 改变其编号 故此值仍设为 04 TT 指的是量测型态设定码 参考 表 8-1- 2 TT 字符串应设为 50 CC 是通讯速度 保持 06 即可 FF 就是组态设定码(参考图 8-1- 6) 所有位均设为 0 即可 故 FF 必须 写成 00 到此已经将这次要作的设定所需的字符串找出 接下来依 然使用 5- 1-3 的项目程序 在命令区文字框中输入”%0404500600” 接着按下 送出指令 按钮 再按下 读取数据 的按钮 若操作 顺利 应出现如图 8- 1-7 中的上图结果(!02 表示指令正确被执行) 而再一次使用”$042”的指令检查模块的组态 经由传回的字符 串 ”!04500600” 也可以知道 模块组态的确是被改变了 如图 8- 1-7 第 294 页,共 606 页图 8- 1- 7 改变组态的指令及组态检查结果 上述的组态改变测试完成后 我们可以再使用”%0404510600” 将组态改回原来的样子 若传回值是”!04” 表示改回来的动作指令 也被成功地执行 8-2 计数/频率输入 在 遇到的工业场合中 计数/频率也是经常会碰到的参数之一 计数的功能在计算机中欲被执行 其实是由计算机或芯片去侦测某 一个讯号的改变速度 在第五章也说明过 计算机只知道电位的高 低 状态的改变 其余的一概需要作转换才行 因此改变速度就是 高低电位的变化速度或是次数 当侦测的是高低电位的改变次数 时 就是量测计数值 而当侦测的是高低电位的改变速度时 就是 量测频率值 8-2-1 讯号的型式 为了让模块可以侦测讯号改变的速率 讯号本身需有一定的要 求 一般说来 讯号的高电位状态存在一次 接着低电位出现一次 二者合起来形成一次讯号 而高低电位的定义则可以依模块或是量 测设备而定 通常以 0V 作为低电位 而以 5V 作为高电位 以 7080D 模块而言 它允许讯号是隔离式或非隔离式二种方式 当选择讯号 以隔离式的方式进入模块时 高电位必须在 3.5V~30V 之间 而低 电位则必须在 1V 以下 若选择非隔离式的讯号输入方式 则高电 位和低电位均可由使用者利用程序予以设定 预设的高电位是 2.4V 以上 而低电位是 0.8V 以下 8-2-2 输入量测的接线方式 隔离式输入的方法使用时机 基本上是外界讯号不和模块共地 时使用 其原理接法如图 8- 2- 1 第 295 页,共 606 页 图 8-2-1 7080D 于 隔离式输入时的接线原理图 IN0+是外部讯号的正端输入 IN0-则是外界讯号的负端输入 当讯号在正负端形成后 正负端压降足以推动发光二极管时 光敏 晶体管即作动 而导致接控制器的线路电平发生变化 讯号输入若 持续振荡 则讯号形成如图 8- 2- 2 的形式 图 8- 2- 2 讯号振荡图 7080D 模块会侦测图 8-2-2 的讯号中有几次的高低电位变化 并 将此值储存在模块中等待主控计算机读取 由于使用的是隔离式的接线方法 因此模块端可以得到保护 当讯号突然有一个的很大的噪声进入的话 可以因此而得以保护 RS-485 网络 但 若输入端的接地线或是电源和模块之间是相通的 话 模块就无法得到保护了 此点必须注意 非隔离式的输入方法直接将讯号的正端接到模块上标明为 IN0 或 IN1 的接脚 而负端接到标明为 D.GND 接脚即可 非隔离式的 方式乃是讯号和模块共享地线 因此直接接到讯号接入脚位即可 由于非隔离式 所以不具保护作用 第 296 页,共 606 页8-2-3 实验讯号产生电路 欲进行计数值的量测 首先就得先让时脉的讯号进到 7080D 模 块 图 8- 2- 3 是一个简单的时脉产生电路设计图 +5V ~ +15V Output U2 NE555 TR2 CV5 Q 3 DIS 7 THR 6 R 4 VCC 8 GND 1 R2 5K C1 0.01uF SW1 1RSW2 C2 1uF C3 0.1uF R1 20K 图 8- 2- 7 模拟输入的实验电路图 使用 NE555 这颗 IC 的原因是其产生方波相当地简单 而在厂 商的产品说明书中都有这颗 IC 的应用电路 很适合我们拿来作实 验 部份比较重要的地方说明如下 1、 使用的电压范围相当广 在 +5V~+15V 之间都可以 2、 输出脚位在第 3 脚 根据 IC 说明书 其输出为 0V 到 5V 的方 波 3、 由于使用不同的电容和电阻会产生不同的方波频率 所以 R1 采 用一个 20K 欧姆的可变电阻 并以 SW1 的开关变换电容值 电 容值愈小 产生的频率愈高 不同的电阻组合及所产生的频率 间的关系如图 8-2- 8 所示(取自 HARRIS 公司 CA555 说明档) 由 图中可以看出 变更 SW1 所 连接的电容可以让产生的频率不同 因此实验板使用了 1u 和 0.1u 的电容 另外 也可以由可变电阻 的改变造成 R1 R2 的组合数值改变 达到变化频率的目的 这 二种改变频率的方式最大的不同在于 使用电阻时 改变的频 率范围较小 而切换电容时 改变的频率范围则较大 我们可 依需求选取不同的范围作实验 4、 若使用的电源不容易找 可接模块上的电源来使用 不过须先 经 7808 的转换 将模块的正 24V 电源转成正 8V 的电源(也可用 7805 或 7812 转成 5V 12V) 如图 8- 2- 8 右边图形所示 第 297 页,共 606 页5、 接地的部份和模块为同一个 因此实验的进行将以非隔离式为 主 图 8- 2- 8 SW1 及 R1 和 R2 组合与频率间的关系 IC 接脚的号码是由 IC 上视图看过去 以 U 型凹处的左方开 始往下计数 直到底部再到右方往上计数 如图 8-2-9 左图 而 7808 的外观则如图 8-2-9 右图 图 8- 2- 9 IC 接脚号码的排列(左图为一般 IC 右图为 77808) 由于 7080D 模块拥有二组输入 因此将图 8-2-7 的线路图复制之后 就可以形二个波形产生电路 分别可以接到通道 1 和通道 2 笔者 也依上述的电路及事项作出了一个实验板电路 如图 8-2-10 第 298 页,共 606 页 图 8-2-10 7080D 的实验板 实验板相关接脚及组件说明如下 1、 电源由分布式模块分接过来 故使用一个 7080 作电压转换 7080 上使用了散热片 可避免其因负载电压而太烫 2、 实验板中的 CF1/CF2 接点分别是第一个通道和第二个通道的输 出脚位 其输出的频率(由图中的二颗 NE-555 产生)分别由图中 下方的 VR1 及 VR2 二个可变电阻作控制 改变可变电阻的同时 也就改变了输出频率 3、 SW1 及 SW2 用来改变输出频率的范围 可以让 NE-555 的输出 有二个不同的范围 便于我们作高低频率的实验 4、 LO HI 接脚用来连接 7080 模块上的 LO HI 脚位 作为一般 的数字输出或是警戒输出 以下的各小节实验都将以此实验板作为讯号来源及输出对象 来进行 试试看此模块的频率及计数功能 当读者将实验板完成后 第一个要作的事情就是检查板上的线路是否正确 而且必须使用电 表量一下电源和地线是否为短路(此点非常重要) 然后才能使用此 实验板作实验 第 299 页,共 606 页8-2-4 计数值的读取 上一小节已发展了一个简单的实验电路板 功能已经可以达到 作实验的目的 而实作上也不是很因难 本小节首先来读取模块所 记录的计数值 实验之前必须确定所有的线路已经都接到 7080 模块上的相关 接脚 也 作了电源 地线的短路检查 由于 7080 模块具有频率及计数二种功能 现在我们要执行其 中的计数功能时 必须将模块上的设定改为计数选项 更改模块的 设定使用的组态指令是”%AANNTTCCFF” 由 8-1-3 节的讨论 我 们必须设定的组态为 站号是 4 维持站号不变(故 AANN 为 0404) 使用计数模式(故 TT 为 50) 传输速度保持 9600bps(故 CC 为 06) 不使用 CheckSum 功能(故 FF 为 00) 另外将会使用的指令说明如 下 $AA6N 将模块中的计数器读值清空 AA 为地址站号 N 为 0 或 1(表示第 0 个计数器或第 1 个计数器) 假设站号为 4 号的 情形下 清除第 0 个计数器使用的字符串指令为”$0460” 而清除第 1 个计数器使用的字符串指令则为”$0461” #AAN 要求模块传回计数值 AA 为地址站号 N 为 0 或 1(表示第 0 个计数器或第 1 个计数器) 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其它 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属性为”cmbCOM”及 ”cmbNO” 作为通讯端口信道及侦测模块 站号的选择区 4、 安排二个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 改为”????” Name 属性为 lblValue 按出 Font 属性 并将字号 设为 18 颜色设为蓝色 读者亦可使用其它的显示设定 5、 安排 4 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为”通讯端口” ”站号” ”计数值 0” ”计数值 1” 作为 各控件的标示之用 第 300 页,共 606 页6、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性 窗口 其 Name 属性改为”lblMsg” BorderStyle 选择”1- 单线固 定 ” BackColor 选择淡黄色 作为显示系统讯息之用 7、 安排三个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始侦测 结束 作为执行相对应的动作之用 8、 安排一个定时器控件 其 Interval 属性设定 100 而 Enabled 属 性设定 False 9、 设计后之画面如图 8-2-11 所示 图 8-2-11 计数值读取画面设计 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 当一切选择妥当后 按下开启通讯端口的按钮 接着激活定时 器 而定时器就依 Interval 属性值一直执行其内部的程序代码 其程 序代码即是不断地对 7080D 模块送出询问二个计数器读值的指令 接着处理传回的指令结果 显示模块计数值在画面上 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 3 '预设的站号为 4 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 '预设为 COM1 cmdStart.Enabled = False 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 并令通讯端口选项的 ListIndex 为 0 让选项留在 第 301 页,共 606 页第一个项目上 由于还未开启通讯端口 因此 开始侦测 的 按钮先行 Disable 掉 模块选项的 ListIndex 则留在第 4 个 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '变更型态为计数模式 使用%AANNTTCCFF 指令 MSComm1.Output = "%" & Buf & Buf & "50" & "06" & "00" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 MSComm1.Output = "$" & Buf & "60" & vbCr '将通道 0 的读值清除 RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 MSComm1.Output = "$" & Buf & "61" & vbCr '将通道 1 的读值清除 RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前 当然得先检查通讯端口号码是否落在 1~16(MSComm 控件仅 支持到 16 个通讯端口 一般情形已足够)之间 接着就是设 定分布式模块所使用的 Settings 参数 再开启通讯端口 一 旦开启通讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 此程序中也针对 7080D 模块的组态作了设定( 使用 %AANNTTCCFF 指令) 将其模式设定在计数模式(Type=50) 程序中也使用$AA6N 的指令将通道0 和通道 1 计数值先清空 3、 双击 开始侦测 的按钮 在其 Click 事件中输入以下程序代码 Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" 第 302 页,共 606 页 Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If 此按钮在被按下后 程序将定时器的状态转态 原来激活的话 就将其暂停 而原来暂停的话 就将其激活 透过定时器状态 的检查 也动态地改变此按钮上显示的文字 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序循环就不需执行 当然直接跳出去即可 这个部份的程序可以在使用者变更通讯 端口号码时 将通讯端口关闭 并将原先 Disable 的开启通讯 端口按钮重新 Enable 5、 双击定时器控件 在其 Timer 事件中输入以下程序代码 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & CStr(CounterCh) & Chr(13) '要求 计数值回传 lblMsg.Caption = "联机" & Buf & "中 " Buf = WaitRS(MSComm1, vbCr, 1000) '取回结果 If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") '文件头位置 Pos2 = InStr(1, Buf, vbCr) '文件尾位置 If Pos1 = 0 Then '若文件头字符不在 表示传值失败 lblMsg.Caption = "传回值不正确 " Exit Sub End If CounterStr = Mid(Buf, Pos1 + 1, Pos2 - (Pos1 + 1)) '分离出计数值的字 符串 CounterStr = "&H" & CounterStr & "&" '将其变成长整数型式的字符串 CounterValue = Val(CounterStr) '转换为实际的数值 '若小于 0 必须作修正 If CounterValue < 0 Then CounterValue = 2147483648# * 2# + CounterValue End If lblValue(CounterCh).Caption = CounterValue '显示在画面上 CounterCh = (CounterCh + 1) Mod 2 '下一个通道 第 303 页,共 606 页 这段程序是真正传送命令及接收传回的循环 程序中使用 Output 属性将命令送出 模块指令规定地址需使用二个字符表示 因此 程序也作了判断 指令一经送出后 程序作了一个之前讨论的 WaitRS 函式接收传回值 要求计数值传回的指令是#AAN 其中的 N 为通道号码 可为 0 或 1 程序中透过静态变量 CounterCh 记录通道 由静态变量的 特性 当此程序循环第一次进入时 CounterCh 为 0 进行到最 后 透过 CounterCh=(CounterCh+1) Mod 2 的处理 除了将数值 加 1 外 (故 CounterCh 此时为 1) 也除以 2 后取其余数(余数为 1) 如此一来 原来 CounterCh 变成 1 程序也会记住这个 CounterCh 的值 因此下一次的 Timer 事件程序执行时 将会以此新值执行 直到再一次执行到 CounterCh=(CounterCh+1) Mod 2 时 CounterCh 为 2 但在 Mod 处理后 余数为 0 又再一次回到之 前的状态 如此周而复始 由于传回的计数值(以十六进制格式传回)是以”>”符号作为前 导 又以 vbCr 作为结束 程序接下来就寻找”>”及 vbCr 字符的 所在位置 程序中撷取此二个字符之间的计数值字符串 并 将其 转换为 Double 数值 在此必须说明的是有关的十六进制数值转换为十进制数值的方 法 (1) 首先是传回的数值字符串是十六进制表示的 须先将此字符 串的前后加上代表十六进制数值的符号 字符串头的&H 表示 是十六进制 而字符串尾的&表示是长整数 这一前一后的字 符串加入 将会使得原来的字符串成为长整数字符串 接下 来使用 Val 函式将此字符串转换为数值即可 (2) 转换为数值后 程序又判断了数值是否小于 0 若小于 0 则 将数值加上 2147483648# * 2# 在 Visual Basic 中的长整数为 4 个位组长度 其记录的范围是- 2147483648 至 2147483647 而模块中的数值虽然也是 4 个字节 但其最高的记录数值却 是 Unsigned Long(不带正负号长整数) 当记录的最高数值超 过 2147483647 时 使用(1)中的方式转换为数值会使得数值变 成负数 故必须加上此一转换程序 避开此问题 完成转换的数值直接显示到画面上即可 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 定 时 器随即被激活 定时器的侦测结果可能如图 8-2-12 所示 第 304 页,共 606 页 图 8-2-12 读计数值程序的执行结果 程序执行后 转动 7080D 实验板的二个可变电阻 画面上的计数 值就会一直改变(递增) 同时 在 7080D 模块本身的 LED 显示器上 也会出现其中一个通道的后 5 位数值 我们可以比较一下二者是否 一致 实验时需特别注意到模块的编号是 04 很多时候会忘掉 同时 通讯端口也得看看自己计算机上所用的通讯端口来决定 以上的程序代码 您可以参考光盘中 范例 \ 第八章 \ 计数 值的读取 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的 测试 完整的程序代码列表如下 Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End 第 305 页,共 606 页End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() Dim Buf$, RetBuf$ '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '变更型态为计数模式 使用%AANNTTCCFF 指令 MSComm1.Output = "%" & Buf & Buf & "50" & "06" & "00" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 MSComm1.Output = "$" & Buf & "60" & vbCr '将通道 0 的读值清除 RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 MSComm1.Output = "$" & Buf & "61" & vbCr '将通道 1 的读值清除 RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmd Start.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If End Sub 第 306 页,共 606 页''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口号码及站号填入 Combo 控件 ' 预设通讯端口的选项是第一个 ' 预设站号的选项是第四个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 3 '预设的站号为 4 号 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件引发后 就不断地执行其中的程序 ' 将读值命令送出 再取得传回计数字符串并判断其值 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, CounterValue As Double Dim CounterStr As String Dim Pos1%, Pos2% Static CounterCh% Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & CStr(CounterCh) & Chr(13) '要求 计数值回传 lblMsg.Caption = "联机" & Buf & "中 " Buf = WaitRS(MSComm1, vbCr, 1000) '取回结果 If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") '文件头位置 Pos2 = InStr(1, Buf, vbCr) '文件尾位置 If Pos1 = 0 Then '若文件头字符不在 表示传值失败 lblMsg.Caption = "传回值不正确 " Exit Sub End If CounterStr = Mid(Buf, Pos1 + 1, Pos2 - (Pos1 + 1)) '分离出计数值的字 符串 CounterStr = "&H" & CounterStr & "&" '将其变成长整数型式的字符串 CounterValue = Val(CounterStr) '转换为实际的数值 '若 小于 0 必须作修正 If CounterValue < 0 Then CounterValue = 2147483648# * 2# + CounterValue End If 第 307 页,共 606 页 lblValue(CounterCh).Caption = CounterValue '显示在画面上 CounterCh = (CounterCh + 1) Mod 2 '下一个通道 End Sub Module 中的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 8-2-5 频率读值及绘图显示 前一小节将计数值由 7080D 模块传回 本小节将把模块内的所 到的频率值传回 而除了显示在画面上之外 也将会将数值绘制到 图片框中 对于绘图的相关程序语法及函式 请参考 6- 2-5 小节的 说明 7080D 模块可用于量测计数或是频率 我们要执行其中的频率 功能时 必须将模块上的设定改为频率选项 更改模块的设定使用 的组态指令是”%AANNTTCCFF” 由 8-1- 3 节的讨论 我们必须设 定的组态为 站号是 4 维持站号不变(故 AANN 为 0404) 使用频 率模式(故 TT 为 51) 传输速度保持 9600bps(故 CC 为 06) 不使用 第 308 页,共 606 页CheckSum 功能(故 FF 为 00) 将组态指令字符串传给 7080D 模块后 即使用此模块作频率侦测之用 接下来是将 7080D 的频率读值绘制到图片框中 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其它 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属性为”cmbCOM”及 ”cmbNO” 作为通讯端口信道及侦测模块 站号的选择区 4、 安排二个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 改为”????” 按出 Font 属性 并将字号设为 18 颜色设为蓝色 作为二个频率读值的显示区 读者亦可使用其它的显示设定 5、 安排二个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 改为”????” 按出 Font 属性 并将字号设为 12 作为图片框中 的 Y 轴最大及最小值的显示之用 6、 安排一个定时器控件 按下 F4 叫出其属性窗口 变更 Interval 属性为 100 当定时器被激活后以 100 毫秒的时间间隔执行程 序 7、 安排 4 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为”通讯端口” ”站号” ”现在读值(Ch0)” ”现在读值 (Ch1)” 作为标示各控件之用 8、 安排二个图片框控件 拉至适当的大小 按下 F4 叫出属性窗口 一个的 Visible 属性设成 True 另一个设成 False 另将其 Name 属性分别设成 picShow 及 picHide 而 BackColor 属性值设成淡 绿色(或其它浅色系列) 其它属性维持默认值即可 这二个图 片框将分别用来作为前景的显示和背景的绘图之用 将二个图 片分开处理可以达到不闪烁的目的 9、 安排一个 Label 控件 按下 F4 叫出属性窗口 作为讯息显示处 10、 安排三个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始侦测 结束 作为执行相对应的动作之用 11、 设计后之画面如图 8-2-13 第 309 页,共 606 页 图 8-2-13 频率值绘图项目的设计画面 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 当一切选择妥当后 按下 开启通讯端口 的按钮 接着按下 开始侦测 激活定时器 而定时器就依 Interval 属性值一直执行其 内部的程序代码 其程序代码是不断地对 7080D 模块送出询问频率 读值的指令(通道 0 及通道 1) 接着处理传回的指令结果 显示二个 通道的频率输入值在画面上 也将取得的频率值以图形的方式显示在 画面上 处理绘图时 通道 0 与通道 1 使用不同的颜色线段(一为红 色 一为蓝色)予以表示 让使用者容易了解整个的频率变化趋势 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 '将数组重新定义 第一个索引表通道 第二个索引表绘图序次 ReDim PlotValue(0 To 1, 0 To MaxPlotNo) cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 3 '预设的模块编号为第 4 个 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False fIsOver = False '设定初值 MinValue = 99999 '设定 MaxY 初值 MaxValue = 0 '设定 MinY 初值 第 310 页,共 606 页通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 令模块选项的 ListIndex 为 3 让选项留在第四个 项目上(因为我们预设 7080D 的模块编号是 04) 通讯端口的预 设 ListIndex 则设为 0(使用 COM1)即可 由于还未开启通讯端 口 因此 开始侦测 的按钮先行 Disable 掉 其中设定图片 框的高度及宽度初值(此时的 ScaleMode 是 Pixel) 也设定了用 来绘图及显示用的 Y 轴之最大及最小值 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '变更型态为频率模式 使用%AANNTTCCFF 指令 MSComm1.Output = "%" & Buf & Buf & "51" & "06" & "00" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当 然得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持 到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布 式模块所使用的 Settings 参数 再开启通讯端口 一旦开启通 讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 其中并将组态指令(%AANNTTCCFF)设为”%0404510600” 指 定为频率模式 3、 双击 开始侦测 的按钮 在其 Click 事件中输入以下程序代码 Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then 第 311 页,共 606 页 cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If 此按钮在被按下后 程序将定时器的状态转态 原来激活的话 就将其暂停 而原来暂停的话 就将其激活 透过定时器状态 的检查 也动态地改变此按钮上显示的文字 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序循环就不需执行 当然直接跳出去即可 这个部份的程序可以在使用者变更通讯 端口号码时 将通讯端口关闭 并将原先 Disable 的开启通讯 端口按钮重新 Enable 5、 双击定时器控件 在其 Timer 事件中输入以下程序代码 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "#" & Buf & CStr(FreqCh) & Chr(13) '要求频率 值回传 lblMsg.Caption = "联机" & Buf & "取值中 " Buf = WaitRS(MSComm1, vbCr, 1000) If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") '文件头位置 Pos2 = InStr(1, Buf, vbCr) '文件尾位置 If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If FreqStr = Mid(Buf, Pos1 + 1, Pos2 - (Pos1 + 1)) '分离出计数值的字符 串 FreqStr = "&H" & FreqStr & "&" '将其变成长整数型式的字符串 FreqValue = Val(FreqStr) '转换为实际的数值 lblValue(FreqCh).Caption = FreqValue & " Hz" '将频率显示在画面 上 PlotValue(FreqCh, NowX) = FreqValue '将值记录到数组中 '绘图上下范围设定 If FreqValue > MaxValue Then MaxValue = FreqValue 第 312 页,共 606 页 If FreqValue < MinValue Then MinValue = FreqValue If MaxValue <= MinValue Then MaxValue = MinValue + 10 lblY(0).Caption = MinValue lblY(1).Caption = MaxValue If FreqCh = 1 Then '取了二个通道的值后再进入绘图子程序 '以下设定绘图范围 (Xmin,YMax)- (XMax,YMin) picHide.Scale (0, MaxValue)- (MaxPlotNo, MinValue) picHide.DrawWidth = 1 '使用画笔为一个像素宽度 Plot NowX '进入绘图子程序 '快速图形拷贝函式 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, picHide.hDC, 0, 0, &HCC0020) NowX = NowX + 1 '位置加 1 End If If NowX > MaxPlotNo Then NowX = 0 fIsOver = True End If FreqCh = (FreqCh + 1) Mod 2 '下一个通道 传值指令格式及传回字符串的解析请参阅上一小节的说明 和上 一小节一样 传回的数值会显示在画面上 由于 7080D 模块拥有 二个通道 当二个通道的数值均取过后 我们才让程序进入绘图 的循环中(Plot) 当绘图完成后 程序则使用 API 函式中的 BitBlt 将 picHide 中的图形快速地拷贝到 picShow 中 此函式的说明请 详见 6- 2- 6 小节 绘图的子程序(Plot)另外撰写 其中的程序只作数值的绘制 如 下 picHide.Cls '清除图形 For j = 0 To 1 '二个通道 If fIsOver Then k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终 点 '当已超过后 就以最大点数作为绘图的终点( 此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) '指定绘图颜色(j=0 时使用红色 否则使用蓝色) ShowColor = IIf(j = 0, RGB(255, 0, 0), RGB(0, 0, 200)) picHide.PSet (0, PlotValue(j, 0)), ShowColor For i = 1 To EndPos picHide.Line - (i, PlotValue(j, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i Next j 二个通道的数值使用二条不同的颜色绘制 其中使用的 IIF 指令 有三个参数 第一参数是比较式 当此比较式成立(True)时 就 第 313 页,共 606 页以第二个参数作为传回值 反之 若比较式不成立(False)时 就 以第三个参数作为传回值 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 转动 可变电阻 实验板上的输出频率会改变 定时器亦会被激活 定时 器的频率侦测结果可能如图 8-2-14 所示 图 8-2-14 频率读值绘图执行结果 以上的程序代码 您可以参考光盘中 范例 \ 第八章 \ 频率 值的读取及绘图 档案夹中的项目 双击 Ex.vbp 项目档 即可进行 以上的测试 完整的程序代码列表如下 Option Explicit Dim NowX As Long '现在的 X 轴位置 Dim MaxPlotNo As Long '最长的 X 轴范围 Dim PreValue As Single '前一个量测值 Dim PlotValue() As Single '读值数组 通道 0 Dim fIsOver As Boolean '是否超过绘图点数的旗标 Dim MinValue As Single, MaxValue As Single '最大及最小数值的记录变 量 Dim P1Width&, P1Height& '绘图的图片框的范围记录 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() 第 314 页,共 606 页 '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端 口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() Dim Buf$, RetBuf$ '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '变更型态为频率模式 使用%AANNTTCCFF 指令 MSComm1.Output = "%" & Buf & Buf & "51" & "06" & "00" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 315 页,共 606 页' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 预设通讯端口的选项是第一个 ' 而模块的预设地址站号则是第 4 号 ' 程序中也针对部份参数作初值设定 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 '将数组重新定义 第一个索引表通道 第二个索引表绘图序次 ReDim PlotValue(0 To 1, 0 To MaxPlotNo) cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 3 '预设的模块编号为第 4 个 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 cmdStart.Enabled = False fIsOver = False '设定初值 MinValue = 99999 '设定 MaxY 初值 MaxValue = 0 '设定 MinY 初值 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件引发后 就不断地执行其中的程序 ' 将模拟读值命令送出 再取得传回字符串并判断 ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim i&, j& Dim Buf$, FreqValue As Double Dim FreqStr As String Dim Pos1%, Pos2% Static FreqCh% Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf 第 316 页,共 606 页 End If MSComm1.Output = "#" & Buf & CStr(FreqCh) & Chr(13) '要求频率 值回传 lblMsg.Caption = "联机" & Buf & "取值中 " Buf = WaitRS(MSComm1, vbCr, 1000) If Buf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, Buf, ">") '文件头位置 Pos2 = InStr(1, Buf, vbCr) '文件尾位置 If Pos1 = 0 Then lblMsg.Caption = "传回值不正确 " Exit Sub End If FreqStr = Mid(Buf, Pos1 + 1, Pos2 - (Pos1 + 1)) '分离出计数值的字符 串 FreqStr = "&H" & FreqStr & "&" '将其变成长整数型式的字符串 FreqValue = Val(FreqStr) '转换为实际的数值 lblValue(FreqCh).Caption = FreqValue & " Hz" '将频率显示在画面 上 PlotValue(FreqCh, NowX) = FreqValue '将值记录到数组中 '绘图上下范围设定 If FreqValue > MaxValue Then MaxValue = FreqValue If FreqValue < MinValue Then MinValue = FreqValue If MaxValue <= MinValue Then MaxValue = MinValue + 10 lblY(0).Caption = MinValue lblY(1).Caption = MaxValue If FreqCh = 1 Then '取了二个通道的值后再进入绘图子程序 '以下设定绘图范围 (Xmin,YMax)- (XMax,YMin) picHide.Scale (0, MaxValue)- (MaxPlotNo, MinValue) picHide.DrawWidth = 1 '使用画笔为一个像素宽度 Plot NowX '进入绘图子程序 '快速图形拷贝函式 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, picHide.hDC, 0, 0, &HCC0020) NowX = NowX + 1 '位置加 1 End If If NowX > MaxPlotNo Then NowX = 0 fIsOver = True End If FreqCh = (FreqCh + 1) Mod 2 '下一个通道 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 绘图的子程序 ' 二个通道的值取后即进行图形的绘制 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Plot(StartX&) Dim i&, j&, k& Dim ShowColor& Dim EndPos& picHide.Cls '清除图形 For j = 0 To 1 '二个通道 If fIsOver Then k = StartX + 1 '第一个值 Else 第 317 页,共 606 页 k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终 点 '当已超过后 就以最大点数作为绘图的终点( 此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) '指定绘图颜色(j=0 时使用红色 否则使用蓝色) ShowColor = IIf(j = 0, RGB(255, 0, 0), RGB(0, 0, 200)) picHide.PSet (0, PlotValue(j, 0)), ShowColor For i = 1 To EndPos picHide.Line - (i, PlotValue(j, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i Next j End Sub Module 中的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, _ ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, _ ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, _ ByVal ySrc As Long, ByVal dwRop As Long) As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 第 318 页,共 606 页 8-3 数字输出及警戒 7080D 中含有二个通道的数字输出 此二个输出通道也可以当 作警戒之用 本节将就此部份予以说明 7080D 所使用的数字输出 的原理和 7012D 模块相同 读者可直接参阅 6- 3-1 小节的说明 本 节直接跳到程序的撰写 8-3-1 数字输出的控制 在 7080D 模块的使用场合中 计数与频率均可以控制数字输出 的状态 计数模式下还具有警戒功能 本小节将使用的数字输出指 令说明如下 @AADO0D 此为 7080D 模块的数字输出指令 其中的 AA 指的是 模块地址 可以由 00~FF DO 固定 而指令中的最后一个 D 则有如表 8- 3-1 所示的几种情形 给定不同的字符 就会 产生不同的数字输出结果 DO0 及 DO1 分别是位 0 及位 1 若该位为 ON 其所产生的数值可用 2 的 0 次方和 2 的一次 方得之 所得的值就是 1 和 2 读者可试算 此 1 和 2 的组 合就可以得到表 8-3- 1 的结果 若该位为 OFF 当然就是 了 @AADI 此为 7080D 模块的数字输出状态查询指令 其中的 AA 指 的是模块地址 可以由 00~FF 此指令在正常执行下的传回 字符串是!AAS0D00 其中的 D 为数字输出状态码 对应值 如表 8- 3-1 所示 S 为警戒状态码 待警戒小节时再予以说 明 依表 8- 3-1 的定义 程序上可以使用 AND 运算判断该 位是否为 1 例如 欲判断 DO0 位是否为 1 只要将取得的 数值和 的 次方作 AND 运算 结果若为 的 次方 即 表示该位为 ON 反之 该位为 OFF 同理 取得的数值和 的 次方作 AND 运算 结果若为 的 次方 即表示该 位为 ON 反之 该位为 OFF 表 8- 3- 1 字符 D 所代表的几种数字输出方式 输出情形 D 字符 DO0 DO1 第 319 页,共 606 页0 OFF OFF 1 ON OFF 2 OFF ON 3 ON ON 数字输出的功能在警戒功能开启时将会失效 因此欲使用程 控数字输出时 必须先将警戒功能关闭 接下来就是数字输出项目的设计 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其也 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”cmbCOM”及”cmbNO” 作为通讯端口信道及侦测模块站号 的选择区 4、 安排四个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgRed” ”imgDisable” ”imgON” ”imgOFF” 而在其各 别的 Picture 属性中选择项目目录下的 Red.jpg Disable.jpg ON.jpg OFF.jpg, 作为显示输出及输入状态之用 另外也要将其 Visible 属性设为 False 令其执行时不显示 5、 另外再安排四个 Image 控件 按下 F4 叫出其属性窗口 变更其 中二个的 Name 属性为”imgOut” 另外二个设为”imgOutStatus” 均设成数组(Index 由 0~1) 调整适当大小并排列之 此四个 Image 控件的 Visible 属性要设成 True 6、 安排 8 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性分 别改为”通讯端口” ”站号” ”数字输出控制” ”数字输出状态” 以及二组的”DO0” ”DO1” 作为各控件的标示之用 7、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性窗 口 其 Name 属性改为”lblMsg” BorderStyle 选择”1-单线固定” BackColor 选择淡黄色 作为显示系统讯息之用 8、 安排一个定时器 按下 F4 叫出属性窗口 修改其 Interval 属性为 100 定时器将以 100 毫秒的周期执行内部的程序代码 9、 安排二个按钮控件 其 Caption 属性分别输入 开启通讯端口 结束 作为执行相对应的动作之用 10、 设计后之画面如图 8-3-1 所示 第 320 页,共 606 页 图 8-3-1 数字输出控制的设计画面 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 当一切选择妥当后 按下开启通讯端口的按钮 接着激活定时 器 而定时器就依 Interval 属性值一直执行其内部的程序代码 其程 序代码即是不断地对 7080D 模块送出询问数字输出状态的指令 并 处理传回的指令结果 显示输出入状态在画面上 当此动作完成后 检查数字输出控制的开关是否被按下 并将输出结果以数字输出指令 送出 若一切正常 我们按下数字输出的按钮时 除了开关会被按下 外 输出状态的灯号也会产生变化 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 '初始状态先将开关状态及灯号状态设关闭 For i = 0 To 1 imgOut(i).Picture = imgOFF.Picture imgOutStatus(i).Picture = imgDisable.Picture Next i '填入站号 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 3 '填入通讯端口 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 显示用的开关及灯号先设为关闭的状态 而通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 此模块 编号通常会被我们设成 04 因此在程序中直接让它出现模块的 第 321 页,共 606 页号码为 4 由于还未开启通讯端口 因此 开始侦测 的按钮 先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '检查是否开关通讯端口 If cmdOpenCOM.Caption = "关闭通讯端口" Then MSComm1.PortOpen = False cmdOpenCOM.Caption = "开启通讯端口" Exit Sub End If '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Caption = "关闭通讯端口" lblMsg.Caption = "可按下 ON- OFF 开 关 执行 DO 的控制工作 " '以下主要是关闭警戒功能 避免干扰数字输出功能 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "%" & Buf & Buf & "500600" & vbCr '设成 Counter 模式 RetBuf = WaitRS(MSComm1, vbCr, 1000) '传回值 MSComm1.Output = "~" & Buf & "A0" & vbCr '模式 0 的警戒型态 RetBuf = WaitRS(MSComm1, vbCr, 1000) '传回值 MSComm1.Output = "@" & Buf & "DA0" & vbCr '关闭通道 0 的警戒 RetBuf = WaitRS(MSComm1, vbCr, 1000) '传回值 MSComm1.Output = "@" & Buf & "DA1" & vbCr '关闭通道 1 的警戒 RetBuf = WaitRS(MSComm1, vbCr, 1000) '传回值 Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前 当然得先检查通讯端口号码是否落在 1~16(MSComm 控件仅 支持到 16 个通讯端口 一般情形已足够)之间 接着就是设 定分布式模块所使用的 Settings 参数 再开启通讯端口 一 旦开启通讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 通讯端口开启后 使用%AANNTTCCFF 指令将模 块设成计数模式 而由于警戒功能的开启将会影响到数字输 出的操作 因此在此程序的后段部份是关闭警戒功能(使用到 第 322 页,共 606 页二种指令 分别是~AAA0 及 @AADAN 详见下一小节的说明) 避免干扰的出现 3、 双击 imgOut 的控件(任何一个均可) 在其 Click 事件中输入以下 程序代码 '将记录转态 并改变 ON- OFF 按钮的显示 DOStatus(Index + 1) = Not DOStatus(Index + 1) If DOStatus(Index + 1) Then imgOut(Index).Picture = imgON.Picture Else imgOut(Index).Picture = imgOFF.Picture End If 程序使用 DOStatus 数组变量记录现在的输出状态 而每一次使 用者按下控件时 就将状态作改变(原来是 ON 的 就将它变成 OFF 原来是 OFF 的 就将它变成 ON) 并且将其中的 Picture 属性作变更 以符合现在的 ON/OFF 状态 在此并末作直接的 输出指令句柄 笔者将此部份的程序和数字输入的侦测放在一 起 4、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的 按钮 使用者未改变通讯端口的号码时 此子程序就不需执行 当然 直接跳出去即可 这个部份的程序可以在使用者变更通讯端口 号码时 将通讯端口关闭 并将原先 Disable 的开启通讯端口 按钮重新 Enable 5、 双击定时器控件 在其 Timer 事件中输入以下的程序代码 '若通讯端口尚未开启 不可送出数据 If cmdOpenCOM.Caption = "开启通讯端口" Then Exit Sub COMBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(COMBuf) = 1 Then COMBuf = "0" & COMBuf End If MSComm1.Output = "@" & COMBuf & "DI" & Chr(13) '组合完整的命 令字符串 lblMsg.Caption = "侦测" & COMBuf & "数字输入/ 事件中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " Else Buf = Mid(RetBuf, 6, 1) '取出代表数字输入状态的字符 If (Val(Buf) And 1) = 1 Then '第 0 个数字输出的检查 imgOutStatus(0).Picture = imgRed.Picture 第 323 页,共 606 页 Else imgOutStatus(0).Picture = imgDisable.Picture End If If (Val(Buf) And 2) = 2 Then '第 1 个数字输出的检查 imgOutStatus(1).Picture = imgRed.Picture Else imgOutStatus(1).Picture = imgDisable.Picture End If End If '以下是数字输出的部份 '计算输出值 DOut = 0 For i = 1 To 2 If DOStatus(i) Then DOut = DOut + 2 ^ (i - 1) Next i MSComm1.Output = "@" & COMBuf & "DO" & Format(DOut, "00") & Chr(13) '组合完整的命令字符串 Buf = WaitRS(MSComm1, vbCr, 1000) '此行在此项目中可省略 If Buf = "" Then lblMsg.Caption = "指令的执行结果错误 " Else lblMsg.Caption = "指令成功 " End If 程序可以看出分成二个部份进行 首先是数字输出状态的检查部 份 利用数字输出/警戒检查指令(@AADI)将传回模块状态传回 后(传回字符串格式为!AAS0D00) 解析第 6 个字符(使用 AND 运 算 ) 便可得而模块的数字输出状态 再来是数字输出控制的部 份 使用者可以从画面上改变 ON-OFF 的开关状态 而状态会被 记录到数组中 在定时器中的第二部份就是将此数组状态作输出 (输出指令格式为@AADO0D) 定时器就如此地周而复始地作数字输入的侦测及数字输出的控 制 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 注意要将实验板上的数字输出接脚和模块上的数字输出接脚 连接妥当 以上项目执行后 我们可以在画面中的通讯端口及站号 选择必要的设定 接着按下 开启通讯端口 的按钮 定时器随即 被激活 定时器的侦测结果可能如图 8-3-2 所示 第 324 页,共 606 页 图 8-3-2 数字输出控制的执行结果 图 8-3-2 的执行即是命令第 0 个数字输出作动 而数字输出状态的 灯号则是显示出侦测到数字输出呈现 DO0 是 ON 的状态 以上的程序代码 您可以参考光盘中 范例 \ 第八章 \ 数 字输出控制 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上 的测试 完整的程序代码列表如下 Option Explicit ' 宣告记录四个数字输出状态的数组变量 Dim DOStatus(1 To 4) As Boolean ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub If Not MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通 讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允 许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 第 325 页,共 606 页' 将通讯控件的参数设定好 并开启 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() Dim Buf$, RetBuf$ '检查是否开关通讯端口 If cmdOpenCOM.Caption = "关闭通讯端口" Then MSComm1.PortOpen = False cmdOpenCOM.Caption = "开启通讯端口" Exit Sub End If '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Caption = "关闭通讯端口" lblMsg.Caption = "可按下 ON- OFF 开关 执行 DO 的控制工作 " '以下主要是关闭警戒功能 避免干扰数字输出功能 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If MSComm1.Output = "%" & Buf & Buf & "500600" & vbCr '设成 Counter 模式 RetBuf = WaitRS(MSComm1, vbCr, 1000) '传回值 MSComm1.Output = "~" & Buf & "A0" & vbCr '模式 0 的警戒型态 RetBuf = WaitRS(MSComm1, vbCr, 1000) '传回值 MSComm1.Output = "@" & Buf & "DA0" & vbCr '关闭通道 0 的警戒 RetBuf = WaitRS(MSComm1, vbCr, 1000) '传回值 MSComm1.Output = "@" & Buf & "DA1" & vbCr '关闭通道 1 的警戒 RetBuf = WaitRS(MSComm1, vbCr, 1000) '传回值 Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 预设 COM 选项是第一个 ' 而 7080 模块的预设站号则是第 4 个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% For i = 0 To 1 imgOut(i).Picture = imgOFF.Picture imgOutStatus(i).Picture = imgDisable.Picture Next i cmbNO.Clear For i = 1 To 255 第 326 页,共 606 页 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 3 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当代表数字输出的 Image 控件被选中时触发此事件 ' 将被选中的 DO 作转态 ' 转态后将数字输出的决定送至模块执行 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub imgOut_Click(Index As Integer) '将记录转态 并改变 ON- OFF 按钮的显示 DOStatus(Index + 1) = Not DOStatus(Index + 1) If DOStatus(Index + 1) Then imgOut(Index).Picture = imgON.Picture Else imgOut(Index).Picture = imgOFF.Picture End If End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器不断地执行 其将传回数字输出的状态供检视之用 ' 数字输出的指定也会在此事件循环中执行 ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim COMBuf$, Buf$, DOut%, i%, RetBuf$ '若通讯端口尚未开启 不可送出数据 If cmdOpenCOM.Caption = "开启通讯端口" Then Exit Sub COMBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(COMBuf) = 1 Then COMBuf = "0" & COMBuf End If MSComm1.Output = "@" & COMBuf & "DI" & Chr(13) '组合完整的命 令字符串 lblMsg.Caption = "侦测" & COMBuf & "数字输入/ 事件中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then '若传回字符串不正确 则显示灰色灯号及讯息 lblMsg.Caption = "未传回正确讯息 " Else Buf = Mid(RetBuf, 6, 1) '取出代表数字输入状态的字符 If (Val(Buf) And 1) = 1 Then '第 0 个数字输出的检查 imgOutStatus(0).Picture = imgRed.Picture Else imgOutStatus(0).Picture = imgDisable.Picture End If If (Va l(Buf) And 2) = 2 Then '第 1 个数字输出的检查 imgOutStatus(1).Picture = imgRed.Picture Else imgOutStatus(1).Picture = imgDisable.Picture End If End If 第 327 页,共 606 页 '以下是数字输出的部份 '计算输出值 DOut = 0 '设定初值 For i = 1 To 2 If DOStatus(i) Then DOut = DOut + 2 ^ (i - 1) Next i MSComm1.Output = "@" & COMBuf & "DO" & Format(DOut, "00") & Chr(13) '组合完整的命令字符串 Buf = WaitRS(MSComm1, vbCr, 1000) '此行在此项目中可省略 If Buf = "" Then lblMsg.Caption = "指令的执行结果错误 " Else lblMsg.Caption = "指令成功 " End If End Sub 模块内的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 8-3-2 计数值与警戒输出 如上一小节项目所述 数字输出的功能可以由程序予以控制 但 7080D 模块的数字输出还有一个相当重要的功能 就是提供警戒 输出 而警戒输出就是使用数字输出的二个通道 警戒主要用于让 模块本身具备判断状态 并随时反应状态及控制的能力 第 328 页,共 606 页在第六章说明 7012D 模块时 该模块本身也提供了警戒输出的 功能 在不需要程序判断的情形下 由模块直接控制数字输出的通 道 而 7080D 模块用于量测计数输入值及频率输入值 警戒输出若 要由模块自行控制 只能在计数模式下使用此功能 频率模式下则 否 分布式的架构中必须使用主控计算机对所有的分散模块作命 令的输出和状态的读入 当主控计算机正在处理其它的事务或当机 时 若此时的模拟值发生了变化 而此变化必须马上被反应出来 主控计算机就无法作出立即的反应 也有可能因此错失一个时间 点 因此本模块的数字输出通道就在此时派上用场 我们可以利用 此二个通道设定计数值必须落在一定的数值以下 若超出所设定的 数值则作数字输出 可藉此输出而达到通知 转控制其它设备的目 的 7080D 在计数模式下所提供的警戒功能有二种模式(Alarm Mode 0 Alarm Mode 1) 模式 0 将数字通道 0(DO0) 数字通道1(DO1) 分别分配给计数通道 0(CH0)和计数通道 1(CH1) 程序可分别设定 每个通道的警戒值 当通道中的计数值超过时 相对应的数字输出 通道即被激活(High Alarm 产生) 若未超过 数字输出则保持为 OFF 警戒模式 1 则只能用于计数通道 0 但通道 0 可以设定二阶段 的警戒值 第一段称为 High Alarm 当计数值超过此值时 DO0 会 被激活 第二段称为 High- High Alarm 当计数值超过此值时 DO1 会被激活 警戒模式 1 时的计数值和数字通道的关系如表 8- 3- 2 所 示 表 8- 3- 2 警戒模式 1 时的情形 DO0 状态 DO1 状态 计数值 0=High Alarm ON OFF 计数值 0>=High- High Alarm ON ON 本小节将使用到的指令说明如下 ~AAAS 设定计数模式下的警戒模式 前二个 AA 为 地址 我们将 7080D 预设为 04 故 AA 为 04 S 为 0 时表示设定为警戒模 式 0 则二个数字输出通道平均分配给二个计数通道 S 为 1 时表示设定为警戒模式 1 二个数字输出通道均给计数通道 0 使用 第 329 页,共 606 页@AAEAN 在警戒模式 0 之下将警戒功能激活 前二个 AA 为地址 我们将 7080D 预设为 04 故 AA 为 04 N 可为 0 或 1 表示 激活第 0 个通道或第 1 个通道的警戒 @AADAN 在警戒模式 0 之下将警戒功能关闭 前二个 AA 为地址 我们将 7080D 预设为 04 故 AA 为 04 N 可为 0 或 1 表示 关闭的是第 0 个通道或第 1 个通道 @AAEAT 在警戒模式 1 之下将警戒功能激活 前二个 AA 为地址 我们将 7080D 预设为 04 故 AA 为 04 T 可为字符 M 或字符 L M 表示瞬间(Momentary) 警戒发生于计数值超过时 但 当计数值下降(例如将计数值归零)时 警戒状态将取消 L 表 示闩锁(Latch) 警戒发生后 相对应的通道即一直激活 计 数值下降后仍是激活状态 除非程序下指令取消 @AADA 在警戒模式 1 之下将警戒功能关闭(数字通道 0 1 均关 闭 ) 前二个 AA 为地址 我们将 7080D 预设为 04 故 AA 为 04 @AACA 在警戒模式 1 之下清除被闩锁住的警戒状态 前二个 AA 为地址 我们将 7080D 预设为 04 故 AA 为 04 $AA6N 清除计数器读值及溢位旗标 使其重新计数 前二个 AA 为地址 我们将 7080D 预设为 04 故 AA 为 04 N 可为 0 或 1 表示清除的是第 0 个通道或第 1 个通道 @AAPA(Data) 在警戒模式 0 之下设定通道 0 的警戒值 或是在模 式 1 之下设定通道 0 的第一段警戒值(High Alarm) 前二个 AA 为地址 我们将 7080D 预设为 04 故 AA 为 04 (Data) 的格式是 8 个字符表示成的十六进制数值 @AASA(Data) 在警戒模式 0 之下设定通道 1 的警戒值 或是在模 式 1 之下设定通道 0 的第二段警戒值(High- High Alarm) 前 二个 AA 为地址 我们将 7080D 预设为 04 故 AA 为 04 (Data) 的格式是 8 个字符表示成的十六进制数值 数值格式的讨论 设定计数警戒值时 使用@AAPA(Data)或 @AASA(Data)时 其中的 Data 格式是十六进制表示的数值 由于 7080D 模块使用 4 个字节记录计数值 故以十六进制来表示时共有 8 位数 也因此指令字符串中使用 8 个字符作数值传送 十六进制 第 330 页,共 606 页的表示方法适合用在指令字符串的传送 可是一般却是以十进制的 数值比较容易了解和感觉 在 Visual Basic 中一个十进制的数值可 以很方便地用 Hex 函式将其转为十六进制的数值 而且直接就是字 符串的格式 由数值格式中可以看出 除了要以十六进制表示数值 外 这些数值字符串的总数还必须保持为 8 个字符 欲达到此要求 最简单的方式当然就是检查字符串数 接着在此字符串的前面加上 字符”0”即可 若以程序方法为之 以下是一个例子 Buf2 = Hex(Value1) '转换为字符串 Buf2 = Space(8 - Len(Buf2)) & Buf2 '补 上空白 Buf2 = Replace(Buf2, Chr(32), "0") '将空白变成 0 以上的三行程序里 欲传送的数值是 Value1 第一行使用 Hex 函式 转换为十六进制的数值字符串 接下来把字符串中不足 8 个字符数 的前方全部加上空格符 再使用替代函式 把空格符取代为”0”字 符 这样就完成了十进制到十六进制固定数值字符串长度的转换 一般的十进制数值的处理中 可以使用 Format 函式达到固定格式 的输出 而十六进制中无法使用 Format 函式 而使用上述的三个 式子也可以达到相同的效果 接下来就是项目的设计 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其也 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”cmbCOM”及”cmbNO” 作为通讯端口信道及侦测模块站号 的选择区 4、 安排二个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgRed” ”imgGreen” 而在其各别的 Picture 属性中选择 项目目录下的 Red.jpg Green.jpg 作为显示输出及输入状态之 用 另外也要将其 Visible 属性设为 False 令其执行时不显示 5、 安排二个 Image 控件 其成为物数组 按下 F4 叫出其属性窗口 变更 Name 属性为”imgAlarm” 此二个 Image 控件的 Visible 属 性要设成 True 作为高低警戒显示用灯号 6、 安排二个文字框 同样使其成为对象数组 按下 F4 叫出属性窗 口 予其 Name 属性中输入”txtHighAlarm” 作为警戒值设定的 输入区 7、 安排一个 Frame 控件 按下 F4 叫出属性窗口 设定其 Caption 属性为”现在读值” 另在其内部安排二个 Label 控件 使其成为 第 331 页,共 606 页对象数组 在其 Name 属性输入”lblValue” 作为计数值的显示区 8、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性窗 口 其 Name 属性改为”lblMsg” BorderStyle 选择”1-单线固定” BackColor 选择淡黄色 作为显示系统讯息之用 9、 安排一个定时器 按下 F4 叫出属性窗口 修改其 Interval 属性为 100 Enabled 属性设为 False 定时器被激活后 将以 100 毫秒 的周期执行内部的程序代码 10、 安排一个 Check Box 控件 按下 F4 叫出属性窗口 将其 Name 属性改为 chkHHAlarm 并将 Caption 属性设为”激活 HH 警戒” 作为是否激活模式 1 警戒的选项 11、 安排 8 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为” 通讯端口” ” 站号” ” 警戒设定-1” ” 警戒设定 -2” ”Alarm-0” ”Alarm- 1” ”CH0” ”CH1” 作为各控件的标 示之用 12、 安排 5 个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始侦测 结束 清除 CH0 读值 清除 CH1 读值 作 为执行相对应的动作之用 13、 设计后之画面如图 8-3-3 所示 图 8-3-3 计数值与警戒的设计画面 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 另外也让使用者可以设定警戒 并以一个 CheckBox 决定是否 激活模式 1 的警戒(预设是模式 0 的警戒) 当一切选择及设定妥当后 按下开启通讯端口的按钮及开始侦测的按钮 激活定时器后 其程序 代码会不断地对 7080D 模块送出询问二个计数通道数值的指令 接 着处理传回的指令结果 显示输入的计数值到画面上 当按下显示下 第 332 页,共 606 页方的清除指令时 会将模块的计数值清除 并重新开始计数 当计数 值超过所设定的警戒值时 数字输出通道即会作相对应的输出 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 3 '预设的站号为 4 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 '预设为 COM1 cmdStart.Enabled = False 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 此模块编号通常会被我们设成 04 因此在程序中 直接让它出现模块的号码为 4 由于还未开启通讯端口 因此 开始侦测 的按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 ' 判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable Buf = cmbNO.List(cmbNO.ListIndex) '若地址只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '变更型态为计数模式 使用%AANNTTCCFF 指令 MSComm1.Output = "%" & Buf & Buf & "50" & "06" & "00" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 第 333 页,共 606 页 使用通讯端口的先决条件就是必须将通讯端口开启 开启前 当然得先检查通讯端口号码是否落在 1~16(MSComm 控件仅 支持到 16 个通讯端口 一般情形已足够)之间 接着就是设 定分布式模块所使用的 Settings 参数 再开启通讯端口 一 旦开启通讯端口后 就关闭此按钮 以避免使用者按二次 造成错误 由于我们将要进行计数值的读取 因此使用组态指令将模式 设定在计数模式 3、 双击 清除 CH0 读值 的按钮控件 在其 Click 事件中输入以下 程序代码 fClearCH(Index) = True 将旗标设成 True 使系统得知必须清除计数值 并重新开始计 数 真正的清除动作程序整合在定时器中执行 4、 双击 开始侦测 的按钮控件 在其 Click 事件中输入以下程序 代码 Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '设定计数值从 0 开始 MSComm1.Output = "$" & Buf & "60" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) MSComm1.Output = "$" & Buf & "61" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) If chkHHAlarm.Value = 1 Then 'Alarm Mode 1 '设定型态为 Alarm 1 MSComm1.Output = "~" & Buf & "A1" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '设定警戒 MSComm1.Output = "@" & Buf & "EAM" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '设定 HIGH ALARM 数值 Buf2 = Hex(Val(txtHighAlarm(0).Text)) '转换为字符串 Buf2 = Space(8 - Len(Buf2)) & Buf2 '补上空白 Buf2 = Replace(Buf2, Chr(32), "0") '将空白变成 0 MSComm1.Output = "@" & Buf & "PA" & Buf2 & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '设定 HIGH- HIGH ALARM 数值 Buf2 = Hex(Val(txtHighAlarm(1).Text)) '转换为字符串 Buf2 = Space(8 - Len(Buf2)) & Buf2 '补上空白 Buf2 = Replace(Buf2, Chr(32), "0") '将空白变成 0 MSComm1.Output = "@" & Buf & "SA" & Buf2 & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) Else 'Alarm Mode 0 '设定型态为 Alarm 0 MSComm1.Output = "~" & Buf & "A0" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) 第 334 页,共 606 页 'CH- 0 MSComm1.Output = "@" & Buf & "EA0" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '设定 Counter0 ALARM 数值 Buf2 = Hex(Val(txtHighAlarm(0).Text)) '转换为字符串 Buf2 = Space(8 - Len(Buf2)) & Buf2 '补上空白 Buf2 = Replace(Buf2, Chr(32), "0") '将空白变成 0 MSComm1.Output = "@" & Buf & "PA" & Buf2 & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) 'CH- 1 MSComm1.Output = "@" & Buf & "EA1" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '设定 HIGH- HIGH ALARM 数值 Buf2 = Hex(Val(txtHighAlarm(1).Text)) '转换为字符串 Buf2 = Space(8 - Len(Buf2)) & Buf2 '补上空白 Buf2 = Replace(Buf2, Chr(32), "0") '将空白变成 0 MSComm1.Output = "@" & Buf & "SA" & Buf2 & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) End If Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If 程序一开始就使用”$AA6N”指令将计数值先清空 使其重新计 数 接下来的警戒设定分成二种情形 一为模式 0 一为模式 1 这二种模式使用的指令不完全相同 故分成二个程序部份处理 警戒值的设定中使用的设定字符串在数值部份必须固定 8 个字 符 相关处理请参阅本小节 数值格式讨论 部份的说明 5、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub MSComm1.PortOpen = False '关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序就不需执行 当然 直接跳出去即可 这个部份的程序可以在使用者变更通讯端口 号码时 将 通讯端口关闭 并将原先 Disable 的开启通讯端口 按钮重新 Enable 6、 双击定时器控件 在其 Timer 事件中输入以下的程序代码 COMBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(COMBuf) = 1 Then COMBuf = "0" & COMBuf End If MSComm1.Output = "#" & COMBuf & CStr(CounterCh) & Chr(13) ' 要 求计数值回传 第 335 页,共 606 页 lblMsg.Caption = "联机" & COMBuf & "中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, RetBuf, ">") '文件头位置 Pos2 = InStr(1, RetBuf, vbCr) '文件尾位置 If Pos1 = 0 Then '若文件头字符不在 表示传值失败 lblMsg.Caption = "传回值不正确 " Exit Sub End If CounterStr = Mid(RetBuf, Pos1 + 1, Pos2 - (Pos1 + 1)) '分离出计数值 的字符串 CounterStr = "&H" & CounterStr & "&" '将其变成长整数型式的字符串 CounterValue = Val(CounterStr) '转换为实际的数值 '若小于 0 必须作修正 If CounterValue < 0 Then CounterValue = 2147483648# * 2# + CounterValue End If lblValue(CounterCh).Caption = CounterValue '显示在画面上 CounterCh = (CounterCh + 1) Mod 2 '下一个通道 '读取警戒输出现在的状态 MSComm1.Output = "@" & COMBuf & "DI" & Chr(13) '组合警戒状 态的命令字符串 lblMsg.Caption = "侦测" & COMBuf & "警戒状态中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then '若传回字符串不正确 则显示讯息 lblMsg.Caption = "未传回正确状态讯息 " Else Buf = Mid(RetBuf, 6, 1) '取出代表数字输入状态的字符 If (Val(Buf) And 1) = 1 Then '第 0 个数字输出的检查 imgAlarm(0).Picture = imgRed.Picture Else imgAlarm(0).Picture = imgGreen.Picture End If If (Val(Buf) And 2) = 2 Then '第 1 个数字输出的检查 imgAlarm(1).Picture = imgRed.Picture Else imgAlarm(1).Picture = imgGreen.Picture End If End If '以下检查清除计数值的旗标 For i = 0 To 1 If fClearCH(i) Then '下达清除的指令 MSComm1.Output = "$" & COMBuf & "6" & CStr(i) & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) fClearCH(i) = False '清除完后再将旗标改回 False 表示不清除 End If Next i 程序可分成三个部份来讨论 第一部份是读值的部份 使 用”#AAN”指令取得通道的计数值 接着对此取的字符串作分离 处理 找出所需的数值 第二部份是数字输出状态检查的部份 第 336 页,共 606 页由于警戒的功能交由模块自由运作 故使用”@AADI”指令取得数 字输出状态 并将此状态显示在画面上 第三部份则是清除计数 值的部份 程序允许使用者清除计数值 程序一检查到旗标为 True 时 即使用”$AA6N”对该通道作清除计数值的动作 7、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必 要的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 定 时器随即被激活 定时器的所取得的模拟输入结果及警戒状态值可 能如图 8-3-4 所示 图 8-3-4 计数值与警戒项目的执行结果 以上的程序代码 您可以参考光盘中 范例 \ 第八章 \ 计 数值与警戒输出-计数 档案夹中的项目 双击 Ex.vbp 项目档 即 可进行以上的测试 完整的程序代码列表如下 Option Explicit Dim fClearCH(0 To 1) As Boolean '清除各通道计数值的旗标 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端 口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" 第 337 页,共 606 页 cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 清除通道 按钮后激活此事件 ' 程序中将把该相应通道的计数值清空旗标设成 True ' 真正的清除动作将会在定时器中进行 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdClsCH_Click(Index As Integer) fClearCH(Index) = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() Dim Buf$, RetBuf$ '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable Buf = cmbNO.List(cmbNO.ListIndex) '若地址只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '变更型态为计数模式 使用%AANNTTCCFF 指令 MSComm1.Output = "%" & Buf & Buf & "50" & "06" & "00" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 338 页,共 606 页' 按下 开始侦测 按钮后激活此事件 ' 根据使用者的选择 程序对模块下设定的指令 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Dim Buf$, RetBuf$, Buf2$ Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '设定计数值从 0 开始 MSComm1.Output = "$" & Buf & "60" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) MSComm1.Output = "$" & Buf & "61" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) If chkHHAlarm.Value = 1 Then 'Alarm Mode 1 '设定型态为 Alarm 1 MSComm1.Output = "~" & Buf & "A1" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '设定警戒 MSComm1.Output = "@" & Buf & "EAM" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '设定 HIGH ALARM 数值 Buf2 = Hex(Val(txtHighAlarm(0).Text)) '转换为字符串 Buf2 = Space(8 - Len(Buf2)) & Buf2 '补上空白 Buf2 = Replace(Buf2, Chr(32), "0") '将空白变成 0 MSComm1.Output = "@" & Buf & "PA" & Buf2 & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '设定 HIGH- HIGH ALARM 数值 Buf2 = Hex(Val(txtHighAlarm(1).Text)) '转换为字符串 Buf2 = Space(8 - Len(Buf2)) & Buf2 '补上空白 Buf2 = Replace(Buf2, Chr(32), "0") '将空白变成 0 MSComm1.Output = "@" & Buf & "SA" & Buf2 & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) Else 'Alarm Mode 0 '设定型态为 Alarm 0 MSComm1.Output = "~" & Buf & "A0" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) 'CH- 0 MSComm1.Output = "@" & Buf & "EA0" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '设定 Counter0 ALARM 数值 Buf2 = Hex(Val(txtHighAlarm(0).Text)) '转换为字符串 Buf2 = Space(8 - Len(Buf2)) & Buf2 '补上空白 Buf2 = Replace(Buf2, Chr(32), "0") '将空白变成 0 MSComm1.Output = "@" & Buf & "PA" & Buf2 & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) 'CH- 1 MSComm1.Output = "@" & Buf & "EA1" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '设定 HIGH- HIGH ALARM 数值 Buf2 = Hex(Val(txtHighAlarm(1).Text)) '转换为字符串 Buf2 = Space(8 - Len(Buf2)) & Buf2 '补上空白 Buf2 = Replace(Buf2, Chr(32), "0") '将空白变成 0 MSComm1.Output = "@" & Buf & "SA" & Buf2 & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) End If 第 339 页,共 606 页 Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 输入图形暂时设为灰色 表示无状态讯息进入 ' 将通讯端口号码及站号填入 Combo 控件 预设通讯端口的选项是第一个 ' 而模块的预设选择则是第 4 个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 3 '预设的站号为 4 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 '预设为 COM1 cmdStart.Enabled = False End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件引发后 就不断地执行其中的程序 ' 将模拟读值命令送出 再取得传回字符串并判断 ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, COMBuf$, Pos1%, Pos2% Dim RetBuf$, CounterStr As String Dim CounterValue As Double Dim i% Static CounterCh As Integer COMBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(COMBuf) = 1 Then COMBuf = "0" & COMBuf End If MSComm1.Output = "#" & COMBuf & CStr(CounterCh) & Chr(13) ' 要求计数值回传 lblMsg.Caption = "联机" & COMBuf & "中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, RetBuf, ">") '文件头位置 第 340 页,共 606 页 Pos2 = InStr(1, RetBuf, vbCr) '文件尾位置 If Pos1 = 0 Then '若文件头字符不在 表示传值失败 lblMsg.Caption = "传回值不正确 " Exit Sub End If CounterStr = Mid(RetBuf, Pos1 + 1, Pos2 - (Pos1 + 1)) '分离出计数值 的字符串 CounterStr = "&H" & CounterStr & "&" '将其变成长整数型式的字符串 CounterValue = Val(CounterStr) '转换为实际的数值 '若小于 0 必须作修正 If CounterValue < 0 Then CounterValue = 2147483648# * 2# + CounterValue End If lblValue(CounterCh).Caption = CounterValue '显示在画面上 CounterCh = (CounterCh + 1) Mod 2 '下一个通道 '读取警戒输出现在的状态 MSComm1.Output = "@" & COMBuf & "DI" & Chr(13) '组合警戒状 态的命令字符串 lblMsg.Caption = "侦测" & COMBuf & "警戒状态中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then '若传回字符串不正确 则显示讯息 lblMsg.Caption = "未传回正确状态讯息 " Else Buf = Mid(RetBuf, 6, 1) '取出代表数字输入状态的字符 If (Val(Buf) And 1) = 1 Then '第 0 个数字输出的检查 imgAlarm(0).Picture = imgRed.Picture Else imgAlarm(0).Picture = imgGreen.Picture End If If (Val(Buf) And 2) = 2 Then '第 1 个数字输出的检查 imgAlarm(1).Picture = imgRed.Picture Else imgAlarm(1).Picture = imgGreen.Picture End If End If '以下检查清除计数值的旗标 For i = 0 To 1 If fClearCH(i) Then '下达清除的指令 MSComm1.Output = "$" & COMBuf & "6" & CStr(i) & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) fClearCH(i) = False '清除完后再将旗标改回 False 表示不清除 End If Next i End Sub 模块内的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents 第 341 页,共 606 页 Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 8-3-3 频率读值与警戒输出 由于 7080D 模块也可以用于量测频率输入值 本小节也将讨论 结合频率量测和数字输出二种功能的项目 和计数模块比较 当使用在频率量测时 模块本身没有提供警 戒功能 也就是说 频率模式下模块无法直接判断输入频率值而对 数字输出通道作控制 这部份就要由主控端的程序自行处理了 项 目中会使用到组态指令 读值指令 和之前的项目的指令是一样的 请读者直接参考之前的项目中的指令说明 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 作为串行通讯的信道 其也 的属性均维持默认值 3、 安排二个 Combo 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”cmbCOM”及”cmbNO” 作为通讯端口信道及侦测模块站号 的选择区 4、 安排二个 Image 控件 按下 F4 叫出其属性窗口 变更 Name 属 性为”imgRed” ”imgGreen” 而在其各别的 Picture 属性中选择 项目目录下的 Red.jpg Green.jpg 作为显示输出及输入状态之 第 342 页,共 606 页用 另外也要将其 Visible 属性设为 False 令其执行时不显示 5、 安排二个 Image 控件 其成为物数组 按下 F4 叫出其属性窗口 变更 Name 属性为”imgAlarm” 此二个 Image 控件的 Visible 属 性要设成 True 作为高低警戒显示用灯号 6、 安排二个文字框 同样使其成为对象数组 按下 F4 叫出属性窗 口 予其 Name 属性中输入”txtFreqAlarm” 作为警戒值设定的 输入区 7、 安排一个 Frame 控件 按下 F4 叫出属性窗口 设定其 Caption 属性为”现在读值” 另在其内部安排二个 Label 控件 使其成为 对象数组 在其 Name 属性输入”lblValue” 作为计数值的显示区 8、 再安排一个 Label 控件 放在窗体的最下方 按下 F4 叫出属性窗 口 其 Name 属性改为”lblMsg” BorderStyle 选择”1-单线固定” BackColor 选择淡黄色 作为显示系统讯息之用 9、 安排一个定时器 按下 F4 叫出属性窗口 修改其 Interval 属性为 100 Enabled 属性设为 False 定时器被激活后 将以 100 毫秒 的周期执行内部的程序代码 10、 安排 8 个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 分别改为” 通讯端口” ” 站号” ” 警戒设定-1” ” 警戒设定 -2” ”Alarm-0” ”Alarm- 1” ”CH0” ”CH1” 作为各控件的标 示之用 11、 安排 3 个按钮控件 其 Caption 属性分别输入 开启通讯端口 开始侦测 结束 作为执行相对应的动作之用 12、 设计后之画面如图 8-3-5 所示 图 8-3-5 频率侦测与警戒的设计画面 动作流程解析 使用者可以在画面上选择通讯端口号码 模块的 编号 另外也让使用者可以设定警戒 当一切选择及设定妥当后 按 下开启通讯端口的按钮及开始侦测的按钮 激活定时器后 其程序代 第 343 页,共 606 页码会不断地对 7080D 模块送出询问二个频率数值的指令 接着处理 传回的指令结果 显示输入的频率值到画面上 当频率值超过所设定 的警戒值时 数字输出通道即会作相对应的输出 画面上也可以看到 数字输出通道灯号显示出来 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 3 '预设的站号为 4 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 '预设为 COM1 cmdStart.Enabled = False 通讯端口可能是 COM1 或 COM2 而模块的编号也可由 01~FF 因此在此事件中 程序填入可让使用者选择 COM 及编号的 Combo 控件 此模块编号通常会被我们设成 04 因此在程序中 直接让它出现模块的号码为 4 由于还未开启通讯端口 因此 开始侦测 的按钮先行 Disable 掉 2、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False ' 将此按钮 Disable Buf = cmbNO.List(cmbNO.ListIndex) '若地址只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '变更型态为频率模式 使用%AANNTTCCFF 指令 MSComm1.Output = "%" & Buf & Buf & "51" & "06" & "00" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub 第 344 页,共 606 页comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 使用通讯端口的先决条件就是必须将通讯端口开启 开启前当然 得先检查通讯端口号码是否落在 1~16(MSComm 控件仅支持到 16 个通讯端口 一般情形已足够)之间 接着就是设定分布式模 块所使用的 Settings 参数 再开启通讯端口 一旦开启通讯端口 后 就关闭此按钮 以避免使用者按二次 造成错误 由于我们 将要进行频率值的读取 因此使用组态指令将模式设定在频率模 式 (Type=51) 3、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中输入 以下程序代码 '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端口 End If cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 使用者未改变通讯端口的号码时 此子程序就不需执行 当然 直接跳出去即可 这个部份的程序可以在使用者变更通讯端口 号码时 将通讯端口关闭 并将原先 Disable 的开启通讯端口 按钮重新 Enable 4、 双击定时器控件 在其 Timer 事件中输入以下的程序代码 COMBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(COMBuf) = 1 Then COMBuf = "0" & COMBuf End If MSComm1.Output = "#" & COMBuf & CStr(FreqCh) & Chr(13) '要 求频率值回传 RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, RetBuf, ">") '文件头位置 Pos2 = InStr(1, RetBuf, vbCr) '文件尾位置 If Pos1 = 0 Then '若文件头字符不在 表示传值失败 lblMsg.Caption = " 传回值不正确 " Exit Sub End If FreqStr = Mid(RetBuf, Pos1 + 1, Pos2 - (Pos1 + 1)) '分离出计数值的字 符串 FreqStr = "&H" & FreqStr & "&" '将其变成长整数型式的字符串 第 345 页,共 606 页 FreqValue = Val(FreqStr) '转换为实际的数值 频率值最高 100K 故 不需作范围检查 lblValue(FreqCh).Caption = FreqValue & "Hz" '显示在画面上 '读取警戒输出现在的状态 MSComm1.Output = "@" & COMBuf & "DI" & Chr(13) '组合警戒状 态的命令字符串 lblMsg.Caption = "侦测" & COMBuf & "警戒状态中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then '若传回字符串不正确 则显示讯息 lblMsg.Caption = "未传回正确状态讯息 " Else Buf = Mid(RetBuf, 6, 1) '取出代表数字输入状态的字符 If (Val(Buf) And 1) = 1 Then '第 0 个数字输出的检查 imgAlarm(0).Picture = imgRed.Picture Else imgAlarm(0).Picture = imgGreen.Picture End If If (Val(Buf) And 2) = 2 Then '第 1 个数字输出的检查 imgAlarm(1).Picture = imgRed.Picture Else imgAlarm(1).Picture = imgGreen.Picture End If OutValue = Val(Buf) End If '检查频率值 If FreqValue > Val(txtFreqAlarm(FreqCh).Text) Then '作警戒输出 OutValue = OutValue Or 2 ^ FreqCh '作 OR 运算 找出应 High 的通 道 Else OutValue = OutValue And (Not 2 ^ FreqCh) '作 OR 运算 找出应 High 的通道 End If MSComm1.Output = "@" & COMBuf & "DO0" & OutValue & Chr(13) ' 组合警戒状态的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) FreqCh = (FreqCh + 1) Mod 2 '下一个通道 程序分成三个部份进行 首先是频率值读取的部份 利用#AAN 指令将执行字符串传回后 解析文件头字符和文件尾字符间的频 率数值字符串 并将其由十六进制数值转换为十进制数值 转换 完成后将其显示在 lblValue 再来是数字状态的检查部份 使用 @AADI 传回数字输出状态并显示在 imgAlarm 对象中 最后一个部份是警戒部份 程序会去比较所得的数值是否超过设 定的警戒值 若是超过 则使用位运算设定数字输出的数值 再 使用@AADO0D 指令送出输出控制字符串 检查频率值有二种情形需考量 当读值超过设定时 要将对应的 位 High 起来的方法是使用 Or 运算 任何数值一旦和 1 作 Or 则结果一定是 1 第二种情形是未超过时 需将该通道的数值设 为 0 程序中使用的方法是先 Not 那么原来是 1 的数值就会变 成 0 接下来再使用 And 运算 任何数值和 0 作 And 后 结果一 定是 0 这样就可以达到强迫该位为 0 的目的 第 346 页,共 606 页定时器就如此地周而复始地作计数值的读取和清除的工作 5、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 定时 器随即被激活 定时器的侦测结果可能如图 8-3-6 所示 图 8-3-6 频率侦测和警戒输出的执行结果 以上的程序代码 您可以参考光盘中 范例 \ 第八章 \ 计 数值与警戒输出-频率 档案夹中的项目 双击 Ex.vbp 项目档 即 可进行以上的测试 完整的程序代码列表如下 Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当选择通讯端口的 Combo 被点中后激活此事件 ' 若使用者改变通讯端口时 关闭通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmbCOM_Click() '若通讯端口号码和现在的选择一样时就不必理会 直接跳出此子程序 If cmbCOM.ListIndex + 1 = MSComm1.CommPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If MSComm1.PortOpen Then MSComm1.PortOpen = False '关闭通讯端 口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 第 347 页,共 606 页' 此程序中会先关闭警戒的部份 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() Dim Buf$, RetBuf$ Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If If MSComm1.PortOpen Then MSComm1.Output = "@" & Buf & "DA" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 1000) End If End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开启通讯端口 按钮后激活此事件 ' 将通讯控件的参数设定好 并开启 ' 致能(Enable) 开始侦测 的按钮 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdOpenCOM_Click() Dim Buf$, RetBuf$ '判断 COM 号码是否落在 1~16 之间 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex <= 16 Then MSComm1.CommPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr MSComm1.Settings = "9600,n,8,1" '设定通讯参数 MSComm1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable Buf = cmbNO.List(cmbNO.ListIndex) '若地址只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '变更型态为频率模式 使用%AANNTTCCFF 指令 MSComm1.Output = "%" & Buf & Buf & "51" & "06" & "00" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) '等待响应字符 cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 开始侦测 按钮后激活此事件 ' 将定时器激活或关闭 并显示对应的文字在按钮上 以指示使用者动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 348 页,共 606 页Private Sub cmdStart_Click() Dim Buf$, RetBuf$, Buf2$ Buf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '设定为频率侦测模式 '设定 Input Mode MSComm1.Output = "$" & Buf & "B0" & vbCr RetBuf = WaitRS(MSComm1, vbCr, 3000) Timer1.Enabled = Not Timer1.Enabled '定时器转态 If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口号码及站号填入 Combo 控件 通讯端口的预设选项是第一个 ' 站号的预设选项是第四个 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i% cmbNO.Clear For i = 1 To 255 cmbNO.AddItem CStr(Hex(i)) Next i cmbNO.ListIndex = 3 '预设的站号为 4 cmbCOM.Clear cmbCOM.AddItem "COM1" cmbCOM.AddItem "COM2" cmbCOM.ListIndex = 0 '预设为 COM1 cmdStart.Enabled = False End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件引发后 就不断地执行其中的程序 ' 将模拟读值命令送出 再取得传回字符串并判断 ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, COMBuf$, Pos1%, Pos2% Dim RetBuf$, FreqStr As String Dim FreqValue As Double Dim OutValue As Integer Static FreqCh As Integer COMBuf = cmbNO.List(cmbNO.ListIndex) '若只有一位数 则在此位数的前端加上一个 0 If Len(COMBuf) = 1 Then COMBuf = "0" & COMBuf End If 第 349 页,共 606 页 MSComm1.Output = "#" & COMBuf & CStr(FreqCh) & Chr(13) '要 求频率值回传 RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then lblMsg.Caption = "取值失败 " Exit Sub End If '先找到">"符号所在的位置 Pos1 = InStr(1, RetBuf, ">") '文件头位置 Pos2 = InStr(1, RetBuf, vbCr) '文件尾位置 If Pos1 = 0 Then '若文件头字符不在 表示传值失败 lblMsg.Caption = "传回值不正确 " Exit Sub End If FreqStr = Mid(RetBuf, Pos1 + 1, Pos2 - (Pos1 + 1)) '分离出计数值的字 符串 FreqStr = "&H" & FreqStr & "&" '将其变成长整数型式的字符串 FreqValue = Val(FreqStr) '转换为实际的数值 频率值最高 100K 故 不需作范围检查 lblValue(FreqCh).Caption = FreqValue & "Hz" '显示在画面上 '读取警戒输出现在的状态 MSComm1.Output = "@" & COMBuf & "DI" & Chr(13) '组合警戒状 态的命令字符串 lblMsg.Caption = "侦测" & COMBuf & "警戒状态中 " RetBuf = WaitRS(MSComm1, vbCr, 1000) If RetBuf = "" Then '若传回字符串不正确 则显示讯息 lblMsg.Caption = "未传回正确状态讯息 " Else Buf = Mid(RetBuf, 6, 1) '取出代表数字输入状态的字符 If (Val(Buf) And 1) = 1 Then '第 0 个数字输出的检查 imgAlarm(0).Picture = imgRed.Picture Else imgAlarm(0).Picture = imgGreen.Picture End If If (Val(Buf) And 2) = 2 Then '第 1 个数字输出的检查 imgAlarm(1).Picture = imgRed.Picture Else imgAlarm(1).Picture = imgGreen.Picture End If OutValue = Val(Buf) End If '检查频率值 If FreqValue > Val(txtFreqAlarm(FreqCh).Text) Then '作警戒输出 OutValue = OutValue Or 2 ^ FreqCh '作 OR 运算 找出应 High 的通 道 Else OutValue = OutValue And (Not 2 ^ FreqCh) '作 OR 运算 找出应 High 的通道 End If MSComm1.Output = "@" & COMBuf & "DO0" & OutValue & Chr(13) ' 组合警戒状态的命令字符串 RetBuf = WaitRS(MSComm1, vbCr, 1000) FreqCh = (FreqCh + 1) Mod 2 '下一个通道 End Sub 第 350 页,共 606 页模块内的程序代码则如下 Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不 正常时传回值是空字符串 Function WaitRS(Comm As MSComm, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" TT = GetTickCount Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then WaitRS = Buf Else WaitRS = "" End If End Function 第 351 页,共 606 页本章习题 1、 参考 8- 2- 4 小节 试完成一频率侦测的项目 2、 7080D 模块的最高记录数值是 4 个字节 在计数模式下读回 数值后可能需作修正 那么在频率模式呢?试说明原因 3、 8-3-2 小节使用模块本身的警戒功能作数字输出 试作一程 序 不倚靠模块本身的功能 而是在程序中比较数值去作 数字输出 4、 同上一题 试作一项目程序 当警戒值超过后 令数字输 出约 10 秒钟 接着清除计数值 重新计数 5、 同上一题 试作一项目程序 侦测频率值 当警戒值超过 后 令数字输出约 10 秒钟 第 352 页,共 606 页第九章 模块进阶设定及控制 在本章之前的讨论中 针对每一个模块 除了模块的站号依各 个模块而不一样外 其它的设定我们均是以模块的内定值为主 并 没有特别地去作设定 以下除了说明更改设定的相关重点外 也将 对于程序引入错误的预防—CheckSum 的使用及撰写 9-1 通讯参数变更 通讯是主控计算机和分布式模块主要的沟通过程 通讯的双方必 须取得一致的参数才能让通讯正确地进行 一般说来 分布式模块 可以透过原商所附的公用程序作相关的通讯参数变更 实际上也可 以利用 Visual Basic 的程控而达到相同的功能 9-1-1 模块初始化 分布式模块在模块被接上电源的同时 模块会以其内部的记忆而 开始对模块作必要的控制 并且以其所记录在 EEROM 中的通讯参 数和主控计算机通讯 这是模块的初始化 因此每个模块一开始运 作时的情况就视模块中所储存的参数而定 每个分布式模块都有不 一样的功能 使用及设定的过程中也可能去变更一些参数 当所变 更的参数已经忘了 或是希望重新对某一个模块作设定时 最方便 的方式可能就是把模块回复到出厂时的设定值 执行模块的初始化 而使得模块可以回到出厂时的设定值 最重 要的是对 INIT*脚位作些动作 此 INIT*脚位是每个输出入模块(除 了转换模块 如 7520)均会存在的 执行重置动作时的步骤如下 1、 将模块断电 2、 找一条导线将模块上标示为 INIT*和 GND 的二支脚位相接 3、 重新接上电源 利用 Visual Basic 撰写的程序或公用程序重新规 划模块的组态 4、 将模块断电后 拆掉连接在 INIT*和 GND 的二支脚位上的导线 5、 重新接上电源 模块已经成为新的组态了 执行以上的 5 个步骤后 模块的组态将回而出厂的默认值 此默认 第 353 页,共 606 页值为 模块地址 00 传输速度(BaudRate)9600Bps CheckSum 禁 能 (Disable) 在上述的第 3 个步骤时 我们可以利用章节所发展的 Visual Basic 项目对该模块作参数的检查 例如 将 7012D 依上述的方法 重置 而在 INIT* 和 GND 间的接线尚未拆掉时 利用组态指 令 ”$AA2”可以取得模块的组态 其情形如图 9- 1- 1 所示 图 9-1-1 组态指令及结果 由于 INIT*和 GND 相接后 模块会回到原来的出厂默认值 而 出厂值的模块地址是 00 因此组态指令须为”$002” 检查传回的字 符串可以发现 其结果是”!02080600” 这个传回的结果显示了”模 块的地址为 02 模块型态是 08 通讯速度 9600 未使用 CheckSum” 却不是出厂的默认值 是不是出错了呢?其实 在 INIT*和 GND 未 拆除的情形下 当下达组态指令时 所传回的结果是上一次该模块 的设定值 虽在此看到的模块地址是 02 不过 在未变更地址为 02 前 所使用的指令中的地址码必须使用 00 图 9- 1-2 是更改模块 地址的二种情形 可依实际的需要而选择所要设定的地址 而且每 次所下达的指令中的地址码均是 00(不管已经改过多少次了) 第 354 页,共 606 页 图 9-1-2 改变为不同的地址 在接上 INIT*和 GND 的情形下 也可以控制该模块 图 9- 1-3 是控制的情形 我 们可以发现 使用模块地址也是 00 图 9-1-3 控制模块(传回名称及读值) 当使用组态指令(%AANNTTCCFF)执行组态的变更后 接下来拆 掉在 INIT*和 GND间 连接线并重新送电 模块就成为新的设定值(步 骤 4 及步骤 5) 以上的程序可以在 范例 \ 第九章 \ 指令的送出及读取 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的测试 9-1-2 变更模块地址 分布式模块中的组态指令(%AANNTTCCFF)在每个不同的模块 均适用 仅因各模块功能的不同 而有不同的指令字符 但其格式 却是完全相同 变更模块的地址在此指令是将旧有的地址 AA 改为 第 355 页,共 606 页新的地址 NN 其值的范围可由 00~FF 共有 256 个地址可设定 须特别注意的是 一个 485 网络中的每个模块都必须拥有独一无二 的地址 否则将会造成讯号的错误 而使得同一个地址的模块们和 主控计算机间的通讯形同断线 改变模块地址时不需特别的连接线路 直接在一般的联机情形下 直接下达组态指令即可变更 9-1-3 通讯速度及 CheckSum 的改变 通讯速度的改变在分布式模块中也是必须将 INIT* GND 二支 脚位相连 不过和 9-1- 1 小节中的接线方式稍微不同 INIT*和 GND 连接起来的动作是在指令下达之前才执行 而不是在模块断电后执 行 如此才能透过组态指令改变该模块的通讯速度 在之前的讨论中 项目程序均未使用 CheckSum 的检查 也就 是主控计算机和模块间的资料交换是没有考虑到数据传输的可能 错误 模块本身也提供了 CheckSum 的致能/禁能 依使用者及使用 情形的不同 可以选择不同的设定 和通讯速度的改变一样 如果 模块需要改变 CheckSum 的致能/禁能状态 必须也将 INIT*和 GND 连接起来 接着使用组态指令改变其状态 若如上述的动作般地改变了组态的通讯速度及CheckSum 致能/ 禁能状态后 将模块断电 拆掉连接在 INIT*和 GND 间的连接线 再重新接上电源 模块就可以依最新的设定状态工作 一个系统作规划时 通常就必须考量到模块的设定 而且一 旦决定后 也不会随意地更改 更改的频率也不要过多 太多的更 改也造成程序上设计的困扰 9-1-4 模块安全性与 Watch-Dog 分布式模块使用在各个不同的场合 环境上难免会有不尽理想 之处 噪声的存在使得模块可能因此而受到干扰 进而使得模块功 能轻则受到干扰 重则使模块失效 受到干扰时可能使传输的资料 不正确 这个可以透过 CheckSum 的启用而降低收到不正确资料时 所导致的程序判断错误及控制错误 至于模块失效的问题则可透过 看门狗(WatchDog)的作用而降低模块的当机或控制失常 第 356 页,共 606 页模块的工作可以分成二个部份 输入及输出 当模块接传送本 身输入状态给主控计算机时 就算模块出了问题 应该也不会对其 他的设备或装置造成太大的影响 因为它是一个输入的状态 但是 当模块接受主控计算机的指令而进行输出动作时 模块对于其它的 设备就具有相当的影响了 如果此时的主控计算机失常 而使得输 出动作不断进行 很有可能造成系统的损坏 甚至是人员的损失 此时就必须考虑到控制动作的安全性 分布式模块所使用的 Watch- Dog 机制有二重 第一重的机制是 硬件上的 Watch- Dog 它属于硬件重置线路 此线路会监视模块的 操作状况 当模块处于恶劣的工作环境下而导致工作不正常时 此 监视线路会使得模块继续工作而不致当掉 另一种的 Watch- Dog 机制属于软件 主控计算机和分布式模块 通讯的过程中 主控计算机必须在规定的时间内传送一个讯号给模 块 让模块知道主控计算机是没问题 而主控计算机所下达的指令 也会在此情形下被模块所接受并执行 当主控计算机在规定的时间 间隔内未传送讯号告知主控计算机状态时 模块就把主控计算机视 为当机 并且不接受控制指令 除非主控计算机下指令清除此 Watch- Dog 状态 如图 9- 1- 4 啟動看門狗並設 定時間間隔 傳送HOST OK指令 其他的監控指令 模組重置? 否 送出清除 WatchDog指令是 图 9- 1- 4 Watch Dog 流程 9-1-5 开机值与安全值 当分布式模块开机时(也就是电源刚接通上时) 模块本身会依 内部的设定而开始输出及输入工作 其中有关输出时的状态可以由 主控计算机予以设定 此即为 开机值 (Power- On Value) 第 357 页,共 606 页Watch- Dog 功能被激活后 如果一段时间主控计算机没有送 HOST OK 指令给模块的话 模块会认为主控计算机已经当掉 而 不继续接受输出控制的指令 并且将所有的输出调整到事先定义的 安全值 当主计算机恢复正常 必须检查模块是否因清除 Watch- Dog 指令未下达而导致模块不接受接下来的输出指令 若是 则必须先清除模块的 Watch- Dog 的重置状态 才能继续送输出指 令 另外须注意的是 使用清除 Watch- Dog 重置状态指令后 Watch- Dog 功能即失去 必须再下一次激活 Watch- Dog 的指令 第 358 页,共 606 页9-2 程序的实作 上一节已针对模块本身作参数改变时 必须作的接线 步骤及相 关说明 本节将以程序予以实验证明 由于模块地址的变更无须作 硬件的接线 本节着重在须作硬件接线的通讯速度及 CheckSum 的 程序 9-2-1 多传输速度的设计 分布式模块本身提供了多种的传输速度 利用上一节的方法可 以变更模块的传输速度 以之前讨论过的几个模块(7012D 7021 7060D 7080D)为例 首先使用 范例 \ 第九章 \ 指令的送出 及读取 中的 Ex.vbp 项目 改变每一个模块的传输速度 改变模 块组态的指令是”%AANNTTCCFF” 但在下达模块组态指令前必须 将 INIT*及 GND 二支脚位相连 才会使得改变通讯速度的指令得以 正确执行 表 9- 2-1 是准备实验的规划表 其中将每个模块都设定 成不同的传输速度 表 9- 2- 1 模块及传输速度 模块编号 模块地址 原传输速度(Bps) 新传输速度(Bps) I- 7060D 01 9600 9600 I- 7012D 02 9600 19200 I- 7021 03 9600 38400 I- 7080D 04 9600 115200 表 9-2- 1 所列的各个模块的原始速度之前均为 9600Bps 必须 使用下列的步骤改变它们的传输速度 1、 将主控计算机和模块连接妥当 并接上电源 2、 执行 指令的送出及读取 中的 Ex.vbp 项目 3、 下达”$AA2”指令检查各模块的状态 4、 使用连接线连接 INIT*和 GND 二支脚位 5、 下达”%AANNTTCCFF”指令变更传输速度 例如要将 7021 模块 改为 38400 其对应的 CC 字符串是 08 故应下 达 ”%0303080800” 就可以将传输速度改为 38400 6、 再 下达”$AA2”指令检查各模块的状态 看看传输速度是否已被 第 359 页,共 606 页过来 7、 关闭电源 再重新激活电源 模块传输速度即已改变完成 执行以上的步骤可以使用 Visual Basic 予以完成 也可以使用原厂 所附的公用程序来作 如果使用公用程序 当执行的步骤错误时 程序还会提供说明 设定的画面如图 9- 2- 1 所示 图 9-2-1 控制模块(传回名称及读值) 选中一个模块 并双击后便会出现组态设定画面供使用者作组态参 数的设定 设定完成后 按下画面上的 Setting 按钮即可 所有的模块作完设定后 我们发展一个项目程序 在不同的传 输速度的模块间传送指令 并取得结果 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 按下 F4 将 CommPort 属性改 为 1 使程序使用 COM1 为通讯的信道 其它的属性均维持默 认值 读者可视需要改为其它的通讯端口 3、 安排二个文字框控件 按下 F4 叫出属性窗口 其 Name 属性改 为 txtSend 和 txtReceive 按出 Font 属性 并将字号设为 12 作 为指令的输入区及结果的传回区 读者亦可使用其它的显示设 定 4、 安排二个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 改为”命令区”及 ”传回区” 作为显示之用 5、 安排 5 个命令按钮 按下 F4 叫出属性窗口 其 Name 属性改 为 ”cmdSend7060” ”cmdSend7012” ”cmdSend7021” ”cmdSe nd7080” ”cmdEnd” 而 Caption 属性则分别改为”送出指令 第 360 页,共 606 页-7060D” ”送出指令-7012D” ”送出指令-7021” ”送出指令 -7080D” ”结束” 作为每个模块的命令传送按钮及结束系统之 用 6、 设计后之画面如图 9- 2- 2 图 9-2-2 与不同速度模块通讯项目的设计画面 动作流程解析 使用者在控制不同的模块时 由于每个模块的通 讯速度均不相同 因此必须考虑到主控计算机端串行通讯端口的速度 是否符合模块的需求 画面上有四个按钮 分别对应到四个不同的模 块 当使用者在 命令区 中输入命令后 需要依据模块的不同而按 下不一样的按钮 而回传的结果会显示在 传回区 的文字框内 程序代码部份如下 1、 双击 送出指令-7060D 按钮 在其 Click 事件中输入以下程序 代码 MSComm1.CommPort = 1 MSComm1.Settings = "9600,n,8,1" MSComm1.PortOpen = True TimeDelay 100 '延迟一下 '将指令送出 并取回结果 txtReceive.Text = SendCmd2Comm(MSComm1, Trim(CStr(txtSend.Text)) & vbCr, vbCr, 2000) MSComm1.PortOpen = False '关闭通讯端口 串行端口号码的指定不似之前的章节在 Form_Load 事件中执 行 而是放在即将送出指令之前才执行 如此的目的是由于每 一个模块的传输速度均不相同 无法以一固定的通讯速度达到 控制所有模块的目的 只好在送出指令时才作此工作 第 361 页,共 606 页2、 双击 送出指令-7012D 按钮 在其 Click 事件中输入以下程序 代码 MSComm1.CommPort = 1 MSComm1.Settings = "19200,n,8,1" MSComm1.PortOpen = True TimeDelay 100 '延迟一下 '将指令送出 并取回结果 txtReceive.Text = SendCmd2Comm(MSComm1, Trim(CStr(txtSend.Text)) & vbCr, vbCr, 2000) MSComm1.PortOpen = False '关闭通讯端口 如此的理由和上一点相同 不过通讯速度为 19200bps 3、 双击 送出指令-7021 按钮 在其 Click 事件中输入以下程序代 码 MSComm1.CommPort = 1 MSComm1.Settings = "38400,n,8,1" MSComm1.PortOpen = True TimeDelay 100 '延迟一下 '将指令送出 并取回结果 txtReceive.Text = SendCmd2Comm(MSComm1, Trim(CStr(txtSend.Text)) & vbCr, vbCr, 2000) MSComm1.PortOpen = Fals e '关闭通讯端口 如此的理由和上一点相同 不过通讯速度为 38400bps 4、 双击 送出指令-7080D 按钮 在其 Click 事件中输入以下程序 代码 MSComm1.CommPort = 1 MSComm1.Settings = "115200,n,8,1" MSComm1.PortOpen = True TimeDelay 100 '延迟一下 '将指令送出 并取回结果 txtReceive.Text = SendCmd2Comm(MSComm1, Trim(CStr(txtSend.Text)) & vbCr, vbCr, 2000) MSComm1.PortOpen = False '关闭通讯端口 如此的理由和上一点相同 不过通讯速度为 115200bps 5、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 本项目所使用的程序除了速度设定不同外 其余的程序几乎是一 样的 由于分布式模块的指令传送后 必定会有传回字符串(若无则 表示指令错误 或站号错误) 既然如此 在此项目中也将之前各章 节所使用的程序作小幅度的修改 将这些必然的步骤作成一个子程 序 并 建立一个新的函式—SendCmd2Comm 其意为 由通讯端口 传送指令出去 其传回值即为模块的传回字符串 流程图如图 9- 2-3 所示 第 362 页,共 606 页清除輸入緩衝區 送出指令 讀取輸入緩衝區 內的資料 已收到結尾字 元? 等待時間已到? 傳回字串 函式結束 是 否 否 是 图 9- 2- 3 传送及接收的流程图 函式程序代码则如下 Function SendCmd2Comm(Comm As MSComm, CS As String, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = Comm.Input Comm.Output = CS '指令送出 Buf = "" TT = GetTickCount '等待直到时间到 或是字符串传回 Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then SendCmd2Comm = Buf Else SendCmd2Comm = "" End If End Function 此函式的参数有四 它 们分别是通讯端口对象 传送指令 传回字 符串的结尾字符及逾时等候时间 使用此函式时 只要将参数传入 模块就会将结果传回 若指令错误 站号错误 等错误发生 而使 得指令字符串不被任何一个模块所承认时 函式也会在程序所设定 等候时间之后传回空字符串 程序可因而确认指令的执行成功或失 败 此函式可以让我们原来的程序更加地简洁 往后的各节也将采 用此一改良后的函式 此函式置于模块中 以上的每个按钮中的程序尾端均将通讯端口关闭 免得造成下次 开启通讯端口时发生 通讯端口已开启 的错误 第 363 页,共 606 页 以上项目执行后 我们可以在命令区文字框中输入所要传输的指 令 接着按下右方所列的各个的按钮(须视命令中的地址而选择正确 的按钮 别忘了) 实验的结果可能如图 9-2-4 所示 图 9-2-4 各模块的改变结果 由图 9-2-4 的结果 (A) (B) (C) (D)分别是下组态查询指令 给 4 个模块 而且每个模块所传回的结果 显示出主控计算机和每 个模块的沟通均正常 这个项目也显示出在不同传输速度下的资料 传递必须将主控计算机上的通讯速度改变与模块相同 才可使得传 输动作正常 一般说来 如果 485 网络上的模块传输速度太多种 程序中就必须时常切换速度以符合模块要求 程序建构上是比较复 杂的 因此使用单一传输速度在笔者认是比较好的 以上的程序代码 您可以参考光盘中 范例 \ 第九章 \ 不 同速度模块的通讯 档案夹中的项目 双击 Ex.vbp 项目档 即可进 行以上的测试 完整的程序代码列表如下 Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 送出指令- 7012D 按钮后激活此事件 ' 此模块的通讯速度为 19200 在此事件中再改变 第 364 页,共 606 页' 将命令区中的指令送出 ' 并将传回值显示在传回区中 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdSend7012_Click() MSComm1.CommPort = 1 MSComm1.Settings = "19200,n,8,1" MSComm1.PortOpen = True TimeDelay 100 '延迟一下 '将指令送出 并取回结果 txtReceive.Text = SendCmd2Comm(MSComm1, Trim(CStr(txtSend.Text)) & vbCr, vbCr, 2000) MSComm1.PortOpen = False '关闭通讯端口 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 送出指令- 7021 按钮后激活此事件 ' 此模块的通讯速度为 38400 在此事件中再改变 ' 将命令区中的指令送出 ' 并将传回值显示在传回区中 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdSend7021_Click() MSComm1.CommPort = 1 MSComm1.Settings = "38400,n,8,1" MSComm1.PortOpen = True TimeDelay 100 '延迟一下 '将指令送出 并取回结果 txtReceive.Text = SendCmd2Comm(MSComm1, Trim(CStr(txtSend.Text)) & vbCr, vbCr, 2000) MSComm1.PortOpen = False '关闭通讯端口 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 送出指令- 7060D 按钮后激活此事件 ' 此模块的通讯速度为 9600 在此事件中再改变 ' 将命令区中的指令送出 ' 并将传回值显示在传回区中 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdSend7060_Click() MSComm1.CommPort = 1 MSComm1.Settings = "9600,n,8,1" MSComm1.PortOpen = True TimeDelay 100 '延迟一下 '将指令送出 并取回结果 txtReceive.Text = SendCmd2Comm(MSComm1, Trim(CStr(txtSend.Text)) & vbCr, vbCr, 2000) MSComm1.PortOpen = False '关闭通讯端口 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 送出指令- 7080D 按钮后激活此事件 ' 此模块的通讯速度为 115200 在此事件中再改变 ' 将命令区中的指令送出 ' 并将传回值显示在传回区中 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdSend7080_Click() MSComm1.CommPort = 1 MSComm1.Settings = "115200,n,8,1" 第 365 页,共 606 页 MSComm1.PortOpen = True TimeDelay 100 '延迟一下 '将指令送出 并取回结果 txtReceive.Text = SendCmd2Comm(MSComm1, Trim(CStr(txtSend.Text)) & vbCr, vbCr, 2000) MSComm1.PortOpen = False '关闭通讯端口 End Sub 模块中的程序则如下 Option Explicit Declare Function GetTickCount Lib "kernel32" () As Long Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 指令送出 并等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'CS 是命令字符串 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不 正常时传回值是空字符串 Function SendCmd2Comm(Comm As MSComm, CS As String, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = Comm.Input Comm.Output = CS '指令送出 Buf = "" TT = GetTickCount '等待直到时间到 或是字符串传回 Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then SendCmd2Comm = Buf Else SendCmd2Comm = "" End If End Function 9-2-2 CheckSum 的激活 使用 CheckSum 的机制可以预防数据传输的错误 经常可以在 第 366 页,共 606 页传输程序中见到此种机制的存在 不同的设备和场合可能采用的 CheckSum 计算法则会不一样 而分布式模块 7000 系统采用的计算 法则如 4- 1- 5 节所示 本节即讨论此种机制的实现方法 首先还是得先用公用程序 或是自行建立程序改变模块的 CheckSum 状态 若是自行建立程序作变更的动作 可以使用 指 令的送出及读取 中的 Ex.vbp 项目 请参考上一小节中对于组态 的变更 下达”%AANNTTCCFF”指令 其中的 FF 指令字符串就含 有 CheckSum 的激活/ 关闭旗标位 例如要将 7012 模块中的 CheckSum 激活 组态指令必须为”%0202320640” 其中的 40 就含 可将模块的 CheckSum 激活 在这里我们使用原厂提供的公用程序将各个模块的 CheckSum 先行激活 激活的程序如下 1、 将主控计算机和模块连接妥当 并接上电源 2、 执行原厂的 7000Utility 公用程序( 详细过程及安装请见附 录 ???) 3、 激活寻找各个模块的按钮 待公用程序找出 485 网络线上的模 块 4、 使用连接线连接 INIT*和 GND 二支脚位 5、 在画面上双击模块 进入设定画面后 选择激活 CheckSum 的 选项 并且按下 Setting 按钮 6、 关闭电源 拆掉 INIT*和 GND 二支脚位的连接线 再重新激活 电源 模块传输速度即已改变完成 7、 可以将每一个模块均作过设定后 再执行第 6 步骤 若未接上 INIT*和 GND 二支脚位的连接线就执行变更的按钮 画面 上会出现错误讯息 若成功设定也会出现讯息告知使用者 二种讯 息的出现画面如图 9- 2- 5 所示 图 9-2-5 错误讯息(左 )及正确讯息(右 ) 第 367 页,共 606 页一切均按照正常的程序动作 而且也功成后 重新再让公用程 序寻找一次 485 网络上的模块 画面上的模块情形就会出现 CheckSum 已被激活的字眼 如图 9- 2- 6 图 9-2-6 CheckSum 激活后的模块搜寻画面 9-2-3 CheckSum 函式的建立 CheckSum 的计算 首先必须将所要传送的字符串中的字符一 个个地转换为 ASCII 码中所对应的号码 在 Visual Basic 中一个字 符转换为 ASCII 号码的函式就是 Asc 例如 No1=Asc(“$”) 所传回的结果就是”$”字符在 ASCII 中的号码(结果是 36) 其型态 是数字 2 将全部传送的字符(除了结尾字符 vbCr)的 ASCII 码相加 所得 的数值取十六进制 并取此十六进制中的最未端二位 即为 CheckSum 的结果 程序的写法则可如下 CheckSum = 0 '初始化 For i = 1 To BufLen Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i 上述程序的最后 CheckSum 变量即为作完计算的数值 为了使其可 以并入原字符串中传送 只要在程序中加入 Hex(CheckSum)即可形 成字符 第 368 页,共 606 页分布式模块的资料传送分成二个部份 主控计算机除了将指令 送到模块命令其执行外 模块执行的结果也将回传给主控计算机 故在传送与接收二个阶段均需使用到检查的程序 而且主要程序相 同 而最后的结果二者不一样 送出指令时 CheckSum 的结果并 入原送出字符串一起送出给模块 接收时 收到的字符串必须在检 查过 CheckSum 后 将除了 CheckSum 和结尾字符的其它字符取出 因此笔者分别就这二个部份发展了二个函式 第一个函式(OutCheckSum)用于处理所要送出的字符串 只要将所 要送出的字符串传入 传回的结果字符串即是作完 CheckSum 处理 其流程图如图 9- 2- 7 所示 計算字串長度 數值歸零 (CheckSum=0) CheckSum和字元 的ASCII碼相加 所有字元已算 完? 轉換CheckSum為 字串格式 傳回組合字串 和&HFF作AND運 算否 組合原始字串、 CheckSum字串及 結尾字元 函式結束 是 图 9- 2- 7 输出指令时的 CheckSum 流程 而程序撰写则如下 Function OutCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Long BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i '字符串重组 并加上结尾字符 Cr Buf = IIf(Len(Hex(CheckSum)) = 1, "0" & Hex(CheckSum), Hex(CheckSum)) OutCheckSum = InBuf & Buf & vbCr End Function 第 369 页,共 606 页上述的函式中使用了 IIf 函式 IIf 函式的第一个参数是判断式 当 此判断式成立时 第二个参数结果传回 若否 则传回第三个参数 我们用来判断所形成的 CheckSum 是只有一位数或是二位数 模块 指令要求必须是二位数 故不足的位数前面必须加上一个 0 第二个函式(INCheckSum)用于处理接收到的传回字符串 只要将所 收到的字符串传入 传回的结果字符串即是作完 CheckSum 检查 并去除 CheckSum 及结尾字符 其流程图如图 9- 2- 8 所示 取出扣掉 CheckSum及結尾 字元的字串 數值歸零 (CheckSum=0) CheckSum和字元 的ASCII碼相加 所有字元已算 完? 轉換CheckSum為 字串格式 傳回去除 CheckSum及結尾 的字串 和&HFF作AND運 算否 與傳入字串的 CheckSum作比對 函式結束 是 二者 CheckSum相同? 是 傳回空字串 否 图 9- 2- 8 检查传回结果时的 CheckSum 流程 而程序撰写则如下 Function INCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Integer BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen - 3 '需扣掉二个 CheckSum 字符和结尾字符 Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i If CheckSum = Val("&H" & Mid(InBuf, BufLen - 3 + 1, 2)) Then INCheckSum = Mid(InBuf, 1, BufLen - 3) Else INCheckSum = "" End If End Function 有了以上的二个函式 以下将原来的测试程序稍作修改 使其 成为一个具有 CheckSum 检查功能的项目 画面设计如下 第 370 页,共 606 页1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 按下 F4 将 CommPort 属性改 为 1 使程序使用 COM1 为通讯的信道 其它的属性均维持默 认值 读者可视需要改为其它的通讯端口 3、 安排二个文字框控件 按下 F4 叫出属性窗口 其 Name 属性改 为 txtSend 和 txtReceive 按出 Font 属性 并将字号设为 12 作 为指令的输入区及结果的传回区 读者亦可使用其它的显示设 定 4、 安排二个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 改为”命令区”及 ”传回区” 作为显示之用 5、 安排二个命令按钮 按下 F4 叫出属性窗口 其 Name 属性改 为 ”cmdSend” ”cmdEnd” 而 Caption 属性则分别改为”送出指 令 ” ”结束” 作为命令传送按钮及结束系统之用 6、 设计后之画面如图 9- 2- 9 图 9-2-9 含 CheckSum 项目的设计画面 动作流程解析 使用者将命令填入命令区文字框中后 按下送出 指令按钮 回传的结果会显示在 传回区 的文字框内 传送与接收 的过程都是作了 CheckSum 程序代码部份如下 1、 双击 送出指令 按钮 在其 Click 事件中输入以下程序代码 txtReceive.Text = SendCmd2Comm(MSComm1, txtSend.Text, vbCr, 2000) 分布式模块的每个指令的传送均包含有指令的送出及传回值的 接收二个部份 这二个部份在这里已被整合起来 因此程序到 此已精减至只剩一行 主要是函式 SendCmd2Comm 的作用 此 第 371 页,共 606 页函式输入 4 个参数 分别是通讯对象 控制指令 结尾字符及 逾时等待时间 Function SendCmd2Comm(Co mm As MSComm, CS As String, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = Comm.Input Buf = OutCheckSum(CS) 'CheckSum 计算 Comm.Output = Buf '指令送出 Buf = "" TT = GetTickCount '等待直到时间到 或是字符串传回 Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then '以下是传回值 CheckSum 的检查 SendCmd2Comm = INCheckSum(Buf) Else SendCmd2Comm = "" End If End Function 此函式大概分成二个部份说明 首先是指令的送出 在指令送 出前会经过 OutCheckSum 函式将输出的指令作 CheckSum 的运 算 并且将计算结果和原始字符串及结尾字符结合而经由串行 通讯端口送出 第二部份是传回字符串接收的部份 利用之前 所发展的智能型接收程序 在一个循环中 一方面等待结尾字 符 (vbCr)的传回 一方面也检查是否已超过等待的时间 当所 有的传回字符皆收到后 也使用 INCheckSum 作传回字符串的 CheckSum 检查 当检查成功后 就将结果传回 否则传回空字 符串 2、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 以上项目执行后 我们可以在命令区文字框中输入所要传输的指 令 接着按下右方的 送出指令 按钮 实验的结果可能如图 9-2-10 所示 第 372 页,共 606 页 图 9-2-10 CheckSum 项目执行结果 图 9-2-10 所显示的结果和我们之前未使用 CheckSum 程序的程序所 传回的结果是一样的 但是在这里多了一个保障 我们可以检查出 出错的资料串行 以上的程序代码 您可以参考光盘中 范例 \ 第九章 \ 指 令的送出及读取-检查码 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的测试 完整的程序代码列表如下 Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 送出指令 按钮后激活此事件 ' 执行送出指令子程序 该子程序作 CheckSum 的送出及收回的检查 ' 只传回不含 Checksum 及结尾字符的结果 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdSend_Click() txtReceive.Text = SendCmd2Comm(MSComm1, txtSend.Text, vbCr, 2000) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口在窗体加载时就开启 让其它的程序可以开始使用通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() 第 373 页,共 606 页 MSComm1.PortOpen = True End Sub 模块内的程序则如下 Option Explicit Declare Function GetTickCount Lib "kernel32" () As Long ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 时间延迟子程序 单位为毫秒(ms) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 指令送出 并等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'CS 是命令字符串 不需含结尾字符 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function SendCmd2Comm(Comm As MSComm, CS As String, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = Comm.Input Buf = OutCheckSum(CS) 'CheckSum 计算 Comm.Output = Buf '指令送出 Buf = "" TT = GetTickCount '等待直到时间到 或是字符串传回 Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then '以下是传回值 CheckSum 的检查 SendCmd2Comm = INCheckSum(Buf) Else SendCmd2Comm = "" End If End Function 'HOST OK 指令送出 'Comm 是通讯组件名称 Sub SendHostOK(Comm As MSComm) Dim Buf$, TT As Long Buf = Comm.Input Buf = OutCheckSum("~**") 'CheckSum 计 算 Comm.Output = Buf '指令送出 End Sub 第 374 页,共 606 页'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将要送出的字符串计算 CheckSum 的结果 'InBuf 是传入的字符串 ' 函式的传回值是作了 CheckSum 的结果字符串 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function OutCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Long BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i '字符串重组 并加上结尾字符 Cr Buf = IIf(Len(Hex(CheckSum)) = 1, "0" & Hex(CheckSum), Hex(CheckSum)) OutCheckSum = InBuf & Buf & vbCr End Function '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将模块传回的字符串检查 CheckSum 是否正确 'InBuf 是由模块传回的字符串 ' 函式的传回值为去除 CheckSum 及结尾字符( 正确时) ' 或空字符串( 不正确时) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function INCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Integer BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen - 3 '需扣掉二个 CheckSum 字符和结尾字符 Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i If CheckSum = Val("&H" & Mid(InBuf, BufLen - 3 + 1, 2)) Then INCheckSum = Mid(InBuf, 1, BufLen - 3) Else INCheckSum = "" End If End Function 9-2-4 Watch-Dog 相关指令 在 9-1-4 及 9-1-5 二小节说明了模块使用 Watch-Dog 的时机及概 第 375 页,共 606 页念 只要模块本身具有输出功能 Watch-Dog 就可在必要的时候派 上用场 至于那些模块是具有输出功能呢?只要有数字输出 模拟 输出等等的模块 就可以使用到 Watch-Dog 的技巧 针对 Watch- Dog 的操作所有的模块皆类似 除了设定安全值及 开机值略有不同外 其余相同 操作上依然是由主控计算机下达到 指令给模块 而由模块执行相对应的动作 本节使用的 Watch- Dog 操作指令说明如下 ~** HOST OK 指令 此指令由主控计算机发出 用来告诉线上的 分布式模块主控计算机处于正常状态 各模块必须接受主控 计算机所发出的控制指令 此指令会被所有的模块撷取 无 法等待特定模块之传回字符串 故不需在传送此指令后等待 其传回字符串 也就是说 此指令无传回字符串 ~AA0 读取模块状态值 可得知模块 Watch- Dog 是否激活及逾时 时间是否已达二个状态 其中的 AA 为模块地址 可由 00~FF 正常时的传回字符串格式为”!AASS” SS 所对应的 8 个位如表 9- 2- 2 的排列 表 9- 2- 2 位排列及意义 7 6 5 4 3 2 1 0 *1 保留 *2 保留 *1 位 7 为 0 时表示 Watch- Dog关闭 为 1 时表示 Watch- Dog 激活 *2 位 2 为 0 时表示逾时旗标已被清除 为 1 时表示逾时 旗标已被设定 ~AA1 重置模块状态 若逾时旗标已被设定 就可使用此指令促 使模块清除此一旗标 免得其它的输出指令失效 ~AA2 读取 Watch- Dog 逾时时间的设定值 正常时的传回字符串 格式为”!AAEVV” 若 E 为 0 表示未激活 Watch- Dog 若为 1 表示已激活 Watch- Dog VV 为逾时时间十六进制数值 其数值可由 00~FF 除以 0.1 后即为实际的时间 故时间可 由 0.1 秒至 25.5 秒 ~AA3EVV 激活 Watch- Dog 及设定 Watch- Dog 的逾时时间值 E 为 0 表示关闭 Watch- Dog 若为 1 表示激活 Watch- Dog VV 为逾时时间十六进制数值 其数值可由 00~FF 除以 0.1 后 即为实际的时间 故时间可由 0.1 秒至 25.5 秒 第 376 页,共 606 页以上的几个指令是与 Watch- Dog相关的指令 除了这些指令外 尚有一些和安全值 开机值相关的指令 依每模块的功能不同 这 些安全值 开机值相关的指令也略有差异 就之前作过实验的几个 模块(I-7080D 除外 因其无安全值 开机值之设定) 笔者说明如 下 I- 7060D 数字输出入模块部份 ~AA4V 读取开机值或安全值 V 为 P 时表示读取开机值 V 为 S 时表示读取安全值 正常时的传回字符串格式 为 ”!AA0X00” 其中的 X 可由 0~F 表示 4 个 Relay 的状态 ~AA5V 设定开机值或安全值 V 为 P 时表示将现在的数字输出状 态设为开机值 V 为 S 时表示将现在的数字输出状态设为安 全值 I- 7012D 模拟输入模块部份 ~AA4 读取开机值及安全值 正常时的传回字符串格式 为 ”!AAPPSS” PP 为开机值 SS 为安全值 各有四种可能 的情形 PP 及 SS 各别的情形如表 9- 2- 3 所示 表 9- 2- 3 数值和状态的对应 PP/SS 的数值 DO1 状态 DO0 状态 00 OFF OFF 01 OFF ON 10 ON OFF 11 ON ON ~AA5PPSS 设定开机值及安全值 PP 为设定开机值 SS 为设定安 全值 设定的数值可如表 9- 2- 3 I- 7021 模拟输出模块部份 ~AA4 读取安全值 正常时的传回字符串格式为”!AA(Data)” (Data) 的格式请参阅表 7-2-1 ~AA5 设定安全值 将现在的模拟输出值设定为安全值 $AA4 设定开机值 将现在的模拟输出值设定为开机值 第 377 页,共 606 页9-2-5 Watch-Dog 程序建立 本小节以之前的四个模块为例 说明如何建立 Watch- Dog 机制 的项目 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 按下 F4 将 CommPort 属性改 为 1 使程序使用 COM1 为通讯的信道 其它的属性均维持默 认值 读者可视需要改为其它的通讯端口 3、 安排一个定时器控件 按下 F4 叫出属性窗口 其 Interval 属性 改为 100 使其每 100 毫秒执行一次 Timer1 事件内的程序代码 4、 安排一个 CheckBox 控件 按下 F4 叫出属性窗口 其 Name 属性 改为 chkHostOK 按出 Font 属性 并将字号设为 12 作为是否 送出 Host OK 指令的旗标 5、 安排二个 Image 控件 按下 F4 叫出属性窗口 其 Name 属性分别改 为 imgOFF 及 imgON 它们的 Visible 属性设成 False 并加载 红色及绿色球形 此二个 Image 将作为状态显示之用 6、 安排四个 Image 控件 按下 F4 叫出属性窗口 其 Name 属性改 为 imgStatus 并设成对象数组 作为显示模块状态之用 7、 安排四个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属性 改为”7060D” ”7012D” ”7021” ”7080D” 作为显示之用 8、 安 排三个命令按钮 按下 F4 叫出属性窗口 其 Name 属性改 为 ”cmdWatchDog” ”cmdReset” ”cmdEnd” 而 Caption 属性则 分别改为”激活看门狗” ”重置状态” ”结束” 作为相对命令 激活之用 9、 另安排一个 Label 控件 置于画面之下 作为显示相关讯息之 用 10、 设计后之画面如图 9- 2- 11 第 378 页,共 606 页 图 9-2-11 Watch Dog 项目的设计画面 动作流程解析 程序会在定时器不断地运作下 询问每个模块的 Watch- Dog 状态 并将状态以红色灯或绿色灯显示在画面上 送出 HOST OK 的选择用来选择是否对所有的模块送出此一指令 告诉 模块主控计算机仍处于正常状态 当按下 激活看门狗 后 主控计 算机会送给每个模块指令 要求每个模块激活内部的看门狗监视 若 此时的 送出 HOST OK 被勾选 当然画面上的状态均会是绿色的(因 此主控计算机一直送出 Ok 的指令) 反过来说 若 送出 HOST OK 末勾选 则在文字框内的时间(以秒计)超过后 每个模块就会传回红 色状态 表示 WatchDog 已侦测到时间逾时 按下 重置状态 后 每个模块内部的逾时旗标会被清除 而且看门狗的设定也会被取消 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 MSComm1.PortOpen = True 开启通讯端口 准备接下来的通讯工作 2、 双击 激活看门狗 按钮 在其 Click 事件中输入以下程序代码 fWatchDog = True 将激活的旗标设为 True 真正的送出动作在定时器中进行 3、 双击 激活看门狗 按钮 在其 Click 事件中输入以下程序代码 fWatchDog = True 将激活的旗标设为 True 真正的送出动作在定时器中进行 4、 双击 重置状态 按钮 在其 Click 事件中输入以下程序代码 fReset = True 第 379 页,共 606 页 将重置的旗标设为 True 真正的送出动作在定时器中进行 目 的在于将原本已被设定的重置状态清除掉 5、 双击定时器控件 在其 Timer 事件中输入以下程序代码 '检查是否激活 WatchDog If fWatchDog And (Not WatchDogEnabled) Then '送出 7060D 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = Val(txtTimeOut(0).Text) * 10 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0131" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7060D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7012D 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = Val(txtTimeOut(1).Text) * 10 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0231" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7012D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7021 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = Val(txtTimeOut(2).Text) * 10 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0331" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7021 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7080D 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = Val(txtTimeOut(3).Text) * 10 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0431" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7080D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" fWatchDog = False '设为 False 表示已作过设定 WatchDogEnabled = True '设为 True 表示已激活 Watch- Dog DoEvents End If '若勾选 HostOK 选项 则送出 OK 指令 If chkHostOK.Value = 1 Then SendHostOK MSComm1 lblMsg.Caption = "HostOK- >" & CStr(L) DoEvents L = L + 1 第 380 页,共 606 页 End If DoEvents '询问各模块的 Watch- Dog 状态 For i = 0 To 3 BufNo = Format(Hex(i + 1), "00") If Len(BufNo) = 1 Then BufNo = "0" & BufNo Buf = SendCmd2Comm(MSComm1, "~" & BufNo & "0", vbCr, 1000) lblMsg.Caption = "Ret- >" & Buf If Buf <> "" Then '若正确传回字符串 则分离出状态 If Val(Mid(Buf, 4, 2)) = 4 Then '若被 Reset 则显示红色 imgStatus(i).Picture = imgON.Picture Else '否则显示绿色 imgStatus(i).Picture = imgOFF.Picture End If End If DoEvents Next '检查是否清除 Reset 状态 If fReset Then For i = 0 To 3 BufNo = Format(Hex(i + 1), "00") If Len(BufNo) = 1 Then BufNo = "0" & BufNo Buf = SendCmd2Comm(MSComm1, "~" & BufNo & "1", vbCr, 1000) Next i fReset = False WatchDogEnabled = False DoEvents End If 真正的通讯工作在此事件中 整个程序分成四个部份进行 第一个部份是激活 Watch- Dog 的部份 当 激活看门狗 的按 钮被按下后 旗标被设为 True 此部份的程序代码就激活每个 模块的 Watch- Dog 功能(使用”~AA3EVV”指令) 在激活每个模 块的 Watch- Dog 功能时 也将画面上设定的每个模块逾时等候 时间一并传送给模块 第二部份是送出 HOST OK 指令(使用”~**”指令) 当画面上的 CheckBox 被勾选后 表示将要持续送出 OK 指令 而由于 OK 指令不含传回字符串 故未使用原来的传送子程序 而是另外 写一个传送的函式 第三部份是询问模块现在的状态 使用的是”~AA0”指令 传回 每一个模块的状态后(传回格式”!AASS” 若被设定时 SS 为 04) 后不同的颜色灯号显示在画面上 第四部份是清除状态 当按下 重置状态 按钮后 主控计算 机就送指令给每一个模块 要求清除被设定的 Reset 状态 设 定完成后 画面上的灯号会回复到绿色(“~AA0”指令的传回值中 的 SS 为 00) 6、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者按 下后就结束系统 第 381 页,共 606 页 以上项目执行后 若勾选 送出 HOST OK 再按下 激活看 门狗 则画面上的四个灯号会一直维持在绿色 侦测的过程中 取消 送出 HOST OK 则在一定的秒数后(依每个灯号下方的设 定秒数) 灯号会由绿转红 而使得实验的结果可能如图 9-2-12 所 示 图 9-2-12 Watch Dog 项目执行结果 图 9-2- 12 所显示的结果即已激活看门狗功能 而未送出 HOST OK 的指令 导致 10 秒钟后 定时器询问的模块状态有三个模块已 被设定 Watch- Dog 失败 而呈现红色灯号 以 上的程序代码 您可以参考光盘中 范例 \ 第九章 \ Watch-Dog 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以 上的测试 完整的程序代码列表如下 Option Explicit Dim fWatchDog As Boolean 'WatchDog 是否激活的旗标 Dim WatchDogEnabled As Boolean '是否已送出激活 WatchDog 机制旗标 Dim fReset As Boolean '清除 Reset 的旗标 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 重置状态 按钮后激活此事件 ' 将所有模块的 WatchDog 状态清除 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdReset_Click() fReset = True End Sub 第 382 页,共 606 页 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 激活看门狗 按钮后激活此事件 ' 只是简单地激活旗标 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdWatchDog_Click() fWatchDog = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口在窗体加载时就开启 让其它的程序可以开始使用通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() MSComm1.PortOpen = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件 固定时间周期执行内部程序代码一次 ' 与各模块的沟通在此事件中执行 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, BufNo$ Dim TimeOutStr$, Value1% Dim i% Static L& '检查是否激活 WatchDog If fWatchDog And (Not WatchDogEnabled) Then '送出 7060D 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = Val(txtTimeOut(0).Text) * 10 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0131" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7060D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7012D 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = Val(txtTimeOut(1).Text) * 10 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0231" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7012D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7021 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = Val(txtTimeOut(2).Text) * 10 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0331" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7021 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents 第 383 页,共 606 页 '送出 7080D 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = Val(txtTimeOut(3).Text) * 10 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0431" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7080D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" fWatchDog = False '设为 False 表示已作过设定 WatchDogEnabled = True '设为 True 表示已激活 Watch- Dog DoEvents End If '若勾选 HostOK 选项 则送出 OK 指令 If chkHostOK.Value = 1 Then SendHostOK MSComm1 lblMsg.Caption = "HostOK- >" & CStr(L) DoEvents L = L + 1 End If DoEvents '询问各模块的 Watch- Dog 状态 For i = 0 To 3 BufNo = Format(Hex(i + 1), "00") If Len(BufNo) = 1 Then BufNo = "0" & BufNo Buf = SendCmd2Comm(MSComm1, "~" & BufNo & "0", vbCr, 1000) lblMsg.Caption = "Ret- >" & Buf If Buf <> "" Then '若正确传回字符串 则分离出状态 If Val(Mid(Buf, 4, 2)) = 4 Then '若被 Reset 则显示红色 imgStatus(i).Picture = imgON.Picture Else '否则显示绿色 imgStatus(i).Picture = imgOFF.Picture End If End If DoEvents Next '检查是否清除 Reset 状态 If fReset Then For i = 0 To 3 BufNo = Format(Hex(i + 1), "00") If Len(BufNo) = 1 Then BufNo = "0" & BufNo Buf = SendCmd2Comm(MSComm1, "~" & BufNo & "1", vbCr, 1000) Next i fReset = False WatchDogEnabled = False DoEvents End If End Sub 模块内的程序则如下 Option Explicit Declare Function GetTickCount Lib "kernel32" () As Long ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 时间延迟子程序 单位为毫秒(ms) 第 384 页,共 606 页''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 指令送出 并等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'CS 是命令字符串 不需含结尾字符 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function SendCmd2Comm(Comm As MSComm, CS As String, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = Comm.Input Buf = OutCheckSum(CS) 'CheckSum 计算 Comm.Output = Buf '指令送出 Buf = "" TT = GetTickCount '等待直到时间到 或是字符串传回 Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then '以下是传回值 CheckSum 的检查 SendCmd2Comm = INCheckSum(Buf) Else SendCmd2Comm = "" End If End Function 'HOST OK 指令送出 'Comm 是通讯组件名称 Sub SendHostOK(Comm As MSComm) Dim Buf$, TT As Long Buf = Comm.Input Buf = OutCheckSum("~**") 'CheckSum 计算 Comm.Output = Buf '指令送出 End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将要送出的字符串计算 CheckSum 的结果 'InBuf 是传入的字符串 ' 函式的传回值是作了 CheckSum 的结果字符串 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function OutCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Long BufLen = Len(InBuf) '取得传入字符串的长度 第 385 页,共 606 页 CheckSum = 0 '初始化 For i = 1 To BufLen Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i '字符串重组 并加上结尾字符 Cr Buf = IIf(Len(Hex(CheckSum)) = 1, "0" & Hex(CheckSum), Hex(CheckSum)) OutCheckSum = InBuf & Buf & vbCr End Function '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将模块传回的字符串检查 CheckSum 是否正确 'InBuf 是由模块传回的字符串 ' 函式的传回值为去除 CheckSum 及结尾字符( 正确时) ' 或空字符串( 不正确时) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function INCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Integer BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen - 3 '需扣掉二个 CheckSum 字符和结尾字符 Buf = Mid(InBuf, i, 1) '取出字符 '和 & HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i If CheckSum = Val("&H" & Mid(InBuf, BufLen - 3 + 1, 2)) Then INCheckSum = Mid(InBuf, 1, BufLen - 3) Else INCheckSum = "" End If End Function 9-2-6 安全值及开机值程序的建立 了解看门狗的相关机制后 本小节以 7060D 7021 模块为例 说明如何建立安全值及开机值的项目 画面设计如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 MSComm 控件 按下 F4 将 CommPort 属性改 为 1 使程序使用 COM1 为通讯的信道 其它的属性均维持默 认值 读者可视需要改为其它的通讯端口 3、 安排一个定时器控件 按下 F4 叫出属性窗口 其 Interval 属性 改为 100 使其每 100 毫秒执行一次 Timer1 事件内的程序代码 4、 安排一个 CheckBox 控件 按下 F4 叫出属性窗口 其 Name 属性 第 386 页,共 606 页改为 chkHostOK 按出 Font 属性 并将字号设为 12 作为是否 送出 Host OK 指令的旗标 5、 安排二个 Frame 控件 按下 F4 叫出属性窗口 其 Caption 属性分别 改为”模块输出状态”及 ”模块输出控制” 用于放置各显示控件 6、 安排二个 Image 控件 按下 F4 叫出属性窗口 其 Name 属性分别改 为 imgGreen 及 imgRed 它们的 Visible 属性设成 False 并加载 绿色及红色球形 此二个 Image 将作为状态显示之用 并放入 Frame 中 7、 安排二个 Image 控件 按下 F4 叫出属性窗口 其 Name 属性分别改 为 imgOFF 及 imgON 它们的 Visible 属性设成 False 并加载 OFF 开关及 ON 开关 此二个 Image 将作为 Relay 开关显示之 用 8、 安排四个 Image 控件 按下 F4 叫出属性窗口 其 Name 属性改 为 imgStatus 并设成对象数组 作为 Relay 显示模块状态之用 并放入 Frame 中 9、 安排四个 Image 控件 按下 F4 叫出属性窗口 其 Name 属性改 为 imgRL 并设成对象数组 作为 Relay 控制显示之用 10、 安排四个 Label 控件 按下 F4 叫出属性窗口 其 Caption 属 性改为”7060D” ”7060D” ”7012D” ”7012D” 作为显示之用 11、 安排五个命令按钮 按下 F4 叫出属性窗口 其 Name 属性 改 为 ”cmdPowerOn” ”cmdSafe” ”cmdWatchDog” ”cmdReset” ”cmdEnd” 而 Caption 属性则分别改为”设为开机值” ”设为 安全值” ”激活看门狗” ”重置状态” ”结束” 作为相对命 令激活之用 12、 设计后之画面如图 9- 2- 11 图 9-2-13 安全值/开机值项目的设计画面 第 387 页,共 606 页动作流程解析 程序会在定时器不断地运作下 询问每个模块的 输出状态 并将状态以红色灯或绿色灯显示在画面上 送出 HOST OK 的选择用来选择是否对所有的模块送出此一指令 告诉模块主 控计算机仍处于正常状态 当按下 激活看门狗 后 主控计算机会 送给每个模块指令 使用者仍可由画面右方的 Relay 控制按钮控制 Relay 的动作 而其下方的模拟输出值 则可由使用输入电压值 在 改变时 7021 随即将模拟值输出 左方的状态区会显示出 7060D 及 7021 二者的输出状态 我们要观察正常状况及 Watch- Dog 激活并超 过逾时时间的情形下 左右双方的状态是否一致 当逾时时间超过后 按下 重置状态 会使得每个模块内部的逾 时旗标被清除 而且看门狗的设定也会被取消 程序代码部份如下 1、 双击窗体 在其 Load 事件中输入以下程序代码 MSComm1.PortOpen = True For i = 0 To 3 imgRL(i).Picture = imgOFF.Picture Next i 'Relay 全部先关闭 Buf = SendCmd2Comm(MSComm1, "#010000", vbCr, 2000) 开启通讯端口 准备接下来的通讯工作 7060D 的数字输出先 全部清空 并将开关图形全调为 OFF 的状态 2、 双击 激活看门狗 按钮 在其 Click 事件中输入以下程序代码 fWatchDog = True 将激活的旗标设为 True 真正的送出动作在定时器中进行 3、 双击 激活看门狗 按钮 在其 Click 事件中输入以下程序代码 fWatchDog = True 将激活的旗标设为 True 真正的送出动作在定时器中进行 4、 双击 重置状态 按钮 在其 Click 事件中输入以下程序代码 fReset = True 将重置的旗标设为 True 真正的送出动作在定时器中进行 目 的在于将原本已被设定的重置状态清除掉 5、 双击 设为开机值 按钮 在其 Click 事件中输入以下程序代码 fPowerOn = True 第 388 页,共 606 页将旗标设为 True 真正的送出动作在定时器中进行 6、 双击 设为安全值 按钮 在其 Click 事件中输入以下程序代码 fSafe = True 将旗标设为 True 真正的送出动作在定时器中进行 7、 双击右方的 Image 控件 在其 Click 事件中输入以下程序代码 RLOut(Index) = Not RLOut(Index) '转态 If RLOut(Index) Then '变更图形 imgRL(Index).Picture = imgON.Picture Else imgRL(Index).Picture = imgOFF.Picture End If 将 7060 数字输出的旗标设为转态(由 ON 改为 OFF 由 OFF 改 为 ON) 并改变 ON-OFF 的图形 真正的送出动作在定时器中 进行 8、 双击右方的文字框控件 在其 Changek 事件中输入以下程序代码 f7021 = True 将 7021 模拟输出的旗标设为 True 真正的送出动作在定时器中 进行 9、 双击定时器控件 在其 Timer 事件中输入以下程序代码 '检查是否激活 WatchDog If fWatchDog And (Not WatchDogEnabled) Then '送出 7060D 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = 10 * 10 '10 秒 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0131" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7060D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7021 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = 15 * 10 '15 秒 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0331" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7021 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents fWatchDog = False '设为 False 表示已作过设定 WatchDogEnabled = True '设为 True 表示已激活 Watch- Dog DoEvents End If 第 389 页,共 606 页 '询问各模块的 Watch- Dog 状态 For i = 1 To 3 Step 2 BufNo = Format(Hex(i), "00") Buf = SendCmd2Comm(MSComm1, "~" & BufNo & "0", vbCr, 1000) If Buf <> "" Then '若正确传回字符串 则分离出状态 If Val(Mid(Buf, 4, 2)) = 4 Then '若被 Reset 则显示红色 If i = 1 Then '7060D lbl7060.FontBold = True lbl7060.ForeColor = RGB(255, 0, 0) Else '7021 lbl7021.FontBold = True lbl7021.ForeColor = RGB(255, 0, 0) End If Else '否则显示绿色 If i = 1 Then '7060D lbl7060.FontBold = False lbl7060.ForeColor = RGB(0, 255, 0) Else '7021 lbl7021.FontBold = False lbl7021.ForeColor = RGB(0, 255, 0) End If End If End If DoEvents Next '作 7060 的数字输出 DOValue = 0 For i = 0 To 3 DOValue = DOValue + IIf(RLOut(i), 1, 0) * 2 ^ i Next i Buf = SendCmd2Comm(MSComm1, "#01000" & Hex(DOValue), vbCr, 1000) '作 7021 的模拟输出 If f7021 Then Buf = SendCmd2Comm(MSComm1, "#03" & Format(Val(txt7021.Text), "00.000"), vbCr, 1000) f7021 = False End If '检查 7060 的输出状态 Buf = SendCmd2Comm(MSComm1, "$016", vbCr, 1000) If Buf <> "" Then DOValue = Val("&H" & Mid(Buf, 2, 2)) For i = 0 To 3 If (DOValue And 2 ^ i) = 2 ^ i Then 'RL 激活 imgStatus(i).Picture = imgRed.Picture Else imgStatus(i).Picture = imgGreen.Picture End If Next i End If '检查 7021 的输出值 Buf = SendCmd2Comm(MSComm1, "$036", vbCr, 1000) lblVoltage.Caption = Mid(Buf, 4) & "V" '检查是否清除 Reset 状态 If fReset Then 第 390 页,共 606 页 Buf = SendCmd2Comm(MSComm1, "~011", vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "~031", vbCr, 1000) fReset = False WatchDogEnabled = False DoEvents End If '检查是否设为开机值 If fPowerOn Then Buf = SendCmd2Comm(MSComm1, "~015P", vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "$034", vbCr, 1000) fPowerOn = False End If '检查是否设为安全值 If fSafe Then Buf = SendCmd2Comm(MSComm1, "~015S", vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "~035", vbCr, 1000) fSafe = False End If '若勾选 HostOK 选项 则送出 OK 指令 If chkHostOK.Value = 1 Then SendHostOK MSComm1 End If DoEvents 真正的通讯工作在此事件中 整个程序分成 7 个部份进行(指令 的说明见 9- 2- 4 小节) 第一个部份是激活 Watch- Dog 的部份 当 激活看门狗 的按 钮被按下后 旗标被设为 True 此部份的程序代码就激活每个 模块的 Watch- Dog 功能(使用”~AA3EVV”指令) 在激活每个模 块的 Watch- Dog 功能时 也将画面上设定的每个模块逾时等候 时间一并传送给模块 第二部份是询问模块现在的状态 使用的是”~AA0”指令 传回 每一个模块的状态后(传回格式”!AASS” 若被设定时 SS 为 04) 后不同的颜色灯号显示在画面上 第三部份是 7060D 和 7021 的输出控制部份 当输出的旗标被激 活后 就执行输出的指令 第四部份是 7060D 和 7021 的输出状态回传部份 下达状态询问 指令 要求模块传回输出的状态 并显示到画面上 第五部份是清除状态 当按下 重置状态 按钮后 主控计算 机就送指令给每一个模块 要求清除被设定的 Reset 状态 设 定完成后 画面右方的控制动作就会和左方的状态显示出一致 的情形 第六部份是开机值及安全值的设定 当按下 设为开机值 按 钮或 设为安全值 的按钮后 主控计算机就送指令让模块将 现在的输出状态设为开机值或安全值 第 391 页,共 606 页第七部份是送出 HOST OK 指令(使用”~**”指令) 当画面上的 CheckBox 被勾选后 表示将要持续送出 OK 指令 而由于 OK 指令不含传回字符串 故未使用原来的传送子程序 而是另外 写一个传送的函式 10、 双击 结束 的按钮 在其 Click 事件中输入 End 当使用者 按下后就结束系统 以上项目执行后 预设会勾选 送出 HOST OK 按下 激活 看门狗 则当控制画面右方的控制区之 Relay 及模拟输出值时 左方的状态会显示出相同的状态 也可以在实验的过程中将某一个 输出状态以按下 设为开机值 或 设为安全值 将该状态设为模 块的开机值或安全值 实验的结果可能如图 9-2-14 所示 图 9-2-14 Watch Dog 项目执行结果 若将 送出 HOST OK 的选项取消 主控计算机将不会送出清 除 Watch- Dog 状态的指令 模块将会因逾时时间到达而使得模块进 入安全状态 一切的输出状态会出现安全设定值 并且不接受输出 的指令 如图 9-2- 15 当安全状态开启后 输出状态一律以安全值 出现(7060D 的 RL1 为 ON 其余为 OFF 7021 的输出为 2V) 原来 左方绿色出现的模块号码变成红色 当以右方的控制区予以控制 时 不管键入何值或切换何开关 均被模块忽略不执行 故画面二 边的数值状态不一致 第 392 页,共 606 页图 9-2-15 Watch Dog 项目执行结果 以上的程序代码 您可以参考光盘中 范例 \ 第九章 \ 开 机值-安全值 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以 上的测试 完整的程序代码列表如下 Option Explicit Dim fWatchDog As Boolean 'WatchDog 是否激活的旗标 Dim WatchDogEnabled As Boolean '是否已送出激活 WatchDog 机制旗标 Dim fReset As Boolean '清除 Reset 的旗标 Dim f7021 As Boolean '电压值是否改变的旗标 Dim fPowerOn As Boolean '设为开机值旗标 Dim fSafe As Boolean '设为安全值旗标 Dim RLOut(0 To 3) As Boolean 'Relay 是否 ON 的旗标 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 设为开机值 按钮后激活此事件 ' 将旗标设为 True 真正的动作在定时器作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdPowerOn_Click() fPowerOn = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 重置状态 按钮后激活此事件 ' 将所有模块的 WatchDog 状态清除 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdReset_Click() fReset = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 设为安全值 按钮后激活此事件 ' 将旗标设为 True 真正的动作在定时器作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdSafe_Click() fSafe = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 激活看门狗 按钮后激活此事件 ' 只是简单地激活旗标 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdWatchDog_Click() 第 393 页,共 606 页 fWatchDog = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口在窗体加载时就开启 让其它的程序可以开始使用通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim i%, Buf$ MSComm1.PortOpen = True For i = 0 To 3 imgRL(i).Picture = imgOFF.Picture Next i 'Relay 全部先关闭 Buf = SendCmd2Comm(MSComm1, "#010000", vbCr, 2000) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'Relay 的控制 ' 改变按钮的状态 并将旗标设定起来 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub imgRL_Click(Index As Integer) RLOut(Index) = Not RLOut(Index) '转态 If RLOut(Index) Then '变更图形 imgRL(Index).Picture = imgON.Picture Else imgRL(Index).Picture = imgOFF.Picture End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件 固定时间周期执行内部程序代码一次 ' 与各模块的沟通在此事件中执行 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$, BufNo$, i% Dim TimeOutStr$, Value1% Dim DOValue As Integer '检查是否激活 WatchDog If fWatchDog And (Not WatchDogEnabled) Then '送出 7060D 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = 10 * 10 '10 秒 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0131" & TimeOutStr, vbCr, 2000) If Buf = "" Then MsgBox "7060D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7021 的 WatchDog 设定 '转换逾时格式为十六进制 Value1 = 15 * 10 '15 秒 TimeOutStr = Format(Hex(Value1), "00") If Len(TimeOutStr) = 1 Then TimeOutStr = "0" & TimeOutStr Buf = SendCmd2Comm(MSComm1, "~0331" & TimeOutStr, vbCr, 2000) 第 394 页,共 606 页 If Buf = "" Then MsgBox "7021 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents fWatchDog = False '设为 False 表示已作过设定 WatchDogEnabled = True '设为 True 表示已激活 Watch- Dog DoEvents End If '询问各模块的 Watch- Dog 状态 For i = 1 To 3 Step 2 BufNo = Format(Hex(i), "00") Buf = SendCmd2Comm(MSComm1, "~" & BufNo & "0", vbCr, 1000) If Buf <> "" Then '若正确传回字符串 则分离出状态 If Val(Mid(Buf, 4, 2)) = 4 Then '若被 Reset 则显示红色 If i = 1 Then '7060D lbl7060.FontBold = True lbl7060.ForeColor = RGB(255, 0, 0) Else '7021 lbl7021.FontBold = True lbl7021.ForeColor = RGB(255, 0, 0) End If Else '否则显示绿色 If i = 1 Then '7060D lbl7060.FontBold = False lbl7060.ForeColor = RGB(0, 255, 0) Else '7021 lbl7021.FontBold = False lbl7021.ForeColor = RGB(0, 255, 0) End If End If End If DoEvents Next '作 7060 的数字输出 DOValue = 0 For i = 0 To 3 DOValue = DOValue + IIf(RLOut(i), 1, 0) * 2 ^ i Next i Buf = SendCmd2Comm(MSComm1, "#01000" & Hex(DOValue), vbCr, 1000) '作 7021 的模拟输出 If f7021 Then Buf = SendCmd2Comm(MSComm1, "#03" & Format(Val(txt7021.Text), "00.000"), vbCr, 1000) f7021 = False End If '检查 7060 的输出状态 Buf = SendCmd2Comm(MSComm1, "$016", vbCr, 1000) If Buf <> "" Then DOValue = Val("&H" & Mid(Buf, 2, 2)) For i = 0 To 3 If (DOValue And 2 ^ i) = 2 ^ i Then 'RL 激活 imgStatus(i).Picture = imgRed.Picture Else imgStatus(i).Picture = imgGreen.Picture End If Next i End If 第 395 页,共 606 页 '检查 7021 的输出值 Buf = SendCmd2Comm(MSComm1, "$036", vbCr, 1000) lblVoltage.Caption = Mid(Buf, 4) & "V" '检查是否清除 Reset 状态 If fReset Then Buf = SendCmd2Comm(MSComm1, "~011", vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "~031", vbCr, 1000) fReset = False WatchDogEnabled = False DoEvents End If '检查是否设为开机值 If fPowerOn Then Buf = SendCmd2Comm(MSComm1, "~015P", vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "$034", vbCr, 1000) fPowerOn = False End If '检查是否设为安全值 If fSafe Then Buf = SendCmd2Comm(MSComm1, "~015S", vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "~035", vbCr, 1000) fSafe = False End If '若勾选 HostOK 选项 则送出 OK 指令 If chkHostOK.Value = 1 Then SendHostOK MSComm1 End If DoEvents End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当使用者改变文字框内的数值时 此事件被引发 ' 设定改变旗标为 True 在定时器中再读取数值 并送出 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub txt7021_Change() f7021 = True End Sub 模块内的程序则如下 Option Explicit Declare Function GetTickCount Lib "kernel32" () As Long ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 时间延迟子程序 单位为毫秒(ms) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents 第 396 页,共 606 页Loop Until GetTickCount() - TT >= t End Sub ' 指令送出 并等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'CS 是命令字符串 不需含结尾字符 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function SendCmd2Comm(Comm As MSComm, CS As String, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = Comm.Input Buf = OutCheckSum(CS) 'CheckSum 计算 Comm.Output = Buf '指令送出 Buf = "" TT = GetTickCount '等待直到时间到 或是字符串传回 Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then '以下是传回值 CheckSum 的检查 SendCmd2Comm = INCheckSum(Buf) Else SendCmd2Comm = "" End If End Function 'HOST OK 指令送出 'Comm 是通讯组件名称 Sub SendHostOK(Comm As MSComm) Dim Buf$, TT As Long Buf = Comm.Input Buf = OutCheckSum("~**") 'CheckSum 计算 Comm.Output = Buf '指令送出 End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将要送出的字符串计算 CheckSum 的结果 'InBuf 是传入的字符串 ' 函式的传回值是作了 CheckSum 的结果字符串 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function OutCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Long BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i 第 397 页,共 606 页 '字符串重组 并加上结尾字符 Cr Buf = IIf(Len(Hex(CheckSum)) = 1, "0" & Hex(CheckSum), Hex(CheckSum)) OutCheckSum = InBuf & Buf & vbCr End Function '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将模块传回的字符串检查 CheckSum 是否正确 'InBuf 是由模块传回的字符串 ' 函式的传回值为去除 CheckSum 及结尾字符( 正确时) ' 或空字符串( 不正确时) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function INCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Integer BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen - 3 '需扣掉二个 CheckSum 字符和结尾字符 Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i If CheckSum = Val("&H" & Mid(InBuf, BufLen - 3 + 1, 2)) Then INCheckSum = Mid(InBuf, 1, BufLen - 3) Else INCheckSum = "" End If End Function 第 398 页,共 606 页本章习题 1、 描述将模块的传输速度由 9600BPS 改为 19200BPS 的步骤 2、 参考 9-2-3 的范例程序 将 7060D 7012D 的 CheckSum 改 为 Enable 而 7021 7080D 的 CheckSum 设为 Disable 如 此一来的程序应如何撰写 3、 参考 9-2-5 范例程序 修改程序使 Watch-Dog 状态被模块激 活后 程序自动送出清除指令 并重新激活看门狗机制 第 399 页,共 606 页第十章 整合实验 前一章引入 CheckSum 的使用 已经可以让模块在传送时较有保 障 我们也比较不会取到不正确的资料 第五章至第八章则是分别 详述了具代表性的模块在操作上的注意事项 本章中我们将结合第 五章至第九章的相关经验 设计一个监控系统 由于已经进入到第十章 相信读者对于之前的章节也已了解 本章部份的组件设计步骤直接说明 不再详细解释其中的部份细 节 当读者不太了解笔者对于画面设计的说明时 请参阅前面章节 的说明 本章所使用在之前的章节均被使用过 若未使用过 笔者 会特别说明 10-1 被监控系统 不同的讯号型态可用不同功能的分布式模块予以达到讯号撷取 的目的 讯号大致分为模拟输入 模拟输出 数字输入 数字输出 频率/计数输出 频率/计数输入这几类 活用这几类型态的模块便 可效设计出监控系统 我们将由各子画面设计到监控程序设计逐步 完成此一监控系统项目 10-1-1 系统架构 本章所欲完成的监控系统为一动力系统 由加热装置对一锅炉加 热 所产生的水蒸汽经一管路而至涡轮机 利用水蒸汽之高压通过 涡轮机时产生推力 进而使涡轮机转动 而转动之涡轮机再带动其 它的设备 系统布置及组件说明如图 10-1-1 所示 第 400 页,共 606 页 图 10-1-1 系统各组件说明 系统每个子部份说明如下 1、 锅炉热源为下方的加热装置提供 其中有一个阀门开关可控 制其是否加热 2、 加水装置由一部马达控制 用以将水送入锅炉中 3、 警戒灯及泄汽装置用来警戒温度状况 及警急排出蒸汽之 用 4、 阀门开关用来控制水蒸汽的通过与否 5、 压力阀为当该段管路压力过大时会自动激活 6、 涡轮机前后二级的速度有传感器可侦测之 在这些要求中 我们先分离出输出入方向 必须被量测到的参数 就是输入 而需要被控制的参数即为输出 因此上述的子系统可以 归纳出 1、 锅炉温度需被量测 故为输入 数量为 1 个 2、 加水马达需被控制 故为输出 数量为 1 个 3、 警戒灯及泄汽装置需被控制 故它们是输出 数量为 2 个 4、 阀门开关需被控制 故为输出 数量为 4 个 5、 压力阀用被来通知我们相关状态 故为输入 数量为 4 个 6、 二级的转速需被量测 故为输入 数量为 2 个 第 401 页,共 606 页有了输出入方向及数量的分析后 便可以适当地选择分布式模 块 10-1-2 监控要求 此系统中温度是相当重要的参数 加热系统中 温度的掌控必 须适当 太低无法推动涡轮机 而太高时 可能会造成危险 压力也是系统中必须时常注意的参数 压力过高同样会造成不 好影响 因此在管路中均设计有压力阀 在压力过高时会产生动作 也可传出讯号 系统被监控时 要求下列各项必须作到 1、 温度须实时地显示出来 2、 温度的规定范围须可设定 3、 温度过低时 警戒灯亮起 4、 温度过高时 开启泄汽阀门 关闭热源开关 并同步加水 5、 温度过高时 能发出声音通知监控人员 6、 压力阀装置为自动激活 这些压力阀状态必须显示出来 7、 阀门开关的状态也必须显示出来 同时 可由画面上控制这 些阀门开关的状态 8、 涡轮机的转速有前后二级的讯号拉出 这二个转速值须显示 在画面上 9、 温度 二个转速值均能在画面上显示其趋势图 第 402 页,共 606 页10-2 监控系统的画面设计 监控系统程序的设计一般的程序是由画面设计开始 当画面设计 完成后 再接下去作程序的撰写 笔者也强烈建议如此的程序 针 对上一节的系统描述 本节将对应出实验的模块及相对应的接点 并说明设计细节 10-2-1 系统对照图 首先考虑系统架构图及相关的要求 我们可以将原来的系统图 放置在监控画面上 目的在于让使用者可以明了所监控的系统构 造 一方面也容易明白所监控参数所在位置 依照上节所述的要求 我们将使用 7012 7060 7021 7060 四个模块 并依模块的特性 列出参数和模块间的对应关系 1、 7012 用于量测锅炉温度 并激活警戒设定 使其可以激活警 戒灯及泄汽阀门 2、 7021 用于控制加水马达 3、 7060 用来显示压力阀状态及控制阀门开关 4、 7080 用来量测涡轮机前后二级的转速 利用图 10-1- 1 及上述讨论的模块 结合系统要求后可用 Visual Basic 设计如图 10- 2- 1 所示的系统布置图 图 10-2-1 系统布置与参数对照 系统布置图(System Layout)将各参数的位置标出 并以不同的名称 第 403 页,共 606 页作为区别 这些参数用途及其所对应的分布式模块编号如表 10-2- 1 表 10- 2- 1 系统布置图参数说明 参数 名 称 作用 输出入 对应模块 连接脚位 T 量测锅炉温度 输入 7012 +IN - IN E1 低温警戒 输出 7012 DO0/LO E2 高温警戒 输出 7012 DO1/HI V 加水马达控制 输出 7021 +Vout - Vout F1 涡轮机转速- 1 输入 7080 In0 F2 涡轮机转速- 2 输入 7080 In1 R1 热源开关 输出 7060 RL1 R2 阀门开关 输出 7060 RL2 R3 阀门开关 输出 7060 RL3 R4 阀门开关 输出 7060 RL4 P1 压力阀 输入 7060 DI0 P2 压力阀 输入 7060 DI1 P3 压力阀 输入 7060 DI2 P4 压力阀 输入 7060 DI3 如何利用 Visual Basic 作出图 10- 2-1 的画面供使用者参考呢? 我们以下就依各步骤予以说明 1、 先将系统的布置图绘出 我们称为系统原图 读者可使用 PowerPoint CorelDraw 或其它的绘图软件制作 这部份不 在本书讨论范围 请读者参考其它的绘图相关书藉 2、 在 Visual Basic 窗体中布置一个图片框 叫出其属性表 点 选 Picture 属 性 加载系统原图(就如图 10-1- 1 样子的图形) 3、 安排一个 Label 控件 并置放于图片框的右下角 其 Caption 属性输入”SYSTEM LAYOUT” 改变适当字型 加上底线 4、 安排 14 个 Label 控件 置放于图片框中 各 Label 控件的 Caption 属性值依表 10- 2-1 中的参数名称输入 控件的颜色 依目的的不同分别调整 5、 各 Label 控件的位置稍作安排 使其整齐(如图 10- 2- 1 中的 安排是靠上靠左) 6、 为了让使用者明白所在位置 各 Label 控件和所代表的各硬 第 404 页,共 606 页件组件间以细线条予以连接 为免颜色的混淆 笔 者使用绿 色作为连接线 7、 至此算是完成了布置图的设计了 10-2-2 状态值的显示 上一小节完成了系统对照图的设计 由于这些组件均是放在图 片框内 图片框为一对象收纳器(Container) 只要移动该图片框 里面的所有对象便会跟着移动 我们可以依据情形将其位置作排 列 另一个必须考虑的是状态值的显示 将各参数标示在图片上后 可以让使用者了解每一个参数的实际所在位置 但是还是要将参数 的数值或状态显示在画面上供使用者查看 根据上一小节的讨论 系统共有 14 个参数需要显示出来 参 数大概可以略分为数值及状态二种 笔者将数值的直接显示出来 而状态的部份则以灯号予以表示 图 10- 2-2 是一个设计的例子 图 10-2-2 参数显示的设计 该显示区以一个状态区的范围将其集中在一起 数值的部份显 示在左方 而其它的状态部份则是以红绿色的灯号标示出 如果是 ON 就设定显示红灯 若状态是 OFF 那么就显示绿灯 14 个参数 就可以在一个 Frame 控件框架中显示完成 将以上的画面以 Visual Basic 的方式作设计时 其步骤如下 1、 在画面上安排一个 Frame 控件 拉出至适当的外观 并在其 Caption 属性中填入”状态区” 也可改变其 ForeColor 颜色属性 2、 此安排 14 个 Label 控件 并将其中的 Caption 属性改成表 10- 2-1 中的 14 个参数名称 各 Label 的颜色定成和图 10- 2-1 中参数名 称一样 第 405 页,共 606 页3、 温度 压力 转速共 4 个 使用 4 个 Label 控件 其 Caption 属 性先填入”????” 并调整其 Font 属性至适当的大小 4、 其它的状态显示因为要以图形显示 我们安排了 10 个 Image 控 件 它们的 Stretch 属性均调成 True 表示可以让图形依控件的 大小自动作缩放 5、 为了要能切换红绿色灯号 另需安排二个 Image 控件 名称可 定为 imgRed imgGreen 并在它们的 Picture 属性中分别选择 红色及绿色的灯号的图档 6、 安排后的设计画面可如图 10- 2- 3 所示 图 10-2-3 设计模式下的参数显示设计 这样子也就完成了参数的显示部份的画面设计 10-2-3 趋势图的画面 绘图方法的使用在第六章时作过讨论 在 6- 2- 5 节讨论了在 Visual Basic 中绘图的基本观念 在 6- 2-6 小节则是讨论了如何作出 扫描式的绘图方法 使得图形不会有闪烁的现象 本章对于图形的处理 采用的是 6-2- 6 小节的方法 希望所表 现出来的就如图 10- 2-4 所示 其中共有三种不同的线条 它们分别 是温度 转速 1 及转速 2 第 406 页,共 606 页 图 10-2-4 执行时的绘图情形 将以上的画面以 Visual Basic 的方式作设计时 其步骤如下 1、 在画面上安排一个 Frame 控件 拉出至适当的外观 并在其 Caption 属性中填入”趋势图形” ForeColor 属性设成蓝色或其 它读者认为适当的颜色 2、 此安排 2 个 图片框控件 并将其中的 Name 属性改为 picHide 及 picShow picHide 图片框的 AutoRedraw 属性设成 True 但 picShow 的 AutoRedraw 属性设成 False 此二各图片框设定成一 样的大小 3、 在 picShow图片框的二边分别安排 3 个 Label控件 左边的 Label 作为温度值的范围标示 而右边的 label 则作为转速的范围标 示 4、 安排后的设计画面可如图 10- 2- 5 所示 图 10-2-5 设计阶段的控件安排情形 图形显示的初步于是完成 至于更进一步的图形绘图显示需有 程序的辅助 我们将在下节说明 第 407 页,共 606 页10-2-4 完整的画面 由前面三个小节的讨论 主要的画面部份已经都完成了 接下 来是将这三个子画面组合起来 再加上一些必须的控件形成一个完 整的监控画面 如图 110-2-6 所示 图 10-2-6 完整主画面设计 笔者将前三小节所讨论的设计安排在同一个主画面中 并且加 上了一些其它的控件 所加的控件集中在右下方部份 分述如下 1、 红色与绿色的灯号 这二个是 Image 控件 也是在右上方的显 示区所要使用的显示灯号 2、 类似 CD 控制盘面的多媒体控件 此多媒体控件可以拿来控制 声卡而发出声音 我们将利用此一控件发出警报声响 3、 一个通讯控件 它将用来和分布式模块通讯 我们之前的的每 个项目也都使用到 4、 定时器控件 利用此控件的固定时间执行一次程序代码的特性 我们将使用它来首分布式模块作通讯 5、 二个按钮 它们分别用来激活监控动作的开始/停止及结束系 统 完成整个主控画面的制作后 其它的就要靠程序予以完成 接 下来继续讨论相关的程序作法 第 408 页,共 606 页10-3 监控系统程序设计 上一节我们完成了系统的主画面 但尚未加入程序 接下来的部 份将继续地加入监控的程序 10-3-1 程序设计步骤分析 先不要急着写程序 这点很重要 系统虽然大小有别 不过通 常不会有那种非常简单 而不需要思考再三的项目交到我们的手 中 所以不要期望有那种只要随便 Coding 几下就可以完成项目的机 会 还是要先思考一下如何下手比较安全 毕 竟多花时间在事先的 准备及思考上 就可以少花时间在事后的修改及除错(就算是自己写 的程序 修改 除错也是相当累人的一件工作) 先考虑监控程序的执行过程 监控程序的主要流程可以分成 1、 数值的读取及显示 其中包括了 (1). 锅炉温度读取及显示(使用 7012 模块) (2). 涡轮机前后级转速的读取及显示(使用 7080 模块) (3). 所有压力阀及阀门开关的读取及显示(7060 模块) (4). 加水马达的控制电压的读取及显示(7021 模块) 2、 警戒的处理 其中包括了 (1). 温度过高时的自动警戒 (2). 压力阀开关作动时的处理 3、 控制动作的执行 其中包括了 (1). 各阀门开阀的控制 (2). 加水马达的电压控制 4、 绘图的呈现 其中包括了温度 前级转速 后级转速的数值绘 图 5、 以 7012 的电压值表示成温度值 因此必须有一个使用者可输入 转换值的灵敏度输入设定区 第 409 页,共 606 页6、 使用者可输入的高低温度警戒值设定 此数值将设定 7012 作自 动警戒输出 了解应处理的程序流程后 我们也可以发现如果照着上述的程 序作下去的话 模块会交错着出现在程序中 这也不是什么不好的 事情 不过笔者认为如果可以每个模块尽可能地分开撰写程序代 码 将可以让程序更容易了解 也容易作往后的维护 因此 照着 上述的程序 以下的各节就依各模块为对象 分别发展各别模块的 程序代码 发展的过程中 完全依照上述的流程进行 10-3-2 7012 模块部份 在第六章讨论到 7012D 的相关实验时 7012D 模块的站号被设 成 02 在此沿用此站号在此模块上 查照上一小节的程序 和 7012D 模块相关的程序以流程图表示如图 10- 3- 1 讀取電壓值 轉換為溫度值 顯示溫度值 改變參數顏色低值警戒發生? 高值警戒發生? 改變參數顏色 啟動警報聲響 改變警戒值? 改變警戒設定 是 否 是 是 結束 開始 图 10- 3- 1 7012 工作流程 各部份所使用的程序技巧如下所述 1、 读取电压值 及将电压值转换为温度值的部份 使用的函式 为 SendCmd2Comm 是在第九章时发展完成的函式 其中含 有 CheckSum 的检查 可以使得结果较为正确 函式传回值 中即含有电压值 再依其传回格式取出电压值 转换值以一 第 410 页,共 606 页变量 TempSensitivity 记录 乘上此值即为实际的温度值 此 温度值也存入变量 PlotValue 中 以备绘图之用 2、 显示部份 将转换完成的数值显示到主画面上右边显示区的 温度区(Label 控件的 Caption 属性)即可 3、 查看警戒状态是否被模块激活部份 7012 模块中下 达 ”@02DI”即可传回模块的警戒开关状态字符串 再依所代 表的数值查出警戒状态 注意 此时并不是使用程序去作输 入值的比对 而是利用模块传回的状态字符串 这二种方式 是不一样的 查出的警戒状态后 改变状态区中的警戒灯号 若引发警戒并转换系统布置图上的参数名称底色颜色 以便 于辨认位置 4、 激活警报声响部份 这里需要使用一个 Visual Basic 中的 MMControl 控件 此控件可用于控制多媒体设备 在我们这 个项目的中 只有在温度过高时 会由监控系统发出警报声 所以可以在系统程序加载时预先将所要使用警报声档案也加 载 有关多媒体控件的说明 请见后述 5、 是否改变高低警戒部份 当改变的旗标为 ON 时 将高低警 戒的数值传送至模块 使模块本身的警戒范围改变 改变警 戒值所使用的命令字符串为"@02HI(Data)" 及 "@02LO(Data)"(可见第六章的说明) 多媒体控件(MultiMedia Control) 多媒体控件并不是 Visual Basic 预设的控件 所以它不会在项 目开启时出现在工具箱中 欲将其加入工具箱的方法如下 1、 在 Visual Basic 的设计环境下选择菜单的 项目 \ 设定使 用组件 待出现选择画面后 选择 Microsoft Multimedia Control 按下确定 工具箱的画面上即会多一个多媒体图 标 如图 10- 3- 2 所示 第 411 页,共 606 页 图 10- 3- 2 多媒体控件加入工具箱的过程 2、 点选工具箱中的多媒体控件图标 在主画面上的任一处拉出 适当外观 其外观如一般的媒体播放上的控制盘面 执行后 可以透过此盘面直接控制多媒体设备的工作 方便又很好辨 认 如图 10- 3- 3 所示 图 10- 3- 3 在画面上布置多媒体控件 3、 此控件的使用方式有二种 一是直接在画面上操作该控件的 各个按钮 一是使用程序下达动作指令的方法作控制 以监 控系统的角度看来 当然是使用第二种方法来得好 在监测 有问题时 直接就由系统发出声音 4、 此控件的控制程序依然是对象 属性 事件 方法四个步骤 在属性的使用上 最常使用的属性说明如下 (1). DeviceType 设备型态 用于指定控件所控制的设备 输入的型态是字符串 常使用的型态字符串 第 412 页,共 606 页有 ”WaveAudio”(播放声音档) ”Sequencer”(播放 MIDI 音乐) ”CDAudio”(播放 CD) (2). FileName 播放檔名 用来指定所要播放的多媒体文件 名称 (3). Command 执行指令字符串 控件上的所有控制按钮都 可以改由此属性下达指令字符串而达到相同的功能 指 令字符串包括 了 ”Open” ”Close” ”Stop” ”Pause” ”Back” ”Step” ”Prev” ”Next” ”Seek” ”Record” ”Eject” ”Save” 看这些字符串大约可以了解其功能 当指令字符串下达 后 控件即马上作出控制的动作 监控系统即采用此方 法 (4). UpdateInterval 指定改变播放显示状态的间隔时间(单位 为毫秒) 当设为 0 时 表示不使用此项功能 不为 0 时 会引发 StatusUpdate 事件 (5). Notify 通知指令 设成 True 时 若指令执行完成将会 引发 Notify 事件 藉以达而通知的目的 通常利用此一 指令将播放完毕后的档案归位 监控系统会使用到的事件说明如下 (1). Done 当 Notify 属性设为 True 时 一个指令完成后即 会引发此事件 通常我们在播放声音的同时 也会设定 Notify 为 True 而在 Done 事件中输入停止及倒带的指 令 5、 了解上述的各项要点后 多媒体控件的控制流程再表示如图 10- 3- 4 所示 第 413 页,共 606 页下達停止指令 下達關閉指令 指定設備型態、 檔案名稱 下達開啟指令 高值警戒發生? 下達播放指令 播放完畢? 引發Done事件 開始 下達停止及倒帶 的指令 過程結束 是 是 图 10- 3- 4 多媒体控件程序流程 再将上述的讨论发展成一个 7012 模块的子程序列于下 Sub Sub7012() Dim Buf$ Dim i% '读取温度值 并显示 Buf = SendCmd2Comm(MSComm1, "#02", vbCr, 1000) If Buf <> "" Then '若正确传值回来 则 PlotValue(0, NowX) = CLng(Val(Mid(Buf, 2)) * TempSensitivity) lblTemp.Caption = PlotValue(0, NowX) Else '否则显示问号 lblTemp.Caption = "????" End If '检查警戒状态 Buf = SendCmd2Comm(MSComm1, "@02DI", vbCr, 1000) i = Val(Mid(Buf, 5, 2)) If (i And 1) = 1 Then '低值警戒是否激活 '改变颜色为红色 imgE1.Picture = imgRed.Picture lblLoAlarm.BackColor = RGB(100, 255, 0) Else '改变颜色为绿色 imgE1.Picture = imgGreen.Picture lblLoAlarm.BackColor = &H8000000F End If If (i And 2) = 2 Then '高值警戒是否激活 imgE2.Picture = imgRed.Picture lblHiAlarm.BackColor = RGB(100, 255, 0) '高值警戒时 亦激活声音播放 If mciOK Then mciOK = False MMC1.Notify = True '激活通知功能 MMC1.Command = "Play" '下达播放指令 End If Else imgE2.Picture = imgGreen.Picture lblHiAlarm.BackColor = &H8000000F 第 414 页,共 606 页 End If If fLoHi Then '若改变高低警戒 则改变设定 '设定值先除以灵敏值 以得到实际的电压值 Buf = SendCmd2Comm(MSComm1, "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02EAM", vbCr, 1000) fLoHi = False '将旗标设成 False 以免再次被执行 End If End Sub 10-3-3 7080 模块部份 7080D 模块的站号在之前的实验中是被设成 04 在此沿用此站 号在此模块上 此模块用来传回涡轮机的前后级转速 由于它拥有 二个通道 程序中可以分别使用”#040”及 ”#041”指令字符串将二个 通道的转速值传回 传回的转速值显示在画面上的显示区 并且存入变量 PlotValue 数组中 以备绘图之用 相关的取值讨论 请参看第八章 在此就 不再说明了 流程图如图 10- 3- 5 所示 傳送"#040"指令字 串取得Ch0轉速 處理Ch0的傳回結 果 顯示及存入陣列 下達開啟指令 開始 過程結束 傳送"#041"指令字 串取得Ch1轉速 處理Ch1的傳回結 果 顯示及存入陣列 图 10- 3- 5 7080 模块程序流程图 发展完成的子程序列于下 Sub Sub7080() Dim Buf$, SpeedStr$ Dim Pos1%, Pos2% '二个通道分别要求传回数值 '要求 Ch0 传回转速值 Buf = SendCmd2Comm(MSComm1, "#040", vbCr, 1000) '若成功传回结果字符串 If Buf <> "" Then 第 415 页,共 606 页 SpeedStr = Mid(Buf, 2, 8) '分离出计数值的字符串 SpeedStr = "&H" & SpeedStr & "&" '将其变成长整数型式的字符串 PlotValue(1, NowX) = Val(SpeedStr) '转换为实际的数值 lblF1.Caption = PlotValue(1, NowX) '显示在画面上 Else '否则就显示问号 lblF1.Caption = "????" End If '要求 Ch1 传回转速值 Buf = SendCmd2Comm(MSComm1, "#041", vbCr, 1000) If Buf <> "" Then SpeedStr = Mid(Buf, 2, 8) '分离出计数值的字符串 SpeedStr = "&H" & SpeedStr & "&" '将其变成长整数型式的字符串 PlotValue(2, NowX) = Val(SpeedStr) '转换为实际的数值 lblF2.Caption = PlotValue(2, NowX) '显示在画面上 Else '否则就显示问号 lblF2.Caption = "????" End If End Sub 10-3-4 7060 模块部份 在第五章讨论到 7060D 的相关实验时 7060D 模块的站号被设 成 01 在此沿用此站号在此模块上 此模块用于撷取阀门开关及压 力阀的开关状态 程序的流程可如图 10- 3- 6 所示 讀取數位狀態 取出輸出狀態字 元 顯示狀態 改變輸出狀態? 計算輸出值 結束 開始 取出輸入狀態字 元 顯示狀態 執行數位輸出指 令 是 否 图 10-3-6 7060 模块的程序流程 在程序撰写上 首先使用”@01”指令字符串取得 7060 模块的数 字状态 此数字状态含有输出及输入二个部份 因此程序必须就这 第 416 页,共 606 页二个部份分别将所代表的字符取出(7060 所拥有的数字输出入通道 各四个 因此只要一个字符就可以代表四个通道的状态 有些模块 的输出入通道较多 就不是一个字符可以代表得完 此时就必须注 意状态字符串的处理) 并将取出的字符以红绿色的灯号作表示(替 换掉原来 Image 控件中的 Picture 属性中的图片 ) 判断该通道的状态是 1 或 0 的方法有许多种 在笔者的习惯里 是使用数学中的 AND 计算 当用 1 去和某一个通道的状态作运算 时 若该通道是 1 那么所得的结果必定是 1 反之 若该通道是 0 也会得到 0 的数值 现有 4 个通道需作运算 可以如图 10-3-7 的方 式求出 狀態字元A 運算數值 1 0 1 0 1 1 1 1 運算結果 1 0 1 0 權值 3 2 1 0 2 2 2 2 運算結果 1 0 1 0 數值 8 0 2 0 图 10-3-7 状态字符的 AND 运算 由图 10-3-7 可以看出 某一个位若是 1 经由 AND 运算 再和权 值作相乘 若所得的结果和权值的数值相同 则表示该位为 1 否 则为 0 使用此概念可以将模块上每个通道的状态都抓到 作完判断后 程序还要查看是否需要作数字输出的控制 若代 表该状态的旗标被 True 则表示需要作数字输出 程序接着作数字 输出数值的计算 计算完毕后再执行数字输出指令( 指 令 ”01(Data)”) 发展完成的子程序列于下 Sub Sub7060() Dim Buf$, RBuf$ Dim i%, OutValue% '侦测数字状态 Buf = SendCmd2Comm(MSComm1, "@01", vbCr, 1000) '若正确传回结果字符串 则进行判断 If Buf <> "" Then '数字输出状态 RBuf = Mid(Buf, 3, 1) '取出状态字符 '改变状态灯号颜色 For i = 0 To 3 imgR(i).Picture = IIf(Val("&H" & RBuf) And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i '数字输入状态 RBuf = Mid(Buf, 5, 1) '取出状态字符 '改变状态灯号颜色 For i = 0 To 3 第 417 页,共 606 页 imgP(i).Picture = IIf(Val("&H" & RBuf) And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i End If '判断是否作 Relay 输出 If fRelay Then OutValue = 0 '计算数字输出的数值 For i = 0 To 3 OutValue = OutValue + IIf(Relay(i), 2 ^ i, 0) Next i '送出数字输出的指令 Buf = SendCmd2Comm(MSComm1, "@01" & Hex(OutValue), vbCr, 1000) fRelay = False End If End Sub 10-3-5 7021 模块部份 在第七章讨论到 7021 模块的相关实验时 7021 的站号被设成 03 在此沿用此站号在此模块上 整合测试项目中 7021 的主要目 的是控制加水马达 程序流程如图 10- 3- 8 讀取現在類比值 顯示類比值在顯 示區 輸出類比值? 傳送類比指令字 串 超過警戒? 關閉Relay0 開始 設定類比值等於 輸入電壓值 過程結束 是 否 是 關閉加水馬達 否 图 10- 3- 8 7021 模块程序流程 读取模拟值输出使用的是”$036” 此指令会要求 7021 传回上一 次模拟输出动作执行时的模拟值 检查是否需要模拟输出后 实际 的输出指令使用的是”#03(Data)” 程序也检查是否超过警戒值 若 是超过警戒值 则会将加水马达的控制电压设成和读取到的电压值 一样(温度愈高 所需的加水动作也要愈大) 当温度回复到正常范 围后 加水马达的控制电压就关闭 所需的输出电压均是设定数值 后将输出旗标设成 True 再由输出指令送出 并不直接以输出指 令 ”#03(Data)”为之 下一小节将讨论此一作法 第 418 页,共 606 页发展完成的子程序列于下 Sub Sub7021() Dim Buf$ ' 取得现在的模拟输出值 Buf = SendCmd2Comm(MSComm1, "$036", vbCr, 1000) '若取值正确 则显示在状态区 If Buf <> "" Then lblVoltage.Caption = Val(Mid(Buf, 4)) Else '否则就显示问号 lblVoltage.Caption = "????" End If If fVOut Then '判断是否作模拟输出 '送出模拟指令 Buf = SendCmd2Comm(MSComm1, "#03" & Format(VOut, "00.000"), vbCr, 1000) fVOut = False End If '若温度过高 关闭热源(R1) 激活加水马达(V) If PlotValue(0, NowX) >= LoHiValue(1) Then '以下并不直接作输出 而是激活旗标 Relay(0) = False fRelay = True VOut = PlotValue(0, NowX) / TempSensitivity fVOut = True Else '若温度回复高值以下 则关闭加水马达 激活旗标 VOut = 0 fVOut = True End If End Sub 10-3-6 控制旗标 旗标(Flag)的使用经常出现在各式的场合中 监控程序中也经 常使用到旗标 旗标设成 True 时 通常指明一个状态的成立 而当 此状态被处理后 该状态旗标于是被设 False 在旗标变成 False 之 前均是表示该状态尚未被处理掉 由 10-3-2~10-3-5 共四个小节 我们发展了每个模块的监控程序 片断 而每一个模块在架构上也依据着 10-2 节所讨论的流程 读者 可以发现 其实每一个模块或多或少会和其它的模块有一些交错使 用 如果将 7012 模块中夹杂着 7060 模块的指令 会让程序设计变 得复杂 往后的维护上也会变得不容易 改善的方法就如 7021 模 块中所展现的方式 使用旗标 当需要在一个模块中使用到另一个 第 419 页,共 606 页模块的功能时 将某一旗标设为 True 当另一个模块被执行时 再 去检查旗标是否被设为 True 若被设为 True 则执行实际的指令 执行完毕后 再将原来的旗标设成 False 表示该次程序已被执行 过 同样的旗标使用方式也出现在其它地方 在整合测试的项目 中 除了相关的状态需要了取得之外 尚有阀门开关 模拟输出值 的设定 高低警戒值及温度灵敏度值的设定这几个部份在必要时应 该可以改变 若在设定的同时就使用输出指令 同样会使得程序产 生复杂度 不利程序维护 而且在整个程序运作上 一方面要不断 地读取现有的监测情形 一方面又要查看控制动作是否需进行 而 作出控制指令的传送 如此一来 SendCmd2Comm 指令势必到处存 在 如此不利于大型项目的发展 解决方法就是使用旗标 并将实 际的 SendCmd2Comm 指 令集中在一起 旗标的使用首先考虑到的是什么参数需要用到 回顾到整合测 试项目的要求规格上可以发现有以下的几个地方将使用到 1、 阀门开关的控制 阀门开关使用的是 7060 模块的数字输出功 能 画面上必须要有个地方可以让使用者可以改变数字输出 状态 但是画面上似乎已经没有多余的地方可以摆放控制的 按钮了 因此笔者决定在系统布置图上的 R1~R4 这四个 Label 控件上动手脚 先将这四个控件改其 Name 属性 并使用对 象数组的方式 使其 Index 依序排列(由 0~3) 我们在它们的 Click 事件程序中放上旗标 表示输出动作需作改变 并将改 变的 Relay 号码放入 程序写法如下 Private Sub lblRL_Click(Index As Integer) Relay(Index) = Not Relay(Index) fRelay = True End Sub 程序中使用的变量是 Relay 变量 其中就记录了 R1~R4 这四 个 Relay 的状态 每按一次 Label 控件 状态就转态 并且将 fRelay 旗标设为 True 用来告诉系统 Relay 状态已改变 必 须作实际的输出 2、 高低警戒值的输入 7012 除了撷取电压输入值外 本身也具 有 实时反应的能力 此即为第六章所述的高低警戒功能 系 统开始执行时 会有一个初始化的警戒值 不过 随着系统 的运作 使用者可能需要对于高低警戒值作变更 因此我们 也将系统布置图上的高低电压参数 Label控件更改其 Name 属 性 一个为 lblHiAlarm 一个为 lblLoAlarm 程序写法如下 第 420 页,共 606 页Private Sub lblHiAlarm_Click() Dim Buf$ Buf = InputBox(" 请输入高温警戒值", "改变高温警戒", LoHiValue(1)) LoHiValue(1) = Val(Buf) fLoHi = True End Sub Private Sub lblLoAlarm_Click() Dim Buf$ Buf = InputBox(" 请输入低温警戒值", "改变低温警戒", LoHiValue(0)) LoHiValue(0) = Val(Buf) fLoHi = True End Sub 我们在 Click 事件中引入 InputBox 函式 让使用者可以输入 其数值 程序中使用的变量是 LoHiValue 变量 其中就记录 了高低警戒值的设定数值 输入完成后并改变 fLoHi 旗标 InputBox 函式的参数常用的是前三个 第一个是提示字句 用来告诉使用者讯息 第二个参数是输入画面的抬头 第三 个参数则是输入区中的默认值 一般用来提醒使用者应该输 入的数值 我们则用来显示现在的警戒值 3、 加水马达的电压控制 发展 7021 模块部份的程序时(10- 3-5 小节) 当温度值高于高值警戒时 程序会输出和 7012 所取 的电压值相同的电压 用以控制加水马达 这是监控上的需 要 但在一般时候 也可必要可以手动控制加水马达的动作 因此也要有一个方法可以让使用者可以输入控制的电压值 同样在系统布置图上改变参数 V 的 Name 属性 并输入其 Click 事件的程序代码 此部份的程序写法如下 Private Sub lblVOut_Click() VOut = Val(InputBox("请输入电压值(0.0~10.0)", "7021 模拟输出", "0")) fVOut = True End Sub 程序亦会引出 InputBox 函式 让使用者输入电压值 预设的 电压值是 0 当输入完成后 使用变量 fVOut 旗标告诉系统需 改变输出的电压值 另一个不需要旗标但也必须在系统布置图上处理的是温度值 灵敏度的输入 我们使用 7012D 作为量测温度的信道 但 7012D 所 量测的是电压而非温度 所以采用了一个对照的方式 让使用者可 以输入转换的数值 而此数值的单位是(度 C/V) 也就是每 1V 对应 到的温度值 系统的默认值是 30 也就是每 1V 代表 30 度 C 此部 第 421 页,共 606 页份也在系统布置图上的 T 参数 Label 处理 其 Click 事件中输入以 下的程序代码 Private Sub lblSensitivity_Click() TempSensitivity = Val(InputBox("请输入温度灵敏度", "灵敏度改变", Temp Sensitivity)) End Sub 简单地利用 InputBox 函式就可以达到改变参数值的目的 以上针对旗标及灵敏度的讨论中 程序均未直接对模块送出指 令字符串 只是将数字记录到相关的变量中 再激活旗标 既然未 直接送出指令字符串 这些旗标当然就是送到各个模块的子程序去 处理 而各个模块的子程序再被集合起来作处理 如何处理呢?由 于程序必须一直地对整个系统作监控 当然就是使用定时器啰!下一 小节马上进行这方面的讨论 10-3-7 整体讨论 主要的各项工作 除了稍后即将讨论的绘图之外 其它的部份 均已发展得差不多了 本节还要将这些分散的部份作组合 使其成 为一个完整的项目程序 绘图方法 绘图的方法在第六章的 6-2-6 首先引用了快速绘图 而在第八 章的 8-2-5 则使用二个通道绘图方法 本项目将采用这二小节的绘 图程序为基础 作出平滑移动且三个趋势数值显示的绘图 尚不了 解的读者请先回头查看一下该二小节的程序说明 产生绘图数据的模块为 7012D 及 7080D 它们分别会有温度值 及涡轮机前后级转速 在之前的模块建立时 程序使用 PlotValue 变量记录温度值 前级转速 后级转速在数组中不同的索引里 在 7012D 模块的是 PlotValue(0, NowX) = CLng(Val(Mid(Buf, 2)) * TempSensitivity) 使用了索引 0 记录所撷取的温度值 在 7080D 模块中则以 PlotValue(1, NowX) = Val(SpeedStr) '转换为实际的数值 和 PlotValue(2, NowX) = Val(SpeedStr) '转换为实际的数值 第 422 页,共 606 页使用索引 1 及索引 2 记录所撷取的前级转速及后级转速 有了这三 个需要绘图的数值后 再建立绘图子程序如下 Sub Plot(StartX&) Dim i&, j&, k& Dim ShowColor& Dim EndPos& PicHide.Cls '清除图形 '先绘温度的部份 PicHide.Scale (0, 10 * TempSensitivity)- (MaxPlotNo, 0) PicHide.ForeColor = RGB(0, 0, 0) '以下作绘格子线的绘图定设定 PicHide.DrawWidth = 1 PicHide.DrawStyle = vbDot '以下二个子程序是作格子线的绘制 For i = 1 To 9 PicHide.Line (i * MaxPlotNo / 10, 0)- (i * MaxPlotNo / 10, 10 * TempSensitivity) Next i For i = 1 To 4 PicHide.Line (0, i * 10 * TempSensitivity / 5)- (MaxPlotNo, i * 10 * TempSensitivity / 5) Next i '以下是绘趋势线的格式设定 PicHide.DrawWidth = 2 PicHide.DrawStyle = vbSolid '判断是否已超过设定的绘图笔数 If fIsOver Then k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终点 '当已超过后 就以最大点数作为绘图的终点( 此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) ShowColor = RGB(255, 0, 0) '先作温度值的绘图 PicHide.PSet (0, PlotValue(0, 0)), ShowColor For i = 1 To EndPos PicHide.Line - (i, PlotValue(0, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i '再作转速的绘图 '以下设定转速绘图范围 (Xmin,YMax)- (XMax,YMin) PicHide.Scale (0, 15000)- (MaxPlotNo, 5000) For j = 1 To 2 '二个通道 If fIsOver Then k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 第 423 页,共 606 页 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终 点 '当已超过后 就以最大点数作为绘图的终点(此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) '指定绘图颜色(j=0 时使用红色 否则使用蓝色) ShowColor = IIf(j = 1, RGB(255, 255, 0), RGB(0, 0, 200)) PicHide.PSet (0, PlotValue(j, 0)), ShowColor For i = 1 To EndPos PicHide.Line - (i, PlotValue(j, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i Next j '快速图形拷贝函式 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.hDC, 0, 0, &HCC0020) NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 fIsOver = True End If End Sub 子程序依然采用 Ring Buffer 的方式储存所要绘制的资料 程序中 首先是绘制格子线 格子线的好处是让观看者容易找到数值的大约 范围 在仪器上经常可以见到格子线的使用 接着是温度值的绘制 转速接在温度值之后被绘上 需要注意的是 二种数值的数值差异 相当大 绘制趋势图时 必须将二种数值分开指定绘图坐标范围 而且在显示的画面上也最好显示二种不一样的数值范围 让使用者 可以了解所见到的趋势线的范围所在 绘制完成的图形藉由 BitBlt 函式的作用快速地拷贝到显示的图片框中 如此就完成了绘图的部 份了 系统初始化 系统一开始运作时 我们会给定一些参数使其在一定的情形下 执行监控的任务 这些参数当然也可能设计给使用者在执行的过程 中动态地去改变 项目一开始通常是以窗体的 Form_Load 事件开始 执行(也可以使用 Main 函式开始执行) 因 此在其事件程序中可写入 以下的程序代码 '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotValue(0 To 2, 0 To MaxPlotNo) TempSensitivity = 30 '每 1 伏为 30 度 C 默认值 MSComm1.PortOpen = True 第 424 页,共 606 页 TimeDelay 1000 '激活 7012 的警戒 LoHiValue(0) = 3 * TempSensitivity LoHiValue(1) = 8 * TempSensitivity Buf = SendCmd2Comm(MSComm1, "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02EAM", vbCr, 1000) '先将 7060 Relay 全部打开 Buf = SendCmd2Comm(MSComm1, "@01F", vbCr, 1000) For i = 0 To 3 Relay(i) = True Next i '变更 7080 型态为频率模式 使用%AANNTTCCFF 指令 Buf = SendCmd2Comm(MSComm1, "%0404510640", vbCr, 1000) '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ Alarm1.wav" MMC1.Command = "Open" mciOK = True 初始化的程序包括了图片框 绘图笔数 数组 7012 模块 7060 模块 7080 模块及多媒体控件的初始化 使监控的工作有一个初始 值 程序的组合 通常在 Visual Basic 程序中需要使用到不间断 或是固定时间 执行周期动作的情形下 使用 Timer 控件是相当普遍的作法 而此 控件也的确提供了不少的便利性 在我们这个项目中也将使用这个 控件 使其不断地自各模块取得数据 并将控制指令下达到各个模 块 本项目将定时器的 Interval 属性设成 100 表示每 100 毫秒执 行一次 Timer 内的程序代码 我们已经在之前的各小节中发展了各模块的程序 现在必须将 这些程序组合起来 我们在定时器的 Timer 事件中写入以下的程序 代码 '7012 模块函式 Sub7012 '7080 模块函式 Sub7080 '7060 模块函式 Sub7060 '7021 模块函式 Sub7021 '以下是趋势绘图 第 425 页,共 606 页 Plot NowX '进入绘图子程序 程序非常地短 均使用子程序 而且也都是我们在前几小节发展的 子程序 当定时器被激活后这些子程序即会被执行 而之前讨论的 各 结果也就一一地展现出来 定时器的激活可以使用一个按钮来控制 因此在画面上可设计 一个按钮作为切换定时器激活及关闭的工具 在该控制的按钮之 Click 事件中可输入以下的程序代码 Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止系统" Else cmdStart.Caption = "激活系统" End If 当定时器激活时 按钮表面的显示文字就出现”停止系统” 反之则 出现”激活系统” 这是用来告诉使用按钮被按下时会有的动作 程 序中利用 Enabled 属性的转态 即可控制定时器的运作 程序的执行 开始程序的执行之前必须先将各实验的线路接妥 由于使用到 7520 7060D 7012D 7021 7080D 等模块 而各模块也有其实验 的电路板 线路的整理可能会使得整个的实验看起来不太容易(其实 现场的线路更是复杂 我们的实验还算是小 Case 呢 !) 接妥当后还 必须一一地检查接线的状况 而且必须检查模块的电源和接地端不 可短路(非常重要 故再次强调) 硬件线路准备妥当且作过检查 而相关程序细节也已作过讨 论 当程序执行后 按下 激活系统 的按钮 程序即开始作资料 的撷取及显示 而使用者也可以在画面上的 R1~R4 按一下鼠标控制 7060D 继电器的开关 按下高低警戒的参数也可改变其设定值 如 果觉得温度范围不合用还可以按下温度参数的 Label 改变其灵敏度 值的设定 这些被改变的设定 由于旗标会被打开 而各模块程序 就依之前的设计分别执行自己的内部程序代码 执行程序后 未按下 激活系统 如图 10- 3- 9 所示 第 426 页,共 606 页 图 10- 3- 9 未按下 激活系统 按钮前的情形 图 10- 3-9 中的显示部份均尚未有资料 按下 激活系统 按钮 后 一段时间后的监控情形如图 10- 3- 10 所示 图 10- 3- 10 激活系统 按钮后的一般情形 图 10-3- 10 中的趋势图形就是由实验板所产生的 按下系统布 置图上的 R1~R4 可以直接控制继电器的动作 而在显示区中也可以 看到灯号的改变 进而证实控制动作的成功与否 当温度值过高而 导致警戒发生时 监控程序则可以能如图 10- 3- 11 所示 第 427 页,共 606 页 图 10- 3- 11 发生高警戒时的情形 图 10-3- 11 中的趋势图最右端的温度线已超越警戒值 故在显 示区中的警戒(E2)灯号为红色 并且在左方的系统布置图中的 E2 参数颜色也发生了变化 整体测试的情形已如上述 程序运作一尚可 亦无重大问题产 生 实际的程序验证及操作 还是得由读者亲自上场 方能体会 以上的程序代码 您可以参考光盘中 范例 \ 第十章 \ 整 合测试 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的测 试 完整的程序代码列表如下 Option Explicit Dim fRelay As Boolean 'Relay 是否动作的旗标 Dim fLoHi As Boolean 'Hi- Lo 是否动作的旗 Dim fTemp As Boolean '温度 Sensitivity 是否改变的旗标 Dim fVOut As Boolean '模拟输出是否动作的旗标 Dim TempSensitivity As Single '温度灵敏度值 Dim LoHiValue(0 To 1) As Integer '高低值警戒值 Dim Relay(0 To 3) As Boolean '四个 Relay 的开关状态 Dim VOut As Single '模拟输出值 Dim NowX As Long '现在的 X 轴位置 Dim MaxPlotNo As Long '最长的 X 轴范围 Dim PlotValue() As Long '读值数组 Dim fIsOver As Boolean '是否超过绘图点数的旗标 Dim P1Width&, P1Height& '绘图的图片框的范围记录 Dim mciOK As Boolean '是否播音完毕 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() 第 428 页,共 606 页 End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 激活系统 按钮后激活此事件 ' 将定时器作转态 在激活与关闭间作切换 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止系统" Else cmdStart.Caption = "激活系统" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将 通讯端口在窗体加载时就开启 让其它的程序可以开始使用通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim Buf$, i% '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotValue(0 To 2, 0 To MaxPlotNo) TempSensitivity = 30 '每 1 伏为 30 度 C 默认值 MSComm1.PortOpen = True TimeDelay 1000 '激活 7012 的警戒 LoHiValue(0) = 3 * TempSensitivity LoHiValue(1) = 8 * TempSensitivity Buf = SendCmd2Comm(MSComm1, "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02EAM", vbCr, 1000) '先将 Relay 全部打开 Buf = SendCmd2Comm(MSComm1, "@01F", vbCr, 1000) For i = 0 To 3 Relay(i) = True Next i '变更 7080 型态为频率模式 使用%AANNTTCCFF 指令 Buf = SendCmd2Comm(MSComm1, "%0404510640", vbCr, 1000) '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ Alarm1.wav" MMC1.Command = "Open" mciOK = True End Sub 第 429 页,共 606 页 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'E2 的 Click 事件区 ' 在此可改变高温警戒的设定 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblHiAlarm_Click() Dim Buf$ Buf = InputBox("请输入高温警戒值", "改变高温警戒", LoHiValue(1)) LoHiValue(1) = Val(Buf) fLoHi = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'E1 的 Click 事件区 ' 在此可改变低值警戒的设定 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblLoAlarm_Click() Dim Buf$ Buf = InputBox("请输入低温警戒值", "改变低温警戒", LoHiValue(0)) LoHiValue(0) = Val(Buf) fLoHi = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'R1~R4 的 Click 事件区 ' 在此可控制 Relay 的动作 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblRL_Click(Index As Integer) Relay(Index) = Not Relay(Index) fRelay = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'T 的 Click 事件区 ' 在此可温度灵敏度的数值 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblSensitivity_Click() TempSensitivity = Val(InputBox("请输入温度灵敏度", "灵敏度改变", TempSensitivity)) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '7021 的 Click 事件区 ' 在此可控制模拟输出的数值 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblVOut_Click() VOut = Val(InputBox(" 请输入电压值(0.0~10.0)", "7021 模拟输出", "0")) fVOut = True End Sub Private Sub MMC1_Done(NotifyCode As Integer) If NotifyCode = 1 Then MMC1.Command = "Stop" 第 430 页,共 606 页 MMC1.Command = "Prev" mciOK = True End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件 ' 所有的通讯程序在此程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() '7012 模块函式 Sub7012 '7080 模块函式 Sub7080 '7060 模块函式 Sub7060 '7021 模块函式 Sub7021 '以下是趋势绘图 Plot NowX '进入绘图子程序 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7012 模块的子程序 模块站号 02 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7012() Dim Buf$ Dim i% '读取温度值 并显示 Buf = SendCmd2Comm(MSComm1, "#02", vbCr, 1000) If Buf <> "" Then '若正确传值回来 则 PlotValue(0, NowX) = CLng(Val(Mid(Buf, 2)) * TempSensitivity) lblTemp.Caption = PlotValue(0, NowX) Else '否则显示问号 lblTemp.Caption = "????" End If '检查警戒状态 Buf = SendCmd2Comm(MSComm1, "@02DI", vbCr, 1000) i = Val(Mid(Buf, 5, 2)) If (i And 1) = 1 Then '低值警戒是否激活 '改变颜色为红色 imgE1.Picture = imgRed.Picture lblLoAlarm.BackColor = RGB(100, 255, 0) Else '改变颜色为绿色 imgE1.Picture = imgGreen.Picture lblLoAlarm.BackColor = &H8000000F End If If (i And 2) = 2 Then '高值警戒是否激活 imgE2.Picture = imgRed.Picture lblHiAlarm.BackColor = RGB(100, 255, 0) '高值警戒时 亦激活声音播放 第 431 页,共 606 页 If mciOK Then mciOK = False MMC1.Notify = True '激活通知功能 MMC1.Command = "Play" '下达播放指令 End If Else imgE2.Picture = imgGreen.Picture lblHiAlarm.BackColor = &H8000000F End If If fLoHi Then '若改变高低警戒 则改变设定 '设定值先除以灵敏值 以得到实际的电压值 Buf = SendCmd2Comm(MSComm1, "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02EAM", vbCr, 1000) fLoHi = False '将旗标设成 False 以免再次被执行 End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模 拟输入 7080 模块的子程序 模块站号 04 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7080() Dim Buf$, SpeedStr$ Dim Pos1%, Pos2% '二个通道分别要求传回数值 '要求 Ch0 传回转速值 Buf = SendCmd2Comm(MSComm1, "#040", vbCr, 1000) '若成功传回结果字符串 If Buf <> "" Then SpeedStr = Mid(Buf, 2, 8) '分离出计数值的字符串 SpeedStr = "&H" & SpeedStr & "&" '将其变成长整数型式的字符串 PlotValue(1, NowX) = Val(SpeedStr) '转换为实际的数值 lblF1.Caption = PlotValue(1, NowX) '显示在画面上 Else '否则就显示问号 lblF1.Caption = "????" End If '要求 Ch1 传回转速值 Buf = SendCmd2Comm(MSComm1, "#041", vbCr, 1000) If Buf <> "" Then SpeedStr = Mid(Buf, 2, 8) '分离出计数值的字符串 SpeedStr = "&H" & SpeedStr & "&" '将其变成长整数型式的字符串 PlotValue(2, NowX) = Val(SpeedStr) '转换为实际的数值 lblF2.Caption = PlotValue(2, NowX) '显示在画面上 Else '否则就显示问号 lblF2.Caption = "????" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7060 模块的子程序 模块站号 01 第 432 页,共 606 页' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7060() Dim Buf$, RBuf$ Dim i%, OutValue% '侦测数字状态 Buf = SendCmd2Comm(MSComm1, "@01", vbCr, 1000) '若正确传回结果字符串 则进行判断 If Buf <> "" Then '数字输出状态 RBuf = Mid(Buf, 3, 1) '取出状态字符 '改变状态灯号颜色 For i = 0 To 3 imgR(i).Picture = IIf(Val("&H" & RBuf) And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i '数字输入状态 RBuf = Mid(Buf, 5, 1) '取出状态字符 '改变状态灯号颜色 For i = 0 To 3 imgP(i).Picture = IIf(Val("&H" & RBuf) And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i End If '判断是否作 Relay 输出 If fRelay Then OutValue = 0 '计算数字输出的数值 For i = 0 To 3 OutValue = OutValue + IIf(Relay(i), 2 ^ i, 0) Next i '送出数字输出的指令 Buf = SendCmd2Comm(MSComm1, "@01" & Hex(OutValue), vbCr, 1000) fRelay = False End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7021 模块的子程序 模块站号 03 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7021() Dim Buf$ '取得现在的模拟输出值 Buf = SendCmd2Comm(MSComm1, "$036", vbCr, 1000) '若取值正确 则显示在状态区 If Buf <> "" Then lblVoltage.Caption = Val(Mid(Buf, 4)) Else '否则就显示问号 lblVoltage.Caption = "????" End If If fVOut Then '判断是否作模拟输出 '送出模拟指令 Buf = SendCmd2Comm(MSComm1, "#03" & Format(VOut, "00.000"), vbCr, 1000) 第 433 页,共 606 页 fVOut = False End If '若温度过高 关闭热源(R1) 激活加水马达(V) If PlotValue(0, NowX) >= LoHiValue(1) Then '以下并不直接作输出 而是激活旗标 Relay(0) = False fRelay = True VOut = PlotValue(0, NowX) / TempSensitivity fVOut = True Else '若温度回复高值以下 则关闭加水马达 激活旗标 VOut = 0 fVOut = True End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 绘图的子程序 ' 三个通道的值取后即进行图形的绘制 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Plot(StartX&) Dim i&, j&, k& Dim ShowColor& Dim EndPos& PicHide.Cls '清除图形 '先绘温度的部份 PicHide.Scale (0, 10 * TempSensitivity)- (MaxPlotNo, 0) PicHide.ForeColor = RGB(0, 0, 0) '以下作绘格子线的绘图定设定 PicHide.DrawWidth = 1 PicHide.DrawStyle = vbDot '以下二个子程序是作格子线的绘制 For i = 1 To 9 PicHide.Line (i * MaxPlotNo / 10, 0)- (i * MaxPlotNo / 10, 10 * TempSensitivity) Next i For i = 1 To 4 PicHide.Line (0, i * 10 * TempSensitivity / 5)- (MaxPlotNo, i * 10 * TempSensitivity / 5) Next i '以下是绘趋势线的格式设定 PicHide.DrawWidth = 2 PicHide.DrawStyle = vbSolid '判断是否已超过设定的绘图笔数 If fIsOver Then k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终点 '当已超过后 就以最大点数作为绘图的终点( 此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) ShowColor = RGB(255, 0, 0) '先作温度值的绘图 第 434 页,共 606 页 PicHide.PSet (0, PlotValue(0, 0)), ShowColor For i = 1 To EndPos PicHide.Line - (i, PlotValue(0, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i '再作转速的绘图 '以下设定转速绘图范围 (Xmin,YMax)- (XMax,YMin) PicHide.Scale (0, 15000)- (MaxPlotNo, 5000) For j = 1 To 2 '二个通道 If fIsOver Then k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终 点 '当已超过后 就以最大点数作为绘图的终点(此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) '指定绘图颜色(j=0 时使用红色 否则使用蓝色) ShowColor = IIf(j = 1, RGB(255, 255, 0), RGB(0, 0, 200)) PicHide.PSet (0, PlotValue(j, 0)), ShowColor For i = 1 To EndPos PicHide.Line - (i, PlotValue(j, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i Next j '快速图形拷贝函式 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.h DC, 0, 0, &HCC0020) NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 fIsOver = True End If End Sub 模块中的程序代码则如下 Option Explicit Declare Function GetTickCount Lib "kernel32" () As Long Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, _ ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, _ ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, _ ByVal ySrc As Long, ByVal dwRop As Long) As Long ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 时间延迟子程序 单位为毫秒(ms) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& 第 435 页,共 606 页TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 指令送出 并等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'CS 是命令字符串 不需含结尾字符 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function SendCmd2Comm(Comm As MSComm, CS As String, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = Comm.Input Buf = OutCheckSum(CS) 'CheckSum 计算 Comm.Output = Buf '指令送出 Buf = "" TT = GetTickCount '等待直到时间到 或是字符串传回 Do Buf = Buf & Comm.Input Loop Until InStr(1, Buf, RS) > 0 Or GetTickCount - TT >= DT If InStr(1, Buf, RS) > 0 Then '以下是传回值 CheckSum 的检查 SendCmd2Comm = INCheckSum(Buf) Else SendCmd2Comm = "" End If End Function 'HOST OK 指令送出 'Comm 是通讯组件名称 Sub SendHostOK(Comm As MSComm) Dim Buf$, TT As Long Buf = Comm.Input Buf = OutCheckSum("~**") 'CheckSum 计算 Comm.Output = Buf '指令送出 End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将要送出的字符串计算 CheckSum 的结果 'InBuf 是传入的字符串 ' 函式的传回值是作了 CheckSum 的结果字符串 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function OutCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Long BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 第 436 页,共 606 页 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i '字符串重组 并加上结尾字符 Cr Buf = IIf(Len(Hex(CheckSum)) = 1, "0" & Hex(CheckSum), Hex(CheckSum)) OutCheckSum = InBuf & Buf & vbCr End Function '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将模块传回的字符串检查 CheckSum 是否正确 'InBuf 是由模块传回的字符串 ' 函式的传回值为去除 CheckSum 及结尾字符( 正 确时) ' 或空字符串( 不正确时) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function INCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Integer BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen - 3 '需扣掉二个 CheckSum 字符和结尾字符 Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i If CheckSum = Val("&H" & Mid(InBuf, BufLen - 3 + 1, 2)) Then INCheckSum = Mid(InBuf, 1, BufLen - 3) Else INCheckSum = "" End If End Function 第 437 页,共 606 页本章习题 1、 若要求在高值警戒时出现一种声音 而低值警戒时出现另 外一种声音 程序应如何修改? 2、 为系统程序加上时间的显示 3、 加上时间显示的资料若被要求资料存盘 档案处理的片断 程序如何作? 第 438 页,共 606 页第十一章 监控的延伸 分布式模块透过 485 网络可以达到模块分散的目的 一般的情 形下已经足以应付需求 许多实际的应用中 存在有不同的环境和 场合 工程师也必须考虑到不同情况下的适用性及可行性 有时候基于实际的需求 系统必须作部份的修改才能符合监控 的要求或环境的限制 甚至是为了兼容原本系统的架构 本章将就 部份可能的情形作讨论 介绍部份的解决方案 11-1 使用 TCP/IP 在网际网络如此发达的现代 使用网络成为现代人的必备的知 识 使用网络存取资料也是趋势 而这样子的趋势在系统控制的领 域也是如此 网络是由一群计算机相互连结而成 透过一定的传输媒介及传 送程序而分享资源 服务及讯息 由于使用网络可以达到远程传输 的目的 本节首先讨论网络的基本概念 再利用网络到分布式监控 11-1-1 什么是 TCP/IP TCP/IP 是一组通讯协议的集合 其代表 TCP(Transmission Control Protocol) 和 IP(Internet Protocol) 这二种主要的通讯协议 而 所谓的通讯协议就是在装置之间移动资料的规则 标准或方法 这 也类似于我们在前面的章节中提到的串行通讯中的通讯协议一样 双方利用相同的规则及程序对讯息作一定的编码及译码 这个协议的来源就要追溯到 1960 年代的美国国防部的网络计 画 早期的计算机是没有相连的 也因此许多的资料均是散布于每 个个别的计算机内 美国国防部的想法是希望让这些分散的计算机 可以共享资源 也希望重要的资源可以利用网络分散在各地 以避 免被攻击的危险 在这种想法的推进下 建立了 ARPAnet 这个为人 所熟知的分散的网络协议 这也是 TCP/IP 的起源 像 TCP/IP 这样子的通讯协议 必须能够处理下列的各项工作 第 439 页,共 606 页1、 将讯息切割成可管理的资料区块 以提高透过传输媒介的 传送效率 2、 与网络卡硬件沟通 3、 传送资料的计算机必须能够将资料传送至接收资料的计算 机 接收计算机则必须能够辨认应该收到的资料 4、 不管距多远 只要线路存在 协议就必须能够将资料利用 各种的可能路径送到目的地 5、 必须能作错误的检查及流程的管制 6、 接收来自应用程序(如 Visual Basic 作成的程序)的资料 并 将其传送至网络上 11-1-2 IP 地址及 Port 就像道路旁的每间房子都有个门牌号码一样 每部计算机在网 络上也都有一个独一无二的地址 资料欲在网络上传输时 就是要 透过这个地址的指定才能顺利地达到目的地 这个地址就是 IP 地 址 IP 地 址使用 4 个 8 位的数字予以代表 每个数字间以逗号(. ) 区隔 例如”140.96.159.23”就是一个 IP 地址 指的是一部计算机在 网络上的地址 基本上 每一部计算机通常配有一片网络卡 计算 机的 IP 地址就依附于这片网络卡 如果计算机内的网络卡有二片 呢 ?这部计算机就会有二个地址 若只是在自己家里架设网络的话 那 IP 地址可以自己设定 只 要符合设定的规则即可 若是连上外界的话 IP 的地址就不能自行 指定了 在大单位里 通常会连上单位里的服务器 再由服务器指 定一个 IP 地址给我们用的这部计算机 由于数字对于记忆来说是比较吃力 也很少人可以记住这些数 字 因此使用对应的网域名称成为大家采用的方法 网域名称和 IP 是成对应关系的 此对应关系由 Internet Network Information Center(InterNIC)所发布 例如www.vbio.com.tw等即是 在网域名称的最后一码 tw 表示台湾 不同的国家会有不同的代 码 而 com 表示是 Commercial 意为商业 此部份的分类还不少 如表 11-1-1 第 440 页,共 606 页表 11-1-1 简写与其意义 简写 说明 com Commercial 商业网站 gov Government 政府单位 edu Education 教育单位 net Network 网络单位 org Organization 未分类的组识 Port(连接端口) 用于辨识在网际网络上的机器所执行的一个应 用程序 这就类似于在窗口操作系统中 每一个执行的窗口都会由 操作系统给一个代码(Handle)一般 此概念就是用于分别出不同的 应用程序 网络上的 Port 可由伺服端及客户端自行协议即可 自行 发展的应用程序通常使用 1024 以上的 Port 另外 现行网络上通 用的程序会因不一样的协议而采用不同的连接端口号码 如表 11-1-2 是固定使用的连接埠 表 11-1-2 常用协议及其使用的连接埠 连接埠 协议名称 80 HTTP WWW 浏览器用 20 和 21 FTP 档案传输 70 Gopher 文件搜寻 25 SMTP 邮件系统 110 POP3 邮件系统 23 Telnet 终端机网络联机 43 Whois NIC 名称 79 Finger 欲让数据传输正确无误 必须 IP 和 Port 配合 通讯双方对于 此二个组合必须一致才行 这二个的组合也被称之为一个 Socket 数据传输的过程中 双方都必须知道对方的 IP 地址及所使用的 通讯端口(也就是一个 Socket) 通讯时的资料就透过此 Socket 所指 明的位置(IP)及应用软件(Port)而传达 情形如图 11- 1- 1 應用軟體A 應用軟體BIP:140.96.111.4 Port:1029 IP:169.34.1.234 Port:23 資料流向 指定 140.96.111.4位址 的Port:1029 指定 169.34.1.234位址 的Port:23 資料流向 第 441 页,共 606 页图 11- 1- 1 TCP/IP 二端的数据传输流程 11-1-3 网络分割 如果网络的规模很大 大量的资料就会透过网络进行交换 而 这些交换将会使得网络传输媒介的负荷增加 也会使用传输的资料 花费较多的时间才到达目的地 小型的网络也许还好 网络上的计 算机数一多 一定会使得传输的速度降低 欲解决此种情形 大部 份的大型网络均会利用联机装置将网络作分割 以提高网络速度及 效率 并让网络的流量达而最小 资料的传输范围若只是在很小的 区域内 就不需要经由外部的网络传输媒介 外界的媒介就还可以 给其它需要传输的资料使用 其架构如图 11-1-2 IBM 相容型 IBM 相容型 IBM 相容型 路徑器 IBM 相容型 IBM 相容型 IBM 相容型 其它群組 图 11- 1- 2 网络的分割 在网络分割的概念下 发展出数种专为网络分割的设备 这些 设备有以下的几种 桥接器 路由器 桥接路由器 这些设备可以 分辨网络上的讯息 如果属于局域网络的讯息的话 当然就不需要 传送到其它的网络区段 也就不会使得所有的网络区段都存在着大 量的资料而减低网络的速度 资料由计算机 A 到计算机 B 实际的传输过程如图 11-1-3 第 442 页,共 606 页電腦A 電腦B路由器1 路由器2 應用程式 網路卡 傳輸媒介 傳輸媒介 傳輸媒介 傳輸媒介 傳輸媒介 同一區域的 電腦群 同一區域的 電腦群 應用程式 網路卡 傳輸媒介 傳輸媒介 傳輸媒介 图 11-1-3 二部计算机间的数据传输 计算机内部的应用程序将相关的讯息透过网络卡的转换 再到 传输媒介 如果目标计算机在本区内 路由器不会将讯息传送出去 若本区找不到目标计算机的 IP 地址的话 路由器便将讯息转送出 去 而由其它的路由器负责解读目标 IP 地址所应走的路径 置到 找到目标 IP 地址为止 当然啦!如果该部计算机不存在 建立联机 之初就会失败 资料也不可能在传输媒介之间旅行 另外还有一个保护或限制局域网络内部计算机的装置 — 防火 墙 它防止一个未经认证的使用者透过网际网络入侵区域计算机或 限制内部计算机自由出入局域网络 一般说来 防止外部入侵是大 多数的防火墙的工作 其情形如图 11-1-4. Local伺服器 Local電腦 Local電腦 網際網路 防火牆 Internet電腦 區域網路 图 11-1-4 防火墙概念 上图局域网络中的计算机可以自由地沟通 并且存取服务器上 的资源 而经由网际网络过来的计算机就会受到防火墙的阻隔而无 法进入局域网络的系统中 也无法向服务器取得资料 这使得服务 器上的资料受到了保护 第 443 页,共 606 页 11-2 Winsock 控件 有了以上的网络的概念后 我们接着探讨在 Visual Basic 中专 门用来作 TCP/IP 传输的 Winsock 控件 11-2-1 Winsock 控件简介 在 Visual Basic 专业版和企业版中包含有应用在网络技术上的 控件 Winsock 便是其中之一 利用 WinSock 控件可以与远程计算 机建立联机 并透过使用者资料记录通讯协议(UDP)或者传输控制 协议(TCP)通讯协议来进行资料交换 这两种通讯协议都可以用来 建立客户端与伺服端的应用程序 在使用 WinSock 控件时 首先需考虑要使用 TCP 还是 UDP 通 讯协议 两种通讯协议之间的重要区别在于它们的联机状态 1 TCP 通讯协议控件是有联机的通讯协议 类似于电话系统 在 开始数据传输之前 使用者必须先建立联机 其上并有错误检查 机制 避免资料被分散传递 却因传输的过程而错误 如果资料 是比较重要的 使用这种方式是比较好的 2 UDP 通讯协议是一种无联机通讯协议 两台计算机之间的传输 类似于传递邮件 讯息从一台计算机传送到另一台计算机 但是 两者之间没有明确的联机 由于和 TCP 的方式比起来 它的错 误检查比较简单 因此速度比较快 要求速度时 使用此种方式 是比较恰当的 使用在工业应用的场合中 每一笔资料都很重要 因此我们着 重在使用 TCP 协议的应用上 第 444 页,共 606 页11-2-2 Winsock 控件的属性 Winsock 控件的属性不多(和其它画面控件比起来) 以下分别简 要说明 RemoteHost 传回设定控件要接收或传送的远程机器 可以提供主 机名 例如 FTP://ftp.microsoft.com 也可以提供点格式的 IP 地 址字符串 例如 100.0.1.1 RemoteHostIP 传回远程计算机的 IP 地址 对于客户端应用程序来 说 在要求建立建立联机后 这个属性就包含了远程计算机(也就 是伺服端)的 IP 字符串 对于伺服端应用程序来说 在通过请求联 机之后 这个属性就包含了激活联机的远程计算机(也就是客户端) 的 IP 字符串 当使用 UDP 通讯协议时 在 DataArrival 事件出现之 后 这个属性包含了传送 UDP 资料的计算机的 IP 地址 有了这个属性 对于服务器程序来说就可以得知是那一部计算机提 出要求 知道 IP 后 就可以对这些提出要求的计算机作资格的控 管 RemotePort 传回或设定要连结的远程埠数目 这就是在上文所提 到的通讯端口号码 以下是经常使用到的通讯连接端口 我们如果 写自己的通讯程序时 要避免使用以下的各号码 一般的建议是使 用 1024 以上的号码 Protocol 传回或设定 Winsock 控件所使用的通讯协议 可能是 TCP 或是 UDP 常数 值 描述 sckTCPProtocol 0 TCP 通讯协议( 预设选项) sckUDPProtocol 1 UDP 通讯协议 SocketHandle 传回一个与 Socket 对象代码对应的值 控件用 Socket 对象代码与 Winsock 层通讯 此属性在设计阶段是只读的 而且是无法使用的 State 传回控件的状态 这是以列举型态来表示 此属性在设计阶 段是只读的 而且是无法使用的 第 445 页,共 606 页当使用 Winsock 控件作相关的动作时 必须经常地读取 State 属性 以了解现在控件的动作过程 常数 值 描述 sckClosed 0 关闭( 默认值) sckOpen 1 开启 sckListening 2 聆听 sckConnectionPending 3 联机暂停执行 sckResolvingHost 4 识别主机 sckHostResolved 5 已识别主机 sckConnecting 6 正在联机 sckConnected 7 已联机 sckClosing 8 这台计算机正在关闭联机 sckError 9 错误 BytesReceived 传回接收到(目前在接收端缓冲区内)数据的字节数 使用 GetData 方法可撷取资料 此属性在设计阶段是只读的 而且 是无法使用 11-2-3 Winsock 控件的事件 Winsock 控件运作时会因为通讯的因素而产生事件 这些可能 产生的事件分别简要说明如下 ConnectionRequest 当远程计算机向伺服端计算机请求联机时 将 引发伺服端计算机此事件 在请求一个新联机时会激活该事件 激 活事件之后 RemoteHostIP 和 RemotePort 属性会储存有关于客户端 的信息 服务器可决定是否要接受联机 如果不接受新联机 则客户端 将得到 Close 事件 若在此事件程序内使用 Accept 方法则表示接受 接受新联机 Close:当远程计算机关闭联机时会引发此事件 不管服务器端或客 户端 只要一方关闭联机 另一方的此事件就会被引发 应用程序 应正确使用 Close 方法来关闭 TCP 联机 Connect 发生于联机动作完成时 第 446 页,共 606 页DataArrival: 当有新资料到达时 将引发此事件 如果没有撷取到 一次 GetData 呼叫的全部资料 则此事件不会引发 只有当出现新 资料时才会激活事件 要检查有多少可用的数据量时 可以随时使 用 BytesReceived 属性 此事件有一参数(bytesTotal) 此参数将会传 回接收到的字节数 利用此一事件 我们可以将资料接收进来 并 作后续的处理 SendProgress 当正在传送资料中 将引发此事件 其语法如下 object_SendProgress (bytesSent As Long, bytesRemaining As Long) SendProgress 事件的语法包含以下二个参数 参数 说明 bytesSent 从前次激活事件以来 已传送的字节数 bytesRemaining 在缓冲区等待传送的字节数 如果传送的资料很大量时 可以使用此一事件监视传送的情形 SendComplete 当传送的过程完成 将会引发此一事件 与上一个 事件共同组合 可作为传送过程及结束的监视 11-2-4 Winsock 控件的方法 控制 Winsock 控件的方法有以下的几个 Accept 仅适用于 TCP 服务器应用程序 在处理 ConnectionRequest 事件时用这个方法接受新联机 二者通常是合并使用的 执行过此 一方法后 二者之间的联机才算是建立 也才能相互传输资料 Listen 建立 Socket 并将其设定为聆听模式 此方法仅适用于 TCP 联机中的服务器端程序 也唯有在聆听模式下才有可能接受客户端 的联机请求 SendData 将资料传送给远程计算机 此方法适合服务器端或客户 端程序 一般传送的是字符串数据时 只要将字符串当成自变量传 送即可 也可以指定字符串变量予以传输 如果是二进制数据的话 传输的资料就必须以字节数组的方式储存 第 447 页,共 606 页GetData 撷取目前的数据区块 并将其储存在参数所定的变量中 Bind 指定用于 TCP 联机的 LocalPort 和 LocalIP 如果计算机中有 超过一片以上的网络卡 就用这个方法固定 Port 和 IP 以避免其 它的应用程序使用 在呼叫 Listen 方法之前 必须呼叫 Bind 方法 其语法如下 object.Bind LocalPort, LocalIP Bind 方法的语法包含以下几个参数 参数 说明 LocalPort 用来建立联机的连接埠 LocalIP 用来建立联机的本地 Internet 地址 Close 对客户端机和服务器应用程序关闭 TCP 联机或聆听 Socket 此动作将终止双方的联机 而且对方也会引发 Close 事件 PeekData PeekData 除了不会从输入队列移除资料外 其它点与 GetData 相似 PeekData 方法仅适用于 TCP 联机 平常较不使用此 法 因此法将不会清除缓冲区中的资料 11-2-5 建立 TCP 联机的步骤 要建立 TCP 联机 首先要将 Winsock 控件加载到项目中 使用 项目 \ 设定使用组件 选择 Microsoft Winsock Control 6.0 开启此组件即可 工具箱中也会出现 Winsock 控件的图标 如图 11- 2- 1 所示 第 448 页,共 606 页图 11-2-1 Winsock 的引用过程 一个完整的联机一定含有传输的双方 我们的目标在于建立一 个使用 TCP 协议的通讯程序 而 使用 Winsock 控件建立联机时 需 要考虑两方面 一个是服务器端程序 另一个则是客户端程序 服务器端是响应客户端要求的一方 它必须可以得知对方提出 联机的要求 并对此要求作出响应 接着是双方的资料交换 服务器端应有的建立步骤如下 1、 决定使用的 Port 号码 2、 透过网络卡及上述的 Port 开始聆听是否有程序提出联机要 求 3、 在 ConnectRequest 事件中决定是否 Accept 对方的联机要 求 4、 在 DataArrival 事件中 针对要求端送来的资料作接收(使 用 GetData 方法)的动作 将其存在变量中并作后续的处理 5、 若需传送资料 则使用 SendData 方法 另外在客户端应有的建立步骤如下 1、 决定呼叫的 IP 和 Port 号码(远程服务器的相关设定) 2、 使用 Connect 方法要求和伺服端建立联机 3、 当建线完成后 使用 SendData 方法开始传送资料给服务器 端 4、 在 DataArrival 事件中 针对要求端送来的资料作接收(使 用 GetData 方法)的动作 将其存在变量中并作后续的处理 当任何一方执行了 Close 的方法 另外的一方都会引发 Close 的事件 我们可以将必要的程序写入此事件程序中 第 449 页,共 606 页11-2-6 建立 TCP 联机的程序 这一节我们首先建立负责提供资料的伺服端程序和提出联机 要求 资料传送要求的客户端程序 作为建立远程监控的基础 依据上 11- 2- 5 节的步骤 以下是建立伺服端程序的作法 画面设计的步骤如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 Winsock 控件 Name 属性设成 Winsock2 其它属性均维持为默认值 3、 安排三个文字框控件 二个作为息讯显示区及传送讯息输入区 另一个则作为传送埠的输入区 4、 安排三个 Label 控件 其 Caption 属性分别输入 传送端口 讯息区 及 输出讯息区 标示上述的三个文字框 5、 安排三个按钮控件 其 Caption 属性分别给定 聆听联机 结束系统 及 传送讯息 作为执行相对功能之用 6、 依以上的步骤所作出的画面如图 11-2-2 图 11-2-2 伺服端程序的画面设计 动作流程解析 使用者设定欲开启的 Port 号码后 随即按下 聆 听联机 按钮 服务器程序会处于等待联机的状态 当远程发出联机 请求时 服务器须接收此请求 并显示出已联机的字符串 当按下 传 送讯息 的按钮时 服务器程序会将输出讯息区中的文字送出 第 450 页,共 606 页程序代码部份 1、 双击 聆听联机 按钮 在 Click 事件中加入以下的程序代码 If Winsock2.State <> sckClosed Then Winsock2.Close Winsock2.LocalPort = Val(txtPort.Text) '设定通讯端口号码 Winsock2.Listen '开始聆听 Me.Caption = "聆听中 " 此部份的程序一开始就检查是否处于联机状态 若是 则因必须 聆听 故必须先中断联机 并使用 Listen 方法进行所设定的通讯 端口的聆听工作 2、 双击 Winsock 控件 在其程序代码编辑窗口中的 ConnectionRequest 事件中输入以下的程序代码 If Winsock2.State <> sckClosed Then Winsock2.Close Winsock2.Accept requestID Me.Caption = "联机中 " 此部份的目的在于处理要求端的一个联机要求 一般说来均会同 意联机 因此在此也就直接 Accept 联机要求 3、 同样是 Winsock 控件 在其程序代码编辑窗口中的 DataArrival 事件中输入以下的程序代码 Winsock2.GetData strData '接收资料 '以下将资料显示出来 txtReceive.Text = txtReceive.Text & strData & vbCrLf txtReceive.SelLength = Len(txtReceive.Text) 联机完成后 客户端会送字符串过来伺服端 针对所送过来的指 令 此部份的程序就接收后就将其显示而接收区的文字框中 4、 同样是 Winsock 控件 在其程序代码编辑窗口中的 Close 事件中 输入以下的程序代码 txtReceive.Text = txtReceive.Text & "对方执行中断!" Winsock2.Close Winsock2.Listen '重新开始聆听 当要求端断线时(不管是什么原因) Winsock 控件的此事件就会 被引发 在此我们的程序就执行断线的指令后 重新聆听 使下 一个联机要求可以被接受 5、 双击 传送讯息 按钮 在其 Click 事件中输入以下的程序代码 If Winsock2.State <> sckConnected Then Exit Sub Winsock2.SendData txtSend.Text & vbCr 按下此按钮后 首先检查是否处于联机状态 若非处于联机状态 则无法传送字符串 当然就跳出循环 而在联机状态下则传送字 第 451 页,共 606 页符串出去 6、 双击 结束系统 的按钮 在其 Click 事件中输入以下的程序代 码 Winsock2.Close '关闭联机 End '结束系统 在关闭联机后 结束本系统 将此程序项目执行起来 按下 聆听联机 的按钮 本端所设 定的 Port 就会开始被侦测是否有联机的要求进来 并针对所传送过 来的字符串作相对应的处理 有了这个程序 我们还得发展客户端 的程序 这样才会形成完整的一组测试环境 类似伺服端程序的步骤 以下是建立客户端程序的作法 画面设计的步骤如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 Winsock 控件 Name 属性设成 Winsock1 其它属性均维持为默认值 3、 安排四个文字框控件 二个作为息讯显示区及传送讯息输入区 一个作为 IP 地址的指定区 最后一个则作为传送埠指定的输入 区 4、 安排四个 Label 控件 其 Caption 属性分别输入 传送地址 传送端口 讯息区 及 输出讯息区 标示上述的四 个文字框 5、 安排三个按钮控件 其 Caption 属性分别给定 联机 结 束系统 及 输出 作为执行相对功能之用 6、 依以上的步骤所作出的画面如图 11-2-3 第 452 页,共 606 页 图 11-2-3 客户端程序的画面设计 动作流程解析 使用者设定欲传送的 IP 地址及欲开启的 Port 号 码后 按下 联机 按钮后 客户端的程序会发出联机要求 当联机 被接受后就显示出此状况 当按下 输 出 的按钮时 客户端程序会 将输出讯息区中的文字送出给服务器 必须确定远程已开启聆听联 机 否则无法联机成功 而且也无法传送讯息给服务器计算机 程序代码部份 1、 双击 联机 按钮 在 Click 事件中加入以下的程序代码 '检查状态 若不是关闭 则执行关闭 If Winsock1.State <> 0 Then Winsock1.Close '设定远程的地址及通讯端口号码 Winsock1.RemoteHost = txtIP.Text Winsock1.RemotePort = txtPort.Text Winsock1.Connect '执行联机 TimeDelay 100 '下最循环等待联机成功 Do DoEvents Loop Until Winsock1.State = sckConnected Me.Caption = "已联机" cmdSend.Enabled = True 此部份的程序一开始就检查是否处于联机状态 若是 则因必须 聆听 故必须先中断联机 将要求联机所需要的 IP 地址和通讯端 口号码指定给属性后 就执行 Connect 方法要求联机 直到联机 被建立为止 2、 同样是 Winsock 控件 在其程序代码编辑窗口中的 DataArrival 第 453 页,共 606 页事件中输入以下的程序代码 Winsock1.GetData strData '接收资料 ResponseData = strData txtReceive.Text = txtReceive.Text & ResponseData '显示资料 txtReceive.SelLength = Len(txtReceive.Text) 联机完成后 若它端传送资料过来 将资料接收后显示到文字框 中 3、 同样是 Winsock 控件 在其程序代码编辑窗口中的 Close 事件中 输入以下的程序代码 txtReecive.Text = txtReceive.Text & " 对方要求中断 " & vbCr Winsock1.Close '中断联机 cmdSend.Enabled = False 当另一端断线时(不管是什么原因) Winsock 控件的此事件就会 被引发 在此我们的程序就执行断线的指令 4、 双击 输出 按钮 在其 Click 事件中输入以下的程序代码 I f Winsock1.State <> sckConnected Then Exit Sub Winsock1.SendData txtSend.Text & vbCr 按下此按钮后 首先检查是否处于联机状态 若非处于联机状态 则无法传送字符串 当然就跳出循环 而在联机状态下则传送字 符串出去 5、 双击 结束系统 的按钮 在其 Click 事件中输入以下的程序代 码 Winsock1.Close '关闭联机 End '结束系统 在关闭联机后 结束本系统 将此程序项目执行起来 若伺服端的 聆听联机 按钮被按下 的话 就可以按下此项目画面上的 联机 以取得和伺服端的联机 (请注意必须确定 IP 地址是否正确 否则是连不上的) 一旦二端的 联机建立后 双方便可以进行资料的交换 在这个初步的测试中 我们是以字符串的传送作为例子 图 11-2-4 是双方取得联机 并且 互传字符串的情形 第 454 页,共 606 页 图 12-2-4 客户端和伺服端的联机测试 以上的程序代码 您可以参考光盘中 范例 \ 第十一章 \ 状态控制-伺服端 档案夹中的项目 双击 Server.vbp 项目文件 即可开启伺服端测试的项目 而 范例 \ 第十一章 \ 状态控 制 -客户端 档案夹中的项目 双击 Client.vbp 项目档 则是开启客 户端测试的项目 二个项目可以同一部计算机上执行及测试 也可 以分成二部计算机作测试(笔者比较建议分开二部计算机作 情况 较真实) 伺服端的程序代码列表如下 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 检查是否处于联机 若否 则跳出事件程序 ' 若是则传送字符串出去 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdSend_Click() If Winsock2.State <> sckConnected Then Exit Sub Winsock2.SendData txtSend.Text & vbCr End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 关闭联机 并结束系统 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Command1_Click() Winsock2.Close '关闭联机 End '结束系统 End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 设定 Winsock2 控件为聆听控件 ' 设定打印机的地址 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Command3_Click() If Winsock2.State <> sckClosed Then Winsock2.Close 第 455 页,共 606 页 Winsock2.LocalPort = Va l(txtPort.Text) '设定通讯端口号码 Winsock2.Listen '开始聆听 Me.Caption = "聆听中 " End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 对方若中断联机会引发以下的事件 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock2_Close() txtReceive.Text = txtReceive.Text & "对方执行中断!" Winsock2.Close Winsock2.Listen '重新开始聆听 End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当对方要求联机时 会引发以下的事件 ' 检查状态 并同意联机要求 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock2_ConnectionRequest(ByVal requestID As Long) If Winsock2.State <> sckClosed Then Winsock2.Close Winsock2.Accept requestID Me.Caption = "联机中 " End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 接收对方送来的资料 ' 检查送来的资料 分离出控制的讯息 并输出至打印机资料端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock2_DataArrival(ByVal bytesTotal As Long) Dim strData As String, sType As String, iLen As Integer Dim Pos1%, Out1$ Winsock2.GetData strData '接收资料 '以下将资料显示出来 txtReceive.Text = txtReceive.Text & strData & vbCrLf txtReceive.SelLength = Len(txtReceive.Text) End Sub 客户端的程序则列表如下 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 简单地执行传送指令 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdSend_Click() If Winsock1.State <> sckConnected Then Exit Sub Winsock1.SendData txtSend.Text & vbCr End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 关闭联机并结束程序 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Command2_Click() Winsock1.Close '关闭联机 End '结束程序 End Sub 第 456 页,共 606 页'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 设定连接参数 ' 执行连结的动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Command3_Click() Dim i% '检查状态 若不是关闭 则执行关闭 If Winsock1.State <> 0 Then Winsock1.Close '设定远程的地址及通讯端口号码 Winsock1.RemoteHost = txtIP.Text Winsock1.RemotePort = txtPort.Text Winsock1.Connect '执行联机 TimeDelay 100 '下最循环等待联机成功 Do DoEvents Loop Until Winsock1.State = sckConnected Me.Caption = "已联机" cmdSend.Enabled = True End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当联机一端中止联机时 便会引发以下的事件 ' 我们将联机关闭 并作适当处理 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock1_Close() Dim i% txtReecive.Text = txtReceive.Text & " 对方要求中断 " & vbCr Winsock1.Close '中断联机 cmdSend.Enabled = False End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 控件收到对方送来的资料时 会引发以下的事件 ' 我们将资料接收到后 显示在文字框中 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long) Dim strData As String Winsock1.GetData strData '接收资料 ResponseData = strData txtReceive.Text = txtReceive.Text & ResponseData '显示资料 txtReceive.SelLength = Len(txtReceive.Text) End Sub 第 457 页,共 606 页 11-3 透过 Internet 达到远程监控 既然网际网络必定是未来的走向 我们也希望可以透过网络达 到远程监控的目的 本节将假设现场的分布式模块正常地透过主控 计算机作监控 但同时在另一远程的计算机也希望能够同步地监控 现场的分布式模块 有了 11-2-6 小节的建立基础后 本节即将以第十章的整合测试 为例 建立起远程监控的项目 在以下的各小节中 我们需要使用二部计算机 此二部计算机 一部作为现场监控的主控计算机 另一方面也当作是 Internet 传输 过程的服务器 专接收他方提出的要求 另一部计算机则是位于远 程的计算机 它和现场隔离甚远 二者是透过 Internet 相连接 示 意图如图 11- 3- 1 图 11- 3- 1 远程监控示意图 11-3-1 现场监控计算机端的程序 首先是建立伺服端 此伺服端位于现场 在一般时候执行其固 有的监控工作 而整个的工作情形就如第十章所发展的 整 合测试 项目 因此这部份的程序也必须由第十章的程序稍作变化及修改而 来 伺服端的程序画面除了多出一个 WinSock 控件之外 其余均和 第 458 页,共 606 页整合测试 项目一样 在此我们省略画面建置的步骤 图 11-3- 2 是服务器端的画面情形 此画面中就是多了一个 WinSock 控件 准 备作为和远程交换资料之用 其余相同 图 11-3-2 现场监控端的画面设计 资料格式 透过 TCP/IP 传输的目的在于资料的交换(已于 11-2- 6 作 过实验) 既然是资料的交换 交换的双方必须先行协议交换的方 式及内容 透过此种协议 双方所作的资料交换才能被双方所认可 基于此种原理 现场监控端必须将监控的结果传输到客户端 让客 户端的显示资料和现场监控端是完全一样的 回顾整合测试项目所 显示的状态 所需显示的信息包括以下的各项 1、 锅炉温度值 2、 马达控制电压 3、 涡轮机的前后级转速 4、 温度的高低警戒状态 5、 四个压力阀状态 6、 四个阀门控制状态 在前一小节的测试中 我们在 WinSock 联机后使用字符串传递双方 的信息 上述的 6 个项目也可以使用字符串的传送 达到信息交换 的目的 笔者将这些信息列于表 11- 3- 1 表 11- 3- 1 传送的资料及其选用格式 欲传送的显示值 格式 说明 温度值 00000 其为整数 以 5 个字符表示 电压值 00.000 单精度数值 以小数点前二位 小数点后三位 表示 前后级转速 000000 模块最高可量到 100K 因此使用 6 个字符表 第 459 页,共 606 页示 温度警戒状态 0 以 0 或 1 表示是否有警戒 压力阀状态 0 以 0 或 1 表示压力阀之开启/ 关闭 阀门开关状态 0 以 0 或 1 表示是否开启/ 关阀门 双方传输的格式就以表 11- 3-1 作为标准 现场伺服端将状态以此表 作编码后传送 而远程客户端则将接收到的状态字符串亦依此表作 译码 并显示到画面上 动作流程解析 一般的动作过程均一样 但在此部份必须加上网 络程序的接收及传送 此部份需建立 Winsock 伺服端的程序式 一旦 接受请求后 就利用上述的格式将欲显示的资料传送到远程的计算机 中 程序代码部份 1、 程序代码的部份和 整合测试 项目是相当类似的 二者的不同 在于本项目多了一个 WinSock 控件 此控件的目的在于达到信息 传输的目的 而程序的架构上也是多了 WinSock 的部份 其它的 部份是保持一样的 以下就仅列出 WinSock 的部份 2、 双击窗体 在 Form_Load 事件中加入以下的程序代码 Ocaption = Me.Caption '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotValue(0 To 2, 0 To MaxPlotNo) TempSensitivity = 30 '每 1 伏为 30 度 C 默认值 MSComm1.PortOpen = True TimeDelay 1000 '激活 7012 的警戒 LoHiValue(0) = 3 * TempSensitivity LoHiValue(1) = 8 * TempSensitivity Buf = SendCmd2Comm(MSComm1, "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02EAM", vbCr, 1000) '先将 Relay 全部打开 Buf = SendCmd2Comm(MSComm1, "@01F", vbCr, 1000) For i = 0 To 3 Relay(i) = True Next i '变更 7080 型态为频率模式 使用%AANNTTCCFF 指令 Buf = SendCmd 2Comm(MSComm1, "%0404510640", vbCr, 1000) '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" 第 460 页,共 606 页 MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ Alarm1.wav" MMC1.Command = "Open" mciOK = True 'WinSock 控件 If Winsock2.State <> sckClosed Then Winsock2.Close Winsock2.LocalPort = 3000 '设定通讯端口号码 Winsock2.Listen '开始聆听 Me.Caption = Ocaption & " *** 等待远程计算机的联机中 ***" 粗黑色字体的部份是和原来的程序不一样的地方 目的在于开启 聆听的网络 Port 以便让远程的计算机可以和此部现场监控计算 机取得联机 并要求最新的监控资料 在此选择的 Port 号码是 3000 以避免和其它的应用程序用到相同的信道 3、 双击 Winsock 控件 在其程序代码编辑窗口中的 ConnectionRequest 事件中输入以下的程序代码 If Winsock2.State <> sckClosed Then Winsock2.Close Winsock2.Accept requestID Me.Caption = Ocaption & " *** 远程计算机已联机上本机 ***" 此部份的目的在于处理要求端的一个联机要求 一般说来均会同 意联机 因此在此也就直接 Accept 联机要求 4、 同样是 Winsock 控件 在其程序代码编辑窗口中的 DataArrival 事件中输入以下的程序代码 Winsock2.GetData strData '接收资料 '将资料作处理 Pos1 = InStr(1, strData, "Status") '若寻到此字符串 则分离出里面的控制指令 If Pos1 > 0 Then '温度值 Out1 = Format(Val(lblTemp.Caption), "00000") '电压值 Out1 = Out1 & Format(Val(lblVoltage.Caption), "00.000") '前级转速 Out1 = Out1 & Format(Val(lblF1.Caption), "000000") '后级转速 Out1 = Out1 & Format(Val(lblF2.Caption), "000000") '警戒状态 Out1 = Out1 & IIf(imgE1.Picture = imgRed.Picture, "1", "0") Out1 = Out1 & IIf(imgE2.Picture = imgRed.Picture, "1", "0") '压力阀状态 For i = 0 To 3 Out1 = Out1 & IIf(imgP(i).Picture = imgRed.Picture, "1", "0") Next i '阀门控制状态 For i = 0 To 3 Out1 = Out1 & IIf(imgR(i).Picture = imgRed.Picture, "1", "0") Next i 第 461 页,共 606 页 Winsock2.SendData "S" & Out1 '传送信息 Exit Sub End If ' 侦测数字输出 Pos1 = InStr(1, strData, "DO") '数字输出控制 If Pos1 > 0 Then DOutValue = Val("&H" & Mid(strData, Pos1 + 2, 1)) For i = 0 To 3 Relay(i) = IIf(DOutValue And 2 ^ i, True, False) Next i fRelay = True Exit Sub End If '侦测模拟输出 Pos1 = InStr(1, strData, "AO") '数字输出控制 If Pos1 > 0 Then VOut = Val(Mid(strData, Pos1 + 2, 6)) fVOut = True End If 联机完成后 要求端会送要求的指令过来伺服端 针对所送过来 的指令 此部份的程序就作了必须的处理 一开始使用 GetData 的方法将要求端的指令字符串收进来 接下来就检查传送过来的 字符串是否含有”Status” ”DO” ”AO”等字符串 这三组字符串 分别表示状态传送 数字输出及模拟输出等三种控制动作 若是 接收到”Status”字符串 则将现在监控画面上的各个控件数值编码 组合后 以一个”S”作开头传送整个编码字符串给远程计算机 编码的方式就依表 11-3-1 数字输出的动作是由远程计算机传送过来的 表示远程计算机要 求监控端必须作相对应的数字输出控制 和现场的控制方法一 样 笔者对于此一指令的作法是改变数组内容 并且改变旗标 让定时器中的检查得知必须作数字输出 模拟输出的动作也是由远程计算机送过来的 而作法也是改变模 拟输出变量的值 并且改变旗标 当定时器循环检查旗标时便作 模拟输出的动作 5、 同样是 Winsock 控件 在其程序代码编辑窗口中的 Close 事件中 输入以下的程序代码 Me.Caption = Ocaption & " *** 等待远程计算机的联机中 ***" Winsock2.Close Winsock2.Listen '重新开始聆听 当要求端断线时 Winsock 控件的此事件就会被引发 在此我们 的程序就执行断线的指令后 重新聆听 使下一个联机要求可以 被接受 6、 双击 结束系统 的按钮 在其 Click 事件中输入以下的程序代 第 462 页,共 606 页码 Winsock2.Close '关闭联机 End '结束系统 在关闭联机后 结束本系统 其它未述及的程序部份和 整合测试 项目是相同的 笔者在 此就对予列出 读者可参阅第十章的说明 将此程序项目执行起来 程序会自动开启一个准备联机的信 道 并等待远程计算机的联机 当 激活系统 按钮被按下后 本 地的计算机仍然和第十章的情形一样不断地取各个状态 并显示到 画面上 图 11-3-3 是程序执行的画面 此时远程计算机尚未要求联 机 图 11-3-3 现场监控端的执行情形(此时远程尚未联机) 以上的程序代码 您可以参考光盘中 范例 \ 第十一章 \ 整合测试-现场监控端 档案夹中的项目 双击 Server.vbp 项目档 档案夹中的项目 双击 Server.vbp 项目档 即可开启测试的项目 欲完成一个完整的测试 还需要一个客户端的项目程序 下一小节 即将进行此部份的讨论 11-3-2 远程监控计算机端的程序 上一小节已经把服务器的程序 依照我们的控制的需求建立起 来了 这一小节还要进一步地建立客户端(也就是要求端或命令端) 的程序 第 463 页,共 606 页此部份的画面我们希望可以和现场监控端一致 但在网络联机 上还需要一个可以输入 IP 地址的地方 因此在画面上除了和现场 监控端一样的画面外 还多了一个输入 IP 的文字框 如图 11-3-4 所示 图 11-3-4 远程监控画面的设计 此画面的设计也是由 整合测试 的项目衍生而来 读者可由 该项目的画面设计步骤得知其画面设计的大概 此远程画面所需的 IP 输入区可用简单的文字框便可达到该功能 画面设计的部份就由 整合测试 项目再加上一个文字框即可 本小节只讨论程序的设 计部份 本部份的程序需考虑联机上的数据传输 在现场的监控程序部 份主要是由 MSComm 控件传送实际的控制指令给各模块 并取得 相关的模块 IO 数据 但此远程部份当然是不可能利用 MSComm 控 件作此功能 而是必须利用 WinSock 控件达到数据传输的目的 所 需的显示资料在联机到现场的监控端后要求现场端的计算机传过 来 上一小节已经讨论到资料的编码 本小节利用同样的程序建立 相关的程序 并将所取得的资料显示到画面上 除了将资料传送过来 并显示到画面上之外 还必须考虑到是 否允许远程的计算机执行现场端的控制动作(以整合测试来说就是 数字输出及模拟输出二种功能) 设计者也许基于某些考量而不允 许远程执行控制功能 或允许在通过密码验证后执行部份控制功 能 我们暂不考量限制问题 一律允许使用者执行远程的控制 以 下是建立的程序代码步骤 第 464 页,共 606 页动作流程解析 此部份的动作流程和第十章类似 不过其使用 网络的方式式取得资料 因此我们要在画面设有一个输入 IP 地址 的地方 当按下激活系统按钮时 程序去联机现场的监控计算机 并要求监控计算机将数据传回来 程序代码部份 1、 双击窗体 在 Form_Load 事件程序中加入以下的程序代码 Ocaption = Me.Caption '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 TempSensitivity = 30 For i = 0 To 3 Relay(i) = True Next i '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotValue(0 To 2, 0 To MaxPlotNo) '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ Alarm1.wav" MMC1.Command = "Open" mciOK = True 初始化的动作在此程序内先行建立 2、 双击 激活系统 按钮 在 Click 事件中加入以下的程序代码 If Timer1.Enabled Then Winsock1.Close '中断联机 Timer1.Enabled = False Me.Caption = Ocaption cmdStart.Caption = "激活系统" Exit Sub End If '网络设定及连接 '检查状态 若不是关闭 则执行关闭 Me.Caption = Ocaption & " ---联机现场计算机中 " If Winsock1.State <> 0 Then Winsock1.Close '设定远程的地址及通讯端口号码 Winsock1.RemoteHost = txtIP.Text Winsock1.RemotePort = 3000 Winsock1.Connect '执行联机 '下最循环等待联机成功 TT = GetTickCount Do DoEvents Loop Until Winsock1.State = sckConnected Or GetTickCount - TT >= 5000 第 465 页,共 606 页 '若未成功联机 则跳出循环 MIRL If Winsock1.State <> sckConnected Then Me.Caption = Ocaption MsgBox "联机现场计算机失败!", vbCritical + vbOKOnly, "连接讯 息 " Exit Sub End If Me.Caption = Ocaption & " *** 已联机上现场的计算机 ***" Timer1.Enabled = True cmdStart.Caption = "停止系统" 此部份会先建立 WinSock 的联机 在联机动作执行时是由画面上 的 IP 地址文字框取得所要联机的地址 所使用 Port 设定为 3000 这和现场监控端是一样的 程序中的循环是让联机动作有时间可 以进行 但也设定 5 秒钟的限制 免得联机不成功而使得系统的 程序停在该处 而形同当机 一旦联机成功 画面上出现 已连 上远程计算机 的字符串 并且激活定时器 让传输资料的动作 开始进行 3、 双击定时器控件 在其 Timer 事件中输入以下的程序代码 '传回显示状态 Buf = SendCmd2TCPIP(Winsock1, "Status", 5000) If Buf <> "" Then Pos1 = InStr(1, Buf, "S") If Pos1 > 0 Then '取得温度值 Pos1 = Pos1 + 1 lblTemp.Caption = Str(Val(Mid(Buf, Pos1, 5))) '填入数组中 PlotValue(0, NowX) = Val(lblTemp.Caption) '取得电压值 Pos1 = Pos1 + 5 lblVoltage.Caption = Str(Val(Mid(Buf, Pos1, 6))) Pos1 = Pos1 + 6 '取得前级转速 lblF1.Caption = CStr(Val(Mid(Buf, Pos1, 6))) PlotValue(1, NowX) = Val(lblF1.Caption) Pos1 = Pos1 + 6 '取得后级转速 lblF2.Caption = CStr(Val(Mid(Buf, Pos1, 6))) PlotValue(2, NowX) = Val(lblF2.Caption) Pos1 = Pos1 + 6 '判断警戒状态 imgE1.Picture = IIf(Mid(Buf, Pos1, 1) = "1", imgRed.Picture, imgGreen.Picture) Pos1 = Pos1 + 1 imgE2.Picture = IIf(Mid(Buf, Pos1, 1) = "1", imgRed.Picture, imgGreen.Picture) Pos1 = Pos1 + 1 '判断压力阀门状态( 数字输入) For i = 0 To 3 imgP(i).Picture = IIf(Mid(Buf, Pos1, 1) = "1", imgRed.Picture, imgGreen.Picture) Pos1 = Pos1 + 1 第 466 页,共 606 页 Next i '判断阀门状态( 数字输出) For i = 0 To 3 imgR(i).Picture = IIf(Mid(Buf, Pos1, 1) = "1", imgRed.Picture, imgGreen.Picture) Pos1 = Pos1 + 1 Next i End If End If '检查控制需求并传送控制讯息 CheckControl '以下是趋势绘图 Plot NowX '进入绘图子程序 此循环是主要的工作就是和现场监控计算机取得沟通 首先使用 SendCmd2TCPIP 传送要求远程计算机传回状态的”Status”字符 串 当远程计算机收到此字符串后就会将现在的状态编码后传 回 函式传回值就是被编码后的字符串 接下来是将收到的状态 字符串予以译码 并指定给相关的显示控件 需显示在图片框中 的变量尚需先存放到数组中 译码的顺序及方法如表 11-3-1 SendCmd2TCPIP 函式如下 Function SendCmd2TCPIP(TCP As Winsock, CS As String, WaitTime As Long) As String Dim Buf$, TT As Long '传送指令 fSend = True TCP.SendData CS TT = GetTickCount '下面循环等待 TCPIP 接收到传回的字符串 Do DoEvents Loop Until Not fSend Or GetTickCount - TT >= WaitTime '若收到字符串就传回该字符串 '否则传回空字符串 If Not fSend Then SendCmd2TCPIP = TCPStr Else SendCmd2TCPIP = "" End If End Function 此函式的参数有三个 第一个是 WinSock 控件 第二个是所要传 送的字符串 第三个是等待远程计算机回传的时间 由 11-2-6 可 知讯息的传递是透过 WinSock 控件的 DataArrival 事件 所以在 程序中以一个 fSend 变量作为判断是否已接收到讯息 当然在 WinSock 控件中当资料传回时也将此变量改为 False 用以判断资 料是否已由 WinSock 接收完成 若超过等待时间仍未接收到讯息 则传回空字符串 表示此次的传输失败 译码完成后 另外还有数字输出和模拟输出的部份要检查 这二 个部份是使用 CheckControl 子程序予以检查 该子程序如下所示 Sub CheckControl() 第 467 页,共 606 页 Dim i%, OutValue% Dim Buf$ '若数字输出旗标成立 If fRelay Then OutValue = 0 '计算数字输出的数值 For i = 0 To 3 OutValue = OutValue + IIf(Relay(i), 2 ^ i, 0) Next i Buf = SendCmd2TCPIP(Winsock1, "DO" & Hex(OutValue), 20) fRelay = False End If '若改变马达控制旗标成立 If fVOut Then '送出模拟指令 Buf = SendCmd2TCPIP(Winsock1, "AO" & Format(VOut, "00.000"), 20) fVOut = False End If 将数字输出和模拟输出编码后 同样是利用 SendCmd2TCPIP 将 信息送给现场的计算机 绘图的程序和之前是一样的 就不加赘述了 4、 双击 Winsock 控件 在其程序代码编辑窗口中的 DataArrival 事件 中输入以下的程序代码 Dim strData As String Winsock1.GetData TCPStr '接收资料 fSend = False '将旗标设成 False 以通知 SendCmd2TCPIP 函式 当资料传送过来时 就利用 GetData 方法接收进来 并将接收旗 标设成 False 表示已接收完成 此旗标会提供 SendCmd2TCPIP 一个讯息 使该程序可以进行其后续的工作 5、 同样是 WinSock 按钮 在其 Close 事件中输入以下的程序代码 Me.Caption = Ocaption Winsock1.Close '中断联机 当远程断线讯息传入时 本端也执行断线的工作 6、 双击 结束 的按钮 在其 Click 事件中输入以下的程序代码 Winsock2.Close '关闭联机 End '结束系统 在关闭联机后 结束本系统 将此程序项目执行起来 设定远程的 IP 地址后 按下 激活系 统 的按钮就可以和服务器联机(顺利的话) 现场监控端也会将资 料传回本端 画面上会出现和现场一样的资料 第 468 页,共 606 页图 11-3-5 是现场监控端和远程客户端程序执行时现场监控端的 实验情形 图 11-3-6 是现场监控端和远程客户端程序执行时远程客 户的实验情形 请读者注意二张图中以圆形框住的部份 它们显示 相同的情形 由图中显示客户端联机较晚 故相同的监控情形较慢 出现在图中 图 11-3-5 现场监控端实验情形 图 11-3-6 远程客户端实验情形 远程客户端的程序代码 您可以参考光盘中 范例 \ 第十一 章 \ 整合测试-远程监控端 档案夹中的项目 双击 Client.vbp 项目档档案夹中的项目 双击 Client.vbp 项目档 即可开启测试的 项目 本小节的完整程序代码列表如下 第 469 页,共 606 页Option Explicit Dim fRelay As Boolean 'Relay 是否动作的旗标 Dim fTemp As Boolean '温度 Sensitivity 是否改变的旗标 Dim fVOut As Boolean '模拟输出是否动作的旗标 Dim LoHiValue(0 To 1) As Integer '高低值警戒值 Dim Relay(0 To 3) As Boolean '四个 Relay 的开关状态 Dim VOut As Single '模拟输出值 Dim NowX As Long '现在的 X 轴位置 Dim MaxPlotNo As Long '最长的 X 轴范围 Dim PlotValue() As Long '读值数组 Dim fIsOver As Boolean '是否超过绘图点数的旗标 Dim P1Width&, P1Height& '绘图的图片框的范围记录 Dim mciOK As Boolean '是否播音完毕 Dim TempSensitivity As Single ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() Winsock2.Close '关闭联机 End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 激活系统 按钮后激活此事件 ' 将定时器作转态 在激活与关闭间作切换 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Dim TT As Long If Timer1.Enabled Then Winsock1.Close '中断联机 Timer1.Enabled = False Me.Caption = Ocaption cmdStart.Caption = "激活系统" Exit Sub End If '网络设定及连接 '检查状态 若不是关闭 则执行关闭 Me.Caption = Ocaption & " ---联机现场计算机中 " If Winsock1.State <> 0 Then Winsock1.Close '设定远程的地址及通讯端口号码 Winsock1.RemoteHost = txtIP.Text Winsock1.RemotePort = 3000 Winsock1.Connect '执行联机 '下最循环等待联机成功 TT = GetTickCount Do DoEvents Loop Until Winsock1.State = sckConnected Or GetTickCount - TT >= 5000 '若未成功联机 则跳出循环 MIRL If Winsock1.State <> sckConnected Then Me.Caption = Ocaption MsgBox "联机现场计算机失败!", vbCritical + vbOKOnly, "连接讯 息 " Exit Sub 第 470 页,共 606 页 End If Me.Caption = Ocaption & " *** 已联机上现场的计算机 ***" Timer1.Enabled = True cmdStart.Caption = "停止系统" End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口在窗体加载时就开启 让其它的程序可以开始使用通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim Buf$, i% Ocaption = Me.Caption '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 TempSensitivity = 30 For i = 0 To 3 Relay(i) = True Next i '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotVa lue(0 To 2, 0 To MaxPlotNo) '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ Alarm1.wav" MMC1.Command = "Open" mciOK = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'R1~R4 的 Click 事件区 ' 在此可控制 Relay 的动作 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblRL_Click(Index As Integer) Relay(Index) = Not Relay(Index) fRelay = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '7021 的 Click 事件区 ' 在此可控制模拟输出的数值 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblVOut_Click() VOut = Val(InputBox(" 请输入电压值(0.0~10.0)", "7021 模拟输出" , "0")) fVOut = True 第 471 页,共 606 页End Sub Private Sub MMC1_Done(NotifyCode As Integer) If NotifyCode = 1 Then MMC1.Command = "Stop" MMC1.Command = "Prev" mciOK = True End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件 ' 所有的通讯程序在此程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim Buf$ Dim Pos1%, i% '传回显示状态 Buf = SendCmd2TCPIP(Winsock1, "Status", 5000) If Buf <> "" Then Pos1 = InStr(1, Buf, "S") If Pos1 > 0 Then '取得温度值 Pos1 = Pos1 + 1 lblTemp.Caption = Str(Val(Mid(Buf, Pos1, 5))) '填入数组中 PlotValue(0, NowX) = Val(lblTemp.Caption) '取得电压值 Pos1 = Pos1 + 5 lblVoltage.Caption = Str(Val(Mid(Buf, Pos1, 6))) Pos1 = Pos1 + 6 '取得前级转速 lblF1.Caption = CStr(Val(Mid(Buf, Pos1, 6))) PlotValue(1, NowX) = Val(lblF1.Caption) Pos1 = Pos1 + 6 '取得后级转速 lblF2.Caption = CStr(Val(Mid(Buf, Pos1, 6))) PlotValue(2, NowX) = Val(lblF2.Caption) Pos1 = Pos1 + 6 '判断警戒状态 imgE1.Picture = IIf(Mid(Buf, Pos1, 1) = "1", imgRed.Picture, imgGreen.Picture) Pos1 = Pos1 + 1 imgE2.Picture = IIf(Mid(Buf, Pos1, 1) = "1", imgRed.Picture, imgGreen.Picture) Pos1 = Pos1 + 1 '判断压力阀门状态( 数字输入) For i = 0 To 3 imgP(i).Picture = IIf(Mid(Buf, Pos1, 1) = "1", imgRed.Picture, imgGreen.Picture) Pos1 = Pos1 + 1 Next i '判断阀门状态( 数字输出) For i = 0 To 3 imgR(i).Picture = IIf(Mid(Buf, Pos1, 1) = "1", imgRed.Picture, imgGreen.Picture) Pos1 = Pos1 + 1 Next i 第 472 页,共 606 页 End If End If '检查控制需求并传送控制讯息 CheckControl '以下是趋势绘图 Plot NowX '进入绘图子程序 End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 检查控制需求并传送控制讯息 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub CheckControl() Dim i%, OutValue% Dim Buf$ '若数字输出旗标成立 If fRelay Then OutValue = 0 '计算数字输出的数值 For i = 0 To 3 OutValue = OutValue + IIf(Relay(i), 2 ^ i, 0) Next i Buf = SendCmd2TCPIP(Winsock1, "DO" & Hex(OutValue), 20) fRelay = False End If '若改变马达控制旗标成立 If fVOut Then '送出模拟指令 Buf = SendCmd2TCPIP(Winsock1, "AO" & Format(VOut, "00.000"), 20) fVOut = False End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 绘图的子程序 ' 三个通道的值取后即进行图形的绘制 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Plot(StartX&) Dim i&, j&, k& Dim ShowColor& Dim EndPos& PicHide.Cls '清除图形 '先绘温度的部份 PicHide.Scale (0, 10 * TempSensitivity)- (MaxPlotNo, 0) PicHide.ForeColor = RGB(0, 0, 0) '以下作绘格子线的绘图定设定 PicHide.DrawWidth = 1 PicHide.DrawStyle = vbDot '以下二个子程序是作格子线的绘制 For i = 1 To 9 PicHide.Line (i * MaxPlotNo / 10, 0)- (i * MaxPlotNo / 10, 10 * T empSensitivity) Next i For i = 1 To 4 第 473 页,共 606 页 PicHide.Line (0, i * 10 * TempSensitivity / 5)- (MaxPlotNo, i * 10 * TempSensitivity / 5) Next i '以下是绘趋势线的格式设定 PicHide.DrawWidth = 2 PicHide.DrawStyle = vbSolid '判断是否已超过设定的绘图笔数 If fIsOver Then k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终点 '当已超过后 就以最大点数作为绘图的终点( 此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) ShowColor = RGB(255, 0, 0) '先作温度值的绘图 PicHide.PSet (0, PlotValue(0, 0)), ShowColor For i = 1 To EndPos PicHide.Line - (i, PlotValue(0, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i '再作转速的绘图 '以下设定转速绘图范围 (Xmin,YMax)- (XMax,YMin) PicHide.Scale (0, 15000)- (MaxPlotNo, 5000) For j = 1 To 2 '二个通道 If fIsOver Then k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终 点 '当已超过后 就以最大点数作为绘图的终点(此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) '指定绘图颜色(j=0 时使用红色 否则使用蓝色) ShowColor = IIf(j = 1, RGB(255, 255, 0), RGB(0, 0, 200)) PicHide.PSet (0, PlotValue(j, 0)), ShowColor For i = 1 To EndPos PicHide.Line - (i, PlotValue(j, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i Next j '快速图形拷贝函式 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.hDC, 0, 0, &HCC0020) NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 第 474 页,共 606 页 fIsOver = True End If End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当联机一端中止联机时 便会引发以下的事件 ' 我们将联机关闭 并作适当处理 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock1_Close() Me.Caption = Ocaption Winsock1.Close '中断联机 End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 控件收到对方送来的资料时 会引发以下的事件 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long) Dim strData As String Winsock1.GetData TCPStr '接收资料 fSend = False '将旗标设成 False 以通知 SendCmd2TCPIP 函式 End Sub 模块内的程序行表如下 Option Explicit Declare Function GetTickCount Lib "kernel32" () As Long Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, _ ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, _ ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, _ ByVal ySrc As Long, ByVal dwRop As Long) As Long Public fSend As Boolean '已送出指令的旗标 Public TCPStr As String '由 TCP 传回的结果字符串 Public Ocaption As String '记录原来的窗体抬头文字 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 时间延迟子程序 单位为毫秒(ms) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 指令送出 并等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 'CS 是命令字符串 Function SendCmd2TCPIP(TCP As Winsock, CS As String, WaitTime As Long) As String Dim Buf$, TT As Long 第 475 页,共 606 页 '传送指令 fSend = True TCP.SendData CS TT = GetTickCount '下面循环等待 TCPIP 接收到传回的字符串 Do DoEvents Loop Until Not fSend Or GetTickCount - TT >= WaitTime '若收到字符串就传回该字符串 '否则传回空字符串 If Not fSend Then SendCmd2TCPIP = TCPStr Else SendCmd2TCPIP = "" End If End Function 11-3-3 完整的网络控制测试 前二个小节中 我们发展了用于现场监控端程序及远程监控程 序 本节展示如何把这二个程序执行起来的步骤及可能的执行问 题 可能的话 利用二部计算机 一部执行 11-3-1 的范例程序 此 范例程序是现场监控端 第二部计算机执行 11-3-2 的范例程序 此 范例程序是远程控制端 也就是要求端 输入服务器端的 IP 地址(在 我们的例子中是”169.254.3.162” 读者必须依自己的计算机情形而 更改)后 可以按下 激活系统 按钮来要求连结到现场监控计算 机 当联机成功后 远程计算机便会出现现场的讯息 若找不到二部计算机 也可以使用同一部计算机 同时执行这 二个程序 而 IP 地址也是给本部计算机的 IP 即可 也可以得而相 同的结果 只不过就没有那么有趣了 地址不正确 如果一切顺利的话 我们都可以得到图 11-3-5 的 测试 结果 不过若是在网络上找不到所指定的 IP 地址的话 计 算机会 尝试从其它的途径寻找(例如询问是否拨接上网) 若还是无法找到 则程序将由于无法连接上服务器而出现错误讯息 在客户端的程序 是以 WinSock 的 State 属性作判断而得知 尚未联机 若尚未联机就急着送出控制的资料 将会因为联机的不 存在而出错 因此我们在客户端的程序中 当按下 激活系统 后 会先联机伺服端计算机 就是为了避免此种情形的发生 当读者进 第 476 页,共 606 页行 Internet 的程序时 也必须要了解联机建立是传输资料的先决条 件 并检查联机状态 第 477 页,共 606 页11-4 Internet 模块-7188E 使用一部现场的监控计算机除了可以作监控外 尚可以接受远 程计算机的联机要求而传送讯息到远程 如果是现场的情形限制而 无法放置一部计算机 但是又必须将现场的资料传送到网络上的监 控计算机时 使用具有网络传输功能的 7188E 模块就成了必须的选 择 本节即将讨论如何使用此模块 11-4-1 模块介绍 7188E 模块外观如图 11-4-1 所示 其中有一个连接 RJ-45 网络 线的接头位置 另各有一组 RS- 232 及 RS- 485 的接线位置 图 11-4-1 7188E 模块外观 主要的接线位置在图 11-4-1 的左方 电源及 RS- 485 的接线部 份和其它的模块均相同 请读者参考各章对于模块上的接线说明即 可 另有一组 RS- 232 的接线位置 此组接线可让使用者接到 RS- 232 的相关设备上 除了一般 232 应具备有 RxD(接收)及 TxD(传送)脚 位外 尚有 CTS RTS 二支脚位 可视需要而使用 RJ- 45 的接线位置在图左的上方 是标准的网络线接头 可以 使用在 10/100M 的环境下 使用上只要将网络线接在此接头位置和 集线器(Hub)中的接头位置即可 第 478 页,共 606 页模块编号 7188 开头的模块里面为一 80188 的 CPU 可以执行执 行档 因此可让使用者自行写入控制程序在其中 弹性相当大 当 模块的部份功能无法提供时 或是使用者有特殊应用需求时 均可 使用 7188 编号开头的模块达到需求 我们在此仅讨论 7188E 的应 用 使它可以顺利地在主控计算机和分布式模块间传送控制指令 此模块的主要功能在于透过 Ethernet 网络线传送 RS- 485 的控 制指令 以便可以透过网络线而达到控制距离较远的分布式模块的 目的 架构上如图 11- 4- 2 图 11- 4- 2 使用 7188E 监控示意图 由图 11- 4-2 主控计算机的位置也许位于网络上的某一个节点 因此控制 7000 分布式模块当然是无法直接接上 RS- 485 网络 此时 可透过 7188E 模块作为中继站 将主控计算机的指令先送到 7188E 作处理 再传送到分布式模块 由于路径上是主控计算机- >7188E- > 分布式模块 程序上则是必须考虑主控计算机到 7188E 的网络程 序 以及由 7188E 到分布式模块的 RS- 485 字符串指令 11-4-2 7188E 软件架构 7188E 在硬件上提供了一个接 RJ- 45 网络线的接头 主控计算 机的所有指令必须透过此信道而到达 7188E 此信道我们称为 命 令信道 (Command Channel) 当命令到达 7188E 后 控制程序可 以控制该模块 也可以将指令送到该模块所提供的 RS- 232 信道 或是 RS- 485 信道 透过这二个信道 不管使用者接的是 232 设备 或是 485 设备 都可以透过信道而进行资料的交换 我们称这二个 信道为 数据信道 (Data Channel) 第 479 页,共 606 页编号 7188 开头的模块均允许使用者透过自行撰写的程序进行 控制 RS-485 模块的动作 甚至是较为低阶的总线控制 而 7188E 模块自然也允许使用自行 DownLoad 程序作独立发的控制 不过 此编号的模块在出厂时已事先装入程序 并且在电源接上后会自行 激活运作 我们也将针对此种情形作讨论 而独立系统的开发并不 在讨论范围 有兴趣的读者请自行参考原厂光盘片中有关 7188 的 说明 模块的内建程序中已具备设定 转向(将网络指令转到串行端口 网络)等功能 主控计算机透过命令信道和 7188E 取得通讯 有关 7188E 本身的设定及控制也透过此信道进行 有关模块的命令如表 11- 4- 1 所示 表 11- 4- 1 7188E 命令列表 命令 格式 范例 说明 Version <01> ex:01 => return “v1.4” 传回模块版本号码 RTS <05,Port(1),Set(1)> ex: 0511=> com1 RTS on 设定 232 埠上的 RTS 状态 Baud <06,Port(1),Baud> ex:0619600 => com1 9600 设定资料信道的传 输速度(BaudRate) Line <07,Port(1),LineContr ol(3)> ex:0718N1=> com1 N,8,1 设定资料信道的传 输格式 SetIp <08,IP(12)> ex:081921682550 01 =>192.168.255.1 设定模块的 IP 地址 Recv Timeout <09,Timeout(4)> ex:091000 => recv timeout 1 sec 设定接收的逾时时 间设定( 毫秒) ServerName <10> ex:10 => return “7188E2” 传回模块名称 Diag <11,String(<80)> ex:11hello => return “” 送出诊断字符串 ( 正常时会送回相 同字符串) 由表 11-4-1 针对模块本身(命令信道)及串行通讯信道(资料信 道 ) 均有设定的命令可供使用 这些指令的格式由一个命令号码 开始(01~11) 再接上埠号(RS-232 的埠号为 1 而 RS-485 的埠号为 2) 再接上其它的指令字符串 若不需要指令端口号者 则是直接 接上命令字符串 再者 由于此模块是透过 RJ-45 网络线和主控连 接 它们二者均是网络节点一员 因此若要传送表 11- 4-1 的指令到 模块的话 还是必须撰写网络程序才能传送 我们在下一小节将会 进行程序建构的讨论 第 480 页,共 606 页11-4-3 改变 7188E 模块的 IP 地址 由 11-2 及 11-3 节 我们已经了解到如何建立网络程序 达到 讯息(字符串)交换的目的 相同的方法可以应用到主控计算机和 7188E 之间的通讯 以下我们就讨论如何和 7188E 建立联机 我们也已经讨论过如何建立网络的联机 并且顺利地在二部计 算机间传送讯息 建立网络联机的第一步必须确定所要连结的 IP 地址是否正确且存在 7188E 出厂时会烧入执行文件 当模块接上 电源后 马上会开始运作 而 IP 地址也已被定义在其中 当模块 激活时 LED 上会显示该模块的预设 IP 地址 此地址不一定可以 接在我们的网络节点上(必须考虑到网络分割及子网掩码的设定) 通常拿到模块后还必须先变更 IP 地址 以符合实际的网络设定 另外 除了 IP 地址必须注意之外 通讯 Port 也必须注意 依据使 用手册的规定 7188E 模块使用了 3 个 Port 编号分别是 10000 10001 10002 在上一小节提到的信道中 编号 10000 的 Port 用于 和模块的命令信道沟通 编号 10001 的 Port 用于和 RS-232 的数据 信道沟通 编号 10002 的 Port 则是用于和 RS- 485 的数据信道沟通 联机时我们必须考虑所要使用的信道而决定使用的 Port 以免发生 错误 改变 IP 的方法有二种 第一种是先改变主控计算机上的 IP 地 址 使得和 7188E 上的地址处于同一个网络屏蔽内 例如笔者所使 用的 7188E 模块出厂时的 IP 设定是 192.168.255.1 为了可以让 11- 2 节的程序可以顺利地和模块相连接 笔者将所使用的计算机变更其 IP 设定为 192.168.255.2 子网掩码设定为 255.255.255.0 如此一 来主控计算机就可以和模块取得正常的联机 而不需要经 Router 到其它的网络区域去寻找计算机 改变主控计算机的步骤如图 11-4-3 第 481 页,共 606 页 图 11-4-3 变更主控计算机 IP 的步骤 改变完成 并按下确定后 系统会要求重新开机 重新开机后 就可以使用新的 IP 地址 更改后 使用 范例 \ 第十一章 \ 状态控制-客户端 档案夹中 Client.vbp 项目档 我们可以依据表 11-4-1 的方式下达到指令 为了和整个工作的网络接近 笔者还建 议将模块改成和操作环境接近的 IP 设定较佳 例如笔者计算机的 预设 IP 值是 169.254.3.162 虽然因应连接模块而改成 192.168.255.2 一旦使用 Client.Vbp 项目连接上后 就可传送字符 串 ” 08169254003165”(参考表 11-4- 1 命令)将其改为 169.254.3.162 使其方便和原来的网络系统处于同一层 进行改变 IP 的工作完成 后 当然主控计算机还是得改回自动取得 IP 地址 以恢复原来的 样子 第二种方式是采用 7188E 内部 OS 的设定方式 假设控计算机 的 IP 是 169.254.3.162(笔者计算机的预设 IP 值 ) 我们当然希望模 块的地址也位于附近 例如我们可以将模块的 IP 位设定 169.254.3.165 7188E 便可以使用主控计算机的屏幕和键盘作为模 块的标准输出和输入 改变步骤如下 1、 将 7188E 断电 使用原厂附线连接主控计算机的 COM 端口及 7188E 的 RS- 232 埠 2、 将 7188E 模块的 INIT*和 GND 接脚位相连接 3、 激活电源 模 块上的 LED 出现模块编号 并开始出现计数 4、 拔掉连接 7188E 和 GND 的连接线 5、 激活 7188X.EXE 执行档 第 482 页,共 606 页6、 主控计算机出现 DOS 画面 在此画面中键入 SetIP 169 254 3 165 将模块 IP 改为 169.254.3.165 7、 将模块断电 并重新激活电源 模块便会以新的 IP 地址开始工 作 主控计算机可以使用新的地址和模块沟通 设定 IP 的画面如图 11-4-4 所示 使用者可在 DOS 画面下达改 变的指令 图 11- 4- 4 利用原厂的 7188X 执行档变更 IP 在此特别说明一点 7188E2 的 IP 地址是一个 不过该模块上 的一个命令信道及二个资料信道均使用一个网络 Port 作资料交换 程序需使用模块的资料信道和 232 或 485 网络时 程序必须连带地 也和命令建立联机才行 因此 笔者的作法是一次就建立三个网络 信道(Socket) 以此模块来说 除了 IP 一样外 使用的 Port 号码则 是 10000 10001 10002 我们可以使用 范例 \ 第十一章 \ 状态控制-客户端 的项目为基础 新建一个简单的测试程序 以下说明建立的步骤 画面设计的步骤如下 1、 激活 Visual Basic 6.0 并开启一个新的项目 2、 于窗体上安排一个 Winsock 控件 Name 属性设成 Winsock1 其它属性均维持为默认值 再 Copy 二个 使其成为对象数组 因此共有三个 Winsock 控件 3、 安排六个文字框控件 一个作为 IP 地址的指定区 一个则作 为传送埠指定的输入区 一个作为传回讯息的显示区 另三个 第 483 页,共 606 页作为传送讯息输入区(分别是命令信道 第一个数据信道及第二 个数据信道) 4、 安排四个 Label 控件 其 Caption 属性分别输入 传送地址 传送端口 讯息区 及 输出讯息区 标示上述的四 个文字框 5、 安排五个按钮控件 其 Caption 属性分别给定 联机 结 束系统 输出(0) 输出(1) 及 输出(2) 作为执 行相对功能之用 6、 依以上的步骤所作出的画面如图 11-4-5 图 11- 4- 5 建立和 7188E2 的沟通程序 动作流程解析 在确定 IP 地址后 按下 联机 按钮后 随即 和 7188E 的三个网络 Port 连结 连结成功后 可在传送讯息区中输 入要传送的指令 并按下 输出(0) ~ 输出(2) 后输出指令 程序代码部份 1、 双击 联机 按钮 在 Click 事件中加入以下的程序代码 For i = 0 To 2 '检查状态 若不是关闭 则执行关闭 If Winsock1(i).State <> 0 Then Winsock1(i).Close '设定远程的地址及通讯端口号码 Winsock1(i).RemoteHost = txtIP.Text Winsock1(i).RemotePort = txtPort.Text + i Winsock1(i).Connect '执行联机 TimeDelay 100 '下最循环等待联机成功 Do DoEvents Loop Until Winsock1(i).State = sckConnected cmdSend(i).Enabled = True Next i Me.Caption = OCaption & "已联机" 第 484 页,共 606 页 此部份的程序一开始就检查是否处于联机状态 若是 则因必须 聆听 故必须先中断联机 将要求联机所需要的 IP 地址和通讯端 口号码指定给属性后 就执行 Connect 方法要求联机 直到联机 被建立为止 这里比较不同的是一次建立三个 Socket 联机 2、 双击 Winsock 控件 在 其程序代码编辑窗口中的 DataArrival 事件 中输入以下的程序代码 Dim strData As String Winsock1(Index).GetData strData '接收资料 ResponseData = strData txtReceive.Text = txtReceive.Text & ResponseData '显示资料 txtReceive.SelLength = Len(txtReceive.Text) 联机完成后 若它端传送资料过来 将资料接收后显示到文字框 中 3、 同样是 Winsock 控件 在其程序代码编辑窗口中的 Close 事件中 输入以下的程序代码 txtReecive.Text = txtReceive.Text & " 对方要求中断 " & vbCr Winsock1(Index).Close '中断联机 当另一端断线时(不管是什么原因) Winsock 控件的此事件就会 被引发 在此我们的程序就执行断线的指令 4、 双击任一个输出按钮 在其 Click 事件中输入以下的程序代码 If Winsock1(Index).State <> sckConnected Then Exit Sub Buf = OutCheckSum(txtSend(Index).Text) 'CheckSum 计算 Winsock1(Index).SendData Buf 按下此按钮后 首先检查是否处于联机状态 若非处于联机状态 则无法传送字符串 当然就跳出循环 而在联机状态下则将字符 串作 CheckSum 计算后传送出去(主要用于控制 7000 模块) 5、 双击 结束系统 的按钮 在其 Click 事件中输入以下的程序代 码 For i = 0 To 2 Winsock1(i).Close '关闭联机 Next i End '结束程序 在关闭联机后 结束本系统 将线路接好后 便可以和模块沟通 执行的情形如图 11-4-6 所 示 第 485 页,共 606 页 图 11- 4- 6 和 7188E2 沟通的情形 由图 11-4-6 笔者按下 联机 按钮后 分别在 输出(0) 及 输出(2) 中键入控制指令及分布式模块的组态指令 其结果均正 确地传回在讯息区中 讯息区中的组态指令回传字符串含有多出来 的二个字符是因模块及程序均采用 CheckSum 检查码 因此模块所 传回来的结果就会含有 CheckSum 的 二个字符 此小节的程序代码 您可以参考光盘中 范例 \ 第十一章 \ 7188E-客户端 档案夹中的项目 双击 Client.vbp 项目档档案夹 中的项目 双击 Client.vbp 项目档 即可开启测试的项目 本小节的完整程序代码列表如下 Dim OCaption$ '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 传送指令的按钮程序 ' 将指令作 CheckSum 后 以 SendData 指令送出 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdSend_Click(Index As Integer) Dim Buf$ If Winsock1(Index).State <> sckConnected Then Exit Sub Buf = OutCheckSum(txtSend(Index).Text) 'CheckSum 计算 Winsock1(Index).SendData Buf End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 关闭联机并结束程序 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Command2_Click() Dim i% For i = 0 To 2 Winsock1(i).Close '关闭联机 Next i End '结束程序 End Sub 第 486 页,共 606 页 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 设定连接参数 ' 执行连结的动作 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Command3_Click() Dim i% For i = 0 To 2 '检查状态 若不是关闭 则执行关闭 If Winsock1(i).State <> 0 Then Winsock1(i).Close '设定远程的地址及通讯端口号码 Winsock1(i).RemoteHost = txtIP.Text Winsock1(i).RemotePort = txtPort.Text + i Winsock1(i).Connect '执行联机 TimeDelay 100 '下最循环等待联机成功 Do DoEvents Loop Until Winsock1(i).State = sckConnected cmdSend(i).Enabled = True Next i Me.Caption = OCaption & "已联机" End Sub Private Sub Form_Load() OCaption = Me.Caption End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 当联机一端中止联机时 便会引发以下的事件 ' 我们将联机关闭 并作适当处理 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock1_Close(Index As Integer) txtReecive.Text = txtReceive.Text & " 对方要求中断 " & vbCr Winsock1(Index).Close '中断联机 End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 控件收到对方送来的资料时 会引发以下的事件 ' 我们将资料接收到后 显示在文字框中 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock1_DataArrival(Index As Integer, ByVal bytesTotal As Long) Dim strData As String Winsock1(Index).GetData strData '接收资料 ResponseData = strData txtReceive.Text = txtReceive.Text & ResponseData '显示资料 txtReceive.SelLength = Len(txtReceive.Text) End Sub 模块内的程序代码则如下(主要是 CheckSum 的程序) Public Declare Function GetTickCount Lib "kernel32" () As Long Public ResponseData As String 第 487 页,共 606 页Sub TimeDelay(TT As Long) Dim t As Long t = GetTickCount() Do DoEvents If GetTickCount - t < 0 Then t = GetTickCount Loop Until GetTickCount - t >= TT End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将要送出的字符串计算 CheckSum 的结果 'InBuf 是传入的字符串 ' 函式的传回值是作了 CheckSum 的结果字符串 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function OutCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Long BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i '字符串重组 并加上结尾字符 Cr Buf = IIf(Len(Hex(CheckSum)) = 1, "0" & Hex(CheckSum), Hex(CheckSum)) OutCheckSum = InBuf & Buf & vbCr End Function '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将模块传回的字符串检查 CheckSum 是否正确 'InBuf 是由模块传回的字符串 ' 函式的传回值为去除 CheckSum 及结尾字符( 正确时) ' 或空字符串( 不正确时) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function INCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Integer BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen - 3 '需扣掉二个 CheckSum 字符和结尾字符 Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i If CheckSum = Val("&H" & Mid(InBuf, BufLen - 3 + 1, 2)) Then INCheckSum = Mid(InBuf, 1, BufLen - 3) Else INCheckSum = "" End If End Function 第 488 页,共 606 页 11-4-4 利用 7188E 模块控制分布式模块 了解 7188E2 的相关原理后 本小节将接着将原来的监控系统 修改为透过此模块而监控 其实我们应该可以想象此结果会和 11- 3 节的结果一样 而这二个最大的不同就在于一个在现场备有一部计 算机直接和分布式模块作联机 但在 11-4 节里 使用一颗 7188E2 取代一部计算机的工作 不见得取代了计算机就表示必须采用的一 个方案 实际上还是多方考量 毕竟一个模块和一部计算机在功能 配备上还是有很大的不同的 进行此部份的实验时 笔者采用 7188E 上的 RS-485 信道(也就 是 Port2) 并不是用 232 信道 直接使用 RS- 485 信道的原因是可 以省掉一个 7520 模块 原来我们利用主控计算机上的 RS-232 透 过 7520 和分布式模块取得通讯 如果读者认为如此的方式较好 也可使用 RS- 232 的信道 此部份的画面设计照样延用第十章的设计 不过必须多一个可 以输入 7188E 的 IP 地址之处 因此多了一个文字框控件 各位读 者可以参考第十章的画面建立步骤 再加上一个文字框即可 图 11- 4-7 是设计的画面 另外 由上一小节的讨论可知 欲和 7188E 建立联机 最好是同时建立三个管道 因此在画面上也必须有三个 Winsock 控件 需较多改变的部份是在程序上 因此笔者会说明程 序上的改变之处 第 489 页,共 606 页 图 11-4-7 透过 7188E 监控的画面设计 动作流程解析 在确定 IP 地址后 按下 激活系统 的按钮后 随即和 7188E 的三个网络 Port 连结 连结成功后 将要求的输出指 令送至 7188E 上的 RS- 485 数据信道 并从该信道取得传回的数据 指令部份相同 但原来是直接送到串行通讯端口 此时则是透过 7188E 再送至 485 网络上 程序代码部份 1、 双击窗体 在 Form_Load 事件程序中加入以下的程序代码 Ocaption = Me.Caption '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotValue(0 To 2, 0 To MaxPlotNo) TempSensitivity = 30 '每 1 伏为 30 度 C 默认值 TimeDelay 1000 '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ Alarm1.wav" MMC1.Command = "Open" mciOK = True 执行初始化的动作及多媒体的指定工作 2、 双击 激活系统 按钮 在 Click 事件中加入以下的程序代码 第 490 页,共 606 页 If Timer1.Enabled Then StopFlag = True Exit Sub End If StopFlag = False '网络设定及连接 '检查状态 若不是关闭 则执行关闭 Me.Caption = Ocaption & " ---联机现场伺服模块中 " For i = 0 To 2 If Winsock2(i).State <> 0 Then Winsock2(i).Close '设定远程的地址及通讯端口号码 Winsock2(i).RemoteHost = txtIP.Text Winsock2(i).RemotePort = 10000 + i '命令及资料信道编号 Winsock2(i).Connect '执行联机 '下最循环等待联机成功 TT = GetTickCount Do DoEvents Loop Until Winsock2(i).State = sckConnected Or GetTickCount - TT >= 1000 '若未成功联机 则跳出循环 MIRL If Winsock2(i).State <> sckConnected Then Me.Caption = Ocaption MsgBox "联机现场伺服模块失败!", vbCritical + vbOKOnly, "连 接讯息" Exit Sub End If Next i Me.Caption = Ocaption & " *** 已联机上现场的伺服模块 ***" '激活 7012 的警戒 LoHiValue(0) = 3 * TempSensitivity LoHiValue(1) = 8 * TempSensitivity Buf = SendCmd2TCPIP(Winsock2(2), "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2TCPIP(Winsock2(2), "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2TCPIP(Winsock2(2), "@02EAM", vbCr, 1000) '先将 Relay 全部打开 Buf = SendCmd2TCPIP(Winsock2(2), "@01F", vbCr, 1000) For i = 0 To 3 Relay(i) = True Next i '变更 7080 型态为频率模式 使用%AANNTTCCFF 指令 Buf = SendCmd2TCPIP(Winsock2(2), "%0404510640", vbCr, 1000) Timer1.Enabled = True cmdStart.Caption = "停止系统" 在这里和第十章不同之处就于采用 WinSock 组件 而非 MSComm 组件 在此笔者将原来的 SendCmd2Comm 函式改成了 SendCmd2TCPIP 函式 并且保持一样的参数数目及格式 读者可 以比较一下二者的不同 除了开启和 7188E 的沟通联机外 此段程序也送出设定的字符串 到 7188E 当 7188E 收到控制字符串后 会再转送到 RS-485 网 第 491 页,共 606 页络上(也就是 7188E 上的 Port2 由于分布式模块会传回执行结果 因此 SendCmd2TCPIP 也被设定为拥有传回值(型态为字符串) 3、 双击 Winsock 控件 在其程序代码编辑窗口中的 DataArrival 事件 中输入以下的程序代码 Buf = "" Winsock2(Index).GetData TCPStr '接收资料 If Len(TCPStr) < 3 Then TimeDelay 30 Winsock2(Index).GetData Buf '接收资料 End If TCPStr = TCPStr & Buf fReceive = True '将旗标设成 False 以通知 SendCmd2TCPIP 函式 当分布式模块收到由 7188E 传来的指令后 后将执行的结果送到 7188E 再由 7188E 透过网络线传回而主控计算机中 因此我们 必须在此事件中收回资料 并将 fReceive 旗标设成 True 让 SendSCmd2TCPIP 可以得知资料已收集完毕 4、 同样是 Winsock 控件 在其程序代码编辑窗口中的 Close 事件中 输入以下的程序代码 Me.Caption = Ocaption StopFlag = True 当伺服端断线时(不管是什么原因) Winsock 控件的此事件就会 被引发 在此我们的程序就将停止的旗标设定 使程序可以停止 运作 5、 双击定时器控件 在其 Timer 事件中输入以下的程序代码 '7012 模块函式 Sub7012 '7080 模块函式 Sub7080 '7060 模块函式 Sub7060 '7021 模块函式 Sub7021 '以下是趋势绘图 Plot NowX '进入绘图子程序 If StopFlag Then For i = 0 To 2 Winsock2(i).Close '中断联机 Next i Timer1.Enabled = False Me.Caption = Ocaption cmdStart.Caption = "激活系统" End If 第 492 页,共 606 页同样是四个模块的子程序及绘图子程序 不过多了一个检查是否 停止监控的程序片断 四个模块的子程序均和第十章发展的相 同 笔者说过其不同点在于 SendCmd2TCPIP 的程序 以下就列 出此子程序 Function SendCmd2TCPIP(TCP As Winsock, CS As String, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" Buf = OutCheckSum(CS) 'CheckSum 计算 TCP.SendData Buf '指令送出 fReceive = False TT = GetTickCount '等待直到时间到 或是字符串传回 Buf = "" TCPStr = "" Do DoEvents Loop Until (InStr(1, TCPStr, RS) And Len(TCPStr) > 2) Or GetTickCount - TT >= DT 'Loop Until fReceive Or GetTickCount - TT >= DT If InStr(1, TCPStr, RS) > 0 Then '以下是传回值 CheckSum 的检查 SendCmd2TCPIP = INCheckSum(TCPStr) Else SendCmd2TCPIP = "" End If End Function 读者可以发现 和之前使用 MSComm 时似乎相去不远 的确 笔者只是将其替换为 WinSock 组件 而接收传回结果字符串的部 份交由 Winsock 的 DataArrival 事件处理 并以 fReceive 旗标通 知是否完成 本程序亦采用了 CheckSum 的检查机制 以确保资 料的正确性 CheckSum 检查的部份也不因采用 Winsock 方式而 有不同 6、 双击 结束 的按钮 在其 Click 事件中输入以下的程序代码 For i = 0 To 2 Winsock2(i).Close Next i End 在关闭联机后 结束本系统 首先确定已所有的实验接线连接妥当 将此程序项目执行起 来 设定远程的 IP 地址和通讯端口号码后 按下 激活系统 的 按钮就可以和 7188E 联机(顺利的话) 图 11-4-8 是程序执行的画面 及实验情形 第 493 页,共 606 页 图 11-4-8 利用 7188E2 监控的执行情形 以上的程序代码 您可以参考光盘中 范例 \ 第十一章 \ 整合测试-7188E 档案夹中的项目 双击 Server.vbp 项目档 即 可开启测试的项目 欲完成一个完整的测试 还需要一个接收端的 项目程序 也就是上一小节的项目 本小节的完整程序代码列表如下 Option Explicit Dim fRelay As Boolean 'Relay 是否动作的旗标 Dim fLoHi As Boolean 'Hi- Lo 是否动作的旗 Dim fTemp As Boolean '温度 Sensitivity 是否改变的旗标 Dim fVOut As Boolean '模拟输出是否动作的旗标 Dim TempSensitivity As Single '温度灵敏度值 Dim LoHiValue(0 To 1) As Integer '高低值警戒值 Dim Relay(0 To 3) As Boolean '四个 Relay 的开关状态 Dim VOut As Single '模拟输出值 Dim NowX As Long '现在的 X 轴位置 Dim MaxPlotNo As Long '最长的 X 轴范围 Dim PlotValue() As Long '读值数组 Dim fIsOver As Boolean '是否超过绘图点数的旗标 Dim P1Width&, P1Height& '绘图的图片框的范围记录 Dim mciOK As Boolean '是否播音完毕 Dim StopFlag As Boolean ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 关闭 WinSock 通讯信道 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() Dim i% For i = 0 To 2 Winsock2(i).Close Next i End 第 494 页,共 606 页End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 激活系统 按钮后激活此事件 ' 将定时器作转态 在激活与关闭间作切换 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Dim Buf$, TT As Long Dim i% If Timer1.Enabled Then StopFlag = True Exit Sub End If StopFlag = False '网络设定及连接 '检查状态 若不是关闭 则执行关闭 Me.Caption = Ocaption & " ---联机现场伺服模块中 " For i = 0 To 2 If Winsock2(i).State <> 0 Then Winsock2(i).Close '设定远程的地址及通讯端口号码 Winsock2(i).RemoteHost = txtIP.Text Winsock2(i).RemotePort = 10000 + i '命令及资料信道编号 Winsock2(i).Connect '执行联机 '下最循环等待联机成功 TT = GetTickCount Do DoEvents Loop Until Winsock2(i).State = sckConnected Or GetTickCount - TT >= 1000 '若未成功联机 则跳出循环 MIRL If Winsock2(i).State <> sckConnected Then Me.Caption = Ocaption MsgBox "联机现场伺服模块失败!", vbCritical + vbOKOnly, "连 接讯息" Exit Sub End If Next i Me.Caption = Ocaption & " *** 已联机上现场的伺服模块 ***" '激活 7012 的警戒 LoHiValue(0) = 3 * TempSensitivity LoHiValue(1) = 8 * TempSensitivity Buf = SendCmd2TCPIP(Winsock2(2), "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2TCPIP(Winsock2(2), "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2TCPIP(Winsock2(2), "@02EAM", vbCr, 1000) '先将 Relay 全部打开 Buf = SendCmd2TCPIP(Winsock2(2), "@01F", vbCr, 1000) For i = 0 To 3 Relay(i) = True Next i '变更 7080 型态为频率模式 使用%AANNTTCCFF 指令 Buf = SendCmd2TCPIP(Winsock2(2), "%0404510640", vbCr, 1000) Timer1.Enabled = True cmdStart.Caption = "停止系统 " End Sub 第 495 页,共 606 页''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口在窗体加载时就开启 让其它的程序可以开始使用通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim Buf$, i% Ocaption = Me.Caption '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotValue(0 To 2, 0 To MaxPlotNo) TempSensitivity = 30 '每 1 伏为 30 度 C 默认值 TimeDelay 1000 '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ Alarm1.wav" MMC1.Command = "Open" mciOK = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'E2 的 Click 事件区 ' 在此可改变高温警戒的设定 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblHiAlarm_Click() Dim Buf$ Buf = InputBox("请输入高温警戒值", " 改变高温警戒", LoHiValue(1)) LoHiValue(1) = Val(Buf) fLoHi = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'E1 的 Click 事件区 ' 在此可改变低值警戒的设定 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblLoAlarm_Click() Dim Buf$ Buf = InputBox("请输入低温警戒值", " 改变低温警戒", LoHiValue(0)) LoHiValue(0) = Val(Buf) fLoHi = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'R1~R4 的 Click 事件区 ' 在此可控制 Relay 的动作 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblRL_Click(Index As Integer) Relay(Index) = Not Relay(Index) 第 496 页,共 606 页 fRelay = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'T 的 Click 事件区 ' 在此可温度灵敏度的数值 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblSensitivity_Click() TempSensitivity = Val(InputBox("请输入温度灵敏度", "灵敏度改变", TempSensitivity)) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '7021 的 Click 事件区 ' 在此可控制模拟输出的数值 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblVOut_Click() VOut = Val(InputBox(" 请输入电压值(0.0~10.0)", "7021 模拟输出", "0")) fVOut = True End Sub Private Sub MMC1_Done(NotifyCode As Integer) If NotifyCode = 1 Then MMC1.Command = "Stop" MMC1.Command = "Prev" mciOK = True End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件 ' 所有的通讯程序在此程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() Dim i% '7012 模块函式 Sub7012 '7080 模块函式 Sub7080 '7060 模块函式 Sub7060 '7021 模块函式 Sub7021 '以下是趋势绘图 Plot NowX '进入绘图子程序 If StopFlag Then For i = 0 To 2 Winsock2(i).Close '中断联机 Next i Timer1.Enabled = False Me.Caption = Ocaption cmdStart.Caption = "激活系统" End If End Sub 第 497 页,共 606 页 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7012 模块的子程序 模块站号 02 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7012() Dim Buf$ Dim i% '读取温度值 并显示 Buf = SendCmd2TCPIP(Winsock2(2), "#02", vbCr, 1000) If Buf <> "" Then '若正确传值回来 则 PlotValue(0, NowX) = CLng(Val(Mid(Buf, 2)) * TempSensitivity) lblTemp.Caption = PlotValue(0, NowX) Else '否则显示问号 lblTemp.Caption = "????" End If '检查警戒状态 Buf = SendCmd2TCPIP(Winsock2(2), "@02DI", vbCr, 1000) i = Val(Mid(Buf, 5, 2)) If (i And 1) = 1 Then '低值警戒是否激活 '改变颜色为红色 imgE1.Picture = imgRed.Picture lblLoAlarm.BackColor = RGB(100, 255, 0) Else '改变颜色为绿色 imgE1.Picture = imgGreen.Picture lblLoAlarm.BackColor = &H8000000F End If If (i And 2) = 2 Then '高值警戒是否激活 imgE2.Picture = imgRed.Picture lblHiAlarm.BackColor = RGB(100, 255, 0) '高值警戒时 亦激活声音播放 If mciOK Then mciOK = False MMC1.Notify = True '激活通知功能 MMC1.Command = "Play" '下达播放指令 End If Else imgE2.Picture = imgGreen.Picture lblHiAlarm.BackColor = &H8000000F End If If fLoHi Then '若改变高低警戒 则改变设定 '设定值先除以灵敏值 以得到实际的电压值 Buf = SendCmd2TCPIP(Winsock2(2), "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2TCPIP(Winsock2(2), "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2TCPIP(Winsock2(2), "@02EAM", vbCr, 1000) fLoHi = False '将旗标设成 False 以免再次被执行 End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7080 模块的子程序 模块站号 04 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 498 页,共 606 页Sub Sub7080() Dim Buf$, SpeedStr$ Dim Pos1%, Pos2% '二个通道分别要求传回数值 '要求 Ch0 传回转速值 Buf = SendCmd2TCPIP(Winsock2(2), "#040", vbCr, 1000) '若成功传回结果字符串 If Buf <> "" Then SpeedStr = Mid(Buf, 2, 8) '分离出计数值的字符串 SpeedStr = "&H" & SpeedStr & "&" '将其变成长整数型式的字符串 PlotValue(1, NowX) = Val(SpeedStr) '转换为实际的数值 lblF1.Caption = PlotValue(1, NowX) '显示在画面上 Else '否则就显示问号 If NowX > 1 Then PlotValue(1, NowX) = PlotValue(1, NowX - 1) lblF1.Caption = "????" End If '要求 Ch1 传回转速值 Buf = SendCmd2TCPIP(Winsock2(2), "#041", vbCr, 1000) If Buf <> "" Then SpeedStr = Mid(Buf, 2, 8) '分离出计数值的字符串 SpeedStr = "&H" & SpeedStr & "&" '将其变成长整数型式的字符串 PlotValue(2, NowX) = Val(SpeedStr) '转换为实际的数值 lblF2.Caption = PlotValue(2, NowX) '显示在画面上 Else '否则就显示问号 If NowX > 1 Then PlotValue(2, NowX) = PlotValue(2, NowX - 1) lblF2.Caption = "????" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7060 模块的子程序 模块站号 01 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7060() Dim Buf$, RBuf$ Dim i%, OutValue% '侦测数字状态 Buf = SendCmd2TCPIP(Winsock2(2), "@01", vbCr, 1000) '若正确传回结果字符串 则进行判断 If Buf <> "" Then '数字输出状态 RBuf = Mid(Buf, 3, 1) '取出状态字符 '改变状态灯号颜色 For i = 0 To 3 imgR(i).Picture = IIf(Val("&H" & RBuf) And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i '数字输入状态 RBuf = Mid(Buf, 5, 1) '取出状态字符 '改变状态灯号颜色 For i = 0 To 3 imgP(i).Picture = IIf(Val("&H" & RBuf) And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i 第 499 页,共 606 页 End If '判断是否作 Relay 输出 If fRelay Then OutValue = 0 '计算数字输出的数值 For i = 0 To 3 OutValue = OutValue + IIf(Relay(i), 2 ^ i, 0) Next i '送出数字输出的指令 Buf = SendCmd2TCPIP(Winsock2(2), "@01" & Hex(OutValue), vbCr, 1000) fRelay = False End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7021 模块的子程序 模块站号 03 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7021() Dim Buf$ '取得现在的模拟输出值 Buf = SendCmd2TCPIP(Winsock2(2), "$036", vbCr, 1000) '若取值正确 则显示在状态区 If Buf <> "" Then lblVoltage.Caption = Val(Mid(Buf, 4)) Else '否则就显示问号 lblVoltage.Caption = "????" End If If fVOut Then '判断是否作模拟输出 '送出模拟指令 Buf = SendCmd2TCPIP(Winsock2(2), "#03" & Format(VOut, "00.000"), vbCr, 1000) fVOut = False End If '若温度过高 关闭热源(R1) 激活加水马达(V) If PlotValue(0, NowX) >= LoHiValue(1) Then '以下并不直接作输出 而是激活旗标 Relay(0) = False fRelay = True VOut = PlotValue(0, NowX) / TempSensitivity fVOut = True End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 绘图的子程序 ' 三个通道的值取后即进行图形的绘制 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Plot(StartX&) Dim i&, j&, k& Dim ShowColor& Dim EndPos& PicHide.Cls '清除图形 '先绘温度的部份 第 500 页,共 606 页 PicHide.Scale (0, 10 * TempSensitivity)- (MaxPlotNo, 0) PicHide.ForeColor = RGB(0, 0, 0) '以下作绘格子线的绘图定设定 PicHide.DrawWidth = 1 PicHide.DrawStyle = vbDot '以下二个子程序是作格子线的绘制 For i = 1 To 9 PicHide.Line (i * MaxPlotNo / 10, 0)- (i * MaxPlotNo / 10, 10 * TempSensitivity) Next i For i = 1 To 4 PicHide.Line (0, i * 10 * TempSensitivity / 5)-(MaxPlotNo, i * 10 * TempSensitivity / 5) Next i '以下是绘趋势线的格式设定 PicHide.DrawWidth = 2 PicHide.DrawStyle = vbSolid '判断是否已超过设定的绘图笔数 If fIsOver Then k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终点 '当已超过后 就以最大点数作为绘图的终点( 此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) ShowColor = RGB(255, 0, 0) '先作温度值的绘图 PicHide.PSet (0, PlotValue(0, 0)), ShowColor For i = 1 To EndPos PicHide.Line - (i, PlotValue(0, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i '再作转速的绘图 '以下设定转速绘图范围 (Xmin,YMax)- (XMax,YMin) PicHide.Scale (0, 11000)- (MaxPlotNo, 1000) For j = 1 To 2 '二个通道 If fIsOver Then k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终 点 '当已超过后 就以最大点数作为绘图的终点(此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) '指定绘图颜色(j=0 时使用红色 否则使用蓝色) ShowColor = IIf(j = 1, RGB(255, 255, 0), RGB(0, 0, 200)) PicHide.PSet (0, PlotValue(j, 0)), ShowColor For i = 1 To EndPos 第 501 页,共 606 页 PicHide.Line - (i, PlotValue(j, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i Next j '快速图形拷贝函式 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.hDC, 0, 0, &HCC0020) NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 fIsOver = True End If End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 对方若中断联机会引发以下的事件 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock2_Close(Index As Integer) Dim i% Me.Caption = Ocaption StopFlag = True End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 接收对方送来的资料 ' 检查送来的资料 分离出控制的讯息 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Winsock2_DataArrival(Index As Integer, ByVal bytesTotal As Long) Buf = "" Winsock2(Index).GetData TCPStr '接收资料 If Len(TCPStr) < 3 Then TimeDelay 30 Winsock2(Index).GetData Buf '接收资料 End If TCPStr = TCPStr & Buf fReceive = True '将旗标设成 False 以通知 SendCmd2TCPIP 函式 End Sub 模块内的程序行表如下 其中是变量的宣告及 TimeDelay 函式 的原型 另有 CheckSum 检查的子程序 Option Explicit Declare Function GetTickCount Lib "kernel32" () As Long Public Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, _ ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, _ ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, _ ByVal ySrc As Long, ByVal dwRop As Long) As Long Public Ocaption As String Public fReceive As Boolean '是否已接收到资料的旗标 Public TCPStr As String '存放 TCPIP 字符串数据的变量 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 502 页,共 606 页' 时间延迟子程序 单位为毫秒(ms) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TimeDelay(t As Long) 'Time Delay Sub. Unit=mSec Dim TT& TT = GetTickCount() Do DoEvents Loop Until GetTickCount() - TT >= t End Sub ' 指令送出 并等待 RS 字符串传回 或是时间到达 'Comm 是通讯组件名称 在此使用 WinSock 组件 'CS 是命令字符串 不需含结尾字符 'RS 是欲等待的字符 'DT 是最常的等待时间 ' 正常时传回值是所得的完整字符串 不正常时传回值是空字符串 Function SendCmd2TCPIP(TCP As Winsock, CS As String, RS As String, DT As Long) As String Dim Buf$, TT As Long Buf = "" Buf = OutCheckSum(CS) 'CheckSum 计算 TCP.SendData Buf '指令送出 fReceive = False TT = GetTickCount '等待直到时间到 或是字符串传回 Buf = "" TCPStr = "" Do DoEvents Loop Until (InStr(1, TCPStr, RS) And Len(TCPStr) > 2) Or GetTickCount - TT >= DT 'Loop Until fReceive Or GetTickCount - TT >= DT If InStr(1, TCPStr, RS) > 0 Then '以下是传回值 CheckSum 的检查 SendCmd2TCPIP = INCheckSum(TCPStr) Else SendCmd2TCPIP = "" End If End Function '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将要送出的字符串计算 CheckSum 的结果 'InBuf 是传入的字符串 ' 函式的传回值是作了 CheckSum 的结果字符串 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function OutCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Long BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF 第 503 页,共 606 页 Next i '字符串重组 并加上结尾字符 Cr Buf = IIf(Len(Hex(CheckSum)) = 1, "0" & Hex(CheckSum), Hex(CheckSum)) OutCheckSum = InBuf & Buf & vbCr End Function '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 将模块传回的字符串检查 CheckSum 是否正确 'InBuf 是由模块传回的字符串 ' 函式的传回值为去除 CheckSum 及结尾字符( 正确时) ' 或空字符串( 不正确时) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function INCheckSum(InBuf As String) As String Dim BufLen As Integer, Buf As String Dim i As Integer Dim CheckSum As Integer On Error GoTo INErr BufLen = Len(InBuf) '取得传入字符串的长度 CheckSum = 0 '初始化 For i = 1 To BufLen - 3 '需扣掉二个 CheckSum 字符和结尾字符 Buf = Mid(InBuf, i, 1) '取出字符 '和 &HFF 作 AND 运算可以将结果限定在&HFF 以内 符合模块要求 CheckSum = (CheckSum + Asc(Buf)) And &HFF Next i If CheckSum = Val("&H" & Mid(InBuf, BufLen - 3 + 1, 2)) Then INCheckSum = Mid(InBuf, 1, BufLen - 3) Else INCheckSum = "" End If Exit Function INErr: INCheckSum = "" On Error GoTo 0 End Function 第 504 页,共 606 页11-5 无线通讯模块-SST-2400 实际的应用需求是千奇百怪的 有可能在某些场合中 有线的 传输是不适用的 工程师必须考虑使用无线的方法传输资料 并进 行控制 本节讨论的模块即是使用在无线传输场合中的 SST- 2400 11-5-1 模块介绍 SST-2400 模块外观如图 11-5-1 所示 其中有一个连接 RJ-45 网 络线的接头位置 另各有一组 RS- 232 及 RS- 485 的接线位置 图 11-5-1 SST-2400 模块外观 主要的接线位置在图 11-5-1 的左方 电源及 RS- 485 的接线部 份和其它的模块均相同 请读者参考各章对于模块上的接线说明即 可 另有一组 RS- 232 的接线位置 此组接线可让使用者接到 RS- 232 的相关设备上 模 块提供一般 232 应具备有 RxD(接收)及 TxD(传送) 脚位 此模块的主要工作是负责将资料在主控计算机和分布式模块 之间作传送 不管所传送的是什么 它只管将收到的资料藉由无线 通讯通讯的方式往外传播 接收端再将所收到的信息还原为原来的 样子 换句话说 如果想象原来使用线材将一条线作连接 现在则 是将此线材从中切断 而接上无线电一样 原来使用的任何串行通 讯指令将不需作任何的改变 此模块的内部电路方块图如图 11-5-2 所示 由图中可以看出模 块将 232 或是 485 的指令利用 4 无线电系统作为传播的信道 第 505 页,共 606 页 图 11-5-2 SST- 2400 内部电路示意图 既然使用无线电作传输 自然有频率的设定 SST-2400 所使用 的频率通道共 8 个 由 2426.112MHz~2458.368MHz 间隔为 4.608MHz 传输的双方必须设定同样的通道才能使通讯正常 而通 道选择是在模块内部以 Jumper 的方式为之 SST- 2400 模块的传输距离在使用厂商所附的天线时为 300 公 尺 若需增加传输的距离可改用不同的天线 最长的距离可达 5 公 里 11-5-2 通讯模式及设定 此模块的组态有二个地方是必须作选择的 一是全双工/半双工 的选择 一是同步/异步的选择 全双工(Full-Duplex)模式下 模块在资料的传送及接收是同时进 行 而且数据的传输只能在二个 SST-2400 模块间进行 我们称之 为 点对点操作(Peer- to-peer Operation) 亦即只能在二个固定的模块 间传输数据 半双工(Half-Duplex)模式时 模块进行的资料传送/接 收是分于不同的时段 模块在一个时间点上只会有传送或接收二个 动作间的一个 而不会同时进行 另一方面 半双工模块允许多个 SST- 2400 互传数据 而不只二个 同步传输(Synchronous)时 传输双方采用相同的通讯格式 其 格式为 1 个开始位 8 个资料位 无同位检查及一个停止位 传输 及接收均使用此一格式 而设定为异步传输(Asynchronous)时 格 式可由双方的控制计算机自行设定 不过异步传输采用取样的方式 第 506 页,共 606 页取得资料 而模块内部的取样频率有限 故其最高的串行传输速度 只能到达 14400bps 除此之外 异步传输只能使用在 RS- 232 信道 不能使用在 RS- 485 的信道 模块尚分成 Master 及 Slave 二种设定(由模块内部的 Jumper 作 设定) 当工作于全双工模式时 二个模块处于点对点的传输状态 其中一个模块必须设定为 Master 而另一个模块须设定为 Slave 当使用半双工模式时 不管使用的通讯模块有多少个 每一个模块 均需设成 Slave 模式 以上所讨论的模式设定均需打开模块 当模块打开后 模块内 部的 Jumper 设定分布图及设定说明如图 11- 5- 3 图 11- 5- 3 Jumper 位置及意义 传输的频率 全双工/半双工 同步/异步的设定可以参考图 11-5-3 的说明 就可以完成二个模块(或多个模块)的组态设定 依 需求设定后就可以进行无线的传输了 11-5-3 实验设计 上一小节已就设定部份作过说明 本小节说明如何应用到分布 式模块中 将原来和主控计算机相接的 RS-232 线路取代掉 而以 无线模块达到相同的功能 考虑使用的情形后 笔者决定以下的重 要项目 第 507 页,共 606 页1、 二个 SST- 2400 模块 2、 SST-2400 模块间的通讯使用全双工模式 3、 采用点对点的方式 4、 模块间的通讯频宽共有 8 个通道可以选择 我们使用预设的通 讯通道 5、 和主控计算机相连的 SST- 2400 设定为 RS-232 而和分布式模块 相连的 SST- 2400 则设定为 RS- 485(如此便可省略一个 7520 模 块 ) 6、 由于采用点对点的方式 而且主要是针对分布式模块 因此可 以使用同步传输 通讯格式则固定为”1 个启始位+8 个资料位+1 个终止位” 7、 二个模块必须一个设为 Mater 另一个设为 Slave 有了以上的想法后 以下列出设定模块组态的步骤(须先断电) 1、 打开其中一个模块 查看内部的 Jumper 设定 2、 Freuqency 及 Channel 设定使用默认值 3、 BaudRate 使用 9600BPS 4、 设定为 Master 模式 5、 设定为全双工的工作模式 6、 设定为同步的传输模式 7、 设定使用 RS- 232 的控制模式(此模块将连接主控计算机) 8、 设定完后 将外盖锁好回复原状 9、 打开另外一个模块 查看其内部的 Jumper 设定 10、 Freuqency 及 Channel 设定使用默认值 11、 BaudRate 使用 9600BPS 12、 设定为 Slave 模式 13、 设定为全双工的工作模式 14、 设定为同步的传输模式 15、 设定使用 RS- 485 的控制模式(此模块将连接 7060 等分布式模 块 ) 16、 设定完后 将外盖锁好回复原状 二个模块的设定基本上是一样的 但必须一个是 Master 而另一 个是 Slave 将二个的模块的设定程序完成后 将 Master 的 SST-2400 一组和主控计算机的 COM 端口作连接 而 Slave 的 SST- 2400 一组 则和分布式模块作连接 第 508 页,共 606 页所谓的速率(BaudRate)指的是 SST- 2400 模块本身与所接的 设备之间的传输速率 这二者必须设定一样的速率 此二个无线模 块上的速率是可以不同的 因为速率指的是设备与模块间而言 而 非无线模块间 无线模块间只管无线电频率是否相同 设定完成后 我们引用第十章的相同实验设备及程序架构 将 无线模块应用上去 架构图如图 11- 5- 4 所示 图 11-5-4 无线模块实验接线示意图 细部的接线方法则如图 11- 5-5 所示 图右是和分布式模块接线 时的作法 而图左是和主控计算机接线时的作法 图 11- 5- 5 脚位接线图 完成图 11-5- 4 的接线后 将模块电源接上 利用光盘片上 范 例 \ 第十章 \ 整合测试 档案夹中的项目 双击 Ex.vbp 项目 档 即可进行以上的测试 第 509 页,共 606 页在程序完全一样的情形下 我们使用了无线通讯的模块达到了 一样的监控工作 如此说明了无线模块类似将原来的线路转换一个 不一样的方法传送 而传送的结果还是一样的 测试的过程中 请 读者检视 SST- 2400 正面左上角的 LED 当讯息交换时 LED 会呈 现红色 而一般开机情形下则是呈现绿色 所以我们可以看到模块 的 LED 灯号一直处于红绿变换的状态 第 510 页,共 606 页11-6 可寻址 232 转换模块-7521 由于 RS- 485 网络在工业应用上相当受欢迎 也有很多的架构 是以 485 为基础而建立 但是 RS-232 也是众多设备支持的接口 如何让 232 接口的设备也可以连接到 485 网络上 而以同一部主控 计算机控制呢?模块编号 7521/7522/7523 就是一个解决方案 其中 7521 只提供 1 个 232 的信道 而 7522 及 7523 分别提供 2 个及 3 个 232 的信道 本小节主要说明 7521 7522 和 7523 同理可证 11-6-1 模块介绍 7521 模块外观如图 11-6-1 所示 其外观和 7520 相 当类似 图 11-6-1 7521 模块外观 接线的位置分二处 图 11-6-1 的右方是 RS-232 的连接区 而 左下角处的接脚编号(包括 485 的讯号接脚和电源接脚)和其它的分 布式模块相同 请读者参考其它模块的说明 另外 左边尚有 3 支 脚位可作数字输入之用(DI1~DI3) 亦有 3 个数字输出的通道可供 使用 其实 7521~7523 系列也是由 7188 系列衍生而来 自然也就 拥有 7188 系列的弹性 不过 如同使用 7188E2 一样 笔者只强调 利用 7521 模块内之出厂时所烧入的程序 而由 485 网络控制 232 设备之应用 并不讨论如何在 7521 上撰写其内部程序 我们可将 其视为与之前讨论过的 7060/7012/7021 一样的模块 利用内建的指 令字符串而控制此模块 达到我们的监控目的 第 511 页,共 606 页11-6-2 相关指令及测试 从第五章至第八章 我们分别讨论了各个具代表性 IO 的模块 也了解到使用字符串指令和模块取得沟通的方法 同样的 主控计 算机也用相同的方式和 7521 模块沟通 本小节将讨论部份指令 表 11- 6- 1 是部份较常用的指令及说明 表 11- 6- 1 7521 之指令及说明 指令字符串格式 正确时的响应格式 简述 $AAA[addr] !AA 读取/设定模块地址 $AABN[baudrate] !AA[baudrate] 读取/设定通讯端口鲍率 $AADN[databit] !AA[databit] 读取/设定资料位长度 $AAPN[paritybit] !AA[paritybit] 读取/设定同位检查 $AAON[stopbit] !AA[stopbit] 读取/设定停止位 $AAC[delimiter] !AA[delimiter] 读取/设定通讯端口前导 码 (delimiter)AA(bypass) 将字符串传给 232 端口 $AAKN !AA 读取/设定 485 的 CheckSum $AATN !AA 读取/设定通讯端口结尾 字符 $AAYN !AA 读取/数字输入状态 $AAZNV !AA 设定数字输出 $AA2 !AABDPK 读取 485 组态 指令的使用已在之前的章节说明过 接下来我们直接使用范例 下达指令 看看实际的反应是如何 所使用的是范例光盘片中的 范 例 \ 第十一章 \ 指令的送出及读取 档案夹中的项目 双击 ex1.vbp 项目档 即可开启此项目 由于 7521 出厂的地址默认值是 01 并且不激活 CheckSum 我 们首先部针对此部份作些修正 为了和整合测试的实验配合上 笔 者希望地址改为 05 并且激活 CheckSum 开启上述的项目后 此 项目只是简单地将命令送出 也可将回传值取回 并且其中不含有 CheckSum 的检查 正好适合模块第一次的需求 另外 速度的默 认值为 9600 资料位数为 8 停止位数为 1 不使用同位位检查 这些和我们经常使用的设定符合 因此程序中的通讯参数不需要再 作改变 图 11- 6- 2 是数次指令的执行情形 第 512 页,共 606 页 图 11- 6- 2 以无 CheckSum 的项目激活 7521 的 CheckSum 如图 11-6- 2 将 CheckSum 激活后 若一样使用此项目程序 我们就会得到如图中的第 4 个步骤结果 无法得到模块的响应 因 此我门关闭此项目后 激活范例光盘片中的 范例 \ 第十一章 \ 指令的送出及读取-检查码 档案夹中的项目 双击 ex1.vbp 项目 档 此项目中含有 CheckSum 的检查 使用的结果如图 11- 6-3 所示 图 11- 6- 3 以 CheckSum 的项目改变 7521 的地址 由 11-6- 3 的图中可以看出使用含有 CheckSum 的项目便可顺利 取得组态 并且改变模块地址 到此我们已完成了基本的设定工作 了 使用 7521 的目的是为了可以透过 485 网络将 232 设备的指令 传送出去 并且得到 232 设备的传回值 由表 11- 6- 1 可以使用 第 513 页,共 606 页By Pass 的前导码将 232 指令送出 而取得前导码的指令是”$AAD” 图 11- 6- 4 是执行的结果 图 11- 6- 4 取得 By Pass 的前导码字符 由图 11- 6-4 可以看出 7521 所使用的预设前导码是分号”:” 也就是 说 当我们希望 7521 能够将指定的字符串送出 参考表 11- 6- 1 及 图 11- 6-4 可知 必须在字符串前加上分号及模块地址(在此是 05) 而整个送出字符串从模块地址后的数据就会被一五一十地转送到 7521 模块中的 232 的通讯端口(模块上标明为 COM1) 当然 若该 埠所连接的 232 设备有传回值的话 也会被转送到 485 网络上 而 且被主控计算机收到 下一节即将介绍其应用 11-6-3 实验架构 发展至今 整合实验必然是比较能够反应实际的需求 因此笔 者也试着将 7521 连同第十章的整合测试一起作实验 希望从较复 杂的测试中得到模块的适用性 图 11-6-5 是实验的架构图 其中大部份是和第十章的整合测试 一样的 但是其中我们加上了一颗 7521 并且在 7521 的 COM1 连 接上一 232 的设备 第 514 页,共 606 页 图 11-6-5 使用 7521 让 232 设备连上 485 网络 图 11-6-5 中的 232 设备是一部称之为 PSIS 的 仿真器 由骅达 科技公司出品 其中的一部份功能是提供仪器的模块接口 使初学 串行通讯的同好可以有一个测试的环境 以了解程序架构及控制法 则是否正确无误 我们选择 PSIS 仿真器中十项仿真功能中的一项 — Data Server 此功能会在主控计算机下达”%%DATA1”时传送一 笔资料随机回来 若下达”%%DATA”则会送回一组资料回来 透过 此资料 我们可以去确认是否已成功地控制 7521 (拟模器资料请 详见参考文献 2) 依然以第十章的整合测试项目为基础 以下说明画面及程序的 改变 画面上需改变的部份是加上一个图片框 此图片框将绘制由 PSIS 所传回的一组资料(约 50 笔数据) 其它的部份维持不变 改 变后的画面如图11-6-6 所示 其中是将原来绘制线条的图片框缩小 而空出地方加上一个名称为 Graph1 的图片框控件 此控件的背景 使用淡黄色 第 515 页,共 606 页 图 11-6-6 将 7521 加入整合测试之画面设计 动作流程解析 除了和原来的监控相同的步骤外 系统也需要 初始化 PSIS 仿真器 并在指令传送的定时器程序中加上要求仿真 器传送资料的程序 程序代码部份 程序代码的部份也只作了一部份的改变 主要是加了传送命令到 7521 再由 7521 将指令转送到 PSIS 仿真器 并接收由 PSIS 仿真器 传送回来的数据 不同之处笔者将以粗体字标出 1、 双击窗体 在 Form_Load 事件中加入以下的程序代码 '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotValue(0 To 2, 0 To MaxPlotNo) TempSensitivity = 30 '每 1 伏为 30 度 C 默认值 MSComm1.PortOpen = True TimeDelay 1000 '激活 7012 的警戒 LoHiValue(0) = 3 * TempSensitivity LoHiValue(1) = 8 * TempSensitivity Buf = SendCmd2Comm(MSComm1, "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02EAM", vbCr, 1000) '先将 Relay 全部打开 Buf = SendCmd2Comm(MSComm1, "@01F", vbCr, 1000) For i = 0 To 3 Relay(i) = True Next i 第 516 页,共 606 页 '变更 7080 型态为频率模式 使用%AANNTTCCFF 指令 Buf = SendCmd2Comm(MSComm1, "%0404510640", vbCr, 1000) '设定 232 设备及 GRAPH1 Buf = SendCmd2Comm(MSComm1, ":05SET TYPE 3", vbCr, 1000) Graph1.Scale (0, 50)-( 50, 0) Graph1.ForeColor = RGB(0, 0, 255) '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ Alarm1.wav" MMC1.Command = "Open" mciOK = True 除了原来的程序外 此部份的程序也将设定 PSIS 仿真器的字符 串指令”SET TYPE 3” 利用 7521 转送到 PSIS 仿真器上 另外是 将新增的图片框设定绘图范围 如粗体字部份 2、 双击 Timer 控件 Timer 事件中输入以下的程序代码 '7012 模块函式 Sub7012 '7080 模块函式 Sub7080 '7060 模块函式 Sub7060 '7021 模块函式 Sub7021 '7521 模块函式 Sub7521 '以下是趋势绘图 Plot NowX '进入绘图子程序 这个部份就多了一个 Sub7521 函式 负责处理 7521 相关的资料 其函式内容如下 Sub Sub7521() Dim ReceiveStr$ Dim StrPos%, i% Dim x!(51), y!(51) '取得现在的模拟输出值 ReceiveStr = SendCmd2Comm(MSComm1, ":05%%DATA", vbCr, 3000) '若取值正确 则显示图形 Graph1.Cls '清除图片框 Do StrPos = InStr(1, ReceiveStr, ",") If StrPos = 0 Then Exit Do '将取出的数据放入数组中 x(i) = i y(i) = Val(Left(ReceiveStr, StrPos - 1)) '取出其它剩下的数据字符串 ReceiveStr = Right(ReceiveStr, Len(ReceiveStr) - InStr(1, ReceiveStr, ",")) 第 517 页,共 606 页 If i = 0 Then Graph1.PSet (x(i), y(i)) '指定原点 Else Graph1.Line - (x(i), y(i)) '绘线 End If i = i + 1 Loop End Sub 根据 PSIS 仿真器的说明 当要求传送一组数据时 须下 达 ”%%DATA”的字符串指令 因此程序中以”:05%%DATA”要求 7521 模块将 05 后的字符串指令送出 当 PSIS 仿真器收到指令后 会将结果字符串传回 传回的过字符串会以逗号(,)分隔每一笔数 据 因此程序中是以逗号的位置来区分一个数据点是否存在 若 存在 则将数据存入数组 Y 中 一直到逗号不存在表示数据点已 全部处理完毕 再将数据点绘至图片框 Graph1 中 程序其它的部份是和原来第十章 整合测试 完全相同 故不 再赘述 读者可参考第十章的说明即可 将设备如同图 11-6-5 般接 好后 即可开始进行测试 测试的结果如图 11-6-7 所示 图 11-6-7 测试的结果图形 图 11-6-7 的测试结果中 原来的绘图工作照样进行 所增加的 图片框是一直不断地将 7521 传回的 PSIS 仿真器资料绘上 因此可 以见到其图形一直变更 观察测试的过程序也发现 由于 PSIS 一 次所传送的数据较多(约 300 个字节) 因此花在接收其资料的时间 也较长 整个系统的执行周期被明显地拉长 当我们进行其它的 232 设备联机工作时 可能也要考虑到此种情形 看看系统效能是否被 拉低 是否处于可忍受范围 第 518 页,共 606 页以上的程序代码 您可以参考光盘中 范例 \ 第十一章 \ 整合测试-7521 档案夹中的项目 双击 e1.vbp 项目档 即可开 启测试的项目 本小节的完整程序代码列表如下 Option Explicit Dim fRelay As Boolean 'Relay 是否动作的旗标 Dim fLoHi As Boolean 'Hi- Lo 是否动作的旗 Dim fTemp As Boolean '温度 Sensitivity 是否改变的旗标 Dim fVOut As Boolean '模拟输出是否动作的旗标 Dim TempSensitivity As Single '温度灵敏度值 Dim LoHiValue(0 To 1) As Integer '高低值警戒值 Dim Relay(0 To 3) As Boolean '四个 Relay 的开关状态 Dim VOut As Single '模拟输出值 Dim NowX As Long '现在的 X 轴位置 Dim MaxPlotNo As Long '最长的 X 轴范围 Dim PlotValue() As Long '读值数组 Dim fIsOver As Boolean '是否超过绘图点数的旗标 Dim P1Width&, P1Height& '绘图的图片框的范围记录 Dim mciOK As Boolean '是否播音完毕 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 结束 按钮后激活此事件 ' 使用 End 指令将系统结束掉 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdEnd_Click() End End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 按下 激活系统 按钮后激活此事件 ' 将定时器作转态 在激活与关闭间作切换 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub cmdStart_Click() Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止系统" Else cmdStart.Caption = "激活系统" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 窗体的加载(Load)事件 ' 将通讯端口在窗体加载时就开启 让其它的程序可以开始使用通讯端口 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Form_Load() Dim Buf$, i% '记录图片框的宽度及高度 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 第 519 页,共 606 页 '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotValue(0 To 2, 0 To MaxPlotNo) TempSensitivity = 30 '每 1 伏为 30 度 C 默认值 MSComm1.PortOpen = True TimeDelay 1000 '激活 7012 的警戒 LoHiValue(0) = 3 * TempSensitivity LoHiValue(1) = 8 * TempSensitivity Buf = SendCmd2Comm(MSComm1, "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02EAM", vbCr, 1000) '先将 Relay 全部打开 Buf = SendCmd2Comm(MSComm1, "@01F", vbCr, 1000) For i = 0 To 3 Relay(i) = True Next i '变更 7080 型态为频率模式 使用%AANNTTCCFF 指令 Buf = SendCmd2Comm(MSComm1, "%0404510640", vbCr, 1000) '设定 232 设备及 GRAPH1 Buf = SendCmd2Comm(MSComm1, ":05SET TYPE 3", vbCr, 1000) Graph1.Scale (0, 50)- (50, 0) Graph1.ForeColor = RGB(0, 0, 255) '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ A larm1.wav" MMC1.Command = "Open" mciOK = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'E2 的 Click 事件区 ' 在此可改变高温警戒的设定 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblHiAlarm_Click() Dim Buf$ Buf = InputBox("请输入高温警戒值", " 改变高温警戒", LoHiValue(1)) LoHiValue(1) = Val(Buf) fLoHi = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'E1 的 Click 事件区 ' 在此可改变低值警戒的设定 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblLoAlarm_Click() Dim Buf$ Buf = InputBox("请输入低温警戒值", " 改变低温警戒", LoHiValue(0)) 第 520 页,共 606 页 LoHiValue(0) = Val(Buf) fLoHi = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'R1~R4 的 Click 事件区 ' 在此可控制 Relay 的动作 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblRL_Click(Index As Integer) Relay(Index) = Not Relay(Index) fRelay = True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'T 的 Click 事件区 ' 在此可温度灵敏度的数值 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblSensitivity_Click() TempSensitivity = Val(InputBox("请输入温度灵敏度", "灵敏度改变", TempSensitivity)) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '7021 的 Click 事件区 ' 在此可控制模拟输出的数值 ' 并使用旗标通知定时器 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub lblVOut_Click() VOut = Val(InputBox(" 请输入电压值(0.0~10.0)", "7021 模拟输出", "0")) fVOut = True End Sub Private Sub MMC1_Done(NotifyCode As Integer) If NotifyCode = 1 Then MMC1.Command = "Stop" MMC1.Command = "Prev" mciOK = True End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 定时器的 Timer 事件 ' 所有的通讯程序在此程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Private Sub Timer1_Timer() '7012 模块函式 Sub7012 '7080 模块函式 Sub7080 '7060 模块函式 Sub7060 '7021 模块函式 Sub7021 '7521 模块函式 第 521 页,共 606 页 Sub7521 '以下是趋势绘图 Plot NowX '进入绘图子程序 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7012 模块的子程序 模块站号 02 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7012() Dim Buf$ Dim i% '读取温度值 并显示 Buf = SendCmd2Comm(MSComm1, "#02", vbCr, 1000) If Buf <> "" Then '若正确传值回来 则 PlotValue(0, NowX) = CLng(Val(Mid(Buf, 2)) * TempSensitivity) lblTemp.Caption = PlotValue(0, NowX) Else '否则显示问号 lblTemp.Caption = "????" End If '检查警戒状态 Buf = SendCmd2Comm(MSComm1, "@02DI", vbCr, 1000) i = Val(Mid(Buf, 5, 2)) If (i And 1) = 1 Then '低值警戒是否激活 '改变颜色为红色 imgE1.Picture = imgRed.Picture lblLoAlarm.BackColor = RGB(100, 255, 0) Else '改变颜色为绿色 imgE1.Picture = imgGreen.Picture lblLoAlarm.BackColor = &H8000000F End If If (i And 2) = 2 Then '高值警戒是否激活 imgE2.Picture = imgRed.Picture lblHiAlarm.BackColor = RGB(100, 255, 0) '高值警戒时 亦激活声音播放 If mciOK Then mciOK = False MMC1.Notify = True '激活通知功能 MMC1.Command = "Play" '下达播放指令 End If Else imgE2.Picture = imgGreen.Picture lblHiAlarm.BackColor = &H8000000F End If If fLoHi Then '若改变高低警戒 则改变设定 '设定值先除以灵敏值 以得到实际的电压值 Buf = SendCmd2Comm(MSComm1, "@02HI+" & Format(LoHiValue(1) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02LO+" & Format(LoHiValue(0) / TempSensitivity, "00.000"), vbCr, 1000) Buf = SendCmd2Comm(MSComm1, "@02EAM", vbCr, 1000) fLoHi = False '将旗标设成 False 以免再次被执行 End If End Sub 第 522 页,共 606 页 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7080 模块的子程序 模块站号 04 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7080() Dim Buf$, SpeedStr$ Dim Pos1%, Pos2% '二个通道分别要求传回数值 '要求 Ch0 传回转速值 Buf = SendCmd2Comm(MSComm1, "#040", vbCr, 1000) '若成功传回结果字符串 If Buf <> "" Then SpeedStr = Mid(Buf, 2, 8) '分离出计数值的字符串 SpeedStr = "&H" & SpeedStr & "&" '将其变成长整数型式的字符串 PlotValue(1, NowX) = Val(SpeedStr) '转换为实际的数值 lblF1.Caption = PlotValue(1, NowX) '显示在画面上 Else '否则就显示问号 lblF1.Caption = "????" End If '要求 Ch1 传回转速值 Buf = SendCmd2Comm(MSComm1, "#041", vbCr, 1000) If Buf <> "" Then SpeedStr = Mid(Buf, 2, 8) '分离出计数值的字符串 SpeedStr = "&H" & SpeedStr & "&" '将其变成长整数型式的字符串 PlotValue(2, NowX) = Val(SpeedStr) '转换为实际的数值 lblF2.Caption = PlotValue(2, NowX) '显示在画面上 Else '否则就显示问号 lblF2.Caption = "????" End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7060 模块的子程序 模块站号 01 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7060() Dim Buf$, RBuf$ Dim i%, OutValue% '侦测数字状态 Buf = SendCmd2Comm(MSComm1, "@01", vbCr, 1000) '若正确传回结果字符串 则进行判断 If Buf <> "" Then '数字输出状态 RBuf = Mid(Buf, 3, 1) '取出状态字符 '改变状态灯号颜色 For i = 0 To 3 imgR(i).Picture = IIf(Val("&H" & RBuf) And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i '数字输入状态 RBuf = Mid(Buf, 5, 1) '取出状态字符 '改变状态灯号颜色 For i = 0 To 3 第 523 页,共 606 页 imgP(i).Picture = IIf(Val("&H" & RBuf) And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i End If '判断是否作 Relay 输出 If fRelay Then OutValue = 0 '计算数字输出的数值 For i = 0 To 3 OutValue = OutValue + IIf(Relay(i), 2 ^ i, 0) Next i '送出数字输出的指令 Buf = SendCmd2Comm(MSComm1, "@01" & Hex(OutValue), vbCr, 1000) fRelay = False End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 模拟输入 7021 模块的子程序 模块站号 03 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Sub7021() Dim Buf$ '取得现在的模拟输出值 Buf = SendCmd2Comm(MSComm1, "$036", vbCr, 1000) '若取值正确 则显示在状态区 If Buf <> "" Then lblVoltage.Caption = Val(Mid(Buf, 4)) Else '否则就显示问号 lblVoltage.Caption = "????" End If If fVOut Then '判断是否作模拟输出 '送出模拟指令 Buf = SendCmd2Comm(MSComm1, "#03" & Format(VOut, "00.000"), vbCr, 1000) fVOut = False End If '若温度过高 关闭热源(R1) 激活加水马达(V) If PlotValue(0, NowX) >= LoHiValue(1) Then '以下并不直接作输出 而是激活旗标 Relay(0) = False fRelay = True VOut = PlotValue(0, NowX) / TempSensitivity fVOut = True Else '若温度回复高值以下 则关闭加水马达 激活旗标 VOut = 0 fVOut = True End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 处理 7521 模块的子程序 模块站号 05 ' 有关该模块的通讯工作在此子程序中完成 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 第 524 页,共 606 页Sub Sub7521() Dim ReceiveStr$ Dim StrPos%, i% Dim x!(51), y!(51) '取得现在的模拟输出值 ReceiveStr = SendCmd2Comm(MSComm1, ":05%%DATA", vbCr, 3000) '若取值正确 则显示图形 Graph1.Cls '清除图片框 Do StrPos = InStr(1, ReceiveStr, ",") If StrPos = 0 Then Exit Do '将取出的数据放入数组中 x(i) = i y(i) = Val(Left(ReceiveStr, StrPos - 1)) '取出其它剩下的数据字符串 ReceiveStr = Right(ReceiveStr, Len(ReceiveStr) - InStr(1, ReceiveStr, ",")) If i = 0 Then Graph1.PSet (x(i), y(i)) '指定原点 Else Graph1.Line - (x(i), y(i)) '绘线 End If i = i + 1 Loop End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 绘图的子程序 ' 三个通道的值取后即进行图形的绘制 '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Plot(StartX&) Dim i&, j&, k& Dim ShowColor& Dim EndPos& PicHide.Cls '清除图形 '先绘温度的部份 PicHide.Scale (0, 10 * TempSensitivity)-( MaxPlotNo, 0) PicHide.ForeColor = RGB(0, 0, 0) '以下作绘格子线的绘图定设定 PicHide.DrawWidth = 1 PicHide.DrawStyle = vbDot '以下二个子程序是作格子线的绘制 For i = 1 To 9 PicHide.Line (i * MaxPlotNo / 10, 0)- (i * MaxPlotNo / 10, 10 * TempSensitivity) Next i For i = 1 To 4 PicHide.Line (0, i * 10 * TempSensitivity / 5)- (MaxPlotNo, i * 10 * TempSensitivity / 5) Next i '以下是绘趋势线的格式设定 PicHide.DrawWidth = 2 PicHide.DrawStyle = vbSolid '判断是否已超过设定的绘图笔数 If fIsOver Then 第 525 页,共 606 页 k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终点 '当已超过后 就以最大点数作为绘图的终点( 此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) ShowColor = RGB(255, 0, 0) '先作温度值的绘图 PicHide.PSet (0, PlotVa lue(0, 0)), ShowColor For i = 1 To EndPos PicHide.Line - (i, PlotValue(0, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i '再作转速的绘图 '以下设定转速绘图范围 (Xmin,YMax)- (XMax,YMin) PicHide.Scale (0, 15000)- (MaxPlotNo, 5000) For j = 1 To 2 '二个通道 If fIsOver Then k = StartX + 1 '第一个值 Else k = 0 '第一个值 End If If k > MaxPlotNo Then k = 0 '决定绘制的最终位置 当未达设定的最大点数时 以现有点数为终 点 '当已超过后 就以最大点数作为绘图的终点(此时的数据是以 Ring Buffer) '的方式存在资料数组中 EndPos = IIf(fIsOver, MaxPlotNo, StartX) '指定绘图颜色(j=0 时使用红色 否则使用蓝色) ShowColor = IIf(j = 1, RGB(255, 255, 0), RGB(0, 0, 200)) PicHide.PSet (0, PlotValue(j, 0)), ShowColor For i = 1 To EndPos PicHide.Line - (i, PlotValue(j, k)), ShowColor '绘线 k = k + 1 If k > MaxPlotNo Then k = 0 '点号超过时 折回第 1 点 Next i Next j '快速图形拷贝函式 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.hDC, 0, 0, &HCC0020) NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 fIsOver = True End If End Sub 模块内的程序与原来第十章的相同 请读者参考第十章的说 明 第 526 页,共 606 页本章习题 1、 试描述网络监控程序的流程 2、 设计一组程序 联机成功后 会在服务器端显示要求端的 IP 地址 3、 11-4 节是使用 RS-485 信道送出指令 请将测试设备接上 7520 而以 RS- 232 作为传送的信道 4、 改变 7521 中的 COM1 前导码(原来是分号) 并测试是否可用 5、 修正 11-6-3 小节的程序 原来以”%%DATA”作数据传输 现 改为使用”%%DATA1” 一次只传输一笔资料点 并将此资料 绘制 Graph1 图片框中 第 527 页,共 606 页第十二章 分布式模块的 OCX 与 DLL 在 Visual Basic 的环境下控制分布式模块 使用 MSComm 控件 是相当容易且直接的用法 但 除了此控件外 厂商也开发了不一样 的工具给窗口环境下的开发程序使用 在 Visual Basic 中可以使用 的是 OCX 和 DLL 本章将进行这二个部份的讨论 12-1 分布式模块控件-NAP7000X 使用 Visual Basic 上的 MSComm 控件已经为我们建立了分布式 模块的控制 在使用上是以 MSComm 控件为基础 配合上各分布 式模块的操作指令字符串 利用计算机上的 RS- 232 串 行通讯端口 送出要求字符串 并同样由该串行通讯端口接收由模块所传回的执 行结果 NAP7000X 是由厂商特别设计用来操作 7000 分布式模块的控 件 一样需要使用一个串行通讯端口 使用的时候和 MSComm 控 件很类似 但是不需要特别注意模块的指令字符串格式 节省了部 份的发展时间 既然可以替代 MSComm 控件 笔者又何必讲了那 么多的 MSComm 控件 直接使用 NAP7000X 不就好了吗?其实 NAP7000X 的建立过程也和我们之前使用过的步骤一样 不过它经 过再一层的包装 让使用者更加容易控制模块 基础是一样的 就 笔的观点看来 基础打得好 未来才能适应其它的挑战 另外 不 见得读者一定是使用力激科技的 7000 系统分布式模块 对不对?如 果读者使用其它厂商发展的分布式模块 若无相对应的控件可用 那该如何?因此笔者才会以 Visual Basic 上的固有组件为基础说明 如何控制分布式模块 只要读者了解步骤和原理 换了不同的商牌 的分布式模块 一定可以依样画葫芦地发展各式的系统 NAP7000X 为一控件 所以使用的过程和一般的控件类似 以 下会加以说明 第 528 页,共 606 页12-1-1 安装及选用 OCX 安装此控件必须有原厂的光盘片 本书附有力激科技授权之光 盘片 请读者取出原厂光盘片 放入光驱中 点选 Napdos \ Nap7000x \ Setup 目录 如图 12- 1-1 执行其中的 Setup.exe 执行档 进行 OCX 之安装 图 12-1-1 安装 Nap7000X 安装过程所出现的询问画面 通常直接按 Next 按钮即可 最后 程序会要求重新开机 随即完成 OCX 的安装 如图 12- 1- 2 图 12-1-2 安装 Nap7000X 第 529 页,共 606 页重开机后 进入 Visual Basic 设计环境下 选择主菜单下的 项 目 \ 设定使用组件 叫出组件的选择画面 选择 NAP7000X 控件 即完成选择的步骤 如图 12- 1- 3 图 12-1-3 Nap7000X 选用步骤 接下来就可以在项目中使用此一控件 12-1-2 属性介绍 Nap7000X 控件可用各分布式模块 其属性不少 分别说明如下 共享属性 1、 ModuleAddress 用于设定模块在 485 网络上的编号 回顾 在第五章至第八章 每一个模块均有一个模块地址 此模 块地址即是网络上的编号 可用的编号 0~255 2、 COMPort 指定通讯所使用的串行通讯端口号码 可使用 的号码为 1~255 3、 BaudRate 设定通讯端口所使用的传输速度(单位 BPS) 可使用的速度有 1200 2400 4800 9600 19200 38400 57600 115200 第 530 页,共 606 页4、 ParityBit 指定是否使用同位位检查 此为串行通讯中的 一个参数 可使用的数值为 0~2 分别表示使用 None Odd Even 等三种 Parity Check 5、 StopBit 指定停止位的位数 可使用的数值为 0~2 分别 表示使用 1 1.5 2 等三种不同长度的停止位 6、 DataBit 指定资料位长度 可使用的数值为 5~8 7、 CheckSum 指定是否使用 CheckSum 于模块通讯上 可设 为 True 或 False 8、 PortOpen 指定通讯端口是否开启 设为 True 表示开启通 讯端口 False 表示关闭通讯端口 当准备开始控制 485 网 络上的模块时 必须设为 True 9、 ModuleID 指定所要控制的模块的编号 此编号以十六进 制数值表示 例如 7012 模块 就要将此属性设成&H7012 10、 ChannelNo 所要控制的通道号码 数值可由 0~15 11、 ErrorCode 当模块执行主控计算机所下的指令而发生错误 时 传回的错误号码 12、 ErrorString 上述的错误号码所伴随的错误说明字符串 13、 HostWatchdogInterval 设定看门狗的时间 超过此时间 还未向模块送 OK 指令 将导致模块拒绝接受控制指令 14、 HostWatchdogEnabled 是否激活看门狗 True 为激活 False 为关闭 指定为 True 前必须先将 HostWatchdogInterval 时间作设定 15、 HostWatchdogPCDead 读取模块上所认知的主控计算机是 否当机 若此值为 True 则该模块将不接受输出控制指令 除了共享的属性外 尚有其它属性的使用和所要控制的模块有 关 以下针对本书所使用到的 7012D和 7080D 模块列出相关的属性 7012D 模块相关属性 1、 EventCounter 自模块上取回事件计数值 2、 AlarmOutputState 读回模块上的警戒输出通道状态 数 值由 0~3 3、 AlarmType 警戒的型态 数值由 0~2 分别表示关闭警 戒 瞬间警戒及闩锁警戒三种之一 第 531 页,共 606 页4、 AlarmHigh 设定或读回高值警戒的设定值 5、 AlarmLow 设定或读回低值警戒的设定值 7080D 模块相关属性 1、 InputMode7080 设定 7080 模块的讯号输入型态 2、 Counter7080 读回 7080 模块上计数值 或是令其为 0 清 除计数值 若模块设成频率模式 也可读回频率值 3、 CounterEnabled7080 激活或停止计数 4、 CounterAlarmType7080 激活或关闭计数模式下的警戒模 式 0(请参阅第八章 8- 3- 2 小节) 5、 CounterAlarmType7080D 激活或关闭计数模式下的警戒 模式 1(请参阅第八章 8- 3- 2 小节) 6、 AlarmOutputState7080 读取警戒输出通道状态 7、 ModuleAlarmMode7080 设定激活计数模式下的模式 0 或 模式 1 警戒 数值可为 0 或 1 代表激活模式 0 或 1 12-1-3 方法(Method)介绍 所谓的方法也就是子程序 此子程序直接和模块沟通 并传回 结果 功能相同的模块会使用同一个方法 本书所说明的四个模块 功能不同 因此方法可以互通的情形不多 以下分别说明各模块所 使用的方法 共享方法 1、 ReadRangeCode 传回模块的型态码 通常是量测范围或 输出范围 需参考各模块的手册说明 2、 ReadDataFormatType 模块的资料格式 传回值由 0~3 分别表示工程单位 百分比 十六进制及欧姆值 3、 SendReceiveCmd 将指令传送到 485 网络 传回值即为模 块的传回值 此指令自动加上 vbCr 及 CheckSum 4、 SendCmd 将指令传送到 485 网络 传回值为错误码 此 指令自动加上 vbCr 及 CheckSum 第 532 页,共 606 页5、 ReceiveCmd 接收来自 485 网络的资料 此指令自动加上 vbCr 及 CheckSum 6、 SendStr 将指令传送到 485 网络 传回值为错误码 此指 令不加上任何的字符 而是直接传送到网络上 7、 ReceiveStr 接收来自 485 网络的固定长度资料 参数即 为指定的字符串长度 此指令不加上任何的字符 8、 SendHostIsOK 告诉 485 网 络上的模块主控计算机处于 OK 状态 模块可以接受主控计算机的控制指令 7060D 模块相关方法 1、 DigitalIn 自模块上取回数字输入数值 型态为长整数 例如 7060D 有四个通道的数字输入 若通道 0 和通道 1 为 ON 其余为 OFF 则传回数值为 3(2^0+2^1=3) 2、 DigitalInCh 特别读取某一个通道的数字输入状态 若传 回值为 True 表示该通道为 ON 否则为 OFF 使用前必须 使用 ChannelNo 属性指定通道号码 3、 DigitalOut 数字输出 参数即为输出数值 4、 DigitalInLatched 传回被闩锁住的数字输入状态 参数指 明是低电平闩锁或高电平闩锁 传回值和 DigitalIn 方法一 样 5、 DigitalOutCh 单独对某一个通道作数字输出 参数设为 True 为 ON 输出 若设为 False 则为 OFF 输出 使用前 必须使用 ChannelNo 属性指定通道号码 6、 DigitalOutReadBack 读回数字输出的状态 7012D 模块相关方法 1、 AnalogIn 自模块上取回模拟输入值 使用前必须使用 ChannelNo 属性指定通道号码 2、 AnalogInHex 亦是取回模拟输入值 不过传回时使用的 是十六进制格式处理(请参阅第六章 6- 2- 3 及表 6- 2- 1) 3、 AnalogInFsr 亦是取回模拟输入值 不过传回时使用的是 百分比格式处理 7021 模块相关方法 第 533 页,共 606 页1、 AnalogOut 由模块送出模拟值(电压或电流) 参数是所欲 传送的模拟值 而传回值是错误码 7080D 模块相关方法 1、 SetCFMode7080 指定 7080 模块使用的模式是 Counter 或 Frequency 第一参数指定使用的网关时间(0 为 0.1 秒 1 秒 ) 第二个参数指定模式(0 为 Counter 1 为 Frequency) 12-1-4 事件介绍 NAP7000X 控件只有一个事件-OnError 当控件发生错误时 会 引发此事件 使用者可以将相关的程序写入此事件中 用以记录原 因或通知使用者 错误码如表 12- 1-1 所示 由常数定义可以看出错 误的原因 表 12- 1- 1 错误定义 常数定义 错误码 NAP7000X_NoError 0 NAP7000X_FunctionError 1 NAP7000X_PortError 2 NAP7000X_BaudRateError 3 NAP7000X_DataError 4 NAP7000X_StopError 5 NAP7000X_ParityError 6 NAP7000X_CheckSumError 7 NAP7000X_ComPortNotOpen 8 NAP7000X_SendThreadCreateError 9 NAP7000X_SendCmdError 10 NAP7000X_ReadComStatusError 11 NAP7000X_ResultStrCheckError 12 NAP7000X_CmdError 13 NAP7000X_TimeOut 15 NAP7000X_ModuleIdError 17 NAP7000X_AdChannelError 18 NAP7000X_UnderInputRange 19 NAP7000X_ExceedInputRange 20 NAP7000X_InvalidateCounterNo 21 NAP7000X_InvalidateCounterValue 22 NAP7000X_InvalidateGateMode 23 NAP7000X_AddressError 100 NAP7000X_COMPortOpenError 101 NAP7000X_ReadBackCurCmdError 102 NAP7000X_AlarmModeError 103 NAP7000X_InputSignalModeError 104 NAP7000X_GateTimeError 105 NAP7000X_CounterModeError 106 第 534 页,共 606 页 第 535 页,共 606 页12-2 NAP7000X 的应用 上一节对于 NAP7000X 控件作过说明后 本节即将以控件作各 模块的控制 笔者分别由各章取出一个例子说明如何将原来使用 MSComm 控件的范例改为使用 NAP7000 控件 12-2-1 使用流程 虽然使用 NAP7000X 控件可以简化监控程序 不过还是需要了 解串行通讯基本原理 模块的动作程序和原理 才能充份地运用控 件 使用此控件的步骤如图 12- 2- 1 開始 傳輸參數設 定 設定 CheckSum 設定通訊埠 號碼 設定模組編 號 設定模組位 址 開啟通訊埠 操控模組 图 12- 2- 1 OCX 使用流程 从引入控件 到开始对模块进行控制时 应该遵照图 12-2-1 的 流程 如同串行通讯组件(MSComm) 传送资料开始前必须先开启 该通讯端口 其流程转换为控件的属性时应如下 1、 传输参数设定 需设定的属性有 BaudRate ParityBit DataBit StopBit 类似 MSComm 控件的 Settings 属性 2、 设定 CheckSum 设定 CheckSum 属性 决定模块是否使 用 CheckSum 机制 从第九章开始 本书已采用 CheckSum 机制 故此属性均设为 True 第 536 页,共 606 页3、 设定通讯端口号码 设定属性 COMPort 以决定利用主控 计算机上的那一个通讯端口传送资料 的一般的计算机只 有二个通讯端口 故此值不是 1 就是 2 4、 开启通讯端口 设定属性 PortOpen 为 True 即可开启通讯 端口 此动作必须在其它的传送/接收动作之前 否则将发 生错迹 5、 设定模块地址 设定属性 ModuleAddress 在 RS- 485 网络 上此值可由 0~255 每一个模块在一般情形下只会拥有唯 一的一个模块地址 6、 设定模块编号 设定属性 ModuleID 每一个模块都有一个 模块编号 例如 7012 7060 7080 此即为模块编号 完成上述各项的设定后 就可以再利用其它的属性或方法和模 块作沟通 第 5 项和第 6 项请注意 在相同通讯参数的情形下 ModuleID 可以一样 但 ModuleAddress 一定不一样 例如同一个 485 网络上有 5 个相同的 7060 模块 这 5 个 7060 模块的地址就不 一样 但其模块编号却是一样的 因为它们都是 7060 以下各节将说明如何替代项目中的 MSComm 控件 而以 NAP7000X 控件达到相同功能 12-2-2 7060D 的控制--使用 OCX 本小节项目程序取自第五章 数字输入及数字输出-修正 项目 画面设计完全保留原样 仅程序部份需作改变 画面的设计请读者 参阅第五章 5-4-1 小节的说明 画面除了将 MSComm 换成NAP7000X 外 其余相同 画面如图 12- 2- 2 第 537 页,共 606 页 图 12-2-2 7060 数字输出入控制画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex < 255 Then NAP7000X1.COMPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr NAP7000X1.BaudRate = 9600 NAP7000X1.ParityBit = 0 NAP7000X1.DataBit = 8 NAP7000X1.StopBit = 1 NAP7000X1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 原来设定通讯参数的部份 MSComm 控件使用的是 Settings 属性 在 Nap7000X 控件中则是分成了数个属性指定 2、 双击 开始侦测 其 Click 事件中的程序代码输入如下 '指定模块站号 NAP7000X1.ModuleAddress = cmbNO.ListIndex + 1 NAP7000X1.ModuleID = &H7060 NAP7000X1.CheckSum = True 第 538 页,共 606 页 If cmdStart.Caption = "开始侦测" Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" End If Do GetDIOFrom7060D '取数据的子程序 DoEvents Loop Until cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" ModuleAddress 用于指定模块的地址 而 ModuleID 则是用于指 定模块本身的编号 例如 7012 7060 7021 7080 等等 一 般说来 这二个属性在进行对模块的控制前需指定 也有部份控 制的函式(或属性)不需要指定 ModuleID 其中的 GetDIFrom7060D 函式更换的内容则如下 '取回数字输出的状态 Buf1 = CStr(NAP7000X1.DigitalOutReadBack) CheckDOStatus Buf1 '送入 DO 状态侦测子程序 '取回数字输入的状态 Buf2 = CStr(NAP7000X1.DigitalIn) CheckDIStatus Buf2 '送入状态侦测子程序 SendRelayOut Buf2 '将侦测的结果转控制至继电器输出 其中的子程序更换的有 SendRelayOut 列出如下 Sub SendRelayOut(RLOut As String) Dim Buf$, i% '数字输出指令 NAP7000X1.DigitalOut (CLng(RLOut)) End Sub 3、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中更换 的程序代码列如下 If cmbCOM.ListIndex + 1 = NAP7000X1.COMPort Then Exit Sub TimeDelay 100 NAP7000X1.PortOpen = False '关闭通讯端 口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 4、 另外加上 Nap7000X 控制的错误事件函式如下 Private Sub NAP7000X1_OnError(ByVal lErrorCode As Long) Debug.Print "Error_>"; lErrorCode Debug.Print ":"; NAP7000X1.ErrorString End Sub 第 539 页,共 606 页 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 程序 的侦测结果与第五章的图 5-4-3 一样 当我们改变数字输入状态时 也会连带地使得数字输出得到对应的通道输出 以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ OCX \ 数字输入及数字输出-修正 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的测试 12-2-3 7012D 的控制--使用 OCX 本小节项目程序取自第六章 6-3-4 小节 模拟值与警戒输出 项目 画面设计完全保留原样 仅程序部份需作改变 画面的设计 请读者参阅第六章 6-3-4 小节的说明 画面除了将 MSComm 换成 NAP7000X 外 其余相同 画面如图 12- 2- 3 图 12-2-3 7012 模拟输入控制画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 双击指定通讯端口的 Combo 控件 在其 Click 事件中输入以下程 序代码 If cmbCOM.ListIndex + 1 = NAP7000X1.COMPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If NAP7000X1.PortOpen Then NAP7000X1.PortOpen = False '关闭通讯端 口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" 第 540 页,共 606 页 cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 通讯端口号码的指定使用了 Nap7000X 的 COMPort 属性 2、 双击 开启通讯端口 按钮 其 Click 事件中的程序代码输入如 下 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex < 255 Then NAP7000X1.COMPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr NAP7000X1.BaudRate = 9600 NAP7000X1.ParityBit = 0 NAP7000X1.DataBit = 8 NAP7000X1.StopBit = 1 NAP7000X1.CheckSum = True '激活 CheckSum 机制 NAP7000X1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 3、 双击 开始侦测 的按钮 在其 Click 事件中更换的程序代码列 如下 NAP7000X1.ModuleAddress = cmbNO.ListIndex + 1 NAP7000X1.ModuleID = &H7012 If chkAlarm.Value = 1 Then '激活警戒 NAP7000X1.AlarmHigh = Val(txtHighAlarm.Text) NAP7000X1.AlarmLow = Val(txtLowAlarm.Text) NAP7000X1.AlarmType = 1 '采用 Momentary Alarm Else NAP7000X1.AlarmType = 0 '将 Alarm 取消 End If Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If 4、 定时器控件中的程序则如下 NAP7000X1.ChannelNo = 0 '设定通道 VolValue = NAP7000X1.AnalogIn '取得模拟输入值 第 541 页,共 606 页 lblMsg.Caption = "联机" & Buf & "中 " lblValue.Caption = Format(VolValue, "0.000") & "V" '显示在画面上 PlotValue(NowX) = VolValue '将值记录到数组中 If Not fIsOver Then If NowX = 0 Then PicShow.Cls '清除图形 PicShow.DrawWidth = 1 '使用画笔为一个像素宽度画格子线 PicShow.DrawStyle = vbDot '绘 X 轴的格子线, 均分 10 等分 For i = 0 To 9 x = i * MaxPlotNo / 10 PicShow.Line (x, 0)- (x, 10) Next i '绘 Y 轴的格子线, 均分 5 等分 For i = 0 To 4 y = i * 10 / 5 PicShow.Line (0, y)- (MaxPlotNo, y) Next i PicShow.DrawWidth = 2 '绘线宽度改为 2 PicShow.DrawStyle = vbSolid '以实线绘图 PicShow.PSet (0, VolValue), RGB(255, 0, 0) '设定起点 Else '以下判断现在的读值是否大于前一次的读值 若是 则以红色绘 线 '若否 则以蓝色绘线 If VolValue > PreValue + 0.01 Then PicShow.Line - (NowX, VolValue), RGB(255, 0, 0) '由上一次 的位置画至此点 Else PicShow.Line - (NowX, VolValue), RGB(0, 0, 255) '由上一次 的位置画至此点 End If End If Else lblStart.Caption = CStr(Val(lblStart.Caption) + 1) lblEnd.Caption = CStr(Val(lblEnd.Caption) + 1) Plot NowX + 1 '将图绘在另一个隐藏的图片框 '绘好的图形转移至显示的图片框 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.hDC, 0, 0, &HCC0020) End If PreValue = VolValue NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 fIsOver = True End If '检查警戒状态 AlarmValue = NAP7000X1.AlarmOutputState If NAP7000X1.AlarmType <> 0 Then '若激活警戒才作检查 If (AlarmValue And 1) = 1 Then '低值警戒是否激活 imgLO.Picture = imgGreen.Picture Else imgLO.Picture = imgDisable.Picture End If If (AlarmValue And 2) = 2 Then '高值警戒是否激活 imgHigh.Picture = imgRed.Picture 第 542 页,共 606 页 Else imgHigh.Picture = imgDisable.Picture End If End If 以上各部份是作了更换的部份 其它均维持原来的设计 并无变 更 读者可以将变更的前后作个比较 以上项目执行后 我们可以在画面中的通讯端口及站号选择必 要的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 选 择是否激活警戒 程序的侦测结果与第六章的 6-3-4 测试结果会是 一样的 以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ OCX \ 模拟值与警戒输出 档案夹中的项目 双击 Ex.vbp 项 目档 即可进行以上的测试 12-2-4 7021 的控制--使用 OCX 本小节项目程序取自第七章 7-2-4 小节 电压的输出 项目 画面设计完全保留原样 仅程序部份需作改变 画面的设计请读者 参阅第七章 7-2-4 小节的说明 画面除了将 MSComm 换成NAP7000X 外 其余相同 画面如图 12- 2- 4 图 12-2-4 7021 模拟输出控制画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 双击指定通讯端口的 Combo 控件 在其 Click 事件中输入以下程 序代码 If cmbCOM.ListIndex + 1 = NAP7000X1.COMPort Then Exit Sub 第 543 页,共 606 页 Timer1.Enabled = False '关闭定时器 TimeDelay 100 If NAP7000X1.PortOpen Then NAP7000X1.PortOpen = False '关闭通讯端 口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 通讯端口号码的指定使用了 Nap7000X 的 COMPort 属性 2、 双击 开启通讯端口 按钮 其 Click 事件中的程序代码输入如 下 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex < 255 Then NAP7000X1.COMPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr NAP7000X1.BaudRate = 9600 NAP7000X1.ParityBit = 0 NAP7000X1.DataBit = 8 NAP7000X1.StopBit = 1 NAP7000X1.CheckSum = True '激活 CheckSum 机制 NAP7000X1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable lblMsg.Caption = "可选择 Slide 控件的指针 执行输出的工作 " If cmdOpenCOM.Enabled Then lblMsg.Caption = "尚未开启通讯端口!" Exit Sub End If NAP7000X1.ModuleAddress = cmbNO.ListIndex + 1 NAP7000X1.ModuleID = &H7021 '先恢复到 0V 的位置 NAP7000X1.AnalogOut 0 Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 3、 双击 Slider 控件 在其 Scroll 事件中更换的程序代码列如下 If cmdOpenCOM.Enabled Then lblMsg.Caption = "尚未开启通讯端口!" Exit Sub End If lblValue.Caption = Slider1.Value / 10 & "V" '显示在画面上 '送出模拟指令 NAP7000X1.AnalogOut Val(lblValue.Caption) lblMsg.Caption = "联机" & Buf & "中 " If NAP7000X1.ErrorCode <> 0 Then lblMsg.Caption = "输出电压失败 " 第 544 页,共 606 页 Exit Sub End If '取回现在读值 RetValue = NAP7000X1.AnalogOutReadBack(1) '现在的输出电压值 lblReadBack.Caption = CStr(RetValue) & "V" '显示在画面上 4、 结束 按钮的程序则如下 NAP7000X1.AnalogOut 0 End 以上各部份是作了更换的部份 其它均维持原来的设计 并无变 更 读者可以将变更的前后作个比较 项目执行后 我们可以在画面中的通讯端口及站号选择必要的 设定 接着按下 开启通讯端口 的按钮 拉动 Slider 控件的拉杆 模拟输出值即会跟着改变 程序的侦测结果与第七章的 7-2-4 测试 结果会是一样的 以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ OCX \ 电压的输出 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的测试 12-2-5 7080D 的控制--使用 OCX 本小节项目程序取自第八章 8-3-3 小节 计数值与警戒输出-频 率 项目 画面设计完全保留原样 仅程序部份需作改变 画面的 设计请读者参阅第八章 8-3-3 小节的说明 画面除了将 MSComm 换 成 NAP7000X 外 其余相同 画面如图 12- 2- 5 图 12-2-5 7080 频率计数及警戒的画面设计 第 545 页,共 606 页程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 双击指定通讯端口的 Combo 控件 在其 Click 事件中输入以下程 序代码 If cmbCOM.ListIndex + 1 = NAP7000X1.COMPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If NAP7000X1.PortOpen Then NAP7000X1.PortOpen = False '关闭通讯端 口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 通讯端口号码的指定使用了 Nap7000X 的 COMPort 属性 2、 双击 开启通讯端口 按钮 其 Click 事件中的程序代码输入如 下 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex < 255 Then NAP7000X1.COMPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr NAP7000X1.BaudRate = 9600 NAP7000X1.ParityBit = 0 NAP7000X1.DataBit = 8 NAP7000X1.StopBit = 1 NAP7000X1.CheckSum = True '激活 CheckSum 机制 NAP7000X1.PortOpen = True '开启通讯端口 cmdOpenCOM.Enabled = False '将此按钮 Disable NAP7000X1.ModuleAddress = cmbNO.ListIndex + 1 NAP7000X1.ModuleID = &H7080 Buf = cmbNO.List(cmbNO.ListIndex) '若地址只有一位数 则在此位数的前端加上一个 0 If Len(Buf) = 1 Then Buf = "0" & Buf End If '变更型态为频率模式 使用%AANNTTCCFF 指令 RetBuf = NAP7000X1.SendReceiveCmd("%" & Buf & Buf & "51" & "06" & "00") If NAP7000X1.ErrorCode <> 0 Then MsgBox "组态指令执行错误! 请检查指令或模块 ", vbCritical + vbOKOnly, "系统讯息" End If cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 第 546 页,共 606 页 和之前的变更的部份是一样的 不过在此我们注意到在变更 7080 模块的组态所使用的指令并不是 Nap7000X 控件所提供的属性或 是方法 而是使用 SendReceiveCmd 函式 由于此控件并无提供 更改组态的相关属性 因此采用 SendReceiveCmd 函式 此函式 可以为我们传送任何的命令字符串 而且也会自动为我们的 CheckSum 作计算 或是计算传回字符串的 CheckSum 结果 当控 件未提供的属性 就可以使用此法来弥补其不足 3、 双击 开始侦测 的按钮 在其 Click 事件中更换的程序代码列 如下 NAP7000X1.SetCFMode7080 0, 1 Timer1.Enabled = Not Timer1.Enabled '定时器转态 If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If 4、 定时器控件中的程序则如下 NAP7000X1.ChannelNo = FreqCh FreqValue = NAP7000X1.Counter7080 '要求频率值回传 If NAP7000X1.ErrorCode <> 0 Then lblMsg.Caption = "取值失败 " Exit Sub End If lblValue(FreqCh).Caption = FreqValue & "Hz" '显示在画面上 '读取警戒输出现在的状态 AlarmOut = NAP7000X1.AlarmOutputState7080 If NAP7000X1.ErrorCode <> 0 Then '若发生错误 则显示讯息 lblMsg.Caption = "未传回正确状态讯息 " Else If (AlarmOut And 1) = 1 Then '第 0 个数字输出的检查 imgAlarm(0).Picture = imgRed.Picture Else imgAlarm(0).Picture = imgGreen.Picture End If If (AlarmOut And 2) = 2 Then '第 1 个数字输出的检查 imgAlarm(1).Picture = imgRed.Picture Else imgAlarm(1).Picture = imgGreen.Picture End If OutValue = Val(AlarmOut) End If '检查频率值 If FreqValue > Val(txtFreqAlarm(FreqCh).Text) Then '作警戒输出 OutValue = OutValue Or 2 ^ FreqCh '作 OR 运算 找出应 High 的通 道 Else 第 547 页,共 606 页 OutValue = OutValue And (Not 2 ^ FreqCh) '作 OR 运算 找出应 High 的通道 End If NAP7000X1.DigitalOut OutValue FreqCh = (FreqCh + 1) Mod 2 '下一个通道 其中的 ChannelNo 用来变更通道号码 其号码由 0~1 所使用的 取频率值是 Counter7080 属性 必 须通道码变更才能取得不同通 道的频率值 5、 结束 按钮的 Click 事件程序则如下 If NAP7000X1.PortOpen Then NAP7000X1.AlarmType = 0 '解除警戒 End If End 以上各部份是作了更换的部份 其它均维持原来的设计 并无变 更 读者可以将变更的前后作个比较 项目执行后 我们可以在画面中的通讯端口及站号选择必要的 设定 接着按下 开启通讯端口 及 开始侦测 的按钮 选择是 否激活警戒 程序的侦测结果与第八章的 8-3-3 测试结果会是一样 的 以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ OCX \ 计数值与警戒输出-频率 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的测试 12-2-6 Dual-WatchDog 的实现--使用 OCX 本小节项目程序取自第九章 9-2-5 小节 Watch-Dog 项目 画 面设计完全保留原样 仅程序部份需作改变 画面的设计请读者参 阅第九章 9-2-5 小节的说明 画面除了将 MSComm 换成 NAP7000X 外 其余相同 画面如图 12- 2- 6 第 548 页,共 606 页 图 12-2-6 Watch-Dog 的画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 在窗体的 Form_Load 事件中输入以下程序代码 NAP7000X1.BaudRate = 9600 NAP7000X1.ParityBit = 0 NAP7000X1.DataBit = 8 NAP7000X1.StopBit = 1 NAP7000X1.CheckSum = True '激活 CheckSum 机制 NAP7000X1.COMPort = 1 '使用第一个 COM NAP7000X1.PortOpen = True '开启通讯端口 2、 定时器控件中的程序则如下 If fWatchDog And (Not WatchDogEnabled) Then '送出 7060D 的 WatchDog 设定 NAP7000X1.ModuleAddress = 1 NAP7000X1.ModuleID = &H7060 NAP7000X1.HostWatchdogInterval = Val(txtTimeOut(0).Text) * 10 NAP7000X1.HostWatchdogEnabled = True If NAP7000X1.ErrorCode <> 0 Then MsgBox "7060D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7012D 的 WatchDog 设定 NAP7000X1.ModuleAddress = 2 NAP7000X1.ModuleID = &H7012 NAP7000X1.HostWatchdogInterval = Val(txtTimeOut(1).Text) * 10 NAP7000X1.HostWatchdogEnabled = True If NAP7000X1.ErrorCode <> 0 Then MsgBox "7012D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7021 的 WatchDog 设定 NAP7000X1.ModuleAddress = 3 NAP7000X1.ModuleID = &H7021 NAP7000X1.HostWatchdogInterval = Val(txtTimeOut(2).Text) * 10 NAP7000X1.HostWatchdogEnabled = True If NAP7000X1.ErrorCode <> 0 Then MsgBox "7021 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents 第 549 页,共 606 页 '送出 7080D 的 WatchDog 设定 NAP7000X1.ModuleAddress = 4 NAP7000X1.ModuleID = &H7080 NAP7000X1.HostWatchdogInterval = Val(txtTimeOut(3).Text) * 10 NAP7000X1.HostWatchdogEnabled = True If NAP7000X1.ErrorCode <> 0 Then MsgBox "7080D 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" fWatchDog = False '设为 False 表示已作过设定 WatchDogEnabled = True '设为 True 表示已激活 Watch- Dog DoEvents End If DoEvents '询问各模块的 Watch- Dog 状态 For i = 0 To 3 NAP7000X1.ModuleAddress = i + 1 ModuleStatus = NAP7000X1.HostWatchdogPCDead If NAP7000X1.ErrorCode = 0 Then '若正确传回字符串 则分离出状 态 If ModuleStatus Then '若被 Reset 则显示红色 imgStatus(i).Picture = imgON.Picture Else '否则显示绿色 imgStatus(i).Picture = imgOFF.Picture End If End If DoEvents Next '检查是否清除 Reset 状态 If fReset Then For i = 0 To 3 Buf = NAP7000X1.SendReceiveCmd("~0" & CStr(i + 1) & "1") Next i fReset = False WatchDogEnabled = False DoEvents End If '若勾选 HostOK 选项 则送出 OK 指令 If chkHostOK.Value = 1 Then NAP7000X1.SendHostIsOK lblMsg.Caption = "HostOK- >" & CStr(L) DoEvents L = L + 1 End If Timer 事件是主要的程序执行区 由于别会针对四个模块作动作 因此在每一程序片断中都指定了 ModuleAddress 和 ModuleID 二个 属性 询问是否已经超过时间的设定 则是使用 HostWatchdogPCDead 属性 当此属性为 True 时表示所设定的时 间已经超过 超过时间的模块 上面的灯号会一直闪烁 比较不 同的是 控件上并无提供清除模块闩锁状态的属性或是方法 因 此使用 SendReceiveCmd 的函式 将清除的指令送到每个模块去 第 550 页,共 606 页 以上各部份是作了更换的部份 其它均维持原来的设计 并无变 更 读者可以将变更的前后作个比较 以上项目执行后 我们可以在画面中的通讯端口及站号选择必 要的设定 接着按下 激活看门狗 选择是否送出 HostOK 指令 以清除模块上的侦测 程序的执行结果与第九章的 9-2-5 测试结果 会是一样的 以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ OCX \ Watch-Dog 档案夹中的项目 双击 Ex.vbp 项目档 即 可进行以上的测试 12-2-7 整合测试项目的程序变更--使用 OCX 本小节项目程序取自第十章 整合测试 项目 画面设计完全 保留原样 仅程序部份需作改变 画面的设计请读者参阅第十章的 说明 画面除了将 MSComm 换成 NAP7000X 外 其余相同 画面 如图 12- 2- 7 图 12-2-7 整合测试的画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 在窗体的 Form_Load 事件中输入以下程序代码 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotValue(0 To 2, 0 To MaxPlotNo) 第 551 页,共 606 页 TempSensitivity = 30 '每 1 伏为 30 度 C 默认值 NAP7000X1.BaudRate = 9600 NAP7000X1.ParityBit = 0 NAP7000X1.DataBit = 8 NAP7000X1.StopBit = 1 NAP7000X1.CheckSum = True '激活 CheckSum 机制 NAP7000X1.PortOpen = True '开启通讯端口 TimeDelay 1000 '激活 7012 的警戒 LoHiValue(0) = 3 * TempSensitivity LoHiValue(1) = 8 * TempSensitivity NAP7000X1.ModuleAddress = 2 NAP7000X1.ModuleID = &H7012 NAP7000X1.AlarmType = 1 NAP7000X1.AlarmHigh = LoHiValue(1) / TempSensitivity NAP7000X1.AlarmLow = LoHiValue(0) / TempSensitivity '先将 7060 的 Relay 全部打开 NAP7000X1.ModuleAddress = 1 NAP7000X1.ModuleID = &H7060 NAP7000X1.DigitalOut &HF For i = 0 To 3 Relay(i) = True Next i '变更 7080 型态为频率模式 使用%AANNTTCCFF 指令 Buf = NAP7000X1.SendReceiveCmd("%0404510640") '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ Alarm1.wav" MMC1.Command = "Open" mciOK = True 同样的 变更组态设定时 并无对应的属性或方法可以使用 所 以使用 SendReceiveCmd 函式达到变更组态的目的 2、 原来在定时器控件的 Timer 事件中有四个处理各模块的子程序 它们均有变更 列出如下 Sub Sub7012() Dim Buf$ Dim i% Dim RetValue As Single NAP7000X1.ModuleAddress = 2 NAP7000X1.ModuleID = &H7012 '读取温度值 并显示 NAP7000X1.ChannelNo = 0 RetValue = NAP7000X1.AnalogIn If NAP7000X1.ErrorCode = 0 Then '若正确传值回来 则 PlotValue(0, NowX) = RetValue * TempSensitivity lblTemp.Caption = PlotValue(0, NowX) Else '否则显示问号 lblTemp.Caption = "????" End If '检查警戒状态 RetValue = NAP7000X1.AlarmOutputState 第 552 页,共 606 页 If (RetValue And 1) = 1 Then '低值警戒是否激活 '改变颜色为红色 imgE1.Picture = imgRed.Picture lblLoAlarm.BackColor = RGB(100, 255, 0) Else '改变颜色为绿色 imgE1.Picture = imgGreen.Picture lblLoAlarm.BackColor = &H8000000F End If If (RetValue And 2) = 2 Then '高值警戒是否激活 imgE2.Picture = imgRed.Picture lblHiAlarm.BackColor = RGB(100, 255, 0) '高值警戒时 亦 激活声音播放 If mciOK Then mciOK = False MMC1.Notify = True '激活通知功能 MMC1.Command = "Play" '下达播放指令 End If Else imgE2.Picture = imgGreen.Picture lblHiAlarm.BackColor = &H8000000F End If If fLoHi Then '若改变高低警戒 则改变设定 '设定值先除以灵敏值 以得到实际的电压值 NAP7000X1.AlarmHigh = LoHiValue(1) / TempSensitivity NAP7000X1.AlarmLow = LoHiValue(0) / TempSensitivity fLoHi = False '将旗标设成 False 以免再次被执行 End If End Sub Sub Sub7080() Dim SpeedValue As Long NAP7000X1.ModuleAddress = 4 NAP7000X1.ModuleID = &H7080 '二个通道分别要求传回数值 '要求 Ch0 传回转速值 NAP7000X1.ChannelNo = 0 SpeedValue = NAP7000X1.Counter7080 '若成功传回结果字符串 If NAP7000X1.ErrorCode = 0 Then PlotValue(1, NowX) = SpeedValue '转换为实际的数值 lblF1.Caption = PlotValue(1, NowX) '显示在画面上 Else '否则就显示问号 lblF1.Caption = "????" End If '要求 Ch1 传回转速值 NAP7000X1.ChannelNo = 1 SpeedValue = NAP7000X1.Counter7080 If NAP7000X1.ErrorCode = 0 Then PlotValue(2, NowX) = SpeedValue '转换为实际的数值 lblF2.Caption = PlotValue(2, NowX) '显示在画面上 Else '否则就显示问号 lblF2.Caption = "????" End If 第 553 页,共 606 页End Sub Sub Sub7060() Dim Buf$, RBuf$ Dim i%, OutValue% Dim RetValue As Integer NAP7000X1.ModuleAddress = 1 NAP7000X1.ModuleID = &H7060 '侦测数字状态, 数字输出状态 RetValue = NAP7000X1.DigitalOutReadBack '若正确传回结果字符串 则进行判断 If NAP7000X1.ErrorCode = 0 Then '改变状态灯号颜色 For i = 0 To 3 imgR(i).Picture = IIf(RetValue And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i End If RetValue = NAP7000X1.DigitalIn If NAP7000X1.ErrorCode = 0 Then '数字输入状态 For i = 0 To 3 imgP(i).Picture = IIf(RetValue And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i End If '判断是否作 Relay 输出 If fRelay Then OutValue = 0 '计算数字输出的数值 For i = 0 To 3 OutValue = OutValue + IIf(Relay(i), 2 ^ i, 0) Next i '送出数字输出的指令 NAP7000X1.DigitalOut OutValue fRelay = False End If End Sub Sub Sub7021() Dim RetValue As Single NAP7000X1.ModuleAddress = 3 NAP7000X1.ModuleID = &H7021 '取得现在的模拟输出值 NAP7000X1.ChannelNo = 0 RetValue = NAP7000X1.AnalogOutReadBack(1) '若取值正确 则显示在状态区 If NAP7000X1.ErrorCode = 0 Then lblVoltage.Caption = RetValue Else '否则就显示问号 lblVoltage.Caption = "????" End If If fVOut Then '判断是否作模拟输出 '送出模拟指令 NAP7000X1.ChannelNo = 0 第 554 页,共 606 页 NAP7000X1.AnalogOut VOut fVOut = False End If '若温度过高 关闭热源(R1) 激活加水马达(V) If PlotValue(0, NowX) >= LoHiValue(1) Then '以下并不直接作输出 而是激活旗标 Relay(0) = False fRelay = True VOut = PlotValue(0, NowX) / TempSensitivity fVOut = True End If End Sub Timer 事件是主要的程序执行区 由于会针对四个模块作动作 因此在每一程序片断中都指定了 ModuleAddress 和 ModuleID 二个 属性 以上各部份是作了更换的部份 其它均维持原来的设计 并无变 更 读者可以将变更的前后作个比较 以上项目执行后 按下 激活系统 即可进行系统测试 程序 的执行结果与第十章的结果会是一样的 以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ OCX \ 整合测试 档案夹中的项目 双击 Ex.vbp 项目档 即 可进行以上的测试 第 555 页,共 606 页12-3 分布式模块动态链接库-NAP7000P 前面几节使用 Nap7000X 控件已经成功地控制了各模块 而且 也应用在之前所建立的几个范例程序上 我们可以发现除了名称不 太一样之外 Nap7000X 和 MSComm 控件好象在控制模块上都非常 地方便 另外一种也是控制分布式模块的接口为 Nap7000P 它所提供的 是一组的动态链接库(DLL,Dynamic Link Library) 可以类似使用函 式一般地控制各模块 使用 DLL 和 OCX 在计算机中有一些不一样 OCX 比较高阶 也容易让程序设计师依据属性 事件 方法的设计 程序对 OCX 作操作 而对 OCX 作操作的同时 就是在对模块作操 作 DLL 比较低阶 没有所谓的对象 属性 事件 方法等设计步 骤 直接就是靠程序设计师对程序接口和模块的了解直接下达控制 指令给模块 速度上比 OCX 要快到达传输的接口(在此就是通讯端 口 ) 使用 OCX 控制模块时 实际上也是会使用到 DLL 由此可以 了解到 OCX 的速度的确会比 DLL 稍微慢一点点 从另一个角度来 说 加速程序的执行的背后 程序设计人员就必须了解所使用的函 式的先后顺序及方法 才能发挥 DLL 的最大效能 12-3-1 安装 Nap7000P DLL 安装此控件必须有原厂的光盘片 本书附有力激科技授权之光 盘片 请读者取出原厂光盘片 放入光驱中 点选 Napdos \ Nap7000p \ Setup 目录 如图 12- 3-1 执行其中的 Setup.exe 执行档 进行 DLL 之安装 第 556 页,共 606 页 图 12-3-1 安装 Nap7000P 安装过程所出现的询问画面 通常直接按 Next 按钮即可 最后 程序会出现安装完成的讯息 随即完成 DLL 的安装 如图 12- 3-2 图 12-3-2 安装 Nap7000P 分布式模块所使用的 DLL 共有二个 分别是 Uart.DLL 及 I7000.DLL 并且被安装在 Windows 目录下的 System 目录 为了程 序设计的方便 厂商并且先写好引用的宣告 它们被放在安装后的 目录 例如依原先的目录不变更的话 它们会在 C:\DAQPro\Nap7000P\Driver\VB5 里面 另外在 Demo 目录下也会 存有一份 名称为 I7000.Bas 可以直接放在 Visual Basic 的模块中 供项目呼叫 如图 12- 3- 3 第 557 页,共 606 页 图 12- 3- 3 I7000 模块所在位置 12-3-2 函式介绍 Nap7000P 可用和 Nap7000X 控 件一样用于控制分布式模块 但 使用的方式是透过函式的呼叫而达到 这点和我们原先使用控件的 方法是不太一样的 以下将说明本章会使用到及一般常用的函式 共享函式 1、 Open_Com 用于依参数要求开启通讯端口 执行其它的指 令之前必须先将通讯端口开启 传回值若为 0 表示执行无 误 参数意义如下表 参数名称 型态及意义 范围 CPort Byte 通讯端口号码 1~255 dwBaudRate Long 鲍率 1200~115200 CData Byte 资料长度 5/6/7/8 CParity Byte 同位检查 0:None,1:Odd,2:Even CStop Byte 停止位长度 0:1,1:1.5,2:2 2、 Close_Com 关闭通讯端口 参数是通讯端口号码 型态为 Byte 此一函式和 Open_Com 为一组 所有其它和通讯端 口相关的指令均在这二个函式之间 传回值若为 0 表示执 行无误 3、 Send_Receive_Cmd 将分布式模块指令由通讯端口送出 并接收自模块传回的结果 传回值若为 0 表示执行无误 参数意义如下表 参数名称 型态及意义 范围 CPort Byte 通讯端口号码 1~255 SzCmd String 欲送出的指令 ** 第 558 页,共 606 页SzResult String 传回的结果 ** wTimeOut Integer 逾时设定 单位为毫秒 WCheckSum Integer CheckSum 检查是否激活 0:关闭 1:激活 Watch- Dog Integer 执行指令所 花费的时间 单位为毫秒 除了共享的函式外 尚有其它函式的使用和所要控制的模块有 关 以下针对本书所使用到的各 模块列出相关的函式 以资参考 以下所针对的各模块所使用的函式 其参数数目固定有四个(有 几个例外 请详见 7080 模块部份的线上手册) 第一个是整数型态 的参数数组 第二是单精度型态的参数数组 第三个是传出的字符 串 第四个是传回的字符串 每个函式由于本身功能的不同 需要 改变的参数均可由第一个及第二个参数中的数组改变而得 第三及 第四个参数是保留给使用者作为传出及传回的字符串检查之用 并 不需填入字符串在其中 但必须保留这二个字符串的位置供函式操 作之用 另外 每个函式的传回值将指出此函式执行的结果 若不 为 0 表示有错误发生 由于四个参数固定 因此我们在范例中使用了四个 Public 的变 量存放这四个参数 Public SendTo7000 As String '传给 7000 模块的字符串 Public ReceiveFrom7000 As String '由 7000 模块回传的字符串 Public w7000(0 To 10) As Integer '整数参数数组 Public f7000(0 To 10) As Single '单精度参数数组 其中的整数参数数组(w7000)是最常变动的部份 其中亦有固定的参 数 范例中将会再加以说明 使用 Nap7000P 作程序设计时 笔者 建议是一边 PDF参考手册上的说明(安装Nap7000P时会连带地安装 进去) 一边写程序中的参数指定 比较不会有 Loss 的情形 参数 说明中若未列出表示不必指定其参数 或是该参数不使用于将数值 传回 7012D 模块相关函式 1、 AnalogIn 自模块上取回模拟输入值 整数参数的说明如 下 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 若使用 7012 则设为&H7012 第 559 页,共 606 页w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 通道号码 若模块为单一通道 此项可忽 略 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 单精度参数说明如下 单精度参数数组 代表意义 f7000(0) 模块所传回的模拟值 2、 ReadEventCounter 自模块上取回事件计数值 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 若使用 7012 则设为&H7012 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 w7000(7) 事件值被存放处 3、 ClearEventCounter 将模块的事件计数值记录清除 参数 如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 若使用 7012 则设为&H7012 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 4、 EnableAlarm 激活警戒功能 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 若使用 7012 则设为&H7012 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 0:瞬间警戒 1:闩锁警戒 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 5、 DisableAlarm 取消警戒功能 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 第 560 页,共 606 页w7000(2) 模块编号 若使用 7012 则设为&H7012 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 6、 ClearLatchAlarm 清除闩锁警戒 当闩锁警戒发生时警 戒输出将锁住 利用此函式可清除此情形 参数同 DisableAlarm 7、 SetAlarmLimitValue 设定高低警戒值 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 若使用 7012 则设为&H7012 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 0:低值警戒设定 1:高值警戒设定 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 单精度参数说明如下 单精度参数数组 代表意义 f7000(0) 欲设定的警戒值 8、 ReadAlarmLimitValue 读取高低警戒值 参数表的说明 如 SetAlarmLimitValue 但 f7000(0)所表示的是传回的高 低警戒设定值 9、 ReadOutputAlarmState 读取警戒输出的状态 参数如下 表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 若使用 7012 则设为&H7012 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 w7000(7) 0:警戒关闭 1:瞬间警戒 2:闩锁警戒 w7000(8) 0: DO0/OFF DO1 OFF 1: DO0/ON DO1 OFF 2: DO0/OFF DO1 ON 3: DO0/ON DO1 ON 7080D 模块相关属性 第 561 页,共 606 页1、 CounterIn_7080 读回 7080 模块上计数值 若模块设成 频率模式 也可读回频率值 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 在此为&H7080 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 0:读取通道 0 1:读取通道 1 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 w7000(7) 计数值的高二字节 (Long 的前二个 Byte) w7000(8) 计数值的低二字节(Long 的后二个 Byte) 2、 ClearCounter 清除计数值 重新再计数 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 在此为&H7080 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 0:读取通道 0 1:读取通道 1 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 3、 StartCounting_7080 激活或停止计数 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 在此为&H7080 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 0:读取通道 0 1:读取通道 1 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 w7000(7) 0:停止计数 1 : 开始计数 4、 SetModuleMode_7080 激活或关闭计数模式下的警戒模式 0(请参阅第八章 8- 3- 2 小节) 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 在此为&H7080 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 0:设定警戒模式 0 1: 设定警戒模式 1 w 7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 第 562 页,共 606 页存入字符串 5、 EnableCounterAlarm_7080 激活警戒 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 在此为&H7080 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 0:激活通道 0 警戒 1: 激活通道 1 警戒 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 6、 DisableCounterAlarm_7080 关闭警戒 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 在此为&H7080 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 7、 ReadOutputAlarmState_7080 读取警戒输出通道的状态 参数如下 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 在此为&H7080 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 w7000(7) 模式 0: 0:Alarm0/OFF Alarm 1/OFF 1:Alarm 0/ON Alarm 1/OFF 2:Alarm /OFF Alarm 1/ON 3:Alarm /ON Alarm 1/ON 模式 1: 0:Alarm 关闭 1:激活瞬间 Alarm 2:激活闩锁 Alarm w7000(8) 0: DO0/OFF DO1 OFF 1: DO0/ON DO1 OFF 2: DO0/OFF DO1 ON 3: DO0/ON DO1 ON 7060 模块相关函式 第 563 页,共 606 页1、 DigitalOut 数字输出 整数参数的说明如下 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 在此为&H7060 w7000(3) 0:关闭 ChcekSum 1:开 启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 欲输出的数值 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 2、 DigitalIn 自模块上取回模拟输入状态值 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 若使用 7012 则设为&H7012 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 数字输入值 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 3、 DigitalOutReadBack 将数字输出的结果读回 参数如下 表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 若 使用 7012 则设为&H7012 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 读回的数字输出值 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 7021 模块相关函式 1、 AnalogOut 模拟输出 整数参数的说明如下 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模 块地址 0~255 w7000(2) 模块编号 在此为&H7021 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 欲输出的通道号码 若只有单一通道 则 此参数不使用 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 第 564 页,共 606 页单精度参数说明如下 单精度参数数组 代表意义 f7000(0) 欲输出的模拟值 2、 AnalogOutReadBack 自模块上取回模拟输出值 参数如 下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 若使用 7012 则设为&H7012 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 0:上一次的模拟指令指定的模拟值 1: 模块 现在正在输出的模拟值 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 w7000(7) 通道号码 若只有单一通道 并不需使用 此参数 看门狗相关函式 1、 HostIsOK 主控计算机送出 OK 指令 整数参数的说明如 下 (只用到四个参数) 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 2、 ToSetupHostWatchdog 激活看门狗 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 0:关闭看门狗 1:激活看门狗 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 w7000(7) 更新时间 单位为 0.1 秒 3、 ReadModuleHostWatchdogStatus 检查模块上的看门狗状 态 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum 第 565 页,共 606 页w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 0:看门狗正常 4:看门狗已逾时 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 4、 ResetModuleHostWatchdogStatus 将看门狗状态重置 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 5、 SetSafeValueForDO 设定数字输出的安全值 参数如下 表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 如 &H7060 &H7050 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 安全设定值 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 6、 SetPowerOnValueForDO 设定数字输出的开机初始值 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 如 &H7060 &H7050 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 开机初始值 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 7、 SetSafeValueForAO 设定数字输出的安全值 参数如下 表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 如 &H7021 &H7024 w7000(3) 0:关闭 ChcekSum 1 : 开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 第 566 页,共 606 页w7000(5) 通道号码 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 单精度参数说明如下 单精度参数数组 代表意义 f7000(0) 欲输出的安全值 8、 SetPowerOnValueForAO 设定数字输出的开机初始值 参数如下表 整数参数数组 代表意义 w7000(0) RS- 232 通讯端口号码 1~255 w7000(1) 模块地址 0~255 w7000(2) 模块编号 如 &H7021 &H70240 w7000(3) 0:关闭 ChcekSum 1:开启 CheckSum w7000(4) 逾时设定 以毫秒单位 预设为 100 w7000(5) 通道号码 w7000(6) 0:将字符串存入 SendTo7000 及 ReceiveFrom7000 二个字符串变量 1:不 存入字符串 单精度参数说明如下 单精度参数数组 代表意义 f7000(0) 欲输出的开机初始值 以下是 7000 分布式模块可使用函式的列表及简要说明 若读 者需要时可参考线上手册中的详细说明 函式名称 函式用途 Open_Com 开启通讯端口 Close_Com 关闭通讯端口 Send_Cmd 传送指令 Read_Com_Status 读取通讯端口状态 Send_Str 传送字符串 Send_Receive_Cmd 传送指令 并接收传回结果 ReadConfigStatus 读取模块组态 Test 测试 DLL 用 AnalogIn 读取模拟输入 AnalogInFsr 以百分比格式读取模拟输入 AnalogInHex 以十六进制格式读取模拟输入 AnalogIn8 一次读取 8 个通道的模拟输入 In8_7017 读取 7017 模块的 8 各模拟输入 值 AnalogOut 执行模拟输出 AnalogOutReadBack 将模拟输出值读回 DigitalOut 数字输出 DigitalOutReadBack 将数字输出值读回 DigitalIn 读取数字输入状态 ThermocoupleOpen_7011 检查 7011 模块上的热电偶是 否开路 第 567 页,共 606 页EnableAlarm 激活模块上的警戒功能 DisableAlarm 关闭模块上的警戒功能 ClearLatchAlarm 清除被闩锁住的警戒 SetAlarmLimitValue 设定警戒值 ReadAlarmLimitValue 读取警戒设定值 ReadOutputAlarmState 读取警戒输出通道状态 ReadEventCounter 读取事件计数值 ClearEventCounter 清除事件计数值 CounterIn_7080 读取 7080 模块的计数值或频 率值 ReadCounterMaxValue_7080 读取 7080 最高计数值 SetCounterMaxValue_7080 设定 7080 最高计数值 ReadAlarmLimitValue_7080 读取 7080 警戒设定值 SetAlarmLimitValue_7080 设定 7080 警戒值 ReadCounterStatus_7080 读取 7080 计数器状态 ClearCounter_7080 清除 7080 的计数 ReadOutputAlarmState_7080 读取 7080 的警戒输出状态 EnableCounterAlarm_7080 激活 7080 的警戒模式 0 DisableCounterAlarm_7080 关闭 7080 的警戒模式 0 EnableCounterAlarm_7080D 激活 7080 的警戒模式 1 DisableCounterAlarm_7080D 关闭 7080 的警戒模式 1 SetInputSignalMode_7080 设定 7080 输入讯号模式 ReadInputSignalMode_7080 读取 7080 输入讯号模式 ReadPresetCounterValue_70 80 读取 7080 的预设计数值 PresetCounterValue_7080 设定 7080 的预设计数值 StartCounting_7080 激活 7080 的计数器开始计数 ReadModuleMode_7080 读取 7080 的操作模式 SetModuleMode_7080 设定 7080 的操作模式 ReadLevelVolt_7080 读取 7080 的电平电压值 ReadMinSignalWidth_7080 读取 7080 的最小讯号宽度 SetMinSignalWidth_7080 设定 7080 的最小讯号宽度 SetGateMode_7080 设定 7080 的网关控制模式 ReadGateMode_7080 控制 7080 的网关控制模式 DataToLED_7080 将数值显示到 7080 的 LED SetConfiguration_7080 设定 7080 组态 GetLedDisplay_7033 取得 7033 的 LED 显示值 SetLedDisplay_7033 设定 7033 的 LED 显示值 GetLedDisplay 取得模块的 LED 显示值 SetLedDisplay 设定模块的 LED 显示值 SetupLinearMapping 设定线性对应功能 EnableLinearMapping 激活线性对应功能 DisableLinearMapping 关闭线性对应功能 ReadLinearMappingStatus 读取线性对应状态 DigitalOut_7016 对 7016 模块作数字输出 DigitalBitOut 针对数字输出中的单一通道作 输出 ReadPowerOnValueForDo 读取数字输出模块的开机值 ReadSafeValueForDo 读取数字输出模块的安全值 DigitalInCounterRead 数字输入计数读值 ClearDigitalInCounter 清除数字输入计数读值 第 568 页,共 606 页HostIsOK 传送主控计算机 OK 的讯息 ReadModuleResetStatus 读取模块重置状态 ToSetupHostWatchdog 安装看门狗功能 ToReadHostWatchdog 读取看门狗设定 ReadModuleHostWatchdogSt atus 读取看门狗状态 ResetModuleHostWatchdogSt atus 重置模块上的看门狗 SetSafeValueForDo 设定数字输出模块的安全值 SetPowerOnValueForDo 设定数字输出模块的开机值 SetSafeValueForAo 设定模拟输出模块的安全值 SetPowerOnValueForAo 设定模拟输出模块的开机值 SetPowerOnSafeValue 设定开机及安全值 12-3-3 DLL 与 Visual Basic 由于 DLL 是由厂商所提供 作为应用程序控制分布式模块的方 法之一 Visual Basic 并不知道到底应如何对这些分布式模块作控 制 因此我们必须先将函式告诉 Visual Basic 让 Visual Basic 知道 有这些函式存在 当程序执行的过程中碰到这些函式也才知道如何 去执行 DLL 中的程序代码 在 12-3- 1 小节中说明了如何安装 Nap7000P 的程序 而安装完 成后会产生一个专给 Visual Basic 使用的模块(I7000.Bas) 将此模 块 Copy 到我们的程序项目目录下 引用进项目总管中就可以了 引用的步骤如图 12- 3- 4 图 12- 3- 4 DLL 引用过程 第 569 页,共 606 页以下是其中的二个函式宣告 Declare Function Open_Com Lib "uart.dll" (ByVal Port As Byte, ByVal BaudRate As Long,ByVal cData As Byte, ByVal cParity As Byte, ByVal cStop As Byte) As Integer Declare Function EnableAlarm Lib "i7000.dll" (w7000 As Integer, f7000 As Single, ByVal SendTo7000 As String, ByVal ReceiveFrom7000 As String) As Integer 第一个函式是开启通讯端口 第二个函式是激活警戒 在函式 的结构上均相似 开头的 Declare 指的是宣告 Function 保留字说 明所宣告的是一个函式(具有传回值) 接下是函式名称(Open_Com 及 EnableAlarm) Lib 保留字指出函式来自于后面的函式库 uart.dll 及 i7000.dll 就是分别提供二个函式的函式库了 函式库名称后面是 参数列表 我们随后讨论 在 参数列表后的 As Integer 表示此函式 的传回值是整数型态(在此是传回错误代码) 参数中的宣告有二种方式 一种类似 ByVal Port As Byte 此种 宣告方法说明此参数以”传值”的方式呼叫 当函式被呼叫时 传进 函式的参数会先复制一份 然后将此复制的一份参数传进函式中去 作运算 另一种是类似 w7000 As Integer 此种方式是”传址”的方 式呼叫 此参数的前面少了 ByVal 保留字 当函式被呼叫时 传进 去也会是变量名称 不过 Visual Basic 会将此变量的地址找出来 而将找出的变量内存地址传给函式作处理 一般使用时 若只是传 送数值之类的变量 使用传值呼叫较多 而当传送的是数组的数据 时 由于传值必须先复制一份 比较花时间 因此以使用值址呼叫 较佳 传值呼叫时 只要给变量的名称即可 它会自动作其它该作的 复制动作 回顾上一小节的讨论 我们传送给模块控制函式时 必 须传送二个数组 一个是整数变量数组(w7000) 一个是单精度变 量数组(f7000) 当我们在程序中写了 w7000()指的是数组的地址 此地址是长整数型态 而 w7000(0)则是指此数组元素 0 其内的型 态当然就是所宣告的整数了 在宣告中使用 w7000 As Integer 时 在 Visual Basic 程序中必须给定的是整数的地址 若是整数 当然 就是给 w7000 中的任一个元素了 而此数组的开始位置是第 0 个元 素 所以必须给定 w7000(0)这个变量 传进 DLL 后 此将变为地 址而被运算 12-3-4 函式呼叫顺序 NAP7000P 提供了很多的函式给程序呼叫 几乎大多的功能都 可以利用此组函式予以达成 使用 DLL 比使用 OCX 更低阶 一般 第 570 页,共 606 页情形下 利用 DLL 所得到的系统效能比使用 OCX 来得高 不过还 是有必须注意的地方 1、 务必清楚函式执行顺序 错误的顺序将得不到正确的结 果 2、 参数必须正确 参数不正确或未正确配置内存严重时将导 致程序当掉 既然选择了使用 DLL 发展程序 在串行通讯的过程中 以下是 必须遵守的顺序 1、 正确指定每一个通讯参数 如通讯端口号码 通讯速度 资料长度 极性检查 停止位长度等等 2、 参数正确后 开启通讯端口 3、 每组函式中的 SendTo7000 及 ReceiveFrom7000 参数必须 给定内存空间 以免造成函式执行错误 4、 每个函式中 w7000 及 f7000 数组中的各个元素均需正确 指定 每个元素所需作的指定依不同的模块及功能而略 有不同 当撰写程序代码时最好提将线上手册也开启边 参考边写 5、 即使一直使用同一组函式 其中的参数也可能必须不断 变更(例如对不同模块执行相当功能或设定) 因此必须清 楚是否需要对参数作修正 6、 每个函式皆有传回值 必须时要检查传回值是否为 0 依 定义 若为 0 表示函式执行正确 反之 表示函式有错 误发生 7、 使用完毕后关闭通讯端口 其中的第 7 项若未执行 当应用程序结束时 将会自动将所使 用通讯端口关闭 不过笔者建议还是使用通讯端口关闭函式将通讯 端口关闭掉 以保安全 综上所述 其程序应该是开启通讯端口->指定参数->呼叫函式 ->关闭通讯端口 在开启通讯端口和关闭通讯端口之间执行控制函 式 第 571 页,共 606 页12-4 NAP7000P 的应用 上一节对于 NAP7000P 函式库作过说明后 本节即将以 DLL 作 各模块的控制 笔者分别由各章取出一个例子说明如何将原来使用 MSComm 控件的范例改为使用 DLL 这些例子和 Nap7000X 所取的 完全相同 读者也可以比较这二种方法上的差异 12-4-1 使用流程 使用 DLL 作控制属于较为低阶的一种作法 因此在操作程序上 需要比较小心一些 使用 Nap7000P 的步骤如图 12- 4- 1 開始 結束結束? 傳輸參數設 定 設定 CheckSum 設定通訊埠 號碼 設定模組編 號 設定模組位 址 開啟通訊埠 給定其他參 數 填入f7000元 素 填入w7000元 素 呼叫函式 是 否 图 12- 4- 1 控制流程 所需作的设定请和厂商所使用的宣告相同的型态 相同的型态 会使用相同的内存大小 DLL 操作上使用到大量的记忆体操作 若 型态不正确可能导致 DLL 使用到不一致的内存区块 若侵犯到其 它程序的内存区间严重时会使应用程序当掉 不可不小心 依上述的过程调用小心使用 应该都可以顺利地使用 DLL 控制 分布式模块 以下各节将说明如何替代项目中的 MSComm 控件 而以 NAP7000P 控件达到相同功能 第 572 页,共 606 页12-4-2 7060D 的控制--使用 DLL 本小节项目程序取自第五章 数字输入及数字输出-修正 项目 画面设计完全保留原样 仅程序部份需作改变 画面的设计请读者 参阅第五章 5-4-1 小节的说明 画面除了将 MSComm 换成NAP7000X 外 其余相同 画面如图 12-4- 2 从画面上已可发现没有 MSComm 或是 Nap7000X 控件 图 12-4-2 7060 数字输出入控制画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 双击 开启通讯端口 的按钮 在其 Click 事件中输入以下程序 代码 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex < 255 Then pCOMPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr pBaudRate = 9600 RetVal = OpenCom(pCOMPort, pBaudRate) If RetVal <> 0 Then '开启通讯端口 MsgBox "开启通讯端口时错误!", vbCritical + vbOKOnly, "系统讯息 " Exit Sub End If 第 573 页,共 606 页 cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 程序使用 OpenCom 函式开启通讯端口 此函式置于模块中 其写 法如下 Public Function OpenCom(ByVal cComPort As Byte, ByVal dwBaudRate As Long) As Integer OpenCom = Open_Com(cComPort, dwBaudRate, 8, 0, 0) fComOpen = True End Function 由于分布式模块的资料长度固定为 8 亦不使用同位检查 再加 上停止位是 1 个位 因此参数中只传进通讯端口号码及 BaudRate 即可 2、 双击 开始侦测 其 Click 事件中的程序代码输入如下 '保留空白位置给 DLL 放数据用 SendTo7000 = Space(100) ReceiveFrom7000 = Space(100) '指定模块站号 pModuleAddress = cmbNO.ListIndex + 1 pModuleID = &H7060 pCheckSum = 1 pTimeOut = 1000 w7000(0) = pCOMPort w7000(1) = pModuleAddress w7000(2) = pModuleID w7000(3) = pCheckSum w7000(4) = pTimeOut w7000(6) = 1 If cmdStart.Caption = "开始侦测" Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" End If Do GetDIOFrom7060D '取数据的子程序 DoEvents Loop Until cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" 其中笔者要特别提出说明的是 SendTo7000 及 ReceiveFrom7000 二个字符串变量 这二个变量用来让 DLL 将传送及接收的字符 串填入 也许我们不会使用到(若不需 Debug 自然不使用) 但是 还是要指定一个空白的记忆区给 DLL 否则可能造成 DLL 无法 配置记忆区而发生 这个程序的执行无效 的执行阶段错误 第 574 页,共 606 页整数数组 w7000 也在程序中给定 而 GetDIFrom7060D 函式更换 的内容则如下 '取回数字输出的状态 RetVal = DigitalOutReadBack(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) Buf1 = CStr(w7000(5)) CheckDOStatus Buf1 '送入 DO 状态侦测子程序 '取回数字输入的状态 RetVal = DigitalIn(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) Buf2 = CStr(w7000(5)) CheckDIStatus Buf2 '送入状态侦测子程序 SendRelayOut Buf2 '将侦测的结果转控制至继电器输出 其中的子程序更换的有 SendRelayOut 列出如下 Sub SendRelayOut(RLOut As String) Dim Buf$, i%, RetVal% '数字输出指令 w7000(5) = Val(RLOut) RetVal = DigitalOut(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) End Sub 由于数字输出时 欲输出的数值是放在 w7000(5)数组中 因此在 输出前时须作指定 接着就使用 DigitalOut 指令送出句柄 3、 双击可选择通讯端口号码的 Combo 控件 在其 Click 事件中更换 的程序代码列于下 If cmbCOM.ListIndex + 1 = pCOMPort Then Exit Sub TimeDelay 100 If fComOpen Then RetVal = CloseCom(pCOMPort) ' 关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 其中欲关闭通讯端口的方式是执行 CloseCom 的指令 此指令亦 放于模块中 其内容为 Public Function CloseCom(cComPort As Byte) CloseCom = Close_Com(cComPort) fComOpen = False End Function 执行 Close_Com 指令后 将 fComOpen 旗标设为 False 表示通讯 端口已被关闭 以上项目执行后 我们可以在画面中的通讯端口及站号选择必要 的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 程序 的侦测结果与第五章的图 5-4-3 一样 当我们改变数字输入状态时 也会连带地使得数字输出得到对应的通道输出 第 575 页,共 606 页以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ DLL \ 数字输入及数字输出-修正 档案夹中的项目 双击 Ex.vbp 项目档 即 可进行以上的测试 12-4-3 7012D 的控制--使用 DLL 本小节项目程序取自第六章 6-3-4 小节 模拟值与警戒输出 项目 画面设计完全保留原样 仅程序部份需作改变 画面的设计 请读者参阅第六章 6-3-4 小节的说明 画面除了将 MSComm 换成 NAP7000X 外 其余相同 画面如图 12-4- 3 从画面上已可发现没 有 MSComm 或是 Nap7000X 控件 图 12-4-3 7012 模拟输入控制画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 双击指定通讯端口的 Combo 控件 在其 Click 事件中输入以下程 序代码 If cmbCOM.ListIndex + 1 = pCOMPort Then Exit Sub TimeDelay 100 If fComOpen Then RetVal = CloseCom(pCOMPort) ' 关闭通讯端口 lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 2、 双击 开启通讯端口 按钮 其 Click 事件中的程序代码输入如 下 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex < 255 Then pCOMPort = cmbCOM.ListIndex + 1 Else 第 576 页,共 606 页 MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr pBaudRate = 9600 '指定传输速度 RetVal = OpenCom(pCOMPort, pBaudRate) If RetVal <> 0 Then '开启通讯端口 MsgBox "开启通讯端口时错误!", vbCritical + vbOKOnly, "系统讯息 " RetVal = CloseCom(pCOMPort) If RetVal <> 0 Then MsgBox "关闭通讯端口时错误!", vbCritical + vbOKOnly, "系统 讯息" End If Exit Sub End If cmdOpenCOM.Enabled = False '将此按钮 Disable cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 3、 双击 开始侦测 的按钮 在其 Click 事件中更换的程序代码列 如下 '保留空白位置给 DLL 放数据用 SendTo7000 = Space(100) ReceiveFrom7000 = Space(100) '指定模块站号 pModuleAddress = cmbNO.ListIndex + 1 pModuleID = &H7012 pCheckSum = 1 '激活 CheckSum 检查 pTimeOut = 1000 '逾时设定 1000 毫秒 w7000(0) = pCOMPort w7000(1) = pModuleAddress w7000(2) = pModuleID w7000(3) = pCheckSum w7000(4) = pTimeOut w7000(6) = 1 If chkAlarm.Value = 1 Then '激活警戒 w7000(5) = 0 '设定 Low Alarm f7000(0) = Val(txtLowAlarm.Text) RetVal = SetAlarmLimitValue(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) w7000(5) = 1 '设定 High Alarm f7000(0) = Val(txtHighAlarm.Text) RetVal = SetAlarmLimitValue(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) w7000(5) = 0 '指定为 Momentary Alarm RetVal = EnableAlarm(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) Else 第 577 页,共 606 页 '将 Alarm 取消 RetVal = DisableAlarm(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) End If Timer1.Enabled = Not Timer1.Enabled If Timer1.Enabled Then cmdStart.Caption = "停止侦测" Else cmdStart.Caption = "开始侦测" lblMsg.Caption = "已停止侦测" End If 4、 定时器控件中的程序则如下 w7000(5) = 0 '设定通道 RetVal = AnalogIn(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) VolValue = f7000(0) '取得模拟输入值 lblMsg.Caption = "联机" & Buf & "中 " lblVa lue.Caption = Format(VolValue, "0.000") & "V" '显示在画面上 PlotValue(NowX) = VolValue '将值记录到数组中 If Not fIsOver Then If NowX = 0 Then PicShow.Cls '清除图形 PicShow.DrawWidth = 1 '使用画笔为一个像素宽度画格子线 PicShow.DrawStyle = vbDot '绘 X 轴的格子线, 均分 10 等分 For i = 0 To 9 x = i * MaxPlotNo / 10 PicShow.Line (x, 0)- (x, 10) Next i '绘 Y 轴的格子线, 均分 5 等分 For i = 0 To 4 y = i * 10 / 5 PicShow.Line (0, y)- (MaxPlotNo, y) Next i PicShow.DrawWidth = 2 ' 绘线宽度改为 2 PicShow.DrawStyle = vbSolid '以实线绘图 PicShow.PSet (0, VolValue), RGB(255, 0, 0) '设定起点 Else '以下判断现在的读值是否大于前一次的读值 若是 则以红色绘 线 '若否 则以蓝色绘线 If VolValue > PreValue + 0.01 Then PicShow.Line - (NowX, VolValue), RGB(255, 0, 0) '由上一次 的位置画至此点 Else PicShow.Line - (NowX, VolValue), RGB(0, 0, 255) '由上一次 的位置画至此点 End If End If Else lblStart.Caption = CStr(Val(lblStart.Caption) + 1) lblEnd.Caption = CStr(Val(lblEnd.Caption) + 1) Plot NowX + 1 '将图绘在另一个隐藏的图片框 '绘好的图形转移至显示的图片框 第 578 页,共 606 页 Call BitBlt(PicShow.hDC, 0, 0, P1Width, P1Height, PicHide.hDC, 0, 0, &HCC0020) End If PreValue = VolValue NowX = NowX + 1 '位置加 1 If NowX > MaxPlotNo Then NowX = 0 fIsOver = True End If '检查警戒状态 RetVal = ReadOutputAlarmState(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) AlarmValue = w7000(8) If w7000(7) <> 0 Then '若激活警戒才作检查 If (AlarmValue And 1) = 1 Then '低值警戒是否激活 imgLO.Picture = imgGreen.Picture Else imgLO.Picture = imgDisable.Picture End If If (AlarmValue And 2) = 2 Then '高值警戒是否激活 imgHigh.Picture = imgRed.Picture Else imgHigh.Picture = imgDisable.Picture End If End If 以上各部份是作了更换的部份 其它均维持原来的设计 并无变 更 读者可以将变更的前后作个比较 以上项目执行后 我们可以在画面中的通讯端口及站号选择必 要的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 选 择是否激活警戒 程序的侦测结果与第六章的 6-3-4 测试结果会是 一样的 以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ DLL \ 模拟值与警戒输出 档案夹中的项目 双击 Ex.vbp 项 目档 即可进行以上的测试 12-4-4 7021 的控制--使用 DLL 本小节项目程序取自第七章 7-2-4 小节 电压的输出 项目 画面设计完全保留原样 仅程序部份需作改变 画面的设计请读者 参阅第七章 7-2-4 小节的说明 画面除了将 MSComm 换 成 NAP7000X 外 其余相同 画面如图 12-4- 4 从画面上已可发现没有 MSComm 或是 Nap7000X 控件 第 579 页,共 606 页 图 12-4-4 7021 模拟输出控制画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 双击指定通讯端口的 Combo 控件 在其 Click 事件中输入以下程 序代码 If cmbCOM.ListIndex + 1 = pCOMPort Then Exit Sub TimeDelay 100 If fComOpen Then RetVal = CloseCom(pCOMPort) '关闭通讯端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 2、 双击 开启通讯端口 按钮 其 Click 事件中的程序代码输入如 下 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex < 255 Then pCOMPort = cmbCOM.ListIndex + 1 Els e MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr pBaudRate = 9600 RetVal = OpenCom(pCOMPort, pBaudRate) cmdOpenCOM.Enabled = False '将此按钮 Disable lblMsg.Caption = "可选择 Slide 控件的指针 执行输出的工作 " If cmdOpenCOM.Enabled Then lblMsg.Caption = "尚未开启通讯端口!" Exit Sub End If '保留空白位置给 DLL 放数据用 SendTo7000 = Space(100) ReceiveFrom7000 = Space(100) '指定模块站号 pModuleAddress = cmbNO.ListIndex + 1 pModuleID = &H7021 pCheckSum = 1 '激活 CheckSum 检查 第 580 页,共 606 页 pTimeOut = 1000 '逾时设定 1000 毫秒 w7000(0) = pCOMPort w7000(1) = pModuleAddress w7000(2) = pModuleID w7000(3) = pCheckSum w7000(4) = pTimeOut w7000(6) = 1 '先恢复到 0V 的位置 f7000(0) = 0 RetVal = AnalogOut(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 3、 双击 Slider 控件 在其 Scroll 事件中更换的程序代码列如下 If Not fComOpen Then lblMsg.Caption = "尚未开启通讯端口!" Exit Sub End If lblValue.Caption = Slider1.Value / 10 & "V" '显示在画面上 '送出模拟指令 f7000(0) = Val(lblValue.Caption) RetVal = AnalogOut(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) lblMsg.Caption = "联机" & Buf & "中 " If RetVal <> 0 Then lblMsg.Caption = "输出电压失败 " Exit Sub End If '取回现在读值 w7000(5) = 1 '读取现在的输出值 RetVal = AnalogOutReadBack(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) RetVal = f7000(0) '现在的输出电压值 lblReadBack.Caption = CStr(RetVal) & "V" '显示在画面上 4、 结束 按钮的程序则如下 If fComOpen Then f7000(0) = 0 RetVal = AnalogOut(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) End If End 以上各部份是作了更换的部份 其它均维持原来的设计 并无变 更 读者可以将变更的前后作个比较 以上项目执行后 我们可以在画面中的通讯端口及站号选择必 要的设定 接着按下 开启通讯端口 的按钮 拉动 Slider 控件的 第 581 页,共 606 页拉杆 模拟输出值即会跟着改变 程序的侦测结果与第七章的 7-2-4 测试结果会是一样的 以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ DLL \ 电压的输出 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的测试 12-4-5 7080D 的控制--使用 DLL 本小节项目程序取自第八章 8-3-3 小节 计数值与警戒输出-频 率 项目 画面设计完全保留原样 仅程序部份需作改变 画面的 设计请读者参阅第八章 8-3-3 小节的说明 画面除了将 MSComm 换 成 NAP7000X 外 其余相同 画面如图 12-4- 5 从画面上已可发现 没有 MSComm 或是 Nap7000X 控件 图 12-4-5 7080 频率计数及警戒的画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 双击指定通讯端口的 Combo 控件 在其 Click 事件中输入以下程 序代码 If cmbCOM.ListIndex + 1 = pCOMPort Then Exit Sub Timer1.Enabled = False '关闭定时器 TimeDelay 100 If fComOpen Then RetVal = CloseCom(pCOMPort) '关闭通讯 端口 End If lblMsg.Caption = "已停止侦测并关闭通讯端口" cmdOpenCOM.Enabled = True '允许使用 开启通讯端口 的按钮 第 582 页,共 606 页 2、 双击 开启通讯端口 按钮 其 Click 事件中的程序代码输入如 下 If cmbCOM.ListIndex >= 0 And cmbCOM.ListIndex < 255 Then pCOMPort = cmbCOM.ListIndex + 1 Else MsgBox "指定通讯端口时发生错误!", vbCritical + vbOKOnly, "系统 讯息" Exit Sub End If '激活错误侦测机制 On Error GoTo comErr pBaudRate = 9600 RetVal = OpenCom(pCOMPort, pBaudRate) If RetVal <> 0 Then MsgBox "通讯端口开启错误!", vbCritical + vbOKOnly, " 系统讯息" Exit Sub End If cmdOpenCOM.Enabled = False '将此按钮 Disable '保留空白位置给 DLL 放数据用 SendTo7000 = Space(100) ReceiveFrom7000 = Space(100) '指定模块站号 pModuleAddress = cmbNO.ListIndex + 1 pModuleID = &H7080 pCheckSum = 1 '激活 CheckSum 检查 pTimeOut = 1000 '逾时设定 1000 毫秒 w7000(0) = pCOMPort w7000(1) = pModuleAddress w7000(2) = pModuleID w7000(3) = pCheckSum w7000(4) = pTimeOut w7000(6) = 1 '变更型态为频率模式 使用%AANNTTCCFF 指令 SendTo7000 = "%0" & CStr(pCOMPort) & "0" & CStr(pCOMPort) & "51" & "06" & "00" RetVal = Send_Receive_Cmd(pCOMPort, SendTo7000, ReceiveFrom7000, pTimeOut, pCheckSum, Temp1) If RetVal <> 0 Then MsgBox "组态指令执行错误! 请检查指令或模块 ", vbCritical + vbOKOnly, "系统讯息" End If cmdStart.Enabled = True 'Enable 开始侦测的按钮 lblMsg.Caption = "可按下 开始侦测 按钮 执行侦测的工作 " Exit Sub comErr: MsgBox "开启通讯端口时发生错误! 请确定通讯端口存在且正常 ", vbCritical + vbOKOnly, "系统讯息" 和之前的变更的部份是一样的 不过在此我们注意到在变更 7080 模块的组态所使用的指令并不是 DLL 所提供的组态函式 而是使 用 Send_Receive_Cmd 函式 此函式可以为我们传送任何的命令 第 583 页,共 606 页字符串 而且也会自动为我们的 CheckSum 作计算 或是计算传 回字符串的 CheckSum 结果 当 DLL 未提供的函式 就可以使用 此法弥补其不足 3、 定时器控件中的程序则如下 w7000(5) = FreqCh '指定通道 RetVal = CounterIn_7080(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) If RetVal <> 0 Then lblMsg.Caption = "取值失败 " Exit Sub End If FreqValue = 65536 * w7000(7) + w7000(8) '要求频率值回传 lblValue(FreqCh).Caption = FreqValue & "Hz" '显示在画面上 '读取警戒输出现在的状态 RetVal = ReadOutputAlarmState_7080(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) AlarmOut = w7000(8) If RetVal <> 0 Then '若发生错误 则显示讯息 lblMsg.Caption = "未传回正确状态讯息 " Else If (AlarmOut And 1) = 1 Then '第 0 个数字输出的检查 imgAlarm(0).Picture = imgRed.Picture Else imgAlarm(0).Picture = imgGreen.Picture End If If (AlarmOut And 2) = 2 Then '第 1 个数字输出的检查 imgAlarm(1).Picture = imgRed.Picture Else imgAlarm(1).Picture = imgGreen.Picture End If OutValue = Val(AlarmOut) End If '检查频率值 If FreqValue > Val(txtFreqAlarm(FreqCh).Text) Then '作警戒输出 OutValue = OutValue Or 2 ^ FreqCh '作 OR 运算 找出应 High 的通 道 Else OutValue = OutValue And (Not 2 ^ FreqCh) '作 OR 运 算 找出应 High 的通道 End If w7000(5) = OutValue RetVal = DigitalOut(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) FreqCh = (FreqCh + 1) Mod 2 '下一个通道 变更通道号码必须在呼叫 CounterIn_7080 前放入 w7000(5)元素 中 其号码由 0~1 才能取得各通道的频率值 4、 结束 按钮的 Click 事件程序则如下 RetVal = CloseCom(pCOMPort) End 第 584 页,共 606 页 以上各部份是作了更换的部份 其它均维持原来的设计 并无变 更 读者可以将变更的前后作个比较 以上项目执行后 我们可以在画面中的通讯端口及站号选择必 要的设定 接着按下 开启通讯端口 及 开始侦测 的按钮 选 择是否激活警戒 程序的侦测结果与第八章的 8-3-3 测试结果会是 一样的 以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ DLL \ 计数值与警戒输出-频率 档案夹中的项目 双击 Ex.vbp 项目档 即可进行以上的测试 12-4-6 Dual-WatchDog 的实现--使用 DLL 本小节项目程序取自第九章 9-2-5 小节 Watch-Dog 项目 画 面设计完全保留原样 仅程序部份需作改变 画面的设计请读者参 阅第九章 9-2-5 小节的说明 画面除了将 MSComm 换成 NAP7000X 外 其余相同 画面如图 12-4- 6 从画面上已可发现没有 MSComm 或是 Nap7000X 控件 图 12-4-6 Watch-Dog 的画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 在窗体的 Form_Load 事件中输入以下程序代码 pBaudRate = 9600 pCOMPort = 1 '使用 COM1 RetVal = OpenCom(pCOMPort, pBaudRate) 开启通讯端口 第 585 页,共 606 页2、 定时器控件中的程序则如下 '保留空白位置给 DLL 放数据用 SendTo7000 = Space(100) ReceiveFrom7000 = Space(100) '指定参数 pCheckSum = 1 '激活 CheckSum 检查 pTimeOut = 100 '逾时设定 100 毫秒 w7000(0) = pCOMPort w7000(3) = pCheckSum w7000(4) = pTimeOut '检查是否激活 WatchDog If fWatchDog And (Not WatchDogEnabled) Then '送出 7060D 的 WatchDog 设定 w7000(1) = 1 'Module Address w7000(5) = 1 '激活 Watch- Dog w7000(7) = Val(txtTimeOut(0).Text) * 10 '时间设定 RetVal = ToSetupHostWatchdog(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) If RetVal <> 0 Then MsgBox "7060D 指定 Watch- Dog 过程发生错 误 !", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7012D 的 WatchDog 设定 w7000(1) = 2 'Module Address w7000(5) = 1 '激活 Watch- Dog w7000(7) = Val(txtTimeOut(1).Text) * 10 '时间设定 RetVal = ToSetupHostWatchdog(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) If RetVal <> 0 Then MsgBox "7012D 指定 Watch- Dog 过程发生错 误 !", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7021 的 WatchDog 设定 w7000(1) = 3 'Module Address w7000(5) = 1 '激活 Watch- Dog w7000(7) = Val(txtTimeOut(2).Text) * 10 '时间设定 RetVal = ToSetupHostWatchdog(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) If RetVal <> 0 Then MsgBox "7021 指定 Watch- Dog 过程发生错误!", vbCritical + vbOKOnly, "系统讯息" DoEvents '送出 7080D 的 WatchDog 设定 w7000(1) = 4 'Module Address w7000(5) = 1 '激活 Watch- Dog w7000(7) = Val(txtTimeOut(3).Text) * 10 '时间设定 RetVal = ToSetupHostWatchdog(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) If RetVal <> 0 Then MsgBox "7080D 指定 Watch- Dog 过程发生错 误 !", vbCritical + vbOKOnly, "系统讯息" fWatchDog = False '设为 False 表示已作过设定 WatchDogEnabled = True '设为 True 表示已激活 Watch- Dog DoEvents End If DoEvents '询问各模块的 Watch- Dog 状态 For i = 0 To 3 w7000(1) = i + 1 'Module Address 第 586 页,共 606 页 RetVal = ReadModuleHostWatchdogStatus(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) ModuleStatus = w7000(5) If RetVal = 0 Then '若正确传回字符串 则显示出状态 If ModuleStatus Then '若被 Reset 则显示红色 imgStatus(i).Picture = imgON.Picture Else '否则显示绿色 imgStatus(i).Picture = imgOFF.Picture End If End If DoEvents Next '检查是否清除 Reset 状态 If fReset Then For i = 0 To 3 w7000(1) = i + 1 'Module Address RetVal = ResetModuleHostWatchdogStatus(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) Next i fReset = False WatchDogEnabled = False DoEvents End If '若勾选 HostOK 选项 则送出 OK 指令 If chkHostOK.Value = 1 Then RetVal = HostIsOK(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) lblMsg.Caption = "HostOK- >" & CStr(L) DoEvents L = L + 1 End If Timer 事件是主要的程序执行区 由于别会针对四个模块作动作 因此在每一程序片断中都适当地指定了 w7000 变量数组中的值 询问是否已经超过时间的设定 则是使用 ReadModuleHostWatchdogStatus 函式 当此属性为 True 时表示所 设定的时间已经超过 超过时间的模块 上面的灯号会一直闪烁 清除模块闩锁状态的函式使用的是 ResetModuleHostWatchdogStatus 函式 将清除的指令送到每个模 块去 以上各部份是作了更换的部份 其它均维持原来的设计 并无变 更 读者可以将变更的前后作个比较 以上项目执行后 我们可以在画面中的通讯端口及站号选择必 要的设定 接着按下 激活看门狗 选择是否送出 HostOK 指令 以清除模块上的侦测 程序的执行结果与第九章的 9-2-5 测试结果 会是一样的 第 587 页,共 606 页以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ DLL \ Watch-Dog 档案夹中的项目 双击 Ex.vbp 项目档 即 可进行以上的测试 12-4-7 整合测试项目的程序变更--使用 DLL 本小节项目程序取自第十章 整合测试 项目 画面设计完全 保留原样 仅程序部份需作改变 画面的设计请读者参阅第十章的 说明 画面如图 12- 4-7 从画面上已可发现没有 MSComm 或是 Nap7000X 控件 图 12-4-7 整合测试的画面设计 程序代码部份应更换处说明如下(不同处以粗黑字体标出) 1、 在窗体的 Form_Load 事件中输入以下程序代码 P1Width = PicShow.ScaleWidth P1Height = PicShow.ScaleHeight MaxPlotNo = 200 '将数组重新定义 第一个索引表温度 转速 第二个索引表绘图序次 ReDim PlotValue(0 To 2, 0 To MaxPlotNo) TempSensitivity = 30 '每 1 伏为 30 度 C 默认值 pCOMPort = 1 pBaudRate = 9600 RetVal = OpenCom(pCOMPort, pBaudRate) TimeDelay 100 '保留空白位置给 DLL 放数据用 SendTo7000 = Space(100) ReceiveFrom7000 = Space(100) '指定模块站号 pCheckSum = 1 '激活 CheckSum 检查 pTimeOut = 1000 '逾时设定 1000 毫秒 w7000(0) = pCOMPort 第 588 页,共 606 页 w7000(3) = pCheckSum w7000(4) = pTimeOut '激活 7012 的警戒 LoHiValue(0) = 3 * TempSensitivity LoHiValue(1) = 8 * TempSensitivity w7000(1) = 2 w7000(2) = &H7012 w7000(5) = 0 'Low Alarm f7000(0) = LoHiValue(0) / TempSensitivity RetVal = SetAlarmLimitValue(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) w7000(5) = 1 'High Alarm f7000(0) = LoHiValue(1) / TempSensitivity RetVal = SetAlarmLimitValue(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) w7000(5) = 0 'Momentary Alarm RetVal = EnableAlarm(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) '先将 7060 的 Relay 全部打开 w7000(1) = 1 w7000(2) = &H7060 w7000(5) = &HF RetVal = DigitalOut(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) For i = 0 To 3 Relay(i) = True Next i '变更 7080 型态为频率模式 使用%AANNTTCCFF 指令 SendTo7000 = "%0404510640" RetVal = Send_Receive_Cmd(pCOMPort, SendTo7000, ReceiveFrom7000, pTimeOut, pCheckSum, 100) '多媒体控件的初始化 MMC1.UpdateInterval = 0 MMC1.Command = "Stop" MMC1.Command = "Close" MMC1.Wait = True MMC1.DeviceType = "WaveAudio" MMC1.FileName = App.Path & "\ Alarm1.wav" MMC1.Command = "Open" mciOK = True 同样的 变更组态设定时 并无对应的属性或方法可以使用 所 以使用 SendReceiveCmd 函式达到变更组态的目的 2、 原来在定时器控件的 Timer 事件中有四个处理各模块的子程序 它们均有变更 列出如下 Sub Sub7012() Dim Buf$ Dim i%, RetVal As Integer Dim RetTemp As Single w7000(1) = 2 w7000(2) = &H7012 '读取温度值 并显示 RetVal = AnalogIn(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) RetTemp = f7000(0) 第 589 页,共 606 页 If RetVal = 0 Then '若正确传值回来 则 PlotValue(0, NowX) = RetTemp * TempSensitivity lblTemp.Caption = PlotValue(0, NowX) Else '否则显示问号 lblTemp.Caption = "????" End If '检查警戒状态 RetVal = ReadOutputAlarmState(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) RetVal = w7000(8) If (RetVal And 1) = 1 Then '低值警戒是否激活 '改变颜色为红色 imgE1.Picture = imgRed.Picture lblLoAlarm.BackColor = RGB(100, 255, 0) Else '改变颜色为绿色 imgE1.Picture = imgGreen.Picture lblLoAlarm.BackColor = &H8000000F End If If (RetVal And 2) = 2 Then '高值警戒是否激活 imgE2.Picture = imgRed.Picture lblHiAlarm.BackColor = RGB(100, 255, 0) '高值警戒时 亦激活声音播放 If mciOK Then mciOK = False MMC1.Notify = True '激活通知功能 MMC1.Command = "Play" '下达播放指令 End If Else imgE2.Picture = imgGreen.Picture lblHiAlarm.BackColor = &H8000000F End If If fLoHi Then '若改变高低警戒 则改变设定 '设定值先除以灵敏值 以得到实际的电压值 w7000(5) = 0 'Low Alarm f7000(0) = LoHiValue(0) / TempSensitivity RetVal = SetAlarmLimitValue(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) w7000(5) = 1 'High Alarm f7000(0) = LoHiValue(1) / TempSensitivity RetVal = SetAlarmLimitValue(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) w7000(5) = 0 'Momentary Alarm RetVal = EnableAlarm(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) fLoHi = False '将旗标设成 False 以免再次被执行 End If End Sub Sub Sub7080() Dim SpeedValue As Long Dim RetVal As Integer w7000(1) = 4 w7000(2) = &H7080 '二个通道分别要求传回数值 '要求 Ch0 传回转速值 w7000(5) = 0 '设定 Ch0 第 590 页,共 606 页 RetVal = CounterIn_7080(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) SpeedValue = 65536 * w7000(7) + w7000(8) '若成功传回结果字符串 If RetVal = 0 Then PlotValue(1, NowX) = SpeedValue '转换为实际的数值 lblF1.Caption = PlotValue(1, NowX) '显示在画面上 Else '否则就显示问号 lblF1.Caption = "????" End If '要求 Ch1 传回转速值 w7000(5) = 1 '设定 Ch0 RetVal = CounterIn_7080(w7000(0), f7000(0 ), SendTo7000, ReceiveFrom7000) SpeedValue = 65536 * w7000(7) + w7000(8) If RetVal = 0 Then PlotValue(2, NowX) = SpeedValue '转换为实际的数值 lblF2.Caption = PlotValue(2, NowX) '显示在画面上 Else '否则就显示问号 lblF2.Caption = "????" End If End Sub S ub Sub7060() Dim Buf$, RBuf$ Dim i%, OutValue%, InValue% Dim RetVal As Integer w7000(1) = 1 w7000(2) = &H7060 '侦测数字状态, 数字输出状态 RetVal = DigitalOutReadBack(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) OutValue = w7000(5) '若正确传回结果字符串 则 进行判断 If RetVal = 0 Then '改变状态灯号颜色 For i = 0 To 3 imgR(i).Picture = IIf(OutValue And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i End If RetVal = DigitalIn(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) InValue = w7000(5) If RetVal = 0 Then '数字输入状态 For i = 0 To 3 imgP(i).Picture = IIf(InValue And 2 ^ i, imgRed.Picture, imgGreen.Picture) Next i End If '判断是否作 Relay 输出 If fRelay Then OutValue = 0 第 591 页,共 606 页 '计算数字输出的数值 For i = 0 To 3 OutValue = OutValue + IIf(Relay(i), 2 ^ i, 0) Next i '送出数字输出的指令 w7000(5) = OutValue RetVal = DigitalOut(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) fRelay = False End If End Sub Sub Sub7021() Dim RetVal As Integer w7000(1) = 3 w7000(2) = &H7021 w7000(5) = 1 '读取现在的输出值 '取得现在的模拟输出值 RetVal = AnalogOutReadBack(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) '若取值正确 则显示在状态区 If RetVal = 0 Then lblVoltage.Caption = f7000(0) Else '否则就显示问号 lblVoltage.Caption = "????" End If If fVOut Then '判断是否作模拟输出 '送出模拟指令 f7000(0) = VOut RetVal = AnalogOut(w7000(0), f7000(0), SendTo7000, ReceiveFrom7000) fVOut = False End If '若温度过高 关闭热源(R1) 激活加水马达(V) If PlotValue(0, NowX) >= LoHiValue(1) Then '以下并不直接作输出 而是激活旗标 Relay(0) = False fRelay = True VOut = PlotValue(0, NowX) / TempSensitivity fVOut = True End If End Sub Timer 事件是主要的程序执行区 由于会针对四个模块作动作 因此在每一程序片断中都指定 w7000 或是 f7000 变量数组内的元 素值 以上各部份是作了更换的部份 其它均维持原来的设计 并无变 更 读者可以将变更的前后作个比较 以上项目执行后 按下 激活系统 即可进行系统测试 程序 的执行结果与第十章的结果会是一样的 第 592 页,共 606 页以上的程序代码 您可以参考光盘中 范例 \ 第十二章 \ DLL \ 整合测试 档案夹中的项目 双击 Ex.vbp 项目档 即 可进行以上的测试 第 593 页,共 606 页本章习题 1、 设计一组程序 7021 的模拟输出配合上电压改变率( 使用 NAP7000X 控件) 2、 设计一组程序 取得 7012 上的 Event 值 (使用 NAP7000X 控 件 ) 3、 修改 12-2-5 小节程序代码 将频率值的读取改为计数值的读 取 并激活模块上的警戒设定(使用 NAP7000X 控件) 第 594 页,共 606 页附录: 1 如何使用本书的光盘片 本书使用了 Lotus 的 ScreenCam 软件将操作的画面录下来 希 望读者可以藉由影音的说明而更加了解笔者所想要表达的内容 我 们最大的期望就是读者可以习得书中所载明的技术 以下是光盘片的操作说明 1、 将本书的光盘片放入光驱中 接着可以在档案总管的光驱目录 中看到如下的画面 档案总管的右边显示出本光盘片所包含的内容 主要分成三个 档案夹 专题范例的档案夹中含有所范例的原始程序代码 并 以章次作为分隔 语音说明的档案夹中含有语音说明的执行文 件 点选后可以看到笔者以 Lotus Sc reenCam 所录制的说明程 序 其它资料是书中提到 而笔者可以提供给各位读者的一部 份参考资料 应该都是原文的 希望对读者有用 2、 欲执行范例程序 请点选光盘片中的专题范例档案夹 并选择 所要执行的章次 例如我们希望执行第八章的某一个范例 则 可以如下图一般地作选择 第 595 页,共 606 页 3、 接着双击该项目档(*.vbp) 计算机会以 Visual Basic 6 将项目激 活 并且带领读者进入设计环境 读者就可以开始针对该项目 作练习的工作 如下图 4、 当读者已将项目练习后 可以直接按下 F5 让项目执行起来 而 执行起来会如下图所示 第 596 页,共 606 页 光 盘片中的档案是只读性的档案 如果直接由光盘片中开启 当 修改完后 是无法存盘的 所以我们建议读者先将范例部份先拷 贝到硬盘中 并将其档案的只读属性解除 如此才能在修改后存 盘 欲解除档案的只读属性 乃是在档案总管中选取所要变更的档 案 选定后 按下鼠标右键 选择 内容 选项 如下图 接着出现内容画面后 再将原来打勾的只读属性取消 按下确定 或套用即可 如下图 第 597 页,共 606 页 5、 如果读者觉得自己手动练习似乎还有不足的地方 也可以藉由 影音档的辅助而了解项目的执行及设计程序 请读者开启光盘 片中的语音说明 其中以章次作了清楚的分类 若有语音说明 文件 皆会在档案夹中显示 如下图 6、 请读者点选欲观看的影音说明文件 计算机随即会执行该执行 档 而将原先录制的画面过程及说明显示在计算机上 读者可 以看看是否与自己作该专题时有不一样的地方 影音说明的录 制均以范例作为对象 试者可以自行比较 如下图所示 第 598 页,共 606 页 影音说明的画面会有一个控制的接口 我们可以控制其播放 停止 向前 向后及音量的大小 本书的诞生期间不算短 先 后使用了二个版本的录制软件 上图是较早的软件版本 而下 图则是使用了较新的软件版本 不管新旧版本 只是表面有些 不同 其它操作上是一样的 第 599 页,共 606 页 7、 当观赏完录制的画面后 画面上会留一个控制的画面 此画面 不会自动消失 如果此控制画面不关闭而再双击一个影音说明 执行档时 会产生以下的错误 此错误产生时 请读者先将画面上的控制画面关闭(按该画面右 上角的叉叉记号) 接着就可以再开启其它的影音执行档了 第 600 页,共 606 页附录: 2 7000 Utility 公用程序 7000 Utility 是由力激科技所开发 专门用来侦测 设定 诊断 分布式模块 一开始操作分布式模块时 使用此公用程序是比较方 便的 安装此程序时 请将光盘片放入光驱中 如图 I2-1 所示 选 择其中的 Setup.exe 安装程序 并执行它 图 I2- 1 公用程序的所在位置 经图 I2-2 的各主要安装步骤(只要选择默认值即可)后 程序会 要求重新开机 而开机完成后 在开始程序集里就会出现如图 I2- 3 的应用程序 图 I2- 2 主要的安装过程画面 第 601 页,共 606 页 图 I2- 3 公用程序 7000Utility 点选 7000Utility 程序即会在画面上出现此公用程序的主画面 而且此程序会开始搜寻预设串行端口(预设为 COM2)上的 RS- 485网 络 及其上的分布式模块 我们也可以改变通讯端口的号码及必要 设定 如图 I2- 4 所 示 并要求重新搜寻 其结果可能如图 I2-5 所 示 图 I2- 4 设定参数 第 602 页,共 606 页图 I2- 5 搜寻结果 一旦结果出现 可以按下停止按钮使其停止搜寻 并且可以也 选画面上所找到的模块 进一步地进入控制/设定画面 例如 我们 想针对画面上的 7060D 模块作控制或设定 便可在结果画面上的 7060D 编号上双击 程序便引出如图 I2- 6 的画面 图 I2- 6 7060D 的控制/设定画面 图 I2-6 中的上方圆形是数字输入显示区 而其下的圆形是数字 输出的控制区 画面的左下方区域则是组态设定区 数字输出入区 中灰色表示该波道模块未提供 而红色表示该波道处于高电位 白 敬色表示该波道处于低电位 组态设定区中的 Address 用于设定模块的 RS-485 网络地址 依 实际情形需作变更 例如图 I2- 5 中就是本书所使用的模块 编号都 经笔者事先以公用程序予以变更过 方便实验的进行 CheckSum 及 BuadRate 分别用于设定是否激活 CheckSum 检查 及改变传输速度 此二项设定若需改变时 必须将模块上的 INIT* 和 GND 脚位短路 改变设定才有效 否则将出现如图 I2-7 的错误 讯息 图 I2- 7 设定时未接 INIT*和 GND 第 603 页,共 606 页当所有模块的设定均完成后 通常笔者会重新将模块断电 再 接上电源 使模块状态全部初始化 使用公用程序的最大好处是不 需自行撰写程序 就可以设定相关的参数 方便使用者在开始架构 工作前先行了解实际的模块状况 第 604 页,共 606 页 附录: 3 参考文献 1、 Visual Basic 与 RS-232 串行通讯控制 范逸之 陈立元 赖俊朋编着 文 魁信息股份有限公司 民国八十八年十月初版 2、 Visual Basic 与串并列通讯控制实务 范逸之 陈立元 孙德萱 程正孚编 着 文魁信息股份有限公司 民国八十九年十一月初版 3、 I-7000 User Manual 泓格科技股份有限公司 4、 RS-232 入门浅论 林宗宏译 儒林图书有限公司 民国七十五年二月初版 5、 图形监控 廖文辉 周至宏编着 全华科技图书股份有限公司 民国八十 七年一月 6、 数据通信与计算机网络 黄丰隆编着 儒林图书公司 1991 年 9 月初版 7、 RS-232-C 技术详解与应用 白中和编译 全华科技图书股份有限公司 民 国八十年十月再版 8、 微电脑资料传送技术 白中和编译 全华科技图书股份有限公司 民国七 十五年二月二版 9、 通讯宝典(10)-计算机通信 广磁信息股份有限公司 民国八十三年九月初 版 10、 精通序列通讯 赵丽松 张瑗译 儒林图书有限公司 1994 年 12 月初版 11、 RS-232-C 接口技术应用 白中和编译 全华科技图书股份有限公司 民国 八十年十一月再版 12、 MicroSoft Visual Studio On Line Help 13、 分布式应用程序设计指南 许建志 编译 Ted Pattison原著 微软出版社 华彩软件发行 1999 年 7 月 一版一刷 第 605 页,共 606 页附录: 4 ASCII 码 以下列出 ASCII 码的前 128 个 前 32 个通常有其特殊的意义 在串行通讯中也经常使用到这 32 个字码 后 128 个字码为不可见 字符 而日本的 JIS Code 则将其编为假名 在此就不予列出 当使 用到字码时 可依其 Binary 数值在本表中查到对应的字符 7 0 0 0 0 1 1 1 1 6 0 0 1 1 0 0 1 1 BITS 5 0 1 0 1 0 1 0 1 4 3 2 1 0 0 0 0 NUL DLE SP 0 @ P ` p 0 0 0 1 SOH DC1 ! 1 A Q a q 0 0 1 0 STX DC2 “ 2 B R b r 0 0 1 1 ETX DC3 # 3 C S c s 0 1 0 0 EOT DC4 $ 4 D T d t 0 1 0 1 ENQ NAK % 5 E U e u 0 1 1 0 ACK SYN & 6 F V f v 0 1 1 1 BEL ETB ‘ 7 G W g w 1 0 0 0 BS CAN ( 8 H X h x 1 0 0 1 HT EM ) 9 I Y i y 1 0 1 0 LF SUB * : J Z j z 1 0 1 1 VT ESC + ; K [ k { 1 1 0 0 FF FS , < L \ l | 1 1 0 1 CR GS - = M ] m } 1 1 1 0 SO RS . > N ^ n ~ 1 1 1 1 SI US / ? O _ o DEL 第 606 页,共 606 页
还剩606页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

1147768077

贡献于2013-12-17

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf