diff --git a/examples/FSK4/FSK4_Transmit/FSK4_Transmit.ino b/examples/FSK4/FSK4_Transmit/FSK4_Transmit.ino new file mode 100644 index 00000000..045469cd --- /dev/null +++ b/examples/FSK4/FSK4_Transmit/FSK4_Transmit.ino @@ -0,0 +1,122 @@ +/* + RadioLib FSK4 Transmit Example + + This example sends an example FSK-4 'Horus Binary' message using SX1278's + FSK modem. + + This signal can be demodulated using a SSB demodulator (SDR or otherwise), and + horusdemodlib: https://github.com/projecthorus/horusdemodlib/wiki + + Other modules that can be used for FSK4: + (Untested, but work with RTTY to are likely to work here too) + - SX127x/RFM9x + - RF69 + - SX1231 + - CC1101 + - SX126x + - nRF24 + - Si443x/RFM2x + - SX128x + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ +*/ + +// include the library +#include + +// SX1278 has the following connections: +// NSS pin: 10 +// DIO0 pin: 2 +// RESET pin: 9 +// DIO1 pin: 3 +SX1278 radio = new Module(10, 2, 9, 3); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//SX1278 radio = RadioShield.ModuleA; + +// create FAK4 client instance using the FSK module +FSK4Client fsk4(&radio); + +// A 'canned' encoded Horus Binary telemetry packet. +// Refer here for packet format information: +// https://github.com/projecthorus/horusdemodlib/wiki/2---Modem-Details#horus-binary-v1-mode-4-fsk +// After demodulation, deinterleaving, and descrambling, this results in a packet: 00000001172D0000000000000000D20463010AFF2780 +// This decodes to the Habitat-compatible telemetry string: $$4FSKTEST,0,01:23:45,0.00000,0.00000,1234,99,1,10,5.00*ABCD +uint8_t sample_packet[] = { + 0x45, 0x24, 0x24, 0x48, 0x2F, 0x12, 0x16, 0x08, 0x15, 0xC1, + 0x49, 0xB2, 0x06, 0xFC, 0x92, 0xEB, 0x93, 0xD7, 0xEE, 0x5D, + 0x35, 0xA0, 0x91, 0xDA, 0x8D, 0x5F, 0x85, 0x6B, 0x63, 0x03, + 0x6B, 0x60, 0xEA, 0xFE, 0x55, 0x9D, 0xF1, 0xAB, 0xE5, 0x5E, + 0xDB, 0x7C, 0xDB, 0x21, 0x5A, 0x19 +}; +uint8_t sample_packet_len = 45; + +void setup() { + Serial.begin(9600); + + // initialize SX1278 with default settings + Serial.print(F("[SX1278] Initializing ... ")); + int state = radio.beginFSK(); + + // when using one of the non-LoRa modules for FSK4 + // (RF69, CC1101, Si4432 etc.), use the basic begin() method + // int state = radio.begin(); + + if(state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // initialize FSK4 client + // NOTE: FSK4 frequency shift will be rounded + // to the nearest multiple of frequency step size. + // The exact value depends on the module: + // SX127x/RFM9x - 61 Hz + // RF69 - 61 Hz + // CC1101 - 397 Hz + // SX126x - 1 Hz + // nRF24 - 1000000 Hz + // Si443x/RFM2x - 156 Hz + // SX128x - 198 Hz + Serial.print(F("[FSK4] Initializing ... ")); + // low ("space") frequency: 434.0 MHz + // frequency shift: 270 Hz (actually results in a shift of 244 Hz) + // baud rate: 100 baud + state = fsk4.begin(434.0, 270, 100); + if(state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + +} + +void loop() { + Serial.print(F("[FSK4] Sending FSK4 data packet ... ")); + + // send out idle condition for 1000 ms + fsk4.idle(); + delay(1000); + + // FSK4Client supports the write(uint8_t b) and write(uint8_t* buff, size_t len) method. + // We use the write(uint8_t b) method to send a few bytes of preamble to allow the demodulator + // to lock on to the signal. + fsk4.write(0x1B); fsk4.write(0x1B); fsk4.write(0x1B); fsk4.write(0x1B); + fsk4.write(0x1B); fsk4.write(0x1B); fsk4.write(0x1B); fsk4.write(0x1B); + + // We then send the encoded packet. + fsk4.write(sample_packet, sample_packet_len); + + Serial.println(F("done!")); + +} diff --git a/src/RadioLib.h b/src/RadioLib.h index e8e26ad0..558eec22 100644 --- a/src/RadioLib.h +++ b/src/RadioLib.h @@ -98,6 +98,7 @@ #include "protocols/Morse/Morse.h" #include "protocols/RTTY/RTTY.h" #include "protocols/SSTV/SSTV.h" +#include "protocols/FSK4/FSK4.h" // transport layer protocols #include "protocols/TransportLayer/TransportLayer.h" diff --git a/src/protocols/FSK4/FSK4.cpp b/src/protocols/FSK4/FSK4.cpp new file mode 100644 index 00000000..df331d9c --- /dev/null +++ b/src/protocols/FSK4/FSK4.cpp @@ -0,0 +1,119 @@ +#include "FSK4.h" +#if !defined(RADIOLIB_EXCLUDE_FSK4) + + + +FSK4Client::FSK4Client(PhysicalLayer* phy) { + _phy = phy; + #if !defined(RADIOLIB_EXCLUDE_AFSK) + _audio = nullptr; + #endif +} + +//#if !defined(RADIOLIB_EXCLUDE_AFSK) +// FSK4Client::FSK4Client(AFSKClient* audio) { +// _phy = audio->_phy; +// _audio = audio; +// } +//#endif + +int16_t FSK4Client::begin(float base, uint32_t shift, uint16_t rate) { + // save configuration + _baseHz = base; + _shiftHz = shift; + + + // calculate duration of 1 bit + _bitDuration = (uint32_t)1000000/rate; + + // calculate module carrier frequency resolution + uint32_t step = round(_phy->getFreqStep()); + + // check minimum shift value + if(shift < step / 2) { + return(ERR_INVALID_RTTY_SHIFT); + } + + // round shift to multiples of frequency step size + if(shift % step < (step / 2)) { + _shift = shift / step; + } else { + _shift = (shift / step) + 1; + } + + // Write resultant tones into arrays for quick lookup when modulating. + _tones[0] = 0; + _tones[1] = _shift; + _tones[2] = _shift*2; + _tones[3] = _shift*3; + + _tonesHz[0] = 0; + _tonesHz[1] = _shiftHz; + _tonesHz[2] = _shiftHz*2; + _tonesHz[3] = _shiftHz*3; + + // calculate 24-bit frequency + _base = (base * 1000000.0) / _phy->getFreqStep(); + + // configure for direct mode + return(_phy->startDirect()); +} + +void FSK4Client::idle() { + // Idle at Tone 0. + tone(0); +} + +size_t FSK4Client::write(uint8_t* buff, size_t len) { + size_t n = 0; + for(size_t i = 0; i < len; i++) { + n += FSK4Client::write(buff[i]); + } + FSK4Client::standby(); + return(n); +} + +size_t FSK4Client::write(uint8_t b) { + + int k; + // Send symbols MSB first. + for (k=0;k<4;k++) + { + // Extract 4FSK symbol (2 bits) + uint8_t symbol = (b & 0xC0) >> 6; + // Modulate + FSK4Client::tone(symbol); + // Shift to next symbol. + b = b << 2; + } + + return(1); +} + +void FSK4Client::tone(uint8_t i) { + uint32_t start = Module::micros(); + transmitDirect(_base + _tones[i], _baseHz + _tonesHz[i]); + while(Module::micros() - start < _bitDuration) { + Module::yield(); + } +} + +int16_t FSK4Client::transmitDirect(uint32_t freq, uint32_t freqHz) { + #if !defined(RADIOLIB_EXCLUDE_AFSK) + if(_audio != nullptr) { + return(_audio->tone(freqHz)); + } + #endif + return(_phy->transmitDirect(freq)); +} + +int16_t FSK4Client::standby() { + #if !defined(RADIOLIB_EXCLUDE_AFSK) + if(_audio != nullptr) { + return(_audio->noTone()); + } + #endif + return(_phy->standby()); +} + +#endif diff --git a/src/protocols/FSK4/FSK4.h b/src/protocols/FSK4/FSK4.h new file mode 100644 index 00000000..eb83c66e --- /dev/null +++ b/src/protocols/FSK4/FSK4.h @@ -0,0 +1,82 @@ +#if !defined(_RADIOLIB_FSK4_H) +#define _RADIOLIB_FSK4_H + +#include "../../TypeDef.h" + +#if !defined(RADIOLIB_EXCLUDE_FSK4) + +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../AFSK/AFSK.h" + + +/*! + \class FSK4Client + + \brief Client for FSK-4 communication. The public interface is the same as Arduino Serial. +*/ +class FSK4Client { + public: + /*! + \brief Constructor for FSK-4 mode. + + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + explicit FSK4Client(PhysicalLayer* phy); + + #if !defined(RADIOLIB_EXCLUDE_AFSK) + /*! + \brief Constructor for AFSK mode. + + \param audio Pointer to the AFSK instance providing audio. + */ + //explicit FSK4Client(AFSKClient* audio); + #endif + + // basic methods + + /*! + \brief Initialization method. + + \param base Base (space) frequency to be used in MHz (in FSK-4 mode), or the space tone frequency in Hz (in AFSK mode) + + \param shift Frequency shift between each tone in Hz. + + \param rate Baud rate to be used during transmission. + + + \returns \ref status_codes + */ + int16_t begin(float base, uint32_t shift, uint16_t rate); + + /*! + \brief Send out idle condition (RF tone at mark frequency). + */ + void idle(); + + size_t write(uint8_t* buff, size_t len); + size_t write(uint8_t b); + + +#ifndef RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* _phy; + #if !defined(RADIOLIB_EXCLUDE_AFSK) + AFSKClient* _audio; + #endif + + uint32_t _base = 0, _baseHz = 0; + uint32_t _shift = 0, _shiftHz = 0; + uint32_t _bitDuration = 0; + uint32_t _tones[4]; + uint32_t _tonesHz[4]; + + void tone(uint8_t i); + + int16_t transmitDirect(uint32_t freq = 0, uint32_t freqHz = 0); + int16_t standby(); +}; + +#endif + +#endif