menuLoop()
is where settings can be changed while the sketch runs. The top half of this function deals with the menu on the screen as the user clicks the B button. It keeps track of which menu lines should appear on the screen as the user scrolls down, which line is highlighted with the black stripe cursor, but not what each menu text should say. That's done later by the menuLines()
function.
boolean menuLoop(uint16_t bgColor) { // Lay out a menu screen, interact to change values int counter1 = 0, scrollPosition = 0; boolean exitFlag = false, settingsChanged = false; uint32_t menuButtons; arcada.display->fillScreen(bgColor); arcada.display->fillRect(0, 12 * (counter1 + scrollPosition) + MENU_VPOS - 2, 160, 12, 0x0000); // Black stripe cursor on menu arcada.display->setTextColor(0xFFFF); // White text arcada.display->setCursor(16, 120); // at screen bottom arcada.display->print("B:ADVANCE A:CHANGE"); // for button labels for(counter1 = 0; counter1 < MENU_ROWS; ++counter1) { // Display menu texts menuLines(counter1, scrollPosition); } counter1 = 0; while(!exitFlag) { // Loop until exit is activated if(!buttonActive && (buttonBits & ARCADA_BUTTONMASK_B)) { // Fresh press of B:ADVANCE button? buttonActive = true; // Set button flag deBounce = millis() + DE_BOUNCE; // and start debounce timer. arcada.display->fillRect(0, 12 * (counter1 - scrollPosition) + MENU_VPOS - 2, 160, 12, bgColor); // Erase cursor & text menuLines(counter1, scrollPosition); // Refresh menu text line counter1 = (counter1 + 1) % MENU_LEN; // Advance menu counter if(counter1 == 0) { // Have we cycled around to the menu top? scrollPosition = 0; for(int counter2 = 0; counter2 < MENU_ROWS; ++counter2) { // Redisplay all menu texts arcada.display->fillRect(0, 12 * counter2 + MENU_VPOS - 2, 160, 12, bgColor); // Erase old text menuLines(counter2 + scrollPosition, scrollPosition); // Redraw each text line } } else if((counter1 + 1 < MENU_LEN) && (counter1 - scrollPosition == MENU_ROWS - 1)) { // Should we scroll down 1 menu line? ++scrollPosition; for(int counter2 = 0; counter2 < MENU_ROWS; ++counter2) { // Redisplay all menu texts arcada.display->fillRect(0, 12 * counter2 + MENU_VPOS - 2, 160, 12, bgColor); // Erase old text menuLines(counter2 + scrollPosition, scrollPosition); // Redraw each text line } } arcada.display->fillRect(0, 12 * (counter1 - scrollPosition) + MENU_VPOS - 2, 160, 12, 0x0000); // New black cursor menuLines(counter1, scrollPosition); // Refresh text line deBounce = millis() + DE_BOUNCE; // Restart debounce timer, just for safety }
Here's the other half of menuLoop()
. It controls what happens when button A clicks. A switch()
statement jumps to the right code snippet, depending on where the menu cursor is standing. Some choices just toggle a Boolean flag, some step through a range of numbers, and some immediately affect the hardware, like the backlight LED. Once done, the screen menu gets updated, the button debounce values are refreshed, and the loop either exits or continues, depending on what got clicked.
if(!buttonActive && (buttonBits & ARCADA_BUTTONMASK_A)) { // Fresh press of A:CHANGE button? buttonActive = true; // Set button flag deBounce = millis() + DE_BOUNCE; // and start debounce timer. switch(counter1) { // Change whichever setting is currently hilighted case 0: showLastCap = true; // Set flag to display the last frame captured to SD exitFlag = true; // and exit break; case 1: celsiusFlag = !celsiusFlag; // Toggle Celsius/Fahrenheit break; case 2: buttonRfunc = (buttonRfunc + 1) % 3; // Step through button functions break; case 3: loadPalette(paletteNum = (paletteNum + 1) % 5); // Step through various color palettes break; case 4: thermRange = (thermRange + 1) % 6; // Step through various temp range presets break; case 5: markersOn = !markersOn; // Toggle hot/cold marker visibility break; case 6: mirrorFlag = !mirrorFlag; // Toggle mirrored display break; case 7: switch(frameRate = (frameRate + 1) % 6) { // 6 frame rates, 0.5 to 16 in powers of 2 case 0: mlx.setRefreshRate(MLX90640_0_5_HZ); break; case 1: mlx.setRefreshRate(MLX90640_1_HZ); break; case 2: mlx.setRefreshRate(MLX90640_2_HZ); break; case 3: mlx.setRefreshRate(MLX90640_4_HZ); break; case 4: mlx.setRefreshRate(MLX90640_8_HZ); break; default: mlx.setRefreshRate(MLX90640_16_HZ); break; } break; case 8: emissivity = (emissivity + 90) % 100; // Step from 95% to 5% by -10% break; case 9: smoothing = !smoothing; // Toggle pixel smoothing break; case 10: arcada.setBacklight((screenDim = !screenDim) ? 64 : 255); // Change backlight LED break; default: exitFlag = true; break; } if((counter1 > 0) && (counter1 < MENU_LEN - 1)) // Was any setting just changed? settingsChanged = true; arcada.display->fillRect(0, 12 * (counter1 - scrollPosition) + MENU_VPOS - 2, 160, 12, 0x0000); // Erase hilit menu line menuLines(counter1, scrollPosition); // Retype hilit menu line } if(buttonActive && millis() > deBounce && (buttonBits & (ARCADA_BUTTONMASK_A | ARCADA_BUTTONMASK_B)) == 0) // Has de-bounce wait expired & all buttons released? buttonActive = false; // Clear flag to allow another button press } return(settingsChanged); }
When menuLoop()
displays a menu on the screen, it lets the menuLines()
function print each line, one at a time. This function receives two numbers, the number of the line item to print, and where to print it. The name for each setting appears on the left, and its current value on the right. A couple of frills are there as well, like scrolling arrows and mini color palettes.
void menuLines(int lineNumber, int scrollPos) { // Screen print a single line in the settings menu arcada.display->setTextColor(0xFFFF); // White text arcada.display->setCursor(10, 12 * (lineNumber - scrollPos) + MENU_VPOS); // Menu lines 12 pixels apart if(lineNumber - scrollPos == 0 && scrollPos > 0) { // Are any menu lines scrolled off screen top? arcada.display->print(" ^"); // Print a small up arrow indicator } else if(lineNumber - scrollPos == 8 && lineNumber + 1 < MENU_LEN) { // How about off the bottom? arcada.display->print(" v"); // Print a small down arrow indicator... yeah, it's a v } else { switch(lineNumber) { case 0: arcada.display->print(" Display last capture"); break; case 1: arcada.display->print(" Scale - "); arcada.display->print(celsiusFlag ? "CELSIUS" : "FAHRENHEIT"); break; case 2: arcada.display->print(" Rt button - "); switch(buttonRfunc) { case 1: arcada.display->print("CAPTURE"); break; case 2: arcada.display->print("RECORD"); break; default: arcada.display->print("FREEZE"); break; } break; case 3: arcada.display->print(" Palette - "); for(int xPos = 0; xPos < 72; ++xPos) // Display the current heat spectrum colors arcada.display->drawFastVLine(xPos + 87, (lineNumber - scrollPos) * 12 + MENU_VPOS, 8, colorPal[map(xPos, 0, 71, 0, 255)]); switch(paletteNum) { case 1: arcada.display->print("IRONBOW"); break; case 2: arcada.display->print("FIREBOW"); break; case 3: arcada.display->setTextColor(0x0000); // Black text for reverse contrast arcada.display->print("ALARM"); break; case 4: arcada.display->setTextColor(0x0000); // Black text arcada.display->print("BLACK HOT"); break; default: arcada.display->print("WHITE HOT"); break; } break; case 4: arcada.display->print("Temp range - "); setColorRange(thermRange); switch(thermRange) { case 1: arcada.display->print("STANDARD"); break; case 2: arcada.display->print("COOL/WARM"); break; case 3: arcada.display->print("WARM/WARMER"); break; case 4: arcada.display->print("HOT SPOTS"); break; case 5: arcada.display->print("FIRE & ICE"); break; default: arcada.display->print("AUTO-RANGE"); break; } break; case 5: arcada.display->print(" Markers - "); arcada.display->print(markersOn ? "ON" : "OFF"); break; case 6: arcada.display->print(" Image - "); arcada.display->print(mirrorFlag ? "MIRRORED" : "FORWARD"); break; case 7: arcada.display->print("Frame rate - "); arcada.display->print((float)(1 << frameRate) * 0.5); arcada.display->print(" FPS"); break; case 8: arcada.display->setTextColor(GRAY_33 << 1); // Grayed menu item arcada.display->print("Emissivity - "); arcada.display->print(emissivity); arcada.display->print("%"); break; case 9: arcada.display->setTextColor(GRAY_33 << 1); // Grayed menu item arcada.display->print(" Smoothing - "); arcada.display->print(smoothing ? "ON" : "OFF"); break; case 10: arcada.display->print(" Backlight - "); arcada.display->print(screenDim ? "DIM" : "FULL"); break; case 11: arcada.display->print(" Exit menu"); } } }
The last little function is the one I promised way earlier. Its only job is to set Boolean flags whenever it detects button A or B pressing. That's it, but it gets called by a timer 50 times a second no matter what else is in progress. That way almost no button clicks get missed, and interactivity is preserved. Nice function.
// This is the function that substitutes for GPIO external interrupts // It will check for A and B button presses at 50Hz void buttonCatcher(void) { buttonBits = arcada.readButtons(); clickFlagMenu |= (buttonBits & ARCADA_BUTTONMASK_B) != 0; clickFlagSelect |= (buttonBits & ARCADA_BUTTONMASK_A) != 0; }
You made it to the end! Well done! The only prize I can offer is a deeper insight into the workings of your thermal camera software. That, plus the Processing sketches that can turn your captured images into fascinating visual media. They're next.
Page last edited March 08, 2024
Text editor powered by tinymce.