From fb049cc3affb61d126af8bc49a16e864093030e2 Mon Sep 17 00:00:00 2001 From: jgromes Date: Sat, 13 Jul 2024 14:53:57 +0100 Subject: [PATCH] [SSTV] Added Robot36 and Robot72 modes (#1160) --- keywords.txt | 2 + src/protocols/SSTV/SSTV.cpp | 217 ++++++++++++++++++++++++------------ src/protocols/SSTV/SSTV.h | 20 ++-- 3 files changed, 158 insertions(+), 81 deletions(-) diff --git a/keywords.txt b/keywords.txt index dab4fb051..44d2487f3 100644 --- a/keywords.txt +++ b/keywords.txt @@ -74,6 +74,8 @@ Wrasse KEYWORD1 PasokonP3 KEYWORD1 PasokonP5 KEYWORD1 PasokonP7 KEYWORD1 +Robot36 KEYWORD1 +Robot72 KEYWORD1 # Bell Modems Bell101 KEYWORD1 diff --git a/src/protocols/SSTV/SSTV.cpp b/src/protocols/SSTV/SSTV.cpp index 9d31e5979..3f72a3b23 100644 --- a/src/protocols/SSTV/SSTV.cpp +++ b/src/protocols/SSTV/SSTV.cpp @@ -8,13 +8,13 @@ const SSTVMode_t Scottie1 { .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 } + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .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_CR, .len = 0, .freq = 0 } } }; @@ -25,13 +25,13 @@ const SSTVMode_t Scottie2 { .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 } + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .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_CR, .len = 0, .freq = 0 } } }; @@ -42,13 +42,13 @@ const SSTVMode_t ScottieDX { .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 } + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .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_CR, .len = 0, .freq = 0 } } }; @@ -59,14 +59,14 @@ const SSTVMode_t Martin1 { .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 } + { .type = tone_t::GENERIC, .len = 4862, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 } } }; @@ -77,14 +77,14 @@ const SSTVMode_t Martin2 { .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 } + { .type = tone_t::GENERIC, .len = 4862, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 } } }; @@ -95,11 +95,11 @@ const SSTVMode_t Wrasse { .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 } + { .type = tone_t::GENERIC, .len = 5523, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 500, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 } } }; @@ -110,13 +110,13 @@ const SSTVMode_t PasokonP3 { .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 } + { .type = tone_t::GENERIC, .len = 5208, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1042, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1042, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1042, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 } } }; @@ -127,13 +127,13 @@ const SSTVMode_t PasokonP5 { .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 } + { .type = tone_t::GENERIC, .len = 7813, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1563, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1563, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1563, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 } } }; @@ -144,13 +144,48 @@ const SSTVMode_t PasokonP7 { .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 } + { .type = tone_t::GENERIC, .len = 10417, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 2083, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 2083, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 2083, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t Robot36 { + .visCode = RADIOLIB_SSTV_ROBOT_36, + .width = 320, + .height = 240, + .scanPixelLen = 275, // this is the Y-scan length, Cb/Cr are one half + .numTones = 6, + .tones = { + { .type = tone_t::GENERIC, .len = 9000, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 3000, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 4500, .freq = 1500 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1900 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, // on even lines, this is the Cr component + } +}; + +const SSTVMode_t Robot72 { + .visCode = RADIOLIB_SSTV_ROBOT_72, + .width = 320, + .height = 240, + .scanPixelLen = 431, // this is the Y-scan length, Cb/Cr are one half + .numTones = 9, + .tones = { + { .type = tone_t::GENERIC, .len = 9000, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 3000, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 4500, .freq = 1500 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1900 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 4500, .freq = 2300 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, } }; @@ -210,8 +245,8 @@ void SSTVClient::idle() { } void SSTVClient::sendHeader() { - // save first header flag for Scottie modes - firstLine = true; + // reset line counter + lineCount = 0; phyLayer->transmitDirect(); // send the first part of header (leader-break-leader) @@ -247,10 +282,8 @@ void SSTVClient::sendHeader() { } void SSTVClient::sendLine(const uint32_t* imgLine) { - // check first line flag in Scottie modes - if(firstLine && ((txMode.visCode == RADIOLIB_SSTV_SCOTTIE_1) || (txMode.visCode == RADIOLIB_SSTV_SCOTTIE_2) || (txMode.visCode == RADIOLIB_SSTV_SCOTTIE_DX))) { - firstLine = false; - + // check first line in Scottie modes + if((lineCount == 0) && ((txMode.visCode == RADIOLIB_SSTV_SCOTTIE_1) || (txMode.visCode == RADIOLIB_SSTV_SCOTTIE_2) || (txMode.visCode == RADIOLIB_SSTV_SCOTTIE_DX))) { // send start sync tone this->tone(RADIOLIB_SSTV_TONE_BREAK, 9000); } @@ -258,31 +291,67 @@ void SSTVClient::sendLine(const uint32_t* imgLine) { // send all tones in sequence for(uint8_t i = 0; i < txMode.numTones; i++) { if((txMode.tones[i].type == tone_t::GENERIC) && (txMode.tones[i].len > 0)) { + // Robot36 has different separator tones for even and odd lines + uint32_t freq = txMode.tones[i].freq; + if((txMode.visCode == RADIOLIB_SSTV_ROBOT_36) && (i == 3)) { + freq = (lineCount % 2) ? 2300 : txMode.tones[3].freq; + } + // sync/porch tones - this->tone(txMode.tones[i].freq, txMode.tones[i].len); + this->tone(freq, txMode.tones[i].len); + } else { // scan lines for(uint16_t j = 0; j < txMode.width; j++) { uint32_t color = imgLine[j]; + uint32_t len = txMode.scanPixelLen; + + // Robot modes work in YCbCr + if((txMode.visCode == RADIOLIB_SSTV_ROBOT_36) || (txMode.visCode == RADIOLIB_SSTV_ROBOT_72)) { + uint8_t r = (color & 0x00FF0000) >> 16; + uint8_t g = (color & 0x0000FF00) >> 8; + uint8_t b = (color & 0x000000FF); + uint8_t y = 16.0 + (0.003906 * ((65.738 * r) + (129.057 * g) + (25.064 * b))); + uint8_t cb = 128.0 + (0.003906 * ((-37.945 * r) + (-74.494 * g) + (112.439 * b))); + uint8_t cr = 128.0 + (0.003906 * ((112.439 * r) + (-94.154 * g) + (-18.285 * b))); + color = ((uint32_t)y << 8); + if(txMode.visCode == RADIOLIB_SSTV_ROBOT_36) { + // odd lines carry Cb, even lines carry Cr + color |= (lineCount % 2) ? cb : cr; + } else { + color |= ((uint32_t)cr << 16) | cb; + } + + } + switch(txMode.tones[i].type) { - case(tone_t::SCAN_RED): + case(tone_t::SCAN_RED_CR): color &= 0x00FF0000; color >>= 16; + if((txMode.visCode == RADIOLIB_SSTV_ROBOT_36) || (txMode.visCode == RADIOLIB_SSTV_ROBOT_72)) { + len /= 2; + } break; - case(tone_t::SCAN_GREEN): + case(tone_t::SCAN_GREEN_Y): color &= 0x0000FF00; color >>= 8; break; - case(tone_t::SCAN_BLUE): + case(tone_t::SCAN_BLUE_CB): color &= 0x000000FF; + if((txMode.visCode == RADIOLIB_SSTV_ROBOT_36) || (txMode.visCode == RADIOLIB_SSTV_ROBOT_72)) { + len /= 2; + } break; case(tone_t::GENERIC): break; } - this->tone(RADIOLIB_SSTV_TONE_BRIGHTNESS_MIN + ((float)color * 3.1372549), txMode.scanPixelLen); + this->tone(RADIOLIB_SSTV_TONE_BRIGHTNESS_MIN + ((float)color * 3.1372549), len); } } } + + // increment line counter (needed for Robot36 mode) + lineCount++; } uint16_t SSTVClient::getPictureHeight() const { diff --git a/src/protocols/SSTV/SSTV.h b/src/protocols/SSTV/SSTV.h index 7c0feacd2..3ec493a4e 100644 --- a/src/protocols/SSTV/SSTV.h +++ b/src/protocols/SSTV/SSTV.h @@ -21,6 +21,8 @@ #define RADIOLIB_SSTV_PASOKON_P3 113 #define RADIOLIB_SSTV_PASOKON_P5 114 #define RADIOLIB_SSTV_PASOKON_P7 115 +#define RADIOLIB_SSTV_ROBOT_36 8 +#define RADIOLIB_SSTV_ROBOT_72 12 // SSTV tones in Hz #define RADIOLIB_SSTV_TONE_LEADER 1900 @@ -46,9 +48,9 @@ struct tone_t { */ enum { GENERIC = 0, - SCAN_GREEN, - SCAN_BLUE, - SCAN_RED + SCAN_GREEN_Y, + SCAN_BLUE_CB, + SCAN_RED_CR } type; /*! @@ -96,7 +98,7 @@ struct SSTVMode_t { /*! \brief Sequence of tones in each transmission line. This is used to create the correct encoding sequence. */ - tone_t tones[8]; + tone_t tones[9]; }; // all currently supported SSTV modes @@ -109,6 +111,8 @@ extern const SSTVMode_t Wrasse; extern const SSTVMode_t PasokonP3; extern const SSTVMode_t PasokonP5; extern const SSTVMode_t PasokonP7; +extern const SSTVMode_t Robot36; +extern const SSTVMode_t Robot72; /*! \class SSTVClient @@ -136,7 +140,8 @@ class SSTVClient { \brief Initialization method for 2-FSK. \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. + ScottieDX, Martin1, Martin2, Wrasse, PasokonP3, PasokonP5 and PasokonP7, + Robot36 and Robot37. \returns \ref status_codes */ int16_t begin(float base, const SSTVMode_t& mode); @@ -145,7 +150,8 @@ class SSTVClient { /*! \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. + ScottieDX, Martin1, Martin2, Wrasse, PasokonP3, PasokonP5 and PasokonP7, + Robot36 and Robot37. \returns \ref status_codes */ int16_t begin(const SSTVMode_t& mode); @@ -192,7 +198,7 @@ class SSTVClient { uint32_t baseFreq = 0; SSTVMode_t txMode = Scottie1; - bool firstLine = true; + uint32_t lineCount = 0; void tone(float freq, RadioLibTime_t len = 0); };