Skip to content

Commit

Permalink
[SSTV] Added Robot36 and Robot72 modes (#1160)
Browse files Browse the repository at this point in the history
  • Loading branch information
jgromes committed Jul 13, 2024
1 parent d1bfccd commit fb049cc
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 81 deletions.
2 changes: 2 additions & 0 deletions keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ Wrasse KEYWORD1
PasokonP3 KEYWORD1
PasokonP5 KEYWORD1
PasokonP7 KEYWORD1
Robot36 KEYWORD1
Robot72 KEYWORD1

# Bell Modems
Bell101 KEYWORD1
Expand Down
217 changes: 143 additions & 74 deletions src/protocols/SSTV/SSTV.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
};

Expand All @@ -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 }
}
};

Expand All @@ -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 }
}
};

Expand All @@ -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 }
}
};

Expand All @@ -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 }
}
};

Expand All @@ -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 }
}
};

Expand All @@ -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 }
}
};

Expand All @@ -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 }
}
};

Expand All @@ -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 },
}
};

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -247,42 +282,76 @@ 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);
}

// 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 {
Expand Down
20 changes: 13 additions & 7 deletions src/protocols/SSTV/SSTV.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;

/*!
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
};
Expand Down

0 comments on commit fb049cc

Please sign in to comment.