This commit is contained in:
furrtek 2016-01-31 09:34:24 +01:00
parent 29ec87a9ad
commit 44638e504b
166 changed files with 8700 additions and 3967 deletions

View File

@ -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 \

View 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 */

View 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__*/

View 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);
}

View File

@ -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__*/

View 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 */

View 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__*/

View 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();
}
}

View 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__*/

View 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);
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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();

View 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();
}
}

View File

@ -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__*/

View File

@ -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();

View File

@ -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 {

View File

@ -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);

View File

@ -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
};

View File

@ -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();

View File

@ -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);
}

View File

@ -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 */

View File

@ -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() {

View File

@ -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__*/

View File

@ -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"

View 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__*/

View File

@ -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 */

View File

@ -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) {

View File

@ -808,8 +808,9 @@ public:
void set_mixer_current(const uint8_t value);
void set_frequency(const rf::Frequency lo_frequency);
RegisterMap registers();
void set_gpo1(const bool new_value);
reg_t read(const address_t reg_num);
private:
spi::SPI _bus;
@ -818,7 +819,6 @@ private:
DirtyRegisters<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);

View 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 */

View File

@ -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__*/

View 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__*/

View 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);
}

View 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__*/

View 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');
}

View 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__*/

View 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;
}

View 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__*/

View 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 */

View 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__*/

View File

@ -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 { };

View File

@ -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,

View File

@ -26,6 +26,8 @@
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "message.hpp"
#include <cstdint>
namespace ui {

View File

@ -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);
}

View File

@ -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,

View File

@ -26,6 +26,8 @@
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "message.hpp"
#include <cstdint>
namespace ui {

View File

@ -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;
}
}

View File

@ -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,
&registers_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(); };
}

View File

@ -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"
};
};

View File

@ -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) {

View File

@ -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);

View File

@ -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 */

View File

@ -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 */

View File

@ -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,

View File

@ -26,6 +26,8 @@
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "message.hpp"
#include <cstdint>
namespace ui {

View 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 */

View 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__*/

View File

@ -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 }); } },

View File

