phidgets_native 0.2.1 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +17 -2
- data/README.rdoc +9 -1
- data/Rakefile +7 -6
- data/examples/spatial.rb +3 -2
- data/examples/test_throttle.rb +40 -0
- data/ext/phidgets_native/extconf.rb +10 -3
- data/ext/phidgets_native/phidgets_native.c +124 -0
- data/ext/phidgets_native/phidgets_native.h +39 -0
- data/ext/phidgets_native/spatial.c +405 -9
- data/ext/phidgets_native/spatial_ruby.c +407 -1
- data/phidgets_native.gemspec +2 -1
- metadata +40 -24
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZTJhOGE5ZGUyNDM2ZjExODdjMjVjMWRhOTU0YjZjMjdhZjRlNWRiNw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDMwMDllMGFlMzQ2NzhjZTdlOWJlNzgxZWFkOTQxYzFmNTczMzhlYQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZjAzYTVkYTIyNzRjMTg3NjUyOTQyOGQwMTFmM2Y4MzZlM2I4MDcwMDJlOTU0
|
10
|
+
NTlmMTNhODM2Y2QzOWViMzYxYzA0MTI1OWRjMGU2YzFmYWRjYjhmYzBmZmI4
|
11
|
+
MTZhN2RlYmEzZTNkZGU3NGIxODQ1MjQ2ZTJlYzVmOGEwNjljYjA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OGNlOGE5MTk0MDMxZWY1ZTM3Mzg2MzU5MWI5ZDEzNTVmYWEzM2E3YjJmNDY2
|
14
|
+
MDZjMTJhZDM1ODljMzFiZmQzYWEwOGVmYWJkZWE1MGI3Mzk0ZWEwNTIxMjA4
|
15
|
+
YTk5ODdjMDFhMWMyZWFkNWUzOWJkZDEwZDIwYjRiNTJmNzU2MzA=
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,12 +1,27 @@
|
|
1
1
|
GEM
|
2
2
|
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
-
|
5
|
-
|
4
|
+
diff-lcs (1.2.5)
|
5
|
+
json (1.8.1)
|
6
|
+
rake (10.1.1)
|
7
|
+
rake-compiler (0.9.2)
|
6
8
|
rake
|
9
|
+
rdoc (4.1.1)
|
10
|
+
json (~> 1.4)
|
11
|
+
rspec (2.14.1)
|
12
|
+
rspec-core (~> 2.14.0)
|
13
|
+
rspec-expectations (~> 2.14.0)
|
14
|
+
rspec-mocks (~> 2.14.0)
|
15
|
+
rspec-core (2.14.8)
|
16
|
+
rspec-expectations (2.14.5)
|
17
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
18
|
+
rspec-mocks (2.14.6)
|
7
19
|
|
8
20
|
PLATFORMS
|
9
21
|
ruby
|
10
22
|
|
11
23
|
DEPENDENCIES
|
24
|
+
rake
|
12
25
|
rake-compiler
|
26
|
+
rdoc
|
27
|
+
rspec
|
data/README.rdoc
CHANGED
@@ -175,8 +175,16 @@ Note that you can also use the log method to send your own messages to the logge
|
|
175
175
|
if you think you have something you need to add.
|
176
176
|
|
177
177
|
=Changelog
|
178
|
+
== Version 0.2.5
|
179
|
+
- Added orientation tracking code to the Spatial sensor via the Madgwick AHRS library
|
180
|
+
- Fixed compile issues on ARM
|
181
|
+
|
178
182
|
== Version 0.2.1
|
179
183
|
- Added dual-ratiometric mode features and interfaces to the InterfaceKit
|
180
184
|
|
181
185
|
== Version 0.2.0
|
182
|
-
-
|
186
|
+
- Added support for all api functions of the AdvancedServo Phidget
|
187
|
+
- Added support for all api functions of the InterfaceKit Phidget
|
188
|
+
|
189
|
+
== Version 0.1.0
|
190
|
+
- First gem release, supporting all api functions of the Spatial & GPS Phidgets
|
data/Rakefile
CHANGED
@@ -1,17 +1,14 @@
|
|
1
1
|
# File: Rakefile
|
2
2
|
|
3
|
+
require 'rake'
|
3
4
|
require 'rake/extensiontask'
|
4
5
|
require 'rake/packagetask'
|
5
6
|
require 'rdoc/task'
|
7
|
+
require 'rspec/core/rake_task'
|
6
8
|
|
7
9
|
spec = Gem::Specification.load('phidgets_native.gemspec')
|
8
|
-
|
9
10
|
Rake::ExtensionTask.new 'phidgets_native', spec
|
10
|
-
|
11
|
-
Gem::PackageTask.new(spec) do |pkg|
|
12
|
-
|
13
|
-
|
14
|
-
end
|
11
|
+
Gem::PackageTask.new spec
|
15
12
|
|
16
13
|
RDOC_FILES = FileList["README.rdoc", "ext/phidgets_native/phidgets_native_ruby.c",
|
17
14
|
"ext/phidgets_native/*_ruby.c"]
|
@@ -29,3 +26,7 @@ Rake::RDocTask.new(:ri) do |rd|
|
|
29
26
|
rd.rdoc_files.include(RDOC_FILES)
|
30
27
|
end
|
31
28
|
|
29
|
+
RSpec::Core::RakeTask.new('spec')
|
30
|
+
|
31
|
+
task :spec => :compile
|
32
|
+
|
data/examples/spatial.rb
CHANGED
@@ -41,11 +41,12 @@ phidgets_example_for( PhidgetsNative::Spatial, additional_attribs ) do |spatial|
|
|
41
41
|
'Sample Rate',
|
42
42
|
'%-40s' % 'Accelerometer',
|
43
43
|
'%-40s' % 'Gyroscope',
|
44
|
-
'%-40s' % 'Compass'
|
44
|
+
'%-40s' % 'Compass',
|
45
|
+
'%-40s' % 'OrientationQ',
|
45
46
|
]).output(:header => (i == 0), :separator => false) do |columns|
|
46
47
|
i+=1
|
47
48
|
[ ['%d Hz' % spatial.sample_rate]+[
|
48
|
-
spatial.accelerometer, spatial.gyro, spatial.compass ].collect{ |axes|
|
49
|
+
spatial.accelerometer, spatial.gyro, spatial.compass, spatial.orientation_to_quaternion ].collect{ |axes|
|
49
50
|
(axes.kind_of? Array) ?
|
50
51
|
'[%s]' % axes.collect{|a| '%0.6f' % a}.join(', ') : axes.inspect
|
51
52
|
} ]
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require '%s/lib/common' % File.dirname(__FILE__)
|
5
|
+
|
6
|
+
additional_attribs = [
|
7
|
+
%w(Motor\ Count motor_count),
|
8
|
+
%w(Acceleration\ Max acceleration_max),
|
9
|
+
%w(Acceleration\ Min acceleration_min),
|
10
|
+
%w(Velocity\ Max velocity_max),
|
11
|
+
%w(Velocity\ Min velocity_min)
|
12
|
+
]
|
13
|
+
|
14
|
+
phidgets_example_for(PhidgetsNative::AdvancedServo, additional_attribs) do |servo|
|
15
|
+
#servo.acceleration 0, servo.acceleration_max[0]
|
16
|
+
#servo.velocity_limit 0, servo.velocity_max[0]
|
17
|
+
# NOTE that these doesn't seem to be supported on all servo types
|
18
|
+
#servo.position_max 0, 220
|
19
|
+
#servo.position_min 0, 0
|
20
|
+
# /NOTE
|
21
|
+
servo.servo_type 0, :default
|
22
|
+
servo.speed_ramping 0, false
|
23
|
+
servo.engaged 0, true
|
24
|
+
|
25
|
+
#servo_range = servo.position_max(0) - servo.position_min(0)
|
26
|
+
#servo_middle = servo.position_min(0) + servo_range/2
|
27
|
+
#servo_left = servo.position_min(0) + servo_middle - servo_range / 4
|
28
|
+
#servo_right = servo.position_min(0) + servo_middle + servo_range / 4
|
29
|
+
|
30
|
+
servo.position 0, 0
|
31
|
+
|
32
|
+
|
33
|
+
puts "\nPolled Values:"
|
34
|
+
i = 0
|
35
|
+
begin
|
36
|
+
i+=1
|
37
|
+
puts "Hai #{i}"
|
38
|
+
servo.position 0, (i % 2 == 0) ? 0 : 92 # NOTE: 92 seems to be the lowest that goes
|
39
|
+
end while sleep(3)
|
40
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# File: extconf.rb
|
2
2
|
require 'mkmf'
|
3
|
+
require 'rbconfig'
|
3
4
|
|
4
5
|
extension_name = 'phidgets_native'
|
5
6
|
|
@@ -8,11 +9,17 @@ HEADER_DIRS = [ '/Library/Frameworks/Phidget21.framework/Headers' ]
|
|
8
9
|
find_header 'phidget21.h', *HEADER_DIRS
|
9
10
|
find_library 'libphidget21', 'CPhidget_getLibraryVersion', '/usr/lib/'
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
case RbConfig::CONFIG['host_os']
|
13
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
14
|
+
raise StandardError, "Though possible to compile in win32, this extconf wasn't designed for it.... Fix me?"
|
15
|
+
when /darwin|mac os/
|
16
|
+
have_framework 'Phidget21'
|
17
|
+
else
|
18
|
+
have_library('phidget21')
|
19
|
+
end
|
13
20
|
|
14
21
|
dir_config extension_name
|
15
22
|
|
16
|
-
$CFLAGS << ' -Wno-unused-variable -std=gnu99'
|
23
|
+
$CFLAGS << ' -Wno-unused-variable -Wno-declaration-after-statement -std=gnu99'
|
17
24
|
|
18
25
|
create_makefile extension_name
|
@@ -44,6 +44,29 @@ VALUE phidgetbool_array_to_rb(int *bool_array, int length) {
|
|
44
44
|
return rb_ary;
|
45
45
|
}
|
46
46
|
|
47
|
+
/* This converts a 3x3 array of doubles into a ruby matrix, or into
|
48
|
+
* nil for the case of an invalid dbl_array
|
49
|
+
*/
|
50
|
+
VALUE double3x3_to_matrix_rb(double m3x3[][3]) {
|
51
|
+
if (!m3x3) return Qnil;
|
52
|
+
|
53
|
+
// To compose a Matrix, we need a 3x3 array of floats:
|
54
|
+
VALUE aryRet = rb_ary_new2(3);
|
55
|
+
for (unsigned int i=0; i < 3; i++) {
|
56
|
+
VALUE arRow = rb_ary_new2(3);
|
57
|
+
|
58
|
+
for (unsigned int j=0; j < 3; j++)
|
59
|
+
rb_ary_store(arRow, j, rb_float_new(m3x3[i][j]));
|
60
|
+
|
61
|
+
rb_ary_store(aryRet, i, arRow);
|
62
|
+
}
|
63
|
+
|
64
|
+
VALUE c_Matrix = rb_const_get( rb_cObject, rb_intern("Matrix") );
|
65
|
+
VALUE rbMatrixRet = rb_funcall(c_Matrix, rb_intern("rows"), 2, aryRet, Qfalse);
|
66
|
+
|
67
|
+
return rbMatrixRet;
|
68
|
+
}
|
69
|
+
|
47
70
|
// The name is ambiguous, but the main purpose here is dry things out a bit
|
48
71
|
// and let us do a better job of reporting errors to ruby
|
49
72
|
int ensure(int result) {
|
@@ -195,3 +218,104 @@ int sample_tick(SampleRate *sample_rate, CPhidget_Timestamp *ts) {
|
|
195
218
|
return 0;
|
196
219
|
}
|
197
220
|
|
221
|
+
// There's a number of ways to implement this, at the moment, I'm more interested
|
222
|
+
// in accuracy... so we use the close-to-optimal method with low cost from :
|
223
|
+
// http://pizer.wordpress.com/2008/10/12/fast-inverse-square-root
|
224
|
+
// as opposed to: 1.0f / sqrtf(x)
|
225
|
+
float inv_sqrt(float x) {
|
226
|
+
unsigned int i = 0x5F1F1412 - (*(unsigned int*)&x >> 1);
|
227
|
+
float tmp = *(float*)&i;
|
228
|
+
return tmp * (1.69000231f - 0.714158168f * x * tmp * tmp);
|
229
|
+
}
|
230
|
+
|
231
|
+
|
232
|
+
// Quaternion Multiplication operator. Expects its 4-element arrays in wxyz order
|
233
|
+
void quat_mult(float a[4], float b[4], float ret[4]) {
|
234
|
+
ret[0] = (b[0] * a[0]) - (b[1] * a[1]) - (b[2] * a[2]) - (b[3] * a[3]);
|
235
|
+
ret[1] = (b[0] * a[1]) + (b[1] * a[0]) + (b[2] * a[3]) - (b[3] * a[2]);
|
236
|
+
ret[2] = (b[0] * a[2]) + (b[2] * a[0]) + (b[3] * a[1]) - (b[1] * a[3]);
|
237
|
+
ret[3] = (b[0] * a[3]) + (b[3] * a[0]) + (b[1] * a[2]) - (b[2] * a[1]);
|
238
|
+
|
239
|
+
return;
|
240
|
+
}
|
241
|
+
|
242
|
+
// Quaternion Normalization operator. Expects its 4-element arrays in wxyz order
|
243
|
+
void quat_norm(float a[4]) {
|
244
|
+
float n = a[1]*a[1] + a[2]*a[2] + a[3]*a[3] + a[0]*a[0];
|
245
|
+
|
246
|
+
if (n == 1.0f)
|
247
|
+
return ;
|
248
|
+
|
249
|
+
n = inv_sqrt(n);
|
250
|
+
|
251
|
+
a[1]*=n;
|
252
|
+
a[2]*=n;
|
253
|
+
a[3]*=n;
|
254
|
+
a[0]*=n;
|
255
|
+
|
256
|
+
return;
|
257
|
+
}
|
258
|
+
|
259
|
+
// Quaternion to direction cosine matrix. Simple enough:
|
260
|
+
void quat_to_dcm(float q[4], double dcm[][3]) {
|
261
|
+
float q_w = q[0];
|
262
|
+
float q_x = q[1];
|
263
|
+
float q_y = q[2];
|
264
|
+
float q_z = q[3];
|
265
|
+
|
266
|
+
float sqw = q_w*q_w;
|
267
|
+
float sqx = q_x*q_x;
|
268
|
+
float sqy = q_y*q_y;
|
269
|
+
float sqz = q_z*q_z;
|
270
|
+
|
271
|
+
// invs (inverse square length) is only required if quaternion is not already normalised
|
272
|
+
float invs = 1 / (sqx + sqy + sqz + sqw);
|
273
|
+
dcm[0][0] = ( sqx - sqy - sqz + sqw)*invs; // since sqw + sqx + sqy + sqz =1/invs*invs
|
274
|
+
dcm[1][1] = (-sqx + sqy - sqz + sqw)*invs;
|
275
|
+
dcm[2][2] = (-sqx - sqy + sqz + sqw)*invs;
|
276
|
+
|
277
|
+
float tmp1 = q_x*q_y;
|
278
|
+
float tmp2 = q_z*q_w;
|
279
|
+
|
280
|
+
dcm[1][0] = 2.0 * (tmp1 + tmp2)*invs;
|
281
|
+
dcm[0][1] = 2.0 * (tmp1 - tmp2)*invs;
|
282
|
+
|
283
|
+
tmp1 = q_x*q_z;
|
284
|
+
tmp2 = q_y*q_w;
|
285
|
+
dcm[2][0] = 2.0 * (tmp1 - tmp2)*invs;
|
286
|
+
dcm[0][2] = 2.0 * (tmp1 + tmp2)*invs;
|
287
|
+
|
288
|
+
tmp1 = q_y*q_z;
|
289
|
+
tmp2 = q_x*q_w;
|
290
|
+
dcm[2][1] = 2.0 * (tmp1 + tmp2)*invs;
|
291
|
+
dcm[1][2] = 2.0 * (tmp1 - tmp2)*invs;
|
292
|
+
|
293
|
+
return;
|
294
|
+
}
|
295
|
+
|
296
|
+
|
297
|
+
// Quaternion to euler:
|
298
|
+
void quat_to_euler(float q[4], float e[3]) {
|
299
|
+
float sqw = q[0]*q[0];
|
300
|
+
float sqx = q[1]*q[1];
|
301
|
+
float sqy = q[2]*q[2];
|
302
|
+
float sqz = q[3]*q[3];
|
303
|
+
e[2] = -1.0f * atan2f(2.f * (q[1]*q[2] + q[3]*q[0]), sqx - sqy - sqz + sqw);
|
304
|
+
e[1] = asinf(-2.f * (q[1]*q[3] - q[2]*q[0]));
|
305
|
+
e[0] = -1.0f * atan2f(2.f * (q[2]*q[3] + q[1]*q[0]), -sqx - sqy + sqz + sqw);
|
306
|
+
|
307
|
+
return;
|
308
|
+
}
|
309
|
+
|
310
|
+
// Given an axis, and a angle (in radians), construct a quat
|
311
|
+
// NOTE: Assumes axis is already normalised
|
312
|
+
void quat_from_axis_and_angle(double axis[3], double angle, float fRetQ[4]) {
|
313
|
+
double s = sin(angle/2);
|
314
|
+
fRetQ[0] = cos(angle/2); // w
|
315
|
+
fRetQ[1] = axis[0] * s; // x
|
316
|
+
fRetQ[2] = axis[1] * s; // y
|
317
|
+
fRetQ[3] = axis[2] * s; // z
|
318
|
+
|
319
|
+
return;
|
320
|
+
}
|
321
|
+
|
@@ -1,8 +1,10 @@
|
|
1
1
|
#include <stdio.h>
|
2
2
|
#include <stdbool.h>
|
3
3
|
#include <math.h>
|
4
|
+
#include <sys/time.h>
|
4
5
|
#include <time.h>
|
5
6
|
#include <unistd.h>
|
7
|
+
#include <ctype.h>
|
6
8
|
|
7
9
|
#include <ruby.h>
|
8
10
|
#include <phidget21.h>
|
@@ -15,6 +17,8 @@ static double const MICROSECONDS_IN_SECOND = 1000000.0;
|
|
15
17
|
static int const DEGREES_IN_CIRCLE = 360;
|
16
18
|
static int const DEFAULT_SPATIAL_DATA_RATE = 16;
|
17
19
|
|
20
|
+
static float const SPATIAL_AHRS_BETA = 0.1f; // 2 * proportional gain (Kp)
|
21
|
+
|
18
22
|
// Make sure one of these is non-zero and the other is 0
|
19
23
|
static int const DEFAULT_INTERFACEKIT_DATA_RATE = 8;
|
20
24
|
static int const DEFAULT_INTERFACEKIT_CHANGE_TRIGGER = 0;
|
@@ -90,9 +94,16 @@ typedef struct spatial_info {
|
|
90
94
|
|
91
95
|
bool is_gyroscope_known;
|
92
96
|
double *gyroscope;
|
97
|
+
double gyroscope_dcm[3][3];
|
93
98
|
|
94
99
|
// This is used by the gyro:
|
95
100
|
double last_microsecond;
|
101
|
+
|
102
|
+
// quaternion elements representing the estimated orientation
|
103
|
+
float orientation_q[4];
|
104
|
+
|
105
|
+
// This tells us whether it's our first pass
|
106
|
+
bool is_ahrs_initialized;
|
96
107
|
} SpatialInfo;
|
97
108
|
|
98
109
|
typedef struct gps_info {
|
@@ -212,12 +223,19 @@ VALUE double_array_to_rb(double *dbl_array, int length);
|
|
212
223
|
VALUE int_array_to_rb(int *int_array, int length);
|
213
224
|
VALUE int_array_zeronils_to_rb(int *int_array, int length);
|
214
225
|
VALUE phidgetbool_array_to_rb(int *bool_array, int length);
|
226
|
+
VALUE double3x3_to_matrix_rb(double m3x3[][3]);
|
215
227
|
int ensure(int result);
|
216
228
|
int report(int result);
|
217
229
|
SampleRate *sample_create();
|
218
230
|
int sample_free(SampleRate *sample_rate);
|
219
231
|
int sample_zero(SampleRate *sample_rate);
|
220
232
|
int sample_tick(SampleRate *sample_rate, CPhidget_Timestamp *ts);
|
233
|
+
float inv_sqrt(float x);
|
234
|
+
void quat_mult(float a[4], float b[4], float ret[4]);
|
235
|
+
void quat_norm(float a[4]);
|
236
|
+
void quat_to_dcm(float q[4], double dcm[][3]);
|
237
|
+
void quat_to_euler(float q[4], float e[3]);
|
238
|
+
void quat_from_axis_and_angle(double axis[3], double angle, float fRetQ[4]);
|
221
239
|
|
222
240
|
// Phidget Module
|
223
241
|
VALUE phidget_enable_logging(int argc, VALUE *argv, VALUE class);
|
@@ -252,6 +270,12 @@ int CCONV spatial_on_attach(CPhidgetHandle phid, void *userptr);
|
|
252
270
|
int CCONV spatial_on_detach(CPhidgetHandle phid, void *userptr);
|
253
271
|
int CCONV spatial_on_data(CPhidgetSpatialHandle spatial, void *userptr, CPhidgetSpatial_SpatialEventDataHandle *data, int count);
|
254
272
|
int spatial_set_compass_correction_by_array(CPhidgetSpatialHandle phid, double *correction);
|
273
|
+
void spatial_ahrs_update(SpatialInfo *spatial_info, float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz);
|
274
|
+
void spatial_ahrs_update_imu(SpatialInfo *spatial_info, float gx, float gy, float gz, float ax, float ay, float az);
|
275
|
+
void spatial_ahrs_init(SpatialInfo *spatial_info, double ax, double ay, double az, double mx, double my, double mz);
|
276
|
+
void spatial_orientation_from_accel_and_compass(double dRetEuler[3], double ax, double ay, double az, double mx, double my, double mz);
|
277
|
+
void spatial_madgeq_to_openglq(float *fMadgQ, float *fRetQ);
|
278
|
+
|
255
279
|
VALUE spatial_initialize(VALUE self, VALUE serial);
|
256
280
|
VALUE spatial_close(VALUE self);
|
257
281
|
VALUE spatial_sample_rate(VALUE self);
|
@@ -280,6 +304,21 @@ VALUE spatial_data_rate_max(VALUE self);
|
|
280
304
|
VALUE spatial_data_rate_set(VALUE self, VALUE data_rate);
|
281
305
|
VALUE spatial_data_rate_get(VALUE self);
|
282
306
|
|
307
|
+
VALUE spatial_direction_cosine_matrix(int argc, VALUE *argv, VALUE self);
|
308
|
+
VALUE spatial_accelerometer_to_roll_and_pitch(VALUE self);
|
309
|
+
VALUE spatial_accelerometer_to_euler(VALUE self);
|
310
|
+
VALUE spatial_accelerometer_to_dcm(VALUE self);
|
311
|
+
VALUE spatial_gravity_to_roll_and_pitch(VALUE self);
|
312
|
+
VALUE spatial_compass_bearing(VALUE self);
|
313
|
+
VALUE spatial_compass_bearing_to_euler(VALUE self);
|
314
|
+
VALUE spatial_compass_bearing_to_dcm(VALUE self);
|
315
|
+
VALUE spatial_gyro_to_dcm(VALUE self);
|
316
|
+
VALUE spatial_orientation_to_quaternion(VALUE self);
|
317
|
+
VALUE spatial_orientation_to_dcm(VALUE self);
|
318
|
+
VALUE spatial_orientation_to_euler(VALUE self);
|
319
|
+
|
320
|
+
int euler_to_3x3dcm(double *mRet, double around_x, double around_y, double around_z, const char *in_order);
|
321
|
+
|
283
322
|
// PhidgetsNative::InterfaceKit
|
284
323
|
void interfacekit_on_free(void *type_info);
|
285
324
|
int CCONV interfacekit_on_attach(CPhidgetHandle phid, void *userptr);
|
@@ -1,5 +1,7 @@
|
|
1
1
|
#include "phidgets_native.h"
|
2
2
|
|
3
|
+
const char DEFAULT_IN_ORDER_STR[] = "xyz";
|
4
|
+
|
3
5
|
void spatial_on_free(void *type_info) {
|
4
6
|
SpatialInfo *spatial_info = type_info;
|
5
7
|
if (spatial_info->acceleration)
|
@@ -34,6 +36,57 @@ int spatial_set_compass_correction_by_array(CPhidgetSpatialHandle phid, double *
|
|
34
36
|
cc[10], cc[11], cc[12] ));
|
35
37
|
}
|
36
38
|
|
39
|
+
/*
|
40
|
+
* This method will rotate the double[3][3] mRet around the axial rotations, in order of in_order )
|
41
|
+
*/
|
42
|
+
int euler_to_3x3dcm(double *mRet, double around_x, double around_y, double around_z, const char *in_order) {
|
43
|
+
double mA[3][3];
|
44
|
+
double mB[3][3];
|
45
|
+
double mAccum[3][3];
|
46
|
+
|
47
|
+
memcpy(&mAccum, mRet, sizeof(mAccum));
|
48
|
+
|
49
|
+
unsigned long in_order_length = strlen((in_order) ? in_order : DEFAULT_IN_ORDER_STR);
|
50
|
+
|
51
|
+
for (unsigned long i = 0; i < in_order_length; i++) {
|
52
|
+
switch (tolower((in_order) ? in_order[i] : DEFAULT_IN_ORDER_STR[i])) {
|
53
|
+
case 'x':
|
54
|
+
mB[0][0] = 1.0; mB[0][1] = 0.0; mB[0][2] = 0.0;
|
55
|
+
mB[1][0] = 0.0; mB[1][1] = cos(around_x); mB[1][2] = sin(around_x) * (-1.0);
|
56
|
+
mB[2][0] = 0.0; mB[2][1] = sin(around_x); mB[2][2] = cos(around_x);
|
57
|
+
break;
|
58
|
+
case 'y':
|
59
|
+
mB[0][0] = cos(around_y); mB[0][1] = 0.0; mB[0][2] = sin(around_y);
|
60
|
+
mB[1][0] = 0.0; mB[1][1] = 1.0; mB[1][2] = 0.0;
|
61
|
+
mB[2][0] = sin(around_y) * (-1.0); mB[2][1] = 0.0; mB[2][2] = cos(around_y);
|
62
|
+
break;
|
63
|
+
case 'z':
|
64
|
+
mB[0][0] = cos(around_z); mB[0][1] = sin(around_z) * (-1.0); mB[0][2] = 0.0;
|
65
|
+
mB[1][0] = sin(around_z); mB[1][1] = cos(around_z); mB[1][2] = 0.0;
|
66
|
+
mB[2][0] = 0.0; mB[2][1] = 0.0; mB[2][2] = 1.0;
|
67
|
+
break;
|
68
|
+
default:
|
69
|
+
// Fatal Error - return without setting mRet
|
70
|
+
return 1;
|
71
|
+
break;
|
72
|
+
}
|
73
|
+
|
74
|
+
// Accumulate into ret, via multiplication
|
75
|
+
memcpy(&mA, &mAccum, sizeof(mAccum));
|
76
|
+
memset(&mAccum, 0, sizeof(mAccum));
|
77
|
+
|
78
|
+
for (unsigned int i = 0; i < 3; i++)
|
79
|
+
for (unsigned int j = 0; j < 3; j++)
|
80
|
+
for (unsigned int k = 0; k < 3; k++)
|
81
|
+
mAccum[i][j] += mA[i][k] * mB[k][j];
|
82
|
+
}
|
83
|
+
|
84
|
+
// Success:
|
85
|
+
memcpy(mRet, &mAccum, sizeof(mAccum));
|
86
|
+
|
87
|
+
return 0;
|
88
|
+
}
|
89
|
+
|
37
90
|
int CCONV spatial_on_attach(CPhidgetHandle phid, void *userptr) {
|
38
91
|
PhidgetInfo *info = userptr;
|
39
92
|
SpatialInfo *spatial_info = info->type_info;
|
@@ -67,6 +120,12 @@ int CCONV spatial_on_attach(CPhidgetHandle phid, void *userptr) {
|
|
67
120
|
spatial_info->gyroscope_min = ALLOC_N(double, spatial_info->gyro_axes);
|
68
121
|
spatial_info->gyroscope_max = ALLOC_N(double, spatial_info->gyro_axes);
|
69
122
|
|
123
|
+
// Initialize the gyro dcm:
|
124
|
+
if (spatial_info->gyro_axes == 3) {
|
125
|
+
double identity3x3[3][3] = {{1.0,0.0,0.0}, {0.0,1.0,0.0}, {0.0,0.0,1.0}};
|
126
|
+
memcpy(&spatial_info->gyroscope_dcm, &identity3x3, sizeof(identity3x3));
|
127
|
+
}
|
128
|
+
|
70
129
|
// Accelerometer
|
71
130
|
for(int i=0; i < spatial_info->accelerometer_axes; i++) {
|
72
131
|
report(CPhidgetSpatial_getAccelerationMin((CPhidgetSpatialHandle)phid, i, &spatial_info->acceleration_min[i]));
|
@@ -83,6 +142,9 @@ int CCONV spatial_on_attach(CPhidgetHandle phid, void *userptr) {
|
|
83
142
|
report(CPhidgetSpatial_getAngularRateMax((CPhidgetSpatialHandle)phid, i, &spatial_info->gyroscope_max[i]));
|
84
143
|
}
|
85
144
|
|
145
|
+
// Ahrs Values:
|
146
|
+
spatial_info->is_ahrs_initialized = false;
|
147
|
+
|
86
148
|
// Set the data rate for the spatial events in milliseconds.
|
87
149
|
// Note that 1000/16 = 62.5 Hz
|
88
150
|
report(CPhidgetSpatial_setDataRate((CPhidgetSpatialHandle)phid, spatial_info->data_rate));
|
@@ -104,6 +166,7 @@ int CCONV spatial_on_detach(CPhidgetHandle phidget, void *userptr) {
|
|
104
166
|
memset(spatial_info->acceleration, 0, sizeof(double) * spatial_info->accelerometer_axes);
|
105
167
|
memset(spatial_info->gyroscope, 0, sizeof(double) * spatial_info->gyro_axes);
|
106
168
|
memset(spatial_info->compass, 0, sizeof(double) * spatial_info->compass_axes);
|
169
|
+
memset(&spatial_info->gyroscope_dcm, 0, sizeof(spatial_info->gyroscope_dcm));
|
107
170
|
|
108
171
|
spatial_info->last_microsecond = 0;
|
109
172
|
spatial_info->is_acceleration_known = false;
|
@@ -145,24 +208,357 @@ int CCONV spatial_on_data(CPhidgetSpatialHandle spatial, void *userptr, CPhidget
|
|
145
208
|
spatial_info->is_compass_known = true;
|
146
209
|
}
|
147
210
|
|
148
|
-
//
|
149
|
-
// NOTE: Other people may have a better way to do this, but this is the method
|
150
|
-
// I grabbed from the phidget sample. Maybe I should report these in radians...
|
211
|
+
// Gyro Time! these get handled slightly different...
|
151
212
|
double timestamp = data[i]->timestamp.seconds + data[i]->timestamp.microseconds/MICROSECONDS_IN_SECOND;
|
152
213
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
214
|
+
double timechange = (spatial_info->last_microsecond > 0) ?
|
215
|
+
timestamp - spatial_info->last_microsecond : 0;
|
216
|
+
|
217
|
+
// This is kind of superceeded by the orientation_ functions, but I'm
|
218
|
+
// leaving it in for posterity:
|
219
|
+
for(int j=0; j < spatial_info->gyro_axes; j++)
|
220
|
+
spatial_info->gyroscope[j] += data[i]->angularRate[j] * timechange;
|
159
221
|
|
160
222
|
spatial_info->is_gyroscope_known = true;
|
161
223
|
|
224
|
+
// If we're dealing with 3 dimensional sensors, we can calculate orientation:
|
225
|
+
if (spatial_info->gyro_axes == 3) {
|
226
|
+
// This is a shortcut to keep things DRY below:
|
227
|
+
double angular_rate_in_rad[3];
|
228
|
+
for(int j=0; j < 3; j++)
|
229
|
+
angular_rate_in_rad[j] = data[i]->angularRate[j] * M_PI/180;
|
230
|
+
|
231
|
+
euler_to_3x3dcm((double *) &spatial_info->gyroscope_dcm,
|
232
|
+
angular_rate_in_rad[0] * timechange,
|
233
|
+
angular_rate_in_rad[1] * timechange,
|
234
|
+
angular_rate_in_rad[2] * timechange,
|
235
|
+
NULL);
|
236
|
+
|
237
|
+
// Madgwick Orientation time - Update the ahrs:
|
238
|
+
if (!spatial_info->is_ahrs_initialized) {
|
239
|
+
if (spatial_info->is_compass_known)
|
240
|
+
spatial_ahrs_init(spatial_info,
|
241
|
+
data[i]->acceleration[0],
|
242
|
+
data[i]->acceleration[1],
|
243
|
+
data[i]->acceleration[2],
|
244
|
+
data[i]->magneticField[0],
|
245
|
+
data[i]->magneticField[1],
|
246
|
+
data[i]->magneticField[2]);
|
247
|
+
} else {
|
248
|
+
float gx = (float) angular_rate_in_rad[0];
|
249
|
+
float gy = (float) angular_rate_in_rad[1];
|
250
|
+
float gz = (float) angular_rate_in_rad[2];
|
251
|
+
|
252
|
+
float ax = (float) data[i]->acceleration[0];
|
253
|
+
float ay = (float) data[i]->acceleration[1];
|
254
|
+
float az = (float) data[i]->acceleration[2];
|
255
|
+
|
256
|
+
if (spatial_info->is_compass_known)
|
257
|
+
// We have enough info for a MARG update:
|
258
|
+
spatial_ahrs_update(spatial_info, gx, gy, gz, ax, ay, az,
|
259
|
+
(float) data[i]->magneticField[0],
|
260
|
+
(float) data[i]->magneticField[1],
|
261
|
+
(float) data[i]->magneticField[2]);
|
262
|
+
else
|
263
|
+
// We only have enough info for a IMU update:
|
264
|
+
spatial_ahrs_update_imu(spatial_info, gx, gy, gz, ax, ay, az);
|
265
|
+
}
|
266
|
+
}
|
267
|
+
|
162
268
|
// We'll need this on the next go around:
|
163
269
|
spatial_info->last_microsecond = timestamp;
|
270
|
+
|
164
271
|
}
|
165
272
|
|
166
273
|
return 0;
|
167
274
|
}
|
168
275
|
|
276
|
+
void spatial_orientation_from_accel_and_compass(double dRetEuler[3],
|
277
|
+
double ax, double ay, double az,
|
278
|
+
double mx, double my, double mz) {
|
279
|
+
|
280
|
+
/*
|
281
|
+
* Roll Angle - about axis 0
|
282
|
+
* tan(roll) = gy/gz
|
283
|
+
* Use Atan2 so we have an output os (-180 - 180) degrees
|
284
|
+
*/
|
285
|
+
double dbl_grv_roll = atan2(ay, az);
|
286
|
+
|
287
|
+
/*
|
288
|
+
* Pitch Angle - about axis 1
|
289
|
+
* tan(pitch) = -gx / (gy * sin(roll angle) * gz * cos(roll angle))
|
290
|
+
* Pitch angle range is (-90 - 90) degrees
|
291
|
+
*/
|
292
|
+
double dbl_grv_pitch = atan(-1.0 * ax / (ay * sin(dbl_grv_roll) + az * cos(dbl_grv_roll)));
|
293
|
+
|
294
|
+
/*
|
295
|
+
* Yaw Angle - about axis 2
|
296
|
+
* tan(yaw) = (mz * sin(roll) – my * cos(roll)) /
|
297
|
+
* (mx * cos(pitch) + my * sin(pitch) * sin(roll) + mz * sin(pitch) * cos(roll))
|
298
|
+
* Use Atan2 to get our range in (-180 - 180)
|
299
|
+
*
|
300
|
+
* Yaw angle == 0 degrees when axis 0 is pointing at magnetic north
|
301
|
+
*/
|
302
|
+
double dbl_yaw = atan2( (mz * sin(dbl_grv_roll)) - (my * cos(dbl_grv_roll)),
|
303
|
+
(mx * cos(dbl_grv_pitch)) +
|
304
|
+
(my * sin(dbl_grv_pitch) * sin(dbl_grv_roll)) +
|
305
|
+
(mz * sin(dbl_grv_pitch) * cos(dbl_grv_roll)) );
|
306
|
+
|
307
|
+
dRetEuler[0] = dbl_grv_pitch;
|
308
|
+
dRetEuler[1] = dbl_grv_roll;
|
309
|
+
dRetEuler[2] = dbl_yaw;
|
310
|
+
|
311
|
+
return;
|
312
|
+
}
|
313
|
+
|
314
|
+
|
315
|
+
// NOTE: The spatial_ahrs_* functions were largely copied from Madgwick's
|
316
|
+
// Quaternion implementation of the 'DCM filter'
|
317
|
+
// https://code.google.com/p/imumargalgorithm30042010sohm/
|
318
|
+
void spatial_ahrs_init(SpatialInfo *spatial_info,
|
319
|
+
double ax, double ay, double az,
|
320
|
+
double mx, double my, double mz) {
|
321
|
+
|
322
|
+
double xaxis[3] = {1.0, 0.0, 0.0};
|
323
|
+
double yaxis[3] = {0.0, 1.0, 0.0};
|
324
|
+
double zaxis[3] = {0.0, 0.0, 1.0};
|
325
|
+
|
326
|
+
// We'll assume the current state by way of the accelerometer and compass readings:
|
327
|
+
double dOrientEuler[3];
|
328
|
+
spatial_orientation_from_accel_and_compass((double *)&dOrientEuler, ax, ay, az, mx, my, mz);
|
329
|
+
|
330
|
+
// Now we construct a Roll & Pitch Quat from the Euler's
|
331
|
+
float fRollQ[4];
|
332
|
+
quat_from_axis_and_angle((double *) &xaxis, dOrientEuler[1], (float *) &fRollQ);
|
333
|
+
|
334
|
+
float fPitchQ[4];
|
335
|
+
quat_from_axis_and_angle((double *) &yaxis, dOrientEuler[0], (float *) &fPitchQ);
|
336
|
+
|
337
|
+
float fRollPitchQ[4];
|
338
|
+
quat_mult((float *) &fRollQ, (float *) &fPitchQ, (float *)&fRollPitchQ);
|
339
|
+
|
340
|
+
// Rotate the the Quat around our 'Z' so that we're facing North:
|
341
|
+
// NOTE, Due to the madgwick origin, -0.5*M_PI is North, and we add the
|
342
|
+
// bearing offset to this angle
|
343
|
+
double bearing_in_rad = 2*M_PI+dOrientEuler[2];
|
344
|
+
|
345
|
+
float fCompassQ[4];
|
346
|
+
quat_from_axis_and_angle((double *) &zaxis, bearing_in_rad, (float *) &fCompassQ);
|
347
|
+
|
348
|
+
// Commit this to the persisting orientation state:
|
349
|
+
quat_mult((float *) &fRollPitchQ, (float *) &fCompassQ, (float *) spatial_info->orientation_q);
|
350
|
+
|
351
|
+
spatial_info->is_ahrs_initialized = true;
|
352
|
+
}
|
353
|
+
|
354
|
+
void spatial_ahrs_update(SpatialInfo *spatial_info,
|
355
|
+
float gx, float gy, float gz,
|
356
|
+
float ax, float ay, float az,
|
357
|
+
float mx, float my, float mz) {
|
358
|
+
|
359
|
+
// Just a shorthand meant to make this a bit easier to work with, and transact
|
360
|
+
// commits (eventually)
|
361
|
+
float q0 = spatial_info->orientation_q[0];
|
362
|
+
float q1 = spatial_info->orientation_q[1];
|
363
|
+
float q2 = spatial_info->orientation_q[2];
|
364
|
+
float q3 = spatial_info->orientation_q[3];
|
365
|
+
|
366
|
+
float sample_rate_in_hz = 1000.0f / spatial_info->data_rate;
|
367
|
+
|
368
|
+
float recipNorm;
|
369
|
+
float s0, s1, s2, s3;
|
370
|
+
float qDot1, qDot2, qDot3, qDot4;
|
371
|
+
float hx, hy;
|
372
|
+
float _2q0mx, _2q0my, _2q0mz, _2q1mx, _2bx, _2bz, _4bx, _4bz, _2q0, _2q1, _2q2, _2q3, _2q0q2, _2q2q3, q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3;
|
373
|
+
|
374
|
+
// Rate of change of quaternion from gyroscope
|
375
|
+
qDot1 = 0.5f * (-q1 * gx - q2 * gy - q3 * gz);
|
376
|
+
qDot2 = 0.5f * (q0 * gx + q2 * gz - q3 * gy);
|
377
|
+
qDot3 = 0.5f * (q0 * gy - q1 * gz + q3 * gx);
|
378
|
+
qDot4 = 0.5f * (q0 * gz + q1 * gy - q2 * gx);
|
379
|
+
|
380
|
+
// Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
|
381
|
+
if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {
|
382
|
+
|
383
|
+
// Normalise accelerometer measurement
|
384
|
+
recipNorm = inv_sqrt(ax * ax + ay * ay + az * az);
|
385
|
+
ax *= recipNorm;
|
386
|
+
ay *= recipNorm;
|
387
|
+
az *= recipNorm;
|
388
|
+
|
389
|
+
// Normalise magnetometer measurement
|
390
|
+
recipNorm = inv_sqrt(mx * mx + my * my + mz * mz);
|
391
|
+
mx *= recipNorm;
|
392
|
+
my *= recipNorm;
|
393
|
+
mz *= recipNorm;
|
394
|
+
|
395
|
+
// Auxiliary variables to avoid repeated arithmetic
|
396
|
+
_2q0mx = 2.0f * q0 * mx;
|
397
|
+
_2q0my = 2.0f * q0 * my;
|
398
|
+
_2q0mz = 2.0f * q0 * mz;
|
399
|
+
_2q1mx = 2.0f * q1 * mx;
|
400
|
+
_2q0 = 2.0f * q0;
|
401
|
+
_2q1 = 2.0f * q1;
|
402
|
+
_2q2 = 2.0f * q2;
|
403
|
+
_2q3 = 2.0f * q3;
|
404
|
+
_2q0q2 = 2.0f * q0 * q2;
|
405
|
+
_2q2q3 = 2.0f * q2 * q3;
|
406
|
+
q0q0 = q0 * q0;
|
407
|
+
q0q1 = q0 * q1;
|
408
|
+
q0q2 = q0 * q2;
|
409
|
+
q0q3 = q0 * q3;
|
410
|
+
q1q1 = q1 * q1;
|
411
|
+
q1q2 = q1 * q2;
|
412
|
+
q1q3 = q1 * q3;
|
413
|
+
q2q2 = q2 * q2;
|
414
|
+
q2q3 = q2 * q3;
|
415
|
+
q3q3 = q3 * q3;
|
416
|
+
|
417
|
+
// Reference direction of Earth's magnetic field
|
418
|
+
hx = mx * q0q0 - _2q0my * q3 + _2q0mz * q2 + mx * q1q1 + _2q1 * my * q2 + _2q1 * mz * q3 - mx * q2q2 - mx * q3q3;
|
419
|
+
hy = _2q0mx * q3 + my * q0q0 - _2q0mz * q1 + _2q1mx * q2 - my * q1q1 + my * q2q2 + _2q2 * mz * q3 - my * q3q3;
|
420
|
+
_2bx = sqrt(hx * hx + hy * hy);
|
421
|
+
_2bz = -_2q0mx * q2 + _2q0my * q1 + mz * q0q0 + _2q1mx * q3 - mz * q1q1 + _2q2 * my * q3 - mz * q2q2 + mz * q3q3;
|
422
|
+
_4bx = 2.0f * _2bx;
|
423
|
+
_4bz = 2.0f * _2bz;
|
424
|
+
|
425
|
+
// Gradient decent algorithm corrective step
|
426
|
+
s0 = -_2q2 * (2.0f * q1q3 - _2q0q2 - ax) + _2q1 * (2.0f * q0q1 + _2q2q3 - ay) - _2bz * q2 * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (-_2bx * q3 + _2bz * q1) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + _2bx * q2 * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
|
427
|
+
s1 = _2q3 * (2.0f * q1q3 - _2q0q2 - ax) + _2q0 * (2.0f * q0q1 + _2q2q3 - ay) - 4.0f * q1 * (1 - 2.0f * q1q1 - 2.0f * q2q2 - az) + _2bz * q3 * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (_2bx * q2 + _2bz * q0) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + (_2bx * q3 - _4bz * q1) * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
|
428
|
+
s2 = -_2q0 * (2.0f * q1q3 - _2q0q2 - ax) + _2q3 * (2.0f * q0q1 + _2q2q3 - ay) - 4.0f * q2 * (1 - 2.0f * q1q1 - 2.0f * q2q2 - az) + (-_4bx * q2 - _2bz * q0) * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (_2bx * q1 + _2bz * q3) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + (_2bx * q0 - _4bz * q2) * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
|
429
|
+
s3 = _2q1 * (2.0f * q1q3 - _2q0q2 - ax) + _2q2 * (2.0f * q0q1 + _2q2q3 - ay) + (-_4bx * q3 + _2bz * q1) * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (-_2bx * q0 + _2bz * q2) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + _2bx * q1 * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
|
430
|
+
recipNorm = inv_sqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude
|
431
|
+
s0 *= recipNorm;
|
432
|
+
s1 *= recipNorm;
|
433
|
+
s2 *= recipNorm;
|
434
|
+
s3 *= recipNorm;
|
435
|
+
|
436
|
+
// Apply feedback step
|
437
|
+
qDot1 -= SPATIAL_AHRS_BETA * s0;
|
438
|
+
qDot2 -= SPATIAL_AHRS_BETA * s1;
|
439
|
+
qDot3 -= SPATIAL_AHRS_BETA * s2;
|
440
|
+
qDot4 -= SPATIAL_AHRS_BETA * s3;
|
441
|
+
}
|
442
|
+
|
443
|
+
// Integrate rate of change of quaternion to yield quaternion
|
444
|
+
q0 += qDot1 * (1.0f / sample_rate_in_hz);
|
445
|
+
q1 += qDot2 * (1.0f / sample_rate_in_hz);
|
446
|
+
q2 += qDot3 * (1.0f / sample_rate_in_hz);
|
447
|
+
q3 += qDot4 * (1.0f / sample_rate_in_hz);
|
448
|
+
|
449
|
+
// Normalise quaternion
|
450
|
+
recipNorm = inv_sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
|
451
|
+
q0 *= recipNorm;
|
452
|
+
q1 *= recipNorm;
|
453
|
+
q2 *= recipNorm;
|
454
|
+
q3 *= recipNorm;
|
455
|
+
|
456
|
+
spatial_info->orientation_q[0] = q0;
|
457
|
+
spatial_info->orientation_q[1] = q1;
|
458
|
+
spatial_info->orientation_q[2] = q2;
|
459
|
+
spatial_info->orientation_q[3] = q3;
|
460
|
+
}
|
461
|
+
|
462
|
+
void spatial_ahrs_update_imu(SpatialInfo *spatial_info,
|
463
|
+
float gx, float gy, float gz,
|
464
|
+
float ax, float ay, float az) {
|
465
|
+
|
466
|
+
// Just a shorthand meant to make this a bit easier to work with, and transact
|
467
|
+
// commits (eventually)
|
468
|
+
float q0 = spatial_info->orientation_q[0];
|
469
|
+
float q1 = spatial_info->orientation_q[1];
|
470
|
+
float q2 = spatial_info->orientation_q[2];
|
471
|
+
float q3 = spatial_info->orientation_q[3];
|
472
|
+
|
473
|
+
float sample_rate_in_hz = 1000.0f / spatial_info->data_rate;
|
474
|
+
|
475
|
+
float recipNorm;
|
476
|
+
float s0, s1, s2, s3;
|
477
|
+
float qDot1, qDot2, qDot3, qDot4;
|
478
|
+
float _2q0, _2q1, _2q2, _2q3, _4q0, _4q1, _4q2 ,_8q1, _8q2, q0q0, q1q1, q2q2, q3q3;
|
479
|
+
|
480
|
+
// Rate of change of quaternion from gyroscope
|
481
|
+
qDot1 = 0.5f * (-q1 * gx - q2 * gy - q3 * gz);
|
482
|
+
qDot2 = 0.5f * (q0 * gx + q2 * gz - q3 * gy);
|
483
|
+
qDot3 = 0.5f * (q0 * gy - q1 * gz + q3 * gx);
|
484
|
+
qDot4 = 0.5f * (q0 * gz + q1 * gy - q2 * gx);
|
485
|
+
|
486
|
+
// Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
|
487
|
+
if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {
|
488
|
+
|
489
|
+
// Normalise accelerometer measurement
|
490
|
+
recipNorm = inv_sqrt(ax * ax + ay * ay + az * az);
|
491
|
+
ax *= recipNorm;
|
492
|
+
ay *= recipNorm;
|
493
|
+
az *= recipNorm;
|
494
|
+
|
495
|
+
// Auxiliary variables to avoid repeated arithmetic
|
496
|
+
_2q0 = 2.0f * q0;
|
497
|
+
_2q1 = 2.0f * q1;
|
498
|
+
_2q2 = 2.0f * q2;
|
499
|
+
_2q3 = 2.0f * q3;
|
500
|
+
_4q0 = 4.0f * q0;
|
501
|
+
_4q1 = 4.0f * q1;
|
502
|
+
_4q2 = 4.0f * q2;
|
503
|
+
_8q1 = 8.0f * q1;
|
504
|
+
_8q2 = 8.0f * q2;
|
505
|
+
q0q0 = q0 * q0;
|
506
|
+
q1q1 = q1 * q1;
|
507
|
+
q2q2 = q2 * q2;
|
508
|
+
q3q3 = q3 * q3;
|
509
|
+
|
510
|
+
// Gradient decent algorithm corrective step
|
511
|
+
s0 = _4q0 * q2q2 + _2q2 * ax + _4q0 * q1q1 - _2q1 * ay;
|
512
|
+
s1 = _4q1 * q3q3 - _2q3 * ax + 4.0f * q0q0 * q1 - _2q0 * ay - _4q1 + _8q1 * q1q1 + _8q1 * q2q2 + _4q1 * az;
|
513
|
+
s2 = 4.0f * q0q0 * q2 + _2q0 * ax + _4q2 * q3q3 - _2q3 * ay - _4q2 + _8q2 * q1q1 + _8q2 * q2q2 + _4q2 * az;
|
514
|
+
s3 = 4.0f * q1q1 * q3 - _2q1 * ax + 4.0f * q2q2 * q3 - _2q2 * ay;
|
515
|
+
recipNorm = inv_sqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude
|
516
|
+
s0 *= recipNorm;
|
517
|
+
s1 *= recipNorm;
|
518
|
+
s2 *= recipNorm;
|
519
|
+
s3 *= recipNorm;
|
520
|
+
|
521
|
+
// Apply feedback step
|
522
|
+
qDot1 -= SPATIAL_AHRS_BETA * s0;
|
523
|
+
qDot2 -= SPATIAL_AHRS_BETA * s1;
|
524
|
+
qDot3 -= SPATIAL_AHRS_BETA * s2;
|
525
|
+
qDot4 -= SPATIAL_AHRS_BETA * s3;
|
526
|
+
}
|
527
|
+
|
528
|
+
// Integrate rate of change of quaternion to yield quaternion
|
529
|
+
q0 += qDot1 * (1.0f / sample_rate_in_hz);
|
530
|
+
q1 += qDot2 * (1.0f / sample_rate_in_hz);
|
531
|
+
q2 += qDot3 * (1.0f / sample_rate_in_hz);
|
532
|
+
q3 += qDot4 * (1.0f / sample_rate_in_hz);
|
533
|
+
|
534
|
+
// Normalise quaternion
|
535
|
+
recipNorm = inv_sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
|
536
|
+
q0 *= recipNorm;
|
537
|
+
q1 *= recipNorm;
|
538
|
+
q2 *= recipNorm;
|
539
|
+
q3 *= recipNorm;
|
540
|
+
|
541
|
+
spatial_info->orientation_q[0] = q0;
|
542
|
+
spatial_info->orientation_q[1] = q1;
|
543
|
+
spatial_info->orientation_q[2] = q2;
|
544
|
+
spatial_info->orientation_q[3] = q3;
|
545
|
+
}
|
546
|
+
|
547
|
+
/*
|
548
|
+
The Madwick algorithm :
|
549
|
+
* Uses +x to indicate North, we want +y which means we need a -90 degree rotation around z.
|
550
|
+
* Uses the following coordinate system:
|
551
|
+
http://stackoverflow.com/questions/17788043/madgwicks-sensor-fusion-algorithm-on-ios
|
552
|
+
*/
|
553
|
+
void spatial_madgeq_to_openglq(float *fMadgQ, float *fRetQ) {
|
554
|
+
float fTmpQ[4];
|
555
|
+
float fXYRotationQ[4] = { sqrt(0.5), 0, 0, -1.0*sqrt(0.5) }; // wxyz
|
556
|
+
|
557
|
+
fTmpQ[0] = fMadgQ[0];
|
558
|
+
fTmpQ[1] = fMadgQ[1] * -1.0f;
|
559
|
+
fTmpQ[2] = fMadgQ[2];
|
560
|
+
fTmpQ[3] = fMadgQ[3] * -1.0f;
|
561
|
+
|
562
|
+
// And then store the Rotation into fTranslatedQ
|
563
|
+
quat_mult((float *) &fTmpQ, (float *) &fXYRotationQ, fRetQ);
|
564
|
+
}
|