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!

This guide was first published on Mar 27, 2020. It was last updated on Mar 27, 2020.

This page (Code Walkthrough: Setting Up) was last updated on Oct 23, 2021.

Text editor powered by tinymce.