diff --git a/firmware/Makefile b/firmware/Makefile index 18302e652..5bce1a9fc 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -64,7 +64,7 @@ modules: $(TARGET_BASEBAND).bin $(TARGET_BASEBAND_TX).bin cp $(PATH_BASEBAND).bin ../sdcard/$(PATH_BASEBAND).bin cp $(PATH_BASEBAND_TX).bin ../sdcard/$(PATH_BASEBAND_TX).bin -$(TARGET).bin: modules $(MAKE_SPI_IMAGE) $(TARGET_BOOTSTRAP).bin $(TARGET_HACKRF_FIRMWARE).dfu $(TARGET_BASEBAND)_inc.bin $(TARGET_APPLICATION).bin +$(TARGET).bin: modules $(MAKE_SPI_IMAGE) $(TARGET_BOOTSTRAP).bin $(TARGET_HACKRF_FIRMWARE).dfu $(TARGET_BASEBAND_TX)_inc.bin $(TARGET_APPLICATION).bin $(MAKE_SPI_IMAGE) $(TARGET_BOOTSTRAP).bin $(TARGET_HACKRF_FIRMWARE).dfu $(TARGET_BASEBAND)_inc.bin $(TARGET_APPLICATION).bin $(TARGET).bin $(TARGET_BOOTSTRAP).bin: $(TARGET_BOOTSTRAP).elf diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 4aeff3f09..caef1a995 100755 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -151,6 +151,7 @@ CPPSRC = main.cpp \ lcd_ili9341.cpp \ ui.cpp \ ui_alphanum.cpp \ + ui_handwrite.cpp \ ui_about.cpp \ ui_text.cpp \ ui_widget.cpp \ @@ -162,6 +163,7 @@ CPPSRC = main.cpp \ ui_channel.cpp \ ui_audio.cpp \ ui_audiotx.cpp \ + ui_soundboard.cpp \ ui_lcr.cpp \ ui_rds.cpp \ ui_jammer.cpp \ diff --git a/firmware/application/touch.hpp b/firmware/application/touch.hpp index 0402243a6..c814db2c3 100644 --- a/firmware/application/touch.hpp +++ b/firmware/application/touch.hpp @@ -180,12 +180,12 @@ private: static constexpr size_t touch_count_threshold { 4 }; static constexpr uint32_t touch_stable_bound { 4 }; - static constexpr float calib_x_low = 0.07f; - static constexpr float calib_x_high = 0.94f; + static constexpr float calib_x_low = 0.15f; + static constexpr float calib_x_high = 0.98f; static constexpr float calib_x_range = calib_x_high - calib_x_low; static constexpr float calib_y_low = 0.04f; - static constexpr float calib_y_high = 0.91f; + static constexpr float calib_y_high = 0.80f; //91 static constexpr float calib_y_range = calib_y_high - calib_y_low; // Ensure filter length is equal or less than touch_count_threshold, diff --git a/firmware/application/ui_audiotx.hpp b/firmware/application/ui_audiotx.hpp index 4cc7ede4e..d9074cf5a 100644 --- a/firmware/application/ui_audiotx.hpp +++ b/firmware/application/ui_audiotx.hpp @@ -40,7 +40,6 @@ public: AudioTXView(NavigationView& nav); ~AudioTXView(); - void focus() override; private: diff --git a/firmware/application/ui_handwrite.cpp b/firmware/application/ui_handwrite.cpp new file mode 100644 index 000000000..243ea88a8 --- /dev/null +++ b/firmware/application/ui_handwrite.cpp @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * 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_handwrite.hpp" + +#include "ch.h" + +#include "ff.h" +#include "portapack.hpp" +#include "event_m0.hpp" +#include "string_format.hpp" +#include "hackrf_hal.hpp" +#include "portapack_shared_memory.hpp" +#include "time.hpp" + +#include + +using namespace portapack; + +namespace ui { + +HandWriteView::~HandWriteView() { + time::signal_tick_second -= signal_token_tick_second; +} + +HandWriteView::HandWriteView( + NavigationView& nav, + char txt[], + uint8_t max_len +) { + _max_len = max_len; + _lowercase = false; + + txtidx = 0; + //memcpy(txtinput, txt, max_len+1); + + add_child(&text_input); + + const auto button_fn = [this](Button& button) { + this->on_button(button); + }; + + size_t n = 0; + for(auto& button : num_buttons) { + add_child(&button); + button.on_select = button_fn; + button.set_parent_rect({ + static_cast(n * 24), + static_cast(240), + 24, 20 + }); + const std::string label { + n + 0x30 + }; + button.set_text(label); + button.id = n; + n++; + } + //set_uppercase(); + + /*add_child(&button_lowercase); + button_lowercase.on_select = [this, &nav, txt, max_len](Button&) { + if (_lowercase == true) { + _lowercase = false; + button_lowercase.set_text("UC"); + set_uppercase(); + } else { + _lowercase = true; + button_lowercase.set_text("LC"); + set_lowercase(); + } + };*/ + + add_child(&text_debug_x); + add_child(&text_debug_y); + add_child(&text_debug_write); + add_child(&button_done); + button_done.on_select = [this, &nav, txt, max_len](Button&) { + //memcpy(txt, txtinput, max_len+1); + //on_changed(this->value()); + nav.pop(); + }; + + signal_token_tick_second = time::signal_tick_second += [this]() { + this->on_tick_second(); + }; + + //update_text(); +} + +bool HandWriteView::MM(uint8_t idx, char cmp) { + if (idx < move_index) { + if ((cmp == 'U') && ((move_list[idx] & 0xF0) == 0x00)) return true; + if ((cmp == 'D') && ((move_list[idx] & 0xF0) == 0x10)) return true; + if ((cmp == 'L') && ((move_list[idx] & 0x0F) == 0x00)) return true; + if ((cmp == 'R') && ((move_list[idx] & 0x0F) == 0x01)) return true; + } + return false; +} + +bool HandWriteView::MM(uint8_t idx, char cmpud, char cmplr) { + if (idx < move_index) { + if (cmpud == 'U') cmpud = 0; + if (cmpud == 'D') cmpud = 1; + if (cmpud == '?') cmpud = 2; + if (cmplr == 'L') cmplr = 0; + if (cmplr == 'R') cmplr = 1; + if (cmplr == '?') cmplr = 2; + if (((move_list[idx] >> 4) == cmpud) && ((move_list[idx] & 0x0F) == cmplr)) return true; + } + return false; +} + +bool HandWriteView::MI(uint8_t idx) { + if (move_index - 1 < idx) + return true; + else + return false; +} + +bool HandWriteView::MLAST(char cmp) { + if ((cmp == 'U') && ((move_list[move_index - 1] & 0xF0) == 0x00)) return true; + if ((cmp == 'D') && ((move_list[move_index - 1] & 0xF0) == 0x10)) return true; + if ((cmp == 'L') && ((move_list[move_index - 1] & 0x0F) == 0x00)) return true; + if ((cmp == 'R') && ((move_list[move_index - 1] & 0x0F) == 0x01)) return true; + return false; +} + +bool HandWriteView::on_touch(const TouchEvent event) { + char guess; + + if (event.type == ui::TouchEvent::Type::Start) { + move_index = 0; + move_wait = 4; + tracing = true; + } + if (event.type == ui::TouchEvent::Type::End) { + tracing = false; + + display.fill_rectangle( + {{0, 16}, {240, 230}}, + Color::black() + ); + + // Letter guessing + guess = '?'; + + if (MM(0, 'U')) { + if (MM(0, 'U', '?')) { + if (MI(1)) + guess = 'A'; + else + guess = 'F'; + } else if (MM(0, 'U', 'R')) { + if (MI(1)) { + guess = 'K'; + } else { + if (MM(1, 'L')) { + if (txt_idx > 0) txtinput[txt_idx--] = 0; // Erase + } else { + guess = 'N'; + } + } + } else if (MM(0, 'U', 'L')) { + if (MM(1, 'U', 'R')) guess = 'C'; + if (MM(1, 'D', 'L')) guess = 'M'; + } + } else if (MM(0, 'D')) { + if (MM(0, 'D', 'R') || MM(1, 'R')) + guess = 'P'; + else + guess = 'Q'; + if (MM(0, 'D', '?')) { + if (MI(1)) { + guess = 'I'; + } else { + if (MM(1, 'R') && MI(2)) { + guess = 'L'; + } else if (MM(1, 'L')) { + if (MI(2)) guess = 'J'; + } else if (MM(1, 'U', 'R')) { + if (MM(2, 'D')) guess = 'W'; + } + } + } + if (MM(0, 'D', 'R')) { + if (MI(1)) guess = 'R'; + if (MM(1, 'U', 'R') && MI(2)) guess = 'V'; + if (MM(1, 'D', 'L')) guess = 'B'; + } else if (MM(0, 'D', 'L')) { + if (MI(1)) guess = 'Y'; + if (MM(1, 'U', 'L') && MI(2)) guess = 'U'; + if (MM(1, 'D', 'R')) guess = 'D'; + } + } + + if (MM(0, 'L')) { + if (!MI(2) && (MLAST('U') || MLAST('L'))) guess = 'O'; + if (MM(0, '?', 'L')) { + if (MI(1)) + guess = 'E'; + else + guess = 'S'; + } + } else if (MM(0, 'R')) { + if (!MI(2) && (MLAST('U') || MLAST('R'))) guess = 'X'; + if (MM(0, '?', 'R')) { + if (MM(1, 'U') && MI(2)) { + guess = 'G'; + } else if (MM(1, 'D', '?') && MI(2)) { + guess = 'H'; + } else if (MM(1, 'L')) { + guess = 'Z'; + } else if (MI(1)) { + guess = 'T'; + } + } + } + + // if (guess = '?') guess = ' '; + if (guess != '!') txtinput[txt_idx++] = guess; + update_text(); + } + if (event.type == ui::TouchEvent::Type::Move) { + if (tracing) { + current_pos = event.point; + } + } + return true; +} + +void HandWriteView::sample_pen() { + int16_t diff_x, diff_y; + uint8_t dir, i; + + if (!(sample_skip & 1)) { + if (tracing) { + if (move_wait) { + move_wait--; // ~133ms delay + } else { + diff_x = current_pos.x - last_pos.x; + diff_y = current_pos.y - last_pos.y; + + text_debug_x.set(to_string_dec_int(diff_x)); + text_debug_y.set(to_string_dec_int(diff_y)); + + display.fill_rectangle( + {{current_pos.x, current_pos.y}, {4, 4}}, + Color::grey() + ); + + dir = 0; + if (abs(diff_x) > 7) { + if (diff_x > 0) + dir |= 0x01; // R + } else { + dir |= 0x02; // ? + } + if (abs(diff_y) > 7) { + if (diff_y > 0) + dir |= 0x10; // D + } else { + dir |= 0x20; // ? + } + + if ((dir & 0x11) == (dir_prev & 0x11)) + dir_cnt++; + else + dir_cnt = 0; + dir_prev = dir; + + // text_debug_d.set(to_string_dec_uint(dir)); + + if (dir_cnt > 1) { + dir_cnt = 0; + if (move_index) { + if ((move_list[move_index - 1] != dir) && (dir != 0x22)) { + if ((dir & 0xF0) == 0x20) { + if ((move_list[move_index - 1] & 0x0F) != (dir & 0x0F)) { + move_list[move_index] = dir; + move_index++; + } + } else if ((dir & 0x0F) == 0x02) { + if ((move_list[move_index - 1] & 0xF0) != (dir & 0xF0)) { + move_list[move_index] = dir; + move_index++; + } + } else { + // Replacement ? + if (((move_list[move_index - 1] & 0xF0) == 0x20) && ((dir & 0xF0) != 0x20)) { + if ((move_list[move_index - 1] & 0x0F) == (dir & 0x0F)) { + move_list[move_index - 1] = dir; + } else if ((dir & 0x0F) == 0x02) { + // Merge + move_list[move_index - 1] = (dir & 0xF0) | (move_list[move_index - 1] & 0x0F); + } else { + move_list[move_index] = dir; + move_index++; + } + } else if (((move_list[move_index - 1] & 0x0F) == 0x02) && ((dir & 0x0F) != 0x02)) { + if ((move_list[move_index - 1] & 0xF0) == (dir & 0xF0)) { + move_list[move_index - 1] = dir; + } else if ((dir & 0xF0) == 0x20) { + // Merge + move_list[move_index - 1] = (dir & 0x0F) | (move_list[move_index - 1] & 0xF0); + } else { + move_list[move_index] = dir; + move_index++; + } + } else { + move_list[move_index] = dir; + move_index++; + } + } + } + } else { + if (dir != 0x22) { + move_list[move_index] = dir; + move_index++; + } + } + + // DEBUG + /*if (move_index) { + memset(txtinput, 0, 20); + txtidx = 0; + for (i = 0; i < move_index; i++) { + if ((move_list[i] & 0x03) == 0) char_add('L'); + if ((move_list[i] & 0x03) == 1) char_add('R'); + if ((move_list[i] & 0x03) == 2) char_add('?'); + if ((move_list[i] >> 4) == 0) char_add('U'); + if ((move_list[i] >> 4) == 1) char_add('D'); + if ((move_list[i] >> 4) == 2) char_add('?'); + char_add(' '); + } + update_text(); + }*/ + + } + } + + last_pos = current_pos; + } + } + + sample_skip++; +} + +void HandWriteView::on_show() { + // Use screen refresh rate as sampling frequency + EventDispatcher::message_map().unregister_handler(Message::ID::DisplayFrameSync); + EventDispatcher::message_map().register_handler(Message::ID::DisplayFrameSync, + [this](const Message* const) { + sample_pen(); + } + ); +} + +char * HandWriteView::value() { + return txtinput; +} + +void HandWriteView::on_button(Button& button) { + char_add(button.id + 0x30); + update_text(); +} + +void HandWriteView::char_add(const char c) { + if (txtidx < _max_len) { + txtinput[txtidx] = c; + txtidx++; + } +} + +void HandWriteView::char_delete() { + if (txtidx) { + txtidx--; + txtinput[txtidx] = ' '; + } +} + +void HandWriteView::update_text() { + text_input.set(txtinput); +} + +} diff --git a/firmware/application/ui_handwrite.hpp b/firmware/application/ui_handwrite.hpp new file mode 100644 index 000000000..25aee9838 --- /dev/null +++ b/firmware/application/ui_handwrite.hpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * 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.hpp" +#include "ui_widget.hpp" +#include "ui_painter.hpp" +#include "ui_menu.hpp" +#include "ui_navigation.hpp" +#include "ui_font_fixed_8x16.hpp" +#include "clock_manager.hpp" +#include "message.hpp" +#include "signal.hpp" + +namespace ui { + +class HandWriteView : public View { +public: + std::function on_changed; + + HandWriteView(NavigationView& nav, char txt[], uint8_t max_len); + ~HandWriteView(); + + void on_show() override; + bool on_touch(const TouchEvent event) override; + + char * value(); + + uint8_t txtidx; + + void char_add(const char c); + void char_delete(); + +private: + SignalToken signal_token_tick_second; + + uint8_t _max_len, txt_idx = 0; + uint8_t dir_cnt = 0; + uint8_t dir_prev; + bool tracing = false; + uint8_t move_index; + uint8_t sample_skip, move_wait; + uint8_t move_list[32]; // TODO: Cap ! + Point start_pos, current_pos, last_pos; + bool _lowercase = false; + static constexpr size_t button_w = 240 / 5; + static constexpr size_t button_h = 28; + char txtinput[21] = {0}; // DEBUG + + bool MM(uint8_t idx, char cmp); + bool MM(uint8_t idx, char cmpud, char cmplr); + bool MI(uint8_t idx); + bool MLAST(char cmp); + + void sample_pen(); + + Text text_input { + { 0, 0, 240, 16 } + }; + + Text text_debug_x { + { 0, 16, 32, 16 } + }; + Text text_debug_y { + { 0, 32, 32, 16 } + }; + Text text_debug_write { + { 80, 24, 150, 16 } + }; + + std::array num_buttons; + + Button button_lowercase { + { 88+64+16, 270, 32, 24 }, + "UC" + }; + + Button button_done { + { 88, 270, 64, 24 }, + "Done" + }; + + void on_button(Button& button); + + void update_text(); +}; + +} /* namespace ui */ diff --git a/firmware/application/ui_lcr.cpp b/firmware/application/ui_lcr.cpp index a0d8f60d3..fd3339578 100644 --- a/firmware/application/ui_lcr.cpp +++ b/firmware/application/ui_lcr.cpp @@ -78,6 +78,10 @@ void LCRView::make_frame() { lcrframe[5] = 127; lcrframe[6] = 127; lcrframe[7] = 15; // SOM + + strcpy(rgsb, RGSB_list[adr_code.value()]); + button_setrgsb.set_text(rgsb); + strcat(lcrframe, rgsb); strcat(lcrframe, "PA "); if (checkbox_am_a.value() == true) { @@ -191,7 +195,7 @@ LCRView::LCRView( }; transmitter_model.set_baseband_configuration({ - .mode = 1, + .mode = 3, .sampling_rate = 2280000, // Is this right ? .decimation_factor = 1, }); @@ -200,11 +204,12 @@ LCRView::LCRView( memset(litteral, 0, 5*8); memset(rgsb, 0, 5); - strcpy(rgsb, RGSB_list[29]); + strcpy(rgsb, RGSB_list[adr_code.value()]); button_setrgsb.set_text(rgsb); add_children({ { &text_recap, + &adr_code, &button_setrgsb, &button_txsetup, &checkbox_am_a, @@ -290,7 +295,9 @@ LCRView::LCRView( memcpy(shared_memory.lcrdata, lcrframe_f, 256); shared_memory.afsk_transmit_done = false; - shared_memory.afsk_repeat = (portapack::persistent_memory::afsk_config() >> 8) & 0xFF; + shared_memory.afsk_repeat = 5; //(portapack::persistent_memory::afsk_config() >> 8) & 0xFF; + + EventDispatcher::message_map().unregister_handler(Message::ID::TXDone); EventDispatcher::message_map().register_handler(Message::ID::TXDone, [this,&transmitter_model](Message* const p) { @@ -316,7 +323,49 @@ LCRView::LCRView( transmitter_model.enable(); }; - + /* + button_transmit_scan.on_select() = [this,&transmitter_model](Button&){ + make_frame(); + + shared_memory.afsk_samples_per_bit = 228000/portapack::persistent_memory::afsk_bitrate(); + shared_memory.afsk_phase_inc_mark = portapack::persistent_memory::afsk_mark_freq()*(0x40000*256)/2280; + shared_memory.afsk_phase_inc_space = portapack::persistent_memory::afsk_space_freq()*(0x40000*256)/2280; + + shared_memory.afsk_fmmod = portapack::persistent_memory::afsk_bw() * 8; + + memset(shared_memory.lcrdata, 0, 256); + memcpy(shared_memory.lcrdata, lcrframe_f, 256); + + shared_memory.afsk_transmit_done = false; + shared_memory.afsk_repeat = 5; //(portapack::persistent_memory::afsk_config() >> 8) & 0xFF; + + EventDispatcher::message_map().unregister_handler(Message::ID::TXDone); + + EventDispatcher::message_map().register_handler(Message::ID::TXDone, + [this,&transmitter_model](Message* const p) { + char str[8]; + const auto message = static_cast(p); + if (message->n > 0) { + text_status.set(" "); + strcpy(str, to_string_dec_int(message->n).c_str()); + strcat(str, "/"); + strcat(str, to_string_dec_int((portapack::persistent_memory::afsk_config() >> 8) & 0xFF).c_str()); + text_status.set(str); + } else { + text_status.set("Done ! "); + transmitter_model.disable(); + } + } + ); + + char str[8]; + strcpy(str, "0/"); + strcat(str, to_string_dec_int(shared_memory.afsk_repeat).c_str()); + text_status.set(str); + + transmitter_model.enable(); + }; + */ button_txsetup.on_select = [&nav](Button&){ nav.push(); }; diff --git a/firmware/application/ui_lcr.hpp b/firmware/application/ui_lcr.hpp index 0d282ed49..11cfb8eb7 100644 --- a/firmware/application/ui_lcr.hpp +++ b/firmware/application/ui_lcr.hpp @@ -44,6 +44,7 @@ public: void paint(Painter& painter) override; private: + uint8_t adri = 22; const char RGSB_list[37][5] = { "EAA0", "EAB0", "EAC0", "EAD0", "EbA0", "EbB0", "EbC0", "EbD0", @@ -70,9 +71,17 @@ private: }; Text text_recap { - { 32, 6, 192, 16 }, + { 8, 6, 18 * 8, 16 }, "-" }; + + NumberField adr_code { + { 220, 6 }, + 2, + { 0, 36 }, + 1, + '0' + }; Button button_setrgsb { { 16, 24, 96, 32 }, diff --git a/firmware/application/ui_loadmodule.cpp b/firmware/application/ui_loadmodule.cpp index a183a9a6f..b045bee26 100644 --- a/firmware/application/ui_loadmodule.cpp +++ b/firmware/application/ui_loadmodule.cpp @@ -35,8 +35,10 @@ #include "ui_rds.hpp" #include "ui_xylos.hpp" #include "ui_lcr.hpp" -#include "ui_audiotx.hpp" +#include "analog_audio_app.hpp" +#include "ui_soundboard.hpp" #include "ui_debug.hpp" +#include "ui_audiotx.hpp" #include #include @@ -150,9 +152,12 @@ void LoadModuleView::loadmodule() { LoadModuleView::LoadModuleView( NavigationView& nav, const char * hash, - uint8_t ViewID + uint8_t ViewID, + bool loadplz ) { + if (loadplz == false) nav.pop(); // Useless, remove ! + add_children({ { &text_info, &text_infob, @@ -163,10 +168,11 @@ LoadModuleView::LoadModuleView( button_ok.on_select = [this, &nav, ViewID](Button&){ if (_mod_loaded == true) { - if (ViewID == 0) nav.push(); + if (ViewID == 0) nav.push(); //nav.push(); if (ViewID == 1) nav.push(); if (ViewID == 2) nav.push(); - if (ViewID == 3) nav.push(); + if (ViewID == 3) nav.push(); + if (ViewID == 10) nav.push(); } else { nav.pop(); } diff --git a/firmware/application/ui_loadmodule.hpp b/firmware/application/ui_loadmodule.hpp index 37b78cd27..fa7c05a8e 100644 --- a/firmware/application/ui_loadmodule.hpp +++ b/firmware/application/ui_loadmodule.hpp @@ -32,7 +32,7 @@ namespace ui { class LoadModuleView : public View { public: - LoadModuleView(NavigationView& nav, const char * hash, uint8_t ViewID); + LoadModuleView(NavigationView& nav, const char * hash, uint8_t ViewID, bool loadplz); void loadmodule(); void on_show() override; diff --git a/firmware/application/ui_menu.cpp b/firmware/application/ui_menu.cpp index 5f584eeba..30ec2c26a 100644 --- a/firmware/application/ui_menu.cpp +++ b/firmware/application/ui_menu.cpp @@ -53,7 +53,7 @@ void MenuItemView::paint(Painter& painter) { paint_style.background ); - ui::Color final_item_color = item.color; + ui::Color final_item_color = (highlighted() && parent()->has_focus()) ? ui::Color::black() : item.color; if (final_item_color.v == paint_style.background.v) final_item_color = paint_style.foreground; diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index a58e4fe05..077fc3725 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -33,6 +33,9 @@ #include "ui_setup.hpp" #include "ui_debug.hpp" +#include "ui_handwrite.hpp" // DEBUG +#include "ui_soundboard.hpp" // DEBUG + #include "analog_audio_app.hpp" #include "ais_app.hpp" #include "ert_app.hpp" @@ -155,7 +158,7 @@ void NavigationView::focus() { } } -/* TransceiversMenuView **************************************************/ +/* TranspondersMenuView **************************************************/ TranspondersMenuView::TranspondersMenuView(NavigationView& nav) { add_items<3>({ { @@ -170,7 +173,9 @@ TranspondersMenuView::TranspondersMenuView(NavigationView& nav) { ReceiverMenuView::ReceiverMenuView(NavigationView& nav) { add_items<2>({ { - { "Audio", ui::Color::white(), [&nav](){ nav.push(); } }, + { "Audio", ui::Color::white(), [&nav](){ nav.push(md5_baseband, 10, true); } }, + + //{ "Audio", ui::Color::white(), [&nav](){ nav.push(); } }, { "Transponders", ui::Color::white(), [&nav](){ nav.push(); } }, } }); on_left = [&nav](){ nav.pop(); }; @@ -180,18 +185,18 @@ ReceiverMenuView::ReceiverMenuView(NavigationView& nav) { SystemMenuView::SystemMenuView(NavigationView& nav) { add_items<10>({ { - { "Play dead", ui::Color::red(), [&nav](){ nav.push(false); } }, - { "Receiver", ui::Color::cyan(), [&nav](){ nav.push(); } }, - { "RDS TX", ui::Color::yellow(), [&nav](){ nav.push(md5_baseband_tx, 0); } }, - { "Xylos TX", ui::Color::yellow(), [&nav](){ nav.push(md5_baseband_tx, 1); } }, - { "TEDI/LCR TX", ui::Color::yellow(), [&nav](){ nav.push(md5_baseband_tx, 2); } }, - { "Audio TX", ui::Color::orange(), [&nav](){ nav.push(md5_baseband_tx, 3); } }, - //{ "Capture", ui::Color::white(), [&nav](){ nav.push(); } }, - //{ "Analyze", ui::Color::white(), [&nav](){ nav.push(); } }, - { "Setup", ui::Color::white(), [&nav](){ nav.push(); } }, - { "About", ui::Color::white(), [&nav](){ nav.push(); } }, - { "Debug", ui::Color::white(), [&nav](){ nav.push(); } }, - { "HackRF", ui::Color::white(), [&nav](){ nav.push(); } }, + { "Play dead", ui::Color::red(), [&nav](){ nav.push(false); } }, + { "Receiver RX", ui::Color::cyan(), [&nav](){ nav.push(); } }, + { "Soundboard TX", ui::Color::orange(), [&nav](){ nav.push(md5_baseband_tx, 3, true); } }, + { "Audio TX TX", ui::Color::yellow(), [&nav](){ nav.push(md5_baseband_tx, 0, true); } }, + { "Xylos TX", ui::Color::yellow(), [&nav](){ nav.push(md5_baseband_tx, 1, true); } }, + { "TEDI/LCR TX", ui::Color::yellow(), [&nav](){ nav.push(md5_baseband_tx, 2, true); } }, + //{ "Capture", ui::Color::white(), [&nav](){ nav.push(); } }, + //{ "Analyze", ui::Color::white(), [&nav](){ nav.push(); } }, + { "Setup", ui::Color::white(), [&nav](){ nav.push(); } }, + { "About", ui::Color::white(), [&nav](){ nav.push(); } }, + { "Debug", ui::Color::white(), [&nav](){ nav.push(); } }, + { "HackRF", ui::Color::white(), [&nav](){ nav.push(); } }, } }); /* @@ -224,7 +229,8 @@ SystemView::SystemView( set_style(&style_default); constexpr ui::Dim status_view_height = 16; - + char debugtxt[21] = {0}; + add_child(&status_view); status_view.set_parent_rect({ { 0, 0 }, @@ -255,7 +261,9 @@ SystemView::SystemView( if (portapack::persistent_memory::ui_config() & 1) navigation_view.push(); else - navigation_view.push(); + //navigation_view.push(); + //navigation_view.push(); + navigation_view.push(debugtxt, 20); } Context& SystemView::context() const { diff --git a/firmware/application/ui_soundboard.cpp b/firmware/application/ui_soundboard.cpp new file mode 100644 index 000000000..c45b26674 --- /dev/null +++ b/firmware/application/ui_soundboard.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * 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_soundboard.hpp" + +#include "ch.h" + +#include "ui_alphanum.hpp" +#include "ff.h" +#include "hackrf_gpio.hpp" +#include "portapack.hpp" +#include "radio.hpp" +#include "event_m0.hpp" +#include "string_format.hpp" + +#include "hackrf_hal.hpp" +#include "portapack_shared_memory.hpp" + +#include + +using namespace portapack; + +namespace ui { + +void SoundBoardView::on_show() { + /* + // Just in case + EventDispatcher::message_map().unregister_handler(Message::ID::DisplayFrameSync); + + // "Vertical blank interrupt" + EventDispatcher::message_map().register_handler(Message::ID::DisplayFrameSync, + [this](const Message* const) { + pbar_test.set_value(testv/4); + testv++; + } + );*/ +} + +std::string SoundBoardView::title() const { + return "Sound board"; +}; + +void SoundBoardView::focus() { + buttons[0].focus(); +} + +void SoundBoardView::on_tuning_frequency_changed(rf::Frequency f) { + transmitter_model.set_tuning_frequency(f); +} + +void SoundBoardView::on_button(Button& button) { + text_test.set(to_string_dec_uint(button.id)); +} + +SoundBoardView::SoundBoardView( + NavigationView& nav +) +{ + size_t n; + + for (n = 0; n < 12; n++) { + sounds[n].filename = ""; + sounds[n].shortname = "Empty"; + sounds[n].min = 0; + sounds[n].sec = 0; + } + + add_children({ { + &text_test, + &field_frequency, + &number_bw, + &pbar_test, + &button_load, + &button_exit + } }); + + const auto button_fn = [this](Button& button) { + this->on_button(button); + }; + + for(auto& button : buttons) { + add_child(&button); + button.id = n; + button.on_select = button_fn; + button.set_parent_rect({ + static_cast((n % 3) * 70 + 15), + static_cast((n / 3) * 50 + 30), + 70, 50 + }); + button.set_text(sounds[n].shortname); + n++; + } + + field_frequency.set_value(transmitter_model.tuning_frequency()); + field_frequency.set_step(receiver_model.frequency_step()); + 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(receiver_model.tuning_frequency()); + new_view->on_changed = [this](rf::Frequency f) { + this->on_tuning_frequency_changed(f); + this->field_frequency.set_value(f); + }; + }; + + /*button_transmit.on_select = [](Button&){ + transmitter_model.set_baseband_configuration({ + .mode = 1, + .sampling_rate = 1536000, + .decimation_factor = 1, + }); + transmitter_model.set_rf_amp(true); + transmitter_model.enable(); + };*/ + + button_exit.on_select = [&nav](Button&){ + nav.pop(); + }; +} + +SoundBoardView::~SoundBoardView() { + transmitter_model.disable(); +} + +} diff --git a/firmware/application/ui_soundboard.hpp b/firmware/application/ui_soundboard.hpp new file mode 100644 index 000000000..398c5b0d0 --- /dev/null +++ b/firmware/application/ui_soundboard.hpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * 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.hpp" +#include "ui_widget.hpp" +#include "ui_painter.hpp" +#include "ui_menu.hpp" +#include "ui_navigation.hpp" +#include "ui_font_fixed_8x16.hpp" +#include "clock_manager.hpp" +#include "message.hpp" +#include "rf_path.hpp" +#include "max2837.hpp" +#include "volume.hpp" +#include "ui_receiver.hpp" +#include "transmitter_model.hpp" + +namespace ui { + +class SoundBoardView : public View { +public: + SoundBoardView(NavigationView& nav); + ~SoundBoardView(); + + std::string title() const; + void on_show() override; + void focus() override; + +private: + struct sound { + std::string filename; + std::string shortname; + uint8_t min; + uint8_t sec; + }; + + sound sounds[12]; + + std::array buttons; + void on_button(Button& button); + + void on_tuning_frequency_changed(rf::Frequency f); + + Text text_test { + { 120, 4, 64, 16 } + }; + + FrequencyField field_frequency { + { 1 * 8, 4 }, + }; + + NumberField number_bw { + { 16 * 8, 4 }, + 5, + {1000, 50000}, + 500, + ' ' + }; + + ProgressBar pbar_test { + { 45, 236, 150, 16 } + }; + + Button button_load { + { 8, 270, 64, 32 }, + "Load" + }; + + Button button_exit { + { 96, 270, 64, 32 }, + "Exit" + }; +}; + +} /* namespace ui */ diff --git a/firmware/application/ui_xylos.cpp b/firmware/application/ui_xylos.cpp index 22ff1b172..56de2e677 100644 --- a/firmware/application/ui_xylos.cpp +++ b/firmware/application/ui_xylos.cpp @@ -135,10 +135,10 @@ void XylosView::paint(Painter& painter) { void XylosView::upd_message() { uint8_t c; - ccirmessage[0] = '0'; - ccirmessage[1] = '0'; - ccirmessage[2] = '0'; - ccirmessage[3] = '0'; + ccirmessage[0] = (header_code_a.value() / 10) + 0x30; + ccirmessage[1] = (header_code_a.value() % 10) + 0x30; + ccirmessage[2] = (header_code_b.value() / 10) + 0x30; + ccirmessage[3] = (header_code_b.value() % 10) + 0x30; ccirmessage[4] = (city_code.value() / 10) + 0x30; ccirmessage[5] = (city_code.value() % 10) + 0x30; @@ -228,7 +228,7 @@ XylosView::XylosView( }; transmitter_model.set_baseband_configuration({ - .mode = 4, + .mode = 2, .sampling_rate = 1536000, .decimation_factor = 1, }); @@ -236,6 +236,9 @@ XylosView::XylosView( add_children({ { &text_title, &button_txtest, + &text_header, + &header_code_a, + &header_code_b, &text_city, &city_code, &text_family, @@ -265,11 +268,21 @@ XylosView::XylosView( family_code.set_value(1); subfamily_code.set_value(1); receiver_code.set_value(1); + header_code_a.set_value(0); + header_code_b.set_value(0); options_freq.set_selected_index(5); checkbox_wcsubfamily.set_value(true); checkbox_wcid.set_value(true); + header_code_a.on_change = [this](int32_t v) { + (void)v; + XylosView::upd_message(); + }; + header_code_b.on_change = [this](int32_t v) { + (void)v; + XylosView::upd_message(); + }; city_code.on_change = [this](int32_t v) { (void)v; XylosView::upd_message(); diff --git a/firmware/application/ui_xylos.hpp b/firmware/application/ui_xylos.hpp index 586dbb540..0d8d79c69 100644 --- a/firmware/application/ui_xylos.hpp +++ b/firmware/application/ui_xylos.hpp @@ -41,8 +41,6 @@ namespace ui { #define XYLOS_VOICE_RELAYS 21 #define XYLOS_VOICE_TRAILER 25 -void do_something(); - class XylosRXView : public View { public: XylosRXView(NavigationView& nav); @@ -152,47 +150,38 @@ public: private: bool txing = false; const rf::Frequency xylos_freqs[7] = { 31325000, 31387500, 31437500, 31475000, 31687500, 31975000, 88000000 }; - uint8_t xylos_voice_phrase[32] = { 0xFF }; - const char * xylos_voice_filenames[26] = { "zero.wav", - "one.wav", - "two.wav", - "three.wav", - "four.wav", - "five.wav", - "six.wav", - "seven.wav", - "eight.wav", - "nine.wav", - "a.wav", - "b.wav", - "c.wav", - "d.wav", - "e.wav", - "f.wav", - "header.wav", - "city.wav", - "family.wav", - "subfamily.wav", - "address.wav", - "relays.wav", - "ignored.wav", - "off.wav", - "on.wav", - "trailer.wav" - }; char ccirmessage[21]; char ccir_received[21]; Text text_title { - { 1 * 8, 1 * 16, 11, 16 }, + { 8, 8, 11, 16 }, "BH Xylos TX" }; Button button_txtest { - { 170, 1 * 16, 40, 24 }, + { 180, 8, 40, 24 }, "TEST" }; + Text text_header { + { 8 * 8, 2 * 16, 7 * 8, 16 }, + "Header:" + }; + NumberField header_code_a { + { 16 * 8, 2 * 16 }, + 2, + { 0, 99 }, + 1, + '0' + }; + NumberField header_code_b { + { 18 * 8, 2 * 16 }, + 2, + { 0, 99 }, + 1, + '0' + }; + Text text_city { { 4 * 8, 3 * 16, 11 * 8, 16 }, "Code ville:" @@ -256,7 +245,7 @@ private: "Frequence:" }; OptionsField options_freq { - { 16 * 8, 9 * 16 }, + { 16 * 8, 9 * 16 + 4}, 7, { { "31.3250", 0 }, diff --git a/firmware/baseband-tx.bin b/firmware/baseband-tx.bin index f456c721b..3e65f73ab 100644 Binary files a/firmware/baseband-tx.bin and b/firmware/baseband-tx.bin differ diff --git a/firmware/baseband-tx/Makefile b/firmware/baseband-tx/Makefile index 0b07fa9dd..48f288b78 100755 --- a/firmware/baseband-tx/Makefile +++ b/firmware/baseband-tx/Makefile @@ -140,6 +140,8 @@ CPPSRC = main.cpp \ matched_filter.cpp \ proc_audiotx.cpp \ proc_playaudio.cpp \ + proc_xylos.cpp \ + proc_fsk_lcr.cpp \ dsp_squelch.cpp \ clock_recovery.cpp \ packet_builder.cpp \ diff --git a/firmware/baseband-tx/audio_compressor.cpp b/firmware/baseband-tx/audio_compressor.cpp new file mode 100644 index 000000000..95bbaee30 --- /dev/null +++ b/firmware/baseband-tx/audio_compressor.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * 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 "audio_compressor.hpp" + +float GainComputer::operator()(const float x) const { + const auto abs_x = std::abs(x); + const auto db = (abs_x < lin_floor) ? db_floor : log2_db_k * fast_log2(abs_x); + const auto overshoot_db = db - threshold_db; + if( knee_width_db > 0.0f ) { + const auto w2 = knee_width_db / 2.0f; + const auto a = w2 / (knee_width_db * knee_width_db); + const auto in_transition = (overshoot_db > -w2) && (overshoot_db < w2); + const auto rectified_overshoot = in_transition ? (a * std::pow(overshoot_db + w2, 2.0f)) : std::max(overshoot_db, 0.0f); + return rectified_overshoot * slope; + } else { + const auto rectified_overshoot = std::max(overshoot_db, 0.0f); + return rectified_overshoot * slope; + } +} + +void FeedForwardCompressor::execute_in_place(const buffer_f32_t& buffer) { + constexpr float makeup_gain = std::pow(10.0f, (threshold - (threshold / ratio)) / -20.0f); + for(size_t i=0; i + +/* Code based on article in Journal of the Audio Engineering Society + * Vol. 60, No. 6, 2012 June, by Dimitrios Giannoulis, Michael Massberg, + * Joshua D. Reiss "Digital Dynamic Range Compressor Design – A Tutorial + * and Analysis" + */ + +class GainComputer { +public: + constexpr GainComputer( + float ratio, + float threshold + ) : ratio { ratio }, + slope { 1.0f / ratio - 1.0f }, + threshold_db { threshold } + { + } + + float operator()(const float x) const; + +private: + const float ratio; + const float slope; + const float threshold_db; + + static constexpr float knee_width_db = 0.0f; + + static constexpr float db_floor = -120.0f; + static constexpr float lin_floor = std::pow(10.0f, db_floor / 20.0f); + static constexpr float log2_db_k = 20.0f * std::log10(2.0f); +}; + +class PeakDetectorBranchingSmooth { +public: + constexpr PeakDetectorBranchingSmooth( + float att_a, + float rel_a + ) : att_a { att_a }, + rel_a { rel_a } + { + } + + float operator()(const float db) { + const auto a = (db > state) ? att_a : rel_a; + state = db + a * (state - db); + return state; + } + +private: + float state { 0.0f }; + const float att_a; + const float rel_a; +}; + +class FeedForwardCompressor { +public: + void execute_in_place(const buffer_f32_t& buffer); + +private: + static constexpr float fs = 12000.0f; + static constexpr float ratio = 10.0f; + static constexpr float threshold = -30.0f; + + GainComputer gain_computer { ratio, threshold }; + PeakDetectorBranchingSmooth peak_detector { tau_alpha(0.010f, fs), tau_alpha(0.300f, fs) }; + + float execute_once(const float x); + + static constexpr float tau_alpha(const float tau, const float fs) { + return std::exp(-1.0f / (tau * fs)); + } +}; + +#endif/*__AUDIO_COMPRESSOR_H__*/ diff --git a/firmware/baseband-tx/baseband_thread.cpp b/firmware/baseband-tx/baseband_thread.cpp index d87f43b0d..e6d77a704 100644 --- a/firmware/baseband-tx/baseband_thread.cpp +++ b/firmware/baseband-tx/baseband_thread.cpp @@ -33,6 +33,8 @@ #include "proc_playaudio.hpp" #include "proc_audiotx.hpp" +#include "proc_xylos.hpp" +#include "proc_fsk_lcr.hpp" #include "portapack_shared_memory.hpp" @@ -120,6 +122,8 @@ BasebandProcessor* BasebandThread::create_processor(const int32_t mode) { switch(mode) { case 0: return new PlayAudioProcessor(); case 1: return new AudioTXProcessor(); + case 2: return new XylosProcessor(); + case 3: return new LCRFSKProcessor(); default: return nullptr; } } diff --git a/firmware/baseband-tx/ook.hpp b/firmware/baseband-tx/ook.hpp new file mode 100644 index 000000000..273339229 --- /dev/null +++ b/firmware/baseband-tx/ook.hpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * 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 __OOK_HPP__ +#define __OOK_HPP__ + +#include "phase_detector.hpp" +#include "phase_accumulator.hpp" + +#include +#include +#include +#include + +class OOKSlicerMagSquaredInt { +public: + using symbol_t = bool; + + constexpr OOKSlicerMagSquaredInt( + const float samples_per_symbol + ) : mag2_threshold_leak_factor { + static_cast( + factor_sq(-1.0f / (8.0f * samples_per_symbol)) * float(1ULL << 32) + ) + } + { + } + + symbol_t operator()(const std::complex in) { + const uint32_t real2 = in.real() * in.real(); + const uint32_t imag2 = in.imag() * in.imag(); + const uint32_t mag2 = real2 + imag2; + + const uint32_t mag2_attenuated = mag2 >> 3; // Approximation of (-4.5dB)^2 + mag2_threshold = (uint64_t(mag2_threshold) * uint64_t(mag2_threshold_leak_factor)) >> 32; + mag2_threshold = std::max(mag2_threshold, mag2_attenuated); + const bool symbol = (mag2 > mag2_threshold); + return symbol; + } + +private: + const uint32_t mag2_threshold_leak_factor; + uint32_t mag2_threshold = 0; + + constexpr float factor_sq(float db) { + return std::pow(10.0f, db / (10.0f / 2)); + } +}; + +class OOKClockRecovery { +public: + constexpr OOKClockRecovery( + const float samples_per_symbol + ) : symbol_phase_inc_nominal { static_cast(std::round((1ULL << 32) / samples_per_symbol)) }, + phase_detector { samples_per_symbol }, + phase_accumulator { symbol_phase_inc_nominal } + { + } + + template + void operator()(const uint32_t slicer_history, SymbolHandler symbol_handler) { + if( phase_accumulator() ) { + const auto detector_result = phase_detector(slicer_history); + phase_accumulator.set_inc(symbol_phase_inc_nominal + detector_result.error * (symbol_phase_inc_nominal >> 3)); + symbol_handler(detector_result.symbol); + } + } + +private: + const uint32_t symbol_phase_inc_nominal; + PhaseDetectorEarlyLateGate phase_detector; + PhaseAccumulator phase_accumulator; +}; + +#endif/*__OOK_HPP__*/ diff --git a/firmware/baseband-tx/phase_accumulator.hpp b/firmware/baseband-tx/phase_accumulator.hpp new file mode 100644 index 000000000..bf3b331a3 --- /dev/null +++ b/firmware/baseband-tx/phase_accumulator.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * 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 __PHASE_ACCUMULATOR_HPP__ +#define __PHASE_ACCUMULATOR_HPP__ + +#include + +class PhaseAccumulator { +public: + constexpr PhaseAccumulator( + const uint32_t phase_inc + ) : phase_inc { phase_inc } + { + } + + bool operator()() { + const auto last_phase = phase; + phase += phase_inc; + return (phase < last_phase); + } + + void set_inc(const uint32_t new_phase_inc) { + phase_inc = new_phase_inc; + } + +private: + uint32_t phase { 0 }; + uint32_t phase_inc; +}; + +#endif/*__PHASE_ACCUMULATOR_HPP__*/ diff --git a/firmware/baseband-tx/phase_detector.hpp b/firmware/baseband-tx/phase_detector.hpp new file mode 100644 index 000000000..89cf4b5ca --- /dev/null +++ b/firmware/baseband-tx/phase_detector.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * 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 __PHASE_DETECTOR_HPP__ +#define __PHASE_DETECTOR_HPP__ + +#include +#include +#include + +class PhaseDetectorEarlyLateGate { +public: + using history_t = uint32_t; + + using symbol_t = bool; + using error_t = int; + + struct result_t { + symbol_t symbol; + error_t error; + }; + + constexpr PhaseDetectorEarlyLateGate( + const float samples_per_symbol + ) : late_mask { (1U << static_cast(std::ceil(samples_per_symbol / 2))) - 1 }, + early_mask { late_mask << static_cast(std::floor(samples_per_symbol / 2)) }, + sample_bit { static_cast(std::floor(samples_per_symbol / 2)) } + { + } + + result_t operator()(const history_t symbol_history) const { + // history = ...0111, early + // history = ...1110, late + + const symbol_t symbol = (symbol_history >> sample_bit) & 1; + const int late_side = __builtin_popcount(symbol_history & late_mask); + const int early_side = __builtin_popcount(symbol_history & early_mask); + const int lateness = late_side - early_side; + const int direction = lateness; //std::min(std::max(lateness, -1), 1); + const error_t error = direction; + return { symbol, error }; + } + +private: + const history_t late_mask; + const history_t early_mask; + const size_t sample_bit; +}; + +#endif/*__PHASE_DETECTOR_HPP__*/ diff --git a/firmware/baseband-tx/proc_audiotx.cpp b/firmware/baseband-tx/proc_audiotx.cpp index a619a620c..b47cd02c9 100644 --- a/firmware/baseband-tx/proc_audiotx.cpp +++ b/firmware/baseband-tx/proc_audiotx.cpp @@ -23,63 +23,9 @@ #include "proc_audiotx.hpp" #include "portapack_shared_memory.hpp" #include "sine_table.hpp" -#include "audio_output.hpp" -#include "lfsr_random.hpp" #include -uint32_t lfsr(uint32_t v) { - - enum { - length = 31, - tap_0 = 31, - tap_1 = 18, - shift_amount_0 = 12, - shift_amount_1 = 12, - shift_amount_2 = 8 - }; - - const lfsr_word_t zero = 0; - v = ( - ( - v << shift_amount_0 - ) | ( - ( - (v >> (tap_0 - shift_amount_0)) ^ - (v >> (tap_1 - shift_amount_0)) - ) & ( - ~(~zero << shift_amount_0) - ) - ) - ); - v = ( - ( - v << shift_amount_1 - ) | ( - ( - (v >> (tap_0 - shift_amount_1)) ^ - (v >> (tap_1 - shift_amount_1)) - ) & ( - ~(~zero << shift_amount_1) - ) - ) - ); - v = ( - ( - v << shift_amount_2 - ) | ( - ( - (v >> (tap_0 - shift_amount_2)) ^ - (v >> (tap_1 - shift_amount_2)) - ) & ( - ~(~zero << shift_amount_2) - ) - ) - ); - - return v; -} - void AudioTXProcessor::execute(const buffer_c8_t& buffer){ for (size_t i = 0; i class AudioTXProcessor : public BasebandProcessor { diff --git a/firmware/baseband-tx/proc_tpms.cpp b/firmware/baseband-tx/proc_tpms.cpp new file mode 100644 index 000000000..9d401a1c1 --- /dev/null +++ b/firmware/baseband-tx/proc_tpms.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * 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_tpms.hpp" + +#include "dsp_fir_taps.hpp" + +TPMSProcessor::TPMSProcessor() { + decim_0.configure(taps_200k_decim_0.taps, 33554432); + decim_1.configure(taps_200k_decim_1.taps, 131072); +} + +void TPMSProcessor::execute(const buffer_c8_t& buffer) { + /* 2.4576MHz, 2048 samples */ + + const auto decim_0_out = decim_0.execute(buffer, dst_buffer); + const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer); + const auto decimator_out = decim_1_out; + + /* 307.2kHz, 256 samples */ + feed_channel_stats(decimator_out); + + for(size_t i=0; ipacket_builder_ook_subaru.execute(symbol); + }); + ook_clock_recovery_gmc(slicer_history, [this](const bool symbol) { + this->packet_builder_ook_gmc.execute(symbol); + }); + } +} + +void TPMSProcessor::consume_symbol( + const float raw_symbol +) { + const uint_fast8_t sliced_symbol = (raw_symbol >= 0.0f) ? 1 : 0; + packet_builder.execute(sliced_symbol); +} + +void TPMSProcessor::payload_handler( + const baseband::Packet& packet +) { + const TPMSPacketMessage message { tpms::SignalType::FLM, packet }; + shared_memory.application_queue.push(message); +} diff --git a/firmware/baseband-tx/proc_tpms.hpp b/firmware/baseband-tx/proc_tpms.hpp new file mode 100644 index 000000000..759f73836 --- /dev/null +++ b/firmware/baseband-tx/proc_tpms.hpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * 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_TPMS_H__ +#define __PROC_TPMS_H__ + +#include "baseband_processor.hpp" + +#include "channel_decimator.hpp" +#include "matched_filter.hpp" + +#include "clock_recovery.hpp" +#include "symbol_coding.hpp" +#include "packet_builder.hpp" +#include "baseband_packet.hpp" + +#include "ook.hpp" + +#include "message.hpp" +#include "portapack_shared_memory.hpp" + +#include +#include +#include + +// Translate+rectangular filter +// sample=307.2k, deviation=38400, symbol=19200 +// Length: 16 taps, 1 symbols, 2 cycles of sinusoid +constexpr std::array, 16> rect_taps_307k2_1t_p { { + { 6.2500000000e-02f, 0.0000000000e+00f }, { 4.4194173824e-02f, 4.4194173824e-02f }, + { 0.0000000000e+00f, 6.2500000000e-02f }, { -4.4194173824e-02f, 4.4194173824e-02f }, + { -6.2500000000e-02f, 0.0000000000e+00f }, { -4.4194173824e-02f, -4.4194173824e-02f }, + { 0.0000000000e+00f, -6.2500000000e-02f }, { 4.4194173824e-02f, -4.4194173824e-02f }, + { 6.2500000000e-02f, 0.0000000000e+00f }, { 4.4194173824e-02f, 4.4194173824e-02f }, + { 0.0000000000e+00f, 6.2500000000e-02f }, { -4.4194173824e-02f, 4.4194173824e-02f }, + { -6.2500000000e-02f, 0.0000000000e+00f }, { -4.4194173824e-02f, -4.4194173824e-02f }, + { 0.0000000000e+00f, -6.2500000000e-02f }, { 4.4194173824e-02f, -4.4194173824e-02f }, +} }; + +class TPMSProcessor : public BasebandProcessor { +public: + TPMSProcessor(); + + void execute(const buffer_c8_t& buffer) override; + +private: + std::array dst; + const buffer_c16_t dst_buffer { + dst.data(), + dst.size() + }; + + dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0; + dsp::decimate::FIRC16xR16x16Decim2 decim_1; + + dsp::matched_filter::MatchedFilter mf { rect_taps_307k2_1t_p, 8 }; + + clock_recovery::ClockRecovery clock_recovery { + 38400, 19200, { 0.0555f }, + [this](const float symbol) { this->consume_symbol(symbol); } + }; + PacketBuilder packet_builder { + { 0b010101010101010101010101010110, 30, 1 }, + { }, + { 256 }, + [this](const baseband::Packet& packet) { + this->payload_handler(packet); + } + }; + + static constexpr float channel_rate_in = 307200.0f; + static constexpr size_t channel_decimation = 8; + static constexpr float channel_sample_rate = channel_rate_in / channel_decimation; + OOKSlicerMagSquaredInt ook_slicer_5sps { 5 }; + uint32_t slicer_history { 0 }; + + OOKClockRecovery ook_clock_recovery_subaru { + channel_sample_rate / 8192.0f + }; + + PacketBuilder packet_builder_ook_subaru { + { 0b010101010101010101011110, 24, 0 }, + { }, + { 80 }, + [](const baseband::Packet& packet) { + const TPMSPacketMessage message { tpms::SignalType::Subaru, packet }; + shared_memory.application_queue.push(message); + } + }; + OOKClockRecovery ook_clock_recovery_gmc { + channel_sample_rate / 8400.0f + }; + + PacketBuilder packet_builder_ook_gmc { + { 0b01010101010101010101010101100101, 32, 0 }, + { }, + { 192 }, + [](const baseband::Packet& packet) { + const TPMSPacketMessage message { tpms::SignalType::GMC, packet }; + shared_memory.application_queue.push(message); + } + }; + void consume_symbol(const float symbol); + void payload_handler(const baseband::Packet& packet); +}; + +#endif/*__PROC_TPMS_H__*/ diff --git a/firmware/baseband-tx/proc_xylos.cpp b/firmware/baseband-tx/proc_xylos.cpp index b3da393d3..61cdd2416 100644 --- a/firmware/baseband-tx/proc_xylos.cpp +++ b/firmware/baseband-tx/proc_xylos.cpp @@ -23,7 +23,7 @@ #include "proc_xylos.hpp" #include "dsp_iir_config.hpp" -//#include "audio_output.hpp" +#include "audio_output.hpp" #include "portapack_shared_memory.hpp" #include "sine_table.hpp" @@ -75,7 +75,7 @@ void XylosProcessor::execute(const buffer_c8_t& buffer) { // Audio preview sample generation: 1536000/48000 = 32 if (as >= 31) { as = 0; - preview_audio_buffer.p[ai++] = sample * 128; + audio[ai++] = sample * 128; } else { as++; } @@ -92,5 +92,5 @@ void XylosProcessor::execute(const buffer_c8_t& buffer) { buffer.p[i] = {(int8_t)re,(int8_t)im}; } - //audio_output.write(preview_audio_buffer); + //audio_output.write(audio_buffer); } diff --git a/firmware/baseband-tx/proc_xylos.hpp b/firmware/baseband-tx/proc_xylos.hpp index 0add67121..97695997a 100644 --- a/firmware/baseband-tx/proc_xylos.hpp +++ b/firmware/baseband-tx/proc_xylos.hpp @@ -28,7 +28,7 @@ #include "dsp_decimate.hpp" #include "dsp_demodulate.hpp" -//#include "audio_output.hpp" +#include "audio_output.hpp" #include "baseband_processor.hpp" #define CCIR_TONELENGTH 15360-1 // 1536000/10/10 @@ -40,11 +40,12 @@ public: private: int16_t audio_data[64]; - - const buffer_s16_t preview_audio_buffer { - audio_data, - sizeof(int16_t)*64 - }; + + std::array audio; + /*const buffer_s16_t audio_buffer { + audio.data(), + audio.size() + };*/ uint32_t ccir_phases[16] = { (uint32_t)(1981*PHASEV), @@ -74,7 +75,7 @@ private: int32_t sample, frq; TXDoneMessage message; - //AudioOutput audio_output; + AudioOutput audio_output; }; #endif diff --git a/firmware/baseband-tx/stream_input.hpp b/firmware/baseband-tx/stream_input.hpp new file mode 100644 index 000000000..1b1ec552a --- /dev/null +++ b/firmware/baseband-tx/stream_input.hpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * 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 __STREAM_INPUT_H__ +#define __STREAM_INPUT_H__ + +#include "portapack_shared_memory.hpp" + +#include "fifo.hpp" + +#include +#include +#include + +class StreamInput { +public: + StreamInput(const size_t K) : + K { K }, + data { std::make_unique(1UL << K) }, + fifo { data.get(), K } + { + // TODO: Send stream creation message. + shared_memory.FIFO_HACK = &fifo; + } + + ~StreamInput() { + // TODO: Send stream distruction message. + shared_memory.FIFO_HACK = nullptr; + } + + size_t write(const void* const data, const size_t length) { + const auto written = fifo.in(reinterpret_cast(data), length); + + const auto last_bytes_written = bytes_written; + bytes_written += written; + if( (bytes_written & event_bytes_mask) < (last_bytes_written & event_bytes_mask) ) { + creg::m4txevent::assert(); + } + + return written; + } + + uint64_t written() const { + return bytes_written; + } + +private: + const size_t K; + const uint64_t event_bytes_mask = (1ULL << (K - 2)) - 1; + uint64_t bytes_written = 0; + std::unique_ptr data; + FIFO fifo; +}; + +#endif/*__STREAM_INPUT_H__*/ diff --git a/firmware/baseband.bin b/firmware/baseband.bin index ab19dc42d..7f9b8511d 100644 Binary files a/firmware/baseband.bin and b/firmware/baseband.bin differ diff --git a/firmware/common/modules.h b/firmware/common/modules.h index 5810db698..3cd201d06 100644 --- a/firmware/common/modules.h +++ b/firmware/common/modules.h @@ -1,2 +1,2 @@ -const char md5_baseband[16] = {0x37,0x04,0xf7,0x51,0x68,0x66,0x8a,0x20,0x73,0xa0,0xf7,0x69,0xa2,0xe2,0xb4,0x3a,}; -const char md5_baseband_tx[16] = {0x6c,0x1a,0x90,0x6b,0x68,0x78,0x6e,0xd2,0x08,0x3b,0x05,0xb1,0xbe,0x61,0xf8,0xe7,}; +const char md5_baseband[16] = {0xf2,0x01,0x2e,0xbf,0xc2,0xee,0x9f,0x0e,0x36,0x75,0x0e,0xb7,0x87,0x28,0x49,0xbd,}; +const char md5_baseband_tx[16] = {0xb1,0xe1,0xca,0x79,0x83,0x86,0x2f,0x20,0xba,0x94,0xd3,0x0c,0xc7,0x9d,0x43,0xe2,}; diff --git a/firmware/common/ui.hpp b/firmware/common/ui.hpp index fa2f0fa5a..bee55b86c 100644 --- a/firmware/common/ui.hpp +++ b/firmware/common/ui.hpp @@ -30,12 +30,18 @@ using Coord = int16_t; using Dim = int16_t; struct Color { - uint16_t v; + uint16_t v; // rrrrrGGGGGGbbbbb constexpr Color( ) : v { 0 } { } + + constexpr Color( + uint16_t v + ) : v { v } + { + } constexpr Color( uint8_t r, @@ -49,6 +55,10 @@ struct Color { )} { } + + Color operator-() const { + return (v ^ 0xffff); + } static constexpr Color black() { return { 0, 0, 0 }; @@ -75,7 +85,7 @@ struct Color { } static constexpr Color cyan() { - return { 0, 128, 255 }; + return { 0, 255, 255 }; } static constexpr Color white() { diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 31d03ebf3..d8a0aee97 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -292,6 +292,36 @@ void Text::paint(Painter& painter) { ); } +/* ProgressBar ***********************************************************/ + +ProgressBar::ProgressBar( + Rect parent_rect +) : Widget { parent_rect } +{ +} + +void ProgressBar::set_value(const uint16_t value) { + if (value > 100) + _value = 100; + else + _value = value; + set_dirty(); +} + +void ProgressBar::paint(Painter& painter) { + uint16_t v_sized; + + const auto rect = screen_rect(); + const auto s = style(); + + v_sized = (rect.size.w * _value) / 100; + + painter.fill_rectangle({rect.pos, {v_sized, rect.size.h}}, ui::Color::green()); + painter.fill_rectangle({{rect.pos.x + v_sized, rect.pos.y}, {rect.size.w - v_sized, rect.size.h}}, s.background); + + painter.draw_rectangle(rect, s.foreground); +} + /* Checkbox **************************************************************/ Checkbox::Checkbox( @@ -427,12 +457,16 @@ void Button::paint(Painter& painter) { paint_style.background ); - const auto label_r = paint_style.font.size_of(text_); - painter.draw_string( - { r.pos.x + (r.size.w - label_r.w) / 2, r.pos.y + (r.size.h - label_r.h) / 2 }, - paint_style, - text_ - ); + //char *token = strtok(text_.c_str(), "\n"); + //while(token) { + const auto label_r = paint_style.font.size_of(text_); + painter.draw_string( + { r.pos.x + (r.size.w - label_r.w) / 2, r.pos.y + (r.size.h - label_r.h) / 2 }, + paint_style, + text_ + ); + // token = strtok(NULL, " "); + //} } bool Button::on_key(const KeyEvent key) { diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index f154b86b6..4a22f3f21 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -111,6 +111,8 @@ public: bool highlighted() const; void set_highlighted(const bool value); + + uint16_t id = 0; protected: void dirty_overlapping_children_in_rect(const Rect& child_rect); @@ -195,6 +197,19 @@ private: std::string text; }; +class ProgressBar : public Widget { +public: + ProgressBar(Rect parent_rect); + + void set_value(const uint16_t value); + uint16_t value() const; + + void paint(Painter& painter) override; + +private: + uint16_t _value = 0; +}; + class Checkbox : public Widget { public: std::function on_select; diff --git a/firmware/portapack-h1-firmware.bin b/firmware/portapack-h1-firmware.bin index a28ebe8f4..47fe19478 100644 Binary files a/firmware/portapack-h1-firmware.bin and b/firmware/portapack-h1-firmware.bin differ diff --git a/sdcard/baseband-tx.bin b/sdcard/baseband-tx.bin index f456c721b..3e65f73ab 100644 Binary files a/sdcard/baseband-tx.bin and b/sdcard/baseband-tx.bin differ diff --git a/sdcard/baseband.bin b/sdcard/baseband.bin index ab19dc42d..7f9b8511d 100644 Binary files a/sdcard/baseband.bin and b/sdcard/baseband.bin differ