第一篇 : Windows CE

Size: px
Start display at page:

Download "第一篇 : Windows CE"

Transcription

1 第一篇 : Windows CE.NET 应用程序设计基础 第一章 :Windows CE.NET 简介 1.1 什么是 Windows CE 微软 Windows CE 是一个开放且多样化的 32- 位嵌入式操作系统 其设计目的是为符合广泛的智能设备的需求, 例如从企业工具诸如工业控制器, 通信集线器, 和收款机系统 (POS) 到电子消费性产品诸如摄影机 电话和家庭娱乐设备等, 提供自动控制 视听娱乐 行动计算 终端机 资料收集 资料分享及连网功能等各个应用领域一个稳定 实时及多任务的操作系统 一个典型的 Windows CE 嵌入式系统常被订做为达到特定目的, 并且需要有一个轻量化及内建能果决反应系统中断的操作系统 Windows CE.NET 目前支持四大系列的 CPU 架构 (ARM, MIPS, SHx, X86) 及超过两百种品牌的 CPU, 也因为轻量化及高度的模块化及客制化,Windows CE.NET 被广泛的应用来设计出各种的设备如下表 行动电话 / 智能型通话装置 (Cell Phone/Smartphone Device) 顾客装置 (Custom Device) 数字影像装置 (Digital Imaging Device) 工业自动化控制器 (Industrial Automation Device) 网际网络应用装置 (Internet Appliance) 媒体应用装置 (Media Appliance) 个人数字助理 / 行动手持装置 (PDA/Mobile Handheld Device) 常驻型网关器 (Residential Gateway) 收款机装置 (Retail Point-of-Sale Device) 机上盒 (Set-Top Box) 上网板 (Web Pad) 窗口化简易终端机 (Windows Thin Client)

2 一个 Windows CE.NET 装置的操作系统是由许多单独的组件所组成, 微软更提 供超过 400 种以上的组件让 Windows CE.NET 操作系统开发者组成所需的操作 系统平台 下表依据毎种技术作分类来介绍所提供的组件 : 技术分类应用程序和服务开发支持 (Applications and Services Development) 机板支持套件 (Board Support Packages) 通讯服务和连网 (Communication Services and Networking) 核心操作系统服务 (Core OS Services) 国际化 (Internationalization) 多媒体技术 (Multimedia Technologies) 对象贮存及登录 (Object Store and Registry) 登录设定 (Registry Settings) 安全服务 (Security Services) 介殻程序及使用者接口 (Shell and User Interface) 使用者应用程序 (User Applications) 内容及说明提供 ActiveSync COM MFC XML 等技术及支持以协助开发应用程序及服务标准发展板的所有软件, 包含客制化调适层 (OAL : OEM adaptation layer) 和装置驱动程序等提供例如 networking Web servers UPNP(Universal Plug and Play) TAPI(Telephony API) 等技术, 来与其它装置通讯或交换资料 Windows CE 操作系统平台的一般性功能和提供系统核心的信息支持装置本土化 (localization), 例如中文的显示及输入提供 waveform audio Microsoft Direct3D Microsoft Windows Media 和 DVD-Video API 等技术以协助开发快速的影音应用程序提供档案系统 对象贮存 (Object Store ) 登录 (Registry) 系统等技术以协助在平台上储存资料可在登录 (Registry) 中记录硬件及软件信息提供例如 CryptoAPI SSL(Secure Sockets Layer) 和 smart card 等技术支持, 协助应用程序及资料的安全性和保密提供 GDI(graphics device interface) GWES(Graphics, Windowing, and Events Subsystem) 和输入方式等技术, 做为使用者 应用程序和操作系统三方间的接口提供标准应用程序, 例如 Microsoft Internet Explorer Windows Media player Pocket Office 和游戏等

3 本书将着重在应用程序开发部份的介绍 Windows CE 提供程序开发者标准 Win-32 API, ActiveX 控件, MSMQ (message queuing), COM(Component Object Model) 接口, ATL(Active Template Library) 和 MFC 链接库 (Microsoft Foundation Classes Library) 等熟悉的开发环境 ActiveSync 提供桌上型计算机和嵌入装置之间容易的 ( 网络 ) 联机, 不管是透过序列连结, 红外线串行端口, 或是网络电缆 Windows CE 对多媒体 ( 包括 DirectX), 通讯 (TCP/IP, SNMP, TAPI, 或者更多 ), 还有安全性提供内建的支持 种种的整合应用程序, 包括了小型的 IE 浏览器, 针对小型的 Outlook 的收信信箱的客户程序, 还有小型的容许你延伸并自订现行的系统的 Word expose 对象, 和延伸你的应用程序的功能

4 1.2 什么是 Windows CE.NET Windows CE.NET 是 Windows CE 3.0 的下一个版本, 从 2002 年推出以后, 目前已有 Windows CE.NET 4.0, Windows CE.NET 4.1, Windows CE.NET 4.2 及 Windows CE.NET 5.0 等版本 而其.NET 的意思是微软 将其近年来在桌上型 (desktop) 操作系统上主推的.NET 架构技术 (.NET Framework) 也在 Windows CE 的嵌入式操作系统上提供, 而在 Windows CE 上所推出的.NET 技术又名为.NET Compact Framework, 顾名思义, 是一个精简型的.NET Framework, 提供给系统资源有限的嵌入式系统使用.NET Compact Framework 体现了 Windows CE 装置的.NET Framework 技术, 它提供了例如个人数字助理 (PDAs) 口袋型装置(Pocket PC) 行动电话(mobile phones) 机上盒(set-top boxes) 自动控制装置和客制化嵌入装置等资源有限 (resource-constrained) 的 Windows CE 装置一个非关硬件 (hardware-independent) 的运行环境来执行各种应用程序.NET Compact Framework 是.NET Framework 类别函式 (class library) 的子集合, 它是继承了.NET Framework 的架构重新设计, 提供包括通用语言运行 (common language runtime) 和托管码运行 (managed code execution) 内存管理和资源回收(garbage collection) 等功能.NET Compact Framework 同时提供了例如 Visual Basic.NET/Visual C#.NET 等另一种开发工具语言, 并提供 XML 网页服务 (XML Web Services) Microsoft ADO.NET 资料存取 窗口窗体 (Windows Forms) 和网络通讯等应用程序开发功能

5 1.3 Windows CE.NET 与严格实时系统 (Hard real-time system) 在需要严格时间反应 (time-critical responses) 的一些高效率 (high-performance) 嵌入式应用程序中, 实时效率 (Real-time performance) 常是最重要的要求和课题 例如在工业控制中常需要快速的 I/O 机器人(robotics) 及机械等控制, 其它领域例如在电子通信切换 医学工程仪器 航空和导航仪器等需要实时反应的机置都需要一个能提供开发实时应用程序的软硬件平台 实时 (real time) 的定义 实时 (real time) 有分二种领域, 严格实时 ( 或称 硬实时,hard real time) 和轻松实时 ( 或称 软实时,soft real time) 实时 (real time) 的定义有很多种, 微软嵌入式操作系统采用 OMAC(Open, Modular, Architecture Control) 的定义如下 : 硬实时系统是一个如果没有达到它的计时需求就会失败的系统, 而软实时系统则是当它反应一些类似中断 定时器和排班等系统服务时能容忍较大计时延迟的系统 其英文全文如下 : A hard real-time system is a system that would fail if its timing requirements were not met; a soft real-time system can tolerate large latencies in the delivery of operating system services like interrupts, timers, and scheduling. 实时系统 (real time system) 范例 我们可以从以下这个箱子传送带处理系统的例子来说明 硬实时 和 软实时 的差别 这个系统使用传送带运送箱子如下图, 当箱子传感器 (box sensor) 侦测到输送带送来一个箱子时, 会发出中断 (interrupt) 通知控制器 (controller) 是否这个箱子是瑕疵品 如果送来的箱子是瑕疵品, 控制器 (controller) 将会改变在分流处 (divert area) 的传送带轨道将瑕疵品往左送 所以, 如果当箱子通过箱子传感器 (box sensor) 一直到分流处 (divert area) 之前的中间这段时间内, 如果控制器不能完成判断箱子是否是瑕疵品和做出必要的改变传送带轨道的动作, 系统将会因错过对瑕疵品的筛检而出错

6 介绍完这个箱子传送带处理系统的工作原理后, 我们来看看 硬实时 系统和 软实时 系统如何实做这个箱子传送带处理系统 如果控制器是 软实时 系统, 在箱子传感器 (box sensor) 侦测到输送带送来一个箱子而发出中断 (interrupt) 通知控制器 (controller) 的同时, 也必须激活停止点 (stop) 的开关来暂时停止箱子输送带 等到控制器收到箱子传感器送来的中断 (interrupt) 并完成判断箱子是否是瑕疵品和做出必要的改变传送带轨道的动作后, 再由控制器去关闭停止点 (stop) 的开关继续箱子输送带的输送 反观如果控制器是 硬实时 系统, 因为 硬实时 系统能够保证特定的动作能在特定的时间内完成, 也就是控制器的处理动作必须在箱子通过箱子传感器 (box sensor) 一直到分流处 (divert area) 之前的中间这段时间内完成, 所以不需要像 软实时 系统一样还需要额外的停止点 (stop) 开关装置和开关动作而让输送带停停走走的降低了处理效率 实时系统 (real-time system) 和实时操作系统 (RTOS : real-time operating system) 在此, 我们必须区分实时系统 (real-time system) 和实时操作系统 (RTOS : real-time operating system) 的差别 实时系统 (real-time system) 是由所有必要元素所组成, 包括硬件 操作系统和应用程序等 而 RTOS 只是组成实时系统 (real-time system) 的所有必要元素其中之一, 必须确保能够称职的协助整套系统达到实时的需求 Windows CE 是一个严格实时操作系统 OMAC(Open, Modular, Architecture Control) 同时研究实验了上百种的应用系统, 并发现百分之九十五的应用系统需要 1 毫秒 (one millisecond) 或比 1 ms 更长的循 环时间 (cycle times) 以处理一个事件, 并容许有循环时间百分之十的 准备误

7 差 (variation) 或 准备延迟 (latencies), 即 100 微秒 (100us) 或比 100 微秒 (100us) 更长 准备误差 (variation) 或 准备延迟 (latencies) 指得是, 当一个事件发生一直到事件开始被处理的中间这段时间 以 RS-232 串行端口 (serial port) 通讯处理为例子来说明, 当有资料送进 RS-232 串行端口时, 串行端口硬件会产生硬件中断以通知 Windows CE.NET 的系统核心 (kernel), 然后系统核心会判断中断的种类及做一些初始动作和准备动作, 最后核心会呼叫相关的中断服务执行绪 (IST)- 序列通讯驱动程序 (serial driver) 而 准备延迟 (latencies) 指的就是在串行端口硬件产生硬件中断到序列通讯中断服务执行绪 (IST) 开始处理的中间这段时间, 这段时间通常是系统核心 (kernel) 在做一些必要的判断及初始化的准备动作 OMAC 也在一台 CPU 速度 200-megahertz (MHz) 的硬件上使用 Windows CE 来做测试, 结果发现 Windows CE.NET 可以达到 : 中断服务执行绪 (IST : Interrupt Service Thread) 的准备误差或延迟不会超过 100 微秒 (100us) 可提供精细度至 1 毫秒 (one millisecond) 的定时器 (timers), 并保证最大延迟 (latency) 不会超过 100 微秒 (100us) 系统核心的排班 (scheduler), 预设为每 1 毫秒 (one millisecond) 重新排班 (rescheduling) 一次因此,OMAC 也认可了 Windows CE.NET 符合了工业定义的 硬实时 (hard real-time) 的需求 而值得一提的是 Windows XP Embedded 是一个 软实时 (soft real-time) 操作系统 Windows CE.NET 是一个硬实时操作系统 (hard real-time operating system), 是组成一套完整的硬实时系统 (hard real-time system) 的重要元素之一, 它提供了以下的功能和支持来提供应用程序开发和系统整合工程师架构完整可靠的硬实时系统 (hard real-time system) : 256 个等级的执行绪优先权 (thread priority) : 提供嵌入式系统对执行绪 (thread) 排班 (scheduling) 的控制能更弹性化 套叠的中断 (Nested interrupts) : 能先暂停优先权低的中断服务, 让优先权高的中断服务先执行 每个执行绪的执行时间量 (Per-thread quantums) : 应用程序 (application) 可以设定执行绪 (thread) 排班 (scheduling) 时所分配到的执行时间量 (quantum) 优先权改变或倒置 (Priority inversion) : 当系统资源例如 mutex 关键区 (critical section) 和信号 (semaphore) 等被优先权低的执行绪 (thread) 取得占用时, 如果同时有其它的优先权高的执行绪 (thread) 也在等待取得同样的资源才能继续执行, 这将会造成某种程度的竞争现象 (racing condition) 而严重影响系统整体的效率 因为优先权低的执行绪 (thread) 必须执行完毕后才会释放这些资源, 而此时的执行权是由优先权高的执行绪 (thread) 所取得, 但是优先权高的执行绪 (thread) 却必需取得这些资源才能继续执行,

8 这就便成了互不相让而使系统迟滞甚至于死机 为了解决这个问题, Windows CE 提供了优先权改变或倒置 (Priority inversion) 的功能, 当系统核心发现这种现象时, 会提高原本优先权低的执行绪 (thread) 的优先权, 让它高于原本优先权高的执行绪 (thread), 以取得执行权后赶快执行完赶快释放资源, 好让其它也需要这些资料的执行绪 (thread) 接着执行 在自动化控制常需要实时系统 (real time system), 而在工业领域对实时 (real-time) 的定义和需求又更严格 本章先对 Windows CE.NET 做初歩的介绍后, 便马上介绍实时系统 (real time system), 和强调 Windows CE.NET 对实时系统 (real time system) 支持的功能 本书旨在介绍如何开发 Windows CE.NET 装置或控制器的工业用自动控制应用程序, 因此必须先对 Windows CE.NET 和实时系统 (real time system 做一概述, 对于 Windows CE.NET 的核心技术 操作系统 (OS : Operating System) 的原理和实时系统 (real time system) 理论等, 并不是本书所要讨论的重点, 从下一章节开始, 讨论重点将会是在 Windows CE.NET 应用程序的开发与实做

9 第二章 :Windows CE.NET 应用程序开发简介 2.1 开发环境与工具微软 (Microsoft) 提供了两个整合的开发环境来开发 Windows CE.NET 控制器的应用程序 : Visual Studio.NET 和 embedded Visual C++ 他们都是由一套整合的窗口(windows), 工具 (tools), 选单 (menus), 工具列 (toolbars), 目录 (directories), 和其它组件所组成, 并用以帮助我们建立, 开发, 测试, 和侦错 Windows CE.NET 的应用程序 本书所介绍的是目前最新版的 Windows CE.NET 且使用的应用程序发展机器 Wincon-8000 亦是使用 Windows CE.NET 为其嵌入式操作系统 是故本书所介绍的开发工具亦是目前最新支持 Windows CE.NET 的 Embedded Visual C 和 Visual Studio.NET 2003 以往用 Embedded Visual C 和 Embedded Visual Basic 3.0 或更旧的版本所开发出来的应用程序不能在 Windows CE.NET 运行, 只能在其 Windows CE 3.0 上运行 Embedded Visual C 我们很快的先看一下以下 embedded Visual C++ IDE(Integrated Development Environment) 整合开发环境的一些画面 如果大家曾经使用过 Microsoft Visual C++ 发展应用程序, 大家将发现 embedded Visual C++ 提供一个很类似的发展环境 当然, 与 Visual C++ 不同的是 embedded Visual C++ 提供了一些独特的工具和资源以用来开发 Windows CE.NET 的应用程序 我们很快的先看一下以下 embedded Visual C++ IDE(Integrated Development Environment) 整合开发环境的一些画面

10 图 : 使用 embedded Visual C++ IDE 开启一个新的项目 图 : 使用 embedded Visual C++ IDE 编辑资源 (resources)

11 图 : 使用 embedded Visual C++ IDE 编辑程序代码,Compile, Build, and Debug 如果读者熟悉 Visual Studio 的 Visual C++, 你会发现 embedded Visual C++ 的整合开发环境 (IDE : Integrated Development Environment) 非常相似 embedded Visual C++ 包含了一些主 要功能和工具如下表 下一章我们将详细介绍如何安装和使用 embedded Visual C++ 名称 功能 Project Workspace 组织管理项目 (projects) 和项目的组件 (components) Text editor 编辑程序代码 Resource editors 设计和修改资源 (resources), 例如对话盒 (dialog boxes), 选单 (menus), 标记 (icons) 等 Compiler 编译 C 或 C++ 的原始程序代码 (source code) Windows CE device 在 PC 上仿真和测试应用程序的运行 emulators Platform Manager 在目标机器 (target device) 实地测试和运行应用程序 Debugger 侦错器 AppWizard 建立新的应用程序 Source Browser 在程序中检查和编辑函式 (functions) 和类别 (classes), 并快速得知 他们之间的关系 ClassWizard 自动维护类别的程序代码 Windows diagnostic tools 例如 Windows CE Remote Spy (Cespy.exe), 提供更多的侦错功能 Online documentation and 线上技术手册

12 help Visual Studio.NET 2003 透过 Smart Device Extensions (SDE) for Microsoft Visual Studio.NET 可让我们使用.NET 的技术在支持.NET Compact Framework 的 Windows CE.NET 控制器 ( 例如 Wincon8000 支持.NET Compact Framework) 上开发我们的应用程序 而 Windows CE.NET 所使用的语言和开发工具和在桌上型 PC 开发.NET 的应用程序是一样的, 那就是 Visual Basic.NET( 或 Visual C#) 和 Visual Studio.NET 在最新的 Microsoft Visual Studio.NET 2003 以后 ( 含 ) 的版本,SDE 已经内建整合在 Microsoft Visual Studio.NET 开发环境中, 如果你使用的是 Microsoft Visual Studio.NET 2002 年版, 你必需加装 SDE 这个套件, 而 Microsoft Visual Studio.NET 2002 年以前的版本是不支持.NET 应用程序的开发 如下图所示, 只要在你建立新项目时选择 [Smart Device Application], 接下来所使用的开发工具或开发方式, 都和开发桌上型微软操作系统 (desktop Microsoft Operating System)(ex: Windows NT/2000/XP) 的.NET 应用程序一样

13 2.2 使用 Embedded Visual C++ 开发你的应用程序安装 embedded Visual C++ 4.0: 本书所附光盘有收录 embedded Visual C 及 SP1(Service Pack 1) 或 SP2(Service Pack 2) for embedded Visual C++ 4.0, 读者亦可自行到微软的下载中心 ( 键入 embedded Visual C++ 来搜寻最新版的 embedded Visual C++ 并下载 安装需求操作系统 : Windows 2000, Windows XP, Microsoft Windows 2000 Professional SP2, Microsoft Windows 2000 Server SP2, or Microsoft Windows XP Professional 桌上型计算机使用 Pentium-II 450 MHz 以上处理器 200 MB 以上的硬盘可用空间 CD-ROM drive VGA 或 Super VGA (800 x 600 or larger) Mouse 注册码 TRT7H-KD36T-FRH8D-6QH8P-VFJHQ 激活 embedded Visual C 待你成功的安装 embedded Visual C 在你的 PC 后, 从 [ 开始 ] 菜单的 [ 程序集 ] 中可以找到 [Microsoft embedded Visual C++ 4.0] 如下图, 直接点选来执行以激活 embedded Visual C 待 embedded Visual C 成功激活后会显示画面如下图

14 接下来, 我们将介绍使用 embedded Visual C 开发应用程序的一些基本操作 管理项目 (Projects) 和工作区 (Workspaces) 使用 embedded Visual C++, 是在工作区 (Workspace) 中开发应用程序 你可以同时建立一个项目 (project) 和工作区 (workspace), 或者建立一个工作区 (Workspace), 然后再把一个或一个以上的项目加入, 接着, 就可以去开始组合 ( 汇编 ) 应用程序 建立一个工作区 (workspace) 后, 你可以在一个现存的项目和子项目中加入新的项目 新的规划设定 工作区 (workspace) 是一个发展项目的容器 当你建了一个新的平台时, 同时也建立了一个工作区 (workspace) 使用 Project view 去看并获得存取项目中不同的组件 工作区 (Workspace) 可以容纳多个项目, 包括了子项目, 但只能有一个平台 项目 (project) 是一个结构或规划和一群产生应用程序或最终二进制文件的档案 子项目 (subproject) 是依靠另一个的项目的项目 这其间的附属物可以由分享档案构成, 这些分享档案需要先被建立在这子项目中, 或者它可以包括必需要在子项目中先更新的分享资源

15 建立新项目建立项目 (project) 就是在 [File] 选单中选择 [New], 于 [Project] 页签中, 选择你想建立的应用程序类型 你可以同时建新的项目 (project) 和工作区 (workspace) 你可以建立一个空的项目或是使用 embedded Visual C++ 提供的精灵针对你的执行文件或其它的二进制文件的档案去设定框架 (framework) 你建立一个项目的同时也开启了一个工作区(workspace), 你可以选择把项目加入已存在的 workspace, 或是建立一个新的工作区 (workspace) 如下图, 同时你也必须选定你的应用程序将会在那种 CPU 的 Windows CE 装置上运行 下面的表格描述 Windows CE 应用程序精灵 应用程序精灵 (Application Wizard) WCE Application WCE ATL COM AppWizard WCE Dynamic-Link Library 项目 (Project) 类型可建立一个空的项目 标准的 Windows CE 应用程序 或是 Windows CE 典型的 " Hello World " 应用程序 建立 Windows CE 的 ATL COM 对象建立一个空的或是标准的动态链接库 (DLL: dynamic-link library) WCE MFC ActiveX Control Wizard 建立一个使用 MFC 的 Windows CE ActiveX 制控项 (control) WCE MFC AppWizard (exe) 的 Windows CE 应用程序

16 WCE MFC AppWizard (dll) 建立一个使用 MFC 的 Windows CE 动态链接库 (DLL: dynamic-link library) WCE Static Library 建立 Windows CE 静态函件库 (static library ) 当你使用精灵建立项目时, 精灵针对应用程序产生的来源和标头檔, 就如同描述每个的档案内容的 ReadMe.txt 一样 此外,MFC 和 ATL 应用程序产生一组完整的以 MFC 或 ATL 的类别为基础的的来源檔 (.C/.C++), 标头档 (.h), 和资源档 (.res) 精灵提供选项使你可以自订应用程序的来源文件 (source file) 和标头启始档 (header starter file) 例如, 假如你建立了一个 MFC 的应用程序, 你可以选择你的应用程序是否支持 MFC document/view 的架构 假如你建了一个空的 Windows CE 应用程序 一个空的动态联结函式库 或是 Windows CE 不含前置编译标头文件的静态函式库时, 应用程序精灵并不会产生标头档 (head file) 建立一个新的项目 (project) 和新的工作区 (workspace) 1. 在 [File] 选单中选择 [New] 2. 在 [Projects] 页签中选择应用程序型态 3. 于 Project name 字段中键入项目的名称 4. 在 Location 字段中, 新的项目档放在预设的磁盘驱动器和路径, 但你可以改变它 5. 点选 Create new workspace 选项 6. 于 CPUs 中, 选择将建立的项目所在的 Windows CE 装置硬件平台 7. 点选 OK 8. 在精灵中, 选择你的应用程序所须呈现的特性 完成选项后点选 Finish 9. 在 New Project Information 对话框中, 确定你新的骨干项目的规格 建立 workspace 后, 你可以加入新的或已存在的项目, 也可删除工作区 (workspace) 中已存在的项目 加入一个新项目到现存的工作区 1. 在 [File] 选单中选择 [New] 2. 在 [Projects] 页签中选择应用程序型态 3. 于 Project name 字段中键入项目的名称 4. 在 Location 字段中, 新的项目档放在预设的磁盘驱动器和路径, 但你可以改变它 5. 点选 Add to current workspace 选项 你可以选择勾选框架的相依性 6. 于 CPUs 中, 选择将建立的项目所在的 Windows CE 装置硬件平台 7. 点选 OK 8. 在精灵中, 选择你的应用程序所须呈现的特性 完成选项后点选 Finish 9. 在 New Project Information 对话框中, 确定你新的骨干项目的规格 加入一个现存新到工作区 1. 开启你想加入项目的项目工作区 2. 在项目 Project 选单中, 点选 Insert Project into Workspace,

17 3. 在 Insert Projects into Workspace 对话框中, 浏览并找出你想加入的项目所在位置, 然后点选这个项目档欲加入的工作区 (workspace) 4. 点选勾选盒的相依性并选择其它表列在相依性对话框中项目的名称 删除工作区 (workspace) 中的项目 在 FileView 页签中选择项目然后按下 DELETE 键 注意, 在删除此项目同时, 也从 其它项目移除这个子项目, 和从其它工作区移除了这个项目 删除项目的配置 1. 在 Build 选单中, 点选 Configurations 选项 2. 在配置设定对话框中, 展开项目并删除 3. 选择你要删除的配置设定, 然后点选 Remove 加入控件 (Controls) 到对话盒 (Dialog Box) 对话框边辑器在控制工具列中提供了标准的控制型态 系统预设, 当对话编辑器被开启时, 显示控制工具列如下图

18 当你在对话盒中增加或改变控件的位置, 对话框编辑器针对特别类型的控制器, 给予它标准的大小 同样地你拖曳控制器, 控件的位置以虚线被画出轮廓, 但是最后的位置可能取决于导览或边框, 或者是你是否开启布局控制格 此外你也可以使用对话盒工具列的一些命令来对控件做对齐 置中 排列和调整大小等 除了在控件工具列中预设的控件, 你可在对话框点击右键, 然后点选 Insert ActiveX Controls, 去增加一 ActiveX 控件 要改变控件的属性, 例如控件的 ID 或标题, 便是点选了控件后, 再于 View 选单中选择属性 属性包括了控件的 ID, 内文调准, 型态的设定 建置应用程序 (Building an Application) 建置应用程序涉及了前置处理器 (preprocessor), 编译器 (compiler), 和连结器 (linker): 前置处理器透过翻译宏 操作数 和指示来准备编译器的来源档 (source file) 编译器建立一个含有机器码 连结程序指示器节 外部参考和函式 / 资料名的对象文件 (object file) The linker combines code from the object files created by the compiler and from statically-linked libraries, resolves the name references, and creates an executable file. 连结器则是连结由编译器创造的对象文件和函式库以产生执行档 (.exe)

19 下面的图表显示在 embedded Visual C++ 开发应用程序的流程 : 设定连结器的选项 (Setting Linker Options): 1. 于 Project 选单中点选 Settings 2. 然后在 Settings for 中选择设定 (configuration) 3. 在项目树状图中, 于 Settings for 中点选某个项目 4. 点选 Link 页签, 显示出可选择的项目如下图 5. 为了把对象文件或标准的函式库传给连结程序, 在对象 / 函式库模块内文方块中指定档名 你能够以文件名称指定绝对或者相对的路径, 并且能够把万用字符用于文件名

20 指定档案的路径 在 embedded Visual C++ 的 [Tools] 的 [Options] 中, 可指定项目中的文件寻找路径 包括可对下 表各种型态的档案设定寻找路径表 : 型态的档案 (File type) 设定 Executable files 执行档指定公用程序位置, 例如编译器 (CL, CLARM, CLMIPS, 等等 ), NMAKE, 炼结程序, 和 BSCMAKE Include files 含括檔指定编译器应该在哪里寻找概括档 ( 例如 #include <stdio.h> ) Library files 函式库档指定连结程序应该在哪里寻找函式库以解决外部参考 Source files 来源档指定侦错器应该在哪寻找来源档, 例如 Microsoft Foundation Class Library 和微 Microsoft run-time library 在目录设定中存储了路径信息, 这允许你改变这些设定 你能够改变在这些设定中列的路径和在 embedded Visual C++ 搜寻他们的次序 Microsoft embedded Visual C++ 依他们在这些列表 ( 目录 ) 中出现的次序的搜寻资料夹如下图

21 待设定好这些常用设定后, 就可以成功的建置 (build) 出应用程序的运行文件 (.exe) 并下载到 Windows CE 装置来运行

22 2.3 使用 Visual Studio.NET 开发你的应用程序 The Visual Studio.NET Visual Studio.NET 整合发展环境已支持开发智能型装置例如掌上型计算机 个人数字助理 (PDA) 等 (smart device) 应用程序的工具 使用这些工具和.NET Compact Framework, 你能够建立 (create) 建置(build) 除错(debug) 和布署(deploy) 在个人数字助理 ( PDAs ) 行动电话 和其它资源受限装置等透过.NET Compact Framework 执行的应用程序.NET Compact Framework 是.NET Framework 的子集, 应用程序开发者可以使用相同和熟谙的 Visual Studio.NET 技术来开发 Windows CE.NET 装置的应用程序 你可以使用已获得的 Visual Studio.NET 技术 ( 以 ) 建立用于掌上型计算机 2000, 掌上型计算机 2002, 或者任何以 Windows CE.NET-based 的装置 Visual Studio.NET 2003 年版本 ( 含 ) 以后已支持应用程序开发者在 Visual Studio.NET 使用 Visual Basic.NET 和 Visual C# 程序语言来撰写应用程序 ; 而这些应用程序则必需透过.NET Compact Framework 来开发和执行 开发环境对.NET Compact Framework 支持已经由 Visual Studio.NET 来完成, 而 Windows CE.NET 装置同时也必需支持.NET Compact Framework 以提供使用使用 Visual Basic.NET 和 Visual C# 程序语言开发的应用程序可在装置上执行 Visual Studio.NET 是一个必须付费购买的开发工具, 安装在个人计算机的系统需求如下表 : 安装需求操作系统 : Windows 2000, Windows XP, Microsoft Windows 2000 Professional SP2, Microsoft Windows 2000 Server SP2, or Microsoft Windows XP Professional 桌上型计算机使用 Pentium-II 450 MHz 以上处理器 200 MB 以上的硬盘可用空间 CD-ROM drive VGA 或 Super VGA (800 x 600 or larger) Mouse Visual Studio.NET 有企业版 (Enterprise) 专业版(Professional) 教育版(Education) 和免费试用版 (Try Version) 等 教育版和免费试用版不支持开发智能型装置应用程序, 一般都使用专业版来开发智能型装置应用程序 Visual Studio.NET 的试用版可在以下网站取得 : Visual Studio.NET 的安装可参照其网站或光盘所附的安装说明按步骤安装完成 安装完成可从开始菜单的程序集中的 [Microsoft Visual Studio.NET 2003] 如下图来激活 Visual Studio.NET.NET Compact Framework

23 Microsoft Windows CE.NET 是 Windows CE 3.0 的后继版本, 它是一个小 简洁 可靠 实时和多任务的嵌入式操作系统 而 ".NET" 代表的是它实现 ( 支持 ) 了.NET 的技术 Windows CE.NET 版本的.NET 技术, 我们叫它做.NET Compact Framework 顾名思义, Compact 表示.NET Compact Framework 是.NET Framework 的简洁版 简单来说,.NET Framework 主要由二大部份所组成,Common Language Runtime(CLR) 和.NET Framework class library 它提供了一个新的应用程序运行平台, 一个高度管理的运行平台 使用者所开发的.NET Framework 应用程序在运行时是透过.NET Framework 的运行平台展现出该应用程序的功能, 并且被.NET Framework 的运行平台严格控管以降低应用程序出错而导致号系统不正常的状况 另外,.NET Framework 也大大的简化了应用程序的开发和部署 (deployment), 且开发出的应用程序是与硬件无关 (hardware independent) 而可跨机器平台 (cross machine platform) 执行的 Common Language Runtime(CLR): Common Language Runtime(CLR) 可看成是一个运行 (runtime) 或一个代理者程序 (agent), 负责管理程序代码的执行, 管理内存 (memory management), 管理执行绪 (thread management), 转换和检查型别 (type checking) 等确保系统强固, 稳定, 和安全 所以, 那些透过 Common Language Runtime(CLR) 运行的应用程序或程序代码 ( 用.NET Compact Framework 及 Visual Basic.NET 或 Visual C# 开发出的应用程序 ), 又称做 被管理的程序代码 (managed code) 反之, 那些不需透过 Common Language Runtime(CLR) 运行的应用程序或程序代码 (embedded Visual C++ 开发出的应用程序 ), 又称做 不被管理的程序代码 (unmanaged code), 或称做本机码或内驻码 (native code).net Framework class library :.NET Framework class library 是由许多对象导向 (Object-Oriented) 的类别 (class) 程序接口 (interface) 和资料型别 (type) 所组成, 以提供了以下功能让应用程序开发者使用简单并快速的开发各种应用程序 : 呈现基本资料型别 (base data types) 和例外处理 (exceptions) 封装 (Encapsulate) 数据结构 (data structures) 完成处理输出入 (Perform I/O) 存取加载型别 (loaded types) 的信息 引用.NET Framework 的保全 (security) 稽核 提供资料存取和图形使用者接口 (client-side GUI).NET Compact Framework 的类别 (Classes) 参考技术文件同样也使用.NET Framework 类别函式库的使用参考文件 要查看.NET Compact Framework 是否有支持.NET Framework 类别函式库中的某个类别或者成员 可在参考技术文件中的 "Requirements" 叙述的 Platforms 中是否有列出.NET Compact Framework 以下图为例, 在技术文件说明如何使用这个类别 (class) 时, 也会列如是否.NET Compact Framework 也有支持这个类别 (class) 但是要注意的是, 技术文件中所附的范例程序但并未在.NET Compact Framework 中试过, 有可能完全兼容可以运行, 也有可能需做小部份修改才能运行

24

25 开启新项目 正如同你在 Visual Studio 中开启任何其它新项目一样, 是透过开启新项目对话盒来开启, 然 后选择一种项目类型和样板 (template) 如下图 在 Visual Studio.NET 为智能型装置建立一个新项目的详细步骤如下 : 1. 在 Visual Studio.NET 的档案 [File] 选单中, 点选新增 [New], 然后点选项目 [Project] 后将出现开启新项目对话盒下图 : 2. Under Project Types in the New Project dialog box, click Visual Basic Projects or Visual C# Projects. 在新项目 [New Project] 的对话框中于项目类型 [Project Types] 内, 点选 Visual Basic Projects 或 Visual C# Projects 来选定要使用 Visual Basic.NET 或 Visual C#.NET 来撰写应用程序 3. 在路径位置 [Location] 中键入或使用浏览 [Browse] 来指定新项目建立相关档案的位置 4. 在名称 [Name] 中键入指定新项目名称 5. 在模板 [Templates] 中, 选定智能型装置应用程序 (Smart Device Application) 6. 点选 [OK] 进入以下画面 :

26 7. 选择目的装置 (target) 的平台 (platform), 可选择 Windows CE.NET 平台或 Pocket PC 平台 智能型装置项目中的平台是一组.NET Compact Framework 针对一大群相关装置的功能性去设计规划的 Visual studio.net 2003 支持两种平台 : Windows CE.NET 和掌上型计算机 (Pocket PC) Windows CE.NET 平台设计为一般 Windows CE.NET 项目提供功能性 而 Pocket PC 平台被设计为专门在 Pocket PC 上执行的项目提供功能性 以本书所使用的 Windows CE 装置为例, 本书所使用的泓格科技的 Wincon-8000 的应用程序开发必须选择 Windows CE.NET 平台而非掌上型计算机 (Pocket PC) 平台 8. 选择项目型别 (project type) 每一个平台都支持一些专门模板(templates) 例如, Windows CE.NET 平台提供了专用的模板来发展 Windows CE.NET 装置的窗口应用程序 (Windows Application) 类别函式库(Class Library) 无窗口的操作台应用程序 (Console application), 或者一个空白项目 (Empty Project) Windows CE 装置应用程序与桌上型个人计算机应用程序比较 Windows CE.NET 版本的.NET 技术, 我们叫它做.NET Compact Framework 顾名思义, Compact 表示.NET Compact Framework 是.NET Framework 的简洁版 因为 Windows CE device 所能提供的资源有限和一些系统的限制, 有些类别 (class) 和控件 (control) 在.NET Compact Framework 是不支持的, 如下图所示, 图中阴影部份表示.NET Compact Framework 不支持的类别

27 同理, 当我们使用 Visual Studio.NET 的开发环境开发 Visual Basic.NET 或 Visual C#.NET 的 Windows CE 装置的应用程序时有些控件 (control) 也不支持, 如下图所示 :

28 虽然开发桌上型计算机应用程序和开发都是使用 Visual Studio.NET, 但是在开发 Windows CE 装置应用程序时还是有一些需要注意的地方 例如 : Visual Studio 开发环境提供额外附加的工具来支持连接和侦错远程的 Windows CE 装置 除了当你建立一个项目时要选择一种项目类型和样板 (template) 以外, 你尚须指定一个装置来执行和侦错应用程序 装置可能是连接到开发计算机的任一个实体 Windows CE 装置 网络装置 或是一个在开发计算机上执行的装置仿真器 (device emulator) 在 [Tools] 选单中, 点选选项 [Options], 然后点选装置工具 [Device Tools] 将出现以下画面供你选择装置和侦错方式.NET Compact Framework 支持较少的类别 (classes) 或类别的成员 (members), 例 如.NET Compact Framework 不支持 Web forms remoting messaging management 和 printing 等类别, 并且通常会有不同的类别列举 (enumeration of classes) 这可以使 用 Intellisense 或 Visual Studio 的对象浏览器 (Object Browser) 来检视.NET Compact Framework 支持有限的 COM 组件的交互引用 (interoperating), 同 时.NET Compact Framework 不支持使用托管码 (managed code) 来开发 COM 组件, 也 不支持 ActiveX 控件 (controls) 的交互引用 另外例如显示 (display) 的大小 电源管理 内存限制等装置硬件的特性也必须列入开 发应用程序时的考量

29 2.4 各种开发工具的比较前二章节已经介绍过在 Windows CE 装置上开发应用程序的工具, 包含使用 embedded Visual C++ 或在 Visual Studio.NET 使用 Visual Basic.NET 或 Visual C#.NET 来开发应用程序 本章将讨论这两种开发方式的差别和优缺点 使用 embedded Visual C++ 开发出的应用程序代码又称为本机码 (native code), 而使用 Visual Studio.NET 的 Visual Basic.NET 或 Visual C#.NET 开发出来的.NET Compact Framework 应用程序代码又称为托管码 (managed code) 本机码 (native code) 本机码应用程序是使用一套特定软件平台的应用程序开发接口 (APIs) 来开发, 并且被编译成一个特定处理器 (microprocessor) 的目的码 (object code) 或机器码 (machine code) 一般情况下, 本机码提供较高的效能 (performance) 和最小的资源需求 (footprint), 但是被编译好的本机码或是执行档 (executable) 却只能在此特定软件平台和特定处理器上运行 以本书范例装置为例, 用 embedded Visual C++ 在泓格科技 Wincon-8000 上开发应用程序时, 是使用 Windows CE.NET 特定的软件平台和 Windows CE.NET 所提供的接口 (APIs) 来开发, 并编译成特定 ARM4 处理器的机器码 此外, 开发本机码应用程序常需要应用程序开发者必须自行处理类似内存管理 资源 (resource) 管理 保全性 (security) 管理等, 而这些通常必须要由经验丰富的 C++ 应用程序开发工程师来处理 托管码 (managed code) 托管码应用程序是使用一套运行环境 (run-time environment) 的应用程序开发接口 (APIs) 来开发 一般情况下, 托管码应用程序的开发会比较简单和快速, 并且可跨软件平台和处理器来运行, 所以开发出的托管码也能重新使用 (reuse) 和有较高的可移植性 (portable) 另外, 内存管理 资源 (resource) 管理 资源收集 (garbage collection) 保全性(security) 管理等琐碎的工作都由运行环境 (run-time environment) 来处理, 应用程序开发工程师不须费心处理 托管码应用程序在目标机器 (target device) 上运行时, 是透过目标机器端的实时编译器 (Just-in-Time compiler) 来实时把托管码编译成目标机器码后在目标机器上执行 以本书范例装置为例, 使用 Visual Studio.NET 的 Visual Basic.NET 或 Visual C#.NET 开发泓格科技 Wincon-8000 的应用程序时, 是使用 Visual Studio.NET 的.NET Compact Framework 这个运行环境 (run-time environment), 开发完成的托管码下载到 Wincon-8000 上执行时是透过支持.NET Compact Framework 的 Windows CE.NET 操作系统中的实时编译器 (Just-in-Time compiler) 来实时编译使用 Visual Basic.NET 或 Visual C#.NET 开发出的托管码 选择适当的开发方式来开发应用程序介绍到此, 读者应对本机码应用程序和托管码应用程序有初步了解 一般来说, 如果效能和控制是应用程序的最大需求时, 开发工程师应使用 embedded Visual C++ 开发成本机码应用程序 相反地, 若开发工程师需要的是快速开发完成的工具时,Visual Studio.NET 则

30 是最佳的选择 下表列举比较了使用标准 Win32 API 或 MFC 来开发本机码与使用.NET Compact Framework 来开发托管码的优缺点 应选择本机码或是托管码来开发应用程序, 牵 涉到的选择考量非常广泛, 包括开发工具 平台支持 应用程序需求及应用领域 开发者习惯 和共享及时效性等 API 优点缺点 Microsoft Win32 SDK (C / C++) 最简化和最快的运行档 (.exe) 和动态连结函式库 (DLL) 使用最少的内存 装置驱动程序必须用 Win32 开发 控制平台程序 (control panel applets) 必须用 Win32 开发 外观延伸 (shell extensions) 例如软件输入平板 (Software Input Panel) 使用者接口表层 (user interface skin) 软件时钟等, 必须用 Win32 开发 不需任何运行 (runtime), 直接在 Windows CE.NET 上运行 艰涩难懂的低阶 API. 资源及对象的清除释放必须由程序自行处理, 一不小心常会造成内存溢漏 (memory leaks) 程序化 (Procedure-oriented) 的 API, 没有对象导向 (object-oriented) 的观念 MFC (C++) 支持对象导向 (Object-oriented), 包括继承 (Inheritance) 封装 (Encapsulation) 多形 (Polymorphism) 和函式重载 (function overloading) 等 容器类别 (Container classes) 支持数组 (arrays) 链接 (lists) 对象对应 (object maps) 和简化的数据处理 (data handling) 型别安全 (Type safety) Embedded Visual C++ 提供完整的 MFC 原始码 强大的工具支持, 一系列的精灵 Good tool support. A set of (wizards) 来协助加入例如窗口讯息处理 (message handlers) 虚拟函式 (virtual functions) 窗体 (forms) 和类别 (classes) 等 虽然代为处理部份的资源及对象的清除释放, 但某些时候仍须由程序自行处理, 一不小心常会造成内存溢漏 (memory leaks).net Compact Framework (C# and Visual Basic.NET) 简单易懂的 API 支持对象导向 (Object-oriented), 包括继承 (Inheritance) 封装 (Encapsulation) 多形 (Polymorphism) 和函式重载 (function overloading) 等 容器类别 (Container classes) 支持数组 (arrays) 链接 (lists) 对象对应 (object maps) 简化的数据处理 (data handling) 紧凑表 (hashtables) 对照表 (dictionaries) 和堆栈 (stacks) 等 型别安全 (Type safety) 名称集 (Namespaces) 自动资源回收 (garbage collection) 以避免忆体溢漏 (memory leaks) 具可移植性 (Portable) 的机器指令集 (MSIL : Microsoft Intermediate Language / CIL : Common Intermediate Language) 提供运行档 (.exe /.dll) 迅速开发网页服务客户端 (web service clients) 频繁的托管码 (managed code) 实时编译使得执行效率最小 常需使用包装 Win32 的方式来使用 COM 对象的接口函式 未提供原始码

31 支持 XML 优异的工具支持, 例如, 整合的窗体设计师 (Integrated Forms Designer) 可自工具盒 (toolbox) 拖曳 (drag & drop) 项目 (items) 来设计窗体, 并可自动产生程序代码 结论 Win32 SDK 是操作系统所提供的应用程序开发接口 (API), 可用来开发应用程序 (applications) 驱动程序(drivers) 控制面板程序(control panel applets) 和动态连结函式库 (DLLs) 等, 并且开发出来的程序可以有最小的档案大小 也因为 Win32 SDK 是操作系统所提供的最低阶的应用程序开发接口 (API), 使用 Win32 SDK 开发应用程序需要较长的开发时间 当要开发驱动程序 (drivers) 控制面板程序(control panel applets) 低阶程序代码或实时程序代码 (real-time code) 等,Win32 SDK 是唯一的选择 至于要开发使用者应用程序 (user application) 时, 除了 Win32 SDK 外, 也可使用 MFC 或.NET Compact Framework 我们可以把一台 Windows CE 装置看成是由层次化的软件所组成, 最底层是使用 Win32 本机码所开发出的驱动程序 在这层上面是由多个类似资料解析 (data-analysis) DLL 和 COM 组件等的中介层 (middle-tier) 程序所组成, 这些可用 Win32 本机码 MFC 或 ATL 来开发 而在最上层的则是提供使用者接口和图型窗口接口的应用程序, 这些使用者应用程序除了使用 Win32 MFC 或 ATL 来开发外, 更可以使用.NET Compact Framework 来开发 Windows CE 版本的 Microsoft Foundation Classes(MFC) 是由两个 DLL 所提供, MFCCE400.DLL 和支持 OLE (COM) 的 OLECE400.DLL 简而言之,MFC 是对 Win32 APIs 的包装 (wrapper), 支持近 160 种的类别 (classes), 包装了例如窗口 (windows) 检视(views) 文件 (documents) 数组(array) 字符串(string) 插座(socket) 和 GDI 等对象 当我们安装好 embedded Visual C++ 后, 可以在 \Program Files\Windows CE Tools\wce410\STANDARDSDK_410\Mfc\Src 里找到相关的 MFC 原始程序代码 Windows CE 版本的 Microsoft Foundation Classes(MFC) 的相关使用文件可以在微软的参考技术文件中找到专门介绍 Windows CE 版本的 Microsoft Foundation Classes(MFC) 的使用方法 MFC 应用程序必须使用 embedded Visual C++ 来开发 至于.NET Compact Framework 已在上一章节作一简单介绍, 本书主要介绍如何使用 embedded Visual C++ 来开发 Windows CE 装置应用程序, 如何使用.NET Compact Framework 来开发 Windows CE 装置应用程序将在笔者的下一本书作更详尽的介绍 而在本书介绍如何使用 embedded Visual C++ 来开发 Windows CE 装置应用程序时, 又着重于介绍使用 Win32 API, 原因是 Windows CE 所提供的 MFC 与其它 Windows 操作系统 ( 例如 Windows XP/2000 等 ) 几近相同, 笔者将不再重复交待 开发 Windows CE 装置的应用程序的注意事项 本章最后比较开发 Windows CE 装置的应用程序和开发一般 desktop Windows( 例如 Windows 95/98/NT/2000/XP) 的应用程序不同之处和要注意的事项如下表 :

32 CPU Windows CE 装置的 CPU 效能通常都比 PC 来的慢, 且没有协同处理器 (co-processor), 所以应避免复杂的数学运算和图型显示屏幕 Windows CE 装置通常使用小尺寸的显示屏和较小的分辨率 (resolution), 因此应用程序的窗口大小比须配合硬件的限制输入装置 Windows CE 装置常频繁的使用触摸屏或输入笔来做为使用者输入工具, 因此应用程序的开发必须了解使用上与鼠标的差别与讯息的处理 (message handling) 资源 Windows CE 装置因资源有限, 例如内存及外围等, 所以开发应用程序时必须注意资源的取得 使用及释放 Unicode 传统使用 ASCII 字符集时只使用一个字节来表示字符, 而 Windows CE 时使用 Unicode 编码方式, 也就是使用两个字节来表示一个字符 其目的是为了让我们可以很快且很容易的制成多国语版本和快速的转换或移植应用程序 但是应用程序开发者要注意内存的使用和宣告, 因为使用 Unicode 字符集表示字符串会比使用 ASCII 字符集表示字符串多占一倍的空间 另外, 应用程序开发者要处理必要的字符集转换, 例如 Unicode 字符转换成 ASCII 字符, 或 ASCII 字符转换成 Unicode 字符 控件因为 Windows CE 资源比较少的缘故, 与 desktop Windows 相较, 有些控件 (control) 在 Windows CE 上是不支持的, 或有些控件的功能有变少窗口因为资源有限的关系,Windows CE 装置的应用程序窗口的功能会比桌上型计算机和桌上型窗口操作系统来的简单, 所以有些功能并不支持, 例如窗口不能动态地改变大小 (resize) 档案系统和目录服务 Windows CE 装置只支持 FAT32 档案系统 简化的目录服务 (light directory service) 和简单的保全 (security) 和稽核

33 开发跨平台的应用程序 (authentication) 使用跨平台 (cross platform) 的标头档 (head file) 及引用檔 (include file) 及避免使用硬件相关 (hardware dependent) 和平台相关 (platform dependent) 的程序代码可以让应用程序跨硬件平台而更有可重用性 (reusable) 除以上所强调的部份外, 使用 embedded Visual C++ 为 Windows CE 装置开发应用程序和使用 Visual Studio 的 Visual C++ 为一般 Windows(Windows 95/98/NT/2000/XP) 开发应用程序几乎都是一样 它们有很类似的整合开发环境 (IDE) 和工具 同样的窗口控制方式 同样的窗口讯息传递 同样的资源使用和建立方式 同样的控件程使用方法 同样的窗口, 甚至于同样的应用程序架构程写法 相信用过 Microsoft Visual C++ 开发应用程序的人在使用 embedded Visual C++ 为 Windows CE 装置开发应用程序时一定不觉得陌生

34 第三章 :Windows CE.NET 窗口应用程序 微软 Windows CE.NET 结合微软 Win32 应用程序设计接口 (API), 使用者接口 (UI), 和图形装置界面函式库而组成图型窗口事件子系统 (GWES: Graphics, Windowing, and Events Subsystem) 模块 (Gwes.exe). GWES 是使用者 应用程序和操作系统三者间的接口 GWES 支持所有组成 Window CE 使用者接口 (UI) 的窗口 (windows) 对话盒(dialog boxes) 控制组件(controls) 菜单(menus) 和资源 (resources) 等, 让使用者能够控制应用程序 GWES 同时也提供以数字映像 (bitmaps) 脱字符号(carets) 光标 (cursors) 原文(text) 和图标 (icons) 等形式的使用者信息给使用者

35 3.1 窗口 (Windows) 窗口是总是长方形的 彼此顺着屏幕垂面的虚线, 一前一后的摆放 这堆窗口称为 z-order( 叠置顺序 ) 每个的窗口在 z-order( 叠置顺序 ) 有唯一的位置 在 z-order( 叠置顺序 ) 上第 1 个出现的窗口被考虑在稍后出现的窗口的前面 或上面 在 z-order( 叠置顺序 ) 中的窗口位置, 影响它的呈现 ; 窗口可部份或完全地遮蔽另外的窗口, 视其位置, 尺寸, 和在 z-order( 叠置顺序 ) 的位置 窗口分成非使用者区 (nonclient area), 这范围包括了边框 (borders) 滚动条条(scroll bars) 和各种其它控制 (controls), 而使用者区 (client area) 则位在 nonclient area 里面的中央空间 在 Microsoft Windows CE.NET 操作系统中, 窗口的 nonclient 部分由窗口管理员 (window manager) 专门控制 Windows CE 并不会把处理 nonclient 范围的讯息传送到应用程序 nonclient area client area 窗口能够显示或者隐藏, 取决于它的 WS_VISIBLE 的属性是否开启或者关闭 若 WS_VISIBLE 属性为关闭, 则窗口将不显示在屏幕上 ; 若 WS_VISIBLE 属性为开启, 则窗口将可能显示在屏幕上, 也可能隐藏, 这取决于它是否被其它窗口遮蔽 透过移动另一个窗口去覆盖或者显示窗口, 不必改变 WS_VISIBLE 的格式 然而, 使用附有 ShowWindow 旗标的 ShowWindow 应用程序界面, 能使窗口显现, 而使用附有 SW_HIDE 旗标的 ShowWindow 应用程序界面, 则能隐藏窗口 每个窗口户都有一个唯一当作窗口的标识符 (identifier) 当成功建立窗口时, 会传回窗口的处理数 (handle), 接下来呼叫其它函式使用这个窗口时, 必须传入这个窗口的处理数 (handle)

36 注册窗口类别 每个窗口都是一个窗口类别 (window class) 的成员 窗口类别是为了建立窗口的模板 (template) 当开发 窗口应用程序时, 必须注册所有被使用去建立窗口的窗口类别 为了简化建立窗口的过程,Window CE 提供了一些系统定义的窗口类别 ; 因为 Window CE 自动地注册了这些类别, 你可以用它们马上新建窗 口 我们可以使用 RegisterClass 函式来注册窗口类别如下 : ATOM MyRegisterClass(HINSTANCE hinstance, LPTSTR szwindowclass) WNDCLASS wc; wc.style = CS_HREDRAW CS_VREDRAW; wc.lpfnwndproc = (WNDPROC) WndProc; wc.cbcls Extra = 0; wc.cbwndex tra = 0; wc.hinstance = hinstance; wc.hicon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_UIDEMO)); wc.hcursor = 0; wc.hbrbackgro und = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszmenunam e = 0; wc.lpszclass Name = szwindowclass; return RegisterClass(& wc); 我们先宣告和定义一个 WNDCLASS 结构来设定窗口类别的属性, 最后再呼叫 RegisterClass 函式时传 入此 WNDCLASS 结构来注册此窗口类别 WNDCLASS 结构的定义如下 : typedef struct _WNDCLASS UINT style; WNDPROC lpfnwndproc; int cbclsextra; int cbwndextra; HANDLE hinstance; HICON hicon; HCURSOR hcursor; HBRUSH hbrbackground; LPCTSTR lpszmenuname; LPCTSTR lpszclassname; WNDCLASS ; WNDCLASS 结构的成员意义如下 : style 设定窗口类别的风格 (style) 可以设定多个风格 (styles), 使用 OR ( ) 运算子 (operator) 来结合这 些风格 (styles) 以下是所有窗口类别风格(styles) 列表 : CS_DBLCLKS Value CS_GLOBALCLASS Description 当使用者在属于此窗口类别的窗口上双击鼠标时, 会将这双 击的讯息 (double-click m essage) 送给窗口的窗口处理程序 (window procedure) 允许应用程序在呼叫 CreateWindow 或 C reatewindowexallows 函式时不用特别指定 hinstance 参

37 CS_HREDRAW CS_NOCLOSE CS_PARENTDC 数的值 如果没有设定此风格, 在呼叫 CreateWindow 或 CreateWindowExAllows 函式时 hinstance 参数的值必需和呼叫 RegisterClass 函式时设定的值相同 当我们开发一个动态函式库 (DLL) 提供全域 (global) 窗口类别时, 必须设定此风格 (styles) 当窗口移动或窗口宽度改变时, 要重画整个窗口 使窗口选单的 Close 没有作用 设定子窗口的剪贴区 (clipping region) 在父窗口, 以便在父窗口绘图 CS_VREDRAW 当窗口移动或窗口高度改变时, 要重画整个窗口 lpfnwndproc 设定一个指针指向窗口处理程序 (window procedure) cbclsextra 设定窗口类别结构 (window-class structure) 的额外字节个数 cbwndextra 设定窗口例子 (window instance) 的额外字节个数 hinstance 设定窗口处理程序 (window procedure) 所在的例子 (istance) hicon Windows CE 不支持, 设为 NULL hcursor 设定光标资源 (cursor resource) 的处理数 (handle) 当设定为 NULL 时, 应用程序必须设定当鼠标移到应用程序的窗口时的形状 hbrbackground 设定背景 (background) 的笔刷资源 (brush resource) 的处理数 (handle) 可以设定一个实际存在的笔刷或是设定成一个颜色 颜色可以为以下任一种, 并呼叫 GetStockObject 函式来转成 HBRUSH 型别 COLOR_ACTIVEBORDER COLOR_HIGHLIGHTTEXT COLOR_ACTIVECAPTION COLOR_INACTIVEBORDER COLOR_APPWORKSPACE COLOR_INACTIVECAPTION COLOR_BACKGROUND COLOR_MENU COLOR_BTNFACE COLOR_MENUTEXT COLOR_BTNSHADOW COLOR_SCROLLBAR COLOR_BTNTEXT COLOR_WINDOW COLOR_CAPTIONTEXT COLOR_WINDOWFRAME COLOR_GRAYTEXT COLOR_WINDOWTEXT

38 COLOR_HIGHLIGHT 当窗口类别被结束释放时, 系统会自动结束及释放笔刷, 应用程序不用也不可自己结束或释放笔刷, 因为可能其它同类别的窗口尚在使用此笔刷 若设为 NULL, 则应用程序必须在每次需要重画背景时指定笔刷 应用程序可以使用 WM_ERASEBKGND 这个讯息来确认是否需要重画背景, 或是也可检查 BeginPaint 函式回传的 PAINTSTRUCT 结构的 ferase 成员来判定是否需要重画背景 lpszmenuname 指向一个以 NULL 结尾的字符串, 用来设定选单 (menu) 资源的名称 也可以使用 MAKEINTRESOURCE 宏 (macro) 来指定选单 (menu) 资源的标识符 如果设为 NULL, 即表示窗口没有预设的选单 (menu) lpszclassname 可以设定一个以 NULL 结尾的字符串来指定窗口类别的名字, 或是指定一个 atom 建立窗口 在多执行绪的应用程序, 任何子窗口建立执行绪之前, 主要的执行绪必须先建立主应用程序 我们可 以使用 CreateWindow 或 CreateWindowEx 函式来建立新窗口 这两个函式之间的差异仅仅是 CreateWindowEx 函式支持额外的参数,dwExStyle, 但是 CreateWindow 函式不支持 在 Windows CE, CreateWindow 函式其实是一个呼叫 CreateWindowEx 函式的宏 CreateWindowEx 函式使用方式如下 : HWND CreateWindowEx( DWORD dwexstyle, //Extended style LPCWSTR lpclassname, //Class name LPCWSTR lpwindowname, //Window name DWORD dwstyle, //Style int X, //Horizontal position int Y, //Vertical position int nwidth, //Width int nheight, //Height HWND hwndparent, //Parent window HMENU hmenu, //Menu HINSTANCE hinstance, //Instance handle LPVOID lpparam); //Creation data 以下表格解释使用 CreateWindowEx 函式建立窗口时指定的各种窗口属性 : Extended style 窗口属性 描述 使用 dwexstyle 参数来设定一个或一个以上的窗口延伸风格, 这些风格的设定选项以 WS_EX_* 为开头

39 Class name Window name Style Horizontal and vertical coordinates Width and height coordinates Parent 每个窗口都属于某个窗口类别, 除了像控件这种系统内建的窗口类别外, 其它的窗口类别都必须先注册才能建立这类别的窗口 lpclassname 参数即使用来指定建立窗口时要使用何种窗口类别做用模板 (template) 使用 lpwindowname 参数来指定窗口的本文 (text), 例如像窗口 (window) 对话盒(dialog box) 或讯息盒 (message box) 等, 本文 (text) 会显示在抬头 (title), 按钮 (button) 编辑(edit) 和 static 等的控件 (control) 则是显示在中央, 其它像 list box, combo box, 或滚动条等则不会显示 使用 dwstyle 参数设定窗口的风格 (style), 例如 WS_BORDER 设定窗口有边框 (border) 设定窗口在屏幕的位置, 包含 x 轴和 y 轴两个参数, 即窗口在屏幕的最左上角那点的坐标 使用 nwidth 和 nheight 参数设定窗口的寛和高 使用 hwndparent 参数来设定窗口的父亲 Menu Windows CE 不支持, 设为 NULL Instance handle 使用 hinstance 参数来设定建立定此窗口的应用程序的例子 (instance) Creation data 当窗口被建立时, 窗口的窗口处理程序 (window procedure) 会收到 WM_CREATE 讯息 可以使用 lpparam 参数来设定伴随 WM_CREATE 讯息所传入的资料, 通常都传入一个指向某种结构 (structure) 的指针 要注意的是, 新窗口类别的类别名称, 必须是万国码 (Unicode) 字符串 我们可以使用名为 TEXT 的宏 (macro) 来将一个字符串表达或转型成 unicode 的字符串, 或是使用 _T 宏, 例如,TEXT("classname"), 或是 T("classname") 另外, 或是在宣告字符串就宣告成长字符串 (Long string), 例如 : (L "classname"), 则此长字符串即是万国码 (Unicode) 对话盒 (Dialog Box) 和讯息盒 (Message Box) Window CE 同时也提供了一些特殊用途的窗口例如 : 使用 DialogBox 函式来建立一个 modal 的对话盒 ; 使用 EndDialog 函式结束一个对话盒 ; 使用 CreateDialog 函式建立一个 modeless 对话盒 ; 和呼叫 MessageBox 函式来建立 显示和操作一个讯息盒 (message box) 讯息盒(message box) 已有一些内建的图标 (icons) 和按钮 (buttons) 等控件 (controls), 而应用程序可以显示欲显示的讯息 (message) 和抬头 (title) MessageBox 函式提供我们一个很便利的方式来与窗口操作者互动, 或是在开发应用程序和侦错时使用来显示执行状态和变量内容

40 显示窗口系统建立窗口后, 系统不会自动地显示主窗口, 而是应用程序的 WinMain 函式使用 ShowWindow 函式显示窗口 建立窗口后, 应用程序可以使用 SetWindowText 函式改变窗口显示在抬头 (title bar) 的名称, 和使用 GetWindowTextLength 和 GetWindowText 函式来检索窗口抬头 (title bar) 的内文 透过使用 ShowWindow 或 SetWindowPos 函式, 或者使用 SetWindowLong 函式激活或关闭窗口的 WS_VISIBLE 属性, 我们可以控制窗口的能见度 (visibility) 而呼叫 IsWindowVisible 函式, 可以检查窗口是否可见 这函式检查窗口和它的祖先窗口以决定窗口是否可见 窗口可以设定为可见, 但是假如它被其它窗口遮盖, 就可能不出现在屏幕上 另外, 我们可以使用 SetForegroundWindow 函式来设定窗口到前景 (foreground) 结束窗口 (Destroying a Window) 结束窗口的同时也自动地结束其 我们可以呼叫 DestroyWindow 函式来结束窗口,DestroyWindow 函式会先发送给启始窗口, 再发给其它所有子窗口同样的 WM_DESTROY 讯息 一般来说, 窗口处理程序 (window procedure) 会在接收到 WM_DESTORY 或 WM_CLOSE 的窗口讯息 (window messages) 时呼叫 DestroyWindow 函式来结束窗口

41 3.2 窗口应用程序的主程序进入点 : WinMain WinMain 函式是窗口应用程序的程序执行进入点 (entry point), 同时也是窗口应用程序的主要执行绪 (primary thread) WinMain 会完成一些主要任务如下 : 1. 注册窗口类别 (registering a window class) : 呼叫 RegisterClass 函式来为已定义好的主窗口向窗口管理员注册 2. 建立主窗口 (create main window) : 透过呼叫 CreateWindowEx 函式来建立主窗口 3. 建立及进入讯息循环 (create and enter message loop) : 建立讯息循环, 及进入讯息循环中等待对主窗口有意义且重要的事件 (events), 并对有意义且重要的事件 (events) 作出相对应的动作

42 WinMain 不需要亲自去注册一个窗口类别或者建立主要窗口,WinMain 可以呼叫其它已定义好的子程序去完成这些任务 但是,WinMain 必须自己执行完成的一个任务便是, 建立讯息循环 (message loop) 讯息循环会从一个执行绪 (thread) 讯息队列 (message queue) 中取出 (retrieve) 讯息和发送 (dispatch) 讯息到适当窗口程序 (window procedure) 同时会针对一特定执行绪的讯息队列协调(coordinate) 讯息的传输 (transmission) 每个执行绪只能有一个讯息队列, 讯息被传给窗口时, 讯息会被放在窗口执行绪的讯息队列上, 执行绪便从讯息队列中接收并发送这个讯息 传送讯息给窗口执行绪的讯息队列有两种方式 : 贴出讯息 (posting a message) 和寄送讯息 (sending a message) 基本上,PostMessage 函式贴出讯息并立即返回, 而 SendMessage 函式会等待来自传送讯息的窗口的响应 ( 当讯息的传送端呼叫 SendMessage 的同时, 将不会立即的产生呼叫返回值, 直到接收端的执行绪完成此一讯息的处理并且回传为止 ) 下列的程序代码显示如何呼叫 PostMessage 函式和 SendMessage 函式 PostMessage(HWND hwnd, UINT Msg, WPARAM wparam, LPARAM lparam);

43 RetVal = SendMessage(HWND hwnd, UINT Msg, WPARAM wparam, LPARAM lparam); 而下列的程序代码显示如何透过注册窗口类别 建立主窗口和进入讯息循环, 去建立一个简易的 WinMain 函式 如前所言,WinMain 自己只执行完成建立讯息循环 (message loop) 的任务,WinMain 呼叫了其它已定义好的子程序 MyRegisterClass 函式和 InitInstance 函式去注册一个窗口类别和建立主要窗口 : int WINAPI WinMain( HINSTANCE hinstance, HINSTANCE hprevinstance, LPTSTR lpcmdline, int ncmdshow) MSG msg; HACCEL hacceltable; // Perform application initialization: if (!InitInstance (hinstance, ncmdshow)) return FALSE; hacceltable = LoadAccelerators(hInstance, (LPCTSTR)IDC_UIDEMO); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) if (!TranslateAccelerator(msg.hwnd, hacceltable, &msg)) TranslateMessage(&msg); DispatchMessage(&msg); return msg.wparam; // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // // COMMENTS: // // It is important to call this function so that the application // will get 'well formed' small icons associated with it. // ATOM MyRegisterClass(HINSTANCE hinstance, LPTSTR szwindowclass) WNDCLASS wc; wc.style = CS_HREDRAW CS_VREDRAW; wc.lpfnwndproc = (WNDPROC) WndProc; wc.cbclsextra = 0; wc.cbwndextra = 0; wc.hinstance = hinstance; wc.hicon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_UIDEMO));

44 wc.hcursor = 0; wc.hbrbackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszmenuname = 0; wc.lpszclassname = szwindowclass; return RegisterClass(&wc); // // FUNCTION: InitInstance(HANDLE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance(HINSTANCE hinstance, int ncmdshow) HWND hwnd; TCHAR sztitle[max_loadstring]; // The title bar text TCHAR szwindowclass[max_loadstring]; // The window class name hinst = hinstance; // Store instance handle in our global variable // Initialize global strings LoadString(hInstance, IDC_UIDEMO, szwindowclass, MAX_LOADSTRING); MyRegisterClass(hInstance, szwindowclass); LoadString(hInstance, IDS_APP_TITLE, sztitle, MAX_LOADSTRING); hwnd = CreateWindow(szWindowClass, sztitle, WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL); if (!hwnd) return FALSE; ShowWindow(hWnd, ncmdshow); UpdateWindow(hWnd); if (hwndcb) CommandBar_Show(hwndCB, TRUE); return TRUE;

45 3.3 窗口处理程序 (window procedure) : WndProc 如同其它窗口应用程序一样, 一个 Windows CE 的窗口应用程序有两个主函式, WinMain 和窗口处理程序 (window procedure) WinMain 对应用程序提供一个入口点,WndProc 函式处理窗口讯息 一般而言, 应用程序仅仅处理对它有关的讯息, 并把其它讯息传回到操作系统 除应用程序主讯息程序外, WinMain 也处理初始化和关机如上所述 Window CE 应用程序会有一个或超过一个的窗口来接收和处理讯息循环中讯息 窗口可能是可见的 (visual), 或者是不需要使用者界面, 即为非可见 (nonvisible) 每一个窗口都有一个窗口处理数 (window handle) 和窗口处理程序 (window procedure) 我们也可以使用窗口处理数 (window handle) 来呼叫任何相关的函式 WinProc 每一个窗口都必须有一个窗口处理程序 (window procedure) 或 WinProc Windows CE 会呼叫这个窗口程序来传递讯息给应用程序, 因此窗口处理程序 (window procedure) 或 WinProc 是一个回呼程序 (callback function) 在注册窗口类别时, 会指定属于这个特定的窗口类别的窗口处理程序 (window procedure) 如下 : // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // // COMMENTS: // // It is important to call this function so that the application // will get 'well formed' small icons associated with it. // ATOM MyRegisterClass(HINSTANCE hinstance, LPTSTR szwindowclass) WNDCLASS wc; wc.style = CS_HREDRAW CS_VREDRAW; wc.lpfnwndproc = (WNDPROC) WndProc; wc.cbclsextra = 0; wc.cbwndextra = 0; wc.hinstance = hinstance; wc.hicon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_UIDEMO)); wc.hcursor = 0; wc.hbrbackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszmenuname = 0; wc.lpszclassname = szwindowclass; return RegisterClass(&wc); WinProc 会控制所有传送到此窗口类别的现存窗口的所有讯息 (messages) 或事件 (events) 窗口处理程序

46 (window procedure) 会在 WinMain 的讯息循环中持续地被呼叫去处理发生在此窗口的事件, 例如鼠标 键盘 触控笔和其它控制动作等 一个基本的 WinProc, 常由一个 switch 陈述 (switch statement) 组成, 在此陈述中来处理窗口讯息或传递讯息给系统预设的 WinProc 所以一个基本的 WinProc 必须至少有 两个组成部份 : 1. 处理窗口讯息的 switch 陈述指令 2. 把讯息传送到预设窗口程序的陈述指令 下面的程序代码例子显示 WinProc 的典型执行 LRESULT CALLBACK WinProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) HDC hdc; int wmid, wmevent; // The switch statement handles messages. switch (message) case WM_CREATE: // Handle something before the window is created. break; // Handle various windows messages. case WM_DESTROY: PostQuitMessage(0); break; // The default window procedure default: return DefWindowProc(hWnd, message, wparam, lparam); return 0; 一个讯息由讯息标识符 (identifier) 和其它参数选项 (optional parameters) 所组成 在 Windows CE 中典型的 讯息标识符格式为 WM_XXXXXXX, 例如 WM_COMMAND 或 WM_CREATE 当窗口程序接收到讯 息时, 使用一个讯息标识符去检测如何处理讯息 例如 : WM_CREATE 讯息会在窗口被建立时发出并送给窗口处理程序 (window procedure) WM_DESTROY 讯息会在窗口被结束时发出并送给窗口处理程序 (window procedure) WM_PAINT 讯息会在窗口使用者区 (client area) 被改变并需重新绘制窗口外观时, 传递给窗口的处理程 序 (window procedure) 来执行绘制应用程序外观的工作 讯息参数包括了窗口处理程序 (window procedure) 常用来处理讯息的资料或资料的位置 这些讯息参数的含意和值取决于讯息标识符 讯息参数能够含有整数 位旗标 指到一个含有附加数据结构的指针 或者其它的信息 窗口必须检查讯息标识符去决定如何解译讯息参数 message 这个专用名词常被使用去表示讯息标识符或同时表示标识符和参数 系统透过讯息的资料或参数来传送讯息给窗口处理程序 (window procedure), 然后窗口处理程序 (window procedure) 针对接收到的讯息去完成一个适当动作, 包括检查讯息标识符和处理讯息时, 使用讯息参数指定的资料 如果一个窗口程序不处理讯息, 就应该向前传递讯息给预设处理程序 窗口处理程序 (window procedure) 透过呼叫 DefWindowProc 函式来达到这个动作 DefWindowProc 函式会完成一个预设动作和传回一个讯息结果 然后窗口程序必须传回

47 这个值, 作为本身的讯息结果 多数窗口处理程序 (window procedure) 只处理一些为数不多的讯息, 并把其它讯息传给 DefWindowProc 窗口处理程序(window procedure) 可以被多个不同的窗口 (windows) 分享,WinProc 中的第一个参数即是窗口的处理数 (window handle) 透过不同的窗口处理数(window handle), 窗口处理程序 (window procedure) 会作用在指定的窗口 Windows Manager Application SendMessage PostMessage Sending Message Queue User Inputs Application Application GetMessage Receiving Message Queue

48 3.4 处理讯息 (Messages Handling) Windows CE 是事件驱动的操作系统, 所有的讯息 (message) 都是使用一个名为 MSG 的结构来传递 MSG 结构含有六个字段 ( 或成员 ), 下面的表格显示这些成员 : Member Description hwnd 窗口处理数 (window handle) message 讯息代码 wparam 额外的讯息资料 lparam 额外的讯息资料 time 指定讯息被投寄的时间 pt 当讯息被投寄时, 被动作所在的屏幕位置的坐标 接收和分配讯息 (Receiving and Dispatching Messages) 我们可以使用 GetMessage 函式来接收讯息 (message) 当应用程序呼叫 GetMessage 函式来接收讯息 (message) 时,Windows CE 会到相关执行绪 (thread) 的讯息队列 (message queue) 中取得讯息并做相关处理,Window CE 会一直检查讯息队列中由 SendMessage 函式送出的讯息, 并将这些讯息从讯息队列中取出后配送给所有呼叫 GetMessage 函式在等待这些讯息的窗口处理程序 (window procedure) 这样就能保证讯息接收队列和讯息传送队列的同步 WinMain 函式的讯息循环 (message loop) 还记得我们先前讨论 WinMain 主函式时, 曾提到 WinMain 主函式必须建立一个讯息循环 (message loop) 来处理讯息, 其中即是以呼叫 GetMessage 函式开始, 此讯息循环 (message loop) 一般写成以下形式 : while (GetMessage(&msg, NULL, 0, 0)) if (!TranslateAccelerator(msg.hwnd, hacceltable, &msg)) if (hwnddlgcurrent!= NULL!IsDialogMessage(hwndDLGCurrent, &msg)) TranslateMessage(&msg); DispatchMessage(&msg);

49 在循环中,GetMessage 函式持续地接收讯息, 并且在接收到讯息时呼叫 DispatchMessage 函式来分配讯息给相关的窗口处理程序 (window procedure) 而当 GetMessage 函式接收到 WM_QUIT 讯息时会传回 0, 并使循环结束执行 另外, 在 GetMessage 函式接收到讯息后, 和呼叫 DispatchMessage 函式来分配讯息给相关的窗口处理程序 (window procedure) 前, 可能须要对接收到的讯息做一些处理动作 例如呼叫 TranslateMessage TranslateAccelerator 和 IsDialogMessage 等函式来做一些必要的处理动作, 这些函式有时候会自已处理讯息的分配 (dispatch) 而不须要再呼叫 DispatchMessage 函式 我们通常会在呼叫 DispatchMessage 函式之前来呼叫 TranslateMessage 函式,TranslateMessage 函式会确认键盘讯息 (keyboard messages) 所夹带的字符 (characters), 并且张贴 (post) 这些字符 (characters) 到讯息队列 (message queue) 以便在下一次的循环时取出 另外, 我们可以使用 TranslateAccelerator 函式来解译键盘讯息 (keyboard messages) 和产生选单命令 (menu commands), 而 IsDialogMessage 函式可以用来确保 modeless 对话盒的动作正确无误 传送讯息 (Sending Messages) 我们可以呼叫 SendMessage 函式来传送讯息到窗口 SendMessage 函式是一个同步 (synchronous) 函式, 函式的呼叫会一直等到接收此讯息的窗口的窗口处理程序 (window procedure) 处理完这个送来的讯息后 才会返回 SendMessage 函式使用方式如下 : DWORD dwretval = SendMessage(MyWindow, WM_COMMAND, MyWParam, MyLParam); 张贴讯息 (Posting Messages) 相反地,PostMessage 函式是一个异步 (asynchronous) 函式 呼叫 PostMessage 函式时,PostMessage 函 式会送出讯息到讯息队列后便马上返回, 而不会等到接收此讯息的窗口的窗口处理程序 (window procedure) 处理完这个送来的讯息后才返回 PostMessage 函式使用方式如下 : PostMessage(MyWindow, WM_COMMAND, MyWParam, MyLParam); 定义新的讯息 Windows CE 支持系统定义的讯息和应用程序定义的讯息 系统定义讯息 (System-defined messages) 的标识符 (identifiers) 的值是从 0 到 0x3ff, 而从 0x400 到 0x7fff 的值是给应用程序定义的讯息 (application-defined messages) 使用 WM_USER 这个讯息的讯息值 (message number) 是 0x400, 如果我们要定义应用程序的使用者讯息 (custom message), 可以从这个值开始如下 :

50 #define WM_MYNEWMESSAGE (WM_USER + 999) 而为了确保我们的应用程序定义的讯息不会跟其它应用程序相同而冲突, 可以呼叫 RegisterWindowMessage 函式来检查 至于系统定义的讯息又分二类, 通用窗口讯息 (general window messages) 和特殊目的讯息 (special-purpose messages) 通用窗口讯息(general window messages) 是毎种窗口都用的到的讯息, 而特 殊目的讯息 (special-purpose messages) 则是某些类型的窗口才会用到 通用窗口讯息 (general window messages) 的标识符的开头都是以 WM_ 开头, 例如 WM_CREATE 和 WM_DESTROY 等的输入装 置 键盘输入 窗口的建立和管理等的讯息 而特殊目的讯息 (special-purpose messages), 举例来说, 以 BM_ 开头的讯息则只用在按钮控件 (button controls) 这种窗口 Window CE 的讯息的种类如下表, 例如,WM_CREATE WM_DESTROY 和 WM_COMMAND 属 于 WM 的讯息种类 讯息种类 说明 BM Button message BN Button notification CB Combo box message CBN Combo box notification CDM Common dialog box message CDN Common dialog box notification CPL Control panel message DB Object store message DM Dialog box default command button message DTM Date and time picker and Hypertext Markup Language (HTML) viewer messages DTN Date and time picker notification EM Edit control message EN Edit control notification HDM Header control message HDN Header control notification IMN Input context message LB List box control message LBN List box notification LINE Line device message LVM List view message LVN List view notification

51 MCM MCN NM PBM PSM PSN RB RBN SB SBM STM STN TB TBM TBN TCM TCN TVM TVN UDM UDN WM Month calendar message Month calendar notification Messages sent by a variety of controls Progress bar message Property sheet message Property sheet notification Rebar message Rebar notification Status bar window message Scroll bar message Static bar message Static bar notification Toolbar message Trackbar message Trackbar notification Tab control message Tab control notification Tree-view message Tree-view notification Up-down control message Up-down control notification General window messages

52 3.5 使用控制组件 (Working with controls) 在应用程序执行时, 我们常需要去取得或设定对话盒或窗口中的控件的信息, 本章节将介绍一些常用的函式来达到这些功能 取得和设定对话盒中控件的显示本文 (Text) 或整数值 (Int) 我们可以呼叫 GetDlgItemText 函式来取得对话盒中控件的显示本文 (Text) 如下 : UINT GetDlgItemText( HWND hdlg, int niddlgitem, LPTSTR lpstring, int nmaxcount ); 参数 : hdlg 指定控件所在的对话盒的处理数 (handle) niddlgitem 指定控件的标识符 (identifier) lpstring 设定一个指向缓冲区的指针, 用来接收抬头 (title) 或本文 (text) nmaxcount 设定 lpstring 参数所指定的缓冲区的最大长度 传回值 : 接收到的字符数 0 表示失败, 可以呼叫 GetLastError 函式来取得错误代码 注释 : 相对地, 我们可以使用 SetDlgItemText 函式来取得对话盒中控件的显示本文 (Text) 另外, 当我们要取得和设定对话盒中控件的整数值 (Int) 时, 可以使用 GetDlgItemInt 和 SetDlgItemInt 这组函式, 最常使用的状况就是在编辑盒 (edit box) 中取得使用者输入的整数值 致能或失能一个控件 (Enable or disable a control) 我们可以呼叫 EnableWindow 函式来致能或失能一个窗口或对话盒中的控件的鼠标和键盘输入 : BOOL EnableWindow( HWND hwnd,

53 BOOL benable ); 参数 : hwnd 设定窗口的处理数 (handle) benable 设为 TRUE 时表示要致能 (enable) 此窗口,FALSE 表示要失能 (disable) 此窗口 传回值 : 0 表示此窗口之前没有被失能 (disable), 非 0 表示此窗口之前是被失能 (disable), 可以呼叫 GetLastError 函式来取得错误代码 注释 : 而当我们要将键盘输入聚焦 (focus) 在某个特定窗口或控件时, 可以呼叫 SetForcus 函式来达成 传送信息给对话盒中控件 我们可以呼叫 SendDlgItemMessage 函式来 : LONG SendDlgItemMessage( HWND hdlg, int niddlgitem, UINT Msg, WPARAM wparam, LPARAM lparam ); 参数 : hdlg 指定控件所在的对话盒的处理数 (handle) niddlgitem 指定控件的标识符 (identifier) Msg 指定要送出的讯息 wparam 指定讯息相关的额外资料 lparam 指定讯息相关的额外资料

54 传回值 : 传回讯息被处理的结果 注释 : SendDlgItemMessage 函式会一直到送出的讯息被处理后才返回 其作用如同是呼叫 SendMessage 函式送讯息给某个控件 ( 控件其实都也是一种窗口 ) 例如以下用法, 我们对某个对话盒的一个特别的编辑控件 (edit control) 送出 EM_REPLACESEL 讯息, 编辑控件 (edit control) 会接收并处理这个讯息 编辑控件 (edit control) 会读取伴随讯息而来的储存在 LPARAM 参数的字符串并显示出来 SendDlgItemMessage (gdemodlg, ID_RCVTEXT, EM_REPLACESEL, 0, (LPARAM)Buffer); 我们在上一节中有提到过, 控件是属于系统内建的窗口, 并且定义了预设的讯息, 且每个控件会处理自己相关的讯息, 而 SendDlgItemMessage 函式是一个同步函式, 会等到控件处理完讯息后才返回 以本例来说明,EM_XXX 这种以 EM_ 开头的讯息是编辑盒 (edit box) 控件这种窗口会去接收和处理的 举例来说, 编辑盒 (edit box) 控件这种窗口对 EM_REPLACESEL 讯息的处理, 原文的技术文件描述如下 : This message replaces the current selection in an edit control with the specified text. EM_REPLACESEL wparam = (WPARAM) fcanundo; lparam = (LPARAM)(LPCTSTR) lpszreplace; Parameters fcanundo Boolean value that specifies whether the replacement operation can be undone. If set to TRUE, the operation can be undone. If set to FALSE, the operation cannot be undone. lpszreplace Long pointer to a null-terminated string that contains the replacement text. Return Values None. 技术文件说明了, 编辑盒 (edit box) 控件这种窗口对 EM_REPLACESEL 讯息的处理, 和所伴随的参数的意义, 和处理结果 我们也可以使用本节提到的讯息种类分类表来了解每种控件窗口的预设处理讯息, 并配合技术文件来找到如何或应该送何种讯息给控件以使用控件 例如下图, 从技术文件的 [Application Development]->[User Interface Services]->[Reference] 一路找下去各找到每种控件预设处理讯息和用法 :

55

56 3.6 本章范例程序 本章提供一个名为 UIdemo.exe 的范例程序来说明如何开发 Window CE 窗口应用程序 此应用程序执 行画面如下 : 本范例程序有二个下拉式选单 (menu),[file] 和 [Help] 在[Help] 下拉式选单 (menu) 中有一个 [About] 选项 (menu item), 点选时会带出一个简单的对话盒 (dialog) 显示简单的应用程序信息 而 [File] 下拉式选单 (menu) 中则提供了三个选项 (menu item),[open File] [Dialog] 和 [Exit], 其功能如下 : [Open File] : 开启档案选取对话盒, 并将使用者选取的文件名称显示于使用者本文区 (client context area) 档案选取对话盒是系统提供的一个对话盒, 用来浏览和选取档案 [Dialog] : 开启范例程序建立的一个对话盒, 此对话盒会示范如何使用 List box, combo box, check box, button, image, text box, static 等控件 (controls), 并会将使用者的操作结果显示在使用者本文区 (client context area)

57 [Exit] : 结束应用程序 现在, 我们就从一刚开始应用程序是如何建立来说明, 并以此范例程序来说明 Windows CE 的 PC 仿真器 (emulator) 如何调校和使用 如下图, 一刚开始, 我们先在 embedded Visual C++ 的 [File] 下拉式选单中选取 [New] 来激活新增应用程序精灵 (Application wizard), 并选取要新增 Projects 和选择要新增的应用程序种类为 WCE Application 接下来再指定项目名称(Project name) 和项目位置 (Location) 我们的这个应用程序将会在 Windows CE 的 PC 仿真器 (emulator) 和在本书范例 Windows CE 装置 ( 使用 StrongArm CPU 的泓格科技 WinCon-8000 工业用控制器 ) 上执行, 所以在 CPUs 选取 CPU 种类时要选取 Win32 [WCE emulator] 和 Win32 [WCE ARMV4] 最后在按下[OK] 跳到下一个画面 在下一个画面中, 我们选择要建立的窗口应用程序的种型, 我们选择 [A typical Hello World! application.] 以建立一个已经有初步窗口架构的窗口应用程序

58 最后, 选取完成 [Finish], 程序精灵 (Application wizard) 便会自动产生相关档案例 如 StdAfx.cpp UIdemo.cpp UIdemo.rc resource.h UIdemo.h 等, 如下图 :

59 程序精灵 (Application wizard) 所产生出来的初步的窗口应用程序, 基本上已经可以执行了, 我们可以先将此应用程序编译 (compile) 和建立 (build) 成可以在仿真器 (emulator) 上执行, 并且在仿真器 (emulator) 上侦错 (debug) 以加速开发应用程序的时间 欲将此应用程序编译 (compile) 和建立 (build) 成可以在仿真器 (emulator) 上执行必须成以下几个动作如上图 : 1. 在选单条 (menu bar) 或在 [Build] 选单的 [Set Active Platform] 选项中选择使用 STANDARDSDK_ 在选单条 (menu bar) 或在 [Build] 选单的 [Set Active Configuration] 选项中选择 Win32 [WCE emulator] Debug 3. 在选单条 (menu bar) 或在 [Tools] 选单的 [Configure Platform Manager ] 选项中选择 STANDARDSDK_410 Emulator 最后, 再选取 [Build] 选单的 [Rebuild All], 应用程序便会成功被编译 (compile) 和建立 (build) 如上图

60 在应用程序成功被编译 (compile) 和建立 (build) 后, 会试着下载 (download) 到仿真器 (emulator) 上执行 在成功的下载 (download) 到仿真器 (emulator) 上执行前必须先完成以下几个设定如上图 : 1. 在 [Tools] 选单中点选 [Configure Platform Manager ], 便会出现 Windows CE Platform Manager Configuration 窗口如上图 2. 在 Windows CE Platform Manager Configuration 窗口中点选 STANDARDSDK_410 Emulator 并按下 [Properties ] 键 此时会再出现 Device Properites 窗口如上图 3. 在 Device Properties 窗口中指定 [Transport:] 为 TCP)/IP Transport for Windows CE, 并指定 [Start Server] 为 Emulator Startup Server 4. 在 Device Properties 窗口中指定 [Transport:] 和 [Start Server] 后, 按下相对应的 [Configure ] 键, 便出现 TCP/IP Transport Configuration 窗口和 Emulation Configuration Settings 窗口如上图

61 5. 在 TCP/IP Transport Configuration 窗口和 Emulation Configuration Settings 窗口设定相关选项如上图 最后, 再执行 [Build] 选单的 [Rebuild All], 应用程序便会成功被编译 (compile) 和建立 (build), 并且 Windows CE 仿真器 (emulator) 会被自动激活如下, 而应用程序也会被自动下载到 Windows CE 仿真器 (emulator) 中 如上图, 我们点选 Windows CE 仿真器 (emulator) 的 My Computer, 便会找到下载到 Windows CE 仿真 器 (emulator) 中的 UIdemo.exe 应用程序如下图

62 我们双击 (double click) UIdemo.exe 这个图标 (icon) 来执行这个窗口应用程序如下图

63 到目前为止, 我们都尚未对这个程序精灵 (Application wizard) 所产生出来的初步的窗口应用程序做任何 修改或加功能 接下来我们将结束 Windows CE 仿真器 (emulator), 并回到 embedded Visual C++ 中, 继 续对这个程序加上其它功能

64 如上图所示, 我们使用资源编辑器 (resource editor) 对此应用程序新增一个对话盒 (dialog), 并在对话盒 (dialog) 中加入一些控件 (controls) 同时我们也加入了一些必须的程序代码在此应用程序, 本章最后将 列如完整的程序代码

65 接着, 我们再执行 [Build] 选单的 [Rebuild All] 来重新编译 (compile) 和建立 (build) 应用程序, 并且下载到 Windows CE 仿真器 (emulator) 中执行和侦错, 一直到程序功能正常执行如上图

66 一旦我们在 Windows CE 仿真器 (emulator) 中侦错和执行我们的窗口应用程序无误后, 接下来我们将再执行 [Build] 选单的 [Rebuild All] 来重新编译 (compile) 和建立 (build) 应用程序, 以使此应用程序可以在 Windows CE 装置中执行 我们必须执行以下动作如上图, 以重新编译 (compile) 和建立 (build) 可以在 Windows CE 装置中执行的应用程序 : 1. 在选单条 (menu bar) 或在 [Build] 选单的 [Set Active Platform] 选项中选择使用 SA_IA SA_IA 是泓格科技针对 WinCon-8000 工业用控制器提供的 SDK 2. 在选单条 (menu bar) 或在 [Build] 选单的 [Set Active Configuration] 选项中选择 Win32 [WCE ARMV] Release 3. 最后, 执行 [Build] 选单的 [Rebuild All], 此窗口应用程序的执行文件将产生在指定的位置如下图 4. 如果我们需要在 Windows CE 装置中执行侦错, 则如前所述, 可以在 [Tools] 选单的 [Configure Platform Manager ] 选项中去设定侦错时要去连结和下载应用程序的 Windows CE 装置如上图 我们将会在第五章再详细讨论如何在 Windows CE 装置中侦错我们开发的应用程序 同时, 我们也会在第五章介绍如何使用 ActiveSync 来下载应用程序和资料到 Windows CE 装置

67 除了使用使用 ActiveSync 来下载应用程序和资料到 Windows CE 装置外, 我们尚可使用 FTP 或使用档案共享的方式来执行应用程序和下载资料到 Windows CE 装置 如上图, 我们可以将我们所建立 (build) 出要在 Windows CE 装置执行的应用程序的所在目录透过档案分享的方式分享出来如上图, 这样我们就可以从 Windows CE 装置连结到 PC 的这个分享出来的目录来执行此应用程序或复制资料

68 例如, 我的 PC 的名称为 vincent_ibm, 我们所建立 (build) 出要在 Windows CE 装置执行的应用程序的 所在目录分享出来的名称为 UIdemo 如上图 当我们成功连结到此 PC 和找到 UIdemo.exe 后便可双击 (double click) UIdemo.exe 这个图标 (icon) 来执行这个窗口应用程序如下图

69 以下就是本章应用程序范例的完整程序代码 : // UIdemo.cpp : Defines the entry point for the application. // #include "stdafx.h" #include "UIdemo.h" #include <commctrl.h> // Vincent added code here #include <Commdlg.h> #define MAX_LOADSTRING 100 // Global Variables: HINSTANCE hinst; // The current instance HWND hwndcb; // The command bar handle // Forward declarations of functions included in this code module: ATOM MyRegisterClass (HINSTANCE, LPTSTR); BOOL InitInstance (HINSTANCE, int); LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);

70 LRESULT CALLBACK About (HWND, UINT, WPARAM, LPARAM); // Vincent added code here typedef struct TCHAR *pszlabel; DWORD wnotification; NOTELABELS; LRESULT CALLBACK DialogWndProc (HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK ModelessDlgProc(HWND hdlg, UINT message, WPARAM wparam, LPARAM lparam); void Print(TCHAR *pformat,...); void MenuCommandOpen(HWND hwnd); HWND ghwndmain; NOTELABELS nlcombo[] = TEXT ("CBN_ERRSPACE "), (-1), TEXT("CBN_SELCHANGE "), 1, TEXT("CBN_DBLCLK "), 2, TEXT("CBN_SETFOCUS "), 3, TEXT ("CBN_KILLFOCUS "), 4, TEXT("CBN_EDITCHANGE "), 5, TEXT("CBN_EDITUPDATE "), 6, TEXT("CBN_DROPDOWN "), 7, TEXT("CBN_CLOSEUP "), 8, TEXT("CBN_SELENDOK "), 9, TEXT ("CBN_SELENDCANCEL"), 10, ; int WINAPI WinMain( MSG msg; HACCEL hacceltable; HINSTANCE hinstance, HINSTANCE hprevinstance, LPTSTR lpcmdline, int ncmdshow) // Perform application initialization: if (!InitInstance (hinstance, ncmdshow)) return FALSE; hacceltable = LoadAccelerators(hInstance, (LPCTSTR)IDC_UIDEMO); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) if (!TranslateAccelerator(msg.hwnd, hacceltable, &msg)) TranslateMessage(&msg); DispatchMessage(&msg); return msg.wparam; //

71 // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // // COMMENTS: // // It is important to call this function so that the application // will get 'well formed' small icons associated with it. // ATOM MyRegisterClass(HINSTANCE hinstance, LPTSTR szwindowclass) WNDCLASS wc; wc.style = CS_HREDRAW CS_VREDRAW; wc.lpfnwndproc = (WNDPROC) WndProc; wc.cbclsextra = 0; wc.cbwndextra = 0; wc.hinstance = hinstance; wc.hicon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_UIDEMO)); wc.hcursor = 0; wc.hbrbackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszmenuname = 0; wc.lpszclassname = szwindowclass; return RegisterClass(&wc); // // FUNCTION: InitInstance(HANDLE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance(HINSTANCE hinstance, int ncmdshow) HWND hwnd; TCHAR sztitle[max_loadstring]; // The title bar text TCHAR szwindowclass[max_loadstring]; // The window class name hinst = hinstance; // Store instance handle in our global variable // Initialize global strings LoadString(hInstance, IDC_UIDEMO, szwindowclass, MAX_LOADSTRING); MyRegisterClass(hInstance, szwindowclass); LoadString(hInstance, IDS_APP_TITLE, sztitle, MAX_LOADSTRING); hwnd = CreateWindow(szWindowClass, sztitle, WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL); if (!hwnd) return FALSE; ShowWindow(hWnd, ncmdshow); UpdateWindow(hWnd);

72 if (hwndcb) CommandBar_Show(hwndCB, TRUE); return TRUE; // // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) // // PURPOSE: Processes messages for the main window. // // WM_COMMAND - process the application menu // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return // // LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) HDC hdc; int wmid, wmevent; PAINTSTRUCT ps; TCHAR szhello[max_loadstring]; INT nheight=0; LPCREATESTRUCT lpcs; INT i; HWND hwndchild; ghwndmain = hwnd; switch (message) case WM_COMMAND: wmid = LOWORD(wParam); wmevent = HIWORD(wParam); // Parse the menu selections: switch (wmid) case IDM_HELP_ABOUT: Print(TEXT("AboutBox is launched")); DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hwnd, (DLGPROC)About); Print(TEXT("About Box is Closed")); break; case IDM_FILE_EXIT: DestroyWindow(hWnd); break; // Vincent added code here case IDM_OPEN: Print(TEXT("GetFileDialog is Launched")); MenuCommandOpen(hWnd); break; case IDM_DIALOG: Print(TEXT("Main dialog is launched")); DialogBox(hInst, (LPCTSTR)IDD_DIALOG, hwnd, (DLGPROC)DialogWndProc); Print(TEXT("Main dialog is Closed")); break; default: return DefWindowProc(hWnd, message, wparam, lparam);

73 break; case WM_CREATE: hwndcb = CommandBar_Create(hInst, hwnd, 1); CommandBar_InsertMenubar(hwndCB, hinst, IDM_MENU, 0); CommandBar_AddAdornments(hwndCB, 0, 0); // Vincent added code here nheight = CommandBar_Height (hwndcb); lpcs = (LPCREATESTRUCT) lparam; hwndchild = CreateWindowEx (0, TEXT ("listbox"), TEXT (""), WS_VISIBLE WS_CHILD WS_VSCROLL LBS_USETABSTOPS LBS_NOINTEGRALHEIGHT, 0, nheight, lpcs->cx, lpcs->cy - nheight, hwnd, (HMENU)IDC_RPTLIST, lpcs->hinstance, NULL); // Destroy frame if window not created. if (!IsWindow (hwndchild)) DestroyWindow (hwnd); return 0; // Initialize tab stops for display list box. i = 8; SendMessage (hwndchild, LB_SETTABSTOPS, 1, (LPARAM)&i); break; case WM_PAINT: RECT rt; hdc = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rt); LoadString(hInst, IDS_HELLO, szhello, MAX_LOADSTRING); DrawText(hdc, szhello, _tcslen(szhello), &rt, DT_SINGLELINE DT_VCENTER DT_CENTER); EndPaint(hWnd, &ps); break; case WM_DESTROY: CommandBar_Destroy(hwndCB); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wparam, lparam); return 0; // Mesage handler for the About box. LRESULT CALLBACK About(HWND hdlg, UINT message, WPARAM wparam, LPARAM lparam) RECT rt, rt1; int DlgWidth, DlgHeight; // dialog width and height in pixel units int NewPosX, NewPosY; switch (message) case WM_INITDIALOG: // trying to center the About dialog if (GetWindowRect(hDlg, &rt1)) GetClientRect(GetParent(hDlg), &rt); DlgWidth = rt1.right - rt1.left; DlgHeight = rt1.bottom - rt1.top ; NewPosX = (rt.right - rt.left - DlgWidth)/2; NewPosY = (rt.bottom - rt.top - DlgHeight)/2;

74 // if the About box is larger than the physical screen if (NewPosX < 0) NewPosX = 0; if (NewPosY < 0) NewPosY = 0; SetWindowPos(hDlg, 0, NewPosX, NewPosY, 0, 0, SWP_NOZORDER SWP_NOSIZE); return TRUE; case WM_COMMAND: if ((LOWORD(wParam) == IDOK) (LOWORD(wParam) == IDCANCEL)) EndDialog(hDlg, LOWORD(wParam)); return TRUE; break; return FALSE; //================================================================================ ======== //=============================== Vincent added code here ================================ //================================================================================ ======== //=============================== Vincent added code here for callback functions ================================ LRESULT CALLBACK DialogWndProc(HWND hdlg, UINT message, WPARAM wparam, LPARAM lparam) RECT rt, rt1; int DlgWidth, DlgHeight; // dialog width and height in pixel units int NewPosX, NewPosY; TCHAR tchbuffer[128]; switch (message) case WM_INITDIALOG: // trying to center the About dialog if (GetWindowRect(hDlg, &rt1)) GetClientRect(GetParent(hDlg), &rt); DlgWidth = rt1.right - rt1.left; DlgHeight = rt1.bottom - rt1.top ; NewPosX = (rt.right - rt.left - DlgWidth)/2; NewPosY = (rt.bottom - rt.top - DlgHeight)/2; // if the About box is larger than the physical screen if (NewPosX < 0) NewPosX = 0; if (NewPosY < 0) NewPosY = 0; SetWindowPos(hDlg, 0, NewPosX, NewPosY, 0, 0, SWP_NOZORDER SWP_NOSIZE); SendDlgItemMessage(hDlg, IDC_COMBO1, CB_INSERTSTRING, 0, (LPARAM)(LPCSTR)TEXT("Item 3")); SendDlgItemMessage(hDlg, IDC_COMBO1, CB_INSERTSTRING, 0, (LPARAM)(LPCSTR)TEXT("Item 2")); SendDlgItemMessage(hDlg, IDC_COMBO1, CB_INSERTSTRING, 0, (LPARAM)(LPCSTR)TEXT("Item 1"));

75 SendDlgItemMessage(hDlg, IDC_COMBO1, CB_INSERTSTRING, 0, (LPARAM)(LPCSTR)TEXT("Item 0")); SendDlgItemMessage(hDlg, IDC_COMBO1, CB_SETCURSEL, 0, 0); return TRUE; case WM_COMMAND: if ((LOWORD(wParam) == IDOK) (LOWORD(wParam) == IDCANCEL)) EndDialog(hDlg, LOWORD(wParam)); return TRUE; else if (LOWORD(wParam) == IDC_BUTTON1) Print(TEXT("Button [Read] is pressed")); GetDlgItemText(hDlg, IDC_EDIT1, tchbuffer, sizeof(tchbuffer)); SetDlgItemText(hDlg, IDC_STATIC1, tchbuffer); Print(TEXT("Contents of [Edit box] : %s"), tchbuffer); else if (LOWORD(wParam) == IDC_COMBO1) for (int i = 0; i < dim(nlcombo); i++) if(hiword (wparam) == nlcombo[i].wnotification) Print(TEXT("Notification : %s, Combo box [Item %d] is selected"), nlcombo[i].pszlabel, SendDlgItemMessage (hdlg, IDC_COMBO1, CB_GETCURSEL, 0, 0) ); break; else if (LOWORD(wParam) == IDC_RADIO1) if (HIWORD (wparam) == BN_CLICKED) Print(TEXT("Radio 1")); else if (LOWORD(wParam) == IDC_RADIO2) if (HIWORD (wparam) == BN_CLICKED) Print(TEXT("Radio 2")); else if (LOWORD(wParam) == IDC_RADIO3) if (HIWORD (wparam) == BN_CLICKED) Print(TEXT("Radio 3")); else if (LOWORD(wParam) == IDC_CHECK1) if (HIWORD (wparam) == BN_CLICKED) if (IsDlgButtonChecked(hDlg, IDC_CHECK1) == BST_CHECKED) Print(TEXT("Check1 is checked")); else Print(TEXT("Check1 is unchecked")); else if (LOWORD(wParam) == IDC_CHECK2) if (HIWORD (wparam) == BN_CLICKED) if (IsDlgButtonChecked(hDlg, IDC_CHECK2) == BST_CHECKED) Print(TEXT("Check2 is checked")); else

76 Print(TEXT("Check2 is unchecked")); else if (LOWORD(wParam) == IDC_CHECK3) if (HIWORD (wparam) == BN_CLICKED) if (IsDlgButtonChecked(hDlg, IDC_CHECK3) == BST_CHECKED) Print(TEXT("Check3 is checked")); else Print(TEXT("Check3 is unchecked")); else break; return FALSE; //=============================== Vincent added code here for local functions ================================ void Print(TCHAR *pformat,...) va_list ArgList; TCHAR Buffer[256]; INT i=0; va_start (ArgList, pformat); (void)wvsprintf (Buffer, pformat, ArgList); i = SendDlgItemMessage(gHwndMain, IDC_RPTLIST, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR)Buffer); if (i!= LB_ERR) SendDlgItemMessage (ghwndmain, IDC_RPTLIST, LB_SETTOPINDEX, i, 0); va_end(arglist); void MenuCommandOpen(HWND hwnd) OPENFILENAME of; TCHAR szfilename [100] = 0; const LPTSTR pszopenfilter = TEXT ("All Documents (*.*)\0*.*\0\0"); INT rc; szfilename[0] = '\0'; memset (&of, 0, sizeof (of)); // Initialize filename. // Initialize File Open structure. of.lstructsize = sizeof (of); of.hwndowner = hwnd; of.lpstrfile = szfilename; of.nmaxfile = 100; of.lpstrfilter = pszopenfilter; of.flags = 0; rc = GetOpenFileName (&of); Print(TEXT("GetOpenFileName returned: %x, filename: %s"), rc, szfilename);

77 第四章 :Windows CE.NET 应用程序开发进阶 在上一章我们介绍了如何使用 Win32 SDK 来为 Windows CE 装置建立一个基本的窗口应用程序, 并从基本的窗口应用程序来介绍了如何开发使用者接口 本章将更进一步介绍如何在 Windows CE.NET 应用程序增加其它功能, 例如, 在应用程序中处理序列通讯 在应用程序中加入 Windows Socket 通讯功能 在应用程序中使用 建立和存取案系统或登录信息 以及如何开发多执行绪的应用程序等 4.1 序列通讯应用程序 序列读写 (Serial I/O) 通常是所有 Windows CE 装置都支持的最基本的通讯功能, 透过序列通讯接口 (Serial Communication Interface),Windows CE 装置可轻易的连接到个人计算机 打印机 机械 机台 控制器 modems 或 GPS (Global Positioning System) 等其它装置以控制这些装置或与这些装置沟通 序列通讯应用程序开发接口

78 应用程序和串行端口的沟通, 主要是透过序列装置驱动程序 (serial device driver) 如上图所示, 序列装置驱动程序是属于资料流装置驱动程序 (Stream Interface Driver) 的一种, 而应用程序主要是透过操作系统的档案系统的应用程序开发接口 (File System API) 来存取序列装置驱动程序以传送 / 接收资料和控制序列装置 Windows CE 支持大部份的 Windows XP 所提供的序列通讯函式 (functions) 的结构 (structures), 如下表所示 : Function Description CreateFile 开启一个序列通讯端口 GetCommState 读取一个序列通讯端口的状态并将读取结果填入装置控制区块 (device-control block) 结构 -> DCB structure SetCommState 根据 DCB 结构的内容来设定调校一个序列通讯端口 这个功能会重设硬件的设定但不会清空读写队列 (I/O queues) GetCommTimeouts 读取一个序列通讯端口的读写逾时参数 (time-out parameters) SetCommTimeouts 设定一个序列通讯端口的读写逾时参数 (time-out parameters) WriteFile 写入数据到一个序列通讯端口 ReadFile 从一个序列通讯端口读取数据 SetCommMask 设定一个通讯装置的事件屏蔽 (event mask) 以等待事件的事生 GetCommMask 读取一个通讯装置的事件屏蔽 (event mask) WaitCommEvent 等待一个通讯装置的事件的发生 WaitCommEvent 所等待的事件是在事件屏蔽 (event mask) 中设定

79 EscapeCommFunction ClearCommBreak ClearCommError 设定一个通讯装置去完成一个延伸功能 通常用来将一个序列通讯端口变成红外线模式 (IR mode) 对一个通讯装置重新存入字符转换 (character transmission), 并置入转换行 (transmission line) 在非中断状态 (non-break state) 读取一个通讯装置的错误讯息和目前状态 本章节范例程序 : 本章节的范例程序是一个简单的点对点聊天程序 如下图所示, 使用者先指定所要使用的 CO M 埠, 和设定 COM 端口的传输速率后, 接着点选 [OPEN] 来开启 COM 埠 待 COM 埠开启成功后, 使用者便可以开始收送资料如下图 使用者可以在画面中间的编辑盒 (Edit Box) 输入欲送出的资料, 然后点选 [Send] 便可将资料透过 COM 端口送出 画面最底下的编辑盒 (Edit Box) 则只是用来显示操作日志 (log), 包括 COM 埠开启状态 从 COM 端口受到的资料和从 COM 端口成功送出的资料

80 使用者可以透过一条序列线 (RS232 serial cable) 连接 Wincon-8000 的 COM2 到 PC 或另一台 Wincon-8000 的 COM2 或者, 最简单的, 只要把 Wincon-8000 的 COM2 的标准 RS 针接头的第 2 脚针和第 3 脚针串接在一起 (short) 这样在同一台 Wincon-8000 的发送端和接收端就串接在一起, 自己是发送端也是接收端 我们将透过本范例来说明如何撰写序列通讯应用程序 而序列通讯应用程序通常需处理或实做 (implement) 以下事项 : 开启序列通讯端口 设定序列通讯端口 设定逾时参数 (time-out parameters) 写入通讯端口 读取通讯端口 使用通讯事件 (Communication Event) 关闭序列通讯端口 开启序列通讯端口 呼叫 CreateFile 函式去开启串行端口 因为硬件制造商和装置驱动程序的研发者会赋予串行端口名称, 每个应用程序会列出可使用的串行端口, 让使用者设定将埠开启 如果这个埠并不存在,CreateFile 将会传回 ERROR_FILE_NOT_FOUND 的讯息, 而且使用者会被通知这

81 个埠不存在 开启序列通讯端口的歩骤如下 : 1. 在参数 lpzportname 传入指定的串行端口名称, 例如 : COM2: 2. 将参数 dwsharemode 设为零, 表示不允许这个 COM 端口被其它应用程序同时开启 3. 将参数 dwcreationdisposition 设为 OPEN_EXISTING 4. 将参数 dwflagsandattributes 设为 0,Windows CE 只支持非交错式输出入 (nonoverlapped I/O) 开启串行端口的范例程序代码 : // 开启串行端口 hport = CreateFile (lpszportname, // 指定 PORT 名称 GENERIC_RE AD GENERIC_WRITE, // 存取模式 (read-write) 0, // 共享模式 NULL, // 安全属性 OPEN_EXISTING, // 开启方式 0, // Port 属性 NON-OVERLAPPED NULL ); // Handle to port with attribute to copy 设定序列通讯端口序列通讯程序设计中最关键的步骤就是以 DCB 结构设定好序列通讯端口,DCB 结构初始化错误是个常见的问题 当序列通讯函式无法产生预期的结果时, 可能是 DCB 结构有错 呼叫 Create File 函式开启一个有预设串行端口设定值的串行端口时, 通常应用程序必须改变串行端口的默认值 使用 GetC ommstate 函式去取得默认值, 再使用 SetCommState 函式去完成新的设定值的设定 另外, 串行端口的设定需要使用 COMMTIMEOUTS 结构去设定读写操作时的逾时值 当发生逾时,ReadFile 或 WriteFile 函式会传回已成功传输的字符个数 设定序列通讯端口的歩骤如下 : 1. 初始化 DCB 结构的 DCBlength 字段以设定整个数据结构的大小 将这个字段以变量型态传递给任何函式前, 必须先初始化 2. 呼叫 GetCommS tate 函式, 并传入呼叫 CreateFile 函式成功后的回传值 (hport), 以查询透过 CreateFi le 函式开启的串行端口的默认值 3. 修改欲修改的 DCB 结构的字段 以下的表格显示 DCB 结构各字段的意义 :

82 成员定义及说明呼叫 GetCommState 函式前, 设定这个字段为整个数据结构的大 DCBlength 小 忽略这个步骤会导致失败或传回错误的资料 Specifies the device communication rate. Assigns an actual baud rate BaudRate or an index by specifying a CBR_ constant. 设定装置的传输速率 指定一个实际的传输速率或是透过一个 CBR_ 常数来设定 设定二进制模式是否被允许 微软的 Win32 应用程序接口并未支 fbinary 持非二进制的模式传输, 所以这个字段一定要设为 TRUE 设定是否激活同位检查 (parity checking) 若此字段设为 TRUE, fparity 将激活同位检查和错误回报 foutxctsflow 开启或关闭 CTS 信息流控制以使用 RTS/CTS 信息流控制 开启或关闭 DSR 信息流控制 DSR 资料流控制很少被使用 使 foutxdsrflow 用 DTR 线路控制典型的串行端口输出时, 这个字段要设为 FALSE 设定 DTR 资料流控制 设定 DTR_CONTROL_ENABLE 时表示连结时激活 DTR 线路 设定 DTR_CONTROL_HANDSHAKE 时 fdtrcontrol 表示要求序列驱动程序根据接收缓冲区目前的资料量自动控制切换 设定 DTR_CONTROL_DISABLE 时表示从此关闭 DTR 线路 Specifies if the communications driver is sensitive to the state of the DSR signal. If this member is TRUE, the driver ignores any bytes fdsrsensitivity received, unless the DSR modem input line is high. 如果这个栏为被设成 TRUE, 驱动程序会忽略所有传入的资料, 除非 DSR modem 输入讯号处于激活状态 设定当输入缓冲区已满和驱动程序已送出 XoffChar 时传输是否 ftxcontinueonxoff 继续 foutx 设定传输时的 XON/XOFF 流量控制 finx 设定接收时的 XON/XOFF 流量控制设定当接收到字节发生同位错误 (parity error) 时是否要使用 ferrorchar ErrorChar 所指定的字符来取代 fnull 设定是否要避开 NULL 字符设定 RTS 流量控制的开关 有 RTS_CONTROL_ENABLE frtscontrol RTS_CONTROL_HANDSHAKE RTS_CONTROL_DISABLE 及 RTS_CONTROL_TOGGLE 等设定选项 fabortonerror 设定当有错误发生时, 读或写的动作是否继续 fdummy2 未使用, 设为 0

83 wreserved 未使用, 设为 0 XonLim 设定队列 (queue) 允许的最大字节个数 XoffLim 设定在未设定 XOFF 之前输入缓冲区所能接收的最大字节个数 ByteSize 设定传送与接收时每个字节的位数 Parity 设定同位检查方式, 一般都设为 NOPARITY. StopBits 设定停止位数, 最常的设定是 ONESTOPBIT XonChar 设定传送与接收时 XON 字符的值 XoffChar 设定传送与接收时 XOFF 字符的值 ErrorChar 设定当接收到字节发生同位错误 (parity error) 时要用什么字符来取代 EofChar 设定代表资料结束的字符 EvtChar 设定代表事件的字符 WReserved1 未使用, 设为 呼叫 SetCommState 函式去设定新的串行端口的设定值 设定序列通讯端口的范例程序代码 : // 宣告一个 DCB 结构 DCB PortDCB; // 初始化 DCBlength PortDCB.DCB length = siz eof (DCB); // 取得预设的 port 设定 GetCommStat e (hport, &PortDCB); // 改变 DCB 结构的各个设定 PortDCB.BaudRate = 9600; // 传输速率 PortDCB.fBinary = TRUE; // Binary mode; no EOF check PortDCB.fParity = TRUE; // 激活同位检查 parity checking PortDCB.fOutxCtsFlow = FALSE ; // No CTS output flow control PortDCB.fOutxDsrFlow = FALSE ; // No DSR output flow control PortDCB.fDtrCont rol = DTR_CONTROL_ENABLE; // DTR flow control type PortDCB.fDsrSensitivity = FA LSE; // DSR sensitivity PortDCB.fTXContinueOnXoff = T RUE; // XOFF continues Tx PortDCB.fOutX = FALSE; // No XON/XOFF out flow control

84 PortDCB.fInX = FALSE; PortDCB.fErrorChar = FALSE; // No XON/XOFF in flow control // Disable error replacement PortDC B.fNull= FALSE; // Disable null stripping PortDCB.fRtsControl = RTS_CONTROL_ENABLE; PortDCB.fAbortOnError = FALSE; // RTS flow control // Do not abort reads/writes on // error PortDCB.ByteSize = 8; // Number of bits/byte, 4-8 PortDCB.Parity = NOPARITY; // 0-4=no,odd,even,mark,space PortDCB.StopBits = ONESTOPBIT; // 0,1,2 = 1, 1.5, 2 // 便用 DCB 结构的内容来调校 port 的设定 if (!SetCommState (hport, &PortDCB)) // 设定失败 dwerror = GetLastError (); MessageBox (hmainwnd, TEXT("Unable to configure the serial port"), return FALSE; TEXT("Error"), MB_OK) ; 设定逾时参数 (time-out parameters) 应用程序必须使用 COMMTIMEOU TS 结构来设定串行端口的逾时值 如果没有使用这个结构设定逾时值, 就会使用这个串行端口驱动程序的预设逾时值或是之前使用过这个串行端口的通讯应用程序设定的逾时值做为串行端口的逾时值 当对这个串行端口的读写操作逾时, ReadFile 和 WriteFile 函式在动作完成后并不会传回错误值, 但可透过验证实际完成传输的字节数目是否小于被要求的数目, 以判定是否发生逾时 譬如, 假如 ReadFile 函式传回 TRUE 表示动作成功完成, 但是如果完成读取的字节个数较少于呼叫 ReadFile 函式时被要求完成的字节个数, 这就表示读取动作发生逾时 应用程序可以使用 COMMTIMEOUTS 结构来设定读取串行端口时的逾时值和写入串行端口时的逾时值, 这通常都是透过指定一个 常数 (constant) 和一个 乘数 (multiplier) 来计算出一个读取串行端口动作或写入串行端口动作的逾时值 例如, 当我们呼叫 WriteFile 函式来传送 N 个字节, 并设定 常数 (constant) 为 C 和 乘数 (multiplier) 为 M, 则其逾时值为 C + N * M 毫秒, 当 WriteFile 函式执行时间超过这个计算出来的逾时值, 则 WriteFile 函式会无条件完成并返回, 应用程序可透过验证实际完成传输的字节数目是否小于被要求的数目, 以判定是否发生逾时 同理, 使用 ReadFile 函式接收串行端口资料时, 也可以对此串行端口设定另一组 常数 (constant) 和 乘

85 数 (multiplier) 来设定接收动作的逾时值 除此之外, 使用 ReadFile 函式接收串行端口资料时, 还可以设定另一种逾时值, 那就是在接收一个一个的字符时设定收到下一个字符所可以容忍等待的最大时间 如果等待的毫秒 (milliseconds) 时间超过这个设定值, 则视为逾时 同样地, 当这种逾时产生时,ReadFile 函式的读取动作会结束并回传目前已接收到的资料和个数, 应用程序可以透过验证实际完成接收的字节数目是否小于被要求的数目, 以判定是否发生逾时 因此,ReadFile 函式的读取动作可以设定两种逾时, 全部接收动作完成可容忍的最大时间 (C + N * M 毫秒 ), 和收到下一个字符所可以容忍等待的最大时间, 这两个逾时只要有任何一个先产生逾时,ReadFile 函式的读取动作就回完成并返回 设定逾时值的歩骤如下 : 1. 初始化 COMMITEMEOUTS 结构, 并呼叫 GetCommTimeouts 函式以取得串行端口驱动程序的预设逾时值或是之前使用过这个串行端口的通讯应用程序设定过的逾时值 2. 设定 COMMITEMEOUTS 结构的成员, 包括 ReadIntervalTimeout ReadTotalTimeoutMultiplier ReadTotalTimeoutConstant WriteTotalTimeoutMultiplier 和 WriteTotalTimeoutConstant 成员 ReadIntervalTimeout ReadTotalTimeoutMultiplier ReadTotalTimeoutConstant WriteTotalTimeoutMultiplier WriteTotalTimeoutConstant 定义及说明以毫秒 (milliseconds) 计算, 在呼叫 ReadFile 函式接收串行端口的资料时, 用来设定收到下一个字符所可以容忍等待的最大时间 如果等待的毫秒 (milliseconds) 时间超过这个设定值, 则视为逾时,ReadFile 函式的读取动作会结束并回传目前已接收到的资料和个数, 应用程序可以透过验证实际完成传输的字节数目是否小于被要求的数目, 以判定是否发生逾时 若对这成员填入 0, 表示不使用这个逾时功能,ReadFile 函式的读取动作会一直等到所有要接收的字符都收到了才完成并返回 以毫秒 (milliseconds) 计算, 用来设定接收串行端口资料时的逾时值的乘数 (M) 以毫秒 (milliseconds) 计算, 用来设定接收串行端口资料时的逾时值的常数 (C) 当 ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都设定为 0 时, 表示不使用这个逾时功能,ReadFile 函式的读取动作会一直等到所有要接收的字符都收到了才完成并返回 以毫秒 (milliseconds) 计算, 用来设定写入串行端口资料时的逾时值的乘数 (M) 以毫秒 (milliseconds) 计算, 用来设定写入串行端口资料时的逾时值的常数 (C) 当 WriteTotalTimeoutMultiplier 和

86 WriteTotalTimeoutConstant 都设定为 0 时, 表示不使用 这个逾时功能,WriteFile 函式的写入动作会一直等到所 有要写入的字符都写入了才完成并返回 3. 呼叫 SetCommTimeouts 函式来设定串行端口的逾时值 在多任务 (multitasking) 的环境下, 通常都将 COMMTIMEOUT 结构的 ReadIntervalTimeout 成员设为 MAXWORD, 并将 ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 设为 0, 以使得 ReadFile 读取串行端口的动作能在读取到所有欲读取的字符数后马上返回 设定逾时值的范例程序代码 : // 宣告一个 COMMTIMEOUTS 结构 COMMTIMEOUTS CommTimeouts; // 串行端口驱动程序的预设逾时值或是之前使用过这个串行端口的通讯应用程序设定过的逾时值 GetCommTimeouts (hport, &CommTimeouts); // 变更 COMMTIMEOUTS 结构的设定值 CommTimeouts.ReadIntervalTimeout = MAXDWORD; CommTimeouts.ReadTotalTimeoutMultiplier = 0; CommTimeouts.ReadTotalTimeoutConstant = 0; CommTimeouts.WriteTotalTimeoutMultiplier = 10; CommTimeouts.WriteTotalTimeoutConstant = 1000; // 使用 COMMTIMEOUTS 结构的设定值来设定读写动作的 time-out if (!SetCommTimeouts (hport, &CommTimeouts)) // 设定失败 MessageBox (hmainwnd, TEXT("Unable to set the time-out parameters"), TEXT("Error"), MB_OK); dwerror = GetLastError (); return FALSE; 写入通讯端口 WriteFile 函式透过序列通讯端口连接到另外的装置来传输资料 呼叫这功能之前, 应用程序必须打开和设定串行端口 因为 Windo w CE 的序列通讯不支持交错式输出入 (overlapped I/O) 应用程序的主要执行绪或任何建立窗口的执行绪不应写进大量的资料到一串行端口,

87 否则, 这些执行绪会因被阻塞 (blocked) 来等待输出入完成, 而无法管理其它相关的讯息队列 所以, 应用程序应透过建立多执行绪 (multiple thread) 去处理交错 I/O, 控制读写操作 为了调节执行绪, 应用程序可呼叫 WaitCommEvent 函式去阻塞执行绪, 直到特定的通讯事件发生 本节稍后将介绍更多关于通讯事件的信息, 而有关多执行绪 (multi-thread) 应用程序的撰写方式, 将在本章的另一节介绍 而所谓的非交错式输出入 (non-overlapped I/O), 简单来说, 就是当一个应用程序对一个通讯装置执行读写动作时, 在通讯装置未完成整个读写动作前, 同一个应用程序或其它应用程序不能再对此通讯装置执行读写动作, 而应用程序也必须在那里等待并确认读写动作完成 这也就是为什么要建议使用多执行绪 (multi-thread) 来另外建立一个执行绪 (thread) 来等待并确认读写动作完成, 而不能让应用程序的主要执行绪或任何建立窗口的执行绪来等待并确认读写动作完成, 否则应用程序会发生严重的迟疑或阻碍 (blocked) 写入序列通讯端口的歩骤如下 : 1. 以 hfile 参数传递串行端口控制数 (handle) 给 WriteFile 函式, 而这个传入的 hfile 串行端口控制数 (handle) 参数即是成功呼叫 CreateFile 函式后传回的 2. 使用 lpbuffer 参数填入一个指向欲写入资料的指针 (pointer), 欲写入的资料通常是二进制数据或是字符数组 3. 使用 nnumberofbytestowrit 参数填入欲写入数据的字节个数 4. 使用 lpnumberofbyteswritten 参数填入一个整数指针 (pointer), 当 WriteFile 函式完成返回时, 会将实际写入串行端口的字节个数写入这个指针所指的变量 应用程序可以比较这个变量值和 nnumberofbytestowrit 的值以判定是否发生逾时 5. lpoverlapped 必须填入零, 因为 Window CE 不支持交错式 I/O(overlapped I/O) 写入序列通讯端口的范例程序代码 : DWORD dwerror, dwnumbyteswritten; WriteFile (hport, // Port handle &Byte, // 指向欲写入的资料 1, // 欲写入数据的字节个数 &dwnu mbyteswritten, // 实际写入的字节个数 NUL L // NULLfor Windows CE ); 读取通讯端口

88 应用程序呼叫 Re adfile 函式去接收从其它末端的序列连接装置来的资料 ReadFile 使用和 Wr itefile 函式相同的参数 一般来说, 读取动作是独立的执行绪, 也就是, 此执行绪总是准备处理到达序列通讯端口的数据 当通讯事件发生时会发信号通知在等候读取序列通讯端口资料的读取执行绪, 然后读取执行绪完成读取动作后继续等候下一个通讯事件的发生 一般状况下, 应用程序等候 EV_RXCHAR 事作, 然后在非常短的逾时值内读取序列通讯端口的数据 读取序列通讯端口的歩骤如下 : 1. 以 hfile 参数传递串行端口控制数 (handle) 给 ReadFile 函式, 而这个传入的 hfile 串行端口控制数 (handle) 参数即是成功呼叫 CreateFile 函式后传回的 2. 使用 lpbuffer 参数填入一个指向存放资料所在的指针 (pointer) 3. 使用 nnumberofbytestowrit 参数填入欲读取数据的字节个数 4. 使用 lpnumberofbyteswritten 参数填入一个整数指针 (pointer), 当 ReadFile 函式完成返回时, 会将实际自串行端口读取到的字节个数写入这个指针所指的变量 应用程序可以比较这个变量值和 nnumberofbytestowrit 的值以判定是否发生逾时 5. lpoverlapped 必须填入零, 因为 Window CE 不支持交错式 I/O(overlapped I/O) 读取序列通讯端口的范例程序代码 : BYTE Byte; DWORD dwbytestransferred; ReadFile (hport, // Port handle &Byte, // 指向读取的资料存放之处 1, // 欲读取的字节个数 &dwbytestransferred, // 实际读取的字节个数 NULL // Must be NULL for Windows CE ); 使用通讯事件 (Communication Event) 通讯事件是有重要的事件发生时, 由 Window CE 发送通知到应用程序 使用 WaitCommEvent 函式, 应用程序能使某个执行绪暂停, 直到特定事件发生 在继续处理动作之前, 需呼叫 SetCommMask 函式指定等待发生的事件 多于 1 个事件被指定时, 只要任何一个单一的被指定事件出现时, 都会造成 WaitCommEvent 返回 这机制能够使应用程序发现何时资料到达序列通讯端口 藉由等候资料到达的通讯事件, 可避免在等待资料到达的应

89 用程序在资料尚未完全到达时抢先行动去呼叫 ReadFile 函式而破坏这序列通讯端口, 进而保证只有在资料完备时, ReadFile 函式才被呼叫 下列表格列出, 应用程序能使用 WaitCommEvent 函式的通讯事件 事件 定义及说明 EV_BREAK 侦测到输入中断 EV_CTS CTS 讯号状态改变 EV_DSR DSR 讯号状态改变 EV_ERR 侦测到线路 - 状态错误 (Line-status errors) 发生 线路 - 状态错误有 CE_FRAME, CE_OVERRUN, 和 CE_RXPARITY EV_RING 侦测到环形指示器 (ring indicator) EV_RLSD RLSD(receive-line-signal-detect) 状态改变 EV_RXCHAR 接收到一个字符, 而且放置在输入的缓冲存储器 EV_RXFLAG 收到事件字符 (event character), 而且放置在输入的缓冲存储器 EV_TXEMPTY 传输缓冲区中已无资料 使用通讯事件 (Communication Event): 1. 呼叫 SetCommMask 函式来设定欲等待的事件 2. 呼叫 WaitCommEvent 函式进入等待 当一应用程序指定多个欲等待事件时, 这 lpevtmask 参数会指到一个储存引起 WaitCommEvent 返回的事件的变量 3. 再次呼叫 SetCommMask 设定欲等待的事件 4. SetCommMask 通常是应用程序在监控串行端口和读取资料的回圈中, 最先被呼叫的 使用通讯事件的范例程序代码 : BYTE Byte; DWORD dwbytestransferred; // 设定欲等待以下事件的发生 : EV_RXCHAR EV_CTS EV_DSR EV_RLSD EV_RING SetCommMask (hport, EV_RXCH AR EV_ CTS EV_DSR EV_RLSD EV_RING); while (hport!= INVALID_ HANDLE_VALU E) // 其中一个等待的事件发生 WaitCommEvent (hport, &dwcommmodemstatus, 0);

90 // 重新设定欲等待的事件 SetCommMask (hport, EV_RXCHAR EV_CTS EV_DSR EV_RING); if (dwcommmodemstatus & EV_RXCHAR) // 循环等待资料 do // 从串行端口读取资料 ReadFile (hport, &Byte, 1, &dwbytestransferred, 0); // 处理读取到的资料 while (dwbytestransferred == 1); 关闭序列通讯端口当应用程序使用序列通讯端口完毕时, 呼叫 CloseHandle 函式去关闭序列通讯端口 CloseHandle 需传入一参数, 即是呼叫 CreateFile 函式开启序列通讯端口的回传的控制数 (handle) CloseHandle 被呼叫之后, 在串行端口通讯被关闭和资源释放之前, 会有 2 秒的延迟 SerialComm.cpp 以下为本章节范例程序的程序原始码, 本应用程序是使用 Win32 SDK 撰写, 主要由 3 个重要功能所组成 InitCommunication 函式负责初始化和开启串行端口, 而串行端口的资料读取和资料写人则由两支执行绪来负责, 分别是 ReadThread 和 SendThread

91 // SerialComm.cpp : Defines the entry point for the application. // #include "stdafx.h" #include "SerialComm.h" #include <commctrl.h> #define MAX_LOADSTRING 100 // Global Variables: HINSTANCE hinst; // The current instance HWND hwndcb; // The command bar handle // Forward declarations of functions included in this code module: ATOM MyRegisterClass (HINSTANCE, LPTSTR); BOOL InitInstance (HINSTANCE, int); LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About (HWN D, UINT, WPARAM, LPARAM); // Vincent added #define TEXTSIZE 256 LRESULT CALLBACK DoDemo (HWND, UINT, WPARAM, LPARAM); HANDLE InitCommunication (HWND hwnd, LPTSTR pszdevname, INT nspeed); DWORD ReadThread (PVOID parg); DWORD SendThread (PVOID parg); HANDLE hcomport = INVALID_HANDLE_VALUE; HANDLE g_hsendevent = INVALID_HANDLE_VALUE; HANDLE hreadthread = INVALID_HANDLE_VALUE; BOOL fcontinue = TRUE; int TermInstance (HINSTANCE hinstance, int ndefrc); int WINAPI WinMain( MSG msg; HACCEL hacceltable; HINSTANCE hinstance, HINSTANCE hprevinstance, LPTSTR lpcmdline, int ncmdshow) // Perform application initialization: if (!InitInstance (hinstance, ncmdshow)) return FALSE; hacceltable = LoadAccelerators(hInstance, (LPCTSTR)IDC_SERIALCOMM); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) if (!TranslateAccelerator(msg.hwnd, hacceltable, &msg)) TranslateMessage(&msg); DispatchMessage(&msg); return TermInstance (hinstance, msg.wparam);

92 // return msg.wparam; // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // // COMME NTS: // // It is important to call this function so that the application // will get 'well formed' small icons associated with it. // ATOM MyRegisterClass(HINSTANCE hinstance, LPTSTR szwindowclass) WNDCLASS wc; wc.style = CS_HREDRAW CS_VREDRAW; wc.lpfnwndproc = (WNDPROC) WndProc; wc.cbclsextra = 0; wc.cbwndextra = 0; wc.hinstance = hinstance; wc.hicon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SERIALCOMM)); wc.hcursor = 0; wc.hbrbackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszmenuname = 0; wc.lpszclassname = szwindowclass; return RegisterClass(&wc); // // FUNCTION: InitInstance(HANDLE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance(HINSTANCE hinstance, int ncmdshow) HWND hwnd; TCHAR sztitle[max_loadstring]; // The title bar text TCHAR szwindowclass[max_loadstring]; // The window class name hinst = hinstance; // Store instance handle in our global variable // Initialize global strings LoadString(hInstance, IDC_SERIALCOMM, szwindowclass, MAX_LOADSTRING); MyRegisterClass(hInstance, szwindowclass); g_hsendevent = CreateEvent (NULL, FALSE, FALSE, NULL); LoadString(hInstance, IDS_APP_TITLE, sztitle, MAX_LOADSTRING); hwnd = CreateWindow(szWindowClass, sztitle, WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, NULL); if (!hwnd)

93 return FALSE; ShowWindow(hWnd, ncmdshow); UpdateWindow(hWnd); if (hwndcb) CommandBar_Show(hwndCB, TRUE); return TRUE; // // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) // // PURPOSE: Processes messages for the main window. // // WM_COMMAND - process the application menu // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return // // LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) HDC hdc; int wmid, wmevent; PAINTSTRUCT ps; TCHAR szhello[max_loadstring]; switch (message) case WM_COMMAND: wmid = LOWORD(wParam); wmevent = HIWORD(wParam); // Parse the menu selections: switch (wmid) case IDM_HELP_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hwnd, (DLGPROC)About); break; case IDM_FILE_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wparam, lparam); break; case WM_CREATE: hwndcb = CommandBar_Create(hInst, hwnd, 1); CommandBar_InsertMenubar(hwndCB, hinst, IDM_MENU, 0); CommandBar_AddAdornments(hwndCB, 0, 0); // Vincent added DialogBox(hInst, (LPCTSTR)IDD_DIALOG_DEMO, hwnd, (DLGPROC)DoDemo); DestroyWindow(hWnd); break; case WM_PAINT: RECT rt;

94 hdc = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rt); LoadString(hInst, IDS_HELLO, szhello, MAX_LOADSTRING); DrawText(hdc, szhello, _tcslen(szhello), &rt, DT_SINGLELINE DT_VCENTER DT_CENTER); EndPaint(hWnd, &ps); break; case WM_DESTROY: CommandBar_Destroy(hwndCB); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wparam, lparam); return 0; // Mesage handler for the About box. LRESU LT CALLBACK About(HWND hdlg, UINT message, WPARAM wparam, LPARAM lparam) RECT rt, rt1; int DlgWidth, DlgHeight; // dialog width and height in pixel units int NewPosX, NewPosY; switch (message) case WM_INITDIALOG: // trying to center the About dialog if (GetWindowRect(hDlg, &rt1)) GetClientRect(GetParent(hDlg), &rt); DlgWidth = rt1.right - rt1.left; DlgHeight = rt1.bottom - rt1.top ; NewPosX = (rt.right - rt.left - DlgWidth)/2; NewPosY = (rt.bottom - rt.top - DlgHeight)/2; // if the About box is larger than the physical screen if (NewPosX < 0) NewPosX = 0; if (NewPosY < 0) NewPosY = 0; SetWindowPos(hDlg, 0, NewPosX, NewPosY, 0, 0, SWP_NOZORDER SWP_NOSIZE); return TRUE; case WM_COMMAND: if ((LOWORD(wParam) == IDOK) (LOWORD(wParam) == IDCANCEL)) EndDialog(hDlg, LOWORD(wParam)); return TRUE; break; return FALSE; // ================================ // Vincent added all codes below // ================================ LRESULT CALLBACK DoDemo(HWND hdlg, UINT message, WPARAM wparam, LPARAM lparam) RECT rt, rt1;

95 int DlgWidth, DlgHeight; // dialog width and height in pixel units int NewPosX, NewPosY; INT nspeed; TCHAR szdev[32]; HANDLE hthread; DWORD rc; switch (message) case WM_INITDIALOG: // trying to center the About dialog if (GetWindowRect(hDlg, &rt1)) GetClientRect(GetParent(hDlg), &rt); DlgWidth = rt1.right - rt1.left; DlgHeight = rt1.bottom - rt1.top ; NewPosX = (rt.right - rt.left - DlgWidth)/2; NewPosY = (rt.bottom - rt.top - DlgHeight)/2; // if the About box is larger than the physical screen if (NewPosX < 0) NewPosX = 0; if (NewPosY < 0) NewPosY = 0; SetWindowPos(hDlg, 0, NewPosX, NewPosY, 0, 0, SWP_NOZORDER SWP_NOSIZE); SendDlgItemMessage(hDlg, IDC_COMBO_COM_PORT, CB_INSERTSTRING, -1, (LPARAM)(LPCSTR)TEXT("COM1:")); SendDlgItemMessage(hDlg, IDC_COMBO_COM_PORT, CB_INSERTSTRING, -1, (LPARAM)(LPCSTR)TEXT("COM2:")); SendDlgItemMessage(hDlg, IDC_COMBO_COM_PORT, CB_INSERTSTRING, -1, (LPARAM)(LPCSTR)TEXT("COM3:")); SendDlgItemMessage(hDlg, IDC_COMBO_COM_PORT, CB_SETCURSEL, 1, 0); SendDlgItemMessage(hDlg, IDC_COMBO_BAUD_RATE, CB_INSERTSTRING, -1, (LPARAM)(LPCSTR)TEXT("9600")); SendDlgItemMessage(hDlg, IDC_COMBO_BAUD_RATE, CB_INSERTSTRING, -1, (LPARAM)(LPCSTR)TEXT("19200")); SendDlgItemMessage(hDlg, IDC_COMBO_BAUD_RATE, CB_INSERTSTRING, -1, (LPARAM)(LPCSTR)TEXT("38400")); SendDlgItemMessage(hDlg, IDC_COMBO_BAUD_RATE, CB_INSERTSTRING, -1, (LPARAM)(LPCSTR)TEXT("115200")); SendDlgItemMessage(hDlg, IDC_COMBO_BAUD_RATE, CB_SETCURSEL, 1, 0); hthread = CreateThread (NULL, 0, SendThread, hdlg, 0, &rc); if (hthread) CloseHandle (hthread); else EndDialog(hDlg, LOWORD(wParam)); return TRUE; case WM_COMMAND: if ((LOWORD(wParam) == IDOK) (LOWORD(wParam) == IDCANCEL)) EndDialog(hDlg, LOWORD(wParam)); return TRUE;

96 else if (LOWORD(wParam) == IDC_BUTTON_OPEN) switch (SendDlgItemMessage (hdlg, IDC_COMBO_BAUD_RATE, CB_GETCURSEL, 0, 0)) case 0 : nspeed = CBR_9600; break; case 1 : nspeed = CBR_19200; break; case 2 : nspeed = CBR_38400; break; case 3 : nspeed = CBR_115200; break; default : break; switch (SendDlgItemMessage (hdlg, IDC_COMBO_COM_PORT, CB_GETCURSEL, 0, 0)) case 0 : SendDlgItemMessage(hDlg, IDC_COMBO_COM_PORT, CB_GETLBTEXT, 0, (LPARAM)szDev); break; case 1 : SendDlgItemMessage(hDlg, IDC_COMBO_COM_PORT, CB_GETLBTEXT, 1, (LPARAM)szDev); break; case 2 : SendDlgItemMessage(hDlg, IDC_COMBO_COM_PORT, CB_GETLBTEXT, 2, (LPARAM)szDev); break; default : break; if (InitCommunication(hDlg, szdev, nspeed) == INVALID_HANDLE_VALUE) MessageBox(hDlg, TEXT("Open COM port failed"), TEXT("Open COM port"), MB_OK); else if (LOWORD(wParam) == ID_SENDBTN) SetEvent (g_hsendevent); SetFocus (GetDlgItem (hdlg, ID_SENDTEXT)); else break; return FALSE; HANDLE InitCommunication (HWND hwnd, LPTSTR pszdevname, INT nspeed) DCB dcb; TCHAR szdbg[128]; COMMTIMEOUTS cto;

97 HANDLE hlocal; DWORD dwtstat; hlocal = hcomport; hcomport = INVALID_HANDLE_VALUE; if (hlocal!= INVALID_HANDLE_VALUE) CloseHandle (hlocal); // This causes WaitCommEvent to return. // The com port name is the last 5 characters of the string. hloca l = CreateFile (pszdevname, GENERIC_READ GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hlocal!= INVALID_HANDLE_VALUE) // Configure port. GetCommState (hlocal, &dcb); dcb.baudrate = nspeed; dcb.fparity = FALSE; dcb.fnull = FALSE; dcb.stopbits = ONESTOPBIT; dcb.parity = NOPARITY; dcb.bytesize = 8; SetCommState (hlocal, &dcb); // Set the timeouts. Set infinite read timeout. cto.readintervaltimeout = 0; cto.readtotaltimeoutmultiplier = 0; cto.readtotaltimeoutconstant = 0; cto.writetotaltimeoutmultiplier = 0; cto.writetotaltimeoutconstant = 0; SetCommTimeouts (hlocal, &cto); wsprintf (szdbg, TEXT ("Port %s opened\r\n"), pszdevname); SendDlgItemMessage (hwnd, ID_RCVTEXT, EM_REPLACESEL, 0, (LPARAM)szDbg); // Start read thread if not already started. hcomport = hlocal; if (!GetExitCodeThread (hreadthread, &dwtstat) (dwtstat!= STILL_ACTIVE)) hreadthread = CreateThread (NULL, 0, ReadThread, hwnd, 0, &dwtstat); if (hreadthread) CloseHandle (hreadthread); else wsprintf (szdbg, TEXT ("Couldn\'t open port %s. rc=%d\r\n"), pszdevname, GetLastError()); SendDlgItemMessage (hwnd, ID_RCVTEXT, EM_REPLACESEL, 0, (LPARAM)szDbg); return hcomport; DWORD SendThread (PVOID parg) HWND hwnd, hwndstext; DWORD cbytes, rc; TCHAR sztext[textsize], szoutputtext[textsize]; hwnd = (HWND)pArg;

98 hwndstext = GetDlgItem (hwnd, ID_SENDTEXT); while (1) rc = WaitForSingleObject (g_hsendevent, INFINITE); if (rc == WAIT_OBJECT_0) if (!fcontinue) break; // Disable send button while sending EnableWindow (GetDlgItem (hwnd, ID_SENDBTN), FALSE); GetWindowText (hwndstext, sztext, dim(sztext)); lstrcat (sztext, TEXT ("\r\n")); rc = WriteFile (hcomport, sztext, lstrlen (sztext)*sizeof (TCHAR), &cbytes, 0); if (rc) // Copy sent text to output window -> recieve text box wsprintf(szoutputtext, TEXT("Send -> ")); lstrcat (szoutputtext, sztext); SendDlgItemMessage (hwnd, ID_RCVTEXT, EM_REPLACESEL, 0, (LPARAM)szOutputText); SetWindowText (hwndstext, TEXT ("")); // Clear send text box else // Else, print error message wsprintf (sztext, TEXT ("Send failed rc=%d\r\n"), GetLastError()); EnableWindow (GetDlgItem (hwnd, ID_SENDBTN), TRUE); else MessageBox(hWnd, TEXT("Time out"), TEXT("SendThread"), MB_OK); break; return 0; //====================================================================== // ReadThread - Receives characters from the serial port // DWORD ReadThread (PVOID parg) HWND hwnd; int i; DWORD cbytes; BYTE sztext[textsize], *pptr; TCHAR tch, szoutputtext[textsize]; hwnd = (HWND)pArg; while (fcontinue) tch = 0; pptr = sztext; for (i = 0; i < sizeof (sztext)-sizeof (TCHAR); i++) while (!ReadFile (hcomport, pptr, 1, &cbytes, 0)) if (hcomport == INVALID_HANDLE_VALUE)

99 return 0; // This syncs the proper byte order for Unicode. tch = (tch << 8) & 0xff00; tch = *pptr++; if (tch == TEXT ('\n')) break; *pptr++ = 0; // Avoid alignment problems by addressing as bytes. *pptr++ = 0; // If out of byte sync, move bytes down one. if (i% 2) pptr = sztext; while (*pptr *(pptr+1)) *pptr = *(pptr+1); pptr++; *pptr = 0; wsprintf(szoutputtext, TEXT("Recieved <- ")); lstrcat (szoutputtext, (unsigned short *)sztext); SendDlgItemMessage (hwnd, ID_RCVTEXT, EM_REPLACESEL, 0, (LPARAM)szOutputText); return 0; int TermInstance (HINSTANCE hinstance, int ndefrc) HANDLE hport = hcomport; fcontinue = FALSE; hcomport = INVALID_HANDLE_VALUE; if (hport!= INVALID_HANDLE_VALUE) CloseHandle (hport); if (g_hsendevent!= INVALID_HANDLE_VALUE) PulseEvent (g_hsendevent); Sleep(100); CloseHandle (g_hsendevent); return ndefrc;

100 4.2 WinSock 通讯应用程序 本章节将概论描述微软 Windows CE.NET 操作系统所提供的 TCP/IP 层次, 并配合实际样板程序详述如何使用 Windows CE.NET 操作系统所提供的 TCP/IP 层次来撰写通讯应用程序 Windows CE.NET 操作系统所提供的 TCP/IP 层次是遵循并兼容于微软桌上型操作系统, 即是赫赫有名的 Windows Socket, 简称 WinSock WinSock 是参照国际标准组织 ( 简称 ISO : International Organization for Standardization) 的 OSI(Open System Interface) 七层架构标准实做出来的, 用以处理第三层及第四层的动作及资料交换事宜, 并提供程序开发接口给第五层到第七层 国际标准组织 ( 简称 ISO : International Organization for Standardization) 的 OSI(Open System Interface) 七层架构标准是一种 ISO 的通讯网路 (network) 架构标准, 分为七层 (layer) 来管理通讯协议 (protocol), 每一层通讯协议与下一层连结, 发送资料时发送端由第七层开始传递到第一层, 并由每一层通讯协议加以包装 (encapsulation), 经过传输媒介 (media) 到接收端的第一层, 再由第一层开始打开包装, 直到对应的第七层完成传输, 这样子的架构称为 OSI Model, 但并没有厂商实作全部七层, 有的厂商将几个相邻层次的功能使用一个通讯协议来实作, 或将一层的功能分为两部分完成 OSI 共分七层, 其各层的定义功能如下 : 1. 实体层 (Physical layer) : 定义媒体 (media), 传输方法 (transmission method) 与布线方式 (topology) 2. 数据链路层 (Data Link layer) : 定义如何确保资料正确传输, 将资料切割并加上来源与目的地地址与资料长度等讯息包装成页框 (frame) IEEE 802 规格将此层分为 LLC (802 2) 与 MAC (802 3,802 4,802 5) 3. 网络层 (Network layer): 负责资料绕径 (routing), 包括转换地址 (translates addresses), 寻找最佳路径 (best routing) 与管理流量 (traffic) 4. 传输层 (Transport layer): 确保资料到达顺序与正确性, 包括重组资料大小, 封包顺序检查, 流量控制, 超时 (timeout) 与重传, 错误侦测与多任务组合 (multiplexing) 5. 会议层 (Session layer): 定义连结建立与结束的对话 (dialog), 错误处理与逻辑 (logical) 地址名称转换 6. 表现层 (Presentation layer): 处理资料格式, 包括格式转换, 加密 (encryption) 与解密 (decryption), 压缩 (compression) 与还原 7. 应用层 (Application layer): 定义供应用程序存取的接口与功能, 还有目录服务 (directory service) 及档案存取 下图配合国际标准组织 ( 简称 ISO : International Organization for Standardization) 的 OSI(Open System Interface) 七层架构标准来说明 Windows CE.NET 操作系统所提供的 TCP/IP 层次

101

102 Winsock 所开放出的应用程序发展接口 (API), 可大致分成两大类 第一部份是 [ 柏克莱版 ] 风格 (Berkeley-style) 的 socket 函式, 另一部份是微软窗口特有的窗口专属延伸函式 (Windows-specific extension functions) [ 柏克莱版 ] 风格的 socket 函式 : 函式 功能 accept 接受一个联机要求并马上建立一个 socket 与其连结, 原来等待联机要求的 socket 继续回到倾听状态 bind 对未命名的 socket 设定一个本地名 (local name) closesocket 关闭一个既存的 socket 应用程序中, 毎个 socket 函式的使用, 必须有相对应的使用 closesocket 函式来释放这个 socket 资源 connect 连结到指定的 socket freeaddrinfo 释放由 getaddrinfo 函式自动配置在 addrinfo 结构的地址信息 getaddrinfo 不管为何种协议 (protocol), 提供机器名 (host name) 到地址 (address) 的转换 gethostbyaddr 用网络地址来取得机器信息 gethostbyname 用机器名来取得机器信息 gethostname 取得本地机器 (local machine) 的标准机器名 getnameinfo 用地址来取得机器名 getpeername 取得同时连结到此 socket 的同侪 (peer) 的名称 getsockname 取得此 socket 被连结 (bound) 的本地地址 getsockopt 取得此 socket 相关的选项 (options) htonl 转换一个主机位顺序 (host byte order) 的 32 位数目为 TCP/IP 网络位顺序 (network byte order),tcp/ip 网络位顺序为 big-endian htons 转换一个主机位顺序 (host byte order) 的 16 位数目为 TCP/IP 网络位顺序 (network byte order),tcp/ IP 网络位顺序为 big-endian inet_addr 转换一个有. 的标准网络地址, 例如 , 成网际网络地址值 (Internet address value) inet_ntoa 转换网际网络地址值 (Internet address value) 为一个有. 的标准网络地址 ioctlsocket 提供 socket 控制 listen 在一特定 socket 倾听进来的联机要求 ntohl 转换一个 TCP/IP 网络位顺序 (network byte order) 的 32 位数目为主机位顺序 (host byte order),tcp/ip 网络位顺序为 big-endian

103 ntohs 转换一个 TCP/IP 网络位顺序 (network byte order) 的 16 位数目为主机位 顺序 (host byte order),tcp/ip 网络位顺序为 big-endian recv 从已连结或未连结的 sockets 接收资料 recvfrom 从一个已连结或未连结的 socket 接收资料 select 完成同步输出入多任务 (synchronous I/O multiplexing) send 传送资料到已连结的 sockets sendto setsockopt shutdown socket 传送资料到一已连结或未连结的 socket 设定 socket 选项 关闭传输或接收 建立 socket. 窗口专属延伸函式 (Windows-specific extension functions) : WSAAccept 函式 WSACleanup WSACloseEvent WSAConnect WSACreateEvent 功能 Accept 函式的延伸版本, 允许条件式接受联机 停止使用 Windows Sockets DLL. 关闭一个事件对象 (event object) connect 函式的延伸版本, 允许交换 connect 资料 建立一个事件对象 (event object) WSAEnumNetworkEvents 查询发生在这 socket 的事件 WSAEnumProtocols WSAEventSelect WSAGetLastError 取得可用协议 (protocol) 的信息 将网络事件 (network events) 与事件对象 (event object) 相联 取得最近一次 Winsock 错误讯息 WSAGetOverlappedResult 取得交错式 (overlapped) 动作执行结果 WSAHtonl WSAHtons WSAIoctl WSAJoinLeaf htonl 函式的延伸版本 htons 函式的延伸版本 IOCTL 函式的延伸版本 在多点会议 (multipoint session) 加入分支点 (leaf node) 并交换连结资 料 WSALookupServiceBegin 遵守 WSAQUERYSET 结构的限定来执行客户端查询 WSALookupServiceEnd WSALookupServiceNext 在应用程中使用 WSALookupServiceBegin 或 WSALookupServiceNext 后, 呼叫此函式释放控制数 (handle) 在应用程中使用 WSALookupServiceBegin 后, 使用此函式来取得 所需服务的信息

104 WSANtohl ntohl 函式的延伸版本 WSANtohs ntohs 函式的延伸版本 WSARecv recv 函式的延伸版本 WSARecvFrom recvfrom 函式的延伸版本 WSAResetEvent 重设事件对象 WSASend send 函式的延伸版本 WSASendTo sendto 函式的延伸版本 WSASetEvent Sets an event object. WSASetLastError 设定错误信息让下个 WSAGetLastError 被呼叫时传回 WSASetService 设定服务 WSASocket socket 函式的延伸版本 WSAStartup 激活使用 Winsock DLL. WSAStringToAddress 转换一个数字字符串为 sockaddr 结构 [ 柏克莱版 ] 风格 (Berkeley-style) 的 socket 函式和窗口专属延伸函式 (Windows-specific extension functions) 的使用说明, 读者皆可在微软的 Windows CE.NET 的技术文件及线上说 明或 MSDN 中取得 目前 Windows CE.NET 的 Winsock 支持最新的 2.2 版本, 使用以上两大类由 Winsock 所提供的标准 Win32 应用程序开发接口 (API : Application Programming Interface) 撰写你的 embedded Visual C++ 应用程序时要记得引入 <Winsock2.h> 标头档 (head hile) 及连结 Ws2.lib 函式库 (library) 除此之外, 你也可以使用 embedded Visual C++ 的 MFC 来撰写你的应用通讯程序 MFC 也实做了一些 Windows Sockets 的类别 (class), 其中最常用到的 Windows Socket 类别 (class) 为 CSocketFile 和 CSocket 在 embedded Visual C++ 使用 MFC 来撰写 Windows CE.NET 应用通讯程序的方法和在 Visual Studio/Visual C++ 使用 MFC 来撰写 PC 端应用通讯程序的方法完全一样 另外,.NET Compact Framework 也实做 Windows Socket 的一些软件组件 (namespace), 提供程序开发者撰写 Visual Basic.NET 或 Visual C#.NET 应用通讯程序, 其中最常用的一些软件组件 (namespace) 为 System.Net 和 System.Net.Sockets 本章节重点及范例将介绍如何使用 Winsock 所提供的标准 Win32 应用程序开发接口 (API : Application Programming Interface) 撰写 embedded Visual C++ 通讯应用程序 WinSock 可让网络应用程序在网络上存取资料, 而一台硬件装置可能只有一个实体网络连上网, 但可同时开启多个 sockets 来交换资料 一支客户端 socket 应用程序 (WinSock Client Application) 可透过 WinSock API 来传送封包 (packet) 到网络上或到服务端, 而一支服务端 socket 应用程序 (WinSock Server Application) 则是透过 WinSock API 来接收客户端 socket 应用程序传送出来的封包 客户端和服务端必需使用相同的 socket 型态才能互相通讯, 例如同样使用 TCP 或同样使用 UDP 例如本章范例开启一支客户端 socket 应用程序 (WinSock Client Application) 来传送讯息给服务端, 同时开启一支服务端 socket 应用程序 (WinSock Server Application) 在网络上接受来自客户端的讯息, 并在接受到客户端的讯息时把讯息再回馈给客

105 户端 本章节范例程序如下图所示, 本章节范例程序可扮演客户端应用程序或服务端应用程序, 并且程序一开始会显示本地机器的第一个网络卡的 IP 地址和预设通讯端口号码 (port number), 操作者可以设定欲开启的 socket 所在地址和通讯端口号码 如下图, 我们可以先激活一次本章节范例程序来设定扮演服务端应用程序, 并使用程序一开始显示的本地机器的第一个网络卡的 IP 地址为欲开启 socket 的地址, 而通讯端口号码 (por t number) 也使用默认值 8000, 最后点选 [start] 来激活 socket 服务端以开始倾听 (listen) 客户端的联机要求 接着, 再激活本章节范例程序一次来扮演客户端应用程序, 并指定要连结的 socket 的 IP 住址为程序一开始显示的本地机器的第一个网络卡的 IP 地址和使用默认值 8000, 最后同样点选 [start] 来激活 socket 客户端并连结 (connect) 到服务端 ( 前一支扮演服务端的应用程序 )

106 当服务端应用程序接受 (accept) 客户端应用程序的联机要求后, 服务端应用程序与客户端应用程序间的 socket 即建立完成 接下来可在客户端应用程序键入欲传送的讯息后点选 [send] 送出讯息, 然后等待服务端应用程序的响应 (echo) 当服务端应用程序收到客户端应用程序送来的讯息后会原封不动的再响应 (echo) 到客户端应用程序, 此时在等待服务端应用程序响应 (echo) 的客户端应用程序便会送到来自服务端应用程序的响应 (echo) 如下图

107 建立 Winsock 通讯应用程序不管 Winsock 服务端应用程序或 Winsock 客户端应用程序都必须在程序的一开始呼叫 WSAStartup 来激活和初始化 Winsock 动态连结函式库 (Winsock2.dll), 且无论使用的 Winsock 是那个版本, WSAStartup 都会初始化一个 WSADATA 结构, 这个结构会提供该版本的功能说明 以下程序代码说明使用 WSAStartup 激活和初始化 Winsock 动态连结函式库 (Winsock2.dll) 的方法 函式原型 : int WSAStartup( WORD wversionrequested, LPWSADATA lpwsadata ); 参数 : wversionrequested : 指定要使用的 Winsock 动态连结函式库的版本, 使用 2 个字节的 WORD 来指定, 高字节

108 (high-order byte) 指定副版本编号 (minor version), 低字节 (low-order byte) 指定主版本编号 (major version number) lpwsadata : 一个指向 WSADATA 结构的指针, 用来接收传回的 Winsock 动态连结函式库的初始结果和功能 范例 : WORD wversionrequested; WSADATA wsadata; int err; wversionrequested = MAKEWORD( 2, 2 ); err = WSAStartup( wversionrequested, &wsadata ); if ( err!= 0 ) /* Tell the user that we could not find a usable */ /* WinSock DLL. */ return; /* Confirm that the WinSock DLL supports 2.2.*/ /* Note that if the DLL supports versions greater */ /* than 2.2 in addition to 2.2, it will still return */ /* 2.2 in wversion since that is the version we */ /* requeste d. */ if ( LOBYTE( wsadata.wvers ion )!= 2 HIBYTE( wsadata.wversion )!= 2 ) /* Tell the user that we could not find a usable */ /* Win Sock DLL. */ WSACleanup( ); return; 如果 WSAStartup 执行成功,WSAStartup 的传回值是 0, 且 Winsock 动态连结函式库的初始结果和功能会储存于一个指向 WSADATA 结构的指针 (lpwsadata) 并存回 在应用程序中毎当要取得 Winsock 动态连结函式库的初始结果和功能时, 可以再次呼叫 WSAStartup 函式, 但是应用程序中的毎个呼叫, 在最后都必须有个相对应的 WSACleanup 呼叫以释放资源

109 WSAC leanup 函式的使用说明如下 : 函式原型 : int WSACleanup (void); 如果 WSACleanup 执行成功,WSACleanup 的传回值是 0 如果 WSACleanup 执行失败, 可以呼叫 WSAGetLastError 函式来取得错误代码 通常在呼叫 Winsock 动态连结函式库的函式执行失败时, 可以透过呼叫 WSAGetLastError 函式来取得错误代码, WSAGetLastError 函式的使用说明如下 : 函式原型 : int WSAGetLastError (void); WSAGetLastError 函式的传回值即为错误代码 在 WS AStartup 函式呼叫成功后, 应用程序便可以开始使用所有的 Winsock 动态连结函式库的函式, 包括 [ 柏克莱版 ] 风格 (Berkeley-style) 的 socket 函式和微软窗口特有的窗口专属延伸函式 (Windows-specific extension functions) 以下我们将主要使用[ 柏克莱版 ] 风格 (Berkeley-style) 的 socket 函式来建立 Winsock 服务端应用程序和 Winsock 客户端应用程序 建立 Winsock 服务端应用程序 以下我们将建立一支 Winsock socket 服务端 (socket server) 应用程序, 它将建立连结导向 (connection-or iented) 的字符串流 (stream) socket 来接收来自 socket 客户端 (socket client) 应用程序的资料, 并将接收到的资料再响应 (echo) 1. 使用 getaddrinfo 函式传入网络地址 (IP address) 及通讯端口号码以取得主机信息 呼叫 getaddrinfo 函式时同时要传入一个指向 addrinfo 结构数组的指针以用来储存传回的主机主机信息 成功取得主机信息后, 应用程序就可利用这取得的主机信息来呼叫 socket 函式开启 socket 和呼叫 bind 函式来连结 socket 以下说明 getaddrinfo 函式的使用方式和范例程序代码 : 函式原型 : int getaddrinfo( const char FAR *nodename, const char FAR *servname, const struct addrinfo FAR *hints, struct addrinfo FAR *FAR *res );

110 参数 : nodename 传入一字符串指定 IP 地址 servname 传入一字符串指定服务 (service) 名称或通讯端口号码 (port number) hints 传入一个指向 addrinfo 结构的指针, 利用此结构来指定欲使用的通讯协议 (protocol) 及 s ocket 类型 res 传入一个指向 addrinfo 结构数组的指针以用来储存传回的主机主机信息 范例程序代码 : A DDRINFO Hints, *AddrInfo = NULL; memset(&hints, 0, sizeof(hints)); H ints.ai_family = AF_UNSPEC; Hints.ai_socktype = SOCK_STREAM; Hints.ai_flags = AI_NUMERICHOST AI_PASSIVE; if(getaddrinfo(gipaddress, gportnumber, &Hints, &AddrInfo)) Print(TEXT("ERROR: getaddrinfo failed with error %d\r\n"), WSAGetLastError()); return 0; 2. 使用 socket 函式建立 socket 如果建立成功, 其返回值即为成功建立的 socket, 如果建立 socket 失败, 其返回值即为 INVALID_SOCKET 这个值, 此时可呼叫 WSAGetLastError 函式来取得错误代码 范例程序代码 : 函式原型 : SOCKET socket( int af, int type, int protocol ); 参数 : af 指定地址种类 (address family), 例如 PF_INET

111 type 指定 socket 种类, 有两种选项 : 选项 SOCK_STREAM 说明 用来传输循序式 (sequenced) 可靠 双向 连结导向 (connection-based) 的字节信息流 (byte streams), 并使用占频寛 的传输机制程使用 TCP 地址种类 (address family) SOCK_DGRAM 用来传输不可靠 非连结导向 (connectio nless) 的固定长度资料包 (datagrams), 并使用 UDP 地址种类 (address family) protocol 指定所使用的协议 (protocol) 种类, 例如可指定 IPPROTO_TCP 或 IPPROTO_UDP 范例程序代码 : // // ADDRINFO *AI; SOCKET gserversock; gserversock = socket(ai->ai_family, AI->ai_socktype, AI->ai_protocol); if (gserversock!= INVALID_SOCKET) else freeaddrinfo(ai); 3. 呼叫 bind 连结 socket 时必须传入欲连结的 socket, 即上一个步骤使用 socket 函式产生的 socket, 和传入记录于 sockaddr 结构的地址信息 本章范例程序代码 函式原型 : int bind( SOCKET s, const struct SOCK_ADDR *name, int namelen ); 参数 : s 指定欲结合的 socket. name 使用 sockaddr 结构来指定欲结合的地址 (address) namelen 指定 name 参数的长度

112 范例程序代码 : // // ADDRINFO *AI; if (bind(gserversock, AI->ai_addr, AI->ai_addrlen) == SOCKET_ERROR) closesocket(gserversock); return 0; 4. 呼叫 listen 函式并指定欲倾听的 socket 来开始倾听从客户端来的连结要求 本章范例程序代码 函式原型 : int listen( SOCKET s, int backlog ); 参数 : s backlog 指定一个已结合 (bind) 但未与客户端连结 (connect) 的 socket 设定等待联机伫例的最大等待个数, 一般都设定为 SOMAXCONN 以让底层的驱动程序去 决定 范例程序代码 : // // if (listen(gserversock, SOMAXCONN) == SOCKET_ERROR) Print(TEXT("Server go to listen mode failed\r\n")); closesocket(gserversock); return 0; // show message 5. 使用 accept 函式来接受来自客户端的联机要求,accept 函式会另外建立一个新的 socket 来与自客户端交换资料并传回此新建立的 socket 原本 socket 服务端应用程序使用 so cket 函式建立的 socket 会继续倾听从客户端来的连结要求 本章范例程序代码 函式原型 : SOCKET accept(

113 SOCKET s, struct SOCK_ADDR * addr, int FAR *addrlen ); 参数 : s addr addrlen 指定呼叫 listen 函式进入倾听状态倾听来自客户端联机要求的那个 socket 用来记录函式传回的要求联机的客户端的地址 指定 addr 结构的长度 范例程序代码 : // // SOCKET gclientsock, gserversock; SOCKADDR_STORAGE ssremot eaddr; int cbremoteaddrsize; // Wait for incomming data/connections gclientsock = accept(gserversock, (SOCKADDR*)&ssRemoteAddr, &cbremoteaddrsize); if(gclientsock == INVALID_SOCKET) Print(TEXT("ERROR: accept() failed with error = %d\r\n"), WSAGetLastError()); return 0; Print(TEXT("Accepted TCP connection from socket 0x%08x\r\n"), gclientsock); 6. 使用 recvfrom 函式来接收来的客户端的资料, 并使用 sendto 传送响应 (echo) 资料结客户端 本章范例程序代码 函式原型 : int recvfrom( SOCKET s, char FAR *buf, int len, int flags, struct SOCK_ADDR *from, int FAR *fromlen ); 参数 : s buf len 指定使用 accept 函式接收客户端联机要求时新建立的 socket 指定要接收资料的缓冲区 (buffer)

114 指定缓冲区的长度 flags 通常填入 0 from fromlen 指定缓冲区来接收传回的客户端地址 指定 from 参数指定的缓冲区的长度 函式原型 : int sendto( SOCKET s, const char FAR *buf, int len, int flags, const struct SOCK_ADDR *to, int tolen ); 参数 : s 指向一已连结 (connected) 的 socket buf 指定储存欲传送资料的缓冲区 (buffer) len buf 参数指定的缓冲区 (buffer) 的长度 flags 通常填入 0 to 指定欲使用来传送资料的 socket 的地址 tolen 指定 to 参数的长度 范例程序代码 : // // SOCKET gclientsock; SOCKADDR_STORAGE ssremoteaddr; int cbremoteaddrsize, cbxfer, cbtotalrecvd;char chbuffer[100]; // Receive data from a client memset(chbuffer, 0, sizeof(chbuffer)); cbtotalrecvd = 0; do cbremoteaddrsize = sizeof(ssremoteaddr); cbxfer = recvfrom(gclientsock, chbuffer + cbtotalrecvd, sizeof(chbuffer) - cbtotalrecvd, 0, (SOCKADDR *)&ssremoteaddr, &cbremoteaddrsize); cbtotalrecvd += cbxfer; while(cbxfer > 0 && cbtotalrecvd < sizeof(chbuffer));

115 // Echo the data back to the client cbxfer = 0; cbxfer = sendto(gclientsock, chbuffer, cbtotalrecvd, 0, (SOCKADDR *)&ssremoteaddr, cbremoteaddrsize); if(cbxfer!= cbtotalrecvd) Print(TEXT("ERROR: Couldn't send the data! error = %d\r\n"), WSAGetLastError()); return 0; 7. 使用 shutdown 函式来停止 socket 函式原型 : int shutdown( SOCKET s, int how ); 参数 : s how 指定欲停止的 socket 指定欲停止的功能, 一般填入 SD_BOTH 8. 当 Winsock 服务端应用程序和 Winsock 客户端应用程序间交换资料完成后, 呼叫 closesocket 函式来关闭 socket 呼叫 closesocket 函式来关闭 socket 之前必须先使用 shutdown 函式来停止 socket 一个应用程序对毎一个成功的 socket 函式呼叫, 都应该永远有一个相对应 closesocket 呼叫, 以便释放 socket 函式呼叫时取得的系统资源 (resource) 每请求可以复原任一 socket 资源给系统 在 When data exchange between the server and client ends, close the socket with the closesocket function. 当数据在服务器及客户端间交换时, 以 closesocket 函式来关闭 socket An application should call shutdown before calling closesocket. 应用程序在关机前先用 closesocket 函式 对于使用 TCP 资料流 (TCP stream) 型态的 socket 而言, 当 socket 联机结束时, 服务端必须关闭 accept 函式所建立的 socket 另外, 如果未关闭由呼叫 socket 函式时所产生的第一个 socket, 则此 socket 会继续倾听任何来自客户端的联机要求 本章范例程序代码 函式原型 : int closesocket( SOCKET s );

116 参数 : s 指定欲关闭的 socket 范例程序代码 : // // SOCKET gsock if(gsock!= INVALID_SOCKET) shutdown(gsock, SD_BOTH); closesocket(gsock); 建立 Winsock 客户端应用程序 以下我们将建立一支 Winsock socket 客户端 (socket client) 应用程序, 它将传送资料给 Winsock socket 服务端 (socket server) 应用程序, 并接收服务端 (socket server) 响应 (echo) 回来的资料 1. 和建立 Winsock 服务端应用程序的第一步一样, 使用 getaddrinfo 函式等来取得欲联机的服务端主机的地址 使用方法和范例程序请参考建立 Winsock 服务端应用程序的第一步 2. 也是和建立 Winsock 服务端应用程序的第二步一样, 当取得欲联机的服务端主机的地址和相关信息后, 使用 socket 函式建立客户端 socket 使用方法和范例程序请参考建立 Winsock 服务端应用程序的第二步 3. 使用 connect 函式来对服务端指出联机求 本章范例程序代码 函式原型 : int connect( SOCKET s, const struct SOCK_ADDR *name, int namelen ); 参数 : s 指定欲连结的 socket name 指定欲联机的主机名称或地址 namelen

117 name 参数的长度 范例程序代码 : // // SOCKET gsock; ADDRINFO *gai; if (gai->ai_socktype == SOCK_STREAM) if(connect(gsock, gai->ai_addr, gai->ai_addrlen) == SOCKET_ERROR) Print(TEXT("Connect to server failed\r\n")); closesocket(gsock); return 0; 4. 和建立 Winsock 服务端应用程序一样, 使用 sendto 来传送资料到客户端, 并使用 recvfrom 来接收来自服务端的响应 (echo) 使用方法和范例程序请参考建立 Winsock 服务端应用程序的第六步骤 5. 最后也和建立 Winsock 服务端应用程序一样, 使用 shutdown 和 closesocket 来停止 socket 功能和关闭 socket 使用方法和范例程序请参考建立 Winsock 服务端应用程序的最后第七和第八步骤 最后, 我们用以下的图例来说明 Winsock 服务端应用程序和 Winsock 客户端应用程序的激活 及初始化的动作, 并且最重要的是要解说 Winsock 服务端应用程序和 Winsock 客户端应用程 序是如何互动和配合的 : 1 Server App Server Socket Client App 服务端 : 建立一个 socket 客户端 : 2 Server App Bind Server Socket Client App 服务端 : bind 一个 socket 客户端 :

118 3 Server App Bind Server Socket listening Client App 服务端 : 倾听客户端联机要求 客户端 : 4 Server App Bind Server Socket Listening Client Socket Client App 服务端 : 客户端 : 建立 socket 5 Server App Bind Server Socket Connect Listening Client Socket Client App 服务端 : 客户端 : 提出联机要求 6 Server App Bind Server Socket Connected Client Socket Client App 服务端 : 接受客户端的联机要求 客户端 : sendto 7 Server App Bind Server Socket Connected Client Socket Client App recvfrom

119 服务端 : 接收资料客户端 : 传送资料 Server App Bind Server Socket Connected Client Socket Client App 8 sendto recvfrom 服务端 : 送出响应资料 客户端 : 收到服务端的响应资料 9 Server App Client App 服务端 : 停止和关闭所有 socket 客户端 : 停止和关闭所有 socket 结论 本章节所介绍的 Winsock 服务端应用程序和 Winsock 客户端应用程序的开发方式和本书所附范例程序都是以介绍 Win32 SDK 的 Winsock 所开放出的应用程序发展接口 (API) 的使用方法, 以下所列为本章节范例程序完整程序代码 除了使用 Win32 SDK 的 Winsock 来开发 Winsock 通讯应用程序外, 我们尚可使用 MFC 的 CSocketFile 和 CSocket 等类别 (class) 来开发 或者在撰写 VB.NET 或 VC#.NET 时也可使用 System.Net.Sockets 和 System.Net 等命名空间 (namespace) 来开发 Winsock 通讯应用程序 而这其它两种开发方式与开发 PC 端的 Winsock 通讯应用程序的方式无异, 本书不再赘述

120 本章范例程序代码 // WinsockComm.cpp : Defines the entry point for the application. // #include "stdafx.h" #include "WinsockComm.h" #include <commctrl.h> // Vincent added codes here for include files #include <stdio.h> #include <string.h> #include <windows.h> #include <winsock2.h> #include <Winuser.h> #include <Pwinreg.h> #include <Iphlpapi.h> #include <IpTypes.h> #include <Ws2tcpip.h> #define MAX_LOADSTRING 100 // Global Variables: HINSTANCE hinst; // The current instance HWND hwndcb; // The command bar handle // Forward declarations of functions included in this code module: ATOM MyRegisterClass (HINSTANCE, LPTSTR); BOOL InitInstance (HINSTANCE, int); LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About (HWND, UINT, WPARAM, LPARAM); // Vincent added codes here for function declarations LRESULT CALLBACK DoDemo (HWND, UINT, WPARAM, LPARAM); BOOL GetLANinfo(HWND hdlg); DWORD WINAPI ServerThread (PVOID parg); DWORD WINAPI ClientStart (PVOID parg); int _tmainclient(pvoid parg); char * ConvertUnicodeToAscii(LPCWSTR p,int l); void Print(TCHAR *pformat,...); // Vincent added codes here for global variable declarations IP_ADAPTER_INFO gstrucadpinfo[4]; ADDRINFO *gaddrinfo, *gai; SOCKET gsock = INVALID_SOCKET; SOCKET gclientsock, gserversock; char *gipaddress, *gportnumber; HANDLE hreadthread = INVALID_HANDLE_VALUE; HWND gdemodlg = NULL; int WINAPI WinMain( MSG msg; HACCEL hacceltable; HINSTANCE hinstance, HINSTANCE hprevinstance, LPTSTR lpcmdline, int ncmdshow) // Perform application initialization: if (!InitInstance (hinstance, ncmdshow)) return FALSE;

121 hacceltable = LoadAccelerators(hInstance, (LPCTSTR)IDC_WINSOCKCOMM); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) if (!TranslateAccelerator(msg.hwnd, hacceltable, &msg)) TranslateMessage(&msg); DispatchMessage(&msg); return msg.wparam; // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // // COMMENTS: // // It is important to call this function so that the application // will get 'well formed' small icons associated with it. // ATOM MyRegisterClass(HINSTANCE hinstance, LPTSTR szwindowclass) WNDCLASS wc; wc.style = CS_HREDRAW CS_VREDRAW; wc.lpfnwndproc = (WNDPROC) WndProc; wc.cbclsextra = 0; wc.cbwndextra = 0; wc.hinstance = hinstance; wc.hicon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINSOCKCOMM)); wc.hcursor = 0; wc.hbrbackground = (HBRUSH) GetStockObject(WHITE_BRUSH); wc.lpszmenuname = 0; wc.lpszclassname = szwindowclass; return RegisterClass(&wc); // // FUNCTION: InitInstance(HANDLE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In th is function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance(HINSTANCE hinstance, int ncmdshow) HWND hwnd; TCHAR sztitle[max_loadstring]; // The title bar text TCHAR szwindowclass[max_loadstring]; // The window class name

122 hinst = hinstance; // Store instance handle in our global variable // Initialize global strings LoadString(hInstance, IDC_WINSOCKCOMM, szwindowclass, MAX_LOADSTRING); MyRegisterClass(hInstance, szwindowclass); LoadString(hInstance, IDS_APP_TITLE, sztitle, MAX_LOADSTRING); hwnd = CreateWindow(szWindowClass, sztitle, WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, N ULL, hinstance, NULL); if (!hwnd) return FALSE; ShowWindow(hWnd, ncmdshow); UpdateWindow(hWnd); if (hwndcb) CommandBar_Show(hwndCB, TRUE); return TRUE; // // FUNCTION: WndProc(HWND, unsigned, WORD, LONG) // // PURPOSE: Processes messages for the main window. // // WM_COMMAND - process the application menu // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return // // LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) // Vincent to mark all /* HDC h dc; int wmid, wmevent; PAINTSTRUCT ps; TCHAR szhello[max_loadstring]; */ switch (message) c ase WM_ COMMAND: // Vincent to mark all /* wmid = LOWORD(wParam); wmevent = HIWORD(wParam); // Parse the menu selections: switch (wmid) case IDM_HELP_ABOUT: DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hwnd, (DLGPROC)About); break; case IDM_FILE_EXIT: DestroyWindow(hWnd); break; default:

123 return DefWindowProc(hWnd, message, wparam, lparam); */ break; case WM_CREATE: // Vincent to mark all /* hwndcb = CommandBar_Create(hInst, hwnd, 1); CommandBar_InsertMenubar(hwndCB, hinst, IDM_MENU, 0); CommandBar_AddAdornments(hwndCB, 0, 0); */ // Vincent added codes here DialogBox(hInst, (LPCTSTR)IDD_DIALOG_DEMO, hwnd, (DLGPROC)DoDemo); DestroyWindow(hWnd); break; case WM_PAINT: // Vincent to mark all /* RECT rt; hdc = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rt); LoadString(hInst, IDS_HELLO, szhello, MAX_LOADSTRING); DrawText(hdc, szhello, _tcslen(szhello), &rt, DT_SINGLELINE DT_VCENTER DT_CENTER); EndPaint(hWnd, &ps); */ break; case WM_DESTROY: // Vincent to mark all /* CommandBar_Destroy(hwndCB); PostQuitMessage(0); */ break; default: return DefWindowProc(hWnd, message, wparam, lparam); return 0; // Mesage handler for the About box. LRESULT CALLBACK About(HWND hdlg, UINT message, WPARAM wparam, LPARAM lparam) RECT rt, rt1; int DlgWidth, DlgHeight; // dialog width and height in pixel units int NewPosX, NewPosY; switch (message) ca se WM_INITDIALOG: // trying to center the About dialog if (GetWindowRect(hDlg, &rt1)) GetClientRect(GetParent(hDlg), &rt); DlgWidth = rt1.right - rt1.left; DlgHeight = rt1.bottom - rt1.top ; NewPosX = (rt.right - rt.left - DlgWidth)/2; NewPosY = (rt.bottom - rt.top - DlgHeight)/2; // if the About box is larger than the physical screen

124 if (NewPosX < 0) NewPosX = 0; if (NewPosY < 0) NewPosY = 0; SetWindowPos(hDlg, 0, NewPosX, NewPosY, 0, 0, SWP_NOZORDER SWP_NOSIZE); return TRUE; case WM_COMMAND: if ((LOWORD(wParam) == IDOK) (LOWORD(wParam) == IDCANCEL)) EndDialog(hDlg, LOWORD(wParam)); return TRUE; break; return FALSE; // ================================ // Vincent added all codes below // ================================ LRESULT CALLBACK DoDemo(HWND hdlg, UINT message, WPARAM wparam, LPARAM lparam) RECT rt, rt1; int DlgWidth, DlgHeight; // dialog width and height in pixel units int NewPosX, NewPosY; TCHAR tchbuffer[20]; UINT cnttext; DWORD dwtstat; gdemodlg = hdlg; WSADATA wsadata; switch (message) case WM_INITDIALOG: // trying to center the About dialog if (GetWindowRect(hDlg, &rt1)) GetClientRect(GetParent(hDlg), &rt); DlgWidth = rt1.right - rt1.left; DlgHeight = rt1.bottom - rt1.top ; NewPosX = (rt.right - rt.left - DlgWidth)/2; NewPosY = (rt.bottom - rt.top - DlgHeight)/2; // if the About box is larger than the physical screen if (NewPosX < 0) NewPosX = 0; if (NewPosY < 0) NewPosY = 0; SetWindowPos(hDlg, 0, NewPosX, NewPosY, 0, 0, SWP_NOZORDER SWP_NOSIZE); // Vincent added code here SendDlgItemMessage(hDlg, IDC_COMBO_ROLE, CB_INSERTSTRING, -1, (LPARAM)(LPCSTR)TEXT("Server")); SendDlgItemMessage(hDlg, IDC_COMBO_ROLE, CB_INSERTSTRING, -1, (LPARAM)(LPCSTR)TEXT("Client")); SendDlgItemMessage(hDlg, IDC_COMBO_ROLE, CB_SETCURSEL, 0, 0); // Initiate WinSock Library if(wsastartup(makeword(2,2), &wsadata))

125 // WSAStartup failed return FALSE; // Get Local IP and show in IP edit box if (!GetLANinfo(hDlg)) MessageBox(hDlg, TEXT("Get Local IP Failed"), TEXT("LAN info"), MB_OK); return FALSE; return TRUE; case WM_COMMAND: if ((LOWORD(wParam) == IDOK) (LOWORD(wParam) == IDCANCEL)) // Added by Vincent to clear resources if(gsock!= INVALID_SOCKET) shutdown(gsock, SD_BOTH); closesocket(gsock); if(gserversock!= INVALID_SOCKET) closesocket(gserversock); if(gclientsock!= INVALID_SOCKET) shutdown(gclientsock, SD_BOTH); closesocket(gclientsock); if(gaddrinfo) freeaddrinfo(gaddrinfo); WSACleanup(); 0)) EndDialog(hDlg, LOWORD(wParam)); return TRUE; else if (LOWORD(wParam) == IDC_BUTTON_OPEN) // Get Server IP cnttext = GetDlgItemText(hDlg, IDC_EDIT_IP, tchbuffer, sizeof(tchbuffer)); gipaddress = ConvertUnicodeToAscii(tchBuffer, cnttext); cnttext = GetDlgItemText(hDlg, IDC_EDIT_PORT, tchbuffer, sizeof(tchbuffer)); gportnumber = ConvertUnicodeToAscii(tchBuffer, cnttext); switch (SendDlgItemMessage (hdlg, IDC_COMBO_ROLE, CB_GETCURSEL, 0, case 0 : // Create Server Thread to listen Print(TEXT("======== Server Starts ========\r\n")); EnableWindow(GetDlgItem(hDlg, ID_SENDTEXT), FALSE); EnableWindow(GetDlgItem(hDlg, ID_SENDBTN), FALSE); STILL_ACTIVE)) &dwtstat); if (!GetExitCodeThread (hreadthread, &dwtstat) (dwtstat!= hreadthread = CreateThread (NULL, 0, ServerThread, hdlg, 0, if (hreadthread) CloseHandle (hreadthread); break; case 1 : // client

126 return FALSE; // show something let user now it connected Print(TEXT("======== Client Starts ========\r\n")); break; default : break; return TRUE; else if (LOWORD(wParam) == ID_SENDBTN) ClientStart(hDlg); _tmainclient(hdlg); SetDlgItemText(hDlg, ID_SENDTEXT, TEXT("")); SetFocus (GetDlgItem (hdlg, ID_SENDTEXT)); return TRUE; else break; BOOL GetLANinfo(HWND hdlg) ULONG ulong; DWORD dw; ulong=sizeof(gstrucadpinfo); TCHAR strbuffer[100]; if ((dw=getadaptersinfo(gstrucadpinfo, &ulong))==error_success) wsprintf(strbuffer, TEXT("%hs"), gstrucadpinfo[0].ipaddresslist.ipaddress.string); // Set Default Port Number and IP and show in edit box SetDlgItemText(hDlg, IDC_EDIT_IP, strbuffer); SetDlgItemText(hDlg, IDC_EDIT_PORT, TEXT("8000")); else return 0; return 1; //================================================================================ ====== void Print(TCHAR *pformat,...) va_list ArgList; TCHAR Buffer[256]; va_start (ArgList, pformat); (void)wvsprintf (Buffer, pformat, ArgList); SendDlgItemMessage (gdemodlg, ID_RCVTEXT, EM_REPLACESEL, 0, (LPARAM)Buffer); va_end(arglist);

127 DWORD WINAPI ServerThread (PVOID parg) SOCKADDR_STORAGE ssremoteaddr; int cbremoteaddrsize, cbxfer, cbtotalrecvd; ADDRINFO Hints, *AddrInfo = NULL, *AI; char szremoteaddrstring[128]; HWND hdlg; char chbuffer[100]; hdlg = (HWND)pArg; gclientsock = INVALID_SOCKET; // Get get first LAN card and its configurations memset(&hints, 0, sizeof(hints)); Hints.ai_family = AF_UNSPEC; Hints.ai_socktype = SOCK_STREAM; Hints.ai_flags = AI_NUMERICHOST AI_PASSIVE; if(getaddrinfo(gipaddress, gportnumber, &Hints, &AddrInfo)) Print(TEXT("ERROR: getaddrinfo failed with error %d\r\n"), WSAGetLastError()); return 0; // Create a server socket AI = AddrInfo; gserversock = socket(ai->ai_family, AI->ai_socktype, AI->ai_protocol); if (gserversock!= INVALID_SOCKET) if (bind(gserversock, AI->ai_addr, AI->ai_addrlen) == SOCKET_ERROR) closesocket(gserversock); return 0; else if (listen(gserversock, SOMAXCONN) == SOCKET_ERROR) Print(TEXT("Server go to listen mode failed\r\n")); closesocket(gserversock); return 0; Print(TEXT("Socket 0x%08x ready for connection with %hs family, %hs type, on port %hs\r\n"), gserversock, (AI->ai_family == AF_INET)? "AF_INET" : ((AI->ai_family == AF_INET6)? "AF_INET6" : "UNKNOWN"), (AI->ai_socktype == SOCK_STREAM)? "TCP" : ((AI->ai_socktype == SOCK_DGRAM)? "UDP" : "UNKNOWN"), gportnumber); freeaddrinfo(addrinfo); cbremoteaddrsize = sizeof(ssremoteaddr); while (1) // Wait for incomming data/connections gclientsock = accept(gserversock, (SOCKADDR*)&ssRemoteAddr, &cbremoteaddrsize); if(gclientsock == INVALID_SOCKET)

128 Print(TEXT("ERROR: accept() failed with error = %d\r\n"), WSAGetLastError()); return 0; Print(TEXT("Accepted TCP connection from socket 0x%08x\r\n"), gclientsock); // Receive data from a client memset(chbuffer, 0, sizeof(chbuffer)); cbtotalrecvd = 0; do cbremoteaddrsize = sizeof(ssremoteaddr); cbxfer = recvfrom(gclientsock, chbuffer + cbtotalrecvd, sizeof(chbuffer) - cbtotalrecvd, 0, (SOCKADDR *)&ssremoteaddr, &cbremoteaddrsize); cbtotalrecvd += cbxfer; while(cbxfer > 0 && cbtotalrecvd < sizeof(chbuffer)); if(cbxfer == SOCKET_ERROR) Print(TEXT("ERROR: Couldn't receive the data! Error = %d\r\n"), WSAGetLastError()); return 0; else if(cbxfer == 0) Print(TEXT("ERROR: Didn't get all the expected data from the client!\r\n")); return 0; else cbremoteaddrsize = sizeof(ssremoteaddr); getpeername(gclientsock, (SOCKADDR *)&ssremoteaddr, &cbremoteaddrsize); // get client address if (getnameinfo((sockaddr *)&ssremoteaddr, cbremoteaddrsize, szremoteaddrstring, sizeof(szremoteaddrstring), NULL, 0, NI_NUMERICHOST)!= 0) strcpy(szremoteaddrstring, ""); Print(TEXT("Received from client(%hs) <---- %hs\r\n"), szremoteaddrstring, chbuffer); // Echo the data back to the client cbxfer = 0; cbxfer = sendto(gclientsock, chbuffer, cbtotalrecvd, 0, (SOCKADDR *)&ssremoteaddr, cbremoteaddrsize); if(cbxfer!= cbtotalrecvd) Print(TEXT("ERROR: Couldn't send the data! error = %d\r\n"), WSAGetLastError()); return 0; else Print(TEXT("Send Echo to client ---->%hs\r\n"), chbuffer); return 0; //================================================================================ = === ======== int _tmainclient(pvoid parg) int cbxfer, cbtotalrecvd; char chbuffer[100], *pbuffer; UINT cnttext;

129 TCHAR tchbuffer[100]; HWND hdlg; hdlg = (HWND)pArg; // Send data to the server cnttext = GetDlgItemText(hDlg, ID_SENDTEXT, tchbuffer, sizeof(tchbuffer)); pbuffer = ConvertUnicodeToAscii(tchBuffer, cnttext); memset(chbuffer, 0, sizeof(chbuffer)); sprintf(chbuffer, "%s", pbuffer); cbxfer = 0; cbxfer = sendto(gsock, chbuffer, sizeof(chbuffer), 0, gai->ai_addr, gai->ai_addrlen); if(cbxfer!= sizeof(chbuffer)) Print(TEXT("ERROR: Couldn't return 0; send the data! error = %d\r\n"), WSAGetLastError()); Print(TEXT("Sent to server(%hs) ----> %hs\r\n"), gipaddress, chbuffer); // Receive the echo'd data back from the server cbtotalrecvd = 0; memset(chbuffer, 0, sizeof(chbuffer)); do cbxfer = recvfrom(gsock, chbuffer + cbtotalrecvd, sizeof(chbuffer) - cbtotalrecvd, 0, NULL, NULL); cbtotalrecvd += cbxfer; while(cbxfer > 0 && cbtotalrecvd < sizeof(chbuffer)); if(cbxfer == SOCKET_ERROR) Print(TEXT("ERROR: Couldn't receive the data! Error = %d\r\n"), WSAGetLastError()); return 0; else if(cbtotalrecvd!= sizeof(chbuffer)) Print(TEXT("ERROR: Server didn't send back all the expected data!\r\n")); return 0; Print(TEXT( "Received Echo from server(%hs) <---- %hs\r\n"), gipaddress, chbuffer); return 0; //= ========= ============================================== ========== ======= char * ConvertUnicodeToAscii(LPCWSTR p,int l) char *Out; int nchars; nchars=widechartomultibyte(cp_acp, 0,p,l,NULL,0,NULL,NULL); if(nchars!=0) Out=(char *)malloc(nchars+1); if(out!=null) memset(out,0,nchars+1); nchars=widechartomultibyte(cp_acp, 0,p,l,Out,nChars,NULL,NULL); if(nchars==0)

130 free(out); Out=NULL; else Out=NULL; if(out==null) Out=(char *)malloc(size of(char) ); *Out=0; return Out; DWORD WINAPI ClientStart (PVOID parg) ADDRINFO Hints; HWND hdlg; hdlg = (HWND)pArg; memset(&hints, 0, sizeof(hints)); Hints.ai_family = AF_UNSPEC; Hints.ai_socktype = SOCK_STREAM; if(getaddrinfo(gipaddress, gportnumber, &Hints, &gaddrinfo)) Print(TEXT(" ERROR: Couldn't get resolve the server name/address!")); return 0; // Attempt to connect to each address until we find one that succeeds gai = gaddrinfo; gsock = socket(gai->ai_family, gai->ai_socktype, gai->ai_protocol); if (gsock!= INVALID_SOCKET) if (gai->ai_socktype == SOCK_STREAM) if(connect(gsock, gai->ai_addr, gai->ai_addrlen) == SOCKET_ERROR) Print(TEXT("Connect to server failed\r\n")); closesocket(gsock); return 0; // connect() succeeded else Print(TEXT("Client create socket failed\r\n")); return 0; return 1;

131 4.3 档案系统及登录信息 Windows CE 的档案应用程序开发接口 (file API) 是继承使用标准 Win32 的 API Win32 的 API 是基于 Windows NT 的, 所以标准 Win32 的档案存取及维护 API 常会有一些参数是用来设定安全性及权限 而在 Windows CE 不支持像 Windows NT/XP 完整复杂的安全性及权限, 所以这些有关安全性及权限的参数通常必须填入 NULL 值 Windows CE 不像 Windows XP 支持交错式读写 (overlapped I/O), 所以开启档案或装置时不能使用 FILE_FLAG_OVERLAPPE 属性 另外,Windows CE 遵循标准 Win32, 所以也支持长档名, 一般最长可到 260 个字符 Windows CE 不像 PC 一样使用磁盘驱动器代码, 例如, C: 或 D:, 所有的贮存媒体例如 Compact Flash memory card 或 USB 贮存碟等, 都是以一个目录的型态出现 例如, 本书使用的泓格 Windows CE 工业用控制器支持 Compact Flash memory card 和 USB 贮存碟, 当其开机后在档案总管中会看到 Compact Flash memory card 是以一个名为 \Compact Flash 的目录出现, 而 USB 贮存碟则是出现一个 \Hard Disk 的目录 Win dows CE 支持几乎所有 Windows XP 的档案读写功能, 例如 CreateFile, ReadFile, WriteFile, and CloseFile 等 但是还是有一些不一样的事项,Windows CE 应用程序开发者要特别留意 例如, 标准 C 函式库的档案读写功能像 fopen, fread, 和 fprintf 等,Windows CE 就不支持 同样地, 旧有的 Win16 档案读写功能像 _lread, _lwrite, and _llseek 等也不支持 Windows CE 的档案存取方式是 handle-based, 当档案被成功的开启后会传回一个 handle, 接下来的读写动作都必须传入 handle 这个参数, 最后当完成所有读写动作后, 必须关闭这个档案的 handle 下表介绍所有常用的档案存取函式, 依字母顺序排列 : Programming element Description CeGetFileNotificationInfo 读取档案感测信息 CloseHandle 关闭已开启的 handle CopyF ile 复制一个即有档案 CreateDirectory 建立一个目录 CreateFi le 建立或开启一个档案 通讯端口 (COM port) 装置 服务 或操作台 (console) DeleteAndRenameFile 将来源档案的内容复制到目的档案后再删除来源档案 DeleteFile 删除档案 FindClose 寻找一个特定档案或 handle 并予以关闭 FindCloseChangeNotification 停止改变感测 handle 监控 (notification handle monitoring) FindFirstFileEx 在一个目录中寻找一个目称和属性符合的档案 FindFirstChangeNotification 建立一个变更感测 handle(change notification handle) 并设定初始变更感测过滤条件

132 FindFirstFile 在一个目录中寻找一个目称符合的档案 FindNextChangeNotification 要求操作系统在下一次有相关改变时发出变更感测的信号 FindNextFile 继续上一次 FindFirstFile 函式的呼叫后再作档案寻找 FlushFileB uffers 清空档案暂存 (buffer), 并把暂存的资料实际写到档案里 GetDiskFreeSpaceEx 读取磁盘驱动器可用容量 G etfileattributes 謮取档案或目录属性 GetFileAttributesEx 謮取档案或目录属性 GetFileInformationByHandle 謮取档案信息 GetFileSize 謮取档案大小 GetStoreInformation 将一个对象贮存 (object store) 的容量大小及剩余容量大小等信 息贮存到结构 STORE_INFORMATION 后传回 GetTempPath 读取暂存盘所在目录路径名称 GetTempFileName 命名一个暂存盘 MoveFile 重新命名一个档案或目录 ReadFile 读取档案内容, 从目前档案指针 (file pointer) 开始读起 RemoveDirectory 删取一空白目录 SetEndOfFile 在目前档案指针 (file pointer) 所在设定档案结束 SetFileAttributes 设定档案属性 SetFilePointer 设定档案指针 (file pointer) WriteFile 写入资料到档案 建立和开启档案 使用 CreateFile 函式来建立一个新档案或是开启一个已经存在的档案 : HANDLE CreateFile (LPCTSTR lpfilename, DWORD dwdesiredaccess, DWORD dwsharemode, LPSECURITY_ATTRIBUTES lpsecurityattributes, DWORD dwcreationdistribution, DWORD dwflagsandattributes, HANDLE htemplatefile); 参数 : lpfilename 填入字符串指定欲建立或开启的档案的档名 文件名必须为完整路径名, 若未指定路径, 则预设为装置的根目录 DwDesiredAccess

133 使用 dwdesiredaccess 参数来设定档案的存取模式 当设定为 GENERIC_READ 时, 可对档案有读取权利 当设定为 GENERIC_WRITE 时, 可对档案有写入权利 或可同时设定为 GENERIC_READ 和 GENERIC_WRITE, 则同时对档案有读取和写入的功能 dwsharemode dwsharemode 参数可设定档案的共享原则, 即设定其它应用程序或处理程序 (process) 是否可以同时对档案作存取动作 lpsecurityattributes Windows CE 未支持此功能, 必须填入 NULL dwcreationdistribution 使用 dwcreationdistribution 参数决定建立或开启的方式, 可使用以下的旗标 (flags) : CREATE_NEW : 建立一个新的档案, 若档名已存在, 函式回传回失败 CREATE_ALWAYS : 建立一个新的档案, 或开启一已存在的档案并截断 (truncate) 档案已有资料 OPEN_EXISTING : 开启一已存在的档案 OPEN_ALWAYS : 建立一个新的档案 或开启一已存在的档案但不截断 (truncate) 档案已有资料, 而继续从最后开始加入资料 TRUNCATE_EXISTING : 开启一已存在的档案并截断 (truncate) 档案已有资料 若档案不存在, 函式传回失败 dwflagsandattributes 使用 dwflagsandattributes 参数来设定档案的属性旗标 (attribute flags) Windows CE 支持以下的属性旗标 (attribute flags) : FILE_ATTRIBUTE_NORMAL : 预设的属性, 可被其它属性取代 FILE_ATTRIBUTE_READONLY : 设定档案为只读 (read-only) 模式 FILE_ATTRIBUTE_ARCHIVE 设定档案为原始 (archive) 模式 FILE_ATTRIBUTE_SYSTEM 设定档案为系统 (system) 档案 FILE_ATTRIBUTE_HIDDEN 设定档案为隐藏 (hidden) 模式 FILE_FLAG_WRITE_THROUGH 对档案的写入会直接写入到档案系统或例如硬盘 Compact Flash memory card 等的储存媒体 (storage medium), 而不会透过快存 (cashe) 的方式 FILE_FLAG_RANDOM_ACCESS 设定档案的存取模式为随机 (random) 而非循序 (sequential) 为隐藏 (hidden) 模式, 操作系统会因此而决定最合适的快取策略 (caching strategy) Windows CE 不支持以下 Win 32 在 Windows Me or Windows XP 有支持的档案模式 : FILE_ATTRIBUTE_OFFLINE, FILE_FLAG_OVERLAPPED, FILE_FLAG_NO_BUFFERING, FILE_FLAG_SEQUENTIAL_SCAN, FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_BACKUP_SEMANTICS, and FILE_FLAG_POSIX_SEMANTICS htemplatefile Windows CE 未支持此功能, 必须填入 NULL

134 传回值 : 当呼叫 CreateFile 函式成功时会传回被开启的档案的处理数 (handle) 若呼叫函式失败, 会传回 INVALID_HANDLE_VALUE 当函式呼叫失败时, 可呼叫 GetLastError 函式来取得错误代码并从微软的线上技术文件中查询错误代码的意义 本章最后有附上错误代码对照表, 读者亦可在 eembedded Visual C++ 的求助 (help) 文件中的索引 (index) 中键入 Error Value 即可找到此对照表进入点如下 : 读取和写入档案 Windows CE 支持标准 Win32 函式像 ReadFile 函式和 WriteFile 函式来读取和写入档案 使用 ReadFile 函式来读取档案 : BOOL ReadFile( HANDLE hfile, LPVOID lpbuffer, DWORD nnumberofbytestoread, LPDWORD lpnumberofbytesread,

135 LPOVERLAPPED lpoverlapped ); 参数 : hfile 指定欲读取的档案的处理数 (handle) 档案必须在开启时设定具备有 GENERIC_READ 的存取模式 lpbuffer 指定一个指向缓冲区 (buffer) 的指针, 该缓冲区 (buffer) 用来接收从档案读取到的资料 nnumberofbytestoread 指定欲读取数据的字节个数 lpnumberofbytesread 指定一个指向一个 DWORD 型别的变量, 用来传回实际读取到的资料的字节个数 呼叫函式之前必须先把此变量值设成 0 lpoverlapped Windows CE 不支持交错式输出入 (overlapped I/O), 所以传入 NULL 值 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 批注 : 被读取的资料是从档案的档案指针 (file pointer) 所在的位移处 (offset) 开始读取, 待成功读取完成后, 档案指针 (file pointer) 会依被读取的资料的字节个数来调整档案指针 (file pointer) 指向的位置 呼叫 ReadFile 函式读取资料时, 不能读超过档案结尾 (end of file), 如果欲读取的资料的字节个数大于档案指针 (file pointer) 到档案结尾 (end of file) 的字节个数时, 函式呼叫仍会成功执行完成, 但是传回的实际读取到的资料的字节个数会是档案指针 (file pointer) 到档案结尾 (end of file) 的字节个数时, 因此必须养成习惯地检查 lpnumberofbytesread 所传回的值是否等于欲读取数据的字节个数, 以检查是否已经读到了档案的结尾 (end of file) 另外, 如果在档案指针 (file pointer) 已指向档案结尾 (end of file) 时呼叫 ReadFile 函式读取资料, 函式呼叫仍会成功执行完成, 但是传回的实际读取到的资料的字节个数会是 0 使用 WriteFile 函式来写入档案 : BOOL WriteFile( HANDLE hfile, LPCVOID lpbuffer, DWORD nnumberofbytestowrite, LPDWORD lpnumberofbyteswritten, LPOVERLAPPED lpoverlapped

136 ); 参数 : hfile 指定欲写入的档案的处理数 (handle) 档案必须在开启时设定具备有 GENERIC_WRITE 的存取模式 lpbuffer 指定一个指向缓冲区 (buffer) 的指针, 该缓冲区 (buffer) 用来储存欲写入到档案的资料 nnumberofbytestowrite 指定欲写入数据的字节个数 当传入 0 时, 会完成零写入的动作 (null write operation), 一个写入的动作 (null write operation) 不会写入任何资料至档案, 但是会改变档案的时间卷标 (time stamp) lpnumberofbyteswritten 指定一个指向一个 DWORD 型别的变量, 用来传回实际写入的资料的字节个数 呼叫函式之前必须先把此变量值设成 0. lpoverlapped Windows CE 不支持交错式输出入 (overlapped I/O), 所以传入 NULL 值 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 批注 : 被写入的资料是从档案的档案指针 (file pointer) 所在的位移处 (offset) 开始写入, 待成功写入完成后, 档案指针 (file pointer) 会依被写入的资料的字节个数来调整档案指针 (file pointer) 指向的位置 呼叫 WriteFile 函式写入资料时, 可先呼叫 SetEndOfFile 函式来指定档案结尾 (end of file) 以决定后续的 WriteFile 函式的写入动作是截断 (truncate) 档案再写入还是从档案结尾 (end of file) 继续增加 (extend) 写入的资料 稍后将介绍 SetEndOfFile 函式的用法 另外, 为确保写入的资料已写入贮存装置 (storage device) 而不是只写入快存或动态存取内存, 可以呼叫 FlushFileBuffers 函式如下 : WINBASEAPI BOOL WINAPI FlushFileBuffers (HANDLE hfile); 移动档案指针 (File Pointer) 使用 SetFilePointer 函式来移动档案指针 (File Pointer) 的用法如下 : DWORD SetFilePointer( HANDLE hfile,

137 LONG ldistancetomove, PLONG lpdistancetomovehigh, DWORD dwmovemethod ); 参数 : hfile 指定欲移动档案指针 (File Pointer) 的档案的处理数 (handle) 档案必须在开启时设定具备有 GENERIC_WRITE 或 GENERIC_READ 的存取模式 ldistancetomove 指定欲移动的位移值, 正数表示往档尾位移, 负数表示往档头位移 lpdistancetomovehigh Windows CE 不支持, 所以传入 NULL 值 dwmovemethod 指定档案指针 (File Pointer) 从何处开始位移, 有以下的选项 : Value Description FILE_BEGIN 从档头开始 FILE_CURRENT 从目前所在位置 FILE_END 从档尾开始 传回值 : 函式呼叫执行失败时, 传回值 0xFFFF FFFF; 函式呼叫及执行成功时, 传回值为成功移动后的档案指针 (File Pointer) 所在位置 当函式呼叫执行失败时可透过呼叫 GetLastError 函式来取得错误代码 批注 : 欲取得目前档案指针 (File Pointer) 所在位置, 可呼叫此函式时设定欲位移值为 0 如下 : ncurrfileptr = SetFilePointer (hfile, 0, NULL, FILE_CURRENT); 关闭档案 如何关闭档案如下所示 : BOOL CloseHandle( HANDLE hobject );

138 参数 : hobject 指定已开启而欲关闭的档案的处理数 (handle) 传回值 : 传回值为 0 时, 表示函式呼叫及执行成功 ; 传回值不为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 截断 (truncate) 档案当要截断 (truncate) 档案时, 可以先移动档案指针 (File Pointer) 到欲截断的地方再呼叫 SetEndOfFile 函式 : BOOL SetEndOfFile( HANDLE hfile ); 参数 : hfile 指定欲截断 (truncate) 的档案的处理数 (handle) 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 取得档案信息当知道一个档案或目录名称时, 可呼叫 GetFileAttributes 函式来取得档案或目录的详细信息 : DWORD GetFileAttributes( LPCTSTR lpfilename ); 参数 : lpfilename 填入字符串指定档案或目录的名称 文件名必须为完整路径名, 若未指定路径, 则预设

139 为装置的根目录 档案或目录的名称的最大长度由 MAX_PATH 决定 传回值 : 传回值为 0 时, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 传回值不为 0 时, 表示函式呼叫及执行成功, 传回值为一个 DWORD 值, 内含档案或目录的详细信息, 可使用逻辑 OR 来比较如下表的各个属性值以取得确切的档案或目录信息 : 属性值说明 FILE_ATTRIBUTE_ARCHIVE FILE_ATTRIBUTE _COMPRESSED FILE_ATTRIBUTE_DIRECTORY F ILE_ATTRIBUTE_ENCRYPTED FILE_ATTRIBUTE_HIDDEN FILE_ATTRIBUTE_INROM 档案或目录为原始 (archive) 模式, 应用程序使用这属性来标明要备份 (backup) 或移除的档案或目录 档案或目录内的子目录 (subdirectories) 及档案使用压缩 (compressed) 传入的文件名是目录档案或者目录内的子目录及档案是编译成密码 (encrypted) 档案或者目录是隐藏模式 在档案总管检视所有档案或目录时不会显示, 除非用档案总管检视时使用列出 所有档案或目录 ( 包含隐藏的档案或者目录 ), 才会显示 档案是操作系统的档案, 贮存于 ROM, 只能读取, 不能更改 FILE_ATTRIBUTE_NORMAL 档案或者目录不使用其具备他属性 FILE_ATTRIBUTE_READONLY 档案或者目录为只读 (read-only) 模式, 应用程序只能读取, 不能更改或删除 FILE_ ATTRIBUTE_ROMMODULE 档案是操作系统的档案, 贮存于 ROM, 并直接从 ROM 执行, 而非先复制至 RAM 这种类型的档案, 不能使用 CreateFile 函式来开启, 必须使用 LoadLibrary 函式或 CreateProcess 函式来存取这种类型的档案 FILE_ATTRIBUTE_ROMSTATICREF 档案是动态连结函式库模块 (DLL module), 并且至少有一个静态参考到操作系统映像 (OS image) 里的其它档案, 此动态连结函式库模块 (DLL module) 的功能无法被复制到 RAM 的同一个动态连结函式库模块 (DLL module) 档案所取代 当一个档案有这种属性时, 必定也同时具备 FILE_ATTRIBUTE_INROM 和 FILE_ATTRIBUTE_ROMMODULE 属性 FILE_ATTRIBUTE_SYSTEM 档案或者目录系统文件, 即操作系统专用的或属于操 作系统的一部份

140 FILE_ATTRIBUTE_TEMPORARY 档案用作临时性存储, 档案系统为更快速的存取而把资料贮存于内存中而非马上贮存入类似贮存卡 (storage card) 的贮存装置 应用程序在不再使用临时性档案时, 要马上关闭临时性档案 批注当一个目录具备 FILE_ATTRIBUTE_TEMPORARY 和 FILE_ATTRIBUTE_DIRECTORY 属性时, 即表示该目录为类似记忆卡 (Compact Flash card) 或贮存卡 (storage card) 等贮存装置 (storage device) 的根目录 (root directory) 除了使用 GetFileAttributes 函式取得档案或目录的信息外, 当要设定档案或目录的属性时可使用 SetFileAttributes 函式如下 SetFileAttributes 函式传入欲设定的档案或目录名称及欲设定的属性 BOOL SetFileAttributes (LPCTSTR lpfilename, DWORD dwfileattributes); 档案时间 (File Times) 标准的 Win32 应用程序开发界面 (API) 支持三个档案时间 : 建立 (created) 时间 最后一次被存取时间 及最后一次被更改时间 我们可以使用 GetFileTime 函式来取得档案时间如下 : BOOL GetFileTime( HANDLE hfile, LPFILETIME lpcreationtime, LPFILETIME lplastaccesstime, LPFILETIME lplastwritetime ); 参数 : hfile 指定档案的处理数 (handle) 档案必须在开启时设定具备有 GENERIC_READ 的存取模式 lpcreationtime 传入一个指向 FILETIME 结构 (structure) 的指针, 该结构用来接收档案的建立 (created) 时间 若不需取得档案的建立 (created) 时间, 可传入 NULL lplastaccesstime 传入一个指向 FILETIME 结构 (structure) 的指针, 该结构用来接收档案最后一次被存取的时间 若不需取得档案最后一次被存取的时间, 可传入 NULL lplastwritetime 传入一个指向 FILETIME 结构 (structure) 的指针, 该结构用来接收档案最后一次被更改的时间 若不需取得档案最后一次被更改的时间, 可传入 NULL

141 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 批注 : 除了使用 GetFileTime 函式来取得档案时间外, 我们也可以使用 SetFileTime 函式来设定档 案时间如下 : BOOL SetFileTime (HANDLE hfile, const FILETIME *lpcreationtime, const FILETIME *lplastaccesstime, const FILETIME *lplastwritetime); 另外, 我们也可以使用以下的时间转换函式来将档案时间 (FILETIME ) 转换成系统时间 (SYSTEMTIME), 或将系统时间 (SYSTEMTIME) 转换成本地时间 : BOOL FileTimeToSystemTime (const FILETIME *lpfiletime, LPSYSTEMTIME lpsystemtime); BOOL FileTimeToLocalFileTime (const FILETIME *lpfiletime, LPFILETIME lplocalfiletime); 档案大小 (File Size) 及其它档案信息我们可以使用 GetFileSize 函式来取得档案大小如下 : DWORD GetFileSize( HANDLE hfile, LPDWORD lpfilesizehigh ); 参数 : hfile 指定档案的处理数 (handle) 档案必须在开启时设定具备有 GENERIC_READ 或 GENERIC_ WRITE 的存取模式 lpfilesizehigh 传入一个指向 DWORD 的指针, 用来接收档案大小的高字符 (high-order word) 如果不需要, 亦可传入 NULL 传回值 : 函式呼叫执行失败时, 传回值 0xFFFFFFFF; 函式呼叫及执行成功时, 传回值为档案大小的

142 低字符 (low-order DWORD 当函式呼叫执行失败时可透过呼叫 GetLastError 函式来取得 错误代码 批注 : 呼叫 G etfilesize 函式会传回一个 DWORD 表示档案大小, 当档案小于 4 GB 时, 可以不使用 lpfilesizehigh 参数而传入 NULL, 当档案大于 4 GB 时, 可以使用 lpfilesizehigh 参数来传回档案大小的高字符 (high-order DWORD) 另外, 应用程序也可以使用以下的 GetFileInformationByHandle 函式来取得档案的额外信息 BOOL GetFileInformationByHandle (HANDLE hfile, LPBY_HANDLE_FILE_INFORMATION lpfileinformatio ); 而 BY_HANDLE_FILE_INFORMATION 结构的定义如下 : typedef struct _BY_HANDLE_FILE_INFORMATION DWORD dwfileattributes; FILETIME ftcreationtime; FILETIME ftlastaccesstime; FILETIME ftlastwritetime; DWORD dwvolumeserialnumber; DWORD nfilesizehigh; DWORD nfilesizelow; DWORD nnumberoflinks; DWORD nfileindexhigh; DWORD nfileindexlow; DWORD dwoid; BY_HANDLE_FILE_INFORMATION; 管理档案系统 到目前为止, 我们介绍了如何开启档案 读取档案 写入档案及取得档案相关信息等, 接下来, 我们将介绍例如 CopyFile, MoveFile, DeleteFile, FindFirstFile 和 CreateDirectory, RemoteDirectory 等函式 我们可以使用这些函式来达到复制档案 移动档案 删除档案 建立目录 删除目录和寻找档案等对档案系统的管理动作 复制档案 使用 C opyfile 函式复制档案如下 :

143 BOOL CopyFile( LPCTSTR lpexistingfilename, LPCTSTR lpnewfilename, BOOL bfailifexists ); 参数 : lpexistingfilename 传入一个字符串指针, 用来指定一个已存在的文件名称 lpnewfilename 传入一个字符串指针, 用来指定一个新的文件名称 bfailifexists 设定当指定的新的档案已存在时的处理动作 当指定的新的档案已存在, 若此参数设为 TRUE, 则函式返回并传回失败 ; 若此参数设为 FALSE, 则函式会继续执行并覆写 (overwrite) 这已存在的新文件名称 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 重新对档案命名使用 MoveFile 函式重新对档案命名如下 : BOOL MoveFile( LPCTSTR lpexistingfilename, LPCTSTR lpnewfilename ); 参数 : lpexistingfilename 传入一个字符串指针, 用来指定一个已存在的文件名称 lpnewfilename 传入一个字符串指针, 用来指定一个新的文件名称 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码

144 删除档案使用 DeleteFile 函式删除档案如下 : BOOL DeleteFile( LPCTSTR lpfilename ); 参数 : lpfilename 传入一个字符串指针, 用来指定一个已存在而欲删除的文件名称 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 建立目录使用 CreateDirectory 函式建立目录如下 : BOOL CreateDirectory( LPCTSTR lppathname, LPSECURITY_ATTRIBUTES lpsecurityattributes ); 参数 : lppathname 传入一个字符串指针, 用来指定欲建立的目录名称 lpsecurityattributes Windows CE 不支持, 设成 0 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 删除目录使用 RemoveDirectory 函式删除目录如下 : BOOL RemoveDirectory( LPCTSTR lppathname

145 ); 参数 : lppathname 传入一个字符串指针, 用来指定欲删除的目录名称 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 寻找档案 Windo ws CE 和 Windows XP 一样支持标准和基本的 API 例如 FindFirstFile, FindNextFile, 和 FindClose 等函式来提供应用程序寻找档案的功能如下 : HANDLE FindFirstFile( LPCTSTR lpfilename, LPWIN32_FIND_DATA lpfindfiledata ); 参数 : lpfilename 指定一个字符串指针, 用来设定欲寻找的档案或目录 字符串中可以使用万用字符 (wildcard characters), 例如 * 和? 举例说明, 当字符串为 \Windows\file?.txt 表示欲寻找的档案可能是 \Windows\file1.txt \Windows\file2.txt 或 \Windows\fileA.txt 等, 而当字符串为 \Windows\*.txt 则欲寻找在 Windows 目录下的任何附属档名为.txt 的档案 lpfindfiledata 指定一个指向 WIN32_FIND_DATA 结构的指针, 用来接收档案或目录的信息 传回值 : 函式呼叫及执行成功时会传回寻找处理数 (search handle), 应用程序接下来的呼叫 FindNextFile 函式和 FindClose 函式必须传入此寻找处理数 (search handle) 当传回值为 INVALID_HANDLE_VALUE 时, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 批注 : 当找不到相关档案时, 函式传回值为 INVALID_HANDLE_VALUE, 相反地, 若找到相关档案, 会将找到的相关档案的信息存入 WIN32_FIND_DATA 结构传回

146 WIN32_FIND_DATA 结构定义如下 : typedef struct _WIN32_FIND_DATA DWORD dwfileattributes; FILETIME ftcreationtime; FILETIME ftlastaccesstime; FILETIME ftlastwritetime; DWORD nfilesizehigh; DWORD nfilesizelow; DWORD dwoid; WCHAR cfilename[ MAX_PATH ]; WIN32_FIND_DATA; 当应用程序寻找到第一个符合的档案后想要再继续往下寻找第二个 第三个等其它符合的档案时, 可呼叫 FindNextFile 函式如下 : BOOL FindNextFile ( HANDLE hfindfile, LPWIN32_FIND_DATA lpfindfiledata ); 参数 : hfindfile 传入呼叫 FindFirstFile 函式执行成功时传回的寻找处理数 (search handle) lpfindfiledata 指定一个指向 WIN 32_FIND_DATA 结构的指针, 用来接收档案或目录的信息 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 当寻找不到相关档案时, 传回值为是 0, 透过呼叫 GetLastError 函式取得的错误代码会是 ERROR_ NO_MORE_FILES 最后, 当应用程序欲结束寻找时, 必须呼叫 FindClose 函式如下 : BOOL FindClose( HANDLE hfindfile ); 参数 : hfindfile 传入呼叫 FindFirstFile 函式执行成功时传回的寻找处理数 (search handle)

147 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 以下所列范例程序片断说明以上寻找档案的整个流程, 本范例程序片断会寻找 Windows 目录下所有的档案, 并加总所有档案的大小 : WIN32_FIND_DATA fd; HANDLE hfind; INT ntotalsize = 0; // Start search for all files in the windows directory. hfind = FindFirstFile (TEXT ("\\windows\\*.*"), &fd); // If a file was found, hfind will be valid. if (hfind!= INVALID_HANDLE_VALUE) // Loo p through found files. Be sure to process file // found with FindFirstFile before calling FindNextFile. do // If found file is not a directory, add its size to // the total. (Assume that the total size of all files // is less than 2 GB.) if (!(fd.dwfileattributes & FILE_ATTRIBUTE_DIRECTORY)) ntotalsize += fd.nfilesizelow; // See if another file exists. while (FindNextFile (hfind, &fd)); // Clean up by closing file search handle. FindClose (hfind);

148 登录或注册 (Registry) API 注册 (registry) 是系统内部的数据库, 用来贮存装置 系统 驱动程序 应用程序等的设定参 数 W indows CE 的注册 (registry) 的结构和存取方式与 Windows XP/NT 类似, 但并不如 Windows XP/NT 般复杂, 所以 Windows CE 存取和维护注册 (registry) 的应用程序开发接口 (API) 也并不完全跟 Windows XP/NT 相同或遵照 Windows XP/NT 与微软其它的 Windows 操作系统一样, 注册 (registry) 是由一大堆键 (key) 及值 (value) 所组成, 键 (key) 可以再内含键 (key) 及值 (value), 并有阶层的结构 Windows CE 的注册 (registry) 主要由三大根键 (root key) 所组成 : Key Contents HKEY_LOCAL_MACHINE 贮存硬件及驱动程序的配置信息 HKEY_CURRENT_USER 贮存使用者相关和喜好设定的信息 HKEY_ CLASSES_ROOT 贮存档案类形匹配及 OLE 配置信息 下图是本书所使用的泓格 Wincon-8000 Windows CE.NET 工业用控制器内的注册 (registry) 内 容 Windows CE 的注册应用程序开发接口 (registry API) 提供程序开发者可以去读取和修改维护 Windows CE 装置的注册 下表是所有常用的 API: 函式 说明 RegCloseKey 释放金钥 (key) 的处理数 (handle) RegCreateKe yex 建立金钥 (key), 若金钥 (key) 已存在则开启此金钥 (key) RegDeleteKey 删除金钥 (key) RegDeleteVal ue 删除金钥 (key) 中的一个指定值 RegEnumKeyEx 列举金钥 (key) 中的子金钥 (key) RegEnumValue 列举已开启金钥 (key) 中的所有值 RegFlushKey 将已开启金钥 (key) 中的所有值写入登录中 RegOpenKeyEx 开启此金钥 (key) RegQueryInfoKey 取得金钥 (key) 的资源 RegQu eryvalueex 取得金钥 (key) 中的一个指定值的资料型别及内容等 RegSetValueEx 设定金钥 (key) 中的一个指定值的内容

149 建立或开启金钥 (key) 使用 RegOpenKeyEx 函式开启金钥 (key) 如下 : LONG RegOpenKeyEx( HKEY hkey, LPCWSTR lpsubkey, DWORD uloptions, REGSAM samdesired, PHKEY phkresult ); 参数 : hkey 指定以下预设的根金钥 (root key): HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE HKEY_USERS Windows CE 不支持其它像 HKEY_CURRENT_CONFIG, HKEY_PERFORMANCE_DATA, 或 HKEY_DYN_DATA 等的预设的根金钥 (root key) lpsubkey 指定一个字符串指针, 用来设定欲开启的子金钥 (subkey) uloptions Windows CE 不支持, 设为 0 samdesired Windows CE 不支持, 设为 0 phkresult 传入一个处理数指针, 用来接收欲开启的金钥的处理数 (handle) 传回值 : 函式呼叫及执行成功时传回值为 ERROR_SUCCESS, 其它传回值表示函式呼叫及执行失败, 可在 Winerror.h 档案中找到错误代码及错误信息 批注 : 例如, 当应用程序欲开启以下子金钥 (subkey) :

150 HKEY_LOCAL_MACHINE\Software\Microsoft\Pocket Word,hKey 参数须指定为 HKEY_LOCAL_MACHINE, 而 lpsubkey 参数则需指定为 \Software\Microsoft\Pocket Word 以开启此子金钥 (subkey) 另外, 我们也可以呼叫 RegCreateKeyEx 函式来开启金钥如下 两个函式主要不同之处在于当欲开启的金钥不存在时,RegCreateKeyEx 函式会新建立此欲开启的金钥, 而 RegOpenKeyEx 函式不会新建立此不存在而欲开启的金钥并传回错误 使用 RegOpenKeyEx 函式建立或开启金钥 (key) 如下 : LONG RegCreateKeyEx( HKEY hkey, LPCWSTR lpsubkey, DWORD Reserved, LPWSTR lpclass, DW ORD dwoptions, REGSAM samdesired, LPSECURITY_ATTRIBUTES lpsecurityattributes, PHKEY phkresult, L PDWORD lpdwdisposition ); 参数 : hkey 指定以下预设的根金钥 (root key): HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE HKEY_USERS Windows CE 不支持其它像 HKEY_CURRENT_CONFIG, HKEY_PERFORMANCE_DATA, 或 HKEY_DYN_DATA 等的预设的根金钥 (root key) lpsubkey 指定一个字符串指针, 用来设定欲开启的子金钥 (subkey) Reserved Windows CE 不支持, 设为 0 lpclass 当要新建立时, 可设定 class 名, 或一般 class 都已存可设定为 NULL

151 dwoptions Windows CE 不支持, 设为 0 samdesired Windows CE 不支持, 设为 0 lpsecurityattributes Windows CE 不支持, 设为 NULL phkresult 传入一个处理数指针, 用来接收欲开启的金钥的处理数 (handle) lpdwdisposition 用来接收函式的处理状况 可比对下表的值来查验 : 值说明 REG_ C REATED_NEW_KEY 金钥不存在, 新建立此金钥 REG_OPENED_EXISTING_KEY 金钥已存在, 开启此金钥 传回值 : 函式呼叫及执行成功时传回值为 ERROR_SUCCESS, 其它传回值表示函式呼叫及执行失败, 可在 Winerror.h 档案中找到错误代码及错误信息 读取注册值 (Reading Registry Values ) 在成功开启金钥后, 可使用 RegQueryValueEx 函式来读取金钥的注册值如下 : LONG RegQueryValueEx( HKEY hkey, LPCWSTR lpvaluename, LPDWORD lpreserved, LPDWORD lptype, LPBYTE lpdata, LPDWORD lpcbdata ); 参数 : hkey 指定使用 RegCreateKeyEx 函式或 RegOpenKeyEx 函式成功开启的金钥, 或指定以下预设的根金钥 (root key): HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE

152 HKEY_USERS lpvaluename 传入一个字符串指针, 用来指定欲读取的注册值名称 lpreserved Windows CE 不支持, 设为 0 lptype 用来接收欲读取的注册值的资料型别 (data type), 有以下几种 : REG_ BINARY REG_DWORD REG_DWORD_LITTLE_ENDIAN REG_DWORD_BIG_ENDIAN REG_EXPAND_SZ REG_LINK REG_MULTI_SZ REG_NONE REG_RESOURCE_LIST REG_SZ lpdata 一个指向资料缓冲区的指针, 用来接收读取到的资料 lpcbdata 指定 lpdata 参数所指定的资料缓冲区的大小, 而当函式返回时则传回实际写入此缓冲区的字节个数 传回值 : 函式呼叫及执行成功时传回值为 ERROR_SUCCESS, 其它传回值表示函式呼叫及执行失败, 可在 Winerr or.h 档案中找到错误代码及错误信息 写入注册值 (Writing Registry Values) 在成功开启金钥后, 可使用 RegSetValueEx 函式来更改金钥的注册值如下 : LONG RegSetValueEx( HKEY hkey, LPCWSTR lpvaluename, DWORD Reserved, DWORD dwtype,

153 const BYTE *lpdata, DWORD cbdata ); 参数 : hkey 指定使用 RegCreateKeyEx 函式或 RegOpenKeyEx 函式成功开启的金钥, 或指定以下预设的根金钥 (root key): HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE HKEY_USERS lpvaluename 传入一个字符串指针, 用来指定欲写入的注册值名称 Reserved Windows CE 不支持, 设为 0 dwtype 用来指定接收欲写人的注册值的资料型别 (data type), 资料型别 (data type) 种类如上 RegQueryValueEx 函式中的说明 lpdata 一个指向资料缓冲区的指针, 资料缓冲区用来贮存欲写入的资料 cbdata 指定欲写入的数据的字节个数 传回值 : 函式呼叫及执行成功时传回值为 ERROR_SUCCESS, 其它传回值表示函式呼叫及执行失败, 可在 Winerror.h 档案中找到错误代码及错误信息 删除金钥和删除注册值我们可以使用 RegDeleteKey 函式来删除金钥如下 : LONG RegDeleteKey( HKEY hkey, LPCWSTR lpsubkey );

154 参数 : hkey 指定使用 RegCreateKeyEx 函式或 RegOpenKeyEx 函式成功开启的金钥, 或指定以下预设的根金钥 (root key): HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE HKEY_USERS lpsubkey 指定子金钥 (subkey) 名称, 必须为 hkey 参数所指定的金钥的子金钥 (subkey) 传回值 : 式呼叫及执行成功时传回值为 ERROR_SUCCESS, 其它传回值表示函式呼叫及执行失败, 可在 Winerror.h 档案中找到错误代码及错误信息 另外, 我们可以使用 RegDeleteValue 函式来删除金钥内的注册值如下 : LONG RegDeleteValue( HKEY hkey, LPCWSTR lpvaluename ); 参数 : hkey 指定使用 RegCreateKeyEx 函式或 RegOpenKeyEx 函式成功开启的金钥, 或指定以下预设的根金钥 (root key): HKEY_CLASSES_ROOT HKEY_CURRENT_USER HKEY_LOCAL_MACHINE HKEY_USERS lpvaluename 指定 hkey 参数所指定的金钥内欲删除的注册值 传回值 : 式呼叫及执行成功时传回值为 ERROR_SUCCESS, 其它传回值表示函式呼叫及执行失败, 可在 Winerror.h 档案中找到错误代码及错误信息

155 关闭金钥当应用程序使用完一个金钥后, 最后要呼叫 RegCloseKey 函式来释放相关资源如下 : LONG RegCloseKey( HKEY hkey ); 参数 : hkey 指定使用 RegCreat ekeyex 函式或 RegOpenKeyEx 函式成功开启的金钥 传回值 : 式呼叫及执行成功时传回值为 ERROR_SUCCESS, 其它传回值表示函式呼叫及执行失败, 可在 Winerror.h 档案中找到错误代码及错误信息 列举 (e numerates) 金钥及金钥内的注册值 在某些情况下, 我们可能想知道金钥内有那些子金钥及注册值, 我们可以使用 RegEnumKeyEx 函式如下 : L ONG RegEnumKeyEx( HKEY hkey, DWORD dwindex, LPWSTR lpname, LPDWORD lpcbname, L PDWORD lpreserved, LPWSTR lpclass, LPDWORD lpcbclass, PFILETIME lpftlastwritetime ); 参数 : hkey 指定使用 RegCreateKeyEx 函式或 RegOpenKeyEx 函式成功开启的金钥, 或指定以下预设的根金钥 (root key): HKEY_CLASSES_ROOT

156 HKEY_CURRENT_USER HKEY_LOCAL_MACHINE HKEY_USERS dwindex 指定子金钥的索引 (index), 第一次呼叫时必须传入 0, 接下来递增此索引 (index) 来指定其它的子金钥 lpname 传入一个缓冲区的指针, 此缓冲区用来接收读取到的子金钥的名称 lpcbname 传入 lpname 参数所指定用来接收读取到子金钥名称的缓冲区大小, 函式返回时回传回实际读取到的子金钥名称的字节大小 lpreserved 未使用, 设为 NULL. lpclass 传入一个缓冲区的指针, 此缓冲区用来接收读取到的子金钥的类别 (class) 名称, 通常不需要且设为 NULL lpcbclass 传入 lpclass 参数所指定用来接收子金钥 class 名称的缓冲区大小, 函式返回时回传回实际读取到的子金钥 class 名称的字节大小 lpftlastwritetime 未使用, 设为 NULL. 传回值 : 式呼叫及执行成功时传回值为 ERROR_SUCCESS, 其它传回值表示函式呼叫及执行失败, 可在 Winerror.h 档案中找到错误代码及错误信息 另外, 我们可以呼叫 RegEnumValue 函式来列举 (enumerates) 金钥内的注册值如下 : LONG RegEnumValue( HKEY hkey, DWORD dwindex, LPWSTR lpvaluename, LPDWORD lpcbvaluename, LPDWORD lpreserved, LPDWORD lptype, LPBYTE lpdata, LPDWORD lpcbdata );

157 ); 参数 : hkey 指定成功开启的金钥 dwindex 指定注册值的索引 (index), 第一次呼叫时必须传入 0, 接下来递增此索引 (index) 来指定其它的注册值 lpvaluename 传入一个字符串指针, 用来接收注册值的名称 lpcbvaluename 传入 lpvaluename 参数所指定用来接收注册值名称的缓冲区大小, 函式返回时回传回实际读取到的注册值名称的字节大小 lpreserved 未使用, 设为 NULL. lptype 接收注册值的资料型别, 可能是以下之一 : REG_BINARY REG_DWORD REG_DWORD_LITTLE_ENDIAN REG_DWORD_BIG_ENDIAN REG_EXPAND_SZ REG_LINK REG_MULTI_SZ REG_NONE REG_RESOURCE_LIST REG_SZ lpdata 一个指向资料缓冲区的指针, 用来接收读取到的注册值资料 lpcbdata 指定 lpdata 参数所指定的资料缓冲区的大小, 而当函式返回时则传回实际传回此缓冲区的字节个数 传回值 : 式呼叫及执行成功时传回值为 ERROR_SUCCESS, 其它传回值表示函式呼叫及执行失败, 可在 Winerr or.h 档案中找到错误代码及错误信息

158 4.4 处理程序及执行序微软 Windows CE.NET 是一个先暂式多任务操作系统 多任务处理 (Multitasking) 意指可以让使用者同时执行一个以上的应用程序 Windows CE 支持先占式多任务 (preemptive multita sking), 可以同时地执行多个执行绪 当一个处理程序有不只一个执行绪在执行, 操作系统 (OS) 会快速地在两者之间做切换来提供 CPU 和系统资源供他们运行, 以此来达到同时执行多个执行绪 虽然实际上 CPU 不可能同时给一个以上的执行绪来使用, 但经由快速的切换 CPU 的执行权给这多个执行绪, 感觉起来就好象这些执行绪或应用程序是同时在执行, 这就是多任务 (multitasking) 的基本原理 Windows CE 支持最多可有 32 个处理程序 (process) 可在系统中同时执行 而一个处理程序 (process) 可拥有多少个执行绪则是受限于虚拟内存 (virtual memory) 的安排 Microsoft Windows CE 操作系统的核心是一支名为 Nk.exe 的模块, 它提供系统核心服务供应用程序使用操作系统核心功能, 核心操作系统服务 (Core operati ng system services) 主要包括处理程序 (process) 执行绪(thread) 和内存管理 (memory management) 等如下图所示 : 所有 Windows CE 的应用程序都是由一个程序 (process) 和一个或更多的执行绪 (thread) 所组成 一个程序 (process) 是一个应用程序的运行例子 而执行绪 (thread) 是 Windows CE 操作系统分配处理器 (processor) 执行时间的基本单位 一个执行绪 (thread) 能够执行程序 (process) 中的任何一段执行码, 包括目前正由另一个执行绪 (thread) 执行中的任何一部分 程序 (Process)

159 一个程序 (process) 是一个运行中的应用程序, 包含了专用的虚拟地址空间 程序代码 (code) 数据(data) 及其它操作系统资源 (resource) 例如档案 管线 (pipes) 及同步对象 (synchronization objects) 等 一个程序 (process) 通常包含了一个或多个执行绪 (thread) 在此程序 (process) 的环境 (context) 里执行 程序 (process) 是一个运行中的应用程序, 或说是一个应用程序的运行实例 (instance) 每一个处理程序(process) 流程可以包含多个执行绪 (thread), 而且每个执行绪都有各自的执行路径 (path of execution) When the system starts, at least four processes are created: NK.EXE, which provides the kernel services; FILESYS.EXE, which provides file system services; GWES.EXE, which provides the GUI support; and DEVICE.EXE, which loads and maintains the device drivers for the system. 建立程序 (process) 使用 CreateProcess 函式来执行一个应用程序的同时也建立了该应用程序的一个运行实例, CreateProcess 函式的使用方式如下 : CreateProcess( LPCWSTR lpszimagename, LPCWSTR lpszcmdline, LPSECURITY_ATTRIBUTES lpsaprocess, LPSECURITY_ATTRIBUTES lpsathread, BOOL finherithandles, DWORD fdwcreate, LPVOID lpvenvironment, LPWSTR lpszcurdir, LPSTARTUPINFOW lpsistartinfo, LPPROCESS_INFORMATION lppiprocinfo ); 参数 : lpszimagename 传入一个字符串指针, 字符串指定要执行的应用程序名称, 应用程序名称可为完整路径名或相对路径名 应用程序名称若没有附属档名, 预设使用.EXE 为其附属档名, 若应用程序名称以. 结尾, 则不指定附属档名 Windows CE.NET 会依以下目录顺序寻找应用程序档案 : \windows \ 根目录 Windows CE 侦错模式所指定的目录

160 lpszcm dline 传入一个字符串指针, 字符串指定命令列 (command line) 当字符串设定 NULL 时表示使用 lpszimagename 参数所指定的执行档名称为命令列 (command line) lpsaprocess Windows CE 不支持, 填入 NULL lpsathread Windows CE 不支持, 填入 NULL finherithandles Windows CE 不支持, 填入 NULL fdwcreate 指定程序 (process) 建立的方式,Windows CE 只支持以下的选项, 可指定多个选项 Value Description CREATE_NEW_CONSOLE 新建立的程序 (process) 会使用新的控制台 (console), 而非继承其父亲的控制台 (console) 不能和 DETACHED_PROCESS 选项共享 CREATE_SUSPENDED 新建立的程序 (process) 的主要执行绪 (primary thread) 会被暂停 (suspended), 直到 ResumeThread 函式被呼叫才开始执行 DEBUG_PROCESS 呼叫函式的程序 (process) 是侦错器 (debugger), 新建立的程序 (process) 和其建立的其它子程序都是要被侦错的程序 (process), 而只有呼叫函式的侦错器 (debugger) 程序 (process) 可以呼叫 WaitForDebugEvent 函式 DEBUG_ONLY_THIS_PROCESS 和 DEBUG_PROCESS 类似, 但是只侦错新建立的程序 (process), 此新建立程序 (process) 再建立的其它子程序不会被侦错 若填入 0, 表示建立之般标准的程序 (process) lpvenvironment Windows CE 不支持, 填入 NULL lpszcurdir Windows CE 不支持, 填入 NULL lpsistartinfo Windows CE 不支持, 填入 NULL lppiprocinfo

161 指向 PROCESS_INFORMATION 结构的指针, 用来接收新建立程序 (process) 的信息 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透 过呼叫 GetLastError 函式来取得错误代码 批注 : 函式成功执行完成后, 会将新建立程序 (process) 的信息存入 lppiprocinfo 参数所指定的 PROCESS_INFORMATION 结构,PROCESS_INFORMATION 结构定义如下 : typedef struct _PROCESS_INFORMATION HANDLE hprocess; HANDLE hthread; DWORD dwprocessid; DWORD dwthreadid; PROCESS_INFORMATION; PROCESS_INFORMATION 结构成员 : hprocess : 程序 (process) 的处理数 (handle), 后续和程序相关的函式都必须传入此函式 hthread : 程序 (process) 的主要执行绪 (primary thread) dwprocessid : 程序 (process) 的标识符 (identifier) dwthreadid : 执行绪 (primary thread) 的标识符 (identifier) 关闭金钥 以下范例程序代码说明 CreateProcess 函式的使用方式 : TCHAR szfilename[max_path]; TCHAR szcmdline[64]; DWORD dwcreationflags; PROCESS_INFORMATION pi;

162 INT rc; lstrcpy (szfilename, TEXT ("MyApp1.exe")); lstrcpy (szcmdline, TEXT ("")); dwcreationflags = 0; rc = CreateProcess (szfilename, szcmdline, NULL, NULL, FALSE, dwcreationflags, NULL, NULL, NULL, &pi); if (rc) CloseHandle (pi.hthread); CloseHandle (pi.hprocess); 结束程序 (Terminating a Process) 使用 TerminateProcess 函式来结束如下 : BOOL TerminateProcess( HANDLE hprocess, DWORD uexitcode ); 参数 : hprocess 传入要结束的程序的处理数 uexitcode 指定, 可使用 GetExitCodeProcess 函式来取得程序结束时的离开码 (exit code) 传回值 : 式呼叫及执行成功时传回值为 ERROR_SUCCESS, 其它传回值表示函式呼叫及执行失败, 可在 Winerror.h 档案中找到错误代码及错误信息 执行绪 (Thread) 执行绪 (Thread) 可使一个应用程序同时完成多个任务 (task) 执行绪(thread) 是 Windows CE 操作系统分配处理器 (processor) 执行时间的基本单位,Windows CE 操作系统对先占权

163 (Preemption) 的决定完全取决于执行绪的优先层级 (Priority Level),Windows CE.NET 有 256 个基本优先层级 (Priority Level) 一个程序(process) 通常包含了一个或多个执行绪 (thread) 在此程序 (process) 的环境 (context) 里执行, 而毎个执行绪 (thread) 都有各自的执行绪局部内存 (TLS : thread local storage) 来贮存执行绪自已特有的资料 例如, 一个电子表格应用程序在使用者毎次开始一个新的例子 (instance) 时, 执行绪都会创造一个自已的电子表格并储存于 TLS 中 每一个处理程序 (process) 流程都是从一个单一的主要执行绪开始执行, 这个单一执行绪通常称作主要执行绪 (primary thread) 当执行应用程序的进入点函式(entry point function) WinMain 被激活执行时也同时建立了主要执行绪如下图所示 : Windows CE 不支持交错式输出入 (Overlapped I/O), 因此我们常使用一个执行绪在背景 中执行一个费时的 I/O 操作, 以让其它执行绪可以不用管这个费时的输出入而专心的去处理 其它工作 系统排班 (The System Scheduler) :

164 Wi ndows CE 先占 (preemptive) 模式排班执行绪 (thread) 的执行 执行绪 (thread) 运行一个时间量 ( quantum) 或时间片断 (time slice) 当这一个时间量(quantum) 或时间片断 (time slice) 用完时, 若执行绪 (thread) 尚未完成所有工作, 便会被暂停 (suspend), 且系统会排班其它执行绪 (thread) 来运行 至于下一个该排班运行的执行绪 (thread) 是那一个呢?Windows CE 的排班器 (schedul er) 是基于其优先权 (priority) 等级来选择 优先权(priority) 高的执行绪 (thread) 会排班在优先权 ( priority) 低的执行绪 (thread) 之前来运行, 而相同优先权 (priority) 的执行绪 (thread) 则使 用 round-robin 的方式来排班 而所谓先占 (preemptive) 模式即指当有一个优先权 (priority) 高的执行绪 ( thread) 等执运行时, 若目前正排班执行的执行绪 (thread) 优先权 (priority) 较低, 则此优先权 ( priority) 低的执行绪 (thread) 会马上被暂停, 由此优先权 (priority) 高的执行绪 (thread) 来 运行 只有一种状况例外, 就是当优先权 (priority) 低的执行绪 (thread) 拥有一个优先权 (priority) 高的执行绪 (thread) 在等待的资源时, 会暂时提高这个优先权 (priority) 低的执行绪的优先权 (priority), 以便让它赶快执行完毕后释放出资源, 这种状况称为优先权倒转 (priority inversion) Windows CE 的执行绪 (thread) 的优先权 (priority) 有 256 个等级, 应用程序最常用的是优先权 (priority) 最低的以下 8 个 ( 依序列出第 249 到第 256 等 ), 其它较高的 248 个常由系统程序或驱动程序使用 THREAD_PRIORITY_TIME_CRITICAL THREAD_PRIORITY_HIGHEST THREAD_PRIORITY_ABOVE_ NORMAL THREAD_ PRIORITY_NORMAL THREAD_ PRIORITY_BELOW_NORMAL THREAD_ PRIORITY_LOWEST THREAD_ PRIORITY_ABOVE_IDLE THREAD_PRIORITY_IDLE 一般应用程序的执行绪的预设优先权为 THREAD_PRIORITY_NORMAL 建立执行绪 使用 CreateThread 函式来建立执行绪如下 : HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpthreadattributes, DWORD dwstacksize, LPTHREAD_ START_ROUTINE lpstartaddress, LPVOID lpparameter, DWORD dwcreationflags, LPDWORD lpthreadid ); 参数 :

165 lpthreadattributes Windows CE 不支持, 填入 NULL dwstacksize 常填入 0 除非 dwcreationflags 参数设为 STACK_SIZE_PARAM_IS_A_RESERVATION 时, 表示要指定堆栈 (stack) 大小 lpstartaddress 一个指向 LPTHREAD_START_ROUTINE 型别的应用程序定义的函式指针, 这个函式就是这个新建立的执行绪所要执行的 此函式原型(prototype) 定义如下 : DWORD WINAPI ThreadProc( LPVOID lpparameter ); lpparameter 参数用来接收传给此函式的参数, 函式的传回值回反应函式是否成功执行 lpparameter 指定要传给此执行绪的参数 dwcreationflags 指定执行绪的建立方式, 有以下选项 : Value CREATE_SUSPENDED Description 执行绪被建立后即进入暂停 (suspend) 模式方式, 直到 ResumeThread 函式被呼叫 STACK_SIZE_PARAM_IS_A_RESERVATION 使用 dwstacksize 参数指定堆栈 (stack) 大小 lpthreadid 接收执行绪识别码 (identifier) 可设为 NULL, 表示不需要传回执行绪识别码 (identifier) 传回值 : 传回值为 NULL 时, 表示函式呼叫及执行失败, 可呼叫 GetLastError 函式取得错误代码 函式呼叫及执行成功时回传回成功建立的执行绪的处理数 (handle) 批注 : 以下程序代码举例说明使用方法 : HANDLE hthread1; DWORD dwthread1id = 0; INT nparameter = 5; hthread1 = CreateThread (NULL, 0, Thread1, (PVOID)nParameter, 0,

166 &dwthread1id); CloseHandle (hthread1); // // thread routine // DWORD WINAPI Thread1 (PVOID parg) INT nparam = (INT) parg; // // Do something here. //. //. //. return 0x15; 结束执行绪 执行绪可以呼叫 ExitThread 函式来结束执行绪如下 : VOID ExitThread( DWORD dwexitcode ); 参数 : dwexitcode 设定执行绪的结束码 (exit code) 应用程序可以呼叫 GetExitCodeThread 函式来取得执行绪的结束码 (exit code) 传回值 : 无 设定执行绪优先权我们可以使用 SetThreadPriority 函式或 CeSetThreadPriority 函式来设定执行绪优先权, 其唯一差别在于 SetThreadPriority 函式只能设定优先权最低的八个层级, 分别是 249 到 256, 优先权的值愈大表示优先权愈低 ; 而 CeSetThreadPriority 函式可以设定 Windows CE 所支持的所有 256 个优先权层级 : BOOL SetThreadPriority( HANDLE hthread, int npriority );

167 参数 : hthread 执行绪的处理数 npriority 指定执行绪的的优先权, 八种默认值如下, 分别是优先权 249 到优先权 256, 优先权的值愈大表示优先权愈低 THREAD_PRIORITY_TIME_CRITICAL THREAD_PRIORITY_HIGHEST THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_NORMAL THREAD_PRIORITY_BELOW_NORMAL THREAD_PRIORITY_LOWEST THREAD_PRIORITY_ABOVE_IDLE THREAD_PRIORITY_IDLE 传回值 : 传回值不为 0 时, 表示函式呼叫及执行成功 ; 传回值为 0, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 批注 : 另外我们也可以呼叫相对应的 CeGetThreadPriority 函式和 GetThreadPriority 函式来取得执行绪的优先权层级 (priority level) 设定执行绪的时间量 (Quantum) 我们可以使用以下的 CeSetThreadQuantum 函式来设定执行绪的执行权被占领前的最大执行时间量, 一般来说,Windows CE 装置的执行绪的预设执行时间量为 100 毫秒 (milisecond): BOOL CeSetThreadQuantum( HANDLE hthread, DWORD dwtime ); 参数 : hthread 执行绪的处理数 (handle) dwtime

168 执行绪的执行时间量 (Quantum), 以毫秒 (milisecond) 计 若填入 0, 则执行绪会一直 到执行完毕才交出执行权 传回值 : TRUE 表示成功,FALSE 表示失败, 可透过呼叫 GetLastError 函式来取得错误代码 暂停 (suspend) 执行绪 我们可以使用以下的 SuspendThread 函式来暂停执行绪的执行 : DWORD Sus pendthread( HANDLE hthread ); 参数 : hthread 执行绪的处理数 (handle) 传回值 : 函式呼叫及执行成功时, 传回值为执行绪被暂停的次数 传回值为 0xFFFFFFFF 时, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 批注 : Windows CE 会记录每个执行绪被暂停的次数 毎次呼叫 SuspendThread 函式时, 执行绪被暂停的次数会加 1; 毎次呼叫 ResumeThread 函式时, 执行绪被暂停的次数会减 1 当执行绪被暂停的次数为 0 时,Windows CE 才会排班执行绪来执行 因此每个 SuspendThread 函式呼叫都应该会有一个相对应的 ResumeThread 函式呼叫 继续 (resume) 执行执行绪我们可以使用以下的 ResumeThread 函式来继续 (resume) 执行绪的执行 : DWORD ResumeThread( HANDLE hthread ); 参数 : hthread 执行绪的处理数 (handle)

169 传回值 : 函式呼叫及执行成功时, 传回值为执行绪被暂停的次数 传回值为 0xFFFFFFFF 时, 表示函式呼叫及执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 sleep 另外, 当我们要执行绪休息和等待一段时间, 可使用 sleep 函式如下 : void Sleep( DWORD dwmilliseconds ); 参数 : dwmilliseconds 设定要休息和等待的时间, 以毫秒 (milliseconds) 计 传回值 : 无 执行绪同步 (thread synchronization) 一个程序 (process) 通常包含了一个或多个执行绪 (thread), 因此应用程序有时需要协调综合 (coordinate) 两个或多个执行绪执行的方法 执行绪可以改变同步对象 (synchronization object) 的状态, 或是等待当这个同步对象 (synchronization object) 状态被改变时才被触发 (signaled) 去做事 同步性对象 (synchronization object) 是一个用来协调仲裁多个执行绪同时执行的对象, 常在执行绪呼叫等待函式 (wait functions) 时使用 使用等候函式 (wait function) 可以允许执行绪去暂停他自己的执行, 等候函式会在符合指定的判断准则 (criteria) 时才返回 使用等候函式 (wait function) 去等候一个同步对象 (synchronization object) 执行绪可以使用以下的 WaitForSingleObject 函式去等候一个同步对象 (synchronization object): DWORD WaitForSingleObject( HANDLE hhandle,

170 DWORD dwmilliseconds ); 参数 : hhandle 设定所要等待的同步对象 (synchronization object) 的处理数 (handle) dwmilliseconds 设定中止时间值 (time-out interval), 以毫秒计 当时间到而仍未等到所要等待的同步对象 (synchronization object) 发生, 函式会返回 当中止时间值 (time-out interval) 设为 0 时, 函式会马上检查所要等待的同步对象 (synchronization object) 而不等待 ; 当中止时间值 (time-out interval) 设为 INFINITE 时, 函式会一直等待所要等待的同步对象 (synchronization object) 发生 传回值 : 传回值 WAIT_OBJECT_0 WAIT_TIMEOUT WAIT_FAILED 说明所要等待的同步对象 (synchronization object) 已发生中止时间到而仍未等到所要等待的同步对象 (synchronization object) 发生函式执行失败, 可透过呼叫 GetLastError 函式来取得错误代码 批注 : 另外, 我们可以使用 CreateEvent 函式来建立一个同步对象 (synchronization object), 和使用 SetEvent 函式来促使同步对象 (synchronization object) 发生或发出通知 本章范例 : 本章第一节讨论的序列通讯范例程序和第二节讨论的 WinSock 通讯范例程序都有使用多执行绪的撰写方式 我们以第一节讨论的序列通讯范例程序来说明, 因为 Windows CE 不支持序列通讯端口的交错式输出入 (overlapped I/O), 所以程序中宣告和定义了两个执行绪函式 (ThreadProc) 如下 : DWORD ReadThread (PVOID parg); DWORD SendThread (PVOID parg); 程序一开始使建立了这两个绪函式来执行如下 负责传送资料的 SendThread 执行绪会等后使用者输入欲传送的资料后才开始传送资料, 而负责接收资料的 ReadThread 执行绪负一直监聴序列通讯端口和接收数据 HANDLE hthread;

171 hthread = CreateThread (NULL, 0, SendThread, hdlg, 0, &rc); if (hthread) CloseHandle (hthread); HANDLE hreadthread = INVALID_HANDLE_VALUE; if (!GetExitCodeThread (hreadthread, &dwtstat) (dwtstat!= STILL_ACTIVE)) hreadthread = CreateThread (NULL, 0, Read Thread, hwnd, 0, &dwtstat); if (hreadthread) CloseHandle (hreadthread); 并且也建立了一个同步对象 (synchronization object) 如下 : HANDLE g_hsendevent = INVALID_HANDLE_VALUE; g_hsendevent = CreateEvent (NULL, FALSE, FALSE, NULL); 负责传送资料的 SendThread 执行绪会呼叫等候函式 (wait function) 来等后这个同步对象 (sync hronization object) 发生时才开始传送资料如下 : while (1) rc = WaitForSingleObject (g_hsendevent, INFINITE); // 传送数据到序列通讯端口 而当我们键入欲传送的数据并按下 [Send] 按钮时, 便会启同这个同步对象 (synchronization objec t) 发生如下 而负责传送资料的 SendThread 执行绪会被通知这个同步对象 (synchronization object) 已发生且等候函式 (wait funct ion) 会返回开始执行后续的传送资料动 作 case WM_COMMAND: if ((LOWORD(wParam) == IDOK) (LOWORD(wParam) == IDCANCEL)) EndDialog(hDlg, LOWORD(wParam)); return TRUE; else if (LOWORD(wParam) == ID_SENDBTN) SetEvent (g_hsendevent);

172 SetFocus (GetDlgItem (hdlg, ID_SENDTEXT)); 最后, 在应用程序结式前会关闭已建立的同步对象 (synchronization object) 如下 : int TermInstance (HINSTANCE hinstance, int ndefrc) if (g_hsendevent!= INVALID_HANDLE_VALUE) PulseEvent (g_hsendevent); Sleep(100); CloseHandle (g_hsendevent);

173 本章附录 : 系统错误 (system error) 代码对照表 : Code Description Name 0 The operation completed successfully. ERROR_SUCCESS 1 Incorrect function. ERROR_INVALID_FUNCTION 2 The system cannot find the file specified. ERROR_FILE_NOT_FOUND 3 The system cannot find the path specified. ERROR_PATH_NOT_FOUND 4 The system cannot open the file. ERROR_TOO_MANY_OPEN_FILES 5 Access is denied. ERROR_ACCESS_DENIED 6 The handle is invalid. ERROR_INVALID_HANDLE 7 The storage control blocks were destroyed. ERROR_ARENA_TRASHED 8 Not enough storage is available to process this command. ERROR_NOT_ENOUGH_MEMORY 9 The storage control block address is invalid. ERROR_INVALID_BLOCK 10 The environment is incorrect. ERROR_BAD_ENVIRONMENT 11 An attempt was made to load a program with an incorrect format. ERROR_BAD_FORMAT 12 The access code is invalid. ERROR_INVALID_ACCESS 13 The data is invalid. ERROR_INVALID_DATA 14 Not enough storage is available to complete this operation. ERROR_OUTOFMEMORY 15 The system cannot find the drive specified. ERROR_INVALID_DRIVE 16 The directory cannot be removed. ERROR_CURRENT_DIRECTORY 17 The system cannot move the file to a different disk drive. ERROR_NOT_SAME_DEVICE 18 There are no more files. ERROR_NO_MORE_FILES 19 The media is write protected. ERROR_WRITE_PROTECT 20 The system cannot find the specified device. ERROR_BAD_UNIT 21 The device is not ready. ERROR_NOT_READY 22 The device does not recognize the command. ERROR_BAD_COMMAND 23 Data error (cyclic redundancy check). ERROR_CRC 24 The program issued a command but the command length is incorrect. ERROR_BAD_LENGTH 25 The drive cannot locate a specific area or track on the disk. ERROR_SEEK 26 The specified disk or diskette cannot be accessed. ERROR_NOT_DOS_DISK 27 The drive cannot find the sector requested. ERROR_SECTOR_NOT_FOUND 28 The printer is out of paper. ERROR_OUT_OF_PAPER 29 The system cannot write to the specified device. ERROR_WRITE_FAULT 30 The system cannot read from the specified device. ERROR_READ_FAULT 31 A device attached to the system is not functioning. ERROR_GEN_FAILURE 32 The process cannot access the file because it is being used by ERROR_SHARING_VIOLATION another process. 33 The process cannot access the file because another process has locked a portion of the file. 34 The wrong diskette is in the drive. Insert %2 (Volume Serial Number: %3) into drive %1. ERROR_LOCK_VIOLATION ERROR_WRONG_DISK 36 Too many files opened for sharing. ERROR_SHARING_BUFFER_EXCEEDED 38 Reached the end of the file. ERROR_HANDLE_EOF 39 The disk is full. ERROR_HANDLE_DISK_FULL 50 The network request is not supported. ERROR_NOT_SUPPORTED 51 The remote computer is not available. ERROR_REM_NOT_LIST 52 A duplicate name exists on the network. ERROR_DUP_NAME 53 The network path was not found. ERROR_BAD_NETPATH 54 The network is busy. ERROR_NETWORK_BUSY 55 The specified network resource or device is no longer available. ERROR_DEV_NOT_EXIST 56 The network BIOS command limit has been reached. ERROR_TOO_MANY_CMDS 57 A network adapter hardware error occurred. ERROR_ADAP_HDW_ERR 58 The specified server cannot perform the requested operation. ERROR_BAD_NET_RESP 59 An unexpected network error occurred. ERROR_UNEXP_NET_ERR 60 The remote adapter is not compatible. ERROR_BAD_REM_ADAP 61 The printer queue is full. ERROR_PRINTQ_FULL 62 Space to store the file waiting to be printed is not available on the server. ERROR_NO_SPOOL_SPACE

174 63 Your file waiting to be printed was deleted. ERROR_PRINT_CANCELLED 64 The specified network name is no longer available. ERROR_NETNAME_DELETED 65 Network access is denied. ERROR_NETWORK_ACCESS_DENIED 66 The network resource type is not correct. ERROR_BAD_DEV_TYPE 67 The network name cannot be found. ERROR_BAD_NET_NAME 68 The name limit for the local computer network adapter card was exceeded. ERROR_TOO_MANY_NAMES 69 The network BIOS session limit was exceeded. ERROR_TOO_MANY_SESS 70 The remote server has been paused or is in the process of being started. ERROR_SHARING_PAUSED 71 No more connections can be made to this remote computer at this ERROR_REQ_NOT_ACCEP time because there are already as many connections as the computer can accept. 72 The specified printer or disk device has been paused. ERROR_REDIR_PAUSED 80 The file exists. ERROR_FILE_EXISTS 82 The directory or file cannot be created. ERROR_CANNOT_MAKE 83 Fail on interrupt 24 handler. ERROR_FAIL_I24 84 Storage to process this request is not available. ERROR_OUT_OF_STRUCTURES 85 The local device name is already in use. ERROR_ALREADY_ASSIGNED 86 The specified network password is not correct. ERROR_INVALID_PASSWORD 87 The parameter is incorrect. ERROR_INVALID_PARAMETER 88 A write fault occurred on the network. ERROR_NET_WRITE_FAULT 89 The system cannot start another process at this time. ERROR_NO_PROC_SLOTS 100 Cannot create another system semaphore. ERROR_TOO_MANY_SEMAPHORES 101 The exclusive semaphore is owned by another process. ERROR_EXCL_SEM_ALREADY_OWNED 102 The semaphore is set and cannot be closed. ERROR_SEM_IS_SET 103 The semaphore cannot be set again. ERROR_TOO_MANY_SEM_REQUESTS 104 Cannot request exclusive semaphores at interrupt time. ERROR_INVALID_AT_INTERRUPT_TIME 105 The previous ownership of this semaphore has ended. ERROR_SEM_OWNER_DIED 106 Insert the diskette for drive %1. ERROR_SEM_USER_LIMIT 107 The program stopped because an alternate diskette was not inserted. ERROR_DISK_CHANGE 108 The disk is in use or locked by another process. ERROR_DRIVE_LOCKED 109 The pipe has been ended. ERROR_BROKEN_PIPE 110 The system cannot open the device or file specified. ERROR_OPEN_FAILED 111 The file name is too long. ERROR_BUFFER_OVERFLOW 112 There is not enough space on the disk. ERROR_DISK_FULL 113 No more internal file identifiers available. ERROR_NO_MORE_SEARCH_HANDLES 114 The target internal file identifier is incorrect. ERROR_INVALID_TARGET_HANDLE 117 The IOCTL call made by the application program is not correct. ERROR_INVALID_CATEGORY 118 The verify-on-write switch parameter value is not correct. ERROR_INVALID_VERIFY_SWITCH 119 The system does not support the command requested. ERROR_BAD_DRIVER_LEVEL 120 This function is not valid on this platform. ERROR_CALL_NOT_IMPLEMENTED 121 The semaphore time-out period has expired. ERROR_SEM_TIMEOUT 122 The data area passed to a system call is too small. ERROR_INSUFFICIENT_BUFFER 123 The file name, directory name, or volume label syntax is incorrect. ERROR_INVALID_NAME 124 The system call level is not correct. ERROR_INVALID_LEVEL 125 The disk has no volume label. ERROR_NO_VOLUME_LABEL 126 The specified module could not be found. ERROR_MOD_NOT_FOUND 127 The specified procedure could not be found. ERROR_PROC_NOT_FOUND 128 There are no child processes to wait for. ERROR_WAIT_NO_CHILDREN 129 The %1 application cannot be run in Windows NT mode. ERROR_CHILD_NOT_COMPLETE 130 Attempt to use a file handle to an open disk partition for an ERROR_DIRECT_ACCESS_HANDLE operation other than raw disk I/O. 131 An attempt was made to move the file pointer before the beginning ERROR_NEGATIVE_SEEK of the file. 132 The file pointer cannot be set on the specified device or file. ERROR_SEEK_ON_DEVICE 133 A JOIN or SUBST command cannot be used for a drive that contains previously joined drives. 134 An attempt was made to use a JOIN or SUBST command on a drive that has already been joined. ERROR_IS_JOIN_TARGET ERROR_IS_JOINED

175 135 An attempt was made to use a JOIN or SUBST command on a ERROR_IS_SUBSTED drive that has already been substituted. 136 The system tried to delete the JOIN of a drive that is not joined. ERROR_NOT_JOINED 137 The system tried to delete the substitution of a drive that is not ERROR_NOT_SUBSTED substituted. 138 The system tried to join a drive to a directory on a joined drive. ERROR_JOIN_TO_JOIN 139 The system tried to substitute a drive to a directory on a substituted ERROR_SUBST_TO_SUBST drive. 140 The system tried to join a drive to a directory on a substituted drive. ERROR_JOIN_TO_SUBST 141 The system tried to SUBST a drive to a directory on a joined drive. ERROR_SUBST_TO_JOIN 142 The system cannot perform a JOIN or SUBST at this time. ERROR_BUSY_DRIVE 143 The system cannot join or substitute a drive to or for a directory on ERROR_SAME_DRIVE the same drive. 144 The directory is not a subdirectory of the root directory. ERROR_DIR_NOT_ROOT 145 The directory is not empty. ERROR_DIR_NOT_EMPTY 146 The path specified is being used in a substitute. ERROR_IS_SUBST_PATH 147 Not enough resources are available to process this command. ERROR_IS_JOIN_PATH 148 The path specified cannot be used at this time. ERROR_PATH_BUSY 149 An attempt was made to join or substitute a drive for which a ERROR_IS_SUBST_TARGET directory on the drive is the target of a previous substitute. 150 System trace information was not specified in your Config.sys file, or tracing is disallowed. ERROR_SYSTEM_TRACE 151 The number of specified semaphore events for DosMuxSemWait is ERROR_INVALID_EVENT_COUNT not correct. 152 DosMuxSemWait did not execute; too many semaphores are ERROR_TOO_MANY_MUXWAITERS already set. 153 The DosMuxSemWait list is not correct. ERROR_INVALID_LIST_FORMAT 154 The volume label you entered exceeds the label character limit of ERROR_LABEL_TOO_LONG the target file system. 155 Cannot create another thread. ERROR_TOO_MANY_TCBS 156 The recipient process has refused the signal. ERROR_SIGNAL_REFUSED 157 The segment is already discarded and cannot be locked. ERROR_DISCARDED 158 The segment is already unlocked. ERROR_NOT_LOCKED 159 The address for the thread identifier is not correct. ERROR_BAD_THREADID_ADDR 160 The argument string passed to DosExecPgm is not correct. ERROR_BAD_ARGUMENTS 161 The specified path is invalid. ERROR_BAD_PATHNAME 162 A signal is already pending. ERROR_SIGNAL_PENDING 164 No more threads can be created in the system. ERROR_MAX_THRDS_REACHED 167 Unable to lock a region of a file. ERROR_LOCK_FAILED 170 The requested resource is in use. ERROR_BUSY 173 A lock request was not outstanding for the supplied cancel region. ERROR_CANCEL_VIOLATION 174 The file system does not support atomic changes to the lock type. ERROR_ATOMIC_LOCKS_NOT_SUPPORTED 180 The system detected a segment number that was not correct. ERROR_INVALID_SEGMENT_NUMBER 182 The operating system cannot run %1. ERROR_INVALID_ORDINAL 183 Cannot create a file when that file already exists. ERROR_ALREADY_EXISTS 186 The flag passed is not correct. ERROR_INVALID_FLAG_NUMBER 187 The specified system semaphore name was not found. ERROR_SEM_NOT_FOUND 188 The operating system cannot run %1. ERROR_INVALID_STARTING_CODESEG 189 The operating system cannot run %1. ERROR_INVALID_STACKSEG 190 The operating system cannot run %1. ERROR_INVALID_MODULETYPE 191 Cannot run %1 in Windows NT mode. ERROR_INVALID_EXE_SIGNATURE 192 The operating system cannot run %1. ERROR_EXE_MARKED_INVALID 193 Is not a valid application. ERROR_BAD_EXE_FORMAT 194 The operating system cannot run %1. ERROR_ITERATED_DATA_EXCEEDS_64k 195 The operating system cannot run %1. ERROR_INVALID_MINALLOCSIZE 196 The operating system cannot run this application program. ERROR_DYNLINK_FROM_INVALID_RING 197 The operating system is not presently configured to run this ERROR_IOPL_NOT_ENABLED application. 198 The operating system cannot run %1. ERROR_INVALID_SEGDPL 199 The operating system cannot run this application program. ERROR_AUTODATASEG_EXCEEDS_64k 200 The code segment cannot be greater than or equal to 64 KB. ERROR_RING2SEG_MUST_BE_MOVABLE

176 201 The operating system cannot run %1. ERROR_RELOC_CHAIN_XEEDS_ SEGLIM 202 The operating system cannot run %1. ERROR_INFLOOP_IN_RELOC_CHAIN 203 The system could not find the environment option that was entered. ERROR_ENVVAR_NOT_FOUND 205 No process in the command subtree has a signal handler. ERROR_NO_SIGNAL_SENT 206 The file name or extension is too long. ERROR_FILENAME_EXCED_RANGE 207 The ring 2 stack is in use. ERROR_RING2_STACK_IN_USE 208 The global file name characters, * or?, are entered incorrectly or ERROR_META_EXPANSION_TOO_LONG too many global file name characters are specified. 209 The signal being posted is not correct. ERROR_INVALID_SIGNAL_NUMBER 210 The signal handler cannot be set. ERROR_THREAD_1_INACTIVE 212 The segment is locked and cannot be reallocated. ERROR_LOCKED 214 Too many dynamic-link modules are attached to this program or dynamic-link module. ERROR_TOO_MANY_MODULES 215 Cannot nest calls to the LoadModule function. ERROR_NESTING_NOT_ALLOWED 216 The image file %1 is valid, but is for a machine type other than the ERROR_EXE_MACHINE_TYPE_MISMATCH current machine. 230 The pipe state is invalid. ERROR_BAD_PIPE 231 All pipe instances are busy. ERROR_PIPE_BUSY 232 The pipe is being closed. ERROR_NO_DATA 233 No process is on the other end of the pipe. ERROR_PIPE_NOT_CONNECTED 234 More data is available. ERROR_MORE_DATA 240 The session was canceled. ERROR_VC_DISCONNECTED 254 The specified extended attribute name was invalid. ERROR_INVALID_EA_NAME 255 The extended attributes are inconsistent. ERROR_EA_LIST_INCONSISTENT 259 No more data is available. ERROR_NO_MORE_ITEMS 266 The copy functions cannot be used. ERROR_CANNOT_COPY 267 The directory name is invalid. ERROR_DIRECTORY 275 The extended attributes did not fit in the buffer. ERROR_EAS_DIDNT_FIT 276 The extended attribute file on the mounted file system is corrupt. ERROR_EA_FILE_CORRUPT 277 The extended attribute table file is full. ERROR_EA_TABLE_FULL 278 The specified extended attribute handle is invalid. ERROR_INVALID_EA_HANDLE 282 The mounted file system does not support extended attributes. ERROR_EAS_NOT_SUPPORTED 288 Attempt to release mutex not owned by caller. ERROR_NOT_OWNER 298 Too many posts were made to a semaphore. ERROR_TOO_MANY_POSTS 299 Only part of a ReadProcessMemory or WriteProcessMemory ERROR_PARTIAL_COPY request was completed. 317 The system cannot find message text for message number 0x%1 in ERROR_MR_MID_NOT_FOUND the message file for % Attempt to access invalid address. ERROR_INVALID_ADDRESS 534 Arithmetic result exceeded 32 bits. ERROR_ARITHMETIC_OVERFLOW 535 There is a process on other end of the pipe. ERROR_PIPE_CONNECTED 536 Waiting for a process to open the other end of the pipe. ERROR_PIPE_LISTENING 994 Access to the extended attribute was denied. ERROR_EA_ACCESS_DENIED 995 The I/O operation has been aborted because of either a thread exit or an application request. ERROR_OPERATION_ABORTED 996 Overlapped I/O event is not in a signaled state. ERROR_IO_INCOMPLETE 997 Overlapped I/O operation is in progress. ERROR_IO_PENDING 998 Invalid access to memory location. ERROR_NOACCESS 999 Error performing inpage operation. ERROR_SWAPERROR 1001 Recursion too deep; the stack overflowed. ERROR_STACK_OVERFLOW 1002 The window cannot act on the sent message. ERROR_INVALID_MESSAGE 1003 Cannot complete this function. ERROR_CAN_NOT_COMPLETE 1004 Invalid flags. ERROR_INVALID_FLAGS 1005 The volume does not contain a recognized file system. Verify that ERROR_UNRECOGNIZED_VOLUME all required file system drivers are loaded and that the volume is not corrupted The volume for a file has been externally altered so that the opened file is no longer valid. ERROR_FILE_INVALID 1007 The requested operation cannot be performed in full-screen mode. ERROR_FULLSCREEN_MODE 1008 An attempt was made to reference a token that does not exist. ERROR_NO_TOKEN 1009 The configuration registry database is corrupt. ERROR_BADDB

177 1010 The configuration registry key is invalid. ERROR_BADKEY 1011 The configuration registry key could not be opened. ERROR_CANTOPEN 1012 The configuration registry key could not be read. ERROR_CANTREAD 1013 The configuration registry key could not be written. ERROR_CANTWRITE 1014 One of the files in the registry database had to be recovered by use ERROR_REGISTRY_RECOVERED of a log or alternate copy. The recovery was successful The registry is corrupted. The structure of one of the files that ERROR_REGISTRY_CORRUPT contains registry data is corrupted, or the system's image of the file in memory is corrupted, or the file could not be recovered because the alternate copy or log was absent or corrupted An I/O operation initiated by the registry failed unrecoverably. The ERROR_REGISTRY_IO_FAILED registry could not read in, or write out, or flush, one of the files that contain the system's image of the registry The system has attempted to load or restore a file into the registry, but the specified file is not in a registry file format Illegal operation attempted on a registry key that has been marked for deletion. ERROR_NOT_REGISTRY_FILE ERROR_KEY_DELETED 1019 System could not allocate the required space in a registry log. ERROR_NO_LOG_SPACE 1020 Cannot create a symbolic link in a registry key that already has subkeys or values. ERROR_KEY_HAS_CHILDREN 1021 Cannot create a stable subkey under a volatile parent key. ERROR_CHILD_MUST_BE_VOLATILE 1022 A notify change request is being completed and the information is not being returned in the caller's buffer. The caller now needs to enumerate the files to find the changes A stop control has been sent to a service that other running services are dependent on. ERROR_NOTIFY_ENUM_DIR ERROR_DEPENDENT_SERVICES_RUNNING 1052 The requested control is not valid for this service. ERROR_INVALID_SERVICE_CONTROL 1053 The service did not respond to the start or control request in a ERROR_SERVICE_REQUEST_TIMEOUT timely fashion A thread could not be created for the service. ERROR_SERVICE_NO_ THREAD 1055 The service database is locked. ERROR_SERVICE_DATABASE_LOCKED 1056 An instance of the service is already running. ERROR_SERVICE_ALREADY_RUNNING 1057 The account name is invalid or does not exist. ERROR_INVALID_SERVICE_ACCOUNT 1058 The specified service is disabled and cannot be started. ERROR_SERVICE_DISABLED 1059 Circular service dependency was specified. ERROR_CIRCULAR_DEPENDENCY 1060 The specified service does not exist as an installed service. ERROR_SERVICE_DOES_NOT_EXIST 1061 The service cannot accept control messages at this time. ERROR_SERVICE_CANNOT_ACCEPT_CTRL 1062 The service has not been started. ERROR_SERVICE_NOT_ACTIVE 1063 The service process could not connect to the service controller. ERROR_FAILED_SERVICE_CONTROLLER_CONNECT 1064 An exception occurred in the service when handling the control request. ERROR_EXCEPTION_IN_SERVICE 1065 The database specified does not exist. ERROR_DATABASE_DOES_NOT_EXIST 1066 The service has returned a service-specific error code. ERROR_SERVICE_SPECIFIC_ERROR 1067 The process terminated unexpectedly. ERROR_PROCESS_ABORTED 1068 The dependency service or group failed to start. ERROR_SERVICE_DEPENDENCY_FAIL 1069 The service did not start due to a logon failure. ERROR_SERVICE_LOGON_FAILED 1070 After starting, the service stopped responding (hung) in a start-pending state. ERROR_SERVICE_START_HANG 1071 The specified service database lock is invalid. ERROR_INVALID_SERVICE_LOCK 1072 The specified service has been marked for deletion. ERROR_SERVICE_MARKED_FOR_DELETE 1073 The specified service already exists. ERROR_SERVICE_EXISTS 1074 The system is currently running with the last-known-good configuration. ERROR_ALREADY_RUNNING_LKG 1075 The dependency service does not exist or has been marked for ERROR_SERVICE_DEPENDENCY_DELETED deletion The current boot has already been accepted for use as the ERROR_BOOT_ALREADY_ACCEPTED last-known-good control set No attempts to start the service have been made since the last ERROR_SERVICE_NEVER_STARTED boot The name is already in use as either a service name or a service ERROR_DUPLICATE_SERVICE_NAME display name The account specified for this service is different from the account specified for other services running in the same process. ERROR_DIFFERENT_SERVICE_ACCOUNT 1100 The physical end of the tape has been reached. ERROR_END_OF_MEDIA

178 1101 A tape access reached a filemark. ERROR_FILEMARK_DETECTED 1102 The beginning of the tape or partition was encountered. ERROR_BEGINNING_OF_MEDIA 1103 A tape access reached the end of a set of files. ERROR_SETMARK_DETECTED 1104 No more data is on the tape. ERROR_NO_DATA_DETECTED 1105 Tape could not be partitioned. ERROR_PARTITION_FAILURE 1106 When accessing a new tape of a multivolume partition, the current block size is incorrect. ERROR_INVALID_BLOCK_LENGTH 1107 Tape partition information could not be found when loading a tape. ERROR_DEVICE_NOT_PARTITIONED 1108 Unable to lock the media eject mechanism. ERROR_UNABLE_TO_LOCK_MEDIA 1109 Unable to unload the media. ERROR_UNABLE_TO_UNLOAD_MEDIA 1110 The media in the drive may have changed. ERROR_MEDIA_CHANGED 1111 The I/O bus was reset. ERROR_BUS_RESET 1112 No media in drive. ERROR_NO_MEDIA_IN_DRIVE 1113 No mapping for the Unicode character exists in the target multibyte ERROR_NO_UNICODE_TRANSLATION code page A dynamic link library (DLL) initialization routine failed. ERROR_DLL_INIT_FAILED 1115 A system shutdown is in progress. ERROR_SHUTDOWN_IN_PROGRESS 1116 Unable to abort the system shutdown because no shutdown was in progress The request could not be performed because of an I/O device error. ERROR_IO_DEVICE 1118 No serial device was successfully initialized. The serial driver will unload Unable to open a device that was sharing an interrupt request (IRQ) with other devices. At least one other device that uses that IRQ was already opened. ERROR_NO_SHUTDOWN_IN_PROGRESS ERROR_SERIAL_NO_DEVICE ERROR_IRQ_BUSY 1120 A serial I/O operation was completed by another write to the serial ERROR_MORE_WRITES port. The IOCTL_SERIAL_XOFF_COUNTER reached zero.) 1121 A serial I/O operation completed because the time-out period ERROR_COUNTER_TIMEOUT expired. In other words, the IOCTL_SERIAL_XOFF_COUNTER did not reach zero No identifier address mark was found on the floppy disk. ERROR_FLOPPY_ID_MARK_NOT_FOUND 1123 Mismatch between the floppy disk sector identifier field and the ERROR_FLOPPY_WRONG_CYLINDER floppy disk controller track address The floppy disk controller reported an error that is not recognized ERROR_FLOPPY_UNKNOWN_ERROR by the floppy disk driver The floppy disk controller returned inconsistent results in its registers While accessing the hard disk, a recalibrate operation failed, even after retries While accessing the hard disk, a disk operation failed even after retries. ERROR_FLOPPY_BAD_REGISTERS ERROR_DISK_RECALIBRATE_FAILED ERROR_DISK_OPERATION_FAILED 1128 While accessing the hard disk, a disk controller reset was needed, ERROR_DISK_RESET_FAILED but even that failed Physical end of tape encountered. ERROR_EOM_OVERFLOW 1130 Not enough server storage is available to process this command. ERROR_NOT_ENOUGH_SERVER_MEMORY 1131 A potential deadlock condition has been detected. ERROR_POSSIBLE_DEADLOCK 1132 The base address or the file offset specified does not have the proper alignment An attempt to change the system power state was vetoed by another application or driver. ERROR_MAPPED_ALIGNMENT ERROR_SET_POWER_STATE_VETOED 1141 The basic input/output system (BIOS) failed an attempt to change ERROR_SET_POWER_STATE_FAILED the system power state An attempt was made to create more links on a file than the file system supports. ERROR_TOO_MANY_LINKS 1150 The specified program requires a newer version of Windows. ERROR_OLD_WIN_VERSION 1151 The specified program is not a Windows or MS-DOS program. ERROR_APP_WRONG_OS 1152 Cannot start more than one instance of the specified program. ERROR_SINGLE_INSTANCE_APP 1153 The specified program was written for an earlier version of Windows. ERROR_RMODE_APP 1154 One of the library files needed to run this application is damaged. ERROR_INVALID_DLL 1155 No application is associated with the specified file for this operation. ERROR_NO_ASSOCIATION 1156 An error occurred in sending the command to the application. ERROR_DDE_FAIL 1157 One of the library files needed to run this application cannot be found. ERROR_DLL_NOT_FOUND

179 1200 The specified device name is invalid. ERROR_BAD_DEVICE 1201 The device is not currently connected but it is a remembered connection. ERROR_CONNECTION_UNAVAIL 1202 An attempt was made to remember a device that had previously ERROR_DEVICE_ALREADY_REMEMBERED been remembered No network provider accepted the given network path. ERROR_NO_NET_OR_BAD_PATH 1204 The specified network provider name is invalid. ERROR_BAD_PROVIDER 1205 Unable to open the network connection profile. ERROR_CANNOT_OPEN_PROFILE 1206 The network connection profile is corrupt. ERROR_BAD_PROFILE 1207 Cannot enumerate a noncontainer. ERROR_NOT_CONTAINER 1208 An extended error has occurred. ERROR_EXTENDED_ERROR 1209 The format of the specified group name is invalid. ERROR_INVALID_GROUPNAME 1210 The format of the specified computer name is invalid. ERROR_INVALID_COMPUTERNAME 1211 The format of the specified event name is invalid. ERROR_INVALID_EVENTNAME 1212 The format of the specified domain name is invalid. ERROR_INVALID_DOMAINNAME 1213 The format of the specified service name is invalid. ERROR_INVALID_SERVICENAME 1214 The format of the specified network name is invalid. ERROR_INVALID_NETNAME 1215 The format of the specified share name is invalid. ERROR_INVALID_SHARENAME 1216 The format of the specified password is invalid. ERROR_INVALID_PASSWORDNAME 1217 The format of the specified message name is invalid. ERROR_INVALID_MESSAGENAME 1218 The format of the specified message destination is invalid. ERROR_INVALID_MESSAGEDEST 1219 The credentials supplied conflict with an existing set of credentials. ERROR_SESSION_CREDENTIAL_CONFLICT 1220 An attempt was made to establish a session to a network server, ERROR_REMOTE_SESSION_LIMIT_EXCEEDED but there are already too many sessions established to that server The workgroup or domain name is already in use by another computer on the network. ERROR_DUP_DOMAINNAME 1222 The network is not present or not started. ERROR_NO_NETWORK 1223 The operation was canceled by the user. ERROR_CANCELLED 1224 The requested operation cannot be performed on a file with a user-mapped section open. ERROR_USER_MAPPED_FILE 1225 The remote system refused the network connection. ERROR_CONNECTION_REFUSED 1226 The network connection was gracefully closed. ERROR_GRACEFUL_DISCONNECT 1227 The network transport endpoint already has an address associated with it. ERROR_ADDRESS_ALREADY_ASSOCIATED 1228 An address has not yet been associated with the network endpoint. ERROR_ADDRESS_NOT_ASSOCIATED 1229 An operation was attempted on a nonexistent network connection. ERROR_CONNECTION_INVALID 1230 An invalid operation was attempted on an active network ERROR_CONNECTION_ACTIVE connection The remote network is not reachable by the transport. ERROR_NETWORK_UNREACHABLE 1232 The remote system is not reachable by the transport. ERROR_HOST_UNREACHABLE 1233 The remote system does not support the transport protocol. ERROR_PROTOCOL_UNREACHABLE 1234 No service is operating at the destination network endpoint on the remote system. ERROR_PORT_UNREACHABLE 1235 The request was aborted. ERROR_REQUEST_ABORTED 1236 The network connection was aborted by the local system. ERROR_CONNECTION_ABORTED 1237 The operation could not be completed. A retry should be performed A connection to the server could not be made because the limit on the number of concurrent connections for this account has been reached Attempting to log in during an unauthorized time of day for this account. ERROR_RETRY ERROR_CONNECTION_COUNT_LIMIT ERROR_LOGIN_TIME_RESTRICTION 1240 The account is not authorized to log in from this station. ERROR_LOGIN_WKSTA_RESTRICTION 1241 The network address could not be used for the operation requested. ERROR_INCORRECT_ADDRESS 1242 The service is already registered. ERROR_ALREADY_REGISTERED 1243 The specified service does not exist. ERROR_SERVICE_NOT_FOUND 1244 The operation being requested was not performed because the user has not been authenticated The operation being requested was not performed because the user has not logged on to the network. The specified service does not exist. ERROR_NOT_AUTHENTICATED ERROR_NOT_LOGGED_ON

180 1246 Caller to continue with work in progress. ERROR_CONTINUE 1247 An attempt was made to perform an initialization operation when ERROR_ALREADY_INITIALIZED initialization has already been completed No more local devices. ERROR_NO_MORE_DEVICES 1300 Not all privileges referenced are assigned to the caller. ERROR_NOT_ALL_ASSIGNED 1301 Some mapping between account names and security IDs was not done. ERROR_SOME_NOT_MAPPED 1302 No system quota limits are specifically set for this account. ERROR_NO_QUOTAS_FOR_ACCOUNT 1303 No encryption key is available. A well-known encryption key was returned The password is too complex to be converted to a LAN Manager password. The LAN Manager password returned is a null string. ERROR_LOCAL_USER_SESSION_KEY ERROR_NULL_LM_PASSWORD 1305 The revision level is unknown. ERROR_UNKNOWN_REVISION 1306 Indicates two revision levels are incompatible. ERROR_REVISION_MISMATCH 1307 This security identifier may not be assigned as the owner of this object. ERROR_INVALID_OWNER 1308 This security identifier may not be assigned as the primary group of ERROR_INVALID_PRIMARY_GROUP an object An attempt has been made to operate on an impersonation token ERROR_NO_IMPERSONATION_TOKEN by a thread that is not currently impersonating a client The group cannot be disabled. ERROR_CANT_DISABLE_MANDATORY 1311 There are currently no logon servers available to service the logon request A specified logon session does not exist. It may already have been terminated. ERROR_NO_LOGON_SERVERS ERROR_NO_SUCH_LOGON_SESSION 1313 A specified privilege does not exist. ERROR_NO_SUCH_PRIVILEGE 1314 A required privilege is not held by the client. ERROR_PRIVILEGE_NOT_HELD 1315 The name provided is not a properly formed account name. ERROR_INVALID_ACCOUNT_NAME 1316 The specified user already exists. ERROR_USER_EXISTS 1317 The specified user does not exist. ERROR_NO_SUCH_USER 1318 The specified group already exists. ERROR_GROUP_EXISTS 1319 The specified group does not exist. ERROR_NO_SUCH_GROUP 1320 Either the specified user account is already a member of the ERROR_MEMBER_IN_GROUP specified group, or the specified group cannot be deleted because it contains a member The specified user account is not a member of the specified group account The last remaining administration account cannot be disabled or deleted Unable to update the password. The value provided as the current password is incorrect Unable to update the password. The value provided for the new password contains values that are not allowed in passwords Unable to update the password because a password update rule has been violated. ERROR_MEMBER_NOT_IN_GROUP ERROR_LAST_ADMIN ERROR_WRONG_PASSWORD ERROR_ILL_FORMED_PASSWORD ERROR_PASSWORD_RESTRICTION 1326 Logon failure unknown user name or bad password. ERROR_LOGON_FAILURE 1327 Logon failure user account restriction. ERROR_ACCOUNT_RESTRICTION 1328 Logon failure account logon time restriction violation. ERROR_INVALID_LOGON_HOURS 1329 Logon failure user not allowed to log on to this computer. ERROR_INVALID_WORKSTATION 1330 Logon failure the specified account password has expired. ERROR_PASSWORD_EXPIRED 1331 Logon failure account currently disabled. ERROR_ACCOUNT_DISABLED 1332 No mapping between account names and security IDs was done. ERROR_NONE_MAPPED 1333 Too many LUIDs were requested at one time. ERROR_TOO_MANY_LUIDS_REQUESTED 1334 No more LUIDs are available. ERROR_LUIDS_EXHAUSTED 1335 The subauthority part of a security identifier is invalid for this ERROR_INVALID_SUB_AUTHORITY particular use The access control list (ACL) structure is invalid. ERROR_INVALID_ACL 1337 The security identifier structure is invalid. ERROR_INVALID_SID 1338 The security descriptor structure is invalid. ERROR_INVALID_SECURITY_DESCR 1340 The inherited access control list (ACL) or access control entry (ACE) could not be built. ERROR_BAD_INHERITANCE_ACL 1341 The server is currently disabled. ERROR_SERVER_DISABLED 1342 The server is currently enabled. ERROR_SERVER_NOT_DISABLED

181 1343 The value provided was an invalid value for an identifier authority. ERROR_INVALID_ID_AUTHORITY 1344 No more memory is available for security information updates. ERROR_ALLOTTED_SPACE_EXCEEDED 1345 The specified attributes are invalid, or incompatible with the ERROR_INVALID_GROUP_ATTRIBUTES attributes for the group as a whole Either a required impersonation level was not provided, or the provided impersonation level is invalid. ERROR_BAD_IMPERSONATION_LEVEL 1347 Cannot open an anonymous level security token. ERROR_CANT_OPEN_ANONYMOUS 1348 The validation information class requested was invalid. ERROR_BAD_VALIDATION_CLASS 1349 The type of the token is inappropriate for its attempted use. ERROR_BAD_TOKEN_TYPE 1350 Unable to perform a security operation on an object that has no ERROR_NO_SECURITY_ON_ OBJECT associated security Indicates that a Windows NT Server could not be contacted or that ERROR_CANT_ACCESS_DOMAIN_INFO objects within the domain are protected such that necessary information could not be retrieved The security account manager (SAM) or local security authority ERROR_INVALID_SERVER_STATE (LSA) server was in the wrong state to perform the security operation The domain was in the wrong state to perform the security ERROR_INVALID_DOMAIN_STATE operation This operation is only allowed for the Primary Domain Controller ERROR_INVALID_DOMAIN_ROLE (PDC) of the domain The specified domain did not exist. ERROR_NO_SUCH_DOMAIN 1356 The specified domain already exists. ERROR_DOMAIN_EXISTS 1357 An attempt was made to exceed the limit on the number of domains ERROR_DOMAIN_LIMIT_EXCEEDED per server Unable to complete the requested operation because of either a catastrophic media failure or a data structure corruption on the d isk. ERROR_INTERNAL_DB_CORRUPTION 1359 The security account database contains an internal inconsistency. ERROR_INTERNAL_ERROR 1360 Generic access types were contained in an access mask that ERROR_GENERIC_NOT_MAPPED should already be mapped to nongeneric types A security descriptor is not in the right format (absolute or ERROR_BAD_DESCRIPTOR_FORMAT self-relative) The requested action is restricted for use by logon processes only. ERROR_NOT_LOGON_PROCESS The calling process has not registered as a logon process Cannot start a new logon session with an identifier that is already in ERROR_LOGON_SESSION_EXISTS use A specified authentication package is unknown. ERROR_NO_SUCH_PACKAGE 1365 The logon session is not in a state that is consistent with the ERROR_BAD_LOGON_SESSION_STATE requested operation The logon session identifier is already in use. ERROR_LOGON_SESSION_COLLISION 1367 A logon request contained an invalid logon type value. ERROR_INVALID_LOGON_TYPE 1368 Unable to impersonate using a named pipe until data has been ERROR_CANNOT_IMPERSONATE read from that pipe The transaction state of a registry subtree is incompatible with the ERROR_RXACT_INVALID_STATE requested operation An internal security database corruption has been encountered. ERROR_RXACT_COMMIT_FAILURE 1371 Cannot perform this operation on built-in accounts. ERROR_SPECIAL_ACCOUNT 1372 Cannot perform this operation on this built-in special group. ERROR_SPECIAL_GROUP 1373 Cannot perform this operation on this built-in special user. ERROR_SPECIAL_USER 1374 The user cannot be removed from a group because the group is ERROR_MEMBERS_PRIMARY_GROUP currently the user's primary group The token is already in use as a primary token. ERROR_TOKEN_ALREADY_IN_USE 1376 The specified local group does not exist. ERROR_NO_SUCH_ALIAS 1377 The specified account name is not a member of the local group. ERROR_MEMBER_NOT_ IN_ALIAS 1378 The specified account name is already a member of the local ERROR_MEMBER_IN_ALIAS group The specified local group already exists. ERROR_ALIAS_EXISTS 1380 Logon failure the user has not been granted the requested logon ERROR_LOGON_NOT_GRANTED type at this computer The maximum number of secrets that may be stored in a single ERROR_TOO_MANY_SECRETS system has been exceeded The length of a secret exceeds the maximum length allowed. ERROR_SECRET_TOO_LONG 1383 The local security authority database contains an internal ERROR_INTERNAL_DB_ERROR inconsistency.

182 1384 During a logon attempt, the user's security context accumulated too ERROR_TOO_MANY_CONTEXT_IDS many security IDs Logon failure the user has not been granted the requested logon ERROR_LOGON_TYPE_NOT_GRANTED type at this computer A cross-encrypted password is necessary to change a user password A new member could not be added to a local group because the member does not exist A new member could not be added to a local group because the member has the wrong account type. ERROR_NT_CROSS_ENCRYPTION_REQUIRED ERROR_NO_SUCH_MEMBER ERROR_INVALID_MEMBER 1389 Too many security IDs have been specified. ERROR_TOO_MANY_SIDS 1390 A cross-encrypted password is necessary to change this user ERROR_LM_CROSS_ENCRYPTION_ REQUIRED password Indicates an ACL contains no inheritable components. ERROR_NO_INHERITANCE 1392 The file or directory is corrupted and non-readable. ERROR_FILE_CORRUPT 1393 The disk structure is corrupted and non-readable. ERROR_DISK_CORRUPT 1394 There is no user session key for the specified logon session. ERROR_NO_USER_SESSION_KEY 1395 The service being accessed is licensed for a particular number of ERROR_LICENSE_QUOTA_EXCEEDED connections. No more connections can be made to the service at this time because there are already as many connections as the service can accept Invalid window handle. ERROR_INVALID_WINDOW_HANDLE 1401 Invalid menu handle. ERROR_INVALID_MENU_ HANDLE 1402 Invalid cursor handle. ERROR_INVALID_CURSOR_HANDLE 1403 Invalid accelerator table handle. ERROR_INVALID_ACCEL_HANDLE 1404 Invalid hook handle. ERROR_INVALID_HOOK_HANDLE 1405 Invalid handle to a multiple-window position structure. ERROR_INVALID_DWP_HANDLE 1406 Cannot create a top-level child window. ERROR_TLW_WITH_WSCHILD 1407 Cannot find window class. ERROR_CANNOT_FIND_WND_CLASS 1408 Invalid window, it belongs to another thread. ERROR_WINDOW_OF_OTHER_THREAD 1409 Hot key is already registered. ERROR_HOTKEY_ALREADY_REGISTERED 1410 Class already exists. ERROR_CLASS_ALREADY_EXISTS 1411 Class does not exist. ERROR_CLASS_DOES_NOT_EXIST 1412 Class still has open windows. ERROR_CLASS_HAS_WINDOWS 1413 Invalid index. ERROR_INVALID_INDEX 1414 Invalid icon handle. ERROR_INVALID_ICON_HANDLE 1415 Using private DIALOG window words. ERROR_PRIVATE_DIALOG_INDEX 1416 The list box identifier was not found. ERROR_LISTBOX_ID_NOT_FOUND 1417 No wildcards were found. ERROR_NO_WILDCARD_CHARACTERS 1418 Thread does not have a clipboard open. ERROR_CLIPBOARD_NOT_OPEN 1419 Hot key is not registered. ERROR_HOTKEY_NOT_REGISTERED 1420 The window is not a valid dialog window. ERROR_WINDOW_NOT_DIALOG 1421 Control identifier not found. ERROR_CONTROL_ID_NOT_FOUND 1422 Invalid message for a combo box because it does not have an edit control. ERROR_INVALID_COMBOBOX_MESSAGE 1423 The window is not a combo box. ERROR_WINDOW_NOT_COMBOBOX 1424 Height must be less than 256. ERROR_INVALID_EDIT_HEIGHT 1425 Invalid device context (DC) handle. ERROR_DC_NOT_FOUND 1426 Invalid hook procedure type. ERROR_INVALID_HOOK_FILTER 1427 Invalid hook procedure. ERROR_INVALID_FILTER_PROC 1428 Cannot set nonlocal hook without a module handle. ERROR_HOOK_NEEDS_HMOD 1429 This hook procedure can only be set globally. ERROR_GLOBAL_ONLY_HOOK 1430 The journal hook procedure is already installed. ERROR_JOURNAL_HOOK_SET 1431 The hook procedure is not installed. ERROR_HOOK_NOT_INSTALLED 1432 Invalid message for single-selection list box. ERROR_INVALID_LB_MESSAGE 1434 This list box does not support tab stops. ERROR_LB_WITHOUT_TABSTOPS 1435 Cannot destroy object created by another thread. ERROR_DESTROY_OBJECT_OF_OTHER_THREAD 1436 Child windows cannot have menus. ERROR_CHILD_WINDOW_MENU 1437 The window does not have a system menu. ERROR_NO_SYSTEM_MENU 1438 Invalid message box style. ERROR_INVALID_MSGBOX_STYLE 1439 Invalid system-wide (SPI_*) parameter. ERROR_INVALID_SPI_VALUE

183 1440 Screen already locked. ERROR_SCREEN_ALREADY_LOCKED 1441 All handles to windows in a multiple-window position structure must have the same parent. ERROR_HWNDS_HAVE_DIFF_PARENT 1442 The window is not a child window. ERROR_NOT_CHILD_WINDOW 1443 Invalid GW_* command. ERROR_INVALID_GW_COMMAND 1444 Invalid thread identifier. ERROR_INVALID_THREAD_ID 1445 Cannot process a message from a window that is not a multiple-document interface (MDI) window. ERROR_NON_MDICHILD_WINDOW 1446 Pop-up menu already active. ERROR_POPUP_ALREADY_ACTIVE 1447 The window does not have scroll bars. ERROR_NO_SCROLLBARS 1448 Scroll bar range cannot be greater than 0x7FFF. ERROR_INVALID_SCROLLBAR_RANGE 1449 Cannot show or remove the window in the way specified. ERROR_INVALID_SHOWWIN_COMMAND 1450 Insufficient system resources exist to complete the requested service Insufficient system resources exist to complete the requested service Insufficient system resources exist to complete the requested service. ERROR_NO_SYSTEM_RESOURCES ERROR_NONPAGED_SYSTEM_RESOURCES ERROR_PAGED_SYSTEM_RESOURCES 1453 Insufficient quota to complete the requested service. ERROR_WORKING_SET_QUOTA 1454 Insufficient quota to complete the requested service. ERROR_PAGEFILE_QUOTA 1455 The paging file is too small for this operation to complete. ERROR_COMMITMENT_LIMIT 1456 A menu item was not found. ERROR_MENU_ITEM_NOT_FOUND 1457 Invalid keyboard layout handle. ERROR_INVALID_KEYBOARD_HANDLE 1458 Hook type not allowed. ERROR_HOOK_TYPE_NOT_ALLOWED 1459 This operation requires an interactive window station. ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION 1460 This operation returned because the time-out period expired. ERROR_TIMEOUT 1500 The event tracking file is corrupted. ERROR_EVENTLOG_FILE_CORRUPT 1501 No event tracking file could be opened, so the event tracking service did not start. ERROR_EVENTLOG_CANT_START 1502 The event tracking file is full. ERROR_LOG_FILE_FULL 1503 The event tracking file has changed between read operations. ERROR_EVENTLOG_FILE_CHANGED 1700 The string binding is invalid. RPC_S_INVALID_STRING_BINDING 1701 The binding handle is not the correct type. RPC_S_WRONG_KIND_OF_BINDING 1702 The binding handle is invalid. RPC_S_INVALID_BINDING 1703 The RPC protocol sequence is not supported. RPC_S_PROTSEQ_NOT_SUPPORTED 1704 The RPC protocol sequence is invalid. RPC_S_INVALID_RPC_PROTSEQ 1705 The string universal unique identifier (UUID) is invalid. RPC_S_INVALID_STRING_UUID 1706 The endpoint format is invalid. RPC_S_INVALID_ENDPOINT_FORMAT 1707 The network address is invalid. RPC_S_INVALID_NET_ADDR 1708 No endpoint was found. RPC_S_NO_ENDPOINT_FOUND 1709 The time-out value is invalid. RPC_S_INVALID_TIMEOUT 1710 The object universal unique identifier (UUID) was not found. RPC_S_OBJECT_NOT_FOUND 1711 The object universally unique identifier (UUID) has already been registered. RPC_S_ALREADY_REGISTERED 1712 The type UUID has already been registered. RPC_S_TYPE_ALREADY_REGISTERED 1713 The remote procedure call (RPC) server is already listening. RPC_S_ALREADY_LISTENING 1714 No protocol sequences have been registered. RPC_S_NO_PROTSEQS_REGISTERED 1715 The RPC server is not listening. RPC_S_NOT_LISTENING 1716 The manager type is unknown. RPC_S_UNKNOWN_MGR_TYPE 1717 The interface is unknown. RPC_S_UNKNOWN_IF 1718 There are no bindings. RPC_S_NO_BINDINGS 1719 There are no protocol sequences. RPC_S_NO_PROTSEQS 1720 The endpoint cannot be created. RPC_S_CANT_CREATE_ENDPOINT 1721 Not enough resources are available to complete this operation. RPC_S_OUT_OF_RESOURCES 1722 The RPC server is unavailable. RPC_S_SERVER_UNAVAILABLE 1723 The RPC server is too busy to complete this operation. RPC_S_SERVER_TOO_BUSY 1724 The network options are invalid. RPC_S_INVALID_NETWORK_OPTIONS 1725 There is not a remote procedure call active in this thread. RPC_S_NO_CALL_ACTIVE 1726 The remote procedure call failed. RPC_S_CALL_FAILED 1727 The remote procedure call failed and did not execute. RPC_S_CALL_FAILED_DNE 1728 A remote procedure call (RPC) protocol error occurred. RPC_S_PROTOCOL_ERROR

184 1730 The transfer syntax is not supported by the RPC server. RPC_S_UNSUPPORTED_TRANS_SYN 1732 The universal unique identifier (UUID) type is not supported. RPC_S_UNSUPPORTED_TYPE 1733 The tag is invalid. RPC_S_INVALID_TAG 1734 The array bounds are invalid. RPC_S_INVALID_BOUND 1735 The binding does not contain an entry name. RPC_S_NO_ENTRY_NAME 1736 The name syntax is invalid. RPC_S_INVALID_NAME_SYNTAX 1737 The name syntax is not supported. RPC_S_UNSUPPORTED_NAME_SYNTAX 1739 No network address is available to use to construct a universal unique identifier (UUID). RPC_S_UUID_NO_ADDRESS 1740 The endpoint is a duplicate. RPC_S_DUPLICATE_ENDPOINT 1741 The authentication type is unknown. RPC_S_UNKNOWN_AUTHN_TYPE 1742 The maximum number of calls is too small. RPC_S_MAX_CALLS_TOO_SMALL 1743 The string is too long. RPC_S_STRING_TOO_LONG 1744 The RPC protocol sequence was not found. RPC_S_PROTSEQ_NOT_FOUND 1745 The procedure number is out of range. RPC_S_PROCNUM_OUT_OF_RANGE 1746 The binding does not contain any authentication information. RPC_S_BINDING_HAS_NO_AUTH 1747 The authentication service is unknown. RPC_S_UNKNOWN_AUTHN_SERVICE 1748 The authentication level is unknown. RPC_S_UNKNOWN_AUTHN_LEVEL 1749 The security context is invalid. RPC_S_INVALID_AUTH_IDENTITY 1750 The authorization service is unknown. RPC_S_UNKNOWN_AUTHZ_SERVICE 1751 The entry is invalid. EPT_S_INVALID_ENTRY 1752 The server endpoint cannot perform the operation. EPT_S_CANT_PERFORM_OP 1753 There are no more endpoints available from the endpoint mapper. EPT_S_NOT_REGISTERED 1754 No interfaces have been exported. RPC_S_NOTHING_TO_EXPORT 1755 The entry name is incomplete. RPC_S_INCOMPLETE_NAME 1756 The version option is invalid. RPC_S_INVALID_VERS_OPTION 1757 There are no more members. RPC_S_NO_MORE_MEMBERS 1758 There is nothing to unexport. RPC_S_NOT_ALL_OBJS_UNEXPORTED 1759 The interface was not found. RPC_S_INTERFACE_NOT_FOUND 1760 The entry already exists. RPC_S_ENTRY_ALREADY_EXISTS 1761 The entry is not found. RPC_S_ENTRY_NOT_FOUND 1762 The name service is unavailable. RPC_S_NAME_SERVICE_UNAVAILABLE 1763 The network address family is invalid. RPC_S_INVALID_NAF_ID 1764 The requested operation is not supported. RPC_S_CANNOT_SUPPORT 1765 No security context is available to allow impersonation. RPC_S_NO_CONTEXT_AVAILABLE 1766 An internal error occurred in a remote procedure call (RPC). RPC_S_INTERNAL_ERROR 1767 The RPC server attempted an integer division by zero. RPC_S_ZERO_DIVIDE 1768 An addressing error occurred in the RPC server. RPC_S_ADDRESS_ERROR 1769 A floating-point operation at the RPC server caused a division by zero. RPC_S_FP_DIV_ZERO 1770 A floating-point underflow occurred at the RPC server. RPC_S_FP_UNDERFLOW 1771 A floating-point overflow occurred at the RPC server. RPC_S_FP_OVERFLOW 1772 The list of RPC servers available for the binding of auto handles has been exhausted. RPC_X_NO_MORE_ENTRIES 1773 Unable to open the character translation table file. RPC_X_SS_CHAR_TRANS_OPEN_FAIL 1774 The file containing the character translation table has fewer than 512 bytes A null context handle was passed from the client to the host during a remote procedure call. RPC_X_SS_CHAR_TRANS_SHORT_FILE RPC_X_SS_IN_NULL_CONTEXT 1777 The context handle changed during a remote procedure call. RPC_X_SS_CONTEXT_DAMAGED 1778 The binding handles passed to a remote procedure call do not match. RPC_X_SS_HANDLES_MISMATCH 1779 The stub is unable to get the remote procedure call handle. RPC_X_SS_CANNOT_GET_CALL_HANDLE 1780 A null reference pointer was passed to the stub. RPC_X_NULL_REF_POINTER 1781 The enumeration value is out of range. RPC_X_ENUM_VALUE_OUT_OF_RANGE 1782 The byte count is too small. RPC_X_BYTE_COUNT_TOO_SMALL 1783 The stub received bad data. RPC_X_BAD_STUB_DATA 1784 The supplied user buffer is not valid for the requested operation. ERROR_INVALID_USER_BUFFER 1785 The disk media is not recognized. It may not be formatted. ERROR_UNRECOGNIZED_MEDIA 1786 The workstation does not have a trust secret. ERROR_NO_TRUST_LSA_SECRET

185 1787 The SAM database on the Windows NT Server does not have a computer account for this workstation trust relationship The trust relationship between the primary domain and the trusted domain failed The trust relationship between this workstation and the primary domain failed. ERROR_NO_TRUST_SAM_ACCOUNT ERROR_TRUSTED_DOMAIN_FAILURE ERROR_TRUSTED_RELATIONSHIP_FAILURE 1790 The network logon failed. ERROR_TRUST_FAILURE 1791 A remote procedure call is already in progress for this thread. RPC_S_CALL_IN_PROGRESS 1792 An attempt was made to logon, but the network logon service was not started. ERROR_NETLOGON_NOT_STARTED 1793 The user's account has expired. ERROR_ACCOUNT_EXPIRED 1794 The redirector is in use and cannot be unloaded. ERROR_REDIRECTOR_HAS_OPEN_HANDLES 1795 The specified printer driver is already installed. ERROR_PRINTER_DRIVER_ALREADY_INSTALLED 1796 The specified port is unknown. ERROR_UNKNOWN_PORT 1797 The printer driver is unknown. ERROR_UNKNOWN_PRINTER_DRIVER 1798 The print processor is unknown. ERROR_UNKNOWN_PRINTPROCESSOR 1799 The specified separator file is invalid. ERROR_INVALID_SEPARATOR_FILE 1800 The specified priority is invalid. ERROR_INVALID_PRIORITY 1801 The printer name is invalid. ERROR_INVALID_PRINTER_NAME 1802 The printer already exists. ERROR_PRINTER_ALREADY_EXISTS 1803 The printer command is invalid. ERROR_INVALID_PRINTER_COMMAND 1804 The specified data type is invalid. ERROR_INVALID_DATATYPE 1805 The environment specified is invalid. ERROR_INVALID_ENVIRONMENT 1806 There are no more bindings. RPC_S_NO_MORE_BINDINGS 1807 The account used is an interdomain trust account. Use your global user account or local user account to access this server The account used is a computer account. Use your global user account or local user account to access this server The account used is a server trust account. Use your global user account or local user account to access this server The name or security identifier (SID) of the domain specified is inconsistent with the trust information for that domain. ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUN T ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUN T ERROR_NOLOGON_SERVER_TRUST_ACCOUNT ERROR_DOMAIN_TRUST_INCONSISTENT 1811 The server is in use and cannot be unloaded. ERROR_SERVER_HAS_OPEN_HANDLES 1812 The specified image file did not contain a resource section. ERROR_RESOURCE_DATA_NOT_FOUND 1813 The specified resource type cannot be found in the image file. ERROR_RESOURCE_TYPE_NOT_FOUND 1814 The specified resource name cannot be found in the image file. ERROR_RESOURCE_NAME_NOT_FOUND 1815 The specified resource language identifier cannot be found in the image file. ERROR_RESOURCE_LANG_NOT_FOUND 1816 Not enough quota is available to process this command. ERROR_NOT_ENOUGH_QUOTA 1817 No interfaces have been registered. RPC_S_NO_INTERFACES 1818 The server was altered while processing this call. RPC_S_CALL_CANCELLED 1819 The binding handle does not contain all required information. RPC_S_BINDING_INCOMPLETE 1820 Communications failure. RPC_S_COMM_FAILURE 1821 The requested authentication level is not supported. RPC_S_UNSUPPORTED_AUTHN_LEVEL 1822 No principal name registered. RPC_S_NO_PRINC_NAME 1823 The error specified is not a valid Windows NT RPC error value. RPC_S_NOT_RPC_ERROR 1824 A UUID that is valid only on this computer has been allocated. RPC_S_UUID_LOCAL_ONLY 1825 A security package specific error occurred. RPC_S_SEC_PKG_ERROR 1826 Thread is not canceled. RPC_S_NOT_CANCELLED 1827 Invalid operation on the encoding/decoding handle. RPC_X_INVALID_ES_ACTION 1828 Incompatible version of the serializing package. RPC_X_WRONG_ES_VERSION 1829 Incompatible version of the RPC stub. RPC_X_WRONG_STUB_VERSION 1830 The idl pipe object is invalid or corrupted. RPC_X_INVALID_PIPE_OBJECT 1831 The operation is invalid for a given idl pipe object. RPC_X_INVALID_PIPE_OPERATION 1832 The Interface Definition Language (IDL) pipe version is not supported. RPC_X_WRONG_PIPE_VERSION 1898 The group member was not found. RPC_S_GROUP_MEMBER_NOT_FOUND 1899 The endpoint mapper database could not be created. EPT_S_CANT_CREATE 1900 The object UUID is the nil UUID. RPC_S_INVALID_OBJECT 1901 The specified time is invalid. ERROR_INVALID_TIME 1902 The specified form name is invalid. ERROR_INVALID_FORM_NAME

186 1903 The specified form size is invalid. ERROR_INVALID_FORM_SIZE 1904 The specified printer handle is already being waited on ERROR_ALREADY_WAITING 1905 The specified printer has been deleted. ERROR_PRINTER_DELETED 1906 The state of the printer is invalid. ERROR_INVALID_PRINTER_STATE 1907 The user must change his password before he logs on the first time. ERROR_PASSWORD_MUST_CHANGE 1908 Could not find the domain controller for this domain. ERROR_DOMAIN_CONTROLLER_NOT_FOUND 1909 The referenced account is currently locked out and may not be logged on to. ERROR_ACCOUNT_LOCKED_OUT 1910 The object exporter specified was not found. OR_INVALID_OXID 1911 The object specified was not found. OR_INVALID_OID 1912 The object resolver set specified was not found. OR_INVALID_SET 1913 Some data remains to be sent in the request buffer. RPC_S_SEND_INCOMPLETE 2000 The pixel format is invalid. ERROR_INVALID_PIXEL_FORMAT 2001 The specified driver is invalid. ERROR_BAD_DRIVER 2002 The window style or class attribute is invalid for this operation. ERROR_INVALID_WINDOW_STYLE 2003 The requested metafile operation is not supported. ERROR_METAFILE_NOT_SUPPORTED 2004 The requested transformation operation is not supported. ERROR_TRANSFORM_NOT_SUPPORTED 2005 The requested clipping operation is not supported. ERROR_CLIPPING_NOT_SUPPORTED 2202 The specified user name is invalid. ERROR_BAD_USERNAME 2250 This network connection does not exist. ERROR_NOT_CONNECTED 2401 This network connection has files open or requests pending. ERROR_OPEN_FILES 2402 Active connections still exist. ERROR_ACTIVE_CONNECTIONS 2404 The device is in use by an active process and cannot be disconnected. ERROR_DEVICE_IN_USE 3000 The specified print monitor is unknown. ERROR_UNKNOWN_PRINT_MONITOR 3001 The specified printer driver is currently in use. ERROR_PRINTER_DRIVER_IN_USE 3002 The spool file was not found. ERROR_SPOOL_FILE_NOT_FOUND 3003 A StartDocPrinter call was not issued. ERROR_SPL_NO_STARTDOC 3004 An AddJob call was not issued. ERROR_SPL_NO_ADDJOB 3005 The specified print processor has already been installed. ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED 3006 The specified print monitor has already been installed. ERROR_PRINT_MONITOR_ALREADY_INSTALLED 3007 The specified print monitor does not have the required functions. ERROR_INVALID_PRINT_MONITOR 3008 The specified print monitor is currently in use. ERROR_PRINT_MONITOR_IN_USE 3009 The requested operation is not allowed when there are jobs queued to the printer The requested operation is successful. Changes will not be effective until the system is rebooted The requested operation is successful. Changes will not be effective until the service is restarted. ERROR_PRINTER_HAS_JOBS_QUEUED ERROR_SUCCESS_REBOOT_REQUIRED ERROR_SUCCESS_RESTART_REQUIRED 4000 WINS encountered an error while processing the command. ERROR_WINS_INTERNAL 4001 The local WINS cannot be deleted. ERROR_CAN_NOT_DEL_LOCAL_WINS 4002 The importation from the file failed. ERROR_STATIC_INIT 4003 The backup failed. Was a full backup done before? ERROR_INC_BACKUP 4004 The backup failed. Check the directory to which you are backing the database. ERROR_FULL_BACKUP 4005 The name does not exist in the WINS database. ERROR_REC_NON_EXISTENT 4006 Replication with a nonconfigured partner is not allowed. ERROR_RPL_NOT_ALLOWED 6118 The list of servers for this workgroup is not currently available. ERROR_NO_BROWSER_SERVERS_FOUND

187 第五章 Windows CE.NET 侦错环境 Microsoft ActiveSync 是一套安装于 Windows ( 2000 / xp / 98 / Me ) PC 中的 Windows CE 装置联机软件, 使您的 PC 可以认识 Windows CE 装置, 并进行 PC 端与 Windows CE 装置端的资料同步 管理 安装新软件及侦错的联机工具 例如, 在 embedded Visual C++ 开发 Windows CE 装置应用程序时最常用的侦错方式就是使用 Microsoft ActiveSync 联机到 Windows CE 装置来进行应用程序侦错及除错 在开始使用 embedded Visual C++ 线上侦错的功能之前, 你必须完成以下准备事项来建立起你的应用程序开发侦错环境 : 1. 在 PC 端安装 Microsoft ActiveSync 2. 使用 Microsoft ActiveSync 连接你的 PC 和 Windows CE 装置 3. 安装 Windows CE 装置制造商所提供的 SDK 4. 使用 Windows CE 装置制造商所提供的 SDK 建置你的应用程序 5. 调校平台管理员 (Platform manager) 然后, 你就可以使用 embedded Visual C++ 的侦错器 (Debugger) 和远程工具 (Remote Tools) 来进行你的 Windows CE 装置应用程序的侦错和除错 5-1 在 PC 端安装 Microsoft ActiveSync Microsoft ActiveSync 可以在以下的微软公司软件下载中心下载 : 这是一个免费的软件, 目前最新的版本是 3.71 版 待你进入微软公司软件下载中心后, 键入 ActiveSyn 就可找到并下载 本书所附光盘亦有收录笔者完稿时的最新版本的 Microsoft ActiveSync 3.71 版, 档名为 MSASYNC.EXE 其安装歩骤如下 : Step 1 : 执行 MSASYNC.EXE, 接着会出现以下安装画面

188 Step 2 : 点选下一歩 (next) 到下一个安装画面

189 Step 3 : 设定安装路径及目录后再点选下一歩 (next),microsoft ActiveSync 便开始自动安装安装完成后开始菜单右方状态列将会出现 ActiveSync 状态图标, 并且于桌面上或开始的程序集也会出现 Microsoft ActiveSync 图标 5-2 使用 Microsoft ActiveSync 连接你的 PC 和 Windows CE 装置 在 PC 端点选的开始菜单右方状态列的 ActiveSync 图标或从桌面上或开始功能的 程序集中 Microsoft ActiveSync 将出现以下画面 : 如果是第一次激活 ActiveSync 连结 Windows CE 装置, 要先点选 [ 档案 ] 中的 [ 取得 联机 ] 并按 [ 下一步 ] 如下图

190 此时 ActiveSync 会开始侦测 PC 端的 USB 埠 各个 COM 端口及红外通讯端口 (IR) 是否有连结到支持 ActiveSync 的 Windows CE 装置, 如下图 在此同时必需迅速 的在 Windows CE 装置端也激活 ActiveSync 来响应 PC 端的 ActiveSync

191 如果不是第一次激活 ActiveSync 连结 Windows CE 装置而是之前就已成功联机过, 只要点选 [ 档案 ] 中的 [ 联机设定 ] 并按 [ 下一步 ] 如下图, 或直接只激活 Windows CE 装置端的 ActiveSync

192 注 1 : ActiveSync 目前支持 USB 埠 COM 埠 (NULL MODEM) 及红外通讯端口 (IR), 每个 Windows CE 装置制造商可以选择要使用那一种来 例如本书所使用的泓格科技 Wincon-8000 即是使用 COM 埠及 NULL MODEM serial cable 来支持 注 2 : 当你的 PC 端用来支持 ActiveSync 接口不想再用在支持而想用在其它功能上 ( 例如 COM 想用来做 Hyper Terminal) 时, 必须记得在 ActiveSync 点选 [ 档案 ] 中的 [ 联机设定 ] 去取消该接口, 否则此接口不管有无与 Windows CE 装置联机都会被独占 接下来, 无论你是不是第一次激活 ActiveSync 连结 Windows CE 装置, 都会出现 以下画面来设定与该 Windows CE 装置联机的方式 :

193 一旦 PC 端 ActiveSync 连结 Windows CE 装置成功后 PC 上会出现 ActiveSync 激活 画面如下图, 此时开始菜单中的状态也会变为绿色

194 成功联机后, 可以执行工具列中的 [ 详细资料 ] 来浏览 Windows CE 装置的内容及 做档案数据传输或应用程序的下载如下图 5-3 安装 Windows CE 装置制造商所提供的 SDK 要在 embedded Visual C++ 开发工具上使用 ActiveSync 的联机来执行 Windows CE 装置的应用程序开发侦错, 必须先安装 Windows CE 装置制造商所提供的 SDK 例如, 泓格科技 Wincon-8000 提供了支持 embedded Visual C++ 侦错的 SDK 如下图, 只要直接双击这个 SDK 档案来执行并按步骤安装在你的 PC 端, 即可支持 embedded Visual C++ 侦错 Windows CE 装置应用程序开发的侦错功能 该 SDK 亦收录于本书光盘中, 读者亦可到泓格科技网站下载最新版本

195 5-4 使用 Windows CE 装置制造商所提供的 SDK 建置你的应用程序 例如, 泓格科技提供侦错的 SDK 名为 SA_IA, 必须在编译 (build) 应用程序时 选用这个 SDK 及选择编译 (build) 成侦错 (debug) 版本如下图, 如此该 Windows CE 装置应用程序在开发阶段就能支持如在 Visual C++ 开发 PC 端的应用程序般的侦

196 错功能 5-5 调校平台管理员 (Platform manager) 最后, 要使用在 embedded Visual C++ 的 [Tools] 的 [Configure Platform Manager] 来设 定 ActiveSync 联机, 如下图

197 在开始侦错应用程序之前需在 [Build] 下拉选单中选取 [Set Active Platform] 来指定 你侦错的平台, 如下图 侦错平台的建立是使用上述的平台管理员 (Platform manager) [] 所建立的

198 5-6 开始侦错 待 ActiveSync 联机完成 应用程序建置完成及 embedded Visual C++ 开发环境设定完成后, 便可开始对应用程序开始侦错, 如下图 embedded Visual C++ 的侦错器 (Debugger) 可以让你设定和移除断点 (breakpoints), 并执行一行一行的执行你的应用程序 同时, 也可以开启观察 (Watch) 变量(Variables) 缓存器(Registers) 内存 (Memory) 呼叫堆栈(Call Stack) 和反组译 (Disassembly) 等窗口来显示额外信息提供更完整的信息来协助侦错 embedded Visual C++ 除了提供侦错功能外, 也在 [Tools] 的选单中提供了完整的远 程工具如下图所示, 以协助侦错或观察远程 Windows CE 装置

199

200 第二篇泓格 Wincon-8000 Windows CE.NET 嵌入式工业用控制器第六章 :Windows Based 工业用控制器简介 6-1 沿革在自动控制技术领域上, 从 60 年代后期, 美国 Bedford Associate 提出 Modular Digital Controller(MODICON) 来取代继电器控制盘开始, 并研发出世界上第一种投入商业生产的 PLC (Programmable Logic Controller)- MODICON 084 后, PLC 厂家始终处于独领风骚的地位, 不仅是规格的制定者, 更主导了整个市场的走向 可程序逻辑控制器 ( Programmable Logic Controller, 简称 PLC), 从美国发明使用, 而一直延用至今也有三十年历史了, 所以它的控制法则 程序编程我在这就不多加赘言 那么针对功能性来看, 基本上可程控器就象是加了特殊界面的微电脑系统, 一切的数据处理在程序执行器中进行, 输入模块取得外面受控系统目前的位置状态, 经过程序判断后, 决定驱动输出模块上的那些驱动器, 以驱动受控系统 本来其用途是为了取代电气控制所使用的传统电机配电盘而衍生出来的产品, 所以其外观结构和电气讯号处理方式也与一般 PC Based 不太一样 ; 当然电子科技的进步, 也让它与计算机整合运用, 分布式网络链接用途, 增加了许多的弹性 但是近年来, 由于用户对开放性架构的强力要求及渇望, 且因为信息技术 (IT) 发展的日新月异, 使得 PLC 厂家不得不认真面对这一变革, 这结果同时也让基于 PC-Based 技术发展多年的工业计算机厂商重新取得了参赛权, 我们可以预见的是未来不管是 PLC 或 PC-Based 控制器, 除了保留 PLC 在过程控制 (Process Control) 上的优点之外, 随着计算技术 (Computing), 通信技术 (Communication) 和软件编程技术的发展, 即将趋向于使用开放式的硬件平台, 操作系统以及统一的编程工具, 此外, 为达到 万物连网 的要求, 采用开放的以太网络 (Ethernet) 接口来串起其它工业现场总线, 也是非常必要的. 这其中最关键的技术就在于 1. 嵌入式 PC 系统 2. 支持各种现场总线的 I/O 模块 3. 符合 IEC 标准的编程软件近代社会由于个人生活品质的提升, 使得对工业制品的需求量与日俱增, 而且在产品的个性化及多样性趋势也遍及所有产业, 这代表着新一代制造加工技术必须更具弹性及效率, 而计算机应用在工业似乎更能符合未来市场的潮流 因此, 一个具备弹性及效率的制造系统, 在机电高度整合下, 为了处理庞大的信息, 通常需要操作系统在整体上, 包括通讯 显示 数学演算 资料撷取的硬件与驱动程序, 能展现更高的效益, 以作为外围接口与电控核心资料交换及讯号处理的桥接 ; 所以可预期 Windows Based 工业控制器在日后应用将会更为广泛 6-2 Windows Based 工业用控制器 v.s PLC PC Based 工业用控制器的作业平台, 如 Unix Linux OS/2 QNX., 而最广为大众使用的莫过于 Microsoft Windows OS 窗口软件, 其中大部份都不具实时运

201 作能力 (Real-Time), 甚至效能及稳定性也落后其它作业平台, 那为什么还要选择使用窗口操作系统呢? 原因不外乎其便利性 普及性 及外围接口资源高度的整合兼容性, 我想这是他的优点, 也是将来面对各种型态 OS 的竞争中, 所展现最大的优势 当然窗口作业是微软窗口软件的利基, 但是有些 PC Based 工业控制器, 不只要求实时性, 另外还有嵌入式系统的需求 ; 所以近年来微软在 RTOS & Embedded System 也有很大的进步与成果, 例如 Windows Embedded 操作系统的 Windows CE.NET 是一个具备强实时 (Hard Real-Time) Windows Embedded OS 的 PC Based 工业用控制器, 将是未来工控界另一新的利器 身为中国自动化产品生产厂家一员的泓格科技, 经过近两年的努力, 不仅将嵌入式的技术应用在 WinCon 产品上, 更将积累多年在研发各类总线 I/O 的技术附加在其中, 并为了提供符合 IEC 的编程软件, 而把嵌入式运行版 (Run-time Engine) 的 ISaGRAF 与 Micro TRACE MODE 5 加入操作系统映像 (OS image) 中, 此外为求达到使用者便利性, 在嵌入式人机接口 (HMI), 我们则提供了 Indusoft Web Studio 与 AdAstrA Embedded HMI. 6-3 结论在硬件方面,WinCon-8000 提供了像 IPC 般的标准接口, 例如 : VGA 显示, PS/2 键盘鼠标, Compact Flash, USB Host, LAN, RS-232/485, 但却不象 IPC 般的过度膨胀, 在功能上正好满足工控上的需求, 且提供更且竞争的价格, 同时因为是去无硬盘及低功耗 RISC CPU 及无风扇的特性, 远比 IPC 更能承受工控现场的恶劣环境 WinCon-8000 还提供很多 I/O 模块以满足各种应用 在软件方面 Windows CE.net 的强实时及多任务的特性提供了工控界实时和确定性控制 在应用方面 Windows CE.net 提供了象 Microsoft desktop Windows OS ( 如 Windows 2000/XP) 般的多种组件, 让应用开发者有完备的开发平台例如网络, Internet Service, Web Server, FTP Server, DCOM,.NET Compact Framework, Connectivity to ERP 等软件组件, 让系统开发者有充分的工具以把更多时间和精力放在他的应用和控制逻辑上 总之,WinCon-8000 就象是一台 IPC 加上各种 I/O 模块, 利用 Windows CE.net 强大功能来为你 IA 领域做任何事情

202 第七章 : 泓格 ICPDAS WinCON 8000 系列简介 Wincon-8000 是泓格科技所制造最新型的嵌入式控制器, 适时提供你传统 PLC 的便利和 PC 的窗口能力, 他的领导技术包含这两者所特有的优点 Wincon-8000 系统被 Windows CE.NET 赋予强大的能力, 并且将窗口的程序设计风格和技术带进个人化计算机的 PLC 世界 应用开发者可以用 WinconSDK 在微软的 Visual Studio.NET 和 embedded Visual C++ 上直接开发自动控制应用程序, 然后将程序下载到 Wincon-8000 执行 除了使用微软官方的 Visual Studio.NET 和 embedded Visual C++ 开发工具撰写 C 或 Visaul Bais.NET/Visual C#.NET 的应用程序外, 因为 Wincon-8000 的操作系统是开放的 Windows CE.NET 平台, 应用系统开发者也可以使用第三方 (third party) 应用软件来开发应用系统 例如, Wincon-8000 支持 InduSoft 和 IsaGraf,InduSoft 是一个 HMI/SCADA 应用软件, 而 IsaGraf 是一个软逻辑 (soft PLC) 软件, 这两种软件都有提供 Windows CE.NET 的运行版, 应用系统开发者可以在 PC 端的开发环境下开发应用程序再下载到 Wincon-8000 执行 Wincon 8000 的操作系统, 是以微软 Windows CE.NET 平台为架构基础, 再发展一系列之软硬件驱动程序, 及特殊运用软件, 以符合工业界, 高品质, 高信赖性, 耐久性佳之工业级控制器平台, 并被广泛的使用在以下各种领域 : 建筑结构, 桥梁结构, 水坝结构. 监控警报系统 产业机械, 专用机控制系统 ( 伺服系统 ) QC 量测平台 工厂自动化生产,ERP 系统, 人事门禁系统 网际网络控制系统 气象资料搜集系统, 环境环保监测系统 水泥厂控制系统 大楼消防监控系统, 厂房居家保全系统

203 射出成型机控制器 交通仪控系统 消费点餐系统 信息家电整合系统 电力监控, 电信机房监控 石油化学生产系统监控, 警报系统 资料长期安全搜集系统本书重点将着重在如何使用微软官方的 Visual Studio.NET 和 embedded Visual C++ 开发工具在 Wincon-8000 上开发应用程序, 读者若对 InduSoft 或 IsaGraph 有兴趣可到泓格科技网站上取得更进一步的详细资料 7-1 Wincon-8000 = Industrial PC + PLC Wincon-8000 结合了工业计算机 (Industrial-PC) 和 PLC 的功能, 它提供了工业计算机的功能例如 VGA LAN PS/2 键盘 / 鼠标 USB Compact Flash 串口, 和提供多样的 PLC 功能例如数据量输出入 (Digital Input/Output) 模拟量输出入(Analog Input/Output) 计数器 (Counter) 频率量测(Frequency) 和运动控制 (Motion Control) 等 除此之外, Wincon-8000 也改良达到传统工业计算机 (Industrial-PC) 和 PLC 无法达到的功能如下表 : 改良传统的 PLC: 改良工业用的 PC: 更高阶的性能 低花费 充足的内部存储器和巨大的非挥发 较低的电源消耗性储存设备 单纯的操作环境 标准的计算机接口及网络 没有硬盘死机的危险 工业标准的 Modbus, OPC Server, 同时控制好几个 I/O 模块

204 CAN Bus, 等 RTOS (Windows CE.NET) 窗口功能和图形用户接口 高级的微软窗口语言及工具 企业广域资料的连接 软件更新 第三方软件的支持 提供稳定性的系统 强实时系统 在窗口当住时, 继续运行 充沛的装置驱动器 Managed Code 的应用程序 7-2 硬件规格 WinCON 8000 系统架构及相关接口如下图

205 Wincon-8000 主要硬件规格如下表 : 控制器部分实现工业计算机的功能 Intel Strong ARM CPU, 206MHz SDRAM: 64M bytes Flash Memory: 32M bytes EEPROM: 16K bytes 64-bit 硬件单一序列号内置 Watchdog Timer 实时时钟 10 Base T: NE2000 兼容 1 VGA port: 320x240x16 to 1024x768x16 默认是 640x480x16 2 PS/2 端口 : 键盘和鼠标 1 Compact Flash 槽 : CF memory 卡 1 USB 1.1 接口 : USB 装置或 USB 鼠标复位键电源 LED 指示 底座部分

206 COM0: 系统内部侦错用 COM1: 标准 RS-232 COM2: RS-485 COM3: 内含于底内控制 PLC PLC 模块扩充插座 : 可加入 PLC 模块, 有 3 槽和 7 槽两种 集中式控制 PLC : 插于 PLC 模块扩充槽中 PLC 模块 远程控制 PLC : 透过底座的 RS-485 来控制 机构及环境特性供电及秏电 : 20W, +10Vdc +30Vdc 工作温度 : -25 C +75 C 湿度 : 5~95%, 无凝露尺寸 : 418x110x75.5mm(7 槽 ), 418x110x75.5mm(3 槽 ) 另外泓格科技也提供触控屏幕液晶显示器 工业级以太网交换机 电源供应器 USB 碟 CF 记忆卡及卡片阅读机等相关外围等, 读者有兴趣可以上泓格科技网站 ( 查询

207 Wincon-8000 硬件装置连结图 : 上图是典型的 Wincon-8000 应用系统架构图,WinCON 8000 提供 VGA 显示功能, 使用者可以接上显示器或触摸屏, 同时也提供 PS/2 供使用者接上键盘及鼠标, 再配合 PLC 模块的功能就是一台高度整合具价格优势的人机接口 (HMI: Human Machine Interface) 系统 I/O 模块部分, 提供了插于底座的集中式 I/O 模块及通过 RS-485 连接的远程控制 I/O 模块 Wincon-8000 的标准 RS-232 可连接调制解调器 (MODEM) 无线模块及设备等 7-3 WinCon8000 嵌入式操作系统功能 : Wincon 8000 的嵌入式操作系统, 是以微软 Windows CE.NET 平台为架构基础, 并依系统需求及应用需求进而调校及增加许多 Windows CE 装置标准功能如下表 : Windows CE 系统核心 应用程序开发支持 强及时确定性的控制多任务处理 GWES 窗口化使用者接口丰富的档案系统以太网连接性和标准网络界面支持多国语言及 UNICODE Windows CE 标准 SDK, ATL, ActiveX 组件, MFC Windows CE DCOM 及.NET Compact Framework Windows Sockets 2.2 版

208 服务及服务器 ADOCE/OLEDBCE, ADO.NET 等数据库组件 ActiveSync 侦错及装置信息同步内建网页服务器 (Http server) 并支持 Web-based 自动控制应用程序开发 FTP 服务器支持档案传输 XML, SOAP 及.NET Web Services HTTP, IIS, RDA 支持与 ERP 系统作连结 DOS 命令列 RAS/PPP PPPoE, VPN 7-4 WinCon-8000 的编程方式 : WinCON-8000 控制器的编程方式主要可分为三大类, 使用微软官方的 Visual Studio.NET 和 embedded Visual C++ 开发工具撰写 C 或 Visaul Bais.NET/Visual C#.NET 的应用程序, 使用图控工具软件 (SCADA/HMI) 开发, 或使用软逻辑 (Soft PLC/ Soft Logics) 工具软件开发 应用系统开发者可视各自专长 项目需求及现场特性来选择适合的编程方式 1. 使用标准程序语言开发 : 使用者可用微软提供的 Win CE 开发工具 (EVC++,VB.NET,C#.NET), 直接开发符合使用者需求的应用软件, 利用微软现有对象及其对象导向设计及可视化界面, 从单机之资料监视到配合后端数据库服务器,ERP 应用, 网际网络应用, 控制建置客制化之专业应用程序

209 而在这种编程方式下,Wincon-8000 也提供了以下的软件支持以提供应用程序开发者更便利快速的开发出稳定可靠及功能强大的应用程序 WinconSDK WinconSDK 提供 EVC++ VB.NET 及 VC#.NET 程序开发者完整的函数库来控制 Wincon-8000 硬件 取得系统信息 使用看门狗功能 读写 EEPROM 及控制泓格科技全系列类 PLC I/O 控制模块 (PLC-style I/O modules), 包括 I-8000/I-87K/I7000 等本地端或远程 I/O 模块 ModBus TCP/RTU Client and Server Wincon-8000 不但提供客户端 (Client side) 函数库以开发应用程序在 Wincon-8000 运行来读取其它 Modbus 装置外,Wincon-8000 本身也运行 Modbus Server 以扮演一个标准的 Modbus 装置来服务其它的 Modbus 客户端应用程序 (Modbus client application) i-push Embedded i-push Embedded 让 WinCon8000 拥有主动推播资料的能力, 能在 TCP/IP 的网络架构上 ( Wireless LAN / VPN / Internet / GPRS ), 把采集的资料 如监测数据 / 异常警讯 / 设备状态 源源不断的的把资料实时主动地推送到远程 而 i-push Embedded 更提供各种远程装置多样化的应用软件开发支持来开发应用程序接收 WinCon8000 主动推播来的实时讯息 2. 使用图控工具软件 (SCADA/HMI) 开发 : Wincon-8000 目前已经支持 Indusoft 图控软件, 其它如组态王 Adastra TraceMode 等知名图控软件亦进入最后出版测试中 透过泓格 ICPDAS 已开发完成之驱动程序联结, 使用者可以用应用 Indusoft 平台之功能, 快速设计使用者界面, 联结所有之 I/O 模块功能,

210 达到监视, 与控制的目的, 减少系统开发时间, 达到快速稳定经济之应用系统, 可在 Windows XP,2000,NT, 及 CE.NET 中执行, 在单机或透过 Internet,Intranet 环境, 当成人机界面 HMI(Human Machine Interface) 及监控系统 SCADA(Supervisory Control and Data Acquisition) System, 更可配合其 Indusoft Web Studio 及 WinCON-8000 之 http Server 达成远程 Web 网页应用 3. 使用软逻辑 (Soft PLC/ Soft Logics) 工具软件开发 ISaGRAF 是一个符合 IEC 的编程软件,Wincon-8000 目前已经支持 ISaGRAF 熟悉 PLC 的使用者能快速应用熟悉的 PLC 界面, 快速将应用系统建置起来, 让传统 PLC 使用者, 以熟悉之开发环境, 降低 WINcon-8000 学习门槛, 缩短开发应用程序时间, 在短期内即可应用顺利, 开发达到稳定经济之应用系统 也可配合 VB.NET 或 VC#.NET 所开发之应用程序与 ISaGRAF 作动态资料交换, 可达到更有弹性整合之应用系统 由于 Windows CE.NET 为多任务 (Multi-tasking) 操作系统, 以上介绍的所有编程方式所产出的各种应用程序皆可同时在同一台 Wincon-8000 上运行, 唯系统开发者必须自己调合各个装置和资源的共享性及整体运行时的效率 本书稍后之章节即将带领使用者, 针对使用 WinconSDK 撰写 EVC++ 应用程序的编程方式做介绍, 因本书篇幅有限, 将无法详尽介绍其它编程方式及软件支持, 读者有兴趣可自泓格科技网站 ( 下载相关资料和下载最新版的相关软件

211 7-5 应用实例 (Application story) WinCon-8000 控制系统架构 Web-based Control RS-232/485 M-Link Ethernet TCP/IP Third Parties HMI Ethernet/RS-232/485 Modbus RTU Built-in I/Os PLC Modbus TCP RS-232/485 M-Link RS 232/485 I-7000/I-8000 SoftPLC inside I-7000/I-8000 I/Os 如上图所示, WinCon-8000 控制系统主要由下列几个部分所组成 : 主控制器层 : 用来存放应用软件的 128MB Compact Flash 记忆卡, 在外围的支持方面则提供了与 PC 相同的键盘, 鼠标接口, 以太网接口, USB 接口, 及标准 VGA 接口 至于操作系统部份则采用微软公司具强实时 (Hard Real-Time) 性的 Windows CE.NET 除了窗口操作画面外, 它也配置了嵌入式浏览器, Web 和 FTP 服务器等解决了 Web-based Control 的难题 网络通信层 : WinCon-8000 控制器上层采用以太网 (Ethernet) 总线, 透过标准的 TCP/IP 协议, 可以连接上 Intranet/Internet, 而透过标准的 Modbus/TCP 协议, 则可以与 SCADA 软件及支持 Modbus 的装置沟通. 至于下层的网络, 标准 RS-232/485 序列串口, 可以与泓格的远程模块 I-7000 紧凑式控制器 I-8000 以及其它支持标准 RS-232/485 序列串口的装置及机器通讯 I/O 层 : 采用与原有 I-8000 相同的 I/O 模块, 提供了模拟量输出 / 入, 数字量输出 / 入, 开关量输出, 热电阻 (RTD)/ 热电偶 (Thermal Couple) 输入, 应变力 (Strain Gauge) 输入, 计数器 / 定时器 (Counter/Timer) 以及 RS-232 多串口等模块, 在扩充性上, 可以透过 87Kx(RS-485)

212 与 87Kx(Ethernet) 扩充槽来连接 87K 模块. 操作接口层 : 为让用户有不同的选择, 我们将嵌入式 HMI 软件整合到控制器上, 透过标准的 VGA 接口, 您可以连接一般屏幕与触摸屏, 不需要再透过 RS-232 外接传统的 HMI, 大大的节省了建置成本. 如要与上位机的图控软件做资料交换, 则可以透过 OPC 的标准来达成. 信息系统层 : 如何将工厂的生产流程与企业资源管理 (ERP) 整合在一起, 一直是一个非常重要的课题, WinCon-8000 内置了微软公司的 SQL Server CE 2.0, 大大的提升在本机中处理数据库的能力, 至于在如何同时解决远距与实时性这两个问题上,Wincon-8000 使用了全球实时大量讯息领导厂商艾扬科技 ( 的远距实时讯息平台 ipush Server Embedded Edition( 简称 ipush Embedded), 将实时讯息主动交换的能力整合进 WinCon-8000, 使其具有新世代工业控制器必备的网络实时讯息基础架构 透过 ipush Embedded,WinCon-8000 拥有主动推送 (Active Push) 资料的能力 无论是在局域网络 (LAN), 还是在远距网络 (Internet / VPN / Wireless LAN / GPRS / ADSL) 的环境下, 只要是透过 TCP/IP 通讯协议,WinCon-8000 就可以源源不绝地把所采集到的资料 包括监测数据 (Data) 异常警讯(Alarm) 与设备状态(Status), 实时主动推播到远程的监控软件, 或是送至监控中心, 而达到分散测集 中央实时监控的目的 这对于一些远距监控的应用如远程无人机房来说, 可说是一大突破, 亦是工业控制产业必走之路 另外, 藉由 WinCon8000 上的 ipush Embedded, 可以与艾扬科技所提供的中心端 ICE ipush Communication Server 相串联, 让使用者可以非常轻易地进阶架构出一满足远距 / 实时 / 多点的监控解决方案, 打造最完美的工业控制讯息供应链 下图即为 Wincon-8000 与 ipush Embedded 及 ICE ipush Communication Server 完美结合应用示意图 :

213 PDA Java Phone Excel Browser TCP/IP Networking ( Internet / VPN / Wireless LAN / GPRS ) PLC Ethernet RS 232/485 RS 232/485 ipush Server Embedded A Remote Real-time Push Engine RS 232/485 i7000/i8000 I/O 以下详列 WinCon-8000 控制系统实际应用在各个领域的系统结构及应用方式 : 1. 单一或远程控制盘 : 1.1 应用说明 : 一般控制盘都是将现场信号配线至端子盘后, 接至 PLC 的 I/O Module 内, 设计 Ladder Diagram 程序做控制, 再通信连接至 PC 的 SCADA 系统或者专用的监控系统接口设备, 做各种人机画面操作 如今可以将 Ladder Diagram 以及 SCADA 监控系统设计于同一台 WinCON 8000 内 而且还有先进的 Web Internet 远程监控功能, 再加上 ipush 主动将信号送至所指定的系统上 1.2 主要功能 : 一台设备即可组合成 IPC+PLC 相同的功能 远程监控功能, 尤其适合于该控制盘配置于人员无法随时管理的地方, 透过此功能达到遥控目的 网络配置只要利用现有电讯设备, 例如 :ADSL 等即可 1.3 系统组成 :Soft PLC SCADA ipush 等系统 2. 配方机器控制 : 2.1 应用说明 : 有许多生产设备需要配方表的控制, 例如 : 塑料成型设备 预拌混凝土生产系统 饲料生产设备 等等

214 依据配方会连动不一样的 I/O 控制 而且以网络与中控室主计算机系统联机视需要随时更新配方规格 目前系统大都使用 PLC 再加 PC 或者监控触控屏幕, 采单机作业, 无法网络整合 2.2 主要功能 : 一台设备即可组合成 IPC+PLC 相同的功能 SQL Server 储存配方规格及操作步骤程序等, 提供最人性化的接口操作 与中控系统联网随时做资料交换及更新 2.3 系统组成 :Soft PLC SCADA SQL Server 等系统 3. 居家保全控制主机 : 3.1 应用说明 : 以往保全系统是一个专属的封闭系统, 除了居家的门窗 火警 门禁等信号外, 都使用电讯专线以达到联机通信的功能 所以保全系统较难扩展至广泛应用领域 使用 WinCON 8000 可以设计成保全主控机, 搭配现有电讯网络系统, 就是保全系统 社区式的保全系统, 将很容易构成, 甚至老人居家服务系统都可以实践 3.2 主要功能 : 使用标准 PLC I/O 信号连接门窗 火警 门禁等信号点, 排除目前保全系统特殊的规格限制 标准电讯网络系统, 马上联网上线, 还有 Web Internet 功能, 屋主随时随地都可以上网查知目前状况 有警报可以透过手机 简讯 等各种方式, 实时通知相关人员 可以发展成标准化产品, 实践客户自行安装使用的可能性 3.3 系统组成 :Soft PLC SCADA SQL Server ipush 等系统 如果再加上开发成保全套装系统, 客户透过少许的设定就可以使用 4. 运输卡车控制器 : 4.1 应用说明 : 有许多大宗物品 ( 例如 : 水泥 谷物 石油 石化产品 砂石 预拌混凝土 ) 装载 运送 卸货时都需要做管理 卡车行进时, 还需要纪录里程数 油料管理 目前位置 纪录及预估到达及回厂时间等资料 此大宗物质需要特殊的仓储设备, 提发货时需要过磅作业 目前尚无完美的运转数据控制器, 但是 WinCON 8000 可以做到 4.2 主要功能 : 连接 GPS 卫星定位系统, 透过无线网络随时发出卡车位置, 行控中心实时掌控各卡车目前位置, 预测到达时间, 作为调度车辆重要参考 以 I/O 信号连接哩程 油料等信号, 纪录每趟实际数, 取代现有的纪录纸设备 依据实际值可以统计出各种有用信息, 例如 : 行车路线改进 卡车运转状态 司机操作管理 等等

215 SQL Server 也可以记录载运货品的送货 订货 提货 等各式资料 至货物仓储设备提卸货时, 可以与其控制设备联机, 做自动控制操作, 目前此方面国外已经有人做到无人化的管理 4.3 系统组成 :Soft PLC SCADA ipush SQL Server 外加 GPS 设备及无线上网等 5. 多种通信规约转换设备 : 5.1 应用说明 : 各种控制器的通信规约都不相同, 如何将两者资料转换通信就是一个大问题 使用 WinCE 基本系统,COM Port Ethernet Multi-Task 等再加上开发相关程序, 就可以变成一个 Multi-Gateway 新的产品 5.2 主要功能 : 通信规约的转换 5.3 系统组成 :Visual C# Embedded Visual C++ 等开发工具, 再使用 WinCE 系统 API, 依据不同通信规约开发相关程序 6. 远程警报传送设备 : 6.1 应用说明 : 环保监测器 大型管线监测站 自来水加压站 河流水位监测 气象监测站 电讯基地台 等等使用情形, 都是单独将监控设备置于户外或者无人操作的场合 此应用最需要的两个功能 : 一为有必要时上网查看, 另一为警报发生时主动通报 以目前 PLC 的做法, 无法单独完成, 都必须再配置一台 PC 才能完成上述的两个功能 6.2 主要功能 : 一台设备即可组合成 IPC+PLC 相同的功能 信号监视 纪录 警报处理等 Web Internet 画面查询, 警报时透过网络主动通知 可以连接门禁设备 刷卡管制等相关设备 6.3 系统组成 :Soft PLC SCADA ipush 等 再加上 Visual C# Visual C++ 开发相关特殊功能 7. MES 系统区域控制器 : 7.1 应用说明 : MES 缩写为 Manufactory Execution System, 此为生产信息管理系统 与生产线的控制台 设备信号 刷卡等各种实时信息相连接 需要下载生产工单 配方 产品规格 原物料补充 生产操作程序 等等生产命令 也要上传实际生产数量 机台运转状态 产品品质 原物料情形 等等各种信息 必须有设备负责此方面资料联机 资料暂存 设备间接口通信等功能 目前大都以 PC 再加 PLC 做此工作,WinCE 将来可能变成此种整合工作的标准产品 7.2 主要功能 : 生产控制台与中控系统的通信 Gateway

216 SQL Server 储存生产相关数据 监控画面显示生产状况及操作画面 I/O 信号与相关设备连接做必要的控制 7.3 系统组成 :Soft PLC SCADA ipush SQL Server 等 再加上 Visual C# Visual C++ 开发相关特殊功能 8. 仓储检料系统 : 8.1 应用说明 : 电子产品 化妆品 药品 食品饮料 等等具有各种型号及件数计算的产品发货时, 会面临每一种发货单都是不一样的型号及数量 所以仓储人员检料配货常常出错 目前检料系统大都以灯号 LED 显示等方式提醒操作人员 WinCON 8000 可以提供此功能还可以再加上许多新功能, 防止出错 8.2 主要功能 : 灯号及仓储设备开关都可以由 I/O 信号或者通信联机控制 CRT 画面显示取代原有 LED, 可以得到更多的信息 例如 : 出货单资料 产品规格及影像等 检料完成马上于画面操作, 实时更新数据库 8.3 系统组成 :Soft PLC SCADA SQL Server 等

217 第八章激活及设定 WinCon 一般设定 设定机器名称从开始 (Star)-> 设定 (Settings)-> 控制台 (Control Panel)-> 系统 (System) 图 8-1 机器名称 (Device Name); 直接输入即可 ->OK 即可设定完成 ( 参考图 8-1) 网络设定 从开始 (Star)-> 设定 (Settings)-> 网络及拨号联机 (Network and dial-up Connections)-> 双击 (LAN90001)

218 图 8-2 第一个卷标为 IP 设定, 有两种选择, 如果网络中有 DHCP Server 而且使用者不必指定其为固定 IP 用此选项即可, 其它的完全由 Server 指定即可, 如果使用者想设为固定 IP, 请与系统人员取得相关资料, 填入即可 ( 参考图 8-2) 图 8-3 第二个卷标 DNS( 上网际网络用 ) 及 WINS 设定, 如果使用者需设定本数据, 请 直接输入相关 IP 位置 ( 参考图 8-3)

219 其它设定 从开始 (Star)-> 设定 (Settings)-> 工作列即开始菜单 (Taskbar and Start Menu..) 图 8-4 第一个卷标有三项设定功能, 打勾为有效, 没打勾为无效 最上层显示 (Always on top): 工作列, 永远在最上层 自动隐藏 (Auto hide): 工作列, 自动隐藏 显示时钟 (Show Clock): 工作列中, 显示现在时间 ( 参考图 8-4) 8-2 特殊设定 (WinCon Utility) 贮存设定 如图 8-5 提供存盘 / 检视系统注册码的信息和设定 HTTP/FTP 目录路径功能. 当你更换任何系统信息及注册码, 一定要执行储存的动作, 以确保资料永久储存至 flash 内存中. 请用鼠标按 存盘和重新激活 按钮更新系系统设定资料. 假如你不将目前的规划设定存盘至 flash 内存中, 重新激活后你将回复到 Wincon 8000 先前所作的系统设定状态. 备注 :: 当按 存盘和重新激活 按钮后它会耗用 个秒存盘, 假如在更新这注册码信息到闪存期间, 我们按了 reset 或关闭 WinCon 8000 的电源这操作系统在闪存中的映像档将会损毁, 因为它是很重要的!, 请把这些重要信息加入你的用户手册中

220 图 8-5 变更显示器分辨率 设定 VGA 分辨率 (Resolution): 前提供的分辨率有 320x240,640x480,800x600,1024x768 四种可供使用者选择, 色彩 (Bpp) 设定显示颜色 bit 数, 目前提供 2,4,8,16 bit 颜色分辨率供使用者选择, 屏幕更新率 (Frequncy) 设定 VGA 输出之更新频率, 一般 TFT LCD 设定为 60 Hz( 参考图 8-5) 变更预设目录更换 FTP 预设目录 : 此处可变更 FTP 服务器预设资料夹路径, 请先在文字框中输入资料夹路径, 在按 change 按键, 变更之更换 HTTP 预设目录 : 此处可变更 HTTP 服务器预设资料夹路径, 请先在文字框中输入资料夹路径, 在按 change 按键, 变更之 ( 参考图 8-5)

221 系统信息 如图 8-6 允许你到检视 Wincon 嵌入的控制器系统, 目前之系统硬件 信息, 及设定 MAC 地址 图 8-6 这卷标包括下列的活页夹 : 序号 (Serial Number): 这区域显示 Wincon 的产品硬件序号 (64 bit), 使用者可以利用此序号, 来作版权加密金钥, 以保护软件著作权 网络硬件地址 (MAC address): 这区域显示当前的网络硬件地址, 如有网络硬件地址冲突时你可以更换这网络硬件地址, 如无, 建议使用者不要随意更改以免发生不可预测之结果 EEPROM 容量 (EEPROM Size): 这区域显示 Wincon 8000 EEPROM 的容量 闪存容量 (Flash Memory Size): 这区域显示 Wincon 闪存的容量

222 OS 版本 (OS Version): 这区域显示当前的 WIN CE 操作系统版本 OS 映像档大小 (OS Image Size): 这区域显示尺寸的当前的操作系统 WinCon SDK 版本 (WinCon SDK Version): 这区域显示当前的 WinconSDK_DLL 的版本 设定启机自动运行应用程序 如图 8-7 提供 10 个执行档案, 当 WinCon 系统激活后这 10 个程序会 自动执行 你可以利用流览 (Brower) 按钮选择 0~10 个执行档案, 显示在在下面图 片 他们被执行的顺序 : 依照程序 1, 程序 2,... 顺序同时激活执行 图 8-7 此卷标包含下列项目 : 程序 1~10(Program 1~10): 显示目前 10 个程序 你可以利用流览 (Brower) 按钮选择 0~10 个执行档案, 因为激活后自动执行, 因此要确认这些执行档在开机后即会存在, 如系统提供之程序或将使用者设计之程序, 置

223 于 Compact Flash 中 储存按钮 (Save Setting): 如果你有对上述 10 数据作变更, 你必需按此按 钮储存你所变更之数据, 在下次开机时才会生效并自动自动执行 设定串行式触摸屏幕如图 8-8 Wincon-8000 有内建提供序列 Port 触控屏幕设定通讯 Com Port No, 目前有提供支持厂商有 ELO,Dynapro,EGALAX., 请依使用厂牌及插入位置选用正确 Com Port No 图 系统更新 OS 映像档更新 如图 8-9 提供操作系统更新功能, 用户能从泓格科技网址 : 下载开机系统映像文件, 并先将映像档储存在 Compact

224 Flash 中, 或利用行动碟 (PAN Driver) 插到 USB 中, 目前有提供的语言版本有, 繁体中文版, 简体中文版, 英文版三种版本, 请按流览 (Brower) 按钮选择档案, 再按写入 (Write to Flash now) 更新目前的开机系统版本, 它会作业 10~15 分钟确认档案, 再来几分钟更新你的开机系统到闪存, 而且重新激活你的系统, 即完成更新动作. 请注意 : 这动作需要一些时间, 为确保更新顺利, 请小心电源, 不要有断电之 危险, 最好能准备 UPS 不断电系统, 以免更新失败, 而造成系统损毁无法开机. 图 8-9 CF 卡资料更新请至网址 ftp://ftp.icpdas.com/pub/upload/wincon8000/release/ 下载整个 Compact Flash 资料夹, 复写原 CF 卡中之所有资料即可作 CF 卡之更新动作 WinCon SDK 开发程序馆更新 请至网址 ftp://ftp.icpdas.com/pub/upload/wincon8000/release/

225 下载 setup.exe 档案, 并在次安装 WinCON SDK 复写原所有资料即可作 WinCON SDK 之更新动作, 并将 WinCON.DLL 复制到 WinCON 中应用程序相关位置即可

226 第九章开发 Wincon-8000 应用程序 9-1 安装 WinCon SDK 至 PC 开发环境 Wincon -8000SDK 包括下列的主要的项目. - WinconSDK 函数库文档 - WinconSDK 包含文档 - Wincon.DLL 文档 - 范例程序在安装 Wincon -8000SDK 软件程序的前, 你必须先安装微软开发软件, 如果你是 用免费的 Embedded Visual C 可以在微软的网站直接下载, 安装, 如果你是要以 VB.NET 及 C# 开发软件必须先安装 Visual Studio.NET 2003 软件程序, 假如你还没有 安装微软 Visual Studio.NET 2003, 请洽微软购买软件, 及协助你安装这软件包, 的后 请你按下面步骤到安装这套由泓格科技 ICP DAS 研发, 容易上手的 Wincon 嵌入的控制器 工作平台开发工具. 按部就班的安装的 WinconSDK 1. 请插入光盘片进入你的光驱. 2. 然后执行光驱中 \napdos\wincon8000\setup.exe 妳将会看到下面的安装画 面参考 ( 参考图 9-1) 然后按 Next

227 图 选择 Wincon 8000 SDK 安装路径, 默认值路径是 C:\DAQPro\, 参考 ( 参考图 9-2) 参考图 请按 Next 按钮到安装软件,( 参考图 9-3)

228 图 顺利地完成安装软件的后, 请按下 Finish 按钮来完成安装开发工具的安 装程序, ( 参考图 9-4) 图 开启 C:\DAQPro\Wincon 文件夹. 参考图 9-5, 这样就完成安装程序, 你可

229 以开始软件设计开发的工作了. 图 开发第一个 Embedded Visual C++ 应用程序 如何建立一个应用程序的步骤如下 ; 每一步更多详细的信息, 将在后面的章节叙述 1. 建立一个基本窗体的应用程序 2. 指定范例程序的位置 3. 配置编译器选项 4. 设计一个应用程序 5. 建构应用程序 6. 在 Wincon-8000 平台执行应用程序 建立一个以窗体为基础的应用程序一个窗体对话框与控件, 让使用者存取并尽可能更改资料, 你可以开发应用程序, 从使用者在窗体的选项中挑选 通常一个窗体基础 (forms-based) 的应用程序, 允许使用者在 File 选单中选择 New 去存取窗体 而对话框基础 (dialog-based) 的应用程序, 有提供下拉式选单方式, 也有提供窗体基础方式的应用程序

230 使用者能运用 WCE MFC AppWizard (exe) 去建立一个以窗体为基础的应用程序 当创建新窗体时, 他们能选择新建单一文件接口或建立以对话框为基础的应用程序 单一文件接口 Single Document Interface (SDI), 窗体应用程序在同一时间, 只允许一个窗体在执行 然而, 它是可以同时从 SDI 执行不同的窗体, 窗体应用程序在文档菜单, 从选择 New 指令建立新窗体 单一文件接口 (SDI), 窗体应用程序同一时间只允许一个窗体在执行 然而, 它是可以同时从 SDI 执行不同的窗体, 窗体应用程序在文档菜单从选择 New 指令建立新窗体 对话框应用程序是根据窗体应用程序定义 然而, 假如你选择这个选项, 你将不能使用 New 指令去建立新窗体, 来加入应用程序 对话框应用程序, 不用文件 / 检视结构 那么, 这时候你需要到增加新窗体, 自行建立和存取 向导最后的步骤是检视和更改刚刚建立的类别设定 为了单一或多重的文件接口应用程序, 你能设定 CformView, 为了你的应用程序的基本类别 假如你的使用者应用数据库, 也可以选择其它的类别, 从 CformView 中获得 窗体是任何窗口的来源, 可以从 CformView 或从 CformView 产生任何类别 展示新建立窗体应用程序 1. 首先, 用户需要打开 EVC++ 软件 然后, 请按鼠标键 File -> New 来开启新程序 2. 在 Projects 卷标, 选择 WCE MFC AppWizard (exe) 并且设定项目名称为 Demo 并在 Location 填入它的文档路径 然后, 在中央处理器列表框中选择 Win 32[WCE ARMV4], 如果必要的话也一起勾选其它的选项 最后按鼠标键 OK 开始使用, 如显示在下列图片的向导程序 3. 规划 WCE MFC AppWizard

231 Fig (3a) WCE MFC AppWizard: 步骤 1/4: 设定 Dialog based 同样地显示在下 列的图片 按鼠标键 Next 跳到下列的步骤 Fig 9-2-2

232 (3b) WCE MFC AppWizard- 步骤 2/4: 请输入标题在你定义的对话框, 并按鼠标键 Next 跳到下列的步骤 ( 看图片 9-2-3) Fig (3c) WCE MFC AppWizard- 步骤 3/4: 在这步骤中, 请选择 Yes,please 来产生源文件的注释 ; 并且设定 MFC 链接库, 当做一个分享的动态或静态的链接库 然后按鼠标键 Next 跳至下一步骤 Fig (3d) WCE MFC AppWizard- 第 4 /4 步骤 ; 按鼠标键 Finish 按钮开始让向导 建立新的上层类别, 请参考下面的图片 ( 看图片 9-2-5)

233 Fig 说明范例文档放置的位置 目录设置选项是为了说明并找寻你的 embedded Visual C++ 项目路径 这些包含 下列型态的文档路径 : 文档型态可执行文档含括文档链接库文档源文件 路径属性 建立工具菜单位置, 诸如编译器 (CL, CLARM, CLMIPS, 等等 ), NMAKE, LINK, 和 BSCMAKE 编译器会寻找 include 档的地方 ( 譬如 #include <stdio.h>) 联结器会寻找外部 链接库文档 的地方 说明除错程序会寻找默认值源文件的地方, 诸如 MFC 链接库和微软 run-time 链接库 贮藏在文件夹中设置的路径信息, 它允许我们去自行规划 你能变更他们的路径和 embedded VisualC++ 的优先搜寻路径顺序

234 针对你安装的 embedded Visual C++, 它支持不同的设备和不同的工作平台 ( 诸如 WCE 仿真器 ), 分别去设定他们的路径 设定范例程序的默认值文件夹 1. 在 Tools 工能表中开启 Options 对话框 2. 选 Directories 卷标, 在 Platform 项目选择 STANDARDSDK, 在 CPUs 项目选择 Win32 [WCE ARMV4] 和在 include files 项目选择 Show directories 3. 增加含括文件的路径, 在 Directories 下方空行处 ( 指出附近空的长方形 ) 双击 - 按鼠标键, 如显示在下列的图片 ; 请输入包括用户安装驱动程序的文档路径 4. 在 Show directories 项目中选择 Library files Fig 增加链接库的文档路径, 在 Directories 下方空行处 ( 指出附近空的长方形 ) 双击 - 按鼠标键, 输入包括含有用户安装链接库的文档路径, 显示在下列的图片

235 Fig 备注 :: 假如你想要删除文件夹, 请选择它并且按 DELETE 键即可 设定编译程序的选项 设定编译程序在开发环境中的选项, 请跟随这些步骤 : 1. 在 Project 选单, 选择 Settings 来开启 Project Setting 对话框 2. 在 Settings for 项目, 选择 Win32 [WCE ARMV4] Release 如显示在下列的图片

236 Fig 在项目设置对话框中, 选择 Link 卷标及在 Object/library modules 项目中, 设定链接库文件名称为 WinconSDK.lib Fig 9-2-9

237 设计一应用程序 当你建立一基础项目后, 你可以设计自己的使用者接口 这些接口能包含第 1 个构思并开设对话栏 菜单 工具栏 快速装置, 和其它可视化及交互式对象 然后你可以设计相关的程序代码 由于在尺寸和对象造形上的差异, 用户接口基础依设计要求, 去修改每个对象的尺寸及位置 在下列我们会以流览方式, 说明如何在用户的程序中, 创建一个新的控件 建立新编辑框 : 1. 在 Workspace 窗口中, 选择 ResourceView 卷标 选择 Dialog 夹展开, 并双击 IDD_DEMO_DIALOG 来开启如下的对话框 Fig 选择图标建立一个静态的卷标 3. 在卷标上按鼠标右键选择属性 Properties 4. 在卷标属性对话框中, 到 Caption 位置中输入 Input DO Value

238 Fig 选择图标建立一个编辑文字框 6. 在卷标上按鼠标右键选择 Class Wizard 7. 当 MFC Class-Wizard 对话框显示出来时, 请选择 Member Variables 卷标 Fig 选择 Add Variable 按钮新增一个数值变量

239 Fig 在 Member variable name 位置中填入 m_do, 及在 Category: 下拉式选单中, 选择 Control 及 Variable type: 设为 CEdit 当你完成上述步骤后, 按 OK 来储存我们所作的设定资料, 并离开这个窗体对话框 建立新按钮 : 1. 选择图标建立一个新按钮 2. 在按钮上按鼠标右键选择属性 Properties 3. 当 Push Button Properties 对话框显示出来后, 到 Caption 位置键入 Digital Output ( 看图片 ), 然后关闭 Fig 双击按钮对象, 你能编辑按钮的事件码 然后键入 OnBtnDO 进入 Member function name: 位置 ( 看图片 ), 再按鼠标 OK 按钮, 回到显示编辑器窗口

240 Fig 在编辑器窗口中, 键入程序代码如下 ; Fig 按下 CTRL+Home 移动你的光标到上面, 并在声明区域输入 #include WinconSDK.h Fig 建立应用程序 微软 embedded Visual C++ 提供两种方法去编译建立应用程序 第 1 种也是最普 遍的方法, 是在 embedded Visual C++ 的开发环境中建立应用程序 第 2 种方法, 是

241 在 MS-DOS 提示符号的操作环境下, 使用指令建立应用程序 程序去建构应用程序, 包含若干细节, 即前处理器 编译器和连接器 他们的主要的函数叙述如下 ; 前处理器为了编译器准备源文件 翻译宏 运算符和命令 编译器新建目标文档包含机器码 链接器指引 区段 外部参考及功能 / 资料名称 连接器结合编译器产生机器码和静态 - 连接的链接库, 再加上解译名称的参考, 同时建立可执行文件 建构程序 ; 下列的图, 表示从你的程序代码编辑器, 到使用 embedded Visual C++ 的 建构程序成员 Fig 假如你在 IDE 外建立你的程序, 你可以使用 makefile 指令工具 Microsofte embedded VisualC++ 为了处理 makefiles 而提供 NMAKE 公用程序 假如你在 IDE 建立你的程序,eMbedded Visual C++ 系统会使用这个项目 (.vcp) 文档储存做为信息 这.vcp 文档是不能和 NMAKE 兼容的, 然而, 如果你的程序宁愿使用 makefile.vcp 文档, 在开发环境中将当做外部的项目, 但是你能仍然可以建立它 建立范例应用程序 1. 在 Build 选单中选择 Build Demo 2. 假如你已完成上面的步骤, 你将在输出的窗口中获得下列的讯息, 那意味着执行应用程序已被建立了 不然的话, 你将获得错误的讯息 那么你必需更正原

242 始程序, 并再次重建应用程序直到无错误为止 Fig 假如在那里没有产生错误的信息, 你可以在 C:\Project\Demo \ARMV4Rel\ 发 现 Demo.exe 文档 在 Wincon 工作平台上执行应用程序 当你已完成 Demo.exe 文档时, 你必需将应用开发程序 (Demo.exe) 复制到 Wincon 控制器中执行 例如, 你能使用轻便的储存媒体 ( 象 U 盘 ) 存储文档并拷贝到 Wincon 8000 机器中 在下列的章节, 我们会开发一些方法, 利用 Ethernet (TCP/IP) 或 RS -232 系列的协议, 下载应用程序 (Demo.exe) 到主控制器 请按下面步骤在 WinCon 执行使用者程序 1. 从 C:\Project\Demo\ARMV4Rel\ subfolder 复制 Demo.exe 到 Wincon Wincon-8000 双击 Demo.exe 执行, 结果显示在下列的图片 Fig 输入 3 到 Input DO Value 位置 ( 如上图 ), 再按下 Digital

243 Output 按钮 然后, 你可以看见在 Wincon 8000 Slot 1 插槽的数字输 出模块 0 和 1 的输出为 High 9-3 开发第一个 Visual Basic.NET 应用程序 建立 Visual Basic.NET 新项目 让我们带领读者开始建立我们第一个 Visual Basic.NET 应用程序项目, 首先请确认在 PC 上须先安装 Visual Studio.NET 2003 版本的开发软件, 及智能型应用延伸套件, 还有 WinCON SDK, 如尚未安装, 请参考先前的安装章节, 先行准备 确认完成后以下我们将一步一步来完成我们的第一个应用程序 1. 首先先将 Microsoft Visual Studio.NET 软件开起来, 在 File 的选择 New, New Project 出现对话框 2. 新增一个项目, 选择开发工具, Project Type 选择 "Visual Basic Projects", 并在又边选 "Smart Device Application" 如图 (9-3-1) 图 可以依你的需求, 变更你要建立项目的路径 c:\project 4. 给一个应用程序名称 Demo, 按 OK 即可

244 5. 的后智能型应用程序向导将出现 ( 请看图 9-3-2) 在此你必须选择 Windows CE 或 Pocket PC 型态的应用程序, 我们选择 Windows CE, 及其下的 Windows Application 型态项目 图 按 OK 即可, 等一会向导即位我们建立一个开发项目的样板 7. Visual Basic.NET( 项目的样板如图 )

245 图 增加应用程序参考到我们的应用程序 在使用 Wcon 这关键词的前我们必须先将 WinCON.DLL 加入成为我们的参考链接库, 请按以下的步骤来加入参考 1. 在 Project Add Reference 选择 "Add Reference" 出现对话框 ( 请看图 9-3-4).

246 图 选择 ICP DAS Wcon Inside I-8000/I-87K series modules for Wincon-8000 再按 Select 备注 : 如你找不到 ICP DAS Wcon Inside I-8000/I-87K series modules for Wincon-8000 请按 Browse 并在 C:\DAQPro\Wincon\ 中选 Wincon.DLL 即可 3. 你还要加选 mscorlib 文档

247 4. 按 OK 增加参考 图 备注 : 如果出现 The reference Wincon.dll may or may not be valid for the active platform. Add references with care to ensure your application will run correctly 这对话框, 请按 OK 来关闭它 ( 请看图 9-3-6) 图 当你新增完 mscorlib 及 Wincon.dll 到你的应用程序后, 请开起项 目参考内容以确定有参考到 ( 请看图 9-3-7)

248 图 设计应用程序 在 Visual Basic.NET 开发环境, 可以注意到在 Smart Device Extension 开发环境工具列中的对象, 与一般 Windows 应用程序有一些不同, 包括其对象变少, 对象属性也相对少了许多, 这一切都是为精简以适用到我们资源有限的 Windows CE.NET 系统所设计 因此如你在一般 Windows 应用程序开发已有相当的经验, 一般而言应是没什么问题, 只是要注意一些细节限制即可应用自如 现在让我们开始设计一小应用程序下一步骤 1. 在 Form1 上按鼠标右键, 在选单中选择 View Code ( 请看图 9-3-8) 图 将光标移到程序代码前端按开发工具分别说明如下 : 在 VB.NET 开发工具, 在第一行插入 Imports Wincon 如图 9-3-9

249 图 a 3. 在 Form1.vb [Design]* 卷标按一下鼠标, 切换到画面设计窗口 4. 在 Toolbox 中选图标建立一文字卷标 5. 在其属性窗口 ( Properties ) 中的 Text 位置输入 Input DO Value 图 在 Toolbox 中选图标建立一文字框对象 7. 在其属性窗口 ( Properties ) 中的 Text 位置清除 TextBox1 8. 在 Toolbox 中选图标建立一按钮对象 9. 在其属性窗口 ( Properties ) 中的 Text 位置输入 Digital Output 10. 在双击 (Double-click) 按钮对象, 在程序窗口即会出现相对映的 事件 (event handler) 处理子程序, 并输入程序 :

250 Dim slot As Integer = 1 Dim data As Integer data = Val(TextBox1.Text) 图 插入新行并输入 Wcon., 如此即会弹出 WinCON-8000 所有支持 I8000 系列 的模块, 以引导使用者很容易作正确的引用 I8000 所提供的链接库 图

251 12. 选 I8064 后再按 TAB 键 13. 在 I8064 后按., 即会显示必须输入参数的引导信息如下图所示 图 选择 DO_8 再按 TAB 键, 然后输入 (slot, data) 参数 图 编译应用程序 完成上述步骤后我们开始编译应用程序的步骤如下 : 1. 在规划工具下拉选单中选 Release 图 在开发目标 (Deployment Device) 下拉选单中选 Windows CE.NET Device ( 请看图 )

252 图 在 Build 选单中选择 Build Demo 4. 如果你完成 1~4 步骤, 你可以在输出窗口中了解编译结果, 如无错误你可以看到如下图的讯息, 如有问题须要找出问题所在, 并解决所有问题才能完成应用程序 图 完成后你可以在 C:\project\Demo\Demo\bin\Release\ 找到 Demo.exe 执行文 件 在 Wincon-8000 平台执行应用程序 我们编译完成的执行文件, 必须复制到 WinCON-8000 系统中以实际执行的, 我们可以用 Ethernet(FTP) 或 RS-232 或直接用 U 盘将 Demo.exe 及 Wincon.DLL 复制到 WinCON-8000 中, 我们用最简单的方法是用 U 盘, 插入 WinCON-8000 USB 插槽, 并直接以文件管理器中开启如图

253 图 在 Input DO Value 中输入 3 ( 如上图 ), 再按 Digital Output 按钮, 你将会在 Wincon-8000 的第一个插槽 I8064 的数字输出模块第 0,1 看到输出 High( 亮灯 ) 读者想要进一步的传送及更方便的开发工具整合 ( 包括线上单步执行, 除错等 ) 环境, 在此不加以详述, 请找相关 ICPDAS 泓格科技的技术支持单位或微软技术支持单 位, 洽询详细内容 9-4 开发第一个 Visual C#.NET 应用程序 建立 Visual C#.NET 新项目 让我们带领读者开始建立我们第一个 Visual C#.NET 应用程序项目, 首先请确认在 PC 上须先安装 Visual Studio.NET 2003 版本的开发软件, 及智能型应用延伸套件, 还有 WinCON SDK, 如尚未安装, 请参考先前的安装章节, 先行准备 确认完成后以下我们将一步一步来完成我们的第一个应用程序 8. 首先先将 Microsoft Visual Studio.NET 软件开起来, 在 File 的选择

254 New, New Project 出现对话框, 新增一个项目, 选择开发工具, 在 Project Type 选择 "Visual C# Projects", 并在又边选 "Smart Device Application" 如图 (9-4-1) 图 可以按你的需求, 变更你要建立项目的路径 c:\project 10. 给一个应用程序名称 Demo, 按 OK 即可 11. 的后智能型应用程序向导将出现 ( 请看图 9-4-2) 在此你必须选择 Windows CE 或 Pocket PC 型态的应用程序, 我们选择 Windows CE, 及其下的 Windows Application 型态项目

255 图 按 OK 即可, 等一会向导即位我们建立一个开发项目的样板 13. Visual C#.NET 项目的样板如图 ) 图 增加应用程序参考到我们的应用程序 在使用 Wcon 这关键词的前我们必须先将 WinCON.DLL 加入成为我们的参考链接库, 请按以下的步骤来加入参考 6. 在 Project Add Reference 选择 "Add Reference" 出现对话框 ( 请看图 9-4-4).

256 图 选择 ICP DAS Wcon Inside I-8000/I-87K series modules for Wincon-8000 再按 Select 备注 : 如你找不到 ICP DAS Wcon Inside I-8000/I-87K series modules for Wincon-8000 请按 Browse 并在 C:\DAQPro\Wincon\ 中选 Wincon.DLL 即可 8. 你还要加选, MSCorLib 文档

257 9. 按 OK 增加参考 图 备注 : 如果出现 The reference Wincon.dll may or may not be valid for the active platform. Add references with care to ensure your application will run correctly 这对话框, 请按 OK 来关闭它 ( 请看图 9-4-6) 图 当你新增完 mscorlib 及 Wincon.dll 到你的应用程序后, 请开起项 目参考内容以确定有参考到 ( 请看图 )

258 图 设计应用程序 在 Visual C#.NET 中其开发环境, 可以注意到在 Smart Device Extension 开发环境工具列中的对象, 与一般 Windows 应用程序有一些不同, 包括其对象变少, 对象属性也相对少了许多, 这一切都是为精简以适用到我们资源有限的 Windows CE.NET 系统所设计 因此如你在一般 Windows 应用程序开发已有相当的经验, 一般而言应是没什么问题, 只是要注意一些细节限制即可应用自如 现在让我们开始设计一小应用程序下一步骤 15. 在 Form1 上按鼠标右键, 在选单中选择 View Code ( 请看图 9-4-8) 图 将光标移到程序代码前端在 using System.Data; 后插入 Using Wincon; 如图 9-4-9

259 图 在 Form1.cs [Design]* 卷标按一下鼠标, 切换到画面设计窗口 17. 在 Toolbox 中选图标建立一文字卷标 18. 在其属性窗口 ( Properties ) 中的 Text 位置输入 Input DO Value 图 在 Toolbox 中选图标建立一文字框对象 20. 在其属性窗口 ( Properties ) 中的 Text 位置清除 TextBox1 21. 在 Toolbox 中选图标建立一按钮对象 22. 在其属性窗口 ( Properties ) 中的 Text 位置输入 Digital Output 23. 在双击 (Double-click) 按钮对象, 在程序窗口即会出现相对映的 事件 (event handler) 处理子程序, 并输入程序 :

260 int slot=1; byte data; data=convert.tobyte(textbox1.text); 图 b 24. 插入新行并输入 Wcon., 如此即会弹出 WinCON-8000 所有支持 I8000 系列 的模块, 以引导使用者很容易作正确的引用 I8000 所提供的链接库 25. 选 I8064 后再按 TAB 键 图 在 I8064 后按., 即会显示必须输入参数的引导信息如下图所示

261 图 选择 DO_8 再按 TAB 键, 然后输入 (slot, data) 参数 图 编译应用程序 完成上述步骤后我们开始编译应用程序的步骤如下 : 6. 在规划工具下拉选单中选 Release 图 在开发目标 (Deployment Device) 下拉选单中选 Windows CE.NET Device ( 请看图 )

262 图 在 Build 选单中选择 Build Demo 9. 如果你完成 1~4 步骤, 你可以在输出窗口中了解编译结果, 如无错误你可以看到如下图的讯息, 如有问题须要找出问题所在, 并解决所有问题才能完成应用程序 图 完成后你可以在 C:\Project\Demo\ARMV4Rel\ 找到 Demo.exe 执行文件 在 Wincon-8000 平台执行应用程序 我们编译完成的执行文件, 必须复制到 WinCON-8000 系统中以实际执行的, 我们可以用 Ethernet(FTP) 或 RS-232 或直接用 U 盘将 Demo.exe 及 Wincon.DLL 复制到 WinCON-8000 中, 我们用最简单的方法是用 U 盘, 插入 WinCON-8000 USB 插槽, 并直接以文件管理器中开启如图

263 图 在 Input DO Value 中输入 3 ( 如上图 ), 再按 Digital Output 按钮, 你将会在 Wincon-8000 的第一个插槽 I8064 的数字输出模块第 0,1 看到输出 High( 亮灯 ) 读者想要进一步的传送及更方便的开发工具整合 ( 包括线上单步执行, 除错等 ) 环境, 在此不加以详述, 请找相关 ICPDAS 泓格科技的技术支持单位或微软技术支持单 位, 洽询详细内容 9-5 WinconSDK 应用程序开发接口 (API) 链接库及使用手册 使用手册部分因资料繁多, 请读者直接参考 CD 中所附的 Software_Guide.pdf 文档, 其中有详尽的说明 APIs 链接库在此将其一般用途说明如下, 详细资料请参考 CD 中所附的 Software_Guide.pdf 文档 WinCON SDK 函数库介绍 在这章节我们会集中描述 WinCon DLL 的功能 WinconSDK.DLL 的功能主要分成 在下面的 10 个群组 : 系统信息函数 ;

264 软件信息函数 ; 数字输入 / 输出函数 ; 看门狗定时器函数 ; EEPROM 读写函数 ; 模拟输入函数 ; 模拟输出函数 ; 3- 轴编码器函数 ; 2- 轴步进 / 伺服函数 ; 计数器 / 频率函数 本章节将列出 Wincon 使用功能提供给使用者参考运用, 在这下面章节说明嵌入 (embedded) Visual C++ 每个功能的语法格式, 由于篇幅限制, 其它详细资料及范例应用和其它程序语法及 (Visual Visual VB.NET 和 C #.NET) 请参考详细电子文件的说明 然而, 为了简化和清楚做描述其输入 [nput] 和输出 [output] 参数的功能属性, 请参考下列的范例表格 关键词 使用者呼叫这功能的前需传入 呼叫这功能的后, 可获得传回资料? 设定参数? [input] 是 否 [output] 否 是 当使用嵌入的 VisualC++ 开发工具来设计应用程序时候, 你必须含括 WinconSDK.h 这个文档, 和编译连结应用程序时须用到 WinconSDK.lib 的链接库 应用于 WinCE 版本宣告含括连结 WinCon 及更新版 WinConSDK.h WinConSDK.h WinConSDK.lib

265 9-5-1 系统信息函数 ChangeSlotTo87K 这功能是为了将 Wincon 8000 背板插槽的模式切换到控制的 87K 系列模块, 序列总线 (COM1) 模式 譬如, 假如你想要从指定插槽发送或接收资料, 你需要先用此子程序这功能设定, 然后你才能正确使用其插槽功能 [C ++ ] void ChangeSlotTo87K(unsigned char slotno) GetModuleType 这功能是为了查询插在 Wincon 8000 背板指定插槽的模块种类 [C ++ ] int GetModuleType(int slot) GetNameOfModule 这功能是为了查询插在 Wincon 8000 背板指定插槽的模块名称 [C ++ ] int GetNameOfModule(int slot, char *string1) GetTimeTicks 这功能被使用查询, 自从 Window CE 激活到现在所经过的微秒数, 以 DWORD 值传回, 因此假如系统连续地开机 49 7 天时间会环绕到零 ( 归零 ) [C ++ ] DWORD GetTimeTicks(void) GetSlotAddr 这功能查询指定插槽的基底内存地址, 以 DWORD 值传回 [C ++ ] DWORD GetSlotAddr(int slot)

266 GetSystemSerialNumber 这功能查询 WinCon 主控制器的硬件序号 这功能读回 64-Bit 位串行芯片序号 [C ++] int GetSystemSerialNumber(char *string1)

267 9-5-2 软件信息函数 GetSDKversion 这功能查询 WinCon SDK 程序馆文档的版本编号 [C ++ ] void GetSDKversion(LPTSTR lpsdkversion) GetOSversion 这功能查询嵌入的开机系统的版本编号 [C ++ ] void GetOSversion(LPTSTR lposversion) 数字输入 / 输出函数 DO_8 这功能使用于输出 8- 字节资料到指定插槽的数字输出模块, 0~8- 字节的输出到 0~7 插槽的数字模块输出 [C ++ ] void DO_8(int slot, unsigned char cdata) 可使用模块 : i8060, i8064, i8065, i8066, i8068 DO_16 这功能使用于输出 16- 字节资料到指定插槽的数字输出模块, 0~16- 字节的输出到 0~7 插槽的数字模块输出 [C ++ ] void DO_16(int slot, unsigned int cdata) 可使用模块 :I8057, I8056

268 DO_32 这功能使用于输出 32- 位 (bit) 资料到指定插槽的数字输出模块, 0~31 位 (bit) 的资料输出到 0~7 插槽的数字模块 [C ++ ] void DO_32(int slot, unsigned long cdata) 可使用模块 : I8041 DO_8_BW 在 8- 频道 (bit) 数字输出系列模块, 指定插槽的指定频道 (bit) 输出的值为 High 或 Low 输出值是 true 或 false [C ++ ] void DO_8_BW(int slot, int channel, BOOL data) 可使用模块 : I8060, I8064,I8065, I8066, I8068 DO_16_BW 在 16- 频道 (bit) 数字输出系列模块, 指定插槽的指定频道 (bit) 输出的值为 High 或 Low 输出值是 true 或 false [C ++ ] void DO_16_BW(int slot, int channel, BOOL data) 可使用模块 : I8057; I8056 DO_32_BW 在 32- 频道 (bit) 数字输出系列模块, 指定插槽的指定频道 (bit) 输出的值为 High 或 Low 输出值是 true 或 false [C ++ ] void DO_32_BW(int slot, int channel, BOOL data) 可使用模块 : I8041

269 DI_8 一次从指定插槽的 8 字节的数字输入模块, 传回 8- 位 (bit) 组输入值 [C ++ ] unsigned char DI_8(int slot) 可使用模块 : I8052, I8058 DI_16 一次从指定插槽的 16 位 (bit) 的数字输入模块, 传回 16- 位 (bit) 组输入值 [C ++ ] unsigned int DI_16(int slot) 可使用模块 : I8051, I8053, I8056 DI_32 一次从指定插槽的 32 位 (bit) 的数字输入模块, 传回 32- 位 (bit) 输入值 [C ++ ] unsigned long DI_32(int slot) 可使用模块 : I8040 DI_8_BW 从指定插槽的 8 位 (bit) 的数字输入模块, 传回指定频道 (bit) 输入的值, 传回值是 true 或 false [C ++ ] BOOL DI_8_BW(int slot, int channel) 可使用模块 : I8052, I8058 DI_16_BW 从指定插槽的 16 位 (bit) 的数字输入模块, 传回指定频道 (bit) 输入的值, 传回值是 true 或 false

270 [C ++ ] BOOL DI_16_BW(int slot, int channel) 可使用模块 : i8051, i8053, i8056 DI_32_BW 从指定插槽的 32 位 (bit) 的数字输入模块, 传回指定频道 (bit) 输入的值, 传回值是 true 或 false [C ++ ] BOOL DI_32_BW(int slot, int channel) 可使用模块 : i8040 DIO_DO_8 这功能被使用输出 8- 字节资料到 DIO 模块 这些模块拥有 8 个数字输入和 8 个数字输出通路 8 位 (bit) 的输出数据, 将从指定的插槽输出到指定的 DIO 模块 [C ++ ] void DIO_DO_8(int slot, unsigned char data) 可使用模块 : i8054, i8055, i8063 DIO_DO_16 这功能被使用输出 16- 字节资料到 DIO 模块 这些模块拥有 16 个数字输入和 16 个数字输出通路 16 位 (bit) 的输出数据, 将从指定的插槽输出到指定的 DIO 模块 [C ++ ] void DIO_DO_16(int slot, unsigned int data) 可使用模块 : i8042,i8050 DIO_DO_8_BW 在 8- 频道 (bit) 数字输出 / 入系列模块, 指定插槽的指定频道 (bit) 输出的值为 High 或 Low 输出值是 true 或 false

271 [C ++ ] void DIO_DO_8_BW(int slot, int channel, BOOL data) 可使用模块 : i8054, i8055,i8063 DIO_DO_16_BW 在 16- 频道 (bit) 数字输出 / 入系列模块, 指定插槽的指定频道 (bit) 输出的值为 High 或 Low 输出值是 true 或 false [C ++ ] void DIO_DO_16_BW(int slot, int channel, BOOL data) 可使用模块 : i8042, i8050 DO_8_RB 从指定插槽的 8 位 (bit) 的 I-8000 系列数字输出模块, 传回目前输出状态值 [C ++ ] unsigned char DO_8_RB(int slot) 可使用模块 : i8060, i8064, i8065, i8066, i8068 DO_16_RB 从指定插槽的 16 位 (bit) 的 I-8000 系列数字输出模块, 传回目前输出状态值 [C ++ ] unsigned int DO_16_RB(int slot) 可使用模块 : i8057, i8056 DO_32_RB 从指定插槽的 32 位 (bit) 的 I-8000 系列数字输出模块, 传回目前输出状态值 [C ++ ] unsigned long DO_32_RB(int slot) 可使用模块 : i8041

272 DIO_DO_8_RB 从指定插槽的 8 位 (bit) 的 I-8000 系列数字输出 / 入模块, 传回目前输出状态值 [C ++ ] unsigned char DO_8_RB(int slot) 可使用模块 : i8054, i8055, i8063 DIO_DO_16_RB 从指定插槽的 16 位 (bit) 的 I-8000 系列数字输出 / 入模块, 传回目前输出状态值 [C ++ ] unsigned int DO_16_RB(int slot) 可使用模块 : i8042, i8050

273 9-5-4 看门狗定时器函数 EnableWDT 这功能激活看门狗定时器 [C ++ ] void EnableWDT(void) 即将可用 DisableWDT 这功能关闭看门狗定时器 [C ++ ] void DisableWDT(void) 即将可用 RefreshWDT 这功能更新 (clear) 看门狗定时器 [C ++ ] void RefreshWDT(void) 即将可用 IsResetByWatchDogTimer 这功能被使用检查是否系统是因看门狗定时器而重新开机 [C ++ ] int IsResetByWatchDogTimer(void) 即将可用 IsResetByPowerOff 这功能被使用检查是否系统是因电源关闭而重新开机 [C ++ ] int IsResetByPowerOff(void) 即将可用

274

275 9-5-5 EEPROM 读写函数 ReadEEP 从 EEPROM 读取 1 个字节数据 在 WinCon 系统有 16 K- 字节 EEPROM 控制单元 这 EEPROM 分为 256 个区块 (0 到 255), 而每个的区块是 64 个字节地址从 0 到 63 这是为了贮藏重要数据到不可抹除内存 (EEPROM), 而另提供存取的机制 [C ++ ] unsigned char ReadEEP(int block, int offset) WriteEEP 到写入 1 字节数据到 EEPROM 在 WinCon 系统有 16 K- 字节 EEPROM 控制单元 这 EEPROM 分为 256 个区块 (0 到 255), 而每个的区块是 64 个字节地址从 0 到 63 这是为了贮藏重要数据到不可抹除内存 (EEPROM), 而另提供存取的机制 [C ++ ] void WriteEEP(int block, int offset, unsigned char ucdata)

276 9-5-6 模拟输入函数 Init_8017H 这功能使用于初始化 I-8017H 模块 ( 模拟输入模块 ) 到指定的插槽 用户必须先执行这功能才能使用 I-8017H 的其它函数 [C ++ ] void Init_8017H(int slot) 可使用模块 : I8017H Set_8017H_LED 开 / 关 I-8017H 模块发光二级体灯号 他们可以用来当警报使用 [C ++ ] void Set_8017H_LED(int slot, unsigned int led) 可使用模块 : I8017H Set_8017H_Channel_Gain_Mode 这功能使用于规划及设定模拟输入信道设定, 在执行指定插槽使用 I-8017H 模块 的 ADC( 模拟数字 ) 转换的前需先设定 [C ++ ] void Set_8017H_Channel_Gain_Mode(int slot, int ch, int gain, int mode) 可使用模块 : I8017H Get_AD_FValue 从模拟输入模块, 传回模拟输入电压值 ( 浮点值格式 ), 在呼叫的前请先作转换设定 (Set_8017H_Channel_Gain_Mode) [C ++ ] float Get_AD_FValue(int gain) 可使用模块 : I8017H

277 Get_AD_IValue 从模拟输入模块, 传回模拟输入电流值 ( 浮点值格式 ), 在呼叫的前请先作转换设定 (Set_8017H_Channel_Gain_Mode) [C ++ ] float Get_AD_IValue(void) 可使用模块 : I8017H Get_AD_HValue 从模拟输入模块, 传回模拟输入电压值 ( 十六进制格式 HEX) [C ++ ] short Get_AD_HValue(void) 可使用模块 : I8017H I8017H_AD_POLLING 一次从模拟输入模块, 传回指定 数量 连续模拟输入电压值 ( 十六进制格式 HEX) 到一数组变量中 [C ++ ] int I8017H_AD_POLLING(int slot, int ch, int gain, int datacount, int *DataPtr) 可使用模块 : I-8017H ARRAY_HEX_TO_FLOAT_ALL 这功能依据规划设定的插槽, 增益, 和资料长度从 十六进制数组 转换到 浮 点值数组 ( 电压或电流 ) [C ++ ] void ARRAY_HEX_TO_FLOAT_ALL(int *HexValue, float *Ivalue, int slot, int gain, int len) 可使用模块 : I-8017H

278 9-5-7 模拟输出函数 I-8024_Initial 这功能使用在指定插槽初始化 I-8024 模块 在你使用其它 I-8024 函数的前你必须先执行这函数 [C ++ ] void I8024_Initial(int slot) 可使用模块 : I-8024 I8024_VoltageOut 这功能使用于输出指定电压值到 Wincon 8000 中 I-8024 模块指定 D/A 输出信道 [C ++ ] void I8024_VoltageOut(int slot, int ch, float data) 可使用模块 : I-8024 I8024_CurrentOut 这功能使用于初始化及输出指定电流值到 Wincon 8000 中 I-8024 模块指定 D/A 输出信道 在你使用其它 I-8024 电流函数的前你必须先执行这函数 [C ++ ] void I8024_CurrentOut(int slot, int ch, float data) 可使用模块 : I-8024 I8024_VoltageHexOut 这功能使用于输出指定电压值 ( 十六进制格式 ) 到 Wincon 8000 中 I-8024 模块指定 D/A 输出信道 [C ++ ] void I8024_VoltageHexOut(int slot, int ch, int data) 可使用模块 : I-8024

279 I8024_CurrentHexOut 这功能使用于初始化及输出指定电流值 ( 十六进制格式 ) 到 Wincon 8000 中 I-8024 模块指定 D/A 输出信道 [C ++ ] void I8024_CurrentHexOut(int slot, int ch, int data) 可使用模块 : I-8024 I8024_VoltageOutReadBack 这功能使用于读回 I-8024 模块指定 D/A 输出信道目前的输出电压值 [C ++ ] float I8024_VoltageOutReadBack(int slot, int ch) 可使用模块 : I-8024 I8024_CurrentOutReadBack 这功能使用于读回 I-8024 模块指定 D/A 输出信道目前的输出电流值 [C ++ ] float I8024_CurrentOutReadBack(int slot, int ch) 可使用模块 : I-8024 I8024_VoltageHexOutReadBack 这功能使用于读回 I-8024 模块指定 D/A 输出信道目前的输出电压值 ( 十六进制格式 ) [C ++ ] int I8024_VoltageHexOutReadBack(int slot, int ch) 可使用模块 : I-8024 I8024_CurrentHexOutReadBack 这功能使用于读回 I-8024 模块指定 D/A 输出信道目前的输出电流值 ( 十六进制格式 ) [C ++ ] int I8024_CurrentHexOutReadBack(int slot, int ch)

280 可使用模块 : I-8024

281 轴编码器 (Encoder) 函数 i8090_registration 这功能用来指定卡号 (cardno) 到在指定的插槽的 I-8090 模块 为了辨认在 WinCon 工作平台中多张 I-8090 卡, I-8090 模块应于使用的前注册它 假如在 WinCon 和给的插槽没有 I-8090, 如果注册失败会传回 0 请注意 [C ++ ] unsigned char i8090_registration(unsigned char cardno, int slot) 可使用模块 : I-8090 i8090_init_card 这功能被应用重新设定指定卡的 3 个轴记录值 [C ++ ] void i8090_init_card(unsigned char cardno, unsigned char x_mode, unsigned char y_mode, unsigned char z_mode) 可使用模块 : I-8090 i8090_get_encoder 这功能将查询目前 ENCODER 的记录值 (16- 位格式 ) [C ++ ] unsigned int i8090_get_encoder(unsigned char cardno,unsigned char axis) 可使用模块 : I-8090 i8090_reset_encoder 这功能将重设指定卡, 指定轴的 ENCODER 值 (=0) [C ++ ] void i8090_reset_encoder(unsigned char cardno, unsigned char axis) 可使用模块 : I-8090

282 i8090_get_encoder32 这功能用来取得指定卡, 指定轴的 ENCODER 值 (32- 位格式 ) 格式化 在使用这 功能前用户必须执行子程序 I8090 _ENCODER 32_ISR () 设定 [C ++ ] long i8090_get_encoder32(unsigned char cardno, unsigned char axis) 可使用模块 : I-8090 i8090_reset_encoder32 这功能应用于重设计数器值 (For i8090_get_encoder32) [C ++ ] void i8090_reset_encoder32(unsigned char cardno, unsigned char axis) 可使用模块 : I-8090 i8090_get_index 这功能用于传回指定卡的 X,Y,Z INDEX 值 [C ++ ] unsigned char i8090_get_index(unsigned char cardno) 可使用模块 : I-8090 i8090_encoder32_isr 这功能被使用计算脉冲值 现在 和 上次 的间的差值 依据这种想法, 使用定时器中断或手工方法周期性地 (2~10ms) 执行 I8090 _ENCODER 32_ISR () 函数 [C ++ ] void i8090_encoder32_isr(unsigned char cardno) 可使用模块 : I-8090

283

284 轴步进 / 伺服函数 i8091_registration 这功能用来指定卡号 (cardno) 到在指定的插槽的 I-8091 模块 为了辨认在 WinCon 工作平台中多张 I-8091 卡, I-8091 模块应于使用的前注册它 假如在 WinCon 和给的插槽没有 I-8091, 如果注册失败会传回 0 请注意 [C ++ ] unsigned char i8091_registration(unsigned char cardno, int slot) i8091_reset_system 这功能使用于重设和终止正在 8091 模块中运行的指令 用户能应用这指令当做在软件紧急停止功能 它能也清除全部 8091 模块设设定 执行这指令后, 用户需要重新设定在 I-8091 卡中的参数 [C ++ ] void i8091_reset_system(unsigned char cardno) i8091_set_var 这功能用于放设定 DDA 周期, 输出 Pulse 的加速度 / 减速度, 低速 (Low Speed) 和高速 (High Speed) 的参数值到指定的 I-8091 卡 [C ++ ] void i8091_set_var(unsigned char cardno, unsigned char DDA_cycle, unsigned char Acc_Dec, unsigned int Low_Speed, unsigned int High_Speed) i8091_set_defdir 这功能用来定义 X 和 Y 轴的马达 ( 步进或伺服 ) 旋转正方向 有时,X 轴的输出正方向或 Y 轴输出正方向与我们想要的方向相反, 此指令可以设定 X 轴与 Y 轴输出正方向 [C ++ ] void i8091_set_defdir(unsigned char cardno, unsigned char

285 defdirx,unsigned char defdiry) i8091_set_mode 这功能用于设定在指定的 I-8091 卡上 X 轴与 Y 轴的控制模式, 模式有 CW/CCW 及 Pulse/DIR 两种 [C ++ ] void i8091_set_mode(unsigned char cardno, unsigned char modex, unsigned char modey) i8091_set_servo_on 这功能用于控制在指定的 I-8091 卡上,X 轴与 Y 轴输出到伺服驱动器的伺服激活 (SERVO ON) 或伺服关闭 (SERVO OFF) [C ++ ] void i8091_set_servo_on(unsigned char cardno, unsigned char sonx, unsigned char sony) i8091_set_nc 这功能用来设定下列的极限开关为 N.C ( 常闭 ) 或 N.O ( 正常的开着的 ) 假如用户设定 sw 参数当做 N.O, 然后那些的极限开关是 低态 (Low) 有效 假如用户设定 sw 参数当做 N.C, 那些的极限开关是 高态 (High) 有效 无论它是 N.O 或 N.C 自动 - 保护系统会自动地判别 极限开关有 : ORG1, LS11, LS14, ORG2, LS21, LS24, EMG [C ++ ] void i8091_set_nc(unsigned char cardno, unsigned char sw) i8091_stop_x 这功能用于 X 轴立即停止 [C ++ ] void i8091_stop_x(unsigned char cardno) i8091_stop_y 这功能用于 Y 轴立即停止

286 [C ++ ] void i8091_stop_y(unsigned char cardno) i8091_stop_all 这功能用于 X 轴与 Y 轴立即停止, 并会清除已送到 FIFO 中所有指令 [C ++ ] void i8091_stop_all(unsigned char cardno) i8091_emg_stop 这功能同于 i8091_stop_all 功能, 但是只能被使用在中断程序, 并会清除已送到 FIFO 中所有指令 [C ++ ] void i8091_emg_stop(unsigned char cardno) i8091_lsp_org 这功能使 (X/Y) 轴以低速 (Low Speed) 移动归零 ( 碰到 ORG1/ORG2 极限开关而立即 停止 [C ++ ] void i8091_lsp_org(unsigned char cardno, unsigned char DIR, unsigned char AXIS) i8091_hsp_org 这功能使 (X/Y) 轴以高速 (High Speed)) 移动归零 ( 碰到 ORG1/ORG2 极限开关而立 即停止

287 [C ++ ] void i8091_hsp_org(unsigned char cardno, unsigned char DIR, unsigned char AXIS) i8091_lsp_pulse_move 这功能使 (X/Y) 轴以低速 (Low Speed) 移动指定步数 (pulse) [C ++ ] void i8091_lsp_pulse_move(unsigned char cardno, unsigned char AXIS, long pulsen) i8091_hsp_pulse_move 这功能使 (X/Y) 轴以高速 (High Speed) 移动指定步数 (pulse), 在移动中同时有加 减速动作 [C ++ ] void i8091_hsp_pulse_move(unsigned char cardno, unsigned char AXIS, long pulsen) i8091_lsp_move 这功能使 (X/Y) 轴以低速 (Low Speed) 向指定方向移动, 可以用 i8091_stop_x 或 i8091_stop_y 或 i8091_stop_all 令其停止 [C ++ ] void i8091_lsp_move(unsigned char cardno, unsigned char DIR, unsigned char AXIS)

288 i8091_hsp_move 这功能使 (X/Y) 轴以加速到高速 (High Speed) 向指定方向移动, 可以用 i8091_stop_x 或 i8091_stop_y 或 i8091_stop_all 令其停止 [C ++ ] void i8091_hsp_move(unsigned char cardno, unsigned char DIR, unsigned char AXIS) i8091_csp_move 这功能使 (X/Y) 轴以加速到指定速度向指定方向移动, 并可连续下指令动态变更其速度, 可以用 i8091_stop_x 或 i8091_stop_y 或 i8091_stop_all 或 i8091_slow_stop() 令其停止 [C ++ ] void i8091_csp_move(unsigned char cardno, unsigned char dir, unsigned char axis, unsigned int move_speed) i8091_slow_down 这功能使 (X/Y) 轴减速到低速 (Low Speed) 继续移动, 可以用 i8091_stop_x 或 i8091_stop_y 或 i8091_stop_all 令其停止 [C ++ ] void i8091_slow_down(unsigned char cardno, unsigned char AXIS)

289 i8091_slow_stop 这功能使 (X/Y) 轴减速到停止 [C ++ ] void i8091_slow_stop(unsigned char cardno, unsigned char AXIS) i8091_intp_pulse 这功能用于移动短距离 ( 短距离补间 ) 在 X-Y 平面 这命令提供用户了在 X-Y 平 面执行任意移动曲线方法 [C ++ ] void i8091_intp_pulse(unsigned char cardno, int Xpulse, int Ypulse) i8091_intp_line 这命令会在 X-Y 平面作长距离直线补间移动 I-8091 中央处理器运用 DDA 方法执 行补间位移, 在补间位移期间并会以向量加减速 ( 梯型速度曲线 ) [C ++ ] void i8091_intp_line(unsigned char cardno, int Xpulse, int Ypulse) i8091_intp_line02 This function is used to move a long interpolation line in the X-Y plane The host will automatically generate a trapezoidal speed profile of the X-axis and Y-axis via the state-machine-type calculation method The i8091_intp_line02 function only sets parameters into the driver Users can directly utilize the do while (i8091_intp_stop( )!=READY) method to

290 apply the computing entity 这命令会在 X-Y 平面作长距离直线补间移动 在补间位移期间并会以利用 state-machine-type 计算方法 ( 梯型速度曲线 ) I8091_INTP_ LINE 02 个功能仅仅放置参数进入驱动程序 用户能直接地利用 do while (i8091_intp_stop( )!=READY) 方法应用的 [C ++ ] void i8091_intp_line02(unsigned char cardno, long x, long y, unsigned int speed, unsigned char acc_mode) i8091_intp_circle02 This function is used to generate an interpolation circle in the X-Y plane The host will automatically generate a trapezoidal speed profile of the X-axis and Y-axis via the state-machine-type calculation method The i8091_intp_circle02 function only sets parameters into the driver Users can directly utilize the do while (i8091_intp_stop( )!=READY) method to execute the computing entity 这命令会在 X-Y 平面作圆补间移动 在补间位移期间并会以利用 state-machine-type 计算方法 ( 梯型速度曲线 ) I8091_INTP_ CIRCLE02 个功能仅仅放置参数进入驱动程序 用户能直接地利用 do while (i8091_intp_stop( )!=READY) 方法应用的 [C ++ ] void i8091_intp_circle02(unsigned char cardno, long x, long y, unsigned chat dir, unsigned int speed, unsigned char acc_mode)

291 i8091_intp_arc02 This command generates an interpolation arc in the X-Y plane The host will automatically generate a trapezoidal speed profile of the X-axis and Y-axis via the state-machine-type calculation method The i8091_intp_arc02() only sets parameters into the driver Users can directly call the do while (i8091_intp_stop( )!=READY) to execute the computing entity 这命令会在 X-Y 平面作圆弧补间移动 在补间位移期间并会以利用 state-machine-type 计算方法 ( 梯型速度曲线 ) I8091_INTP_ ARC02() 个功能, 仅仅放置参数进入驱动程序 用户能直接地利用 do while (i8091_intp_stop( )!=READY) 方法应用的 [C ++ ] void i8091_intp_arc02(unsigned char cardno, long x, long y, long R, unsigned char dir, unsigned int speed, unsigned char acc_mode) i8091_intp_stop This function must be applied when stopping the motor motion control for the above described 3 state-machine-type interpolation functions, to be precise the i8091_intp_line02, i8091_intp_circle02 and i8091_intp_arc02 functions The state-machine-type interpolation functions only set parameters into the driver The computing entity is in the i8091_intp_stop

292 function This function computes the interpolation profile It will return READY(0) for interpolation command completion, and return BUSY(1) for when it is not yet completed 为了停止上面的描写 3 个 state-machine-type 补间函数 (i8091_intp_line02, i8091_intp_circle02 及 i8091_intp_arc02) 的时候一定得执行 state-machine-type 函数仅仅放置参数进入驱动程序 真实计算是在 I8091_INTP_STOP 中进行 这功能为补间完成会传回 READY(0), 和未完成传回 BUSY(1) [C ++ ] unsigned char i8091_intp_stop(void) i8091_limit_x 这功能用于传回 X 轴极限关的状况 [C ++ ] unsigned char i8091_limit_x(unsigned char cardno) i8091_limit_y 这功能用于传回 Y 轴极限关的状况 [C ++ ] unsigned char i8091_limit_y(unsigned char cardno) i8091_wait_x 此功能将等 X 轴移动完成, 到达停止状态 [C ++ ] void i8091_wait_x(unsigned char cardno) i8091_wait_y 此功能将等 Y 轴移动完成, 到达停止状态 [C ++ ] void i8091_wait_y(unsigned char cardno)

293 i8091_is_x_stop 此功能将传回 X 轴是否为停止状态 [C ++ ] unsigned char i8091_is_x_stop(unsigned char cardno) i8091_is_y_stop 功能将传回 Y 轴是否为停止状态 [C ++ ] unsigned char i8091_is_y_stop(unsigned char cardno)

294 计数器 / 频繁函数 i8080_initdriver 初始化 8080 个模块函数 [C ++ ] int i8080_initdriver(int Slot) i8080_autoscan 这功能用来更新计数器值及更新硬件低 16 位计数器进位到软件高 16 位计数器, 使用者必须以回圈或 Timer 来执行更新, 详细请参考 I-8080 软件使用手册 [C ++ ] void i8080_autoscan(void) i8080_readdixor 这功能用于在 I8080 Xor 控制处理完成的后读取 8 个通路信号状态 [C ++ ] int i8080_readdixor(int Slot, short *Di) i8080_readdixorlp 这功能用于在 I8080 Xor 控制及低通虑波器处理完成的后读取 8 个通路信号状态 [C ++ ] int i8080_readdixorlp(int Slot, short *Di) i8080_readxorregister 功能被使用读取在 I8080 中 8 个频道 Xor 缓存器 [C ++ ] int i8080_readxorregister(int Slot, short *XorVal)

295 i8080_setxorregister 这功能用于设定 I8080 中 8 个频道 Xor 注册为 0 或 1 设置值不会存到 EEPROM, 因此 I8080 重新激活后所有 Xor 注册会回复默认值 (0) [C ++ ] int i8080_setxorregister(int Slot, short XorVal) i8080_eepwriteenable 功能用于设定 EEPROM 到可以写入模式, 然后 EEPROM 会允许用户写资料到 EEPROM [C ++ ] int i8080_eepwriteenable(int Slot) i8080_eepwritedisable 功能用于设定 EEPROM 到保护模式, 然后 EEPROM 不允许用户写资料到 EEPROM [C ++ ] int i8080_eepwritedisable(int Slot) i8080_eepwriteword 这功能用于写入 16- 字节资料到 I8080 EEPROM 中 这 16- 字节数据分为高 - 字节 (8 个字节 ) 和低 - 字节 (8- 字节 ) [C ++ ] int i8080_eepwriteword(int Slot, short Addr, short Hi, short Lo) i8080_eepreadword 这功能用于从 I8080 EEPROM 读取 16- 字节数据 这 16- 字节数据分为高度 - 字节 和低 - 字节 [C ++ ] int i8080_eepreadword(int Slot, short *Addr, short *Hi, short *Lo) i8080_readchannelmode 这功能用于读取运算模式, 包括计频算法, 低通滤波器,Xor 控制

296 [C ++ ] int i8080_readchannelmode(int Slot, int Channel, short *Mode) i8080_setchannelmode 这功能用于设定运算模式, 包括计频算法, 低通滤波器,Xor 控制 设置值不会存到 EEPROM, 因此 I8080 重新激活后所有值会回复默认值 [C ++ ] int i8080_setchannelmode(int Slot, int Channel, short Mode) i8080_readfreq 这功能用于读取 ( 频率模式中 ) 测量频率值 [C ++ ] int i8080_readfreq(int Slot, int Channel, float *f) i8080_readcntup 这功能被用于读取上计数器值 ( 上计数模式中 ) [C ++ ] int i8080_readcntup(int Slot,int Channel,unsigned long *Cnt32U, unsigned int *Overflow) i8080_readcntupdown 这功能被用于读取上计数值及下计数值 ( 上下计数及 Dir/Pulse 模式中 ) [C ++ ] int i8080_readcntupdown(int Slot,int Channel,unsigned long *Cnt32U, unsigned int *Overflow) i8080_setlowpassus 这功能使用于设定低通滤波器的参数 其它关系于低通滤波器详细信息, 请参考 I-8080 硬件手册 [C ++ ] int i8080_setlowpassus(int Slot, int Channel, unsigned short Us)

297 i8080_readlowpassus 这功能使用于读取低通滤波器的参数 其它关系于低通滤波器详细信息, 请参考 I-8080 硬件手册 [C ++ ] int i8080_readlowpassus(int Slot, int Channel, unsigned short *Us) i8080_clrcnt 这功能使用于清除计数器值 ( 归零 ), 可用于所有模式中 (Dir/ Pulse,Up/Down,Up Counter, and Frequency mode) [C ++ ] int i8080_clrcnt(int Slot, int Channel) i8080_readfreqconfiguration 这功能被用于读取上计数器设定资料 ( 频率模式中 ) [C ++ ] int i8080_readfreqconfiguration(int Slot, short *AutoTT, short *AutoVV, short *LowTT, short *LowVV, short *HighTT, short *HighVV) i8080_setfreqconfiguration 这功能被用于设定频率模式资料 ( 频率模式中 ) 设置值不会存到 EEPROM, 因此 I8080 重新激活后所有值会回复默认值 [C ++ ] int i8080_setfreqconfiguration(int Slot, short AutoTT, short AutoVV, short LowTT, short LowVV, short HighTT, short HighVV)

298 第十章 :Wincon-8000 应用程序范例 10-1 范例程序说明 在前一章介绍安装完 WinCON SDK 后, 在安装目录会有以下范例文件夹 : 87K_Inside_Slot_Demo 87K 模块使用范例 AI_AO_Demo A/D,D/A 模块使用范例 DI_DO_Demo DI/DO 模块使用范例 i8080demo 计数 / 频模块 I8080 使用范例 i8090demo 三轴 ENCODER I8090 使用范例 i8091demo 两轴轴卡 I8091 模块使用范例 Remote_ComPort_Demo I7K,I8K,I87K 远程独立模块使用范例 其中每一个资料夹各包含三个目录 C#,EVC++,VB.NET 分别为个开发平台之范 例程序项目原始文件, 读者可以直接开启, 以作为开始学习范例, 从中可以学到一 些基本运用 ( 参考图 9-6,9-7) 图 9-6

299 图 9-7 其它文件夹分别为 _NET 放置.Net 2003 (C# and VB) 用 DCONCE.DLL,WINCON.DLL 链接库文件 INC 放置 Embedded Visual C++ 用的包含文件 LIB 放置 Embedded Visual C++ 用的链接库文件 10-2 应用本机端集中式并列 (Parallel) 高速 I/O 模块 i-8000 I/O 模块, 是并列式的高速模块, 并且能直接安装在主控制器的插槽上 特性 : 高速的 A/D:100K 取样 / 秒 高速的 D/A:30K 从 -10V to +10V 高速的 D/I & D/O; 所有的数字 I/O 经由发光二极管指示器的状态, 提供可视化的指示 高速的步进 / 伺服运动控制模块 高速的编码器模块 高效能的计数 / 计频模块 高速的多信道 RS-232/RS-422/RS-485 模块 Printer 接口 & X-Socket 接口模块

300 应用程序的 DLL( 动态链接库 ) 功能, 首先你要安装 Wincon-8000 SDK, 上一章已介绍过了, 请参照, 然后使用 embedded Visual C ++ (MFC), 去开发你 的应用程序 所有 embedded Visual C 的范例 (demo) 程序, 都列在下面这个目录 路径, \WINCON\DI_DO_Demo\EVC++\Demo ; 在 Wince.net4.1 或在更新的 版本中, 使用 embedded Visual C 的开发工具去执行, 他们经过测试已 没有问题 当使用者要建立一个新程序, 他们能从 EVC++ 操作环境工具包, 去使用这 些程序, 如下所示 : 1. \WINCON\INC\WinconSDK.h 含括所有.h 的档案 2. \WINCON\INC\WinCon.h 实现 D/I, D/O 的功能 3. \WINCON\INC\i8017h.h 实现 I-8017H 模块的功能 4. \WINCON\INC\i8024.h 实现 I-8024 模块的功能 5. \WINCON\INC\i8090.h 实现 I-8090 模块的功能 6. \WINCON\INC\i8091.h 实现 I-8091 模块的功能 7. \WINCON\LIB\WinconSDK.lib WinconSDK.DLL 的输入程序 DI_DO_Demo for ECV ++ : ( 数字输入 输出范例 ) 这个范例程序能够使用在 Wincon 控制器上 I-8000 系列数字输出 / 输入模块 ( 看图片 1-1-1) 先选择 Wincon 数字输出的插槽 然后已被发现的模块和这个模块名称将显示在编辑框区域 当所有通道显示在数字输出面板, 你可以选择指定输出通道复选框 (check box ) 然后按鼠标键 Digital Output 按钮, 激活数据输出模块即输出 对于 DI 模块, 先选择 Wincon 数字输入的插槽 然后已被发现模块 和这个模块名称亦将显示在编辑框区域 当每个频道显示在数字输入面板, 用 户可按鼠标键 Auto Scan DI Enable 按钮来取得目前之输入状态 同时, Auto Scan DI Enable 按钮会变成 Auto Scan DI Disable 它意味用 户能再用鼠标键按钮, 停止自动扫描输入的功能

301 Fig AI_AO_Demo for EVC ++ :( 模拟输入 输出范例 ) 这个范例程序示范如何在 Wincon 控制器上使用 I-8000 系列模拟量输出 / 输入模块 ( 看图片 1-1-2) 用户可以按 Output Analog float 按钮, 去改变 I-8024 频道模拟输出值 针对 I-8017H 模块, 按鼠标键 Manual Scan 按钮获得目前输入值 假如用户按鼠标键 Auto Scan Enable 按钮, 模拟输入值将自动定时显示在窗体中 同时, Auto Scan Enable 按钮会变成 Auto Scan Disable 它意味用户能再按鼠标键按钮, 停止自动扫描输入的功能

302 Fig I-8090 Demo for EVC ++ :( 三轴编码器范例 ) 这个范例程序展示在 Wincon 控制器中 I-8090 模块应用 ( 看图片 1-1-3) 用户可以按 ReadEncoder Enable 按钮, 来获得编码器目前 X 轴 Y 轴和 Z 轴的编码器输入状态值 Fig I-8091 Demo for EVC ++ :( 两轴步进电机控制卡范例 )

303 这个范例程序展示在 Wincon 控制器中 I-8091 模块应用 ( 看图片 1-1-4) 用户能利用这个范例程控步进马达或伺服马达 Fig 当 I-8091 模块已被系统找到时, 然后显示程序全部的按钮将变为都能够使用 按鼠标键相对功能按钮, 然后 I-8091 模块会执行相关功能 譬如, 按鼠标键在 Single Axis Move 的 I8091 Lsp Move 按钮 当 I-8091 Lsp Move 对话框秀出 ( 看图片 1-1-5); 在 Which axis 选择 X-axis 选项按钮, 然后按鼠标键 OK 按钮 这步进马达会通过 I-8091 模块而激活

304 Fig I-8080 Demo for EVC ++ : 这个范例程序展示在 Wincon 控制器中 I-8080 模块应用 ( 看图片 1-1-6) I-8080 是为了 I-8000 系列和 WinCon 8000 设计的 4/8 通道计数器 / 频率模块 如以计数器模式运作, 可以统计诸如移动和流量, 在计频模式下可以量取瞬间的差量, 象旋转速度 频率 流速 等等 以这个范例展示你可以使用 Set 8080 Channel Mode 按钮及下拉式选单, 去设定每一通道的模式 每个通道目前的模式会显示在 Current Channel Mode 中 它是十六进制码, ( 请参考 I-8080 软件手册 ) 按鼠标键 Read 按钮后, 每个通道的值会被显示在画面上

305 Fig.1-1-6

306 10-3 应用本机端集中式串行 (serial) I/O 模块 I-87K I/O 模块, 是串行式模块, 并且能直接安装在每一个串行或并列的扩充插槽上 特性 : RTD Sensor 输入模块 Thermocouple 输入模块 Strain Gauge 输入模块 高分辨率多信道模拟输入模块 绝缘多信道数字转模拟模块 数字输入和数字输出模块有栓锁和计数的功能 计数 / 计频模块 下面展示程序示范在 Wincon-8000 控制器 ( 看图片 1-2-1) 应用 I I 和 I 模块 如果你有使用 Wincon-8000 插上 I-87K 系列模块去设计应用程序的计划, 那么这个范例程序将描述三个动态链接库的用途, WinconSDK.dll UARTCE.dll I7000CE.dll, 如果你想要更详细有关于 UARTCE.dll I7000CE.dll 的信息, 请参考 DCON_DLL 的手册 你必须在 embedded Visual C++ 开发操作环境中, 在编译器选项, 进入 UARTCE.lib I7000CE.lib WinconSDK.lib object/library 模块编辑框 在 include 程序里面加入使用者程序代码, 于特定的区域 #include "WinconSDK.h" #include "UARTCE.h" #include "I7000CE.h" 当你使用 Wincon-8000 插上 I-87K 系列模块, 你首先必须开启 COM1 串行端 口 这是因为 I-87K 系列模块是属于串行通讯系统, 而且它的串行通讯传输率 只有 1,115,200 一种, 以下为例子 : Open_Com( 1, , Data_Bit, Parity, Stop_Bit); 当你要开始读写 I-87K 系列模块时, 你必须先调用 ChangeSlotTo87K(slot) 函数, 你可以改变 COM1 串行端口适用于扩充槽上的号码 I-87K 系列模块 RS-485 的地址是设为 0 你下一次必须调用 I7000CE 函数库时, 你将能轻易地去读写

307 I-87K 系列模块, 如下范例 : int slot=1; unsigned long m_dwbuf[12]; float m_fbuf[12]; char m_szsend[100]; char m_szreceive[100]; m_dwbuf[0]=1; // WinCon COM1 埠 m_dwbuf[1]=0; // 在 WinCon RS-485 地址 = 0 m_dwbuf[2]=0x87055; m_dwbuf[3]=0; m_dwbuf[4]=100; m_dwbuf[6]=0; // 模块的 ID 是 16 进制号码 // Checksum 检查码 // 时间超过 // 旗标 0= 没储存,1= 储存传送 / 接收字 符串 ChangeSlotTo87K(slot); DigitalIn_87K(m_dwBuf,m_fBuf,m_szSend,m_szReceive);

308 Fig Fig

309 10-4 应用 I-7000 系列远程 I/O 模块 这个展示程序示范下面远程模块的应用 ;I-7060 I-7012 和 I-7021 模块 他们能透过 RS -485 网络连接到 Wincon 嵌入式控制器 ( 看图片 2-1-1) 使用者必需设定串行通讯端口, 来连接到他们的远程模块, 并且需要设定每个模块 RS -485 的地址 移动鼠标按下 Open COM 按钮, 打开 WinCon 嵌入式控制器的串行通讯端口 检查 I-7060 模块 DO0 ~DO3 复选框所输出的一个数字值 键入 0~10 浮动值并且移动鼠标按下 Analog Output I-7021 按钮, 从 I-7021 模块输出一个模拟转换值 移动鼠标按下 Auto Scan DI/AI Enable 按钮, 从 I-7060 模块去读取数字输入值, 并且同时去获得 I-7021 模块的模拟输入转换值 Fig

310 10-5 应用 I-87K 4/8 系列扩展单元 这个展示程序示范应用连结远程 I-87K 扩充槽内每一个 I I 和 I 的模块 这范例能透过 RS -485 网络连接到 Wincon 嵌入式控制器 ( 看图片 2-2-1) 为了连接到远程模块, 使用者需设定他们的串行通讯端口, 并且需去设定每一个模块 RS -485 的地址 移动鼠标按下 Open COM 按钮, 去打开 WinCon 嵌入式控制器的串行通讯端口 检查 I 模块 DO0 ~DO7 复选框所输出的一个数字值 键入 0~10 浮动值, 并且移动鼠标按下 Analog Output 按钮, 从 I-8024 的模块输出一个模拟转换值 移动鼠标按下 Auto Scan DI/AI Enable 按钮, 从 I 模块去读取数字输入值, 并且同时去获得 I 模块的模拟输入转换值 Fig

311 10-6 综合应用范例程序 这个范例程序, 主要的功能在整合第十章第一 二两节的各类型模块, 提 供使用者一个比较完整的架构应用 ; 并针对程序代码, 做比较详细的介绍 硬件连接说明 : 1. 将 I 插在 Wincon-8000 (slot 1) 2. 将 I 的 DI0 DI1 DI2 对应接到 DO0 DO1 DO2 3. 将 I-8041 插在 Wincon-8000 (slot 2) 4. 以 Wincon-8000 的 COM3 (RS-485) 做为远程连接端口 5. 以 RS-485 连接 I-7012D 6. 以 RS-485 连接 I-87K4, 将 I 插在 (slot 0) 7. 以 RS-485 连接 I-8000, 将 I-8024 插在 (slot 0) 功能使用说明 :( 参考图片 3-1) 1. I-7012D (Vin1), 第一组模拟电压输入 2. I (Vin2), 第二组模拟电压输入 3. 选择 Vin1-Vin2(DO0 输出 ) Vin2-Vin1(DO1 输出 ) Vin1+Vin2(DO2 输出 ) 4. 程序判断 I 的 DI0 DI1 DI2 输入状态, 决定运算模式 5. 运算后的模拟量从 I-8024 (Vout) 输出 6. 运算后的模拟转换数字量从 I-8041 (DO) 输出 7. 在 DO (HEX) 显示 16 进制数值 Fig. 3-1

312 demo_alldlg.cpp 程序范列说明 // demo_alldlg.cpp : implementation file // #include "stdafx.h" #include "demo_all.h" #include "demo_alldlg.h" // ICP DAS 所提供的标头档 #include "WinconSDK.h" #include "UARTCE.h" #include "I7000CE.h" // 设定 i87k 参数 #define i x87055 #define i87055_slot 1 // 设定模块名称 // 设定 I 插在第一个插槽 #define comport 1 // 设定 I 通讯端口为 COM1 ( 底板 ) #define baudrate // 设定串行传输率为 #define address 0 // 设定串行传输地址为 0 #define parity 0 // 不使用 #define timeout 100 #define checksum 0 // 不使用 // 设定通讯逾时 100ms #define databit 8 // 数据位 8bit #define stopbit 0 // 停止位 0bit #define flag 0 // 定义基本的 Remote 变量 static WORD m_comport=3; static WORD m_databit=8; static WORD m_stopbit=0; // 通讯端口编号 COM3 // 数据位 8bit // 停止位 0bit static WORD m_baudrate=9600; // 设定串行传输率为 9600 static WORD m_parity=0; static WORD m_timeout=100; static WORD m_checksum=0; static WORD m_7012addr=0; static WORD m_87017addr=1; // 不使用 // 设定通讯逾时 100ms // 不使用 // 定义 I-7012 地址,Addr=0 // 定义 I 地址,Addr=1

313 static WORD m_netid=2; static WORD m_8024slot=0; static WORD m_wbuf[12]; static DWORD m_dwbuf[12]; static float m_fbuf[12]; static char m_szsend[100]; static char m_szreceive[100]; // 定义 I-8024 地址,Addr=2 // 定义 I-8024 插在第 0 槽位置 // 通讯命令码暂存 // 通讯命令码暂存 // 通讯命令码暂存 // 通讯数据暂存 // 通讯数据暂存 #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = FILE ; #endif ///////////////////////////////////////////////////////////////////////////// // CDemo_allDlg dialog CDemo_allDlg::CDemo_allDlg(CWnd* pparent /*=NULL*/) : CDialog(CDemo_allDlg::IDD, pparent) //AFX_DATA_INIT(CDemo_allDlg) //AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hicon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); // 程序类别的一些成员变量的定义 void CDemo_allDlg::DoDataExchange(CDataExchange* pdx) CDialog::DoDataExchange(pDX); //AFX_DATA_MAP(CDemo_allDlg) DDX_Control(pDX, IDC_G3, m_g3); DDX_Control(pDX, IDC_G2, m_g2); DDX_Control(pDX, IDC_G1, m_g1); DDX_Control(pDX, IDC_DOon1, m_doon1);

314 DDX_Control(pDX, IDC_ARITHMETIC3, m_arithmetic3); DDX_Control(pDX, IDC_ARITHMETIC2, m_arithmetic2); DDX_Control(pDX, IDC_ARITHMETIC1, m_arithmetic1); DDX_Control(pDX, IDC_DOon, m_doon); DDX_Control(pDX, IDC_DOoff, m_dooff); DDX_Control(pDX, IDC_VIN1, m_vin1); DDX_Control(pDX, IDC_VIN2, m_vin2); DDX_Control(pDX, IDC_VOUT, m_vout); DDX_Control(pDX, IDC_DO_HEX, m_do_hex); DDX_Control(pDX, IDC_STOP, m_stop); DDX_Control(pDX, IDC_START, m_start); DDX_Control(pDX, IDC_87055_DO7, m_87055_do7); DDX_Control(pDX, IDC_87055_DO6, m_87055_do6); DDX_Control(pDX, IDC_87055_DO5, m_87055_do5); DDX_Control(pDX, IDC_87055_DO4, m_87055_do4); DDX_Control(pDX, IDC_87055_DO3, m_87055_do3); DDX_Control(pDX, IDC_87055_DO2, m_87055_do2); DDX_Control(pDX, IDC_87055_DO1, m_87055_do1); DDX_Control(pDX, IDC_87055_DO0, m_87055_do0); DDX_Control(pDX, IDC_87055_DI7, m_87055_di7); DDX_Control(pDX, IDC_87055_DI6, m_87055_di6); DDX_Control(pDX, IDC_87055_DI5, m_87055_di5); DDX_Control(pDX, IDC_87055_DI4, m_87055_di4); DDX_Control(pDX, IDC_87055_DI3, m_87055_di3); DDX_Control(pDX, IDC_87055_DI2, m_87055_di2); DDX_Control(pDX, IDC_87055_DI1, m_87055_di1); DDX_Control(pDX, IDC_87055_DI0, m_87055_di0); DDX_Control(pDX, IDC_8041_DO9, m_8041_do9); DDX_Control(pDX, IDC_8041_DO8, m_8041_do8); DDX_Control(pDX, IDC_8041_DO7, m_8041_do7); DDX_Control(pDX, IDC_8041_DO6, m_8041_do6); DDX_Control(pDX, IDC_8041_DO5, m_8041_do5); DDX_Control(pDX, IDC_8041_DO4, m_8041_do4); DDX_Control(pDX, IDC_8041_DO31, m_8041_do31); DDX_Control(pDX, IDC_8041_DO30, m_8041_do30); DDX_Control(pDX, IDC_8041_DO3, m_8041_do3);

315 DDX_Control(pDX, IDC_8041_DO29, m_8041_do29); DDX_Control(pDX, IDC_8041_DO28, m_8041_do28); DDX_Control(pDX, IDC_8041_DO27, m_8041_do27); DDX_Control(pDX, IDC_8041_DO26, m_8041_do26); DDX_Control(pDX, IDC_8041_DO25, m_8041_do25); DDX_Control(pDX, IDC_8041_DO24, m_8041_do24); DDX_Control(pDX, IDC_8041_DO23, m_8041_do23); DDX_Control(pDX, IDC_8041_DO22, m_8041_do22); DDX_Control(pDX, IDC_8041_DO21, m_8041_do21); DDX_Control(pDX, IDC_8041_DO20, m_8041_do20); DDX_Control(pDX, IDC_8041_DO2, m_8041_do2); DDX_Control(pDX, IDC_8041_DO19, m_8041_do19); DDX_Control(pDX, IDC_8041_DO18, m_8041_do18); DDX_Control(pDX, IDC_8041_DO17, m_8041_do17); DDX_Control(pDX, IDC_8041_DO16, m_8041_do16); DDX_Control(pDX, IDC_8041_DO15, m_8041_do15); DDX_Control(pDX, IDC_8041_DO14, m_8041_do14); DDX_Control(pDX, IDC_8041_DO13, m_8041_do13); DDX_Control(pDX, IDC_8041_DO12, m_8041_do12); DDX_Control(pDX, IDC_8041_DO11, m_8041_do11); DDX_Control(pDX, IDC_8041_DO10, m_8041_do10); DDX_Control(pDX, IDC_8041_DO1, m_8041_do1); DDX_Control(pDX, IDC_8041_DO0, m_8041_do0); //AFX_DATA_MAP //demo_all 有使用的函式宣告 BEGIN_MESSAGE_MAP(CDemo_allDlg, CDialog) //AFX_MSG_MAP(CDemo_allDlg) ON_BN_CLICKED(IDC_START, OnStart) ON_BN_CLICKED(IDC_STOP, OnStop) ON_BN_CLICKED(IDC_ARITHMETIC1, OnArithmetic1) ON_BN_CLICKED(IDC_ARITHMETIC2, OnArithmetic2) ON_BN_CLICKED(IDC_ARITHMETIC3, OnArithmetic3) ON_WM_TIMER() ON_WM_CTLCOLOR()

316 //AFX_MSG_MAP END_MESSAGE_MAP() //I 模块输出和输入运算按钮副函式 ///////////////////////////////////////////////////////////////////////////// // CDemo_allDlg UI message handlers void CDemo_allDlg::ArithmeticDO() // output // dwbuf[0]; // RS-232 port number: 1/2/3/4.../255 // dwbuf[1]; // module address: 0x00 to 0xFF // dwbuf[2]; // module ID: 0x87054/55/56/57/60/63/64/65/66/68 // dwbuf[3]; // checksum: 0=disable, 1=enable // dwbuf[4]; // TimeOut constant: normal=100 // dwbuf[5]; // 16-bit digital data to output // dwbuf[6]; // flag: 0=no save, 1=save send/receive string m_dwbuf[0]=comport; m_dwbuf[1]=address; m_dwbuf[2]=i87055; m_dwbuf[3]=checksum; m_dwbuf[4]=timeout; //m_dwbuf[5]=1; m_dwbuf[6]=flag; ChangeSlotTo87K(i87055_slot); DigitalOut_87K(m_dwBuf,m_fBuf,m_szSend,m_szReceive); void CDemo_allDlg::ArithmeticDI() unsigned char InTemp=0; // input // dwbuf[0]; // RS-232 port number: 1/2/3/4.../255 // dwbuf[1]; // module address: 0x00 to 0xFF

317 // dwbuf[2]; // module ID: 0x8051/52/53/54/55/63 // dwbuf[3]; // checksum: 0=disable, 1=enable // dwbuf[4]; // TimeOut constant: normal=100 // dwbuf[6]; // flag: 0=no save, 1=save send/receive string // input // dwbuf[5]; // Read 16-bit input digital data m_dwbuf[0]=comport; m_dwbuf[1]=address; m_dwbuf[2]=i87055; m_dwbuf[3]=checksum; m_dwbuf[4]=timeout; m_dwbuf[6]=flag; Sleep(2); ChangeSlotTo87K(i87055_slot); DigitalIn_87K(m_dwBuf,m_fBuf,m_szSend,m_szReceive); InTemp=(unsigned char)m_dwbuf[5]; // 判断 I 的 DI0 DI1 DI2 信号输入 if ((InTemp&0x01)==0) m_87055_di0.setbitmap(m_dooff.getbitmap()); else m_87055_di0.setbitmap(m_doon1.getbitmap()); if ((InTemp&0x02)==0) m_87055_di1.setbitmap(m_dooff.getbitmap()); else m_87055_di1.setbitmap(m_doon1.getbitmap()); if ((InTemp&0x04)==0) m_87055_di2.setbitmap(m_dooff.getbitmap()); else m_87055_di2.setbitmap(m_doon1.getbitmap());

318 ///////////////////////////////////////////////////////////////////////////// // CDemo_allDlg message handlers BOOL CDemo_allDlg::OnInitDialog() CDialog::OnInitDialog(); // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); SetIcon(m_hIcon, FALSE); // Set big icon // Set small icon CenterWindow(GetDesktopWindow()); // center to the hpc screen // TODO: Add extra initialization hereopen_com(comport, baudrate, databit, parity, stopbit); m_start.enablewindow(true); m_stop.enablewindow(false); Open_Com(comport, baudrate, databit, parity, stopbit); // 开启 Wincon-8000 COM1 m_arithmetic1.enablewindow(false); m_arithmetic2.enablewindow(true); m_arithmetic3.enablewindow(true); m_87055_do0.setbitmap(m_doon.getbitmap()); m_87055_do1.setbitmap(m_dooff.getbitmap()); m_87055_do2.setbitmap(m_dooff.getbitmap()); m_dwbuf[5]=1; ArithmeticDO(); ArithmeticDI();

319 return TRUE; // return TRUE unless you set the focus to a control void CDemo_allDlg::OnStart() // 按激活钮的处理函式 // TODO: Add your control notification handler code here m_start.enablewindow(false); m_stop.enablewindow(true); Open_Com((char)m_ComPort,m_Baudrate,(char)m_DataBit,(char)m_Parity,(char)m_S topbit); SetTimer(10,200,NULL); // 开启 Remote COM3(RS-485) // 设定定时器编号为 10;200ms 中断一次 void CDemo_allDlg::OnStop() // 按停止钮的处理函式 // TODO: Add your control notification handler code here m_start.enablewindow(true); m_stop.enablewindow(false); Close_Com((char)m_ComPort); KillTimer(10); // 关闭 Remote COM3(RS-485) // 关闭编号 10 的定时器 // 运算按钮 (Vin1-Vin2 Vin2-Vin1 Vin1+Vin2) 的三个处理函式 void CDemo_allDlg::OnArithmetic1() // TODO: Add your control notification handler code here m_arithmetic1.enablewindow(false); m_arithmetic2.enablewindow(true); m_arithmetic3.enablewindow(true); m_87055_do0.setbitmap(m_doon.getbitmap()); m_87055_do1.setbitmap(m_dooff.getbitmap()); m_87055_do2.setbitmap(m_dooff.getbitmap()); m_dwbuf[5]=1;

320 ArithmeticDO(); ArithmeticDI(); void CDemo_allDlg::OnArithmetic2() // TODO: Add your control notification handler code here m_arithmetic1.enablewindow(true); m_arithmetic2.enablewindow(false); m_arithmetic3.enablewindow(true); m_87055_do0.setbitmap(m_dooff.getbitmap()); m_87055_do1.setbitmap(m_doon.getbitmap()); m_87055_do2.setbitmap(m_dooff.getbitmap()); m_dwbuf[5]=2; ArithmeticDO(); ArithmeticDI(); void CDemo_allDlg::OnArithmetic3() // TODO: Add your control notification handler code here m_arithmetic1.enablewindow(true); m_arithmetic2.enablewindow(true); m_arithmetic3.enablewindow(false); m_87055_do0.setbitmap(m_dooff.getbitmap()); m_87055_do1.setbitmap(m_dooff.getbitmap()); m_87055_do2.setbitmap(m_doon.getbitmap()); m_dwbuf[5]=4; ArithmeticDO(); ArithmeticDI();

321 // 定时器中断处理函式 void CDemo_allDlg::OnTimer(UINT nidevent) // TODO: Add your message handler code here and/or call default TCHAR temp[20],*stopstring; unsigned char InTemp=0; float AI1; float AI2; float AO; //Exp7K WORD CALLBACK AnalogIn(WORD wbuf[], float fbuf[], // char szsend[], char szreceive[]); // input // wbuf[0]; // RS-232 port number: 1/2/3/4.../255 // wbuf[1]; // module address: 0x00 to 0xFF // wbuf[2]; // module ID: 0x7011/12/13/14/16/17/18/33 // wbuf[3]; // checksum: 0=disable, 1=enable // wbuf[4]; // TimeOut constant: normal=100 // wbuf[5]; // channel number for multi-channel // wbuf[6]; // flag: 0=no save, 1=save send/receive string // output // fbuf[0]=analog input value m_wbuf[0]=m_comport; m_wbuf[1]=m_7012addr; m_wbuf[2]=0x7012; m_wbuf[3]=m_checksum; m_wbuf[4]=m_timeout; m_wbuf[5]=0; m_wbuf[6]=0; AnalogIn(m_wBuf, m_fbuf, m_szsend, m_szreceive); swprintf(temp, _T("%.3f"), m_fbuf[0]); m_vin1.setwindowtext(temp); AI1=m_fBuf[0];

322 //Exp87K WORD CALLBACK AnalogIn_87K(DWORD dwbuf[], float fbuf[], // char szsend[], char szreceive[]); // input // dwbuf[0]; // RS-232 port number: 1/2/3/4.../255 // dwbuf[1]; // module address: 0x00 to 0xFF // dwbuf[2]; // module ID: 0x87013/17/18/33 // dwbuf[3]; // checksum: 0=disable, 1=enable // dwbuf[4]; // TimeOut constant: normal=100 // dwbuf[5]; // channel number for multi-channel // dwbuf[6]; // flag: 0=no save, 1=save send/receive string // output // fbuf[0]=analog input value m_dwbuf[0]=m_comport; m_dwbuf[1]=m_87017addr; m_dwbuf[2]=0x87017; m_dwbuf[3]=m_checksum; m_dwbuf[4]=m_timeout; m_dwbuf[5]=0; m_dwbuf[6]=0; AnalogIn_87K(m_dwBuf, m_fbuf, m_szsend, m_szreceive); swprintf(temp, _T("%.3f"), m_fbuf[0]); m_vin2.setwindowtext(temp); AI2=m_fBuf[0]; // input // dwbuf[0]; // RS-232 port number: 1/2/3/4.../255 // dwbuf[1]; // module address: 0x00 to 0xFF // dwbuf[2]; // module ID: 0x8051/52/53/54/55/63 // dwbuf[3]; // checksum: 0=disable, 1=enable // dwbuf[4]; // TimeOut constant: normal=100 // dwbuf[6]; // flag: 0=no save, 1=save send/receive string // output // dwbuf[5]; // Read 16-bit input digital data m_dwbuf[0]=comport; m_dwbuf[1]=address;

323 m_dwbuf[2]=i87055; m_dwbuf[3]=checksum; m_dwbuf[4]=timeout; m_dwbuf[6]=flag; ChangeSlotTo87K(i87055_slot); DigitalIn_87K(m_dwBuf,m_fBuf,m_szSend,m_szReceive); InTemp=(unsigned char)m_dwbuf[5]; if ((InTemp&0x01)==0) m_87055_di0.setbitmap(m_dooff.getbitmap()); else m_87055_di0.setbitmap(m_doon1.getbitmap()); AO=AI1-AI2; if ((InTemp&0x02)==0) m_87055_di1.setbitmap(m_dooff.getbitmap()); else m_87055_di1.setbitmap(m_doon1.getbitmap()); AO=AI2-AI1; if ((InTemp&0x04)==0) m_87055_di2.setbitmap(m_dooff.getbitmap()); else m_87055_di2.setbitmap(m_doon1.getbitmap()); AO=AI1+AI2;

324 // output // i-8041 Wincon 8000 solt // display HEX value // // 计算 I-8041 输出值 32bit, 及灯号的显示 //Vin1 Vin2 满度为 +-5V //Vout DO(Hex) 满度为 +-10V unsigned long DOVal; int slot=2; if (AO == 10) DOVal=AO/10; DOVal=DOVal* ; else DOVal=AO/10* ; DO_32(slot,DOVal); swprintf(temp, _T("%Xh"), DOVal); m_do_hex.setwindowtext(temp); int i; CStatic* GetPic; unsigned long j=0x ; for (i=0; i<32; i++) GetPic = (CStatic*)GetDlgItem(1034-i); if ((DOVal & (j>>i)) == 0) GetPic->SetBitmap(m_DOoff.GetBitmap()); else GetPic->SetBitmap(m_DOon.GetBitmap());

325 //Exp8K WORD CALLBACK AnalogOut_8K(DWORD dwbuf[], float fbuf[], // char szsend[], char szreceive[]); // input // wbuf[0]; // RS-232 port number: 1/2/3/4.../255 // wbuf[1]; // module address: 0x00 to 0xFF // wbuf[2]; // module ID: 0x8024,8022,8026 // wbuf[3]; // checksum: 0=disable, 1=enable // wbuf[4]; // TimeOut constant: normal=100 // wbuf[5]; // Channel No.(0 to 3) // wbuf[6]; // flag: 0=no save, 1=save send/receive string // wbuf[7]; // slot // f7000[0]; // analog output value swprintf(temp, _T("%.3f"), AO); m_vout.setwindowtext(temp); AO=(float)wcstod(temp, &stopstring); m_dwbuf[0]=m_comport; m_dwbuf[1]=m_netid; m_dwbuf[2]=0x8024; m_dwbuf[3]=m_checksum; m_dwbuf[4]=m_timeout; m_dwbuf[5]=0; m_dwbuf[6]=0; m_dwbuf[7]=m_8024slot; m_fbuf[0]=ao; AnalogOut_8K(m_dwBuf, m_fbuf, m_szsend, m_szreceive); CDialog::OnTimer(nIDEvent); // 更改一些字型的颜色 HBRUSH CDemo_allDlg::OnCtlColor(CDC* pdc, CWnd* pwnd, UINT nctlcolor)

326 HBRUSH hbr = CDialog::OnCtlColor(pDC, pwnd, nctlcolor); // TODO: Change any attributes of the DC here if (CTLCOLOR_BTN == nctlcolor) if (pwnd == &m_start) pdc->settextcolor(rgb(0,255,0)); if (pwnd == &m_stop) pdc->settextcolor(rgb(255,0,0)); if (pwnd == &m_arithmetic1) pdc->settextcolor(rgb(255,255,0)); if (pwnd == &m_arithmetic2) pdc->settextcolor(rgb(255,255,0)); if (pwnd == &m_arithmetic3) pdc->settextcolor(rgb(255,255,0)); if (pwnd->getdlgctrlid() == IDC_DO_HEX) pdc->settextcolor(rgb(255,17,136)); if (pwnd->getdlgctrlid() == IDC_VIN1) pdc->settextcolor(rgb(60,247,143)); if (pwnd->getdlgctrlid() == IDC_VIN2) pdc->settextcolor(rgb(60,247,143)); if (pwnd->getdlgctrlid() == IDC_VOUT) pdc->settextcolor(rgb(84,191,222)); if (pwnd->getdlgctrlid() == IDC_G1) pdc->settextcolor(rgb(255,128,0)); if (pwnd->getdlgctrlid() == IDC_G2) pdc->settextcolor(rgb(0,0,160)); if (pwnd->getdlgctrlid() == IDC_G3) pdc->settextcolor(rgb(128,0,128)); // TODO: Return a different brush if the default is not desired return hbr;

327 第十一章 : 以 evc 设计自控程序的基础设计自动控制系统与一般的画面显示 报表打印 资料计算等等有不同的设计理念 必须考虑实时性, 通信程序及逻辑运算的优先性 所谓实时控制就是系统必须于一定时间内对于各种信号及状况作出反应, 例如 :PLC 设备可能为 0.1 秒之内 PC 的监控系统可能为 1 秒之内 因此本章先介绍于 WinCE 系统必须先建立一些基本的设计架构, 才能更进一步建立完整的系统 1 设计准备工作 : 我们先建立一个 Demo_01 的项目, 然后以此项目展示如何设计 Thread 异步通信 网络通信等基本功能 藉此步骤让读者充分了解设计程序, 以便于后面章节的说明 1.1 evc 产生新的项目首先于 evc++4.1 的 Menu 选择 [File][New] 显示下面画面, 于 Projects 画面页选择 WCE MFC AppWizard (exe) Project Name 输入 Demo_01 CPUs 选择 Win32 [WCE ARMV4] 等 按下 OK Button 后进入下一个画面 然后接着次页以后的各画面, 点选各选项再按 Next Button 以完成项目的建立 WinCON 8000 是使用 ARMV4 的 CPU, 此点必须特别注意 evc++4.1 所编译出来的程序执行文件会有所不同 图 11-1:eVC 产生新项目画面

328 于此画面选择 Single document 含有 Menu 的功能, 语言选择 英语 [ 美国 ] 图 11-2:eVC 新项目 setup-1 画面于此画面选择 Windows Sockets 以包含网络的功能 图 11-3:eVC 新项目 setup-2 画面

329 于此画面选择 Yes please 以产生原始档, 使用 As a shared DLL 图 11-4:eVC 新项目 setup-3 画面

330 最后产生下面画面内的四个原始档 再选择 Cdemo_01View, 然后于 Base clase 选取 CListView, 使得主画面为 List View 显示 最后按 Finish Button 开始产生新项目 利用 ListView 的方式, 将重要讯息显示于画面上, 此种方式也将应用于自动控制系统 Demo8000 程序 图 11-5:eVC 新项目 setup-4 画面所产生的新项目如下画面 注意此项目可以编译成 Win32 (WCE ARMV4) Debug 及 Release 等两种执行档 如果我们于新项目的 CPU 选项增加时, 此处的 项目就会增加, 所以当设计相关程序完成后, 由此选项 Compiler and Link 我们 所要的执行档 至于 evc++4.1 的程序设计方式与 PC 系统的 VC++ 几乎完全一样, 只是 WinCE 可以说是 Windows 一个子系统, 例如 : 系统 API 只有主要部份 有 Single document 的画面而没有 Multi-document 的功能等等 我们可以由 Menu 的 [Help][Content] 的说明找到相关资料 我们选择 Win32 (WCE ARMV4) Release 然后由 Menu [Build][Build Demo_01.exe] 产生一个新的执行档 于此项目所存放的目录 ( 此例目录为 \WinCON8000\Demo_01\ARMV4Rel) 找到此执行档 复制此档于 Jet Flash Memory Card, 插入 WinCON 8000 的 USB Port, 由 HardDisk 目录上, 找到 Demo_01.exe, 激活此程序, 就会得到附带 Menu 但是空白的画面 下面章节将 加入展示功能, 至此已经建立一个最基本的程序范例

331 图 11-6:eVC 开发设计的主画面 1.2 加入 WinCON 8000 的 SDK 等 选择 Menu [Tools][Options] 进入如下画面, 加入 WinCON 8000 所使用的 SDK 等 Library 及 Include 檔 图 11-7:eVC 设计 Option 的设定画面

332 将 WinCON 8000 所附的光盘片安装于 PC 上后, 其相关档案是存放于 \DAQPro 目录内 选择 [Directories] 页然后加上 Library 及 Include 的所在目录, 如下两画面所示, 当程序用到这些档案时, 才找得到位置 图 11-8:eVC 设定 Include file 目录画面 图 11-9:eVC 设定 Library file 目录画面

333 于 Menu [Project][Settings] 进入如下画面, 然后于 Object/library modules 加 入 WinconSDK.LIB 往后程序才可以 Link 到此 Library 图 11-10:eVC 加入 WinCONSDK.LIB 画面 2 Thread 基本设计方式 自动控制系统对于通信及信号处理都是必须实时反应的特性, 同时运作而不 能相互等待等要求 Thread 就是提供于一个程序内可以同时处理不同功能的设计 方式 Windows 或 WinCE 操作系统都是 Multi-Task Multi-Thread 的架构, 而不 像早期的 DOS 只有 Single Task 的功能 于 MFC 设计 Thread 相当的简单, 以下 说明建立 Thread 的方式及其基本的运作, 以利后面实际控制程序的了解 本章节提供 Demo_01 程序, 以说明 Thread 基本架构 RS232C 通信 TCP/IP 通信 Global 变量. 等等功能展示 本程序许多设计手法于后面的自动控制系 统 Demo8000 将会使用到, 希望读者先行熟悉 2.1 使用 THREAD_INFO 架构传送必要系数 : typedef struct HWND hwnd; // Windows Handle for display data unsigned char *Para_1; // parameter-1 pointer unsigned char *Para_2; // parameter-2 pointer BOOL *lpkillthread; // Thread flag for start/stop THREAD_INFO; Thread 为独立运作的程序, 如果要与主程序作资料的交换可以于 stdafx.h 先 定义上面的 struct 架构, 然后于激活 Thread 将此 struct 的 pointer 传送给 Thread, 就可以依据此作各种的处理 此 struct 并不一定要照以上内容可由程序设计者自 行定义, 本书所提供的是依据多年的经验提供以上内容最为有效 以下说明各项

334 系数的意义 我们要善用 C++ 的优点, 所有系数都以 pointer 来传送, 使得 Thread 非常便利取入各项系数或者子程序等 HWND hwnd: 当某一个 Class 激活 Thread 时, 带入该程序 Class 的 Handle, 如此 Thread 将可以使用到该 Class 的所有 function 及系数等 unsigned char *Para_1: 某一区域资料的 pointer, 此区域资料无论为 struct int string. 等各种型态, 都转换为 unsigned char *, 当 Thread 接到后再转型为原有的资料型态, 如此使用具有最大的弹性 unsigned char *Para_1: 另一区域资料 意义与上同 BOOL *lpkillthread: 此为一个 Flag 进入 Thread 后, 常常会以 While Loop 作无停止的循环一直处理 主程序可以利用此 Flag 此之跳出循环 2.2 激活 Thread: 于 Demo_01 程序的 Cdemo_01Doc Class 一开始就激活所要的 Thread CDemo_01Doc::CDemo_01Doc() // TODO: add one-time construction code here WSADATA WSAData; // Contains details of the Windows // Sockets implementation CString msg; if (WSAStartup (MAKEWORD(1,1), &WSAData)!= 0) // 此为使用 TCP/IP 功能的系统初期处理 msg.format(_t("wsastartup failed. Error: %d"), WSAGetLastError ()); AfxMessageBox(msg, MB_OK); // 以下要激活 TCP/IP 通信 RS232 通信 Date/Time 显示等三个 Thread // 首先将各项系数归零 // TODO: add construction code here // This is the flag that tells the // thread to quit. m_btcpkill = FALSE; m_brs232kill = FALSE; m_btimerkill = FALSE; // NULL the pointer to the thread so // we know it hasn't been created. m_ptcpthread = NULL; m_prs232thread = NULL; m_ptimerthread = NULL;

335 if( m_ptcpthread == NULL ) // TCP/IP 通信 Thread 设定及激活, 内容与最后 Timer Thread 相似 if( m_prs232thread == NULL ) // RS232 通信 Thread 设定及激活, 内容与最后 Timer Thread 相似 if( m_ptimerthread == NULL ) // Timer Thread 设定及激活 // Store the window handle and the Kill flag. m_timerthreadinfo.hwnd = (HWND)this;// 存入 CDemo_01Doc 的 pointer m_timerthreadinfo.para_1 = NULL; m_timerthreadinfo.para_2 = NULL; m_timerthreadinfo.lpkillthread = &m_btimerkill; // 激活此 Thread, 然后带入 THREAD_INFO 的 struct pointer m_ptimerthread = AfxBeginThread(TimerThreadProc, (LPVOID) &m_timerthreadinfo); 2.3 执行 Thread: 以 Timer Thread 为例说明 Thread 激活后先取入 struct THREAD_INFO, 然后由此得到 CDemo_01Doc 的 Class pointer 进入主循环后, 每次取入系统日期及时间, 显示于主画面的 ListView 上 因为 Thread 本身无法直接控制画面的显示, 所以藉由 Class pointer 取用 DisplayItem() 的 Function, 再由此使用 CDemo_01View 的 SetListItem() 的 Function 最后显示于画面上 以上转两个弯的做法, 是因为 MFC 的架构为 Object Class, 必须藉由 Class 的 Method 来作接口, 无法直接控制, 请读者体会此关键点 本 Thread 使用 WinCE 的 API 取入系统日期及时间, 然后显示于画面上, 我们可以看到时间于画面上随时变动 系统时间 API 只提供到秒为止, 至于 Millisecond 的时间, 此对自动控制程序非常的重要, 则必须使用 WinCON 8000 控制器的 WinCONSDK Library, 我们将于下一个章说明 UINT TimerThreadProc( LPVOID lpparam )

336 // Get a THREAD_INFO pointer from the // parameter that was passed in. CDemo_01Doc* pdoc; CString msg; SYSTEMTIME t; int year, month, day, hour, minute, second, nsec; Sleep(3000); // 由 Thread 传来系数转换成 THREAD_INFO 的 pointer THREAD_INFO *lpthreadinfo = (THREAD_INFO *) lpparam; // 再由此取入 CDemo_01Doc Class Pointer pdoc= (CDemo_01Doc *)lpthreadinfo->hwnd; msg.format(_t("timer Thread Start")); pdoc->displayitem(1, 1, msg); // 使用此 Class pointer 显示讯息于 ListView // 进入主循环 while(1) GetLocalTime(&t); // 取入系统日期及时间, 此为 WinCE API year=t.wyear ; month=t.wmonth ; day=t.wday ; hour=t.whour ; minute=t.wminute ; second=t.wsecond ; // nsec =t.wmilliseconds ; // always zero in WinCE System get_nsec_time(); // 此点于下一章说明 nsec =NSEC ; msg.format(_t("%04d/%02d/%02d %02d:%02d:%02d %4d"), year, month, day, hour, minute, second, NSEC); pdoc->displayitem(1, 2, msg); // 显示日期时间于 ListView 上 Sleep(100); // 暂停 0.1 秒 return 0; void CDemo_01Doc::DisplayItem(int row, int itemno, CString msg)

337 // TODO: Add your command handler code here pdemoview->setlistitem(row, itemno, msg); void CDemo_01View::SetListItem(int row, int itemno, CString msg) CListCtrl& list = GetListCtrl(); list.setitemtext(row, itemno, msg); 2.4 停止 Thread: 于程序结束时 (Class deconstruction), 取消 Thread CDemo_01Doc::~CDemo_01Doc() HANDLE hthread; BOOL flag; flag= false; if(m_ptcpthread!= NULL) hthread =m_ptcpthread->m_hthread; // Set the flag to kill the thread to TRUE; m_btcpkill = TRUE; // Wait for the thread to end. ::WaitForSingleObject( hthread, 5000 ); if(flag==false && m_prs232thread!= NULL) hthread =m_prs232thread->m_hthread; // Set the flag to kill the thread to TRUE; m_brs232kill = TRUE; // Wait for the thread to end. ::WaitForSingleObject( hthread, 5000 ); if(flag==false && m_ptimerthread!= NULL)

338 hthread =m_ptimerthread->m_hthread; // Set the flag to kill the thread to TRUE; m_btimerkill = TRUE; // Wait for the thread to end. ::WaitForSingleObject( hthread, 5000 ); WSACleanup (); // 结束 TCP/IP 的系统设定 图 11-11:Demo_01 程序激活的初期画面

339 3 异步通信端口的设计技巧 : 此 Demo 分成两大部分, 一为 Dialog 画面设定通信系数 Open COM Port 以及将资料传送出去 另一为 Thread 以接收外部传来的讯息, 然后显示于 ListView 上, 此种做法是因为外部系统可能随时会传来讯息, 所以需要 Thread 随时检查 对于 COM Port 是否 Open 则以 Global 变量来作纪录, 如此于任何一个程序都可以读写这些变量 Thread 对于未 Open COM Port 则依据此 Global 变量来判断 3.1 Global 变量使用 : 此技巧也会使用在自动控制程序 Demo8000, 请读者注意 于 Demo_01.cpp 主程序最前面宣称如下 // GLOBAL Variable BOOL m_com2, m_com3; // 此两个 Flag 纪录 COM2 COM3 是否 Open 于 stdafx.h 再宣称如下 表示可以被其它程序所连接使用 如下图的程序设 计画面上看到 extern BOOL m_com2, m_com3; 图 11-12:eVC 加入 Global 变量的画面

340 3.2 Open Port Dialog 画面 : 由主画面 Menu [Demo Function][COM Port Demo] 激活如下 Dialog 画面 选择所要的通信系数后, 按 [Open COM2] 或 [Open COM3] Button, 会显示 MessageBox 显示 Open COM Port 是否成功 如果已经 Open, 然后再 Open 一次当然会显示错误讯息 于 Data Send 的 Edit 字段输出任何文字, 再按 [COM2 Send Data] 或 [COM3 Send Data] 就会将此字段文字传送出去 当此 Dialog 被结束时, 也将被 Open 的 Port Close 图 1-13:Demo_01 程序的 RS232 Dialog 画面 3.3 RS232C Thread 程序说明 : 此 Thread 执行后进入主循环, 依据 Global 变量 m_com2 m_com3 的状态来判断是否需要检查该 COM Port 当 Port 被前面 Dialog 画面 Open 后, 相对应的变量也设定为 TRUE, 所以主循环开始检查是否有通信资料进来, 如果有存入 Buffer 内, 一直检查到 Carriage Return Code(Hex: 0x0D) 表示资料结束, 然后将结果显示于主画面 ListView 上 如果经过约 1 秒时间未接到 0x0D, 此时会显示通信 Time out 讯息 如果一次通信资料超过 Buffer 最大长度 256 Bytes, 也会显示 Buffer Over 的讯息 此 Thread 展示通信逾时 Buffer Size 不足 以特殊定义的 Code 来当作

341 一段资料的结束等通信设计的技巧, 这些都是非常重要的观念, 请读者细心 体会 UINT RS232ThreadProc( LPVOID lpparam ) // Get a THREAD_INFO pointer from the // parameter that was passed in. CDemo_01Doc* pdoc; CString msg; int i, step_1, step_2, cnt_1, cnt_2, timecnt_1, timecnt_2; char bf_1[256], bf_2[256]; short ch; TCHAR Tdata[256]; Sleep(3000); THREAD_INFO *lpthreadinfo = (THREAD_INFO *) lpparam; pdoc= (CDemo_01Doc *)lpthreadinfo->hwnd; msg.format(_t("rs232 Thread Start")); pdoc->displayitem(2, 1, msg); msg.format(_t("rs232 Thread Start")); pdoc->displayitem(3, 1, msg); step_1= step_2= 0; cnt_1= cnt_2 = 0; timecnt_1= timecnt_2= 0; memset(bf_1, 0, sizeof(bf_1)); memset(bf_2, 0, sizeof(bf_2)); while(1) // COM2 Port Read data if(m_com2) switch(step_1) case 0: // 等待第一个 Byte 资料进来 ch= serrecv(2); if(ch!= -1) // 不是 1 时, 表示接到资料, 此时为第一个 Byte

342 cnt_1= 0; bf_1[cnt_1++]= (unsigned char )ch; step_1= 10; // 转到下一个步骤处理 timecnt_1= 0; memset(tdata, 0, sizeof(tdata)); break; case 10: // 继续等待后面的资料 ch= serrecv(2); if(ch== -1) // 未有资料进来时, 就要检查时间是否逾时 timecnt_1++; // 因为循环最后会 Sleep(10), 每次当超过 100 次, 约等于 1 秒时间 if(timecnt_1 >100) step_1= 0; msg.format(_t("com2 Read timeout")); pdoc->displayitem(2, 1, msg); else if((cnt_1+1) >=sizeof(bf_1)) // 如果长度超过 Buffer Size step_1= 0; msg.format(_t("com2 Read Buffer over")); pdoc->displayitem(2, 1, msg); else if(ch!= 0x0D) msg.format(_t("%d %04X"), cnt_1, ch); pdoc->displayitem(2, 3, msg); bf_1[cnt_1++]= (unsigned char )ch; else

343 msg.format(_t("com2 Data receive")); pdoc->displayitem(2, 1, msg); msg.format(_t("data length=%d"), cnt_1); pdoc->displayitem(2, 3, msg); // 接到的资料为 ASCII Code, 于 WinCE 必须转换成 Double Bytes Code 形式 for(i=0; i<255; i++) Tdata[i]= bf_1[i]; msg.format(_t("%s"), Tdata); pdoc->displayitem(2, 2, msg); step_1= 0; break; // COM3 Port Read data if(m_com3) // 此内容与 COM2 Port 做法一样, 只是控制变量为第二组 Sleep(10); return 0; 3.4 RS232C 通信子程序说明 : RS232C 通信程序设计与通信模块硬件息息相关, 首先要了解 Baud Rate Parity Data Bit Stop Bit 等通信系数如何设定, 然后如何输出或读取通信资料等 此方面 Windows 及 WinCE 系统提供许多 RS232C 通信相关的系统 API 让使用者很容易设计此类程序 本书将这些 API 再进一步整理设计一系列的子程序存于 RS232_SUB.cpp 原始档内 此子程序有详尽的使用下列系统 API 的设计方式, 请读者详加研读 此子程序也提供读者一个非常便利的方式, 只要使用者则利用下表的数个 Function 也就可以做 RS232C 通信程序设计 CreateFile() GetCommState() SetCommState() GetCommTimeouts()

344 SetCommTimeouts() CloseHandle() WriteFile () ReadFile () PurgeComm () ClearCommError () 等 RS232C 通信相关的 Windows System API RS232C 通信子程序 功能说明 Seropen Open COM Port Serclose Close COM Port Serwrite 传送指定长度的资料 Sersend 传送一个 Byte serrecv 接收一个 Byte serread 接收指定长度的资料 serflush 清除系统的 Buffer serloc 检查系统的 Buffer 尚有的 Bytes 数 RS232C 通信程序设计必须了解资料与系统 系统与硬件之间流程, 才能设计完善的程序 在此说明主要步骤, 以让读者有一个清晰的概念 当透过系统 API 设定各项通信系数完毕后, 系统会准备输出及输入等两个 Buffer 应用程序需要传送资料时, 经过 WriteFile () API 将资料送出 实际上是系统首先将资料转存入输出 Buffer, 然后再一次一个 Bytes 转送出去 当外部系统有资料传入时 首先经过通信模块接收, 转存入输入 Buffer 应用程序使用 ReadFile () 将资料读入 所以读者要清楚是以系统 Buffer 为中接口作资料交换, 硬件的操控都透过系统 API 完成 尤其于 seropen() 子程序内首先使用 CreateFile() open port 后, 然后以 GetCommState() SetCommState() GetCommTimeouts() SetCommTimeouts() 等 API 作所有通信系数 这些步骤有些复杂, 请读者注意, 还有此设计方式与 Windows 系统相同, 也就是相同原始程序 4 TCP/IP 网络的设计技巧 : TCP/IP 基本上为 Client/Server 架构 Server 方面为被动等待 Client 的 Request Message, 然后依据 Message 内容作数据处理后, 回传 Response Message 给 Client Client 方面则是主动连接远程的 Server, 发出 Request Message 等待响应, 然后接收回传的 Response Message 所以此 Demo 分成两大部分, 一为 Thread 的激活后, 以 TCP/IP 的 listen() accept() API 等待 Remote Client 的 connect 另一为 Dialog 画面设定要连接外部 Server 的 IP Address Port No. 等, 然后使用 connect() API 作远程 Server 连接, 再传送资料出去

345 4.1 系统初期设定 : 于 Windows 或 WinCE 系统要使用网络的 API 必须做下列系统的初期设定, 程序结束也必须作 Close 以 Demo_01 程序为例, 此两步骤是设计于 CDemo_01Doc 的 Class 内, 当 Class 产生时作初期设定,Class 删除时作 Close 处理 以上处理非常重要, 有些设计者常常忘记这些步骤 程序片断如下 : CDemo_01Doc::CDemo_01Doc() // TODO: add one-time construction code here WSADATA WSAData; // Contains details of the Windows // Sockets implementation CString msg; if (WSAStartup (MAKEWORD(1,1), &WSAData)!= 0) msg.format(_t("wsastartup failed. Error: %d"), WSAGetLastError ()); AfxMessageBox(msg, MB_OK); CDemo_01Doc::~CDemo_01Doc() WSACleanup (); 4.2 TCP/IP Dialog 画面 : 由主画面 Menu [Demo Function][TCP/IP Demo] 激活如下 Dialog 画面 输入 IP Address 及 Port No., 按 [Connect] Button 连接 Remote Server 如果成功会显示 Connect OK 讯息 如果对方不存在则会等待约 秒后, 系统回传 Error Code, 然后显示 Can t Connect 讯息 于 Data Send 的 Edit 字段输出任何文字, 再按 [Send Data] 就会将此字段文字传送出去 此程序使用的系统 API 有 coonect() 作连接 send() 作资料传送

346 图 11-14:Demo_01 程序的 TCP/IP Dialog 画面 4.3 TCP/IP Server Thread 程序 : Thread 程序一开始使用 socket() bind() listen() accept() 等 API 设定为 Server 状态, 然后进入主循环以 recv() 等待外部 Client 的连接 当外部连接后 recv() 由系统得到资料也回传状态, 此时可能有三种状态 一为接到正确资料, 此时 recv() 回传正值, 表示资料的 Bytes 数 一为 recv() 回传值为零, 表示 Client 已经 disconnect, 所以程序再 accept() 等待另一个 client 作 connect() 另一为 recv() 回传值 SOCKET_ERROR 表示系统有错误 请读者注意整体步骤的建立 接收的资料及各种状态会显示于主画面 ListView 上 UINT TCPThreadProc( LPVOID lpparam ) // Get a THREAD_INFO pointer from the // parameter that was passed in. CDemo_01Doc* pdoc; CString msg; int type, protocol, sin_len, err, i; long set; SOCKET socket_hd, socket_cln; // Socket Handle SOCKADDR_IN sin, ssin; char data[1024];

347 TCHAR Tdata[1024]; Sleep(3000); THREAD_INFO *lpthreadinfo = (THREAD_INFO *) lpparam; pdoc= (CDemo_01Doc *)lpthreadinfo->hwnd; msg.format(_t("tcp/ip Thread Start")); pdoc->displayitem(4, 1, msg); type = SOCK_STREAM; // TCP/IP protocol = 0; // always 0 socket_hd = socket( PF_INET, type, protocol ); memset( (char *)&sin, 0, sizeof(sin) ); sin.sin_family = AF_INET; // INADDR_ANY is a 32bits zero value sin.sin_addr.s_addr = htonl(inaddr_any); sin.sin_port = htons( 9600); // TCP Port No bind(socket_hd, (struct sockaddr *)&sin, sizeof(sin) ); listen( socket_hd, 32 ); // listen 32 coming socket sin_len= sizeof(ssin); socket_cln = accept(socket_hd, (struct sockaddr *)&ssin, &sin_len ); msg.format(_t("remote connect socket ID=%d"), socket_cln); pdoc->displayitem(4, 1, msg); // Set Block mode for recv() set = 0; ioctlsocket(socket_cln, FIONBIO, (unsigned long*) &set); // 可以接收一次一个 remote client connect // 当此 remote client disconnect 后可以再接收另一个 client while(1) memset(data, 0, sizeof(data)); memset(tdata, 0, sizeof(tdata)); err= recv(socket_cln, (char *)data, sizeof(data),0 ); if(err== SOCKET_ERROR) msg.format(_t("received data error=%d"), err); pdoc->displayitem(4, 1, msg);

348 closesocket(socket_cln); //close socket else if(err==0) msg.format(_t("remote disconnect socket ID=%d"), socket_cln); pdoc->displayitem(4, 1, msg); socket_cln = accept(socket_hd, (struct sockaddr *)&ssin, &sin_len ); msg.format(_t("remote connect socket ID=%d"), socket_cln); pdoc->displayitem(4, 1, msg); // Set Block mode for recv() set = 0; ioctlsocket(socket_cln, FIONBIO, (unsigned long*) &set); else msg.format(_t("data length=%d"), err); pdoc->displayitem(4, 3, msg); for(i=0; i<1023; i++) Tdata[i]= data[i]; msg.format(_t("%s"), Tdata); pdoc->displayitem(4, 2, msg); return 0;

349 第十二章 :WinCON 8000 基本 API 功能的运用 1 实时日期 时间的取入 : 日期 时间对于实时自动控制系统是非常重要的数据 尤其是 Millisecond 更是关键的所在 我们于上一章 Demo_01 程序已经展示使用系统 API GetLocalTime() 取入日期及时间 于 Windows 系统此 API 也可以取入 Millisecond, 但是于 WinCE 系统则此值为 0 泓格的 WinCON 8000 的 WinCONSDK Library 提供一个 GetTimeTicks() Function 可以取入系统开机至目前为止所经过的时间, 以 Millisecond 为单位 此值为 unsigned long, 当此值 Overflow 时会归零, 再重新累积时间值 所以我们可以利用此 Function 再加上一些技巧的得到我们所要的 Millisecond 值 本书提供如下的程序 : static int MINUTE, SECOND, NSEC, OLD_NSEC=-1, INIT_FLAG=-1; static DWORD OLD_MS; void get_nsec_time(void); /* */ /* get system tick and set NSEC SECOND */ /* */ void get_nsec_time(void) DWORD ms, max; unsigned char *cptr; int delta; ms= GetTimeTicks(); if(init_flag==-1) // initial set, when program start OLD_MS= ms; NSEC= 0; SECOND= 0; MINUTE= 0; INIT_FLAG=0; return; if(ms >= OLD_MS) delta= (int)(ms - OLD_MS); else cptr= (unsigned char *)&max;

350 *(cptr)= 0xFF; *(cptr+1)= 0xFF; *(cptr+2)= 0xFF; *(cptr+3)= 0xFF; delta= (int) ((max- OLD_MS) + ms); NSEC= NSEC + delta; if(nsec >= 1000) SECOND= SECOND + NSEC/1000; if(second >=60) SECOND= SECOND - 60; MINUTE= MINUTE+1; if(minute >= 60) MINUTE= MINUTE - 60; NSEC= NSEC % 1000; OLD_MS= ms; 2 模块信号处理 : 泓格 I/O Module 分成三种类 : 串行式连接 Ethernet 网络连接 Parallel Bus(Build in I/O) 连接等 其中串行式速度最慢,Ethernet 网络次之,Parallel Bus 最快 此三种架构的 I/O Module 都有适用的场合, 因应所规划的信号响应速度, 选择所需的种类 但是在此必须提醒的一点是 Parallel Bus I/O, 具有非常快速的信号反应, 又因为与主机在同一个设备上共享同一电源, 所以没有串行式及 Ethernet 网络等两种 I/O, 可能发生网络断线或电源断线等故障 也就是 Parallel Bus I/O 的速度及稳定度远大于其它两种 I/O, 这是在规划一个控制系统架构时, 必须考虑的要点 而不要迷信只有 Ethernet I/O 就可以取代一切信号点的做法 另外网络 I/O 尚有 CAN(Control Area Network) Bus 与泓格自有的 FRNet 透过 Modbus/RTU CANOpen DeviceNet 等通信规约, 可以与泓格的 I-7000 I-8000 及其它厂牌的 CAN I/O 相连接, 而且 FRNet 则是沿用了 PLC 架构的优点, 采用 Token Ring 的方式, 无须软件协议, 在固定的时间内可以扫描所有的 I/O 点, 再透过 Dual-Port RAM 的方式与主控制层做沟通

351 于 I/O Module 的通信 : 串行式连接特定的通信规约, 与泓格的 I-7000 I-8000 等方式都一样 Ethernet 网络连接使用 Modbus TCP 通信规约, 此为业界公认的标准方式 Parallel Bus(Build in I/O) 连接, 由 WinCON 8000 直接控制, 所以泓格提供 WinCONSDK Library 作接口 本书重点将介绍此 Library 的使用 于 WinCON 8000 的安装磁盘, 安装于 PC Windows 系统完成后的目录 \DAQPro\Wincon 内, 有 AI_AO_Demo 及 DI_AO_Demo 等两个 Demo 程序, 一些基本用法可以参考此程序 但是使用上还有一些关键点, 于 Demo 程序未能展示之处, 此为本章节的重点所在, 请读者必须详细了解 2.1 系统基本 API: 介绍一些主要的 API, 程序都必须考虑到, 才能成为真正实际的应用系统, 而不只是练习用或 Demo 的程序而已 模块型号检查 :GetNameOfModule( int slot, char *str) slot: 编号 1-7 str: 回传 Module 名称字符串 Return value: 传回 Module ID No., 此为 Module No. 的最后两位数值 此 API 最大的使用于应用程序激活后, 读取每个 slot 的 Module No., 然后与程序内部设定的 I/O Module 检查是否相符 我们想象一下, 每个不同的应用系统, 都会规划不同的 I/O Module, 而且程序设计时就会设定完成 但是实际上使用 WinCON 8000 控制器时, 很可能发生 I/O Module 插错 Slot 的事情, 应用程序激活后必须作此检查处理 取入控制器序号 :GetSystemSerialNumber(char *str) str: 回传 WinCON 8000 控制器的硬件序号, 共有 8 bytes Return value:0 表示成功 1 表示失败 因为控制器序号是唯一的, 可以延伸出有许多用途 例如 : 一个系统中有许多控制器配备相同的 I/O Module, 应用程序也是相同, 只是分别控制某一个分区, 可以用序号来作管理上的区分 如果一种应用程序希望锁定于某一台控制器, 当程序被复制至另一台控制器后就不能执行, 就可以以序号作判断 WinCE 系统及 WinCONSDK 等版本 :GetOSversion(char *str) GetSDKversion(char *str) str: 回传软件版本字符串 由版本判定应用程序是否适合此系统 因为应用程序会随着控制器而更新, 当用到系统内新的 API, 就必须检查本控制器是否为新的 WinCE 及 SDK Library EEPROM Read/Write Function:ReadEEP(int block, int offset) WriteEEP(int block, int offset, unsigned char data) block:block no. offset: 该 block 内的第几个 byte data: 要写入的 byte 值 每次可以读取或写入一个 byte WinCON 8000 配有 16 KB 的 EEPROM 内存, 分成 256 block 编号为 block no., 每个 block 有 64 byts 编号为 0 63 offset 此为内

352 存于 Power off 后还是会保存, 而不是一般的档案, 于档案管理程序看不到, 不能以一般的档案处理方式来设计 非常适合用来储存一些特殊的资料, 因为不会被粗心的使用者以一般档案方式删除之 例如 : 密码 最主要的设定系数 等等 2.2 AI/AO 模块 : 以 I-8017H 为代表 AI 以 I-8024 为代表 AO 对于模拟信号的处理, 必须考虑到信号的种类及范围 现场控制信号有温度 压力 流量 速度.. 等等种类, 都以各种信号感应器 (Sensor) 转换成为电器信号, 然后再接入 AI/AO Module, 最后计算机系统经由通信取入电器信号值, 再经过转换计算成为温度 压力 等等实际值 ( 详细计算方式于下章讨论 ) 此处先解说计算机系统如何读取或写入电器信号的基本方式 主要关键点是要搞清楚所使用的模块设定在何种电器信号值, 然后才能换算为正确的信号值, 但是实际使用状况非常的多, 常常忽略此要点而以为是程序有问题, 造成许多不必要的困扰 AI/AO 模块电器信号表如下 : AI/AO Module Gain Signal Range I-8017H AI Module ~ V ~ +5.0 V ~ +2.5 V ~ V ~ ma I-8024 AO Module Voltage ~ V Current -0.0 ~ ma AI Module 读取 : 使用此模块前必须先以 Init_I8017H(slot) 作一次初期设定 AI 模块信号为模拟数值, 此数值随时会有些变动, 不像 DI/DO 模块只有 On/Off 两种状态, 非常的清楚 当 AI 数值有随时变动时, 我们当然不希望取到某一个瞬间值, 比较没有代表性可言 所以 AI 模块会有取样次数的设定, 然后再得到此平均值, 如此较具代表性 然后以下列两种方式做读取处理 第一种方式 : 单次取入, 首先作电器信号 Gain 的设定, 然后依据电器信号别或十六进制方式使用相配的 API int slot; // range 1 ~ 7 int channel; // range 0 ~ 7 int gain; // range 0 ~ 4 如同上表 int polling; // 0: normal polling mode float fdata;

353 short sdata; // save HEX value for(i=1; i<=7; i++) Init_I8017(i); // 于程序激活时作一次初期设定即可 slot= 2; // 设定为第 2 slot channel= 3; // 设定为第 3 channel gain= 0; // 设定为 10.0 ~ V polling= 0; // 0: normal polling mode // 设定电器信号 gain Set_8017H_Channel_Gain_Mode(slot, channel, gain, polling); fdata=get_ad_fvalue(gain); // 取入电压值 gain= 4; // 设定为 20.0 ~ ma Set_8017H_Channel_Gain_Mode(slot, channel, gain, polling); fdata=get_ad_ivalue(); // 取入电流值 // 电器信号都换算为十六进制值 0x00 ~ 0x3FFF sdata=get_ad_hvalue(); // 取入十六进制值 第二种方式 : 多次取入, 首先设定并取入此多次的值, 然后依据电器信号 Gain 转换后, 再自行平均之, 得到较正确的信号值 int slot; // range 1 ~ 7 int channel; // range 0 ~ 7 int gain; // range 0 ~ 4 如同上表 int datacnt; // 取入信号次数 int idata[20]; // 瞬间信号十六进制 buffer float fdata[20]; // 瞬间电器信号 buffer float fl; // 换算完成信号值 slot= 2; // 设定为第 2 slot channel= 3; // 设定为第 3 channel gain= 0; // 设定为 10.0 ~ V datacnt= 10; // 设定取入 10 次瞬间信号值 I8017H_AD_POLLING(slot, channel, gain, datacnt, idata); ARRAY_HEX_TO_FLOAT_ALL(idata, fdata, slot, gain, 10); for(n=1, fl=0.0; n<9; n++) fl= fl + fdata[n]; fl= fl / (float)8.0; // 取入中间八次平均值

354 2.2.2 AO Module 输出 : 使用此模块前必须先以 I8024_Initial(slot) 作一次初期设定 然后以下列两种方式做输出 第一种方式 : 以 float 数值输出, 此值既为上表提到的 Signal Range I8024_VoltageOut(slot, channel, data) 电压信号输出 I8024_CurrentOut(slot, channel, data) 电流信号输出 int slot; // range 1-7 int channel; // range 0-3 float data; slot= 2; // 设定为第 2 个插槽 channel= 3; // 设定为第 3 channel data= 5.0; // 设定输出数值 // 使用电压输出为 5.0 V I8024_VoltageOut(slot, channel, data); // 使用电流输出为 5.0 ma I8024_CurrentOut(slot, channel, data); 第二种方式 : 以十六进制数值输出, 数值 Range 为 0x00 ~ 0x3FFF 对应至 10.0 ~ 10.0 V 或者 0.0 ~ 20.0 ma I8024_VoltageHexOut(slot, channel, data) 电压信号输出 I8024_CurrentHexOut(slot, channel, data) 电流信号输出 int slot; // range 1-7 int channel; // range 0-3 short data; slot= 1; // 设定为第 1 个插槽 channel= 0; // 设定为第 0 channel // 使用电压输出为 5.0 V 要先转换为十六进制数值再输出 data= (5.0 (-10.0)) / (10.0 (-10.0)) * (0x3FFF 0x00) // 得到 取整数转换成十六进制为 0x2FFF I8024_VoltageHexOut(slot, channel, data); // 使用电流输出为 5.0 ma 要先转换为十六进制数值再输出 data= (5.0 0) / (20.0 0) * (0x3FFF 0x00) // 得 取整数转换成十六进制为 0x0FFF I8024_CurrentHexOut(slot, channel, data);

355 所输出的数值也可以使用下列四种 API 将值读回 此用途为当程序停止后再次执行, 此时程序内部变量都为零, 必须先读取此 AO 数值, 作为起始值 另一种状况 WinCE 为 Multi-Task 操作系统, 可能会有其它程序也对此 AO 作输出, 所以程序最好每次 Scan AO 值, 以保证程序内变量为最新值 I8024_VoltageOutReadBack(slot, channel) I8024_CurrentOutReadBack(slot, channel) I8024_VoltageHexOutReadBack(slot, channel) I8024_CurrentHexOutReadBack(slot, channel) 2.3 DI/DO 模块 : 泓格提供多种的 DI DO 模块, 有些模块尚且为 DI/DO 两种混合形式, 让使用者有最大的组合运用空间, 非常不错的安排 针对各种形式模块, 也提供一系列的 API, 必须注意的一点每种模块要 配合相对应的 API 才能得到所要的结果, 兹整理表格如下 : 模块型号 DI/DO 种类 所使用 API I8040 DI:32 DI_32() I8041 DO:32 DO_32() I8042 DI:16 DO:16 DIO_DO_16() for DO DI_16() for DI I8050 DI:16 DO:16 DIO_DO_16() for DO DI_16() for DI I8051 DI:16 DI_16() I8052 DI:8 DI_8() I8053 DI:16 DI_16() I8054 DI:8 DO:8 DIO_DO_8() for DO DI_8() for DI I8055 DI:8 DO:8 DIO_DO_8() for DO DI_8() for DI I8056 DO:16 DO_16() I8057 DO:16 DO_16() I8058 DI:8 DI_8() I8060 DO:6 DO_8() I8063 DI:4 DO:4 DIO_DO_8() for DO DI_8() for DI I8064 DO:8 DO_8() I8065 DO:8 DO_8() I8066 DO:8 DO_8() I8068 DO:8 DO_8() I8069 DO:8 DO_8() API 使用方式及资料形式 :

356 DI 读取 API 传送系数 Slot No. 为 1-7, 然后 Return code( 回传值 ) 就是所得到的该模块全部 channel 数值 DO 输出 API 是传送系数 Slot No. 为 1 7 以及输出值 无论是 DI 回传值或者 DO 输出值, 都必须配合 DI 做适当的资料型态宣称如下, 而且为 unsigned type 才能用到最左边的 bit DI-8 unsigned char DI-16 unsigned short DI-32 unsigned long 实际使用例如下 : unsigned char cbyte; unsigned short sdata; unsigned long ldata; cbyte= DI_8(1); sdata= DI_16(2); ldata= DI_32(3); cbyte= 0x10; DO_8(1, cbyte); DIO_DO_8(2, cbyte); sdata= 0x0507; DO_16(2, sdata); DIO_DO_16(3, sdata); ldata= 0x00100F0F; DO_32(3, ldata); DO 输出的注意事项 : 由以上 API 的说明, 我们知道无论 DI 或 DO 模块都是对所有 channel 一次读取或输出 于 DI 模块一次读取是非常便利, 但是对 DO 模块一次全部输出, 就会产生不合理的现象 试想我们每次只要对某一个 channel 作 On/Off 设定, 但是不影响其它 channel 的状态, 以上面的 API 就无法做到这一点, 所以必须先行读回 DO channel 所有数值, 然后设定所要的 channel 的 On/Off, 最后再将数值输出 以下例子说明 DO-32 的设计方式, 至于 DO-16 DO-8, 还是以 GetDIOData32(slot) 取入数值, 只是取入对应位置的数值即可 unsigned long ldata; unsigned char bf[10]; int chnl; // channel no.

357 int dd; ldata= GetDIOData32(slot); // 取入 DO channel value // copy 入 buffer, 必须注意 High/Low word, High/Low byte 位置的考虑 memcpy(bf, (unsigned char *)&ldata, 4); dd= 1; if(chnl>=0 && chnl<=7) Coil_Bit_Write(&bf[0], chnl, dd); // 设定对应 bit 的 On/Off else if(chnl>=8 && chnl<=15) Coil_Bit_Write(&bf[1], chnl-8, dd); else if(chnl>=16 && chnl<=23) Coil_Bit_Write(&bf[2], chnl-16, dd); else if(chnl>=24 && chnl<=31) Coil_Bit_Write(&bf[3], chnl-24, dd); memcpy((unsigned char *)&ldata, bf, 4); DO_32(slot, ldata); // 输出整个模块 DO 数值 如何设定某一个 Bit On/Off:C 语言处理资料最小单位为 Byte, 虽然也提供对 bit 处理的计算子 (Operand), 但是对于指定位置 bit 的处理还是不大方便, 以下提供 Read/Write bit 的子程序, 供读者参考使用 int Coil_Bit_Read(unsigned char* data, int bitno) unsigned short data2; if(bitno <0 bitno>=8) return(-1); data2= *((unsigned short *)data); data2 <<= (15-bitno); data2 >>= 15; return((int) data2); int Coil_Bit_Write(unsigned char* data, int bitno, int onoff) unsigned short mask2, patt2; if(bitno <0 bitno>=8) return(-1); if(onoff<0 onoff>1) return(-1);

358 mask2= (unsigned short )0xFFFF << (15-bitno); mask2 >>= 15; mask2 <<= bitno; patt2= onoff; patt2 <<= bitno; patt2 &= mask2; *((unsigned short *)data) &= ~mask2; *((unsigned short *)data) = patt2; return(0);

359 第十三章 : 自动控制系统基本发展平台以往 PC Based 控制器都是采用 DOS 操作系统, 属于 Single Task 方式 于泓格 WinCON 8000 控制器则是采用最先进的 WinCE 操作系统, 此为 Multi-Task 的方式,CPU 速度也增加许多,WinCON 8000 实际上可以等于 IPC+PLC 的双重系统 所以本书提供一个实际的自动控制系统 (Demo8000 程序 ), 除了展示 WinCON 8000 强大的功能外, 也说明如何以 evc++ 语言设计 WinCE 程序, 同时介绍自动控制系统许多基本原理 最后该 Demo Program 也是实际可用的系统, 此程序已经处理所有的基本 I/O 信号输出入 控制计算 对外通信等基本功能, 读者只要修改其中一条特殊控制功能子程序, 就可以变成某一个自动控制应用系统 希望藉此程序能够让读者得到了解自动控制原理 WinCON 8000 系统功能 如何设计自动控制程序以及获得一个基础系统以缩短往后设计时程等好处 1 系统基本规格 1.1 系统功能说明 : 整体系统分成两大程序 : 一为 WinCE 程序做信号取入 对外通信 控制计算等功能 另一为 Windows Utility 程序, 此程序于 PC 执行, 做各种系数设定 下载系数 信号通信设定等功能, 当 WinCE 程序执行控制时, 可以由此 Utility 程序做各种操作修改 连接泓格 Parallel Bus 系列的 I/O 模块 信号依据 Modbus 规格方成四大种类 :Output Coil(DO) Input Coil(DI) Holding Register(AI) Input Register(AO) 等四种 此四种信号除了硬件的 I/O 信号外, 还包含多种类的内部信号, 于下面章节详细说明 所有信号点依据 Modbus 通信规约, 以 TCP/IP 方式对外通信 此通信方式为 Modbus Slave, 可以同时连接 8 个外部 Modbus Master 做资料交换 提供 Modbus Master(COM Port) 联机功能, 可以连接许多 Modbus Device, 例如 : 电表 仪表 控制器. 等多种工控设备 数值模拟信号 (AI AO) 有信号原始值以及工程单位的转换处理 数值模拟信号 (AI AO) 有上上限 上限 下限 下下限等四种警报方式 状态信号 (DI DO) 包含 AI AO 警报超限点, 以纪录警报最新状态 状态信号 (DI DO) 包含 Pulse ON Pulse OFF 的检查, 可以得知信号由 On 变成 Off 或者 Off 变成 On 的瞬间状态 提供与 PLC 相同功能的 Timer Counter 处理功能 WinCE 程序由 evc 开发工具设计,Utility 由 Borland C++ Builder 5.0 开发工具设计

360 1.2 系统架构图 WinCE 自动控制程序 Demo8000 程序内各 Thread 及画面读写 COMMON 变量 信号资料 通信系数 控制计算 Thread Modbus Slave Thread 提供本系统 信号资料 (TCP/IP) Ethernet PC WindowsUtility 程序 WinCON8000_A Modbus Master Thread 与外部设备 通信 (RS232) On-Line 画面直接 通信读写信号 AI/AO 信号 设定画面 Off-Line 设定信号系数画面, 产生 Demo8000.TBL 供 WinCE 程序使用 DI/DO 信号 设定画面 Timer/Counter 设定画面

361 2 以 Modbus 规格定义 I/O 信号点 : Modbus 为工控界最被广泛使用的信号规范, 通信规约也几乎是工控业界的标准 以 Modbus 规格来定义本系统, 可以与绝大部分的工控系统作整合 2.1 I/O 信号种类 : Output Coil: 也称做 Digital Output, 简称 DO 表示输出信号的 On 或 Off 地址最大范围: Input Coil: 也称做 Digital Input, 简称 DI 表示输入信号的 On 或 Off 地址最大范围: Holding Register: 也称做 Analog Output, 简称 AO 表示模拟输出信号值 以 16 bits 表示一个整数值 地址最大范围 : Input Register: 也称做 Analog Input, 简称 AI 表示模拟输入信号值 以 16 bits 表示一个整数值 地址最大范围 : 内部使用信号点 : 以上四种信号都安排各式内部计算结果或者暂存的信号点 此种信号也是符合 Modbus 规格, 可以与外部系统通信 也被称做 Internal Relay 及 Internal Register 或者 Soft I/O 信号点等 信号地址范围 : 以上所谓的地址最大范围是指 Modbus 所能定义的最大地址数 但是本 Demo 系统实际使用的地址范围详如 2.2. 表格所示 如果通信超过实际地址会收到地址超出范围的错误码 (Exception Code) Modbus 是以一个 16 bits integer 表示地址, 此 unsigned integer 最大值为 各信号点及系数定义架构 : 此 Demo 系统各种信号点以 Modbus 定义各信号点如下面四个表格所示 信号名称 说明 Modbus 地址范围 点数 DI Bare 硬件信号点 DI Soft 内部信号点 DI PLON Plus On DI PLOFF Plus Off 表格 3-1:Input Coil 信号表 要点说明 : 硬件信号 512 点 以 Utility 定义实际所插入或联机 WinCON 8000 的 DI 模块, 然后存入所设定的地址 内部信号 1024 点 可存入程序内部计算结果 Plus On Plus Off 计算结果共 1536 点, 含硬件及内部信号点

362 信号名称 说明 Modbus 地址范围 点数 DO Bare 硬件信号点 DO Soft 内部信号点 AI HH AI 超上上限 AI H AI 超上限 AI L AI 超下限 AI LL AI 超下下限 AO HH AO 超上上限 AO H AO 超上限 AO L AO 超下限 AO LL AO 超下下限 TM BIT Timer Contact Bit CN BIT Counter Contact Bit DO PLON Plus On DO PLOFF Plus Off DO INIT DO Initial Value DI INIT DI Initial Value 表格 3-2:Output Coil 信号表 要点说明 : 硬件信号 512 点 以 Utility 定义实际所插入或联机 WinCON 8000 的 DO 模块, 然后存入所设定的地址 内部信号 1024 点 可存入程序内部计算结果 AI HH - AI LL 为存入 AI 信号点计算警报超限的结果 On 表示警报 发生,Off 表示警报消失 AO HH - AO LL 为存入 AI 信号点计算警报超限的结果 On 表示警 报发生,Off 表示警报消失 Timer Contact Bit 此为 Timer 计算结果的接触点 On 表示此 Timer 计时 已经到时 (Time Up) Counter Contact Bit 此为 Counter 计算结果的接触点 On 表示此 Counter 累积计次已经到目标值 (Count Up) Plus On Plus Off 计算结果共 6912 点, 含以上所有的信号点 硬件 内部 AI HH - AI LL AO HH - AO LL TM Bit CN Bit 等信号 点 DO Initial Value 为程序激活时的 DO 初期值 DI Initial Value 为程序激活时的 DI 初期值

363 信号名称 说明 Modbus 地址范围 点数 AI Bare 硬件信号点 AI ENG 转换成工程值 AI Soft 内部信号点 表格 3-3:Input Register 信号表 要点说明 : 硬件信号 128 点 以 Utility 定义实际所插入或联机 WinCON 8000 的 AI 模块, 然后存入所设定的地址 工程值 (Engineer data) 存入信号原始值 (Bare data) 转换计算的结果 内部信号 512 点 可存入程序内部计算结果 所有信号都以 float 来表示, 此为 Modbus 特殊用法, 详细说明参考 综 合要点 信号名称 说明 Modbus 地址范围 点数 AO Bare 硬件信号点 AO ENG 转换成工程值 AO Soft 内部信号点 AI RH AI 工程值范围的最大值 Range high AI RL AI 工程值范围的最小值 Range low AO RH AO 工程值范围的最大值 Range high AO RL AO 工程值范围的最小值 Range low AI BH AI 信号原始值范围的最大 值 Bare high AI BL AI 信号原始值范围的最小值 Bare low AO BH AO 信号原始值范围的最大 值 Bare high AO BL AO 信号原始值范围的最小 值 Bare low AI FL AI 一次滤波系数 Low pass filter AO FL AO 一次滤波系数 Low pass filter 128

364 AI HH AI 警报上上限值 Alarm High High Limit Value AI H AI 警报上限值 Alarm High Limit Value AI L AI 警报下限值 Alarm Low Limit Value AI LL AI 警报下下限值 Alarm Low Low Limit Value AI DEAD AI 警报不感带百分率值 Dead Value AO HH AO 警报上上限值 Alarm High High Limit Value AO H AO 警报上限值 Alarm High Limit Value AO L AO 警报下限值 Alarm Low Limit Value AO LL AO 警报下下限值 Alarm Low Low Limit Value AO DEAD AO 警报不感带百分率值 Dead Value AO INIT AO 信号 Initial Value AI INIT AI 信号 Initial Value TM PSET Timer 目标值 Preset Value TM CURR Timer 目前值 Current Value CN PSET Counter 目标值 Preset Value CN CURR Counter 目前值 Current Value 表格 3-4:Holding Register 信号表 要点说明 : 硬件信号 128 点 以 Utility 定义实际所插入或联机 WinCON 8000 的 AO 模块, 然后存入所设定的地址 工程值 (Engineer data) 存入信号原始值 (Bare data) 转换计算的结果 内部信号 512 点 可存入程序内部计算结果 AI RH - AI RL 为转换 AI 硬件信号的工程值范围 AO RH - AO RL 为转换 AO 硬件信号的工程值范围 AI BH - AI BL 为 AI 硬件信号的原始值范围 AO BH - AO BL 为 AO 硬件信号的原始值范围 AI FL - AO FL 为硬件信号经过工程值转换后, 以此值做噪声的滤波

365 计算 AI RH - AI DEAD 为计算 AI 警报所用的警报范围及不感带值 AO RH - AO DEAD 为计算 AO 警报所用的警报范围及不感带值 AI INIT - AO INIT 为程序激活时, 对 AI AO 等工程值及内部 Soft 信号点的 Initial Value TM PSET - TM CURR 为计算 Timer 所要用的目标值及目前值 CN PSET - CN CURR 为计算 Counter 所要用的目标值及目前值 Timer 及 Counter 相关信号以 16 bits integer 来表示, 其它所有信号都以 float 来表示, 此为 Modbus 特殊用法, 详细说明参考 综合要点 综合要点 : 此处表格先列出信号种类, 详细各种信号的意义会于下面章节说明 以上信号点表格 : 信号名称为 Demo 程序内部使用的变量名 Modbus 地址范围为外部 Modbus 通信 Driver 以 Modbus Master 与 Demo 程序通信时, 使用此地址就可以取到相对应的信号值 点数为 Demo 系统所提供各种信号的点数 所有关于 AI AO 的信号值都是以 4 bytes float 来表示, 此与一般 Modbus Register 以 16 bits integer 有所不同 因为一个信号值占用 4 bytes 此会用到 2 个 Modbus 地址 所以这些信号的地址都必须为奇数, 因为下一个偶数地址已经被占用 3 数值 Analog 信号处理 : 所谓数值 Analog 模拟信号是用来表示有数值的信号点 举例来说 : 温度 压力 湿度 饲料量 转速 风速 重量 含氧量 CO2 量 档版开度. 等等各种仪器或仪表数值 不同的信号测量需要不同的传感器 (Sensor) 做测量, 然后转换成电器模拟信号, 再由 AI/O 模块接收此模拟信号, 再转换成数值以利计算机系统接收, 最后换算成实际的工程值, 配合所测的仪表点换算成 oc KG RPM Ton/Hour % g/cm2 等等单位 以下各点详细说明各处理步骤的原理

366 3.1 信号转换步骤图 : 现场信号源 信号传感器 I/O 模块 WinCON8000 信号控制器 接收或输出电器信号 RS485 Parallel Bus 通信 WinCON8000 换算成 工程单位值 图 3 2:Analog 信号转换步骤图 AI 信号由现场信号源开始一路转换至 WinCON 8000 内的工程值 AO 信号刚好相反步骤, 由 WinCON 8000 设定 AO 工程值后一路转换至信号控制器, 然后控制某一个设备操作 3.2 电器信号种类说明 : 所转换的电器信号有许多种类, 必须详细参考 I/O 模块的规格书 最常见到的有 :0 10V V 0 5V -5 5V 0 20mA 4 20mA 等 目前最常用的是 4 20mA, 因为 ma 可以传送长距离信号传送不会衰减, 以 4 ma 表示信号点最小值, 当信号值小于 4 ma 就表示信号可能断线 所以目前绝大部分的信号都以 4 20 ma 为标准信号 3.3 模拟转换数字值的分辨率 : 当电器信号接到 AI/O 模块后, 就模拟与数字的转换 AI 信号由模拟转换为数字, 此为 A/D Converter AO 信号由数字转换为模拟, 此为 D/A Converter 此 Converter 的转换就有模拟对应到数字时, 以多少 Bits 来表示的规格 此种规格称做分辨率 (Resolution), 所对应 Bits 越多表示精确度越高 一般使用 12 bits 此最大值为 4095 最高使用 16 bits 此最大值为 但是也有一些 I/O 模块分辨率为自行规定, 例如 :4 20 ma 的数字范围为 某一种温度数字范围为 而且预设小数一位, 所以对应的温度值为 oc 此分辨率为非常重要的系数, 读者必须详查各种 I/O 模块的规格书, 此会影响所转换成工程值的精确度 例如 : 有一个重量信号范围为 KG 必须精确到小数点一位, 此时使用分辨率必须 以上, 所以 12 bits 绝对达不到要求,13 bits 还差一点 ( 最大值为 8191), 起码必须 14 bits 以上

367 可, 其最大值为 AI AO 信号值与工程值的转换 : 信号原始值 (Bare data or Raw data) 与工程值 (Engineer data) 的转换处理, 为转换公式的计算 如下图所示的对应关系, 以 4 20 ma 信号, 节析度 4095(12 bits), 换算成 oc, 对应关系的公式线性为例 : ma 20 4 解析度 图 3 3:Analog 电器信号与数字分辨率对应图 解析度 工程單位 图 3 4:Analog 数字分辨率与工程单位对应图 RH: 工程值范围最大值 Range High RL: 工程值范围最小值 Range Low BH: 分辨率范围最大值 Bare High BL: 分辨率范围最小值 Bare Low EH: 电器信号范围最大值 Electric Range High EL: 电器信号范围最小值 Electric Range Low AI 信号换算处理由 ma 分辨率 工程单位首先由 ma 转换成分辨率, 此步骤是由 AI 模块的 A/D Converter 所完成 例如 : 信号值为 16 ma 时, 分辨率为 3071, 也就是计算机系统所取到的值 换算公式 (( 目前信号值 -EL)/(EH-EL))*(BH-BL)+BL= 目前分辨率本例子的计算

368 (( 目前信号值 - 4)/ (20-4))*(4095-0)+ 0 = 3071 然后由所得到的分辨率值, 再换算成工程值 换算公式 (( 目前分辨率 -BL)/(BH-BL))*(RH-RL)+RL= 目前工程值本例子的计算 (( 目前分辨率 - 0)/ (4095-0))*( )+20= oc 由此例子我们来了解分辨率的重要性 首先 16 ma 换成百分比为 75%, 也就是说此值位于 4 20 ma 线性的 75% 的位置 我们以下表来说明各种分 辨率的误差范围 分辨率 Bit 数 分辨率最大值 16mA 分辨率值 反算后百分率 表格 3-5: 分辨率精确度比较表 由上表我们知道分辨率 Bit 数越高其转换后精确度越大, 越接近 75% 的 真正值 但是还是有些许误差, 这是因为模拟信号经过数字转换后一定被舍 弃小数以后的值, 所造成的结果 例如 :4095 * 75% = 只取入 3071 AO 信号换算处理由工程单位 分辨率 ma 首先由计算机系统将工程值换算成分辨率, 通信输出至 AO 模块换算公式 (( 目前工程值 -RL)/(RH-RL))*(BH-BL)+BL= 目前分辨率本例子的计算 (( )/ ( ))*(4095-0)+0= 3070 最后再由 AO 模块经过 D/A Converter 转换成模拟信号输出换算公式 (( 目前分辨率 -BL)/(BH-BL))*(EH-EL)+EL= 目前信号值本例子的计算 (( 目前分辨率 - 0)/ (4095-0))*(20-4)+ 4 = ma 由以上 ma 值, 而不是 16mA 可知, 这也是数字转换分辨率的

369 误差, 无法得到真正的 16mA 在此读者一定要有此种误差存在的观念, 于 系统规划时, 我们只是评估使用何种分辨率的 AI/O 模块, 所产生的误差是 可以被接受的 3.5 一次滤波处理 : 电器信号都有噪声的干扰, 于 I/O 模块都会做此方面的处理 本系统再提供一种便利的滤波方式, 称做一次滤波 (Low Pass Filter) 此种处理方式为信号由 AI 模块取入换算成工程值后, 再做一次滤波计算, 公式如下 : 最新工程值 = 最新取入值 * Filter + 前次工程值 *(1.0 - Filter) Filter 为 之间的系数 例如 : 最新取入值为 oc 前次值为 oc Filter Value 为 * * ( ) = oc 由上面公式可以知道, 还会包含前次信号值, 所以可以缓和本次所接到信号值突然变化很大的干扰 当 Filter Value 越小时, 所包含的前次值越大, 所以得到的最新值变化越小 每个信号 Filter Value 都不会相同, 必须于现场实际调整之 最为便利的调整法就是使用趋势图 (Trend Display), 大多数的监控软件包都会提供趋势图功能 当 Filter Value 设为 1.0 就是只用到最新取入值不做任何滤波处理, 此时所显示的趋势图与将 Filter Value 设为 1.0 以下的趋势图会有不同的曲线显示, 我们可以看到有做滤波处理的趋势图, 其信号值的变化趋势较为明显, 而不会有信号突然升高或降低的现象 当信号是用来作控制对象时, 减少突然噪声的干扰是非常重要的一件事, 才不会产生失控的现象 3.6 内部数值信号应用 : Demo 系统内除了硬件信号的 I/O 值外, 也需要与硬件无关, 只提供程序内部使用的信号点, 此称做内部数值信号点 (Internal Register or Soft Register) 此种信号也安排为 Modbus I/O 定义, 所以可以与外部监控系统通信, 如此资料的交换, 外部系统就可以各种控制的操作与选择 一般控制操作, 都是以外部监控系统将所要控制的值, 经过通信设定入内部信号值, 然后再由控制系统内部运算后, 输出至硬件 I/O 模块上, 避免直接设定至硬件 Modbus I/O 信号点上, 如此设计方式才能有多样性的控制 读者谨记此种观念, 内部信号点的运用非常的重要 3.7 警报超限检查 : 警报处理大部分都使用两段的方式, 超出上限或下限称为警告性警报, 一般以黄色表示 超出上上限或下下限称为严重性警报, 一般以红色表示 警报计算非常简单只要大于上限 上上限或小于下限 下下限值, 就是警报发生 只要位于警报限值范围内就是警报恢复 一般控制会利用警报发生或恢复的瞬间, 做一些特殊控制 ( 如何判断瞬间的发生, 请参考 Plus On/Off

370 的章节说明 ) 但是有一种状况必须考虑,AI/O 信号于警报限值附近时, 可能本次取入的最新值刚好超限, 下次取入值又低于警报限值, 如此会产生警报突然发生突然恢复的跳动现象 如此情况不利于警报的控制, 所以用不感带来缓和此种现象 不感带英文称做 Dead Zone 或者 Hysterics 原理如下图所示 : 信号值 警报发生 警报上限 不感带值 警报恢复 不感带值 警报下限 图 3 4: 警报不感带处理示意图 时间 当信号值大于警报上限值时, 表示警报发生, 但是当信号值必须小于上限不感带值以下才断定警报恢复 当警报已经发生 ( 超出警报限值 ), 信号值于上限与不感带值之间, 还是当作警报发生中 不感带值以百分率来表示, 一般为 % 之间, 由使用者设定之 实际值再以此百分率乘以信号工程范围值 公式如下 : 不感带值 = 不感带百分率 * (RH - RL) 举例计算 : RH:100.0 RL:20.0 AH:95.0 Dead%:5.0% Dead Value:5.0% * ( ) = 4 当信号大于 95.0 时警报发生 一直到信号值小于 91.0( ) 才算警报恢复 4 状态 Digital 信号处理 : 4.1 DI DO 信号点 : DI DO 信号状态为 On/Off 两种, 可以由一个 bit 来表示 举例来说 : 开关 马达运转 Limit Switch 警报点. 等等运转或控制信号 只有 On/Off 所以没有分辨率的问题 一般 DI/O 模块都以 等点数规格提供, 于计算机系统程序内可以用 bit 来表示, 但是一般程序处理变量的最小单位为 Byte, 于 C 语言最好的用法是宣称为 unsigned char, 注意必须为 unsigned 表示最前面一个 1 bit( 由右边算起 ) 不会表示为正负值 由信号点 Address

371 所对应的 bit 如下图所示 : 程序宣称 Output Coil 为 unsigned char DO[128], 共计 1024 点 (128 * 8) DO[0] DO[1] : : : : DO[127] 图 3 5: 状态 Digital 信号资料安排示意图 4.2 延伸性状态信号点 : Pulse ON Pulse OFF: 当一个 On/Off 信号产生变化时, 会有两种情形 : 由 Off 变成 On 称做 Pulse ON 由 On 变成 Off 称做 Pulse Off 此两种延伸性状态信号非常重要, 许多操作或控制的起点都是此信号的发生而开始的 我们由前面第 2 点小节的 Modbus 规格定义 I/O 信号点的内容安排, 可以将 DI/O 分成下列数种, 最后都安排有 Pulse ON Pulse OFF 使用之 DI DO Bare: 硬件信号的输出入, 例如有一点 DI 信号接到一个操作 Switch, 当被打开的瞬间就是 Pulse ON, 被关闭的瞬间就是 Pulse OFF, 所以程序就可以判断 Switch 的操作变化而做控制 又例如有一点 DO 信号为控制马达运转, 当被 ON 的瞬间就是 Pulse ON 也是马达开始运转, 被 OFF 的瞬间就是 Pulse OFF 也就是马达停止运转 DI DO Soft: 内部使用状态信号点, 例如有一点信号对应至画面一个操作 Button, 当被按下的瞬间就是 Pulse ON, 离开 Button 的瞬间就是 Pulse OFF 程序得到此信号的变化就可以执行所设计的功能 AI/O 的警报点 : 当 Analog 信号超出或者低于警报限值时, 程序都会于相对应的位置设定某一个警报 Bit,On 为已超限,Off 为未超限 当警报发生的瞬间就是 Pulse ON, 警报恢复的瞬间就是 Pulse OFF 我们常常会检查警报产生或恢复时, 要做一些控制操作, 就可以利用此状态来判断 Timer,Counter 的状态 : 当 Timer,Counter 到达的瞬间就是 Pulse ON

372 Pulse ON/OFF 的信号只有于信号产生变化的一瞬间才会产生, 也就是程序每次取入或计算相关信号点后, 就开始检查 Pluse ON/OFF, 当发生时于 Pulse ON 及 Pulse OFF 相对应变量位置设 ON, 然后供其它子程序使用 特别注意的一点是, 此种信号只是于发生时会设定为 ON, 而程序下一次 Loop 再取入及计算信号时, 因为已经没有变化, 所以此 Pulse ON Pulse OFF 的值会设定为 OFF 程序的判断公式如下 : Pulse ON:( 前次信号值 XOR 本次信号值 ) AND 本次信号值 Pulse OFF:( 前次信号值 XOR 本次信号值 ) AND 前次信号值 Alarm Bit: 当由 AI/AO 信号检查超限结果纪录下列八种此 Alarm Bit 中 AI HH Bit:AI 警报超上上限 AI H Bit:AI 警报超上限 AI L Bit:AI 警报超下限 AI LL Bit:AI 警报超下下限 AO HH Bit:AO 警报超上上限 AO H Bit:AO 警报超上限 AO L Bit:AO 警报超下限限 AO LL Bit:AO 警报超下下限以上信号如果 ON 表示警报发生中,OFF 表示警报未发生对于以上八种 Alarm Bit 都有对应的 Pulse ON/OFF 信号 Pulse ON 时表示警报刚发生 Pulse OFF 时表示警报刚恢复 由以上各种警报信号就可以依据状况做各种控制 Timer Contact Bit: Timer 控制原理是当某一个条件成立时, 指定触动一个 Timer Table 开始计时, 此 Timer Table 由计时目标值 (Timer Preset Value) 计时目前值 (Timer Current Value) 计时结果 bit(timer Contact Bit) 等三种信号组成 本次 Demo 程序规划目标值及目前值为 Modbus Holding Register Type, 计时结果 Bit 为 Modbus Output Coil Type, 如此外部系统可以经由 Modbus 通信规约操作 Timer Table Timer 分成非累积型及累积型两种型式 假设信号如下 ( 依据第 2 点小节的 Modbus 规格定义 I/O 信号点 ) 条件信号 : Address 表示使用 Input Coil Address-1

373 Timer 目标值 :Address 表示使用 Holding Register Address Timer 目前值 :Address 表示使用 Holding Register Address Timer 结果 Bit:Address 表示使用 Holding Register Address 备注信号 Address 为六位数, 其中最前面的数字表示信号的种类 0:Output Coil 1:Input Coil 3:Input Register 4:Output Register 本次 Demo 程序的最小 Timer 单位值为 10 msec 设定 Timer 目标值为 3000 表示为 msec 就是 30 秒 非累积型定时器的控制程序说明如下 : 当条件信号 ON 时, 激活 Timer 计时目前值 开始由零一直累积时间, 当目前值达到目标值时, 将 Timer 结果 Bit 设定 ON 然后程序可以利用此 Bit 做某种控制 如果正计时期间, 变成 OFF, 此时目前值归零不再计时 又计时完成后, 也要继续为 ON 才可以保持计时结果 Bit ON 所以此最基本的控制现象, 就是 ON 后延迟 30 秒后再做所要的控制, 而且 变成 OFF 后, 就不做控制 累积型定时器的控制程序说明如下 : 当条件信号 ON 时, 激活 Timer 计时目前值 开始由零一直累积时间, 当目前值达到目标值时, 将 Timer 结果 Bit 设定 ON 如果于计时期间 变成 OFF 时, 保持已累积的时间, 于 再变 ON 时, 又继续累积一直达到目标值, 将 Timer 结果 Bit 设定 ON 计时完成后, 也要继续为 ON 才可以保持计时结果 Bit ON 所以累积计时可以分成数段进行只要总和达到目标值即可 Timer 控制程序图如下图所示 : 条件信号 秒 目前值 累积计时未完成 Timer Bit 累积计时完成 条件信号 OFF 图 3 6: 非累积型 Timer 程序图

374 总计 30 秒 条件信号 目前值 累积计时继续中 Timer Bit 累积计时完成 条件信号 OFF 图 3 6: 累积型 Timer 程序图 Counter Contact Bit: Counter 控制原理是当某一个条件成立时, 指定触动一个 Counter Table 开始计数, 此 Counter Table 由计数目标值 (Counter Preset Value) 计数目前值 (Timer Current Value) 计数结果 bit(counter Contact Bit) 等三种信号组成 本次 Demo 程序规划目标值及目前值为 Modbus Holding Register Type, 计数结果 Bit 为 Modbus Output Coil Type, 如此外部系统可以经由 Modbus 通信规约操作 Counter Table 假设信号如下 ( 依据第 2 点小节的 Modbus 规格定义 I/O 信号点 ) 条件信号 : Address 表示使用 Input Coil Address-1 Counter 目标值 :Address 表示使用 Holding Register Address Counter 目前值 :Address 表示使用 Holding Register Address Counter 结果 Bit:Address 表示使用 Holding Register Address 计数器的控制程序说明如下 : 每次当条件信号 Pulse ON 时, 激活 Counter 计数目前值 加一, 当目前值达到目标值时, 将 Counter 结果 Bit 设定 ON 然后程序可以利用此 Bit 做某种控制 计数完成后, 继续 Pulse ON 就不再累加计数 举例来说, 此种计数可以计算警报发生次数, 因为每次警报发生就 Pluse ON 一次, 此时计数就加一也就是计算警报发生次数 另一例子, 当某一个

375 Switch 被按下一次就是 Pulse ON 一次, 此时计数一次, 就可以计算此 Switch 被操作的次数 设定 counter 目标值为 5 表示为计数 5 次就达到目标 条件信号 累积计数完成后, 不再累积 目前值 以另一信号来 Reset 计数 累积计数中 Counter Bit 累积计数完成 图 3 7:Counter 程序图 4.3 信号的输出入反应速度 : 一般控制方式都是先取入信号 (DI AI), 然后程序的执行, 最后输出信号 (DO AO) 的步骤, 一再由开始至最后步骤循环执行之, 所以整体控制是被区分成三大阶段来看 当有一个信号于程序执行阶段内发生, 而在程序执行完成前就消失, 如此信号将不会被侦测到有变化, 当然下一次的程序执行不会对此信号有所控制, 此种信号的反应时间非常重要, 读者必须谨记在心 On 此 On 信号 无法抓到 On Off 此 Off 信号 无法抓到 I 程序执行 O I 程序执行 O I 程序执行 O I I 表示信号取入 O 表示信号输出 图 3 7: 信号反应时间图

376 5 Modbus 对外通信功能 5.1 Modbus 的基本概念 : Modbus 的通信方式是采取主动端送出要求命令, 然后被动端响应此要求资料的一去一回通信模式 (Query / Response Cycle) 通信规约主要术语有下列 : 主动端 : 又称做 Master Client 端, 主动定时送出 Request Command (Query Message), 然后等待被动端的响应资料 主动端 : 又称做 Slave Server 端, 随时等待主动端的 Request Command, 然后依据 Command 内容整理响应资料 Response Message 传送回去 通信接口 : 通信硬件包含 RS232 RS485 等异步通信及同步通信, 以及 Ethernet 的 TCP/IP 通信 资料格式 : 对于 Request Command 及 Response Message 都有规定资料格式 (Data Format), 所以通信程序才可以依据此格式, 解读其中的意义, 做必要的数据处理 格式的重要字段如下所示 : Modbus Address: 表示每一台设备的编号, 必须于网络上唯一的 Function Code: 各种功能码, 表示处理不同的资料程序, 于 5.2 章节说明 资料区 :Output Coil Input Coil Holding Register Input Register 等各种信号的资料 检查码 : 使用 RS232 RS422 等方式通信, 需要加上 LRC, CRC 等检查码, 使得对方收到 Message 后再计算一次, 如果检查码不相同, 表示传送 Message 过程中, 资料可能被噪声干扰 此种处理以确保通信讯息内容 如果使用 Ethernet TCP/IP 通信方式, 则检查码由 TCP/IP 层次做处理, 不用于 Modbus 内规定 通信方式 : 本书依据 WinCON 8000 具有的 COM Port 及 Ethernet Port 等两种通信埠, 所以将介绍 RS232 RS485 的异步通信及 TCP/IP 通信方式 其中异步通信又分成 RTU 及 ASCII 等两种资料格式 总计 RTU ASCII TCP 等三种通信方式 信号地址表示法 : 此为必须特别注意一点 于 Modbus 文件规格上说明四种信号点地址如下所示 : 但是 Modbus 资料格式内部地址计算为, 只取后 5 位数的 offset, 而且要减一, 就是由 0 开始算起 Output Coil 地址以 开始表示 Input Coil 地址以 开始表示 Holding Register 地址以 开始表示

377 Input Register 地址以 开始表示 Modbus Message 长度 : 任何一种资料格式, 其 Message 最大长度 规定为 256 bytes 所以本书所提供的 API 一次读写的最大信号点 数如下表所示 : 信号种类 RTU ASCII TCP Output Coil Input Coil Holding Register Input Register 表格 3-6:Modbus 一次通信最大信号点数表 5.2 Modbus 主要通信功能 (Function Code): 以下要点先说明所用到的 Modbus function Code 基本意义, 至于详细用法本书提供一个 DLL 可以做 Modbus Message Data Format 的整理, 同时做外部数据通信, 其详细用法后面章节说明 Function -1: 读取 Output Coil 信号点 设定起始地址及所要读取的点数,Modbus Slave 回传该地址开始的连续点数信号值 以 Modbus/TCP 为例一次读取最大点数为 1920 点 Function -2: 读取 Input Coil 信号点 设定起始地址及所要读取的点数,Modbus Slave 回传该地址开始的连续点数信号值 以 Modbus/TCP 为例一次读取最大点数为 1920 点 Function -3: 读取 Hodling Register 信号点 设定起始地址及所要读取的点数,Modbus Slave 回传该地址开始的连续点数信号值 以 Modbus/TCP 为例一次读取最大点数为 120 点 (16 bits integer) 或者 60 点 (4 bytes float) Function -4: 读取 Input Register 信号点 设定起始地址及所要读取的点数,Modbus Slave 回传该地址开始的连续点数信号值 以 Modbus/TCP 为例一次读取最大点数为 120 点 (16 bits integer) 或者 60 点 (4 bytes float) Function -5: 设定 Output Coil 一点信号的 On/Off 值 Function -6: 设定 Holding Register 一点信号 16 bits integer 值 Function -15: 设定 Output Coil 连续点数的信号值 On/Off Function -16: 设定 Holding Register 连续点数的信号值 5.3 Modbus 通信子程序 : Modbus 通信程序设计需要含括 RTU ASCII TCP 等三种资料格式转 换 加上检查码 通信埠 Open Close Read Write 通信 Timing 的控制

378 等处理 因此会有许多低阶的程序设计, 本书已经将以上功能整理成一个 MBTool.Dll, 读者只要使用本书所提供的 API, 就可以很简便的设计 Modbus 通信系统, 请善加利用 API 列表如下 : API 名称 Modbus Master RTU Format MBRTUInit MBRTU_R_Coils MBRTU_W_Coil MBRTU_R_Registers MBRTU_W_Register MBRTU_W_Multi_Coils MBRTU_W_Multi_Registers MBRTUClose ASCII Format MBASCInit MBASC_R_Coils MBASC_W_Coil MBASC_R_Registers MBASC_W_Register MBASC_W_Multi_Coils MBASC_W_Multi_Registers MBASCClose TCP Format MBTCPInit MBTCP_R_Coils 功能说明 RTU Format 通信开始处理 RTU Format Fun-1 2 读取连续点数 Output/Input Coil RTU Format Fun-5 设定一点 Output Coil 信号值 RTU Format Fun-3 4 读取连续点数 Holding/Input Register RTU Format Fun-6 设定一点 Holding Register 信号值 RTU Format Fun-15 设定连续多点 Output Coil 信号值 RTU Format Fun-16 设定连续多点 Holding Register 信号值 RTU Format 通信结束处理 ASC Format 通信开始处理 ASC Format Fun-1 2 读取连续点数 Output/Input Coil ASC Format Fun-5 设定一点 Output Coil 信号值 ASC Format Fun-3 4 读取连续点数 Holding/Input Register ASC Format Fun-6 设定一点 Holding Register 信号值 ASC Format Fun-15 设定连续多点 Output Coil 信号值 ASC Format Fun-16 设定连续多点 Holding Register 信号值 ASC Format 通信结束处理 TCP Format 通信开始处理 TCP Format Fun-1 2 读取连续点数

379 MBTCP_W_Coil MBTCP_R_Registers MBTCP_W_Register MBTCP_W_Multi_Coils MBTCP_W_Multi_Registers MBTCPClose Output/Input Coil TCP Format Fun-5 设定一点 Output Coil 信号值 TCP Format Fun-3 4 读取连续点数 Holding/Input Register TCP Format Fun-6 设定一点 Holding Register 信号值 TCP Format Fun-15 设定连续多点 Output Coil 信号值 TCP Format Fun-16 设定连续多点 Holding Register 信号值 TCP Format 通信结束处理 表格 3-7:Modbus Master API Function 一览表 API 名称 Modbus Slave 功能说明 RTU Format SLRTUInit RTU Format 通信开始处理 SLRTU_Wait_Query RTU Format 等待 Request Command SLRTU_Set_Coils RTU Format 回传 Fun-1 2 读取连续 Output/Input Coils 信号点数的 Response Message SLRTU_Set_Registers RTU Format 回传 Fun-3 4 读取连续 Holding/Input Reisters 信号点数的 Response Message SLRTU_Get_Coils RTU Format 取入 Fun-5 15 所要设定的 Output Coil 信号值, 代入程序内部变量, 然后回传 Response Message SLRTU_Get_Registers RTU Format 取入 Fun-6 16 所要设定的 Holding Register 信号值, 代入程序内部变量, 然后回传 Response Message SLRTU_Send_Exception RTU Format 当所要求 Request Command 不符合规定时, 要回传 Exception Code SLRTUClose RTU Format 通信结束处理 ASCII Format SLASCInit ASC Format 通信开始处理 SLASC_Wait_Query ASC Format 等待 Request Command

380 SLASC_Set_Coils ASC Format 回传 Fun-1 2 读取连续 Output/Input Coils 信号点数的 Response Message SLASC_Set_Registers ASC Format 回传 Fun-3 4 读取连续 Holding/Input Reisters 信号点数的 Response Message SLASC_Get_Coils ASC Format 取入 Fun-5 15 所要设定的 Output Coil 信号值, 代入程序内部变量, 然后回传 Response Message SLASC_Get_Registers ASC Format 取入 Fun-6 16 所要设定的 Holding Register 信号值, 代入程序内部变量, 然后回传 Response Message SLASC_Send_Exception ASC Format 当所要求 Request Command 不符合规定时, 要回传 Exception Code SLASCClose ASC Format 通信结束处理 TCP Format SLTCPInit TCP Format 通信开始处理 SLTCP_Wait_Query TCP Format 等待 Request Command SLTCP_Set_Coils TCP Format 回传 Fun-1 2 读取连续 Output/Input Coils 信号点数的 Response Message SLTCP_Set_Registers TCP Format 回传 Fun-3 4 读取连续 Holding/Input Reisters 信号点数的 Response Message SLTCP_Get_Coils TCP Format 取入 Fun-5 15 所要设定的 Output Coil 信号值, 代入程序内部变量, 然后回传 Response Message SLTCP_Get_Registers TCP Format 取入 Fun-6 16 所要设定的 Holding Register 信号值, 代入程序内部变量, 然后回传 Response Message SLTCP_Send_Exception TCP Format 当所要求 Request Command 不符合规定时, 要回传 Exception Code SLTCPClose TCP Format 通信结束处理 表格 3-8:Modbus Slave API Function 一览表 以上的 API 分成 Matser/RTU Master/ASCII Master/TCP Slave/RTU Slave/ASCII Slave/TCP 等六大区域 每一区域的 API 使用都是大同小异,

381 本书以 Slave/TCP 及 Master/RTU 作为范例说明详细用法, 于 6.4 及 6.5 章节说明如何使用此两种 API 设计 Modbus 通信程序 Slave/TCP 就是展示 Demo 程序内所定义的各种信号点, 如何透过 Modbus 通信与外部系统交换资料 Master/RTU 就是展示如何以 COM Port 连接外部的 Modbus 设备, 例如 : 电流表 温度计 PID 控制器 等等, 以读写相关信号点 详细 API 系数以及 Return Code 的定义, 请参考本书所附光盘片内 MBDLLTool.h 的 Head File 内 5.4 Modbus Slave 通信程序 : Demo 程序以 Modbus/TCP 设计一个 Modbus Slave Thread, 以提供外部系统透过 Ethernet 通信, 就可以读写 2.2 章节所定义 Demo 程序所使用的各种信号点 当 Demo 程序激活后就会 Create 一个 Thread 专门等待外部系统的 Modbus Request Command, 然后依据要求整理 Response Message 回传 此程序 Source 为 ModbusThread.cpp Thread 程序名为 UINT TCPSlaveThreadProc( LPVOID lpparam ) 首先说明所使用的 API 详细用法 : int SLTCPInit(int tcpipport); 通信开始的第一步, 建立相关的 TCP/IP Socket 如果为 RTU 或 ASCII 就是 Open COM Port 等 API 系数说明 : tcpipport:modbus IP Port No. Modbus 所规定的 Port No. 为 502 本次 Demo 程序使用 503, 因为 WinCON 8000 另有系统提供 Modbus Slave 接口会使用 502 在此特别提醒一点的是, 当外部系统要作通信时, 必须定义相同的 Port No. 才能够通信 int SLTCP_Wait_Query (int *islavenumber, int *istartaddress, int *icount, int *ifuncnumber); 等待外部系统所传来的 Request Command, 如果没有 Command 则程序会一直 Wait 在此步骤, 直到有 Command 此 API 才会 return, 此时 API 会解读 Command 内容, 然后将各种字段拆解后存入 API 系数内 API 系数说明 : islavenumber:modbus Address 也称做 Modbus ID No. istartaddress,: 所要求信号点的起始位置 icount: 所要求的信号点数 ifuncnumber: 所要求的 Function Code 本次 API 提供 等八种 Function Code

382 int SLTCP_Has_Query(int *islavenumber, int *istartaddress, int *icount, int *ifuncnumber); 因为 Modbus/TCP 可以连接多数个 Modbus Client, 所以可能一次会接到多数个 Request Command, 所以必须设计一个检查机制, 以检验是否还有 Request Command 存在, 如果有则处理后回复 Response Message, 没有则回到 SLTCP_Wait_Query 等待 API 系数说明 : 如同 SLTCP_Wait_Query int SLTCP_Set_Coils(unsigned char *coil); 当外部系统使用 Fun-1 2 要求读取 Output/Input Coil 的资料时, 程序先将每一点信号依照起始位置, 由程序内部信号变量取入, 然后依序存入 coil 变量内, 此 coil 变量宣称为 unsigned char, 就是表示每 1 个 byte 可以存入每 8 点 digital 信号 ( 此观念说明参考 4.1 章节 ) 如何由 1 个 byte 读或写其中的 1 个 bit 的方法, 请见后面 Coil_Bit_Read Coil_Bit_Write 两个 API 使用说明 使用者只要信号值存入 coil 变量内后, 其它 Modbus 资料格式转换, 通信输出都由 API 内部处理完成 API 系数说明 : coil: 所要传送 Output/Input Coil 信号值存入 Array 变量 int SLTCP_Set_Registers(unsigned short *reg); 当外部系统使用 Fun-3 4 要求读取 Holding/Input Register 时, 程序先将每一点信号值存入 reg 的变量内, 此为 16 bit integer 的 Array 变量 也就是信号值依序存入 reg Array 变量内, 其它 Modbus 资料格式转换, 通信输出都由 API 内部处理完成 API 系数说明 : reg: 所要传送的 Holding/Input Register 信号值存入 Array 变量 int SLTCP_Get_Coils(unsigned char *coil); 当外部系统使用 Fun-5 15 要求写入 Output Coil 一点或多点连续地址信号点时, 此 API 已经将所要写入的每一点信号值转换存入 coil Array 变量内 每 1 个 byte 可以存入每 8 点 digital 信号 API 系数说明 : coil: 所要设定信号值存入 Array 变量 int SLTCP_Get_Registers(unsigned short *reg); 当外部系统使用 Fun-6 16 要求写入 Holding Register 一点或多点连续地址信号点时, 此 API 已经将所要写入的每一点信号值转换存入 reg Array 变量内

383 API 系数说明 : reg: 所要设定信号值存入 Array 变量 int SLTCP_Send_Exception(unsigned char code); 当外部系统所要求的信号地址可能超出范围, 或者 Function Code 不是 等 依照 Modbus 规定必须回复 Exception Code, 其中 1 表示 function code 不合法 (illegal function code),2 表示信号地址不合法 (illegal data address) 此 API 会转换通信资料格式及通信输出 API 系数说明 : code: 所要传送的 Exception Code void SLTCPClose(void); 不再使用通信时 Close 通信埠用 int Coil_Bit_Read(unsigned char *coil, int bitno); 读取 1 个 byte 内, 其中 1 个 bit 的值 API 系数说明 : coil: 所要读取的变量 bitno:bit 的地址,0 7 内, 由右边算起 return code: 所读取该 bit 值,0 或 1 int Coil_Bit_Write(unsigned char *coil, int bitno, int onoff); 写入 1 个 byte 内, 其中 1 个 bit 的值, 而且其它 bit 值维持原状 API 系数说明 : coil: 所要写入的变量 bitno:bit 的地址,0 7 内, 由右边算起 onoff: 所写入该 bit 值,0 或 1 此 Slave 程序重点是激活 Thread 后, 先以 SLTCPInit 设定 Modbus IP Port, 同时 Open Socket, 做 TCP/IP 网络通信的准备 再进入 While Loop 等待外部 Request Command 的到来, 如果尚未有 Command 则程序一直停留于 SLTCP_Wait_Query API 内, 有 Command 则回到 Thread 内取入 Command 内容, 依据 Function Code 做不同数据处理后, 回复 Response Message 最后处理完成回到等待下一个 Command

384 Modbus/TCP Slave 主架构图 Thread 激活 各种初期值设定 SLTCPInit(503) 进入等待 Message While Loop SLTCP_Wait_Query() 接到 message 检查还有 Message While Loop SLTCP_Has_Query() 還有 message 依据 fun code 做不同的处理 : 检查信号地址超出范围及 fun code 合理性 合理, 整理 Reponse Message 送出不合理传送 Exception Code 检查是否还有 Message While Loop end 进入等待 Message While Loop end Thread End SLTCPClose() 图 3 7:Modbus/TCP Slave Thread 流程图

385 以下为程序主要片段, 于每个重要步骤都有批注说明, 完整程序请参考光盘片内 Demo Program UINT TCPSlaveThreadProc( LPVOID lpparam ) int i, rc, rc1, fun, id, addr, count, max; int bytes, bitno, m, n; unsigned char coil[256], *cptr, *cptr_1; // 储存 Register 信号值内部变量 short reg[256], *iptr; // 储存 Coil 信号值内部变量 unsigned short val; //Modbus 预设 IP Port 为 502, 本次 Demo 使用 IP Port 为 503 rc= SLTCPInit(503); while(1) // 等待 Request Command 的 While Loop // 有 Message 此 API 才会 return rc=sltcp_wait_query(&id, &addr, &count, &fun); if(rc==mb_rtc_ok) // 表示接到 Message 为 Modbus 资料格式 while(1) // 检查是否还有 Request Command 的 While Loop rc1= SLTCP_Has_Query(&id, &addr, &count, &fun); if(rc1!= MB_TCP_HAS_DATA) break; // 已经没有 Message switch(fun) // 依据 Fun. Code 分别处理 case 1: // Read Output Coil // set I/O data, send back to Modbus Master max= DO_INIT_PTR + DD_MAX_BIT*3; // 检查信号地址是否合理 if(addr>max (addr+count)>max) // 不合理回复 illegal addr. SLTCP_Send_Exception(0x02); break; memset(coil, 0, sizeof(coil)); // 设定 Common 变量内 DO 起点 cptr= Coil.DO_BARE; for(i=0; i<count; i++)

386 // 计算每一点信号所存变量位置 Byte and Bit no. bytes= (addr+i)/8; bitno= (addr+i)%8; // 取入该信号值 rc= Coil_Bit_Read((cptr+bytes), bitno); // 计算要传送位置 Byte and Bit no. m= i/8; n= i%8; Coil_Bit_Write(&coil[m], n, rc);// 存入该信号值 // 信号值整理完成后, 通信输出 SLTCP_Set_Coils(coil); break; case 2: // Read Input Status 处理方式与 case 1 相似 break; case 3: // Read Holding Register // set I/O data, send back to Modbus Master max= CN_CURR_PTR + CT_MAX_CASE; // 检查信号地址是否合理 if(addr>max (addr+count)>max) SLTCP_Send_Exception(0x02); // illegal addr. break; // 本 Demo 程序有使用 float 变量 // it must even address when TAG is float if((addr%2)!=0 && addr<tm_pset_ptr) SLTCP_Send_Exception(0x02); // illegal addr. break; // it must two words per TAG when it is float if((count%2)!=0 && addr<tm_pset_ptr) SLTCP_Send_Exception(0x02); // illegal addr. break;

387 memset(reg, 0, sizeof(reg)); // 设定 Common 变量内 AO 起点 cptr= (unsigned char *)Reg.AO_BARE; cptr_1= (unsigned char *)&val; for(i=0; i<count; i++) m= addr*2+i*2; // 计算信号于 AO 变量内位置 memcpy(cptr_1, (cptr+m), 2); reg[i]= val; // 存入该信号值 // 信号值整理完成后, 通信输出 SLTCP_Set_Registers((unsigned short *)reg) break; case 4: // Read Input Register 处理方式与 case 3 类似 break; case 5: // Write Output Coil one bit 处理方式与 case 15 类似 break; case 6: // Write Holding Register one words 处理方式与 case 16 类似 break; case 15: // Write Output Coil multi-bit max= DO_INIT_PTR + DD_MAX_BIT*3; // 检查信号地址是否合理 if(addr>max (addr+count)>max) SLTCP_Send_Exception(0x02); // illegal addr. break; memset(coil, 0, sizeof(coil)); SLTCP_Get_Coils(coil); // 取入所要设定的信号值 // 设定 Common 变量内 DO 起点 cptr= Coil.DO_BARE; for(i=0; i<count; i++) // 计算每一点所要设定信号位置 Byte and Bit no. m= i/8; n= i%8; rc= Coil_Bit_Read(&coil[m], n);

388 // 计算每一点信号所存 Common 变量位置 Byte and Bit no. bytes= (addr+i)/8; bitno= (addr+i)%8; if(rc==0) Coil_Bit_Write((cptr+bytes), bitno, 0); else Coil_Bit_Write((cptr+bytes), bitno, 1); break; case 16: // Write Holding Register multi-words max= CN_CURR_PTR + CT_MAX_CASE; // 检查信号地址是否合理 if(addr>max (addr+count)>max) SLTCP_Send_Exception(0x02); // illegal addr. break; // 本 Demo 程序有使用 float 变量 // it must even address when TAG is float if((addr%2)!=0 && addr<tm_pset_ptr) SLTCP_Send_Exception(0x02); // illegal addr. break; // it must two words per TAG when it is float if((count%2)!=0 && addr<tm_pset_ptr) SLTCP_Send_Exception(0x02); // illegal addr. break; memset(reg, 0, sizeof(reg)); // 取入所设定的信号值 SLTCP_Get_Registers((unsigned short *)reg); if(addr<tm_pset_ptr) // it is float TAG iptr= (short *)Reg.AO_BARE; for(i=0; i<count; i++)

389 m= (addr+i); *(iptr+m)= reg[i]; else // it is timer or counter TAG iptr= Reg.TM_PSET; for(i=0; i<count; i++) m= (addr+i)- TM_PSET_PTR; *(iptr+m)= reg[i]; break; default: SLTCP_Send_Exception(0x01); // illegal fun. break; // while(has_data) // while(1) SLTCPClose(); return 0; 5.5 Modbus Master 通信程序 : Demo 程序以 Modbus/RTU 设计一个 Modbus Master Thread, 以提供连接外部 Modbus 设备, 例如 : 电流表 温度计 仪器 等等 此种设备大都具备 Modbus/RTU 的通信接口 当 Demo 程序激活后就会 Create 一个 Thread 专门主动对外通信, 送出 Request Command, 然后依据回传的 Response Message 取得所要的电流值, 温度值等, 存入程序变量内 此程序 Source 为 ModbusThread.cpp Thread 程序名为 UINT TCPMasterThreadProc( LPVOID lpparam ) 首先说明所使用的 API 详细用法 : int MBRTUInit(int iportnumber, int ibaudrate,int iparity, int idatabit, int istopbit, int itimeout);

390 通信开始处理,Open COM Port, 设定 baud rate parity data bits stop bits 等异步通信硬件系数 API 系数说明 : iportnumber: 所要 Open COM Port No. ibaudrate: 通信速率 bps iparity:parity check None Even Odd 等三种 idatabit:7 8 bits per byte 两种 istopbit:1 或 2 bit itimeout: 通信逾时, 单位 msec 例如:3000 表示 3 秒 于 Modbus Master 主动送出 Request Command 后, 就开始计时, 如果超过此时间尚未完成 Response Message 的接收, 表示通信逾时断线 int MBRTU_R_Coils(int iportnumber, int islavenumber, int istartaddress, int icount, unsigned char *irecv, int ifuncnumber); 读取 Output/Input Coil 连续地址的信号值, 此 API 会整理出 Request Command 资料格式, 加上检查码, 然后通信输出 API 系数说明 : iportnumber: 所要通信的 COM Port No. islavenumbe:modbus 设备的 Address or ID No. istartaddress: 所要读取的信号起始位置 icount: 所要读取的信号点数 irecv: 接到 Response Message 后, 将各信号值整理至此变量内 每 8 个信号点 (bit) 放在 1 个 byte 内 ifuncnumber:fun-1: 读取 Output Coil Fun-2: 读取 Input Coil int MBRTU_W_Coil(int iportnumber, int islavenumber, int icoiladdress, int icoilstatus); 当要设定一点 Output Coil 信号值时使用, 此为 Fun-5 API 系数说明 : iportnumber: 所要通信的 COM Port No. islavenumbe:modbus 设备的 Address or ID No. icoiladdress: 所要设定的信号位置, 只有一点 icoilstatus: 信号值 1 表示 On 0 表示 Off int MBRTU_R_Registers(int iportnumber, int islavenumber, int istartaddress, int icount, short *irecv, int ifuncnumber); 读取 Holding/Input Register 连续地址的信号值, 此 API 会整理出 Request Command 资料格式, 加上检查码, 然后通信输出

391 API 系数说明 : iportnumber: 所要通信的 COM Port No. islavenumbe:modbus 设备的 Address or ID No. istartaddress: 所要读取的信号起始位置 icount: 所要读取的信号点数 irecv: 接到 Response Message 后, 将各信号值整理至此变量内 每 1 个信号为 16 bit integer 值 ifuncnumber:fun-3: 读取 Holding Register Fun-4: 读取 Input Register int MBRTU_W_Register(int iportnumber, int islavenumber, int iregaddress, short iregstatus); 当要设定一点 Holding Register 信号值时使用, 此为 Fun-6 API 系数说明 : iportnumber: 所要通信的 COM Port No. islavenumbe:modbus 设备的 Address or ID No. iregaddress: 所要设定的信号位置, 只有一点 iregstatus:1 个 16 bit integer 值 int MBRTU_W_Multi_Coils(int iportnumber, int islavenumber, int icoiladdress, int icount, unsigned char *icoilstatus); API 系数说明 : iportnumber: 所要通信的 COM Port No. islavenumbe:modbus 设备的 Address or ID No. icoiladdress: 所要设定的信号起始位置 icount: 所要设定的信号点数 icoilstatus: 所要设定信号值 将连续地址的信号值依序设定于此 Array 变量内 每 8 个信号点 (bit) 放在 1 个 byte 内 int MBRTU_W_Multi_Registers(int iportnumber, int islavenumber, int iregaddress, int icount, short *iregstatus); API 系数说明 : iportnumber: 所要通信的 COM Port No. islavenumbe:modbus 设备的 Address or ID No. iregaddress: 所要设定的信号起始位置 icount,: 所要设定的信号点数 iregstatus: 所要设定信号值 将连续地址的信号值依序设定于此 Array 变

392 量内 每个信号值为 1 个 16 bits integer void MBRTUClose(int iportnumber); 关闭通信 COM Port API 系数说明 : iportnumber: 所要通信的 COM Port No. 此 Slave 程序设计重点为有一组 polling unit 的观念, 首先设定每个 unit 所要通信各种 Function Code 信号地址 点数等系数 激活 Thread 后 首先 MBRTUInit Open COM Port 及设定通信系数 然后进入 While Loop 依据 polling unit 内容, 主动对外通信, 完成一个 unit 后接续下一个 unit 通信, 如此循环通信操作之

393 Modbus/RTU Master 主架构图 Thread 激活 各种初期值设定 进入等待 Message While Loop 决定本次要通信的 Unit 依据 Function code Call 相对应 sub MBRTU_R_Coils() MBRTU_R_Registers() MBRTU_W_Coil() MBRTU_W_Register() MBRTU_W_Multi_Coils() MBRTU_W_Multi_Registers() 然后等待通信回复 message 接到 message 1. Read Fun. 存入相对应的 Relay or Register 的变量内 2. Write Fun. 只检查 Return code 即可 决定下一个通信 Unit Sleep(10) 等待 10 msec. 回到 While Loop 前面, 做下一个 Unit 通信 While Loop 跳出结束时 Thread End MBRTUClose() 图 3 8:Modbus/RTU Master Thread 流程图

394 以下为程序主要片段, 于每个重要步骤都有批注说明, 完整程序请参考光盘片内 Demo Program UINT RTUMasterThreadProc( LPVOID lpparam ) // Get a THREAD_INFO pointer from the // parameter that was passed in. THREAD_INFO *lpthreadinfo = (THREAD_INFO *) lpparam; // AfxMessageBox(_T("RTU Master Thread Start..."), MB_OK); int i, port, idx, rc, val; int bytes, bitno, m, n; int id, fun, req_addr, save_addr, words; CString msg; unsigned char coil[256], *cptr; short reg[256]; // 存入有定义的通信 Unit Index short flag[modbus_master_unit_max]; // search active unit no. for(i=0; i<modbus_master_unit_max; i++) flag[i]= -1; // 先将此值设定为 1, 表示不做通信 for(i=0, idx=0; i<modbus_master_unit_max; i++) if(mb_unit[i].active==1) flag[idx++]= i; // 将有通信 Unit Index 存入 port= MB_PORT.portno; rc= MBRTUInit(port, MB_PORT.baud, MB_PORT.parity, MB_PORT.data_bit, MB_PORT.stop_bit, MB_PORT.timeout); // 先 open COM Port if(rc!= MB_RTC_OK) msg.format(_t("open Modbus RTU Master COM Port error=%d"), rc); AfxMessageBox(msg, MB_ICONEXCLAMATION); return(0); idx= 0; // 开始 Polling index 设为 0, 表示由 flag[0] 第 0 case 开始通信 // 进入主 while loop, 开始一次一个 Unit 的通信 Polling while(1)

395 // 决定本次通信 Unit Index for(i=idx; i<modbus_master_unit_max; i++) if(flag[i]!= -1) idx=flag[i]; break; if(idx >= MODBUS_MASTER_UNIT_MAX) // it is over last unit idx= 0; // return to first unit continue; // 整理本 unit 的 Modbus 参数等 fun= MB_UNIT[idx].fun; id= MB_UNIT[idx].station; req_addr= MB_UNIT[idx].req_addr; words= MB_UNIT[idx].words; save_addr= MB_UNIT[idx].save_addr; // 依据 Function code 做通信处理 switch(fun) case 1: // read output coil rc= MBRTU_R_Coils(port, id, req_addr, words, coil, fun); if(rc==mb_rtc_ok) // 通信成功将每一个 output coil 存入 Common 变量 cptr= Coil.DO_SOFT; for(i=0; i<words; i++) m= i/8; n= i%8; rc= Coil_Bit_Read(&coil[m], n); // 取入每个 Byte 的 bit 值 bytes= (save_addr+i)/8; bitno= (save_addr+i)%8; // 将此 bit 值写入对应 Coil 位置 if(rc==0) Coil_Bit_Write((cptr+bytes), bitno, 0); else

396 Coil_Bit_Write((cptr+bytes), bitno, 1); break; case 2: // read input coil rc= MBRTU_R_Coils(port, id, req_addr, words, coil, fun); if(rc==mb_rtc_ok) // 通信成功将每一个 input coil 存入 Common 变量 cptr= Coil.DI_SOFT; for(i=0; i<words; i++) m= i/8; n= i%8; rc= Coil_Bit_Read(&coil[m], n); // 取入每个 Byte 的 bit 值 bytes= (save_addr+i)/8; bitno= (save_addr+i)%8; // 将此 bit 值写入对应 Coil 位置 if(rc==0) Coil_Bit_Write((cptr+bytes), bitno, 0); else Coil_Bit_Write((cptr+bytes), bitno, 1); break; case 3: // read holding register rc= MBRTU_R_Registers(port, id, req_addr, words, reg, fun); if(rc==mb_rtc_ok) // 通信成功将每一个 holding register 存入 Common 变量 for(i=0; i<words; i++) Reg.AO_SOFT[save_addr+i]= reg[i]; break; case 4: // read input register rc= MBRTU_R_Registers(port, id, req_addr, words, reg, fun); if(rc==mb_rtc_ok) // 通信成功将每一个 input register 存入 Common 变量

397 for(i=0; i<words; i++) Reg.AI_SOFT[save_addr+i]= reg[i]; break; case 5: // write output coil one TAG m= save_addr/8; n= save_addr%8; rc= Coil_Bit_Read(&Coil.DO_SOFT[m], n); val= rc; rc= MBRTU_W_Coil(port, id, req_addr, val); break; case 6: // write holding register one TAG reg[0]= (short)reg.ao_soft[save_addr]; // just send integer value rc= MBRTU_W_Register(port, id, req_addr, reg[0]); break; case 15: // write output coil multi-tag memset(coil, 0, sizeof(coil)); cptr= Coil.DO_SOFT; for(i=0; i<words; i++) m= (save_addr+i)/8; n= (save_addr+i)%8; rc= Coil_Bit_Read((cptr+m), n); bytes= i/8; bitno= i%8; if(rc==0) Coil_Bit_Write(&coil[bytes], bitno, 0); else Coil_Bit_Write(&coil[bytes], bitno, 1); rc= MBRTU_W_Multi_Coils(port, id, req_addr, words, coil); break; case 16: // write holding register multi-tag memset(reg, 0, sizeof(reg)); for(i=0; i<words; i++)

398 reg[i]= (short)reg.ao_soft[save_addr+i]; rc= MBRTU_W_Multi_Registers(port, id, req_addr, words, reg); break; idx++; // change to next unit index if(idx>= MODBUS_MASTER_UNIT_MAX) idx=0; if(flag[idx]== -1) idx=0; Sleep(10); // wait 10 msec and then polling next unit // while(1) MBRTUClose(port); // close COM Port return 0;

399 6 I/O 信号通信程序 6.1 I/O Module 介绍 WinCON 8000 控制器有三种类型的 I/O Module, 如下所示 : RS485 I/O Module: 以异步串行式通信, 速度最慢 Ethernet I/O Module: 透过网络与 I/O Module 联机, 速度比串行式快 10 倍以上, 但是有网络与其它设备共享时, 速度被降低或网络断线等不确定因素 Parallel I/O Module: 因为与主机在同一个设备上共享同一电源, 具有非常快速的信号反应, 所以没有串行式及 Ethernet 网络等两种 I/O, 可能发生网络断线或电源断线等故障 由以上的说明可知,Parallel I/O Module 附于 WinCON 8000 控制器上, 绝大部分的系统都会选用此种 I/O Module, 所以本次 Demo8000 程序应用以此种 I/O Module 为主对象 从泓格的规格书上有多种 I/O Module 可以选择, 但是由程序的观点来看, 就是区分为 DO DI AO AI 等四种信号, 也就是 Modbus 的 Output Coil Input Coil Holding Register Input Register 等 兹将 I/O Module 信号点数列表如下 : Module DO DI AO AI Module DO DI AO AI I I I I I I I I I I I I I I I I I I-8017H 8 I I 通信程序设计泓格的 WinCON 8000 由提供如何读取及写入这些 I/O Module 的 API 接口程序, 存于 WInCONSDK.DLL 内, 其内容与泓格一系列控制器的 API 一样 此 API 的基本用法已经于 WinCON 8000 API 基本用法 章节说明过, 在此主要是说明如何活用此 API, 以建立一个真正的控制系统 所考虑的重点是 : 以最快的速度读取 AI DI 信号, 如此才能快速反应现场状态 当输出 AO DO 信号时必须以最高的优先度输出 所有信号都存入程序内共享变量内, 然后以 Modbus 通信规约与外界交换资料 所以程序设计必须要有一些技巧, 希望读者了解

400 系统架构 : 由主程序激活控制 Thread ControlThreadProc() While loop I/O Module 通信的初期处理 initial_slot() 读取及写入信号处理 ( 详见下面 ) ICP_local_main() 各式控制处理 control_calc() Sleep(10) 暂停 0.01 秒 读取及写入信号处理 ( 详细流程 ) 检查写入 Queue 有无待通信纪录, 并且将所有 AO DO 处理完成 local_check_qbuff02 () 再读取 AI DI 信号 由 Modbus Slave 所要 求输出 AO DO 信号 由控制逻辑程序要求 输出 AO DO 信号 写入 Queue 由画面设定所要求输 出 AO DO 信号 图 3 9:Control Thread 流程图

401 由上页的架构图可以看到 Demo8000 程序执行后, 有一个计算控制逻辑的 Thread 激活 依据下列说明要点处理之 WinCON8000 控制器有 3 及 7 Slot 等两种款式, 系统提供以下的 struct PARAELL_IO 定义, 供使用者存入每个 Slot 的 I/O Module 型号, 以及对应要存入 Demo8000 程序的 Coil 或 Reg 变量内的 Modbus Address struct ICPDAS_IO_DAS 则是存入各种 I/O Module 的信号点数供程序判断用 至于定义操作则由 PC Utility 程序处理, 详见下面第 8 节 typedef struct _ICPDAS_IO_DEF short mod_no; // Module define No. short in_max; // Input channel no. 0 for empty channel short out_max; // Output channel no. 0 for empty channel ICPDAS_IO_DEF, *PICPDAS_IO_DEF; #define SLOT_MAX_CASE 7 // max. slot for WinCON 8000 typedef struct _PARAELLEL_IO int mod_no; // I/O Module No. 0 is no use int addr_di; // DI Modbus start address int addr_do; // DO Modbus start address int addr_ai; // AI Modbus start address int addr_ao; // AO Modbus start address int gain_ao; // for AO use int gain_ai; // for AI use PARALLEL_IO, *PPARALLEL_IO; PARALLEL_IO PARA_IO[SLOT_MAX_CASE]; ICPDAS_IO_DEF IO_DEF[I8024]= I8040, 32, 0, I8041, 0, 32, I8042, 16, 16, I8051, 16, 0, I8052, 8, 0, I8053, 16, 0, I8054, 8, 8, I8055, 8, 8, I8056, 0, 16, I8057, 0, 16, I8058, 8, 0, I8060, 0, 6, I8063, 4, 4, I8064, 0, 8, I8065, 0, 8, I8066, 0, 8, I8068, 0, 8, I8069, 0, 8, I8017H, 8, 0, I8024, 0, 4 ;

402 有关 I/O Module 处理程序有三个都存于 Com_sub.cpp 内 程序名功能说明 initial_slot() I/O Module 初期设定处理, 只做一次 ICP_local_main() 每次循环都读取 I/O 信号 local_check_qbuff02() 每次循环都检查 Queue 将所有 DO AO 输出 首先处理 I/O 通信的初期设定, 检查所定义的 I/O Module 是否与实际插入的是否相同 如果不同会显示警告讯息, 然后此 Thread 不会主控制 While Loop 内 然后进入控制回路 While Loop 内, 此程序存于 ModbusThread.cpp 内, 如下程序内容, 主要就是执行完成 ICP_local_main() 后, 接着执行 control_calc() 此为所有控制逻辑的计算将于下一节说明, 然后暂停 10 msec 再进行下一次循环 所以各位读者可以体会出就是以 I/O 信号处理, 接着控制逻辑计算及设定信号输出于 Queue 内的循环流程 UINT ControlThreadProc( LPVOID lpparam ) // Get a THREAD_INFO pointer from the // parameter that was passed in. THREAD_INFO *lpthreadinfo = (THREAD_INFO *) lpparam; // initial Parallel I/O Module if( initial_local()!=0) return(1); // I/O Module is not consistent while(1) ICP_local_main(); // read/write I/O Module signal control_calc(); Sleep(10); return(0); ICP_local_main() 每次循环都读取 I/O 信号, 使用 WinCONSDK.dll 的 API, 其详细使用方式于第二章已经详细说明 在此解说处理流程重点, 首先以 local_check_qbuff02() 将所有 Queue 内的 DO AO 输出处理完毕, 接着进入读取信号的 for() loop, 依据每个 I/O Module 使用配合的 API 取入信号值 void ICP_local_main(void) int i, m, n, slot, module, chnl_in, chnl_out, addr, gain, bytes, bitno;

403 int dd; unsigned short data; unsigned char bf[10]; unsigned long ldata; float fl; int idata[20]; float fdata[20]; // CString msg; // check write request Queue QBUFF10 while(1) if(local_check_qbuff02() == -1) break; // get AI channel data counting for delay time CUR_COUNT++; if(cur_count >= 2) CUR_COUNT=0; for(i=0; i<slot_max_case; i++) if(para_io[i].mod_no == 0) continue; // empty slot slot= i+1; module= PARA_IO[i].mod_no; chnl_in= IO_DEF[module -1].in_max; chnl_out= IO_DEF[module -1].out_max; switch(module) case I8017H: // 8 AI channel gain= PARA_IO[i].gain_ai; // signal input range if(cur_count ==0) for(m=0; m<chnl_in; m++) memset((char *)idata, 0, sizeof(idata)); memset((char *)fdata, 0, sizeof(fdata)); I8017H_AD_POLLING(slot, m, gain, 10, idata);

404 ARRAY_HEX_TO_FLOAT_ALL(idata, fdata, slot, gain, 10); for(n=1, fl=0.0; n<9; n++) fl= fl + fdata[n]; fl= fl / (float)8.0; // 取入中间八次平均值 addr= PARA_IO[i].addr_ai -1 +m; if(addr < 0) continue; else Reg.AI_BARE[addr]= fl; break; case I8024: // 4 AO channel gain= PARA_IO[i].gain_ao; for(m=0; m<chnl_out; m++) if(gain==0) fl= I8024_VoltageOutReadBack(slot, m); else fl= I8024_CurrentOutReadBack(slot, m); Reg.AO_BARE[addr]= fl; break; case I8052: // 8 DI channel case I8058: bf[0]= DI_8(slot); for(m=0; m<chnl_in; m++) dd= Coil_Bit_Read(&bf[0], m); addr= PARA_IO[i].addr_di -1 +m; bytes= addr/8; bitno= addr%8; Coil_Bit_Write(&Coil.DI_BARE[bytes], bitno, dd); break; // 其它 DI DO Module 处理都类似, 请参照光盘片原始程序不再列出

405 return; local_check_qbuff02() 每次循环都检查 Queue 将所有 DO AO 输出 设计要点是如何执行 Queue 方式, 在此提供一种方法, 首先宣称 Queue Buffer 的 struct 定义及所处理的子程序 ( 于 Com_sub.cpp 内 ) 如下所示 : typedef struct _AODO_OUTPUT char type; // DO, AO type char spare; int addr; // Modbus DO or AD address float val; // output data AODO_OUTPUT, *PAODO_OUTPUT; #define QUEUE_SIZE sizeof(struct _AODO_OUTPUT) #define QUEUE_MAX_CASE 1000 AODO_OUTPUT QBUFF10[QUEUE_MAX_CASE]; int AODO_RD_INDEX, AODO_WT_INDEX; 程序名 que_initial() que_reset() que_save() que_fifo_read() que_lifo_read() que_fifo_del() que_lifo_del() set_aodo_output() 功能说明将 Queue buffer 作初期处理将 Queue buffer 规到最初状态将一笔 Queue Record 存入 Queue 内读取最先存入的一笔 Queue Record 读取最后存入的一笔 Queue Record 删除最先存入的一笔 Queue Record 删除最后存入的一笔 Queue Record 由其它子程序要求 DO AO 输出时, 转存入 Queue 以上 que 开头的子程序为处理 Queue 的基本功能, 可以处理各种形式的 Queue buffer, 最后提供一个其它程序有要求 DO AO 输出时的接口 如此当由外部系统经过 Modbus 通信 画面人工设定输出 控制逻辑计算输出等都经由 set_aodo_output() 存到 Queue 内, 最后再由 local_check_qbuff02() 检查 Queue 内容, 将所要求输出的信号设定入 I/O Module 请读者详细研究所附的原始程序 int local_check_qbuff02(void)

406 int i, slot, bytes, bitno, module, dd, rec, chnl, gain; int s1, s2, addr_1, word_1; AODO_OUTPUT output02; float fl; unsigned short data; unsigned long ldata; unsigned char bf[10]; CString msg; // 检查 Queue 内是否有输出 Record rec= 0; if( que_fifo_read((char *)QBUFF10, (char *)&output02, QUEUE_SIZE, AODO_RD_INDEX, AODO_WT_INDEX, rec, QUEUE_MAX_CASE)!= 0) return(-1); // 无输出 Queue Record 回到 ICP_local_main() 程序 // 有输出 Queue Record, 先将 Queue 内的此 record 删除, 再作下面处理 que_fifo_del(queue_max_case, &AODO_RD_INDEX, &AODO_WT_INDEX); addr_1= output02.addr; // 由信号的内部 address 找到所属的 Slot No. and Module No. if(output02.type == DO_TYPE) for(i=0, slot=0, chnl=-1; i<slot_max_case; i++) if(para_io[i].mod_no == 0) continue; // empty slot module= PARA_IO[i].mod_no; if(module==i8041 module==i8056 module==i8057 module==i8060 module==i8064 module==i8065 module==i8066 module==i8068 module==i8069 module==i8042 module==i8054 module==i8055 module==i8063 ) // 找到后再找出, 此 DO 信号于该 Module 的 channel no. addr_1= output02.addr; s1= PARA_IO[i].addr_do -1; word_1= IO_DEF[module -1].out_max; s2= s1 + word_1; if(addr_1>=s1 && addr_1<s2)

407 // found slot slot= i+1; chnl= addr_1 -s1; break; if(slot==0) // 此不是 I/O Module 的 Slot, 所以值接设定入内部变量 addr_1= output02.addr; if(output02.val == 1) dd= 1; else dd= 0; bytes= addr_1/8; bitno= addr_1%8; Coil_Bit_Write(&Coil.DO_BARE[bytes], bitno, dd); return(0); else if(output02.type == AO_TYPE) for(i=0, slot=0, chnl=-1; i<slot_max_case; i++) if(para_io[i].mod_no == 0) continue; // empty slot if(para_io[i].mod_no == I8024) // 找到后再找出, 此 AO 信号于该 Module 的 channel no. module= PARA_IO[i].mod_no; addr_1= output02.addr; s1= PARA_IO[i].addr_ao -1; word_1= IO_DEF[module -1].out_max; s2= s1 + word_1; if(addr_1>=s1 && addr_1<s2) // found slot slot= i+1; chnl= addr_1 -s1; break;

408 if(slot==0) // 此不是 I/O Module 的 Slot, 所以值接设定入内部变量 addr_1= output02.addr; Reg.AO_BARE[addr_1]= output02.val; return(0); else return(0); if(slot==0 chnl==-1) return(0); // 不是 I/O Module 就直接回 ICP_local_main() if(output02.val == 1) dd= 1; else dd= 0; switch(module) // 开始作 DO AO 的实际硬件输出 case I8024: // write AO value by one channel // check bare data high and low // it must be long data type fl= output02.val; gain= PARA_IO[slot-1].gain_ao; if(gain==0) I8024_VoltageOut(slot, chnl, fl); else I8024_CurrentOut(slot, chnl, fl); Reg.AO_BARE[addr_1]= output02.val; break; case I8041: // 32 DO ldata= GetDIOData32(slot); memcpy(bf, (unsigned char *)&ldata, 4); if(chnl>=0 && chnl<=7) Coil_Bit_Write(&bf[0], chnl, dd); else if(chnl>=8 && chnl<=15) Coil_Bit_Write(&bf[1], chnl-8, dd); else if(chnl>=16 && chnl<=23) Coil_Bit_Write(&bf[2], chnl-16, dd); else if(chnl>=24 && chnl<=31)

409 Coil_Bit_Write(&bf[3], chnl-24, dd); memcpy((unsigned char *)&ldata, bf, 4); DO_32(slot, ldata); bytes= addr_1/8; bitno= addr_1%8; Coil_Bit_Write(&Coil.DO_BARE[bytes], bitno, dd); break; // 其它 DO Module 处理都类似, 请参照光盘片原始程序不再列出 // switch; return(0);

410 7 控制用 Timer Counter 的设计基本观念于前面 及 的章节已经说明过, 在此是要展示如何以程序来表现 Timer Counter 的功能 我们将功能设计成下面表格内的子程序, 然后使用时就是组合这些子程序 子程序名称功能说明 set_timer_start(int case_no, short value) 设定 Timer 的起始值 Calc_timer(int nsec) 计算 Timer 经过时间 check_timer_up(int case_no) 检查 Timer 是否时间到的状态 timer_reset(int case_no) 将 Timer 初始化 set_counter_start(int case_no, short value) Calc_counter(int case_no) check_counter_up(int case_no) counter_reset(int case_no) 设定 Counter 的起始值计算 Counter 次数检查 Counter 是否计数已到达的状态将 Counter 初始化 case_no: 表示第几个 Timer or Counter value: 设定 Timer 或 Counter 起始值 Timer 值 : 单位为 10 msec. 例如:300 表示 300 * 10 msec 为 3 秒 Time up Counter 值 : 表示计数目标值 例如 :5 表示到达 5 次, 既为 Counter up nsec: 进入 Timer 程序的前一次与本次的时间差, 单位 :millisecond 7.1 程序设计说明 : 控制 Timer Counter 主要有三个变量 : 目标值 (Preset Value): 此值既为前面所提到的起始值 目前值 (Current Value): 此为正在进行的值, 由程控随时计算与目标值比较, 如果到达目标值, 就将状态值设为 ON 状态值 (Contact Bit): 表示是否到达目标值的状态 以上三个值于 Demo8000 程序, 以 struct Register_TAG 内表示目标值及目前值, 以 struct Coil_TAG 内表示状态值 ( 详见 WinCONSYS.h 定义 ) 兹将此片段定义列如下, 以方便程序的说明 typedef struct _REGISTER_TAG // 其它信号变量的定义 short TM_PSET[CT_MAX_CASE]; // Timer preset value short TM_CURR[CT_MAX_CASE]; // Timer current value short CN_PSET[CT_MAX_CASE]; // Counter preset value short CN_CURR[CT_MAX_CASE]; // Counter current value

411 REGISTER_TAG, *pregister_tag; typedef struct _COIL_TAG // 其它信号变量的定义 unsigned char TM_BIT[CT_MAX_DD_BYTE]; unsigned char CN_BIT[CT_MAX_DD_BYTE]; COIL_TAG, *pcoil_tag; // Timer contact bit // Counter contact bit #define CT_MAX_CASE 128 // Timer and Counter max. case #define CT_MAX_DD_BYTE 16 // Timer, Counter Contact 以上定义 Timer Counter 的个数各别为 128 个 目前值及目标值以 16 Bit word 整数表示 状态值每个只要一个 bit, 所以宣称变量使用 unsigned char 只要 16 bytes Timer Counter 设定起始值 : Timer Counter 一开始必须设定一个目标值, 并且将目前值归零, 以准备开始计时或计数 程序很简单如下所示 : void set_timer_start(int case_no, short value) int addr; addr= case_no; Reg.TM_PSET[addr]= value; Reg.TM_CURR[addr]= 0; return; void set_counter_start(int case_no, short value) int addr; addr= case_no; Reg.CN_PSET[addr]= value; Reg.CN_CURR[addr]= 0; return; Timer 时间的计算 : 此为 Timer 的核心程序 因为控制计算为单独一个 Thread 执行, 此

412 计算是一个不停止的循环 (While Loop), 要做信号读出及写入 工程值转换 警报超限检查 Timer 计算. 等等工作 Timer 为其中的一项处理 Timer 最主要参数就是时间, 而且是与前一次处理后所经过的间隔时间, 如此才能计算出已经经过的时间, 然后判定此 Timer 是否到达 兹将由控制 Thread 至 Calc_timer() 的程序及连接状况说明如下 : UINT ControlThreadProc( LPVOID lpparam ) // 此控制 Thread 于 Demo8000 激活后, 就被执行 // Get a THREAD_INFO pointer from the // parameter that was passed in. THREAD_INFO *lpthreadinfo = (THREAD_INFO *) lpparam; // initial Parallel I/O Module, 模块设定参数取入 if( initial_local()!=0) return(1); // I/O Module is not consistent while(1) ICP_local_main(); // read/write I/O Module signal control_calc(); // 基本控制逻辑计算 special_control(); // 特殊控制计算, 依据每种应用而不同 Sleep(10); // 暂停 0.01 sec, 也表示就是每次控制周期 return(0); // 详细的基本控制逻辑程序如下 void control_calc() int i, nsec; // get system time, 最主要为 millisecond 时间 get_nsec_time(); NSEC= NSEC; if(old_nsec==-1) OLD_NSEC= NSEC; // when program start nsec= NSEC - OLD_NSEC; // 计算两次循环的时间差 if(nsec<0) nsec= nsec ; // 如果小于 0, 加上 1000 补正 nsec= nsec /10; // 再除以 10, 因为 Timer 单位为 10 msec.

413 // AI bare data to eng. data for(i=0; i<ed_max; i++) Reg.AI_ENG[i]=bare_to_eng(Reg.AI_ENG[i], Reg.AI_BARE[i], Reg.AI_RH[i], Reg.AI_RL[i], Reg.AI_BH[i], Reg.AI_BL[i], Reg.AI_FL[i]); // check Alarm calc_alarm(); // 信号警报超限的检查 calc_timer(nsec); // Timer 的计算 calc_plus_onoff(); // 对所有 DI/DO 点作 Plus On/Off 的计算 // 将本次 DI/DO 移到另一变量区 (OLD), 以作为下次计算 Plus 用 // move DI to DI_OLD memcpy(coil.di_old, Coil.DI_BARE, DI_MAX_PLUS_BYTE); // move DO to DO_OLD memcpy(coil.do_old, Coil.DO_BARE, DO_MAX_PLUS_BYTE); // save system time OLD_NSEC= NSEC; // 计算 Timer 经过时间 void calc_timer(int nsec) int i, bytes, bitno; for(i=0; i<ct_max_case; i++) if(reg.tm_pset[i]==0) continue; // no use else if(reg.tm_curr[i]>=0 && Reg.TM_CURR[i]<Reg.TM_PSET[i]) Reg.TM_CURR[i]= Reg.TM_CURR[i] + nsec; // 每次增加时间 else if(reg.tm_curr[i]>= Reg.TM_PSET[i]) // timer up 当大于目标值 bytes= i/8; bitno= i%8; Coil_Bit_Write(&Coil.TM_BIT[bytes], bitno, 1); // 对应的状态 Bit On return;

414 7.1.3 Counter 次数的计算 : Counter 次数的计算是要依据某一个 DI/DO 的 Plus On 或 Off 时, 加计一次 所以计算程序如下 此程序的激活不像 Timer 由 Thread 连动, 而是由各应用逻辑, 依据 Plus On/Off 激活此程序 ( 使用方式后面说明 ) void calc_counter(int case_no) int i, bytes, bitno; i= case_no; if(reg.cn_pset[i]==0) return; // no use else if(reg.cn_curr[i]>=0 && Reg.CN_CURR[i]<Reg.CN_PSET[i]) Reg.CN_CURR[i]= Reg.CN_CURR[i] + 1; // 每次加一 if(reg.cn_curr[i]>= Reg.CN_PSET[i]) // counter up bytes= i/8; bitno= i%8; Coil_Bit_Write(&Coil.CN_BIT[bytes], bitno, 1); // 计数到将状态 Bit On return; Timer Counter 状态检查 : 传 状态的检查就是读取 Timer Counter 所对应的 Bit, 然后将此值回 int check_timer_up(int case_no) int bytes, bitno, dd; bytes= case_no/8; bitno= case_no%8; dd= Coil_Bit_Read(&Coil.TM_BIT[bytes], bitno); return(dd); int check_counter_up(int case_no)

415 int bytes, bitno, dd; bytes= case_no/8; bitno= case_no%8; dd= Coil_Bit_Read(&Coil.CN_BIT[bytes], bitno); return(dd); Timer Counter Reset: Reset 处理就是将 Timer Counter 的目前值归零及状态 Bit Off, 以 预备下次使用 void timer_reset(int case_no) int addr, bytes, bitno; addr= case_no; Reg.TM_CURR[addr]= 0; // reset timer current value bytes= addr/8; bitno= addr%8; Coil_Bit_Write(&Coil.TM_BIT[bytes], bitno, 0); // reset Timer contact bit return; void counter_reset(int case_no) int addr, bytes, bitno; addr= case_no; Reg.CN_CURR[addr]= 0; // reset Counter current value bytes= addr/8; bitno= addr%8; Coil_Bit_Write(&Coil.CN_BIT[bytes], bitno, 0); return; 7.2 使用方式读者要记住 Timer Counter 核心程序只处理 Time Up 或 Count Up 后, 状态值 Bit 被 On 至于状态发生后要做何种处理, 此属于各种应用控制的层次 所以以下举出几种应用设计简单例子, 以供读者参考

416 例一 : 于 WinCON 8000 控制器 Slot-1 插入 I 点 DO 模块, 对应于 DO Bare 0-31 然后由 PC utility WinCON800_A 外部通信或者画面设定 DO Soft-10 On 此时 Demo8000 设计一段特殊控制程序如下所示 程序检查到 DO Soft-10 plus on 后, 激活 timer-5(1 秒 ) 及设定 DO Bare-24 On 当 timer-5 时间到 plus on, 激活 timer-6 (1 秒 ) 及设定 DO Bare-24 Off DO Bare-25 On 当 timer-6 时间到 plus on, 激活 timer-7 (1 秒 ) 及设定 DO Bare-25 Off DO Bare-26 On 当 timer-7 时间到 plus on, 设定 DO Bare-26 Off 及 DO Soft-10 Off 以供下一次使用 如此会看到 DO-Bare 会轮流各输出 1 秒信号, 于模块相对应的 Led 也会按顺序各亮 1 秒钟 if(get_plus_on(do_soft_type, 10)==1) timer_reset(5); set_timer_start(5, 100); // set timer-0 100*0.01 sec set_coil(do_bare_type, 24, 1); // set DO-24 On if(get_plus_on(tm_bit_type, 5)==1) //timer-5 时间到 plus on timer_reset(6); set_timer_start(6, 100); // set timer-0 100*0.01 sec set_coil(do_bare_type, 25, 1); // set DO-25 On set_coil(do_bare_type, 24, 0); if(get_plus_on(tm_bit_type, 6)==1) //timer-6 时间到 plus on timer_reset(7); set_timer_start(7, 100); // set timer-0 100*0.01 sec set_coil(do_bare_type, 26, 1); // set DO-26 On set_coil(do_bare_type, 25, 0); if(get_plus_on(tm_bit_type, 7)==1) //timer-7 时间到 plus on set_coil(do_bare_type, 26, 0); set_coil(do_soft_type, 10, 0); // set Off 以供下一个 Cycle 使用

417 例二 : 于 WinCON 8000 控制器 Slot-1 插入 I 点 DO 模块, 对应于 DO Bare 0-31 Demo8000 设计一段特殊控制程序如下所示 首先设定 Counter 0 3 的初始值 这些 Counter 将供计数 DO Soft-10 各种超限次数, 当超过 Counter 目标值后, 会输出相对应的 DO Bare 由 PC utility WinCON800_A 外部通信, 设定 AO Soft-10 值, 当此值超过警报上下限值, 会触动 Alarm Bit On, 然后相对应 Counter 加一 最后当 Count Up 时相对应的 DO Bare 输出 if( (check_counter_up(0)==0) && get_register(cn_pset_type, 0)==0) set_counter_start(0, 10); // initial counter preset value if( (check_counter_up(1)==0) && get_register(cn_pset_type, 1)==0) set_counter_start(1, 10); // initial counter preset value if( (check_counter_up(2)==0) && get_register(cn_pset_type, 2)==0) set_counter_start(2, 10); // initial counter preset value if( (check_counter_up(3)==0) && get_register(cn_pset_type, 3)==0) set_counter_start(3, 10); // initial counter preset value if(get_plus_on(ai_soft_hh_bit_type, 10)==1) // AI Soft has HH alarm calc_counter(0); // counter plus one if(get_plus_on(ai_soft_h_bit_type, 10)==1) // AI Soft has H alarm calc_counter(1); // counter plus one if(get_plus_on(ai_soft_l_bit_type, 10)==1) // AI Soft has L alarm calc_counter(2); // counter plus one if(get_plus_on(ai_soft_ll_bit_type, 10)==1) // AI Soft has LL alarm calc_counter(3); // counter plus one if(get_plus_on(cn_bit_type, 0)==1) // counter up plus on set_coil(do_bare_type, 16, 1); if(get_plus_on(cn_bit_type, 1)==1) // counter up plus on set_coil(do_bare_type, 17, 1); if(get_plus_on(cn_bit_type, 2)==1) // counter up plus on set_coil(do_bare_type, 18, 1); if(get_plus_on(cn_bit_type, 3)==1) // counter up plus on set_coil(do_bare_type, 19, 1);

418 8 Demo8000 程序说明 : Demo8000 程序主要功能已经于前面各章节说明过, 在此希望以整体说明以 及一些关键用法等方面, 作一个综合性的说明, 希望读者得到最完整性的观念 本书除了 WinCE 的程序设计技巧外, 也提供许多自动控制的基本观念, 读者必 须深入了解这些观念, 才能在设计或应用自控系统时, 得心应手不会出错 8.1 程序说明 : Demo8000 程序及定义文件如下表 : 名称 说明 Demo8000.vcw Project Demo8000.cpp 主程序 Mainfrm.cpp VC MFC 架构主画面 Doc.cpp VC MFC 架构 document 控制程序 TAGView.cpp 以 Listview 显示信号值的主画面 AIAODlg.cpp AI/AO 信号值设定画面 DIDODlg.cpp DI/AO 信号值设定画面 TMCNDlg.cpp Timer Counter 信号值设定画面 Com_sub.cpp 所有共享子程序 Com_sub.h 共享子程序定义文件 ModbusThread.cpp ModbusTCP Slave ModbusRTU Master Control Calculation 等三个 Thread 程序 WinCONSYS.h 所有 Common 变量的定义文件 MBDlltool.h Modbus DLL Header File MBTool.dll Modbus Basic Subroutine DLL MBTool.Lib Modbus Basic Subroutine Link Library 程序分成下列六大类 : 显示画面 :TAGView 此画面以 Listview 组件为主, 于 Menu 点选某一个信号种类后, 将该种类信号值显示于画面上 信号种类有 Input Coil Output Coil Input Register Output Register Input Coil Soft Output Coil Soft Input Register Soft Output Register Soft Timer Table Counter Table 等多种 因为每种信号个数很多, 显示较会费时间, 所以不作定时更新画面动作, 可以由 Menu Refresh Value 作最新值的更新 ( 实际上使用 PC utility WinCON800_A 作网络通信, 然后于 PC 画面上看到信号更新速度就非常的快, 主要是 CPU 等级不同 ) 主画面显示如次页:

419 图 13 10:WinCE Demo8000 程序主画面 设定画面 :AIAODlg DIDODlg TMCNDlg 当 Mouse 移到 Listview 信号区的某一个信号上,Double click 该信号就会出现所属的设定 画面, 可以设定信号的所有相关系数 画面如次页所示 :

420 图 13 11:AI/AO 设定画面 图 13 12:DI/DO 设定画面

421 图 13 13:Timer/Counter 设定画面 Thread 程序 :ModbusThread.cpp 包含三个 Thread 为 ModbusTCP Slave 提供外部系统读取及写入信号的方式 ModbusRTU Master 主动对外部 Modbus 设备作通信 Control Thread 作信号读写控制及所有控制逻辑的计算 Thread 于程序激活后, 由 Doc.cpp 程序建立 Thread handle 激活之, 此时已经以 Background 执行中 ModbusThread.cpp 内容为 Thread 的主程序部分, 其中使用一大堆子程序, 有关通信就是 MBTool.dll, 至于控制子程序就是在 Com_sub.cpp, 这些子程序详细意义于前面章节都说明过, 读者必须详细研读程序内容, 然后对照控制原理, 如此才能了解自动控制原理的真谛 Modbus 基本通信程序 :MBTool.dll 提供 Modbus TCP RTU ASCII 的 Master Slave 等通信基本子程序, 也就是通信的资料格式, 检查码计算 (Sum check), 实际通信处理等都由此 DLL 处理, 应用程序只需 Call 相关子程序, 请读者善加利用 Common 变量基本定义 : 所有信号定义 通信系数 I/O 模块定义等都在此档案 研读 Demo8000 程序前, 必须先了解各式变量的基本意义, 才能懂得程序每一个步骤的背后涵义 就好象说程序表面上是对资料一连串的判断及计算, 这是依据每种资料的定义及其处

422 理流程, 程序才会如此设计的 而不是先有程序, 然后才有资料及 处理流程 研读 WinCONSYS.h 档, 请参考本章最前面所提的信号 规格及通信运作等章节对照观看 共享子程序 :Com_Sub.cpp 子程序非常的多, 兹分类列表如下, 以 让读者更清楚了解 Com_Sub.cpp 子程序一览表 : 名称说明显示使用 dis play_tag 将指定信号种类所有 TAG 显示于主画面 Listview 上 控制基本子程序 read_sys_table 系统激活读取 Demo8000.tbl 设定档 set_initial_tag 读取上面系统文件后, 依据设定系数内的 Initial Value, 将所有信号值初始化 control_calc 基本控制主程序, 由 Control Thread 所激活, 以下子程序都是由此带动执行 bare_to_eng AI 信号取入后, 将原始值转换成工程值 eng_to_bare AO 信号输出前将工程值转换成原始值 calc_alarm 计算 AI/AO 信号警报的主程序 check_alarm 由上面程序带动的警报计算程序 calc_plus_onoff 检查 DI/DO 前一次与本次的变化状况 get_nsec_time 取入最新的系统 Millisecond Timer 子程序 set_timer_start 设定 Timer 起始值 calc_timer 计算 Timer 核心程序 check_ timer_up 检查 Timer Contact Bit 状态 On/Off timer_reset Timer 归零处理 Counter 子程序 set_counter_start 设定 Counter 起始值 calc_counter 计算 Counter 核心程序 check_counter_up 检查 Counter Contact Bit 状态 On/Off counter_reset Counter 归零处理信号处理子程序 initial_local 检查 I/O 模块设定系数是否与实际硬件一至 ICP_local_main 每次循环检查是否有 AO DO 信号输出, 及读取 AI DI 信号 local_check_qbu ff02 由上面程序带动, 检查 AO DO 信号输出 Queue 是否有待输出的 Record, 如果有则一次将每笔都处理完成, 再回去前一个程序, 此保证输出都可以得到最快速的处理 set_aodo_output 其它程序需要信号输出时, 使用此子程序, 再实际存入 Queue 内, 等待上面程序的输出处理

423 que_initial que_reset que_save que_fifo_read que_lifo_read que_fifo_del que_lifo_del 其它控制子程序使用 special_control set_coil set_register get_coil get_register get_plus_on get_plus_off 输出 Queue 的初始化输出 Queue 归零处理存入一笔 Record 于输出 Queue 按照先进先出方式 (First in First out) 读取一笔 Record 按照后进先出方式 (Last in First out) 读取一笔 Record 按照先进先出方式 (First in First out) 删除一笔 Record 按照后进先出方式 (Last in First out) 删除一笔 Record 特殊控制子程序 此用于每种应用的控制逻辑将不同, 所作的特殊设计程序 使用以下的子程序就可以简单的取入或写入所要的信号点设定一点 DI/DO 信号设定一点 AI/AO 信号取入一点 DI/DO 信号取入一点 AI/AO 信号读取一点 DI/DO 信号的 Plus On 变化读取一点 DI/DO 信号的 Plus Off 变化 8.2 Demo8000 使用要点 : 由上面的 Com_Sub.cpp 表格可以看到所有控制相关子程序, 其中控制基本子程序 Timer 子程序 Counter 子程序 信号处理子程序等于前面章节已经说明过 本次重点在于表格的最后一项 其它控制子程序使用, 如何简单使用 我们想象一下, 自动控制系统就是随时检查信号间的变化, 如果有变化就要对此作出反应达到立刻调整的目的, 如此保持所控制的对象于一定程序的运作及稳定的状态等 随时监视, 实时控制 就是实时控制系统的真义, 我们再将此观念要点详细说明如下 : 时间因素 : 此为实时控制的第一要素, 自动控制是随着时间而演变, 当来不及控制时, 并不表示往后控制是可以补救的 时间又分为两种型态 : 实时反应 (real time Response) 控制间隔时间(control interval) 等 实时反应就是当现场信号有变化后, 必须于最短的一定时间内做出反应 例如 : PLC 控制器一般程序执行一次约 100 ~ 200 millisecond 左右, 所以当现场开关被按下或者警报超限后, 最慢约 200 msec 会做出反应, PLC 可以看做 100 msec 等级的控制器 又例如 : 当供电系统需要察知 1 msec 发生的瞬间断电发生, 此时使用 PLC 就不对的选择, 必须使用一

424 种特殊的断电监视仪器 再例如 :PC 的监控软件包经由通信程序与 PLC 读写信号资料, 应用系统都会有数百 数千甚至万点的信号, 将所有信 号值读取一次都会超过 1 秒以上时间, 此时要求显示画面资料的更新必 须于 1 秒以内, 就是不合理的地方, 又当由画面操作一个 Button 以激活 现场马达, 此时如果输出命令等到所有信号读取一次后再输出, 就不是 很好的系统, 应该当有输出命令时, 必须为第一优先度的考虑 以上说 明实时反应也是有单位的, 依据不同的控制场合结合不同反应单位 (1 msec 100 msec 或 1 秒 ) 的设备, 形成完整的自动控制系统 控制时间间隔 : 有些控制必须于一定时间内控制一次, 例如 :PID 控制 通信读取外部信号 等等定时控制 此也有时间单位, 例如 PLC 的软件 方式 PID 计算, 大多数以 100 msec 为单位, 因为前面提到 PLC 程序一 次执行约 100 ~ 200 msec, 当然不能要求小于 100 msec 的控制 当面对 基本处理程序 : 本书提供信号读取及输出 模拟信号转换成工程值 警 或使用者都需要深入了解 态又分成持续发生中及刚发生等两种, 持续发生就是状态保持 On 或 应用控制程序的设计 : 依据以上的说明 Demo8000 程序, 已经做完基本 值等四个 function, 其 Return 值为 1 表示 On,0 表示 Off, 模拟信号 Return // 检查条件成立, 作一些计算处理 if(get_plus_on(do_soft_type, 10)==1 && get_coil(di_bare_type, 1)== 1) 高速变动使用时, 例如风压, 风速控制等, 就必须使用单独的硬件控制 器才可能应付 报检查 状态变化 (Plus On/Off) 检查 Timer/Counter 处理等基本原理 于每一次自动控制系统都需要依据这些原理, 所以不论你是系统设计者 状态发生 : 所有控制都是发生某一个状态, 然后再作控制处理 例如 : 电器开关的打开或关闭 警报感知器接触 马达运转或停止 温度超限 一定时间间隔的经过 某一个计数值完成. 等等都是所谓发生状态 状 Off, 刚发生就是那一瞬间 Plus On ( 由 Off 变成 On) 或 Plus Off ( 由 On 变 成 Off) 例如 : 马达刚激活为 Plus On 运转中为 On 马达刚停止为 Plus Off 停止中为 Off 等四种状态 PLC Ladder 就是依据状态的判断, 如 果成立就会作后面的控制处理, 如果不成立就会接着下一个判断 处理程序 然后提供 get_coil() 取入运转中状态 get_plus_on() 取入 Plus On 状态 get_plus_off() 取入 Plus Off 状态 get_register() 取入模拟信号 值为信号实数值 最后以 set_coil() 设定某一个 DI/DO On/Off, 以 set_register() 设定某一个 AI/AO 数值 以此六个 Function 就可以组成一 个大的应用系统 set_coil(di_soft_type, 20, 1); // 设定 DI Soft-20 On set_register(ai_soft_type, 30, 1.23); // 设定 AI Soft-30 值为 1.23

425 再进一步的设计技巧, 请参考光盘片内所附的 sample_1.cpp 及 sample_2.cpp 程序 详见下面最后第 10 小节的说明

426 9 PC Utility 功能说明 9.1 基本规格 : 此 Utility 程序与 Win CE 的 Demo8000 Program 联机, 做各种执行结果的显示以及系数的设定等操作 主要功能如下所示 : 以 Borland C++ Cbuilder 5.0 所设计 与 Demo8000 Program 的联机使用 Modbus TCP 方式 功能分成两大部分 :On-Line Control 与 Off-Line Configure 等 On-Line Control: 与 Demo Program 联机, 实时显示控制结果, 并且可以修改任何信号的系数等 Off-Line Configure: 提供使用者先行于 PC 上定义各种系数, 存入 Demo8000.TBL 档案, 然后将此档案与 Win CE Demo Program 一起存入 WinCON 8000 控制器内 如此 Demo Program 就会按照此定义做控制 所有信号的定义就是前面章节的 Demo8000 信号规格 此 Utility 的设计希望提供使用者一个更便利的工具, 以及如何设计 Modbus TCP 的联机方式 以此方式建立 PC Utility 与 Win CE Demo Program 对谈的模式

427 9.2 画面操作说明 功能 Utility 主画面 : 程序激活后如下画面, 然后使用者依据 Menu 选择所要 目前日期与时间讯息显示设定系数及结果联机状态 主画面显示各种 图 13-14:Utility 主画面 信号内容 Menu 功能 : [Link][Open TCP]: 设定与 Win CE Demo Program 联机的对方 IP Address, 并且建立 Modbus TCP 联机 [Link][Close TCP]: 结束 Modbus TCP 联机 [Link][Exit]: 程序结束 [Online Control][Input Coil]: 实时通信, 并且显示 Input Coil 信号点结果 [Online Control][Output Coil]: 实时通信, 并且显示 Output Coil 信号点结果 [Online Control][Input Register]: 实时通信, 并且显示 Input Register 信号点结果

428 [Online Control][Holding Register]: 实时通信, 并且显示 Holding Register 信号点结果 [Online Control][Input Coil Soft]: 实时通信, 并且显示 Input Coil Soft TAG 结果 [Online Control][Output Coil Soft]: 实时通信, 并且显示 Output Coil Soft TAG 结果 [Online Control][Input Register Soft]: 实时通信, 并且显示 Input Register Soft TAG 结果 [Online Control][Holding Register Soft]: 实时通信, 并且显示 Holding Register Soft TAG 结果 [Online Control][Timer Table]: 实时通信, 并且显示 Timer Table 结果 [Online Control][Counter Table]: 实时通信, 并且显示 Counter Table 结果 [Online Control][Mix Display]: 实时通信, 提供一个显示混合各种信号目前值, 以增加操作便利性 [Offline Configure][TAG Define]: 定义各式信号系数并存入 Demo8000.TBL 檔 [Offline Configure][Modbus Master Unit]: 定义对外与 Modbus Slave RTU Device 联机的通信 Unit [Offline Configure][Parallel I/O Module]: 定义 WinCON 8000 所用的 I/O Module 其信号所对应的 Modbus Coil or Register Address [Help][About]: 显示此 Utility 的版本及设计者资料等 Open TCP 画面 : 输入要联机的 WinCON 8000 控制器的 IP Address, 按 OK Button 后开始联机 当联机成功或失败都会出现相关的讯息画面如 下图, 同时于主画面右下方的状态区位也会显示 TCP Disconnect 或 TCP Connect 等讯息 如果要停止联机就操作 Menu [Link][Close TCP] 即可 图 13-15:Open TCP 主画面

429 [Online Control] 的各项操作画面说明 : 当选择此 Menu 内各种画面时, 会出现相对应的 Coil 或 Register 的信号一览表 这些信号就是 Demo8000 程序的规格, 此信号分成下列三大部分 : 以下举数个代表例说明应用方法 硬件 I/O 模块信号 : Input Coil Output Coil Input Register Holding Register 等对应于实际的 I/O 模块 内部信号 Soft TAG:Input Coil Soft Output Coil Soft Input Register Soft Holding Register Soft 等内部使用的控制点 Timer 及 Counter: 定时器及计数器等控制 当联机激活后,Utility 程序依照所选定的信号画面与 Demo8000 通信, On-Line 取入信号的最新值 处理方式为程序整理通信 Unit, 然后透过 Modbus TCP 联机方式取入最新值 通信处理状态显示于主画面左下方的状态列的第二个字段上 因为各种信号的点数及系数不同, 所以通信 Unit 总数会不同, 使用者可以由状态列上看到 Unit 编号会一直变化, 表示通信正进行中, 或者当通信断线时会有 ret 号码的出现 详细设计方式于下一个章节说明

430 图 13-16:Output Coil 显示画面及系数设定画面 Output Coil 显示画面共有四个字段 : TAG Type: 显示 Output Coil Modbus: 以 Modbus 规格所定义的信号地址 Status: 目前信号状态值 On or Off Initial Value: 初期值设定 当 Demo8000 程序激活时, 所设定的值 当 Mouse double click 某一行的信号点时, 会出现该信号系数设定画面, 然后操作设定值, 按 OK Button 该数值立刻透过 Modbus TCP 联机设定入 Demo8000 程序内 Input Coil Input Coil Soft Output Coil Soft 等也是类似的画面

431 图 13-17:Input Register 显示画面及系数设定画面 Input Register 显示画面共有十九个字段 :Register 信号为数值, 而不像 Coil 信号只有 On/Off 两种状态 所以必须含有许多设定系数, 这些系数于前面章节 Demo8000 的规格都有详细说明 所以一行的信号点必须将该信号的有关系数全部显示出来 (Holding Register 也是类似的画面 ) TAG Type: 显示 Input Register Modbus: 以 Modbus 规格所定义的信号地址 Bare data: 信号的原始值 Eng. data: 原始值换算成工程值 ALM HH ALM LL: 警报超上上限 上限 下限 下下限等状态 Range High Range Low Bare High Bare Low: 信号原始值与工程值换算用数值最高及最低范围 Filter: 一次滤波系数 范围 : 之间 Alarm HH Alarm LL: 警报超限值等 Dead Zone: 警报不感带百分率 范围 : 之间 Initial Value: 初期值设定 当 Demo8000 程序激活时, 所设定的值 当 Mouse double click 某一行的信号点时, 会出现该信号系数设定画面, 然后操作设定值, 按 OK Button 该数值立刻透过 Modbus TCP 联机设定入 Demo8000 程序内

432 图 13-18:Holding Register Soft 显示画面及系数设定画面 Holding Register Soft 显示画面共有十三个字段 : 此为内部数值信号, 所 以无硬件信号所需 Bare Data Range High Range Low Bare High Bare Low

433 Filter 等字段, 其它与 Input Register 画面都相同 (Input Register Soft 也是类 似的画面 ) 图 13-19:Timer Table 显示画面及系数设定画面 Timer Table 显示画面共有七个字段 : 主要用来计时用, 当计时时间到达, 对应的 Coil On (Counter Table 也是类似的画面 ) TAG Type: 显示 Timer Table 第一个 Modbus: 以 Modbus 规格所定义的 Preset Value 地址 Preset Value: 计时目标值 第二个 Modbus: 以 Modbus 规格所定义的 Current Value 地址 Current Value: 计时目前值

434 第三个 Modbus: 以 Modbus 规格所定义的 Contact Value 地址 Contact Value: 计时到达后设定 On/Off 的 Contact Coil 当 Mouse double click 某一行的信号点时, 会出现该信号系数设定画面, 然后操作设定值, 按 OK Button 该数值立刻透过 Modbus TCP 联机设定入 Demo8000 程序内 Mix Display 显示画面共有四个字段 : 主要混合显示各种信号目前值, 先以设定画面定义所要显示的信号, 然后经过通信取入信号值 当 Mouse double click 某一行的信号点时, 会出现该信号系数设定画面, 然后操作设定值, 按 OK Button 该数值立刻透过 Modbus TCP 联机设定入 Demo8000 程序内

435 图 13-20:Mix Display 显示画面及系数设定画面 [Offline Configure][TAG define] 的选项说明 : 当选择此功能时, 会出现如下的 TAG Define Form 画面 主要做各种 TAG 的系数设定, 设定完毕都会存入 Demo8000.TBL 档案内 画面以蓝色字显示表示为 Offline Configure, 与 Online Control 的黑色字做区别 每种信号字段种类都不相同, 以下三个画面分别为 Output Coil Holding Register Counter Table 等为代表, 其它画面都与此类似

436 图 13-21:TAG Define 主画面

437 Menu 功能 : 点选下列功能就会出现如同上图相对应 TAG Define 画面 [Coil][Input Coil]: 激活 Input Coil 系数设定画面 [Coil][Output Coil]: 激活 Output Coil 系数设定画面 [Coil][Input Coil Soft]: 激活 Input Coil Soft 系数设定画面 [Coil][Output Coil Soft]: 激活 Output Coil Soft 系数设定画面 [Register][Input Register]: 激活 Input Register 系数设定画面 [Register][Holding Register]: 激活 Holding Register 系数设定画面 [Register][Input Register Soft]: 激活 Input Register Soft 系数设定画面 [Register][Holding Register Soft]: 激活 Holding Register Soft 系数设定画面 [Timer/Counter][Timer Table]: 激活 Timer Table 系数设定画面 [Timer/Counter][Counter Table]: 激活 Counter Table 系数设定画面 [Quit][Exit and not Save]: 所设定的系数不做储存而离开此画面 [Quit][Exit and Save]: 所设定的系数存入 Demo8000.TBL 而离开此画面 当 Mouse double click 某一行的信号点时, 会出现该信号系数设定画面, 然后操作设定值, 按 OK Button 该系数先行存入暂存区内, 然后再以 [Quit][Exit and Save] 做最后存盘 这些 TAG 系数设定画面与 Online Control 的设定画面相类似, 在此不再重复说明 [Offline Configure][Modbus Master Unit] 的选项说明 : 此为建立与外部 Modbus RTU Slave Device 通信用 Unit 等系数的画面 图 13-22:Modbus Master 通信设定画面

438 字段说明 : 画面分成 COM port 通信系数及通信 Unit 定义等两大部分 RS232/485 Communication Parameter COM Port: 选择通信 COM Port 1 4 Baud Rate: 通信速率 等 Parity Check:Odd Even None 等三种 Data Bits:7 8 等两种 Stop Bits:1 2 等两种 Timeout: sec. 等 Modbus Master RTU Comm. Unit Define Unit No.: 选择所要定义的通信 Unit 编号:1-100 Active:Disable 表示此 Unit 不做通信 Enable 表示此 Unit 要通信 Function No.: 对应 Modbus Fun. Code 等共 8 个 由 ComboBox 内选项既为 Read Output Coils Read Input Coils Read Holding Registers Read Input Registers Write Output Coil one TAG Write Holding Register one TAG Write Output Coil multi- TAG Write Holding Register multi-tag 等 Modbus Address (ID No.):Modbus Device 的编号 Relay/Register Address: 所要通信信号起始地址 Communication Bits/Words: 通信信号的点数 Soft TAG Address: 相对应的 Soft TAG 地址 图 13-23:Parallel Slot I/O Module 设定画面字段说明 : 定义 Slot 所插的 I/O Module 及其所要存入的 Modbus Address 当 Demo8000 执行时, 依据此定义资料, 随时对各 I/O Module 读取 DI AI 或写入 DO AO 等信号

439 Slot No.:WinCON 8000 控制器的 Slot No. 有三个及七个两种 Modle 每个 Slot 插一片 I/O Module Modbus No.: 选择所插入的 I/O Module 如果是空 Slot 则选择 empty slot Coil or Register Address: 此为该 I/O Module 所要对应的 Modbus 信号点位置 画面会依据所选的 I/O Module, 显示所对应的信号点, 由使用者输入 如果定义的 Modbus Address 有与其它 Slot 重复时, 程序也会检查出来, 并且以 Message Box 画面警告之 又如果为 Analog I/O Module 再加上 Input Range, 此为电气信号的范围, Module 使用前必须以泓格的 Utility 程序依据实际使用情形, 定义某一个信号范围, 例如 :-10.0 ~ V, 当接到现场信号后, 程序经过泓格的 WInCONSDK.DLL API 接口, 就会取得信号实际值, 此既为原始值 Bare Data 所以信号的 Input Range 必须设定正确, 否则换算工程值会产生错误 9.3 程序说明 : U tility 程序分成画面显示 通信处理等两大部分, 本章节会对系统架构 及通信处理做详尽的说明, 至于画面显示部分为一般的程序设计技巧, 只会 做一个大慨的说明 程序一览表如下 WinCON8000_A Utility 程序及定义文件如下表 : 名称 说明 WinCON8000_A.bpr Project MAIN.cpp 主程序 MBTool.DLL Modbus Basic Subroutine DLL MBTool.Lib Modbus Basic Subroutine Link Library MBTool.h Modbus Basic Subroutine Defined Header WinCONSYS.h 共享变量的定义文件 AIO_Dlg.cpp Register 信号设定 Dialog Box DIO_Dlg.cpp Coil 信号设定 Dialog Box TMCN_Dlg.cpp Timer/Counter 设定 Dialog Box TAGDef.cpp TAG Offline Configure 主画面 AIO_Def.cpp Register 信号 Offline 设定 Dialog Box DIO_Def.cpp Coil 信号 Offline 设定 Dialog Box TMCN_Def.cpp Timer/Counter 信号 Offline 设定 Dialog Box STD_SUB.cpp 共享子程序 OpenTCP.cpp Open Modbus TCP Port Dialog Box MBMaster_Dlg.cpp 设定 Modbus Master 通信 Unit Dialog Box IOModule.cpp 设定 Parallel I/O Module Dialog Box About.cpp About Dialog Box Demo8000.tbl 信号系数定义文件

440 系统架构图 WinCON8000_A main.cpp 依据 Online Control 功能选择 显示各信号内容 main.cpp 的 Timer-1(1 sec.) 将所选择的信号内容做最新值显示 main.cpp 主画面上 M ou se Double Click 某一信号点, 显示该信号的设定画面 选择 [Link][Open TCP] 显示 Open TCP Dialog Box 设定 IP Address 开始联机激活 Timer-2 main.cpp 的 Timer-2(0.1 sec.) 针对所显示的信号做通信取入最新值 选择 [Offline Configure] [TAG Define] 显示 TAG Define 主画面 TAGDef.cpp 主画面上 Mouse Double Click 某一信号点, 显示该信号的设定画面 选择 [Offline Configure] [ Modbus Master Unit] 显示 Modbus 通信 Unit 设定画面 选择 [Offline Configure] [ Parallel I/O Module] 显示 Slot I/O Module 设定画面 通信处理的说明 : 分成两大阶段, 第一阶段为依据所选择的信号种类, 由程序设定所要通信 Unit 的个数及内容 第二阶段为激活 Timer-1 以每 0.1 sec 的间隔时间通信一次一个 Unit 取入最新值 我们先说明通信 Unit 的设定, 此 Unit 定义存于 WinCONSYS.h 内 #define POLL_MAX_CASE 100 typedef struct _COMM_UNIT short id; short start_addr;

441 short words; // Register words or Coil point short fun; short flag; // 0: no use 1: use COMM_UNIT, *PCOMM_UNIT; COMM_UNIT POLL_UNIT[POLL_MAX_CASE]; short POLL_TOTAL; // total unit of active polling short POLL_CURR; // current polling unit no. 以上 Unit 定义 100 个, 针对每个不同的信号, 要通信的点数不同, 所以会用到不同数量的 Unit 重点在于如何依据各式信号整理出所要的通信 Unit 内容 我们以 Output Coil 及 Holding Register 等两种信号举例说明 : 处理子程序存于 STD_SUB.cpp 内的 set_poll_unit( int idx) 程序依据所选择的信号种类的指针 idx, 然后以 switch case 方式分别整理所要的通信 Unit 以下程序片断为 Output Coil 及 Holding Register 等两种信号, 其它信号可以参考本书所附的光盘片内原始程序 又程序内许多常数的 define 定义存于 WinCONSYS.h 档案内 重点为所有信号定义为一个 Struct 构造, 每一种信号所要使用的系数都不同, 所以必须依据此信号的系数种类整理所要的通信 Unit Output Coil 的通信 Unit: 系数有原始值 DO_BARE 及初期值 DO_INIT 等两种, 定义的最大点数为 DD_MAX_BIT 512 点 以 Modbus TCP 通信规约每次约可取入 1920 Coil 点, 所以只要设定两个 Unit 就可以应付 至于 Modbus 的 start address 也是都以定义 DO_BARE_PTR DO_INIT_PTR 等表示, 参考 WinCONSYS.h 檔 Holding Register 的通信 Unit: 系数有原始值 AO_BARE 工程值 AO_ ENG 初期值 AO_INIT 换算用 Range AO_RH AO_RL AO_BH AO_BL 一次滤波 AO_FL 警报上下限 AO_HH AO_H AO_L AO_LL AO_DEAD 警报结果 AO_HH_BIT AO_H_BIT AO_L_BIT AO_LL_BIT 等 17 种系数 又信号定义的最大点数为 ED_MAX 128 点且为 float 数值 以 Modbus TCP 通信规约每次约可取入 120 Register 点 (integer 数值 ), 所以每种系数需要读取的点数为 256 点 (128*2), 必须设定四个 Unit 才可以应付, 而且总共有 17 种系数, 每种系数的 Modbus start address 都不同 ( 请参考 WinCONSYS.h 文件 ), 程序必须依每种系数分别设定通信 Unit, 因此部份程序非常的长, 但是处理方式类似 switch(idx) case 1: // Output Coil POLL_UNIT[0].id=1; POLL_UNIT[0].start_addr=DO_BARE_PTR +1; POLL_UNIT[0].words=DD_MAX_BIT;

442 POLL_UNIT[0].fun=1; POLL_UNIT[0].flag= 1; POLL_UNIT[1].id=1; POLL_UNIT[1].start_addr=DO_INIT_PTR +1; // 此乘以 3 的意义是含 Soft TAG 都一次取入, 后面程序处理可以比较简化 POLL_UNIT[1].words=DD_MAX_BIT*3; POLL_UNIT[1].fun=1; POLL_UNIT[1].flag= 1; POLL_TOTAL= 2; break; case 3: // Holding Register m= ED_MAX/60; n= ED_MAX%60; cnt=0; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_BARE_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_BARE_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_ENG_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2;

443 POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_ENG_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; m= (ED_MAX)/60; // just take Eng. only n= (ED_MAX)%60; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_INIT_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_INIT_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; m= ED_MAX/60; n= ED_MAX%60; for(i=0; i<m; i++)

444 POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_RH_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_RH_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_RL_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_RL_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_BH_PTR*2 +120*i+1;

445 POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_BH_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_BL_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_BL_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_FL_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3;

446 POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_FL_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_HH_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_HH_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_H_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++;

447 if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_H_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_L_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_L_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_LL_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0)

448 POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_LL_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; for(i=0; i<m; i++) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_DEAD_PTR*2 +120*i+1; POLL_UNIT[cnt].words=60*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; if(n>0) POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_DEAD_PTR*2 +120*i+1; POLL_UNIT[cnt].words=n*2; POLL_UNIT[cnt].fun=3; POLL_UNIT[cnt].flag= 1; cnt++; POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_HH_BIT_PTR +1; POLL_UNIT[cnt].words=ED_MAX*5; POLL_UNIT[cnt].fun=1; POLL_UNIT[cnt].flag= 1; cnt++; POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_H_BIT_PTR +1; POLL_UNIT[cnt].words=ED_MAX*5; POLL_UNIT[cnt].fun=1; POLL_UNIT[cnt].flag= 1;

449 cnt++; POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_L_BIT_PTR +1; POLL_UNIT[cnt].words=ED_MAX*5; POLL_UNIT[cnt].fun=1; POLL_UNIT[cnt].flag= 1; cnt++; POLL_UNIT[cnt].id=1; POLL_UNIT[cnt].start_addr=AO_LL_BIT_PTR +1; POLL_UNIT[cnt].words=ED_MAX*5; POLL_UNIT[cnt].fun=1; POLL_UNIT[cnt].flag= 1; cnt++; POLL_TOTAL= cnt; break; // end of switch() Poll 通信处理的说明 : 当设定完成通信 Unit 后, 就激活 Timer-1 以每 0.1 秒通信一个 Unit 以 POLL_CURR 纪录目前所通信的 Unit 编号, POLL_TOTAL 为要通信 Unit 的总数, 如此控制一次通信一个 Unit 方式, 将信号值读取入 COIL_TAG REGISTER_TAG 等共享变量内, 再交由 Timer-2 以每 1 秒取入共享变量显示于画面上 ( 共享变量请参考 WinCONSYS.h) short POLL_TOTAL; // total unit of active polling short POLL_CURR; // current polling unit no. 通信所用的 Driver 设计于 MBTool.dll 内 (PC 版本 ), 此用法与章节 5.2. 的 WinCE 版本完全一样 本次使用 Modbus TCP Master 系列子程序, 以主动方式发出 Modbus Command, 然后等待 Response Message, 依据此 Message 内容转存入 COIL_TAG REGISTER_TAG 等共享变量内 如果等待后的 Return Code 为 0 表示通信正常, 不为 0 表示通信异常, 此异常 Code 可参考 MBTool.h 档内的定义 void fastcall TForm1::Timer2Timer(TObject *Sender) // Polling TAG int idx, id, addr, fun, rc, words, bytes, bitno; int i, m, n, k;

450 unsigned char coil[256], *cptr; short reg[256], *iptr, *iptr_1; AnsiString s; char buff[80]; // 决定本次要通信的 Unit Index idx= POLL_CURR; if(poll_unit[idx].flag==0) return; CURR_UNIT= POLL_UNIT[idx]; // 由此 Unit 取入本次要通信的 Modbus 系数 id= CURR_UNIT.id; //Modbus Address (ID Mo.) addr= CURR_UNIT.start_addr; //Coil or Register Start address words= CURR_UNIT.words; // 所要通信的信号点数 fun= CURR_UNIT.fun; // Function Code if(fun==1 fun==2) // Read Output or Input Coil rc= MBTCP_R_Coils(SockNo, id, addr, words, coil, fun); else // fun=3, 4 Read Holding or Output Register rc= MBTCP_R_Registers(SockNo, id, addr, words, reg, fun); if(rc==mb_rtc_ok) // Return code 为 0 表示正常通信完毕 addr= addr -1; // Coil or Register address 是由 1 算起, 程序内使用要扣 1 switch(fun) // 依据 Function Code 分开处理 case 1: // Read Output Coil cptr= Coil.DO_BARE; for(i=0; i<words; i++) m= i/8; n= i%8; k= Coil_Bit_Read(&coil[m], n); bytes= (addr+i)/8; bitno= (addr+i)%8; if(k==0) Coil_Bit_Write((cptr+bytes), bitno, 0); else Coil_Bit_Write((cptr+bytes), bitno, 1);

451 break; case 2: // Read Input Coil cptr= Coil.DI_BARE; for(i=0; i<words; i++) m= i/8; n= i%8; k= Coil_Bit_Read(&coil[m], n); bytes= (addr+i)/8; bitno= (addr+i)%8; if(k==0) Coil_Bit_Write((cptr+bytes), bitno, 0); else Coil_Bit_Write((cptr+bytes), bitno, 1); break; case 3: // Read Holding Register iptr= (short*)reg.ao_bare; iptr_1= (short *)Reg.TM_PSET; for(i=0; i<words; i++) if((addr+i)<tm_pset_ptr) // it is float m= (addr+i); *(iptr+m)= reg[ i]; else // it is timer or counter m= (addr+i)- TM_PSET_PTR; *(iptr_1+m)= reg[i]; break; case 4: // Read Input Register iptr= (short*)reg.ai_bare; for(i=0; i<words; i++) m= (addr+i);

452 *(iptr+m)= reg[i]; break; // Unit Index 加 1, 以计算出下一个通信 Unit POLL_CURR++; if(poll_curr>= POLL_TOTAL) POLL_CURR=0; if(poll_curr>= POLL_MAX_CASE) POLL_CURR=0; if(poll_unit[poll_curr].flag==0) POLL_CURR=0; // 通信正常结果显示于状态列上 sprintf(buff, "Modbus TCP connect unit=%d", POLL_CURR); s= buff; display_status_message(s); else // 通信异常结果显示于状态列上 sprintf(buff, "Modbus TCP error rc=%d, unit=%d", rc, POLL_CURR); s= buff; display_status_message(s); POLL_CURR++; if(poll_curr>= POLL_TOTAL) POLL_CURR=0; if(poll_curr>= POLL_MAX_CASE) POLL_CURR=0; if(poll_unit[poll_curr].flag==0) POLL_CURR=0;

453 10 基本发展平台功能展示 : 经过前面的种种解说, 我想读者对自动控制系统有一个基本的认识, 而且有一个基本的架构可用 本书最后一段就是加上每个应用系统所需的特殊控制子程序, 就成为一个实际控制程序 此特殊控制子程序以 include file 方式加入 Control Thread 前面 (ModbusThread.cpp 档案内 ), 所使用子程序名为 special_control(), 如此安排只要 include 本次所要的程序档案, 使用相同的子程序名, 重新 Compiler and link 后产生一个新的执行档, 就是一个新的应用程序 此程序的设计尽量使用第 8.2 点的状态条件结果来作控制, 以下设计两个子程序来展示设计方法 系统建立如下 WinCON 8000 控制器于 Slot-1 安装 I 点 DO 模块 以 Utility WinCON8000_A 的 Menu [Offline Configure][Parallel I/O Module] 功能进入设定画面, 将 Slot-1 设定为 I-8041 模块, 其 DO Output Coil Address 设为 1, 表示 DO 32 点所对应的 DO Bare 内部 Address 为 0 ~ 31 以 EVC++ 取入 Demo8000 Project 修改 ModbusThread.cpp 内的 incluide Sample_1.cpp 或 Sample-2.cpp 选择 [Win32 (WCE ARMV4) Release] 后, Complier and Link 程序 Demo8000.exe 将 Utility 作成的控制檔 Demo8000.TBL EVC++ Compiler and Link 完成的程序 Demo8000.exe Modbus 通信用 MBTool.DLL 等三个档存入 WinCON 8000 控制器内的目录 \Compact Flash\Demo8000 于 WinCON 8000 于左下方 [Start][Setting ][Network and Dial-up Connections] 进入网络设定选项 激活 LAN9001 进入 IP Address 设定画面, 选择 Specify an IP Address, 并且设定所要的 IP Address 激活 Demo8000 后, 显示主画面, 程序已进入控制及通信状态 激活 Utility WinCON8000_A 以 Menu [Link][Open TCP] 设定 WinCON 8000 控制器的 IP Address, 再由 Menu [Online Control] 选择一项信号, 画面显示该信号所有 TAG 相关的数值, 同时看到下方状态列 (status bar) 已经与控制器通信中, 表示已经通信中, 可以由此 Utility 设定信号值至控制器 10.1 Sample_1.cpp: Sampel_1 是展示 DI/DO set On/Off Plus On/Off 及 AI 计算 警报等功能 使用 I/O 一览表如下 : 信号内部 Address 由 0 起算 Modbus Address 由 1 起算 信号 Modbus 说明 Address Output Coil Soft 内部 Coil, 可由通信设定其值 Output Coil I/O 模块 DO-10 Input Coil Soft 内部 Coil, 只能程序使用 Output Coil Soft 内部 Coil, 可由通信设定其值 Input Register Soft 内部 Register, 只能程序使用

454 Output Coil I/O 模块 DO-15 Output Register Soft 内部 Register, 可由通信设定其值 Output Coil I/O 模块 DO-16 Output Coil I/O 模块 DO-17 Output Coil I/O 模块 DO-18 Output Coil I/O 模块 DO-19 以 Utility Menu [Online Configure][Mix Display] 作以上信号点的设 定如下画面 : 第一种展示功能 : 设定 Output Coil Soft 第 0 点 On, 会设定 DO 模块的 DO-10 On, 看到 LED 亮, 设定 Off 则 LED 灭 同时检查 Output Coil Soft 第 0 点 Plus On 时, 设定 DO-11 On LED 亮, 以及 Input Coil Soft 第 0 点 On 当检查 Output Coil Soft 第 0 点 Plus Off 时, 设定 DO-11 Off LED 灭, 以及 Input Coil Soft 第 0 点 Off 以上控制展示依据 Output Coil Soft-0 随时控制 DO-10 On/Off Output Coil Soft-0 的 Plus On/Off 控制 DO-11 On/Off 第二种展示功能 : 当 Output Coil Soft 第 1 点 On 时,Input Register Soft 第 10 点, 每次加 1 当 Input Register Soft 第 10 点超过上上限时, 设定 DO-15 On LED

epub83-1

epub83-1 C++Builder 1 C + + B u i l d e r C + + B u i l d e r C + + B u i l d e r C + + B u i l d e r 1.1 1.1.1 1-1 1. 1-1 1 2. 1-1 2 A c c e s s P a r a d o x Visual FoxPro 3. / C / S 2 C + + B u i l d e r / C

More information

ebook140-9

ebook140-9 9 VPN VPN Novell BorderManager Windows NT PPTP V P N L A V P N V N P I n t e r n e t V P N 9.1 V P N Windows 98 Windows PPTP VPN Novell BorderManager T M I P s e c Wi n d o w s I n t e r n e t I S P I

More information

EK-STM32F

EK-STM32F STMEVKIT-STM32F10xx8 软 件 开 发 入 门 指 南 目 录 1 EWARM 安 装... 1 1.1 第 一 步 : 在 线 注 册... 1 1.2 第 二 步 : 下 载 软 件... 2 1.3 第 三 步 : 安 装 EWARM... 3 2 基 于 STMEVKIT-STM32F10xx8 的 示 例 代 码 运 行... 6 2.1 GPIO Demo... 6 2.2

More information

基于UML建模的管理管理信息系统项目案例导航——VB篇

基于UML建模的管理管理信息系统项目案例导航——VB篇 PowerBuilder 8.0 PowerBuilder 8.0 12 PowerBuilder 8.0 PowerScript PowerBuilder CIP PowerBuilder 8.0 /. 2004 21 ISBN 7-03-014600-X.P.. -,PowerBuilder 8.0 - -.TP311.56 CIP 2004 117494 / / 16 100717 http://www.sciencep.com

More information

Chapter 2

Chapter 2 2 (Setup) ETAP PowerStation ETAP ETAP PowerStation PowerStation PowerPlot ODBC SQL Server Oracle SQL Server Oracle Windows SQL Server Oracle PowerStation PowerStation PowerStation PowerStation ETAP PowerStation

More information

Symantec™ Sygate Enterprise Protection 防护代理安装使用指南

Symantec™ Sygate Enterprise Protection 防护代理安装使用指南 Symantec Sygate Enterprise Protection 防 护 代 理 安 装 使 用 指 南 5.1 版 版 权 信 息 Copyright 2005 Symantec Corporation. 2005 年 Symantec Corporation 版 权 所 有 All rights reserved. 保 留 所 有 权 利 Symantec Symantec 徽 标 Sygate

More information

ebook140-8

ebook140-8 8 Microsoft VPN Windows NT 4 V P N Windows 98 Client 7 Vintage Air V P N 7 Wi n d o w s NT V P N 7 VPN ( ) 7 Novell NetWare VPN 8.1 PPTP NT4 VPN Q 154091 M i c r o s o f t Windows NT RAS [ ] Windows NT4

More information

PowerPoint Presentation

PowerPoint Presentation 1 概 述 概 述 基 本 概 念 回 顾 微 软 的 嵌 入 式 平 台 WinCE 系 统 定 制 简 介 WinCE 应 用 开 发 简 介 2 嵌 入 式 系 统 - 基 本 概 念 回 顾 嵌 入 式 系 统 什 么 是 嵌 入 式 系 统 嵌 入 式 系 统 特 点 嵌 入 式 系 统 的 二 种 应 用 模 式 嵌 入 式 系 统 结 构 嵌 入 式 操 作 系 统 什 么 是 嵌 入

More information

WebSphere Studio Application Developer IBM Portal Toolkit... 2/21 1. WebSphere Portal Portal WebSphere Application Server stopserver.bat -configfile..

WebSphere Studio Application Developer IBM Portal Toolkit... 2/21 1. WebSphere Portal Portal WebSphere Application Server stopserver.bat -configfile.. WebSphere Studio Application Developer IBM Portal Toolkit... 1/21 WebSphere Studio Application Developer IBM Portal Toolkit Portlet Doug Phillips (dougep@us.ibm.com),, IBM Developer Technical Support Center

More information

INTRODUCTION TO COM.DOC

INTRODUCTION TO COM.DOC How About COM & ActiveX Control With Visual C++ 6.0 Author: Curtis CHOU mahler@ms16.hinet.net This document can be freely release and distribute without modify. ACTIVEX CONTROLS... 3 ACTIVEX... 3 MFC ACTIVEX

More information

PPBSalesDB.doc

PPBSalesDB.doc Pocket PowerBuilder SalesDB Pocket PowerBuilder PDA Pocket PowerBuilder Mobile Solution Pocket PowerBuilder Pocket PowerBuilder C:\Program Files\Sybase\Pocket PowerBuilder 1.0 %PPB% ASA 8.0.2 ASA 9 ASA

More information

indows CE 1996 Semiconductor Industry Association (SIA) Windows CE Windows Embedded CE 6.0 Windows CE Windows Embedded CE 6.0 Micr

indows CE 1996 Semiconductor Industry Association (SIA) Windows CE Windows Embedded CE 6.0 Windows CE Windows Embedded CE 6.0 Micr Windows Embedded CE 6.0 Windows Embedded CE 6.0 : Windows Embedded CE 6.0 : Windows CE Windows Embedded CE 6.0 API CE 6.0 Windows CE W indows CE 1996 Semiconductor Industry Association (SIA) 1994 1000

More information

Basic System Administration

Basic System Administration 基 本 系 统 管 理 ESX Server 3.5 ESX Server 3i 版 本 3.5 Virtual Center 2.5 基 本 管 理 指 南 基 本 管 理 指 南 修 订 时 间 :20080410 项 目 :VI-CHS-Q208-490 我 们 的 网 站 提 供 最 新 的 技 术 文 档, 网 址 为 : http://www.vmware.com/cn/support/

More information

概述

概述 OPC Version 1.6 build 0910 KOSRDK Knight OPC Server Rapid Development Toolkits Knight Workgroup, eehoo Technology 2002-9 OPC 1...4 2 API...5 2.1...5 2.2...5 2.2.1 KOS_Init...5 2.2.2 KOS_InitB...5 2.2.3

More information

ARM JTAG实时仿真器安装使用指南

ARM JTAG实时仿真器安装使用指南 ARM JTAG Version 1.31 2003. 11. 12 ARM JTAG ARM JTAG.3 ARM 2.1.4 2.2.4 ARM JTAG 3.1 18 3.2 18 3.2.1 Multi-ICE Server.18 3.2.2 ADS..21 ARM JTAG 4.1 Multi-ICE Server 33 4.1.1 Multi-ICE Server..... 33 4.1.2

More information

Cadence SPB 15.2 VOICE Cadence SPB 15.2 PC Cadence 3 (1) CD1 1of 2 (2) CD2 2of 2 (3) CD3 Concept HDL 1of 1

Cadence SPB 15.2 VOICE Cadence SPB 15.2 PC Cadence 3 (1) CD1 1of 2 (2) CD2 2of 2 (3) CD3 Concept HDL 1of 1 Cadence SPB 15.2 VOICE 2005-05-07 Cadence SPB 15.2 PC Cadence 3 (1) CD1 1of 2 (2) CD2 2of 2 (3) CD3 Concept HDL 1of 1 1 1.1 Cadence SPB 15.2 2 Microsoft 1.1.1 Windows 2000 1.1.2 Windows XP Pro Windows

More information

6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM

6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM CHAPTER 6 SQL SQL SQL 6-1 Table Column Data Type Row Record 1. DBMS 2. DBMS MySQL Microsoft Access SQL Server Oracle 3. ODBC SQL 1. Structured Query Language 2. IBM 3. 1986 10 ANSI SQL ANSI X3. 135-1986

More information

RUN_PC連載_12_.doc

RUN_PC連載_12_.doc PowerBuilder 8 (12) PowerBuilder 8.0 PowerBuilder PowerBuilder 8 PowerBuilder 8 / IDE PowerBuilder PowerBuilder 8.0 PowerBuilder PowerBuilder PowerBuilder PowerBuilder 8.0 PowerBuilder 6 PowerBuilder 7

More information

1 SQL Server 2005 SQL Server Microsoft Windows Server 2003NTFS NTFS SQL Server 2000 Randy Dyess DBA SQL Server SQL Server DBA SQL Server SQL Se

1 SQL Server 2005 SQL Server Microsoft Windows Server 2003NTFS NTFS SQL Server 2000 Randy Dyess DBA SQL Server SQL Server DBA SQL Server SQL Se 1 SQL Server 2005 DBA Microsoft SQL Server SQL ServerSQL Server SQL Server SQL Server SQL Server SQL Server 2005 SQL Server 2005 SQL Server 2005 o o o SQL Server 2005 1 SQL Server 2005... 3 2 SQL Server

More information

目次 

目次  軟 體 工 程 期 末 報 告 網 路 麻 將 91703014 資 科 三 黃 偉 嘉 91703024 資 科 三 丘 祐 瑋 91703030 資 科 三 江 致 廣 1 目 次 壹 前 言 (Preface) P.4 貳 計 畫 簡 述 及 預 期 效 益 (Project Description and Expected Results) P.4 參 系 統 開 發 需 求 (System

More information

Table of Contents Design Concept 03 Copyrights & TradeMark 04 Special Notice 05 Notice to concerned 05 Installation and Registration Introduction 07 s

Table of Contents Design Concept 03 Copyrights & TradeMark 04 Special Notice 05 Notice to concerned 05 Installation and Registration Introduction 07 s MapAsia MapKing TM User Guide Full Function Version (Pocket PC and PC) For Microsoft Pocket PC/ Pocket PC 2002/2003 Microsoft Windows XP/2000/Me/98 Edition 2004 ( : ) 2002-2004, MapAsia.com Limited Table

More information

AL-M200 Series

AL-M200 Series NPD4754-00 TC ( ) Windows 7 1. [Start ( )] [Control Panel ()] [Network and Internet ( )] 2. [Network and Sharing Center ( )] 3. [Change adapter settings ( )] 4. 3 Windows XP 1. [Start ( )] [Control Panel

More information

mvc

mvc Build an application Tutor : Michael Pan Application Source codes - - Frameworks Xib files - - Resources - ( ) info.plist - UIKit Framework UIApplication Event status bar, icon... delegation [UIApplication

More information

1.ai

1.ai HDMI camera ARTRAY CO,. LTD Introduction Thank you for purchasing the ARTCAM HDMI camera series. This manual shows the direction how to use the viewer software. Please refer other instructions or contact

More information

Learning Java

Learning Java Java Introduction to Java Programming (Third Edition) Prentice-Hall,Inc. Y.Daniel Liang 2001 Java 2002.2 Java2 2001.10 Java2 Philip Heller & Simon Roberts 1999.4 Java2 2001.3 Java2 21 2002.4 Java UML 2002.10

More information

言1.PDF

言1.PDF MSP430 WINDOWS WORKBENCH MSP430 Flash Green MCU Flash Flash MCU MSP430 16 RISC 27 125ns 1.8V~3.6V A/D 6 s MSP430 10 ESD MSP430 MSP430 10 MSP430 2001 MSP430 Windows Workbench Interface Guide Windows Workbench

More information

Microsoft PowerPoint - ARC110_栾跃.ppt

Microsoft PowerPoint - ARC110_栾跃.ppt ARC110 软 件 构 架 设 计 的 原 则 和 指 南 课 程 内 容 概 述 介 绍 和 引 言 软 件 构 架 和 构 架 师 软 件 构 架 的 设 计 模 式 框 架 和 参 照 设 计 自 我 介 绍 第 一 代 自 费 留 学 生 : 美 国 南 伊 利 诺 州 立 大 学 (SIUE) 电 机 工 程 学 士 (1984) 及 硕 士 学 位 (1985) 历 任 OwensIllinois,

More information

untitled

untitled 2006 6 Geoframe Geoframe 4.0.3 Geoframe 1.2 1 Project Manager Project Management Create a new project Create a new project ( ) OK storage setting OK (Create charisma project extension) NO OK 2 Edit project

More information

Oracle Oracle Solaris Studio IDE makefile C C++ Fortran makefile IDE Solaris Linux C/C++/Fortran Oracle IDE "P

Oracle Oracle Solaris Studio IDE makefile C C++ Fortran makefile IDE Solaris Linux C/C++/Fortran Oracle IDE P Oracle Solaris Studio 12.3 IDE 2011 12 E26461-01 2 7 8 9 9 Oracle 10 12 14 21 26 27 29 31 32 33 Oracle Solaris Studio IDE makefile C C++ Fortran makefile IDE Solaris Linux C/C++/Fortran Oracle IDE "Project

More information

epub 61-2

epub 61-2 2 Web Dreamweaver UltraDev Dreamweaver 3 We b We b We Dreamweaver UltraDev We b Dreamweaver UltraDev We b We b 2.1 Web We b We b D r e a m w e a v e r J a v a S c r i p t We b We b 2.1.1 Web We b C C +

More information

Microsoft Word - 11.doc

Microsoft Word - 11.doc 除 錯 技 巧 您 將 於 本 章 學 到 以 下 各 項 : 如 何 在 Visual C++ 2010 的 除 錯 工 具 控 制 下 執 行 程 式? 如 何 逐 步 地 執 行 程 式 的 敘 述? 如 何 監 看 或 改 變 程 式 中 的 變 數 值? 如 何 監 看 程 式 中 計 算 式 的 值? 何 謂 Call Stack? 何 謂 診 斷 器 (assertion)? 如 何

More information

2/80 2

2/80 2 2/80 2 3/80 3 DSP2400 is a high performance Digital Signal Processor (DSP) designed and developed by author s laboratory. It is designed for multimedia and wireless application. To develop application

More information

WinMDI 28

WinMDI 28 WinMDI WinMDI 2 Region Gate Marker Quadrant Excel FACScan IBM-PC MO WinMDI WinMDI IBM-PC Dr. Joseph Trotter the Scripps Research Institute WinMDI HP PC WinMDI WinMDI PC MS WORD, PowerPoint, Excel, LOTUS

More information

声 明 本 公 司 及 全 体 董 事 监 事 高 级 管 理 人 员 承 诺 不 存 在 任 何 虚 假 记 载 误 导 性 陈 述 或 重 大 遗 漏, 并 对 其 真 实 性 准 确 性 完 整 性 承 担 个 别 和 连 带 的 法 律 责 任 本 公 司 负 责 人 和 主 管 会 计 工

声 明 本 公 司 及 全 体 董 事 监 事 高 级 管 理 人 员 承 诺 不 存 在 任 何 虚 假 记 载 误 导 性 陈 述 或 重 大 遗 漏, 并 对 其 真 实 性 准 确 性 完 整 性 承 担 个 别 和 连 带 的 法 律 责 任 本 公 司 负 责 人 和 主 管 会 计 工 Shenzhen WitSoft Information Technology Co., Ltd. 主 办 券 商 二 〇 一 六 年 二 月 声 明 本 公 司 及 全 体 董 事 监 事 高 级 管 理 人 员 承 诺 不 存 在 任 何 虚 假 记 载 误 导 性 陈 述 或 重 大 遗 漏, 并 对 其 真 实 性 准 确 性 完 整 性 承 担 个 别 和 连 带 的 法 律 责 任 本

More information

<4D6963726F736F667420506F776572506F696E74202D20C8EDBCFEBCDCB9B9CAA6D1D0D0DEBDB2D7F92E707074>

<4D6963726F736F667420506F776572506F696E74202D20C8EDBCFEBCDCB9B9CAA6D1D0D0DEBDB2D7F92E707074> 软 件 架 构 师 研 修 讲 座 胡 协 刚 软 件 架 构 师 UML/RUP 专 家 szjinco@public.szptt.net.cn 中 国 软 件 架 构 师 网 东 软 培 训 中 心 小 故 事 : 七 人 分 粥 当 前 软 件 团 队 的 开 发 现 状 和 面 临 的 问 题 软 件 项 目 的 特 点 解 决 之 道 : 从 瀑 布 模 型 到 迭 代 模 型 解 决 项

More information

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO Car DVD New GUI IR Flow User Manual V0.1 Jan 25, 2008 19, Innovation First Road Science Park Hsin-Chu Taiwan 300 R.O.C. Tel: 886-3-578-6005 Fax: 886-3-578-4418 Web: www.sunplus.com Important Notice SUNPLUS

More information

Oracle Solaris Studio makefile C C++ Fortran IDE Solaris Linux C/C++/Fortran IDE "Project Properties" IDE makefile 1.

Oracle Solaris Studio makefile C C++ Fortran IDE Solaris Linux C/C++/Fortran IDE Project Properties IDE makefile 1. Oracle Solaris Studio 12.2 IDE 2010 9 2 8 9 10 11 13 20 26 28 30 32 33 Oracle Solaris Studio makefile C C++ Fortran IDE Solaris Linux C/C++/Fortran IDE "Project Properties" IDE makefile 1. "File" > "New

More information

BOOL EnumWindows(WNDENUMPROC lparam); lpenumfunc, LPARAM (Native Interface) PowerBuilder PowerBuilder PBNI 2

BOOL EnumWindows(WNDENUMPROC lparam); lpenumfunc, LPARAM (Native Interface) PowerBuilder PowerBuilder PBNI 2 PowerBuilder 9 PowerBuilder Native Interface(PBNI) PowerBuilder 9 PowerBuilder C++ Java PowerBuilder 9 PBNI PowerBuilder Java C++ PowerBuilder NVO / PowerBuilder C/C++ PowerBuilder 9.0 PowerBuilder Native

More information

RUN_PC連載_10_.doc

RUN_PC連載_10_.doc PowerBuilder 8 (10) Jaguar CTS ASP Jaguar CTS PowerDynamo Jaguar CTS Microsoft ASP (Active Server Pages) ASP Jaguar CTS ASP Jaguar CTS ASP Jaguar CTS ASP Jaguar CTS ASP Jaguar CTS ASP Jaguar Server ASP

More information

第 15 章 程 式 編 写 語 言 15.1 程 式 編 写 語 言 的 角 色 程 式 編 寫 語 言 是 程 式 編 寫 員 與 電 腦 溝 通 的 界 面 語 法 是 一 組 規 則 讓 程 式 編 寫 員 將 字 詞 集 合 起 來 電 腦 是 處 理 位 元 和 字 節 的 機 器, 與

第 15 章 程 式 編 写 語 言 15.1 程 式 編 写 語 言 的 角 色 程 式 編 寫 語 言 是 程 式 編 寫 員 與 電 腦 溝 通 的 界 面 語 法 是 一 組 規 則 讓 程 式 編 寫 員 將 字 詞 集 合 起 來 電 腦 是 處 理 位 元 和 字 節 的 機 器, 與 程 式 編 写 語 言 在 完 成 這 章 後, 你 將 能 夠 了 解 程 式 編 写 語 言 的 功 能 了 解 高 階 語 言 和 低 階 語 言 之 間 的 分 別 知 道 翻 譯 程 式 的 意 義 和 能 夠 把 翻 譯 程 式 分 類 為 : 匯 編 程 式 編 譯 程 式 和 解 譯 程 式 認 識 不 同 翻 譯 程 式 的 優 點 和 缺 點 程 式 是 指 揮 電 腦 的 指

More information

Windows XP

Windows XP Windows XP What is Windows XP Windows is an Operating System An Operating System is the program that controls the hardware of your computer, and gives you an interface that allows you and other programs

More information

audiogram3 Owners Manual

audiogram3 Owners Manual USB AUDIO INTERFACE ZH 2 AUDIOGRAM 3 ( ) * Yamaha USB Yamaha USB ( ) ( ) USB Yamaha (5)-10 1/2 AUDIOGRAM 3 3 MIC / INST (XLR ) (IEC60268 ): 1 2 (+) 3 (-) 2 1 3 Yamaha USB Yamaha Yamaha Steinberg Media

More information

ebook140-11

ebook140-11 11 VPN Windows NT4 B o r d e r M a n a g e r VPN VPN V P N V P N V P V P N V P N TCP/IP 11.1 V P N V P N / ( ) 11.1.1 11 V P N 285 2 3 1. L A N LAN V P N 10MB 100MB L A N VPN V P N V P N Microsoft PPTP

More information

2 SGML, XML Document Traditional WYSIWYG Document Content Presentation Content Presentation Structure Structure? XML/SGML 3 2 SGML SGML Standard Gener

2 SGML, XML Document Traditional WYSIWYG Document Content Presentation Content Presentation Structure Structure? XML/SGML 3 2 SGML SGML Standard Gener SGML HTML XML 1 SGML XML Extensible Markup Language XML SGML Standard Generalized Markup Language, ISO 8879, SGML HTML ( Hypertext Markup Language HTML) (Markup Language) (Tag) < > Markup (ISO) 1986 SGML

More information

128 ( ) ( ) [ 1 ] [2] [3] (1) (2) (3) [1] [2] [3] 10 2 ( ) (1997.6) ( ) 64

128 ( ) ( ) [ 1 ] [2] [3] (1) (2) (3) [1] [2] [3] 10 2 ( ) (1997.6) ( ) 64 BIBLID 1026-5279 (2005) 94:2 p. 127-154 (2005.12) 127 Keywords Digital Library High School Library Library Website Open Source E-mail frank@hchs.hc.edu.tw 128 (2005.12) ( ) 6 0 68 [ 1 ] [2] [3] (1) (2)

More information

D C 93 2

D C 93 2 D9223468 3C 93 2 Java Java -- Java UML Java API UML MVC Eclipse API JavadocUML Omendo PSPPersonal Software Programming [6] 56 8 2587 56% Java 1 epaper(2005 ) Java C C (function) C (reusability) eat(chess1,

More information

前言 C# C# C# C C# C# C# C# C# microservices C# More Effective C# More Effective C# C# C# C# Effective C# 50 C# C# 7 Effective vii

前言 C# C# C# C C# C# C# C# C# microservices C# More Effective C# More Effective C# C# C# C# Effective C# 50 C# C# 7 Effective vii 前言 C# C# C# C C# C# C# C# C# microservices C# More Effective C# More Effective C# C# C# C# Effective C# 50 C# C# 7 Effective vii C# 7 More Effective C# C# C# C# C# C# Common Language Runtime CLR just-in-time

More information

FAQ -PowerDesigner9.5.DOC

FAQ -PowerDesigner9.5.DOC PowerDesigner 9.5 FAQ 1. PowerDesigner PowerDesigner PowerDesigner (CASE Tool,Computer Aided Software Engineering) PowerDesigner 1989 9.5 2. PowerDesigner PowerDesigner Internet ( Java) PowerDesigner 7.0

More information

RunPCPB8 new feature.PDF

RunPCPB8 new feature.PDF Client/Server Web N-Tier PowerBuilder 8.0 PowerBuilder 8.0 IDE Client/Server Web PowerBuilder / Web-based IT IDE PowerBuilder PowerBuilder 8.0 PowerBuilder 8.0 PowerBuilder 8.0 PowerBuilder Sybase PowerBuilder

More information

untitled

untitled 21 Visual FoxPro Visual FoxPro 6.0 11 Visual FoxPro Visual FoxPro CIP Visual FoxPro 2004 21 ISBN 7-03-014834-7 V Visual FoxPro TP311.138 CIP 2004 143035 16 100717 http://www.sciencep.com * 2004 12 7871092

More information

RunPC2_.doc

RunPC2_.doc PowerBuilder 8 (5) PowerBuilder Client/Server Jaguar Server Jaguar Server Connection Cache Thin Client Internet Connection Pooling EAServer Connection Cache Connection Cache Connection Cache Connection

More information

untitled

untitled MySQL DBMS under Win32 Editor: Jung Yi Lin, Database Lab, CS, NCTU, 2005/09/16 MySQL 料 理 MySQL 兩 Commercial License 利 GPL MySQL http://www.mysql.com Developer Zone http://www.mysql.com Download 連 連 MySQL

More information

Microsoft Word zw

Microsoft Word zw 第 1 章 Android 概述 学习目标 : Android Android Android Studio Android Android APK 1.1 1. 智能手机的定义 Smartphone 2. 智能手机的发展 1973 4 3 PC IBM 1994 IBM Simon PDA PDA Zaurus OS 1996 Nokia 9000 Communicator Nokia 9000

More information

ICD ICD ICD ICD ICD

ICD ICD ICD ICD ICD MPLAB ICD2 MPLAB ICD2 PIC MPLAB-IDE V6.0 ICD2 usb PC RS232 MPLAB IDE PC PC 2.0 5.5V LED EEDATA MPLAB ICD2 Microchip MPLAB-IDE v6.0 Windows 95/98 Windows NT Windows 2000 www.elc-mcu.com 1 ICD2...4 1.1 ICD2...4

More information

Microsoft Word - OPIGIMAC 譯本.doc

Microsoft Word - OPIGIMAC 譯本.doc OPISYSTEMS OPIGIMAC 系 統 使 用 說 明 使 用 者 手 冊 OPI 版 本 7.0.X 140705 翻 譯 版 本 V1.0 Table of Contents 頁 數 1. 簡 介 3 2. 系 統 需 求 4 3. 安 裝 4 4. 開 始 OPIGIMAC 5 5. 功 能 列 7 6. 功 能 圖 示 鍵 10 7. 重 點 操 作 說 明 13 7-1. 設 定

More information

NEXT SDT2.51 C:\ARM251 SDT2.51 ARM SDT 2.51 ARM PROJECT MANAGER SDT 2

NEXT SDT2.51 C:\ARM251 SDT2.51 ARM SDT 2.51 ARM PROJECT MANAGER SDT 2 S3C44B0 SDT DRAGNBOY MICROSTAR ARM 51 ARM S3C44B0 ARM SDT2.51 IAR ADS SDT2.51 S3C44B0 LEDTEST SDT ARM 1 2 SDT embed.8800.org SDT2.51 SDT2.51 ARM ARM CPU ARM SDT ADS ADS MULTI-ICE SDT JTAG JTAG SDT SDT2.51

More information

投影片 1

投影片 1 9 1 9-1 Windows XP Windows Server 2003 Mac OS Linux, 都 (OS, Operating System ) 2 3 , 來, 行 3 理 行 4 ,, (UI, User Interface), 滑, 令 列 (CLI, Command-Line Interface) (GUI, Graphical User Interface) 2 5 令 列,

More information

IC-900W Wireless Pan & Tilt Wireless Pan & Tilt Remote Control / Night Vision FCC ID:RUJ-LR802UWG

IC-900W Wireless Pan & Tilt Wireless Pan & Tilt Remote Control / Night Vision FCC ID:RUJ-LR802UWG IC-900W Wireless Pan & Tilt Wireless Pan & Tilt Remote Control / Night Vision FCC ID:RUJ-LR802UWG --------------------------------------------TABLE OF CONTENTS------------------------------------------

More information

f2.eps

f2.eps 前 言, 目 录 产 品 概 况 1 SICAM PAS SICAM 电 力 自 动 化 系 统 配 置 和 使 用 说 明 配 置 2 操 作 3 实 时 数 据 4 人 机 界 面 5 SINAUT LSA 转 换 器 6 状 态 与 控 制 信 息 A 版 本 号 : 08.03.05 附 录, 索 引 安 全 标 识 由 于 对 设 备 的 特 殊 操 作 往 往 需 要 一 些 特 殊 的

More information

EJB-Programming-3.PDF

EJB-Programming-3.PDF :, JBuilder EJB 2.x CMP EJB Relationships JBuilder EJB Test Client EJB EJB Seminar CMP Entity Beans Value Object Design Pattern J2EE Design Patterns Value Object Value Object Factory J2EE EJB Test Client

More information

Guide to Install SATA Hard Disks

Guide to Install SATA Hard Disks SATA RAID 1. SATA. 2 1.1 SATA. 2 1.2 SATA 2 2. RAID (RAID 0 / RAID 1 / JBOD).. 4 2.1 RAID. 4 2.2 RAID 5 2.3 RAID 0 6 2.4 RAID 1.. 10 2.5 JBOD.. 16 3. Windows 2000 / Windows XP 20 1. SATA 1.1 SATA Serial

More information

59 1 CSpace 2 CSpace CSpace URL CSpace 1 CSpace URL 2 Lucene 3 ID 4 ID Web 1. 2 CSpace LireSolr 3 LireSolr 3 Web LireSolr ID

59 1 CSpace 2 CSpace CSpace URL CSpace 1 CSpace URL 2 Lucene 3 ID 4 ID Web 1. 2 CSpace LireSolr 3 LireSolr 3 Web LireSolr ID 58 2016. 14 * LireSolr LireSolr CEDD Ajax CSpace LireSolr CEDD Abstract In order to offer better image support services it is necessary to extend the image retrieval function of our institutional repository.

More information

VB程序设计教程

VB程序设计教程 高 等 学 校 教 材 Visual Basic 程 序 设 计 教 程 魏 东 平 郑 立 垠 梁 玉 环 石 油 大 学 出 版 社 内 容 提 要 本 书 是 按 高 等 学 校 计 算 机 程 序 设 计 课 程 教 学 大 纲 编 写 的 大 学 教 材, 主 要 包 括 VB 基 础 知 识 常 用 程 序 结 构 和 算 法 Windows 用 户 界 面 设 计 基 础 文 件 处

More information

VIDEOJET connect 7000 VJC-7000-90 zh- CHS Operation Manual VIDEOJET connect 7000 zh-chs 3 目 录 1 浏 览 器 连 接 7 1.1 系 统 要 求 7 1.2 建 立 连 接 7 1.2.1 摄 像 机 中 的 密 码 保 护 7 1.3 受 保 护 的 网 络 7 2 系 统 概 述 8 2.1 实 况

More information

Microsoft Word - Front cover_white.doc

Microsoft Word - Front cover_white.doc Real Time Programme 行 情 报 价 程 序 Seamico Securities Public Company Limited WWW.SEAMICO.COM Table of Content 目 录 开 始 使 用 开 始 使 用 Z Net 程 序 程 序 1 股 票 观 察 者 4 每 日 股 票 按 时 间 的 交 易 查 询 10 多 股 同 列 13 股 票 行 情

More information

ebook111-4

ebook111-4 Flash 4 Flash 4 F l a s h 5 Flash 4 Flash Flash 4 Flash 4 Flash 4 4.1 Flash 4 Flash 4 Flash 4 Flash Flash 4 Flash 4 4.2 Flash 4 Flash 4 A Flash 4 S h i f t F i l e P r e f e r e n c e s > > Flash 4 Flash

More information

Simulator By SunLingxi 2003

Simulator By SunLingxi 2003 Simulator By SunLingxi sunlingxi@sina.com 2003 windows 2000 Tornado ping ping 1. Tornado Full Simulator...3 2....3 3. ping...6 4. Tornado Simulator BSP...6 5. VxWorks simpc...7 6. simulator...7 7. simulator

More information

Business Objects 5.1 Windows BusinessObjects 1

Business Objects 5.1 Windows BusinessObjects 1 Business Objects 5.1 Windows BusinessObjects 1 BusinessObjects 2 BusinessObjects BusinessObjects BusinessObjects Windows95/98/NT BusinessObjects Windows BusinessObjects BusinessObjects BusinessObjects

More information

PaPaGO! PaPaGO! PaPaGO! PaPaGO! PaPaGO! PaPaGO!

PaPaGO! PaPaGO! PaPaGO! PaPaGO! PaPaGO! PaPaGO! PaPaGO! 7 COPILOT Pocket PC Design for Microsoft Pocket PC 2002/2003/2003 se Version 7.3 http://www.mactiotnech.com 1 ...5...6...7...9...11 2.1...11 2.2 PaPaGO!...11 2.3 PaPaGO!...17 2.5...19...20 PaPaGO!...21

More information

untitled

untitled SAP SAP Business One ... 4 SAP Business One... 5... 5 SAP Business One... 7 SAP Business One... 8... 8... 8... 9... 10... 11 mysap Business Suite... 12... 13... 14 Copyright 2004 SAP AG. All rights reserved.

More information

InstallShield InstallShield InstallShield Windows Installer ISWI ISWI InstallShield InstallShield InstallShield Windows Installer WI In

InstallShield InstallShield InstallShield Windows Installer ISWI ISWI InstallShield InstallShield InstallShield Windows Installer WI In InstallShield 1 InstallShield InstallShield InstallShield Windows Installer ISWI ISWI InstallShield InstallShield5 2000 InstallShield2000 2002 Windows Installer WI InstallShield Professional Version 6

More information

(DMO) 1 1 Microsoft Windows SQL Server 2005 SQL Server Analysis ServicesNotification Services SQL Server 8 SQL Server IP SQL Server 2005 SQL Server 20

(DMO) 1 1 Microsoft Windows SQL Server 2005 SQL Server Analysis ServicesNotification Services SQL Server 8 SQL Server IP SQL Server 2005 SQL Server 20 Microsoft.com Go SQL Server Windows Server System > SQL Server 2005 SQL Server 2005 SQL Server TechCenter SQL SQL Server Server 2005 (IT) SQL Server SQL Server 2005 IT SQL Server 2005 SQL Server 2005 SQL

More information

Microsoft PowerPoint - ch6 [相容模式]

Microsoft PowerPoint - ch6 [相容模式] UiBinder wzyang@asia.edu.tw UiBinder Java GWT UiBinder XML UI i18n (widget) 1 2 UiBinder HelloWidget.ui.xml: UI HelloWidgetBinder HelloWidget.java XML UI Owner class ( Composite ) UI XML UiBinder: Owner

More information

IP505SM_manual_cn.doc

IP505SM_manual_cn.doc IP505SM 1 Introduction 1...4...4...4...5 LAN...5...5...6...6...7 LED...7...7 2...9...9...9 3...11...11...12...12...12...14...18 LAN...19 DHCP...20...21 4 PC...22...22 Windows...22 TCP/IP -...22 TCP/IP

More information

EJB-Programming-4-cn.doc

EJB-Programming-4-cn.doc EJB (4) : (Entity Bean Value Object ) JBuilder EJB 2.x CMP EJB Relationships JBuilder EJB Test Client EJB EJB Seminar CMP Entity Beans Session Bean J2EE Session Façade Design Pattern Session Bean Session

More information

CC213

CC213 : (Ken-Yi Lee), E-mail: feis.tw@gmail.com 9 [P.11] : Dev C++ [P.12] : http://c.feis.tw [P.13] [P.14] [P.15] [P.17] [P.23] Dev C++ [P.24] [P.27] [P.34] C / C++ [P.35] 10 C / C++ C C++ C C++ C++ C ( ) C++

More information

1 目 錄 1. 簡 介... 2 2. 一 般 甄 試 程 序... 2 3. 第 一 階 段 的 準 備... 5 4. 第 二 階 段 的 準 備... 9 5. 每 間 學 校 的 面 試 方 式... 11 6. 各 程 序 我 的 做 法 心 得 及 筆 記... 13 7. 結 論..

1 目 錄 1. 簡 介... 2 2. 一 般 甄 試 程 序... 2 3. 第 一 階 段 的 準 備... 5 4. 第 二 階 段 的 準 備... 9 5. 每 間 學 校 的 面 試 方 式... 11 6. 各 程 序 我 的 做 法 心 得 及 筆 記... 13 7. 結 論.. 如 何 準 備 研 究 所 甄 試 劉 富 翃 1 目 錄 1. 簡 介... 2 2. 一 般 甄 試 程 序... 2 3. 第 一 階 段 的 準 備... 5 4. 第 二 階 段 的 準 備... 9 5. 每 間 學 校 的 面 試 方 式... 11 6. 各 程 序 我 的 做 法 心 得 及 筆 記... 13 7. 結 論... 20 8. 附 錄 8.1 推 甄 書 面 資 料...

More information

Oracle 4

Oracle 4 Oracle 4 01 04 Oracle 07 Oracle Oracle Instance Oracle Instance Oracle Instance Oracle Database Oracle Database Instance Parameter File Pfile Instance Instance Instance Instance Oracle Instance System

More information

Microsoft Word - template.doc

Microsoft Word - template.doc HGC efax Service User Guide I. Getting Started Page 1 II. Fax Forward Page 2 4 III. Web Viewing Page 5 7 IV. General Management Page 8 12 V. Help Desk Page 13 VI. Logout Page 13 Page 0 I. Getting Started

More information

高 职 计 算 机 类 优 秀 教 材 书 目 * 序 号 书 号 (ISBN) 书 名 作 者 定 价 出 版 / 印 刷 日 期 ** 配 套 资 源 页 码 计 算 机 基 础 课 1 978-7-111-30658-0 计 算 机 应 用 基 础 刘 升 贵 29.00 2012 年 8 月

高 职 计 算 机 类 优 秀 教 材 书 目 * 序 号 书 号 (ISBN) 书 名 作 者 定 价 出 版 / 印 刷 日 期 ** 配 套 资 源 页 码 计 算 机 基 础 课 1 978-7-111-30658-0 计 算 机 应 用 基 础 刘 升 贵 29.00 2012 年 8 月 高 职 计 算 机 类 优 秀 教 材 书 目 * 序 号 书 号 (ISBN) 书 名 作 者 定 价 出 版 / 印 刷 日 期 ** 配 套 资 源 页 码 计 算 机 基 础 课 1 978-7-111-30658-0 计 算 机 应 用 基 础 刘 升 贵 29.00 2012 年 8 月 电 子 教 案 P1 2 978-7-111-27081-2 计 算 机 应 用 基 础 ( 第 2

More information

Desktop Management Guide

Desktop Management Guide Ĵ* 商 用 台 式 机 文 档 部 件 号 312947-AA1 2003 詤 3 本 指 南 介 绍 了 预 装 在 某 些 机 型 上 的 安 全 保 护 功 能 和 智 能 管 理 功 能 的 定 义 及 使 用 说 明 2002 Hewlett-Packard Company 2002 Hewlett-Packard Development Company, L.P. HP Hewlett

More information

untitled

untitled PowerBuilder Tips 利 PB11 Web Service 年度 2 PB Tips PB9 EAServer 5 web service PB9 EAServer 5 了 便 web service 來說 PB9 web service 力 9 PB11 release PB11 web service 力更 令.NET web service PB NVO 論 不 PB 來說 說

More information

1. 2. Flex Adobe 3.

1. 2. Flex Adobe 3. 1. 2. Flex Adobe 3. Flex Adobe Flex Flex Web Flex Flex Flex Adobe Flash Player 9 /rich Internet applications/ria Flex 1. 2. 3. 4. 5. 6. SWF Flash Player Flex 1. Flex framework Adobe Flex 2 framework RIA

More information

(Microsoft Word - PK254P\262\331\327\366\312\326\262\341.doc)

(Microsoft Word - PK254P\262\331\327\366\312\326\262\341.doc) PROKIN 3.0 软 件 操 作 手 册 TecnoBody S.r.l. Published: Luglio 2006 Code-Version: 1 目 录 第 一 节... 7 介 绍... 7 手 册 中 使 用 的 安 全 标 志 和 符 号... 8 概 述... 10 安 全 规 则... 11 PROKIN 系 统 安 装... 11 系 统 组 成... 13 系 统 安 装

More information

Serial ATA ( Nvidia nforce430)...2 (1) SATA... 2 (2) B I O S S A T A... 3 (3) RAID BIOS RAID... 6 (4) S A T A... 9 (5) S A T A (6) Microsoft Win

Serial ATA ( Nvidia nforce430)...2 (1) SATA... 2 (2) B I O S S A T A... 3 (3) RAID BIOS RAID... 6 (4) S A T A... 9 (5) S A T A (6) Microsoft Win Serial ATA ( Nvidia nforce430)...2 (1) SATA... 2 (2) B I O S S A T A... 3 (3) RAID BIOS RAID... 6 (4) S A T A... 9 (5) S A T A... 11 (6) Microsoft Windows 2000... 14 Ác Åé å Serial ATA ( Nvidia nforce430)

More information

Windows 2000 Server for T100

Windows 2000 Server for T100 2 1 Windows 95/98 Windows 2000 3.5 Windows NT Server 4.0 2 Windows DOS 3.5 T200 2002 RAID RAID RAID 5.1 Windows 2000 Server T200 2002 Windows 2000 Server Windows 2000 Server Windows 2000 Server 3.5 for

More information

Ác Åé å Serial ATA ( Sil3132) S A T A (1) SATA (2) BIOS SATA (3)* RAID BIOS RAID (4) SATA (5) SATA (a) S A T A ( S A T A R A I D ) (b) (c) Windows XP

Ác Åé å Serial ATA ( Sil3132) S A T A (1) SATA (2) BIOS SATA (3)* RAID BIOS RAID (4) SATA (5) SATA (a) S A T A ( S A T A R A I D ) (b) (c) Windows XP Serial ATA ( Sil3132)...2 (1) SATA... 2 (2) B I O S S A T A... 3 (3) RAID BIOS RAID... 6 (4) S A T A... 10 (5) S A T A... 12 Ác Åé å Serial ATA ( Sil3132) S A T A (1) SATA (2) BIOS SATA (3)* RAID BIOS

More information

目 錄 版 次 變 更 記 錄... 2 原 始 程 式 碼 類 型 之 使 用 手 冊... 3 一 安 裝 軟 體 套 件 事 前 準 備... 3 二 編 譯 流 程 說 明... 25 1

目 錄 版 次 變 更 記 錄... 2 原 始 程 式 碼 類 型 之 使 用 手 冊... 3 一 安 裝 軟 體 套 件 事 前 準 備... 3 二 編 譯 流 程 說 明... 25 1 科 技 部 自 由 軟 體 專 案 原 始 程 式 碼 使 用 手 冊 Source Code Manual of NSC Open Source Project 可 信 賴 的 App 安 全 應 用 框 架 -App 應 用 服 務 可 移 轉 性 驗 證 Trusted App Framework -Transferability Verification on App MOST 102-2218-E-011-012

More information

int *p int a 0x00C7 0x00C7 0x00C int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++;

int *p int a 0x00C7 0x00C7 0x00C int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++; Memory & Pointer trio@seu.edu.cn 2.1 2.1.1 1 int *p int a 0x00C7 0x00C7 0x00C7 2.1.2 2 int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++; 2.1.3 1. 2. 3. 3 int A,

More information

T1028_Manual_KO_V3 0.pdf

T1028_Manual_KO_V3 0.pdf 2009 : 2009/09 PC Microsoft, MS-DOS, Windows, Windows Sound System Microsoft Corporation Intel, Atom Intel Corporation Sound Blaster, Sound Blaster ProCreative Technology I AC AC AC AC AC - 115 V/60 Hz

More information

CH01.indd

CH01.indd 3D ios Android Windows 10 App Apple icloud Google Wi-Fi 4G 1 ( 3D ) 2 3 4 5 CPU / / 2 6 App UNIX OS X Windows Linux (ios Android Windows 8/8.1/10 BlackBerry OS) 7 ( ZigBee UWB) (IEEE 802.11/a/b/g/n/ad/ac

More information

提纲 1 2 OS Examples for 3

提纲 1 2 OS Examples for 3 第 4 章 Threads2( 线程 2) 中国科学技术大学计算机学院 October 28, 2009 提纲 1 2 OS Examples for 3 Outline 1 2 OS Examples for 3 Windows XP Threads I An Windows XP application runs as a seperate process, and each process may

More information

Java 1 Java String Date

Java 1 Java String Date JAVA SCJP Java 1 Java String Date 1Java 01 Java Java 1995 Java Java 21 Java Java 5 1-1 Java Java 1990 12 Patrick Naughton C++ C (Application Programming Interface API Library) Patrick Naughton NeXT Stealth

More information

untitled

untitled Ogre Rendering System http://antsam.blogone.net AntsamCGD@hotmail.com geometry systemmaterial systemshader systemrendering system API API DirectX OpenGL API Pipeline Abstraction API Pipeline Pipeline configurationpipeline

More information

10384 X2009230010 UDC The Design and Implementation of Small and Medium-sized Courier Company Logistics Vehicle Scheduling System 2012 06 Abstract With the arrival of the information age, tremendous

More information

untitled

untitled \ \ \ DOP11B 06/2011 16929837 / ZH SEW-EURODRIVE Driving the world 1 5 1.1 5 1.2 5 1.3 6 1.4 6 1.5 6 1.6 6 1.7 6 2 7 2.1 7 2.2 7 2.3 8 2.4 8 2.5 8 2.6 9 2.7 / 11 2.8 11 2.9 11 2.10 11 2.11 12 3 (DOP11B-10

More information

1500XA Daniel Danalyzer 1500XA Rosemount Analytical 1500XA P/N 3-9000-757 A 2010 5 ii 1500XA 1500XA iii iv 1500XA : 1-2 1500XA - 1500XA 1-3 1-4 1500XA 1500XA 1-5 1-6 1500XA 1500XA 1-7 1-8 1500XA

More information

CL-S10w

CL-S10w Data Management Software CL-S10w WindowsWindows XP Microsoft Windows XP Professional Operating System WindowsWindows 7 Microsoft Windows 7 Professional Operating System Excel Microsoft Excel MicrosoftWindowsWindows

More information

Microsoft Word - Web Dynpro For ABAP跟踪测试工具简介 _2_.doc

Microsoft Word - Web Dynpro For ABAP跟踪测试工具简介 _2_.doc Web Dynpro For ABAP 跟 踪 测 试 工 具 简 介 概 述 从 传 统 ABAP UI 开 发 ( 如 Dynpro,ABAP List 等 等 ) 直 接 转 到 Web Dynpro For ABAP 开 发 来, 我 们 可 能 会 发 现 那 些 传 统 的 跟 踪 测 试 工 具 ( 如 SAT, 也 许 SAAB 还 是 一 个 简 单 易 用 的 合 适 的 工 具

More information

穨IC-1000

穨IC-1000 IC-1000 LEDOMARS Information Coporation :(02)27913828 :(02)27945895 (04)2610628 (04)2650852 (07)3897016 (07)3897165 http://www.ledomars.com.tw 1 1. IC-1000 2. IC-1000 LED : ERROR LNK/ACT PWR TEST PWR(Power)

More information