File Coverage

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