diff --git a/firmware/application/Makefile b/firmware/application/Makefile index e6d6daa9..cb1ab48e 100755 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -118,11 +118,11 @@ CSRC = $(PORTSRC) \ # C++ sources that can be compiled in ARM or THUMB mode depending on the global # setting. CPPSRC = main.cpp \ - irq_ipc.cpp \ irq_lcd_frame.cpp \ irq_controls.cpp \ irq_rtc.cpp \ event.cpp \ + event_m0.cpp \ message_queue.cpp \ hackrf_hal.cpp \ portapack.cpp \ @@ -136,7 +136,6 @@ CPPSRC = main.cpp \ wm8731.cpp \ radio.cpp \ baseband_cpld.cpp \ - baseband_sgpio.cpp \ tuning.cpp \ rf_path.cpp \ rffc507x.cpp \ @@ -149,26 +148,20 @@ CPPSRC = main.cpp \ encoder.cpp \ lcd_ili9341.cpp \ ui.cpp \ - ui_alphanum.cpp \ ui_text.cpp \ ui_widget.cpp \ ui_painter.cpp \ ui_focus.cpp \ ui_navigation.cpp \ ui_menu.cpp \ - ui_about.cpp \ ui_rssi.cpp \ ui_channel.cpp \ ui_audio.cpp \ ui_font_fixed_8x16.cpp \ ui_setup.cpp \ ui_debug.cpp \ - ui_rds.cpp \ - ui_lcr.cpp \ - ui_whistle.cpp \ - ui_jammer.cpp \ - ui_afsksetup.cpp \ ui_baseband_stats_view.cpp \ + ui_sd_card_status_view.cpp \ ui_console.cpp \ ui_receiver.cpp \ ui_spectrum.cpp \ @@ -177,10 +170,24 @@ CPPSRC = main.cpp \ ui_sigfrx.cpp \ ui_xylos.cpp \ ui_numbers.cpp \ + recent_entries.cpp \ receiver_model.cpp \ transmitter_model.cpp \ spectrum_color_lut.cpp \ + analog_audio_app.cpp \ ais_baseband.cpp \ + ../commom/ais_packet.cpp \ + ais_app.cpp \ + tpms_app.cpp \ + ert_app.cpp \ + ../common/ert_packet.cpp \ + spectrum_analysis_app.cpp \ + sd_card.cpp \ + file.cpp \ + log_file.cpp \ + manchester.cpp \ + string_format.cpp \ + temperature_logger.cpp \ ../common/utility.cpp \ ../common/chibios_cpp.cpp \ ../common/debug.cpp \ diff --git a/firmware/application/ais_app.cpp b/firmware/application/ais_app.cpp new file mode 100644 index 00000000..c3867e34 --- /dev/null +++ b/firmware/application/ais_app.cpp @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2014 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 "ais_app.hpp" + +#include "event_m0.hpp" + +#include "string_format.hpp" + +#include "portapack.hpp" +using namespace portapack; + +#include + +namespace ais { +namespace format { + +static std::string latlon_abs_normalized(const int32_t normalized, const char suffixes[2]) { + const auto suffix = suffixes[(normalized < 0) ? 0 : 1]; + const uint32_t normalized_abs = std::abs(normalized); + const uint32_t t = (normalized_abs * 5) / 3; + const uint32_t degrees = t / (100 * 10000); + const uint32_t fraction = t % (100 * 10000); + return to_string_dec_uint(degrees) + "." + to_string_dec_uint(fraction, 6, '0') + suffix; +} + +static std::string latitude(const Latitude value) { + if( value.is_not_available() ) { + return "not available"; + } else if( value.is_valid() ) { + return latlon_abs_normalized(value.normalized(), "SN"); + } else { + return "invalid"; + } +} + +static std::string longitude(const Longitude value) { + if( value.is_not_available() ) { + return "not available"; + } else if( value.is_valid() ) { + return latlon_abs_normalized(value.normalized(), "WE"); + } else { + return "invalid"; + } +} + +static std::string latlon(const Latitude latitude, const Longitude longitude) { + if( latitude.is_valid() && longitude.is_valid() ) { + return latlon_abs_normalized(latitude.normalized(), "SN") + " " + latlon_abs_normalized(longitude.normalized(), "WE"); + } else if( latitude.is_not_available() && longitude.is_not_available() ) { + return "not available"; + } else { + return "invalid"; + } +} + +static std::string mmsi( + const ais::MMSI& mmsi +) { + return to_string_dec_uint(mmsi, 9); +} + +static std::string datetime( + const ais::DateTime& datetime +) { + return to_string_dec_uint(datetime.year, 4, '0') + "/" + + to_string_dec_uint(datetime.month, 2, '0') + "/" + + to_string_dec_uint(datetime.day, 2, '0') + " " + + to_string_dec_uint(datetime.hour, 2, '0') + ":" + + to_string_dec_uint(datetime.minute, 2, '0') + ":" + + to_string_dec_uint(datetime.second, 2, '0'); +} + +static std::string navigational_status(const unsigned int value) { + switch(value) { + case 0: return "under way w/engine"; + case 1: return "at anchor"; + case 2: return "not under command"; + case 3: return "restricted maneuv"; + case 4: return "constrained draught"; + case 5: return "moored"; + case 6: return "aground"; + case 7: return "fishing"; + case 8: return "sailing"; + case 9: case 10: case 13: return "reserved"; + case 11: return "towing astern"; + case 12: return "towing ahead/along"; + case 14: return "SART/MOB/EPIRB"; + case 15: return "undefined"; + default: return "unknown"; + } +} + +static std::string rate_of_turn(const RateOfTurn value) { + switch(value) { + case -128: return "not available"; + case -127: return "left >5 deg/30sec"; + case 0: return "0 deg/min"; + case 127: return "right >5 deg/30sec"; + default: + { + std::string result = (value < 0) ? "left " : "right "; + const float value_deg_sqrt = value / 4.733f; + const int32_t value_deg = value_deg_sqrt * value_deg_sqrt; + result += to_string_dec_uint(value_deg); + result += " deg/min"; + return result; + } + } +} + +static std::string speed_over_ground(const SpeedOverGround value) { + if( value == 1023 ) { + return "not available"; + } else if( value == 1022 ) { + return ">= 102.2 knots"; + } else { + return to_string_dec_uint(value / 10) + "." + to_string_dec_uint(value % 10, 1) + " knots"; + } +} + +static std::string course_over_ground(const CourseOverGround value) { + if( value > 3600 ) { + return "invalid"; + } else if( value == 3600 ) { + return "not available"; + } else { + return to_string_dec_uint(value / 10) + "." + to_string_dec_uint(value % 10, 1) + " deg"; + } +} + +static std::string true_heading(const TrueHeading value) { + if( value == 511 ) { + return "not available"; + } else if( value > 359 ) { + return "invalid"; + } else { + return to_string_dec_uint(value) + " deg"; + } +} + +} /* namespace format */ +} /* namespace ais */ + +void AISLogger::on_packet(const ais::Packet& packet) { + // TODO: Unstuff here, not in baseband! + if( log_file.is_ready() ) { + std::string entry; + entry.reserve((packet.length() + 3) / 4); + + for(size_t i=0; i= 10) ? ('W' + nibble) : ('0' + nibble); + } + + log_file.write_entry(packet.received_at(), entry); + } +} + +void AISRecentEntry::update(const ais::Packet& packet) { + received_count++; + + switch(packet.message_id()) { + case 1: + case 2: + case 3: + navigational_status = packet.read(38, 4); + last_position.rate_of_turn = packet.read(42, 8); + last_position.speed_over_ground = packet.read(50, 10); + last_position.timestamp = packet.received_at(); + last_position.latitude = packet.latitude(89); + last_position.longitude = packet.longitude(61); + last_position.course_over_ground = packet.read(116, 12); + last_position.true_heading = packet.read(128, 9); + break; + + case 4: + last_position.timestamp = packet.received_at(); + last_position.latitude = packet.latitude(107); + last_position.longitude = packet.longitude(79); + break; + + case 5: + call_sign = packet.text(70, 7); + name = packet.text(112, 20); + destination = packet.text(302, 20); + break; + + case 21: + name = packet.text(43, 20); + last_position.timestamp = packet.received_at(); + last_position.latitude = packet.latitude(192); + last_position.longitude = packet.longitude(164); + break; + + default: + break; + } +} + +namespace ui { + +static const std::array, 2> ais_columns { { + { "MMSI", 9 }, + { "Name/Call", 20 }, +} }; + +template<> +void RecentEntriesView::draw_header( + const Rect& target_rect, + Painter& painter, + const Style& style +) { + auto x = 0; + for(const auto& column : ais_columns) { + const auto width = column.second; + auto text = column.first; + if( width > text.length() ) { + text.append(width - text.length(), ' '); + } + + painter.draw_string({ x, target_rect.pos.y }, style, text); + x += (width * 8) + 8; + } +} + +template<> +void RecentEntriesView::draw( + const Entry& entry, + const Rect& target_rect, + Painter& painter, + const Style& style, + const bool is_selected +) { + const auto& draw_style = is_selected ? style.invert() : style; + + std::string line = ais::format::mmsi(entry.mmsi) + " "; + if( !entry.name.empty() ) { + line += entry.name; + } else { + line += entry.call_sign; + } + + line.resize(target_rect.width() / 8, ' '); + painter.draw_string(target_rect.pos, draw_style, line); +} + +AISRecentEntryDetailView::AISRecentEntryDetailView() { + add_children({ { + &button_done, + } }); + + button_done.on_select = [this](const ui::Button&) { + if( this->on_close ) { + this->on_close(); + } + }; +} + +void AISRecentEntryDetailView::focus() { + button_done.focus(); +} + +Rect AISRecentEntryDetailView::draw_field( + Painter& painter, + const Rect& draw_rect, + const Style& style, + const std::string& label, + const std::string& value +) { + const int label_length_max = 4; + + painter.draw_string(Point { draw_rect.left(), draw_rect.top() }, style, label); + painter.draw_string(Point { draw_rect.left() + (label_length_max + 1) * 8, draw_rect.top() }, style, value); + + return { draw_rect.left(), draw_rect.top() + draw_rect.height(), draw_rect.width(), draw_rect.height() }; +} + +void AISRecentEntryDetailView::paint(Painter& painter) { + View::paint(painter); + + const auto s = style(); + const auto rect = screen_rect(); + + auto field_rect = Rect { rect.left(), rect.top() + 16, rect.width(), 16 }; + + field_rect = draw_field(painter, field_rect, s, "MMSI", ais::format::mmsi(entry_.mmsi)); + field_rect = draw_field(painter, field_rect, s, "Name", entry_.name); + field_rect = draw_field(painter, field_rect, s, "Call", entry_.call_sign); + field_rect = draw_field(painter, field_rect, s, "Dest", entry_.destination); + field_rect = draw_field(painter, field_rect, s, "Last", to_string_datetime(entry_.last_position.timestamp)); + field_rect = draw_field(painter, field_rect, s, "Pos ", ais::format::latlon(entry_.last_position.latitude, entry_.last_position.longitude)); + field_rect = draw_field(painter, field_rect, s, "Stat", ais::format::navigational_status(entry_.navigational_status)); + field_rect = draw_field(painter, field_rect, s, "RoT ", ais::format::rate_of_turn(entry_.last_position.rate_of_turn)); + field_rect = draw_field(painter, field_rect, s, "SoG ", ais::format::speed_over_ground(entry_.last_position.speed_over_ground)); + field_rect = draw_field(painter, field_rect, s, "CoG ", ais::format::course_over_ground(entry_.last_position.course_over_ground)); + field_rect = draw_field(painter, field_rect, s, "Head", ais::format::true_heading(entry_.last_position.true_heading)); + field_rect = draw_field(painter, field_rect, s, "Rx #", to_string_dec_uint(entry_.received_count)); +} + +void AISRecentEntryDetailView::set_entry(const AISRecentEntry& entry) { + entry_ = entry; + set_dirty(); +} + +AISAppView::AISAppView(NavigationView&) { + add_children({ { + &label_channel, + &options_channel, + &recent_entries_view, + &recent_entry_detail_view, + } }); + + recent_entry_detail_view.hidden(true); + + EventDispatcher::message_map().register_handler(Message::ID::AISPacket, + [this](Message* const p) { + const auto message = static_cast(p); + const ais::Packet packet { message->packet }; + if( packet.is_valid() ) { + this->on_packet(packet); + } + } + ); + + options_channel.on_change = [this](size_t, OptionsField::value_t v) { + this->on_frequency_changed(v); + }; + options_channel.set_by_value(162025000); + + receiver_model.set_baseband_configuration({ + .mode = 3, + .sampling_rate = 2457600, + .decimation_factor = 1, + }); + receiver_model.set_baseband_bandwidth(1750000); + receiver_model.set_rf_amp(false); + receiver_model.set_lna(32); + receiver_model.set_vga(32); + receiver_model.enable(); + + recent_entries_view.on_select = [this](const AISRecentEntry& entry) { + this->on_show_detail(entry); + }; + recent_entry_detail_view.on_close = [this]() { + this->on_show_list(); + }; +} + +AISAppView::~AISAppView() { + receiver_model.disable(); + EventDispatcher::message_map().unregister_handler(Message::ID::AISPacket); +} + +void AISAppView::focus() { + options_channel.focus(); +} + +void AISAppView::set_parent_rect(const Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + const Rect content_rect { 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height }; + recent_entries_view.set_parent_rect(content_rect); + recent_entry_detail_view.set_parent_rect(content_rect); +} + +void AISAppView::on_packet(const ais::Packet& packet) { + logger.on_packet(packet); + const auto updated_entry = recent.on_packet(packet.source_id(), packet); + recent_entries_view.set_dirty(); + + // TODO: Crude hack, should be a more formal listener arrangement... + if( updated_entry.key() == recent_entry_detail_view.entry().key() ) { + recent_entry_detail_view.set_entry(updated_entry); + } +} + +void AISAppView::on_show_list() { + recent_entries_view.hidden(false); + recent_entry_detail_view.hidden(true); + recent_entries_view.focus(); +} + +void AISAppView::on_show_detail(const AISRecentEntry& entry) { + recent_entries_view.hidden(true); + recent_entry_detail_view.hidden(false); + recent_entry_detail_view.set_entry(entry); + recent_entry_detail_view.focus(); +} + +void AISAppView::on_frequency_changed(const uint32_t new_frequency) { + receiver_model.set_tuning_frequency(new_frequency); +} + +} /* namespace ui */ diff --git a/firmware/application/ais_app.hpp b/firmware/application/ais_app.hpp new file mode 100644 index 00000000..73b005d3 --- /dev/null +++ b/firmware/application/ais_app.hpp @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2014 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 __AIS_APP_H__ +#define __AIS_APP_H__ + +#include "ui_widget.hpp" +#include "ui_navigation.hpp" + +#include "log_file.hpp" + +#include "ais_packet.hpp" + +#include "lpc43xx_cpp.hpp" +using namespace lpc43xx; + +#include +#include +#include +#include +#include + +#include + +#include "recent_entries.hpp" + +struct AISPosition { + rtc::RTC timestamp { }; + ais::Latitude latitude; + ais::Longitude longitude; + ais::RateOfTurn rate_of_turn { -128 }; + ais::SpeedOverGround speed_over_ground { 1023 }; + ais::CourseOverGround course_over_ground { 3600 }; + ais::TrueHeading true_heading { 511 }; +}; + +struct AISRecentEntry { + using Key = ais::MMSI; + + static constexpr Key invalid_key = 0xffffffff; + + ais::MMSI mmsi; + std::string name; + std::string call_sign; + std::string destination; + AISPosition last_position; + size_t received_count; + int8_t navigational_status; + + AISRecentEntry( + ) : AISRecentEntry { 0 } + { + } + + AISRecentEntry( + const ais::MMSI& mmsi + ) : mmsi { mmsi }, + last_position { }, + received_count { 0 }, + navigational_status { -1 } + { + } + + Key key() const { + return mmsi; + } + + void update(const ais::Packet& packet); +}; + +using AISRecentEntries = RecentEntries; + +class AISLogger { +public: + void on_packet(const ais::Packet& packet); + +private: + LogFile log_file { "ais.txt" }; +}; + +namespace ui { + +using AISRecentEntriesView = RecentEntriesView; + +class AISRecentEntryDetailView : public View { +public: + std::function on_close; + + AISRecentEntryDetailView(); + + void set_entry(const AISRecentEntry& new_entry); + const AISRecentEntry& entry() const { return entry_; }; + + void focus() override; + void paint(Painter&) override; + +private: + AISRecentEntry entry_; + + Button button_done { + { 72, 216, 96, 24 }, + "Done" + }; + + Rect draw_field( + Painter& painter, + const Rect& draw_rect, + const Style& style, + const std::string& label, + const std::string& value + ); +}; + +class AISAppView : public View { +public: + AISAppView(NavigationView& nav); + ~AISAppView(); + + void set_parent_rect(const Rect new_parent_rect) override; + + // Prevent painting of region covered entirely by a child. + // TODO: Add flag to View that specifies view does not need to be cleared before painting. + void paint(Painter&) override { }; + + void focus() override; + + std::string title() const override { return "AIS"; }; + +private: + AISRecentEntries recent; + AISLogger logger; + + AISRecentEntriesView recent_entries_view { recent }; + AISRecentEntryDetailView recent_entry_detail_view; + + static constexpr auto header_height = 1 * 16; + + Text label_channel { + { 0 * 8, 0 * 16, 2 * 8, 1 * 16 }, + "Ch" + }; + + OptionsField options_channel { + { 3 * 8, 0 * 16 }, + 3, + { + { "87B", 161975000 }, + { "88B", 162025000 }, + } + }; + + void on_packet(const ais::Packet& packet); + void on_show_list(); + void on_show_detail(const AISRecentEntry& entry); + + void on_frequency_changed(const uint32_t new_frequency); +}; + +} /* namespace ui */ + +#endif/*__AIS_APP_H__*/ diff --git a/firmware/application/analog_audio_app.cpp b/firmware/application/analog_audio_app.cpp new file mode 100644 index 00000000..ec50f4d5 --- /dev/null +++ b/firmware/application/analog_audio_app.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 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 "analog_audio_app.hpp" + +#include "portapack.hpp" +#include "portapack_shared_memory.hpp" +using namespace portapack; + +#include "utility.hpp" + +AnalogAudioModel::AnalogAudioModel(ReceiverModel::Mode mode) { + receiver_model.set_baseband_configuration({ + .mode = toUType(mode), + .sampling_rate = 3072000, + .decimation_factor = 1, + }); + receiver_model.set_baseband_bandwidth(1750000); + + switch(mode) { + case ReceiverModel::Mode::NarrowbandFMAudio: + configure_nbfm(); + break; + + case ReceiverModel::Mode::WidebandFMAudio: + configure_wfm(); + break; + + case ReceiverModel::Mode::AMAudio: + configure_am(); + break; + + default: + break; + } + +} + +void AnalogAudioModel::configure_nbfm() { + const NBFMConfigureMessage message { + taps_4k25_decim_0, + taps_4k25_decim_1, + taps_4k25_channel, + 2500, + }; + shared_memory.baseband_queue.push(message); +} + +void AnalogAudioModel::configure_wfm() { + const WFMConfigureMessage message { + taps_200k_wfm_decim_0, + taps_200k_wfm_decim_1, + taps_64_lp_156_198, + 75000, + }; + shared_memory.baseband_queue.push(message); +} + +void AnalogAudioModel::configure_am() { + const AMConfigureMessage message { + taps_6k0_decim_0, + taps_6k0_decim_1, + taps_6k0_channel, + }; + shared_memory.baseband_queue.push(message); +} diff --git a/firmware/baseband/irq_ipc_m4.cpp b/firmware/application/analog_audio_app.hpp similarity index 60% rename from firmware/baseband/irq_ipc_m4.cpp rename to firmware/application/analog_audio_app.hpp index 07a75335..7c840ced 100644 --- a/firmware/baseband/irq_ipc_m4.cpp +++ b/firmware/application/analog_audio_app.hpp @@ -19,36 +19,36 @@ * Boston, MA 02110-1301, USA. */ -#include "irq_ipc_m4.hpp" +#ifndef __ANALOG_AUDIO_APP_H__ +#define __ANALOG_AUDIO_APP_H__ -#include "ch.h" -#include "hal.h" +#include "receiver_model.hpp" +#include "ui_spectrum.hpp" -#include "event_m4.hpp" +class AnalogAudioModel { +public: + AnalogAudioModel(ReceiverModel::Mode mode); -#include "lpc43xx_cpp.hpp" -using namespace lpc43xx; +private: + void configure_nbfm(); + void configure_wfm(); + void configure_am(); +}; -void m0apptxevent_interrupt_enable() { - nvicEnableVector(M0CORE_IRQn, CORTEX_PRIORITY_MASK(LPC43XX_M0APPTXEVENT_IRQ_PRIORITY)); -} +namespace ui { -void m0apptxevent_interrupt_disable() { - nvicDisableVector(M0CORE_IRQn); -} +class AnalogAudioView : public spectrum::WaterfallWidget { +public: + AnalogAudioView( + ReceiverModel::Mode mode + ) : model { mode } + { + } -extern "C" { +private: + AnalogAudioModel model; +}; -CH_IRQ_HANDLER(MAPP_IRQHandler) { - CH_IRQ_PROLOGUE(); +} /* namespace ui */ - chSysLockFromIsr(); - events_flag_isr(EVT_MASK_BASEBAND); - chSysUnlockFromIsr(); - - creg::m0apptxevent::clear(); - - CH_IRQ_EPILOGUE(); -} - -} +#endif/*__ANALOG_AUDIO_APP_H__*/ diff --git a/firmware/application/ert_app.cpp b/firmware/application/ert_app.cpp new file mode 100644 index 00000000..9ca5800d --- /dev/null +++ b/firmware/application/ert_app.cpp @@ -0,0 +1,175 @@ +/* + * 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 "ert_app.hpp" + +#include "event_m0.hpp" + +#include "portapack.hpp" +using namespace portapack; + +#include "manchester.hpp" + +#include "crc.hpp" +#include "string_format.hpp" + +namespace ert { + +namespace format { + +std::string type(Packet::Type value) { + switch(value) { + default: + case Packet::Type::Unknown: return "???"; + case Packet::Type::IDM: return "IDM"; + case Packet::Type::SCM: return "SCM"; + } +} + +std::string id(ID value) { + return to_string_dec_uint(value, 10); +} + +std::string consumption(Consumption value) { + return to_string_dec_uint(value, 10); +} + +} /* namespace format */ + +} /* namespace ert */ + +void ERTLogger::on_packet(const ert::Packet& packet) { + if( log_file.is_ready() ) { + const auto formatted = packet.symbols_formatted(); + log_file.write_entry(packet.received_at(), formatted.data + "/" + formatted.errors); + } +} + +void ERTRecentEntry::update(const ert::Packet& packet) { + received_count++; + + last_consumption = packet.consumption(); +} + +namespace ui { + +static const std::array, 3> ert_columns { { + { "ID", 10 }, + { "Consumpt", 10 }, + { "Cnt", 3 }, +} }; + +template<> +void RecentEntriesView::draw_header( + const Rect& target_rect, + Painter& painter, + const Style& style +) { + auto x = 0; + for(const auto& column : ert_columns) { + const auto width = column.second; + auto text = column.first; + if( width > text.length() ) { + text.append(width - text.length(), ' '); + } + + painter.draw_string({ x, target_rect.pos.y }, style, text); + x += (width * 8) + 8; + } +} + +template<> +void RecentEntriesView::draw( + const Entry& entry, + const Rect& target_rect, + Painter& painter, + const Style& style, + const bool is_selected +) { + const auto& draw_style = is_selected ? style.invert() : style; + + std::string line = ert::format::id(entry.id) + " " + ert::format::consumption(entry.last_consumption); + + if( entry.received_count > 999 ) { + line += " +++"; + } else { + line += " " + to_string_dec_uint(entry.received_count, 3); + } + + line.resize(target_rect.width() / 8, ' '); + painter.draw_string(target_rect.pos, draw_style, line); +} + +ERTAppView::ERTAppView(NavigationView&) { + add_children({ { + &recent_entries_view, + } }); + + EventDispatcher::message_map().register_handler(Message::ID::ERTPacket, + [this](Message* const p) { + const auto message = static_cast(p); + const ert::Packet packet { message->type, message->packet }; + this->on_packet(packet); + } + ); + + receiver_model.set_baseband_configuration({ + .mode = 6, + .sampling_rate = 4194304, + .decimation_factor = 1, + }); + receiver_model.set_baseband_bandwidth(2500000); + receiver_model.set_rf_amp(false); + receiver_model.set_lna(32); + receiver_model.set_vga(32); + receiver_model.set_tuning_frequency(911600000); + receiver_model.enable(); +} + +ERTAppView::~ERTAppView() { + receiver_model.disable(); + EventDispatcher::message_map().unregister_handler(Message::ID::ERTPacket); +} + +void ERTAppView::focus() { + recent_entries_view.focus(); +} + +void ERTAppView::set_parent_rect(const Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + recent_entries_view.set_parent_rect({ 0, 0, new_parent_rect.width(), new_parent_rect.height() }); +} + +void ERTAppView::on_packet(const ert::Packet& packet) { + logger.on_packet(packet); + + if( packet.crc_ok() ) { + recent.on_packet(packet.id(), packet); + recent_entries_view.set_dirty(); + } +} + +void ERTAppView::on_show_list() { + recent_entries_view.hidden(false); + recent_entries_view.focus(); +} + +} /* namespace ui */ diff --git a/firmware/application/ert_app.hpp b/firmware/application/ert_app.hpp new file mode 100644 index 00000000..065f7f9b --- /dev/null +++ b/firmware/application/ert_app.hpp @@ -0,0 +1,102 @@ +/* + * 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 __ERT_APP_H__ +#define __ERT_APP_H__ + +#include "ui_navigation.hpp" + +#include "log_file.hpp" + +#include "ert_packet.hpp" + +#include "recent_entries.hpp" + +#include +#include + +struct ERTRecentEntry { + using Key = ert::ID; + + // TODO: Is this the right choice of invalid key value? + static constexpr Key invalid_key = 0; + + ert::ID id { invalid_key }; + + size_t received_count { 0 }; + + ert::Consumption last_consumption; + + ERTRecentEntry( + const Key& key + ) : id { key } + { + } + + Key key() const { + return id; + } + + void update(const ert::Packet& packet); +}; + +class ERTLogger { +public: + void on_packet(const ert::Packet& packet); + +private: + LogFile log_file { "ert.txt" }; +}; + +using ERTRecentEntries = RecentEntries; + +namespace ui { + +using ERTRecentEntriesView = RecentEntriesView; + +class ERTAppView : public View { +public: + ERTAppView(NavigationView& nav); + ~ERTAppView(); + + void set_parent_rect(const Rect new_parent_rect) override; + + // Prevent painting of region covered entirely by a child. + // TODO: Add flag to View that specifies view does not need to be cleared before painting. + void paint(Painter&) override { }; + + void focus() override; + + std::string title() const override { return "ERT"; }; + +private: + ERTRecentEntries recent; + ERTLogger logger; + + ERTRecentEntriesView recent_entries_view { recent }; + + void on_packet(const ert::Packet& packet); + void on_show_list(); +}; + +} /* namespace ui */ + +#endif/*__ERT_APP_H__*/ diff --git a/firmware/application/event_m0.cpp b/firmware/application/event_m0.cpp new file mode 100644 index 00000000..dc557397 --- /dev/null +++ b/firmware/application/event_m0.cpp @@ -0,0 +1,244 @@ +/* + * 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 "event_m0.hpp" + +#include "portapack.hpp" +#include "portapack_shared_memory.hpp" + +#include "sd_card.hpp" + +#include "message.hpp" +#include "message_queue.hpp" + +#include "irq_controls.hpp" + +#include "ch.h" + +#include "lpc43xx_cpp.hpp" +using namespace lpc43xx; + +#include + +extern "C" { + +CH_IRQ_HANDLER(M4Core_IRQHandler) { + CH_IRQ_PROLOGUE(); + + chSysLockFromIsr(); + EventDispatcher::events_flag_isr(EVT_MASK_APPLICATION); + chSysUnlockFromIsr(); + + creg::m4txevent::clear(); + + CH_IRQ_EPILOGUE(); +} + +} + +MessageHandlerMap EventDispatcher::message_map_; +Thread* EventDispatcher::thread_event_loop = nullptr; + +EventDispatcher::EventDispatcher( + ui::Widget* const top_widget, + ui::Painter& painter, + ui::Context& context +) : top_widget { top_widget }, + painter(painter), + context(context) +{ + thread_event_loop = chThdSelf(); + touch_manager.on_event = [this](const ui::TouchEvent event) { + this->on_touch_event(event); + }; +} + +void EventDispatcher::run() { + creg::m4txevent::enable(); + + while(is_running) { + const auto events = wait(); + dispatch(events); + } + + creg::m4txevent::disable(); +} + +void EventDispatcher::request_stop() { + is_running = false; +} + +void EventDispatcher::set_display_sleep(const bool sleep) { + // TODO: Distribute display sleep message more broadly, shut down data generation + // on baseband side, since all that data is being discarded during sleep. + if( sleep ) { + portapack::io.lcd_backlight(false); + portapack::display.sleep(); + } else { + portapack::display.wake(); + portapack::io.lcd_backlight(true); + } + display_sleep = sleep; +}; + +eventmask_t EventDispatcher::wait() { + return chEvtWaitAny(ALL_EVENTS); +} + +void EventDispatcher::dispatch(const eventmask_t events) { + if( events & EVT_MASK_APPLICATION ) { + handle_application_queue(); + } + + if( events & EVT_MASK_RTC_TICK ) { + handle_rtc_tick(); + } + + if( events & EVT_MASK_SWITCHES ) { + handle_switches(); + } + + if( !display_sleep ) { + if( events & EVT_MASK_LCD_FRAME_SYNC ) { + handle_lcd_frame_sync(); + } + + if( events & EVT_MASK_ENCODER ) { + handle_encoder(); + } + + if( events & EVT_MASK_TOUCH ) { + handle_touch(); + } + } +} + +void EventDispatcher::handle_application_queue() { + std::array message_buffer; + while(Message* const message = shared_memory.application_queue.pop(message_buffer)) { + message_map().send(message); + } +} + +void EventDispatcher::handle_rtc_tick() { + sd_card::poll_inserted(); + + portapack::temperature_logger.second_tick(); +} + +ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent event) { + if( !w->hidden() ) { + // To achieve reverse depth ordering (last object drawn is + // considered "top"), descend first. + for(const auto child : w->children()) { + const auto touched_widget = touch_widget(child, event); + if( touched_widget ) { + return touched_widget; + } + } + + const auto r = w->screen_rect(); + if( r.contains(event.point) ) { + if( w->on_touch(event) ) { + // This widget responded. Return it up the call stack. + return w; + } + } + } + return nullptr; +} + +void EventDispatcher::on_touch_event(ui::TouchEvent event) { + /* TODO: Capture widget receiving the Start event, send Move and + * End events to the same widget. + */ + /* Capture Start widget. + * If touch is over Start widget at Move event, then the widget + * should be highlighted. If the touch is not over the Start + * widget at Move event, widget should un-highlight. + * If touch is over Start widget at End event, then the widget + * action should occur. + */ + if( event.type == ui::TouchEvent::Type::Start ) { + captured_widget = touch_widget(this->top_widget, event); + } + + if( captured_widget ) { + captured_widget->on_touch(event); + } +} + +void EventDispatcher::handle_lcd_frame_sync() { + DisplayFrameSyncMessage message; + message_map().send(&message); + painter.paint_widget_tree(top_widget); +} + +void EventDispatcher::handle_switches() { + const auto switches_state = get_switches_state(); + + if( display_sleep ) { + // Swallow event, wake up display. + if( switches_state.any() ) { + set_display_sleep(false); + } + return; + } + + for(size_t i=0; i(i); + if( !event_bubble_key(event) ) { + context.focus_manager().update(top_widget, event); + } + } + } +} + +void EventDispatcher::handle_encoder() { + const uint32_t encoder_now = get_encoder_position(); + const int32_t delta = static_cast(encoder_now - encoder_last); + encoder_last = encoder_now; + const auto event = static_cast(delta); + event_bubble_encoder(event); +} + +void EventDispatcher::handle_touch() { + touch_manager.feed(get_touch_frame()); +} + +bool EventDispatcher::event_bubble_key(const ui::KeyEvent event) { + auto target = context.focus_manager().focus_widget(); + while( (target != nullptr) && !target->on_key(event) ) { + target = target->parent(); + } + + /* Return true if event was consumed. */ + return (target != nullptr); +} + +void EventDispatcher::event_bubble_encoder(const ui::EncoderEvent event) { + auto target = context.focus_manager().focus_widget(); + while( (target != nullptr) && !target->on_encoder(event) ) { + target = target->parent(); + } +} \ No newline at end of file diff --git a/firmware/application/event_m0.hpp b/firmware/application/event_m0.hpp new file mode 100644 index 00000000..d8b1b396 --- /dev/null +++ b/firmware/application/event_m0.hpp @@ -0,0 +1,110 @@ +/* + * 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 __EVENT_M0_H__ +#define __EVENT_M0_H__ + +#include "event.hpp" + +#include "ui_widget.hpp" +#include "ui_painter.hpp" + +#include "portapack.hpp" + +#include "message.hpp" + +#include "touch.hpp" + +#include "ch.h" + +#include + +constexpr auto EVT_MASK_RTC_TICK = EVENT_MASK(0); +constexpr auto EVT_MASK_LCD_FRAME_SYNC = EVENT_MASK(1); +constexpr auto EVT_MASK_SWITCHES = EVENT_MASK(3); +constexpr auto EVT_MASK_ENCODER = EVENT_MASK(4); +constexpr auto EVT_MASK_TOUCH = EVENT_MASK(5); +constexpr auto EVT_MASK_APPLICATION = EVENT_MASK(6); + +class EventDispatcher { +public: + EventDispatcher( + ui::Widget* const top_widget, + ui::Painter& painter, + ui::Context& context + ); + + void run(); + void request_stop(); + + void set_display_sleep(const bool sleep); + + static inline void events_flag(const eventmask_t events) { + if( thread_event_loop ) { + chEvtSignal(thread_event_loop, events); + } + } + + static inline void events_flag_isr(const eventmask_t events) { + if( thread_event_loop ) { + chEvtSignalI(thread_event_loop, events); + } + } + + static MessageHandlerMap& message_map() { + return message_map_; + } + +private: + static MessageHandlerMap message_map_; + static Thread* thread_event_loop; + + touch::Manager touch_manager; + ui::Widget* const top_widget; + ui::Painter& painter; + ui::Context& context; + uint32_t encoder_last = 0; + bool is_running = true; + bool sd_card_present = false; + bool display_sleep = false; + + eventmask_t wait(); + void dispatch(const eventmask_t events); + + void handle_application_queue(); + void handle_rtc_tick(); + + static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event); + + ui::Widget* captured_widget { nullptr }; + + void on_touch_event(ui::TouchEvent event); + + void handle_lcd_frame_sync(); + void handle_switches(); + void handle_encoder(); + void handle_touch(); + + bool event_bubble_key(const ui::KeyEvent event); + void event_bubble_encoder(const ui::EncoderEvent event); +}; + +#endif/*__EVENT_M0_H__*/ diff --git a/firmware/application/file.cpp b/firmware/application/file.cpp new file mode 100644 index 00000000..439c0a9c --- /dev/null +++ b/firmware/application/file.cpp @@ -0,0 +1,71 @@ +/* + * 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 "file.hpp" + +File::~File() { + close(); +} + +bool File::open_for_append(const std::string file_path) { + const auto open_result = f_open(&f, file_path.c_str(), FA_WRITE | FA_OPEN_ALWAYS); + if( open_result == FR_OK ) { + const auto seek_result = f_lseek(&f, f_size(&f)); + if( seek_result == FR_OK ) { + return true; + } else { + close(); + } + } + + return false; +} + +bool File::close() { + f_close(&f); + return true; +} + +bool File::is_ready() { + return f_error(&f) == 0; +} + +bool File::read(void* const data, const size_t bytes_to_read) { + UINT bytes_read = 0; + const auto result = f_read(&f, data, bytes_to_read, &bytes_read); + return (result == FR_OK) && (bytes_read == bytes_to_read); +} + +bool File::write(const void* const data, const size_t bytes_to_write) { + UINT bytes_written = 0; + const auto result = f_write(&f, data, bytes_to_write, &bytes_written); + return (result == FR_OK) && (bytes_written == bytes_to_write); +} + +bool File::puts(const std::string string) { + const auto result = f_puts(string.c_str(), &f); + return (result >= 0); +} + +bool File::sync() { + const auto result = f_sync(&f); + return (result == FR_OK); +} diff --git a/firmware/application/irq_ipc.cpp b/firmware/application/file.hpp similarity index 100% rename from firmware/application/irq_ipc.cpp rename to firmware/application/file.hpp diff --git a/firmware/application/irq_controls.cpp b/firmware/application/irq_controls.cpp index ddf8f154..5a789721 100644 --- a/firmware/application/irq_controls.cpp +++ b/firmware/application/irq_controls.cpp @@ -24,7 +24,7 @@ #include "ch.h" #include "hal.h" -#include "event.hpp" +#include "event_m0.hpp" #include "touch.hpp" #include "touch_adc.hpp" @@ -165,7 +165,7 @@ void timer0_callback(GPTDriver* const) { /* Signal event loop */ if( event_mask ) { chSysLockFromIsr(); - events_flag_isr(event_mask); + EventDispatcher::events_flag_isr(event_mask); chSysUnlockFromIsr(); } diff --git a/firmware/application/irq_lcd_frame.cpp b/firmware/application/irq_lcd_frame.cpp index 65498ad4..585feb1f 100644 --- a/firmware/application/irq_lcd_frame.cpp +++ b/firmware/application/irq_lcd_frame.cpp @@ -21,7 +21,7 @@ #include "irq_lcd_frame.hpp" -#include "event.hpp" +#include "event_m0.hpp" #include "ch.h" #include "hal.h" @@ -54,7 +54,7 @@ CH_IRQ_HANDLER(PIN_INT4_IRQHandler) { CH_IRQ_PROLOGUE(); chSysLockFromIsr(); - events_flag_isr(EVT_MASK_LCD_FRAME_SYNC); + EventDispatcher::events_flag_isr(EVT_MASK_LCD_FRAME_SYNC); chSysUnlockFromIsr(); LPC_GPIO_INT->IST = (1U << 4); diff --git a/firmware/application/irq_rtc.cpp b/firmware/application/irq_rtc.cpp index f393d0f4..332f1b3e 100644 --- a/firmware/application/irq_rtc.cpp +++ b/firmware/application/irq_rtc.cpp @@ -26,7 +26,7 @@ #include "lpc43xx_cpp.hpp" using namespace lpc43xx; -#include "event.hpp" +#include "event_m0.hpp" void rtc_interrupt_enable() { rtc::interrupt::enable_second_inc(); @@ -39,7 +39,7 @@ CH_IRQ_HANDLER(RTC_IRQHandler) { CH_IRQ_PROLOGUE(); chSysLockFromIsr(); - events_flag_isr(EVT_MASK_RTC_TICK); + EventDispatcher::events_flag_isr(EVT_MASK_RTC_TICK); chSysUnlockFromIsr(); rtc::interrupt::clear_all(); diff --git a/firmware/application/log_file.cpp b/firmware/application/log_file.cpp new file mode 100644 index 00000000..3b5f4f01 --- /dev/null +++ b/firmware/application/log_file.cpp @@ -0,0 +1,65 @@ +/* + * 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 "log_file.hpp" + +#include "string_format.hpp" + +#include "lpc43xx_cpp.hpp" +using namespace lpc43xx; + +LogFile::LogFile( + const std::string file_path +) : file_path { file_path } +{ + file.open_for_append(file_path); + + sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status status) { + this->on_sd_card_status(status); + }; +} + +LogFile::~LogFile() { + sd_card::status_signal -= sd_card_status_signal_token; + + file.close(); +} + +bool LogFile::is_ready() { + return file.is_ready(); +} + +bool LogFile::write_entry(const rtc::RTC& datetime, const std::string& entry) { + std::string timestamp = to_string_timestamp(datetime); + return write(timestamp + " " + entry + "\r\n"); +} + +bool LogFile::write(const std::string& message) { + return file.puts(message) && file.sync(); +} + +void LogFile::on_sd_card_status(const sd_card::Status status) { + if( status == sd_card::Status::Mounted ) { + file.open_for_append(file_path); + } else { + file.close(); + } +} diff --git a/firmware/application/event.hpp b/firmware/application/log_file.hpp similarity index 53% rename from firmware/application/event.hpp rename to firmware/application/log_file.hpp index fbf40d0a..0ddf781c 100644 --- a/firmware/application/event.hpp +++ b/firmware/application/log_file.hpp @@ -19,32 +19,36 @@ * Boston, MA 02110-1301, USA. */ -#ifndef __EVENT_H__ -#define __EVENT_H__ +#ifndef __LOG_FILE_H__ +#define __LOG_FILE_H__ -#include "ch.h" +#include -constexpr auto EVT_MASK_RTC_TICK = EVENT_MASK(0); -constexpr auto EVT_MASK_LCD_FRAME_SYNC = EVENT_MASK(1); -constexpr auto EVT_MASK_SWITCHES = EVENT_MASK(3); -constexpr auto EVT_MASK_ENCODER = EVENT_MASK(4); -constexpr auto EVT_MASK_TOUCH = EVENT_MASK(5); -constexpr auto EVT_MASK_APPLICATION = EVENT_MASK(6); +#include "file.hpp" +#include "sd_card.hpp" -void events_initialize(Thread* const event_loop_thread); +#include "lpc43xx_cpp.hpp" +using namespace lpc43xx; -extern Thread* thread_event_loop; +class LogFile { +public: + LogFile(const std::string file_path); + ~LogFile(); -inline void events_flag(const eventmask_t events) { - if( thread_event_loop ) { - chEvtSignal(thread_event_loop, events); - } -} + bool is_ready(); -inline void events_flag_isr(const eventmask_t events) { - if( thread_event_loop ) { - chEvtSignalI(thread_event_loop, events); - } -} + bool write_entry(const rtc::RTC& datetime, const std::string& entry); -#endif/*__EVENT_H__*/ +private: + const std::string file_path; + + File file; + + SignalToken sd_card_status_signal_token; + + bool write(const std::string& message); + + void on_sd_card_status(const sd_card::Status status); +}; + +#endif/*__LOG_FILE_H__*/ diff --git a/firmware/application/main.cpp b/firmware/application/main.cpp index e5f48072..a36598cf 100755 --- a/firmware/application/main.cpp +++ b/firmware/application/main.cpp @@ -49,16 +49,9 @@ //TODO: TX power #include "ch.h" -#include "test.h" - -#include "lpc43xx_cpp.hpp" -using namespace lpc43xx; #include "portapack.hpp" -#include "portapack_io.hpp" #include "portapack_shared_memory.hpp" -#include "portapack_persistent_memory.hpp" -using namespace portapack; #include "cpld_update.hpp" @@ -74,7 +67,7 @@ using namespace portapack; #include "irq_controls.hpp" #include "irq_rtc.hpp" -#include "event.hpp" +#include "event_m0.hpp" #include "m4_startup.hpp" #include "spi_image.hpp" @@ -84,280 +77,13 @@ using namespace portapack; #include "gcc.hpp" -#include +#include "lpc43xx_cpp.hpp" +using namespace lpc43xx; #include "sd_card.hpp" #include -class EventDispatcher { -public: - EventDispatcher( - ui::Widget* const top_widget, - ui::Painter& painter, - ui::Context& context - ) : top_widget { top_widget }, - painter(painter), - context(context) - { - // touch_manager.on_started = [this](const ui::TouchEvent event) { - // this->context.focus_manager.update(this->top_widget, event); - // }; - - touch_manager.on_event = [this](const ui::TouchEvent event) { - this->on_touch_event(event); - }; - } - - void run() { - while(is_running) { - const auto events = wait(); - dispatch(events); - } - } - - void request_stop() { - is_running = false; - } - -private: - touch::Manager touch_manager; - ui::Widget* const top_widget; - ui::Painter& painter; - ui::Context& context; - uint32_t encoder_last = 0; - bool is_running = true; - bool sd_card_present = false; - - eventmask_t wait() { - return chEvtWaitAny(ALL_EVENTS); - } - - void dispatch(const eventmask_t events) { - if( events & EVT_MASK_APPLICATION ) { - handle_application_queue(); - } - - if( events & EVT_MASK_RTC_TICK ) { - handle_rtc_tick(); - } - - if( events & EVT_MASK_LCD_FRAME_SYNC ) { - handle_lcd_frame_sync(); - } - - if( events & EVT_MASK_SWITCHES ) { - handle_switches(); - } - - if( events & EVT_MASK_ENCODER ) { - handle_encoder(); - } - - if( events & EVT_MASK_TOUCH ) { - handle_touch(); - } - } - - void handle_application_queue() { - std::array message_buffer; - while(Message* const message = shared_memory.application_queue.pop(message_buffer)) { - context.message_map().send(message); - } - } - - void handle_rtc_tick() { - uint16_t bloff_time; - const auto sd_card_present_now = sdc_lld_is_card_inserted(&SDCD1); - - bloff_time = portapack::persistent_memory::ui_config_bloff(); - if (bloff_time) { - if (portapack::bl_tick_counter >= bloff_time) - io.lcd_backlight(0); - else - portapack::bl_tick_counter++; - } - - if( sd_card_present_now != sd_card_present ) { - sd_card_present = sd_card_present_now; - - if( sd_card_present ) { - if( sdcConnect(&SDCD1) == CH_SUCCESS ) { - if( sd_card::filesystem::mount() == FR_OK ) { - SDCardStatusMessage message { true }; - context.message_map().send(&message); - } else { - // TODO: Error, modal warning? - } - } else { - // TODO: Error, modal warning? - } - } else { - sdcDisconnect(&SDCD1); - - SDCardStatusMessage message { false }; - context.message_map().send(&message); - } - } - } - - static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event) { - if( !w->hidden() ) { - // To achieve reverse depth ordering (last object drawn is - // considered "top"), descend first. - for(const auto child : w->children()) { - const auto touched_widget = touch_widget(child, event); - if( touched_widget ) { - return touched_widget; - } - } - - const auto r = w->screen_rect(); - if( r.contains(event.point) ) { - if( w->on_touch(event) ) { - // This widget responded. Return it up the call stack. - return w; - } - } - } - return nullptr; - } - - ui::Widget* captured_widget { nullptr }; - - void on_touch_event(ui::TouchEvent event) { - /* TODO: Capture widget receiving the Start event, send Move and - * End events to the same widget. - */ - /* Capture Start widget. - * If touch is over Start widget at Move event, then the widget - * should be highlighted. If the touch is not over the Start - * widget at Move event, widget should un-highlight. - * If touch is over Start widget at End event, then the widget - * action should occur. - */ - if( event.type == ui::TouchEvent::Type::Start ) { - captured_widget = touch_widget(this->top_widget, event); - } - - if( captured_widget ) { - captured_widget->on_touch(event); - } - } - - void handle_lcd_frame_sync() { - DisplayFrameSyncMessage message; - context.message_map().send(&message); - painter.paint_widget_tree(top_widget); - } - - void handle_switches() { - const auto switches_state = get_switches_state(); - - io.lcd_backlight(1); - portapack::bl_tick_counter = 0; - - for(size_t i=0; i(i); - if( !event_bubble_key(event) ) { - context.focus_manager().update(top_widget, event); - } - } - } - } - - void handle_encoder() { - const uint32_t encoder_now = get_encoder_position(); - const int32_t delta = static_cast(encoder_now - encoder_last); - - io.lcd_backlight(1); - portapack::bl_tick_counter = 0; - - encoder_last = encoder_now; - const auto event = static_cast(delta); - event_bubble_encoder(event); - } - - void handle_touch() { - touch_manager.feed(get_touch_frame()); - } - - bool event_bubble_key(const ui::KeyEvent event) { - auto target = context.focus_manager().focus_widget(); - while( (target != nullptr) && !target->on_key(event) ) { - target = target->parent(); - } - - /* Return true if event was consumed. */ - return (target != nullptr); - } - - void event_bubble_encoder(const ui::EncoderEvent event) { - auto target = context.focus_manager().focus_widget(); - while( (target != nullptr) && !target->on_encoder(event) ) { - target = target->parent(); - } - } -}; - -/////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////// - -/* Thinking things through a bit: - - main() produces UI events. - Touch events: - Hit test entire screen hierarchy and send to hit widget. - If modal view shown, block UI events destined outside. - Navigation events: - Move from current focus widget to "nearest" focusable widget. - If current view is modal, don't allow events to bubble outside - of modal view. - System events: - Power off from WWDT provides enough time to flush changes to - VBAT RAM? - SD card events? Insert/eject/error. - - - View stack: - Views that are hidden should deconstruct their widgets? - Views that are shown after being hidden will reconstruct their - widgets from data in their model? - Hence, hidden views will not eat up memory beyond their model? - Beware loops where the stack can get wildly deep? - Breaking out data models from views should allow some amount of - power-off persistence in the VBAT RAM area. In fact, the data - models could be instantiated there? But then, how to protect - from corruption if power is pulled? Does WWDT provide enough - warning to flush changes? - - Navigation... - If you move off the left side of the screen, move to breadcrumb - "back" item, no matter where you're coming from? -*/ - -/* -message_handlers[Message::ID::FSKPacket] = [](const Message* const p) { - const auto message = static_cast(p); - fsk_packet(message); -}; - -message_handlers[Message::ID::TestResults] = [&system_view](const Message* const p) { - const auto message = static_cast(p); - char c[10]; - c[0] = message->results.translate_by_fs_over_4_and_decimate_by_2_cic3 ? '+' : '-'; - c[1] = message->results.fir_cic3_decim_2_s16_s16 ? '+' : '-'; - c[2] = message->results.fir_64_and_decimate_by_2_complex ? '+' : '-'; - c[3] = message->results.fxpt_atan2 ? '+' : '-'; - c[4] = message->results.multiply_conjugate_s16_s32 ? '+' : '-'; - c[5] = 0; - system_view.status_view.portapack.set(c); -}; -*/ - int main(void) { portapack::init(); @@ -365,14 +91,11 @@ int main(void) { chSysHalt(); } - init_message_queues(); - portapack::io.init(); portapack::display.init(); sdcStart(&SDCD1, nullptr); - events_initialize(chThdSelf()); init_message_queues(); ui::Context context; @@ -383,19 +106,22 @@ int main(void) { ui::Painter painter; EventDispatcher event_dispatcher { &system_view, painter, context }; - auto& message_handlers = context.message_map(); - message_handlers.register_handler(Message::ID::Shutdown, + EventDispatcher::message_map().register_handler(Message::ID::Shutdown, [&event_dispatcher](const Message* const) { event_dispatcher.request_stop(); } ); + EventDispatcher::message_map().register_handler(Message::ID::DisplaySleep, + [&event_dispatcher](const Message* const) { + event_dispatcher.set_display_sleep(true); + } + ); m4_init(portapack::spi_flash::baseband, portapack::memory::map::m4_code); controls_init(); lcd_frame_sync_configure(); rtc_interrupt_enable(); - m4txevent_interrupt_enable(); event_dispatcher.run(); diff --git a/firmware/application/max2837.cpp b/firmware/application/max2837.cpp index bf040a8f..33e4a8b6 100644 --- a/firmware/application/max2837.cpp +++ b/firmware/application/max2837.cpp @@ -40,7 +40,7 @@ constexpr std::array lookup_8db_steps { }; static uint_fast8_t gain_ordinal(const int8_t db) { - int8_t db_sat = std::min(std::max(gain_db_min, db), gain_db_max); + const auto db_sat = gain_db_range.clip(db); return lna::lookup_8db_steps[(db_sat >> 3) & 7]; } @@ -49,7 +49,7 @@ static uint_fast8_t gain_ordinal(const int8_t db) { namespace vga { static uint_fast8_t gain_ordinal(const int8_t db) { - int8_t db_sat = std::min(std::max(gain_db_min, db), gain_db_max); + const auto db_sat = gain_db_range.clip(db); return ((db_sat >> 1) & 0b11111) ^ 0b11111; } @@ -124,6 +124,8 @@ void MAX2837::init() { _dirty.set(); flush(); + + set_mode(Mode::Standby); } void MAX2837::set_mode(const Mode mode) { @@ -149,22 +151,22 @@ void MAX2837::flush_one(const Register reg) { _dirty.clear(reg_num); } -inline void MAX2837::write(const address_t reg_num, const reg_t value) { +void MAX2837::write(const address_t reg_num, const reg_t value) { uint16_t t = (0U << 15) | (reg_num << 10) | (value & 0x3ffU); _target.transfer(&t, 1); } -inline reg_t MAX2837::read(const address_t reg_num) { +reg_t MAX2837::read(const address_t reg_num) { uint16_t t = (1U << 15) | (reg_num << 10); _target.transfer(&t, 1U); return t & 0x3ffU; } -inline void MAX2837::write(const Register reg, const reg_t value) { +void MAX2837::write(const Register reg, const reg_t value) { write(toUType(reg), value); } -inline reg_t MAX2837::read(const Register reg) { +reg_t MAX2837::read(const Register reg) { return read(toUType(reg)); } @@ -194,18 +196,16 @@ void MAX2837::set_lpf_rf_bandwidth(const uint32_t bandwidth_minimum) { bool MAX2837::set_frequency(const rf::Frequency lo_frequency) { /* TODO: This is a sad implementation. Refactor. */ - if( lo_frequency < lo::band[0].min ) { - return false; - } else if( lo_frequency < lo::band[0].max ) { + if( lo::band[0].contains(lo_frequency) ) { _map.r.syn_int_div.LOGEN_BSW = 0b00; /* 2300 - 2399.99MHz */ _map.r.rxrf_1.LNAband = 0; /* 2.3 - 2.5GHz */ - } else if( lo_frequency < lo::band[1].max ) { + } else if( lo::band[1].contains(lo_frequency) ) { _map.r.syn_int_div.LOGEN_BSW = 0b01; /* 2400 - 2499.99MHz */ _map.r.rxrf_1.LNAband = 0; /* 2.3 - 2.5GHz */ - } else if( lo_frequency < lo::band[2].max ) { + } else if( lo::band[2].contains(lo_frequency) ) { _map.r.syn_int_div.LOGEN_BSW = 0b10; /* 2500 - 2599.99MHz */ _map.r.rxrf_1.LNAband = 1; /* 2.5 - 2.7GHz */ - } else if( lo_frequency < lo::band[3].max ) { + } else if( lo::band[3].contains(lo_frequency) ) { _map.r.syn_int_div.LOGEN_BSW = 0b11; /* 2600 - 2700Hz */ _map.r.rxrf_1.LNAband = 1; /* 2.5 - 2.7GHz */ } else { diff --git a/firmware/application/max2837.hpp b/firmware/application/max2837.hpp index e3e8ce9b..6c91ae58 100644 --- a/firmware/application/max2837.hpp +++ b/firmware/application/max2837.hpp @@ -50,10 +50,10 @@ enum class Mode { namespace lo { constexpr std::array band { { - { .min = 2300000000, .max = 2400000000, }, - { .min = 2400000000, .max = 2500000000, }, - { .min = 2500000000, .max = 2600000000, }, - { .min = 2600000000, .max = 2700000000, }, + { 2300000000, 2400000000 }, + { 2400000000, 2500000000 }, + { 2500000000, 2600000000 }, + { 2600000000, 2700000000 }, } }; } /* namespace lo */ @@ -62,13 +62,12 @@ constexpr std::array band { { namespace lna { -constexpr int8_t gain_db_min = 0; -constexpr int8_t gain_db_max = 40; +constexpr range_t gain_db_range { 0, 40 }; constexpr int8_t gain_db_step = 8; constexpr std::array band { { - { .min = 2300000000, .max = 2500000000, }, - { .min = 2500000000, .max = 2700000000, }, + { 2300000000, 2500000000 }, + { 2500000000, 2700000000 }, } }; } /* namespace lna */ @@ -77,8 +76,7 @@ constexpr std::array band { { namespace vga { -constexpr int8_t gain_db_min = 0; -constexpr int8_t gain_db_max = 62; +constexpr range_t gain_db_range { 0, 62 }; constexpr int8_t gain_db_step = 2; } /* namespace vga */ @@ -576,7 +574,7 @@ constexpr RegisterMap initial_register_values { Register_Type { .LNAtune = 0, .LNAde_Q = 1, .L = 0b000, - .iqerr_trim = 0b0000, + .iqerr_trim = 0b00000, .RESERVED0 = 0, }, .lpf_1 = { /* 2 */ @@ -880,6 +878,8 @@ public: reg_t temp_sense(); + reg_t read(const address_t reg_num); + private: spi::arbiter::Target& _target; @@ -889,7 +889,6 @@ private: void flush_one(const Register reg); void write(const address_t reg_num, const reg_t value); - reg_t read(const address_t reg_num); void write(const Register reg, const reg_t value); reg_t read(const Register reg); diff --git a/firmware/application/portapack.cpp b/firmware/application/portapack.cpp index 73c905cf..57f2b04d 100644 --- a/firmware/application/portapack.cpp +++ b/firmware/application/portapack.cpp @@ -27,7 +27,6 @@ #include "hackrf_gpio.hpp" using namespace hackrf::one; -#include "si5351.hpp" #include "clock_manager.hpp" #include "i2c_pp.hpp" @@ -66,6 +65,8 @@ ReceiverModel receiver_model { clock_manager }; +TemperatureLogger temperature_logger; + TransmitterModel transmitter_model { clock_manager }; diff --git a/firmware/application/portapack.hpp b/firmware/application/portapack.hpp index 676b86b2..221ebe47 100644 --- a/firmware/application/portapack.hpp +++ b/firmware/application/portapack.hpp @@ -26,9 +26,11 @@ #include "spi_pp.hpp" #include "wm8731.hpp" +#include "si5351.hpp" #include "lcd_ili9341.hpp" #include "radio.hpp" +#include "temperature_logger.hpp" namespace portapack { @@ -41,11 +43,15 @@ extern SPI ssp1; extern wolfson::wm8731::WM8731 audio_codec; +extern si5351::Si5351 clock_generator; + extern ReceiverModel receiver_model; extern TransmitterModel transmitter_model; extern uint8_t bl_tick_counter; +extern TemperatureLogger temperature_logger; + void init(); void shutdown(); diff --git a/firmware/application/radio.cpp b/firmware/application/radio.cpp index dcb6bfa7..b58222c5 100644 --- a/firmware/application/radio.cpp +++ b/firmware/application/radio.cpp @@ -22,10 +22,8 @@ #include "radio.hpp" #include "rf_path.hpp" -#include "max2837.hpp" #include "max5864.hpp" #include "baseband_cpld.hpp" -#include "baseband_sgpio.hpp" #include "portapack_shared_memory.hpp" #include "tuning.hpp" @@ -88,10 +86,9 @@ static spi::arbiter::Target ssp1_target_max5864 { static rf::path::Path rf_path; rffc507x::RFFC507x first_if; -static max2837::MAX2837 second_if { ssp1_target_max2837 }; +max2837::MAX2837 second_if { ssp1_target_max2837 }; static max5864::MAX5864 baseband_codec { ssp1_target_max5864 }; static baseband::CPLD baseband_cpld; -static baseband::SGPIO baseband_sgpio; static rf::Direction direction { rf::Direction::Receive }; @@ -101,7 +98,6 @@ void init() { second_if.init(); baseband_codec.init(); baseband_cpld.init(); - baseband_sgpio.init(); } void set_direction(const rf::Direction new_direction) { @@ -113,7 +109,6 @@ void set_direction(const rf::Direction new_direction) { rf_path.set_direction(direction); baseband_codec.set_mode((direction == rf::Direction::Transmit) ? max5864::Mode::Transmit : max5864::Mode::Receive); - baseband_sgpio.configure((direction == rf::Direction::Transmit) ? baseband::Direction::Transmit : baseband::Direction::Receive); } bool set_tuning_frequency(const rf::Frequency frequency) { @@ -157,18 +152,15 @@ void set_baseband_decimation_by(const size_t n) { baseband_cpld.set_decimation_by(n); } -void streaming_enable() { - baseband_sgpio.streaming_enable(); -} - -void streaming_disable() { - baseband_sgpio.streaming_disable(); +void set_antenna_bias(const bool on) { + /* Pull MOSFET gate low to turn on antenna bias. */ + first_if.set_gpo1(on ? 0 : 1); } void disable() { - baseband_sgpio.streaming_disable(); + set_antenna_bias(false); baseband_codec.set_mode(max5864::Mode::Shutdown); - second_if.set_mode(max2837::Mode::Shutdown); + second_if.set_mode(max2837::Mode::Standby); first_if.disable(); set_rf_amp(false); } diff --git a/firmware/application/radio.hpp b/firmware/application/radio.hpp index 442968aa..715fd0d5 100644 --- a/firmware/application/radio.hpp +++ b/firmware/application/radio.hpp @@ -28,6 +28,7 @@ #include #include "rffc507x.hpp" +#include "max2837.hpp" namespace radio { @@ -41,12 +42,12 @@ void set_vga_gain(const int_fast8_t db); void set_sampling_frequency(const uint32_t frequency); void set_baseband_filter_bandwidth(const uint32_t bandwidth_minimum); void set_baseband_decimation_by(const size_t n); +void set_antenna_bias(const bool on); -void streaming_enable(); -void streaming_disable(); void disable(); extern rffc507x::RFFC507x first_if; +extern max2837::MAX2837 second_if; } /* namespace radio */ diff --git a/firmware/application/receiver_model.cpp b/firmware/application/receiver_model.cpp index 406d4108..4f4e4d1c 100644 --- a/firmware/application/receiver_model.cpp +++ b/firmware/application/receiver_model.cpp @@ -52,6 +52,15 @@ void ReceiverModel::set_reference_ppm_correction(int32_t v) { clock_manager.set_reference_ppb(v * 1000); } +bool ReceiverModel::antenna_bias() const { + return antenna_bias_; +} + +void ReceiverModel::set_antenna_bias(bool enabled) { + antenna_bias_ = enabled; + update_antenna_bias(); +} + bool ReceiverModel::rf_amp() const { return rf_amp_; } @@ -92,12 +101,10 @@ uint32_t ReceiverModel::sampling_rate() const { return baseband_configuration.sampling_rate; } - uint32_t ReceiverModel::modulation() const { return baseband_configuration.mode; } - volume_t ReceiverModel::headphone_volume() const { return headphone_volume_; } @@ -112,36 +119,41 @@ uint32_t ReceiverModel::baseband_oversampling() const { return baseband_configuration.decimation_factor; } - void ReceiverModel::enable() { + enabled_ = true; radio::set_direction(rf::Direction::Receive); update_tuning_frequency(); + update_antenna_bias(); update_rf_amp(); update_lna(); update_vga(); update_baseband_bandwidth(); update_baseband_configuration(); - radio::streaming_enable(); update_headphone_volume(); } -void ReceiverModel::disable() { - /* TODO: This is a dumb hack to stop baseband from working so hard. */ - BasebandConfigurationMessage message { - .configuration = { - .mode = NONE, - .sampling_rate = 0, - .decimation_factor = 1, +void ReceiverModel::baseband_disable() { + shared_memory.baseband_queue.push_and_wait( + BasebandConfigurationMessage { + .configuration = { }, } - }; - shared_memory.baseband_queue.push(message); + ); +} +void ReceiverModel::disable() { + enabled_ = false; + update_antenna_bias(); + baseband_disable(); + + // TODO: Responsibility for enabling/disabling the radio is muddy. + // Some happens in ReceiverModel, some inside radio namespace. radio::disable(); } int32_t ReceiverModel::tuning_offset() { - if( baseband_configuration.mode == 4 ) { + if( (baseband_configuration.mode == 4) || + (baseband_configuration.mode == 6) ) { return 0; } else { return -(sampling_rate() / 4); @@ -152,6 +164,10 @@ void ReceiverModel::update_tuning_frequency() { radio::set_tuning_frequency(persistent_memory::tuned_frequency() + tuning_offset()); } +void ReceiverModel::update_antenna_bias() { + radio::set_antenna_bias(antenna_bias_ && enabled_); +} + void ReceiverModel::update_rf_amp() { radio::set_rf_amp(rf_amp_); } @@ -174,7 +190,12 @@ void ReceiverModel::set_baseband_configuration(const BasebandConfiguration confi } void ReceiverModel::update_baseband_configuration() { - radio::streaming_disable(); + // TODO: Move more low-level radio control stuff to M4. It'll enable tighter + // synchronization for things like wideband (sweeping) spectrum analysis, and + // protocols that need quick RX/TX turn-around. + + // Disabling baseband while changing sampling rates seems like a good idea... + baseband_disable(); clock_manager.set_sampling_frequency(sampling_rate() * baseband_oversampling()); update_tuning_frequency(); @@ -182,8 +203,6 @@ void ReceiverModel::update_baseband_configuration() { BasebandConfigurationMessage message { baseband_configuration }; shared_memory.baseband_queue.push(message); - - radio::streaming_enable(); } void ReceiverModel::update_headphone_volume() { diff --git a/firmware/application/receiver_model.hpp b/firmware/application/receiver_model.hpp index 296d60d8..ac3ce913 100644 --- a/firmware/application/receiver_model.hpp +++ b/firmware/application/receiver_model.hpp @@ -33,6 +33,16 @@ class ReceiverModel { public: + enum class Mode : int32_t { + AMAudio = 0, + NarrowbandFMAudio = 1, + WidebandFMAudio = 2, + AIS = 3, + SpectrumAnalysis = 4, + TPMS = 5, + ERT = 6, + }; + constexpr ReceiverModel( ClockManager& clock_manager ) : clock_manager(clock_manager) @@ -48,6 +58,9 @@ public: int32_t reference_ppm_correction() const; void set_reference_ppm_correction(int32_t v); + bool antenna_bias() const; + void set_antenna_bias(bool enabled); + bool rf_amp() const; void set_rf_amp(bool enabled); @@ -76,14 +89,16 @@ public: private: rf::Frequency frequency_step_ { 25000 }; + bool enabled_ { false }; bool rf_amp_ { false }; + bool antenna_bias_ { false }; int32_t lna_gain_db_ { 32 }; uint32_t baseband_bandwidth_ { max2837::filter::bandwidth_minimum }; int32_t vga_gain_db_ { 32 }; BasebandConfiguration baseband_configuration { - .mode = NONE, + .mode = 1, /* TODO: Enum! */ .sampling_rate = 3072000, - .decimation_factor = 4, + .decimation_factor = 1, }; volume_t headphone_volume_ { -43.0_dB }; ClockManager& clock_manager; @@ -91,6 +106,7 @@ private: int32_t tuning_offset(); void update_tuning_frequency(); + void update_antenna_bias(); void update_rf_amp(); void update_lna(); void update_baseband_bandwidth(); @@ -98,6 +114,7 @@ private: void update_baseband_configuration(); void update_headphone_volume(); + void baseband_disable(); }; #endif/*__RECEIVER_MODEL_H__*/ diff --git a/firmware/application/irq_ipc.hpp b/firmware/application/recent_entries.cpp similarity index 81% rename from firmware/application/irq_ipc.hpp rename to firmware/application/recent_entries.cpp index 70d59d15..bfb21aee 100644 --- a/firmware/application/irq_ipc.hpp +++ b/firmware/application/recent_entries.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. * * This file is part of PortaPack. * @@ -19,9 +19,4 @@ * Boston, MA 02110-1301, USA. */ -#ifndef __IRQ_IPC_H__ -#define __IRQ_IPC_H__ - -void m4txevent_interrupt_enable(); - -#endif/*__IRQ_IPC_H__*/ +#include "recent_entries.hpp" diff --git a/firmware/application/recent_entries.hpp b/firmware/application/recent_entries.hpp new file mode 100644 index 00000000..8fbbb71a --- /dev/null +++ b/firmware/application/recent_entries.hpp @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2014 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 __RECENT_ENTRIES_H__ +#define __RECENT_ENTRIES_H__ + +#include "ui_widget.hpp" +#include "ui_font_fixed_8x16.hpp" + +#include +#include +#include +#include +#include +#include +#include + +template +class RecentEntries { +public: + using EntryType = Entry; + using Key = typename Entry::Key; + using ContainerType = std::list; + using const_reference = typename ContainerType::const_reference; + using const_iterator = typename ContainerType::const_iterator; + using RangeType = std::pair; + + const Entry& on_packet(const Key key, const Packet& packet) { + auto matching_recent = find(key); + if( matching_recent != std::end(entries) ) { + // Found within. Move to front of list, increment counter. + entries.push_front(*matching_recent); + entries.erase(matching_recent); + } else { + entries.emplace_front(key); + truncate_entries(); + } + + auto& entry = entries.front(); + entry.update(packet); + + return entry; + } + + const_reference front() const { + return entries.front(); + } + + const_iterator find(const Key key) const { + return std::find_if( + std::begin(entries), std::end(entries), + [key](const Entry& e) { return e.key() == key; } + ); + } + + const_iterator begin() const { + return entries.begin(); + } + + const_iterator end() const { + return entries.end(); + } + + bool empty() const { + return entries.empty(); + } + + RangeType range_around( + const_iterator item, const size_t count + ) const { + auto start = item; + auto end = item; + size_t i = 0; + + // Move start iterator toward first entry. + while( (start != std::begin(entries)) && (i < count / 2) ) { + std::advance(start, -1); + i++; + } + + // Move end iterator toward last entry. + while( (end != std::end(entries)) && (i < count) ) { + std::advance(end, 1); + i++; + } + + return { start, end }; + } + +private: + ContainerType entries; + const size_t entries_max = 64; + + void truncate_entries() { + while(entries.size() > entries_max) { + entries.pop_back(); + } + } +}; + +namespace ui { + +template +class RecentEntriesView : public View { +public: + using Entry = typename Entries::EntryType; + + std::function on_select; + + RecentEntriesView( + Entries& recent + ) : recent { recent } + { + flags.focusable = true; + } + + void paint(Painter& painter) override { + const auto r = screen_rect(); + const auto& s = style(); + + Rect target_rect { r.pos, { r.width(), s.font.line_height() }}; + const size_t visible_item_count = r.height() / s.font.line_height(); + + const Style style_header { + .font = font::fixed_8x16, + .background = Color::blue(), + .foreground = Color::white(), + }; + + draw_header(target_rect, painter, style_header); + target_rect.pos.y += target_rect.height(); + + auto selected = recent.find(selected_key); + if( selected == std::end(recent) ) { + selected = std::begin(recent); + } + + auto range = recent.range_around(selected, visible_item_count); + + for(auto p = range.first; p != range.second; p++) { + const auto& entry = *p; + const auto is_selected_key = (selected_key == entry.key()); + draw(entry, target_rect, painter, s, (has_focus() && is_selected_key)); + target_rect.pos.y += target_rect.height(); + } + + painter.fill_rectangle( + { target_rect.left(), target_rect.top(), target_rect.width(), r.bottom() - target_rect.top() }, + style().background + ); + } + + bool on_encoder(const EncoderEvent event) override { + advance(event); + return true; + } + + bool on_key(const ui::KeyEvent event) override { + if( event == ui::KeyEvent::Select ) { + if( on_select ) { + const auto selected = recent.find(selected_key); + if( selected != std::end(recent) ) { + on_select(*selected); + return true; + } + } + } + return false; + } + + void on_focus() override { + advance(0); + } + +private: + Entries& recent; + + using EntryKey = typename Entry::Key; + EntryKey selected_key = Entry::invalid_key; + + void advance(const int32_t amount) { + auto selected = recent.find(selected_key); + if( selected == std::end(recent) ) { + if( recent.empty() ) { + selected_key = Entry::invalid_key; + } else { + selected_key = recent.front().key(); + } + } else { + if( amount < 0 ) { + if( selected != std::begin(recent) ) { + std::advance(selected, -1); + } + } + if( amount > 0 ) { + std::advance(selected, 1); + if( selected == std::end(recent) ) { + return; + } + } + selected_key = selected->key(); + } + + set_dirty(); + } + + void draw_header( + const Rect& target_rect, + Painter& painter, + const Style& style + ); + + void draw( + const Entry& entry, + const Rect& target_rect, + Painter& painter, + const Style& style, + const bool is_selected + ); +}; + +} /* namespace ui */ + +#endif/*__RECENT_ENTRIES_H__*/ diff --git a/firmware/application/rf_path.hpp b/firmware/application/rf_path.hpp index 97cbaa08..84a0a0b3 100644 --- a/firmware/application/rf_path.hpp +++ b/firmware/application/rf_path.hpp @@ -22,29 +22,14 @@ #ifndef __RF_PATH_H__ #define __RF_PATH_H__ +#include "utility.hpp" + #include namespace rf { using Frequency = int64_t; - -struct FrequencyRange { - Frequency min; - Frequency max; - /* TODO: static_assert low < high? */ - - bool below_range(const Frequency f) const { - return f < min; - } - - bool contains(const Frequency f) const { - return (f >= min) && (f < max); - } - - bool out_of_range(const Frequency f) const { - return !contains(f); - } -}; +using FrequencyRange = range_t; enum class Direction { /* Zero-based, used as index into table */ @@ -54,20 +39,9 @@ enum class Direction { namespace path { -constexpr FrequencyRange band_low { - .min = 0, - .max = 2150000000, -}; - -constexpr FrequencyRange band_high { - .min = 2750000000, - .max = 7250000000, -}; - -constexpr FrequencyRange band_mid { - .min = band_low.max, - .max = band_high.min, -}; +constexpr FrequencyRange band_low { 0, 2150000000 }; +constexpr FrequencyRange band_high { 2750000000, 7250000000 }; +constexpr FrequencyRange band_mid { band_low.maximum, band_high.minimum }; enum class Band { /* Zero-based, used as index into frequency_bands table */ @@ -94,10 +68,7 @@ private: } /* path */ -constexpr FrequencyRange tuning_range { - .min = path::band_low.min, - .max = path::band_high.max, -}; +constexpr FrequencyRange tuning_range { path::band_low.minimum, path::band_high.maximum }; } /* rf */ diff --git a/firmware/application/rffc507x.cpp b/firmware/application/rffc507x.cpp index 1ac3fec3..e15265e4 100644 --- a/firmware/application/rffc507x.cpp +++ b/firmware/application/rffc507x.cpp @@ -51,10 +51,7 @@ constexpr auto reference_frequency = rffc5072_reference_f; namespace vco { -constexpr rf::FrequencyRange range { - .min = 2700000000U, - .max = 5400000000U, -}; +constexpr rf::FrequencyRange range { 2700000000, 5400000000 }; } /* namespace vco */ @@ -66,10 +63,7 @@ constexpr size_t divider_log2_max = 5; constexpr size_t divider_min = 1U << divider_log2_min; constexpr size_t divider_max = 1U << divider_log2_max; -constexpr rf::FrequencyRange range { - .min = vco::range.min / divider_max, - .max = vco::range.max / divider_min, -}; +constexpr rf::FrequencyRange range { vco::range.minimum / divider_max, vco::range.maximum / divider_min }; size_t divider_log2(const rf::Frequency lo_frequency) { /* TODO: Error */ @@ -183,23 +177,23 @@ void RFFC507x::flush() { } } -inline void RFFC507x::write(const address_t reg_num, const spi::reg_t value) { +void RFFC507x::write(const address_t reg_num, const spi::reg_t value) { _bus.write(reg_num, value); } -inline spi::reg_t RFFC507x::read(const address_t reg_num) { +spi::reg_t RFFC507x::read(const address_t reg_num) { return _bus.read(reg_num); } -inline void RFFC507x::write(const Register reg, const spi::reg_t value) { +void RFFC507x::write(const Register reg, const spi::reg_t value) { write(toUType(reg), value); } -inline spi::reg_t RFFC507x::read(const Register reg) { +spi::reg_t RFFC507x::read(const Register reg) { return read(toUType(reg)); } -inline void RFFC507x::flush_one(const Register reg) { +void RFFC507x::flush_one(const Register reg) { const auto reg_num = toUType(reg); write(reg_num, _map.w[reg_num]); _dirty.clear(reg_num); @@ -262,6 +256,18 @@ void RFFC507x::set_frequency(const rf::Frequency lo_frequency) { flush(); } +void RFFC507x::set_gpo1(const bool new_value) { + if( new_value ) { + _map.r.gpo.p2gpo |= 1; + _map.r.gpo.p1gpo |= 1; + } else { + _map.r.gpo.p2gpo &= ~1; + _map.r.gpo.p1gpo &= ~1; + } + + flush_one(Register::GPO); +} + spi::reg_t RFFC507x::readback(const Readback readback) { /* TODO: This clobbers the rest of the DEV_CTRL register * Time to implement bitfields for registers. @@ -272,10 +278,6 @@ spi::reg_t RFFC507x::readback(const Readback readback) { return read(Register::READBACK); } -RegisterMap RFFC507x::registers() { - return _map; -} - #if 0 /* Test of RFFC507x reset over temperature */ while(true) { diff --git a/firmware/application/rffc507x.hpp b/firmware/application/rffc507x.hpp index 72fdd6b9..016f0f11 100644 --- a/firmware/application/rffc507x.hpp +++ b/firmware/application/rffc507x.hpp @@ -808,8 +808,9 @@ public: void set_mixer_current(const uint8_t value); void set_frequency(const rf::Frequency lo_frequency); - - RegisterMap registers(); + void set_gpo1(const bool new_value); + + reg_t read(const address_t reg_num); private: spi::SPI _bus; @@ -818,7 +819,6 @@ private: DirtyRegisters _dirty; void write(const address_t reg_num, const reg_t value); - reg_t read(const address_t reg_num); void write(const Register reg, const reg_t value); reg_t read(const Register reg); diff --git a/firmware/application/sd_card.cpp b/firmware/application/sd_card.cpp new file mode 100644 index 00000000..78f8e287 --- /dev/null +++ b/firmware/application/sd_card.cpp @@ -0,0 +1,80 @@ +/* + * 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 "sd_card.hpp" + +#include + +#include "ff.h" + +namespace sd_card { + +namespace { + +bool card_present = false; + +Status status_ { Status::NotPresent }; + +FATFS fs; + +FRESULT mount() { + return f_mount(&fs, "", 0); +} + +FRESULT unmount() { + return f_mount(NULL, "", 0); +} + +} /* namespace */ + +Signal status_signal; + +void poll_inserted() { + const auto card_present_now = sdc_lld_is_card_inserted(&SDCD1); + if( card_present_now != card_present ) { + card_present = card_present_now; + + Status new_status { card_present ? Status::Present : Status::NotPresent }; + + if( card_present ) { + if( sdcConnect(&SDCD1) == CH_SUCCESS ) { + if( mount() == FR_OK ) { + new_status = Status::Mounted; + } else { + new_status = Status::MountError; + } + } else { + new_status = Status::ConnectError; + } + } else { + sdcDisconnect(&SDCD1); + } + + status_ = new_status; + status_signal.emit(status_); + } +} + +Status status() { + return status_; +} + +} /* namespace sd_card */ diff --git a/firmware/application/sd_card.hpp b/firmware/application/sd_card.hpp index d7827b90..1989f465 100644 --- a/firmware/application/sd_card.hpp +++ b/firmware/application/sd_card.hpp @@ -22,26 +22,26 @@ #ifndef __SD_CARD_H__ #define __SD_CARD_H__ -#include "ff.h" +#include + +#include "signal.hpp" namespace sd_card { -namespace filesystem { -namespace { +enum class Status : int32_t { + IOError = -3, + MountError = -2, + ConnectError = -1, + NotPresent = 0, + Present = 1, + Mounted = 2, +}; -FATFS fs; +extern Signal status_signal; -} +void poll_inserted(); +Status status(); -FRESULT mount() { - return f_mount(&fs, "", 0); -} - -FRESULT unmount() { - return f_mount(NULL, "", 0); -} - -} /* namespace filesystem */ } /* namespace sd_card */ #endif/*__SD_CARD_H__*/ diff --git a/firmware/application/signal.hpp b/firmware/application/signal.hpp new file mode 100644 index 00000000..09baf777 --- /dev/null +++ b/firmware/application/signal.hpp @@ -0,0 +1,85 @@ +/* + * 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 __SIGNAL_H__ +#define __SIGNAL_H__ + +#include +#include +#include +#include + +#include "utility.hpp" + +/* Tweaked and vastly simplified implementation of Simple::Signal, from + * https://testbit.eu/cpp11-signal-system-performance/ + * + * Original license: + * CC0 Public Domain + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +using SignalToken = uint32_t; + +template +struct Signal { + using Callback = std::function; + + SignalToken operator+=(const Callback& callback) { + const SignalToken token = next_token++; + entries.emplace_back(std::make_unique(callback, token)); + return token; + } + + bool operator-=(const SignalToken token) { + entries.remove_if([token](EntryType& entry) { + return entry.get()->token == token; + }); + return true; + } + + void emit(Args... args) { + for(auto& entry : entries) { + entry.get()->callback(args...); + }; + } + +private: + struct CallbackEntry { + const Callback callback; + const SignalToken token; + + constexpr CallbackEntry( + const Callback& callback, + const SignalToken token + ) : callback { callback }, + token { token } + { + } + }; + + using EntryType = std::unique_ptr; + + std::list entries; + SignalToken next_token = 1; +}; + +#endif/*__SIGNAL_H__*/ diff --git a/firmware/application/spectrum_analysis_app.cpp b/firmware/application/spectrum_analysis_app.cpp new file mode 100644 index 00000000..de3a14b0 --- /dev/null +++ b/firmware/application/spectrum_analysis_app.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2014 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 "spectrum_analysis_app.hpp" + +#include "portapack.hpp" +using namespace portapack; + +SpectrumAnalysisModel::SpectrumAnalysisModel() { + receiver_model.set_baseband_configuration({ + .mode = 4, + .sampling_rate = 20000000, + .decimation_factor = 1, + }); + receiver_model.set_baseband_bandwidth(12000000); +} diff --git a/firmware/application/spectrum_analysis_app.hpp b/firmware/application/spectrum_analysis_app.hpp new file mode 100644 index 00000000..9e8196af --- /dev/null +++ b/firmware/application/spectrum_analysis_app.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2014 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 __SPECTRUM_ANALYSIS_APP_H__ +#define __SPECTRUM_ANALYSIS_APP_H__ + +#include "receiver_model.hpp" +#include "ui_spectrum.hpp" + +class SpectrumAnalysisModel { +public: + SpectrumAnalysisModel(); +}; + +namespace ui { + +class SpectrumAnalysisView : public spectrum::WaterfallWidget { +public: + +private: + SpectrumAnalysisModel model; +}; + +} /* namespace ui */ + +#endif/*__SPECTRUM_ANALYSIS_APP_H__*/ diff --git a/firmware/application/string_format.cpp b/firmware/application/string_format.cpp new file mode 100644 index 00000000..5e151297 --- /dev/null +++ b/firmware/application/string_format.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2014 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 "string_format.hpp" + +static char* to_string_dec_uint_internal( + char* p, + uint32_t n +) { + *p = 0; + auto q = p; + + do { + const uint32_t d = n % 10; + const char c = d + 48; + *(--q) = c; + n /= 10; + } while( n != 0 ); + + return q; +} + +static char* to_string_dec_uint_pad_internal( + char* const term, + const uint32_t n, + const int32_t l, + const char fill +) { + auto q = to_string_dec_uint_internal(term, n); + + if( fill ) { + while( (term - q) < l ) { + *(--q) = fill; + } + } + + return q; +} + +std::string to_string_dec_uint( + const uint32_t n, + const int32_t l, + const char fill +) { + char p[16]; + auto term = p + sizeof(p) - 1; + auto q = to_string_dec_uint_pad_internal(term, n, l, fill); + + // Right justify. + while( (term - q) < l ) { + *(--q) = ' '; + } + + return q; +} + +std::string to_string_dec_int( + const int32_t n, + const int32_t l, + const char fill +) { + const size_t negative = (n < 0) ? 1 : 0; + uint32_t n_abs = negative ? -n : n; + + char p[16]; + auto term = p + sizeof(p) - 1; + auto q = to_string_dec_uint_pad_internal(term, n_abs, l - negative, fill); + + // Add sign. + if( negative ) { + *(--q) = '-'; + } + + // Right justify. + while( (term - q) < l ) { + *(--q) = ' '; + } + + return q; +} + +static void to_string_hex_internal(char* p, const uint32_t n, const int32_t l) { + const uint32_t d = n & 0xf; + p[l] = (d > 9) ? (d + 87) : (d + 48); + if( l > 0 ) { + to_string_hex_internal(p, n >> 4, l - 1); + } +} + +std::string to_string_hex(const uint32_t n, const int32_t l) { + char p[16]; + to_string_hex_internal(p, n, l - 1); + p[l] = 0; + return p; +} + +std::string to_string_datetime(const rtc::RTC& value) { + return to_string_dec_uint(value.year(), 4, '0') + "/" + + to_string_dec_uint(value.month(), 2, '0') + "/" + + to_string_dec_uint(value.day(), 2, '0') + " " + + to_string_dec_uint(value.hour(), 2, '0') + ":" + + to_string_dec_uint(value.minute(), 2, '0') + ":" + + to_string_dec_uint(value.second(), 2, '0'); +} + +std::string to_string_timestamp(const rtc::RTC& value) { + return to_string_dec_uint(value.year(), 4, '0') + + to_string_dec_uint(value.month(), 2, '0') + + to_string_dec_uint(value.day(), 2, '0') + + to_string_dec_uint(value.hour(), 2, '0') + + to_string_dec_uint(value.minute(), 2, '0') + + to_string_dec_uint(value.second(), 2, '0'); +} diff --git a/firmware/application/string_format.hpp b/firmware/application/string_format.hpp new file mode 100644 index 00000000..b90030e2 --- /dev/null +++ b/firmware/application/string_format.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 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 __STRING_FORMAT_H__ +#define __STRING_FORMAT_H__ + +#include +#include + +// BARF! rtc::RTC is leaking everywhere. +#include "lpc43xx_cpp.hpp" +using namespace lpc43xx; + +// TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp... + +std::string to_string_dec_uint(const uint32_t n, const int32_t l = 0, const char fill = 0); +std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0); +std::string to_string_hex(const uint32_t n, const int32_t l = 0); + +std::string to_string_datetime(const rtc::RTC& value); +std::string to_string_timestamp(const rtc::RTC& value); + +#endif/*__STRING_FORMAT_H__*/ diff --git a/firmware/application/temperature_logger.cpp b/firmware/application/temperature_logger.cpp new file mode 100644 index 00000000..474c3dd0 --- /dev/null +++ b/firmware/application/temperature_logger.cpp @@ -0,0 +1,69 @@ +/* + * 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 "temperature_logger.hpp" + +#include "radio.hpp" + +#include + +void TemperatureLogger::second_tick() { + sample_phase++; + if( sample_phase >= sample_interval ) { + push_sample(read_sample()); + } +} + +size_t TemperatureLogger::size() const { + return std::min(capacity(), samples_count); +} + +size_t TemperatureLogger::capacity() const { + return samples.size(); +} + +std::vector TemperatureLogger::history() const { + std::vector result; + + const auto n = size(); + result.resize(n); + + // Copy the last N samples from the buffer, since new samples are added at the end. + std::copy(samples.cend() - n, samples.cend(), result.data()); + + return result; +} + +TemperatureLogger::sample_t TemperatureLogger::read_sample() { + // MAX2837 does not return a valid temperature if in "shutdown" mode. + return radio::second_if.temp_sense() & 0x1f; +} + +void TemperatureLogger::push_sample(const TemperatureLogger::sample_t sample) { + // Started out building a pseudo-FIFO, then got lazy. + + // Shift samples: samples[1:] -> samples[0:-1] + // New sample goes into samples[-1] + std::copy(samples.cbegin() + 1, samples.cend(), samples.begin()); + samples.back() = sample; + samples_count++; + sample_phase = 0; +} diff --git a/firmware/application/temperature_logger.hpp b/firmware/application/temperature_logger.hpp new file mode 100644 index 00000000..51f8e451 --- /dev/null +++ b/firmware/application/temperature_logger.hpp @@ -0,0 +1,52 @@ +/* + * 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 __TEMPERATURE_LOGGER_H__ +#define __TEMPERATURE_LOGGER_H__ + +#include +#include +#include +#include + +class TemperatureLogger { +public: + using sample_t = uint8_t; + + void second_tick(); + + size_t size() const; + size_t capacity() const; + + std::vector history() const; + +private: + std::array samples; + + static constexpr size_t sample_interval = 5; + size_t sample_phase = 0; + size_t samples_count = 0; + + sample_t read_sample(); + void push_sample(const sample_t sample); +}; + +#endif/*__TEMPERATURE_LOGGER_H__*/ diff --git a/firmware/application/tpms_app.cpp b/firmware/application/tpms_app.cpp new file mode 100644 index 00000000..2cc05c08 --- /dev/null +++ b/firmware/application/tpms_app.cpp @@ -0,0 +1,285 @@ +/* + * 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 "tpms_app.hpp" + +#include "event_m0.hpp" + +#include "portapack.hpp" +using namespace portapack; + +#include "string_format.hpp" + +#include "crc.hpp" + +#include "utility.hpp" + +namespace tpms { + +namespace format { + +std::string type(Reading::Type type) { + return to_string_dec_uint(toUType(type), 2); +} + +std::string id(TransponderID id) { + return to_string_hex(id.value(), 8); +} + +std::string pressure(Pressure pressure) { + return to_string_dec_int(pressure.kilopascal(), 3); +} + +std::string temperature(Temperature temperature) { + return to_string_dec_int(temperature.celsius(), 3); +} + +} /* namespace format */ + +Timestamp Packet::received_at() const { + return packet_.timestamp(); +} + +ManchesterFormatted Packet::symbols_formatted() const { + return format_manchester(decoder_); +} + +Optional Packet::reading() const { + const auto length = crc_valid_length(); + + switch(length) { + case 64: + return Reading { + Reading::Type::FLM_64, + reader_.read(0, 32), + Pressure { static_cast(reader_.read(32, 8)) * 4 / 3 }, + Temperature { static_cast(reader_.read(40, 8) & 0x7f) - 50 } + }; + + case 72: + return Reading { + Reading::Type::FLM_72, + reader_.read(0, 32), + Pressure { static_cast(reader_.read(40, 8)) * 4 / 3 }, + Temperature { static_cast(reader_.read(48, 8)) - 50 } + }; + + case 80: + return Reading { + Reading::Type::FLM_80, + reader_.read(8, 32), + Pressure { static_cast(reader_.read(48, 8)) * 4 / 3 }, + Temperature { static_cast(reader_.read(56, 8)) - 50 } + }; + + default: + return { }; + } +} + +size_t Packet::crc_valid_length() const { + constexpr uint32_t checksum_bytes = 0b1111111; + constexpr uint32_t crc_72_bytes = 0b111111111; + constexpr uint32_t crc_80_bytes = 0b1111111110; + + std::array bytes; + for(size_t i=0; i crc_72 { 0x01, 0x00 }; + CRC crc_80 { 0x01, 0x00 }; + + for(size_t i=0; i= 1<<32, weirdness will ensue! + const auto tuning_frequency_str = to_string_dec_uint(tuning_frequency, 10); + + std::string entry = tuning_frequency_str + " FSK 38.4 19.2 " + hex_formatted.data + "/" + hex_formatted.errors; + log_file.write_entry(packet.received_at(), entry); + } +} + +const TPMSRecentEntry::Key TPMSRecentEntry::invalid_key = { tpms::Reading::Type::None, 0 }; + +void TPMSRecentEntry::update(const tpms::Reading& reading) { + received_count++; + + if( reading.pressure().is_valid() ) { + last_pressure = reading.pressure(); + } + if( reading.temperature().is_valid() ) { + last_temperature = reading.temperature(); + } +} + +namespace ui { + +static const std::array, 5> tpms_columns { { + { "Tp", 2 }, + { "ID", 8 }, + { "kPa", 3 }, + { "C", 3 }, + { "Cnt", 3 }, +} }; + +template<> +void RecentEntriesView::draw_header( + const Rect& target_rect, + Painter& painter, + const Style& style +) { + auto x = 0; + for(const auto& column : tpms_columns) { + const auto width = column.second; + auto text = column.first; + if( width > text.length() ) { + text.append(width - text.length(), ' '); + } + + painter.draw_string({ x, target_rect.pos.y }, style, text); + x += (width * 8) + 8; + } +} + +template<> +void RecentEntriesView::draw( + const Entry& entry, + const Rect& target_rect, + Painter& painter, + const Style& style, + const bool is_selected +) { + const auto& draw_style = is_selected ? style.invert() : style; + + std::string line = tpms::format::type(entry.type) + " " + tpms::format::id(entry.id); + + if( entry.last_pressure.is_valid() ) { + line += " " + tpms::format::pressure(entry.last_pressure.value()); + } else { + line += " " " "; + } + + if( entry.last_temperature.is_valid() ) { + line += " " + tpms::format::temperature(entry.last_temperature.value()); + } else { + line += " " " "; + } + + if( entry.received_count > 999 ) { + line += " +++"; + } else { + line += " " + to_string_dec_uint(entry.received_count, 3); + } + + line.resize(target_rect.width() / 8, ' '); + painter.draw_string(target_rect.pos, draw_style, line); +} + +TPMSAppView::TPMSAppView(NavigationView&) { + add_children({ { + &recent_entries_view, + } }); + + EventDispatcher::message_map().register_handler(Message::ID::TPMSPacket, + [this](Message* const p) { + const auto message = static_cast(p); + const tpms::Packet packet { message->packet }; + this->on_packet(packet); + } + ); + + receiver_model.set_baseband_configuration({ + .mode = 5, + .sampling_rate = 2457600, + .decimation_factor = 1, + }); + receiver_model.set_baseband_bandwidth(1750000); + receiver_model.set_rf_amp(false); + receiver_model.set_lna(32); + receiver_model.set_vga(32); + receiver_model.set_tuning_frequency(315000000); + receiver_model.enable(); +} + +TPMSAppView::~TPMSAppView() { + receiver_model.disable(); + EventDispatcher::message_map().unregister_handler(Message::ID::TPMSPacket); +} + +void TPMSAppView::focus() { + recent_entries_view.focus(); +} + +void TPMSAppView::set_parent_rect(const Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + recent_entries_view.set_parent_rect({ 0, 0, new_parent_rect.width(), new_parent_rect.height() }); +} + +void TPMSAppView::on_packet(const tpms::Packet& packet) { + logger.on_packet(packet); + + const auto reading_opt = packet.reading(); + if( reading_opt.is_valid() ) { + const auto reading = reading_opt.value(); + recent.on_packet({ reading.type(), reading.id() }, reading); + recent_entries_view.set_dirty(); + } +} + +void TPMSAppView::on_show_list() { + recent_entries_view.hidden(false); + recent_entries_view.focus(); +} + +} /* namespace ui */ diff --git a/firmware/application/tpms_app.hpp b/firmware/application/tpms_app.hpp new file mode 100644 index 00000000..0ecf4884 --- /dev/null +++ b/firmware/application/tpms_app.hpp @@ -0,0 +1,226 @@ +/* + * 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 __TPMS_APP_H__ +#define __TPMS_APP_H__ + +#include "ui_widget.hpp" +#include "ui_navigation.hpp" + +#include "field_reader.hpp" +#include "baseband_packet.hpp" +#include "manchester.hpp" +#include "log_file.hpp" + +#include "recent_entries.hpp" + +#include "optional.hpp" + +#include "units.hpp" +using units::Temperature; +using units::Pressure; + +namespace tpms { + +class TransponderID { +public: + constexpr TransponderID( + ) : id_ { 0 } + { + } + + constexpr TransponderID( + const uint32_t id + ) : id_ { id } + { + } + + constexpr uint32_t value() const { + return id_; + } + +private: + uint32_t id_; +}; + +class Reading { +public: + enum Type { + None = 0, + FLM_64 = 1, + FLM_72 = 2, + FLM_80 = 3, + }; + + constexpr Reading( + ) : type_ { Type::None } + { + } + + constexpr Reading( + Type type, + TransponderID id + ) : type_ { type }, + id_ { id } + { + } + + constexpr Reading( + Type type, + TransponderID id, + Optional pressure = { }, + Optional temperature = { } + ) : type_ { type }, + id_ { id }, + pressure_ { pressure }, + temperature_ { temperature } + { + } + + Type type() const { + return type_; + } + + TransponderID id() const { + return id_; + } + + Optional pressure() const { + return pressure_; + } + + Optional temperature() const { + return temperature_; + } + +private: + Type type_ { Type::None }; + TransponderID id_ { 0 }; + Optional pressure_ { }; + Optional temperature_ { }; +}; + +class Packet { +public: + constexpr Packet( + const baseband::Packet& packet + ) : packet_ { packet }, + decoder_ { packet_, 0 }, + reader_ { decoder_ } + { + } + + Timestamp received_at() const; + + ManchesterFormatted symbols_formatted() const; + + Optional reading() const; + +private: + using Reader = FieldReader; + + const baseband::Packet packet_; + const ManchesterDecoder decoder_; + + const Reader reader_; + + size_t crc_valid_length() const; +}; + +} /* namespace tpms */ + +namespace std { + +constexpr bool operator==(const tpms::TransponderID& lhs, const tpms::TransponderID& rhs) { + return (lhs.value() == rhs.value()); +} + +} /* namespace std */ + +struct TPMSRecentEntry { + using Key = std::pair; + + static const Key invalid_key; + + tpms::Reading::Type type { invalid_key.first }; + tpms::TransponderID id { invalid_key.second }; + + size_t received_count { 0 }; + + Optional last_pressure; + Optional last_temperature; + + TPMSRecentEntry( + const Key& key + ) : type { key.first }, + id { key.second } + { + } + + Key key() const { + return { type, id }; + } + + void update(const tpms::Reading& reading); +}; + +using TPMSRecentEntries = RecentEntries; + +class TPMSLogger { +public: + void on_packet(const tpms::Packet& packet); + +private: + LogFile log_file { "tpms.txt" }; +}; + +namespace ui { + +using TPMSRecentEntriesView = RecentEntriesView; + +class TPMSAppView : public View { +public: + TPMSAppView(NavigationView& nav); + ~TPMSAppView(); + + void set_parent_rect(const Rect new_parent_rect) override; + + // Prevent painting of region covered entirely by a child. + // TODO: Add flag to View that specifies view does not need to be cleared before painting. + void paint(Painter&) override { }; + + void focus() override; + + std::string title() const override { return "TPMS"; }; + +private: + TPMSRecentEntries recent; + TPMSLogger logger; + + TPMSRecentEntriesView recent_entries_view { recent }; + + void on_packet(const tpms::Packet& packet); + void on_show_list(); +}; + +} /* namespace ui */ + +#endif/*__TPMS_APP_H__*/ diff --git a/firmware/application/tuning.cpp b/firmware/application/tuning.cpp index b662fbc3..0ca8711b 100644 --- a/firmware/application/tuning.cpp +++ b/firmware/application/tuning.cpp @@ -19,8 +19,6 @@ * Boston, MA 02110-1301, USA. */ -#include "ch.h" - #include "tuning.hpp" #include "utility.hpp" @@ -51,45 +49,30 @@ constexpr rf::Frequency high_band_second_lo_frequency(const rf::Frequency target Config low_band(const rf::Frequency target_frequency) { const rf::Frequency first_lo_frequency = target_frequency + low_band_second_lo_frequency(target_frequency); const rf::Frequency second_lo_frequency = first_lo_frequency - target_frequency; - return { - .first_lo_frequency = first_lo_frequency, - .second_lo_frequency = second_lo_frequency, - .rf_path_band = rf::path::Band::Low, - .baseband_q_invert = true, - }; + const bool baseband_q_invert = true; + return { first_lo_frequency, second_lo_frequency, rf::path::Band::Low, baseband_q_invert }; } Config mid_band(const rf::Frequency target_frequency) { - return { - .first_lo_frequency = 0, - .second_lo_frequency = target_frequency, - .rf_path_band = rf::path::Band::Mid, - .baseband_q_invert = false, - }; + return { 0, target_frequency, rf::path::Band::Mid, false }; } Config high_band(const rf::Frequency target_frequency) { const rf::Frequency first_lo_frequency = target_frequency - high_band_second_lo_frequency(target_frequency); const rf::Frequency second_lo_frequency = target_frequency - first_lo_frequency; - return { - .first_lo_frequency = first_lo_frequency, - .second_lo_frequency = second_lo_frequency, - .rf_path_band = rf::path::Band::High, - .baseband_q_invert = false, - }; + const bool baseband_q_invert = false; + return { first_lo_frequency, second_lo_frequency, rf::path::Band::High, baseband_q_invert }; } } /* namespace */ Config create(const rf::Frequency target_frequency) { /* TODO: This is some lame code. */ - if( target_frequency < rf::path::band_low.min ) { - return { }; - } else if( target_frequency < rf::path::band_low.max ) { + if( rf::path::band_low.contains(target_frequency) ) { return low_band(target_frequency); - } else if( target_frequency < rf::path::band_mid.max ) { + } else if( rf::path::band_mid.contains(target_frequency) ) { return mid_band(target_frequency); - } else if( target_frequency < rf::path::band_high.max ) { + } else if( rf::path::band_high.contains(target_frequency) ) { return high_band(target_frequency); } else { return { }; diff --git a/firmware/application/ui_audio.cpp b/firmware/application/ui_audio.cpp index 0dd698e2..65fd3832 100644 --- a/firmware/application/ui_audio.cpp +++ b/firmware/application/ui_audio.cpp @@ -21,12 +21,16 @@ #include "ui_audio.hpp" +#include "event_m0.hpp" + +#include "utility.hpp" + #include namespace ui { void Audio::on_show() { - context().message_map().register_handler(Message::ID::AudioStatistics, + EventDispatcher::message_map().register_handler(Message::ID::AudioStatistics, [this](const Message* const p) { this->on_statistics_update(static_cast(p)->statistics); } @@ -34,21 +38,23 @@ void Audio::on_show() { } void Audio::on_hide() { - context().message_map().unregister_handler(Message::ID::AudioStatistics); + EventDispatcher::message_map().unregister_handler(Message::ID::AudioStatistics); } void Audio::paint(Painter& painter) { const auto r = screen_rect(); - const int32_t db_min = -r.width(); - const int32_t x_0 = 0; - const int32_t x_rms = std::max(x_0, rms_db_ - db_min); - const int32_t x_max = std::max(x_rms + 1, max_db_ - db_min); - const int32_t x_lim = r.width(); + constexpr int db_min = -96; + constexpr int db_max = 0; + constexpr int db_delta = db_max - db_min; + const range_t x_rms_range { 0, r.width() - 1 }; + const auto x_rms = x_rms_range.clip((rms_db_ - db_min) * r.width() / db_delta); + const range_t x_max_range { x_rms + 1, r.width() }; + const auto x_max = x_max_range.clip((max_db_ - db_min) * r.width() / db_delta); const Rect r0 { - static_cast(r.left() + x_0), r.top(), - static_cast(x_rms - x_0), r.height() + static_cast(r.left()), r.top(), + static_cast(x_rms), r.height() }; painter.fill_rectangle( r0, @@ -75,7 +81,7 @@ void Audio::paint(Painter& painter) { const Rect r3 { static_cast(r.left() + x_max), r.top(), - static_cast(x_lim - x_max), r.height() + static_cast(r.width() - x_max), r.height() }; painter.fill_rectangle( r3, diff --git a/firmware/application/ui_audio.hpp b/firmware/application/ui_audio.hpp index 0ec2e6a5..cecacc7a 100644 --- a/firmware/application/ui_audio.hpp +++ b/firmware/application/ui_audio.hpp @@ -26,6 +26,8 @@ #include "ui_widget.hpp" #include "ui_painter.hpp" +#include "message.hpp" + #include namespace ui { diff --git a/firmware/application/ui_baseband_stats_view.cpp b/firmware/application/ui_baseband_stats_view.cpp index fcf0816e..0d576e81 100644 --- a/firmware/application/ui_baseband_stats_view.cpp +++ b/firmware/application/ui_baseband_stats_view.cpp @@ -21,12 +21,16 @@ #include "ui_baseband_stats_view.hpp" +#include "event_m0.hpp" + #include #include #include "hackrf_hal.hpp" using namespace hackrf::one; +#include "string_format.hpp" + namespace ui { /* BasebandStatsView *****************************************************/ @@ -38,7 +42,7 @@ BasebandStatsView::BasebandStatsView() { } void BasebandStatsView::on_show() { - context().message_map().register_handler(Message::ID::BasebandStatistics, + EventDispatcher::message_map().register_handler(Message::ID::BasebandStatistics, [this](const Message* const p) { this->on_statistics_update(static_cast(p)->statistics); } @@ -46,7 +50,7 @@ void BasebandStatsView::on_show() { } void BasebandStatsView::on_hide() { - context().message_map().unregister_handler(Message::ID::BasebandStatistics); + EventDispatcher::message_map().unregister_handler(Message::ID::BasebandStatistics); } diff --git a/firmware/application/ui_channel.cpp b/firmware/application/ui_channel.cpp index a5c8a568..62f1b49a 100644 --- a/firmware/application/ui_channel.cpp +++ b/firmware/application/ui_channel.cpp @@ -21,12 +21,16 @@ #include "ui_channel.hpp" +#include "event_m0.hpp" + +#include "utility.hpp" + #include namespace ui { void Channel::on_show() { - context().message_map().register_handler(Message::ID::ChannelStatistics, + EventDispatcher::message_map().register_handler(Message::ID::ChannelStatistics, [this](const Message* const p) { this->on_statistics_update(static_cast(p)->statistics); } @@ -34,20 +38,21 @@ void Channel::on_show() { } void Channel::on_hide() { - context().message_map().unregister_handler(Message::ID::ChannelStatistics); + EventDispatcher::message_map().unregister_handler(Message::ID::ChannelStatistics); } void Channel::paint(Painter& painter) { const auto r = screen_rect(); - const int32_t db_min = -r.width(); - const int32_t x_0 = 0; - const int32_t x_max = std::max(x_0, max_db_ - db_min); - const int32_t x_lim = r.width(); + constexpr int db_min = -96; + constexpr int db_max = 0; + constexpr int db_delta = db_max - db_min; + const range_t x_max_range { 0, r.width() - 1 }; + const auto x_max = x_max_range.clip((max_db_ - db_min) * r.width() / db_delta); const Rect r0 { - static_cast(r.left() + x_0), r.top(), - static_cast(x_max - x_0), r.height() + static_cast(r.left()), r.top(), + static_cast(x_max), r.height() }; painter.fill_rectangle( r0, @@ -65,7 +70,7 @@ void Channel::paint(Painter& painter) { const Rect r2 { static_cast(r.left() + x_max + 1), r.top(), - static_cast(x_lim - (x_max + 1)), r.height() + static_cast(r.width() - (x_max + 1)), r.height() }; painter.fill_rectangle( r2, diff --git a/firmware/application/ui_channel.hpp b/firmware/application/ui_channel.hpp index 34457de2..97ac61c8 100644 --- a/firmware/application/ui_channel.hpp +++ b/firmware/application/ui_channel.hpp @@ -26,6 +26,8 @@ #include "ui_widget.hpp" #include "ui_painter.hpp" +#include "message.hpp" + #include namespace ui { diff --git a/firmware/application/ui_console.cpp b/firmware/application/ui_console.cpp index 0c299acd..65482236 100644 --- a/firmware/application/ui_console.cpp +++ b/firmware/application/ui_console.cpp @@ -40,17 +40,21 @@ void Console::write(const std::string message) { const Font& font = s.font; const auto rect = screen_rect(); for(const auto c : message) { - const auto glyph = font.glyph(c); - const auto advance = glyph.advance(); - if( (pos.x + advance.x) > rect.width() ) { + if( c == '\n' ) { crlf(); + } else { + const auto glyph = font.glyph(c); + const auto advance = glyph.advance(); + if( (pos.x + advance.x) > rect.width() ) { + crlf(); + } + const Point pos_glyph { + rect.pos.x + pos.x, + display.scroll_area_y(pos.y) + }; + display.draw_glyph(pos_glyph, glyph, s.foreground, s.background); + pos.x += advance.x; } - const Point pos_glyph { - static_cast(rect.pos.x + pos.x), - display.scroll_area_y(pos.y) - }; - display.draw_glyph(pos_glyph, glyph, s.foreground, s.background); - pos.x += advance.x; } } diff --git a/firmware/application/ui_debug.cpp b/firmware/application/ui_debug.cpp index 11cdad55..7e26d571 100644 --- a/firmware/application/ui_debug.cpp +++ b/firmware/application/ui_debug.cpp @@ -24,16 +24,16 @@ #include "ch.h" #include "ff.h" -#include "led.hpp" #include "hackrf_gpio.hpp" #include "portapack.hpp" #include "portapack_shared_memory.hpp" #include "radio.hpp" +#include "string_format.hpp" namespace ui { - -FRESULT fr; /* FatFs function common result code */ + +/* DebugMemoryView *******************************************************/ DebugMemoryView::DebugMemoryView(NavigationView& nav) { add_children({ { @@ -62,156 +62,186 @@ void DebugMemoryView::focus() { button_done.focus(); } -void DebugRFFC5072RegistersWidget::update() { +/* TemperatureWidget *****************************************************/ + +void TemperatureWidget::paint(Painter& painter) { + const auto logger = portapack::temperature_logger; + + const auto rect = screen_rect(); + const Color color_background { 0, 0, 64 }; + const Color color_foreground = Color::green(); + const Color color_reticle { 128, 128, 128 }; + + const auto graph_width = static_cast(logger.capacity()) * bar_width; + const Rect graph_rect { + rect.left() + (rect.width() - graph_width) / 2, rect.top() + 8, + graph_width, rect.height() + }; + const Rect frame_rect { + graph_rect.left() - 1, graph_rect.top() - 1, + graph_rect.width() + 2, graph_rect.height() + 2 + }; + painter.draw_rectangle(frame_rect, color_reticle); + painter.fill_rectangle(graph_rect, color_background); + + const auto history = logger.history(); + for(size_t i=0; i&& reader +) : Widget { }, + config(std::move(config)), + reader(std::move(reader)) +{ +} + +void RegistersWidget::update() { set_dirty(); } -void DebugRFFC5072RegistersWidget::paint(Painter& painter) { - draw_legend(painter); +void RegistersWidget::paint(Painter& painter) { + const Coord left = (size().w - config.row_width()) / 2; - const auto registers = radio::first_if.registers(); - draw_values(painter, registers); + draw_legend(left, painter); + draw_values(left, painter); } -void DebugRFFC5072RegistersWidget::draw_legend(Painter& painter) { - for(size_t i=0; i((i / registers_per_row) * row_height) + left, (i / config.registers_per_row) * row_height }; - const auto text = to_string_hex(i, legend_length); + const auto text = to_string_hex(i, config.legend_length); painter.draw_string( - screen_pos() + offset, - style(), + pos + offset, + style().invert(), text ); } } -void DebugRFFC5072RegistersWidget::draw_values( - Painter& painter, - const rffc507x::RegisterMap registers +void RegistersWidget::draw_values( + const Coord left, + Painter& painter ) { - for(size_t i=0; i(legend_width + 8 + (i % registers_per_row) * (value_width + 8)), - static_cast((i / registers_per_row) * row_height) + left + config.legend_width() + 8 + (i % config.registers_per_row) * (config.value_width() + 8), + (i / config.registers_per_row) * row_height }; - const uint16_t value = registers.w[i]; + const auto value = reader(i); - const auto text = to_string_hex(value, value_length); + const auto text = to_string_hex(value, config.value_length); painter.draw_string( - screen_pos() + offset, + pos + offset, style(), text ); } } -DebugRFFC5072View::DebugRFFC5072View(NavigationView& nav) { +/* RegistersView *********************************************************/ + +RegistersView::RegistersView( + NavigationView& nav, + const std::string& title, + RegistersWidgetConfig&& config, + std::function&& reader +) : registers_widget { std::move(config), std::move(reader) } +{ add_children({ { &text_title, - &widget_registers, + ®isters_widget, &button_update, &button_done, } }); button_update.on_select = [this](Button&){ - this->widget_registers.update(); + this->registers_widget.update(); }; button_done.on_select = [&nav](Button&){ nav.pop(); }; + + registers_widget.set_parent_rect({ 0, 48, 240, 192 }); + + text_title.set_parent_rect({ + (240 - static_cast(title.size()) * 8) / 2, 16, + static_cast(title.size()) * 8, 16 + }); + text_title.set(title); } -void DebugRFFC5072View::focus() { - button_done.focus(); -} - -void DebugSDView::paint(Painter& painter) { - const Point offset = { - static_cast(32), - static_cast(32) - }; - - const auto text = to_string_hex(fr, 2); - painter.draw_string( - screen_pos() + offset, - style(), - text - ); -} - -DebugSDView::DebugSDView(NavigationView& nav) { - add_children({ { - &text_title, - &text_modules, - &button_makefile, - &button_done - } }); - - FIL fdst; - char buffer[256]; - uint8_t mods_version, mods_count; - UINT bw; - - const auto open_result = f_open(&fdst, "ppmods.bin", FA_OPEN_EXISTING | FA_READ); - if (open_result == FR_OK) { - f_read(&fdst, &mods_version, 1, &bw); - if (mods_version == 1) { - f_read(&fdst, &mods_count, 1, &bw); - f_read(&fdst, buffer, 8, &bw); - f_read(&fdst, buffer, 16, &bw); - buffer[16] = 0; - text_modules.set(buffer); - } else { - text_modules.set("Bad version"); - } - } - - button_makefile.on_select = [this](Button&){ - FATFS fs; /* Work area (file system object) for logical drives */ - FIL fdst; /* File objects */ - int16_t buffer[512]; /* File copy buffer */ - UINT bw; /* File read/write count */ - - sdcConnect(&SDCD1); - - fr = f_mount(&fs, "", 1); - - fr = f_open(&fdst, "TST.SND", FA_OPEN_EXISTING | FA_READ); - - //if (!fr) led_rx.on(); - - /*fr = f_read(&fdst, buffer, 512*2, &bw); - - Coord oy,ny; - - oy = 128; - - for (int c=0;c<512;c++) { - ny = 128+32-(buffer[c]>>10); - portapack::display.draw_line({static_cast(c/3),oy},{static_cast((c+1)/3),ny},{255,127,0}); - oy = ny; - }*/ - - /* - //if (fr) return; - - fr = f_write(&fdst, buffer, br, &bw); - //if (fr || bw < br) return;*/ - - //set_dirty(); - - f_close(&fdst); - - f_mount(NULL, "", 0); - - }; - - button_done.on_select = [&nav](Button&){ nav.pop(); }; -} - -void DebugSDView::focus() { +void RegistersView::focus() { button_done.focus(); } @@ -267,15 +297,30 @@ void DebugLCRView::focus() { button_done.focus(); } +/* DebugMenuView *********************************************************/ + DebugMenuView::DebugMenuView(NavigationView& nav) { - add_items<7>({ { - { "Memory", ui::Color::white(), [&nav](){ nav.push(new DebugMemoryView { nav }); } }, - { "Radio State", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } }, - { "SD Card", ui::Color::white(), [&nav](){ nav.push(new DebugSDView { nav }); } }, - { "RFFC5072", ui::Color::white(), [&nav](){ nav.push(new DebugRFFC5072View { nav }); } }, - { "MAX2837", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } }, - { "Si5351C", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } }, - { "WM8731", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } }, + add_items<8>({ { + { "Memory", [&nav](){ nav.push(); } }, + { "Radio State", [&nav](){ nav.push(); } }, + { "SD Card", [&nav](){ nav.push(); } }, + { "RFFC5072", [&nav](){ nav.push( + "RFFC5072", RegistersWidgetConfig { 31, 2, 4, 4 }, + [](const size_t register_number) { return radio::first_if.read(register_number); } + ); } }, + { "MAX2837", [&nav](){ nav.push( + "MAX2837", RegistersWidgetConfig { 32, 2, 3, 4 }, + [](const size_t register_number) { return radio::second_if.read(register_number); } + ); } }, + { "Si5351C", [&nav](){ nav.push( + "Si5351C", RegistersWidgetConfig { 96, 2, 2, 8 }, + [](const size_t register_number) { return portapack::clock_generator.read_register(register_number); } + ); } }, + { "WM8731", [&nav](){ nav.push( + "WM8731", RegistersWidgetConfig { wolfson::wm8731::reg_count, 1, 3, 4 }, + [](const size_t register_number) { return portapack::audio_codec.read(register_number); } + ); } }, + { "Temperature", [&nav](){ nav.push(); } }, } }); on_left = [&nav](){ nav.pop(); }; } diff --git a/firmware/application/ui_debug.hpp b/firmware/application/ui_debug.hpp index 41aad942..ecae1bdb 100644 --- a/firmware/application/ui_debug.hpp +++ b/firmware/application/ui_debug.hpp @@ -29,6 +29,11 @@ #include "ui_navigation.hpp" #include "rffc507x.hpp" +#include "max2837.hpp" +#include "portapack.hpp" + +#include +#include namespace ui { @@ -79,67 +84,128 @@ private: }; }; -class DebugRFFC5072RegistersWidget : public Widget { +class TemperatureWidget : public Widget { public: - constexpr DebugRFFC5072RegistersWidget( + explicit constexpr TemperatureWidget( Rect parent_rect ) : Widget { parent_rect } { } + void paint(Painter& painter) override; + +private: + using sample_t = uint32_t; + using temperature_t = int32_t; + + temperature_t temperature(const sample_t sensor_value) const; + Coord screen_y(const temperature_t temperature, const Rect& screen_rect) const; + + std::string temperature_str(const temperature_t temperature) const; + + static constexpr temperature_t display_temp_min = 0; + static constexpr temperature_t display_temp_scale = 3; + static constexpr int bar_width = 1; + static constexpr int temp_len = 3; +}; + +class TemperatureView : public View { +public: + explicit TemperatureView(NavigationView& nav); + + void focus() override; + +private: + Text text_title { + { 76, 16, 240, 16 }, + "Temperature", + }; + + TemperatureWidget temperature_widget { + { 0, 40, 240, 180 }, + }; + + Button button_done { + { 72, 264, 96, 24 }, + "Done" + }; +}; + +struct RegistersWidgetConfig { + int registers_count; + int legend_length; + int value_length; + int registers_per_row; + + constexpr int legend_width() const { + return legend_length * 8; + } + + constexpr int value_width() const { + return value_length * 8; + } + + constexpr int registers_row_length() const { + return (registers_per_row * (value_length + 1)) - 1; + } + + constexpr int registers_row_width() const { + return registers_row_length() * 8; + } + + constexpr int row_width() const { + return legend_width() + 8 + registers_row_width(); + } + + constexpr int rows() const { + return registers_count / registers_per_row; + } +}; + +class RegistersWidget : public Widget { +public: + RegistersWidget( + RegistersWidgetConfig&& config, + std::function&& reader + ); + void update(); void paint(Painter& painter) override; private: - static constexpr size_t registers_count { 31 }; + const RegistersWidgetConfig config; + const std::function reader; - static constexpr size_t legend_length { 2 }; - static constexpr Dim legend_width { legend_length * 8 }; + static constexpr int row_height = 16; - static constexpr size_t value_length { 4 }; - static constexpr Dim value_width { value_length * 8 }; - - static constexpr size_t registers_per_row { 4 }; - static constexpr size_t registers_row_length { - (registers_per_row * (value_length + 1)) - 1 - }; - static constexpr Dim registers_row_width { - registers_row_length * 8 - }; - - static constexpr size_t rows { - registers_count / registers_per_row - }; - static constexpr Dim row_height { 16 }; - - void draw_legend(Painter& painter); - void draw_values(Painter& painter, const rffc507x::RegisterMap registers); + void draw_legend(const Coord left, Painter& painter); + void draw_values(const Coord left, Painter& painter); }; -class DebugRFFC5072View : public View { +class RegistersView : public View { public: - DebugRFFC5072View(NavigationView& nav); + RegistersView( + NavigationView& nav, + const std::string& title, + RegistersWidgetConfig&& config, + std::function&& reader + ); - void focus() override; + void focus(); private: - Text text_title { - { 88, 16, 40, 16 }, - "RFFC5072", - }; + Text text_title; - DebugRFFC5072RegistersWidget widget_registers { - { 32, 48, 176, 128 } - }; + RegistersWidget registers_widget; Button button_update { - { 16, 192, 96, 24 }, + { 16, 256, 96, 24 }, "Update" }; Button button_done { - { 128, 192, 96, 24 }, + { 128, 256, 96, 24 }, "Done" }; }; diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 4d4ef56e..711931ea 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -22,6 +22,7 @@ #include "ui_navigation.hpp" #include "portapack.hpp" +#include "event_m0.hpp" #include "receiver_model.hpp" #include "transmitter_model.hpp" #include "portapack_persistent_memory.hpp" @@ -42,7 +43,10 @@ #include "ui_sigfrx.hpp" #include "ui_numbers.hpp" -#include "portapack.hpp" +#include "ais_app.hpp" +#include "ert_app.hpp" +#include "tpms_app.hpp" + #include "m4_startup.hpp" #include "spi_image.hpp" @@ -56,31 +60,78 @@ namespace ui { SystemStatusView::SystemStatusView() { add_children({ { - &portapack, + &button_back, + &title, + &button_sleep, + &sd_card_status_view, } }); + sd_card_status_view.set_parent_rect({ 28 * 8, 0 * 16, 2 * 8, 1 * 16 }); + + button_back.on_select = [this](Button&){ + if( this->on_back ) { + this->on_back(); + } + }; + + button_sleep.on_select = [this](Button&) { + DisplaySleepMessage message; + EventDispatcher::message_map().send(&message); + }; +} + +void SystemStatusView::set_back_visible(bool new_value) { + button_back.hidden(!new_value); +} + +void SystemStatusView::set_title(const std::string new_value) { + if( new_value.empty() ) { + title.set(default_title); + } else { + title.set(new_value); + } } /* Navigation ************************************************************/ -NavigationView::NavigationView() -{ +bool NavigationView::is_top() const { + return view_stack.size() == 1; } -void NavigationView::push(View* new_view) { - // TODO: Trap nullptr? - // TODO: Trap push of object already on stack? - view_stack.push_back(new_view); - set_view(new_view); +View* NavigationView::push_view(std::unique_ptr new_view) { + free_view(); + + const auto p = new_view.get(); + view_stack.emplace_back(std::move(new_view)); + + update_view(); + + return p; } void NavigationView::pop() { // Can't pop last item from stack. if( view_stack.size() > 1 ) { - const auto old_view = view_stack.back(); + free_view(); + view_stack.pop_back(); - const auto new_view = view_stack.back(); - set_view(new_view); - delete old_view; + + update_view(); + } +} + +void NavigationView::free_view() { + remove_child(view()); +} + +void NavigationView::update_view() { + const auto new_view = view_stack.back().get(); + add_child(new_view); + new_view->set_parent_rect({ {0, 0}, size() }); + focus(); + set_dirty(); + + if( on_view_changed ) { + on_view_changed(*new_view); } } @@ -88,51 +139,53 @@ Widget* NavigationView::view() const { return children_.empty() ? nullptr : children_[0]; } -void NavigationView::set_view(Widget* const new_view) { - const auto old_view = view(); - if( old_view ) { - remove_child(old_view); - } - - // TODO: Allow new_view == nullptr?! - if( new_view ) { - add_child(new_view); - new_view->set_parent_rect({ {0, 0}, size() }); - focus(); - } - - set_dirty(); -} - void NavigationView::focus() { if( view() ) { view()->focus(); } } +/* TransceiversMenuView **************************************************/ + +TranspondersMenuView::TranspondersMenuView(NavigationView& nav) { + add_items<3>({ { + { "AIS: Boats", [&nav](){ nav.push(); } }, + { "ERT: Utility Meters", [&nav](){ nav.push(); } }, + { "TPMS: Cars", [&nav](){ nav.push(); } }, + } }); +} + +/* ReceiverMenuView ******************************************************/ + +ReceiverMenuView::ReceiverMenuView(NavigationView& nav) { + add_items<2>({ { + { "Audio", [&nav](){ nav.push(); } }, + { "Transponders", [&nav](){ nav.push(); } }, + } }); +} + /* SystemMenuView ********************************************************/ SystemMenuView::SystemMenuView(NavigationView& nav) { add_items<10>({ { - { "Play dead", ui::Color::red(), [&nav](){ nav.push(new PlayDeadView { nav, false }); } }, - { "Receiver", ui::Color::cyan(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new ReceiverView { nav, receiver_model }}); } }, + { "Play dead", ui::Color::red(), [&nav](){ nav.push(new PlayDeadView { nav, false }); } }, + { "Receiver", ui::Color::cyan(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new ReceiverMenuView { nav, receiver_model }}); } }, //{ "Nordic/BTLE RX", ui::Color::cyan(), [&nav](){ nav.push(new NotImplementedView { nav }); } }, - { "Jammer", ui::Color::white(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new JammerView { nav, transmitter_model }}); } }, + { "Jammer", ui::Color::white(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new JammerView { nav, transmitter_model }}); } }, //{ "Audio file TX", ui::Color::white(), [&nav](){ nav.push(new NotImplementedView { nav }); } }, //{ "Encoder TX", ui::Color::green(), [&nav](){ nav.push(new NotImplementedView { nav }); } }, //{ "Whistle", ui::Color::purple(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new WhistleView { nav, transmitter_model }}); } }, - //{ "SIGFOX RX", ui::Color::orange(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new SIGFRXView { nav, receiver_model }}); } }, - { "RDS TX", ui::Color::yellow(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new RDSView { nav, transmitter_model }}); } }, - { "Xylos TX", ui::Color::orange(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new XylosView { nav, transmitter_model }}); } }, + //{ "SIGFOX RX", ui::Color::orange(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new SIGFRXView { nav, receiver_model }}); } }, + { "RDS TX", ui::Color::yellow(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new RDSView { nav, transmitter_model }}); } }, + { "Xylos TX", ui::Color::orange(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new XylosView { nav, transmitter_model }}); } }, //{ "Xylos RX", ui::Color::green(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new XylosRXView { nav, receiver_model }}); } }, - //{ "AFSK RX", ui::Color::cyan(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new AFSKRXView { nav, receiver_model }}); } }, - { "TEDI/LCR TX", ui::Color::yellow(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new LCRView { nav, transmitter_model }}); } }, - //{ "Numbers station", ui::Color::purple(),[&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new NumbersStationView { nav, transmitter_model }}); } }, - - { "Setup", ui::Color::white(), [&nav](){ nav.push(new SetupMenuView { nav }); } }, - { "About", ui::Color::white(), [&nav](){ nav.push(new AboutView { nav, transmitter_model }); } }, - { "Debug", ui::Color::white(), [&nav](){ nav.push(new DebugMenuView { nav }); } }, - { "HackRF", ui::Color::white(), [&nav](){ nav.push(new HackRFFirmwareView { nav }); } }, + //{ "AFSK RX", ui::Color::cyan(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband, new AFSKRXView { nav, receiver_model }}); } }, + { "TEDI/LCR TX", ui::Color::yellow(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new LCRView { nav, transmitter_model }}); } }, + //{ "Numbers station", ui::Color::purple(), [&nav](){ nav.push(new LoadModuleView { nav, md5_baseband_tx, new NumbersStationView { nav, transmitter_model }}); } }, + { "Setup", ui::Color::white(), [&nav](){ nav.push(new SetupMenuView { nav }); } }, + { "About", ui::Color::white(), [&nav](){ nav.push(new AboutView { nav, transmitter_model }); } }, + { "Debug", ui::Color::white(), [&nav](){ nav.push(new DebugMenuView { nav }); } }, + { "HackRF", ui::Color::white(), [&nav](){ nav.push(new HackRFFirmwareView { nav }); } }, } }); } @@ -159,12 +212,19 @@ SystemView::SystemView( { 0, 0 }, { parent_rect.width(), status_view_height } }); + status_view.on_back = [this]() { + this->navigation_view.pop(); + }; add_child(&navigation_view); navigation_view.set_parent_rect({ { 0, status_view_height }, { parent_rect.width(), static_cast(parent_rect.height() - status_view_height) } }); + navigation_view.on_view_changed = [this](const View& new_view) { + this->status_view.set_back_visible(!this->navigation_view.is_top()); + this->status_view.set_title(new_view.title()); + }; // Initial view. // TODO: Restore from non-volatile memory? @@ -175,73 +235,15 @@ SystemView::SystemView( // navigation_view.push(new BMPView { navigation_view }); if (portapack::persistent_memory::ui_config() & 1) - navigation_view.push(new BMPView { navigation_view }); + navigation_view.push(); else - navigation_view.push(new SystemMenuView { navigation_view }); + navigation_view.push(); } Context& SystemView::context() const { return context_; } -/* ***********************************************************************/ - -void BMPView::focus() { - button_done.focus(); -} - -BMPView::BMPView(NavigationView& nav) { - add_children({ { - &text_info, - &button_done - } }); - - button_done.on_select = [this,&nav](Button&){ - nav.pop(); - nav.push(new SystemMenuView { nav }); - }; -} - -void BMPView::paint(Painter& painter) { - (void)painter; - portapack::display.drawBMP({(240-185)/2, 0}, splash_bmp); -} - -/* PlayDeadView **********************************************************/ - -void PlayDeadView::focus() { - button_done.focus(); -} - -PlayDeadView::PlayDeadView(NavigationView& nav, bool booting) { - _booting = booting; - persistent_memory::set_playing_dead(0x59); - - add_children({ { - &text_playdead1, - &text_playdead2, - &button_done, - } }); - - button_done.on_dir = [this,&nav](Button&, KeyEvent key){ - sequence = (sequence<<3) | static_cast::type>(key); - }; - - button_done.on_select = [this,&nav](Button&){ - if (sequence == persistent_memory::playdead_sequence()) { - persistent_memory::set_playing_dead(0); - if (_booting) { - nav.pop(); - nav.push(new SystemMenuView { nav }); - } else { - nav.pop(); - } - } else { - sequence = 0; - } - }; -} - /* HackRFFirmwareView ****************************************************/ HackRFFirmwareView::HackRFFirmwareView(NavigationView& nav) { diff --git a/firmware/application/ui_navigation.hpp b/firmware/application/ui_navigation.hpp index c5f98855..4e00b4e3 100644 --- a/firmware/application/ui_navigation.hpp +++ b/firmware/application/ui_navigation.hpp @@ -30,44 +30,76 @@ #include "ui_rssi.hpp" #include "ui_channel.hpp" #include "ui_audio.hpp" +#include "ui_sd_card_status_view.hpp" #include +#include namespace ui { class SystemStatusView : public View { public: + std::function on_back; + SystemStatusView(); + void set_back_visible(bool new_value); + void set_title(const std::string new_value); + private: - Text portapack { - { 0, 0, 15 * 8, 1 * 16 }, - "PortaPack/HAVOC" + static constexpr auto default_title = "PortaPack/HAVOC"; + + Button button_back { + { 0 * 8, 0 * 16, 3 * 8, 16 }, + " < ", }; + + Text title { + { 3 * 8, 0, 16 * 8, 1 * 16 }, + default_title, + }; + + Button button_sleep { + { 25 * 8, 0, 2 * 8, 1 * 16 }, + "ZZ", + }; + + SDCardStatusView sd_card_status_view; }; class NavigationView : public View { public: - NavigationView(); + std::function on_view_changed; + + NavigationView() { } NavigationView(const NavigationView&) = delete; NavigationView(NavigationView&&) = delete; - void push(View* new_view); + bool is_top() const; + + template + T* push(Args&&... args) { + return reinterpret_cast(push_view(std::unique_ptr(new T(*this, std::forward(args)...)))); + } + void pop(); void focus() override; private: - std::vector view_stack; + std::vector> view_stack; Widget* view() const; - void set_view(Widget* const new_view); + + void free_view(); + void update_view(); + View* push_view(std::unique_ptr new_view); }; -class SystemMenuView : public MenuView { +class TranspondersMenuView : public MenuView { public: - SystemMenuView(NavigationView& nav); + TranspondersMenuView(NavigationView& nav); }; class BMPView : public View { @@ -88,6 +120,15 @@ private: }; }; +class ReceiverMenuView : public MenuView { +public: + ReceiverMenuView(NavigationView& nav); +}; + +class SystemMenuView : public MenuView { +public: + SystemMenuView(NavigationView& nav); +}; class SystemView : public View { public: @@ -104,29 +145,6 @@ private: Context& context_; }; -class PlayDeadView : public View { -public: - PlayDeadView(NavigationView& nav, bool booting); - void focus() override; - -private: - bool _booting; - uint32_t sequence = 0; - Text text_playdead1 { - { 6 * 8, 7 * 16, 14 * 8, 16 }, - "Firmware error" - }; - Text text_playdead2 { - { 6 * 8, 9 * 16, 16 * 8, 16 }, - "0x1400_0000 : 2C" - }; - - Button button_done { - { 240, 0, 1, 1 }, - "" - }; -}; - class HackRFFirmwareView : public View { public: HackRFFirmwareView(NavigationView& nav); diff --git a/firmware/application/ui_receiver.cpp b/firmware/application/ui_receiver.cpp index 046c43b1..4263a84c 100644 --- a/firmware/application/ui_receiver.cpp +++ b/firmware/application/ui_receiver.cpp @@ -21,48 +21,19 @@ #include "ui_receiver.hpp" -#include "ui_spectrum.hpp" -#include "ui_console.hpp" - -#include "ff.h" - #include "portapack.hpp" using namespace portapack; -#include "ais_baseband.hpp" -#include "m4_startup.hpp" +#include "string_format.hpp" + +#include "analog_audio_app.hpp" +#include "ais_app.hpp" +#include "tpms_app.hpp" +#include "ert_app.hpp" +#include "spectrum_analysis_app.hpp" namespace ui { -/* BasebandBandwidthField ************************************************/ - -BasebandBandwidthField::BasebandBandwidthField( - Point parent_pos -) : OptionsField { - parent_pos, - 4, - { - { " 1M8", 1750000 }, - { " 2M5", 2500000 }, - { " 3M5", 3500000 }, - { " 5M ", 5000000 }, - { " 5M5", 5500000 }, - { " 6M ", 6000000 }, - { " 7M ", 7000000 }, - { " 8M ", 8000000 }, - { " 9M ", 9000000 }, - { "10M ", 10000000 }, - { "12M ", 12000000 }, - { "14M ", 14000000 }, - { "15M ", 15000000 }, - { "20M ", 20000000 }, - { "24M ", 24000000 }, - { "28M ", 28000000 }, - } - } -{ -} - /* FrequencyField ********************************************************/ FrequencyField::FrequencyField( @@ -127,25 +98,7 @@ bool FrequencyField::on_key(const ui::KeyEvent event) { } return false; } -/* -bool FrequencyField::on_key(const ui::KeyEvent event) override { - if( event == ui::KeyEvent::Select ) { - // NOTE: For testing sampling rate / decimation combinations - turbo = !turbo; - if( turbo ) { - clock_manager.set_sampling_frequency(18432000); - radio::set_baseband_decimation_by(6); - } else { - clock_manager.set_sampling_frequency(12288000); - radio::set_baseband_decimation_by(4); - } - return true; - } - - return false; -} -*/ bool FrequencyField::on_encoder(const EncoderEvent delta) { set_value(value() + (delta * step)); return true; @@ -165,13 +118,7 @@ void FrequencyField::on_focus() { } rf::Frequency FrequencyField::clamp_value(rf::Frequency value) { - if( value > range.max ) { - value = range.max; - } - if( value < range.min ) { - value = range.min; - } - return value; + return range.clip(value); } /* FrequencyKeypadView ***************************************************/ @@ -188,7 +135,7 @@ FrequencyKeypadView::FrequencyKeypadView( const char* const key_caps = "123456789<0."; - size_t n = 0; + int n = 0; for(auto& button : buttons) { add_child(&button); const std::string label { @@ -196,8 +143,8 @@ FrequencyKeypadView::FrequencyKeypadView( }; button.on_select = button_fn; button.set_parent_rect({ - static_cast((n % 3) * button_w), - static_cast((n / 3) * button_h + button_h), + (n % 3) * button_w, + (n / 3) * button_h + button_h, button_w, button_h }); button.set_text(label); @@ -301,7 +248,6 @@ FrequencyOptionsView::FrequencyOptionsView( add_children({ { &text_step, &options_step, - &text_correction, &field_ppm, &text_ppm, } }); @@ -339,19 +285,11 @@ RadioGainOptionsView::RadioGainOptionsView( add_children({ { &label_rf_amp, &field_rf_amp, - //&label_agc, - //&field_agc } }); field_rf_amp.on_change = [this](int32_t v) { this->on_rf_amp_changed(v); }; - /* - field_agc.set_value(receiver_model.agc()); - field_agc.on_change = [this](int32_t v) { - this->on_agc_changed(v); - }; - */ } void RadioGainOptionsView::set_rf_amp(int32_t v_db) { @@ -363,19 +301,14 @@ void RadioGainOptionsView::on_rf_amp_changed(bool enable) { on_change_rf_amp(enable); } } -/* -void RadioGainOptionsView::on_agc_changed(bool v) { - receiver_model.set_agc(v); -} -*/ /* LNAGainField **********************************************************/ LNAGainField::LNAGainField( Point parent_pos ) : NumberField { - { parent_pos }, 2, - { max2837::lna::gain_db_min, max2837::lna::gain_db_max }, + parent_pos, 2, + { max2837::lna::gain_db_range.minimum, max2837::lna::gain_db_range.maximum }, max2837::lna::gain_db_step, ' ', } @@ -392,30 +325,21 @@ void LNAGainField::on_focus() { /* ReceiverView **********************************************************/ ReceiverView::ReceiverView( - NavigationView& nav, - ReceiverModel& receiver_model -) : receiver_model(receiver_model) -{ + NavigationView& nav +) { add_children({ { &rssi, &channel, &audio, - &button_done, &field_frequency, &field_lna, - //&options_baseband_bandwidth, &field_vga, &options_modulation, - //&options_baseband_oversampling, &field_volume, &view_frequency_options, &view_rf_gain_options, } }); - button_done.on_select = [&nav](Button&){ - nav.pop(); - }; - field_frequency.set_value(receiver_model.tuning_frequency()); field_frequency.set_step(receiver_model.frequency_step()); field_frequency.on_change = [this](rf::Frequency f) { @@ -423,12 +347,11 @@ ReceiverView::ReceiverView( }; field_frequency.on_edit = [this, &nav]() { // TODO: Provide separate modal method/scheme? - auto new_view = new FrequencyKeypadView { nav, this->receiver_model.tuning_frequency() }; + 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); }; - nav.push(new_view); }; field_frequency.on_show_options = [this]() { this->on_show_options_frequency(); @@ -441,13 +364,7 @@ ReceiverView::ReceiverView( field_lna.on_show_options = [this]() { this->on_show_options_rf_gain(); }; - /* - options_baseband_bandwidth.set_by_value(receiver_model.baseband_bandwidth()); - options_baseband_bandwidth.on_change = [this](size_t n, OptionsField::value_t v) { - (void)n; - this->on_baseband_bandwidth_changed(v); - }; - */ + field_vga.set_value(receiver_model.vga()); field_vga.on_change = [this](int32_t v_db) { this->on_vga_changed(v_db); @@ -456,15 +373,9 @@ ReceiverView::ReceiverView( options_modulation.set_by_value(receiver_model.modulation()); options_modulation.on_change = [this](size_t n, OptionsField::value_t v) { (void)n; - this->on_modulation_changed((mode_type)v); + this->on_modulation_changed(static_cast(v)); }; -/* - options_baseband_oversampling.set_by_value(receiver_model.baseband_oversampling()); - options_baseband_oversampling.on_change = [this](size_t n, OptionsField::value_t v) { - (void)n; - this->on_baseband_oversampling_changed(v); - }; -*/ + field_volume.set_value((receiver_model.headphone_volume() - wolfson::wm8731::headphone_gain_range.max).decibel() + 99); field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); @@ -499,110 +410,20 @@ ReceiverView::~ReceiverView() { } void ReceiverView::on_show() { - auto& message_map = context().message_map(); - message_map.register_handler(Message::ID::AISPacket, - [this](Message* const p) { - const auto message = static_cast(p); - this->on_packet_ais(*message); - } - ); - message_map.register_handler(Message::ID::TPMSPacket, - [this](Message* const p) { - const auto message = static_cast(p); - this->on_packet_tpms(*message); - } - ); - message_map.register_handler(Message::ID::SDCardStatus, - [this](Message* const p) { - const auto message = static_cast(p); - this->on_sd_card_mounted(message->is_mounted); - } - ); + View::on_show(); + + // TODO: Separate concepts of baseband "modulation" and receiver "mode". + on_modulation_changed(static_cast(receiver_model.modulation())); } void ReceiverView::on_hide() { - auto& message_map = context().message_map(); - message_map.unregister_handler(Message::ID::SDCardStatus); - message_map.unregister_handler(Message::ID::TPMSPacket); - message_map.unregister_handler(Message::ID::AISPacket); -} + on_modulation_changed(static_cast(-1)); -void ReceiverView::on_packet_ais(const AISPacketMessage& message) { - const auto result = baseband::ais::packet_decode(message.packet.payload, message.packet.bits_received); - - auto console = reinterpret_cast(widget_content.get()); - if( result.first == "OK" ) { - console->writeln(result.second); - } -} - -static FIL fil_tpms; - -void ReceiverView::on_packet_tpms(const TPMSPacketMessage& message) { - auto payload = message.packet.payload; - auto payload_length = message.packet.bits_received; - - std::string hex_data; - std::string hex_error; - uint8_t byte_data = 0; - uint8_t byte_error = 0; - for(size_t i=0; i> 1) & 7) == 7 ) { - hex_data += to_string_hex(byte_data, 2); - hex_error += to_string_hex(byte_error, 2); - } - } - - auto console = reinterpret_cast(widget_content.get()); - console->writeln(hex_data.substr(0, 240 / 8)); - - if( !f_error(&fil_tpms) ) { - rtc::RTC datetime; - rtcGetTime(&RTCD1, &datetime); - std::string timestamp = - to_string_dec_uint(datetime.year(), 4) + - to_string_dec_uint(datetime.month(), 2, '0') + - to_string_dec_uint(datetime.day(), 2, '0') + - to_string_dec_uint(datetime.hour(), 2, '0') + - to_string_dec_uint(datetime.minute(), 2, '0') + - to_string_dec_uint(datetime.second(), 2, '0'); - - const auto tuning_frequency = receiver_model.tuning_frequency(); - // TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue! - const auto tuning_frequency_str = to_string_dec_uint(tuning_frequency, 10); - - std::string log = timestamp + " " + tuning_frequency_str + " FSK 38.4 19.2 " + hex_data + "/" + hex_error + "\r\n"; - f_puts(log.c_str(), &fil_tpms); - f_sync(&fil_tpms); - } -} - -void ReceiverView::on_sd_card_mounted(const bool is_mounted) { - if( is_mounted ) { - const auto open_result = f_open(&fil_tpms, "tpms.txt", FA_WRITE | FA_OPEN_ALWAYS); - if( open_result == FR_OK ) { - const auto fil_size = f_size(&fil_tpms); - const auto seek_result = f_lseek(&fil_tpms, fil_size); - if( seek_result != FR_OK ) { - f_close(&fil_tpms); - } - } else { - // TODO: Error, indicate somehow. - } - } + View::on_hide(); } void ReceiverView::focus() { - button_done.focus(); + field_frequency.focus(); } void ReceiverView::on_tuning_frequency_changed(rf::Frequency f) { @@ -625,56 +446,27 @@ void ReceiverView::on_vga_changed(int32_t v_db) { receiver_model.set_vga(v_db); } -void ReceiverView::on_modulation_changed(mode_type modulation) { - /* TODO: This is TERRIBLE!!! */ - switch(modulation) { - case 3: - case 5: - receiver_model.set_baseband_configuration({ - .mode = modulation, - .sampling_rate = 2457600, - .decimation_factor = 4, - }); - receiver_model.set_baseband_bandwidth(1750000); - break; - - case 4: - receiver_model.set_baseband_configuration({ - .mode = modulation, - .sampling_rate = 20000000, - .decimation_factor = 1, - }); - receiver_model.set_baseband_bandwidth(12000000); - break; - - default: - receiver_model.set_baseband_configuration({ - .mode = modulation, - .sampling_rate = 3072000, - .decimation_factor = 4, - }); - receiver_model.set_baseband_bandwidth(1750000); - break; - } - +void ReceiverView::on_modulation_changed(ReceiverModel::Mode mode) { remove_child(widget_content.get()); widget_content.reset(); - switch(modulation) { - case 3: - case 5: - widget_content = std::make_unique(); - add_child(widget_content.get()); + switch(mode) { + case ReceiverModel::Mode::AMAudio: + case ReceiverModel::Mode::NarrowbandFMAudio: + case ReceiverModel::Mode::WidebandFMAudio: + widget_content = std::make_unique(mode); + break; + + case ReceiverModel::Mode::SpectrumAnalysis: + widget_content = std::make_unique(); break; default: - widget_content = std::make_unique(); - add_child(widget_content.get()); break; } if( widget_content ) { - const ui::Dim header_height = 3 * 16; + add_child(widget_content.get()); const ui::Rect rect { 0, header_height, parent_rect.width(), static_cast(parent_rect.height() - header_height) }; widget_content->set_parent_rect(rect); } @@ -710,8 +502,4 @@ void ReceiverView::on_headphone_volume_changed(int32_t v) { receiver_model.set_headphone_volume(new_volume); } -// void ReceiverView::on_baseband_oversampling_changed(int32_t v) { -// receiver_model.set_baseband_oversampling(v); -// } - } /* namespace ui */ diff --git a/firmware/application/ui_receiver.hpp b/firmware/application/ui_receiver.hpp index c5109e89..6ab20236 100644 --- a/firmware/application/ui_receiver.hpp +++ b/firmware/application/ui_receiver.hpp @@ -44,11 +44,6 @@ namespace ui { -class BasebandBandwidthField : public OptionsField { -public: - BasebandBandwidthField(Point parent_pos); -}; - class FrequencyField : public Widget { public: std::function on_change; @@ -77,8 +72,6 @@ private: rf::Frequency value_; rf::Frequency step { 25000 }; - //bool turbo { false }; - rf::Frequency clamp_value(rf::Frequency value); }; @@ -118,13 +111,6 @@ public: } void add_digit(const char c) { - /* - if( justify == Justify::Right ) { - push_right(c); - } else { - insert_right(c); - } - */ insert_right(c); } @@ -209,16 +195,15 @@ public: void set_value(const rf::Frequency new_value); private: - static constexpr size_t button_w = 240 / 3; - static constexpr size_t button_h = 48; + static constexpr int button_w = 240 / 3; + static constexpr int button_h = 48; - static constexpr size_t mhz_digits = 4; - static constexpr size_t submhz_digits = 4; + static constexpr int mhz_digits = 4; + static constexpr int submhz_digits = 4; - static constexpr size_t mhz_mod = pow(10, mhz_digits); - static constexpr size_t submhz_base = pow(10, 6 - submhz_digits); - //static constexpr size_t submhz_mod = pow(10, submhz_digits); - static constexpr size_t text_digits = mhz_digits + 1 + submhz_digits; + static constexpr int mhz_mod = pow(10, mhz_digits); + static constexpr int submhz_base = pow(10, 6 - submhz_digits); + static constexpr int text_digits = mhz_digits + 1 + submhz_digits; Text text_value { { 0, 0, text_digits * button_w, button_h } @@ -274,6 +259,10 @@ private: { { " 100", 100 }, { " 1k ", 1000 }, + { " 3k ", 3000 }, /* Approximate SSB bandwidth */ + { " 5k ", 5000 }, + { " 6k3", 6250 }, + { " 9k ", 9000 }, /* channel spacing for LF, MF in some regions */ { " 10k ", 10000 }, { " 12k5", 12500 }, { " 25k ", 25000 }, @@ -286,11 +275,6 @@ private: void on_step_changed(rf::Frequency v); void on_reference_ppm_correction_changed(int32_t v); - Text text_correction { - { 17 * 8, 0 * 16, 5 * 8, 16 }, - "Corr.", - }; - NumberField field_ppm { { 23 * 8, 0 * 16 }, 3, @@ -326,18 +310,6 @@ private: 1, ' ', }; - /* - Text label_agc { - { 6 * 8, 0 * 16, 3 * 8, 1 * 16 }, - "AGC" - }; - - NumberField field_agc { - { 10 * 8, 0 * 16}, - 1, - { 0, 1 } - }; - */ void on_rf_amp_changed(bool enable); }; @@ -359,87 +331,58 @@ constexpr Style style_options_group { class ReceiverView : public View { public: - ReceiverView(NavigationView& nav, ReceiverModel& receiver_model); + ReceiverView(NavigationView& nav); ~ReceiverView(); - void focus() override; - void on_show() override; void on_hide() override; + void focus() override; + private: - ReceiverModel& receiver_model; + static constexpr ui::Dim header_height = 2 * 16; RSSI rssi { - { 19 * 8, 0, 11 * 8, 4 }, + { 21 * 8, 0, 6 * 8, 4 }, }; Channel channel { - { 19 * 8, 5, 11 * 8, 4 }, + { 21 * 8, 5, 6 * 8, 4 }, }; Audio audio { - { 19 * 8, 10, 11 * 8, 4 }, - }; - - Button button_done { - { 0 * 8, 0 * 16, 3 * 8, 16 }, - " < ", + { 21 * 8, 10, 6 * 8, 4 }, }; FrequencyField field_frequency { - { 0 * 8, 1 * 16 }, + { 5 * 8, 0 * 16 }, }; LNAGainField field_lna { - { 13 * 8, 1 * 16 } + { 15 * 8, 0 * 16 } }; - /* - BasebandBandwidthField options_baseband_bandwidth { - { 15 * 8, 1 * 16 }, - }; - */ + NumberField field_vga { - { 16 * 8, 1 * 16}, + { 18 * 8, 0 * 16}, 2, - { max2837::vga::gain_db_min, max2837::vga::gain_db_max }, + { max2837::vga::gain_db_range.minimum, max2837::vga::gain_db_range.maximum }, max2837::vga::gain_db_step, ' ', }; OptionsField options_modulation { - { 19 * 8, 1 * 16 }, + { 0 * 8, 0 * 16 }, 4, { - { " AM ", RX_NBAM_AUDIO }, - { "NFM ", RX_NBFM_AUDIO }, - { "WFM ", RX_WBFM_AUDIO }, - { "AIS ", RX_AIS }, - { "TPMS", RX_TPMS }, - { "SPEC", RX_WBSPECTRUM }, + { " AM ", toUType(ReceiverModel::Mode::AMAudio) }, + { "NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio) }, + { "WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio) }, + { "SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis) }, } }; -/* - OptionsField options_baseband_oversampling { - { 24 * 8, 1 * 16 }, - 1, - { - { "4", 4 }, - { "6", 6 }, - { "8", 8 }, - } - }; -*/ - NumberField field_vregmode { - { 24 * 8, 1 * 16 }, - 1, - { 0, 1 }, - 1, - ' ', - }; NumberField field_volume { - { 28 * 8, 1 * 16 }, + { 28 * 8, 0 * 16 }, 2, { 0, 99 }, 1, @@ -447,12 +390,12 @@ private: }; FrequencyOptionsView view_frequency_options { - { 0 * 8, 2 * 16, 30 * 8, 1 * 16 }, + { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, &style_options_group }; RadioGainOptionsView view_rf_gain_options { - { 0 * 8, 2 * 16, 30 * 8, 1 * 16 }, + { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, &style_options_group }; @@ -463,18 +406,13 @@ private: void on_rf_amp_changed(bool v); void on_lna_changed(int32_t v_db); void on_vga_changed(int32_t v_db); - void on_modulation_changed(mode_type modulation); + void on_modulation_changed(ReceiverModel::Mode mode); void on_show_options_frequency(); void on_show_options_rf_gain(); void on_frequency_step_changed(rf::Frequency f); void on_reference_ppm_correction_changed(int32_t v); void on_headphone_volume_changed(int32_t v); -// void on_baseband_oversampling_changed(int32_t v); void on_edit_frequency(); - - void on_packet_ais(const AISPacketMessage& message); - void on_packet_tpms(const TPMSPacketMessage& message); - void on_sd_card_mounted(const bool is_mounted); }; } /* namespace ui */ diff --git a/firmware/application/ui_rssi.cpp b/firmware/application/ui_rssi.cpp index 7cbba2a1..a5646863 100644 --- a/firmware/application/ui_rssi.cpp +++ b/firmware/application/ui_rssi.cpp @@ -21,12 +21,16 @@ #include "ui_rssi.hpp" +#include "event_m0.hpp" + +#include "utility.hpp" + #include namespace ui { void RSSI::on_show() { - context().message_map().register_handler(Message::ID::RSSIStatistics, + EventDispatcher::message_map().register_handler(Message::ID::RSSIStatistics, [this](const Message* const p) { this->on_statistics_update(static_cast(p)->statistics); } @@ -34,27 +38,29 @@ void RSSI::on_show() { } void RSSI::on_hide() { - context().message_map().unregister_handler(Message::ID::RSSIStatistics); + EventDispatcher::message_map().unregister_handler(Message::ID::RSSIStatistics); } void RSSI::paint(Painter& painter) { const auto r = screen_rect(); - /* - constexpr int32_t rssi_min = 0.# * 256 / 3.3; - constexpr int32_t rssi_max = 2.5 * 256 / 3.3; - // (23 - 194) / 2 - */ - /* TODO: Clip maximum */ - constexpr int32_t raw_min = 23; - const int32_t x_0 = 0; - const int32_t x_min = std::max(x_0, (min_ - raw_min) / 2); - const int32_t x_avg = std::max(x_min, (avg_ - raw_min) / 2); - const int32_t x_max = std::max(x_avg + 1, (max_ - raw_min) / 2); - const int32_t x_lim = r.width(); + + constexpr int rssi_sample_range = 256; + constexpr float rssi_voltage_min = 0.4; + constexpr float rssi_voltage_max = 2.2; + constexpr float adc_voltage_max = 3.3; + constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max; + constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max; + constexpr int raw_delta = raw_max - raw_min; + const range_t x_avg_range { 0, r.width() - 1 }; + const auto x_avg = x_avg_range.clip((avg_ - raw_min) * r.width() / raw_delta); + const range_t x_min_range { 0, x_avg }; + const auto x_min = x_min_range.clip((min_ - raw_min) * r.width() / raw_delta); + const range_t x_max_range { x_avg + 1, r.width() }; + const auto x_max = x_max_range.clip((max_ - raw_min) * r.width() / raw_delta); const Rect r0 { - static_cast(r.left() + x_0), r.top(), - static_cast(x_min - x_0), r.height() + static_cast(r.left()), r.top(), + static_cast(x_min), r.height() }; painter.fill_rectangle( r0, @@ -90,7 +96,7 @@ void RSSI::paint(Painter& painter) { const Rect r4 { static_cast(r.left() + x_max), r.top(), - static_cast(x_lim - x_max), r.height() + static_cast(r.width() - x_max), r.height() }; painter.fill_rectangle( r4, diff --git a/firmware/application/ui_rssi.hpp b/firmware/application/ui_rssi.hpp index 503353b7..b8f8259e 100644 --- a/firmware/application/ui_rssi.hpp +++ b/firmware/application/ui_rssi.hpp @@ -26,6 +26,8 @@ #include "ui_widget.hpp" #include "ui_painter.hpp" +#include "message.hpp" + #include namespace ui { diff --git a/firmware/application/ui_sd_card_status_view.cpp b/firmware/application/ui_sd_card_status_view.cpp new file mode 100644 index 00000000..d790a760 --- /dev/null +++ b/firmware/application/ui_sd_card_status_view.cpp @@ -0,0 +1,85 @@ +/* + * 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_sd_card_status_view.hpp" + +#include +#include + +namespace ui { + +/* SDCardStatusView *****************************************************/ + +SDCardStatusView::SDCardStatusView() { + add_children({ { + &text_status, + } }); + + on_status(sd_card::status()); +} + +void SDCardStatusView::on_show() { + sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status status) { + this->on_status(status); + }; +} + +void SDCardStatusView::on_hide() { + sd_card::status_signal -= sd_card_status_signal_token; +} + +void SDCardStatusView::on_status(const sd_card::Status status) { + std::string msg("??"); + + switch(status) { + case sd_card::Status::IOError: + msg = "IO"; + break; + + case sd_card::Status::MountError: + msg = "MT"; + break; + + case sd_card::Status::ConnectError: + msg = "CN"; + break; + + case sd_card::Status::NotPresent: + msg = "XX"; + break; + + case sd_card::Status::Present: + msg = "OO"; + break; + + case sd_card::Status::Mounted: + msg = "OK"; + break; + + default: + msg = "--"; + break; + } + + text_status.set(msg); +} + +} /* namespace ui */ diff --git a/firmware/application/ui_sd_card_status_view.hpp b/firmware/application/ui_sd_card_status_view.hpp new file mode 100644 index 00000000..dd542f3c --- /dev/null +++ b/firmware/application/ui_sd_card_status_view.hpp @@ -0,0 +1,50 @@ +/* + * 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 __UI_SD_CARD_STATUS_VIEW_H__ +#define __UI_SD_CARD_STATUS_VIEW_H__ + +#include "ui_widget.hpp" +#include "sd_card.hpp" + +namespace ui { + +class SDCardStatusView : public View { +public: + SDCardStatusView(); + + void on_show() override; + void on_hide() override; + +private: + Text text_status { + { 0 * 8, 0, 2 * 8, 1 * 16 }, + "", + }; + + SignalToken sd_card_status_signal_token; + + void on_status(const sd_card::Status status); +}; + +} /* namespace ui */ + +#endif/*__UI_SD_CARD_STATUS_VIEW_H__*/ diff --git a/firmware/application/ui_setup.cpp b/firmware/application/ui_setup.cpp index 4b599315..809b42dc 100644 --- a/firmware/application/ui_setup.cpp +++ b/firmware/application/ui_setup.cpp @@ -19,19 +19,14 @@ * Boston, MA 02110-1301, USA. */ -#include "ui_font_fixed_8x16.hpp" #include "ui_setup.hpp" -#include "touch.hpp" -#include "portapack.hpp" #include "portapack_persistent_memory.hpp" #include "lpc43xx_cpp.hpp" - -#include -#include - using namespace lpc43xx; -using namespace portapack; + +#include "portapack.hpp" +using portapack::receiver_model; namespace ui { @@ -149,15 +144,33 @@ SetFrequencyCorrectionModel SetFrequencyCorrectionView::form_collect() { }; } -SetTouchCalibView::SetTouchCalibView(NavigationView& nav) { - add_children({{ +AntennaBiasSetupView::AntennaBiasSetupView(NavigationView& nav) { + add_children({ { &text_title, - &text_debugx, - &text_debugy, - &button_ok - }}); + &text_description_1, + &text_description_2, + &text_description_3, + &text_description_4, + &options_bias, + &button_done, + } }); - button_ok.on_select = [&nav](Button&){ nav.pop(); }; + options_bias.set_by_value(receiver_model.antenna_bias() ? 1 : 0); + options_bias.on_change = [this](size_t, OptionsField::value_t v) { + receiver_model.set_antenna_bias(v); + }; + + button_done.on_select = [&nav](Button&){ nav.pop(); }; +} + +void AntennaBiasSetupView::focus() { + button_done.focus(); +} + + + +void AboutView::focus() { + button_ok.focus(); } void SetTouchCalibView::focus() { @@ -419,10 +432,11 @@ void ModInfoView::focus() { } SetupMenuView::SetupMenuView(NavigationView& nav) { - add_items<6>({ { + add_items<7>({ { { "SD card modules", ui::Color::white(), [&nav](){ nav.push(new ModInfoView { nav }); } }, { "Date/Time", ui::Color::white(), [&nav](){ nav.push(new SetDateTimeView { nav }); } }, - { "Frequency correction", ui::Color::white(), [&nav](){ nav.push(new SetFrequencyCorrectionView { nav }); } }, + { "Frequency correction", ui::Color::white(), [&nav](){ nav.push(); } }, + { "Antenna Bias Voltage", [&nav](){ nav.push(); } }, { "Touch screen", ui::Color::white(), [&nav](){ nav.push(new SetTouchCalibView { nav }); } }, { "Play dead", ui::Color::red(), [&nav](){ nav.push(new SetPlayDeadView { nav }); } }, { "UI", ui::Color::white(), [&nav](){ nav.push(new SetUIView { nav }); } }, diff --git a/firmware/application/ui_setup.hpp b/firmware/application/ui_setup.hpp index 76d80f79..a1383851 100644 --- a/firmware/application/ui_setup.hpp +++ b/firmware/application/ui_setup.hpp @@ -22,8 +22,6 @@ #ifndef __UI_SETUP_H__ #define __UI_SETUP_H__ -#include "ff.h" - #include "ui_widget.hpp" #include "ui_menu.hpp" #include "ui_navigation.hpp" @@ -207,129 +205,50 @@ private: }; }; -class SetUIView : public View { +class AntennaBiasSetupView : public View { public: - SetUIView(NavigationView& nav); + AntennaBiasSetupView(NavigationView& nav); + void focus() override; - + private: - Checkbox checkbox_showsplash { - { 3 * 8, 2 * 16}, - "Show splash" - }; - - Checkbox checkbox_bloff { - { 3 * 8, 4 * 16}, - "Backlight off after:" + Text text_title { + { 5 * 8, 3 * 16, 20 * 8, 16 }, + "Antenna Bias Voltage" }; - OptionsField options_bloff { - { 10 * 8, 5 * 16 + 4 }, - 10, + Text text_description_1 { + { 24, 6 * 16, 24 * 8, 16 }, + "CAUTION: Ensure that all" + }; + + Text text_description_2 { + { 28, 7 * 16, 23 * 8, 16 }, + "devices attached to the" + }; + + Text text_description_3 { + { 8, 8 * 16, 28 * 8, 16 }, + "antenna connector can accept" + }; + + Text text_description_4 { + { 68, 9 * 16, 13 * 8, 16 }, + "a DC voltage!" + }; + + OptionsField options_bias { + { 100, 12 * 16 }, + 5, { - { "5 seconds ", 0 }, - { "15 seconds", 1 }, - { "1 minute ", 2 }, - { "5 minutes ", 3 }, - { "10 minutes", 4 } + { " Off ", 0 }, + { " On ", 1 }, } }; - - Button button_ok { - { 4 * 8, 272, 64, 24 }, - "Ok" - }; -}; -class SetPlayDeadView : public View { -public: - SetPlayDeadView(NavigationView& nav); - void focus() override; -private: - bool entermode = false; - uint32_t sequence = 0; - uint8_t keycount, key_code; - char sequence_txt[11]; - - Text text_sequence { - { 64, 32, 14 * 8, 16 }, - "Enter sequence", - }; - - Button button_enter { - { 16, 192, 96, 24 }, - "Enter" - }; - Button button_cancel { - { 128, 192, 96, 24 }, - "Cancel" - }; -}; - -class ModInfoView : public View { -public: - ModInfoView(NavigationView& nav); - void focus() override; - void on_show() override; - -private: - void update_infos(uint8_t modn); - - typedef struct moduleinfo_t{ - char filename[9]; - uint16_t version; - uint32_t size; - char md5[16]; - char name[16]; - char description[214]; - } moduleinfo_t; - - moduleinfo_t module_list[8]; // 8 max for now - - Text text_modcount { - { 2 * 8, 1 * 16, 18 * 8, 16 }, - "Searching..." - }; - - OptionsField option_modules { - { 2 * 8, 2 * 16 }, - 24, - { { "-", 0 } - } - }; - - Text text_name { - { 2 * 8, 4 * 16, 5 * 8, 16 }, - "Name:" - }; - Text text_namestr { - { 8 * 8, 4 * 16, 16 * 8, 16 }, - "..." - }; - Text text_size { - { 2 * 8, 5 * 16, 5 * 8, 16 }, - "Size:" - }; - Text text_sizestr { - { 8 * 8, 5 * 16, 16 * 8, 16 }, - "..." - }; - Text text_md5 { - { 2 * 8, 6 * 16, 4 * 8, 16 }, - "MD5:" - }; - Text text_md5_a { - { 7 * 8, 6 * 16, 16 * 8, 16 }, - "..." - }; - Text text_md5_b { - { 7 * 8, 7 * 16, 16 * 8, 16 }, - "..." - }; - - Button button_ok { - { 4 * 8, 272, 64, 24 }, - "Ok" + Button button_done { + { 72, 15 * 16, 96, 24 }, + "Done" }; }; diff --git a/firmware/application/ui_spectrum.cpp b/firmware/application/ui_spectrum.cpp index b0a81b07..27e7da43 100644 --- a/firmware/application/ui_spectrum.cpp +++ b/firmware/application/ui_spectrum.cpp @@ -21,6 +21,279 @@ #include "ui_spectrum.hpp" -namespace ui { +#include "event_m0.hpp" +#include "spectrum_color_lut.hpp" + +#include "portapack.hpp" +#include "portapack_shared_memory.hpp" +using namespace portapack; + +#include "string_format.hpp" + +#include +#include + +namespace ui { +namespace spectrum { + +/* FrequencyScale ********************************************************/ + +void FrequencyScale::on_show() { + clear(); +} + +void FrequencyScale::set_spectrum_sampling_rate(const int new_sampling_rate) { + if( (spectrum_sampling_rate != new_sampling_rate) ) { + spectrum_sampling_rate = new_sampling_rate; + set_dirty(); + } +} + +void FrequencyScale::set_channel_filter( + const int pass_frequency, + const int stop_frequency +) { + if( (channel_filter_pass_frequency != pass_frequency) || + (channel_filter_stop_frequency != stop_frequency) ) { + channel_filter_pass_frequency = pass_frequency; + channel_filter_stop_frequency = stop_frequency; + set_dirty(); + } +} + +void FrequencyScale::paint(Painter& painter) { + const auto r = screen_rect(); + + clear_background(painter, r); + + if( !spectrum_sampling_rate ) { + // Can't draw without non-zero scale. + return; + } + + draw_filter_ranges(painter, r); + draw_frequency_ticks(painter, r); +} + +void FrequencyScale::clear() { + spectrum_sampling_rate = 0; + set_dirty(); +} + +void FrequencyScale::clear_background(Painter& painter, const Rect r) { + painter.fill_rectangle(r, Color::black()); +} + +void FrequencyScale::draw_frequency_ticks(Painter& painter, const Rect r) { + const auto x_center = r.width() / 2; + + const Rect tick { r.left() + x_center, r.top(), 1, r.height() }; + painter.fill_rectangle(tick, Color::white()); + + constexpr int tick_count_max = 4; + float rough_tick_interval = float(spectrum_sampling_rate) / tick_count_max; + int magnitude = 1; + int magnitude_n = 0; + while(rough_tick_interval >= 10.0f) { + rough_tick_interval /= 10; + magnitude *= 10; + magnitude_n += 1; + } + const int tick_interval = std::ceil(rough_tick_interval); + + auto tick_offset = tick_interval; + while((tick_offset * magnitude) < spectrum_sampling_rate / 2) { + const Dim pixel_offset = tick_offset * magnitude * spectrum_bins / spectrum_sampling_rate; + + const std::string zero_pad = + ((magnitude_n % 3) == 0) ? "" : + ((magnitude_n % 3) == 1) ? "0" : "00"; + const std::string unit = + (magnitude_n >= 6) ? "M" : + (magnitude_n >= 3) ? "k" : ""; + const std::string label = to_string_dec_uint(tick_offset) + zero_pad + unit; + + const Coord offset_low = r.left() + x_center - pixel_offset; + const Rect tick_low { offset_low, r.top(), 1, r.height() }; + painter.fill_rectangle(tick_low, Color::white()); + painter.draw_string({ offset_low + 2, r.top() }, style(), label ); + + const Coord offset_high = r.left() + x_center + pixel_offset; + const Rect tick_high { offset_high, r.top(), 1, r.height() }; + painter.fill_rectangle(tick_high, Color::white()); + painter.draw_string({ offset_high + 2, r.top() }, style(), label ); + + tick_offset += tick_interval; + } +} + +void FrequencyScale::draw_filter_ranges(Painter& painter, const Rect r) { + if( channel_filter_pass_frequency ) { + const auto x_center = r.width() / 2; + + const auto pass_offset = channel_filter_pass_frequency * spectrum_bins / spectrum_sampling_rate; + const auto stop_offset = channel_filter_stop_frequency * spectrum_bins / spectrum_sampling_rate; + + const auto pass_x_lo = x_center - pass_offset; + const auto pass_x_hi = x_center + pass_offset; + + if( channel_filter_stop_frequency ) { + const auto stop_x_lo = x_center - stop_offset; + const auto stop_x_hi = x_center + stop_offset; + + const Rect r_stop_lo { + r.left() + stop_x_lo, r.bottom() - filter_band_height, + pass_x_lo - stop_x_lo, filter_band_height + }; + painter.fill_rectangle( + r_stop_lo, + Color::yellow() + ); + + const Rect r_stop_hi { + r.left() + pass_x_hi, r.bottom() - filter_band_height, + stop_x_hi - pass_x_hi, filter_band_height + }; + painter.fill_rectangle( + r_stop_hi, + Color::yellow() + ); + } + + const Rect r_pass { + r.left() + pass_x_lo, r.bottom() - filter_band_height, + pass_x_hi - pass_x_lo, filter_band_height + }; + painter.fill_rectangle( + r_pass, + Color::green() + ); + } +} + +/* WaterfallView *********************************************************/ + +void WaterfallView::on_show() { + clear(); + + const auto screen_r = screen_rect(); + display.scroll_set_area(screen_r.top(), screen_r.bottom()); +} + +void WaterfallView::on_hide() { + /* TODO: Clear region to eliminate brief flash of content at un-shifted + * position? + */ + display.scroll_disable(); +} + +void WaterfallView::paint(Painter& painter) { + // Do nothing. + (void)painter; +} + +void WaterfallView::on_channel_spectrum( + const ChannelSpectrum& spectrum +) { + /* TODO: static_assert that message.spectrum.db.size() >= pixel_row.size() */ + + std::array pixel_row; + for(size_t i=0; i<120; i++) { + const auto pixel_color = spectrum_rgb3_lut[spectrum.db[256 - 120 + i]]; + pixel_row[i] = pixel_color; + } + + for(size_t i=120; i<240; i++) { + const auto pixel_color = spectrum_rgb3_lut[spectrum.db[i - 120]]; + pixel_row[i] = pixel_color; + } + + const auto draw_y = display.scroll(1); + + display.draw_pixels( + { { 0, draw_y }, { pixel_row.size(), 1 } }, + pixel_row + ); +} + +void WaterfallView::clear() { + display.fill_rectangle( + screen_rect(), + Color::black() + ); +} + +/* WaterfallWidget *******************************************************/ + +WaterfallWidget::WaterfallWidget() { + add_children({ + &waterfall_view, + &frequency_scale, + }); +} + +void WaterfallWidget::on_show() { + EventDispatcher::message_map().register_handler(Message::ID::ChannelSpectrumConfig, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->fifo = message.fifo; + } + ); + EventDispatcher::message_map().register_handler(Message::ID::DisplayFrameSync, + [this](const Message* const) { + if( this->fifo ) { + ChannelSpectrum channel_spectrum; + while( fifo->out(channel_spectrum) ) { + this->on_channel_spectrum(channel_spectrum); + } + } + } + ); + + shared_memory.baseband_queue.push_and_wait( + SpectrumStreamingConfigMessage { + SpectrumStreamingConfigMessage::Mode::Running + } + ); +} + +void WaterfallWidget::on_hide() { + shared_memory.baseband_queue.push_and_wait( + SpectrumStreamingConfigMessage { + SpectrumStreamingConfigMessage::Mode::Stopped + } + ); + + EventDispatcher::message_map().unregister_handler(Message::ID::DisplayFrameSync); + EventDispatcher::message_map().unregister_handler(Message::ID::ChannelSpectrumConfig); +} + +void WaterfallWidget::set_parent_rect(const Rect new_parent_rect) { + constexpr Dim scale_height = 20; + + View::set_parent_rect(new_parent_rect); + frequency_scale.set_parent_rect({ 0, 0, new_parent_rect.width(), scale_height }); + waterfall_view.set_parent_rect({ + 0, scale_height, + new_parent_rect.width(), + new_parent_rect.height() - scale_height + }); +} + +void WaterfallWidget::paint(Painter& painter) { + // TODO: + (void)painter; +} + +void WaterfallWidget::on_channel_spectrum(const ChannelSpectrum& spectrum) { + waterfall_view.on_channel_spectrum(spectrum); + frequency_scale.set_spectrum_sampling_rate(spectrum.sampling_rate); + frequency_scale.set_channel_filter( + spectrum.channel_filter_pass_frequency, + spectrum.channel_filter_stop_frequency + ); +} + +} /* namespace spectrum */ } /* namespace ui */ diff --git a/firmware/application/ui_spectrum.hpp b/firmware/application/ui_spectrum.hpp index 6a10088f..0c9321df 100644 --- a/firmware/application/ui_spectrum.hpp +++ b/firmware/application/ui_spectrum.hpp @@ -24,274 +24,69 @@ #include "ui.hpp" #include "ui_widget.hpp" -#include "spectrum_color_lut.hpp" - -#include "portapack.hpp" -using namespace portapack; #include "message.hpp" #include -#include -#include +#include namespace ui { namespace spectrum { class FrequencyScale : public Widget { public: - void on_show() override { - clear(); - } + void on_show() override; - void set_spectrum_sampling_rate(const uint32_t new_sampling_rate, const size_t new_spectrum_bins) { - if( (spectrum_sampling_rate != new_sampling_rate) || - (spectrum_bins != new_spectrum_bins) ) { - spectrum_sampling_rate = new_sampling_rate; - spectrum_bins = new_spectrum_bins; - set_dirty(); - } - } + void set_spectrum_sampling_rate(const int new_sampling_rate); + void set_channel_filter(const int pass_frequency, const int stop_frequency); - void set_channel_filter( - const uint32_t pass_frequency, - const uint32_t stop_frequency - ) { - if( (channel_filter_pass_frequency != pass_frequency) || - (channel_filter_stop_frequency != stop_frequency) ) { - channel_filter_pass_frequency = pass_frequency; - channel_filter_stop_frequency = stop_frequency; - set_dirty(); - } - } - - void paint(Painter& painter) override { - const auto r = screen_rect(); - - clear_background(painter, r); - - if( !spectrum_sampling_rate || !spectrum_bins ) { - // Can't draw without non-zero scale. - return; - } - - draw_filter_ranges(painter, r); - draw_frequency_ticks(painter, r); - } + void paint(Painter& painter) override; private: - static constexpr Dim filter_band_height = 4; + static constexpr int filter_band_height = 4; - uint32_t spectrum_sampling_rate { 0 }; - size_t spectrum_bins { 0 }; - uint32_t channel_filter_pass_frequency { 0 }; - uint32_t channel_filter_stop_frequency { 0 }; + int spectrum_sampling_rate { 0 }; + const int spectrum_bins = std::tuple_size::value; + int channel_filter_pass_frequency { 0 }; + int channel_filter_stop_frequency { 0 }; - void clear() { - spectrum_sampling_rate = 0; - spectrum_bins = 0; - set_dirty(); - } + void clear(); + void clear_background(Painter& painter, const Rect r); - void clear_background(Painter& painter, const Rect r) { - painter.fill_rectangle(r, Color::black()); - } - - void draw_frequency_ticks(Painter& painter, const Rect r) { - const auto x_center = r.width() / 2; - - const Rect tick { - static_cast(r.left() + x_center), r.top(), - 1, r.height() - }; - painter.fill_rectangle(tick, Color::white()); - - constexpr uint32_t tick_count_max = 4; - float rough_tick_interval = float(spectrum_sampling_rate) / tick_count_max; - size_t magnitude = 1; - size_t magnitude_n = 0; - while(rough_tick_interval >= 10.0f) { - rough_tick_interval /= 10; - magnitude *= 10; - magnitude_n += 1; - } - const size_t tick_interval = std::ceil(rough_tick_interval); - - size_t tick_offset = tick_interval; - while((tick_offset * magnitude) < spectrum_sampling_rate / 2) { - const Dim pixel_offset = tick_offset * magnitude * spectrum_bins / spectrum_sampling_rate; - - const std::string zero_pad = - ((magnitude_n % 3) == 0) ? "" : - ((magnitude_n % 3) == 1) ? "0" : "00"; - const std::string unit = - (magnitude_n >= 6) ? "M" : - (magnitude_n >= 3) ? "k" : ""; - const std::string label = to_string_dec_uint(tick_offset) + zero_pad + unit; - - const Coord offset_low = static_cast(r.left() + x_center - pixel_offset); - const Rect tick_low { offset_low, r.top(), 1, r.height() }; - painter.fill_rectangle(tick_low, Color::white()); - painter.draw_string({ static_cast(offset_low + 2), r.top() }, style(), label ); - - const Coord offset_high = static_cast(r.left() + x_center + pixel_offset); - const Rect tick_high { offset_high, r.top(), 1, r.height() }; - painter.fill_rectangle(tick_high, Color::white()); - painter.draw_string({ static_cast(offset_high + 2), r.top() }, style(), label ); - - tick_offset += tick_interval; - } - } - - void draw_filter_ranges(Painter& painter, const Rect r) { - if( channel_filter_pass_frequency ) { - const auto x_center = r.width() / 2; - - const auto pass_offset = channel_filter_pass_frequency * spectrum_bins / spectrum_sampling_rate; - const auto stop_offset = channel_filter_stop_frequency * spectrum_bins / spectrum_sampling_rate; - - const auto pass_x_lo = x_center - pass_offset; - const auto pass_x_hi = x_center + pass_offset; - - if( channel_filter_stop_frequency ) { - const auto stop_x_lo = x_center - stop_offset; - const auto stop_x_hi = x_center + stop_offset; - - const Rect r_stop_lo { - static_cast(r.left() + stop_x_lo), static_cast(r.bottom() - filter_band_height), - static_cast(pass_x_lo - stop_x_lo), filter_band_height - }; - painter.fill_rectangle( - r_stop_lo, - Color::yellow() - ); - - const Rect r_stop_hi { - static_cast(r.left() + pass_x_hi), static_cast(r.bottom() - filter_band_height), - static_cast(stop_x_hi - pass_x_hi), filter_band_height - }; - painter.fill_rectangle( - r_stop_hi, - Color::yellow() - ); - } - - const Rect r_pass { - static_cast(r.left() + pass_x_lo), static_cast(r.bottom() - filter_band_height), - static_cast(pass_x_hi - pass_x_lo), filter_band_height - }; - painter.fill_rectangle( - r_pass, - Color::green() - ); - } - } + void draw_frequency_ticks(Painter& painter, const Rect r); + void draw_filter_ranges(Painter& painter, const Rect r); }; class WaterfallView : public Widget { public: - void on_show() override { - clear(); + void on_show() override; + void on_hide() override; - const auto screen_r = screen_rect(); - display.scroll_set_area(screen_r.top(), screen_r.bottom()); - } + void paint(Painter& painter) override; - void on_hide() override { - /* TODO: Clear region to eliminate brief flash of content at un-shifted - * position? - */ - display.scroll_disable(); - } - - void paint(Painter& painter) override { - // Do nothing. - (void)painter; - } - - void on_channel_spectrum( - const ChannelSpectrum& spectrum - ) { - /* TODO: static_assert that message.spectrum.db.size() >= pixel_row.size() */ - - std::array pixel_row; - for(size_t i=0; i<120; i++) { - const auto pixel_color = spectrum_rgb3_lut[spectrum.db[256 - 120 + i]]; - pixel_row[i] = pixel_color; - } - - for(size_t i=120; i<240; i++) { - const auto pixel_color = spectrum_rgb3_lut[spectrum.db[i - 120]]; - pixel_row[i] = pixel_color; - } - - const auto draw_y = display.scroll(1); - - display.draw_pixels( - { { 0, draw_y }, { pixel_row.size(), 1 } }, - pixel_row - ); - } + void on_channel_spectrum(const ChannelSpectrum& spectrum); private: - void clear() { - display.fill_rectangle( - screen_rect(), - Color::black() - ); - } + void clear(); }; class WaterfallWidget : public View { public: - WaterfallWidget() { - add_children({ - &waterfall_view, - &frequency_scale, - }); - } + WaterfallWidget(); - void on_show() override { - context().message_map().register_handler(Message::ID::ChannelSpectrum, - [this](const Message* const p) { - this->on_channel_spectrum(reinterpret_cast(p)->spectrum); - } - ); - } + void on_show() override; + void on_hide() override; - void on_hide() override { - context().message_map().unregister_handler(Message::ID::ChannelSpectrum); - } + void set_parent_rect(const Rect new_parent_rect) override; - void set_parent_rect(const Rect new_parent_rect) override { - constexpr Dim scale_height = 20; - - View::set_parent_rect(new_parent_rect); - frequency_scale.set_parent_rect({ 0, 0, new_parent_rect.width(), scale_height }); - waterfall_view.set_parent_rect({ - 0, scale_height, - new_parent_rect.width(), - static_cast(new_parent_rect.height() - scale_height) - }); - } - - void paint(Painter& painter) override { - // TODO: - (void)painter; - } + void paint(Painter& painter) override; private: WaterfallView waterfall_view; FrequencyScale frequency_scale; + ChannelSpectrumFIFO* fifo; - void on_channel_spectrum(const ChannelSpectrum& spectrum) { - waterfall_view.on_channel_spectrum(spectrum); - frequency_scale.set_spectrum_sampling_rate(spectrum.sampling_rate, spectrum.db_count); - frequency_scale.set_channel_filter( - spectrum.channel_filter_pass_frequency, - spectrum.channel_filter_stop_frequency - ); - } + void on_channel_spectrum(const ChannelSpectrum& spectrum); }; } /* namespace spectrum */ diff --git a/firmware/baseband/Makefile b/firmware/baseband/Makefile index f36ce108..d6a2c04c 100755 --- a/firmware/baseband/Makefile +++ b/firmware/baseband/Makefile @@ -124,22 +124,26 @@ CSRC = $(PORTSRC) \ # setting. CPPSRC = main.cpp \ message_queue.cpp \ + event.cpp \ event_m4.cpp \ - irq_ipc_m4.cpp \ gpdma.cpp \ baseband_dma.cpp \ + baseband_sgpio.cpp \ portapack_shared_memory.cpp \ + baseband_thread.cpp \ baseband_processor.cpp \ - channel_decimator.cpp \ + baseband_stats_collector.cpp \ dsp_decimate.cpp \ dsp_demodulate.cpp \ matched_filter.cpp \ proc_am_audio.cpp \ proc_nfm_audio.cpp \ + spectrum_collector.cpp \ proc_wfm_audio.cpp \ proc_ais.cpp \ proc_wideband_spectrum.cpp \ proc_tpms.cpp \ + proc_ert.cpp \ proc_afskrx.cpp \ proc_sigfrx.cpp \ dsp_squelch.cpp \ @@ -147,11 +151,15 @@ CPPSRC = main.cpp \ packet_builder.cpp \ dsp_fft.cpp \ dsp_fir_taps.cpp \ + dsp_iir.cpp \ fxpt_atan2.cpp \ rssi.cpp \ rssi_dma.cpp \ + rssi_thread.cpp \ audio.cpp \ + audio_output.cpp \ audio_dma.cpp \ + audio_stats_collector.cpp \ touch_dma.cpp \ ../common/utility.cpp \ ../common/chibios_cpp.cpp \ diff --git a/firmware/baseband/audio_dma.cpp b/firmware/baseband/audio_dma.cpp index 6fa6439b..60e85096 100644 --- a/firmware/baseband/audio_dma.cpp +++ b/firmware/baseband/audio_dma.cpp @@ -208,8 +208,8 @@ void enable() { } void disable() { - gpdma_channel_i2s0_tx.disable_force(); - gpdma_channel_i2s0_rx.disable_force(); + gpdma_channel_i2s0_tx.disable(); + gpdma_channel_i2s0_rx.disable(); } buffer_t tx_empty_buffer() { diff --git a/firmware/baseband/audio_output.cpp b/firmware/baseband/audio_output.cpp new file mode 100644 index 00000000..6e29fa16 --- /dev/null +++ b/firmware/baseband/audio_output.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2014 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_output.hpp" + +#include "portapack_shared_memory.hpp" + +#include "audio_dma.hpp" + +#include "message.hpp" + +#include +#include +#include + +void AudioOutput::configure( + const iir_biquad_config_t& hpf_config, + const iir_biquad_config_t& deemph_config, + const float squelch_threshold +) { + hpf.configure(hpf_config); + deemph.configure(deemph_config); + squelch.set_threshold(squelch_threshold); +} + +void AudioOutput::write( + const buffer_s16_t& audio +) { + std::array audio_f; + for(size_t i=0; i + +class AudioOutput { +public: + void configure( + const iir_biquad_config_t& hpf_config, + const iir_biquad_config_t& deemph_config = iir_config_passthrough, + const float squelch_threshold = 0.0f + ); + + void write(const buffer_s16_t& audio); + void write(const buffer_f32_t& audio); + +private: + IIRBiquadFilter hpf; + IIRBiquadFilter deemph; + FMSquelch squelch; + + AudioStatsCollector audio_stats; + + uint64_t audio_present_history = 0; + + void fill_audio_buffer(const buffer_f32_t& audio); + void feed_audio_stats(const buffer_f32_t& audio); +}; + +#endif/*__AUDIO_OUTPUT_H__*/ diff --git a/firmware/baseband/audio_stats_collector.cpp b/firmware/baseband/audio_stats_collector.cpp new file mode 100644 index 00000000..227d5887 --- /dev/null +++ b/firmware/baseband/audio_stats_collector.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 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_stats_collector.hpp" + +#include "utility.hpp" + +void AudioStatsCollector::consume_audio_buffer(const buffer_f32_t& src) { + auto src_p = src.p; + const auto src_end = &src.p[src.count]; + while(src_p < src_end) { + const auto sample = *(src_p++); + const auto sample_squared = sample * sample; + squared_sum += sample_squared; + if( sample_squared > max_squared ) { + max_squared = sample_squared; + } + } +} + +bool AudioStatsCollector::update_stats(const size_t sample_count, const size_t sampling_rate) { + count += sample_count; + + const size_t samples_per_update = sampling_rate * update_interval; + + if( count >= samples_per_update ) { + statistics.rms_db = complex16_mag_squared_to_dbv_norm(squared_sum / count); + statistics.max_db = complex16_mag_squared_to_dbv_norm(max_squared); + statistics.count = count; + + squared_sum = 0; + max_squared = 0; + count = 0; + + return true; + } else { + return false; + } +} + +bool AudioStatsCollector::feed(const buffer_f32_t& src) { + consume_audio_buffer(src); + + return update_stats(src.count, src.sampling_rate); +} + +bool AudioStatsCollector::mute(const size_t sample_count, const size_t sampling_rate) { + return update_stats(sample_count, sampling_rate); +} diff --git a/firmware/baseband/audio_stats_collector.hpp b/firmware/baseband/audio_stats_collector.hpp index 189fff80..10e17edc 100644 --- a/firmware/baseband/audio_stats_collector.hpp +++ b/firmware/baseband/audio_stats_collector.hpp @@ -22,9 +22,8 @@ #ifndef __AUDIO_STATS_COLLECTOR_H__ #define __AUDIO_STATS_COLLECTOR_H__ -#include "buffer.hpp" +#include "dsp_types.hpp" #include "message.hpp" -#include "utility.hpp" #include #include @@ -32,64 +31,33 @@ class AudioStatsCollector { public: template - void feed(buffer_s16_t src, Callback callback) { - consume_audio_buffer(src); - - if( update_stats(src.count, src.sampling_rate) ) { + void feed(const buffer_f32_t& src, Callback callback) { + if( feed(src) ) { callback(statistics); } } template void mute(const size_t sample_count, const size_t sampling_rate, Callback callback) { - if( update_stats(sample_count, sampling_rate) ) { + if( mute(sample_count, sampling_rate) ) { callback(statistics); } } private: static constexpr float update_interval { 0.1f }; - uint64_t squared_sum { 0 }; - uint32_t max_squared { 0 }; + float squared_sum { 0 }; + float max_squared { 0 }; size_t count { 0 }; AudioStatistics statistics; - void consume_audio_buffer(buffer_s16_t src) { - auto src_p = src.p; - const auto src_end = &src.p[src.count]; - while(src_p < src_end) { - const auto sample = *(src_p++); - const uint64_t sample_squared = sample * sample; - squared_sum += sample_squared; - if( sample_squared > max_squared ) { - max_squared = sample_squared; - } - } - } + void consume_audio_buffer(const buffer_f32_t& src); - bool update_stats(const size_t sample_count, const size_t sampling_rate) { - count += sample_count; + bool update_stats(const size_t sample_count, const size_t sampling_rate); - const size_t samples_per_update = sampling_rate * update_interval; - - if( count >= samples_per_update ) { - const float squared_sum_f = squared_sum; - const float max_squared_f = max_squared; - const float squared_avg_f = squared_sum_f / count; - statistics.rms_db = complex16_mag_squared_to_dbv_norm(squared_avg_f); - statistics.max_db = complex16_mag_squared_to_dbv_norm(max_squared_f); - statistics.count = count; - - squared_sum = 0; - max_squared = 0; - count = 0; - - return true; - } else { - return false; - } - } + bool feed(const buffer_f32_t& src); + bool mute(const size_t sample_count, const size_t sampling_rate); }; #endif/*__AUDIO_STATS_COLLECTOR_H__*/ diff --git a/firmware/baseband/baseband_dma.cpp b/firmware/baseband/baseband_dma.cpp index aa119dc6..7467f551 100644 --- a/firmware/baseband/baseband_dma.cpp +++ b/firmware/baseband/baseband_dma.cpp @@ -20,7 +20,6 @@ */ #include "baseband_dma.hpp" -#include "portapack_shared_memory.hpp" #include #include @@ -35,8 +34,6 @@ using namespace lpc43xx; namespace baseband { namespace dma { - - int quitt = 0; constexpr uint32_t gpdma_ahb_master_sgpio = 0; constexpr uint32_t gpdma_ahb_master_memory = 1; @@ -102,17 +99,12 @@ constexpr size_t msg_count = transfers_per_buffer - 1; static std::array lli_loop; static constexpr auto& gpdma_channel_sgpio = gpdma::channels[portapack::sgpio_gpdma_channel_number]; -//static Mailbox mailbox; -//static std::array messages; static Semaphore semaphore; static volatile const gpdma::channel::LLI* next_lli = nullptr; -void transfer_complete() { +static void transfer_complete() { next_lli = gpdma_channel_sgpio.next_lli(); - quitt = 0; - /* TODO: Is Mailbox the proper synchronization mechanism for this? */ - //chMBPostI(&mailbox, 0); chSemSignalI(&semaphore); } @@ -121,7 +113,6 @@ static void dma_error() { } void init() { - //chMBInit(&mailbox, messages.data(), messages.size()); chSemInit(&semaphore, 0); gpdma_channel_sgpio.set_handlers(transfer_complete, dma_error); @@ -148,7 +139,6 @@ void enable(const baseband::Direction direction) { const auto gpdma_config = config(direction); gpdma_channel_sgpio.configure(lli_loop[0], gpdma_config); - //chMBReset(&mailbox); chSemReset(&semaphore, 0); gpdma_channel_sgpio.enable(); @@ -159,14 +149,11 @@ bool is_enabled() { } void disable() { - gpdma_channel_sgpio.disable_force(); + gpdma_channel_sgpio.disable(); } baseband::buffer_t wait_for_rx_buffer() { - //msg_t msg; - //const auto status = chMBFetch(&mailbox, &msg, TIME_INFINITE); const auto status = chSemWait(&semaphore); - if (quitt) return { nullptr, 0 }; if( status == RDY_OK ) { const auto next = next_lli; if( next ) { @@ -174,28 +161,10 @@ baseband::buffer_t wait_for_rx_buffer() { const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask; return { reinterpret_cast(lli_loop[free_index].destaddr), transfer_samples }; } else { - return { nullptr, 0 }; + return { }; } } else { - return { nullptr, 0 }; - } -} - -baseband::buffer_t wait_for_tx_buffer() { - //msg_t msg; - //const auto status = chMBFetch(&mailbox, &msg, TIME_INFINITE); - const auto status = chSemWait(&semaphore); - if( status == RDY_OK ) { - const auto next = next_lli; - if( next ) { - const size_t next_index = next - &lli_loop[0]; - const size_t free_index = (next_index + transfers_per_buffer - 2) & transfers_mask; - return { reinterpret_cast(lli_loop[free_index].srcaddr), transfer_samples }; - } else { - return { nullptr, 0 }; - } - } else { - return { nullptr, 0 }; + return { }; } } diff --git a/firmware/baseband/baseband_processor.cpp b/firmware/baseband/baseband_processor.cpp index 83e6d857..4113dec5 100644 --- a/firmware/baseband/baseband_processor.cpp +++ b/firmware/baseband/baseband_processor.cpp @@ -23,100 +23,14 @@ #include "portapack_shared_memory.hpp" -#include "dsp_fft.hpp" - -#include "audio_dma.hpp" - #include "message.hpp" -#include "event_m4.hpp" -#include "utility.hpp" -#include -#include - -void BasebandProcessor::update_spectrum() { - // Called from idle thread (after EVT_MASK_SPECTRUM is flagged) - if( channel_spectrum_request_update ) { - /* Decimated buffer is full. Compute spectrum. */ - channel_spectrum_request_update = false; - fft_c_preswapped(channel_spectrum); - - ChannelSpectrumMessage spectrum_message; - for(size_t i=0; i .magnitude, or something more (less!) accurate. */ - spectrum_message.spectrum.db_count = spectrum_message.spectrum.db.size(); - spectrum_message.spectrum.sampling_rate = channel_spectrum_sampling_rate; - spectrum_message.spectrum.channel_filter_pass_frequency = channel_filter_pass_frequency; - spectrum_message.spectrum.channel_filter_stop_frequency = channel_filter_stop_frequency; - shared_memory.application_queue.push(spectrum_message); - } -} - -void BasebandProcessor::feed_channel_stats(const buffer_c16_t channel) { +void BasebandProcessor::feed_channel_stats(const buffer_c16_t& channel) { channel_stats.feed( channel, - [this](const ChannelStatistics statistics) { - this->post_channel_stats_message(statistics); + [](const ChannelStatistics& statistics) { + const ChannelStatisticsMessage channel_stats_message { statistics }; + shared_memory.application_queue.push(channel_stats_message); } ); } - -void BasebandProcessor::feed_channel_spectrum( - const buffer_c16_t channel, - const uint32_t filter_pass_frequency, - const uint32_t filter_stop_frequency -) { - channel_filter_pass_frequency = filter_pass_frequency; - channel_filter_stop_frequency = filter_stop_frequency; - channel_spectrum_decimator.feed( - channel, - [this](const buffer_c16_t data) { - this->post_channel_spectrum_message(data); - } - ); -} - -void BasebandProcessor::fill_audio_buffer(const buffer_s16_t audio) { - auto audio_buffer = audio::dma::tx_empty_buffer();; - for(size_t i=0; ipost_audio_stats_message(statistics); - } - ); -} - -void BasebandProcessor::post_audio_stats_message(const AudioStatistics statistics) { - audio_stats_message.statistics = statistics; - shared_memory.application_queue.push(audio_stats_message); -} diff --git a/firmware/baseband/baseband_processor.hpp b/firmware/baseband/baseband_processor.hpp index a1a22889..28ef5063 100644 --- a/firmware/baseband/baseband_processor.hpp +++ b/firmware/baseband/baseband_processor.hpp @@ -23,54 +23,24 @@ #define __BASEBAND_PROCESSOR_H__ #include "dsp_types.hpp" -#include "complex.hpp" -#include "block_decimator.hpp" #include "channel_stats_collector.hpp" -#include "audio_stats_collector.hpp" -#include -#include -#include +#include "message.hpp" class BasebandProcessor { public: virtual ~BasebandProcessor() = default; - virtual void execute(buffer_c8_t buffer) = 0; + virtual void execute(const buffer_c8_t& buffer) = 0; - void update_spectrum(); + virtual void on_message(const Message* const) { }; protected: - void feed_channel_stats(const buffer_c16_t channel); - - void feed_channel_spectrum( - const buffer_c16_t channel, - const uint32_t filter_pass_frequency, - const uint32_t filter_stop_frequency - ); - - void fill_audio_buffer(const buffer_s16_t audio); - - volatile bool channel_spectrum_request_update { false }; - std::array, 256> channel_spectrum; - uint32_t channel_spectrum_sampling_rate { 0 }; - uint32_t channel_filter_pass_frequency { 0 }; - uint32_t channel_filter_stop_frequency { 0 }; + void feed_channel_stats(const buffer_c16_t& channel); private: - BlockDecimator<256> channel_spectrum_decimator { 4 }; - ChannelStatsCollector channel_stats; - ChannelStatisticsMessage channel_stats_message; - - AudioStatsCollector audio_stats; - AudioStatisticsMessage audio_stats_message; - - void post_channel_stats_message(const ChannelStatistics statistics); - void post_channel_spectrum_message(const buffer_c16_t data); - void feed_audio_stats(const buffer_s16_t audio); - void post_audio_stats_message(const AudioStatistics statistics); }; #endif/*__BASEBAND_PROCESSOR_H__*/ diff --git a/firmware/baseband/baseband_stats_collector.cpp b/firmware/baseband/baseband_stats_collector.cpp new file mode 100644 index 00000000..8eec12c5 --- /dev/null +++ b/firmware/baseband/baseband_stats_collector.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 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 "baseband_stats_collector.hpp" + +#include "lpc43xx_cpp.hpp" + +bool BasebandStatsCollector::process(const buffer_c8_t& buffer) { + samples += buffer.count; + + const size_t report_samples = buffer.sampling_rate * report_interval; + const auto report_delta = samples - samples_last_report; + return report_delta >= report_samples; +} + +BasebandStatistics BasebandStatsCollector::capture_statistics() { + BasebandStatistics statistics; + + const auto idle_ticks = thread_idle->total_ticks; + statistics.idle_ticks = (idle_ticks - last_idle_ticks); + last_idle_ticks = idle_ticks; + + const auto main_ticks = thread_main->total_ticks; + statistics.main_ticks = (main_ticks - last_main_ticks); + last_main_ticks = main_ticks; + + const auto rssi_ticks = thread_rssi->total_ticks; + statistics.rssi_ticks = (rssi_ticks - last_rssi_ticks); + last_rssi_ticks = rssi_ticks; + + const auto baseband_ticks = thread_baseband->total_ticks; + statistics.baseband_ticks = (baseband_ticks - last_baseband_ticks); + last_baseband_ticks = baseband_ticks; + + statistics.saturation = lpc43xx::m4::flag_saturation(); + lpc43xx::m4::clear_flag_saturation(); + + samples_last_report = samples; + + return statistics; +} diff --git a/firmware/baseband/baseband_stats_collector.hpp b/firmware/baseband/baseband_stats_collector.hpp index a44c2936..b628a8b2 100644 --- a/firmware/baseband/baseband_stats_collector.hpp +++ b/firmware/baseband/baseband_stats_collector.hpp @@ -26,7 +26,6 @@ #include "dsp_types.hpp" #include "message.hpp" -#include "utility_m4.hpp" #include #include @@ -46,36 +45,9 @@ public: } template - void process(buffer_c8_t buffer, Callback callback) { - samples += buffer.count; - - const size_t report_samples = buffer.sampling_rate * report_interval; - const auto report_delta = samples - samples_last_report; - if( report_delta >= report_samples ) { - BasebandStatistics statistics; - - const auto idle_ticks = thread_idle->total_ticks; - statistics.idle_ticks = (idle_ticks - last_idle_ticks); - last_idle_ticks = idle_ticks; - - const auto main_ticks = thread_main->total_ticks; - statistics.main_ticks = (main_ticks - last_main_ticks); - last_main_ticks = main_ticks; - - const auto rssi_ticks = thread_rssi->total_ticks; - statistics.rssi_ticks = (rssi_ticks - last_rssi_ticks); - last_rssi_ticks = rssi_ticks; - - const auto baseband_ticks = thread_baseband->total_ticks; - statistics.baseband_ticks = (baseband_ticks - last_baseband_ticks); - last_baseband_ticks = baseband_ticks; - - statistics.saturation = m4_flag_saturation(); - clear_m4_flag_saturation(); - - callback(statistics); - - samples_last_report = samples; + void process(const buffer_c8_t& buffer, Callback callback) { + if( process(buffer) ) { + callback(capture_statistics()); } } @@ -91,6 +63,9 @@ private: uint32_t last_rssi_ticks { 0 }; const Thread* const thread_baseband; uint32_t last_baseband_ticks { 0 }; + + bool process(const buffer_c8_t& buffer); + BasebandStatistics capture_statistics(); }; #endif/*__BASEBAND_STATS_COLLECTOR_H__*/ diff --git a/firmware/baseband/baseband_thread.cpp b/firmware/baseband/baseband_thread.cpp new file mode 100644 index 00000000..92b968f5 --- /dev/null +++ b/firmware/baseband/baseband_thread.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2014 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 "baseband_thread.hpp" + +#include "dsp_types.hpp" + +#include "baseband.hpp" +#include "baseband_stats_collector.hpp" +#include "baseband_sgpio.hpp" +#include "baseband_dma.hpp" + +#include "rssi.hpp" +#include "i2s.hpp" + +#include "proc_am_audio.hpp" +#include "proc_nfm_audio.hpp" +#include "proc_wfm_audio.hpp" +#include "proc_ais.hpp" +#include "proc_wideband_spectrum.hpp" +#include "proc_tpms.hpp" +#include "proc_ert.hpp" + +#include "portapack_shared_memory.hpp" + +#include + +static baseband::SGPIO baseband_sgpio; + +WORKING_AREA(baseband_thread_wa, 4096); + +Thread* BasebandThread::start(const tprio_t priority) { + return chThdCreateStatic(baseband_thread_wa, sizeof(baseband_thread_wa), + priority, ThreadBase::fn, + this + ); +} + +void BasebandThread::set_configuration(const BasebandConfiguration& new_configuration) { + if( new_configuration.mode != baseband_configuration.mode ) { + disable(); + + // TODO: Timing problem around disabling DMA and nulling and deleting old processor + auto old_p = baseband_processor; + baseband_processor = nullptr; + delete old_p; + + baseband_processor = create_processor(new_configuration.mode); + + enable(); + } + + baseband_configuration = new_configuration; +} + +void BasebandThread::on_message(const Message* const message) { + if( message->id == Message::ID::BasebandConfiguration ) { + set_configuration(reinterpret_cast(message)->configuration); + } else { + if( baseband_processor ) { + baseband_processor->on_message(message); + } + } +} + +void BasebandThread::run() { + baseband_sgpio.init(); + baseband::dma::init(); + + const auto baseband_buffer = new std::array(); + baseband::dma::configure( + baseband_buffer->data(), + direction() + ); + //baseband::dma::allocate(4, 2048); + + BasebandStatsCollector stats { + chSysGetIdleThread(), + thread_main, + thread_rssi, + chThdSelf() + }; + + while(true) { + // TODO: Place correct sampling rate into buffer returned here: + const auto buffer_tmp = baseband::dma::wait_for_rx_buffer(); + if( buffer_tmp ) { + buffer_c8_t buffer { + buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate + }; + + if( baseband_processor ) { + baseband_processor->execute(buffer); + } + + stats.process(buffer, + [](const BasebandStatistics& statistics) { + const BasebandStatisticsMessage message { statistics }; + shared_memory.application_queue.push(message); + } + ); + } + } + + delete baseband_buffer; +} + +BasebandProcessor* BasebandThread::create_processor(const int32_t mode) { + switch(mode) { + case 0: return new NarrowbandAMAudio(); + case 1: return new NarrowbandFMAudio(); + case 2: return new WidebandFMAudio(); + case 3: return new AISProcessor(); + case 4: return new WidebandSpectrum(); + case 5: return new TPMSProcessor(); + case 6: return new ERTProcessor(); + default: return nullptr; + } +} + +void BasebandThread::disable() { + if( baseband_processor ) { + i2s::i2s0::tx_mute(); + baseband::dma::disable(); + baseband_sgpio.streaming_disable(); + rf::rssi::stop(); + } +} + +void BasebandThread::enable() { + if( baseband_processor ) { + if( direction() == baseband::Direction::Receive ) { + rf::rssi::start(); + } + baseband_sgpio.configure(direction()); + baseband::dma::enable(direction()); + baseband_sgpio.streaming_enable(); + } +} diff --git a/firmware/baseband/baseband_thread.hpp b/firmware/baseband/baseband_thread.hpp new file mode 100644 index 00000000..81d35433 --- /dev/null +++ b/firmware/baseband/baseband_thread.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2014 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 __BASEBAND_THREAD_H__ +#define __BASEBAND_THREAD_H__ + +#include "thread_base.hpp" +#include "message.hpp" +#include "baseband_processor.hpp" + +#include + +class BasebandThread : public ThreadBase { +public: + BasebandThread( + ) : ThreadBase { "baseband" } + { + } + + Thread* start(const tprio_t priority); + + void on_message(const Message* const message); + + // This getter should die, it's just here to leak information to code that + // isn't in the right place to begin with. + baseband::Direction direction() const { + return baseband::Direction::Receive; + } + + Thread* thread_main { nullptr }; + Thread* thread_rssi { nullptr }; + BasebandProcessor* baseband_processor { nullptr }; + +private: + BasebandConfiguration baseband_configuration; + + void run() override; + + BasebandProcessor* create_processor(const int32_t mode); + + void disable(); + void enable(); + + void set_configuration(const BasebandConfiguration& new_configuration); +}; + +#endif/*__BASEBAND_THREAD_H__*/ diff --git a/firmware/baseband/block_decimator.hpp b/firmware/baseband/block_decimator.hpp index dbca0ebd..1f7710ef 100644 --- a/firmware/baseband/block_decimator.hpp +++ b/firmware/baseband/block_decimator.hpp @@ -65,7 +65,7 @@ public: } template - void feed(const buffer_c16_t src, BlockCallback callback) { + void feed(const buffer_c16_t& src, BlockCallback callback) { /* NOTE: Input block size must be >= factor */ set_input_sampling_rate(src.sampling_rate); diff --git a/firmware/baseband/channel_decimator.cpp b/firmware/baseband/channel_decimator.cpp index a7e7edf7..c8a7cf5c 100644 --- a/firmware/baseband/channel_decimator.cpp +++ b/firmware/baseband/channel_decimator.cpp @@ -21,7 +21,7 @@ #include "channel_decimator.hpp" -buffer_c16_t ChannelDecimator::execute_decimation(buffer_c8_t buffer) { +buffer_c16_t ChannelDecimator::execute_decimation(const buffer_c8_t& buffer) { const buffer_c16_t work_baseband_buffer { work_baseband.data(), work_baseband.size() @@ -39,19 +39,15 @@ buffer_c16_t ChannelDecimator::execute_decimation(buffer_c8_t buffer) { * -> gain of 256 * -> decimation by 2 * -> 1.544MHz complex[1024], [-32768, 32512] */ - const auto stage_0_out = translate.execute(buffer, work_baseband_buffer); - - //if( fs_over_4_downconvert ) { - // // TODO: - //} else { - // Won't work until cic_0 will accept input type of buffer_c8_t. - // stage_0_out = cic_0.execute(buffer, work_baseband_buffer); - //} + auto stage_0_out = execute_stage_0(buffer, work_baseband_buffer); + if( decimation_factor == DecimationFactor::By2 ) { + return stage_0_out; + } /* 1.536MHz complex[1024], [-32768, 32512] * -> 3rd order CIC: -0.1dB @ 0.028fs, -1dB @ 0.088fs, -60dB @ 0.468fs * -0.1dB @ 43kHz, -1dB @ 136kHz, -60dB @ 723kHz - * -> gain of 8 + * -> gain of 1 * -> decimation by 2 * -> 768kHz complex[512], [-8192, 8128] */ auto cic_1_out = cic_1.execute(stage_0_out, work_baseband_buffer); @@ -82,3 +78,14 @@ buffer_c16_t ChannelDecimator::execute_decimation(buffer_c8_t buffer) { return cic_4_out; } + +buffer_c16_t ChannelDecimator::execute_stage_0( + const buffer_c8_t& buffer, + const buffer_c16_t& work_baseband_buffer +) { + if( fs_over_4_downconvert ) { + return translate.execute(buffer, work_baseband_buffer); + } else { + return cic_0.execute(buffer, work_baseband_buffer); + } +} diff --git a/firmware/baseband/channel_decimator.hpp b/firmware/baseband/channel_decimator.hpp index 3e20c0df..956964b0 100644 --- a/firmware/baseband/channel_decimator.hpp +++ b/firmware/baseband/channel_decimator.hpp @@ -32,6 +32,7 @@ class ChannelDecimator { public: enum class DecimationFactor { + By2, By4, By8, By16, @@ -39,13 +40,16 @@ public: }; constexpr ChannelDecimator( - ) : decimation_factor { DecimationFactor::By32 } + ) : decimation_factor { DecimationFactor::By32 }, + fs_over_4_downconvert { true } { } constexpr ChannelDecimator( - const DecimationFactor decimation_factor - ) : decimation_factor { decimation_factor } + const DecimationFactor decimation_factor, + const bool fs_over_4_downconvert = true + ) : decimation_factor { decimation_factor }, + fs_over_4_downconvert { fs_over_4_downconvert } { } @@ -53,7 +57,7 @@ public: decimation_factor = f; } - buffer_c16_t execute(buffer_c8_t buffer) { + buffer_c16_t execute(const buffer_c8_t& buffer) { auto decimated = execute_decimation(buffer); return decimated; @@ -62,18 +66,22 @@ public: private: std::array work_baseband; - //const bool fs_over_4_downconvert = true; - dsp::decimate::TranslateByFSOver4AndDecimateBy2CIC3 translate; - //dsp::decimate::DecimateBy2CIC3 cic_0; + dsp::decimate::Complex8DecimateBy2CIC3 cic_0; dsp::decimate::DecimateBy2CIC3 cic_1; dsp::decimate::DecimateBy2CIC3 cic_2; dsp::decimate::DecimateBy2CIC3 cic_3; dsp::decimate::DecimateBy2CIC3 cic_4; DecimationFactor decimation_factor; + const bool fs_over_4_downconvert; - buffer_c16_t execute_decimation(buffer_c8_t buffer); + buffer_c16_t execute_decimation(const buffer_c8_t& buffer); + + buffer_c16_t execute_stage_0( + const buffer_c8_t& buffer, + const buffer_c16_t& work_baseband_buffer + ); }; #endif/*__CHANNEL_DECIMATOR_H__*/ diff --git a/firmware/baseband/channel_stats_collector.hpp b/firmware/baseband/channel_stats_collector.hpp index 8c91ac5b..d03f03a1 100644 --- a/firmware/baseband/channel_stats_collector.hpp +++ b/firmware/baseband/channel_stats_collector.hpp @@ -34,7 +34,7 @@ class ChannelStatsCollector { public: template - void feed(buffer_c16_t src, Callback callback) { + void feed(const buffer_c16_t& src, Callback callback) { auto src_p = src.p; while(src_p < &src.p[src.count]) { const uint32_t sample = *__SIMD32(src_p)++; diff --git a/firmware/baseband/dsp_decimate.cpp b/firmware/baseband/dsp_decimate.cpp index e8fd1824..5c5ba40e 100644 --- a/firmware/baseband/dsp_decimate.cpp +++ b/firmware/baseband/dsp_decimate.cpp @@ -26,7 +26,461 @@ namespace dsp { namespace decimate { -buffer_c16_t TranslateByFSOver4AndDecimateBy2CIC3::execute(buffer_c8_t src, buffer_c16_t dst) { +static inline complex32_t mac_fs4_shift( + const vec2_s16* const z, + const vec2_s16* const t, + const size_t index, + const complex32_t accum +) { + /* Accumulate sample * tap results for samples already in z buffer. + * Multiply using swap/negation to achieve Fs/4 shift. + * For iterations where samples are shifting out of z buffer (being discarded). + * Expect negated tap t[2] to accomodate instruction set limitations. + */ + const bool negated_t2 = index & 1; + const auto q1_i0 = z[index*2 + 0]; + const auto i1_q0 = z[index*2 + 1]; + const auto t1_t0 = t[index]; + const auto real = negated_t2 ? smlsd(q1_i0, t1_t0, accum.real()) : smlad(q1_i0, t1_t0, accum.real()); + const auto imag = negated_t2 ? smlad(i1_q0, t1_t0, accum.imag()) : smlsd(i1_q0, t1_t0, accum.imag()); + return { real, imag }; +} + +static inline complex32_t mac_shift( + const vec2_s16* const z, + const vec2_s16* const t, + const size_t index, + const complex32_t accum +) { + /* Accumulate sample * tap results for samples already in z buffer. + * For iterations where samples are shifting out of z buffer (being discarded). + * real += i1 * t1 + i0 * t0 + * imag += q1 * t1 + q0 * t0 + */ + const auto i1_i0 = z[index*2 + 0]; + const auto q1_q0 = z[index*2 + 1]; + const auto t1_t0 = t[index]; + const auto real = smlad(i1_i0, t1_t0, accum.real()); + const auto imag = smlad(q1_q0, t1_t0, accum.imag()); + return { real, imag }; +} + +static inline complex32_t mac_fs4_shift_and_store( + vec2_s16* const z, + const vec2_s16* const t, + const size_t decimation_factor, + const size_t index, + const complex32_t accum +) { + /* Accumulate sample * tap results for samples already in z buffer. + * Place new samples into z buffer. + * Expect negated tap t[2] to accomodate instruction set limitations. + */ + const bool negated_t2 = index & 1; + const auto q1_i0 = z[decimation_factor + index*2 + 0]; + const auto i1_q0 = z[decimation_factor + index*2 + 1]; + const auto t1_t0 = t[decimation_factor / 2 + index]; + z[index*2 + 0] = q1_i0; + const auto real = negated_t2 ? smlsd(q1_i0, t1_t0, accum.real()) : smlad(q1_i0, t1_t0, accum.real()); + z[index*2 + 1] = i1_q0; + const auto imag = negated_t2 ? smlad(i1_q0, t1_t0, accum.imag()) : smlsd(i1_q0, t1_t0, accum.imag()); + return { real, imag }; +} + +static inline complex32_t mac_shift_and_store( + vec2_s16* const z, + const vec2_s16* const t, + const size_t decimation_factor, + const size_t index, + const complex32_t accum +) { + /* Accumulate sample * tap results for samples already in z buffer. + * Place new samples into z buffer. + * Expect negated tap t[2] to accomodate instruction set limitations. + */ + const auto i1_i0 = z[decimation_factor + index*2 + 0]; + const auto q1_q0 = z[decimation_factor + index*2 + 1]; + const auto t1_t0 = t[decimation_factor / 2 + index]; + z[index*2 + 0] = i1_i0; + const auto real = smlad(i1_i0, t1_t0, accum.real()); + z[index*2 + 1] = q1_q0; + const auto imag = smlad(q1_q0, t1_t0, accum.imag()); + return { real, imag }; +} + +static inline complex32_t mac_fs4_shift_and_store_new_c8_samples( + vec2_s16* const z, + const vec2_s16* const t, + const vec4_s8* const in, + const size_t decimation_factor, + const size_t index, + const size_t length, + const complex32_t accum +) { + /* Accumulate sample * tap results for new samples. + * Place new samples into z buffer. + * Expect negated tap t[2] to accomodate instruction set limitations. + */ + const bool negated_t2 = index & 1; + const auto q1_i1_q0_i0 = in[index]; + const auto t1_t0 = t[(length - decimation_factor) / 2 + index]; + const auto i1_q1_i0_q0 = rev16(q1_i1_q0_i0); + const auto i1_q1_q0_i0 = pkhbt(q1_i1_q0_i0, i1_q1_i0_q0); + const auto q1_i0 = sxtb16(i1_q1_q0_i0); + const auto i1_q0 = sxtb16(i1_q1_q0_i0, 8); + z[length - decimation_factor * 2 + index*2 + 0] = q1_i0; + const auto real = negated_t2 ? smlsd(q1_i0, t1_t0, accum.real()) : smlad(q1_i0, t1_t0, accum.real()); + z[length - decimation_factor * 2 + index*2 + 1] = i1_q0; + const auto imag = negated_t2 ? smlad(i1_q0, t1_t0, accum.imag()) : smlsd(i1_q0, t1_t0, accum.imag()); + return { real, imag }; +} + +static inline complex32_t mac_shift_and_store_new_c16_samples( + vec2_s16* const z, + const vec2_s16* const t, + const vec2_s16* const in, + const size_t decimation_factor, + const size_t index, + const size_t length, + const complex32_t accum +) { + /* Accumulate sample * tap results for new samples. + * Place new samples into z buffer. + * Expect negated tap t[2] to accomodate instruction set limitations. + */ + const auto q0_i0 = in[index*2+0]; + const auto q1_i1 = in[index*2+1]; + const auto i1_i0 = pkhbt(q0_i0, q1_i1, 16); + const auto q1_q0 = pkhtb(q1_i1, q0_i0, 16); + const auto t1_t0 = t[(length - decimation_factor) / 2 + index]; + z[length - decimation_factor * 2 + index*2 + 0] = i1_i0; + const auto real = smlad(i1_i0, t1_t0, accum.real()); + z[length - decimation_factor * 2 + index*2 + 1] = q1_q0; + const auto imag = smlad(q1_q0, t1_t0, accum.imag()); + return { real, imag }; +} + +static inline uint32_t scale_round_and_pack( + const complex32_t value, + const int32_t scale_factor +) { + /* Multiply 32-bit components of the complex by a scale factor, + * into int64_ts, then round to nearest LSB (1 << 32), saturate to 16 bits, + * and pack into a complex. + */ + const auto scaled_real = __SMMULR(value.real(), scale_factor); + const auto saturated_real = __SSAT(scaled_real, 16); + + const auto scaled_imag = __SMMULR(value.imag(), scale_factor); + const auto saturated_imag = __SSAT(scaled_imag, 16); + + return __PKHBT(saturated_real, saturated_imag, 16); +} + +// FIRC8xR16x24FS4Decim4 ////////////////////////////////////////////////// + +FIRC8xR16x24FS4Decim4::FIRC8xR16x24FS4Decim4() { + z_.fill({}); +} + +void FIRC8xR16x24FS4Decim4::configure( + const std::array& taps, + const int32_t scale, + const Shift shift +) { + const int negate_factor = (shift == Shift::Up) ? -1 : 1; + for(size_t i=0; i(__builtin_assume_aligned(z_.data(), 4)); + const vec2_s16* const t = static_cast(__builtin_assume_aligned(taps_.data(), 4)); + uint32_t* const d = static_cast(__builtin_assume_aligned(dst.p, 4)); + + const auto k = output_scale; + + const size_t count = src.count / decimation_factor; + for(size_t i=0; i(__builtin_assume_aligned(&src.p[i * decimation_factor], 4)); + + complex32_t accum; + + // Oldest samples are discarded. + accum = mac_fs4_shift(z, t, 0, accum); + accum = mac_fs4_shift(z, t, 1, accum); + + // Middle samples are shifted earlier in the "z" delay buffer. + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 0, accum); + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 1, accum); + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 2, accum); + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 3, accum); + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 4, accum); + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 5, accum); + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 6, accum); + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 7, accum); + + // Newest samples come from "in" buffer, are copied to "z" delay buffer. + accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 0, taps_count, accum); + accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 1, taps_count, accum); + + d[i] = scale_round_and_pack(accum, k); + } + + return { + dst.p, + count, + src.sampling_rate / decimation_factor + }; +} + +// FIRC8xR16x24FS4Decim8 ////////////////////////////////////////////////// + +FIRC8xR16x24FS4Decim8::FIRC8xR16x24FS4Decim8() { + z_.fill({}); +} + +void FIRC8xR16x24FS4Decim8::configure( + const std::array& taps, + const int32_t scale, + const Shift shift +) { + const int negate_factor = (shift == Shift::Up) ? -1 : 1; + for(size_t i=0; i(__builtin_assume_aligned(z_.data(), 4)); + const vec2_s16* const t = static_cast(__builtin_assume_aligned(taps_.data(), 4)); + uint32_t* const d = static_cast(__builtin_assume_aligned(dst.p, 4)); + + const auto k = output_scale; + + const size_t count = src.count / decimation_factor; + for(size_t i=0; i(__builtin_assume_aligned(&src.p[i * decimation_factor], 4)); + + complex32_t accum; + + // Oldest samples are discarded. + accum = mac_fs4_shift(z, t, 0, accum); + accum = mac_fs4_shift(z, t, 1, accum); + accum = mac_fs4_shift(z, t, 2, accum); + accum = mac_fs4_shift(z, t, 3, accum); + + // Middle samples are shifted earlier in the "z" delay buffer. + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 0, accum); + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 1, accum); + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 2, accum); + accum = mac_fs4_shift_and_store(z, t, decimation_factor, 3, accum); + + // Newest samples come from "in" buffer, are copied to "z" delay buffer. + accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 0, taps_count, accum); + accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 1, taps_count, accum); + accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 2, taps_count, accum); + accum = mac_fs4_shift_and_store_new_c8_samples(z, t, in, decimation_factor, 3, taps_count, accum); + + d[i] = scale_round_and_pack(accum, k); + } + + return { + dst.p, + count, + src.sampling_rate / decimation_factor + }; +} + +// FIRC16xR16x16Decim2 //////////////////////////////////////////////////// + +FIRC16xR16x16Decim2::FIRC16xR16x16Decim2() { + z_.fill({}); +} + +void FIRC16xR16x16Decim2::configure( + const std::array& taps, + const int32_t scale +) { + std::copy(taps.cbegin(), taps.cend(), taps_.begin()); + output_scale = scale; +} + +buffer_c16_t FIRC16xR16x16Decim2::execute( + const buffer_c16_t& src, + const buffer_c16_t& dst +) { + vec2_s16* const z = static_cast(__builtin_assume_aligned(z_.data(), 4)); + const vec2_s16* const t = static_cast(__builtin_assume_aligned(taps_.data(), 4)); + uint32_t* const d = static_cast(__builtin_assume_aligned(dst.p, 4)); + + const auto k = output_scale; + + const size_t count = src.count / decimation_factor; + for(size_t i=0; i(__builtin_assume_aligned(&src.p[i * decimation_factor], 4)); + + complex32_t accum; + + // Oldest samples are discarded. + accum = mac_shift(z, t, 0, accum); + + // Middle samples are shifted earlier in the "z" delay buffer. + accum = mac_shift_and_store(z, t, decimation_factor, 0, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 1, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 2, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 3, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 4, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 5, accum); + + // Newest samples come from "in" buffer, are copied to "z" delay buffer. + accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 0, taps_count, accum); + + d[i] = scale_round_and_pack(accum, k); + } + + return { + dst.p, + count, + src.sampling_rate / decimation_factor + }; +} + +// FIRC16xR16x32Decim8 //////////////////////////////////////////////////// + +FIRC16xR16x32Decim8::FIRC16xR16x32Decim8() { + z_.fill({}); +} + +void FIRC16xR16x32Decim8::configure( + const std::array& taps, + const int32_t scale +) { + std::copy(taps.cbegin(), taps.cend(), taps_.begin()); + output_scale = scale; +} + +buffer_c16_t FIRC16xR16x32Decim8::execute( + const buffer_c16_t& src, + const buffer_c16_t& dst +) { + vec2_s16* const z = static_cast(__builtin_assume_aligned(z_.data(), 4)); + const vec2_s16* const t = static_cast(__builtin_assume_aligned(taps_.data(), 4)); + uint32_t* const d = static_cast(__builtin_assume_aligned(dst.p, 4)); + + const auto k = output_scale; + + const size_t count = src.count / decimation_factor; + for(size_t i=0; i(__builtin_assume_aligned(&src.p[i * decimation_factor], 4)); + + complex32_t accum; + + // Oldest samples are discarded. + accum = mac_shift(z, t, 0, accum); + accum = mac_shift(z, t, 1, accum); + accum = mac_shift(z, t, 2, accum); + accum = mac_shift(z, t, 3, accum); + + // Middle samples are shifted earlier in the "z" delay buffer. + accum = mac_shift_and_store(z, t, decimation_factor, 0, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 1, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 2, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 3, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 4, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 5, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 6, accum); + accum = mac_shift_and_store(z, t, decimation_factor, 7, accum); + + // Newest samples come from "in" buffer, are copied to "z" delay buffer. + accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 0, taps_count, accum); + accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 1, taps_count, accum); + accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 2, taps_count, accum); + accum = mac_shift_and_store_new_c16_samples(z, t, in, decimation_factor, 3, taps_count, accum); + + d[i] = scale_round_and_pack(accum, k); + } + + return { + dst.p, + count, + src.sampling_rate / decimation_factor + }; +} + +buffer_c16_t Complex8DecimateBy2CIC3::execute(const buffer_c8_t& src, const buffer_c16_t& dst) { + /* Decimates by two using a non-recursive third-order CIC filter. + */ + + /* CIC filter (decimating by two): + * D_I0 = i3 * 1 + i2 * 3 + i1 * 3 + i0 * 1 + * D_Q0 = q3 * 1 + q2 * 3 + q1 * 3 + q0 * 1 + * + * D_I1 = i5 * 1 + i4 * 3 + i3 * 3 + i2 * 1 + * D_Q1 = q5 * 1 + q4 * 3 + q3 * 3 + q2 * 1 + */ + + uint32_t i1_i0 = _i1_i0; + uint32_t q1_q0 = _q1_q0; + + /* 3:1 Scaled by 32 to normalize output to +/-32768-ish. */ + constexpr uint32_t scale_factor = 32; + constexpr uint32_t k_3_1 = 0x00030001 * scale_factor; + uint32_t* src_p = reinterpret_cast(&src.p[0]); + uint32_t* const src_end = reinterpret_cast(&src.p[src.count]); + uint32_t* dst_p = reinterpret_cast(&dst.p[0]); + while(src_p < src_end) { + const uint32_t q3_i3_q2_i2 = *(src_p++); // 3 + const uint32_t q5_i5_q4_i4 = *(src_p++); + + const uint32_t d_i0_partial = __SMUAD(k_3_1, i1_i0); // 1: = 3 * i1 + 1 * i0 + const uint32_t i3_i2 = __SXTB16(q3_i3_q2_i2, 0); // 1: (q3_i3_q2_i2 ror 0)[23:16]:(q3_i3_q2_i2 ror 0)[7:0] + const uint32_t d_i0 = __SMLADX(k_3_1, i3_i2, d_i0_partial); // 1: + 3 * i2 + 1 * i3 + + const uint32_t d_q0_partial = __SMUAD(k_3_1, q1_q0); // 1: = 3 * q1 * 1 * q0 + const uint32_t q3_q2 = __SXTB16(q3_i3_q2_i2, 8); // 1: (q3_i3_q2_i2 ror 8)[23:16]:(q3_i3_q2_i2 ror 8)[7:0] + const uint32_t d_q0 = __SMLADX(k_3_1, q3_q2, d_q0_partial); // 1: + 3 * q2 + 1 * q3 + + const uint32_t d_q0_i0 = __PKHBT(d_i0, d_q0, 16); // 1: (Rm<<16)[31:16]:Rn[15:0] + + const uint32_t d_i1_partial = __SMUAD(k_3_1, i3_i2); // 1: = 3 * i3 + 1 * i2 + const uint32_t i5_i4 = __SXTB16(q5_i5_q4_i4, 0); // 1: (q5_i5_q4_i4 ror 0)[23:16]:(q5_i5_q4_i4 ror 0)[7:0] + const uint32_t d_i1 = __SMLADX(k_3_1, i5_i4, d_i1_partial); // 1: + 1 * i5 + 3 * i4 + + const uint32_t d_q1_partial = __SMUAD(k_3_1, q3_q2); // 1: = 3 * q3 * 1 * q2 + const uint32_t q5_q4 = __SXTB16(q5_i5_q4_i4, 8); // 1: (q5_i5_q4_i4 ror 8)[23:16]:(q5_i5_q4_i4 ror 8)[7:0] + const uint32_t d_q1 = __SMLADX(k_3_1, q5_q4, d_q1_partial); // 1: + 1 * q5 + 3 * q4 + + const uint32_t d_q1_i1 = __PKHBT(d_i1, d_q1, 16); // 1: (Rm<<16)[31:16]:Rn[15:0] + + *(dst_p++) = d_q0_i0; // 3 + *(dst_p++) = d_q1_i1; + + i1_i0 = i5_i4; + q1_q0 = q5_q4; + } + _i1_i0 = i1_i0; + _q1_q0 = q1_q0; + + return { dst.p, src.count / 2, src.sampling_rate / 2 }; +} + +buffer_c16_t TranslateByFSOver4AndDecimateBy2CIC3::execute(const buffer_c8_t& src, const buffer_c16_t& dst) { /* Translates incoming complex samples by -fs/4, * decimates by two using a non-recursive third-order CIC filter. */ @@ -111,8 +565,8 @@ buffer_c16_t TranslateByFSOver4AndDecimateBy2CIC3::execute(buffer_c8_t src, buff } buffer_c16_t DecimateBy2CIC3::execute( - buffer_c16_t src, - buffer_c16_t dst + const buffer_c16_t& src, + const buffer_c16_t& dst ) { /* Complex non-recursive 3rd-order CIC filter (taps 1,3,3,1). * Gain of 8. @@ -164,9 +618,15 @@ buffer_c16_t DecimateBy2CIC3::execute( return { dst.p, src.count / 2, src.sampling_rate / 2 }; } +void FIR64AndDecimateBy2Real::configure( + const std::array& new_taps +) { + std::copy(new_taps.cbegin(), new_taps.cend(), taps.begin()); +} + buffer_s16_t FIR64AndDecimateBy2Real::execute( - buffer_s16_t src, - buffer_s16_t dst + const buffer_s16_t& src, + const buffer_s16_t& dst ) { /* int16_t input (sample count "n" must be multiple of 4) * -> int16_t output, decimated by 2. @@ -197,9 +657,21 @@ buffer_s16_t FIR64AndDecimateBy2Real::execute( return { dst.p, src.count / 2, src.sampling_rate / 2 }; } +void FIRAndDecimateComplex::configure( + const int16_t* const taps, + const size_t taps_count, + const size_t decimation_factor +) { + samples_ = std::make_unique(taps_count); + taps_reversed_ = std::make_unique(taps_count); + taps_count_ = taps_count; + decimation_factor_ = decimation_factor; + std::reverse_copy(&taps[0], &taps[taps_count], &taps_reversed_[0]); +} + buffer_c16_t FIRAndDecimateComplex::execute( - buffer_c16_t src, - buffer_c16_t dst + const buffer_c16_t& src, + const buffer_c16_t& dst ) { /* int16_t input (sample count "n" must be multiple of decimation_factor) * -> int16_t output, decimated by decimation_factor. @@ -308,8 +780,8 @@ buffer_c16_t FIRAndDecimateComplex::execute( } buffer_s16_t DecimateBy2CIC4Real::execute( - buffer_s16_t src, - buffer_s16_t dst + const buffer_s16_t& src, + const buffer_s16_t& dst ) { auto src_p = src.p; auto dst_p = dst.p; @@ -328,76 +800,6 @@ buffer_s16_t DecimateBy2CIC4Real::execute( return { dst.p, src.count / 2, src.sampling_rate / 2 }; } -#if 0 -buffer_c16_t DecimateBy2HBF5Complex::execute( - buffer_c16_t const src, - buffer_c16_t const dst -) { - auto src_p = src.p; - auto dst_p = dst.p; - int32_t n = src.count; - for(; n>0; n-=2) { - /* TODO: Probably a lot of room to optimize... */ - z[0] = z[2]; - //z[1] = z[3]; - z[2] = z[4]; - //z[3] = z[5]; - z[4] = z[6]; - z[5] = z[7]; - z[6] = z[8]; - z[7] = z[9]; - z[8] = z[10]; - z[9] = *(src_p++); - z[10] = *(src_p++); - int32_t t_real { z[5].real * 256 }; - int32_t t_imag { z[5].imag * 256 }; - t_real += (z[ 0].real + z[10].real) * 3; - t_imag += (z[ 0].imag + z[10].imag) * 3; - t_real -= (z[ 2].real + z[ 8].real) * 25; - t_imag -= (z[ 2].imag + z[ 8].imag) * 25; - t_real += (z[ 4].real + z[ 6].real) * 150; - t_imag += (z[ 4].imag + z[ 6].imag) * 150; - *(dst_p++) = { t_real / 256, t_imag / 256 }; - } - - return { dst.p, src.count / 2, src.sampling_rate / 2 }; -} - -buffer_c16_t DecimateBy2HBF7Complex::execute( - buffer_c16_t const src, - buffer_c16_t const dst -) { - auto src_p = src.p; - auto dst_p = dst.p; - int32_t n = src.count; - for(; n>0; n-=2) { - /* TODO: Probably a lot of room to optimize... */ - z[0] = z[2]; - //z[1] = z[3]; - z[2] = z[4]; - //z[3] = z[5]; - z[4] = z[6]; - z[5] = z[7]; - z[6] = z[8]; - z[7] = z[9]; - z[8] = z[10]; - z[9] = *(src_p++); - z[10] = *(src_p++); - - int32_t t_real { z[5].real * 512 }; - int32_t t_imag { z[5].imag * 512 }; - t_real += (z[ 0].real + z[10].real) * 7; - t_imag += (z[ 0].imag + z[10].imag) * 7; - t_real -= (z[ 2].real + z[ 8].real) * 53; - t_imag -= (z[ 2].imag + z[ 8].imag) * 53; - t_real += (z[ 4].real + z[ 6].real) * 302; - t_imag += (z[ 4].imag + z[ 6].imag) * 302; - *(dst_p++) = { t_real / 512, t_imag / 512 }; - } - - return { dst.p, src.count / 2, src.sampling_rate / 2 }; -} -#endif } /* namespace decimate */ } /* namespace dsp */ diff --git a/firmware/baseband/dsp_decimate.hpp b/firmware/baseband/dsp_decimate.hpp index 9cc90b88..d3f41555 100644 --- a/firmware/baseband/dsp_decimate.hpp +++ b/firmware/baseband/dsp_decimate.hpp @@ -31,14 +31,28 @@ #include "dsp_types.hpp" +#include "simd.hpp" + namespace dsp { namespace decimate { +class Complex8DecimateBy2CIC3 { +public: + buffer_c16_t execute( + const buffer_c8_t& src, + const buffer_c16_t& dst + ); + +private: + uint32_t _i1_i0 { 0 }; + uint32_t _q1_q0 { 0 }; +}; + class TranslateByFSOver4AndDecimateBy2CIC3 { public: buffer_c16_t execute( - buffer_c8_t src, - buffer_c16_t dst + const buffer_c8_t& src, + const buffer_c16_t& dst ); private: @@ -49,8 +63,8 @@ private: class DecimateBy2CIC3 { public: buffer_c16_t execute( - buffer_c16_t src, - buffer_c16_t dst + const buffer_c16_t& src, + const buffer_c16_t& dst ); private: @@ -62,20 +76,134 @@ class FIR64AndDecimateBy2Real { public: static constexpr size_t taps_count = 64; - FIR64AndDecimateBy2Real( + void configure( const std::array& taps - ) : taps(taps) - { - } + ); buffer_s16_t execute( - buffer_s16_t src, - buffer_s16_t dst + const buffer_s16_t& src, + const buffer_s16_t& dst ); private: std::array z; - const std::array& taps; + std::array taps; +}; + +class FIRC8xR16x24FS4Decim4 { +public: + static constexpr size_t taps_count = 24; + static constexpr size_t decimation_factor = 4; + + using sample_t = complex8_t; + using tap_t = int16_t; + + enum class Shift : bool { + Down = true, + Up = false + }; + + FIRC8xR16x24FS4Decim4(); + + void configure( + const std::array& taps, + const int32_t scale, + const Shift shift = Shift::Down + ); + + buffer_c16_t execute( + const buffer_c8_t& src, + const buffer_c16_t& dst + ); + +private: + std::array z_; + std::array taps_; + int32_t output_scale = 0; +}; + +class FIRC8xR16x24FS4Decim8 { +public: + static constexpr size_t taps_count = 24; + static constexpr size_t decimation_factor = 8; + + using sample_t = complex8_t; + using tap_t = int16_t; + + enum class Shift : bool { + Down = true, + Up = false + }; + + FIRC8xR16x24FS4Decim8(); + + void configure( + const std::array& taps, + const int32_t scale, + const Shift shift = Shift::Down + ); + + buffer_c16_t execute( + const buffer_c8_t& src, + const buffer_c16_t& dst + ); + +private: + std::array z_; + std::array taps_; + int32_t output_scale = 0; +}; + +class FIRC16xR16x16Decim2 { +public: + static constexpr size_t taps_count = 16; + static constexpr size_t decimation_factor = 2; + + using sample_t = complex16_t; + using tap_t = int16_t; + + FIRC16xR16x16Decim2(); + + void configure( + const std::array& taps, + const int32_t scale + ); + + buffer_c16_t execute( + const buffer_c16_t& src, + const buffer_c16_t& dst + ); + +private: + std::array z_; + std::array taps_; + int32_t output_scale = 0; +}; + +class FIRC16xR16x32Decim8 { +public: + static constexpr size_t taps_count = 32; + static constexpr size_t decimation_factor = 8; + + using sample_t = complex16_t; + using tap_t = int16_t; + + FIRC16xR16x32Decim8(); + + void configure( + const std::array& taps, + const int32_t scale + ); + + buffer_c16_t execute( + const buffer_c16_t& src, + const buffer_c16_t& dst + ); + +private: + std::array z_; + std::array taps_; + int32_t output_scale = 0; }; class FIRAndDecimateComplex { @@ -99,16 +227,12 @@ public: const T& taps, const size_t decimation_factor ) { - samples_ = std::make_unique(taps.size()); - taps_reversed_ = std::make_unique(taps.size()); - taps_count_ = taps.size(); - decimation_factor_ = decimation_factor; - std::reverse_copy(taps.cbegin(), taps.cend(), &taps_reversed_[0]); + configure(taps.data(), taps.size(), decimation_factor); } buffer_c16_t execute( - buffer_c16_t src, - buffer_c16_t dst + const buffer_c16_t& src, + const buffer_c16_t& dst ); private: @@ -118,124 +242,25 @@ private: std::unique_ptr taps_reversed_; size_t taps_count_; size_t decimation_factor_; + + void configure( + const int16_t* const taps, + const size_t taps_count, + const size_t decimation_factor + ); }; class DecimateBy2CIC4Real { public: buffer_s16_t execute( - buffer_s16_t src, - buffer_s16_t dst + const buffer_s16_t& src, + const buffer_s16_t& dst ); private: int16_t z[5]; }; -#if 0 -class DecimateBy2HBF5Complex { -public: - buffer_c16_t execute( - buffer_c16_t const src, - buffer_c16_t const dst - ); -private: - complex16_t z[11]; -}; - -class DecimateBy2HBF7Complex { -public: - buffer_c16_t execute( - buffer_c16_t const src, - buffer_c16_t const dst - ); - -private: - complex16_t z[11]; -}; -#endif -/* From http://www.dspguru.com/book/export/html/3 - -Here are several basic techniques to fake circular buffers: - -Split the calculation: You can split any FIR calculation into its "pre-wrap" -and "post-wrap" parts. By splitting the calculation into these two parts, you -essentially can do the circular logic only once, rather than once per tap. -(See fir_double_z in FirAlgs.c above.) - -Duplicate the delay line: For a FIR with N taps, use a delay line of size 2N. -Copy each sample to its proper location, as well as at location-plus-N. -Therefore, the FIR calculation's MAC loop can be done on a flat buffer of N -points, starting anywhere within the first set of N points. The second set of -N delayed samples provides the "wrap around" comparable to a true circular -buffer. (See fir_double_z in FirAlgs.c above.) - -Duplicate the coefficients: This is similar to the above, except that the -duplication occurs in terms of the coefficients, not the delay line. -Compared to the previous method, this has a calculation advantage of not -having to store each incoming sample twice, and it also has a memory -advantage when the same coefficient set will be used on multiple delay lines. -(See fir_double_h in FirAlgs.c above.) - -Use block processing: In block processing, you use a delay line which is a -multiple of the number of taps. You therefore only have to move the data -once per block to implement the delay-line mechanism. When the block size -becomes "large", the overhead of a moving the delay line once per block -becomes negligible. -*/ - -#if 0 -template -class FIRAndDecimateBy2Complex { -public: - FIR64AndDecimateBy2Complex( - const std::array& taps - ) : taps { taps } - { - } - - buffer_c16_t execute( - buffer_c16_t const src, - buffer_c16_t const dst - ) { - /* int16_t input (sample count "n" must be multiple of 4) - * -> int16_t output, decimated by 2. - * taps are normalized to 1 << 16 == 1.0. - */ - - return { dst.p, src.count / 2 }; - } - -private: - std::array z; - const std::array& taps; - - complex process_one(const size_t start_offset) { - const auto split = &z[start_offset]; - const auto end = &z[z.size()]; - auto tap = &taps[0]; - - complex t { 0, 0 }; - - auto p = split; - while(p < end) { - const auto t = *(tap++); - const auto c = *(p++); - t.real += c.real * t; - t.imag += c.imag * t; - } - - p = &z[0]; - while(p < split) { - const auto t = *(tap++); - const auto c = *(p++); - t.real += c.real * t; - t.imag += c.imag * t; - } - - return { t.real / 65536, t.imag / 65536 }; - } -}; -#endif } /* namespace decimate */ } /* namespace dsp */ diff --git a/firmware/baseband/dsp_demodulate.cpp b/firmware/baseband/dsp_demodulate.cpp index 4ee6ed68..b6cbc45e 100644 --- a/firmware/baseband/dsp_demodulate.cpp +++ b/firmware/baseband/dsp_demodulate.cpp @@ -30,9 +30,9 @@ namespace dsp { namespace demodulate { -buffer_s16_t AM::execute( - buffer_c16_t src, - buffer_s16_t dst +buffer_f32_t AM::execute( + const buffer_c16_t& src, + const buffer_f32_t& dst ) { /* Intermediate maximum value: 46341 (when input is -32768,-32768). */ /* Normalized to maximum 32767 for int16_t representation. */ @@ -49,15 +49,8 @@ buffer_s16_t AM::execute( const uint32_t sample1 = *__SIMD32(src_p)++; const uint32_t mag_sq0 = __SMUAD(sample0, sample0); const uint32_t mag_sq1 = __SMUAD(sample1, sample1); - const int32_t mag0_int = __builtin_sqrtf(mag_sq0); - const int32_t mag0_sat = __SSAT(mag0_int, 16); - const int32_t mag1_int = __builtin_sqrtf(mag_sq1); - const int32_t mag1_sat = __SSAT(mag1_int, 16); - *__SIMD32(dst_p)++ = __PKHBT( - mag0_sat, - mag1_sat, - 16 - ); + *(dst_p++) = __builtin_sqrtf(mag_sq0); + *(dst_p++) = __builtin_sqrtf(mag_sq1); } return { dst.p, src.count, src.sampling_rate }; @@ -69,17 +62,44 @@ static inline float angle_approx_4deg0(const complex32_t t) { } */ static inline float angle_approx_0deg27(const complex32_t t) { - const auto x = static_cast(t.imag()) / static_cast(t.real()); - return x / (1.0f + 0.28086f * x * x); + if( t.real() ) { + const auto x = static_cast(t.imag()) / static_cast(t.real()); + return x / (1.0f + 0.28086f * x * x); + } else { + return (t.imag() < 0) ? -1.5707963268f : 1.5707963268f; + } } -/* + static inline float angle_precise(const complex32_t t) { return atan2f(t.imag(), t.real()); } -*/ + +buffer_f32_t FM::execute( + const buffer_c16_t& src, + const buffer_f32_t& dst +) { + auto z = z_; + + const auto src_p = src.p; + const auto src_end = &src.p[src.count]; + auto dst_p = dst.p; + while(src_p < src_end) { + const auto s0 = *__SIMD32(src_p)++; + const auto s1 = *__SIMD32(src_p)++; + const auto t0 = multiply_conjugate_s16_s32(s0, z); + const auto t1 = multiply_conjugate_s16_s32(s1, s0); + z = s1; + *(dst_p++) = angle_approx_0deg27(t0) * k; + *(dst_p++) = angle_approx_0deg27(t1) * k; + } + z_ = z; + + return { dst.p, src.count, src.sampling_rate }; +} + buffer_s16_t FM::execute( - buffer_c16_t src, - buffer_s16_t dst + const buffer_c16_t& src, + const buffer_s16_t& dst ) { auto z = z_; diff --git a/firmware/baseband/dsp_demodulate.hpp b/firmware/baseband/dsp_demodulate.hpp index c5f871e1..c93d3b19 100644 --- a/firmware/baseband/dsp_demodulate.hpp +++ b/firmware/baseband/dsp_demodulate.hpp @@ -29,39 +29,36 @@ namespace demodulate { class AM { public: - buffer_s16_t execute( - buffer_c16_t src, - buffer_s16_t dst + buffer_f32_t execute( + const buffer_c16_t& src, + const buffer_f32_t& dst ); }; class FM { public: - /* - * angle: -pi to pi. output range: -32768 to 32767. - * Maximum delta-theta (output of atan2) at maximum deviation frequency: - * delta_theta_max = 2 * pi * deviation / sampling_rate - */ - constexpr FM( - const float sampling_rate, - const float deviation_hz - ) : z_ { 0 }, - k { static_cast(32767.0f / (2.0 * pi * deviation_hz / sampling_rate)) } - { - } + buffer_f32_t execute( + const buffer_c16_t& src, + const buffer_f32_t& dst + ); buffer_s16_t execute( - buffer_c16_t src, - buffer_s16_t dst + const buffer_c16_t& src, + const buffer_s16_t& dst ); void configure(const float sampling_rate, const float deviation_hz) { + /* + * angle: -pi to pi. output range: -32768 to 32767. + * Maximum delta-theta (output of atan2) at maximum deviation frequency: + * delta_theta_max = 2 * pi * deviation / sampling_rate + */ k = static_cast(32767.0f / (2.0 * pi * deviation_hz / sampling_rate)); } private: - complex16_t::rep_type z_; - float k; + complex16_t::rep_type z_ { 0 }; + float k { 0 }; }; } /* namespace demodulate */ diff --git a/firmware/baseband/dsp_fir_taps.hpp b/firmware/baseband/dsp_fir_taps.hpp deleted file mode 100644 index 9bf5b5f3..00000000 --- a/firmware/baseband/dsp_fir_taps.hpp +++ /dev/null @@ -1,278 +0,0 @@ -/* - * 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 __DSP_FIR_TAPS_H__ -#define __DSP_FIR_TAPS_H__ - -#include -#include - -#include "complex.hpp" - -template -struct fir_taps_real { - float pass_frequency_normalized; - float stop_frequency_normalized; - std::array taps; -}; - -/* 3kHz/6.7kHz @ 96kHz. sum(abs(taps)): 89429 */ -constexpr fir_taps_real<64> taps_64_lp_031_070_tfilter { - .pass_frequency_normalized = 0.031f, - .stop_frequency_normalized = 0.070f, - .taps = { { - 56, 58, 81, 100, 113, 112, 92, 49, - -21, -120, -244, -389, -543, -692, -819, -903, - -923, -861, -698, -424, -34, 469, 1073, 1756, - 2492, 3243, 3972, 4639, 5204, 5634, 5903, 5995, - 5903, 5634, 5204, 4639, 3972, 3243, 2492, 1756, - 1073, 469, -34, -424, -698, -861, -923, -903, - -819, -692, -543, -389, -244, -120, -21, 49, - 92, 112, 113, 100, 81, 58, 56, 0, - } }, -}; - -/* 4kHz/7.5kHz @ 96kHz. sum(abs(taps)): 96783 */ -constexpr fir_taps_real<64> taps_64_lp_042_078_tfilter { - .pass_frequency_normalized = 0.042f, - .stop_frequency_normalized = 0.078f, - .taps = { { - -19, 39, 72, 126, 197, 278, 360, 432, - 478, 485, 438, 327, 152, -82, -359, -651, - -922, -1132, -1236, -1192, -968, -545, 81, 892, - 1852, 2906, 3984, 5012, 5910, 6609, 7053, 7205, - 7053, 6609, 5910, 5012, 3984, 2906, 1852, 892, - 81, -545, -968, -1192, -1236, -1132, -922, -651, - -359, -82, 152, 327, 438, 485, 478, 432, - 360, 278, 197, 126, 72, 39, -19, 0, - } }, -}; - -/* 5kHz/8.5kHz @ 96kHz. sum(abs(taps)): 101312 */ -constexpr fir_taps_real<64> taps_64_lp_052_089_tfilter { - .pass_frequency_normalized = 0.052f, - .stop_frequency_normalized = 0.089f, - .taps = { { - -65, -88, -129, -163, -178, -160, -100, 9, - 160, 340, 523, 675, 758, 738, 591, 313, - -76, -533, -987, -1355, -1544, -1472, -1077, -335, - 738, 2078, 3579, 5104, 6502, 7627, 8355, 8608, - 8355, 7627, 6502, 5104, 3579, 2078, 738, -335, - -1077, -1472, -1544, -1355, -987, -533, -76, 313, - 591, 738, 758, 675, 523, 340, 160, 9, - -100, -160, -178, -163, -129, -88, -65, 0, - } }, -}; - -/* 6kHz/9.6kHz @ 96kHz. sum(abs(taps)): 105088 */ -constexpr fir_taps_real<64> taps_64_lp_063_100_tfilter { - .pass_frequency_normalized = 0.063f, - .stop_frequency_normalized = 0.100f, - .taps = { { - 43, 21, -2, -54, -138, -245, -360, -453, - -493, -451, -309, -73, 227, 535, 776, 876, - 773, 443, -86, -730, -1357, -1801, -1898, -1515, - -585, 869, 2729, 4794, 6805, 8490, 9611, 10004, - 9611, 8490, 6805, 4794, 2729, 869, -585, -1515, - -1898, -1801, -1357, -730, -86, 443, 773, 876, - 776, 535, 227, -73, -309, -451, -493, -453, - -360, -245, -138, -54, -2, 21, 43, 0, - } }, -}; - -/* 7kHz/10.4kHz @ 96kHz: sum(abs(taps)): 110157 */ -constexpr fir_taps_real<64> taps_64_lp_073_108_tfilter { - .pass_frequency_normalized = 0.073f, - .stop_frequency_normalized = 0.108f, - .taps = { { - 79, 145, 241, 334, 396, 394, 306, 130, - -109, -360, -550, -611, -494, -197, 229, 677, - 1011, 1096, 846, 257, -570, -1436, -2078, -2225, - -1670, -327, 1726, 4245, 6861, 9146, 10704, 11257, - 10704, 9146, 6861, 4245, 1726, -327, -1670, -2225, - -2078, -1436, -570, 257, 846, 1096, 1011, 677, - 229, -197, -494, -611, -550, -360, -109, 130, - 306, 394, 396, 334, 241, 145, 79, 0, - } }, -}; - -/* 8kHz/11.5kHz @ 96kHz. sum(abs(taps)): 112092 */ -constexpr fir_taps_real<64> taps_64_lp_083_120_tfilter { - .pass_frequency_normalized = 0.083f, - .stop_frequency_normalized = 0.120f, - .taps = { { - -63, -72, -71, -21, 89, 248, 417, 537, - 548, 407, 124, -237, -563, -723, -621, -238, - 337, 919, 1274, 1201, 617, -382, -1514, -2364, - -2499, -1600, 414, 3328, 6651, 9727, 11899, 12682, - 11899, 9727, 6651, 3328, 414, -1600, -2499, -2364, - -1514, -382, 617, 1201, 1274, 919, 337, -238, - -621, -723, -563, -237, 124, 407, 548, 537, - 417, 248, 89, -21, -71, -72, -63, 0, - } }, -}; - -/* 9kHz/12.4kHz @ 96kHz. sum(abs(taps)): 116249 */ -constexpr fir_taps_real<64> taps_64_lp_094_129_tfilter { - .pass_frequency_normalized = 0.094f, - .stop_frequency_normalized = 0.129f, - .taps = { { - 5, -93, -198, -335, -449, -478, -378, -144, - 166, 444, 563, 440, 82, -395, -788, -892, - -589, 73, 859, 1421, 1431, 734, -530, -1919, - -2798, -2555, -837, 2274, 6220, 10103, 12941, 13981, - 12941, 10103, 6220, 2274, -837, -2555, -2798, -1919, - -530, 734, 1431, 1421, 859, 73, -589, -892, - -788, -395, 82, 440, 563, 444, 166, -144, - -378, -478, -449, -335, -198, -93, 5, 0, - } }, -}; - -/* 10kHz/13.4kHz @ 96kHz. sum(abs(taps)): 118511 */ -constexpr fir_taps_real<64> taps_64_lp_104_140_tfilter { - .pass_frequency_normalized = 0.104f, - .stop_frequency_normalized = 0.140f, - .taps = { { - 89, 159, 220, 208, 84, -147, -412, -597, - -588, -345, 58, 441, 595, 391, -128, -730, - -1080, -914, -198, 793, 1558, 1594, 678, -942, - -2546, -3187, -2084, 992, 5515, 10321, 13985, 15353, - 13985, 10321, 5515, 992, -2084, -3187, -2546, -942, - 678, 1594, 1558, 793, -198, -914, -1080, -730, - -128, 391, 595, 441, 58, -345, -588, -597, - -412, -147, 84, 208, 220, 159, 89, 0, - } }, -}; - -/* Wideband FM channel filter - * 103kHz/128kHz @ 768kHz - */ -constexpr fir_taps_real<64> taps_64_lp_130_169_tfilter { - .pass_frequency_normalized = 0.130f, - .stop_frequency_normalized = 0.169f, - .taps = { { - 100, 127, 62, -157, -470, -707, -678, -332, - 165, 494, 400, -85, -610, -729, -253, 535, - 1026, 734, -263, -1264, -1398, -332, 1316, 2259, - 1447, -988, -3474, -3769, -385, 6230, 13607, 18450, - 18450, 13607, 6230, -385, -3769, -3474, -988, 1447, - 2259, 1316, -332, -1398, -1264, -263, 734, 1026, - 535, -253, -729, -610, -85, 400, 494, 165, - -332, -678, -707, -470, -157, 62, 127, 100, - } }, -}; - -// 41kHz/70kHz @ 192kHz -// http://t-filter.appspot.com -constexpr fir_taps_real<64> taps_64_lp_410_700_tfilter { - .pass_frequency_normalized = 0.213f, - .stop_frequency_normalized = 0.364f, - .taps = { { - 0, - 0, - 0, - 0, - -1, - 0, - 3, - -3, - -7, - 12, - 10, - -35, - -3, - 79, - -37, - -138, - 146, - 180, - -361, - -126, - 688, - -149, - -1062, - 800, - 1308, - -1991, - -1092, - 3963, - -286, - -7710, - 6211, - 32368, - 32368, - 6211, - -7710, - -286, - 3963, - -1092, - -1991, - 1308, - 800, - -1062, - -149, - 688, - -126, - -361, - 180, - 146, - -138, - -37, - 79, - -3, - -35, - 10, - 12, - -7, - -3, - 3, - 0, - -1, - 0, - 0, - 0, - 0 - } }, -}; - -/* Wideband audio filter */ -/* 96kHz int16_t input - * -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop - * -> 48kHz int16_t output, gain of 1.0 (I think). - * Padded to multiple of four taps for unrolled FIR code. - * sum(abs(taps)): 125270 - */ -constexpr fir_taps_real<64> taps_64_lp_156_198 { - .pass_frequency_normalized = 0.156f, - .stop_frequency_normalized = 0.196f, - .taps = { { - -27, 166, 104, -36, -174, -129, 109, 287, - 148, -232, -430, -130, 427, 597, 49, -716, - -778, 137, 1131, 957, -493, -1740, -1121, 1167, - 2733, 1252, -2633, -4899, -1336, 8210, 18660, 23254, - 18660, 8210, -1336, -4899, -2633, 1252, 2733, 1167, - -1121, -1740, -493, 957, 1131, 137, -778, -716, - 49, 597, 427, -130, -430, -232, 148, 287, - 109, -129, -174, -36, 104, 166, -27, 0, - } }, -}; - -#endif/*__DSP_FIR_TAPS_H__*/ diff --git a/firmware/baseband/dsp_iir.cpp b/firmware/baseband/dsp_iir.cpp new file mode 100644 index 00000000..443183ba --- /dev/null +++ b/firmware/baseband/dsp_iir.cpp @@ -0,0 +1,57 @@ +/* + * 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 "dsp_iir.hpp" + +#include + +void IIRBiquadFilter::configure(const iir_biquad_config_t& new_config) { + config = new_config; +} + +void IIRBiquadFilter::execute(const buffer_f32_t& buffer_in, const buffer_f32_t& buffer_out) { + const auto a_ = config.a; + const auto b_ = config.b; + + auto x_ = x; + auto y_ = y; + + // TODO: Assert that buffer_out.count == buffer_in.count. + for(size_t i=0; i b; - const std::array a; + std::array b; + std::array a; +}; + +constexpr iir_biquad_config_t iir_config_passthrough { + { { 1.0f, 0.0f, 0.0f } }, + { { 0.0f, 0.0f, 0.0f } }, +}; + +constexpr iir_biquad_config_t iir_config_no_pass { + { { 0.0f, 0.0f, 0.0f } }, + { { 0.0f, 0.0f, 0.0f } }, }; class IIRBiquadFilter { public: // http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt + constexpr IIRBiquadFilter( + ) : IIRBiquadFilter(iir_config_no_pass) + { + } // Assume all coefficients are normalized so that a0=1.0 constexpr IIRBiquadFilter( @@ -42,34 +56,15 @@ public: { } - void execute(buffer_s16_t buffer_in, buffer_s16_t buffer_out) { - // TODO: Assert that buffer_out.count == buffer_in.count. - for(size_t i=0; i x { { 0.0f, 0.0f, 0.0f } }; std::array y { { 0.0f, 0.0f, 0.0f } }; - - float execute_sample(const float in) { - x[0] = x[1]; - x[1] = x[2]; - x[2] = in; - - y[0] = y[1]; - y[1] = y[2]; - y[2] = config.b[0] * x[2] + config.b[1] * x[1] + config.b[2] * x[0] - - config.a[1] * y[1] - config.a[2] * y[0]; - - return y[2]; - } }; #endif/*__DSP_IIR_H__*/ diff --git a/firmware/baseband/dsp_iir_config.hpp b/firmware/baseband/dsp_iir_config.hpp index e20a51fc..096acd99 100644 --- a/firmware/baseband/dsp_iir_config.hpp +++ b/firmware/baseband/dsp_iir_config.hpp @@ -24,14 +24,37 @@ #include "dsp_iir.hpp" -constexpr iir_biquad_config_t audio_hpf_config { - { 0.93346032f, -1.86687724f, 0.93346032f }, - { 1.0f , -1.97730264f, 0.97773668f } +// scipy.signal.butter(2, 30 / 24000.0, 'highpass', analog=False) +constexpr iir_biquad_config_t audio_hpf_30hz_config { + { 0.99722705f, -1.99445410f, 0.99722705f }, + { 1.00000000f, -1.99444641f, 0.99446179f } }; +// scipy.signal.butter(2, 300 / 24000.0, 'highpass', analog=False) +constexpr iir_biquad_config_t audio_hpf_300hz_config { + { 0.97261390f, -1.94522780f, 0.97261390f }, + { 1.00000000f, -1.94447766f, 0.94597794f } +}; + +// scipy.signal.iirdesign(wp=8000 / 24000.0, ws= 4000 / 24000.0, gpass=1, gstop=18, ftype='ellip') constexpr iir_biquad_config_t non_audio_hpf_config { { 0.51891061f, -0.95714180f, 0.51891061f }, { 1.0f , -0.79878302f, 0.43960231f } }; +// scipy.signal.butter(1, 300 / 24000.0, 'lowpass', analog=False) +// NOTE: Technically, order-1 filter, b[2] = a[2] = 0. +constexpr iir_biquad_config_t audio_deemph_300_6_config { + { 0.01925927f, 0.01925927f, 0.00000000f }, + { 1.00000000f, -0.96148145f, 0.00000000f } +}; + +// 75us RC time constant, used in broadcast FM in Americas, South Korea +// scipy.signal.butter(1, 2122 / 24000.0, 'lowpass', analog=False) +// NOTE: Technically, order-1 filter, b[2] = a[2] = 0. +constexpr iir_biquad_config_t audio_deemph_2122_6_config { + { 0.12264116f, 0.12264116f, 0.00000000f }, + { 1.00000000f, -0.75471767f, 0.00000000f } +}; + #endif/*__DSP_IIR_CONFIG_H__*/ diff --git a/firmware/baseband/dsp_squelch.cpp b/firmware/baseband/dsp_squelch.cpp index 4cfe651a..b4a2d3a4 100644 --- a/firmware/baseband/dsp_squelch.cpp +++ b/firmware/baseband/dsp_squelch.cpp @@ -24,22 +24,30 @@ #include #include -bool FMSquelch::execute(buffer_s16_t audio) { +bool FMSquelch::execute(const buffer_f32_t& audio) { + if( threshold_squared == 0.0f ) { + return true; + } + // TODO: No hard-coded array size. - std::array squelch_energy_buffer; - const buffer_s16_t squelch_energy { + std::array squelch_energy_buffer; + const buffer_f32_t squelch_energy { squelch_energy_buffer.data(), squelch_energy_buffer.size() }; non_audio_hpf.execute(audio, squelch_energy); - uint64_t max_squared = 0; + float non_audio_max_squared = 0; for(const auto sample : squelch_energy_buffer) { - const uint64_t sample_squared = sample * sample; - if( sample_squared > max_squared ) { - max_squared = sample_squared; + const float sample_squared = sample * sample; + if( sample_squared > non_audio_max_squared ) { + non_audio_max_squared = sample_squared; } } - return (max_squared < (threshold * threshold)); + return (non_audio_max_squared < threshold_squared); +} + +void FMSquelch::set_threshold(const float new_value) { + threshold_squared = new_value * new_value; } diff --git a/firmware/baseband/dsp_squelch.hpp b/firmware/baseband/dsp_squelch.hpp index 06cb2cdd..4701b9ac 100644 --- a/firmware/baseband/dsp_squelch.hpp +++ b/firmware/baseband/dsp_squelch.hpp @@ -31,14 +31,14 @@ class FMSquelch { public: - bool execute(buffer_s16_t audio); + bool execute(const buffer_f32_t& audio); + + void set_threshold(const float new_value); private: static constexpr size_t N = 32; - static constexpr int16_t threshold = 3072; + float threshold_squared { 0.0f }; - // nyquist = 48000 / 2.0 - // scipy.signal.iirdesign(wp=8000 / nyquist, ws= 4000 / nyquist, gpass=1, gstop=18, ftype='ellip') IIRBiquadFilter non_audio_hpf { non_audio_hpf_config }; }; diff --git a/firmware/baseband/event_m4.cpp b/firmware/baseband/event_m4.cpp index 50b0c0b7..0e90cd15 100644 --- a/firmware/baseband/event_m4.cpp +++ b/firmware/baseband/event_m4.cpp @@ -21,10 +21,99 @@ #include "event_m4.hpp" +#include "portapack_shared_memory.hpp" + +#include "message_queue.hpp" + #include "ch.h" -Thread* thread_event_loop = nullptr; +#include "lpc43xx_cpp.hpp" +using namespace lpc43xx; -void events_initialize(Thread* const event_loop_thread) { - thread_event_loop = event_loop_thread; +#include +#include + +extern "C" { + +CH_IRQ_HANDLER(MAPP_IRQHandler) { + CH_IRQ_PROLOGUE(); + + chSysLockFromIsr(); + EventDispatcher::events_flag_isr(EVT_MASK_BASEBAND); + chSysUnlockFromIsr(); + + creg::m0apptxevent::clear(); + + CH_IRQ_EPILOGUE(); +} + +} + +Thread* EventDispatcher::thread_event_loop = nullptr; + +void EventDispatcher::run() { + thread_event_loop = chThdSelf(); + lpc43xx::creg::m0apptxevent::enable(); + + baseband_thread.thread_main = chThdSelf(); + baseband_thread.thread_rssi = rssi_thread.start(NORMALPRIO + 10); + baseband_thread.start(NORMALPRIO + 20); + + while(is_running) { + const auto events = wait(); + dispatch(events); + } + + lpc43xx::creg::m0apptxevent::disable(); +} + +void EventDispatcher::request_stop() { + is_running = false; +} + +eventmask_t EventDispatcher::wait() { + return chEvtWaitAny(ALL_EVENTS); +} + +void EventDispatcher::dispatch(const eventmask_t events) { + if( events & EVT_MASK_BASEBAND ) { + handle_baseband_queue(); + } + + if( events & EVT_MASK_SPECTRUM ) { + handle_spectrum(); + } +} + +void EventDispatcher::handle_baseband_queue() { + std::array message_buffer; + while(Message* const message = shared_memory.baseband_queue.peek(message_buffer)) { + on_message(message); + shared_memory.baseband_queue.skip(); + } +} + +void EventDispatcher::on_message(const Message* const message) { + switch(message->id) { + case Message::ID::Shutdown: + on_message_shutdown(*reinterpret_cast(message)); + break; + + default: + on_message_default(message); + break; + } +} + +void EventDispatcher::on_message_shutdown(const ShutdownMessage&) { + request_stop(); +} + +void EventDispatcher::on_message_default(const Message* const message) { + baseband_thread.on_message(message); +} + +void EventDispatcher::handle_spectrum() { + const UpdateSpectrumMessage message; + baseband_thread.on_message(&message); } diff --git a/firmware/baseband/event_m4.hpp b/firmware/baseband/event_m4.hpp index be113ca8..ad9871d1 100644 --- a/firmware/baseband/event_m4.hpp +++ b/firmware/baseband/event_m4.hpp @@ -22,25 +22,54 @@ #ifndef __EVENT_M4_H__ #define __EVENT_M4_H__ +#include "event.hpp" + +#include "baseband_thread.hpp" +#include "rssi_thread.hpp" + +#include "message.hpp" + #include "ch.h" constexpr auto EVT_MASK_BASEBAND = EVENT_MASK(0); constexpr auto EVT_MASK_SPECTRUM = EVENT_MASK(1); -void events_initialize(Thread* const event_loop_thread); +class EventDispatcher { +public: + void run(); + void request_stop(); -extern Thread* thread_event_loop; - -inline void events_flag(const eventmask_t events) { - if( thread_event_loop ) { - chEvtSignal(thread_event_loop, events); + static inline void events_flag(const eventmask_t events) { + if( thread_event_loop ) { + chEvtSignal(thread_event_loop, events); + } } -} -inline void events_flag_isr(const eventmask_t events) { - if( thread_event_loop ) { - chEvtSignalI(thread_event_loop, events); + static inline void events_flag_isr(const eventmask_t events) { + if( thread_event_loop ) { + chEvtSignalI(thread_event_loop, events); + } } -} + +private: + static Thread* thread_event_loop; + + BasebandThread baseband_thread; + RSSIThread rssi_thread; + + bool is_running = true; + + eventmask_t wait(); + + void dispatch(const eventmask_t events); + + void handle_baseband_queue(); + + void on_message(const Message* const message); + void on_message_shutdown(const ShutdownMessage&); + void on_message_default(const Message* const message); + + void handle_spectrum(); +}; #endif/*__EVENT_M4_H__*/ diff --git a/firmware/baseband/main.cpp b/firmware/baseband/main.cpp index 439b3831..eea7e572 100755 --- a/firmware/baseband/main.cpp +++ b/firmware/baseband/main.cpp @@ -20,7 +20,6 @@ */ #include "ch.h" -#include "test.h" #include "lpc43xx_cpp.hpp" @@ -29,44 +28,13 @@ #include "gpdma.hpp" -#include "baseband.hpp" -#include "baseband_dma.hpp" - #include "event_m4.hpp" -#include "irq_ipc_m4.hpp" - -#include "rssi.hpp" -#include "rssi_dma.hpp" - #include "touch_dma.hpp" -#include "modules.h" - -#include "dsp_decimate.hpp" -#include "dsp_demodulate.hpp" -#include "dsp_fft.hpp" -#include "dsp_fir_taps.hpp" -#include "dsp_iir.hpp" -#include "dsp_iir_config.hpp" -#include "dsp_squelch.hpp" - -#include "baseband_stats_collector.hpp" -#include "rssi_stats_collector.hpp" - -#include "channel_decimator.hpp" +#include "baseband_thread.hpp" +#include "rssi_thread.hpp" #include "baseband_processor.hpp" -#include "proc_am_audio.hpp" -#include "proc_nfm_audio.hpp" -#include "proc_wfm_audio.hpp" -#include "proc_ais.hpp" -#include "proc_wideband_spectrum.hpp" -#include "proc_tpms.hpp" -#include "proc_afskrx.hpp" -#include "proc_sigfrx.hpp" - -#include "clock_recovery.hpp" -#include "packet_builder.hpp" #include "message_queue.hpp" @@ -84,9 +52,6 @@ #include #include #include -#include - -static baseband::Direction direction = baseband::Direction::Receive; class ThreadBase { public: @@ -141,41 +106,22 @@ private: }; while(true) { - if (direction == baseband::Direction::Transmit) { - const auto buffer_tmp = baseband::dma::wait_for_tx_buffer(); - - const buffer_c8_t buffer { - buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate - }; + // TODO: Place correct sampling rate into buffer returned here: + const auto buffer_tmp = baseband::dma::wait_for_rx_buffer(); + const buffer_c8_t buffer { + buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate + }; - if( baseband_processor ) { - baseband_processor->execute(buffer); - } - - stats.process(buffer, - [](const BasebandStatistics statistics) { - const BasebandStatisticsMessage message { statistics }; - shared_memory.application_queue.push(message); - } - ); - } else { - const auto buffer_tmp = baseband::dma::wait_for_rx_buffer(); - - const buffer_c8_t buffer { - buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate - }; - - if( baseband_processor ) { - baseband_processor->execute(buffer); - } - - stats.process(buffer, - [](const BasebandStatistics statistics) { - const BasebandStatisticsMessage message { statistics }; - shared_memory.application_queue.push(message); - } - ); + if( baseband_processor ) { + baseband_processor->execute(buffer); } + + stats.process(buffer, + [](const BasebandStatistics statistics) { + const BasebandStatisticsMessage message { statistics }; + shared_memory.application_queue.push(message); + } + ); } } }; @@ -346,25 +292,8 @@ private: } }; -const auto baseband_buffer = - new std::array(); +static constexpr auto direction = baseband::Direction::Receive; -char ram_loop[32]; -typedef int (*fn_ptr)(void); -fn_ptr loop_ptr; - -void ram_loop_fn(void) { - while(1) {} -} - -void wait_for_switch(void) { - memcpy(&ram_loop[0], reinterpret_cast(&ram_loop_fn), 32); - loop_ptr = reinterpret_cast(&ram_loop[0]); - ReadyForSwitchMessage message; - shared_memory.application_queue.push(message); - (*loop_ptr)(); -} - int main(void) { init(); @@ -373,18 +302,6 @@ int main(void) { EventDispatcher event_dispatcher; auto& message_handlers = event_dispatcher.message_handlers(); - - message_handlers.register_handler(Message::ID::ModuleID, - [&message_handlers](Message* p) { - ModuleIDMessage reply; - auto message = static_cast(p); - if (message->query == true) { // Shouldn't be needed - memcpy(reply.md5_signature, (const void *)(0x10087FF0), 16); - reply.query = false; - shared_memory.application_queue.push(reply); - } - } - ); message_handlers.register_handler(Message::ID::BasebandConfiguration, [&message_handlers](const Message* const p) { @@ -403,47 +320,29 @@ int main(void) { delete old_p; switch(message->configuration.mode) { - case RX_NBAM_AUDIO: - direction = baseband::Direction::Receive; + case 0: baseband_thread.baseband_processor = new NarrowbandAMAudio(); break; - case RX_NBFM_AUDIO: - direction = baseband::Direction::Receive; + case 1: baseband_thread.baseband_processor = new NarrowbandFMAudio(); break; - case RX_WBFM_AUDIO: + case 2: baseband_thread.baseband_processor = new WidebandFMAudio(); break; - case RX_AIS: - direction = baseband::Direction::Receive; + case 3: baseband_thread.baseband_processor = new AISProcessor(); break; - case RX_WBSPECTRUM: - direction = baseband::Direction::Receive; + case 4: baseband_thread.baseband_processor = new WidebandSpectrum(); break; - case RX_TPMS: - direction = baseband::Direction::Receive; + case 5: baseband_thread.baseband_processor = new TPMSProcessor(); break; - - case RX_AFSK: - direction = baseband::Direction::Receive; - baseband_thread.baseband_processor = new AFSKRXProcessor(); - break; - - case RX_SIGFOX: - direction = baseband::Direction::Receive; - baseband_thread.baseband_processor = new SIGFRXProcessor(); - break; - - case SWITCH: - wait_for_switch(); default: break; @@ -454,14 +353,8 @@ int main(void) { rf::rssi::start(); } baseband::dma::enable(direction); - rf::rssi::stop(); } } - - baseband::dma::configure( - baseband_buffer->data(), - direction - ); baseband_thread.baseband_configuration = message->configuration; } @@ -475,26 +368,23 @@ int main(void) { /* TODO: Ensure DMAs are configured to point at first LLI in chain. */ - rf::rssi::dma::allocate(4, 400); + if( direction == baseband::Direction::Receive ) { + rf::rssi::dma::allocate(4, 400); + } touch::dma::allocate(); touch::dma::enable(); - + + const auto baseband_buffer = + new std::array(); baseband::dma::configure( baseband_buffer->data(), direction ); - //baseband::dma::allocate(4, 2048); - event_dispatcher.run(); shutdown(); - ShutdownMessage shutdown_message; - shared_memory.application_queue.push(shutdown_message); - - halt(); - return 0; } diff --git a/firmware/baseband/matched_filter.cpp b/firmware/baseband/matched_filter.cpp index 5040349a..511410df 100644 --- a/firmware/baseband/matched_filter.cpp +++ b/firmware/baseband/matched_filter.cpp @@ -21,9 +21,26 @@ #include "matched_filter.hpp" +#include +#include + +#include "utility.hpp" + namespace dsp { namespace matched_filter { +void MatchedFilter::configure( + const tap_t* const taps, + const size_t taps_count, + const size_t decimation_factor +) { + samples_ = std::make_unique(taps_count); + taps_reversed_ = std::make_unique(taps_count); + taps_count_ = taps_count; + decimation_factor_ = decimation_factor; + std::reverse_copy(&taps[0], &taps[taps_count], &taps_reversed_[0]); +} + bool MatchedFilter::execute_once( const sample_t input ) { diff --git a/firmware/baseband/matched_filter.hpp b/firmware/baseband/matched_filter.hpp index 741f5018..d3ee30be 100644 --- a/firmware/baseband/matched_filter.hpp +++ b/firmware/baseband/matched_filter.hpp @@ -22,17 +22,10 @@ #ifndef __MATCHED_FILTER_H__ #define __MATCHED_FILTER_H__ -#include "utility.hpp" - #include - #include -#include #include -#include -#include - namespace dsp { namespace matched_filter { @@ -61,11 +54,7 @@ public: const T& taps, size_t decimation_factor ) { - samples_ = std::make_unique(taps.size()); - taps_reversed_ = std::make_unique(taps.size()); - taps_count_ = taps.size(); - decimation_factor_ = decimation_factor; - std::reverse_copy(taps.cbegin(), taps.cend(), &taps_reversed_[0]); + configure(taps.data(), taps.size(), decimation_factor); } bool execute_once(const sample_t input); @@ -93,6 +82,12 @@ private: bool is_new_decimation_cycle() const { return (decimation_phase == 0); } + + void configure( + const tap_t* const taps, + const size_t taps_count, + const size_t decimation_factor + ); }; } /* namespace matched_filter */ diff --git a/firmware/baseband/packet_builder.hpp b/firmware/baseband/packet_builder.hpp index edf50357..d32d129d 100644 --- a/firmware/baseband/packet_builder.hpp +++ b/firmware/baseband/packet_builder.hpp @@ -28,12 +28,26 @@ #include #include "bit_pattern.hpp" +#include "baseband_packet.hpp" + +struct NeverMatch { + bool operator()(const BitHistory&, const size_t) const { + return false; + } +}; + +struct FixedLength { + bool operator()(const BitHistory&, const size_t symbols_received) const { + return symbols_received >= length; + } + + const size_t length; +}; template class PacketBuilder { public: - using PayloadType = std::bitset<1024>; - using PayloadHandlerFunc = std::function; + using PayloadHandlerFunc = std::function; PacketBuilder( const PreambleMatcher preamble_matcher, @@ -64,18 +78,19 @@ public: switch(state) { case State::Preamble: - if( preamble(bit_history, bits_received) ) { + if( preamble(bit_history, packet.size()) ) { state = State::Payload; } break; case State::Payload: - if( !unstuff(bit_history, bits_received) ) { - payload[bits_received++] = symbol; + if( !unstuff(bit_history, packet.size()) ) { + packet.add(symbol); } - if( end(bit_history, bits_received) ) { - payload_handler(payload, bits_received); + if( end(bit_history, packet.size()) ) { + packet.set_timestamp(Timestamp::now()); + payload_handler(packet); reset_state(); } else { if( packet_truncated() ) { @@ -97,7 +112,7 @@ private: }; bool packet_truncated() const { - return bits_received >= payload.size(); + return packet.size() >= packet.capacity(); } const PayloadHandlerFunc payload_handler; @@ -107,12 +122,11 @@ private: UnstuffMatcher unstuff; EndMatcher end; - size_t bits_received { 0 }; State state { State::Preamble }; - PayloadType payload; + baseband::Packet packet; void reset_state() { - bits_received = 0; + packet.clear(); state = State::Preamble; } }; diff --git a/firmware/baseband/proc_ais.cpp b/firmware/baseband/proc_ais.cpp index e3005b93..80452dd0 100644 --- a/firmware/baseband/proc_ais.cpp +++ b/firmware/baseband/proc_ais.cpp @@ -23,36 +23,28 @@ #include "portapack_shared_memory.hpp" -#include "i2s.hpp" -using namespace lpc43xx; +#include "dsp_fir_taps.hpp" -void AISProcessor::execute(buffer_c8_t buffer) { +AISProcessor::AISProcessor() { + decim_0.configure(taps_11k0_decim_0.taps, 33554432); + decim_1.configure(taps_11k0_decim_1.taps, 131072); +} + +void AISProcessor::execute(const buffer_c8_t& buffer) { /* 2.4576MHz, 2048 samples */ - auto decimator_out = decimator.execute(buffer); + 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; - /* 76.8kHz, 64 samples */ + /* 38.4kHz, 32 samples */ feed_channel_stats(decimator_out); - /* No spectrum display while AIS decoding. - feed_channel_spectrum( - channel, - decimator_out.sampling_rate * channel_filter_taps.pass_frequency_normalized, - decimator_out.sampling_rate * channel_filter_taps.stop_frequency_normalized - ); - */ for(size_t i=0; ifloat is not allowed. - const std::complex sample { - static_cast(decimator_out.p[i].real()), - static_cast(decimator_out.p[i].imag()) - }; - if( mf.execute_once(sample) ) { + if( mf.execute_once(decimator_out.p[i]) ) { clock_recovery(mf.get_output()); } } - - i2s::i2s0::tx_mute(); } void AISProcessor::consume_symbol( @@ -65,11 +57,8 @@ void AISProcessor::consume_symbol( } void AISProcessor::payload_handler( - const std::bitset<1024>& payload, - const size_t bits_received + const baseband::Packet& packet ) { - AISPacketMessage message; - message.packet.payload = payload; - message.packet.bits_received = bits_received; + const AISPacketMessage message { packet }; shared_memory.application_queue.push(message); } diff --git a/firmware/baseband/proc_ais.hpp b/firmware/baseband/proc_ais.hpp index e52983f6..966d739a 100644 --- a/firmware/baseband/proc_ais.hpp +++ b/firmware/baseband/proc_ais.hpp @@ -30,6 +30,7 @@ #include "clock_recovery.hpp" #include "symbol_coding.hpp" #include "packet_builder.hpp" +#include "baseband_packet.hpp" #include "message.hpp" @@ -41,13 +42,20 @@ class AISProcessor : public BasebandProcessor { public: - using payload_t = std::bitset<1024>; + AISProcessor(); - void execute(buffer_c8_t buffer) override; + void execute(const buffer_c8_t& buffer) override; private: - ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By32 }; - dsp::matched_filter::MatchedFilter mf { baseband::ais::rrc_taps_76k8_4t_p, 4 }; + std::array dst; + const buffer_c16_t dst_buffer { + dst.data(), + dst.size() + }; + + dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0; + dsp::decimate::FIRC16xR16x32Decim8 decim_1; + dsp::matched_filter::MatchedFilter mf { baseband::ais::rrc_taps_38k4_4t_p, 2 }; clock_recovery::ClockRecovery clock_recovery { 19200, 9600, { 0.0555f }, @@ -58,13 +66,13 @@ private: { 0b0101010101111110, 16, 1 }, { 0b111110, 6 }, { 0b01111110, 8 }, - [this](const payload_t& payload, const size_t bits_received) { - this->payload_handler(payload, bits_received); + [this](const baseband::Packet& packet) { + this->payload_handler(packet); } }; void consume_symbol(const float symbol); - void payload_handler(const payload_t& payload, const size_t bits_received); + void payload_handler(const baseband::Packet& packet); }; #endif/*__PROC_AIS_H__*/ diff --git a/firmware/baseband/proc_am_audio.cpp b/firmware/baseband/proc_am_audio.cpp index 7a688a08..06314ee0 100644 --- a/firmware/baseband/proc_am_audio.cpp +++ b/firmware/baseband/proc_am_audio.cpp @@ -21,39 +21,67 @@ #include "proc_am_audio.hpp" -#include +#include "dsp_iir_config.hpp" +#include "audio_output.hpp" -void NarrowbandAMAudio::execute(buffer_c8_t buffer) { - auto decimator_out = decimator.execute(buffer); +#include - const buffer_c16_t work_baseband_buffer { - (complex16_t*)decimator_out.p, - sizeof(*decimator_out.p) * decimator_out.count - }; +void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) { + if( !configured ) { + return; + } - /* 96kHz complex[64] - * -> FIR filter, 48kHz int16_t[32] */ - auto channel = channel_filter.execute(decimator_out, work_baseband_buffer); + 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 channel_out = channel_filter.execute(decim_1_out, dst_buffer); // TODO: Feed channel_stats post-decimation data? - feed_channel_stats(channel); - feed_channel_spectrum( - channel, - decimator_out.sampling_rate * channel_filter_taps.pass_frequency_normalized, - decimator_out.sampling_rate * channel_filter_taps.stop_frequency_normalized - ); + feed_channel_stats(channel_out); + channel_spectrum.feed(channel_out, channel_filter_pass_f, channel_filter_stop_f); - const buffer_s16_t work_audio_buffer { - (int16_t*)decimator_out.p, - sizeof(*decimator_out.p) * decimator_out.count - }; + auto audio = demod.execute(channel_out, work_audio_buffer); - /* 48kHz complex[32] - * -> AM demodulation - * -> 48kHz int16_t[32] */ - auto audio = demod.execute(channel, work_audio_buffer); - - audio_hpf.execute_in_place(audio); - fill_audio_buffer(audio); + audio_output.write(audio); +} + +void NarrowbandAMAudio::on_message(const Message* const message) { + switch(message->id) { + case Message::ID::UpdateSpectrum: + case Message::ID::SpectrumStreamingConfig: + channel_spectrum.on_message(message); + break; + + case Message::ID::AMConfigure: + configure(*reinterpret_cast(message)); + break; + + default: + break; + } +} + +void NarrowbandAMAudio::configure(const AMConfigureMessage& message) { + constexpr size_t baseband_fs = 3072000; + + constexpr size_t decim_0_input_fs = baseband_fs; + constexpr size_t decim_0_decimation_factor = 8; + constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0_decimation_factor; + + constexpr size_t decim_1_input_fs = decim_0_output_fs; + constexpr size_t decim_1_decimation_factor = 8; + constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1_decimation_factor; + + constexpr size_t channel_filter_input_fs = decim_1_output_fs; + constexpr size_t channel_filter_decimation_factor = 1; + constexpr size_t channel_filter_output_fs = channel_filter_input_fs / channel_filter_decimation_factor; + + decim_0.configure(message.decim_0_filter.taps, 33554432); + decim_1.configure(message.decim_1_filter.taps, 131072); + channel_filter.configure(message.channel_filter.taps, channel_filter_decimation_factor); + channel_filter_pass_f = message.channel_filter.pass_frequency_normalized * channel_filter_input_fs; + channel_filter_stop_f = message.channel_filter.stop_frequency_normalized * channel_filter_input_fs; + channel_spectrum.set_decimation_factor(std::floor((channel_filter_output_fs / 2) / ((channel_filter_pass_f + channel_filter_stop_f) / 2))); + audio_output.configure(audio_hpf_300hz_config); + + configured = true; } diff --git a/firmware/baseband/proc_am_audio.hpp b/firmware/baseband/proc_am_audio.hpp index 6c0d91e5..5da5136d 100644 --- a/firmware/baseband/proc_am_audio.hpp +++ b/firmware/baseband/proc_am_audio.hpp @@ -24,28 +24,45 @@ #include "baseband_processor.hpp" -#include "channel_decimator.hpp" #include "dsp_decimate.hpp" #include "dsp_demodulate.hpp" -#include "dsp_fir_taps.hpp" -#include "dsp_iir.hpp" -#include "dsp_iir_config.hpp" + +#include "audio_output.hpp" +#include "spectrum_collector.hpp" + +#include class NarrowbandAMAudio : public BasebandProcessor { public: - NarrowbandAMAudio() { - decimator.set_decimation_factor(ChannelDecimator::DecimationFactor::By32); - channel_filter.configure(channel_filter_taps.taps, 2); - } - - void execute(buffer_c8_t buffer) override; + void execute(const buffer_c8_t& buffer) override; + + void on_message(const Message* const message) override; private: - ChannelDecimator decimator; - const fir_taps_real<64>& channel_filter_taps = taps_64_lp_031_070_tfilter; + std::array dst; + const buffer_c16_t dst_buffer { + dst.data(), + dst.size() + }; + const buffer_f32_t work_audio_buffer { + (float*)dst.data(), + sizeof(dst) / sizeof(float) + }; + + dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0; + dsp::decimate::FIRC16xR16x32Decim8 decim_1; dsp::decimate::FIRAndDecimateComplex channel_filter; + uint32_t channel_filter_pass_f; + uint32_t channel_filter_stop_f; + dsp::demodulate::AM demod; - IIRBiquadFilter audio_hpf { audio_hpf_config }; + + AudioOutput audio_output; + + SpectrumCollector channel_spectrum; + + bool configured { false }; + void configure(const AMConfigureMessage& message); }; #endif/*__PROC_AM_AUDIO_H__*/ diff --git a/firmware/baseband/proc_ert.cpp b/firmware/baseband/proc_ert.cpp new file mode 100644 index 00000000..e6ae439c --- /dev/null +++ b/firmware/baseband/proc_ert.cpp @@ -0,0 +1,103 @@ +/* + * 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_ert.hpp" + +#include "portapack_shared_memory.hpp" + +float ERTProcessor::abs(const complex8_t& v) { + // const int16_t r = v.real() - offset_i; + // const int16_t i = v.imag() - offset_q; + // const uint32_t r2 = r * r; + // const uint32_t i2 = i * i; + // const uint32_t r2_i2 = r2 + i2; + // return std::sqrt(static_cast(r2_i2)); + const float r = static_cast(v.real()) - offset_i; + const float i = static_cast(v.imag()) - offset_q; + const float r2 = r * r; + const float i2 = i * i; + const float r2_i2 = r2 + i2; + return std::sqrt(r2_i2); +} + +void ERTProcessor::execute(const buffer_c8_t& buffer) { + /* 4.194304MHz, 2048 samples */ + + const complex8_t* src = &buffer.p[0]; + const complex8_t* const src_end = &buffer.p[buffer.count]; + + average_i += src->real(); + average_q += src->imag(); + average_count++; + if( average_count == average_window ) { + offset_i = static_cast(average_i) / average_window; + offset_q = static_cast(average_q) / average_window; + average_i = 0; + average_q = 0; + average_count = 0; + } + + const float gain = 128 * samples_per_symbol; + const float k = 1.0f / gain; + + while(src < src_end) { + float sum = 0.0f; + for(size_t i=0; i<(samples_per_symbol / 2); i++) { + sum += abs(*(src++)); + } + sum_half_period[1] = sum_half_period[0]; + sum_half_period[0] = sum; + + sum_period[2] = sum_period[1]; + sum_period[1] = sum_period[0]; + sum_period[0] = (sum_half_period[0] + sum_half_period[1]) * k; + + manchester[2] = manchester[1]; + manchester[1] = manchester[0]; + manchester[0] = sum_period[2] - sum_period[0]; + + const auto data = manchester[0] - manchester[2]; + + clock_recovery(data); + } +} + +void ERTProcessor::consume_symbol( + const float raw_symbol +) { + const uint_fast8_t sliced_symbol = (raw_symbol >= 0.0f) ? 1 : 0; + scm_builder.execute(sliced_symbol); + idm_builder.execute(sliced_symbol); +} + +void ERTProcessor::scm_handler( + const baseband::Packet& packet +) { + const ERTPacketMessage message { ert::Packet::Type::SCM, packet }; + shared_memory.application_queue.push(message); +} + +void ERTProcessor::idm_handler( + const baseband::Packet& packet +) { + const ERTPacketMessage message { ert::Packet::Type::IDM, packet }; + shared_memory.application_queue.push(message); +} diff --git a/firmware/baseband/proc_ert.hpp b/firmware/baseband/proc_ert.hpp new file mode 100644 index 00000000..9c0663c9 --- /dev/null +++ b/firmware/baseband/proc_ert.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. + */ + +#ifndef __PROC_ERT_H__ +#define __PROC_ERT_H__ + +#include "baseband_processor.hpp" + +#include "channel_decimator.hpp" + +#include "clock_recovery.hpp" +#include "symbol_coding.hpp" +#include "packet_builder.hpp" +#include "baseband_packet.hpp" + +#include "message.hpp" + +#include +#include +#include + +// ''.join(['%d%d' % (c, 1-c) for c in map(int, bin(0x1f2a60)[2:].zfill(21))]) +constexpr uint64_t scm_preamble_and_sync_manchester { 0b101010101001011001100110010110100101010101 }; +constexpr size_t scm_preamble_and_sync_length { 42 - 10 }; +constexpr size_t scm_payload_length_max { 150 }; + +// ''.join(['%d%d' % (c, 1-c) for c in map(int, bin(0x555516a3)[2:].zfill(32))]) +constexpr uint64_t idm_preamble_and_sync_manchester { 0b0110011001100110011001100110011001010110011010011001100101011010 }; +constexpr size_t idm_preamble_and_sync_length { 64 - 16 }; + +constexpr size_t idm_payload_length_max { 1408 }; + +class ERTProcessor : public BasebandProcessor { +public: + void execute(const buffer_c8_t& buffer) override; + +private: + const uint32_t baseband_sampling_rate = 4194304; + const size_t decimation = 1; + const float symbol_rate = 32768; + + const uint32_t channel_sampling_rate = baseband_sampling_rate / decimation; + const size_t samples_per_symbol = channel_sampling_rate / symbol_rate; + const float clock_recovery_rate = symbol_rate * 2; + + clock_recovery::ClockRecovery clock_recovery { + clock_recovery_rate, symbol_rate, { 1.0f / 18.0f }, + [this](const float symbol) { this->consume_symbol(symbol); } + }; + + PacketBuilder scm_builder { + { scm_preamble_and_sync_manchester, scm_preamble_and_sync_length, 1 }, + { }, + { scm_payload_length_max }, + [this](const baseband::Packet& packet) { + this->scm_handler(packet); + } + }; + + PacketBuilder idm_builder { + { idm_preamble_and_sync_manchester, idm_preamble_and_sync_length, 1 }, + { }, + { idm_payload_length_max }, + [this](const baseband::Packet& packet) { + this->idm_handler(packet); + } + }; + + void consume_symbol(const float symbol); + void scm_handler(const baseband::Packet& packet); + void idm_handler(const baseband::Packet& packet); + + float sum_half_period[2]; + float sum_period[3]; + float manchester[3]; + + const size_t average_window { 2048 }; + int32_t average_i { 0 }; + int32_t average_q { 0 }; + size_t average_count { 0 }; + float offset_i { 0.0f }; + float offset_q { 0.0f }; + + float abs(const complex8_t& v); +}; + +#endif/*__PROC_ERT_H__*/ diff --git a/firmware/baseband/proc_nfm_audio.cpp b/firmware/baseband/proc_nfm_audio.cpp index e9b70cf9..906617cd 100644 --- a/firmware/baseband/proc_nfm_audio.cpp +++ b/firmware/baseband/proc_nfm_audio.cpp @@ -21,54 +21,70 @@ #include "proc_nfm_audio.hpp" +#include "dsp_iir_config.hpp" +#include "audio_output.hpp" + #include #include -void NarrowbandFMAudio::execute(buffer_c8_t buffer) { - /* Called every 2048/3072000 second -- 1500Hz. */ - - auto decimator_out = decimator.execute(buffer); - - const buffer_c16_t work_baseband_buffer { - (complex16_t*)decimator_out.p, - sizeof(*decimator_out.p) * decimator_out.count - }; - - /* 96kHz complex[64] - * -> FIR filter, <6kHz (0.063fs) pass, gain 1.0 - * -> 48kHz int16_t[32] */ - auto channel = channel_filter.execute(decimator_out, work_baseband_buffer); - - // TODO: Feed channel_stats post-decimation data? - feed_channel_stats(channel); - feed_channel_spectrum( - channel, - decimator_out.sampling_rate * channel_filter_taps.pass_frequency_normalized, - decimator_out.sampling_rate * channel_filter_taps.stop_frequency_normalized - ); - - const buffer_s16_t work_audio_buffer { - (int16_t*)decimator_out.p, - sizeof(*decimator_out.p) * decimator_out.count - }; - - /* 48kHz complex[32] - * -> FM demodulation - * -> 48kHz int16_t[32] */ - auto audio = demod.execute(channel, work_audio_buffer); - - static uint64_t audio_present_history = 0; - const auto audio_present_now = squelch.execute(audio); - audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0); - const bool audio_present = (audio_present_history != 0); - - if( !audio_present ) { - // Zero audio buffer. - for(size_t i=0; iid) { + case Message::ID::UpdateSpectrum: + case Message::ID::SpectrumStreamingConfig: + channel_spectrum.on_message(message); + break; + + case Message::ID::NBFMConfigure: + configure(*reinterpret_cast(message)); + break; + + default: + break; + } +} + +void NarrowbandFMAudio::configure(const NBFMConfigureMessage& message) { + constexpr size_t baseband_fs = 3072000; + + constexpr size_t decim_0_input_fs = baseband_fs; + constexpr size_t decim_0_decimation_factor = 8; + constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0_decimation_factor; + + constexpr size_t decim_1_input_fs = decim_0_output_fs; + constexpr size_t decim_1_decimation_factor = 8; + constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1_decimation_factor; + + constexpr size_t channel_filter_input_fs = decim_1_output_fs; + constexpr size_t channel_filter_decimation_factor = 1; + constexpr size_t channel_filter_output_fs = channel_filter_input_fs / channel_filter_decimation_factor; + + constexpr size_t demod_input_fs = channel_filter_output_fs; + + decim_0.configure(message.decim_0_filter.taps, 33554432); + decim_1.configure(message.decim_1_filter.taps, 131072); + channel_filter.configure(message.channel_filter.taps, channel_filter_decimation_factor); + demod.configure(demod_input_fs, message.deviation); + channel_filter_pass_f = message.channel_filter.pass_frequency_normalized * channel_filter_input_fs; + channel_filter_stop_f = message.channel_filter.stop_frequency_normalized * channel_filter_input_fs; + channel_spectrum.set_decimation_factor(std::floor((channel_filter_output_fs / 2) / ((channel_filter_pass_f + channel_filter_stop_f) / 2))); + audio_output.configure(audio_hpf_300hz_config, audio_deemph_300_6_config, 6144); + + configured = true; } diff --git a/firmware/baseband/proc_nfm_audio.hpp b/firmware/baseband/proc_nfm_audio.hpp index f3553387..69ce6ee0 100644 --- a/firmware/baseband/proc_nfm_audio.hpp +++ b/firmware/baseband/proc_nfm_audio.hpp @@ -24,31 +24,44 @@ #include "baseband_processor.hpp" -#include "channel_decimator.hpp" #include "dsp_decimate.hpp" #include "dsp_demodulate.hpp" -#include "dsp_fir_taps.hpp" -#include "dsp_iir.hpp" -#include "dsp_iir_config.hpp" -#include "dsp_squelch.hpp" + +#include "audio_output.hpp" +#include "spectrum_collector.hpp" class NarrowbandFMAudio : public BasebandProcessor { public: - NarrowbandFMAudio() { - decimator.set_decimation_factor(ChannelDecimator::DecimationFactor::By32); - channel_filter.configure(channel_filter_taps.taps, 2); - } + void execute(const buffer_c8_t& buffer) override; - void execute(buffer_c8_t buffer) override; + void on_message(const Message* const message) override; private: - ChannelDecimator decimator; - const fir_taps_real<64>& channel_filter_taps = taps_64_lp_042_078_tfilter; - dsp::decimate::FIRAndDecimateComplex channel_filter; - dsp::demodulate::FM demod { 48000, 7500 }; + std::array dst; + const buffer_c16_t dst_buffer { + dst.data(), + dst.size() + }; + const buffer_f32_t work_audio_buffer { + (float*)dst.data(), + sizeof(dst) / sizeof(float) + }; - IIRBiquadFilter audio_hpf { audio_hpf_config }; - FMSquelch squelch; + dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0; + dsp::decimate::FIRC16xR16x32Decim8 decim_1; + + dsp::decimate::FIRAndDecimateComplex channel_filter; + uint32_t channel_filter_pass_f = 0; + uint32_t channel_filter_stop_f = 0; + + dsp::demodulate::FM demod; + + AudioOutput audio_output; + + SpectrumCollector channel_spectrum; + + bool configured { false }; + void configure(const NBFMConfigureMessage& message); }; #endif/*__PROC_NFM_AUDIO_H__*/ diff --git a/firmware/baseband/proc_tpms.cpp b/firmware/baseband/proc_tpms.cpp index edabf468..d9b2375a 100644 --- a/firmware/baseband/proc_tpms.cpp +++ b/firmware/baseband/proc_tpms.cpp @@ -23,36 +23,49 @@ #include "portapack_shared_memory.hpp" -#include "i2s.hpp" -using namespace lpc43xx; +#include "dsp_fir_taps.hpp" -void TPMSProcessor::execute(buffer_c8_t buffer) { +// IFIR image-reject filter: fs=2457600, pass=100000, stop=407200, decim=4, fout=614400 +static constexpr fir_taps_real<24> taps_200k_decim_0 = { + .pass_frequency_normalized = 100000.0f / 2457600.0f, + .stop_frequency_normalized = 407200.0f / 2457600.0f, + .taps = { { + 90, 94, 4, -240, -570, -776, -563, 309, + 1861, 3808, 5618, 6710, 6710, 5618, 3808, 1861, + 309, -563, -776, -570, -240, 4, 94, 90, + } }, +}; + +// IFIR prototype filter: fs=614400, pass=100000, stop=207200, decim=2, fout=307200 +static constexpr fir_taps_real<16> taps_200k_decim_1 = { + .pass_frequency_normalized = 100000.0f / 614400.0f, + .stop_frequency_normalized = 207200.0f / 614400.0f, + .taps = { { + -132, -256, 545, 834, -1507, -2401, 4666, 14583, + 14583, 4666, -2401, -1507, 834, 545, -256, -132, + } }, +}; + +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 */ - auto decimator_out = decimator.execute(buffer); + 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; - /* 76.8kHz, 64 samples */ + /* 307.2kHz, 256 samples */ feed_channel_stats(decimator_out); - /* No spectrum display while FSK decoding. - feed_channel_spectrum( - channel, - decimator_out.sampling_rate * channel_filter_taps.pass_frequency_normalized, - decimator_out.sampling_rate * channel_filter_taps.stop_frequency_normalized - ); - */ for(size_t i=0; ifloat is not allowed. - const std::complex sample { - static_cast(decimator_out.p[i].real()), - static_cast(decimator_out.p[i].imag()) - }; - if( mf.execute_once(sample) ) { + if( mf.execute_once(decimator_out.p[i]) ) { clock_recovery(mf.get_output()); } } - - i2s::i2s0::tx_mute(); } void TPMSProcessor::consume_symbol( @@ -63,11 +76,8 @@ void TPMSProcessor::consume_symbol( } void TPMSProcessor::payload_handler( - const std::bitset<1024>& payload, - const size_t bits_received + const baseband::Packet& packet ) { - TPMSPacketMessage message; - message.packet.payload = payload; - message.packet.bits_received = bits_received; + const TPMSPacketMessage message { packet }; shared_memory.application_queue.push(message); } diff --git a/firmware/baseband/proc_tpms.hpp b/firmware/baseband/proc_tpms.hpp index 0bde7d68..0c42beb7 100644 --- a/firmware/baseband/proc_tpms.hpp +++ b/firmware/baseband/proc_tpms.hpp @@ -30,6 +30,7 @@ #include "clock_recovery.hpp" #include "symbol_coding.hpp" #include "packet_builder.hpp" +#include "baseband_packet.hpp" #include "message.hpp" @@ -37,39 +38,37 @@ #include #include -struct NeverMatch { - bool operator()(const BitHistory&, const size_t) const { - return false; - } -}; - -struct FixedLength { - bool operator()(const BitHistory&, const size_t symbols_received) const { - return symbols_received >= length; - } - - const size_t length; -}; - // Translate+rectangular filter -// sample=153.6k, deviation=38400, symbol=19200 -// Length: 8 taps, 1 symbols, 2 cycles of sinusoid -constexpr std::array, 8> rect_taps_153k6_1t_p { { - { 1.2500000000e-01f, 0.0000000000e+00f }, { 7.6540424947e-18f, 1.2500000000e-01f }, - { -1.2500000000e-01f, 1.5308084989e-17f }, { -2.2962127484e-17f, -1.2500000000e-01f }, - { 1.2500000000e-01f, -3.0616169979e-17f }, { 3.8270212473e-17f, 1.2500000000e-01f }, - { -1.2500000000e-01f, 4.5924254968e-17f }, { -5.3578297463e-17f, -1.2500000000e-01f }, +// 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: - using payload_t = std::bitset<1024>; + TPMSProcessor(); - void execute(buffer_c8_t buffer) override; + void execute(const buffer_c8_t& buffer) override; private: - ChannelDecimator decimator { ChannelDecimator::DecimationFactor::By16 }; - dsp::matched_filter::MatchedFilter mf { rect_taps_153k6_1t_p, 4 }; + 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 }, @@ -79,13 +78,13 @@ private: { 0b010101010101010101010101010110, 30, 1 }, { }, { 256 }, - [this](const payload_t& payload, const size_t bits_received) { - this->payload_handler(payload, bits_received); + [this](const baseband::Packet& packet) { + this->payload_handler(packet); } }; void consume_symbol(const float symbol); - void payload_handler(const payload_t& payload, const size_t bits_received); + void payload_handler(const baseband::Packet& packet); }; #endif/*__PROC_TPMS_H__*/ diff --git a/firmware/baseband/proc_wfm_audio.cpp b/firmware/baseband/proc_wfm_audio.cpp index 5f4a09cc..33526af8 100644 --- a/firmware/baseband/proc_wfm_audio.cpp +++ b/firmware/baseband/proc_wfm_audio.cpp @@ -21,45 +21,46 @@ #include "proc_wfm_audio.hpp" +#include "dsp_iir_config.hpp" +#include "audio_output.hpp" + #include -void WidebandFMAudio::execute(buffer_c8_t buffer) { - auto decimator_out = decimator.execute(buffer); +void WidebandFMAudio::execute(const buffer_c8_t& buffer) { + if( !configured ) { + return; + } - const buffer_s16_t work_audio_buffer { - (int16_t*)decimator_out.p, - sizeof(*decimator_out.p) * decimator_out.count - }; - - auto channel = decimator_out; + const auto decim_0_out = decim_0.execute(buffer, dst_buffer); + const auto channel = decim_1.execute(decim_0_out, dst_buffer); // TODO: Feed channel_stats post-decimation data? feed_channel_stats(channel); - //feed_channel_spectrum(channel); - /* 768kHz complex[512] + spectrum_samples += channel.count; + if( spectrum_samples >= spectrum_interval_samples ) { + spectrum_samples -= spectrum_interval_samples; + channel_spectrum.feed(channel, channel_filter_pass_f, channel_filter_stop_f); + } + + /* 384kHz complex[256] * -> FM demodulation - * -> 768kHz int16_t[512] */ + * -> 384kHz int16_t[256] */ /* TODO: To improve adjacent channel rejection, implement complex channel filter: * pass < +/- 100kHz, stop > +/- 200kHz */ - auto audio_oversampled = demod.execute(decimator_out, work_audio_buffer); - - /* 768kHz int16_t[512] - * -> 4th order CIC decimation by 2, gain of 1 - * -> 384kHz int16_t[256] */ - auto audio_8fs = audio_dec_1.execute(audio_oversampled, work_audio_buffer); + auto audio_oversampled = demod.execute(channel, work_audio_buffer); /* 384kHz int16_t[256] * -> 4th order CIC decimation by 2, gain of 1 * -> 192kHz int16_t[128] */ - auto audio_4fs = audio_dec_2.execute(audio_8fs, work_audio_buffer); + auto audio_4fs = audio_dec_1.execute(audio_oversampled, work_audio_buffer); /* 192kHz int16_t[128] * -> 4th order CIC decimation by 2, gain of 1 * -> 96kHz int16_t[64] */ - auto audio_2fs = audio_dec_3.execute(audio_4fs, work_audio_buffer); + auto audio_2fs = audio_dec_2.execute(audio_4fs, work_audio_buffer); /* 96kHz int16_t[64] * -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop, gain of 1 @@ -67,6 +68,51 @@ void WidebandFMAudio::execute(buffer_c8_t buffer) { auto audio = audio_filter.execute(audio_2fs, work_audio_buffer); /* -> 48kHz int16_t[32] */ - audio_hpf.execute_in_place(audio); - fill_audio_buffer(audio); + audio_output.write(audio); +} + +void WidebandFMAudio::on_message(const Message* const message) { + switch(message->id) { + case Message::ID::UpdateSpectrum: + case Message::ID::SpectrumStreamingConfig: + channel_spectrum.on_message(message); + break; + + case Message::ID::WFMConfigure: + configure(*reinterpret_cast(message)); + break; + + default: + break; + } +} + +void WidebandFMAudio::configure(const WFMConfigureMessage& message) { + constexpr size_t baseband_fs = 3072000; + + constexpr size_t decim_0_input_fs = baseband_fs; + constexpr size_t decim_0_decimation_factor = 4; + constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0_decimation_factor; + + constexpr size_t decim_1_input_fs = decim_0_output_fs; + constexpr size_t decim_1_decimation_factor = 2; + constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1_decimation_factor; + + constexpr size_t demod_input_fs = decim_1_output_fs; + + constexpr auto spectrum_rate_hz = 50.0f; + spectrum_interval_samples = decim_1_output_fs / spectrum_rate_hz; + spectrum_samples = 0; + + decim_0.configure(message.decim_0_filter.taps, 33554432); + decim_1.configure(message.decim_1_filter.taps, 131072); + channel_filter_pass_f = message.decim_1_filter.pass_frequency_normalized * decim_1_input_fs; + channel_filter_stop_f = message.decim_1_filter.stop_frequency_normalized * decim_1_input_fs; + demod.configure(demod_input_fs, message.deviation); + audio_filter.configure(message.audio_filter.taps); + audio_output.configure(audio_hpf_30hz_config, audio_deemph_2122_6_config); + + channel_spectrum.set_decimation_factor(1); + + configured = true; } diff --git a/firmware/baseband/proc_wfm_audio.hpp b/firmware/baseband/proc_wfm_audio.hpp index 3a0eb65c..5c30c2e9 100644 --- a/firmware/baseband/proc_wfm_audio.hpp +++ b/firmware/baseband/proc_wfm_audio.hpp @@ -24,32 +24,47 @@ #include "baseband_processor.hpp" -#include "channel_decimator.hpp" #include "dsp_decimate.hpp" #include "dsp_demodulate.hpp" -#include "dsp_fir_taps.hpp" -#include "dsp_iir.hpp" -#include "dsp_iir_config.hpp" + +#include "audio_output.hpp" +#include "spectrum_collector.hpp" class WidebandFMAudio : public BasebandProcessor { public: - WidebandFMAudio() { - decimator.set_decimation_factor(ChannelDecimator::DecimationFactor::By4); - } + void execute(const buffer_c8_t& buffer) override; - void execute(buffer_c8_t buffer) override; + void on_message(const Message* const message) override; private: - ChannelDecimator decimator; + std::array dst; + const buffer_c16_t dst_buffer { + dst.data(), + dst.size() + }; + const buffer_s16_t work_audio_buffer { + (int16_t*)dst.data(), + sizeof(dst) / sizeof(int16_t) + }; - dsp::demodulate::FM demod { 768000, 75000 }; + dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0; + dsp::decimate::FIRC16xR16x16Decim2 decim_1; + uint32_t channel_filter_pass_f = 0; + uint32_t channel_filter_stop_f = 0; + + dsp::demodulate::FM demod; dsp::decimate::DecimateBy2CIC4Real audio_dec_1; dsp::decimate::DecimateBy2CIC4Real audio_dec_2; - dsp::decimate::DecimateBy2CIC4Real audio_dec_3; - const fir_taps_real<64>& audio_filter_taps = taps_64_lp_156_198; - dsp::decimate::FIR64AndDecimateBy2Real audio_filter { audio_filter_taps.taps }; + dsp::decimate::FIR64AndDecimateBy2Real audio_filter; - IIRBiquadFilter audio_hpf { audio_hpf_config }; + AudioOutput audio_output; + + SpectrumCollector channel_spectrum; + size_t spectrum_interval_samples = 0; + size_t spectrum_samples = 0; + + bool configured { false }; + void configure(const WFMConfigureMessage& message); }; #endif/*__PROC_WFM_AUDIO_H__*/ diff --git a/firmware/baseband/proc_wideband_spectrum.cpp b/firmware/baseband/proc_wideband_spectrum.cpp index 17f47551..399cf626 100644 --- a/firmware/baseband/proc_wideband_spectrum.cpp +++ b/firmware/baseband/proc_wideband_spectrum.cpp @@ -23,9 +23,6 @@ #include "event_m4.hpp" -#include "i2s.hpp" -using namespace lpc43xx; - #include "dsp_fft.hpp" #include @@ -33,37 +30,45 @@ using namespace lpc43xx; #include -void WidebandSpectrum::execute(buffer_c8_t buffer) { +void WidebandSpectrum::execute(const buffer_c8_t& buffer) { // 2048 complex8_t samples per buffer. // 102.4us per buffer. 20480 instruction cycles per buffer. - static int phase = 0; - if( phase == 0 ) { std::fill(spectrum.begin(), spectrum.end(), 0); } - if( (phase & 7) == 0 ) { + for(size_t i=0; i { buffer.p[i].real(), buffer.p[i].imag() }; - } + spectrum[i] += buffer.p[i + 0]; + spectrum[i] += buffer.p[i + 1024]; } - if( phase == 23 ) { - if( channel_spectrum_request_update == false ) { - fft_swap(spectrum, channel_spectrum); - channel_spectrum_sampling_rate = buffer.sampling_rate; - channel_filter_pass_frequency = 0; - channel_filter_stop_frequency = 0; - channel_spectrum_request_update = true; - events_flag(EVT_MASK_SPECTRUM); - phase = 0; - } + if( phase == 127 ) { + const buffer_c16_t buffer_c16 { + spectrum.data(), + spectrum.size(), + buffer.sampling_rate + }; + channel_spectrum.feed( + buffer_c16, + 0, 0 + ); + phase = 0; } else { phase++; } - - i2s::i2s0::tx_mute(); +} + +void WidebandSpectrum::on_message(const Message* const message) { + switch(message->id) { + case Message::ID::UpdateSpectrum: + case Message::ID::SpectrumStreamingConfig: + channel_spectrum.on_message(message); + break; + + default: + break; + } } diff --git a/firmware/baseband/proc_wideband_spectrum.hpp b/firmware/baseband/proc_wideband_spectrum.hpp index bbbe04ea..5a875735 100644 --- a/firmware/baseband/proc_wideband_spectrum.hpp +++ b/firmware/baseband/proc_wideband_spectrum.hpp @@ -23,6 +23,9 @@ #define __PROC_WIDEBAND_SPECTRUM_H__ #include "baseband_processor.hpp" +#include "spectrum_collector.hpp" + +#include "message.hpp" #include #include @@ -30,12 +33,16 @@ class WidebandSpectrum : public BasebandProcessor { public: - void execute(buffer_c8_t buffer) override; + void execute(const buffer_c8_t& buffer) override; + + void on_message(const Message* const message) override; private: - size_t sample_count = 0; + SpectrumCollector channel_spectrum; - std::array, 256> spectrum; + std::array spectrum; + + size_t phase = 0; }; #endif/*__PROC_WIDEBAND_SPECTRUM_H__*/ diff --git a/firmware/baseband/rssi_dma.cpp b/firmware/baseband/rssi_dma.cpp index 5b060604..91f3b496 100644 --- a/firmware/baseband/rssi_dma.cpp +++ b/firmware/baseband/rssi_dma.cpp @@ -157,7 +157,7 @@ bool is_enabled() { } void disable() { - gpdma_channel.disable_force(); + gpdma_channel.disable(); } rf::rssi::buffer_t wait_for_buffer() { diff --git a/firmware/baseband/rssi_stats_collector.hpp b/firmware/baseband/rssi_stats_collector.hpp index 6ab945bf..7a1cd494 100644 --- a/firmware/baseband/rssi_stats_collector.hpp +++ b/firmware/baseband/rssi_stats_collector.hpp @@ -31,7 +31,7 @@ class RSSIStatisticsCollector { public: template - void process(rf::rssi::buffer_t buffer, Callback callback) { + void process(const rf::rssi::buffer_t& buffer, Callback callback) { auto p = buffer.p; if( p == nullptr ) { return; diff --git a/firmware/baseband/rssi_thread.cpp b/firmware/baseband/rssi_thread.cpp new file mode 100644 index 00000000..0bf15334 --- /dev/null +++ b/firmware/baseband/rssi_thread.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 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 "rssi_thread.hpp" + +#include "rssi.hpp" +#include "rssi_dma.hpp" +#include "rssi_stats_collector.hpp" + +#include "message.hpp" +#include "portapack_shared_memory.hpp" + +WORKING_AREA(rssi_thread_wa, 128); + +Thread* RSSIThread::start(const tprio_t priority) { + return chThdCreateStatic(rssi_thread_wa, sizeof(rssi_thread_wa), + priority, ThreadBase::fn, + this + ); +} + +void RSSIThread::run() { + rf::rssi::init(); + rf::rssi::dma::allocate(4, 400); + + RSSIStatisticsCollector stats; + + while(true) { + // TODO: Place correct sampling rate into buffer returned here: + const auto buffer_tmp = rf::rssi::dma::wait_for_buffer(); + const rf::rssi::buffer_t buffer { + buffer_tmp.p, buffer_tmp.count, sampling_rate + }; + + stats.process( + buffer, + [](const RSSIStatistics& statistics) { + const RSSIStatisticsMessage message { statistics }; + shared_memory.application_queue.push(message); + } + ); + } + + rf::rssi::dma::free(); +} diff --git a/firmware/baseband/rssi_thread.hpp b/firmware/baseband/rssi_thread.hpp new file mode 100644 index 00000000..0a00ef8e --- /dev/null +++ b/firmware/baseband/rssi_thread.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 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 __RSSI_THREAD_H__ +#define __RSSI_THREAD_H__ + +#include "thread_base.hpp" + +#include + +#include + +class RSSIThread : public ThreadBase { +public: + RSSIThread( + ) : ThreadBase { "rssi" } + { + } + + Thread* start(const tprio_t priority); + +private: + void run() override; + + const uint32_t sampling_rate { 400000 }; +}; + +#endif/*__RSSI_THREAD_H__*/ diff --git a/firmware/baseband/spectrum_collector.cpp b/firmware/baseband/spectrum_collector.cpp new file mode 100644 index 00000000..2b661c9a --- /dev/null +++ b/firmware/baseband/spectrum_collector.cpp @@ -0,0 +1,130 @@ +/* + * 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 "spectrum_collector.hpp" + +#include "dsp_fft.hpp" + +#include "utility.hpp" +#include "event_m4.hpp" +#include "portapack_shared_memory.hpp" + +#include "event_m4.hpp" + +#include + +void SpectrumCollector::on_message(const Message* const message) { + switch(message->id) { + case Message::ID::UpdateSpectrum: + update(); + break; + + case Message::ID::SpectrumStreamingConfig: + set_state(*reinterpret_cast(message)); + break; + + default: + break; + } +} + +void SpectrumCollector::set_state(const SpectrumStreamingConfigMessage& message) { + if( message.mode == SpectrumStreamingConfigMessage::Mode::Running ) { + start(); + } else { + stop(); + } +} + +void SpectrumCollector::start() { + streaming = true; + ChannelSpectrumConfigMessage message { &fifo }; + shared_memory.application_queue.push(message); +} + +void SpectrumCollector::stop() { + streaming = false; + fifo.reset_in(); +} + +void SpectrumCollector::set_decimation_factor( + const size_t decimation_factor +) { + channel_spectrum_decimator.set_factor(decimation_factor); +} + +/* TODO: Refactor to register task with idle thread? + * It's sad that the idle thread has to call all the way back here just to + * perform the deferred task on the buffer of data we prepared. + */ + +void SpectrumCollector::feed( + const buffer_c16_t& channel, + const uint32_t filter_pass_frequency, + const uint32_t filter_stop_frequency +) { + // Called from baseband processing thread. + channel_filter_pass_frequency = filter_pass_frequency; + channel_filter_stop_frequency = filter_stop_frequency; + + channel_spectrum_decimator.feed( + channel, + [this](const buffer_c16_t& data) { + this->post_message(data); + } + ); +} + +void SpectrumCollector::post_message(const buffer_c16_t& data) { + // Called from baseband processing thread. + if( streaming && !channel_spectrum_request_update ) { + fft_swap(data, channel_spectrum); + channel_spectrum_sampling_rate = data.sampling_rate; + channel_spectrum_request_update = true; + EventDispatcher::events_flag(EVT_MASK_SPECTRUM); + } +} + +void SpectrumCollector::update() { + // Called from idle thread (after EVT_MASK_SPECTRUM is flagged) + if( streaming && channel_spectrum_request_update ) { + /* Decimated buffer is full. Compute spectrum. */ + fft_c_preswapped(channel_spectrum); + + ChannelSpectrum spectrum; + spectrum.sampling_rate = channel_spectrum_sampling_rate; + spectrum.channel_filter_pass_frequency = channel_filter_pass_frequency; + spectrum.channel_filter_stop_frequency = channel_filter_stop_frequency; + for(size_t i=0; i +#include + +#include "message.hpp" + +class SpectrumCollector { +public: + constexpr SpectrumCollector( + ) : channel_spectrum_decimator { 1 } + { + } + + void on_message(const Message* const message); + + void set_decimation_factor(const size_t decimation_factor); + + void feed( + const buffer_c16_t& channel, + const uint32_t filter_pass_frequency, + const uint32_t filter_stop_frequency + ); + +private: + BlockDecimator<256> channel_spectrum_decimator; + ChannelSpectrumFIFO fifo; + + volatile bool channel_spectrum_request_update { false }; + bool streaming { false }; + std::array, 256> channel_spectrum; + uint32_t channel_spectrum_sampling_rate { 0 }; + uint32_t channel_filter_pass_frequency { 0 }; + uint32_t channel_filter_stop_frequency { 0 }; + + void post_message(const buffer_c16_t& data); + + void set_state(const SpectrumStreamingConfigMessage& message); + void start(); + void stop(); + + void update(); +}; + +#endif/*__SPECTRUM_COLLECTOR_H__*/ diff --git a/firmware/baseband/thread_base.hpp b/firmware/baseband/thread_base.hpp new file mode 100644 index 00000000..9b5e8a99 --- /dev/null +++ b/firmware/baseband/thread_base.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 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 __THREAD_BASE_H__ +#define __THREAD_BASE_H__ + +#include + +class ThreadBase { +public: + constexpr ThreadBase( + const char* const name + ) : name { name } + { + } + +protected: + static msg_t fn(void* arg) { + auto obj = static_cast(arg); + chRegSetThreadName(obj->name); + obj->run(); + + return 0; + } + +private: + const char* const name; + + virtual void run() = 0; +}; + +#endif/*__THREAD_BASE_H__*/ diff --git a/firmware/baseband/touch_dma.cpp b/firmware/baseband/touch_dma.cpp index aa120ff3..ce66617a 100644 --- a/firmware/baseband/touch_dma.cpp +++ b/firmware/baseband/touch_dma.cpp @@ -122,7 +122,7 @@ bool is_enabled() { } void disable() { - gpdma_channel.disable_force(); + gpdma_channel.disable(); } } /* namespace dma */ diff --git a/firmware/chibios-portapack/os/hal/platforms/LPC43xx/lpc43xx.inc b/firmware/chibios-portapack/os/hal/platforms/LPC43xx/lpc43xx.inc index 26a7d07e..fb09b782 100644 --- a/firmware/chibios-portapack/os/hal/platforms/LPC43xx/lpc43xx.inc +++ b/firmware/chibios-portapack/os/hal/platforms/LPC43xx/lpc43xx.inc @@ -1372,43 +1372,6 @@ typedef struct { // ------------------------------------------------------------------------------------------------ -#ifdef __cplusplus - -#define LPC_PERIPHERAL(_t, _i) constexpr auto const LPC_ ## _i = reinterpret_cast(LPC_ ## _i ## _BASE) - -LPC_PERIPHERAL(GPDMA, GPDMA); -LPC_PERIPHERAL(SPIFI, SPIFI); -LPC_PERIPHERAL(SDMMC, SDMMC); -LPC_PERIPHERAL(CREG, CREG); -LPC_PERIPHERAL(RTC, RTC); -LPC_PERIPHERAL(CGU, CGU); -LPC_PERIPHERAL(CCU1, CCU1); -LPC_PERIPHERAL(CCU2, CCU2); -LPC_PERIPHERAL(RGU, RGU); -LPC_PERIPHERAL(USART, USART0); -LPC_PERIPHERAL(UART, UART1); -LPC_PERIPHERAL(SSPx, SSP0); -LPC_PERIPHERAL(TIMER, TIMER0); -LPC_PERIPHERAL(TIMER, TIMER1); -LPC_PERIPHERAL(SCU, SCU); -LPC_PERIPHERAL(GPIO_INT, GPIO_INT); -LPC_PERIPHERAL(I2Cx, I2C0); -LPC_PERIPHERAL(I2S, I2S0); -LPC_PERIPHERAL(I2S, I2S1); -LPC_PERIPHERAL(RITIMER, RITIMER); -LPC_PERIPHERAL(USART, USART2); -LPC_PERIPHERAL(USART, USART3); -LPC_PERIPHERAL(TIMER, TIMER2); -LPC_PERIPHERAL(TIMER, TIMER3); -LPC_PERIPHERAL(SSPx, SSP1); -LPC_PERIPHERAL(I2Cx, I2C1); -LPC_PERIPHERAL(ADCx, ADC0); -LPC_PERIPHERAL(ADCx, ADC1); -LPC_PERIPHERAL(GPIO, GPIO); -LPC_PERIPHERAL(SGPIO, SGPIO); - -#else - #define LPC_GPDMA ((LPC_GPDMA_Type *) LPC_GPDMA_BASE) #define LPC_SPIFI ((LPC_SPIFI_Type *) LPC_SPIFI_BASE) #define LPC_SDMMC ((LPC_SDMMC_Type *) LPC_SDMMC_BASE) @@ -1439,7 +1402,6 @@ LPC_PERIPHERAL(SGPIO, SGPIO); #define LPC_ADC1 ((LPC_ADCx_Type *) LPC_ADC1_BASE) #define LPC_GPIO ((LPC_GPIO_Type *) LPC_GPIO_BASE) #define LPC_SGPIO ((LPC_SGPIO_Type *) LPC_SGPIO_BASE) -#endif #ifdef __cplusplus } diff --git a/firmware/chibios-portapack/os/hal/platforms/LPC43xx_M4/lpc43xx_m4.h b/firmware/chibios-portapack/os/hal/platforms/LPC43xx_M4/lpc43xx_m4.h index 592ed3de..25f56907 100644 --- a/firmware/chibios-portapack/os/hal/platforms/LPC43xx_M4/lpc43xx_m4.h +++ b/firmware/chibios-portapack/os/hal/platforms/LPC43xx_M4/lpc43xx_m4.h @@ -134,34 +134,34 @@ typedef enum IRQn { /* Overload of __SXTB16() to add ROR argument, since using __ROR() as an * argument to the existing __SXTB16() doesn't produce optimum/sane code. */ -__attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __SXTB16(uint32_t rm, uint32_t ror) +__attribute__( ( always_inline ) ) __STATIC_INLINE int32_t __SXTB16(uint32_t rm, uint32_t ror) { - uint32_t rd; + int32_t rd; __ASM volatile ("sxtb16 %0, %1, ror %2" : "=r" (rd) : "r" (rm), "I" (ror)); return rd; } -__attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __SXTH(uint32_t rm, uint32_t ror) +__attribute__( ( always_inline ) ) __STATIC_INLINE int32_t __SXTH(uint32_t rm, uint32_t ror) { - uint32_t rd; + int32_t rd; __ASM volatile ("sxth %0, %1, ror %2" : "=r" (rd) : "r" (rm), "I" (ror)); return rd; } -__attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __SMLATB(uint32_t rm, uint32_t rs, uint32_t rn) { - uint32_t rd; +__attribute__( ( always_inline ) ) __STATIC_INLINE int32_t __SMLATB(uint32_t rm, uint32_t rs, uint32_t rn) { + int32_t rd; __ASM volatile ("smlatb %0, %1, %2, %3" : "=r" (rd) : "r" (rm), "r" (rs), "r" (rn)); return rd; } -__attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __SMLABB(uint32_t rm, uint32_t rs, uint32_t rn) { - uint32_t rd; +__attribute__( ( always_inline ) ) __STATIC_INLINE int32_t __SMLABB(uint32_t rm, uint32_t rs, uint32_t rn) { + int32_t rd; __ASM volatile("smlabb %0, %1, %2, %3" : "=r" (rd) : "r" (rm), "r" (rs), "r" (rn)); return rd; } -__attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __SXTAH(uint32_t rn, uint32_t rm, uint32_t ror) { - uint32_t rd; +__attribute__( ( always_inline ) ) __STATIC_INLINE int32_t __SXTAH(uint32_t rn, uint32_t rm, uint32_t ror) { + int32_t rd; __ASM volatile("sxtah %0, %1, %2, ror %3" : "=r" (rd) : "r" (rn), "r" (rm), "I" (ror)); return rd; } @@ -172,37 +172,37 @@ __attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __BFI(uint32_t rd, u return rd; } -__attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __SMULBB(uint32_t op1, uint32_t op2) { - uint32_t result; +__attribute__( ( always_inline ) ) __STATIC_INLINE int32_t __SMULBB(uint32_t op1, uint32_t op2) { + int32_t result; __ASM volatile ("smulbb %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); return result; } -__attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __SMULBT(uint32_t op1, uint32_t op2) { - uint32_t result; +__attribute__( ( always_inline ) ) __STATIC_INLINE int32_t __SMULBT(uint32_t op1, uint32_t op2) { + int32_t result; __ASM volatile ("smulbt %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); return result; } -__attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __SMULTB(uint32_t op1, uint32_t op2) { - uint32_t result; +__attribute__( ( always_inline ) ) __STATIC_INLINE int32_t __SMULTB(uint32_t op1, uint32_t op2) { + int32_t result; __ASM volatile ("smultb %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); return result; } -__attribute__( ( always_inline ) ) __STATIC_INLINE uint32_t __SMULTT(uint32_t op1, uint32_t op2) { - uint32_t result; +__attribute__( ( always_inline ) ) __STATIC_INLINE int32_t __SMULTT(uint32_t op1, uint32_t op2) { + int32_t result; __ASM volatile ("smultt %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); return result; } #undef __SMLALD -__attribute__( ( always_inline ) ) static inline uint64_t __SMLALD (uint32_t op1, uint32_t op2, uint64_t acc) +__attribute__( ( always_inline ) ) static inline int64_t __SMLALD (uint32_t op1, uint32_t op2, int64_t acc) { union llreg_u{ uint32_t w32[2]; - uint64_t w64; + int64_t w64; } llr; llr.w64 = acc; @@ -213,11 +213,11 @@ __attribute__( ( always_inline ) ) static inline uint64_t __SMLALD (uint32_t op1 #undef __SMLALDX -__attribute__( ( always_inline ) ) static inline uint64_t __SMLALDX (uint32_t op1, uint32_t op2, uint64_t acc) +__attribute__( ( always_inline ) ) static inline int64_t __SMLALDX (uint32_t op1, uint32_t op2, int64_t acc) { union llreg_u{ uint32_t w32[2]; - uint64_t w64; + int64_t w64; } llr; llr.w64 = acc; @@ -228,11 +228,11 @@ __attribute__( ( always_inline ) ) static inline uint64_t __SMLALDX (uint32_t op #undef __SMLSLD -__attribute__( ( always_inline ) ) static inline uint64_t __SMLSLD (uint32_t op1, uint32_t op2, uint64_t acc) +__attribute__( ( always_inline ) ) static inline int64_t __SMLSLD (uint32_t op1, uint32_t op2, int64_t acc) { union llreg_u{ uint32_t w32[2]; - uint64_t w64; + int64_t w64; } llr; llr.w64 = acc; @@ -241,6 +241,14 @@ __attribute__( ( always_inline ) ) static inline uint64_t __SMLSLD (uint32_t op1 return(llr.w64); } +__attribute__( ( always_inline ) ) __STATIC_INLINE int32_t __SMMULR (int32_t op1, int32_t op2) +{ + uint32_t result; + + __ASM volatile ("smmulr %0, %1, %2" : "=r" (result) : "r" (op1), "r" (op2) ); + return(result); +} + #endif /* __cplusplus */ #endif /* __LPC43XX_M4_H */ diff --git a/firmware/common/ais_baseband.cpp b/firmware/common/ais_baseband.cpp index 841bec0b..d137a6a5 100644 --- a/firmware/common/ais_baseband.cpp +++ b/firmware/common/ais_baseband.cpp @@ -20,357 +20,3 @@ */ #include "ais_baseband.hpp" - -#include -#include -#include -#include - -#include "crc.hpp" - -// TODO: Move string formatting elsewhere!!! -#include "ui_widget.hpp" - -namespace baseband { - -template -class FieldReader { -public: - constexpr FieldReader( - const T& data - ) : data { data } - { - } - - uint32_t read(const size_t start_bit, const size_t length) const { - uint32_t value = 0; - for(size_t i=start_bit; i<(start_bit + length); i++) { - value = (value << 1) | data[bit_remap(i)]; - } - return value; - } - -private: - const T& data; - const BitRemap bit_remap { }; -}; - -namespace ais { - -struct BitRemap { - size_t operator()(const size_t bit_index) const { - return bit_index ^ 7; - } -}; - -struct CRCBitRemap { - size_t operator()(const size_t bit_index) const { - return bit_index; - } -}; - -using FieldReader = baseband::FieldReader, BitRemap>; -using CRCFieldReader = baseband::FieldReader, CRCBitRemap>; - -struct PacketLengthRange { - constexpr PacketLengthRange( - ) : min_bytes { 0 }, - max_bytes { 0 } - { - } - - constexpr PacketLengthRange( - const uint16_t min_bits, - const uint16_t max_bits - ) : min_bytes { static_cast(min_bits / 8U) }, - max_bytes { static_cast(max_bits / 8U) } - { - // static_assert((min_bits & 7) == 0, "minimum bits not a multiple of 8"); - // static_assert((max_bits & 7) == 0, "minimum bits not a multiple of 8"); - } - - bool contains(const size_t bit_count) const { - return !is_above(bit_count) && !is_below(bit_count); - } - - bool is_above(const size_t bit_count) const { - return (min() > bit_count); - } - - bool is_below(const size_t bit_count) const { - return (max() < bit_count); - } - - size_t min() const { - return min_bytes * 8; - } - - size_t max() const { - return max_bytes * 8; - } - -private: - const uint8_t min_bytes; - const uint8_t max_bytes; -}; - -static constexpr std::array packet_length_range { { - { 0, 0 }, // 0 - { 168, 168 }, // 1 - { 168, 168 }, // 2 - { 168, 168 }, // 3 - { 168, 168 }, // 4 - { 424, 424 }, // 5 - { 0, 0 }, // 6 - { 0, 0 }, // 7 - { 0, 1008 }, // 8 - { 0, 0 }, // 9 - { 0, 0 }, // 10 - { 0, 0 }, // 11 - { 0, 0 }, // 12 - { 0, 0 }, // 13 - { 0, 0 }, // 14 - { 0, 0 }, // 15 - { 0, 0 }, // 16 - { 0, 0 }, // 17 - { 168, 168 }, // 18 - { 0, 0 }, // 19 - { 72, 160 }, // 20 - { 272, 360 }, // 21 - { 168, 168 }, // 22 - { 160, 160 }, // 23 - { 160, 168 }, // 24 - { 0, 168 }, // 25 - { 0, 0 }, // 26 - { 0, 0 }, // 27 - { 0, 0 }, // 28 - { 0, 0 }, // 29 - { 0, 0 }, // 30 - { 0, 0 }, // 31 -} }; - -struct PacketLengthValidator { - bool operator()(const uint_fast8_t message_id, const size_t length) { - return packet_length_range[message_id].contains(length); - } -}; - -struct PacketTooLong { - bool operator()(const uint_fast8_t message_id, const size_t length) { - return packet_length_range[message_id].is_below(length); - } -}; - -struct CRCCheck { - bool operator()(const std::bitset<1024>& payload, const size_t data_length) { - CRCFieldReader field_crc { payload }; - CRC ais_fcs { 0x1021 }; - - uint16_t crc_calculated = 0xffff; - - for(size_t i=0; i(field.read(start_bit, 27) << 5) / 32; -} - -static int32_t ais_longitude_normalized( - const FieldReader& field, - const size_t start_bit -) { - // Shifting and dividing is to sign-extend the source field. - // TODO: There's probably a more elegant way to do it. - return static_cast(field.read(start_bit, 28) << 4) / 16; -} - -static std::string ais_format_latlon_normalized(const int32_t normalized) { - const int32_t t = (normalized * 5) / 3; - const int32_t degrees = t / (100 * 10000); - const int32_t fraction = std::abs(t) % (100 * 10000); - return ui::to_string_dec_int(degrees) + "." + ui::to_string_dec_int(fraction, 6, '0'); -} - -static std::string ais_format_latitude( - const FieldReader& field, - const size_t start_bit -) { - const auto value = static_cast(field.read(start_bit, 27) << 5) / 32; - return ais_format_latlon_normalized(value); -} - -static std::string ais_format_longitude( - const FieldReader& field, - const size_t start_bit -) { - const auto value = static_cast(field.read(start_bit, 28) << 4) / 16; - return ais_format_latlon_normalized(value); -} - -struct ais_datetime { - uint16_t year; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; -}; - -static ais_datetime ais_get_datetime( - const FieldReader& field, - const size_t start_bit -) { - return { - static_cast(field.read(start_bit + 0, 14)), - static_cast(field.read(start_bit + 14, 4)), - static_cast(field.read(start_bit + 18, 5)), - static_cast(field.read(start_bit + 23, 5)), - static_cast(field.read(start_bit + 28, 6)), - static_cast(field.read(start_bit + 34, 6)), - }; -} - -static std::string ais_format_datetime( - const FieldReader& field, - const size_t start_bit -) { - const auto datetime = ais_get_datetime(field, start_bit); - return ui::to_string_dec_uint(datetime.year, 4) + "/" + - ui::to_string_dec_uint(datetime.month, 2, '0') + "/" + - ui::to_string_dec_uint(datetime.day, 2, '0') + " " + - ui::to_string_dec_uint(datetime.hour, 2, '0') + ":" + - ui::to_string_dec_uint(datetime.minute, 2, '0') + ":" + - ui::to_string_dec_uint(datetime.second, 2, '0'); -} - -static char ais_char_to_ascii(const uint8_t c) { - return (c ^ 32) + 32; -} - -static std::string ais_read_text( - const FieldReader& field, - const size_t start_bit, - const size_t character_count -) { - std::string result; - const size_t character_length = 6; - const size_t end_bit = start_bit + character_count * character_length; - for(size_t i=start_bit; i& payload, const size_t payload_length) { - // TODO: Unstuff here, not in baseband! - - // Subtract end flag (8 bits) - one unstuffing bit (occurs during end flag). - const size_t data_and_fcs_length = payload_length - 7; - - if( data_and_fcs_length < 38 ) { - return { "short " + ui::to_string_dec_uint(data_and_fcs_length, 3), "" }; - } - - const size_t extra_bits = data_and_fcs_length & 7; - if( extra_bits != 0 ) { - return { "extra bits " + ui::to_string_dec_uint(data_and_fcs_length, 3), "" }; - } - - FieldReader field { payload }; - - const auto message_id = field.read(0, 6); - - const size_t data_length = data_and_fcs_length - 16; - PacketLengthValidator packet_length_valid; - if( !packet_length_valid(message_id, data_length) ) { - return { "bad length " + ui::to_string_dec_uint(data_length, 3), "" }; - } - - CRCCheck crc_ok; - if( !crc_ok(payload, data_length) ) { - return { "crc", "" }; - } - - const auto source_id = field.read(8, 30); - std::string result { ui::to_string_dec_uint(message_id, 2) + " " + ui::to_string_dec_uint(source_id, 10) }; - - switch(message_id) { - case 1: - case 2: - case 3: - { - const auto navigational_status = field.read(38, 4); - result += " " + ais_format_navigational_status(navigational_status); - result += " " + ais_format_latlon_normalized(ais_latitude_normalized(field, 89)); - result += " " + ais_format_latlon_normalized(ais_longitude_normalized(field, 61)); - } - break; - - case 4: - { - result += " " + ais_format_datetime(field, 38); - result += " " + ais_format_latlon_normalized(ais_latitude_normalized(field, 107)); - result += " " + ais_format_latlon_normalized(ais_longitude_normalized(field, 79)); - } - break; - - case 5: - { - const auto call_sign = ais_read_text(field, 70, 7); - const auto name = ais_read_text(field, 112, 20); - const auto destination = ais_read_text(field, 302, 20); - result += " \"" + call_sign + "\" \"" + name + "\" \"" + destination + "\""; - } - break; - - case 21: - { - const auto name = ais_read_text(field, 43, 20); - result += " \"" + name + "\" " + ais_format_latitude(field, 192) + " " + ais_format_longitude(field, 164); - } - break; - - default: - break; - } - - return { "OK", result }; -} - -} /* namespace ais */ -} /* namespace baseband */ diff --git a/firmware/common/ais_baseband.hpp b/firmware/common/ais_baseband.hpp index 2be4753b..1d7a2aa6 100644 --- a/firmware/common/ais_baseband.hpp +++ b/firmware/common/ais_baseband.hpp @@ -36,31 +36,19 @@ namespace ais { // cleaning up ISI. // Translate+RRC filter -// sample=76.8k, deviation=2400, b=0.5, symbol=9600 -// Length: 32 taps, 4 symbols, 1 cycle of sinusoid -constexpr std::array, 32> rrc_taps_76k8_4t_p { { - { 5.3368736990e-03f, 0.0000000000e+00f }, { 4.6850211151e-03f, 9.3190864122e-04f }, - { 1.7970187432e-03f, 7.4434953528e-04f }, { -2.5478608559e-03f, -1.7024261963e-03f }, - { -6.6710920858e-03f, -6.6710920858e-03f }, { -8.6960320791e-03f, -1.3014531722e-02f }, - { -7.5474785839e-03f, -1.8221225159e-02f }, { -3.8115552023e-03f, -1.9161981995e-02f }, - { -8.1697309033e-19f, -1.3342183083e-02f }, { 3.6940784220e-05f, -1.8571386338e-04f }, - { -7.5474785839e-03f, 1.8221225159e-02f }, { -2.4954265902e-02f, 3.7346698152e-02f }, - { -5.1450054092e-02f, 5.1450054092e-02f }, { -8.3018814119e-02f, 5.5471398140e-02f }, - { -1.1321218147e-01f, 4.6894020990e-02f }, { -1.3498960022e-01f, 2.6851100952e-02f }, - { -1.4292666316e-01f, 1.7503468055e-17f }, { -1.3498960022e-01f, -2.6851100952e-02f }, - { -1.1321218147e-01f, -4.6894020990e-02f }, { -8.3018814119e-02f, -5.5471398140e-02f }, - { -5.1450054092e-02f, -5.1450054092e-02f }, { -2.4954265902e-02f, -3.7346698152e-02f }, - { -7.5474785839e-03f, -1.8221225159e-02f }, { 3.6940784220e-05f, 1.8571386338e-04f }, - { 2.4509192710e-18f, 1.3342183083e-02f }, { -3.8115552023e-03f, 1.9161981995e-02f }, - { -7.5474785839e-03f, 1.8221225159e-02f }, { -8.6960320791e-03f, 1.3014531722e-02f }, - { -6.6710920858e-03f, 6.6710920858e-03f }, { -2.5478608559e-03f, 1.7024261963e-03f }, - { 1.7970187432e-03f, -7.4434953528e-04f }, { 4.6850211151e-03f, -9.3190864122e-04f }, +// sample=38.4k, deviation=2400, b=0.5, symbol=9600 +// Length: 16 taps, 4 symbols, 1 cycles of sinusoid +constexpr std::array, 16> rrc_taps_38k4_4t_p { { + { 1.0619794019e-02f, 0.0000000000e+00f }, { 3.5758705854e-03f, 1.4811740938e-03f }, + { -1.3274742629e-02f, -1.3274742629e-02f }, { -1.5018657262e-02f, -3.6258246051e-02f }, + { 0.0000000000e+00f, -2.6549484581e-02f }, { -1.5018657262e-02f, 3.6258246051e-02f }, + { -1.0237997393e-01f, 1.0237997393e-01f }, { -2.2527985355e-01f, 9.3313970669e-02f }, + { -2.8440842032e-01f, 0.0000000000e+00f }, { -2.2527985355e-01f, -9.3313970669e-02f }, + { -1.0237997393e-01f, -1.0237997393e-01f }, { -1.5018657262e-02f, -3.6258246051e-02f }, + { 0.0000000000e+00f, 2.6549484581e-02f }, { -1.5018657262e-02f, 3.6258246051e-02f }, + { -1.3274742629e-02f, 1.3274742629e-02f }, { 3.5758705854e-03f, -1.4811740938e-03f }, } }; -using decoded_packet = std::pair; - -decoded_packet packet_decode(const std::bitset<1024>& data, const size_t data_length); - } /* namespace ais */ } /* namespace baseband */ diff --git a/firmware/common/ais_packet.cpp b/firmware/common/ais_packet.cpp new file mode 100644 index 00000000..53159ff2 --- /dev/null +++ b/firmware/common/ais_packet.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2014 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 "ais_packet.hpp" + +#include "crc.hpp" + +#include + +namespace ais { + +struct PacketLengthRange { + constexpr PacketLengthRange( + ) : min_bytes { 0 }, + max_bytes { 0 } + { + } + + constexpr PacketLengthRange( + const uint16_t min_bits, + const uint16_t max_bits + ) : min_bytes { static_cast(min_bits / 8U) }, + max_bytes { static_cast(max_bits / 8U) } + { + // static_assert((min_bits & 7) == 0, "minimum bits not a multiple of 8"); + // static_assert((max_bits & 7) == 0, "minimum bits not a multiple of 8"); + } + + constexpr bool contains(const size_t bit_count) const { + return !is_above(bit_count) && !is_below(bit_count); + } + + constexpr bool is_above(const size_t bit_count) const { + return (min() > bit_count); + } + + constexpr bool is_below(const size_t bit_count) const { + return (max() < bit_count); + } + + constexpr size_t min() const { + return min_bytes * 8; + } + + constexpr size_t max() const { + return max_bytes * 8; + } + +private: + const uint8_t min_bytes; + const uint8_t max_bytes; +}; + +static constexpr std::array packet_length_range { { + { 0, 0 }, // 0 + { 168, 168 }, // 1 + { 168, 168 }, // 2 + { 168, 168 }, // 3 + { 168, 168 }, // 4 + { 424, 424 }, // 5 + { 0, 0 }, // 6 + { 0, 0 }, // 7 + { 0, 1008 }, // 8 + { 0, 0 }, // 9 + { 0, 0 }, // 10 + { 0, 0 }, // 11 + { 0, 0 }, // 12 + { 0, 0 }, // 13 + { 0, 0 }, // 14 + { 0, 0 }, // 15 + { 0, 0 }, // 16 + { 0, 0 }, // 17 + { 168, 168 }, // 18 + { 0, 0 }, // 19 + { 72, 160 }, // 20 + { 272, 360 }, // 21 + { 168, 168 }, // 22 + { 160, 160 }, // 23 + { 160, 168 }, // 24 + { 0, 168 }, // 25 + { 0, 0 }, // 26 + { 0, 0 }, // 27 + { 0, 0 }, // 28 + { 0, 0 }, // 29 + { 0, 0 }, // 30 + { 0, 0 }, // 31 +} }; + +struct PacketLengthValidator { + constexpr bool operator()(const uint_fast8_t message_id, const size_t length) const { + return packet_length_range[message_id].contains(length); + } +}; + +struct PacketTooLong { + constexpr bool operator()(const uint_fast8_t message_id, const size_t length) const { + return packet_length_range[message_id].is_below(length); + } +}; + +static constexpr char char_to_ascii(const uint8_t c) { + return (c ^ 32) + 32; +} + +size_t Packet::length() const { + return packet_.size(); +} + +bool Packet::is_valid() const { + return length_valid() && crc_ok(); +} + +Timestamp Packet::received_at() const { + return packet_.timestamp(); +} + +uint32_t Packet::message_id() const { + return field_.read(0, 6); +} + +MMSI Packet::user_id() const { + return field_.read(8, 30); +} + +MMSI Packet::source_id() const { + return field_.read(8, 30); +} + +uint32_t Packet::read(const size_t start_bit, const size_t length) const { + return field_.read(start_bit, length); +} + +std::string Packet::text( + const size_t start_bit, + const size_t character_count +) const { + std::string result; + result.reserve(character_count); + + const size_t character_length = 6; + const size_t end_bit = start_bit + character_count * character_length; + for(size_t i=start_bit; i(field_.read(start_bit + 0, 14)), + static_cast(field_.read(start_bit + 14, 4)), + static_cast(field_.read(start_bit + 18, 5)), + static_cast(field_.read(start_bit + 23, 5)), + static_cast(field_.read(start_bit + 28, 6)), + static_cast(field_.read(start_bit + 34, 6)), + }; +} + +Latitude Packet::latitude(const size_t start_bit) const { + return field_.read(start_bit, 27); +} + +Longitude Packet::longitude(const size_t start_bit) const { + return field_.read(start_bit, 28); +} + +bool Packet::crc_ok() const { + CRCReader field_crc { packet_ }; + CRC ais_fcs { 0x1021, 0xffff, 0xffff }; + + for(size_t i=0; i +#include +#include + +namespace ais { + +struct DateTime { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +}; + +template +struct LatLonBase { + constexpr LatLonBase( + ) : LatLonBase { raw_not_available } + { + } + + constexpr LatLonBase( + const int32_t raw + ) : raw_ { raw } + { + } + + constexpr LatLonBase( + const LatLonBase& other + ) : raw_ { other.raw_ } + { + } + + int32_t normalized() const { + return static_cast(raw() << sign_extend_shift) / (1 << sign_extend_shift); + } + + int32_t raw() const { + return raw_; + } + + bool is_not_available() const { + return raw() == raw_not_available; + } + + bool is_valid() const { + return (normalized() >= raw_valid_min) && (normalized() <= raw_valid_max); + } + +private: + int32_t raw_; + + static constexpr size_t sign_extend_shift = 32 - FieldSize; + + static constexpr int32_t raw_not_available = NAValue; + static constexpr int32_t raw_valid_min = -DegMax * 60 * 10000; + static constexpr int32_t raw_valid_max = DegMax * 60 * 10000; +}; + +using Latitude = LatLonBase<27, 90, 0x3412140>; +using Longitude = LatLonBase<28, 180, 0x6791AC0>; + +using RateOfTurn = int8_t; +using SpeedOverGround = uint16_t; +using CourseOverGround = uint16_t; +using TrueHeading = uint16_t; + +using MMSI = uint32_t; + +class Packet { +public: + constexpr Packet( + const baseband::Packet& packet + ) : packet_ { packet }, + field_ { packet_ } + { + } + + size_t length() const; + + bool is_valid() const; + + Timestamp received_at() const; + + uint32_t message_id() const; + MMSI user_id() const; + MMSI source_id() const; + + uint32_t read(const size_t start_bit, const size_t length) const; + + std::string text(const size_t start_bit, const size_t character_count) const; + + DateTime datetime(const size_t start_bit) const; + + Latitude latitude(const size_t start_bit) const; + Longitude longitude(const size_t start_bit) const; + + bool crc_ok() const; + +private: + using Reader = FieldReader; + using CRCReader = FieldReader; + + const baseband::Packet packet_; + const Reader field_; + + const size_t fcs_length = 16; + + size_t data_and_fcs_length() const; + size_t data_length() const; + + bool length_valid() const; +}; + +} /* namespace ais */ + +#endif/*__AIS_PACKET_H__*/ diff --git a/firmware/common/baseband.hpp b/firmware/common/baseband.hpp index 6bd9c85f..dfeef046 100644 --- a/firmware/common/baseband.hpp +++ b/firmware/common/baseband.hpp @@ -35,9 +35,6 @@ enum class Direction { Transmit = 1, }; -buffer_t wait_for_rx_buffer(); -buffer_t wait_for_tx_buffer(); - } /* namespace baseband */ #endif/*__BASEBAND_H__*/ diff --git a/firmware/common/baseband_packet.hpp b/firmware/common/baseband_packet.hpp new file mode 100644 index 00000000..f5bf161c --- /dev/null +++ b/firmware/common/baseband_packet.hpp @@ -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. + */ + +#ifndef __BASEBAND_PACKET_H__ +#define __BASEBAND_PACKET_H__ + +#include "baseband.hpp" + +#include +#include + +namespace baseband { + +class Packet { +public: + void set_timestamp(const Timestamp& value) { + timestamp_ = value; + } + + Timestamp timestamp() const { + return timestamp_; + } + + void add(const bool symbol) { + if( count < capacity() ) { + data[count++] = symbol; + } + } + + uint_fast8_t operator[](const size_t index) const { + return (index < size()) ? data[index] : 0; + } + + size_t size() const { + return count; + } + + size_t capacity() const { + return data.size(); + } + + void clear() { + count = 0; + } + +private: + std::bitset<1408> data; + Timestamp timestamp_ { }; + size_t count { 0 }; +}; + +} /* namespace baseband */ + +#endif/*__BASEBAND_PACKET_H__*/ diff --git a/firmware/application/baseband_sgpio.cpp b/firmware/common/baseband_sgpio.cpp similarity index 100% rename from firmware/application/baseband_sgpio.cpp rename to firmware/common/baseband_sgpio.cpp diff --git a/firmware/application/baseband_sgpio.hpp b/firmware/common/baseband_sgpio.hpp similarity index 100% rename from firmware/application/baseband_sgpio.hpp rename to firmware/common/baseband_sgpio.hpp diff --git a/firmware/common/bit_pattern.hpp b/firmware/common/bit_pattern.hpp index 33a3616a..8014e022 100644 --- a/firmware/common/bit_pattern.hpp +++ b/firmware/common/bit_pattern.hpp @@ -31,12 +31,12 @@ public: history = (history << 1) | (bit & 1); } - uint32_t value() const { + uint64_t value() const { return history; } private: - uint32_t history { 0 }; + uint64_t history { 0 }; }; class BitPattern { @@ -49,24 +49,24 @@ public: } constexpr BitPattern( - const uint32_t code, + const uint64_t code, const size_t code_length, const size_t maximum_hanning_distance = 0 ) : code_ { code }, - mask_ { (1U << code_length) - 1U }, + mask_ { (1ULL << code_length) - 1ULL }, maximum_hanning_distance_ { maximum_hanning_distance } { } bool operator()(const BitHistory& history, const size_t) const { const auto delta_bits = (history.value() ^ code_) & mask_; - const size_t count = __builtin_popcountl(delta_bits); + const size_t count = __builtin_popcountll(delta_bits); return (count <= maximum_hanning_distance_); } private: - uint32_t code_; - uint32_t mask_; + uint64_t code_; + uint64_t mask_; size_t maximum_hanning_distance_; }; diff --git a/firmware/common/buffer.hpp b/firmware/common/buffer.hpp index cfde42c3..2b3d7170 100644 --- a/firmware/common/buffer.hpp +++ b/firmware/common/buffer.hpp @@ -25,30 +25,77 @@ #include #include +/* LPC43xx RTC structure. Avoiding using the ChibiOS-defined structure because + * it pulls in all sorts of dependencies and initialization and other stuff that + * the M0 needs to remain in control of. + * + * But yes, this is a hack, and something better is needed. It's too tangled of + * a knot to tackle at the moment, though... + */ +#if defined(LPC43XX_M4) +#include "lpc43xx_m4.h" + +struct Timestamp { + uint32_t tv_date { 0 }; + uint32_t tv_time { 0 }; + + static Timestamp now() { + // Code stolen from LPC43xx rtc_lld.c + Timestamp timestamp; + do { + timestamp.tv_time = LPC_RTC->CTIME0; + timestamp.tv_date = LPC_RTC->CTIME1; + } while( (timestamp.tv_time != LPC_RTC->CTIME0) || (timestamp.tv_date != LPC_RTC->CTIME1) ); + return timestamp; + } +}; +#endif + +#if defined(LPC43XX_M0) +#include "lpc43xx_cpp.hpp" + +using Timestamp = lpc43xx::rtc::RTC; +#endif + template struct buffer_t { T* const p; const size_t count; const uint32_t sampling_rate; + const Timestamp timestamp; constexpr buffer_t( - T* const p, - const size_t count - ) : p { p }, - count { count }, - sampling_rate { 0 } + ) : p { nullptr }, + count { 0 }, + sampling_rate { 0 }, + timestamp { } { - }; + } + + constexpr buffer_t( + const buffer_t& other + ) : p { other.p }, + count { other.count }, + sampling_rate { other.sampling_rate }, + timestamp { other.timestamp } + { + } constexpr buffer_t( T* const p, const size_t count, - const uint32_t sampling_rate + const uint32_t sampling_rate = 0, + const Timestamp timestamp = { } ) : p { p }, count { count }, - sampling_rate { sampling_rate } + sampling_rate { sampling_rate }, + timestamp { timestamp } { - }; + } + + operator bool() const { + return (p != nullptr); + } }; #endif/*__BUFFER_H__*/ diff --git a/firmware/common/complex.hpp b/firmware/common/complex.hpp index d9ceb139..645a9b43 100644 --- a/firmware/common/complex.hpp +++ b/firmware/common/complex.hpp @@ -66,8 +66,8 @@ public: private: union { - int8_t _v[2]; - uint16_t _rep; + value_type _v[2]; + rep_type _rep; }; }; @@ -101,14 +101,28 @@ public: void real(int16_t v) { _v[0] = v; } void imag(int16_t v) { _v[1] = v; } + template + complex& operator+=(const complex& other) { + _v[0] += other.real(); + _v[1] += other.imag(); + return *this; + } + constexpr uint32_t __rep() const { return _rep; } + constexpr operator std::complex() const { + return { + static_cast(_v[0]), + static_cast(_v[1]) + }; + } + private: union { - int16_t _v[2]; - uint32_t _rep; + value_type _v[2]; + rep_type _rep; }; }; diff --git a/firmware/common/crc.hpp b/firmware/common/crc.hpp index 0b825dc2..ab10455d 100644 --- a/firmware/common/crc.hpp +++ b/firmware/common/crc.hpp @@ -25,61 +25,74 @@ #include #include +/* Inspired by + * http://www.barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code + * + * ...then munged into a shape resembling boost::crc_basic. + * http://www.boost.org/doc/libs/release/libs/crc/ + */ + template class CRC { public: constexpr CRC( - const T polynomial/*, - const T initial*/ - ) : polynomial { polynomial }/*, - initial { initial }*/ + const T truncated_polynomial, + const T initial_remainder = 0, + const T final_xor_value = 0 + ) : truncated_polynomial { truncated_polynomial }, + initial_remainder { initial_remainder }, + final_xor_value { final_xor_value }, + remainder { initial_remainder } { - // CRC LSB must always be 1 } -/* - template - T calculate(const U& bits, const size_t length) { - if( length > bits.size() ) { - // Exception. - return 0; - } - T crc = 0; - - for(size_t i=0; i0; --bit) { + for(size_t bit=0; bit<8; bit++) { if( remainder & top_bit() ) { - remainder = (remainder << 1) ^ polynomial; + remainder = (remainder << 1) ^ truncated_polynomial; } else { remainder = (remainder << 1); } } - return remainder; } -/* - T calculate(const uint8_t* const data, const size_t length) { - T remainder = initial; - for(size_t byte=0; byte& data) { template void fft_c_preswapped(std::array& data) { static_assert(power_of_two(N), "only defined for N == power of two"); + constexpr auto K = log_2(N); + + constexpr size_t K_max = 8; + static_assert(K <= K_max, "No FFT twiddle factors for K > 8"); + static constexpr std::array, K_max> wp_table { { + { -2.0f, 0.0f }, + { -1.0f, -1.0f }, + { -0.2928932188134524756f, -0.7071067811865475244f }, + { -0.076120467488713243872f, -0.38268343236508977173f }, + { -0.019214719596769550874f, -0.19509032201612826785f }, + { -0.0048152733278031137552f, -0.098017140329560601994f }, + { -0.0012045437948276072852f, -0.049067674327418014255f }, + { -0.00030118130379577988423f, -0.024541228522912288032f }, + } }; /* Provide data to this function, pre-swapped. */ - for(size_t mmax = 1; N > mmax; mmax <<= 1) { - const float theta = -pi / mmax; - const float wtemp = sin_f32(0.5f * theta); - const T wp { - -2.0f * wtemp * wtemp, - sin_f32(theta) - }; + for(size_t k = 0; k < log_2(N); k++) { + const size_t mmax = 1 << k; + const auto wp = wp_table[k]; T w { 1.0f, 0.0f }; for(size_t m = 0; m < mmax; ++m) { for(size_t i = m; i < N; i += mmax * 2) { diff --git a/firmware/baseband/dsp_fir_taps.cpp b/firmware/common/dsp_fir_taps.cpp similarity index 100% rename from firmware/baseband/dsp_fir_taps.cpp rename to firmware/common/dsp_fir_taps.cpp diff --git a/firmware/common/dsp_fir_taps.hpp b/firmware/common/dsp_fir_taps.hpp new file mode 100644 index 00000000..c043cab9 --- /dev/null +++ b/firmware/common/dsp_fir_taps.hpp @@ -0,0 +1,230 @@ +/* + * 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 __DSP_FIR_TAPS_H__ +#define __DSP_FIR_TAPS_H__ + +#include +#include + +#include "complex.hpp" + +template +struct fir_taps_real { + float pass_frequency_normalized; + float stop_frequency_normalized; + std::array taps; +}; + +// NBFM 16K0F3E emission type ///////////////////////////////////////////// + +// IFIR image-reject filter: fs=3072000, pass=8000, stop=344000, decim=8, fout=384000 +constexpr fir_taps_real<24> taps_16k0_decim_0 { + .pass_frequency_normalized = 8000.0f / 3072000.0f, + .stop_frequency_normalized = 344000.0f / 3072000.0f, + .taps = { { + 1, 67, 165, 340, 599, 944, 1361, 1820, + 2278, 2684, 2988, 3152, 3152, 2988, 2684, 2278, + 1820, 1361, 944, 599, 340, 165, 67, 1, + } }, +}; + +// IFIR prototype filter: fs=384000, pass=8000, stop=40000, decim=8, fout=48000 +constexpr fir_taps_real<32> taps_16k0_decim_1 { + .pass_frequency_normalized = 8000.0f / 384000.0f, + .stop_frequency_normalized = 40000.0f / 384000.0f, + .taps = { { + -26, -125, -180, -275, -342, -359, -286, -90, + 250, 733, 1337, 2011, 2688, 3289, 3740, 3982, + 3982, 3740, 3289, 2688, 2011, 1337, 733, 250, + -90, -286, -359, -342, -275, -180, -125, -26, + } }, +}; + +// Channel filter: fs=48000, pass=8000, stop=12400, decim=1, fout=48000 +constexpr fir_taps_real<32> taps_16k0_channel { + .pass_frequency_normalized = 8000.0f / 48000.0f, + .stop_frequency_normalized = 12400.0f / 48000.0f, + .taps = { { + -73, -285, -376, -8, 609, 538, -584, -1387, + -148, 2173, 1959, -2146, -5267, -297, 12915, 24737, + 24737, 12915, -297, -5267, -2146, 1959, 2173, -148, + -1387, -584, 538, 609, -8, -376, -285, -73, + } }, +}; + +// NBFM 11K0F3E emission type ///////////////////////////////////////////// + +// IFIR image-reject filter: fs=3072000, pass=5500, stop=341500, decim=8, fout=384000 +constexpr fir_taps_real<24> taps_11k0_decim_0 { + .pass_frequency_normalized = 5500.0f / 3072000.0f, + .stop_frequency_normalized = 341500.0f / 3072000.0f, + .taps = { { + 38, 102, 220, 406, 668, 1004, 1397, 1822, + 2238, 2603, 2875, 3020, 3020, 2875, 2603, 2238, + 1822, 1397, 1004, 668, 406, 220, 102, 38, + } }, +}; + +// IFIR prototype filter: fs=384000, pass=5500, stop=42500, decim=8, fout=48000 +constexpr fir_taps_real<32> taps_11k0_decim_1 { + .pass_frequency_normalized = 5500.0f / 384000.0f, + .stop_frequency_normalized = 42500.0f / 384000.0f, + .taps = { { + -42, -87, -157, -234, -298, -318, -255, -75, + 246, 713, 1306, 1976, 2656, 3265, 3724, 3971, + 3971, 3724, 3265, 2656, 1976, 1306, 713, 246, + -75, -255, -318, -298, -234, -157, -87, -42, + } }, +}; + +// Channel filter: fs=48000, pass=5500, stop=8900, decim=1, fout=48000 +constexpr fir_taps_real<32> taps_11k0_channel { + .pass_frequency_normalized = 5500.0f / 48000.0f, + .stop_frequency_normalized = 8900.0f / 48000.0f, + .taps = { { + -68, -345, -675, -867, -582, 247, 1222, 1562, + 634, -1379, -3219, -3068, 310, 6510, 13331, 17795, + 17795, 13331, 6510, 310, -3068, -3219, -1379, 634, + 1562, 1222, 247, -582, -867, -675, -345, -68, + } }, +}; + +/// NBFM 8K50F3E emission type //////////////////////////////////////////// + +// IFIR image-reject filter: fs=3072000, pass=4250, stop=340250, decim=8, fout=384000 +constexpr fir_taps_real<24> taps_4k25_decim_0 { + .pass_frequency_normalized = 4250.0f / 3072000.0f, + .stop_frequency_normalized = 340250.0f / 3072000.0f, + .taps = { { + 38, 103, 222, 409, 671, 1006, 1399, 1821, + 2236, 2599, 2868, 3012, 3012, 2868, 2599, 2236, + 1821, 1399, 1006, 671, 409, 222, 103, 38, + } }, +}; + +// IFIR prototype filter: fs=384000, pass=4250, stop=43750, decim=8, fout=48000 +constexpr fir_taps_real<32> taps_4k25_decim_1 { + .pass_frequency_normalized = 4250.0f / 384000.0f, + .stop_frequency_normalized = 43750.0f / 384000.0f, + .taps = { { + -33, -74, -139, -214, -280, -306, -254, -87, + 222, 682, 1274, 1951, 2644, 3268, 3741, 3996, + 3996, 3741, 3268, 2644, 1951, 1274, 682, 222, + -87, -254, -306, -280, -214, -139, -74, -33, + } }, +}; + +// Channel filter: fs=48000, pass=4250, stop=7900, decim=1, fout=48000 +constexpr fir_taps_real<32> taps_4k25_channel { + .pass_frequency_normalized = 4250.0f / 48000.0f, + .stop_frequency_normalized = 7900.0f / 48000.0f, + .taps = { { + -58, -14, 153, 484, 871, 1063, 770, -141, + -1440, -2488, -2435, -614, 3035, 7771, 12226, 14927, + 14927, 12226, 7771, 3035, -614, -2435, -2488, -1440, + -141, 770, 1063, 871, 484, 153, -14, -58, + } }, +}; + +// DSB AM 6K00A3E emission type /////////////////////////////////////////// + +// IFIR image-reject filter: fs=3072000, pass=3000, stop=339000, decim=8, fout=384000 +constexpr fir_taps_real<24> taps_6k0_decim_0 { + .pass_frequency_normalized = 3000.0f / 3072000.0f, + .stop_frequency_normalized = 339000.0f / 3072000.0f, + .taps = { { + 39, 104, 224, 412, 674, 1008, 1400, 1821, + 2234, 2594, 2863, 3006, 3006, 2863, 2594, 2234, + 1821, 1400, 1008, 674, 412, 224, 104, 39, + } }, +}; + +// IFIR prototype filter: fs=384000, pass=3000, stop=45000, decim=8, fout=48000 +constexpr fir_taps_real<32> taps_6k0_decim_1 { + .pass_frequency_normalized = 3000.0f / 384000.0f, + .stop_frequency_normalized = 45000.0f / 384000.0f, + .taps = { { + -26, -63, -123, -195, -263, -295, -253, -99, + 199, 651, 1242, 1927, 2633, 3273, 3760, 4023, + 4023, 3760, 3273, 2633, 1927, 1242, 651, 199, + -99, -253, -295, -263, -195, -123, -63, -26, + } }, +}; + +// Channel filter: fs=48000, pass=3000, stop=6700, decim=1, fout=48000 +constexpr fir_taps_real<32> taps_6k0_channel { + .pass_frequency_normalized = 3000.0f / 48000.0f, + .stop_frequency_normalized = 6700.0f / 48000.0f, + .taps = { { + 95, 178, 247, 208, -21, -474, -1080, -1640, + -1857, -1411, -83, 2134, 4978, 7946, 10413, 11815, + 11815, 10413, 7946, 4978, 2134, -83, -1411, -1857, + -1640, -1080, -474, -21, 208, 247, 178, 95, + } }, +}; + +// WFM 200KF8E emission type ////////////////////////////////////////////// + +// IFIR image-reject filter: fs=3072000, pass=100000, stop=484000, decim=4, fout=768000 +constexpr fir_taps_real<24> taps_200k_wfm_decim_0 = { + .pass_frequency_normalized = 100000.0f / 3072000.0f, + .stop_frequency_normalized = 484000.0f / 3072000.0f, + .taps = { { + 48, -18, -151, -364, -557, -548, -139, 789, + 2187, 3800, 5230, 6071, 6071, 5230, 3800, 2187, + 789, -139, -548, -557, -364, -151, -18, 48, + } }, +}; + +// IFIR prototype filter: fs=768000, pass=100000, stop=284000, decim=2, fout=384000 +constexpr fir_taps_real<16> taps_200k_wfm_decim_1 = { + .pass_frequency_normalized = 100000.0f / 768000.0f, + .stop_frequency_normalized = 284000.0f / 768000.0f, + .taps = { { + -67, -123, 388, 622, -1342, -2185, 4599, 14486, + 14486, 4599, -2185, -1342, 622, 388, -123, -67, + } }, +}; + +/* Wideband audio filter */ +/* 96kHz int16_t input + * -> FIR filter, <15kHz (0.156fs) pass, >19kHz (0.198fs) stop + * -> 48kHz int16_t output, gain of 1.0 (I think). + * Padded to multiple of four taps for unrolled FIR code. + * sum(abs(taps)): 125270 + */ +constexpr fir_taps_real<64> taps_64_lp_156_198 { + .pass_frequency_normalized = 0.156f, + .stop_frequency_normalized = 0.196f, + .taps = { { + -27, 166, 104, -36, -174, -129, 109, 287, + 148, -232, -430, -130, 427, 597, 49, -716, + -778, 137, 1131, 957, -493, -1740, -1121, 1167, + 2733, 1252, -2633, -4899, -1336, 8210, 18660, 23254, + 18660, 8210, -1336, -4899, -2633, 1252, 2733, 1167, + -1121, -1740, -493, 957, 1131, 137, -778, -716, + 49, 597, 427, -130, -430, -232, 148, 287, + 109, -129, -174, -36, 104, 166, -27, 0, + } }, +}; + +#endif/*__DSP_FIR_TAPS_H__*/ diff --git a/firmware/common/dsp_types.hpp b/firmware/common/dsp_types.hpp index 56fbe3d7..b27e6b40 100644 --- a/firmware/common/dsp_types.hpp +++ b/firmware/common/dsp_types.hpp @@ -30,5 +30,7 @@ using buffer_c8_t = buffer_t; using buffer_c16_t = buffer_t; using buffer_s16_t = buffer_t; +using buffer_c32_t = buffer_t; +using buffer_f32_t = buffer_t; #endif/*__DSP_TYPES_H__*/ diff --git a/firmware/common/ert_packet.cpp b/firmware/common/ert_packet.cpp new file mode 100644 index 00000000..da2a6124 --- /dev/null +++ b/firmware/common/ert_packet.cpp @@ -0,0 +1,96 @@ +/* + * 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 "ert_packet.hpp" + +#include "crc.hpp" + +namespace ert { + +size_t Packet::length() const { + return decoder_.symbols_count(); +} + +bool Packet::is_valid() const { + return true; +} + +Timestamp Packet::received_at() const { + return packet_.timestamp(); +} + +Packet::Type Packet::type() const { + return type_; +} + +ID Packet::id() const { + if( type() == Type::SCM ) { + const auto msb = reader_.read(0, 2); + const auto lsb = reader_.read(35, 24); + return (msb << 24) | lsb; + } + if( type() == Type::IDM ) { + return reader_.read(5 * 8, 32); + } + return invalid_id; +} + +Consumption Packet::consumption() const { + if( type() == Type::SCM ) { + return reader_.read(11, 24); + } + if( type() == Type::IDM ) { + return reader_.read(25 * 8, 32); + } + return invalid_consumption; +} + +ManchesterFormatted Packet::symbols_formatted() const { + return format_manchester(decoder_); +} + +bool Packet::crc_ok() const { + switch(type()) { + case Type::SCM: return crc_ok_scm(); + case Type::IDM: return crc_ok_idm(); + default: return false; + } +} + +bool Packet::crc_ok_scm() const { + CRC ert_bch { 0x6f63 }; + size_t start_bit = 5; + ert_bch.process_byte(reader_.read(0, start_bit)); + for(size_t i=start_bit; i ert_crc_ccitt { 0x1021, 0xffff, 0x1d0f }; + for(size_t i=0; i +#include + +#include "field_reader.hpp" +#include "baseband_packet.hpp" +#include "manchester.hpp" + +namespace ert { + +using ID = uint32_t; +using Consumption = uint32_t; + +class Packet { +public: + enum class Type : uint32_t { + Unknown = 0, + IDM = 1, + SCM = 2, + }; + + Packet( + const Type type, + const baseband::Packet& packet + ) : packet_ { packet }, + decoder_ { packet_ }, + reader_ { decoder_ }, + type_ { type } + { + } + + size_t length() const; + + bool is_valid() const; + + Timestamp received_at() const; + + Type type() const; + ID id() const; + Consumption consumption() const; + + ManchesterFormatted symbols_formatted() const; + + bool crc_ok() const; + +private: + using Reader = FieldReader; + + const baseband::Packet packet_; + const ManchesterDecoder decoder_; + const Reader reader_; + const Type type_; + + const ID invalid_id = 0; + const Consumption invalid_consumption = 0; + + bool crc_ok_idm() const; + bool crc_ok_scm() const; +}; + +} /* namespace ert */ + +#endif/*__ERT_PACKET_H__*/ diff --git a/firmware/application/event.cpp b/firmware/common/event.cpp similarity index 79% rename from firmware/application/event.cpp rename to firmware/common/event.cpp index 5133c0c2..eee675cd 100644 --- a/firmware/application/event.cpp +++ b/firmware/common/event.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. * * This file is part of PortaPack. * @@ -22,9 +22,3 @@ #include "event.hpp" #include "ch.h" - -Thread* thread_event_loop = nullptr; - -void events_initialize(Thread* const event_loop_thread) { - thread_event_loop = event_loop_thread; -} diff --git a/firmware/baseband/irq_ipc_m4.hpp b/firmware/common/event.hpp similarity index 84% rename from firmware/baseband/irq_ipc_m4.hpp rename to firmware/common/event.hpp index 61328685..a6432674 100644 --- a/firmware/baseband/irq_ipc_m4.hpp +++ b/firmware/common/event.hpp @@ -19,10 +19,9 @@ * Boston, MA 02110-1301, USA. */ -#ifndef __IRQ_IPC_M4_H__ -#define __IRQ_IPC_M4_H__ +#ifndef __EVENT_H__ +#define __EVENT_H__ -void m0apptxevent_interrupt_enable(); -void m0apptxevent_interrupt_disable(); +#include "ch.h" -#endif/*__IRQ_IPC_M4_H__*/ +#endif/*__EVENT_H__*/ diff --git a/firmware/common/field_reader.hpp b/firmware/common/field_reader.hpp new file mode 100644 index 00000000..03d0a24d --- /dev/null +++ b/firmware/common/field_reader.hpp @@ -0,0 +1,65 @@ +/* + * 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 __FIELD_READER_H__ +#define __FIELD_READER_H__ + +#include +#include + +struct BitRemapNone { + constexpr size_t operator()(const size_t& bit_index) const { + return bit_index; + } +}; + +struct BitRemapByteReverse { + constexpr size_t operator()(const size_t bit_index) const { + return bit_index ^ 7; + } +}; + +template +class FieldReader { +public: + constexpr FieldReader( + const T& data + ) : data { data } + { + } + + /* The "start_bit" winds up being the MSB of the returned field value. */ + /* The BitRemap functor determines which bits are read from the source + * packet. */ + uint32_t read(const size_t start_bit, const size_t length) const { + uint32_t value = 0; + for(size_t i=start_bit; i<(start_bit + length); i++) { + value = (value << 1) | data[bit_remap(i)]; + } + return value; + } + +private: + const T& data; + const BitRemap bit_remap { }; +}; + +#endif/*__FIELD_READER_H__*/ diff --git a/firmware/common/fifo.hpp b/firmware/common/fifo.hpp index f7a490c7..86c06a73 100644 --- a/firmware/common/fifo.hpp +++ b/firmware/common/fifo.hpp @@ -42,6 +42,10 @@ public: _in = _out = 0; } + void reset_in() { + _in = _out; + } + void reset_out() { _out = _in; } @@ -59,19 +63,21 @@ public: } bool is_full() const { - return len() > mask(); + return unused() == 0; } -/* + bool in(const T& val) { - const bool is_not_full = !is_full(); - if( is_not_full ) { - _data[_in & mask()] = val; - smp_wmb(); - _in++; + if( is_full() ) { + return false; } - return is_not_full; + + _data[_in & mask()] = val; + smp_wmb(); + _in += 1; + + return true; } -*/ + size_t in(const T* const buf, size_t len) { const size_t l = unused(); if( len > l ) { @@ -93,7 +99,7 @@ public: _in += len + recsize(); return len; } -/* + bool out(T& val) { if( is_empty() ) { return false; @@ -105,13 +111,33 @@ public: return true; } -*/ + size_t out(T* const buf, size_t len) { len = out_peek(buf, len); _out += len; return len; } + bool skip() { + if( is_empty() ) { + return false; + } + + size_t len = peek_n(); + _out += len + recsize(); + return true; + } + + size_t peek_r(void* const buf, size_t len) { + if( is_empty() ) { + return 0; + } + + size_t n; + len = out_copy_r((T*)buf, len, &n); + return len; + } + size_t out_r(void* const buf, size_t len) { if( is_empty() ) { return 0; @@ -199,8 +225,8 @@ private: } T _data[size()]; - size_t _in; - size_t _out; + volatile size_t _in; + volatile size_t _out; }; #endif/*__FIFO_H__*/ diff --git a/firmware/common/gpdma.cpp b/firmware/common/gpdma.cpp index b320af91..034a6248 100644 --- a/firmware/common/gpdma.cpp +++ b/firmware/common/gpdma.cpp @@ -54,7 +54,7 @@ void Channel::configure( const LLI& first_lli, const uint32_t config ) const { - disable_force(); + disable(); clear_interrupts(); LPC_GPDMA_Channel_Type* const channel = &LPC_GPDMA->CH[number]; @@ -69,7 +69,6 @@ void Channel::configure( extern "C" { -LOCATE_IN_RAM CH_IRQ_HANDLER(DMA_IRQHandler) { CH_IRQ_PROLOGUE(); diff --git a/firmware/common/gpdma.hpp b/firmware/common/gpdma.hpp index b97dde14..c403569b 100644 --- a/firmware/common/gpdma.hpp +++ b/firmware/common/gpdma.hpp @@ -313,7 +313,7 @@ public: return LPC_GPDMA->CH[number].CONFIG & (1U << 0); } - void disable_force() const { + void disable() const { LPC_GPDMA->CH[number].CONFIG &= ~(1U << 0); } @@ -349,7 +349,7 @@ public: void disable() const { for(const auto& channel : channels) { - channel.disable_force(); + channel.disable(); } LPC_GPDMA->CONFIG &= ~(1U << 0); } diff --git a/firmware/common/gpio.hpp b/firmware/common/gpio.hpp index 21a08d6b..0f02d6ec 100644 --- a/firmware/common/gpio.hpp +++ b/firmware/common/gpio.hpp @@ -28,12 +28,12 @@ #include "hal.h" struct PinConfig { - const uint32_t mode : 3; - const uint32_t pd : 1; - const uint32_t pu : 1; - const uint32_t fast : 1; - const uint32_t input : 1; - const uint32_t ifilt : 1; + const uint32_t mode; + const uint32_t pd; + const uint32_t pu; + const uint32_t fast; + const uint32_t input; + const uint32_t ifilt; constexpr operator uint16_t() { return diff --git a/firmware/common/lcd_ili9341.cpp b/firmware/common/lcd_ili9341.cpp index 95995fd2..c5845b23 100644 --- a/firmware/common/lcd_ili9341.cpp +++ b/firmware/common/lcd_ili9341.cpp @@ -43,6 +43,20 @@ void lcd_reset() { chThdSleepMilliseconds(120); } +void lcd_sleep_in() { + io.lcd_data_write_command_and_data(0x10, {}); + chThdSleepMilliseconds(5); +} + +void lcd_sleep_out() { + io.lcd_data_write_command_and_data(0x11, {}); + chThdSleepMilliseconds(120); +} + +void lcd_display_on() { + io.lcd_data_write_command_and_data(0x29, {}); +} + void lcd_init() { // LCDs are configured for IM[2:0] = 001 // 8080-I system, 16-bit parallel bus @@ -149,12 +163,8 @@ void lcd_init() { 0x47, 0x04, 0x0C, 0x0B, 0x29, 0x2F, 0x05 }); - // Exit Sleep - io.lcd_data_write_command_and_data(0x11, {}); - chThdSleepMilliseconds(120); - - // Display on - io.lcd_data_write_command_and_data(0x29, {}); + lcd_sleep_out(); + lcd_display_on(); // Turn on Tearing Effect Line (TE) output signal. io.lcd_data_write_command_and_data(0x35, { 0b00000000 }); @@ -232,6 +242,14 @@ void ILI9341::shutdown() { lcd_reset(); } +void ILI9341::sleep() { + lcd_sleep_in(); +} + +void ILI9341::wake() { + lcd_sleep_out(); +} + void ILI9341::fill_rectangle(ui::Rect r, const ui::Color c) { const auto r_clipped = r.intersect(screen_rect()); if( !r_clipped.is_empty() ) { diff --git a/firmware/common/lcd_ili9341.hpp b/firmware/common/lcd_ili9341.hpp index b6457a45..888a7699 100644 --- a/firmware/common/lcd_ili9341.hpp +++ b/firmware/common/lcd_ili9341.hpp @@ -44,6 +44,9 @@ public: void init(); void shutdown(); + void sleep(); + void wake(); + void fill_rectangle(ui::Rect r, const ui::Color c); void draw_line(const ui::Point start, const ui::Point end, const ui::Color color); void fill_circle( diff --git a/firmware/common/lpc43xx_cpp.hpp b/firmware/common/lpc43xx_cpp.hpp index abdb0d62..92ca64ec 100644 --- a/firmware/common/lpc43xx_cpp.hpp +++ b/firmware/common/lpc43xx_cpp.hpp @@ -30,6 +30,21 @@ namespace lpc43xx { +#if defined(LPC43XX_M4) +namespace m4 { + +static inline bool flag_saturation() { + return __get_APSR() & (1U << 27); +} + +static inline void clear_flag_saturation() { + uint32_t flags = 1; + __asm volatile ("MSR APSR_nzcvqg, %0" : : "r" (flags)); +} + +} /* namespace m4 */ +#endif + namespace creg { static_assert(offsetof(LPC_CREG_Type, CREG0) == 0x004, "CREG0 offset wrong"); @@ -43,6 +58,16 @@ static_assert(offsetof(LPC_CREG_Type, USB1FLADJ) == 0x600, "USB1FLADJ offset wro namespace m4txevent { +#if defined(LPC43XX_M0) +inline void enable() { + nvicEnableVector(M4CORE_IRQn, CORTEX_PRIORITY_MASK(LPC43XX_M4TXEVENT_IRQ_PRIORITY)); +} + +inline void disable() { + nvicDisableVector(M4CORE_IRQn); +} +#endif + #if defined(LPC43XX_M4) inline void assert() { __SEV(); @@ -57,6 +82,16 @@ inline void clear() { namespace m0apptxevent { +#if defined(LPC43XX_M4) +inline void enable() { + nvicEnableVector(M0CORE_IRQn, CORTEX_PRIORITY_MASK(LPC43XX_M0APPTXEVENT_IRQ_PRIORITY)); +} + +inline void disable() { + nvicDisableVector(M0CORE_IRQn); +} +#endif + #if defined(LPC43XX_M0) inline void assert() { __SEV(); diff --git a/firmware/common/manchester.cpp b/firmware/common/manchester.cpp new file mode 100644 index 00000000..3862facc --- /dev/null +++ b/firmware/common/manchester.cpp @@ -0,0 +1,71 @@ +/* + * 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 "manchester.hpp" + +#include "string_format.hpp" + +ManchesterDecoder::DecodedSymbol ManchesterDecoder::operator[](const size_t index) const { + const size_t encoded_index = index * 2; + if( (encoded_index + 1) < packet.size() ) { + const auto value = packet[encoded_index + sense]; + const auto error = packet[encoded_index + 0] == packet[encoded_index + 1]; + return { value, error }; + } else { + return { 0, 1 }; + } +} + +size_t ManchesterDecoder::symbols_count() const { + return packet.size() / 2; +} + +ManchesterFormatted format_manchester( + const ManchesterDecoder& decoder +) { + const size_t payload_length_decoded = decoder.symbols_count(); + const size_t payload_length_hex_characters = (payload_length_decoded + 3) / 4; + const size_t payload_length_symbols_rounded = payload_length_hex_characters * 4; + + std::string hex_data; + std::string hex_error; + hex_data.reserve(payload_length_hex_characters); + hex_error.reserve(payload_length_hex_characters); + + uint_fast8_t data = 0; + uint_fast8_t error = 0; + for(size_t i=0; i +#include +#include +#include + +#include "baseband_packet.hpp" + +class ManchesterDecoder { +public: + struct DecodedSymbol { + uint_fast8_t value; + uint_fast8_t error; + }; + + constexpr ManchesterDecoder( + const baseband::Packet& packet, + const size_t sense = 0 + ) : packet { packet }, + sense { sense } + { + } + + DecodedSymbol operator[](const size_t index) const; + + size_t symbols_count() const; + +private: + const baseband::Packet& packet; + const size_t sense; +}; + +template +T operator|(const T& l, const ManchesterDecoder::DecodedSymbol& r) { + return l | r.value; +} + +struct ManchesterFormatted { + const std::string data; + const std::string errors; +}; + +ManchesterFormatted format_manchester( + const ManchesterDecoder& decoder +); + +#endif/*__MANCHESTER_H__*/ diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 1e714b4a..50afc442 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -27,6 +27,11 @@ #include #include +#include "baseband_packet.hpp" +#include "ert_packet.hpp" +#include "dsp_fir_taps.hpp" +#include "fifo.hpp" + #include "utility.hpp" #include "ch.h" @@ -34,27 +39,33 @@ class Message { public: static constexpr size_t MAX_SIZE = 276; - - enum class ID : uint16_t { + + enum class ID : uint32_t { /* Assign consecutive IDs. IDs are used to index array. */ RSSIStatistics = 0, BasebandStatistics = 1, ChannelStatistics = 2, DisplayFrameSync = 3, - ChannelSpectrum = 4, - AudioStatistics = 5, - BasebandConfiguration = 6, - TPMSPacket = 7, + AudioStatistics = 4, + BasebandConfiguration = 5, + TPMSPacket = 6, Shutdown = 8, - AISPacket = 9, - TXDone = 10, - SDCardStatus = 11, - Retune = 12, - ReadyForSwitch = 13, - AFSKData = 14, - ModuleID = 15, - FIFOSignal = 16, - FIFOData = 17, + AISPacket = 7, + ERTPacket = 9, + UpdateSpectrum = 10, + NBFMConfigure = 11, + WFMConfigure = 12, + AMConfigure = 13, + ChannelSpectrumConfig = 14, + SpectrumStreamingConfig = 15, + DisplaySleep = 16, + TXDone = 17, + Retune = 18, + ReadyForSwitch = 19, + AFSKData = 20, + ModuleID = 21, + FIFOSignal = 22, + FIFOData = 23, MAX }; @@ -122,13 +133,23 @@ struct ChannelStatistics { class ChannelStatisticsMessage : public Message { public: constexpr ChannelStatisticsMessage( - ) : Message { ID::ChannelStatistics } + const ChannelStatistics& statistics + ) : Message { ID::ChannelStatistics }, + statistics { statistics } { } ChannelStatistics statistics; }; +class DisplayFrameSyncMessage : public Message { +public: + constexpr DisplayFrameSyncMessage( + ) : Message { ID::DisplayFrameSync } + { + } +}; + struct AudioStatistics { int32_t rms_db; int32_t max_db; @@ -152,125 +173,119 @@ struct AudioStatistics { } }; +class DisplaySleepMessage : public Message { +public: + constexpr DisplaySleepMessage( + ) : Message { ID::DisplaySleep } + { + } +}; + class AudioStatisticsMessage : public Message { public: constexpr AudioStatisticsMessage( + const AudioStatistics& statistics ) : Message { ID::AudioStatistics }, - statistics { } + statistics { statistics } { } AudioStatistics statistics; }; -typedef enum { - RX_NBAM_AUDIO = 0, - RX_NBFM_AUDIO, - RX_WBFM_AUDIO, - RX_AIS, - RX_WBSPECTRUM, - RX_TPMS, - RX_AFSK, - RX_SIGFOX, - - TX_RDS, - TX_LCR, - TX_TONE, - TX_JAMMER, - TX_XYLOS, - - PLAY_AUDIO, - - NONE, - SWITCH = 0xFF -} mode_type; - struct BasebandConfiguration { - mode_type mode; + int32_t mode; uint32_t sampling_rate; size_t decimation_factor; constexpr BasebandConfiguration( - mode_type mode = NONE, - uint32_t sampling_rate = 0, + int32_t mode, + uint32_t sampling_rate, size_t decimation_factor = 1 ) : mode { mode }, sampling_rate { sampling_rate }, decimation_factor { decimation_factor } { } + + constexpr BasebandConfiguration( + ) : BasebandConfiguration { -1, 0, 1 } + { + } }; class BasebandConfigurationMessage : public Message { public: constexpr BasebandConfigurationMessage( - BasebandConfiguration configuration + const BasebandConfiguration& configuration ) : Message { ID::BasebandConfiguration }, - configuration(configuration) + configuration { configuration } { } BasebandConfiguration configuration; }; +class SpectrumStreamingConfigMessage : public Message { +public: + enum class Mode : uint32_t { + Stopped = 0, + Running = 1, + }; + + constexpr SpectrumStreamingConfigMessage( + Mode mode + ) : Message { ID::SpectrumStreamingConfig }, + mode { mode } + { + } + + Mode mode { Mode::Stopped }; +}; + struct ChannelSpectrum { std::array db { { 0 } }; - size_t db_count { 256 }; uint32_t sampling_rate { 0 }; uint32_t channel_filter_pass_frequency { 0 }; uint32_t channel_filter_stop_frequency { 0 }; }; -class ChannelSpectrumMessage : public Message { +using ChannelSpectrumFIFO = FIFO; + +class ChannelSpectrumConfigMessage : public Message { public: - constexpr ChannelSpectrumMessage( - ) : Message { ID::ChannelSpectrum } + constexpr ChannelSpectrumConfigMessage( + ChannelSpectrumFIFO* fifo + ) : Message { ID::ChannelSpectrumConfig }, + fifo { fifo } { } - ChannelSpectrum spectrum; -}; - -#include - -struct AISPacket { - std::bitset<1024> payload; - size_t bits_received { 0 }; + ChannelSpectrumFIFO* fifo { nullptr }; }; class AISPacketMessage : public Message { public: constexpr AISPacketMessage( - ) : Message { ID::AISPacket } + const baseband::Packet& packet + ) : Message { ID::AISPacket }, + packet { packet } { } - AISPacket packet; -}; - -struct TPMSPacket { - std::bitset<1024> payload; - size_t bits_received { 0 }; + baseband::Packet packet; }; class TPMSPacketMessage : public Message { public: constexpr TPMSPacketMessage( - ) : Message { ID::TPMSPacket } + const baseband::Packet& packet + ) : Message { ID::TPMSPacket }, + packet { packet } { } - TPMSPacket packet; -}; - -class AFSKDataMessage : public Message { -public: - constexpr AFSKDataMessage( - ) : Message { ID::AFSKData } - { - } - - int16_t data[128] = {0}; + baseband::Packet packet; }; class ShutdownMessage : public Message { @@ -281,16 +296,88 @@ public: } }; -class SDCardStatusMessage : public Message { +class ERTPacketMessage : public Message { public: - constexpr SDCardStatusMessage( - bool is_mounted - ) : Message { ID::SDCardStatus }, - is_mounted { is_mounted } + constexpr ERTPacketMessage( + const ert::Packet::Type type, + const baseband::Packet& packet + ) : Message { ID::ERTPacket }, + type { type }, + packet { packet } { } - const bool is_mounted; + ert::Packet::Type type; + + baseband::Packet packet; +}; + +class UpdateSpectrumMessage : public Message { +public: + constexpr UpdateSpectrumMessage( + ) : Message { ID::UpdateSpectrum } + { + } +}; + +class NBFMConfigureMessage : public Message { +public: + constexpr NBFMConfigureMessage( + const fir_taps_real<24> decim_0_filter, + const fir_taps_real<32> decim_1_filter, + const fir_taps_real<32> channel_filter, + const size_t deviation + ) : Message { ID::NBFMConfigure }, + decim_0_filter(decim_0_filter), + decim_1_filter(decim_1_filter), + channel_filter(channel_filter), + deviation { deviation } + { + } + + const fir_taps_real<24> decim_0_filter; + const fir_taps_real<32> decim_1_filter; + const fir_taps_real<32> channel_filter; + const size_t deviation; +}; + +class WFMConfigureMessage : public Message { +public: + constexpr WFMConfigureMessage( + const fir_taps_real<24> decim_0_filter, + const fir_taps_real<16> decim_1_filter, + const fir_taps_real<64> audio_filter, + const size_t deviation + ) : Message { ID::WFMConfigure }, + decim_0_filter(decim_0_filter), + decim_1_filter(decim_1_filter), + audio_filter(audio_filter), + deviation { deviation } + { + } + + const fir_taps_real<24> decim_0_filter; + const fir_taps_real<16> decim_1_filter; + const fir_taps_real<64> audio_filter; + const size_t deviation; +}; + +class AMConfigureMessage : public Message { +public: + constexpr AMConfigureMessage( + const fir_taps_real<24> decim_0_filter, + const fir_taps_real<32> decim_1_filter, + const fir_taps_real<32> channel_filter + ) : Message { ID::AMConfigure }, + decim_0_filter(decim_0_filter), + decim_1_filter(decim_1_filter), + channel_filter(channel_filter) + { + } + + const fir_taps_real<24> decim_0_filter; + const fir_taps_real<32> decim_1_filter; + const fir_taps_real<32> channel_filter; }; class TXDoneMessage : public Message { diff --git a/firmware/common/message_queue.hpp b/firmware/common/message_queue.hpp index 94745f3e..c4430a23 100644 --- a/firmware/common/message_queue.hpp +++ b/firmware/common/message_queue.hpp @@ -47,6 +47,25 @@ public: return push(&message, sizeof(message)); } + template + bool push_and_wait(const T& message) { + const bool result = push(message); + if( result ) { + // TODO: More graceful method of waiting for empty? Maybe sleep for a bit? + while( !is_empty() ); + } + return result; + } + + Message* peek(std::array& buf) { + Message* const p = reinterpret_cast(buf.data()); + return fifo.peek_r(buf.data(), buf.size()) ? p : nullptr; + } + + bool skip() { + return fifo.skip(); + } + Message* pop(std::array& buf) { Message* const p = reinterpret_cast(buf.data()); return fifo.out_r(buf.data(), buf.size()) ? p : nullptr; diff --git a/firmware/common/optional.hpp b/firmware/common/optional.hpp new file mode 100644 index 00000000..a6ca3ecf --- /dev/null +++ b/firmware/common/optional.hpp @@ -0,0 +1,42 @@ +/* + * 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 __OPTIONAL_H__ +#define __OPTIONAL_H__ + +#include + +template +class Optional { +public: + constexpr Optional() : value_ { }, valid_ { false } { }; + constexpr Optional(const T& value) : value_ { value }, valid_ { true } { }; + constexpr Optional(T&& value) : value_ { std::move(value) }, valid_ { true } { }; + + bool is_valid() const { return valid_; }; + T value() const { return value_; }; + +private: + T value_; + bool valid_; +}; + +#endif/*__OPTIONAL_H__*/ diff --git a/firmware/common/portapack_persistent_memory.cpp b/firmware/common/portapack_persistent_memory.cpp index 772538fc..6fd4d7ec 100644 --- a/firmware/common/portapack_persistent_memory.cpp +++ b/firmware/common/portapack_persistent_memory.cpp @@ -23,32 +23,14 @@ #include "hal.h" +#include "utility.hpp" + #include #include namespace portapack { namespace persistent_memory { -/* TODO: This is widely applicable. Factor this to somewhere useful. */ -template -struct range_t { - const T minimum; - const T maximum; - - const T& clip(const T& value) const { - return std::max(std::min(value, maximum), minimum); - } - - void reset_if_outside(T& value, const T& reset_value) const { - if( (value < minimum ) || - (value > maximum ) ) { - value = reset_value; - } - } -}; - -using tuned_frequency_range_t = range_t; -constexpr tuned_frequency_range_t tuned_frequency_range { rf::tuning_range.min, rf::tuning_range.max }; constexpr rf::Frequency tuned_frequency_reset_value { 858750000 }; using ppb_range_t = range_t; @@ -93,12 +75,12 @@ static_assert(sizeof(data_t) <= 0x100, "Persistent memory structure too large fo static data_t* const data = reinterpret_cast(LPC_BACKUP_REG_BASE); rf::Frequency tuned_frequency() { - tuned_frequency_range.reset_if_outside(data->tuned_frequency, tuned_frequency_reset_value); + rf::tuning_range.reset_if_outside(data->tuned_frequency, tuned_frequency_reset_value); return data->tuned_frequency; } void set_tuned_frequency(const rf::Frequency new_value) { - data->tuned_frequency = tuned_frequency_range.clip(new_value); + data->tuned_frequency = rf::tuning_range.clip(new_value); } ppb_t correction_ppb() { diff --git a/firmware/common/simd.hpp b/firmware/common/simd.hpp new file mode 100644 index 00000000..0d65b8dd --- /dev/null +++ b/firmware/common/simd.hpp @@ -0,0 +1,108 @@ +/* + * 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 __SIMD_H__ +#define __SIMD_H__ + +#if defined(LPC43XX_M4) + +#include + +#include + +struct vec4_s8 { + union { + int8_t v[4]; + uint32_t w; + }; +}; + +struct vec2_s16 { + constexpr vec2_s16( + ) : v { 0, 0 } + { + } + + constexpr vec2_s16( + const int16_t v0, + const int16_t v1 + ) : v { v0, v1 } + { + } + + constexpr vec2_s16( + const vec2_s16& other + ) : w { other.w } + { + } + + vec2_s16& operator=(const vec2_s16& other) { + w = other.w; + return *this; + } + + union { + int16_t v[2]; + uint32_t w; + }; +}; + +static inline vec4_s8 rev16(const vec4_s8 v) { + vec4_s8 result; + result.w = __REV16(v.w); + return result; +} + +static inline vec4_s8 pkhbt(const vec4_s8 v1, const vec4_s8 v2, const size_t sh = 0) { + vec4_s8 result; + result.w = __PKHBT(v1.w, v2.w, sh); + return result; +} + +static inline vec2_s16 pkhbt(const vec2_s16 v1, const vec2_s16 v2, const size_t sh = 0) { + vec2_s16 result; + result.w = __PKHBT(v1.w, v2.w, sh); + return result; +} + +static inline vec2_s16 pkhtb(const vec2_s16 v1, const vec2_s16 v2, const size_t sh = 0) { + vec2_s16 result; + result.w = __PKHTB(v1.w, v2.w, sh); + return result; +} + +static inline vec2_s16 sxtb16(const vec4_s8 v, const size_t sh = 0) { + vec2_s16 result; + result.w = __SXTB16(v.w, sh); + return result; +} + +static inline int32_t smlsd(const vec2_s16 v1, const vec2_s16 v2, const int32_t accum) { + return __SMLSD(v1.w, v2.w, accum); +} + +static inline int32_t smlad(const vec2_s16 v1, const vec2_s16 v2, const int32_t accum) { + return __SMLAD(v1.w, v2.w, accum); +} + +#endif /* defined(LPC43XX_M4) */ + +#endif/*__SIMD_H__*/ diff --git a/firmware/common/ui.cpp b/firmware/common/ui.cpp index e85c07c9..4fd40088 100644 --- a/firmware/common/ui.cpp +++ b/firmware/common/ui.cpp @@ -36,9 +36,7 @@ Rect Rect::intersect(const Rect& o) const { const auto y1 = std::max(top(), o.top()); const auto y2 = std::min(bottom(), o.bottom()); if( (x2 >= x1) && (y2 > y1) ) { - return { - x1, y1, - static_cast(x2 - x1), static_cast(y2 - y1) }; + return { x1, y1, x2 - x1, y2 - y1 }; } else { return { }; } @@ -54,7 +52,7 @@ Rect& Rect::operator+=(const Rect& p) { pos = { x1, y1 }; const auto x2 = std::max(right(), p.right()); const auto y2 = std::max(bottom(), p.bottom()); - size = { static_cast(x2 - x1), static_cast(y2 - y1) }; + size = { x2 - x1, y2 - y1 }; } return *this; } diff --git a/firmware/common/ui.hpp b/firmware/common/ui.hpp index 89324944..14749f64 100644 --- a/firmware/common/ui.hpp +++ b/firmware/common/ui.hpp @@ -115,23 +115,23 @@ struct Point { } constexpr Point( - Coord x, - Coord y - ) : x { x }, - y { y } + int x, + int y + ) : x { static_cast(x) }, + y { static_cast(y) } { } Point operator-() const { - return { static_cast(-x), static_cast(-y) }; + return { -x, -y }; } Point operator+(const Point& p) const { - return { static_cast(x + p.x), static_cast(y + p.y) }; + return { x + p.x, y + p.y }; } Point operator-(const Point& p) const { - return { static_cast(x - p.x), static_cast(y - p.y) }; + return { x - p.x, y - p.y }; } Point& operator+=(const Point& p) { @@ -171,10 +171,7 @@ private: /* Clockwise rotate (in screen coordinates), with a gain in * magnitude of sqrt(2). */ - return { - static_cast(x - y), - static_cast(x + y) - }; + return { x - y, x + y }; } #endif }; @@ -190,10 +187,10 @@ struct Size { } constexpr Size( - Dim w, - Dim h - ) : w { w }, - h { h } + int w, + int h + ) : w { static_cast(w) }, + h { static_cast(h) } { } @@ -213,8 +210,8 @@ struct Rect { } constexpr Rect( - Coord x, Coord y, - Dim w, Dim h + int x, int y, + int w, int h ) : pos { x, y }, size { w, h } { @@ -228,35 +225,33 @@ struct Rect { { } - Coord top() const { + + int top() const { return pos.y; } - Coord bottom() const { + int bottom() const { return pos.y + size.h; } - Coord left() const { + int left() const { return pos.x; } - Coord right() const { + int right() const { return pos.x + size.w; } - Dim width() const { + int width() const { return size.w; } - Dim height() const { + int height() const { return size.h; } Point center() const { - return { - static_cast(pos.x + size.w / 2), - static_cast(pos.y + size.h / 2) - }; + return { pos.x + size.w / 2, pos.y + size.h / 2 }; } bool is_empty() const { diff --git a/firmware/common/ui_painter.cpp b/firmware/common/ui_painter.cpp index 1f772fa1..7409b8eb 100644 --- a/firmware/common/ui_painter.cpp +++ b/firmware/common/ui_painter.cpp @@ -36,13 +36,13 @@ Style Style::invert() const { }; } -size_t Painter::draw_char(const Point p, const Style& style, const char c) { +int Painter::draw_char(const Point p, const Style& style, const char c) { const auto glyph = style.font.glyph(c); display.draw_glyph(p, glyph, style.foreground, style.background); return glyph.advance().x; } -size_t Painter::draw_string(Point p, const Style& style, const std::string text) { +int Painter::draw_string(Point p, const Style& style, const std::string text) { size_t width = 0; for(const auto c : text) { const auto glyph = style.font.glyph(c); @@ -54,19 +54,19 @@ size_t Painter::draw_string(Point p, const Style& style, const std::string text) return width; } -void Painter::draw_hline(Point p, size_t width, const Color c) { - display.fill_rectangle({ p, { static_cast(width), 1 } }, c); +void Painter::draw_hline(Point p, int width, const Color c) { + display.fill_rectangle({ p, { width, 1 } }, c); } -void Painter::draw_vline(Point p, size_t height, const Color c) { - display.fill_rectangle({ p, { 1, static_cast(height) } }, c); +void Painter::draw_vline(Point p, int height, const Color c) { + display.fill_rectangle({ p, { 1, height } }, c); } void Painter::draw_rectangle(const Rect r, const Color c) { draw_hline(r.pos, r.size.w, c); - draw_vline({ r.pos.x, static_cast(r.pos.y + 1) }, r.size.h - 2, c); - draw_vline({ static_cast(r.pos.x + r.size.w - 1), static_cast(r.pos.y + 1) }, r.size.h - 2, c); - draw_hline({ r.pos.x, static_cast(r.pos.y + r.size.h - 1) }, r.size.w, c); + draw_vline({ r.pos.x, r.pos.y + 1 }, r.size.h - 2, c); + draw_vline({ r.pos.x + r.size.w - 1, r.pos.y + 1 }, r.size.h - 2, c); + draw_hline({ r.pos.x, r.pos.y + r.size.h - 1 }, r.size.w, c); } void Painter::fill_rectangle(const Rect r, const Color c) { diff --git a/firmware/common/ui_painter.hpp b/firmware/common/ui_painter.hpp index 40e03edf..7ac7b40a 100644 --- a/firmware/common/ui_painter.hpp +++ b/firmware/common/ui_painter.hpp @@ -46,9 +46,9 @@ public: Painter(const Painter&) = delete; Painter(Painter&&) = delete; - size_t draw_char(const Point p, const Style& style, const char c); + int draw_char(const Point p, const Style& style, const char c); - size_t draw_string(Point p, const Style& style, const std::string text); + int draw_string(Point p, const Style& style, const std::string text); void draw_rectangle(const Rect r, const Color c); void fill_rectangle(const Rect r, const Color c); @@ -56,8 +56,8 @@ public: void paint_widget_tree(Widget* const w); private: - void draw_hline(Point p, size_t width, const Color c); - void draw_vline(Point p, size_t height, const Color c); + void draw_hline(Point p, int width, const Color c); + void draw_vline(Point p, int height, const Color c); void paint_widget(Widget* const w); }; diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 7462948c..26fee967 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -21,12 +21,13 @@ #include "ui_widget.hpp" #include "ui_painter.hpp" -#include "portapack.hpp" #include #include #include +#include "string_format.hpp" + namespace ui { static bool ui_dirty = true; @@ -43,99 +44,6 @@ bool is_dirty() { return ui_dirty; } -constexpr size_t to_string_max_length = 16; - -static char* to_string_dec_uint_internal( - char* p, - uint32_t n -) { - *p = 0; - auto q = p; - - do { - const uint32_t d = n % 10; - const char c = d + 48; - *(--q) = c; - n /= 10; - } while( n != 0 ); - - return q; -} - -static char* to_string_dec_uint_pad_internal( - char* const term, - const uint32_t n, - const int32_t l, - const char fill -) { - auto q = to_string_dec_uint_internal(term, n); - - if( fill ) { - while( (term - q) < l ) { - *(--q) = fill; - } - } - - return q; -} - -std::string to_string_dec_uint( - const uint32_t n, - const int32_t l, - const char fill -) { - char p[16]; - auto term = p + sizeof(p) - 1; - auto q = to_string_dec_uint_pad_internal(term, n, l, fill); - - // Right justify. - while( (term - q) < l ) { - *(--q) = ' '; - } - - return q; -} - -std::string to_string_dec_int( - const int32_t n, - const int32_t l, - const char fill -) { - const size_t negative = (n < 0) ? 1 : 0; - uint32_t n_abs = negative ? -n : n; - - char p[16]; - auto term = p + sizeof(p) - 1; - auto q = to_string_dec_uint_pad_internal(term, n_abs, l - negative, fill); - - // Add sign. - if( negative ) { - *(--q) = '-'; - } - - // Right justify. - while( (term - q) < l ) { - *(--q) = ' '; - } - - return q; -} - -static void to_string_hex_internal(char* p, const uint32_t n, const int32_t l) { - const uint32_t d = n & 0xf; - p[l] = (d > 9) ? (d + 87) : (d + 48); - if( l > 0 ) { - to_string_hex_internal(p, n >> 4, l - 1); - } -} - -std::string to_string_hex(const uint32_t n, const int32_t l) { - char p[16]; - to_string_hex_internal(p, n, l - 1); - p[l] = 0; - return p; -} - /* Widget ****************************************************************/ Point Widget::screen_pos() { @@ -187,7 +95,9 @@ void Widget::hidden(bool hide) { // If parent is hidden, either of these is a no-op. if( hide ) { - parent()->set_dirty(); + // TODO: Instead of dirtying parent entirely, dirty only children + // that overlap with this widget. + parent()->dirty_overlapping_children_in_rect(parent_rect); /* TODO: Notify self and all non-hidden children that they're * now effectively hidden? */ @@ -290,6 +200,14 @@ void Widget::visible(bool v) { } } +void Widget::dirty_overlapping_children_in_rect(const Rect& child_rect) { + for(auto child : children()) { + if( !child_rect.intersect(child->parent_rect).is_empty() ) { + child->set_dirty(); + } + } +} + /* View ******************************************************************/ void View::set_parent_rect(const Rect new_parent_rect) { @@ -339,8 +257,20 @@ Widget* View::initial_focus() { return nullptr; } +std::string View::title() const { + return ""; +}; + /* Rectangle *************************************************************/ +Rectangle::Rectangle( + Rect parent_rect, + Color c +) : Widget { parent_rect }, + color { c } +{ +} + void Rectangle::set_color(const Color c) { color = c; set_dirty(); @@ -355,6 +285,20 @@ void Rectangle::paint(Painter& painter) { /* Text ******************************************************************/ +Text::Text( + Rect parent_rect, + std::string text +) : Widget { parent_rect }, + text { text } +{ +} + +Text::Text( + Rect parent_rect +) : Text { parent_rect, { } } +{ +} + void Text::set(const std::string value) { text = value; set_dirty(); @@ -362,165 +306,33 @@ void Text::set(const std::string value) { void Text::paint(Painter& painter) { if (style_ == nullptr) style_ = &style(); + const auto rect = screen_rect(); + + painter.fill_rectangle(rect, s.background); painter.draw_string( - screen_pos(), + rect.pos, (*style_), text ); } -void Text::set_style(const Style* new_style) { - if( new_style != style_ ) { - style_ = new_style; - set_dirty(); - } -} - -/* Checkbox **************************************************************/ - -void Checkbox::set_text(const std::string value) { - text_ = value; - set_dirty(); -} - -std::string Checkbox::text() const { - return text_; -} - -void Checkbox::set_value(const bool value) { - value_ = value; - set_dirty(); -} - -bool Checkbox::value() const { - return value_; -} - -void Checkbox::paint(Painter& painter) { - const auto r = screen_rect(); - - const auto paint_style = (has_focus() || flags.highlighted) ? style().invert() : style(); - - painter.draw_rectangle({ r.pos.x, r.pos.y, 24, 24 }, style().foreground); - - painter.fill_rectangle( - { - static_cast(r.pos.x + 1), static_cast(r.pos.y + 1), - static_cast(24 - 2), static_cast(24 - 2) - }, - style().background - ); - - painter.draw_rectangle({ r.pos.x+2, r.pos.y+2, 24-4, 24-4 }, paint_style.background); - - if (value_ == true) { - // Check - portapack::display.draw_line( {r.pos.x+2, r.pos.y+14}, {r.pos.x+6, r.pos.y+18}, ui::Color::green()); - portapack::display.draw_line( {r.pos.x+6, r.pos.y+18}, {r.pos.x+20, r.pos.y+4}, ui::Color::green()); - } else { - // Cross - portapack::display.draw_line( {r.pos.x+1, r.pos.y+1}, {r.pos.x+24-2, r.pos.y+24-2}, ui::Color::red()); - portapack::display.draw_line( {r.pos.x+24-2, r.pos.y+1}, {r.pos.x+1, r.pos.y+24-2}, ui::Color::red()); - } - - const auto label_r = paint_style.font.size_of(text_); - painter.draw_string( - { - static_cast(r.pos.x + 24 + 4), - static_cast(r.pos.y + (24 - label_r.h) / 2) - }, - paint_style, - text_ - ); -} - -bool Checkbox::on_key(const KeyEvent key) { - if( key == KeyEvent::Select ) { - value_ = not value_; - set_dirty(); - - if( on_select ) { - on_select(*this); - return true; - } - } - - return false; -} - -bool Checkbox::on_touch(const TouchEvent event) { - switch(event.type) { - case TouchEvent::Type::Start: - flags.highlighted = true; - set_dirty(); - return true; - - - case TouchEvent::Type::End: - flags.highlighted = false; - value_ = not value_; - set_dirty(); - if( on_select ) { - on_select(*this); - } - return true; - - default: - return false; - } -#if 0 - switch(event.type) { - case TouchEvent::Type::Start: - flags.highlighted = true; - set_dirty(); - return true; - - case TouchEvent::Type::Move: - { - const bool new_highlighted = screen_rect().contains(event.point); - if( flags.highlighted != new_highlighted ) { - flags.highlighted = new_highlighted; - set_dirty(); - } - } - return true; - - case TouchEvent::Type::End: - if( flags.highlighted ) { - flags.highlighted = false; - set_dirty(); - if( on_select ) { - on_select(*this); - } - } - return true; - - default: - return false; - } -#endif -} - /* Button ****************************************************************/ + Button::Button( + Rect parent_rect, + std::string text +) : Widget { parent_rect }, + text_ { text } +{ + flags.focusable = true; +} + void Button::set_text(const std::string value) { text_ = value; set_dirty(); } -void Button::set_text(const int value) { //std::string - text_ = value; - set_dirty(); -} - -void Button::set_style(const Style* new_style) { - if( new_style != style_ ) { - style_ = new_style; - set_dirty(); - } -} - std::string Button::text() const { return text_; } @@ -535,19 +347,13 @@ void Button::paint(Painter& painter) { painter.draw_rectangle(r, style().foreground); painter.fill_rectangle( - { - static_cast(r.pos.x + 1), static_cast(r.pos.y + 1), - static_cast(r.size.w - 2), static_cast(r.size.h - 2) - }, + { r.pos.x + 1, r.pos.y + 1, r.size.w - 2, r.size.h - 2 }, paint_style.background ); const auto label_r = paint_style.font.size_of(text_); painter.draw_string( - { - static_cast(r.pos.x + (r.size.w - label_r.w) / 2), - static_cast(r.pos.y + (r.size.h - label_r.h) / 2) - }, + { r.pos.x + (r.size.w - label_r.w) / 2, r.pos.y + (r.size.h - label_r.h) / 2 }, paint_style, text_ ); @@ -623,6 +429,17 @@ bool Button::on_touch(const TouchEvent event) { /* OptionsField **********************************************************/ +OptionsField::OptionsField( + Point parent_pos, + size_t length, + options_t options +) : Widget { { parent_pos, { static_cast(8 * length), 16 } } }, + length_ { length }, + options { options } +{ + flags.focusable = true; +} + size_t OptionsField::selected_index() const { return selected_index_; } @@ -642,7 +459,7 @@ void OptionsField::set_selected_index(const size_t new_index) { void OptionsField::set_by_value(value_t v) { size_t new_index { 0 }; for(const auto& option : options) { - if( option.second >= v ) { + if( option.second == v ) { set_selected_index(new_index); break; } @@ -650,12 +467,6 @@ void OptionsField::set_by_value(value_t v) { } } -void OptionsField::set_options(options_t new_options) { - options = new_options; - set_by_value(0); - set_dirty(); -} - void OptionsField::paint(Painter& painter) { const auto paint_style = has_focus() ? style().invert() : style(); @@ -683,6 +494,21 @@ bool OptionsField::on_touch(const TouchEvent event) { /* NumberField ***********************************************************/ +NumberField::NumberField( + Point parent_pos, + size_t length, + range_t range, + int32_t step, + char fill_char +) : Widget { { parent_pos, { static_cast(8 * length), 16 } } }, + range { range }, + step { step }, + length_ { length }, + fill_char { fill_char } +{ + flags.focusable = true; +} + int32_t NumberField::value() const { return value_; } diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index b0ebaa7d..b6bfeb0c 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -29,8 +29,6 @@ #include "utility.hpp" -#include "message.hpp" - #include #include #include @@ -41,25 +39,14 @@ void dirty_set(); void dirty_clear(); bool is_dirty(); -// TODO: Move these somewhere else! -// TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp... -std::string to_string_dec_uint(const uint32_t n, const int32_t l = 0, const char fill = 0); -std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0); -std::string to_string_hex(const uint32_t n, const int32_t l = 0); - class Context { public: FocusManager& focus_manager() { return focus_manager_; } - MessageHandlerMap& message_map() { - return message_map_; - } - private: FocusManager focus_manager_; - MessageHandlerMap message_map_; }; class Widget { @@ -68,12 +55,6 @@ public: ) : parent_rect { } { } - - constexpr Widget( - Point parent_point - ) : parent_rect { parent_point, { 24, 24 } } - { - } constexpr Widget( Rect parent_rect @@ -150,6 +131,8 @@ protected: .highlighted = false, .visible = false, }; + + void dirty_overlapping_children_in_rect(const Rect& child_rect); }; class View : public Widget { @@ -174,6 +157,8 @@ public: virtual Widget* initial_focus(); + virtual std::string title() const; + protected: std::vector children_; Rect dirty_screen_rect; @@ -183,13 +168,7 @@ protected: class Rectangle : public Widget { public: - constexpr Rectangle( - const Rect parent_rect, - const Color c - ) : Widget { parent_rect }, - color { c } - { - } + Rectangle(Rect parent_rect, Color c); void paint(Painter& painter) override; @@ -205,19 +184,8 @@ public: ) : text { "" } { } - Text( - Rect parent_rect, - std::string text - ) : Widget { parent_rect }, - text { text } - { - } - - Text( - Rect parent_rect - ) : Text { parent_rect, { } } - { - } + Text(Rect parent_rect, std::string text); + Text(Rect parent_rect); void set(const std::string value); void set_style(const Style* new_style); @@ -267,16 +235,8 @@ private: class Button : public Widget { public: std::function on_select; - std::function on_dir; - Button( - Rect parent_rect, - std::string text - ) : Widget { parent_rect }, - text_ { text } - { - flags.focusable = true; - } + Button(Rect parent_rect, std::string text); Button( ) : Button { { }, { } } @@ -307,18 +267,7 @@ public: std::function on_change; - OptionsField( - Point parent_pos, - size_t length, - options_t options - ) : Widget { { parent_pos, { static_cast(8 * length), 16 } } }, - length_ { length }, - options { options } - { - flags.focusable = true; - } - - void set_options(options_t new_options); + OptionsField(Point parent_pos, size_t length, options_t options); size_t selected_index() const; void set_selected_index(const size_t new_index); @@ -342,20 +291,7 @@ public: using range_t = std::pair; - NumberField( - Point parent_pos, - size_t length, - range_t range, - int32_t step, - char fill_char - ) : Widget { { parent_pos, { static_cast(8 * length), 16 } } }, - range { range }, - step { step }, - length_ { length }, - fill_char { fill_char } - { - flags.focusable = true; - } + NumberField(Point parent_pos, size_t length, range_t range, int32_t step, char fill_char); NumberField(const NumberField&) = delete; NumberField(NumberField&&) = delete; diff --git a/firmware/common/units.hpp b/firmware/common/units.hpp new file mode 100644 index 00000000..d3923426 --- /dev/null +++ b/firmware/common/units.hpp @@ -0,0 +1,81 @@ +/* + * 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 __UNITS_H__ +#define __UNITS_H__ + +#include + +namespace units { + +class Pressure { +public: + constexpr Pressure( + ) : kpa_ { 0 } + { + } + + constexpr Pressure( + const int kilopascal + ) : kpa_ { static_cast(kilopascal) } + { + } + + int kilopascal() const { + return kpa_; + } + + int psi() const { + return kpa_ * 1000 / 6895; + } + +private: + int16_t kpa_; +}; + +class Temperature { +public: + constexpr Temperature( + ) : c_ { 0 } + { + } + + constexpr Temperature( + const int celsius + ) : c_ { static_cast(celsius) } + { + } + + int celsius() const { + return c_; + } + + int fahrenheit() const { + return (c_ * 9 / 5) + 32; + } + +private: + int16_t c_; +}; + +} /* namespace units */ + +#endif/*__UNITS_H__*/ diff --git a/firmware/common/utility.hpp b/firmware/common/utility.hpp index b1d00b65..2a2dc9f4 100644 --- a/firmware/common/utility.hpp +++ b/firmware/common/utility.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -70,6 +71,37 @@ inline float magnitude_squared(const std::complex c) { return r2 + i2; } +template +struct range_t { + const T minimum; + const T maximum; + + const T& clip(const T& value) const { + return std::max(std::min(value, maximum), minimum); + } + + void reset_if_outside(T& value, const T& reset_value) const { + if( (value < minimum ) || + (value > maximum ) ) { + value = reset_value; + } + } + + bool below_range(const T& value) const { + return value < minimum; + } + + bool contains(const T& value) const { + // TODO: Subtle gotcha here! Range test doesn't include maximum! + return (value >= minimum) && (value < maximum); + } + + bool out_of_range(const T& value) const { + // TODO: Subtle gotcha here! Range test in contains() doesn't include maximum! + return !contains(value); + } +}; + namespace std { /*! Stephan T Lavavej (STL!) implementation of make_unique, which has been accepted into the C++14 standard. diff --git a/firmware/common/utility_m4.hpp b/firmware/common/utility_m4.hpp index e7d98e90..c0868081 100644 --- a/firmware/common/utility_m4.hpp +++ b/firmware/common/utility_m4.hpp @@ -26,20 +26,14 @@ #include -static inline bool m4_flag_saturation() { - return __get_APSR() & (1U << 27); -} - -static inline void clear_m4_flag_saturation() { - uint32_t flags = 1; - __asm volatile ("MSR APSR_nzcvqg, %0" : : "r" (flags)); -} - static inline complex32_t multiply_conjugate_s16_s32(const complex16_t::rep_type a, const complex16_t::rep_type b) { // conjugate: conj(a + bj) = a - bj // multiply: (a + bj) * (c + dj) = (ac - bd) + (bc + ad)j // conjugate-multiply: (ac + bd) + (bc - ad)j //return { a.real() * b.real() + a.imag() * b.imag(), a.imag() * b.real() - a.real() * b.imag() }; + // NOTE: Did not use combination of SMUAD and SMUSDX because of non-saturating arithmetic. + // const int32_t r = __SMUAD(a, b); + // const int32_t i = __SMUSDX(b, a); const int32_t rr = __SMULBB(a, b); const int32_t ii = __SMULTT(a, b); const int32_t r = __QADD(rr, ii); diff --git a/firmware/common/volume.hpp b/firmware/common/volume.hpp index 97c432d8..8ef85557 100644 --- a/firmware/common/volume.hpp +++ b/firmware/common/volume.hpp @@ -27,15 +27,15 @@ class volume_t { public: constexpr volume_t operator-() const { - return { .cb = -cb }; + return { -cb }; } constexpr volume_t operator+(const volume_t& other) const { - return { .cb = cb + other.cb }; + return { cb + other.cb }; } constexpr volume_t operator-(const volume_t& other) const { - return { .cb = cb - other.cb }; + return { cb - other.cb }; } volume_t& operator+=(const volume_t& other) { @@ -52,11 +52,11 @@ public: } static constexpr volume_t centibel(const int cb) { - return { .cb = cb }; + return { cb }; } static constexpr volume_t decibel(const int db) { - return { .cb = db * 10 }; + return { db * 10 }; } int32_t centibel() const { diff --git a/firmware/common/wm8731.cpp b/firmware/common/wm8731.cpp index 6ecd0665..f5c454c3 100644 --- a/firmware/common/wm8731.cpp +++ b/firmware/common/wm8731.cpp @@ -19,14 +19,82 @@ * Boston, MA 02110-1301, USA. */ -#include -#include - #include "wm8731.hpp" +#include "utility.hpp" namespace wolfson { namespace wm8731 { +void WM8731::init() { + reset(); + + write(PowerDownControl { + .lineinpd = 1, + .micpd = 0, + .adcpd = 0, + .dacpd = 0, + .outpd = 0, + .oscpd = 1, + .clkoutpd = 1, + .poweroff = 0, + .reserved0 = 0, + }); + + // write(SamplingControl { + // .usb_normal = 0, + // .bosr = 0, + // .sr = 0, + // .clkidiv2 = 0, + // .clkodiv2 = 0, + // .reserved0 = 0, + // }); + + write(DigitalAudioInterfaceFormat { + .format = 2, + .iwl = 0, + .lrp = 0, + .lrswap = 0, + .ms = 0, + .bclkinv = 0, + .reserved0 = 0, + }); + + write(DigitalAudioPathControl { + .adchpd = 0, + .deemp = 0, + .dacmu = 0, + .hpor = 0, + .reserved0 = 0, + }); + + write(AnalogAudioPathControl { + .micboost = 1, // Enable 20dB boost + .mutemic = 0, // Disable mute (unmute) + .insel = 1, // Microphone input to ADC + .bypass = 0, + .dacsel = 1, + .sidetone = 0, + .sideatt = 0, + .reserved0 = 0, + }); + + write(ActiveControl { + .active = 1, + .reserved0 = 0, + }); + + set_line_in_volume(0.0_dB); + headphone_mute(); +} + +void WM8731::reset() { + write(0x0f, 0); +} + +void WM8731::write(const Register reg) { + write(toUType(reg), map.w[toUType(reg)]); +} + void WM8731::write(const address_t reg_address, const reg_t value) { const uint16_t word = (reg_address << 9) | value; const std::array values { @@ -36,5 +104,59 @@ void WM8731::write(const address_t reg_address, const reg_t value) { bus.transmit(bus_address, values.data(), values.size()); } +reg_t WM8731::read(const address_t reg_address) { + return map.w[reg_address]; +} + +void WM8731::write(const LeftLineIn value) { + map.r.left_line_in = value; + write(Register::LeftLineIn); +} + +void WM8731::write(const RightLineIn value) { + map.r.right_line_in = value; + write(Register::RightLineIn); +} + +void WM8731::write(const LeftHeadphoneOut value) { + map.r.left_headphone_out = value; + write(Register::LeftHeadphoneOut); +} + +void WM8731::write(const RightHeadphoneOut value) { + map.r.right_headphone_out = value; + write(Register::RightHeadphoneOut); +} + +void WM8731::write(const AnalogAudioPathControl value) { + map.r.analog_audio_path_control = value; + write(Register::AnalogAudioPathControl); +} + +void WM8731::write(const DigitalAudioPathControl value) { + map.r.digital_audio_path_control = value; + write(Register::DigitalAudioPathControl); +} + +void WM8731::write(const PowerDownControl value) { + map.r.power_down_control = value; + write(Register::PowerDownControl); +} + +void WM8731::write(const DigitalAudioInterfaceFormat value) { + map.r.digital_audio_interface_format = value; + write(Register::DigitalAudioInterfaceFormat); +} + +void WM8731::write(const SamplingControl value) { + map.r.sampling_control = value; + write(Register::SamplingControl); +} + +void WM8731::write(const ActiveControl value) { + map.r.active_control = value; + write(Register::ActiveControl); +} + } /* namespace wm8731 */ } /* namespace wolfson */ diff --git a/firmware/common/wm8731.hpp b/firmware/common/wm8731.hpp index 4a0e1f98..a81e819c 100644 --- a/firmware/common/wm8731.hpp +++ b/firmware/common/wm8731.hpp @@ -23,11 +23,10 @@ #define __WM8731_H__ #include +#include #include "i2c_pp.hpp" -#include "wm8731.hpp" -#include "utility.hpp" #include "volume.hpp" namespace wolfson { @@ -289,71 +288,9 @@ public: { } - void init() { - reset(); + void init(); - write(PowerDownControl { - .lineinpd = 1, - .micpd = 0, - .adcpd = 0, - .dacpd = 0, - .outpd = 0, - .oscpd = 1, - .clkoutpd = 1, - .poweroff = 0, - .reserved0 = 0, - }); - - // write(SamplingControl { - // .usb_normal = 0, - // .bosr = 0, - // .sr = 0, - // .clkidiv2 = 0, - // .clkodiv2 = 0, - // .reserved0 = 0, - // }); - - write(DigitalAudioInterfaceFormat { - .format = 2, - .iwl = 0, - .lrp = 0, - .lrswap = 0, - .ms = 0, - .bclkinv = 0, - .reserved0 = 0, - }); - - write(DigitalAudioPathControl { - .adchpd = 0, - .deemp = 0, - .dacmu = 0, - .hpor = 0, - .reserved0 = 0, - }); - - write(AnalogAudioPathControl { - .micboost = 1, // Enable 20dB boost - .mutemic = 0, // Disable mute (unmute) - .insel = 1, // Microphone input to ADC - .bypass = 0, - .dacsel = 1, - .sidetone = 0, - .sideatt = 0, - .reserved0 = 0, - }); - - write(ActiveControl { - .active = 1, - .reserved0 = 0, - }); - - set_line_in_volume(0.0_dB); - headphone_mute(); - } - - void reset() { - write(0x0f, 0); - } + void reset(); void set_line_in_volume(const volume_t volume) { const auto normalized = line_in_gain_range.normalize(volume); @@ -374,7 +311,7 @@ public: write(LeftHeadphoneOut { .lhpvol = static_cast(n), - .lzcen = 1, + .lzcen = 0, .lrhpboth = 1, .reserved0 = 0, }); @@ -394,66 +331,27 @@ public: // write(Register::AnalogAudioPathControl); // } + reg_t read(const address_t reg_address); + private: I2C& bus; const I2C::address_t bus_address; RegisterMap map { default_after_reset }; - void write(const Register reg) { - write(toUType(reg), map.w[toUType(reg)]); - } - + void write(const Register reg); + void write(const address_t reg_address, const reg_t value); - void write(const LeftLineIn value) { - map.r.left_line_in = value; - write(Register::LeftLineIn); - } - - void write(const RightLineIn value) { - map.r.right_line_in = value; - write(Register::RightLineIn); - } - - void write(const LeftHeadphoneOut value) { - map.r.left_headphone_out = value; - write(Register::LeftHeadphoneOut); - } - - void write(const RightHeadphoneOut value) { - map.r.right_headphone_out = value; - write(Register::RightHeadphoneOut); - } - - void write(const AnalogAudioPathControl value) { - map.r.analog_audio_path_control = value; - write(Register::AnalogAudioPathControl); - } - - void write(const DigitalAudioPathControl value) { - map.r.digital_audio_path_control = value; - write(Register::DigitalAudioPathControl); - } - - void write(const PowerDownControl value) { - map.r.power_down_control = value; - write(Register::PowerDownControl); - } - - void write(const DigitalAudioInterfaceFormat value) { - map.r.digital_audio_interface_format = value; - write(Register::DigitalAudioInterfaceFormat); - } - - void write(const SamplingControl value) { - map.r.sampling_control = value; - write(Register::SamplingControl); - } - - void write(const ActiveControl value) { - map.r.active_control = value; - write(Register::ActiveControl); - } + void write(const LeftLineIn value); + void write(const RightLineIn value); + void write(const LeftHeadphoneOut value); + void write(const RightHeadphoneOut value); + void write(const AnalogAudioPathControl value); + void write(const DigitalAudioPathControl value); + void write(const PowerDownControl value); + void write(const DigitalAudioInterfaceFormat value); + void write(const SamplingControl value); + void write(const ActiveControl value); }; } /* namespace wm8731 */