【摘 要】 本设计研究如何通过MODBUS通讯协议,设计利用单片机,结合
NRF24L01无线收发芯片,实现了温度实时采集与主机(PC机)显示。用STCC52单片机与DS18B20对温度采集显示于数码管,在通过无线模块发送到另一块单片机系统里通过1602液晶显示,成后,基于MODBUS通讯协议通过RS-485的物理层实现串口通信,发送实时数据给主机。经过一系列的处理后,温度得以实时在两个单片机系统中显示并准确显示在主机串口调试软件中。经检测,作品实现了基于MODBUS协议的通讯。
【关键词】 MODBUS NRF24L01 STCC52 PC机
[Abstract] This design to study how through the MODBUS communication protocol
designed to use microcontroller, to combine NRF24L01 wireless transceiver chip temperature real-time acquisition with the host (PC) display. The STCC52 SCM and DS18B20 temperature acquisition and display digital control, the 1602 LCD, after the completion of a microcontroller system via RS-485 physical layer communication protocol based on MODBUS serial communication through a wireless module to send to send real-time data to the host. After a series of treatments, the temperature can be shown in the two single-chip system in real time and accurately displayed on the host serial port debugging software. After testing, the works based on MODBUS protocol communication.
[Key words] The MODBUS nRF24L01 STCC52 host computer
目 录
第1章 绪论 ............................................................ 1
1.1 Modbus 协议简介 ............................................... 1 1.2 Modbus 协议的应用 ............................................. 1 1.3、本论文的主要内容 ............................................. 1 第2章 设计总思路和法案选择 ........................................... 2
2.1、设计总体思路基本原理和框图 ................................... 2 2.2 MODBUS方案选择 ................................................. 2 2.3通讯接口选择 .................................................... 3 2.4无线模块的选择 .................................................. 4 2.5 本章小结 ...................................................... 4 第3章 通讯系统实现的理论原理 .......................................... 5
3.1 MODBUS-RTU通讯协议内容简介 .................................... 5 3.2 RS-485接口简介 ................................................. 7 3.3 NRF24L01无线模块内容简介 ....................................... 8 3.4 本章小结 ....................................................... 9 第4章 通讯系统实现的硬件设计 ......................................... 10
4.1硬件系统结构 ................................................... 10 4.2 单片机最小系统模块 ............................................ 10 4.3温度采集、按键、液晶和数码管显示模块 ....................... 10 4.4无线收发单元模块 ............................................... 11 4.5 RS-232转RS-485通讯接口模块 ................................... 11 4.6 本章小结 ...................................................... 12 第5章 通讯系统实现的软件设计 ......................................... 13
5.1 基于MODBUS协议处理报文的软件设计: ........................... 13 5.2 A机流程图和说明 ............................................... 16 5.3 B机流程图和说明 ............................................... 17 5.4 本章小结 ..................................................... 18 第6章 通讯系统的实现 ................................................. 19
6.1 实物整体外观 .................................................. 19 6.2串口主机(PC机)显示 .......................................... 20 6.3 结果评价 ...................................................... 21 6.4 不足和展望 .................................................... 22 参考文献 .......................................................... 23 致谢 .................................................................. 24 附录一: 系统总原理图和PCB图 ......................................... 25 附录二: 单片机A机程序 ............................................... 27 附录三: 单片机B机程序 ............................................... 40
第1章 绪论
1.1 Modbus 协议简介
Modbus是由Modicon(现为施耐德电气公司的一个品牌)在1979年发明的,是全球第一个真正用于工业现场的总线协议。为更好地普及和推动Modbus在基于以太网上的分布式应用,目前施耐德公司已将Modbus协议的所有权移交给IDA(Interface for Distributed Automation,分布式自动化接口)组织,并成立了Modbus-IDA组织,为Modbus今后的发展奠定了基础。在中国,Modbus已经成为国家标准
GB/T19582-2008。据不完全统计:截止到2007年,Modbus的节点安装数量已经超过了1000万个。
Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。它已经成为一通用工业标准。有了它,不同厂商生产的控制设备可以连成工业网络,进行集中监控。此协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如何回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。 1.2 Modbus 协议的应用
工业控制已从单机控制走向集中监控、集散控制,如今已进入网络时代,工业控制器连网也为网络管理提供了方便。Modbus 就是工业控制器的网络协议中的一种。
Modbus 协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了控制器请求访问其它设备的过程,如何回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。
当在Modbus 网络上通信时,此协议决定了每个控制器须要知道它们的设备地址,识别按地址发来的消息,决定要产生何种行动。如果需要回应,控制器将生成反馈信息并用Modbus 协议发出。在其它网络上,包含了Modbus 协议的消息转换为在此网络上使用的帧或包结构。这种转换也扩展了根据具体的网络解决节地址、路由路径及错误 测的方法。 1.3、本论文的主要内容
考虑到工业上常常要对一些移动性强的设备进行一些监控,本次设计了温度信号实时采集显示。在短距离利用无线模块与一些移动性强的设备进行数据交换克服有线通讯的弊端,再基于Modbus通讯协议将采集到的数据显示于上位机上实现远程通讯。
本论文的主要内容总由五章组成。第一章介绍Modus通讯协议与它的应用;第二章介绍设计的总思路和基本原理,对Mdodbus、通讯接口、无线模块的方案进行介绍和选择;第三章介绍modbus协议、RS485通讯接口、NRF24L01通讯模块的内容;第四章具体介绍硬件设计,最小系统、按键液晶数码管显示、电源电路、RS232—485模块、NRF24L01模块的硬件设计等;第五章介绍Modbus协议的软件设计、A机软件设计、B机软件设计等;第六章介绍整个作品的实现,对设计的整体外观、串口显示、对结果进行评价;说明了不足之处和设计的展望等。
1
第2章 设计总思路和法案选择
2.1、设计总体思路基本原理和框图
本次设计了基于RS232-485为接口,双绞线为介质的MODBUS通讯协议来实现远距离、高稳定、高速率、抗干扰能力强的通讯系统,得以在远离现场进行监测。考虑到现场有时候设备移动性比较强时,比如物料输送时要监测移动的设备的温度时,有线方式通讯是受限的,所以设计中还结合了无线模块来避免这种弊端。
根据设计思路本次设计为A、B两个系统模块和PC机通讯模块;A机和B机可以通过无线模块双向传输信号;在Modbus通讯中B机作为从机,PC机作为主机,通过
RS232-485接口实现通讯。功能如图2-1所示:
液晶显示器 数码管显示 键盘 MCU—A机 温度、时钟 无线收发模块A 电磁波 MCU—B机 无线收发模块B 上位机显示 温度、时钟 Modbus协议 图2-1 总体框图
RS232转485物理层 ⑴A机可以显示时钟和采集到的温度信息,可以向B机发送命令,使B机当前显示的是温度或时钟。
⑵B机可以按A机的命令显示时钟(与甲时钟需同步)或者温度信息,当B机接受了A机的命令时指示灯闪烁一次。
⑶B机的温度、时钟通过Modbus协议与主机通讯实现将温度值、时钟的时分信号读取显示于上位机串口调试软件上。
系统中主要有MCU、液晶显示、数码管显示、按键、温度采集、无线收发、RS232转485、上位机等单元。其中MCU为各单元的控制中心,无线收发模块是实现A机和B机通信的终端;主机、数码管和液晶显示部分用来观察系统工作状态及无线通信的成功与否。
2.2 MODBUS方案选择
在 Modbus 协议中,依数据在传输过程中的具体形式不同,分为两种模式:ASCII 模式和 RTU模式, 采用哪种模式由用户自己根据需要进行选择。在改变数据传输模式时,同一 Modbus 网络中的所有设备的数据传输模式必须一致。
2
方案一、ASCII(American Standard Code for Information Interchange),即美国信息交换标准代码。
在这种模式中,每个 8 比特的字节被转换为两个 ASCII 字符进行传送。这种模式的主要优点是传送中每相邻的两字节之间允许的最大时间间隔较长,可达1秒。
代码系统:十六进制,ASCII字符0…9,A…F,消息中的每个ASCII字符都是一个十六进制字符组成每个字节的位:
表2-1 ASCII模式
起始位 START 1字节 1 CHAR 地址代码 ADDRESS 2字节 2 CHARS 功能代码 FUNCTION 2字节 2 CHARS 数据位 DATA LRC校验码 LRC CHECK 2字节 2 CHARS 结束位 END 2字节 2 CHARS n字节 n CHARS 方案二、RTU(Remote Terminal Unit),即远程终端单元。
当控制器设为在Modbus网络上以RTU(远程终端单元)模式通信,在消息中的每个8Bit字节包含两个4Bit的十六进制字符。
代码系统:8位二进制,十六进制数0...9,A...F,消息中的每个8位域都是一个两个十六进制字符组成每个字节的位:
表2-2 RTU模式
起始位 START T1-T2-T3-T4 地址代码 ADDRESS 8 BITS 功能代码 FUNCTION 8 BITS 数据位 DATA CRC校验码 CRC CHECK 结束位 END n * 8 BITS 16 BITS T1-T2-T3-T4 通过比较可以看到,ASCII协议和RTU协议相比拥有开始和结束标记,因此在进行程序处理时能更加方便,而且由于传输的都是可见的ASCII字符,所以进行调试时就更加的直观,另外它的LRC校验也比较容易。但是因为它传输的都是可见的ASCII字符,RTU传输的数据每一个字节ASCII都要用两个字节来传输,比如RTU传输一个十六进制数0xF9,ASCII就需要传输’F’’9’的ASCII码0x39和0x46两个字节,这样它的传输的效率就比较低。综合各因素本次设计采用方案二即RTU模式。 2.3通讯接口选择
方案一、RS-232接口。由于RS-232-C接口标准出现较早,难免有不足之处,主要有以下四点:⑴ 接口的信号电平值较高,易损坏接口电路的芯片,又因为与TTL 电平不兼容故需使用电平转换电路方能与TTL电路连接。⑵ 传输速率较低,在异步传输时,波特率为20Kbps。⑶ 接口使用一根信号线和一根信号返回线而构成共地的传输形式, 这种共地传输容易产生共模干扰,所以抗噪声干扰性
弱。 ⑷ 传输距离有限,最大传输距离标准值为50英尺,实际上也只能 用在50米左右。
方案二、RS-485接口。针对RS-232-C的不足,于是就不断出现了一些新的接口标准,RS-485就是其中之一,它具有以下特点: ⑴ RS-485的电气特性:逻辑“1”以两线间的电压差为+(2—6) V表示;逻辑“0”以两线间的电压差为-(2—6)V表示。接口信号电平比RS-232-C降低了,就不易损坏接口电路的芯片, 且该电平与TTL电平兼容,可方便与TTL 电路连接。⑵ RS-485的数据最高传输速率为10Mbps ⑶ RS-485接口是采用平衡驱动器和差分接收器的组合,抗共模干能力增强,即抗噪声干扰性好。⑷ RS-485接口的最大传输距离标准值为4000英尺,实际
3
上可达 3000米,另外RS-232-C接口在总线上只允许连接1个收发器, 即单站能力。而RS-485接口在总线上是允许连接多达128个收发器(RS485芯片的驱动能力有限,因而在实际应用中其实只能驱动40多个收发器)。即具有多站能力,这样用户可以利用单一的RS-485接口方便地建立起设备网络。因RS-485接口具有良好的抗噪声干扰性,长的传输距离和多站能力等上述优点所以本次设计采用RS-485接口。 2.4无线模块的选择
方案一:采用nRF24L01无线收发芯片组成智能家庭安全系统的自适应无线传感和控制网络,网络拓扑图如图所示。nRF24L01是一款新型单片射频收发器件。工作于2.4 GHz~2。5 GHz ISM频段。内置频率合成器、功率放大器、晶体振荡器、调制器等功能模块,并融合了增强型ShockBurst技术,其中输出功率和通信频道通过程序进行配置。nRF24L01功耗低,在以一6 dBm的功率发射时,工作电流也只有9 mA;接收时,工作电流只有12.3 mA,多种低功率工模式(掉电模式和空闲模式)使节能设计更方便。而且nRF24L01价格在20元左右,性价比高。
方案二:CC1100 是原Chipcon 公司推出的一种低成本、真正单片的超高频无线收发器, 为低功耗无线应用而设计。整个应用电路的无线频率主要设定在 315MHz、433MHz、868MHz 和 915MHz 四个 ISM (工业、科学和医学) 频段上,也可以容易地设置为300MHz~348MHz、400MHz~ 4MHz 和800MHz~ 928MHz 的其它频率上。芯片低电压(217V~ 316V ) 供电并且功耗较低(接收数据时为1516mA、214kbo s、433MHz)、灵敏度高(112kbos 下为110dBm ) , 可编程控制的数据传输率最高可达500kbo s。CC1100 适用于电子消费产品、住宅、建筑的自动控制、无线警报和安全系统等诸多无线应用领域。
方案三:采用315m超外差无线收发模块。模块优点:成本低廉,频率稳定,接收灵敏度高。模块缺点:静态时会输出短暂针状干扰杂波,用于遥控没有问题,但用微处理器数传时要采取软件滤波;功耗较大,不适宜小容量电池供电应用。
综合各方面因素的考虑,我们选择方案一。 2.5 本章小结
本章介绍了本课题设计总体思路基本原理和框图及其要实现的功能,同时对框架的各个主要模块提出了明确的设计方案,为下文的硬件、软件设计奠定基础。
4
第3章 通讯系统实现的理论原理
3.1 MODBUS-RTU通讯协议内容简介
3.1.1物理层
⑴通讯协议类型:Modbus-RTU ⑵传输方式:RS485半双工
⑶通讯地址:理论上可能的设备地址(1~247) ⑷通讯波特率:300BPS~38400BPS ⑸通讯介质:双绞线
⑹通讯距离:大于 1000 米 3.1.2链路层
⑴传输方式:主从半双工方式。
首先,主机的信号寻址到一台唯一的终端设备软起动器(从机);然后,在相反的方向上终端设备 (软起动器)发出的应答信号传输给主机。协议只允许在主机和终端设备(软起动器)之间,而不允许的终端设备之间的数据交换。这就不会使它们在初始化时占据通讯线路,而仅限于相应到达终端设备本机的查询信号。 ⑵数据帧格式:一个数据帧格式包括:1位起始位,8位数据位,偶校验位, 1位停止位。
⑶数据包格式:
表3-1 数据包格式
地址 功能码 数据 校验码 8-Bits 8-Bits N × 8-Bits 16-Bits ⑷数据格式说明: 本协议详细定义了校验码、数据序列等,这些都是特定数据交换的必要内容。当数据帧到达终端设备时,它通过一个简单的“端口”进入寻址到的设备,该设备去掉数据帧的“信 封”(数据头),读取数据。如果没有错误,就执行数据所请求的任务。然后,它将自己生成的数据加入到取得的“信封”中,把数据帧返回给发送者。返回的响应数据中包含了以下内容:终端从机地址(Address)、被执行了的命令(Function)、执行命令生成的被请求数据(Data)和一个校验码(Check)。终端从机能对来自主机的错误通讯进行识别,并做出不同的错误响应。 3.1.3地址(Address)域
地址域在帧的开始部分,由 8 位组成,理论上可能的设备地址(1~247),这些位标明了用户指定的终端设备的地址,该设备将接收来自与之相连的主机数据。每个终端设备的地址必须是唯一的,仅仅被寻址到的终端会响应, 响应包含了该地址的查询应答。当终端发送回一个响应,响应中的从机地址数据便告诉了主机哪台终端正与之进行通讯。在本次设计中我们采用RS232转485这个接口实现通讯,而因为RS485芯片的驱动能力有限,在实际应用中其实只能驱动40多个从机而已。 3.1.4功能(Function)域
功能域代码告诉了被寻址到的终端执行何种功能操作。下表列出了所有本协议比较常用到的功能码、它们的意义及它们的初始功能。
5
表3-2 功能域 01 02 03 04 05 06 07 读取线圈状态 读取输入状态 读取保持寄存器 读取输入寄存器 强置单线圈 预置单寄存器 读取异常状态 08 回送诊断校验 09 编程(只用于484) 10 控询(只用于484) 11 读取事件计数 12 读取通信事件记录 13 编程(184/384 484 584) 14 探询(184/384 484 584) 取得一组逻辑线圈的当前状态(ON/OFF) 取得一组开关输入的当前状态(ON/OFF) 在一个或多个保持寄存器中取得当前的二进制值 在一个或多个输入寄存器中取得当前的二进制值 强置一个逻辑线圈的通断状态 把具体二进值装入一个保持寄存器 取得8个内部线圈的通断状态,这8个线圈的地址由控制器决定 把诊断校验报文送从机,以对通信处理进行评鉴 使主机模拟编程器作用,修改PC从机逻辑 可使主机与一台正在执行长程序任务从机通信,探询该从机是否已完成其操作任务,仅在含有功能码9的报文发送后,本功能码才发送 可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或其他应答产生通信错误时 可是主机检索每台从机的Modbus事务处理通信事件记录。如果某项事务处理完成,记录会给出有关错误 可使主机模拟编程器功能修改PC从机逻辑 可使主机与正在执行任务的从机通信,定期控询该从机是否已完成其程序操作,仅在含有功能13的报文发送后,本功能码才得发送 强置一串连续逻辑线圈的通断 15 强置多线圈 3.1.5数据域 数据域包含了终端执行特定功能所需要的数据或者终端响应查询时采集到的数据。这些数据的内容
可能是数值、参考地址或者极限值。例如:功能域码告诉终端读取一个寄存器,数据域则需要指明从哪
个寄存器开始读及读取多少个数据,内嵌的地址和数据依照类型和从机之间的不同能力而有所不同。 3.1.6错误校验域
该域允许主机和终端检查传输过程中的错误。有时,由于电噪声和其它干扰,一组数据在从一个设
备传输到另一个设备时在线路上可能会发生一些改变,出错校验能够保证主机或者终端不去响应那些传
输过程中发生了改变的数据,这就提高了系统的安全性和效率,出错校验使用了 16 位循环冗余的方法。 3.1.7错误检测
循环冗余校验(CRC)域占用两个字节,包含了一个 16 位的二进制值。CRC 值由传送设备计算出来, 然后附加到数据帧上,接收设备在接收数据时重新计算 CRC值,然后与接收到的 CRC域中的值进行比较,如果这两个值不相等,就发生了错误。CRC 运算时,首先将一个 16 位的寄存器预置为全 1,然后连续把数据帧中的 8 位
6
字节与该寄存器的 当前值进行运算,仅仅每个字节的 8 个数据位参与生成 CRC,起始位和终止位以及可能使用的奇偶位都不影响 CRC运算。在生成 CRC值时,每个 8位字节与寄存器中的内容进行异或,然后将结果向低位移位,高位则用“0”补充,最低位(LSB)移出并检测,如果是 1,该寄存器就与一个预设的固定值进行一次异或运算,如果最低位为 0,不作任何处理。
上述处理重复进行,直到执行完了 8 次移位操作,当最后一位(第 8 位)移完以后,下一个 8位字节与寄存器的当前值进行异或运算,同样进行上述的另一个 8 次移位异或操作,当数据帧中的所有字节都作了处理,生成的最终值就是 CRC 值。 生成一个 CRC 值的流程为:
⑴ 预置一个16 位寄存器为 0FFFFH(全 1),称之为 CRC 寄存器。
⑵ 把数据帧中的第一个 8 位字节与CRC 寄存器中的低字节进行异或运算,结果存回 CRC 寄存器。
⑶ 将 CRC 寄存器向右移一位,最高位填以 0,最低位移出并检测。 ⑷ 如果最低位为 0:重复第三步(下一次移位);
如果最低位为 1:将CRC寄存器与一个预设的固定值(0A001H)进行异或运算。 ⑸ 重复第3步和第 4 步直到 8 次移位,这样处理完了一个完整的八位。 ⑹ 重复第2步到第 5 步来处理下一个八位,直到所有的字节处理结束。 ⑺ 最终 CRC寄存器的值就是 CRC 的值。 3.2 RS-485接口简介
RS-485 标准理想地用于多分支(multi-drop )应用和远程接口中。它允许在一条数据线
上连接32 个发送器和接收器,非常适合于多分支应用。由于允许使用4000 英尺长的电缆,
RS-485 收发器可以使用一个宽(-7V~+12V)共模方式范围来调整零电位偏差。因为RS-485
是一个差分接口,所以传输数据时完全可以抑制来自发送线的干扰。
图3-1 RS485引脚说明
7
3.3 NRF24L01无线模块内容简介
无线收发单元采用2.4GHz单片高速2Mbps无线收发芯片nRF24L01,nRF2401是挪威NoRDIC SEMICONDUCTOR公司的产品,它把射频收发电路集成在一块芯片上,可以用MCU模拟SPI通信协议实现数字传输。通过编程,nRF2401芯片的射频工作频率和输出信号的功率等参量可以非常方便地调节。
无线收发芯片nRF2401具有以下功能:
⑴ 125个工作频道,工作频道之间的转换时间小于200us ⑵ 无线通信数据具有地址检测和循环冗余检查。 ⑶ 信号的调制方式为频移键控(FSK) ⑷ 最大数据传输速率1Mbps ⑸ 最大输出功率0dB
⑹ 独特的Shock Burst TM射频信号发射模式。该模式降低平均发射功率,降低对微控制器数据传输速率的要求。
⑺ 接收灵敏度—93dBm。
⑻ 独特的DuoCeiverT模式。该模式支持两个不同工作频道的信号同时接收。 ⑼ 工作电压范围:1.9V一3.6V;具有正常、旁路和掉电3种供电模式。
SPI 接口:SPI 接口是标准的 SPI 接口,其最大的数据传输率为 10Mbps。大多数寄存器是可读的。
SPI 指令设置:SPI 接口可能用到的指令在下面有所说明。CSN为低后 SPI 接口等待执行指令。每一条指令的执行都必须通过一次CSN 由高到低的变化。
表3-3 SPI指令格式
指令名称 指令操作 格式 R_REGISTER 000A 读配置寄存器。AAAAA 指出读操作的寄存器地址 AAAA W_REGISTER 001A 写配置寄存器。AAAAA 指出写操作的寄存器地址 AAAA 只有在掉电模式和待机模式下可操作。 R_RX_PAYLO0110 读RX 有效数据:1-32 字节。读操作全部从字节0 开始。当AD 0001 读RX有效数据完成后,FIFO 寄存器中有效数据被清除。应用于接收模式下。 W_RX_PAYLO1010 写TX 有效数据:1-32 字节。写操作从字节0 开始。应用于AD 0000 发射模式下 FLUSH_TX 1110 清除TX FIFO 寄存器,应用于发射模式下。 0001 FLUSH_RX 1110 清除RX FIFO 寄存器,应用于接收模式下在传输应答信号过0010 程中不应执行此指令。也就是说,若传输应答信号过程中执行此指令的话将使得应答信号不能被完整的传输。 REUSE_TX_P1110 重新使用上一包有效数据。当CE 为高过程中,数据包被不L 0011 断的重新发射。在发射数据包过程中必须禁止数据包重利用功能。 NOP 1111 空操作。可以用来读状态寄存器。 1111 R_REGISTER 和W_REGISTER 寄存器可能操作单字节或多字节寄存器。当访问多字节寄存器时首先 要读/写的是最低字节的高位。在所有多字节寄存器被写完之前可以
8
结束写 SPI 操作,在这种情况下没有写完的高字节保持原有内容不变。例如:RX_ADDR_P0寄存器的最低字节可以通过写一个字节给寄存器RX_ADDR_P0 来改变。在CSN 状态由高变低后可以通过MISO 来读取状态寄存器的内容。 中断:
NRF24L01 的中断引脚(IRQ)为低电平触发,当状态寄存器中TX_DS、RX_DR 或MAX_RT 为高时
触发中断。当 MCU 给中断源写‘1’时,中断引脚被禁止。可屏蔽中断可以被IRQ中断屏蔽。通过设置可屏蔽中断位为高,则中断响应被禁止。默认状态下所有的中断源是被禁止的。 3.4 本章小结
本章介绍了modbus通讯协议、RS485接口、NRF24L01通讯模块的具体内容,为硬件、软件设计铺平道路。
9
第4章 通讯系统实现的硬件设计
4.1硬件系统结构
该硬件系统主要包括电源模块、单片机最小系统模块、按键、温度采集、数码管和液晶显示模块、RS232转RS485接口模块、NRF24L01无线通讯模块,结构方框图如图所示,
4.2 单片机最小系统模块
主控MCU如图4-1所示,A、B都采用STCC52为主控MCU,晶振频率为12.0M,C3(10uf),R1(10K)组成上电复位电路。
图4-1 最小系统
4.3温度采集、按键、液晶和数码管显示模块
⑴液晶显示模块如图4-2所示,采用字符型液晶1602,其具有操作简单显示字符多特点,并接R0(104)进行对比度调节,P0为数据接口,P2.0,P2.1,P3.2为控制线。
10
图4-2液晶 图4-3数码管 图4-4按键
⑵数码管显示单元 如图4-3所示,数码管采用四位一体的共阴数码管,由于单片机的I/O输出电流较小,故采用三极管进行放大驱动,当位选为高时三极管饱和导通,对应位的数码管选中。
⑶按键单元
如图4-4,按键用来对系统工作控制,例如调时间,发命令等作用。
⑷温度采集单元
温度采集单元采用达拉斯公司生产的数字温度传感器DS18B20进行数据采集,其采用单总线数据传输,具有硬件简单的优势,而且数据采集周期短,精度高,量程大,可以达到室温计的要求,其数据总线与P2.6相接。 4.4无线收发单元模块
如图4-5所示,在设计NRF24L01无线通讯模块中要注意的是该芯片低工作电压在(1.9~3.6V),而普通5v电压会降低芯片寿命甚至将其烧坏,所以需要对其电平转换,在本设计中采用LM1117-3.3V三端稳压管,该芯片输出电压稳定在NRF24L01的工作电压范围内、输出电流800mA满足无线通讯芯片供应电源。
图4-5 NRF24L01
4.5 RS-232转RS-485通讯接口模块
如图4-6所示:RS232-485转换器主要包括了电源、232电平转换、485电路三部分。本电路的232电平转换电路采用MAX232集成电路,485电路采用了MAX485集成电路。为了使用方便,电源部分设计成无源方式,整个电路的供电直接从PC机的RS232
11
接口中的DTR(4脚)和RTS(7脚)窃取。PC串口每根线可以提供大约9mA的电流,因此两根线提供的电流足够供给这个电路使用了。经实验,本电路只使用其中一条线也能够正常工作。使用本电路需注意PC程序必须使串口的DTR和RTS输出高电平,经过D3稳压后得到VCC,经过实际测试,VCC电压大约在4.7V左右。因此,电路中要说D3起的作用是稳压还不如说是限压功能。
MAX485是通过两个引脚RE(2脚)和DE(3脚)来控制数据的输入和输出。当RE为低电平时,MAX485数据输入有效;当DE为高电平时,MAX485数据输出有效。在半双工使用中,通常可以将这两个脚直接相连,然后由PC或者单片机输出的高低电平就可以让MAX485在接收和发送状态之间转换了。由于本电路DTR和RTS都用于了电路供电,因此使用TX线和MAX232的另外一个通道及Q1来控制MAX485的状态切换。平时MAX232的9脚输出高电平,经Q1倒相后,使MAX485的RE和DE为低电平而处于数据接收状态。当PC机发送数据时,MAX232的9脚输出低电平,经Q1倒相后,使MAX485的RE和DE为高电平而处于数据发送状态。
图4-6 RS232-485
4.6 本章小结
本章主要包括电源模块、单片机最小系统模块、按键、温度采集、数码管和液晶显示模块、RS232转RS485接口模块、NRF24L01无线通讯模块的硬件设计,介绍了设计的基本原理和一些注意事项。
12
第5章 通讯系统实现的软件设计
本设计要实现无线通讯系统对温度的实时采集与显示功能,除硬件外,还需要软件来控制。本章主要介绍报文处理的软件实现,并简要介绍整体A机时钟和B机温度信号无线交换的软件流程图。
5.1 基于MODBUS协议处理报文的软件设计:
5.1.1主机发送数据包和从机响应数据包具体格式:
B机作为从机在本次设计中需要读出从机的温度、从机的小时和分钟信号所以采用读模拟寄存器(即03)这个功能。
⑴计算机发送的数据包:[设备地址] [命令号03] [起始寄存器地址低8位] [高8位] [读取的寄存器数低8位] [高8位] [CRC校验的低8位] [CRC校验的高8位] 即:[01][03][01][00][01][00][CRC低][CRC高] 注释:读从机温度
[01][03][02][00][01][00][CRC低][CRC高] 注释:读从机时钟的小时 [01][03][03][00][01][00][CRC低][CRC高] 注释:读从机时钟的分钟 具体意义如下:
1.设备地址:本次设计中设备地址为0x01。 2.命令号:读模拟量的命令号固定为03。
3.起始地址低8位、高8位:表示想读取的模拟量的逻辑地址,本次设计中的温度逻辑地址为0x01;小时的逻辑地址为0x02;分钟的逻辑地址为0x03。 4.寄存器数低8位、高8位:表示从起始地址开始读多少个模拟量(即逻辑地址长度)。本次设计中每个逻辑地址只需读出一个模拟量。(在返回的信息中一个模拟量需要返回两个字节)。
5.最后16位为CRC校验。 ⑵从机响应回主机的数据包:[设备地址] [命令号03] [返回的字节个数][数据1][数据2]...[数据n][CRC校验的低8位] [CRC校验的高8位]
即 :[01][03][02][00][AC][CRC低][CRC高] 注释:温度值回应给主机(17.2℃)
[01][03][02][00][0B][CRC低][CRC高] 注释:小时值回应给主机(11小时) [01][03][02][00][24][CRC低][CRC高] 注释:分钟值回应给主机(36分钟) 具体意义如下:
1.设备地址和命令号和上面的相同。
2.返回的字节个数:表示数据的字节个数,也就是数据1,2...n中的n的值。设计中返回了1个模拟量的数据,因为一个模拟量需要2个字节所以共2个字节。
3.数据1...n:其中[数据1][数据2]分别是第1个模拟量的高8位和低8位,[数据3][数据4]是第2个模拟量的高8位和低8位,以此类推。设计中只返回了[数据1][数据2],本次设计中若第一个返回的值是[AC]转化为十进制为172即17.2°C ;第二个值[0B]表示11小时;第三个值[24]表示36分钟。 4.CRC校验同上。
5.1.2主机发送报文和从机响应报文软件具体实现 ⑴提高通讯稳定性的程序设计:
Modbus通信协议设计中为了预防干扰,提高系统稳定性,防止系统程序跑飞而造成不可预测的错误(虽然概率比较低,一旦出现将会出现很大的问题),所以有必要经常重新置485为接受状态并将接受地址偏移器(即接收到的字节个数)清零。在此次程序设计中设置了一个1ms定时中断,在中断程序中定义一个1ms标志位,在定时
13
处理子函数中判断是否通讯超时(本次设定为5s),如果超时,即使已经有接受到数据也置之不理,进行重置485、清零地址偏移器、清零偶校验;除此之外每次发送数据结束后我们也需要进行这样处理。程序流程图如5-1所示:
定时处理开始 1ms Timer1中断开始 1ms标志=1? 重装初值 Y 清零1ms标志 1ms标志位设为1 通讯超时否? N 结束 485置为接收 Y N 接受地址偏移寄存器清零 偶校验清零 结束
图5-1提高通讯稳定性的程序设计
⑵发送和接收缓冲区数据处理:
在Modbus协议里,报文的都是以包的形式来发送与接收的,设计中用两个数组来定义数据包的发送和接收缓冲区。发送接收缓冲区再与单片机的串口锁存器sbuf进行交换来实现数据包的发送与接收,在程序中还同时使用偶校验来对传输数据进行检测;在此设置串口中断服务程序,当接收或者发送完8个数据后进入服务程序内处理以上功能。具体程序流程图如5-2下所示:
14
串行口中断开始 TI=1? Y 清零TI中断标志位 N RI=1? Y 清零RI中断标志位 发送位置<发送字节个数? Y 发送缓冲区数据发给串口锁存器(加上校验位) 设置通讯超时值 N 串口锁存器的值发给接受缓冲区 测试校验是否出错 485置为接收 接受地址偏移寄存器清零 偶校验清零 结束
图5-2发送和接收缓冲区数据处理
⑶主机发送数据包和从机响应主机数据包的具体软件实现:
本次设计中我们定义从机(B机)地址为Ox01;采用查询扫描方式,当从机接受数据包第一个有效数据为0X01时说明确实是该机,如果偶校验也正确的话则继续处理接下来的数据;为保证通讯数据的可靠和可信性我们还需要对接收缓冲区做CRC校验。接下来判定第二个数据(即功能码),该设计中除了[03]还扩展了[01][05][06]等功能码以提高代码可移植性,对其扫描检测,如果是[03]则进入读取保存寄存器子函数;在读取保存寄存器子函数里,分别读出接收缓冲区的第三、第四个数据对应得逻辑地址和第五、第六个数据对应的逻辑地址长度。通过逻辑地址我们可以知道要处理的数据,通过逻辑地址长度我们可以知道需要返回的字节数。在这里我们对从机(B机)发来的温度,时钟数据进行处理;处理完后再把这些数据和设备地址、命令号、返回字节数回应给发送缓冲区,再对缓冲区数据进行CRC校验计算。 流程图如5-3所示:
15
检查Uart0数据开始 读取保存寄存器开始 N 设备地址、偶校正确否? Y 对接收缓冲区做CRC校验 读取接收的逻辑地址 读取地址长度 CRC校验正确? N Y 功能码为03否? Y 对应逻辑地址处理数据,需要将温度、时钟的时、分等数据对应给发送缓冲区 将设备地址、命令号、返回字节数对应给发送缓冲区 N:结束或看是否为其它功能 对缓冲区数据进行CRC计算 接受地址偏移寄存器清零 偶校验清零 开始发送缓冲区第一个数据 结束
图5-3 数据包处理
5.2 A机流程图和说明
A机的功能是显示时钟,能通过按键控制B机显示温度或把A机的时钟信号无线传输给B机;所以在程序中需要初始化液晶、无线模块、定时器、还要设置按键扫描和无线接受中断(这里用单片机外部中断0来实现)。程序流程图如5-4所示:
16
主程序开始 Timer0中断开始 无线接收中断开始 液晶初始化 时钟更新 判断中断标志 无线模块初始化 Sendflag为1? Y 读取接收到的数据 定时器0初始化 N 更新液晶显示 发送温度信息 按键扫描 中断返回 中断返回 结束 结束 结束
图5-4 A机流程图
5.3 B机流程图和说明
B机的功能是依据A机命令使它显示温度还是时钟,在主机(PC机)命令下再把温度和时钟信号通过Modbus协议基于485接口响应读取给主机。所以程序中需要初始化无线模块、串口、定时器等;Modbus协议都在主程序中得以体现。另外数码管显示部分和无线部分处理分别使用Timer0中断和无线接收中断。程序流程图如5-5所示:
17
主程序开始 Timer0中断开始 无线接收中断开始 读取一次温度信息 数码管扫描 判断中断标志 无线模块初始化 时钟更新 读取接收到的数据 Timer0、1mTimer1、串口初始化(设置波特率等);无线模块设置为接收 中断返回 置位rcv 结束 定时处理 判断接收到的命令 显示时钟并与A机同步 检查uart0口数据 结束 N 显示温度发回温度信息 rcv为1? Y LED闪烁 结束
图5-5 B机流程图
5.4 本章小结
在这一章中我们介绍了基于MODBUS协议处理报文的软件设计,介绍了报文格式、软件具体实现的方法和注意事项,并设计的介绍了流程图,设计中对系统稳定性方面下了功夫。除此之外还介绍了A机流程图和说明、B机流程图和说明。
18
第6章 通讯系统的实现
本章节主要说明本课题设计的作品,对整体作品的实物及各部分功能模块实物作了全面的介绍,并对本课题设计的作品结果作了全面的分析与评价。
6.1 实物整体外观
实物主要分为A机、B机、Rs232-485三个模块。 A、B机显示如图6-1所示: 万年历 温度显示
温度显示 小时、分钟显示
图6-1 A、B机显示
19
Rs232-485 B机 数码管显示 1620液晶显示 A机 MAX232芯片 18B20温度感器 MAX485芯片 按键模块 52单片机 24l01无线模块 24l01无线模块 A机5v电池盒 串口线 图6-2 实物外观
B机5v电池盒
A机主要有:1602液晶显示模块、52单片机、按键模块、NRF24L01无线模块、5v电源。
B机主要有:数码管显示模块、52单片机芯片、18B20温度传感器、NRF24L01无线模块、5v电池。
RS232-485模块有:MAX232芯片、MAX485芯片、USB转串口线。 6.2串口主机(PC机)显示
本次设计没有进行上位机编程,而是通过串口调试工具来看看数据有没有有效地进行传输串口调试结果如图6-2所示:
20
表示21.3℃ 表示8小时 表示48分钟 读取时钟分钟值。
图6-3 PC机显示
我们发送:[01][03][01][00][01][00][25][C0] 注释:读从机温度
[01][03][02][00][01][00][DD][AA] 注释:读从机时钟的小时 [01][03][03][00][01][00][8C][CA] 注释:读从机时钟的分钟
串口接收:[01][03][02][00][AC][25][C0] 注释:温度值回应给主机(21.3℃)
[01][03][02][00][0B][DD][AA] 注释:小时值回应给主机(8小时)
[01][03][02][00][24][8C][CA] 注释:分钟值回应给主机(48分钟) 03对应读度模拟寄存器功能;[D5]、[08]、[30]就是分别发送三次命令后读出的模拟量;转化为十进制后为213代表21.3℃;8代表8小时;48代表48分钟。 6.3 结果评价
Modbus是在工业现场经常用到的总线协议,已成为为通用工业标准,所以基于Modbus协议去做这个设计很有现实意义。本次设计功能基本实现:A机通过无线模块控制B机实现温度或者时钟显示实现短距离无线通讯,B机还做为从机通过Modbus与PC机通讯,将从A机无线获得的时钟信号和温度信号传输给上位机显示实现长距离有线通讯。
本文的主要成果总结如下:
⑴分析和研究了相关通讯协议的发展趋势,对Modus协议应用的现状与发展动态有了一定的了解,并对单片机技术和数据通信技术结合进行了较深入的研究。 ⑵较详细地分析了Modus协议的基本内容和工作原理,在实现Modbus通信协议的基础上为了预防干扰,提高系统稳定性,防止系统程序跑飞而造成不可预测的错误等,在软件方面做出了一些优化系统设计。在此设计中也对NRF24L01无线模块有了研究。
⑶设计了一个基于Modus通讯协议的单片机通信系统。
21
6.4 不足和展望
虽然此次设计经过调试运行已经基本达到了预期的效果,运行结果表明该装置在技术上有一定的可靠性,但是由于作者本身的研究经验和其它客观条件的,设计中难免存在许多有待进一步开发改进的地方,另外尚有许多理论问题和工程应用问题需要进一步的探索和研究,就研究和发展状况来看今后仍需在以下几个方面努力: ⑴温度采集方面由于DS18B20芯片的局限性精确度不能达到一定的高度。
⑵因为没有编程上位机软件,只是通过串口调试软件来观察数据的,如此的话,通过协议读取的数据信号不能实时性的显示于上位机上,而且操作非常麻烦,用户界面很不友好,还有待继续完善。
⑶随着信息高速发达,已经有更加优化的通讯如:嵌入式工业以太网网络通信、zigbee无线网络通信、wifi无线网络通信等等;关于通讯我们需要面向未来继续努力。
22
参考文献
[1]石海东单片机数据通信技术从入门到精通,西安电子科技大学出版社,2002 [2] 刘永洪.一种通用的RS232/RS485转换器[M].单片机与嵌入式系统应用,2003. [3]郭天祥.51单片机C语言教程.电子工业出版社
[4]李刚、林凌、姜苇.单片机系统设计与应用技巧.北京航空航天大学出版社
[5]常晓明,潘卫华,王建东.CRC校验及其软件实现,电子技术应用.1995(6).45-51 [6]张莲,蒋亮,孙玉林.Protel DXP电路设计入门与应用.机械工业出版社
[7]陈鑫、孙苓生.用DSP实现MODBUS协议与GP触摸屏通讯[J].工业控制计算机.2004.17(3):15.16
[8]王纲常、周有庆.MODBUS协议在保护测试装置内部通讯中的应用[J].仪表技术与传感.2008(6):61.
[9]姜风武、王杭.基于MODBUS协议实现单片机与变频器通信[J].自动化技术与应用.2005.24(4):78.79
[10] GB/T 19582.2-2008基于Modbus协议的工业自动化网络规范,第1部分:Modbus应用协议 [S].北京:中国标准出版社,2008.
[11] GB/T 19582.2-2008基于Modbus协议的工业自动化网络规范,第2部分:Modbus协议在串 口链路上的实现指南[S].北京:中国标准出版社,2008.
[12]黄海容.在Windows95下实现PC机与单片机ATC51的串行通信.微型机与应用.1999. [13]“A Power Line Communication Tutorial Challenges and Technologies”.Phil Sutterlin and Walter Downey Echelon Corporation
[15]Modbus通信编程.http://member.netease.com/Maoyang/
[16]ModbusPlus Planning and Installation.http://www.modicon.com/teehpubs/toc6.html
23
致谢
本设计从开题到方案的设计、具体电路试验及论文写作的实施始终是在老师们的精心指导和周密安排下进行的。在此我特别感谢感谢我的指导老师翁国云,一个月来帮我定题、分析、指导;当我遇到问题时孜孜不倦教导我、帮助我,在完成论文过程中给我细心的指导和详细的批改,使我得以完成这个设计。同时感谢付智河老师、 涂二生老师、 李建华老师、 罗锦彬老师、赖义汉等等老师和同学在学习和生活中给予我的帮助,使我提高了分析问题、处理问题、解决问题的能力,真的让我受益良多。
此外,感谢所有我的关心和帮助的老师及朋友,他们在我成长的道路上给力我信心和勇气,使我跨过一道道难关。最后,感谢在百忙之中给我审稿的诸位老师。
24
附录一: 系统总原理图和PCB图
25
PCB图如下:
26
附录二: 单片机A机程序
#include //****************************************NRF24L01端口定义*************************************** sbit MISO =P3^5; sbit MOSI =P3^3; sbit SCK =P3^6; sbit CE =P3^7; =P3^4; sbit CSN //************************************按键*************************************************** sbit s1=P1^0; sbit s2=P1^1; sbit s3=P1^2; sbit s4=P1^3; sbit s5=P1^4; sbit s6=P1^5; //************************************液晶控制********************************************* sbit lcdrs=P2^0; sbit lcdrw=P2^1; sbit lcden=P2^2; //*********************************************NRF24L01************************************* #define TX_ADR_WIDTH 5 #define RX_ADR_WIDTH 5 #define TX_PLOAD_WIDTH 4 #define RX_PLOAD_WIDTH 4 // 5 uints TX address width // 5 uints RX address width // 4 uints TX payload // 4 uints RX payload 发送地址的宽度 接收地址的宽度 发送数据宽度 uchar const A_ADR[TX_ADR_WIDTH]= {0x34,0x43,0x10,0x10,0x01}; //A机地址 uchar const B_ADR[RX_ADR_WIDTH]= {0x34,0x43,0x10,0x10,0x02}; //B机地址 //***************************************NRF24L01寄存器指令******************************************************* #define READ_REG 0x00 // 读寄存器指令 #define WRITE_REG 0x20 // 写寄存器指令 #define RD_RX_PLOAD 0x61 // 读取接收数据指令 #define WR_TX_PLOAD 0xA0 // 写待发数据指令 #define FLUSH_TX 0xE1 // 清除发送 FIFO指令 #define FLUSH_RX 0xE2 // 清除接收 FIFO指令 #define REUSE_TX_PL 0xE3 // 定义重复装载数据指令 #define NOP 0xFF // 保留 //*************************************SPI(nRF24L01)寄存器地址**************************************************** #define CONFIG 0x00 // 配置收发状态,CRC校验模式以及收发状态响应方式 #define EN_AA 0x01 // 自动应答功能设置 27 #define EN_RXADDR 0x02 // 可用信道设置 #define SETUP_AW 0x03 // 收发地址宽度设置 #define SETUP_RETR 0x04 // 自动重发功能设置 #define RF_CH 0x05 // 工作频率设置 #define RF_SETUP 0x06 // 发射速率、功耗功能设置 #define STATUS 0x07 // 状态寄存器 #define OBSERVE_TX 0x08 // 发送监测功能 #define CD 0x09 // 地址检测 #define RX_ADDR_P0 0x0A // 频道0接收数据地址 #define RX_ADDR_P1 0x0B // 频道1接收数据地址 #define RX_ADDR_P2 0x0C // 频道2接收数据地址 #define RX_ADDR_P3 0x0D // 频道3接收数据地址 #define RX_ADDR_P4 0x0E // 频道4接收数据地址 #define RX_ADDR_P5 0x0F // 频道5接收数据地址 #define TX_ADDR 0x10 // 发送地址寄存器 #define RX_PW_P0 0x11 // 接收频道0接收数据长度 #define RX_PW_P1 0x12 // 接收频道0接收数据长度 #define RX_PW_P2 0x13 // 接收频道0接收数据长度 #define RX_PW_P3 0x14 // 接收频道0接收数据长度 #define RX_PW_P4 0x15 // 接收频道0接收数据长度 #define RX_PW_P5 0x16 // 接收频道0接收数据长度 #define FIFO_STATUS 0x17 // FIFO栈入栈出状态寄存器设置 //****************************************************************************************** uchar bdata sta; //状态标志 =sta^6; =sta^5; =sta^4; sbit RX_DR sbit TX_DS sbit MAX_RT uchar code table[]=\"01234567.-C:\"; //写液晶对应ASCLL码 /****************************************************************************************** /*延时函数 /******************************************************************************************/ /**************延时x毫秒******************/ void delayms(uint x) { uchar y; for(;x>0;x--) for(y=111;y>0;y--); } void write_com(uchar com) { lcdrs=0; P0=com; delayms(1); lcden=1; delayms(1); //向液晶写指令函数 28 } lcden=0; void write_data(uchar date) { } void init() { //液晶初始化函数 lcdrs=1; P0=date; delayms(1); lcden=1; delayms(1); lcden=0; //向液晶写数据函数 lcdrw=0; } /**************************************************************************************************** /*函数:uint SPI_RW(uint uchar) /*功能:NRF24L01的SPI读写时序 /****************************************************************************************************/ uchar SPI_RW(uchar date) { uint bit_ctr; for(bit_ctr=0;bit_ctr<8;bit_ctr++) // output 8-bit { } // return read date MOSI = (date & 0x80); // output 'date', MSB to MOSI date = (date << 1); // shift next bit into MSB.. SCK = 1; // Set SCK high..上升沿给出数据 date |= MISO; // capture current MISO bit // ..then set SCK low again 下降沿读回数据 lcden=0; write_com(0x38); write_com(0x0c); write_com(0x06); write_com(0x01); SCK = 0; return(date); } /**************************************************************************************************** /*函数:uchar SPI_Read(uchar reg) /*功能:NRF24L01的SPI读时序 reg为寄存器地址 /****************************************************************************************************/ uchar SPI_Read(uchar reg) { 29 } uchar reg_val; CSN = 0; // CSN low, initialize SPI communication... SPI_RW(reg); // Select register to read from.. reg_val = SPI_RW(0); // ..then read registervalue CSN = 1; // CSN high, terminate SPI communication return(reg_val); // return register value /****************************************************************************************************/ /*功能:NRF24L01写寄存器函数 /****************************************************************************************************/ void SPI_RW_Reg(uchar reg, uchar value) { } /****************************************************************************************************/ /*函数:uint SPI_Read_Buf(uchar reg, uchar *pBuf, uchar uchars) /*功能: 用于读数据,reg:为寄存器地址,pBuf:为待读出数据地址,uchars:读出数据的个数 /****************************************************************************************************/ uchar SPI_Read_Buf(uchar reg, uchar *pBuf, uchar uchars) { } /********************************************************************************************************* /*函数:uint SPI_Write_Buf(uchar reg, uchar *pBuf, uchar uchars) /*功能: 用于写数据:为寄存器地址,pBuf:为待写入数据地址,uchars:写入数据的个数 /*********************************************************************************************************/ void SPI_Write_Buf(uchar reg, uchar *pBuf, uchar uchars) { } uint uchar_ctr; CSN = 0; //SPI使能 SPI_RW(reg); for(uchar_ctr=0; uchar_ctr // Set CSN low, init SPI tranaction // Select register to write to and read status uchar CSN = 0; // CSN low, init SPI transaction SPI_RW(reg); // select register SPI_RW(value); // ..and write value to it.. CSN = 1; // CSN high again for(uchar_ctr=0;uchar_ctr CSN = 1; return(status); // return nRF24L01 status uchar CSN = 1; //关闭SPI 30 //**************************************************************************************** /*NRF24L01初始化 //***************************************************************************************/ void init_NRF24L01(void) { } /*********************************************************************************************************** /*函数:void nRF24L01_TxPacket(unsigned char * tx_buf) /*功能:发送 tx_buf中数据 /**********************************************************************************************************/ void nRF24L01_TxPacket(unsigned char * tx_buf) { } /****************************************************************************************************/ /*函数:void SetRX_Mode(void) /*功能:数据接收配置 /****************************************************************************************************/ void SetRX_Mode(void) { CE=0; SPI_RW_Reg(WRITE_REG + CONFIG, 0x3f); // IRQ收发完成中断响应,16位CRC ,主接收 CE=0; //StandBy I模式 CE=0; // chip enable CSN=1; // Spi disable SCK=0; // SPI_Write_Buf(WRITE_REG + TX_ADDR, A_ADR, TX_ADR_WIDTH); // 写本地地址 SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, B_ADR, RX_ADR_WIDTH); // 写接收端地址 SPI_RW_Reg(WRITE_REG + EN_AA, 0x01); // 频道0自动 ACK应答允许 SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01); // 允许接收地址只有频道0,如果需要多频道可以参考Page21 SPI_RW_Reg(WRITE_REG + RF_CH, 0); // 设置信道工作为2.4GHZ,收发必须一致 SPI_RW_Reg(WRITE_REG + RX_PW_P0, RX_PLOAD_WIDTH); //设置接收数据长度,本次设置为32字节 SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x07); //设置发射速率为1MHZ,发射功率为最大值0dB SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, A_ADR, TX_ADR_WIDTH); // 装载接收端地址 此时是接受应答信号 SPI_Write_Buf(WR_TX_PLOAD, tx_buf, TX_PLOAD_WIDTH); SPI_RW_Reg(WRITE_REG + CONFIG, 0x3e); CE=1; //置高CE,激发数据发送 // 装载数据 // IRQ收发完成中断响应,16位CRC,主发送 delayms(1); // 大于10US SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, B_ADR, RX_ADR_WIDTH); // 写接收端地址 } //*******************中断与定时器0初始化*********************************************************************** void Init_timer0() CE = 1; delayms(1);//大于130us 31 { TMOD=0x01; TH0=(65536-50000)>>8; TL0=65536-50000; EA=1; ET0=1; TR0 = 1; IT0=1; //触发方式为负跳变触发 } uchar hour=23,min=59,sec=50,month=10,day=28; uint year=2011; //************************************主函数************************************************************ uchar TxBuf[4]={0}; uchar RxBuf[4]={0}; uchar rcv = 0; uchar bai,shi,ge,run; uchar weizhi=0,sendflag=0; /***************************************中断处理**********************************************************/ void nRF24L01_int(void) interrupt 0 //中断说明有数据接收到或其他中断源 { EX0=0; sta=SPI_Read(STATUS); // 读取状态寄存其来判断数据接收状况 if(RX_DR) //接收到数据 { CE = 0; // 进入待机模式读数据 // EX0 = 1; SPI_Read_Buf(RD_RX_PLOAD,RxBuf,TX_PLOAD_WIDTH);// read receive payload from RX_FIFO buffer SPI_RW_Reg(WRITE_REG+STATUS,sta);//清除中断标志 bai = RxBuf[1]; shi = RxBuf[2]; ge = RxBuf[3]; write_com(0x80+0x0a); write_data(table[bai]); write_data(table[shi]); write_data(table[10]); write_data(table[ge]); write_data(0xdf); write_data(table[12]); } CE = 1; EX0=1; } uchar num=0; void Timer0() interrupt 1 { 32 TH0=(65536-50000)>>8; TL0=65536-50000; num++; if(num>=20) { num=0; sec++; if(sec>=60) { sec=0; min++; if(min>=60) { min=0; hour++; if(hour>=24) { hour=0; day++; switch(month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12:if(day>=32) { day=1; month++; } break; case 4: case 6: case 9: case 11: if(day==31) { day=1; month++; } break; case 2:if(run) { 33 if(day==30) { day=1; month++; } } else { if(day==29) { day=1; month++; } } break; } if(month>=13) { month=1; year++; write_com(0x80+0x43); write_data(table[year/1000]); write_data(table[year%1000/100]); write_data(table[year%100/10]); write_data(table[year%10]); if((year%4)==0) run=1; else run=0; } write_com(0x80+0x48); write_data(table[month/10]); write_data(table[month%10]); write_com(0x80+0x4b); write_data(table[day/10]); write_data(table[day%10]); } write_com(0x80+0x00); write_data(table[hour/10]); write_data(table[hour%10]); } write_com(0x80+0x03); write_data(table[min/10]); write_data(table[min%10]); } write_com(0x80+0x06); 34 write_data(table[sec/10]); write_data(table[sec%10]); if(sendflag) { EX0=0; TxBuf[0] = 0x02; TxBuf[1] = 0xff; TxBuf[2] = 0x00; TxBuf[3] = 0x00; // Transmit Tx buffer data 发数据 nRF24L01_TxPacket(TxBuf); SetRX_Mode(); //设置为接收模式 EX0=1; } } } void main() { init(); Init_timer0(); init_NRF24L01(); SetRX_Mode(); //设置为接收模式 系统大多数时候都处于接收模式 write_com(0x80+0x00); write_data(table[hour/10]); write_data(table[hour%10]); write_data(table[13]); write_data(table[min/10]); write_data(table[min%10]); write_data(table[13]); write_data(table[sec/10]); write_data(table[sec%10]); write_com(0x80+0x43); write_data(table[year/1000]); write_data(table[year%1000/100]); write_data(table[year%100/10]); write_data(table[year%10]); write_data(table[11]); write_data(table[month/10]); write_data(table[month%10]); write_data(table[11]); write_data(table[day/10]); write_data(table[day%10]); if((year%4)==0) run=1; else run=0; while(1) 35 { if(s1==0) { delayms(10); if(s1==0) { sendflag = 0; TxBuf[0] = 0x00; TxBuf[1] = 0x00; TxBuf[2] = 0x00; TxBuf[3] = 0x00; EX0=0; // Transmit Tx buffer data 发数据 nRF24L01_TxPacket(TxBuf); SetRX_Mode(); //设置为接收模式 EX0=1; while(s1==0); } } if(s2==0) { delayms(10); if(s2==0) { sendflag = 0; TxBuf[0] = 0x01; TxBuf[1] = hour; TxBuf[2] = min; TxBuf[3] = sec; EX0=0; nRF24L01_TxPacket(TxBuf); SetRX_Mode(); EX0=1; while(s2==0); } } if(s3==0) //位置选择 // Transmit Tx buffer data 发数据 //设置为接收模式 { delayms(10); if(s3==0) { weizhi++; weizhi=weizhi%7; switch(weizhi) { case 0: write_com(0x0c); 36 TR0 = 1; break; case 1: TR0 = 0; write_com(0x80+0x01); write_com(0x0f); break; case 2: write_com(0x80+0x04); write_com(0x0f); break; case 3: write_com(0x80+0x07); write_com(0x0f); break; case 4: TR0 = 0; write_com(0x80+0x46); write_com(0x0f); break; case 5: write_com(0x80+0x49); write_com(0x0f); break; case 6: write_com(0x80+0x4c); write_com(0x0f); break; } while(s3==0); } } if(s4==0) //加 { delayms(10); if(s4==0) { switch(weizhi) { case 1:if(hour==23)hour=0; else hour++; write_com(0x80+0x00); write_data(table[hour/10]); write_data(table[hour%10]); write_com(0x80+0x01); break; case 2:if(min==59)min=0; else min++; write_com(0x80+0x03); write_data(table[min/10]); write_data(table[min%10]); 37 write_com(0x80+0x04); break; case 3:if(sec==59)sec=0; else sec++; write_com(0x80+0x06); write_data(table[sec/10]); write_data(table[sec%10]); write_com(0x80+0x07); break; case 4: year++; if((year%4)==0) run=1; else run=0; write_com(0x80+0x43); write_data(table[year/1000]); write_data(table[year%1000/100]); write_data(table[year%100/10]); write_data(table[year%10]); write_com(0x80+0x46); break; case 5:if(month==12)month=1; else month++; write_com(0x80+0x48); write_data(table[month/10]); write_data(table[month%10]); write_com(0x80+0x049); break; case 6:if(day==31)day=1; else day++; write_com(0x80+0x4b); write_data(table[day/10]); write_data(table[day%10]); write_com(0x80+0x4c); break; } } while(s4==0); } if(s5==0) //减 { delayms(10); if(s5==0) { switch(weizhi) { 38 case 1:if(hour==0)hour=23; else hour--; write_com(0x80+0x00); write_data(table[hour/10]); write_data(table[hour%10]); write_com(0x80+0x01); break; case 2:if(min==0)min=59; else min--; write_com(0x80+0x03); write_data(table[min/10]); write_data(table[min%10]); write_com(0x80+0x04); break; case 3:if(sec==0)sec=59; else sec--; write_com(0x80+0x06); write_data(table[sec/10]); write_data(table[sec%10]); write_com(0x80+0x07); break; case 4: year--; if((year%4)==0) run=1; else run=0; write_com(0x80+0x43); write_data(table[year/1000]); write_data(table[year%1000/100]); write_data(table[year%100/10]); write_data(table[year%10]); write_com(0x80+0x46); break; case 5:if(month==1)month=12; else month--; write_com(0x80+0x48); write_data(table[month/10]); write_data(table[month%10]); write_com(0x80+0x049); break; case 6:if(day==1)day=31; else day--; write_com(0x80+0x4b); write_data(table[day/10]); write_data(table[day%10]); write_com(0x80+0x4c); 39 break; } } while(s5==0); } if(s6==0) { delayms(10); if(s6==0) { sendflag=!sendflag; while(s6==0); } } } } 附录三: 单片机B机程序 main.c文件 #include \"main.h\" /****************************** 微控电子 www.mcuc.cn modbus RTU 的C51程序 单片机S52 通信波特率 9600 8位数据 1位停止位 偶校验 485通位接口 单片机控制板地址 localAddr(变量) 通信可设置数据的地址: 字地址 0 - 255 (只取16位的低8位) 位地址 0 - 255 (只取16位的低8位) *******************************/ uint32 dwTickCount,dwIntTick; //时钟 uint8 idata sendBuf[16],receBuf[16]; //发送接收缓冲区 40 uint8 idata checkoutError; // ==2 偶校验错 uint8 idata receTimeOut; //接收超时 uint8 idata c10ms; //10ms 计时 bit b1ms,bt1ms,b10ms,bt10ms,b100ms,bt100ms; //定时标志位 零或一 uint8 bai,ge,shi; uint16 a; //****************************************NRF24L01端口定义*************************************** sbit MISO =P1^2; sbit MOSI =P3^3; sbit SCK =P1^1; sbit CE =P1^0; sbit CSN =P3^4; //************************************数码管位选********************************************* sbit com1 =P2^0; sbit com2 =P2^1; sbit com3 =P2^2; sbit com4 =P2^3; //*********************************温度传感的数据总线***************************************** sbit DQ =P2^6; //*****************************************LED闪烁************************************************* sbit LED =P2^7; //*********************************************NRF24L01************************************* #define TX_ADR_WIDTH 5 // 5 uints TX address width 发送地址的宽度 #define RX_ADR_WIDTH 5 // 5 uints RX address width 接收地址的宽度 #define TX_PLOAD_WIDTH 4 // 4 uints TX payload 发送数据宽度 #define RX_PLOAD_WIDTH 4 // 4 uints RX payload uint8 const A_ADR[TX_ADR_WIDTH]= {0x34,0x43,0x10,0x10,0x01}; //A机地址 uint8 const B_ADR[RX_ADR_WIDTH]= {0x34,0x43,0x10,0x10,0x02}; //B机地址 //***************************************NRF24L 寄 存 器 指 ******************************************************* #define READ_REG 0x00 // 读寄存器指令 #define WRITE_REG 0x20 // 写寄存器指令 #define RD_RX_PLOAD 0x61 // 读取接收数据指令 #define WR_TX_PLOAD 0xA0 // 写待发数据指令 #define FLUSH_TX 0xE1 // 清除发送 FIFO指令 #define FLUSH_RX 0xE2 // 清除接收 FIFO指令 #define REUSE_TX_PL 0xE3 // 定义重复装载数据指令 #define NOP 0xFF // 保留 //*************************************SPI(nRF24L01寄 存 器 地 *************************************************** #define CONFIG 0x00 // 配置收发状态,CRC校验模式以及收发状态响应方式 #define EN_AA 0x01 // 自动应答功能设置 #define EN_RXADDR 0x02 // 可用信道设置 #define SETUP_AW 0x03 // 收发地址宽度设置 #define SETUP_RETR 0x04 // 自动重发功能设置 41 令 址 #define RF_CH 0x05 // 工作频率设置 #define RF_SETUP 0x06 // 发射速率、功耗功能设置 #define STATUS 0x07 // 状态寄存器 #define OBSERVE_TX 0x08 // 发送监测功能 #define CD 0x09 // 地址检测 #define RX_ADDR_P0 0x0A // 频道0接收数据地址 #define RX_ADDR_P1 0x0B // 频道1接收数据地址 #define RX_ADDR_P2 0x0C // 频道2接收数据地址 #define RX_ADDR_P3 0x0D // 频道3接收数据地址 #define RX_ADDR_P4 0x0E // 频道4接收数据地址 #define RX_ADDR_P5 0x0F // 频道5接收数据地址 #define TX_ADDR 0x10 // 发送地址寄存器 #define RX_PW_P0 0x11 // 接收频道0接收数据长度 #define RX_PW_P1 0x12 // 接收频道0接收数据长度 #define RX_PW_P2 0x13 // 接收频道0接收数据长度 #define RX_PW_P3 0x14 // 接收频道0接收数据长度 #define RX_PW_P4 0x15 // 接收频道0接收数据长度 #define RX_PW_P5 0x16 // 接收频道0接收数据长度 #define FIFO_STATUS 0x17 // FIFO栈入栈出状态寄存器设置 //****************************************************************************************** uint8 bdata sta; //状态标志 =sta^6; =sta^5; sbit RX_DR sbit TX_DS sbit MAX_RT =sta^4; uint8 code table[] ={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; //数码管编码 uint8 code table1[]={0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef};// 带小数点 /****************************************************************************************** /*延时函数 /******************************************************************************************/ /**************延时x毫秒******************/ void delayms(uint16 x) { uint8 y; for(;x>0;x--) for(y=111;y>0;y--); } void delay(uint16 useconds) //x微秒延时 { for(;useconds>0;useconds--); } /*******************************************温度传感器读取***********************************/ uint8 ow_reset(void) //uint8 bai,shi,ge; //复位 { uint8 presence; DQ = 0; // DQ 低电平 42 delay(29); // 480us DQ = 1; // DQ 高电平 delay(3); // 等待 presence = DQ; // presence 信号 delay(25); return(presence); }// 0=presence, 1 = no part uint8 read_byte(void)//从 1-wire 总线上读取一个字节 { uint8 i; uint8 value = 0; for (i=8;i>0;i--) { value>>=1; DQ = 0; DQ = 1; delay(1); if(DQ)value|=0x80; delay(6); } return(value); } void write_byte(char val)//向 1-WIRE 总线上写一个字节 { uint8 i; for (i=8; i>0; i--) // 一次写一字节 { DQ = 0; DQ = val&0x01; delay(5); DQ = 1; val=val/2; } delay(5); } void Read_Temperature()//读取温度 { uint16 a; union{ uint8 c[2]; uint16 x; }temp; ow_reset(); write_byte(0xCC); // 跳过 ROM write_byte(0xBE); // 读 43 temp.c[1]=read_byte(); temp.c[0]=read_byte(); ow_reset(); write_byte(0xCC); write_byte(0x44); // 开始 a=temp.x; a=a*0.625+0.5; bai=a/100; //温度十位 //温度个位 //温度小数点位 shi=(a%100)/10; ge=(a%100)%10; } /**************************************************************************************************** /*函数:uint16 SPI_RW(uint16 uint8) /*功能:NRF24L01的SPI读写时序 /****************************************************************************************************/ uint8 SPI_RW(uint8 date) { uint8 bit_ctr; for(bit_ctr=0;bit_ctr<8;bit_ctr++) // output 8-bit { MOSI = (date & 0x80); // output 'date', MSB to MOSI date = (date << 1); // shift next bit into MSB.. SCK = 1; // Set SCK high..上升沿给出数据 date |= MISO; SCK = 0; // capture current MISO bit // ..then set SCK low again 下降沿读回数据 } return(date); } /**************************************************************************************************** /*函数:uint8 SPI_Read(uint8 reg) /*功能:NRF24L01的SPI读时序 reg为寄存器地址 /****************************************************************************************************/ uint8 SPI_Read(uint8 reg) { } /****************************************************************************************************/ /*功能:NRF24L01写寄存器函数 /****************************************************************************************************/ void SPI_RW_Reg(uint8 reg, uint8 value) uint8 reg_val; // return read date CSN = 0; // CSN low, initialize SPI communication... SPI_RW(reg); // Select register to read from.. reg_val = SPI_RW(0); // ..then read registervalue CSN = 1; // CSN high, terminate SPI communication return(reg_val); // return register value 44 { } CSN = 0; // CSN low, init SPI transaction SPI_RW(reg); // select register SPI_RW(value); // ..and write value to it.. CSN = 1; // CSN high again /****************************************************************************************************/ /*函数:uint16 SPI_Read_Buf(uint8 reg, uint8 *pBuf, uint8 uchars) /*功能: 用于读数据,reg:为寄存器地址,pBuf:为待读出数据地址,uchars:读出数据的个数 /****************************************************************************************************/ uint8 SPI_Read_Buf(uint8 reg, uint8 *pBuf, uint8 uchars) { } /********************************************************************************************************* /*函数:uint16 SPI_Write_Buf(uint8 reg, uint8 *pBuf, uint8 uchars) /*功能: 用于写数据:为寄存器地址,pBuf:为待写入数据地址,uchars:写入数据的个数 /*********************************************************************************************************/ void SPI_Write_Buf(uint8 reg, uint8 *pBuf, uint8 uchars) { } //**************************************************************************************** /*NRF24L01初始化 //***************************************************************************************/ void init_NRF24L01(void) { CE=0; // chip enable CSN=1; // Spi disable SCK=0; // uint8 uchar_ctr; CSN = 0; //SPI使能 SPI_RW(reg); for(uchar_ctr=0; uchar_ctr CSN = 0; status = SPI_RW(reg); // Set CSN low, init SPI tranaction // Select register to write to and read status uchar for(uchar_ctr=0;uchar_ctr CSN = 1; return(status); // return nRF24L01 status uchar 45 SPI_Write_Buf(WRITE_REG + TX_ADDR, B_ADR, TX_ADR_WIDTH); // 写本地地址 SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, A_ADR, RX_ADR_WIDTH); // 写接收端地址 SPI_RW_Reg(WRITE_REG + EN_AA, 0x01); // 频道0自动 ACK应答允许 SPI_RW_Reg(WRITE_REG + EN_RXADDR, 0x01); // 允许接收地址只有频道0,如果需要多频道可以参考 Page21 } /*********************************************************************************************************** /*函数:void nRF24L01_TxPacket(unsigned char * tx_buf) /*功能:发送 tx_buf中数据 /**********************************************************************************************************/ void nRF24L01_TxPacket(unsigned char * tx_buf) { 信号 } /****************************************************************************************************/ /*函数:void SetRX_Mode(void) /*功能:数据接收配置 /****************************************************************************************************/ void SetRX_Mode(void) { CE=0; SPI_RW_Reg(WRITE_REG + CONFIG, 0x3f); // IRQ收发完成中断响应,16位CRC ,主接收 SPI_Write_Buf(WR_TX_PLOAD, tx_buf, TX_PLOAD_WIDTH); SPI_RW_Reg(WRITE_REG + CONFIG, 0x3e); CE=1; //置高CE,激发数据发送 // 装载数据 CE=0; //StandBy I模式 SPI_RW_Reg(WRITE_REG + RF_CH, 0); // 设置信道工作为2.4GHZ,收发必须一致 SPI_RW_Reg(WRITE_REG + RX_PW_P0, RX_PLOAD_WIDTH); //设置接收数据长度,本次设置为32字节 SPI_RW_Reg(WRITE_REG + RF_SETUP, 0x07); //设置发射速率为1MHZ,发射功率为最大值0dB SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, B_ADR, TX_ADR_WIDTH); // 装载接收端地址 此时是接受应答 // IRQ收发完成中断响应,16位CRC,主发送 delayms(1); // 大于10US SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, A_ADR, RX_ADR_WIDTH); // 写接收端地址 } //*******************中断与定时器0初始化 *********************************************************************** uint8 num=0,num1=0,test=0,mode=1; uint8 hour=8,min=59,sec=40; uint8 rv=0; CE = 1; delayms(1);//大于130us 46 //************************************主函数************************************************************ uint8 TxBuf[4]={0}; uint8 RxBuf[4]={0}; void Timer0() interrupt 1 { TH0=(65536-2000)>>8; TL0=65536-2000; if(mode==1) //显示时钟 { switch(num) { case 0: com4 = 0; P0 = table[hour/10]; com1 = 1; break; case 1: com1 = 0; if(sec%2) P0 = table1[hour%10]; else P0 = table[hour%10]; com2 = 1; break; case 2: com2 = 0; P0 = table[min/10]; com3 = 1; break; case 3: com3 = 0; P0 = table[min%10]; com4 = 1; break; } } else //显示温度 { switch(num) { case 0: com4 = 0; P0 = 0x00; com1 = 1; break; case 1: com1 = 0; P0 = table[bai]; com2 = 1; break; case 2: com2 = 0; P0 = table1[shi]; com3 = 1; 47 break; case 3: com3 = 0; P0 = table[ge]; com4 = 1; break; } } num++; if(num>=4) { num=0; num1++; test=(test+1)%60; //680ms测一次 if(test==0) { Read_Temperature(); } if(num1>=125) { sec++; num1=0; if(sec>=60) { sec=0; min++; if(min>=60) { min=0; hour++; if(hour>=24) { hour=0; } } } } } } /*************中断处理*************/ void nRF24L01_int(void) interrupt 0 //中断说明有数据接收到或其他中断源 { EX0=0; // 读取状态寄存其来判断数据接收状况 sta=SPI_Read(STATUS); if(RX_DR) //接收到数据 { 48 CE = 0; // 进入待机模式读数据 SPI_Read_Buf(RD_RX_PLOAD,RxBuf,TX_PLOAD_WIDTH);// read receive payload from RX_FIFO buffer SPI_RW_Reg(WRITE_REG+STATUS,sta); //清除中断标志 if(RxBuf[0]==0x01) //显示时钟命令 { mode = 1; hour = RxBuf[1]; min = RxBuf[2]; sec = RxBuf[3]; rv = 1; } else //显示温度命令 { mode = 0; TxBuf[0] = 0x00; TxBuf[1] = bai; TxBuf[2] = shi; TxBuf[3] = ge; nRF24L01_TxPacket(TxBuf); // Transmit Tx buffer data if(RxBuf[1]!=0xff) rv = 1; } } SetRX_Mode();//设置为接收模式 EX0=1; } void commIntProc() interrupt 4 // 串行中断程序 { if(TI) { TI = 0; if(sendPosi < sendCount) { sendPosi++; ACC = sendBuf[sendPosi]; TB8 = P; //加上校验位 SBUF = sendBuf[sendPosi]; } else { b485Send = 0; //发送完后将485置于接收状态 receCount = 0; //清接收地址偏移寄存器 checkoutError = 0; 49 发送温度数据 } } else if(RI) { } RI = 0; receTimeOut = 10; //通讯超时值 receBuf[receCount] = SBUF; ACC = receBuf[receCount]; if(P != RB8) checkoutError = 2; //偶校验出错 receCount++; //接收地址偏移寄存器加1 receCount &= 0x0f; //最多一次只能接收16个字节 } // void CommIntProc() void timer0IntProc() interrupt 3 //定时器0 1ms 中断 { TL1 = TIMER_LOW; TH1 = TIMER_HIGHT; dwIntTick++; bt1ms = 1; c10ms++; if(c10ms >= 10) { c10ms = 0; //10ms计时器清零 bt10ms = 1; } } // void Timer0IntProc() void timeProc(void) { static uint8 c200ms; //定时处理 bWatchDog = ~ bWatchDog; //看门狗取反 b1ms = 0; b10ms = 0; b100ms = 0; ET0 = 0; dwTickCount = dwIntTick; ET0 = 1; if(bt1ms) { bt1ms = 0; b1ms = 1; if(receTimeOut>0) { receTimeOut--; if(receTimeOut==0 && receCount>0) //判断通讯接收是否超时 50 { b485Send = 0; //将485置为接收状态 receCount = 0; //将接收地址偏移寄存器清零 checkoutError = 0; } } } if(bt100ms) { } bt100ms = 0; b100ms = 1; if(bt10ms) //判断中断10ms标志位是否1 { bt10ms = 0; //清中断10ms标志位 c200ms++; //200ms计时器加1 if(c200ms >= 20) //判断是否计时到200ms { c200ms = 0; //清200ms计时器 bRunLED = ~bRunLED; //取反运行指示灯 } } } // void TimerProc(void) void initUart(void) { T2CON = 0x30; RCAP2H = 0xff; RCAP2L = 0xb8; TR2 = 1; //偶校验 SCON = 0xd0; //T2 用于波特率 9600 //初始化串口 b10ms = 1; PCON = 0; ES = 1; }//void initUart(void) void initInt(void) //初始化中断 { TMOD = 0x51; TH0=(65536-2000)>>8; TL0=65536-2000; TH1 = TIMER_HIGHT; TL1 = TIMER_LOW; TR0 = 1; ET0 = 1; TR1 = 1; //定时器1用于计数定时器2用于波特 51 ET1 = 1; IT0 = 1; IT1 = 1; EX0 = 1; PX0 = 1; EX1 = 0; IP = 0x02; initUart(); EA = 1; } // void initInt(void) void initProg(void) //初始化 { initInt(); b485Send = 0; } void main(void) { com1 = 0; com2 = 0; com3 = 0; com4 = 0; Read_Temperature(); init_NRF24L01(); initProg(); SetRX_Mode(); //设置为接收模式 while(1) { timeProc(); checkComm0Modbus(); if(rv==1) { rv=0; LED = 0; delayms(50); LED = 1; } } } modbus.c文件 #include \"main.h\" //字地址 0 - 255 (只取低8位) //位地址 0 - 255 (只取低8位) /* CRC 高位字节值表 */ const uint8 code auchCRCHi[] = { 系统大多数时候都处于接收模式 52 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 uint8 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, 0x, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 53 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, 0x, 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 } ; uint8 testCoil; //用于测试 位地址1 uint16 testRegister; //用于测试 字址址16 uint8 localAddr = 1; uint8 sendCount; //单片机控制板的地址 //发送字节个数 uint8 receCount; //接收到的字节个数 uint8 sendPosi; //发送位置 uint16 crc16(uint8 *puchMsg, uint16 usDataLen) { uint8 uchCRCHi = 0xFF ; /* 高CRC字节初始化 */ uint8 uchCRCLo = 0xFF ; /* 低CRC 字节初始化 */ uint32 uIndex ; /* CRC循环中的索引 */ while (usDataLen--) /* 传输消息缓冲区 */ { } return (uchCRCHi << 8 | uchCRCLo) ; uIndex = uchCRCHi ^ *puchMsg++ ; /* 计算CRC */ uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ; uchCRCLo = auchCRCLo[uIndex] ; }//uint16 crc16(uint8 *puchMsg, uint16 usDataLen) void beginSend(void) //开始发送 { b485Send = 1; sendPosi = 0; if(sendCount > 1) sendCount--; //设为发送 ACC = sendBuf[0]; TB8 = P; SBUF = sendBuf[0]; }//void beginSend(void) void readCoil(void) { uint8 addr; uint8 tempAddr; //读线圈状态 54 uint8 byteCount; uint8 bitCount; uint16 crcData; uint8 position; uint8 i,k; uint16 tempData; uint8 exit = 0; addr = receBuf[3]; tempAddr = addr; bitCount = receBuf[5]; byteCount = bitCount / 8; if(bitCount%8 != 0) byteCount++; for(k=0;k if(tempAddr >= addr+bitCount) { //读完 exit = 1; break; } } if(exit == 1) break; } sendBuf[0] = localAddr; sendBuf[1] = 0x01; sendBuf[2] = byteCount; byteCount += 3; crcData = crc16(sendBuf,byteCount); sendBuf[byteCount] = crcData >> 8; byteCount++; sendBuf[byteCount] = crcData & 0xff; sendCount = byteCount + 1; beginSend(); }//void readCoil(void) void readRegisters(void) //读寄存器 { //字节个数55 uint8 addr; uint8 tempAddr; uint16 crcData; uint8 readCount; uint8 byteCount; uint16 i; uint16 tempData = 0; addr = receBuf[3]; tempAddr = addr; readCount = receBuf[5]; byteCount = readCount * 2; for(i=0;i //起始地址 //所读数据高位 sendBuf[i+4] = tempData & 0xff; //所读数据低位 sendBuf[0] = localAddr; sendBuf[1] = 3; sendBuf[2] = byteCount; byteCount += 3; crcData = crc16(sendBuf,byteCount); sendBuf[byteCount] = crcData >> 8; byteCount++; sendBuf[byteCount] = crcData & 0xff; sendCount = byteCount + 1; beginSend(); }//void readRegisters(void) void forceSingleCoil(void) { uint8 addr; uint8 tempAddr; uint16 tempData; uint8 onOff; uint8 i; addr = receBuf[3]; tempAddr = addr; onOff = receBuf[4]; if(onOff == 0xff) { } else if(onOff == 0x00) { //设为OFF //设为ON tempData = 1; //强制单个线圈 56 } tempData = 0; setCoilVal(tempAddr,tempData); for(i=0;i sendBuf[i] = receBuf[i]; }//void forceSingleCoil(void) void presetMultipleRegisters(void) //设置多个寄存器 { uint8 addr; uint8 tempAddr; uint8 byteCount; uint8 setCount; uint16 crcData; uint16 tempData; uint8 i; addr = receBuf[3]; tempAddr = addr & 0xff; setCount = receBuf[5]; byteCount = receBuf[6]; for(i=0;i sendBuf[0] = localAddr; sendBuf[1] = 16; sendBuf[2] = addr >> 8; sendBuf[3] = addr & 0xff; sendBuf[4] = setCount >> 8; sendBuf[5] = setCount & 0xff; crcData = crc16(sendBuf,6); sendBuf[6] = crcData >> 8; sendBuf[7] = crcData & 0xff; sendCount = 8; beginSend(); }//void presetMultipleRegisters(void) void checkComm0Modbus(void)//检查uart0数据 { uint16 crcData; 57 uint16 tempData; if(receCount > 4) { } switch(receBuf[1]) { case 1://读取线圈状态(读取点 16位以内) case 3://读取保持寄存器(一个或多个) case 5://强制单个线圈 case 6://设置单个寄存器 if(receBuf[0]==localAddr && checkoutError==0) { crcData = crc16(receBuf,6); if(crcData == receBuf[7]+(receBuf[6]<<8)) {//校验正确 if(receBuf[1] == 1) {//读取线圈状态(读取点 16位以内) } else if(receBuf[1] == 3) {//读取保持寄存器(一个或多个) } else if(receBuf[1] == 5) {//强制单个线圈 } else if(receBuf[1] == 6) { //presetSingleRegister(); forceSingleCoil(); readRegisters(); readCoil(); if(receCount >= 8) {//接收完成一组数据 //应该关闭接收中 } } } receCount = 0; checkoutError = 0; break; case 15://设置多个线圈 tempData = receBuf[6]; tempData += 9; //数据个数 58 } } if(receCount >= tempData) { } break; if(receBuf[0]==localAddr && checkoutError==0) { } crcData = crc16(receBuf,tempData-2); if(crcData == (receBuf[tempData-2]<<8)+ receBuf[tempData-1]) { } //forceMultipleCoils(); receCount = 0; checkoutError = 0; case 16://设置多个寄存器 tempData = (receBuf[4]<<8) + receBuf[5]; tempData = tempData * 2; tempData += 9; if(receCount >= tempData) { } break; if(receBuf[0]==localAddr && checkoutError==0) { } crcData = crc16(receBuf,tempData-2); if(crcData == (receBuf[tempData-2]<<8)+ receBuf[tempData-1]) { } presetMultipleRegisters(); //数据个数 receCount = 0; checkoutError = 0; default: break; }//void checkComm0(void) uint16 getCoilVal(uint16 addr,uint16 *tempData) { //保留 } uint16 setCoilVal(uint16 addr,uint16 tempData) //设定线圈状态 返回0表示成功 { } uint16 getRegisterVal(uint16 addr,uint16 *tempData) //取寄存器值 返回0表示成功 //保留 //取线圈状态 返回0表示成功 59 uint16 result = 0; uint16 tempAddr; tempAddr = addr & 0xfff; switch(tempAddr & 0xff) { case 0: testRegister=a; *tempData = testRegister; break; case 1: testRegister=hour; *tempData = testRegister; break; case 2: testRegister=min; *tempData = testRegister; break; case 3: break; case 4: break; case 5: break; case 6: break; case 7: break; case 8: break; case 9: break; case 10: break; case 11: break; case 12: break; case 13: break; case 14: break; 60 { } case 15: break; case 16: testRegister=a; *tempData = testRegister; break; default: break; return result; }//uint16 getRegisterVal(uint16 addr,uint16 &data) uint16 setRegisterVal(uint16 addr,uint16 tempData) //设置寄存器值 返回0表示成功 { //保留 } MAIN.H头文件 #include \"reg52.h\" typedef unsigned char uint8; typedef unsigned int uint16; typedef unsigned long uint32; sbit bRunLED sbit bWatchDog sbit b485Send = = = P3^4; //运行指示灯 P1^7; //看门狗复位 P1^3; //75LBC184 发送接收控制 0xf8 #define TIMER_HIGHT #define TIMER_LOW 0xcd extern uint16 a; extern uint8 bai,ge,shi; extern uint8 extern uint8 extern uint8 extern uint32 idata sendBuf[16],receBuf[16]; idata checkoutError; // ==2 偶校验错 idata receTimeOut; dwTickCount; #include \"modbus.h\" MODBUS.H头文件 extern uint8 extern uint8 extern uint8 sendCount; receCount; sendPosi; void beginSend(void); void checkComm0Modbus(void); void readCoil(void); void readRegisters(void); void forceSingleCoil(void); void presetSingleRegister(void); void presetMultipleRegisters(void); void forceMultipleCoils(void); 61 uint16 getRegisterVal(uint16 addr,uint16 *tempData); uint16 setRegisterVal(uint16 addr,uint16 tempData); uint16 getCoilVal(uint16 addr,uint16 *tempData); uint16 setCoilVal(uint16 addr,uint16 tempData); 62
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- gamedaodao.net 版权所有 湘ICP备2024080961号-6
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务