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