/* * 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 "ui_sstvtx.hpp" #include "string_format.hpp" #include "portapack.hpp" #include "hackrf_hal.hpp" #include #include using namespace portapack; namespace ui { void SSTVTXView::focus() { if (file_error) nav_.display_modal("No files", "No valid bitmaps\nin /sstv directory.", ABORT, nullptr); else options_bitmaps.focus(); } void SSTVTXView::read_boundary(uint8_t * const buffer, uint32_t position, uint32_t length) { uint32_t to_read, split; uint8_t * buffer_copy = buffer; bmp_file.seek(position); // FatFS bug workaround: we can't read across 512 byte boundaries ?! while (length) { to_read = (length <= 512) ? length : 512; if ((position & 512) != ((position + to_read - 1) & 512)) { // Crossing: do 2 reads before and after the boundary split = 512 - (position & 511); bmp_file.read(buffer_copy, split); bmp_file.read(buffer_copy + split, to_read - split); } else { // No crossing, just read normally bmp_file.read(buffer_copy, to_read); } position += to_read; buffer_copy += to_read; length -= to_read; } } void SSTVTXView::paint(Painter&) { ui::Color line_buffer[160]; Coord line; uint32_t data_idx, bmp_px, pixel_idx; data_idx = bmp_header.image_data; for (line = 0; line < (256 / 2); line++) { // Buffer a whole line read_boundary(pixels_buffer, data_idx, sizeof(pixels_buffer)); for (bmp_px = 0; bmp_px < 160; bmp_px++) { pixel_idx = bmp_px * 3 * 2; line_buffer[bmp_px] = Color(pixels_buffer[pixel_idx + 2], pixels_buffer[pixel_idx + 1], pixels_buffer[pixel_idx + 0]); } portapack::display.render_line({ 16, 80 + 128 - line }, 160, line_buffer); data_idx += sizeof(pixels_buffer) * 2; } } SSTVTXView::~SSTVTXView() { transmitter_model.disable(); baseband::shutdown(); } void SSTVTXView::on_tuning_frequency_changed(rf::Frequency f) { transmitter_model.set_tuning_frequency(f); } void SSTVTXView::prepare_scanline() { sstv_scanline scanline_buffer; uint32_t component, pixel_idx; if (scanline_counter >= (256 * 3)) { progressbar.set_value(0); transmitter_model.disable(); options_bitmaps.set_focusable(true); tx_view.set_transmitting(false); return; } progressbar.set_value(scanline_counter); // Scottie 2 scanline: // (First line: 1200 9ms) // 1500 1.5ms // Green // 1500 1.5ms // Blue // 1200 9ms // 1500 1.5ms // Red // Scanline time: 88.064ms (275.2us/pixel @ 320 pixels/line) 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_baseband_bandwidth(1750000); transmitter_model.enable(); baseband::set_sstv_data( 0b00011101, // Scottie 2, 275.2us/px (uint32_t)(0.0002752 * 3072000.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) { bmp_file.open("/sstv/" + bitmaps[index].string()); bmp_file.read(&bmp_header, sizeof(bmp_header)); progressbar.set_max(256 * 3); set_dirty(); } SSTVTXView::SSTVTXView( NavigationView& nav ) : nav_ (nav) { std::vector file_list; using option_t = std::pair; using options_t = std::vector; options_t bitmap_options; // Search for valid bitmaps file_list = scan_root_files(u"/sstv", u"*.bmp"); if (!file_list.size()) { file_error = true; return; } for (const auto& file_name : file_list) { if (!bmp_file.open("/sstv/" + file_name.string()).is_valid()) { bmp_file.read(&bmp_header, sizeof(bmp_header)); if ((bmp_header.signature == 0x4D42) && (bmp_header.width == 320) && // Must be == 320x256 pixels for now (bmp_header.height == 256) && (bmp_header.planes == 1) && (bmp_header.bpp >= 24) && // 24 or 32 bpp (bmp_header.compression == 0)) { // No compression bitmaps.push_back(file_name); } } } if (!bitmaps.size()) { file_error = true; return; } // 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({ &labels, &options_bitmaps, &text_mode, &progressbar, &tx_view }); for (const auto& bitmap : bitmaps) bitmap_options.emplace_back(bitmap.string().substr(0, 16), 0); options_bitmaps.set_options(bitmap_options); options_bitmaps.on_change = [this](size_t i, int32_t) { this->on_bitmap_changed(i); }; options_bitmaps.set_selected_index(0); on_bitmap_changed(0); tx_view.on_edit_frequency = [this, &nav]() { auto new_view = nav.push(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 */