The sketch is on the large side. I tried to sprinkle it richly with comments, but here I'll give a few more details as we break it down into chunks. (Is that the technical term?)
At the beginning are the libraries, constants, global variables and arrays that will be used throughout the sketch. They dictate how BMP files are named, the bytes that go into their file headers, and bitmaps for the onscreen icons. Variables and Booleans are used to keep track of what the sketch needs to do at each step. Some of them can be changed to different startup values if you wish, like frameRate
.
#include <Adafruit_MLX90640.h> #include "Adafruit_Arcada.h" Adafruit_MLX90640 mlx; Adafruit_Arcada arcada; #if !defined(USE_TINYUSB) #warning "Compile with TinyUSB selected!" #endif File myFile; float mlx90640To[768]; // Here we receive the float vals acquired from MLX90640 #define DE_BOUNCE 200 // Wait this many msec between button clicks #define MENU_LEN 12 // Number of total available menu choices #define MENU_ROWS 9 // Number of menu lines that can fit on screen #define MENU_VPOS 6 #define GRAY_33 0x528A #define BOTTOM_DIR "MLX90640" #define DIR_FORMAT "/dir%05d" #define BMP_FORMAT "/frm%05d.bmp" #define CFG_FLNAME "/config.ini" #define MAX_SERIAL 999 // BMP File Header, little end first, Photoshop ver. const PROGMEM uint8_t BmpPSPHead[14] = { 0x42, 0x4D, // "BM" in hex 0x38, 0x09, 0x00, 0x00, // File size, 2360 0x00, 0x00, // reserved for app data 1 0x00, 0x00, // reserved for app data 2 0x36, 0x00, 0x00, 0x00 // Offset of first pixel, 54 }; // BMP 24-bit DIB Header, little end first, Photoshop ver. const PROGMEM uint8_t DIBHeadPSP1[40] = { 0x28, 0x00, 0x00, 0x00, // Header size, 40 0x20, 0x00, 0x00, 0x00, // pixel width, 32 0x18, 0x00, 0x00, 0x00, // pixel height, 24 0x01, 0x00, // color planes, 1 0x18, 0x00, // bits per pixel, 24 0x00, 0x00, 0x00, 0x00, // Compression method, 0==none 0x00, 0x00, 0x00, 0x00, // Raw bitmap data size, dummy 0 0x12, 0x0B, 0x00, 0x00, // Pixels per meter H, 2834 0x12, 0x0B, 0x00, 0x00, // Pixels per meter V, 2834 0x00, 0x00, 0x00, 0x00, // Colors in palette, 0==default 2^n 0x00, 0x00, 0x00, 0x00 // Number of important colors, 0 }; // BMP file data, 2 byte padding const PROGMEM uint8_t PSPpad[2] = {0x00, 0x00}; //Byte arrays of bitmapped icons, 16 x 12 px: const PROGMEM uint8_t battIcon[] = { 0x0f, 0x00, 0x3f, 0xc0, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x3f, 0xc0}; const PROGMEM uint8_t camIcon[] = { 0x01, 0xe0, 0x61, 0x20, 0xff, 0xf0, 0x80, 0x10, 0x86, 0x10, 0x89, 0x10, 0x90, 0x90, 0x90, 0x90, 0x89, 0x10, 0x86, 0x10, 0x80, 0x10, 0xff, 0xf0}; const PROGMEM uint8_t SDicon[] = { 0x0f, 0xe0, 0x1f, 0xe0, 0x3c, 0x60, 0x78, 0x60, 0x70, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x6f, 0x60, 0x60, 0x60, 0x7f, 0xe0, 0x7f, 0xe0}; const PROGMEM uint8_t snowIcon[] = { 0x15, 0x00, 0x4E, 0x40, 0xC4, 0x60, 0x75, 0xC0, 0x9F, 0x20, 0x0E, 0x00, 0x0E, 0x00, 0x9F, 0x20, 0x75, 0xC0, 0xC4, 0x60, 0x4E, 0x40, 0x15, 0x00}; uint8_t pixelArray[2304]; // BMP image body, 32 pixels * 24 rows * 3 bytes // Some global values that several functions will use, including // 5 floats to append to the BMP pixel data: // coldest pixel, coldest color, center temp, hottest color, hottest pixel float sneakFloats[5] = {3.1415926, 0.0, -11.7, 98.6, -12.34}; // Test values that get overwritten uint16_t highAddr = 0, lowAddr = 0; // Append the pixel addresses, too uint16_t backColor, lowPixel, highPixel, buttonRfunc = 1, emissivity = 95, frameRate = 4, thermRange = 0, paletteNum = 1, colorPal[256], // Array for color palettes nextDirIndex = 0, nextBMPindex = 0, nextBMPsequence = 1; // These keep count of SD files and dirs, 0==error uint32_t deBounce = 0, buttonBits = 0; boolean mirrorFlag = false, celsiusFlag = false, markersOn = true, screenDim = false, smoothing = false, showLastCap = false, save1frame = false, recordingInProg = false, buttonActive = false; float battAverage = 0.0, colorLow = 0.0, colorHigh = 100.0; // Values for managing color range volatile boolean clickFlagMenu = false, clickFlagSelect = false; // Volatiles for timer callback handling
The setup()
function executes first and just once per startup. Its job is to initialize the system, starting with the Arcada code and the onboard hardware it will control, like the screen and buttons, etc.
Then a set of tests determines whether flash storage is ready to receive fresh BMP files.
void setup() { if (!arcada.arcadaBegin()) { // Start TFT and fill with black // Serial.print("Failed to begin"); while (1); } arcada.filesysBeginMSD(); // Set up SD or QSPI flash as an external USB drive arcada.displayBegin(); // Activate TFT screen arcada.display->setRotation(1); // wide orientation arcada.display->setTextWrap(false); arcada.setBacklight(255); // Turn on backlight battAverage = arcada.readBatterySensor(); Serial.begin(115200); // while(!Serial); // Wait for user to open terminal Serial.println("MLX90640 IR Array Example"); if(arcada.filesysBegin()){ // Initialize flash storage, begin setting up indices for saving BMPs if(!arcada.exists(BOTTOM_DIR)) { // Is base "MLX90640" directory absent? if(arcada.mkdir(BOTTOM_DIR)) // Can it be added? nextDirIndex = nextBMPindex = 1; // Success, prepare to store numbered files & dirs } else { // "MLX90640" directory exists, can we add files | directories? // Get the number of the next unused serial directory path nextDirIndex = availableFileNumber(1, BOTTOM_DIR + String(DIR_FORMAT)); // and the next unused serial BMP name nextBMPindex = availableFileNumber(1, BOTTOM_DIR + String(BMP_FORMAT)); } } // By now each global index variable is either 0 (no nums available), or the next unclaimed serial num
Next, the MLX90640 itself must be found, initialized, and configured for use.
If this is successful, the screen is cleared and a basic backdrop is displayed. A function called timerCallback()
is invoked. It will cause another function to be executed at a set rate. In our case, the function is named buttonCatcher()
. It's located at the end of the sketch, and it will execute 50 times a second. Its job will be to rapidly test and remember whether buttons B or A are being pressed. (It's a way of forcing the sketch to regularly check for button clicks, even when it's tied up doing a slow process.)
if(!mlx.begin(MLX90640_I2CADDR_DEFAULT, &Wire)) { Serial.println("MLX90640 not found!"); arcada.haltBox("MLX90640 not found!"); while(1) delay(10); // Halt here } Serial.println("Found Adafruit MLX90640"); Serial.print("Serial number: "); Serial.print(mlx.serialNumber[0], HEX); Serial.print(mlx.serialNumber[1], HEX); Serial.println(mlx.serialNumber[2], HEX); //mlx.setMode(MLX90640_INTERLEAVED); mlx.setMode(MLX90640_CHESS); mlx.setResolution(MLX90640_ADC_18BIT); switch(frameRate) { case 0: mlx.setRefreshRate(MLX90640_0_5_HZ); break; // 6 frame rates, 0.5 to 16 FPS in powers of 2 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; } Wire.setClock(1000000); // max 1 MHz for(int counter01 = 0; counter01 < 2304; ++counter01) pixelArray[counter01] = counter01 / 9; // Initialize BMP pixel buffer with a gradient loadPalette(paletteNum); // Load false color palette backColor = GRAY_33; // 33% gray for BG setBackdrop(backColor, buttonRfunc); // Current BG, current button labels arcada.timerCallback(50, buttonCatcher); // Assign a 50Hz callback function to catch button presses }
The sketch by now should be ready for the next part, sensing temperatures, displaying pixels, and clicking buttons. Cool stuff ahead!
Page last edited March 08, 2024
Text editor powered by tinymce.