336 lines
12 KiB
C++
336 lines
12 KiB
C++
|
|
#include <iostream>
|
|||
|
|
#include <vector>
|
|||
|
|
#include <string>
|
|||
|
|
#include <fcntl.h>
|
|||
|
|
#include <termios.h>
|
|||
|
|
#include <unistd.h>
|
|||
|
|
#include <cstring>
|
|||
|
|
#include <cerrno>
|
|||
|
|
#include <cmath>
|
|||
|
|
#include <sys/ioctl.h>
|
|||
|
|
#include <linux/serial.h>
|
|||
|
|
|
|||
|
|
// 串口设备路径,根据RK3588的实际接线调整
|
|||
|
|
// 通常USB转串口设备会被识别为 /dev/ttyUSB0, /dev/ttyUSB1...
|
|||
|
|
// 板载串口可能是 /dev/ttyS1, /dev/ttyS2... 等
|
|||
|
|
const char* SERIAL_PORT = "/dev/ttyS9";
|
|||
|
|
|
|||
|
|
// 默认Modbus参数
|
|||
|
|
const int DEFAULT_BAUDRATE = B9600;
|
|||
|
|
const unsigned char DEFAULT_SLAVE_ADDR = 0x01;
|
|||
|
|
|
|||
|
|
// 功能码定义
|
|||
|
|
#define READ_HOLDING_REGISTERS 0x03
|
|||
|
|
#define WRITE_SINGLE_REGISTER 0x06
|
|||
|
|
|
|||
|
|
// 寄存器地址
|
|||
|
|
enum RegisterAddress {
|
|||
|
|
REG_TEMP = 0x0000,
|
|||
|
|
REG_HUMI = 0x0001,
|
|||
|
|
REG_TEMP_ALARM = 0x0030,
|
|||
|
|
REG_HUMI_ALARM = 0x0031,
|
|||
|
|
REG_DEVICE_ADDR = 0x0002, // 用于写入
|
|||
|
|
REG_BAUDRATE_CODE = 0x0003 // 用于写入
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 波特率码映射
|
|||
|
|
const int BAUDRATES[] = {1200, 2400, 4800, 9600, 19200, 38400, 57600};
|
|||
|
|
const unsigned char BAUDRATE_CODES[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 计算Modbus RTU的CRC16校验码
|
|||
|
|
* @param data 数据缓冲区
|
|||
|
|
* @param length 数据长度
|
|||
|
|
* @return uint16_t 计算出的CRC16值
|
|||
|
|
*/
|
|||
|
|
uint16_t calculateCRC16(const uint8_t *data, size_t length) {
|
|||
|
|
uint16_t crc = 0xFFFF;
|
|||
|
|
for (size_t i = 0; i < length; ++i) {
|
|||
|
|
crc ^= data[i];
|
|||
|
|
for (int j = 0; j < 8; ++j) {
|
|||
|
|
if (crc & 0x0001) {
|
|||
|
|
crc >>= 1;
|
|||
|
|
crc ^= 0xA001;
|
|||
|
|
} else {
|
|||
|
|
crc >>= 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return crc;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 打开并配置串口
|
|||
|
|
* @param port_name 串口设备名,如 "/dev/ttyUSB0"
|
|||
|
|
* @param baudrate 波特率常量,如 B9600
|
|||
|
|
* @return int 成功返回文件描述符,失败返回-1
|
|||
|
|
*/
|
|||
|
|
int openSerialPort(const char* port_name, speed_t baudrate) {
|
|||
|
|
int fd = open(port_name, O_RDWR | O_NOCTTY | O_NDELAY);
|
|||
|
|
if (fd < 0) {
|
|||
|
|
std::cerr << "Error opening serial port " << port_name << ": " << strerror(errno) << std::endl;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 设置串口参数
|
|||
|
|
termios options;
|
|||
|
|
tcgetattr(fd, &options);
|
|||
|
|
|
|||
|
|
cfsetispeed(&options, baudrate);
|
|||
|
|
cfsetospeed(&options, baudrate);
|
|||
|
|
|
|||
|
|
// 8N1
|
|||
|
|
options.c_cflag &= ~PARENB; // 无校验
|
|||
|
|
options.c_cflag &= ~CSTOPB; // 1位停止位
|
|||
|
|
options.c_cflag &= ~CSIZE; // 清除数据位设置
|
|||
|
|
options.c_cflag |= CS8; // 8位数据位
|
|||
|
|
|
|||
|
|
// 启用接收
|
|||
|
|
options.c_cflag |= (CLOCAL | CREAD);
|
|||
|
|
|
|||
|
|
// 原始输入模式
|
|||
|
|
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
|
|||
|
|
|
|||
|
|
// 原始输出模式
|
|||
|
|
options.c_oflag &= ~OPOST;
|
|||
|
|
|
|||
|
|
// 设置超时 (100ms)
|
|||
|
|
options.c_cc[VMIN] = 0;
|
|||
|
|
options.c_cc[VTIME] = 1; // 0.1s
|
|||
|
|
|
|||
|
|
tcsetattr(fd, TCSANOW, &options);
|
|||
|
|
|
|||
|
|
// 清空输入/输出缓冲区
|
|||
|
|
tcflush(fd, TCIOFLUSH);
|
|||
|
|
|
|||
|
|
// --- 配置RS485为半双工模式(根据芯片型号可能需要ioctl) ---
|
|||
|
|
// 这是一个通用的方法,适用于一些带有转换芯片的USB转串口模块
|
|||
|
|
// serial_rs485 rs485conf;
|
|||
|
|
// memset(&rs485conf, 0, sizeof(rs485conf));
|
|||
|
|
// rs485conf.flags = SER_RS485_ENABLED; // 启用RS485模式
|
|||
|
|
// rs485conf.delay_rts_before_send = 0; // 发送前RTS延时
|
|||
|
|
// rs485conf.delay_rts_after_send = 0; // 发送后RTS延时
|
|||
|
|
// if (ioctl(fd, TIOCSRS485, &rs485conf) < 0) {
|
|||
|
|
// std::cerr << "Warning: Could not set RS485 mode, assuming it's already configured by hardware." << std::endl;
|
|||
|
|
// }
|
|||
|
|
// -----------------------------------------------------------------
|
|||
|
|
|
|||
|
|
return fd;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 根据波特率获取波特率码
|
|||
|
|
* @param baudrate 波特率数值
|
|||
|
|
* @return uint8_t 波特率码,未找到返回0xFF
|
|||
|
|
*/
|
|||
|
|
uint8_t getBaudrateCode(int baudrate) {
|
|||
|
|
for (size_t i = 0; i < sizeof(BAUDRATES) / sizeof(BAUDRATES[0]); ++i) {
|
|||
|
|
if (BAUDRATES[i] == baudrate) {
|
|||
|
|
return BAUDRATE_CODES[i];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return 0xFF;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 读取传感器寄存器数据
|
|||
|
|
* @param fd 串口文件描述符
|
|||
|
|
* @param slave_addr 从站地址
|
|||
|
|
* @param reg_addr 寄存器地址
|
|||
|
|
* @param reg_count 读取寄存器数量
|
|||
|
|
* @return std::vector<uint8_t> 成功返回接收到的数据(不包括地址、功能码和CRC),失败返回空向量
|
|||
|
|
*/
|
|||
|
|
std::vector<uint8_t> readRegister(int fd, uint8_t slave_addr, uint16_t reg_addr, uint16_t reg_count) {
|
|||
|
|
std::vector<uint8_t> request;
|
|||
|
|
request.push_back(slave_addr);
|
|||
|
|
request.push_back(READ_HOLDING_REGISTERS);
|
|||
|
|
request.push_back(reg_addr >> 8);
|
|||
|
|
request.push_back(reg_addr & 0xFF);
|
|||
|
|
request.push_back(reg_count >> 8);
|
|||
|
|
request.push_back(reg_count & 0xFF);
|
|||
|
|
|
|||
|
|
uint16_t crc = calculateCRC16(request.data(), request.size());
|
|||
|
|
request.push_back(crc & 0xFF);
|
|||
|
|
request.push_back(crc >> 8);
|
|||
|
|
|
|||
|
|
// 发送请求
|
|||
|
|
if (write(fd, request.data(), request.size()) != request.size()) {
|
|||
|
|
std::cerr << "Error writing to serial port." << std::endl;
|
|||
|
|
return {};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据协议,标准响应应该是:[地址][功能码][字节数][数据...][CRC_L][CRC_H]
|
|||
|
|
// 预期响应大小 = 5 (头+尾) + reg_count * 2
|
|||
|
|
const int expected_response_size = 5 + reg_count * 2;
|
|||
|
|
std::vector<uint8_t> response(expected_response_size);
|
|||
|
|
int bytes_read = read(fd, response.data(), expected_response_size);
|
|||
|
|
|
|||
|
|
if (bytes_read < expected_response_size) {
|
|||
|
|
std::cerr << "Incomplete response from sensor. Expected " << expected_response_size
|
|||
|
|
<< " bytes, got " << bytes_read << std::endl;
|
|||
|
|
return {};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证响应
|
|||
|
|
if (response[0] != slave_addr || response[1] != READ_HOLDING_REGISTERS) {
|
|||
|
|
std::cerr << "Invalid response header." << std::endl;
|
|||
|
|
return {};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
uint16_t received_crc = (response[bytes_read - 1] << 8) | response[bytes_read - 2];
|
|||
|
|
uint16_t calculated_crc = calculateCRC16(response.data(), bytes_read - 2);
|
|||
|
|
if (received_crc != calculated_crc) {
|
|||
|
|
std::cerr << "CRC check failed." << std::endl;
|
|||
|
|
return {};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 返回有效数据部分(从字节数开始到CRC前)
|
|||
|
|
return std::vector<uint8_t>(response.begin() + 2, response.end() - 2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 写入单个寄存器数据(用于修改地址或波特率)
|
|||
|
|
* @param fd 串口文件描述符
|
|||
|
|
* @param slave_addr 从站地址(通常是0xFF广播地址,用于修改)
|
|||
|
|
* @param reg_addr 寄存器地址
|
|||
|
|
* @param value 要写入的值
|
|||
|
|
* @return bool 成功返回true,失败返回false
|
|||
|
|
*/
|
|||
|
|
bool writeSingleRegister(int fd, uint8_t slave_addr, uint16_t reg_addr, uint16_t value) {
|
|||
|
|
std::vector<uint8_t> request;
|
|||
|
|
request.push_back(slave_addr);
|
|||
|
|
request.push_back(WRITE_SINGLE_REGISTER);
|
|||
|
|
request.push_back(reg_addr >> 8);
|
|||
|
|
request.push_back(reg_addr & 0xFF);
|
|||
|
|
request.push_back(value >> 8);
|
|||
|
|
request.push_back(value & 0xFF);
|
|||
|
|
|
|||
|
|
uint16_t crc = calculateCRC16(request.data(), request.size());
|
|||
|
|
request.push_back(crc & 0xFF);
|
|||
|
|
request.push_back(crc >> 8);
|
|||
|
|
|
|||
|
|
// 发送请求
|
|||
|
|
int bytes_written = write(fd, request.data(), request.size());
|
|||
|
|
if (bytes_written != request.size()) {
|
|||
|
|
std::cerr << "Error writing to serial port." << std::endl;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 读取确认响应
|
|||
|
|
std::vector<uint8_t> response(8);
|
|||
|
|
int bytes_read = read(fd, response.data(), 8);
|
|||
|
|
if (bytes_read != 8) {
|
|||
|
|
std::cerr << "Incomplete response for write operation." << std::endl;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证响应(应该和请求数据一致)
|
|||
|
|
if (memcmp(request.data(), response.data(), 8) == 0) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
std::cerr << "Write confirmation mismatch." << std::endl;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 解析Modbus数据为实际温湿度值
|
|||
|
|
* @param data 高字节在前,低字节在后的两个字节数据
|
|||
|
|
* @return float 解析后的浮点数
|
|||
|
|
*/
|
|||
|
|
float parseSensorData(const std::vector<uint8_t>& data) {
|
|||
|
|
if (data.size() < 2) return NAN;
|
|||
|
|
int16_t raw_value = (data[0] << 8) | data[1];
|
|||
|
|
return static_cast<float>(raw_value) / 10.0f;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
int main() {
|
|||
|
|
std::cout << "--- RK3588 Test for KLHA JWST-20 Sensor ---" << std::endl;
|
|||
|
|
|
|||
|
|
// 1. 打开串口
|
|||
|
|
int serial_fd = openSerialPort(SERIAL_PORT, DEFAULT_BAUDRATE);
|
|||
|
|
if (serial_fd < 0) {
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
std::cout << "Successfully opened serial port: " << SERIAL_PORT << std::endl;
|
|||
|
|
|
|||
|
|
// 2. 读取温湿度数据
|
|||
|
|
std::cout << "\nReading data from sensor at address 0x01..." << std::endl;
|
|||
|
|
|
|||
|
|
// 读取温度
|
|||
|
|
auto temp_data = readRegister(serial_fd, DEFAULT_SLAVE_ADDR, REG_TEMP, 1);
|
|||
|
|
float temperature = NAN;
|
|||
|
|
if (!temp_data.empty()) {
|
|||
|
|
temperature = parseSensorData(temp_data);
|
|||
|
|
std::cout << "Temperature: " << temperature << " °C" << std::endl;
|
|||
|
|
} else {
|
|||
|
|
std::cout << "Failed to read temperature." << std::endl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 读取湿度
|
|||
|
|
auto humi_data = readRegister(serial_fd, DEFAULT_SLAVE_ADDR, REG_HUMI, 1);
|
|||
|
|
float humidity = NAN;
|
|||
|
|
if (!humi_data.empty()) {
|
|||
|
|
humidity = parseSensorData(humi_data);
|
|||
|
|
std::cout << "Humidity: " << humidity << " %RH" << std::endl;
|
|||
|
|
} else {
|
|||
|
|
std::cout << "Failed to read humidity." << std::endl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 读取报警状态
|
|||
|
|
auto temp_alarm_data = readRegister(serial_fd, DEFAULT_SLAVE_ADDR, REG_TEMP_ALARM, 1);
|
|||
|
|
if (!temp_alarm_data.empty()) {
|
|||
|
|
uint8_t alarm_status = temp_alarm_data[0];
|
|||
|
|
if (alarm_status == 0x00) std::cout << "Temperature Alarm: None" << std::endl;
|
|||
|
|
else if (alarm_status == 0x01) std::cout << "Temperature Alarm: High" << std::endl;
|
|||
|
|
else if (alarm_status == 0x02) std::cout << "Temperature Alarm: Low" << std::endl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
auto humi_alarm_data = readRegister(serial_fd, DEFAULT_SLAVE_ADDR, REG_HUMI_ALARM, 1);
|
|||
|
|
if (!humi_alarm_data.empty()) {
|
|||
|
|
uint8_t alarm_status = humi_alarm_data[0];
|
|||
|
|
if (alarm_status == 0x00) std::cout << "Humidity Alarm: None" << std::endl;
|
|||
|
|
else if (alarm_status == 0x01) std::cout << "Humidity Alarm: High" << std::endl;
|
|||
|
|
else if (alarm_status == 0x02) std::cout << "Humidity Alarm: Low" << std::endl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. (高级功能) 示例:修改传感器波特率
|
|||
|
|
// 注意:修改波特率是一个高风险操作,如果设置错误,且拔码开关不可调,可能导致设备无法通信。
|
|||
|
|
// 下面的代码被注释掉,仅作演示。请谨慎使用!
|
|||
|
|
/*
|
|||
|
|
std::cout << "\n--- Advanced: Modifying Baudrate ---" << std::endl;
|
|||
|
|
int new_baudrate_to_set = 19200;
|
|||
|
|
uint8_t new_baud_code = getBaudrateCode(new_baudrate_to_set);
|
|||
|
|
std::cout << "Attempting to change baudrate to " << new_baudrate_to_set << " (Code: 0x" << std::hex << (int)new_baud_code << std::dec << ")" << std::endl;
|
|||
|
|
|
|||
|
|
// 使用广播地址(0xFF)向所有设备发送波特率修改命令
|
|||
|
|
if (writeSingleRegister(serial_fd, 0xFF, REG_BAUDRATE_CODE, new_baud_code)) {
|
|||
|
|
std::cout << "Baudrate change command sent successfully. Device will restart with new baudrate." << std::endl;
|
|||
|
|
// 重要:必须重新打开串口使用新的波特率
|
|||
|
|
close(serial_fd);
|
|||
|
|
sleep(2); // 给设备重启时间
|
|||
|
|
serial_fd = openSerialPort(SERIAL_PORT, B19200); // 使用新波特率重新打开
|
|||
|
|
if (serial_fd < 0) {
|
|||
|
|
std::cerr << "Failed to reopen port with new baudrate. Device might be unresponsive." << std::endl;
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
std::cout << "Reopened port with new baudrate. Attempting to read data again..." << std::endl;
|
|||
|
|
// 重新读取数据来验证
|
|||
|
|
auto new_temp_data = readRegister(serial_fd, DEFAULT_SLAVE_ADDR, REG_TEMP, 1);
|
|||
|
|
if (!new_temp_data.empty()) {
|
|||
|
|
std::cout << "New Temperature: " << parseSensorData(new_temp_data) << " °C" << std::endl;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
std::cerr << "Failed to send baudrate change command." << std::endl;
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// 5. 关闭串口
|
|||
|
|
close(serial_fd);
|
|||
|
|
std::cout << "\nTest finished. Serial port closed." << std::endl;
|
|||
|
|
|
|||
|
|
return 0;
|
|||
|
|
}
|