Commit 61fd3055 authored by 学习的菜鸟's avatar 学习的菜鸟

定位

parents
// main.cpp
#include "serial_port.hpp"
#include <iostream>
#include <vector>
#include <iomanip>
#include <csignal>
#include <cstring>
// 全局的串口对象指针,用于信号处理
std::unique_ptr<SerialPort> g_serial_port;
// 信号处理函数,用于优雅地退出
void signalHandler(int signum) {
std::cout << "\nInterrupt signal (" << signum << ") received." << std::endl;
if (g_serial_port) {
g_serial_port->close();
}
exit(signum);
}
// 打印收到的消息
void print_hex(const std::vector<uint8_t>& data) {
for (uint8_t byte : data) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(byte) << " ";
}
std::cout << std::dec; // 恢复十进制输出
}
// 消息处理回调函数
void onMessageReceived(const std::vector<uint8_t>& message) {
size_t message_len = message.size();
if (message_len == 0) {
return;
}
// 使用我们推荐的 unique_ptr 方法来创建数组
auto data_array = std::make_unique<uint8_t[]>(message_len);
memcpy(data_array.get(), message.data(), message_len);
// --- 正确地以十六进制格式打印数组内容 ---
std::cout << "\n<-- Received " << message_len << " bytes. Copied to array:\n ";
if(data_array[10]=0x01){
uint16_t x_local=(static_cast<uint8_t>(data_array[13])<< 8) | data_array[14];
uint16_t y_local=(static_cast<uint8_t>(data_array[15])<< 8) | data_array[16];
std::cout << std::dec << "tag:" << static_cast<int>(data_array[8]) << std::endl;
std::cout<<" "<<"x_local:" << x_local <<"y_local:" <<y_local <<std::endl;
}
// 恢复为十进制模式是个好习惯,以免影响后续的其他输出
std::cout << std::dec << std::endl;
// 你也可以在这里进行CRC校验等操作
if (message_len > 2) {
uint16_t received_crc = (static_cast<uint16_t>(data_array[message_len - 1]) << 8) | data_array[message_len - 2];
uint16_t calculated_crc = SerialPort::crc16(data_array.get(), message_len - 2);
std::cout << " CRC Check on array data: "
<< (received_crc == calculated_crc ? "OK" : "FAILED!") << std::endl;
}
// 重新打印菜单提示符,让用户界面更友好
std::cout << "\nEnter your choice: " << std::flush;
}
void print_menu() {
std::cout << "\n======================================\n";
std::cout << "Select a command to send:\n";
std::cout << " 1: 一次定位\n";
std::cout << " 2: 获取上次定位结果\n";
std::cout << " 3: 持续定位\n";
std::cout << " 4: 退出定位\n";
std::cout << " q: Quit\n";
std::cout << "======================================\n";
std::cout << "Enter your choice: " << std::flush;
}
int main(int argc, char* argv[]) {
// 注册信号处理,以便Ctrl+C可以正常关闭串口
signal(SIGINT, signalHandler);
std::string port = "/dev/ttyUSB0";
if (argc > 1) {
port = argv[1];
}
g_serial_port = std::make_unique<SerialPort>(port);
if (!g_serial_port->open()) {
return 1;
}
// 启动监听线程,并提供回调函数
g_serial_port->startListening(onMessageReceived);
// 预设指令(不含CRC)
const std::vector<uint8_t> cmd_once = {0x01, 0x10, 0x00, 0x3B, 0x00, 0x01, 0x02, 0x00, 0x01};
const std::vector<uint8_t> cmd_get_last = {0x01, 0x03, 0x01, 0x00, 0x00, 0x15};
const std::vector<uint8_t> cmd_continuous = {0x01 ,0x10 ,0x00 ,0x3B ,0x00 ,0x01 ,0x02 ,0x00 ,0x04 ,0xA3 ,0x18};
const std::vector<uint8_t> cmd_stop_modbus = {0x01,0x10,0x00,0x3B,0x00,0x01,0x02,0x00,0x00,0xA2,0xDB};
char choice;
print_menu();
while (std::cin >> choice && (choice != 'q' && choice != 'Q')) {
std::cout << "--> Sending command '" << choice << "': ";
bool success = false;
switch (choice) {
case '1':
print_hex(cmd_once);
std::cout << std::endl;
success = g_serial_port->send(cmd_once);
break;
case '2':
print_hex(cmd_get_last);
std::cout << std::endl;
success = g_serial_port->send(cmd_get_last);
break;
case '3':
print_hex(cmd_continuous);
std::cout << std::endl;
success = g_serial_port->send(cmd_continuous);
break;
case '4':
print_hex(cmd_stop_modbus);
std::cout << std::endl;
success = g_serial_port->send(cmd_stop_modbus);
break;
default:
std::cout << "Invalid choice. Please try again." << std::endl;
}
if(!success) {
std::cerr << "Failed to send command!" << std::endl;
}
// 重新打印菜单,不清空接收到的消息
if (choice != '\n') {
print_menu();
}
}
std::cout << "Exiting..." << std::endl;
// g_serial_port的析构函数会自动被调用,关闭串口和线程
return 0;
}
\ No newline at end of file
// serial_port.cpp (修改版)
#include "serial_port.hpp"
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <cerrno>
#include <cstring>
// ... 构造函数、析构函数、open、close、isOpen、send、crc16函数保持不变 ...
// ... (为了简洁,这里省略了未改动的代码,请保留您文件中的这些部分) ...
SerialPort::SerialPort(const std::string& port_name)
: port_name_(port_name),
fd_(-1),
is_open_(false),
stop_listener_(false) {}
SerialPort::~SerialPort() {
close();
}
bool SerialPort::open(speed_t baud_rate) {
if (is_open_) {
std::cerr << "Warning: Port " << port_name_ << " is already open." << std::endl;
return true;
}
fd_ = ::open(port_name_.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
if (fd_ == -1) {
std::cerr << "Error: Failed to open serial port " << port_name_
<< " - " << strerror(errno) << std::endl;
return false;
}
fcntl(fd_, F_SETFL, 0);
struct termios options;
if (tcgetattr(fd_, &options) != 0) {
std::cerr << "Error: tcgetattr failed - " << strerror(errno) << std::endl;
::close(fd_);
fd_ = -1;
return false;
}
cfsetispeed(&options, baud_rate);
cfsetospeed(&options, baud_rate);
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
options.c_cflag |= (CLOCAL | CREAD);
options.c_iflag &= ~(IXON | IXOFF | IXANY);
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_oflag &= ~OPOST;
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
if (tcsetattr(fd_, TCSANOW, &options) != 0) {
std::cerr << "Error: tcsetattr failed - " << strerror(errno) << std::endl;
::close(fd_);
fd_ = -1;
return false;
}
tcflush(fd_, TCIOFLUSH);
is_open_ = true;
std::cout << "Serial port " << port_name_ << " opened successfully." << std::endl;
return true;
}
void SerialPort::close() {
stopListening();
if (is_open_) {
is_open_ = false;
::close(fd_);
fd_ = -1;
std::cout << "Serial port " << port_name_ << " closed." << std::endl;
}
}
bool SerialPort::isOpen() const {
return is_open_;
}
bool SerialPort::send(const std::vector<uint8_t>& data) {
if (!is_open_) {
std::cerr << "Error: Port is not open. Cannot send data." << std::endl;
return false;
}
uint16_t crc = crc16(data.data(), data.size());
std::vector<uint8_t> data_with_crc = data;
data_with_crc.push_back(crc & 0xFF);
data_with_crc.push_back((crc >> 8) & 0xFF);
ssize_t written = ::write(fd_, data_with_crc.data(), data_with_crc.size());
if (written < 0) {
std::cerr << "Error: Failed to write to serial port - " << strerror(errno) << std::endl;
return false;
}
if (static_cast<size_t>(written) != data_with_crc.size()) {
std::cerr << "Warning: Not all bytes were written to serial port." << std::endl;
}
return true;
}
void SerialPort::startListening(MessageHandler handler) {
if (!is_open_) {
std::cerr << "Error: Cannot start listening, port is not open." << std::endl;
return;
}
if (listener_thread_.joinable()) {
std::cerr << "Warning: Listener is already running." << std::endl;
return;
}
message_handler_ = handler;
stop_listener_ = false;
listener_thread_ = std::thread(&SerialPort::listenerLoop, this);
}
void SerialPort::stopListening() {
stop_listener_ = true;
if (listener_thread_.joinable()) {
listener_thread_.join();
}
}
// 【新增】辅助函数:根据Modbus协议解析消息长度
// 返回值:>0 表示预期的消息长度,0 表示数据不足无法判断
size_t parseMessageLength(const std::vector<uint8_t>& buffer) {
// 异常响应帧长度固定为5
if (buffer.size() >= 2 && (buffer[1] & 0x80)) {
return 5;
}
// 正常响应帧
if (buffer.size() >= 3) {
uint8_t func_code = buffer[1];
if (func_code == 0x03 || func_code == 0x04) { // 读寄存器
uint8_t byte_count = buffer[2];
// 总长度 = 地址(1) + 功能码(1) + 字节数(1) + 数据(N) + CRC(2)
return 3 + byte_count + 2;
} else if (func_code == 0x06 || func_code == 0x10) { // 写寄存器
// 写响应固定为8字节
return 8;
}
}
// 数据不足或功能码不识别,无法判断长度
return 0;
}
// 【重大修改】监听线程的主循环函数
void SerialPort::listenerLoop() {
std::vector<uint8_t> msg_buffer;
uint8_t rx_buffer[256];
while (!stop_listener_) {
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(fd_, &read_fds);
// 使用一个较短的超时,避免长时间阻塞,以便能及时检查 stop_listener_
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int activity = select(fd_ + 1, &read_fds, nullptr, nullptr, &timeout);
if (activity < 0 && errno != EINTR) {
if (!stop_listener_) std::cerr << "Error: select() failed." << std::endl;
break;
}
// 如果有数据可读
if (FD_ISSET(fd_, &read_fds)) {
ssize_t bytes_read = ::read(fd_, rx_buffer, sizeof(rx_buffer));
if (bytes_read > 0) {
// 将新数据追加到缓冲区
msg_buffer.insert(msg_buffer.end(), rx_buffer, rx_buffer + bytes_read);
} else if (bytes_read < 0) {
if (!stop_listener_) std::cerr << "Error: read() failed." << std::endl;
break;
}
}
// 【核心逻辑】循环处理缓冲区中的数据,直到数据不够一帧
while (true) {
size_t expected_len = parseMessageLength(msg_buffer);
// 如果能确定预期长度,并且缓冲区数据足够
if (expected_len > 0 && msg_buffer.size() >= expected_len) {
// 提取一个完整的消息
std::vector<uint8_t> complete_msg(msg_buffer.begin(), msg_buffer.begin() + expected_len);
// 调用回调函数处理消息
if (message_handler_) {
message_handler_(complete_msg);
}
// 从缓冲区头部删除已处理的消息
msg_buffer.erase(msg_buffer.begin(), msg_buffer.begin() + expected_len);
} else {
// 数据不够一帧,或无法判断帧长,跳出内层循环,等待更多数据
break;
}
}
}
}
uint16_t SerialPort::crc16(const uint8_t *data, size_t len) {
uint16_t crc = 0xFFFF;
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
\ No newline at end of file
// serial_port.hpp
#ifndef SERIAL_PORT_HPP
#define SERIAL_PORT_HPP
#include <string>
#include <vector>
#include <functional>
#include <thread>
#include <atomic>
#include <mutex>
// POSIX/Linux headers
#include <termios.h>
class SerialPort {
public:
// 定义消息处理回调函数的类型
using MessageHandler = std::function<void(const std::vector<uint8_t>&)>;
// 构造函数:传入串口设备名,如 "/dev/ttyUSB0"
explicit SerialPort(const std::string& port_name);
// 析构函数:自动关闭串口和监听线程
~SerialPort();
// 禁止拷贝和赋值
SerialPort(const SerialPort&) = delete;
SerialPort& operator=(const SerialPort&) = delete;
// 打开并配置串口
// baud_rate: 如 B115200, B9600 等
// returns: true on success, false on failure
bool open(speed_t baud_rate = B115200);
// 关闭串口
void close();
// 检查串口是否打开
bool isOpen() const;
// 发送数据(函数内部会自动计算并附加CRC16)
// data: 要发送的原始数据
// returns: true on success, false on failure
bool send(const std::vector<uint8_t>& data);
// 开始异步监听,当接收到完整消息时调用handler
void startListening(MessageHandler handler);
// 停止异步监听
void stopListening();
// 静态工具函数:CRC16校验计算
static uint16_t crc16(const uint8_t* data, size_t len);
private:
// 监听线程的主循环函数
void listenerLoop();
// 内部状态
std::string port_name_;
int fd_; // 文件描述符
std::atomic<bool> is_open_;
// 线程相关
std::thread listener_thread_;
std::atomic<bool> stop_listener_;
MessageHandler message_handler_;
std::mutex write_mutex_; // 保证发送操作的原子性
};
#endif // SERIAL_PORT_HPP
\ No newline at end of file
File added
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment