diff --git a/.travis.yml b/.travis.yml index 6a6c0b9f..53af9c24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,9 @@ notifications: - "Firmware download : https://portapack-h1-builds.s3.amazonaws.com/%{repository_slug}/%{build_number}/%{build_number}.1/firmware/portapack-h1-firmware-%{commit}.tar.bz2" before_script: - - wget https://launchpad.net/gcc-arm-embedded/5.0/5-2015-q4-major/+download/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 -O /tmp/gcc-arm.tar.bz2 + - wget https://launchpad.net/gcc-arm-embedded/5.0/5-2016-q1-update/+download/gcc-arm-none-eabi-5_3-2016q1-20160330-linux.tar.bz2 -O /tmp/gcc-arm.tar.bz2 - tar -xf /tmp/gcc-arm.tar.bz2 - - export PATH=$PWD/gcc-arm-none-eabi-5_2-2015q4/bin:$PATH + - export PATH=$PWD/gcc-arm-none-eabi-5_3-2016q1/bin:$PATH - export CC="arm-none-eabi-gcc" - export CXX="arm-none-eabi-g++" diff --git a/firmware/application/Makefile b/firmware/application/Makefile index caef1a99..d62a6588 100755 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -175,6 +175,7 @@ CPPSRC = main.cpp \ ui_sd_card_debug.cpp \ ui_console.cpp \ ui_receiver.cpp \ + ui_record_view.cpp \ ui_spectrum.cpp \ ui_loadmodule.cpp \ ui_afskrx.cpp \ @@ -196,10 +197,11 @@ CPPSRC = main.cpp \ ../common/ert_packet.cpp \ capture_app.cpp \ sd_card.cpp \ + time.cpp \ file.cpp \ log_file.cpp \ png_writer.cpp \ - audio_thread.cpp \ + capture_thread.cpp \ manchester.cpp \ string_format.cpp \ temperature_logger.cpp \ diff --git a/firmware/application/ais_app.cpp b/firmware/application/ais_app.cpp index 7fde92a4..a6508c91 100644 --- a/firmware/application/ais_app.cpp +++ b/firmware/application/ais_app.cpp @@ -136,7 +136,7 @@ AISLogger::AISLogger( void AISLogger::on_packet(const ais::Packet& packet) { // TODO: Unstuff here, not in baseband! - if( log_file.is_ready() ) { + if( log_file.is_open() ) { std::string entry; entry.reserve((packet.length() + 3) / 4); diff --git a/firmware/application/analog_audio_app.cpp b/firmware/application/analog_audio_app.cpp index 5e4593b4..948e6d5a 100644 --- a/firmware/application/analog_audio_app.cpp +++ b/firmware/application/analog_audio_app.cpp @@ -30,6 +30,8 @@ using namespace portapack; #include "utility.hpp" +#include "string_format.hpp" + namespace ui { /* AMOptionsView *********************************************************/ @@ -84,6 +86,7 @@ AnalogAudioView::AnalogAudioView( &field_vga, &options_modulation, &field_volume, + &record_view, &waterfall, } }); @@ -208,6 +211,8 @@ void AnalogAudioView::remove_options_widget() { } void AnalogAudioView::set_options_widget(std::unique_ptr new_widget) { + remove_options_widget(); + if( new_widget ) { options_widget = std::move(new_widget); add_child(options_widget.get()); @@ -215,11 +220,6 @@ void AnalogAudioView::set_options_widget(std::unique_ptr new_widget) { } void AnalogAudioView::on_show_options_frequency() { - // TODO: This approach of managing options views is error-prone and unsustainable! - remove_options_widget(); - - field_frequency.set_style(&style_options_group); - auto widget = std::make_unique(options_view_rect, &style_options_group); widget->set_step(receiver_model.frequency_step()); @@ -232,14 +232,10 @@ void AnalogAudioView::on_show_options_frequency() { }; set_options_widget(std::move(widget)); + field_frequency.set_style(&style_options_group); } void AnalogAudioView::on_show_options_rf_gain() { - // TODO: This approach of managing options views is error-prone and unsustainable! - remove_options_widget(); - - field_lna.set_style(&style_options_group); - auto widget = std::make_unique(options_view_rect, &style_options_group); widget->set_rf_amp(receiver_model.rf_amp()); @@ -248,23 +244,28 @@ void AnalogAudioView::on_show_options_rf_gain() { }; set_options_widget(std::move(widget)); + field_lna.set_style(&style_options_group); } void AnalogAudioView::on_show_options_modulation() { - // TODO: This approach of managing options views is error-prone and unsustainable! - remove_options_widget(); + std::unique_ptr widget; const auto modulation = static_cast(receiver_model.modulation()); - if( modulation == ReceiverModel::Mode::AMAudio ) { - options_modulation.set_style(&style_options_group); - auto widget = std::make_unique(options_view_rect, &style_options_group); - set_options_widget(std::move(widget)); - } - if( modulation == ReceiverModel::Mode::NarrowbandFMAudio ) { - options_modulation.set_style(&style_options_group); - auto widget = std::make_unique(options_view_rect, &style_options_group); - set_options_widget(std::move(widget)); + switch(modulation) { + case ReceiverModel::Mode::AMAudio: + widget = std::make_unique(options_view_rect, &style_options_group); + break; + + case ReceiverModel::Mode::NarrowbandFMAudio: + widget = std::make_unique(options_view_rect, &style_options_group); + break; + + default: + break; } + + set_options_widget(std::move(widget)); + options_modulation.set_style(&style_options_group); } void AnalogAudioView::on_frequency_step_changed(rf::Frequency f) { @@ -283,7 +284,7 @@ void AnalogAudioView::on_headphone_volume_changed(int32_t v) { void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) { audio::output::mute(); - audio_thread.reset(); + record_view.stop(); const auto is_wideband_spectrum_mode = (modulation == ReceiverModel::Mode::SpectrumAnalysis); receiver_model.set_baseband_configuration({ @@ -294,11 +295,18 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) { receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? 12000000 : 1750000); receiver_model.enable(); + // TODO: This doesn't belong here! There's a better way. + size_t sampling_rate = 0; + switch(modulation) { + case ReceiverModel::Mode::AMAudio: sampling_rate = 12000; break; + case ReceiverModel::Mode::NarrowbandFMAudio: sampling_rate = 24000; break; + case ReceiverModel::Mode::WidebandFMAudio: sampling_rate = 48000; break; + default: + break; + } + record_view.set_sampling_rate(sampling_rate); + if( !is_wideband_spectrum_mode ) { - const auto filename = next_filename_matching_pattern("AUD_????.S16"); - if( !filename.empty() ) { - audio_thread = std::make_unique(filename); - } audio::output::unmute(); } } diff --git a/firmware/application/analog_audio_app.hpp b/firmware/application/analog_audio_app.hpp index dace3245..ebef06e3 100644 --- a/firmware/application/analog_audio_app.hpp +++ b/firmware/application/analog_audio_app.hpp @@ -26,8 +26,7 @@ #include "ui_receiver.hpp" #include "ui_spectrum.hpp" - -#include "audio_thread.hpp" +#include "ui_record_view.hpp" #include "ui_font_fixed_8x16.hpp" @@ -93,7 +92,7 @@ public: void focus() override; private: - static constexpr ui::Dim header_height = 2 * 16; + static constexpr ui::Dim header_height = 3 * 16; const Rect options_view_rect { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }; @@ -142,9 +141,12 @@ private: std::unique_ptr options_widget; - spectrum::WaterfallWidget waterfall; + RecordView record_view { + { 0 * 8, 2 * 16, 30 * 8, 1 * 16 }, + "AUD_????", RecordView::FileType::WAV, 12, 2, + }; - std::unique_ptr audio_thread; + spectrum::WaterfallWidget waterfall; void on_tuning_frequency_changed(rf::Frequency f); void on_baseband_bandwidth_changed(uint32_t bandwidth_hz); diff --git a/firmware/application/audio_thread.hpp b/firmware/application/audio_thread.hpp deleted file mode 100644 index 37802fe8..00000000 --- a/firmware/application/audio_thread.hpp +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. - * - * This file is part of PortaPack. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, - * Boston, MA 02110-1301, USA. - */ - -#ifndef __AUDIO_THREAD_H__ -#define __AUDIO_THREAD_H__ - -#include "ch.h" - -#include "file.hpp" - -#include "event_m0.hpp" - -#include "portapack_shared_memory.hpp" - -#include "hackrf_gpio.hpp" -using namespace hackrf::one; - -#include - -class StreamOutput { -public: - StreamOutput( - FIFO* const fifo - ) : fifo { fifo } - { - } - - size_t available() { - return fifo->len(); - } - - size_t read(void* const data, const size_t length) { - return fifo->out(reinterpret_cast(data), length); - } - -private: - FIFO* const fifo; -}; - -class AudioThread { -public: - AudioThread( - std::string file_path - ) : file_path { std::move(file_path) }, - write_buffer { std::make_unique>() } - { - // Need significant stack for FATFS - thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, AudioThread::static_fn, this); - } - - ~AudioThread() { - chThdTerminate(thread); - chEvtSignal(thread, EVT_FIFO_HIGHWATER); - const auto success = chThdWait(thread); - - if( !success ) { - led_tx.on(); - } - } - - static void check_fifo_isr() { - if( (shared_memory.FIFO_HACK != nullptr) && (thread != nullptr) ) { - auto fifo = reinterpret_cast*>(shared_memory.FIFO_HACK); - if( fifo->len() >= write_size ) { - chEvtSignalI(thread, EVT_FIFO_HIGHWATER); - } - } - } - -private: - static constexpr size_t write_size = 16384; - static constexpr eventmask_t EVT_FIFO_HIGHWATER = 1; - - const std::string file_path; - std::unique_ptr> write_buffer; - File file; - static Thread* thread; - - static msg_t static_fn(void* arg) { - auto obj = static_cast(arg); - return obj->run(); - } - - msg_t run() { - if( !file.open_for_writing(file_path) ) { - return false; - } - - auto fifo = reinterpret_cast*>(shared_memory.FIFO_HACK); - if( !fifo ) { - return false; - } - - StreamOutput stream { fifo }; - - while( !chThdShouldTerminate() ) { - chEvtWaitAny(EVT_FIFO_HIGHWATER); - - while( stream.available() >= write_buffer->size() ) { - if( !transfer(stream, write_buffer.get()) ) { - return false; - } - } - } - - return true; - } - - bool transfer(StreamOutput& stream, std::array* const write_buffer) { - bool success = false; - - led_usb.on(); - - const auto bytes_to_write = stream.read(write_buffer->data(), write_buffer->size()); - if( bytes_to_write == write_buffer->size() ) { - if( file.write(write_buffer->data(), write_buffer->size()) ) { - success = true; - } - } - - led_usb.off(); - - return success; - } -}; - -#endif/*__AUDIO_THREAD_H__*/ diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp new file mode 100644 index 00000000..79906dfb --- /dev/null +++ b/firmware/application/bitmap.hpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __BITMAP_HPP__ +#define __BITMAP_HPP__ + +#include "ui.hpp" + +namespace ui { + +static constexpr uint8_t bitmap_record_data[] = { + 0x00, 0x00, + 0x00, 0x00, + 0xc0, 0x03, + 0xf0, 0x0f, + 0xf8, 0x1f, + 0xf8, 0x1f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xf8, 0x1f, + 0xf8, 0x1f, + 0xf0, 0x0f, + 0xc0, 0x03, + 0x00, 0x00, + 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_record { + { 16, 16 }, bitmap_record_data +}; + +static constexpr uint8_t bitmap_stop_data[] = { + 0x00, 0x00, + 0x00, 0x00, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0xfc, 0x3f, + 0x00, 0x00, + 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_stop { + { 16, 16 }, bitmap_stop_data +}; + +static constexpr uint8_t bitmap_sleep_data[] = { + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x04, + 0x00, 0x08, + 0x00, 0x18, + 0x00, 0x18, + 0x00, 0x38, + 0x00, 0x3c, + 0x00, 0x3c, + 0x00, 0x3e, + 0x84, 0x1f, + 0xf8, 0x1f, + 0xf0, 0x0f, + 0xc0, 0x03, + 0x00, 0x00, + 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_sleep { + { 16, 16 }, bitmap_sleep_data +}; + +static constexpr uint8_t bitmap_camera_data[] = { + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, + 0xcc, 0x03, + 0xe8, 0x07, + 0xfc, 0x3f, + 0x3c, 0x3c, + 0x9c, 0x39, + 0xdc, 0x3b, + 0xdc, 0x3b, + 0x9c, 0x39, + 0x3c, 0x3c, + 0xfc, 0x3f, + 0x00, 0x00, + 0x00, 0x00, + 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_camera { + { 16, 16 }, bitmap_camera_data +}; + +static constexpr uint8_t bitmap_sd_card_ok_data[] = { + 0x00, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0xf0, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, + 0xf8, 0x1f, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_sd_card_ok { + { 16, 16 }, bitmap_sd_card_ok_data +}; + +static constexpr uint8_t bitmap_sd_card_unknown_data[] = { + 0x00, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0xf0, 0x1f, + 0x38, 0x1c, 0x98, 0x19, 0xf8, 0x19, 0xf8, 0x1c, + 0x78, 0x1e, 0x78, 0x1e, 0xf8, 0x1f, 0x78, 0x1e, + 0xf8, 0x1f, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_sd_card_unknown { + { 16, 16 }, bitmap_sd_card_unknown_data +}; + +static constexpr uint8_t bitmap_sd_card_error_data[] = { + 0x00, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0xf0, 0x1f, + 0xf8, 0x1f, 0xd8, 0x1b, 0x98, 0x19, 0x38, 0x1c, + 0x78, 0x1e, 0x38, 0x1c, 0x98, 0x19, 0xd8, 0x1b, + 0xf8, 0x1f, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, +}; + +static constexpr Bitmap bitmap_sd_card_error { + { 16, 16 }, bitmap_sd_card_error_data +}; + +} /* namespace ui */ + +#endif/*__BITMAP_HPP__*/ diff --git a/firmware/application/capture_app.cpp b/firmware/application/capture_app.cpp index 617d4e6b..e03d9476 100644 --- a/firmware/application/capture_app.cpp +++ b/firmware/application/capture_app.cpp @@ -24,20 +24,16 @@ #include "portapack.hpp" using namespace portapack; -#include "file.hpp" - -#include "utility.hpp" - namespace ui { CaptureAppView::CaptureAppView(NavigationView& nav) { add_children({ { - &button_start_stop, &rssi, &channel, &field_frequency, &field_lna, &field_vga, + &record_view, &waterfall, } }); @@ -65,10 +61,6 @@ CaptureAppView::CaptureAppView(NavigationView& nav) { this->on_vga_changed(v_db); }; - button_start_stop.on_select = [this](ImageButton&) { - this->on_start_stop(); - }; - receiver_model.set_baseband_configuration({ .mode = toUType(ReceiverModel::Mode::Capture), .sampling_rate = sampling_rate, @@ -76,6 +68,8 @@ CaptureAppView::CaptureAppView(NavigationView& nav) { }); receiver_model.set_baseband_bandwidth(baseband_bandwidth); receiver_model.enable(); + + record_view.set_sampling_rate(sampling_rate / 8); } CaptureAppView::~CaptureAppView() { @@ -97,22 +91,7 @@ void CaptureAppView::set_parent_rect(const Rect new_parent_rect) { } void CaptureAppView::focus() { - button_start_stop.focus(); -} - -void CaptureAppView::on_start_stop() { - if( capture_thread ) { - capture_thread.reset(); - button_start_stop.set_bitmap(&bitmap_record); - } else { - const auto filename = next_filename_matching_pattern("BBD_????.C16"); - if( filename.empty() ) { - return; - } - - capture_thread = std::make_unique(filename); - button_start_stop.set_bitmap(&bitmap_stop); - } + record_view.focus(); } void CaptureAppView::on_tuning_frequency_changed(rf::Frequency f) { diff --git a/firmware/application/capture_app.hpp b/firmware/application/capture_app.hpp index 8888f9f0..bc896f9e 100644 --- a/firmware/application/capture_app.hpp +++ b/firmware/application/capture_app.hpp @@ -25,61 +25,14 @@ #include "ui_widget.hpp" #include "ui_navigation.hpp" #include "ui_receiver.hpp" +#include "ui_record_view.hpp" #include "ui_spectrum.hpp" -#include "audio_thread.hpp" - #include #include namespace ui { -static constexpr uint8_t bitmap_record_data[] = { - 0x00, 0x00, - 0x00, 0x00, - 0xc0, 0x03, - 0xf0, 0x0f, - 0xf8, 0x1f, - 0xf8, 0x1f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xf8, 0x1f, - 0xf8, 0x1f, - 0xf0, 0x0f, - 0xc0, 0x03, - 0x00, 0x00, - 0x00, 0x00, -}; - -static constexpr Bitmap bitmap_record { - { 16, 16 }, bitmap_record_data -}; - -static constexpr uint8_t bitmap_stop_data[] = { - 0x00, 0x00, - 0x00, 0x00, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0xfc, 0x3f, - 0x00, 0x00, - 0x00, 0x00, -}; - -static constexpr Bitmap bitmap_stop { - { 16, 16 }, bitmap_stop_data -}; - class CaptureAppView : public View { public: CaptureAppView(NavigationView& nav); @@ -94,26 +47,15 @@ public: std::string title() const override { return "Capture"; }; private: - static constexpr ui::Dim header_height = 2 * 16; + static constexpr ui::Dim header_height = 3 * 16; static constexpr uint32_t sampling_rate = 4000000; static constexpr uint32_t baseband_bandwidth = 2500000; - std::unique_ptr capture_thread; - - void on_start_stop(); - void on_tuning_frequency_changed(rf::Frequency f); void on_lna_changed(int32_t v_db); void on_vga_changed(int32_t v_db); - ImageButton button_start_stop { - { 0 * 8, 0, 2 * 8, 1 * 16 }, - &bitmap_record, - Color::red(), - Color::black() - }; - RSSI rssi { { 21 * 8, 0, 6 * 8, 4 }, }; @@ -134,6 +76,11 @@ private: { 18 * 8, 0 * 16 } }; + RecordView record_view { + { 0 * 8, 2 * 16, 30 * 8, 1 * 16 }, + "BBD_????", RecordView::FileType::RawS16, 14, 1, + }; + spectrum::WaterfallWidget waterfall; }; diff --git a/firmware/application/capture_thread.cpp b/firmware/application/capture_thread.cpp new file mode 100644 index 00000000..6bccea57 --- /dev/null +++ b/firmware/application/capture_thread.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "capture_thread.hpp" + +#include "portapack_shared_memory.hpp" + +// StreamOutput /////////////////////////////////////////////////////////// + +class StreamOutput { +public: + StreamOutput(CaptureConfig* const config); + ~StreamOutput(); + + size_t available() { + return fifo->len(); + } + + size_t read(void* const data, const size_t length) { + return fifo->out(reinterpret_cast(data), length); + } + + static FIFO* fifo; + +private: + CaptureConfig* const config; +}; + +FIFO* StreamOutput::fifo = nullptr; + +StreamOutput::StreamOutput( + CaptureConfig* const config +) : config { config } +{ + shared_memory.baseband_queue.push_and_wait( + CaptureConfigMessage { config } + ); + fifo = config->fifo; +} + +StreamOutput::~StreamOutput() { + fifo = nullptr; + shared_memory.baseband_queue.push_and_wait( + CaptureConfigMessage { nullptr } + ); +} + +// CaptureThread ////////////////////////////////////////////////////////// + +Thread* CaptureThread::thread = nullptr; + +CaptureThread::CaptureThread( + std::unique_ptr writer, + size_t write_size_log2, + size_t buffer_count_log2 +) : config { write_size_log2, buffer_count_log2 }, + writer { std::move(writer) } +{ + // Need significant stack for FATFS + thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, CaptureThread::static_fn, this); +} + +CaptureThread::~CaptureThread() { + if( thread ) { + chThdTerminate(thread); + chEvtSignal(thread, EVT_MASK_CAPTURE_THREAD); + chThdWait(thread); + thread = nullptr; + } +} + +void CaptureThread::check_fifo_isr() { + // TODO: Prevent over-signalling by transmitting a set of + // flags from the baseband core. + const auto fifo = StreamOutput::fifo; + if( fifo ) { + chEvtSignalI(thread, EVT_MASK_CAPTURE_THREAD); + } +} + +msg_t CaptureThread::run() { + const size_t write_size = 1U << config.write_size_log2; + const auto write_buffer = std::make_unique(write_size); + if( !write_buffer ) { + return false; + } + + 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; + } + } else { + chEvtWaitAny(EVT_MASK_CAPTURE_THREAD); + } + } + + return true; +} diff --git a/firmware/application/capture_thread.hpp b/firmware/application/capture_thread.hpp new file mode 100644 index 00000000..c4bdbb1c --- /dev/null +++ b/firmware/application/capture_thread.hpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __CAPTURE_THREAD_H__ +#define __CAPTURE_THREAD_H__ + +#include "ch.h" + +#include "event_m0.hpp" + +#include +#include +#include + +class Writer { +public: + virtual bool write(const void* const buffer, const size_t bytes) = 0; + virtual ~Writer() = default; +}; + +class CaptureThread { +public: + CaptureThread( + std::unique_ptr writer, + size_t write_size_log2, + size_t buffer_count_log2 + ); + ~CaptureThread(); + + const CaptureConfig& state() const { + return config; + } + + static void check_fifo_isr(); + +private: + CaptureConfig config; + std::unique_ptr writer; + static Thread* thread; + + static msg_t static_fn(void* arg) { + auto obj = static_cast(arg); + return obj->run(); + } + + msg_t run(); +}; + +#endif/*__CAPTURE_THREAD_H__*/ diff --git a/firmware/application/ert_app.cpp b/firmware/application/ert_app.cpp index 938aef9d..bbdf5848 100644 --- a/firmware/application/ert_app.cpp +++ b/firmware/application/ert_app.cpp @@ -66,7 +66,7 @@ ERTLogger::ERTLogger( } void ERTLogger::on_packet(const ert::Packet& packet) { - if( log_file.is_ready() ) { + if( log_file.is_open() ) { const auto formatted = packet.symbols_formatted(); log_file.write_entry(packet.received_at(), formatted.data + "/" + formatted.errors); } diff --git a/firmware/application/event_m0.cpp b/firmware/application/event_m0.cpp index 49021ada..90e1cb34 100644 --- a/firmware/application/event_m0.cpp +++ b/firmware/application/event_m0.cpp @@ -22,16 +22,17 @@ #include "event_m0.hpp" #include "portapack.hpp" -#include "portapack_persistent_memory.hpp" +#include "portapack_shared_memory.hpp" #include "sd_card.hpp" +#include "time.hpp" #include "message.hpp" #include "message_queue.hpp" #include "irq_controls.hpp" -#include "audio_thread.hpp" +#include "capture_thread.hpp" #include "ch.h" @@ -46,8 +47,8 @@ CH_IRQ_HANDLER(M4Core_IRQHandler) { CH_IRQ_PROLOGUE(); chSysLockFromIsr(); - AudioThread::check_fifo_isr(); - EventDispatcher::events_flag_isr(EVT_MASK_APPLICATION); + CaptureThread::check_fifo_isr(); + EventDispatcher::check_fifo_isr(); chSysUnlockFromIsr(); creg::m4txevent::clear(); @@ -132,6 +133,10 @@ void EventDispatcher::dispatch(const eventmask_t events) { handle_lcd_frame_sync(); } + if( events & EVT_MASK_ENCODER ) { + handle_encoder(); + } + if( events & EVT_MASK_TOUCH ) { handle_touch(); } @@ -158,6 +163,8 @@ void EventDispatcher::handle_rtc_tick() { else portapack::bl_tick_counter++; } + + time::on_tick_second(); } ui::Widget* EventDispatcher::touch_widget(ui::Widget* const w, ui::TouchEvent event) { @@ -278,6 +285,4 @@ void EventDispatcher::init_message_queues() { new (&shared_memory.application_queue) MessageQueue( shared_memory.application_queue_data, SharedMemory::application_queue_k ); - - shared_memory.FIFO_HACK = nullptr; } diff --git a/firmware/application/event_m0.hpp b/firmware/application/event_m0.hpp index 69dcede2..f793eaf7 100644 --- a/firmware/application/event_m0.hpp +++ b/firmware/application/event_m0.hpp @@ -28,6 +28,7 @@ #include "ui_painter.hpp" #include "portapack.hpp" +#include "portapack_shared_memory.hpp" #include "message.hpp" @@ -43,6 +44,7 @@ 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: @@ -57,6 +59,12 @@ public: void set_display_sleep(const bool sleep); + static inline void check_fifo_isr() { + if( !shared_memory.application_queue.is_empty() ) { + events_flag_isr(EVT_MASK_APPLICATION); + } + } + static inline void events_flag(const eventmask_t events) { if( thread_event_loop ) { chEvtSignal(thread_event_loop, events); @@ -73,8 +81,6 @@ public: return message_map_; } - static Thread* thread_record; - private: static MessageHandlerMap message_map_; static Thread* thread_event_loop; diff --git a/firmware/application/file.cpp b/firmware/application/file.cpp index 288f5bde..18e75005 100644 --- a/firmware/application/file.cpp +++ b/firmware/application/file.cpp @@ -23,40 +23,32 @@ #include -File::~File() { - close(); -} - -bool File::open_for_writing(const std::string& file_path) { - const auto open_result = f_open(&f, file_path.c_str(), FA_WRITE | FA_OPEN_ALWAYS); - return (open_result == FR_OK); -} - -bool File::open_for_reading(const std::string& file_path) { - const auto open_result = f_open(&f, file_path.c_str(), FA_READ | FA_OPEN_EXISTING); - return (open_result == FR_OK); -} - -bool File::open_for_append(const std::string& file_path) { - if( open_for_writing(file_path) ) { - const auto seek_result = f_lseek(&f, f_size(&f)); - if( seek_result == FR_OK ) { - return true; - } else { - close(); - } +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; } - return false; + if( f_open(&f, filename.c_str(), fatfs_mode) == FR_OK ) { + if( mode & openmode::ate ) { + if( f_lseek(&f, f_size(&f)) != FR_OK ) { + f_close(&f); + } + } + } } -bool File::close() { +File::~File() { f_close(&f); - return true; -} - -bool File::is_ready() { - return f_error(&f) == 0; } bool File::read(void* const data, const size_t bytes_to_read) { @@ -71,6 +63,17 @@ bool File::write(const void* const data, const size_t bytes_to_write) { return (result == FR_OK) && (bytes_written == bytes_to_write); } +uint64_t File::seek(const uint64_t new_position) { + const auto old_position = f_tell(&f); + if( f_lseek(&f, new_position) != FR_OK ) { + f_close(&f); + } + if( f_tell(&f) != new_position ) { + f_close(&f); + } + return old_position; +} + bool File::puts(const std::string& string) { const auto result = f_puts(string.c_str(), &f); return (result >= 0); @@ -94,22 +97,16 @@ static std::string find_last_file_matching_pattern(const std::string& pattern) { return last_match; } -static std::string increment_filename_ordinal(const std::string& filename) { - std::string result { filename }; +static std::string remove_filename_extension(const std::string& filename) { + const auto extension_index = filename.find_last_of('.'); + return filename.substr(0, extension_index); +} + +static std::string increment_filename_stem_ordinal(const std::string& filename_stem) { + std::string result { filename_stem }; auto it = result.rbegin(); - // Back up past extension. - for(; it != result.rend(); ++it) { - if( *it == '.' ) { - ++it; - break; - } - } - if( it == result.rend() ) { - return { }; - } - // Increment decimal number before the extension. for(; it != result.rend(); ++it) { const auto c = *it; @@ -128,15 +125,16 @@ static std::string increment_filename_ordinal(const std::string& filename) { return result; } -std::string next_filename_matching_pattern(const std::string& filename_pattern) { - auto filename = find_last_file_matching_pattern(filename_pattern); - if( filename.empty() ) { - filename = filename_pattern; - std::replace(std::begin(filename), std::end(filename), '?', '0'); +std::string next_filename_stem_matching_pattern(const std::string& filename_stem_pattern) { + const auto filename = find_last_file_matching_pattern(filename_stem_pattern + ".*"); + auto filename_stem = remove_filename_extension(filename); + if( filename_stem.empty() ) { + filename_stem = filename_stem_pattern; + std::replace(std::begin(filename_stem), std::end(filename_stem), '?', '0'); } else { - filename = increment_filename_ordinal(filename); + filename_stem = increment_filename_stem_ordinal(filename_stem); } - return filename; + return filename_stem; } namespace std { diff --git a/firmware/application/file.hpp b/firmware/application/file.hpp index 71e454be..6be0f9dd 100644 --- a/firmware/application/file.hpp +++ b/firmware/application/file.hpp @@ -32,18 +32,27 @@ 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 open_for_writing(const std::string& file_path); - bool open_for_reading(const std::string& file_path); - bool open_for_append(const std::string& file_path); - bool close(); - - bool is_ready(); + 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 bool write(const std::array& data) { return write(data.data(), N); @@ -57,7 +66,11 @@ private: FIL f; }; -std::string next_filename_matching_pattern(const std::string& filename_pattern); +inline constexpr File::openmode operator|(File::openmode a, File::openmode b) { + return File::openmode(static_cast(a) | static_cast(b)); +} + +std::string next_filename_stem_matching_pattern(const std::string& filename_stem_pattern); namespace std { namespace filesystem { diff --git a/firmware/application/log_file.cpp b/firmware/application/log_file.cpp index e87cce36..06e56246 100644 --- a/firmware/application/log_file.cpp +++ b/firmware/application/log_file.cpp @@ -23,28 +23,14 @@ #include "string_format.hpp" -#include "lpc43xx_cpp.hpp" -using namespace lpc43xx; - LogFile::LogFile( const std::string& file_path -) : file_path { file_path } +) : file { file_path, File::openmode::out | File::openmode::ate } { - file.open_for_append(file_path); - - sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status status) { - this->on_sd_card_status(status); - }; } -LogFile::~LogFile() { - sd_card::status_signal -= sd_card_status_signal_token; - - file.close(); -} - -bool LogFile::is_ready() { - return file.is_ready(); +bool LogFile::is_open() const { + return file.is_open(); } bool LogFile::write_entry(const rtc::RTC& datetime, const std::string& entry) { @@ -55,11 +41,3 @@ bool LogFile::write_entry(const rtc::RTC& datetime, const std::string& entry) { bool LogFile::write(const std::string& message) { return file.puts(message) && file.sync(); } - -void LogFile::on_sd_card_status(const sd_card::Status status) { - if( status == sd_card::Status::Mounted ) { - file.open_for_append(file_path); - } else { - file.close(); - } -} diff --git a/firmware/application/log_file.hpp b/firmware/application/log_file.hpp index 30883b0f..70a1651c 100644 --- a/firmware/application/log_file.hpp +++ b/firmware/application/log_file.hpp @@ -25,7 +25,6 @@ #include #include "file.hpp" -#include "sd_card.hpp" #include "lpc43xx_cpp.hpp" using namespace lpc43xx; @@ -33,22 +32,15 @@ using namespace lpc43xx; class LogFile { public: LogFile(const std::string& file_path); - ~LogFile(); - bool is_ready(); + bool is_open() const; bool write_entry(const rtc::RTC& datetime, const std::string& entry); private: - const std::string file_path; - File file; - SignalToken sd_card_status_signal_token; - bool write(const std::string& message); - - void on_sd_card_status(const sd_card::Status status); }; #endif/*__LOG_FILE_H__*/ diff --git a/firmware/application/portapack.cpp b/firmware/application/portapack.cpp index 6e3c50ad..2d3b2cf1 100644 --- a/firmware/application/portapack.cpp +++ b/firmware/application/portapack.cpp @@ -110,9 +110,23 @@ void init() { } /* Configure other pins */ + /* Glitch filter operates at 3ns instead of 50ns due to the WM8731 + * returning an ACK very fast (170ns) and confusing the I2C state + * machine into thinking there was a bus error. It looks like the + * MCU sees SDA fall before SCL falls, indicating a START at the + * point an ACK is expected. With the glitch filter off or set to + * 3ns, it's probably still a bit tight timing-wise, but improves + * reliability on some problem units. + */ LPC_SCU->SFSI2C0 = - (1U << 3) - | (1U << 11) + (1U << 0) // SCL: 3ns glitch + | (0U << 2) // SCL: Standard/Fast mode + | (1U << 3) // SCL: Input enabled + | (0U << 7) // SCL: Enable input glitch filter + | (1U << 8) // SDA: 3ns glitch + | (0U << 10) // SDA: Standard/Fast mode + | (1U << 11) // SDA: Input enabled + | (0U << 15) // SDA: Enable input glitch filter ; power.init(); diff --git a/firmware/application/audio_thread.cpp b/firmware/application/time.cpp similarity index 78% rename from firmware/application/audio_thread.cpp rename to firmware/application/time.cpp index eed06109..10ba7f3b 100644 --- a/firmware/application/audio_thread.cpp +++ b/firmware/application/time.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. * * This file is part of PortaPack. * @@ -19,6 +19,14 @@ * Boston, MA 02110-1301, USA. */ -#include "audio_thread.hpp" +#include "time.hpp" -Thread* AudioThread::thread = nullptr; +namespace time { + +Signal<> signal_tick_second; + +void on_tick_second() { + signal_tick_second.emit(); +} + +} /* namespace time */ diff --git a/firmware/application/time.hpp b/firmware/application/time.hpp new file mode 100644 index 00000000..470c894d --- /dev/null +++ b/firmware/application/time.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __TIME_H__ +#define __TIME_H__ + +#include "signal.hpp" + +namespace time { + +extern Signal<> signal_tick_second; + +void on_tick_second(); + +} /* namespace time */ + +#endif/*__TIME_H__*/ diff --git a/firmware/application/tpms_app.cpp b/firmware/application/tpms_app.cpp index c80bfb9d..abe27450 100644 --- a/firmware/application/tpms_app.cpp +++ b/firmware/application/tpms_app.cpp @@ -62,7 +62,7 @@ TPMSLogger::TPMSLogger( void TPMSLogger::on_packet(const tpms::Packet& packet, const uint32_t target_frequency) { const auto hex_formatted = packet.symbols_formatted(); - if( log_file.is_ready() ) { + 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); diff --git a/firmware/application/ui_audio.hpp b/firmware/application/ui_audio.hpp index cecacc7a..00d7f908 100644 --- a/firmware/application/ui_audio.hpp +++ b/firmware/application/ui_audio.hpp @@ -34,7 +34,7 @@ namespace ui { class Audio : public Widget { public: - constexpr Audio( + Audio( const Rect parent_rect ) : Widget { parent_rect }, rms_db_ { -120 }, diff --git a/firmware/application/ui_channel.hpp b/firmware/application/ui_channel.hpp index 97ac61c8..3b7f4969 100644 --- a/firmware/application/ui_channel.hpp +++ b/firmware/application/ui_channel.hpp @@ -34,7 +34,7 @@ namespace ui { class Channel : public Widget { public: - constexpr Channel( + Channel( const Rect parent_rect ) : Widget { parent_rect }, max_db_ { -120 } diff --git a/firmware/application/ui_debug.hpp b/firmware/application/ui_debug.hpp index 504ab063..2dca74fe 100644 --- a/firmware/application/ui_debug.hpp +++ b/firmware/application/ui_debug.hpp @@ -84,7 +84,7 @@ private: class TemperatureWidget : public Widget { public: - explicit constexpr TemperatureWidget( + explicit TemperatureWidget( Rect parent_rect ) : Widget { parent_rect } { diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 077fc372..6391690d 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -90,12 +90,12 @@ void SystemStatusView::set_title(const std::string new_value) { } void SystemStatusView::on_camera() { - const auto filename = next_filename_matching_pattern("SCR_????.PNG"); - if( filename.empty() ) { + const auto filename_stem = next_filename_stem_matching_pattern("SCR_????"); + if( filename_stem.empty() ) { return; } - PNGWriter png { filename }; + PNGWriter png { filename_stem + ".PNG" }; for(int i=0; i<320; i++) { std::array row; diff --git a/firmware/application/ui_navigation.hpp b/firmware/application/ui_navigation.hpp index c165a3c3..6bc532f8 100644 --- a/firmware/application/ui_navigation.hpp +++ b/firmware/application/ui_navigation.hpp @@ -32,57 +32,13 @@ #include "ui_audio.hpp" #include "ui_sd_card_status_view.hpp" +#include "bitmap.hpp" + #include #include namespace ui { -static constexpr uint8_t bitmap_sleep_data[] = { - 0x00, 0x00, - 0x00, 0x00, - 0x00, 0x04, - 0x00, 0x08, - 0x00, 0x18, - 0x00, 0x18, - 0x00, 0x38, - 0x00, 0x3c, - 0x00, 0x3c, - 0x00, 0x3e, - 0x84, 0x1f, - 0xf8, 0x1f, - 0xf0, 0x0f, - 0xc0, 0x03, - 0x00, 0x00, - 0x00, 0x00, -}; - -static constexpr Bitmap bitmap_sleep { - { 16, 16 }, bitmap_sleep_data -}; - -static constexpr uint8_t bitmap_camera_data[] = { - 0x00, 0x00, - 0x00, 0x00, - 0x00, 0x00, - 0xcc, 0x03, - 0xe8, 0x07, - 0xfc, 0x3f, - 0x3c, 0x3c, - 0x9c, 0x39, - 0xdc, 0x3b, - 0xdc, 0x3b, - 0x9c, 0x39, - 0x3c, 0x3c, - 0xfc, 0x3f, - 0x00, 0x00, - 0x00, 0x00, - 0x00, 0x00, -}; - -static constexpr Bitmap bitmap_camera { - { 16, 16 }, bitmap_camera_data -}; - class SystemStatusView : public View { public: std::function on_back; diff --git a/firmware/application/ui_record_view.cpp b/firmware/application/ui_record_view.cpp new file mode 100644 index 00000000..3f905f06 --- /dev/null +++ b/firmware/application/ui_record_view.cpp @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui_record_view.hpp" + +#include "portapack.hpp" +using namespace portapack; + +#include "file.hpp" +#include "time.hpp" + +#include "string_format.hpp" +#include "utility.hpp" + +#include + +class RawFileWriter : public Writer { +public: + RawFileWriter( + const std::string& filename + ) : file { filename, File::openmode::out | File::openmode::binary | File::openmode::trunc } + { + } + + bool write(const void* const buffer, const size_t bytes) override { + return file.write(buffer, bytes); + } + +private: + File file; +}; + +class WAVFileWriter : public Writer { +public: + WAVFileWriter( + const std::string& filename, + size_t sampling_rate + ) : file { filename, File::openmode::out | File::openmode::binary | File::openmode::trunc }, + header { sampling_rate } + { + update_header(); + } + + ~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; + } + return success; + } + +private: + struct fmt_pcm_t { + constexpr fmt_pcm_t( + const uint32_t sampling_rate + ) : nSamplesPerSec { sampling_rate }, + nAvgBytesPerSec { nSamplesPerSec * nBlockAlign } + { + } + + private: + const uint8_t ckID[4] { 'f', 'm', 't', ' ' }; + const uint32_t cksize { 16 }; + const uint16_t wFormatTag { 0x0001 }; + const uint16_t nChannels { 1 }; + const uint32_t nSamplesPerSec; + const uint32_t nAvgBytesPerSec; + const uint16_t nBlockAlign { 2 }; + const uint16_t wBitsPerSample { 16 }; + }; + + struct data_t { + void set_size(const uint32_t value) { + cksize = value; + } + + private: + const uint8_t ckID[4] { 'd', 'a', 't', 'a' }; + uint32_t cksize { 0 }; + }; + + struct header_t { + constexpr header_t( + const uint32_t sampling_rate + ) : fmt { sampling_rate } + { + } + + void set_data_size(const uint32_t value) { + data.set_size(value); + cksize = sizeof(header_t) + value - 8; + } + + private: + const uint8_t riff_id[4] { 'R', 'I', 'F', 'F' }; + uint32_t cksize { 0 }; + const uint8_t wave_id[4] { 'W', 'A', 'V', 'E' }; + fmt_pcm_t fmt; + data_t data; + }; + + File file; + header_t header; + uint64_t bytes_written { 0 }; + + void update_header() { + header.set_data_size(bytes_written); + const auto old_position = file.seek(0); + file.write(&header, sizeof(header)); + file.seek(old_position); + } +}; + +namespace ui { + +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 +) : 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 } +{ + add_children({ { + &button_record, + &text_record_filename, + &text_record_dropped, + } }); + + button_record.on_select = [this](ImageButton&) { + this->toggle(); + }; + + signal_token_tick_second = time::signal_tick_second += [this]() { + this->on_tick_second(); + }; +} + +RecordView::~RecordView() { + time::signal_tick_second -= signal_token_tick_second; +} + +void RecordView::focus() { + button_record.focus(); +} + +bool RecordView::is_active() const { + return (bool)capture_thread; +} + +void RecordView::toggle() { + if( is_active() ) { + stop(); + } else { + start(); + } +} + +void RecordView::start() { + stop(); + + text_record_filename.set(""); + text_record_dropped.set(""); + + if( sampling_rate == 0 ) { + return; + } + + const auto filename_stem = next_filename_stem_matching_pattern(filename_stem_pattern); + if( filename_stem.empty() ) { + return; + } + + std::unique_ptr writer; + switch(file_type) { + case FileType::WAV: + writer = std::make_unique( + filename_stem + ".WAV", + sampling_rate + ); + break; + + case FileType::RawS16: + write_metadata_file(filename_stem + ".TXT"); + writer = std::make_unique( + filename_stem + ".C16" + ); + break; + + default: + break; + }; + + if( writer ) { + text_record_filename.set(filename_stem); + button_record.set_bitmap(&bitmap_stop); + capture_thread = std::make_unique( + std::move(writer), + buffer_size_k, buffer_count_k + ); + } +} + +void RecordView::stop() { + if( is_active() ) { + capture_thread.reset(); + button_record.set_bitmap(&bitmap_record); + } +} + +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"); +} + +void RecordView::on_tick_second() { + 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); + } +} + +} /* namespace ui */ diff --git a/firmware/application/ui_record_view.hpp b/firmware/application/ui_record_view.hpp new file mode 100644 index 00000000..0c8c24bb --- /dev/null +++ b/firmware/application/ui_record_view.hpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc. + * + * This file is part of PortaPack. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __UI_RECORD_VIEW_H__ +#define __UI_RECORD_VIEW_H__ + +#include "ui_widget.hpp" + +#include "capture_thread.hpp" +#include "signal.hpp" + +#include "bitmap.hpp" + +#include +#include +#include + +namespace ui { + +class RecordView : public View { +public: + enum FileType { + RawS16 = 2, + WAV = 3, + }; + + RecordView( + const Rect parent_rect, + std::string filename_stem_pattern, + FileType file_type, + const size_t buffer_size_k, + const size_t buffer_count_k + ); + ~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 start(); + void stop(); + + bool is_active() const; + +private: + void toggle(); + void write_metadata_file(const std::string& filename); + + void on_tick_second(); + + const std::string filename_stem_pattern; + const FileType file_type; + const size_t buffer_size_k; + const size_t buffer_count_k; + size_t sampling_rate { 0 }; + SignalToken signal_token_tick_second; + + ImageButton button_record { + { 0 * 8, 0 * 16, 2 * 8, 1 * 16 }, + &bitmap_record, + Color::red(), + Color::black() + }; + + Text text_record_filename { + { 3 * 8, 0 * 16, 8 * 8, 16 }, + "", + }; + + Text text_record_dropped { + { 16 * 8, 0 * 16, 3 * 8, 16 }, + "", + }; + + std::unique_ptr capture_thread; +}; + +} /* namespace ui */ + +#endif/*__UI_RECORD_VIEW_H__*/ diff --git a/firmware/application/ui_rssi.hpp b/firmware/application/ui_rssi.hpp index b8f8259e..5c457039 100644 --- a/firmware/application/ui_rssi.hpp +++ b/firmware/application/ui_rssi.hpp @@ -34,7 +34,7 @@ namespace ui { class RSSI : public Widget { public: - constexpr RSSI( + RSSI( const Rect parent_rect ) : Widget { parent_rect }, min_ { 0 }, diff --git a/firmware/application/ui_sd_card_debug.cpp b/firmware/application/ui_sd_card_debug.cpp index 25d9ba5d..28d164e2 100644 --- a/firmware/application/ui_sd_card_debug.cpp +++ b/firmware/application/ui_sd_card_debug.cpp @@ -133,15 +133,15 @@ private: return Result::FailHeap; } - File file; - if( !file.open_for_writing(filename) ) { + File file { filename, File::openmode::out | File::openmode::binary | File::openmode::trunc }; + if( !file.is_open() ) { return Result::FailFileOpenWrite; } lfsr_word_t v = 1; const halrtcnt_t test_start = halGetCounterValue(); - while( !chThdShouldTerminate() && file.is_ready() && (_stats.write_bytes < bytes_to_write) ) { + while( !chThdShouldTerminate() && file.is_open() && (_stats.write_bytes < bytes_to_write) ) { lfsr_fill(v, reinterpret_cast(buffer->data()), sizeof(*buffer.get()) / sizeof(lfsr_word_t) @@ -164,7 +164,7 @@ private: } } - file.close(); + file.sync(); const halrtcnt_t test_end = halGetCounterValue(); _stats.write_test_duration = test_end - test_start; @@ -178,15 +178,15 @@ private: return Result::FailHeap; } - File file; - if( !file.open_for_reading(filename) ) { + File file { filename, File::openmode::in | File::openmode::binary }; + if( !file.is_open() ) { return Result::FailFileOpenRead; } lfsr_word_t v = 1; const halrtcnt_t test_start = halGetCounterValue(); - while( !chThdShouldTerminate() && file.is_ready() && (_stats.read_bytes < bytes_to_read) ) { + while( !chThdShouldTerminate() && file.is_open() && (_stats.read_bytes < bytes_to_read) ) { const halrtcnt_t read_start = halGetCounterValue(); if( !file.read(buffer->data(), buffer->size()) ) { break; @@ -211,7 +211,7 @@ private: } } - file.close(); + file.sync(); const halrtcnt_t test_end = halGetCounterValue(); _stats.read_test_duration = test_end - test_start; diff --git a/firmware/application/ui_sd_card_status_view.cpp b/firmware/application/ui_sd_card_status_view.cpp index 567d9d99..a0ed49b4 100644 --- a/firmware/application/ui_sd_card_status_view.cpp +++ b/firmware/application/ui_sd_card_status_view.cpp @@ -24,45 +24,14 @@ #include #include +#include "bitmap.hpp" + namespace ui { /* SDCardStatusView *****************************************************/ namespace detail { -static constexpr uint8_t bitmap_sd_card_ok_data[] = { - 0x00, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0xf0, 0x1f, - 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, - 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, - 0xf8, 0x1f, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, -}; - -static constexpr Bitmap bitmap_sd_card_ok { - { 16, 16 }, bitmap_sd_card_ok_data -}; - -static constexpr uint8_t bitmap_sd_card_unknown_data[] = { - 0x00, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0xf0, 0x1f, - 0x38, 0x1c, 0x98, 0x19, 0xf8, 0x19, 0xf8, 0x1c, - 0x78, 0x1e, 0x78, 0x1e, 0xf8, 0x1f, 0x78, 0x1e, - 0xf8, 0x1f, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, -}; - -static constexpr Bitmap bitmap_sd_card_unknown { - { 16, 16 }, bitmap_sd_card_unknown_data -}; - -static constexpr uint8_t bitmap_sd_card_error_data[] = { - 0x00, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0xf0, 0x1f, - 0xf8, 0x1f, 0xd8, 0x1b, 0x98, 0x19, 0x38, 0x1c, - 0x78, 0x1e, 0x38, 0x1c, 0x98, 0x19, 0xd8, 0x1b, - 0xf8, 0x1f, 0xf8, 0x1f, 0x00, 0x00, 0x00, 0x00, -}; - -static constexpr Bitmap bitmap_sd_card_error { - { 16, 16 }, bitmap_sd_card_error_data -}; - const Bitmap& bitmap_sd_card(const sd_card::Status status) { switch(status) { case sd_card::Status::IOError: @@ -113,7 +82,7 @@ const Color color_sd_card(const sd_card::Status status) { SDCardStatusView::SDCardStatusView( const Rect parent_rect -) : Image { parent_rect, &detail::bitmap_sd_card_unknown, detail::color_sd_card_unknown, Color::black() } +) : Image { parent_rect, &bitmap_sd_card_unknown, detail::color_sd_card_unknown, Color::black() } { } diff --git a/firmware/application/ui_spectrum.cpp b/firmware/application/ui_spectrum.cpp index 4362db67..a3b7c896 100644 --- a/firmware/application/ui_spectrum.cpp +++ b/firmware/application/ui_spectrum.cpp @@ -114,6 +114,7 @@ void FrequencyScale::draw_frequency_ticks(Painter& painter, const Rect r) { (magnitude_n >= 6) ? "M" : (magnitude_n >= 3) ? "k" : ""; const std::string label = to_string_dec_uint(tick_offset) + zero_pad + unit; + const auto label_width = style().font.size_of(label).w; const Coord offset_low = r.left() + x_center - pixel_offset; const Rect tick_low { offset_low, r.top(), 1, r.height() }; @@ -123,7 +124,7 @@ void FrequencyScale::draw_frequency_ticks(Painter& painter, const Rect r) { const Coord offset_high = r.left() + x_center + pixel_offset; const Rect tick_high { offset_high, r.top(), 1, r.height() }; painter.fill_rectangle(tick_high, Color::white()); - painter.draw_string({ offset_high + 2, r.top() }, style(), label ); + painter.draw_string({ offset_high - 2 - label_width, r.top() }, style(), label ); tick_offset += tick_interval; } diff --git a/firmware/baseband-tx/stream_input.hpp b/firmware/baseband-tx/stream_input.hpp index 1b1ec552..e8221477 100644 --- a/firmware/baseband-tx/stream_input.hpp +++ b/firmware/baseband-tx/stream_input.hpp @@ -32,18 +32,12 @@ class StreamInput { public: - StreamInput(const size_t K) : + StreamInput(const size_t K, CaptureConfig& config) : K { K }, data { std::make_unique(1UL << K) }, fifo { data.get(), K } { - // TODO: Send stream creation message. - shared_memory.FIFO_HACK = &fifo; - } - - ~StreamInput() { - // TODO: Send stream distruction message. - shared_memory.FIFO_HACK = nullptr; + config.fifo = &fifo; } size_t write(const void* const data, const size_t length) { diff --git a/firmware/baseband/audio_output.cpp b/firmware/baseband/audio_output.cpp index 05d56d20..8bb1520a 100644 --- a/firmware/baseband/audio_output.cpp +++ b/firmware/baseband/audio_output.cpp @@ -96,8 +96,8 @@ void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio, const bool send_t audio_buffer.p[i].left = audio_buffer.p[i].right = sample_saturated; audio_int[i] = sample_saturated; } - if( send_to_fifo ) { - stream.write(audio_int.data(), audio_buffer.count * sizeof(audio_int[0])); + if( stream && send_to_fifo ) { + stream->write(audio_int.data(), audio_buffer.count * sizeof(audio_int[0])); } feed_audio_stats(audio); diff --git a/firmware/baseband/audio_output.hpp b/firmware/baseband/audio_output.hpp index 3837684a..6462e061 100644 --- a/firmware/baseband/audio_output.hpp +++ b/firmware/baseband/audio_output.hpp @@ -32,6 +32,7 @@ #include "audio_stats_collector.hpp" #include +#include class AudioOutput { public: @@ -44,6 +45,10 @@ public: void write(const buffer_s16_t& audio); void write(const buffer_f32_t& audio); + void set_stream(std::unique_ptr new_stream) { + stream = std::move(new_stream); + } + private: static constexpr float k = 32768.0f; static constexpr float ki = 1.0f / k; @@ -54,7 +59,7 @@ private: IIRBiquadFilter deemph; FMSquelch squelch; - StreamInput stream { 14 }; + std::unique_ptr stream; AudioStatsCollector audio_stats; diff --git a/firmware/baseband/proc_am_audio.cpp b/firmware/baseband/proc_am_audio.cpp index 0a4b5faf..f4cb2fac 100644 --- a/firmware/baseband/proc_am_audio.cpp +++ b/firmware/baseband/proc_am_audio.cpp @@ -63,6 +63,10 @@ void NarrowbandAMAudio::on_message(const Message* const message) { configure(*reinterpret_cast(message)); break; + case Message::ID::CaptureConfig: + capture_config(*reinterpret_cast(message)); + break; + default: break; } @@ -93,3 +97,11 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) { configured = true; } + +void NarrowbandAMAudio::capture_config(const CaptureConfigMessage& message) { + if( message.config ) { + audio_output.set_stream(std::make_unique(message.config)); + } else { + audio_output.set_stream(nullptr); + } +} diff --git a/firmware/baseband/proc_am_audio.hpp b/firmware/baseband/proc_am_audio.hpp index af94c416..051f1703 100644 --- a/firmware/baseband/proc_am_audio.hpp +++ b/firmware/baseband/proc_am_audio.hpp @@ -72,6 +72,7 @@ private: bool configured { false }; void configure(const AMConfigureMessage& message); + void capture_config(const CaptureConfigMessage& message); buffer_f32_t demodulate(const buffer_c16_t& channel); }; diff --git a/firmware/baseband/proc_capture.cpp b/firmware/baseband/proc_capture.cpp index f069c206..56f6a948 100644 --- a/firmware/baseband/proc_capture.cpp +++ b/firmware/baseband/proc_capture.cpp @@ -34,23 +34,16 @@ CaptureProcessor::CaptureProcessor() { constexpr size_t decim_1_input_fs = decim_0_output_fs; constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor; - const auto& channel_filter = decim_1_filter; - constexpr size_t channel_filter_input_fs = decim_1_output_fs; - constexpr size_t channel_decimation = 1; - const size_t channel_filter_output_fs = channel_filter_input_fs / channel_decimation; - decim_0.configure(decim_0_filter.taps, 33554432); decim_1.configure(decim_1_filter.taps, 131072); - channel_filter_pass_f = channel_filter.pass_frequency_normalized * channel_filter_input_fs; - channel_filter_stop_f = channel_filter.stop_frequency_normalized * channel_filter_input_fs; + channel_filter_pass_f = decim_1_filter.pass_frequency_normalized * decim_1_input_fs; + channel_filter_stop_f = decim_1_filter.stop_frequency_normalized * decim_1_input_fs; - spectrum_interval_samples = channel_filter_output_fs / spectrum_rate_hz; + spectrum_interval_samples = decim_1_output_fs / spectrum_rate_hz; spectrum_samples = 0; channel_spectrum.set_decimation_factor(1); - - stream = std::make_unique(15); } void CaptureProcessor::execute(const buffer_c8_t& buffer) { @@ -81,7 +74,19 @@ void CaptureProcessor::on_message(const Message* const message) { channel_spectrum.on_message(message); break; + case Message::ID::CaptureConfig: + capture_config(*reinterpret_cast(message)); + break; + default: break; } } + +void CaptureProcessor::capture_config(const CaptureConfigMessage& message) { + if( message.config ) { + stream = std::make_unique(message.config); + } else { + stream.reset(); + } +} diff --git a/firmware/baseband/proc_capture.hpp b/firmware/baseband/proc_capture.hpp index 008c10d2..e975d99c 100644 --- a/firmware/baseband/proc_capture.hpp +++ b/firmware/baseband/proc_capture.hpp @@ -41,7 +41,8 @@ public: void on_message(const Message* const message) override; private: - static constexpr size_t baseband_fs = 2457600; + // TODO: Repeated value needs to be transmitted from application side. + static constexpr size_t baseband_fs = 4000000; static constexpr auto spectrum_rate_hz = 50.0f; std::array dst; @@ -60,6 +61,8 @@ private: SpectrumCollector channel_spectrum; size_t spectrum_interval_samples = 0; size_t spectrum_samples = 0; + + void capture_config(const CaptureConfigMessage& message); }; #endif/*__PROC_CAPTURE_HPP__*/ diff --git a/firmware/baseband/proc_nfm_audio.cpp b/firmware/baseband/proc_nfm_audio.cpp index 3fd546b9..122115fa 100644 --- a/firmware/baseband/proc_nfm_audio.cpp +++ b/firmware/baseband/proc_nfm_audio.cpp @@ -53,6 +53,10 @@ void NarrowbandFMAudio::on_message(const Message* const message) { configure(*reinterpret_cast(message)); break; + case Message::ID::CaptureConfig: + capture_config(*reinterpret_cast(message)); + break; + default: break; } @@ -81,3 +85,11 @@ void NarrowbandFMAudio::configure(const NBFMConfigureMessage& message) { configured = true; } + +void NarrowbandFMAudio::capture_config(const CaptureConfigMessage& message) { + if( message.config ) { + audio_output.set_stream(std::make_unique(message.config)); + } else { + audio_output.set_stream(nullptr); + } +} diff --git a/firmware/baseband/proc_nfm_audio.hpp b/firmware/baseband/proc_nfm_audio.hpp index dbae4f5f..164e0b45 100644 --- a/firmware/baseband/proc_nfm_audio.hpp +++ b/firmware/baseband/proc_nfm_audio.hpp @@ -66,6 +66,7 @@ private: bool configured { false }; void configure(const NBFMConfigureMessage& message); + void capture_config(const CaptureConfigMessage& message); }; #endif/*__PROC_NFM_AUDIO_H__*/ diff --git a/firmware/baseband/proc_wfm_audio.cpp b/firmware/baseband/proc_wfm_audio.cpp index 4246a555..98de47b8 100644 --- a/firmware/baseband/proc_wfm_audio.cpp +++ b/firmware/baseband/proc_wfm_audio.cpp @@ -81,6 +81,10 @@ void WidebandFMAudio::on_message(const Message* const message) { configure(*reinterpret_cast(message)); break; + case Message::ID::CaptureConfig: + capture_config(*reinterpret_cast(message)); + break; + default: break; } @@ -110,3 +114,11 @@ void WidebandFMAudio::configure(const WFMConfigureMessage& message) { configured = true; } + +void WidebandFMAudio::capture_config(const CaptureConfigMessage& message) { + if( message.config ) { + audio_output.set_stream(std::make_unique(message.config)); + } else { + audio_output.set_stream(nullptr); + } +} diff --git a/firmware/baseband/proc_wfm_audio.hpp b/firmware/baseband/proc_wfm_audio.hpp index 15f5bf36..9752f90a 100644 --- a/firmware/baseband/proc_wfm_audio.hpp +++ b/firmware/baseband/proc_wfm_audio.hpp @@ -68,6 +68,7 @@ private: bool configured { false }; void configure(const WFMConfigureMessage& message); + void capture_config(const CaptureConfigMessage& message); }; #endif/*__PROC_WFM_AUDIO_H__*/ diff --git a/firmware/baseband/stream_input.hpp b/firmware/baseband/stream_input.hpp index 1b1ec552..e8221477 100644 --- a/firmware/baseband/stream_input.hpp +++ b/firmware/baseband/stream_input.hpp @@ -32,18 +32,12 @@ class StreamInput { public: - StreamInput(const size_t K) : + StreamInput(const size_t K, CaptureConfig& config) : K { K }, data { std::make_unique(1UL << K) }, fifo { data.get(), K } { - // TODO: Send stream creation message. - shared_memory.FIFO_HACK = &fifo; - } - - ~StreamInput() { - // TODO: Send stream distruction message. - shared_memory.FIFO_HACK = nullptr; + config.fifo = &fifo; } size_t write(const void* const data, const size_t length) { diff --git a/firmware/chibios-portapack/os/hal/platforms/LPC43xx/i2c_lld.c b/firmware/chibios-portapack/os/hal/platforms/LPC43xx/i2c_lld.c index fa7ffd38..5d0fbb83 100644 --- a/firmware/chibios-portapack/os/hal/platforms/LPC43xx/i2c_lld.c +++ b/firmware/chibios-portapack/os/hal/platforms/LPC43xx/i2c_lld.c @@ -187,15 +187,15 @@ static void i2c_lld_abort_operation(I2CDriver *i2cp) { } static bool_t i2c_lld_tx_not_done(I2CDriver *i2cp) { - return i2cp->txidx < i2cp->txbytes; + return i2cp->txbytes > 0; } static bool_t i2c_lld_rx_not_done(I2CDriver *i2cp) { - return i2cp->rxbuf && i2cp->rxbytes; + return i2cp->rxbytes > 0; } static bool_t i2c_lld_rx_last_byte(I2CDriver *i2cp) { - return i2cp->rxidx == (i2cp->rxbytes - 1); + return i2cp->rxbytes == 1; } /** @@ -249,7 +249,8 @@ static void i2c_lld_serve_event_interrupt(I2CDriver *i2cp) { case I2C_MASTER_TX_DATA_ACK: /* 0x28 */ if (i2c_lld_tx_not_done(i2cp)) { //i2c_periph_transmit_byte(dp, i2cp->txbuf[i2cp->txidx++]); - dp->DAT = i2cp->txbuf[i2cp->txidx++]; + dp->DAT = *i2cp->txbuf++; + i2cp->txbytes--; dp->CONCLR = I2C_CONCLR_SIC; } else { if (i2c_lld_rx_not_done(i2cp)) { @@ -266,7 +267,8 @@ static void i2c_lld_serve_event_interrupt(I2CDriver *i2cp) { break; case I2C_MASTER_RX_DATA_ACK: /* 0x50 */ - i2cp->rxbuf[i2cp->rxidx++] = i2c_periph_read_byte(dp); + *i2cp->rxbuf++ = i2c_periph_read_byte(dp); + i2cp->rxbytes--; /* fall through */ case I2C_MASTER_RX_ADDR_ACK: /* 0x40 */ if (i2c_lld_rx_last_byte(i2cp)) { @@ -277,7 +279,8 @@ static void i2c_lld_serve_event_interrupt(I2CDriver *i2cp) { break; case I2C_MASTER_RX_DATA_NACK: /* 0x58 */ - i2cp->rxbuf[i2cp->rxidx] = i2c_periph_read_byte(dp); + *i2cp->rxbuf++ = i2c_periph_read_byte(dp); + i2cp->rxbytes--; i2c_periph_stop(dp); wakeup_isr(i2cp, RDY_OK); /* fall through */ @@ -474,10 +477,8 @@ static msg_t i2c_lld_master_start(I2CDriver *i2cp, uint_fast8_t addr_r, i2cp->addr_r = addr_r; i2cp->txbuf = txbuf; i2cp->txbytes = txbytes; - i2cp->txidx = 0; i2cp->rxbuf = rxbuf; i2cp->rxbytes = rxbytes; - i2cp->rxidx = 0; /* Atomic check on the timer in order to make sure that a timeout didn't happen outside the critical zone.*/ diff --git a/firmware/chibios-portapack/os/hal/platforms/LPC43xx/i2c_lld.h b/firmware/chibios-portapack/os/hal/platforms/LPC43xx/i2c_lld.h index 3db4a803..71e4c5d0 100644 --- a/firmware/chibios-portapack/os/hal/platforms/LPC43xx/i2c_lld.h +++ b/firmware/chibios-portapack/os/hal/platforms/LPC43xx/i2c_lld.h @@ -169,10 +169,6 @@ struct I2CDriver { * @brief Number of bytes of data to send. */ size_t txbytes; - /** - * @brief Current index in buffer when sending data. - */ - size_t txidx; /** * @brief Pointer to the buffer to put received data. */ @@ -181,10 +177,6 @@ struct I2CDriver { * @brief Number of bytes of data to receive. */ size_t rxbytes; - /** - * @brief Current index in buffer when receiving data. - */ - size_t rxidx; /** * @brief Pointer to the I2Cx registers block. */ diff --git a/firmware/chibios-portapack/os/hal/platforms/LPC43xx/sdc_lld.c b/firmware/chibios-portapack/os/hal/platforms/LPC43xx/sdc_lld.c index 66226413..5e31b6b6 100644 --- a/firmware/chibios-portapack/os/hal/platforms/LPC43xx/sdc_lld.c +++ b/firmware/chibios-portapack/os/hal/platforms/LPC43xx/sdc_lld.c @@ -580,6 +580,11 @@ void sdc_lld_start(SDCDriver *sdcp) { sdio_reset(); sdio_reset_card(); + // UM10503 recommendation + LPC_SCU->SDDELAY = + (0x8 << 0) + | (0xf << 8) + ; LPC_SDMMC->CTRL = (1U << 4) /* INT_ENABLE */ | (1U << 25) /* USE_INTERNAL_DMAC */ diff --git a/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC4357_M4_flash.ld b/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC4357_M4_flash.ld deleted file mode 100644 index cf5eb049..00000000 --- a/firmware/chibios-portapack/os/ports/GCC/ARMCMx/LPC43xx_M4/ld/LPC4357_M4_flash.ld +++ /dev/null @@ -1,146 +0,0 @@ -/* - ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio - Copyright (C) 2014 Jared Boone, ShareBrained Technology - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -/* - * LPC43xx M4 memory setup. - */ -__main_stack_size__ = 0x1000; /* Exceptions/interrupts stack */ -__process_stack_size__ = 0x6000; /* main() stack */ - -MEMORY -{ - flash : org = 0x00000000, len = 512k /* Flash bank A @ 0x1a000000 */ - ram : org = 0x10080000, len = 40k /* Local SRAM @ 0x10080000 */ -} - -__ram_start__ = ORIGIN(ram); -__ram_size__ = LENGTH(ram); -__ram_end__ = __ram_start__ + __ram_size__; - -ENTRY(ResetHandler) - -SECTIONS -{ - . = 0; - _text = .; - - startup : ALIGN(16) SUBALIGN(16) - { - KEEP(*(vectors)) - } > flash - - constructors : ALIGN(4) SUBALIGN(4) - { - PROVIDE(__init_array_start = .); - KEEP(*(SORT(.init_array.*))) - KEEP(*(.init_array)) - PROVIDE(__init_array_end = .); - } > flash - - destructors : ALIGN(4) SUBALIGN(4) - { - PROVIDE(__fini_array_start = .); - KEEP(*(.fini_array)) - KEEP(*(SORT(.fini_array.*))) - PROVIDE(__fini_array_end = .); - } > flash - - .text : ALIGN(16) SUBALIGN(16) - { - *(.text.startup.*) - *(.text) - *(.text.*) - *(.rodata) - *(.rodata.*) - *(.glue_7t) - *(.glue_7) - *(.gcc*) - } > flash - - .ARM.extab : - { - *(.ARM.extab* .gnu.linkonce.armextab.*) - } > flash - - .ARM.exidx : { - PROVIDE(__exidx_start = .); - *(.ARM.exidx* .gnu.linkonce.armexidx.*) - PROVIDE(__exidx_end = .); - } > flash - - .eh_frame_hdr : - { - *(.eh_frame_hdr) - } > flash - - .eh_frame : ONLY_IF_RO - { - *(.eh_frame) - } > flash - - .textalign : ONLY_IF_RO - { - . = ALIGN(8); - } > flash - - . = ALIGN(4); - _etext = .; - _textdata = _etext; - - .stacks : - { - . = ALIGN(8); - __main_stack_base__ = .; - . += __main_stack_size__; - . = ALIGN(8); - __main_stack_end__ = .; - __process_stack_base__ = .; - __main_thread_stack_base__ = .; - . += __process_stack_size__; - . = ALIGN(8); - __process_stack_end__ = .; - __main_thread_stack_end__ = .; - } > ram - - .data ALIGN(4) : ALIGN(4) - { - . = ALIGN(4); - PROVIDE(_data = .); - *(.data) - *(.data.*) - *(.ramtext) - . = ALIGN(4); - PROVIDE(_edata = .); - } > ram AT > flash - - .bss ALIGN(4) : ALIGN(4) - { - . = ALIGN(4); - PROVIDE(_bss_start = .); - *(.bss) - *(.bss.*) - *(COMMON) - . = ALIGN(4); - PROVIDE(_bss_end = .); - } > ram -} - -PROVIDE(end = .); -_end = .; - -__heap_base__ = _end; -__heap_end__ = __ram_end__; diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 1f2c7b67..e4727502 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "baseband_packet.hpp" #include "ert_packet.hpp" @@ -61,13 +62,15 @@ public: ChannelSpectrumConfig = 14, SpectrumStreamingConfig = 15, DisplaySleep = 16, - TXDone = 17, - Retune = 18, - ReadyForSwitch = 19, - AFSKData = 20, - ModuleID = 21, - FIFOSignal = 22, - FIFOData = 23, + CaptureConfig = 17, + + TXDone = 20, + Retune = 21, + ReadyForSwitch = 22, + AFSKData = 23, + ModuleID = 24, + FIFOSignal = 25, + FIFOData = 26, MAX }; @@ -416,6 +419,46 @@ public: const iir_biquad_config_t audio_hpf_config; }; +struct CaptureConfig { + const size_t write_size_log2; + const size_t buffer_count_log2; + uint64_t baseband_bytes_received; + uint64_t baseband_bytes_dropped; + FIFO* fifo; + + constexpr CaptureConfig( + const size_t write_size_log2, + const size_t buffer_count_log2 + ) : write_size_log2 { write_size_log2 }, + buffer_count_log2 { buffer_count_log2 }, + baseband_bytes_received { 0 }, + baseband_bytes_dropped { 0 }, + fifo { nullptr } + { + } + + size_t dropped_percent() const { + if( baseband_bytes_dropped == 0 ) { + return 0; + } else { + const size_t percent = baseband_bytes_dropped * 100U / baseband_bytes_received; + return std::max(1U, percent); + } + } +}; + +class CaptureConfigMessage : public Message { +public: + constexpr CaptureConfigMessage( + CaptureConfig* const config + ) : Message { ID::CaptureConfig }, + config { config } + { + } + + CaptureConfig* const config; +}; + class TXDoneMessage : public Message { public: TXDoneMessage( diff --git a/firmware/common/message_queue.cpp b/firmware/common/message_queue.cpp index 54e9a233..3001127d 100644 --- a/firmware/common/message_queue.cpp +++ b/firmware/common/message_queue.cpp @@ -20,3 +20,18 @@ */ #include "message_queue.hpp" + +#include "lpc43xx_cpp.hpp" +using namespace lpc43xx; + +#if defined(LPC43XX_M0) +void MessageQueue::signal() { + creg::m0apptxevent::assert(); +} +#endif + +#if defined(LPC43XX_M4) +void MessageQueue::signal() { + creg::m4txevent::assert(); +} +#endif diff --git a/firmware/common/message_queue.hpp b/firmware/common/message_queue.hpp index 76404be0..3e0a1cbb 100644 --- a/firmware/common/message_queue.hpp +++ b/firmware/common/message_queue.hpp @@ -27,9 +27,6 @@ #include "message.hpp" #include "fifo.hpp" -#include "lpc43xx_cpp.hpp" -using namespace lpc43xx; - #include class MessageQueue { @@ -73,6 +70,10 @@ public: } } + bool is_empty() const { + return fifo.is_empty(); + } + private: FIFO fifo; Mutex mutex_write; @@ -95,10 +96,6 @@ private: return fifo.len(); } - bool is_empty() const { - return fifo.is_empty(); - } - bool push(const void* const buf, const size_t len) { chMtxLock(&mutex_write); const auto result = fifo.in_r(buf, len); @@ -111,18 +108,7 @@ private: return success; } - -#if defined(LPC43XX_M0) - void signal() { - creg::m0apptxevent::assert(); - } -#endif - -#if defined(LPC43XX_M4) - void signal() { - creg::m4txevent::assert(); - } -#endif + void signal(); }; #endif/*__MESSAGE_QUEUE_H__*/ diff --git a/firmware/common/png_writer.cpp b/firmware/common/png_writer.cpp index 15e1b8aa..c1d59244 100644 --- a/firmware/common/png_writer.cpp +++ b/firmware/common/png_writer.cpp @@ -51,9 +51,8 @@ static constexpr std::array png_iend { { PNGWriter::PNGWriter( const std::string& filename -) +) : file { filename, File::openmode::out | File::openmode::binary | File::openmode::trunc } { - file.open_for_writing(filename); file.write(png_file_header); file.write(png_ihdr_screen_capture); diff --git a/firmware/common/portapack_shared_memory.hpp b/firmware/common/portapack_shared_memory.hpp index 266b9493..8fbd689c 100644 --- a/firmware/common/portapack_shared_memory.hpp +++ b/firmware/common/portapack_shared_memory.hpp @@ -47,7 +47,6 @@ struct SharedMemory { uint8_t baseband_queue_data[1 << baseband_queue_k]; MessageQueue application_queue; uint8_t application_queue_data[1 << application_queue_k]; - void* FIFO_HACK; // TODO: M0 should directly configure and control DMA channel that is // acquiring ADC samples. diff --git a/firmware/common/ui_focus.cpp b/firmware/common/ui_focus.cpp index 08310039..92547d35 100644 --- a/firmware/common/ui_focus.cpp +++ b/firmware/common/ui_focus.cpp @@ -85,67 +85,78 @@ static void widget_collect_visible(Widget* const w, TestFn test, test_collection } } +static int32_t rect_distances( + const KeyEvent direction, + const Rect& rect_start, + const Rect& rect_end +) { + Coord on_axis_max, on_axis_min; + + switch(direction) { + case KeyEvent::Right: + on_axis_max = rect_end.left(); + on_axis_min = rect_start.right(); + break; + + case KeyEvent::Left: + on_axis_max = rect_start.left(); + on_axis_min = rect_end.right(); + break; + + case KeyEvent::Down: + on_axis_max = rect_end.top(); + on_axis_min = rect_start.bottom(); + break; + + case KeyEvent::Up: + on_axis_max = rect_start.top(); + on_axis_min = rect_end.bottom(); + break; + + default: + return -1; + } + + Coord on_axis_distance = on_axis_max - on_axis_min; + if( on_axis_distance < 0 ) { + return -1; + } + + Coord perpendicular_axis_start, perpendicular_axis_end; + + switch(direction) { + case KeyEvent::Right: + case KeyEvent::Left: + perpendicular_axis_start = rect_start.center().y; + perpendicular_axis_end = rect_end.center().y; + break; + + case KeyEvent::Up: + case KeyEvent::Down: + perpendicular_axis_start = rect_start.center().x; + perpendicular_axis_end = rect_end.center().x; + break; + + default: + return -1; + } + + return (std::abs(perpendicular_axis_end - perpendicular_axis_start) + 1) * (on_axis_distance + 1); +} + void FocusManager::update( Widget* const top_widget, const KeyEvent event ) { if( focus_widget() ) { const auto focus_screen_rect = focus_widget()->screen_rect(); - const auto center = focus_screen_rect.center(); - const auto test_fn = [¢er, event](ui::Widget* const w) -> test_result_t { + const auto test_fn = [&focus_screen_rect, event](ui::Widget* const w) -> test_result_t { // if( w->visible() && w->focusable() ) { if( w->focusable() ) { - const Point delta = w->screen_rect().center() - center; - - /* Heuristic to compute closeness. */ - /* TODO: Look at metric involving overlap of current - * widget rectangle in the direction of movement, with - * all the prospective widgets. - */ - switch(event) { - case KeyEvent::Right: - if( delta.x > 0 ) { - if( delta.y == 0 ) { - return { w, delta.x }; - } else { - return { w, delta.x * abs(delta.y) + 1000 }; - } - } - break; - - case KeyEvent::Left: - if( delta.x < 0 ) { - if( delta.y == 0 ) { - return { w, -delta.x }; - } else { - return { w, -delta.x * abs(delta.y) + 1000 }; - } - } - break; - - case KeyEvent::Down: - if( delta.y > 0 ) { - if( delta.x == 0 ) { - return { w, delta.y }; - } else { - return { w, delta.y * abs(delta.x) + 1000 }; - } - } - break; - - case KeyEvent::Up: - if( delta.y < 0 ) { - if( delta.x == 0 ) { - return { w, -delta.y }; - } else { - return { w, -delta.y * abs(delta.x) + 1000 }; - } - } - break; - - default: - break; + const auto distance = rect_distances(event, focus_screen_rect, w->screen_rect()); + if( distance >= 0 ) { + return { w, distance }; } } @@ -167,34 +178,5 @@ void FocusManager::update( } } } -#if 0 -void FocusManager::update( - Widget* const top_widget, - const TouchEvent event -) { - const auto test_fn = [event](Widget* const w) -> test_result_t { - if( w->focusable() ) { - const auto r = w->screen_rect(); - if( r.contains(event) ) { - return { w, 0 }; - } - } - return { nullptr, { } }; - }; - - test_collection_t collection; - widget_collect_visible( - top_widget, test_fn, - collection - ); - - if( !collection.empty() ) { - // Take the last object in the collection, it will be rendered last, - // therefore appear "on top". - const auto touched = collection.back().first; - touched->on_touch(event); - } -} -#endif } /* namespace ui */ diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index d8a0aee9..f9426141 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -38,7 +38,7 @@ Size Widget::size() const { return parent_rect.size; } -Rect Widget::screen_rect() { +Rect Widget::screen_rect() const { return parent() ? (parent_rect + parent()->screen_pos()) : parent_rect; } diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 4a22f3f2..1f432338 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -51,12 +51,12 @@ private: class Widget { public: - constexpr Widget( + Widget( ) : parent_rect { } { } - constexpr Widget( + Widget( Rect parent_rect ) : parent_rect { parent_rect } { @@ -69,7 +69,7 @@ public: Point screen_pos(); Size size() const; - Rect screen_rect(); + Rect screen_rect() const; virtual void set_parent_rect(const Rect new_parent_rect); Widget* parent() const;