File Coverage

blib/lib/Device/Chip/CC1101.pm
Criterion Covered Total %
statement 231 243 95.0
branch 31 44 70.4
condition 19 32 59.3
subroutine 36 38 94.7
pod 16 18 88.8
total 333 375 88.8


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