File Coverage

blib/lib/Device/Chip/CC1101.pm
Criterion Covered Total %
statement 223 235 94.8
branch 29 42 69.0
condition 14 27 51.8
subroutine 35 37 94.5
pod 16 19 84.2
total 317 360 88.0


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 6     6   1296805 use v5.26; # postfix-deref, signatures
  6         61  
7 6     6   29 use warnings;
  6         11  
  6         166  
8 6     6   536 use Object::Pad 0.800;
  6         9055  
  6         279  
9              
10             package Device::Chip::CC1101 0.09;
11             class Device::Chip::CC1101
12 1     1   519 :isa(Device::Chip);
  1         14352  
  1         47  
13              
14 6     6   1531 use Carp;
  6         10  
  6         427  
15 6     6   2438 use Data::Bitfield 0.04 qw( bitfield boolfield enumfield intfield signed_intfield );
  6         11322  
  6         418  
16 6     6   41 use Future::AsyncAwait;
  6         10  
  6         32  
17 6     6   2840 use Future::IO;
  6         58706  
  6         305  
18              
19 6     6   42 use constant PROTOCOL => "SPI";
  6         10  
  6         2610  
20              
21             my %BANDS;
22             my %PRESET_MODES;
23              
24             my @CACHED_CONFIG = qw( APPEND_STATUS PACKET_LENGTH LENGTH_CONFIG );
25              
26             =head1 NAME
27              
28             C - chip driver for a F
29              
30             =head1 DESCRIPTION
31              
32             This L subclass provides specific communication to a
33             F F radio transceiver chip attached to a computer
34             via an SPI adapter.
35              
36             The reader is presumed to be familiar with the general operation of this chip;
37             the documentation here will not attempt to explain or define chip-specific
38             concepts or features, only the use of this module to access them.
39              
40             =cut
41              
42             =head1 CONSTRUCTOR
43              
44             =head2 new
45              
46             $chip = Device::Chip::CC1101->new( %ops )
47              
48             Constructs a new C instance. Takes the following
49             optional named arguments:
50              
51             =over 4
52              
53             =item * fosc
54              
55             Gives the XTAL oscillator frequency in Hz. This is used by the
56             L to calculate the actual frequency from the chip config.
57             A default of 26MHz applies if not supplied.
58              
59             =item * poll_interval
60              
61             Interval in seconds to poll the chip status after transmitting. A default of
62             20msec applies if not supplied.
63              
64             =back
65              
66             =cut
67              
68             field $_fosc :param = 26E6; # presets presume 26MHz XTAL
69             field $_poll_interval :param = 0.05;
70              
71             method SPI_options
72 5     5 0 1825 {
73             return (
74 5         28 mode => 0,
75             max_bitrate => 1E6,
76             );
77             }
78              
79 0     0 0 0 method power ( $on ) { $self->protocol->power( $on ) }
  0         0  
  0         0  
  0         0  
  0         0  
80              
81             =head1 METHODS
82              
83             The following methods documented in an C expression return L
84             instances.
85              
86             =cut
87              
88             use constant {
89 6         10424 REG_WRITE => 0x00,
90             REG_BURST => 0x40,
91             REG_READ => 0x80,
92              
93             REG_PKTSTATUS => 0x38,
94             REG_MARCSTATE => 0x35,
95             REG_RXBYTES => 0x3B,
96             REG_PATABLE => 0x3E,
97             REG_TXFIFO => 0x3F, # write-only
98             REG_RXFIFO => 0x3F, # read-only
99              
100             CMD_SRES => 0x30,
101             CMD_SFSTXON => 0x31,
102             CMD_SXOFF => 0x32,
103             CMD_SCAL => 0x33,
104             CMD_SRX => 0x34,
105             CMD_STX => 0x35,
106             CMD_SIDLE => 0x36,
107             CMD_SWOR => 0x38,
108             CMD_SPWD => 0x39,
109             CMD_SFRX => 0x3A,
110             CMD_SFTX => 0x3B,
111             CMD_SWORRST => 0x3C,
112             CMD_SNOP => 0x3D,
113 6     6   43 };
  6         11  
