/* * "Si4432.cpp" * * Added to the "TinySA" program in Version 1.7 by John Price (WA2FZW): * * Functions to handle the Si4432 transceivers as an objects. Some of the * functions in here apply only to the receiver and some apply only to the * transmitter. The "Init" functions remember what type of module (transmitter * or receiver) is being initialized. * * For further information on the register settings, please refer to the * "Silicon Labs Si4430/31/32 - B1" data sheet and "Silicon Labs Technical * Note A440". */ #include // Serial Peripheral Interface library #include "si4432.h" // Our header file #include /* * The "bandpassFilters" array contains a selection of the standard bandpass settings. * Values for status: enum{DO_NOT_USE, USE_IN_AUTO, AVAILABLE} * Filters with strange behaviour are marked as DO_NOT_USE * Filters with large dip in passband or not very good shape are AVAILABLE * Filters with 0.5dB or 1dB dip in passband are USE_IN_AUTO */ bandpassFilter_t Si4432::_bandpassFilters[] { // bw*10, settle, dwn3, ndec, filset, status { 26, 6800, 0, 5, 1, USE_IN_AUTO }, // 0 "AUTO" selection possibility { 28, 6600, 0, 5, 2, USE_IN_AUTO }, // 1 "AUTO" selection possibility { 31, 6600, 0, 5, 3, AVAILABLE }, // 2 If user selects 3KHz -> 3.1KHz actual { 32, 6600, 0, 5, 4, AVAILABLE }, // 3 "AUTO" selection possibility { 37, 6600, 0, 5, 5, USE_IN_AUTO }, // 4 "AUTO" selection possibility { 42, 7000, 0, 5, 6, USE_IN_AUTO }, // 5 "AUTO" selection possibility { 45, 6600, 0, 5, 7, AVAILABLE }, // 6 "AUTO" selection possibility { 49, 5000, 0, 4, 1, USE_IN_AUTO }, // 7 "AUTO" selection possibility { 54, 4200, 0, 4, 2, USE_IN_AUTO }, // 8 "AUTO" selection possibility { 59, 3700, 0, 4, 3, AVAILABLE }, // 9 "AUTO" selection possibility { 61, 3500, 0, 4, 4, AVAILABLE }, // 10 "AUTO" selection possibility { 72, 3300, 0, 4, 5, USE_IN_AUTO }, // 11 "AUTO" selection possibility { 82, 3300, 0, 4, 6, USE_IN_AUTO }, // 12 "AUTO" selection possibility { 88, 4000, 0, 4, 7, AVAILABLE }, // 13 "AUTO" selection possibility { 95, 3300, 0, 3, 1, USE_IN_AUTO }, // 14 "AUTO" selection possibility { 106, 2500, 0, 3, 2, USE_IN_AUTO }, // 15 If user selects 10KHz -> 10.6KHz actual { 115, 2500, 0, 3, 3, AVAILABLE }, // 16 "AUTO" selection possibility { 121, 2500, 0, 3, 4, AVAILABLE }, // 17 "AUTO" selection possibility { 142, 2500, 0, 3, 5, USE_IN_AUTO }, // 18 "AUTO" selection possibility { 162, 2000, 0, 3, 6, USE_IN_AUTO }, // 19 "AUTO" selection possibility { 175, 2000, 0, 3, 7, USE_IN_AUTO }, // 20 "AUTO" selection possibility { 189, 1600, 0, 2, 1, AVAILABLE }, // 21 "AUTO" selection possibility { 210, 1600, 0, 2, 2, USE_IN_AUTO }, // 22 "AUTO" selection possibility { 227, 1500, 0, 2, 3, AVAILABLE }, // 23 "AUTO" selection possibility { 240, 1400, 0, 2, 4, AVAILABLE }, // 24 "AUTO" selection possibility { 282, 1000, 0, 2, 5, USE_IN_AUTO }, // 25 "AUTO" selection possibility { 322, 1000, 0, 2, 6, USE_IN_AUTO }, // 26 If user selects 30KHz -> 32.2KHz actual { 347, 1000, 0, 2, 7, USE_IN_AUTO }, // 27 "AUTO" selection possibility { 377, 1000, 0, 1, 1, AVAILABLE }, // 28 "AUTO" selection possibility { 417, 1000, 0, 1, 2, USE_IN_AUTO }, // 29 "AUTO" selection possibility { 452, 1000, 0, 1, 3, AVAILABLE }, // 30 "AUTO" selection possibility { 479, 1000, 0, 1, 4, AVAILABLE }, // 31 "AUTO" selection possibility { 562, 700, 0, 1, 5, USE_IN_AUTO }, // 32 "AUTO" selection possibility { 641, 700, 0, 1, 6, USE_IN_AUTO }, // 33 "AUTO" selection possibility { 692, 700, 0, 1, 7, USE_IN_AUTO }, // 34 "AUTO" selection possibility { 752, 700, 0, 0, 1, AVAILABLE }, // 35 "AUTO" selection possibility { 832, 600, 0, 0, 2, USE_IN_AUTO }, // 36 "AUTO" selection possibility { 900, 600, 0, 0, 3, AVAILABLE }, // 37 "AUTO" selection possibility { 953, 600, 0, 0, 4, AVAILABLE }, // 38 "AUTO" selection possibility { 1121, 500, 0, 0, 5, USE_IN_AUTO }, // 39 If user selects 100KHz -> 112.1KHz actual { 1279, 600, 0, 0, 6, USE_IN_AUTO }, // 40 "AUTO" selection possibility { 1379, 600, 0, 0, 7, USE_IN_AUTO }, // 41 "AUTO" selection possibility { 1428, 600, 1, 1, 4, AVAILABLE }, // 42 "AUTO" selection possibility { 1678, 600, 1, 1, 5, USE_IN_AUTO }, // 43 "AUTO" selection possibility { 1811, 500, 1, 1, 9, USE_IN_AUTO }, // 44 "AUTO" selection possibility { 1915, 500, 1, 0, 15, AVAILABLE }, // 45 "AUTO" selection possibility { 2251, 500, 1, 0, 1, AVAILABLE }, // 46 "AUTO" selection possibility { 2488, 450, 1, 0, 2, USE_IN_AUTO }, // 47 "AUTO" selection possibility { 2693, 450, 1, 0, 3, AVAILABLE }, // 48 "AUTO" selection possibility { 2849, 450, 1, 0, 4, AVAILABLE }, // 49 "AUTO" selection possibility { 3355, 400, 1, 0, 8, AVAILABLE }, // 50 If user selects 300KHz -> 335.5KHz actual { 3618, 300, 1, 0, 9, USE_IN_AUTO }, // 51 "AUTO" selection possibility { 4202, 300, 1, 0, 10, USE_IN_AUTO }, // 52 "AUTO" selection possibility { 4684, 300, 1, 0, 11, USE_IN_AUTO }, // 53 "AUTO" selection possibility { 5188, 300, 1, 0, 12, USE_IN_AUTO }, // 54 "AUTO" selection possibility { 5770, 300, 1, 0, 13, USE_IN_AUTO }, // 55 "AUTO" selection possibility { 6207, 300, 1, 0, 14, AVAILABLE } // 56 "AUTO" selection possibility }; uint8_t Si4432::_bpfCount = ELEMENTS ( Si4432::_bandpassFilters ); // Number of entries in the array /* * The constructor takes two arguments which are the pointer to the SPI object * and "Chip Select" pin for the module. * * The SPI object can be shared with other objects, so is declared in the main sketch as * * SPIClass* vspi = new SPIClass ( VSPI ); * or SPIClass* hspi = new SPIClass ( HSPI ); * * The SPI object is initialized once in the main sketch setup, along with the relevant pins: * * pinMode ( V_SCLK, OUTPUT ); // SPI Clock pin * pinMode ( V_SDO, INPUT ); // SDO (MISO) pin * pinMode ( V_SDI, OUTPUT ); // SDI (MOSI) pin * * digitalWrite ( V_SCLK, LOW ); // Make SPI clock LOW * digitalWrite ( V_SDI, LOW ); // Along with MOSI * * vspi->begin ( V_SCLK, V_SDO, V_SDI ); // Start the VSPI: SCLK, MISO, MOSI * vspi->setFrequency(10000000); // Max speed according to datasheet */ Si4432::Si4432 ( SPIClass* spi, uint8_t cs, uint8_t id ) // Constructor (argument is chip select pin number) { _cs = cs; // Remember the chip select pin number _bw = 3355; // Set bandwidth to 335.5KHz for now _dt = 400; // Proper delay time for wide bandwidth _spi = spi; // Pointer to SPI object _type = id; pinMode ( _cs, OUTPUT ); // Chip select pin is an output digitalWrite ( _cs, HIGH ); // Deselect the module } /* * There are two different initialization functions, as there are obviously some * differences in how you set up the transmitter and receiver modules. * * ** Modified for version 3.0e by M0WID to be one Init as the SI4432 can be used * for either RX aor TX depending on mode. All SI4432 are set as RX to start with * with no GPIO2 reference output * * The "SubInit" function takes care of the register settings common to both. */ bool Si4432::Init ( uint8_t cap ) { if ( !Reset () ) // Reset the module return false; // If that failed /* * "REG_FBS" is the frequency band select register. We select upper sideband * (0x40) and the band is set to (0x06). What that means is dependent on the * setting of the "HBSEL" bit (0x10), which is set to low band here. * * As near as I can tell from A440, this says we will be tuning in a range of * 300 to 310MHz. */ // WriteByte ( REG_FBS, 0x46 ); // Select high sideband & low freq range? /* * The following two instructions seem to set the carrier frequency to zero * per A440. The setting of "REG_NFC1" to 0x62 was commented out in the * original code. */ // WriteByte ( REG_NCF1, 0x62 ); // Nominal Carrier Frequency 1 // WriteByte ( REG_NCF1, 0x00 ); // WE USE 433.92 MHz // WriteByte ( REG_NCF0, 0x00 ); // Nominal Carrier Frequency 0 /* * Set the receiver modem IF bandwidth. In the original code, the bandwidth * was set to 37.3KHz (0x81); I changed it to 335.5KHz (maximum for the * application). */ // WriteByte ( REG_IFBW, 0x81 ); // RX Modem IF bandwidth (original value) WriteByte ( REG_IFBW, 0x18 ); // IF bandwidth (for 335.5 KHz) /* * Set the AFC loop gearshift override to minimum, turn off AFC */ WriteByte ( REG_AFCGSO, 0x00 ); // AFC Loop Gearshift Override WriteByte ( REG_AFCTC, 0x02 ); // AFC Timing Control /* * Set the "Clock Recovery Gearshift Value". The original code set it to 0x00, * however, the recommended value from A440 is 0x05. * We are not sending data so have no need to syncronise clocks between Tx and Rx * so just leave at the default value */ WriteByte ( REG_CRGO, 0x03 ); // Recommended value from A440 // WriteByte ( REG_CROSR, 0x78 ); // Clock Recovery Oversampling Ratio WriteByte ( REG_CRO2, 0x01 ); // Clock Recovery Offset 2 WriteByte ( REG_CRO1, 0x11 ); // Clock Recovery Offset 1 WriteByte ( REG_CRO0, 0x11 ); // Clock Recovery Offset 0 WriteByte ( REG_CRTLG1, 0x01 ); // Clock Recovery Timing Loop Gain 1 WriteByte ( REG_CRTLG0, 0x13 ); // Clock Recovery Timing Loop Gain 0 WriteByte ( REG_AFCLIM, 0xFF ); // AFC Limiter - Maximum WriteByte ( REG_OOKC1, 0x28 ); // OOK Counter Value 1 WriteByte ( REG_OOKC2, 0x0C ); // OOK Counter Value 2 WriteByte ( REG_SPH, 0x28 ); // OOK Attack & Decay settings WriteByte ( REG_DATAC, 0x61 ); // Disable packet handling /* * The original code had all these choices for what to put into the "REG_AGCOR1" * register (0x69) to control the LNA and pre-amp. Pick only one. */ // WriteByte ( REG_AGCOR1, 0x00 ); // No AGC, min LNA // WriteByte ( REG_AGCOR1, LNAGAIN ); // No AGC, max LNA of 20dB // WriteByte ( REG_AGCOR1, AGCEN ); // AGC enabled, min LNA WriteByte ( REG_AGCOR1, 0x60 ); // AGC, min LNA, Gain increase during signal reductions // WriteByte ( REG_AGCOR1, 0x30 ); // AGC, max LNA // WriteByte ( REG_AGCOR1, 0x70 ); // AGC, max LNA, Gain increase during signal reductions WriteByte ( REG_GPIO0, 0x12 ); // GPIO-0 TX State (output) WriteByte ( REG_GPIO1, 0x15 ); // GPIO-1 RX State (output) WriteByte ( REG_GPIO2, 0x1F ); // Set GPIO-2 output to ground until needed /* * * We turn on receive mode ("RXON"), the PLL ("PLLON") and ready mode * ("XTON"). The Si4432 module does not have the 32.768 kHz crystal for * the microcontroller, so we do not turn on the "X32KSEL" bit. * * The initial frequency is set to 433.92 MHz which is a typical IF * Finally the GPIO-2 pin is set to ground. * * 03/24 - Logic verified against A440 register edscription document */ Tune ( cap ); // Set the crystal capacitance to fine tune frequency WriteByte ( REG_OFC1, ( RXON | PLLON | XTON )); // Receiver, PLL and "Ready Mode" all on SetFrequency ( 443920000 ); // 443.92MHz delayMicroseconds ( 300 ); // Time to allow the SI4432 state machine to do its stuff Serial.printf ( "End of Init - _cs = %i\n", _cs ); return true; } /* * "Reset" - Initializes the Si4432. * * We write the "SW_RESET" (0x80) bit in the "REG_OFC1" register (0x07). * Doing so resets all the registers to their default values. The process * is complete when the "ICHIPRDY" bit (0x02) in the "REG_IS2" register * (0x04) goes high. * * We will try reading the ready bit 100 times with a slight pause between * tries. When it goes high, we're done. * * 03/24 - Logic verified against A440 */ bool Si4432::Reset () { uint32_t count = 0; uint32_t startTime = millis (); uint8_t regRead = 0; const char* unit[4] = { "RX", "TX", "TGIF", "TGLO" }; // For debugging WriteByte ( REG_OFC1, SW_RESET ); // Always perform a system reset delay(10); while ( millis() - startTime < 2000 ) // Try for 2 seconds { regRead = ReadByte ( REG_IS2 ); //**************************************************************************** // Bodge to get Glenn's board going! Do not include in normal release!!!! // Glenn's LO SI4432 does not seen to send signals back to the SPI bus. // //**************************************************************************** #ifdef GLENN_BUILD if (_cs != 5) { if ( ( regRead & ICHIPRDY ) && ( regRead != 0xFF ) ) // Wait for chip ready bit { Serial.printf ( " %s Si4432 Good to go - regRead = %02X _cs = %i ", unit[_type], regRead, _cs ); Serial.printf ( " Type %X Version %X Status %X \n", ReadByte(0), ReadByte(1), ReadByte(2)); return true; // Good to go! } } else { if ( regRead & ICHIPRDY ) // Wait for chip ready bit { Serial.printf ( " LO Si4432 Good to go - regRead = %02X _cs = %i ", regRead, _cs ); Serial.printf ( " Type %X Version %X Status %X \n", ReadByte(0), ReadByte(1), ReadByte(2)); return true; // Good to go! } } #else if ( ( regRead & ICHIPRDY ) && ( regRead != 0xFF ) ) // Wait for chip ready bit { Serial.printf ( " %s Si4432 Good to go - regRead = %02X _cs = %i ", unit[_type], regRead, _cs ); Serial.printf ( " Type %X Version %X Status %X \n", ReadByte(0), ReadByte(1), ReadByte(2)); return true; // Good to go! } #endif /* * If we don't have "ICHIPRDY" yet, only display the error message once per second. */ if ((( millis () - startTime ) % 1000 ) == 0 ) { Serial.print ( "Waiting for " ); Serial.print ( unit[_type] ); Serial.print ( " Si4432 - regRead " ); Serial.println ( regRead ); Serial.printf ( "Type %X Version %X Status %X \n", ReadByte(0), ReadByte(1), ReadByte(2)); } delay ( 1 ); // Slight pause } Serial.printf ( "%s Si4432 Reset failed - _cs = %i\n", unit[_type], _cs ); return false; } /* * "WriteByte" sends a byte of data into the selected Si4432 register. The * specified register number is transmitted first with the MSB turned on * which indicates it's a write operation. That is followed by the data byte * to be written to the specified register. */ void Si4432::WriteByte ( byte reg, byte setting ) { reg |= 0x80 ; // Indicate this is a "write" operation //_spi->beginTransaction ( SPISettings ( BUS_SPEED, BUS_ORDER, BUS_MODE )); //spiSimpleTransaction(_spi->bus()); digitalWrite ( _cs, LOW ); // Select the correct device _spi->transfer ( reg ); // Send the register address _spi->transfer ( setting ); // and the data byte digitalWrite ( _cs, HIGH ); // Deselect the device //_spi->endTransaction(); // Release the bus // delayMicroseconds ( WRITE_DELAY ); } /* * "ReadByte" reads a byte of data from the selected Si4432. Here we send the * register with the MSB set to zero indicating that we want to read the * specified register. * * The "transfer" call to read the data has a dummy argument of '0'; the * argument is needed but has absolutely no meaning. */ uint8_t Si4432::ReadByte ( uint8_t reg ) { uint8_t regValue; // Contains the requested data //_spi->beginTransaction ( SPISettings ( BUS_SPEED, BUS_ORDER, BUS_MODE )); //spiSimpleTransaction(_spi->bus()); digitalWrite ( _cs, LOW ); // Select the correct device _spi->transfer ( reg ); // Send the register address regValue = _spi->transfer ( 0 ); // Read the data byte digitalWrite ( _cs, HIGH ); // Deselect the device //_spi->endTransaction(); return regValue; // Return the answer } /* * "SetFrequency" sets the Si4432 frequency. Technical note A440 explains some of * what's going on in here! */ void Si4432::SetFrequency ( uint32_t freq ) { int hbsel; // high band select, but shifted to the correct location in the byte int sbsel; // Sideband select bit uint16_t carrier; // Carrier frequency uint32_t reqFreq = freq; // Copy of requested frequency uint8_t fbs; // Frequency band select value (N - 24) uint8_t ofc1; // To read the "REG_OFC1" register uint8_t registerBuf[4]; // Used to send frequency data in burst mode if ( freq >= 480000000 ) // Frequency > 480MHz (high band)? { hbsel = HBSEL; // High band is 480MHz to 960MHz freq = freq / 2; // And divide the frequency in half } else // Frequency requested is less than 480MHz { hbsel = 0x00; // Low band is 240KHz to 479.9MHz } sbsel = SBSEL; // Select high sideband (always) /* * add half the frequency resolution to required frequency so the actual value is rounded to nearest, not lowest * Frequency resoluion is 156.25Hz in low band, 312.5Hz in high band but freq is already divided above */ freq = freq + 78; /* * "N" picks the 10MHz range for low band and the 20MHz range for the high band. * It's explained in "Table 12" in the Silicon Labs datasheet. */ int N = freq / 10000000; // Divide freq by 10MHz /* * The low order 5 bits of the "Frequency Band Select" register (0x75) get * loaded with "N - 24" and we add in the band select and sideband select * bits. */ fbs = (( N - 24 ) | hbsel | sbsel ); /* * Compute the actual carrier frequency. * * It takes a few things to actually set the frequency. See Tech Note A440 * for an explanation (it made my head hurt)! * * The carrier frequency is actually an offset from the lower end of the * frequency band selected (N-24), so for example in the initialization * sequence we set the carrier frequency to 443,920,000. From Table 12 in * the datasheet, we see that the frequency is in the "Low Band" (less than * 480MHz) and the actual band will be '20', and "N" will be '44'. * * The "carrier" value is the number of '156.25Hz" increments from the base * frequency of the band. For a frequency of 443,920,000, the answer is * 25,088. * * If we're operating on the "High Band", the "carrier" is the number of * '312.5Hz' increments from the base frequency of the band. */ carrier = ( 4 * ( freq - N * 10000000 )) / 625; //if (_cs == 2 ) // Serial.printf ( "\nRequested frequency = %u \n", reqFreq ); /* * M0WID mod - Update the frequency in "burst" mode as opposed to separate * writes for each register */ uint8_t ncf1 = ( carrier >> 8 ) & 0xFF; registerBuf[0] = REG_FBS|0x80; // First register in write mode (bit 7 set) registerBuf[1] = fbs; // FBS register value registerBuf[2] = ncf1 ; // NCF1 value registerBuf[3] = carrier & 0xFF; // NCF0 value //_spi->beginTransaction ( SPISettings ( BUS_SPEED, BUS_ORDER, BUS_MODE )); // spiSimpleTransaction(_spi->bus()); digitalWrite ( _cs, LOW ); // Select the correct device _spi->transfer ( registerBuf, 4 ); // Send the data digitalWrite ( _cs, HIGH ); // Deselect the device //_spi->endTransaction(); _ncf1 = ncf1; _fbs = fbs; uint32_t fb = ( fbs & F_BAND ) ; _hbsel = hbsel>>5; // should be 1 or 0 // _freq will contain the actual frequency, not necessarily what was requested _freq = (double)(10000000 * (_hbsel + 1 )) * ( (double)fb + (double)24 + (double)carrier / (double)64000) ; // Serial.printf("set Freq :%i, actual:%i, fb:%i, fc:%i, hbsel:%i\n", reqFreq, _freq, fb, carrier, hbsel); // Serial.print ( ", N = " ); // Serial.print ( N ); // fbs = ReadByte ( REG_FBS ); // Serial.print ( ", Freq_Band = " ); // Serial.println ( fbs & F_BAND ); // Serial.print ( "hbsel = " ); // Serial.print ( fbs & HBSEL, HEX ); // carrier = ReadByte ( REG_NCF1 ) << 8; // carrier |= ReadByte ( REG_NCF0 ); // Serial.print ( ", Carrier = " ); // Serial.print ( carrier ); // ofc1 = ReadByte ( REG_OFC1 ); // Serial.print ( ", REG_OFC1 = " ); // Serial.println ( ofc1, HEX ); /* * This delay is needed in the test programs. In the real TinySA software it is * handled by the calling program. */ // delayMicroseconds ( _dt ); } /* * "SetOffsetFreq" adjusts the frequency from the nominal value set in SetFrequency. * It is intended for use by the SI4432 AFC algorithm to enable more accurate * frequency matching of different radios, but we can turn off the AFC and use it * to adjust the frequency without having the Si4432 go through its TX-RX-TX state machine * and enable reduced edlay times from setting a frequency to getting valid readings * The offset can vary +- 80kHz in low bands (f<480MHz) and +-160kHz in high bands (480-960MHz) * negative numbers are twos complement of the positive offset */ void Si4432::SetOffsetFreq ( int32_t freq ) { uint8_t registerBuf[3]; // Used to send data in burst mode uint32_t posFreq; if (freq < 0) posFreq = -freq; else posFreq = freq; uint16_t fo = (double)posFreq/(156.25 * (double)(_hbsel + 1)); if (freq < 0) // do twos complement - invert the bits (0-9) (use XOR) and add one fo = (fo ^ 0x3FF) + 1; //Serial.printf(" offset frequency %i fo=%3x \n", freq, fo); // write to the Si4432 registerBuf[0] = REG_FOFF1|0x80; // First register in write mode (bit 7 set) registerBuf[1] = fo & 0xFF; // first 8 bits registerBuf[2] = (fo >> 8 ) & 0x03; // last 2 bits //_spi->beginTransaction ( SPISettings ( BUS_SPEED, BUS_ORDER, BUS_MODE )); // spiSimpleTransaction(_spi->bus()); digitalWrite ( _cs, LOW ); // Select the correct device _spi->transfer ( registerBuf, 3 ); // Send the data digitalWrite ( _cs, HIGH ); // Deselect the device //_spi->endTransaction(); } /* * "SetOffset" sets the frequency offset registers to correspond to the number of offset steps */ void Si4432::SetOffset ( int32_t offset ) { uint8_t registerBuf[3]; // Used to send data in burst mode uint16_t posOffset; if (offset < 0) posOffset = -offset; else posOffset = offset; uint16_t fo = posOffset; if (offset < 0) // do twos complement - invert the bits (0-9) (use XOR) and add one fo = (fo ^ 0x3FF) + 1; //Serial.printf(" posoffset %3x, offset %i fo=%3x \n", posOffset, offset, fo); // write to the Si4432 registerBuf[0] = REG_FOFF1|0x80; // First register in write mode (bit 7 set) registerBuf[1] = fo & 0xFF; // first 8 bits registerBuf[2] = (fo >> 8 ) & 0x03; // last 2 bits //_spi->beginTransaction ( SPISettings ( BUS_SPEED, BUS_ORDER, BUS_MODE )); // spiSimpleTransaction(_spi->bus()); digitalWrite ( _cs, LOW ); // Select the correct device _spi->transfer ( registerBuf, 3 ); // Send the data digitalWrite ( _cs, HIGH ); // Deselect the device //_spi->endTransaction(); } /* * "SetRBW" Sets the "Resolution Bandwidth" based on a required value passed in * and returns the actual value chosen as well as the required delay to allow the * FIR filter in the SI4432 to settle (in microseconds) Delay time is longer for * narrower bandwidths. * The filter index is also returned. * Filters flagged as DO_NOT_USE will not be chosen */ float Si4432::SetRBW ( float reqBandwidth10, unsigned long* delaytime_p, uint16_t* filter_p ) // "reqBandwidth" in kHz * 10 { int filter = _bpfCount-1; // Elements in the "bandpassFilters" array // Serial.printf ( "bpfCount %i\n", filter ); /* * "filter" is the index into "bandpassFilters" array. If the requested * bandwidth is less than the bandwith in the first entry, we use that entry (2.6KHz). */ if ( reqBandwidth10 <= _bandpassFilters[0].bandwidth10 ) { filter = 0; // Serial.print ("Minimum RBW"); } /* * If the requested bandwidth is greater or equal to the value in the first entry, * find the setting that is nearest (and above) the requested setting. */ else while ( ( ( _bandpassFilters[filter-1].bandwidth10 > reqBandwidth10 - 0.01 ) && ( filter > 0 ) ) || (_bandpassFilters[filter].Status == DO_NOT_USE ) ) filter--; // Serial.printf ( "Required = %f, filter = %i\n", reqBandwidth10, filter); Serial.println ( filter ); /* * Ok, we found the appropriate setting (or ended up with the maximum one), * formulate the byte to sent to the "REG_IFBW" register (0x1C) from the piece parts. */ byte BW = ( _bandpassFilters[filter].dwn3_bypass << 7 ) | ( _bandpassFilters[filter].ndec_exp << 4 ) | _bandpassFilters[filter].filset; WriteByte ( REG_IFBW ,BW ); // Send the bandwidth setting to the Si4432 /* * "Oversampling rate for clock recovery". Let me know if you understand the explanation * in Tech Note A440! */ float rxosr = 500.0 * ( 1.0 + 2.0 * _bandpassFilters[filter].dwn3_bypass ) / ( pow ( 2.0, ( _bandpassFilters[filter].ndec_exp - 3.0 )) * _bandpassFilters[filter].bandwidth10 / 10.0 ); byte integer = (byte) rxosr ; byte fractio = (byte) (( rxosr - integer ) * 8 ); byte memory = ( integer << 3 ) | ( 0x07 & fractio ); WriteByte ( REG_CROSR , memory ); // Clock recovery oversampling rate /* * Set the bandwidth and delay time in the "RBW" structure returned by the function * and in our internal variables. */ _bw = _bandpassFilters[filter].bandwidth10 / 10.0; _dt = _bandpassFilters[filter].settleTime; *delaytime_p = _dt; *filter_p = filter; return _bw; } void Si4432::SetPreampGain ( int gain ) // Sets preamp gain { WriteByte ( REG_AGCOR1, gain ); // Just feed it to the Si4432 _gainReg = gain; _autoGain = (bool)(gain & AGCEN); // Serial.printf("Si4432 set gain:%i, auto:%i\n", _gainReg, _autoGain); } /* * "GetPreampGain" was added by M0WID to read the LNA/PGA gain from the RX Si4432. Later * modified to return the AGC setting state (on or off) and the "PGAGAIN | LNAGAIN" * settings via the pointers in the argument list. */ int Si4432::GetPreampGain () { if (_autoGain) { return ReadByte ( REG_AGCOR1 ); // Just return the register } else { return _gainReg; } } int Si4432::ReadPreampGain () { return ReadByte ( REG_AGCOR1 ); // Just return the register } /* * "PreAmpAGC" returns "true" if the AGC is set to auto: */ bool Si4432::PreAmpAGC () // Return true if agc set to auto { /* * Register 0x69 (REG_AGCOR1) contains the current setting of the LNA and PGA * amplifiers. If AGC is enabled the value can vary during a sweep */ byte Reg69 = ReadByte ( REG_AGCOR1 ); // Read the register // Serial.printf ( "REG_AGCOR1 %X \n", Reg69 ); // Debugging return ( Reg69 & AGCEN ); // And the answer is! } bool Si4432::GetPreAmpAGC () // Return true if agc set to auto { return ( _autoGain ); } /* * "GetRSSI" is perhaps the most important function in the whole TinySA program. * It returns the "Received Signal Strength Indicator" value from the receiver * module. The RSSI is essentially an "S" meter indication from the receiver that * will be used to measure the received level. The daya dheet indicates and accuracy * of +/- 0.5dB. */ uint8_t Si4432::GetRSSI () { uint8_t rawRSSI; rawRSSI = ReadByte ( REG_RSSI ); // float dBm = 0.5 * rawRSSI - 120.0 ; // Serial.println ( dBm, 2 ); return rawRSSI ; } /* * "SetPowerReference" - Set the GPIO-2 output for the LO (TX) SI4432 to required * frequency, or off. * * If freq < 0 or > 6 GPIO-2 is grounded * * Freq = 0 30MHz * Freq = 1 15MHz * Freq = 2 10MHz * Freq = 3 4MHz * Freq = 4 3MHz * Freq = 5 2MHz * Freq = 6 1MHz */ void Si4432::SetPowerReference ( int freq ) { if ( freq < 0 || freq > 6 ) // Illegal frequency selection? WriteByte ( REG_GPIO2, 0x1F ); // Set GPIO-2 to ground else { WriteByte ( REG_GPIO2, 0xC0 ); // Maximum drive and microcontroller clock output WriteByte ( REG_MCOC, freq & 0x07 ); // Set GPIO-2 frequency as specified } } /* * "SetDrive" can be used to to set the transmitter (aka local oscillator) power * output level. * * The drive value can be from '0' to '7' and the output power will be set * according to the following table (from page 39 of the Si4432 datasheet): * * 0 => +1dBm 4 => +11dBm * 1 => +2dBm 5 => +14dBm * 2 => +5dBm 6 => +17dBm * 3 => +8dBm 7 => +20dBm */ void Si4432::SetDrive ( uint8_t level ) // Sets the LO drive level { if (( level < 0 ) || ( level > 7 )) { // Serial.printf ( "VFO %i Drive request refused level was %i\n", _type, level ); return; } else { _pwr = level; WriteByte ( REG_TXPWR, _pwr ); // Set power level // Serial.printf ( "VFO %i Drive set to %i\n", _type, _pwr ); } } /* * The transmitter module isn't always in transmit mode and the receiver module isn't * always in receive mode, so these two functions allow us to switch modes for one or * the other: */ void Si4432::TxMode ( uint8_t level ) // Put module in TX mode { WriteByte ( REG_OFC1, ( TXON | PLLON | XTON )); // Transmitter, PLL and "Ready Mode" all on SetDrive ( level ); } void Si4432::RxMode () // Put module in RX mode { WriteByte ( REG_OFC1, ( RXON | PLLON | XTON )); // Receiver, PLL and "Ready Mode" all on } void Si4432::ReadyMode () // Put module into ready mode, rx and tx off { WriteByte ( REG_OFC1, XTON ); // "Ready Mode" on } void Si4432::SetMode (uint8_t newMode) { WriteByte ( REG_OFC1, newMode ); // "Ready Mode" on } uint8_t Si4432::GetMode () { return ReadByte ( REG_OFC1 ); } /* * "Tune" sets the crystal tuning capacitance. * * We need to cycle the PLL to force a recalibration after the tune */ void Si4432::Tune ( uint8_t cap ) // Set the crystal tuning capacitance { _capacitance = cap; // Save in local data WriteByte ( REG_COLC, _capacitance ); // Send to the Si4432 uint8_t currentMode = ReadByte ( REG_OFC1 ); ReadyMode (); delayMicroseconds(300); SetMode ( currentMode ); } /* * Get frequency from Si4432 */ uint32_t Si4432::GetFrequency () { return _freq; } uint32_t Si4432::ReadFrequency () { uint8_t fbs = ReadByte(REG_FBS); uint16_t ncf1 = ReadByte(REG_NCF1); uint16_t ncf0 = ReadByte(REG_NCF0); uint32_t fc = ( ncf1<<8 ) + ncf0; uint32_t fb = ( fbs & F_BAND ) ; uint32_t hb = ( fbs & HBSEL ) >> 5; // HBSEL is bit 5 // Serial.printf ( "FBS=%X ncf1=%X ncf0=%X HBSEL=%X F_BAND=%X fc=%X (%u)\n", // fbs, ncf1, ncf0, hb, fb, fc, fc); uint32_t f = (uint32_t) ( 10000000.0 * ( (float) hb + 1.0 ) * ( (float) fb + 24.0 + ( (float) fc ) / 64000.0 )); return f; } /* * "GetBandpassFilter10" - Get filter bandwidth * 10 from the specifed element of * the bandpassfilter array */ uint16_t Si4432::GetBandpassFilter10 ( uint8_t index ) { if ( index >= _bpfCount ) return 0; else return _bandpassFilters[index].bandwidth10; } uint8_t Si4432::GetBandpassFilterCount () { return _bpfCount; } /* * For debugging, we can do a register dump on the module. */ void Si4432::PrintRegs () // Dump all the Si4432 registers { for ( int r = 0; r < 0x80; r++) if ( _type == RX_4432 ) // Receiver? Serial.printf ( "RX Reg[0x%02X] = 0x%02X\n", r, ReadByte ( r )); else if ( _type == TX_4432 ) // Transmitter? Serial.printf ( "TX Reg[0x%02X] = 0x%02X\n", r, ReadByte ( r )); else if ( _type == TGIF_4432 ) // Transmitter? Serial.printf ( "TGIF Reg[0x%02X] = 0x%02X\n", r, ReadByte ( r )); else if ( _type == TGLO_4432 ) // Transmitter? Serial.printf ( "TGLO Reg[0x%02X] = 0x%02X\n", r, ReadByte ( r )); }