├── 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 | 
5 | 
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 | 
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 |
--------------------------------------------------------------------------------