@ -22,8 +22,6 @@
#ifndef __UI_SETUP_H__
#define __UI_SETUP_H__
#include "ff.h"
#include "ui_widget.hpp"
#include "ui_menu.hpp"
#include "ui_navigation.hpp"
@ -207,129 +205,50 @@ private:
};
};
class SetUIView : public View {
class AntennaBiasSetupView : public View {
public:
SetUIView(NavigationView& nav);
AntennaBiasSetupView(NavigationView& nav);
void focus() override;
private:
Checkbox checkbox_showsplash {
{ 3 * 8, 2 * 16},
"Show splash"
};
Checkbox checkbox_bloff {
{ 3 * 8, 4 * 16},
"Backlight off after:"
Text text_title {
{ 5 * 8, 3 * 16, 20 * 8, 16 },
"Antenna Bias Voltage"
};
OptionsField options_bloff {
{ 10 * 8, 5 * 16 + 4 },
10,
Text text_description_1 {
{ 24, 6 * 16, 24 * 8, 16 },
"CAUTION: Ensure that all"
};
Text text_description_2 {
{ 28, 7 * 16, 23 * 8, 16 },
"devices attached to the"
};
Text text_description_3 {
{ 8, 8 * 16, 28 * 8, 16 },
"antenna connector can accept"
};
Text text_description_4 {
{ 68, 9 * 16, 13 * 8, 16 },
"a DC voltage!"
};
OptionsField options_bias {
{ 100, 12 * 16 },
5,
{
{ "5 seconds ", 0 },
{ "15 seconds", 1 },
{ "1 minute ", 2 },
{ "5 minutes ", 3 },
{ "10 minutes", 4 }
{ " Off ", 0 },
{ " On ", 1 },
}
};
Button button_ok {
{ 4 * 8, 272, 64, 24 },
"Ok"
};
};
class SetPlayDeadView : public View {
public:
SetPlayDeadView(NavigationView& nav);
void focus() override;
private:
bool entermode = false;
uint32_t sequence = 0;
uint8_t keycount, key_code;
char sequence_txt[11];
Text text_sequence {
{ 64, 32, 14 * 8, 16 },
"Enter sequence",
};
Button button_enter {
{ 16, 192, 96, 24 },
"Enter"
};
Button button_cancel {
{ 128, 192, 96, 24 },
"Cancel"
};
};
class ModInfoView : public View {
public:
ModInfoView(NavigationView& nav);
void focus() override;
void on_show() override;
private:
void update_infos(uint8_t modn);
typedef struct moduleinfo_t{
char filename[9];
uint16_t version;
uint32_t size;
char md5[16];
char name[16];
char description[214];
} moduleinfo_t;
moduleinfo_t module_list[8]; // 8 max for now
Text text_modcount {
{ 2 * 8, 1 * 16, 18 * 8, 16 },
"Searching..."
};
OptionsField option_modules {
{ 2 * 8, 2 * 16 },
24,
{ { "-", 0 }
}
};
Text text_name {
{ 2 * 8, 4 * 16, 5 * 8, 16 },
"Name:"
};
Text text_namestr {
{ 8 * 8, 4 * 16, 16 * 8, 16 },
"..."
};
Text text_size {
{ 2 * 8, 5 * 16, 5 * 8, 16 },
"Size:"
};
Text text_sizestr {
{ 8 * 8, 5 * 16, 16 * 8, 16 },
"..."
};
Text text_md5 {
{ 2 * 8, 6 * 16, 4 * 8, 16 },
"MD5:"
};
Text text_md5_a {
{ 7 * 8, 6 * 16, 16 * 8, 16 },
"..."
};
Text text_md5_b {
{ 7 * 8, 7 * 16, 16 * 8, 16 },
"..."
};
Button button_ok {
{ 4 * 8, 272, 64, 24 },
"Ok"
Button button_done {
{ 72, 15 * 16, 96, 24 },
"Done"
};
};

View File

@ -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 */

View File

@ -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 */

View File

@ -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 \

View File

@ -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() {

View 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);
}
);
}

View 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__*/

View 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);
}

View File

@ -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__*/

View File

@ -20,7 +20,6 @@
*/
#include "baseband_dma.hpp"
#include "portapack_shared_memory.hpp"
#include <cstdint>
#include <cstddef>
@ -35,8 +34,6 @@ using namespace lpc43xx;
namespace baseband {
namespace dma {
int quitt = 0;
constexpr uint32_t gpdma_ahb_master_sgpio = 0;
constexpr uint32_t gpdma_ahb_master_memory = 1;
@ -102,17 +99,12 @@ constexpr size_t msg_count = transfers_per_buffer - 1;
static std::array<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 { };
}
}

View File

@ -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);
}

View File

@ -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__*/

View 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;
}

View File

@ -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__*/

View 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();
}
}

View 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__*/

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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__*/

View File

@ -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)++;

View File

@ -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 */

View File

@ -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 */

View File

@ -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_;

View File

@ -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 */

View File

@ -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__*/

View 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);
}

View File

@ -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__*/

View File

@ -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__*/

View File

@ -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;
}

View File

@ -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 };
};

View File

@ -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);
}

View File

@ -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__*/

View File

