外设驱动库开发笔记28:W5500以太网控制器

以太网通讯是一种被广泛使用的数据通讯方式。在嵌入式应用中也经常使用,但协议栈的实现并不是一件容易的事。不过有些以太网控制器就带有协议栈,如W5500。在本篇中我们将讨论如何设计并实现W5500以太网控制器的驱动。

1、功能概述

W5500WIZnet开发的单芯片全硬件TCP/IP协议栈,能够方便的实现网络连接应用。

1.1、硬件描述

W5500作为一款全硬件TCP/IP嵌入式以太网控制器,为嵌入式系统提供了更加简易的互联网连接方案。W5500 集成了 TCP/IP 协议栈,10/100M 以太网数据链路层(MAC)及物理层(PHY),使得用户使用单芯片就能够在他们的应用中拓展网络连接。 其引脚排布及分装如下:

W5500全硬件 TCP/IP 协议栈支持 TCPUDPIPv4ICMPARPIGMP 以及 PPPoE 协议。W5500 内嵌 32K 字节片上缓存以供以太网包处理。使用W5500,只需要一些简单的Socket 编程就能实现以太网应用。用户可以同时使用8个硬件Socket 独立通讯。

W5500提供了SPI(外设串行接口)从而能够更加容易与外设MCU整合。而且,W5500的使用了新的高效SPI协议支持80MHz速率,从而能够更好的实现高速网络通讯。为了减少系统能耗,W5500提供了网络唤醒模式(WOL)及掉电模式供客户选择使用。

1.2、通讯接口

W5500提供了SPI(串行外部接口)作为外设主机接口,有SCSnSCLKMOSI MISO4路信号,且作为SPI从机工作。W5500MCU的连接方式如下图所示。根据SCSn是否受主机控制,将其工作模式分为可变数据长度模式和固定数据长度模式。在可变数据长度模式中,W5500可以与其他SPI设备共用SPI接口。在固定数据长度模式,SPI将指定给W5500,不能与其他SPI设备共享。

SPI协议定义了四种工作模式(模式 0123)。每种模式的区别是根据SCLK的极性及相位不同定义的。SPI 的模式 0 和模式 3 唯一不同的就是在非活动状态下,SCLK 信号的极性。SPI的模式03,数据都是在SCLK的上升沿锁存,在下降沿输出。W5500支持SPI模式0及模式3MOSIMISO信号无论是接收或发送,均遵从从最高标志位(MSB)到最低标志位(LSB)的传输序列。

1.3、内部寄存器

W5500SPI数据帧包括了16位地址段的偏移地址,8位控制段和N字节数据段。如图下图所示:

地址段为W5500的寄存器或TX/RX缓存区指定了16位的偏移地址。 16 位偏移地址的值来自从最高标志位到最低标志位的顺序传输。

控制段指定了地址段设定的偏移区域归属,读/写访问模式及SPI工作模式。8位控制段可以通过修改区域选择位(BSB[4:0]),读/写访问模式位(RWB)以及SPI工作模式位(OM[1:0])来重新定义。区域选择位选择了归属于偏移地址的区域。

SPI数据帧的数据段通过偏移地址自增(每传输1字节偏移地址加1),支持连续数据读/写。

W55001个通用寄存器,8Socket寄存器区,以及对应每个Socket的收发缓存区。每个区域均通过SPI数据帧的区域选择位(BSB[4:0])来选取。每一个Socket的发送缓存区都在一个16KB的物理发送内存中,初始化分配为2KB。每一个Socket的接收缓存区都在一个16KB 的物理接收内存中,初始化分配为 2KB。无论给每个Socket 分配多大的收/发缓存,都必须在 16 位的偏移地址范围内(从 0x0000 0xFFFF)。

通用寄存器区配置了W5500IP地址、MAC地址等基本信息。该区域可以通过SPI数据帧的区域选择位(BSB[4:0])选定。

W5500支持8Socket作为通讯信道。每一个Socket通过Socket n寄存器区控制(0≤n≤7)。Socket n寄存器可以通过SPI数据帧中的区域选择寄存器(BSB[4:0])来选定对应的寄存器n

2、驱动设计与实现

