Curve fit for sensor equation - Page 1
Forum section: electronics / beginners

author: Vindhyachal.takniki
author_rank: Frequent Contributor
timestamp: July 08, 2020, 06:30:18 am
content: 1. Have a thermistor, data whose dataset is available not anything else from company except B value. Attached is data.2. Trying to make a equation for it and implement for 8 bit MCU, usin IAR IDE.3. First I made this code, steinhart thermistor equation. calculated values of A,B,C from this site:https://www.thinksrs.com/downloads/programs/therm%20calc/ntccalibrator/ntccalculator.html. Put three values at -20C, 25C & 100C and get A,B,CCode:uint8_t get_ntc_temperature_4(float32_t resistance , float32_t *tempearture_c){uint8_t err = 0U;float32_t x;float32_t x2;float32_t x3;if( (resistance <= 956040.0f) && (resistance >= 6683.0f) ){x = logf(resistance);x2 = x * x;x3 = x2 * x;x = 0.000641295533f + (0.0002233488434f * x) + (0.0000000926090998f * x3);x = 1.0f / x;*tempearture_c = x - 273.15f;}else{err = 1U;}return err;}4. Then made equation using curve fit tool and below is code:Code:uint8_t get_ntc_temperature_2(float32_t resistance , float32_t *tempearture_c){uint8_t err = 0U;float32_t c0;float32_t c1;float32_t c2;if( (resistance <= 956040.0f) && (resistance >= 482170.0f) ){c0 = 14.5029518320319131f;c1 = -0.000057344817030149406f;c2 = 0.000000000021788855747503695f;}else if(resistance >= 250140.0f){c0 = 29.3136847463533279f;c1 = -0.000119496884518122221f;c2 = 0.0000000000876747221220605721f;}else if(resistance >= 130230.0f){c0 = 45.3985586636038647f;c1 = -0.000249401966454676711f;c2 = 0.000000000352768556658954028f;}else if(resistance >= 71230.0f){c0 = 62.1448935476119572f;c1 = -0.000506560472110208432f;c2 = 0.00000000135019658772898851f;}else if(resistance >= 39200.0f){c0 = 79.7753263268828902f;c1 = -0.00100556530593662709f;c2 = 0.00000000491385322365418885f;}else if(resistance >= 21830.0f){c0 = 98.8979251125107339f;c1 = -0.00198872984475097995f;c2 = 0.0000000176669521860767602f;}else if(resistance >= 12750.0f){c0 = 118.809289117525665f;c1 = -0.00381338043851388829f;c2 = 0.0000000598289053823529209f;}else if(resistance >= 7327.0f){c0 = 140.313484336487619f;c1 = -0.00722784306211046376f;c2 = 0.000000196440993753396293f;}else if(resistance >= 6683.0f){c0 = 133.215333415477943f;c1 = -0.00480709971651089099f;c2 = 0.0f;}else{err = 1U;}if(0U == err){*tempearture_c = c0 + (c1 * resistance) + (c2 * resistance * resistance);}return err;}5. Size build on full size optimization on IAR for 8 bit MCU:a) No function call:5 138 bytes of readonly code memory139 bytes of readonly data memory357 bytes of readwrite data memoryb) With polynomial equation fit:5 593 bytes of readonly code memory139 bytes of readonly data memory357 bytes of readwrite data memoryc) With natural log:5 820 bytes of readonly code memory139 bytes of readonly data memory359 bytes of readwrite data memory6. Noticed that with polynomial fit extra space is 455 bytes & with natural log its 682 bytes.7. Error with polynomial fit is +0.069 to -0.098C.Error with natural log is slightly more though.8. Any other better method to implement or curve fit this data? Max end function accuracy I am looking is +-0.099C.PS: In function call, I got coefficients generated in float64_t in curve fit toolbox, put f in the end though.I have enough time for float operations and circuit is main powered so no issues in processing time, only thing constaint is size, lesser the better.

author: mikerj
author_rank: Super Contributor
timestamp: July 08, 2020, 07:00:59 am
content: Use a lookup table with linear interpolation and use integer maths rather than floating point. You should be able to significantly reduce your code size and greatly increase execution speed. If your working range is -20 to +100C you can store the temperature points scaled by 256 in 16 bit integers. Use a spreadsheet etc. to determine how many points you need by looking at the maximum interpolation error.What is the tolerance on your thermistor, both the 25C resistance and the Beta value? These typically give quite a spread of resistance over the usable range so you are unlikely to get +-0.099C absolute accuracy without calibrating individual thermistors and the surrounding circuitry. You probably need to start thinking about fixed resistor tempcos at that point as well. Getting a resolution of better than 0.1C should be trivial however, assuming your ADC resolution is adequate.Note that the majority of your code size will be taken up by the floating point library, so it's pointless optimising your temperature function if you then use floating point in other parts of the code. In general avoid the use of floating point on small micros if possible, it's hardly ever needed.