@ -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();
const buffer_c8_t buffer {
buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate
};
// TODO: Place correct sampling rate into buffer returned here:
const auto buffer_tmp = baseband::dma::wait_for_rx_buffer();
const buffer_c8_t buffer {
buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate
};
if( baseband_processor ) {
baseband_processor->execute(buffer);
}
stats.process(buffer,
[](const BasebandStatistics statistics) {
const BasebandStatisticsMessage message { statistics };
shared_memory.application_queue.push(message);
}
);
} else {
const auto buffer_tmp = baseband::dma::wait_for_rx_buffer();
const buffer_c8_t buffer {
buffer_tmp.p, buffer_tmp.count, baseband_configuration.sampling_rate
};
if( baseband_processor ) {
baseband_processor->execute(buffer);
}
stats.process(buffer,
[](const BasebandStatistics statistics) {
const BasebandStatisticsMessage message { statistics };
shared_memory.application_queue.push(message);
}
);
if( baseband_processor ) {
baseband_processor->execute(buffer);
}
stats.process(buffer,
[](const BasebandStatistics statistics) {
const BasebandStatisticsMessage message { statistics };
shared_memory.application_queue.push(message);
}
);
}
}
};
@ -346,25 +292,8 @@ private:
}
};
const auto baseband_buffer =
new std::array<baseband::sample_t, 8192>();
static constexpr auto direction = baseband::Direction::Receive;
char ram_loop[32];
typedef int (*fn_ptr)(void);
fn_ptr loop_ptr;
void ram_loop_fn(void) {
while(1) {}
}
void wait_for_switch(void) {
memcpy(&ram_loop[0], reinterpret_cast<char*>(&ram_loop_fn), 32);
loop_ptr = reinterpret_cast<fn_ptr>(&ram_loop[0]);
ReadyForSwitchMessage message;
shared_memory.application_queue.push(message);
(*loop_ptr)();
}
int main(void) {
init();
@ -373,18 +302,6 @@ int main(void) {
EventDispatcher event_dispatcher;
auto& message_handlers = event_dispatcher.message_handlers();
message_handlers.register_handler(Message::ID::ModuleID,
[&message_handlers](Message* p) {
ModuleIDMessage reply;
auto message = static_cast<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) {
@ -403,47 +320,29 @@ int main(void) {
delete old_p;
switch(message->configuration.mode) {
case RX_NBAM_AUDIO:
direction = baseband::Direction::Receive;
case 0:
baseband_thread.baseband_processor = new NarrowbandAMAudio();
break;
case RX_NBFM_AUDIO:
direction = baseband::Direction::Receive;
case 1:
baseband_thread.baseband_processor = new NarrowbandFMAudio();
break;
case RX_WBFM_AUDIO:
case 2:
baseband_thread.baseband_processor = new WidebandFMAudio();
break;
case RX_AIS:
direction = baseband::Direction::Receive;
case 3:
baseband_thread.baseband_processor = new AISProcessor();
break;
case RX_WBSPECTRUM:
direction = baseband::Direction::Receive;
case 4:
baseband_thread.baseband_processor = new WidebandSpectrum();
break;
case RX_TPMS:
direction = baseband::Direction::Receive;
case 5:
baseband_thread.baseband_processor = new TPMSProcessor();
break;
case RX_AFSK:
direction = baseband::Direction::Receive;
baseband_thread.baseband_processor = new AFSKRXProcessor();
break;
case RX_SIGFOX:
direction = baseband::Direction::Receive;
baseband_thread.baseband_processor = new SIGFRXProcessor();
break;
case SWITCH:
wait_for_switch();
default:
break;
@ -454,14 +353,8 @@ int main(void) {
rf::rssi::start();
}
baseband::dma::enable(direction);
rf::rssi::stop();
}
}
baseband::dma::configure(
baseband_buffer->data(),
direction
);
baseband_thread.baseband_configuration = message->configuration;
}
@ -475,26 +368,23 @@ int main(void) {
/* TODO: Ensure DMAs are configured to point at first LLI in chain. */
rf::rssi::dma::allocate(4, 400);
if( direction == baseband::Direction::Receive ) {
rf::rssi::dma::allocate(4, 400);
}
touch::dma::allocate();
touch::dma::enable();
const auto baseband_buffer =
new std::array<baseband::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;
}

View File

@ -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
) {

View File

@ -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 */

View File

@ -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;
}
};

View File

@ -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);
}

View File

@ -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__*/

View File

@ -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;
}

View File

@ -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(buffer_c8_t buffer) override;
void execute(const buffer_c8_t& buffer) override;
void on_message(const Message* const message) override;
private:
ChannelDecimator decimator;
const fir_taps_real<64>& channel_filter_taps = taps_64_lp_031_070_tfilter;
std::array<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__*/

View 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