| 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-2022 -- leonerd@leonerd.org.uk |
|
5
|
|
|
|
|
|
|
|
|
6
|
6
|
|
|
6
|
|
635358
|
use v5.26; # postfix-deref, signatures |
|
|
6
|
|
|
|
|
70
|
|
|
7
|
6
|
|
|
6
|
|
649
|
use Object::Pad 0.73 ':experimental(init_expr)'; |
|
|
6
|
|
|
|
|
10680
|
|
|
|
6
|
|
|
|
|
31
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
package Device::Chip::CC1101 0.08; |
|
10
|
|
|
|
|
|
|
class Device::Chip::CC1101 |
|
11
|
1
|
|
|
1
|
|
663
|
:isa(Device::Chip); |
|
|
1
|
|
|
|
|
19750
|
|
|
|
1
|
|
|
|
|
44
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
6
|
|
|
6
|
|
2016
|
use Carp; |
|
|
6
|
|
|
|
|
13
|
|
|
|
6
|
|
|
|
|
519
|
|
|
14
|
6
|
|
|
6
|
|
3079
|
use Data::Bitfield 0.04 qw( bitfield boolfield enumfield intfield signed_intfield ); |
|
|
6
|
|
|
|
|
13586
|
|
|
|
6
|
|
|
|
|
492
|
|
|
15
|
6
|
|
|
6
|
|
44
|
use Future::AsyncAwait; |
|
|
6
|
|
|
|
|
13
|
|
|
|
6
|
|
|
|
|
36
|
|
|
16
|
6
|
|
|
6
|
|
3377
|
use Future::IO; |
|
|
6
|
|
|
|
|
70924
|
|
|
|
6
|
|
|
|
|
322
|
|
|
17
|
|
|
|
|
|
|
|
|
18
|
6
|
|
|
6
|
|
47
|
use constant PROTOCOL => "SPI"; |
|
|
6
|
|
|
|
|
12
|
|
|
|
6
|
|
|
|
|
2747
|
|
|
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
|
|
|
|
|
|
|
field $_fosc :param = 26E6; # presets presume 26MHz XTAL |
|
68
|
|
|
|
|
|
|
field $_poll_interval :param = 0.05; |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
method SPI_options |
|
71
|
5
|
|
|
5
|
0
|
2040
|
{ |
|
72
|
|
|
|
|
|
|
return ( |
|
73
|
5
|
|
|
|
|
32
|
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
|
|
|
|
|
12306
|
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
|
|
50
|
}; |
|
|
6
|
|
|
|
|
14
|
|
|
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
|
|
|
|
|
28
|
async method read_register ( $addr ) |
|
|
17
|
|
|
|
|
28
|
|
|
|
17
|
|
|
|
|
22
|
|
|
125
|
17
|
|
|
|
|
32
|
{ |
|
126
|
17
|
50
|
33
|
|
|
95
|
$addr >= 0 and $addr <= 0x3D or |
|
127
|
|
|
|
|
|
|
croak "Invalid register address"; |
|
128
|
17
|
100
|
|
|
|
85
|
$addr |= REG_BURST if $addr >= 0x30; |
|
129
|
|
|
|
|
|
|
|
|
130
|
17
|
|
|
|
|
79
|
return unpack "C", await $self->protocol->write_then_read( |
|
131
|
|
|
|
|
|
|
pack( "C", REG_READ | $addr ), 1 |
|
132
|
|
|
|
|
|
|
); |
|
133
|
17
|
|
|
17
|
1
|
12752
|
} |
|
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
|
|
|
|
|
33002
|
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
|
|
60
|
"\xF8\x56\x10\xA9\x0A\x20\x0D\x41\x00"; |
|
|
6
|
|
|
|
|
13
|
|
|
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
|
|
4423
|
sub _tohex ( $v ) { return sprintf "%v02X", $v } |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
8
|
|
|
|
3
|
|
|
|
|
89
|
|
|
311
|
2
|
|
|
2
|
|
5
|
sub _fromhex ( $v ) { return pack "H*", $v =~ s/\.//gr } |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
19
|
|
|
312
|
|
|
|
|
|
|
|
|
313
|
3
|
|
|
|
|
6
|
async method _read_CONFIG () |
|
|
3
|
|
|
|
|
5
|
|
|
314
|
3
|
|
|
|
|
8
|
{ |
|
315
|
3
|
|
|
|
|
12
|
return await $self->protocol->write_then_read( |
|
316
|
|
|
|
|
|
|
pack( "C", REG_READ | REG_BURST | 0 ), 41 |
|
317
|
|
|
|
|
|
|
); |
|
318
|
3
|
|
|
3
|
|
6
|
} |
|
319
|
|
|
|
|
|
|
|
|
320
|
3
|
|
|
|
|
8
|
async method _read_PATABLE () |
|
|
3
|
|
|
|
|
6
|
|
|
321
|
3
|
|
|
|
|
16
|
{ |
|
322
|
3
|
|
|
|
|
16
|
return await $self->protocol->write_then_read( |
|
323
|
|
|
|
|
|
|
pack( "C", REG_READ | REG_BURST | REG_PATABLE ), 8 |
|
324
|
|
|
|
|
|
|
); |
|
325
|
3
|
|
|
3
|
|
30437
|
} |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
field $_config; |
|
328
|
|
|
|
|
|
|
field $_patable; |
|
329
|
|
|
|
|
|
|
field %_cached_config; |
|
330
|
|
|
|
|
|
|
|
|
331
|
3
|
|
|
|
|
6
|
async method read_config () |
|
|
3
|
|
|
|
|
6
|
|
|
332
|
3
|
|
|
|
|
12
|
{ |
|
333
|
3
|
|
33
|
|
|
33
|
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
|
|
|
|
|
23
|
my $fosc = $_fosc; |
|
340
|
|
|
|
|
|
|
|
|
341
|
3
|
|
|
|
|
23
|
my $channel_spacing = $fosc * ( 256 + $config{CHANSPC_M} ) * 2 ** $config{CHANSPC_E} / 2**18; |
|
342
|
|
|
|
|
|
|
|
|
343
|
3
|
|
|
|
|
42
|
$config{channel_spacing} = sprintf "%.3fkHz", $channel_spacing / 1E3; |
|
344
|
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
$config{carrier_frequency} = sprintf "%.3fMHz", |
|
346
|
3
|
|
|
|
|
28
|
( $fosc * $config{FREQ} / 2**16 + $config{CHAN} * $channel_spacing ) / 1E6; |
|
347
|
|
|
|
|
|
|
|
|
348
|
3
|
|
|
|
|
8
|
my $mod_format = $config{MOD_FORMAT}; |
|
349
|
3
|
50
|
|
|
|
22
|
if( $mod_format =~ m/-FSK$|^GFSK$/ ) { |
|
350
|
|
|
|
|
|
|
$config{deviation} = sprintf "%.3fkHz", |
|
351
|
3
|
|
|
|
|
26
|
$fosc * ( 8 + $config{DEVIATION_M} ) * 2 ** $config{DEVIATION_E} / 2**17 / 1E3; |
|
352
|
|
|
|
|
|
|
} |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
$config{data_rate} = sprintf "%.1fkbps", |
|
355
|
3
|
|
|
|
|
23
|
$fosc * ( 256 + $config{DRATE_M} ) * 2 ** $config{DRATE_E} / 2**28 / 1E3; |
|
356
|
|
|
|
|
|
|
|
|
357
|
3
|
|
|
|
|
22
|
$_cached_config{$_} = $config{$_} for @CACHED_CONFIG; |
|
358
|
3
|
|
|
|
|
106
|
return %config; |
|
359
|
3
|
|
|
3
|
1
|
1114
|
} |
|
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
|
|
|
|
|
12
|
async method _write_CONFIG ( $addr, $bytes ) |
|
|
7
|
|
|
|
|
14
|
|
|
|
7
|
|
|
|
|
25
|
|
|
|
7
|
|
|
|
|
11
|
|
|
398
|
7
|
|
|
|
|
28
|
{ |
|
399
|
7
|
|
|
|
|
28
|
await $self->protocol->write( |
|
400
|
|
|
|
|
|
|
pack "C a*", REG_WRITE | REG_BURST | $addr, $bytes |
|
401
|
|
|
|
|
|
|
); |
|
402
|
7
|
|
|
7
|
|
15
|
} |
|
403
|
|
|
|
|
|
|
|
|
404
|
2
|
|
|
|
|
6
|
async method _write_PATABLE ( $bytes ) |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
4
|
|
|
405
|
2
|
|
|
|
|
6
|
{ |
|
406
|
2
|
|
|
|
|
8
|
await $self->protocol->write( |
|
407
|
|
|
|
|
|
|
pack "C a*", REG_WRITE | REG_BURST | REG_PATABLE, $bytes |
|
408
|
|
|
|
|
|
|
); |
|
409
|
2
|
|
|
2
|
|
4
|
} |
|
410
|
|
|
|
|
|
|
|
|
411
|
8
|
|
|
|
|
15
|
async method change_config ( %changes ) |
|
|
8
|
|
|
|
|
27
|
|
|
|
8
|
|
|
|
|
13
|
|
|
412
|
8
|
|
|
|
|
28
|
{ |
|
413
|
8
|
50
|
|
|
|
40
|
defined $_config or await $self->read_config; |
|
414
|
|
|
|
|
|
|
# Use unpack_CONFIG() directly to avoid the derived keys |
|
415
|
8
|
|
|
|
|
32
|
my %config = unpack_CONFIG( $_config ); |
|
416
|
|
|
|
|
|
|
|
|
417
|
8
|
100
|
|
|
|
5907
|
if( defined( my $mode = delete $changes{mode} ) ) { |
|
418
|
1
|
50
|
|
|
|
5
|
$PRESET_MODES{$mode} or |
|
419
|
|
|
|
|
|
|
croak "Unrecognised preset mode name '$mode'"; |
|
420
|
|
|
|
|
|
|
|
|
421
|
1
|
|
|
|
|
44
|
%config = ( %config, $PRESET_MODES{common}->%*, $PRESET_MODES{$mode}->%* ); |
|
422
|
|
|
|
|
|
|
} |
|
423
|
|
|
|
|
|
|
|
|
424
|
8
|
100
|
|
|
|
32
|
if( defined( my $band = delete $changes{band} ) ) { |
|
425
|
1
|
50
|
|
|
|
5
|
$BANDS{$band} or |
|
426
|
|
|
|
|
|
|
croak "Unrecognised band name '$band'"; |
|
427
|
|
|
|
|
|
|
|
|
428
|
1
|
|
|
|
|
31
|
%config = ( %config, $BANDS{$band}->%* ); |
|
429
|
|
|
|
|
|
|
} |
|
430
|
|
|
|
|
|
|
|
|
431
|
8
|
|
|
|
|
267
|
%config = ( %config, %changes ); |
|
432
|
8
|
|
|
|
|
47
|
my $newpatable = delete $config{PATABLE}; |
|
433
|
8
|
100
|
|
|
|
26
|
$newpatable = _fromhex $newpatable if defined $newpatable; |
|
434
|
|
|
|
|
|
|
|
|
435
|
8
|
|
|
|
|
13
|
my $oldconfig = $_config; |
|
436
|
8
|
|
|
|
|
95
|
my $newconfig = pack_CONFIG( %config ); |
|
437
|
|
|
|
|
|
|
|
|
438
|
8
|
|
|
|
|
13506
|
my $addr = 0; |
|
439
|
8
|
|
100
|
|
|
199
|
$addr++ while $addr < length $newconfig and |
|
440
|
|
|
|
|
|
|
substr( $newconfig, $addr, 1 ) eq substr( $oldconfig, $addr, 1 ); |
|
441
|
|
|
|
|
|
|
|
|
442
|
8
|
|
|
|
|
18
|
my $until = length( $newconfig ); |
|
443
|
8
|
|
100
|
|
|
327
|
$until-- while $until > $addr and |
|
444
|
|
|
|
|
|
|
substr( $newconfig, $until-1, 1 ) eq substr( $oldconfig, $until-1, 1 ); |
|
445
|
|
|
|
|
|
|
|
|
446
|
8
|
100
|
|
|
|
26
|
if( my $len = $until - $addr ) { |
|
447
|
7
|
|
|
|
|
34
|
await $self->_write_CONFIG( $addr, substr( $newconfig, $addr, $len ) ); |
|
448
|
7
|
|
|
|
|
10150
|
$_config = $newconfig; |
|
449
|
|
|
|
|
|
|
} |
|
450
|
|
|
|
|
|
|
|
|
451
|
8
|
100
|
66
|
|
|
40
|
if( defined $newpatable and $newpatable ne $_patable ) { |
|
452
|
2
|
|
|
|
|
8
|
await $self->_write_PATABLE( $newpatable ); |
|
453
|
2
|
|
|
|
|
2771
|
$_patable = $newpatable; |
|
454
|
|
|
|
|
|
|
} |
|
455
|
|
|
|
|
|
|
|
|
456
|
8
|
|
33
|
|
|
170
|
defined $config{$_} and $_cached_config{$_} = $config{$_} for @CACHED_CONFIG; |
|
457
|
8
|
|
|
8
|
1
|
25127
|
} |
|
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
|
|
|
|
|
22
|
async method read_marcstate () |
|
|
11
|
|
|
|
|
13
|
|
|
474
|
11
|
|
|
|
|
40
|
{ |
|
475
|
11
|
|
|
|
|
32
|
my $marcstate = await $self->read_register( REG_MARCSTATE ); |
|
476
|
11
|
|
33
|
|
|
15605
|
return $MARCSTATE[$marcstate] // $marcstate; |
|
477
|
11
|
|
|
11
|
1
|
4380
|
} |
|
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
|
2933
|
method read_chipstatus_rx () { $self->_read_chipstatus( REG_READ ) } |
|
|
1
|
|
|
|
|
34
|
|
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
4
|
|
|
496
|
3
|
|
|
3
|
1
|
694
|
method read_chipstatus_tx () { $self->_read_chipstatus( REG_WRITE ) } |
|
|
3
|
|
|
|
|
5
|
|
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
10
|
|
|
497
|
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
my @STATES = qw( IDLE RX TX FSTXON CALIBRATE SETTLINE RXFIFO_OVERFLOW TXFIFO_UNDERFLOW ); |
|
499
|
|
|
|
|
|
|
|
|
500
|
4
|
|
|
|
|
7
|
async method _read_chipstatus ( $rw ) |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
6
|
|
|
501
|
4
|
|
|
|
|
10
|
{ |
|
502
|
4
|
|
|
|
|
15
|
my $status = unpack "C", await $self->protocol->readwrite( |
|
503
|
|
|
|
|
|
|
pack "C", $rw | CMD_SNOP |
|
504
|
|
|
|
|
|
|
); |
|
505
|
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
return { |
|
507
|
4
|
|
|
|
|
5157
|
STATE => $STATES[ ( $status & 0x70 ) >> 4 ], |
|
508
|
|
|
|
|
|
|
FIFO_BYTES_AVAILABLE => ( $status & 0x0F ), |
|
509
|
|
|
|
|
|
|
}; |
|
510
|
4
|
|
|
4
|
|
7
|
} |
|
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
|
|
|
|
|
2
|
|
|
524
|
1
|
|
|
|
|
5
|
{ |
|
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
|
|
|
|
|
1274
|
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
|
3305
|
} |
|
539
|
|
|
|
|
|
|
|
|
540
|
11
|
|
|
|
|
17
|
async method command ( $cmd ) |
|
|
11
|
|
|
|
|
17
|
|
|
|
11
|
|
|
|
|
15
|
|
|
541
|
11
|
|
|
|
|
28
|
{ |
|
542
|
11
|
50
|
33
|
|
|
53
|
$cmd >= 0x30 and $cmd <= 0x3D or |
|
543
|
|
|
|
|
|
|
croak "Invalid command byte"; |
|
544
|
|
|
|
|
|
|
|
|
545
|
11
|
|
|
|
|
64
|
await $self->protocol->write( pack( "C", $cmd ) ); |
|
546
|
11
|
|
|
11
|
0
|
23
|
} |
|
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
|
|
|
|
|
1
|
|
|
557
|
1
|
|
|
|
|
3
|
{ |
|
558
|
1
|
|
|
|
|
5
|
await $self->command( CMD_SRES ); |
|
559
|
1
|
|
|
1
|
1
|
300
|
} |
|
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
|
|
|
|
|
2
|
|
|
570
|
1
|
|
|
|
|
3
|
{ |
|
571
|
1
|
|
|
|
|
3
|
await $self->command( CMD_SFRX ); |
|
572
|
1
|
|
|
|
|
1376
|
await $self->command( CMD_SFTX ); |
|
573
|
1
|
|
|
1
|
1
|
11998
|
} |
|
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
|
|
|
|
|
3
|
async method start_rx () |
|
|
1
|
|
|
|
|
1
|
|
|
584
|
1
|
|
|
|
|
3
|
{ |
|
585
|
1
|
|
|
|
|
3
|
await $self->command( CMD_SIDLE ); |
|
586
|
1
|
|
|
|
|
1388
|
1 until ( await $self->read_marcstate ) eq "IDLE"; |
|
587
|
|
|
|
|
|
|
|
|
588
|
1
|
|
|
|
|
75
|
await $self->command( CMD_SRX ); |
|
589
|
1
|
|
|
|
|
1361
|
1 until ( await $self->read_marcstate ) eq "RX"; |
|
590
|
1
|
|
|
1
|
1
|
4328
|
} |
|
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
|
|
|
|
|
7
|
async method start_tx () |
|
|
3
|
|
|
|
|
3
|
|
|
601
|
3
|
|
|
|
|
9
|
{ |
|
602
|
3
|
|
|
|
|
11
|
await $self->command( CMD_SIDLE ); |
|
603
|
3
|
|
|
|
|
4298
|
1 until ( await $self->read_marcstate ) eq "IDLE"; |
|
604
|
|
|
|
|
|
|
|
|
605
|
3
|
|
|
|
|
222
|
await $self->command( CMD_STX ); |
|
606
|
3
|
|
|
|
|
4106
|
1 until ( await $self->read_marcstate ) eq "TX"; |
|
607
|
3
|
|
|
3
|
1
|
4206
|
} |
|
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
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
7
|
|
|
631
|
4
|
|
|
|
|
12
|
{ |
|
632
|
4
|
50
|
|
|
|
15
|
await( $self->read_register( REG_RXBYTES ) ) >= $len or |
|
633
|
|
|
|
|
|
|
croak "RX UNDERFLOW - not enough bytes available"; |
|
634
|
|
|
|
|
|
|
|
|
635
|
4
|
|
|
|
|
5711
|
return await $self->protocol->write_then_read( |
|
636
|
|
|
|
|
|
|
pack( "C", REG_READ | REG_BURST | REG_RXFIFO ), $len |
|
637
|
|
|
|
|
|
|
); |
|
638
|
4
|
|
|
4
|
1
|
4109
|
} |
|
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
|
|
|
|
|
6
|
async method write_txfifo ( $bytes ) |
|
|
3
|
|
|
|
|
5
|
|
|
|
3
|
|
|
|
|
6
|
|
|
649
|
3
|
|
|
|
|
11
|
{ |
|
650
|
3
|
|
|
|
|
13
|
await $self->protocol->write( |
|
651
|
|
|
|
|
|
|
pack "C a*", REG_WRITE | REG_BURST | REG_TXFIFO, $bytes |
|
652
|
|
|
|
|
|
|
); |
|
653
|
3
|
|
|
3
|
1
|
4683
|
} |
|
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
|
|
|
|
|
4
|
async method receive () |
|
|
2
|
|
|
|
|
3
|
|
|
683
|
2
|
|
|
|
|
7
|
{ |
|
684
|
|
|
|
|
|
|
# TODO: Check for RX UNDERFLOW somehow? |
|
685
|
|
|
|
|
|
|
|
|
686
|
2
|
|
|
|
|
7
|
my $fixedlen = $_cached_config{LENGTH_CONFIG} eq "fixed"; |
|
687
|
2
|
|
|
|
|
5
|
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
|
|
|
|
1315
|
$len += 2 if $append_status; |
|
694
|
|
|
|
|
|
|
|
|
695
|
2
|
|
|
|
|
8
|
my $bytes = await $self->read_rxfifo( $len ); |
|
696
|
2
|
|
|
|
|
2710
|
my %ret; |
|
697
|
|
|
|
|
|
|
|
|
698
|
2
|
50
|
|
|
|
10
|
if( $append_status ) { |
|
699
|
2
|
|
|
|
|
13
|
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
|
|
|
|
|
6
|
$ret{LQI} = $lqi & 0x7F; |
|
706
|
2
|
|
|
|
|
7
|
$ret{CRC_OK} = !!( $lqi & 0x80 ); |
|
707
|
|
|
|
|
|
|
} |
|
708
|
|
|
|
|
|
|
|
|
709
|
2
|
|
|
|
|
5
|
$ret{data} = $bytes; |
|
710
|
|
|
|
|
|
|
|
|
711
|
2
|
|
|
|
|
8
|
return \%ret; |
|
712
|
2
|
|
|
2
|
1
|
444
|
} |
|
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
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
5
|
|
|
726
|
2
|
|
|
|
|
6
|
{ |
|
727
|
2
|
|
|
|
|
6
|
my $fixedlen = $_cached_config{LENGTH_CONFIG} eq "fixed"; |
|
728
|
|
|
|
|
|
|
|
|
729
|
2
|
|
|
|
|
4
|
my $pktlen = length $bytes; |
|
730
|
2
|
100
|
|
|
|
8
|
if( $fixedlen ) { |
|
731
|
|
|
|
|
|
|
$pktlen == $_cached_config{PACKET_LENGTH} or |
|
732
|
1
|
50
|
|
|
|
4
|
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
|
|
|
|
|
12
|
await $self->start_tx; |
|
741
|
|
|
|
|
|
|
|
|
742
|
2
|
100
|
|
|
|
298
|
$bytes = pack "C a*", $pktlen, $bytes if !$fixedlen; |
|
743
|
|
|
|
|
|
|
|
|
744
|
2
|
|
|
|
|
9
|
await $self->write_txfifo( $bytes ); |
|
745
|
|
|
|
|
|
|
|
|
746
|
2
|
|
|
|
|
2801
|
my $timeout = 20; # TODO: configuration |
|
747
|
2
|
|
|
|
|
7
|
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
|
633
|
} |
|
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__ |