mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-12-05 23:12:13 +00:00
Merge remote-tracking branch 'upstream/master'
Conflicts: firmware/Makefile firmware/application/Makefile firmware/application/event_m0.cpp firmware/application/ui_setup.cpp firmware/application/ui_setup.hpp firmware/baseband/baseband_thread.cpp firmware/baseband/baseband_thread.hpp firmware/bootstrap/CMakeLists.txt firmware/common/message.hpp firmware/common/portapack_shared_memory.hpp hardware/.gitignore
This commit is contained in:
334
firmware/application/CMakeLists.txt
Normal file
334
firmware/application/CMakeLists.txt
Normal file
@@ -0,0 +1,334 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
# Build global options
|
||||
# NOTE: Can be overridden externally.
|
||||
#
|
||||
|
||||
enable_language(C CXX ASM)
|
||||
|
||||
project(application)
|
||||
|
||||
# Compiler options here.
|
||||
set(USE_OPT "-Os --specs=nano.specs")
|
||||
|
||||
# C specific options here (added to USE_OPT).
|
||||
set(USE_COPT "-std=gnu99")
|
||||
|
||||
# C++ specific options here (added to USE_OPT).
|
||||
set(USE_CPPOPT "-std=c++11 -fno-rtti -fno-exceptions")
|
||||
|
||||
# Enable this if you want the linker to remove unused code and data
|
||||
set(USE_LINK_GC yes)
|
||||
|
||||
# Linker extra options here.
|
||||
set(USE_LDOPT)
|
||||
|
||||
# Enable this if you want link time optimizations (LTO)
|
||||
set(USE_LTO no)
|
||||
|
||||
# If enabled, this option allows to compile the application in THUMB mode.
|
||||
set(USE_THUMB yes)
|
||||
|
||||
# Enable this if you want to see the full log while compiling.
|
||||
set(USE_VERBOSE_COMPILE no)
|
||||
|
||||
#
|
||||
# Build global options
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Architecture or project specific options
|
||||
#
|
||||
|
||||
# Enables the use of FPU on Cortex-M4 (no, softfp, hard).
|
||||
set(USE_FPU no)
|
||||
|
||||
#
|
||||
# Architecture or project specific options
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Project, sources and paths
|
||||
#
|
||||
|
||||
set(CPLD_SVF_PATH ${HARDWARE_PATH}/portapack_h1/cpld/output_files/portapack_h1_cpld.svf)
|
||||
set(CPLD_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/portapack_cpld_data.cpp)
|
||||
|
||||
set(HACKRF_CPLD_DATA_HPP ${CMAKE_CURRENT_BINARY_DIR}/hackrf_cpld_data.hpp)
|
||||
set(HACKRF_CPLD_DATA_CPP ${CMAKE_CURRENT_BINARY_DIR}/hackrf_cpld_data.cpp)
|
||||
|
||||
# Imported source files and paths
|
||||
include(${CHIBIOS_PORTAPACK}/boards/GSG_HACKRF_ONE/board.cmake)
|
||||
include(${CHIBIOS_PORTAPACK}/os/hal/platforms/LPC43xx_M0/platform.cmake)
|
||||
include(${CHIBIOS}/os/hal/hal.cmake)
|
||||
include(${CHIBIOS_PORTAPACK}/os/ports/GCC/ARMCMx/LPC43xx_M0/port.cmake)
|
||||
include(${CHIBIOS}/os/kernel/kernel.cmake)
|
||||
include(${CHIBIOS_PORTAPACK}/os/various/fatfs_bindings/fatfs.cmake)
|
||||
include(${CHIBIOS}/test/test.cmake)
|
||||
|
||||
# Define linker script file here
|
||||
set(LDSCRIPT ${PORTLD}/LPC43xx_M0.ld)
|
||||
|
||||
# C sources that can be compiled in ARM or THUMB mode depending on the global
|
||||
# setting.
|
||||
set(CSRC
|
||||
${PORTSRC}
|
||||
${KERNSRC}
|
||||
${TESTSRC}
|
||||
${HALSRC}
|
||||
${PLATFORMSRC}
|
||||
${BOARDSRC}
|
||||
${FATFSSRC}
|
||||
)
|
||||
|
||||
# C++ sources that can be compiled in ARM or THUMB mode depending on the global
|
||||
# setting.
|
||||
set(CPPSRC
|
||||
main.cpp
|
||||
irq_lcd_frame.cpp
|
||||
irq_controls.cpp
|
||||
irq_rtc.cpp
|
||||
${COMMON}/event.cpp
|
||||
event_m0.cpp
|
||||
${COMMON}/message_queue.cpp
|
||||
${COMMON}/hackrf_hal.cpp
|
||||
portapack.cpp
|
||||
${COMMON}/portapack_shared_memory.cpp
|
||||
baseband_api.cpp
|
||||
${COMMON}/portapack_persistent_memory.cpp
|
||||
${COMMON}/portapack_io.cpp
|
||||
${COMMON}/i2c_pp.cpp
|
||||
spi_pp.cpp
|
||||
clock_manager.cpp
|
||||
si5351.cpp
|
||||
${COMMON}/wm8731.cpp
|
||||
radio.cpp
|
||||
baseband_cpld.cpp
|
||||
tuning.cpp
|
||||
rf_path.cpp
|
||||
rffc507x.cpp
|
||||
rffc507x_spi.cpp
|
||||
max2837.cpp
|
||||
max5864.cpp
|
||||
debounce.cpp
|
||||
touch.cpp
|
||||
touch_adc.cpp
|
||||
encoder.cpp
|
||||
audio.cpp
|
||||
${COMMON}/lcd_ili9341.cpp
|
||||
${COMMON}/ui.cpp
|
||||
${COMMON}/ui_text.cpp
|
||||
${COMMON}/ui_widget.cpp
|
||||
${COMMON}/ui_painter.cpp
|
||||
${COMMON}/ui_focus.cpp
|
||||
ui_navigation.cpp
|
||||
ui_menu.cpp
|
||||
ui_rssi.cpp
|
||||
ui_channel.cpp
|
||||
ui_audio.cpp
|
||||
ui_font_fixed_8x16.cpp
|
||||
ui_setup.cpp
|
||||
ui_debug.cpp
|
||||
ui_baseband_stats_view.cpp
|
||||
ui_sd_card_status_view.cpp
|
||||
ui_sd_card_debug.cpp
|
||||
ui_console.cpp
|
||||
ui_receiver.cpp
|
||||
ui_record_view.cpp
|
||||
ui_spectrum.cpp
|
||||
recent_entries.cpp
|
||||
receiver_model.cpp
|
||||
spectrum_color_lut.cpp
|
||||
analog_audio_app.cpp
|
||||
${COMMON}/ais_baseband.cpp
|
||||
${COMMON}/ais_packet.cpp
|
||||
ais_app.cpp
|
||||
tpms_app.cpp
|
||||
${COMMON}/tpms_packet.cpp
|
||||
ert_app.cpp
|
||||
${COMMON}/ert_packet.cpp
|
||||
capture_app.cpp
|
||||
sd_card.cpp
|
||||
time.cpp
|
||||
file.cpp
|
||||
log_file.cpp
|
||||
${COMMON}/png_writer.cpp
|
||||
capture_thread.cpp
|
||||
${COMMON}/manchester.cpp
|
||||
string_format.cpp
|
||||
temperature_logger.cpp
|
||||
${COMMON}/utility.cpp
|
||||
${COMMON}/chibios_cpp.cpp
|
||||
${COMMON}/debug.cpp
|
||||
${COMMON}/gcc.cpp
|
||||
${COMMON}/lfsr_random.cpp
|
||||
core_control.cpp
|
||||
${COMMON}/cpld_max5.cpp
|
||||
${COMMON}/cpld_xilinx.cpp
|
||||
${COMMON}/jtag.cpp
|
||||
${COMMON}/jtag_tap.cpp
|
||||
cpld_update.cpp
|
||||
${CPLD_DATA_CPP}
|
||||
${HACKRF_CPLD_DATA_CPP}
|
||||
)
|
||||
|
||||
# C sources to be compiled in ARM mode regardless of the global setting.
|
||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||
# option that results in lower performance and larger code size.
|
||||
set(ACSRC)
|
||||
|
||||
# C++ sources to be compiled in ARM mode regardless of the global setting.
|
||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||
# option that results in lower performance and larger code size.
|
||||
set(ACPPSRC)
|
||||
|
||||
# C sources to be compiled in THUMB mode regardless of the global setting.
|
||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||
# option that results in lower performance and larger code size.
|
||||
set(TCSRC)
|
||||
|
||||
# C sources to be compiled in THUMB mode regardless of the global setting.
|
||||
# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
|
||||
# option that results in lower performance and larger code size.
|
||||
set(TCPPSRC)
|
||||
|
||||
# List ASM source files here
|
||||
set(ASMSRC ${PORTASM})
|
||||
|
||||
set(INCDIR ${CMAKE_CURRENT_BINARY_DIR} ${COMMON} ${PORTINC} ${KERNINC} ${TESTINC}
|
||||
${HALINC} ${PLATFORMINC} ${BOARDINC}
|
||||
${FATFSINC}
|
||||
${CHIBIOS}/os/various
|
||||
)
|
||||
|
||||
#
|
||||
# Project, sources and paths
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Compiler settings
|
||||
#
|
||||
|
||||
# TODO: Entertain using MCU=cortex-m0.small-multiply for LPC43xx M0 core.
|
||||
# However, on GCC-ARM-Embedded 4.9 2015q2, it seems to produce non-functional
|
||||
# binaries.
|
||||
set(MCU cortex-m0)
|
||||
|
||||
# ARM-specific options here
|
||||
set(AOPT)
|
||||
|
||||
# THUMB-specific options here
|
||||
set(TOPT "-mthumb -DTHUMB")
|
||||
|
||||
# Define C warning options here
|
||||
set(CWARN "-Wall -Wextra -Wstrict-prototypes")
|
||||
|
||||
# Define C++ warning options here
|
||||
set(CPPWARN "-Wall -Wextra")
|
||||
|
||||
#
|
||||
# Compiler settings
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Start of default section
|
||||
#
|
||||
|
||||
# List all default C defines here, like -D_DEBUG=1
|
||||
# TODO: Switch -DCRT0_INIT_DATA depending on load from RAM or SPIFI?
|
||||
# NOTE: _RANDOM_TCC to kill a GCC 4.9.3 error with std::max argument types
|
||||
set(DDEFS -DLPC43XX -DLPC43XX_M0 -D__NEWLIB__ -DHACKRF_ONE -DTOOLCHAIN_GCC -DTOOLCHAIN_GCC_ARM -D_RANDOM_TCC=0 -DGIT_REVISION=\"${GIT_REVISION}\")
|
||||
|
||||
# List all default ASM defines here, like -D_DEBUG=1
|
||||
set(DADEFS)
|
||||
|
||||
# List all default directories to look for include files here
|
||||
set(DINCDIR)
|
||||
|
||||
# List the default directory to look for the libraries here
|
||||
set(DLIBDIR)
|
||||
|
||||
# List all default libraries here
|
||||
set(DLIBS)
|
||||
|
||||
#
|
||||
# End of default section
|
||||
##############################################################################
|
||||
|
||||
##############################################################################
|
||||
# Start of user section
|
||||
#
|
||||
|
||||
# List all user C define here, like -D_DEBUG=1
|
||||
set(UDEFS)
|
||||
|
||||
# Define ASM defines here
|
||||
set(UADEFS)
|
||||
|
||||
# List all user directories here
|
||||
set(UINCDIR)
|
||||
|
||||
# List the user directory to look for the libraries here
|
||||
set(ULIBDIR)
|
||||
|
||||
# List all user libraries here
|
||||
set(ULIBS)
|
||||
|
||||
#
|
||||
# End of user defines
|
||||
##############################################################################
|
||||
|
||||
set(RULESPATH ${CHIBIOS}/os/ports/GCC/ARMCMx)
|
||||
include(${RULESPATH}/rules.cmake)
|
||||
|
||||
##############################################################################
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${CPLD_DATA_CPP}
|
||||
COMMAND ${EXTRACT_CPLD_DATA} ${CPLD_SVF_PATH} >${CPLD_DATA_CPP}
|
||||
DEPENDS ${EXTRACT_CPLD_DATA} ${CPLD_SVF_PATH}
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${HACKRF_CPLD_DATA_HPP} ${HACKRF_CPLD_DATA_CPP}
|
||||
COMMAND ${EXTRACT_SVF_DATA_XC2C64A} ${HACKRF_CPLD_SVF_PATH} hackrf::one::cpld::verify_blocks ${HACKRF_CPLD_DATA_HPP} ${HACKRF_CPLD_DATA_CPP}
|
||||
DEPENDS ${EXTRACT_SVF_DATA_XC2C64A} ${HACKRF_CPLD_SVF_PATH}
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME}.elf ${CSRC} ${CPPSRC} ${ASMSRC})
|
||||
set_target_properties(${PROJECT_NAME}.elf PROPERTIES LINK_DEPENDS ${LDSCRIPT})
|
||||
add_definitions(${DEFS})
|
||||
include_directories(. ${INCDIR})
|
||||
link_directories(${LLIBDIR})
|
||||
target_link_libraries(${PROJECT_NAME}.elf ${LIBS})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${PROJECT_NAME}.bin
|
||||
COMMAND ${CMAKE_OBJCOPY} -O binary ${PROJECT_NAME}.elf ${PROJECT_NAME}.bin
|
||||
DEPENDS ${PROJECT_NAME}.elf
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
${PROJECT_NAME}
|
||||
DEPENDS ${PROJECT_NAME}.bin
|
||||
)
|
||||
@@ -21,8 +21,6 @@
|
||||
|
||||
#include "ais_app.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
@@ -128,25 +126,17 @@ static std::string true_heading(const TrueHeading value) {
|
||||
} /* namespace format */
|
||||
} /* namespace ais */
|
||||
|
||||
AISLogger::AISLogger(
|
||||
const std::string& file_path
|
||||
) : log_file { file_path }
|
||||
{
|
||||
}
|
||||
|
||||
void AISLogger::on_packet(const ais::Packet& packet) {
|
||||
// TODO: Unstuff here, not in baseband!
|
||||
if( log_file.is_open() ) {
|
||||
std::string entry;
|
||||
entry.reserve((packet.length() + 3) / 4);
|
||||
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);
|
||||
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) {
|
||||
@@ -296,6 +286,8 @@ void AISRecentEntryDetailView::set_entry(const AISRecentEntry& entry) {
|
||||
}
|
||||
|
||||
AISAppView::AISAppView(NavigationView&) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_ais);
|
||||
|
||||
add_children({ {
|
||||
&label_channel,
|
||||
&options_channel,
|
||||
@@ -305,16 +297,6 @@ AISAppView::AISAppView(NavigationView&) {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
target_frequency_ = initial_target_frequency;
|
||||
|
||||
radio::enable({
|
||||
@@ -326,12 +308,6 @@ AISAppView::AISAppView(NavigationView&) {
|
||||
1,
|
||||
});
|
||||
|
||||
baseband::start({
|
||||
.mode = 3,
|
||||
.sampling_rate = sampling_rate,
|
||||
.decimation_factor = 1,
|
||||
});
|
||||
|
||||
options_channel.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
this->on_frequency_changed(v);
|
||||
};
|
||||
@@ -344,14 +320,16 @@ AISAppView::AISAppView(NavigationView&) {
|
||||
this->on_show_list();
|
||||
};
|
||||
|
||||
logger = std::make_unique<AISLogger>("ais.txt");
|
||||
logger = std::make_unique<AISLogger>();
|
||||
if( logger ) {
|
||||
logger->append("ais.txt");
|
||||
}
|
||||
}
|
||||
|
||||
AISAppView::~AISAppView() {
|
||||
baseband::stop();
|
||||
radio::disable();
|
||||
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::AISPacket);
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void AISAppView::focus() {
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "log_file.hpp"
|
||||
|
||||
#include "ais_packet.hpp"
|
||||
@@ -90,8 +92,10 @@ using AISRecentEntries = RecentEntries<ais::Packet, AISRecentEntry>;
|
||||
|
||||
class AISLogger {
|
||||
public:
|
||||
AISLogger(const std::string& file_path);
|
||||
|
||||
Optional<File::Error> append(const std::string& filename) {
|
||||
return log_file.append(filename);
|
||||
}
|
||||
|
||||
void on_packet(const ais::Packet& packet);
|
||||
|
||||
private:
|
||||
@@ -173,6 +177,17 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_packet {
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t target_frequency_ = initial_target_frequency;
|
||||
|
||||
void on_packet(const ais::Packet& packet);
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#include "analog_audio_app.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
using namespace portapack;
|
||||
@@ -108,19 +110,10 @@ AnalogAudioView::AnalogAudioView(
|
||||
this->on_show_options_frequency();
|
||||
};
|
||||
|
||||
field_lna.set_value(receiver_model.lna());
|
||||
field_lna.on_change = [this](int32_t v) {
|
||||
this->on_lna_changed(v);
|
||||
};
|
||||
|
||||
field_lna.on_show_options = [this]() {
|
||||
this->on_show_options_rf_gain();
|
||||
};
|
||||
|
||||
field_vga.set_value(receiver_model.vga());
|
||||
field_vga.on_change = [this](int32_t v_db) {
|
||||
this->on_vga_changed(v_db);
|
||||
};
|
||||
field_vga.on_show_options = [this]() {
|
||||
this->on_show_options_rf_gain();
|
||||
};
|
||||
@@ -139,6 +132,10 @@ AnalogAudioView::AnalogAudioView(
|
||||
this->on_headphone_volume_changed(v);
|
||||
};
|
||||
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
|
||||
audio::output::start();
|
||||
|
||||
update_modulation(static_cast<ReceiverModel::Mode>(modulation));
|
||||
@@ -150,6 +147,8 @@ AnalogAudioView::~AnalogAudioView() {
|
||||
audio::output::stop();
|
||||
|
||||
receiver_model.disable();
|
||||
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_hide() {
|
||||
@@ -178,18 +177,6 @@ void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
|
||||
receiver_model.set_baseband_bandwidth(bandwidth_hz);
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_rf_amp_changed(bool v) {
|
||||
receiver_model.set_rf_amp(v);
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_lna_changed(int32_t v_db) {
|
||||
receiver_model.set_lna(v_db);
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_vga_changed(int32_t v_db) {
|
||||
receiver_model.set_vga(v_db);
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_modulation_changed(const ReceiverModel::Mode modulation) {
|
||||
// TODO: Terrible kludge because widget system doesn't notify Waterfall that
|
||||
// it's being shown or hidden.
|
||||
@@ -215,8 +202,11 @@ void AnalogAudioView::set_options_widget(std::unique_ptr<Widget> new_widget) {
|
||||
|
||||
if( new_widget ) {
|
||||
options_widget = std::move(new_widget);
|
||||
add_child(options_widget.get());
|
||||
} else {
|
||||
// TODO: Lame hack to hide options view due to my bad paint/damage algorithm.
|
||||
options_widget = std::make_unique<Rectangle>(options_view_rect, style_options_group.background);
|
||||
}
|
||||
add_child(options_widget.get());
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_show_options_frequency() {
|
||||
@@ -238,11 +228,6 @@ void AnalogAudioView::on_show_options_frequency() {
|
||||
void AnalogAudioView::on_show_options_rf_gain() {
|
||||
auto widget = std::make_unique<RadioGainOptionsView>(options_view_rect, &style_options_group);
|
||||
|
||||
widget->set_rf_amp(receiver_model.rf_amp());
|
||||
widget->on_change_rf_amp = [this](bool enable) {
|
||||
this->on_rf_amp_changed(enable);
|
||||
};
|
||||
|
||||
set_options_widget(std::move(widget));
|
||||
field_lna.set_style(&style_options_group);
|
||||
}
|
||||
@@ -286,6 +271,20 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
|
||||
audio::output::mute();
|
||||
record_view.stop();
|
||||
|
||||
baseband::shutdown();
|
||||
|
||||
portapack::spi_flash::image_tag_t image_tag;
|
||||
switch(modulation) {
|
||||
case ReceiverModel::Mode::AMAudio: image_tag = portapack::spi_flash::image_tag_am_audio; break;
|
||||
case ReceiverModel::Mode::NarrowbandFMAudio: image_tag = portapack::spi_flash::image_tag_nfm_audio; break;
|
||||
case ReceiverModel::Mode::WidebandFMAudio: image_tag = portapack::spi_flash::image_tag_wfm_audio; break;
|
||||
case ReceiverModel::Mode::SpectrumAnalysis: image_tag = portapack::spi_flash::image_tag_wideband_spectrum; break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
baseband::run_image(image_tag);
|
||||
|
||||
const auto is_wideband_spectrum_mode = (modulation == ReceiverModel::Mode::SpectrumAnalysis);
|
||||
receiver_model.set_baseband_configuration({
|
||||
.mode = toUType(modulation),
|
||||
|
||||
@@ -143,16 +143,13 @@ private:
|
||||
|
||||
RecordView record_view {
|
||||
{ 0 * 8, 2 * 16, 30 * 8, 1 * 16 },
|
||||
"AUD_????", RecordView::FileType::WAV, 12, 2,
|
||||
"AUD_????", RecordView::FileType::WAV, 4096, 4
|
||||
};
|
||||
|
||||
spectrum::WaterfallWidget waterfall;
|
||||
|
||||
void on_tuning_frequency_changed(rf::Frequency f);
|
||||
void on_baseband_bandwidth_changed(uint32_t bandwidth_hz);
|
||||
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(const ReceiverModel::Mode modulation);
|
||||
void on_show_options_frequency();
|
||||
void on_show_options_rf_gain();
|
||||
|
||||
@@ -26,8 +26,18 @@
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "core_control.hpp"
|
||||
|
||||
namespace baseband {
|
||||
|
||||
static void send_message(const Message* const message) {
|
||||
// If message is only sent by this function via one thread, no need to check if
|
||||
// another message is present before setting new message.
|
||||
shared_memory.baseband_message = message;
|
||||
creg::m0apptxevent::assert();
|
||||
while(shared_memory.baseband_message);
|
||||
}
|
||||
|
||||
void AMConfig::apply() const {
|
||||
const AMConfigureMessage message {
|
||||
taps_6k0_decim_0,
|
||||
@@ -37,7 +47,7 @@ void AMConfig::apply() const {
|
||||
modulation,
|
||||
audio_12k_hpf_300hz_config
|
||||
};
|
||||
shared_memory.baseband_queue.push(message);
|
||||
send_message(&message);
|
||||
audio::set_rate(audio::Rate::Hz_12000);
|
||||
}
|
||||
|
||||
@@ -51,7 +61,7 @@ void NBFMConfig::apply() const {
|
||||
audio_24k_hpf_300hz_config,
|
||||
audio_24k_deemph_300_6_config
|
||||
};
|
||||
shared_memory.baseband_queue.push(message);
|
||||
send_message(&message);
|
||||
audio::set_rate(audio::Rate::Hz_24000);
|
||||
}
|
||||
|
||||
@@ -64,26 +74,38 @@ void WFMConfig::apply() const {
|
||||
audio_48k_hpf_30hz_config,
|
||||
audio_48k_deemph_2122_6_config
|
||||
};
|
||||
shared_memory.baseband_queue.push(message);
|
||||
send_message(&message);
|
||||
audio::set_rate(audio::Rate::Hz_48000);
|
||||
}
|
||||
|
||||
void start(BasebandConfiguration configuration) {
|
||||
BasebandConfigurationMessage message { configuration };
|
||||
shared_memory.baseband_queue.push(message);
|
||||
}
|
||||
static bool baseband_image_running = false;
|
||||
|
||||
void stop() {
|
||||
shared_memory.baseband_queue.push_and_wait(
|
||||
BasebandConfigurationMessage {
|
||||
.configuration = { },
|
||||
}
|
||||
);
|
||||
void run_image(const portapack::spi_flash::image_tag_t image_tag) {
|
||||
if( baseband_image_running ) {
|
||||
chDbgPanic("BBRunning");
|
||||
}
|
||||
|
||||
creg::m4txevent::clear();
|
||||
|
||||
m4_init(image_tag, portapack::memory::map::m4_code);
|
||||
baseband_image_running = true;
|
||||
|
||||
creg::m4txevent::enable();
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
ShutdownMessage shutdown_message;
|
||||
shared_memory.baseband_queue.push(shutdown_message);
|
||||
if( !baseband_image_running ) {
|
||||
return;
|
||||
}
|
||||
|
||||
creg::m4txevent::disable();
|
||||
|
||||
ShutdownMessage message;
|
||||
send_message(&message);
|
||||
|
||||
shared_memory.application_queue.reset();
|
||||
|
||||
baseband_image_running = false;
|
||||
}
|
||||
|
||||
void spectrum_streaming_start(size_t decimation_factor) {
|
||||
@@ -96,19 +118,27 @@ void spectrum_streaming_start(size_t decimation_factor) {
|
||||
}
|
||||
|
||||
void spectrum_streaming_start() {
|
||||
shared_memory.baseband_queue.push_and_wait(
|
||||
SpectrumStreamingConfigMessage {
|
||||
SpectrumStreamingConfigMessage::Mode::Running
|
||||
}
|
||||
);
|
||||
SpectrumStreamingConfigMessage message {
|
||||
SpectrumStreamingConfigMessage::Mode::Running
|
||||
};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void spectrum_streaming_stop() {
|
||||
shared_memory.baseband_queue.push_and_wait(
|
||||
SpectrumStreamingConfigMessage {
|
||||
SpectrumStreamingConfigMessage::Mode::Stopped
|
||||
}
|
||||
);
|
||||
SpectrumStreamingConfigMessage message {
|
||||
SpectrumStreamingConfigMessage::Mode::Stopped
|
||||
};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void capture_start(CaptureConfig* const config) {
|
||||
CaptureConfigMessage message { config };
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void capture_stop() {
|
||||
CaptureConfigMessage message { nullptr };
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
} /* namespace baseband */
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
#include "dsp_fir_taps.hpp"
|
||||
|
||||
#include "spi_image.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace baseband {
|
||||
@@ -50,15 +52,16 @@ struct WFMConfig {
|
||||
void apply() const;
|
||||
};
|
||||
|
||||
void start(BasebandConfiguration configuration);
|
||||
void stop();
|
||||
|
||||
void run_image(const portapack::spi_flash::image_tag_t image_tag);
|
||||
void shutdown();
|
||||
|
||||
void spectrum_streaming_start(size_t decimation_factor);
|
||||
void spectrum_streaming_start();
|
||||
void spectrum_streaming_stop();
|
||||
|
||||
void capture_start(CaptureConfig* const config);
|
||||
void capture_stop();
|
||||
|
||||
} /* namespace baseband */
|
||||
|
||||
#endif/*__BASEBAND_API_H__*/
|
||||
|
||||
@@ -21,59 +21,72 @@
|
||||
|
||||
#include "capture_app.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui {
|
||||
|
||||
CaptureAppView::CaptureAppView(NavigationView& nav) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
|
||||
add_children({ {
|
||||
&rssi,
|
||||
&channel,
|
||||
&field_frequency,
|
||||
&field_frequency_step,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&record_view,
|
||||
&waterfall,
|
||||
} });
|
||||
|
||||
field_frequency.set_value(receiver_model.tuning_frequency());
|
||||
field_frequency.set_value(target_frequency());
|
||||
field_frequency.set_step(receiver_model.frequency_step());
|
||||
field_frequency.on_change = [this](rf::Frequency f) {
|
||||
this->on_tuning_frequency_changed(f);
|
||||
this->on_target_frequency_changed(f);
|
||||
};
|
||||
field_frequency.on_edit = [this, &nav]() {
|
||||
// TODO: Provide separate modal method/scheme?
|
||||
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
|
||||
auto new_view = nav.push<FrequencyKeypadView>(this->target_frequency());
|
||||
new_view->on_changed = [this](rf::Frequency f) {
|
||||
this->on_tuning_frequency_changed(f);
|
||||
this->on_target_frequency_changed(f);
|
||||
this->field_frequency.set_value(f);
|
||||
};
|
||||
};
|
||||
|
||||
field_lna.set_value(receiver_model.lna());
|
||||
field_lna.on_change = [this](int32_t v) {
|
||||
this->on_lna_changed(v);
|
||||
field_frequency_step.set_by_value(receiver_model.frequency_step());
|
||||
field_frequency_step.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
receiver_model.set_frequency_step(v);
|
||||
this->field_frequency.set_step(v);
|
||||
};
|
||||
|
||||
field_vga.set_value(receiver_model.vga());
|
||||
field_vga.on_change = [this](int32_t v_db) {
|
||||
this->on_vga_changed(v_db);
|
||||
};
|
||||
|
||||
receiver_model.set_baseband_configuration({
|
||||
.mode = toUType(ReceiverModel::Mode::Capture),
|
||||
.sampling_rate = sampling_rate,
|
||||
.decimation_factor = 1,
|
||||
radio::enable({
|
||||
tuning_frequency(),
|
||||
sampling_rate,
|
||||
baseband_bandwidth,
|
||||
rf::Direction::Receive,
|
||||
receiver_model.rf_amp(),
|
||||
static_cast<int8_t>(receiver_model.lna()),
|
||||
static_cast<int8_t>(receiver_model.vga()),
|
||||
1,
|
||||
});
|
||||
receiver_model.set_baseband_bandwidth(baseband_bandwidth);
|
||||
receiver_model.enable();
|
||||
|
||||
record_view.set_sampling_rate(sampling_rate / 8);
|
||||
record_view.on_error = [&nav](std::string message) {
|
||||
nav.display_modal("Error", message);
|
||||
};
|
||||
}
|
||||
|
||||
CaptureAppView::~CaptureAppView() {
|
||||
receiver_model.disable();
|
||||
radio::disable();
|
||||
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void CaptureAppView::on_hide() {
|
||||
@@ -94,16 +107,21 @@ void CaptureAppView::focus() {
|
||||
record_view.focus();
|
||||
}
|
||||
|
||||
void CaptureAppView::on_tuning_frequency_changed(rf::Frequency f) {
|
||||
receiver_model.set_tuning_frequency(f);
|
||||
void CaptureAppView::on_target_frequency_changed(rf::Frequency f) {
|
||||
set_target_frequency(f);
|
||||
}
|
||||
|
||||
void CaptureAppView::on_lna_changed(int32_t v_db) {
|
||||
receiver_model.set_lna(v_db);
|
||||
void CaptureAppView::set_target_frequency(const rf::Frequency new_value) {
|
||||
persistent_memory::set_tuned_frequency(new_value);;
|
||||
radio::set_tuning_frequency(tuning_frequency());
|
||||
}
|
||||
|
||||
void CaptureAppView::on_vga_changed(int32_t v_db) {
|
||||
receiver_model.set_vga(v_db);
|
||||
rf::Frequency CaptureAppView::target_frequency() const {
|
||||
return persistent_memory::tuned_frequency();
|
||||
}
|
||||
|
||||
rf::Frequency CaptureAppView::tuning_frequency() const {
|
||||
return target_frequency() - (sampling_rate / 4);
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -47,38 +47,49 @@ public:
|
||||
std::string title() const override { return "Capture"; };
|
||||
|
||||
private:
|
||||
static constexpr ui::Dim header_height = 3 * 16;
|
||||
static constexpr ui::Dim header_height = 2 * 16;
|
||||
|
||||
static constexpr uint32_t sampling_rate = 4000000;
|
||||
static constexpr uint32_t baseband_bandwidth = 2500000;
|
||||
|
||||
void on_tuning_frequency_changed(rf::Frequency f);
|
||||
void on_lna_changed(int32_t v_db);
|
||||
void on_vga_changed(int32_t v_db);
|
||||
void on_target_frequency_changed(rf::Frequency f);
|
||||
|
||||
rf::Frequency target_frequency() const;
|
||||
void set_target_frequency(const rf::Frequency new_value);
|
||||
|
||||
rf::Frequency tuning_frequency() const;
|
||||
|
||||
RSSI rssi {
|
||||
{ 21 * 8, 0, 6 * 8, 4 },
|
||||
{ 24 * 8, 0, 6 * 8, 4 },
|
||||
};
|
||||
|
||||
Channel channel {
|
||||
{ 21 * 8, 5, 6 * 8, 4 },
|
||||
{ 24 * 8, 5, 6 * 8, 4 },
|
||||
};
|
||||
|
||||
FrequencyField field_frequency {
|
||||
{ 5 * 8, 0 * 16 },
|
||||
{ 0 * 8, 0 * 16 },
|
||||
};
|
||||
|
||||
FrequencyStepView field_frequency_step {
|
||||
{ 10 * 8, 0 * 16 },
|
||||
};
|
||||
|
||||
RFAmpField field_rf_amp {
|
||||
{ 16 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
LNAGainField field_lna {
|
||||
{ 15 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
VGAGainField field_vga {
|
||||
{ 18 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
VGAGainField field_vga {
|
||||
{ 21 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
RecordView record_view {
|
||||
{ 0 * 8, 2 * 16, 30 * 8, 1 * 16 },
|
||||
"BBD_????", RecordView::FileType::RawS16, 14, 1,
|
||||
{ 0 * 8, 1 * 16, 30 * 8, 1 * 16 },
|
||||
"BBD_????", RecordView::FileType::RawS16, 16384, 3
|
||||
};
|
||||
|
||||
spectrum::WaterfallWidget waterfall;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include "capture_thread.hpp"
|
||||
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
// StreamOutput ///////////////////////////////////////////////////////////
|
||||
|
||||
@@ -31,36 +31,43 @@ public:
|
||||
~StreamOutput();
|
||||
|
||||
size_t available() {
|
||||
return fifo->len();
|
||||
return fifo_buffers_full->len();
|
||||
}
|
||||
|
||||
size_t read(void* const data, const size_t length) {
|
||||
return fifo->out(reinterpret_cast<uint8_t*>(data), length);
|
||||
StreamBuffer* get_buffer() {
|
||||
StreamBuffer* p { nullptr };
|
||||
fifo_buffers_full->out(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
static FIFO<uint8_t>* fifo;
|
||||
bool release_buffer(StreamBuffer* const p) {
|
||||
p->empty();
|
||||
return fifo_buffers_empty->in(p);
|
||||
}
|
||||
|
||||
static FIFO<StreamBuffer*>* fifo_buffers_empty;
|
||||
static FIFO<StreamBuffer*>* fifo_buffers_full;
|
||||
|
||||
private:
|
||||
CaptureConfig* const config;
|
||||
};
|
||||
|
||||
FIFO<uint8_t>* StreamOutput::fifo = nullptr;
|
||||
FIFO<StreamBuffer*>* StreamOutput::fifo_buffers_empty = nullptr;
|
||||
FIFO<StreamBuffer*>* StreamOutput::fifo_buffers_full = nullptr;
|
||||
|
||||
StreamOutput::StreamOutput(
|
||||
CaptureConfig* const config
|
||||
) : config { config }
|
||||
{
|
||||
shared_memory.baseband_queue.push_and_wait(
|
||||
CaptureConfigMessage { config }
|
||||
);
|
||||
fifo = config->fifo;
|
||||
baseband::capture_start(config);
|
||||
fifo_buffers_empty = config->fifo_buffers_empty;
|
||||
fifo_buffers_full = config->fifo_buffers_full;
|
||||
}
|
||||
|
||||
StreamOutput::~StreamOutput() {
|
||||
fifo = nullptr;
|
||||
shared_memory.baseband_queue.push_and_wait(
|
||||
CaptureConfigMessage { nullptr }
|
||||
);
|
||||
fifo_buffers_full = nullptr;
|
||||
fifo_buffers_empty = nullptr;
|
||||
baseband::capture_stop();
|
||||
}
|
||||
|
||||
// CaptureThread //////////////////////////////////////////////////////////
|
||||
@@ -69,10 +76,14 @@ Thread* CaptureThread::thread = nullptr;
|
||||
|
||||
CaptureThread::CaptureThread(
|
||||
std::unique_ptr<Writer> writer,
|
||||
size_t write_size_log2,
|
||||
size_t buffer_count_log2
|
||||
) : config { write_size_log2, buffer_count_log2 },
|
||||
writer { std::move(writer) }
|
||||
size_t write_size,
|
||||
size_t buffer_count,
|
||||
std::function<void()> success_callback,
|
||||
std::function<void(File::Error)> error_callback
|
||||
) : config { write_size, buffer_count },
|
||||
writer { std::move(writer) },
|
||||
success_callback { std::move(success_callback) },
|
||||
error_callback { std::move(error_callback) }
|
||||
{
|
||||
// Need significant stack for FATFS
|
||||
thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, CaptureThread::static_fn, this);
|
||||
@@ -81,7 +92,7 @@ CaptureThread::CaptureThread(
|
||||
CaptureThread::~CaptureThread() {
|
||||
if( thread ) {
|
||||
chThdTerminate(thread);
|
||||
chEvtSignal(thread, EVT_MASK_CAPTURE_THREAD);
|
||||
chEvtSignal(thread, event_mask_loop_wake);
|
||||
chThdWait(thread);
|
||||
thread = nullptr;
|
||||
}
|
||||
@@ -90,33 +101,42 @@ CaptureThread::~CaptureThread() {
|
||||
void CaptureThread::check_fifo_isr() {
|
||||
// TODO: Prevent over-signalling by transmitting a set of
|
||||
// flags from the baseband core.
|
||||
const auto fifo = StreamOutput::fifo;
|
||||
const auto fifo = StreamOutput::fifo_buffers_full;
|
||||
if( fifo ) {
|
||||
chEvtSignalI(thread, EVT_MASK_CAPTURE_THREAD);
|
||||
if( !fifo->is_empty() ) {
|
||||
chEvtSignalI(thread, event_mask_loop_wake);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg_t CaptureThread::run() {
|
||||
const size_t write_size = 1U << config.write_size_log2;
|
||||
const auto write_buffer = std::make_unique<uint8_t[]>(write_size);
|
||||
if( !write_buffer ) {
|
||||
return false;
|
||||
msg_t CaptureThread::static_fn(void* arg) {
|
||||
auto obj = static_cast<CaptureThread*>(arg);
|
||||
const auto error = obj->run();
|
||||
if( error.is_valid() && obj->error_callback ) {
|
||||
obj->error_callback(error.value());
|
||||
} else {
|
||||
if( obj->success_callback ) {
|
||||
obj->success_callback();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Optional<File::Error> CaptureThread::run() {
|
||||
StreamOutput stream { &config };
|
||||
|
||||
while( !chThdShouldTerminate() ) {
|
||||
if( stream.available() >= write_size ) {
|
||||
if( stream.read(write_buffer.get(), write_size) != write_size ) {
|
||||
return false;
|
||||
}
|
||||
if( !writer->write(write_buffer.get(), write_size) ) {
|
||||
return false;
|
||||
if( stream.available() ) {
|
||||
auto buffer = stream.get_buffer();
|
||||
auto write_result = writer->write(buffer->data(), buffer->size());
|
||||
if( write_result.is_error() ) {
|
||||
return write_result.error();
|
||||
}
|
||||
stream.release_buffer(buffer);
|
||||
} else {
|
||||
chEvtWaitAny(EVT_MASK_CAPTURE_THREAD);
|
||||
chEvtWaitAny(event_mask_loop_wake);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return { };
|
||||
}
|
||||
|
||||
@@ -26,13 +26,16 @@
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "file.hpp"
|
||||
#include "optional.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
class Writer {
|
||||
public:
|
||||
virtual bool write(const void* const buffer, const size_t bytes) = 0;
|
||||
virtual File::Result<size_t> write(const void* const buffer, const size_t bytes) = 0;
|
||||
virtual ~Writer() = default;
|
||||
};
|
||||
|
||||
@@ -40,8 +43,10 @@ class CaptureThread {
|
||||
public:
|
||||
CaptureThread(
|
||||
std::unique_ptr<Writer> writer,
|
||||
size_t write_size_log2,
|
||||
size_t buffer_count_log2
|
||||
size_t write_size,
|
||||
size_t buffer_count,
|
||||
std::function<void()> success_callback,
|
||||
std::function<void(File::Error)> error_callback
|
||||
);
|
||||
~CaptureThread();
|
||||
|
||||
@@ -52,16 +57,17 @@ public:
|
||||
static void check_fifo_isr();
|
||||
|
||||
private:
|
||||
static constexpr auto event_mask_loop_wake = EVENT_MASK(0);
|
||||
|
||||
CaptureConfig config;
|
||||
std::unique_ptr<Writer> writer;
|
||||
std::function<void()> success_callback;
|
||||
std::function<void(File::Error)> error_callback;
|
||||
static Thread* thread;
|
||||
|
||||
static msg_t static_fn(void* arg) {
|
||||
auto obj = static_cast<CaptureThread*>(arg);
|
||||
return obj->run();
|
||||
}
|
||||
static msg_t static_fn(void* arg);
|
||||
|
||||
msg_t run();
|
||||
Optional<File::Error> run();
|
||||
};
|
||||
|
||||
#endif/*__CAPTURE_THREAD_H__*/
|
||||
|
||||
@@ -41,17 +41,27 @@ char * modhash;
|
||||
* I suppose I could force M4MEMMAP to an invalid memory reason which would
|
||||
* cause an exception and effectively halt the M4. But that feels gross.
|
||||
*/
|
||||
void m4_init(const portapack::spi_flash::region_t from, const portapack::memory::region_t to) {
|
||||
/* Initialize M4 code RAM */
|
||||
std::memcpy(reinterpret_cast<void*>(to.base()), from.base(), from.size);
|
||||
void m4_init(const portapack::spi_flash::image_tag_t image_tag, const portapack::memory::region_t to) {
|
||||
const portapack::spi_flash::chunk_t* chunk = reinterpret_cast<const portapack::spi_flash::chunk_t*>(portapack::spi_flash::images.base());
|
||||
while(chunk->tag) {
|
||||
if( chunk->tag == image_tag ) {
|
||||
/* Initialize M4 code RAM */
|
||||
std::memcpy(reinterpret_cast<void*>(to.base()), &chunk->data[0], chunk->length);
|
||||
|
||||
/* M4 core is assumed to be sleeping with interrupts off, so we can mess
|
||||
* with its address space and RAM without concern.
|
||||
*/
|
||||
LPC_CREG->M4MEMMAP = to.base();
|
||||
/* M4 core is assumed to be sleeping with interrupts off, so we can mess
|
||||
* with its address space and RAM without concern.
|
||||
*/
|
||||
LPC_CREG->M4MEMMAP = to.base();
|
||||
|
||||
/* Reset M4 core */
|
||||
LPC_RGU->RESET_CTRL[0] = (1 << 13);
|
||||
/* Reset M4 core */
|
||||
LPC_RGU->RESET_CTRL[0] = (1 << 13);
|
||||
|
||||
return;
|
||||
}
|
||||
chunk = chunk->next();
|
||||
}
|
||||
|
||||
chDbgPanic("NoImg");
|
||||
}
|
||||
|
||||
void m4_request_shutdown() {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "spi_image.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
void m4_init(const portapack::spi_flash::region_t from, const portapack::memory::region_t to);
|
||||
void m4_init(const portapack::spi_flash::image_tag_t image_tag, const portapack::memory::region_t to);
|
||||
void m4_request_shutdown();
|
||||
void m4_switch(const char * hash);
|
||||
int m4_load_image(void);
|
||||
|
||||
@@ -21,11 +21,14 @@
|
||||
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
#include "hackrf_gpio.hpp"
|
||||
#include "portapack_hal.hpp"
|
||||
|
||||
#include "jtag_target_gpio.hpp"
|
||||
#include "cpld_max5.hpp"
|
||||
#include "cpld_xilinx.hpp"
|
||||
#include "portapack_cpld_data.hpp"
|
||||
#include "hackrf_cpld_data.hpp"
|
||||
|
||||
bool cpld_update_if_necessary() {
|
||||
jtag::GPIOTarget target {
|
||||
@@ -82,3 +85,38 @@ bool cpld_update_if_necessary() {
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
static jtag::GPIOTarget jtag_target_hackrf() {
|
||||
return {
|
||||
hackrf::one::gpio_cpld_tck,
|
||||
hackrf::one::gpio_cpld_tms,
|
||||
hackrf::one::gpio_cpld_tdi,
|
||||
hackrf::one::gpio_cpld_tdo,
|
||||
};
|
||||
}
|
||||
|
||||
bool cpld_hackrf_load_sram() {
|
||||
auto jtag_target_hackrf_cpld = jtag_target_hackrf();
|
||||
cpld::xilinx::XC2C64A hackrf_cpld { jtag_target_hackrf_cpld };
|
||||
|
||||
hackrf_cpld.write_sram(hackrf::one::cpld::verify_blocks);
|
||||
const auto ok = hackrf_cpld.verify_sram(hackrf::one::cpld::verify_blocks);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool cpld_hackrf_verify_eeprom() {
|
||||
auto jtag_target_hackrf_cpld = jtag_target_hackrf();
|
||||
cpld::xilinx::XC2C64A hackrf_cpld { jtag_target_hackrf_cpld };
|
||||
|
||||
const auto ok = hackrf_cpld.verify_eeprom(hackrf::one::cpld::verify_blocks);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void cpld_hackrf_init_from_eeprom() {
|
||||
auto jtag_target_hackrf_cpld = jtag_target_hackrf();
|
||||
cpld::xilinx::XC2C64A hackrf_cpld { jtag_target_hackrf_cpld };
|
||||
|
||||
hackrf_cpld.init_from_eeprom();
|
||||
}
|
||||
|
||||
@@ -24,4 +24,8 @@
|
||||
|
||||
bool cpld_update_if_necessary();
|
||||
|
||||
bool cpld_hackrf_load_sram();
|
||||
bool cpld_hackrf_verify_eeprom();
|
||||
void cpld_hackrf_init_from_eeprom();
|
||||
|
||||
#endif/*__CPLD_UPDATE_H__*/
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
|
||||
#include "ert_app.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "manchester.hpp"
|
||||
@@ -59,17 +57,9 @@ std::string commodity_type(CommodityType value) {
|
||||
|
||||
} /* namespace ert */
|
||||
|
||||
ERTLogger::ERTLogger(
|
||||
const std::string& file_path
|
||||
) : log_file { file_path }
|
||||
{
|
||||
}
|
||||
|
||||
void ERTLogger::on_packet(const ert::Packet& packet) {
|
||||
if( log_file.is_open() ) {
|
||||
const auto formatted = packet.symbols_formatted();
|
||||
log_file.write_entry(packet.received_at(), formatted.data + "/" + formatted.errors);
|
||||
}
|
||||
const auto formatted = packet.symbols_formatted();
|
||||
log_file.write_entry(packet.received_at(), formatted.data + "/" + formatted.errors);
|
||||
}
|
||||
|
||||
const ERTRecentEntry::Key ERTRecentEntry::invalid_key { };
|
||||
@@ -131,18 +121,12 @@ void RecentEntriesView<ERTRecentEntries>::draw(
|
||||
}
|
||||
|
||||
ERTAppView::ERTAppView(NavigationView&) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_ert);
|
||||
|
||||
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);
|
||||
}
|
||||
);
|
||||
|
||||
radio::enable({
|
||||
initial_target_frequency,
|
||||
sampling_rate,
|
||||
@@ -152,20 +136,16 @@ ERTAppView::ERTAppView(NavigationView&) {
|
||||
1,
|
||||
});
|
||||
|
||||
baseband::start({
|
||||
.mode = 6,
|
||||
.sampling_rate = sampling_rate,
|
||||
.decimation_factor = 1,
|
||||
});
|
||||
|
||||
logger = std::make_unique<ERTLogger>("ert.txt");
|
||||
logger = std::make_unique<ERTLogger>();
|
||||
if( logger ) {
|
||||
logger->append("ert.txt");
|
||||
}
|
||||
}
|
||||
|
||||
ERTAppView::~ERTAppView() {
|
||||
baseband::stop();
|
||||
radio::disable();
|
||||
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::ERTPacket);
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void ERTAppView::focus() {
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "log_file.hpp"
|
||||
|
||||
#include "ert_packet.hpp"
|
||||
@@ -85,8 +87,10 @@ struct ERTRecentEntry {
|
||||
|
||||
class ERTLogger {
|
||||
public:
|
||||
ERTLogger(const std::string& file_path);
|
||||
|
||||
Optional<File::Error> append(const std::string& filename) {
|
||||
return log_file.append(filename);
|
||||
}
|
||||
|
||||
void on_packet(const ert::Packet& packet);
|
||||
|
||||
private:
|
||||
@@ -124,6 +128,15 @@ private:
|
||||
|
||||
ERTRecentEntriesView recent_entries_view { recent };
|
||||
|
||||
MessageHandlerRegistration message_handler_packet {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
void on_packet(const ert::Packet& packet);
|
||||
void on_show_list();
|
||||
};
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
|
||||
#include "sd_card.hpp"
|
||||
#include "time.hpp"
|
||||
@@ -42,6 +40,8 @@ using namespace lpc43xx;
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
|
||||
extern "C" {
|
||||
|
||||
CH_IRQ_HANDLER(M4Core_IRQHandler) {
|
||||
@@ -59,34 +59,60 @@ CH_IRQ_HANDLER(M4Core_IRQHandler) {
|
||||
|
||||
}
|
||||
|
||||
MessageHandlerMap EventDispatcher::message_map_;
|
||||
class MessageHandlerMap {
|
||||
public:
|
||||
using MessageHandler = std::function<void(Message* const p)>;
|
||||
|
||||
void register_handler(const Message::ID id, MessageHandler&& handler) {
|
||||
if( map_[toUType(id)] != nullptr ) {
|
||||
chDbgPanic("MsgDblReg");
|
||||
}
|
||||
map_[toUType(id)] = std::move(handler);
|
||||
}
|
||||
|
||||
void unregister_handler(const Message::ID id) {
|
||||
map_[toUType(id)] = nullptr;
|
||||
}
|
||||
|
||||
void send(Message* const message) {
|
||||
if( message->id < Message::ID::MAX ) {
|
||||
auto& fn = map_[toUType(message->id)];
|
||||
if( fn ) {
|
||||
fn(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
using MapType = std::array<MessageHandler, toUType(Message::ID::MAX)>;
|
||||
MapType map_;
|
||||
};
|
||||
|
||||
static MessageHandlerMap message_map;
|
||||
Thread* EventDispatcher::thread_event_loop = nullptr;
|
||||
bool EventDispatcher::is_running = false;
|
||||
|
||||
EventDispatcher::EventDispatcher(
|
||||
ui::Widget* const top_widget,
|
||||
ui::Painter& painter,
|
||||
ui::Context& context
|
||||
) : top_widget { top_widget },
|
||||
painter(painter),
|
||||
painter { },
|
||||
context(context)
|
||||
{
|
||||
init_message_queues();
|
||||
|
||||
thread_event_loop = chThdSelf();
|
||||
is_running = true;
|
||||
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() {
|
||||
@@ -100,7 +126,6 @@ void EventDispatcher::set_display_sleep(const bool sleep) {
|
||||
portapack::io.lcd_backlight(false);
|
||||
portapack::display.sleep();
|
||||
} else {
|
||||
portapack::bl_tick_counter = 0;
|
||||
portapack::display.wake();
|
||||
portapack::io.lcd_backlight(true);
|
||||
}
|
||||
@@ -112,10 +137,48 @@ eventmask_t EventDispatcher::wait() {
|
||||
}
|
||||
|
||||
void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
if( shared_memory.m4_panic_msg[0] != 0 ) {
|
||||
halt = true;
|
||||
}
|
||||
|
||||
if( halt ) {
|
||||
if( shared_memory.m4_panic_msg[0] != 0 ) {
|
||||
painter.fill_rectangle(
|
||||
{ 0, 0, portapack::display.width(), portapack::display.height() },
|
||||
ui::Color::red()
|
||||
);
|
||||
|
||||
constexpr int border = 8;
|
||||
painter.fill_rectangle(
|
||||
{ border, border, portapack::display.width() - (border * 2), portapack::display.height() - (border * 2) },
|
||||
ui::Color::black()
|
||||
);
|
||||
|
||||
painter.draw_string({ 48, 24 }, top_widget->style(), "M4 Guru Meditation");
|
||||
|
||||
shared_memory.m4_panic_msg[sizeof(shared_memory.m4_panic_msg) - 1] = 0;
|
||||
const std::string message = shared_memory.m4_panic_msg;
|
||||
const int x_offset = (portapack::display.width() - (message.size() * 8)) / 2;
|
||||
constexpr int y_offset = (portapack::display.height() - 16) / 2;
|
||||
painter.draw_string(
|
||||
{ x_offset, y_offset },
|
||||
top_widget->style(),
|
||||
message
|
||||
);
|
||||
|
||||
shared_memory.m4_panic_msg[0] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_APPLICATION ) {
|
||||
handle_application_queue();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_LOCAL ) {
|
||||
handle_local_queue();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_RTC_TICK ) {
|
||||
handle_rtc_tick();
|
||||
}
|
||||
@@ -123,16 +186,16 @@ void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
if( events & EVT_MASK_SWITCHES ) {
|
||||
handle_switches();
|
||||
}
|
||||
|
||||
if( events & EVT_MASK_ENCODER ) {
|
||||
handle_encoder();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -141,7 +204,13 @@ void EventDispatcher::dispatch(const eventmask_t events) {
|
||||
|
||||
void EventDispatcher::handle_application_queue() {
|
||||
shared_memory.application_queue.handle([](Message* const message) {
|
||||
message_map().send(message);
|
||||
message_map.send(message);
|
||||
});
|
||||
}
|
||||
|
||||
void EventDispatcher::handle_local_queue() {
|
||||
shared_memory.app_local_queue.handle([](Message* const message) {
|
||||
message_map.send(message);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -170,7 +239,6 @@ ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent ev
|
||||
for(const auto child : w->children()) {
|
||||
const auto touched_widget = touch_widget(child, event);
|
||||
if( touched_widget ) {
|
||||
portapack::bl_tick_counter = 0;
|
||||
return touched_widget;
|
||||
}
|
||||
}
|
||||
@@ -179,7 +247,6 @@ ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent ev
|
||||
if( r.contains(event.point) ) {
|
||||
if( w->on_touch(event) ) {
|
||||
// This widget responded. Return it up the call stack.
|
||||
portapack::bl_tick_counter = 0;
|
||||
return w;
|
||||
}
|
||||
}
|
||||
@@ -209,7 +276,7 @@ void EventDispatcher::on_touch_event(ui::TouchEvent event) {
|
||||
|
||||
void EventDispatcher::handle_lcd_frame_sync() {
|
||||
DisplayFrameSyncMessage message;
|
||||
message_map().send(&message);
|
||||
message_map.send(&message);
|
||||
painter.paint_widget_tree(top_widget);
|
||||
}
|
||||
|
||||
@@ -275,11 +342,17 @@ void EventDispatcher::event_bubble_encoder(const ui::EncoderEvent event) {
|
||||
}
|
||||
|
||||
void EventDispatcher::init_message_queues() {
|
||||
new (&shared_memory.baseband_queue) MessageQueue(
|
||||
shared_memory.baseband_queue_data, SharedMemory::baseband_queue_k
|
||||
);
|
||||
new (&shared_memory.application_queue) MessageQueue(
|
||||
shared_memory.application_queue_data, SharedMemory::application_queue_k
|
||||
);
|
||||
|
||||
new (&shared_memory) SharedMemory;
|
||||
}
|
||||
|
||||
MessageHandlerRegistration::MessageHandlerRegistration(
|
||||
const Message::ID message_id,
|
||||
MessageHandlerMap::MessageHandler&& callback
|
||||
) : message_id { message_id }
|
||||
{
|
||||
message_map.register_handler(message_id, std::move(callback));
|
||||
}
|
||||
|
||||
MessageHandlerRegistration::~MessageHandlerRegistration() {
|
||||
message_map.unregister_handler(message_id);
|
||||
}
|
||||
|
||||
@@ -38,24 +38,19 @@
|
||||
|
||||
#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);
|
||||
constexpr auto EVT_MASK_CAPTURE_THREAD = EVENT_MASK(7);
|
||||
|
||||
class EventDispatcher {
|
||||
public:
|
||||
EventDispatcher(
|
||||
ui::Widget* const top_widget,
|
||||
ui::Painter& painter,
|
||||
ui::Context& context
|
||||
);
|
||||
|
||||
void run();
|
||||
void request_stop();
|
||||
static void request_stop();
|
||||
|
||||
void set_display_sleep(const bool sleep);
|
||||
|
||||
@@ -65,6 +60,14 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static inline void event_isr_rtc_tick() {
|
||||
events_flag_isr(EVT_MASK_RTC_TICK);
|
||||
}
|
||||
|
||||
static inline void event_isr_lcd_frame_sync() {
|
||||
events_flag_isr(EVT_MASK_LCD_FRAME_SYNC);
|
||||
}
|
||||
|
||||
static inline void events_flag(const eventmask_t events) {
|
||||
if( thread_event_loop ) {
|
||||
chEvtSignal(thread_event_loop, events);
|
||||
@@ -77,27 +80,35 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static MessageHandlerMap& message_map() {
|
||||
return message_map_;
|
||||
template<typename T>
|
||||
static void send_message(T& message) {
|
||||
shared_memory.app_local_queue.push(message);
|
||||
events_flag(EVT_MASK_LOCAL);
|
||||
}
|
||||
|
||||
private:
|
||||
static MessageHandlerMap message_map_;
|
||||
static constexpr auto EVT_MASK_RTC_TICK = EVENT_MASK(0);
|
||||
static constexpr auto EVT_MASK_LCD_FRAME_SYNC = EVENT_MASK(1);
|
||||
static constexpr auto EVT_MASK_APPLICATION = EVENT_MASK(6);
|
||||
static constexpr auto EVT_MASK_LOCAL = EVENT_MASK(7);
|
||||
|
||||
static Thread* thread_event_loop;
|
||||
|
||||
touch::Manager touch_manager;
|
||||
ui::Widget* const top_widget;
|
||||
ui::Painter& painter;
|
||||
ui::Painter painter;
|
||||
ui::Context& context;
|
||||
uint32_t encoder_last = 0;
|
||||
bool is_running = true;
|
||||
static bool is_running;
|
||||
bool sd_card_present = false;
|
||||
bool display_sleep = false;
|
||||
bool halt = false;
|
||||
|
||||
eventmask_t wait();
|
||||
void dispatch(const eventmask_t events);
|
||||
|
||||
void handle_application_queue();
|
||||
void handle_local_queue();
|
||||
void handle_rtc_tick();
|
||||
|
||||
static ui::Widget* touch_widget(ui::Widget* const w, ui::TouchEvent event);
|
||||
@@ -117,4 +128,17 @@ private:
|
||||
void init_message_queues();
|
||||
};
|
||||
|
||||
class MessageHandlerRegistration {
|
||||
public:
|
||||
MessageHandlerRegistration(
|
||||
const Message::ID message_id,
|
||||
std::function<void(Message* const p)>&& callback
|
||||
);
|
||||
|
||||
~MessageHandlerRegistration();
|
||||
|
||||
private:
|
||||
const Message::ID message_id;
|
||||
};
|
||||
|
||||
#endif/*__EVENT_M0_H__*/
|
||||
|
||||
@@ -23,65 +23,105 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
File::File(const std::string& filename, openmode mode) {
|
||||
BYTE fatfs_mode = 0;
|
||||
if( mode & openmode::in ) {
|
||||
fatfs_mode |= FA_READ;
|
||||
}
|
||||
if( mode & openmode::out ) {
|
||||
fatfs_mode |= FA_WRITE;
|
||||
}
|
||||
if( mode & openmode::trunc ) {
|
||||
fatfs_mode |= FA_CREATE_ALWAYS;
|
||||
}
|
||||
if( mode & openmode::ate ) {
|
||||
fatfs_mode |= FA_OPEN_ALWAYS;
|
||||
}
|
||||
/* Values added to FatFs FRESULT enum, values outside the FRESULT data type */
|
||||
static_assert(sizeof(FIL::err) == 1, "FatFs FIL::err size not expected.");
|
||||
#define FR_DISK_FULL (0x100)
|
||||
#define FR_EOF (0x101)
|
||||
#define FR_BAD_SEEK (0x102)
|
||||
#define FR_UNEXPECTED (0x103)
|
||||
|
||||
if( f_open(&f, filename.c_str(), fatfs_mode) == FR_OK ) {
|
||||
if( mode & openmode::ate ) {
|
||||
if( f_lseek(&f, f_size(&f)) != FR_OK ) {
|
||||
Optional<File::Error> File::open_fatfs(const std::string& filename, BYTE mode) {
|
||||
auto result = f_open(&f, filename.c_str(), mode);
|
||||
if( result == FR_OK ) {
|
||||
if( mode & FA_OPEN_ALWAYS ) {
|
||||
const auto result = f_lseek(&f, f_size(&f));
|
||||
if( result != FR_OK ) {
|
||||
f_close(&f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( result == FR_OK ) {
|
||||
return { };
|
||||
} else {
|
||||
return { result };
|
||||
}
|
||||
}
|
||||
|
||||
Optional<File::Error> File::open(const std::string& filename) {
|
||||
return open_fatfs(filename, FA_READ);
|
||||
}
|
||||
|
||||
Optional<File::Error> File::append(const std::string& filename) {
|
||||
return open_fatfs(filename, FA_WRITE | FA_OPEN_ALWAYS);
|
||||
}
|
||||
|
||||
Optional<File::Error> File::create(const std::string& filename) {
|
||||
return open_fatfs(filename, FA_WRITE | FA_CREATE_ALWAYS);
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
f_close(&f);
|
||||
}
|
||||
|
||||
bool File::read(void* const data, const size_t bytes_to_read) {
|
||||
File::Result<size_t> 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);
|
||||
if( result == FR_OK ) {
|
||||
return { static_cast<size_t>(bytes_read) };
|
||||
} else {
|
||||
return { static_cast<Error>(result) };
|
||||
}
|
||||
}
|
||||
|
||||
bool File::write(const void* const data, const size_t bytes_to_write) {
|
||||
File::Result<size_t> 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);
|
||||
if( result == FR_OK ) {
|
||||
if( bytes_to_write == bytes_written ) {
|
||||
return { static_cast<size_t>(bytes_written) };
|
||||
} else {
|
||||
return Error { FR_DISK_FULL };
|
||||
}
|
||||
} else {
|
||||
return { static_cast<Error>(result) };
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t File::seek(const uint64_t new_position) {
|
||||
File::Result<uint64_t> File::seek(const uint64_t new_position) {
|
||||
/* NOTE: Returns *old* position, not new position */
|
||||
const auto old_position = f_tell(&f);
|
||||
if( f_lseek(&f, new_position) != FR_OK ) {
|
||||
f_close(&f);
|
||||
const auto result = f_lseek(&f, new_position);
|
||||
if( result != FR_OK ) {
|
||||
return { static_cast<Error>(result) };
|
||||
}
|
||||
if( f_tell(&f) != new_position ) {
|
||||
f_close(&f);
|
||||
return { static_cast<Error>(FR_BAD_SEEK) };
|
||||
}
|
||||
return old_position;
|
||||
return { static_cast<uint64_t>(old_position) };
|
||||
}
|
||||
|
||||
bool File::puts(const std::string& string) {
|
||||
const auto result = f_puts(string.c_str(), &f);
|
||||
return (result >= 0);
|
||||
Optional<File::Error> File::write_line(const std::string& s) {
|
||||
const auto result_s = write(s.c_str(), s.size());
|
||||
if( result_s.is_error() ) {
|
||||
return { result_s.error() };
|
||||
}
|
||||
|
||||
const auto result_crlf = write("\r\n", 2);
|
||||
if( result_crlf.is_error() ) {
|
||||
return { result_crlf.error() };
|
||||
}
|
||||
|
||||
return { };
|
||||
}
|
||||
|
||||
bool File::sync() {
|
||||
Optional<File::Error> File::sync() {
|
||||
const auto result = f_sync(&f);
|
||||
return (result == FR_OK);
|
||||
if( result == FR_OK ) {
|
||||
return { };
|
||||
} else {
|
||||
return { result };
|
||||
}
|
||||
}
|
||||
|
||||
static std::string find_last_file_matching_pattern(const std::string& pattern) {
|
||||
@@ -140,6 +180,36 @@ std::string next_filename_stem_matching_pattern(const std::string& filename_stem
|
||||
namespace std {
|
||||
namespace filesystem {
|
||||
|
||||
std::string filesystem_error::what() const {
|
||||
switch(err) {
|
||||
case FR_OK: return "ok";
|
||||
case FR_DISK_ERR: return "disk error";
|
||||
case FR_INT_ERR: return "insanity detected";
|
||||
case FR_NOT_READY: return "not ready";
|
||||
case FR_NO_FILE: return "no file";
|
||||
case FR_NO_PATH: return "no path";
|
||||
case FR_INVALID_NAME: return "invalid name";
|
||||
case FR_DENIED: return "denied";
|
||||
case FR_EXIST: return "exists";
|
||||
case FR_INVALID_OBJECT: return "invalid object";
|
||||
case FR_WRITE_PROTECTED: return "write protected";
|
||||
case FR_INVALID_DRIVE: return "invalid drive";
|
||||
case FR_NOT_ENABLED: return "not enabled";
|
||||
case FR_NO_FILESYSTEM: return "no filesystem";
|
||||
case FR_MKFS_ABORTED: return "mkfs aborted";
|
||||
case FR_TIMEOUT: return "timeout";
|
||||
case FR_LOCKED: return "locked";
|
||||
case FR_NOT_ENOUGH_CORE: return "not enough core";
|
||||
case FR_TOO_MANY_OPEN_FILES: return "too many open files";
|
||||
case FR_INVALID_PARAMETER: return "invalid parameter";
|
||||
case FR_EOF: return "end of file";
|
||||
case FR_DISK_FULL: return "disk full";
|
||||
case FR_BAD_SEEK: return "bad seek";
|
||||
case FR_UNEXPECTED: return "unexpected";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
directory_iterator::directory_iterator(
|
||||
const char* path,
|
||||
const char* wild
|
||||
@@ -164,5 +234,24 @@ bool is_regular_file(const file_status s) {
|
||||
return !(s & AM_DIR);
|
||||
}
|
||||
|
||||
space_info space(const path& p) {
|
||||
DWORD free_clusters { 0 };
|
||||
FATFS* fs;
|
||||
if( f_getfree(p.c_str(), &free_clusters, &fs) == FR_OK ) {
|
||||
#if _MAX_SS != _MIN_SS
|
||||
static_assert(false, "FatFs not configured for fixed sector size");
|
||||
#else
|
||||
const std::uintmax_t cluster_bytes = fs->csize * _MIN_SS;
|
||||
return {
|
||||
(fs->n_fatent - 2) * cluster_bytes,
|
||||
free_clusters * cluster_bytes,
|
||||
free_clusters * cluster_bytes,
|
||||
};
|
||||
#endif
|
||||
} else {
|
||||
return { 0, 0, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace filesystem */
|
||||
} /* namespace std */
|
||||
|
||||
@@ -24,59 +24,59 @@
|
||||
|
||||
#include "ff.h"
|
||||
|
||||
#include "optional.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <iterator>
|
||||
|
||||
class File {
|
||||
public:
|
||||
enum openmode {
|
||||
app = 0x100,
|
||||
binary = 0x200,
|
||||
in = FA_READ,
|
||||
out = FA_WRITE,
|
||||
trunc = FA_CREATE_ALWAYS,
|
||||
ate = FA_OPEN_ALWAYS,
|
||||
};
|
||||
|
||||
File(const std::string& filename, openmode mode);
|
||||
~File();
|
||||
|
||||
bool is_open() const {
|
||||
return f_error(&f) == 0;
|
||||
}
|
||||
|
||||
bool read(void* const data, const size_t bytes_to_read);
|
||||
bool write(const void* const data, const size_t bytes_to_write);
|
||||
|
||||
uint64_t seek(const uint64_t new_position);
|
||||
|
||||
template<size_t N>
|
||||
bool write(const std::array<uint8_t, N>& data) {
|
||||
return write(data.data(), N);
|
||||
}
|
||||
|
||||
bool puts(const std::string& string);
|
||||
|
||||
bool sync();
|
||||
|
||||
private:
|
||||
FIL f;
|
||||
};
|
||||
|
||||
inline constexpr File::openmode operator|(File::openmode a, File::openmode b) {
|
||||
return File::openmode(static_cast<int>(a) | static_cast<int>(b));
|
||||
}
|
||||
|
||||
std::string next_filename_stem_matching_pattern(const std::string& filename_stem_pattern);
|
||||
|
||||
namespace std {
|
||||
namespace filesystem {
|
||||
|
||||
struct filesystem_error {
|
||||
constexpr filesystem_error(
|
||||
) : err { FR_OK }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr filesystem_error(
|
||||
FRESULT fatfs_error
|
||||
) : err { fatfs_error }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr filesystem_error(
|
||||
unsigned int other_error
|
||||
) : err { other_error }
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t code() const {
|
||||
return err;
|
||||
}
|
||||
|
||||
std::string what() const;
|
||||
|
||||
private:
|
||||
uint32_t err;
|
||||
};
|
||||
|
||||
using path = std::string;
|
||||
using file_status = BYTE;
|
||||
|
||||
struct space_info {
|
||||
static_assert(sizeof(std::uintmax_t) >= 8, "std::uintmax_t too small (<uint64_t)");
|
||||
|
||||
std::uintmax_t capacity;
|
||||
std::uintmax_t free;
|
||||
std::uintmax_t available;
|
||||
};
|
||||
|
||||
struct directory_entry : public FILINFO {
|
||||
file_status status() const {
|
||||
return fattrib;
|
||||
@@ -126,7 +126,96 @@ inline bool operator!=(const directory_iterator& lhs, const directory_iterator&
|
||||
|
||||
bool is_regular_file(const file_status s);
|
||||
|
||||
space_info space(const path& p);
|
||||
|
||||
} /* namespace filesystem */
|
||||
} /* namespace std */
|
||||
|
||||
class File {
|
||||
public:
|
||||
using Error = std::filesystem::filesystem_error;
|
||||
|
||||
template<typename T>
|
||||
struct Result {
|
||||
enum class Type {
|
||||
Success,
|
||||
Error,
|
||||
} type;
|
||||
union {
|
||||
T value_;
|
||||
Error error_;
|
||||
};
|
||||
|
||||
bool is_ok() const {
|
||||
return type == Type::Success;
|
||||
}
|
||||
|
||||
bool is_error() const {
|
||||
return type == Type::Error;
|
||||
}
|
||||
|
||||
const T& value() const {
|
||||
return value_;
|
||||
}
|
||||
|
||||
Error error() const {
|
||||
return error_;
|
||||
}
|
||||
|
||||
Result() = delete;
|
||||
|
||||
constexpr Result(
|
||||
T value
|
||||
) : type { Type::Success },
|
||||
value_ { value }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr Result(
|
||||
Error error
|
||||
) : type { Type::Error },
|
||||
error_ { error }
|
||||
{
|
||||
}
|
||||
|
||||
~Result() {
|
||||
if( type == Type::Success ) {
|
||||
value_.~T();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
File() { };
|
||||
~File();
|
||||
|
||||
/* Prevent copies */
|
||||
File(const File&) = delete;
|
||||
File& operator=(const File&) = delete;
|
||||
|
||||
// TODO: Return Result<>.
|
||||
Optional<Error> open(const std::string& filename);
|
||||
Optional<Error> append(const std::string& filename);
|
||||
Optional<Error> create(const std::string& filename);
|
||||
|
||||
Result<size_t> read(void* const data, const size_t bytes_to_read);
|
||||
Result<size_t> write(const void* const data, const size_t bytes_to_write);
|
||||
|
||||
Result<uint64_t> seek(const uint64_t new_position);
|
||||
|
||||
template<size_t N>
|
||||
Result<size_t> write(const std::array<uint8_t, N>& data) {
|
||||
return write(data.data(), N);
|
||||
}
|
||||
|
||||
Optional<Error> write_line(const std::string& s);
|
||||
|
||||
// TODO: Return Result<>.
|
||||
Optional<Error> sync();
|
||||
|
||||
private:
|
||||
FIL f;
|
||||
|
||||
Optional<Error> open_fatfs(const std::string& filename, BYTE mode);
|
||||
};
|
||||
|
||||
#endif/*__FILE_H__*/
|
||||
|
||||
@@ -168,8 +168,6 @@ void timer0_callback(GPTDriver* const) {
|
||||
EventDispatcher::events_flag_isr(event_mask);
|
||||
chSysUnlockFromIsr();
|
||||
}
|
||||
|
||||
touch::adc::start();
|
||||
}
|
||||
|
||||
/* TODO: Refactor some/all of this to appropriate shared headers? */
|
||||
@@ -187,6 +185,8 @@ static GPTConfig timer0_config {
|
||||
};
|
||||
|
||||
void controls_init() {
|
||||
touch::adc::start();
|
||||
|
||||
/* GPT timer 0 is used to scan user interface controls -- touch screen,
|
||||
* navigation switches.
|
||||
*/
|
||||
|
||||
@@ -54,7 +54,7 @@ CH_IRQ_HANDLER(PIN_INT4_IRQHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
|
||||
chSysLockFromIsr();
|
||||
EventDispatcher::events_flag_isr(EVT_MASK_LCD_FRAME_SYNC);
|
||||
EventDispatcher::event_isr_lcd_frame_sync();
|
||||
chSysUnlockFromIsr();
|
||||
|
||||
LPC_GPIO_INT->IST = (1U << 4);
|
||||
|
||||
@@ -39,7 +39,7 @@ CH_IRQ_HANDLER(RTC_IRQHandler) {
|
||||
CH_IRQ_PROLOGUE();
|
||||
|
||||
chSysLockFromIsr();
|
||||
EventDispatcher::events_flag_isr(EVT_MASK_RTC_TICK);
|
||||
EventDispatcher::event_isr_rtc_tick();
|
||||
chSysUnlockFromIsr();
|
||||
|
||||
rtc::interrupt::clear_all();
|
||||
|
||||
@@ -23,21 +23,15 @@
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
LogFile::LogFile(
|
||||
const std::string& file_path
|
||||
) : file { file_path, File::openmode::out | File::openmode::ate }
|
||||
{
|
||||
}
|
||||
|
||||
bool LogFile::is_open() const {
|
||||
return file.is_open();
|
||||
}
|
||||
|
||||
bool LogFile::write_entry(const rtc::RTC& datetime, const std::string& entry) {
|
||||
Optional<File::Error> LogFile::write_entry(const rtc::RTC& datetime, const std::string& entry) {
|
||||
std::string timestamp = to_string_timestamp(datetime);
|
||||
return write(timestamp + " " + entry + "\r\n");
|
||||
return write_line(timestamp + " " + entry);
|
||||
}
|
||||
|
||||
bool LogFile::write(const std::string& message) {
|
||||
return file.puts(message) && file.sync();
|
||||
Optional<File::Error> LogFile::write_line(const std::string& message) {
|
||||
auto error = file.write_line(message);
|
||||
if( !error.is_valid() ) {
|
||||
file.sync();
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -31,16 +31,16 @@ using namespace lpc43xx;
|
||||
|
||||
class LogFile {
|
||||
public:
|
||||
LogFile(const std::string& file_path);
|
||||
Optional<File::Error> append(const std::string& filename) {
|
||||
return file.append(filename);
|
||||
}
|
||||
|
||||
bool is_open() const;
|
||||
|
||||
bool write_entry(const rtc::RTC& datetime, const std::string& entry);
|
||||
Optional<File::Error> write_entry(const rtc::RTC& datetime, const std::string& entry);
|
||||
|
||||
private:
|
||||
File file;
|
||||
|
||||
bool write(const std::string& message);
|
||||
Optional<File::Error> write_line(const std::string& message);
|
||||
};
|
||||
|
||||
#endif/*__LOG_FILE_H__*/
|
||||
|
||||
@@ -59,8 +59,6 @@
|
||||
#include "portapack.hpp"
|
||||
#include "portapack_shared_memory.hpp"
|
||||
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
#include "message_queue.hpp"
|
||||
|
||||
#include "ui.hpp"
|
||||
@@ -86,50 +84,43 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static void event_loop() {
|
||||
ui::Context context;
|
||||
ui::SystemView system_view {
|
||||
context,
|
||||
portapack::display.screen_rect()
|
||||
};
|
||||
|
||||
EventDispatcher event_dispatcher { &system_view, context };
|
||||
MessageHandlerRegistration message_handler_display_sleep {
|
||||
Message::ID::DisplaySleep,
|
||||
[&event_dispatcher](const Message* const) {
|
||||
event_dispatcher.set_display_sleep(true);
|
||||
}
|
||||
};
|
||||
|
||||
event_dispatcher.run();
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
portapack::init();
|
||||
|
||||
if( !cpld_update_if_necessary() ) {
|
||||
chSysHalt();
|
||||
}
|
||||
|
||||
portapack::io.init();
|
||||
portapack::display.init();
|
||||
|
||||
sdcStart(&SDCD1, nullptr);
|
||||
|
||||
ui::Context context;
|
||||
ui::SystemView system_view {
|
||||
context,
|
||||
portapack::display.screen_rect()
|
||||
};
|
||||
ui::Painter painter;
|
||||
EventDispatcher event_dispatcher { &system_view, painter, context };
|
||||
|
||||
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();
|
||||
|
||||
event_dispatcher.run();
|
||||
event_loop();
|
||||
|
||||
sdcDisconnect(&SDCD1);
|
||||
sdcStop(&SDCD1);
|
||||
|
||||
portapack::shutdown();
|
||||
m4_init(portapack::spi_flash::hackrf, portapack::memory::map::m4_code_hackrf);
|
||||
m4_init(portapack::spi_flash::image_tag_hackrf, portapack::memory::map::m4_code_hackrf);
|
||||
m0_halt();
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -32,6 +32,8 @@ using namespace hackrf::one;
|
||||
#include "touch_adc.hpp"
|
||||
#include "audio.hpp"
|
||||
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
namespace portapack {
|
||||
|
||||
portapack::IO io {
|
||||
@@ -153,6 +155,14 @@ void init() {
|
||||
radio::init();
|
||||
|
||||
touch::adc::init();
|
||||
|
||||
if( !cpld_update_if_necessary() ) {
|
||||
chSysHalt();
|
||||
}
|
||||
|
||||
if( !cpld_hackrf_load_sram() ) {
|
||||
chSysHalt();
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
@@ -160,6 +170,9 @@ void shutdown() {
|
||||
|
||||
radio::disable();
|
||||
audio::shutdown();
|
||||
|
||||
cpld_hackrf_init_from_eeprom();
|
||||
|
||||
clock_manager.shutdown();
|
||||
|
||||
power.shutdown();
|
||||
|
||||
@@ -154,7 +154,6 @@ void ReceiverModel::enable() {
|
||||
void ReceiverModel::disable() {
|
||||
enabled_ = false;
|
||||
update_antenna_bias();
|
||||
baseband::stop();
|
||||
|
||||
// TODO: Responsibility for enabling/disabling the radio is muddy.
|
||||
// Some happens in ReceiverModel, some inside radio namespace.
|
||||
@@ -225,13 +224,9 @@ void ReceiverModel::update_baseband_configuration() {
|
||||
// protocols that need quick RX/TX turn-around.
|
||||
|
||||
// Disabling baseband while changing sampling rates seems like a good idea...
|
||||
baseband::stop();
|
||||
|
||||
radio::set_baseband_rate(sampling_rate() * baseband_oversampling());
|
||||
update_tuning_frequency();
|
||||
radio::set_baseband_decimation_by(baseband_oversampling());
|
||||
|
||||
baseband::start(baseband_configuration);
|
||||
}
|
||||
|
||||
void ReceiverModel::update_headphone_volume() {
|
||||
|
||||
@@ -30,6 +30,27 @@
|
||||
#include "max2837.hpp"
|
||||
#include "volume.hpp"
|
||||
|
||||
struct BasebandConfiguration {
|
||||
int32_t mode;
|
||||
uint32_t sampling_rate;
|
||||
size_t decimation_factor;
|
||||
|
||||
constexpr BasebandConfiguration(
|
||||
int32_t mode,
|
||||
uint32_t sampling_rate,
|
||||
size_t decimation_factor = 1
|
||||
) : mode { mode },
|
||||
sampling_rate { sampling_rate },
|
||||
decimation_factor { decimation_factor }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr BasebandConfiguration(
|
||||
) : BasebandConfiguration { -1, 0, 1 }
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class ReceiverModel {
|
||||
public:
|
||||
enum class Mode : int32_t {
|
||||
|
||||
@@ -70,26 +70,15 @@ void start() {
|
||||
// static constexpr bool monitor_overruns_and_not_dones = false;
|
||||
|
||||
Samples get() {
|
||||
const auto& frame = shared_memory.touch_adc_frame;
|
||||
const auto xp = frame.dr[portapack::adc0_touch_xp_input];
|
||||
const auto xn = frame.dr[portapack::adc0_touch_xn_input];
|
||||
const auto yp = frame.dr[portapack::adc0_touch_yp_input];
|
||||
const auto yn = frame.dr[portapack::adc0_touch_yn_input];
|
||||
|
||||
// if( monitor_overruns_and_not_dones ) {
|
||||
// const auto dr_and = xp & xn & yp & yn;
|
||||
// const auto dr_or = xp | xn | yp | yn;
|
||||
// const bool done = (dr_and >> 31) & 1;
|
||||
// const bool overrun = (dr_or >> 30) & 1;
|
||||
// led_tx.write(overrun);
|
||||
// led_rx.write(!done);
|
||||
// }
|
||||
|
||||
const auto xp_reg = LPC_ADC0->DR[portapack::adc0_touch_xp_input];
|
||||
const auto xn_reg = LPC_ADC0->DR[portapack::adc0_touch_xn_input];
|
||||
const auto yp_reg = LPC_ADC0->DR[portapack::adc0_touch_yp_input];
|
||||
const auto yn_reg = LPC_ADC0->DR[portapack::adc0_touch_yn_input];
|
||||
return {
|
||||
(xp >> 6) & 0x3ff,
|
||||
(xn >> 6) & 0x3ff,
|
||||
(yp >> 6) & 0x3ff,
|
||||
(yn >> 6) & 0x3ff,
|
||||
(xp_reg >> 6) & 0x3ff,
|
||||
(xn_reg >> 6) & 0x3ff,
|
||||
(yp_reg >> 6) & 0x3ff,
|
||||
(yn_reg >> 6) & 0x3ff,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,11 @@
|
||||
|
||||
#include "tpms_app.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "baseband_api.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
#include "string_format.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
@@ -49,26 +50,31 @@ std::string temperature(Temperature temperature) {
|
||||
return to_string_dec_int(temperature.celsius(), 3);
|
||||
}
|
||||
|
||||
std::string flags(Flags flags) {
|
||||
return to_string_hex(flags, 2);
|
||||
}
|
||||
|
||||
static std::string signal_type(SignalType signal_type) {
|
||||
switch(signal_type) {
|
||||
case SignalType::FSK_19k2_Schrader: return "FSK 38400 19200 Schrader";
|
||||
case SignalType::OOK_8k192_Schrader: return "OOK - 8192 Schrader";
|
||||
case SignalType::OOK_8k4_Schrader: return "OOK - 8400 Schrader";
|
||||
default: return "- - - -";
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace format */
|
||||
|
||||
} /* namespace tpms */
|
||||
|
||||
TPMSLogger::TPMSLogger(
|
||||
const std::string& file_path
|
||||
) : log_file { file_path }
|
||||
{
|
||||
}
|
||||
|
||||
void TPMSLogger::on_packet(const tpms::Packet& packet, const uint32_t target_frequency) {
|
||||
const auto hex_formatted = packet.symbols_formatted();
|
||||
|
||||
if( log_file.is_open() ) {
|
||||
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
|
||||
const auto tuning_frequency_str = to_string_dec_uint(target_frequency, 10);
|
||||
// TODO: function doesn't take uint64_t, so when >= 1<<32, weirdness will ensue!
|
||||
const auto tuning_frequency_str = to_string_dec_uint(target_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);
|
||||
}
|
||||
std::string entry = tuning_frequency_str + " " + tpms::format::signal_type(packet.signal_type()) + " " + 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 };
|
||||
@@ -82,16 +88,20 @@ void TPMSRecentEntry::update(const tpms::Reading& reading) {
|
||||
if( reading.temperature().is_valid() ) {
|
||||
last_temperature = reading.temperature();
|
||||
}
|
||||
if( reading.flags().is_valid() ) {
|
||||
last_flags = reading.flags();
|
||||
}
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
|
||||
static const std::array<std::pair<std::string, size_t>, 5> tpms_columns { {
|
||||
static const std::array<std::pair<std::string, size_t>, 6> tpms_columns { {
|
||||
{ "Tp", 2 },
|
||||
{ "ID", 8 },
|
||||
{ "kPa", 3 },
|
||||
{ "C", 3 },
|
||||
{ "Cnt", 3 },
|
||||
{ "Fl", 2 },
|
||||
} };
|
||||
|
||||
template<>
|
||||
@@ -143,63 +153,72 @@ void RecentEntriesView<TPMSRecentEntries>::draw(
|
||||
line += " " + to_string_dec_uint(entry.received_count, 3);
|
||||
}
|
||||
|
||||
if( entry.last_flags.is_valid() ) {
|
||||
line += " " + tpms::format::flags(entry.last_flags.value());
|
||||
} else {
|
||||
line += " " " ";
|
||||
}
|
||||
|
||||
line.resize(target_rect.width() / 8, ' ');
|
||||
painter.draw_string(target_rect.pos, draw_style, line);
|
||||
}
|
||||
|
||||
TPMSAppView::TPMSAppView(NavigationView&) {
|
||||
baseband::run_image(portapack::spi_flash::image_tag_tpms);
|
||||
|
||||
add_children({ {
|
||||
&rssi,
|
||||
&channel,
|
||||
&options_band,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
&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(message->signal_type, packet);
|
||||
}
|
||||
);
|
||||
|
||||
radio::enable({
|
||||
tuning_frequency(),
|
||||
sampling_rate,
|
||||
baseband_bandwidth,
|
||||
rf::Direction::Receive,
|
||||
false, 32, 32,
|
||||
false,
|
||||
static_cast<int8_t>(receiver_model.lna()),
|
||||
static_cast<int8_t>(receiver_model.vga()),
|
||||
1,
|
||||
});
|
||||
|
||||
baseband::start({
|
||||
.mode = 5,
|
||||
.sampling_rate = sampling_rate,
|
||||
.decimation_factor = 1,
|
||||
});
|
||||
options_band.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
this->on_band_changed(v);
|
||||
};
|
||||
options_band.set_by_value(target_frequency());
|
||||
|
||||
logger = std::make_unique<TPMSLogger>("tpms.txt");
|
||||
logger = std::make_unique<TPMSLogger>();
|
||||
if( logger ) {
|
||||
logger->append("tpms.txt");
|
||||
}
|
||||
}
|
||||
|
||||
TPMSAppView::~TPMSAppView() {
|
||||
baseband::stop();
|
||||
radio::disable();
|
||||
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::TPMSPacket);
|
||||
baseband::shutdown();
|
||||
}
|
||||
|
||||
void TPMSAppView::focus() {
|
||||
recent_entries_view.focus();
|
||||
options_band.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() });
|
||||
recent_entries_view.set_parent_rect({ 0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height });
|
||||
}
|
||||
|
||||
void TPMSAppView::on_packet(const tpms::SignalType signal_type, const tpms::Packet& packet) {
|
||||
void TPMSAppView::on_packet(const tpms::Packet& packet) {
|
||||
if( logger ) {
|
||||
logger->on_packet(packet, target_frequency());
|
||||
}
|
||||
|
||||
const auto reading_opt = packet.reading(signal_type);
|
||||
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);
|
||||
@@ -212,8 +231,17 @@ void TPMSAppView::on_show_list() {
|
||||
recent_entries_view.focus();
|
||||
}
|
||||
|
||||
void TPMSAppView::on_band_changed(const uint32_t new_band_frequency) {
|
||||
set_target_frequency(new_band_frequency);
|
||||
}
|
||||
|
||||
void TPMSAppView::set_target_frequency(const uint32_t new_value) {
|
||||
target_frequency_ = new_value;
|
||||
radio::set_tuning_frequency(tuning_frequency());
|
||||
}
|
||||
|
||||
uint32_t TPMSAppView::target_frequency() const {
|
||||
return initial_target_frequency;
|
||||
return target_frequency_;
|
||||
}
|
||||
|
||||
uint32_t TPMSAppView::tuning_frequency() const {
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_receiver.hpp"
|
||||
#include "ui_rssi.hpp"
|
||||
#include "ui_channel.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "log_file.hpp"
|
||||
|
||||
@@ -51,6 +56,7 @@ struct TPMSRecentEntry {
|
||||
|
||||
Optional<Pressure> last_pressure;
|
||||
Optional<Temperature> last_temperature;
|
||||
Optional<tpms::Flags> last_flags;
|
||||
|
||||
TPMSRecentEntry(
|
||||
const Key& key
|
||||
@@ -70,7 +76,9 @@ using TPMSRecentEntries = RecentEntries<tpms::Reading, TPMSRecentEntry>;
|
||||
|
||||
class TPMSLogger {
|
||||
public:
|
||||
TPMSLogger(const std::string& file_path);
|
||||
Optional<File::Error> append(const std::string& filename) {
|
||||
return log_file.append(filename);
|
||||
}
|
||||
|
||||
void on_packet(const tpms::Packet& packet, const uint32_t target_frequency);
|
||||
|
||||
@@ -102,15 +110,61 @@ private:
|
||||
static constexpr uint32_t sampling_rate = 2457600;
|
||||
static constexpr uint32_t baseband_bandwidth = 1750000;
|
||||
|
||||
MessageHandlerRegistration message_handler_packet {
|
||||
Message::ID::TPMSPacket,
|
||||
[this](Message* const p) {
|
||||
const auto message = static_cast<const TPMSPacketMessage*>(p);
|
||||
const tpms::Packet packet { message->packet, message->signal_type };
|
||||
this->on_packet(packet);
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr ui::Dim header_height = 2 * 16;
|
||||
|
||||
RSSI rssi {
|
||||
{ 21 * 8, 0, 6 * 8, 4 },
|
||||
};
|
||||
|
||||
Channel channel {
|
||||
{ 21 * 8, 5, 6 * 8, 4 },
|
||||
};
|
||||
|
||||
OptionsField options_band {
|
||||
{ 0 * 8, 0 * 16 },
|
||||
3,
|
||||
{
|
||||
{ "315", 315000000 },
|
||||
{ "434", 433920000 },
|
||||
}
|
||||
};
|
||||
|
||||
RFAmpField field_rf_amp {
|
||||
{ 13 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
LNAGainField field_lna {
|
||||
{ 15 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
VGAGainField field_vga {
|
||||
{ 18 * 8, 0 * 16 }
|
||||
};
|
||||
|
||||
TPMSRecentEntries recent;
|
||||
std::unique_ptr<TPMSLogger> logger;
|
||||
|
||||
TPMSRecentEntriesView recent_entries_view { recent };
|
||||
|
||||
void on_packet(const tpms::SignalType signal_type, const tpms::Packet& packet);
|
||||
uint32_t target_frequency_ = initial_target_frequency;
|
||||
|
||||
void on_packet(const tpms::Packet& packet);
|
||||
void on_show_list();
|
||||
|
||||
void on_band_changed(const uint32_t new_band_frequency);
|
||||
|
||||
uint32_t target_frequency() const;
|
||||
void set_target_frequency(const uint32_t new_value);
|
||||
|
||||
uint32_t tuning_frequency() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,26 +21,12 @@
|
||||
|
||||
#include "ui_audio.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ui {
|
||||
|
||||
void Audio::on_show() {
|
||||
EventDispatcher::message_map().register_handler(Message::ID::AudioStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const AudioStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void Audio::on_hide() {
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::AudioStatistics);
|
||||
}
|
||||
|
||||
void Audio::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -42,15 +44,19 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
int32_t rms_db_;
|
||||
int32_t max_db_;
|
||||
|
||||
MessageHandlerRegistration message_handler_statistics {
|
||||
Message::ID::AudioStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const AudioStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
};
|
||||
|
||||
void on_statistics_update(const AudioStatistics& statistics);
|
||||
};
|
||||
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
|
||||
#include "ui_baseband_stats_view.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
@@ -41,19 +39,6 @@ BasebandStatsView::BasebandStatsView() {
|
||||
} });
|
||||
}
|
||||
|
||||
void BasebandStatsView::on_show() {
|
||||
EventDispatcher::message_map().register_handler(Message::ID::BasebandStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const BasebandStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void BasebandStatsView::on_hide() {
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::BasebandStatistics);
|
||||
}
|
||||
|
||||
|
||||
static std::string ticks_to_percent_string(const uint32_t ticks) {
|
||||
constexpr size_t decimal_digits = 1;
|
||||
constexpr size_t decimal_factor = decimal_digits * 10;
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#define __UI_BASEBAND_STATS_VIEW_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
namespace ui {
|
||||
@@ -31,15 +34,19 @@ class BasebandStatsView : public View {
|
||||
public:
|
||||
BasebandStatsView();
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
private:
|
||||
Text text_stats {
|
||||
{ 0 * 8, 0, (4 * 4 + 3) * 8, 1 * 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
MessageHandlerRegistration message_handler_stats {
|
||||
Message::ID::BasebandStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const BasebandStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
};
|
||||
|
||||
void on_statistics_update(const BasebandStatistics& statistics);
|
||||
};
|
||||
|
||||
|
||||
@@ -21,26 +21,12 @@
|
||||
|
||||
#include "ui_channel.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ui {
|
||||
|
||||
void Channel::on_show() {
|
||||
EventDispatcher::message_map().register_handler(Message::ID::ChannelStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void Channel::on_hide() {
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::ChannelStatistics);
|
||||
}
|
||||
|
||||
void Channel::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -41,14 +43,18 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
int32_t max_db_;
|
||||
|
||||
MessageHandlerRegistration message_handler_stats {
|
||||
Message::ID::ChannelStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
};
|
||||
|
||||
void on_statistics_update(const ChannelStatistics& statistics);
|
||||
};
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ SystemStatusView::SystemStatusView() {
|
||||
|
||||
button_sleep.on_select = [this](ImageButton&) {
|
||||
DisplaySleepMessage message;
|
||||
EventDispatcher::message_map().send(&message);
|
||||
EventDispatcher::send_message(message);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -123,7 +123,11 @@ void SystemStatusView::on_camera() {
|
||||
return;
|
||||
}
|
||||
|
||||
PNGWriter png { filename_stem + ".PNG" };
|
||||
PNGWriter png;
|
||||
auto create_error = png.create(filename_stem + ".PNG");
|
||||
if( create_error.is_valid() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i=0; i<320; i++) {
|
||||
std::array<ColorRGB888, 240> row;
|
||||
@@ -150,6 +154,10 @@ View* NavigationView::push_view(std::unique_ptr<View> new_view) {
|
||||
}
|
||||
|
||||
void NavigationView::pop() {
|
||||
if( view() == modal_view ) {
|
||||
modal_view = nullptr;
|
||||
}
|
||||
|
||||
// Can't pop last item from stack.
|
||||
if( view_stack.size() > 1 ) {
|
||||
free_view();
|
||||
@@ -160,6 +168,16 @@ void NavigationView::pop() {
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationView::display_modal(
|
||||
const std::string& title,
|
||||
const std::string& message
|
||||
) {
|
||||
/* If a modal view is already visible, don't display another */
|
||||
if( !modal_view ) {
|
||||
modal_view = push<ModalMessageView>(title, message);
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationView::free_view() {
|
||||
remove_child(view());
|
||||
}
|
||||
@@ -306,8 +324,8 @@ Context& SystemView::context() const {
|
||||
/* HackRFFirmwareView ****************************************************/
|
||||
|
||||
HackRFFirmwareView::HackRFFirmwareView(NavigationView& nav) {
|
||||
button_yes.on_select = [&nav](Button&){
|
||||
m4_request_shutdown();
|
||||
button_yes.on_select = [](Button&){
|
||||
EventDispatcher::request_stop();
|
||||
};
|
||||
|
||||
button_no.on_select = [&nav](Button&){
|
||||
@@ -404,4 +422,34 @@ void NotImplementedView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
/* ModalMessageView ******************************************************/
|
||||
|
||||
ModalMessageView::ModalMessageView(
|
||||
NavigationView& nav,
|
||||
const std::string& title,
|
||||
const std::string& message
|
||||
) : title_ { title }
|
||||
{
|
||||
button_done.on_select = [&nav](Button&){
|
||||
nav.pop();
|
||||
};
|
||||
|
||||
add_children({ {
|
||||
&text_message,
|
||||
&button_done,
|
||||
} });
|
||||
|
||||
text_message.set(message);
|
||||
|
||||
const int text_message_width = message.size() * 8;
|
||||
text_message.set_parent_rect({
|
||||
(240 - text_message_width) / 2, 7 * 16,
|
||||
text_message_width, 16
|
||||
});
|
||||
}
|
||||
|
||||
void ModalMessageView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -112,10 +112,13 @@ public:
|
||||
|
||||
void pop();
|
||||
|
||||
void display_modal(const std::string& title, const std::string& message);
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<View>> view_stack;
|
||||
Widget* modal_view { nullptr };
|
||||
|
||||
Widget* view() const;
|
||||
|
||||
@@ -256,6 +259,29 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
class ModalMessageView : public View {
|
||||
public:
|
||||
ModalMessageView(
|
||||
NavigationView& nav,
|
||||
const std::string& title,
|
||||
const std::string& message
|
||||
);
|
||||
|
||||
void focus() override;
|
||||
|
||||
std::string title() const override { return title_; };
|
||||
|
||||
private:
|
||||
const std::string title_;
|
||||
|
||||
Text text_message { };
|
||||
|
||||
Button button_done {
|
||||
{ 10 * 8, 13 * 16, 10 * 8, 24 },
|
||||
"OK",
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
#endif/*__UI_NAVIGATION_H__*/
|
||||
|
||||
@@ -232,7 +232,7 @@ FrequencyOptionsView::FrequencyOptionsView(
|
||||
{
|
||||
set_style(style);
|
||||
|
||||
options_step.on_change = [this](size_t n, OptionsField::value_t v) {
|
||||
field_step.on_change = [this](size_t n, OptionsField::value_t v) {
|
||||
(void)n;
|
||||
this->on_step_changed(v);
|
||||
};
|
||||
@@ -243,14 +243,14 @@ FrequencyOptionsView::FrequencyOptionsView(
|
||||
|
||||
add_children({ {
|
||||
&text_step,
|
||||
&options_step,
|
||||
&field_step,
|
||||
&field_ppm,
|
||||
&text_ppm,
|
||||
} });
|
||||
}
|
||||
|
||||
void FrequencyOptionsView::set_step(rf::Frequency f) {
|
||||
options_step.set_by_value(f);
|
||||
field_step.set_by_value(f);
|
||||
}
|
||||
|
||||
void FrequencyOptionsView::set_reference_ppm_correction(int32_t v) {
|
||||
@@ -269,6 +269,25 @@ void FrequencyOptionsView::on_reference_ppm_correction_changed(int32_t v) {
|
||||
}
|
||||
}
|
||||
|
||||
/* RFAmpField ************************************************************/
|
||||
|
||||
RFAmpField::RFAmpField(
|
||||
Point parent_pos
|
||||
) : NumberField {
|
||||
parent_pos,
|
||||
1,
|
||||
{ 0, 1 },
|
||||
1,
|
||||
' ',
|
||||
}
|
||||
{
|
||||
set_value(receiver_model.rf_amp());
|
||||
|
||||
on_change = [](int32_t v) {
|
||||
receiver_model.set_rf_amp(v);
|
||||
};
|
||||
}
|
||||
|
||||
/* RadioGainOptionsView **************************************************/
|
||||
|
||||
RadioGainOptionsView::RadioGainOptionsView(
|
||||
@@ -282,20 +301,6 @@ RadioGainOptionsView::RadioGainOptionsView(
|
||||
&label_rf_amp,
|
||||
&field_rf_amp,
|
||||
} });
|
||||
|
||||
field_rf_amp.on_change = [this](int32_t v) {
|
||||
this->on_rf_amp_changed(v);
|
||||
};
|
||||
}
|
||||
|
||||
void RadioGainOptionsView::set_rf_amp(int32_t v_db) {
|
||||
field_rf_amp.set_value(v_db);
|
||||
}
|
||||
|
||||
void RadioGainOptionsView::on_rf_amp_changed(bool enable) {
|
||||
if( on_change_rf_amp ) {
|
||||
on_change_rf_amp(enable);
|
||||
}
|
||||
}
|
||||
|
||||
/* LNAGainField **********************************************************/
|
||||
@@ -309,6 +314,11 @@ LNAGainField::LNAGainField(
|
||||
' ',
|
||||
}
|
||||
{
|
||||
set_value(receiver_model.lna());
|
||||
|
||||
on_change = [](int32_t v) {
|
||||
receiver_model.set_lna(v);
|
||||
};
|
||||
}
|
||||
|
||||
void LNAGainField::on_focus() {
|
||||
@@ -329,6 +339,11 @@ VGAGainField::VGAGainField(
|
||||
' ',
|
||||
}
|
||||
{
|
||||
set_value(receiver_model.vga());
|
||||
|
||||
on_change = [](int32_t v) {
|
||||
receiver_model.set_vga(v);
|
||||
};
|
||||
}
|
||||
|
||||
void VGAGainField::on_focus() {
|
||||
|
||||
@@ -229,6 +229,32 @@ private:
|
||||
void update_text();
|
||||
};
|
||||
|
||||
class FrequencyStepView : public OptionsField {
|
||||
public:
|
||||
FrequencyStepView(
|
||||
Point parent_pos
|
||||
) : OptionsField {
|
||||
parent_pos,
|
||||
5,
|
||||
{
|
||||
{ " 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 },
|
||||
{ "100k ", 100000 },
|
||||
{ " 1M ", 1000000 },
|
||||
{ " 10M ", 10000000 },
|
||||
}
|
||||
}
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class FrequencyOptionsView : public View {
|
||||
public:
|
||||
std::function<void(rf::Frequency)> on_change_step;
|
||||
@@ -245,23 +271,8 @@ private:
|
||||
"Step"
|
||||
};
|
||||
|
||||
OptionsField options_step {
|
||||
FrequencyStepView field_step {
|
||||
{ 5 * 8, 0 * 16 },
|
||||
5,
|
||||
{
|
||||
{ " 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 },
|
||||
{ "100k ", 100000 },
|
||||
{ " 1M ", 1000000 },
|
||||
{ " 10M ", 10000000 },
|
||||
}
|
||||
};
|
||||
|
||||
void on_step_changed(rf::Frequency v);
|
||||
@@ -281,29 +292,24 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
class RFAmpField : public NumberField {
|
||||
public:
|
||||
RFAmpField(Point parent_pos);
|
||||
};
|
||||
|
||||
class RadioGainOptionsView : public View {
|
||||
public:
|
||||
std::function<void(bool)> on_change_rf_amp;
|
||||
|
||||
RadioGainOptionsView(const Rect parent_rect, const Style* const style);
|
||||
|
||||
void set_rf_amp(int32_t v_db);
|
||||
|
||||
private:
|
||||
Text label_rf_amp {
|
||||
{ 0 * 8, 0 * 16, 3 * 8, 1 * 16 },
|
||||
"Amp"
|
||||
};
|
||||
|
||||
NumberField field_rf_amp {
|
||||
RFAmpField field_rf_amp {
|
||||
{ 4 * 8, 0 * 16},
|
||||
1,
|
||||
{ 0, 1 },
|
||||
1,
|
||||
' ',
|
||||
};
|
||||
|
||||
void on_rf_amp_changed(bool enable);
|
||||
};
|
||||
|
||||
class LNAGainField : public NumberField {
|
||||
|
||||
@@ -32,43 +32,61 @@ using namespace portapack;
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class RawFileWriter : public Writer {
|
||||
class FileWriter : public Writer {
|
||||
public:
|
||||
RawFileWriter(
|
||||
const std::string& filename
|
||||
) : file { filename, File::openmode::out | File::openmode::binary | File::openmode::trunc }
|
||||
{
|
||||
FileWriter() = default;
|
||||
|
||||
FileWriter(const FileWriter&) = delete;
|
||||
FileWriter& operator=(const FileWriter&) = delete;
|
||||
FileWriter(FileWriter&& file) = delete;
|
||||
FileWriter& operator=(FileWriter&&) = delete;
|
||||
|
||||
Optional<File::Error> create(const std::string& filename) {
|
||||
return file.create(filename);
|
||||
}
|
||||
|
||||
bool write(const void* const buffer, const size_t bytes) override {
|
||||
return file.write(buffer, bytes);
|
||||
File::Result<size_t> write(const void* const buffer, const size_t bytes) override {
|
||||
auto write_result = file.write(buffer, bytes) ;
|
||||
if( write_result.is_ok() ) {
|
||||
bytes_written += write_result.value();
|
||||
}
|
||||
return write_result;
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
File file;
|
||||
uint64_t bytes_written { 0 };
|
||||
};
|
||||
|
||||
class WAVFileWriter : public Writer {
|
||||
using RawFileWriter = FileWriter;
|
||||
|
||||
class WAVFileWriter : public FileWriter {
|
||||
public:
|
||||
WAVFileWriter(
|
||||
const std::string& filename,
|
||||
size_t sampling_rate
|
||||
) : file { filename, File::openmode::out | File::openmode::binary | File::openmode::trunc },
|
||||
header { sampling_rate }
|
||||
) : header { sampling_rate }
|
||||
{
|
||||
update_header();
|
||||
}
|
||||
|
||||
|
||||
WAVFileWriter(const WAVFileWriter&) = delete;
|
||||
WAVFileWriter& operator=(const WAVFileWriter&) = delete;
|
||||
WAVFileWriter(WAVFileWriter&&) = delete;
|
||||
WAVFileWriter& operator=(WAVFileWriter&&) = delete;
|
||||
|
||||
~WAVFileWriter() {
|
||||
update_header();
|
||||
}
|
||||
|
||||
bool write(const void* const buffer, const size_t bytes) override {
|
||||
const auto success = file.write(buffer, bytes) ;
|
||||
if( success ) {
|
||||
bytes_written += bytes;
|
||||
Optional<File::Error> create(
|
||||
const std::string& filename
|
||||
) {
|
||||
const auto create_error = FileWriter::create(filename);
|
||||
if( create_error.is_valid() ) {
|
||||
return create_error;
|
||||
} else {
|
||||
return update_header();
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -121,15 +139,24 @@ private:
|
||||
data_t data;
|
||||
};
|
||||
|
||||
File file;
|
||||
header_t header;
|
||||
uint64_t bytes_written { 0 };
|
||||
|
||||
void update_header() {
|
||||
Optional<File::Error> update_header() {
|
||||
header.set_data_size(bytes_written);
|
||||
const auto old_position = file.seek(0);
|
||||
file.write(&header, sizeof(header));
|
||||
file.seek(old_position);
|
||||
const auto seek_0_result = file.seek(0);
|
||||
if( seek_0_result.is_error() ) {
|
||||
return seek_0_result.error();
|
||||
}
|
||||
const auto old_position = seek_0_result.value();
|
||||
const auto write_result = file.write(&header, sizeof(header));
|
||||
if( write_result.is_error() ) {
|
||||
return write_result.error();
|
||||
}
|
||||
const auto seek_old_result = file.seek(old_position);
|
||||
if( seek_old_result.is_error() ) {
|
||||
return seek_old_result.error();
|
||||
}
|
||||
return { };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,20 +166,24 @@ RecordView::RecordView(
|
||||
const Rect parent_rect,
|
||||
std::string filename_stem_pattern,
|
||||
const FileType file_type,
|
||||
const size_t buffer_size_k,
|
||||
const size_t buffer_count_k
|
||||
const size_t write_size,
|
||||
const size_t buffer_count
|
||||
) : View { parent_rect },
|
||||
filename_stem_pattern { filename_stem_pattern },
|
||||
file_type { file_type },
|
||||
buffer_size_k { buffer_size_k },
|
||||
buffer_count_k { buffer_count_k }
|
||||
write_size { write_size },
|
||||
buffer_count { buffer_count }
|
||||
{
|
||||
add_children({ {
|
||||
&rect_background,
|
||||
&button_record,
|
||||
&text_record_filename,
|
||||
&text_record_dropped,
|
||||
&text_time_available,
|
||||
} });
|
||||
|
||||
rect_background.set_parent_rect({ { 0, 0 }, size() });
|
||||
|
||||
button_record.on_select = [this](ImageButton&) {
|
||||
this->toggle();
|
||||
};
|
||||
@@ -170,6 +201,21 @@ void RecordView::focus() {
|
||||
button_record.focus();
|
||||
}
|
||||
|
||||
void RecordView::set_sampling_rate(const size_t new_sampling_rate) {
|
||||
if( new_sampling_rate != sampling_rate ) {
|
||||
stop();
|
||||
sampling_rate = new_sampling_rate;
|
||||
|
||||
button_record.hidden(sampling_rate == 0);
|
||||
text_record_filename.hidden(sampling_rate == 0);
|
||||
text_record_dropped.hidden(sampling_rate == 0);
|
||||
text_time_available.hidden(sampling_rate == 0);
|
||||
rect_background.hidden(sampling_rate != 0);
|
||||
|
||||
update_status_display();
|
||||
}
|
||||
}
|
||||
|
||||
bool RecordView::is_active() const {
|
||||
return (bool)capture_thread;
|
||||
}
|
||||
@@ -200,17 +246,39 @@ void RecordView::start() {
|
||||
std::unique_ptr<Writer> writer;
|
||||
switch(file_type) {
|
||||
case FileType::WAV:
|
||||
writer = std::make_unique<WAVFileWriter>(
|
||||
filename_stem + ".WAV",
|
||||
sampling_rate
|
||||
);
|
||||
{
|
||||
auto p = std::make_unique<WAVFileWriter>(
|
||||
sampling_rate
|
||||
);
|
||||
auto create_error = p->create(
|
||||
filename_stem + ".WAV"
|
||||
);
|
||||
if( create_error.is_valid() ) {
|
||||
handle_error(create_error.value());
|
||||
} else {
|
||||
writer = std::move(p);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FileType::RawS16:
|
||||
write_metadata_file(filename_stem + ".TXT");
|
||||
writer = std::make_unique<RawFileWriter>(
|
||||
filename_stem + ".C16"
|
||||
);
|
||||
{
|
||||
const auto metadata_file_error = write_metadata_file(filename_stem + ".TXT");
|
||||
if( metadata_file_error.is_valid() ) {
|
||||
handle_error(metadata_file_error.value());
|
||||
return;
|
||||
}
|
||||
|
||||
auto p = std::make_unique<RawFileWriter>();
|
||||
auto create_error = p->create(
|
||||
filename_stem + ".C16"
|
||||
);
|
||||
if( create_error.is_valid() ) {
|
||||
handle_error(create_error.value());
|
||||
} else {
|
||||
writer = std::move(p);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -222,9 +290,19 @@ void RecordView::start() {
|
||||
button_record.set_bitmap(&bitmap_stop);
|
||||
capture_thread = std::make_unique<CaptureThread>(
|
||||
std::move(writer),
|
||||
buffer_size_k, buffer_count_k
|
||||
write_size, buffer_count,
|
||||
[]() {
|
||||
CaptureThreadDoneMessage message { };
|
||||
EventDispatcher::send_message(message);
|
||||
},
|
||||
[](File::Error error) {
|
||||
CaptureThreadDoneMessage message { error.code() };
|
||||
EventDispatcher::send_message(message);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
update_status_display();
|
||||
}
|
||||
|
||||
void RecordView::stop() {
|
||||
@@ -232,20 +310,66 @@ void RecordView::stop() {
|
||||
capture_thread.reset();
|
||||
button_record.set_bitmap(&bitmap_record);
|
||||
}
|
||||
|
||||
update_status_display();
|
||||
}
|
||||
|
||||
void RecordView::write_metadata_file(const std::string& filename) {
|
||||
File file { filename, File::openmode::out | File::openmode::trunc };
|
||||
file.puts("sample_rate=" + to_string_dec_uint(sampling_rate) + "\n");
|
||||
file.puts("center_frequency=" + to_string_dec_uint(receiver_model.tuning_frequency()) + "\n");
|
||||
Optional<File::Error> RecordView::write_metadata_file(const std::string& filename) {
|
||||
File file;
|
||||
const auto create_error = file.create(filename);
|
||||
if( create_error.is_valid() ) {
|
||||
return create_error;
|
||||
} else {
|
||||
const auto error_line1 = file.write_line("sample_rate=" + to_string_dec_uint(sampling_rate));
|
||||
if( error_line1.is_valid() ) {
|
||||
return error_line1;
|
||||
}
|
||||
const auto error_line2 = file.write_line("center_frequency=" + to_string_dec_uint(receiver_model.tuning_frequency()));
|
||||
if( error_line2.is_valid() ) {
|
||||
return error_line2;
|
||||
}
|
||||
return { };
|
||||
}
|
||||
}
|
||||
|
||||
void RecordView::on_tick_second() {
|
||||
update_status_display();
|
||||
}
|
||||
|
||||
void RecordView::update_status_display() {
|
||||
if( is_active() ) {
|
||||
const auto dropped_percent = std::min(99U, capture_thread->state().dropped_percent());
|
||||
const auto s = to_string_dec_uint(dropped_percent, 2, ' ') + "\%";
|
||||
text_record_dropped.set(s);
|
||||
}
|
||||
|
||||
if( sampling_rate ) {
|
||||
const auto space_info = std::filesystem::space("");
|
||||
const uint32_t bytes_per_second = file_type == FileType::WAV ? (sampling_rate * 2) : (sampling_rate * 4);
|
||||
const uint32_t available_seconds = space_info.free / bytes_per_second;
|
||||
const uint32_t seconds = available_seconds % 60;
|
||||
const uint32_t available_minutes = available_seconds / 60;
|
||||
const uint32_t minutes = available_minutes % 60;
|
||||
const uint32_t hours = available_minutes / 60;
|
||||
const std::string available_time =
|
||||
to_string_dec_uint(hours, 3, ' ') + ":" +
|
||||
to_string_dec_uint(minutes, 2, '0') + ":" +
|
||||
to_string_dec_uint(seconds, 2, '0');
|
||||
text_time_available.set(available_time);
|
||||
}
|
||||
}
|
||||
|
||||
void RecordView::handle_capture_thread_done(const File::Error error) {
|
||||
stop();
|
||||
if( error.code() ) {
|
||||
handle_error(error);
|
||||
}
|
||||
}
|
||||
|
||||
void RecordView::handle_error(const File::Error error) {
|
||||
if( on_error ) {
|
||||
on_error(error.what());
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace ui {
|
||||
|
||||
class RecordView : public View {
|
||||
public:
|
||||
std::function<void(std::string)> on_error;
|
||||
|
||||
enum FileType {
|
||||
RawS16 = 2,
|
||||
WAV = 3,
|
||||
@@ -46,19 +48,14 @@ public:
|
||||
const Rect parent_rect,
|
||||
std::string filename_stem_pattern,
|
||||
FileType file_type,
|
||||
const size_t buffer_size_k,
|
||||
const size_t buffer_count_k
|
||||
const size_t write_size,
|
||||
const size_t buffer_count
|
||||
);
|
||||
~RecordView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
void set_sampling_rate(const size_t new_sampling_rate) {
|
||||
if( new_sampling_rate != sampling_rate ) {
|
||||
stop();
|
||||
sampling_rate = new_sampling_rate;
|
||||
}
|
||||
}
|
||||
void set_sampling_rate(const size_t new_sampling_rate);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
@@ -67,17 +64,25 @@ public:
|
||||
|
||||
private:
|
||||
void toggle();
|
||||
void write_metadata_file(const std::string& filename);
|
||||
Optional<File::Error> write_metadata_file(const std::string& filename);
|
||||
|
||||
void on_tick_second();
|
||||
void update_status_display();
|
||||
|
||||
void handle_capture_thread_done(const File::Error error);
|
||||
void handle_error(const File::Error error);
|
||||
|
||||
const std::string filename_stem_pattern;
|
||||
const FileType file_type;
|
||||
const size_t buffer_size_k;
|
||||
const size_t buffer_count_k;
|
||||
const size_t write_size;
|
||||
const size_t buffer_count;
|
||||
size_t sampling_rate { 0 };
|
||||
SignalToken signal_token_tick_second;
|
||||
|
||||
Rectangle rect_background {
|
||||
Color::black()
|
||||
};
|
||||
|
||||
ImageButton button_record {
|
||||
{ 0 * 8, 0 * 16, 2 * 8, 1 * 16 },
|
||||
&bitmap_record,
|
||||
@@ -95,7 +100,20 @@ private:
|
||||
"",
|
||||
};
|
||||
|
||||
Text text_time_available {
|
||||
{ 21 * 8, 0 * 16, 9 * 8, 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
std::unique_ptr<CaptureThread> capture_thread;
|
||||
|
||||
MessageHandlerRegistration message_handler_capture_thread_error {
|
||||
Message::ID::CaptureThreadDone,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const CaptureThreadDoneMessage*>(p);
|
||||
this->handle_capture_thread_done(message.error);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
} /* namespace ui */
|
||||
|
||||
@@ -21,26 +21,12 @@
|
||||
|
||||
#include "ui_rssi.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ui {
|
||||
|
||||
void RSSI::on_show() {
|
||||
EventDispatcher::message_map().register_handler(Message::ID::RSSIStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const RSSIStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void RSSI::on_hide() {
|
||||
EventDispatcher::message_map().unregister_handler(Message::ID::RSSIStatistics);
|
||||
}
|
||||
|
||||
void RSSI::paint(Painter& painter) {
|
||||
const auto r = screen_rect();
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -43,9 +45,6 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
void on_show() override;
|
||||
void on_hide() override;
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
|
||||
private:
|
||||
@@ -53,6 +52,13 @@ private:
|
||||
int32_t avg_;
|
||||
int32_t max_;
|
||||
|
||||
MessageHandlerRegistration message_handler_stats {
|
||||
Message::ID::RSSIStatistics,
|
||||
[this](const Message* const p) {
|
||||
this->on_statistics_update(static_cast<const RSSIStatisticsMessage*>(p)->statistics);
|
||||
}
|
||||
};
|
||||
|
||||
void on_statistics_update(const RSSIStatistics& statistics);
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "lfsr_random.hpp"
|
||||
|
||||
#include "ff.h"
|
||||
#include "diskio.h"
|
||||
|
||||
#include "ch.h"
|
||||
#include "hal.h"
|
||||
@@ -133,22 +134,24 @@ private:
|
||||
return Result::FailHeap;
|
||||
}
|
||||
|
||||
File file { filename, File::openmode::out | File::openmode::binary | File::openmode::trunc };
|
||||
if( !file.is_open() ) {
|
||||
File file;
|
||||
auto file_create_error = file.create(filename);
|
||||
if( file_create_error.is_valid() ) {
|
||||
return Result::FailFileOpenWrite;
|
||||
}
|
||||
|
||||
lfsr_word_t v = 1;
|
||||
|
||||
const halrtcnt_t test_start = halGetCounterValue();
|
||||
while( !chThdShouldTerminate() && file.is_open() && (_stats.write_bytes < bytes_to_write) ) {
|
||||
while( !chThdShouldTerminate() && (_stats.write_bytes < bytes_to_write) ) {
|
||||
lfsr_fill(v,
|
||||
reinterpret_cast<lfsr_word_t*>(buffer->data()),
|
||||
sizeof(*buffer.get()) / sizeof(lfsr_word_t)
|
||||
);
|
||||
|
||||
const halrtcnt_t write_start = halGetCounterValue();
|
||||
if( !file.write(buffer->data(), buffer->size()) ) {
|
||||
const auto result_write = file.write(buffer->data(), buffer->size());
|
||||
if( result_write.is_error() ) {
|
||||
break;
|
||||
}
|
||||
const halrtcnt_t write_end = halGetCounterValue();
|
||||
@@ -178,17 +181,19 @@ private:
|
||||
return Result::FailHeap;
|
||||
}
|
||||
|
||||
File file { filename, File::openmode::in | File::openmode::binary };
|
||||
if( !file.is_open() ) {
|
||||
File file;
|
||||
auto file_open_error = file.open(filename);
|
||||
if( file_open_error.is_valid() ) {
|
||||
return Result::FailFileOpenRead;
|
||||
}
|
||||
|
||||
lfsr_word_t v = 1;
|
||||
|
||||
const halrtcnt_t test_start = halGetCounterValue();
|
||||
while( !chThdShouldTerminate() && file.is_open() && (_stats.read_bytes < bytes_to_read) ) {
|
||||
while( !chThdShouldTerminate() && (_stats.read_bytes < bytes_to_read) ) {
|
||||
const halrtcnt_t read_start = halGetCounterValue();
|
||||
if( !file.read(buffer->data(), buffer->size()) ) {
|
||||
const auto result_read = file.read(buffer->data(), buffer->size());
|
||||
if( result_read.is_error() ) {
|
||||
break;
|
||||
}
|
||||
const halrtcnt_t read_end = halGetCounterValue();
|
||||
@@ -227,14 +232,15 @@ namespace ui {
|
||||
SDCardDebugView::SDCardDebugView(NavigationView& nav) {
|
||||
add_children({ {
|
||||
&text_title,
|
||||
&text_detected_title,
|
||||
&text_detected_value,
|
||||
&text_csd_title,
|
||||
&text_csd_value_3,
|
||||
&text_csd_value_2,
|
||||
&text_csd_value_1,
|
||||
&text_csd_value_0,
|
||||
&text_bus_width_title,
|
||||
&text_bus_width_value,
|
||||
&text_card_mode_title,
|
||||
&text_card_mode_value,
|
||||
// &text_csd_title,
|
||||
// &text_csd_value,
|
||||
&text_card_type_title,
|
||||
&text_card_type_value,
|
||||
&text_block_size_title,
|
||||
&text_block_size_value,
|
||||
&text_block_count_title,
|
||||
@@ -272,10 +278,33 @@ void SDCardDebugView::focus() {
|
||||
button_ok.focus();
|
||||
}
|
||||
|
||||
static std::string format_3dot3_string(const uint32_t value_in_thousandths) {
|
||||
if( value_in_thousandths < 1000000U ) {
|
||||
const uint32_t thousandths_part = value_in_thousandths % 1000;
|
||||
const uint32_t integer_part = value_in_thousandths / 1000U;
|
||||
return to_string_dec_uint(integer_part, 3) + "." + to_string_dec_uint(thousandths_part, 3, '0');
|
||||
} else {
|
||||
return "HHH.HHH";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string format_bytes_size_string(uint64_t value) {
|
||||
static const std::array<char, 5> suffix { { ' ', 'K', 'M', 'G', 'T' } };
|
||||
size_t suffix_index = 1;
|
||||
while( (value >= 1000000U) && (suffix_index < suffix.size()) ) {
|
||||
value /= 1000U;
|
||||
suffix_index++;
|
||||
}
|
||||
return format_3dot3_string(value) + " " + suffix[suffix_index] + "B";
|
||||
}
|
||||
|
||||
void SDCardDebugView::on_status(const sd_card::Status) {
|
||||
text_bus_width_value.set("");
|
||||
text_card_mode_value.set("");
|
||||
// text_csd_value.set("");
|
||||
text_card_type_value.set("");
|
||||
text_csd_value_0.set("");
|
||||
text_csd_value_1.set("");
|
||||
text_csd_value_2.set("");
|
||||
text_csd_value_3.set("");
|
||||
text_block_size_value.set("");
|
||||
text_block_count_value.set("");
|
||||
text_capacity_value.set("");
|
||||
@@ -285,8 +314,6 @@ void SDCardDebugView::on_status(const sd_card::Status) {
|
||||
text_test_read_rate_value.set("");
|
||||
|
||||
const bool is_inserted = sdcIsCardInserted(&SDCD1);
|
||||
text_detected_value.set(is_inserted ? "Yes" : " No");
|
||||
|
||||
if( is_inserted ) {
|
||||
const auto card_width_flags = LPC_SDMMC->CTYPE & 0x10001;
|
||||
size_t card_width = 0;
|
||||
@@ -298,56 +325,50 @@ void SDCardDebugView::on_status(const sd_card::Status) {
|
||||
}
|
||||
|
||||
text_bus_width_value.set(card_width ? to_string_dec_uint(card_width, 1) : "X");
|
||||
text_card_mode_value.set("0x" + to_string_hex(SDCD1.cardmode, 8));
|
||||
// text_csd_value.set("0x" + to_string_hex(SDCD1.csd, 8));
|
||||
|
||||
// TODO: Implement Text class right-justify!
|
||||
BYTE card_type;
|
||||
disk_ioctl(0, MMC_GET_TYPE, &card_type);
|
||||
|
||||
std::string formatted_card_type;
|
||||
switch(card_type & SDC_MODE_CARDTYPE_MASK) {
|
||||
case SDC_MODE_CARDTYPE_SDV11: formatted_card_type = "SD V1.1"; break;
|
||||
case SDC_MODE_CARDTYPE_SDV20: formatted_card_type = "SD V2.0"; break;
|
||||
case SDC_MODE_CARDTYPE_MMC: formatted_card_type = "MMC"; break;
|
||||
default: formatted_card_type = "???"; break;
|
||||
}
|
||||
|
||||
if( card_type & SDC_MODE_HIGH_CAPACITY ) {
|
||||
formatted_card_type += ", SDHC";
|
||||
}
|
||||
text_card_type_value.set(formatted_card_type);
|
||||
|
||||
std::array<uint32_t, 4> csd;
|
||||
disk_ioctl(0, MMC_GET_CSD, csd.data());
|
||||
text_csd_value_3.set(to_string_hex(csd[3], 8));
|
||||
text_csd_value_2.set(to_string_hex(csd[2], 8));
|
||||
text_csd_value_1.set(to_string_hex(csd[1], 8));
|
||||
text_csd_value_0.set(to_string_hex(csd[0], 8));
|
||||
|
||||
BlockDeviceInfo block_device_info;
|
||||
if( sdcGetInfo(&SDCD1, &block_device_info) == CH_SUCCESS ) {
|
||||
text_block_size_value.set(to_string_dec_uint(block_device_info.blk_size, 5));
|
||||
text_block_count_value.set(to_string_dec_uint(block_device_info.blk_num, 9));
|
||||
const uint64_t capacity = block_device_info.blk_size * uint64_t(block_device_info.blk_num);
|
||||
if( capacity >= 1000000000 ) {
|
||||
const uint32_t capacity_mb = capacity / 1000000U;
|
||||
const uint32_t fraction_gb = capacity_mb % 1000;
|
||||
const uint32_t capacity_gb = capacity_mb / 1000U;
|
||||
text_capacity_value.set(
|
||||
to_string_dec_uint(capacity_gb, 3) + "." +
|
||||
to_string_dec_uint(fraction_gb, 3, '0') + " GB"
|
||||
);
|
||||
} else {
|
||||
const uint32_t capacity_kb = capacity / 1000U;
|
||||
const uint32_t fraction_mb = capacity_kb % 1000;
|
||||
const uint32_t capacity_mb = capacity_kb / 1000U;
|
||||
text_capacity_value.set(
|
||||
to_string_dec_uint(capacity_mb, 3) + "." +
|
||||
to_string_dec_uint(fraction_mb, 3, '0') + " MB"
|
||||
);
|
||||
}
|
||||
text_capacity_value.set(format_bytes_size_string(capacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string format_ticks_as_ms(const halrtcnt_t value) {
|
||||
const uint32_t us = uint64_t(value) * 1000000U / halGetCounterFrequency();
|
||||
const uint32_t ms_frac = us % 1000U;
|
||||
const uint32_t ms_int = us / 1000U;
|
||||
if( ms_int < 1000 ) {
|
||||
return to_string_dec_uint(ms_int, 3) + "." + to_string_dec_uint(ms_frac, 3, '0');
|
||||
} else {
|
||||
return "HHH.HHH";
|
||||
}
|
||||
return format_3dot3_string(us);
|
||||
}
|
||||
|
||||
static std::string format_bytes_per_ticks_as_mib(const size_t bytes, const halrtcnt_t ticks) {
|
||||
const uint32_t bps = uint64_t(bytes) * halGetCounterFrequency() / ticks;
|
||||
const uint32_t kbps = bps / 1000U;
|
||||
const uint32_t mbps_frac = kbps % 1000U;
|
||||
const uint32_t mbps_int = kbps / 1000U;
|
||||
if( mbps_int < 1000 ) {
|
||||
return to_string_dec_uint(mbps_int, 3) + "." + to_string_dec_uint(mbps_frac, 3, '0');
|
||||
} else {
|
||||
return "HHH.HHH";
|
||||
}
|
||||
return format_3dot3_string(kbps);
|
||||
}
|
||||
|
||||
void SDCardDebugView::on_test() {
|
||||
|
||||
@@ -49,15 +49,28 @@ private:
|
||||
"SD Card",
|
||||
};
|
||||
|
||||
static constexpr size_t detected_characters = 3;
|
||||
|
||||
Text text_detected_title {
|
||||
Text text_csd_title {
|
||||
{ 0, 3 * 16, (8 * 8), 16 },
|
||||
"Detected",
|
||||
"CSD",
|
||||
};
|
||||
|
||||
Text text_detected_value {
|
||||
{ 240 - (detected_characters * 8), 3 * 16, (detected_characters * 8), 16 },
|
||||
Text text_csd_value_3 {
|
||||
{ 240 - ((8 + 1 + 8) * 8), 3 * 16, (8 * 8), 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
Text text_csd_value_2 {
|
||||
{ 240 - (8 * 8), 3 * 16, (8 * 8), 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
Text text_csd_value_1 {
|
||||
{ 240 - ((8 + 1 + 8) * 8), 4 * 16, (8 * 8), 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
Text text_csd_value_0 {
|
||||
{ 240 - (8 * 8), 4 * 16, (8 * 8), 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
@@ -73,30 +86,18 @@ private:
|
||||
"",
|
||||
};
|
||||
|
||||
static constexpr size_t card_mode_characters = 10;
|
||||
static constexpr size_t card_type_characters = 13;
|
||||
|
||||
Text text_card_mode_title {
|
||||
Text text_card_type_title {
|
||||
{ 0, 6 * 16, (9 * 8), 16 },
|
||||
"Card mode",
|
||||
"Card type",
|
||||
};
|
||||
|
||||
Text text_card_mode_value {
|
||||
{ 240 - (card_mode_characters * 8), 6 * 16, (card_mode_characters * 8), 16 },
|
||||
Text text_card_type_value {
|
||||
{ 240 - (card_type_characters * 8), 6 * 16, (card_type_characters * 8), 16 },
|
||||
"",
|
||||
};
|
||||
|
||||
// static constexpr size_t csd_characters = 10;
|
||||
|
||||
// Text text_csd_title {
|
||||
// { 0, 7 * 16, (3 * 8), 16 },
|
||||
// "CSD",
|
||||
// };
|
||||
|
||||
// Text text_csd_value {
|
||||
// { 240 - (csd_characters * 8), 7 * 16, (csd_characters * 8), 16 },
|
||||
// "",
|
||||
// };
|
||||
|
||||
static constexpr size_t block_size_characters = 5;
|
||||
|
||||
Text text_block_size_title {
|
||||
|
||||
@@ -20,16 +20,15 @@
|
||||
*/
|
||||
|
||||
#include "ui_setup.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "ui_font_fixed_8x16.hpp"
|
||||
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
using namespace lpc43xx;
|
||||
|
||||
#include "portapack.hpp"
|
||||
using portapack::receiver_model;
|
||||
using namespace portapack;
|
||||
|
||||
#include "cpld_update.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -170,15 +169,26 @@ void AntennaBiasSetupView::focus() {
|
||||
button_done.focus();
|
||||
}
|
||||
|
||||
SetTouchCalibView::SetTouchCalibView(NavigationView& nav) {
|
||||
add_children({{
|
||||
AboutView::AboutView(NavigationView& nav) {
|
||||
add_children({ {
|
||||
&text_title,
|
||||
&text_debugx,
|
||||
&text_debugy,
|
||||
&button_ok
|
||||
}});
|
||||
&text_firmware,
|
||||
&text_cpld_hackrf,
|
||||
&text_cpld_hackrf_status,
|
||||
&button_ok,
|
||||
} });
|
||||
|
||||
button_ok.on_select = [&nav](Button&){ nav.pop(); };
|
||||
|
||||
if( cpld_hackrf_verify_eeprom() ) {
|
||||
text_cpld_hackrf_status.set(" OK");
|
||||
} else {
|
||||
text_cpld_hackrf_status.set("BAD");
|
||||
}
|
||||
}
|
||||
|
||||
void AboutView::focus() {
|
||||
button_ok.focus();
|
||||
}
|
||||
|
||||
void SetTouchCalibView::focus() {
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_menu.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ff.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -247,6 +246,39 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
class AboutView : public View {
|
||||
public:
|
||||
AboutView(NavigationView& nav);
|
||||
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
Text text_title {
|
||||
{ 100, 96, 40, 16 },
|
||||
"About",
|
||||
};
|
||||
|
||||
Text text_firmware {
|
||||
{ 0, 128, 240, 16 },
|
||||
"Git Commit Hash " GIT_REVISION,
|
||||
};
|
||||
|
||||
Text text_cpld_hackrf {
|
||||
{ 0, 144, 11*8, 16 },
|
||||
"HackRF CPLD",
|
||||
};
|
||||
|
||||
Text text_cpld_hackrf_status {
|
||||
{ 240 - 3*8, 144, 3*8, 16 },
|
||||
"???"
|
||||
};
|
||||
|
||||
Button button_ok {
|
||||
{ 72, 192, 96, 24 },
|
||||
"OK"
|
||||
};
|
||||
};
|
||||
|
||||
class SetUIView : public View {
|
||||
public:
|
||||
SetUIView(NavigationView& nav);
|
||||
|
||||
@@ -21,8 +21,6 @@
|
||||
|
||||
#include "ui_spectrum.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "spectrum_color_lut.hpp"
|
||||
|
||||
#include "portapack.hpp"
|
||||
@@ -236,31 +234,11 @@ WaterfallWidget::WaterfallWidget() {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
baseband::spectrum_streaming_start();
|
||||
}
|
||||
|
||||
void WaterfallWidget::on_hide() {
|
||||
baseband::spectrum_streaming_stop();
|
||||
|
||||
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) {
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
#include "event_m0.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -86,6 +88,25 @@ private:
|
||||
FrequencyScale frequency_scale;
|
||||
ChannelSpectrumFIFO* fifo { nullptr };
|
||||
|
||||
MessageHandlerRegistration message_handler_spectrum_config {
|
||||
Message::ID::ChannelSpectrumConfig,
|
||||
[this](const Message* const p) {
|
||||
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
|
||||
this->fifo = message.fifo;
|
||||
}
|
||||
};
|
||||
MessageHandlerRegistration message_handler_frame_sync {
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
if( this->fifo ) {
|
||||
ChannelSpectrum channel_spectrum;
|
||||
while( fifo->out(channel_spectrum) ) {
|
||||
this->on_channel_spectrum(channel_spectrum);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void on_channel_spectrum(const ChannelSpectrum& spectrum);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user