File Coverage

blib/lib/Device/Chip/BNO055.pm
Criterion Covered Total %
statement 187 199 93.9
branch 18 32 56.2
condition 10 21 47.6
subroutine 31 31 100.0
pod 13 19 68.4
total 259 302 85.7


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2019-2021 -- leonerd@leonerd.org.uk
5              
6 4     4   307912 use v5.26;
  4         50  
7 4     4   422 use Object::Pad 0.57;
  4         7451  
  4         19  
8              
9             package Device::Chip::BNO055 0.03;
10             class Device::Chip::BNO055
11 1     1   471 :isa(Device::Chip);
  1         11819  
  1         28  
12              
13 4     4   1621 use utf8;
  4         17  
  4         22  
14              
15 4     4   99 use Carp;
  4         5  
  4         260  
16 4     4   17 use Future::AsyncAwait;
  4         14  
  4         20  
17              
18 4     4   1745 use Data::Bitfield 0.03 qw( bitfield enumfield );
  4         7204  
  4         310  
19              
20 4     4   36 use constant PROTOCOL => "I2C";
  4         8  
  4         467  
21              
22             =encoding UTF-8
23              
24             =cut
25              
26             =head1 NAME
27              
28             C - chip driver for F
29              
30             =head1 SYNOPSIS
31              
32             use Device::Chip::BNO055;
33             use Future::AsyncAwait;
34              
35             my $chip = Device::Chip::BNO055->new;
36             await $chip->mount( Device::Chip::Adapter::...->new );
37              
38             =head1 DESCRIPTION
39              
40             This L subclass provides specific communications to a
41             F F orientation sensor chip.
42              
43             The reader is presumed to be familiar with the general operation of this chip;
44             the documentation here will not attempt to explain or define chip-specific
45             concepts or features, only the use of this module to access them.
46              
47             =cut
48              
49             sub I2C_options
50             {
51             return (
52 3     3 0 1057 addr => 0x29,
53             max_bitrate => 400E3,
54             );
55             }
56              
57             =head1 METHODS
58              
59             The following methods documented in an C expression return L
60             instances.
61              
62             =cut
63              
64             # Actual chip hardware uses "paged" registers. We'll fake this for the cache
65             # by using the 8th bit to store the page number
66              
67 4     4   29 use constant REGPAGE_1 => 0x80;
  4         7  
  4         300  
68              
69             use constant {
70 4         3599 REG_CHIP_ID => 0x00,
71             REG_PAGE_ID => 0x07,
72             REG_ACC_DATA_X_LSB => 0x08,
73             REG_MAG_DATA_X_LSB => 0x0E,
74             REG_GYR_DATA_X_LSB => 0x14,
75             REG_EUL_Heading_LSB => 0x1A,
76             REG_QUA_Data_w_LSB => 0x20,
77             REG_LIA_Data_X_LSB => 0x28,
78             REG_GRV_Data_X_LSB => 0x2E,
79             REG_UNIT_SEL => 0x3B,
80             REG_OPR_MODE => 0x3D,
81              
82             REG_ACC_Config => REGPAGE_1 | 0x08,
83 4     4   23 };
  4         4  
