diff --git a/firmware/application/Makefile b/firmware/application/Makefile index a6bb3cb9..31201908 100755 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -164,6 +164,7 @@ CPPSRC = main.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_spectrum.cpp \ @@ -189,6 +190,7 @@ CPPSRC = main.cpp \ ../common/chibios_cpp.cpp \ ../common/debug.cpp \ ../common/gcc.cpp \ + ../common/lfsr_random.cpp \ core_control.cpp \ cpld_max5.cpp \ jtag.cpp \ diff --git a/firmware/application/ui_debug.cpp b/firmware/application/ui_debug.cpp index fda852bc..e3d673a0 100644 --- a/firmware/application/ui_debug.cpp +++ b/firmware/application/ui_debug.cpp @@ -28,6 +28,8 @@ #include "audio.hpp" +#include "ui_sd_card_debug.hpp" + namespace ui { /* DebugMemoryView *******************************************************/ @@ -272,7 +274,7 @@ DebugMenuView::DebugMenuView(NavigationView& nav) { add_items<5>({ { { "Memory", [&nav](){ nav.push(); } }, { "Radio State", [&nav](){ nav.push(); } }, - { "SD Card", [&nav](){ nav.push(); } }, + { "SD Card", [&nav](){ nav.push(); } }, { "Peripherals", [&nav](){ nav.push(); } }, { "Temperature", [&nav](){ nav.push(); } }, } }); diff --git a/firmware/application/ui_sd_card_debug.cpp b/firmware/application/ui_sd_card_debug.cpp new file mode 100644 index 00000000..25d9ba5d --- /dev/null +++ b/firmware/application/ui_sd_card_debug.cpp @@ -0,0 +1,409 @@ +/* + * 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_sd_card_debug.hpp" + +#include "string_format.hpp" + +#include "file.hpp" +#include "lfsr_random.hpp" + +#include "ff.h" + +#include "ch.h" +#include "hal.h" + +class SDCardTestThread { +public: + enum Result { + FailCompare = -8, + FailReadIncomplete = -7, + FailWriteIncomplete = -6, + FailAbort = -5, + FailFileOpenRead = -4, + FailFileOpenWrite = -3, + FailHeap = -2, + FailThread = -1, + Incomplete = 0, + OK = 1, + }; + + struct Stats { + halrtcnt_t write_duration_min { 0 }; + halrtcnt_t write_duration_max { 0 }; + halrtcnt_t write_test_duration { 0 }; + size_t write_bytes { 0 }; + size_t write_count { 0 }; + + halrtcnt_t read_duration_min { 0 }; + halrtcnt_t read_duration_max { 0 }; + halrtcnt_t read_test_duration { 0 }; + size_t read_bytes { 0 }; + size_t read_count { 0 }; + }; + + SDCardTestThread( + ) { + thread = chThdCreateFromHeap(NULL, 2048, NORMALPRIO + 10, SDCardTestThread::static_fn, this); + } + + Result result() const { + return _result; + } + + const Stats& stats() const { + return _stats; + } + + ~SDCardTestThread() { + chThdTerminate(thread); + chThdWait(thread); + } + +private: + static constexpr size_t write_size = 16384; + static constexpr size_t bytes_to_write = 16 * 1024 * 1024; + static constexpr size_t bytes_to_read = bytes_to_write; + + static Thread* thread; + volatile Result _result { Result::Incomplete }; + Stats _stats; + + static msg_t static_fn(void* arg) { + auto obj = static_cast(arg); + obj->_result = obj->run(); + return 0; + } + + Result run() { + const std::string filename { "_PPTEST_.DAT" }; + + const auto write_result = write(filename); + if( write_result != Result::OK ) { + return write_result; + } + + if( _stats.write_bytes < bytes_to_write ) { + return Result::FailWriteIncomplete; + } + + if( chThdShouldTerminate() ) { + return Result::FailAbort; + } + + const auto read_result = read(filename); + if( read_result != Result::OK ) { + return read_result; + } + + f_unlink(filename.c_str()); + + if( _stats.read_bytes < bytes_to_read ) { + return Result::FailReadIncomplete; + } + + if( chThdShouldTerminate() ) { + return Result::FailAbort; + } + + return Result::OK; + } + + Result write(const std::string& filename) { + const auto buffer = std::make_unique>(); + if( !buffer ) { + return Result::FailHeap; + } + + File file; + if( !file.open_for_writing(filename) ) { + 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) ) { + lfsr_fill(v, + reinterpret_cast(buffer->data()), + sizeof(*buffer.get()) / sizeof(lfsr_word_t) + ); + + const halrtcnt_t write_start = halGetCounterValue(); + if( !file.write(buffer->data(), buffer->size()) ) { + break; + } + const halrtcnt_t write_end = halGetCounterValue(); + _stats.write_bytes += buffer->size(); + _stats.write_count++; + + const halrtcnt_t write_duration = write_end - write_start; + if( (_stats.write_duration_min == 0) || (write_duration < _stats.write_duration_min) ) { + _stats.write_duration_min = write_duration; + } + if( write_duration > _stats.write_duration_max ) { + _stats.write_duration_max = write_duration; + } + } + + file.close(); + + const halrtcnt_t test_end = halGetCounterValue(); + _stats.write_test_duration = test_end - test_start; + + return Result::OK; + } + + Result read(const std::string& filename) { + const auto buffer = std::make_unique>(); + if( !buffer ) { + return Result::FailHeap; + } + + File file; + if( !file.open_for_reading(filename) ) { + 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) ) { + const halrtcnt_t read_start = halGetCounterValue(); + if( !file.read(buffer->data(), buffer->size()) ) { + break; + } + const halrtcnt_t read_end = halGetCounterValue(); + _stats.read_bytes += buffer->size(); + _stats.read_count++; + + const halrtcnt_t read_duration = read_end - read_start; + if( (_stats.read_duration_min == 0) || (read_duration < _stats.read_duration_min) ) { + _stats.read_duration_min = read_duration; + } + if( read_duration > _stats.read_duration_max ) { + _stats.read_duration_max = read_duration; + } + + if( !lfsr_compare(v, + reinterpret_cast(buffer->data()), + sizeof(*buffer.get()) / sizeof(lfsr_word_t)) + ) { + return Result::FailCompare; + } + } + + file.close(); + + const halrtcnt_t test_end = halGetCounterValue(); + _stats.read_test_duration = test_end - test_start; + + return Result::OK; + } +}; + +Thread* SDCardTestThread::thread { nullptr }; + +namespace ui { + +SDCardDebugView::SDCardDebugView(NavigationView& nav) { + add_children({ { + &text_title, + &text_detected_title, + &text_detected_value, + &text_bus_width_title, + &text_bus_width_value, + &text_card_mode_title, + &text_card_mode_value, + // &text_csd_title, + // &text_csd_value, + &text_block_size_title, + &text_block_size_value, + &text_block_count_title, + &text_block_count_value, + &text_capacity_title, + &text_capacity_value, + &text_test_write_time_title, + &text_test_write_time_value, + &text_test_write_rate_title, + &text_test_write_rate_value, + &text_test_read_time_title, + &text_test_read_time_value, + &text_test_read_rate_title, + &text_test_read_rate_value, + &button_test, + &button_ok, + } }); + + button_test.on_select = [this](Button&){ this->on_test(); }; + button_ok.on_select = [&nav](Button&){ nav.pop(); }; +} + +void SDCardDebugView::on_show() { + sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status status) { + this->on_status(status); + }; + on_status(sd_card::status()); +} + +void SDCardDebugView::on_hide() { + sd_card::status_signal -= sd_card_status_signal_token; +} + +void SDCardDebugView::focus() { + button_ok.focus(); +} + +void SDCardDebugView::on_status(const sd_card::Status) { + text_bus_width_value.set(""); + text_card_mode_value.set(""); + // text_csd_value.set(""); + text_block_size_value.set(""); + text_block_count_value.set(""); + text_capacity_value.set(""); + text_test_write_time_value.set(""); + text_test_write_rate_value.set(""); + text_test_read_time_value.set(""); + 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; + switch(card_width_flags) { + case 0x00000: card_width = 1; break; + case 0x00001: card_width = 4; break; + case 0x10001: card_width = 8; break; + default: break; + } + + 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)); + + 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" + ); + } + } + } +} + +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"; + } +} + +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"; + } +} + +void SDCardDebugView::on_test() { + text_test_write_time_value.set(""); + text_test_write_rate_value.set(""); + text_test_read_time_value.set(""); + text_test_read_rate_value.set(""); + + SDCardTestThread thread; + + // uint32_t spinner_phase = 0; + while( thread.result() == SDCardTestThread::Result::Incomplete ) { + chThdSleepMilliseconds(100); + + // spinner_phase += 1; + // char c = '*'; + // switch(spinner_phase % 4) { + // case 0: c = '-'; break; + // case 1: c = '\\'; break; + // case 2: c = '|'; break; + // case 3: c = '/'; break; + // default: c = '*'; break; + // } + // text_test_write_value.set({ c }); + } + + if( thread.result() == SDCardTestThread::Result::OK ) { + const auto stats = thread.stats(); + const auto write_duration_avg = stats.write_test_duration / stats.write_count; + + text_test_write_time_value.set( + format_ticks_as_ms(stats.write_duration_min) + "/" + + format_ticks_as_ms(write_duration_avg) + "/" + + format_ticks_as_ms(stats.write_duration_max) + ); + + text_test_write_rate_value.set( + format_bytes_per_ticks_as_mib(stats.write_bytes, stats.write_duration_min * stats.write_count) + " " + + format_bytes_per_ticks_as_mib(stats.write_bytes, stats.write_test_duration) + ); + + const auto read_duration_avg = stats.read_test_duration / stats.read_count; + + text_test_read_time_value.set( + format_ticks_as_ms(stats.read_duration_min) + "/" + + format_ticks_as_ms(read_duration_avg) + "/" + + format_ticks_as_ms(stats.read_duration_max) + ); + + text_test_read_rate_value.set( + format_bytes_per_ticks_as_mib(stats.read_bytes, stats.read_duration_min * stats.read_count) + " " + + format_bytes_per_ticks_as_mib(stats.read_bytes, stats.read_test_duration) + ); + } else { + text_test_write_time_value.set("Fail: " + to_string_dec_int(toUType(thread.result()), 4)); + } +} + +} /* namespace ui */ diff --git a/firmware/application/ui_sd_card_debug.hpp b/firmware/application/ui_sd_card_debug.hpp new file mode 100644 index 00000000..a573f3a1 --- /dev/null +++ b/firmware/application/ui_sd_card_debug.hpp @@ -0,0 +1,203 @@ +/* + * 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_SD_CARD_DEBUG_H__ +#define __UI_SD_CARD_DEBUG_H__ + +#include "ui_widget.hpp" +#include "ui_navigation.hpp" + +#include "sd_card.hpp" + +namespace ui { + +class SDCardDebugView : public View { +public: + SDCardDebugView(NavigationView& nav); + + void on_show() override; + void on_hide() override; + + void focus() override; + +private: + SignalToken sd_card_status_signal_token; + + void on_status(const sd_card::Status status); + void on_test(); + + Text text_title { + { (240 - (7 * 8)) / 2, 1 * 16, (7 * 8), 16 }, + "SD Card", + }; + + static constexpr size_t detected_characters = 3; + + Text text_detected_title { + { 0, 3 * 16, (8 * 8), 16 }, + "Detected", + }; + + Text text_detected_value { + { 240 - (detected_characters * 8), 3 * 16, (detected_characters * 8), 16 }, + "", + }; + + static constexpr size_t bus_width_characters = 1; + + Text text_bus_width_title { + { 0, 5 * 16, (9 * 8), 16 }, + "Bus width", + }; + + Text text_bus_width_value { + { 240 - (bus_width_characters * 8), 5 * 16, (bus_width_characters * 8), 16 }, + "", + }; + + static constexpr size_t card_mode_characters = 10; + + Text text_card_mode_title { + { 0, 6 * 16, (9 * 8), 16 }, + "Card mode", + }; + + Text text_card_mode_value { + { 240 - (card_mode_characters * 8), 6 * 16, (card_mode_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 { + { 0, 8 * 16, (10 * 8), 16 }, + "Block size", + }; + + Text text_block_size_value { + { 240 - (block_size_characters * 8), 8 * 16, (block_size_characters * 8), 16 }, + "", + }; + + static constexpr size_t block_count_characters = 9; + + Text text_block_count_title { + { 0, 9 * 16, (11 * 8), 16 }, + "Block count", + }; + + Text text_block_count_value { + { 240 - (block_count_characters * 8), 9 * 16, (block_count_characters * 8), 16 }, + "", + }; + + static constexpr size_t capacity_characters = 10; + + Text text_capacity_title { + { 0, 10 * 16, (8 * 8), 16 }, + "Capacity", + }; + + Text text_capacity_value { + { 240 - (capacity_characters * 8), 10 * 16, (capacity_characters * 8), 16 }, + "", + }; + + /////////////////////////////////////////////////////////////////////// + + static constexpr size_t test_write_time_characters = 23; + + Text text_test_write_time_title { + { 0, 12 * 16, (4 * 8), 16 }, + "W ms", + }; + + Text text_test_write_time_value { + { 240 - (test_write_time_characters * 8), 12 * 16, (test_write_time_characters * 8), 16 }, + "", + }; + + static constexpr size_t test_write_rate_characters = 23; + + Text text_test_write_rate_title { + { 0, 13 * 16, (6 * 8), 16 }, + "W MB/s", + }; + + Text text_test_write_rate_value { + { 240 - (test_write_rate_characters * 8), 13 * 16, (test_write_rate_characters * 8), 16 }, + "", + }; + + /////////////////////////////////////////////////////////////////////// + + static constexpr size_t test_read_time_characters = 23; + + Text text_test_read_time_title { + { 0, 14 * 16, (4 * 8), 16 }, + "R ms", + }; + + Text text_test_read_time_value { + { 240 - (test_read_time_characters * 8), 14 * 16, (test_read_time_characters * 8), 16 }, + "", + }; + + static constexpr size_t test_read_rate_characters = 23; + + Text text_test_read_rate_title { + { 0, 15 * 16, (6 * 8), 16 }, + "R MB/s", + }; + + Text text_test_read_rate_value { + { 240 - (test_read_rate_characters * 8), 15 * 16, (test_read_rate_characters * 8), 16 }, + "", + }; + + /////////////////////////////////////////////////////////////////////// + + Button button_test { + { 16, 17 * 16, 96, 24 }, + "Test" + }; + + Button button_ok { + { 240 - 96 - 16, 17 * 16, 96, 24 }, + "OK" + }; +}; + +} /* namespace ui */ + +#endif/*__UI_SD_CARD_DEBUG_H__*/