author: DrG
author_rank: Super Contributor
timestamp: July 08, 2020, 01:26:33 pm
content: I have a lot more inferential statistics experience outside of any EE area than inside, but let me throw something out there for you to consider.It looks like you are doing some curve fitting using a series of 2nd order polynomials. IOW, you are breaking up the data into portions and fitting each portion with a quadratic and then doing a series of if-then statement to determine which equation to use.Suppose you fit the entire data set with a higher order polynomial, say a 6th degree polynomial, which looks like this:and has these coefficients:Coefficients:b 319023.3927b -16078.1138533528b 464.2957270671b -9.3632830266b 0.1228073789b -8.9327053559e-4b 2.6697588945e-6and the r2 is very close to 1.0r ² 0.9999876017instead of using an equation for the function, you use a table (as was mentioned) [edit: that you generate from the function]:Here is one with10051 values of temp-resistance pairsCode:xf(x)-20923888.7357-17.6810236.3389-15.2710876.7513-12.8624144.9423-10.4548535.1534-8482690.2205-5.6425391.2633-3.2375547.7412-0.8332187.87761.6294449.45114261570.95376.4232883.11728.8207800.805911.2185815.277813.6166486.811716149437.703418.4134345.627420.8120937.367623.2108982.914325.698289.92845595242888698.574389726630.480076.718569423232.872315.496477718535.265325.24658958637.659031.81172469664053373.20774300542.448296.65958352544.843756.004646287647.239709.463517488249.636117.77803781915232942.716713988354.430145.947473424356.827688.277762169259.225529.261985955961.623627.17629447066421939.360708808466.420422.92859210568.819035.843463363871.217738.363154463273.616494.85131035587615275.956232448278.414061.157065171980.812841.67732573683.211623.765777070785.610432.3446439588889315.025172338690.48346.490531820192.87633.246061359395.27318.736858140297.67588.83270962541008677.6803688132and look up the value. The chore of the program is to interpolate. again as mentioned (rather than just take a midpoint or something like that), between the value above and below the observed value.Edit: Fixed the attachment that got automagically changed

author: 0-8-15 User
author_rank: Regular Contributor
timestamp: July 08, 2020, 01:36:08 pm
content: +1 on the linear interpolation approach. This should allow you to get away without any floating point math and just 35 supporting points in order to meet your 0.1 C accuracy requirement.Code:6683 101.1478717768 96.0486779028 91.07577710494 86.21535512197 81.47099214176 76.83657516477 72.30576519151 67.87731422260 63.54557925872 59.31044430071 55.1659534952 51.10950140625 47.13874447218 43.25112454882 39.4430663789 35.71284974142 32.05751686175 28.474949100162 24.962567116418 21.518715135312 18.141132157274 14.827561182799 11.57652212468 8.385855246951 5.254072287031 2.179332333617 -0.84001387763 -3.805393450697 -6.718374523845 -9.580382608865 -12.392813707684 -15.157009822541 -17.87424956040 -20.545768

author: Renate
author_rank: Super Contributor
timestamp: July 08, 2020, 11:49:31 pm
content: How many bits is your ADC? (Did I miss that? Are you using time constant to measure resistance?)You have to look at your precision loss there too.10 bit lookup is only 2k ROM and as fast as you can get.Floating point is not that great on an 8 bit processor.float (vs. double) is also pointless as you only have about 24 bit accuracy.If you know what you're dealing with 32 bit fixed point makes sense.(Or even 24 bit fixed point if you want to deal with int24_t.)Me, I'd use a digital I²C sensor.

