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__ |