├── interfaces
├── srv
│ └── ImuReset.srv
├── CMakeLists.txt
└── package.xml
├── iahrs_driver
├── launch
│ ├── iahrs_driver_launch.xml
│ └── iahrs_driver.py
├── package.xml
├── CMakeLists.txt
└── src
│ └── iahrs_driver.cpp
└── README.md
/interfaces/srv/ImuReset.srv:
--------------------------------------------------------------------------------
1 | #This message is used to send command to IAHRS(IMU)
2 | #Euler_angle Reset command
3 |
4 | ---
5 | bool result
6 |
--------------------------------------------------------------------------------
/iahrs_driver/launch/iahrs_driver_launch.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/interfaces/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.5)
2 |
3 | project(interfaces)
4 |
5 | # Default to C++14
6 | if(NOT CMAKE_CXX_STANDARD)
7 | set(CMAKE_CXX_STANDARD 14)
8 | endif()
9 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
10 | add_compile_options(-Wall -Wextra -Wpedantic)
11 | endif()
12 |
13 | find_package(ament_cmake REQUIRED)
14 | find_package(builtin_interfaces REQUIRED)
15 | find_package(std_msgs REQUIRED)
16 | find_package(std_srvs REQUIRED)
17 | find_package(geometry_msgs REQUIRED)
18 | find_package(rosidl_default_generators REQUIRED)
19 |
20 | rosidl_generate_interfaces(${PROJECT_NAME}
21 | "srv/ImuReset.srv"
22 | DEPENDENCIES builtin_interfaces geometry_msgs std_msgs std_srvs
23 | )
24 |
25 |
26 | ament_export_dependencies(rosidl_default_runtime)
27 | ament_package()
28 |
--------------------------------------------------------------------------------
/interfaces/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | interfaces
5 | 0.0.1
6 | ROS2 Interface
7 | wbjin
8 | Apache License 2.0
9 |
10 |
11 | ament_cmake
12 |
13 | rclcpp
14 | std_msgs
15 | std_srvs
16 | builtin_interfaces
17 | rosidl_default_generators
18 | geometry_msgs
19 |
20 | rosidl_interface_packages
21 |
22 |
23 | ament_cmake
24 |
25 |
26 |
--------------------------------------------------------------------------------
/iahrs_driver/launch/iahrs_driver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 |
5 | from launch import LaunchDescription
6 | from launch.actions import ExecuteProcess, IncludeLaunchDescription
7 | from launch.launch_description_sources import PythonLaunchDescriptionSource
8 | from launch.substitutions import LaunchConfiguration
9 |
10 | from launch_ros.actions import Node
11 |
12 | # this is the function launch system will look for
13 | def generate_launch_description():
14 |
15 | iahrs_driver_node = Node(
16 | package='iahrs_driver',
17 | executable='iahrs_driver',
18 | output='screen',
19 | parameters=[
20 | {"m_bSingle_TF_option": True}
21 | ]
22 | )
23 |
24 | # create and return launch description object
25 | return LaunchDescription(
26 | [
27 | iahrs_driver_node
28 | ]
29 | )
30 |
--------------------------------------------------------------------------------
/iahrs_driver/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | iahrs_driver
5 | 0.0.1
6 | iahrs_driver ROS2 package
7 | wbjin
8 | Apache License 2.0
9 |
10 | ament_cmake
11 |
12 | rclcpp
13 | rmw_implementation
14 | std_msgs
15 | std_srvs
16 | sensor_msgs
17 | tf2
18 | tf2_ros
19 | geometry_msgs
20 | interfaces
21 |
22 | rclcpp
23 | rmw_implementation
24 | std_msgs
25 | std_srvs
26 | sensor_msgs
27 | tf2
28 | tf2_ros
29 | geometry_msgs
30 | interfaces
31 |
32 | ros2launch
33 |
34 | ament_cmake_gtest
35 | ament_lint_auto
36 | ament_lint_common
37 | rosidl_cmake
38 |
39 |
40 | ament_cmake
41 |
42 |
43 |
--------------------------------------------------------------------------------
/iahrs_driver/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.5)
2 | project(iahrs_driver)
3 |
4 | # Default to C99
5 | if(NOT CMAKE_C_STANDARD)
6 | set(CMAKE_C_STANDARD 99)
7 | endif()
8 |
9 | # Default to C++14
10 | if(NOT CMAKE_CXX_STANDARD)
11 | set(CMAKE_CXX_STANDARD 14)
12 | endif()
13 |
14 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
15 | add_compile_options(-Wall -Wextra -Wpedantic)
16 | endif()
17 |
18 | # find dependencies
19 | find_package(ament_cmake REQUIRED)
20 | find_package(rclcpp REQUIRED)
21 | find_package(rmw_implementation REQUIRED)
22 | find_package(std_srvs REQUIRED)
23 | find_package(sensor_msgs REQUIRED)
24 | find_package(tf2 REQUIRED)
25 | find_package(tf2_ros REQUIRED)
26 | find_package(tf2_geometry_msgs REQUIRED)
27 | find_package(geometry_msgs REQUIRED)
28 | find_package(visualization_msgs REQUIRED)
29 | find_package(interfaces REQUIRED) #interfaces package
30 |
31 |
32 | add_executable(${PROJECT_NAME} src/iahrs_driver.cpp)
33 |
34 | set(dependencies
35 | geometry_msgs
36 | rclcpp
37 | sensor_msgs
38 | std_msgs
39 | tf2
40 | tf2_geometry_msgs
41 | tf2_ros
42 | geometry_msgs
43 | visualization_msgs
44 | interfaces
45 | )
46 |
47 | ament_target_dependencies(${PROJECT_NAME}
48 | ${dependencies}
49 | )
50 |
51 | install(TARGETS
52 | iahrs_driver
53 | DESTINATION lib/${PROJECT_NAME}
54 | )
55 |
56 | install(
57 | DIRECTORY launch
58 | DESTINATION share/${PROJECT_NAME}
59 | )
60 |
61 |
62 | if(BUILD_TESTING)
63 | find_package(ament_lint_auto REQUIRED)
64 | ament_lint_auto_find_test_dependencies()
65 | endif()
66 |
67 | ament_export_dependencies(${dependencies})
68 |
69 | ament_package()
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iahrs_driver_ros2
2 | ros2(foxy / humble / jazzy ) iahrs cpp package
3 |
4 | ===[Device Setting]====================================
5 |
6 | -iAHRS는 USB로 연결되는데 상황에 따라서 인식되는 ttyUSB 포트의 번호가 바뀔 수 있음으로 장치 이름을 고정해서 사용할게요.
7 |
8 | ttyUSB권한 설정.
9 | USB Port에 대한 권한은 ‘sudo chmod 777 ttyUSB0’ 명령어를 통해서 줄 수 있지만, 매번 설정이 번거로우므로 dialout그룹에 추가하는 방법을 이용.
10 |
11 | sudo usermod -a -G dialout $USER
12 |
13 | ttyUSB rule의 설정을 위한 심볼릭 링크 ttyUSBx만들기
14 | 심볼릭 링크를 만드는데 필요한 정보는 아래 3가지.
15 | Vender ID
16 |
17 | Product ID
18 |
19 | Serial Number
20 |
21 | 위의 3가지 정보는 아래 2개의 명령어로 찾을 수 있어요.
22 | $ lsusb
23 |
24 | $ udevadm info -a /dev/ttyUSB0 | grep '{serial}' <-- 저는 처음에 iAHRS가 ttyUSB0으로 잡혀서 이렇게 했어요. 만약 번호가 다르면 그 번호를 입력하세요.
25 |
26 | 
27 |
28 | 위에 출력된 정보가 USB to Serial Device의 Serial Number이며, 해당 번호는 제품별로 다를 수도 있습니다.
29 |
30 | 알아낸 정보를 이용한 .rules file생성.
31 | ‘IMU.rules’ 파일을 ‘/etc/udev/rules.d’ 경로에 생성한 후에 해당 파일의 내용을 아래와 같이 작성한 후 저장.
32 |
33 | KERNEL=="ttyUSB*", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", ATTRS{serial}=="DM03L0C6", MODE:="0666", GROUP:="dialout", SYMLINK+="IMU"
34 |
35 | udev 재시작 명령어를 호출한 후에 PC의 재 시작을 해주어야 적용 됩니다.
36 | $ sudo service udev restart
37 |
38 | 
39 |
40 | 심볼릭 등록의 확인. (아래와 같은 명령어를 호출하면 위의 그림처럼 ttyUSB가 IMU로 고정된 것을 볼 수 있어요 .)
41 | $ ll /dev/
42 |
43 | ==================================================================================
44 |
45 | 1) interfaces package와 iahrs_driver package로 구성되어 있음.
46 | 2) interfaces package 먼저 빌드 후에 iahrs_driver package 빌드 후 사용
47 | 3) 서비스는 오일러 각도 초기화 기능만 구현함.
48 | 4) 설명자료 링크: https://blog.naver.com/zzang0736/223204708311
49 |
50 | 
51 |
52 |
--------------------------------------------------------------------------------
/iahrs_driver/src/iahrs_driver.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | #include "rclcpp/rclcpp.hpp"
20 | #include "std_msgs/msg/float64.hpp"
21 | #include "sensor_msgs/msg/imu.hpp"
22 | #include "sensor_msgs/msg/magnetic_field.hpp"
23 | #include "geometry_msgs/msg/transform_stamped.hpp"
24 | #include "tf2_ros/transform_broadcaster.h"
25 | #include "tf2/LinearMath/Quaternion.h"
26 |
27 | //interface package srv include...
28 | #include "interfaces/srv/imu_reset.hpp"
29 |
30 |
31 | #define SERIAL_PORT "/dev/IMU"
32 | #define SERIAL_SPEED B115200
33 |
34 | typedef struct IMU_DATA
35 | {
36 | double dQuaternion_x = 0.0;
37 | double dQuaternion_y = 0.0;
38 | double dQuaternion_z = 0.0;
39 | double dQuaternion_w = 1.0;
40 |
41 | double dAngular_velocity_x = 0.0;
42 | double dAngular_velocity_y = 0.0;
43 | double dAngular_velocity_z = 0.0;
44 |
45 | double dLinear_acceleration_x = 0.0;
46 | double dLinear_acceleration_y = 0.0;
47 | double dLinear_acceleration_z = 0.0;
48 |
49 | double dEuler_angle_Roll = 0.0;
50 | double dEuler_angle_Pitch = 0.0;
51 | double dEuler_angle_Yaw = 0.0;
52 |
53 | }IMU_DATA;
54 | IMU_DATA _pIMU_data;
55 |
56 | int serial_fd = -1;
57 | double time_offset_in_seconds;
58 | double dSend_Data[10];
59 | double m_dRoll, m_dPitch, m_dYaw;
60 | //single_used TF
61 | bool m_bSingle_TF_option = true; //false;
62 | using namespace std::chrono_literals;
63 |
64 | std::atomic stop_requested(false);
65 | void signal_handler(int signal)
66 | {
67 | stop_requested = true;
68 | printf("Signal received: %d. Preparing to shutdown...\n", signal);
69 | }
70 |
71 | //iahrs_driver class define
72 | class IAHRS : public rclcpp::Node
73 | {
74 | public:
75 | IAHRS() : Node("iahrs_driver")
76 | {
77 | tf_broadcaster = std::make_shared(this);
78 | imu_data_pub = this->create_publisher("imu/data", rclcpp::SensorDataQoS());
79 |
80 | //Service ListUp
81 | euler_angle_reset_srv_ = create_service(
82 | "all_data_reset",
83 | std::bind(&IAHRS::Euler_angle_reset_callback, this, std::placeholders::_1, std::placeholders::_2));
84 |
85 | //PARAM
86 | this->declare_parameter("m_bSingle_TF_option", rclcpp::PARAMETER_BOOL);
87 | m_bSingle_TF_option_param = this->get_parameter("m_bSingle_TF_option");
88 | //Get Param
89 | m_bSingle_TF_option = m_bSingle_TF_option_param.as_bool();
90 |
91 | }
92 |
93 | ////value//////////////////////////////////////////////////////////////////////////////
94 | std::shared_ptr tf_broadcaster;
95 | geometry_msgs::msg::TransformStamped transformStamped;
96 | sensor_msgs::msg::Imu imu_data_msg;
97 | rclcpp::Publisher::SharedPtr imu_data_pub;
98 | rclcpp::Parameter m_bSingle_TF_option_param;
99 |
100 | ////function//////////////////////////////////////////////////////////////////////////////
101 | int serial_open ()
102 | {
103 | printf ("Try to open serial: %s\n", SERIAL_PORT);
104 |
105 | serial_fd = open(SERIAL_PORT, O_RDWR|O_NOCTTY);
106 | if (serial_fd < 0)
107 | {
108 | printf ("Error unable to open %s\n", SERIAL_PORT);
109 | return -1;
110 | }
111 | printf ("%s open success\n", SERIAL_PORT);
112 |
113 | struct termios tio;
114 | tcgetattr(serial_fd, &tio);
115 | cfmakeraw(&tio);
116 | tio.c_cflag = CS8|CLOCAL|CREAD;
117 | tio.c_iflag &= ~(IXON | IXOFF);
118 | cfsetspeed(&tio, SERIAL_SPEED);
119 | tio.c_cc[VTIME] = 0;
120 | tio.c_cc[VMIN] = 0;
121 |
122 | int err = tcsetattr(serial_fd, TCSAFLUSH, &tio);
123 | if (err != 0)
124 | {
125 | printf ("Error tcsetattr() function return error\n");
126 | close(serial_fd);
127 | serial_fd = -1;
128 | return -1;
129 | }
130 | return 0;
131 | }
132 |
133 | static unsigned long GetTickCount()
134 | {
135 | struct timespec ts;
136 |
137 | clock_gettime (CLOCK_MONOTONIC, &ts);
138 |
139 | return ts.tv_sec*1000 + ts.tv_nsec/1000000;
140 | }
141 |
142 | int SendRecv(const char* command, double* returned_data, int data_length)
143 | {
144 | #define COMM_RECV_TIMEOUT 30
145 |
146 | char temp_buff[256];
147 | read (serial_fd, temp_buff, 256);
148 |
149 | int command_len = strlen(command);
150 | int n = write(serial_fd, command, command_len);
151 |
152 | if (n < 0) return -1;
153 |
154 | const int buff_size = 1024;
155 | int recv_len = 0;
156 | char recv_buff[buff_size + 1];
157 |
158 | unsigned long time_start = GetTickCount();
159 |
160 | while (recv_len < buff_size)
161 | {
162 | int n = read (serial_fd, recv_buff + recv_len, buff_size - recv_len);
163 | if (n < 0)
164 | {
165 | return -1;
166 | }
167 | else if (n == 0)
168 | {
169 | usleep(1000);
170 | }
171 | else if (n > 0)
172 | {
173 | recv_len += n;
174 |
175 | if (recv_buff[recv_len - 1] == '\r' || recv_buff[recv_len - 1] == '\n')
176 | {
177 | break;
178 | }
179 | }
180 |
181 | unsigned long time_current = GetTickCount();
182 | unsigned long time_delta = time_current - time_start;
183 |
184 | if (time_delta >= COMM_RECV_TIMEOUT) break;
185 | }
186 | recv_buff[recv_len] = '\0';
187 |
188 | if (recv_len > 0)
189 | {
190 | if (recv_buff[0] == '!')
191 | {
192 | return -1;
193 | }
194 | }
195 |
196 | if (strncmp(command, recv_buff, command_len - 1) == 0)
197 | {
198 | if (recv_buff[command_len - 1] == '=') {
199 | int data_count = 0;
200 |
201 | char* p = &recv_buff[command_len];
202 | char* pp = NULL;
203 |
204 | for (int i = 0; i < data_length; i++)
205 | {
206 | if (p[0] == '0' && p[1] == 'x')
207 | {
208 | returned_data[i] = strtol(p+2, &pp, 16);
209 | data_count++;
210 | }
211 | else
212 | {
213 | returned_data[i] = strtod(p, &pp);
214 | data_count++;
215 | }
216 |
217 | if (*pp == ',')
218 | {
219 | p = pp + 1;
220 | }
221 | else
222 | {
223 | break;
224 | }
225 | }
226 | return data_count;
227 | }
228 | }
229 | return 0;
230 | }
231 |
232 | private:
233 |
234 | rclcpp::Service::SharedPtr euler_angle_reset_srv_;
235 |
236 | bool Euler_angle_reset_callback(
237 | const std::shared_ptr request,
238 | const std::shared_ptr response)
239 | {
240 | bool bResult = false;
241 | double dSend_Data[10];
242 | SendRecv("ra\n", dSend_Data, 10);
243 | bResult = true;
244 | response->result = bResult;
245 | return true;
246 | }
247 |
248 |
249 | };
250 |
251 |
252 | int main (int argc, char** argv)
253 | {
254 | rclcpp::init(argc, argv);
255 | std::signal(SIGINT, signal_handler);
256 | // Create a function for when messages are to be sent.
257 | setvbuf(stdout, NULL, _IONBF, BUFSIZ);
258 | auto node = std::make_shared();
259 |
260 | // These values do not need to be converted
261 | node->imu_data_msg.linear_acceleration_covariance[0] = 0.0064;
262 | node->imu_data_msg.linear_acceleration_covariance[4] = 0.0063;
263 | node->imu_data_msg.linear_acceleration_covariance[8] = 0.0064;
264 | node->imu_data_msg.angular_velocity_covariance[0] = 0.032*(M_PI/180.0);
265 | node->imu_data_msg.angular_velocity_covariance[4] = 0.028*(M_PI/180.0);
266 | node->imu_data_msg.angular_velocity_covariance[8] = 0.006*(M_PI/180.0);
267 | node->imu_data_msg.orientation_covariance[0] = 0.013*(M_PI/180.0);
268 | node->imu_data_msg.orientation_covariance[4] = 0.011*(M_PI/180.0);
269 | node->imu_data_msg.orientation_covariance[8] = 0.006*(M_PI/180.0);
270 |
271 |
272 | rclcpp::WallRate loop_rate(100);
273 | node->serial_open();
274 | node->SendRecv("za\n", dSend_Data, 10); // Euler Angle -> '0.0' Reset
275 | usleep(10000);
276 | printf(" | Z axis \n");
277 | printf(" | \n");
278 | printf(" | / X axis \n");
279 | printf(" ____|__/____ \n");
280 | printf(" Y axis / * | / /| \n");
281 | printf(" _________ /______|/ // \n");
282 | printf(" /___________ // \n");
283 | printf(" |____iahrs___|/ \n");
284 |
285 | while(rclcpp::ok() && !stop_requested)
286 | {
287 | rclcpp::spin_some(node);
288 | if (serial_fd >= 0)
289 | {
290 | const int max_data = 10;
291 | double data[max_data];
292 | int no_data = 0;
293 | no_data = node->SendRecv("g\n", data, max_data); // Read angular_velocity _ wx, wy, wz
294 | if (no_data >= 3)
295 | {
296 | // angular_velocity
297 | node->imu_data_msg.angular_velocity.x = _pIMU_data.dAngular_velocity_x = data[0]*(M_PI/180.0);
298 | node->imu_data_msg.angular_velocity.y = _pIMU_data.dAngular_velocity_y = data[1]*(M_PI/180.0);
299 | node->imu_data_msg.angular_velocity.z = _pIMU_data.dAngular_velocity_z = data[2]*(M_PI/180.0);
300 | }
301 | no_data = node->SendRecv("a\n", data, max_data); // Read linear_acceleration unit: m/s^2
302 | if (no_data >= 3)
303 | {
304 | //// linear_acceleration g to m/s^2
305 | node->imu_data_msg.linear_acceleration.x = _pIMU_data.dLinear_acceleration_x = data[0] * 9.80665;
306 | node->imu_data_msg.linear_acceleration.y = _pIMU_data.dLinear_acceleration_y = data[1] * 9.80665;
307 | node->imu_data_msg.linear_acceleration.z = _pIMU_data.dLinear_acceleration_z = data[2] * 9.80665;
308 | }
309 | no_data = node->SendRecv("e\n", data, max_data); // Read Euler angle
310 | if (no_data >= 3)
311 | {
312 | // Euler _ rad
313 | _pIMU_data.dEuler_angle_Roll = data[0]*(M_PI/180.0);
314 | _pIMU_data.dEuler_angle_Pitch = data[1]*(M_PI/180.0);
315 | _pIMU_data.dEuler_angle_Yaw = data[2]*(M_PI/180.0);
316 | }
317 |
318 | tf2::Quaternion q;
319 | q.setRPY(_pIMU_data.dEuler_angle_Roll , _pIMU_data.dEuler_angle_Pitch, _pIMU_data.dEuler_angle_Yaw);
320 | // orientation
321 | node->imu_data_msg.orientation.x = q.x();
322 | node->imu_data_msg.orientation.y = q.y();
323 | node->imu_data_msg.orientation.z = q.z();
324 | node->imu_data_msg.orientation.w = q.w();
325 | node->imu_data_msg.header.stamp = node->now();
326 | node->imu_data_msg.header.frame_id = "imu_link";
327 |
328 | // publish the IMU data
329 | node->imu_data_pub->publish(node->imu_data_msg);
330 |
331 | if(m_bSingle_TF_option)
332 | {
333 | // Update the timestamp of the transform
334 | node->transformStamped.header.stamp = node->now();
335 | node->transformStamped.header.frame_id = "base_link"; // Parent frame ID
336 | node->transformStamped.child_frame_id = "imu_link"; // IMU frame ID
337 | // Set the transformation translation (position)
338 | node->transformStamped.transform.translation.x = 0.0;
339 | node->transformStamped.transform.translation.y = 0.0;
340 | node->transformStamped.transform.translation.z = 0.2;
341 | node->transformStamped.transform.rotation.x = q.x();
342 | node->transformStamped.transform.rotation.y = q.y();
343 | node->transformStamped.transform.rotation.z = q.z();
344 | node->transformStamped.transform.rotation.w = q.w();
345 | // Publish the transform
346 | node->tf_broadcaster->sendTransform(node->transformStamped);
347 | }
348 | }
349 |
350 | loop_rate.sleep();
351 | }
352 |
353 | close (serial_fd);
354 | rclcpp::shutdown();
355 |
356 | return 0;
357 | }
358 |
--------------------------------------------------------------------------------