luat_lib_mlx90640.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. /*
  2. @module mlx90640
  3. @summary 红外测温(MLX90640)
  4. @version 1.0
  5. @date 2022.1.20
  6. @tag LUAT_USE_MLX90640
  7. */
  8. #include "luat_base.h"
  9. #include "luat_mem.h"
  10. #include <MLX90640_I2C_Driver.h>
  11. #include <MLX90640_API.h>
  12. #include <math.h>
  13. #include "luat_zbuff.h"
  14. #include "luat_lcd.h"
  15. #define LUAT_LOG_TAG "mlx90640"
  16. #include "luat_log.h"
  17. static luat_lcd_conf_t* lcd_conf;
  18. #define FPS1HZ 0x01
  19. #define FPS2HZ 0x02
  20. #define FPS4HZ 0x04
  21. #define FPS8HZ 0x08
  22. #define FPS16HZ 0x10
  23. #define FPS32HZ 0x20
  24. #define FPS64HZ 0x40
  25. #define MLX90640_ADDR 0x33
  26. #define TA_SHIFT 8 //Default shift for MLX90640 in open air
  27. //low range of the sensor (this will be blue on the screen)
  28. #define MINTEMP 20
  29. //high range of the sensor (this will be red on the screen)
  30. #define MAXTEMP 35
  31. #define RAW_DATA_W 32
  32. #define RAW_DATA_H 24
  33. #define RAW_DATA_SIZE RAW_DATA_W*RAW_DATA_H
  34. typedef struct mlx_ctx
  35. {
  36. uint16_t eeMLX90640[832];
  37. float mlx90640To[RAW_DATA_SIZE];
  38. uint16_t frame[834];
  39. }mlx_ctx_t;
  40. static mlx_ctx_t* ctx = NULL;
  41. // static uint16_t eeMLX90640[832];
  42. // static float mlx90640To[RAW_DATA_SIZE];
  43. // static uint16_t frame[834];
  44. static float emissivity=0.95;
  45. static int status;
  46. static float vdd;
  47. static float Ta;
  48. const uint16_t camColors[] = {0x480F,
  49. 0x400F,0x400F,0x400F,0x4010,0x3810,0x3810,0x3810,0x3810,0x3010,0x3010,
  50. 0x3010,0x2810,0x2810,0x2810,0x2810,0x2010,0x2010,0x2010,0x1810,0x1810,
  51. 0x1811,0x1811,0x1011,0x1011,0x1011,0x0811,0x0811,0x0811,0x0011,0x0011,
  52. 0x0011,0x0011,0x0011,0x0031,0x0031,0x0051,0x0072,0x0072,0x0092,0x00B2,
  53. 0x00B2,0x00D2,0x00F2,0x00F2,0x0112,0x0132,0x0152,0x0152,0x0172,0x0192,
  54. 0x0192,0x01B2,0x01D2,0x01F3,0x01F3,0x0213,0x0233,0x0253,0x0253,0x0273,
  55. 0x0293,0x02B3,0x02D3,0x02D3,0x02F3,0x0313,0x0333,0x0333,0x0353,0x0373,
  56. 0x0394,0x03B4,0x03D4,0x03D4,0x03F4,0x0414,0x0434,0x0454,0x0474,0x0474,
  57. 0x0494,0x04B4,0x04D4,0x04F4,0x0514,0x0534,0x0534,0x0554,0x0554,0x0574,
  58. 0x0574,0x0573,0x0573,0x0573,0x0572,0x0572,0x0572,0x0571,0x0591,0x0591,
  59. 0x0590,0x0590,0x058F,0x058F,0x058F,0x058E,0x05AE,0x05AE,0x05AD,0x05AD,
  60. 0x05AD,0x05AC,0x05AC,0x05AB,0x05CB,0x05CB,0x05CA,0x05CA,0x05CA,0x05C9,
  61. 0x05C9,0x05C8,0x05E8,0x05E8,0x05E7,0x05E7,0x05E6,0x05E6,0x05E6,0x05E5,
  62. 0x05E5,0x0604,0x0604,0x0604,0x0603,0x0603,0x0602,0x0602,0x0601,0x0621,
  63. 0x0621,0x0620,0x0620,0x0620,0x0620,0x0E20,0x0E20,0x0E40,0x1640,0x1640,
  64. 0x1E40,0x1E40,0x2640,0x2640,0x2E40,0x2E60,0x3660,0x3660,0x3E60,0x3E60,
  65. 0x3E60,0x4660,0x4660,0x4E60,0x4E80,0x5680,0x5680,0x5E80,0x5E80,0x6680,
  66. 0x6680,0x6E80,0x6EA0,0x76A0,0x76A0,0x7EA0,0x7EA0,0x86A0,0x86A0,0x8EA0,
  67. 0x8EC0,0x96C0,0x96C0,0x9EC0,0x9EC0,0xA6C0,0xAEC0,0xAEC0,0xB6E0,0xB6E0,
  68. 0xBEE0,0xBEE0,0xC6E0,0xC6E0,0xCEE0,0xCEE0,0xD6E0,0xD700,0xDF00,0xDEE0,
  69. 0xDEC0,0xDEA0,0xDE80,0xDE80,0xE660,0xE640,0xE620,0xE600,0xE5E0,0xE5C0,
  70. 0xE5A0,0xE580,0xE560,0xE540,0xE520,0xE500,0xE4E0,0xE4C0,0xE4A0,0xE480,
  71. 0xE460,0xEC40,0xEC20,0xEC00,0xEBE0,0xEBC0,0xEBA0,0xEB80,0xEB60,0xEB40,
  72. 0xEB20,0xEB00,0xEAE0,0xEAC0,0xEAA0,0xEA80,0xEA60,0xEA40,0xF220,0xF200,
  73. 0xF1E0,0xF1C0,0xF1A0,0xF180,0xF160,0xF140,0xF100,0xF0E0,0xF0C0,0xF0A0,
  74. 0xF080,0xF060,0xF040,0xF020,0xF800,};
  75. static float map(float val, float I_Min, float I_Max, float O_Min, float O_Max){
  76. return(((val-I_Min)*((O_Max-O_Min)/(I_Max-I_Min)))+O_Min);
  77. }
  78. #define constrain(amt, low, high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
  79. static paramsMLX90640* mlx90640;
  80. mlx90640_i2c_t mlx90640_i2c;
  81. static uint8_t mlx90640_refresh_rate;
  82. /*
  83. 初始化MLX90640传感器
  84. @api mlx90640.init(i2c_id,refresh_rate) (注意:2023.5.15之后使用此接口,用户需要自行初始化i2c接口)
  85. @int 传感器所在的i2c总线id或者软i2c对象,默认为0
  86. @int 传感器的测量速率,默认为4Hz
  87. @return bool 成功返回true, 否则返回nil或者false
  88. @usage
  89. i2c.setup(i2cid,i2c_speed)
  90. if mlx90640.init(0,mlx90640.FPS4HZ) then
  91. log.info("mlx90640", "init ok")
  92. sys.wait(500) -- 稍等片刻
  93. while 1 do
  94. mlx90640.feed() -- 取一帧数据
  95. mlx90640.draw2lcd(0, 0 ,1)-- 需提前把lcd初始化好
  96. sys.wait(250) -- 默认是4HZ
  97. end
  98. else
  99. log.info("mlx90640", "init fail")
  100. end
  101. */
  102. static int l_mlx90640_init(lua_State *L){
  103. mlx90640_i2c.i2c_id = -1;
  104. if (!lua_isuserdata(L, 1)) {
  105. mlx90640_i2c.i2c_id = luaL_optinteger(L, 1 , 0);
  106. }else if (lua_isuserdata(L, 1)){
  107. mlx90640_i2c.ei2c = toei2c(L);
  108. }
  109. else {
  110. LLOGE("bad i2c arg!!!");
  111. return 0;
  112. }
  113. mlx90640_refresh_rate = luaL_optinteger(L, 2 , FPS4HZ);
  114. lcd_conf = luat_lcd_get_default();
  115. if (ctx == NULL) {
  116. ctx = luat_heap_malloc(sizeof(mlx_ctx_t));
  117. if (ctx == NULL) {
  118. LLOGE("out of memory when malloc mlx_ctx_t");
  119. return 0;
  120. }
  121. }
  122. MLX90640_I2CInit();
  123. mlx90640 = (paramsMLX90640*)luat_heap_malloc(sizeof(paramsMLX90640));
  124. MLX90640_SetRefreshRate(MLX90640_ADDR, mlx90640_refresh_rate);
  125. MLX90640_SetChessMode(MLX90640_ADDR);
  126. status = MLX90640_DumpEE(MLX90640_ADDR, ctx->eeMLX90640);
  127. if (status != 0){
  128. LLOGW("load system parameters error with code:%d",status);
  129. return 0;
  130. }
  131. status = MLX90640_ExtractParameters(ctx->eeMLX90640, mlx90640);
  132. if (status != 0) {
  133. LLOGW("Parameter extraction failed with error code:%d",status);
  134. return 0;
  135. }
  136. //初始化后此处先读帧,去掉初始化后一些错误数据
  137. for (size_t i = 0; i < 3; i++){
  138. int status = MLX90640_GetFrameData(MLX90640_ADDR, ctx->frame);
  139. if (status < 0){
  140. LLOGD("GetFrame Error: %d",status);
  141. return 0;
  142. }
  143. vdd = MLX90640_GetVdd(ctx->frame, mlx90640);
  144. Ta = MLX90640_GetTa(ctx->frame, mlx90640);
  145. MLX90640_CalculateTo(ctx->frame, mlx90640, emissivity , Ta - TA_SHIFT, ctx->mlx90640To);
  146. MLX90640_BadPixelsCorrection(mlx90640->brokenPixels, ctx->mlx90640To, 1, mlx90640);
  147. MLX90640_BadPixelsCorrection(mlx90640->outlierPixels, ctx->mlx90640To, 1, mlx90640);
  148. }
  149. lua_pushboolean(L, 1);
  150. return 1;
  151. }
  152. /*
  153. 取一帧数据
  154. @api mlx90640.feed()
  155. */
  156. static int l_mlx90640_feed(lua_State *L) {
  157. if (ctx == NULL) {
  158. LLOGE("mlx90640 NOT init yet");
  159. return 0;
  160. }
  161. int status = MLX90640_GetFrameData(MLX90640_ADDR, ctx->frame);
  162. if (status < 0){
  163. LLOGD("GetFrame Error: %d",status);
  164. return 0;
  165. }
  166. vdd = MLX90640_GetVdd(ctx->frame, mlx90640);
  167. Ta = MLX90640_GetTa(ctx->frame, mlx90640);
  168. MLX90640_CalculateTo(ctx->frame, mlx90640, emissivity , Ta - TA_SHIFT, ctx->mlx90640To);
  169. MLX90640_BadPixelsCorrection(mlx90640->brokenPixels, ctx->mlx90640To, 1, mlx90640);
  170. MLX90640_BadPixelsCorrection(mlx90640->outlierPixels, ctx->mlx90640To, 1, mlx90640);
  171. lua_pushboolean(L, 1);
  172. return 0;
  173. }
  174. /*
  175. 获取底层裸数据,浮点数矩阵
  176. @api mlx90640.raw_data()
  177. @return table 浮点数数据,768个像素对应的温度值
  178. */
  179. static int l_mlx90640_raw_data(lua_State *L) {
  180. if (ctx == NULL) {
  181. LLOGE("mlx90640 NOT init yet");
  182. return 0;
  183. }
  184. if(lua_isuserdata(L, 1)){
  185. luat_zbuff_t *buff = ((luat_zbuff_t *)luaL_checkudata(L, 1, LUAT_ZBUFF_TYPE));
  186. if (buff->used + RAW_DATA_SIZE > buff->len){
  187. if (__zbuff_resize(buff, buff->used + RAW_DATA_SIZE)){
  188. return 0;
  189. }
  190. }
  191. memcpy(buff->addr + buff->used, ctx->mlx90640To, RAW_DATA_SIZE);
  192. buff->used += RAW_DATA_SIZE;
  193. lua_pushboolean(L , 1);
  194. return 1;
  195. }else{
  196. lua_createtable(L, RAW_DATA_SIZE, 0);
  197. for (size_t i = 0; i < RAW_DATA_SIZE; i++)
  198. {
  199. lua_pushnumber(L, ctx->mlx90640To[i]);
  200. lua_seti(L, -2, i + 1);
  201. }
  202. return 1;
  203. }
  204. return 0;
  205. }
  206. /*
  207. 获取单一点数据
  208. @api mlx90640.raw_point(index)
  209. @int 索引值(0-767)
  210. @return number 单点温度值
  211. */
  212. static int l_mlx90640_raw_point(lua_State *L) {
  213. if (ctx == NULL) {
  214. LLOGE("mlx90640 NOT init yet");
  215. return 0;
  216. }
  217. lua_pushnumber(L, ctx->mlx90640To[luaL_checkinteger(L, 1)]);
  218. return 1;
  219. }
  220. /*
  221. 获取外壳温度
  222. @api mlx90640.ta_temp()
  223. @return number 外壳温度
  224. */
  225. static int l_mlx90640_ta_temp(lua_State *L) {
  226. lua_pushnumber(L, Ta);
  227. return 1;
  228. }
  229. /*
  230. 获取最高温度
  231. @api mlx90640.max_temp()
  232. @return number 最高温度
  233. @return number 最高温度位置
  234. */
  235. static int l_mlx90640_max_temp(lua_State *L) {
  236. float max_temp = -40;
  237. uint16_t index = 0;
  238. if (ctx == NULL) {
  239. LLOGE("mlx90640 NOT init yet");
  240. return 0;
  241. }
  242. for (size_t i = 0; i < RAW_DATA_SIZE; i++){
  243. if (ctx->mlx90640To[i]>max_temp)
  244. {
  245. max_temp = ctx->mlx90640To[i];
  246. index = i;
  247. }
  248. }
  249. lua_pushnumber(L, max_temp);
  250. lua_pushinteger(L, index+1);
  251. return 2;
  252. }
  253. /*
  254. 获取最低温度
  255. @api mlx90640.min_temp()
  256. @return number 最低温度
  257. @return number 最低温度位置
  258. */
  259. static int l_mlx90640_min_temp(lua_State *L) {
  260. float min_temp = 300;
  261. uint16_t index = 0;
  262. if (ctx == NULL) {
  263. LLOGE("mlx90640 NOT init yet");
  264. return 0;
  265. }
  266. for (size_t i = 0; i < RAW_DATA_SIZE; i++){
  267. if (ctx->mlx90640To[i]<min_temp)
  268. {
  269. min_temp = ctx->mlx90640To[i];
  270. index = i;
  271. }
  272. }
  273. lua_pushnumber(L, min_temp);
  274. lua_pushinteger(L, index+1);
  275. return 2;
  276. }
  277. /*
  278. 获取平均温度
  279. @api mlx90640.average_temp()
  280. @return number 平均温度
  281. */
  282. static int l_mlx90640_average_temp(lua_State *L) {
  283. float temp[RAW_DATA_H] = {0};
  284. float temp1=0;
  285. if (ctx == NULL) {
  286. LLOGE("mlx90640 NOT init yet");
  287. return 0;
  288. }
  289. for (size_t j = 0; j < RAW_DATA_H; j++)
  290. {
  291. for (size_t i = 0; i < RAW_DATA_W; i++)
  292. {
  293. temp1 += ctx->mlx90640To[i];
  294. }
  295. temp[j]=temp1/RAW_DATA_W;
  296. temp1 = 0;
  297. }
  298. for (size_t i = 0; i < RAW_DATA_H; i++){
  299. temp1+=temp[i];
  300. }
  301. lua_pushnumber(L, temp1/RAW_DATA_H);
  302. return 1;
  303. }
  304. /*
  305. 获取vdd
  306. @api mlx90640.get_vdd()
  307. @return number 当前vdd
  308. */
  309. static int l_mlx90640_get_vdd(lua_State *L) {
  310. lua_pushnumber(L, vdd);
  311. return 1;
  312. }
  313. static uint8_t * luat_interpolation_double(uint8_t *src, uint16_t rows,uint16_t cols) {
  314. int w1 = cols;
  315. int h1 = rows;
  316. int w2 = w1*2;
  317. //int h2 = h1*2;
  318. uint8_t* dst = (uint8_t*)luat_heap_malloc(rows*cols*4);
  319. // LLOGD("luat_interpolation_double src:%p rows:%d cols:%d rows*cols*4:%d dst:%p ",src,rows,cols,rows*cols*4,dst);
  320. for (size_t y = 0; y < h1; y++){
  321. for (size_t x = 0; x < w1; x++){
  322. dst[y*2*w2+x*2] = src[y*w1+x];
  323. if (x == w1 - 1){
  324. dst[y*2*w2+x*2+1] = (uint8_t)(dst[y*2*w2+x*2]*2-dst[y*2*w2+x*2-1]);
  325. if (y == h1 - 1){
  326. dst[(y*2+1)*w2+x*2] = (uint8_t)(dst[y*2*w2+x*2]*2-dst[(y*2-1)*w2+x*2]);
  327. }else{
  328. dst[(y*2+1)*w2+x*2] = (uint8_t)round((src[y*w1+x]+src[(y+1)*w1+x])/2);
  329. }
  330. }else{
  331. dst[y*2*w2+x*2+1] = (uint8_t)round((src[y*w1+x]+src[y*w1+x+1])/2);
  332. if (y == h1 - 1){
  333. dst[(y*2+1)*w2+x*2] = (uint8_t)(dst[y*2*w2+x*2]*2-dst[(y*2-1)*w2+x*2]);
  334. }else{
  335. dst[(y*2+1)*w2+x*2] = (uint8_t)round((src[y*w1+x]+src[(y+1)*w1+x])/2);
  336. }
  337. }
  338. }
  339. }
  340. for (size_t y = 0; y < h1; y++){
  341. for (size_t x = 0; x < w1; x++){
  342. if ((x == w1 - 1) && (y == h1 - 1)){
  343. dst[(y*2+1)*w2+x*2+1] = (uint8_t)round((dst[(y*2+1)*w2+x*2]+dst[y*2*w2+x*2+1])/2);
  344. }
  345. else if (y == h1 - 1){
  346. dst[(y*2+1)*w2+x*2+1] = (uint8_t)round((dst[(y*2+1)*w2+x*2]+dst[(y*2+1)*w2+x*2+2])/2);
  347. }
  348. else{
  349. dst[(y*2+1)*w2+x*2+1] = (uint8_t)round((dst[y*2*w2+x*2+1]+dst[(y*2+2)*w2+x*2+1])/2);
  350. }
  351. }
  352. }
  353. return dst;
  354. }
  355. static uint8_t * luat_interpolation(uint8_t *src, uint16_t rows,uint16_t cols,uint8_t fold) {
  356. uint8_t* index_data_out1 = NULL;
  357. uint8_t* index_data_out2 = NULL;
  358. for (size_t i = 2; i <= fold; i=i*2){
  359. if (i==2){
  360. index_data_out1 = luat_interpolation_double(src, rows,cols);
  361. luat_heap_free(src);
  362. }else{
  363. if (index_data_out1 == NULL){
  364. index_data_out1 = luat_interpolation_double(index_data_out2, rows,cols);
  365. luat_heap_free(index_data_out2);
  366. index_data_out2 = NULL;
  367. }else{
  368. index_data_out2 = luat_interpolation_double(index_data_out1, rows,cols);
  369. luat_heap_free(index_data_out1);
  370. index_data_out1 = NULL;
  371. }
  372. }
  373. rows = rows*2;
  374. cols = cols*2;
  375. }
  376. if (index_data_out1 != NULL){
  377. return index_data_out1;
  378. }else{
  379. return index_data_out2;
  380. }
  381. }
  382. /*
  383. 绘制到lcd
  384. @api mlx90640.draw2lcd(x, y, fold)
  385. @int 左上角x坐标
  386. @int 左上角y坐标
  387. @int 放大倍数,必须为2的指数倍(1,2,4,8,16...)默认为1
  388. @return bool 成功返回true,否则返回false
  389. */
  390. static int l_mlx90640_draw2lcd(lua_State *L) {
  391. if (lcd_conf == NULL) {
  392. LLOGW("init lcd first!!!");
  393. return 0;
  394. }
  395. uint16_t lcd_x = luaL_optinteger(L, 1 , 0);
  396. uint16_t lcd_y = luaL_optinteger(L, 2 , 0);
  397. uint8_t fold = luaL_optinteger(L, 3 , 1);
  398. uint8_t* index_data = luat_heap_malloc(RAW_DATA_SIZE);
  399. for (size_t i = 0; i < RAW_DATA_SIZE; i++){
  400. float t = ctx->mlx90640To[i];
  401. if (t<MINTEMP) t=MINTEMP;
  402. if (t>MAXTEMP) t=MAXTEMP;
  403. uint8_t colorIndex = (uint8_t)round(map(t, MINTEMP, MAXTEMP, 0, 255));
  404. colorIndex = constrain(colorIndex, 0, 255);
  405. index_data[i] = colorIndex;
  406. }
  407. uint8_t* index_data_out = NULL;
  408. if (fold==1){
  409. index_data_out = index_data;
  410. }else{
  411. index_data_out = luat_interpolation(index_data, RAW_DATA_H,RAW_DATA_W,fold);
  412. }
  413. int index_data_out_w = RAW_DATA_W*fold;
  414. int index_data_out_h = RAW_DATA_H*fold;
  415. luat_color_t *line = luat_heap_malloc(index_data_out_w * sizeof(luat_color_t));
  416. for (size_t y = 0; y < index_data_out_h; y++){
  417. for (size_t x = 0; x < index_data_out_w; x++){
  418. line[x] = color_swap(camColors[index_data_out[y*index_data_out_w + x]]);
  419. }
  420. luat_lcd_draw(lcd_conf, lcd_x, lcd_y+y, lcd_x+index_data_out_w-1, lcd_y+y, line);
  421. }
  422. luat_heap_free(line);
  423. luat_heap_free(index_data_out);
  424. return 0;
  425. }
  426. #include "rotable2.h"
  427. static const rotable_Reg_t reg_mlx90640[] =
  428. {
  429. {"init", ROREG_FUNC(l_mlx90640_init) },
  430. {"feed", ROREG_FUNC(l_mlx90640_feed) },
  431. {"raw_data", ROREG_FUNC(l_mlx90640_raw_data)},
  432. {"raw_point", ROREG_FUNC(l_mlx90640_raw_point)},
  433. {"draw2lcd", ROREG_FUNC(l_mlx90640_draw2lcd)},
  434. {"ta_temp", ROREG_FUNC(l_mlx90640_ta_temp)},
  435. {"max_temp", ROREG_FUNC(l_mlx90640_max_temp)},
  436. {"min_temp", ROREG_FUNC(l_mlx90640_min_temp)},
  437. {"average_temp",ROREG_FUNC(l_mlx90640_average_temp)},
  438. {"get_vdd", ROREG_FUNC(l_mlx90640_get_vdd)},
  439. //@const FPS1HZ number FPS1HZ
  440. { "FPS1HZ", ROREG_INT(FPS1HZ)},
  441. //@const FPS2HZ number FPS2HZ
  442. { "FPS2HZ", ROREG_INT(FPS2HZ)},
  443. //@const FPS4HZ number FPS4HZ
  444. { "FPS4HZ", ROREG_INT(FPS4HZ)},
  445. //@const FPS8HZ number FPS8HZ
  446. { "FPS8HZ", ROREG_INT(FPS8HZ)},
  447. //@const FPS16HZ number FPS16HZ
  448. { "FPS16HZ", ROREG_INT(FPS16HZ)},
  449. //@const FPS32HZ number FPS32HZ
  450. { "FPS32HZ", ROREG_INT(FPS32HZ)},
  451. //@const FPS64HZ number FPS64HZ
  452. { "FPS64HZ", ROREG_INT(FPS64HZ)},
  453. { NULL, ROREG_INT(0) }
  454. };
  455. LUAMOD_API int luaopen_mlx90640( lua_State *L ) {
  456. luat_newlib2(L, reg_mlx90640);
  457. return 1;
  458. }