/*
PAF9701 8 x 8 pixel low-power IR thermal imaging sensor
Copyright 2021 Tlera Corporation
PixArt Imaging's 8 x 8 pixel IR thermal imaging sensor offers wide (60 deg) field of view,
low-power (2 mA) normal mode current usage, wide (-20 to 380 C) object temperature range,
and accurate (+/- 1 degree C) 16-bit object temperatures.
The sketch demonstrates how to initialize the PAF9701 in normal run mode, configure the
data filters and image orientation, set up the data ready interrupt, read the data and plot
the properly scaled data on the serial monitor and on a 160 x 128 pixel Adafruit TFT color display.
The sketch is intended to run using a Tlera Corporation STM32L432 Ladybug development board but just about
any 3V3 dev board with an SPI port (for the display) and I2C port (for the PAF9701) will do.
This sketch may be used without limitations with proper attribution
This example code is in the public domain.
*/
#include "PAF9701.h"
#include <LiquidCrystal_I2C.h>
// TODO: Maybe use different library?: https://github.com/johnrickman/LiquidCrystal_I2C/issues/47
// Display
#define I2C_ADDR 0x27
#define LCD_COLUMNS 16
#define LCD_LINES 2
// PAF9701 definitions
#define PAF9701_SDA D1
#define PAF9701_SCL D5
#define PAF9701_intPin D6 // interrupt pin active LOW
#define PAF9701_shutdownPin D7 // shutdown pin, HIGH for standby, LOW for run mode
#define PAF9701_resetPin D2 // reset pin active LOW
// For Wokwi Sim
// #define PAF9701_SDA 21
// #define PAF9701_SCL 22
// #define PAF9701_intPin 16 // interrupt pin active LOW
// #define PAF9701_shutdownPin 17 // shutdown pin, HIGH for standby, LOW for run mode
// #define PAF9701_resetPin 18 // reset pin active LOW
#define I2C_BUS Wire // Define the I2C bus (Wire instance) you wish to use
I2Cdev i2c_0(&I2C_BUS); // Instantiate the I2Cdev object and point to the desired I2C bus
bool SerialDebug = true;
// Configure the PAF9701
uint8_t runMode = normal_mode; // choices are normal_mode, detection_mode1, detection_mode2, detection_mode3
uint8_t freq = 1; // data rate in Hz, default is 4 Hz, should not be faster than 10 Hz
uint32_t sampleRate = 200000 / (256 * freq); // maximum frame time is 1342 seconds, minimum ~100 ms
uint8_t imageFlip = noflipormirror; // choices are noflipormirror, flip, mirror, flip and mirror
uint8_t imageRotate = orient0; // choices are orient0, orient90, orient180, or orient270
uint8_t digitalFilter = movingAverage; // choices are normalAverage, movingAverage, IIR
uint8_t frameAverage = fourFrames; // choices are oneFrame, twoFrames, fourFrames, and eightFrames
uint8_t IIRAverage = frames0_1; // choices are frames 0_1, frames125_875, ..., frames825_175
bool settle_en = false; // allow settling (~3 sec) before sensor data made available when sensor operation resumes from suspend
int16_t rawTaData = 0, calTaData = 0;
uint8_t temp = 0;
float temperatures[64]; //Contains the calculated object temperature of each pixel in the array
float minTemp, maxTemp, centerTemp, tmpTemp;
volatile bool PAF9701_intFlag = false; // Logic flag for alert signal
PAF9701 PAF9701(&i2c_0); // instantiate PAF9701 class
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
char centerChar = 169;
void IRAM_ATTR PAF9701_inthandler() {
PAF9701_intFlag = true;
}
void setup() {
/* Enable USB UART */
Serial.begin(115200);
pinMode(PAF9701_shutdownPin, OUTPUT);
digitalWrite(PAF9701_shutdownPin, LOW); // shutdown active HIGH
pinMode(PAF9701_resetPin, OUTPUT);
digitalWrite(PAF9701_resetPin, HIGH); // shutdown active LOW
/* initialize wire bus */
I2C_BUS.begin(PAF9701_SDA, PAF9701_SCL);
I2C_BUS.setClock(400000); // I2C frequency at 400 kHz
delay(100);
Serial.println("Scan for I2C devices:");
i2c_0.I2Cscan(); // should detect PAF9701 at 0x14 and BME280 at 0x77
delay(100);
// Display
lcd.init();
lcd.backlight();
lcd.setCursor(5, 0);
lcd.print("Setup");
// Read the PAF9701 Chip ID register, this is a good test of communication
Serial.println("PAF9701 thermal sensor...");
uint16_t PAF9701_CHIPID = PAF9701.getChipID(); // Read CHIP_ID for PAF9701
Serial.print("PAF9701 "); Serial.print("I AM 0x"); Serial.print(PAF9701_CHIPID, HEX); Serial.print(" I should be 0x"); Serial.println(0x0280, HEX);
Serial.println(" ");
delay(100);
// check if all I2C sensors with WHO_AM_I have acknowledged
if(PAF9701_CHIPID == 0x0280) {
Serial.println("PAF9701 is online..."); Serial.println(" ");
PAF9701.coldReset(); // software reset before initialization
delay(200); // wait 200 ms for reset
while( !(PAF9701.getStatus() & 0x20) ) {} // wait for flash bootload to complete
Serial.println("Flash Bootload done!"); Serial.println(" ");
PAF9701.initNormalMode(runMode, sampleRate, settle_en); // select sensor run mode
Serial.print("Sample rate = 0x"); Serial.println(sampleRate, HEX); Serial.println(" ");
temp = PAF9701.getPowerSaveMode();
Serial.print("power save mode register = 0x"); Serial.println(temp, HEX);
PAF9701.setFilter(digitalFilter, frameAverage, IIRAverage);
PAF9701.imageOrientation(imageFlip, imageRotate);
PAF9701.clearInterrupt();
PAF9701.resumeOperation();
if(settle_en) delay(3000); // takes about 3 seconds to settle when settle function enabled
} else {
Serial.println(" PAF9701 not functioning!");
}
pinMode(PAF9701_intPin, INPUT); // define PAF9701 interrupt
attachInterrupt(digitalPinToInterrupt(PAF9701_intPin), PAF9701_inthandler, FALLING); // attach interrupt for INT pin output of PAF9701
PAF9701.clearInterrupt();
} /* end of setup */
void loop() {
// PAF9701 interrupt handling
if(PAF9701_intFlag) { // data ready
PAF9701_intFlag = false;
PAF9701.clearInterrupt();
// Get PAF9701 data
rawTaData = PAF9701.getRawTaData(); // ambient temperature
calTaData = PAF9701.getCalTaData();
PAF9701.getToData(temperatures); // object temperature
// Get min and max temperatures for display
minTemp = 1000.0f;
maxTemp = 0.0f;
for(int y=0; y<8; y++){ //go through all the rows
for(int x=0; x<8; x++){ //go through all the columns
if(temperatures[y+x*8] > maxTemp) maxTemp = temperatures[y+x*8];
if(temperatures[y+x*8] < minTemp) minTemp = temperatures[y+x*8];
}
}
for(int y=0; y<8; y++){ //go through all the rows
for(int x=0; x<8; x++){ //go through all the columns
tmpTemp = temperatures[y+x*8];
Serial.print(tmpTemp, 1); Serial.print(","); // use the serial monitor to plot the data, TFT diplay would be better
if(x == 7) Serial.println(" ");
if(y+x*8 == 63) Serial.println(" ");
// rgb =(uint8_t) (((tmpTemp - minTemp)/(maxTemp - minTemp)) * 199); // 0 - 199 = 200 possible rgb color values
// red = rgb_colors[rgb*3] >> 3; // keep 5 MS bits
// green = rgb_colors[rgb*3 + 1] >> 2; // keep 6 MS bits
// blue = rgb_colors[rgb*3 + 2] >> 3; // keep 5 MS bits
// color = red << 11 | green << 5 | blue; // construct rgb565 color for tft display
// tft.fillRect(x*16, y*16, 16, 16, color); // data on 128 x 128 pixels of a 160 x 128 pixel display
}
}
// tft.fillRect(128, 0, 32, 128, BLACK); // fill 32 x 128 pixel non-data patch with black
// tft.setRotation(0); // 0, 2 are portrait mode, 1,3 are landscape mode
// tft.setTextSize(0);
// tft.setTextColor(WHITE);
// tft.setCursor(32, 4 ); // write min,max temperature on non-data patch
// tft.print("min T = "); tft.print((uint8_t) minTemp); tft.print(" C");
// tft.setCursor(32, 20 );
// tft.print("max T = "); tft.print((uint8_t) maxTemp); tft.print(" C");
// tft.setRotation(3); // 0, 2 are portrait mode, 1,3 are landscape mode
lcd.setCursor(0, 0);
lcd.print("Min: ");
lcd.setCursor(0, 1);
lcd.print(minTemp, 1);
lcd.setCursor(6, 0);
lcd.print("Max: ");
lcd.setCursor(6, 1);
lcd.print(maxTemp, 1);
lcd.setCursor(12, 0);
lcd.print("Cen: ");
lcd.setCursor(12, 1);
// centerTemp = (temperatures[3*8+3] + temperatures[3*8+4] + temperatures[4*8+3] + temperatures[4*8+4]) / 4;
centerTemp = (temperatures[27] + temperatures[28] + temperatures[35] + temperatures[36]) / 4;
lcd.print(centerTemp, 1);
if(SerialDebug) {
Serial.print("min T = "); Serial.println((uint8_t) minTemp);
Serial.print("max T = "); Serial.println((uint8_t) maxTemp);
// Serial.print("Raw Ta ADC counts = "); Serial.println(rawTaData);
// Serial.print("Cal Ta Data = "); Serial.print((float)calTaData * 0.03125f); Serial.println(" C"); Serial.println(" ");
}
}
delay(50);
} /* end of loop*/