From f1ad9796741daa8368f4885bbce360522df24367 Mon Sep 17 00:00:00 2001 From: Clyne Sullivan Date: Mon, 19 Oct 2020 21:01:53 -0400 Subject: [PATCH] gui improvements; code templates --- gui/templates/1_convolve_simple.cpp | 29 ++++ gui/templates/2_convolve_overlap_save.cpp | 47 ++++++ gui/templates/3_fir.cpp | 47 ++++++ gui/wxapp.hpp | 9 +- gui/wxmain.cpp | 186 +++++++++++++++++----- gui/wxmain.hpp | 10 +- 6 files changed, 283 insertions(+), 45 deletions(-) create mode 100644 gui/templates/1_convolve_simple.cpp create mode 100644 gui/templates/2_convolve_overlap_save.cpp create mode 100644 gui/templates/3_fir.cpp diff --git a/gui/templates/1_convolve_simple.cpp b/gui/templates/1_convolve_simple.cpp new file mode 100644 index 0000000..0f1973d --- /dev/null +++ b/gui/templates/1_convolve_simple.cpp @@ -0,0 +1,29 @@ +/** + * 1_convolve_simple.cpp + * Written by Clyne Sullivan. + * + * Computes a convolution in the simplest way possible. While the code is brief, it lacks many + * possible optimizations. The convolution's result will not fill the output buffer either, as the + * transient response is not calculated. + */ + +adcsample_t *process_data(adcsample_t *samples, unsigned int size) +{ + // Define our output buffer. SIZE is the largest size of the 'samples' buffer. + static adcsample_t buffer[SIZE]; + + // Define our filter + constexpr unsigned int filter_size = 3; + float filter[filter_size] = { + 0.3333, 0.3333, 0.3333 + }; + + // Begin convolving: + for (int n = 0; n < size - (filter_size - 1); n++) { + buffer[n] = 0; + for (int k = 0; k < filter_size; k++) + buffer[n] += samples[n + k] * filter[k]; + } + + return buffer; +} diff --git a/gui/templates/2_convolve_overlap_save.cpp b/gui/templates/2_convolve_overlap_save.cpp new file mode 100644 index 0000000..1387d7f --- /dev/null +++ b/gui/templates/2_convolve_overlap_save.cpp @@ -0,0 +1,47 @@ +/** + * 2_convolve_overlap_save.cpp + * Written by Clyne Sullivan. + * + * This convolution examples takes an overlap-save approach, where samples from the previous run + * are saved so that the overall operation is not interrupted (i.e. the observed output will + * transition smoothly between processed "chunks"). + * + * Note that there are still improvements that can be made to the code; for example, notice every + * spot where an integer/float conversion is necessary. Operations like these may slow down the + * computation. + */ + +adcsample_t *process_data(adcsample_t *samples, unsigned int size) +{ + static adcsample_t buffer[SIZE]; + + constexpr unsigned int filter_size = 3; + float filter[filter_size] = { + 0.3333, 0.3333, 0.3333 + }; + + // Keep a buffer of extra samples for overlap-save + static adcsample_t prev[filter_size]; + + for (int n = 0; n < size; n++) { + buffer[n] = 0; + + for (int k = 0; k < filter_size; k++) { + int i = n - (filter_size - 1) + k; + + // If i is >= 0, access current sample buffer. + // If i is < 0, provide the previous samples from the 'prev' buffer + if (i >= 0) + buffer[n] += samples[i] * filter[k]; + else + buffer[n] += prev[filter_size - 1 + i] * filter[k]; + } + } + + // Save samples for the next convolution run + for (int i = 0; i < filter_size; i++) + prev[i] = samples[size - filter_size + i]; + + return buffer; +} + diff --git a/gui/templates/3_fir.cpp b/gui/templates/3_fir.cpp new file mode 100644 index 0000000..7af66a8 --- /dev/null +++ b/gui/templates/3_fir.cpp @@ -0,0 +1,47 @@ +/** + * 3_fir.cpp + * Written by Clyne Sullivan. + * + * The below code was written for applying FIR filters. While this is still essentially an overlap- + * save convolution, other optimizations have been made to allow for larger filters to be applied + * within the available execution time. Samples are also normalized so that they center around zero. + */ + +adcsample_t *process_data(adcsample_t *samples, unsigned int size) +{ + static adcsample_t buffer[SIZE]; + + // Define the filter: + constexpr unsigned int filter_size = 3; + static float filter[filter_size] = { + // Put filter values here (note: precision will be truncated for 'float' size). + 0.3333, 0.3333, 0.3333 + }; + + // Do an overlap-save convolution + static adcsample_t prev[filter_size]; + + for (int n = 0; n < size; n++) { + // Using a float variable for accumulation allows for better code optimization + float v = 0; + + for (int k = 0; k < filter_size; k++) { + int i = n - (filter_size - 1) + k; + + auto s = i >= 0 ? samples[i] : prev[filter_size - 1 + i]; + // Sample values are 0 to 4095. Below, the original sample is normalized to a -1.0 to + // 1.0 range for calculation. + v += (s / 2048.f - 1) * filter[k]; + } + + // Return value to sample range of 0-4095. + buffer[n] = (v + 1) * 2048.f; + } + + // Save samples for next convolution + for (int i = 0; i < filter_size; i++) + prev[i] = samples[size - filter_size + i]; + + return buffer; +} + diff --git a/gui/wxapp.hpp b/gui/wxapp.hpp index 010e3e0..2133d6a 100644 --- a/gui/wxapp.hpp +++ b/gui/wxapp.hpp @@ -12,11 +12,14 @@ public: virtual bool OnInit() final { wxFont::AddPrivateFont("./Hack-Regular.ttf"); - auto mainFrame = new MainFrame; - mainFrame->Show(true); - SetTopWindow(mainFrame); + m_main_frame = new MainFrame; + m_main_frame->Show(true); + SetTopWindow(m_main_frame); return true; } + +private: + MainFrame *m_main_frame = nullptr; }; #endif // WXAPP_HPP_ diff --git a/gui/wxmain.cpp b/gui/wxmain.cpp index 874f26e..58f0d37 100644 --- a/gui/wxmain.cpp +++ b/gui/wxmain.cpp @@ -1,10 +1,14 @@ #include "wxmain.hpp" +#include #include #include #include +#include #include +#include #include +#include #include enum Id { @@ -12,28 +16,69 @@ enum Id { MFileNew, MFileOpen, + MFileOpenTemplate, MFileSave, MFileSaveAs, MFileQuit, MRunConnect, MRunStart, MRunMeasure, - MRunCompile, MRunUpload, - MRunUnload + MRunUnload, + MCodeCompile, + MCodeDisassemble }; -MainFrame::MainFrame() : wxFrame(nullptr, -1, "stmdspgui", wxPoint(50, 50), wxSize(640, 800)) +MainFrame::MainFrame() : wxFrame(nullptr, wxID_ANY, "stmdspgui", wxPoint(50, 50), wxSize(640, 800)) { + auto splitter = new wxSplitterWindow(this, wxID_ANY); + auto panelCode = new wxPanel(splitter, wxID_ANY); + auto panelOutput = new wxPanel(splitter, wxID_ANY); + auto sizerCode = new wxBoxSizer(wxVERTICAL); + auto sizerOutput = new wxBoxSizer(wxVERTICAL); + auto sizerSplitter = new wxBoxSizer(wxVERTICAL); + auto menuFile = new wxMenu; + auto menuRun = new wxMenu; + auto menuCode = new wxMenu; + + // Member initialization m_status_bar = new wxStatusBar(this); + m_text_editor = new wxStyledTextCtrl(panelCode, wxID_ANY, wxDefaultPosition, wxSize(620, 500)); + m_compile_output = new wxTextCtrl(panelOutput, wxID_ANY, wxEmptyString, wxDefaultPosition, + wxSize(620, 250), wxTE_READONLY | wxTE_MULTILINE | wxHSCROLL); + m_measure_timer = new wxTimer(this, Id::MeasureTimer); + m_menu_bar = new wxMenuBar; + m_status_bar->SetStatusText("Ready."); SetStatusBar(m_status_bar); - auto menubar = new wxMenuBar; - auto menuFile = new wxMenu; + splitter->SetSashGravity(0.5); + splitter->SetMinimumPaneSize(20); + + prepareEditor(); + sizerCode->Add(m_text_editor, 1, wxEXPAND | wxALL, 0); + panelCode->SetSizer(sizerCode); + + m_compile_output->SetBackgroundColour(wxColour(0, 0, 0)); + m_compile_output->SetDefaultStyle(wxTextAttr(*wxWHITE, *wxBLACK, wxFont("Hack"))); + sizerOutput->Add(m_compile_output, 1, wxEXPAND | wxALL, 0); + panelOutput->SetSizer(sizerOutput); + + splitter->SplitHorizontally(panelCode, panelOutput, 500); + + sizerSplitter->Add(splitter, 1, wxEXPAND, 5); + SetSizer(sizerSplitter); + sizerSplitter->SetSizeHints(this); + + Bind(wxEVT_TIMER, &MainFrame::onMeasureTimer, this, Id::MeasureTimer); + Bind(wxEVT_MENU, &MainFrame::onFileNew, this, Id::MFileNew, wxID_ANY, menuFile->Append(MFileNew, "&New")); - Bind(wxEVT_MENU, &MainFrame::onFileOpen, this, Id::MFileOpen, wxID_ANY, menuFile->Append(MFileOpen, "&Open")); + Bind(wxEVT_MENU, &MainFrame::onFileOpen, this, Id::MFileOpen, wxID_ANY, + menuFile->Append(MFileOpen, "&Open")); + + menuFile->Append(MFileOpenTemplate, "Open &Template", loadTemplates()); + Bind(wxEVT_MENU, &MainFrame::onFileSave, this, Id::MFileSave, wxID_ANY, menuFile->Append(MFileSave, "&Save")); Bind(wxEVT_MENU, &MainFrame::onFileSaveAs, this, Id::MFileSaveAs, wxID_ANY, @@ -42,7 +87,6 @@ MainFrame::MainFrame() : wxFrame(nullptr, -1, "stmdspgui", wxPoint(50, 50), wxSi Bind(wxEVT_MENU, &MainFrame::onFileQuit, this, Id::MFileQuit, wxID_ANY, menuFile->Append(MFileQuit, "&Quit")); - auto menuRun = new wxMenu; Bind(wxEVT_MENU, &MainFrame::onRunConnect, this, Id::MRunConnect, wxID_ANY, menuRun->Append(MRunConnect, "&Connect")); menuRun->AppendSeparator(); @@ -50,31 +94,34 @@ MainFrame::MainFrame() : wxFrame(nullptr, -1, "stmdspgui", wxPoint(50, 50), wxSi menuRun->Append(MRunStart, "&Start")); m_run_measure = menuRun->AppendCheckItem(MRunMeasure, "&Measure code time"); menuRun->AppendSeparator(); - Bind(wxEVT_MENU, &MainFrame::onRunCompile, this, Id::MRunCompile, wxID_ANY, - menuRun->Append(MRunCompile, "&Compile code")); Bind(wxEVT_MENU, &MainFrame::onRunUpload, this, Id::MRunUpload, wxID_ANY, menuRun->Append(MRunUpload, "&Upload code")); Bind(wxEVT_MENU, &MainFrame::onRunUnload, this, Id::MRunUnload, wxID_ANY, menuRun->Append(MRunUnload, "U&nload code")); - menubar->Append(menuFile, "&File"); - menubar->Append(menuRun, "&Run"); - SetMenuBar(menubar); + Bind(wxEVT_MENU, &MainFrame::onRunCompile, this, Id::MCodeCompile, wxID_ANY, + menuCode->Append(MCodeCompile, "&Compile code")); + Bind(wxEVT_MENU, &MainFrame::onCodeDisassemble, this, Id::MCodeDisassemble, wxID_ANY, + menuCode->Append(MCodeDisassemble, "Show &Disassembly")); - auto window = new wxBoxSizer(wxVERTICAL); + Bind(wxEVT_CLOSE_WINDOW, &MainFrame::onCloseEvent, this, wxID_ANY); - m_text_editor = new wxStyledTextCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(620, 500)); - prepareEditor(); - window->Add(m_text_editor, 2, wxEXPAND | wxALL, 10); - - m_compile_output = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, - wxTE_READONLY | wxTE_MULTILINE | wxHSCROLL); - window->Add(m_compile_output, 1, wxEXPAND | wxALL, 10); - - SetSizerAndFit(window); + m_menu_bar->Append(menuFile, "&File"); + m_menu_bar->Append(menuRun, "&Run"); + m_menu_bar->Append(menuCode, "&Code"); + SetMenuBar(m_menu_bar); +} - m_measure_timer = new wxTimer(this, Id::MeasureTimer); - Bind(wxEVT_TIMER, &MainFrame::onMeasureTimer, this, Id::MeasureTimer); +void MainFrame::onCloseEvent(wxCloseEvent& event) +{ + SetMenuBar(nullptr); + m_menu_bar->Remove(2); + m_menu_bar->Remove(1); + m_menu_bar->Remove(0); + delete m_menu_bar; + delete m_measure_timer; + + event.Skip(); } void MainFrame::onMeasureTimer([[maybe_unused]] wxTimerEvent&) @@ -157,24 +204,25 @@ extern "C" void process_data_entry() wxString MainFrame::compileEditorCode() { - static wxString temp_file_name; + if (m_temp_file_name.IsEmpty()) + m_temp_file_name = wxFileName::CreateTempFileName("stmdspgui"); - if (temp_file_name.IsEmpty()) - temp_file_name = wxFileName::CreateTempFileName("stmdspgui"); - - wxFile file (temp_file_name, wxFile::write); + wxFile file (m_temp_file_name, wxFile::write); file.Write(file_header + m_text_editor->GetText()); file.Close(); - wxFile makefile (temp_file_name + "make", wxFile::write); + wxFile makefile (m_temp_file_name + "make", wxFile::write); wxString make_text (makefile_text); - make_text.Replace("$0", temp_file_name); + make_text.Replace("$0", m_temp_file_name); makefile.Write(make_text); makefile.Close(); - wxString make_output = temp_file_name + "make.log"; - wxString make_command = wxString("make -C ") + temp_file_name.BeforeLast('/') + - " -f " + temp_file_name + "make" + + wxString make_output = m_temp_file_name + "make.log"; + if (wxFile::Exists(make_output)) + wxRemoveFile(make_output); + + wxString make_command = wxString("make -C ") + m_temp_file_name.BeforeLast('/') + + " -f " + m_temp_file_name + "make" + " > " + make_output + " 2>&1"; int result = system(make_command.ToAscii()); @@ -182,9 +230,11 @@ wxString MainFrame::compileEditorCode() if (result == 0) { m_status_bar->SetStatusText("Compilation succeeded."); - return temp_file_name + ".o"; + m_compile_output->ChangeValue(""); + return m_temp_file_name + ".o"; } else { m_status_bar->SetStatusText("Compilation failed."); + m_compile_output->LoadFile(make_output); return ""; } } @@ -211,7 +261,8 @@ void MainFrame::onFileOpen([[maybe_unused]] wxCommandEvent&) if (openDialog.ShowModal() != wxID_CANCEL) { if (wxFileInputStream file_stream (openDialog.GetPath()); file_stream.IsOk()) { auto size = file_stream.GetSize(); - auto buffer = new char[size]; + auto buffer = new char[size + 1]; + buffer[size] = '\0'; if (file_stream.ReadAll(buffer, size)) { m_open_file_path = openDialog.GetPath(); m_text_editor->SetText(buffer); @@ -223,6 +274,25 @@ void MainFrame::onFileOpen([[maybe_unused]] wxCommandEvent&) } } +void MainFrame::onFileOpenTemplate(wxCommandEvent& event) +{ + auto file_path = wxGetCwd() + "/templates/" + m_menu_bar->GetLabel(event.GetId()); + + if (wxFileInputStream file_stream (file_path); file_stream.IsOk()) { + auto size = file_stream.GetSize(); + auto buffer = new char[size + 1]; + buffer[size] = '\0'; + if (file_stream.ReadAll(buffer, size)) { + m_open_file_path = ""; + m_text_editor->SetText(buffer); + //m_text_editor->DiscardEdits(); + m_status_bar->SetStatusText("Ready."); + } + delete[] buffer; + } +} + + void MainFrame::onFileSave(wxCommandEvent& ce) { if (m_text_editor->IsModified()) { @@ -328,11 +398,6 @@ void MainFrame::onRunStart(wxCommandEvent& ce) } } -void MainFrame::onRunCompile([[maybe_unused]] wxCommandEvent&) -{ - compileEditorCode(); -} - void MainFrame::onRunUpload([[maybe_unused]] wxCommandEvent&) { if (auto file = compileEditorCode(); !file.IsEmpty()) { @@ -363,3 +428,42 @@ void MainFrame::onRunUnload([[maybe_unused]] wxCommandEvent&) } } +void MainFrame::onRunCompile([[maybe_unused]] wxCommandEvent&) +{ + compileEditorCode(); +} + +void MainFrame::onCodeDisassemble([[maybe_unused]] wxCommandEvent&) +{ + auto output = m_temp_file_name + ".asm.log"; + wxString command = wxString("arm-none-eabi-objdump -d ") + m_temp_file_name + ".orig.o" + " > " + output + " 2>&1"; + + if (system(command.ToAscii()) == 0) { + m_compile_output->LoadFile(output); + m_status_bar->SetStatusText(wxString::Format(wxT("Done. Line count: %u."), + m_compile_output->GetNumberOfLines())); + } else { + m_compile_output->ChangeValue(""); + m_status_bar->SetStatusText("Failed to load disassembly (code compiled?)."); + } +} + +wxMenu *MainFrame::loadTemplates() +{ + wxMenu *menu = new wxMenu; + + wxArrayString files; + if (wxDir::GetAllFiles(wxGetCwd() + "/templates", &files, "*.cpp", wxDIR_FILES) > 0) { + files.Sort(); + int id = 1000; + for (auto file : files) { + Bind(wxEVT_MENU, &MainFrame::onFileOpenTemplate, this, id, wxID_ANY, + menu->Append(id, file.AfterLast('/'))); + id++; + } + } + + return menu; +} + diff --git a/gui/wxmain.hpp b/gui/wxmain.hpp index 268a08d..ce3e5a5 100644 --- a/gui/wxmain.hpp +++ b/gui/wxmain.hpp @@ -23,18 +23,23 @@ class MainFrame : public wxFrame public: MainFrame(); + void onCloseEvent(wxCloseEvent&); + void onFileNew(wxCommandEvent&); void onFileOpen(wxCommandEvent&); + void onFileOpenTemplate(wxCommandEvent&); void onFileSave(wxCommandEvent&); void onFileSaveAs(wxCommandEvent&); void onFileQuit(wxCommandEvent&); void onRunConnect(wxCommandEvent&); void onRunStart(wxCommandEvent&); - void onRunCompile(wxCommandEvent&); void onRunUpload(wxCommandEvent&); void onRunUnload(wxCommandEvent&); + void onRunCompile(wxCommandEvent&); + void onCodeDisassemble(wxCommandEvent&); + void onMeasureTimer(wxTimerEvent& te); private: @@ -46,13 +51,16 @@ private: wxMenuItem *m_run_measure = nullptr; wxTimer *m_measure_timer = nullptr; wxStatusBar *m_status_bar = nullptr; + wxMenuBar *m_menu_bar = nullptr; wxString m_open_file_path; + wxString m_temp_file_name; stmdsp::device *m_device = nullptr; bool tryDevice(); void prepareEditor(); wxString compileEditorCode(); + wxMenu *loadTemplates(); }; #endif // WXMAIN_HPP_