thermocouple.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. /******************************************************************************
  2. * Additional info
  3. * ****************************************************************************
  4. * Changelog:
  5. * - v. 2.0 Optimize memory usage. Option to use binary search.
  6. * 2016-01-09 by Joakim Soderberg joakim.soderberg@gmail.com
  7. * - v. 1.0 Initial release)
  8. * 2014-04-24 by Albertas Mickėnas mic@wemakethings.net
  9. *
  10. * ****************************************************************************
  11. * Bugs, feedback, questions and modifications can be posted on the github page
  12. * on https://github.com/Miceuz/k-thermocouple-lib/
  13. * ****************************************************************************
  14. * - LICENSE -
  15. * GNU GPL v2 (http://www.gnu.org/licenses/gpl.txt)
  16. * This program is free software. You can redistribute it and/or modify
  17. * it under the terms of the GNU General Public License version 2 as published
  18. * by the Free Software Foundation.
  19. * ****************************************************************************
  20. */
  21. #include <stdio.h>
  22. #include "thermocouple.h"
  23. #ifdef __AVR__
  24. #include <avr/pgmspace.h>
  25. #else
  26. #undef KTHERM_WITH_PROGMEM
  27. #endif
  28. #ifdef KTHERM_TEST
  29. #include <string.h>
  30. #endif
  31. /*
  32. * Values for this table should be multiplied with 1000 when used.
  33. * If we store the table as that instead, we need to use unsigned long
  34. * For example it would need (on an ATmega328):
  35. * 66 * 4 = 264 bytes
  36. * instead of:
  37. * 66 * 2 = 132 bytes
  38. */
  39. #define TEMP_TABLE_SCALE 1000
  40. static const signed int temp_tablek[]
  41. #ifdef KTHERM_WITH_PROGMEM
  42. PROGMEM
  43. #endif
  44. = {
  45. -250,
  46. -240,
  47. -230,
  48. -220,
  49. -210,
  50. -200,
  51. -190,
  52. -180,
  53. -170,
  54. -160,
  55. -150,
  56. -140,
  57. -130,
  58. -120,
  59. -110,
  60. -100,
  61. -90,
  62. -80,
  63. -70,
  64. -60,
  65. -50,
  66. -40,
  67. -30,
  68. -20,
  69. -10,
  70. 0,
  71. 10,
  72. 20,
  73. 30,
  74. 40,
  75. 50,
  76. 60,
  77. 79,
  78. 98,
  79. 116,
  80. 134,
  81. 139,
  82. 155,
  83. 172,
  84. 193,
  85. 212,
  86. 231,
  87. 250,
  88. 269,
  89. 288,
  90. 307,
  91. 326,
  92. 345,
  93. 364,
  94. 383,
  95. 402,
  96. 421,
  97. 440,
  98. 459,
  99. 478,
  100. 497,
  101. 516,
  102. 535,
  103. 554,
  104. 573,
  105. 592,
  106. 611,
  107. 630,
  108. 649,
  109. 668,
  110. 687,
  111. 706,
  112. 725,
  113. 744,
  114. 763,
  115. 782,
  116. 801,
  117. 820,
  118. 839,
  119. 858,
  120. 877,
  121. 896,
  122. 915,
  123. 934,
  124. 953,
  125. 972,
  126. 991,
  127. 1010,
  128. 1029,
  129. 1048,
  130. 1067,
  131. 1086,
  132. 1105,
  133. 1124,
  134. 1143,
  135. 1200
  136. };
  137. #define TEMP_TABLE_SIZEK (sizeof(temp_table) / sizeof(temp_table[0]))
  138. static const signed int volt_tablek[]
  139. #ifdef KTHERM_WITH_PROGMEM
  140. PROGMEM
  141. #endif
  142. = {
  143. -6404,
  144. -6344,
  145. -6262,
  146. -6158,
  147. -6035,
  148. -5891,
  149. -5730,
  150. -5550,
  151. -5354,
  152. -5141,
  153. -4913,
  154. -4669,
  155. -4411,
  156. -4138,
  157. -3852,
  158. -3554,
  159. -3243,
  160. -2920,
  161. -2587,
  162. -2243,
  163. -1889,
  164. -1527,
  165. -1156,
  166. -778,
  167. -392,
  168. 0,
  169. 397,
  170. 798,
  171. 1203,
  172. 1612,
  173. 2023,
  174. 2436,
  175. 3225,
  176. 4013,
  177. 4756,
  178. 5491,
  179. 5694,
  180. 6339,
  181. 7021,
  182. 7859,
  183. 8619,
  184. 9383,
  185. 10153,
  186. 10930,
  187. 11712,
  188. 12499,
  189. 13290,
  190. 14084,
  191. 14881,
  192. 15680,
  193. 16482,
  194. 17285,
  195. 18091,
  196. 18898,
  197. 19707,
  198. 20516,
  199. 21326,
  200. 22137,
  201. 22947,
  202. 23757,
  203. 24565,
  204. 25373,
  205. 26179,
  206. 26983,
  207. 27784,
  208. 28584,
  209. 29380,
  210. 30174,
  211. 30964,
  212. 31752,
  213. 32536,
  214. 33316,
  215. 34093,
  216. 34867,
  217. 35637,
  218. 36403,
  219. 37166,
  220. 37925,
  221. 38680,
  222. 39432,
  223. 40180,
  224. 40924,
  225. 41665,
  226. 42402,
  227. 43134,
  228. 43863,
  229. 44588,
  230. 45308,
  231. 46024,
  232. 46735,
  233. 48838
  234. };
  235. //T
  236. static const signed int temp_tablet[]
  237. #ifdef KTHERM_WITH_PROGMEM
  238. PROGMEM
  239. #endif
  240. = {
  241. -250,
  242. -240,
  243. -230,
  244. -220,
  245. -210,
  246. -200,
  247. -190,
  248. -180,
  249. -170,
  250. -160,
  251. -150,
  252. -140,
  253. -130,
  254. -120,
  255. -110,
  256. -100,
  257. -90,
  258. -80,
  259. -70,
  260. -60,
  261. -50,
  262. -40,
  263. -30,
  264. -20,
  265. -10,
  266. 0,
  267. 10,
  268. 20,
  269. 30,
  270. 40,
  271. 50,
  272. 60,
  273. 79,
  274. 98,
  275. 116,
  276. 134,
  277. 139,
  278. 155,
  279. 172,
  280. 193,
  281. 212,
  282. 231,
  283. 250,
  284. 269,
  285. 288,
  286. 307,
  287. 326,
  288. 345,
  289. 364,
  290. 383,
  291. 400
  292. };
  293. #define TEMP_TABLE_SIZET (sizeof(temp_tablet) / sizeof(temp_tablet[0]))
  294. static const signed int volt_tablet[]
  295. #ifdef KTHERM_WITH_PROGMEM
  296. PROGMEM
  297. #endif
  298. = {
  299. -6180,
  300. -6105,
  301. -6007,
  302. -5888,
  303. -5753,
  304. -5603,
  305. -5439,
  306. -5261,
  307. -5070,
  308. -4865,
  309. -4648,
  310. -4419,
  311. -4177,
  312. -3923,
  313. -3657,
  314. -3379,
  315. -3089,
  316. -2788,
  317. -2476,
  318. -2153,
  319. -1819,
  320. -1475,
  321. -1121,
  322. -757,
  323. -383,
  324. 0,
  325. 391,
  326. 790,
  327. 1196,
  328. 1612,
  329. 2036,
  330. 2468,
  331. 3312,
  332. 4185,
  333. 5036,
  334. 5491,
  335. 5910,
  336. 6956,
  337. 7823,
  338. 8917,
  339. 9930,
  340. 10962,
  341. 12013,
  342. 13082,
  343. 14168,
  344. 15270,
  345. 16387,
  346. 17518,
  347. 18665,
  348. 19825,
  349. 20872
  350. };
  351. #define VOLTAGE_TABLE_SIZEK (sizeof(volt_tablek) / sizeof(volt_tablek[0]))
  352. #define VOLTAGE_TABLE_SIZET (sizeof(volt_tablet) / sizeof(volt_tablet[0]))
  353. #define POINTS_COUNTK (VOLTAGE_TABLE_SIZEK - 1)
  354. #define POINTS_COUNTT (VOLTAGE_TABLE_SIZET - 1)
  355. static inline signed long getTableVal(const signed int *t,
  356. unsigned char i)
  357. {
  358. #ifdef KTHERM_WITH_PROGMEM
  359. return (unsigned long)pgm_read_word_near(t + i);
  360. #else
  361. return (signed long)t[i];
  362. #endif
  363. }
  364. static inline signed long interpolate(signed long val,
  365. signed long rangeStart,
  366. signed long rangeEnd,
  367. signed long valStart,
  368. signed long valEnd)
  369. {
  370. signed long valDiff = valEnd - valStart;
  371. if (valDiff == 0)
  372. return 0;
  373. return rangeStart + (rangeEnd - rangeStart) * (val - valStart) / valDiff;
  374. }
  375. static inline signed long interpolateVoltagek(signed long temp,
  376. signed char i)
  377. {
  378. return interpolate(temp,
  379. getTableVal(volt_tablek, i - 1),
  380. getTableVal(volt_tablek, i),
  381. getTableVal(temp_tablek, i - 1) * TEMP_TABLE_SCALE,
  382. getTableVal(temp_tablek, i) * TEMP_TABLE_SCALE);
  383. }
  384. static inline signed long interpolateVoltaget(signed long temp,
  385. signed char i)
  386. {
  387. return interpolate(temp,
  388. getTableVal(volt_tablet, i - 1),
  389. getTableVal(volt_tablet, i),
  390. getTableVal(temp_tablet, i - 1) * TEMP_TABLE_SCALE,
  391. getTableVal(temp_tablet, i) * TEMP_TABLE_SCALE);
  392. }
  393. static inline signed long interpolateTemperaturek(signed long microvolts,
  394. signed char i)
  395. {
  396. return interpolate(microvolts,
  397. getTableVal(temp_tablek, i - 1) * TEMP_TABLE_SCALE,
  398. getTableVal(temp_tablek, i) * TEMP_TABLE_SCALE,
  399. getTableVal(volt_tablek, i - 1),
  400. getTableVal(volt_tablek, i));
  401. }
  402. static inline signed long interpolateTemperaturet(signed long microvolts,
  403. signed char i)
  404. {
  405. return interpolate(microvolts,
  406. getTableVal(temp_tablet, i - 1) * TEMP_TABLE_SCALE,
  407. getTableVal(temp_tablet, i) * TEMP_TABLE_SCALE,
  408. getTableVal(volt_tablet, i - 1),
  409. getTableVal(volt_tablet, i));
  410. }
  411. static inline unsigned char binarySearchK(const signed int *t,
  412. signed long val,
  413. signed long scale)
  414. {
  415. unsigned char first;
  416. unsigned char middle;
  417. unsigned char last;
  418. unsigned long cur;
  419. // Note that we skip the first item in the list.
  420. first = 1;
  421. last = POINTS_COUNTK - 2;
  422. middle = (first + last) / 2;
  423. while (first <= last)
  424. {
  425. cur = getTableVal(t, middle) * scale;
  426. if (val > cur)
  427. {
  428. first = middle + 1;
  429. }
  430. else if (val == cur)
  431. {
  432. return middle + 1;
  433. }
  434. else
  435. {
  436. last = middle - 1;
  437. }
  438. middle = (first + last) / 2;
  439. }
  440. return last + 1;
  441. }
  442. static inline unsigned char binarySearchT(const signed int *t,
  443. signed long val,
  444. signed long scale)
  445. {
  446. unsigned char first;
  447. unsigned char middle;
  448. unsigned char last;
  449. unsigned long cur;
  450. // Note that we skip the first item in the list.
  451. first = 1;
  452. last = POINTS_COUNTT - 2;
  453. middle = (first + last) / 2;
  454. while (first <= last)
  455. {
  456. cur = getTableVal(t, middle) * scale;
  457. if (val > cur)
  458. {
  459. first = middle + 1;
  460. }
  461. else if (val == cur)
  462. {
  463. return middle + 1;
  464. }
  465. else
  466. {
  467. last = middle - 1;
  468. }
  469. middle = (first + last) / 2;
  470. }
  471. return last + 1;
  472. }
  473. static inline unsigned char linearSearchK(const signed int *t,
  474. signed long val,
  475. signed long scale)
  476. {
  477. unsigned char i;
  478. for (i = 0; i < POINTS_COUNTK; i++)
  479. {
  480. if ((getTableVal(t, i) * scale) > val)
  481. {
  482. return i;
  483. }
  484. }
  485. return POINTS_COUNTK - 1;
  486. }
  487. static inline unsigned char linearSearchT(const signed int *t,
  488. signed long val,
  489. signed long scale)
  490. {
  491. unsigned char i;
  492. for (i = 0; i < POINTS_COUNTT; i++)
  493. {
  494. if ((getTableVal(t, i) * scale) > val)
  495. {
  496. return i;
  497. }
  498. }
  499. return POINTS_COUNTT - 1;
  500. }
  501. /**
  502. * Returns the index of the first point whose temperature
  503. * value is greater than argument
  504. **/
  505. static inline unsigned char searchTempk(signed long temp)
  506. {
  507. #ifdef KTHERM_WITH_BINARY_SEARCH
  508. return binarySearch(temp_table, temp, TEMP_TABLE_SCALE);
  509. #else
  510. return linearSearchK(temp_tablek, temp, TEMP_TABLE_SCALE);
  511. #endif
  512. }
  513. static inline unsigned char searchTempt(signed long temp)
  514. {
  515. #ifdef KTHERM_WITH_BINARY_SEARCH
  516. return binarySearch(temp_table, temp, TEMP_TABLE_SCALE);
  517. #else
  518. return linearSearchT(temp_tablet, temp, TEMP_TABLE_SCALE);
  519. #endif
  520. }
  521. /**
  522. * Returns the index of the first point whose microvolts
  523. * value is greater than argument
  524. **/
  525. static inline unsigned char searchMicrovoltsk(signed long microvolts)
  526. {
  527. #ifdef KTHERM_WITH_BINARY_SEARCH
  528. return binarySearch(volt_table, microvolts, 1);
  529. #else
  530. return linearSearchK(volt_tablek, microvolts, 1);
  531. #endif
  532. }
  533. static inline unsigned char searchMicrovoltst(signed long microvolts)
  534. {
  535. #ifdef KTHERM_WITH_BINARY_SEARCH
  536. return binarySearch(volt_table, microvolts, 1);
  537. #else
  538. return linearSearchT(volt_tablet, microvolts, 1);
  539. #endif
  540. }
  541. /**
  542. * Returns temperature as a function of the ambient temperature
  543. * and measured thermocouple voltage.
  544. * Currently only positive ambient temperature is supported
  545. **/
  546. long thermocoupleConvertWithCJCompensationk(signed long microvoltsMeasured,
  547. signed long ambient)
  548. {
  549. // Convert ambient temp to microvolts
  550. // and add them to the thermocouple measured microvolts
  551. signed long microvolts =
  552. microvoltsMeasured + interpolateVoltagek(ambient, searchTempk(ambient));
  553. // look up microvolts in The Table and interpolate
  554. return interpolateTemperaturek(microvolts, searchMicrovoltsk(microvolts));
  555. }
  556. long thermocoupleConvertWithCJCompensationt(signed long microvoltsMeasured,
  557. signed long ambient)
  558. {
  559. // Convert ambient temp to microvolts
  560. // and add them to the thermocouple measured microvolts
  561. signed long microvolts =
  562. microvoltsMeasured + interpolateVoltaget(ambient, searchTempt(ambient));
  563. // look up microvolts in The Table and interpolate
  564. return interpolateTemperaturet(microvolts, searchMicrovoltst(microvolts));
  565. }
  566. /**
  567. * Returns temperature, equivalent to the voltage provided in microvolts
  568. */
  569. long thermocoupleMvToCk(signed long microvolts)
  570. {
  571. return interpolateTemperaturek(microvolts, searchMicrovoltsk(microvolts));
  572. }
  573. long thermocoupleMvToCt(signed long microvolts)
  574. {
  575. return interpolateTemperaturet(microvolts, searchMicrovoltst(microvolts));
  576. }
  577. #ifdef KTHERM_TEST
  578. int main(int argc, char **argv)
  579. {
  580. unsigned long i = 0;
  581. if (argc < 2)
  582. {
  583. goto usage;
  584. }
  585. for (i = 1; i < argc; i++)
  586. {
  587. if (!strcmp(argv[i], "--temps"))
  588. {
  589. for (i = 0; i < 16383; i++)
  590. {
  591. unsigned long voltage = 5000000 / 16384 * i / 101;
  592. printf("%ld\n", thermocoupleMvToC(voltage));
  593. }
  594. }
  595. else if (!strcmp(argv[i], "--microvolts"))
  596. {
  597. for (i = 0; i < 1280000; i+= 1000)
  598. {
  599. printf("%ld\n", interpolateVoltage(i, searchTemp(i)));
  600. }
  601. }
  602. else
  603. {
  604. fprintf(stderr, "Unknown option %s\n", argv[i]);
  605. goto usage;
  606. }
  607. }
  608. return 0;
  609. usage:
  610. printf("K-Type Thermocouple Library Tests\n");
  611. printf(" Usage: %s [--microvolts] [--temps]\n", argv[0]);
  612. return -1;
  613. }
  614. #endif