author: Nominal Animal
author_rank: Super Contributor
timestamp: July 09, 2020, 01:18:11 am
content: If the number of unique temperature values displayed is smaller than the range of values your ADC produces – and this is probably true! –, instead of storing the temperatures corresponding to each ADC reading, I'd store the smallest ADC reading that corresponds to a specific displayed temperature, then use a binary search to look up the correct interval.For example, if the valid temperature range is -99.9° to 999.9°, you have 11000 distinct outputs (-99.9, -99.8, -99.7, ..., -0.1, -0.0, 0.0, 0.1, ..., 999.9).Let's say you haveCode:#define NUM_OUTPUTS 11000static const uint16_t adc_limit; /* 22000 bytes of flash/ROM */then a binary search will provide the correct output quite efficiently. Here, we assume the limits decrease monotonically, i.e. adc_limit > adc_limit > adc_limit and so on:Code:uint16_t output_for_adc(uint16_t value){uint16_t above, below, middle, limit;below = 0;if (value > adc_limit)return 0; /* Actually, -1, but we fold this to zero. */above = NUM_OUTPUTS - 1;if (value <= adc_limit)return above;while (1) {middle = below + (uint16_t)(above - below) / 2;if (middle == below)return below;limit = adc_limit;if (value < limit)below = value;elseif (value > limit)above = value;elsereturn middle;}}For a range of 22000 values, the above does at most ceil(log(range)/log(2)) = 15 iterations of the while loop. The calculation of the middle index is in a form that avoids overflow, even if both above and below were over 32768.The idea is that ifvalue <= adc_limit && value > adc_limit[i+1], then the function returns i. i then refers to the i'th possible output value (so 0 for -99.9, 1 for -99.8, and so on.)Many of the 8-bit ADCs don't have a 16-bit division, or it is slow. To convert an ADC value to a string, -99.9 to 999.9, I would useCode:void temperature_string(char *to, uint16_t adc_reading){uint16_t u, i;unsigned char d;i = adc_output_for(adc_reading);if (u <= 999) {/* 0..999 map to -999..-0 */*(to++) = '-';i = 999 - u;} else {/* 1000.. map to 0.0... */u = i - 1000;}if (u < 100) goto d_0;if (u < 1000) goto d_00;if (u < 10000) goto d_000;d_0000:d = '0'; while (u >= 10000) { u -= 10000; d++; }*(to++) = d;d_000:d = '0'; while (u >= 1000) { u -= 1000; d++; }*(to++) = d;d_00:d = '0'; while (u >= 100) { u -= 100; d++; }*(to++) = d;d_0:d = '0'; while (u >= 10) { u -= 10; d++; }*(to++) = d;*(to++) = '.';*(to++) = '0' + u;*(to++) = '\0';return;}or similar to expand the looked-up value to a string. If you dislike the use of goto here, you can replace it with a Duff's device-like switch statement, using an extra variable to indicate the number of significant digits in u.Note how the unsigned integer (interval in adc_limit that matches adc_reading) is converted to the displayed numeric value u, and there are always at least two least significant decimal digits, with a decimal point in between.This way, you could use as precise interpolation of the measured values as you want; just do the inverse, and output the source value corresponding to temperatures midway between each displayed value (except exactly at zero for the limit between -0.0 and 0.0), and store them in adc_limit.

author: 0-8-15 User
author_rank: Regular Contributor
timestamp: July 09, 2020, 09:55:56 am
content: @Nominal Animal, I love that idea.

author: Vindhyachal.takniki
author_rank: Frequent Contributor
timestamp: July 09, 2020, 11:14:18 am
content: Mathematically it is working in range of +-0.0999.Hardware i am checking .LUT takes lot of memory though, I tried on -20 to 100C. Storing resistance values itself takes more memory than what what polynomial function taking at the moment. Since i dont have limitation on processing time, leaving LUT for time being, I have limited memory.Or LUT can be optimized further? This is 121 values, each values is 4 bytes of resistance so means 484 bytes itself while my polynmial codes takes only 455 with all the processing. Then will have to implement interploation also which includes division also.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 09, 2020, 11:51:49 am
content: This is hopeless:The 1st order S-H function fits poorly to most parts. Probably it's outside of the rated tolerance by some modest temperature difference (say 20°C from each crossing point??).The S-H method is a method of approximation,not a fundamental equation modeling the device.The higher order terms are necessary to get a good fit to real parts; but you basically have to solve for those if you don't have the parameters.As you've discovered, it's not a good method for embedded (integer, small bus) processors.NTC thermistors aren't good for much better than 1% anyway, and even those start to get pricey. An RTD or thermocouple is preferred.If you just need 0.1°C resolution but not accuracy, okay I guess, but it really doesn't mean anything without an experimental setup or calibration. The LSBs might as well be noise. What does it matter whether it's here or there, if the actual reading is in error by 5°C or more?So, that said -- the most compact solution is a polynomial. If your platform has hardware 8x8 (or better) multiply, the function can equal the accuracy of a 12-bit ADC in perhaps 200 instruction cycles.If it doesn't have hardware multiply, having to use a software multiply will about double this figure. Software division is a very similar algorithm, and may prove more attractive at this point.Long-form investigation here:https://www.seventransistorlabs.com/Calc/Thermistor.htmlThis also explains why a piecewise method is likely a poor idea. Either you need to calculate a division or two (expensive, hundreds of cycles), or you need to store alotof data to get a good enough fit.A higher order polynomial is of course possible, costing some more execution time, and just a few words of additional code space. Probably 2 or 4 additional steps would become useless due to quantization noise, at which point, higher bit length is required -- 24 or 32 bit operands, for which the execution time will go up substantially.BTW, this sort of correction is typical on RTDs too; a Pt100 or similar part, for example, has a very well characterized curve, and because the curvature is slight, a low order polynomial will suffice.TimP.S. Oh hey, I should update that page, now that I've done some AVR assembler in GCC. The ABI and register usage doesn't match, and the labels need to be set to local and global as appropriate; then with a header declaring the function type, it can be called seamlessly from C. Think I would also inline thetmprmaccall in a loop, and do the mid-loop adjustment on the appropriate pass. That saves theRCALL/REToverhead, saving a cool 50 cycles or so. Can also write it in native C easily enough, not that it'll be very fast even on -O3, as avr-gcc will use library calls for multiplication (never inlined), and will probably make dumb register allocations.