84              
85             my @OPR_MODES = qw(
86             CONFIGMODE ACCONLY MAGONLY GYROONLY ACCMAG ACCGYRO MAGGYRO AMG
87             IMU COMPASS M4G NDOF_FMC_OFF NDOF
88             );
89              
90             bitfield { format => "bytes-LE" }, OPR_MODE =>
91             OPR_MODE => enumfield(0, @OPR_MODES);
92              
93             bitfield { format => "bytes-LE" }, CONFIG =>
94             # Page 0
95             #
96             # REG_UNIT_SEL
97             ACC_Unit => enumfield( 0, qw( m/s² mg )),
98             GYR_Unit => enumfield( 1, qw( dps rps )),
99             EUL_Unit => enumfield( 2, qw( degrees radians )),
100             TEMP_Unit => enumfield( 4, qw( Celsius Farenheit )),
101             ORI_Android_Windows => enumfield( 7, qw( Windows Android )),
102              
103             # REG_OPR_MODE
104             OPR_MODE => enumfield(2*8+0, @OPR_MODES),
105              
106             # REG_PWR_MODE
107             PWR_MODE => enumfield(3*8+0, qw( normal low-power suspend . )),
108              
109             # REG_TEMP_SOURCE
110             TEMP_Source => enumfield(5*8+0, qw( accelerometer gyroscope . . )),
111              
112             # REG_AXIS_MAP_CONFIG
113             X_AXIS_MAP => enumfield(6*8+0, qw( X Y Z . )),
114             Y_AXIS_MAP => enumfield(6*8+2, qw( X Y Z . )),
115             Z_AXIS_MAP => enumfield(6*8+4, qw( X Y Z . )),
116             # REG_AXIS_MAP_SIGN
117             X_AXIS_SIGN => enumfield(7*8+0, qw( positive negative )),
118             Y_AXIS_SIGN => enumfield(7*8+1, qw( positive negative )),
119             Z_AXIS_SIGN => enumfield(7*8+2, qw( positive negative )),
120              
121             # Page 1
122             #
123             # REG_ACC_Config
124             ACC_Range => enumfield(8*8+0, qw( 2G 4G 8G 16G )),
125             ACC_BW => enumfield(8*8+2, qw( 7.81Hz 15.63Hz 31.25Hz 62.5Hz 125Hz 250Hz 500Hz 1000Hz )),
126             ACC_PWR_Mode => enumfield(8*8+5, qw( normal suspend low-power-1 standby low-power-2 deep-suspend . . )),
127             # REG_MAG_Config
128             MAG_Data_output_rate => enumfield(9*8+0, qw( 2Hz 6Hz 8Hz 10Hz 15Hz 20Hz 25Hz 30Hz )),
129             MAG_OPR_Mode => enumfield(9*8+3, qw( low-power regular enhanced-regular high-accuracy )),
130             MAG_Power_mode => enumfield(9*8+6, qw( normal sleep suspend force-mode )),
131             # GYR_Config_0
132             GYR_Range => enumfield(10*8+0, qw( 2000dps 1000dps 500dps 250dps 125dps . . . )),
133             GYR_Bandwidth => enumfield(10*8+3, qw( 523Hz 230Hz 116Hz 47Hz 23Hz 12Hz 64Hz 32Hz )),
134             # GYR_Config_1
135             GYR_Power_Mode => enumfield(11*8+0, qw( normal fast-powerup deep-suspend suspend advanced-powersave . . . )),
136             ;
137              
138             has $_reg_page = 0;
139             has $_regcache = "";
140              
141             has $_ACC_Unit;
142             has $_GYR_Unit;
143             has $_EUL_Unit;
144              
145 12         23 async method read_reg ( $reg, $len )
  12         25  
  12         24  
  12         20  
146 12         28 {
147 12         59 my $protocol = $self->protocol;
148              
149 12         83 my $page = 0 + ( $reg >= REGPAGE_1 ); $reg &= ~REGPAGE_1;
  12         24  
150              
151 12 100       42 if( $_reg_page != $page ) {
152 3         34 await $protocol->write( pack "C C", REG_PAGE_ID, $page );
153 3         1439 $_reg_page = $page;
154             }
155              
156 12         93 return await $protocol->write_then_read( pack( "C", $reg ), $len );
157 12     12 0 27 }
158              
159 14         20 async method cached_read_reg ( $reg, $len )
  14         27  
  14         33  
  14         22  
160 14         40 {
161 4     4   29 no warnings 'numeric'; # quiet the warning about negative repeat count
  4         7  
  4         1673  
162              
163 14 100       44 if( length $_regcache >= $reg + $len ) {
164 10         117 return substr( $_regcache, $reg, $len );
165             }
166              
167 4         25 my $bytes = await $self->read_reg( $reg, $len );
168 4         4590 $_regcache .= "\0" x ( $reg + $len - length $_regcache );
169 4         12 substr( $_regcache, $reg, $len ) = $bytes;
170              
171 4         15 return $bytes;
172 14     14 0 14788 }
173              
174 2         3 async method write_reg ( $reg, $bytes )
  2         4  
  2         3  
  2         4  