114              
115             =head2 read_register
116              
117             $value = await $chip->read_register( $addr );
118              
119             Reads a single byte register and returns its numerical value.
120              
121             C<$addr> should be between 0 and 0x3D, giving the register address.
122              
123             =cut
124              
125 17         22 async method read_register ( $addr )
  17         24  
  17         35  
126 17         31 {
127 17 50 33     74 $addr >= 0 and $addr <= 0x3D or
128             croak "Invalid register address";
129 17 100       41 $addr |= REG_BURST if $addr >= 0x30;
130              
131 17         47 return unpack "C", await $self->protocol->write_then_read(
132             pack( "C", REG_READ | $addr ), 1
133             );
134 17     17 1 12005 }
135              
136             my @GDO_CFGs = qw(
137             rx-fifo-full rx-fifo-or-eop tx-fifo-above-threshold tx-fifo-full
138             rx-fifo-overflow tx-fifo-underflow packet-in-flight packet-received
139             pqi-reached cca pll-lock sync-sck
140             sync-sdo async-sdo carrier-sense CRC_OK
141             . . . .
142             . . RX_HARD_DATA[1] RX_HARD_DATA[0]
143             . . . PA_PD
144             LNA_PD RX_SYMBOL_TICK . .
145             . . . .
146             WOR_EVNT0 WOR_EVNT1 CLK_256 CLK_32k
147             . CHIP_RDYn . XOSC_STABLE
148             . . hiZ low
149             CLK_XOSC/1 CLK_XOSC/1.5 CLK_XOSC/2 CLK_XOSC/3
150             CLK_XOSC/4 CLK_XOSC/6 CLK_XOSC/8 CLK_XOSC/12
151             CLK_XOSC/16 CLK_XOSC/24 CLK_XOSC/32 CLK_XOSC/48
152             CLK_XOSC/64 CLK_XOSC/96 CLK_XOSC/128 CLK_XOSC/196
153             );
154              
155             bitfield { format => "bytes-BE" }, CONFIG =>
156             # IOCFG2
157             GDO2_INV => boolfield( 6),
158             GDO2_CFG => enumfield( 0, @GDO_CFGs),
159             # IOCFG1
160             GDO_DS => enumfield( 1*8+7, qw( low high )),
161             GDO1_INV => boolfield( 1*8+6),
162             GDO1_CFG => enumfield( 1*8+0, @GDO_CFGs),
163             # IOCFG0
164             TEMP_SENSOR_ENABLE => boolfield( 2*8+7),
165             GDO0_INV => boolfield( 2*8+6),
166             GDO0_CFG => enumfield( 2*8+0, @GDO_CFGs),
167             # FIFOTHR
168             ADC_RETENTION => boolfield( 3*8+6),
169             CLOSE_IN_RX => enumfield( 3*8+4, qw( 0dB 6dB 12dB 18dB )),
170             FIFO_THR => intfield ( 3*8+0, 4), # TODO enum
171             # SYNC0..1
172             SYNC => intfield ( 4*8+0, 16),
173             # PKTLEN
174             PACKET_LENGTH => intfield ( 6*8+0, 8),
175             # PKTCTRL1
176             PQT => intfield ( 7*8+5, 3),
177             CRC_AUTOFLUSH => boolfield( 7*8+3),
178             APPEND_STATUS => boolfield( 7*8+2),
179             ADR_CHK => enumfield( 7*8+0, qw( none addr addr+bc addr+2bc )),
180             # PKTCTRL0
181             WHITE_DATA => boolfield( 8*8+6),
182             PKT_FORMAT => enumfield( 8*8+4, qw( fifo sync random async )),
183             CRC_EN => boolfield( 8*8+2),
184             LENGTH_CONFIG => enumfield( 8*8+0, qw( fixed variable infinite . )),
185             # ADDR
186             DEVICE_ADDR => intfield ( 9*8+0, 8),
187             # CHANNR
188             CHAN => intfield (10*8+0, 8),
189             # FSCTRL1
190             FREQ_IF => intfield (11*8+0, 5),
191             # FSCTRL0
192             FREQOFF => signed_intfield(12*8+0, 8),
193             # FREQ0..2
194             FREQ => intfield (13*8+0, 24),
195             # MDMCFG4
196             CHANBW_E => intfield (16*8+6, 2),
197             CHANBW_M => intfield (16*8+4, 2),
198             DRATE_E => intfield (16*8+0, 4),
199             # MDMCFG3
200             DRATE_M => intfield (17*8+0, 8),
201             # MDMCFG2
202             DEM_DCFILT_OFF => boolfield(18*8+7),
203             MOD_FORMAT => enumfield(18*8+4, qw( 2-FSK GFSK . ASK 4-FSK . . MSK )),
204             MANCHESTER_EN => boolfield(18*8+3),
205             SYNC_MODE => enumfield(18*8+0, qw( none 15/16 16/16 30/32 cs 15/16+cs 16/16+cs 30/32+cs )),
206             # MDMCFG1
207             FEC_EN => boolfield(19*8+7),
208             NUM_PREAMBLE => enumfield(19*8+4, qw( 2B 3B 4B 6B 8B 12B 16B 24B )),
209             CHANSPC_E => intfield (19*8+0, 2),
210             # MDMCFG0
211             CHANSPC_M => intfield (20*8+0, 8),
212             # DEVIATN
213             DEVIATION_E => intfield (21*8+4, 3),
214             DEVIATION_M => intfield (21*8+0, 3),
215             # MSCM2
216             RX_TIME_RSSI => boolfield(22*8+4),
217             RX_TIME_QUAL => boolfield(22*8+3),
218             RX_TIME => intfield (22*8+0, 3),
219             # MSCM1
220             CCA_MODE => enumfield(23*8+4, qw( always rssi unless-rx rssi-unless-rx )),
221             RXOFF_MODE => enumfield(23*8+2, qw( IDLE FSTXON TX RX )),
222             TXOFF_MODE => enumfield(23*8+0, qw( IDLE FSTXON TX RX )),
223             # MSCM0
224             FS_AUTOCAL => enumfield(24*8+4, qw( never on-unidle on-idle on-idle/4 )),
225             PO_TIMEOUT => enumfield(24*8+2, qw( x1 x16 x64 x256 )),
226             PIN_CTRL_EN => boolfield(24*8+1),
227             XOSC_FORCE_ON => boolfield(24*8+0),
228             # FOCCFG
229             FOC_BS_CS_GATE => boolfield(25*8+5),
230             FOC_PRE_K => enumfield(25*8+3, qw( K 2K 3K 4K )),
231             FOC_POST_K => enumfield(25*8+2, qw( PRE K/2 )),
232             FOC_LIMIT => enumfield(25*8+0, qw( 0 BW/8 BW/4 BW/2 )),
233             # BSCFG
234             BS_PRE_KI => enumfield(26*8+6, qw( KI 2KI 3KI 4KI )),
235             BS_PRE_KP => enumfield(26*8+4, qw( KP 2KP 3KP 4KP )),
236             BS_POST_KI => enumfield(26*8+3, qw( PRE KI/2 )),
237             BS_POST_KP => enumfield(26*8+2, qw( PRE KP )),
238             BS_LIMIT => enumfield(26*8+0, qw( 0 3.125% 6.25% 12.5% )),
239             # AGCCTRL2
240             MAX_DVGA_GAIN => enumfield(27*8+6, qw( max not-top not-top-2 not-top-3 )),
241             MAX_LNA_GAIN => enumfield(27*8+3, "max", map { "max-${_}dB"} qw( 2.6 6.1 7.4 9.2 11.5 14.6 17.1 )),
242             MAGN_TARGET => enumfield(27*8+0, qw( 24dB 27dB 30dB 33dB 36dB 38dB 40dB 42dB )),
243             # AGCCTRL1
244             AGC_LNA_PRIORITY => enumfield(28*8+6, qw( lna-first lna2-first )),
245             CARRIER_SENSE_REL_THR => enumfield(28*8+4, qw( disabled 5dB 10dB 14dB )),
246             CARRIER_SENSE_ABS_THR => enumfield(28*8+0,
247             "at-magn-target", ( map { "${_}dB-above" } 1 .. 7 ),
248             "disabled", ( map { "${_}dB-below" } -7 .. -1 ) ),
249             # AGCCTRL0
250             HYST_LEVEL => enumfield(29*8+6, qw( no low medium high )),
251             WAIT_TIME => enumfield(29*8+4, qw( 8sa 16sa 24sa 32sa )),
252             AGC_FREEZE => enumfield(29*8+2, qw( never after-sync freeze-analog freeze-all )),
253             FILTER_LENGTH => enumfield(29*8+0, qw( 8sa 16sa 32sa 64sa )), # TODO: in OOK/ASK modes this is different
254             # WOREVT0..1
255             EVENT0 => intfield (30*8+0, 16),
256             # WORCTRL
257             RC_PD => boolfield(32*8+7),
258             EVENT1 => enumfield(32*8+4, qw( 4clk 6clk 8clk 12clk 16clk 24clk 32clk 48clk )),
259             RC_CAL => boolfield(32*8+3),
260             WOR_RES => enumfield(32*8+0, qw( 1P 2^5P 2^10P 2^15P )),
261             # FREND1
262             LNA_CURRENT => intfield (33*8+6, 2),
263             LNA2MIX_CURRENT => intfield (33*8+4, 2),
264             LODIV_BUF_CURRENT_RX => intfield (33*8+2, 2),
265             MIX_CURRENT => intfield (33*8+0, 2),
266             # FREND0
267             LODIV_BUF_CURRENT_TX => intfield (34*8+4, 2),
268             PA_POWER => intfield (34*8+0, 3),
269             # The FSCAL registers are basically opaque
270             FSCAL => intfield (35*8+0, 32),
271             # RCCTRL too
272             RCCTRL => intfield (39*8+0, 16),
273             # The remaining registers are test registers not for user use
274             ;
275              
276             # Not used directly by this code, but helpful for unit tests, etc..
277 6         30894 use constant CONFIG_DEFAULT =>
278             "\x29\x2E\x3F\x07\xD3\x91\xFF\x04\x45\x00\x00\x0F\x00\x1E\xC4\xEC" .
279             "\x8C\x22\x02\x22\xF8\x47\x07\x30\x04\x36\x6C\x03\x40\x91\x87\x6B" .
280 6     6   45 "\xF8\x56\x10\xA9\x0A\x20\x0D\x41\x00";
  6         12  
