Skip to content

Commit df6450f

Browse files
committed
added wheel's odometry with ekf fusion example
1 parent 5ced701 commit df6450f

7 files changed

+537
-24
lines changed

arduino/hall_odom_2wheels_PCINT/hall_odom_2wheels_PCINT.ino

+39-10
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
byte last_CH1_state, last_CH2_state, last_CH3_state, last_CH4_state;
44
unsigned long counter_1, counter_2, counter_3, counter_4, current_time;
55
unsigned long last_time_fall_1, last_time_fall_2, last_time_fall_3, last_time_fall_4;
6+
unsigned long last_stamped_L, last_stamped_R;
67
const byte ledPin = 13;
78
volatile byte led_state = LOW;
89
int magnets = 8;
10+
float R_wheel = 0.15;
11+
float circular_dist = 0.9424777961; // this came from 2*pi*R_wheel
12+
float move_in_one_trig = 0.1178125;
913
/////////////////////////////////
1014
/////////// Left wheel //////////
1115
/////////////////////////////////
@@ -19,6 +23,10 @@ volatile float prev_rpm_L = 0.0;
1923
float time_passed_L;
2024
float first_count_time_L;
2125
float hall_timeout_L;
26+
float out_rpm_L;
27+
int counter_L = 0;
28+
float VL = 0.0;
29+
float out_VL;
2230

2331
//////////////////////////////////
2432
/////////// Right wheel //////////
@@ -33,13 +41,15 @@ volatile float prev_rpm_R = 0.0;
3341
float time_passed_R;
3442
float first_count_time_R;
3543
float hall_timeout_R;
36-
float out_rpm_L;
3744
float out_rpm_R;
45+
int counter_R = 0;
46+
float VR = 0.0;
47+
float out_VR;
3848

3949
////////////////////////////////////////////
4050
/////////////// JSON object ////////////////
4151
////////////////////////////////////////////
42-
const size_t CAPACITY = JSON_OBJECT_SIZE(4);
52+
const size_t CAPACITY = JSON_OBJECT_SIZE(6);
4353
StaticJsonDocument<CAPACITY> doc;
4454

