diff --git a/Makefile b/Makefile index 2153aaf..83a8d7a 100644 --- a/Makefile +++ b/Makefile @@ -8,10 +8,11 @@ ALL = MotionCal imuread CC = gcc CXX = g++ CFLAGS = -O2 -Wall -D$(OS) +WXCONFIG = ~/wxwidgets/3.0.2.gtk2-opengl/bin/wx-config +WXFLAGS = `$(WXCONFIG) --cppflags` CXXFLAGS = $(CFLAGS) `$(WXCONFIG) --cppflags` LDFLAGS = SFLAG = -s -WXCONFIG = ~/wxwidgets/3.0.2.gtk2-opengl/bin/wx-config CLILIBS = -lglut -lGLU -lGL -lm MAKEFLAGS = --jobs=12 @@ -20,8 +21,9 @@ ALL = MotionCal.dmg CC = gcc-4.2 CXX = g++-4.2 CFLAGS = -O2 -Wall -D$(OS) -CXXFLAGS = $(CFLAGS) `$(WXCONFIG) --cppflags` WXCONFIG = ~/wxwidgets/3.0.2.mac-opengl/bin/wx-config +WXFLAGS = `$(WXCONFIG) --cppflags` +CXXFLAGS = $(CFLAGS) `$(WXCONFIG) --cppflags` SFLAG = -s CLILIBS = -lglut -lGLU -lGL -lm VERSION = 0.01 @@ -32,6 +34,7 @@ CC = /usr/bin/clang CXX = /usr/bin/clang++ CFLAGS = -O2 -Wall -DMACOSX WXCONFIG = wx-config +WXFLAGS = `$(WXCONFIG) --cppflags` CXXFLAGS = $(CFLAGS) `$(WXCONFIG) --cppflags` SFLAG = CLILIBS = -lglut -lGLU -lGL -lm @@ -39,33 +42,42 @@ VERSION = 0.01 else ifeq ($(OS), WINDOWS) ALL = MotionCal.exe -CC = i686-w64-mingw32-gcc -CXX = i686-w64-mingw32-g++ -WINDRES = i686-w64-mingw32-windres +#MINGW_TOOLCHAIN = i586-mingw32msvc +MINGW_TOOLCHAIN = i686-w64-mingw32 +CC = $(MINGW_TOOLCHAIN)-gcc +CXX = $(MINGW_TOOLCHAIN)-g++ +WINDRES = $(MINGW_TOOLCHAIN)-windres CFLAGS = -O2 -Wall -D$(OS) -CXXFLAGS = $(CFLAGS) `$(WXCONFIG) --cppflags` +WXFLAGS = `$(WXCONFIG) --cppflags` +CXXFLAGS = $(CFLAGS) $(WXFLAGS) LDFLAGS = -static -static-libgcc SFLAG = -s -WXCONFIG = ~/wxwidgets/3.0.2.mingw-opengl/bin/wx-config +#WXCONFIG = ~/wxwidgets/3.0.2.mingw-opengl-i586/bin/wx-config +#WXCONFIG = ~/wxwidgets/3.0.2.mingw-opengl/bin/wx-config +WXCONFIG = ~/wxwidgets/3.1.0.mingw-opengl/bin/wx-config CLILIBS = -lglut32 -lglu32 -lopengl32 -lm MAKEFLAGS = --jobs=12 endif OBJS = visualize.o serialdata.o rawdata.o magcal.o matrix.o fusion.o quality.o mahony.o +IMGS = checkgreen.png checkempty.png checkemptygray.png all: $(ALL) -MotionCal: gui.o portlist.o $(OBJS) +MotionCal: gui.o portlist.o images.o $(OBJS) $(CXX) $(SFLAG) $(CFLAGS) $(LDFLAGS) -o $@ $^ `$(WXCONFIG) --libs all,opengl` -MotionCal.exe: resource.o gui.o portlist.o $(OBJS) +MotionCal.exe: resource.o gui.o portlist.o images.o $(OBJS) $(CXX) $(SFLAG) $(CFLAGS) $(LDFLAGS) -o $@ $^ `$(WXCONFIG) --libs all,opengl` -pjrcwinsigntool $@ -./cp_windows.sh $@ -resource.o: resource.rs icon.ico - $(WINDRES) -o resource.o resource.rs +resource.o: resource.rc icon.ico + $(WINDRES) $(WXFLAGS) -o resource.o resource.rc + +images.cpp: $(IMGS) png2c.pl + perl png2c.pl $(IMGS) > images.cpp MotionCal.app: MotionCal Info.plist icon.icns mkdir -p $@/Contents/MacOS @@ -86,7 +98,7 @@ imuread: imuread.o $(OBJS) $(CC) -s $(CFLAGS) $(LDFLAGS) -o $@ $^ $(CLILIBS) clean: - rm -f gui MotionCal imuread *.o *.exe *.sign? + rm -f gui MotionCal imuread *.o *.exe *.sign? images.cpp rm -rf MotionCal.app MotionCal.dmg .DS_Store dmg_tmpdir gui.o: gui.cpp gui.h imuread.h Makefile diff --git a/checkempty.png b/checkempty.png new file mode 100644 index 0000000..350179e Binary files /dev/null and b/checkempty.png differ diff --git a/checkemptygray.png b/checkemptygray.png new file mode 100644 index 0000000..38da865 Binary files /dev/null and b/checkemptygray.png differ diff --git a/checkgreen.png b/checkgreen.png new file mode 100644 index 0000000..f888858 Binary files /dev/null and b/checkgreen.png differ diff --git a/gui.cpp b/gui.cpp index f471fb3..f39f6a7 100644 --- a/gui.cpp +++ b/gui.cpp @@ -2,10 +2,8 @@ #include "imuread.h" - -wxMenu *port_menu; -wxMenu *sendcal_menu; wxString port_name; +static bool show_calibration_confirmed = false; wxBEGIN_EVENT_TABLE(MyCanvas, wxGLCanvas) @@ -49,9 +47,10 @@ void MyCanvas::OnPaint( wxPaintEvent& WXUNUSED(event) ) void MyCanvas::InitGL() { - //printf("Init\n"); SetCurrent(*m_glRC); visualize_init(); + wxSizeEvent e = wxSizeEvent(GetSize()); + OnSize(e); } @@ -61,144 +60,179 @@ void MyCanvas::InitGL() BEGIN_EVENT_TABLE(MyFrame,wxFrame) EVT_MENU(wxID_ABOUT, MyFrame::OnAbout) EVT_MENU(wxID_EXIT, MyFrame::OnQuit) - EVT_MENU(ID_SENDCAL, MyFrame::OnSendCal) + EVT_MENU(ID_SENDCAL_MENU, MyFrame::OnSendCal) + EVT_BUTTON(ID_CLEAR_BUTTON, MyFrame::OnClear) + EVT_BUTTON(ID_SENDCAL_BUTTON, MyFrame::OnSendCal) EVT_TIMER(ID_TIMER, MyFrame::OnTimer) - EVT_MENU_RANGE(9000, 9999, MyFrame::OnPort) - EVT_MENU_OPEN(MyMenu::OnShowPortList) - EVT_MENU_HIGHLIGHT(-1, MyMenu::OnHighlight) + EVT_MENU_RANGE(9000, 9999, MyFrame::OnPortMenu) + EVT_MENU_OPEN(MyFrame::OnShowMenu) + EVT_COMBOBOX(ID_PORTLIST, MyFrame::OnPortList) + EVT_COMBOBOX_DROPDOWN(ID_PORTLIST, MyFrame::OnShowPortList) END_EVENT_TABLE() + MyFrame::MyFrame(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &position, const wxSize& size, long style) : wxFrame( parent, id, title, position, size, style ) { + wxPanel *panel; wxMenuBar *menuBar; wxMenu *menu; + wxSizer *topsizer; + wxSizer *leftsizer, *middlesizer, *rightsizer; wxSizer *hsizer, *vsizer, *calsizer; wxStaticText *text; - //wxMenuItem *item; int i, j; + topsizer = new wxBoxSizer(wxHORIZONTAL); + panel = new wxPanel(this); + menuBar = new wxMenuBar; menu = new wxMenu; - menu->Append(ID_SENDCAL, wxT("Send Calibration")); - sendcal_menu = menu; - sendcal_menu->Enable(ID_SENDCAL, false); + menu->Append(ID_SENDCAL_MENU, wxT("Send Calibration")); + m_sendcal_menu = menu; + m_sendcal_menu->Enable(ID_SENDCAL_MENU, false); menu->Append(wxID_EXIT, wxT("Quit")); menuBar->Append(menu, wxT("&File")); menu = new wxMenu; menuBar->Append(menu, "Port"); - port_menu = menu; + m_port_menu = menu; menu = new wxMenu; - //item = new wxMenuItem(menu, ID_ABOUT, "About"); menu->Append(wxID_ABOUT, wxT("About")); menuBar->Append(menu, wxT("&Help")); SetMenuBar(menuBar); - wxBoxSizer *topsizer = new wxBoxSizer(wxHORIZONTAL); - wxBoxSizer *leftsizer = new wxStaticBoxSizer(wxVERTICAL, this, "Communication"); - wxBoxSizer *middlesizer = new wxStaticBoxSizer(wxVERTICAL, this, "Magnetometer"); - wxBoxSizer *rightsizer = new wxStaticBoxSizer(wxVERTICAL, this, "Calibration"); + leftsizer = new wxStaticBoxSizer(wxVERTICAL, panel, "Communication"); + middlesizer = new wxStaticBoxSizer(wxVERTICAL, panel, "Magnetometer"); + rightsizer = new wxStaticBoxSizer(wxVERTICAL, panel, "Calibration"); topsizer->Add(leftsizer, 0, wxALL | wxEXPAND | wxALIGN_TOP, 5); topsizer->Add(middlesizer, 1, wxALL | wxEXPAND, 5); topsizer->Add(rightsizer, 0, wxALL | wxEXPAND | wxALIGN_TOP, 5); + vsizer = new wxBoxSizer(wxVERTICAL); + leftsizer->Add(vsizer, 0, wxALL, 8); + text = new wxStaticText(panel, wxID_ANY, "Port"); + vsizer->Add(text, 0, wxTOP|wxBOTTOM, 4); + m_port_list = new wxComboBox(panel, ID_PORTLIST, "", + wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + m_port_list->Append("(none)"); + m_port_list->Append(SAMPLE_PORT_NAME); // never seen, only for initial size + m_port_list->SetSelection(0); + vsizer->Add(m_port_list, 1, wxEXPAND, 0); + + vsizer->AddSpacer(8); + text = new wxStaticText(panel, wxID_ANY, "Actions"); + vsizer->Add(text, 0, wxTOP|wxBOTTOM, 4); + m_button_clear = new wxButton(panel, ID_CLEAR_BUTTON, "Clear"); + m_button_clear->Enable(false); + vsizer->Add(m_button_clear, 1, wxEXPAND, 0); + m_button_sendcal = new wxButton(panel, ID_SENDCAL_BUTTON, "Send Cal"); + vsizer->Add(m_button_sendcal, 1, wxEXPAND, 0); + m_button_sendcal->Enable(false); + vsizer->AddSpacer(16); + text = new wxStaticText(panel, wxID_ANY, "Status"); + vsizer->Add(text, 0, wxTOP|wxBOTTOM, 4); + wxImage::AddHandler(new wxPNGHandler); + //m_confirm_icon = new wxStaticBitmap(panel, ID_CONFIRM_ICON, MyBitmap("checkgreen.png")); + m_confirm_icon = new wxStaticBitmap(panel, wxID_ANY, MyBitmap("checkemptygray.png")); + vsizer->Add(m_confirm_icon, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 0); + vsizer = new wxBoxSizer(wxVERTICAL); middlesizer->Add(vsizer, 1, wxEXPAND | wxALL, 8); - text = new wxStaticText(this, wxID_ANY, ""); + text = new wxStaticText(panel, wxID_ANY, ""); text->SetLabelMarkup("Ideal calibration is a perfectly centered sphere"); vsizer->Add(text, 0, wxALIGN_CENTER_HORIZONTAL, 0); int gl_attrib[20] = { WX_GL_RGBA, WX_GL_MIN_RED, 1, WX_GL_MIN_GREEN, 1, WX_GL_MIN_BLUE, 1, WX_GL_DEPTH_SIZE, 1, WX_GL_DOUBLEBUFFER, 0}; - m_canvas = new MyCanvas(this, wxID_ANY, gl_attrib); - m_canvas->SetMinSize(wxSize(400,400)); + m_canvas = new MyCanvas(panel, wxID_ANY, gl_attrib); + m_canvas->SetMinSize(wxSize(480,480)); vsizer->Add(m_canvas, 1, wxEXPAND | wxALL, 0); - hsizer = new wxGridSizer(4, 0, 15); middlesizer->Add(hsizer, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5); vsizer = new wxBoxSizer(wxVERTICAL); hsizer->Add(vsizer, 1, wxALIGN_CENTER_HORIZONTAL); - text = new wxStaticText(this, wxID_ANY, "Gaps"); + text = new wxStaticText(panel, wxID_ANY, "Gaps"); vsizer->Add(text, 1, wxALIGN_CENTER_HORIZONTAL); - m_err_coverage = new wxStaticText(this, wxID_ANY, "100.0%"); + m_err_coverage = new wxStaticText(panel, wxID_ANY, "100.0%"); vsizer->Add(m_err_coverage, 1, wxALIGN_CENTER_HORIZONTAL); vsizer = new wxBoxSizer(wxVERTICAL); hsizer->Add(vsizer, 1, wxALIGN_CENTER_HORIZONTAL); - text = new wxStaticText(this, wxID_ANY, "Variance"); + text = new wxStaticText(panel, wxID_ANY, "Variance"); vsizer->Add(text, 1, wxALIGN_CENTER_HORIZONTAL); - m_err_variance = new wxStaticText(this, wxID_ANY, "100.0%"); + m_err_variance = new wxStaticText(panel, wxID_ANY, "100.0%"); vsizer->Add(m_err_variance, 1, wxALIGN_CENTER_HORIZONTAL); vsizer = new wxBoxSizer(wxVERTICAL); hsizer->Add(vsizer, 1, wxALIGN_CENTER_HORIZONTAL); - text = new wxStaticText(this, wxID_ANY, "Wobble"); + text = new wxStaticText(panel, wxID_ANY, "Wobble"); vsizer->Add(text, 1, wxALIGN_CENTER_HORIZONTAL); - m_err_wobble = new wxStaticText(this, wxID_ANY, "100.0%"); + m_err_wobble = new wxStaticText(panel, wxID_ANY, "100.0%"); vsizer->Add(m_err_wobble, 1, wxALIGN_CENTER_HORIZONTAL); vsizer = new wxBoxSizer(wxVERTICAL); hsizer->Add(vsizer, 1, wxALIGN_CENTER_HORIZONTAL); - text = new wxStaticText(this, wxID_ANY, "Fit Error"); + text = new wxStaticText(panel, wxID_ANY, "Fit Error"); vsizer->Add(text, 1, wxALIGN_CENTER_HORIZONTAL); - m_err_fit = new wxStaticText(this, wxID_ANY, "100.0%"); + m_err_fit = new wxStaticText(panel, wxID_ANY, "100.0%"); vsizer->Add(m_err_fit, 1, wxALIGN_CENTER_HORIZONTAL); calsizer = new wxBoxSizer(wxVERTICAL); rightsizer->Add(calsizer, 0, wxALL, 8); - text = new wxStaticText(this, wxID_ANY, "Magnetic Offset"); + text = new wxStaticText(panel, wxID_ANY, "Magnetic Offset"); calsizer->Add(text, 0, wxTOP|wxBOTTOM, 4); vsizer = new wxGridSizer(1, 0, 0); calsizer->Add(vsizer, 1, wxLEFT, 20); for (i=0; i < 3; i++) { - m_mag_offset[i] = new wxStaticText(this, wxID_ANY, "0.00"); + m_mag_offset[i] = new wxStaticText(panel, wxID_ANY, "0.00"); vsizer->Add(m_mag_offset[i], 1); } - text = new wxStaticText(this, wxID_ANY, "Magnetic Mapping"); + text = new wxStaticText(panel, wxID_ANY, "Magnetic Mapping"); calsizer->Add(text, 0, wxTOP|wxBOTTOM, 4); vsizer = new wxGridSizer(3, 0, 12); calsizer->Add(vsizer, 1, wxLEFT, 20); for (i=0; i < 3; i++) { for (j=0; j < 3; j++) { - m_mag_mapping[i][j] = new wxStaticText(this, wxID_ANY, + m_mag_mapping[i][j] = new wxStaticText(panel, wxID_ANY, ((i == j) ? "+1.000" : "+0.000")); vsizer->Add(m_mag_mapping[i][j], 1); } } - text = new wxStaticText(this, wxID_ANY, "Magnetic Field"); + text = new wxStaticText(panel, wxID_ANY, "Magnetic Field"); calsizer->Add(text, 0, wxTOP|wxBOTTOM, 4); - m_mag_field = new wxStaticText(this, wxID_ANY, "0.00"); + m_mag_field = new wxStaticText(panel, wxID_ANY, "0.00"); calsizer->Add(m_mag_field, 0, wxLEFT, 20); - text = new wxStaticText(this, wxID_ANY, "Accelerometer"); + text = new wxStaticText(panel, wxID_ANY, "Accelerometer"); calsizer->Add(text, 0, wxTOP|wxBOTTOM, 4); vsizer = new wxGridSizer(1, 0, 0); calsizer->Add(vsizer, 1, wxLEFT, 20); for (i=0; i < 3; i++) { - m_accel[i] = new wxStaticText(this, wxID_ANY, "0.000"); + m_accel[i] = new wxStaticText(panel, wxID_ANY, "0.000"); vsizer->Add(m_accel[i], 1); } - text = new wxStaticText(this, wxID_ANY, "Gyroscope"); + text = new wxStaticText(panel, wxID_ANY, "Gyroscope"); calsizer->Add(text, 0, wxTOP|wxBOTTOM, 4); vsizer = new wxGridSizer(1, 0, 0); calsizer->Add(vsizer, 1, wxLEFT, 20); for (i=0; i < 3; i++) { - m_gyro[i] = new wxStaticText(this, wxID_ANY, "0.000"); + m_gyro[i] = new wxStaticText(panel, wxID_ANY, "0.000"); vsizer->Add(m_gyro[i], 1); } calsizer->AddSpacer(8); - text = new wxStaticText(this, wxID_ANY, ""); + text = new wxStaticText(panel, wxID_ANY, ""); text->SetLabelMarkup("Calibration should be performed\nafter final installation. Presence\nof magnets and ferrous metals\ncan alter magnetic calibration.\nMechanical stress during\nassembly can alter accelerometer\nand gyroscope calibration."); //text->Wrap(200); //calsizer->Add(text, 0, wxEXPAND | wxALIGN_CENTER_HORIZONTAL, 0); calsizer->Add(text, 0, wxALIGN_CENTER_HORIZONTAL, 0); - topsizer->SetSizeHints(this); - SetSizerAndFit(topsizer); + panel->SetSizer(topsizer); + topsizer->SetSizeHints(panel); + Fit(); Show(true); Raise(); @@ -211,6 +245,7 @@ MyFrame::MyFrame(wxWindow *parent, wxWindowID id, const wxString &title, void MyFrame::OnTimer(wxTimerEvent &event) { + static int firstrun=1; float gaps, variance, wobble, fiterror; char buf[32]; int i, j; @@ -218,15 +253,29 @@ void MyFrame::OnTimer(wxTimerEvent &event) //printf("OnTimer\n"); if (port_is_open()) { read_serial_data(); + if (firstrun && m_canvas->IsShown()) { + //int h, w; + //m_canvas->GetSize(&w, &h); + //printf("Canvas initial size = %d, %d\n", w, h); + firstrun = 0; + } m_canvas->Refresh(); gaps = quality_surface_gap_error(); variance = quality_magnitude_variance_error(); wobble = quality_wobble_error(); fiterror = quality_spherical_fit_error(); if (gaps < 15.0f && variance < 4.5f && wobble < 4.0f && fiterror < 5.0f) { - sendcal_menu->Enable(ID_SENDCAL, true); + if (!m_sendcal_menu->IsEnabled(ID_SENDCAL_MENU) || !m_button_sendcal->IsEnabled()) { + m_sendcal_menu->Enable(ID_SENDCAL_MENU, true); + m_button_sendcal->Enable(true); + m_confirm_icon->SetBitmap(MyBitmap("checkempty.png")); + } } else if (gaps > 20.0f && variance > 5.0f && wobble > 5.0f && fiterror > 6.0f) { - sendcal_menu->Enable(ID_SENDCAL, false); + if (m_sendcal_menu->IsEnabled(ID_SENDCAL_MENU) || m_button_sendcal->IsEnabled()) { + m_sendcal_menu->Enable(ID_SENDCAL_MENU, false); + m_button_sendcal->Enable(false); + m_confirm_icon->SetBitmap(MyBitmap("checkemptygray.png")); + } } snprintf(buf, sizeof(buf), "%.1f%%", quality_surface_gap_error()); m_err_coverage->SetLabelText(buf); @@ -257,13 +306,33 @@ void MyFrame::OnTimer(wxTimerEvent &event) m_gyro[i]->SetLabelText(buf); } } else { - sendcal_menu->Enable(ID_SENDCAL, false); + if (!port_name.IsEmpty()) { + //printf("port has closed, updating stuff\n"); + m_sendcal_menu->Enable(ID_SENDCAL_MENU, false); + m_button_clear->Enable(false); + m_button_sendcal->Enable(false); + m_confirm_icon->SetBitmap(MyBitmap("checkemptygray.png")); + m_port_list->Clear(); + m_port_list->Append("(none)"); + m_port_list->SetSelection(0); + port_name = ""; + } } + if (show_calibration_confirmed) { + m_confirm_icon->SetBitmap(MyBitmap("checkgreen.png")); + show_calibration_confirmed = false; + } +} + +void MyFrame::OnClear(wxCommandEvent &event) +{ + //printf("OnClear\n"); + raw_data_reset(); } void MyFrame::OnSendCal(wxCommandEvent &event) { - printf("OnSendCal\n"); + /*printf("OnSendCal\n"); printf("Magnetic Calibration: (%.1f%% fit error)\n", magcal.FitError); printf(" %7.2f %6.3f %6.3f %6.3f\n", magcal.V[0], magcal.invW[0][0], magcal.invW[0][1], magcal.invW[0][2]); @@ -271,23 +340,85 @@ void MyFrame::OnSendCal(wxCommandEvent &event) magcal.V[1], magcal.invW[1][0], magcal.invW[1][1], magcal.invW[1][2]); printf(" %7.2f %6.3f %6.3f %6.3f\n", magcal.V[2], magcal.invW[2][0], magcal.invW[2][1], magcal.invW[2][2]); + */ + m_confirm_icon->SetBitmap(MyBitmap("checkempty.png")); send_calibration(); } -void MyFrame::OnPort(wxCommandEvent &event) +void calibration_confirmed(void) +{ + show_calibration_confirmed = true; +} + + +void MyFrame::OnShowMenu(wxMenuEvent &event) +{ + wxMenu *menu = event.GetMenu(); + if (menu != m_port_menu) return; + //printf("OnShow Port Menu, %s\n", (const char *)menu->GetTitle()); + while (menu->GetMenuItemCount() > 0) { + menu->Delete(menu->GetMenuItems()[0]); + } + menu->AppendRadioItem(9000, " (none)"); + bool isopen = port_is_open(); + if (!isopen) menu->Check(9000, true); + wxArrayString list = serial_port_list(); + int num = list.GetCount(); + for (int i=0; i < num; i++) { + menu->AppendRadioItem(9001 + i, list[i]); + if (isopen && port_name.IsSameAs(list[i])) { + menu->Check(9001 + i, true); + } + } + menu->UpdateUI(); +} + +void MyFrame::OnShowPortList(wxCommandEvent& event) +{ + //printf("OnShowPortList\n"); + m_port_list->Clear(); + m_port_list->Append("(none)"); + wxArrayString list = serial_port_list(); + int num = list.GetCount(); + for (int i=0; i < num; i++) { + m_port_list->Append(list[i]); + } +} + + +void MyFrame::OnPortMenu(wxCommandEvent &event) { int id = event.GetId(); - wxString name = port_menu->FindItem(id)->GetItemLabelText(); + wxString name = m_port_menu->FindItem(id)->GetItemLabelText(); close_port(); - //printf("OnPort, id = %d, name = %s\n", id, (const char *)name); - sendcal_menu->Enable(ID_SENDCAL, false); + //printf("OnPortMenu, id = %d, name = %s\n", id, (const char *)name); port_name = name; + m_port_list->Clear(); + m_port_list->Append(port_name); + m_port_list->SetSelection(0); if (id == 9000) return; raw_data_reset(); open_port((const char *)name); + m_button_clear->Enable(true); } +void MyFrame::OnPortList(wxCommandEvent& event) +{ + int selected = m_port_list->GetSelection(); + if (selected == wxNOT_FOUND) return; + wxString name = m_port_list->GetString(selected); + //printf("OnPortList, %s\n", (const char *)name); + close_port(); + port_name = name; + if (name == "(none)") return; + raw_data_reset(); + open_port((const char *)name); + m_button_clear->Enable(true); +} + + + void MyFrame::OnAbout(wxCommandEvent &event) { @@ -296,7 +427,7 @@ void MyFrame::OnAbout(wxCommandEvent &event) "Paul Stoffregen \n" "http://www.pjrc.com/store/prop_shield.html\n" "https://github.com/PaulStoffregen/MotionCal\n\n" - "Copyright 2016, PJRC.COM, LLC.", + "Copyright 2018, PJRC.COM, LLC.", "About MotionCal", wxOK|wxICON_INFORMATION|wxCENTER); dialog.ShowModal(); } @@ -313,48 +444,6 @@ MyFrame::~MyFrame(void) } -/*****************************************************************************/ -// Port Menu - -MyMenu::MyMenu(const wxString& title, long style) : wxMenu(title, style) -{ -} - -void MyMenu::OnShowPortList(wxMenuEvent &event) -{ - wxMenu *menu; - int any=0; - int num; - - menu = event.GetMenu(); - //printf("OnShowPortList, %s\n", (const char *)menu->GetTitle()); - if (menu != port_menu) return; - while (menu->GetMenuItemCount() > 0) { - menu->Delete(menu->GetMenuItems()[0]); - } - menu->AppendRadioItem(9000, " (none)"); - wxArrayString list = serial_port_list(); - num = list.GetCount(); - bool isopen = port_is_open(); - for (int i=0; i < num; i++) { - //printf("%d: port %s\n", i, (const char *)list[i]); - menu->AppendRadioItem(9001 + i, list[i]); - if (isopen && port_name.IsSameAs(list[i])) { - menu->Check(9001 + i, true); - any = 1; - } - } - if (!any) menu->Check(9000, true); - menu->UpdateUI(); -} - -void MyMenu::OnHighlight(wxMenuEvent &event) -{ - //printf("OnHighlight\n"); -} - - - /*****************************************************************************/ IMPLEMENT_APP(MyApp) diff --git a/gui.h b/gui.h index 2d82427..64af8ac 100644 --- a/gui.h +++ b/gui.h @@ -24,8 +24,10 @@ #define ID_TIMER 10000 -#define ID_SENDCAL 10001 - +#define ID_SENDCAL_MENU 10001 +#define ID_CLEAR_BUTTON 10002 +#define ID_SENDCAL_BUTTON 10003 +#define ID_PORTLIST 10004 class MyCanvas : public wxGLCanvas { @@ -61,9 +63,6 @@ public: const wxSize &size = wxDefaultSize, long style = wxDEFAULT_FRAME_STYLE); ~MyFrame(void); - void InitGL(); - void OnPort(wxCommandEvent &event); - void OnSendCal(wxCommandEvent &event); private: wxStaticText *m_err_coverage; wxStaticText *m_err_variance; @@ -78,20 +77,24 @@ private: MyCanvas *m_canvas; wxTimer *m_timer; + wxButton *m_button_clear; + wxButton *m_button_sendcal; + wxStaticBitmap *m_confirm_icon; + wxMenu *m_port_menu; + wxComboBox *m_port_list; + wxMenu *m_sendcal_menu; + void OnSendCal(wxCommandEvent &event); + void OnClear(wxCommandEvent &event); + void OnShowMenu(wxMenuEvent &event); + void OnShowPortList(wxCommandEvent &event); + void OnPortList(wxCommandEvent& event); + void OnPortMenu(wxCommandEvent &event); + void OnTimer(wxTimerEvent &event); void OnAbout(wxCommandEvent &event); void OnQuit(wxCommandEvent &event); - void OnTimer(wxTimerEvent &event); DECLARE_EVENT_TABLE() }; -class MyMenu: public wxMenu -{ -public: - MyMenu(const wxString& title = "", long style = 0); - void OnShowPortList(wxMenuEvent &event); - void OnHighlight(wxMenuEvent &event); -}; - class MyApp: public wxApp { @@ -106,5 +109,17 @@ private: // portlist.cpp wxArrayString serial_port_list(); +// images.cpp +wxBitmap MyBitmap(const char *name); + +// sample port name, for initial sizing of left panel +#if defined(LINUX) +#define SAMPLE_PORT_NAME "/dev/ttyACM5." +#elif defined(WINDOWS) +#define SAMPLE_PORT_NAME "COM22:." +#elif defined(MACOSX) +#define SAMPLE_PORT_NAME "/dev/cu.usbmodem2457891..." +#endif + #endif diff --git a/imuread.c b/imuread.c index 68412b2..445afc9 100644 --- a/imuread.c +++ b/imuread.c @@ -21,8 +21,67 @@ static void glut_display_callback(void) glutSwapBuffers(); } +extern int invert_q0; +extern int invert_q1; +extern int invert_q2; +extern int invert_q3; +extern int invert_x; +extern int invert_y; +extern int invert_z; + +static void print_invert_state(void) +{ + printf("Invert: %s %s %s %s %s %s %s\n", + (invert_q0 ? "Q0" : " "), + (invert_q1 ? "Q1" : " "), + (invert_q2 ? "Q2" : " "), + (invert_q3 ? "Q3" : " "), + (invert_x ? "x'" : " "), + (invert_y ? "y'" : " "), + (invert_z ? "z'" : " ") + ); +} + + static void glut_keystroke_callback(unsigned char ch, int x, int y) { + if (ch == '0') { + invert_q0 ^= 1; + print_invert_state(); + return; + } + if (ch == '1') { + invert_q1 ^= 1; + print_invert_state(); + return; + } + if (ch == '2') { + invert_q2 ^= 1; + print_invert_state(); + return; + } + if (ch == '3') { + invert_q3 ^= 1; + print_invert_state(); + return; + } + if (ch == 'x') { + invert_x ^= 1; + print_invert_state(); + return; + } + if (ch == 'y') { + invert_y ^= 1; + print_invert_state(); + return; + } + if (ch == 'z') { + invert_z ^= 1; + print_invert_state(); + return; + } + + if (magcal.FitError > 9.0) { printf("Poor Calibration: "); printf("soft iron fit error = %.1f%%\n", magcal.FitError); @@ -38,6 +97,11 @@ static void glut_keystroke_callback(unsigned char ch, int x, int y) send_calibration(); } +void calibration_confirmed(void) +{ + printf("Calibration confirmed!\n"); +} + int main(int argc, char *argv[]) { raw_data_reset(); diff --git a/imuread.h b/imuread.h index 6bcae6f..9be6ce1 100644 --- a/imuread.h +++ b/imuread.h @@ -15,8 +15,8 @@ #if defined(LINUX) #include #include - #include - #include + #include // sudo apt install mesa-common-dev + #include // sudo apt install libglu1-mesa-dev freeglut3-dev #elif defined(WINDOWS) #include #include @@ -69,6 +69,7 @@ extern void close_port(void); void raw_data_reset(void); void cal1_data(const float *data); void cal2_data(const float *data); +void calibration_confirmed(void); void raw_data(const int16_t *data); int send_calibration(void); void visualize_init(void); diff --git a/magcal.c b/magcal.c index b3496b9..3e80c4b 100644 --- a/magcal.c +++ b/magcal.c @@ -453,7 +453,7 @@ static void fUpdateCalibration10EIG(MagCalibration_t *MagCal) // sum between MINEQUATIONS to MAXEQUATIONS entries into the 10x10 product matrix matA iCount = 0; for (j = 0; j < MAGBUFFSIZE; j++) { - if (MagCal->valid[j] != -1) { + if (MagCal->valid[j]) { // use first valid magnetic buffer entry as estimate for offset // to help solution (bit counts) if (iCount == 0) { diff --git a/png2c.pl b/png2c.pl new file mode 100755 index 0000000..d622546 --- /dev/null +++ b/png2c.pl @@ -0,0 +1,95 @@ +#! /usr/bin/perl + +binmode IN, ":bytes"; +$file = $ARGV[0]; + +print "#include \n"; +print "#include \n"; +print "#include \n"; +print "\n"; + +foreach $file (@ARGV) { + open(IN, $file) or die "Can't open $file: $!\n"; + $count = 0; + $len = -s $file; + $filelist[$numfiles] = $file; + $file =~ /^([-_A-Za-z0-9\/]+)/; + $name = "png_$1"; + $name =~ s/\//_/g; + $name =~ s/-/_/g; + $size[$numfiles] = $len; + $list[$numfiles++] = $name; + print "static unsigned char ${name}[$len] = \{\n"; + while (read(IN, $byte, 1) == 1) { + $n = ord($byte); + printf '0x%02X', $n; + print ',' if $count < $len - 1; + print "\n" if $count++ % 12 == 11; + } + print "\};\n"; + close(IN); +} +print "\nstatic const unsigned char *png_image_list[] = {\n"; +for ($i=0; $i<$numfiles; $i++) { + print "$list[$i]"; + print ',' if $i < $numfiles - 1; + print "\n"; +} +print "\};\n"; + +print "\nstatic const char *png_image_name[] = {\n"; +for ($i=0; $i<$numfiles; $i++) { + print "\"$filelist[$i]\""; + print ',' if $i < $numfiles - 1; + print "\n"; +} +print "\};\n"; + +print "\nstatic const unsigned int png_image_size[] = {\n"; +for ($i=0; $i<$numfiles; $i++) { + print "$size[$i]"; + print ',' if $i < $numfiles - 1; + print "\n"; +} +print "\};\n"; + +print "\nstatic wxBitmap * image_list[] = {\n"; +for ($i=0; $i<$numfiles; $i++) { + print "NULL"; + print ',' if $i < $numfiles - 1; + print "\n"; +} + +print "\};\n\n"; + +print <= $numfiles || index < 0) return wxNullBitmap; + wxBitmap *p = image_list[index]; + if (p) return *p; + wxMemoryInputStream istream(png_image_list[index], png_image_size[index]); + wxImage img(istream, wxBITMAP_TYPE_PNG); + p = new wxBitmap(img); + return *p; +} + +wxBitmap MyBitmap(const char *name) +{ + for (size_t i=0; i < $numfiles; i++) { + if (strcmp(name, png_image_name[i]) == 0) return MyBitmap(i); + } + return wxNullBitmap; +} +EOT + + + + + + + + + + + diff --git a/quality.c b/quality.c index 3a3ee85..7e44354 100644 --- a/quality.c +++ b/quality.c @@ -1,5 +1,7 @@ #include "imuread.h" +// Discussion of what these 4 quality metrics really do +// https://forum.pjrc.com/threads/59277-Motion-Sensor-Calibration-Tool-Parameter-Understanding //static int countdown=1000; //static int pr=0; diff --git a/rawdata.c b/rawdata.c index 0547248..caaf063 100644 --- a/rawdata.c +++ b/rawdata.c @@ -6,6 +6,9 @@ static AccelSensor_t accel; static MagSensor_t mag; static GyroSensor_t gyro; +static float cal_data_sent[19]; +static int cal_confirm_needed=0; + void raw_data_reset(void) { rawcount = OVERSAMPLE_RATIO; @@ -25,7 +28,7 @@ static int choose_discard_magcal(void) int32_t rawx, rawy, rawz; int32_t dx, dy, dz; float x, y, z; - uint64_t distsq, minsum=0xFFFFFFFFFFFFFFFF; + uint64_t distsq, minsum=0xFFFFFFFFFFFFFFFFull; static int runcount=0; int i, j, minindex=0; Point_t point; @@ -120,28 +123,72 @@ static void add_magcal_data(const int16_t *data) magcal.valid[i] = 1; } +static int is_float_ok(float actual, float expected) +{ + float err, maxerr; + + err = fabsf(actual - expected); + maxerr = 0.0001f + fabsf(expected) * 0.00003f; + if (err <= maxerr) return 1; + return 0; +} + void cal1_data(const float *data) { -#if 0 - int i; + int i, ok; - printf("got cal1_data:\n"); - for (i=0; i<10; i++) { - printf(" %.5f\n", data[i]); + if (cal_confirm_needed) { + #if 0 + printf("expected cal1: "); + for (i=0; i<10; i++) { + printf(" %.5f,", cal_data_sent[i]); + } + printf("\ngot cal1_data: "); + for (i=0; i<10; i++) { + printf(" %.5f,", data[i]); + } + printf("\n"); + #endif + ok = 1; + for (i=0; i<10; i++) { + if (!is_float_ok(data[i], cal_data_sent[i])) ok = 0; + } + if (ok) { + cal_confirm_needed &= ~1; // got cal1 confirm + if (cal_confirm_needed == 0) { + calibration_confirmed(); + } + } } -#endif } void cal2_data(const float *data) { -#if 0 - int i; + int i, ok; - printf("got cal2_data:\n"); - for (i=0; i<9; i++) { - printf(" %.5f\n", data[i]); + if (cal_confirm_needed) { + #if 0 + printf("expected cal2: "); + for (i=0; i<9; i++) { + printf(" %.5f,", cal_data_sent[i+10]); + } + printf("\ngot cal2_data: "); + for (i=0; i<9; i++) { + printf(" %.5f,", data[i]); + } + printf("\n"); + #endif + ok = 1; + for (i=0; i<9; i++) { + if (!is_float_ok(data[i], cal_data_sent[i+10])) ok = 0; + } + if (ok) { + cal_confirm_needed &= ~2; // got cal2 confirm + if (cal_confirm_needed == 0) { + calibration_confirmed(); + } + } } -#endif } void raw_data(const int16_t *data) @@ -186,10 +233,10 @@ void raw_data(const int16_t *data) z = (float)data[2] * G_PER_COUNT; accel.GpFast[0] = x; accel.GpFast[1] = y; - accel.GpFast[2] = y; + accel.GpFast[2] = z; accel.Gp[0] += x; accel.Gp[1] += y; - accel.Gp[2] += y; + accel.Gp[2] += z; x = (float)data[3] * DEG_PER_SEC_PER_COUNT; y = (float)data[4] * DEG_PER_SEC_PER_COUNT; @@ -267,12 +314,15 @@ int send_calibration(void) *p++ = 84; for (i=0; i < 3; i++) { p = copy_lsb_first(p, 0.0f); // accelerometer offsets + cal_data_sent[0+i] = 0.0f; } for (i=0; i < 3; i++) { p = copy_lsb_first(p, 0.0f); // gyroscope offsets + cal_data_sent[3+i] = 0.0f; } for (i=0; i < 3; i++) { p = copy_lsb_first(p, magcal.V[i]); // 12 bytes offset/hardiron + cal_data_sent[6+i] = magcal.V[i]; } p = copy_lsb_first(p, magcal.B); // field strength p = copy_lsb_first(p, magcal.invW[0][0]); //10 @@ -281,6 +331,17 @@ int send_calibration(void) p = copy_lsb_first(p, magcal.invW[0][1]); //13 p = copy_lsb_first(p, magcal.invW[0][2]); //14 p = copy_lsb_first(p, magcal.invW[1][2]); //15 + cal_data_sent[9] = magcal.B; + cal_data_sent[10] = magcal.invW[0][0]; + cal_data_sent[11] = magcal.invW[0][1]; + cal_data_sent[12] = magcal.invW[0][2]; + cal_data_sent[13] = magcal.invW[1][0]; + cal_data_sent[14] = magcal.invW[1][1]; + cal_data_sent[15] = magcal.invW[1][2]; + cal_data_sent[16] = magcal.invW[2][0]; + cal_data_sent[17] = magcal.invW[2][1]; + cal_data_sent[18] = magcal.invW[2][2]; + cal_confirm_needed = 3; crc = 0xFFFF; for (i=0; i < 66; i++) { crc = crc16(crc, buf[i]); diff --git a/resource.rs b/resource.rc similarity index 52% rename from resource.rs rename to resource.rc index 6d3784b..ae193a5 100644 --- a/resource.rs +++ b/resource.rc @@ -1 +1,2 @@ MotionCal ICON "icon.ico" +#include "wx/msw/wx.rc" diff --git a/serialdata.c b/serialdata.c index faeb071..bdd2695 100644 --- a/serialdata.c +++ b/serialdata.c @@ -75,7 +75,7 @@ static int packet_magnetic_cal(const unsigned char *data) magcal.BpFast[1][n] = y; magcal.BpFast[2][n] = z; magcal.valid[n] = 1; - printf("mag cal, n=%3d: %5d %5d %5d\n", n, x, y, z); + //printf("mag cal, n=%3d: %5d %5d %5d\n", n, x, y, z); } return 1; } @@ -340,7 +340,7 @@ static int ascii_parse(const unsigned char *data, int len) } return ret; fail: - printf("ascii FAIL\n"); + //printf("ascii FAIL\n"); ascii_state = ASCII_STATE_WORD; ascii_raw_data_count = 0; ascii_num = 0; @@ -491,6 +491,7 @@ int open_port(const char *name) len = sizeof(COMMCONFIG); if (!GetCommConfig(port_handle, &port_cfg, &len)) { CloseHandle(port_handle); + port_handle = INVALID_HANDLE_VALUE; return 0; } port_cfg.dcb.BaudRate = 115200; @@ -512,10 +513,12 @@ int open_port(const char *name) port_cfg.dcb.StopBits = ONESTOPBIT; if (!SetCommConfig(port_handle, &port_cfg, sizeof(COMMCONFIG))) { CloseHandle(port_handle); + port_handle = INVALID_HANDLE_VALUE; return 0; } if (!EscapeCommFunction(port_handle, CLRDTR | CLRRTS)) { CloseHandle(port_handle); + port_handle = INVALID_HANDLE_VALUE; return 0; } timeouts.ReadIntervalTimeout = MAXDWORD; @@ -525,10 +528,12 @@ int open_port(const char *name) timeouts.WriteTotalTimeoutConstant = 0; if (!SetCommTimeouts(port_handle, &timeouts)) { CloseHandle(port_handle); + port_handle = INVALID_HANDLE_VALUE; return 0; } if (!EscapeCommFunction(port_handle, SETDTR)) { CloseHandle(port_handle); + port_handle = INVALID_HANDLE_VALUE; return 0; } return 1; @@ -542,10 +547,17 @@ int read_serial_data(void) unsigned char buf[256]; int r; + if (port_handle == INVALID_HANDLE_VALUE) return -1; while (1) { - if (!ClearCommError(port_handle, &errmask, &st)) return -1; + if (!ClearCommError(port_handle, &errmask, &st)) { + r = -1; + break; + } //printf("Read, %d requested, %lu buffered\n", count, st.cbInQue); - if (st.cbInQue <= 0) return 0; + if (st.cbInQue <= 0) { + r = 0; + break; + } // now do a ReadFile, now that we know how much we can read // a blocking (non-overlapped) read would be simple, but win32 // is all-or-nothing on async I/O and we must have it enabled @@ -585,6 +597,10 @@ int read_serial_data(void) if (r <= 0) break; newdata(buf, r); } + if (r < 0) { + CloseHandle(port_handle); + port_handle = INVALID_HANDLE_VALUE; + } return r; } @@ -622,7 +638,7 @@ int write_serial_data(const void *ptr, int len) void close_port(void) { CloseHandle(port_handle); - port_handle = NULL; + port_handle = INVALID_HANDLE_VALUE; } diff --git a/visualize.c b/visualize.c index 1141686..7fc11ab 100644 --- a/visualize.c +++ b/visualize.c @@ -46,6 +46,14 @@ static void rotate(const Point_t *in, Point_t *out, const float *rmatrix) static GLuint spherelist; static GLuint spherelowreslist; +int invert_q0=0; +int invert_q1=0; +int invert_q2=0; +int invert_q3=1; +int invert_x=0; +int invert_y=0; +int invert_z=0; + void display_callback(void) { int i; @@ -76,11 +84,22 @@ void display_callback(void) memcpy(&orientation, ¤t_orientation, sizeof(orientation)); // TODO: this almost but doesn't perfectly seems to get the // real & screen axes in sync.... - //orientation.q0 *= -1.0f; - //orientation.q1 *= -1.0f; - //orientation.q2 *= -1.0f; - orientation.q3 *= -1.0f; + if (invert_q0) orientation.q0 *= -1.0f; + if (invert_q1) orientation.q1 *= -1.0f; + if (invert_q2) orientation.q2 *= -1.0f; + if (invert_q3) orientation.q3 *= -1.0f; quad_to_rotation(&orientation, rotation); + + //rotation[0] *= -1.0f; + //rotation[1] *= -1.0f; + //rotation[2] *= -1.0f; + //rotation[3] *= -1.0f; + //rotation[4] *= -1.0f; + //rotation[5] *= -1.0f; + //rotation[6] *= -1.0f; + //rotation[7] *= -1.0f; + //rotation[8] *= -1.0f; + for (i=0; i < MAGBUFFSIZE; i++) { if (magcal.valid[i]) { apply_calibration(magcal.BpFast[0][i], magcal.BpFast[1][i], @@ -91,6 +110,9 @@ void display_callback(void) quality_update(&point); rotate(&point, &draw, rotation); glPushMatrix(); + if (invert_x) draw.x *= -1.0f; + if (invert_y) draw.y *= -1.0f; + if (invert_z) draw.z *= -1.0f; glTranslatef( draw.x * xscale + xoff, draw.z * yscale + yoff,