diff --git a/Makefile b/Makefile index f05487e..c934c91 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CXX = g++ -CXXFLAGS = -O3 -std=c++17 -Wall -Wextra -LIBS = -lSDL2 -lpthread +CXXFLAGS = -O3 -std=c++17 -Wall -Wextra -I/usr/local/include +LIBS = -lSDL2 -lpthread -llivox_lidar_sdk_shared TARGET = xbox_motor_control_cpp SRC = xbox_motor_control.cpp @@ -8,7 +8,7 @@ SRC = xbox_motor_control.cpp all: $(TARGET) $(TARGET): $(SRC) - $(CXX) $(CXXFLAGS) $(SRC) $(LIBS) -o $(TARGET) + $(CXX) $(CXXFLAGS) $(SRC) -o $(TARGET) $(LIBS) clean: rm -f $(TARGET) diff --git a/mid360s_config.json b/mid360s_config.json new file mode 100644 index 0000000..fb0e54c --- /dev/null +++ b/mid360s_config.json @@ -0,0 +1,21 @@ +{ + "Mid360s": { + "lidar_net_info" : { + "cmd_data_port" : 56100, + "push_msg_port" : 56200, + "point_data_port": 56300, + "imu_data_port" : 56400, + "log_data_port" : 56500 + }, + "host_net_info" : [ + { + "host_ip" : "192.168.1.5", + "cmd_data_port" : 56101, + "push_msg_port" : 56201, + "point_data_port": 56301, + "imu_data_port" : 56401, + "log_data_port" : 56501 + } + ] + } +} diff --git a/run_4wd.sh b/run_4wd.sh index e0ca132..bb49520 100755 --- a/run_4wd.sh +++ b/run_4wd.sh @@ -1,13 +1,13 @@ #!/usr/bin/env bash -# 1-Click Launch Script for C++ 4WD Motor Control +# 1-Click Launch Script for C++ 4WD Motor Control with Mid-360S LiDAR Avoidance PORT="${1:-/dev/ttyUSB0}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" || exit 1 echo "==================================================================" -echo " ๐Ÿš€ ZLAC8015D 4WD C++ ์ดˆ์ €์ง€์—ฐ ๋ชจํ„ฐ ์ œ์–ด ์›ํด๋ฆญ ๋Ÿฐ์น˜ ์‹œ์Šคํ…œ" +echo " ๐Ÿš€ ZLAC8015D 4WD + Livox Mid-360S ๋ผ์ด๋‹ค ์›ํด๋ฆญ ๋Ÿฐ์น˜ ์‹œ์Šคํ…œ" echo "==================================================================" # 1. USB Latency 1ms ๋‹จ์ถ• @@ -23,8 +23,14 @@ if [ ! -f "./xbox_motor_control_cpp" ]; then fi echo "==================================================================" -echo " [์‹œ์ž‘] C++ 100Hz ์ดˆ์ €์ง€์—ฐ 4WD ์กฐ์ด์Šคํ‹ฑ ์ œ์–ด ์‹œ์ž‘ ($PORT)" +echo " [์‹œ์ž‘] C++ 100Hz ์ดˆ์ €์ง€์—ฐ 4WD ์กฐ์ด์Šคํ‹ฑ + ๋ผ์ด๋‹ค ์žฅ์• ๋ฌผ ํšŒํ”ผ ์‹œ์ž‘ ($PORT)" +echo " - ๋ผ์ด๋‹ค ๊ธฐ๋Šฅ ํ† ๊ธ€: Xbox ์ปจํŠธ๋กค๋Ÿฌ X ๋ฒ„ํŠผ" +echo " - ์ •์ง€/์ข…๋ฃŒ: B ๋ฒ„ํŠผ ๋˜๋Š” Ctrl+C" echo "==================================================================" -# 3. C++ 4WD ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ -./xbox_motor_control_cpp --port "$PORT" +# 3. C++ 4WD ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ (์ถ”๊ฐ€ ์ธ์ž ์ „๋‹ฌ ๊ฐ€๋Šฅ) +if [ $# -gt 1 ]; then + ./xbox_motor_control_cpp --port "$PORT" "${@:2}" +else + ./xbox_motor_control_cpp --port "$PORT" +fi diff --git a/xbox_motor_control.cpp b/xbox_motor_control.cpp index de2234c..ffbf1ce 100644 --- a/xbox_motor_control.cpp +++ b/xbox_motor_control.cpp @@ -1,549 +1,1208 @@ -#include -#include -#include -#include -#include -#include +#include #include +#include +#include +#include #include #include -#include -#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include + +#include "livox_lidar_api.h" +#include "livox_lidar_def.h" // Global flag for signal handling volatile std::sig_atomic_t g_running = 1; void signalHandler(int signum) { - (void)signum; - g_running = 0; + (void)signum; + g_running = 0; } // Modbus RTU CRC16 calculation -uint16_t calcCRC16(const uint8_t* data, size_t len) { - uint16_t crc = 0xFFFF; - for (size_t i = 0; i < len; ++i) { - crc ^= data[i]; - for (int j = 0; j < 8; ++j) { - if (crc & 0x0001) { - crc >>= 1; - crc ^= 0xA001; - } else { - crc >>= 1; - } - } +uint16_t calcCRC16(const uint8_t *data, size_t len) { + uint16_t crc = 0xFFFF; + for (size_t i = 0; i < len; ++i) { + crc ^= data[i]; + for (int j = 0; j < 8; ++j) { + if (crc & 0x0001) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } } - return crc; + } + return crc; } class SerialPort { private: - int fd_; - std::string port_name_; + int fd_; + std::string port_name_; public: - SerialPort() : fd_(-1) {} - ~SerialPort() { closePort(); } + SerialPort() : fd_(-1) {} + ~SerialPort() { closePort(); } - bool openPort(const std::string& port_name, int baudrate = 115200) { - port_name_ = port_name; - fd_ = open(port_name.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); - if (fd_ < 0) { - std::cerr << "[์˜ค๋ฅ˜] C++ ํฌํŠธ ์—ด๊ธฐ ์‹คํŒจ: " << port_name << std::endl; - return false; - } - - fcntl(fd_, F_SETFL, 0); - - struct termios options; - tcgetattr(fd_, &options); - - speed_t speed = B115200; - switch (baudrate) { - case 9600: speed = B9600; break; - case 19200: speed = B19200; break; - case 38400: speed = B38400; break; - case 57600: speed = B57600; break; - default: speed = B115200; break; - } - - cfsetispeed(&options, speed); - cfsetospeed(&options, speed); - - options.c_cflag &= ~PARENB; // No parity - options.c_cflag &= ~CSTOPB; // 1 stop bit - options.c_cflag &= ~CSIZE; - options.c_cflag |= CS8; // 8 data bits - options.c_cflag &= ~CRTSCTS; // No hardware flow control - options.c_cflag |= CREAD | CLOCAL; - - options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); - options.c_iflag &= ~(IXON | IXOFF | IXANY | IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); - options.c_oflag &= ~OPOST; - - options.c_cc[VMIN] = 0; - options.c_cc[VTIME] = 1; // 0.1s timeout - - tcsetattr(fd_, TCSANOW, &options); - tcflush(fd_, TCIOFLUSH); - return true; + bool openPort(const std::string &port_name, int baudrate = 115200) { + port_name_ = port_name; + fd_ = open(port_name.c_str(), O_RDWR | O_NOCTTY | O_NDELAY); + if (fd_ < 0) { + std::cerr << "[์˜ค๋ฅ˜] C++ ํฌํŠธ ์—ด๊ธฐ ์‹คํŒจ: " << port_name << std::endl; + return false; } - void closePort() { - if (fd_ >= 0) { - close(fd_); - fd_ = -1; - } + fcntl(fd_, F_SETFL, 0); + + struct termios options; + tcgetattr(fd_, &options); + + speed_t speed = B115200; + switch (baudrate) { + case 9600: + speed = B9600; + break; + case 19200: + speed = B19200; + break; + case 38400: + speed = B38400; + break; + case 57600: + speed = B57600; + break; + default: + speed = B115200; + break; } - bool writeReg(uint8_t slave, uint16_t reg, uint16_t val) { - uint8_t pkt[8]; - pkt[0] = slave; - pkt[1] = 0x06; - pkt[2] = (reg >> 8) & 0xFF; - pkt[3] = reg & 0xFF; - pkt[4] = (val >> 8) & 0xFF; - pkt[5] = val & 0xFF; + cfsetispeed(&options, speed); + cfsetospeed(&options, speed); - uint16_t crc = calcCRC16(pkt, 6); - pkt[6] = crc & 0xFF; - pkt[7] = (crc >> 8) & 0xFF; + options.c_cflag &= ~PARENB; // No parity + options.c_cflag &= ~CSTOPB; // 1 stop bit + options.c_cflag &= ~CSIZE; + options.c_cflag |= CS8; // 8 data bits + options.c_cflag &= ~CRTSCTS; // No hardware flow control + options.c_cflag |= CREAD | CLOCAL; - tcflush(fd_, TCIOFLUSH); - ssize_t written = write(fd_, pkt, 8); - if (written != 8) return false; + options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + options.c_iflag &= ~(IXON | IXOFF | IXANY | IGNBRK | BRKINT | PARMRK | + ISTRIP | INLCR | IGNCR | ICRNL); + options.c_oflag &= ~OPOST; - if (slave == 0) return true; // Broadcast + options.c_cc[VMIN] = 0; + options.c_cc[VTIME] = 1; // 0.1s timeout - uint8_t rx_buf[8]; - ssize_t rx_bytes = 0; - auto start = std::chrono::steady_clock::now(); - while (rx_bytes < 8) { - ssize_t res = read(fd_, rx_buf + rx_bytes, 8 - rx_bytes); - if (res > 0) rx_bytes += res; - auto now = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast(now - start).count() > 50) break; - std::this_thread::sleep_for(std::chrono::microseconds(500)); - } - return rx_bytes == 8; + tcsetattr(fd_, TCSANOW, &options); + tcflush(fd_, TCIOFLUSH); + return true; + } + + void closePort() { + if (fd_ >= 0) { + close(fd_); + fd_ = -1; + } + } + + bool writeReg(uint8_t slave, uint16_t reg, uint16_t val) { + uint8_t pkt[8]; + pkt[0] = slave; + pkt[1] = 0x06; + pkt[2] = (reg >> 8) & 0xFF; + pkt[3] = reg & 0xFF; + pkt[4] = (val >> 8) & 0xFF; + pkt[5] = val & 0xFF; + + uint16_t crc = calcCRC16(pkt, 6); + pkt[6] = crc & 0xFF; + pkt[7] = (crc >> 8) & 0xFF; + + tcflush(fd_, TCIOFLUSH); + ssize_t written = write(fd_, pkt, 8); + if (written != 8) + return false; + + if (slave == 0) + return true; // Broadcast + + uint8_t rx_buf[8]; + ssize_t rx_bytes = 0; + auto start = std::chrono::steady_clock::now(); + while (rx_bytes < 8) { + ssize_t res = read(fd_, rx_buf + rx_bytes, 8 - rx_bytes); + if (res > 0) + rx_bytes += res; + auto now = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast(now - start) + .count() > 50) + break; + std::this_thread::sleep_for(std::chrono::microseconds(500)); + } + if (rx_bytes != 8) + return false; + + // CRC ๊ฒ€์ฆ: ์†์ƒ๋œ ์‘๋‹ต์„ ์ •์ƒ์œผ๋กœ ์˜ค์ธํ•˜์ง€ ์•Š๋„๋ก ํ™•์ธ + uint16_t resp_crc = calcCRC16(rx_buf, 6); + uint16_t recv_crc = static_cast(rx_buf[6]) | + (static_cast(rx_buf[7]) << 8); + return resp_crc == recv_crc && rx_buf[0] == slave; + } + + bool writeRegs(uint8_t slave, uint16_t reg, + const std::vector &vals) { + size_t count = vals.size(); + size_t pkt_len = 7 + count * 2 + 2; + std::vector pkt(pkt_len); + + pkt[0] = slave; + pkt[1] = 0x10; + pkt[2] = (reg >> 8) & 0xFF; + pkt[3] = reg & 0xFF; + pkt[4] = (count >> 8) & 0xFF; + pkt[5] = count & 0xFF; + pkt[6] = static_cast(count * 2); + + for (size_t i = 0; i < count; ++i) { + pkt[7 + i * 2] = (vals[i] >> 8) & 0xFF; + pkt[8 + i * 2] = vals[i] & 0xFF; } - bool writeRegs(uint8_t slave, uint16_t reg, const std::vector& vals) { - size_t count = vals.size(); - size_t pkt_len = 7 + count * 2 + 2; - std::vector pkt(pkt_len); + uint16_t crc = calcCRC16(pkt.data(), pkt_len - 2); + pkt[pkt_len - 2] = crc & 0xFF; + pkt[pkt_len - 1] = (crc >> 8) & 0xFF; - pkt[0] = slave; - pkt[1] = 0x10; - pkt[2] = (reg >> 8) & 0xFF; - pkt[3] = reg & 0xFF; - pkt[4] = (count >> 8) & 0xFF; - pkt[5] = count & 0xFF; - pkt[6] = static_cast(count * 2); + tcflush(fd_, TCIOFLUSH); + ssize_t written = write(fd_, pkt.data(), pkt_len); + if (written != static_cast(pkt_len)) + return false; - for (size_t i = 0; i < count; ++i) { - pkt[7 + i * 2] = (vals[i] >> 8) & 0xFF; - pkt[8 + i * 2] = vals[i] & 0xFF; - } + if (slave == 0) + return true; - uint16_t crc = calcCRC16(pkt.data(), pkt_len - 2); - pkt[pkt_len - 2] = crc & 0xFF; - pkt[pkt_len - 1] = (crc >> 8) & 0xFF; + uint8_t rx_buf[8]; + ssize_t rx_bytes = 0; + auto start = std::chrono::steady_clock::now(); + while (rx_bytes < 8) { + ssize_t res = read(fd_, rx_buf + rx_bytes, 8 - rx_bytes); + if (res > 0) + rx_bytes += res; + auto now = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast(now - start) + .count() > 50) + break; + std::this_thread::sleep_for(std::chrono::microseconds(500)); + } + if (rx_bytes != 8) + return false; - tcflush(fd_, TCIOFLUSH); - ssize_t written = write(fd_, pkt.data(), pkt_len); - if (written != static_cast(pkt_len)) return false; + uint16_t resp_crc = calcCRC16(rx_buf, 6); + uint16_t recv_crc = static_cast(rx_buf[6]) | + (static_cast(rx_buf[7]) << 8); + return resp_crc == recv_crc && rx_buf[0] == slave; + } - if (slave == 0) return true; + bool readRegs(uint8_t slave, uint16_t reg, uint16_t count, + std::vector &out_vals) { + uint8_t pkt[8]; + pkt[0] = slave; + pkt[1] = 0x03; + pkt[2] = (reg >> 8) & 0xFF; + pkt[3] = reg & 0xFF; + pkt[4] = (count >> 8) & 0xFF; + pkt[5] = count & 0xFF; - uint8_t rx_buf[8]; - ssize_t rx_bytes = 0; - auto start = std::chrono::steady_clock::now(); - while (rx_bytes < 8) { - ssize_t res = read(fd_, rx_buf + rx_bytes, 8 - rx_bytes); - if (res > 0) rx_bytes += res; - auto now = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast(now - start).count() > 50) break; - std::this_thread::sleep_for(std::chrono::microseconds(500)); - } - return rx_bytes == 8; + uint16_t crc = calcCRC16(pkt, 6); + pkt[6] = crc & 0xFF; + pkt[7] = (crc >> 8) & 0xFF; + + tcflush(fd_, TCIOFLUSH); + if (write(fd_, pkt, 8) != 8) + return false; + + size_t expected_bytes = 5 + count * 2; + std::vector rx_buf(expected_bytes); + size_t rx_bytes = 0; + + auto start = std::chrono::steady_clock::now(); + while (rx_bytes < expected_bytes) { + ssize_t res = + read(fd_, rx_buf.data() + rx_bytes, expected_bytes - rx_bytes); + if (res > 0) + rx_bytes += res; + auto now = std::chrono::steady_clock::now(); + // 15ms -> 25ms: 100Hz(10ms ์ฃผ๊ธฐ) ๋ฃจํ”„ ๋‚ด์—์„œ ์‘๋‹ต์„ ์•ˆ์ •์ ์œผ๋กœ ๋ฐ›๊ธฐ ์œ„ํ•œ + // ์—ฌ์œ  ํ™•๋ณด + if (std::chrono::duration_cast(now - start) + .count() > 25) + break; + std::this_thread::sleep_for(std::chrono::microseconds(100)); } - bool readRegs(uint8_t slave, uint16_t reg, uint16_t count, std::vector& out_vals) { - uint8_t pkt[8]; - pkt[0] = slave; - pkt[1] = 0x03; - pkt[2] = (reg >> 8) & 0xFF; - pkt[3] = reg & 0xFF; - pkt[4] = (count >> 8) & 0xFF; - pkt[5] = count & 0xFF; - - uint16_t crc = calcCRC16(pkt, 6); - pkt[6] = crc & 0xFF; - pkt[7] = (crc >> 8) & 0xFF; - - tcflush(fd_, TCIOFLUSH); - if (write(fd_, pkt, 8) != 8) return false; - - size_t expected_bytes = 5 + count * 2; - std::vector rx_buf(expected_bytes); - size_t rx_bytes = 0; - - auto start = std::chrono::steady_clock::now(); - while (rx_bytes < expected_bytes) { - ssize_t res = read(fd_, rx_buf.data() + rx_bytes, expected_bytes - rx_bytes); - if (res > 0) rx_bytes += res; - auto now = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast(now - start).count() > 15) break; - std::this_thread::sleep_for(std::chrono::microseconds(100)); - } - - if (rx_bytes == expected_bytes && rx_buf[0] == slave && rx_buf[1] == 0x03) { - out_vals.resize(count); - for (uint16_t i = 0; i < count; ++i) { - out_vals[i] = (static_cast(rx_buf[3 + i * 2]) << 8) | rx_buf[4 + i * 2]; - } - return true; - } + if (rx_bytes == expected_bytes && rx_buf[0] == slave && rx_buf[1] == 0x03) { + // CRC ๊ฒ€์ฆ ์ถ”๊ฐ€: ๋…ธ์ด์ฆˆ๋กœ ์†์ƒ๋œ ์‘๋‹ต์„ ์ •์ƒ ๋ฐ์ดํ„ฐ๋กœ ์˜ค์ธํ•˜์ง€ ์•Š๋„๋ก + // ๋ฐฉ์ง€ + uint16_t resp_crc = calcCRC16(rx_buf.data(), expected_bytes - 2); + uint16_t recv_crc = + static_cast(rx_buf[expected_bytes - 2]) | + (static_cast(rx_buf[expected_bytes - 1]) << 8); + if (resp_crc != recv_crc) return false; + + out_vals.resize(count); + for (uint16_t i = 0; i < count; ++i) { + out_vals[i] = + (static_cast(rx_buf[3 + i * 2]) << 8) | rx_buf[4 + i * 2]; + } + return true; } + return false; + } }; class MotorDriver { private: - SerialPort* port_; - uint8_t slave_id_; + SerialPort *port_; + uint8_t slave_id_; public: - MotorDriver(SerialPort* port, uint8_t slave_id) : port_(port), slave_id_(slave_id) {} + MotorDriver(SerialPort *port, uint8_t slave_id) + : port_(port), slave_id_(slave_id) {} - bool initDriver(uint16_t acl_ms = 500, uint16_t dcl_ms = 500) { - port_->writeReg(slave_id_, 0x200E, 0x0006); // Alarm clear - std::this_thread::sleep_for(std::chrono::milliseconds(30)); - port_->writeReg(slave_id_, 0x200E, 0x0007); // Disable - std::this_thread::sleep_for(std::chrono::milliseconds(30)); - port_->writeReg(slave_id_, 0x200D, 3); // Velocity mode - std::this_thread::sleep_for(std::chrono::milliseconds(30)); + bool initDriver(uint16_t acl_ms = 500, uint16_t dcl_ms = 500) { + port_->writeReg(slave_id_, 0x200E, 0x0006); // Alarm clear + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + port_->writeReg(slave_id_, 0x200E, 0x0007); // Disable + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + port_->writeReg(slave_id_, 0x200D, 3); // Velocity mode + std::this_thread::sleep_for(std::chrono::milliseconds(30)); - port_->writeRegs(slave_id_, 0x2080, {acl_ms, acl_ms}); // Accel - std::this_thread::sleep_for(std::chrono::milliseconds(30)); - port_->writeRegs(slave_id_, 0x2082, {dcl_ms, dcl_ms}); // Decel - std::this_thread::sleep_for(std::chrono::milliseconds(30)); + port_->writeRegs(slave_id_, 0x2080, {acl_ms, acl_ms}); // Accel + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + port_->writeRegs(slave_id_, 0x2082, {dcl_ms, dcl_ms}); // Decel + std::this_thread::sleep_for(std::chrono::milliseconds(30)); - port_->writeReg(slave_id_, 0x200E, 0x0008); // Enable - std::this_thread::sleep_for(std::chrono::milliseconds(30)); - setBrakes(true); - return true; - } - - void setBrakes(bool lock) { - uint16_t val = lock ? 1 : 0; - port_->writeReg(slave_id_, 0x201A, val); - port_->writeReg(slave_id_, 0x201B, val); - } - - void setRPMs(float l_rpm, float r_rpm) { - int16_t l_val = static_cast(std::clamp(l_rpm, -3000.0f, 3000.0f)); - int16_t r_val = static_cast(std::clamp(r_rpm, -3000.0f, 3000.0f)); - port_->writeRegs(slave_id_, 0x2088, {static_cast(l_val), static_cast(r_val)}); - } - - bool readFeedback(float& l_fb, float& r_fb, int32_t& l_tick, int32_t& r_tick) { - std::vector regs; - if (port_->readRegs(slave_id_, 0x20A7, 6, regs)) { - uint32_t val_l = ((static_cast(regs[0]) & 0xFFFF) << 16) | (regs[1] & 0xFFFF); - l_tick = (val_l < 0x80000000U) ? static_cast(val_l) : static_cast(val_l - 0x100000000ULL); - - uint32_t val_r = ((static_cast(regs[2]) & 0xFFFF) << 16) | (regs[3] & 0xFFFF); - r_tick = (val_r < 0x80000000U) ? static_cast(val_r) : static_cast(val_r - 0x100000000ULL); - - int16_t vl = static_cast(regs[4]); - int16_t vr = static_cast(regs[5]); - l_fb = static_cast(vl); - r_fb = static_cast(vr); - return true; - } - return false; + port_->writeReg(slave_id_, 0x200E, 0x0008); // Enable + std::this_thread::sleep_for(std::chrono::milliseconds(30)); + setBrakes(true); + return true; + } + + void setBrakes(bool lock) { + uint16_t val = lock ? 1 : 0; + port_->writeReg(slave_id_, 0x201A, val); + port_->writeReg(slave_id_, 0x201B, val); + } + + void setRPMs(float l_rpm, float r_rpm) { + int16_t l_val = static_cast(std::clamp(l_rpm, -3000.0f, 3000.0f)); + int16_t r_val = static_cast(std::clamp(r_rpm, -3000.0f, 3000.0f)); + port_->writeRegs( + slave_id_, 0x2088, + {static_cast(l_val), static_cast(r_val)}); + } + + bool readFeedback(float &l_fb, float &r_fb, int32_t &l_tick, + int32_t &r_tick, float &l_torque_a, float &r_torque_a) { + std::vector regs; + // 0x20A7~0x20AE 8๋ ˆ์ง€์Šคํ„ฐ ์—ฐ์† ์ฝ๊ธฐ: ํฌ์ง€์…˜ ํ‹ฑ, RPM ํ”ผ๋“œ๋ฐฑ์— ์ด์–ด + // ์‹ค์ œ ํ† ํฌ(์ „๋ฅ˜, 0x20AD/0x20AE)๊นŒ์ง€ ํ•œ ๋ฒˆ์˜ ํ†ต์‹ ์œผ๋กœ ํ™•๋ณด. + // ๋ฌด๋ถ€ํ•˜(๊ณต์ค‘์— ๋œฌ) ๋ฐ”ํ€ด๋Š” ์ „๋ฅ˜๊ฐ€ ๊ธ‰๊ฒฉํžˆ ๋‚ฎ์•„์ง€๋ฏ€๋กœ ์Šฌ๋ฆฝ ์ง„๋‹จ์— ์‚ฌ์šฉ. + if (port_->readRegs(slave_id_, 0x20A7, 8, regs)) { + // ์ˆ˜์ •: uint32_t -> int32_t ์บ์ŠคํŒ…์€ 2์˜ ๋ณด์ˆ˜ ํ‘œํ˜„์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ๋ถ€ํ˜ธ๊ฐ€ + // ์žฌํ•ด์„๋จ. ๊ธฐ์กด์˜ "val - 0x100000000ULL" ๋ฐฉ์‹์€ uint64_t ์Šน๊ฒฉ ํ›„ + // int32_t๋กœ ์ถ•์†Œ ์บ์ŠคํŒ…ํ•˜๋Š” ๊ตฌํ˜„์ •์˜ ๋™์ž‘(ํ˜„์‹ค์ ์œผ๋กœ๋Š” ๋Œ€๋ถ€๋ถ„ ๋™์ž‘ํ•˜์ง€๋งŒ + // ๋ช…ํ™•์„ฑ/์ด์‹์„ฑ์ด ๋–จ์–ด์ง)์ด๋ผ ์ œ๊ฑฐํ•จ. + uint32_t val_l = (static_cast(regs[0]) << 16) | regs[1]; + l_tick = static_cast(val_l); + + uint32_t val_r = (static_cast(regs[2]) << 16) | regs[3]; + r_tick = static_cast(val_r); + + int16_t vl = static_cast(regs[4]); + int16_t vr = static_cast(regs[5]); + l_fb = static_cast(vl) * 0.1f; // 0.1RPM ๋‹จ์œ„ -> RPM + r_fb = static_cast(vr) * 0.1f; + + int16_t tl = static_cast(regs[6]); + int16_t tr = static_cast(regs[7]); + l_torque_a = static_cast(tl) * 0.1f; // 0.1A ๋‹จ์œ„ -> A + r_torque_a = static_cast(tr) * 0.1f; + return true; } + return false; + } }; -float applyDeadzone(float val, float deadzone = 0.1f) { - if (std::abs(val) < deadzone) return 0.0f; - float sign = (val > 0.0f) ? 1.0f : -1.0f; - float norm = (std::abs(val) - deadzone) / (1.0f - deadzone); - // ์„ ํ˜• ์ปค๋ธŒ (Linear Curve): ํ† ํฌ ์ €ํ•˜ ์—†์ด ์Šคํ‹ฑ ์กฐ์ž‘์— 100% ์ฆ‰๊ฐ ์ถœ๋ ฅ์„ ๋ณด์žฅ - return sign * norm; -} +// ----------------------------------------------------------------------------- +// Livox Mid-360S Single LiDAR Obstacle Detection & Avoidance Class +// ----------------------------------------------------------------------------- +struct ObstacleStatus { + float min_dist_front = 999.0f; // m + float min_dist_rear = 999.0f; // m + float min_dist_left = 999.0f; // m + float min_dist_right = 999.0f; // m + bool connected = false; + std::chrono::steady_clock::time_point last_update; +}; -int main(int argc, char** argv) { - std::signal(SIGINT, signalHandler); - std::signal(SIGTERM, signalHandler); +class LidarObstacleDetector { +private: + std::string config_path_; + std::atomic initialized_{false}; + std::mutex status_mutex_; + ObstacleStatus status_; - std::string port1 = "/dev/ttyUSB0"; - std::string port2 = ""; - uint8_t id1 = 1; - uint8_t id2 = 2; - bool bcast_mode = false; - float max_v = 0.30f; // m/s - float max_spin_v = 0.30f; // m/s - float track_width = 0.576f; // ์ขŒ์šฐ ๋ฐ”ํ€ด ์ค‘์‹ฌ ๊ฑฐ๋ฆฌ 576mm - float wheelbase = 0.3684f; // ์ „ํ›„ ๋ฐ”ํ€ด ์ค‘์‹ฌ ๊ฑฐ๋ฆฌ 368.4mm - float wheel_radius = 0.131517f; // ์‚ฌ์šฉ์ž ์‹ค์ธก ์ •๋ฐ€ 10.35์ธ์น˜ ํœ  ๋ฐ˜์ง€๋ฆ„ (131.517mm) - float accel_rate = 120.0f; // RPM/s - float decel_rate = 180.0f; // RPM/s (์—ญ๊ธฐ์ „๋ ฅ ์„œ์ง€ ๋ฐฉ์ง€ ๋ฐ ์†Œํ”„ํŠธ ์ •์ง€ ํŠœ๋‹) - float bumper_offset = 0.045f; // ๋ฐ”ํ€ด ์ถ•์—์„œ ๋กœ๋ด‡ ๋งจ ์•ž ๋ฒ”ํผ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ (45mm) + // Filtering & ROI parameters + float min_z_ = -0.40f; // m (Z axis relative to LiDAR after tilt rotation) + float max_z_ = 0.80f; // m + float min_r_ = 0.15f; // m (blind spot / chassis) + float max_r_ = 3.50f; // m (max range) + float robot_half_width_ = + 0.255f; // m (robot width 0.410m / 2 + 0.05m margin = 0.255m) + float pitch_deg_ = 17.0f; // deg (LiDAR downward tilt angle) + float lidar_height_ = 0.460f; // m (LiDAR height above ground) + float lidar_x_offset_ = + 0.250f; // m (LiDAR forward offset 250mm from robot center) - for (int i = 1; i < argc; ++i) { - std::string arg = argv[i]; - if (arg == "--port" && i + 1 < argc) port1 = argv[++i]; - else if (arg == "--port1" && i + 1 < argc) port1 = argv[++i]; - else if (arg == "--port2" && i + 1 < argc) port2 = argv[++i]; - else if (arg == "--id1" && i + 1 < argc) id1 = static_cast(std::stoi(argv[++i])); - else if (arg == "--id2" && i + 1 < argc) id2 = static_cast(std::stoi(argv[++i])); - else if (arg == "--track_width" && i + 1 < argc) track_width = std::stof(argv[++i]); - else if (arg == "--wheelbase" && i + 1 < argc) wheelbase = std::stof(argv[++i]); - else if (arg == "--radius" && i + 1 < argc) wheel_radius = std::stof(argv[++i]); - else if (arg == "--bumper" && i + 1 < argc) bumper_offset = std::stof(argv[++i]); - else if (arg == "--accel" && i + 1 < argc) accel_rate = std::stof(argv[++i]); - else if (arg == "--decel" && i + 1 < argc) decel_rate = std::stof(argv[++i]); - else if (arg == "--bcast" || arg == "--broadcast") bcast_mode = true; + float cos_pitch_ = std::cos(17.0f * 3.14159265f / 180.0f); + float sin_pitch_ = std::sin(17.0f * 3.14159265f / 180.0f); + +public: + static LidarObstacleDetector *instance_; + + LidarObstacleDetector() { instance_ = this; } + + ~LidarObstacleDetector() { stop(); } + + void setParams(float min_z, float max_z, float robot_half_width, + float pitch_deg = 17.0f, float lidar_height = 0.460f, + float lidar_x_offset = 0.250f) { + min_z_ = min_z; + max_z_ = max_z; + robot_half_width_ = robot_half_width; + pitch_deg_ = pitch_deg; + lidar_height_ = lidar_height; + lidar_x_offset_ = lidar_x_offset; + + float pitch_rad = pitch_deg_ * 3.14159265358979323846f / 180.0f; + cos_pitch_ = std::cos(pitch_rad); + sin_pitch_ = std::sin(pitch_rad); + } + + bool init(const std::string &config_path) { + config_path_ = config_path; + if (!LivoxLidarSdkInit(config_path_.c_str())) { + std::cerr << "[๊ฒฝ๊ณ ] Livox Mid-360S SDK2 ์ดˆ๊ธฐํ™” ์‹คํŒจ (" << config_path_ + << ")\n"; + return false; } - constexpr float PI_VAL = 3.14159265358979323846f; - float rpm_per_ms = 60.0f / (2.0f * PI_VAL * wheel_radius); - - // 4WD ์œ ํšจ ์„ ํšŒ ๊ณ„์ˆ˜ (Track Width 576mm, Wheelbase 368.4mm ์ •๋ฐ€ ๊ธฐํ•˜ํ•™) - float effective_w = std::sqrt(track_width * track_width + wheelbase * wheelbase); - float k_skid = (track_width + (wheelbase * wheelbase) / track_width) / 2.0f; + SetLivoxLidarPointCloudCallBack(PointCloudCallbackStatic, nullptr); + SetLivoxLidarInfoChangeCallback(InfoChangeCallbackStatic, nullptr); - std::cout << "\n==================================================================\n"; - std::cout << " [C++17 4WD ์ •๋ฐ€ ์ง์ง„/๊ฐ์† ๋ณด์ •] ZLAC8015D 4๋ชจํ„ฐ ๋™๊ธฐํ™” ์‹œ์Šคํ…œ\n"; - std::cout << " - ์ขŒ์šฐ ๋ฐ”ํ€ด ๊ฑฐ๋ฆฌ (Track Width): " << track_width * 1000.0f << " mm\n"; - std::cout << " - ์ „ํ›„ ๋ฐ”ํ€ด ๊ฑฐ๋ฆฌ (Wheelbase) : " << wheelbase * 1000.0f << " mm\n"; - std::cout << " - ๋ฒ”ํผ ์˜คํ”„์…‹ (Bumper Offset) : " << bumper_offset * 1000.0f << " mm\n"; - std::cout << " - ์‹ค์‹œ๊ฐ„ ์ง์ง„ ์—”์ฝ”๋” ์ž๋™ ์ˆ˜ํ‰ ๋ณด์ • (Straight Auto-Correction) ํ™œ์„ฑํ™”\n"; - std::cout << "==================================================================\n"; + initialized_ = true; + std::cout << "[์ •๋ณด] Livox Mid-360S ๋ผ์ด๋‹ค ์ˆ˜์‹  ์‹œ์ž‘ (์„ค์ •: " + << config_path_ << ", ํ”ผ์น˜ ๊ธฐ์šธ์ž„: " << pitch_deg_ + << "๋„, ์„ค์น˜๋†’์ด: " << lidar_height_ * 1000.0f + << "mm, ์ „๋ฐฉ ์˜คํ”„์…‹: " << lidar_x_offset_ * 1000.0f << "mm)\n"; + return true; + } - if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { - std::cerr << "[์˜ค๋ฅ˜] SDL ์กฐ์ด์Šคํ‹ฑ ์ดˆ๊ธฐํ™” ์‹คํŒจ: " << SDL_GetError() << std::endl; - return 1; + void stop() { + if (initialized_) { + LivoxLidarSdkUninit(); + initialized_ = false; + std::cout << "[์ •๋ณด] Livox Mid-360S ๋ผ์ด๋‹ค ์ˆ˜์‹ ๊ธฐ ์ข…๋ฃŒ.\n"; } + } - if (SDL_NumJoysticks() < 1) { - std::cerr << "[๊ฒฝ๊ณ ] ๋ฌด์„  Xbox ์ปจํŠธ๋กค๋Ÿฌ(์กฐ์ด์Šคํ‹ฑ)๊ฐ€ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.\n"; - SDL_Quit(); - return 1; - } + static void PointCloudCallbackStatic(const uint32_t handle, + const uint8_t dev_type, + LivoxLidarEthernetPacket *data, + void *client_data) { + (void)dev_type; + (void)client_data; + if (instance_) + instance_->handlePointCloud(handle, data); + } - SDL_Joystick* joystick = SDL_JoystickOpen(0); - if (!joystick) { - std::cerr << "[์˜ค๋ฅ˜] ์กฐ์ด์Šคํ‹ฑ ์—ด๊ธฐ ์‹คํŒจ!\n"; - SDL_Quit(); - return 1; - } - std::cout << "[์ •๋ณด] C++ Xbox ์ปจํŠธ๋กค๋Ÿฌ ์—ฐ๊ฒฐ ์„ฑ๊ณต: " << SDL_JoystickName(joystick) << std::endl; + static void InfoChangeCallbackStatic(const uint32_t handle, + const LivoxLidarInfo *info, + void *client_data) { + (void)client_data; + if (!info) + return; + std::cout << "\n[๋ผ์ด๋‹ค ์—ฐ๊ฒฐ ์™„๋ฃŒ] Handle: " << handle + << ", SN: " << info->sn << ", IP: " << info->lidar_ip + << std::endl; + SetLivoxLidarWorkMode(handle, kLivoxLidarNormal, nullptr, nullptr); + } - SerialPort sp1, sp2; - if (!sp1.openPort(port1)) return 1; - std::cout << "[์ •๋ณด] RS485 ํฌํŠธ1 (" << port1 << ") ์˜คํ”ˆ ์„ฑ๊ณต.\n"; + void handlePointCloud(uint32_t handle, LivoxLidarEthernetPacket *packet) { + (void)handle; + if (!packet) + return; - SerialPort* sp2_ptr = &sp1; - if (!port2.empty()) { - if (sp2.openPort(port2)) { - sp2_ptr = &sp2; - std::cout << "[์ •๋ณด] RS485 ํฌํŠธ2 (" << port2 << ") ์˜คํ”ˆ ์„ฑ๊ณต.\n"; + float curr_front = 999.0f; + float curr_rear = 999.0f; + float curr_left = 999.0f; + float curr_right = 999.0f; + + auto parsePoint = [&](float raw_x, float raw_y, float raw_z) { + // Apply 17 degree pitch rotation (LiDAR tilted downward towards ground) + float x = raw_x * cos_pitch_ + raw_z * sin_pitch_; + float y = raw_y; + float z = -raw_x * sin_pitch_ + raw_z * cos_pitch_; + + if (z < min_z_ || z > max_z_) + return; // Filter ground floor and ceiling + float r = std::sqrt(x * x + y * y); + if (r < min_r_ || r > max_r_) + return; + + // Coordinate Frame: +X Forward, +Y Left, +Z Up + // 1. Front Obstacle Zone + if (x > 0.05f && std::abs(y) <= robot_half_width_) { + if (x < curr_front) + curr_front = x; + } + // 2. Rear Obstacle Zone + else if (x < -0.05f && std::abs(y) <= robot_half_width_) { + if (-x < curr_rear) + curr_rear = -x; + } + + // 3. Side Obstacle Zones (Front Lookahead window: X in [0.05m, 1.8m]) + if (x > 0.05f && x < 1.8f) { + if (y > 0.10f && y < 1.2f) { // Left Side + float dist = std::sqrt(x * x + y * y); + if (dist < curr_left) + curr_left = dist; + } else if (y < -0.10f && y > -1.2f) { // Right Side + float dist = std::sqrt(x * x + y * y); + if (dist < curr_right) + curr_right = dist; } - } - - MotorDriver driver_front(&sp1, bcast_mode ? 0 : id1); - driver_front.initDriver(150, 150); - - MotorDriver* driver_rear_ptr = nullptr; - MotorDriver driver_rear(sp2_ptr, bcast_mode ? 0 : id2); - if (!bcast_mode) { - driver_rear.initDriver(150, 150); - driver_rear_ptr = &driver_rear; - } - - std::cout << "\n[์ •๋ณด] C++ 100Hz ์ดˆ์ €์ง€์—ฐ ๋ฃจํ”„๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. (์ข…๋ฃŒ: B ๋ฒ„ํŠผ ๋˜๋Š” Ctrl+C)\n\n"; - - float target_fl = 0.0f, target_fr = 0.0f; - float target_rl = 0.0f, target_rr = 0.0f; - - float cmd_fl = 0.0f, cmd_fr = 0.0f; - float cmd_rl = 0.0f, cmd_rr = 0.0f; - - const float loop_hz = 100.0f; - const float dt = 1.0f / loop_hz; - const float max_accel_step = accel_rate * dt; - const float max_decel_step = decel_rate * dt; - - std::string state = "STOPPED"; - bool trip_initialized = false; - int32_t start_fl = 0, start_fr = 0, start_rl = 0, start_rr = 0; - - // S-Curve ๋ถ€๋“œ๋Ÿฌ์šด ๊ฐ€์† ๋ณด์ • ๋žŒ๋‹ค ํ•จ์ˆ˜ (์ขŒ/์šฐ ๋ชจํ„ฐ ๋Œ€์นญ ๊ฐ€์† ๋ณด์žฅ) - auto sCurveStep = [](float current, float target, float max_accel, float max_decel) { - float diff = target - current; - if (std::abs(diff) < 0.01f) return target; - - // ์†๋„ ์ ˆ๋Œ“๊ฐ’ ํฌ๊ธฐ๊ฐ€ ์ปค์ง€๋Š” ์ค‘์ด๋ฉด ๊ฐ€์†(accel), ์ค„์–ด๋“œ๋Š” ์ค‘์ด๋ฉด ๊ฐ์†(decel) ์ ์šฉ - bool is_accelerating = std::abs(target) > std::abs(current); - float step_rate = is_accelerating ? max_accel : max_decel; - float step = (diff > 0.0f) ? step_rate : -step_rate; - - float ratio = std::clamp(std::abs(diff) / 60.0f, 0.15f, 1.0f); - float smooth = ratio * ratio * (3.0f - 2.0f * ratio); - float delta = step * smooth; - if (std::abs(delta) > std::abs(diff)) return target; - return current + delta; + } }; - while (g_running) { - SDL_Event event; - while (SDL_PollEvent(&event)) { - if (event.type == SDL_JOYBUTTONDOWN) { - if (event.jbutton.button == 0) { // A ๋ฒ„ํŠผ ๋ˆ„๋ฅด๋ฉด ๊ฑฐ๋ฆฌ 0m ๋ฆฌ์…‹ - trip_initialized = false; - } else if (event.jbutton.button == 1 || event.jbutton.button == 6) { // B or Back - g_running = 0; - } - } - } - - Sint16 lx_raw = SDL_JoystickGetAxis(joystick, 0); - Sint16 ly_raw = SDL_JoystickGetAxis(joystick, 1); - Sint16 rx_raw = SDL_JoystickGetAxis(joystick, 3); - - float ly = applyDeadzone(-static_cast(ly_raw) / 32767.0f); - float lx = applyDeadzone(static_cast(lx_raw) / 32767.0f); - float rx = applyDeadzone(static_cast(rx_raw) / 32767.0f); - - // ์ง์ง„ ์ œ์–ด ๋ฝ (Straight-Drive Lock): ์Šคํ‹ฑ ์ขŒ์šฐ 15% ์ด๋‚ด ๊ธฐ์šธ์ž„์€ 100% ์นผ์ง์ง„ ๊ณ ์ •! - if (std::abs(lx) < 0.15f) { - lx = 0.0f; - } - if (std::abs(rx) < 0.15f) { - rx = 0.0f; - } - - float v_x = ly * max_v; // ์„ ์†๋„ m/s - float omega = 0.0f; // ๊ฐ์†๋„ rad/s - - if (std::abs(rx) > 0.0f) { - // ์ œ์ž๋ฆฌ ํšŒ์ „ (Spin Turn) - omega = -rx * (max_spin_v / (effective_w / 2.0f)); - float v_l = -omega * (effective_w / 2.0f); - float v_r = +omega * (effective_w / 2.0f); - - target_fl = v_l * rpm_per_ms; - target_fr = -v_r * rpm_per_ms; - target_rl = v_l * rpm_per_ms; - target_rr = -v_r * rpm_per_ms; - } else { - // ์ง์ง„ ๋ฐ ์ปค๋ธŒ ์ฐจ๋™ ์„ ํšŒ (Kinematics) - omega = -lx * (max_v / (effective_w / 2.0f)); - float v_l = v_x - omega * k_skid; - float v_r = v_x + omega * k_skid; - - target_fl = v_l * rpm_per_ms; - target_fr = -v_r * rpm_per_ms; - target_rl = v_l * rpm_per_ms; - target_rr = -v_r * rpm_per_ms; - } - - if (state == "STOPPED") { - if (std::abs(target_fl) > 0.1f || std::abs(target_fr) > 0.1f) { - driver_front.setBrakes(false); - if (driver_rear_ptr) driver_rear_ptr->setBrakes(false); - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - state = "RUNNING"; - } - } else if (state == "RUNNING") { - cmd_fl = sCurveStep(cmd_fl, target_fl, max_accel_step, max_decel_step); - cmd_fr = sCurveStep(cmd_fr, target_fr, max_accel_step, max_decel_step); - cmd_rl = sCurveStep(cmd_rl, target_rl, max_accel_step, max_decel_step); - cmd_rr = sCurveStep(cmd_rr, target_rr, max_accel_step, max_decel_step); - - if (std::abs(target_fl) < 0.1f && std::abs(target_fr) < 0.1f && - std::abs(cmd_fl) < 0.5f && std::abs(cmd_fr) < 0.5f) { - state = "STOPPING"; - } - } else if (state == "STOPPING") { - // ๊ฐ์† ์‹œ์—๋Š” ์ฆ‰๊ฐ์ ์œผ๋กœ ์ •์ง€๋˜๋„๋ก ๋น ๋ฅด๊ฒŒ ๊ฐ์† ์ ์šฉ (์Šฌ๋ผ์ด๋”ฉ ๊ฐ์† ์ง€์—ฐ ์ œ๊ฑฐ) - cmd_fl = sCurveStep(cmd_fl, target_fl, max_decel_step, max_decel_step); - cmd_fr = sCurveStep(cmd_fr, target_fr, max_decel_step, max_decel_step); - cmd_rl = sCurveStep(cmd_rl, target_rl, max_decel_step, max_decel_step); - cmd_rr = sCurveStep(cmd_rr, target_rr, max_decel_step, max_decel_step); - - if (std::abs(cmd_fl) < 0.1f && std::abs(cmd_fr) < 0.1f) { - cmd_fl = 0.0f; cmd_fr = 0.0f; - cmd_rl = 0.0f; cmd_rr = 0.0f; - driver_front.setRPMs(0, 0); - if (driver_rear_ptr) driver_rear_ptr->setRPMs(0, 0); - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - driver_front.setBrakes(true); - if (driver_rear_ptr) driver_rear_ptr->setBrakes(true); - state = "STOPPED"; - } - } - - auto comm_start = std::chrono::high_resolution_clock::now(); - - if (state != "STOPPED") { - driver_front.setRPMs(cmd_fl, cmd_fr); - if (driver_rear_ptr) driver_rear_ptr->setRPMs(cmd_rl, cmd_rr); - } - - float fl_fb = 0, fr_fb = 0, rl_fb = 0, rr_fb = 0; - int32_t fl_tick = 0, fr_tick = 0, rl_tick = 0, rr_tick = 0; - - driver_front.readFeedback(fl_fb, fr_fb, fl_tick, fr_tick); - if (driver_rear_ptr) driver_rear_ptr->readFeedback(rl_fb, rr_fb, rl_tick, rr_tick); - - auto comm_end = std::chrono::high_resolution_clock::now(); - float comm_ms = std::chrono::duration(comm_end - comm_start).count(); - - // 16384 Ticks = 4๋ฐฐ ๋ฐฐ์œจ 4์ฑ„๋„ ์—”์ฝ”๋” 1ํšŒ์ „ (0.798m) ์ •๋ฐ€ ๋ฐ˜์˜ - float meters_per_tick = (2.0f * PI_VAL * wheel_radius) / 16384.0f; - if (!trip_initialized && (fl_tick != 0 || fr_tick != 0)) { - start_fl = fl_tick; start_fr = fr_tick; - start_rl = rl_tick; start_rr = rr_tick; - trip_initialized = true; - } - - int32_t delta_fl = std::abs(fl_tick - start_fl); - int32_t delta_fr = std::abs(fr_tick - start_fr); - int32_t delta_rl = std::abs(rl_tick - start_rl); - int32_t delta_rr = std::abs(rr_tick - start_rr); - - float dist_fl = delta_fl * meters_per_tick; - float dist_fr = delta_fr * meters_per_tick; - float dist_rl = delta_rl * meters_per_tick; - float dist_rr = delta_rr * meters_per_tick; - float dist_axle = (dist_fl + dist_fr + dist_rl + dist_rr) / 4.0f; - float dist_bumper = dist_axle + (dist_axle > 0.001f ? bumper_offset : 0.0f); - - - printf("\r\033[K[%-8s] AXLE: %5.3fm | BUMPER: %5.3fm | Ticks: FL=%+5d FR=%+5d | LATENCY: %4.1fms", - state.c_str(), dist_axle, dist_bumper, delta_fl, delta_fr, comm_ms); - fflush(stdout); - - std::this_thread::sleep_for(std::chrono::microseconds(static_cast(dt * 1000000))); + if (packet->data_type == kLivoxLidarCartesianCoordinateHighData) { + LivoxLidarCartesianHighRawPoint *pts = + (LivoxLidarCartesianHighRawPoint *)packet->data; + for (uint32_t i = 0; i < packet->dot_num; ++i) { + parsePoint(pts[i].x / 1000.0f, pts[i].y / 1000.0f, pts[i].z / 1000.0f); + } + } else if (packet->data_type == kLivoxLidarCartesianCoordinateLowData) { + LivoxLidarCartesianLowRawPoint *pts = + (LivoxLidarCartesianLowRawPoint *)packet->data; + for (uint32_t i = 0; i < packet->dot_num; ++i) { + parsePoint(pts[i].x / 100.0f, pts[i].y / 100.0f, pts[i].z / 100.0f); + } } - std::cout << "\n\n[์ •๋ณด] C++ 4WD ์•ˆ์ „ ์ •์ง€ ๋ฐ ๋ธŒ๋ ˆ์ดํฌ ์ž ๊ธˆ ์ฒ˜๋ฆฌ ์ค‘...\n"; - driver_front.setRPMs(0, 0); - if (driver_rear_ptr) driver_rear_ptr->setRPMs(0, 0); - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::lock_guard lock(status_mutex_); + auto updateEMA = [](float &old_val, float new_val) { + if (new_val < old_val) { + old_val = new_val; // Fast response to approaching obstacles + } else { + old_val = old_val * 0.70f + new_val * 0.30f; // Smooth recovery + } + }; - driver_front.setBrakes(true); - if (driver_rear_ptr) driver_rear_ptr->setBrakes(true); + updateEMA(status_.min_dist_front, curr_front); + updateEMA(status_.min_dist_rear, curr_rear); + updateEMA(status_.min_dist_left, curr_left); + updateEMA(status_.min_dist_right, curr_right); + status_.connected = true; + status_.last_update = std::chrono::steady_clock::now(); + } - if (joystick) SDL_JoystickClose(joystick); - SDL_Quit(); + ObstacleStatus getStatus() { + std::lock_guard lock(status_mutex_); + auto now = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast( + now - status_.last_update) + .count() > 800) { + status_.connected = false; + } + return status_; + } +}; - std::cout << "[์ •๋ณด] C++ 4WD ์ œ์–ด ํ”„๋กœ๊ทธ๋žจ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.\n"; - return 0; +LidarObstacleDetector *LidarObstacleDetector::instance_ = nullptr; + +float applyDeadzone(float val, float deadzone = 0.1f) { + if (std::abs(val) < deadzone) + return 0.0f; + float sign = (val > 0.0f) ? 1.0f : -1.0f; + float norm = (std::abs(val) - deadzone) / (1.0f - deadzone); + return sign * norm; } + +int main(int argc, char **argv) { + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); + + std::string port1 = "/dev/ttyUSB0"; + std::string port2 = ""; + uint8_t id1 = 1; + uint8_t id2 = 2; + bool bcast_mode = false; + float max_v = 0.40f; // m/s + float max_spin_v = 0.30f; // m/s + float track_width = 0.576f; // ์ขŒ์šฐ ๋ฐ”ํ€ด ์ค‘์‹ฌ ๊ฑฐ๋ฆฌ 576mm + float wheelbase = 0.3684f; // ์ „ํ›„ ๋ฐ”ํ€ด ์ค‘์‹ฌ ๊ฑฐ๋ฆฌ 368.4mm + float wheel_radius = 0.131517f; // ์‚ฌ์šฉ์ž ์‹ค์ธก ์ •๋ฐ€ 10.35์ธ์น˜ ํœ  ๋ฐ˜์ง€๋ฆ„ + float accel_rate = 120.0f; // RPM/s (์ตœ๋Œ€ ๊ฐ€์†๋„ ํ•œ๊ณ„) + float decel_rate = 180.0f; // RPM/s (์ตœ๋Œ€ ๊ฐ์†๋„ ํ•œ๊ณ„) + float jerk_rate = 600.0f; // RPM/s^2 (์ง์ง„/์ปค๋ธŒ ์„ ํšŒ ์ €ํฌ ํ•œ๊ณ„) + float spin_jerk_rate = 220.0f; // RPM/s^2 (์ œ์ž๋ฆฌ ํšŒ์ „ ์ €ํฌ ํ•œ๊ณ„: ๊ณ ๋งˆ์ฐฐ ๋…ธ๋ฉด์—์„œ + // ์ •์ง€๋งˆ์ฐฐ์ด ๊ธ‰๊ฒฉํžˆ ํŒŒ๊ดด๋˜์ง€ ์•Š๋„๋ก accel_rate์— + // ๋„๋‹ฌํ•˜๋Š” ์‹œ๊ฐ„์„ ๋” ๊ธธ๊ฒŒ ๋Š˜๋ฆผ) + float spin_arc_bias = 0.12f; // 0~1 (์ œ์ž๋ฆฌ ํšŒ์ „ ์‹œ ์„ž์„ ์ตœ์†Œ ํšŒ์ „๋ฐ˜๊ฒฝ์šฉ + // ๋ฏธ์„ธ ์ „์ง„ ์„ฑ๋ถ„ ๋น„์œจ. ICR์„ ๋กœ๋ด‡ ์ค‘์‹ฌ์—์„œ + // ์‚ด์ง ๋ฒ—์–ด๋‚˜๊ฒŒ ํ•ด ์Šคํฌ๋Ÿฝ ๋งˆ์ฐฐ ์ €ํ•ญ์„ ์ค„์ž„) + float bumper_offset = 0.045f; // ๋ฐ”ํ€ด ์ถ•์—์„œ ๋กœ๋ด‡ ๋งจ ์•ž ๋ฒ”ํผ๊นŒ์ง€์˜ ๊ฑฐ๋ฆฌ + + // ๋ฐ”ํ€ด๋ณ„ ์ด์ƒ(๋“ค๋œธ/๊ฑธ๋ฆผ) ํŒ์ • ํŒŒ๋ผ๋ฏธํ„ฐ: ์†๋„ ํ๋ฃจํ”„ ํŠน์„ฑ์ƒ "์ง€๋ น ๋Œ€๋น„ ์‹ค์ œ + // ์†๋„" ํ•˜๋‚˜๋งŒ์œผ๋กœ๋Š” ๋ฌด๋ถ€ํ•˜(๋“ค๋œธ) ํŒ์ •์ด ์•ˆ ๋˜๋ฏ€๋กœ, ๋™๋ฃŒ ๋ฐ”ํ€ด ๋Œ€๋น„ ์ „๋ฅ˜ + // ํŽธ์ฐจ์™€ ํ•จ๊ป˜ ๋‘ ์ถ•์œผ๋กœ ํŒ์ •ํ•œ๋‹ค. + float min_active_rpm = 3.0f; // RPM (์ด ๋ฏธ๋งŒ์ด๋ฉด ํŒ์ • ๋ณด๋ฅ˜: ์ •์ง€ ์ทจ๊ธ‰) + float airborne_current_ratio = 0.35f; // ๋™๋ฃŒ ๋ฐ”ํ€ด ์ค‘์•™๊ฐ’ ์ „๋ฅ˜ ๋Œ€๋น„ ์ด ๋น„์œจ + // ๋ฏธ๋งŒ์ด๋ฉด ๋ฌด๋ถ€ํ•˜(๋“ค๋œธ)๋กœ ํŒ์ • + float stall_vel_ratio = 0.5f; // ์‹ค์ œ์†๋„/์ง€๋ น์†๋„๊ฐ€ ์ด ๋น„์œจ ๋ฏธ๋งŒ์ด๋ฉด + // ์†๋„ ์ง€์—ฐ์œผ๋กœ ํŒ๋‹จ(๊ฑธ๋ฆผ ํ›„๋ณด) + float stall_current_a = 12.0f; // A (์ •๊ฒฉ 15A ๊ทผ์ ‘) ์ด์ƒ์ด๋ฉด์„œ ์†๋„ + // ์ง€์—ฐ์ด ๋™๋ฐ˜๋˜๋ฉด ๊ฑธ๋ฆผ/๊ณผ๋ถ€ํ•˜๋กœ ํŒ์ • + int airborne_debounce_ticks = 5; // ํ‹ฑ (100Hz ๊ธฐ์ค€ 50ms) ์—ฐ์† ๋“ค๋œธ ํŒ์ • + // ์‹œ์—๋งŒ ์‹ค์ œ๋กœ ๋ชฉํ‘œ์†๋„๋ฅผ ๋‚ฎ์ถค + std::string log_path = ""; // ๋น„์–ด์žˆ์œผ๋ฉด ๋กœ๊น… ๋น„ํ™œ์„ฑํ™”. --log <ํŒŒ์ผ๊ฒฝ๋กœ>๋กœ ์ง€์ • + + // LiDAR Parameters & Robot Physical Specs (User Specification) + bool use_lidar = true; + std::string lidar_config = "mid360s_config.json"; + float front_stop_dist = 0.35f; // m (์ „๋ฐฉ ์™„์ „ ์ •์ง€ ๊ฑฐ๋ฆฌ 0.35m) + float front_warn_dist = 0.50f; // m (์ „๋ฐฉ ๊ฐ์†/๊ฒฝ๊ณ  ์‹œ์ž‘ ๊ฑฐ๋ฆฌ 0.50m = 50cm) + float side_dodge_dist = 0.50f; // m (์ธก๋ฉด ์žฅ์• ๋ฌผ ์šฐํšŒ ๊ฐ์ง€ ๊ฑฐ๋ฆฌ 0.50m = 50cm) + float side_stop_dist = 0.20f; // m (์ธก๋ฉด ํ•œ๊ณ„ ์ ‘๊ทผ ๊ฑฐ๋ฆฌ 0.20m) + float max_dodge_omega = 0.35f; // rad/s (์ž๋™ ํšŒํ”ผ ์ตœ๋Œ€ ์กฐํ–ฅ ๊ฐ์†๋„) + float robot_width = 0.410f; // m (๋กœ๋ด‡ ๊ฐ€๋กœ/์ขŒ์šฐ ์‹ค์ธก ์ „ํญ 410mm) + float robot_length = 0.631f; // m (๋กœ๋ด‡ ์„ธ๋กœ/์ „ํ›„ ์‹ค์ธก ์ „์žฅ 631mm) + float robot_height = 0.286f; // m (๋กœ๋ด‡ ์ „๊ณ  286mm) + float lidar_height = 0.460f; // m (์ง€๋ฉด ๊ธฐ์ค€ ๋ผ์ด๋‹ค ๋†’์ด 460mm) + float lidar_pitch = 17.0f; // deg (๋ผ์ด๋‹ค ํ•˜ํ–ฅ ๊ธฐ์šธ์ž„ 17๋„) + float lidar_x_offset = 0.250f; // m (๋กœ๋ด‡ ์ค‘์‹ฌ ๊ธฐ์ค€ ๋ผ์ด๋‹ค ์ „๋ฐฉ ์˜คํ”„์…‹ 250mm) + float min_z = -lidar_height + 0.06f; // m (-0.40m: ์ง€๋ฉด ๊ฐ์ง€ ๋ฐฉ์ง€ ์•ˆ์ „ ์˜คํ”„์…‹) + float max_z = 0.80f; // m (๋ผ์ด๋‹ค ๊ธฐ์ค€ ์ƒ๋‹จ ๋†’์ด) + + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "--port" && i + 1 < argc) + port1 = argv[++i]; + else if (arg == "--port1" && i + 1 < argc) + port1 = argv[++i]; + else if (arg == "--port2" && i + 1 < argc) + port2 = argv[++i]; + else if (arg == "--id1" && i + 1 < argc) + id1 = static_cast(std::stoi(argv[++i])); + else if (arg == "--id2" && i + 1 < argc) + id2 = static_cast(std::stoi(argv[++i])); + else if (arg == "--track_width" && i + 1 < argc) + track_width = std::stof(argv[++i]); + else if (arg == "--wheelbase" && i + 1 < argc) + wheelbase = std::stof(argv[++i]); + else if (arg == "--radius" && i + 1 < argc) + wheel_radius = std::stof(argv[++i]); + else if (arg == "--bumper" && i + 1 < argc) + bumper_offset = std::stof(argv[++i]); + else if (arg == "--accel" && i + 1 < argc) + accel_rate = std::stof(argv[++i]); + else if (arg == "--decel" && i + 1 < argc) + decel_rate = std::stof(argv[++i]); + else if (arg == "--jerk" && i + 1 < argc) + jerk_rate = std::stof(argv[++i]); + else if (arg == "--spin_jerk" && i + 1 < argc) + spin_jerk_rate = std::stof(argv[++i]); + else if (arg == "--spin_arc_bias" && i + 1 < argc) + spin_arc_bias = std::stof(argv[++i]); + else if (arg == "--min_active_rpm" && i + 1 < argc) + min_active_rpm = std::stof(argv[++i]); + else if (arg == "--airborne_current_ratio" && i + 1 < argc) + airborne_current_ratio = std::stof(argv[++i]); + else if (arg == "--stall_vel_ratio" && i + 1 < argc) + stall_vel_ratio = std::stof(argv[++i]); + else if (arg == "--stall_current_a" && i + 1 < argc) + stall_current_a = std::stof(argv[++i]); + else if (arg == "--airborne_debounce_ticks" && i + 1 < argc) + airborne_debounce_ticks = std::stoi(argv[++i]); + else if (arg == "--log" && i + 1 < argc) + log_path = argv[++i]; + else if (arg == "--bcast" || arg == "--broadcast") + bcast_mode = true; + else if (arg == "--lidar_config" && i + 1 < argc) + lidar_config = argv[++i]; + else if (arg == "--front_stop" && i + 1 < argc) + front_stop_dist = std::stof(argv[++i]); + else if (arg == "--front_warn" && i + 1 < argc) + front_warn_dist = std::stof(argv[++i]); + else if (arg == "--side_dodge" && i + 1 < argc) + side_dodge_dist = std::stof(argv[++i]); + else if (arg == "--robot_width" && i + 1 < argc) + robot_width = std::stof(argv[++i]); + else if (arg == "--lidar_height" && i + 1 < argc) + lidar_height = std::stof(argv[++i]); + else if (arg == "--lidar_pitch" && i + 1 < argc) + lidar_pitch = std::stof(argv[++i]); + else if (arg == "--lidar_x_offset" && i + 1 < argc) + lidar_x_offset = std::stof(argv[++i]); + else if (arg == "--min_z" && i + 1 < argc) + min_z = std::stof(argv[++i]); + else if (arg == "--max_z" && i + 1 < argc) + max_z = std::stof(argv[++i]); + else if (arg == "--no_lidar") + use_lidar = false; + } + + constexpr float PI_VAL = 3.14159265358979323846f; + float rpm_per_ms = 60.0f / (2.0f * PI_VAL * wheel_radius); + + // 4WD ์œ ํšจ ์„ ํšŒ ๊ณ„์ˆ˜ + float effective_w = + std::sqrt(track_width * track_width + wheelbase * wheelbase); + float k_skid = (track_width + (wheelbase * wheelbase) / track_width) / 2.0f; + + std::cout << "\n=============================================================" + "=====\n"; + std::cout + << " [C++17 4WD ZLAC8015D + Mid-360S ๋ผ์ด๋‹ค ์ •๋ฐ€ ์žฅ์• ๋ฌผ ํšŒํ”ผ ์‹œ์Šคํ…œ]\n"; + std::cout << " - ๋กœ๋ด‡ ์ŠคํŽ™: ๊ฐ€๋กœ(์ „ํญ) " << robot_width * 1000.0f + << "mm | ์„ธ๋กœ(์ „์žฅ) " << robot_length * 1000.0f << "mm | ๋†’์ด " + << robot_height * 1000.0f << "mm\n"; + std::cout << " - ๋ผ์ด๋‹ค ์„ค์น˜: ์ง€๋ฉด ๋†’์ด " << lidar_height * 1000.0f + << "mm | ํ”ผ์น˜ ๊ฒฝ์‚ฌ๊ฐ " << lidar_pitch << "๋„ (ํ•˜ํ–ฅ) | ์ „๋ฐฉ ์˜คํ”„์…‹ " + << lidar_x_offset * 1000.0f << "mm\n"; + std::cout << " - ํ•„ํ„ฐ๋ง ๋ฒ”์œ„: ๋†’์ด Z(" << min_z << "m ~ " << max_z + << "m) | ํšŒํ”ผ ์ •๋ฐ€ ์ „ํญ " << (robot_width + 0.10f) * 1000.0f + << "mm\n"; + std::cout << " - ์ตœ๊ณ  ์†๋„: " << max_v << "m/s | ๊ฐ€์†: " << accel_rate + << "RPM/s | Jerk: " << jerk_rate << "RPM/sยฒ (์ œ์ž๋ฆฌํšŒ์ „: " + << spin_jerk_rate << "RPM/sยฒ) | ํšŒ์ „ ๊ณก์„ ํ˜ผํ•ฉ: " + << spin_arc_bias * 100.0f << "%\n"; + std::cout << " - ํœ  ์ด์ƒํŒ์ •: ๋ฌด๋ถ€ํ•˜(๋“ค๋œธ) ์ „๋ฅ˜๋น„ " << airborne_current_ratio + << " | ๊ฑธ๋ฆผ ์†๋„๋น„ " << stall_vel_ratio << " / ์ „๋ฅ˜ " + << stall_current_a << "A ์ด์ƒ\n"; + std::cout << " - ๋ผ์ด๋‹ค ์žฅ์• ๋ฌผ ํšŒํ”ผ (LiDAR Dodge): " + << (use_lidar ? "ํ™œ์„ฑํ™” (Mid-360S)" : "๋น„ํ™œ์„ฑํ™”") << "\n"; + std::cout << " - ์ „๋ฐฉ ์ •์ง€ ๊ฑฐ๋ฆฌ: " << front_stop_dist + << "m | ์ „๋ฐฉ ๊ฒฝ๊ณ : " << front_warn_dist + << "m | ์ธก๋ฉด ํšŒํ”ผ: " << side_dodge_dist << "m\n"; + std::cout + << "==================================================================\n"; + + // CSV ๋กœ๊น…: --log <๊ฒฝ๋กœ> ์ง€์ • ์‹œ ๋งค ํ‹ฑ ์ง€๋ น/ํ”ผ๋“œ๋ฐฑ/์ „๋ฅ˜/ํœ ์ƒํƒœ๋ฅผ ํŒŒ์ผ๋กœ ๊ธฐ๋ก. + // ์‹ค์™ธ์—์„œ ๋กœ๋ด‡์„ ์กฐ์ข…ํ•˜๋ฉฐ ํ™”๋ฉด์„ ๋™์‹œ์— ์ฝ๊ธฐ ์–ด๋ ค์šฐ๋ฏ€๋กœ, ๋ฌธ์ œ๊ฐ€ ๋œ + // ๊ตฌ๊ฐ„(ํ„ฑ ๋„˜๋Š” ์ง€์  ๋“ฑ)์„ ๋‚˜์ค‘์— ์ž˜๋ผ์„œ ๋ถ„์„ํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„. + std::ofstream log_stream; + if (!log_path.empty()) { + log_stream.open(log_path); + if (log_stream.is_open()) { + log_stream << "t_ms,state,ly,lx,rx," + "cmd_fl,cmd_fr,cmd_rl,cmd_rr," + "fb_fl,fb_fr,fb_rl,fb_rr," + "amp_fl,amp_fr,amp_rl,amp_rr," + "stat_fl,stat_fr,stat_rl,stat_rr," + "dist_axle,comm_ms\n"; + std::cout << " - CSV ๋กœ๊ทธ ๊ธฐ๋ก: " << log_path << "\n"; + } else { + std::cerr << "[๊ฒฝ๊ณ ] ๋กœ๊ทธ ํŒŒ์ผ ์—ด๊ธฐ ์‹คํŒจ: " << log_path << "\n"; + } + } + auto log_start_time = std::chrono::steady_clock::now(); + + // Initialize LiDAR Detector + LidarObstacleDetector lidar_detector; + if (use_lidar) { + // Robot half width tolerance = robot_width / 2 + 5cm margin + lidar_detector.setParams(min_z, max_z, robot_width / 2.0f + 0.05f, + lidar_pitch, lidar_height, lidar_x_offset); + if (!lidar_detector.init(lidar_config)) { + std::cout << "[๊ฒฝ๊ณ ] ๋ผ์ด๋‹ค ์—ฐ๊ฒฐ ์‹คํŒจ. ์žฅ์• ๋ฌผ ๊ฐ์ง€ ์—†์ด ์กฐ์ด์Šคํ‹ฑ ๋ชจ๋“œ๋กœ " + "์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n"; + } + } + + if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { + std::cerr << "[์˜ค๋ฅ˜] SDL ์กฐ์ด์Šคํ‹ฑ ์ดˆ๊ธฐํ™” ์‹คํŒจ: " << SDL_GetError() + << std::endl; + return 1; + } + + if (SDL_NumJoysticks() < 1) { + std::cerr << "[๊ฒฝ๊ณ ] ๋ฌด์„  Xbox ์ปจํŠธ๋กค๋Ÿฌ(์กฐ์ด์Šคํ‹ฑ)๊ฐ€ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.\n"; + SDL_Quit(); + return 1; + } + + SDL_Joystick *joystick = SDL_JoystickOpen(0); + if (!joystick) { + std::cerr << "[์˜ค๋ฅ˜] ์กฐ์ด์Šคํ‹ฑ ์—ด๊ธฐ ์‹คํŒจ!\n"; + SDL_Quit(); + return 1; + } + std::cout << "[์ •๋ณด] C++ Xbox ์ปจํŠธ๋กค๋Ÿฌ ์—ฐ๊ฒฐ ์„ฑ๊ณต: " + << SDL_JoystickName(joystick) << std::endl; + + SerialPort sp1, sp2; + if (!sp1.openPort(port1)) + return 1; + std::cout << "[์ •๋ณด] RS485 ํฌํŠธ1 (" << port1 << ") ์˜คํ”ˆ ์„ฑ๊ณต.\n"; + + SerialPort *sp2_ptr = &sp1; + if (!port2.empty()) { + if (sp2.openPort(port2)) { + sp2_ptr = &sp2; + std::cout << "[์ •๋ณด] RS485 ํฌํŠธ2 (" << port2 << ") ์˜คํ”ˆ ์„ฑ๊ณต.\n"; + } + } + + MotorDriver driver_front(&sp1, bcast_mode ? 0 : id1); + driver_front.initDriver(150, 150); + + MotorDriver *driver_rear_ptr = nullptr; + MotorDriver driver_rear(sp2_ptr, bcast_mode ? 0 : id2); + if (!bcast_mode) { + driver_rear.initDriver(150, 150); + driver_rear_ptr = &driver_rear; + } + + std::cout << "\n[์ •๋ณด] C++ 100Hz ์ดˆ์ €์ง€์—ฐ ๋ฃจํ”„๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. (์ข…๋ฃŒ: B ๋ฒ„ํŠผ " + "๋˜๋Š” Ctrl+C, ๋ผ์ด๋‹ค ํ† ๊ธ€: X ๋ฒ„ํŠผ)\n\n"; + + float target_fl = 0.0f, target_fr = 0.0f; + float target_rl = 0.0f, target_rr = 0.0f; + + float cmd_fl = 0.0f, cmd_fr = 0.0f; + float cmd_rl = 0.0f, cmd_rr = 0.0f; + // ๊ฐ ๋ฐ”ํ€ด์˜ ํ˜„์žฌ ๊ฐ€์†๋„ ์ƒํƒœ(์ €ํฌ ์ œํ•œ ํ”„๋กœํŒŒ์ผ์šฉ) + float accel_fl = 0.0f, accel_fr = 0.0f; + float accel_rl = 0.0f, accel_rr = 0.0f; + + const float loop_hz = 100.0f; + const float dt = 1.0f / loop_hz; + + // ์ €ํฌ ์ œํ•œ(jerk-limited) S-curve: ๊ฐ€์†๋„ ์ž์ฒด๋ฅผ ๋งค ํ‹ฑ jerk_limit ๋งŒํผ๋งŒ + // ๋ณ€ํ™”์‹œ์ผœ ์„œ์„œํžˆ ๋ชฉํ‘œ ๊ฐ€์†๋„์— ๋„๋‹ฌํ•˜๊ฒŒ ํ•จ. ๊ธฐ์กด ๊ตฌํ˜„์€ ๋ชฉํ‘œ ์ ‘๊ทผ ๊ตฌ๊ฐ„๋งŒ + // ๋ถ€๋“œ๋Ÿฌ์› ๊ณ  ์ •์ง€ ์ƒํƒœ์—์„œ ์ถœ๋ฐœํ•  ๋•Œ๋Š” ์ฒซ ํ‹ฑ๋ถ€ํ„ฐ ๊ฑฐ์˜ ํ’€๊ฐ€์†๋„๊ฐ€ ๊ฑธ๋ ค + // ์ •์ง€๋งˆ์ฐฐ์ด ๊ธ‰๊ฒฉํžˆ ํŒŒ๊ดด๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Œ. + auto jerkLimitedStep = [dt](float current_vel, float target_vel, + float ¤t_accel, float max_accel_mag, + float max_decel_mag, float jerk_limit) { + float diff = target_vel - current_vel; + if (std::abs(diff) < 0.01f) { + current_accel = 0.0f; + return target_vel; + } + + bool is_accelerating = std::abs(target_vel) > std::abs(current_vel); + float accel_cap = is_accelerating ? max_accel_mag : max_decel_mag; + // ์ด๋ฒˆ ํ‹ฑ์— diff๋ฅผ ์—†์• ๋Š”๋ฐ ํ•„์š”ํ•œ ๊ฐ€์†๋„(๋ถ€ํ˜ธ ํฌํ•จ)๋ฅผ ๋ฌผ๋ฆฌ์  ํ•œ๊ณ„๋กœ ํด๋žจํ”„ + float desired_accel = std::clamp(diff / dt, -accel_cap, accel_cap); + + float max_delta_accel = jerk_limit * dt; + current_accel = std::clamp(desired_accel, current_accel - max_delta_accel, + current_accel + max_delta_accel); + + float next_vel = current_vel + current_accel * dt; + bool overshoot = + (diff > 0.0f) ? (next_vel >= target_vel) : (next_vel <= target_vel); + if (overshoot) { + current_accel = 0.0f; + return target_vel; + } + return next_vel; + }; + + std::string state = "STOPPED"; + bool trip_initialized = false; + int32_t start_fl = 0, start_fr = 0, start_rl = 0, start_rr = 0; + + // ๋ฐ”ํ€ด๋ณ„ ์ด์ƒ ์ƒํƒœ(์ง์ „ ํ‹ฑ ํ”ผ๋“œ๋ฐฑ ๊ธฐ๋ฐ˜ ํŒ์ •, 1ํ‹ฑ=10ms ์ง€์—ฐ์œผ๋กœ ๋ฐ˜์˜) + bool airborne_fl = false, airborne_fr = false; + bool airborne_rl = false, airborne_rr = false; + int air_count_fl = 0, air_count_fr = 0, air_count_rl = 0, air_count_rr = 0; + char wheel_status_str[5] = "OOOO"; + + while (g_running) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_JOYBUTTONDOWN) { + if (event.jbutton.button == 0) { // A ๋ฒ„ํŠผ: ํŠธ๋ฆฝ ๋ฆฌ์…‹ + trip_initialized = false; + } else if (event.jbutton.button == + 2) { // X ๋ฒ„ํŠผ: ๋ผ์ด๋‹ค ์žฅ์• ๋ฌผ ํšŒํ”ผ ํ† ๊ธ€ + use_lidar = !use_lidar; + std::cout << "\n[์ •๋ณด] ๋ผ์ด๋‹ค ํšŒํ”ผ ๊ธฐ๋Šฅ: " + << (use_lidar ? "ON" : "OFF") << std::endl; + } else if (event.jbutton.button == 1 || + event.jbutton.button == 6) { // B or Back + g_running = 0; + } + } + } + + Sint16 lx_raw = SDL_JoystickGetAxis(joystick, 0); + Sint16 ly_raw = SDL_JoystickGetAxis(joystick, 1); + Sint16 rx_raw = SDL_JoystickGetAxis(joystick, 3); + + float ly = applyDeadzone(-static_cast(ly_raw) / 32767.0f); + float lx = applyDeadzone(static_cast(lx_raw) / 32767.0f); + float rx = applyDeadzone(static_cast(rx_raw) / 32767.0f); + + if (std::abs(lx) < 0.15f) + lx = 0.0f; + if (std::abs(rx) < 0.15f) + rx = 0.0f; + + float v_x = ly * max_v; // ์„ ์†๋„ m/s + float omega = 0.0f; // ๊ฐ์†๋„ rad/s + + // --------------------------------------------------------------------- + // LiDAR Obstacle Avoidance & Stopping Logic Integration + // (์š”์ฒญ์— ๋”ฐ๋ผ ํ›„์ง„ ์‹œ ์ธก๋ฉด ํšŒํ”ผ๋Š” ๋ฏธ์ ์šฉ - ์ „์ง„ ์‹œ์—๋งŒ ์ขŒ์šฐ ํšŒํ”ผ ๋™์ž‘) + // --------------------------------------------------------------------- + std::string lidar_telemetry = "LIDAR:OFF"; + if (use_lidar) { + ObstacleStatus obs = lidar_detector.getStatus(); + if (obs.connected) { + float speed_scale = 1.0f; + float avoid_omega_offset = 0.0f; + + // 1. Forward / Backward Automatic Stopping + if (v_x > 0.01f) { + if (obs.min_dist_front < front_stop_dist) { + speed_scale = 0.0f; // Complete forward stop + } else if (obs.min_dist_front < front_warn_dist) { + speed_scale = (obs.min_dist_front - front_stop_dist) / + (front_warn_dist - front_stop_dist); + speed_scale = std::clamp(speed_scale, 0.0f, 1.0f); + } + } else if (v_x < -0.01f) { + if (obs.min_dist_rear < front_stop_dist) { + speed_scale = 0.0f; // Complete backward stop + } + } + + // 2. Side Dodge Steering (์ „์ง„ ์ค‘์—๋งŒ ๋™์ž‘) + if (v_x > 0.01f && rx == 0.0f) { + float right_intensity = 0.0f; + float left_intensity = 0.0f; + + if (obs.min_dist_right < side_dodge_dist) { + right_intensity = (side_dodge_dist - obs.min_dist_right) / + (side_dodge_dist - side_stop_dist); + right_intensity = std::clamp(right_intensity, 0.0f, 1.0f); + } + if (obs.min_dist_left < side_dodge_dist) { + left_intensity = (side_dodge_dist - obs.min_dist_left) / + (side_dodge_dist - side_stop_dist); + left_intensity = std::clamp(left_intensity, 0.0f, 1.0f); + } + + // Right obstacle -> Steer Left (+omega) + // Left obstacle -> Steer Right (-omega) + avoid_omega_offset = + (right_intensity - left_intensity) * max_dodge_omega; + } + + v_x *= speed_scale; + + char lbuf[128]; + snprintf(lbuf, sizeof(lbuf), "F:%4.2fm|L:%4.2fm|R:%4.2fm %s", + obs.min_dist_front > 99.0f ? 9.99f : obs.min_dist_front, + obs.min_dist_left > 99.0f ? 9.99f : obs.min_dist_left, + obs.min_dist_right > 99.0f ? 9.99f : obs.min_dist_right, + avoid_omega_offset > 0.05f + ? "[Dodge L]" + : (avoid_omega_offset < -0.05f + ? "[Dodge R]" + : (speed_scale < 0.01f ? "[STOP!]" : "[OK]"))); + lidar_telemetry = lbuf; + + // Add avoidance offset to user steering + omega += avoid_omega_offset; + } else { + lidar_telemetry = "LIDAR:WAITING"; + } + } + + // ์ œ์ž๋ฆฌ ํšŒ์ „ ์—ฌ๋ถ€: ์™ผ์ชฝ ์Šคํ‹ฑ ์ „ํ›„(ly) ์ž…๋ ฅ์ด ์™„์ „ํžˆ ์ค‘๋ฆฝ์ผ ๋•Œ๋งŒ ์ง„์ž…ํ•œ๋‹ค. + // ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด(์ฃผํ–‰ ์ค‘ ์˜ค๋ฅธ์ชฝ ์Šคํ‹ฑ์ด ๋“œ๋ฆฌํ”„ํŠธ ๋“ฑ์œผ๋กœ ๋ฏธ์„ธํ•˜๊ฒŒ๋ผ๋„ ๊ฐ์ง€๋  + // ๊ฒฝ์šฐ) ์ „์ง„ ์ž…๋ ฅ์ด ํ†ต์งธ๋กœ ๋ฌด์‹œ๋˜๊ณ  ์ œ์ž๋ฆฌ ํšŒ์ „ ๋กœ์ง์œผ๋กœ ํŠ€์–ด๋ฒ„๋ ค, ์ปค๋ธŒ + // ์„ ํšŒ ์ค‘์— ๊ฐ‘์ž๊ธฐ ์ด์ƒํ•˜๊ฒŒ ํšŒ์ „ํ•˜๋ ค๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธด๋‹ค. ly๋Š” applyDeadzone์„ + // ๊ฑฐ์ณ ๋ฐ๋“œ์กด ์ดํ•˜์ผ ๋•Œ ์ •ํ™•ํžˆ 0.0f๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ๋“ฑํ˜ธ ๋น„๊ต๊ฐ€ ์•ˆ์ „ํ•˜๋‹ค. + bool is_spin_turn = std::abs(rx) > 0.0f && ly == 0.0f; + + if (is_spin_turn) { + // ์ œ์ž๋ฆฌ ํšŒ์ „ (Spin Turn) + ์ตœ์†Œ ํšŒ์ „๋ฐ˜๊ฒฝ์šฉ ๋ฏธ์„ธ ๊ณก์„  ํ˜ผํ•ฉ + omega = -rx * (max_spin_v / (effective_w / 2.0f)); + float v_bias = std::abs(rx) * max_spin_v * spin_arc_bias; + float v_l = v_bias - omega * (effective_w / 2.0f); + float v_r = v_bias + omega * (effective_w / 2.0f); + + target_fl = v_l * rpm_per_ms; + target_fr = -v_r * rpm_per_ms; + target_rl = v_l * rpm_per_ms; + target_rr = -v_r * rpm_per_ms; + } else { + // ์ง์ง„ ๋ฐ ์ฐจ๋™ ์„ ํšŒ Kinematics + if (std::abs(rx) == 0.0f && std::abs(lx) == 0.0f && + std::abs(omega) > 0.0f) { + // Automated avoidance steering active + float v_l = v_x - omega * k_skid; + float v_r = v_x + omega * k_skid; + target_fl = v_l * rpm_per_ms; + target_fr = -v_r * rpm_per_ms; + target_rl = v_l * rpm_per_ms; + target_rr = -v_r * rpm_per_ms; + } else { + omega += -lx * (max_v / (effective_w / 2.0f)); + float v_l = v_x - omega * k_skid; + float v_r = v_x + omega * k_skid; + + target_fl = v_l * rpm_per_ms; + target_fr = -v_r * rpm_per_ms; + target_rl = v_l * rpm_per_ms; + target_rr = -v_r * rpm_per_ms; + } + } + + // ์ง์ „ ํ‹ฑ ํ”ผ๋“œ๋ฐฑ์—์„œ ๋ฌด๋ถ€ํ•˜(๋“ค๋œธ)๋กœ ํŒ์ •๋œ ๋ฐ”ํ€ด๋Š” ๋ชฉํ‘œ ์†๋„๋ฅผ 0์œผ๋กœ ๋‚ฎ์ถฐ + // ํ—›๋Œ์ด๋ฅผ ์–ต์ œํ•œ๋‹ค. ์ ‘์ง€๋ ฅ์ด ํšŒ๋ณต๋˜์–ด ์ „๋ฅ˜๊ฐ€ ์ •์ƒ์œผ๋กœ ๋Œ์•„์˜ค๋ฉด ๋‹ค์Œ + // ํŒ์ • ํ‹ฑ์—์„œ ์ž๋™์œผ๋กœ ํ•ด์ œ๋˜์–ด ์›๋ž˜ ์ง€๋ น์œผ๋กœ ๋ณต๊ท€ํ•œ๋‹ค. + if (airborne_fl) + target_fl = 0.0f; + if (airborne_fr) + target_fr = 0.0f; + if (airborne_rl) + target_rl = 0.0f; + if (airborne_rr) + target_rr = 0.0f; + + if (state == "STOPPED") { + if (std::abs(target_fl) > 0.1f || std::abs(target_fr) > 0.1f) { + driver_front.setBrakes(false); + if (driver_rear_ptr) + driver_rear_ptr->setBrakes(false); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + state = "RUNNING"; + } + } else if (state == "RUNNING") { + float jerk_now = is_spin_turn ? spin_jerk_rate : jerk_rate; + cmd_fl = jerkLimitedStep(cmd_fl, target_fl, accel_fl, accel_rate, + decel_rate, jerk_now); + cmd_fr = jerkLimitedStep(cmd_fr, target_fr, accel_fr, accel_rate, + decel_rate, jerk_now); + cmd_rl = jerkLimitedStep(cmd_rl, target_rl, accel_rl, accel_rate, + decel_rate, jerk_now); + cmd_rr = jerkLimitedStep(cmd_rr, target_rr, accel_rr, accel_rate, + decel_rate, jerk_now); + + if (std::abs(target_fl) < 0.1f && std::abs(target_fr) < 0.1f && + std::abs(cmd_fl) < 0.5f && std::abs(cmd_fr) < 0.5f) { + state = "STOPPING"; + } + } else if (state == "STOPPING") { + float jerk_now = is_spin_turn ? spin_jerk_rate : jerk_rate; + cmd_fl = jerkLimitedStep(cmd_fl, target_fl, accel_fl, decel_rate, + decel_rate, jerk_now); + cmd_fr = jerkLimitedStep(cmd_fr, target_fr, accel_fr, decel_rate, + decel_rate, jerk_now); + cmd_rl = jerkLimitedStep(cmd_rl, target_rl, accel_rl, decel_rate, + decel_rate, jerk_now); + cmd_rr = jerkLimitedStep(cmd_rr, target_rr, accel_rr, decel_rate, + decel_rate, jerk_now); + + if (std::abs(cmd_fl) < 0.1f && std::abs(cmd_fr) < 0.1f) { + cmd_fl = 0.0f; + cmd_fr = 0.0f; + cmd_rl = 0.0f; + cmd_rr = 0.0f; + accel_fl = accel_fr = accel_rl = accel_rr = 0.0f; + driver_front.setRPMs(0, 0); + if (driver_rear_ptr) + driver_rear_ptr->setRPMs(0, 0); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + driver_front.setBrakes(true); + if (driver_rear_ptr) + driver_rear_ptr->setBrakes(true); + state = "STOPPED"; + } + } + + auto comm_start = std::chrono::high_resolution_clock::now(); + + if (state != "STOPPED") { + driver_front.setRPMs(cmd_fl, cmd_fr); + if (driver_rear_ptr) + driver_rear_ptr->setRPMs(cmd_rl, cmd_rr); + } + + float fl_fb = 0, fr_fb = 0, rl_fb = 0, rr_fb = 0; + float fl_amp = 0, fr_amp = 0, rl_amp = 0, rr_amp = 0; + int32_t fl_tick = 0, fr_tick = 0, rl_tick = 0, rr_tick = 0; + + driver_front.readFeedback(fl_fb, fr_fb, fl_tick, fr_tick, fl_amp, fr_amp); + if (driver_rear_ptr) + driver_rear_ptr->readFeedback(rl_fb, rr_fb, rl_tick, rr_tick, rl_amp, + rr_amp); + + auto comm_end = std::chrono::high_resolution_clock::now(); + float comm_ms = + std::chrono::duration(comm_end - comm_start).count(); + + // ๋ฐ”ํ€ด๋ณ„ ์ด์ƒ(๋“ค๋œธ/๊ฑธ๋ฆผ) ํŒ์ •: ์†๋„ ํ๋ฃจํ”„ ํŠน์„ฑ์ƒ "์ง€๋ น ๋Œ€๋น„ ์‹ค์ œ์†๋„" + // ๋งŒ์œผ๋กœ๋Š” ๋ฌด๋ถ€ํ•˜(๋“ค๋œธ)๋ฅผ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์—†๋‹ค (๋“œ๋ผ์ด๋ฒ„๊ฐ€ ๋ถ€ํ•˜์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ + // ์ง€๋ น RPM์„ ๊ทธ๋Œ€๋กœ ์ถ”์ข…ํ•˜๋ ค ํ•˜๋ฏ€๋กœ). ๊ทธ๋ž˜์„œ ๋‘ ์ถ•์œผ๋กœ ํŒ์ •ํ•œ๋‹ค. + // - ๊ฑธ๋ฆผ/๊ณผ๋ถ€ํ•˜: ์‹ค์ œ์†๋„๊ฐ€ ์ง€๋ น์„ ํฌ๊ฒŒ ๋ชป ๋”ฐ๋ผ๊ฐ€๋ฉด์„œ ์ „๋ฅ˜๊ฐ€ ๋†’์Œ (๋…๋ฆฝ ํŒ์ •) + // - ๋ฌด๋ถ€ํ•˜/๋“ค๋œธ: "๊ฐ™์€ ์ง€๋ น์†๋„๋ฅผ ๋ฐ›๋Š” ์ง(์•ž๋’ค ๊ฐ™์€ ์ชฝ)" ๋Œ€๋น„ ์ „๋ฅ˜๊ฐ€ + // ๋น„์ •์ƒ์ ์œผ๋กœ ๋‚ฎ์Œ. ์ขŒ/์šฐ๋ฅผ ํ†ต์งธ๋กœ ๋น„๊ตํ•˜๋ฉด ์ •์ƒ์ ์ธ ์ปค๋ธŒ ์„ ํšŒ์—์„œ + // ์ง€๋ น์†๋„๊ฐ€ ์›๋ž˜ ๋‹ค๋ฅธ ์ขŒ์šฐ ๋ฐ”ํ€ด๋ฅผ ์˜คํƒํ•˜๋ฏ€๋กœ, ๋ฐ˜๋“œ์‹œ target_fl=target_rl, + // target_fr=target_rr ๋กœ ์ง€๋ น์ด ํ•ญ์ƒ ๊ฐ™์€ ๊ฐ™์€์ชฝ ์•ž๋’ค ์Œ๋ผ๋ฆฌ๋งŒ ๋น„๊ตํ•œ๋‹ค. + auto classifyStall = [&](float cmd_rpm, float actual_rpm, + float amp) -> bool { + float cmd_abs = std::abs(cmd_rpm); + if (cmd_abs < min_active_rpm) + return false; + float vel_ratio = std::abs(actual_rpm) / cmd_abs; + return vel_ratio < stall_vel_ratio && std::abs(amp) >= stall_current_a; + }; + bool stall_fl = classifyStall(cmd_fl, fl_fb, fl_amp); + bool stall_fr = classifyStall(cmd_fr, fr_fb, fr_amp); + bool stall_rl = classifyStall(cmd_rl, rl_fb, rl_amp); + bool stall_rr = classifyStall(cmd_rr, rr_fb, rr_amp); + + bool air_now_fl = false, air_now_rl = false; + bool air_now_fr = false, air_now_rr = false; + auto classifyAirPair = [&](float cmd_rpm, float amp_a, float amp_b, + bool &air_a, bool &air_b) { + if (std::abs(cmd_rpm) < min_active_rpm) + return; // ์ •์ง€/์ €์† ์ค‘์ด๋ฉด ํŒ์ • ๋ณด๋ฅ˜ + float a = std::abs(amp_a), b = std::abs(amp_b); + float hi = std::max(a, b); + if (hi < 0.5f) + return; // ๋‘˜ ๋‹ค ๊ฑฐ์˜ ๋ฌด์ „๋ฅ˜(๊ด€์„ฑ ์ฃผํ–‰ ๋“ฑ)๋ฉด ํŒ์ • ๋ณด๋ฅ˜ + if (a < hi * airborne_current_ratio) + air_a = true; + if (b < hi * airborne_current_ratio) + air_b = true; + }; + classifyAirPair(cmd_fl, fl_amp, rl_amp, air_now_fl, air_now_rl); + classifyAirPair(cmd_fr, fr_amp, rr_amp, air_now_fr, air_now_rr); + + // ๋””๋ฐ”์šด์Šค: ๋…ธ์ด์ฆˆ์„ฑ ์ˆœ๊ฐ„ ์ „๋ฅ˜ ํŽธ์ฐจ๋กœ ๋ชฉํ‘œ์†๋„๊ฐ€ ์ฆ‰์‹œ 0์œผ๋กœ ๊นŽ์ด์ง€ ์•Š๋„๋ก + // ์—ฐ์† airborne_debounce_ticks ํ‹ฑ ์ด์ƒ ์ง€์†๋  ๋•Œ๋งŒ ์‹ค์ œ๋กœ ๊ฐœ์ž…ํ•œ๋‹ค. + air_count_fl = air_now_fl ? air_count_fl + 1 : 0; + air_count_fr = air_now_fr ? air_count_fr + 1 : 0; + air_count_rl = air_now_rl ? air_count_rl + 1 : 0; + air_count_rr = air_now_rr ? air_count_rr + 1 : 0; + + airborne_fl = air_count_fl >= airborne_debounce_ticks; + airborne_fr = air_count_fr >= airborne_debounce_ticks; + airborne_rl = air_count_rl >= airborne_debounce_ticks; + airborne_rr = air_count_rr >= airborne_debounce_ticks; + + auto statusChar = [](bool air, bool stall) { + return air ? 'A' : (stall ? 'S' : 'O'); + }; + wheel_status_str[0] = statusChar(airborne_fl, stall_fl); + wheel_status_str[1] = statusChar(airborne_fr, stall_fr); + wheel_status_str[2] = statusChar(airborne_rl, stall_rl); + wheel_status_str[3] = statusChar(airborne_rr, stall_rr); + + float meters_per_tick = (2.0f * PI_VAL * wheel_radius) / 16384.0f; + if (!trip_initialized && (fl_tick != 0 || fr_tick != 0)) { + start_fl = fl_tick; + start_fr = fr_tick; + start_rl = rl_tick; + start_rr = rr_tick; + trip_initialized = true; + } + + int32_t delta_fl = std::abs(fl_tick - start_fl); + int32_t delta_fr = std::abs(fr_tick - start_fr); + int32_t delta_rl = std::abs(rl_tick - start_rl); + int32_t delta_rr = std::abs(rr_tick - start_rr); + + float dist_fl = delta_fl * meters_per_tick; + float dist_fr = delta_fr * meters_per_tick; + float dist_rl = delta_rl * meters_per_tick; + float dist_rr = delta_rr * meters_per_tick; + + // ์ค‘์•™๊ฐ’(4๊ฐœ ์ค‘ ๊ฐ€์šด๋ฐ 2๊ฐœ ํ‰๊ท ) ๊ธฐ๋ฐ˜ ์ฃผํ–‰๊ฑฐ๋ฆฌ ์‚ฐ์ถœ: ์›…๋ฉ์ด ๋“ฑ์œผ๋กœ ๋ฐ”ํ€ด + // ํ•˜๋‚˜๊ฐ€ ์ง€๋ฉด์—์„œ ๋–จ์–ด์ ธ ์—”์ฝ”๋”๋งŒ ํ—›๋Œ ๋•Œ, ๊ทธ ์ด์ƒ์น˜ ๊ฐ’์ด ํ‰๊ท (mean)์„ + // ์˜ค์—ผ์‹œํ‚ค์ง€ ์•Š๋„๋ก ์ตœ๋Œ“๊ฐ’/์ตœ์†Ÿ๊ฐ’์„ ์ž๋™์œผ๋กœ ๋ฐฐ์ œํ•จ. + float dist_sorted[4] = {dist_fl, dist_fr, dist_rl, dist_rr}; + std::sort(dist_sorted, dist_sorted + 4); + float dist_axle = (dist_sorted[1] + dist_sorted[2]) / 2.0f; + float dist_bumper = dist_axle + (dist_axle > 0.001f ? bumper_offset : 0.0f); + (void)dist_bumper; + + if (log_stream.is_open()) { + float t_ms = std::chrono::duration( + std::chrono::steady_clock::now() - log_start_time) + .count(); + log_stream << t_ms << ',' << state << ',' << ly << ',' << lx << ',' + << rx << ',' << cmd_fl << ',' << cmd_fr << ',' << cmd_rl + << ',' << cmd_rr << ',' << fl_fb << ',' << fr_fb << ',' + << rl_fb << ',' << rr_fb << ',' << fl_amp << ',' << fr_amp + << ',' << rl_amp << ',' << rr_amp << ',' << wheel_status_str[0] + << ',' << wheel_status_str[1] << ',' << wheel_status_str[2] + << ',' << wheel_status_str[3] << ',' << dist_axle << ',' + << comm_ms << '\n'; + } + + printf("\r\033[K[%-8s] AXLE:%5.3fm | %-28s | I(A):FL%+4.1f FR%+4.1f " + "RL%+4.1f RR%+4.1f | WHL(FL/FR/RL/RR):%s | %4.1fms", + state.c_str(), dist_axle, lidar_telemetry.c_str(), fl_amp, fr_amp, + rl_amp, rr_amp, wheel_status_str, comm_ms); + fflush(stdout); + + std::this_thread::sleep_for( + std::chrono::microseconds(static_cast(dt * 1000000))); + } + + std::cout << "\n\n[์ •๋ณด] C++ 4WD ์•ˆ์ „ ์ •์ง€ ๋ฐ ๋ธŒ๋ ˆ์ดํฌ ์ž ๊ธˆ ์ฒ˜๋ฆฌ ์ค‘...\n"; + driver_front.setRPMs(0, 0); + if (driver_rear_ptr) + driver_rear_ptr->setRPMs(0, 0); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + driver_front.setBrakes(true); + if (driver_rear_ptr) + driver_rear_ptr->setBrakes(true); + + if (use_lidar) + lidar_detector.stop(); + + if (log_stream.is_open()) { + log_stream.close(); + std::cout << "[์ •๋ณด] CSV ๋กœ๊ทธ ์ €์žฅ ์™„๋ฃŒ: " << log_path << "\n"; + } + + if (joystick) + SDL_JoystickClose(joystick); + SDL_Quit(); + + std::cout << "[์ •๋ณด] C++ 4WD ์ œ์–ด ํ”„๋กœ๊ทธ๋žจ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.\n"; + return 0; +} \ No newline at end of file diff --git a/xbox_motor_control_cpp b/xbox_motor_control_cpp index d9af75a..3df3056 100755 Binary files a/xbox_motor_control_cpp and b/xbox_motor_control_cpp differ