From 089eeeafe4a87850592d0786508d21f70837bde6 Mon Sep 17 00:00:00 2001 From: furrtek Date: Wed, 22 Mar 2017 03:21:06 +0000 Subject: [PATCH] Tones bugfix, numbers station voice files search --- firmware/application/CMakeLists.txt | 1 + firmware/application/file.cpp | 13 +++ firmware/application/file.hpp | 1 + firmware/application/ui_navigation.cpp | 64 +----------- firmware/application/ui_navigation.hpp | 29 ------ firmware/application/ui_numbers.cpp | 128 +++++++++++++++--------- firmware/application/ui_numbers.hpp | 60 +++++------ firmware/application/ui_playdead.cpp | 81 +++++++++++++++ firmware/application/ui_playdead.hpp | 62 ++++++++++++ firmware/baseband/proc_tones.cpp | 2 +- firmware/common/bmp.hpp | 53 ++++++++++ firmware/common/lcd_ili9341.cpp | 2 + firmware/common/lcd_ili9341.hpp | 33 +----- firmware/hackrf_one_usb.dfu | Bin 0 -> 21992 bytes sdcard/NUMBERS/{UA2/0.wav => UA/0a.wav} | Bin sdcard/NUMBERS/{UA2/1.wav => UA/1a.wav} | Bin sdcard/NUMBERS/{UA2/2.wav => UA/2a.wav} | Bin sdcard/NUMBERS/{UA2/3.wav => UA/3a.wav} | Bin sdcard/NUMBERS/{UA2/4.wav => UA/4a.wav} | Bin sdcard/NUMBERS/{UA2/5.wav => UA/5a.wav} | Bin sdcard/NUMBERS/{UA2/6.wav => UA/6a.wav} | Bin sdcard/NUMBERS/{UA2/7.wav => UA/7a.wav} | Bin sdcard/NUMBERS/{UA2/8.wav => UA/8a.wav} | Bin sdcard/NUMBERS/{UA2/9.wav => UA/9a.wav} | Bin 24 files changed, 335 insertions(+), 194 deletions(-) create mode 100644 firmware/application/ui_playdead.cpp create mode 100644 firmware/application/ui_playdead.hpp create mode 100644 firmware/common/bmp.hpp create mode 100644 firmware/hackrf_one_usb.dfu rename sdcard/NUMBERS/{UA2/0.wav => UA/0a.wav} (100%) rename sdcard/NUMBERS/{UA2/1.wav => UA/1a.wav} (100%) rename sdcard/NUMBERS/{UA2/2.wav => UA/2a.wav} (100%) rename sdcard/NUMBERS/{UA2/3.wav => UA/3a.wav} (100%) rename sdcard/NUMBERS/{UA2/4.wav => UA/4a.wav} (100%) rename sdcard/NUMBERS/{UA2/5.wav => UA/5a.wav} (100%) rename sdcard/NUMBERS/{UA2/6.wav => UA/6a.wav} (100%) rename sdcard/NUMBERS/{UA2/7.wav => UA/7a.wav} (100%) rename sdcard/NUMBERS/{UA2/8.wav => UA/8a.wav} (100%) rename sdcard/NUMBERS/{UA2/9.wav => UA/9a.wav} (100%) diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 197dc5bb..b61c2227 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -174,6 +174,7 @@ set(CPPSRC ui_navigation.cpp ui_numbers.cpp ui_nuoptix.cpp + ui_playdead.cpp ui_pocsag_tx.cpp ui_rds.cpp ui_receiver.cpp diff --git a/firmware/application/file.cpp b/firmware/application/file.cpp index 95069669..5d3013d8 100644 --- a/firmware/application/file.cpp +++ b/firmware/application/file.cpp @@ -180,6 +180,19 @@ std::vector scan_root_files(const std::filesystem::path& return file_list; } +std::vector scan_root_directories(const std::filesystem::path& directory) { + + std::vector directory_list { }; + + for(const auto& entry : std::filesystem::directory_iterator(directory, "*")) { + if( std::filesystem::is_directory(entry.status()) ) { + directory_list.push_back(entry.path()); + } + } + + return directory_list; +} + namespace std { namespace filesystem { diff --git a/firmware/application/file.hpp b/firmware/application/file.hpp index ddeb4e65..f011252d 100644 --- a/firmware/application/file.hpp +++ b/firmware/application/file.hpp @@ -234,6 +234,7 @@ space_info space(const path& p); } /* namespace std */ std::vector scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension); +std::vector scan_root_directories(const std::filesystem::path& directory); std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_stem_pattern); /* Values added to FatFs FRESULT enum, values outside the FRESULT data type */ diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 660e56d7..aec15a9a 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -21,19 +21,16 @@ */ #include "ui_navigation.hpp" -//#include "ui_loadmodule.hpp" #include "modules.h" #include "portapack.hpp" #include "event_m0.hpp" -#include "portapack_persistent_memory.hpp" #include "bmp_splash.hpp" #include "bmp_modal_warning.hpp" #include "ui_about.hpp" #include "ui_adsbtx.hpp" -#include "ui_mictx.hpp" #include "ui_bht_tx.hpp" #include "ui_closecall.hpp" #include "ui_cw.hpp" @@ -42,9 +39,11 @@ #include "ui_freqman.hpp" #include "ui_jammer.hpp" #include "ui_lcr.hpp" +#include "ui_mictx.hpp" #include "ui_morse.hpp" #include "ui_numbers.hpp" #include "ui_nuoptix.hpp" +#include "ui_playdead.hpp" #include "ui_pocsag_tx.hpp" #include "ui_rds.hpp" #include "ui_sd_wipe.hpp" @@ -354,13 +353,14 @@ void SystemMenuView::hackrf_mode(NavigationView& nav) { } SystemMenuView::SystemMenuView(NavigationView& nav) { - add_items<12>({ { + add_items<13>({ { { "Play dead", ui::Color::red(), &bitmap_icon_playdead, [&nav](){ nav.push(); } }, { "Receivers", ui::Color::cyan(), &bitmap_icon_receivers, [&nav](){ nav.push(); } }, { "Capture", ui::Color::blue(), &bitmap_icon_capture, [&nav](){ nav.push(); } }, { "Replay", ui::Color::grey(), &bitmap_icon_replay, [&nav](){ nav.push(); } }, - { "Code transmitters", ui::Color::green(), &bitmap_icon_codetx, [&nav](){ nav.push(); } }, { "Audio transmitters", ui::Color::green(), &bitmap_icon_audiotx, [&nav](){ nav.push(); } }, + { "Code transmitters", ui::Color::green(), &bitmap_icon_codetx, [&nav](){ nav.push(); } }, + { "SSTV transmitter", ui::Color::grey(), nullptr, [&nav](){ nav.push(); } }, { "Close Call", ui::Color::orange(),&bitmap_icon_closecall, [&nav](){ nav.push(); } }, { "Jammer", ui::Color::orange(),&bitmap_icon_jammer, [&nav](){ nav.push(); } }, { "Utilities", ui::Color::purple(),&bitmap_icon_utilities, [&nav](){ nav.push(); } }, @@ -450,60 +450,6 @@ void BMPView::paint(Painter&) { portapack::display.drawBMP({(240 - 185) / 2, 0}, splash_bmp, false); } -/* PlayDeadView **********************************************************/ - -void PlayDeadView::focus() { - button_seq_entry.focus(); -} - -void PlayDeadView::paint(Painter& painter) { - if (!(portapack::persistent_memory::ui_config() & 16)) { - // Blank the whole display - painter.fill_rectangle( - portapack::display.screen_rect(), - style().background - ); - } -} - -PlayDeadView::PlayDeadView(NavigationView& nav) { - rtc::RTC datetime; - - portapack::persistent_memory::set_playing_dead(0x5920C1DF); // Enable - - add_children({ - &text_playdead1, - &text_playdead2, - &text_playdead3, - &button_seq_entry, - }); - - // Seed from RTC - rtcGetTime(&RTCD1, &datetime); - text_playdead2.set("0x" + to_string_hex(lfsr_iterate(datetime.second()), 6) + "00"); - - text_playdead3.hidden(true); - - button_seq_entry.on_dir = [this](Button&, KeyEvent key){ - sequence = (sequence << 3) | (static_cast::type>(key) + 1); - return true; - }; - - button_seq_entry.on_select = [this, &nav](Button&){ - if (sequence == portapack::persistent_memory::playdead_sequence()) { - portapack::persistent_memory::set_playing_dead(0x82175E23); // Disable - if (!(portapack::persistent_memory::ui_config() & 16)) { - text_playdead3.hidden(false); - } else { - nav.pop(); - nav.push(); - } - } else { - sequence = 0; - } - }; -} - /* NotImplementedView ****************************************************/ NotImplementedView::NotImplementedView(NavigationView& nav) { diff --git a/firmware/application/ui_navigation.hpp b/firmware/application/ui_navigation.hpp index aa3a98ad..ba587bce 100644 --- a/firmware/application/ui_navigation.hpp +++ b/firmware/application/ui_navigation.hpp @@ -181,35 +181,6 @@ private: }; }; -class PlayDeadView : public View { -public: - PlayDeadView(NavigationView& nav); - - void focus() override; - void paint(Painter& painter) override; - -private: - uint32_t sequence = 0; - - Text text_playdead1 { - { 6 * 8, 7 * 16, 14 * 8, 16 }, - "\x46irmwa" "re " "er\x72o\x72" - }; - Text text_playdead2 { - { 6 * 8, 9 * 16, 16 * 8, 16 }, - "" - }; - Text text_playdead3 { - { 6 * 8, 12 * 16, 16 * 8, 16 }, - "Please reset" - }; - - Button button_seq_entry { - { 240, 0, 1, 1 }, - "" - }; -}; - class ReceiverMenuView : public MenuView { public: ReceiverMenuView(NavigationView& nav); diff --git a/firmware/application/ui_numbers.cpp b/firmware/application/ui_numbers.cpp index e6c9db77..f6ad29ef 100644 --- a/firmware/application/ui_numbers.cpp +++ b/firmware/application/ui_numbers.cpp @@ -21,6 +21,7 @@ */ #include "ui_numbers.hpp" +#include "string_format.hpp" #include "portapack.hpp" #include "hackrf_hal.hpp" @@ -29,17 +30,15 @@ #include #include -// TODO: Total transmission time (all durations / 44100) - using namespace portapack; namespace ui { void NumbersStationView::focus() { - button_exit.focus(); - if (file_error) - nav_.display_modal("No files", "Missing files in /numbers/", ABORT, nullptr); + nav_.display_modal("No voices", "No valid voices found in\nthe /numbers directory.", ABORT, nullptr); + else + button_exit.focus(); } NumbersStationView::~NumbersStationView() { @@ -54,7 +53,7 @@ void NumbersStationView::on_tuning_frequency_changed(rf::Frequency f) { void NumbersStationView::prepare_audio() { uint8_t code; - if (sample_counter >= sample_duration) { + /*if (sample_counter >= sample_duration) { if (segment == ANNOUNCE) { if (!announce_loop) { segment = MESSAGE; @@ -108,11 +107,11 @@ void NumbersStationView::prepare_audio() { } } - baseband::set_fifo_data(audio_buffer); + baseband::set_fifo_data(audio_buffer);*/ } void NumbersStationView::start_tx() { - sample_duration = sound_sizes[10]; // Announce + //sample_duration = sound_sizes[10]; // Announce sample_counter = sample_duration; code_index = 0; @@ -130,7 +129,7 @@ void NumbersStationView::start_tx() { baseband::set_audiotx_data( (1536000 / 44100) - 1, - number_bw.value(), + 12000, 1, false, 0 @@ -150,56 +149,99 @@ void NumbersStationView::on_tick_second() { } } +void NumbersStationView::on_voice_changed(size_t index) { + std::string flags_string = ""; + + if (voices[index].accent) { + flags_string = "^"; + } + if (voices[index].announce) { + flags_string += "A"; + } + text_voice_flags.set(flags_string); +} + NumbersStationView::NumbersStationView( NavigationView& nav ) : nav_ (nav) { - uint8_t c; + std::vector directory_list; + using option_t = std::pair; + using options_t = std::vector; + options_t voice_options; + uint32_t c, i, ia; + bool valid; //uint8_t y, m, d, dayofweek; reader = std::make_unique(); - c = 0; - for (auto& file_name : file_names) { - if (reader->open("/numbers/" + file_name + ".wav")) { - if ((reader->channels() == 1) && (reader->sample_rate() == 44100) && (reader->bits_per_sample() == 8)) { - sound_sizes[c] = reader->data_size(); - c++; + // Search for valid voice directories + directory_list = scan_root_directories("/numbers"); + if (!directory_list.size()) { + file_error = true; + return; + } + // This is awfully repetitive + for (const auto& dir : directory_list) { + i = 0; + for (const auto& file_name : file_names) { + valid = false; + if (reader->open("/numbers/" + dir.string() + "/" + file_name + ".wav")) { + // Check format (mono, 8 bits) + if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) + valid = true; } + if (!valid) { + if (i < 10) + i = 0; // Missingno, invalid voice + break; + } + i++; + } + if (i) { + // Voice ok, are there accent files ? + ia = 0; + for (const auto& file_name : file_names) { + valid = false; + if (reader->open("/numbers/" + dir.string() + "/" + file_name + "a.wav")) { + // Check format (mono, 8 bits) + if ((reader->channels() == 1) && (reader->bits_per_sample() == 8)) + valid = true; + } + if (!valid) + break; + ia++; + } + + voices.push_back({ dir.string(), (ia >= 10), (i == 11) }); } } - - if (c != 11) file_error = true; + if (!voices.size()) { + file_error = true; + return; + } baseband::run_image(portapack::spi_flash::image_tag_audio_tx); add_children({ - &text_title, - &field_frequency, - &number_bw, - &text_code, &symfield_code, &check_armed, - &button_tx_now, + &options_voices, + &text_voice_flags, + //&button_tx_now, &button_exit }); + + for (const auto& voice : voices) + voice_options.emplace_back(voice.dir.substr(0, 4), c); + + options_voices.set_options(voice_options); + options_voices.on_change = [this](size_t i, int32_t) { + this->on_voice_changed(i); + }; + options_voices.set_selected_index(0); - number_bw.set_value(75); check_armed.set_value(false); - - field_frequency.set_value(transmitter_model.tuning_frequency()); - field_frequency.set_step(50000); - field_frequency.on_change = [this](rf::Frequency f) { - this->on_tuning_frequency_changed(f); - }; - field_frequency.on_edit = [this, &nav]() { - // TODO: Provide separate modal method/scheme? - auto new_view = nav.push(transmitter_model.tuning_frequency()); - new_view->on_changed = [this](rf::Frequency f) { - this->on_tuning_frequency_changed(f); - this->field_frequency.set_value(f); - }; - }; check_armed.on_select = [this](Checkbox&, bool v) { if (v) { @@ -213,6 +255,9 @@ NumbersStationView::NumbersStationView( } }; + for (c = 0; c < 25; c++) + symfield_code.set_symbol_list(c, "0123456789pPE"); + // DEBUG symfield_code.set_sym(0, 10); symfield_code.set_sym(1, 3); @@ -226,9 +271,6 @@ NumbersStationView::NumbersStationView( symfield_code.set_sym(9, 0); symfield_code.set_sym(10, 12); // End - for (c = 0; c < 25; c++) - symfield_code.set_symbol_list(c, "0123456789pPE"); - /* rtc::RTC datetime; rtcGetTime(&RTCD1, &datetime); @@ -243,10 +285,6 @@ NumbersStationView::NumbersStationView( text_title.set(day_of_week[dayofweek]); */ - button_tx_now.on_select = [this, &nav](Button&){ - this->start_tx(); - }; - button_exit.on_select = [&nav](Button&){ nav.pop(); }; diff --git a/firmware/application/ui_numbers.hpp b/firmware/application/ui_numbers.hpp index 3aacb453..306e2fc8 100644 --- a/firmware/application/ui_numbers.hpp +++ b/firmware/application/ui_numbers.hpp @@ -33,6 +33,7 @@ #include "baseband_api.hpp" #include "utility.hpp" #include "message.hpp" +#include "file.hpp" #include "io_wave.hpp" namespace ui { @@ -47,9 +48,12 @@ public: std::string title() const override { return "Numbers station"; }; private: + NavigationView& nav_; + // Sequencing state machine enum segments { - ANNOUNCE = 0, + IDLE = 0, + ANNOUNCE, MESSAGE, SIGNOFF }; @@ -60,12 +64,16 @@ private: .foreground = Color::red() }; - NavigationView& nav_; + struct voice_t { + std::string dir; + bool accent; + bool announce; + }; - segments segment; - bool armed = false; - bool file_error = false; - uint32_t sound_sizes[11]; + segments segment { IDLE }; + bool armed { false }; + bool file_error { false }; + std::vector voices; const std::vector file_names = { { "0" }, @@ -84,16 +92,17 @@ private: // const uint8_t month_table[12] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 }; // const char * day_of_week[7] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" }; - std::unique_ptr reader; + std::unique_ptr reader { }; uint8_t code_index, announce_loop; uint32_t sample_counter; uint32_t sample_duration; int8_t audio_buffer[1024]; uint32_t pause = 0; - bool armed_blink; - SignalToken signal_token_tick_second; + bool armed_blink { false }; + SignalToken signal_token_tick_second { }; + void on_voice_changed(size_t index); void on_tick_second(); void on_tuning_frequency_changed(rf::Frequency f); void prepare_audio(); @@ -109,28 +118,23 @@ private: // End // Frequency list and sequence - Text text_title { - { 1 * 8, 8 * 16, 11, 16 }, - "Schedule:" + Labels labels { + { { 2 * 8, 5 * 8 }, "Voice: Flags:", Color::light_grey() }, + { { 1 * 8, 8 * 8 }, "Code:", Color::light_grey() } }; - FrequencyField field_frequency { - { 1 * 8, 4 }, + OptionsField options_voices { + { 8 * 8, 1 * 8 }, + 4, + { } }; - NumberField number_bw { - { 12 * 8, 4 }, - 3, - {1, 150}, - 1, - ' ' - }; - - Text text_code { - { 20, 4 * 16, 5 * 8, 16 }, - "Code:" + Text text_voice_flags { + { 19 * 8, 1 * 8, 2 * 8, 16 }, + "" }; + SymField symfield_code { - { 20, 5 * 16 }, + { 1 * 8, 10 * 8 }, 25, SymField::SYMFIELD_DEF }; @@ -140,10 +144,10 @@ private: 5, "Armed" }; - Button button_tx_now { + /*Button button_tx_now { { 18 * 8, 13 * 16, 10 * 8, 32 }, "TX now" - }; + };*/ Button button_exit { { 21 * 8, 16 * 16, 64, 32 }, "Exit" diff --git a/firmware/application/ui_playdead.cpp b/firmware/application/ui_playdead.cpp new file mode 100644 index 00000000..36896d93 --- /dev/null +++ b/firmware/application/ui_playdead.cpp @@ -0,0 +1,81 @@ +/* + * 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_playdead.hpp" +#include "portapack_persistent_memory.hpp" +#include "string_format.hpp" + +namespace ui { + +void PlayDeadView::focus() { + button_seq_entry.focus(); +} + +void PlayDeadView::paint(Painter& painter) { + if (!(portapack::persistent_memory::ui_config() & 16)) { + // Blank the whole display + painter.fill_rectangle( + portapack::display.screen_rect(), + style().background + ); + } +} + +PlayDeadView::PlayDeadView(NavigationView& nav) { + rtc::RTC datetime; + + portapack::persistent_memory::set_playing_dead(0x5920C1DF); // Enable + + add_children({ + &text_playdead1, + &text_playdead2, + &text_playdead3, + &button_seq_entry, + }); + + // Seed from RTC + rtcGetTime(&RTCD1, &datetime); + text_playdead2.set("0x" + to_string_hex(lfsr_iterate(datetime.second()), 6) + "00"); + + text_playdead3.hidden(true); + + button_seq_entry.on_dir = [this](Button&, KeyEvent key){ + sequence = (sequence << 3) | (static_cast::type>(key) + 1); + return true; + }; + + button_seq_entry.on_select = [this, &nav](Button&){ + if (sequence == portapack::persistent_memory::playdead_sequence()) { + portapack::persistent_memory::set_playing_dead(0x82175E23); // Disable + if (!(portapack::persistent_memory::ui_config() & 16)) { + text_playdead3.hidden(false); + } else { + nav.pop(); + nav.push(); + } + } else { + sequence = 0; + } + }; +} + +} /* namespace ui */ diff --git a/firmware/application/ui_playdead.hpp b/firmware/application/ui_playdead.hpp new file mode 100644 index 00000000..2169dbe6 --- /dev/null +++ b/firmware/application/ui_playdead.hpp @@ -0,0 +1,62 @@ +/* + * 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 __UI_PLAYDEAD_H__ +#define __UI_PLAYDEAD_H__ + +#include "ui.hpp" +#include "ui_navigation.hpp" + +namespace ui { + +class PlayDeadView : public View { +public: + PlayDeadView(NavigationView& nav); + + void focus() override; + void paint(Painter& painter) override; + +private: + uint32_t sequence = 0; + + Text text_playdead1 { + { 6 * 8, 7 * 16, 14 * 8, 16 }, + "\x46irmwa" "re " "er\x72o\x72" + }; + Text text_playdead2 { + { 6 * 8, 9 * 16, 16 * 8, 16 }, + "" + }; + Text text_playdead3 { + { 6 * 8, 12 * 16, 16 * 8, 16 }, + "Please reset" + }; + + Button button_seq_entry { + { 240, 0, 1, 1 }, + "" + }; +}; + +} /* namespace ui */ + +#endif/*__UI_PLAYDEAD_H__*/ diff --git a/firmware/baseband/proc_tones.cpp b/firmware/baseband/proc_tones.cpp index 375291ac..27aa1921 100644 --- a/firmware/baseband/proc_tones.cpp +++ b/firmware/baseband/proc_tones.cpp @@ -130,7 +130,7 @@ void TonesProcessor::on_message(const Message* const p) { tone_deltas[c] = shared_memory.bb_data.tones_data.tone_defs[c].delta; tone_durations[c] = shared_memory.bb_data.tones_data.tone_defs[c].duration; } - fm_delta = message.fm_delta * (0xFFFFFFFFULL / 1536000) * 2; + fm_delta = message.fm_delta * (0xFFFFFFULL / 1536000); audio_out = message.audio_out; dual_tone = message.dual_tone; diff --git a/firmware/common/bmp.hpp b/firmware/common/bmp.hpp new file mode 100644 index 00000000..8081ee26 --- /dev/null +++ b/firmware/common/bmp.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014 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. + */ + +#pragma pack(push, 1) +struct bmp_header_t { + uint16_t signature; + uint32_t size; + uint16_t reserved_1; + uint16_t reserved_2; + uint32_t image_data; + uint32_t BIH_size; + uint32_t width; + uint32_t height; + uint16_t planes; + uint16_t bpp; + uint32_t compression; + uint32_t data_size; + uint32_t h_res; + uint32_t v_res; + uint32_t colors_count; + uint32_t icolors_count; +}; +#pragma pack(pop) + +#pragma pack(push, 1) +struct bmp_palette_t { + struct color_t { + uint8_t B; + uint8_t G; + uint8_t R; + uint8_t A; + } color[16]; +}; +#pragma pack(pop) diff --git a/firmware/common/lcd_ili9341.cpp b/firmware/common/lcd_ili9341.cpp index fe273264..07efcdbf 100644 --- a/firmware/common/lcd_ili9341.cpp +++ b/firmware/common/lcd_ili9341.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek * * This file is part of PortaPack. * @@ -20,6 +21,7 @@ */ #include "lcd_ili9341.hpp" +#include "bmp.hpp" #include "portapack_io.hpp" using namespace portapack; diff --git a/firmware/common/lcd_ili9341.hpp b/firmware/common/lcd_ili9341.hpp index 06f0dac3..99153640 100644 --- a/firmware/common/lcd_ili9341.hpp +++ b/firmware/common/lcd_ili9341.hpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek * * This file is part of PortaPack. * @@ -110,38 +111,6 @@ private: uint16_t current_position; }; - #pragma pack(push, 1) - struct bmp_header_t { - uint16_t signature; - uint32_t size; - uint16_t reserved_1; - uint16_t reserved_2; - uint32_t image_data; - uint32_t BIH_size; - uint32_t width; - uint32_t height; - uint16_t planes; - uint16_t bpp; - uint32_t compression; - uint32_t data_size; - uint32_t h_res; - uint32_t v_res; - uint32_t colors_count; - uint32_t icolors_count; - }; - #pragma pack(pop) - - #pragma pack(push, 1) - struct bmp_palette_t { - struct color_t { - uint8_t B; - uint8_t G; - uint8_t R; - uint8_t A; - } color[16]; - }; - #pragma pack(pop) - scroll_t scroll_state; void draw_pixels(const ui::Rect r, const ui::Color* const colors, const size_t count); diff --git a/firmware/hackrf_one_usb.dfu b/firmware/hackrf_one_usb.dfu new file mode 100644 index 0000000000000000000000000000000000000000..49e0eee10493358a9d2ec6e5068831ee3ba45c7a GIT binary patch literal 21992 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 z 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