From c39c4f6b0d8ecfda140dd413cc656ee29f54ff9c Mon Sep 17 00:00:00 2001 From: jgromes Date: Tue, 31 Mar 2020 17:31:10 +0200 Subject: [PATCH] [SSTV] Added SSTV support --- README.md | 1 + examples/SSTV/SSTV_Transmit/SSTV_Transmit.ino | 160 +++++++++++ keywords.txt | 19 ++ src/RadioLib.h | 1 + src/protocols/SSTV/SSTV.cpp | 266 ++++++++++++++++++ src/protocols/SSTV/SSTV.h | 123 ++++++++ 6 files changed, 570 insertions(+) create mode 100644 examples/SSTV/SSTV_Transmit/SSTV_Transmit.ino create mode 100644 src/protocols/SSTV/SSTV.cpp create mode 100644 src/protocols/SSTV/SSTV.h diff --git a/README.md b/README.md index 0a789d21..9f5e2dc0 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ RadioLib was originally created as a driver for [__RadioShield__](https://github * __RTTY__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x and Si443x * __Morse Code__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x and Si443x * __AX.25__ for modules: SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, RFM2x and Si443x +* __SSTV__ for modules: SX127x, RFM9x, SX126x, RF69 and SX1231 ### Supported platforms: * __Arduino AVR__ - tested with hardware on Uno, Mega and Leonardo diff --git a/examples/SSTV/SSTV_Transmit/SSTV_Transmit.ino b/examples/SSTV/SSTV_Transmit/SSTV_Transmit.ino new file mode 100644 index 00000000..92bdde6f --- /dev/null +++ b/examples/SSTV/SSTV_Transmit/SSTV_Transmit.ino @@ -0,0 +1,160 @@ +/* + RadioLib SSTV Transmit Example + + The following example sends SSTV picture using + SX1278's FSK modem. + + Other modules that can be used for SSTV: + - SX127x/RFM9x + - RF69 + - SX1231 + - SX126x + + NOTE: SSTV is an analog modulation, and + requires precise frequency control. + Some of the above modules can only + set their frequency in rough steps, + so the result can be distorted. + Using high-precision radio with TCXO + (like SX126x) is recommended. + + 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 SSTV client instance using the FSK module +SSTVClient sstv(&fsk); + +// 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 + // sync word: 0x2D 0x01 + 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 ... ")); + // 0 Hz tone frequency: 434.0 MHz + // 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(434.0, 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 beep at the frequency corresponding to + // 1900 Hz in upper sideband (aka USB) modulation + // (SSTV header "leader tone") + /* + 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/keywords.txt b/keywords.txt index bcf0f9fd..57b8b822 100644 --- a/keywords.txt +++ b/keywords.txt @@ -10,6 +10,7 @@ RadioLib KEYWORD1 RadioShield KEYWORD1 Module KEYWORD1 +# modules CC1101 KEYWORD1 ESP8266 KEYWORD1 HC05 KEYWORD1 @@ -39,6 +40,7 @@ SX1279 KEYWORD1 XBee KEYWORD1 XBeeSerial KEYWORD1 +# protocols MQTTClient KEYWORD1 HTTPClient KEYWORD1 RTTYClient KEYWORD1 @@ -46,6 +48,18 @@ MorseClient KEYWORD1 PagerClient KEYWORD1 AX25Client KEYWORD1 AX25Frame KEYWORD1 +SSTVClient KEYWORD1 + +# SSTV modes +Scottie1 KEYWORD1 +Scottie2 KEYWORD1 +ScottieDX KEYWORD1 +Martin1 KEYWORD1 +Martin2 KEYWORD1 +Wrasse KEYWORD1 +PasokonP3 KEYWORD1 +PasokonP5 KEYWORD1 +PasokonP7 KEYWORD1 ####################################### # Methods and Functions (KEYWORD2) @@ -190,6 +204,11 @@ setRecvSequence KEYWORD2 setSendSequence KEYWORD2 sendFrame KEYWORD2 +# SSTV +sendHeader KEYWORD2 +sendLine KEYWORD2 +getPictureHeight KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### diff --git a/src/RadioLib.h b/src/RadioLib.h index 16947fbd..bb5e085e 100644 --- a/src/RadioLib.h +++ b/src/RadioLib.h @@ -75,6 +75,7 @@ #include "protocols/AX25/AX25.h" #include "protocols/Morse/Morse.h" #include "protocols/RTTY/RTTY.h" +#include "protocols/SSTV/SSTV.h" // transport layer protocols #include "protocols/TransportLayer/TransportLayer.h" diff --git a/src/protocols/SSTV/SSTV.cpp b/src/protocols/SSTV/SSTV.cpp new file mode 100644 index 00000000..dfbeea30 --- /dev/null +++ b/src/protocols/SSTV/SSTV.cpp @@ -0,0 +1,266 @@ +#include "SSTV.h" + +const SSTVMode_t Scottie1 { + .visCode = SSTV_SCOTTIE_1, + .width = 320, + .height = 256, + .scanPixelLen = 432, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 9000, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_RED, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t Scottie2 { + .visCode = SSTV_SCOTTIE_2, + .width = 320, + .height = 256, + .scanPixelLen = 275, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 9000, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_RED, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t ScottieDX { + .visCode = SSTV_SCOTTIE_DX, + .width = 320, + .height = 256, + .scanPixelLen = 1080, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 9000, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_RED, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t Martin1 { + .visCode = SSTV_MARTIN_1, + .width = 320, + .height = 256, + .scanPixelLen = 458, + .numTones = 8, + .tones = { + { .type = tone_t::GENERIC, .len = 4862, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_RED, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 } + } +}; + +const SSTVMode_t Martin2 { + .visCode = SSTV_MARTIN_2, + .width = 320, + .height = 256, + .scanPixelLen = 229, + .numTones = 8, + .tones = { + { .type = tone_t::GENERIC, .len = 4862, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_RED, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 } + } +}; + +const SSTVMode_t Wrasse { + .visCode = SSTV_WRASSE_SC2_180, + .width = 320, + .height = 256, + .scanPixelLen = 734, + .numTones = 5, + .tones = { + { .type = tone_t::GENERIC, .len = 5523, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 500, .freq = 1500 }, + { .type = tone_t::SCAN_RED, .len = 0, .freq = 0 }, + { .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 }, + { .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t PasokonP3 { + .visCode = SSTV_PASOKON_P3, + .width = 640, + .height = 496, + .scanPixelLen = 208, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 5208, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1042, .freq = 1500 }, + { .type = tone_t::SCAN_RED, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1042, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1042, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t PasokonP5 { + .visCode = SSTV_PASOKON_P5, + .width = 640, + .height = 496, + .scanPixelLen = 312, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 7813, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1563, .freq = 1500 }, + { .type = tone_t::SCAN_RED, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1563, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1563, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t PasokonP7 { + .visCode = SSTV_PASOKON_P7, + .width = 640, + .height = 496, + .scanPixelLen = 417, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 10417, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 2083, .freq = 1500 }, + { .type = tone_t::SCAN_RED, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 2083, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 2083, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE, .len = 0, .freq = 0 } + } +}; + +SSTVClient::SSTVClient(PhysicalLayer* phy) { + _phy = phy; +} + +int16_t SSTVClient::begin(float base, SSTVMode_t mode, float correction) { + // save mode + _mode = mode; + + // apply correction factor to all timings + _mode.scanPixelLen *= correction; + for(uint8_t i = 0; i < _mode.numTones; i++) { + _mode.tones[i].len *= correction; + } + + // calculate 24-bit frequency + _base = (base * 1000000.0) / _phy->getFreqStep(); + + // set module frequency deviation to 0 + int16_t state = _phy->setFrequencyDeviation(0); + + return(state); +} + +void SSTVClient::idle() { + tone(SSTV_TONE_LEADER); +} + +void SSTVClient::sendHeader() { + // save first header flag for Scottie modes + _firstLine = true; + + // send the first part of header (leader-break-leader) + tone(SSTV_TONE_LEADER, SSTV_HEADER_LEADER_LENGTH); + tone(SSTV_TONE_BREAK, SSTV_HEADER_BREAK_LENGTH); + tone(SSTV_TONE_LEADER, SSTV_HEADER_LEADER_LENGTH); + + // VIS start bit + tone(SSTV_TONE_BREAK, SSTV_HEADER_BIT_LENGTH); + + // VIS code + uint8_t parityCount = 0; + for(uint8_t mask = 0x01; mask < 0x80; mask <<= 1) { + if(_mode.visCode & mask) { + tone(SSTV_TONE_VIS_1, SSTV_HEADER_BIT_LENGTH); + parityCount++; + } else { + tone(SSTV_TONE_VIS_0, SSTV_HEADER_BIT_LENGTH); + } + } + + // VIS parity + if(parityCount % 2 == 0) { + // even parity + tone(SSTV_TONE_VIS_0, SSTV_HEADER_BIT_LENGTH); + } else { + // odd parity + tone(SSTV_TONE_VIS_1, SSTV_HEADER_BIT_LENGTH); + } + + // VIS stop bit + tone(SSTV_TONE_BREAK, SSTV_HEADER_BIT_LENGTH); +} + +void SSTVClient::sendLine(uint32_t* imgLine) { + // check first line flag in Scottie modes + if(_firstLine && ((_mode.visCode == SSTV_SCOTTIE_1) || (_mode.visCode == SSTV_SCOTTIE_2) || (_mode.visCode == SSTV_SCOTTIE_DX))) { + _firstLine = false; + + // send start sync tone + tone(SSTV_TONE_BREAK, 9000); + } + + // send all tones in sequence + for(uint8_t i = 0; i < _mode.numTones; i++) { + if((_mode.tones[i].type == tone_t::GENERIC) && (_mode.tones[i].len > 0)) { + // sync/porch tones + tone(_mode.tones[i].freq, _mode.tones[i].len); + } else { + // scan lines + for(uint16_t j = 0; j < _mode.width; j++) { + uint32_t color = imgLine[j]; + switch(_mode.tones[i].type) { + case(tone_t::SCAN_RED): + color &= 0x00FF0000; + color >>= 16; + break; + case(tone_t::SCAN_GREEN): + color &= 0x0000FF00; + color >>= 8; + break; + case(tone_t::SCAN_BLUE): + color &= 0x000000FF; + break; + case(tone_t::GENERIC): + break; + } + tone(SSTV_TONE_BRIGHTNESS_MIN + ((float)color * 3.1372549), _mode.scanPixelLen); + } + } + } +} + +uint16_t SSTVClient::getPictureHeight() { + return(_mode.height); +} + +void SSTVClient::tone(float freq, uint32_t len) { + uint32_t start = micros(); + _phy->transmitDirect(_base + (freq / _phy->getFreqStep())); + while(micros() - start < len); +} diff --git a/src/protocols/SSTV/SSTV.h b/src/protocols/SSTV/SSTV.h new file mode 100644 index 00000000..8242d674 --- /dev/null +++ b/src/protocols/SSTV/SSTV.h @@ -0,0 +1,123 @@ +#ifndef _RADIOLIB_SSTV_H +#define _RADIOLIB_SSTV_H + +#include "../../TypeDef.h" +#include "../PhysicalLayer/PhysicalLayer.h" + +// the following implementation is based on information from +// http://www.barberdsp.com/downloads/Dayton%20Paper.pdf + +// VIS codes +#define SSTV_SCOTTIE_1 60 +#define SSTV_SCOTTIE_2 56 +#define SSTV_SCOTTIE_DX 76 +#define SSTV_MARTIN_1 44 +#define SSTV_MARTIN_2 40 +#define SSTV_WRASSE_SC2_180 55 +#define SSTV_PASOKON_P3 113 +#define SSTV_PASOKON_P5 114 +#define SSTV_PASOKON_P7 115 + +// SSTV tones in Hz +#define SSTV_TONE_LEADER 1900 +#define SSTV_TONE_BREAK 1200 +#define SSTV_TONE_VIS_1 1100 +#define SSTV_TONE_VIS_0 1300 +#define SSTV_TONE_BRIGHTNESS_MIN 1500 +#define SSTV_TONE_BRIGHTNESS_MAX 2300 + +// calibration header timing in us +#define SSTV_HEADER_LEADER_LENGTH 300000 +#define SSTV_HEADER_BREAK_LENGTH 10000 +#define SSTV_HEADER_BIT_LENGTH 30000 + +// structure to save data about tone +struct tone_t { + enum { + GENERIC = 0, + SCAN_GREEN, + SCAN_BLUE, + SCAN_RED + } type; + uint32_t len; + uint16_t freq; +}; + +// structure to save data about SSTV mode +struct SSTVMode_t { + uint8_t visCode; + uint16_t width; + uint16_t height; + uint16_t scanPixelLen; + uint8_t numTones; + tone_t tones[8]; +}; + +// all currently supported SSTV modes +extern const SSTVMode_t Scottie1; +extern const SSTVMode_t Scottie2; +extern const SSTVMode_t ScottieDX; +extern const SSTVMode_t Martin1; +extern const SSTVMode_t Martin2; +extern const SSTVMode_t Wrasse; +extern const SSTVMode_t PasokonP3; +extern const SSTVMode_t PasokonP5; +extern const SSTVMode_t PasokonP7; + +class SSTVClient { + public: + /*! + \brief Default constructor. + + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + SSTVClient(PhysicalLayer* phy); + + // basic methods + + /*! + \brief Initialization method. + + \param base Base RF frequency to be used in MHz. In USB modulation, this corresponds to "0 Hz tone". + + \param mode SSTV mode to be used. Currently supported modes are Scottie1, Scottie2, ScottieDX, Martin1, Martin2, Wrasse, PasokonP3, PasokonP5 and PasokonP7. + */ + int16_t begin(float base, SSTVMode_t mode, float correction = 1.0); + + /*! + \brief Sends out tone at 1900 Hz. + */ + void idle(); + + /*! + \brief Sends synchronization header for the SSTV mode set in begin method. + */ + void sendHeader(); + + /*! + \brief Sends single picture line in the currently configured SSTV mode. + + \param imgLine Image line to send, in 24-bit RGB. It is up to the user to ensure that imgLine has enough pixels to send it in the current SSTV mode. + */ + void sendLine(uint32_t* imgLine); + + /*! + \brief Get picture height of the currently configured SSTV mode. + + \returns Picture height of the currently configured SSTV mode in pixels. + */ + uint16_t getPictureHeight(); + +#ifndef RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* _phy; + + uint32_t _base; + SSTVMode_t _mode; + bool _firstLine; + + void tone(float freq, uint32_t len = 0); +}; + +#endif