175 2         8 {
176 2         11 my $protocol = $self->protocol;
177              
178 2         14 my $page = 0 + ( $reg >= REGPAGE_1 ); $reg &= ~REGPAGE_1;
  2         5  
179              
180 2 100       5 if( $_reg_page != $page ) {
181 1         10 await $protocol->write( pack "C C", REG_PAGE_ID, $page );
182 1         226 $_reg_page = $page;
183             }
184              
185 2         16 return await $protocol->write( pack "C a*", $reg, $bytes );
186 2     2 0 12 }
187              
188 3         3 async method cached_write_reg ( $reg, $bytes )
  3         6  
  3         4  
  3         6  
189 3         12 {
190 4     4   25 no warnings 'numeric'; # quiet the warning about negative repeat count
  4         8  
  4         10831  
191              
192             # Trim common prefix/suffix
193 3   100     25 while( length $bytes and substr( $bytes, 0, 1 ) eq substr( $_regcache, $reg, 1 ) ) {
194 11         23 $bytes =~ s/^.//s;
195 11         25 $reg++;
196             }
197              
198 3         5 my $len = length $bytes;
199 3   66     21 while( $len and substr( $bytes, $len - 1, 1 ) eq substr( $_regcache, $reg + $len - 1, 1 ) ) {
200 0         0 $bytes =~ s/.$//s;
201 0         0 $len--;
202             }
203              
204 3 100       14 return unless $len;
205              
206 2         9 await $self->write_reg( $reg, $bytes );
207              
208 2         2243 $_regcache .= "\0" x ( $reg + $len - length $_regcache );
209 2         17 substr( $_regcache, $reg, $len ) = $bytes;
210 3     3 0 1162 }
211              
212             =head2 read_ids
213              
214             $ids = await $chip->read_ids;
215              
216             Returns an 8-character string composed of the four ID registers. For a
217             C chip this should be the string
218              
219             "A0FB320F"
220              
221             =cut
222              
223 1         2 async method read_ids ()
  1         2  
224 1         3 {
225 1         6 return uc unpack "H*", await $self->read_reg( REG_CHIP_ID, 4 );
226 1     1 1 213 }
227              
228             =head2 read_config
229              
230             $config = await $chip->read_config;
231              
232             Returns the current chip configuration.
233              
234             =cut
235              
236 7         13 async method read_config ()
  7         12  
237 7         22 {
238             return {
239 7         41 unpack_CONFIG( join "", await Future->needs_all(
240             $self->cached_read_reg( REG_UNIT_SEL, 8 ),
241             $self->cached_read_reg( REG_ACC_Config, 4 ),
242             ) ),
243             };
244 7     7 1 2822 }
245              
246             =head2 change_config
247              
248             await $chip->change_config( %changes );
249              
250             Changes the configuration. Any field names not mentioned will be preserved at
251             their existing values.
252              
253             This method can only be used while the chip is in config mode, and cannot
254             itself be used to set C. For that, use L.
255              
256             =cut
257              
258 1         2 async method change_config ( %changes )
  1         4  
  1         2  
259 1         6 {
260             $changes{OPR_MODE} and
261 1 50       5 croak "->change_config cannot change OPR_MODE; use ->set_opr_mode\n";
262              
263 1         6 my $config = await $self->read_config;
264              
265 1 50       411 $config->{OPR_MODE} eq "CONFIGMODE" or
266             croak "Can only ->change_config when in CONFIGMODE";
267              
268 1         6 $config->{$_} = $changes{$_} for keys %changes;
269              
270 1         6 my ( $page0, $page1 ) = unpack "a8 a4", pack_CONFIG( %$config );
271              
272 1         441 await Future->needs_all(
273             $self->cached_write_reg( REG_UNIT_SEL, $page0 ),
274             $self->cached_write_reg( REG_ACC_Config, $page1 ),
275             );
276 1     1 1 4281 }
277              
278             =head2 set_opr_mode
279              
280             await $chip->set_opr_mode( $mode );
281              
282             Sets the C register.
283              
284             =cut
285              
286 1         2 async method set_opr_mode ( $mode )
  1         3  
  1         1  
