File Coverage

blib/lib/Device/Chip/BNO055.pm
Criterion Covered Total %
statement 190 202 94.0
branch 18 32 56.2
condition 10 21 47.6
subroutine 32 32 100.0
pod 13 19 68.4
total 263 306 85.9


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