281              
282             =head2 read_config
283              
284             $config = await $chip->read_config;
285              
286             Reads and returns the current chip configuration as a C reference.
287              
288             The returned hash will contain keys with capitalized names representing all of
289             the config register fields in the datasheet, from registers C to
290             C. Values are returned either as integers, or converted enumeration
291             names. Where documented by the datasheet, the enumeration values are
292             capitalised. Where invented by this module from the description they are given
293             in lowercase.
294              
295             The value of C is also returned, rendered as a human-readable hex
296             string in the form
297              
298             PATABLE => "01.23.45.67.89.AB.CD.EF",
299              
300             The following values are also returned, derived from the actual register
301             values as a convenience.
302              
303             carrier_frequency => "800.000MHz",
304             channel_spacing => "191.951kHz",
305             deviation => "47.607kHz",
306              
307             data_rate => "115.1kbps",
308              
309             =cut
310              
311 3     3   3805 sub _tohex ( $v ) { return sprintf "%v02X", $v }
  3         6  
  3         3  
  3         71  
312 2     2   5 sub _fromhex ( $v ) { return pack "H*", $v =~ s/\.//gr }
  2         5  
  2         3  
  2         17  
313              
314 3         5 async method _read_CONFIG ()
  3         6  
315 3         7 {
316 3         10 return await $self->protocol->write_then_read(
317             pack( "C", REG_READ | REG_BURST | 0 ), 41
318             );
319 3     3   6 }
320              
321 3         5 async method _read_PATABLE ()
  3         6  
322 3         11 {
323 3         10 return await $self->protocol->write_then_read(
324             pack( "C", REG_READ | REG_BURST | REG_PATABLE ), 8
325             );
326 3     3   25081 }
327              
328             field $_config;
329             field $_patable;
330             field %_cached_config;
331              
332 3         14 async method read_config ()
  3         5  
333 3         19 {
334 3   33     20 my %config = (
      33        
335             unpack_CONFIG( $_config //= await $self->_read_CONFIG ),
336             PATABLE => _tohex $_patable //= await $self->_read_PATABLE,
337             );
338              
339             # Post-convert some derived fields just for user convenience
340 3         20 my $fosc = $_fosc;
341              
342 3         17 my $channel_spacing = $fosc * ( 256 + $config{CHANSPC_M} ) * 2 ** $config{CHANSPC_E} / 2**18;
343              
344 3         53 $config{channel_spacing} = sprintf "%.3fkHz", $channel_spacing / 1E3;
345              
346             $config{carrier_frequency} = sprintf "%.3fMHz",
347 3         21 ( $fosc * $config{FREQ} / 2**16 + $config{CHAN} * $channel_spacing ) / 1E6;
348              
349 3         7 my $mod_format = $config{MOD_FORMAT};
350 3 50       20 if( $mod_format =~ m/-FSK$|^GFSK$/ ) {
351             $config{deviation} = sprintf "%.3fkHz",
352 3         23 $fosc * ( 8 + $config{DEVIATION_M} ) * 2 ** $config{DEVIATION_E} / 2**17 / 1E3;
353             }
354              
355             $config{data_rate} = sprintf "%.1fkbps",
356 3         20 $fosc * ( 256 + $config{DRATE_M} ) * 2 ** $config{DRATE_E} / 2**28 / 1E3;
357              
358 3         19 $_cached_config{$_} = $config{$_} for @CACHED_CONFIG;
359 3         90 return %config;
360 3     3 1 899 }
361              
362             =head2 change_config
363              
364             await $chip->change_config( %changes );
365              
366             Writes the configuration registers to apply the given changes. Any fields not
367             specified will retain their current values. The value of C can also
368             be set here. Values should be given using the same converted forms as the
369             C returns.
370              
371             The following additional lowercase-named keys are also provided as shortcuts.
372              
373             =over 4
374              
375             =item * band => STRING
376              
377             A convenient shortcut to setting the C and C configuration to
378             one of the standard ISM bands. The names of these bands are
379              
380             433MHz
381             868MHz
382              
383             =item * mode => STRING
384              
385             A convenient shortcut to setting the configuration state to one of the presets
386             supplied with the module. The names of these presets are
387              
388             GFSK-1.2kb
389             GFSK-38.4kb
390             GFSK-100kb
391             MSK-250kb
392             MSK-500kb
393              
394             =back
395              
396             =cut
397              
398 7         11 async method _write_CONFIG ( $addr, $bytes )
  7         11  
  7         18  
  7         10  
399 7         22 {
400 7         24 await $self->protocol->write(
401             pack "C a*", REG_WRITE | REG_BURST | $addr, $bytes
402             );
403 7     7   14 }
404              
405 2         4 async method _write_PATABLE ( $bytes )
  2         4  
  2         3  
406 2         7 {
407 2         7 await $self->protocol->write(
408             pack "C a*", REG_WRITE | REG_BURST | REG_PATABLE, $bytes
409             );
410 2     2   4 }
411              
412 8         13 async method change_config ( %changes )
  8         21  
  8         12  
413 8         25 {
414 8 50       20 defined $_config or await $self->read_config;
415             # Use unpack_CONFIG() directly to avoid the derived keys
416 8         26 my %config = unpack_CONFIG( $_config );
417              
418 8 100       5463 if( defined( my $mode = delete $changes{mode} ) ) {
419 1 50       4 $PRESET_MODES{$mode} or
420             croak "Unrecognised preset mode name '$mode'";
421              
422 1         38 %config = ( %config, $PRESET_MODES{common}->%*, $PRESET_MODES{$mode}->%* );
423             }
424              
425 8 100       28 if( defined( my $band = delete $changes{band} ) ) {
426 1 50       5 $BANDS{$band} or
427             croak "Unrecognised band name '$band'";
428              
429 1         56 %config = ( %config, $BANDS{$band}->%* );
430             }
431              
432 8         219 %config = ( %config, %changes );
433 8         37 my $newpatable = delete $config{PATABLE};
434 8 100       23 $newpatable = _fromhex $newpatable if defined $newpatable;
435              
436 8         12 my $oldconfig = $_config;
437 8         73 my $newconfig = pack_CONFIG( %config );
438              
439 8         11653 my $addr = 0;
440 8   100     165 $addr++ while $addr < length $newconfig and
441             substr( $newconfig, $addr, 1 ) eq substr( $oldconfig, $addr, 1 );
442              
443 8         12 my $until = length( $newconfig );
444 8   100     248 $until-- while $until > $addr and
445             substr( $newconfig, $until-1, 1 ) eq substr( $oldconfig, $until-1, 1 );
446              
447 8 100       22 if( my $len = $until - $addr ) {
448 7         27 await $self->_write_CONFIG( $addr, substr( $newconfig, $addr, $len ) );
449 7         9572 $_config = $newconfig;
450             }
451              
452 8 100 66     32 if( defined $newpatable and $newpatable ne $_patable ) {
453 2         9 await $self->_write_PATABLE( $newpatable );
454 2         2829 $_patable = $newpatable;
455             }
456              
457 8   33     186 defined $config{$_} and $_cached_config{$_} = $config{$_} for @CACHED_CONFIG;
458 8     8 1 40776 }
459              
460             =head2 read_marcstate
461              
462             $state = await $chip->read_marcstate;
463              
464             Reads the C register and returns the state name.
465              
466             =cut
467              
468             my @MARCSTATE = qw(
469             SLEEP IDLE XOFF VCOON_MC REGON_MC MANCAL VCOON REGON
470             STARTCAL BWBOOST FS_LOCK IFADCON ENDCAL RX RX_END RX_RST
471             TXRX_SWITCH RXFIFO_OVERFLOW FSTXON TX TXEND RXTX_SWITCH TXFIFO_UNDERFLOW
472             );
473              
474 11         15 async method read_marcstate ()
  11         14  
475 11         28 {
476 11         27 my $marcstate = await $self->read_register( REG_MARCSTATE );
477 11   33     13372 return $MARCSTATE[$marcstate] // $marcstate;
478 11     11 1 4495 }
479              
480             =head2 read_chipstatus_rx
481              
482             =head2 read_chipstatus_tx
483              
484             $status = await $chip->read_chipstatus_rx;
485              
486             $status = await $chip->read_chipstatus_tx;
487              
488             Reads the chip status word and returns a reference to a hash containing the
489             following:
490              
491             STATE => string
492             FIFO_BYTES_AVAILABLE => integer
493              
494             =cut
495              
496 1     1 1 3007 method read_chipstatus_rx () { $self->_read_chipstatus( REG_READ ) }
  1         4  
  1         1  
  1         5  
497 3     3 1 750 method read_chipstatus_tx () { $self->_read_chipstatus( REG_WRITE ) }
  3         7  
  3         3  
  3         11  
498              
499             my @STATES = qw( IDLE RX TX FSTXON CALIBRATE SETTLINE RXFIFO_OVERFLOW TXFIFO_UNDERFLOW );
500              
501 4         6 async method _read_chipstatus ( $rw )
  4         7  
  4         4  
502 4         8 {
503 4         13 my $status = unpack "C", await $self->protocol->readwrite(
504             pack "C", $rw | CMD_SNOP
505             );
506              
507             return {
508 4         4486 STATE => $STATES[ ( $status & 0x70 ) >> 4 ],
509             FIFO_BYTES_AVAILABLE => ( $status & 0x0F ),
510             };
511 4     4   7 }
512              
513             =head2 read_pktstatus
514              
515             $status = await $chip->read_pktstatus;
516              
517             Reads the C register and returns a reference to a hash containing
518             boolean fields of the following names:
519              
520             CRC_OK CS PQT_REACHED CCA SFD GDO0 GDO2
521              
522             =cut
523              
524 1         2 async method read_pktstatus ()
  1         2  
525 1         4 {
526 1         4 my $pktstatus = unpack "C", await $self->protocol->write_then_read(
527             pack( "C", REG_READ|REG_BURST | REG_PKTSTATUS ), 1
528             );
529              
530             return {
531 1         1142 CRC_OK => !!( $pktstatus & ( 1 << 7 ) ),
532             CS => !!( $pktstatus & ( 1 << 6 ) ),
533             PQT_REACHED => !!( $pktstatus & ( 1 << 5 ) ),
534             CCA => !!( $pktstatus & ( 1 << 4 ) ),
535             SFD => !!( $pktstatus & ( 1 << 3 ) ),
536             GDO2 => !!( $pktstatus & ( 1 << 2 ) ),
537             GDO0 => !!( $pktstatus & ( 1 << 0 ) ),
538             };
539 1     1 1 3639 }
540              
541 11         16 async method command ( $cmd )
  11         14  
  11         26  
542 11         25 {
543 11 50 33     56 $cmd >= 0x30 and $cmd <= 0x3D or
544             croak "Invalid command byte";
545              
546 11         29 await $self->protocol->write( pack( "C", $cmd ) );
547 11     11 0 43 }
548              
549             =head2 reset
550              
551             await $chip->reset;
552              
553             Command the chip to perform a software reset.
554              
555             =cut
556              
557 1         2 async method reset ()
  1         2  
558 1         3 {
559 1         3 await $self->command( CMD_SRES );
560 1     1 1 248 }
561              
562             =head2 flush_fifos
563              
564             await $chip->flush_fifos;
565              
566             Command the chip to flush the RX and TX FIFOs.
567              
568             =cut
569              
570 1         3 async method flush_fifos ()
  1         2  
571 1         3 {
572 1         3 await $self->command( CMD_SFRX );
573 1         1253 await $self->command( CMD_SFTX );
574 1     1 1 11121 }
575              
576             =head2 start_rx
577              
578             await $chip->start_rx;
579              
580             Command the chip to enter RX mode.
581              
582             =cut
583              
584 1         2 async method start_rx ()
  1         2  
585 1         4 {
586 1         4 await $self->command( CMD_SIDLE );
587 1         1169 1 until ( await $self->read_marcstate ) eq "IDLE";
588              
589 1         61 await $self->command( CMD_SRX );
590 1         1209 1 until ( await $self->read_marcstate ) eq "RX";
591 1     1 1 4250 }
592              
593             =head2 start_tx
594              
595             await $chip->start_tx;
596              
597             Command the chip to enter TX mode.
598              
599             =cut
600              
601 3         5 async method start_tx ()
  3         4  
602 3         7 {
603 3         8 await $self->command( CMD_SIDLE );
604 3         3704 1 until ( await $self->read_marcstate ) eq "IDLE";
605              
606 3         179 await $self->command( CMD_STX );
607 3         3508 1 until ( await $self->read_marcstate ) eq "TX";
608 3     3 1 4499 }
609              
610             =head2 idle
611              
612             await $chip->idle;
613              
614             Command the chip to enter IDLE mode.
615              
616             =cut
617              
618 0         0 async method idle ()
  0         0  
619 0         0 {
620 0         0 await $self->command( CMD_SIDLE );
621 0     0 1 0 }
622              
623             =head2 read_rxfifo
624              
625             $bytes = await $chip->read_rxfifo( $len );
626              
627             Reads the given number of bytes from the RX FIFO.
628              
629             =cut
630              
631 4         5 async method read_rxfifo ( $len )
  4         6  
  4         5  
632 4         11 {
633 4 50       10 await( $self->read_register( REG_RXBYTES ) ) >= $len or
634             croak "RX UNDERFLOW - not enough bytes available";
635              
636 4         4830 return await $self->protocol->write_then_read(
637             pack( "C", REG_READ | REG_BURST | REG_RXFIFO ), $len
638             );
639 4     4 1 4257 }
640              
641             =head2 write_txfifo
642              
643             await $chip->write_txfifo( $bytes );
644              
645             Writes the given bytes into the TX FIFO.
646              
647             =cut
648              
649 3         6 async method write_txfifo ( $bytes )
  3         6  
  3         3  
650 3         9 {
651 3         11 await $self->protocol->write(
652             pack "C a*", REG_WRITE | REG_BURST | REG_TXFIFO, $bytes
653             );
654 3     3 1 4553 }
655              
656             =head2 receive
657              
658             $packet = await $chip->receive;
659              
660             Retrieves a packet from the RX FIFO, returning a HASH reference.
661              
662             data => STRING
663              
664             This method automatically strips the C, C and C fields from
665             the data and adds them to the returned hash if the chip is configured with
666             C.
667              
668             RSSI => NUM (in units of dBm)
669             LQI => INT
670             CRC_OK => BOOL
671              
672             This method automatically handles prepending the packet length if the chip is
673             configured in variable-length packet mode.
674              
675             B: Note that, despite its name, this method does not currently wait for
676             a packet to be available - the caller is responsible for calling L
677             and waiting for a packet to be received. This may be provided in a later
678             version by polling chip status or using interrupts if C makes
679             them available.
680              
681             =cut
682              
683 2         3 async method receive ()
  2         2  
684 2         5 {
685             # TODO: Check for RX UNDERFLOW somehow?
686              
687 2         5 my $fixedlen = $_cached_config{LENGTH_CONFIG} eq "fixed";
688 2         4 my $append_status = $_cached_config{APPEND_STATUS};
689              
690             my $len = $fixedlen ?
691             $_cached_config{PACKET_LENGTH} :
692 2 100       7 unpack "C", await $self->read_rxfifo( 1 );
693              
694 2 50       1175 $len += 2 if $append_status;
695              
696 2         6 my $bytes = await $self->read_rxfifo( $len );
697 2         2320 my %ret;
698              
699 2 50       8 if( $append_status ) {
700 2         9 my ( $rssi, $lqi ) = unpack( "c C", substr( $bytes, -2, 2, "" ) );
701              
702             # RSSI is 2s complement in 0.5dBm units offset from -74dBm
703 2         9 $ret{RSSI} = $rssi / 2 - 74;
704              
705             # LQI/CRC_OK
706 2         3 $ret{LQI} = $lqi & 0x7F;
707 2         5 $ret{CRC_OK} = !!( $lqi & 0x80 );
708             }
709              
710 2         4 $ret{data} = $bytes;
711              
712 2         7 return \%ret;
713 2     2 1 360 }
714              
715             =head2 transmit
716              
717             await $chip->transmit( $bytes );
718              
719             Enters TX mode and sends a packet containing the given bytes.
720              
721             This method automatically handles prepending the packet length if the chip is
722             configured in variable-length packet mode.
723              
724             =cut
725              
726 2         2 async method transmit ( $bytes )
  2         3  
  2         2  
727 2         6 {
728 2         5 my $fixedlen = $_cached_config{LENGTH_CONFIG} eq "fixed";
729              
730 2         4 my $pktlen = length $bytes;
731 2 100       5 if( $fixedlen ) {
732             $pktlen == $_cached_config{PACKET_LENGTH} or
733 1 50       4 croak "Expected a packet $_cached_config{PACKET_LENGTH} bytes long";
734             }
735             else {
736             # Ensure we can't overflow either TX or RX FIFO
737 1 50       3 $pktlen <= 62 or
738             croak "Expected no more than 62 bytes of packet data"
739             }
740              
741 2         6 await $self->start_tx;
742              
743 2 100       293 $bytes = pack "C a*", $pktlen, $bytes if !$fixedlen;
744              
745 2         7 await $self->write_txfifo( $bytes );
746              
747 2         2402 my $timeout = 20; # TODO: configuration
748 2         7 while( await( $self->read_chipstatus_tx )->{STATE} eq "TX" ) {
749 0 0         $timeout-- or croak "Timed out waiting for TX to complete";
750 0           await Future::IO->sleep( $_poll_interval );
751             }
752 2     2 1 514 }
753              
754             {
755             while( readline DATA ) {
756             chomp;
757             next if m/^#/;
758             my ( $name, $fields ) = split m/\|/, $_;
759              
760             $PRESET_MODES{$name} = +{
761             map { m/(.*?)=(.*)/ } split m/,/, $fields
762             };
763             }
764              
765             %BANDS = (
766             "433MHz" => {
767             FREQ => 1091426,
768             # From the datasheet presuming 433MHz on multilayer inductors
769             PATABLE => "12.0E.1D.34.60.84.C8.C0",
770             },
771              
772             "868MHz" => {
773             FREQ => 2188650,
774             # From the datasheet presuming 868MHz on multilayer inductors
775             PATABLE => "03.0F.1E.27.50.81.CB.C2",
776             },
777             );
778             }
779              
780             0x55AA;
781              
782             =head1 TODO
783              
784             =over 4
785              
786             =item *
787              
788             Polling/interrupts to wait for RX packet
789              
790             =item *
791              
792             Support addressing modes in L and L
793              
794             =back
795              
796             =head1 AUTHOR
797              
798             Paul Evans
799              
800             =cut
801              
802             __DATA__