我们已经对W5500以太网控制器的引脚封装、接口方式、协议栈的操作流程以及基本操作库有了比较详细的了解。接下来我们将设计并实现W5500以太网控制器的驱动程序。

2.1、对象定义

在使用一个对象之前我们需要获得一个对象。同样的我们想要W5500以太网控制器就需要先定义W5500以太网控制器的对象。

2.1.1、对象的抽象

我们要得到W5500以太网控制器对象,需要先分析其基本特性。一般来说,一个对象至少包含两方面的特性:属性与操作。接下来我们就来从这两个方面思考一下W5500以太网控制器的对象。

先来考虑属性,作为属性肯定是用于标识或记录对象特征的东西。我们来考虑W5500以太网控制器对象属性。作为以太网控制器,W5500对象显然需要有网络配置参数作为它的属性,包括IP地址和MAC地址等。所以我们将网络参数定义为对象的属性。在这里我们以结构体的方式来定义网络参数。

接着我们还需要考虑W5500以太网控制器对象的操作问题。其实我们对W5500的操作就是对SPI接口的操作,这里我们因为使用了厂家的基础库,所以以函数注册回调函数的方式传递了操作函数。我们不需要再将对SPI端口作为对象的操作,而是将他们以函数指针的方式在初始化函数中传入。那么我们对对象的操作就是读取和写入信息的操作,而具体的数据处理总是依赖于具体应用,所以我们将其作为对象的操作。

根据上述我们对W5500以太网控制器的分析,我们可以定义W5500以太网控制器的对象类型如下:

/* 定义W5500对象类型 */
typedef struct W5500Object {wiz_NetInfo gWIZNETINFO;uint16_t (*DataParsing)(uint8_t *rxBuffer,uint16_t rxSize,uint8_t *txBuffer);//接收消息解析及返回消息生成,返回值为返回消息的字节长度uint16_t (*RequestData)(uint8_t *rqBuffer);   //得到请求命令,一般用于客户端发起访问
}W5500ObjectType;

2.1.2、对象初始化

我们知道,一个对象仅作声明是不能使用的,我们需要先对其进行初始化,所以这里我们来考虑W5500以太网控制器对象的初始化函数。一般来说,初始化函数需要处理几个方面的问题。一是检查输入参数是否合理;二是为对象的属性赋初值;三是对对象作必要的初始化配置。据此我们设计W5500以太网控制器对象的初始化函数如下:

/*W5500对象初始化*/
void W5500Initialization(W5500ObjectType *w5500,uint8_t mac[6],        //本地Mac地址uint8_t ip[4],         //本地IP地址uint8_t sn[4],         //子网掩码uint8_t gw[4],         //网关地址uint8_t dns[4],        //DNS服务器地址dhcp_mode dhcp,        //DHCP类型W5500CSCrisType cris_en,W5500CSCrisType cris_ex,W5500CSCrisType cs_sel,W5500CSCrisType cs_desel,W5500SPIReadByteTYpe spi_rb,W5500SPIWriteByteTYpe spi_wb,W5500DataParsingType dataParse,W5500RequestDataType requst)
{if((w5500==NULL)||(cris_en==NULL)||(cris_ex==NULL)||(cs_sel==NULL)||(cs_desel==NULL)||(spi_rb==NULL)||(spi_wb==NULL)){return;}for(int i=0;i<6;i++){w5500->gWIZNETINFO.mac[i]=mac[i];}for(int i=0;i<4;i++){w5500->gWIZNETINFO.ip[i]=ip[i];w5500->gWIZNETINFO.sn[i]=sn[i];w5500->gWIZNETINFO.gw[i]=gw[i];w5500->gWIZNETINFO.dns[i]=dns[i];}w5500->gWIZNETINFO.dhcp=dhcp;/*注册TCP通讯相关的回调函数*/RegisterFunction(cris_en,cris_ex,cs_sel,cs_desel,spi_rb,spi_wb);/*初始化芯片参数*/ChipParametersConfiguration();/*初始化网络通讯参数*/NetworkParameterConfiguration(w5500->gWIZNETINFO);if(dataParse!=NULL){w5500->DataParsing=dataParse;}else{w5500->DataParsing=LoopBackDataHandle;}if(requst!=NULL){w5500->RequestData=requst;}else{w5500->RequestData=DefaultRequest;}
}

2.2、对象操作

我们已经完成了W5500以太网控制器对象类型的定义和对象初始化函数的设计。但我们的主要目标是获取对象的信息,接下来我们还要实现面向W5500以太网控制器的各类操作。

W5500以太网控制器有哪些操作呢?作为通讯接口,最主要的就是数据的发送于接收。这些函数我们当然可以实现它,不过在厂商提供的基础库中已经提供了这些函数,我们直接实用就好了,这里就不再列出了。

3、驱动的使用

我们已经设计了W5500以太网控制器的驱动,接下来我们设计一个简单的应用验证这一驱动。

3.1、声明并初始化对象

使用基于对象的操作我们需要先得到这个对象,所以我们先要使用前面定义的W5500以太网控制器对象类型声明一个W5500以太网控制器对象变量,具体操作格式如下:

W5500ObjectType w5500;

声明了这个对象变量并不能立即使用,我们还需要使用驱动中定义的初始化函数对这个变量进行初始化。这个初始化函数所需要的输入参数如下:

W5500ObjectType *w5500,

uint8_t mac[6],        //本地Mac地址

uint8_t ip[4],         //本地IP地址

uint8_t sn[4],         //子网掩码

uint8_t gw[4],         //网关地址

uint8_t dns[4],        //DNS服务器地址

dhcp_mode dhcp,        //DHCP类型

W5500CSCrisType cris_en,

W5500CSCrisType cris_ex,

W5500CSCrisType cs_sel,

W5500CSCrisType cs_desel,

W5500SPIReadByteTYpe spi_rb,

W5500SPIWriteByteTYpe spi_wb,

W5500DataParsingType dataParse,

W5500RequestDataType requst

对于这些参数,对象变量我们已经定义了。而IP地址这些参数我们只需要睡着时输入就可以了。主要的是我们需要定义几个函数,并将函数指针作为参数。这几个函数的类型如下:

/*解析接收到的数据*/
typedef uint16_t (*W5500DataParsingType)(uint8_t *rxBuffer,uint16_t rxSize,uint8_t *txBuffer);/*得到请求命令,一般用于客户端发起访问*/
typedef uint16_t (*W5500RequestDataType)(uint8_t *rqBuffer);  /*定义片选及临界区操作函数类型*/
typedef void (*W5500CSCrisType)(void);/*定义SPI读一个字节函数类型*/
typedef uint8_t (*W5500SPIReadByteTYpe)(void);/*定义SPI写一个字节函数类型*/
typedef void (*W5500SPIWriteByteTYpe)(uint8_t wb);

对于这几个函数我们根据样式定义就可以了,具体的操作可能与使用的硬件平台有关系。片选操作函数用于多设备需要软件操作时,如采用硬件片选可以传入NULL即可。具体函数定义如下:

/*写1字节数据到SPI总线*/
static void SPI_WriteByte(uint8_t TxData)
{HAL_SPI_Transmit(&w5500hspi,&TxData,1,1000);
}/*从SPI总线读取1字节数据*/
static uint8_t SPI_ReadByte(void)
{uint8_t rxData;HAL_SPI_Receive(&w5500hspi,&rxData,1,1000);return rxData;//返回接收的数据
}/*进入临界区*/
static void SPI_CrisEnter(void)
{__set_PRIMASK(1);
}/*退出临界区*/
static void SPI_CrisExit(void)
{__set_PRIMASK(0);
}/*片选信号输出低电平*/
static void SPI_CS_Select(void)
{HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
}/*片选信号输出高电平*/
static void SPI_CS_Deselect(void)
{HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
}/*数据回环处理*/
static uint16_t LoopBackDataHandle(uint8_t *rxBuffer,uint16_t rxSize,uint8_t *txBuffer)
{uint16_t txSize = 0;txSize=(uint16_t)rxSize;for(int i=0;i<txSize;i++){txBuffer[i]=rxBuffer[i];}return txSize;
}/*默认测试请求*/
static uint16_t DefaultRequest(uint8_t *rqBuffer)
{uint16_t rSize=0;char requstString[]="This is a new client connection.\r\n";rSize=strlen(requstString);for(int i=0;i<rSize;i++){rqBuffer[i]=requstString[i];}return rSize;
}

对于延时函数我们可以采用各种方法实现。我们采用的STM32平台和HAL库则可以直接使用HAL_Delay()函数。于是我们可以调用初始化函数如下:

/* W5500初始化配置 */
void W5500Configuration(void)
{uint8_t mac[6]= {0x01, 0x08, 0xdc,0x00, 0xab, 0xcd};        //本地Mac地址uint8_t ip[4]= {192, 168, 1, 190};         //本地IP地址uint8_t sn[4]= {255,255,255,0};         //子网掩码uint8_t gw[4]= {192, 168, 1, 1};         //网关地址uint8_t dns[4]= {0,0,0,0};        //DNS服务器地址W5500_SPI_Configuration();W5500Initialization(&w5500,mac,ip,sn,gw,dns,NETINFO_STATIC,SPI_CrisEnter,SPI_CrisExit,SPI_CS_Select,SPI_CS_Deselect,SPI_ReadByte,SPI_WriteByte,NULL,NULL);
}

3.2、基于对象进行操作

我们定义了对象变量并使用初始化函数给其作了初始化。接着我们就来考虑操作这一对象获取我们想要的数据。我们在驱动中已经将获取数据并转换为转换值的比例值,接下来我们使用这一驱动开发我们的应用实例。我们实现以个TCP回环服务器。具体调用如下:

W5500TCPServer(&w5500,Socket0,502);

TCP服务器设计如下:

/*TCP服务器数据通讯*/
int32_t W5500TCPServer(W5500ObjectType *w5500,W5500SocketType sn,uint16_t lPort)
{int32_t ret;switch(getSn_SR(sn)){case SOCK_ESTABLISHED:{if(getSn_IR(sn) & Sn_IR_CON){setSn_IR(sn,Sn_IR_CON);}uint16_t size=0;if((size = getSn_RX_RSR(sn)) > 0){if(size > DATA_BUFFER_SIZE){size = DATA_BUFFER_SIZE;}uint8_t rxBuffer[DATA_BUFFER_SIZE];ret = recv(sn,rxBuffer,size);if(ret <= 0){return ret;}//添加数据解析及响应的函数uint8_t txBuffer[DATA_BUFFER_SIZE];uint16_t length=w5500->DataParsing(rxBuffer,ret,txBuffer);uint16_t sentsize=0;while(length != sentsize){ret = send(sn,txBuffer+sentsize,length-sentsize);if(ret < 0){close(sn);return ret;}sentsize += ret; // 不用管SOCKERR_BUSY, 因为它是零.}}break;}case SOCK_CLOSE_WAIT:{if((ret=disconnect(sn)) != SOCK_OK){return ret;}break;}case SOCK_INIT:{if( (ret = listen(sn)) != SOCK_OK){return ret;}break;}case SOCK_CLOSED:{if((ret=socket(sn,Sn_MR_TCP,lPort,0x00)) != sn){return ret;}break;}default:{break;}}return 1;
}

4、应用总结

这一篇中我们设计并实现了W5500以太网控制器的驱动程序,而且也设计了一个简单的应用来验证它。我们也在多个实际项目中使用W5500及驱动程序,并在此基础上实现过如Modbus TCP等数据传输协议,在实际使用中效果良好。

需要说明的是我们并没有从最底层开始实现驱动程序。当然,我们完全可以同过操作寄存器实现最基础的驱动开发,但在本篇中没有这么做是因为已有的驱动底层已经很完备了,不需要重复劳动。另一方面,我们希望再次基础上做更高层次的封装,以便与使用驱动的人能够专注于具体的应用逻辑,所以我们封装了如TCP服务器及TCP客户端等,使用者则可以专注于应用协议本身。

本篇中只是验证了TCP服务器,但在使用驱动时,如果向实现如HTTP服务器只需要修改对象的DataParsing操作就可以了。

源码下载:https://github.com/foxclever/ExPeriphDriver

​欢迎关注:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499330.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

外设驱动库开发笔记29:DS17887实时时钟驱动

一些时候&#xff0c;在我们的嵌入式产品中需要记录时间&#xff0c;所以我们就需要获取时钟&#xff0c;当然实现的方式多种多样&#xff0c;有的MCU本身就有这一功能&#xff0c;不过精度较低。当我们的应用要求较高时就需要使用专门的实时时钟芯片&#xff0c;如DS17887。在…

外设驱动库开发笔记30:宇电AI-BUS通讯驱动

嵌入式系统通常都会与外部设备进行通讯&#xff0c;这就涉及到通讯协议的问题。这些通讯协议有的是标准协议有的厂家自定义的协议&#xff0c;如宇电的AI-BUS。在本篇中&#xff0c;我们将讨论AI-BUS的驱动&#xff0c;以便于与宇电设备的通讯。 1、功能概述 宇电的设备使用基…

步进电机驱动技术3:基于ULN2003的步进电机驱动

在我们的项目中&#xff0c;经常使用到低电压小功率的步进电机&#xff0c;此类步进电机若采用驱动器控制不断成本高也过于复杂&#xff0c;我们可以直接使用场效应管或者达林顿管来实现对其的驱动。在本篇中&#xff0c;我们就来讨论一下基于ULN2003A达林顿管实现对步进电机的…

通讯接口应用笔记2:MAX3160实现多协议通讯

在一些应用需求中&#xff0c;我们需要对外部提供串行通讯端口&#xff0c;但这些端口所通讯的目标设备各有不同&#xff0c;接口协议也有RS232以及RS485和RS422多种。面对这种情况&#xff0c;我们当然可以同时设计多个串口以适应不同需要&#xff0c;但无疑对硬件资源是一种浪…

电机速度曲线规划1:梯形速度曲线设计与实现

电机驱动是很常见的应用&#xff0c;在很多系统中我们都会碰到需要改变电机的速度以实现相应的控制功能&#xff0c;这就涉及到电机速度曲线规划的问题。在这篇中我们就来简单讨论一下电机的梯形曲线规划的问题。 1、基本原理 梯形速度曲线控制算法是工业控制领域应用最为广泛…

文件系统应用笔记之一:FatFS在STM32F4上的移植

在实现如U盘文件读写&#xff0c;SD卡的文件读写等工作时&#xff0c;我们往往需要一个文件系统来支持我们的工作。特别在一些MCU应用中&#xff0c;文件系统的加入能明显改善系统交互的友好性。在这一篇中&#xff0c;我们就来讨论FatFS文件系统在STM32F4上的移植和应用。 1、…

通讯接口应用笔记3:使用W5500实现Modbus TCP服务器

前面我们设计实现了W5500的驱动程序&#xff0c;也讲解了驱动的使用方式。在最近一次的项目应用中&#xff0c;正好有一个使用W5500实现TCP通讯的需求&#xff0c;所以我们就使用该驱动程序轻松实现。这一篇中我们就来说一说基于我们W5500通讯驱动程序实现TCP通讯的过程。 1、…

电机速度曲线规划2:S形速度曲线设计与实现

电机驱动是很常见的应用&#xff0c;在很多系统中我们都会碰到需要改变电机的速度以实现相应的控制功能&#xff0c;这就涉及到电机速度曲线规划的问题。在这篇中我们就来简单讨论一下电机的S型曲线规划的问题。 1、基本原理 S型速度曲线控制算法是工业控制领域另一种常用的加…

USB应用开发笔记之一:STM32上实现USB主机读写U盘

在项目应用中&#xff0c;经常会有对外交换数据的需求。USB接口读写U盘无疑是一种颇为方便的选择。在这一篇中&#xff0c;我们就来讨论如何在STM32上实现USB主机读写U盘文件的方法。 1、应用概述 在我们的产品上有这样一个需求&#xff0c;希望通过大容量的U盘存取数据。我们…

Modbus协议栈综合实例设计

自我们开源了我们的Modbus协议栈之后&#xff0c;就一直有朋友来信说希望提供示例。这次我们整理了几个例子以供参考。 1、应用实例规划 在这次的实例中&#xff0c;我们使用的目标板拥有一个以太网接口、一个RS232串行接口和一个RS485串行接口&#xff0c;所以我们规划实现&a…

ThreadX应用开发笔记之二:移植ThreadX到STM32H7平台

前面我们将ThreadX成功移植到了STM32F4平台&#xff0c;但这只是我们的部分应用。我们希望将ThreadX的优势发挥到我们的更多应用中&#xff0c;所以在这一篇中我们就来实现将ThreadX移植到STM32H7平台中。 1、前期准备 在开始将ThreadX移植到STM32H7平台之前&#xff0c;我们需…

外设驱动库开发笔记31:S-Modlue远红外气体传感器驱动

在气体分析类产品中&#xff0c;我们经常会用到远红外气体传感器。我们就在碳氢类气体成分分析中使用了S-Modlue远红外气体传感器。接下来&#xff0c;我们将讨论S-Modlue远红外气体传感器驱动的设计与实现。 1、功能概述 S-MODULE EVO 使用非分散红外检测技术NDIR&#xff0c…

外设驱动库开发笔记32:HLPM025K3 PM2.5传感器驱动

现在人们对大气环境及室内环境都比较关注。PM2.5在生活中也是常见的词汇。在有些产品中就要求检测PM2.5的数值。检测PM2.5的手段多种多样&#xff0c;在要求不高时我们通常可以采用激光模块。在这一篇中&#xff0c;我们将讨论HLPM025K3 PM2.5传感器驱动的设计与实现。 1、功能…

外设驱动库开发笔记33:LCD1602液晶显示屏驱动

LCD1602是一种工业字符型液晶&#xff0c;能够同时显示16x02即32个字符。LCD1602液晶显示的原理是利用液晶的物理特性&#xff0c;通过电压对其显示区域进行控制&#xff0c;即可以显示出图形。在这一章我们就来讨论LCD1602液晶显示屏驱动的设计与实现。 1、功能概述 LCD1602液…

滤波器开发之四:基于算术平均的中值滤波器

在信号采集系统中&#xff0c;除了我们感兴趣的数据外&#xff0c;难免会有一些来自于环境的干扰信号。但我们总希望我们得到的数据是纯净而真实的&#xff0c;为了达到这个目标&#xff0c;我们不得不想办法去除这些干扰信号&#xff0c;于是滤波器就成为我们必不可少的帮手。…

LwIP应用开发笔记之十一:LwIP带操作系统UDP服务器

我们已经实现了在FreeRTOS系统上的LwIP的移植工作&#xff0c;但只是简单的在系统平台上跑了起来。我们还希望能做更多的事情&#xff0c;这一节我们就在FreeRTOS系统上实现基于LwIP的UDP服务器。 1、UDP协议简述 UDP协议全称是用户数据报协议&#xff0c;在网络中它与TCP协议…

滤波器开发之五:基于算术平均的限幅滤波器

通过AD采集数据时&#xff0c;我们总是希望采集到的数据是纯净而真实的&#xff0c;而实际上环境中存在太多的干扰信号&#xff0c;为了让我们得到的数据尽可能地接近实际值&#xff0c;我们需要降低这些干扰信号的影响。所以软件实现的数字滤波器应运而生&#xff0c;这一篇我…

外设驱动库开发笔记34:OLED显示屏驱动

现在OLED显示屏在嵌入式系统中应用的越来越多。对于一些显示信息不太复杂&#xff0c;以显示信息为主的需求&#xff0c;我们一般会选择OLED显示屏。在这一篇中&#xff0c;我们将讨论OLED显示屏驱动的设计与实现。 1、功能概述 从使用的情况来说&#xff0c;较为常用的是0.96…

外设驱动库开发笔记35:迪文触摸屏驱动

有些时候嵌入式系统也需要显示更为复杂的图形&#xff0c;需要更丰富的数据展示。为此&#xff0c;我们需要更大&#xff0c;色彩更丰富&#xff0c;带触屏的显示屏&#xff0c;当然性价比更高就最好了。在我们的项目中遇到此类需求&#xff0c;我们有时会选择DWIN触摸屏。在本…

快速实现一个室内空气质量检测仪

冬天我们大多会关闭门窗&#xff0c;而依靠暖通空调设备来维持室内温度。而在保证居室温度的同时&#xff0c;我们也希望保持居室内大气环境的健康度。鉴于此&#xff0c;我们设计了一个简单的室内空气质量检测器。 1、系统概述 我们依靠暖通空调设备来维持室内温度、湿度和通…