【ESP32 Arduino】RS485通信及MODBUS RTU通信实例
1、研究官方例子
在Arduino IDE 2.3.2中,示例代码路径
注意代码注释中链接:https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html#circuit-a-collision-detection-circuit
2、示例代码修改与测试
因使用RS485调制解调模块的接口引脚同常规芯片的DE和/RE逻辑相反。故调用ESP更底层C语言API函数解决。
具体为:半双工,使用RTS信号控制RS485调制解调模块的数据方向,函数为: uart_set_line_inverse(1, UART_SIGNAL_RTS_INV);
/* This Sketch demonstrates how to use the Hardware Serial peripheral to communicate over an RS485 bus. Data received on the primary serial port is relayed to the bus acting as an RS485 interface and vice versa. UART to RS485 translation hardware (e.g., MAX485, MAX33046E, ADM483) is assumed to be configured in half-duplex mode with collision detection as described in https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/uart.html#circuit-a-collision-detection-circuit To use the script open the Arduino serial monitor (or alternative serial monitor on the Arduino port). Then, using an RS485 tranciver, connect another serial monitor to the RS485 port. Entering data on one terminal should be displayed on the other terminal. */ #include "hal/uart_types.h" #include "driver/uart.h" #include "driver/gpio.h" #define RS485_RX_PIN 5 #define RS485_TX_PIN 4 #define RS485_RTS_PIN 6 #define RS485 Serial1 void setup() { Serial.begin(115200); while (!Serial) { delay(10); } RS485.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); while (!RS485) { delay(10); } if (!RS485.setPins(-1, -1, -1, RS485_RTS_PIN)) { // -1 保持引脚不变 Serial.print("Failed to set RS485 pins"); } //esp_err_t uart_set_line_inverse(uart_port_t uart_num, uint32_t inverse_mask) uart_set_line_inverse(1, UART_SIGNAL_RTS_INV); // 特殊,修改输出或输入的通信电信号正负逻辑 // Certain versions of Arduino core don't define MODE_RS485_HALF_DUPLEX and so fail to compile. // By using UART_MODE_RS485_HALF_DUPLEX defined in hal/uart_types.h we work around this problem. // If using a newer IDF and Arduino core you can omit including hal/uart_types.h and use MODE_RS485_HALF_DUPLEX // defined in esp32-hal-uart.h (included during other build steps) instead. if (!RS485.setMode(UART_MODE_RS485_HALF_DUPLEX)) { Serial.print("Failed to set RS485 mode"); } } void loop() { // Serial.println("loop:"); // RS485.write("1234567890"); String serial_data=""; /* 存放接收到的串口数据 */ if (RS485.available()) { int c = RS485.read(); /* 读取一字节串口数据 */ while (c >= 0) { serial_data += (char)c; /* 存放到serial_data变量中 */ c = RS485.read(); /* 继续读取一字节串口数据 */ } } if(serial_data.length()>0){ Serial.println(serial_data); RS485.println(serial_data); serial_data =""; } // if (Serial.available()) { // RS485.write(Serial.read()); // } delay(100); }
3、查找的MODBUS参考帖子,确定第三方库eModbus并下载安装
ESP32 使用RS485模块实现Modbus通信(eModbus)
https://mp.weixin.qq.com/s/3mT605kXvFT2JCfg6plYLg?poc_token=HEOUmGajTehMUKHW-FPlFNsfnvrrBHeqGKYBbBht
下载安装eModbus库(https://github.com/eModbus/eModbus)。并需要添加AsyncTCP库( https://github.com/dvarrel/AsyncTCP)
在https://github.com/eModbus/eModbus研究示例代码,也可以在安装的库文件夹中查找代码,并登录相关网页学习。
这里复制RTU16example的main.c文件代码,编译成功,代码初步可用。这也说明安装的库eModbus可以使用。
根据引脚连接关系,定义RX, TX, RTS(视RS485调制解调模块选用)。
其他参考资料:https://getiot.tech/modbus
4、具体示例
使用RTS,使用RS485调制解调模块的接口引脚同常规芯片的DE和/RE逻辑相反。
修改上述示例代码,经调试通过如下:
// ================================================================================================= // eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to ModbusClient // MIT license - see license.md for details // ================================================================================================= // Example code to show the usage of the eModbus library. // Please refer to root/Readme.md for a full description. // Includes: <Arduino.h> for Serial etc. #include <Arduino.h> // Include the header for the ModbusClient RTU style #include "ModbusClientRTU.h" //增添头文件 #include "hal/uart_types.h" #include "driver/uart.h" #include "driver/gpio.h" //增添宏定义 与RS485调制解调模块接口 #define RS485_RX_PIN 5 #define RS485_TX_PIN 4 #define RS485_RTS_PIN 6 #define RS485 Serial1 #define RS485_SerialNum 1 // Create a ModbusRTU client instance // In my case the RS485 module had auto halfduplex, so no parameter with the DE/RE pin is required! ModbusClientRTU MB; // Define an onData handler function to receive the regular responses // Arguments are Modbus server ID, the function code requested, the message data and length of it, // plus a user-supplied token to identify the causing request void handleData(ModbusMessage response, uint32_t token) { // 回调函数 Serial.printf("Response: serverID=%d, FC=%d, Token=%08X, length=%d:\n", response.getServerID(), response.getFunctionCode(), token, response.size()); for (auto &byte : response) { Serial.printf("%02X ", byte); } Serial.println(""); } // Define an onError handler function to receive error responses // Arguments are the error code returned and a user-supplied token to identify the causing request void handleError(Error error, uint32_t token) { // 回调函数 // ModbusError wraps the error code and provides a readable error message for it ModbusError me(error); Serial.printf("Error response: %02X - %s\n", (int)me, (const char *)me); } // Setup() - initialization happens here void setup() { // Init Serial monitor Serial.begin(115200); while (!Serial) {} Serial.println("__ OK __"); // Set up Serial2 connected to Modbus RTU // (Fill in your data here!) // RTUutils::prepareHardwareSerial(Serial2); // Serial2.begin(19200, SERIAL_8N1, GPIO_NUM_17, GPIO_NUM_16); // RS485串口外设 连接 Modbus RTU RTUutils::prepareHardwareSerial(RS485); RS485.begin(9600, SERIAL_8N1, RS485_RX_PIN, RS485_TX_PIN); while (!RS485) { delay(10); } if (!RS485.setPins(-1, -1, -1, RS485_RTS_PIN)) { // -1 保持引脚不变 Serial.print("Failed to set RS485 pins"); } // //esp_err_t uart_set_line_inverse(uart_port_t uart_num, uint32_t inverse_mask) uart_set_line_inverse(RS485_SerialNum, UART_SIGNAL_RTS_INV); // 特殊,修改输出或输入的通信电信号正负逻辑 // Certain versions of Arduino core don't define MODE_RS485_HALF_DUPLEX and so fail to compile. // By using UART_MODE_RS485_HALF_DUPLEX defined in hal/uart_types.h we work around this problem. // If using a newer IDF and Arduino core you can omit including hal/uart_types.h and use MODE_RS485_HALF_DUPLEX // defined in esp32-hal-uart.h (included during other build steps) instead. if (!RS485.setMode(UART_MODE_RS485_HALF_DUPLEX)) { Serial.print("Failed to set RS485 mode"); } // END,RS485串口外设 连接 Modbus RTU // Set up ModbusRTU client. // - provide onData handler function MB.onDataHandler(&handleData); // 回调函数 // - provide onError handler function MB.onErrorHandler(&handleError); // 回调函数 // Set message timeout to 2000ms MB.setTimeout(2000); // Start ModbusRTU background task //MB.begin(Serial2); MB.begin(RS485); // We will first read the registers, then write to them and finally read them again to verify the change // Create request for // (Fill in your data here!) // - server ID = 1 // - function code = 0x03 (read holding register) // - address to read = word 33 // - data words to read = 6 // - token to match the response with the request. // // If something is missing or wrong with the call parameters, we will immediately get an error code // and the request will not be issued uint32_t Token = 1111; Error err = MB.addRequest(Token++, 1, READ_HOLD_REGISTER, 33, 6); if (err != SUCCESS) { ModbusError e(err); Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e); } // Create request for // (Fill in your data here!) // - server ID = 1 // - function code = 0x16 (write multiple registers) // - address to write = word 33ff // - data words to write = see below // - data bytes to write = see below // - token to match the response with the request. // uint16_t wData[] = { 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666 }; err = MB.addRequest(Token++, 1, WRITE_MULT_REGISTERS, 33, 6, 12, wData); if (err != SUCCESS) { ModbusError e(err); Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e); } // Create request for // (Fill in your data here!) // - server ID = 1 // - function code = 0x03 (read holding register) // - address to read = word 33 // - data words to read = 6 // - token to match the response with the request. // err = MB.addRequest(Token++, 1, READ_HOLD_REGISTER, 33, 6); if (err != SUCCESS) { ModbusError e(err); Serial.printf("Error creating request: %02X - %s\n", (int)e, (const char *)e); } // The output on the Serial Monitor will be (depending on your Modbus the data will be different): // __ OK __ // Response: serverID=1, FC=3, Token=00000457, length=15: // 01 03 0C 60 61 62 63 64 65 66 67 68 69 6A 6B // Response: serverID=1, FC=16, Token=00000458, length=19: // 01 10 00 21 00 06 0C 11 11 22 22 33 33 44 44 55 55 66 66 // Response: serverID=1, FC=3, Token=00000459, length=15: // 01 03 0C 11 11 22 22 33 33 44 44 55 55 66 66 } // loop() - nothing done here today! void loop() { }
串口助手观察结果如下:
[2024-07-18 14:17:24.040]# RECV HEX>
01 03 00 21 00 06 95 C2
[2024-07-18 14:17:24.045]# AUTO REPLY HEX>
01 03 0C 12 34 23 45 34 56 45 67 56 78 67 89 CB 36
[2024-07-18 14:17:24.159]# RECV HEX>
01 10 00 21 00 06 0C 11 11 22 22 33 33 44 44 55 55 66 66 8B 81
[2024-07-18 14:17:24.164]# AUTO REPLY HEX>
01 10 00 21 00 06 10 01
[2024-07-18 14:17:24.255]# RECV HEX>
01 03 00 21 00 06 95 C2
[2024-07-18 14:17:24.260]# AUTO REPLY HEX>
01 03 0C 12 34 23 45 34 56 45 67 56 78 67 89 CB 36
5、收获
1、arduino有大量第三方库,有示例代码参考。轮子很多!
2 、arduino-esp32 是在C API基础上进一步的封装,所以可以直接调用更底层的API函数,例如:这里实现的RTS信号逻辑设置。
3 、arduino-esp32文档官网:https://docs.espressif.com/projects/arduino-esp32/en/latest/libraries.html?highlight=uart