File Coverage

blib/lib/Device/Chip/BME280.pm
Criterion Covered Total %
statement 102 114 89.4
branch 9 12 75.0
condition 2 3 66.6
subroutine 19 21 90.4
pod 6 8 75.0
total 138 158 87.3


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, 2020-2021 -- leonerd@leonerd.org.uk
5              
6 5     5   415552 use v5.26;
  5         51  
7 5     5   470 use Object::Pad 0.57;
  5         8427  
  5         22  
8              
9             package Device::Chip::BME280 0.04;
10             class Device::Chip::BME280
11 5     5   2614 :isa(Device::Chip::Base::RegisteredI2C);
  5         27535  
  5         175  
12              
13 5     5   2873 use Device::Chip::Sensor -declare;
  5         9047  
  5         21  
14              
15 5     5   2774 use Data::Bitfield qw( bitfield enumfield boolfield );
  5         9213  
  5         343  
16 5     5   36 use Future::AsyncAwait;
  5         8  
  5         31  
17              
18 5     5   184 use utf8;
  5         6  
  5         26  
19              
20             =encoding UTF-8
21              
22             =head1 NAME
23              
24             C - chip driver for F
25              
26             =head1 SYNOPSIS
27              
28             use Device::Chip::BME280;
29             use Future::AsyncAwait;
30              
31             my $chip = Device::Chip::BME280->new;
32             await $chip->mount( Device::Chip::Adapter::...->new );
33              
34             await $chip->change_config(
35             OSRS_H => 4,
36             OSRS_P => 4,
37             OSRS_T => 4,
38             MODE => "NORMAL",
39             );
40              
41             my ( $pressure, $temperature, $humidity ) = await $chip->read_sensor;
42              
43             printf "Temperature=%.2fC ", $temperature;
44             printf "Pressure=%dPa ", $pressure;
45             printf "Humidity=%.2f%%\n", $humidity;
46              
47             =head1 DESCRIPTION
48              
49             This L subclass provides specific communication to a F
50             F attached to a computer via an I²C adapter.
51              
52             The reader is presumed to be familiar with the general operation of this chip;
53             the documentation here will not attempt to explain or define chip-specific
54             concepts or features, only the use of this module to access them.
55              
56             =cut
57              
58             method I2C_options
59 4     4 0 1284 {
60             return (
61 4         32 addr => 0x76,
62             max_bitrate => 400E3,
63             );
64             }
65              
66             use constant {
67 5         13426 REG_DIG_T1 => 0x88,
68             REG_DIG_P1 => 0x8E,
69             REG_DIG_H1 => 0xA1,
70             REG_ID => 0xD0,
71             REG_RESET => 0xE0,
72             REG_DIG_H2 => 0xE1,
73             REG_CTRL_HUM => 0xF2,
74             REG_STATUS => 0xF3,
75             REG_CTRL_MEAS => 0xF4,
76             REG_CONFIG => 0xF5,
77             REG_PRESS => 0xF7, # 24bit
78             REG_TEMP => 0xFA, # 24bit
79             REG_HUM => 0xFD,
80 5     5   677 };
  5         9  
81              
82             bitfield { format => "bytes-LE" }, config =>
83             # REG_CTRL_HUM
84             OSRS_H => enumfield( 0, qw( SKIP 1 2 4 8 16 16 16 ) ),
85             # REG_CTRL_MEAS
86             MODE => enumfield( 2*8+0, qw( SLEEP FORCED FORCED NORMAL ) ),
87             OSRS_P => enumfield( 2*8+2, qw( SKIP 1 2 4 8 16 16 16 ) ),
88             OSRS_T => enumfield( 2*8+5, qw( SKIP 1 2 4 8 16 16 16 ) ),
89             # REG_CONFIG
90             SPI3W_EN => boolfield( 3*8+0 ),
91             FILTER => enumfield( 3*8+2, qw( OFF 2 4 8 16 16 16 16 ) ),
92             T_SB => enumfield( 3*8+5, qw( 0.5 62.5 125 250 500 1000 10 20 ) );
93              
94             =head1 METHODS
95              
96             The following methods documented in an C expression return L
97             instances.
98              
99             =cut
100              
101             =head2 read_id
102              
103             $id = await $chip->read_id
104              
105             Returns the chip ID.
106              
107             =cut
108              
109             async method read_id
110 1         3 {
111 1         5 return unpack "C", await $self->read_reg( REG_ID, 1 );
112 1     1 1 200 }
113              
114             =head2 read_config
115              
116             $config = await $chip->read_config
117              
118             Returns a C reference containing the chip config, using fields named
119             from the data sheet.
120              
121             FILTER => OFF | 2 | 4 | 8 | 16
122             MODE => SLEEP | FORCED | NORMAL
123             OSRS_H => SKIP | 1 | 2 | 4 | 8 | 16
124             OSRS_P => SKIP | 1 | 2 | 4 | 8 | 16
125             OSRS_T => SKIP | 1 | 2 | 4 | 8 | 16
126             SPI3W_EN => 0 | 1
127             T_SB => 0.5 | 10 | 20 | 62.5 | 125 | 250 | 500 | 1000
128              
129             =cut
130              
131 5         13 async method read_config ()
  5         7  
