refactor(package): rename to dm_imu and restructure directories

Renamed package from dm_imu_pkg to dm_imu across PKG-INFO, egg-info files,
and directories. Updated CMakeLists.txt, __init__.py, and top_level.txt to
reflect new structure. Bumped gradio version from <5.0 to <6.0 in
dependencies. This refactoring improves naming consistency and streamlines
the package layout for better maintainability.
This commit is contained in:
2025-12-10 19:32:28 +08:00
parent e2287178fa
commit ced3669fac
64 changed files with 30 additions and 3304 deletions

21
dm_imu/CMakeLists.txt Normal file
View File

@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.14)
project(imu_py LANGUAGES CXX)
# 使用 C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找 pybind11已通过 pip 安装)
execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "import pybind11; print(pybind11.get_cmake_dir())" OUTPUT_VARIABLE PYBIND11_CMAKE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
set(CMAKE_PREFIX_PATH "${PYBIND11_CMAKE_DIR}" ${CMAKE_PREFIX_PATH})
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src
)
find_package(pybind11 REQUIRED)
pybind11_add_module(imu_py
pybind_imu.cpp
src/imu_driver.cpp
src/bsp_crc.cpp)

113
dm_imu/README.md Normal file
View File

@@ -0,0 +1,113 @@
# Pybind11 Wrapper for DMIMU Driver
## Overview
This project provides a **Python module** that wraps the `DmImu` class from the `dm_imu` driver library using **pybind11**.
After building, you obtain a shared object (`imu_py*.so`) that can be imported in Python to control the IMU, start data acquisition, and retrieve sensor readings as a Python `dict`.
## Prerequisites
| Requirement | Version / Notes |
|------------|-----------------|
| **C++ compiler** | GCC11 (or newer) |
| **CMake** | ≥3.14 |
| **Python** | 3.83.12 (the build uses the interpreter found by CMake) |
| **pybind11** | Installed via `pip install pybind11` (the package provides the CMake config) |
| **Serial port access** | The user must have read/write permission for the device (e.g., `/dev/ttyACM1`). See *Serial Port Permissions* below. |
## Build Steps
```bash
# 1. Install pybind11 (if not already installed)
pip install --user pybind11
# 2. Clone / copy this repository (already present in /home/allenyuan/balance)
# 3. Build the Python extension
cd pybind_imu
rm -rf build
mkdir build && cd build
cmake .. # CMake will locate pybind11 automatically
make -j$(nproc) # Builds imu_py.cpython-<ver>-<arch>.so
```
The compiled shared library will be placed in `pybind_imu/build/`.
## Installing the Module (optional)
You can copy the generated `.so` file to a location that is on `PYTHONPATH`, for example:
```bash
cp build/imu_py.cpython-310-aarch64-linux-gnu.so ~/.local/lib/python3.10/site-packages/
cp build/imu_py.cpython-310-aarch64-linux-gnu.so /home/allenyuan/miniconda3/lib/python3.13/site-packages
```
Or simply import it directly from the `build` directory (see the example below).
## Serial Port Permissions
If you encounter the error:
```
Failed to open IMU serial port: /dev/ttyACM1
```
you have two options:
1. **Run the Python script with sudo** (quick test):
```bash
sudo python3 example.py
```
2. **Add your user to the dialout group** (recommended):
```bash
sudo usermod -aG dialout $USER
# Log out and log back in for the group change to take effect
```
## Usage from Python
```python
import pathlib
import importlib.util
# Load the compiled module (adjust the path if you installed it elsewhere)
lib_path = pathlib.Path('pybind_imu/build/imu_py.cpython-310-aarch64-linux-gnu.so')
spec = importlib.util.spec_from_file_location('imu_py', lib_path)
imu_py = importlib.util.module_from_spec(spec)
spec.loader.exec_module(imu_py)
# Create an IMU instance (default port and baud rate are shown)
imu = imu_py.DmImu('/dev/ttyACM1', 921600)
# Start data acquisition (spawns a background thread)
imu.start()
# Retrieve a single measurement
data = imu.getData()
print('IMU data:', data)
# When finished, stop the thread and close the serial port
imu.stop()
```
## Example Program
A readytorun example is provided as `example.py` in this directory. It demonstrates:
* Loading the module
* Starting the driver
* Reading data in a loop
* Clean shutdown on `KeyboardInterrupt`
Run it with:
```bash
python3 example.py
```
## Troubleshooting
| Symptom | Likely Cause | Fix |
|---------|--------------|-----|
| CMake cannot find `pybind11` | `pybind11` not installed or not on `CMAKE_PREFIX_PATH` | `pip install --user pybind11` and ensure `CMAKE_PREFIX_PATH` points to `~/.local/lib/python3.10/site-packages/pybind11/share/cmake` (the CMakeLists already sets this). |
| Compilation error: `dm_imu/imu_driver.h: No such file or directory` | Include path wrong | The CMake file uses `include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)` which points to the project root where `dm_imu` resides. |
| Runtime: “Failed to open IMU serial port” | No permission on the device | Use `sudo` or add user to `dialout` group (see above). |
| Python import error “module not found” | Wrong path to the `.so` file | Adjust `lib_path` in the Python code to point to the actual location of the compiled shared object. |
---
**Enjoy!** Feel free to adapt the wrapper for additional driver functionality or integrate it into larger Python applications. If you have any questions, open an issue in the repository or contact the maintainer.

