#########################################################################################
# Package HiPi::Interface::HopeRF69
# Description : Control Hope RF69 Transceivers
# Copyright : Copyright (c) 2013-2017 Mark Dootson
# License : This is free software; you can redistribute it and/or modify it under
# the same terms as the Perl 5 programming language system itself.
#########################################################################################
package HiPi::Interface::HopeRF69;
#########################################################################################
use strict;
use warnings;
use parent qw( HiPi::Interface );
use Carp;
use HiPi qw( :rpi :spi :hrf69 );
use Time::HiRes qw( usleep );
__PACKAGE__->create_accessors( qw( devicename reset_gpio update_default_on_reset
fsk_config ook_config ook_repeat) );
our $VERSION ='0.68';
# Hope recommended updated reset defaults
my $reset_defaults = [
[ RF69_REG_LNA, 0x88 ],
[ RF69_REG_RXBW, 0x55 ],
[ RF69_REG_AFCBW, 0x8B ],
[ RF69_REG_DIOMAPPING2, 0x07 ],
[ RF69_REG_RSSITHRESH, 0xE4 ],
[ RF69_REG_SYNCVALUE1, 0x01 ],
[ RF69_REG_FIFOTHRESH, 0x8F ],
[ RF69_REG_TESTDAGC, 0x30 ],
];
sub new {
my( $class, %userparams ) = @_;
my %params = (
devicename => '/dev/spidev0.1',
speed => 9600000, # 9.6 mhz
bitsperword => 8,
delay => 0,
device => undef,
reset_gpio => undef,
update_default_on_reset => 1,
ook_repeat => 15,
fsk_config => [
[ RF69_REG_REGDATAMODUL, 0x00 ], # modulation scheme FSK
[ RF69_REG_FDEVMSB, 0x01 ], # frequency deviation 5kHz 0x0052 -> 30kHz 0x01EC
[ RF69_REG_FDEVLSB, 0xEC ], # frequency deviation 5kHz 0x0052 -> 30kHz 0x01EC
[ RF69_REG_FRMSB, 0x6C ], # carrier freq -> 434.3MHz 0x6C9333
[ RF69_REG_FRMID, 0x93 ], # carrier freq -> 434.3MHz 0x6C9333
[ RF69_REG_FRLSB, 0x33 ], # carrier freq -> 434.3MHz 0x6C9333
[ RF69_REG_AFCCTRL, 0x00 ], # standard AFC routine
#[ RF69_REG_PREAMBLEMSB, 0x00 ], # 3 byte preamble
#[ RF69_REG_PREAMBLELSB, 0x03 ], # 3 byte preamble
[ RF69_REG_LNA, 0x08 ], # 200ohms, gain by AGC loop -> 50ohms
[ RF69_REG_RXBW, 0x43 ], # channel filter bandwidth 10kHz -> 60kHz page:26
[ RF69_REG_BITRATEMSB, 0x1A ], # 4800b/s
[ RF69_REG_BITRATELSB, 0x0B ], # 4800b/s
[ RF69_REG_SYNCCONFIG, 0x88 ], # Size of the Synch word = 2 (SyncSize + 1)
[ RF69_REG_SYNCVALUE1, 0x2D ], # 1st byte of Sync word
[ RF69_REG_SYNCVALUE2, 0xD4 ], # 2nd byte of Sync word
[ RF69_REG_PACKETCONFIG1, 0xA0 ], # Variable length, Manchester coding
[ RF69_REG_PAYLOADLEN, 66 ], # max Length in RX, not used in Tx
[ RF69_REG_NODEADDRESS, 0x06 ], # Node address used in address filtering ( not used in this config )
[ RF69_REG_FIFOTHRESH, 0x81 ], # Condition to start packet transmission: at least one byte in FIFO
[ RF69_REG_OPMODE, RF69_MASK_OPMODE_RX ], # Operating mode to Receive
],
ook_config => [
[ RF69_REG_REGDATAMODUL, 0x08 ], # modulation scheme OOK
[ RF69_REG_FDEVMSB, 0 ], # frequency deviation -> 0kHz
[ RF69_REG_FDEVLSB, 0 ], # frequency deviation -> 0kHz
[ RF69_REG_FRMSB, 0x6C ], # carrier freq -> 433.92MHz 0x6C7AE1
[ RF69_REG_FRMID, 0x7A ], # carrier freq -> 433.92MHz 0x6C7AE1
[ RF69_REG_FRLSB, 0xE1 ], # carrier freq -> 433.92MHz 0x6C7AE1
[ RF69_REG_RXBW, 0x41 ], # channel filter bandwidth 120kHz
[ RF69_REG_BITRATEMSB, 0x40 ], # 1938b/s
[ RF69_REG_BITRATELSB, 0x80 ], # 1938b/s
[ RF69_REG_PREAMBLEMSB, 0 ], # no preamble
[ RF69_REG_PREAMBLELSB, 0 ], # no preamble
[ RF69_REG_SYNCCONFIG, 0x98 ], # Size of the Synch word = 4 (SyncSize + 1)
[ RF69_REG_SYNCVALUE1, 0x80 ], # sync value 1
[ RF69_REG_SYNCVALUE2, 0 ], # sync value 2
[ RF69_REG_SYNCVALUE3, 0 ], # sync value 3
[ RF69_REG_SYNCVALUE4, 0 ], # sync value 4
[ RF69_REG_PACKETCONFIG1, 0 ], # Fixed length, no Manchester coding, OOK
[ RF69_REG_PAYLOADLEN, 13 + 8 * 17 ], # Fixed OOK Payload Length
[ RF69_REG_FIFOTHRESH, 0x1E ], # Condition to start packet transmission: wait for 30 bytes in FIFO
[ RF69_REG_OPMODE, RF69_MASK_OPMODE_TX ], # Transmitter mode
],
);
foreach my $key (sort keys(%userparams)) {
$params{$key} = $userparams{$key};
}
unless( defined($params{device}) ) {
require HiPi::Device::SPI;
$params{device} = HiPi::Device::SPI->new(
speed => $params{speed},
bitsperword => $params{bitsperword},
delay => $params{delay},
devicename => $params{devicename},
);
}
my $self = $class->SUPER::new(%params);
# setup defaults
$self->reset();
$self->configure($self->fsk_config);
return $self;
}
sub configure {
my( $self, $config ) = @_;
for my $msgref ( @$config ) {
$self->write_register(@$msgref);
}
$self->wait_for(RF69_REG_IRQFLAGS1, RF69_MASK_MODEREADY, RF69_TRUE);
}
sub change_mode {
my($self, $mode, $waitmask) = @_;
$waitmask //= RF69_MASK_MODEREADY;
$self->write_register(RF69_REG_OPMODE, $mode);
$self->wait_for(RF69_REG_IRQFLAGS1, $waitmask, RF69_TRUE);
}
sub set_mode_receiver {
my $self = shift;
$self->change_mode(RF69_MASK_OPMODE_RX, RF69_MASK_MODEREADY );
}
sub set_mode_transmitter {
my $self = shift;
$self->change_mode(RF69_MASK_OPMODE_TX, RF69_MASK_MODEREADY | RF69_MASK_TXREADY );
}
sub write_register {
my( $self, @data ) = @_;
# address is first byte
$data[0] |= RF69_MASK_REG_WRITE;
$self->device->transfer_byte_array( @data );
}
sub read_register {
my( $self, $addr, $numbytes ) = @_;
$numbytes ||= 1;
my @data = ( 0 ) x ( $numbytes + 1 );
$data[0] = $addr;
my ($retaddr, @rvals ) = $self->device->transfer_byte_array( @data );
return ( wantarray ) ? @rvals : $rvals[0];
}
sub write_fifo { shift->write_register( 0x0, @_ ); }
sub read_fifo {
my $self = shift;
my( $rval ) = $self->read_register( 0x0, 1 );
return $rval;
}
sub clear_fifo {
my $self = shift;
my $state = $self->read_register( RF69_REG_IRQFLAGS2 );
while ($state & RF69_MASK_FIFONOTEMPTY) {
my $discard = $self->read_fifo;
$state = $self->read_register(RF69_REG_IRQFLAGS2);
}
return;
}
sub reset {
my $self = shift;
my $pin = $self->reset_gpio;
return unless defined($pin);
require HiPi::GPIO;
my $gpio = HiPi::GPIO->new;
$gpio->set_pin_mode( $pin, RPI_MODE_OUTPUT ) if( $gpio->get_pin_mode($pin) != RPI_MODE_OUTPUT );
$gpio->pin_write($pin, RPI_HIGH);
usleep( 100000 ); # 0.1 secs
$gpio->pin_write($pin, RPI_LOW);
if ($self->update_default_on_reset) {
$self->configure($reset_defaults);
}
return;
}
sub wait_for {
my( $self, $addr, $mask, $true) = @_;
my $counter = 0;
my $maxcount = 4000000;
while ( $counter < $maxcount ) {
my $ret = $self->read_register( $addr );
last if( ( $ret & $mask ) == ( $true ? $mask : 0 ) );
$counter ++;
}
if ( $counter >= $maxcount ) {
croak qq(timeout inside wait loop with addr $addr);
}
return;
}
sub assert_register_value {
my($self, $addr, $mask, $true, $desc) = @_;
my $val = $self->read_register( $addr );
if ($true){
if (($val & $mask) != $mask) {
croak sprintf("ASSERTION FAILED: addr:%02x, expVal:%02x(mask:%02x) != val:%02x, desc: %s", $addr, $true, $mask, $val, $desc);
}
} else {
if (($val & $mask) != 0) {
croak sprintf("ASSERTION FAILED: addr:%02x, expVal:%02x(mask:%02x) != val:%02x, desc: %s", $addr, $true, $mask, $val, $desc);
}
}
return;
}
sub send_message {
my($self, $bytes) = @_;
return unless(scalar( @$bytes ));
$self->set_mode_transmitter;
# write to fifo
$self->write_fifo( @$bytes );
# wait for Packet sent
$self->wait_for (RF69_REG_IRQFLAGS2, RF69_MASK_PACKETSENT, RF69_TRUE);
# assert that all bytes sent
$self->assert_register_value(RF69_REG_IRQFLAGS2, RF69_MASK_FIFONOTEMPTY | RF69_MASK_FIFOOVERRUN, RF69_FALSE, q(are all bytes sent?));
# set back to receive mode
$self->set_mode_receiver;
return;
}
sub send_ook_message {
my($self, $bytes, $repeat ) = @_;
return unless scalar @$bytes;
$repeat ||= $self->ook_repeat;
$repeat = 100 if $repeat > 100;
$repeat = 8 if $repeat < 8;
# switch to OOK mode
$self->configure($self->ook_config);
# wait for mode ready for transmit after config
$self->wait_for(RF69_REG_IRQFLAGS1, RF69_MASK_MODEREADY | RF69_MASK_TXREADY, RF69_TRUE);
# send first without preamble
$self->write_fifo( @$bytes[4..15] );
# repeated resend with sync bytes
for (my $i = 0; $i < $repeat; $i++) {
# wait while bytes in FIFO exceed FifoThreshold,
$self->wait_for(RF69_REG_IRQFLAGS2, RF69_MASK_FIFOLEVEL, RF69_FALSE);
$self->write_fifo( @$bytes );
}
# wait for Packet sent
$self->wait_for (RF69_REG_IRQFLAGS2, RF69_MASK_PACKETSENT, RF69_TRUE);
# assert that FIFO is empty and there were no overruns
$self->assert_register_value(RF69_REG_IRQFLAGS2, RF69_MASK_FIFONOTEMPTY | RF69_MASK_FIFOOVERRUN, RF69_FALSE, q(are all bytes sent?));
# return to default mode
$self->configure($self->fsk_config);
return;
}
sub receive_message {
my ( $self ) = @_;
my $fifostate = $self->read_register( RF69_REG_IRQFLAGS2 );
if ( ( $fifostate & RF69_MASK_PAYLOADRDY ) == RF69_MASK_PAYLOADRDY ) {
my @databuffer = ();
while ( $fifostate & RF69_MASK_FIFONOTEMPTY ) {
push @databuffer, $self->read_fifo;
$fifostate = $self->read_register(RF69_REG_IRQFLAGS2);
}
return \@databuffer;
}
return undef;
}
1;
__END__