From 0a07f22e931efe61ceb0c61993214a0a73791892 Mon Sep 17 00:00:00 2001 From: jgromes Date: Thu, 30 Apr 2020 17:11:24 +0200 Subject: [PATCH] [SSTV] Added AFSK support --- .../SSTV_Transmit_AFSK/SSTV_Transmit_AFSK.ino | 155 ++++++++++++++++++ src/protocols/SSTV/SSTV.cpp | 30 +++- src/protocols/SSTV/SSTV.h | 30 +++- 3 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 examples/SSTV/SSTV_Transmit_AFSK/SSTV_Transmit_AFSK.ino diff --git a/examples/SSTV/SSTV_Transmit_AFSK/SSTV_Transmit_AFSK.ino b/examples/SSTV/SSTV_Transmit_AFSK/SSTV_Transmit_AFSK.ino new file mode 100644 index 00000000..b43c3743 --- /dev/null +++ b/examples/SSTV/SSTV_Transmit_AFSK/SSTV_Transmit_AFSK.ino @@ -0,0 +1,155 @@ +/* + RadioLib SSTV Transmit AFSK Example + + The following example sends SSTV picture using + SX1278's FSK modem. The data is modulated + as AFSK. + + Other modules that can be used for SSTV: + with AFSK modulation: + - SX127x/RFM9x + - RF69 + - SX1231 + - CC1101 + - Si443x/RFM2x + + NOTE: Some platforms (such as Arduino Uno) + might not be fast enough to correctly + send pictures via high-speed modes + like Scottie2 or Martin2. For those, + lower speed modes such as Wrasse, + Scottie1 or Martin1 are recommended. + + 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 fsk = new Module(10, 2, 9, 3); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//SX1278 fsk = RadioShield.ModuleA; + +// create AFSK client instance using the FSK module +// pin 5 is connected to SX1278 DIO2 +AFSKClient audio(&fsk, 5); + +// create SSTV client instance using the AFSK instance +SSTVClient sstv(&audio); + +// test "image" - actually just a single 320px line +// will be sent over and over again, to create vertical color stripes at the receiver +uint32_t line[320] = { + // black + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, + + // blue + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, 0x0000FF, + + // green + 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, + 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, 0x00FF00, + + // cyan + 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, + 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, 0x00FFFF, + + // red + 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, + 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, + + // magenta + 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, + 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, 0xFF00FF, + + // yellow + 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, + 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0xFFFF00, + + // white + 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, + 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF +}; + +void setup() { + Serial.begin(9600); + + // initialize SX1278 + Serial.print(F("[SX1278] Initializing ... ")); + // carrier frequency: 434.0 MHz + // bit rate: 48.0 kbps + // frequency deviation: 50.0 kHz + // Rx bandwidth: 125.0 kHz + // output power: 13 dBm + // current limit: 100 mA + int state = fsk.beginFSK(); + if (state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while (true); + } + + // when using one of the non-LoRa modules for SSTV + // (RF69, SX1231 etc.), use the basic begin() method + // int state = fsk.begin(); + + // initialize SSTV client + Serial.print(F("[SSTV] Initializing ... ")); + // SSTV mode: Wrasse (SC2-180) + // correction factor: 0.95 + // NOTE: Due to different speeds of various platforms + // supported by RadioLib (Arduino Uno, ESP32 etc), + // and because SSTV is analog protocol, incorrect + // timing of pulses can lead to distortions. + // To compensate, correction factor can be used + // to adjust the length of timing pulses + // (lower number = shorter pulses). + // The value is usually around 0.95 (95%). + state = sstv.begin(Wrasse, 0.95); + if(state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // to help tune the receiver, SSTVClient can send + // continuous 1900 Hz beep + /* + sstv.idle(); + while(true); + */ +} + +void loop() { + // send picture with 8 color stripes + Serial.print(F("[SSTV] Sending test picture ... ")); + + // send synchronization header first + sstv.sendHeader(); + + // send all picture lines + for(uint16_t i = 0; i < sstv.getPictureHeight(); i++) { + sstv.sendLine(line); + } + + // turn off transmitter + fsk.standby(); + + Serial.println(F("done!")); + + delay(30000); +} diff --git a/src/protocols/SSTV/SSTV.cpp b/src/protocols/SSTV/SSTV.cpp index c3843ad8..4bc16902 100644 --- a/src/protocols/SSTV/SSTV.cpp +++ b/src/protocols/SSTV/SSTV.cpp @@ -155,6 +155,21 @@ const SSTVMode_t PasokonP7 { SSTVClient::SSTVClient(PhysicalLayer* phy) { _phy = phy; + _audio = nullptr; +} + +SSTVClient::SSTVClient(AFSKClient* audio) { + _phy = audio->_phy; + _audio = audio; +} + +int16_t SSTVClient::begin(SSTVMode_t mode, float correction) { + if(_audio == nullptr) { + // this initialization method can only be used in AFSK mode + return(ERR_WRONG_MODEM); + } + + return(begin(0, mode, correction)); } int16_t SSTVClient::begin(float base, SSTVMode_t mode, float correction) { @@ -170,19 +185,24 @@ int16_t SSTVClient::begin(float base, SSTVMode_t mode, float correction) { // calculate 24-bit frequency _base = (base * 1000000.0) / _phy->getFreqStep(); - // set module frequency deviation to 0 - int16_t state = _phy->setFrequencyDeviation(0); + // set module frequency deviation to 0 if using FSK + int16_t state = ERR_NONE; + if(_audio == nullptr) { + state = _phy->setFrequencyDeviation(0); + } return(state); } void SSTVClient::idle() { + _phy->transmitDirect(); tone(SSTV_TONE_LEADER); } void SSTVClient::sendHeader() { // save first header flag for Scottie modes _firstLine = true; + _phy->transmitDirect(); // send the first part of header (leader-break-leader) tone(SSTV_TONE_LEADER, SSTV_HEADER_LEADER_LENGTH); @@ -261,7 +281,11 @@ uint16_t SSTVClient::getPictureHeight() { void SSTVClient::tone(float freq, uint32_t len) { uint32_t start = micros(); - _phy->transmitDirect(_base + (freq / _phy->getFreqStep())); + if(_audio != nullptr) { + _audio->tone(freq, false); + } else { + _phy->transmitDirect(_base + (freq / _phy->getFreqStep())); + } while(micros() - start < len) { yield(); } diff --git a/src/protocols/SSTV/SSTV.h b/src/protocols/SSTV/SSTV.h index d8dc0c8b..4c6777d0 100644 --- a/src/protocols/SSTV/SSTV.h +++ b/src/protocols/SSTV/SSTV.h @@ -3,6 +3,7 @@ #include "../../TypeDef.h" #include "../PhysicalLayer/PhysicalLayer.h" +#include "../AFSK/AFSK.h" // the following implementation is based on information from // http://www.barberdsp.com/downloads/Dayton%20Paper.pdf @@ -116,23 +117,45 @@ extern const SSTVMode_t PasokonP7; class SSTVClient { public: /*! - \brief Default constructor. + \brief Constructor for 2-FSK mode. \param phy Pointer to the wireless module providing PhysicalLayer communication. */ SSTVClient(PhysicalLayer* phy); + /*! + \brief Constructor for AFSK mode. + + \param audio Pointer to the AFSK instance providing audio. + */ + SSTVClient(AFSKClient* phy); + // basic methods /*! - \brief Initialization method. + \brief Initialization method for 2-FSK. - \param base Base RF frequency to be used in MHz. In USB modulation, this corresponds to "0 Hz tone". + \param base Base "0 Hz tone" RF frequency to be used in MHz. \param mode SSTV mode to be used. Currently supported modes are Scottie1, Scottie2, ScottieDX, Martin1, Martin2, Wrasse, PasokonP3, PasokonP5 and PasokonP7. + + \param correction Timing correction factor, used to adjust the length of timing pulses. Less than 1.0 leads to shorter timing pulses, defaults to 1.0 (no correction). + + \returns \ref status_codes */ int16_t begin(float base, SSTVMode_t mode, float correction = 1.0); + /*! + \brief Initialization method for AFSK. + + \param mode SSTV mode to be used. Currently supported modes are Scottie1, Scottie2, ScottieDX, Martin1, Martin2, Wrasse, PasokonP3, PasokonP5 and PasokonP7. + + \param correction Timing correction factor, used to adjust the length of timing pulses. Less than 1.0 leads to shorter timing pulses, defaults to 1.0 (no correction). + + \returns \ref status_codes + */ + int16_t begin(SSTVMode_t mode, float correction = 1.0); + /*! \brief Sends out tone at 1900 Hz. */ @@ -161,6 +184,7 @@ class SSTVClient { private: #endif PhysicalLayer* _phy; + AFSKClient* _audio; uint32_t _base; SSTVMode_t _mode;