47
dm_imu/__init__.py Normal file
View File

@@ -0,0 +1,47 @@
import pathlib
import importlib.util
import sys
def _load_imu_module():
"""
Load the compiled pybind11 module ``imu_py``.
The shared object should be placed in the ``build`` directory next to this ``__init__.py``.
"""
# 1. Try to import if the .so is already in the package directory.
try:
from . import imu_py # type: ignore # pylint: disable=import-error
return imu_py
except Exception:
pass
# 2. Fallback: locate the build output relative to the package root.
possible_paths = [
pathlib.Path(__file__).parent / "build" / "imu_py.cpython-310-darwin.so",
pathlib.Path(__file__).parent / "build" / "imu_py.cpython-312-darwin.so",
# Generic pattern for other Python versions / architectures
pathlib.Path(__file__).parent / "build" / "imu_py.*.so",
]
for p in possible_paths:
# glob pattern handling for the generic case
if "*" in str(p):
matches = list(p.parent.glob(p.name))
if matches:
p = matches[0]
else:
continue
if p.is_file():
spec = importlib.util.spec_from_file_location("imu_py", p)
imu_py = importlib.util.module_from_spec(spec)
spec.loader.exec_module(imu_py) # type: ignore
return imu_py
raise ImportError(
"Unable to locate the compiled 'imu_py' module. "
"Make sure the package is installed with the compiled extension "
"or run the build step before importing."
)
# Expose the DmImu class at package level for convenience
_imu_mod = _load_imu_module()
DmImu = _imu_mod.DmImu

36
dm_imu/pybind_imu.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "imu_driver.h"
namespace py = pybind11;
using namespace dmbot_serial;
/* Convert IMU_Data to a Python dict */
py::dict imu_data_to_dict(const IMU_Data &data) {
py::dict d;
d["accx"] = data.accx;
d["accy"] = data.accy;
d["accz"] = data.accz;
d["gyrox"] = data.gyrox;
d["gyroy"] = data.gyroy;
d["gyroz"] = data.gyroz;
d["roll"] = data.roll;
d["pitch"] = data.pitch;
d["yaw"] = data.yaw;
return d;
}
PYBIND11_MODULE(imu_py, m) {
m.doc() = "Python bindings for DMIMU driver";
py::class_<DmImu>(m, "DmImu")
.def(py::init<const std::string&, int>(),
py::arg("port") = "/dev/ttyACM1",
py::arg("baud") = 921600)
.def("start", &DmImu::start)
.def("stop", &DmImu::stop)
.def("getData",
[](const DmImu &self) {
return imu_data_to_dict(self.getData());
});
}