287 1         6 {
288             # TODO: Only allow state transitions when one of old or new mode is CONFIGMODE
289              
290 1         8 await $self->cached_write_reg( REG_OPR_MODE, pack_OPR_MODE( OPR_MODE => $mode ) );
291 1     1 1 2733 }
292              
293             =head2 read_accelerometer_raw
294              
295             ( $x, $y, $z ) = await $chip->read_accelerometer_raw;
296              
297             Returns the most recent accelerometer readings in raw 16bit signed integers
298              
299             =cut
300              
301 1         2 async method read_accelerometer_raw ()
  1         2  
302 1         3 {
303 1         4 return unpack "s< s< s<", await $self->read_reg( REG_ACC_DATA_X_LSB, 6 );
304 1     1 1 4 }
305              
306             =head2 read_accelerometer
307              
308             ( $x, $y, $z ) = await $chip->read_accelerometer;
309              
310             Returns the most recent accelerometer readings in converted units, either
311             C or C depending on the chip's C configuration.
312              
313             =cut
314              
315 1         2 async method read_accelerometer ()
  1         2  
316 1         5 {
317 1   33     10 my $units = $_ACC_Unit //= ( await $self->read_config )->{ACC_Unit};
318              
319 1 50       421 if( $units eq "mg" ) {
    50          
320 0         0 return map { $_ / 1000 } await $self->read_accelerometer_raw;
  0         0  
321             }
322             elsif( $units eq "m/s²" ) {
323 1         5 return map { $_ / 100 } await $self->read_accelerometer_raw;
  3         1025  
324             }
325 1     1 1 3778 }
326              
327             =head2 read_magnetometer_raw
328              
329             ( $x, $y, $z ) = await $chip->read_magnetometer_raw;
330              
331             Returns the most recent magnetometer readings in raw 16bit signed integers
332              
333             =cut
334              
335 1         2 async method read_magnetometer_raw ()
  1         2  
336 1         2 {
337 1         5 return unpack "s< s< s<", await $self->read_reg( REG_MAG_DATA_X_LSB, 6 );
338 1     1 1 3 }
339              
340             =head2 read_magnetometer
341              
342             ( $x, $y, $z ) = await $chip->read_magnetometer;
343              
344             Returns the most recent magnetometer readings in converted units of C<µT>.
345              
346             =cut
347              
348 1         2 async method read_magnetometer ()
  1         2  
349 1         6 {
350 1         5 return map { $_ / 16 } await $self->read_magnetometer_raw;
  3         1318  
351 1     1 1 3135 }
352              
353             =head2 read_gyroscope_raw
354              
355             ( $x, $y, $z ) = await $chip->read_gyroscope_raw;
356              
357             Returns the most recent gyroscope readings in raw 16bit signed integers
358              
359             =cut
360              
361 1         2 async method read_gyroscope_raw ()
  1         2  
362 1         3 {
363 1         5 return unpack "s< s< s<", await $self->read_reg( REG_GYR_DATA_X_LSB, 6 );
364 1     1 1 3 }
365              
366             =head2 read_gyroscope
367              
368             ( $x, $y, $z ) = await $chip->read_gyroscope;
369              
370             Returns the most recent gyroscope readings in converted units, either
371             C or C depending on the chip's C configuration.
372              
373             =cut
374              
375 1         3 async method read_gyroscope ()
  1         2  
