1.1两种传输方法1 1.2 Modbus消息帧2 1.3毛病检测法子4
一、Modbus 协议分析0
二、法度设计思想5
2.1总体设计5 2.2 硬件设计6
2.2.1单片机串行通信功能6 2.2.2 MAX232芯片7 2.2.3 整体电路设计8 2.3 软件设计9
2.3.1主机系统软件设计9 2.3.2 从机系统软件设计11
三、法度代码14
基于51单片机的双机串行通信设计
一、Modbus 协议分析
Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络和其它设备之间可以通信。它已经成为一通用工业尺度。有了它,分歧厂商生产的控制设备可以连成工业网络,进行集中监控。
此协议界说了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如何回应来自其它设备的请求,以及怎样侦测毛病并记录。它制定了消息域格局和内容的公共格式。
当在一Modbus网络上通信时,此协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要发生何种行动。如果需要回应,控制器将生成反响信息并用Modbus协议收回。在其它网络上,包含了Modbus协议的消息转换为在此网络上使用的帧或包结构。
这种转换也扩展了根据具体的网络解决节地址、路由路径及毛病检测的法子。 1) 在Modbus网络上转输
尺度的Modbus口是使用一RS232C兼容串行接口,它界说了连接口的针脚、电缆、信号位、传输波特率、奇偶校验。控制器能直接或经由Modem组网。
控制器通信使用主—从技术,即仅一设备(主设备)能初始化传输(查询)。其它设备(从设备)根据主设备查询提供的数据作出相应反响。典范的主设备:主机和可编程仪表。典范的从设备:可编程控制器。
主设备可单独和从设备通信,也能以广播方法和所有从设备通信。如果单独通信,从设备前往一消息作为回应,如果是以广播方法查询的,则不作任何回应。Modbus协议建立了主设备查询的格式:设备(或广播)地址、功能代码、所有要发送的数据、一毛病检测域。 从设备回应消息也由Modbus协议构成,包含确认要行动的域、任何要前往的数据、和一毛病检测域。如果在消息接收过程中发生一毛病,或从设备不克不及执行其命令,从设备将建立一毛病消息并把它作为回应发送出去。 2) 在其它类型网络上传输
在其它网络上,控制器使用对等技术通信,故任何控制都能初始和其它控制器的通信。这样在单独的通信过程中,控制器既可作为主设备也可作为从设备。提供的多个内部通道可允许同时发生的传输进程。
在消息位,Modbus协议仍提供了主—从原则,尽管网络通信法子是“对等”。如果一控制器发送一消息,它只是作为主设备,并期望从从设备获得回应。同样,当控制器接收到一消息,它将建立一从设备回应格式并前往给发送的控制器。 3) 查询—回应周期 (i)查询
查询消息中的功能代码告之被选中的从设备要执行何种功能。数据段包含了从设备要执行功能的任何附加信息。例如功能代码03是要求从设备读坚持寄存器并前往它们的内容。数据段必须包含要告之从设备的信息:从何寄存器开始读及要读的寄存器数量。毛病检测域为从设备提供了一种验证消息内容是否正确的法子。 (ii)回应
如果从设备发生一正常的回应,在回应消息中的功能代码是在查询消息中的功能代码的回应。数据段包含了从设备收集的数据:象寄存器值或状态。如果有毛病发生,功能代码将被修改以用于指出回应消息是毛病的,同时数据段包含了描述此毛病信息的代码。毛病检测域允许主设备确认消息内容是否可用。
1.1两种传输方法
控制器能设置为两种传输模式(ASCII或RTU)中的任何一种在尺度的Modbus网络通信。用户选择想要的模式,包含串口通信参数(波特率、校验方法等),在配置每个控制器的时候,在一个Modbus网络上的所有设备都必须选择相同的传输模式和串口参数。 ASCII模式 : 地址 功能代码 数据数量 数据1 ... 数据n LRC高字节 LRC低字节 回车 换行 RTU模式 地址 功能代码 数据数量 数据1 ... 数据n CRC高字节 CRC低字节 所选的ASCII或RTU方法仅适用于尺度的Modbus网络,它界说了在这些网络上连续传输的消息段的每一位,以及决定怎样将信息打包成消息域和如何解码。
在其它网络上(像MAP和Modbus Plus)Modbus消息被转成与串行传输无关的帧。 1)ASCII模式
当控制器设为在Modbus网络上以ASCII(美国尺度信息交换代码)模式通信,在消息中的每个8Bit字节都作为两个ASCII字符发送。这种方法的主要优点是字符发送的时间间隔可达到1秒而不发生毛病。
代码系统
十六进制,ASCII字符0...9,A...F,消息中的每个ASCII字符都是一个十六进制字符组成每个字节的位。 •1个起始位
•7个数据位,最小的有效位先发送 •1个奇偶校验位,无校验则无 •1个停止位(有校验时),2个Bit(无校验时) 毛病检测域
•LRC(纵向冗长检测) 2)RTU模式
当控制器设为在Modbus网络上以RTU(远程终端单位)模式通信,在消息中的每个8Bit字节包含两个4Bit的十六进制字符。这种方法的主要优点是:在同样的波特率下,可比ASCII方法传送更多的数据。 代码系统
•8位二进制,十六进制数0...9,A...F
•消息中的每个8位域都是一个两个十六进制字符组成 每个字节的位 •1个起始位
•8个数据位,最小的有效位先发送 •1个奇偶校验位,无校验则无 •1个停止位(有校验时),2个Bit(无校验时) 毛病检测域
•CRC(循环冗长检测)
1.2 Modbus消息帧
两种传输模式中(ASCII或RTU),传输设备以将Modbus消息帧为有起点和终点的帧,这就允许接收的设备在消息起始处开始工作,读地址分派信息,判断哪一个设备被选中(广播方法则传给所有设备),判知何时信息已完成。部分的消息也能侦测到而且毛病能设置为前往结果。 1) ASCII帧
使用ASCII模式,消息以冒号(:)字符(ASCII码 3AH)开始,以回车换行符结束(ASCII码 0DH,0AH)。
其它域可以使用的传输字符是十六进制的0...9,A...F。网络上的设备不竭侦测“:”字符,当有一个冒号接收到时,每个设备都解码下个域(地址域)来判断是否发给自己的。
消息中字符间发送的时间间隔最长不克不及超出1秒,否则接收的设备将认为传输毛病。一个典范消息帧如下所示: 起始位 1个字符 设备地址 2个字符 功能代码 2个字符 数据 n个字符 LRC校验 2个字符 结束符 2个字符 ASCII消息帧 2) RTU帧
使用RTU模式,消息发送至少要以3.5个字符时间的停顿间隔开始。在网络波特率下多样的字符时间,这是最容易实现的(如下图的T1T2T3T4所示)。传输的第一个域是设备地址。可以使用的传输字符是十六进制的0...9,A...F。网络设备不竭侦测网络总线,包含停顿
间隔时间内。当第一个域(地址域)接收到,每个设备都进行解码以判断是否发往自己的。在最后一个传输字符之后,一个至少3.5个字符时间的停顿标定了消息的结束。一个新的消息可在此停顿后开始。
整个消息帧必须作为一连续的流转输。如果在帧完成之前有超出1.5个字符时间的停登时间,接收设备将刷新不完整的消息并假定下一字节是一个新消息的地址域。同样地,如果一个新消息在小于3.5个字符时间内接着前个消息开始,接收的设备将认为它是前一消息的延续。这将招致一个毛病,因为在最后的CRC域的值不成能是正确的。一典范的消息帧如下所示: 起始位 T1T2T3T4 设备地址 8Bit 功能代码 8Bit 数据 n个8Bit RTU消息帧 3)地址域
消息帧的地址域包含两个字符(ASCII)或8Bit(RTU)。可能的从设备地址是0...247 (十进制)。单个设备的地址规模是1...247。主设备通过将要联络的从设备的地址放入消息中的地址域来选通从设备。当从设备发送回应消息时,它把自己的地址放入回应的地址域中,以便主设备知道是哪一个设备作出回应。地址0是用作广播地址,以使所有的从设备都能认识。当Modbus协议用于更高水准的网络,广播可能不允许或以其它方法取代。 4)如何处理功能域
消息帧中的功能代码域包含了两个字符(ASCII)或8Bits(RTU)。可能的代码规模是十进制的1...255。固然,有些代码是适用于所有控制器,有些是应用于某种控制器,还有些保管以备后用。
当消息从主设备发往从设备时,功能代码域将告之从设备需要执行哪些行为。例如去读取输入的开关状态,读一组寄存器的数据内容,读从设备的诊断状态,允许调入、记录、校验在从设备中的法度等。
当从设备回应时,它使用功能代码域来指示是正常回应(无误)还是有某种毛病发生(称作异议回应)。对正常回应,从设备仅回应相应的功能代码。对异议回应,从设备前往一同即是正常代码的代码,但最重要的位置为逻辑1。
主设备应用法度获得异议的回应后,典范的处理过程是重发消息,或者诊断发给从设备的消息并陈述给把持员。 5)数据域
数据域是由两个十六进制数集合构成的,规模00...FF。根据网络传输模式,这可以是由一对ASCII字符组成或由一RTU字符组成。
从主设备发给从设备消息的数据域包含附加的信息:从设备必须用于进行执行由功能代码所界说的所为。这包含了象不连续的寄存器地址,要处理项的数目,域中实际数据字节数。 6)毛病检测域
尺度的Modbus网络有两种毛病检测法子。毛病检测域的内容视所选的检测法子而定。 ASCII
被选用ASCII模式作字符帧,毛病检测域包含两个ASCII字符。这是使用LRC(纵向冗长检测)法子抵消息内容计算得出的,不包含开始的冒号符及回车换行符。LRC字符附加在回车换行符前面。 RTU
被选用RTU模式作字符帧,毛病检测域包含一16Bits值(用两个8位的字符来实现)。毛病检测域的内容是通过抵消息内容进行循环冗长检测法子得出的。CRC域附加在消息的
CRC校验 16Bit 结束符 T1T2T3T4 最后,添加时先是低字节然后是高字节。故CRC的高位字节是发送消息的最后一个字节。 7)字符的连续传输
当消息在尺度的Modbus系列网络传输时,每个字符或字节以如下方法发送(从左到右): 最低有效位...最高有效位
使用ASCII字符帧时,位的序列是: 有奇偶校验 启始位 无奇偶校验 启始位 1 2 3 4 5 6 7 停止位 停止位 1 2 3 4 5 6 7 奇偶位 停止位 位顺序(ASCII) 使用RTU字符帧时,位的序列是: 有奇偶校验 启始位 无奇偶校验 启始位 1 2 3 4 5 6 7 8 停止位 停止位 1 2 3 4 5 6 7 8 奇偶位 停止位 位顺序(RTU) 1.3毛病检测法子
尺度的Modbus串行网络采纳两种毛病检测法子。奇偶校验对每个字符都可用,帧检测(LRC或CRC)应用于整个消息。它们都是在消息发送前由主设备发生的,从设备在接收过程中检测每个字符和整个消息帧。
用户要给主设备配置一预先界说的超时时间间隔,这个时间间隔要足够长,以使任何从设备都能作为正常反响。如果从设备测到一传输毛病,消息将不会接收,也不会向主设备作出回应。这样超时事件将触发主设备来处理毛病。发往不存在的从设备的地址也会发生超时。 1)奇偶校验
用户可以配置控制器是奇或偶校验,或无校验。这将决定了每个字符中的奇偶校验位是如何设置的。如果指定了奇或偶校验,“1”的位数将算到每个字符的位数中(ASCII模式7个数据位,RTU中8个数据位)。例如RTU字符帧中包含以下8个数据位:1 1 0 0 0 1 0 1,整个“1”的数目是4个。如果便用了偶校验,帧的奇偶校验位将是0,便得整个“1”的个数仍是4个。如果便用了奇校验,帧的奇偶校验位将是1,便得整个“1”的个数是5个。如果没有指定奇偶校验位,传输时就没有校验位,也不进行校验检测。取代一附加的停止位填充至要传输的字符帧中。 2)LRC检测
使用ASCII模式,消息包含了一基于LRC法子的毛病检测域。LRC域检测了消息域中除开始的冒号及结束的回车换行号外的内容。
LRC域是一个包含一个8位二进制值的字节。LRC值由传输设备来计算并放到消息帧中,接收设备在接收消息的过程中计算LRC,并将它和接收到消息中LRC域中的值比较,如果两值不等,说明有毛病。
LRC法子是将消息中的8Bit的字节连续累加,抛弃了进位。 3)CRC检测
使用RTU模式,消息包含了一基于CRC法子的毛病检测域。CRC域检测了整个消息
的内容。
CRC域是两个字节,包含一16位的二进制值。它由传输设备计算后加入到消息中。接收设备重新计算收到消息的CRC,并与接收到的CRC域中的值比较,如果两值分歧,则有误。 CRC是先调入一值是全“1”的16位寄存器,然后调用一过程将消息中连续的8位字节各以后寄存器中的值进行处理。仅每个字符中的8Bit数据对CRC有效,起始位和停止位以及奇偶校验位均无效。
CRC发生过程中,每个8位字符都单独和寄存器内容相或(OR),结果向最低有效位标的目的移动,最高有效位以0填充。LSB被提取出来检测,如果LSB为1,寄存器单独和预置的值或一下,如果LSB为0,则不进行。整个过程要重复8次。在最后一位(第8位)完成后,下一个8位字节又单独和寄存器的以后值相或。最终寄存器中的值,是消息中所有的字节都执行之后的CRC值。
CRC添加到消息中时,低字节先加入,然后高字节。
二、法度设计思想
2.1总体设计
本文在MODBUS协议的基础上通过串行端口实现单片机和单片机的通讯,根据MODBUS协议,它有两种数据传输的方法:ASCII和RTU方法,由于RTU方法传输效率高,采纳CRC校验法子,数据传输准确,所以本文采纳了RTU方法。
硬件部分,使用两片89C4051,通过RS232进行双机通信。发送方的数据由串行口TXD段输出,经过电平转换芯片MAX232将TTL电平转换为RS232电平输出,经过传输线将信号传送到接收端。接收方也使用MAX232芯片进行电平转换后,信号达到接收方串行口的接收端。每个51从设备增加了一个I2C接口的ADC0831,主机通过协议来轮询各个从机的采样信号,解析后显示在主机的LCD屏的对应位置上。
软件部分,通过MODBUS协议进行发送接收,主机发送命令帧给从机,等待从机回应,等待超时则重新发送。从机收到命令帧后检查目的地址,确认后计算接收到数据的检验和,与主机发送来的检验和进行比较,若检验和相同则发送正常回应帧给主机;否则发送毛病陈述帧给主机,告知主机重新发送命令帧。
2.2 硬件设计
2.2.1单片机串行通信功能
图21 AT89C4051
计算机与外界的信息交换称为通信,经常使用的通信方法有两种:并行通信和串行通信。51单片机用4个接口与外界进行数据输入与数据输出就是并行通信,并行通信的特点是传输信号的速度快,但所用的信号线较多,成本高,传输的距离较近。串行通信的特点是只用两条信号线(一条信号线,再加一条地线作为信号回路)即可完成通信,成本低,传输的距离较远。
51单片机的串行接口是一个全双工的接口,它可以作为UART(通用异步接受和发送器)用,也可以作为同步移位寄存器用。51单片机串行接口的结构如下:
(1)数据缓冲器(SBUF)
接受或发送的数据都要先送到SBUF缓存。有两个,一个缓存,另一个接受,用同一直接地址99H,发送时用指令将数据送到SBUF即可启动发送;接收时用指令将SBUF中接收到的数据取出。
(2)串行控制寄存器(是SCON)
SCON用于串行通信方法的选择,收发控制及状态指示,各位含义如下: SM0 SM1 SM2 REN TB8 RB8 TI RI SM0,SM1:串行接口工作方法选择位,这两位组合成00,01,10,11对应于工作方法0、1、2、3。串行接口工作方法特点见下表。 SM0 SM1 0 0 0 1 工作方法 0 1 功能 8位同步移位寄存器(用于I/O扩展) fORC/12 10位异步串行通信(UART) 可变(T1*2SMOD/32) 1 1 0 1 2 3 11位异步串行通信(UART) 11位异步串行通信(UART) fORC/64或fORC/32 可变(T1*2SMOD/32) 溢出率溢出率波特率 SM2:多机通信控制位。
REN:接收允许控制位。软件置1允许接收;软件置0禁止接收。
TB8:方法2或3时,TB8为要发送的第9位数据,根据需要由软件置1或清0。 RB9:在方法2或3时,RB8位接收到的第9位数据,实际为主机发送的第9位数据TB8,使从机根据这一位来判断主机发送的时呼叫地址还是要传送的数据。
TI:发送中断标识表记标帜。发送完一帧数据后由硬件自动置位,并申请中断。必须要软件清零后才干继续发送。
RI:接收中断标识表记标帜。接收完一帧数据后由硬件自动置位,并申请中断。必须要软件清零后才干继续接收。
(3)输入移位寄存器
接收的数据先串行进入输入移位寄存器,8位数据全移入后,再并行送入接收SBUF中。 (4)波特率发生器
波特率发生器用来控制串行通信的数据传输速率的,51系列单片机用按时器T1作为波特率发生器,T1设置在按时方法。波特率时用来暗示串行通信数据传输快慢水平的物理量,界说为每秒钟传送的数据位数。
(5)电源控制寄存器PCON 其最高位为SMOD。 (6)波特率计算
当按时器T1工作在按时方法的时候,按时器T1溢出率=(T1计数率)/(发生溢出所需机器周期)。由于是按时方法,T1计数率= fORC/12。发生溢出所需机器周期数=模M计数初值X。
2.2.2MAX232芯片
用8051串行接口通信,如果两台8051单片机之间的距离很近(不超出1.5m),可以采纳直接将两台8051单片机的串行接口直接相连,利用其自身的TTL电平(05V)直接传输数据信息。如果传输距离较远(超出1.5m),由于传输线的阻抗与散布电容,会发生电平损耗和波形畸变,以至于检测不出数据或数据出错。此时可利用 RS232尺度总线接口,将单片机输出的TTL电平转换为RS232尺度电平(逻辑1为15—5V;逻辑0为+5—+15V)。用RS232可将传输距离提高到15m,如果想远距离传输,可以采纳RS422或者RS485。
电平转换芯片MAX232是美信公司(MAXIM)生产,专用于进行将TTL电平转换为RS232电平的芯片,MAX232内部有泵电源,能将+5V电源电压在芯片内提高到RS232电平所需的+10V或者10V电平。
图22电平转换芯片MAX232
2.2.3整体电路设计
最终设计电路如下图3所示,发送方的数据由串行口TXD段输出,经过电平转换芯片MAX232将TTL电平转换为RS232电平输出,经过传输线将信号传送到接收端。接收方也使用MAX232芯片进行电平转换后,信号达到接收方串行口的接收端。每个51从设备增加了一个I2C接口的ADC0831,主机通过MODBUS协议来轮询各个从机的采样信号,解析后显示在主机的LCD屏的对应位置上。主机和从机发送的数据通过VIRTUAL TERMINAL显示。
图23串行通信电路
2.3 软件设计
通过MODBUS协议进行发送接收,主机发送命令帧给从机,等待从机回应,等待超时则重新发送。从机收到命令帧后检查目的地址,确认后计算接收到数据的检验和,与主机发送来的检验和进行比较,若检验和相同则发送正常回应帧给主机;否则发送毛病陈述帧给主机,告知主机重新发送命令帧。
2.3.1主机系统软件设计
主机要对从机实施控制,首先要实现两者之间的通讯,依照MODBUS协议 的约定,在通讯中的信息数据中设置特定的代码,在通讯胜利后,对信息数据进 行解析和提取,然后启动相应的中断法度进行处理。 本设计中,主机的通信过程如下:
(1)主法度:开始,掀开串口,然后准备要发送的信号数据,调用CRC生
成法度获取信号数据的CRC校验码,组织命令报文,以MODBUS的RTU信息帧方法发送报文,发送完毕后进入侦听阶段,侦测帧头(T1.T2.T3.T4),通过判断,若合适MODBUS协议规定四个字符延时等待时间,等待中断处事处事,中断处理未完毕后前往中断等待,处理完毕,中断前往,前往主法度的数据准备阶段,继续进行侦听;若超时,进行超时处理,处理完毕前往主法度,进入数据准备。
(2)中断法度:响应中断后,进入中断处事法度,接收数据,进行CRC校验,若命令数
据正确,进行命令报文解析,根据协议约定功能进行数据处理,处理完毕进入主法度;否则,重发报文,重发完毕,前往进入主法度。主机软件设计如主机主法度流程图24和主机中断处事子法度流程图25所示。
开 始打开中断准备数据产生CRC校验码组织报文发送侦测帧头否T1-T2-T3-T4?是否否超时?是等待中断超时处理中断返回是
图24 主机主法度流程图
开 始中断入口接收数据产生CRC校验码CRC正确?是否报文解析重发报文数据处理中断返回结 束 图25 主机中断处事子法度流程图
2.3.2 从机系统软件设计
从机的法度设计要实现五年夜功能:第一,要实现主机和从机之间的通讯功能
(接收报文和回报文);第二,要完成命令报文的解析和信息提取(设备地址、功能码,寄存器地址和数据等);第三,实现MODBUS协议规定的主要的功能(收集模拟量、从寄存器中取值、往寄存器写入数值以及向特定的单片机IO端口下置数字量);第四,通讯环节中的一些特定的中断法度的执行(接收和发生的启动、侦测报文开始位和结束位的启动中断等);第五,在通讯过程中包管信息数据的准确是很是重要的,所以从机软件的设计中要进行毛病检测,其法子多种多样,在本设计中采纳循环冗余检测(CRC),其优点是检验的效率比较高、准确率也比较高,实现五年夜功能法度设计,可以使从机完成MODBUS协议的相应功能。从机软件设计如从机主法度流程图26和从机中断子法度流程图27所示。
开 始打开中断打开串口,侦测帧头否T1-T2-T3-T4?是是否等待中断中断返回?
图26 从机主法度流程图
开 始中断入口接收数据是本机号码?产生CRC码CRC正确?报文解析执行报文功能发送响应报文错误报告中断返回结 束
图27 从机中断子法度流程图
三、法度代码
/*******************************主机********************************/ #include #define Uint16 unsigned int #define Uint32 unsigned long #define uchar unsigned char #define LCD_Data P1 #define Busy 0x80 //用于检测LCD状态字中的Busy标识 sbit reving=P3^5; //发送或接收指示灯,1:发送0:接收 sbit LCD_RS=P3^2;//界说引脚 sbit LCD_RW=P3^3; sbit LCD_E=P3^4; bit nodeok; //标识表记标帜位 bit crcok; //校验标识表记标帜位,1:校验正确0:校验毛病 uchar asknode; // 轮询的以后节点 uchar asknode_save; //保管轮询的以后节点 uchar mtx[8],mrx[10]; uchar send101[]={0x01,0x04,0x00,0x00,0x00,0x03}; uchar timercnt; //按时 uchar revptr; Uint16 delaycnt; void InitUART(void); void SendOneByte(unsigned); void mdproc(uchar); Uint16 Crc16(Uint16 *puchMsg, Uint16 usDataLen); void WriteDataLCD(unsigned char WDLCD); void WriteCommandLCD(unsigned char WCLCD,BuysC); unsigned char ReadDataLCD(void); unsigned char ReadStatusLCD(void); void LCDInit(void); void DisplayOneChar(unsigned char X, unsigned char Y, unsigned char DData); void DisplayListChar(unsigned char X, unsigned char Y, unsigned char *DData); void Delay5Ms(void); void Delay400Ms(void); /* CRC 高位字节值表 */ const Uint16 code auchCRCHi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 } ; /* CRC低位字节值表*/ const Uint16 code auchCRCLo[] = { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 } ; void main(void) { unsigned char temp8; Uint16 temp16,temp16_1,temp16_2; unsigned char code row1[] = {\"MODBUS FUNC:04H\ unsigned char code row2[] = {\"NOW GET START\ uchar row3[]={\" V1 V2 V3 \ uchar row4[]={\" . . . \ int i; reving=0; nodeok=0; timercnt=0x00; asknode=0x00; temp16_2=250; for(i=0;i<10;i++){mrx[i]=0x00;} for(i=0;i<8;i++){mtx[i]=0x00;} Delay400Ms(); //启动等待,等LCD讲入工作状态 LCDInit(); //LCD初始化 Delay5Ms(); //延时片刻(可不要) DisplayListChar(1, 0, row1); DisplayListChar(2, 1, row2); ReadDataLCD();//测试用句无意义 delaycnt=0xffff; while(delaycnt){} delaycnt=0xffff; while(delaycnt){} DisplayListChar(0, 0, row3); InitUART(); crcok=1; while(1) { if(crcok==1) { temp16_1=mrx[3]; temp16_1*=temp16_2; temp16_1>>=7; switch(mrx[0]) { case 0x01:{row4[1]=(temp16_1/100)+0x30;row4[3]=((temp16_1%100)/10)+0x30;row4[4]=(temp16_1%10)+0x30;break;} case 0x02:{row4[6]=(temp16_1/100)+0x30;row4[8]=((temp16_1%100)/10)+0x30;row4[9]=(temp16_1%10)+0x30;break;} case 0x03:{row4[11]=(temp16_1/100)+0x30;row4[13]=((temp16_1%100)/10)+0x30;row4[14]=(temp16_1%10)+0x30;break;} default:{break;} } DisplayListChar(0, 1, row4); // 在指定位置显示从机发送来的数据 delaycnt=0x3ff; //显示延迟 while(delaycnt){} reving=1; send101[0]=(asknode++)+0x01; // 打包主机发送帧 asknode_save=asknode1; asknode%=3; temp16=Crc16(send101,6); // 发生16位CRC校验码 for(i=0;i<6;i++){mtx[i]=send101[i];} mtx[6]=(temp16>>8)&0x00ff; mtx[7]=temp16&0x00ff; delaycnt=0x3ff; while(delaycnt){} for(i=0;i<8;i++) { temp8=mtx[i]; SendOneByte(temp8); } crcok=0; delaycnt=0x3ff; while(delaycnt){} timercnt=0; reving=0; // 发送完毕关闭LED,等待接收 } } } //按时100ms,重新发送数据 void Timer0Interrupt(void) interrupt 1 { TH0 = 0x3C; TL0 = 0x0B0; if(++timercnt==5) { crcok=1; asknode=asknode_save; timercnt=0; } } //接收从机数据 void UARTInterrupt(void) interrupt 4 { uchar byterev; if(RI) { RI = 0; byterev=SBUF; mdproc(byterev); } else TI = 0; } void mdproc(uchar b) { Uint16 temp16_1,temp16_2; if(nodeok==0) { if(b==send101[0]) //地址是否正确 { nodeok=1; revptr=1; mrx[0]=b; } } else if(revptr<11) //是否发送完毕 { mrx[revptr++]=b; } else { revptr=0; nodeok=0; temp16_1=Crc16(mrx,9); temp16_2=mrx[9]; temp16_2<<=8; temp16_2|=mrx[10]; if(temp16_1==temp16_2) //CRC校验是否正确 { if(mrx[1]==0x54) {asknode=asknode_save;crcok=1;} else crcok=1; } else { asknode=asknode_save; crcok=1; } } } void SendOneByte(unsigned char c) { SBUF = c; while(!TI); TI = 0; } //初始化串口通信(波特率设置为9600) void InitUART(void) { TMOD = 0x20; SCON = 0x50; TH1 = 0xFD; TL1 = TH1; PCON = 0x00; EA = 1; ES = 1; TR1 = 1; TMOD |= 0x01; TH0 = 0x3C; TL0 = 0xB0; // timer sets at 20ms ET0 = 1; TR0 = 1; } //发生16位CRC校验码 Uint16 Crc16(uchar *puchMsg, Uint16 usDataLen) { Uint16 uchCRCHi = 0xFF ; /* 高CRC字节初始化 */ Uint16 uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */ Uint16 temp16; Uint32 uIndex ; /* CRC循环中的索引 */ while (usDataLen) /* 传输消息缓冲区 */ { temp16=*puchMsg++; uIndex = uchCRCHi ^ temp16 ; /* 计算CRC */ uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ; uchCRCLo = auchCRCLo[uIndex] ; } return (uchCRCHi << 8 | uchCRCLo) ; } //写数据 void WriteDataLCD(unsigned char WDLCD) { ReadStatusLCD(); //检测忙 LCD_Data = WDLCD; LCD_RS = 1; LCD_RW = 0; LCD_E = 0; //若晶振速度太高可以在这后加小的延时 LCD_E = 0; //延时 LCD_E = 0; LCD_E = 1; } //写指令 void WriteCommandLCD(unsigned char WCLCD,BuysC) //BuysC为0时忽略忙检测 { if(BuysC) ReadStatusLCD(); //根据需要检测忙 LCD_Data = WCLCD; LCD_RS = 0; LCD_RW = 0; LCD_E = 0; LCD_E = 0; LCD_E = 1; } //读数据 unsigned char ReadDataLCD(void) { LCD_RS = 1; LCD_RW = 1; LCD_E = 0; LCD_E = 0; LCD_E = 1; return(LCD_Data); } //读状态 unsigned char ReadStatusLCD(void) { LCD_Data = 0xFF; LCD_RS = 0; LCD_RW = 1; LCD_E = 0; LCD_E = 0; LCD_E = 1; while (LCD_Data & Busy); //检测忙信号 return(LCD_Data); } //LCD初始化 void LCDInit(void) { LCD_Data = 0; WriteCommandLCD(0x38,0); //三次显示模式设置,不检测忙信号 Delay5Ms(); WriteCommandLCD(0x38,0); Delay5Ms(); WriteCommandLCD(0x38,0); Delay5Ms(); WriteCommandLCD(0x38,1); //显示模式设置,开始要求每次检测忙信号 WriteCommandLCD(0x08,1); //关闭显示 WriteCommandLCD(0x01,1); //显示清屏 WriteCommandLCD(0x06,1); // 显示光标移动设置 WriteCommandLCD(0x0C,1); // 显示开及光标设置 } //按指定位置显示一个字符 void DisplayOneChar(unsigned char X, unsigned char Y, unsigned char DData) { Y &= 0x1; X &= 0xF; //限制X不克不及年夜于15,Y不克不及年夜于1 if (Y) X |= 0x40; //当要显示第二行时地址码+0x40; X |= 0x80; // 算出指令码 WriteCommandLCD(X, 0); //这里不检测忙信号,发送地址码 WriteDataLCD(DData); } //按指定位置显示一串字符 void DisplayListChar(unsigned char X, unsigned char Y, unsigned char *DData) { unsigned char ListLength; ListLength = 0; Y &= 0x1; X &= 0xF; //限制X不克不及年夜于15,Y不克不及年夜于1 while (DData[ListLength]>=0x20) //若达到字串尾则退出 { if (X <= 0xF) //X坐标应小于0xF { DisplayOneChar(X, Y, DData[ListLength]); //显示单个字符 ListLength++; X++; } } } //5ms延时 void Delay5Ms(void) { unsigned int TempCyc = 5552; while(TempCyc); } //400ms延时 void Delay400Ms(void) { unsigned char TempCycA = 5; unsigned int TempCycB; while(TempCycA) { TempCycB=7269; while(TempCycB); } } /******************************从机*********************************/ #include #define Uint16 unsigned int #define Uint32 unsigned long #define uchar unsigned char sbit nodesel0=P3^2; sbit nodesel1=P3^3; sbit reving=P1^6; sbit SCL2=P1^1; //SCL2界说为P1口的第3位脚,连接ADC0831SCL脚 sbit SDA2=P1^2; //SDA2界说为P1口的第4位脚,连接ADC0831SDA脚 sbit CS2=P1^0; //CS2界说为P1口的第4位脚,连接ADC0831CS脚 bit nodeok; bit crcok; bit procok; uchar node; uchar mtx[11],mrx[10]; uchar send101[9]={0x00,0x04,0x06,0x00,0x00,0x00,0x00,0x00,0x00}; uchar timercnt; uchar byterev; //接收字符 uchar revptr; Uint16 delaycnt; void InitUART(void); void SendOneByte(unsigned); void mdproc(uchar); unsigned char ad0831read(void); //界说该函数为读取ADC0831的数据 Uint16 Crc16(Uint16 *puchMsg, Uint16 usDataLen); void send_error_report(); //CRC校验出错,发送毛病陈述 /* CRC 高位字节值表 */ const Uint16 code auchCRCHi[] = { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 } ; /* CRC低位字节值表*/ const Uint16 code auchCRCLo[] = { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 } ; void main(void) { unsigned char temp8; Uint16 temp16; int i; reving=0; crcok=0; nodeok=0; timercnt=0x00; for(i=0;i<10;i++){mrx[i]=0x00;} for(i=0;i<11;i++){mtx[i]=0x00;} node=!nodesel1; node<<=1; node|=!nodesel0; send101[0]=node; //从节点地址 InitUART(); while(1) { if(crcok==1) { reving=1; send101[3]=ad0831read(); //获得采样数据 for(i=0;i<9;i++) { mtx[i]=send101[i]; } temp16=Crc16(send101,9); mtx[9]=(temp16>>8)&0x00ff; mtx[10]=temp16&0x00ff; delaycnt=0x3fff; while(delaycnt){} rw=1; delaycnt=0x3ff; while(delaycnt){} for(i=0;i<11;i++) { temp8=mtx[i]; SendOneByte(temp8); } crcok=0; delaycnt=0x3ff; while(delaycnt){} reving=0; } } } Uint16 Crc16(uchar *puchMsg, Uint16 usDataLen) { Uint16 uchCRCHi = 0xFF ; /* 高CRC字节初始化 */ Uint16 uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */ Uint16 temp16; Uint32 uIndex ; /* CRC循环中的索引 */ while (usDataLen) /* 传输消息缓冲区 */ { temp16=*puchMsg++; uIndex = uchCRCHi ^ temp16 ; /* 计算CRC */ uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ; uchCRCLo = auchCRCLo[uIndex] ; } return (uchCRCHi << 8 | uchCRCLo) ; } void Timer0Interrupt(void) interrupt 1 { TH0 = 0x3C; TL0 = 0x0B0; if(++timercnt==50) { timercnt=0; } } void UARTInterrupt(void) interrupt 4 { if(RI) { RI = 0; byterev=SBUF; mdproc(byterev); } else TI = 0; } void mdproc(uchar b) //MODBUS 04 功能处理过程 ,先检验节点地址,正确则检验CRC ,正确则回复主机要求数据,不正确发送毛病陈述 if(nodeok==0) { if(b==node) { nodeok=1; datalen=7; mrx[0]=b; revptr=1; } } else { mrx[revptr++]=b; if(revptr==8) { nodeok=0; temp16_1=Crc16(mrx,6); temp16_2=mrx[6]; temp16_2<<=8; temp16_2|=mrx[7]; if(temp16_1==temp16_2) { crcok=1; } else send_error_report(); } } } //发送毛病陈述 void send_error_report() { reving=1; send101[1]=0x54; //修改功能码 for(i=0;i<9;i++){mtx[i]=send101[i];} temp16=Crc16(send101,9); mtx[9]=(temp16>>8)&0x00ff; mtx[10]=temp16&0x00ff; delaycnt=0x3fff; while(delaycnt){} rw=1; delaycnt=0x3ff; while(delaycnt){} for(i=0;i<11;i++) { temp8=mtx[i]; SendOneByte(temp8); } crcok=0; delaycnt=0x3ff; while(delaycnt){} rw=0; reving=0; } void SendOneByte(unsigned char c) { SBUF = c; while(!TI); TI = 0; } void InitUART(void) { TMOD = 0x20; SCON = 0x50; TH1 = 0xFE; TL1 = TH1; PCON = 0x00; EA = 1; ES = 1; TR1 = 1; TMOD |= 0x01; TH0 = 0x3C; TL0 = 0xB0; // timer sets at 20ms ET0 = 1; TR0 = 1; } void delay(void) //空5个指令 { _nop_();_nop_();_nop_();_nop_();_nop_(); } unsigned char ad0831read(void) { unsigned char i=0,tmp=0; SDA2=1; CS2=0; _nop_(); _nop_(); SCL2=0; _nop_(); _nop_(); SCL2=1; _nop_(); _nop_(); SCL2=0; _nop_(); _nop_(); SCL2=1; _nop_(); _nop_(); SCL2=0; _nop_(); _nop_(); for(i=0;i<8;i++){ tmp<<=1; if(SDA2) tmp++; SCL2=1; _nop_(); _nop_(); SCL2=0; _nop_(); _nop_(); } CS2=1; return tmp; } 因篇幅问题不能全部显示,请点此查看更多更全内容