73
dm_imu/src/bsp_crc.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include "bsp_crc.h"
const uint8_t CRC8_table[256] =
{
0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41,
0x9d, 0xc3, 0x21, 0x7f, 0xfc, 0xa2, 0x40, 0x1e, 0x5f, 0x01, 0xe3, 0xbd, 0x3e, 0x60, 0x82, 0xdc,
0x23, 0x7d, 0x9f, 0xc1, 0x42, 0x1c, 0xfe, 0xa0, 0xe1, 0xbf, 0x5d, 0x03, 0x80, 0xde, 0x3c, 0x62,
0xbe, 0xe0, 0x02, 0x5c, 0xdf, 0x81, 0x63, 0x3d, 0x7c, 0x22, 0xc0, 0x9e, 0x1d, 0x43, 0xa1, 0xff,
0x46, 0x18, 0xfa, 0xa4, 0x27, 0x79, 0x9b, 0xc5, 0x84, 0xda, 0x38, 0x66, 0xe5, 0xbb, 0x59, 0x07,
0xdb, 0x85, 0x67, 0x39, 0xba, 0xe4, 0x06, 0x58, 0x19, 0x47, 0xa5, 0xfb, 0x78, 0x26, 0xc4, 0x9a,
0x65, 0x3b, 0xd9, 0x87, 0x04, 0x5a, 0xb8, 0xe6, 0xa7, 0xf9, 0x1b, 0x45, 0xc6, 0x98, 0x7a, 0x24,
0xf8, 0xa6, 0x44, 0x1a, 0x99, 0xc7, 0x25, 0x7b, 0x3a, 0x64, 0x86, 0xd8, 0x5b, 0x05, 0xe7, 0xb9,
0x8c, 0xd2, 0x30, 0x6e, 0xed, 0xb3, 0x51, 0x0f, 0x4e, 0x10, 0xf2, 0xac, 0x2f, 0x71, 0x93, 0xcd,
0x11, 0x4f, 0xad, 0xf3, 0x70, 0x2e, 0xcc, 0x92, 0xd3, 0x8d, 0x6f, 0x31, 0xb2, 0xec, 0x0e, 0x50,
0xaf, 0xf1, 0x13, 0x4d, 0xce, 0x90, 0x72, 0x2c, 0x6d, 0x33, 0xd1, 0x8f, 0x0c, 0x52, 0xb0, 0xee,
0x32, 0x6c, 0x8e, 0xd0, 0x53, 0x0d, 0xef, 0xb1, 0xf0, 0xae, 0x4c, 0x12, 0x91, 0xcf, 0x2d, 0x73,
0xca, 0x94, 0x76, 0x28, 0xab, 0xf5, 0x17, 0x49, 0x08, 0x56, 0xb4, 0xea, 0x69, 0x37, 0xd5, 0x8b,
0x57, 0x09, 0xeb, 0xb5, 0x36, 0x68, 0x8a, 0xd4, 0x95, 0xcb, 0x29, 0x77, 0xf4, 0xaa, 0x48, 0x16,
0xe9, 0xb7, 0x55, 0x0b, 0x88, 0xd6, 0x34, 0x6a, 0x2b, 0x75, 0x97, 0xc9, 0x4a, 0x14, 0xf6, 0xa8,
0x74, 0x2a, 0xc8, 0x96, 0x15, 0x4b, 0xa9, 0xf7, 0xb6, 0xe8, 0x0a, 0x54, 0xd7, 0x89, 0x6b, 0x35,
};
const uint16_t CRC16_table[256] =
{
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
};
/**
* @brief 计算CRC8
* @param[in] ess_value: crc初始值
* @param[in] ptr: 数据指针
* @param[in] len: 校验长度
* @retval CRC-8值
*/
uint8_t Get_CRC8(uint8_t init_value ,uint8_t *ptr, uint8_t len)
{
uint8_t uc_index;
uint8_t ucCRC8 = init_value;
while (len--)
{
uc_index = ucCRC8^(*ptr++);
ucCRC8 = CRC8_table[uc_index];
}
return(ucCRC8);
}
uint16_t Get_CRC16(uint8_t *ptr, uint16_t len)
{
// ISO/IEC 13239 的初始值
uint16_t crc = 0xFFFF;
for (size_t i = 0; i < len; ++i)
{
uint8_t index = (crc >> 8 ^ ptr[i]);
crc = ((crc << 1) ^ CRC16_table[index]);
}
return crc;
}

