mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-12-24 06:49:24 -05:00
SYNC
This commit is contained in:
parent
29ec87a9ad
commit
44638e504b
@ -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 \
|
||||
|
412
firmware/application/ais_app.cpp
Normal file
412
firmware/application/ais_app.cpp
Normal file
@ -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 <algorithm>
|
||||
|
||||
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<packet.length(); i+=4) {
|
||||
const auto nibble = packet.read(i, 4);
|
||||
entry += (nibble >= 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<std::pair<std::string, size_t>, 2> ais_columns { {
|
||||
{ "MMSI", 9 },
|
||||
{ "Name/Call", 20 },
|
||||
} };
|
||||
|
||||
template<>
|
||||
void RecentEntriesView<AISRecentEntries>::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<AISRecentEntries>::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<const AISPacketMessage*>(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 */
|
179
firmware/application/ais_app.hpp
Normal file
179
firmware/application/ais_app.hpp
Normal file
@ -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 <cstdint>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <utility>
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#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<ais::Packet, AISRecentEntry>;
|
||||
|
||||
class AISLogger {
|
||||
public:
|
||||
void on_packet(const ais::Packet& packet);
|
||||
|
||||
private:
|
||||
LogFile log_file { "ais.txt" };
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
|
||||
using AISRecentEntriesView = RecentEntriesView<AISRecentEntries>;
|
||||
|
||||
class AISRecentEntryDetailView : public View {
|
||||
public:
|
||||
std::function<void(void)> 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__*/
|
84
firmware/application/analog_audio_app.cpp
Normal file
84
firmware/application/analog_audio_app.cpp
Normal file
@ -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);
|
||||
}
|
@ -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__*/
|
175
firmware/application/ert_app.cpp
Normal file
175
firmware/application/ert_app.cpp
Normal file
@ -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<std::pair<std::string, size_t>, 3> ert_columns { {
|
||||
{ "ID", 10 },
|
||||
{ "Consumpt", 10 },
|
||||
{ "Cnt", 3 },
|
||||
} };
|
||||
|
||||
template<>
|
||||
void RecentEntriesView<ERTRecentEntries>::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<ERTRecentEntries>::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<const ERTPacketMessage*>(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 */
|
102
firmware/application/ert_app.hpp
Normal file
102
firmware/application/ert_app.hpp
Normal file
@ -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 <cstddef>
|
||||
#include <string>
|
||||
|
||||
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<ert::Packet, ERTRecentEntry>;
|
||||
|
||||
namespace ui {
|
||||
|
||||
using ERTRecentEntriesView = RecentEntriesView<ERTRecentEntries>;
|
||||
|
||||
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__*/
|
244
firmware/application/event_m0.cpp
Normal file
244
firmware/application/event_m0.cpp
Normal file
@ -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 <array>
|
||||
|
||||
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<uint8_t, Message::MAX_SIZE> 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<switches_state.size(); i++) {
|
||||
// TODO: Ignore multiple keys at the same time?
|
||||
if( switches_state[i] ) {
|
||||
const auto event = static_cast<ui::KeyEvent>(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<int32_t>(encoder_now - encoder_last);
|
||||
encoder_last = encoder_now;
|
||||
const auto event = static_cast<ui::EncoderEvent>(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();
|
||||
}
|
||||
}
|
110
firmware/application/event_m0.hpp
Normal file
110
firmware/application/event_m0.hpp
Normal file
@ -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 <cstdint>
|
||||
|
||||
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__*/
|
71
firmware/application/file.cpp
Normal file
71
firmware/application/file.cpp
Normal file
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
65
firmware/application/log_file.cpp
Normal file
65
firmware/application/log_file.cpp
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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 <string>
|
||||
|
||||
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__*/
|
@ -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 <string.h>
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include "sd_card.hpp"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
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<uint8_t, Message::MAX_SIZE> 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<switches_state.size(); i++) {
|
||||
// TODO: Ignore multiple keys at the same time?
|
||||
if( switches_state[i] ) {
|
||||
const auto event = static_cast<ui::KeyEvent>(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<int32_t>(encoder_now - encoder_last);
|
||||
|
||||
io.lcd_backlight(1);
|
||||
portapack::bl_tick_counter = 0;
|
||||
|
||||
encoder_last = encoder_now;
|
||||
const auto event = static_cast<ui::EncoderEvent>(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<const FSKPacketMessage*>(p);
|
||||
fsk_packet(message);
|
||||
};
|
||||
|
||||
message_handlers[Message::ID::TestResults] = [&system_view](const Message* const p) {
|
||||
const auto message = static_cast<const TestResultsMessage*>(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();
|
||||
|
||||
|
@ -40,7 +40,7 @@ constexpr std::array<uint8_t, 8> 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 {
|
||||
|
@ -50,10 +50,10 @@ enum class Mode {
|
||||
namespace lo {
|
||||
|
||||
constexpr std::array<rf::FrequencyRange, 4> 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<rf::FrequencyRange, 4> band { {
|
||||
|
||||
namespace lna {
|
||||
|
||||
constexpr int8_t gain_db_min = 0;
|
||||
constexpr int8_t gain_db_max = 40;
|
||||
constexpr range_t<int8_t> gain_db_range { 0, 40 };
|
||||
constexpr int8_t gain_db_step = 8;
|
||||
|
||||
constexpr std::array<rf::FrequencyRange, 2> band { {
|
||||
{ .min = 2300000000, .max = 2500000000, },
|
||||
{ .min = 2500000000, .max = 2700000000, },
|
||||
{ 2300000000, 2500000000 },
|
||||
{ 2500000000, 2700000000 },
|
||||
} };
|
||||
|
||||
} /* namespace lna */
|
||||
@ -77,8 +76,7 @@ constexpr std::array<rf::FrequencyRange, 2> band { {
|
||||
|
||||
namespace vga {
|
||||
|
||||
constexpr int8_t gain_db_min = 0;
|
||||
constexpr int8_t gain_db_max = 62;
|
||||
constexpr range_t<int8_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);
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <cstddef>
|
||||
|
||||
#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 */
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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__*/
|
||||
|
@ -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"
|
242
firmware/application/recent_entries.hpp
Normal file
242
firmware/application/recent_entries.hpp
Normal file
@ -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 <cstddef>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
|
||||
template<class Packet, class Entry>
|
||||
class RecentEntries {
|
||||
public:
|
||||
using EntryType = Entry;
|
||||
using Key = typename Entry::Key;
|
||||
using ContainerType = std::list<Entry>;
|
||||
using const_reference = typename ContainerType::const_reference;
|
||||
using const_iterator = typename ContainerType::const_iterator;
|
||||
using RangeType = std::pair<const_iterator, const_iterator>;
|
||||
|
||||
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 Entries>
|
||||
class RecentEntriesView : public View {
|
||||
public:
|
||||
using Entry = typename Entries::EntryType;
|
||||
|
||||
std::function<void(const Entry& entry)> 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__*/
|
@ -22,29 +22,14 @@
|
||||
#ifndef __RF_PATH_H__
|
||||
#define __RF_PATH_H__
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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<Frequency>;
|
||||
|
||||
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 */
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -808,8 +808,9 @@ public:
|
||||
|
||||
void set_mixer_current(const uint8_t value);
|
||||
void set_frequency(const rf::Frequency lo_frequency);
|
||||
void set_gpo1(const bool new_value);
|
||||
|
||||
RegisterMap registers();
|
||||
reg_t read(const address_t reg_num);
|
||||
|
||||
private:
|
||||
spi::SPI _bus;
|
||||
@ -818,7 +819,6 @@ private:
|
||||
DirtyRegisters<Register, reg_count> _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);
|
||||
|
80
firmware/application/sd_card.cpp
Normal file
80
firmware/application/sd_card.cpp
Normal file
@ -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 <hal.h>
|
||||
|
||||
#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> 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 */
|
@ -22,26 +22,26 @@
|
||||
#ifndef __SD_CARD_H__
|
||||
#define __SD_CARD_H__
|
||||
|
||||
#include "ff.h"
|
||||
#include <cstdint>
|
||||
|
||||
#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> 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__*/
|
||||
|
85
firmware/application/signal.hpp
Normal file
85
firmware/application/signal.hpp
Normal file
@ -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 <cstdint>
|
||||
#include <list>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#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<class... Args>
|
||||
struct Signal {
|
||||
using Callback = std::function<void (Args...)>;
|
||||
|
||||
SignalToken operator+=(const Callback& callback) {
|
||||
const SignalToken token = next_token++;
|
||||
entries.emplace_back(std::make_unique<CallbackEntry>(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<CallbackEntry>;
|
||||
|
||||
std::list<EntryType> entries;
|
||||
SignalToken next_token = 1;
|
||||
};
|
||||
|
||||
#endif/*__SIGNAL_H__*/
|
34
firmware/application/spectrum_analysis_app.cpp
Normal file
34
firmware/application/spectrum_analysis_app.cpp
Normal file
@ -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);
|
||||
}
|
44
firmware/application/spectrum_analysis_app.hpp
Normal file
44
firmware/application/spectrum_analysis_app.hpp
Normal file
@ -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__*/
|
131
firmware/application/string_format.cpp
Normal file
131
firmware/application/string_format.cpp
Normal file
@ -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');
|
||||
}
|
41
firmware/application/string_format.hpp
Normal file
41
firmware/application/string_format.hpp
Normal file
@ -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 <cstdint>
|
||||
#include <string>
|
||||
|
||||
// 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__*/
|
69
firmware/application/temperature_logger.cpp
Normal file
69
firmware/application/temperature_logger.cpp
Normal file
@ -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 <algorithm>
|
||||
|
||||
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::sample_t> TemperatureLogger::history() const {
|
||||
std::vector<sample_t> 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;
|
||||
}
|
52
firmware/application/temperature_logger.hpp
Normal file
52
firmware/application/temperature_logger.hpp
Normal file
@ -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 <cstddef>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
class TemperatureLogger {
|
||||
public:
|
||||
using sample_t = uint8_t;
|
||||
|
||||
void second_tick();
|
||||
|
||||
size_t size() const;
|
||||
size_t capacity() const;
|
||||
|
||||
std::vector<sample_t> history() const;
|
||||
|
||||
private:
|
||||
std::array<sample_t, 128> 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__*/
|
285
firmware/application/tpms_app.cpp
Normal file
285
firmware/application/tpms_app.cpp
Normal file
@ -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<Reading> 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<int>(reader_.read(32, 8)) * 4 / 3 },
|
||||
Temperature { static_cast<int>(reader_.read(40, 8) & 0x7f) - 50 }
|
||||
};
|
||||
|
||||
case 72:
|
||||
return Reading {
|
||||
Reading::Type::FLM_72,
|
||||
reader_.read(0, 32),
|
||||
Pressure { static_cast<int>(reader_.read(40, 8)) * 4 / 3 },
|
||||
Temperature { static_cast<int>(reader_.read(48, 8)) - 50 }
|
||||
};
|
||||
|
||||
case 80:
|
||||
return Reading {
|
||||
Reading::Type::FLM_80,
|
||||
reader_.read(8, 32),
|
||||
Pressure { static_cast<int>(reader_.read(48, 8)) * 4 / 3 },
|
||||
Temperature { static_cast<int>(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<uint8_t, 10> bytes;
|
||||
for(size_t i=0; i<bytes.size(); i++) {
|
||||
bytes[i] = reader_.read(i * 8, 8);
|
||||
}
|
||||
|
||||
uint32_t checksum = 0;
|
||||
CRC<uint8_t> crc_72 { 0x01, 0x00 };
|
||||
CRC<uint8_t> crc_80 { 0x01, 0x00 };
|
||||
|
||||
for(size_t i=0; i<bytes.size(); i++) {
|
||||
const uint32_t byte_mask = 1 << i;
|
||||
const auto byte = bytes[i];
|
||||
|
||||
if( checksum_bytes & byte_mask ) {
|
||||
checksum += byte;
|
||||
}
|
||||
if( crc_72_bytes & byte_mask ) {
|
||||
crc_72.process_byte(byte);
|
||||
}
|
||||
if( crc_80_bytes & byte_mask ) {
|
||||
crc_80.process_byte(byte);
|
||||
}
|
||||
}
|
||||
|
||||
if( crc_80.checksum() == 0 ) {
|
||||
return 80;
|
||||
} else if( crc_72.checksum() == 0 ) {
|
||||
return 72;
|
||||
} else if( (checksum & 0xff) == bytes[7] ) {
|
||||
return 64;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace tpms */
|
||||
|
||||
void TPMSLogger::on_packet(const tpms::Packet& packet) {
|
||||
const auto hex_formatted = packet.symbols_formatted();
|
||||
|
||||
if( log_file.is_ready() ) {
|
||||
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 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<std::pair<std::string, size_t>, 5> tpms_columns { {
|
||||
{ "Tp", 2 },
|
||||
{ "ID", 8 },
|
||||
{ "kPa", 3 },
|
||||
{ "C", 3 },
|
||||
{ "Cnt", 3 },
|
||||
} };
|
||||
|
||||
template<>
|
||||
void RecentEntriesView<TPMSRecentEntries>::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<TPMSRecentEntries>::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<const TPMSPacketMessage*>(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 */
|
226
firmware/application/tpms_app.hpp
Normal file
226
firmware/application/tpms_app.hpp
Normal file
@ -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> pressure = { },
|
||||
Optional<Temperature> temperature = { }
|
||||
) : type_ { type },
|
||||
id_ { id },
|
||||
pressure_ { pressure },
|
||||
temperature_ { temperature }
|
||||
{
|
||||
}
|
||||
|
||||
Type type() const {
|
||||
return type_;
|
||||
}
|
||||
|
||||
TransponderID id() const {
|
||||
return id_;
|
||||
}
|
||||
|
||||
Optional<Pressure> pressure() const {
|
||||
return pressure_;
|
||||
}
|
||||
|
||||
Optional<Temperature> temperature() const {
|
||||
return temperature_;
|
||||
}
|
||||
|
||||
private:
|
||||
Type type_ { Type::None };
|
||||
TransponderID id_ { 0 };
|
||||
Optional<Pressure> pressure_ { };
|
||||
Optional<Temperature> 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> reading() const;
|
||||
|
||||
private:
|
||||
using Reader = FieldReader<ManchesterDecoder, BitRemapNone>;
|
||||
|
||||
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<tpms::Reading::Type, tpms::TransponderID>;
|
||||
|
||||
static const Key invalid_key;
|
||||
|
||||
tpms::Reading::Type type { invalid_key.first };
|
||||
tpms::TransponderID id { invalid_key.second };
|
||||
|
||||
size_t received_count { 0 };
|
||||
|
||||
Optional<Pressure> last_pressure;
|
||||
Optional<Temperature> 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<tpms::Reading, TPMSRecentEntry>;
|
||||
|
||||
class TPMSLogger {
|
||||
public:
|
||||
void on_packet(const tpms::Packet& packet);
|
||||
|
||||
private:
|
||||
LogFile log_file { "tpms.txt" };
|
||||
};
|
||||
|
||||
namespace ui {
|
||||
|
||||
using TPMSRecentEntriesView = RecentEntriesView<TPMSRecentEntries>;
|
||||
|
||||
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__*/
|
@ -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 { };
|
||||
|
@ -21,12 +21,16 @@
|
||||
|
||||
#include "ui_audio.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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<const AudioStatisticsMessage*>(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<int> 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<int> 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<ui::Coord>(r.left() + x_0), r.top(),
|
||||
static_cast<ui::Dim>(x_rms - x_0), r.height()
|
||||
static_cast<ui::Coord>(r.left()), r.top(),
|
||||
static_cast<ui::Dim>(x_rms), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r0,
|
||||
@ -75,7 +81,7 @@ void Audio::paint(Painter& painter) {
|
||||
|
||||
const Rect r3 {
|
||||
static_cast<ui::Coord>(r.left() + x_max), r.top(),
|
||||
static_cast<ui::Dim>(x_lim - x_max), r.height()
|
||||
static_cast<ui::Dim>(r.width() - x_max), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r3,
|
||||
|
@ -26,6 +26,8 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
|
@ -21,12 +21,16 @@
|
||||
|
||||
#include "ui_baseband_stats_view.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#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<const BasebandStatisticsMessage*>(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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -21,12 +21,16 @@
|
||||
|
||||
#include "ui_channel.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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<const ChannelStatisticsMessage*>(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<int> 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<ui::Coord>(r.left() + x_0), r.top(),
|
||||
static_cast<ui::Dim>(x_max - x_0), r.height()
|
||||
static_cast<ui::Coord>(r.left()), r.top(),
|
||||
static_cast<ui::Dim>(x_max), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r0,
|
||||
@ -65,7 +70,7 @@ void Channel::paint(Painter& painter) {
|
||||
|
||||
const Rect r2 {
|
||||
static_cast<ui::Coord>(r.left() + x_max + 1), r.top(),
|
||||
static_cast<ui::Dim>(x_lim - (x_max + 1)), r.height()
|
||||
static_cast<ui::Dim>(r.width() - (x_max + 1)), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r2,
|
||||
|
@ -26,6 +26,8 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
|
@ -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<Coord>(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<int>(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<history.size(); i++) {
|
||||
const Coord x = graph_rect.right() - (history.size() - i) * bar_width;
|
||||
const auto sample = history[i];
|
||||
const auto temp = temperature(sample);
|
||||
const auto y = screen_y(temp, graph_rect);
|
||||
const Dim bar_height = graph_rect.bottom() - y;
|
||||
painter.fill_rectangle({ x, y, bar_width, bar_height }, color_foreground);
|
||||
}
|
||||
|
||||
if( !history.empty() ) {
|
||||
const auto sample = history.back();
|
||||
const auto temp = temperature(sample);
|
||||
const auto last_y = screen_y(temp, graph_rect);
|
||||
const Coord x = graph_rect.right() + 8;
|
||||
const Coord y = last_y - 8;
|
||||
|
||||
painter.draw_string({ x, y }, style(), temperature_str(temp));
|
||||
}
|
||||
|
||||
const auto display_temp_max = display_temp_min + (graph_rect.height() / display_temp_scale);
|
||||
for(auto temp=display_temp_min; temp<=display_temp_max; temp+=10) {
|
||||
const int32_t tick_length = 6;
|
||||
const auto tick_x = graph_rect.left() - tick_length;
|
||||
const auto tick_y = screen_y(temp, graph_rect);
|
||||
painter.fill_rectangle({ tick_x, tick_y, tick_length, 1 }, color_reticle);
|
||||
const auto text_x = graph_rect.left() - temp_len * 8 - 8;
|
||||
const auto text_y = tick_y - 8;
|
||||
painter.draw_string({ text_x, text_y }, style(), temperature_str(temp));
|
||||
}
|
||||
}
|
||||
|
||||
TemperatureWidget::temperature_t TemperatureWidget::temperature(const sample_t sensor_value) const {
|
||||
return -45 + sensor_value * 5;
|
||||
}
|
||||
|
||||
std::string TemperatureWidget::temperature_str(const temperature_t temperature) const {
|
||||
return to_string_dec_int(temperature, temp_len - 1) + "C";
|
||||
}
|
||||
|
||||
Coord TemperatureWidget::screen_y(
|
||||
const temperature_t temperature,
|
||||
const Rect& rect
|
||||
) const {
|
||||
int y_raw = rect.bottom() - ((temperature - display_temp_min) * display_temp_scale);
|
||||
const auto y_limit = std::min(rect.bottom(), std::max(rect.top(), y_raw));
|
||||
return y_limit;
|
||||
}
|
||||
|
||||
/* TemperatureView *******************************************************/
|
||||
|
||||
TemperatureView::TemperatureView(NavigationView& nav) {
|
||||
add_children({ {
|
||||
&text_title,
|
||||
&temperature_widget,
|
||||
&button_done,
|
||||
} });
|
||||
|
||||
button_done.on_select = [&nav](Button&){ nav.pop(); };
|
||||
}
|
||||
|
||||
void TemperatureView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
/* RegistersWidget *******************************************************/
|
||||
|
||||
RegistersWidget::RegistersWidget(
|
||||
RegistersWidgetConfig&& config,
|
||||
std::function<uint32_t(const size_t register_number)>&& 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<registers_count; i+=registers_per_row) {
|
||||
void RegistersWidget::draw_legend(const Coord left, Painter& painter) {
|
||||
const auto pos = screen_pos();
|
||||
|
||||
for(int i=0; i<config.registers_count; i+=config.registers_per_row) {
|
||||
const Point offset {
|
||||
0, static_cast<Coord>((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<registers_count; i++) {
|
||||
const auto pos = screen_pos();
|
||||
|
||||
for(int i=0; i<config.registers_count; i++) {
|
||||
const Point offset = {
|
||||
static_cast<Coord>(legend_width + 8 + (i % registers_per_row) * (value_width + 8)),
|
||||
static_cast<Coord>((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<uint32_t(const size_t register_number)>&& 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<int>(title.size()) * 8) / 2, 16,
|
||||
static_cast<int>(title.size()) * 8, 16
|
||||
});
|
||||
text_title.set(title);
|
||||
}
|
||||
|
||||
void DebugRFFC5072View::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
void DebugSDView::paint(Painter& painter) {
|
||||
const Point offset = {
|
||||
static_cast<Coord>(32),
|
||||
static_cast<Coord>(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<Coord>(c/3),oy},{static_cast<Coord>((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<DebugMemoryView>(); } },
|
||||
{ "Radio State", [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "SD Card", [&nav](){ nav.push<NotImplementedView>(); } },
|
||||
{ "RFFC5072", [&nav](){ nav.push<RegistersView>(
|
||||
"RFFC5072", RegistersWidgetConfig { 31, 2, 4, 4 },
|
||||
[](const size_t register_number) { return radio::first_if.read(register_number); }
|
||||
); } },
|
||||
{ "MAX2837", [&nav](){ nav.push<RegistersView>(
|
||||
"MAX2837", RegistersWidgetConfig { 32, 2, 3, 4 },
|
||||
[](const size_t register_number) { return radio::second_if.read(register_number); }
|
||||
); } },
|
||||
{ "Si5351C", [&nav](){ nav.push<RegistersView>(
|
||||
"Si5351C", RegistersWidgetConfig { 96, 2, 2, 8 },
|
||||
[](const size_t register_number) { return portapack::clock_generator.read_register(register_number); }
|
||||
); } },
|
||||
{ "WM8731", [&nav](){ nav.push<RegistersView>(
|
||||
"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<TemperatureView>(); } },
|
||||
} });
|
||||
on_left = [&nav](){ nav.pop(); };
|
||||
}
|
||||
|
@ -29,6 +29,11 @@
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include "rffc507x.hpp"
|
||||
#include "max2837.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
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<uint32_t(const size_t register_number)>&& reader
|
||||
);
|
||||
|
||||
void update();
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
static constexpr size_t registers_count { 31 };
|
||||
const RegistersWidgetConfig config;
|
||||
const std::function<uint32_t(const size_t register_number)> 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<uint32_t(const size_t register_number)>&& 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"
|
||||
};
|
||||
};
|
||||
|
@ -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<View> 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<AISAppView>(); } },
|
||||
{ "ERT: Utility Meters", [&nav](){ nav.push<ERTAppView>(); } },
|
||||
{ "TPMS: Cars", [&nav](){ nav.push<TPMSAppView>(); } },
|
||||
} });
|
||||
}
|
||||
|
||||
/* ReceiverMenuView ******************************************************/
|
||||
|
||||
ReceiverMenuView::ReceiverMenuView(NavigationView& nav) {
|
||||
add_items<2>({ {
|
||||
{ "Audio", [&nav](){ nav.push<ReceiverView>(); } },
|
||||
{ "Transponders", [&nav](){ nav.push<TranspondersMenuView>(); } },
|
||||
} });
|
||||
}
|
||||
|
||||
/* 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<ui::Dim>(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<BMPView>();
|
||||
else
|
||||
navigation_view.push(new SystemMenuView { navigation_view });
|
||||
navigation_view.push<SystemMenuView>();
|
||||
}
|
||||
|
||||
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<std::underlying_type<KeyEvent>::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) {
|
||||
|
@ -30,44 +30,76 @@
|
||||
#include "ui_rssi.hpp"
|
||||
#include "ui_channel.hpp"
|
||||
#include "ui_audio.hpp"
|
||||
#include "ui_sd_card_status_view.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class SystemStatusView : public View {
|
||||
public:
|
||||
std::function<void(void)> 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<void(const View&)> on_view_changed;
|
||||
|
||||
NavigationView() { }
|
||||
|
||||
NavigationView(const NavigationView&) = delete;
|
||||
NavigationView(NavigationView&&) = delete;
|
||||
|
||||
void push(View* new_view);
|
||||
bool is_top() const;
|
||||
|
||||
template<class T, class... Args>
|
||||
T* push(Args&&... args) {
|
||||
return reinterpret_cast<T*>(push_view(std::unique_ptr<View>(new T(*this, std::forward<Args>(args)...))));
|
||||
}
|
||||
|
||||
void pop();
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
std::vector<View*> view_stack;
|
||||
std::vector<std::unique_ptr<View>> view_stack;
|
||||
|
||||
Widget* view() const;
|
||||
void set_view(Widget* const new_view);
|
||||
|
||||
void free_view();
|
||||
void update_view();
|
||||
View* push_view(std::unique_ptr<View> 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);
|
||||
|
@ -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<Coord>((n % 3) * button_w),
|
||||
static_cast<Coord>((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<FrequencyKeypadView>(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<ReceiverModel::Mode>(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<const AISPacketMessage*>(p);
|
||||
this->on_packet_ais(*message);
|
||||
}
|
||||
);
|
||||
message_map.register_handler(Message::ID::TPMSPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const TPMSPacketMessage*>(p);
|
||||
this->on_packet_tpms(*message);
|
||||
}
|
||||
);
|
||||
message_map.register_handler(Message::ID::SDCardStatus,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const SDCardStatusMessage*>(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<ReceiverModel::Mode>(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<ReceiverModel::Mode>(-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<Console*>(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<payload_length; i+=2) {
|
||||
const auto bit_data = payload[i+1];
|
||||
const auto bit_error = (payload[i+0] == payload[i+1]);
|
||||
|
||||
byte_data <<= 1;
|
||||
byte_data |= bit_data ? 1 : 0;
|
||||
|
||||
byte_error <<= 1;
|
||||
byte_error |= bit_error ? 1 : 0;
|
||||
|
||||
if( ((i >> 1) & 7) == 7 ) {
|
||||
hex_data += to_string_hex(byte_data, 2);
|
||||
hex_error += to_string_hex(byte_error, 2);
|
||||
}
|
||||
}
|
||||
|
||||
auto console = reinterpret_cast<Console*>(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<Console>();
|
||||
add_child(widget_content.get());
|
||||
switch(mode) {
|
||||
case ReceiverModel::Mode::AMAudio:
|
||||
case ReceiverModel::Mode::NarrowbandFMAudio:
|
||||
case ReceiverModel::Mode::WidebandFMAudio:
|
||||
widget_content = std::make_unique<AnalogAudioView>(mode);
|
||||
break;
|
||||
|
||||
case ReceiverModel::Mode::SpectrumAnalysis:
|
||||
widget_content = std::make_unique<SpectrumAnalysisView>();
|
||||
break;
|
||||
|
||||
default:
|
||||
widget_content = std::make_unique<spectrum::WaterfallWidget>();
|
||||
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<ui::Dim>(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 */
|
||||
|
@ -44,11 +44,6 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
class BasebandBandwidthField : public OptionsField {
|
||||
public:
|
||||
BasebandBandwidthField(Point parent_pos);
|
||||
};
|
||||
|
||||
class FrequencyField : public Widget {
|
||||
public:
|
||||
std::function<void(rf::Frequency)> 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 */
|
||||
|
@ -21,12 +21,16 @@
|
||||
|
||||
#include "ui_rssi.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
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<const RSSIStatisticsMessage*>(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<int> 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<int> x_min_range { 0, x_avg };
|
||||
const auto x_min = x_min_range.clip((min_ - raw_min) * r.width() / raw_delta);
|
||||
const range_t<int> 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<ui::Coord>(r.left() + x_0), r.top(),
|
||||
static_cast<ui::Dim>(x_min - x_0), r.height()
|
||||
static_cast<ui::Coord>(r.left()), r.top(),
|
||||
static_cast<ui::Dim>(x_min), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r0,
|
||||
@ -90,7 +96,7 @@ void RSSI::paint(Painter& painter) {
|
||||
|
||||
const Rect r4 {
|
||||
static_cast<ui::Coord>(r.left() + x_max), r.top(),
|
||||
static_cast<ui::Dim>(x_lim - x_max), r.height()
|
||||
static_cast<ui::Dim>(r.width() - x_max), r.height()
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r4,
|
||||
|
@ -26,6 +26,8 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ui {
|
||||
|
85
firmware/application/ui_sd_card_status_view.cpp
Normal file
85
firmware/application/ui_sd_card_status_view.cpp
Normal file
@ -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 <string>
|
||||
#include <algorithm>
|
||||
|
||||
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 */
|
50
firmware/application/ui_sd_card_status_view.hpp
Normal file
50
firmware/application/ui_sd_card_status_view.hpp
Normal file
@ -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__*/
|
@ -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 <math.h>
|
||||
#include <cstring>
|
||||
|
||||
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<SetFrequencyCorrectionView>(); } },
|
||||
{ "Antenna Bias Voltage", [&nav](){ nav.push<AntennaBiasSetupView>(); } },
|
||||
{ "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 }); } },
|
||||
|
@ -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"
|
||||
Text text_title {
|
||||
{ 5 * 8, 3 * 16, 20 * 8, 16 },
|
||||
"Antenna Bias Voltage"
|
||||
};
|
||||
|
||||
Checkbox checkbox_bloff {
|
||||
{ 3 * 8, 4 * 16},
|
||||
"Backlight off after:"
|
||||
Text text_description_1 {
|
||||
{ 24, 6 * 16, 24 * 8, 16 },
|
||||
"CAUTION: Ensure that all"
|
||||
};
|
||||
|
||||
OptionsField options_bloff {
|
||||
{ 10 * 8, 5 * 16 + 4 },
|
||||
10,
|
||||
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"
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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 <cmath>
|
||||
#include <array>
|
||||
|
||||
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<Color, 240> 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<const ChannelSpectrumConfigMessage*>(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 */
|
||||
|
@ -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 <cstdint>
|
||||
#include <cmath>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
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<decltype(ChannelSpectrum::db)>::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<Coord>(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<Coord>(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<Coord>(offset_low + 2), r.top() }, style(), label );
|
||||
|
||||
const Coord offset_high = static_cast<Coord>(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<Coord>(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<Coord>(r.left() + stop_x_lo), static_cast<Coord>(r.bottom() - filter_band_height),
|
||||
static_cast<Dim>(pass_x_lo - stop_x_lo), filter_band_height
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r_stop_lo,
|
||||
Color::yellow()
|
||||
);
|
||||
|
||||
const Rect r_stop_hi {
|
||||
static_cast<Coord>(r.left() + pass_x_hi), static_cast<Coord>(r.bottom() - filter_band_height),
|
||||
static_cast<Dim>(stop_x_hi - pass_x_hi), filter_band_height
|
||||
};
|
||||
painter.fill_rectangle(
|
||||
r_stop_hi,
|
||||
Color::yellow()
|
||||
);
|
||||
}
|
||||
|
||||
const Rect r_pass {
|
||||
static_cast<Coord>(r.left() + pass_x_lo), static_cast<Coord>(r.bottom() - filter_band_height),
|
||||
static_cast<Dim>(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<Color, 240> 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<const ChannelSpectrumMessage*>(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<Dim>(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 */
|
||||
|
@ -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 \
|
||||
|
@ -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() {
|
||||
|
100
firmware/baseband/audio_output.cpp
Normal file
100
firmware/baseband/audio_output.cpp
Normal file
@ -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 <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
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<float, 32> audio_f;
|
||||
for(size_t i=0; i<audio.count; i++) {
|
||||
audio_f[i] = audio.p[i];
|
||||
}
|
||||
write(buffer_f32_t {
|
||||
audio_f.data(),
|
||||
audio.count,
|
||||
audio.sampling_rate
|
||||
});
|
||||
}
|
||||
|
||||
void AudioOutput::write(
|
||||
const buffer_f32_t& audio
|
||||
) {
|
||||
const auto audio_present_now = squelch.execute(audio);
|
||||
|
||||
hpf.execute_in_place(audio);
|
||||
deemph.execute_in_place(audio);
|
||||
|
||||
audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0);
|
||||
const bool audio_present = (audio_present_history != 0);
|
||||
|
||||
if( audio_present ) {
|
||||
i2s::i2s0::tx_unmute();
|
||||
} else {
|
||||
i2s::i2s0::tx_mute();
|
||||
for(size_t i=0; i<audio.count; i++) {
|
||||
audio.p[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fill_audio_buffer(audio);
|
||||
}
|
||||
|
||||
void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio) {
|
||||
auto audio_buffer = audio::dma::tx_empty_buffer();
|
||||
for(size_t i=0; i<audio_buffer.count; i++) {
|
||||
const int32_t sample_int = audio.p[i];
|
||||
const int32_t sample_saturated = __SSAT(sample_int, 16);
|
||||
audio_buffer.p[i].left = audio_buffer.p[i].right = sample_saturated;
|
||||
}
|
||||
|
||||
feed_audio_stats(audio);
|
||||
}
|
||||
|
||||
void AudioOutput::feed_audio_stats(const buffer_f32_t& audio) {
|
||||
audio_stats.feed(
|
||||
audio,
|
||||
[](const AudioStatistics& statistics) {
|
||||
const AudioStatisticsMessage audio_stats_message { statistics };
|
||||
shared_memory.application_queue.push(audio_stats_message);
|
||||
}
|
||||
);
|
||||
}
|
58
firmware/baseband/audio_output.hpp
Normal file
58
firmware/baseband/audio_output.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 __AUDIO_OUTPUT_H__
|
||||
#define __AUDIO_OUTPUT_H__
|
||||
|
||||
#include "dsp_types.hpp"
|
||||
|
||||
#include "dsp_iir.hpp"
|
||||
#include "dsp_squelch.hpp"
|
||||
|
||||
#include "audio_stats_collector.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
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__*/
|
67
firmware/baseband/audio_stats_collector.cpp
Normal file
67
firmware/baseband/audio_stats_collector.cpp
Normal file
@ -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);
|
||||
}
|
@ -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 <cstdint>
|
||||
#include <cstddef>
|
||||
@ -32,64 +31,33 @@
|
||||
class AudioStatsCollector {
|
||||
public:
|
||||
template<typename Callback>
|
||||
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<typename Callback>
|
||||
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__*/
|
||||
|
@ -20,7 +20,6 @@
|
||||
*/
|
||||
|
||||
#include "baseband_dma.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@ -36,8 +35,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;
|
||||
constexpr uint32_t gpdma_ahb_master_lli_fetch = 0;
|
||||
@ -102,17 +99,12 @@ constexpr size_t msg_count = transfers_per_buffer - 1;
|
||||
static std::array<gpdma::channel::LLI, transfers_per_buffer> lli_loop;
|
||||
static constexpr auto& gpdma_channel_sgpio = gpdma::channels[portapack::sgpio_gpdma_channel_number];
|
||||
|
||||
//static Mailbox mailbox;
|
||||
//static std::array<msg_t, msg_count> 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<sample_t*>(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<sample_t*>(lli_loop[free_index].srcaddr), transfer_samples };
|
||||
} else {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
} else {
|
||||
return { nullptr, 0 };
|
||||
return { };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 <cstddef>
|
||||
#include <algorithm>
|
||||
|
||||
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<spectrum_message.spectrum.db.size(); i++) {
|
||||
const auto mag2 = magnitude_squared(channel_spectrum[i]);
|
||||
const float db = complex16_mag_squared_to_dbv_norm(mag2);
|
||||
constexpr float mag_scale = 5.0f;
|
||||
const unsigned int v = (db * mag_scale) + 255.0f;
|
||||
spectrum_message.spectrum.db[i] = std::max(0U, std::min(255U, v));
|
||||
}
|
||||
|
||||
/* TODO: Rename .db -> .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; i<audio_buffer.count; i++) {
|
||||
audio_buffer.p[i].left = audio_buffer.p[i].right = audio.p[i];
|
||||
}
|
||||
i2s::i2s0::tx_unmute();
|
||||
|
||||
feed_audio_stats(audio);
|
||||
}
|
||||
|
||||
void BasebandProcessor::post_channel_stats_message(const ChannelStatistics statistics) {
|
||||
channel_stats_message.statistics = statistics;
|
||||
shared_memory.application_queue.push(channel_stats_message);
|
||||
}
|
||||
|
||||
void BasebandProcessor::post_channel_spectrum_message(const buffer_c16_t data) {
|
||||
if( !channel_spectrum_request_update ) {
|
||||
fft_swap(data, channel_spectrum);
|
||||
channel_spectrum_sampling_rate = data.sampling_rate;
|
||||
channel_spectrum_request_update = true;
|
||||
events_flag(EVT_MASK_SPECTRUM);
|
||||
}
|
||||
}
|
||||
|
||||
void BasebandProcessor::feed_audio_stats(const buffer_s16_t audio) {
|
||||
audio_stats.feed(
|
||||
audio,
|
||||
[this](const AudioStatistics statistics) {
|
||||
this->post_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);
|
||||
}
|
||||
|
@ -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 <array>
|
||||
#include <cstdint>
|
||||
#include <complex>
|
||||
#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<std::complex<float>, 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__*/
|
||||
|
59
firmware/baseband/baseband_stats_collector.cpp
Normal file
59
firmware/baseband/baseband_stats_collector.cpp
Normal file
@ -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;
|
||||
}
|
@ -26,7 +26,6 @@
|
||||
|
||||
#include "dsp_types.hpp"
|
||||
#include "message.hpp"
|
||||
#include "utility_m4.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@ -46,36 +45,9 @@ public:
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
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__*/
|
||||
|
157
firmware/baseband/baseband_thread.cpp
Normal file
157
firmware/baseband/baseband_thread.cpp
Normal file
@ -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 <array>
|
||||
|
||||
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<const BasebandConfigurationMessage*>(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::sample_t, 8192>();
|
||||
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();
|
||||
}
|
||||
}
|
65
firmware/baseband/baseband_thread.hpp
Normal file
65
firmware/baseband/baseband_thread.hpp
Normal file
@ -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 <ch.h>
|
||||
|
||||
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__*/
|
@ -65,7 +65,7 @@ public:
|
||||
}
|
||||
|
||||
template<typename BlockCallback>
|
||||
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);
|
||||
|
@ -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<int16_t>[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<int16_t>[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<int16_t>[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);
|
||||
}
|
||||
}
|
||||
|
@ -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<complex16_t, 1024> 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__*/
|
||||
|
@ -34,7 +34,7 @@
|
||||
class ChannelStatsCollector {
|
||||
public:
|
||||
template<typename Callback>
|
||||
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)++;
|
||||
|
@ -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<int32_t> by a scale factor,
|
||||
* into int64_ts, then round to nearest LSB (1 << 32), saturate to 16 bits,
|
||||
* and pack into a complex<int16_t>.
|
||||
*/
|
||||
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<tap_t, taps_count>& taps,
|
||||
const int32_t scale,
|
||||
const Shift shift
|
||||
) {
|
||||
const int negate_factor = (shift == Shift::Up) ? -1 : 1;
|
||||
for(size_t i=0; i<taps.size(); i+=4) {
|
||||
taps_[i+0] = taps[i+0];
|
||||
taps_[i+1] = taps[i+1] * negate_factor;
|
||||
taps_[i+2] = -taps[i+2];
|
||||
taps_[i+3] = taps[i+3] * negate_factor;
|
||||
}
|
||||
output_scale = scale;
|
||||
}
|
||||
|
||||
buffer_c16_t FIRC8xR16x24FS4Decim4::execute(
|
||||
const buffer_c8_t& src,
|
||||
const buffer_c16_t& dst
|
||||
) {
|
||||
vec2_s16* const z = static_cast<vec2_s16*>(__builtin_assume_aligned(z_.data(), 4));
|
||||
const vec2_s16* const t = static_cast<vec2_s16*>(__builtin_assume_aligned(taps_.data(), 4));
|
||||
uint32_t* const d = static_cast<uint32_t*>(__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<count; i++) {
|
||||
const vec4_s8* const in = static_cast<const vec4_s8*>(__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<tap_t, taps_count>& taps,
|
||||
const int32_t scale,
|
||||
const Shift shift
|
||||
) {
|
||||
const int negate_factor = (shift == Shift::Up) ? -1 : 1;
|
||||
for(size_t i=0; i<taps.size(); i+=4) {
|
||||
taps_[i+0] = taps[i+0];
|
||||
taps_[i+1] = taps[i+1] * negate_factor;
|
||||
taps_[i+2] = -taps[i+2];
|
||||
taps_[i+3] = taps[i+3] * negate_factor;
|
||||
}
|
||||
output_scale = scale;
|
||||
}
|
||||
|
||||
buffer_c16_t FIRC8xR16x24FS4Decim8::execute(
|
||||
const buffer_c8_t& src,
|
||||
const buffer_c16_t& dst
|
||||
) {
|
||||
vec2_s16* const z = static_cast<vec2_s16*>(__builtin_assume_aligned(z_.data(), 4));
|
||||
const vec2_s16* const t = static_cast<vec2_s16*>(__builtin_assume_aligned(taps_.data(), 4));
|
||||
uint32_t* const d = static_cast<uint32_t*>(__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<count; i++) {
|
||||
const vec4_s8* const in = static_cast<const vec4_s8*>(__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<tap_t, taps_count>& 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<vec2_s16*>(__builtin_assume_aligned(z_.data(), 4));
|
||||
const vec2_s16* const t = static_cast<vec2_s16*>(__builtin_assume_aligned(taps_.data(), 4));
|
||||
uint32_t* const d = static_cast<uint32_t*>(__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<count; i++) {
|
||||
const vec2_s16* const in = static_cast<const vec2_s16*>(__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<tap_t, taps_count>& 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<vec2_s16*>(__builtin_assume_aligned(z_.data(), 4));
|
||||
const vec2_s16* const t = static_cast<vec2_s16*>(__builtin_assume_aligned(taps_.data(), 4));
|
||||
uint32_t* const d = static_cast<uint32_t*>(__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<count; i++) {
|
||||
const vec2_s16* const in = static_cast<const vec2_s16*>(__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<uint32_t*>(&src.p[0]);
|
||||
uint32_t* const src_end = reinterpret_cast<uint32_t*>(&src.p[src.count]);
|
||||
uint32_t* dst_p = reinterpret_cast<uint32_t*>(&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<int8_t> 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<int16_t, taps_count>& 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<samples_t>(taps_count);
|
||||
taps_reversed_ = std::make_unique<taps_t>(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 */
|
||||
|
@ -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<int16_t, taps_count>& 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<int16_t, taps_count + 2> z;
|
||||
const std::array<int16_t, taps_count>& taps;
|
||||
std::array<int16_t, taps_count> 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<tap_t, taps_count>& 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<vec2_s16, taps_count - decimation_factor> z_;
|
||||
std::array<tap_t, taps_count> 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<tap_t, taps_count>& 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<vec2_s16, taps_count - decimation_factor> z_;
|
||||
std::array<tap_t, taps_count> 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<tap_t, taps_count>& taps,
|
||||
const int32_t scale
|
||||
);
|
||||
|
||||
buffer_c16_t execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
std::array<vec2_s16, taps_count - decimation_factor> z_;
|
||||
std::array<tap_t, taps_count> 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<tap_t, taps_count>& taps,
|
||||
const int32_t scale
|
||||
);
|
||||
|
||||
buffer_c16_t execute(
|
||||
const buffer_c16_t& src,
|
||||
const buffer_c16_t& dst
|
||||
);
|
||||
|
||||
private:
|
||||
std::array<vec2_s16, taps_count - decimation_factor> z_;
|
||||
std::array<tap_t, taps_count> 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<samples_t>(taps.size());
|
||||
taps_reversed_ = std::make_unique<taps_t>(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_t> 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<size_t N>
|
||||
class FIRAndDecimateBy2Complex {
|
||||
public:
|
||||
FIR64AndDecimateBy2Complex(
|
||||
const std::array<int16_t, N>& 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<complex16_t, N> z;
|
||||
const std::array<int16_t, N>& taps;
|
||||
|
||||
complex<int16_t> process_one(const size_t start_offset) {
|
||||
const auto split = &z[start_offset];
|
||||
const auto end = &z[z.size()];
|
||||
auto tap = &taps[0];
|
||||
|
||||
complex<int32_t> 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 */
|
||||
|
||||
|
@ -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<float>(t.imag()) / static_cast<float>(t.real());
|
||||
return x / (1.0f + 0.28086f * x * x);
|
||||
if( t.real() ) {
|
||||
const auto x = static_cast<float>(t.imag()) / static_cast<float>(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_;
|
||||
|
||||
|
@ -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<float>(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<float>(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 */
|
||||
|
@ -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 <cstdint>
|
||||
#include <array>
|
||||
|
||||
#include "complex.hpp"
|
||||
|
||||
template<size_t N>
|
||||
struct fir_taps_real {
|
||||
float pass_frequency_normalized;
|
||||
float stop_frequency_normalized;
|
||||
std::array<int16_t, N> 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__*/
|
57
firmware/baseband/dsp_iir.cpp
Normal file
57
firmware/baseband/dsp_iir.cpp
Normal file
@ -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 <hal.h>
|
||||
|
||||
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<buffer_out.count; i++) {
|
||||
x_[0] = x_[1];
|
||||
x_[1] = x_[2];
|
||||
x_[2] = buffer_in.p[i];
|
||||
|
||||
y_[0] = y_[1];
|
||||
y_[1] = y_[2];
|
||||
y_[2] = b_[0] * x_[2] + b_[1] * x_[1] + b_[2] * x_[0]
|
||||
- a_[1] * y_[1] - a_[2] * y_[0];
|
||||
|
||||
buffer_out.p[i] = y_[2];
|
||||
}
|
||||
|
||||
x = x_;
|
||||
y = y_;
|
||||
}
|
||||
|
||||
void IIRBiquadFilter::execute_in_place(const buffer_f32_t& buffer) {
|
||||
execute(buffer, buffer);
|
||||
}
|
@ -27,13 +27,27 @@
|
||||
#include "dsp_types.hpp"
|
||||
|
||||
struct iir_biquad_config_t {
|
||||
const std::array<float, 3> b;
|
||||
const std::array<float, 3> a;
|
||||
std::array<float, 3> b;
|
||||
std::array<float, 3> 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<buffer_out.count; i++) {
|
||||
buffer_out.p[i] = execute_sample(buffer_in.p[i]);
|
||||
}
|
||||
}
|
||||
void configure(const iir_biquad_config_t& new_config);
|
||||
|
||||
void execute_in_place(buffer_s16_t buffer) {
|
||||
execute(buffer, buffer);
|
||||
}
|
||||
void execute(const buffer_f32_t& buffer_in, const buffer_f32_t& buffer_out);
|
||||
void execute_in_place(const buffer_f32_t& buffer);
|
||||
|
||||
private:
|
||||
const iir_biquad_config_t config;
|
||||
iir_biquad_config_t config;
|
||||
std::array<float, 3> x { { 0.0f, 0.0f, 0.0f } };
|
||||
std::array<float, 3> 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__*/
|
||||
|
@ -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__*/
|
||||
|
@ -24,22 +24,30 @@
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
|
||||
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<int16_t, N> squelch_energy_buffer;
|
||||
const buffer_s16_t squelch_energy {
|
||||
std::array<float, N> 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;
|
||||
}
|
||||
|
@ -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 };
|
||||
};
|
||||
|
||||
|
@ -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 <cstdint>
|
||||
#include <array>
|
||||
|
||||
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<uint8_t, Message::MAX_SIZE> 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<const ShutdownMessage*>(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);
|
||||
}
|
||||
|
@ -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__*/
|
||||
|
@ -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 <array>
|
||||
#include <string>
|
||||
#include <bitset>
|
||||
#include <math.h>
|
||||
|
||||
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();
|
||||
// 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
|
||||
};
|
||||
|
||||
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,24 +292,7 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
const auto baseband_buffer =
|
||||
new std::array<baseband::sample_t, 8192>();
|
||||
|
||||
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<char*>(&ram_loop_fn), 32);
|
||||
loop_ptr = reinterpret_cast<fn_ptr>(&ram_loop[0]);
|
||||
ReadyForSwitchMessage message;
|
||||
shared_memory.application_queue.push(message);
|
||||
(*loop_ptr)();
|
||||
}
|
||||
static constexpr auto direction = baseband::Direction::Receive;
|
||||
|
||||
int main(void) {
|
||||
init();
|
||||
@ -374,18 +303,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<ModuleIDMessage*>(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) {
|
||||
auto message = reinterpret_cast<const BasebandConfigurationMessage*>(p);
|
||||
@ -403,48 +320,30 @@ 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,15 +353,9 @@ 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::sample_t, 8192>();
|
||||
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;
|
||||
}
|
||||
|
@ -21,9 +21,26 @@
|
||||
|
||||
#include "matched_filter.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#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<samples_t>(taps_count);
|
||||
taps_reversed_ = std::make_unique<taps_t>(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
|
||||
) {
|
||||
|
@ -22,17 +22,10 @@
|
||||
#ifndef __MATCHED_FILTER_H__
|
||||
#define __MATCHED_FILTER_H__
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <complex>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
namespace dsp {
|
||||
namespace matched_filter {
|
||||
|
||||
@ -61,11 +54,7 @@ public:
|
||||
const T& taps,
|
||||
size_t decimation_factor
|
||||
) {
|
||||
samples_ = std::make_unique<samples_t>(taps.size());
|
||||
taps_reversed_ = std::make_unique<taps_t>(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 */
|
||||
|
@ -28,12 +28,26 @@
|
||||
#include <functional>
|
||||
|
||||
#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<typename PreambleMatcher, typename UnstuffMatcher, typename EndMatcher>
|
||||
class PacketBuilder {
|
||||
public:
|
||||
using PayloadType = std::bitset<1024>;
|
||||
using PayloadHandlerFunc = std::function<void(const PayloadType& payload, const size_t bits_received)>;
|
||||
using PayloadHandlerFunc = std::function<void(const baseband::Packet& packet)>;
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
@ -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; i<decimator_out.count; i++) {
|
||||
// TODO: No idea why implicit cast int16_t->float is not allowed.
|
||||
const std::complex<float> sample {
|
||||
static_cast<float>(decimator_out.p[i].real()),
|
||||
static_cast<float>(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);
|
||||
}
|
||||
|
@ -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<complex16_t, 512> 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::FixedErrorFilter> 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__*/
|
||||
|
@ -21,39 +21,67 @@
|
||||
|
||||
#include "proc_am_audio.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include "dsp_iir_config.hpp"
|
||||
#include "audio_output.hpp"
|
||||
|
||||
void NarrowbandAMAudio::execute(buffer_c8_t buffer) {
|
||||
auto decimator_out = decimator.execute(buffer);
|
||||
#include <array>
|
||||
|
||||
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<int16_t>[64]
|
||||
* -> FIR filter, <?kHz (0.???fs) pass, gain 1.0
|
||||
* -> 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<int16_t>[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<const AMConfigureMessage*>(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;
|
||||
}
|
||||
|
@ -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 <cstdint>
|
||||
|
||||
class NarrowbandAMAudio : public BasebandProcessor {
|
||||
public:
|
||||
NarrowbandAMAudio() {
|
||||
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_031_070_tfilter;
|
||||
std::array<complex16_t, 512> 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__*/
|
||||
|
103
firmware/baseband/proc_ert.cpp
Normal file
103
firmware/baseband/proc_ert.cpp
Normal file
@ -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<float>(r2_i2));
|
||||
const float r = static_cast<float>(v.real()) - offset_i;
|
||||
const float i = static_cast<float>(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<float>(average_i) / average_window;
|
||||
offset_q = static_cast<float>(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);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user