理论部分:
- Modbus简介
- RTU模式
- Modbus消息格式
- Modbus串口通讯模式
- Modbus地址
- 常用功能码
实践部分:
Case #1 – Arduino作为主机读取传感器modbus数据
Case #2 – 百度天工使用Modbus解析
Case #3 – NodeMCU上传数据至百度天工
Case #4 STM32读取Modbus土壤湿度传感器数据
理论部分
Modbus简介
Modbus是一种串行通讯协议,以成为目前工业领域通讯的业界标准,最传统的Modbus是RS-232,后来流行的RS-485,一定要注意的是Modbus是通讯协议,而RS-232或RS-485是物理层实现。相比RS232,RS-485允许更远的通讯距离,更高速的传输,采用主从的通讯方式,一个主机可以带多个从机。目前不仅仅是一些微型控制器或者PLC带有modbus协议,一些传感器也会支持Modbus接口来将数据发送给主机,Modbus主要用于串口通讯(使用通讯线),不过也支持无线通信和支持TCP/IP网络。
Modbus RTU(远程终端单元,负责对现场信号、工业设备的监测和控制) 非常的简单,包括一个16字节的CRC(循环冗余校验)来确保可靠性。Modbus RTU是一个串口通讯协议(RS-232, RS-485)并且基于主/从或客户端/服务器架构。由于操作很简单,Modbus RTU广泛的运用于工业自动化系统,
Modbus控制器使用了主从通讯方法,这意味着只有一个设备(主机),能够初始化通讯。其他的设备(从机)响应主机的通讯消息。主机能够单独的和某一个从机进行通讯,或者一次性和所有从机进行通讯(广播模式),通常一个消息看起来像这样:
Device Address
Function Code
8-bit Data Bytes
Error Checking
主机的消息包含以上的这四个部分,从机收到以后会返回同样的格式(也是这四个部分),需要特别注意的是消息有一个已知的start point和end point。这使得接收设备知道消息到达,并且知道这个到达的消息是不是给自己的以及消息是不是完全的收到
RTU 模式
在RTU(Remote Terminal Unit)中,每隔消息是在一个连续的数据流中被传输。每个8个字节的消息包括4个字节的16进制字符,需要注意的是总共发送的字节数是11个,8个直接指示消息的字节数, 1 start + 8 data + 1 parity + 1 stop = 11 bits ,其中消息使用了CRC来检查错误。
需要特别注意的是RS-485是基于主从模式的,这意味着只有一台设备(主机)能够发布指令,其他的设备(从机)只能够响应。通常Modbus需要12V的供电进行通讯,每个设备都需要有单独的电力供应,即设备不能够使用通讯线来供电。
RTU通讯格式:
Start(4 character delay time) + Address(8 bits) + Function Code(8 bits) + data(n * 8 bits) + Error Check(16 bits) + end(4 character delay time)
Modbus消息格式
每隔Modbus的消息都有相同的结构包括四个部分。通常的通讯过程是一个Modbus主机发送一个消息,从机接收到消息后执行指令并且回复消息。
Modbus串口通讯模式
Modbus串口通讯有两种模式,一种是RTU一种是ASCII,目前ASCII已经很少使用了。Modbus串口通讯并不是使用纯文本的格式,而是构造成一种能够让接收者通过检测起始消息和结束消息来探测消息。
Modbus地址
Modbus消息的第一部分是接收者的地址,这一部分包括了一个字节的信息。有效的地址范围为0-247,其中0代表了广播模式,所有的slave都会收到该地址为0的消息。
在一个Modbus中,holding registers, input 和 output 的范围为1-10000. 这些数字用于读取或设置值,需要注意的是在modbus中的的这个范围是从0开始的,也就是说如果想读取output(coil)18,需要在请求消息中将值设置为17。
ModBus常用功能码 (功能码为10进制)
Modbus消息的第二部分是函数码,这些函数码定义了一个消息的类型已经从机需要响应的类型。函数码只有一个字节的信息,下面是常用的函数码。通常情况下当一个Modbus从机回复一个请求时会使用请求中相同的函数码进行回复。然而,当一个错误被检测到的时候,一个高字节的函数码会被开启,通过这种方式,主机能够探测到成功的响应和失败的响应
01 READ COIL STATUS 读线圈寄存器
02 READ INPUT STATUS 读状态寄存器
03 READ HOLDING REGISTER 读保持寄存器
04 READ INPUT REGISTER 读输入寄存器
05 WRITE SINGLE COIL 写单线圈寄存器
06 WRITE SINGLE REGISTER 写单保持寄存器
实践部分
Case #1 – Arduino作为主机读取传感器数据
要读取土壤湿度传感器的数据首先要阅读传感器的Data sheet找出所需要的参数:
从Data Sheet中我们可以知道以下的这些参数用于arduino主机的配置:
- 波特率:9600
- 校验位:无
- 数据位:8
- 停止位:1
- 地址(Device Address):1(该设备默认地址是1,可以进行修改)
- 寄存器的地址:0(0X00)
有的传感器可能有多个指标,比如可以同时测定温度和湿度,那么就会有多个寄存器的地址对应不同的测定指标,这里只有一个寄存器的地址对应的寄存器地址是0.
接线图:
接线没有什么好解释的,按照TTL485-V2.0的应交连接上Modbus设备和Arduino即可
代码中要用到这个库:https://github.com/4-20ma/ModbusMaster
所以对应arduino的代码是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
// include the library code: #include <LiquidCrystal.h> #include <ModbusMaster.h> // instantiate ModbusMaster object ModbusMaster node; // variable int data_modbus; float float_data_modbus; void setup() { // init vars data_modbus = 0; // use Serial (port 0); initialize Modbus communication baud rate Serial.begin(9600); // communicate with Modbus slave ID 1 over Serial (port 0) // 设备的地址是1 node.begin(1, Serial); } void loop() { // read modbus data static uint32_t i; uint8_t j, result; uint16_t data[6]; i++; // set word 0 of TX buffer to least-significant word of counter (bits 15..0) node.setTransmitBuffer(0, lowWord(i)); // set word 1 of TX buffer to most-significant word of counter (bits 31..16) node.setTransmitBuffer(1, highWord(i)); // read holding register // 第一个参数是寄存器的地址,第二个参数是往后读多少位 result = node.readHoldingRegisters(0, 1); if (result == node.ku8MBSuccess) { data_modbus = node.getResponseBuffer(0); float_data_modbus = data_modbus/10; Serial.print("DATA:"); Serial.print(float_data_modbus); Serial.println("%"); } } |
实际输出:
Case #2 – 百度天工使用Modbus解析
大致是以下的流程:
- 网关设备管理中创建一个网关,然后创建子设备,有两种模式一种是TCP另一种是RTU,本文中是使用的RTU的方式没有提及TCP的方式,
其实除了直接通过线来连接modbus设备之外还可以直接使用网络来连接。可以看到下面所有的参数在之前都提及过(这里的子设备ID是自定义的,只要是一个不同的名字就可以了)
- 创建好网关和子设备之后需要创建解析项目,数据来源就是创建的网关的名字,然后需要给一个数据的目的地,这是一个MQTT主题自定义即可,
填写后可以使用快速创建TSDB。
- 然后在创建好的解析项目的解析设置中创建地址表用于解析收到的数据,计算公式中原始数据默认是一个无符号整数,以x标识,这里支持比较丰富的运算如位运算、左移、右移或tofloat函数转换成浮点数。
- 创建好地址表之后需要创建轮询的请求设置,下图中的设置会每5秒读取40001和40002两个寄存器的数据。
- 现在天工的云端部分基本上就配置好了,轮训请求创建好了之后可以看到一个上传格式的按钮,然后会看到数据上传的MQTT信息,这里是我们真实的MQTT信息,网关就是用这个主题、密码、topic、网关ID等等。接下来到网关的界面点击配置下发,然后去下载网关程序,创建一个gwconfig.txt将密匙写入,注意这里密匙的username和key以及url都是真实的之后要根据这里来连接mqtt主题。另外还要注意要是网关修改了配置要重新写入一下密匙。
- 现在运行程序并且使用diagslave作为从机,
7.看到这里天工已经可以收到数据了,我们可以使用一个MQTT客户端来查看数据,这里我使用的是MQTT.fx,首先需要进行配置,配置的参数就在刚才复制到gwconfig.txt的密匙里面,
连接上之后订阅解析的主题:
现在已经可以看到解析后的数据了在parseResponse里面,需要注意的是由于使用的是diagslave作为TCP从机,这个从机读的数据是0,因此看到的temperature是0.8(因为配置的公式是(8+x)*0.1)。收到的完整数据是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
{ "bdModbusVer" : 1, "gatewayid" : "9ac746b3-a994-4f17-a2ef-8178c44e1c6f", "trantable" : "b399374a-6753-4363-b4a9-a5fd2d7db058", "modbus" : { "request" : { "functioncode" : 3, "slaveid" : 1, "startAddr" : 0, "length" : 2 }, "response" : "00000000", "parsedResponse" : [ { "desc" : "temperature", "type" : "REAL", "unit" : "", "value" : "0.8", "errno" : 0 }, { "desc" : "humidity", "type" : "REAL", "unit" : "", "value" : "0.0", "errno" : 0 } ], "error" : null }, "timestamp" : "1558546565", "metrics" : { "temperature" : 0.8, "humidity" : 0.0 }, "misc" : null } |
8.之前一键配置了写入时序数据库,现在我们就可以在时序数据库中看到写入的数据了,这里查的humidity,写入的是0
9. 其实网关不只是采集也可以从云端进行控制,可以通过backControlTopic进行控制
1 2 3 4 5 6 7 |
{ "endpoint":"tcp://parxxxxxxxxxxxxx883", "topic":"mb_commxxxx558503156214", "backControlTopic":"mb_backControlTopic_15585xxxx6214", "user":"parsxxxxxxxxxxxxxxxxxx503156214", "password":"Tx0zEjeMxxxxWyH" } |
在gwconfig中有一个backControlTopic是mb_backControlTopic_15585xxxx6214,这个网关会订阅这个主题,订阅这个主题下面的任何一个主题都可以,即mb_backControlTopic_15585xxxx6214/# 是一个通配符,可以通过向这个主题publish一个信息来写数据,返控的格式是:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "request1": { "slaveid": 1, "address": 1, "data": "0101010100000000" }, "request2": { "slaveid": 2, "address": 40001, "data": "00ff1234" } } |
一个消息可以包含多个指令,分别用requestx(x为数字编号,1,2,3…n)来表示。slaveid为需要反控的modbus从站编号, address为需要写的寄存器的起始地址,data为要写往modbus从站的数据,从address开始,依次往后写。如上面的request1,会向地址1-8等8个离散值(coins)写数据,写入的值分别为1,1,1,1,0,0,0,0; 上面的request2,会写2个寄存器,40001和40002,写入的值分别为00ff, 1234。
需要说明的是,如果backControlTopic为”mb_backControlTopic_1493783120844″,那么网关对”mb_backControlTopic_1493783120844/#”都是有权限的。也就是说,在gwconfig.txt文件中,把backControlTopic设置成如下主题,都是可以的:
1 2 3 4 5 |
"backControlTopic": "mb_backControlTopic_1493783120844" "backControlTopic": "mb_backControlTopic_1493783120844/a" "backControlTopic": "mb_backControlTopic_1493783120844/b" ... "backControlTopic": "mb_backControlTopic_1493783120844/anystring" |
现在来测试一下:
这样会把第一个寄存器改成00ff第二个寄存器改成1234,然后可以看到刚才订阅的消息已经变了,温度变成了26.3,湿度变成了466.0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
{ "bdModbusVer" : 1, "gatewayid" : "9ac746b3-a994-4f17-a2ef-8178c44e1c6f", "trantable" : "b399374a-6753-4363-b4a9-a5fd2d7db058", "modbus" : { "request" : { "functioncode" : 3, "slaveid" : 1, "startAddr" : 0, "length" : 2 }, "response" : "00FF1234", "parsedResponse" : [ { "desc" : "temperature", "type" : "REAL", "unit" : "", "value" : "26.3", "errno" : 0 }, { "desc" : "humidity", "type" : "REAL", "unit" : "", "value" : "466.0", "errno" : 0 } ], "error" : null }, "timestamp" : "1558547410", "metrics" : { "temperature" : 26.3, "humidity" : 466.0 }, "misc" : null } |
10. 很多时候一个网关在多个物理网关上运行,需要区分不同网关的数据,这个时候可以卸载misc中,默认的话misc为空,如果在gwconfig.txt中填写了的话,会原封不同的返回在MQTT解析后的主题中:
gwconfig.txt
1 2 3 4 5 6 7 8 9 10 |
{ "endpoint":"tcp://paxxxxxxxiot.gz.baxxxxx83", "topic":"mb_cxxxxxxxx", "backControlTopic":"mb_xxxxxxxxxxxxxx", "user":"parser15585xxxxxxxxx4", "password":"TxxxxxxxxxxxxxH", "misc":{ "gatewayid":"gateway001" } } |
在MQTT的主题中可以看到原封不动的返回了,这样我们就可以区分数据是来自哪里的,存TSDB的时候可以将其作为一个TAG来进行区分
11.同时网关也具备了缓存功能,如果网关断开了连接也会将数据缓存在cache.data里面,有网络的时候在发出去,默认情况这个文件最大存储500MB的数据,可以通过在gwconfig.txt里面配置cachesize字段,注意这个参数是以字节为单位的,如果满了的话会用新的数据覆盖老的数据。另外提供了断网监控的功能,具体参考github上的文档。
Case #3 – NodeMCU上传数据至百度天工
前面已经提到了如何使用Aduino读取modbus的数据,然后介绍了如何使用天工直接解析Modbus的数据,实际上数据可以直接在本地解析然后上传至天工,即不使用天工的物解析服务,直接使用MQTT将JSON上传至指定的服务器,这里使用的是天工的设备型物接入,参考天工的设备型物接入创建一个物影子叫做test用来采集温度和湿度。然后使用NodeMCU采集Modbus的数据上传,需要注意的是这份代码没有包括读取Modbus的代码,需要参考之前的Modbus读取的代码,这里使用的随机数来模拟传感器的数值,另外一点是由于之前的Modbus转接板是使用的TX和RX两个引脚,因此如果要读取多个MODBUS的数据可以使用软串口,或者将下面的设备的按照下面的方式连接起来成多个SLAVE,分开来读取数据:
NodeMCU使用了arduino的编译器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
#include <TaskScheduler.h> #include <PubSubClient.h> #include <ESP8266WiFi.h> #include <ArduinoJson.h> const char* WIFI_SSID = "Luyouqi"; // 修改WIFI名,不能是中文 const char* WIFI_PASSWORD = "a13269326360"; // 修改WIFi密码 const char* IOT_ENDPOINT = "5ac35xxubce.com"; const char* IOT_USERNAME = "xxx/test"; const char* IOT_KEY = "xxx"; const char* IOT_TOPIC = "$baidu/iot/shadow/test/update"; const char* IOT_CLIENT_ID = "test"; WiFiClientSecure client; PubSubClient mqttclient(IOT_ENDPOINT, 1884, &mqtt_callback, client); Task schedule_task(5000, TASK_FOREVER, &ticker_handler); // 每5s执行一次 Scheduler runner; // 初始化JSON DynamicJsonDocument doc(1024); void setup() { Serial.begin(74880); while (!Serial); connect_wifi(); runner.init(); runner.addTask(schedule_task); schedule_task.enable(); } void loop() { mqttclient.loop(); runner.execute(); } // 不断执行 void ticker_handler() { if (!mqttclient.connected()) { Serial.print(F("MQTT state: ")); Serial.println(mqttclient.state()); String clientid {IOT_CLIENT_ID}; if (mqttclient.connect(clientid.c_str(), IOT_USERNAME, IOT_KEY)) { Serial.print(F("MQTT Connected. Client id = ")); Serial.println(clientid); // mqttclient.subscribe(IOT_TOPIC); } } else { // 接收arduino传过来的数据并发送至指定主题 // if (Serial.available() > 0) { // String buffer = Serial.readStringUntil('\r'); // mqttclient.publish(IOT_TOPIC, buffer.c_str()); // } doc["reported"]["temperature"] = random(1, 500) / 10.0; doc["reported"]["humidity"] = random(1, 500) / 10.0; Serial.println(""); Serial.println(doc.as<String>()); mqttclient.publish(IOT_TOPIC, doc.as<String>().c_str()); // 这里用于测试 // 需要构造的JSON // { // "reported": { // "temperature":123, // "humidity":321 // } // } // doc["reported"] = "gps"; // 清空串口数据 while (Serial.read() >= 0) {}; } } // MQTT收到数据的回调 void mqtt_callback (char* topic, byte* payload, unsigned int length) { byte *end = payload + length; for (byte *p = payload; p < end; ++p) { Serial.print(*((const char *)p)); } Serial.println(""); } // 连接WiFi void connect_wifi() { WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.print("\nWiFi connected to "); Serial.println(WIFI_SSID); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } |
需要注意几个地方:
- 如果出现了MQTT -2或者MQTT -4的错误码,那么就需要将ESP8266的库降级而不是采用最新了,目前还不知道为什么最新ESP8266的库不能SSL,有可能是我设置错误的问题,能够正常表一的版本是2.4.2我的设置如图:
- ArudinoJSON库5和6的语法有一些不一样一定要注意,我使用的6+的版本,5的版本可能会不能正常编译。
- 这里只是使用了随机数来模拟温度和湿度的数据,只要用两个读取Modbus传感器的函数返回读取的数值来替换即可。
- 这个程序的核心部分是跑在TaskScheduler这个库上的,会无限执行读取Modbus的数据,注意处理一下没有读到数据的情况,可以给一个0的初始值,每5s执行一次
- MQTT是基于PubSubClient这个库的,文档在https://pubsubclient.knolleary.net/index.html如果出现错误码无法连接上MQTT客户端可以查看文档
- 这里的WiFi密码是写死在程序中的,如果想要通过手机来设置wifi的话可以使用WiFiManager这个库,这需要将NodeMCU配置成一个WebServer,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
#include <TaskScheduler.h> #include <PubSubClient.h> #include <ESP8266WiFi.h> #include <ArduinoJson.h> // 连接WIFI需要用的库 #include <DNSServer.h> #include <ESP8266WebServer.h> #include <WiFiManager.h> //const char* WIFI_SSID = "Luyouqi"; //const char* WIFI_PASSWORD = "a13269326360"; const char* IOT_ENDPOINT = "5ac35d8d3e3e4665827b8caeaa9cdba1.mqtt.iot.gz.baidubce.com"; const char* IOT_USERNAME = "5ac35d8d3e3e4665827b8caeaa9cdba1/test"; const char* IOT_KEY = "9d6wre3vftdbq0n8"; const char* IOT_TOPIC = "$baidu/iot/shadow/test/update"; const char* IOT_CLIENT_ID = "test"; WiFiClientSecure client; PubSubClient mqttclient(IOT_ENDPOINT, 1884, &mqtt_callback, client); Task schedule_task(1000, TASK_FOREVER, &ticker_handler); // 每5s执行一次 Scheduler runner; // 初始化JSON DynamicJsonDocument doc(1024); void setup() { Serial.begin(74880); // 连接WIFI的代码,如果没有连上会一直卡在这里 WiFiManager wifiManager; wifiManager.resetSettings(); wifiManager.autoConnect("FeiFeiDevice_00001","password"); // 增加这一行可以删除掉配置信息,每次断电都会重新连接 while (!Serial); // connect_wifi(); runner.init(); runner.addTask(schedule_task); schedule_task.enable(); } void loop() { mqttclient.loop(); runner.execute(); } // 不断执行 void ticker_handler() { if (!mqttclient.connected()) { Serial.print(F("MQTT state: ")); Serial.println(mqttclient.state()); String clientid {IOT_CLIENT_ID}; if (mqttclient.connect(clientid.c_str(), IOT_USERNAME, IOT_KEY)) { Serial.print(F("MQTT Connected. Client id = ")); Serial.println(clientid); // mqttclient.subscribe(IOT_TOPIC); } } else { // 接收arduino传过来的数据并发送至指定主题 // if (Serial.available() > 0) { // String buffer = Serial.readStringUntil('\r'); // mqttclient.publish(IOT_TOPIC, buffer.c_str()); // } doc["reported"]["temperature"] = random(1, 500) / 10.0; doc["reported"]["humidity"] = random(1, 500) / 10.0; Serial.println(""); Serial.println(doc.as<String>()); mqttclient.publish(IOT_TOPIC, doc.as<String>().c_str()); // 这里用于测试 // 需要构造的JSON // { // "reported": { // "temperature":123, // "humidity":321 // } // } // doc["reported"] = "gps"; // 清空串口数据 while (Serial.read() >= 0) {}; } } // MQTT收到数据的回调 void mqtt_callback (char* topic, byte* payload, unsigned int length) { byte *end = payload + length; for (byte *p = payload; p < end; ++p) { Serial.print(*((const char *)p)); } Serial.println(""); } // 连接WiFi //void connect_wifi() { // WiFi.begin(WIFI_SSID, WIFI_PASSWORD); // // while (WiFi.status() != WL_CONNECTED) { // delay(500); // Serial.print("."); // } // // Serial.print("\nWiFi connected to "); // Serial.println(WIFI_SSID); // Serial.print("IP address: "); // Serial.println(WiFi.localIP()); //} |
上传到NodeMCU之后,可以使用手机wifi会看到FeiFeiDevice_00001这个WiFi,连接上之后输入密码password来到连接的界面,选择要连接的wifi并输入密码即可,如果没有连上WIFI程序会一直卡在setup的位置不会走接下来的程序。
Case #4 STM32读取Modbus土壤湿度传感器数据
这里需要注意根据上文中土壤湿度传感器的源码我们注意到拿到的返回值是16进制高位和低位的数据,要得到准确的数据还需要转换再计算一下,高位转换成10进制再乘以256之后再加上低位的十进制因此程序是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
#include "lcd.h" #include "usart.h" #include "rs485.h" #include "math.h" int hexadecimal_to_decimal(int x) { int decimal_number, remainder, count = 0; while(x > 0) { remainder = x % 10; decimal_number = decimal_number + remainder * pow(16, count); x = x / 10; count++; } return decimal_number; } int main(void) { u8 key; u8 i=0,t=0; u8 cnt=0; u8 rs485buf[8]; char sendBuf[]= {0x01, 0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A}; delay_init(); //延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级 uart_init(115200); //串口初始化为115200 LED_Init(); //初始化与LED连接的硬件接口 LCD_Init(); //初始化LCD KEY_Init(); //按键初始化 RS485_Init(9600); //初始化RS485 POINT_COLOR=RED;//设置字体为红色 LCD_ShowString(30,50,200,16,16,"ELITE STM32"); LCD_ShowString(30,70,200,16,16,"RS485 TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,"2015/1/15"); LCD_ShowString(30,130,200,16,16,"KEY0:Send"); //显示提示信息 POINT_COLOR=BLUE;//设置字体为蓝色 LCD_ShowString(30,150,200,16,16,"Count:"); //显示当前计数值 LCD_ShowString(30,170,200,16,16,"Send Data:"); //提示发送的数据 LCD_ShowString(30,210,200,16,16,"Receive Data:"); //提示接收到的数据 LCD_ShowString(30,250,200,16,16,"Float Data:"); while(1) { // 发送数据 key=KEY_Scan(0); if(key==KEY0_PRES)//KEY0按下,发送一次数据 { for(i=0;i<8;i++) { rs485buf[i]=sendBuf[i];//填充发送缓冲区 LCD_ShowxNum(30+i*32,190,rs485buf[i],3,16,0X80); //显示数据 } RS485_Send_Data(rs485buf,8);//发送5个字节 } // 接收数据 RS485_Receive_Data(rs485buf,&key); if(key)//接收到有数据 { if(key>8)key=8;//最大是5个数据. for(i=0;i<key;i++)LCD_ShowxNum(30+i*32,230,rs485buf[i],3,16,0X80); //显示数据 // LCD_ShowxNum(30+i*32,230,rs485buf[1],3,16,0X80); // LCD_ShowxNum(30+i*32,230,hexadecimal_to_decimal(rs485buf[3])*256+hexadecimal_to_decimal(rs485buf[3]),3,16,0X80); LCD_ShowxNum(30,270,hexadecimal_to_decimal(rs485buf[3])*256+hexadecimal_to_decimal(rs485buf[4]),3,16,0X80); } t++; delay_ms(10); // 提示系统正在运行 if(t==20) { LED0=!LED0;//提示系统正在运行 t=0; cnt++; LCD_ShowxNum(30+48,150,cnt,3,16,0X80); //显示数据 } } } |