10
dm_imu/src/bsp_crc.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef __BSP_CRC_H
#define __BSP_CRC_H
#include <iostream>
uint8_t Get_CRC8(uint8_t init_value ,uint8_t *ptr, uint8_t len);
uint16_t Get_CRC16(uint8_t *ptr, uint16_t len);
#endif

1001
dm_imu/src/imu_data.csv Normal file

File diff suppressed because it is too large Load Diff

318
dm_imu/src/imu_driver.cpp Normal file
View File

@@ -0,0 +1,318 @@
#include "imu_driver.h"
#include <chrono>
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#ifndef B460800
#define B460800 460800
#endif
#ifndef B921600
#define B921600 921600
#endif
namespace dmbot_serial
{
DmImu::DmImu(const std::string& port, int baud)
: imu_serial_port(port), imu_seial_baud(baud), stop_thread_(false)
{
// 初始化串口并完成 IMU 配置
init_imu_serial();
// 进入配置模式并初始化 IMU
enter_setting_mode();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
turn_on_accel();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
turn_on_gyro();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
turn_on_euler();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
turn_off_quat();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
set_output_1000HZ();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
save_imu_para();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
exit_setting_mode();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
DmImu::~DmImu()
{
stop();
if (serial_fd >= 0)
{
close(serial_fd);
serial_fd = -1;
}
}
// -------------------------------
// Public API
// -------------------------------
bool DmImu::start()
{
if (rec_thread.joinable())
{
// 已经在运行
return true;
}
// 确保串口已打开
if (serial_fd < 0)
{
init_imu_serial();
}
stop_thread_ = false;
rec_thread = std::thread(&DmImu::get_imu_data_thread, this);
return true;
}
void DmImu::stop()
{
stop_thread_ = true;
if (rec_thread.joinable())
{
rec_thread.join();
}
if (serial_fd >= 0)
{
close(serial_fd);
serial_fd = -1;
}
}
IMU_Data DmImu::getData() const
{
std::lock_guard<std::mutex> lock(data_mutex);
return data;
}
// -------------------------------
// Private implementation
// -------------------------------
void DmImu::init_imu_serial()
{
// 打开串口
serial_fd = open(imu_serial_port.c_str(), O_RDWR | O_NOCTTY | O_SYNC);
if (serial_fd < 0)
{
std::cerr << "Failed to open IMU serial port: " << imu_serial_port << std::endl;
std::exit(EXIT_FAILURE);
}
// 配置波特率和串口属性
struct termios tty;
if (tcgetattr(serial_fd, &tty) != 0)
{
std::cerr << "Error from tcgetattr" << std::endl;
std::exit(EXIT_FAILURE);
}
// 设置波特率
speed_t speed;
switch (imu_seial_baud)
{
case 115200: speed = B115200; break;
case 230400: speed = B230400; break;
case 460800: speed = B460800; break;
case 921600: speed = B921600; break;
default:
std::cerr << "Unsupported baud rate: " << imu_seial_baud << std::endl;
std::exit(EXIT_FAILURE);
}
cfsetospeed(&tty, speed);
cfsetispeed(&tty, speed);
// 8N1, 无硬件流控制
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8 位字符
tty.c_iflag &= ~IGNBRK; // 禁用 BREAK 处理
tty.c_lflag = 0; // 无信号字符、回显、规范模式
tty.c_oflag = 0; // 原始输出
tty.c_cc[VMIN] = 0; // 读取不阻塞
tty.c_cc[VTIME] = 5; // 0.5 秒读取超时
tty.c_cflag |= (CLOCAL | CREAD); // 忽略调制解调器控制线,启用接收
tty.c_cflag &= ~(PARENB | PARODD); // 无奇偶校验
tty.c_cflag &= ~CSTOPB; // 1 位停止位
tty.c_cflag &= ~CRTSCTS; // 无硬件流控制
if (tcsetattr(serial_fd, TCSANOW, &tty) != 0)
{
std::cerr << "Error from tcsetattr" << std::endl;
std::exit(EXIT_FAILURE);
}
std::cout << "IMU serial port opened successfully." << std::endl;
}
// -------------------------------
// 配置指令(与原始 ROS 代码保持一致,仅去掉 ros::Duration
// -------------------------------
void DmImu::enter_setting_mode()
{
uint8_t txbuf[4] = {0xAA, 0x06, 0x01, 0x0D};
for (int i = 0; i < 5; ++i)
{
write(serial_fd, txbuf, sizeof(txbuf));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void DmImu::turn_on_accel()
{
uint8_t txbuf[4] = {0xAA, 0x01, 0x14, 0x0D};
for (int i = 0; i < 5; ++i)
{
write(serial_fd, txbuf, sizeof(txbuf));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void DmImu::turn_on_gyro()
{
uint8_t txbuf[4] = {0xAA, 0x01, 0x15, 0x0D};
for (int i = 0; i < 5; ++i)
{
write(serial_fd, txbuf, sizeof(txbuf));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void DmImu::turn_on_euler()
{
uint8_t txbuf[4] = {0xAA, 0x01, 0x16, 0x0D};
for (int i = 0; i < 5; ++i)
{
write(serial_fd, txbuf, sizeof(txbuf));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void DmImu::turn_off_quat()
{
uint8_t txbuf[4] = {0xAA, 0x01, 0x07, 0x0D};
for (int i = 0; i < 5; ++i)
{
write(serial_fd, txbuf, sizeof(txbuf));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void DmImu::set_output_1000HZ()
{
uint8_t txbuf[5] = {0xAA, 0x02, 0x01, 0x00, 0x0D};
for (int i = 0; i < 5; ++i)
{
write(serial_fd, txbuf, sizeof(txbuf));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void DmImu::save_imu_para()
{
uint8_t txbuf[4] = {0xAA, 0x03, 0x01, 0x0D};
for (int i = 0; i < 5; ++i)
{
write(serial_fd, txbuf, sizeof(txbuf));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void DmImu::exit_setting_mode()
{
uint8_t txbuf[4] = {0xAA, 0x06, 0x00, 0x0D};
for (int i = 0; i < 5; ++i)
{
write(serial_fd, txbuf, sizeof(txbuf));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void DmImu::restart_imu()
{
uint8_t txbuf[4] = {0xAA, 0x00, 0x00, 0x0D};
for (int i = 0; i < 5; ++i)
{
write(serial_fd, txbuf, sizeof(txbuf));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
// -------------------------------
// 数据采集线程
// -------------------------------
void DmImu::get_imu_data_thread()
{
int error_num = 0;
while (!stop_thread_)
{
if (serial_fd < 0)
{
std::cerr << "IMU serial port not open in data thread." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
// 读取完整帧(共 57 字节)
ssize_t len = read(serial_fd, &receive_data, sizeof(receive_data));
if (len != static_cast<ssize_t>(sizeof(receive_data)))
{
++error_num;
continue;
}
// 验证帧头
if (receive_data.FrameHeader1 == 0x55 && receive_data.flag1 == 0xAA &&
receive_data.slave_id1 == 0x01 && receive_data.reg_acc == 0x01)
{
// CRC 校验并提取数据
if (Get_CRC16(reinterpret_cast<uint8_t*>(&receive_data.FrameHeader1), 16) == receive_data.crc1)
{
data.accx = *reinterpret_cast<float*>(&receive_data.accx_u32);
data.accy = *reinterpret_cast<float*>(&receive_data.accy_u32);
data.accz = *reinterpret_cast<float*>(&receive_data.accz_u32);
}
if (Get_CRC16(reinterpret_cast<uint8_t*>(&receive_data.FrameHeader2), 16) == receive_data.crc2)
{
data.gyrox = *reinterpret_cast<float*>(&receive_data.gyrox_u32);
data.gyroy = *reinterpret_cast<float*>(&receive_data.gyroy_u32);
data.gyroz = *reinterpret_cast<float*>(&receive_data.gyroz_u32);
}
if (Get_CRC16(reinterpret_cast<uint8_t*>(&receive_data.FrameHeader3), 16) == receive_data.crc3)
{
data.roll = *reinterpret_cast<float*>(&receive_data.roll_u32);
data.pitch = *reinterpret_cast<float*>(&receive_data.pitch_u32);
data.yaw = *reinterpret_cast<float*>(&receive_data.yaw_u32);
}
// 线程安全更新共享数据
{
std::lock_guard<std::mutex> lock(data_mutex);
// data 已在上面更新,无需额外操作
}
}
else
{
++error_num;
if (error_num > 1200)
{
std::cerr << "Failed to find correct IMU frame header (0x55)." << std::endl;
error_num = 0;
}
}
}
}
}

103
dm_imu/src/imu_driver.h Normal file
View File

@@ -0,0 +1,103 @@
#ifndef _IMU_DRIVER_H_
#define _IMU_DRIVER_H_
#include <iostream>
#include <thread>
#include <initializer_list>
#include <fstream>
#include <array>
#include <mutex>
#include <atomic>
#include <math.h>
#include "bsp_crc.h"
namespace dmbot_serial
{
#pragma pack(1)
typedef struct
{
uint8_t FrameHeader1;
uint8_t flag1;
uint8_t slave_id1;
uint8_t reg_acc;
uint32_t accx_u32;
uint32_t accy_u32;
uint32_t accz_u32;
uint16_t crc1;
uint8_t FrameEnd1;
uint8_t FrameHeader2;
uint8_t flag2;
uint8_t slave_id2;
uint8_t reg_gyro;
uint32_t gyrox_u32;
uint32_t gyroy_u32;
uint32_t gyroz_u32;
uint16_t crc2;
uint8_t FrameEnd2;
uint8_t FrameHeader3;
uint8_t flag3;
uint8_t slave_id3;
uint8_t reg_euler; // r-p-y
uint32_t roll_u32;
uint32_t pitch_u32;
uint32_t yaw_u32;
uint16_t crc3;
uint8_t FrameEnd3;
} IMU_Receive_Frame;
#pragma pack()
typedef struct
{
float accx;
float accy;
float accz;
float gyrox;
float gyroy;
float gyroz;
float roll;
float pitch;
float yaw;
} IMU_Data;
class DmImu
{
public:
DmImu(const std::string& port = "/dev/ttyACM1", int baud = 921600);
~DmImu();
// Start data acquisition thread; returns true on success
bool start();
// Stop acquisition and close serial port
void stop();
// Get latest IMU data (threadsafe copy)
IMU_Data getData() const;
private:
void init_imu_serial();
void get_imu_data_thread();
void enter_setting_mode();
void turn_on_accel();
void turn_on_gyro();
void turn_on_euler();
void turn_off_quat();
void set_output_1000HZ();
void save_imu_para();
void exit_setting_mode();
void restart_imu();
int imu_seial_baud;
std::string imu_serial_port;
int serial_fd = -1;
std::thread rec_thread;
mutable std::mutex data_mutex;
bool stop_thread_ = false;
IMU_Receive_Frame receive_data{};
IMU_Data data{};
};
}
#endif

BIN
dm_imu/src/imu_plot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

119
dm_imu/src/plot_imu.py Normal file
View File

@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""
绘制 dm_imu/test_imu.cpp 生成的 CSV 数据。
使用方式:
1. 确保已安装 matplotlibpip install matplotlib
2. 运行 C++ 程序生成 imu_data.csv。
3. 执行本脚本:
python3 dm_imu/plot_imu.py
4. 脚本会在同目录下生成 `imu_plot.png` 并显示图形窗口(如果有可视化环境)。
"""
import csv
import os
import sys
import matplotlib.pyplot as plt
def read_csv(csv_path):
"""读取 CSV返回每列的数据列表。"""
if not os.path.isfile(csv_path):
print(f"错误:未找到 CSV 文件 {csv_path}", file=sys.stderr)
sys.exit(1)
indices = []
roll = []
pitch = []
yaw = []
accx = []
accy = []
accz = []
gyrox = []
gyroy = []
gyroz = []
with open(csv_path, newline='') as f:
reader = csv.DictReader(f)
for row in reader:
indices.append(int(row['index']))
roll.append(float(row['roll']))
pitch.append(float(row['pitch']))
yaw.append(float(row['yaw']))
accx.append(float(row['accx']))
accy.append(float(row['accy']))
accz.append(float(row['accz']))
gyrox.append(float(row['gyrox']))
gyroy.append(float(row['gyroy']))
gyroz.append(float(row['gyroz']))
return {
'index': indices,
'roll': roll,
'pitch': pitch,
'yaw': yaw,
'accx': accx,
'accy': accy,
'accz': accz,
'gyrox': gyrox,
'gyroy': gyroy,
'gyroz': gyroz,
}
def plot_data(data, out_path):
"""绘制三组曲线(姿态、加速度、陀螺仪)并保存为 PNG。"""
idx = data['index']
plt.figure(figsize=(12, 9))
# 1. 姿态Roll, Pitch, Yaw
ax1 = plt.subplot(3, 1, 1)
ax1.plot(idx, data['roll'], label='Roll')
ax1.plot(idx, data['pitch'], label='Pitch')
ax1.plot(idx, data['yaw'], label='Yaw')
ax1.set_ylabel('Angle (°)')
ax1.set_title('IMU 姿态')
ax1.legend()
ax1.grid(True)
# 2. 加速度X, Y, Z
ax2 = plt.subplot(3, 1, 2)
ax2.plot(idx, data['accx'], label='Acc X')
ax2.plot(idx, data['accy'], label='Acc Y')
ax2.plot(idx, data['accz'], label='Acc Z')
ax2.set_ylabel('Acceleration (g)')
ax2.set_title('加速度')
ax2.legend()
ax2.grid(True)
# 3. 陀螺仪X, Y, Z
ax3 = plt.subplot(3, 1, 3)
ax3.plot(idx, data['gyrox'], label='Gyro X')
ax3.plot(idx, data['gyroy'], label='Gyro Y')
ax3.plot(idx, data['gyroz'], label='Gyro Z')
ax3.set_xlabel('Sample Index')
ax3.set_ylabel('Angular Velocity (°/s)')
ax3.set_title('陀螺仪')
ax3.legend()
ax3.grid(True)
plt.tight_layout()
plt.savefig(out_path)
print(f"绘图已保存至 {out_path}")
# 如果当前环境支持图形界面,显示窗口
try:
plt.show()
except Exception:
pass
def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
csv_path = os.path.join(script_dir, 'imu_data.csv')
out_path = os.path.join(script_dir, 'imu_plot.png')
data = read_csv(csv_path)
plot_data(data, out_path)
if __name__ == '__main__':
main()

52
dm_imu/src/test_imu.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include "imu_driver.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <fstream>
int main()
{
// 根据实际设备路径与波特率创建 IMU 对象
dmbot_serial::DmImu imu("/dev/ttyACM0", 921600);
// 启动采集线程
if (!imu.start())
{
std::cerr << "Failed to start IMU driver." << std::endl;
return 1;
}
// 打开 CSV 文件用于保存数据
std::ofstream csvFile("dm_imu/imu_data.csv");
csvFile << "index,roll,pitch,yaw,accx,accy,accz,gyrox,gyroy,gyroz\n";
// 示例:读取 1000 次数据(约 10 秒100 Hz
for (int i = 0; i < 1000; ++i)
{
dmbot_serial::IMU_Data data = imu.getData();
std::cout << "Roll: " << data.roll << " Pitch: " << data.pitch
<< " Yaw: " << data.yaw << std::endl;
std::cout << "Acc : [" << data.accx << ", " << data.accy << ", " << data.accz << "]"
<< std::endl;
std::cout << "Gyro : [" << data.gyrox << ", " << data.gyroy << ", " << data.gyroz << "]"
<< std::endl;
std::cout << "----------------------------------------" << std::endl;
// 写入 CSV
csvFile << i << ',' << data.roll << ',' << data.pitch << ',' << data.yaw << ','
<< data.accx << ',' << data.accy << ',' << data.accz << ','
<< data.gyrox << ',' << data.gyroy << ',' << data.gyroz << '\n';
// 10 ms 间隔(约 100 Hz
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
csvFile.close();
// 停止采集并关闭串口
imu.stop();
std::system("python3 dm_imu/plot_imu.py");
return 0;
}