author: mikerj
author_rank: Super Contributor
timestamp: July 09, 2020, 02:01:33 pm
content: Quote from: T3sl4co1l on July 09, 2020, 11:51:49 amDepends on how you do it. With a table of temperatures arranged with an ADC interval which is a power of 2 then the division reduces to shifts and the interpolation is fast.

author: Renate
author_rank: Super Contributor
timestamp: July 09, 2020, 07:16:55 pm
content: Would you kindly tell me:1) What method are you using to get a resistance? (uPs don't naturally measure resistance, they might have ADCs though).2) How many bits is your input resolution?

author: Vindhyachal.takniki
author_rank: Frequent Contributor
timestamp: July 19, 2020, 06:29:14 am
content: My thermistor equation works ok mathematically. Now I want to fit equation for thermopile. PFA its data-sheet, excel data points and text file(which is tab separated values for area of interest around 38301 values).In text file: We are able to measure :a) Thermopile mV (column 3)b) NTC temperature(column 2)c) Need to find object temperature from above two variables.So need to make equation: Z= F(x,y)We asked the company manufacturer for equation here is what they have replied: "The table is built in 25 degree C. The others are generated via mathematical formula which is confidential."So need to make equation on own.Page 6 of pdf has a formula, but it doesn't fit the formula given. If we put Vtp, Tobj & Ta , K values are always different for any given data set.I made the equation: its third order equation contains 10 coefficients with x & y powers.But had to divide entire thermopile range into 13 parts like : -50 to 7C , 7C to 58C and so on upto 700C. Each of these 13 sets have different 3 order coefficients to fit, within error range of +-0.09999.This is purely mathematical model required here, anyone has idea on what is there any equation which fits as manufacturer mentions but didn't disclose.

author: Vindhyachal.takniki
author_rank: Frequent Contributor
timestamp: July 20, 2020, 03:25:58 pm
content: This formula doesn't exactly fit in dataset given.Vtp = K(Tobj^4 - Tnytc^4)For example:1. If I take set no 1 from text file from my previous post:Tobj = -50+273.15KNTC = 0+ 273.15KVtp = -2.90303987541749/1000 voltsvalues of K comes out to be K : 9.40360645303949000000E-132. If I take set no 61 from text fileTobj = -49+273.15KNTC = 9+ 273.15KVtp = -3.5222018314122336/1000 voltsvalues of K comes out to be K : 9.23698403345936000000E-132. If I take set no 8053 from text fileTobj = 107+273.15KNTC = 45+ 273.15KVtp = 8.260617415613451/1000 voltsvalues of K comes out to be K : 7.76451812140848000000E-13Similarly I tried plotting graph of K calculated from all 38301 values given: this is what I get for first 32000 values graph(excel limitation)Here is its graph:https://forum.allaboutcircuits.com/attachments/1595256164157-png.212591/As seen value of K keeps on falling for given dataset.I have attached excel also on which I had calculated K values.Those falling to zero are actually diviide by zero error, where Tobj & Tamb equals each other.

author: 0-8-15 User
author_rank: Regular Contributor
timestamp: July 21, 2020, 02:12:59 pm
content: Quote from: Vindhyachal.takniki on July 19, 2020, 06:29:14 amI would split the problem into two.Step 1: Find a function F1 that describes Tobj as a function of Vtp (basically assume that NTC is always 0).Step 2: Find a function F2 that describes the Vtp offset as a function of NTC.The final calculation will then be Tobj = F1(vtp + F2(ntc)).However I am not sure if you will be able to find an easy to handle formula (with just a handful of coefficients) that will fit the entire curve.

author: Renate
author_rank: Super Contributor
timestamp: July 22, 2020, 12:21:36 am
content: You folks have seriously gone down the rabbit hole with your 10 sig digs constants.How are you even getting the resistance measurements?I presume an 8-1/2 digit Keithley using 4 wire measurement by the way you are going about this.