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.