来源:网络 2012-07-15 关键词:组态软件 控制系统 自动化 CAN-bus 总线  摘要:介绍一种利用组态软件MCGS 和OPC 服务器ZOPC_SERVER 以及ZLGCAN 接口卡实现基于CAN-bus 总线的模拟空调温/湿度控制系统的方法。  一、系统结构  本系统是一个室内空调温/湿度控制系统的模拟系统数据采集及控制中心通过CAN-bus 总线定时采集各个房间的温/湿度数据,并对各个房间的温/湿度进行控制。系统的数据采集及控制中心由上位机的硬件即任一款ZLGCAN 系列接口卡和PC 构成,软件由组态软件MCGS 和ZOPC_Server 组成。控制室即下位机由DP-668 实验仪和ZLGCAN 系列接口卡中的PCI-9810 接口卡模拟。  

  二、MCGS 工程框架  本空调温/湿度控制系统需要对各个控制室及风道的温/湿度值进行监控,因此工程需要有实时显示和记录各控制室温/湿度值、修改房间温/湿度SV 值、报警显示、报警显示浏览记录等功能、工程框架如下:  用户窗口:封面窗口、主控窗口、控制室窗口1~6、风道平面图、状态条、修改控制室1~6 SV值、修改SV 值消息窗口、风道电加热段消息窗口、修改风道温度表1~2 SV 值、修改风道湿度表1~2 SV 值、风道内三级加热报警窗口。  运行策略:启动策略、退出策略、循环策略、卡车运动策略、控制柜灯闪烁策略、显示控制室1~6策略、显示时间策略、主控窗口中提示块显示策略。  主菜单:用户登录、封面窗口、打开主控窗口、打开各控制室、风道平面图、修改SV 值、历史记录、通信错误记录、温/湿度异常记录、退出系统  子菜单:第一~六控制室、修改一~六号房间、SV 值修改风道温度、表1~2 SV 值、修改风道湿度表1~2 SV 值  三、主要数据对象  建立好一个空调温/湿度控制系统的MCGS 工程后,实现上位机的主要任务就是建立组态工程与OPC设备的连接,实现上位机的主要任务就是建立组态工程与OPC设备的连接,并对采集到的数据进行处理和显示。在这个工程的实时数据库中,要进行显示、操作的数据对象如表1 所示。由于风道的数据对象较多,为了统一管理,将风道当作两个房间节点来处理。这样,每个房间都只有1 个温度值对象、1 个湿度之对象、1 个温度SV 值对象和1 个湿度SV 值对象。表 1 系统主要数据对象  为了使系统具备记录数据及浏览历史数据、错误数据和异常数据的能力,在实时数据库中建立了save、ErrorSave 和exception 三个数据对象组。其中ErrorSave 和exception 的组对象成员有:RoomID1 、ErrorTemp、ErrorHum 、ErrorTempSV 和ErrorHumSV ,save 的组成员对象如表1 所示。在运行过程中,系统会定时保存save 组对象到数据库。当通信产生错误和房间温/湿度异常时,系统会将ErrorSave 和exception 保存到数据库。  在此系统中的OPC 设备使用的是ZOPC_Server 服务器。ZOPC_Server 是一个OPC 服务器软件本软件,支持操作全系列的ZLGCAN 系列接口卡,只要在一台PC 机上插上ZLGCAN 系列接口卡中的任何一种或几种,再运行本服务器软件,就可以使用任何一种支持OPC 协议的客户端软件(比如组态软件:组态王KingView、昆仑通态MCGS 和Intouch 等)来连接到此服务器通过此服务器,来跟CAN-bus 网络进行数据的传输。  本设计中,ZOPC_Server 在数值存储模式下和字符串存储模式下提供的数据项都不能直接连接到实时数据库中的数据对象,因此必须编写脚本程序对数据进行处理。关于数据项存储模式,这里选用被推荐的字符串存储模式;但是,使用数值存储模式会更容易实现此系统。  在实时数据库添加字符型数据对象In_CANData 和Out_CANData,字符数为30,将In_CANData 和Out_CANData 分别连接到OPC 设备的输入通道和输出通道,In_CANData 的读写属性为只读,Out_CANData的读写属性为只写。由于这两个数据对象是字符型的,不便于进行数据处理,所以应该先将它们转换为数值型对象。在MCGS 脚本程序中,用户不能定义子程序、子函数和变量,而数据对象可以看作是脚本程序中的全局变量,在所有的程序段共用。这给编写较复杂的脚本程序带来不便。要进行类似子程序和子函数的操作,只能用先将要处理的数据放入全局变量,然后调用策略行中的脚本进行处理,最后将返回的数据放入全局变量的方法进行处理。在实时数据库加添以下数值型对象作为中间变量:  

  然后,在运行策略中新建一个名为StringToObject 的用户策略,新增一策略行并添加以下脚本程序,用于将In_CANData 转换到数值型对象:  In_Flag = !Hex2I(!mid(In_CANData, 1, 2))  In_Extern = !Hex2I(!mid(In_CANData, 3, 1))  In_Remote = !Hex2I(!mid(In_CANData, 4, 1))  In_ID = !Hex2I(!mid(In_CANData, 5, 8))  In_DataLen = !Hex2I(!mid(In_CANData, 13 , 2))  In_Data0 = !Hex2I(!mid(In_CANData, 15, 2))  In_Data1 = !Hex2I(!mid(In_CANData, 17, 2))  In_Data2 = !Hex2I(!mid(In_CANData, 19, 2))  In_Data3 = !Hex2I(!mid(In_CANData, 21, 2))  In_Data4 = !Hex2I(!mid(In_CANData, 23, 2))  In_Data5 = !Hex2I(!mid(In_CANData, 25, 2))  In_Data6 = !Hex2I(!mid(In_CANData, 27, 2))  In_Data7 = !Hex2I(!mid(In_CANData, 29, 2))  同样,在运行策略中新建一个名为ObjectToString 的用户策略,新增一策略行并添加下面的脚本程序,用于将数值型对象转换到Out_CANData 。在下面的程序中,Out_SendID 进行自加是因为ZOPC_Server 要判断写入的Out_SendID 和上一次写入的Out_SendID 是否相同,如果不同才将报文发出。  ‘ 转换 Out_SendID 到字符型  if Out_SendID <= 255 then  Out_SendID = Out_SendID + 1  else  Out_SendID = 0  endif  Out_CANData1 = !I2Hex(Out_SendID)  if !Len(Out_CANData1) <> 2 then Out_CANData1 = "0" + Out_CANData1  Out_CANData2 = Out_CANData1  ‘ 转换 Out_Extern 和 Out_Remote 到字符型  Out_CANData1 = !I2Hex(Out_Extern) + !I2Hex(Out_Remote)  Out_CANData2 = Out_CANData2 + Out_CANData1  ‘ 转换 Out_ID 到字符型  Out_CANData1 = !I2Hex(Out_ID)  Lenght = !Len(Out_CANData1)  while Lenght < 8  Out_CANData1 = "0" + Out_CANData1  Lenght = !Len(Out_CANData1)  endwhile  Out_CANData2 = Out_CANData2 + Out_CANData1  ‘ 转换 Out_DataLen 到字符型  Out_CANData1 = !I2Hex(Out_DataLen)  if !Len(Out_CANData1) <> 2 then Out_CANData1 = "0" + Out_CANData1  Out_CANData2 = Out_CANData2 + Out_CANData1  ‘ 转换 Out_Data0 7 到字符型  Out_CANData1 = !I2Hex(Out_Data0)  if !Len(Out_CANData1) <> 2 then Out_CANData1 = "0" + Out_CANData1  Out_CANData2 = Out_CANData2 + Out_CANData1  Out_CANData1 = !I2Hex(Out_Data1)  if !Len(Out_CANData1) <> 2 then Out_CANData1 = "0" + Out_CANData1  Out_CANData2 = Out_CANData2 + Out_CANData1  Out_CANData1 = !I2Hex(Out_Data2)  if !Len(Out_CANData1) <> 2 then Out_CANData1 = "0" + Out_CANData1  Out_CANData2 = Out_CANData2 + Out_CANData1  Out_CANData1 = !I2Hex(Out_Data3)  if !Len(Out_CANData1) <> 2 then Out_CANData1 = "0" + Out_CANData1  Out_CANData2 = Out_CANData2 + Out_CANData1  Out_CANData1 = !I2Hex(Out_Data4)  if !Len(Out_CANData1) <> 2 then Out_CANData1 = "0" + Out_CANData1  Out_CANData2 = Out_CANData2 + Out_CANData1  Out_CANData1 = !I2Hex(Out_Data5)  if !Len(Out_CANData1) <> 2 then Out_CANData1 = "0" + Out_CANData1  Out_CANData2 = Out_CANData2 + Out_CANData1  Out_CANData1 = !I2Hex(Out_Data6)  if !Len(Out_CANData1) <> 2 then Out_CANData1 = "0" + Out_CANData1  Out_CANData2 = Out_CANData2 + Out_CANData1  Out_CANData1 = !I2Hex(Out_Data7)  if !Len(Out_CANData1) <> 2 then Out_CANData1 = "0" + Out_CANData1  Out_CANData2 = Out_CANData2 + Out_CANData1  这样,以后要将In_CANData 的数据提取到In_*变量中,只需在脚本中按!setstgy(StringToObject)就可以了。而要将Out_*数据合并到Out_CANData, 可先调用!setstgy(ObjectToString),然后再把Out_CANData2的值赋给Out_CANData。  四、协议及报文格式  因为MCGS 不便于编写复杂的脚本程序,所以传输协议的设计以简单为原则。本系统使用HiLon 协议A 。HiLon 协议A 是一个通用的协议,基于非对称型主从式网络结构,支持广播和点对点传送命令数据,命令数据包可长达256 字节,非常适合用作本系统的通信协议HiLon 协议以CAN2.0A 帧结构为基础。下图是帧报文格式,一个CAN2.0A 标准帧由11 位ID 、1 位RTR 、4 位DLC 、数据区(最多8 个字节)组成。  DIR :方向位。方向位决定一半的优先级而剩余的优先级,由节点地址决定低地址优先级高。当方向位为“1” 时,地址域是源节点地址(从节点到主节点),优先级由地址决定;当方向位为“0” 时,地址域是目标节点地址(主节点到从节点),优先级由地址决定。从节点也可使用地址滤波技术从而减少需处理的网络信息量,因而能有效节省CAN 节点控制器资源,提高控制器效率。  Address :目标地址,表示节点地址,范围只能设定为0~125  TYPE :帧类型。见下表中的帧类型说明。  

  DLC: 每帧字节数(1~8)  Index :索引字节。对于单帧数据,该字节表示传输数据的第一个字节;对于多帧数据,此字节表示索引字节,即此帧数据在数据包中的位置。  Data :数据  在本系统中,数据中心要对各个房间的温/湿度进行监控并修各个房间的温/湿度SV 值,因此给各个控制室分配唯一的标志符;在下位机向上位机发送的数据报文中携带的数据是房间的温/湿度值;上位机向下位机发送的命令报文携带命令号及控制室的温/湿度SV 值。本系统的传输数据量较小,且MCGS 的采样周期本系统取5ms 相对下位机来说较长,因此,本系统选择使用单帧(点对点)类型帧。利用HiLon报文的特点,将7 位Address 分配给房间ID,每一个房间ID 对应一个Address ,地址0 保留。当数据方向是从节点到主节点时,8 字节数据的前4 字节用于传递房间温度,后4 字节用于传递房间湿度,当数据方向是主节点到从节点时,8字节数据的前4 字节作为命令ID,后4 字节用于传递命令参数(房间温/湿度SV值)。报文帧的格式如图1 所示。  对主节点到从节点的命令ID 的定义如下: 表 3 控制命令类型及其参数  本系统要监控的数据是各房间的温度和湿度及它们的SV 值。要将这些数据在总线上传输,必须将它们装入报文帧。为了使传输的数据只占用较小的空间而达到较高的精确度,在报文中每一种数值都分配了4 字节的空间,数据按IEEE-754 标准的float 数据类型的格式存储。这样,在下位机进行编程就比较方便。但是,上位机的处理程序是用类似VB 脚本的语言写的,数据对象的类型只有数值型、开关型和字符型三种,不能直接使用接收到的数据。因此,要对接收到的数据进行转换。  按IEEE-754 标准,一个浮点数用两个部分表示:尾数和2 的幂。 二的幂代表指数。指数的保存形式是一个0 到255 的8 位值。指数的实际值是保存值0 到255 减去127,一个范围在127 到-128 之间的值。  尾数是一个24 位值(代表大约7 个十进制数),最高位(MSB) 通常是1, 因此,不保存。一个符号位表示浮点数是正或负。在尾数的左边有一个省略的二进制点和1。 这个数在浮点数的保存中经常省略。  浮点数保存的字节格式如下图 6 IEEE-754 标准 float 存储格式:  这里:  S 代表符号位,1 是负,0 是正。  E 幂,偏移127。  M 24 位的尾数(保存在23 位中)。  零是一个特定值,表示幂是0, 尾数是0。  在运行策略中新建一个名为SplitFloat 的用户策略,新增一策略行并添加以下脚本程序,用于将数值型对象float 转换到4 字节存储单元Byte0 Byte3:  ‘ 计算浮点数的幂(二进制数小数点的位置)  exponent = 0  float1 = !abs(float)  while float1<>0  float1 = !BitRShift(float1, 1)  exponent = exponent + 1  endwhile  exponent = exponent - 1  ‘ 计算浮点数的底数  mantissa = !abs(float) * (!BitLShift(2, 23 - (exponent + 1))) - 8388608  exponent = exponent + 127  Byte0 = !BitRShift(exponent, 1)  if room1tempsv < 0 then  Byte0 = !BitOr(Byte0, 128)  endif  Byte1 = !BitAnd(!BitOr(!BitRShift(mantissa, 16), !BitLShift(exponent, 7)), 255)  Byte2 = !BitAnd(!BitRShift(mantissa, 8), 255)  Byte3 = !BitAnd(mantissa, 255)  在运行策略中新建一个名为UniteFloat 的用户策略,新增一策略行并添加以下脚本程序,用于将4 字节存储单元Byte0 Byte3 转换到数值型对象float:  mantissa = (!BitAnd(Byte0, 128) + !BitAnd(Byte1, 127)) * 65536 + _  (Byte2 * 256) + Byte3 + 8388608  exponent = !BitOr(!BitLShift(Byte0, 1), !BitRShift(Byte1, 7)) - 127  float = mantissa / (!BitLShift(2, 23 - (exponent + 1)))  五、实现  系统的控制中心采用定时查询的方法,每2 秒钟对各个房间的温度值和湿度值进行一次查询。查询时,组态软件先向实时数据库中的数据对象Out_CANData 写入查询房间温/湿度命令的报文。命令报文的房间ID 对应要查询的房间号,命令ID 为0x00000000,无命令参数。然后经过ZOPC_Server 将报文发到CAN总线上。在发送查询命令后控制中心将等待一段时间(这一段时间要大于MCGS 的最小采集周期),然后再从实时数据库中的数据对象In_CANData 读取数据并进行处理和显示。  如果控制中心要修改房间的SV 值,首先发出查询房间温/湿度SV 值的命令,在收到房间温/湿度SV值后,在“修改控制室SV 值”窗口中显示SV 值(或在“修改风道温度/湿度表SV 值”窗口中显示),然后发出带有参数的修改房间温度SV 值命令报文,参数的内容就是要修改的SV 值。  下位机的验收码设置成ID10 为0,ID9~ID3 为房间ID,后3 位屏蔽。当总线上有发给该房间的报文时,并根据命令进行相应的操作。如果收到的是查询命令,下位机立即将房间的温/湿度数据发送到CAN 总线上。数据报文的ID 也是该房间的ID,以表示报文中的数据是该控制室的。如果是修改房间温/湿度SV 值命令,下位机就从命令参数取出SV 值并替换旧的SV 值。  上位机控制流程编写的具体步骤:  1. 在运行策略中新建一个名为“GetRoomTHV ”的用户策略,并添加3 个脚本程序,1 个退出策略行,如图 图 7 GetRoomTHV 策略:  其中,“查询房间温/湿度值”脚本如下:  ‘ 发送控制室温/湿度查询命令  Out_Extern = 0  Out_Remote = 0  Out_ID = !BitLShift(RoomID, 3) + 0 ‘ 控制室ID + 单帧(点对点)  Out_DataLen = 8  Out_Data0 = 0  Out_Data1 = 0  Out_Data2 = 0  Out_Data3 = 0  Out_Data4 = 0  Out_Data5 = 0  Out_Data6 = 0  Out_Data7 = 0  !setstgy(ObjectToString)  ‘ 发出命令  Out_CANData = Out_CANData2  ‘ 等待命令发出  !TimerReset(1, 0)  !TimerRun(1)  !TimerWaitFor(1, Delay)  !TimerStop(1)  ‘ 接收控制室温/湿度  !setstgy(StringToObject)  roomtemp = roomhum = 0  if (In_Extern <> 0) or (In_Remote <> 0) _  or ((!BitAnd(In_ID, 1024)<>1024) _  and (!BitAnd(In_ID, 7)<>0)) then  In_ID = 0  exit  endif  “计算控制室的温度”的执行条件是!BitAnd(!BitRShift(In_ID, 3), 127) = RoomID 表达式的值为非0,脚本程序如下:  Byte0 = In_Data0  Byte1 = In_Data1  Byte2 = In_Data2  Byte3 = In_Data3  !setstgy(UniteFloat)  roomtemp = float  “计算控制室的湿度”的执行条件是!BitAnd(!BitRShift(In_ID, 3), 127) = RoomID 表达式的值为非0,脚本程序如下:  Byte0 = In_Data4  Byte1 = In_Data5  Byte2 = In_Data6  Byte3 = In_Data7  !setstgy(UniteFloat)  roomhum = float  2. 在运行策略中新建名为“查询各控制室温/湿度”的循环策略,循环时间为2000ms。 添加如下图所示的策略行,图 8 查询各控制室温/湿度策略。  

  图 8 查询各控制室温/湿度策略  “初始化”的脚本程序如下:  RoomID = 1  “查询1 号控制室温/湿度”的脚本程序如下:  RoomID1 = RoomID  RoomID = RoomID + 1  if (roomtemp = 0) or (roomhum = 0) then  room1st = 1  ErrorTemp = roomtemp  ErrorHum = roomhum  !SaveData(ErrorSave) ‘ 记录通信错误  exit  endif  room1temp = roomtemp  room1hum = roomhum  if room1temp > room1tempsv then  room1st = 1  ErrorTemp = room1temp  ErrorHum = room1hum  !SaveData(exception) ‘ 记录温度异常  exit  else  room1st = 0  endif  其它策略行脚本程序与上类似。  所有的“策略调用”均调用GetRoomTHV 策略。  3. 在运行策略中新建一个名为“GetRoomSV” 的用户策略,其他步骤同1,图 9 GetRoomSV 策略。  “查询房间温/湿度SV 值”脚本如下:  ‘ 发送控制室温/湿度SV 查询命令  Out_Extern = 0  Out_Remote = 0  Out_ID = !BitLShift(RoomID, 3) + 0 ‘ 控制室ID + 单帧(点对点)  Out_DataLen = 8  Out_Data0 = 0  Out_Data1 = 0  Out_Data2 = 0  Out_Data3 = 1  Out_Data4 = 0  Out_Data5 = 0  Out_Data6 = 0  Out_Data7 = 0  !setstgy(ObjectToString)  ‘ 发出命令  Out_CANData = Out_CANData2  ‘ 等待命令发出  !TimerReset(1, 0)  !TimerRun(1)  !TimerWaitFor(1, Delay)  !TimerStop(1)  ‘ 接收控制室温/湿度  !setstgy(StringToObject)  if (In_Extern <> 0) or (In_Remote <> 0) _  or ((!BitAnd(In_ID, 1024)<>1024) _  and (!BitAnd(In_ID, 7)<>0)) then  In_ID = 0  exit  endif  roomtemp = roomhum = 0  “计算控制室的温度SV” 的执行条件是!BitAnd(!BitRShift(In_ID, 3), 127) = RoomID 表达式的值为非0, 脚本程序如下:  Byte0 = In_Data0  Byte1 = In_Data1  Byte2 = In_Data2  Byte3 = In_Data3  !setstgy(UniteFloat)  roomtempsv = float  “计算控制室的湿度SV” 的执行条件是!BitAnd(!BitRShift(In_ID, 3), 127) = RoomID 表达式的值为非0, 脚本程序如下:  Byte0 = In_Data4  Byte1 = In_Data5  Byte2 = In_Data6  Byte3 = In_Data7  !setstgy(UniteFloat)  roomhumsv = float  4. 在运行策略中新建名为“查询房间1SV 值”的用户策略,添加如下图所示的3 个策略行,图 10 查询房间1SV 值策略。  

  “before” 策略行脚本程序如下:  !EnableStgy(查询各控制室温/湿度策略, 0)  RoomID = 1  “策略调用”调用GetRoomSV 策略。  “after” 策略行脚本程序如下:  room1tempsv = roomtempsv  !EnableStgy(查询各控制室温/湿度策略, 1)  5. 重复步骤4。 添加“查询房间2~6 SV 值”和“查询风道温/湿度表1~2 sv 值”策略,并由菜单“修改1~6 号房间SV 值”调用对应的策略。  6. 双击主控窗口中名为“修改一号房间SV 值”的菜单项,在菜单属性设置对话框的“菜单操作”页中添加执行运行策略块“查询房间1 SV 值”。  7. 重复步骤6, 添加其它房间的运行策略。  8. 在运行策略中建立一个名为“SetRoomTSV” 的用户策略,添加以下脚本程序:  float = roomtempsv  !setstgy(SplitFloat)  ‘ 发送控制室温/湿度SV 设置命令  Out_Extern = 0  Out_Remote = 0  Out_ID = !BitLShift(RoomID, 3) + 0 ‘ 控制室ID + 单帧(点对点)  Out_DataLen = 8  Out_Data0 = 0  Out_Data1 = 0  Out_Data2 = 0  Out_Data3 = 2  Out_Data4 = Byte0  Out_Data5 = Byte1  Out_Data6 = Byte2  Out_Data7 = Byte3  !setstgy(ObjectToString)  ‘ 发出命令  Out_CANData = Out_CANData2  ‘ 等待命令发出  !TimerReset(1, 0)  !TimerRun(1)  !TimerWaitFor(1, Delay)  !TimerStop(1)  9. 在运行策略中建立一个名为“调整房间1SV 值”的用户策略,并添加以下程序:  !EnableStgy(查询各控制室温/湿度策略, 0)  RoomID = 1  roomtempsv = room1tempsv  float = roomtempsv  !setstgy(SetRoomTSV)  !EnableStgy(查询各控制室温/湿度策略, 1)  10. 重复步骤9, 添加其它5 个控制室及风道的脚本程序。  11. 给“修改控制室1SV 值”窗口的“确认”按钮添加如下脚本:  if room1tempsv1<-10 or room1tempsv1>100 then  !setwindow(修改SV 值消息窗口,1)  else  room1tempsv=room1tempsv1  !setwindow(修改控制室1SV 值,3)  room1tempsv1=0  !setstgy(调整房间1SV 值)  endif  12. 重复步骤11, 添加其它窗口的脚本。  六 模拟控制室  本系统可用DP-668 实验仪模拟产生控制室数据。DP-668 实验仪具有模拟控制室温/湿度变化、自修改温/湿度SV 值以及报警等功能。其模拟温/湿度变化算法如下:  extern unsigned char code RoomID = 1; /* 房间 ID */  extern float RoomTemp = 0; /* 房间温度 */  extern float RoomHumi = 0; /* 房间湿度 */  ...  float code RoomTempTab[] = {  19.0, 19.2, 19.4, 19.6, 19.8,  20.0, 20.2, 20.4, 20.6, 20.8,  21.0, 21.2, 21.4, 21.6, 21.8,  21.8, 21.6, 21.4, 21.2, 21.0,  20.8, 20.6, 20.4, 20.2, 20.0,  19.8, 19.6, 19.4, 19.2, 19.0  };  float code RoomHumiTab[] = {  55.0, 55.5, 56.0, 56.5,  57.0, 57.5, 58.0, 58.5,  59.0, 59.5, 60.0, 60.5,  61.0, 61.5, 62.0, 62.5,  63.0, 63.5, 64.0, 64.5  };  void main(void)  {  unsigned int idata i, j;  ...  while (1)  {  /* 模拟温/湿度变化 */  RoomTemp = RoomTempTab[j % (sizeof (RoomTempTab)/sizeof(RoomTempTab[0]))];  RoomHumi = RoomHumiTab[j++ % (sizeof (RoomHumiTab)/sizeof(RoomHumiTab[0]))];  ...  }  }  本系统也可用任一款ZLGCAN 接口卡和PC 组成的系统来模拟产生控制室数据,基于ZLGCAN 通用函数接口编程,同样具有模拟控制室温/湿度变化、自修改温/湿度SV 值以及报警等功能。其模拟温/湿度变化算法(VC 示范)如下:  float m_dwTemp[8];// 房间1 6 及风道1 2 的温度  float m_dwHumi[8];// 房间1 6 及风道1 2 的湿度  ...  static double i;  i += 0.1;  m_dwTemp[0] = (float)sin(i + 0.0) + 20;  m_dwTemp[1] = (float)sin(i + 0.1) + 20;  m_dwTemp[2] = (float)sin(i + 0.2) + 20;  m_dwTemp[3] = (float)sin(i + 0.3) + 20;  m_dwTemp[4] = (float)sin(i + 0.4) + 20;  m_dwTemp[5] = (float)sin(i + 0.5) + 20;  m_dwTemp[6] = (float)sin(i + 0.6) + 20;  m_dwTemp[7] = (float)sin(i + 0.7) + 20;  m_dwHumi[0] = (float)cos(i + 0.0) + 60;  m_dwHumi[1] = (float)cos(i + 0.1) + 60;  m_dwHumi[2] = (float)cos(i + 0.2) + 60;  m_dwHumi[3] = (float)cos(i + 0.3) + 60;  m_dwHumi[4] = (float)cos(i + 0.4) + 60;  m_dwHumi[5] = (float)cos(i + 0.5) + 60;  m_dwHumi[6] = (float)cos(i + 0.6) + 60;  m_dwHumi[7] = (float)cos(i + 0.7) + 60;  ...