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
+16 -9
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 \
+412
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 */
+179
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__*/
+84
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);
}
@@ -19,49 +19,36 @@
* Boston, MA 02110-1301, USA.
*/
#ifndef __BASEBAND_GPIO_H__
#define __BASEBAND_GPIO_H__
#ifndef __ANALOG_AUDIO_APP_H__
#define __ANALOG_AUDIO_APP_H__
#include <cstdint>
#include "receiver_model.hpp"
#include "ui_spectrum.hpp"
#include "baseband.hpp"
#include "hal.h"
namespace baseband {
class SGPIO {
class AnalogAudioModel {
public:
void init() {
}
AnalogAudioModel(ReceiverModel::Mode mode);
void configure(const Direction direction);
private:
void configure_nbfm();
void configure_wfm();
void configure_am();
};
void streaming_enable() {
/* TODO: Any reason not to control from general GPIO facility? */
LPC_SGPIO->GPIO_OUTREG &= ~(1U << 10);
}
namespace ui {
void streaming_disable() {
/* TODO: Any reason not to control from general GPIO facility? */
LPC_SGPIO->GPIO_OUTREG |= (1U << 10);
}
bool streaming_is_enabled() const {
/* TODO: Any reason not to control from general GPIO facility? */
return (LPC_SGPIO->GPIO_OUTREG >> 10) & 1;
class AnalogAudioView : public spectrum::WaterfallWidget {
public:
AnalogAudioView(
ReceiverModel::Mode mode
) : model { mode }
{
}
private:
void disable_all_slice_counters() {
set_slice_counter_enables(0);
}
void set_slice_counter_enables(const uint16_t enable_mask) {
LPC_SGPIO->CTRL_ENABLE = enable_mask;
}
AnalogAudioModel model;
};
}
} /* namespace ui */
#endif/*__BASEBAND_GPIO_H__*/
#endif/*__ANALOG_AUDIO_APP_H__*/
-373
View File
@@ -1,373 +0,0 @@
/*
* 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_sgpio.hpp"
#include "baseband.hpp"
#include "utility.hpp"
namespace baseband {
/*
struct PinConfig {
P_OUT_CFG p_out_cfg;
P_OE_CFG p_oe_cfg { P_OE_CFG::GPIO_OE };
constexpr SGPIOPinConfig(
P_OUT_CFG p_out_cfg
) :
p_out_cfg(p_out_cfg)
{
}
};
static constexpr bool slice_mode_multislice = false;
static constexpr P_OUT_CFG output_multiplexing_mode =
slice_mode_multislice ? P_OUT_CFG::DOUT_DOUTM8C : P_OUT_CFG::DOUT_DOUTM8A;
static constexpr std::array<PinConfig, 16> pin_config { {
[PIN_D0] = { output_multiplexing_mode, SLICE_A },
[PIN_D1] = { output_multiplexing_mode, SLICE_I },
[PIN_D2] = { output_multiplexing_mode, },
[PIN_D3] = { output_multiplexing_mode, },
[PIN_D4] = { output_multiplexing_mode, },
[PIN_D5] = { output_multiplexing_mode, },
[PIN_D6] = { output_multiplexing_mode, },
[PIN_D7] = { output_multiplexing_mode, },
[PIN_CLKIN] = { P_OUT_CFG::DOUT_DOUTM1, },
[PIN_CAPTURE] = { P_OUT_CFG::DOUT_DOUTM1, },
[PIN_DISABLE] = { P_OUT_CFG::GPIO_OUT, },
[PIN_DIRECTION] = { P_OUT_CFG::GPIO_OUT, },
[PIN_INVERT] = { P_OUT_CFG::GPIO_OUT, },
[PIN_DECIM0] = { P_OUT_CFG::GPIO_OUT, },
[PIN_DECIM1] = { P_OUT_CFG::DOUT_DOUTM1, },
[PIN_DECIM2] = { P_OUT_CFG::GPIO_OUT, },
} };
*/
/*
static constexpr std::array<LPC_SGPIO_OUT_MUX_CFG_Type, 16> out_mux_cfg_receive {
{ },
};
static constexpr std::array<LPC_SGPIO_OUT_MUX_CFG_Type, 16> out_mux_cfg_transmit {
{ },
};
*/
enum class P_OUT_CFG : uint8_t {
DOUT_DOUTM1 = 0x0,
DOUT_DOUTM2A = 0x1,
DOUT_DOUTM2B = 0x2,
DOUT_DOUTM2C = 0x3,
GPIO_OUT = 0x4,
DOUT_DOUTM4A = 0x5,
DOUT_DOUTM4B = 0x6,
DOUT_DOUTM4C = 0x7,
CLK_OUT = 0x8,
DOUT_DOUTM8A = 0x9,
DOUT_DOUTM8B = 0xa,
DOUT_DOUTM8C = 0xb,
};
enum class P_OE_CFG : uint8_t {
GPIO_OE = 0x0,
DOUT_OEM1 = 0x4,
DOUT_OEM2 = 0x5,
DOUT_OEM4 = 0x6,
DOUT_OEM8 = 0x7,
};
enum class CONCAT_ORDER : uint8_t {
SELF_LOOP = 0x0,
TWO_SLICES = 0x1,
FOUR_SLICES = 0x2,
EIGHT_SLICES = 0x3,
};
enum class CONCAT_ENABLE : uint8_t {
EXTERNAL_DATA_PIN = 0x0,
CONCATENATE_DATA = 0x1,
};
enum class CLK_CAPTURE_MODE : uint8_t {
RISING_CLOCK_EDGE = 0,
FALLING_CLOCK_EDGE = 1,
};
enum class PARALLEL_MODE : uint8_t {
SHIFT_1_BIT_PER_CLOCK = 0x0,
SHIFT_2_BITS_PER_CLOCK = 0x1,
SHIFT_4_BITS_PER_CLOCK = 0x2,
SHIFT_1_BYTE_PER_CLOCK = 0x3,
};
enum {
PIN_D0 = 0,
PIN_D1 = 1,
PIN_D2 = 2,
PIN_D3 = 3,
PIN_D4 = 4,
PIN_D5 = 5,
PIN_D6 = 6,
PIN_D7 = 7,
PIN_CLKIN = 8,
PIN_CAPTURE = 9,
PIN_DISABLE = 10,
PIN_DIRECTION = 11,
PIN_INVERT = 12,
PIN_DECIM0 = 13,
PIN_DECIM1 = 14,
PIN_DECIM2 = 15,
};
enum class Slice : uint8_t {
A = 0,
B = 1,
C = 2,
D = 3,
E = 4,
F = 5,
G = 6,
H = 7,
I = 8,
J = 9,
K = 10,
L = 11,
M = 12,
N = 13,
O = 14,
P = 15,
};
constexpr bool slice_mode_multislice = false;
constexpr uint8_t pos_count_multi_slice = 0x1f;
constexpr uint8_t pos_count_single_slice = 0x03;
constexpr Slice slice_order[] {
Slice::A,
Slice::I,
Slice::E,
Slice::J,
Slice::C,
Slice::K,
Slice::F,
Slice::L,
Slice::B,
Slice::M,
Slice::G,
Slice::N,
Slice::D,
Slice::O,
Slice::H,
Slice::P,
};
constexpr uint32_t gpio_outreg(const Direction direction) {
return ((direction == Direction::Transmit) ? (1U << 11) : 0U) | (1U << 10);
}
constexpr uint32_t gpio_oenreg(const Direction direction) {
return
(0U << PIN_DECIM2)
| (1U << PIN_DECIM1)
| (0U << PIN_DECIM0)
| (0U << PIN_INVERT)
| (1U << PIN_DIRECTION)
| (1U << PIN_DISABLE)
| (0U << PIN_CAPTURE)
| (0U << PIN_CLKIN)
| ((direction == Direction::Transmit) ? 0xffU : 0x00U)
;
}
constexpr uint32_t out_mux_cfg(const P_OUT_CFG out, const P_OE_CFG oe) {
return
(toUType(out) << 0)
| (toUType(oe) << 4)
;
}
constexpr uint32_t data_sgpio_mux_cfg(
const CONCAT_ENABLE concat_enable,
const CONCAT_ORDER concat_order
) {
return
(1U << 0)
| (0U << 1)
| (0U << 3)
| (3U << 5)
| (1U << 7)
| (0U << 9)
| (toUType(concat_enable) << 11)
| (toUType(concat_order) << 12)
;
}
constexpr uint32_t data_slice_mux_cfg(
const PARALLEL_MODE parallel_mode,
const CLK_CAPTURE_MODE clk_capture_mode
) {
return
(0U << 0)
| (toUType(clk_capture_mode) << 1)
| (1U << 2)
| (0U << 3)
| (0U << 4)
| (toUType(parallel_mode) << 6)
| (0U << 8)
;
}
constexpr uint32_t pos(
const uint32_t pos,
const uint32_t pos_reset
) {
return
(pos << 0)
| (pos_reset << 8)
;
}
constexpr uint32_t data_pos(
const bool multi_slice
) {
return pos(
(multi_slice ? pos_count_multi_slice : pos_count_single_slice),
(multi_slice ? pos_count_multi_slice : pos_count_single_slice)
);
}
constexpr CONCAT_ENABLE data_concat_enable(
const bool input_slice,
const bool single_slice
) {
return (input_slice || single_slice)
? CONCAT_ENABLE::EXTERNAL_DATA_PIN
: CONCAT_ENABLE::CONCATENATE_DATA
;
}
constexpr CONCAT_ORDER data_concat_order(
const bool input_slice,
const bool single_slice
) {
return (input_slice || single_slice)
? CONCAT_ORDER::SELF_LOOP
: CONCAT_ORDER::EIGHT_SLICES
;
}
constexpr CLK_CAPTURE_MODE data_clk_capture_mode(
const Direction direction
) {
return (direction == Direction::Transmit)
? CLK_CAPTURE_MODE::RISING_CLOCK_EDGE
: CLK_CAPTURE_MODE::FALLING_CLOCK_EDGE
;
}
constexpr P_OUT_CFG data_p_out_cfg(
const bool multi_slice
) {
return (multi_slice)
? P_OUT_CFG::DOUT_DOUTM8C
: P_OUT_CFG::DOUT_DOUTM8A
;
}
void SGPIO::configure(const Direction direction) {
disable_all_slice_counters();
LPC_SGPIO->GPIO_OUTREG = gpio_outreg(direction);
LPC_SGPIO->GPIO_OENREG = gpio_oenreg(direction);
LPC_SGPIO->OUT_MUX_CFG[ 8] = out_mux_cfg(P_OUT_CFG::DOUT_DOUTM1, P_OE_CFG::GPIO_OE);
LPC_SGPIO->OUT_MUX_CFG[ 9] = out_mux_cfg(P_OUT_CFG::DOUT_DOUTM1, P_OE_CFG::GPIO_OE);
LPC_SGPIO->OUT_MUX_CFG[10] = out_mux_cfg(P_OUT_CFG::GPIO_OUT, P_OE_CFG::GPIO_OE);
LPC_SGPIO->OUT_MUX_CFG[11] = out_mux_cfg(P_OUT_CFG::GPIO_OUT, P_OE_CFG::GPIO_OE);
LPC_SGPIO->OUT_MUX_CFG[12] = out_mux_cfg(P_OUT_CFG::GPIO_OUT, P_OE_CFG::GPIO_OE);
LPC_SGPIO->OUT_MUX_CFG[13] = out_mux_cfg(P_OUT_CFG::GPIO_OUT, P_OE_CFG::GPIO_OE);
LPC_SGPIO->OUT_MUX_CFG[14] = out_mux_cfg(P_OUT_CFG::DOUT_DOUTM1, P_OE_CFG::GPIO_OE);
LPC_SGPIO->OUT_MUX_CFG[15] = out_mux_cfg(P_OUT_CFG::GPIO_OUT, P_OE_CFG::GPIO_OE);
const auto data_out_mux_cfg = out_mux_cfg(data_p_out_cfg(slice_mode_multislice), P_OE_CFG::GPIO_OE);
for(size_t i=0; i<8; i++) {
LPC_SGPIO->OUT_MUX_CFG[i] = data_out_mux_cfg;
}
const auto slice_gpdma = Slice::H;
const size_t slice_count = slice_mode_multislice ? 8 : 1;
const auto clk_capture_mode = data_clk_capture_mode(direction);
const auto single_slice = !slice_mode_multislice;
uint32_t slice_enable_mask = 0;
for(size_t i=0; i<slice_count; i++) {
const auto slice = slice_order[i];
const auto slice_index = toUType(slice);
const auto input_slice = (i == 0) && (direction != Direction::Transmit);
const auto concat_order = data_concat_order(input_slice, single_slice);
const auto concat_enable = data_concat_enable(input_slice, single_slice);
LPC_SGPIO->SGPIO_MUX_CFG[slice_index] = data_sgpio_mux_cfg(
concat_enable,
concat_order
);
LPC_SGPIO->SLICE_MUX_CFG[slice_index] = data_slice_mux_cfg(
PARALLEL_MODE::SHIFT_1_BYTE_PER_CLOCK,
clk_capture_mode
);
LPC_SGPIO->PRESET[slice_index] = 0;
LPC_SGPIO->COUNT[slice_index] = 0;
LPC_SGPIO->POS[slice_index] = data_pos(slice_mode_multislice);
LPC_SGPIO->REG[slice_index] = 0;
LPC_SGPIO->REG_SS[slice_index] = 0;
slice_enable_mask |= (1U << slice_index);
}
if( !slice_mode_multislice ) {
const auto slice_index = toUType(slice_gpdma);
LPC_SGPIO->SGPIO_MUX_CFG[slice_index] = data_sgpio_mux_cfg(
CONCAT_ENABLE::CONCATENATE_DATA,
CONCAT_ORDER::SELF_LOOP
);
LPC_SGPIO->SLICE_MUX_CFG[slice_index] = data_slice_mux_cfg(
PARALLEL_MODE::SHIFT_1_BIT_PER_CLOCK,
clk_capture_mode
);
LPC_SGPIO->PRESET[slice_index] = 0;
LPC_SGPIO->COUNT[slice_index] = 0;
LPC_SGPIO->POS[slice_index] = pos(0x1f, 0x1f);
LPC_SGPIO->REG[slice_index] = 0x11111111;
LPC_SGPIO->REG_SS[slice_index] = 0x11111111;
slice_enable_mask |= (1 << slice_index);
}
set_slice_counter_enables(slice_enable_mask);
}
} /* namespace baseband */
+175
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 */
+102
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__*/
+244
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();
}
}
+110
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__*/
+71
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);
}
+2 -2
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();
}
+2 -2
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);
+2 -2
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();
+65
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();
}
}
@@ -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__*/
+9 -283
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();
+12 -12
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 {
+11 -12
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);
+2 -1
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
};
+6
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();
+6 -14
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);
}
+3 -2
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 */
+36 -17
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() {
+19 -2
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__*/
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
@@ -19,9 +19,4 @@
* Boston, MA 02110-1301, USA.
*/
#ifndef __IRQ_IPC_H__
#define __IRQ_IPC_H__
void m4txevent_interrupt_enable();
#endif/*__IRQ_IPC_H__*/
#include "recent_entries.hpp"
+242
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__*/
+7 -36
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 */
+19 -17
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) {
+3 -3
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);
+80
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 */
+14 -14
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__*/
+85
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__*/
@@ -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,12 +19,16 @@
* Boston, MA 02110-1301, USA.
*/
#include "event.hpp"
#include "spectrum_analysis_app.hpp"
#include "ch.h"
#include "portapack.hpp"
using namespace portapack;
Thread* thread_event_loop = nullptr;
void events_initialize(Thread* const event_loop_thread) {
thread_event_loop = event_loop_thread;
SpectrumAnalysisModel::SpectrumAnalysisModel() {
receiver_model.set_baseband_configuration({
.mode = 4,
.sampling_rate = 20000000,
.decimation_factor = 1,
});
receiver_model.set_baseband_bandwidth(12000000);
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __SPECTRUM_ANALYSIS_APP_H__
#define __SPECTRUM_ANALYSIS_APP_H__
#include "receiver_model.hpp"
#include "ui_spectrum.hpp"
class SpectrumAnalysisModel {
public:
SpectrumAnalysisModel();
};
namespace ui {
class SpectrumAnalysisView : public spectrum::WaterfallWidget {
public:
private:
SpectrumAnalysisModel model;
};
} /* namespace ui */
#endif/*__SPECTRUM_ANALYSIS_APP_H__*/
+131
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');
}
+41
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__*/
@@ -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;
}
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __TEMPERATURE_LOGGER_H__
#define __TEMPERATURE_LOGGER_H__
#include <cstddef>
#include <cstdint>
#include <array>
#include <vector>
class TemperatureLogger {
public:
using sample_t = uint8_t;
void second_tick();
size_t size() const;
size_t capacity() const;
std::vector<sample_t> history() const;
private:
std::array<sample_t, 128> samples;
static constexpr size_t sample_interval = 5;
size_t sample_phase = 0;
size_t samples_count = 0;
sample_t read_sample();
void push_sample(const sample_t sample);
};
#endif/*__TEMPERATURE_LOGGER_H__*/
+285
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 */
+226
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__*/
+8 -25
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 { };
+16 -10
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,
+2
View File
@@ -26,6 +26,8 @@
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "message.hpp"
#include <cstdint>
namespace ui {
@@ -21,12 +21,16 @@
#include "ui_baseband_stats_view.hpp"
#include "event_m0.hpp"
#include <string>
#include <algorithm>
#include "hackrf_hal.hpp"
using namespace hackrf::one;
#include "string_format.hpp"
namespace ui {
/* BasebandStatsView *****************************************************/
@@ -38,7 +42,7 @@ BasebandStatsView::BasebandStatsView() {
}
void BasebandStatsView::on_show() {
context().message_map().register_handler(Message::ID::BasebandStatistics,
EventDispatcher::message_map().register_handler(Message::ID::BasebandStatistics,
[this](const Message* const p) {
this->on_statistics_update(static_cast<const BasebandStatisticsMessage*>(p)->statistics);
}
@@ -46,7 +50,7 @@ void BasebandStatsView::on_show() {
}
void BasebandStatsView::on_hide() {
context().message_map().unregister_handler(Message::ID::BasebandStatistics);
EventDispatcher::message_map().unregister_handler(Message::ID::BasebandStatistics);
}
+14 -9
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,
+2
View File
@@ -26,6 +26,8 @@
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "message.hpp"
#include <cstdint>
namespace ui {
+13 -9
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;
}
}
+168 -123
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(); };
}
+101 -35
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"
};
};
+105 -103
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) {
+50 -32
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);
+37 -249
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 */
+32 -94
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 */
+23 -17
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,
+2
View File
@@ -26,6 +26,8 @@
#include "ui_widget.hpp"
#include "ui_painter.hpp"
#include "message.hpp"
#include <cstdint>
namespace ui {
@@ -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 */
@@ -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__*/
+31 -17
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 }); } },
+35 -116
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"
};
};
+274 -1
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 */
+26 -231
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 */