132 5         23 {
133 5         30 my $bytes = await $self->cached_read_reg( REG_CTRL_HUM, 4 );
134              
135 5         18868 return { unpack_config( $bytes ) };
136 5     5 1 3378 }
137              
138             =head2 change_config
139              
140             await $chip->change_config( %changes )
141              
142             Writes updates to the configuration registers.
143              
144             Note that these two methods use a cache of configuration bytes to make
145             subsequent modifications more efficient.
146              
147             =cut
148              
149 1         3 async method change_config ( %changes )
  1         5  
  1         2  
150 1         5 {
151 1         6 my $config = await $self->read_config;
152              
153 1         163 my $bytes = pack_config( %$config, %changes );
154              
155             # Don't write REG_STATUS
156 1         189 await $self->cached_write_reg( REG_CTRL_HUM, substr( $bytes, 0, 1 ) );
157 1         2011 await $self->cached_write_reg( REG_CTRL_MEAS, substr( $bytes, 2, 2 ) );
158 1     1 1 2781 }
159              
160 0         0 async method initialize_sensors ()
  0         0  
161 0         0 {
162 0         0 await $self->change_config(
163             MODE => "NORMAL",
164             OSRS_H => 4,
165             OSRS_P => 4,
166             OSRS_T => 4,
167             FILTER => 4,
168             );
169              
170             # First read after startup contains junk values
171 0         0 await $self->read_sensor;
172 0     0 0 0 }
173              
174             =head2 read_status
175              
176             $status = await $chip->read_status;
177              
178             =cut
179              
180 0         0 async method read_status ()
  0         0  
181 0         0 {
182 0         0 my $byte = await $self->read_reg( REG_STATUS, 1 );
183              
184             return {
185 0         0 MEASURING => !!( $byte & (1<<3) ),
186             IM_UPDATE => !!( $byte & (1<<0) ),
187             };
188 0     0 1 0 }
189              
190             =head2 read_raw
191              
192             ( $adc_P, $adc_T, $adc_H ) = await $chip->read_raw
193              
194             Returns three integers containing the raw ADC reading values from the sensor.
195              
196             This method is mostly for testing or internal purposes only. For converted
197             sensor readings in real-world units you want to use L.
198              
199             =cut
200              
201 8         11 async method read_raw ()
  8         10  
202 8         12 {
203 8         25 my ( $bytesP, $bytesT, $bytesH ) = unpack "a3 a3 a2",
204             await $self->read_reg( REG_PRESS, 8 );
205              
206             return (
207 7         13577 unpack( "L>", "\x00" . $bytesP ) >> 4,
208             unpack( "L>", "\x00" . $bytesT ) >> 4,
209             unpack( "S>", $bytesH ),
210             );
211 8     8 1 214 }
212              
213             # Compensation formulae directly from BME280 datasheet section 8.1
214              
215             has $_t_fine;
216              
217             has @_dig_T;
218              
219 10         12 async method _compensate_temperature ( $adc_T )
  10         13  
  10         12  
220 10         27 {
221 10 100       34 @_dig_T or
222             @_dig_T = ( undef, unpack "S< s< s<", await $self->read_reg( REG_DIG_T1, 6 ) );
223              
224 10         2324 my $var1 = ($adc_T / 16384 - $_dig_T[1] / 1024) * $_dig_T[2];
225 10         30 my $var2 = ($adc_T / 131072 - $_dig_T[1] / 8192) ** 2 * $_dig_T[3];
226              
227 10         18 $_t_fine = int( $var1 + $var2 );
228 10         19 my $T = ( $var1 + $var2 ) / 5120.0;
229 10         51 return $T;
230 10     10   18 }
231              
232             has @_dig_P;
233              
234 4         7 async method _compensate_pressure ( $adc_P )
  4         6  
  4         8  
