├── screenshot.png ├── README.md ├── LICENSE ├── MLX90640.ino └── MLX90640.py /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fogleman/MLX90640/HEAD/screenshot.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MLX90640 Python Driver 2 | 3 | This code uses the Arduino as a simple I2C to Serial relay, leaving all of the 4 | calibration and image processing to Python code running on the PC host. 5 | 6 | (ATmega-based Arduinos like the Arduino Nano do not have enough RAM to hold the 7 | 16-bit 32x24px image in memory, so the usual MLX90640 libraries do not work.) 8 | 9 | ### Usage 10 | 11 | python MLX90640.py /dev/tty.usbserial-XXX 12 | 13 | ### Screenshot 14 | 15 | ![Screenshot](screenshot.png) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Michael Fogleman 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 | -------------------------------------------------------------------------------- /MLX90640.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define MLX90640_ADDR 0x33 4 | 5 | #define CMD_READ 0 6 | #define CMD_WRITE 1 7 | #define CMD_READ_MANY 2 8 | #define CMD_READ_CHESS_PAGE 3 9 | #define CMD_READ_INTERLEAVED_PAGE 4 10 | 11 | void read(uint16_t addr) { 12 | Wire.beginTransmission(MLX90640_ADDR); 13 | Wire.write(addr >> 8); 14 | Wire.write(addr & 0xff); 15 | Wire.endTransmission(false); 16 | Wire.requestFrom(MLX90640_ADDR, 2); 17 | Serial.write(Wire.read()); 18 | Serial.write(Wire.read()); 19 | } 20 | 21 | void write(uint16_t addr, uint16_t value) { 22 | Wire.beginTransmission(MLX90640_ADDR); 23 | Wire.write(addr >> 8); 24 | Wire.write(addr & 0xff); 25 | Wire.write(value >> 8); 26 | Wire.write(value & 0xff); 27 | Wire.endTransmission(); 28 | } 29 | 30 | void readMany(uint16_t addr, int words) { 31 | while (words > 0) { 32 | Wire.beginTransmission(MLX90640_ADDR); 33 | Wire.write(addr >> 8); 34 | Wire.write(addr & 0xff); 35 | Wire.endTransmission(false); 36 | int n = min(16, words); 37 | Wire.requestFrom(MLX90640_ADDR, n * 2); 38 | for (int i = 0; i < n; i++) { 39 | Serial.write(Wire.read()); 40 | Serial.write(Wire.read()); 41 | } 42 | addr += n; 43 | words -= n; 44 | } 45 | } 46 | 47 | void readPage(int chess, int page) { 48 | uint16_t addr = 0x0400; 49 | for (int y = 0; y < 24; y++) { 50 | for (int h = 0; h < 2; h++) { 51 | Wire.beginTransmission(MLX90640_ADDR); 52 | Wire.write(addr >> 8); 53 | Wire.write(addr & 0xff); 54 | Wire.endTransmission(false); 55 | Wire.requestFrom(MLX90640_ADDR, 32); 56 | for (int i = 0; i < 16; i++) { 57 | int x = h * 16 + i; 58 | int p = chess ? (x ^ y) & 1 : y & 1; 59 | uint8_t msb = Wire.read(); 60 | uint8_t lsb = Wire.read(); 61 | if (p == page) { 62 | Serial.write(msb); 63 | Serial.write(lsb); 64 | } 65 | } 66 | addr += 16; 67 | } 68 | } 69 | } 70 | 71 | uint16_t serialRead2() { 72 | while (Serial.available() < 2); 73 | return Serial.read() << 8 | Serial.read(); 74 | } 75 | 76 | void setup() { 77 | Wire.begin(); 78 | Wire.setClock(400000); 79 | Serial.begin(115200); 80 | } 81 | 82 | void loop() { 83 | while (!Serial.available()); 84 | uint8_t cmd = Serial.read(); 85 | if (cmd == CMD_READ) { 86 | uint16_t addr = serialRead2(); 87 | read(addr); 88 | } else if (cmd == CMD_WRITE) { 89 | uint16_t addr = serialRead2(); 90 | uint16_t value = serialRead2(); 91 | write(addr, value); 92 | } else if (cmd == CMD_READ_MANY) { 93 | uint16_t addr = serialRead2(); 94 | uint16_t words = serialRead2(); 95 | readMany(addr, words); 96 | } else if (cmd == CMD_READ_CHESS_PAGE) { 97 | uint8_t page = Serial.read(); 98 | readPage(1, page); 99 | } else if (cmd == CMD_READ_INTERLEAVED_PAGE) { 100 | uint8_t page = Serial.read(); 101 | readPage(0, page); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /MLX90640.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | import numpy as np 3 | import serial 4 | import sys 5 | import time 6 | 7 | REFRESH_RATE_Hz = 2 8 | CHESS_PATTERN = True 9 | 10 | BAUD_RATE = 115200 11 | ROWS = 24 12 | COLS = 32 13 | V_REF = 3.3 14 | T_REF = 25 15 | ZERO = -273.15 16 | EMISSIVITY = 0.95 17 | 18 | CHESS_1 = np.indices((ROWS, COLS)).sum(axis=0) % 2 19 | CHESS_0 = 1 - CHESS_1 20 | CHESS = [CHESS_0, CHESS_1] 21 | INTERLEAVED_0 = np.tile([1, 0], (32, 12)).T 22 | INTERLEAVED_1 = 1 - INTERLEAVED_0 23 | INTERLEAVED = [INTERLEAVED_0, INTERLEAVED_1] 24 | CONVERSION = np.tile([[0, -1, 0, 1], [0, 1, 0, -1]], (12, 8)) 25 | 26 | EEPROM = namedtuple('EEPROM', [ 27 | 'occ_scale_rem', 'occ_scale_col', 'occ_scale_row', 'k_ptat', 28 | 'offset_average', 'occ_row', 'occ_col', 29 | 'acc_scale_rem', 'acc_scale_col', 'acc_scale_row', 'alpha_scale', 30 | 'alpha_ref', 'acc_row', 'acc_col', 31 | 'gain', 'ptat_25', 'k_t_ptat', 'k_v_ptat', 'v_dd_25', 32 | 'k_v_dd', 'k_v_re_ce', 'k_v_ro_ce', 'k_v_re_co', 'k_v_ro_co', 33 | 'il_chess_c1', 'il_chess_c2', 'il_chess_c3', 34 | 'k_ta_re_co', 'k_ta_ro_co', 'k_ta_re_ce', 'k_ta_ro_ce', 35 | 'k_ta_scale_2', 'k_ta_scale_1', 'k_v_scale', 'resolution_cal', 36 | 'alpha_cp_sp_0', 'alpha_cp_sp_1', 'offset_cp_sp_0', 'offset_cp_sp_1', 37 | 'k_ta_cp', 'k_v_cp', 'tgc', 38 | 'k_s_ta', 'k_s_to_scale', 'k_s_to1', 'k_s_to2', 'k_s_to3', 'k_s_to4', 39 | 'ct_step', 'ct3', 'ct4', 40 | 'pixel_outlier', 'pixel_k_ta', 'pixel_alpha', 'pixel_offset' 41 | ]) 42 | 43 | RAM = namedtuple('RAM', [ 44 | 'pixels', 45 | 'v_be', 'cp_sp0', 'gain', 46 | 'v_ptat', 'cp_sp1', 'v_dd', 47 | ]) 48 | 49 | Registers = namedtuple('Registers', [ 50 | 'last_page', 'new_page_available', 'overwrite_enabled', 51 | 'pages_enabled', 'page_hold_enabled', 52 | 'select_page_enabled', 'selected_page', 53 | 'refresh_rate', 'adc_resolution', 'chess_pattern_enabled', 54 | 'fast_mode_disabled', 'i2c_threshold_mode', 'sda_current_limit_disabled', 55 | ]) 56 | 57 | class MLX90640: 58 | def __init__(self, port, baudrate=BAUD_RATE): 59 | self.port = serial.Serial(port, baudrate) 60 | time.sleep(2) # wait for arduino to restart 61 | self.mem = {} # addr => value 62 | self.read_eeprom() 63 | self.read_ram() 64 | self.read_registers() 65 | 66 | def read(self, addr): 67 | self.port.write(b'\x00' + addr.to_bytes(2, 'big')) 68 | return int.from_bytes(self.port.read(2), 'big') 69 | 70 | def write(self, addr, value, size=16, offset=0): 71 | if size != 16 or offset != 0: 72 | prev = self.read(addr) 73 | mask = ((1 << size) - 1) << offset 74 | value = (prev & ~mask) | (value << offset) 75 | self.port.write(b'\x01' + addr.to_bytes(2, 'big') + value.to_bytes(2, 'big')) 76 | 77 | def read_many(self, addr, words): 78 | self.port.write(b'\x02' + addr.to_bytes(2, 'big') + words.to_bytes(2, 'big')) 79 | return [int.from_bytes(self.port.read(2), 'big') for i in range(words)] 80 | 81 | def read_page(self, chess, page): 82 | cmd = 3 if chess else 4 83 | self.port.write(cmd.to_bytes(1, 'big') + page.to_bytes(1, 'big')) 84 | return [int.from_bytes(self.port.read(2), 'big') for i in range(ROWS * COLS // 2)] 85 | 86 | def read_into_mem(self, addr, words): 87 | data = self.read_many(addr, words) 88 | for i, v in enumerate(data): 89 | self.mem[addr + i] = v 90 | 91 | def read_ram(self): 92 | self.read_into_mem(0x0400, 0x0340) 93 | self.ram = self.decode_ram() 94 | 95 | def read_eeprom(self): 96 | self.read_into_mem(0x2400, 0x0340) 97 | self.ee = self.decode_eeprom() 98 | 99 | def read_registers(self): 100 | for addr in [0x8000, 0x800d, 0x800f]: 101 | self.mem[addr] = self.read(addr) 102 | self.reg = self.decode_registers() 103 | 104 | def read_last_page(self): 105 | chess = self.reg.chess_pattern_enabled 106 | page = self.reg.last_page 107 | data = self.read_page(chess, page) 108 | self.read_into_mem(0x0700, 0x0030) 109 | for y in range(ROWS): 110 | for x in range(COLS): 111 | p = (x ^ y) & 1 if chess else y & 1 112 | if p == page: 113 | addr = 0x0400 + y * COLS + x 114 | self.mem[addr] = data.pop(0) 115 | self.ram = self.decode_ram() 116 | 117 | def set_refresh_rate_Hz(self, rr_Hz): 118 | rr = np.log(rr_Hz) / np.log(2) + 1 119 | rr = int(round(np.clip(rr, 0, 7))) 120 | self.write(0x800d, rr, 3, 7) 121 | 122 | def clear_new_page_available(self): 123 | self.write(0x8000, 0, 1, 3) 124 | 125 | def enable_chess_pattern(self, enable): 126 | value = int(bool(enable)) 127 | self.write(0x800d, value, 1, 12) 128 | 129 | def get_next_frame(self): 130 | while True: 131 | self.read_registers() 132 | if self.reg.new_page_available: 133 | break 134 | time.sleep(0.01) 135 | self.clear_new_page_available() 136 | self.read_last_page() 137 | return self.get_t_o() 138 | 139 | def unsigned(self, addr, bits=16, offset=0): 140 | return (self.mem[addr] >> offset) & ((1 << bits) - 1) 141 | 142 | def signed(self, addr, bits=16, offset=0): 143 | value = self.unsigned(addr, bits, offset) 144 | if value > (1 << (bits - 1)) - 1: 145 | value -= 1 << bits 146 | return value 147 | 148 | def decode_registers(self): 149 | unsigned = self.unsigned 150 | return Registers( 151 | last_page = unsigned(0x8000, 3, 0), 152 | new_page_available = unsigned(0x8000, 1, 3), 153 | overwrite_enabled = unsigned(0x8000, 1, 4), 154 | pages_enabled = unsigned(0x800d, 1, 0), 155 | page_hold_enabled = unsigned(0x800d, 1, 2), 156 | select_page_enabled = unsigned(0x800d, 1, 3), 157 | selected_page = unsigned(0x800d, 3, 4), 158 | refresh_rate = 2 ** (unsigned(0x800d, 3, 7) - 1), 159 | adc_resolution = unsigned(0x800d, 2, 10) + 16, 160 | chess_pattern_enabled = unsigned(0x800d, 1, 12), 161 | fast_mode_disabled = unsigned(0x800f, 1, 0), 162 | i2c_threshold_mode = unsigned(0x800f, 1, 1), 163 | sda_current_limit_disabled = unsigned(0x800f, 1, 2)) 164 | 165 | def decode_ram(self): 166 | signed = self.signed 167 | return RAM( 168 | pixels = [signed(i) for i in range(0x0400, 0x0700)], 169 | v_be = signed(0x0700), 170 | cp_sp0 = signed(0x0708), 171 | gain = signed(0x070a), 172 | v_ptat = signed(0x0720), 173 | cp_sp1 = signed(0x0728), 174 | v_dd = signed(0x072a)) 175 | 176 | def decode_eeprom(self): 177 | signed = self.signed 178 | unsigned = self.unsigned 179 | 180 | occ_scale_rem = unsigned(0x2410, 4, 0) 181 | occ_scale_col = unsigned(0x2410, 4, 4) 182 | occ_scale_row = unsigned(0x2410, 4, 8) 183 | k_ptat = unsigned(0x2410, 4, 12) / 4 + 8 184 | offset_average = signed(0x2411) 185 | occ_row = tuple(signed(0x2412 + i // 4, 4, i % 4 * 4) for i in range(ROWS)) 186 | occ_col = tuple(signed(0x2418 + i // 4, 4, i % 4 * 4) for i in range(COLS)) 187 | 188 | acc_scale_rem = unsigned(0x2420, 4, 0) 189 | acc_scale_col = unsigned(0x2420, 4, 4) 190 | acc_scale_row = unsigned(0x2420, 4, 8) 191 | alpha_scale = unsigned(0x2420, 4, 12) + 30 192 | alpha_ref = unsigned(0x2421) 193 | acc_row = tuple(signed(0x2422 + i // 4, 4, i % 4 * 4) for i in range(ROWS)) 194 | acc_col = tuple(signed(0x2428 + i // 4, 4, i % 4 * 4) for i in range(COLS)) 195 | 196 | gain = signed(0x2430) 197 | ptat_25 = signed(0x2431) 198 | k_t_ptat = signed(0x2432, 10, 0) / 8 199 | k_v_ptat = signed(0x2432, 6, 10) / 4096 200 | v_dd_25 = (unsigned(0x2433, 8, 0) - 256) * 32 - 8192 201 | k_v_dd = signed(0x2433, 8, 8) * 32 202 | 203 | k_v_re_ce = signed(0x2434, 4, 0) 204 | k_v_ro_ce = signed(0x2434, 4, 4) 205 | k_v_re_co = signed(0x2434, 4, 8) 206 | k_v_ro_co = signed(0x2434, 4, 12) 207 | 208 | il_chess_c1 = signed(0x2435, 6, 0) / 16 209 | il_chess_c2 = signed(0x2435, 5, 6) / 2 210 | il_chess_c3 = signed(0x2435, 5, 11) / 8 211 | 212 | k_ta_re_co = signed(0x2436, 8, 0) 213 | k_ta_ro_co = signed(0x2436, 8, 8) 214 | k_ta_re_ce = signed(0x2437, 8, 0) 215 | k_ta_ro_ce = signed(0x2437, 8, 8) 216 | 217 | k_ta_scale_2 = unsigned(0x2438, 4, 0) 218 | k_ta_scale_1 = unsigned(0x2438, 4, 4) + 8 219 | k_v_scale = unsigned(0x2438, 4, 8) 220 | resolution_cal = unsigned(0x2438, 2, 12) 221 | 222 | alpha_cp_scale = unsigned(0x2420, 4, 12) + 27 223 | alpha_cp_sp_0 = signed(0x2439, 10, 0) / (1 << alpha_cp_scale) 224 | alpha_cp_sp_1 = alpha_cp_sp_0 * (1 + signed(0x2439, 6, 10) / 128) 225 | 226 | offset_cp_sp_0 = signed(0x243a, 10, 0) 227 | offset_cp_sp_1 = offset_cp_sp_0 + signed(0x243a, 6, 10) 228 | 229 | k_ta_cp = signed(0x243b, 8, 0) / (1 << k_ta_scale_1) 230 | k_v_cp = signed(0x243b, 8, 8) / (1 << k_v_scale) 231 | 232 | tgc = signed(0x243c, 8, 0) / 32 233 | k_s_ta = signed(0x243c, 8, 8) / 8192 234 | 235 | k_s_to_scale = unsigned(0x243f, 4, 0) + 8 236 | k_s_to1 = signed(0x243d, 8, 0) / (1 << k_s_to_scale) 237 | k_s_to2 = signed(0x243d, 8, 8) / (1 << k_s_to_scale) 238 | k_s_to3 = signed(0x243e, 8, 0) / (1 << k_s_to_scale) 239 | k_s_to4 = signed(0x243e, 8, 8) / (1 << k_s_to_scale) 240 | ct_step = unsigned(0x243f, 2, 12) * 10 241 | ct3 = unsigned(0x243f, 4, 4) * ct_step 242 | ct4 = unsigned(0x243f, 4, 8) * ct_step + ct3 243 | 244 | pixel_outlier = tuple(unsigned(0x2440 + i, 1, 0) for i in range(768)) 245 | pixel_k_ta = tuple(signed(0x2440 + i, 3, 1) for i in range(768)) 246 | pixel_alpha = tuple(signed(0x2440 + i, 6, 4) for i in range(768)) 247 | pixel_offset = tuple(signed(0x2440 + i, 6, 10) for i in range(768)) 248 | 249 | return EEPROM( 250 | occ_scale_rem, occ_scale_col, occ_scale_row, k_ptat, 251 | offset_average, occ_row, occ_col, 252 | acc_scale_rem, acc_scale_col, acc_scale_row, alpha_scale, 253 | alpha_ref, acc_row, acc_col, 254 | gain, ptat_25, k_t_ptat, k_v_ptat, v_dd_25, 255 | k_v_dd, k_v_re_ce, k_v_ro_ce, k_v_re_co, k_v_ro_co, 256 | il_chess_c1, il_chess_c2, il_chess_c3, 257 | k_ta_re_co, k_ta_ro_co, k_ta_re_ce, k_ta_ro_ce, 258 | k_ta_scale_2, k_ta_scale_1, k_v_scale, resolution_cal, 259 | alpha_cp_sp_0, alpha_cp_sp_1, offset_cp_sp_0, offset_cp_sp_1, 260 | k_ta_cp, k_v_cp, tgc, 261 | k_s_ta, k_s_to_scale, k_s_to1, k_s_to2, k_s_to3, k_s_to4, 262 | ct_step, ct3, ct4, 263 | pixel_outlier, pixel_k_ta, pixel_alpha, pixel_offset) 264 | 265 | def get_page(self): 266 | return self.reg.last_page 267 | 268 | def get_v_dd(self): 269 | ee, ram, reg = self.ee, self.ram, self.reg 270 | resolution_corr = (1 << ee.resolution_cal) / (1 << (reg.adc_resolution - 16)) 271 | return (resolution_corr * ram.v_dd - ee.v_dd_25) / ee.k_v_dd + V_REF 272 | 273 | def get_t_a(self): 274 | ee, ram = self.ee, self.ram 275 | d_v = (ram.v_dd - ee.v_dd_25) / ee.k_v_dd 276 | v_ptat_art = (ram.v_ptat / (ram.v_ptat * ee.k_ptat + ram.v_be)) * (1 << 18) 277 | return (v_ptat_art / (1 + ee.k_v_ptat * d_v) - ee.ptat_25) / ee.k_t_ptat + T_REF 278 | 279 | def get_t_o(self): 280 | ee, ram, reg = self.ee, self.ram, self.reg 281 | 282 | occ_row = np.repeat([ee.occ_row], COLS, axis=0).T 283 | occ_col = np.repeat([ee.occ_col], ROWS, axis=0) 284 | acc_row = np.repeat([ee.acc_row], COLS, axis=0).T 285 | acc_col = np.repeat([ee.acc_col], ROWS, axis=0) 286 | 287 | k_ta_rc = np.zeros((ROWS, COLS)) 288 | k_ta_rc[0::2,0::2] = ee.k_ta_ro_co 289 | k_ta_rc[1::2,0::2] = ee.k_ta_re_co 290 | k_ta_rc[0::2,1::2] = ee.k_ta_ro_ce 291 | k_ta_rc[1::2,1::2] = ee.k_ta_re_ce 292 | 293 | k_v_rc = np.zeros((ROWS, COLS)) 294 | k_v_rc[0::2,0::2] = ee.k_v_ro_co 295 | k_v_rc[1::2,0::2] = ee.k_v_re_co 296 | k_v_rc[0::2,1::2] = ee.k_v_ro_ce 297 | k_v_rc[1::2,1::2] = ee.k_v_re_ce 298 | 299 | pixel_k_ta = np.array(ee.pixel_k_ta).reshape((ROWS, COLS)) 300 | pixel_alpha = np.array(ee.pixel_alpha).reshape((ROWS, COLS)) 301 | pixel_offset = np.array(ee.pixel_offset).reshape((ROWS, COLS)) 302 | pixels = np.array(ram.pixels).reshape((ROWS, COLS)) 303 | 304 | # device voltage and ambient temperature 305 | v_dd = self.get_v_dd() 306 | t_a = self.get_t_a() 307 | 308 | # gain 309 | k_gain = ee.gain / ram.gain 310 | pixels = pixels * k_gain 311 | 312 | # offset 313 | conversion = ee.il_chess_c3 * INTERLEAVED[reg.last_page] - ee.il_chess_c2 * CONVERSION 314 | if reg.chess_pattern_enabled: 315 | conversion = 0 316 | 317 | pixel_offset_ref = ee.offset_average + occ_row * (1 << ee.occ_scale_row) + occ_col * (1 << ee.occ_scale_col) + pixel_offset * (1 << ee.occ_scale_rem) 318 | k_ta = (k_ta_rc + pixel_k_ta * (1 << ee.k_ta_scale_2)) / (1 << ee.k_ta_scale_1) 319 | k_v = k_v_rc / (1 << ee.k_v_scale) 320 | k = (1 + k_ta * (t_a - T_REF)) * (1 + k_v * (v_dd - V_REF)) 321 | pixels = pixels + conversion - pixel_offset_ref * k 322 | 323 | # emissivity 324 | pixels = pixels / EMISSIVITY 325 | 326 | # compensation pixels 327 | il_offset = ee.il_chess_c1 328 | if reg.chess_pattern_enabled: 329 | il_offset = 0 330 | k = (1 + ee.k_ta_cp * (t_a - T_REF)) * (1 + ee.k_v_cp * (v_dd - V_REF)) 331 | pix_os_cp_sp0 = (ram.cp_sp0 * k_gain) - ee.offset_cp_sp_0 * k 332 | pix_os_cp_sp1 = (ram.cp_sp1 * k_gain) - (ee.offset_cp_sp_1 + il_offset) * k 333 | pixels = pixels - ee.tgc * (CHESS_0 * pix_os_cp_sp0 + CHESS_1 * pix_os_cp_sp1) 334 | 335 | # sensitivity 336 | alpha = (ee.alpha_ref + acc_row * (1 << ee.acc_scale_row) + acc_col * (1 << ee.acc_scale_col) + pixel_alpha * (1 << ee.acc_scale_rem)) / (1 << ee.alpha_scale) 337 | alpha = (alpha - ee.tgc * (CHESS_0 * ee.alpha_cp_sp_0 + CHESS_1 * ee.alpha_cp_sp_1)) * (1 + ee.k_s_ta * (t_a - T_REF)) 338 | 339 | # temperature 340 | t_r = t_a - 8 # NOTE: this is a fudged number meant to represent room temperature, which is probably lower than the device's measured ambient temperature 341 | t_a_k4 = (t_a - ZERO) ** 4 342 | t_r_k4 = (t_r - ZERO) ** 4 343 | t_ar = t_r_k4 - (t_r_k4 - t_a_k4) / EMISSIVITY 344 | s_x = ee.k_s_to2 * (alpha ** 3 * pixels + alpha ** 4 * t_ar) ** (1 / 4) 345 | t_o = (pixels / (alpha * (1 + ee.k_s_to2 * ZERO) + s_x) + t_ar) ** (1 / 4) + ZERO 346 | 347 | # extended temperature ranges 348 | ct = np.array([-40, 0, ee.ct3, ee.ct4]) 349 | k_s_to = np.array([ee.k_s_to1, ee.k_s_to2, ee.k_s_to3, ee.k_s_to4]) 350 | corr = np.array([ 351 | 1 / (1 + ee.k_s_to1 * 40), 352 | 1, 353 | 1 + ee.k_s_to2 * ee.ct3, 354 | (1 + ee.k_s_to2 * ee.ct3) * (1 + ee.k_s_to3 * (ee.ct4 - ee.ct3)), 355 | ]) 356 | 357 | a_idx = np.full((ROWS, COLS), 3, dtype=int) 358 | a_idx[t_o < ct[1]] = 0 359 | a_idx[t_o < ct[2]] = 1 360 | a_idx[t_o < ct[3]] = 2 361 | 362 | a_ct = ct[a_idx] 363 | a_k_s_to = k_s_to[a_idx] 364 | a_corr = corr[a_idx] 365 | 366 | t_o = (pixels / (alpha * a_corr * (1 + a_k_s_to * (t_o - a_ct))) + t_ar) ** (1 / 4) + ZERO 367 | 368 | return t_o 369 | 370 | def main(): 371 | import matplotlib.pyplot as plt 372 | 373 | args = sys.argv[1:] 374 | if len(args) != 1: 375 | print('Usage: python thermal.py port') 376 | return 377 | 378 | mlx = MLX90640(args[0]) 379 | mlx.enable_chess_pattern(CHESS_PATTERN) 380 | mlx.set_refresh_rate_Hz(REFRESH_RATE_Hz) 381 | 382 | fig, ax = plt.subplots() 383 | im = ax.imshow(np.zeros((ROWS, COLS)), cmap='viridis', interpolation='bicubic') 384 | plt.colorbar(im, label='Temperature (°C)') 385 | plt.show(block=False) 386 | 387 | while True: 388 | a = mlx.get_next_frame() 389 | a = np.fliplr(a) 390 | 391 | page = mlx.get_page() 392 | v_dd = mlx.get_v_dd() 393 | t_a = mlx.get_t_a() 394 | t_o = np.mean(a) 395 | 396 | print('P=%d, V=%.3f, T_a=%.3f °C, T_o=%.3f °C' % (page, v_dd, t_a, t_o)) 397 | 398 | im.set_data(a) 399 | im.set_clim(a.min(), a.max()) 400 | 401 | fig.canvas.draw() 402 | plt.pause(0.001) 403 | 404 | if __name__ == '__main__': 405 | main() 406 | --------------------------------------------------------------------------------