esp32对于485信号是没有办法直接读取的,本次使用的土壤湿度传感器是二合一的,还可以进行温度测量,RS485使用的是差分信号,分为A,B两线,RS485还是半双工通讯方式,因此可以通过读取地址的方式进行多个rs485传感器读取。
这是我使用的485传感器,其通讯协议如下
我这边就将一次读取以及修改地址写为一体了,因为我修改地址采用的是广播地址0XFF,要修改地址的时候请只接一个传感器不然传感器的地址将会全部改变。
以下是esp32代码
#include <HardwareSerial.h>
#define RS485Serial Serial2
// CRC16校验计算函数
unsigned int calculateCRC16(const uint8_t *data, uint8_t length) {
unsigned int crc = 0xFFFF;
for (uint8_t i = 0; i < length; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 1)
crc = (crc >> 1) ^ 0xA001;
else
crc >>= 1;
}
}
return crc;
}
// 发送Modbus命令
void sendModbusCommand(uint8_t address, uint8_t functionCode, uint16_t registerAddress, uint16_t value) {
uint8_t command[8];
command[0] = address; // 设备地址
command[1] = functionCode; // 功能码
command[2] = registerAddress >> 8; // 寄存器高字节
command[3] = registerAddress & 0xFF; // 寄存器低字节
command[4] = value >> 8; // 数据高字节
command[5] = value & 0xFF; // 数据低字节
unsigned int crc = calculateCRC16(command, 6); // 计算CRC
command[6] = crc & 0xFF; // CRC低字节
command[7] = crc >> 8; // CRC高字节
RS485Serial.write(command, 8); // 发送命令
}
// 读取响应数据
int read_time2=1000;
bool readModbusResponse(uint8_t *buffer, uint8_t expectedLength) {
uint32_t startTime = millis();
uint8_t index = 0;
while (millis() - startTime < read_time2) { // 1秒超时
if (RS485Serial.available()) {
buffer[index++] = RS485Serial.read();
if (index == expectedLength) {
return true;
}
}
}
return false;
}
//-----------------------------检查传感器是否存在
#define MAX_ADDRESS 0x3F // 最大地址 0x3F
bool validAddresses[MAX_ADDRESS + 1]; // 存储地址是否有效的数组
bool addressesChecked = false; // 标志位,表示地址是否已检查完毕
void checkAddresses() {
int read_time3=read_time2;
read_time2=500;
for (uint8_t i = 0x01; i <= MAX_ADDRESS; i++) {
sendModbusCommand(i, 0x03, 0x0000, 0x0002); // 读取2个寄存器
uint8_t readResponse[9];
if (readModbusResponse(readResponse, 9)) {
validAddresses[i] = true;
Serial.print("地址 0x");
Serial.print(i, HEX);
Serial.println(" 有效。");
} else {
validAddresses[i] = false;
Serial.print("地址 0x");
Serial.print(i, HEX);
Serial.println(" 无响应或无效。");
}
}
read_time2=read_time3;
addressesChecked = true; // 设置标志位,表示地址已检查完毕
}
void change(){
if (Serial.available()) {
int newAddress = Serial.parseInt(); // 获取用户输入的新地址
// 检查输入地址是否有效
if (newAddress >= 1 && newAddress <= 247) {
uint8_t broadcastAddress = 0xFF; // 广播地址
// 创建Modbus命令帧
uint8_t message[8];
message[0] = broadcastAddress; // 广播地址
message[1] = 0x06; // 功能码:写单个寄存器
message[2] = 0x07; // 寄存器地址高字节(0x07D0)
message[3] = 0xD0; // 寄存器地址低字节
message[4] = 0x00; // 新地址高字节
message[5] = newAddress; // 新地址低字节
// 计算CRC
uint16_t crc = calculateCRC16(message, 6);
message[6] = crc & 0xFF; // CRC低字节
message[7] = (crc >> 8) & 0xFF; // CRC高字节
// 发送数据到传感器
RS485Serial.write(message, 8);
// 延迟等待传感器响应
delay(100);
// 提示用户地址更改已发送
Serial.print("已发送地址更改命令,新的地址为:");
Serial.println(newAddress);
} else {
Serial.println("无效的地址,请输入1到247之间的数字。");
}
// 继续等待下一个输入
Serial.println("请输入新地址(1-247):");
}
}
void setup() {
// 初始化调试串口
Serial.begin(115200);
// 初始化RS485串口,假设RX连接到GPIO16,TX连接到GPIO17
RS485Serial.begin(4800, SERIAL_8N1, 17, 16); // RX: GPIO16, TX: GPIO17
Serial.println("请输入目标地址,例如 '2' 将地址修改为 0x02");
checkAddresses();
}
void loop() {
for (uint8_t i = 0x01; i <= 0x0f; i++) {//修改为合适的数量依次读取0f是依次读15次
change();
if (validAddresses[i]) {
sendModbusCommand(i, 0x03, 0x0000, 0x0002); // 读取2个寄存器
uint8_t readResponse[9];
if (readModbusResponse(readResponse, 9)) {
uint16_t moisture = (readResponse[3] << 8) | readResponse[4];
uint16_t temperature = (readResponse[5] << 8) | readResponse[6];
// 将湿度和温度转换为浮点型
float moisture_float = moisture / 10.0;
float temperature_float = temperature / 10.0;
if(moisture_float>101||temperature_float>200){
checkAddresses();
}
Serial.print("地址 0x");
Serial.print(i, HEX);
Serial.print(" 湿度: ");
Serial.print(moisture_float);
Serial.println("%");
Serial.print("地址 0x");
Serial.print(i, HEX);
Serial.print(" 温度: ");
Serial.println(temperature_float);
} else {
Serial.print("读取地址 0x");
Serial.print(i, HEX);
Serial.println(" 数据失败或无响应。");
}
}
}
delay(1000); // 每隔一秒读取一次,防止过度读取
}
上面代码的checkAddresses()是自动寻址代码,会从0x01寻址到0x3f查看是否有传感器存在,不需要可以删掉