235 4         35 {
236 4 100       17 @_dig_P or
237             @_dig_P = ( undef, unpack "S< s< s< s< s< s< s< s< s<", await $self->read_reg( REG_DIG_P1, 18 ) );
238              
239 4         2297 my $var1 = ($_t_fine / 2) - 64000;
240 4         10 my $var2 = $var1 * $var1 * $_dig_P[6] / 32768;
241 4         10 $var2 = $var2 + $var1 * $_dig_P[5] * 2;
242 4         11 $var2 = ($var2 / 4) + ($_dig_P[4] * 65536);
243 4         8 $var1 = ($_dig_P[3] * $var1 * $var1 / 524288 + $_dig_P[2] * $var1) / 524288;
244 4         20 $var1 = (1 + $var1 / 32768) * $_dig_P[1];
245 4 50       11 return 0 if $var1 == 0; # avoid exception caused by divide-by-zero
246 4         8 my $P = 1048576 - $adc_P;
247 4         10 $P = ($P - ($var2 / 4096)) * 6250 / $var1;
248 4         7 $var1 = $_dig_P[9] * $P * $P / 2147483648;
249 4         7 $var2 = $P * $_dig_P[8] / 32768;
250 4         9 $P = $P + ($var1 + $var2 + $_dig_P[7]) / 16;
251 4         14 return $P;
252 4     4   7 }
253              
254             has @_dig_H;
255              
256 4         7 async method _compensate_humidity ( $adc_H )
  4         4  
  4         6  
257 4         9 {
258 4 100       11 unless( @_dig_H ) {
259 2         8 @_dig_H = (
260             undef,
261             unpack( "C", await $self->read_reg( REG_DIG_H1, 1 ) ),
262             unpack( "s< C ccc c", await $self->read_reg( REG_DIG_H2, 7 ) ),
263             );
264             # Reshape the two 12bit values
265 2         4592 my ( $b0, $b1, $b2 ) = splice @_dig_H, 4, 3;
266 2         11 splice @_dig_H, 4, 0,
267             ( $b0 << 4 | $b1 & 0x0F ), # H4
268             ( $b1 >> 4 | $b2 << 4 ); # H5
269             }
270              
271 4         9 my $var_H = $_t_fine - 76800;
272 4         20 $var_H = ($adc_H - ($_dig_H[4] * 64.0 + $_dig_H[5] / 16384.0 * $var_H)) *
273             ($_dig_H[2] / 65536.0 * (1.0 + $_dig_H[6] / 67108864.0 * $var_H * (1.0 + $_dig_H[3] / 67108864.0 * $var_H)));
274 4         9 $var_H = $var_H * (1.0 - $_dig_H[1] * $var_H / 524288.0);
275              
276 4 50       12 return 0 if $var_H < 0;
277 4 50       12 return 100 if $var_H > 100;
278 4         14 return $var_H;
279 4     4   74 }
280              
281             =head2 read_sensor
282              
283             ( $pressure, $temperature, $humidity ) = await $chip->read_sensor
284              
285             Returns the sensor readings appropriately converted into units of Pascals for
286             pressure, degrees Celcius for temperature, and percentage relative for
287             humidity.
288              
289             =cut
290              
291             async method read_sensor
292 1         3 {
293 1         3 my ( $adc_P, $adc_T, $adc_H ) = await $self->read_raw;
294              
295             # Must do temperature first
296 1         54 my $T = await $self->_compensate_temperature( $adc_T );
297              
298             return (
299 1         52 await $self->_compensate_pressure( $adc_P ),
300             $T,
301             await $self->_compensate_humidity( $adc_H ),
302             );
303 1     1 1 3120 }
304              
305             has $_pending_read_f;
306              
307             method _next_read
308 12     12   17 {
309             return $_pending_read_f //=
310 12   66 6   44 $self->read_raw->on_ready(sub { undef $_pending_read_f });
  6         416  
311             }
312              
313             declare_sensor pressure =>
314             method => async method {
315             my ( $rawP, $rawT, undef ) = await $self->_next_read;
316             $self->_compensate_temperature( $rawT );
317             return await $self->_compensate_pressure( $rawP );
318             },
319             units => "pascals",
320             precision => 0;
321              
322             declare_sensor temperature =>
323             method => async method {
324             my ( undef, $rawT, undef ) = await $self->_next_read;
325             return await $self->_compensate_temperature( $rawT );
326             },
327             units => "°C",
328             precision => 2;
329              
330             declare_sensor humidity =>
331             method => async method {
332             my ( undef, $rawT, $rawH ) = await $self->_next_read;
333             $self->_compensate_temperature( $rawT );
334             return await $self->_compensate_humidity( $rawH );
335             },
336             units => "%RH",
337             precision => 2;
338              
339             =head1 AUTHOR
340              
341             Paul Evans
342              
343             =cut
344              
345             0x55AA;