├── images ├── pinmap.jpeg ├── sample_1.jpeg └── sample_2.jpeg ├── LICENSE.md ├── README.md ├── Lepton.cpp ├── Lepton.h ├── main.ino └── 3.5 /images/pinmap.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSpiner/ThermalScope/HEAD/images/pinmap.jpeg -------------------------------------------------------------------------------- /images/sample_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSpiner/ThermalScope/HEAD/images/sample_1.jpeg -------------------------------------------------------------------------------- /images/sample_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSpiner/ThermalScope/HEAD/images/sample_2.jpeg -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2022] [JSpiner] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ThremalScope 2 | ESP32 + Lepton based Toy Thermal Scope Project 3 | 4 | ![Sample1](./images/sample_1.jpeg) 5 | ![Sample1](./images/sample_2.jpeg) 6 | 7 | # Pinmap 8 | ``` 9 | /* Lepton Pinmap 10 | * Lepton ESP32 11 | * GND(1) -> GND 12 | * Power(2) -> VCC 13 | * SDA(5) -> 21 14 | * SCL(8) -> 22 15 | * CS(10) -> 5 16 | * CLK(SCK)(7)-> 18 17 | * MISO(12) -> 19 18 | * MOSI(9) -> 23 19 | */ 20 | 21 | /* ILI9341 320x240 tft LCD Pinmap (HSPI) 22 | * LCD ESP32 23 | * CS -> 15 24 | * RESET -> 4 25 | * DC -> 2 26 | * SDI(MOSI) -> 13 27 | * CLK(SCK) -> 14 28 | * LED -> VCC 29 | * SDOK(MISO) -> 12 30 | * T_CLK -> SCK 31 | * T_CS -> 34 32 | * T_DIN -> MOSI 33 | * T_DO -> MISO 34 | ``` 35 | 36 | ![Pinmap](./images/pinmap.jpeg) 37 | 38 | # Legal Compliance in Korea 39 | 이 프로젝트는 레저용 장난감(에어소프건) 용도로 개발되었습니다. 40 | 41 | 프로젝트 내에선 영점조절 기능이 사용 불가능하도록 처리되어 있습니다. 42 | 43 | 한국에서 본 프로젝트의 영점조절 기능을 구현 할 경우 조준경(총포 부품) 해석될 수 있음을 알립니다. 44 | 45 | [총포ㆍ도검ㆍ화약류 등의 안전관리에 관한 법률 시행령](https://www.law.go.kr/%EB%B2%95%EB%A0%B9/%EC%B4%9D%ED%8F%AC%E3%86%8D%EB%8F%84%EA%B2%80%E3%86%8D%ED%99%94%EC%95%BD%EB%A5%98%EB%93%B1%EC%9D%98%EC%95%88%EC%A0%84%EA%B4%80%EB%A6%AC%EC%97%90%EA%B4%80%ED%95%9C%EB%B2%95%EB%A5%A0%EC%8B%9C%ED%96%89%EB%A0%B9) 46 | 47 | # OSS Notice 48 | - [GroupGets/LeptonModule](https://github.com/groupgets/LeptonModule)
49 | Copyright (c) 2014, Pure Engineering LLC
50 | BSD 2-Clause 51 | 52 | - [matt-williams/arduino-lepton](https://github.com/matt-williams/arduino-lepton)
53 | Copyright (c) 2016, Matt Williams 54 | BSD License 55 | -------------------------------------------------------------------------------- /Lepton.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lepton.h - Library for accessing the Lepton thermal imaging camera (http://lepton.flir.com/) 3 | Created by Matt Williams, 17-19 June 2016 4 | BSD license - check license.txt for more information 5 | */ 6 | 7 | #include "Arduino.h" 8 | #include "Lepton.h" 9 | #include "Wire.h" 10 | #include "SPI.h" 11 | 12 | Lepton::Lepton(int sdaPin, int sclPin, int ssPin, int width, int height) : _sdaPin(sdaPin), _sclPin(sclPin), _ssPin(ssPin), _width(width), _height(height) { 13 | } 14 | 15 | void Lepton::begin() { 16 | Wire.begin(_sdaPin, _sclPin); 17 | 18 | pinMode(_ssPin, OUTPUT); 19 | digitalWrite(_ssPin, HIGH); 20 | 21 | SPI.begin(); 22 | } 23 | 24 | uint16_t Lepton::readRegister(uint16_t reg) { 25 | setRegister(reg); 26 | Wire.requestFrom(DEVICE_ID, (uint8_t)2); 27 | return readWord(); 28 | } 29 | 30 | void Lepton::writeRegister(uint16_t reg, uint16_t value) { 31 | startTransmission(reg); 32 | transmitWord(value); 33 | endTransmission(); 34 | } 35 | 36 | uint16_t Lepton::doGetCommand(uint16_t commandIdBase, uint16_t* data) { 37 | writeRegister(REG_COMMAND_ID, commandIdBase | TYPE_GET); 38 | waitIdle(); 39 | return readData(data); 40 | } 41 | 42 | void Lepton::doSetCommand(uint16_t commandIdBase, uint16_t* data, uint16_t dataLen) { 43 | writeData(data, dataLen); 44 | writeRegister(REG_COMMAND_ID, commandIdBase | TYPE_SET); 45 | waitIdle(); 46 | } 47 | 48 | uint16_t Lepton::doRunCommand(uint16_t commandIdBase, uint16_t* data, uint16_t dataLen) { 49 | writeData(data, dataLen); 50 | writeRegister(REG_COMMAND_ID, commandIdBase | TYPE_RUN); 51 | waitIdle(); 52 | return readData(data); 53 | } 54 | 55 | void Lepton::syncFrame() { 56 | SPI.beginTransaction(SPISettings(15000000, MSBFIRST, SPI_MODE3)); 57 | digitalWrite(_ssPin, LOW); 58 | delay(0.02); 59 | 60 | static int count; 61 | if(count < 1) 62 | delay(1000); 63 | count++; 64 | if(count>=5) 65 | count = 5; 66 | } 67 | 68 | int Lepton::readFrame(uint16_t* data) { 69 | uint16_t row = 0; 70 | uint16_t id = waitNextFrame(); 71 | while ((id & 0xfff) == row) { 72 | uint16_t crc = readFrameWord(); 73 | for (int col = 0; col < 80; col++) { 74 | data[row * 80 + col] = readFrameWord(); 75 | } 76 | row++; 77 | if (row < 45) { 78 | id = readFrameWord(); 79 | } else { 80 | return 1; 81 | } 82 | } 83 | Serial.printf("readFrame ended with row %4x != id %4x\n", row, id, (id >> 12) & 0x7, id & 0xfff); 84 | return 0; 85 | } 86 | 87 | void Lepton::end() { 88 | digitalWrite(_ssPin, HIGH); 89 | SPI.endTransaction(); 90 | } 91 | 92 | void Lepton::readFrameRaw(uint16_t* data) { 93 | data[0] = waitNextFrame(); 94 | for (int i = 1; i < 82 * 60; i++) { 95 | data[i] = readFrameWord(); 96 | } 97 | } 98 | 99 | void Lepton::startTransmission(uint16_t reg) { 100 | Wire.beginTransmission(DEVICE_ID); 101 | transmitWord(reg); 102 | } 103 | 104 | void Lepton::transmitWord(uint16_t value) { 105 | Wire.write(value >> 8 & 0xff); 106 | Wire.write(value & 0xff); 107 | } 108 | 109 | void Lepton::endTransmission() { 110 | uint8_t error = Wire.endTransmission(); 111 | if (error != 0) { 112 | Serial.print("error="); 113 | Serial.println(error); 114 | } 115 | } 116 | 117 | uint16_t Lepton::readWord() { 118 | uint16_t value = Wire.read() << 8; 119 | value |= Wire.read(); 120 | return value; 121 | } 122 | 123 | void Lepton::setRegister(uint16_t reg) { 124 | startTransmission(reg); 125 | endTransmission(); 126 | } 127 | 128 | void Lepton::waitIdle() { 129 | while (readRegister(REG_STATUS) & STATUS_BIT_BUSY) { 130 | } 131 | } 132 | 133 | uint16_t Lepton::readData(uint16_t* data) { 134 | uint16_t dataLen = readRegister(REG_DATA_LEN) / 2; // The data sheet says the data length register is in 16-bit words, but it actually seems to be in bytes 135 | setRegister(REG_DATA_BASE); 136 | Wire.requestFrom(DEVICE_ID, (uint8_t)(dataLen * 2)); 137 | for (int i = 0; i < dataLen; i++) { 138 | data[i] = readWord(); 139 | } 140 | // TODO Check CRC 141 | return dataLen; 142 | } 143 | 144 | void Lepton::writeData(uint16_t* data, uint16_t dataLen) { 145 | startTransmission(REG_DATA_LEN); 146 | transmitWord(dataLen); 147 | for (int i = 0; i < dataLen; i++) { 148 | transmitWord(data[i]); 149 | } 150 | endTransmission(); 151 | } 152 | 153 | uint16_t Lepton::readFrameWord() { 154 | // digitalWrite(_ssPin, LOW); 155 | uint16_t data = SPI.transfer(0x00) << 8; 156 | data |= SPI.transfer(0x00); 157 | // digitalWrite(_ssPin, HIGH); 158 | return data; 159 | } 160 | 161 | uint16_t Lepton::waitNextFrame() { 162 | uint16_t id = readFrameWord(); 163 | while ((id & 0x0f00) == 0x0f00) { 164 | for (int i = 0; i < (_height + 1); i++) { 165 | readFrameWord(); 166 | } 167 | id = readFrameWord(); 168 | // Serial.printf("wait %4x %4x %4x \n", id, id & 0xf000, id & 0xfff); 169 | } 170 | return id; 171 | } 172 | 173 | void Lepton::dumpHex(uint16_t *data, int dataLen) { 174 | for (int i = 0; i < dataLen; i++) { 175 | Serial.printf("%4x ", data[i]); 176 | } 177 | Serial.println(); 178 | } 179 | 180 | -------------------------------------------------------------------------------- /Lepton.h: -------------------------------------------------------------------------------- 1 | /* 2 | Lepton.h - Library for accessing the Lepton thermal imaging camera (http://lepton.flir.com/) 3 | Created by Matt Williams, 17-19 June 2016 4 | BSD license - check license.txt for more information 5 | */ 6 | 7 | #ifndef Lepton_h 8 | #define Lepton_h 9 | 10 | #include "Arduino.h" 11 | 12 | class Lepton { 13 | public: 14 | // Registers for use with readRegister and writeRegister. 15 | static const uint16_t REG_POWER = 0; 16 | static const uint16_t REG_STATUS = 2; 17 | static const uint16_t REG_COMMAND_ID = 4; 18 | static const uint16_t REG_DATA_LEN = 6; 19 | static const uint16_t REG_DATA_BASE = 8; 20 | 21 | // Automatic Gain Control commands 22 | static const uint16_t CMD_AGC_ENABLE = 0x0100; 23 | static const uint16_t CMD_AGC_ROI = 0x0108; 24 | static const uint16_t CMD_AGC_HISTOGRAM_STATISTICS = 0x010c; 25 | static const uint16_t CMD_AGC_HEQ_DAMPING_FACTOR = 0x0124; 26 | static const uint16_t CMD_AGC_HEQ_CLIP_LIMIT_HIGH = 0x012c; 27 | static const uint16_t CMD_AGC_HEQ_CLIP_LIMIT_LOW = 0x0130; 28 | static const uint16_t CMD_AGC_HEQ_EMPTY_COUNT = 0x013c; 29 | static const uint16_t CMD_AGC_HEQ_SCALE_FACTOR = 0x0144; 30 | static const uint16_t CMD_AGC_HEQ_CALC_ENABLE_STATE = 0x0148; 31 | 32 | // System commands 33 | static const uint16_t CMD_SYS_PING = 0x0200; 34 | static const uint16_t CMD_SYS_STATUS = 0x0204; 35 | static const uint16_t CMD_SYS_FLIR_SERIAL_NUMBER = 0x0208; 36 | static const uint16_t CMD_SYS_CAMERA_UPTIME = 0x020C; 37 | static const uint16_t CMD_SYS_AUX_TEMPERATURE_KELVIN = 0x0210; 38 | static const uint16_t CMD_SYS_FPA_TEMPERATURE_KELVIN = 0x0214; 39 | static const uint16_t CMD_SYS_TELEMETRY_ENABLE = 0x0218; 40 | static const uint16_t CMD_SYS_TELEMETRY_LOCATION = 0x021c; 41 | static const uint16_t CMD_SYS_FRAMES_TO_AVERAGE = 0x0224; 42 | static const uint16_t CMD_SYS_CUST_SERIAL_NUMBER = 0x0228; 43 | static const uint16_t CMD_SYS_SCENE_STATISTICS = 0x022c; 44 | static const uint16_t CMD_SYS_SCENE_ROI = 0x0230; 45 | static const uint16_t CMD_SYS_THERMAL_SHUTDOWN_COUNT = 0x0234; 46 | static const uint16_t CMD_SYS_SHUTTER_POSITION = 0x0238; 47 | static const uint16_t CMD_SYS_FFC_SHUTTER_MODE = 0x023c; 48 | static const uint16_t CMD_SYS_FFC_NORMALIZATION = 0x0240; 49 | static const uint16_t CMD_SYS_FFC_STATUS = 0x0244; 50 | 51 | // Video commands 52 | static const uint16_t CMD_VID_USER_LUT = 0x0308; 53 | static const uint16_t CMD_VID_FOCUS_CALC_ENABLE = 0x030c; 54 | static const uint16_t CMD_VID_FOCUS_ROI = 0x0310; 55 | static const uint16_t CMD_VID_FOCUS_METRIC_THRESHOLD = 0x0314; 56 | static const uint16_t CMD_VID_FOCUS_METRIC = 0x0318; 57 | static const uint16_t CMD_VID_FREEZE_ENABLE = 0x0324; 58 | 59 | static const uint16_t STATUS_BIT_BUSY = 1; 60 | static const uint16_t STATUS_BIT_BOOT_MODE = 2; 61 | static const uint16_t STATUS_BIT_BOOT_STATUS = 4; 62 | 63 | // Create a new Lepton instance 64 | Lepton(int sdaPin, int sclPin, int ssPin, int width, int height); 65 | 66 | // Start Lepton access 67 | void begin(); 68 | 69 | // Read a (16-bit) register 70 | uint16_t readRegister(uint16_t reg); 71 | 72 | // Write a (16-bit) register 73 | void writeRegister(uint16_t reg, uint16_t value); 74 | 75 | // Do a get command, and get the resulting data 76 | uint16_t doGetCommand(uint16_t commandIdBase, uint16_t* data); 77 | 78 | // Do a set command, using the provided data 79 | void doSetCommand(uint16_t commandIdBase, uint16_t* data, uint16_t dataLen); 80 | 81 | // Do a run command, using the provided data and returning the result in the same buffer 82 | uint16_t doRunCommand(uint16_t commandIdBase, uint16_t* data, uint16_t dataLen); 83 | 84 | // (Re-)synchronize the frame stream 85 | void syncFrame(); 86 | 87 | void end(); 88 | 89 | // Read a frame into a 80 * 60 uint16_t buffer 90 | int readFrame(uint16_t* data); 91 | 92 | // Read completely raw frame data - for debugging purposes 93 | void readFrameRaw(uint16_t* data); 94 | 95 | private: 96 | // Device ID on the I2C interface 97 | static const uint8_t DEVICE_ID = 0x2A; 98 | 99 | // Command types 100 | static const uint16_t TYPE_GET = 0; 101 | static const uint16_t TYPE_SET = 1; 102 | static const uint16_t TYPE_RUN = 2; 103 | 104 | // Start I2C transmission relating to a specific register 105 | void startTransmission(uint16_t reg); 106 | 107 | // Transmit a word over I2C 108 | void transmitWord(uint16_t value); 109 | 110 | // End I2C transmission 111 | void endTransmission(); 112 | 113 | // Read a word over I2C 114 | uint16_t readWord(); 115 | 116 | // Set the current register over I2C 117 | void setRegister(uint16_t reg); 118 | 119 | // Wait for the camera to become idle, e.g. after performing a command 120 | void waitIdle(); 121 | 122 | // Read data from a buffer 123 | uint16_t readData(uint16_t* data); 124 | 125 | // Write data to a buffer 126 | void writeData(uint16_t* data, uint16_t dataLen); 127 | 128 | // Read a word of frame data over SPI 129 | uint16_t readFrameWord(); 130 | 131 | // Wait for the next frame over SPI 132 | uint16_t waitNextFrame(); 133 | 134 | // Dump a buffer as hex to serial - for debugging purposes 135 | void dumpHex(uint16_t *data, int dataLen); 136 | 137 | // SDA, SCL and SPI CS pins 138 | int _sdaPin; 139 | int _sclPin; 140 | int _ssPin; 141 | 142 | int _width; 143 | int _height; 144 | }; 145 | 146 | #endif 147 | -------------------------------------------------------------------------------- /main.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* Lepton Pinmap 7 | * SDA -> 21 8 | * SCL -> 22 9 | * CS -> 5 10 | * CLK(SCK) -> 18 11 | * MISO -> 19 12 | * MOSI -> 23 13 | */ 14 | Lepton lepton(21, 22, 5); 15 | const int leptonWidth = 60; 16 | const int leptonHeight = 80; 17 | uint16_t frameData[leptonWidth * leptonHeight]; 18 | 19 | /* LCD Pinmap (HSPI) 20 | * LCD : 320*240 21 | * CS -> 15 22 | * RESET -> 4 23 | * DC -> 2 24 | * SDI(MOSI) -> 13 25 | * CLK(SCK) -> 14 26 | * LED -> VCC 27 | * SDOK(MISO) -> 12 28 | * T_CLK -> SCK 29 | * T_CS -> 34 30 | * T_DIN -> MOSI 31 | * T_DO -> MISO 32 | * // also need to allow USE_HSPI_PORT option in User_Setup.h 33 | */ 34 | TFT_eSPI tft = TFT_eSPI(); 35 | TFT_eSprite videoSprite = TFT_eSprite(&tft); 36 | const int videoScale = 4; 37 | byte renderData[leptonWidth * videoScale][leptonHeight * videoScale]; 38 | 39 | // color map from : https://github.com/groupgets/LeptonModule/blob/master/software/raspberrypi_video/Palettes.cpp 40 | const byte colormap_rainbow[] = {1, 3, 74, 0, 3, 74, 0, 3, 75, 0, 3, 75, 0, 3, 76, 0, 3, 76, 0, 3, 77, 0, 3, 79, 0, 3, 82, 0, 5, 85, 0, 7, 88, 0, 10, 91, 0, 14, 94, 0, 19, 98, 0, 22, 100, 0, 25, 103, 0, 28, 106, 0, 32, 109, 0, 35, 112, 0, 38, 116, 0, 40, 119, 0, 42, 123, 0, 45, 128, 0, 49, 133, 0, 50, 134, 0, 51, 136, 0, 52, 137, 0, 53, 139, 0, 54, 142, 0, 55, 144, 0, 56, 145, 0, 58, 149, 0, 61, 154, 0, 63, 156, 0, 65, 159, 0, 66, 161, 0, 68, 164, 0, 69, 167, 0, 71, 170, 0, 73, 174, 0, 75, 179, 0, 76, 181, 0, 78, 184, 0, 79, 187, 0, 80, 188, 0, 81, 190, 0, 84, 194, 0, 87, 198, 0, 88, 200, 0, 90, 203, 0, 92, 205, 0, 94, 207, 0, 94, 208, 0, 95, 209, 0, 96, 210, 0, 97, 211, 0, 99, 214, 0, 102, 217, 0, 103, 218, 0, 104, 219, 0, 105, 220, 0, 107, 221, 0, 109, 223, 0, 111, 223, 0, 113, 223, 0, 115, 222, 0, 117, 221, 0, 118, 220, 1, 120, 219, 1, 122, 217, 2, 124, 216, 2, 126, 214, 3, 129, 212, 3, 131, 207, 4, 132, 205, 4, 133, 202, 4, 134, 197, 5, 136, 192, 6, 138, 185, 7, 141, 178, 8, 142, 172, 10, 144, 166, 10, 144, 162, 11, 145, 158, 12, 146, 153, 13, 147, 149, 15, 149, 140, 17, 151, 132, 22, 153, 120, 25, 154, 115, 28, 156, 109, 34, 158, 101, 40, 160, 94, 45, 162, 86, 51, 164, 79, 59, 167, 69, 67, 171, 60, 72, 173, 54, 78, 175, 48, 83, 177, 43, 89, 179, 39, 93, 181, 35, 98, 183, 31, 105, 185, 26, 109, 187, 23, 113, 188, 21, 118, 189, 19, 123, 191, 17, 128, 193, 14, 134, 195, 12, 138, 196, 10, 142, 197, 8, 146, 198, 6, 151, 200, 5, 155, 201, 4, 160, 203, 3, 164, 204, 2, 169, 205, 2, 173, 206, 1, 175, 207, 1, 178, 207, 1, 184, 208, 0, 190, 210, 0, 193, 211, 0, 196, 212, 0, 199, 212, 0, 202, 213, 1, 207, 214, 2, 212, 215, 3, 215, 214, 3, 218, 214, 3, 220, 213, 3, 222, 213, 4, 224, 212, 4, 225, 212, 5, 226, 212, 5, 229, 211, 5, 232, 211, 6, 232, 211, 6, 233, 211, 6, 234, 210, 6, 235, 210, 7, 236, 209, 7, 237, 208, 8, 239, 206, 8, 241, 204, 9, 242, 203, 9, 244, 202, 10, 244, 201, 10, 245, 200, 10, 245, 199, 11, 246, 198, 11, 247, 197, 12, 248, 194, 13, 249, 191, 14, 250, 189, 14, 251, 187, 15, 251, 185, 16, 252, 183, 17, 252, 178, 18, 253, 174, 19, 253, 171, 19, 254, 168, 20, 254, 165, 21, 254, 164, 21, 255, 163, 22, 255, 161, 22, 255, 159, 23, 255, 157, 23, 255, 155, 24, 255, 149, 25, 255, 143, 27, 255, 139, 28, 255, 135, 30, 255, 131, 31, 255, 127, 32, 255, 118, 34, 255, 110, 36, 255, 104, 37, 255, 101, 38, 255, 99, 39, 255, 93, 40, 255, 88, 42, 254, 82, 43, 254, 77, 45, 254, 69, 47, 254, 62, 49, 253, 57, 50, 253, 53, 52, 252, 49, 53, 252, 45, 55, 251, 39, 57, 251, 33, 59, 251, 32, 60, 251, 31, 60, 251, 30, 61, 251, 29, 61, 251, 28, 62, 250, 27, 63, 250, 27, 65, 249, 26, 66, 249, 26, 68, 248, 25, 70, 248, 24, 73, 247, 24, 75, 247, 25, 77, 247, 25, 79, 247, 26, 81, 247, 32, 83, 247, 35, 85, 247, 38, 86, 247, 42, 88, 247, 46, 90, 247, 50, 92, 248, 55, 94, 248, 59, 96, 248, 64, 98, 248, 72, 101, 249, 81, 104, 249, 87, 106, 250, 93, 108, 250, 95, 109, 250, 98, 110, 250, 100, 111, 251, 101, 112, 251, 102, 113, 251, 109, 117, 252, 116, 121, 252, 121, 123, 253, 126, 126, 253, 130, 128, 254, 135, 131, 254, 139, 133, 254, 144, 136, 254, 151, 140, 255, 158, 144, 255, 163, 146, 255, 168, 149, 255, 173, 152, 255, 176, 153, 255, 178, 155, 255, 184, 160, 255, 191, 165, 255, 195, 168, 255, 199, 172, 255, 203, 175, 255, 207, 179, 255, 211, 182, 255, 216, 185, 255, 218, 190, 255, 220, 196, 255, 222, 200, 255, 225, 202, 255, 227, 204, 255, 230, 206, 255, 233, 208, 41 | 0}; 42 | const byte colormap_grayscale[] = {0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44, 44, 44, 45, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, 207, 207, 207, 208, 208, 208, 209, 209, 209, 210, 210, 210, 211, 211, 211, 212, 212, 212, 213, 213, 213, 214, 214, 214, 215, 215, 215, 216, 216, 216, 217, 217, 217, 218, 218, 218, 219, 219, 219, 220, 220, 220, 221, 221, 221, 222, 222, 222, 223, 223, 223, 224, 224, 224, 225, 225, 225, 226, 226, 226, 227, 227, 227, 228, 228, 228, 229, 229, 229, 230, 230, 230, 231, 231, 231, 232, 232, 232, 233, 233, 233, 234, 234, 234, 235, 235, 235, 236, 236, 236, 237, 237, 237, 238, 238, 238, 239, 239, 239, 240, 240, 240, 241, 241, 241, 242, 242, 242, 243, 243, 243, 244, 244, 244, 245, 245, 245, 246, 246, 246, 247, 247, 247, 248, 248, 248, 249, 249, 249, 250, 250, 250, 251, 251, 251, 252, 252, 252, 253, 253, 253, 254, 254, 254, 255, 255, 255, 43 | 0}; 44 | const byte colormap_ironblack[] = {255, 255, 255, 253, 253, 253, 251, 251, 251, 249, 249, 249, 247, 247, 247, 245, 245, 245, 243, 243, 243, 241, 241, 241, 239, 239, 239, 237, 237, 237, 235, 235, 235, 233, 233, 233, 231, 231, 231, 229, 229, 229, 227, 227, 227, 225, 225, 225, 223, 223, 223, 221, 221, 221, 219, 219, 219, 217, 217, 217, 215, 215, 215, 213, 213, 213, 211, 211, 211, 209, 209, 209, 207, 207, 207, 205, 205, 205, 203, 203, 203, 201, 201, 201, 199, 199, 199, 197, 197, 197, 195, 195, 195, 193, 193, 193, 191, 191, 191, 189, 189, 189, 187, 187, 187, 185, 185, 185, 183, 183, 183, 181, 181, 181, 179, 179, 179, 177, 177, 177, 175, 175, 175, 173, 173, 173, 171, 171, 171, 169, 169, 169, 167, 167, 167, 165, 165, 165, 163, 163, 163, 161, 161, 161, 159, 159, 159, 157, 157, 157, 155, 155, 155, 153, 153, 153, 151, 151, 151, 149, 149, 149, 147, 147, 147, 145, 145, 145, 143, 143, 143, 141, 141, 141, 139, 139, 139, 137, 137, 137, 135, 135, 135, 133, 133, 133, 131, 131, 131, 129, 129, 129, 126, 126, 126, 124, 124, 124, 122, 122, 122, 120, 120, 120, 118, 118, 118, 116, 116, 116, 114, 114, 114, 112, 112, 112, 110, 110, 110, 108, 108, 108, 106, 106, 106, 104, 104, 104, 102, 102, 102, 100, 100, 100, 98, 98, 98, 96, 96, 96, 94, 94, 94, 92, 92, 92, 90, 90, 90, 88, 88, 88, 86, 86, 86, 84, 84, 84, 82, 82, 82, 80, 80, 80, 78, 78, 78, 76, 76, 76, 74, 74, 74, 72, 72, 72, 70, 70, 70, 68, 68, 68, 66, 66, 66, 64, 64, 64, 62, 62, 62, 60, 60, 60, 58, 58, 58, 56, 56, 56, 54, 54, 54, 52, 52, 52, 50, 50, 50, 48, 48, 48, 46, 46, 46, 44, 44, 44, 42, 42, 42, 40, 40, 40, 38, 38, 38, 36, 36, 36, 34, 34, 34, 32, 32, 32, 30, 30, 30, 28, 28, 28, 26, 26, 26, 24, 24, 24, 22, 22, 22, 20, 20, 20, 18, 18, 18, 16, 16, 16, 14, 14, 14, 12, 12, 12, 10, 10, 10, 8, 8, 8, 6, 6, 6, 4, 4, 4, 2, 2, 2, 0, 0, 0, 0, 0, 9, 2, 0, 16, 4, 0, 24, 6, 0, 31, 8, 0, 38, 10, 0, 45, 12, 0, 53, 14, 0, 60, 17, 0, 67, 19, 0, 74, 21, 0, 82, 23, 0, 89, 25, 0, 96, 27, 0, 103, 29, 0, 111, 31, 0, 118, 36, 0, 120, 41, 0, 121, 46, 0, 122, 51, 0, 123, 56, 0, 124, 61, 0, 125, 66, 0, 126, 71, 0, 127, 76, 1, 128, 81, 1, 129, 86, 1, 130, 91, 1, 131, 96, 1, 132, 101, 1, 133, 106, 1, 134, 111, 1, 135, 116, 1, 136, 121, 1, 136, 125, 2, 137, 130, 2, 137, 135, 3, 137, 139, 3, 138, 144, 3, 138, 149, 4, 138, 153, 4, 139, 158, 5, 139, 163, 5, 139, 167, 5, 140, 172, 6, 140, 177, 6, 140, 181, 7, 141, 186, 7, 141, 189, 10, 137, 191, 13, 132, 194, 16, 127, 196, 19, 121, 198, 22, 116, 200, 25, 111, 203, 28, 106, 205, 31, 101, 207, 34, 95, 209, 37, 90, 212, 40, 85, 214, 43, 80, 216, 46, 75, 218, 49, 69, 221, 52, 64, 223, 55, 59, 224, 57, 49, 225, 60, 47, 226, 64, 44, 227, 67, 42, 228, 71, 39, 229, 74, 37, 230, 78, 34, 231, 81, 32, 231, 85, 29, 232, 88, 27, 233, 92, 24, 234, 95, 22, 235, 99, 19, 236, 102, 17, 237, 106, 14, 238, 109, 12, 239, 112, 12, 240, 116, 12, 240, 119, 12, 241, 123, 12, 241, 127, 12, 242, 130, 12, 242, 134, 12, 243, 138, 12, 243, 141, 13, 244, 145, 13, 244, 149, 13, 245, 152, 13, 245, 156, 13, 246, 160, 13, 246, 163, 13, 247, 167, 13, 247, 171, 13, 248, 175, 14, 248, 178, 15, 249, 182, 16, 249, 185, 18, 250, 189, 19, 250, 192, 20, 251, 196, 21, 251, 199, 22, 252, 203, 23, 252, 206, 24, 253, 210, 25, 253, 213, 27, 254, 217, 28, 254, 220, 29, 255, 224, 30, 255, 227, 39, 255, 229, 53, 255, 231, 67, 255, 233, 81, 255, 234, 95, 255, 236, 109, 255, 238, 123, 255, 240, 137, 255, 242, 151, 255, 244, 165, 255, 246, 179, 255, 248, 193, 255, 249, 207, 255, 251, 221, 255, 253, 235, 255, 255, 24, 45 | 0}; 46 | int colorMode = 0; 47 | 48 | unsigned long startTime; 49 | unsigned long lastMillis; 50 | unsigned long frameCount; 51 | unsigned int framesPerSecond; 52 | unsigned long lastRenderTime = 0; 53 | 54 | const boolean isDebug = true; 55 | 56 | const int UI_VIDEO_VIEW = 11000; 57 | const int UI_SETTING_VIEW = 12000; 58 | 59 | int uiState = UI_VIDEO_VIEW; 60 | boolean needToInvalidate = true; 61 | 62 | unsigned long lastTouchCheckTime = 0; 63 | uint16_t touchX = -1, touchY = -1; 64 | 65 | const int SCALE_MODE_NNI = 1001; // Nearest Neighbor Interpolation 66 | const int SCALE_MODE_BLI = 1002; // Biliniear Interpolation 67 | int scaleMode = SCALE_MODE_BLI; 68 | 69 | const int AUTO_TEMP_MODE_MINMAX = 1; 70 | const int AUTO_TEMP_MODE_FLAT_MINMAX = 2; 71 | int autoTempMode = AUTO_TEMP_MODE_MINMAX; 72 | 73 | int autoTempFlatMin = 65535; 74 | int autoTempFlatMax = 0; 75 | 76 | void setup() { 77 | Serial.begin(115200); 78 | 79 | setupLepton(); 80 | setupTft(); 81 | } 82 | 83 | void setupLepton() { 84 | lepton.begin(); 85 | 86 | uint16_t serialNumber[4]; 87 | lepton.doGetCommand(Lepton::CMD_SYS_FLIR_SERIAL_NUMBER, serialNumber); 88 | if (isDebug) { 89 | Serial.printf("FLIR Serial Number: %4x %4x %4x %4x\n", serialNumber[0], serialNumber[1], serialNumber[2], serialNumber[3]); 90 | } 91 | 92 | lepton.syncFrame(); 93 | if (isDebug) { 94 | Serial.println("sync done"); 95 | } 96 | } 97 | 98 | void setupTft() { 99 | tft.init(); 100 | 101 | tft.setRotation(1); 102 | 103 | videoSprite.setColorDepth(8); 104 | videoSprite.createSprite( 105 | leptonHeight * videoScale, 106 | leptonWidth * videoScale 107 | ); 108 | videoSprite.fillSprite(TFT_BLUE); 109 | } 110 | 111 | void loop() { 112 | startTime = millis(); 113 | 114 | if (uiState == UI_VIDEO_VIEW && !lepton.readFrame(frameData)) { 115 | Serial.println("skip"); 116 | return; 117 | } else { 118 | invalidateVideo(); 119 | } 120 | 121 | static uint16_t color; 122 | 123 | // in video view, check touch once per second. it costs lot. 124 | if (uiState != UI_VIDEO_VIEW || millis() - lastTouchCheckTime >= 3000) { 125 | if (!tft.getTouch(&touchX, &touchY)) { 126 | touchX = -1; 127 | touchY = -1; 128 | } 129 | lastTouchCheckTime = millis(); 130 | } 131 | render(); 132 | 133 | handleTouch(); 134 | 135 | measureFrame(); 136 | } 137 | 138 | void invalidateVideo() { 139 | if (uiState != UI_VIDEO_VIEW) return; 140 | 141 | uint16_t avg = 0; 142 | uint16_t minValue = 65535; 143 | uint16_t maxValue = 0; 144 | 145 | for (int i = 0; i < leptonWidth; i++){ 146 | for(int j = 0; j < leptonHeight; j++){ 147 | uint16_t pixelData = frameData[i * leptonHeight + j]; 148 | avg += (double(pixelData) / (leptonWidth * leptonHeight)); 149 | 150 | if (pixelData !=0) { 151 | if (minValue > pixelData) { 152 | minValue = pixelData; 153 | } 154 | if (maxValue < pixelData) { 155 | maxValue = pixelData; 156 | } 157 | } 158 | } 159 | } 160 | 161 | if (autoTempMode == AUTO_TEMP_MODE_FLAT_MINMAX) { 162 | if (minValue > autoTempFlatMin) minValue = autoTempFlatMin; 163 | if (maxValue < autoTempFlatMax) maxValue = autoTempFlatMax; 164 | 165 | autoTempFlatMin = minValue; 166 | autoTempFlatMax = maxValue; 167 | } 168 | 169 | double scale = 0; 170 | if (maxValue != 0 && minValue != 0) { 171 | scale = 255.0 / (maxValue - minValue); 172 | } else { 173 | scale = 1.0; 174 | } 175 | 176 | if (isDebug) { 177 | Serial.print("avg : "); 178 | Serial.println(avg); 179 | Serial.print("maxValue : "); 180 | Serial.println(maxValue); 181 | Serial.print("minValue : "); 182 | Serial.println(minValue); 183 | Serial.print("scale : "); 184 | Serial.println(scale); 185 | } 186 | 187 | unsigned long sum2 = 0; 188 | 189 | for (int i = 0; i < leptonWidth; i++) { 190 | for (int j = 0; j < leptonHeight; j++) { 191 | uint16_t pixelData = ((frameData[i * leptonHeight + j] ) - minValue) * scale; 192 | 193 | if (pixelData < 0) { 194 | pixelData = 0; 195 | } else if (pixelData > 255) { 196 | pixelData = 255; 197 | } 198 | sum2 += pixelData; 199 | 200 | for(int k = 0; k < videoScale; k++) { 201 | for(int l = 0; l < videoScale; l++) { 202 | renderData[i * videoScale + k][j * videoScale + l] = pixelData; 203 | } 204 | } 205 | } 206 | } 207 | 208 | if (scaleMode == SCALE_MODE_BLI) { 209 | int frameLength = videoScale * 2; 210 | for (int i = 0; i < leptonWidth * videoScale; i+=(frameLength - 1)) { 211 | for (int j = 0; j < leptonHeight * videoScale; j+=(frameLength - 1)) { 212 | 213 | uint16_t originValues[4] = { 214 | renderData[i][j], 215 | renderData[i][j+(frameLength - 1)], 216 | renderData[i+(frameLength - 1)][j+(frameLength - 1)], 217 | renderData[i+(frameLength - 1)][j+(frameLength - 1)], 218 | }; 219 | for (int k = 0; k < frameLength; k++) { 220 | for (int l = 0; l < frameLength; l++) { 221 | if (i * videoScale + k >= leptonWidth * videoScale || j * videoScale + l >= leptonHeight * videoScale) continue; 222 | 223 | renderData[i + k][j + l] = ( 224 | originValues[0] * ((frameLength - k) * (frameLength - l)) + 225 | originValues[1] * (k * (frameLength - l)) + 226 | originValues[2] * ((frameLength - k) * l) + 227 | originValues[3] * (k * l) 228 | ) / ((frameLength) * (frameLength)); 229 | } 230 | } 231 | 232 | } 233 | } 234 | } 235 | 236 | if (isDebug) { 237 | Serial.print("=="); 238 | Serial.print("avg2 : " ); 239 | Serial.println(sum2 / (leptonWidth * leptonHeight)); 240 | } 241 | needToInvalidate = true; 242 | } 243 | 244 | void render() { 245 | if (needToInvalidate) { 246 | if (uiState == UI_VIDEO_VIEW) { 247 | renderThermalVideo(); 248 | } else { 249 | renderSettingUi(); 250 | } 251 | } 252 | 253 | renderSystemUi(); 254 | 255 | needToInvalidate = false; 256 | } 257 | 258 | void renderThermalVideo() { 259 | const byte* colorMap; 260 | if (colorMode == 0) { 261 | colorMap = colormap_rainbow; 262 | } else if (colorMode == 1) { 263 | colorMap = colormap_grayscale; 264 | } else if (colorMode == 2) { 265 | colorMap = colormap_ironblack; 266 | } 267 | 268 | for (int i = 0; i < leptonWidth * videoScale; i++) { 269 | for (int j = 0; j < leptonHeight * videoScale; j++) { 270 | uint16_t color = tft.color565( 271 | colorMap[renderData[i][j] * 3 + 0], 272 | colorMap[renderData[i][j] * 3 + 1], 273 | colorMap[renderData[i][j] * 3 + 2] 274 | ); 275 | 276 | videoSprite.drawPixel( 277 | j, 278 | i, 279 | color 280 | ); 281 | } 282 | } 283 | videoSprite.pushSprite(0, 0); 284 | } 285 | 286 | void renderSettingUi() { 287 | tft.fillScreen(TFT_BLUE); 288 | 289 | tft.setTextSize(3); 290 | tft.drawString("settings", 0, 0); 291 | 292 | tft.setTextSize(2); 293 | tft.drawString("rainbow (" + String((colorMode == 0) ? "o" : " ") + ")", 0, 30); 294 | tft.drawString("grayscale (" + String((colorMode == 1) ? "o" : " ") + ")", 0, 50); 295 | tft.drawString("ironblack (" + String((colorMode == 2) ? "o" : " ") + ")", 0, 70); 296 | 297 | tft.drawString("auto temp (" + String((autoTempMode == AUTO_TEMP_MODE_MINMAX) ? "o" : " ") + ")", 0, 100); 298 | tft.drawString("flat temp (" + String((autoTempMode == AUTO_TEMP_MODE_FLAT_MINMAX) ? "o" : " ") + ")", 0, 120); 299 | 300 | tft.drawString("scale_nni (" + String((scaleMode == SCALE_MODE_NNI) ? "o" : " ") + ")", 0, 150); 301 | tft.drawString("scale_bli (" + String((scaleMode == SCALE_MODE_BLI) ? "o" : " ") + ")", 0, 170); 302 | 303 | tft.setTextSize(3); 304 | tft.drawString("back", 250, 210); 305 | } 306 | 307 | void renderSystemUi() { 308 | if (isDebug) { 309 | Serial.print("fps : "); 310 | Serial.println(framesPerSecond); 311 | } 312 | 313 | tft.setTextSize(1); 314 | tft.drawString("fps : " + String(framesPerSecond) + " frame : " + String(lastRenderTime) + "ms", 200, 0); 315 | } 316 | 317 | void handleTouch() { 318 | if (touchX == -1 || touchX == 65535 || touchY == -1 || touchY == 65535) return; 319 | 320 | if (uiState == UI_VIDEO_VIEW) { 321 | uiState = UI_SETTING_VIEW; 322 | } else { 323 | handleTouchInSettingUi(); 324 | } 325 | 326 | // after change ui, prevent touch in 1s. 327 | lastTouchCheckTime = millis() + 1000; 328 | 329 | needToInvalidate = true; 330 | } 331 | 332 | void handleTouchInSettingUi() { 333 | // back button 334 | if (touchIn(250, 200, 320, 240)) { 335 | uiState = UI_VIDEO_VIEW; 336 | } 337 | // color - rainbow 338 | else if (touchIn(0, 30, 120, 50)) { 339 | colorMode = 0; 340 | } 341 | // color - grayscale 342 | else if (touchIn(0, 50, 120, 70)) { 343 | colorMode = 1; 344 | } 345 | // color - ironblack 346 | else if (touchIn(0, 70, 120, 90)) { 347 | colorMode = 2; 348 | } 349 | // tempMode - minmax 350 | else if (touchIn(0, 100, 120, 120)) { 351 | autoTempMode = AUTO_TEMP_MODE_MINMAX; 352 | } 353 | // tempMode = flat 354 | else if (touchIn(0, 120, 120, 140)) { 355 | autoTempMode = AUTO_TEMP_MODE_FLAT_MINMAX; 356 | 357 | autoTempFlatMin = 65535; 358 | autoTempFlatMax = 0; 359 | } 360 | // scaleMode = nni 361 | else if (touchIn(0, 150, 120, 170)) { 362 | scaleMode = SCALE_MODE_NNI; 363 | } 364 | // scaleMode = bli 365 | else if (touchIn(0, 170, 120, 190)) { 366 | scaleMode = SCALE_MODE_BLI; 367 | } 368 | } 369 | 370 | boolean touchIn(int startX, int startY, int endX, int endY) { 371 | if (touchX >= startX && touchX <= endX && touchY >= startY && touchY <= endY) { 372 | return true; 373 | } else { 374 | return false; 375 | } 376 | } 377 | 378 | void measureFrame() { 379 | unsigned long now = millis(); 380 | 381 | frameCount ++; 382 | if (now - lastMillis >= 1000) { 383 | framesPerSecond = frameCount; 384 | frameCount = 0; 385 | lastMillis = now; 386 | } 387 | 388 | lastRenderTime = now - startTime; 389 | } 390 | -------------------------------------------------------------------------------- /3.5: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* Lepton Pinmap 7 | * SDA -> 21 8 | * SCL -> 22 s= 9 | * CS -> 5 10 | * CLK(SCK) -> 18 11 | * MISO -> 19 12 | * MOSI -> 23 13 | */ 14 | Lepton lepton(21, 22, 5, 60, 80); 15 | const int leptonWidth = 60; 16 | const int leptonHeight = 80; 17 | uint16_t frameData[leptonWidth * leptonHeight]; 18 | 19 | /* LCD Pinmap (HSPI) 20 | * LCD : 320*240 21 | * CS -> 15 22 | * RESET -> 4 23 | * DC -> 2 24 | * SDI(MOSI) -> 13 25 | * CLK(SCK) -> 14 26 | * LED -> VCC 27 | * SDOK(MISO) -> 12 28 | * T_CLK -> SCK 29 | * T_CS -> 34 30 | * T_DIN -> MOSI 31 | * T_DO -> MISO 32 | * // also need to allow USE_HSPI_PORT option in User_Setup.h 33 | */ 34 | TFT_eSPI tft = TFT_eSPI(); 35 | TFT_eSprite videoSprite = TFT_eSprite(&tft); 36 | const int videoScale = 1; 37 | byte renderData[leptonWidth * videoScale][leptonHeight * videoScale]; 38 | 39 | // color map from : https://github.com/groupgets/LeptonModule/blob/master/software/raspberrypi_video/Palettes.cpp 40 | const byte colormap_rainbow[] = {1, 3, 74, 0, 3, 74, 0, 3, 75, 0, 3, 75, 0, 3, 76, 0, 3, 76, 0, 3, 77, 0, 3, 79, 0, 3, 82, 0, 5, 85, 0, 7, 88, 0, 10, 91, 0, 14, 94, 0, 19, 98, 0, 22, 100, 0, 25, 103, 0, 28, 106, 0, 32, 109, 0, 35, 112, 0, 38, 116, 0, 40, 119, 0, 42, 123, 0, 45, 128, 0, 49, 133, 0, 50, 134, 0, 51, 136, 0, 52, 137, 0, 53, 139, 0, 54, 142, 0, 55, 144, 0, 56, 145, 0, 58, 149, 0, 61, 154, 0, 63, 156, 0, 65, 159, 0, 66, 161, 0, 68, 164, 0, 69, 167, 0, 71, 170, 0, 73, 174, 0, 75, 179, 0, 76, 181, 0, 78, 184, 0, 79, 187, 0, 80, 188, 0, 81, 190, 0, 84, 194, 0, 87, 198, 0, 88, 200, 0, 90, 203, 0, 92, 205, 0, 94, 207, 0, 94, 208, 0, 95, 209, 0, 96, 210, 0, 97, 211, 0, 99, 214, 0, 102, 217, 0, 103, 218, 0, 104, 219, 0, 105, 220, 0, 107, 221, 0, 109, 223, 0, 111, 223, 0, 113, 223, 0, 115, 222, 0, 117, 221, 0, 118, 220, 1, 120, 219, 1, 122, 217, 2, 124, 216, 2, 126, 214, 3, 129, 212, 3, 131, 207, 4, 132, 205, 4, 133, 202, 4, 134, 197, 5, 136, 192, 6, 138, 185, 7, 141, 178, 8, 142, 172, 10, 144, 166, 10, 144, 162, 11, 145, 158, 12, 146, 153, 13, 147, 149, 15, 149, 140, 17, 151, 132, 22, 153, 120, 25, 154, 115, 28, 156, 109, 34, 158, 101, 40, 160, 94, 45, 162, 86, 51, 164, 79, 59, 167, 69, 67, 171, 60, 72, 173, 54, 78, 175, 48, 83, 177, 43, 89, 179, 39, 93, 181, 35, 98, 183, 31, 105, 185, 26, 109, 187, 23, 113, 188, 21, 118, 189, 19, 123, 191, 17, 128, 193, 14, 134, 195, 12, 138, 196, 10, 142, 197, 8, 146, 198, 6, 151, 200, 5, 155, 201, 4, 160, 203, 3, 164, 204, 2, 169, 205, 2, 173, 206, 1, 175, 207, 1, 178, 207, 1, 184, 208, 0, 190, 210, 0, 193, 211, 0, 196, 212, 0, 199, 212, 0, 202, 213, 1, 207, 214, 2, 212, 215, 3, 215, 214, 3, 218, 214, 3, 220, 213, 3, 222, 213, 4, 224, 212, 4, 225, 212, 5, 226, 212, 5, 229, 211, 5, 232, 211, 6, 232, 211, 6, 233, 211, 6, 234, 210, 6, 235, 210, 7, 236, 209, 7, 237, 208, 8, 239, 206, 8, 241, 204, 9, 242, 203, 9, 244, 202, 10, 244, 201, 10, 245, 200, 10, 245, 199, 11, 246, 198, 11, 247, 197, 12, 248, 194, 13, 249, 191, 14, 250, 189, 14, 251, 187, 15, 251, 185, 16, 252, 183, 17, 252, 178, 18, 253, 174, 19, 253, 171, 19, 254, 168, 20, 254, 165, 21, 254, 164, 21, 255, 163, 22, 255, 161, 22, 255, 159, 23, 255, 157, 23, 255, 155, 24, 255, 149, 25, 255, 143, 27, 255, 139, 28, 255, 135, 30, 255, 131, 31, 255, 127, 32, 255, 118, 34, 255, 110, 36, 255, 104, 37, 255, 101, 38, 255, 99, 39, 255, 93, 40, 255, 88, 42, 254, 82, 43, 254, 77, 45, 254, 69, 47, 254, 62, 49, 253, 57, 50, 253, 53, 52, 252, 49, 53, 252, 45, 55, 251, 39, 57, 251, 33, 59, 251, 32, 60, 251, 31, 60, 251, 30, 61, 251, 29, 61, 251, 28, 62, 250, 27, 63, 250, 27, 65, 249, 26, 66, 249, 26, 68, 248, 25, 70, 248, 24, 73, 247, 24, 75, 247, 25, 77, 247, 25, 79, 247, 26, 81, 247, 32, 83, 247, 35, 85, 247, 38, 86, 247, 42, 88, 247, 46, 90, 247, 50, 92, 248, 55, 94, 248, 59, 96, 248, 64, 98, 248, 72, 101, 249, 81, 104, 249, 87, 106, 250, 93, 108, 250, 95, 109, 250, 98, 110, 250, 100, 111, 251, 101, 112, 251, 102, 113, 251, 109, 117, 252, 116, 121, 252, 121, 123, 253, 126, 126, 253, 130, 128, 254, 135, 131, 254, 139, 133, 254, 144, 136, 254, 151, 140, 255, 158, 144, 255, 163, 146, 255, 168, 149, 255, 173, 152, 255, 176, 153, 255, 178, 155, 255, 184, 160, 255, 191, 165, 255, 195, 168, 255, 199, 172, 255, 203, 175, 255, 207, 179, 255, 211, 182, 255, 216, 185, 255, 218, 190, 255, 220, 196, 255, 222, 200, 255, 225, 202, 255, 227, 204, 255, 230, 206, 255, 233, 208, 41 | 0}; 42 | const byte colormap_grayscale[] = {0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44, 44, 44, 45, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, 70, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 81, 81, 81, 82, 82, 82, 83, 83, 83, 84, 84, 84, 85, 85, 85, 86, 86, 86, 87, 87, 87, 88, 88, 88, 89, 89, 89, 90, 90, 90, 91, 91, 91, 92, 92, 92, 93, 93, 93, 94, 94, 94, 95, 95, 95, 96, 96, 96, 97, 97, 97, 98, 98, 98, 99, 99, 99, 100, 100, 100, 101, 101, 101, 102, 102, 102, 103, 103, 103, 104, 104, 104, 105, 105, 105, 106, 106, 106, 107, 107, 107, 108, 108, 108, 109, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 119, 119, 119, 120, 120, 120, 121, 121, 121, 122, 122, 122, 123, 123, 123, 124, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127, 127, 128, 128, 128, 129, 129, 129, 130, 130, 130, 131, 131, 131, 132, 132, 132, 133, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 137, 137, 138, 138, 138, 139, 139, 139, 140, 140, 140, 141, 141, 141, 142, 142, 142, 143, 143, 143, 144, 144, 144, 145, 145, 145, 146, 146, 146, 147, 147, 147, 148, 148, 148, 149, 149, 149, 150, 150, 150, 151, 151, 151, 152, 152, 152, 153, 153, 153, 154, 154, 154, 155, 155, 155, 156, 156, 156, 157, 157, 157, 158, 158, 158, 159, 159, 159, 160, 160, 160, 161, 161, 161, 162, 162, 162, 163, 163, 163, 164, 164, 164, 165, 165, 165, 166, 166, 166, 167, 167, 167, 168, 168, 168, 169, 169, 169, 170, 170, 170, 171, 171, 171, 172, 172, 172, 173, 173, 173, 174, 174, 174, 175, 175, 175, 176, 176, 176, 177, 177, 177, 178, 178, 178, 179, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 183, 183, 183, 184, 184, 184, 185, 185, 185, 186, 186, 186, 187, 187, 187, 188, 188, 188, 189, 189, 189, 190, 190, 190, 191, 191, 191, 192, 192, 192, 193, 193, 193, 194, 194, 194, 195, 195, 195, 196, 196, 196, 197, 197, 197, 198, 198, 198, 199, 199, 199, 200, 200, 200, 201, 201, 201, 202, 202, 202, 203, 203, 203, 204, 204, 204, 205, 205, 205, 206, 206, 206, 207, 207, 207, 208, 208, 208, 209, 209, 209, 210, 210, 210, 211, 211, 211, 212, 212, 212, 213, 213, 213, 214, 214, 214, 215, 215, 215, 216, 216, 216, 217, 217, 217, 218, 218, 218, 219, 219, 219, 220, 220, 220, 221, 221, 221, 222, 222, 222, 223, 223, 223, 224, 224, 224, 225, 225, 225, 226, 226, 226, 227, 227, 227, 228, 228, 228, 229, 229, 229, 230, 230, 230, 231, 231, 231, 232, 232, 232, 233, 233, 233, 234, 234, 234, 235, 235, 235, 236, 236, 236, 237, 237, 237, 238, 238, 238, 239, 239, 239, 240, 240, 240, 241, 241, 241, 242, 242, 242, 243, 243, 243, 244, 244, 244, 245, 245, 245, 246, 246, 246, 247, 247, 247, 248, 248, 248, 249, 249, 249, 250, 250, 250, 251, 251, 251, 252, 252, 252, 253, 253, 253, 254, 254, 254, 255, 255, 255, 43 | 0}; 44 | const byte colormap_ironblack[] = {255, 255, 255, 253, 253, 253, 251, 251, 251, 249, 249, 249, 247, 247, 247, 245, 245, 245, 243, 243, 243, 241, 241, 241, 239, 239, 239, 237, 237, 237, 235, 235, 235, 233, 233, 233, 231, 231, 231, 229, 229, 229, 227, 227, 227, 225, 225, 225, 223, 223, 223, 221, 221, 221, 219, 219, 219, 217, 217, 217, 215, 215, 215, 213, 213, 213, 211, 211, 211, 209, 209, 209, 207, 207, 207, 205, 205, 205, 203, 203, 203, 201, 201, 201, 199, 199, 199, 197, 197, 197, 195, 195, 195, 193, 193, 193, 191, 191, 191, 189, 189, 189, 187, 187, 187, 185, 185, 185, 183, 183, 183, 181, 181, 181, 179, 179, 179, 177, 177, 177, 175, 175, 175, 173, 173, 173, 171, 171, 171, 169, 169, 169, 167, 167, 167, 165, 165, 165, 163, 163, 163, 161, 161, 161, 159, 159, 159, 157, 157, 157, 155, 155, 155, 153, 153, 153, 151, 151, 151, 149, 149, 149, 147, 147, 147, 145, 145, 145, 143, 143, 143, 141, 141, 141, 139, 139, 139, 137, 137, 137, 135, 135, 135, 133, 133, 133, 131, 131, 131, 129, 129, 129, 126, 126, 126, 124, 124, 124, 122, 122, 122, 120, 120, 120, 118, 118, 118, 116, 116, 116, 114, 114, 114, 112, 112, 112, 110, 110, 110, 108, 108, 108, 106, 106, 106, 104, 104, 104, 102, 102, 102, 100, 100, 100, 98, 98, 98, 96, 96, 96, 94, 94, 94, 92, 92, 92, 90, 90, 90, 88, 88, 88, 86, 86, 86, 84, 84, 84, 82, 82, 82, 80, 80, 80, 78, 78, 78, 76, 76, 76, 74, 74, 74, 72, 72, 72, 70, 70, 70, 68, 68, 68, 66, 66, 66, 64, 64, 64, 62, 62, 62, 60, 60, 60, 58, 58, 58, 56, 56, 56, 54, 54, 54, 52, 52, 52, 50, 50, 50, 48, 48, 48, 46, 46, 46, 44, 44, 44, 42, 42, 42, 40, 40, 40, 38, 38, 38, 36, 36, 36, 34, 34, 34, 32, 32, 32, 30, 30, 30, 28, 28, 28, 26, 26, 26, 24, 24, 24, 22, 22, 22, 20, 20, 20, 18, 18, 18, 16, 16, 16, 14, 14, 14, 12, 12, 12, 10, 10, 10, 8, 8, 8, 6, 6, 6, 4, 4, 4, 2, 2, 2, 0, 0, 0, 0, 0, 9, 2, 0, 16, 4, 0, 24, 6, 0, 31, 8, 0, 38, 10, 0, 45, 12, 0, 53, 14, 0, 60, 17, 0, 67, 19, 0, 74, 21, 0, 82, 23, 0, 89, 25, 0, 96, 27, 0, 103, 29, 0, 111, 31, 0, 118, 36, 0, 120, 41, 0, 121, 46, 0, 122, 51, 0, 123, 56, 0, 124, 61, 0, 125, 66, 0, 126, 71, 0, 127, 76, 1, 128, 81, 1, 129, 86, 1, 130, 91, 1, 131, 96, 1, 132, 101, 1, 133, 106, 1, 134, 111, 1, 135, 116, 1, 136, 121, 1, 136, 125, 2, 137, 130, 2, 137, 135, 3, 137, 139, 3, 138, 144, 3, 138, 149, 4, 138, 153, 4, 139, 158, 5, 139, 163, 5, 139, 167, 5, 140, 172, 6, 140, 177, 6, 140, 181, 7, 141, 186, 7, 141, 189, 10, 137, 191, 13, 132, 194, 16, 127, 196, 19, 121, 198, 22, 116, 200, 25, 111, 203, 28, 106, 205, 31, 101, 207, 34, 95, 209, 37, 90, 212, 40, 85, 214, 43, 80, 216, 46, 75, 218, 49, 69, 221, 52, 64, 223, 55, 59, 224, 57, 49, 225, 60, 47, 226, 64, 44, 227, 67, 42, 228, 71, 39, 229, 74, 37, 230, 78, 34, 231, 81, 32, 231, 85, 29, 232, 88, 27, 233, 92, 24, 234, 95, 22, 235, 99, 19, 236, 102, 17, 237, 106, 14, 238, 109, 12, 239, 112, 12, 240, 116, 12, 240, 119, 12, 241, 123, 12, 241, 127, 12, 242, 130, 12, 242, 134, 12, 243, 138, 12, 243, 141, 13, 244, 145, 13, 244, 149, 13, 245, 152, 13, 245, 156, 13, 246, 160, 13, 246, 163, 13, 247, 167, 13, 247, 171, 13, 248, 175, 14, 248, 178, 15, 249, 182, 16, 249, 185, 18, 250, 189, 19, 250, 192, 20, 251, 196, 21, 251, 199, 22, 252, 203, 23, 252, 206, 24, 253, 210, 25, 253, 213, 27, 254, 217, 28, 254, 220, 29, 255, 224, 30, 255, 227, 39, 255, 229, 53, 255, 231, 67, 255, 233, 81, 255, 234, 95, 255, 236, 109, 255, 238, 123, 255, 240, 137, 255, 242, 151, 255, 244, 165, 255, 246, 179, 255, 248, 193, 255, 249, 207, 255, 251, 221, 255, 253, 235, 255, 255, 24, 45 | 0}; 46 | int colorMode = 0; 47 | 48 | unsigned long startTime; 49 | unsigned long lastMillis; 50 | unsigned long frameCount; 51 | unsigned int framesPerSecond; 52 | unsigned long lastRenderTime = 0; 53 | 54 | const boolean isDebug = false; 55 | 56 | const int UI_VIDEO_VIEW = 11000; 57 | const int UI_SETTING_VIEW = 12000; 58 | 59 | int uiState = UI_VIDEO_VIEW; 60 | boolean needToInvalidate = true; 61 | 62 | unsigned long lastTouchCheckTime = 0; 63 | uint16_t touchX = -1, touchY = -1; 64 | 65 | const int SCALE_MODE_NNI = 1001; // Nearest Neighbor Interpolation 66 | const int SCALE_MODE_BLI = 1002; // Biliniear Interpolation 67 | int scaleMode = SCALE_MODE_BLI; 68 | 69 | const int AUTO_TEMP_MODE_MINMAX = 1; 70 | const int AUTO_TEMP_MODE_FLAT_MINMAX = 2; 71 | int autoTempMode = AUTO_TEMP_MODE_MINMAX; 72 | 73 | int autoTempFlatMin = 65535; 74 | int autoTempFlatMax = 0; 75 | 76 | int visibleMinTemp = 0; 77 | int visibleMaxTemp = 0; 78 | 79 | void setup() { 80 | Serial.begin(115200); 81 | Serial.println("on setup"); 82 | 83 | setupLepton(); 84 | setupTft(); 85 | Serial.println("setup done"); 86 | } 87 | 88 | void setupLepton() { 89 | lepton.begin(); 90 | 91 | uint16_t serialNumber[4]; 92 | lepton.doGetCommand(Lepton::CMD_SYS_FLIR_SERIAL_NUMBER, serialNumber); 93 | if (isDebug) { 94 | Serial.printf("FLIR Serial Number: %4x %4x %4x %4x\n", serialNumber[0], serialNumber[1], serialNumber[2], serialNumber[3]); 95 | } 96 | 97 | lepton.syncFrame(); 98 | 99 | uint16_t SYNC = 5,DELAY = 3,oen = 1,dat; 100 | lepton.doSetCommand(0x4854, &SYNC, 1); 101 | lepton.doSetCommand(0x4858, &DELAY, 1); 102 | 103 | lepton.end(); 104 | if (isDebug) { 105 | Serial.println("sync done"); 106 | } 107 | } 108 | 109 | void setupTft() { 110 | tft.init(); 111 | 112 | tft.setRotation(1); 113 | 114 | videoSprite.setColorDepth(8); 115 | videoSprite.createSprite( 116 | 160, 117 | 128 118 | ); 119 | videoSprite.fillSprite(TFT_BLUE); 120 | } 121 | 122 | void loop() { 123 | startTime = millis(); 124 | 125 | lepton.syncFrame(); 126 | 127 | if (uiState == UI_VIDEO_VIEW && !lepton.readFrame(frameData)) { 128 | Serial.println("skip"); 129 | lepton.end(); 130 | return; 131 | } else { 132 | invalidateVideo(); 133 | } 134 | lepton.end(); 135 | 136 | static uint16_t color; 137 | 138 | // in video view, check touch once per second. it costs lot. 139 | if (uiState != UI_VIDEO_VIEW || millis() - lastTouchCheckTime >= 3000) { 140 | if (!tft.getTouch(&touchX, &touchY)) { 141 | touchX = -1; 142 | touchY = -1; 143 | } 144 | lastTouchCheckTime = millis(); 145 | } 146 | render(); 147 | 148 | handleTouch(); 149 | 150 | measureFrame(); 151 | } 152 | 153 | void invalidateVideo() { 154 | if (uiState != UI_VIDEO_VIEW) return; 155 | 156 | uint16_t avg = 0; 157 | uint16_t minValue = 65535; 158 | uint16_t maxValue = 0; 159 | 160 | for (int i = 0; i < leptonWidth; i++){ 161 | for(int j = 0; j < leptonHeight; j++){ 162 | uint16_t pixelData = frameData[i * leptonHeight + j]; 163 | avg += (double(pixelData) / (leptonWidth * leptonHeight)); 164 | 165 | if (pixelData !=0) { 166 | if (minValue > pixelData) { 167 | minValue = pixelData; 168 | } 169 | if (maxValue < pixelData) { 170 | maxValue = pixelData; 171 | } 172 | } 173 | } 174 | } 175 | visibleMinTemp = toCelsius(minValue); 176 | visibleMaxTemp = toCelsius(maxValue); 177 | 178 | if (autoTempMode == AUTO_TEMP_MODE_FLAT_MINMAX) { 179 | if (minValue > autoTempFlatMin) minValue = autoTempFlatMin; 180 | if (maxValue < autoTempFlatMax) maxValue = autoTempFlatMax; 181 | 182 | autoTempFlatMin = minValue; 183 | autoTempFlatMax = maxValue; 184 | } 185 | 186 | double scale = 0; 187 | if (maxValue != 0 && minValue != 0) { 188 | scale = 255.0 / (maxValue - minValue); 189 | } else { 190 | scale = 1.0; 191 | } 192 | 193 | if (isDebug) { 194 | Serial.print("avg : "); 195 | Serial.println(avg); 196 | Serial.print("maxValue : "); 197 | Serial.println(maxValue); 198 | Serial.print("minValue : "); 199 | Serial.println(minValue); 200 | Serial.print("scale : "); 201 | Serial.println(scale); 202 | } 203 | 204 | unsigned long sum2 = 0; 205 | 206 | for (int i = 0; i < leptonWidth; i++) { 207 | for (int j = 0; j < leptonHeight; j++) { 208 | uint16_t pixelData = ((frameData[i * leptonHeight + j] ) - minValue) * scale; 209 | 210 | if (pixelData < 0) { 211 | pixelData = 0; 212 | } else if (pixelData > 255) { 213 | pixelData = 255; 214 | } 215 | sum2 += pixelData; 216 | 217 | for(int k = 0; k < videoScale; k++) { 218 | for(int l = 0; l < videoScale; l++) { 219 | renderData[i * videoScale + k][j * videoScale + l] = pixelData; 220 | } 221 | } 222 | } 223 | } 224 | 225 | if (scaleMode == SCALE_MODE_BLI) { 226 | int frameLength = videoScale * 2; 227 | for (int i = 0; i < leptonWidth * videoScale; i+=(frameLength - 1)) { 228 | for (int j = 0; j < leptonHeight * videoScale; j+=(frameLength - 1)) { 229 | 230 | uint16_t originValues[4] = { 231 | renderData[i][j], 232 | renderData[i][j+(frameLength - 1)], 233 | renderData[i+(frameLength - 1)][j+(frameLength - 1)], 234 | renderData[i+(frameLength - 1)][j+(frameLength - 1)], 235 | }; 236 | for (int k = 0; k < frameLength; k++) { 237 | for (int l = 0; l < frameLength; l++) { 238 | if (i * videoScale + k >= leptonWidth * videoScale || j * videoScale + l >= leptonHeight * videoScale) continue; 239 | 240 | renderData[i + k][j + l] = ( 241 | originValues[0] * ((frameLength - k) * (frameLength - l)) + 242 | originValues[1] * (k * (frameLength - l)) + 243 | originValues[2] * ((frameLength - k) * l) + 244 | originValues[3] * (k * l) 245 | ) / ((frameLength) * (frameLength)); 246 | } 247 | } 248 | 249 | } 250 | } 251 | } 252 | 253 | if (isDebug) { 254 | Serial.print("=="); 255 | Serial.print("avg2 : " ); 256 | Serial.println(sum2 / (leptonWidth * leptonHeight)); 257 | } 258 | needToInvalidate = true; 259 | } 260 | 261 | void render() { 262 | if (needToInvalidate) { 263 | if (uiState == UI_VIDEO_VIEW) { 264 | renderThermalVideo(); 265 | } else { 266 | renderSettingUi(); 267 | } 268 | } 269 | 270 | renderSystemUi(); 271 | 272 | needToInvalidate = false; 273 | } 274 | 275 | void renderThermalVideo() { 276 | const byte* colorMap; 277 | if (colorMode == 0) { 278 | colorMap = colormap_rainbow; 279 | } else if (colorMode == 1) { 280 | colorMap = colormap_grayscale; 281 | } else if (colorMode == 2) { 282 | colorMap = colormap_ironblack; 283 | } 284 | 285 | for (int i = 0; i < leptonWidth * videoScale; i++) { 286 | for (int j = 0; j < leptonHeight * videoScale; j++) { 287 | uint16_t color = tft.color565( 288 | colorMap[renderData[i][j] * 3 + 0], 289 | colorMap[renderData[i][j] * 3 + 1], 290 | colorMap[renderData[i][j] * 3 + 2] 291 | ); 292 | int x = j / 2; 293 | int y = (i % 2) * 80 + i; 294 | for (int k=0;k<2;k++) { 295 | for(int l=0;l<2;l++) { 296 | 297 | videoSprite.drawPixel( 298 | j*2 + k, 299 | i*2 + l, 300 | color 301 | ); 302 | 303 | } 304 | } 305 | } 306 | } 307 | videoSprite.pushSprite(0, 0); 308 | } 309 | 310 | void renderSettingUi() { 311 | tft.fillScreen(TFT_BLUE); 312 | 313 | tft.setTextSize(3); 314 | tft.drawString("settings", 0, 0); 315 | 316 | tft.setTextSize(2); 317 | tft.drawString("rainbow (" + String((colorMode == 0) ? "o" : " ") + ")", 0, 30); 318 | tft.drawString("grayscale (" + String((colorMode == 1) ? "o" : " ") + ")", 0, 50); 319 | tft.drawString("ironblack (" + String((colorMode == 2) ? "o" : " ") + ")", 0, 70); 320 | 321 | tft.drawString("auto temp (" + String((autoTempMode == AUTO_TEMP_MODE_MINMAX) ? "o" : " ") + ")", 0, 100); 322 | tft.drawString("flat temp (" + String((autoTempMode == AUTO_TEMP_MODE_FLAT_MINMAX) ? "o" : " ") + ")", 0, 120); 323 | 324 | tft.drawString("scale_nni (" + String((scaleMode == SCALE_MODE_NNI) ? "o" : " ") + ")", 0, 150); 325 | tft.drawString("scale_bli (" + String((scaleMode == SCALE_MODE_BLI) ? "o" : " ") + ")", 0, 170); 326 | 327 | tft.setTextSize(3); 328 | tft.drawString("back", 250, 210); 329 | } 330 | 331 | void renderSystemUi() { 332 | if (isDebug) { 333 | Serial.print("fps : "); 334 | Serial.println(framesPerSecond); 335 | } 336 | 337 | tft.setTextSize(1); 338 | tft.drawString("min : " + String(visibleMinTemp) + "'C max : " + String(visibleMaxTemp) + "'C", 0, 0); 339 | 340 | tft.drawString("fps : " + String(framesPerSecond) + " frame : " + String(lastRenderTime) + "ms", 200, 0); 341 | } 342 | 343 | void handleTouch() { 344 | if (touchX == -1 || touchX == 65535 || touchY == -1 || touchY == 65535) return; 345 | 346 | if (uiState == UI_VIDEO_VIEW) { 347 | uiState = UI_SETTING_VIEW; 348 | } else { 349 | handleTouchInSettingUi(); 350 | } 351 | 352 | // after change ui, prevent touch in 1s. 353 | lastTouchCheckTime = millis() + 1000; 354 | 355 | needToInvalidate = true; 356 | } 357 | 358 | void handleTouchInSettingUi() { 359 | // back button 360 | if (touchIn(250, 200, 320, 240)) { 361 | uiState = UI_VIDEO_VIEW; 362 | } 363 | // color - rainbow 364 | else if (touchIn(0, 30, 120, 50)) { 365 | colorMode = 0; 366 | } 367 | // color - grayscale 368 | else if (touchIn(0, 50, 120, 70)) { 369 | colorMode = 1; 370 | } 371 | // color - ironblack 372 | else if (touchIn(0, 70, 120, 90)) { 373 | colorMode = 2; 374 | } 375 | // tempMode - minmax 376 | else if (touchIn(0, 100, 120, 120)) { 377 | autoTempMode = AUTO_TEMP_MODE_MINMAX; 378 | } 379 | // tempMode = flat 380 | else if (touchIn(0, 120, 120, 140)) { 381 | autoTempMode = AUTO_TEMP_MODE_FLAT_MINMAX; 382 | 383 | autoTempFlatMin = 65535; 384 | autoTempFlatMax = 0; 385 | } 386 | // scaleMode = nni 387 | else if (touchIn(0, 150, 120, 170)) { 388 | scaleMode = SCALE_MODE_NNI; 389 | } 390 | // scaleMode = bli 391 | else if (touchIn(0, 170, 120, 190)) { 392 | scaleMode = SCALE_MODE_BLI; 393 | } 394 | } 395 | 396 | boolean touchIn(int startX, int startY, int endX, int endY) { 397 | if (touchX >= startX && touchX <= endX && touchY >= startY && touchY <= endY) { 398 | return true; 399 | } else { 400 | return false; 401 | } 402 | } 403 | 404 | void measureFrame() { 405 | unsigned long now = millis(); 406 | 407 | frameCount ++; 408 | if (now - lastMillis >= 1000) { 409 | framesPerSecond = frameCount; 410 | frameCount = 0; 411 | lastMillis = now; 412 | } 413 | 414 | lastRenderTime = now - startTime; 415 | } 416 | 417 | int toCelsius(int rawValue) { 418 | int kelvin = rawValue / 100; 419 | return kelvin - 273.15; 420 | } 421 | --------------------------------------------------------------------------------