phidgets_native 0.2.1 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
}
|