376 1         6 {
377 1   33     7 my $units = $_GYR_Unit //= ( await $self->read_config )->{GYR_Unit};
378              
379 1 50       409 if( $units eq "dps" ) {
    0          
380 1         6 return map { $_ / 16 } await $self->read_gyroscope_raw;
  3         1305  
381             }
382             elsif( $units eq "rps" ) {
383 0         0 return map { $_ / 900 } await $self->read_gyroscope_raw;
  0         0  
384             }
385 1     1 1 2757 }
386              
387             =head2 read_euler_angles
388              
389             ( $heading, $roll, $pitch ) = await $chip->read_euler_angles;
390              
391             Returns the most recent Euler angle fusion readings in converted units, either
392             degrees or radians depending on the chip's C configuration.
393              
394             =cut
395              
396 1         2 async method read_euler_angles ()
  1         3  
397 1         6 {
398 1   33     7 my $units = $_EUL_Unit //= ( await $self->read_config )->{EUL_Unit};
399              
400 1         408 my ( $heading, $roll, $pitch ) = unpack "s< s< s<", await $self->read_reg( REG_EUL_Heading_LSB, 6 );
401              
402 1 50       1219 if( $units eq "degrees" ) {
    0          
403 1         3 return map { $_ / 16 } ( $heading, $roll, $pitch );
  3         9  
404             }
405             elsif( $units eq "radians" ) {
406 0         0 return map { $_ / 900 } ( $heading, $roll, $pitch );
  0         0  
407             }
408 1     1 1 2890 }
409              
410             =head2 read_quarternion
411              
412             ( $w, $x, $y, $z ) = await $chip->read_quarternion;
413              
414             Returns the most recent quarternion fusion readings in converted units as
415             scaled numbers between -1 and 1.
416              
417             =cut
418              
419 1         2 async method read_quarternion ()
  1         2  
420 1         5 {
421 1         6 my ( $w, $x, $y, $z ) = unpack "s< s< s< s<", await $self->read_reg( REG_QUA_Data_w_LSB, 8 );
422              
423 1         1234 return map { $_ / 2**14 } ( $w, $x, $y, $z );
  4         10  
424 1     1 1 2796 }
425              
426             =head2 read_linear_acceleration
427              
428             ( $x, $y, $z ) = await $chip->read_linear_acceleration;
429              
430             Returns the most recent linear acceleration fusion readings in converted
431             units, either C or C depending on the chip's C
432             configuration.
433              
434             =cut
435              
436 1         3 async method read_linear_acceleration ()
  1         2  
437 1         5 {
438 1   33     8 my $units = $_ACC_Unit //= ( await $self->read_config )->{ACC_Unit};
439              
440 1         6 my ( $x, $y, $z ) = unpack "s< s< s<", await $self->read_reg( REG_LIA_Data_X_LSB, 6 );
441              
442 1 50       1256 if( $units eq "mg" ) {
    50          
443 0         0 return map { $_ / 1000 } ( $x, $y, $z );
  0         0  
444             }
445             elsif( $units eq "m/s²" ) {
446 1         3 return map { $_ / 100 } ( $x, $y, $z );
  3         8  
447             }
448 1     1 1 2860 }
449              
450             =head2 read_linear_acceleration
451              
452             ( $x, $y, $z ) = await $chip->read_linear_acceleration;
453              
454             Returns the most recent gravity fusion readings in converted units, either
455             C or C depending on the chip's C configuration.
456              
457             =cut
458              
459 1         2 async method read_gravity ()
  1         2  
460 1         6 {
461 1   33     5 my $units = $_ACC_Unit //= ( await $self->read_config )->{ACC_Unit};
462              
463 1         5 my ( $x, $y, $z ) = unpack "s< s< s<", await $self->read_reg( REG_GRV_Data_X_LSB, 6 );
464              
465 1 50       1261 if( $units eq "mg" ) {
    50          
466 0         0 return map { $_ / 1000 } ( $x, $y, $z );
  0         0  
467             }
468             elsif( $units eq "m/s²" ) {
469 1         56 return map { $_ / 100 } ( $x, $y, $z );
  3         12  
470             }
471 1     1 0 3130 }
472              
473             =head1 AUTHOR
474              
475             Paul Evans
476              
477             =cut
478              
479             0x55AA;