proteus怎么找ds18b20 |
您所在的位置:网站首页 › proteus中reset怎么找 › proteus怎么找ds18b20 |
5.1 简介 使用数字式温度传感器DS18B20实现温度测量,并且串口输出温度测量值 5.2 关于DS18B20 DS18B20元件官方链接如下: https://www.maximintegrated.com/cn/products/sensors/DS18B20.html DS18B20引脚定义: DQ为数字信号输入/输出端;GND为电源地;VDD为外接供电电源输入端(在寄生电源接线方式时接地) 5.3 原理图 添加DS18B20元件,在元器件搜索栏中输入“DS18B20”,并添加至元器件选择栏,如图 并将其DQ管脚接至数字口2脚,也就是ATmega328P的4管脚,同时接10kΩ的上拉电阻,VCC接至+5V,GND接至地端,修改后的Arduino UNO仿真图如图所示 编辑DS18B20参数属性,弹出如图所示的DS18B20设置框。设置框里有内部序列号、当前温度值、调节的最小变化量。 内部序列号用于单总线上挂有多个DS18B20的情况下,甄别不同的传感器。只有内部序列号相同的DS18B20才会响应单总线上的命令并作出相应的响应。 5.4 编辑代码 DS18B20设置完成后,下面进入Arduino编程部分。 相关库引入 两个库均在GitHub上,可以在release标签页中下载最新项目文件。 OneWire库 项目地址:https://github.com/PaulStoffregen/OneWire,官网地址 DallasTemperature库 项目地址:https://github.com/milesburton/Arduino-Temperature-Control-Library 下载两个库的zip包后在Arduino IDE中选择:项目 > 加载库 > 添加一个 .ZIP库 ,两个库添加完成后就可以使用了。 在开始编写代码之前将Paul Stoffregen的OneWire包括在库管理器中,这是为了 DallasTemperature库的调用,在编写代码时,可以不用包含此库。 启动arduino IDE,选择DallasTemperature中示例simple文件并打开,复制代码,在protues中切换到“Source Code”标签,粘贴代码,然后编译代码。 5.5 运行仿真 oneWire 和DallasTemperature库说明 OneWire库 这是用于1-Wire总线通讯的库,1-Wire总线是达拉斯半导体(已经被美信收购)推出的一种总线技术,一根线完成数据双向通讯,甚至总线上的设备还能从这条线取电。对于布线和现场施工来说这个技术蛮好的,但是这方面方便了别的地方就需要牺牲点了,具体来说1-Wire总线对通讯时间控制有些要求,通常通过程序来读写的话就需要牺牲一些性能了,因为读写过程中很多时候是阻塞的,OneWire库中可以看到很多延时存在。只用OneWire库就可以操作DS18B20了,可以参考该库的例程。 下面是OneWire库中部分方法说明(这个库不用太在意因为我们主要用的是下面那个库): OneWire(uint8_t pin) { begin(pin); } 构造方法,指定总线接入的pin号;void begin(uint8_t pin) 初始化总线接口;uint8_t reset(void) 复位总线,如果总线上有可用设备则返回1,没有则返回0;void select(const uint8_t rom[8]) 选择指定设备,rom[8]为设备序列号;void skip(void) 发送跳过指令(0xCC);void write(uint8_t v, uint8_t power = 0)void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0)uint8_t read(void)void read_bytes(uint8_t *buf, uint16_t count)void write_bit(uint8_t v)uint8_t read_bit(void) 读写相关操作;void depower(void) 停止向总线供电;void reset_search()void target_search(uint8_t family_code)bool search(uint8_t *newAddr, bool search_mode = true) 搜索设备相关操作;static uint8_t crc8(const uint8_t *addr, uint8_t len)static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0)static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0) 计算CRC校验; DallasTemperature库 这个库在上面那个库之上再封装了一层,方便直接使用DS18B20等系列的温度传感器。这个库中温度转换可以设置为阻塞或非阻塞模式,阻塞模式下运行温度转换请求方法时会阻塞一段时间。这个库中访问设备可以通过设备地址(序列号)或是索引,通过索引方式访问相对会耗更多时间。 部分方法说明如下: DallasTemperature(OneWire*) 构造函数;void begin(void) 初始化总线,获取总线上有多少OneWire设备和其中多少为DS18等系列设备;uint8_t getDS18Count(void) 返回DS18等系列设备数量;bool getAddress(uint8_t* deviceAddress, uint8_t index) 获取指定索引设备的地址到deviceAddress对象;bool isConnected(const uint8_t* deviceAddress, uint8_t* scratchPad) 返回指定地址(序列号)设备是否连接到总线,并读取设备寄存器数据到scratchPad对象;uint8_t getResolution() 返回全局设备最大分辨率;void setResolution(uint8_t newResolution) 设置所有设备数据分辨率;uint8_t getResolution(const uint8_t* deviceAddress) 返回指定地址(序列号)设备分辨率;bool setResolution(const uint8_t* deviceAddress, uint8_t newResolution, bool skipGlobalBitResolutionCalculation) 设置指定地址(序列号)设备分辨率;void setWaitForConversion(bool flag) 设置温度转换时是否阻塞,true则阻塞,默认为true;bool getWaitForConversion(void) 返回温度转换时是否阻塞;void setCheckForConversion(bool flag) 设置是否在阻塞时检查转换完成,true则检查,默认为true;bool getCheckForConversion(void) 返回是否在阻塞时检查转换完成;void requestTemperatures(void) 向总线上所有设备发送温度转换指令,阻塞模式下该方法将阻塞一定时间; 阻塞时间和全局设备最大分辨率以及是否在阻塞时检查转换完成标志有关, 分辨率影响: 9:最大94ms;10:最大188ms;11:最大375ms;其它:最大750ms;bool requestTemperaturesByAddress(const uint8_t* deviceAddress) 向总线上指定地址(序列号)设备发送温度转换指令,阻塞模式下该方法将阻塞一定时间;bool requestTemperaturesByIndex(uint8_t deviceIndex) 向总线上指定索引设备发送温度转换指令,阻塞模式下该方法将阻塞一定时间;int16_t getTemp(const uint8_t* deviceAddress) 返回指定地址(序列号)设备温度数据,数据分辨率为1/128℃,如果发生错误则返回DEVICE_DISCONNECTED_RAW(-7040,默认值); 为了兼容性考虑这里温度数据的分辨率和DS18B20有所不同,DS18B20默认分别率为1/16℃;float getTempC(const uint8_t* deviceAddress) 返回指定地址(序列号)设备摄氏温度,如果发生错误则返回DEVICE_DISCONNECTED_C(-127,默认值);float getTempF(const uint8_t* deviceAddress) 返回指定地址(序列号)设备华氏温度,如果发生错误则返回DEVICE_DISCONNECTED_F(-196.6,默认值);float getTempCByIndex(uint8_t deviceIndex)float getTempFByIndex(uint8_t deviceIndex) 返回指定索引设备温度,如果发送错误则返回默认值;bool isParasitePowerMode(void) 返回是否需要总线寄生供电,需要则返回true; 警报相关方法不在此列出,请自行查询源码static float toFahrenheit(float) 将摄氏温度转为华氏温度;static float toCelsius(float) 将华氏温度转为摄氏温度;static float rawToCelsius(int16_t) 将原始温度转换为摄氏温度;static float rawToFahrenheit(int16_t) 将原始温度转换为华氏温度; 总结 基于OneWire和DallasTemperature库使用DS18B20主要内容就是上面这些了,更多信息可以参考美信对于1-Wire总线的引用笔记: https://www.maximintegrated.com/cn/app-notes/index.mvp/id/126 https://www.maximintegrated.com/cn/app-notes/index.mvp/id/187 一、概述 DS18B20数字温度传感器提供9bit到12bit的摄氏温度测量精度和一个用户可编程的非易失性且具有过温和低温触发报警的报警功能。DS18B20采用的1-Wire即单总线通信方式,即仅采用一个数据线与微控制器进行通信。该传感器的温度监测范围为-55℃至+125℃,并且在温度超过-10℃至85℃之外时还具有+-0.5℃的精度。此外,DS18B20可以直接由数据线供电而不需要外部电源供电。(本篇文章重在以简单例子讲清楚该型传感器最难的部分即工作时序,同时向大家分享例程及自己遇到的编程中的“坑”,帮助大家少走弯路尽快上手该型传感器,而不追求功能上的尽善尽美,因此本文仿真只能实现正整数温度值的显示,对于小数则进行四舍五入后再显示) 二、重要特性 独特的1-wire总线接口仅需要一个管脚来通信每个设备的内部ROM上都烧写了一个独一无二的64位序列号多路采集能力使得分布式温度采集应用更加简单无需外围元件能够采用数据线供电;供电范围为3.0V至5.5V温度可测量范围为-55℃至+125℃(-67℉至+257℉)温度超过-10℃至85℃之外时还具有+-0.5℃的精度内部温度采集精度可由用户自定义为9bit至12bit(上电默认12bit)温度转换时间在12bit时达到最大值750ms用户自定义非易失性的报警设置 三、工作指令 温度转换指令:0x44(即44H),启动Ds18b20启动转换温度读暂存器指令:0xBE(即BEH),读取暂存器中的九字节数据写暂存器置零:0x4E(即4EH),把数据写入暂存器的TH、TL赋值暂存器:0x48(即48H),把暂存器中的TH、TL写入EEPROM中读电源供电方式:0xB4(即B4H):启动Ds18b20,发送电源供电方式重调EEPROM:0xB8(即B8H):把EEPROM中的TH、TL读至暂存器 四·、通过单总线访问DS18B20的顺序 初始化ROM操作指令存储器操作命令执行/数据 五、工作时序 (一)初始化(复位操作) 在初始化序列期间,总线上的主设备通过拉低1-wire总线超过480us来发送(TX)复位脉冲。之后主设备释放总线而进入接收模式(RX)。当总线释放后,5KΩ左右的上拉电阻将1-wire总线拉至高电平。当DS18B20检测到该上升沿后,其等待15us至60us后通过1-wire总线拉低60us至240us来是实现发送一个存在脉冲。 图5.1? “复位”操作时序图
根据上述描述及时序图,可以写出“复位”操作的子函数: void Init_Ds(void)//DS18B20初始化 { Bus=0;//主动拉低480-960us(此处选择600us) Delay600us(); Bus=1;//释放总线,传感器15-60us后拉低总线 while(Bus);//等待传感器拉低; while(!Bus);//度过传感器被拉低的时间(60-240us)后主动拉高 Bus=1;//主动拉高 } (二)控制器的“写”操作(先写低位后写高位) “写”时段有两种情况:写“1”时段和写“0”时段。控制器通过写1时段来向DS18B20中写入逻辑1以及通过写0时段来向DS18B20中写入逻辑0。每个写时段最小必须有60us的持续时间且堵路的写时段之间至少要有1us的恢复时间。两个写时段都是由控制器通过将1-wire中先拉低来进行初始化(详见图5.2)。 为了形成写1时段,在将1-wire总线拉低后,主设备必须在15us之内释放总线。当总线释放后,5KΩ的上拉电阻将总线拉高;为了形成写0时段,在将1-wire总线拉低后,在整个时段期间控制器必须一直拉低总线(至少60us)。 在控制器初始化写时段后,DS18B20将会在15us至60us的时间窗口对1-wire总线进行采样。如果总线在采样窗口期间是高电平,则逻辑1被写入DS18B20;若总线是低电平,则逻辑0被写入DS18B20。 图5.2 “写”操作时序图
根据上述描述及时序图,可以写出“写”操作的子函数: /********************************向DS18B20写入一字节***********************/ void Write_Ds(uchar com)//从低位开始写入 { uchar mask; for(mask=0x01;mask!=0;mask { //该位为0,先拉低,15us后在拉高,并通过延时使整个周期为60us //该位为1,先拉低并在15us内(此处选择5us)拉高,并通过延时使整个周期为60us Bus=0; _nop_();_nop_();_nop_();_nop_();_nop_();//先拉低5us if((com&mask)==0)//该位是0 { Bus=0; } else//该位是1 { Bus=1; } Delay10us();Delay10us();Delay10us();Delay10us();Delay10us();;//延时60us _nop_();_nop_();_nop_();_nop_();_nop_(); Bus=1;//拉高 _nop_();_nop_();//写两个位之间至少有1us的间隔(此处选择2us) } } (三)控制器的“读”操作(先读低位后读高位) 仅在读时段期间DS18B20才能向主设备传动数据。因此,主设备在执行完读暂存寄存器[BEh]或读取供电模式[B4h]后,必须及时的生成读时段,这样DS18B20才能提供所需的数据。此外,主设备可以在执行完温度转换[44h]或拷贝EEPROM[B8h]命令后生成读时段,以便获得在“DS18B20功能命令”章节中提到的操作信息。 每个读时段最小必须有60us的持续时间且独立的写时段之间至少间隔1us。读时段通过控制器将总线拉低超过1us再释放总线来实现初始化(详见图5.3)。当控制器初始化完读时段后,DS18B20将会向总线发送0或1。DS18B20将通过拉高总线发送逻辑1,拉低总线发送逻辑0.发送完逻辑0后,DS18B20将会释放总线,在通过上拉电阻将该总线拉至高电平的闲置状态。从DS18B20中输出的数据在初始化读时序后仅有15us的有效时间。因此。控制器再开始改读时段后的15us之内必须释放总线,并且对总线进行采样。 图5.3 “读”操作时序图
根据上述描述及时序图,可以写出“读”操作的子函数 /********************************从DS18B20读出一字节***********************/ uchar Read_Ds(void)//先读的是低位,整个读周期至少为60us,但控制器采样要在15us内完成,相邻“位”之间至少间隔1us { uchar value=0,mask; for(mask=0x01;mask!=0;mask { Bus=0;//先把总线拉低超过1us(此处选择2us)后释放 _nop_();_nop_(); Bus=1; _nop_();_nop_();_nop_();_nop_();_nop_();_nop_();//再延时6us后读总线数据 if(Bus==0)//如果该位是0 { value&=(~mask); } else { value|=mask; } Delay10us();Delay10us();Delay10us();Delay10us();Delay10us();//再延时52us,凑够至少60us的采样周期 _nop_();_nop_(); Bus=1; _nop_();_nop_();//写两个位之间至少有1us的间隔(此处选择2us) } return value; } 六、注意事项(我踩过的坑) 1. 关于延时问题 DS18B20最大的优势之一就是单总线通信,我们通过一根数据线就可以完成诸多操作,但作为代价的是,DS18B20的工作时序十分复杂,因此对定时精度要求极高。平时大家操作定时精度要求不高的传感器可能会养成一个习惯,比如我们已经有了一个1ms且0误差的延时函数,当我们遇到一个20ms的延时需求时,可能会通过for/while循环将延时为1ms的延时函数执行20次。实际上,这样的方式所达到的延时时间的远大于20ms的,但对于定时精度要求不高的传感器,毫秒级的误差不会带来影响,但对于该传感器则不可。所以,在这款传感器的操作中,即使已经有一个10us的延时函数而需要一个20us的延时时,也要重新写一个20us的延时函数,不可将10us的延时函数循环执行两次。 2. 关于总时序问题 该传感器中的所有操作都要遵循“初始化-ROM命令-DS18B20功能命令”的总时序。比如,测量温度的操作要先后经过“初始化-跳过ROM命令-转换温度命令”与“初始化-跳过ROM命令-读取温度命令”这两大步。常犯的错误为“初始化-跳过ROM命令-转换温度命令-读取温度命令”,也就是说认为初始化与ROM命令在操作传感器的最初执行一次即可,这种想法是错误的。 3. 关于编程细节 在自己编程的过程中,遭遇了一个细节性的bug,即将命令值com与掩码mask相与是否为0作为进入if语句内部的判断条件的过程中,判断条件是这么写的if(com&mask==0),而实际应该写为if((com&mask)==0),即com&mask需要用括号括起来作为一个整体,否则会出错。 七、完整例程(例程均为自己编写且验证成功) /*所用单片机型号为AT89C52,晶振为12MHz,显示模块采用LCD1602液晶屏*/ #include #include typedef unsigned char uchar; typedef unsigned int uint; sbit Bus=P3^0;//数据单总线 sbit RS=P3^3; sbit RW=P3^4; sbit E=P3^5; void Delay10us(void);//10us延时函数 void Delay600us(void);//600us延时子函数 void Delay(uint n);//LCD1602中延时子函数 void Delay1ms(uint t);//t毫秒延时子函数 void Init_Ds(void);//DS18B20初始化 void Write_Ds(uchar com);//向DS18B20写入一字节 uchar Read_Ds(void);//从DS18B20读出一字节 uint Get_Tem(void);//获取温度值 void Change(uint x);//把整型数值x转换为字符串 void Write_com(uchar com);//写命令子函数 void Write_dat(uchar dat);//写数据子函数 void Init_1602(void);//LCD1602初始化子函数 void Show(uchar x,uchar y,uchar *str);//LCD1602显示子函数 uchar str[4];//储存转换值对应的字符串 void main() { unsigned int temp; Init_1602(); temp=Get_Tem(); Change(temp); Show(1,1,"T:"); Show(1,3,str); while(1); } /***************************************延时函数体**************************/ void Delay10us(void)//10us延时函数 { unsigned char a,b; for(b=1;b>0;b--) for(a=1;a>0;a--); } void Delay600us(void)//600us延时函数 { unsigned char a,b; for(b=119;b>0;b--) for(a=1;a>0;a--); } void Delay(uint n)//LCD1602中延时函数 { uint x,y; for(x=n;x>0;x--) for(y=110;y>0;y--); } void Delay1ms(uint t)//t毫秒延时函数 { unsigned char a,b; uint i; for(i=0;i for(b=199;b>0;b--) for(a=1;a>0;a--); } /********************************DS18B20初始化函数*************************/ void Init_Ds(void)//DS18B20初始化 { Bus=0;//主动拉低480-960us(此处选择600us) Delay600us(); Bus=1;//释放总线,传感器15-60us后拉低总线 while(Bus);//等待传感器拉低; while(!Bus);//度过传感器被拉低的时间(60-240us)后主动拉高 Bus=1;//主动拉高 } /********************************向DS18B20写入一字节***********************/ void Write_Ds(uchar com)//从低位开始写入 { uchar mask; for(mask=0x01;mask!=0;mask { //该位为0,先拉低,15us后在拉高,并通过延时使整个周期为60us //该位为1,先拉低并在15us内(此处选择5us)拉高,并通过延时使整个周期为60us Bus=0; _nop_();_nop_();_nop_();_nop_();_nop_();//先拉低5us if((com&mask)==0)//该位是0 { Bus=0; } else//该位是1 { Bus=1; } Delay10us();Delay10us();Delay10us();Delay10us();Delay10us();;//延时60us _nop_();_nop_();_nop_();_nop_();_nop_(); Bus=1;//拉高 _nop_();_nop_();//写两个位之间至少有1us的间隔(此处选择2us) } } /********************************从DS18B20读出一字节***********************/ uchar Read_Ds(void)//先读的是低位,整个读周期至少为60us,但控制器采样要在15us内完成,相邻“位”之间至少间隔1us { uchar value=0,mask; for(mask=0x01;mask!=0;mask { Bus=0;//先把总线拉低超过1us(此处选择2us)后释放 _nop_();_nop_(); Bus=1; _nop_();_nop_();_nop_();_nop_();_nop_();_nop_();//再延时6us后读总线数据 if(Bus==0)//如果该位是0 { value&=(~mask); } else { value|=mask; } Delay10us();Delay10us();Delay10us();Delay10us();Delay10us();//再延时52us,凑够至少60us的采样周期 _nop_();_nop_(); Bus=1; _nop_();_nop_();//写两个位之间至少有1us的间隔(此处选择2us) } return value; } /**********************************获取温度值函数***************************/ uint Get_Tem(void) { uint temp=0; float tp; uchar LSB=0,MSB=0; Delay1ms(10);//延时10ms度过不稳定期 Init_Ds();//Ds18b20初始化 Delay1ms(1); Write_Ds(0xcc);//跳过ROM寻址 Write_Ds(0x44);//启动一次温度转换 Delay1ms(1000);//延时1s等待转化 Init_Ds();//Ds18b20初始化 Delay1ms(1); Write_Ds(0xcc);//跳过ROM寻址 Write_Ds(0xbe);//发送读值命令· LSB=Read_Ds(); MSB=Read_Ds(); temp=MSB; temp temp|=LSB; tp=temp*0.0625; temp=tp; if(tp-temp>=0.5) { temp+=1; } return temp; } /******************************把整型数据转换为字符串**********************/ void Change(uint x) { str[0]=x/100+48; str[1]=(x/10)%10+48; str[2]=x%10+48; str[3]='\0'; }
/********************************写命令函数体****************************/ void Write_com(uchar com) { RS=0; P2=com; Delay(5); E=1; Delay(5); E=0; } /********************************写数据函数体****************************/ void Write_dat(uchar dat) { RS=1; P2=dat; Delay(5); E=1; Delay(5); E=0; } /*****************************LCD1602初始化函数体*************************/ void Init_1602() { uchar i=0; RW=0; Write_com(0x38);//屏幕初始化 Write_com(0x0c);//打开显示 无光标 无光标闪烁 Write_com(0x06);//当读或写一个字符是指针后一一位 Write_com(0x01);//清屏 Write_com(0x80);//设置位置 } /*******************************显示内容函数体**************************/ void Show(uchar x,uchar y,uchar *str) { unsigned char addr; if (x==1) { addr=0x00+y-1; //从第一行、第y列开始显示 } else { addr=0x40+y-1; //第二行、第y列开始显示 } Write_com(addr+0x80); while (*str!='\0') { Write_dat(*str++); }
} 八、Proteus仿真图 图8.1 仿真图
? ? 左肩理想右肩担当,君子不怨永远不会停下脚步! ? ? ? ? ? ? ? ? ? ? 一、硬件链接 在元件库中搜索DS18B20,按下图的连接方式链接。 ?二、驱动代码 #define uchar unsigned char #define uint unsigned int sbit DS1820_dat=P3^2; void DS18B20_delay(uint T) //???? { while(T--); }
void Initial() { DS1820_dat=1; DS18B20_delay(8); //??16?? DS1820_dat=0; DS18B20_delay(80); DS1820_dat=1; DS18B20_delay(15); }
void Input(uchar f) //18B20??????? { uchar j; for (j=0;j { DS1820_dat=0; DS1820_dat=f&0x01; DS18B20_delay(10); DS1820_dat=1; f>>=1; } } uchar Output() //18b20 ???????? { uchar j,b; for (j=0;j { DS1820_dat=0; b>>=1; DS1820_dat=1; if (DS1820_dat) { b=b|0x80; } DS18B20_delay(10); } return(b); } int Temper() { int Store; int Store_2,Store_1; Initial(); //对 DS18B20 复位 Input(0xcc); //跳过 ROM Input(0x44); //启动温度转换 Initial(); //对 DS18B20 复位 Input(0xcc); //跳过 ROM Input(0xbe); //读 RAM 命令 Store_1=Output(); //读温度的低字节 Store_2=Output(); //读温度的高字节 Store_2=(Store_2*256)+Store_1; //合起来为温度的整数部分 Store=(int) Store_2*0.625; return Store; } 三、软件示例 完整代码功能为51单片机温度控制系统,proteus仿真资料 功能: 1、低于设定最低温度时加热丝加热,高于最高温度时停止加热。可用按键设置高低温度。 2、显示当前时间,时间可设置。 3、DAC液位监测,液位低于10%开启电机上水,高于90%关闭电机上水,百分比可在程序中修改 评论或私信“205资料”获取完整工程文件 /*****************主函数********************/ void main() {
beep = 0; //开机蜂鸣器叫一声 delay_1ms(200); P0 = P1 = P2 = P3 = 0xff; //初始化IO口 init_time0(); //初始化定时器 init_1602(); //lcd1602初始化 init_1602_dis_csf(); //lcd1602初始化显示 do { temp=Temper(); } while(temp==850); //因为DS18B20上电之后会读出来850,但不是温度值,所以等待读出温度值 delay_1ms(100); while(1) { key(); //按键程序 if(key_can > 0) { beep = 0; delay_1ms(50); beep = 1; key_with(); }
if(flag_200ms == 1) { flag_200ms = 0; if(menu_1 == 0) { read_time(); //读时间 init_1602_ds1302(); //显示时钟 vol_value=VOL_VAL*A_D()/256; //获取模拟电压值 percent = 100.0*vol_value/VOL_VAL; display_temp(55); } } delay_1ms(1); } } 标签: proteus怎么找ds18b20_DS18B20型温度传感器的使用详解及Proteus仿真(附源码) |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |