diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index fee94a09..50dc53bb 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -148,6 +148,7 @@ set(CPPSRC ui_audio.cpp ui_font_fixed_8x16.cpp ui_setup.cpp + ui_touch_calibration.cpp ui_debug.cpp ui_baseband_stats_view.cpp ui_sd_card_status_view.cpp diff --git a/firmware/application/ui_setup.cpp b/firmware/application/ui_setup.cpp index 5a10b20e..9bc75389 100644 --- a/firmware/application/ui_setup.cpp +++ b/firmware/application/ui_setup.cpp @@ -21,6 +21,8 @@ #include "ui_setup.hpp" +#include "ui_touch_calibration.hpp" + #include "portapack_persistent_memory.hpp" #include "lpc43xx_cpp.hpp" using namespace lpc43xx; @@ -196,7 +198,7 @@ SetupMenuView::SetupMenuView(NavigationView& nav) { { "Date/Time", [&nav](){ nav.push(); } }, { "Frequency Correction", [&nav](){ nav.push(); } }, { "Antenna Bias Voltage", [&nav](){ nav.push(); } }, - { "Touch", [&nav](){ nav.push(); } }, + { "Touch", [&nav](){ nav.push(); } }, } }); on_left = [&nav](){ nav.pop(); }; } diff --git a/firmware/application/ui_touch_calibration.cpp b/firmware/application/ui_touch_calibration.cpp new file mode 100644 index 00000000..d812535d --- /dev/null +++ b/firmware/application/ui_touch_calibration.cpp @@ -0,0 +1,207 @@ +/* + * 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_touch_calibration.hpp" + +#include "irq_controls.hpp" + +#if defined(TOUCH_DEBUG) +#include "string_format.hpp" +#endif + +namespace ui { + +TouchCalibrationView::TouchCalibrationView( + NavigationView& nav +) : nav { nav }, + calibration { touch::default_calibration() } +{ + add_children({ { + &image_calibrate_0, + &image_calibrate_1, + &image_calibrate_2, + &image_verify_0, + &image_verify_1, + &image_verify_2, + &label_calibrate, + &label_verify, + &label_success, + &label_failure, + &button_cancel, + &button_ok, + } }); + + button_cancel.on_select = [this](Button&){ this->on_cancel(); }; + button_ok.on_select = [this](Button&){ this->on_ok(); }; + + set_phase(Phase::Calibrate0); +} + +void TouchCalibrationView::focus() { + button_cancel.focus(); +} + +void TouchCalibrationView::update_target() { + const auto phase_calibrate = (phase == Phase::Calibrate0) || (phase == Phase::Calibrate1) || (phase == Phase::Calibrate2); + const auto phase_verify = (phase == Phase::Verify0) || (phase == Phase::Verify1) || (phase == Phase::Verify2); + + image_calibrate_0.hidden(phase != Phase::Calibrate0); + image_calibrate_1.hidden(phase != Phase::Calibrate1); + image_calibrate_2.hidden(phase != Phase::Calibrate2); + + image_verify_0.hidden(phase != Phase::Verify0); + image_verify_1.hidden(phase != Phase::Verify1); + image_verify_2.hidden(phase != Phase::Verify2); + + label_calibrate.hidden(!phase_calibrate); + label_verify.hidden(!phase_verify); + label_success.hidden(phase != Phase::Success); + label_failure.hidden(phase != Phase::Failure); + + button_ok.hidden((phase != Phase::Success) && (phase != Phase::Failure)); + + /* TODO: Such a hack to get around a poor repaint implementation! This "technique" + * occurs in other places... + */ + set_dirty(); +} + +void TouchCalibrationView::set_phase(const Phase value) { + if( value != phase ) { + phase = value; + update_target(); + } +} + +uint32_t TouchCalibrationView::distance_squared(const Point& touch_point, const Image& target) { + const auto target_point = target.screen_rect().center(); + const int32_t dx = target_point.x - touch_point.x; + const int32_t dy = target_point.y - touch_point.y; + const uint32_t dx2 = dx * dx; + const uint32_t dy2 = dy * dy; + return dx2 + dy2; +} + +void TouchCalibrationView::touch_complete() { + auto next_phase = static_cast(toUType(phase) + 1); + + switch(phase) { + case Phase::Calibrate0: + case Phase::Verify0: + digitizer_points[0] = average; + break; + + case Phase::Calibrate1: + case Phase::Verify1: + digitizer_points[1] = average; + break; + + case Phase::Calibrate2: + case Phase::Verify2: + digitizer_points[2] = average; + break; + + default: + break; + } + + if( phase == Phase::Calibrate2 ) { + const std::array display_points { { + image_calibrate_0.screen_rect().center(), + image_calibrate_1.screen_rect().center(), + image_calibrate_2.screen_rect().center(), + } }; + + calibration = { digitizer_points, display_points }; + } + + if( phase == Phase::Verify2 ) { + const auto calibrated_0 = calibration.translate(digitizer_points[0]); + const auto d_sq_0 = distance_squared(calibrated_0, image_verify_0); + + const auto calibrated_1 = calibration.translate(digitizer_points[1]); + const auto d_sq_1 = distance_squared(calibrated_1, image_verify_1); + + const auto calibrated_2 = calibration.translate(digitizer_points[2]); + const auto d_sq_2 = distance_squared(calibrated_2, image_verify_2); + + if( (d_sq_0 < verify_d_sq_max) && (d_sq_1 < verify_d_sq_max) && (d_sq_2 < verify_d_sq_max) ) { + next_phase = Phase::Success; + } else { + next_phase = Phase::Failure; + } + } + + set_phase(next_phase); +} + +void TouchCalibrationView::on_ok() { + if( phase == Phase::Success ) { + touch::set_calibration(calibration); + nav.pop(); + } + if( phase == Phase::Failure ) { + set_phase(Phase::Calibrate0); + } +} + +void TouchCalibrationView::on_cancel() { + nav.pop(); +} + +void TouchCalibrationView::on_frame_sync() { + switch(phase) { + case Phase::Calibrate0: + case Phase::Calibrate1: + case Phase::Calibrate2: + case Phase::Verify0: + case Phase::Verify1: + case Phase::Verify2: + break; + + default: + return; + } + + const auto frame = get_touch_frame(); + const auto metrics = touch::calculate_metrics(frame); + const auto x = metrics.x * 1024; + const auto y = metrics.y * 1024; + + if( metrics.r < 1000.0f ) { + if( samples_count > 0 ) { + average.x = ((average.x * 7) + x) / 8; + average.y = ((average.y * 7) + y) / 8; + } else { + average.x = x; + average.y = y; + } + + samples_count += 1; + } else { + if( samples_count >= samples_limit ) { + touch_complete(); + } + samples_count = 0; + } +} + +} /* namespace ui */ diff --git a/firmware/application/ui_touch_calibration.hpp b/firmware/application/ui_touch_calibration.hpp new file mode 100644 index 00000000..8e2d9b08 --- /dev/null +++ b/firmware/application/ui_touch_calibration.hpp @@ -0,0 +1,161 @@ +/* + * 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_TOUCH_CALIBRATION_HPP__ +#define __UI_TOUCH_CALIBRATION_HPP__ + +#include "ui_widget.hpp" +#include "ui_navigation.hpp" +#include "touch.hpp" + +namespace ui { + +class TouchCalibrationView : public View { +public: + TouchCalibrationView(NavigationView& nav); + + void focus() override; + +private: + enum class Phase { + Init, + Calibrate0, + Calibrate1, + Calibrate2, + Verify0, + Verify1, + Verify2, + Success, + Failure, + }; + + NavigationView& nav; + Phase phase { Phase::Init }; + + void update_target(); + + void set_phase(const Phase value); + + uint32_t distance_squared(const Point& touch_point, const Image& target); + + void touch_complete(); + void on_ok(); + void on_cancel(); + + const uint32_t samples_limit { 40 }; + const uint32_t verify_d_sq_max = 10 * 10; + + uint32_t samples_count { 0 }; + + touch::DigitizerPoint average; + + std::array digitizer_points; + + touch::Calibration calibration; + + static constexpr ui::Coord y_touch_values = 320 - 16 - 16; + static constexpr ui::Coord y_xlate_values = y_touch_values - 16; + + Image image_calibrate_0 { + { 32 - 16, 32 - 16, 32, 32 }, + &bitmap_target_calibrate, + Color::white(), + Color::black() + }; + + Image image_calibrate_1 { + { 240 - 32 - 16, (320 - 16) / 2 - 16, 32, 32 }, + &bitmap_target_calibrate, + Color::white(), + Color::black() + }; + + Image image_calibrate_2 { + { 240 / 2 - 16, (320 - 16) - 32 - 16, 32, 32 }, + &bitmap_target_calibrate, + Color::white(), + Color::black() + }; + + Image image_verify_0 { + { 32 - 16, 32 - 16, 32, 32 }, + &bitmap_target_verify, + Color::white(), + Color::black() + }; + + Image image_verify_1 { + { 240 - 32 - 16, (320 - 16) / 2 - 16, 32, 32 }, + &bitmap_target_verify, + Color::white(), + Color::black() + }; + + Image image_verify_2 { + { 240 / 2 - 16, (320 - 16) - 32 - 16, 32, 32 }, + &bitmap_target_verify, + Color::white(), + Color::black() + }; + + Text label_calibrate { + { 16, 5 * 16, 26 * 8, 1 * 16 }, + "Touch targets to calibrate" + }; + + Text label_verify { + { 28, 5 * 16, 23 * 8, 1 * 16 }, + "Touch targets to verify" + }; + + Text label_success { + { 32, 5 * 16, 22 * 8, 1 * 16 }, + "Apply new calibration?" + }; + + Text label_failure { + { 16, 5 * 16, 26 * 8, 1 * 16 }, + "Calibration failed. Retry?" + }; + + Button button_cancel { + { 40, 200, 64, 24 }, + "Cancel" + }; + + Button button_ok { + { 136, 200, 64, 24 }, + "OK" + }; + + void on_frame_sync(); + + MessageHandlerRegistration message_handler_frame_sync { + Message::ID::DisplayFrameSync, + [this](const Message* const) { + this->on_frame_sync(); + } + }; +}; + +} /* namespace ui */ + +#endif/*__UI_TOUCH_CALIBRATION_HPP__*/