[SSTV] Added SSTV support
This commit is contained in:
parent
75b9395349
commit
c39c4f6b0d
6 changed files with 570 additions and 0 deletions
|
@ -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
|
||||
|
|
160
examples/SSTV/SSTV_Transmit/SSTV_Transmit.ino
Normal file
160
examples/SSTV/SSTV_Transmit/SSTV_Transmit.ino
Normal file
|
@ -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 <RadioLib.h>
|
||||
|
||||
// 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);
|
||||
}
|
19
keywords.txt
19
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)
|
||||
#######################################
|
||||
|
|
|
@ -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"
|
||||
|
|
266
src/protocols/SSTV/SSTV.cpp
Normal file
266
src/protocols/SSTV/SSTV.cpp
Normal file
|
@ -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);
|
||||
}
|
123
src/protocols/SSTV/SSTV.h
Normal file
123
src/protocols/SSTV/SSTV.h
Normal file
|
@ -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
|
Loading…
Add table
Reference in a new issue