4555
void setup() {
@@ -65,14 +75,18 @@ void loop() {
6575

6676
if (fwd_dir_L == 0){
6777
out_rpm_L = -rpm_L;
78+
out_VL = -VL;
6879
} else{
6980
out_rpm_L = rpm_L;
81+
out_VL = VL;
7082
}
7183

7284
if (fwd_dir_R == 0){
7385
out_rpm_R = -rpm_R;
86+
out_VR = -VR;
7487
} else{
7588
out_rpm_R = rpm_R;
89+
out_VR = VR;
7690
}
7791

7892
digitalWrite(ledPin, led_state);
@@ -84,8 +98,10 @@ void loop() {
8498

8599
doc["rpm_L"] = out_rpm_L;
86100
doc["rpm_R"] = out_rpm_R;
87-
doc["count_L"] = count_L;
88-
doc["count_R"] = count_R;
101+
doc["counter_L"] = counter_L;
102+
doc["counter_R"] = counter_R;
103+
doc["VL"] = out_VL;
104+
doc["VR"] = out_VR;
89105
serializeJson(doc, Serial);
90106
Serial.println();
91107

@@ -98,12 +114,14 @@ void loop() {
98114
rpm_L = 0.0;
99115
prev_rpm_L = 0.0;
100116
count_L = 0;
117+
VL = 0.0;
101118
}
102119

103120
if (hall_timeout_R > 1.2){
104121
rpm_R = 0.0;
105122
prev_rpm_R = 0.0;
106123
count_R = 0;
124+
VR = 0.0;
107125
}
108126

109127

@@ -130,7 +148,7 @@ ISR(PCINT0_vect){
130148
led_state = !led_state;
131149
last_time_fall_1 = current_time;
132150
ch1_fall = true;
133-
151+
VL = move_in_one_trig/((current_time - last_stamped_L)/1000000.0);
134152
//////////////////////////
135153
/////// rpm_L cal //////////
136154
//////////////////////////
@@ -143,11 +161,14 @@ ISR(PCINT0_vect){
143161
// Calculate rpm_L according to how many magnets it passed...
144162
rpm_L = (float(count_L-1)/magnets)/(time_passed_L/60.0);
145163
prev_rpm_L = rpm_L;
146-
} else if (count_L >= (magnets+1)){
147-
count_L = 0;
164+
if (count_L == (magnets+1)){
165+
count_L = 0;
166+
}
148167
} else{
149168
rpm_L = prev_rpm_L;
150169
}
170+
171+
last_stamped_L = micros();
151172

152173
}
153174

@@ -183,7 +204,8 @@ ISR(PCINT0_vect){
183204
//led_state = !led_state;
184205
last_time_fall_3 = current_time;
185206
ch3_fall = true;
186-
207+
208+
VR = move_in_one_trig/((current_time - last_stamped_R)/1000000.0);
187209
//////////////////////////
188210
/////// rpm_R cal //////////
189211
//////////////////////////
@@ -196,11 +218,14 @@ ISR(PCINT0_vect){
196218
// Calculate rpm_L according to how many magnets it passed...
197219
rpm_R = (float(count_R-1)/magnets)/(time_passed_R/60.0);
198220
prev_rpm_R = rpm_R;
199-
} else if (count_R >= (magnets+1)){
200-
count_R = 0;
221+
if (count_R >= (magnets+1)){
222+
count_R = 0;
223+
}
201224
} else{
202225
rpm_R = prev_rpm_R;
203226
}
227+
228+
last_stamped_R = micros();
204229

205230
}
206231

@@ -229,9 +254,11 @@ ISR(PCINT0_vect){
229254
if (ch1_fall && ch2_fall){
230255
if (last_time_fall_1 < last_time_fall_2){
231256
fwd_dir_L = true;
257+
counter_L += 1;
232258
}
233259
else{
234260
fwd_dir_L = false;
261+
counter_L -= 1;
235262
}
236263
ch1_fall = false;
237264
ch2_fall = false;
@@ -245,9 +272,11 @@ ISR(PCINT0_vect){
245272
if (ch3_fall && ch4_fall){
246273
if (last_time_fall_3 < last_time_fall_4){
247274
fwd_dir_R = true;
275+
counter_R += 1;
248276
}
249277
else{
250278
fwd_dir_R = false;
279+
counter_R -= 1;
251280
}
252281
ch3_fall = false;
253282
ch4_fall = false;

docs/wheels_hall_sensor.md

+27-1
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,30 @@ The magnet ring of this new design uses simple 1mm double-sided tape to attach o
5252

5353
Arduino's firmware and `jmoab-ros-wheels-rpm` are same.
5454

55-
Please check on [this video](https://youtu.be/tCoUMoUfn2s), of how to operate it.
55+
Please check on [this video](https://youtu.be/tCoUMoUfn2s), of how to operate it.
56+
57+
## Nodes
58+
59+
Once you started `jmoab-ros-wheels-rpm.py`, it's publishing
60+
61+
- `wheels_rpm` as array of [left_rpm, right_rpm]
62+
63+
- `wheels_speed` as array of [VL, VR], linear velocity of the wheels
64+
65+
- `odom` as Odometry topic, this is quite poor because our sensor is only 8 ticks per revolution.
66+
67+
To improve the odom topic, we need [robot_pose_ekf package](http://wiki.ros.org/robot_pose_ekf) to fuse the sensor of wheel's odometry with IMU sensors.
68+
69+
I changed [this](https://github.com/ros-planning/robot_pose_ekf/blob/fd6cef32b447e8b344a1111373e515aa2f8bfc50/robot_pose_ekf.launch#L5) `base_footprint` to `base_link`, and disable [this](https://github.com/ros-planning/robot_pose_ekf/blob/fd6cef32b447e8b344a1111373e515aa2f8bfc50/robot_pose_ekf.launch#L10) `vo_used` to false.
70+
71+
You will need to run
72+
73+
roslaunch jmoab-ros jmoab-ros-ekf-odom-test.launch
74+
75+
to start the nodes, and also
76+
77+
roslaunch robot_pose_ekf robot_pose_ekf.launch
78+
79+
You will get `ekf_odom` topic for a fused odom. Please check on this [demo video](https://www.youtube.com/watch?v=SlqJzMmxYxU&ab_channel=stepbystep-robotics).
80+
81+
The green arrow is `ekf_odom`, red arrow is original `odom`.

example/ekf_odom_generate.py

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#! /usr/bin/env python
2+
import rospy
3+
from geometry_msgs.msg import PoseWithCovarianceStamped
4+
import numpy as np
5+
from geometry_msgs.msg import TransformStamped
6+
import tf2_ros
7+
8+
br = tf2_ros.TransformBroadcaster()
9+
t = TransformStamped()
10+
ekf_odom_pub = rospy.Publisher("/ekf_odom", PoseWithCovarianceStamped, queue_size=10)
11+
ekf_odom_msg = PoseWithCovarianceStamped()
12+
13+
def callback(data):
14+
15+
# print(data)
16+
17+
x = data.pose.pose.position.x
18+
y = data.pose.pose.position.y
19+
z = data.pose.pose.position.z
20+
21+
qw = data.pose.pose.orientation.w
22+
qx = data.pose.pose.orientation.x
23+
qy = data.pose.pose.orientation.y
24+
qz = data.pose.pose.orientation.z
25+
26+
# construct tf
27+
t.header.frame_id = "ekf_odom"
28+
t.header.stamp = rospy.Time.now()
29+
t.child_frame_id = "base_link_ekf" #"base_footprint" #"base_link"
30+
t.transform.translation.x = x
31+
t.transform.translation.y = y
32+
t.transform.translation.z = z
33+
34+
t.transform.rotation.x = qx
35+
t.transform.rotation.y = qy
36+
t.transform.rotation.z = qz
37+
t.transform.rotation.w = qw
38+
br.sendTransform(t)
39+
40+
ekf_odom_msg.header.stamp = rospy.Time.now()
41+
ekf_odom_msg.header.frame_id = "ekf_odom"
42+
ekf_odom_msg.pose.pose.position.x = x
43+
ekf_odom_msg.pose.pose.position.y = y
44+
ekf_odom_msg.pose.pose.position.z = z
45+
ekf_odom_msg.pose.pose.orientation.x = qx
46+
ekf_odom_msg.pose.pose.orientation.y = qy
47+
ekf_odom_msg.pose.pose.orientation.z = qz
48+
ekf_odom_msg.pose.pose.orientation.w = qw
49+
50+
ekf_odom_msg.pose.covariance = data.pose.covariance
51+
52+
ekf_odom_pub.publish(ekf_odom_msg)
53+
54+
55+
def listener():
56+
57+
rospy.init_node('generate_tf_for_ekf_odom', anonymous=True)
58+
rospy.Subscriber("/robot_pose_ekf/odom_combined", PoseWithCovarianceStamped, callback)
59+
60+
rospy.spin()
61+
62+
if __name__ == '__main__':
63+
listener()

launch/jmoab-ros-ekf-odom-test.launch

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<launch>
2+
3+
<!-- We create a dummy map frame to let ekf_odom and odom have same references-->
4+
<node pkg="tf" type="static_transform_publisher" name="map_to_odom" args="1.0 0.0 0.0 0.0 0.0 0.0 1.0 /map /odom 100" />
5+
<node pkg="tf" type="static_transform_publisher" name="map_to_ekf_odom" args="1.0 0.0 0.0 0.0 0.0 0.0 1.0 /map /ekf_odom 100" />
6+
<remap from="/jmoab_imu_raw" to="/imu_data" />
7+
8+
<node pkg="jmoab-ros" name="jmoab_ros_atcart_node" type="jmoab-ros-atcart.py" output="screen"/>
9+
<node pkg="jmoab-ros" name="jmoab_ros_imu_node" type="jmoab-ros-imu.py" output="screen"/>
10+
11+
<!-- For hall sensor wheel odometry-->
12+
<node pkg="jmoab-ros" name="jmoab_ros_wheels_rpm_node" type="jmoab-ros-wheels-rpm.py" args="/dev/ttyUSB0" output="screen"/>
13+
<node pkg="jmoab-ros" name="ekf_odom_generate" type="ekf_odom_generate.py" output="screen"/>
14+
15+
<!-- For robot_pose_ekf-->
16+
<!-- <include file="$(find robot_pose_ekf)/robot_pose_ekf.launch" /> -->
17+
18+
19+
</launch>

src/jmoab-ros-compass-simulation.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ def kalman_filter(self, measure, prev_state_est, prev_error_est):
255255
################
256256
## prediction ##
257257
################
258-
self.error_est = cur_error_est + 0.01 # 0.001 is process noise, could be removed
258+
self.error_est = cur_error_est + 0.01 # 0.01 is process noise, could help when hdg_offset is fluctuating during time
259259
# hdg_offset is not dynamic behaviour, so predicted state is constant as estimated state
260260
self.state_predict = cur_state_est
261261

0 commit comments

Comments
 (0)