mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-01-11 15:29:28 -05:00
SSTV transmit beta (320x256 24bpp Scottie 2 only)
This commit is contained in:
parent
5b74b83458
commit
6a0bcb9cca
@ -91,6 +91,14 @@ void set_tones_data(const uint32_t bw, const uint32_t pre_silence, const uint16_
|
|||||||
send_message(&message);
|
send_message(&message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration) {
|
||||||
|
const SSTVConfigureMessage message {
|
||||||
|
vis_code,
|
||||||
|
pixel_duration
|
||||||
|
};
|
||||||
|
send_message(&message);
|
||||||
|
}
|
||||||
|
|
||||||
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space,
|
void set_afsk_data(const uint32_t afsk_samples_per_bit, const uint32_t afsk_phase_inc_mark, const uint32_t afsk_phase_inc_space,
|
||||||
const uint8_t afsk_repeat, const uint32_t afsk_bw, const bool afsk_alt_format) {
|
const uint8_t afsk_repeat, const uint32_t afsk_bw, const bool afsk_alt_format) {
|
||||||
const AFSKConfigureMessage message {
|
const AFSKConfigureMessage message {
|
||||||
|
@ -57,6 +57,7 @@ struct WFMConfig {
|
|||||||
|
|
||||||
void set_tones_data(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count,
|
void set_tones_data(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count,
|
||||||
const bool dual_tone, const bool audio_out);
|
const bool dual_tone, const bool audio_out);
|
||||||
|
void set_sstv_data(const uint8_t vis_code, const uint32_t pixel_duration);
|
||||||
void set_audiotx_data(const uint32_t divider, const uint32_t bw, const uint32_t gain_x10,
|
void set_audiotx_data(const uint32_t divider, const uint32_t bw, const uint32_t gain_x10,
|
||||||
const bool ctcss_enabled, const uint32_t ctcss_phase_inc);
|
const bool ctcss_enabled, const uint32_t ctcss_phase_inc);
|
||||||
void set_fifo_data(const int8_t * data);
|
void set_fifo_data(const int8_t * data);
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
// Color bitmaps generated with:
|
// Color bitmaps generated with:
|
||||||
// Gimp image > indexed colors (16), then "xxd -i *.bmp"
|
// Gimp image > indexed colors (16), then "xxd -i *.bmp"
|
||||||
|
|
||||||
|
//BUG: Length is wrong in soundboard for long files (> 1min ?)
|
||||||
//BUG: RDS Radiotext is not recognized in Redsea (and car radio)
|
//BUG: RDS Radiotext is not recognized in Redsea (and car radio)
|
||||||
//BUG: RDS doesn't stop baseband when stopping tx ?
|
//BUG: RDS doesn't stop baseband when stopping tx ?
|
||||||
//BUG: Check AFSK transmit end, skips last bits ?
|
//BUG: Check AFSK transmit end, skips last bits ?
|
||||||
|
@ -361,7 +361,7 @@ SystemMenuView::SystemMenuView(NavigationView& nav) {
|
|||||||
{ "Replay", ui::Color::grey(), &bitmap_icon_replay, [&nav](){ nav.push<NotImplementedView>(); } },
|
{ "Replay", ui::Color::grey(), &bitmap_icon_replay, [&nav](){ nav.push<NotImplementedView>(); } },
|
||||||
{ "Audio transmitters", ui::Color::green(), &bitmap_icon_audiotx, [&nav](){ nav.push<TransmitterAudioMenuView>(); } },
|
{ "Audio transmitters", ui::Color::green(), &bitmap_icon_audiotx, [&nav](){ nav.push<TransmitterAudioMenuView>(); } },
|
||||||
{ "Code transmitters", ui::Color::green(), &bitmap_icon_codetx, [&nav](){ nav.push<TransmitterCodedMenuView>(); } },
|
{ "Code transmitters", ui::Color::green(), &bitmap_icon_codetx, [&nav](){ nav.push<TransmitterCodedMenuView>(); } },
|
||||||
{ "SSTV transmitter", ui::Color::grey(), &bitmap_icon_sstv, [&nav](){ nav.push<SSTVTXView>(); } },
|
{ "SSTV transmitter", ui::Color::dark_orange(), &bitmap_icon_sstv, [&nav](){ nav.push<SSTVTXView>(); } },
|
||||||
{ "Close Call", ui::Color::cyan(), &bitmap_icon_closecall, [&nav](){ nav.push<CloseCallView>(); } },
|
{ "Close Call", ui::Color::cyan(), &bitmap_icon_closecall, [&nav](){ nav.push<CloseCallView>(); } },
|
||||||
{ "Jammer", ui::Color::orange(),&bitmap_icon_jammer, [&nav](){ nav.push<JammerView>(); } },
|
{ "Jammer", ui::Color::orange(),&bitmap_icon_jammer, [&nav](){ nav.push<JammerView>(); } },
|
||||||
{ "Utilities", ui::Color::purple(),&bitmap_icon_utilities, [&nav](){ nav.push<UtilitiesView>(); } },
|
{ "Utilities", ui::Color::purple(),&bitmap_icon_utilities, [&nav](){ nav.push<UtilitiesView>(); } },
|
||||||
|
@ -68,7 +68,6 @@ void SSTVTXView::paint(Painter&) {
|
|||||||
ui::Color line_buffer[160];
|
ui::Color line_buffer[160];
|
||||||
Coord line;
|
Coord line;
|
||||||
uint32_t data_idx, bmp_px, pixel_idx;
|
uint32_t data_idx, bmp_px, pixel_idx;
|
||||||
uint8_t pixels_buffer[320 * 3]; // 320 pixels @ 24bpp
|
|
||||||
|
|
||||||
data_idx = bmp_header.image_data;
|
data_idx = bmp_header.image_data;
|
||||||
|
|
||||||
@ -97,28 +96,22 @@ void SSTVTXView::on_tuning_frequency_changed(rf::Frequency f) {
|
|||||||
transmitter_model.set_tuning_frequency(f);
|
transmitter_model.set_tuning_frequency(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SSTVTXView::start_tx() {
|
void SSTVTXView::prepare_scanline() {
|
||||||
// Baseband SSTV TX code should have a 2 scanlines buffer, and ask
|
sstv_scanline scanline_buffer;
|
||||||
// for fill-up when there's 1 or less remaining. This should leave
|
uint32_t component, pixel_idx;
|
||||||
// enough time for the code here to generate the scanline data
|
|
||||||
// before tx.
|
|
||||||
|
|
||||||
const uint8_t VIS_code = 0b00011101; // Scottie 2
|
if (scanline_counter >= (256 * 3)) {
|
||||||
|
progressbar.set_value(0);
|
||||||
|
transmitter_model.disable();
|
||||||
|
options_bitmaps.set_focusable(true);
|
||||||
|
tx_view.set_transmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Calibration:
|
progressbar.set_value(scanline_counter);
|
||||||
// 1900 300ms
|
|
||||||
// 1200 10ms
|
// Scottie 2 scanline:
|
||||||
// 1900 300ms
|
// (First line: 1200 9ms)
|
||||||
// VIS: (30ms * 10 = 300ms)
|
|
||||||
// 1200
|
|
||||||
// 00011101 (0=1300, 1=1100)
|
|
||||||
// 1200
|
|
||||||
|
|
||||||
// Scottie 2: 320x256 px
|
|
||||||
|
|
||||||
// V-sync ?
|
|
||||||
// First line: 1200 9ms
|
|
||||||
// Scanline:
|
|
||||||
// 1500 1.5ms
|
// 1500 1.5ms
|
||||||
// Green
|
// Green
|
||||||
// 1500 1.5ms
|
// 1500 1.5ms
|
||||||
@ -126,27 +119,71 @@ void SSTVTXView::start_tx() {
|
|||||||
// 1200 9ms
|
// 1200 9ms
|
||||||
// 1500 1.5ms
|
// 1500 1.5ms
|
||||||
// Red
|
// Red
|
||||||
// Scanline time: 88.064ms (.2752ms/pixel @ 320 pixels/line)
|
// Scanline time: 88.064ms (275.2us/pixel @ 320 pixels/line)
|
||||||
|
|
||||||
transmitter_model.set_sampling_rate(1536000U);
|
component = scanline_counter % 3;
|
||||||
|
|
||||||
|
if ((!scanline_counter) || (component == 2)) {
|
||||||
|
scanline_buffer.start_tone.frequency = SSTV_F2D(1200);
|
||||||
|
scanline_buffer.start_tone.duration = SSTV_MS2S(9);
|
||||||
|
} else
|
||||||
|
scanline_buffer.start_tone.duration = 0;
|
||||||
|
|
||||||
|
scanline_buffer.gap_tone.frequency = SSTV_F2D(1500);
|
||||||
|
scanline_buffer.gap_tone.duration = SSTV_MS2S(1.5);
|
||||||
|
|
||||||
|
if (component == 0) {
|
||||||
|
// Read a new line
|
||||||
|
read_boundary(pixels_buffer,
|
||||||
|
bmp_header.image_data + ((255 - (scanline_counter / 3)) * sizeof(pixels_buffer)),
|
||||||
|
sizeof(pixels_buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t bmp_px = 0; bmp_px < 320; bmp_px++) {
|
||||||
|
pixel_idx = bmp_px * 3;
|
||||||
|
if (component == 0)
|
||||||
|
scanline_buffer.luma[bmp_px] = pixels_buffer[pixel_idx + 1]; // Green
|
||||||
|
else if (component == 1)
|
||||||
|
scanline_buffer.luma[bmp_px] = pixels_buffer[pixel_idx]; // Blue
|
||||||
|
else
|
||||||
|
scanline_buffer.luma[bmp_px] = pixels_buffer[pixel_idx + 2]; // Red
|
||||||
|
}
|
||||||
|
|
||||||
|
baseband::set_fifo_data((int8_t *)&scanline_buffer);
|
||||||
|
|
||||||
|
scanline_counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSTVTXView::start_tx() {
|
||||||
|
// Baseband SSTV TX code should have a 2 scanlines buffer, and ask
|
||||||
|
// for fill-up when there's 1 or less remaining. This should leave
|
||||||
|
// enough time for the code here to generate the scanline data
|
||||||
|
// before tx. See sstv.hpp:
|
||||||
|
|
||||||
|
// Scottie 2 is 320x256 px
|
||||||
|
|
||||||
|
scanline_counter = 0;
|
||||||
|
prepare_scanline();
|
||||||
|
|
||||||
|
transmitter_model.set_sampling_rate(3072000U);
|
||||||
transmitter_model.set_rf_amp(true);
|
transmitter_model.set_rf_amp(true);
|
||||||
//transmitter_model.set_lna(40);
|
|
||||||
//transmitter_model.set_vga(40);
|
|
||||||
transmitter_model.set_baseband_bandwidth(1750000);
|
transmitter_model.set_baseband_bandwidth(1750000);
|
||||||
transmitter_model.enable();
|
transmitter_model.enable();
|
||||||
|
|
||||||
/*baseband::set_sstvtx_data(
|
baseband::set_sstv_data(
|
||||||
(1536000 / 44100) - 1,
|
0b00011101, // Scottie 2, 275.2us/px
|
||||||
12000,
|
(uint32_t)(0.0002752 * 3072000.0)
|
||||||
1,
|
);
|
||||||
false,
|
|
||||||
0
|
// Todo: Find a better way to prevent user from changing bitmap during tx
|
||||||
);*/
|
options_bitmaps.set_focusable(false);
|
||||||
|
tx_view.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SSTVTXView::on_bitmap_changed(size_t index) {
|
void SSTVTXView::on_bitmap_changed(size_t index) {
|
||||||
bmp_file.open("/sstv/" + bitmaps[index].string());
|
bmp_file.open("/sstv/" + bitmaps[index].string());
|
||||||
bmp_file.read(&bmp_header, sizeof(bmp_header));
|
bmp_file.read(&bmp_header, sizeof(bmp_header));
|
||||||
|
progressbar.set_max(256 * 3);
|
||||||
set_dirty();
|
set_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,7 +195,6 @@ SSTVTXView::SSTVTXView(
|
|||||||
using option_t = std::pair<std::string, int32_t>;
|
using option_t = std::pair<std::string, int32_t>;
|
||||||
using options_t = std::vector<option_t>;
|
using options_t = std::vector<option_t>;
|
||||||
options_t bitmap_options;
|
options_t bitmap_options;
|
||||||
uint32_t c;
|
|
||||||
|
|
||||||
// Search for valid bitmaps
|
// Search for valid bitmaps
|
||||||
file_list = scan_root_files(u"/sstv", u"*.bmp");
|
file_list = scan_root_files(u"/sstv", u"*.bmp");
|
||||||
@ -184,12 +220,16 @@ SSTVTXView::SSTVTXView(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//baseband::run_image(portapack::spi_flash::image_tag_sstv_tx);
|
// Maybe this could be merged with proc_tones ? Pretty much the same except lots
|
||||||
|
// of different tones (256+)
|
||||||
|
baseband::run_image(portapack::spi_flash::image_tag_sstv_tx);
|
||||||
|
|
||||||
add_children({
|
add_children({
|
||||||
&labels,
|
&labels,
|
||||||
&options_bitmaps,
|
&options_bitmaps,
|
||||||
&text_mode
|
&text_mode,
|
||||||
|
&progressbar,
|
||||||
|
&tx_view
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const auto& bitmap : bitmaps)
|
for (const auto& bitmap : bitmaps)
|
||||||
@ -200,6 +240,26 @@ SSTVTXView::SSTVTXView(
|
|||||||
this->on_bitmap_changed(i);
|
this->on_bitmap_changed(i);
|
||||||
};
|
};
|
||||||
options_bitmaps.set_selected_index(0);
|
options_bitmaps.set_selected_index(0);
|
||||||
|
on_bitmap_changed(0);
|
||||||
|
|
||||||
|
tx_view.on_edit_frequency = [this, &nav]() {
|
||||||
|
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
|
||||||
|
new_view->on_changed = [this](rf::Frequency f) {
|
||||||
|
receiver_model.set_tuning_frequency(f);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
tx_view.on_start = [this]() {
|
||||||
|
start_tx();
|
||||||
|
tx_view.set_transmitting(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
tx_view.on_stop = [this]() {
|
||||||
|
baseband::set_sstv_data(0, 0);
|
||||||
|
tx_view.set_transmitting(false);
|
||||||
|
transmitter_model.disable();
|
||||||
|
options_bitmaps.set_focusable(true);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
@ -47,20 +47,25 @@ public:
|
|||||||
void focus() override;
|
void focus() override;
|
||||||
void paint(Painter&) override;
|
void paint(Painter&) override;
|
||||||
|
|
||||||
std::string title() const override { return "SSTV transmit"; };
|
std::string title() const override { return "SSTV TX (beta)"; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
|
|
||||||
|
sstv_scanline scanline_buffer { };
|
||||||
|
|
||||||
File bmp_file { };
|
File bmp_file { };
|
||||||
bmp_header_t bmp_header { };
|
bmp_header_t bmp_header { };
|
||||||
std::vector<std::filesystem::path> bitmaps { };
|
std::vector<std::filesystem::path> bitmaps { };
|
||||||
bool file_error { false };
|
bool file_error { false };
|
||||||
|
uint32_t scanline_counter { 0 };
|
||||||
|
uint8_t pixels_buffer[320 * 3]; // 320 pixels @ 24bpp
|
||||||
|
|
||||||
void read_boundary(uint8_t * buffer, uint32_t position, uint32_t length);
|
void read_boundary(uint8_t * buffer, uint32_t position, uint32_t length);
|
||||||
void on_bitmap_changed(size_t index);
|
void on_bitmap_changed(size_t index);
|
||||||
void on_tuning_frequency_changed(rf::Frequency f);
|
void on_tuning_frequency_changed(rf::Frequency f);
|
||||||
void start_tx();
|
void start_tx();
|
||||||
|
void prepare_scanline();
|
||||||
|
|
||||||
Labels labels {
|
Labels labels {
|
||||||
{ { 1 * 8, 1 * 8 }, "File:", Color::light_grey() }
|
{ { 1 * 8, 1 * 8 }, "File:", Color::light_grey() }
|
||||||
@ -73,7 +78,7 @@ private:
|
|||||||
};
|
};
|
||||||
Text text_mode {
|
Text text_mode {
|
||||||
{ 2 * 8, 4 * 8, 16 * 8, 16 },
|
{ 2 * 8, 4 * 8, 16 * 8, 16 },
|
||||||
"Scottie 2 (beta)"
|
"Scottie 2"
|
||||||
};
|
};
|
||||||
|
|
||||||
ProgressBar progressbar {
|
ProgressBar progressbar {
|
||||||
@ -86,15 +91,15 @@ private:
|
|||||||
12
|
12
|
||||||
};
|
};
|
||||||
|
|
||||||
/*MessageHandlerRegistration message_handler_fifo_signal {
|
MessageHandlerRegistration message_handler_fifo_signal {
|
||||||
Message::ID::RequestSignal,
|
Message::ID::RequestSignal,
|
||||||
[this](const Message* const p) {
|
[this](const Message* const p) {
|
||||||
const auto message = static_cast<const RequestSignalMessage*>(p);
|
const auto message = static_cast<const RequestSignalMessage*>(p);
|
||||||
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
|
||||||
this->prepare_audio();
|
this->prepare_scanline();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};*/
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
@ -387,6 +387,13 @@ set(MODE_CPPSRC
|
|||||||
)
|
)
|
||||||
DeclareTargets(PREP replay)
|
DeclareTargets(PREP replay)
|
||||||
|
|
||||||
|
### SSTV TX
|
||||||
|
|
||||||
|
set(MODE_CPPSRC
|
||||||
|
proc_sstvtx.cpp
|
||||||
|
)
|
||||||
|
DeclareTargets(PSTX sstvtx)
|
||||||
|
|
||||||
### Tones
|
### Tones
|
||||||
|
|
||||||
set(MODE_CPPSRC
|
set(MODE_CPPSRC
|
||||||
|
166
firmware/baseband/proc_sstvtx.cpp
Normal file
166
firmware/baseband/proc_sstvtx.cpp
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
* Copyright (C) 2016 Furrtek
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "proc_sstvtx.hpp"
|
||||||
|
#include "sine_table_int8.hpp"
|
||||||
|
#include "event_m4.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// This is called at 3072000/2048 = 1500Hz
|
||||||
|
void SSTVTXProcessor::execute(const buffer_c8_t& buffer) {
|
||||||
|
|
||||||
|
if (!configured) return;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < buffer.count; i++) {
|
||||||
|
|
||||||
|
if (!sample_count) {
|
||||||
|
|
||||||
|
// This FSM is a mess. It seems to do a lot where it shouldn't (I/Q loop),
|
||||||
|
// but it actually doesn't do much. Used for sequencing the different parts
|
||||||
|
// of the scanline. Todo: simplify !
|
||||||
|
|
||||||
|
if (state == STATE_CALIBRATION) {
|
||||||
|
// Once per scanline
|
||||||
|
tone_delta = calibration_sequence[substep].first;
|
||||||
|
sample_count = calibration_sequence[substep].second;
|
||||||
|
if (substep == 2) {
|
||||||
|
substep = 0;
|
||||||
|
state = STATE_VIS;
|
||||||
|
} else
|
||||||
|
substep++;
|
||||||
|
} else if (state == STATE_VIS) {
|
||||||
|
// Once per scanline
|
||||||
|
if (substep == 10) {
|
||||||
|
current_scanline = &scanline_buffer[buffer_flip];
|
||||||
|
buffer_flip ^= 1;
|
||||||
|
if (asked == false) {
|
||||||
|
// Ask application for a new scanline
|
||||||
|
shared_memory.application_queue.push(sig_message);
|
||||||
|
asked = true;
|
||||||
|
}
|
||||||
|
if (current_scanline->start_tone.duration) {
|
||||||
|
state = STATE_START;
|
||||||
|
tone_delta = current_scanline->start_tone.frequency;
|
||||||
|
sample_count = current_scanline->start_tone.duration;
|
||||||
|
} else {
|
||||||
|
state = STATE_PIXELS;
|
||||||
|
tone_delta = current_scanline->gap_tone.frequency;
|
||||||
|
sample_count = current_scanline->gap_tone.duration;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tone_delta = vis_code_sequence[substep];
|
||||||
|
sample_count = SSTV_MS2S(30); // VIS code bit is 30ms
|
||||||
|
substep++;
|
||||||
|
}
|
||||||
|
} else if (state == STATE_START) {
|
||||||
|
// Once per scanline, optional
|
||||||
|
state = STATE_PIXELS;
|
||||||
|
tone_delta = current_scanline->gap_tone.frequency;
|
||||||
|
sample_count = current_scanline->gap_tone.duration;
|
||||||
|
} else if (state == STATE_PIXELS) {
|
||||||
|
// Many times per scanline
|
||||||
|
pixel_luma = current_scanline->luma[pixel_index];
|
||||||
|
|
||||||
|
pixel_index++;
|
||||||
|
|
||||||
|
tone_delta = SSTV_F2D(1500 + ((pixel_luma * 800) / 256));
|
||||||
|
|
||||||
|
sample_count = pixel_duration;
|
||||||
|
|
||||||
|
if (pixel_index >= 320) {
|
||||||
|
// Scanline done, (dirty) state jump
|
||||||
|
pixel_index = 0;
|
||||||
|
state = STATE_VIS;
|
||||||
|
substep = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sample_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
tone_sample = (sine_table_i8[(tone_phase & 0xFF000000U) >> 24]);
|
||||||
|
tone_phase += tone_delta;
|
||||||
|
|
||||||
|
// FM
|
||||||
|
delta = tone_sample * fm_delta;
|
||||||
|
|
||||||
|
phase += delta;
|
||||||
|
sphase = phase + (64 << 24);
|
||||||
|
|
||||||
|
re = (sine_table_i8[(sphase & 0xFF000000U) >> 24]);
|
||||||
|
im = (sine_table_i8[(phase & 0xFF000000U) >> 24]);
|
||||||
|
|
||||||
|
buffer.p[i] = {re, im};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SSTVTXProcessor::on_message(const Message* const msg) {
|
||||||
|
const auto message = *reinterpret_cast<const SSTVConfigureMessage*>(msg);
|
||||||
|
|
||||||
|
switch(msg->id) {
|
||||||
|
case Message::ID::SSTVConfigure:
|
||||||
|
pixel_duration = message.pixel_duration;
|
||||||
|
|
||||||
|
if (!pixel_duration) {
|
||||||
|
configured = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vis_code = message.vis_code;
|
||||||
|
|
||||||
|
// VIS code:
|
||||||
|
// 1200
|
||||||
|
// 00011101 (0=1300, 1=1100)
|
||||||
|
// 1200
|
||||||
|
vis_code_sequence[0] = SSTV_VIS_SS;
|
||||||
|
for (uint32_t c = 0; c < 8; c++) {
|
||||||
|
vis_code_sequence[c + 1] = ((vis_code << c) & 0x80) ? SSTV_VIS_ONE : SSTV_VIS_ZERO;
|
||||||
|
}
|
||||||
|
vis_code_sequence[9] = SSTV_VIS_SS;
|
||||||
|
|
||||||
|
fm_delta = 9000 * (0xFFFFFFULL / 3072000); // Fixed for now
|
||||||
|
|
||||||
|
pixel_index = 0;
|
||||||
|
sample_count = 0;
|
||||||
|
tone_phase = 0;
|
||||||
|
state = STATE_CALIBRATION;
|
||||||
|
substep = 0;
|
||||||
|
|
||||||
|
configured = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Message::ID::FIFOData:
|
||||||
|
memcpy(&scanline_buffer[buffer_flip], static_cast<const FIFODataMessage*>(msg)->data, sizeof(sstv_scanline));
|
||||||
|
asked = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
EventDispatcher event_dispatcher { std::make_unique<SSTVTXProcessor>() };
|
||||||
|
event_dispatcher.run();
|
||||||
|
return 0;
|
||||||
|
}
|
87
firmware/baseband/proc_sstvtx.hpp
Normal file
87
firmware/baseband/proc_sstvtx.hpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
* Copyright (C) 2016 Furrtek
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __PROC_SSTV_TX_H__
|
||||||
|
#define __PROC_SSTV_TX_H__
|
||||||
|
|
||||||
|
#include "portapack_shared_memory.hpp"
|
||||||
|
#include "baseband_processor.hpp"
|
||||||
|
#include "baseband_thread.hpp"
|
||||||
|
#include "sstv.hpp"
|
||||||
|
|
||||||
|
using namespace sstv;
|
||||||
|
|
||||||
|
class SSTVTXProcessor : public BasebandProcessor {
|
||||||
|
public:
|
||||||
|
void execute(const buffer_c8_t& buffer) override;
|
||||||
|
|
||||||
|
void on_message(const Message* const p) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// 1900 300ms
|
||||||
|
// 1200 10ms
|
||||||
|
// 1900 300ms
|
||||||
|
const std::pair<uint32_t, uint32_t> calibration_sequence[3] = {
|
||||||
|
{ SSTV_F2D(1900), SSTV_MS2S(300) },
|
||||||
|
{ SSTV_F2D(1200), SSTV_MS2S(10) },
|
||||||
|
{ SSTV_F2D(1900), SSTV_MS2S(300) }
|
||||||
|
};
|
||||||
|
|
||||||
|
enum state_t {
|
||||||
|
STATE_CALIBRATION = 0,
|
||||||
|
STATE_VIS,
|
||||||
|
STATE_START,
|
||||||
|
STATE_PIXELS
|
||||||
|
};
|
||||||
|
|
||||||
|
state_t state { };
|
||||||
|
|
||||||
|
bool configured { false };
|
||||||
|
|
||||||
|
BasebandThread baseband_thread { 3072000, this, NORMALPRIO + 20, baseband::Direction::Transmit };
|
||||||
|
|
||||||
|
uint32_t vis_code_sequence[10] { };
|
||||||
|
sstv_scanline scanline_buffer[2] { };
|
||||||
|
uint8_t buffer_flip { 0 }, substep { 0 };
|
||||||
|
|
||||||
|
uint8_t vis_code { };
|
||||||
|
uint32_t pixel_duration { };
|
||||||
|
|
||||||
|
sstv_scanline * current_scanline { };
|
||||||
|
|
||||||
|
uint8_t pixel_luma { };
|
||||||
|
uint32_t fm_delta { 0 };
|
||||||
|
uint32_t tone_phase { 0 };
|
||||||
|
uint32_t tone_delta { 0 };
|
||||||
|
uint32_t pixel_index { 0 };
|
||||||
|
uint32_t sample_count { 0 };
|
||||||
|
uint32_t phase { 0 }, sphase { 0 };
|
||||||
|
int32_t tone_sample { 0 }, delta { 0 };
|
||||||
|
int8_t re { 0 }, im { 0 };
|
||||||
|
|
||||||
|
bool asked { false };
|
||||||
|
|
||||||
|
RequestSignalMessage sig_message { RequestSignalMessage::Signal::FillRequest };
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -86,6 +86,7 @@ public:
|
|||||||
JammerConfigure = 41,
|
JammerConfigure = 41,
|
||||||
WidebandSpectrumConfig = 42,
|
WidebandSpectrumConfig = 42,
|
||||||
FSKConfigure = 43,
|
FSKConfigure = 43,
|
||||||
|
SSTVConfigure = 44,
|
||||||
|
|
||||||
POCSAGPacket = 50,
|
POCSAGPacket = 50,
|
||||||
|
|
||||||
@ -712,6 +713,21 @@ public:
|
|||||||
const uint32_t pause_symbols;
|
const uint32_t pause_symbols;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SSTVConfigureMessage : public Message {
|
||||||
|
public:
|
||||||
|
constexpr SSTVConfigureMessage(
|
||||||
|
const uint8_t vis_code,
|
||||||
|
const uint32_t pixel_duration
|
||||||
|
) : Message { ID::SSTVConfigure },
|
||||||
|
vis_code(vis_code),
|
||||||
|
pixel_duration(pixel_duration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t vis_code;
|
||||||
|
const uint32_t pixel_duration;
|
||||||
|
};
|
||||||
|
|
||||||
class FSKConfigureMessage : public Message {
|
class FSKConfigureMessage : public Message {
|
||||||
public:
|
public:
|
||||||
constexpr FSKConfigureMessage(
|
constexpr FSKConfigureMessage(
|
||||||
|
@ -76,7 +76,6 @@ constexpr image_tag_t image_tag_wideband_spectrum { 'P', 'S', 'P', 'E' };
|
|||||||
constexpr image_tag_t image_tag_jammer { 'P', 'J', 'A', 'M' };
|
constexpr image_tag_t image_tag_jammer { 'P', 'J', 'A', 'M' };
|
||||||
constexpr image_tag_t image_tag_audio_tx { 'P', 'A', 'T', 'X' };
|
constexpr image_tag_t image_tag_audio_tx { 'P', 'A', 'T', 'X' };
|
||||||
constexpr image_tag_t image_tag_afsk { 'P', 'A', 'F', 'S' };
|
constexpr image_tag_t image_tag_afsk { 'P', 'A', 'F', 'S' };
|
||||||
constexpr image_tag_t image_tag_epar { 'P', 'E', 'P', 'R' };
|
|
||||||
constexpr image_tag_t image_tag_tones { 'P', 'T', 'O', 'N' };
|
constexpr image_tag_t image_tag_tones { 'P', 'T', 'O', 'N' };
|
||||||
constexpr image_tag_t image_tag_rds { 'P', 'R', 'D', 'S' };
|
constexpr image_tag_t image_tag_rds { 'P', 'R', 'D', 'S' };
|
||||||
constexpr image_tag_t image_tag_ook { 'P', 'O', 'O', 'K' };
|
constexpr image_tag_t image_tag_ook { 'P', 'O', 'O', 'K' };
|
||||||
@ -84,6 +83,7 @@ constexpr image_tag_t image_tag_adsb_tx { 'P', 'A', 'D', 'S' };
|
|||||||
constexpr image_tag_t image_tag_replay { 'P', 'R', 'E', 'P' };
|
constexpr image_tag_t image_tag_replay { 'P', 'R', 'E', 'P' };
|
||||||
constexpr image_tag_t image_tag_fsktx { 'P', 'F', 'S', 'K' };
|
constexpr image_tag_t image_tag_fsktx { 'P', 'F', 'S', 'K' };
|
||||||
constexpr image_tag_t image_tag_mic_tx { 'P', 'M', 'T', 'X' };
|
constexpr image_tag_t image_tag_mic_tx { 'P', 'M', 'T', 'X' };
|
||||||
|
constexpr image_tag_t image_tag_sstv_tx { 'P', 'S', 'T', 'X' };
|
||||||
|
|
||||||
constexpr image_tag_t image_tag_noop { 'P', 'N', 'O', 'P' };
|
constexpr image_tag_t image_tag_noop { 'P', 'N', 'O', 'P' };
|
||||||
|
|
||||||
|
@ -19,12 +19,25 @@
|
|||||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
* Boston, MA 02110-1301, USA.
|
* Boston, MA 02110-1301, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifndef __SSTV_H__
|
||||||
|
#define __SSTV_H__
|
||||||
|
|
||||||
namespace sstv {
|
namespace sstv {
|
||||||
|
|
||||||
|
#define SSTV_SAMPLERATE 3072000
|
||||||
|
#define SSTV_DELTA_COEF ((1ULL << 32) / SSTV_SAMPLERATE)
|
||||||
|
|
||||||
|
#define SSTV_F2D(f) (uint32_t)((f) * SSTV_DELTA_COEF)
|
||||||
|
#define SSTV_MS2S(f) (uint32_t)((f) / 1000.0 * (float)SSTV_SAMPLERATE)
|
||||||
|
|
||||||
|
#define SSTV_VIS_SS SSTV_F2D(1200)
|
||||||
|
#define SSTV_VIS_ZERO SSTV_F2D(1300)
|
||||||
|
#define SSTV_VIS_ONE SSTV_F2D(1100)
|
||||||
|
|
||||||
struct sstv_tone {
|
struct sstv_tone {
|
||||||
uint16_t frequency;
|
uint32_t frequency;
|
||||||
uint16_t duration;
|
uint32_t duration;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sstv_scanline {
|
struct sstv_scanline {
|
||||||
@ -35,3 +48,4 @@ struct sstv_scanline {
|
|||||||
|
|
||||||
} /* namespace sstv */
|
} /* namespace sstv */
|
||||||
|
|
||||||
|
#endif/*__SSTV_H__*/
|
||||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user