2023-02-16 07:09:23 -05:00
/*
* Copyright ( C ) 2014 Jared Boone , ShareBrained Technology , Inc .
* Copyright ( C ) 2023 Great Scott Gadgets
*
* This file is part of PortaPack .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 , or ( at your option )
* any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; see the file COPYING . If not , write to
* the Free Software Foundation , Inc . , 51 Franklin Street ,
* Boston , MA 02110 - 1301 , USA .
*/
# include "max2839.hpp"
# include "hackrf_hal.hpp"
# include "hackrf_gpio.hpp"
using namespace hackrf : : one ;
# include "ch.h"
# include "hal.h"
# include <algorithm>
namespace max2839 {
namespace lna {
using namespace max283x : : lna ;
2023-05-18 16:16:05 -04:00
constexpr std : : array < uint8_t , 8 > lookup_8db_steps {
0b11 , 0b11 , 0b10 , 0b10 ,
0b01 , 0b00 , 0b00 , 0b00 } ;
2023-02-16 07:09:23 -05:00
static uint_fast8_t gain_ordinal ( const int8_t db ) {
2023-05-18 16:16:05 -04:00
const auto db_sat = gain_db_range . clip ( db ) ;
return lna : : lookup_8db_steps [ ( db_sat > > 3 ) & 7 ] ;
2023-02-16 07:09:23 -05:00
}
} /* namespace lna */
namespace vga {
using namespace max283x : : vga ;
2023-05-18 16:16:05 -04:00
constexpr range_t < int8_t > gain_db_range_internal { 0 , 63 } ;
2023-02-16 07:09:23 -05:00
static uint_fast8_t gain_ordinal ( const int8_t db ) {
2023-05-18 16:16:05 -04:00
const auto db_sat = gain_db_range_internal . clip ( db ) ;
return ( db_sat & 0b111111 ) ^ 0b111111 ;
2023-02-16 07:09:23 -05:00
}
} /* namespace vga */
namespace tx {
using namespace max283x : : tx ;
static uint_fast8_t gain_ordinal ( const int8_t db ) {
2023-05-18 16:16:05 -04:00
const auto db_sat = gain_db_range . clip ( db ) ;
return 47 - db_sat ;
2023-02-16 07:09:23 -05:00
}
} /* namespace tx */
namespace filter {
using namespace max283x : : filter ;
static uint_fast8_t bandwidth_ordinal ( const uint32_t bandwidth ) {
2023-05-18 16:16:05 -04:00
/* Determine filter setting that will provide bandwidth greater than or
* equal to requested bandwidth .
*/
return std : : lower_bound ( bandwidths . cbegin ( ) , bandwidths . cend ( ) , bandwidth ) - bandwidths . cbegin ( ) ;
2023-02-16 07:09:23 -05:00
}
} /* namespace filter */
/* Empirical testing indicates about 25us is necessary to get a valid
* temperature sense conversion from the ADC .
*/
constexpr float seconds_for_temperature_sense_adc_conversion = 30.0e-6 ;
constexpr halrtcnt_t ticks_for_temperature_sense_adc_conversion = ( base_m4_clk_f * seconds_for_temperature_sense_adc_conversion + 1 ) ;
constexpr uint32_t reference_frequency = max283x_reference_f ;
constexpr uint32_t pll_factor = 1.0 / ( 4.0 / 3.0 / reference_frequency ) + 0.5 ;
static int_fast8_t requested_rx_lna_gain = 0 ;
static int_fast8_t requested_rx_vga_gain = 0 ;
void MAX2839 : : init ( ) {
2023-05-18 16:16:05 -04:00
set_mode ( Mode : : Shutdown ) ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
gpio_max283x_enable . output ( ) ;
gpio_max2839_rxtx . output ( ) ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . rxrf_1 . MIMOmode = 1 ; /* enable RXINB */
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . pa_drv . TXVGA_GAIN_SPI_EN = 1 ;
_map . r . tx_gain . TXVGA_GAIN_SPI = 0x00 ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . hpfsm_3 . HPC_STOP = 1 ; /* 1kHz */
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . rxrf_2 . LNAgain_SPI_EN = 1 ; /* control LNA gain from SPI */
_map . r . lpf_vga_1 . L = 0b000 ;
_map . r . lpf_vga_2 . L = 0b000 ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . rx_top_1 . VGAgain_SPI_EN = 1 ; /* control VGA gain from SPI */
_map . r . lpf_vga_1 . VGA = 0b000000 ;
_map . r . lpf_vga_2 . VGA = 0b010101 ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . lpf_vga_2 . BUFF_VCM = 0b11 ; /* maximum RX output common-mode voltage */
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . lpf_vga_1 . ModeCtrl = 0b01 ; /* Rx LPF */
_map . r . lpf . FT = 0b0000 ; /* 1.75 MHz LPF */
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . spi_en . EN_SPI = 1 ; /* enable chip functions when ENABLE pin set */
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . lo_gen . LOGEN_2GM = 0 ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . rssi_vga . RSSI_MODE = 1 ; /* RSSI independent of RXHP */
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
/*
* There are two LNA band settings , but we only use one of them .
* Switching to the other one doesn ' t make the overall spectrum any
* flatter but adds a surprise step in the middle .
*/
_map . r . rxrf_1 . LNAband = 0 ; /* 2.3 - 2.5GHz */
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_dirty . set ( ) ;
flush ( ) ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
set_mode ( Mode : : Standby ) ;
2023-02-16 07:09:23 -05:00
}
2024-02-03 18:57:45 -05:00
void MAX2839 : : set_tx_LO_iq_phase_calibration ( const size_t v ) {
/* IQ phase deg CAL adj (+4 ...-4) This IC in 64 steps (6 bits), 000000 = +4deg (Q lags I by 94degs, default), 011111 = +0deg, 111111 = -4deg (Q lags I by 86degs) */
// TX calibration , 2 x Logic pins , ENABLE, RXENABLE = 1,0, (2dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
set_mode ( Mode : : Tx_Calibration ) ; // write to ram 3 LOGIC Pins .
gpio_max283x_enable . output ( ) ; // max2839 has only 2 x pins + regs to decide mode.
gpio_max2839_rxtx . output ( ) ; // Here is combined rx & tx pin in one port.
_map . r . spi_en . CAL_SPI = 1 ; // Register Settings reg address 16, D1 (CAL mode 1)
_map . r . spi_en . EN_SPI = 1 ; // Register Settings reg address 16, DO (CHIP ENABLE 1)
flush_one ( Register : : SPI_EN ) ;
_map . r . pa_drv . TXLO_IQ_SPI_EN = 1 ; // reg 27 D6, TX LO I/Q Phase SPI Adjust. Active when Address 27 D6 (TXLO_IQ_SPI_EN) = 1.
_map . r . pa_drv . TXLO_IQ_SPI = v ; // reg 27 D5:D0 6 bits, TX LO I/Q Phase SPI Adjust.
flush_one ( Register : : PA_DRV ) ;
// Exit Calibration mode, Go back to reg 16, D1:D0 , Out of CALIBRATION , back to default conditions, but keep CS activated.
_map . r . spi_en . CAL_SPI = 0 ; // Register Settings reg address 16, D1 (0 = Normal operation (default)
_map . r . spi_en . EN_SPI = 1 ; // Register Settings reg address 16, DO (1 = Chip select enable )
flush_one ( Register : : SPI_EN ) ;
set_mode ( Mode : : Standby ) ; // Back 3 logic pins CALIBRATION mode -> Standby.
}
2023-02-16 07:09:23 -05:00
enum class Mask {
2023-05-18 16:16:05 -04:00
Enable = 0b01 ,
RxTx = 0b10 ,
Shutdown = 0b00 ,
Standby = RxTx ,
Receive = Enable | RxTx ,
Transmit = Enable ,
2024-02-03 18:57:45 -05:00
Rx_calibration = Enable | RxTx , // sets the same 2 x logic pins to the Receive operating mode.
Tx_calibration = Enable , // sets the same 2 x logic pins to the Transmit operating mode.
2023-02-16 07:09:23 -05:00
} ;
Mask mode_mask ( const Mode mode ) {
2023-05-18 16:16:05 -04:00
switch ( mode ) {
case Mode : : Standby :
return Mask : : Standby ;
case Mode : : Receive :
return Mask : : Receive ;
case Mode : : Transmit :
return Mask : : Transmit ;
2024-02-03 18:57:45 -05:00
case Mode : : Rx_Calibration : // Let's add this not used previously Rx calibration mode.
return Mask : : Rx_calibration ; // same logic pins as Receive mode = Enable | RxTx, ,(the difference is in Regs )
case Mode : : Tx_Calibration : // Let's add this not used previously Tx calibration mode.
return Mask : : Tx_calibration ; // same logic pins as Transmit = Enable , (the difference is in Reg add 16 D1:DO)
2023-05-18 16:16:05 -04:00
default :
return Mask : : Shutdown ;
}
2023-02-16 07:09:23 -05:00
}
void MAX2839 : : set_mode ( const Mode mode ) {
2023-05-18 16:16:05 -04:00
Mask mask = mode_mask ( mode ) ;
gpio_max283x_enable . write ( toUType ( mask ) & toUType ( Mask : : Enable ) ) ;
gpio_max2839_rxtx . write ( toUType ( mask ) & toUType ( Mask : : RxTx ) ) ;
2023-02-16 07:09:23 -05:00
}
void MAX2839 : : flush ( ) {
2023-05-18 16:16:05 -04:00
if ( _dirty ) {
for ( size_t n = 0 ; n < reg_count ; n + + ) {
if ( _dirty [ n ] ) {
write ( n , _map . w [ n ] ) ;
}
}
_dirty . clear ( ) ;
}
2023-02-16 07:09:23 -05:00
}
void MAX2839 : : flush_one ( const Register reg ) {
2023-05-18 16:16:05 -04:00
const auto reg_num = toUType ( reg ) ;
write ( reg_num , _map . w [ reg_num ] ) ;
_dirty . clear ( reg_num ) ;
2023-02-16 07:09:23 -05:00
}
void MAX2839 : : write ( const address_t reg_num , const reg_t value ) {
2023-05-18 16:16:05 -04:00
uint16_t t = ( 0U < < 15 ) | ( reg_num < < 10 ) | ( value & 0x3ffU ) ;
_target . transfer ( & t , 1 ) ;
2023-02-16 07:09:23 -05:00
}
reg_t MAX2839 : : read ( const address_t reg_num ) {
2023-05-18 16:16:05 -04:00
uint16_t t = ( 1U < < 15 ) | ( reg_num < < 10 ) ;
_target . transfer ( & t , 1U ) ;
return t & 0x3ffU ;
2023-02-16 07:09:23 -05:00
}
void MAX2839 : : write ( const Register reg , const reg_t value ) {
2023-05-18 16:16:05 -04:00
write ( toUType ( reg ) , value ) ;
2023-02-16 07:09:23 -05:00
}
reg_t MAX2839 : : read ( const Register reg ) {
2023-05-18 16:16:05 -04:00
return read ( toUType ( reg ) ) ;
2023-02-16 07:09:23 -05:00
}
void MAX2839 : : set_tx_vga_gain ( const int_fast8_t db ) {
2023-05-18 16:16:05 -04:00
_map . r . tx_gain . TXVGA_GAIN_SPI = tx : : gain_ordinal ( db ) ;
_dirty [ Register : : TX_GAIN ] = 1 ;
flush ( ) ;
2023-02-16 07:09:23 -05:00
}
/*
* MAX2839 gain rain ranges differ slightly from MAX2837 ' s but are close
* enough that it makes sense to emulate MAX2837 gain ranges for a consistent
* user experience .
*/
void MAX2839 : : configure_rx_gain ( ) {
2023-05-18 16:16:05 -04:00
/* Apply MAX2837 restrictions to requested gain settings. */
int_fast8_t lna_gain = lna : : gain_db_range . clip ( requested_rx_lna_gain ) ;
lna_gain & = 0x38 ;
int_fast8_t vga_gain = vga : : gain_db_range . clip ( requested_rx_vga_gain ) ;
vga_gain & = 0x3e ;
/*
* MAX2839 has lower full - scale RX output voltage than MAX2837 , so we
* adjust the VGA ( baseband ) gain to compensate .
*/
vga_gain + = 3 ;
/*
* If that adjustment puts VGA gain out of range , use LNA gain to
* compensate . MAX2839 VGA gain can be any number from 0 through 63.
*/
if ( vga_gain > 63 ) {
if ( lna_gain < = 32 ) {
vga_gain - = 8 ;
lna_gain + = 8 ;
} else {
vga_gain = 63 ;
}
}
/*
* MAX2839 lacks max - 24 dB ( 16 dB ) and max - 40 dB ( 0 dB ) LNA gain
* settings , so we use VGA gain to compensate .
*/
if ( lna_gain = = 0 ) {
lna_gain = 8 ;
vga_gain = ( vga_gain > = 8 ) ? vga_gain - 8 : 0 ;
}
if ( lna_gain = = 16 ) {
if ( vga_gain > 32 ) {
vga_gain - = 8 ;
lna_gain + = 8 ;
} else {
vga_gain + = 8 ;
lna_gain - = 8 ;
}
}
_map . r . lpf_vga_2 . L = lna : : gain_ordinal ( lna_gain ) ;
_dirty [ Register : : RXRF_2 ] = 1 ;
_map . r . lpf_vga_2 . VGA = vga : : gain_ordinal ( vga_gain ) ;
_dirty [ Register : : LPF_VGA_2 ] = 1 ;
flush ( ) ;
2023-02-16 07:09:23 -05:00
}
void MAX2839 : : set_lna_gain ( const int_fast8_t db ) {
2023-05-18 16:16:05 -04:00
requested_rx_lna_gain = db ;
configure_rx_gain ( ) ;
2023-02-16 07:09:23 -05:00
}
void MAX2839 : : set_vga_gain ( const int_fast8_t db ) {
2023-05-18 16:16:05 -04:00
requested_rx_vga_gain = db ;
configure_rx_gain ( ) ;
2023-02-16 07:09:23 -05:00
}
2023-10-08 11:50:31 -04:00
void MAX2839 : : set_lpf_rf_bandwidth_rx ( const uint32_t bandwidth_minimum ) {
_map . r . lpf_vga_1 . ModeCtrl = 0b01 ; /* Address reg 5, D9-D8, Set mode lowpass filter block to Rx LPF . Active when Address 8 D<2> = 1 */
flush_one ( Register : : LPF_VGA_1 ) ;
_map . r . rx_top_1 . LPF_MODE_SEL = 1 ; /* Address 8 reg, D2 bit:LPF mode mux, LPF_MODE_SEL 0 = Normal operation, 1 = Operating mode is programmed Address 5 D<9:8> */
flush_one ( Register : : RX_TOP_1 ) ;
2023-05-18 16:16:05 -04:00
_map . r . lpf . FT = filter : : bandwidth_ordinal ( bandwidth_minimum ) ;
2023-10-08 11:50:31 -04:00
flush_one ( Register : : LPF ) ;
_map . r . rx_top_1 . LPF_MODE_SEL = 0 ; /* Address 8 reg, D2 bit:LPF mode mux, LPF_MODE_SEL 0 = Normal operation, 1 = Operating mode is programmed Address 5 D<9:8> */
flush_one ( Register : : RX_TOP_1 ) ; /* Leave LPF_MODE_SEL 0 = Normal operation */
}
void MAX2839 : : set_lpf_rf_bandwidth_tx ( const uint32_t bandwidth_minimum ) {
_map . r . lpf_vga_1 . ModeCtrl = 0b10 ; /* Address reg 5, D9-D8, Set mode lowpass filter block to Tx LPF . Active when Address 8 D<2> = 1 */
flush_one ( Register : : LPF_VGA_1 ) ;
_map . r . rx_top_1 . LPF_MODE_SEL = 1 ; /* Address 8 reg, D2 bit:LPF mode mux, LPF_MODE_SEL 0 = Normal operation, 1 = Operating mode is programmed Address 5 D<9:8> */
flush_one ( Register : : RX_TOP_1 ) ;
_map . r . lpf . FT = filter : : bandwidth_ordinal ( bandwidth_minimum ) ;
flush_one ( Register : : LPF ) ;
_map . r . rx_top_1 . LPF_MODE_SEL = 0 ; /* Address 8 reg, D2 bit:LPF mode mux, LPF_MODE_SEL 0 = Normal operation, 1 = Operating mode is programmed Address 5 D<9:8> */
flush_one ( Register : : RX_TOP_1 ) ; /* Leave LPF_MODE_SEL 0 = Normal operation */
2023-02-16 07:09:23 -05:00
}
bool MAX2839 : : set_frequency ( const rf : : Frequency lo_frequency ) {
2023-05-18 16:16:05 -04:00
/* TODO: This is a sad implementation. Refactor. */
if ( lo : : band [ 0 ] . contains ( lo_frequency ) ) {
_map . r . syn_int_div . LOGEN_BSW = 0b00 ; /* 2300 - 2399.99MHz */
} else if ( lo : : band [ 1 ] . contains ( lo_frequency ) ) {
_map . r . syn_int_div . LOGEN_BSW = 0b01 ; /* 2400 - 2499.99MHz */
} else if ( lo : : band [ 2 ] . contains ( lo_frequency ) ) {
_map . r . syn_int_div . LOGEN_BSW = 0b10 ; /* 2500 - 2599.99MHz */
} else if ( lo : : band [ 3 ] . contains ( lo_frequency ) ) {
_map . r . syn_int_div . LOGEN_BSW = 0b11 ; /* 2600 - 2700Hz */
} else {
return false ;
}
_dirty [ Register : : SYN_INT_DIV ] = 1 ;
const uint64_t div_q20 = ( lo_frequency * ( 1 < < 20 ) ) / pll_factor ;
_map . r . syn_int_div . SYN_INTDIV = div_q20 > > 20 ;
_dirty [ Register : : SYN_INT_DIV ] = 1 ;
_map . r . syn_fr_div_2 . SYN_FRDIV_19_10 = ( div_q20 > > 10 ) & 0x3ff ;
_dirty [ Register : : SYN_FR_DIV_2 ] = 1 ;
/* flush to commit high FRDIV first, as low FRDIV commits the change */
flush ( ) ;
_map . r . syn_fr_div_1 . SYN_FRDIV_9_0 = ( div_q20 & 0x3ff ) ;
_dirty [ Register : : SYN_FR_DIV_1 ] = 1 ;
flush ( ) ;
return true ;
2023-02-16 07:09:23 -05:00
}
2024-03-09 17:46:38 -05:00
/*
void MAX2839 : : set_rx_LO_iq_phase_calibration ( const size_t v ) { // Original code , rewritten below
2023-05-18 16:16:05 -04:00
_map . r . rxrf_2 . RX_IQERR_SPI_EN = 1 ;
_dirty [ Register : : RXRF_2 ] = 1 ;
_map . r . rxrf_1 . iqerr_trim = v ;
_dirty [ Register : : RXRF_1 ] = 1 ;
flush ( ) ;
2024-03-09 17:46:38 -05:00
} */
void MAX2839 : : set_rx_LO_iq_phase_calibration ( const size_t v ) {
/* RX IQ phase deg CAL adj (+4 ...-4) in 64 steps (6 bits), 000000 = +4deg (Q lags I by 94degs, default), 011111 = +0deg, 111111 = -4deg (Q lags I by 86degs) */
// RX calibration , Logic pins , ENABLE, RXENABLE, TXENABLE = 1,1,0 (3dec), and Reg address 16, D1 (CAL mode 1):DO (CHIP ENABLE 1)
set_mode ( Mode : : Rx_Calibration ) ; // write to ram 3 LOGIC Pins .
gpio_max283x_enable . output ( ) ; // max2839 has only 2 x pins + regs to decide mode.
gpio_max2839_rxtx . output ( ) ; // Here is combined rx & tx pin in one port.
_map . r . spi_en . CAL_SPI = 1 ; // Register Settings reg address 16, D1 (CAL mode 1)
_map . r . spi_en . EN_SPI = 1 ; // Register Settings reg address 16, DO (CHIP ENABLE 1)
flush_one ( Register : : SPI_EN ) ;
_map . r . rxrf_2 . RX_IQERR_SPI_EN = 1 ; // reg 2 D<2> = 1, RX LO IQ calibration SPI control. Active when Address 2 D<2> = 1.
_dirty [ Register : : RXRF_2 ] = 1 ;
_map . r . rxrf_1 . iqerr_trim = v ;
_dirty [ Register : : RXRF_1 ] = 1 ;
flush ( ) ;
_map . r . spi_en . CAL_SPI = 0 ; // Register Settings reg address 16, D1 (CAL mode 1)
_map . r . spi_en . EN_SPI = 1 ; // Register Settings reg address 16, DO (CHIP ENABLE 1)
flush_one ( Register : : SPI_EN ) ;
2023-02-16 07:09:23 -05:00
}
void MAX2839 : : set_rx_buff_vcm ( const size_t v ) {
2023-05-18 16:16:05 -04:00
_map . r . lpf_vga_2 . BUFF_VCM = v ;
_dirty [ Register : : LPF_VGA_2 ] = 1 ;
flush ( ) ;
2023-02-16 07:09:23 -05:00
}
2023-11-06 14:56:09 -05:00
int8_t MAX2839 : : temp_sense ( ) {
2023-05-18 16:16:05 -04:00
if ( ! _map . r . rx_top_2 . ts_en ) {
_map . r . rx_top_2 . ts_en = 1 ;
flush_one ( Register : : RX_TOP_2 ) ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
chThdSleepMilliseconds ( 1 ) ;
}
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . rx_top_2 . ts_adc_trigger = 1 ;
flush_one ( Register : : RX_TOP_2 ) ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
halPolledDelay ( ticks_for_temperature_sense_adc_conversion ) ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
/*
2023-11-06 14:56:09 -05:00
* Conversion to degrees C determined by testing - does not match data sheet .
2023-05-18 16:16:05 -04:00
*/
2023-11-06 14:56:09 -05:00
reg_t value = read ( Register : : TEMP_SENSE ) & 0x1F ;
2023-02-16 07:09:23 -05:00
2023-05-18 16:16:05 -04:00
_map . r . rx_top_2 . ts_adc_trigger = 0 ;
flush_one ( Register : : RX_TOP_2 ) ;
2023-02-16 07:09:23 -05:00
2023-11-18 10:50:57 -05:00
return std : : min ( 127 , ( int ) ( value * 4.31 - 40 ) ) ; // reg value is 0 to 31; possible return range is -40 C to 127 C
2023-02-16 07:09:23 -05:00
}
2023-05-18 16:16:05 -04:00
} // namespace max2839